[
  {
    "path": ".clang-format",
    "content": "﻿---\nBasedOnStyle: Google\nIndentWidth: '4'\nDerivePointerAlignment: false\nPointerAlignment: Middle\nColumnLimit: 160\nAllowShortFunctionsOnASingleLine: false\nAllowShortBlocksOnASingleLine: 'false'\nAllowShortCaseLabelsOnASingleLine: 'false'\nAllowShortIfStatementsOnASingleLine: 'false'\nAllowShortLoopsOnASingleLine: 'false'\nSpaceAfterCStyleCast: 'true'\n\n...\n"
  },
  {
    "path": ".github/workflows/build.yml",
    "content": "name: CI\non: [push]\n\njobs:\n\n    ubuntu-24_04-python3-ggwave-deb:\n        runs-on: ubuntu-24.04\n\n        steps:\n            - name: Clone\n              uses: actions/checkout@v1\n              with:\n                  submodules: recursive\n\n            - name: Dependencies\n              run: |\n                  sudo apt update\n                  sudo apt install cython3 build-essential debhelper-compat\n                  sudo apt install dh-python python3-all-dev python3-build\n                  sudo apt install python3-cogapp python3-venv\n\n            - name: Build Debian package for Python bindings\n              run: |\n                cd bindings/python\n                make deb\n\n    ubuntu-24_04-libggwave-dev-deb:\n        runs-on: ubuntu-24.04\n        strategy:\n            matrix:\n                build: [Release]\n\n        steps:\n            - name: Clone\n              uses: actions/checkout@v1\n              with:\n                  submodules: recursive\n\n            - name: Dependencies\n              run: |\n                  sudo apt update\n                  sudo apt install build-essential\n\n            - name: Build Debian package for libggwave\n              run: |\n                cmake . -DGGWAVE_BUILD_EXAMPLES=OFF -DCMAKE_BUILD_TYPE=${{ matrix.build }}\n                make\n                cpack\n\n            - name: Inspect generated Debian package\n              run: |\n                dpkg --contents dist/libggwave-dev_*.deb\n                dpkg -I dist/libggwave-dev_*.deb\n                sha256sum dist/libggwave-dev_*.deb\n\n    ubuntu-24_04-target-deb:\n        runs-on: ubuntu-24.04\n        strategy:\n            matrix:\n                build: [Release]\n\n        steps:\n            - name: Clone\n              uses: actions/checkout@v1\n              with:\n                  submodules: recursive\n\n            - name: Dependencies\n              run: |\n                  sudo apt update\n                  sudo apt install cython3 build-essential debhelper-compat\n                  sudo apt install dh-python python3-all-dev python3-build\n                  sudo apt install python3-cogapp python3-venv\n\n            - name: Build Debian packages\n              run: |\n                cmake . -DGGWAVE_BUILD_EXAMPLES=OFF -DCMAKE_BUILD_TYPE=${{ matrix.build }}\n                make deb\n\n            - name: Inspect generated Debian packages\n              run: |\n                dpkg --contents dist/libggwave*.deb\n                dpkg -I dist/libggwave*.deb\n                dpkg --contents dist/python3*.deb\n                dpkg -I dist/python3*.deb\n                sha256sum dist/*.deb\n\n    ubuntu-24_04-gcc:\n        runs-on: ubuntu-24.04\n\n        strategy:\n            matrix:\n                build: [Debug, Release]\n\n        steps:\n            - name: Clone\n              uses: actions/checkout@v1\n              with:\n                  submodules: recursive\n\n            - name: Dependencies\n              run: |\n                  sudo apt update\n                  sudo apt install build-essential xorg-dev libglu1-mesa-dev\n                  sudo apt install cmake\n                  sudo apt install libsdl2-dev\n\n            - name: Configure\n              run: cmake . -DCMAKE_BUILD_TYPE=${{ matrix.build }}\n\n            - name: Build\n              run: |\n                make\n                ctest --output-on-failure\n                ./bin/test-ggwave-cpp --full\n\n    ubuntu-24_04-python:\n        runs-on: ubuntu-24.04\n\n        strategy:\n            fail-fast: false\n            matrix:\n                build: [Release]\n                python-version: [3.8.18, 3.9.20, 3.11.11, 3.12.9]\n\n        steps:\n            - name: Clone\n              uses: actions/checkout@v1\n              with:\n                  submodules: recursive\n\n            - name: Set up Python ${{ matrix.python-version }}\n              uses: actions/setup-python@v2\n              with:\n                  python-version: ${{ matrix.python-version }}\n\n            - name: Dependencies\n              run: |\n                  sudo apt update\n                  sudo apt install build-essential\n                  sudo apt install cmake\n                  python3 -m venv venv\n                  source venv/bin/activate && pip install setuptools cython cogapp\n\n            - name: Configure\n              run: cmake . -DGGWAVE_SUPPORT_PYTHON=ON -DGGWAVE_BUILD_EXAMPLES=OFF -DCMAKE_BUILD_TYPE=${{ matrix.build }}\n\n            - name: Build\n              run: |\n                source venv/bin/activate\n                make\n                ctest --output-on-failure\n\n    ubuntu-24_04-gcc-sanitized:\n        runs-on: ubuntu-24.04\n\n        strategy:\n            matrix:\n                sanitizer: [ADDRESS, THREAD, UNDEFINED]\n\n        steps:\n            - name: Clone\n              uses: actions/checkout@v1\n              with:\n                  submodules: recursive\n\n            - name: Dependencies\n              run: |\n                  sudo apt update\n                  sudo apt install build-essential\n                  sudo apt install cmake\n\n            - name: Configure\n              run: cmake . -DCMAKE_BUILD_TYPE=Debug -DGGWAVE_BUILD_EXAMPLES=OFF -DGGWAVE_SANITIZE_${{ matrix.sanitizer }}=ON\n\n            - name: Build\n              run: |\n                make\n                ctest --output-on-failure\n\n    ubuntu-24_04-clang:\n        runs-on: ubuntu-24.04\n\n        strategy:\n            matrix:\n                build: [Release]\n\n        steps:\n            - name: Clone\n              uses: actions/checkout@v1\n              with:\n                  submodules: recursive\n\n            - name: Dependencies\n              run: |\n                  sudo apt update\n                  sudo apt install build-essential xorg-dev libglu1-mesa-dev\n                  sudo apt install cmake\n                  sudo apt install libsdl2-dev\n\n            - name: Configure\n              run: cmake . -DCMAKE_BUILD_TYPE=${{ matrix.build }} -DCMAKE_CXX_COMPILER=clang++ -DCMAKE_C_COMPILER=clang\n\n            - name: Build\n              run: |\n                make\n                ctest --output-on-failure\n\n    macOS-latest:\n        runs-on: macOS-latest\n\n        strategy:\n            matrix:\n                build: [Release]\n\n        steps:\n            - name: Clone\n              uses: actions/checkout@v1\n              with:\n                  submodules: recursive\n\n            - name: Dependencies\n              run: |\n                  brew update\n                  brew install sdl2\n\n            - name: Configure\n              run: cmake . -DCMAKE_BUILD_TYPE=${{ matrix.build }}\n\n            - name: Build\n              run: |\n                make\n                ctest --output-on-failure\n\n    emscripten:\n        runs-on: ubuntu-24.04\n\n        strategy:\n            matrix:\n                build: [Release]\n\n        steps:\n            - name: Clone\n              uses: actions/checkout@v1\n              with:\n                  submodules: recursive\n\n            - name: Dependencies\n              run: |\n                wget -q https://github.com/emscripten-core/emsdk/archive/master.tar.gz\n                tar -xvf master.tar.gz\n                emsdk-master/emsdk update\n                emsdk-master/emsdk install latest\n                emsdk-master/emsdk activate latest\n\n            - name: Configure\n              run: echo \"tmp\"\n\n            - name: Build\n              run: |\n                pushd emsdk-master\n                source ./emsdk_env.sh\n                popd\n                emcmake cmake . -DCMAKE_BUILD_TYPE=${{ matrix.build }}\n                make && ctest --output-on-failure\n\n    windows-msys2:\n      runs-on: windows-latest\n      defaults:\n        run:\n          shell: msys2 {0}\n      strategy:\n          matrix:\n              build: [Release]\n      steps:\n        - name: Clone\n          uses: actions/checkout@v1\n          with:\n            submodules: recursive\n\n        - name: Dependencies\n          uses: msys2/setup-msys2@v2\n          with:\n            msystem: MINGW64\n            update: true\n            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\n\n        - name: Configure\n          run: cmake . -DCMAKE_BUILD_TYPE=${{ matrix.build }} -G \"Unix Makefiles\"\n\n        - name: Build\n          run: |\n            ls -alh .\n            make\n            ctest --output-on-failure\n"
  },
  {
    "path": ".gitignore",
    "content": "build\nbuild-*\ncompile_commands.json\n.exrc\n.clangd\n.cache\n.vimspector.json\n.*.swp\n.ycm_extra_conf.py\n.DS_Store\n"
  },
  {
    "path": ".gitmodules",
    "content": "[submodule \"examples/third-party/imgui/imgui\"]\n\tpath = examples/third-party/imgui/imgui\n\turl = https://github.com/ocornut/imgui\n[submodule \"examples/third-party/ggsock\"]\n\tpath = examples/third-party/ggsock\n\turl = https://github.com/ggerganov/ggsock\n[submodule \"bindings/ios\"]\n\tpath = bindings/ios\n\turl = https://github.com/ggerganov/ggwave-spm\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "# Changelog\n\n## [Unreleased]\n\n## [v0.4.0] - 2022-07-05\n\n**This release introduces some breaking changes in the C and C++ API!**\n\nMake sure to read the `ggwave.h` header for more information\n\n- Major refactoring in order to support microcontrollers ([#65](https://github.com/ggerganov/ggwave/pull/65)\n- Zero memory allocations during runtime\n- Do not include STL headers anymore\n- New, low-frequency, mono-tone (MT) protocols suitable for microcontrollers\n- Remove code-duplication for some of the examples\n- Better FFT implementation\n- Less memory usage\n- Bug fix in fixed-length payload decoding\n- Add Arduino and ESP32 examples\n- Support for Direct Sequence Spread (DSS)\n\n## [v0.3.1] - 2021-11-27\n\n- Add interface for changing ggwave's internal logging ([#52](https://github.com/ggerganov/ggwave/pull/52), [#55](https://github.com/ggerganov/ggwave/pull/55))\n- Fix out-of-bounds access in `ggwave_decode` ([#53](https://github.com/ggerganov/ggwave/pull/53))\n- Add C interface for selecting Rx protocols ([#60](https://github.com/ggerganov/ggwave/pull/60))\n\n## [v0.3.0] - 2021-07-03\n\n- Resampling fixes\n- Add `soundMarkerThreshold` parameter ([f4fb02d](https://github.com/ggerganov/ggwave/commit/f4fb02d5d4cfd6c1021d73b55a0e52ac9d3dbdfa))\n- Sampling rates are now consistently represented as float instead of int\n- Add option to query the generated tones ([ba87a65](https://github.com/ggerganov/ggwave/commit/ba87a651e3e27ce3fa9a85d53ca988a0cedd2e46))\n- Fix python build on Windows ([d73b184](https://github.com/ggerganov/ggwave/commit/d73b18426bf0df0e610c31c948e0ddf9a0784073))\n\n## [v0.2.0] - 2021-02-20\n\n- Supported sampling rates: 6kHz - 96kHz\n- Variable-length payloads\n- Fixed-length payloads (no sound markers emitted)\n- Reed-Solomon based ECC\n- Ultrasound support\n\n[unreleased]: https://github.com/ggerganov/ggwave/compare/ggwave-v0.4.0...HEAD\n[v0.4.0]: https://github.com/ggerganov/ggwave/releases/tag/ggwave-v0.4.0\n[v0.3.1]: https://github.com/ggerganov/ggwave/releases/tag/ggwave-v0.3.1\n[v0.3.0]: https://github.com/ggerganov/ggwave/releases/tag/ggwave-v0.3.0\n[v0.2.0]: https://github.com/ggerganov/ggwave/releases/tag/ggwave-v0.2.0\n"
  },
  {
    "path": "CMakeLists.txt",
    "content": "cmake_minimum_required (VERSION 3.10)\nproject(ggwave VERSION 0.4.2)\n\nset(GGWAVE_VERSION_PYTHON 0.4.2)\n\nset(CMAKE_EXPORT_COMPILE_COMMANDS \"on\")\nset(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)\nset(CMAKE_INSTALL_RPATH \"${CMAKE_INSTALL_PREFIX}/lib\")\n\nif(CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR)\n    set(GGWAVE_STANDALONE ON)\n    include(cmake/GitVars.cmake)\n    include(cmake/BuildTypes.cmake)\n\n    # configure project version\n    configure_file(${CMAKE_SOURCE_DIR}/README-tmpl.md                        ${CMAKE_SOURCE_DIR}/README.md @ONLY)\n    configure_file(${CMAKE_SOURCE_DIR}/bindings/python/setup-tmpl.py         ${CMAKE_SOURCE_DIR}/bindings/python/setup.py @ONLY)\n    configure_file(${CMAKE_SOURCE_DIR}/bindings/javascript/package-tmpl.json ${CMAKE_SOURCE_DIR}/bindings/javascript/package.json @ONLY)\n    configure_file(${CMAKE_SOURCE_DIR}/bindings/ios/Makefile-tmpl            ${CMAKE_SOURCE_DIR}/bindings/ios/Makefile @ONLY)\nelse()\n    set(GGWAVE_STANDALONE OFF)\nendif()\n\nif (EMSCRIPTEN)\n    set(GGWAVE_SUPPORT_SDL2_DEFAULT ON)\n    set(BUILD_SHARED_LIBS_DEFAULT OFF)\n\n    option(GGWAVE_WASM_SINGLE_FILE \"ggwave: embed WASM inside the generated ggwave.js\" ON)\nelse()\n    set(GGWAVE_SUPPORT_SDL2_DEFAULT ON)\n    if (WIN32)\n        set(BUILD_SHARED_LIBS_DEFAULT OFF)\n    else()\n        set(BUILD_SHARED_LIBS_DEFAULT ON)\n    endif()\nendif()\n\n# options\n\noption(BUILD_SHARED_LIBS              \"ggwave: build shared libs\" ${BUILD_SHARED_LIBS_DEFAULT})\noption(USE_FINDSDL2                   \"ggwave: use the FindSDL2.cmake script\" OFF)\n\noption(GGWAVE_ALL_WARNINGS            \"ggwave: enable all compiler warnings\" ON)\noption(GGWAVE_ALL_WARNINGS_3RD_PARTY  \"ggwave: enable all compiler warnings in 3rd party libs\" ON)\n\noption(GGWAVE_SANITIZE_THREAD         \"ggwave: enable thread sanitizer\" OFF)\noption(GGWAVE_SANITIZE_ADDRESS        \"ggwave: enable address sanitizer\" OFF)\noption(GGWAVE_SANITIZE_UNDEFINED      \"ggwave: enable undefined sanitizer\" OFF)\n\noption(GGWAVE_SUPPORT_SDL2            \"ggwave: support for libSDL2\" ${GGWAVE_SUPPORT_SDL2_DEFAULT})\noption(GGWAVE_SUPPORT_PYTHON          \"ggwave: support for python\" OFF)\noption(GGWAVE_SUPPORT_SWIFT           \"ggwave: support for swift\" OFF)\n\noption(GGWAVE_BUILD_TESTS             \"ggwave: build examples\" ${GGWAVE_STANDALONE})\noption(GGWAVE_BUILD_EXAMPLES          \"ggwave: build examples\" ${GGWAVE_STANDALONE})\n\n# sanitizers\n\nif (GGWAVE_SANITIZE_THREAD)\n    set(CMAKE_C_FLAGS \"${CMAKE_C_FLAGS} -fsanitize=thread\")\n    set(CMAKE_CXX_FLAGS \"${CMAKE_CXX_FLAGS} -fsanitize=thread\")\nendif()\n\nif (GGWAVE_SANITIZE_ADDRESS)\n    set(CMAKE_C_FLAGS \"${CMAKE_C_FLAGS} -fsanitize=address -fno-omit-frame-pointer -D_GLIBCXX_DEBUG\")\n    set(CMAKE_CXX_FLAGS \"${CMAKE_CXX_FLAGS} -fsanitize=address -fno-omit-frame-pointer -D_GLIBCXX_DEBUG\")\nendif()\n\nif (GGWAVE_SANITIZE_UNDEFINED)\n    set(CMAKE_C_FLAGS \"${CMAKE_C_FLAGS} -fsanitize=undefined\")\n    set(CMAKE_CXX_FLAGS \"${CMAKE_CXX_FLAGS} -fsanitize=undefined\")\nendif()\n\n# dependencies\n\n# main\n\nset(CMAKE_CXX_STANDARD 11)\nset(CMAKE_CXX_STANDARD_REQUIRED ON)\n\nif (GGWAVE_ALL_WARNINGS)\n    if (CMAKE_COMPILER_IS_GNUCC OR CMAKE_CXX_COMPILER_ID MATCHES \"Clang\")\n        set(CMAKE_C_FLAGS \"${CMAKE_C_FLAGS} -Wall -Wextra\")\n        set(CMAKE_CXX_FLAGS \"${CMAKE_CXX_FLAGS} -Wall -Wextra -pedantic\")\n    else()\n        # todo : windows\n    endif()\nendif()\n\nadd_subdirectory(src)\nadd_subdirectory(bindings)\n\nif (GGWAVE_BUILD_TESTS)\n    enable_testing()\n    add_subdirectory(tests)\nendif()\n\nif (GGWAVE_BUILD_EXAMPLES)\n    add_subdirectory(examples)\nendif()\n\n\n\nset(CPACK_COMPONENTS_GROUPING ALL_COMPONENTS_IN_ONE)\nset(CPACK_DEB_COMPONENT_INSTALL YES)\nset(CPACK_DEBIAN_FILE_NAME DEB-DEFAULT)\nset(CPACK_DEBIAN_PACKAGE_DEPENDS \"libc6\")\nset(CPACK_DEBIAN_PACKAGE_HOMEPAGE \"https://github.com/ggerganov/ggwave\")\nset(CPACK_DEBIAN_PACKAGE_MAINTAINER \"Georgi Gerganov\")\nset(CPACK_DEBIAN_PACKAGE_SHLIBDEPS YES)\nset(CPACK_GENERATOR DEB)\nSET(CPACK_OUTPUT_FILE_PREFIX \"${CMAKE_SOURCE_DIR}/dist\")\nset(CPACK_PACKAGE_CONTACT \"ggerganov@gmail.com\")\nset(CPACK_PACKAGE_DESCRIPTION_SUMMARY \"Tiny data-over-sound library\")\nset(CPACK_PACKAGE_INSTALL_DIRECTORY ${CPACK_PACKAGE_NAME})\nset(CPACK_PACKAGE_NAME \"libggwave-dev\")\nset(CPACK_PACKAGE_VENDOR \"ggwave\")\nset(CPACK_PACKAGE_VERSION_MAJOR ${PROJECT_VERSION_MAJOR})\nset(CPACK_PACKAGE_VERSION_MINOR ${PROJECT_VERSION_MINOR})\nset(CPACK_PACKAGE_VERSION_PATCH ${PROJECT_VERSION_PATCH})\nset(CPACK_RESOURCE_FILE_LICENSE \"${CMAKE_CURRENT_SOURCE_DIR}/LICENSE\")\nset(CPACK_RESOURCE_FILE_README \"${CMAKE_CURRENT_SOURCE_DIR}/README.md\")\nset(CPACK_STRIP_FILES YES)\nset(CPACK_VERBATIM_VARIABLES YES)\ninclude(CPack)\n\nset(DEB_PYTHON_BUILD_DIR \"${CMAKE_SOURCE_DIR}/bindings/python/\")\nset(DEB_PYTHON_OUTPUT_FILE_PREFIX \"${DEB_PYTHON_BUILD_DIR}/dist\")\n\nset(ENV{SOURCE_DATE_EPOCH} \"0\")\nexecute_process(\n  COMMAND git log -1 --pretty=%ct\n  WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}\n  TIMEOUT 1\n  OUTPUT_VARIABLE SOURCE_DATE_EPOCH\n  OUTPUT_STRIP_TRAILING_WHITESPACE\n)\nset(ENV{SOURCE_DATE_EPOCH} ${SOURCE_DATE_EPOCH})\n\nadd_custom_target(deb_python_binding\n    COMMAND cd ${DEB_PYTHON_BUILD_DIR} && make deb\n    COMMENT \"Building Debian package for Python binding\"\n    WORKING_DIRECTORY ${CMAKE_BINARY_DIR}\n)\n\nadd_custom_target(deb_c_library\n    COMMAND SOURCE_DATE_EPOCH=${SOURCE_DATE_EPOCH} cpack \n    COMMENT \"Building Debian package for C library\"\n    WORKING_DIRECTORY ${CMAKE_BINARY_DIR}\n)\n\nadd_custom_target(deb_python_move\n    COMMAND mv ${DEB_PYTHON_OUTPUT_FILE_PREFIX}/*.deb ${CPACK_OUTPUT_FILE_PREFIX}/\n    COMMENT \"Moving Debian package for Python binding into ${CPACK_OUTPUT_FILE_PREFIX}/\"\n    WORKING_DIRECTORY ${CMAKE_BINARY_DIR}\n    DEPENDS deb_python_binding deb_c_library\n)\n\nadd_custom_target(deb\n    COMMAND sha256sum ${CPACK_OUTPUT_FILE_PREFIX}/* \n    COMMENT \"Debian package sha256 hashes\"\n    DEPENDS deb_c_library deb_python_binding deb_python_move\n    WORKING_DIRECTORY ${CMAKE_BINARY_DIR}\n)\n\nadd_custom_command(TARGET deb\n    POST_BUILD\n    COMMAND ls -alh ${CPACK_OUTPUT_FILE_PREFIX}/* \n    COMMENT \"SOURCE_DATE_EPOCH=${SOURCE_DATE_EPOCH}\"\n    WORKING_DIRECTORY ${CMAKE_BINARY_DIR}\n)\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2020 Georgi Gerganov\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README-tmpl.md",
    "content": "# ggwave\n\n[![Actions Status](https://github.com/ggerganov/ggwave/workflows/CI/badge.svg)](https://github.com/ggerganov/ggwave/actions)\n[![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](https://opensource.org/licenses/MIT)\n[![ggwave badge][changelog-badge]][changelog]\n[![pypi](https://img.shields.io/pypi/v/ggwave.svg)](https://pypi.org/project/ggwave/)\n[![npm](https://img.shields.io/npm/v/ggwave.svg)](https://www.npmjs.com/package/ggwave/)\n\nTiny data-over-sound library.\n\nClick on the images below to hear what it sounds like:\n\n<a href=\"https://youtu.be/S2YdGefZiy4\"><img width=\"100%\" src=\"media/ggwave0.gif\"></img></a>\n\n<a href=\"https://user-images.githubusercontent.com/1991296/161401690-013023ba-1d21-4fb7-8d7f-9953f51c1e5b.mp4\"><img width=\"100%\" src=\"https://user-images.githubusercontent.com/1991296/109401710-d7d3d880-7958-11eb-9b7e-364be0b4cf55.gif\"></img></a>\n\n<a href=\"https://youtu.be/Zcgf77T71QM\"><img width=\"100%\" src=\"media/waver-preview1.gif\"></img></a>\n\nhttps://user-images.githubusercontent.com/1991296/166411509-5e1b9bcb-3655-40b1-9dc3-9bec72889dcf.mp4\n\nhttps://user-images.githubusercontent.com/1991296/168469004-aeb9b9fe-cf81-4db7-b602-62e4ae659341.mp4\n\n<a href=\"https://youtu.be/aj_GLBtU3Vw\"><img width=\"100%\" src=\"https://user-images.githubusercontent.com/1991296/177214041-26456254-d4b5-425b-bc57-48bcfc8f816e.png\"></img></a>\n\n## Details\n\nThis 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.\n\nThis 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.\n\nHere is a list of possible applications of **ggwave** with a few examples:\n\n- **Serverless, one-to-many broadcast**\n  - [wave-share](https://github.com/ggerganov/wave-share) - file sharing through sound\n- **Internet of Things**\n  - [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\n  - [r2t2](https://github.com/ggerganov/ggwave/tree/master/examples/r2t2) - Transmit data with the PC speaker\n  - [buttons](https://github.com/ggerganov/ggwave/tree/master/examples/buttons) - Record and send commands via [Talking buttons](https://github.com/ggerganov/ggwave/discussions/27)\n- **Audio QR codes**\n  - [[Twitter]](https://twitter.com/ggerganov/status/1509558482567057417) - Broadcast your clipboard to nearby devices\n- **Device pairing**\n- **Authorization**\n\n## Try it out\n\nYou 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:\n\n<a href=\"https://apps.apple.com/us/app/waver-data-over-sound/id1543607865?itsct=apps_box&amp;itscg=30200&ign-itsct=apps_box#?platform=iphone\" style=\"display: inline-block; overflow: hidden; border-radius: 13px; width: 250px; height: 83px;\"><img height=\"60px\" src=\"https://tools.applemediaservices.com/api/badges/download-on-the-app-store/white/en-US?size=250x83&amp;releaseDate=1607558400&h=8e5fafc57929918f684abc83ff8311ef\" alt=\"Download on the App Store\"></a>\n<a href='https://play.google.com/store/apps/details?id=com.ggerganov.Waver&pcampaignid=pcampaignidMKT-Other-global-all-co-prtnr-py-PartBadge-Mar2515-1'><img alt='Get it on Google Play' src='https://i.imgur.com/BKDCbKv.png' height=\"60px\"/></a>\n<a href=\"https://snapcraft.io/waver\">\n<img alt=\"Get it from the Snap Store\" src=\"https://snapcraft.io/static/images/badges/en/snap-store-black.svg\" height=\"60px\"/>\n</a>\n\n### Browser demos\n\n  - https://waver.ggerganov.com\n  - https://ggwave.ggerganov.com\n  - https://ggwave-js.ggerganov.com\n\n### [HTTP service](https://github.com/ggerganov/ggwave/blob/master/examples/ggwave-to-file/README.md#http-service)\n\n  ```bash\n  # audible example\n  curl -sS 'https://ggwave-to-file.ggerganov.com/?m=Hello%20world!' --output hello.wav\n\n  # ultrasound example\n  curl -sS 'https://ggwave-to-file.ggerganov.com/?m=Hello%20world!&p=4' --output hello.wav\n  ```\n\n\n## Technical details\n\nBelow is a short summary of the modulation and demodulation algorithm used in `ggwave` for encoding and decoding data into sound.\n\n### Modulation (Tx)\n\nThe 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:\n\n| Freq, [Hz]   | Value, [bits]   | Freq, [Hz]   | Value, [bits]   | ... | Freq, [Hz]   | Value, [bits]   |\n| ------------ | --------------- | ------------ | --------------- | --- | ------------ | --------------- |\n| `F0 + 00*dF` | Chunk 0: `0000` | `F0 + 16*dF` | Chunk 1: `0000` | ... | `F0 + 80*dF` | Chunk 5: `0000` |\n| `F0 + 01*dF` | Chunk 0: `0001` | `F0 + 17*dF` | Chunk 1: `0001` | ... | `F0 + 81*dF` | Chunk 5: `0001` |\n| `F0 + 02*dF` | Chunk 0: `0010` | `F0 + 18*dF` | Chunk 1: `0010` | ... | `F0 + 82*dF` | Chunk 5: `0010` |\n| ...          | ...             | ...          | ...             | ... | ...          | ...             |\n| `F0 + 14*dF` | Chunk 0: `1110` | `F0 + 30*dF` | Chunk 1: `1110` | ... | `F0 + 94*dF` | Chunk 5: `1110` |\n| `F0 + 15*dF` | Chunk 0: `1111` | `F0 + 31*dF` | Chunk 1: `1111` | ... | `F0 + 95*dF` | Chunk 5: `1111` |\n\nFor all protocols: `dF = 46.875 Hz`. For non-ultrasonic protocols: `F0 = 1875.000 Hz`. For ultrasonic protocols: `F0 = 15000.000 Hz`.\n\nThe 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.\n\n### Demodulation (Rx)\n\nBeginning 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.\n\nReed-Solomon decoding is finally performed to obtain the original data.\n\n\n## Examples\n\nThe [examples](https://github.com/ggerganov/ggwave/blob/master/examples/) folder contains several sample applications of the library:\n\n\n| Example | Description | Audio |\n| ------- | ----------- | ----- |\n| [ggtag](https://github.com/rgerganov/ggtag) | Sound-programmable e-paper badge | PDM mic |\n| [ggwave-rx](https://github.com/ggerganov/ggwave/blob/master/examples/ggwave-rx) | Very basic receive-only program | SDL |\n| [ggwave-cli](https://github.com/ggerganov/ggwave/blob/master/examples/ggwave-cli) | Command line tool for sending/receiving data through sound | SDL |\n| [ggwave-wasm](https://github.com/ggerganov/ggwave/blob/master/examples/ggwave-wasm) | WebAssembly module for web applications | SDL |\n| [ggwave-to-file](https://github.com/ggerganov/ggwave/blob/master/examples/ggwave-to-file) | Output a generated waveform to an uncompressed WAV file | - |\n| [ggwave-from-file](https://github.com/ggerganov/ggwave/blob/master/examples/ggwave-from-file) | Decode a waveform from an uncompressed WAV file | - |\n| [waver](https://github.com/ggerganov/ggwave/blob/master/examples/waver) | GUI application for sending/receiving data through sound | SDL |\n| [ggwave-py](https://github.com/ggerganov/ggwave/blob/master/examples/ggwave-py) | Python examples | PortAudio |\n| [ggwave-js](https://github.com/ggerganov/ggwave/blob/master/examples/ggwave-js) | Javascript example | Web Audio API |\n| [spectrogram](https://github.com/ggerganov/ggwave/blob/master/examples/spectrogram) | Spectrogram tool | SDL |\n| [ggweb-spike](https://gitlab.com/commonsguy/ggweb-spike) | Android example using a `WebView` to wrap `ggwave` into a simple app | WebAudio |\n| [buttons](https://github.com/ggerganov/ggwave/blob/master/examples/buttons) | Record and send commands via Talking buttons | Web Audio API |\n| [r2t2](https://github.com/ggerganov/ggwave/blob/master/examples/r2t2) | Transmit data through the PC speaker | PC speaker |\n| [ggwave-objc](https://github.com/ggerganov/ggwave-objc) | Minimal Objective-C iOS app using ggwave | AudioToolbox |\n| [ggwave-java](https://github.com/ggerganov/ggwave-java) | Minimal Java Android app using ggwave | android.media |\n| [ggwave-fm](https://github.com/rgerganov/ggwave-fm) | Transmit ggwave messages with HackRF | Radio |\n| [esp32-rx](https://github.com/ggerganov/ggwave/tree/master/examples/esp32-rx) | Transmit and receive messages using ESP32 | - |\n| [rp2040-rx](https://github.com/ggerganov/ggwave/tree/master/examples/rp2040-rx) | Transmit and receive messages using Raspberry Pi Pico (RP2040) | - |\n| [arduino-rx](https://github.com/ggerganov/ggwave/tree/master/examples/arduino-rx) | Transmit and receive messages using Arduino RP2040 | - |\n| [arduino-tx](https://github.com/ggerganov/ggwave/tree/master/examples/arduino-tx) | Transmit messages using Arduino Uno | - |\n| [arduino-rx-web](https://github.com/ggerganov/ggwave/tree/master/examples/arduino-rx-web) | Receive messages from Arduino Uno | Web Audio API |\n\nOther projects using **ggwave** or one of its prototypes:\n\n- [wave-gui](https://github.com/ggerganov/wave-gui) - a GUI for exploring different modulation protocols\n- [wave-share](https://github.com/ggerganov/wave-share) - WebRTC file sharing with sound signaling\n\n\n## Building\n\n### Dependencies for SDL-based examples\n\n    [Ubuntu]\n    $ sudo apt install libsdl2-dev\n\n    [Mac OS with brew]\n    $ brew install sdl2\n\n    [MSYS2]\n    $ pacman -S git cmake make mingw-w64-x86_64-dlfcn mingw-w64-x86_64-gcc mingw-w64-x86_64-SDL2\n\n### Linux, Mac, Windows (MSYS2)\n\n```bash\n# build\ngit clone https://github.com/ggerganov/ggwave --recursive\ncd ggwave && mkdir build && cd build\ncmake ..\nmake\n\n# running\n./bin/ggwave-cli\n```\n\n#### Local Debian packages\n\nBuild reproducible `libggwave-dev` and `python3-ggwave` Debian packages:\n```bash\n# Fetch source\ngit clone https://github.com/ggerganov/ggwave --recursive\ncd ggwave\n\n# Configure\ncmake . -DGGWAVE_BUILD_EXAMPLES=OFF -DCMAKE_BUILD_TYPE=Release\n\n# Build\nmake deb\n\n# Install\nsudo dpkg -i dist/*.deb\n```\n\n### Emscripten\n\n```bash\ngit clone https://github.com/ggerganov/ggwave --recursive\ncd ggwave\nmkdir build && cd build\nemcmake cmake ..\nmake\n```\n\n### Python\n\n```bash\npip install ggwave\n```\n\nMore info: https://pypi.org/project/ggwave/\n\n### Node.js\n\n```bash\nnpm install ggwave\n```\n\nMore info: https://www.npmjs.com/package/ggwave\n\n### iOS\n\nAvailable as a Swift Package: https://github.com/ggerganov/ggwave-spm\n\n## Installing the Waver application\n\n[![Get it from the Snap Store](https://snapcraft.io/static/images/badges/en/snap-store-black.svg)](https://snapcraft.io/waver)\n\n### Linux\n\n```bash\nsudo snap install waver\nsudo snap connect waver:audio-record :audio-record\n```\n\n### Mac OS\n\n  ```bash\n  brew install ggerganov/ggerganov/waver\n  ```\n\n[changelog]: ./CHANGELOG.md\n[changelog-badge]: https://img.shields.io/badge/changelog-ggwave%20v@PROJECT_VERSION@-dummy\n[license]: ./LICENSE\n"
  },
  {
    "path": "README.md",
    "content": "# ggwave\n\n[![Actions Status](https://github.com/ggerganov/ggwave/workflows/CI/badge.svg)](https://github.com/ggerganov/ggwave/actions)\n[![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](https://opensource.org/licenses/MIT)\n[![ggwave badge][changelog-badge]][changelog]\n[![pypi](https://img.shields.io/pypi/v/ggwave.svg)](https://pypi.org/project/ggwave/)\n[![npm](https://img.shields.io/npm/v/ggwave.svg)](https://www.npmjs.com/package/ggwave/)\n\nTiny data-over-sound library.\n\nClick on the images below to hear what it sounds like:\n\n<a href=\"https://youtu.be/S2YdGefZiy4\"><img width=\"100%\" src=\"media/ggwave0.gif\"></img></a>\n\n<a href=\"https://user-images.githubusercontent.com/1991296/161401690-013023ba-1d21-4fb7-8d7f-9953f51c1e5b.mp4\"><img width=\"100%\" src=\"https://user-images.githubusercontent.com/1991296/109401710-d7d3d880-7958-11eb-9b7e-364be0b4cf55.gif\"></img></a>\n\n<a href=\"https://youtu.be/Zcgf77T71QM\"><img width=\"100%\" src=\"media/waver-preview1.gif\"></img></a>\n\nhttps://user-images.githubusercontent.com/1991296/166411509-5e1b9bcb-3655-40b1-9dc3-9bec72889dcf.mp4\n\nhttps://user-images.githubusercontent.com/1991296/168469004-aeb9b9fe-cf81-4db7-b602-62e4ae659341.mp4\n\n<a href=\"https://youtu.be/aj_GLBtU3Vw\"><img width=\"100%\" src=\"https://user-images.githubusercontent.com/1991296/177214041-26456254-d4b5-425b-bc57-48bcfc8f816e.png\"></img></a>\n\n## Details\n\nThis 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.\n\nThis 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.\n\nHere is a list of possible applications of **ggwave** with a few examples:\n\n- **Serverless, one-to-many broadcast**\n  - [wave-share](https://github.com/ggerganov/wave-share) - file sharing through sound\n- **Internet of Things**\n  - [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\n  - [r2t2](https://github.com/ggerganov/ggwave/tree/master/examples/r2t2) - Transmit data with the PC speaker\n  - [buttons](https://github.com/ggerganov/ggwave/tree/master/examples/buttons) - Record and send commands via [Talking buttons](https://github.com/ggerganov/ggwave/discussions/27)\n- **Audio QR codes**\n  - [[Twitter]](https://twitter.com/ggerganov/status/1509558482567057417) - Broadcast your clipboard to nearby devices\n- **Device pairing / Contact exchange**\n  - [PairSonic](https://github.com/seemoo-lab/pairsonic) - Exchange contact information and public keys with nearby devices\n- **Authorization**\n\n## Try it out\n\nYou 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:\n\n<a href=\"https://apps.apple.com/us/app/waver-data-over-sound/id1543607865?itsct=apps_box&amp;itscg=30200&ign-itsct=apps_box#?platform=iphone\" style=\"display: inline-block; overflow: hidden; border-radius: 13px; width: 250px; height: 83px;\"><img height=\"60px\" src=\"https://tools.applemediaservices.com/api/badges/download-on-the-app-store/white/en-US?size=250x83&amp;releaseDate=1607558400&h=8e5fafc57929918f684abc83ff8311ef\" alt=\"Download on the App Store\"></a>\n<a href='https://play.google.com/store/apps/details?id=com.ggerganov.Waver&pcampaignid=pcampaignidMKT-Other-global-all-co-prtnr-py-PartBadge-Mar2515-1'><img alt='Get it on Google Play' src='https://i.imgur.com/BKDCbKv.png' height=\"60px\"/></a>\n<a href=\"https://snapcraft.io/waver\">\n<img alt=\"Get it from the Snap Store\" src=\"https://snapcraft.io/static/images/badges/en/snap-store-black.svg\" height=\"60px\"/>\n</a>\n\n### Browser demos\n\n  - https://waver.ggerganov.com\n  - https://ggwave.ggerganov.com\n  - https://ggwave-js.ggerganov.com\n\n### [HTTP service](https://github.com/ggerganov/ggwave/blob/master/examples/ggwave-to-file/README.md#http-service)\n\n  ```bash\n  # audible example\n  curl -sS 'https://ggwave-to-file.ggerganov.com/?m=Hello%20world!' --output hello.wav\n\n  # ultrasound example\n  curl -sS 'https://ggwave-to-file.ggerganov.com/?m=Hello%20world!&p=4' --output hello.wav\n  ```\n\n\n## Technical details\n\nBelow is a short summary of the modulation and demodulation algorithm used in `ggwave` for encoding and decoding data into sound.\n\n### Modulation (Tx)\n\nThe 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:\n\n| Freq, [Hz]   | Value, [bits]   | Freq, [Hz]   | Value, [bits]   | ... | Freq, [Hz]   | Value, [bits]   |\n| ------------ | --------------- | ------------ | --------------- | --- | ------------ | --------------- |\n| `F0 + 00*dF` | Chunk 0: `0000` | `F0 + 16*dF` | Chunk 1: `0000` | ... | `F0 + 80*dF` | Chunk 5: `0000` |\n| `F0 + 01*dF` | Chunk 0: `0001` | `F0 + 17*dF` | Chunk 1: `0001` | ... | `F0 + 81*dF` | Chunk 5: `0001` |\n| `F0 + 02*dF` | Chunk 0: `0010` | `F0 + 18*dF` | Chunk 1: `0010` | ... | `F0 + 82*dF` | Chunk 5: `0010` |\n| ...          | ...             | ...          | ...             | ... | ...          | ...             |\n| `F0 + 14*dF` | Chunk 0: `1110` | `F0 + 30*dF` | Chunk 1: `1110` | ... | `F0 + 94*dF` | Chunk 5: `1110` |\n| `F0 + 15*dF` | Chunk 0: `1111` | `F0 + 31*dF` | Chunk 1: `1111` | ... | `F0 + 95*dF` | Chunk 5: `1111` |\n\nFor all protocols: `dF = 46.875 Hz`. For non-ultrasonic protocols: `F0 = 1875.000 Hz`. For ultrasonic protocols: `F0 = 15000.000 Hz`.\n\nThe 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.\n\n### Demodulation (Rx)\n\nBeginning 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.\n\nReed-Solomon decoding is finally performed to obtain the original data.\n\n\n## Examples\n\nThe [examples](https://github.com/ggerganov/ggwave/blob/master/examples/) folder contains several sample applications of the library:\n\n\n| Example | Description | Audio |\n| ------- | ----------- | ----- |\n| [ggtag](https://github.com/rgerganov/ggtag) | Sound-programmable e-paper badge | PDM mic |\n| [ggwave-rx](https://github.com/ggerganov/ggwave/blob/master/examples/ggwave-rx) | Very basic receive-only program | SDL |\n| [ggwave-cli](https://github.com/ggerganov/ggwave/blob/master/examples/ggwave-cli) | Command line tool for sending/receiving data through sound | SDL |\n| [ggwave-wasm](https://github.com/ggerganov/ggwave/blob/master/examples/ggwave-wasm) | WebAssembly module for web applications | SDL |\n| [ggwave-to-file](https://github.com/ggerganov/ggwave/blob/master/examples/ggwave-to-file) | Output a generated waveform to an uncompressed WAV file | - |\n| [ggwave-from-file](https://github.com/ggerganov/ggwave/blob/master/examples/ggwave-from-file) | Decode a waveform from an uncompressed WAV file | - |\n| [waver](https://github.com/ggerganov/ggwave/blob/master/examples/waver) | GUI application for sending/receiving data through sound | SDL |\n| [ggwave-py](https://github.com/ggerganov/ggwave/blob/master/examples/ggwave-py) | Python examples | PortAudio |\n| [ggwave-js](https://github.com/ggerganov/ggwave/blob/master/examples/ggwave-js) | Javascript example | Web Audio API |\n| [spectrogram](https://github.com/ggerganov/ggwave/blob/master/examples/spectrogram) | Spectrogram tool | SDL |\n| [ggweb-spike](https://gitlab.com/commonsguy/ggweb-spike) | Android example using a `WebView` to wrap `ggwave` into a simple app | WebAudio |\n| [buttons](https://github.com/ggerganov/ggwave/blob/master/examples/buttons) | Record and send commands via Talking buttons | Web Audio API |\n| [r2t2](https://github.com/ggerganov/ggwave/blob/master/examples/r2t2) | Transmit data through the PC speaker | PC speaker |\n| [ggwave-objc](https://github.com/ggerganov/ggwave-objc) | Minimal Objective-C iOS app using ggwave | AudioToolbox |\n| [ggwave-java](https://github.com/ggerganov/ggwave-java) | Minimal Java Android app using ggwave | android.media |\n| [ggwave-kmm](https://github.com/wooram-yang/ggwave-kmm) | Kotlin Multiplatform Project using ggwave | android.media, javax.sound.sampled |\n| [ggwave-fm](https://github.com/rgerganov/ggwave-fm) | Transmit ggwave messages with HackRF | Radio |\n| [esp32-rx](https://github.com/ggerganov/ggwave/tree/master/examples/esp32-rx) | Transmit and receive messages using ESP32 | - |\n| [rp2040-rx](https://github.com/ggerganov/ggwave/tree/master/examples/rp2040-rx) | Transmit and receive messages using Raspberry Pi Pico (RP2040) | - |\n| [arduino-rx](https://github.com/ggerganov/ggwave/tree/master/examples/arduino-rx) | Transmit and receive messages using Arduino RP2040 | - |\n| [arduino-tx](https://github.com/ggerganov/ggwave/tree/master/examples/arduino-tx) | Transmit messages using Arduino Uno | - |\n| [arduino-rx-web](https://github.com/ggerganov/ggwave/tree/master/examples/arduino-rx-web) | Receive messages from Arduino Uno | Web Audio API |\n\nOther projects using **ggwave** or one of its prototypes:\n\n- [wave-gui](https://github.com/ggerganov/wave-gui) - a GUI for exploring different modulation protocols\n- [wave-share](https://github.com/ggerganov/wave-share) - WebRTC file sharing with sound signaling\n\n\n## Building\n\n### Dependencies for SDL-based examples\n\n    [Ubuntu]\n    $ sudo apt install libsdl2-dev\n\n    [Mac OS with brew]\n    $ brew install sdl2\n\n    [MSYS2]\n    $ pacman -S git cmake make mingw-w64-x86_64-dlfcn mingw-w64-x86_64-gcc mingw-w64-x86_64-SDL2\n\n### Linux, Mac, Windows (MSYS2)\n\n```bash\n# build\ngit clone https://github.com/ggerganov/ggwave --recursive\ncd ggwave && mkdir build && cd build\ncmake ..\nmake\n\n# running\n./bin/ggwave-cli\n```\n\n#### Local Debian packages\n\nBuild reproducible `libggwave-dev` and `python3-ggwave` Debian packages:\n```bash\n# Fetch source\ngit clone https://github.com/ggerganov/ggwave --recursive\ncd ggwave\n\n# Configure\ncmake . -DGGWAVE_BUILD_EXAMPLES=OFF -DCMAKE_BUILD_TYPE=Release\n\n# Build\nmake deb\n\n# Install\nsudo dpkg -i dist/*.deb\n```\n\n### Emscripten\n\n```bash\ngit clone https://github.com/ggerganov/ggwave --recursive\ncd ggwave\nmkdir build && cd build\nemcmake cmake ..\nmake\n```\n\n### Python\n\n```bash\npip install ggwave\n```\n\nMore info: https://pypi.org/project/ggwave/\n\n### Node.js\n\n```bash\nnpm install ggwave\n```\n\nMore info: https://www.npmjs.com/package/ggwave\n\n### iOS\n\nAvailable as a Swift Package: https://github.com/ggerganov/ggwave-spm\n\n## Installing the Waver application\n\n[![Get it from the Snap Store](https://snapcraft.io/static/images/badges/en/snap-store-black.svg)](https://snapcraft.io/waver)\n\n### Linux\n\n```bash\nsudo snap install waver\nsudo snap connect waver:audio-record :audio-record\n```\n\n### Mac OS\n\n  ```bash\n  brew install ggerganov/ggerganov/waver\n  ```\n\n## References\n\n- [Evaluating Acoustic Data Transmission Schemes for Ad-Hoc Communication Between Nearby Smart Devices](https://dl.acm.org/doi/10.1145/3779439)\n\n[changelog]: ./CHANGELOG.md\n[changelog-badge]: https://img.shields.io/badge/changelog-ggwave%20v0.4.2-dummy\n[license]: ./LICENSE\n"
  },
  {
    "path": "bindings/CMakeLists.txt",
    "content": "if (EMSCRIPTEN)\n    add_subdirectory(javascript)\n\n    add_custom_command(\n        OUTPUT ${CMAKE_CURRENT_SOURCE_DIR}/javascript/publish.log\n        DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/javascript/ggwave.js\n        DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/javascript/package.json\n        WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/javascript\n        COMMAND npm publish\n        COMMAND touch publish.log\n        COMMENT \"Publishing npm module v${PROJECT_VERSION}\"\n        VERBATIM\n        )\n\n    add_custom_target(publish-npm\n        DEPENDS javascript/publish.log\n        )\nendif()\n\nif (GGWAVE_SUPPORT_PYTHON)\n    file(GLOB_RECURSE GGWAVE_SOURCES \"../include/*\" \"../src/*\")\n\n    add_custom_command(\n        OUTPUT ${CMAKE_CURRENT_SOURCE_DIR}/python/ggwave.bycython.cpp\n        OUTPUT ${CMAKE_CURRENT_SOURCE_DIR}/python/ggwave\n        DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/python/Makefile\n        DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/python/ggwave.pyx\n        DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/python/cggwave.pxd\n        DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/python/setup.py\n        DEPENDS ${GGWAVE_SOURCES}\n        WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/python\n        COMMAND make clean\n        COMMAND make\n        COMMENT \"Compiling Python module\"\n        VERBATIM\n        )\n\n    add_custom_target(ggwave-py ALL\n        DEPENDS python/ggwave.bycython.cpp\n        DEPENDS python/ggwave\n        )\n\n    add_custom_command(\n        OUTPUT ${CMAKE_CURRENT_SOURCE_DIR}/python/dist\n        DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/python/Makefile\n        DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/python/ggwave.pyx\n        DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/python/cggwave.pxd\n        DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/python/setup.py\n        DEPENDS ${GGWAVE_SOURCES}\n        WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/python\n        COMMAND make publish\n        COMMENT \"Publishing Python module v${GGWAVE_VERSION_PYTHON}\"\n        VERBATIM\n        )\n\n    add_custom_target(publish-pypi\n        DEPENDS python/dist\n        )\nendif()\n\nif (GGWAVE_SUPPORT_SWIFT)\n    file(GLOB_RECURSE GGWAVE_SOURCES \"../include/*\" \"../src/*\")\n\n    add_custom_command(\n        OUTPUT ${CMAKE_CURRENT_SOURCE_DIR}/ios/.build\n        DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/ios/Makefile\n        DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/ios/Package.swift\n        DEPENDS ${GGWAVE_SOURCES}\n        WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/ios\n        COMMAND make clean\n        COMMAND make build-submodule\n        COMMENT \"Compiling Swift package\"\n        VERBATIM\n        )\n\n    add_custom_target(ggwave-spm ALL\n        DEPENDS ios/.build\n        )\n\n    add_custom_command(\n        OUTPUT ${CMAKE_CURRENT_SOURCE_DIR}/ios/publish\n        DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/ios/publish-trigger\n        DEPENDS ${GGWAVE_SOURCES}\n        WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/ios\n        COMMAND make publish\n        COMMENT \"Publishing Swift package v${PROJECT_VERSION}\"\n        VERBATIM\n        )\n\n    add_custom_target(publish-spm\n        DEPENDS ios/publish\n        )\nendif()\n"
  },
  {
    "path": "bindings/javascript/.gitignore",
    "content": "publish.log\n"
  },
  {
    "path": "bindings/javascript/CMakeLists.txt",
    "content": "set(TARGET libggwave)\n\nadd_executable(${TARGET}\n    emscripten.cpp\n    )\n\ntarget_link_libraries(${TARGET} PRIVATE\n    ggwave\n    )\n\nunset(EXTRA_FLAGS)\nif (GGWAVE_WASM_SINGLE_FILE)\n    set(EXTRA_FLAGS \"-s SINGLE_FILE=1\")\n    message(STATUS \"Embedding WASM inside ggwave.js\")\n\n    add_custom_command(\n        TARGET libggwave POST_BUILD\n        COMMAND ${CMAKE_COMMAND} -E copy\n        ${CMAKE_BINARY_DIR}/bin/libggwave.js\n        ${CMAKE_CURRENT_SOURCE_DIR}/ggwave.js\n        )\nendif()\n\nset_target_properties(${TARGET} PROPERTIES LINK_FLAGS \" \\\n    --bind \\\n    -s MODULARIZE=1 \\\n    -s ALLOW_MEMORY_GROWTH=1 \\\n    -s EXPORT_NAME=\\\"'ggwave_factory'\\\" \\\n    ${EXTRA_FLAGS} \\\n    \")\n"
  },
  {
    "path": "bindings/javascript/README.md",
    "content": "# ggwave\n\nTiny data-over-sound library.\n\n- Audible and ultrasound transmissions available\n- Bandwidth of 8-16 bytes/s (depending on the transmission protocol)\n- Robust FSK modulation\n- Reed-Solomon based error correction\n\n## Example Usage\n\n```js\nvar factory = require('ggwave')\n\nfactory().then(function(ggwave) {\n    // create ggwave instance with default parameters\n    var parameters = ggwave.getDefaultParameters();\n\n    parameters.operatingMode |= ggwave.GGWAVE_OPERATING_MODE_USE_DSS;\n\n    var instance = ggwave.init(parameters);\n    console.log('instance: ' + instance);\n\n    var payload = 'hello js';\n\n    // generate audio waveform for string \"hello js\"\n    var waveform = ggwave.encode(instance, payload, ggwave.ProtocolId.GGWAVE_PROTOCOL_AUDIBLE_FAST, 10);\n\n    // decode the audio waveform back to text\n    var res = ggwave.decode(instance, waveform);\n\n    if (new TextDecoder(\"utf-8\").decode(res) != payload) {\n        process.exit(1);\n    }\n});\n```\n"
  },
  {
    "path": "bindings/javascript/emscripten.cpp",
    "content": "#include \"ggwave/ggwave.h\"\n\n#include <emscripten.h>\n#include <emscripten/bind.h>\n\nEMSCRIPTEN_BINDINGS(ggwave) {\n    emscripten::enum_<ggwave_SampleFormat>(\"SampleFormat\")\n        .value(\"GGWAVE_SAMPLE_FORMAT_UNDEFINED\", GGWAVE_SAMPLE_FORMAT_UNDEFINED)\n        .value(\"GGWAVE_SAMPLE_FORMAT_U8\",        GGWAVE_SAMPLE_FORMAT_U8)\n        .value(\"GGWAVE_SAMPLE_FORMAT_I8\",        GGWAVE_SAMPLE_FORMAT_I8)\n        .value(\"GGWAVE_SAMPLE_FORMAT_U16\",       GGWAVE_SAMPLE_FORMAT_U16)\n        .value(\"GGWAVE_SAMPLE_FORMAT_I16\",       GGWAVE_SAMPLE_FORMAT_I16)\n        .value(\"GGWAVE_SAMPLE_FORMAT_F32\",       GGWAVE_SAMPLE_FORMAT_F32)\n        ;\n\n    emscripten::enum_<ggwave_ProtocolId>(\"ProtocolId\")\n        .value(\"GGWAVE_PROTOCOL_AUDIBLE_NORMAL\",     GGWAVE_PROTOCOL_AUDIBLE_NORMAL)\n        .value(\"GGWAVE_PROTOCOL_AUDIBLE_FAST\",       GGWAVE_PROTOCOL_AUDIBLE_FAST)\n        .value(\"GGWAVE_PROTOCOL_AUDIBLE_FASTEST\",    GGWAVE_PROTOCOL_AUDIBLE_FASTEST)\n        .value(\"GGWAVE_PROTOCOL_ULTRASOUND_NORMAL\",  GGWAVE_PROTOCOL_ULTRASOUND_NORMAL)\n        .value(\"GGWAVE_PROTOCOL_ULTRASOUND_FAST\",    GGWAVE_PROTOCOL_ULTRASOUND_FAST)\n        .value(\"GGWAVE_PROTOCOL_ULTRASOUND_FASTEST\", GGWAVE_PROTOCOL_ULTRASOUND_FASTEST)\n        .value(\"GGWAVE_PROTOCOL_DT_NORMAL\",          GGWAVE_PROTOCOL_DT_NORMAL)\n        .value(\"GGWAVE_PROTOCOL_DT_FAST\",            GGWAVE_PROTOCOL_DT_FAST)\n        .value(\"GGWAVE_PROTOCOL_DT_FASTEST\",         GGWAVE_PROTOCOL_DT_FASTEST)\n        .value(\"GGWAVE_PROTOCOL_MT_NORMAL\",          GGWAVE_PROTOCOL_MT_NORMAL)\n        .value(\"GGWAVE_PROTOCOL_MT_FAST\",            GGWAVE_PROTOCOL_MT_FAST)\n        .value(\"GGWAVE_PROTOCOL_MT_FASTEST\",         GGWAVE_PROTOCOL_MT_FASTEST)\n\n        .value(\"GGWAVE_PROTOCOL_CUSTOM_0\", GGWAVE_PROTOCOL_CUSTOM_0)\n        .value(\"GGWAVE_PROTOCOL_CUSTOM_1\", GGWAVE_PROTOCOL_CUSTOM_1)\n        .value(\"GGWAVE_PROTOCOL_CUSTOM_2\", GGWAVE_PROTOCOL_CUSTOM_2)\n        .value(\"GGWAVE_PROTOCOL_CUSTOM_3\", GGWAVE_PROTOCOL_CUSTOM_3)\n        .value(\"GGWAVE_PROTOCOL_CUSTOM_4\", GGWAVE_PROTOCOL_CUSTOM_4)\n        .value(\"GGWAVE_PROTOCOL_CUSTOM_5\", GGWAVE_PROTOCOL_CUSTOM_5)\n        .value(\"GGWAVE_PROTOCOL_CUSTOM_6\", GGWAVE_PROTOCOL_CUSTOM_6)\n        .value(\"GGWAVE_PROTOCOL_CUSTOM_7\", GGWAVE_PROTOCOL_CUSTOM_7)\n        .value(\"GGWAVE_PROTOCOL_CUSTOM_8\", GGWAVE_PROTOCOL_CUSTOM_8)\n        .value(\"GGWAVE_PROTOCOL_CUSTOM_9\", GGWAVE_PROTOCOL_CUSTOM_9)\n        ;\n\n    emscripten::constant(\"GGWAVE_OPERATING_MODE_RX\",            (int) GGWAVE_OPERATING_MODE_RX);\n    emscripten::constant(\"GGWAVE_OPERATING_MODE_TX\",            (int) GGWAVE_OPERATING_MODE_TX);\n    emscripten::constant(\"GGWAVE_OPERATING_MODE_RX_AND_TX\",     (int) GGWAVE_OPERATING_MODE_RX | GGWAVE_OPERATING_MODE_TX);\n    emscripten::constant(\"GGWAVE_OPERATING_MODE_TX_ONLY_TONES\", (int) GGWAVE_OPERATING_MODE_TX_ONLY_TONES);\n    emscripten::constant(\"GGWAVE_OPERATING_MODE_USE_DSS\",       (int) GGWAVE_OPERATING_MODE_USE_DSS);\n\n    emscripten::value_object<ggwave_Parameters>(\"Parameters\")\n        .field(\"payloadLength\",        & ggwave_Parameters::payloadLength)\n        .field(\"sampleRateInp\",        & ggwave_Parameters::sampleRateInp)\n        .field(\"sampleRateOut\",        & ggwave_Parameters::sampleRateOut)\n        .field(\"sampleRate\",           & ggwave_Parameters::sampleRate)\n        .field(\"samplesPerFrame\",      & ggwave_Parameters::samplesPerFrame)\n        .field(\"soundMarkerThreshold\", & ggwave_Parameters::soundMarkerThreshold)\n        .field(\"sampleFormatInp\",      & ggwave_Parameters::sampleFormatInp)\n        .field(\"sampleFormatOut\",      & ggwave_Parameters::sampleFormatOut)\n        .field(\"operatingMode\",        & ggwave_Parameters::operatingMode)\n        ;\n\n    emscripten::function(\"getDefaultParameters\", & ggwave_getDefaultParameters);\n    emscripten::function(\"init\", & ggwave_init);\n    emscripten::function(\"free\", & ggwave_free);\n\n    emscripten::function(\"encode\", emscripten::optional_override(\n                    [](ggwave_Instance instance,\n                       const std::string & data,\n                       ggwave_ProtocolId protocolId,\n                       int volume) {\n                        auto n = ggwave_encode(instance, data.data(), data.size(), protocolId, volume, nullptr, 1);\n\n                        // TODO: how to return the waveform data?\n                        //       for now using this static vector and returning a pointer to it\n                        static std::vector<char> result(n);\n                        result.resize(n);\n\n                        int nActual = ggwave_encode(instance, data.data(), data.size(), protocolId, volume, result.data(), 0);\n\n                        // printf(\"n = %d, nActual = %d\\n\", n, nActual);\n                        return emscripten::val(emscripten::typed_memory_view(nActual, result.data()));\n                    }));\n\n    emscripten::function(\"decode\", emscripten::optional_override(\n                    [](ggwave_Instance instance,\n                       const std::string & data) {\n                        // TODO: how to return the result?\n                        //       again using a static array and returning a pointer to it\n                        static char output[256];\n\n                        auto n = ggwave_decode(instance, data.data(), data.size(), output);\n\n                        if (n > 0) {\n                            return emscripten::val(emscripten::typed_memory_view(n, output));\n                        }\n\n                        return emscripten::val(emscripten::typed_memory_view(0, output));\n                    }));\n\n    emscripten::function(\"disableLog\", emscripten::optional_override(\n                    []() {\n                        ggwave_setLogFile(NULL);\n                    }));\n\n    emscripten::function(\"enableLog\", emscripten::optional_override(\n                    []() {\n                        ggwave_setLogFile(stderr);\n                    }));\n\n    emscripten::function(\"rxToggleProtocol\", emscripten::optional_override(\n                    [](ggwave_ProtocolId protocolId,\n                       int state) {\n                        ggwave_rxToggleProtocol(protocolId, state);\n                    }));\n\n    emscripten::function(\"txToggleProtocol\", emscripten::optional_override(\n                    [](ggwave_ProtocolId protocolId,\n                       int state) {\n                        ggwave_txToggleProtocol(protocolId, state);\n                    }));\n\n    emscripten::function(\"rxProtocolSetFreqStart\", emscripten::optional_override(\n                    [](ggwave_ProtocolId protocolId,\n                       int freqStart) {\n                        ggwave_rxProtocolSetFreqStart(protocolId, freqStart);\n                    }));\n\n    emscripten::function(\"txProtocolSetFreqStart\", emscripten::optional_override(\n                    [](ggwave_ProtocolId protocolId,\n                       int freqStart) {\n                        ggwave_txProtocolSetFreqStart(protocolId, freqStart);\n                    }));\n\n    emscripten::function(\"rxDurationFrames\", emscripten::optional_override(\n                    [](ggwave_Instance instance) {\n                        return ggwave_rxDurationFrames(instance);\n                    }));\n}\n"
  },
  {
    "path": "bindings/javascript/ggwave.js",
    "content": "\nvar ggwave_factory = (() => {\n  var _scriptDir = typeof document !== 'undefined' && document.currentScript ? document.currentScript.src : undefined;\n  if (typeof __filename !== 'undefined') _scriptDir = _scriptDir || __filename;\n  return (\nfunction(moduleArg = {}) {\n\nvar 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/IAAoAgxBAm23n0QAAAAAAAAIQKAiDJlEAAAAAAAA4EFjBEAg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OSABIAM6ADogASALQQF0IhM7ATAgASATIANB/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<myTypes.length;++i){registerType(myTypes[i],myTypeConverters[i])}}var typeConverters=new Array(dependentTypes.length);var unregisteredTypes=[];var registered=0;dependentTypes.forEach((dt,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.length;++i){if(emval_handles.allocated[i]!==undefined){++count}}return count};var init_emval=()=>{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;i<argTypes.length;++i){if(argTypes[i]!==null&&argTypes[i].destructorFunction===undefined){needsDestructorStack=true;break}}var returns=argTypes[0].name!==\"void\";var argsList=\"\";var argsListWired=\"\";for(var i=0;i<argCount-2;++i){argsList+=(i!==0?\", \":\"\")+\"arg\"+i;argsListWired+=(i!==0?\", \":\"\")+\"arg\"+i+\"Wired\"}var invokerFnBody=`\\n        return function ${makeLegalFunctionName(humanName)}(${argsList}) {\\n        if (arguments.length !== ${argCount-2}) {\\n          throwBindingError('function ${humanName} called with ' + arguments.length + ' arguments, expected ${argCount-2}');\\n        }`;if(needsDestructorStack){invokerFnBody+=\"var destructors = [];\\n\"}var dtorStack=needsDestructorStack?\"destructors\":\"null\";var args1=[\"throwBindingError\",\"invoker\",\"fn\",\"runDestructors\",\"retType\",\"classParam\"];var args2=[throwBindingError,cppInvokerFunc,cppTargetFunc,runDestructors,argTypes[0],argTypes[1]];if(isClassMethodFunc){invokerFnBody+=\"var thisWired = classParam.toWireType(\"+dtorStack+\", this);\\n\"}for(var i=0;i<argCount-2;++i){invokerFnBody+=\"var arg\"+i+\"Wired = argType\"+i+\".toWireType(\"+dtorStack+\", arg\"+i+\"); // \"+argTypes[i+2].name+\"\\n\";args1.push(\"argType\"+i);args2.push(argTypes[i+2])}if(isClassMethodFunc){argsListWired=\"thisWired\"+(argsListWired.length>0?\", \":\"\")+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<argTypes.length;++i){var paramName=i===1?\"thisWired\":\"arg\"+(i-2)+\"Wired\";if(argTypes[i].destructorFunction!==null){invokerFnBody+=paramName+\"_dtor(\"+paramName+\"); // \"+argTypes[i].name+\"\\n\";args1.push(paramName+\"_dtor\");args2.push(argTypes[i].destructorFunction)}}}if(returns){invokerFnBody+=\"var ret = retType.fromWireType(rv);\\n\"+\"return ret;\\n\"}else{}invokerFnBody+=\"}\\n\";args1.push(invokerFnBody);return newFunc(Function,args1).apply(null,args2)}var heap32VectorToArray=(count,firstElement)=>{var array=[];for(var i=0;i<count;i++){array.push(HEAPU32[firstElement+i*4>>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>>>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<str.length;++i){var u=str.charCodeAt(i);if(u>=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<str.length;++i){var c=str.charCodeAt(i);if(c<=127){len++}else if(c<=2047){len+=2}else if(c>=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<endPtr){var u0=heapOrArray[idx++];if(!(u0&128)){str+=String.fromCharCode(u0);continue}var u1=heapOrArray[idx++]&63;if((u0&224)==192){str+=String.fromCharCode((u0&31)<<6|u1);continue}var u2=heapOrArray[idx++]&63;if((u0&240)==224){u0=(u0&15)<<12|u1<<6|u2}else{u0=(u0&7)<<18|u1<<12|u2<<6|heapOrArray[idx++]&63}if(u0<65536){str+=String.fromCharCode(u0)}else{var ch=u0-65536;str+=String.fromCharCode(55296|ch>>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<length;++i){a[i]=String.fromCharCode(HEAPU8[payload+i])}str=a.join(\"\")}_free(value);return str},\"toWireType\":(destructors,value)=>{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;i<length;++i){var charCode=value.charCodeAt(i);if(charCode>255){_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<length;++i){HEAPU8[ptr+i]=value[i]}}}if(destructors!==null){destructors.push(_free,base)}return base},\"argPackAdvance\":GenericWireTypeSize,\"readValueFromPointer\":readPointer,destructorFunction:ptr=>_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<str.length*2?maxBytesToWrite/2:str.length;for(var i=0;i<numCharsToWrite;++i){var codeUnit=str.charCodeAt(i);HEAP16[outPtr>>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<str.length;++i){var codeUnit=str.charCodeAt(i);if(codeUnit>=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<str.length;++i){var codeUnit=str.charCodeAt(i);if(codeUnit>=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<iovcnt;i++){var ptr=HEAPU32[iov>>2];var len=HEAPU32[iov+4>>2];iov+=8;for(var j=0;j<len;j++){printChar(fd,HEAPU8[ptr+j])}num+=len}HEAPU32[pnum>>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;i<decoded.length;++i){bytes[i]=decoded.charCodeAt(i)}return bytes}catch(_){throw new Error(\"Converting base64 string to bytes failed.\")}}function tryParseAsDataURI(filename){if(!isDataURI(filename)){return}return intArrayFromBase64(filename.slice(dataURIPrefix.length))}var calledRun;dependenciesFulfilled=function runCaller(){if(!calledRun)run();if(!calledRun)dependenciesFulfilled=runCaller};function run(){if(runDependencies>0){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();\n\n\n  return moduleArg.ready\n}\n\n);\n})();\nif (typeof exports === 'object' && typeof module === 'object')\n  module.exports = ggwave_factory;\nelse if (typeof define === 'function' && define['amd'])\n  define([], () => ggwave_factory);\n"
  },
  {
    "path": "bindings/javascript/package-tmpl.json",
    "content": "{\n  \"name\": \"ggwave\",\n  \"version\": \"@PROJECT_VERSION@\",\n  \"description\": \"Tiny data-over-sound library\",\n  \"main\": \"ggwave.js\",\n  \"scripts\": {\n    \"test\": \"echo \\\"todo: add tests\\\" && exit 0\"\n  },\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/ggerganov/ggwave.git\"\n  },\n  \"keywords\": [\n    \"data-over-sound\",\n    \"fsk\",\n    \"sound-library\",\n    \"ultrasound\",\n    \"ecc\"\n  ],\n  \"author\": \"Georgi Gerganov\",\n  \"license\": \"MIT\",\n  \"bugs\": {\n    \"url\": \"https://github.com/ggerganov/ggwave/issues\"\n  },\n  \"homepage\": \"https://github.com/ggerganov/ggwave#readme\"\n}\n"
  },
  {
    "path": "bindings/javascript/package.json",
    "content": "{\n  \"name\": \"ggwave\",\n  \"version\": \"0.4.2\",\n  \"description\": \"Tiny data-over-sound library\",\n  \"main\": \"ggwave.js\",\n  \"scripts\": {\n    \"test\": \"echo \\\"todo: add tests\\\" && exit 0\"\n  },\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/ggerganov/ggwave.git\"\n  },\n  \"keywords\": [\n    \"data-over-sound\",\n    \"fsk\",\n    \"sound-library\",\n    \"ultrasound\",\n    \"ecc\"\n  ],\n  \"author\": \"Georgi Gerganov\",\n  \"license\": \"MIT\",\n  \"bugs\": {\n    \"url\": \"https://github.com/ggerganov/ggwave/issues\"\n  },\n  \"homepage\": \"https://github.com/ggerganov/ggwave#readme\"\n}\n"
  },
  {
    "path": "bindings/python/.gitignore",
    "content": "ggwave.so\nREADME.rst\nggwave.bycython.cpp\nggwave.cpython-*-x86_64-linux-gnu.so\nggwave/\nggwave.egg-info/\ndist/\n"
  },
  {
    "path": "bindings/python/MANIFEST.in",
    "content": "recursive-include ggwave/include *\nrecursive-include ggwave/src/ *\ninclude *.pxd\ninclude *.pyx\n"
  },
  {
    "path": "bindings/python/Makefile",
    "content": "default: build\n\nPKG_NAME := ggwave\nDEST := dist/\nVER := $(shell cat setup.py | grep version |cut -d\\\"  -f2)\nVERSION := $(strip $(VER))\nORIG_TGZ := $(PKG_NAME)_$(VERSION).orig.tar.gz\nARCH := $(shell dpkg --print-architecture)\nDEB_BUILD_OPTIONS := nocheck\nDEB_BUILD_DIR := $(DEST)$(PKG_NAME)-$(VERSION)\nDEB_VER := 0\nDEB := python3-$(PKG_NAME)_$(VERSION)-$(DEB_VER)_$(ARCH).deb\nSOURCE_DATE_EPOCH := $(shell git log -1 --pretty=%ct)\nSOURCE_PKG_CMD := python -m build --sdist\nDEB_BUILD_CMD := dpkg-buildpackage -rfakeroot -uc -us\nCOGAPP ?= $(shell which cog || which cogapp)\n\n.PHONY:\nggwave:\n\t# create a clean (maybe updated) copy of ggwave src\n\trm -rf ggwave && mkdir ggwave && cp -r ../../include ggwave/ && cp -r ../../src ggwave/\n\ndeps:\n\techo python -m pip install cogapp cython twine\n\npyggwave.bycython.cpp: ggwave.pyx cggwave.pxd\n\tcython --cleanup=3 --cplus ggwave.pyx -o ggwave.bycython.cpp\n\n# To build package, README.rst is needed, because it goes into long description of package,\n# which is what is visible on PyPI.\n# However, to generate README.rst from README-tmpl.rst, built package is needed (for `import ggwave` in cog)!\n# Therefore, we first build package without README.rst, use it to generate README.rst,\n# and then finally build package again but with README.rst.\n\nBUILD_SOURCE_FILES=ggwave pyggwave.bycython.cpp setup.py\n\n$(DEST)$(DEB): $(DEST)$(ORIG_TGZ)\n\t-rm -r $(DEB_BUILD_DIR)\n\ttar -C $(DEST) -xf $(DEST)$(ORIG_TGZ)\n\tcp -arp debian $(DEB_BUILD_DIR)\n\tcd $(DEB_BUILD_DIR) && \\\n\tDEB_BUILD_OPTIONS=$(DEB_BUILD_OPTIONS) $(DEB_BUILD_CMD)\n\ndeb_sdist: $(BUILD_SOURCE_FILES) README.rst\n\tSOURCE_DATE_EPOCH=$(SOURCE_DATE_EPOCH) $(SOURCE_PKG_CMD)\n\tmv $(DEST)$(PKG_NAME)-$(VERSION).tar.gz $(DEST)$(ORIG_TGZ)\n\n_deb:\n\t@$(eval COGAPP=cogapp)\n\ndeb: _deb deb_sdist $(DEST)$(DEB)\n\tdpkg --contents $(DEST)$(DEB)\n\tls -alh $(DEST)*$(PKG_NAME)*\n\tsha256sum $(DEST)$(DEB)\n\nbuildWithoutREADME.rst: ${BUILD_SOURCE_FILES}\n\tGGWAVE_OMIT_README_RST=1 python setup.py build_ext -i\n\nREADME.rst: buildWithoutREADME.rst README-tmpl.rst\n\t$(COGAPP) -d -o README.rst README-tmpl.rst\n\nBUILD_FILES=${BUILD_SOURCE_FILES} README.rst\n\nbuild: ${BUILD_FILES}\n\tpython setup.py build_ext -i\n\nsdist: ggwave pyggwave.bycython.cpp setup.py README.rst MANIFEST.in\n\tpython setup.py sdist\n\npublish: clean sdist\n\ttwine upload --repository pypi dist/*\n\nclean:\n\trm -rf ggwave dist ggwave.egg-info build\n\trm -f ggwave.c *.bycython.* ggwave.*.so\n\trm -f README.rst\n"
  },
  {
    "path": "bindings/python/README-tmpl.rst",
    "content": "..  [[[cog\n\n    import cog\n    import ggwave\n\n    def indent(text, indentation = \"    \"):\n        return indentation + text.replace(\"\\n\", \"\\n\" + indentation)\n\n    def comment(text):\n        return \"# \" + text.replace(\"\\n\", \"\\n# \")\n\n    def cogOutExpression(expr):\n        cog.outl(indent(expr))\n        cog.outl(indent(comment(str(eval(expr)))))\n\n    ]]]\n    [[[end]]]\n\n======\nggwave\n======\n\nTiny data-over-sound library.\n\n..  [[[cog\n\n    cog.outl()\n    cog.outl(\".. code:: python\")\n    cog.outl()\n\n    cog.outl(indent(comment('generate audio waveform for string \"hello python\"')))\n    cog.outl(indent('waveform = ggwave.encode(\"hello python\")'))\n    cog.outl()\n\n    cog.outl(indent(comment('decode audio waveform')))\n    cog.outl(indent('text = ggwave.decode(instance, waveform)'))\n    cog.outl()\n\n    ]]]\n\n.. code::\n\n   {{ Basic code examples will be generated here. }}\n\n..  [[[end]]]\n\n--------\nFeatures\n--------\n\n* Audible and ultrasound transmissions available\n* Bandwidth of 8-16 bytes/s (depending on the transmission protocol)\n* Robust FSK modulation\n* Reed-Solomon based error correction\n\n------------\nInstallation\n------------\n::\n\n    pip install ggwave\n\n---\nAPI\n---\n\nencode()\n--------\n\n.. code:: python\n\n    encode(payload, [protocolId], [volume], [instance])\n\nEncodes ``payload`` into an audio waveform.\n\n..  [[[cog\n\n    import pydoc\n\n    help_str = pydoc.plain(pydoc.render_doc(ggwave.encode, \"%s\"))\n\n    cog.outl()\n    cog.outl('Output of ``help(ggwave.encode)``:')\n    cog.outl()\n    cog.outl('.. code::\\n')\n    cog.outl(indent(help_str))\n\n    ]]]\n\n.. code::\n\n   {{ Content of help(ggwave.encode) will be generated here. }}\n\n..  [[[end]]]\n\ndecode()\n--------\n\n.. code:: python\n\n    decode(instance, waveform)\n\nAnalyzes and decodes ``waveform`` into to try and obtain the original payload.\nA preallocated ggwave ``instance`` is required.\n\n..  [[[cog\n\n    import pydoc\n\n    help_str = pydoc.plain(pydoc.render_doc(ggwave.decode, \"%s\"))\n\n    cog.outl()\n    cog.outl('Output of ``help(ggwave.decode)``:')\n    cog.outl()\n    cog.outl('.. code::\\n')\n    cog.outl(indent(help_str))\n\n    ]]]\n\n.. code::\n\n   {{ Content of help(ggwave.decode) will be generated here. }}\n\n..  [[[end]]]\n\n\n-----\nUsage\n-----\n\n* Encode and transmit data with sound:\n\n.. code:: python\n\n    import ggwave\n    import pyaudio\n\n    p = pyaudio.PyAudio()\n\n    # generate audio waveform for string \"hello python\"\n    waveform = ggwave.encode(\"hello python\", protocolId = 1, volume = 20)\n\n    print(\"Transmitting text 'hello python' ...\")\n    stream = p.open(format=pyaudio.paFloat32, channels=1, rate=48000, output=True, frames_per_buffer=4096)\n    stream.write(waveform, len(waveform)//4)\n    stream.stop_stream()\n    stream.close()\n\n    p.terminate()\n\n* Capture and decode audio data:\n\n.. code:: python\n\n    import ggwave\n    import pyaudio\n\n    p = pyaudio.PyAudio()\n\n    stream = p.open(format=pyaudio.paFloat32, channels=1, rate=48000, input=True, frames_per_buffer=1024)\n\n    print('Listening ... Press Ctrl+C to stop')\n    instance = ggwave.init()\n\n    try:\n        while True:\n            data = stream.read(1024, exception_on_overflow=False)\n            res = ggwave.decode(instance, data)\n            if (not res is None):\n                try:\n                    print('Received text: ' + res.decode(\"utf-8\"))\n                except:\n                    pass\n    except KeyboardInterrupt:\n        pass\n\n    ggwave.free(instance)\n\n    stream.stop_stream()\n    stream.close()\n\n    p.terminate()\n\n----\nMore\n----\n\nCheck out `<http://github.com/ggerganov/ggwave>`_ for more information about ggwave!\n\n-----------\nDevelopment\n-----------\n\nCheck out `ggwave python package on Github <https://github.com/ggerganov/ggwave/tree/master/bindings/python>`_.\n"
  },
  {
    "path": "bindings/python/README.md",
    "content": "# ggwave python package\n\nThis 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/).\n\nREADME.rst is not commited to git because it is generated from [README-tmpl.rst](./README-tmpl.rst).\n\n\n## Building\n\nRun `make build` to generate an extension module as .so file.\nYou 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).\nThis is useful for testing while developing.\n\nRun `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`.\nUse this to check that tarball is well structured and contains all needed files, before you publish.\nGood 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.\n\n`make clean` removes all generated files.\n\nREADME.rst is auto-generated from [README-tmpl.rst](./README-tmpl.rst), to run regeneration do `make README.rst`.\nREADME.rst is also automatically regenerated when building package (e.g. `make build`).\nThis enables us to always have up to date results of code execution and help documentation of ggwave methods in readme.\n\n## Publishing\n\nRemember to update version in setup.py before publishing.\n\nTo 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.\n\nYou can also publish new version manually if needed: run `make publish` to create a source distribution and publish it to the PyPI.\n\n## Acknowledgments\n\nThese Python bindings are generated by following [edlib](https://github.com/Martinsos/edlib) example\n"
  },
  {
    "path": "bindings/python/cggwave.pxd",
    "content": "cdef extern from \"ggwave.h\" nogil:\n\n    ctypedef enum ggwave_SampleFormat:\n        GGWAVE_SAMPLE_FORMAT_UNDEFINED,\n        GGWAVE_SAMPLE_FORMAT_U8,\n        GGWAVE_SAMPLE_FORMAT_I8,\n        GGWAVE_SAMPLE_FORMAT_U16,\n        GGWAVE_SAMPLE_FORMAT_I16,\n        GGWAVE_SAMPLE_FORMAT_F32\n\n    ctypedef enum ggwave_ProtocolId:\n        GGWAVE_PROTOCOL_AUDIBLE_NORMAL,\n        GGWAVE_PROTOCOL_AUDIBLE_FAST,\n        GGWAVE_PROTOCOL_AUDIBLE_FASTEST,\n        GGWAVE_PROTOCOL_ULTRASOUND_NORMAL,\n        GGWAVE_PROTOCOL_ULTRASOUND_FAST,\n        GGWAVE_PROTOCOL_ULTRASOUND_FASTEST,\n        GGWAVE_PROTOCOL_DT_NORMAL,\n        GGWAVE_PROTOCOL_DT_FAST,\n        GGWAVE_PROTOCOL_DT_FASTEST,\n        GGWAVE_PROTOCOL_MT_NORMAL,\n        GGWAVE_PROTOCOL_MT_FAST,\n        GGWAVE_PROTOCOL_MT_FASTEST,\n\n        GGWAVE_PROTOCOL_CUSTOM_0,\n        GGWAVE_PROTOCOL_CUSTOM_1,\n        GGWAVE_PROTOCOL_CUSTOM_2,\n        GGWAVE_PROTOCOL_CUSTOM_3,\n        GGWAVE_PROTOCOL_CUSTOM_4,\n        GGWAVE_PROTOCOL_CUSTOM_5,\n        GGWAVE_PROTOCOL_CUSTOM_6,\n        GGWAVE_PROTOCOL_CUSTOM_7,\n        GGWAVE_PROTOCOL_CUSTOM_8,\n        GGWAVE_PROTOCOL_CUSTOM_9\n\n    enum:\n        GGWAVE_OPERATING_MODE_RX,\n        GGWAVE_OPERATING_MODE_TX,\n        GGWAVE_OPERATING_MODE_RX_AND_TX,\n        GGWAVE_OPERATING_MODE_TX_ONLY_TONES,\n        GGWAVE_OPERATING_MODE_USE_DSS\n\n    ctypedef struct ggwave_Parameters:\n        int payloadLength\n        float sampleRateInp\n        float sampleRateOut\n        float sampleRate\n        int samplesPerFrame\n        float soundMarkerThreshold\n        ggwave_SampleFormat sampleFormatInp\n        ggwave_SampleFormat sampleFormatOut\n        int operatingMode\n\n    ctypedef int ggwave_Instance\n\n    ggwave_Parameters ggwave_getDefaultParameters();\n\n    ggwave_Instance ggwave_init(const ggwave_Parameters parameters);\n\n    void ggwave_free(ggwave_Instance instance);\n\n    int ggwave_encode(\n            ggwave_Instance instance,\n            const void * payloadBuffer,\n            int payloadSize,\n            ggwave_ProtocolId protocolId,\n            int volume,\n            void * waveformBuffer,\n            int query);\n\n    int ggwave_decode(\n            ggwave_Instance instance,\n            const void * waveformBuffer,\n            int waveformSize,\n            void * payloadBuffer);\n\n    void ggwave_setLogFile(void * fptr);\n\n    void ggwave_rxToggleProtocol(\n            ggwave_ProtocolId protocolId,\n            int state);\n\n    void ggwave_txToggleProtocol(\n            ggwave_ProtocolId protocolId,\n            int state);\n"
  },
  {
    "path": "bindings/python/debian/changelog",
    "content": "ggwave (0.4.2-0) unstable; urgency=low\n\n  * Initial release\n\n --  Georgi Gerganov <ggerganov@gmail.com>  Sat, 15 Mar 2025 11:17:22 +0000\n"
  },
  {
    "path": "bindings/python/debian/control",
    "content": "Source: ggwave\nSection: python\nPriority: optional\nMaintainer: Georgi Gerganov <ggerganov@gmail.com>\nBuild-Depends: debhelper-compat (= 13),\n               dh-python,\n               cython3,\n               python3-all-dev,\n               python3-setuptools,\nStandards-Version: 4.6.2.0\nTestsuite: autopkgtest-pkg-pybuild\nHomepage: https://github.com/ggerganov/ggwave\nRules-Requires-Root: no\n\nPackage: python3-ggwave\nArchitecture: any\nDepends: ${misc:Depends}, ${python3:Depends}, ${shlibs:Depends},\nDescription: Tiny data-over-sound library\n Tiny data-over-sound library.\n .\n .\n  .. code:: python\n .\n     # generate audio waveform for string \"hello python\"\n     waveform = ggwave.encode(\"hello python\")\n .\n     # decode audio waveform\n     text = ggwave.decode(instance, waveform)\n .\n .\n --------\n Features\n --------\n .\n  * Audible and ultrasound transmissions available\n  * Bandwidth of 8-16 bytes/s (depending on the transmission protocol)\n  * Robust FSK modulation\n  * Reed-Solomon based error correction\n .\n ------------\n"
  },
  {
    "path": "bindings/python/debian/copyright",
    "content": "Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/\nUpstream-Name: ggwave\nUpstream-Contact: Georgi Gerganov <ggerganov@gmail.com>\nSource: https://github.com/ggerganov/ggwave\n\nFiles: *\nCopyright: Georgi Gerganov <ggerganov@gmail.com>\nLicense: MIT\n\nFiles: debian/*\nCopyright: Georgi Gerganov <ggerganov@gmail.com>\nLicense: MIT\n\nLicense: MIT\n"
  },
  {
    "path": "bindings/python/debian/python3-ggwave.docs",
    "content": "README.rst\n"
  },
  {
    "path": "bindings/python/debian/rules",
    "content": "#! /usr/bin/make -f\n\nexport PYBUILD_NAME=ggwave\n%:\n\tdh $@ --with python3 --buildsystem=pybuild\n"
  },
  {
    "path": "bindings/python/debian/source/format",
    "content": "3.0 (quilt)\n"
  },
  {
    "path": "bindings/python/debian/source/options",
    "content": "extend-diff-ignore=\"^[^/]+.(egg-info|dist-info)/\"\n"
  },
  {
    "path": "bindings/python/ggwave.pyx",
    "content": "# cython: language_level=3str\ncimport cython\n\nfrom libc.stdio cimport stderr\nfrom cpython.mem cimport PyMem_Malloc, PyMem_Free\n\nimport re\n\ncimport cggwave\n\ndef getDefaultParameters():\n    return cggwave.ggwave_getDefaultParameters()\n\ndef init(parameters = None):\n    if (parameters is None):\n        parameters = getDefaultParameters()\n\n    return cggwave.ggwave_init(parameters)\n\ndef free(instance):\n    return cggwave.ggwave_free(instance)\n\ndef encode(payload, protocolId = 1, volume = 10, instance = None):\n    \"\"\" Encode payload into an audio waveform.\n        @param {string} payload, the data to be encoded\n        @return Generated audio waveform bytes representing 16-bit signed integer samples.\n    \"\"\"\n\n    if isinstance(payload, str):\n        payload = payload.encode('utf-8')\n    cdef bytes data_bytes = payload\n\n    cdef char* cdata = data_bytes\n\n    own = False\n    if (instance is None):\n        own = True\n        instance = init(getDefaultParameters())\n\n    n = cggwave.ggwave_encode(instance, cdata, len(data_bytes), protocolId, volume, NULL, 1)\n\n    cdef bytes output_bytes = bytes(n)\n    cdef char* coutput = output_bytes\n\n    n = cggwave.ggwave_encode(instance, cdata, len(data_bytes), protocolId, volume, coutput, 0)\n\n    if (own):\n        free(instance)\n\n    return output_bytes\n\ndef decode(instance, waveform):\n    \"\"\" Analyze and decode audio waveform to obtain original payload\n        @param {bytes} waveform, the audio waveform to decode\n        @return The decoded payload if successful.\n    \"\"\"\n\n    cdef bytes data_bytes = waveform\n    cdef char* cdata = data_bytes\n\n    cdef bytes output_bytes = bytes(256)\n    cdef char* coutput = output_bytes\n\n    rxDataLength = cggwave.ggwave_decode(instance, cdata, len(data_bytes), coutput)\n\n    if (rxDataLength > 0):\n        return coutput[0:rxDataLength]\n\n    return None\n\ndef disableLog():\n    cggwave.ggwave_setLogFile(NULL);\n\ndef enableLog():\n    cggwave.ggwave_setLogFile(stderr);\n\ndef rxToggleProtocol(protocolId, state):\n    cggwave.ggwave_rxToggleProtocol(protocolId, state);\n\ndef txToggleProtocol(protocolId, state):\n    cggwave.ggwave_txToggleProtocol(protocolId, state);\n"
  },
  {
    "path": "bindings/python/setup-tmpl.py",
    "content": "from setuptools import setup, Extension\nfrom codecs import open\nimport os\n\ncmdclass = {}\nlong_description = \"\"\n\n# Build directly from cython source file(s) if user wants so (probably for some experiments).\n# Otherwise, pre-generated c source file(s) are used.\n# User has to set environment variable GGWAVE_USE_CYTHON.\n# e.g.: GGWAVE_USE_CYTHON=1 python setup.py install\nUSE_CYTHON = os.getenv('GGWAVE_USE_CYTHON', False)\nif USE_CYTHON:\n    from Cython.Build import build_ext\n    ggwave_module_src = \"ggwave.pyx\"\n    cmdclass['build_ext'] = build_ext\nelse:\n    ggwave_module_src = \"ggwave.bycython.cpp\"\n\n# Load README.rst into long description.\n# User can skip using README.rst as long description: GGWAVE_OMIT_README_RST=1 python setup.py install\nOMIT_README_RST = os.getenv('GGWAVE_OMIT_README_RST', False)\nif not OMIT_README_RST:\n    here = os.path.abspath(os.path.dirname(__file__))\n    with open(os.path.join(here, 'README.rst'), encoding='utf-8') as f:\n        long_description = f.read()\n\nsetup(\n    # Information\n    name = \"ggwave\",\n    description = \"Tiny data-over-sound library.\",\n    long_description = long_description,\n    version = \"@GGWAVE_VERSION_PYTHON@\",\n    url = \"https://github.com/ggerganov/ggwave\",\n    author = \"Georgi Gerganov\",\n    author_email = \"ggerganov@gmail.com\",\n    license = \"MIT\",\n    keywords = \"data-over-sound fsk ecc serverless pairing qrcode ultrasound\",\n    # Build instructions\n    ext_modules = [Extension(\"ggwave\",\n                             [ggwave_module_src, \"ggwave/src/ggwave.cpp\"],\n                             include_dirs=[\"ggwave/include\", \"ggwave/include/ggwave\"],\n                             depends=[\"ggwave/include/ggwave/ggwave.h\"],\n                             language=\"c++\",\n                             extra_compile_args=[\"-O3\", \"-std=c++11\"])],\n    cmdclass = cmdclass\n)\n"
  },
  {
    "path": "bindings/python/setup.py",
    "content": "from setuptools import setup, Extension\nfrom codecs import open\nimport os\n\ncmdclass = {}\nlong_description = \"\"\n\n# Build directly from cython source file(s) if user wants so (probably for some experiments).\n# Otherwise, pre-generated c source file(s) are used.\n# User has to set environment variable GGWAVE_USE_CYTHON.\n# e.g.: GGWAVE_USE_CYTHON=1 python setup.py install\nUSE_CYTHON = os.getenv('GGWAVE_USE_CYTHON', False)\nif USE_CYTHON:\n    from Cython.Build import build_ext\n    ggwave_module_src = \"ggwave.pyx\"\n    cmdclass['build_ext'] = build_ext\nelse:\n    ggwave_module_src = \"ggwave.bycython.cpp\"\n\n# Load README.rst into long description.\n# User can skip using README.rst as long description: GGWAVE_OMIT_README_RST=1 python setup.py install\nOMIT_README_RST = os.getenv('GGWAVE_OMIT_README_RST', False)\nif not OMIT_README_RST:\n    here = os.path.abspath(os.path.dirname(__file__))\n    with open(os.path.join(here, 'README.rst'), encoding='utf-8') as f:\n        long_description = f.read()\n\nsetup(\n    # Information\n    name = \"ggwave\",\n    description = \"Tiny data-over-sound library.\",\n    long_description = long_description,\n    version = \"0.4.2\",\n    url = \"https://github.com/ggerganov/ggwave\",\n    author = \"Georgi Gerganov\",\n    author_email = \"ggerganov@gmail.com\",\n    license = \"MIT\",\n    keywords = \"data-over-sound fsk ecc serverless pairing qrcode ultrasound\",\n    # Build instructions\n    ext_modules = [Extension(\"ggwave\",\n                             [ggwave_module_src, \"ggwave/src/ggwave.cpp\"],\n                             include_dirs=[\"ggwave/include\", \"ggwave/include/ggwave\"],\n                             depends=[\"ggwave/include/ggwave/ggwave.h\"],\n                             language=\"c++\",\n                             extra_compile_args=[\"-O3\", \"-std=c++11\"])],\n    cmdclass = cmdclass\n)\n"
  },
  {
    "path": "bindings/python/test.py",
    "content": "import sys\nimport ggwave\n\ntestFailed = False\n\nggwave.disableLog()\nggwave.enableLog()\n\nsamples = ggwave.encode(\"hello python\")\n\nif not (samples):\n    testFailed = True\n\nif testFailed:\n    print(\"Some of the tests failed!\")\nelse:\n    print(\"All tests passed!\")\n\nsys.exit(testFailed)\n"
  },
  {
    "path": "cmake/BuildTypes.cmake",
    "content": "# Add new build types\n\n# ReleaseGG - Release with enabled asserts\n\nSET(CMAKE_CXX_FLAGS_RELEASEGG\n    \"-O3\"\n    CACHE STRING \"Flags used by the c++ compiler during release builds with enabled asserts.\"\n    FORCE )\nSET(CMAKE_C_FLAGS_RELEASEGG\n    \"-O3\"\n    CACHE STRING \"Flags used by the compiler during release builds with enabled asserts.\"\n    FORCE )\nSET(CMAKE_EXE_LINKER_FLAGS_RELEASEGG\n    \"\"\n    CACHE STRING \"Flags used for linking binaries during release builds with enabled asserts.\"\n    FORCE )\nSET(CMAKE_SHARED_LINKER_FLAGS_RELEASEGG\n    \"\"\n    CACHE STRING \"Flags used by the shared libraries linker during release builds with enabled asserts.\"\n    FORCE )\nMARK_AS_ADVANCED(\n    CMAKE_CXX_FLAGS_RELEASEGG\n    CMAKE_C_FLAGS_RELEASEGG\n    CMAKE_EXE_LINKER_FLAGS_RELEASEGG\n    CMAKE_SHARED_LINKER_FLAGS_RELEASEGG )\n\n# RelWithDebInfoGG - RelWithDebInfo with enabled asserts\n\nSET(CMAKE_CXX_FLAGS_RELWITHDEBINFOGG\n    \"-O2 -g\"\n    CACHE STRING \"Flags used by the c++ compiler during release builds with debug symbols and enabled asserts.\"\n    FORCE )\nSET(CMAKE_C_FLAGS_RELWITHDEBINFOGG\n    \"-O2 -g\"\n    CACHE STRING \"Flags used by the compiler during release builds with debug symbols and enabled asserts.\"\n    FORCE )\nSET(CMAKE_EXE_LINKER_FLAGS_RELWITHDEBINFOGG\n    \"\"\n    CACHE STRING \"Flags used for linking binaries during release builds with debug symbols and enabled asserts.\"\n    FORCE )\nSET(CMAKE_SHARED_LINKER_FLAGS_RELWITHDEBINFOGG\n    \"\"\n    CACHE STRING \"Flags used by the shared libraries linker during release builds with debug symbols and enabled asserts.\"\n    FORCE )\nMARK_AS_ADVANCED(\n    CMAKE_CXX_FLAGS_RELWITHDEBINFOGG\n    CMAKE_C_FLAGS_RELWITHDEBINFOGG\n    CMAKE_EXE_LINKER_FLAGS_RELWITHDEBINFOGG\n    CMAKE_SHARED_LINKER_FLAGS_RELWITHDEBINFOGG )\n\nif (NOT XCODE AND NOT MSVC AND NOT CMAKE_BUILD_TYPE)\n    set(CMAKE_BUILD_TYPE Release CACHE STRING \"Build type\" FORCE)\n    set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS \"Debug\" \"Release\" \"MinSizeRel\" \"RelWithDebInfo\" \"ReleaseGG\" \"RelWithDebInfoGG\")\nendif()\n"
  },
  {
    "path": "cmake/GitVars.cmake",
    "content": "find_package(Git)\n\n# the commit's SHA1\nexecute_process(COMMAND\n    \"${GIT_EXECUTABLE}\" describe --match=NeVeRmAtCh --always --abbrev=8\n    WORKING_DIRECTORY \"${CMAKE_SOURCE_DIR}\"\n    OUTPUT_VARIABLE GIT_SHA1\n    ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE)\n\n# the date of the commit\nexecute_process(COMMAND\n    \"${GIT_EXECUTABLE}\" log -1 --format=%ad --date=local\n    WORKING_DIRECTORY \"${CMAKE_SOURCE_DIR}\"\n    OUTPUT_VARIABLE GIT_DATE\n    ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE)\n\n# the subject of the commit\nexecute_process(COMMAND\n    \"${GIT_EXECUTABLE}\" log -1 --format=%s\n    WORKING_DIRECTORY \"${CMAKE_SOURCE_DIR}\"\n    OUTPUT_VARIABLE GIT_COMMIT_SUBJECT\n    ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE)\n"
  },
  {
    "path": "cmake/sdl2/FindSDL2.cmake",
    "content": "# Distributed under the OSI-approved BSD 3-Clause License. See accompanying\n# file Copyright.txt or https://cmake.org/licensing for details.\n\n#.rst:\n# FindSDL2\n# -------\n#\n# Locate SDL2 library\n#\n# This module defines\n#\n# ::\n#\n# SDL2_LIBRARY, the name of the library to link against\n# SDL2_FOUND, if false, do not try to link to SDL\n# SDL2_INCLUDE_DIR, where to find SDL.h\n# SDL2_VERSION_STRING, human-readable string containing the version of SDL\n#\n#\n#\n# This module responds to the flag:\n#\n# ::\n#\n# SDL2_BUILDING_LIBRARY\n# If this is defined, then no SDL2_main will be linked in because\n# only applications need main().\n# Otherwise, it is assumed you are building an application and this\n# module will attempt to locate and set the proper link flags\n# as part of the returned SDL2_LIBRARY variable.\n#\n#\n#\n# Don't forget to include SDLmain.h and SDLmain.m your project for the\n# OS X framework based version. (Other versions link to -lSDLmain which\n# this module will try to find on your behalf.) Also for OS X, this\n# module will automatically add the -framework Cocoa on your behalf.\n#\n#\n#\n# Additional Note: If you see an empty SDL2_LIBRARY_TEMP in your\n# configuration and no SDL2_LIBRARY, it means CMake did not find your SDL\n# library (SDL.dll, libsdl.so, SDL.framework, etc). Set\n# SDL2_LIBRARY_TEMP to point to your SDL library, and configure again.\n# Similarly, if you see an empty SDLMAIN_LIBRARY, you should set this\n# value as appropriate. These values are used to generate the final\n# SDL2_LIBRARY variable, but when these values are unset, SDL2_LIBRARY\n# does not get created.\n#\n#\n#\n# $SDLDIR is an environment variable that would correspond to the\n# ./configure --prefix=$SDLDIR used in building SDL. l.e.galup 9-20-02\n#\n# Modified by Eric Wing. Added code to assist with automated building\n# by using environmental variables and providing a more\n# controlled/consistent search behavior. Added new modifications to\n# recognize OS X frameworks and additional Unix paths (FreeBSD, etc).\n# Also corrected the header search path to follow \"proper\" SDL\n# guidelines. Added a search for SDLmain which is needed by some\n# platforms. Added a search for threads which is needed by some\n# platforms. Added needed compile switches for MinGW.\n#\n# On OSX, this will prefer the Framework version (if found) over others.\n# People will have to manually change the cache values of SDL2_LIBRARY to\n# override this selection or set the CMake environment\n# CMAKE_INCLUDE_PATH to modify the search paths.\n#\n# Note that the header path has changed from SDL/SDL.h to just SDL.h\n# This needed to change because \"proper\" SDL convention is #include\n# \"SDL.h\", not <SDL/SDL.h>. This is done for portability reasons\n# because not all systems place things in SDL/ (see FreeBSD).\n\nif(NOT SDL2_DIR)\n  set(SDL2_DIR \"\" CACHE PATH \"SDL2 directory\")\nendif()\n\nfind_path(SDL2_INCLUDE_DIR SDL_scancode.h\n  HINTS\n    ENV SDLDIR\n    ${SDL2_DIR}\n  PATH_SUFFIXES SDL2\n                # path suffixes to search inside ENV{SDLDIR}\n                include/SDL2 include\n)\n\nif(CMAKE_SIZEOF_VOID_P EQUAL 8)\n  set(VC_LIB_PATH_SUFFIX lib/x64)\nelse()\n  set(VC_LIB_PATH_SUFFIX lib/x86)\nendif()\n\n# SDL-1.1 is the name used by FreeBSD ports...\n# don't confuse it for the version number.\nfind_library(SDL2_LIBRARY_TEMP\n  NAMES SDL2\n  HINTS\n    ENV SDLDIR\n    ${SDL2_DIR}\n  PATH_SUFFIXES lib ${VC_LIB_PATH_SUFFIX}\n)\n\n# Hide this cache variable from the user, it's an internal implementation\n# detail. The documented library variable for the user is SDL2_LIBRARY\n# which is derived from SDL2_LIBRARY_TEMP further below.\nset_property(CACHE SDL2_LIBRARY_TEMP PROPERTY TYPE INTERNAL)\n\nif(NOT SDL2_BUILDING_LIBRARY)\n  if(NOT SDL2_INCLUDE_DIR MATCHES \".framework\")\n    # Non-OS X framework versions expect you to also dynamically link to\n    # SDLmain. This is mainly for Windows and OS X. Other (Unix) platforms\n    # seem to provide SDLmain for compatibility even though they don't\n    # necessarily need it.\n    find_library(SDL2MAIN_LIBRARY\n      NAMES SDL2main\n      HINTS\n        ENV SDLDIR\n        ${SDL2_DIR}\n      PATH_SUFFIXES lib ${VC_LIB_PATH_SUFFIX}\n      PATHS\n      /sw\n      /opt/local\n      /opt/csw\n      /opt\n    )\n  endif()\nendif()\n\n# SDL may require threads on your system.\n# The Apple build may not need an explicit flag because one of the\n# frameworks may already provide it.\n# But for non-OSX systems, I will use the CMake Threads package.\nif(NOT APPLE)\n  find_package(Threads)\nendif()\n\n# MinGW needs an additional link flag, -mwindows\n# It's total link flags should look like -lmingw32 -lSDLmain -lSDL -mwindows\nif(MINGW)\n  set(MINGW32_LIBRARY mingw32 \"-mwindows\" CACHE STRING \"link flags for MinGW\")\nendif()\n\nif(SDL2_LIBRARY_TEMP)\n  # For SDLmain\n  if(SDL2MAIN_LIBRARY AND NOT SDL2_BUILDING_LIBRARY)\n    list(FIND SDL2_LIBRARY_TEMP \"${SDL2MAIN_LIBRARY}\" _SDL2_MAIN_INDEX)\n    if(_SDL2_MAIN_INDEX EQUAL -1)\n      set(SDL2_LIBRARY_TEMP \"${SDL2MAIN_LIBRARY}\" ${SDL2_LIBRARY_TEMP})\n    endif()\n    unset(_SDL2_MAIN_INDEX)\n  endif()\n\n  # For OS X, SDL uses Cocoa as a backend so it must link to Cocoa.\n  # CMake doesn't display the -framework Cocoa string in the UI even\n  # though it actually is there if I modify a pre-used variable.\n  # I think it has something to do with the CACHE STRING.\n  # So I use a temporary variable until the end so I can set the\n  # \"real\" variable in one-shot.\n  if(APPLE)\n    set(SDL2_LIBRARY_TEMP ${SDL2_LIBRARY_TEMP} \"-framework Cocoa\")\n  endif()\n\n  # For threads, as mentioned Apple doesn't need this.\n  # In fact, there seems to be a problem if I used the Threads package\n  # and try using this line, so I'm just skipping it entirely for OS X.\n  if(NOT APPLE)\n    set(SDL2_LIBRARY_TEMP ${SDL2_LIBRARY_TEMP} ${CMAKE_THREAD_LIBS_INIT})\n  endif()\n\n  # For MinGW library\n  if(MINGW)\n    set(SDL2_LIBRARY_TEMP ${MINGW32_LIBRARY} ${SDL2_LIBRARY_TEMP})\n  endif()\n\n  # Set the final string here so the GUI reflects the final state.\n  set(SDL2_LIBRARY ${SDL2_LIBRARY_TEMP} CACHE STRING \"Where the SDL Library can be found\")\nendif()\n\nif(SDL2_INCLUDE_DIR AND EXISTS \"${SDL2_INCLUDE_DIR}/SDL2_version.h\")\n  file(STRINGS \"${SDL2_INCLUDE_DIR}/SDL2_version.h\" SDL2_VERSION_MAJOR_LINE REGEX \"^#define[ \\t]+SDL2_MAJOR_VERSION[ \\t]+[0-9]+$\")\n  file(STRINGS \"${SDL2_INCLUDE_DIR}/SDL2_version.h\" SDL2_VERSION_MINOR_LINE REGEX \"^#define[ \\t]+SDL2_MINOR_VERSION[ \\t]+[0-9]+$\")\n  file(STRINGS \"${SDL2_INCLUDE_DIR}/SDL2_version.h\" SDL2_VERSION_PATCH_LINE REGEX \"^#define[ \\t]+SDL2_PATCHLEVEL[ \\t]+[0-9]+$\")\n  string(REGEX REPLACE \"^#define[ \\t]+SDL2_MAJOR_VERSION[ \\t]+([0-9]+)$\" \"\\\\1\" SDL2_VERSION_MAJOR \"${SDL2_VERSION_MAJOR_LINE}\")\n  string(REGEX REPLACE \"^#define[ \\t]+SDL2_MINOR_VERSION[ \\t]+([0-9]+)$\" \"\\\\1\" SDL2_VERSION_MINOR \"${SDL2_VERSION_MINOR_LINE}\")\n  string(REGEX REPLACE \"^#define[ \\t]+SDL2_PATCHLEVEL[ \\t]+([0-9]+)$\" \"\\\\1\" SDL2_VERSION_PATCH \"${SDL2_VERSION_PATCH_LINE}\")\n  set(SDL2_VERSION_STRING ${SDL2_VERSION_MAJOR}.${SDL2_VERSION_MINOR}.${SDL2_VERSION_PATCH})\n  unset(SDL2_VERSION_MAJOR_LINE)\n  unset(SDL2_VERSION_MINOR_LINE)\n  unset(SDL2_VERSION_PATCH_LINE)\n  unset(SDL2_VERSION_MAJOR)\n  unset(SDL2_VERSION_MINOR)\n  unset(SDL2_VERSION_PATCH)\nendif()\n\nset(SDL2_LIBRARIES ${SDL2_LIBRARY})\nset(SDL2_INCLUDE_DIRS ${SDL2_INCLUDE_DIR})\n\nFIND_PACKAGE_HANDLE_STANDARD_ARGS(SDL\n                                  REQUIRED_VARS SDL2_LIBRARIES SDL2_INCLUDE_DIRS\n                                  VERSION_VAR SDL2_VERSION_STRING)\n\nmark_as_advanced(SDL2_LIBRARY SDL2_INCLUDE_DIR)\n\n"
  },
  {
    "path": "examples/CMakeLists.txt",
    "content": "# dependencies\n\nfind_package(Threads REQUIRED)\n\nif (GGWAVE_SUPPORT_SDL2)\n    # SDL2\n\n    if (EMSCRIPTEN)\n        set(CMAKE_CXX_FLAGS \" \\\n        -s USE_SDL=2 \\\n        -s DISABLE_EXCEPTION_CATCHING=0 \\\n        \")\n\n        set(CMAKE_CXX_LINK_FLAGS \" \\\n        --bind \\\n        -s TOTAL_MEMORY=67108864 \\\n        -s ASSERTIONS=1 \\\n        -s 'EXPORTED_RUNTIME_METHODS=[\\\"writeArrayToMemory\\\"]' \\\n        \")\n\n        unset(SDL2_INCLUDE_DIRS)\n        unset(SDL2_LIBRARIES)\n    endif()\n\n    if (NOT EMSCRIPTEN)\n        find_package(SDL2)\n\n        if (NOT USE_FINDSDL2 AND NOT SDL2_FOUND)\n            message(WARNING \"Unable to find SDL2 library. It is either not installed or CMake cannot find it.\"\n                \" In the latter case, setting the USE_FINDSDL2 variable might help:\\n\"\n                \"   $ cmake -D USE_FINDSDL2 ..\"\n                )\n\n            message(FATAL_ERROR \"Aborting\")\n        endif()\n\n        string(STRIP \"${SDL2_LIBRARIES}\" SDL2_LIBRARIES)\n\n        message(STATUS \"SDL2_INCLUDE_DIRS = ${SDL2_INCLUDE_DIRS}\")\n        message(STATUS \"SDL2_LIBRARIES = ${SDL2_LIBRARIES}\")\n    endif()\nendif()\n\n# third-party\n\nadd_subdirectory(third-party)\n\n# helper libraries\n\nadd_library(ggwave-common\n    ggwave-common.cpp\n    )\n\ntarget_link_libraries(ggwave-common PRIVATE\n    ${CMAKE_DL_LIBS}\n    )\n\nif (MINGW)\n    target_link_libraries(ggwave-common PUBLIC\n        stdc++\n    )\nendif()\n\nif (GGWAVE_SUPPORT_SDL2)\n    # ggwave-common-sdl2\n\n    add_library(ggwave-common-sdl2\n        ggwave-common-sdl2.cpp\n        )\n\n    target_include_directories(ggwave-common-sdl2 PUBLIC\n        ${SDL2_INCLUDE_DIRS}\n        )\n\n    target_link_libraries(ggwave-common-sdl2 PRIVATE\n        ggwave\n        imgui-sdl2\n        ${SDL2_LIBRARIES}\n        )\nendif()\n\n# examples\n\nif (EMSCRIPTEN)\n    add_subdirectory(ggwave-js)\n    add_subdirectory(buttons)\nelse()\n    add_subdirectory(ggwave-to-file)\n    add_subdirectory(ggwave-from-file)\n\n    add_subdirectory(arduino-rx)\n    add_subdirectory(arduino-tx)\n    add_subdirectory(arduino-tx-obsolete)\n    add_subdirectory(esp32-rx)\n    add_subdirectory(rp2040-rx)\nendif()\n\nif (GGWAVE_SUPPORT_SDL2)\n    if (UNIX AND NOT APPLE)\n        add_subdirectory(r2t2)\n    endif()\n\n    add_subdirectory(arduino-rx-web)\n    if (EMSCRIPTEN)\n        # emscripten sdl2 examples\n\n        add_subdirectory(ggwave-wasm)\n    else()\n        # non-emscripten sdl2 examples\n\n        add_subdirectory(ggwave-rx)\n        add_subdirectory(ggwave-cli)\n    endif()\n\n    add_subdirectory(waver)\n    add_subdirectory(spectrogram)\nendif()\n\ninstall(TARGETS ggwave-common\n    LIBRARY DESTINATION lib\n    ARCHIVE DESTINATION lib/static\n    )\n\nif (GGWAVE_SUPPORT_SDL2)\n    install(TARGETS ggwave-common-sdl2\n        LIBRARY DESTINATION lib\n        ARCHIVE DESTINATION lib/static\n        )\nendif()\n"
  },
  {
    "path": "examples/arduino-rx/.gitignore",
    "content": "ggwave\nggwave.cpp\nfft.h\nresampler.h\nresampler.cpp\nreed-solomon\n"
  },
  {
    "path": "examples/arduino-rx/CMakeLists.txt",
    "content": "#\n# arduino-rx\n\n#configure_file(${CMAKE_SOURCE_DIR}/include/ggwave/ggwave.h   ${CMAKE_CURRENT_SOURCE_DIR}/ggwave.h              COPYONLY)\n#configure_file(${CMAKE_SOURCE_DIR}/src/ggwave.cpp            ${CMAKE_CURRENT_SOURCE_DIR}/ggwave.cpp            COPYONLY)\n#configure_file(${CMAKE_SOURCE_DIR}/src/fft.h                 ${CMAKE_CURRENT_SOURCE_DIR}/fft.h                 COPYONLY)\n#configure_file(${CMAKE_SOURCE_DIR}/src/reed-solomon/gf.hpp   ${CMAKE_CURRENT_SOURCE_DIR}/reed-solomon/gf.hpp   COPYONLY)\n#configure_file(${CMAKE_SOURCE_DIR}/src/reed-solomon/rs.hpp   ${CMAKE_CURRENT_SOURCE_DIR}/reed-solomon/rs.hpp   COPYONLY)\n#configure_file(${CMAKE_SOURCE_DIR}/src/reed-solomon/poly.hpp ${CMAKE_CURRENT_SOURCE_DIR}/reed-solomon/poly.hpp COPYONLY)\n#configure_file(${CMAKE_SOURCE_DIR}/src/reed-solomon/LICENSE  ${CMAKE_CURRENT_SOURCE_DIR}/reed-solomon/LICENSE  COPYONLY)\n"
  },
  {
    "path": "examples/arduino-rx/README.md",
    "content": "# arduino-rx\n\nThis is a sample project for receiving and transmitting audio data using [Arduino RP2040](https://docs.arduino.cc/hardware/nano-rp2040-connect) microcontroller.\nThe development board has a built-in microphone which makes this device very suitable for data-over-sound projects.\n\n## Setup\n\n- Arduino RP2040 Connect\n- OLED SSD1306\n- Generic speaker\n\n## Pinout for Arduino Nano RP2040 Connect\n\n### I2C Display (optional)\n\n| MCU           | Display   |\n| ------------- | --------- |\n| GND           | GND       |\n| 3.3V          | VCC / VDD |\n| D18 / GPIO 12 | SDA       |\n| D19 / GPIO 13 | SCL       |\n\n### Peripherals (optional)\n\n| MCU           | Periph. |\n| ------------- | ------- |\n|  D5 / GPIO 17 | Button  |\n| D10 / GPIO  5 | Speaker |\n\n![Sketch-Breadboard](fritzing-sketch_bb.png)\n\n![Sketch-Photo](https://user-images.githubusercontent.com/1991296/177850326-e5fefde3-93ee-4cf9-8fa5-861eef9565f7.JPEG)\n\n## Demo\n\nhttps://user-images.githubusercontent.com/1991296/177210657-3c7421ce-5c12-4caf-a86c-251191eefe50.mp4\n\n[Watch high quality on Youtube](https://youtu.be/HiDpGvnxPLs)\n"
  },
  {
    "path": "examples/arduino-rx/arduino-rx.ino",
    "content": "// arduino-rx\n//\n// Sample sketch for receiving data using \"ggwave\"\n//\n// Tested with:\n//   - Arduino Nano RP2040 Connect\n//\n// The Arduino Nano RP2040 Connect board has a built-in microphone which is used\n// in this example to capture audio data.\n//\n// The sketch optionally supports displaying the received \"ggwave\" data on an OLED display.\n// Use the DISPLAY_OUTPUT macro to enable or disable this functionality.\n//\n// If you don't have a display, you can simply observe the decoded data in the serial monitor.\n//\n// If you want to perform a quick test, you can use the free \"Waver\" application:\n//   - Web:     https://waver.ggerganov.com\n//   - Android: https://play.google.com/store/apps/details?id=com.ggerganov.Waver\n//   - iOS:     https://apps.apple.com/us/app/waver-data-over-sound/id1543607865\n//\n// Make sure to enable the \"Fixed-length\" option in \"Waver\"'s settings and set the number of\n// bytes to be equal to \"payloadLength\" used in the sketch. Also, select a protocol that is\n// listed as Rx in the current sketch.\n//\n// Demo: https://youtu.be/HiDpGvnxPLs\n//\n// Sketch: https://github.com/ggerganov/ggwave/tree/master/examples/arduino-rx\n//\n// ## Pinout for Arduino Nano RP2040 Connect\n//\n// ### I2C Display (optional)\n//\n// | MCU           | Display   |\n// | ------------- | --------- |\n// | GND           | GND       |\n// | 3.3V          | VCC / VDD |\n// | D18 / GPIO 12 | SDA       |\n// | D19 / GPIO 13 | SCL       |\n//\n// ### Peripherals (optional)\n//\n// | MCU           | Periph. |\n// | ------------- | ------- |\n// |  D5 / GPIO 17 | Button  |\n// | D10 / GPIO  5 | Speaker |\n//\n\n// Uncoment this line to enable SSD1306 display output\n//#define DISPLAY_OUTPUT 1\n\n// Uncoment this line to enable long-range transmission\n// The used protocols are slower and use more memory to decode, but are much more robust\n//#define LONG_RANGE 1\n\n#include <ggwave.h>\n\n#include <PDM.h>\n\n// Pin configuration\nconst int kPinLED0    = 2;\nconst int kPinButton0 = 5;\nconst int kPinSpeaker = 10;\n\n// Audio capture configuration\nusing TSample = int16_t;\nconst size_t kSampleSize_bytes = sizeof(TSample);\n\nconst char channels        = 1;\nconst int  sampleRate      = 6000;\nconst int  samplesPerFrame = 128;\n\n// Audio capture ring-buffer\nconst int qpow = 9;\nconst int qmax = 1 << qpow;\n\nvolatile int qhead = 0;\nvolatile int qtail = 0;\nvolatile int qsize = 0;\n\nTSample sampleBuffer[qmax];\n\n// Error handling\nvolatile int err = 0;\n\n// Global GGwave instance\nGGWave ggwave;\n\n#ifdef DISPLAY_OUTPUT\n\n#include <SPI.h>\n#include <Wire.h>\n#include <Adafruit_GFX.h>\n#include <Adafruit_SSD1306.h>\n\n#define SCREEN_WIDTH 128 // OLED display width, in pixels\n#define SCREEN_HEIGHT 32 // OLED display height, in pixels\n\n// Declaration for an SSD1306 display connected to I2C (SDA, SCL pins)\n// The pins for I2C are defined by the Wire-library.\n// On an arduino UNO:       A4(SDA), A5(SCL)\n// On an arduino MEGA 2560: 20(SDA), 21(SCL)\n// On an arduino LEONARDO:   2(SDA),  3(SCL), ...\n#define OLED_RESET     -1 // Reset pin # (or -1 if sharing Arduino reset pin)\n#define SCREEN_ADDRESS 0x3C ///< See datasheet for Address; 0x3D for 128x64, 0x3C for 128x32\nAdafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);\n\n#endif\n\n// Helper function to output the generated GGWave waveform via a buzzer\nvoid send_text(GGWave & ggwave, uint8_t pin, const char * text, GGWave::TxProtocolId protocolId) {\n    Serial.print(F(\"Sending text: \"));\n    Serial.println(text);\n\n    ggwave.init(text, protocolId);\n    ggwave.encode();\n\n    const auto & protocol = GGWave::Protocols::tx()[protocolId];\n    const auto tones = ggwave.txTones();\n    const auto duration_ms = protocol.txDuration_ms(ggwave.samplesPerFrame(), ggwave.sampleRateOut());\n    for (auto & curTone : tones) {\n        const auto freq_hz = (protocol.freqStart + curTone)*ggwave.hzPerSample();\n        tone(pin, freq_hz);\n        delay(duration_ms);\n    }\n\n    noTone(pin);\n    digitalWrite(pin, LOW);\n}\n\nvoid setup() {\n    Serial.begin(57600);\n    while (!Serial);\n\n    pinMode(kPinLED0, OUTPUT);\n    pinMode(kPinSpeaker, OUTPUT);\n    pinMode(kPinButton0, INPUT_PULLUP);\n\n    digitalWrite(kPinLED0, LOW);\n\n#ifdef DISPLAY_OUTPUT\n    {\n        Serial.println(F(\"Initializing display...\"));\n\n        // SSD1306_SWITCHCAPVCC = generate display voltage from 3.3V internally\n        if(!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) {\n            Serial.println(F(\"SSD1306 allocation failed\"));\n            for(;;); // Don't proceed, loop forever\n        }\n\n        // Show initial display buffer contents on the screen --\n        // the library initializes this with an Adafruit splash screen.\n        //display.display();\n        //delay(2000); // Pause for 2 seconds\n\n        // Clear the buffer\n        display.clearDisplay();\n\n        display.setTextSize(2);\n        display.setTextColor(SSD1306_WHITE); // Draw white text\n        display.setCursor(0, 0);     // Start at top-left corner\n        display.println(F(\"GGWave!\"));\n        display.setTextSize(1);\n        display.println(F(\"\"));\n        display.println(F(\"Listening...\"));\n\n        display.display();\n    }\n#endif\n\n    // Initialize \"ggwave\"\n    {\n        Serial.println(F(\"Trying to initialize the ggwave instance\"));\n\n        ggwave.setLogFile(nullptr);\n\n        auto p = GGWave::getDefaultParameters();\n\n        // Adjust the \"ggwave\" parameters to your needs.\n        // Make sure that the \"payloadLength\" parameter matches the one used on the transmitting side.\n#ifdef LONG_RANGE\n        // The \"FAST\" protocols require 2x more memory, so we reduce the payload length to compensate:\n        p.payloadLength   = 8;\n#else\n        p.payloadLength   = 16;\n#endif\n        Serial.print(F(\"Using payload length: \"));\n        Serial.println(p.payloadLength);\n\n        p.sampleRateInp   = sampleRate;\n        p.sampleRateOut   = sampleRate;\n        p.sampleRate      = sampleRate;\n        p.samplesPerFrame = samplesPerFrame;\n        p.sampleFormatInp = GGWAVE_SAMPLE_FORMAT_I16;\n        p.sampleFormatOut = GGWAVE_SAMPLE_FORMAT_U8;\n        p.operatingMode   = GGWAVE_OPERATING_MODE_RX | GGWAVE_OPERATING_MODE_TX | GGWAVE_OPERATING_MODE_USE_DSS | GGWAVE_OPERATING_MODE_TX_ONLY_TONES;\n\n        // Protocols to use for TX\n        // Remove the ones that you don't need to reduce memory usage\n        GGWave::Protocols::tx().disableAll();\n        //GGWave::Protocols::tx().toggle(GGWAVE_PROTOCOL_DT_NORMAL,  true);\n        //GGWave::Protocols::tx().toggle(GGWAVE_PROTOCOL_DT_FAST,    true);\n        //GGWave::Protocols::tx().toggle(GGWAVE_PROTOCOL_DT_FASTEST, true);\n        //GGWave::Protocols::tx().toggle(GGWAVE_PROTOCOL_MT_NORMAL,  true);\n        //GGWave::Protocols::tx().toggle(GGWAVE_PROTOCOL_MT_FAST,    true);\n        GGWave::Protocols::tx().toggle(GGWAVE_PROTOCOL_MT_FASTEST, true);\n        //GGWave::Protocols::tx()[GGWAVE_PROTOCOL_MT_FASTEST].freqStart += 48;\n\n        // Protocols to use for RX\n        // Remove the ones that you don't need to reduce memory usage\n        GGWave::Protocols::rx().disableAll();\n        //GGWave::Protocols::rx().toggle(GGWAVE_PROTOCOL_DT_NORMAL,  true);\n#ifdef LONG_RANGE\n        GGWave::Protocols::rx().toggle(GGWAVE_PROTOCOL_DT_FAST,    true);\n#endif\n        GGWave::Protocols::rx().toggle(GGWAVE_PROTOCOL_DT_FASTEST, true);\n        //GGWave::Protocols::rx().toggle(GGWAVE_PROTOCOL_MT_NORMAL,  true);\n#ifdef LONG_RANGE\n        GGWave::Protocols::rx().toggle(GGWAVE_PROTOCOL_MT_FAST,    true);\n#endif\n        GGWave::Protocols::rx().toggle(GGWAVE_PROTOCOL_MT_FASTEST, true);\n\n        // Print the memory required for the \"ggwave\" instance:\n        ggwave.prepare(p, false);\n\n        Serial.print(F(\"Required memory by the ggwave instance: \"));\n        Serial.print(ggwave.heapSize());\n        Serial.println(F(\" bytes\"));\n\n        // Initialize the \"ggwave\" instance:\n        ggwave.prepare(p, true);\n        Serial.print(F(\"Instance initialized successfully! Memory used: \"));\n    }\n\n    // Start capturing audio\n    {\n        // Configure the data receive callback\n        PDM.onReceive(onPDMdata);\n\n        // Optionally set the gain\n        // Defaults to 20 on the BLE Sense and -10 on the Portenta Vision Shields\n        //PDM.setGain(30);\n\n        // Initialize PDM:\n        if (!PDM.begin(channels, sampleRate)) {\n            Serial.println(F(\"Failed to start PDM!\"));\n            while (1);\n        }\n    }\n}\n\nvoid loop() {\n    int nr = 0;\n    int niter = 0;\n    int but0Prev = HIGH;\n\n    GGWave::TxRxData result;\n\n    char resultLast[17];\n    int tLastReceive = -10000;\n\n    // Main loop ..\n    while (true) {\n        while (qsize >= samplesPerFrame) {\n            // Use this with the serial plotter to observe real-time audio signal\n            //for (int i = 0; i < samplesPerFrame; i++) {\n            //    Serial.println(sampleBuffer[qhead + i]);\n            //}\n\n            // We have enough captured samples - try to decode any \"ggwave\" data:\n            auto tStart = millis();\n\n            ggwave.decode(sampleBuffer + qhead, samplesPerFrame*kSampleSize_bytes);\n            qsize -= samplesPerFrame;\n            qhead += samplesPerFrame;\n            if (qhead >= qmax) {\n                qhead = 0;\n            }\n\n            auto tEnd = millis();\n            if (++niter % 10 == 0) {\n                // Print the time it took the last decode() call to complete.\n                // The time should be smaller than samplesPerFrame/sampleRate seconds\n                // For example: samplesPerFrame = 128, sampleRate = 6000 => not more than 20 ms\n                Serial.println(tEnd - tStart);\n                if (tEnd - tStart > 1000*(float(samplesPerFrame)/sampleRate)) {\n                    Serial.println(F(\"Warning: decode() took too long to execute!\"));\n                }\n            }\n\n            // Check if we have successfully decoded any data:\n            nr = ggwave.rxTakeData(result);\n            if (nr > 0) {\n                Serial.println(tEnd - tStart);\n                Serial.print(F(\"Received data with length \"));\n                Serial.print(nr); // should be equal to p.payloadLength\n                Serial.println(F(\" bytes:\"));\n\n                Serial.println((char *) result.data());\n\n                strcpy(resultLast, (char *) result.data());\n                tLastReceive = tEnd;\n            }\n\n#ifdef DISPLAY_OUTPUT\n            const auto t = millis();\n\n            static GGWave::Spectrum rxSpectrum;\n            if (ggwave.rxTakeSpectrum(rxSpectrum) && t > 2000) {\n                const bool isNew = t - tLastReceive < 2000;\n\n                if (isNew) {\n                    digitalWrite(kPinLED0, HIGH);\n                } else {\n                    digitalWrite(kPinLED0, LOW);\n                }\n\n                display.clearDisplay();\n\n                display.setTextSize(isNew ? 2 : 1);\n                display.setTextColor(SSD1306_WHITE);\n                display.setCursor(0, 0);\n                display.println(resultLast);\n\n                const int nBin0 = 16;\n                const int nBins = 64;\n                const int dX = SCREEN_WIDTH/nBins;\n\n                float smax = 0.0f;\n                for (int x = 0; x < nBins; x++) {\n                    smax = std::max(smax, rxSpectrum[nBin0 + x]);\n                }\n                smax = smax == 0.0f ? 1.0f : 1.0f/smax;\n\n                const float h = isNew ? 0.25f: 0.75f;\n                for (int x = 0; x < nBins; x++) {\n                    const int x0 = x*dX;\n                    const int x1 = x0 + dX;\n                    const int y = (int) (h*SCREEN_HEIGHT*(rxSpectrum[nBin0 + x]*smax));\n                    display.fillRect(x0, SCREEN_HEIGHT - y, dX, y, SSD1306_WHITE);\n                }\n\n                display.display();\n            }\n#endif\n        }\n\n        // This should never happen.\n        // If it does - there is something wrong with the audio capturing callback.\n        // For example, the microcontroller is not able to process the captured data in real-time.\n        if (err > 0) {\n            Serial.println(F(\"ERRROR\"));\n            Serial.println(err);\n            err = 0;\n        }\n\n        // If the button has been presse - transmit the last received data:\n        int but0 = digitalRead(kPinButton0);\n        if (but0 == LOW && but0Prev == HIGH) {\n            Serial.println(F(\"Button 0 pressed - transmitting ..\"));\n\n            {\n                // pause microphone capture while transmitting\n                PDM.end();\n                delay(500);\n\n                send_text(ggwave, kPinSpeaker, resultLast, GGWAVE_PROTOCOL_MT_FASTEST);\n\n                // resume microphone capture\n                if (!PDM.begin(channels, sampleRate)) {\n                    Serial.println(F(\"Failed to start PDM!\"));\n                    while (1);\n                }\n            }\n\n            Serial.println(F(\"Done\"));\n\n            but0Prev = LOW;\n        } else if (but0 == HIGH && but0Prev == LOW) {\n            but0Prev = HIGH;\n        }\n    }\n}\n\n/**\n  Callback function to process the data from the PDM microphone.\nNOTE: This callback is executed as part of an ISR.\nTherefore using `Serial` to print messages inside this function isn't supported.\n * */\nvoid onPDMdata() {\n    const int bytesAvailable = PDM.available();\n    const int nSamples = bytesAvailable/kSampleSize_bytes;\n\n    if (qsize + nSamples > qmax) {\n        // If you hit this error, try to increase qmax\n        err += 10;\n\n        qhead = 0;\n        qtail = 0;\n        qsize = 0;\n    }\n\n    PDM.read(sampleBuffer + qtail, bytesAvailable);\n\n    qtail += nSamples;\n    qsize += nSamples;\n\n    if (qtail > qmax) {\n        // If you hit this error, qmax is probably not a multiple of the recorded samples\n        err += 1;\n    }\n\n    if (qtail >= qmax) {\n        qtail -= qmax;\n    }\n}\n"
  },
  {
    "path": "examples/arduino-rx-web/CMakeLists.txt",
    "content": "#\n# arduino-rx-web\n\nset(TARGET arduino-rx-web)\n\nif (NOT EMSCRIPTEN)\n    add_executable(${TARGET}\n        arduino-rx-web.cpp\n        )\n\n    target_include_directories(${TARGET} PRIVATE\n        ..\n        ${SDL2_INCLUDE_DIRS}\n        )\n\n    target_link_libraries(${TARGET} PRIVATE\n        ggwave-common\n        ggwave\n        ${SDL2_LIBRARIES}\n        )\nelse()\n    configure_file(${CMAKE_CURRENT_SOURCE_DIR}/index-tmpl.html       ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${TARGET}/index.html @ONLY)\n    configure_file(${CMAKE_SOURCE_DIR}/bindings/javascript/ggwave.js ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${TARGET}/ggwave.js COPYONLY)\nendif()\n"
  },
  {
    "path": "examples/arduino-rx-web/README.md",
    "content": "# arduino-rx-web\n\nA simple web page to receive the data from the [arduino-tx](https://github.com/ggerganov/ggwave/tree/master/examples/arduino-tx) example\n\nhttps://arduino-rx.ggerganov.com\n\n## Demo\n\nhttps://user-images.githubusercontent.com/1991296/168469004-aeb9b9fe-cf81-4db7-b602-62e4ae659341.mp4\n\n[Watch high quality on Youtube](https://youtu.be/qbzKo3zbQcI)\n"
  },
  {
    "path": "examples/arduino-rx-web/arduino-rx-web.cpp",
    "content": "#include \"ggwave-common.h\"\n\n#include \"ggwave/ggwave.h\"\n\n#ifdef __EMSCRIPTEN__\n#include \"build_timestamp.h\"\n#include <emscripten.h>\n#else\n#define EMSCRIPTEN_KEEPALIVE\n#endif\n\n#include <SDL.h>\n#include <SDL_opengl.h>\n\n#include <array>\n#include <chrono>\n#include <string>\n#include <thread>\n#include <functional>\n\nnamespace {\n\nstd::string g_defaultCaptureDeviceName = \"\";\n\nSDL_AudioDeviceID g_devIdInp = 0;\nSDL_AudioDeviceID g_devIdOut = 0;\n\nSDL_AudioSpec g_obtainedSpecInp;\nSDL_AudioSpec g_obtainedSpecOut;\n\nGGWave *g_ggWave = nullptr;\n\n}\n\nstatic std::function<bool()> g_doInit;\nstatic std::function<void(int, int)> g_setWindowSize;\nstatic std::function<bool()> g_mainUpdate;\n\nvoid mainUpdate(void *) {\n    g_mainUpdate();\n}\n\n// JS interface\n\nextern \"C\" {\n    EMSCRIPTEN_KEEPALIVE\n        int sendData(int textLength, const char * text, int protocolId, int volume) {\n            g_ggWave->init(textLength, text, GGWave::TxProtocolId(protocolId), volume);\n            return 0;\n        }\n\n    EMSCRIPTEN_KEEPALIVE\n        int getText(char * text) {\n            std::copy(g_ggWave->rxData().begin(), g_ggWave->rxData().end(), text);\n            return 0;\n        }\n\n    EMSCRIPTEN_KEEPALIVE\n        float sampleRate()        { return g_ggWave->sampleRateInp(); }\n\n    EMSCRIPTEN_KEEPALIVE\n        int framesToRecord()      { return g_ggWave->rxFramesToRecord(); }\n\n    EMSCRIPTEN_KEEPALIVE\n        int framesLeftToRecord()  { return g_ggWave->rxFramesLeftToRecord(); }\n\n    EMSCRIPTEN_KEEPALIVE\n        int framesToAnalyze()     { return g_ggWave->rxFramesToAnalyze(); }\n\n    EMSCRIPTEN_KEEPALIVE\n        int framesLeftToAnalyze() { return g_ggWave->rxFramesLeftToAnalyze(); }\n\n    EMSCRIPTEN_KEEPALIVE\n        int hasDeviceOutput()     { return g_devIdOut; }\n\n    EMSCRIPTEN_KEEPALIVE\n        int hasDeviceCapture()    { return g_devIdInp; }\n\n    EMSCRIPTEN_KEEPALIVE\n        int doInit()              { return g_doInit(); }\n}\n\nvoid GGWave_setDefaultCaptureDeviceName(std::string name) {\n    g_defaultCaptureDeviceName = std::move(name);\n}\n\nbool GGWave_init(\n        const int playbackId,\n        const int captureId,\n        const int payloadLength,\n        const float sampleRateOffset,\n        const bool useDSS) {\n\n    if (g_devIdInp && g_devIdOut) {\n        return false;\n    }\n\n    if (g_devIdInp == 0 && g_devIdOut == 0) {\n        SDL_LogSetPriority(SDL_LOG_CATEGORY_APPLICATION, SDL_LOG_PRIORITY_INFO);\n\n        if (SDL_Init(SDL_INIT_AUDIO) < 0) {\n            SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, \"Couldn't initialize SDL: %s\\n\", SDL_GetError());\n            return (1);\n        }\n\n        SDL_SetHintWithPriority(SDL_HINT_AUDIO_RESAMPLING_MODE, \"medium\", SDL_HINT_OVERRIDE);\n\n        {\n            int nDevices = SDL_GetNumAudioDevices(SDL_FALSE);\n            printf(\"Found %d playback devices:\\n\", nDevices);\n            for (int i = 0; i < nDevices; i++) {\n                printf(\"    - Playback device #%d: '%s'\\n\", i, SDL_GetAudioDeviceName(i, SDL_FALSE));\n            }\n        }\n        {\n            int nDevices = SDL_GetNumAudioDevices(SDL_TRUE);\n            printf(\"Found %d capture devices:\\n\", nDevices);\n            for (int i = 0; i < nDevices; i++) {\n                printf(\"    - Capture device #%d: '%s'\\n\", i, SDL_GetAudioDeviceName(i, SDL_TRUE));\n            }\n        }\n    }\n\n    bool reinit = false;\n\n    if (g_devIdOut == 0) {\n        printf(\"Initializing playback ...\\n\");\n\n        SDL_AudioSpec playbackSpec;\n        SDL_zero(playbackSpec);\n\n        playbackSpec.freq = GGWave::kDefaultSampleRate + sampleRateOffset;\n        playbackSpec.format = AUDIO_S16SYS;\n        playbackSpec.channels = 1;\n        playbackSpec.samples = 16*1024;\n        playbackSpec.callback = NULL;\n\n        SDL_zero(g_obtainedSpecOut);\n\n        if (playbackId >= 0) {\n            printf(\"Attempt to open playback device %d : '%s' ...\\n\", playbackId, SDL_GetAudioDeviceName(playbackId, SDL_FALSE));\n            g_devIdOut = SDL_OpenAudioDevice(SDL_GetAudioDeviceName(playbackId, SDL_FALSE), SDL_FALSE, &playbackSpec, &g_obtainedSpecOut, 0);\n        } else {\n            printf(\"Attempt to open default playback device ...\\n\");\n            g_devIdOut = SDL_OpenAudioDevice(NULL, SDL_FALSE, &playbackSpec, &g_obtainedSpecOut, 0);\n        }\n\n        if (!g_devIdOut) {\n            printf(\"Couldn't open an audio device for playback: %s!\\n\", SDL_GetError());\n            g_devIdOut = 0;\n        } else {\n            printf(\"Obtained spec for output device (SDL Id = %d):\\n\", g_devIdOut);\n            printf(\"    - Sample rate:       %d (required: %d)\\n\", g_obtainedSpecOut.freq, playbackSpec.freq);\n            printf(\"    - Format:            %d (required: %d)\\n\", g_obtainedSpecOut.format, playbackSpec.format);\n            printf(\"    - Channels:          %d (required: %d)\\n\", g_obtainedSpecOut.channels, playbackSpec.channels);\n            printf(\"    - Samples per frame: %d (required: %d)\\n\", g_obtainedSpecOut.samples, playbackSpec.samples);\n\n            if (g_obtainedSpecOut.format != playbackSpec.format ||\n                g_obtainedSpecOut.channels != playbackSpec.channels ||\n                g_obtainedSpecOut.samples != playbackSpec.samples) {\n                g_devIdOut = 0;\n                SDL_CloseAudio();\n                fprintf(stderr, \"Failed to initialize playback SDL_OpenAudio!\");\n\n                return false;\n            }\n\n            reinit = true;\n        }\n    }\n\n    if (g_devIdInp == 0) {\n        SDL_AudioSpec captureSpec;\n        captureSpec = g_obtainedSpecOut;\n        captureSpec.freq = GGWave::kDefaultSampleRate + sampleRateOffset;\n        captureSpec.format = AUDIO_F32SYS;\n        captureSpec.samples = 1024;\n\n        SDL_zero(g_obtainedSpecInp);\n\n        if (captureId >= 0) {\n            printf(\"Attempt to open capture device %d : '%s' ...\\n\", captureId, SDL_GetAudioDeviceName(captureId, SDL_TRUE));\n            g_devIdInp = SDL_OpenAudioDevice(SDL_GetAudioDeviceName(captureId, SDL_TRUE), SDL_TRUE, &captureSpec, &g_obtainedSpecInp, 0);\n        } else {\n            printf(\"Attempt to open default capture device ...\\n\");\n            g_devIdInp = SDL_OpenAudioDevice(g_defaultCaptureDeviceName.empty() ? nullptr : g_defaultCaptureDeviceName.c_str(),\n                                            SDL_TRUE, &captureSpec, &g_obtainedSpecInp, 0);\n        }\n        if (!g_devIdInp) {\n            printf(\"Couldn't open an audio device for capture: %s!\\n\", SDL_GetError());\n            g_devIdInp = 0;\n        } else {\n            printf(\"Obtained spec for input device (SDL Id = %d):\\n\", g_devIdInp);\n            printf(\"    - Sample rate:       %d\\n\", g_obtainedSpecInp.freq);\n            printf(\"    - Format:            %d (required: %d)\\n\", g_obtainedSpecInp.format, captureSpec.format);\n            printf(\"    - Channels:          %d (required: %d)\\n\", g_obtainedSpecInp.channels, captureSpec.channels);\n            printf(\"    - Samples per frame: %d\\n\", g_obtainedSpecInp.samples);\n\n            reinit = true;\n        }\n    }\n\n    GGWave::SampleFormat sampleFormatInp = GGWAVE_SAMPLE_FORMAT_UNDEFINED;\n    GGWave::SampleFormat sampleFormatOut = GGWAVE_SAMPLE_FORMAT_UNDEFINED;\n\n    switch (g_obtainedSpecInp.format) {\n        case AUDIO_U8:      sampleFormatInp = GGWAVE_SAMPLE_FORMAT_U8;  break;\n        case AUDIO_S8:      sampleFormatInp = GGWAVE_SAMPLE_FORMAT_I8;  break;\n        case AUDIO_U16SYS:  sampleFormatInp = GGWAVE_SAMPLE_FORMAT_U16; break;\n        case AUDIO_S16SYS:  sampleFormatInp = GGWAVE_SAMPLE_FORMAT_I16; break;\n        case AUDIO_S32SYS:  sampleFormatInp = GGWAVE_SAMPLE_FORMAT_F32; break;\n        case AUDIO_F32SYS:  sampleFormatInp = GGWAVE_SAMPLE_FORMAT_F32; break;\n    }\n\n    switch (g_obtainedSpecOut.format) {\n        case AUDIO_U8:      sampleFormatOut = GGWAVE_SAMPLE_FORMAT_U8;  break;\n        case AUDIO_S8:      sampleFormatOut = GGWAVE_SAMPLE_FORMAT_I8;  break;\n        case AUDIO_U16SYS:  sampleFormatOut = GGWAVE_SAMPLE_FORMAT_U16; break;\n        case AUDIO_S16SYS:  sampleFormatOut = GGWAVE_SAMPLE_FORMAT_I16; break;\n        case AUDIO_S32SYS:  sampleFormatOut = GGWAVE_SAMPLE_FORMAT_F32; break;\n        case AUDIO_F32SYS:  sampleFormatOut = GGWAVE_SAMPLE_FORMAT_F32; break;\n            break;\n    }\n\n    if (reinit) {\n        if (g_ggWave) delete g_ggWave;\n\n        GGWave::OperatingMode mode = GGWAVE_OPERATING_MODE_RX_AND_TX;\n        if (useDSS) mode |= GGWAVE_OPERATING_MODE_USE_DSS;\n\n        g_ggWave = new GGWave({\n            payloadLength,\n            (float) g_obtainedSpecInp.freq,\n            (float) g_obtainedSpecOut.freq,\n            GGWave::kDefaultSampleRate,\n            512,\n            GGWave::kDefaultSoundMarkerThreshold,\n            sampleFormatInp,\n            sampleFormatOut,\n            mode,\n        });\n    }\n\n    return true;\n}\n\nGGWave *& GGWave_instance() { return g_ggWave; }\n\nbool GGWave_mainLoop() {\n    if (g_devIdInp == 0 && g_devIdOut == 0) {\n        return false;\n    }\n\n    if (g_ggWave->txHasData() == false) {\n        SDL_PauseAudioDevice(g_devIdOut, SDL_FALSE);\n\n        static auto tLastNoData = std::chrono::high_resolution_clock::now();\n        auto tNow = std::chrono::high_resolution_clock::now();\n\n        if ((int) SDL_GetQueuedAudioSize(g_devIdOut) < g_ggWave->samplesPerFrame()*g_ggWave->sampleSizeOut()) {\n            SDL_PauseAudioDevice(g_devIdInp, SDL_FALSE);\n            const int nHave = (int) SDL_GetQueuedAudioSize(g_devIdInp);\n            const int nNeed = g_ggWave->samplesPerFrame()*g_ggWave->sampleSizeInp();\n            if (::getTime_ms(tLastNoData, tNow) > 500.0f && nHave >= nNeed) {\n                static std::vector<uint8_t> dataInp(nNeed);\n                SDL_DequeueAudio(g_devIdInp, dataInp.data(), nNeed);\n\n                if (g_ggWave->decode(dataInp.data(), dataInp.size()) == false) {\n                    fprintf(stderr, \"Warning: failed to decode input data!\\n\");\n                } else {\n                    GGWave::TxRxData rxData;\n                    int n = g_ggWave->rxTakeData(rxData);\n                    if (n > 0) {\n                        std::time_t timestamp = std::time(nullptr);\n                        std::string tstr = std::asctime(std::localtime(&timestamp));\n                        tstr.back() = 0;\n                        printf(\"[%s] Received: '%s'\\n\", tstr.c_str(), rxData.data());\n                    }\n                }\n\n                if (nHave > 32*nNeed) {\n                    fprintf(stderr, \"Warning: slow processing, clearing queued audio buffer of %d bytes ...\", SDL_GetQueuedAudioSize(g_devIdInp));\n                    SDL_ClearQueuedAudio(g_devIdInp);\n                }\n            } else {\n                SDL_ClearQueuedAudio(g_devIdInp);\n            }\n        } else {\n            tLastNoData = tNow;\n        }\n    } else {\n        SDL_PauseAudioDevice(g_devIdOut, SDL_TRUE);\n        SDL_PauseAudioDevice(g_devIdInp, SDL_TRUE);\n\n        const auto nBytes = g_ggWave->encode();\n        SDL_QueueAudio(g_devIdOut, g_ggWave->txWaveform(), nBytes);\n    }\n\n    return true;\n}\n\nbool GGWave_deinit() {\n    if (g_devIdInp == 0 && g_devIdOut == 0) {\n        return false;\n    }\n\n    delete g_ggWave;\n    g_ggWave = nullptr;\n\n    SDL_PauseAudioDevice(g_devIdInp, 1);\n    SDL_CloseAudioDevice(g_devIdInp);\n    SDL_PauseAudioDevice(g_devIdOut, 1);\n    SDL_CloseAudioDevice(g_devIdOut);\n\n    g_devIdInp = 0;\n    g_devIdOut = 0;\n\n    return true;\n}\n\nint main(int argc, char** argv) {\n#ifdef __EMSCRIPTEN__\n    printf(\"Build time: %s\\n\", BUILD_TIMESTAMP);\n    printf(\"Press the Init button to start\\n\");\n\n    if (argv[1]) {\n        GGWave_setDefaultCaptureDeviceName(argv[1]);\n    }\n#else\n    printf(\"Usage: %s [-cN] [-lN]\\n\", argv[0]);\n    printf(\"    -cN - select capture device N\\n\");\n    printf(\"    -lN - fixed payload length of size N, N in [1, %d]\\n\", GGWave::kMaxLengthFixed);\n    printf(\"\\n\");\n#endif\n\n    GGWave::Protocols::rx() = { {\n        { \"[R2T2] Normal\",      64,  9, 1, 2, true, },\n        { \"[R2T2] Fast\",        64,  6, 1, 2, true, },\n        { \"[R2T2] Fastest\",     64,  3, 1, 2, true, },\n        { \"[R2T2] Low Normal\",  16,  9, 1, 2, true, },\n        { \"[R2T2] Low Fast\",    16,  6, 1, 2, true, },\n        { \"[R2T2] Low Fastest\", 16,  3, 1, 2, true, },\n    } };\n\n    const auto argm = parseCmdArguments(argc, argv);\n    const int captureId = argm.count(\"c\") == 0 ? 0 : std::stoi(argm.at(\"c\"));\n    const int payloadLength = argm.count(\"l\") == 0 ? 16 : std::stoi(argm.at(\"l\"));\n\n    bool isInitialized = false;\n\n    g_doInit = [&]() {\n        if (GGWave_init(0, captureId, payloadLength, 0, true) == false) {\n            fprintf(stderr, \"Failed to initialize GGWave\\n\");\n            return false;\n        }\n\n        isInitialized = true;\n        printf(\"Listening for payload with length = %d bytes ..\\n\", payloadLength);\n\n        return true;\n    };\n\n    g_mainUpdate = [&]() {\n        if (isInitialized == false) {\n            return true;\n        }\n\n        GGWave_mainLoop();\n\n        return true;\n    };\n\n#ifdef __EMSCRIPTEN__\n    emscripten_set_main_loop_arg(mainUpdate, NULL, 60, true);\n#else\n    if (g_doInit() == false) {\n        printf(\"Error: failed to initialize audio\\n\");\n        return -2;\n    }\n\n    while (true) {\n        std::this_thread::sleep_for(std::chrono::milliseconds(1));\n        if (g_mainUpdate() == false) break;\n    }\n\n    GGWave_deinit();\n\n    // Cleanup\n    SDL_CloseAudio();\n    SDL_Quit();\n#endif\n\n    return 0;\n}\n"
  },
  {
    "path": "examples/arduino-rx-web/index-tmpl.html",
    "content": "<!doctype html>\n<html lang=\"en-us\">\n    <head>\n        <title>ggwave : arduino-rx</title>\n\n        <style>\n            .block {\n                display: block;\n                width: 100%;\n                height: 100%;\n                border: none;\n                background-color: #FFFFFF00;\n                color: #000000;\n                padding: 14px 28px;\n                padding-top: 40vh;\n                font-size: 100px;\n                cursor: pointer;\n                text-align: center;\n            }\n        </style>\n    </head>\n    <body>\n        <div id=\"main-container\">\n            <div id=\"controls\">\n                <div id=\"controls0\">\n                    <button id=\"captureStart\" class=\"block\">Start</button>\n                </div>\n                <div id=\"controls1\" hidden>\n                    <button id=\"rxData\" class=\"block\">[no data]</button>\n                </div>\n            </div>\n\n            <div id=\"debug\" hidden>\n                <br><br>\n\n                <button onclick=\"onSend('RRR');\">Red</button>\n                <button onclick=\"onSend('GGG');\">Green</button>\n                <button onclick=\"onSend('BBB');\">Blue</button>\n\n                <br><br>\n\n                <textarea name=\"textarea\" id=\"rxData2\" style=\"width:300px;height:100px;\" disabled></textarea><br>\n\n                <button id=\"captureStart2\">Start capturing</button>\n                <button id=\"captureStop\" hidden>Stop capturing</button>\n\n                <br><br>\n\n                <div class=\"cell-version\">\n                    <span>\n                        |\n                        Build time: <span class=\"nav-link\">@GIT_DATE@</span> |\n                        Commit hash: <a class=\"nav-link\" href=\"https://github.com/ggerganov/ggwave/commit/@GIT_SHA1@\">@GIT_SHA1@</a> |\n                        Commit subject: <span class=\"nav-link\">@GIT_COMMIT_SUBJECT@</span> |\n                        <a class=\"nav-link\" href=\"https://github.com/ggerganov/ggwave/tree/master/examples/ggwave-js\">Source Code</a> |\n                    </span>\n                </div>\n            </div>\n        </div>\n\n        <script type=\"text/javascript\" src=\"ggwave.js\"></script>\n        <script type='text/javascript'>\n            window.AudioContext = window.AudioContext || window.webkitAudioContext;\n            window.OfflineAudioContext = window.OfflineAudioContext || window.webkitOfflineAudioContext;\n\n            var context = null;\n            var recorder = null;\n\n            // the ggwave module instance\n            var ggwave = null;\n            var parameters = null;\n            var instance = null;\n\n            const kPayloadLength = 16;\n\n            // instantiate the ggwave instance\n            // ggwave_factory comes from the ggwave.js module\n            ggwave_factory().then(function(obj) {\n                ggwave = obj;\n            });\n\n            var txData = document.getElementById(\"txData\");\n            var rxData = document.getElementById(\"rxData\");\n            var captureStart = document.getElementById(\"captureStart\");\n            var captureStop = document.getElementById(\"captureStop\");\n\n            // helper function\n            function convertTypedArray(src, type) {\n                var buffer = new ArrayBuffer(src.byteLength);\n                var baseView = new src.constructor(buffer).set(src);\n                return new type(buffer);\n            }\n\n            // initialize audio context and ggwave\n            function init() {\n                if (!context) {\n                    context = new AudioContext({sampleRate: 48000});\n\n                    // If you applied a shift on the transmitter side, you need to apply the same shift on the receiver side:\n                    //ggwave.rxProtocolSetFreqStart(ggwave.ProtocolId.GGWAVE_PROTOCOL_MT_FASTEST, 24 + 48);\n\n                    parameters = ggwave.getDefaultParameters();\n                    parameters.payloadLength = kPayloadLength;\n                    parameters.samplesPerFrame = 1024;\n                    parameters.sampleRateInp = context.sampleRate;\n                    parameters.sampleRateOut = context.sampleRate;\n                    parameters.operatingMode = ggwave.GGWAVE_OPERATING_MODE_RX_AND_TX | ggwave.GGWAVE_OPERATING_MODE_USE_DSS;\n                    instance = ggwave.init(parameters);\n                }\n            }\n\n            //\n            // Tx\n            //\n\n            function onSend(text) {\n                init();\n\n                // DSS : xor the text with the magic numbers\n                var payload = new Uint8Array(kPayloadLength);\n                for (var i = 0; i < kPayloadLength; i++) {\n                    payload[i] = 0;\n                }\n                for (var i = 0; i < text.length; i++) {\n                    payload[i] = text.charCodeAt(i);\n                }\n\n                // generate audio waveform\n                var waveform = ggwave.encode(instance, payload, ggwave.ProtocolId.GGWAVE_PROTOCOL_MT_FASTEST, 25)\n\n                // play audio\n                var buf = convertTypedArray(waveform, Float32Array);\n                var buffer = context.createBuffer(1, buf.length, context.sampleRate);\n                buffer.getChannelData(0).set(buf);\n                var source = context.createBufferSource();\n                source.buffer = buffer;\n                source.connect(context.destination);\n                source.start(0);\n            }\n\n            //\n            // Rx\n            //\n\n            captureStart.addEventListener(\"click\", function () {\n                init();\n\n                let constraints = {\n                    audio: {\n                        // not sure if these are necessary to have\n                        echoCancellation: false,\n                        autoGainControl: false,\n                        noiseSuppression: false\n                    }\n                };\n\n                navigator.mediaDevices.getUserMedia(constraints).then(function (e) {\n                    mediaStream = context.createMediaStreamSource(e);\n\n                    var bufferSize = 1024;\n                    var numberOfInputChannels = 1;\n                    var numberOfOutputChannels = 1;\n\n                    if (context.createScriptProcessor) {\n                        recorder = context.createScriptProcessor(\n                                bufferSize,\n                                numberOfInputChannels,\n                                numberOfOutputChannels);\n                    } else {\n                        recorder = context.createJavaScriptNode(\n                                bufferSize,\n                                numberOfInputChannels,\n                                numberOfOutputChannels);\n                    }\n\n                    recorder.onaudioprocess = function (e) {\n                        var source = e.inputBuffer;\n                        var res = ggwave.decode(instance, convertTypedArray(new Float32Array(source.getChannelData(0)), Int8Array));\n                        if (res && res.length == kPayloadLength) {\n                            // DSS\n                            var payload = \"\";\n                            res8 = convertTypedArray(res, Uint8Array);\n                            for (var i = 0; i < kPayloadLength; i++) {\n                                payload += String.fromCharCode(res8[i]);\n                                if (res8[i] == 0) {\n                                    break;\n                                }\n                            }\n                            console.log(res8);\n                            document.getElementById(\"rxData\").value = payload;\n                            document.getElementById(\"rxData\").innerHTML = payload;\n\n                            document.body.style.backgroundColor = '#aaffaa';\n                            setTimeout(function () {\n                                document.body.style.backgroundColor = '#FFFFFF';\n                            }, 250);\n                        }\n                    }\n\n                    mediaStream.connect(recorder);\n                    recorder.connect(context.destination);\n                }).catch(function (e) {\n                    console.error(e);\n                });\n\n                rxData.value = 'Listening ...';\n                document.getElementById(\"controls0\").hidden = true;\n                document.getElementById(\"controls1\").hidden = false;\n                captureStart.hidden = true;\n                captureStop.hidden = false;\n            });\n\n            captureStop.addEventListener(\"click\", function () {\n                if (recorder) {\n                    recorder.disconnect(context.destination);\n                    mediaStream.disconnect(recorder);\n                    recorder = null;\n                }\n\n                rxData.value = 'Audio capture is paused! Press the \"Start capturing\" button to analyze audio from the microphone';\n                captureStart.hidden = false;\n                captureStop.hidden = true;\n            });\n\n            captureStop.click();\n        </script>\n    </body>\n</html>\n"
  },
  {
    "path": "examples/arduino-tx/.gitignore",
    "content": "ggwave\nggwave.cpp\nfft.h\nresampler.h\nresampler.cpp\nreed-solomon\n"
  },
  {
    "path": "examples/arduino-tx/CMakeLists.txt",
    "content": "#\n# arduino-tx\n\n#configure_file(${CMAKE_SOURCE_DIR}/include/ggwave/ggwave.h   ${CMAKE_CURRENT_SOURCE_DIR}/ggwave.h              COPYONLY)\n#configure_file(${CMAKE_SOURCE_DIR}/src/ggwave.cpp            ${CMAKE_CURRENT_SOURCE_DIR}/ggwave.cpp            COPYONLY)\n#configure_file(${CMAKE_SOURCE_DIR}/src/fft.h                 ${CMAKE_CURRENT_SOURCE_DIR}/fft.h                 COPYONLY)\n#configure_file(${CMAKE_SOURCE_DIR}/src/reed-solomon/gf.hpp   ${CMAKE_CURRENT_SOURCE_DIR}/reed-solomon/gf.hpp   COPYONLY)\n#configure_file(${CMAKE_SOURCE_DIR}/src/reed-solomon/rs.hpp   ${CMAKE_CURRENT_SOURCE_DIR}/reed-solomon/rs.hpp   COPYONLY)\n#configure_file(${CMAKE_SOURCE_DIR}/src/reed-solomon/poly.hpp ${CMAKE_CURRENT_SOURCE_DIR}/reed-solomon/poly.hpp COPYONLY)\n#configure_file(${CMAKE_SOURCE_DIR}/src/reed-solomon/LICENSE  ${CMAKE_CURRENT_SOURCE_DIR}/reed-solomon/LICENSE  COPYONLY)\n"
  },
  {
    "path": "examples/arduino-tx/README.md",
    "content": "# arduino-tx\n\nThis is a sample project for transmitting data via sound using [Arduino Uno](https://store.arduino.cc/products/arduino-uno-rev3) microcontroller.\n\n## Setup\n\n- Arduino Uno R3\n- Generic buzzer\n\n![Sketch-Breadboard](fritzing-sketch_bb.png)\n![IMG_0257](https://user-images.githubusercontent.com/1991296/173232151-c01d01e9-8b36-4705-83a9-fb52b58382c7.jpg)\n\n## Demo\n\nhttps://user-images.githubusercontent.com/1991296/168469004-aeb9b9fe-cf81-4db7-b602-62e4ae659341.mp4\n\n[Watch high quality on Youtube](https://youtu.be/qbzKo3zbQcI)\n"
  },
  {
    "path": "examples/arduino-tx/arduino-tx.ino",
    "content": "// arduino-tx\n//\n// Sample sketch for transmitting data using \"ggwave\"\n//\n// Tested with:\n//   - Arduino Uno R3\n//   - Arduino Nano RP2040 Connect\n//   - NodeMCU-ESP32-S\n//   - NodeMCU-ESP8266EX\n//\n// If you want to perform a quick test, you can use the free \"Waver\" application:\n//   - Web:     https://waver.ggerganov.com\n//   - Android: https://play.google.com/store/apps/details?id=com.ggerganov.Waver\n//   - iOS:     https://apps.apple.com/us/app/waver-data-over-sound/id1543607865\n//\n// Make sure to enable the \"Fixed-length\" option in \"Waver\"'s settings and set the number of\n// bytes to be equal to \"payloadLength\" used in the sketch.\n//\n// Demo: https://youtu.be/qbzKo3zbQcI\n//\n// Sketch: https://github.com/ggerganov/ggwave/tree/master/examples/arduino-tx\n//\n\n#include <ggwave.h>\n\n// Pin configuration\nconst int kPinLed0    = 13;\nconst int kPinSpeaker = 10;\nconst int kPinButton0 = 2;\nconst int kPinButton1 = 4;\n\nconst int samplesPerFrame = 128;\nconst int sampleRate      = 6000;\n\n// Global GGwave instance\nGGWave ggwave;\n\nchar txt[64];\n#define P(str) (strcpy_P(txt, PSTR(str)), txt)\n\n// Helper function to output the generated GGWave waveform via a buzzer\nvoid send_text(GGWave & ggwave, uint8_t pin, const char * text, GGWave::TxProtocolId protocolId) {\n    Serial.print(F(\"Sending text: \"));\n    Serial.println(text);\n\n    ggwave.init(text, protocolId);\n    ggwave.encode();\n\n    const auto & protocol = GGWave::Protocols::tx()[protocolId];\n    const auto tones = ggwave.txTones();\n    const auto duration_ms = protocol.txDuration_ms(ggwave.samplesPerFrame(), ggwave.sampleRateOut());\n    for (auto & curTone : tones) {\n        const auto freq_hz = (protocol.freqStart + curTone)*ggwave.hzPerSample();\n        tone(pin, freq_hz);\n        delay(duration_ms);\n    }\n\n    noTone(pin);\n    digitalWrite(pin, LOW);\n}\n\nvoid setup() {\n    Serial.begin(57600);\n    while (!Serial);\n\n    pinMode(kPinLed0,    OUTPUT);\n    pinMode(kPinSpeaker, OUTPUT);\n    pinMode(kPinButton0, INPUT);\n    pinMode(kPinButton1, INPUT);\n\n    // Initialize \"ggwave\"\n    {\n        Serial.println(F(\"Trying to initialize the ggwave instance\"));\n\n        ggwave.setLogFile(nullptr);\n\n        auto p = GGWave::getDefaultParameters();\n\n        // Adjust the \"ggwave\" parameters to your needs.\n        // Make sure that the \"payloadLength\" parameter matches the one used on the transmitting side.\n        p.payloadLength   = 16;\n        p.sampleRateInp   = sampleRate;\n        p.sampleRateOut   = sampleRate;\n        p.sampleRate      = sampleRate;\n        p.samplesPerFrame = samplesPerFrame;\n        p.sampleFormatInp = GGWAVE_SAMPLE_FORMAT_I16;\n        p.sampleFormatOut = GGWAVE_SAMPLE_FORMAT_U8;\n        p.operatingMode   = GGWAVE_OPERATING_MODE_TX | GGWAVE_OPERATING_MODE_TX_ONLY_TONES | GGWAVE_OPERATING_MODE_USE_DSS;\n\n        // Protocols to use for TX\n        GGWave::Protocols::tx().only(GGWAVE_PROTOCOL_MT_FASTEST);\n\n        // Sometimes, the speaker might not be able to produce frequencies in the Mono-tone range of 1-2 kHz.\n        // In such cases, you can shift the base frequency up by changing the \"freqStart\" parameter of the protocol.\n        // Make sure that in the receiver (for example, the \"Waver\" app) the base frequency is shifted by the same amount.\n        // Here we shift the frequency by +48 bins. Each bin is equal to 48000/1024 = 46.875 Hz.\n        // So the base frequency is shifted by +2250 Hz.\n        //GGWave::Protocols::tx()[GGWAVE_PROTOCOL_MT_FASTEST].freqStart += 48;\n\n        // Initialize the ggwave instance and print the memory usage\n        ggwave.prepare(p);\n        Serial.println(ggwave.heapSize());\n\n        Serial.println(F(\"Instance initialized successfully!\"));\n    }\n}\n\n// Button state\nint pressed = 0;\nbool isDown = false;\n\nvoid loop() {\n    delay(1000);\n\n    digitalWrite(kPinLed0, HIGH);\n    send_text(ggwave, kPinSpeaker, P(\"Hello!\"), GGWAVE_PROTOCOL_MT_FASTEST);\n    digitalWrite(kPinLed0, LOW);\n\n    delay(2000);\n\n    digitalWrite(kPinLed0, HIGH);\n    send_text(ggwave, kPinSpeaker, P(\"This is a\"),   GGWAVE_PROTOCOL_MT_FASTEST);\n    send_text(ggwave, kPinSpeaker, P(\"ggwave demo\"), GGWAVE_PROTOCOL_MT_FASTEST);\n    digitalWrite(kPinLed0, LOW);\n\n    delay(2000);\n\n    digitalWrite(kPinLed0, HIGH);\n    send_text(ggwave, kPinSpeaker, P(\"The arduino\"),      GGWAVE_PROTOCOL_MT_FASTEST);\n    delay(200);\n    send_text(ggwave, kPinSpeaker, P(\"transmits data\"),   GGWAVE_PROTOCOL_MT_FASTEST);\n    delay(200);\n    send_text(ggwave, kPinSpeaker, P(\"using sound\"),      GGWAVE_PROTOCOL_MT_FASTEST);\n    delay(200);\n    send_text(ggwave, kPinSpeaker, P(\"through a buzzer\"), GGWAVE_PROTOCOL_MT_FASTEST);\n    digitalWrite(kPinLed0, LOW);\n\n    delay(1000);\n\n    digitalWrite(kPinLed0, HIGH);\n    send_text(ggwave, kPinSpeaker, P(\"The sound is\"), GGWAVE_PROTOCOL_MT_FASTEST);\n    delay(200);\n    send_text(ggwave, kPinSpeaker, P(\"decoded in a\"), GGWAVE_PROTOCOL_MT_FASTEST);\n    delay(200);\n    send_text(ggwave, kPinSpeaker, P(\"web page.\"),    GGWAVE_PROTOCOL_MT_FASTEST);\n    digitalWrite(kPinLed0, LOW);\n\n    delay(1000);\n\n    digitalWrite(kPinLed0, HIGH);\n    send_text(ggwave, kPinSpeaker, P(\"Press the button!\"), GGWAVE_PROTOCOL_MT_FASTEST);\n    digitalWrite(kPinLed0, LOW);\n\n    Serial.println(F(\"Starting main loop\"));\n\n    while (true) {\n        int but0 = digitalRead(kPinButton0);\n        int but1 = digitalRead(kPinButton1);\n\n        if (but1 == LOW && isDown == false) {\n            delay(200);\n            ++pressed;\n            isDown = true;\n        } else if (but1 == HIGH) {\n            isDown = false;\n        }\n\n        if (but0 == LOW) {\n            snprintf(txt, 16, \"Pressed: %d\", pressed);\n\n            digitalWrite(kPinLed0, HIGH);\n            send_text(ggwave, kPinSpeaker, txt, GGWAVE_PROTOCOL_MT_FASTEST);\n            digitalWrite(kPinLed0, LOW);\n            pressed = 0;\n        }\n    }\n}\n"
  },
  {
    "path": "examples/arduino-tx-obsolete/.gitignore",
    "content": "ggwave\nggwave.cpp\nfft.h\nresampler.h\nresampler.cpp\nreed-solomon\n"
  },
  {
    "path": "examples/arduino-tx-obsolete/CMakeLists.txt",
    "content": ""
  },
  {
    "path": "examples/arduino-tx-obsolete/arduino-tx.ino",
    "content": "// This example uses a custom ggwave imlpementation specifically for Arduino UNO.\n// Since the Arduino UNO has only 2KB of RAM, the ggwave library is not able to\n// to fit into the Arduino's memory (eventhough it is very close).\n// If your microcontroller has more than 2KB of RAM, you should check the other Tx\n// examples to see if you can use the original ggwave library.\n#include \"ggwave.h\"\n\nconst int kPinLed0    = 13;\nconst int kPinSpeaker = 10;\nconst int kPinButton0 = 2;\nconst int kPinButton1 = 4;\n\nvoid setup() {\n    Serial.begin(57600);\n\n    pinMode(kPinLed0,    OUTPUT);\n    pinMode(kPinSpeaker, OUTPUT);\n    pinMode(kPinButton0, INPUT);\n    pinMode(kPinButton1, INPUT);\n\n    delay(3000);\n\n    digitalWrite(kPinLed0, HIGH);\n    GGWave::send_text(kPinSpeaker, \"Hello!\", GGWave::GGWAVE_PROTOCOL_MT_FASTEST);\n    digitalWrite(kPinLed0, LOW);\n\n    delay(2000);\n\n    digitalWrite(kPinLed0, HIGH);\n    GGWave::send_text(kPinSpeaker, \"This is a\",   GGWave::GGWAVE_PROTOCOL_MT_FASTEST);\n    GGWave::send_text(kPinSpeaker, \"ggwave demo\", GGWave::GGWAVE_PROTOCOL_MT_FASTEST);\n    digitalWrite(kPinLed0, LOW);\n\n    delay(2000);\n\n    digitalWrite(kPinLed0, HIGH);\n    GGWave::send_text(kPinSpeaker, \"The arduino\",      GGWave::GGWAVE_PROTOCOL_MT_FASTEST);\n    delay(200);\n    GGWave::send_text(kPinSpeaker, \"transmits data\",   GGWave::GGWAVE_PROTOCOL_MT_FASTEST);\n    delay(200);\n    GGWave::send_text(kPinSpeaker, \"using sound\",      GGWave::GGWAVE_PROTOCOL_MT_FASTEST);\n    delay(200);\n    GGWave::send_text(kPinSpeaker, \"through a buzzer\", GGWave::GGWAVE_PROTOCOL_MT_FASTEST);\n    digitalWrite(kPinLed0, LOW);\n\n    delay(1000);\n\n    digitalWrite(kPinLed0, HIGH);\n    GGWave::send_text(kPinSpeaker, \"The sound is\", GGWave::GGWAVE_PROTOCOL_MT_FASTEST);\n    delay(200);\n    GGWave::send_text(kPinSpeaker, \"decoded in a\", GGWave::GGWAVE_PROTOCOL_MT_FASTEST);\n    delay(200);\n    GGWave::send_text(kPinSpeaker, \"web page.\",    GGWave::GGWAVE_PROTOCOL_MT_FASTEST);\n    digitalWrite(kPinLed0, LOW);\n\n    delay(1000);\n\n    digitalWrite(kPinLed0, HIGH);\n    GGWave::send_text(kPinSpeaker, \"Press the button!\", GGWave::GGWAVE_PROTOCOL_MT_FASTEST);\n    digitalWrite(kPinLed0, LOW);\n}\n\nchar txt[16];\nint pressed = 0;\nbool isDown = false;\n\nvoid loop() {\n    int but0 = digitalRead(kPinButton0);\n    int but1 = digitalRead(kPinButton1);\n\n    if (but1 == LOW && isDown == false) {\n        delay(200);\n        ++pressed;\n        isDown = true;\n    } else if (but1 == HIGH) {\n        isDown = false;\n    }\n\n    if (but0 == LOW) {\n        snprintf(txt, 16, \"Pressed: %d\", pressed);\n\n        digitalWrite(kPinLed0, HIGH);\n        GGWave::send_text(kPinSpeaker, txt, GGWave::GGWAVE_PROTOCOL_MT_FASTEST);\n        digitalWrite(kPinLed0, LOW);\n        pressed = 0;\n    }\n}\n"
  },
  {
    "path": "examples/arduino-tx-obsolete/ggwave.h",
    "content": "#pragma once\n\n//\n// The code in the RS namespace provides Reed-Solomon based error correction.\n// The code is taken from https://github.com/mersinvald/Reed-Solomon.\n// The LICENSE of the code is copied below:\n//\n// Copyright © 2015 Mike Lubinets, github.com/mersinvald\n//\n// Permission is hereby granted, free of charge, to any person\n// obtaining a copy of this software and associated documentation files\n// (the “Software”), to deal in the Software without restriction,\n// including without limitation the rights to use, copy, modify, merge,\n// publish, distribute, sublicense, and/or sell copies of the Software,\n// and to permit persons to whom the Software is furnished to do so,\n// subject to the following conditions:\n//\n// The above copyright notice and this permission notice shall be\n// included in all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND,\n// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS\n// BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN\n// ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\n// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n// SOFTWARE.\n//\n// The GGWave implementation is in the GGWave namespace at the bottom of this file.\n//\n\n#include <assert.h>\n\nnamespace RS {\n\nstruct Poly {\n    Poly()\n        : length(0), _memory(NULL) {}\n\n    Poly(uint8_t id, uint16_t offset, uint8_t size) \\\n        : length(0), _id(id), _size(size), _offset(offset), _memory(NULL) {}\n\n    /* @brief Append number at the end of polynomial\n     * @param num - number to append\n     * @return false if polynomial can't be stretched */\n    inline bool Append(uint8_t num) {\n        assert(length < _size);\n        ptr()[length++] = num;\n        return true;\n    }\n\n    /* @brief Polynomial initialization */\n    inline void Init(uint8_t id, uint16_t offset, uint8_t size, uint8_t** memory_ptr) {\n        this->_id     = id;\n        this->_offset = offset;\n        this->_size   = size;\n        this->length  = 0;\n        this->_memory = memory_ptr;\n    }\n\n    /* @brief Polynomial memory zeroing */\n    inline void Reset() {\n        memset((void*)ptr(), 0, this->_size);\n    }\n\n    /* @brief Copy polynomial to memory\n     * @param src    - source byte-sequence\n     * @param size   - size of polynomial\n     * @param offset - write offset */\n    inline void Set(const uint8_t* src, uint8_t len, uint8_t offset = 0) {\n        assert(src && len <= this->_size-offset);\n        memcpy(ptr()+offset, src, len * sizeof(uint8_t));\n        length = len + offset;\n    }\n\n#define poly_max(a, b) ((a > b) ? (a) : (b))\n\n    inline void Copy(const Poly* src) {\n        length = poly_max(length, src->length);\n        Set(src->ptr(), length);\n    }\n\n    inline uint8_t& at(uint8_t i) const {\n        assert(i < _size);\n        return ptr()[i];\n    }\n\n    inline uint8_t id() const {\n        return _id;\n    }\n\n    inline uint8_t size() const {\n        return _size;\n    }\n\n    // Returns pointer to memory of this polynomial\n    inline uint8_t* ptr() const {\n        assert(_memory && *_memory);\n        return (*_memory) + _offset;\n    }\n\n    uint8_t length;\n\nprotected:\n\n    uint8_t   _id;\n    uint8_t   _size;    // Size of reserved memory for this polynomial\n    uint16_t  _offset;  // Offset in memory\n    uint8_t** _memory;  // Pointer to pointer to memory\n};\n\nnamespace gf {\n\n\n/* GF tables pre-calculated for 0x11d primitive polynomial */\n\nconst uint8_t exp[512] PROGMEM = {\n    0x1, 0x2, 0x4, 0x8, 0x10, 0x20, 0x40, 0x80, 0x1d, 0x3a, 0x74, 0xe8, 0xcd, 0x87, 0x13, 0x26, 0x4c,\n    0x98, 0x2d, 0x5a, 0xb4, 0x75, 0xea, 0xc9, 0x8f, 0x3, 0x6, 0xc, 0x18, 0x30, 0x60, 0xc0, 0x9d,\n    0x27, 0x4e, 0x9c, 0x25, 0x4a, 0x94, 0x35, 0x6a, 0xd4, 0xb5, 0x77, 0xee, 0xc1, 0x9f, 0x23, 0x46,\n    0x8c, 0x5, 0xa, 0x14, 0x28, 0x50, 0xa0, 0x5d, 0xba, 0x69, 0xd2, 0xb9, 0x6f, 0xde, 0xa1, 0x5f,\n    0xbe, 0x61, 0xc2, 0x99, 0x2f, 0x5e, 0xbc, 0x65, 0xca, 0x89, 0xf, 0x1e, 0x3c, 0x78, 0xf0, 0xfd,\n    0xe7, 0xd3, 0xbb, 0x6b, 0xd6, 0xb1, 0x7f, 0xfe, 0xe1, 0xdf, 0xa3, 0x5b, 0xb6, 0x71, 0xe2, 0xd9,\n    0xaf, 0x43, 0x86, 0x11, 0x22, 0x44, 0x88, 0xd, 0x1a, 0x34, 0x68, 0xd0, 0xbd, 0x67, 0xce, 0x81,\n    0x1f, 0x3e, 0x7c, 0xf8, 0xed, 0xc7, 0x93, 0x3b, 0x76, 0xec, 0xc5, 0x97, 0x33, 0x66, 0xcc, 0x85,\n    0x17, 0x2e, 0x5c, 0xb8, 0x6d, 0xda, 0xa9, 0x4f, 0x9e, 0x21, 0x42, 0x84, 0x15, 0x2a, 0x54, 0xa8,\n    0x4d, 0x9a, 0x29, 0x52, 0xa4, 0x55, 0xaa, 0x49, 0x92, 0x39, 0x72, 0xe4, 0xd5, 0xb7, 0x73, 0xe6,\n    0xd1, 0xbf, 0x63, 0xc6, 0x91, 0x3f, 0x7e, 0xfc, 0xe5, 0xd7, 0xb3, 0x7b, 0xf6, 0xf1, 0xff, 0xe3,\n    0xdb, 0xab, 0x4b, 0x96, 0x31, 0x62, 0xc4, 0x95, 0x37, 0x6e, 0xdc, 0xa5, 0x57, 0xae, 0x41, 0x82,\n    0x19, 0x32, 0x64, 0xc8, 0x8d, 0x7, 0xe, 0x1c, 0x38, 0x70, 0xe0, 0xdd, 0xa7, 0x53, 0xa6, 0x51,\n    0xa2, 0x59, 0xb2, 0x79, 0xf2, 0xf9, 0xef, 0xc3, 0x9b, 0x2b, 0x56, 0xac, 0x45, 0x8a, 0x9, 0x12,\n    0x24, 0x48, 0x90, 0x3d, 0x7a, 0xf4, 0xf5, 0xf7, 0xf3, 0xfb, 0xeb, 0xcb, 0x8b, 0xb, 0x16, 0x2c,\n    0x58, 0xb0, 0x7d, 0xfa, 0xe9, 0xcf, 0x83, 0x1b, 0x36, 0x6c, 0xd8, 0xad, 0x47, 0x8e, 0x1, 0x2,\n    0x4, 0x8, 0x10, 0x20, 0x40, 0x80, 0x1d, 0x3a, 0x74, 0xe8, 0xcd, 0x87, 0x13, 0x26, 0x4c, 0x98,\n    0x2d, 0x5a, 0xb4, 0x75, 0xea, 0xc9, 0x8f, 0x3, 0x6, 0xc, 0x18, 0x30, 0x60, 0xc0, 0x9d, 0x27,\n    0x4e, 0x9c, 0x25, 0x4a, 0x94, 0x35, 0x6a, 0xd4, 0xb5, 0x77, 0xee, 0xc1, 0x9f, 0x23, 0x46, 0x8c,\n    0x5, 0xa, 0x14, 0x28, 0x50, 0xa0, 0x5d, 0xba, 0x69, 0xd2, 0xb9, 0x6f, 0xde, 0xa1, 0x5f, 0xbe,\n    0x61, 0xc2, 0x99, 0x2f, 0x5e, 0xbc, 0x65, 0xca, 0x89, 0xf, 0x1e, 0x3c, 0x78, 0xf0, 0xfd, 0xe7,\n    0xd3, 0xbb, 0x6b, 0xd6, 0xb1, 0x7f, 0xfe, 0xe1, 0xdf, 0xa3, 0x5b, 0xb6, 0x71, 0xe2, 0xd9, 0xaf,\n    0x43, 0x86, 0x11, 0x22, 0x44, 0x88, 0xd, 0x1a, 0x34, 0x68, 0xd0, 0xbd, 0x67, 0xce, 0x81, 0x1f,\n    0x3e, 0x7c, 0xf8, 0xed, 0xc7, 0x93, 0x3b, 0x76, 0xec, 0xc5, 0x97, 0x33, 0x66, 0xcc, 0x85, 0x17,\n    0x2e, 0x5c, 0xb8, 0x6d, 0xda, 0xa9, 0x4f, 0x9e, 0x21, 0x42, 0x84, 0x15, 0x2a, 0x54, 0xa8, 0x4d,\n    0x9a, 0x29, 0x52, 0xa4, 0x55, 0xaa, 0x49, 0x92, 0x39, 0x72, 0xe4, 0xd5, 0xb7, 0x73, 0xe6, 0xd1,\n    0xbf, 0x63, 0xc6, 0x91, 0x3f, 0x7e, 0xfc, 0xe5, 0xd7, 0xb3, 0x7b, 0xf6, 0xf1, 0xff, 0xe3, 0xdb,\n    0xab, 0x4b, 0x96, 0x31, 0x62, 0xc4, 0x95, 0x37, 0x6e, 0xdc, 0xa5, 0x57, 0xae, 0x41, 0x82, 0x19,\n    0x32, 0x64, 0xc8, 0x8d, 0x7, 0xe, 0x1c, 0x38, 0x70, 0xe0, 0xdd, 0xa7, 0x53, 0xa6, 0x51, 0xa2,\n    0x59, 0xb2, 0x79, 0xf2, 0xf9, 0xef, 0xc3, 0x9b, 0x2b, 0x56, 0xac, 0x45, 0x8a, 0x9, 0x12, 0x24,\n    0x48, 0x90, 0x3d, 0x7a, 0xf4, 0xf5, 0xf7, 0xf3, 0xfb, 0xeb, 0xcb, 0x8b, 0xb, 0x16, 0x2c, 0x58,\n    0xb0, 0x7d, 0xfa, 0xe9, 0xcf, 0x83, 0x1b, 0x36, 0x6c, 0xd8, 0xad, 0x47, 0x8e, 0x1, 0x2\n};\n\nconst uint8_t log[256] PROGMEM = {\n    0x0, 0x0, 0x1, 0x19, 0x2, 0x32, 0x1a, 0xc6, 0x3, 0xdf, 0x33, 0xee, 0x1b, 0x68, 0xc7, 0x4b, 0x4,\n    0x64, 0xe0, 0xe, 0x34, 0x8d, 0xef, 0x81, 0x1c, 0xc1, 0x69, 0xf8, 0xc8, 0x8, 0x4c, 0x71, 0x5,\n    0x8a, 0x65, 0x2f, 0xe1, 0x24, 0xf, 0x21, 0x35, 0x93, 0x8e, 0xda, 0xf0, 0x12, 0x82, 0x45, 0x1d,\n    0xb5, 0xc2, 0x7d, 0x6a, 0x27, 0xf9, 0xb9, 0xc9, 0x9a, 0x9, 0x78, 0x4d, 0xe4, 0x72, 0xa6, 0x6,\n    0xbf, 0x8b, 0x62, 0x66, 0xdd, 0x30, 0xfd, 0xe2, 0x98, 0x25, 0xb3, 0x10, 0x91, 0x22, 0x88, 0x36,\n    0xd0, 0x94, 0xce, 0x8f, 0x96, 0xdb, 0xbd, 0xf1, 0xd2, 0x13, 0x5c, 0x83, 0x38, 0x46, 0x40, 0x1e,\n    0x42, 0xb6, 0xa3, 0xc3, 0x48, 0x7e, 0x6e, 0x6b, 0x3a, 0x28, 0x54, 0xfa, 0x85, 0xba, 0x3d, 0xca,\n    0x5e, 0x9b, 0x9f, 0xa, 0x15, 0x79, 0x2b, 0x4e, 0xd4, 0xe5, 0xac, 0x73, 0xf3, 0xa7, 0x57, 0x7,\n    0x70, 0xc0, 0xf7, 0x8c, 0x80, 0x63, 0xd, 0x67, 0x4a, 0xde, 0xed, 0x31, 0xc5, 0xfe, 0x18, 0xe3,\n    0xa5, 0x99, 0x77, 0x26, 0xb8, 0xb4, 0x7c, 0x11, 0x44, 0x92, 0xd9, 0x23, 0x20, 0x89, 0x2e, 0x37,\n    0x3f, 0xd1, 0x5b, 0x95, 0xbc, 0xcf, 0xcd, 0x90, 0x87, 0x97, 0xb2, 0xdc, 0xfc, 0xbe, 0x61, 0xf2,\n    0x56, 0xd3, 0xab, 0x14, 0x2a, 0x5d, 0x9e, 0x84, 0x3c, 0x39, 0x53, 0x47, 0x6d, 0x41, 0xa2, 0x1f,\n    0x2d, 0x43, 0xd8, 0xb7, 0x7b, 0xa4, 0x76, 0xc4, 0x17, 0x49, 0xec, 0x7f, 0xc, 0x6f, 0xf6, 0x6c,\n    0xa1, 0x3b, 0x52, 0x29, 0x9d, 0x55, 0xaa, 0xfb, 0x60, 0x86, 0xb1, 0xbb, 0xcc, 0x3e, 0x5a, 0xcb,\n    0x59, 0x5f, 0xb0, 0x9c, 0xa9, 0xa0, 0x51, 0xb, 0xf5, 0x16, 0xeb, 0x7a, 0x75, 0x2c, 0xd7, 0x4f,\n    0xae, 0xd5, 0xe9, 0xe6, 0xe7, 0xad, 0xe8, 0x74, 0xd6, 0xf4, 0xea, 0xa8, 0x50, 0x58, 0xaf\n};\n\n\n\n/* ################################\n * # OPERATIONS OVER GALUA FIELDS #\n * ################################ */\n\n/* @brief Addition in Galua Fields\n * @param x - left operand\n * @param y - right operand\n * @return x + y */\ninline uint8_t add(uint8_t x, uint8_t y) {\n    return x^y;\n}\n\n/* ##### GF substraction ###### */\n/* @brief Substraction in Galua Fields\n * @param x - left operand\n * @param y - right operand\n * @return x - y */\ninline uint8_t sub(uint8_t x, uint8_t y) {\n    return x^y;\n}\n\n/* @brief Multiplication in Galua Fields\n * @param x - left operand\n * @param y - rifht operand\n * @return x * y */\ninline uint8_t mul(uint16_t x, uint16_t y){\n    if (x == 0 || y == 0)\n        return 0;\n    return pgm_read_byte(exp + pgm_read_byte(log + x) + pgm_read_byte(log + y));\n}\n\n/* @brief Division in Galua Fields\n * @param x - dividend\n * @param y - divisor\n * @return x / y */\ninline uint8_t div(uint8_t x, uint8_t y){\n    assert(y != 0);\n    if(x == 0) return 0;\n    return pgm_read_byte(exp + (pgm_read_byte(log + x) + 255 - pgm_read_byte(log + y)) % 255);\n}\n\n/* @brief X in power Y w\n * @param x     - operand\n * @param power - power\n * @return x^power */\ninline uint8_t pow(uint8_t x, intmax_t power){\n    intmax_t i = pgm_read_byte(log + x);\n    i *= power;\n    i %= 255;\n    if(i < 0) i = i + 255;\n    return pgm_read_byte(exp + i);\n}\n\n/* @brief Inversion in Galua Fields\n * @param x - number\n * @return inversion of x */\ninline uint8_t inverse(uint8_t x){\n    return pgm_read_byte(exp + 255 - pgm_read_byte(log + x)); /* == div(1, x); */\n}\n\n/* ##########################\n * # POLYNOMIALS OPERATIONS #\n * ########################## */\n\n/* @brief Multiplication polynomial by scalar\n * @param &p    - source polynomial\n * @param &newp - destination polynomial\n * @param x     - scalar */\ninline void\npoly_scale(const Poly *p, Poly *newp, uint16_t x) {\n    newp->length = p->length;\n    for(uint16_t i = 0; i < p->length; i++){\n        newp->at(i) = mul(p->at(i), x);\n    }\n}\n\n/* @brief Addition of two polynomials\n * @param &p    - right operand polynomial\n * @param &q    - left operand polynomial\n * @param &newp - destination polynomial */\ninline void\npoly_add(const Poly *p, const Poly *q, Poly *newp) {\n    newp->length = poly_max(p->length, q->length);\n    memset(newp->ptr(), 0, newp->length * sizeof(uint8_t));\n\n    for(uint8_t i = 0; i < p->length; i++){\n        newp->at(i + newp->length - p->length) = p->at(i);\n    }\n\n    for(uint8_t i = 0; i < q->length; i++){\n        newp->at(i + newp->length - q->length) ^= q->at(i);\n    }\n}\n\n\n/* @brief Multiplication of two polynomials\n * @param &p    - right operand polynomial\n * @param &q    - left operand polynomial\n * @param &newp - destination polynomial */\ninline void\npoly_mul(const Poly *p, const Poly *q, Poly *newp) {\n    newp->length = p->length + q->length - 1;\n    memset(newp->ptr(), 0, newp->length * sizeof(uint8_t));\n    /* Compute the polynomial multiplication (just like the outer product of two vectors,\n     * we multiply each coefficients of p with all coefficients of q) */\n    for(uint8_t j = 0; j < q->length; j++){\n        for(uint8_t i = 0; i < p->length; i++){\n            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])) */\n        }\n    }\n}\n\n/* @brief Division of two polynomials\n * @param &p    - right operand polynomial\n * @param &q    - left operand polynomial\n * @param &newp - destination polynomial */\ninline void\npoly_div(const Poly *p, const Poly *q, Poly *newp) {\n    if(p->ptr() != newp->ptr()) {\n        memcpy(newp->ptr(), p->ptr(), p->length*sizeof(uint8_t));\n    }\n\n    newp->length = p->length;\n\n    uint8_t coef;\n\n    for(int i = 0; i < (p->length-(q->length-1)); i++){\n        coef = newp->at(i);\n        if(coef != 0){\n            for(uint8_t j = 1; j < q->length; j++){\n                if(q->at(j) != 0)\n                    newp->at(i+j) ^= mul(q->at(j), coef);\n            }\n        }\n    }\n\n    size_t sep = p->length-(q->length-1);\n    memmove(newp->ptr(), newp->ptr()+sep, (newp->length-sep) * sizeof(uint8_t));\n    newp->length = newp->length-sep;\n}\n\n/* @brief Evaluation of polynomial in x\n * @param &p - polynomial to evaluate\n * @param x  - evaluation point */\ninline int8_t\npoly_eval(const Poly *p, uint16_t x) {\n    uint8_t y = p->at(0);\n    for(uint8_t i = 1; i < p->length; i++){\n        y = mul(y, x) ^ p->at(i);\n    }\n    return y;\n}\n\n} /* end of gf namespace */\n\n#define MSG_CNT 3   // message-length polynomials count\n#define POLY_CNT 14 // (ecc_length*2)-length polynomialc count\n\nclass ReedSolomon {\npublic:\n    const uint8_t msg_length;\n    const uint8_t ecc_length;\n\n    uint8_t * generator_cache = nullptr;\n    bool    generator_cached = false;\n\n    ReedSolomon(uint8_t msg_length_p, uint8_t ecc_length_p) :\n        msg_length(msg_length_p), ecc_length(ecc_length_p) {\n            generator_cache = new uint8_t[ecc_length + 1];\n\n            const uint8_t   enc_len  = msg_length + ecc_length;\n            const uint8_t   poly_len = ecc_length * 2;\n            uint8_t** memptr   = &memory;\n            uint16_t  offset   = 0;\n\n            /* Initialize first six polys manually cause their amount depends on template parameters */\n\n            polynoms[0].Init(ID_MSG_IN, offset, enc_len, memptr);\n            offset += enc_len;\n\n            polynoms[1].Init(ID_MSG_OUT, offset, enc_len, memptr);\n            offset += enc_len;\n\n            for(uint8_t i = ID_GENERATOR; i < ID_MSG_E; i++) {\n                polynoms[i].Init(i, offset, poly_len, memptr);\n                offset += poly_len;\n            }\n\n            polynoms[5].Init(ID_MSG_E, offset, enc_len, memptr);\n            offset += enc_len;\n\n            for(uint8_t i = ID_TPOLY3; i < ID_ERR_EVAL+2; i++) {\n                polynoms[i].Init(i, offset, poly_len, memptr);\n                offset += poly_len;\n            }\n        }\n\n    ~ReedSolomon() {\n        delete [] generator_cache;\n        // Dummy destructor, gcc-generated one crashes programm\n        memory = NULL;\n    }\n\n    /* @brief Message block encoding\n     * @param *src - input message buffer      (msg_lenth size)\n     * @param *dst - output buffer for ecc     (ecc_length size at least) */\n    void EncodeBlock(const void* src, void* dst) {\n        assert(msg_length + ecc_length < 256);\n\n        ///* Allocating memory on stack for polynomials storage */\n        uint8_t stack_memory[MSG_CNT * msg_length + POLY_CNT * ecc_length * 2];\n        this->memory = stack_memory;\n\n        // gg : allocation is now on the heap\n        //std::vector<uint8_t> stack_memory(MSG_CNT * msg_length + POLY_CNT * ecc_length * 2);\n        //this->memory = stack_memory.data();\n\n        const uint8_t* src_ptr = (const uint8_t*) src;\n        uint8_t* dst_ptr = (uint8_t*) dst;\n\n        Poly *msg_in  = &polynoms[ID_MSG_IN];\n        Poly *msg_out = &polynoms[ID_MSG_OUT];\n        Poly *gen     = &polynoms[ID_GENERATOR];\n\n        // Weird shit, but without reseting msg_in it simply doesn't work\n        msg_in->Reset();\n        msg_out->Reset();\n\n        // Using cached generator or generating new one\n        if(generator_cached) {\n            gen->Set(generator_cache, ecc_length + 1);\n        } else {\n            GeneratorPoly();\n            memcpy(generator_cache, gen->ptr(), gen->length);\n            generator_cached = true;\n        }\n\n        // Copying input message to internal polynomial\n        msg_in->Set(src_ptr, msg_length);\n        msg_out->Set(src_ptr, msg_length);\n        msg_out->length = msg_in->length + ecc_length;\n\n        // Here all the magic happens\n        uint8_t coef = 0; // cache\n        for(uint8_t i = 0; i < msg_length; i++){\n            coef = msg_out->at(i);\n            if(coef != 0){\n                for(uint32_t j = 1; j < gen->length; j++){\n                    msg_out->at(i+j) ^= gf::mul(gen->at(j), coef);\n                }\n            }\n        }\n\n        // Copying ECC to the output buffer\n        memcpy(dst_ptr, msg_out->ptr()+msg_length, ecc_length * sizeof(uint8_t));\n    }\n\n    /* @brief Message encoding\n     * @param *src - input message buffer      (msg_lenth size)\n     * @param *dst - output buffer             (msg_length + ecc_length size at least) */\n    void Encode(const void* src, void* dst) {\n        uint8_t* dst_ptr = (uint8_t*) dst;\n\n        // Copying message to the output buffer\n        memcpy(dst_ptr, src, msg_length * sizeof(uint8_t));\n\n        // Calling EncodeBlock to write ecc to out[ut buffer\n        EncodeBlock(src, dst_ptr+msg_length);\n    }\n\n    /* @brief Message block decoding\n     * @param *src         - encoded message buffer   (msg_length size)\n     * @param *ecc         - ecc buffer               (ecc_length size)\n     * @param *msg_out     - output buffer            (msg_length size at least)\n     * @param *erase_pos   - known errors positions\n     * @param erase_count  - count of known errors\n     * @return RESULT_SUCCESS if successfull, error code otherwise */\n    int DecodeBlock(const void* src, const void* ecc, void* dst, uint8_t* erase_pos = NULL, size_t erase_count = 0) {\n        assert(msg_length + ecc_length < 256);\n\n        const uint8_t *src_ptr = (const uint8_t*) src;\n        const uint8_t *ecc_ptr = (const uint8_t*) ecc;\n        uint8_t *dst_ptr = (uint8_t*) dst;\n\n        const uint8_t src_len = msg_length + ecc_length;\n        const uint8_t dst_len = msg_length;\n\n        bool ok;\n\n        ///* Allocation memory on stack  */\n        uint8_t stack_memory[MSG_CNT * msg_length + POLY_CNT * ecc_length * 2];\n        this->memory = stack_memory;\n\n        // gg : allocation is now on the heap\n        //std::vector<uint8_t> stack_memory(MSG_CNT * msg_length + POLY_CNT * ecc_length * 2);\n        //this->memory = stack_memory.data();\n\n        Poly *msg_in  = &polynoms[ID_MSG_IN];\n        Poly *msg_out = &polynoms[ID_MSG_OUT];\n        Poly *epos    = &polynoms[ID_ERASURES];\n\n        // Copying message to polynomials memory\n        msg_in->Set(src_ptr, msg_length);\n        msg_in->Set(ecc_ptr, ecc_length, msg_length);\n        msg_out->Copy(msg_in);\n\n        // Copying known errors to polynomial\n        if(erase_pos == NULL) {\n            epos->length = 0;\n        } else {\n            epos->Set(erase_pos, erase_count);\n            for(uint8_t i = 0; i < epos->length; i++){\n                msg_in->at(epos->at(i)) = 0;\n            }\n        }\n\n        // Too many errors\n        if(epos->length > ecc_length) return 1;\n\n        Poly *synd   = &polynoms[ID_SYNDROMES];\n        Poly *eloc   = &polynoms[ID_ERRORS_LOC];\n        Poly *reloc  = &polynoms[ID_TPOLY1];\n        Poly *err    = &polynoms[ID_ERRORS];\n        Poly *forney = &polynoms[ID_FORNEY];\n\n        // Calculating syndrome\n        CalcSyndromes(msg_in);\n\n        // Checking for errors\n        bool has_errors = false;\n        for(uint8_t i = 0; i < synd->length; i++) {\n            if(synd->at(i) != 0) {\n                has_errors = true;\n                break;\n            }\n        }\n\n        // Going to exit if no errors\n        if(!has_errors) goto return_corrected_msg;\n\n        CalcForneySyndromes(synd, epos, src_len);\n        FindErrorLocator(forney, NULL, epos->length);\n\n        // Reversing syndrome\n        // TODO optimize through special Poly flag\n        reloc->length = eloc->length;\n        for(int8_t i = eloc->length-1, j = 0; i >= 0; i--, j++){\n            reloc->at(j) = eloc->at(i);\n        }\n\n        // Fing errors\n        ok = FindErrors(reloc, src_len);\n        if(!ok) return 1;\n\n        // Error happened while finding errors (so helpfull :D)\n        if(err->length == 0) return 1;\n\n        /* Adding found errors with known */\n        for(uint8_t i = 0; i < err->length; i++) {\n            epos->Append(err->at(i));\n        }\n\n        // Correcting errors\n        CorrectErrata(synd, epos, msg_in);\n\nreturn_corrected_msg:\n        // Wrighting corrected message to output buffer\n        msg_out->length = dst_len;\n        memcpy(dst_ptr, msg_out->ptr(), msg_out->length * sizeof(uint8_t));\n        return 0;\n    }\n\n    /* @brief Message block decoding\n     * @param *src         - encoded message buffer   (msg_length + ecc_length size)\n     * @param *msg_out     - output buffer            (msg_length size at least)\n     * @param *erase_pos   - known errors positions\n     * @param erase_count  - count of known errors\n     * @return RESULT_SUCCESS if successfull, error code otherwise */\n    int Decode(const void* src, void* dst, uint8_t* erase_pos = NULL, size_t erase_count = 0) {\n        const uint8_t *src_ptr = (const uint8_t*) src;\n        const uint8_t *ecc_ptr = src_ptr + msg_length;\n\n        return DecodeBlock(src, ecc_ptr, dst, erase_pos, erase_count);\n    }\n\n#ifndef DEBUG\nprivate:\n#endif\n\n    enum POLY_ID {\n        ID_MSG_IN = 0,\n        ID_MSG_OUT,\n        ID_GENERATOR,   // 3\n        ID_TPOLY1,      // T for Temporary\n        ID_TPOLY2,\n\n        ID_MSG_E,       // 5\n\n        ID_TPOLY3,     // 6\n        ID_TPOLY4,\n\n        ID_SYNDROMES,\n        ID_FORNEY,\n\n        ID_ERASURES_LOC,\n        ID_ERRORS_LOC,\n\n        ID_ERASURES,\n        ID_ERRORS,\n\n        ID_COEF_POS,\n        ID_ERR_EVAL\n    };\n\n    // Pointer for polynomials memory on stack\n    uint8_t* memory;\n    Poly polynoms[MSG_CNT + POLY_CNT];\n\n    void GeneratorPoly() {\n        Poly *gen = polynoms + ID_GENERATOR;\n        gen->at(0) = 1;\n        gen->length = 1;\n\n        Poly *mulp = polynoms + ID_TPOLY1;\n        Poly *temp = polynoms + ID_TPOLY2;\n        mulp->length = 2;\n\n        for(int8_t i = 0; i < ecc_length; i++){\n            mulp->at(0) = 1;\n            mulp->at(1) = gf::pow(2, i);\n\n            gf::poly_mul(gen, mulp, temp);\n\n            gen->Copy(temp);\n        }\n    }\n\n    void CalcSyndromes(const Poly *msg) {\n        Poly *synd = &polynoms[ID_SYNDROMES];\n        synd->length = ecc_length+1;\n        synd->at(0) = 0;\n        for(uint8_t i = 1; i < ecc_length+1; i++){\n            synd->at(i) = gf::poly_eval(msg, gf::pow(2, i-1));\n        }\n    }\n\n    void FindErrataLocator(const Poly *epos) {\n        Poly *errata_loc = &polynoms[ID_ERASURES_LOC];\n        Poly *mulp = &polynoms[ID_TPOLY1];\n        Poly *addp = &polynoms[ID_TPOLY2];\n        Poly *apol = &polynoms[ID_TPOLY3];\n        Poly *temp = &polynoms[ID_TPOLY4];\n\n        errata_loc->length = 1;\n        errata_loc->at(0)  = 1;\n\n        mulp->length = 1;\n        addp->length = 2;\n\n        for(uint8_t i = 0; i < epos->length; i++){\n            mulp->at(0) = 1;\n            addp->at(0) = gf::pow(2, epos->at(i));\n            addp->at(1) = 0;\n\n            gf::poly_add(mulp, addp, apol);\n            gf::poly_mul(errata_loc, apol, temp);\n\n            errata_loc->Copy(temp);\n        }\n    }\n\n    void FindErrorEvaluator(const Poly *synd, const Poly *errata_loc, Poly *dst, uint8_t ecclen) {\n        Poly *mulp = &polynoms[ID_TPOLY1];\n        gf::poly_mul(synd, errata_loc, mulp);\n\n        Poly *divisor = &polynoms[ID_TPOLY2];\n        divisor->length = ecclen+2;\n\n        divisor->Reset();\n        divisor->at(0) = 1;\n\n        gf::poly_div(mulp, divisor, dst);\n    }\n\n    void CorrectErrata(const Poly *synd, const Poly *err_pos, const Poly *msg_in) {\n        Poly *c_pos     = &polynoms[ID_COEF_POS];\n        Poly *corrected = &polynoms[ID_MSG_OUT];\n        c_pos->length = err_pos->length;\n\n        for(uint8_t i = 0; i < err_pos->length; i++)\n            c_pos->at(i) = msg_in->length - 1 - err_pos->at(i);\n\n        /* uses t_poly 1, 2, 3, 4 */\n        FindErrataLocator(c_pos);\n        Poly *errata_loc = &polynoms[ID_ERASURES_LOC];\n\n        /* reversing syndromes */\n        Poly *rsynd = &polynoms[ID_TPOLY3];\n        rsynd->length = synd->length;\n\n        for(int8_t i = synd->length-1, j = 0; i >= 0; i--, j++) {\n            rsynd->at(j) = synd->at(i);\n        }\n\n        /* getting reversed error evaluator polynomial */\n        Poly *re_eval = &polynoms[ID_TPOLY4];\n\n        /* uses T_POLY 1, 2 */\n        FindErrorEvaluator(rsynd, errata_loc, re_eval, errata_loc->length-1);\n\n        /* reversing it back */\n        Poly *e_eval = &polynoms[ID_ERR_EVAL];\n        e_eval->length = re_eval->length;\n        for(int8_t i = re_eval->length-1, j = 0; i >= 0; i--, j++) {\n            e_eval->at(j) = re_eval->at(i);\n        }\n\n        Poly *X = &polynoms[ID_TPOLY1]; /* this will store errors positions */\n        X->length = 0;\n\n        int16_t l;\n        for(uint8_t i = 0; i < c_pos->length; i++){\n            l = 255 - c_pos->at(i);\n            X->Append(gf::pow(2, -l));\n        }\n\n        /* Magnitude polynomial\n           Shit just got real */\n        Poly *E = &polynoms[ID_MSG_E];\n        E->Reset();\n        E->length = msg_in->length;\n\n        uint8_t Xi_inv;\n\n        Poly *err_loc_prime_temp = &polynoms[ID_TPOLY2];\n\n        uint8_t err_loc_prime;\n        uint8_t y;\n\n        for(uint8_t i = 0; i < X->length; i++){\n            Xi_inv = gf::inverse(X->at(i));\n\n            err_loc_prime_temp->length = 0;\n            for(uint8_t j = 0; j < X->length; j++){\n                if(j != i){\n                    err_loc_prime_temp->Append(gf::sub(1, gf::mul(Xi_inv, X->at(j))));\n                }\n            }\n\n            err_loc_prime = 1;\n            for(uint8_t j = 0; j < err_loc_prime_temp->length; j++){\n                err_loc_prime = gf::mul(err_loc_prime, err_loc_prime_temp->at(j));\n            }\n\n            y = gf::poly_eval(re_eval, Xi_inv);\n            y = gf::mul(gf::pow(X->at(i), 1), y);\n\n            E->at(err_pos->at(i)) = gf::div(y, err_loc_prime);\n        }\n\n        gf::poly_add(msg_in, E, corrected);\n    }\n\n    bool FindErrorLocator(const Poly *synd, Poly *erase_loc = NULL, size_t erase_count = 0) {\n        Poly *error_loc = &polynoms[ID_ERRORS_LOC];\n        Poly *err_loc   = &polynoms[ID_TPOLY1];\n        Poly *old_loc   = &polynoms[ID_TPOLY2];\n        Poly *temp      = &polynoms[ID_TPOLY3];\n        Poly *temp2     = &polynoms[ID_TPOLY4];\n\n        if(erase_loc != NULL) {\n            err_loc->Copy(erase_loc);\n            old_loc->Copy(erase_loc);\n        } else {\n            err_loc->length = 1;\n            old_loc->length = 1;\n            err_loc->at(0)  = 1;\n            old_loc->at(0)  = 1;\n        }\n\n        uint8_t synd_shift = 0;\n        if(synd->length > ecc_length) {\n            synd_shift = synd->length - ecc_length;\n        }\n\n        uint8_t K = 0;\n        uint8_t delta = 0;\n        uint8_t index;\n\n        for(uint8_t i = 0; i < ecc_length - erase_count; i++){\n            if(erase_loc != NULL)\n                K = erase_count + i + synd_shift;\n            else\n                K = i + synd_shift;\n\n            delta = synd->at(K);\n            for(uint8_t j = 1; j < err_loc->length; j++) {\n                index = err_loc->length - j - 1;\n                delta ^= gf::mul(err_loc->at(index), synd->at(K-j));\n            }\n\n            old_loc->Append(0);\n\n            if(delta != 0) {\n                if(old_loc->length > err_loc->length) {\n                    gf::poly_scale(old_loc, temp, delta);\n                    gf::poly_scale(err_loc, old_loc, gf::inverse(delta));\n                    err_loc->Copy(temp);\n                }\n                gf::poly_scale(old_loc, temp, delta);\n                gf::poly_add(err_loc, temp, temp2);\n                err_loc->Copy(temp2);\n            }\n        }\n\n        uint32_t shift = 0;\n        while(err_loc->length && err_loc->at(shift) == 0) shift++;\n\n        uint32_t errs = err_loc->length - shift - 1;\n        if(((errs - erase_count) * 2 + erase_count) > ecc_length){\n            return false; /* Error count is greater then we can fix! */\n        }\n\n        memcpy(error_loc->ptr(), err_loc->ptr() + shift, (err_loc->length - shift) * sizeof(uint8_t));\n        error_loc->length = (err_loc->length - shift);\n        return true;\n    }\n\n    bool FindErrors(const Poly *error_loc, size_t msg_in_size) {\n        Poly *err = &polynoms[ID_ERRORS];\n\n        uint8_t errs = error_loc->length - 1;\n        err->length = 0;\n\n        for(uint8_t i = 0; i < msg_in_size; i++) {\n            if(gf::poly_eval(error_loc, gf::pow(2, i)) == 0) {\n                err->Append(msg_in_size - 1 - i);\n            }\n        }\n\n        /* Sanity check:\n         * the number of err/errata positions found\n         * should be exactly the same as the length of the errata locator polynomial */\n        if(err->length != errs)\n            /* couldn't find error locations */\n            return false;\n        return true;\n    }\n\n    void CalcForneySyndromes(const Poly *synd, const Poly *erasures_pos, size_t msg_in_size) {\n        Poly *erase_pos_reversed = &polynoms[ID_TPOLY1];\n        Poly *forney_synd = &polynoms[ID_FORNEY];\n        erase_pos_reversed->length = 0;\n\n        for(uint8_t i = 0; i < erasures_pos->length; i++){\n            erase_pos_reversed->Append(msg_in_size - 1 - erasures_pos->at(i));\n        }\n\n        forney_synd->Reset();\n        forney_synd->Set(synd->ptr()+1, synd->length-1);\n\n        uint8_t x;\n        for(uint8_t i = 0; i < erasures_pos->length; i++) {\n            x = gf::pow(2, erase_pos_reversed->at(i));\n            for(int8_t j = 0; j < forney_synd->length - 1; j++){\n                forney_synd->at(j) = gf::mul(forney_synd->at(j), x) ^ forney_synd->at(j+1);\n            }\n        }\n    }\n};\n\n}\n\nnamespace GGWave {\n\n// Direct-sequence spread magic numbers\n// Used to xor the actual payload\nconst uint8_t kDSSMagic[] = {\n    0x96, 0x9f, 0xb4, 0xaf, 0x1b, 0x91, 0xde, 0xc5, 0x45, 0x75, 0xe8, 0x2e, 0x0f, 0x32, 0x4a, 0x5f,\n    0xb4, 0x56, 0x95, 0xcb, 0x7f, 0x6a, 0x54, 0x6a, 0x48, 0xf2, 0x0b, 0x7b, 0xcd, 0xfb, 0x93, 0x6d,\n    0x3c, 0x77, 0x5e, 0xc3, 0x33, 0x47, 0xc0, 0xf1, 0x71, 0x32, 0x33, 0x27, 0x35, 0x68, 0x47, 0x1f,\n    0x4e, 0xac, 0x23, 0x42, 0x5f, 0x00, 0x37, 0xa4, 0x50, 0x6d, 0x48, 0x24, 0x91, 0x7c, 0xa1, 0x4e,\n};\n\nuint8_t mymax(uint8_t a, uint8_t b) {\n    return (a > b) ? a : b;\n}\n\nuint8_t getECCBytesForLength(uint8_t len) {\n    return len < 4 ? 2 : mymax(4, 2*(len/5));\n}\n\nconst uint8_t kDataLength_bytes = 16;\nconst uint8_t kECCLength_bytes = getECCBytesForLength(kDataLength_bytes);\n\ntypedef enum {\n    GGWAVE_PROTOCOL_MT_NORMAL,\n    GGWAVE_PROTOCOL_MT_FAST,\n    GGWAVE_PROTOCOL_MT_FASTEST,\n} TxProtocolId;\n\nstruct Parameters {\n    int sampleRate;\n    uint8_t freqStart;\n    uint8_t timePerFrame_ms;\n};\n\nvoid send(uint8_t pin, const uint8_t * data, const Parameters & parameters) {\n    const float dF = 48000.0/float(parameters.sampleRate);\n\n    char buf[kDataLength_bytes];\n    char enc[kDataLength_bytes + kECCLength_bytes];\n\n    for (uint8_t i = 0; i < kDataLength_bytes; i++) {\n        buf[i] = data[i] ^ kDSSMagic[i%sizeof(kDSSMagic)];\n    }\n\n    RS::ReedSolomon rsLength(kDataLength_bytes, kECCLength_bytes);\n    rsLength.Encode(buf, enc);\n\n    float fcur = 0.0f;\n    for (int i = 0; i < 2*(kDataLength_bytes + kECCLength_bytes); i++) {\n        uint8_t cur = enc[i/2];\n        if (i%2 == 0) {\n            cur = cur & 0x0F;\n        } else {\n            cur = cur >> 4;\n        }\n\n        fcur = float(parameters.freqStart)*dF + float(cur)*dF;\n\n        tone(pin, fcur);\n        delay(parameters.timePerFrame_ms);\n    }\n\n    noTone(pin);\n    digitalWrite(pin, LOW);\n}\n\nvoid send(uint8_t pin, const uint8_t * data, TxProtocolId protocolId = GGWAVE_PROTOCOL_MT_FASTEST) {\n    Parameters parameters = { 1024, 24, 64 };\n\n    switch (protocolId) {\n        case GGWAVE_PROTOCOL_MT_NORMAL:  parameters = { 1024, 24, 192 }; break;\n        case GGWAVE_PROTOCOL_MT_FAST:    parameters = { 1024, 24, 128 }; break;\n        case GGWAVE_PROTOCOL_MT_FASTEST: parameters = { 1024, 24,  64 }; break;\n    };\n\n    send(pin, data, parameters);\n}\n\nvoid send_text(uint8_t pin, const char * text, TxProtocolId protocolId = GGWAVE_PROTOCOL_MT_FASTEST) {\n    char tx[kDataLength_bytes];\n    memset(tx, 0, sizeof(tx));\n    strncpy(tx, text, sizeof(tx));\n    send(pin, (uint8_t *) tx, protocolId);\n}\n\n}\n"
  },
  {
    "path": "examples/buttons/CMakeLists.txt",
    "content": "set(TARGET buttons)\n\nconfigure_file(${CMAKE_CURRENT_SOURCE_DIR}/index-tmpl.html       ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${TARGET}/index.html @ONLY)\nconfigure_file(${CMAKE_SOURCE_DIR}/bindings/javascript/ggwave.js ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${TARGET}/ggwave.js COPYONLY)\n"
  },
  {
    "path": "examples/buttons/README.md",
    "content": "# buttons\n\nRecord and send commands via [Talking buttons](https://github.com/ggerganov/ggwave/discussions/27)\n\nLive demo: https://buttons.ggerganov.com\n"
  },
  {
    "path": "examples/buttons/index-tmpl.html",
    "content": "<!doctype html>\n<html lang=\"en-us\">\n    <head>\n        <title>ggwave : buttons</title>\n    </head>\n    <body>\n        <div id=\"main-container\">\n            Talking buttons tester:\n\n            <br><br>\n\n            <button onclick=\"onSend('RRR');\">Red</button>\n            <button onclick=\"onSend('GGG');\">Green</button>\n            <button onclick=\"onSend('BBB');\">Blue</button>\n\n            <br><br>\n\n            <i>If you have Fluent Pet talking buttons, enable this option during the recording:</i><br><br>\n            <input type=\"checkbox\" id=\"checkbox-fp\">Fluent Pet</input>\n\n            <br>\n\n            <textarea name=\"textarea\" id=\"rxData\" style=\"width:300px;height:100px;\" disabled hidden></textarea><br>\n\n            <button id=\"captureStart\">Start capturing</button>\n            <button id=\"captureStop\" hidden>Stop capturing</button>\n\n            <br><br>\n\n            <div class=\"cell-version\">\n                <span>\n                    |\n                    Build time: <span class=\"nav-link\">@GIT_DATE@</span> |\n                    Commit hash: <a class=\"nav-link\" href=\"https://github.com/ggerganov/ggwave/commit/@GIT_SHA1@\">@GIT_SHA1@</a> |\n                    Commit subject: <span class=\"nav-link\">@GIT_COMMIT_SUBJECT@</span> |\n                    <a class=\"nav-link\" href=\"https://github.com/ggerganov/ggwave/tree/master/examples/ggwave-js\">Source Code</a> |\n                </span>\n            </div>\n        </div>\n\n        <script type=\"text/javascript\" src=\"ggwave.js\"></script>\n        <script type='text/javascript'>\n            window.AudioContext        = window.AudioContext || window.webkitAudioContext;\n            window.OfflineAudioContext = window.OfflineAudioContext || window.webkitOfflineAudioContext;\n\n            var context  = null;\n            var recorder = null;\n\n            // the ggwave module instance\n            var ggwave           = null;\n            var ggwave_FluentPet = null;\n            var parameters       = null;\n            var instance         = null;\n\n            const kPayloadLength = 3;\n\n            // instantiate the ggwave instance\n            // ggwave_factory comes from the ggwave.js module\n            ggwave_factory().then(function(obj) {\n                ggwave = obj;\n            });\n\n            // instantiate a second instance specific for the Fluent Pet talking buttons\n            ggwave_factory().then(function(obj) {\n                ggwave_FluentPet = obj;\n            });\n\n            var txData       = document.getElementById(\"txData\");\n            var rxData       = document.getElementById(\"rxData\");\n            var captureStart = document.getElementById(\"captureStart\");\n            var captureStop  = document.getElementById(\"captureStop\");\n\n            // helper function\n            function convertTypedArray(src, type) {\n                var buffer = new ArrayBuffer(src.byteLength);\n                var baseView = new src.constructor(buffer).set(src);\n                return new type(buffer);\n            }\n\n            // initialize audio context and ggwave\n            function init() {\n                if (!context) {\n                    context = new AudioContext({sampleRate: 48000});\n\n                    parameters = ggwave.getDefaultParameters();\n                    parameters.payloadLength = kPayloadLength;\n                    parameters.sampleRateInp = context.sampleRate;\n                    parameters.sampleRateOut = context.sampleRate;\n                    parameters.operatingMode = GGWAVE_OPERATING_MODE_RX_AND_TX | GGWAVE_OPERATING_MODE_USE_DSS;\n                    instance = ggwave.init(parameters);\n\n                    parameters = ggwave_FluentPet.getDefaultParameters();\n                    parameters.payloadLength = kPayloadLength;\n                    parameters.sampleRateInp = context.sampleRate;\n                    parameters.sampleRateOut = context.sampleRate - 512;\n                    parameters.operatingMode = GGWAVE_OPERATING_MODE_RX_AND_TX | GGWAVE_OPERATING_MODE_USE_DSS;\n                    instance = ggwave_FluentPet.init(parameters);\n                }\n            }\n\n            //\n            // Tx\n            //\n\n            function onSend(text) {\n                init();\n\n                var payload = new Uint8Array(text.length);\n                for (var i = 0; i < text.length; i++) {\n                    payload[i] = text.charCodeAt(i);\n                }\n\n                // generate audio waveform\n                var waveform = null;\n                if (document.getElementById(\"checkbox-fp\").checked) {\n                    waveform = ggwave_FluentPet.encode(instance, payload, ggwave_FluentPet.ProtocolId.GGWAVE_PROTOCOL_DT_FAST, 25)\n                } else {\n                    waveform = ggwave.encode(instance, payload, ggwave.ProtocolId.GGWAVE_PROTOCOL_DT_FAST, 25)\n                }\n\n                // play audio\n                var buf = convertTypedArray(waveform, Float32Array);\n                var buffer = context.createBuffer(1, buf.length, context.sampleRate);\n                buffer.getChannelData(0).set(buf);\n                var source = context.createBufferSource();\n                source.buffer = buffer;\n                source.connect(context.destination);\n                source.start(0);\n            }\n\n            //\n            // Rx\n            //\n\n            captureStart.addEventListener(\"click\", function () {\n                init();\n\n                let constraints = {\n                    audio: {\n                        // not sure if these are necessary to have\n                        echoCancellation: false,\n                        autoGainControl: false,\n                        noiseSuppression: false\n                    }\n                };\n\n                navigator.mediaDevices.getUserMedia(constraints).then(function (e) {\n                    mediaStream = context.createMediaStreamSource(e);\n\n                    var bufferSize = 1024;\n                    var numberOfInputChannels = 1;\n                    var numberOfOutputChannels = 1;\n\n                    if (context.createScriptProcessor) {\n                        recorder = context.createScriptProcessor(\n                                bufferSize,\n                                numberOfInputChannels,\n                                numberOfOutputChannels);\n                    } else {\n                        recorder = context.createJavaScriptNode(\n                                bufferSize,\n                                numberOfInputChannels,\n                                numberOfOutputChannels);\n                    }\n\n                    recorder.onaudioprocess = function (e) {\n                        var source = e.inputBuffer;\n                        var res = ggwave.decode(instance, convertTypedArray(new Float32Array(source.getChannelData(0)), Int8Array));\n                        if (res && res.length == kPayloadLength) {\n                            // DSS\n                            var payload = \"\";\n                            res8 = convertTypedArray(res, Uint8Array);\n                            for (var i = 0; i < 3; i++) {\n                                payload += String.fromCharCode(res8[i]);\n                            }\n\n                            if (payload == 'RRR' || payload == 'GGG' || payload == 'BBB') {\n                                rxData.value = payload;\n\n                                // change page background color\n                                switch (payload) {\n                                    case 'RRR':\n                                        document.body.style.backgroundColor = '#ff0000';\n                                        break;\n                                    case 'GGG':\n                                        document.body.style.backgroundColor = '#00ff00';\n                                        break;\n                                    case 'BBB':\n                                        document.body.style.backgroundColor = '#0000ff';\n                                        break;\n                                }\n\n                                setTimeout(function () {\n                                    document.body.style.backgroundColor = '#ffffff';\n                                }, 1000);\n                            }\n                        }\n                    }\n\n                    mediaStream.connect(recorder);\n                    recorder.connect(context.destination);\n                }).catch(function (e) {\n                    console.error(e);\n                });\n\n                rxData.value = 'Listening ...';\n                captureStart.hidden = true;\n                captureStop.hidden = false;\n            });\n\n            captureStop.addEventListener(\"click\", function () {\n                if (recorder) {\n                    recorder.disconnect(context.destination);\n                    mediaStream.disconnect(recorder);\n                    recorder = null;\n                }\n\n                rxData.value = 'Audio capture is paused! Press the \"Start capturing\" button to analyze audio from the microphone';\n                captureStart.hidden = false;\n                captureStop.hidden = true;\n            });\n\n            captureStop.click();\n        </script>\n    </body>\n</html>\n"
  },
  {
    "path": "examples/dr_wav.h",
    "content": "/*\nWAV audio loader and writer. Choice of public domain or MIT-0. See license statements at the end of this file.\ndr_wav - v0.12.16 - 2020-12-02\n\nDavid Reid - mackron@gmail.com\n\nGitHub: https://github.com/mackron/dr_libs\n*/\n\n/*\nRELEASE NOTES - VERSION 0.12\n============================\nVersion 0.12 includes breaking changes to custom chunk handling.\n\n\nChanges to Chunk Callback\n-------------------------\ndr_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\ncontainer (RIFF or Wave64) and the FMT chunk which contains information about the format of the data in the wave file.\n\nPreviously, 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\nWave64 containers encode chunk ID's differently). The `container` parameter can be used to know which ID to use.\n\nSometimes 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\ncallback 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\n`DR_WAVE_FORMAT_*` tokens.\n*/\n\n/*\nIntroduction\n============\nThis is a single file library. To use it, do something like the following in one .c file.\n    \n    ```c\n    #define DR_WAV_IMPLEMENTATION\n    #include \"dr_wav.h\"\n    ```\n\nYou 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:\n\n    ```c\n    drwav wav;\n    if (!drwav_init_file(&wav, \"my_song.wav\", NULL)) {\n        // Error opening WAV file.\n    }\n\n    drwav_int32* pDecodedInterleavedPCMFrames = malloc(wav.totalPCMFrameCount * wav.channels * sizeof(drwav_int32));\n    size_t numberOfSamplesActuallyDecoded = drwav_read_pcm_frames_s32(&wav, wav.totalPCMFrameCount, pDecodedInterleavedPCMFrames);\n\n    ...\n\n    drwav_uninit(&wav);\n    ```\n\nIf you just want to quickly open and read the audio data in a single operation you can do something like this:\n\n    ```c\n    unsigned int channels;\n    unsigned int sampleRate;\n    drwav_uint64 totalPCMFrameCount;\n    float* pSampleData = drwav_open_file_and_read_pcm_frames_f32(\"my_song.wav\", &channels, &sampleRate, &totalPCMFrameCount, NULL);\n    if (pSampleData == NULL) {\n        // Error opening and reading WAV file.\n    }\n\n    ...\n\n    drwav_free(pSampleData);\n    ```\n\nThe 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\naudio data in its internal format (see notes below for supported formats):\n\n    ```c\n    size_t framesRead = drwav_read_pcm_frames(&wav, wav.totalPCMFrameCount, pDecodedInterleavedPCMFrames);\n    ```\n\nYou 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:\n\n    ```c\n    size_t bytesRead = drwav_read_raw(&wav, bytesToRead, pRawDataBuffer);\n    ```\n\ndr_wav can also be used to output WAV files. This does not currently support compressed formats. To use this, look at `drwav_init_write()`,\n`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.\n\n    ```c\n    drwav_data_format format;\n    format.container = drwav_container_riff;     // <-- drwav_container_riff = normal WAV files, drwav_container_w64 = Sony Wave64.\n    format.format = DR_WAVE_FORMAT_PCM;          // <-- Any of the DR_WAVE_FORMAT_* codes.\n    format.channels = 2;\n    format.sampleRate = 44100;\n    format.bitsPerSample = 16;\n    drwav_init_file_write(&wav, \"data/recording.wav\", &format, NULL);\n\n    ...\n\n    drwav_uint64 framesWritten = drwav_write_pcm_frames(pWav, frameCount, pSamples);\n    ```\n\ndr_wav has seamless support the Sony Wave64 format. The decoder will automatically detect it and it should Just Work without any manual intervention.\n\n\nBuild Options\n=============\n#define these options before including this file.\n\n#define DR_WAV_NO_CONVERSION_API\n  Disables conversion APIs such as `drwav_read_pcm_frames_f32()` and `drwav_s16_to_f32()`.\n\n#define DR_WAV_NO_STDIO\n  Disables APIs that initialize a decoder from a file such as `drwav_init_file()`, `drwav_init_file_write()`, etc.\n\n\n\nNotes\n=====\n- Samples are always interleaved.\n- 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()`\n  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\n  formats include the following:\n  - Unsigned 8-bit PCM\n  - Signed 12-bit PCM\n  - Signed 16-bit PCM\n  - Signed 24-bit PCM\n  - Signed 32-bit PCM\n  - IEEE 32-bit floating point\n  - IEEE 64-bit floating point\n  - A-law and u-law\n  - Microsoft ADPCM\n  - IMA ADPCM (DVI, format code 0x11)\n- dr_wav will try to read the WAV file as best it can, even if it's not strictly conformant to the WAV format.\n*/\n\n#ifndef dr_wav_h\n#define dr_wav_h\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\n#define DRWAV_STRINGIFY(x)      #x\n#define DRWAV_XSTRINGIFY(x)     DRWAV_STRINGIFY(x)\n\n#define DRWAV_VERSION_MAJOR     0\n#define DRWAV_VERSION_MINOR     12\n#define DRWAV_VERSION_REVISION  16\n#define DRWAV_VERSION_STRING    DRWAV_XSTRINGIFY(DRWAV_VERSION_MAJOR) \".\" DRWAV_XSTRINGIFY(DRWAV_VERSION_MINOR) \".\" DRWAV_XSTRINGIFY(DRWAV_VERSION_REVISION)\n\n#include <stddef.h> /* For size_t. */\n\n/* Sized types. */\ntypedef   signed char           drwav_int8;\ntypedef unsigned char           drwav_uint8;\ntypedef   signed short          drwav_int16;\ntypedef unsigned short          drwav_uint16;\ntypedef   signed int            drwav_int32;\ntypedef unsigned int            drwav_uint32;\n#if defined(_MSC_VER)\n    typedef   signed __int64    drwav_int64;\n    typedef unsigned __int64    drwav_uint64;\n#else\n    #if defined(__clang__) || (defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)))\n        #pragma GCC diagnostic push\n        #pragma GCC diagnostic ignored \"-Wlong-long\"\n        #if defined(__clang__)\n            #pragma GCC diagnostic ignored \"-Wc++11-long-long\"\n        #endif\n    #endif\n    typedef   signed long long  drwav_int64;\n    typedef unsigned long long  drwav_uint64;\n    #if defined(__clang__) || (defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)))\n        #pragma GCC diagnostic pop\n    #endif\n#endif\n#if defined(__LP64__) || defined(_WIN64) || (defined(__x86_64__) && !defined(__ILP32__)) || defined(_M_X64) || defined(__ia64) || defined (_M_IA64) || defined(__aarch64__) || defined(__powerpc64__)\n    typedef drwav_uint64        drwav_uintptr;\n#else\n    typedef drwav_uint32        drwav_uintptr;\n#endif\ntypedef drwav_uint8             drwav_bool8;\ntypedef drwav_uint32            drwav_bool32;\n#define DRWAV_TRUE              1\n#define DRWAV_FALSE             0\n\n#if !defined(DRWAV_API)\n    #if defined(DRWAV_DLL)\n        #if defined(_WIN32)\n            #define DRWAV_DLL_IMPORT  __declspec(dllimport)\n            #define DRWAV_DLL_EXPORT  __declspec(dllexport)\n            #define DRWAV_DLL_PRIVATE static\n        #else\n            #if defined(__GNUC__) && __GNUC__ >= 4\n                #define DRWAV_DLL_IMPORT  __attribute__((visibility(\"default\")))\n                #define DRWAV_DLL_EXPORT  __attribute__((visibility(\"default\")))\n                #define DRWAV_DLL_PRIVATE __attribute__((visibility(\"hidden\")))\n            #else\n                #define DRWAV_DLL_IMPORT\n                #define DRWAV_DLL_EXPORT\n                #define DRWAV_DLL_PRIVATE static\n            #endif\n        #endif\n\n        #if defined(DR_WAV_IMPLEMENTATION) || defined(DRWAV_IMPLEMENTATION)\n            #define DRWAV_API  DRWAV_DLL_EXPORT\n        #else\n            #define DRWAV_API  DRWAV_DLL_IMPORT\n        #endif\n        #define DRWAV_PRIVATE DRWAV_DLL_PRIVATE\n    #else\n        #define DRWAV_API extern\n        #define DRWAV_PRIVATE static\n    #endif\n#endif\n\ntypedef drwav_int32 drwav_result;\n#define DRWAV_SUCCESS                        0\n#define DRWAV_ERROR                         -1   /* A generic error. */\n#define DRWAV_INVALID_ARGS                  -2\n#define DRWAV_INVALID_OPERATION             -3\n#define DRWAV_OUT_OF_MEMORY                 -4\n#define DRWAV_OUT_OF_RANGE                  -5\n#define DRWAV_ACCESS_DENIED                 -6\n#define DRWAV_DOES_NOT_EXIST                -7\n#define DRWAV_ALREADY_EXISTS                -8\n#define DRWAV_TOO_MANY_OPEN_FILES           -9\n#define DRWAV_INVALID_FILE                  -10\n#define DRWAV_TOO_BIG                       -11\n#define DRWAV_PATH_TOO_LONG                 -12\n#define DRWAV_NAME_TOO_LONG                 -13\n#define DRWAV_NOT_DIRECTORY                 -14\n#define DRWAV_IS_DIRECTORY                  -15\n#define DRWAV_DIRECTORY_NOT_EMPTY           -16\n#define DRWAV_END_OF_FILE                   -17\n#define DRWAV_NO_SPACE                      -18\n#define DRWAV_BUSY                          -19\n#define DRWAV_IO_ERROR                      -20\n#define DRWAV_INTERRUPT                     -21\n#define DRWAV_UNAVAILABLE                   -22\n#define DRWAV_ALREADY_IN_USE                -23\n#define DRWAV_BAD_ADDRESS                   -24\n#define DRWAV_BAD_SEEK                      -25\n#define DRWAV_BAD_PIPE                      -26\n#define DRWAV_DEADLOCK                      -27\n#define DRWAV_TOO_MANY_LINKS                -28\n#define DRWAV_NOT_IMPLEMENTED               -29\n#define DRWAV_NO_MESSAGE                    -30\n#define DRWAV_BAD_MESSAGE                   -31\n#define DRWAV_NO_DATA_AVAILABLE             -32\n#define DRWAV_INVALID_DATA                  -33\n#define DRWAV_TIMEOUT                       -34\n#define DRWAV_NO_NETWORK                    -35\n#define DRWAV_NOT_UNIQUE                    -36\n#define DRWAV_NOT_SOCKET                    -37\n#define DRWAV_NO_ADDRESS                    -38\n#define DRWAV_BAD_PROTOCOL                  -39\n#define DRWAV_PROTOCOL_UNAVAILABLE          -40\n#define DRWAV_PROTOCOL_NOT_SUPPORTED        -41\n#define DRWAV_PROTOCOL_FAMILY_NOT_SUPPORTED -42\n#define DRWAV_ADDRESS_FAMILY_NOT_SUPPORTED  -43\n#define DRWAV_SOCKET_NOT_SUPPORTED          -44\n#define DRWAV_CONNECTION_RESET              -45\n#define DRWAV_ALREADY_CONNECTED             -46\n#define DRWAV_NOT_CONNECTED                 -47\n#define DRWAV_CONNECTION_REFUSED            -48\n#define DRWAV_NO_HOST                       -49\n#define DRWAV_IN_PROGRESS                   -50\n#define DRWAV_CANCELLED                     -51\n#define DRWAV_MEMORY_ALREADY_MAPPED         -52\n#define DRWAV_AT_END                        -53\n\n/* Common data formats. */\n#define DR_WAVE_FORMAT_PCM          0x1\n#define DR_WAVE_FORMAT_ADPCM        0x2\n#define DR_WAVE_FORMAT_IEEE_FLOAT   0x3\n#define DR_WAVE_FORMAT_ALAW         0x6\n#define DR_WAVE_FORMAT_MULAW        0x7\n#define DR_WAVE_FORMAT_DVI_ADPCM    0x11\n#define DR_WAVE_FORMAT_EXTENSIBLE   0xFFFE\n\n/* Constants. */\n#ifndef DRWAV_MAX_SMPL_LOOPS\n#define DRWAV_MAX_SMPL_LOOPS        1\n#endif\n\n/* Flags to pass into drwav_init_ex(), etc. */\n#define DRWAV_SEQUENTIAL            0x00000001\n\nDRWAV_API void drwav_version(drwav_uint32* pMajor, drwav_uint32* pMinor, drwav_uint32* pRevision);\nDRWAV_API const char* drwav_version_string(void);\n\ntypedef enum\n{\n    drwav_seek_origin_start,\n    drwav_seek_origin_current\n} drwav_seek_origin;\n\ntypedef enum\n{\n    drwav_container_riff,\n    drwav_container_w64,\n    drwav_container_rf64\n} drwav_container;\n\ntypedef struct\n{\n    union\n    {\n        drwav_uint8 fourcc[4];\n        drwav_uint8 guid[16];\n    } id;\n\n    /* The size in bytes of the chunk. */\n    drwav_uint64 sizeInBytes;\n\n    /*\n    RIFF = 2 byte alignment.\n    W64  = 8 byte alignment.\n    */\n    unsigned int paddingSize;\n} drwav_chunk_header;\n\ntypedef struct\n{\n    /*\n    The format tag exactly as specified in the wave file's \"fmt\" chunk. This can be used by applications\n    that require support for data formats not natively supported by dr_wav.\n    */\n    drwav_uint16 formatTag;\n\n    /* The number of channels making up the audio data. When this is set to 1 it is mono, 2 is stereo, etc. */\n    drwav_uint16 channels;\n\n    /* The sample rate. Usually set to something like 44100. */\n    drwav_uint32 sampleRate;\n\n    /* Average bytes per second. You probably don't need this, but it's left here for informational purposes. */\n    drwav_uint32 avgBytesPerSec;\n\n    /* Block align. This is equal to the number of channels * bytes per sample. */\n    drwav_uint16 blockAlign;\n\n    /* Bits per sample. */\n    drwav_uint16 bitsPerSample;\n\n    /* The size of the extended data. Only used internally for validation, but left here for informational purposes. */\n    drwav_uint16 extendedSize;\n\n    /*\n    The number of valid bits per sample. When <formatTag> is equal to WAVE_FORMAT_EXTENSIBLE, <bitsPerSample>\n    is always rounded up to the nearest multiple of 8. This variable contains information about exactly how\n    many bits are valid per sample. Mainly used for informational purposes.\n    */\n    drwav_uint16 validBitsPerSample;\n\n    /* The channel mask. Not used at the moment. */\n    drwav_uint32 channelMask;\n\n    /* The sub-format, exactly as specified by the wave file. */\n    drwav_uint8 subFormat[16];\n} drwav_fmt;\n\nDRWAV_API drwav_uint16 drwav_fmt_get_format(const drwav_fmt* pFMT);\n\n\n/*\nCallback for when data is read. Return value is the number of bytes actually read.\n\npUserData   [in]  The user data that was passed to drwav_init() and family.\npBufferOut  [out] The output buffer.\nbytesToRead [in]  The number of bytes to read.\n\nReturns the number of bytes actually read.\n\nA return value of less than bytesToRead indicates the end of the stream. Do _not_ return from this callback until\neither the entire bytesToRead is filled or you have reached the end of the stream.\n*/\ntypedef size_t (* drwav_read_proc)(void* pUserData, void* pBufferOut, size_t bytesToRead);\n\n/*\nCallback for when data is written. Returns value is the number of bytes actually written.\n\npUserData    [in]  The user data that was passed to drwav_init_write() and family.\npData        [out] A pointer to the data to write.\nbytesToWrite [in]  The number of bytes to write.\n\nReturns the number of bytes actually written.\n\nIf the return value differs from bytesToWrite, it indicates an error.\n*/\ntypedef size_t (* drwav_write_proc)(void* pUserData, const void* pData, size_t bytesToWrite);\n\n/*\nCallback for when data needs to be seeked.\n\npUserData [in] The user data that was passed to drwav_init() and family.\noffset    [in] The number of bytes to move, relative to the origin. Will never be negative.\norigin    [in] The origin of the seek - the current position or the start of the stream.\n\nReturns whether or not the seek was successful.\n\nWhether 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\ndrwav_seek_origin_current.\n*/\ntypedef drwav_bool32 (* drwav_seek_proc)(void* pUserData, int offset, drwav_seek_origin origin);\n\n/*\nCallback for when drwav_init_ex() finds a chunk.\n\npChunkUserData    [in] The user data that was passed to the pChunkUserData parameter of drwav_init_ex() and family.\nonRead            [in] A pointer to the function to call when reading.\nonSeek            [in] A pointer to the function to call when seeking.\npReadSeekUserData [in] The user data that was passed to the pReadSeekUserData parameter of drwav_init_ex() and family.\npChunkHeader      [in] A pointer to an object containing basic header information about the chunk. Use this to identify the chunk.\ncontainer         [in] Whether or not the WAV file is a RIFF or Wave64 container. If you're unsure of the difference, assume RIFF.\npFMT              [in] A pointer to the object containing the contents of the \"fmt\" chunk.\n\nReturns the number of bytes read + seeked.\n\nTo 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\nbe the total number of bytes you have read _plus_ seeked.\n\nUse the `container` argument to discriminate the fields in `pChunkHeader->id`. If the container is `drwav_container_riff` or `drwav_container_rf64` you should\nuse `id.fourcc`, otherwise you should use `id.guid`.\n\nThe `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\n`DR_WAVE_FORMAT_*` identifiers. \n\nThe 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.\n*/\ntypedef 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);\n\ntypedef struct\n{\n    void* pUserData;\n    void* (* onMalloc)(size_t sz, void* pUserData);\n    void* (* onRealloc)(void* p, size_t sz, void* pUserData);\n    void  (* onFree)(void* p, void* pUserData);\n} drwav_allocation_callbacks;\n\n/* Structure for internal use. Only used for loaders opened with drwav_init_memory(). */\ntypedef struct\n{\n    const drwav_uint8* data;\n    size_t dataSize;\n    size_t currentReadPos;\n} drwav__memory_stream;\n\n/* Structure for internal use. Only used for writers opened with drwav_init_memory_write(). */\ntypedef struct\n{\n    void** ppData;\n    size_t* pDataSize;\n    size_t dataSize;\n    size_t dataCapacity;\n    size_t currentWritePos;\n} drwav__memory_stream_write;\n\ntypedef struct\n{\n    drwav_container container;  /* RIFF, W64. */\n    drwav_uint32 format;        /* DR_WAVE_FORMAT_* */\n    drwav_uint32 channels;\n    drwav_uint32 sampleRate;\n    drwav_uint32 bitsPerSample;\n} drwav_data_format;\n\n\n/* See the following for details on the 'smpl' chunk: https://sites.google.com/site/musicgapi/technical-documents/wav-file-format#smpl */\ntypedef struct\n{\n    drwav_uint32 cuePointId;\n    drwav_uint32 type;\n    drwav_uint32 start;\n    drwav_uint32 end;\n    drwav_uint32 fraction;\n    drwav_uint32 playCount;\n} drwav_smpl_loop;\n\n typedef struct\n{\n    drwav_uint32 manufacturer;\n    drwav_uint32 product;\n    drwav_uint32 samplePeriod;\n    drwav_uint32 midiUnityNotes;\n    drwav_uint32 midiPitchFraction;\n    drwav_uint32 smpteFormat;\n    drwav_uint32 smpteOffset;\n    drwav_uint32 numSampleLoops;\n    drwav_uint32 samplerData;\n    drwav_smpl_loop loops[DRWAV_MAX_SMPL_LOOPS];\n} drwav_smpl;\n\ntypedef struct\n{\n    /* A pointer to the function to call when more data is needed. */\n    drwav_read_proc onRead;\n\n    /* A pointer to the function to call when data needs to be written. Only used when the drwav object is opened in write mode. */\n    drwav_write_proc onWrite;\n\n    /* A pointer to the function to call when the wav file needs to be seeked. */\n    drwav_seek_proc onSeek;\n\n    /* The user data to pass to callbacks. */\n    void* pUserData;\n\n    /* Allocation callbacks. */\n    drwav_allocation_callbacks allocationCallbacks;\n\n\n    /* Whether or not the WAV file is formatted as a standard RIFF file or W64. */\n    drwav_container container;\n\n\n    /* Structure containing format information exactly as specified by the wav file. */\n    drwav_fmt fmt;\n\n    /* The sample rate. Will be set to something like 44100. */\n    drwav_uint32 sampleRate;\n\n    /* The number of channels. This will be set to 1 for monaural streams, 2 for stereo, etc. */\n    drwav_uint16 channels;\n\n    /* The bits per sample. Will be set to something like 16, 24, etc. */\n    drwav_uint16 bitsPerSample;\n\n    /* Equal to fmt.formatTag, or the value specified by fmt.subFormat if fmt.formatTag is equal to 65534 (WAVE_FORMAT_EXTENSIBLE). */\n    drwav_uint16 translatedFormatTag;\n\n    /* The total number of PCM frames making up the audio data. */\n    drwav_uint64 totalPCMFrameCount;\n\n\n    /* The size in bytes of the data chunk. */\n    drwav_uint64 dataChunkDataSize;\n    \n    /* The position in the stream of the first byte of the data chunk. This is used for seeking. */\n    drwav_uint64 dataChunkDataPos;\n\n    /* The number of bytes remaining in the data chunk. */\n    drwav_uint64 bytesRemaining;\n\n\n    /*\n    Only used in sequential write mode. Keeps track of the desired size of the \"data\" chunk at the point of initialization time. Always\n    set to 0 for non-sequential writes and when the drwav object is opened in read mode. Used for validation.\n    */\n    drwav_uint64 dataChunkDataSizeTargetWrite;\n\n    /* Keeps track of whether or not the wav writer was initialized in sequential mode. */\n    drwav_bool32 isSequentialWrite;\n\n\n    /* smpl chunk. */\n    drwav_smpl smpl;\n\n\n    /* A hack to avoid a DRWAV_MALLOC() when opening a decoder with drwav_init_memory(). */\n    drwav__memory_stream memoryStream;\n    drwav__memory_stream_write memoryStreamWrite;\n\n    /* Generic data for compressed formats. This data is shared across all block-compressed formats. */\n    struct\n    {\n        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. */\n    } compressed;\n    \n    /* Microsoft ADPCM specific data. */\n    struct\n    {\n        drwav_uint32 bytesRemainingInBlock;\n        drwav_uint16 predictor[2];\n        drwav_int32  delta[2];\n        drwav_int32  cachedFrames[4];  /* Samples are stored in this cache during decoding. */\n        drwav_uint32 cachedFrameCount;\n        drwav_int32  prevFrames[2][2]; /* The previous 2 samples for each channel (2 channels at most). */\n    } msadpcm;\n\n    /* IMA ADPCM specific data. */\n    struct\n    {\n        drwav_uint32 bytesRemainingInBlock;\n        drwav_int32  predictor[2];\n        drwav_int32  stepIndex[2];\n        drwav_int32  cachedFrames[16]; /* Samples are stored in this cache during decoding. */\n        drwav_uint32 cachedFrameCount;\n    } ima;\n} drwav;\n\n\n/*\nInitializes a pre-allocated drwav object for reading.\n\npWav                         [out]          A pointer to the drwav object being initialized.\nonRead                       [in]           The function to call when data needs to be read from the client.\nonSeek                       [in]           The function to call when the read position of the client data needs to move.\nonChunk                      [in, optional] The function to call when a chunk is enumerated at initialized time.\npUserData, pReadSeekUserData [in, optional] A pointer to application defined data that will be passed to onRead and onSeek.\npChunkUserData               [in, optional] A pointer to application defined data that will be passed to onChunk.\nflags                        [in, optional] A set of flags for controlling how things are loaded.\n\nReturns true if successful; false otherwise.\n\nClose the loader with drwav_uninit().\n\nThis is the lowest level function for initializing a WAV file. You can also use drwav_init_file() and drwav_init_memory()\nto open the stream from a file or from a block of memory respectively.\n\nPossible values for flags:\n  DRWAV_SEQUENTIAL: Never perform a backwards seek while loading. This disables the chunk callback and will cause this function\n                    to return as soon as the data chunk is found. Any chunks after the data chunk will be ignored.\n\ndrwav_init() is equivalent to \"drwav_init_ex(pWav, onRead, onSeek, NULL, pUserData, NULL, 0);\".\n\nThe onChunk callback is not called for the WAVE or FMT chunks. The contents of the FMT chunk can be read from pWav->fmt\nafter the function returns.\n\nSee also: drwav_init_file(), drwav_init_memory(), drwav_uninit()\n*/\nDRWAV_API drwav_bool32 drwav_init(drwav* pWav, drwav_read_proc onRead, drwav_seek_proc onSeek, void* pUserData, const drwav_allocation_callbacks* pAllocationCallbacks);\nDRWAV_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);\n\n/*\nInitializes a pre-allocated drwav object for writing.\n\nonWrite   [in]           The function to call when data needs to be written.\nonSeek    [in]           The function to call when the write position needs to move.\npUserData [in, optional] A pointer to application defined data that will be passed to onWrite and onSeek.\n\nReturns true if successful; false otherwise.\n\nClose the writer with drwav_uninit().\n\nThis is the lowest level function for initializing a WAV file. You can also use drwav_init_file_write() and drwav_init_memory_write()\nto open the stream from a file or from a block of memory respectively.\n\nIf the total sample count is known, you can use drwav_init_write_sequential(). This avoids the need for dr_wav to perform\na post-processing step for storing the total sample count and the size of the data chunk which requires a backwards seek.\n\nSee also: drwav_init_file_write(), drwav_init_memory_write(), drwav_uninit()\n*/\nDRWAV_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);\nDRWAV_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);\nDRWAV_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);\n\n/*\nUtility function to determine the target size of the entire data to be written (including all headers and chunks).\n\nReturns the target size in bytes.\n\nUseful if the application needs to know the size to allocate.\n\nOnly writing to the RIFF chunk and one data chunk is currently supported.\n\nSee also: drwav_init_write(), drwav_init_file_write(), drwav_init_memory_write()\n*/\nDRWAV_API drwav_uint64 drwav_target_write_size_bytes(const drwav_data_format* pFormat, drwav_uint64 totalSampleCount);\n\n/*\nUninitializes the given drwav object.\n\nUse this only for objects initialized with drwav_init*() functions (drwav_init(), drwav_init_ex(), drwav_init_write(), drwav_init_write_sequential()).\n*/\nDRWAV_API drwav_result drwav_uninit(drwav* pWav);\n\n\n/*\nReads raw audio data.\n\nThis is the lowest level function for reading audio data. It simply reads the given number of\nbytes of the raw internal sample data.\n\nConsider using drwav_read_pcm_frames_s16(), drwav_read_pcm_frames_s32() or drwav_read_pcm_frames_f32() for\nreading sample data in a consistent format.\n\npBufferOut can be NULL in which case a seek will be performed.\n\nReturns the number of bytes actually read.\n*/\nDRWAV_API size_t drwav_read_raw(drwav* pWav, size_t bytesToRead, void* pBufferOut);\n\n/*\nReads up to the specified number of PCM frames from the WAV file.\n\nThe output data will be in the file's internal format, converted to native-endian byte order. Use\ndrwav_read_pcm_frames_s16/f32/s32() to read data in a specific format.\n\nIf the return value is less than <framesToRead> it means the end of the file has been reached or\nyou have requested more PCM frames than can possibly fit in the output buffer.\n\nThis function will only work when sample data is of a fixed size and uncompressed. If you are\nusing a compressed format consider using drwav_read_raw() or drwav_read_pcm_frames_s16/s32/f32().\n\npBufferOut can be NULL in which case a seek will be performed.\n*/\nDRWAV_API drwav_uint64 drwav_read_pcm_frames(drwav* pWav, drwav_uint64 framesToRead, void* pBufferOut);\nDRWAV_API drwav_uint64 drwav_read_pcm_frames_le(drwav* pWav, drwav_uint64 framesToRead, void* pBufferOut);\nDRWAV_API drwav_uint64 drwav_read_pcm_frames_be(drwav* pWav, drwav_uint64 framesToRead, void* pBufferOut);\n\n/*\nSeeks to the given PCM frame.\n\nReturns true if successful; false otherwise.\n*/\nDRWAV_API drwav_bool32 drwav_seek_to_pcm_frame(drwav* pWav, drwav_uint64 targetFrameIndex);\n\n\n/*\nWrites raw audio data.\n\nReturns the number of bytes actually written. If this differs from bytesToWrite, it indicates an error.\n*/\nDRWAV_API size_t drwav_write_raw(drwav* pWav, size_t bytesToWrite, const void* pData);\n\n/*\nWrites PCM frames.\n\nReturns the number of PCM frames written.\n\nInput samples need to be in native-endian byte order. On big-endian architectures the input data will be converted to\nlittle-endian. Use drwav_write_raw() to write raw audio data without performing any conversion.\n*/\nDRWAV_API drwav_uint64 drwav_write_pcm_frames(drwav* pWav, drwav_uint64 framesToWrite, const void* pData);\nDRWAV_API drwav_uint64 drwav_write_pcm_frames_le(drwav* pWav, drwav_uint64 framesToWrite, const void* pData);\nDRWAV_API drwav_uint64 drwav_write_pcm_frames_be(drwav* pWav, drwav_uint64 framesToWrite, const void* pData);\n\n\n/* Conversion Utilities */\n#ifndef DR_WAV_NO_CONVERSION_API\n\n/*\nReads a chunk of audio data and converts it to signed 16-bit PCM samples.\n\npBufferOut can be NULL in which case a seek will be performed.\n\nReturns the number of PCM frames actually read.\n\nIf the return value is less than <framesToRead> it means the end of the file has been reached.\n*/\nDRWAV_API drwav_uint64 drwav_read_pcm_frames_s16(drwav* pWav, drwav_uint64 framesToRead, drwav_int16* pBufferOut);\nDRWAV_API drwav_uint64 drwav_read_pcm_frames_s16le(drwav* pWav, drwav_uint64 framesToRead, drwav_int16* pBufferOut);\nDRWAV_API drwav_uint64 drwav_read_pcm_frames_s16be(drwav* pWav, drwav_uint64 framesToRead, drwav_int16* pBufferOut);\n\n/* Low-level function for converting unsigned 8-bit PCM samples to signed 16-bit PCM samples. */\nDRWAV_API void drwav_u8_to_s16(drwav_int16* pOut, const drwav_uint8* pIn, size_t sampleCount);\n\n/* Low-level function for converting signed 24-bit PCM samples to signed 16-bit PCM samples. */\nDRWAV_API void drwav_s24_to_s16(drwav_int16* pOut, const drwav_uint8* pIn, size_t sampleCount);\n\n/* Low-level function for converting signed 32-bit PCM samples to signed 16-bit PCM samples. */\nDRWAV_API void drwav_s32_to_s16(drwav_int16* pOut, const drwav_int32* pIn, size_t sampleCount);\n\n/* Low-level function for converting IEEE 32-bit floating point samples to signed 16-bit PCM samples. */\nDRWAV_API void drwav_f32_to_s16(drwav_int16* pOut, const float* pIn, size_t sampleCount);\n\n/* Low-level function for converting IEEE 64-bit floating point samples to signed 16-bit PCM samples. */\nDRWAV_API void drwav_f64_to_s16(drwav_int16* pOut, const double* pIn, size_t sampleCount);\n\n/* Low-level function for converting A-law samples to signed 16-bit PCM samples. */\nDRWAV_API void drwav_alaw_to_s16(drwav_int16* pOut, const drwav_uint8* pIn, size_t sampleCount);\n\n/* Low-level function for converting u-law samples to signed 16-bit PCM samples. */\nDRWAV_API void drwav_mulaw_to_s16(drwav_int16* pOut, const drwav_uint8* pIn, size_t sampleCount);\n\n\n/*\nReads a chunk of audio data and converts it to IEEE 32-bit floating point samples.\n\npBufferOut can be NULL in which case a seek will be performed.\n\nReturns the number of PCM frames actually read.\n\nIf the return value is less than <framesToRead> it means the end of the file has been reached.\n*/\nDRWAV_API drwav_uint64 drwav_read_pcm_frames_f32(drwav* pWav, drwav_uint64 framesToRead, float* pBufferOut);\nDRWAV_API drwav_uint64 drwav_read_pcm_frames_f32le(drwav* pWav, drwav_uint64 framesToRead, float* pBufferOut);\nDRWAV_API drwav_uint64 drwav_read_pcm_frames_f32be(drwav* pWav, drwav_uint64 framesToRead, float* pBufferOut);\n\n/* Low-level function for converting unsigned 8-bit PCM samples to IEEE 32-bit floating point samples. */\nDRWAV_API void drwav_u8_to_f32(float* pOut, const drwav_uint8* pIn, size_t sampleCount);\n\n/* Low-level function for converting signed 16-bit PCM samples to IEEE 32-bit floating point samples. */\nDRWAV_API void drwav_s16_to_f32(float* pOut, const drwav_int16* pIn, size_t sampleCount);\n\n/* Low-level function for converting signed 24-bit PCM samples to IEEE 32-bit floating point samples. */\nDRWAV_API void drwav_s24_to_f32(float* pOut, const drwav_uint8* pIn, size_t sampleCount);\n\n/* Low-level function for converting signed 32-bit PCM samples to IEEE 32-bit floating point samples. */\nDRWAV_API void drwav_s32_to_f32(float* pOut, const drwav_int32* pIn, size_t sampleCount);\n\n/* Low-level function for converting IEEE 64-bit floating point samples to IEEE 32-bit floating point samples. */\nDRWAV_API void drwav_f64_to_f32(float* pOut, const double* pIn, size_t sampleCount);\n\n/* Low-level function for converting A-law samples to IEEE 32-bit floating point samples. */\nDRWAV_API void drwav_alaw_to_f32(float* pOut, const drwav_uint8* pIn, size_t sampleCount);\n\n/* Low-level function for converting u-law samples to IEEE 32-bit floating point samples. */\nDRWAV_API void drwav_mulaw_to_f32(float* pOut, const drwav_uint8* pIn, size_t sampleCount);\n\n\n/*\nReads a chunk of audio data and converts it to signed 32-bit PCM samples.\n\npBufferOut can be NULL in which case a seek will be performed.\n\nReturns the number of PCM frames actually read.\n\nIf the return value is less than <framesToRead> it means the end of the file has been reached.\n*/\nDRWAV_API drwav_uint64 drwav_read_pcm_frames_s32(drwav* pWav, drwav_uint64 framesToRead, drwav_int32* pBufferOut);\nDRWAV_API drwav_uint64 drwav_read_pcm_frames_s32le(drwav* pWav, drwav_uint64 framesToRead, drwav_int32* pBufferOut);\nDRWAV_API drwav_uint64 drwav_read_pcm_frames_s32be(drwav* pWav, drwav_uint64 framesToRead, drwav_int32* pBufferOut);\n\n/* Low-level function for converting unsigned 8-bit PCM samples to signed 32-bit PCM samples. */\nDRWAV_API void drwav_u8_to_s32(drwav_int32* pOut, const drwav_uint8* pIn, size_t sampleCount);\n\n/* Low-level function for converting signed 16-bit PCM samples to signed 32-bit PCM samples. */\nDRWAV_API void drwav_s16_to_s32(drwav_int32* pOut, const drwav_int16* pIn, size_t sampleCount);\n\n/* Low-level function for converting signed 24-bit PCM samples to signed 32-bit PCM samples. */\nDRWAV_API void drwav_s24_to_s32(drwav_int32* pOut, const drwav_uint8* pIn, size_t sampleCount);\n\n/* Low-level function for converting IEEE 32-bit floating point samples to signed 32-bit PCM samples. */\nDRWAV_API void drwav_f32_to_s32(drwav_int32* pOut, const float* pIn, size_t sampleCount);\n\n/* Low-level function for converting IEEE 64-bit floating point samples to signed 32-bit PCM samples. */\nDRWAV_API void drwav_f64_to_s32(drwav_int32* pOut, const double* pIn, size_t sampleCount);\n\n/* Low-level function for converting A-law samples to signed 32-bit PCM samples. */\nDRWAV_API void drwav_alaw_to_s32(drwav_int32* pOut, const drwav_uint8* pIn, size_t sampleCount);\n\n/* Low-level function for converting u-law samples to signed 32-bit PCM samples. */\nDRWAV_API void drwav_mulaw_to_s32(drwav_int32* pOut, const drwav_uint8* pIn, size_t sampleCount);\n\n#endif  /* DR_WAV_NO_CONVERSION_API */\n\n\n/* High-Level Convenience Helpers */\n\n#ifndef DR_WAV_NO_STDIO\n/*\nHelper for initializing a wave file for reading using stdio.\n\nThis holds the internal FILE object until drwav_uninit() is called. Keep this in mind if you're caching drwav\nobjects because the operating system may restrict the number of file handles an application can have open at\nany given time.\n*/\nDRWAV_API drwav_bool32 drwav_init_file(drwav* pWav, const char* filename, const drwav_allocation_callbacks* pAllocationCallbacks);\nDRWAV_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);\nDRWAV_API drwav_bool32 drwav_init_file_w(drwav* pWav, const wchar_t* filename, const drwav_allocation_callbacks* pAllocationCallbacks);\nDRWAV_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);\n\n/*\nHelper for initializing a wave file for writing using stdio.\n\nThis holds the internal FILE object until drwav_uninit() is called. Keep this in mind if you're caching drwav\nobjects because the operating system may restrict the number of file handles an application can have open at\nany given time.\n*/\nDRWAV_API drwav_bool32 drwav_init_file_write(drwav* pWav, const char* filename, const drwav_data_format* pFormat, const drwav_allocation_callbacks* pAllocationCallbacks);\nDRWAV_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);\nDRWAV_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);\nDRWAV_API drwav_bool32 drwav_init_file_write_w(drwav* pWav, const wchar_t* filename, const drwav_data_format* pFormat, const drwav_allocation_callbacks* pAllocationCallbacks);\nDRWAV_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);\nDRWAV_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);\n#endif  /* DR_WAV_NO_STDIO */\n\n/*\nHelper for initializing a loader from a pre-allocated memory buffer.\n\nThis does not create a copy of the data. It is up to the application to ensure the buffer remains valid for\nthe lifetime of the drwav object.\n\nThe buffer should contain the contents of the entire wave file, not just the sample data.\n*/\nDRWAV_API drwav_bool32 drwav_init_memory(drwav* pWav, const void* data, size_t dataSize, const drwav_allocation_callbacks* pAllocationCallbacks);\nDRWAV_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);\n\n/*\nHelper for initializing a writer which outputs data to a memory buffer.\n\ndr_wav will manage the memory allocations, however it is up to the caller to free the data with drwav_free().\n\nThe buffer will remain allocated even after drwav_uninit() is called. The buffer should not be considered valid\nuntil after drwav_uninit() has been called.\n*/\nDRWAV_API drwav_bool32 drwav_init_memory_write(drwav* pWav, void** ppData, size_t* pDataSize, const drwav_data_format* pFormat, const drwav_allocation_callbacks* pAllocationCallbacks);\nDRWAV_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);\nDRWAV_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);\n\n\n#ifndef DR_WAV_NO_CONVERSION_API\n/*\nOpens and reads an entire wav file in a single operation.\n\nThe return value is a heap-allocated buffer containing the audio data. Use drwav_free() to free the buffer.\n*/\nDRWAV_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);\nDRWAV_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);\nDRWAV_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);\n#ifndef DR_WAV_NO_STDIO\n/*\nOpens and decodes an entire wav file in a single operation.\n\nThe return value is a heap-allocated buffer containing the audio data. Use drwav_free() to free the buffer.\n*/\nDRWAV_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);\nDRWAV_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);\nDRWAV_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);\nDRWAV_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);\nDRWAV_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);\nDRWAV_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);\n#endif\n/*\nOpens and decodes an entire wav file from a block of memory in a single operation.\n\nThe return value is a heap-allocated buffer containing the audio data. Use drwav_free() to free the buffer.\n*/\nDRWAV_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);\nDRWAV_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);\nDRWAV_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);\n#endif\n\n/* Frees data that was allocated internally by dr_wav. */\nDRWAV_API void drwav_free(void* p, const drwav_allocation_callbacks* pAllocationCallbacks);\n\n/* Converts bytes from a wav stream to a sized type of native endian. */\nDRWAV_API drwav_uint16 drwav_bytes_to_u16(const drwav_uint8* data);\nDRWAV_API drwav_int16 drwav_bytes_to_s16(const drwav_uint8* data);\nDRWAV_API drwav_uint32 drwav_bytes_to_u32(const drwav_uint8* data);\nDRWAV_API drwav_int32 drwav_bytes_to_s32(const drwav_uint8* data);\nDRWAV_API drwav_uint64 drwav_bytes_to_u64(const drwav_uint8* data);\nDRWAV_API drwav_int64 drwav_bytes_to_s64(const drwav_uint8* data);\n\n/* Compares a GUID for the purpose of checking the type of a Wave64 chunk. */\nDRWAV_API drwav_bool32 drwav_guid_equal(const drwav_uint8 a[16], const drwav_uint8 b[16]);\n\n/* Compares a four-character-code for the purpose of checking the type of a RIFF chunk. */\nDRWAV_API drwav_bool32 drwav_fourcc_equal(const drwav_uint8* a, const char* b);\n\n#ifdef __cplusplus\n}\n#endif\n#endif  /* dr_wav_h */\n\n\n/************************************************************************************************************************************************************\n ************************************************************************************************************************************************************\n\n IMPLEMENTATION\n\n ************************************************************************************************************************************************************\n ************************************************************************************************************************************************************/\n#if defined(DR_WAV_IMPLEMENTATION) || defined(DRWAV_IMPLEMENTATION)\n#ifndef dr_wav_c\n#define dr_wav_c\n\n#include <stdlib.h>\n#include <string.h> /* For memcpy(), memset() */\n#include <limits.h> /* For INT_MAX */\n\n#ifndef DR_WAV_NO_STDIO\n#include <stdio.h>\n#include <wchar.h>\n#endif\n\n/* Standard library stuff. */\n#ifndef DRWAV_ASSERT\n#include <assert.h>\n#define DRWAV_ASSERT(expression)           assert(expression)\n#endif\n#ifndef DRWAV_MALLOC\n#define DRWAV_MALLOC(sz)                   malloc((sz))\n#endif\n#ifndef DRWAV_REALLOC\n#define DRWAV_REALLOC(p, sz)               realloc((p), (sz))\n#endif\n#ifndef DRWAV_FREE\n#define DRWAV_FREE(p)                      free((p))\n#endif\n#ifndef DRWAV_COPY_MEMORY\n#define DRWAV_COPY_MEMORY(dst, src, sz)    memcpy((dst), (src), (sz))\n#endif\n#ifndef DRWAV_ZERO_MEMORY\n#define DRWAV_ZERO_MEMORY(p, sz)           memset((p), 0, (sz))\n#endif\n#ifndef DRWAV_ZERO_OBJECT\n#define DRWAV_ZERO_OBJECT(p)               DRWAV_ZERO_MEMORY((p), sizeof(*p))\n#endif\n\n#define drwav_countof(x)                   (sizeof(x) / sizeof(x[0]))\n#define drwav_align(x, a)                  ((((x) + (a) - 1) / (a)) * (a))\n#define drwav_min(a, b)                    (((a) < (b)) ? (a) : (b))\n#define drwav_max(a, b)                    (((a) > (b)) ? (a) : (b))\n#define drwav_clamp(x, lo, hi)             (drwav_max((lo), drwav_min((hi), (x))))\n\n#define DRWAV_MAX_SIMD_VECTOR_SIZE         64  /* 64 for AVX-512 in the future. */\n\n/* CPU architecture. */\n#if defined(__x86_64__) || defined(_M_X64)\n    #define DRWAV_X64\n#elif defined(__i386) || defined(_M_IX86)\n    #define DRWAV_X86\n#elif defined(__arm__) || defined(_M_ARM)\n    #define DRWAV_ARM\n#endif\n\n#ifdef _MSC_VER\n    #define DRWAV_INLINE __forceinline\n#elif defined(__GNUC__)\n    /*\n    I've had a bug report where GCC is emitting warnings about functions possibly not being inlineable. This warning happens when\n    the __attribute__((always_inline)) attribute is defined without an \"inline\" statement. I think therefore there must be some\n    case where \"__inline__\" is not always defined, thus the compiler emitting these warnings. When using -std=c89 or -ansi on the\n    command line, we cannot use the \"inline\" keyword and instead need to use \"__inline__\". In an attempt to work around this issue\n    I am using \"__inline__\" only when we're compiling in strict ANSI mode.\n    */\n    #if defined(__STRICT_ANSI__)\n        #define DRWAV_INLINE __inline__ __attribute__((always_inline))\n    #else\n        #define DRWAV_INLINE inline __attribute__((always_inline))\n    #endif\n#elif defined(__WATCOMC__)\n    #define DRWAV_INLINE __inline\n#else\n    #define DRWAV_INLINE\n#endif\n\n#if defined(SIZE_MAX)\n    #define DRWAV_SIZE_MAX  SIZE_MAX\n#else\n    #if defined(_WIN64) || defined(_LP64) || defined(__LP64__)\n        #define DRWAV_SIZE_MAX  ((drwav_uint64)0xFFFFFFFFFFFFFFFF)\n    #else\n        #define DRWAV_SIZE_MAX  0xFFFFFFFF\n    #endif\n#endif\n\n#if defined(_MSC_VER) && _MSC_VER >= 1400\n    #define DRWAV_HAS_BYTESWAP16_INTRINSIC\n    #define DRWAV_HAS_BYTESWAP32_INTRINSIC\n    #define DRWAV_HAS_BYTESWAP64_INTRINSIC\n#elif defined(__clang__)\n    #if defined(__has_builtin)\n        #if __has_builtin(__builtin_bswap16)\n            #define DRWAV_HAS_BYTESWAP16_INTRINSIC\n        #endif\n        #if __has_builtin(__builtin_bswap32)\n            #define DRWAV_HAS_BYTESWAP32_INTRINSIC\n        #endif\n        #if __has_builtin(__builtin_bswap64)\n            #define DRWAV_HAS_BYTESWAP64_INTRINSIC\n        #endif\n    #endif\n#elif defined(__GNUC__)\n    #if ((__GNUC__ > 4) || (__GNUC__ == 4 && __GNUC_MINOR__ >= 3))\n        #define DRWAV_HAS_BYTESWAP32_INTRINSIC\n        #define DRWAV_HAS_BYTESWAP64_INTRINSIC\n    #endif\n    #if ((__GNUC__ > 4) || (__GNUC__ == 4 && __GNUC_MINOR__ >= 8))\n        #define DRWAV_HAS_BYTESWAP16_INTRINSIC\n    #endif\n#endif\n\nDRWAV_API void drwav_version(drwav_uint32* pMajor, drwav_uint32* pMinor, drwav_uint32* pRevision)\n{\n    if (pMajor) {\n        *pMajor = DRWAV_VERSION_MAJOR;\n    }\n\n    if (pMinor) {\n        *pMinor = DRWAV_VERSION_MINOR;\n    }\n\n    if (pRevision) {\n        *pRevision = DRWAV_VERSION_REVISION;\n    }\n}\n\nDRWAV_API const char* drwav_version_string(void)\n{\n    return DRWAV_VERSION_STRING;\n}\n\n/*\nThese limits are used for basic validation when initializing the decoder. If you exceed these limits, first of all: what on Earth are\nyou doing?! (Let me know, I'd be curious!) Second, you can adjust these by #define-ing them before the dr_wav implementation.\n*/\n#ifndef DRWAV_MAX_SAMPLE_RATE\n#define DRWAV_MAX_SAMPLE_RATE       384000\n#endif\n#ifndef DRWAV_MAX_CHANNELS\n#define DRWAV_MAX_CHANNELS          256\n#endif\n#ifndef DRWAV_MAX_BITS_PER_SAMPLE\n#define DRWAV_MAX_BITS_PER_SAMPLE   64\n#endif\n\nstatic 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 */\nstatic 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 */\n/*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 */\nstatic 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 */\nstatic 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 */\nstatic 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 */\nstatic 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 */\n\nstatic DRWAV_INLINE drwav_bool32 drwav__guid_equal(const drwav_uint8 a[16], const drwav_uint8 b[16])\n{\n    int i;\n    for (i = 0; i < 16; i += 1) {\n        if (a[i] != b[i]) {\n            return DRWAV_FALSE;\n        }\n    }\n\n    return DRWAV_TRUE;\n}\n\nstatic DRWAV_INLINE drwav_bool32 drwav__fourcc_equal(const drwav_uint8* a, const char* b)\n{\n    return\n        a[0] == b[0] &&\n        a[1] == b[1] &&\n        a[2] == b[2] &&\n        a[3] == b[3];\n}\n\n\n\nstatic DRWAV_INLINE int drwav__is_little_endian(void)\n{\n#if defined(DRWAV_X86) || defined(DRWAV_X64)\n    return DRWAV_TRUE;\n#elif defined(__BYTE_ORDER) && defined(__LITTLE_ENDIAN) && __BYTE_ORDER == __LITTLE_ENDIAN\n    return DRWAV_TRUE;\n#else\n    int n = 1;\n    return (*(char*)&n) == 1;\n#endif\n}\n\nstatic DRWAV_INLINE drwav_uint16 drwav__bytes_to_u16(const drwav_uint8* data)\n{\n    return (data[0] << 0) | (data[1] << 8);\n}\n\nstatic DRWAV_INLINE drwav_int16 drwav__bytes_to_s16(const drwav_uint8* data)\n{\n    return (short)drwav__bytes_to_u16(data);\n}\n\nstatic DRWAV_INLINE drwav_uint32 drwav__bytes_to_u32(const drwav_uint8* data)\n{\n    return (data[0] << 0) | (data[1] << 8) | (data[2] << 16) | (data[3] << 24);\n}\n\nstatic DRWAV_INLINE drwav_int32 drwav__bytes_to_s32(const drwav_uint8* data)\n{\n    return (drwav_int32)drwav__bytes_to_u32(data);\n}\n\nstatic DRWAV_INLINE drwav_uint64 drwav__bytes_to_u64(const drwav_uint8* data)\n{\n    return\n        ((drwav_uint64)data[0] <<  0) | ((drwav_uint64)data[1] <<  8) | ((drwav_uint64)data[2] << 16) | ((drwav_uint64)data[3] << 24) |\n        ((drwav_uint64)data[4] << 32) | ((drwav_uint64)data[5] << 40) | ((drwav_uint64)data[6] << 48) | ((drwav_uint64)data[7] << 56);\n}\n\nstatic DRWAV_INLINE drwav_int64 drwav__bytes_to_s64(const drwav_uint8* data)\n{\n    return (drwav_int64)drwav__bytes_to_u64(data);\n}\n\nstatic DRWAV_INLINE void drwav__bytes_to_guid(const drwav_uint8* data, drwav_uint8* guid)\n{\n    int i;\n    for (i = 0; i < 16; ++i) {\n        guid[i] = data[i];\n    }\n}\n\n\nstatic DRWAV_INLINE drwav_uint16 drwav__bswap16(drwav_uint16 n)\n{\n#ifdef DRWAV_HAS_BYTESWAP16_INTRINSIC\n    #if defined(_MSC_VER)\n        return _byteswap_ushort(n);\n    #elif defined(__GNUC__) || defined(__clang__)\n        return __builtin_bswap16(n);\n    #else\n        #error \"This compiler does not support the byte swap intrinsic.\"\n    #endif\n#else\n    return ((n & 0xFF00) >> 8) |\n           ((n & 0x00FF) << 8);\n#endif\n}\n\nstatic DRWAV_INLINE drwav_uint32 drwav__bswap32(drwav_uint32 n)\n{\n#ifdef DRWAV_HAS_BYTESWAP32_INTRINSIC\n    #if defined(_MSC_VER)\n        return _byteswap_ulong(n);\n    #elif defined(__GNUC__) || defined(__clang__)\n        #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. */\n            /* Inline assembly optimized implementation for ARM. In my testing, GCC does not generate optimized code with __builtin_bswap32(). */\n            drwav_uint32 r;\n            __asm__ __volatile__ (\n            #if defined(DRWAV_64BIT)\n                \"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! */\n            #else\n                \"rev %[out], %[in]\" : [out]\"=r\"(r) : [in]\"r\"(n)\n            #endif\n            );\n            return r;\n        #else\n            return __builtin_bswap32(n);\n        #endif\n    #else\n        #error \"This compiler does not support the byte swap intrinsic.\"\n    #endif\n#else\n    return ((n & 0xFF000000) >> 24) |\n           ((n & 0x00FF0000) >>  8) |\n           ((n & 0x0000FF00) <<  8) |\n           ((n & 0x000000FF) << 24);\n#endif\n}\n\nstatic DRWAV_INLINE drwav_uint64 drwav__bswap64(drwav_uint64 n)\n{\n#ifdef DRWAV_HAS_BYTESWAP64_INTRINSIC\n    #if defined(_MSC_VER)\n        return _byteswap_uint64(n);\n    #elif defined(__GNUC__) || defined(__clang__)\n        return __builtin_bswap64(n);\n    #else\n        #error \"This compiler does not support the byte swap intrinsic.\"\n    #endif\n#else\n    /* Weird \"<< 32\" bitshift is required for C89 because it doesn't support 64-bit constants. Should be optimized out by a good compiler. */\n    return ((n & ((drwav_uint64)0xFF000000 << 32)) >> 56) |\n           ((n & ((drwav_uint64)0x00FF0000 << 32)) >> 40) |\n           ((n & ((drwav_uint64)0x0000FF00 << 32)) >> 24) |\n           ((n & ((drwav_uint64)0x000000FF << 32)) >>  8) |\n           ((n & ((drwav_uint64)0xFF000000      )) <<  8) |\n           ((n & ((drwav_uint64)0x00FF0000      )) << 24) |\n           ((n & ((drwav_uint64)0x0000FF00      )) << 40) |\n           ((n & ((drwav_uint64)0x000000FF      )) << 56);\n#endif\n}\n\n\nstatic DRWAV_INLINE drwav_int16 drwav__bswap_s16(drwav_int16 n)\n{\n    return (drwav_int16)drwav__bswap16((drwav_uint16)n);\n}\n\nstatic DRWAV_INLINE void drwav__bswap_samples_s16(drwav_int16* pSamples, drwav_uint64 sampleCount)\n{\n    drwav_uint64 iSample;\n    for (iSample = 0; iSample < sampleCount; iSample += 1) {\n        pSamples[iSample] = drwav__bswap_s16(pSamples[iSample]);\n    }\n}\n\n\nstatic DRWAV_INLINE void drwav__bswap_s24(drwav_uint8* p)\n{\n    drwav_uint8 t;\n    t = p[0];\n    p[0] = p[2];\n    p[2] = t;\n}\n\nstatic DRWAV_INLINE void drwav__bswap_samples_s24(drwav_uint8* pSamples, drwav_uint64 sampleCount)\n{\n    drwav_uint64 iSample;\n    for (iSample = 0; iSample < sampleCount; iSample += 1) {\n        drwav_uint8* pSample = pSamples + (iSample*3);\n        drwav__bswap_s24(pSample);\n    }\n}\n\n\nstatic DRWAV_INLINE drwav_int32 drwav__bswap_s32(drwav_int32 n)\n{\n    return (drwav_int32)drwav__bswap32((drwav_uint32)n);\n}\n\nstatic DRWAV_INLINE void drwav__bswap_samples_s32(drwav_int32* pSamples, drwav_uint64 sampleCount)\n{\n    drwav_uint64 iSample;\n    for (iSample = 0; iSample < sampleCount; iSample += 1) {\n        pSamples[iSample] = drwav__bswap_s32(pSamples[iSample]);\n    }\n}\n\n\nstatic DRWAV_INLINE float drwav__bswap_f32(float n)\n{\n    union {\n        drwav_uint32 i;\n        float f;\n    } x;\n    x.f = n;\n    x.i = drwav__bswap32(x.i);\n\n    return x.f;\n}\n\nstatic DRWAV_INLINE void drwav__bswap_samples_f32(float* pSamples, drwav_uint64 sampleCount)\n{\n    drwav_uint64 iSample;\n    for (iSample = 0; iSample < sampleCount; iSample += 1) {\n        pSamples[iSample] = drwav__bswap_f32(pSamples[iSample]);\n    }\n}\n\n\nstatic DRWAV_INLINE double drwav__bswap_f64(double n)\n{\n    union {\n        drwav_uint64 i;\n        double f;\n    } x;\n    x.f = n;\n    x.i = drwav__bswap64(x.i);\n\n    return x.f;\n}\n\nstatic DRWAV_INLINE void drwav__bswap_samples_f64(double* pSamples, drwav_uint64 sampleCount)\n{\n    drwav_uint64 iSample;\n    for (iSample = 0; iSample < sampleCount; iSample += 1) {\n        pSamples[iSample] = drwav__bswap_f64(pSamples[iSample]);\n    }\n}\n\n\nstatic DRWAV_INLINE void drwav__bswap_samples_pcm(void* pSamples, drwav_uint64 sampleCount, drwav_uint32 bytesPerSample)\n{\n    /* Assumes integer PCM. Floating point PCM is done in drwav__bswap_samples_ieee(). */\n    switch (bytesPerSample)\n    {\n        case 2: /* s16, s12 (loosely packed) */\n        {\n            drwav__bswap_samples_s16((drwav_int16*)pSamples, sampleCount);\n        } break;\n        case 3: /* s24 */\n        {\n            drwav__bswap_samples_s24((drwav_uint8*)pSamples, sampleCount);\n        } break;\n        case 4: /* s32 */\n        {\n            drwav__bswap_samples_s32((drwav_int32*)pSamples, sampleCount);\n        } break;\n        default:\n        {\n            /* Unsupported format. */\n            DRWAV_ASSERT(DRWAV_FALSE);\n        } break;\n    }\n}\n\nstatic DRWAV_INLINE void drwav__bswap_samples_ieee(void* pSamples, drwav_uint64 sampleCount, drwav_uint32 bytesPerSample)\n{\n    switch (bytesPerSample)\n    {\n    #if 0   /* Contributions welcome for f16 support. */\n        case 2: /* f16 */\n        {\n            drwav__bswap_samples_f16((drwav_float16*)pSamples, sampleCount);\n        } break;\n    #endif\n        case 4: /* f32 */\n        {\n            drwav__bswap_samples_f32((float*)pSamples, sampleCount);\n        } break;\n        case 8: /* f64 */\n        {\n            drwav__bswap_samples_f64((double*)pSamples, sampleCount);\n        } break;\n        default:\n        {\n            /* Unsupported format. */\n            DRWAV_ASSERT(DRWAV_FALSE);\n        } break;\n    }\n}\n\nstatic DRWAV_INLINE void drwav__bswap_samples(void* pSamples, drwav_uint64 sampleCount, drwav_uint32 bytesPerSample, drwav_uint16 format)\n{\n    switch (format)\n    {\n        case DR_WAVE_FORMAT_PCM:\n        {\n            drwav__bswap_samples_pcm(pSamples, sampleCount, bytesPerSample);\n        } break;\n\n        case DR_WAVE_FORMAT_IEEE_FLOAT:\n        {\n            drwav__bswap_samples_ieee(pSamples, sampleCount, bytesPerSample);\n        } break;\n\n        case DR_WAVE_FORMAT_ALAW:\n        case DR_WAVE_FORMAT_MULAW:\n        {\n            drwav__bswap_samples_s16((drwav_int16*)pSamples, sampleCount);\n        } break;\n\n        case DR_WAVE_FORMAT_ADPCM:\n        case DR_WAVE_FORMAT_DVI_ADPCM:\n        default:\n        {\n            /* Unsupported format. */\n            DRWAV_ASSERT(DRWAV_FALSE);\n        } break;\n    }\n}\n\n\nstatic void* drwav__malloc_default(size_t sz, void* pUserData)\n{\n    (void)pUserData;\n    return DRWAV_MALLOC(sz);\n}\n\nstatic void* drwav__realloc_default(void* p, size_t sz, void* pUserData)\n{\n    (void)pUserData;\n    return DRWAV_REALLOC(p, sz);\n}\n\nstatic void drwav__free_default(void* p, void* pUserData)\n{\n    (void)pUserData;\n    DRWAV_FREE(p);\n}\n\n\nstatic void* drwav__malloc_from_callbacks(size_t sz, const drwav_allocation_callbacks* pAllocationCallbacks)\n{\n    if (pAllocationCallbacks == NULL) {\n        return NULL;\n    }\n\n    if (pAllocationCallbacks->onMalloc != NULL) {\n        return pAllocationCallbacks->onMalloc(sz, pAllocationCallbacks->pUserData);\n    }\n\n    /* Try using realloc(). */\n    if (pAllocationCallbacks->onRealloc != NULL) {\n        return pAllocationCallbacks->onRealloc(NULL, sz, pAllocationCallbacks->pUserData);\n    }\n\n    return NULL;\n}\n\nstatic void* drwav__realloc_from_callbacks(void* p, size_t szNew, size_t szOld, const drwav_allocation_callbacks* pAllocationCallbacks)\n{\n    if (pAllocationCallbacks == NULL) {\n        return NULL;\n    }\n\n    if (pAllocationCallbacks->onRealloc != NULL) {\n        return pAllocationCallbacks->onRealloc(p, szNew, pAllocationCallbacks->pUserData);\n    }\n\n    /* Try emulating realloc() in terms of malloc()/free(). */\n    if (pAllocationCallbacks->onMalloc != NULL && pAllocationCallbacks->onFree != NULL) {\n        void* p2;\n\n        p2 = pAllocationCallbacks->onMalloc(szNew, pAllocationCallbacks->pUserData);\n        if (p2 == NULL) {\n            return NULL;\n        }\n\n        if (p != NULL) {\n            DRWAV_COPY_MEMORY(p2, p, szOld);\n            pAllocationCallbacks->onFree(p, pAllocationCallbacks->pUserData);\n        }\n\n        return p2;\n    }\n\n    return NULL;\n}\n\nstatic void drwav__free_from_callbacks(void* p, const drwav_allocation_callbacks* pAllocationCallbacks)\n{\n    if (p == NULL || pAllocationCallbacks == NULL) {\n        return;\n    }\n\n    if (pAllocationCallbacks->onFree != NULL) {\n        pAllocationCallbacks->onFree(p, pAllocationCallbacks->pUserData);\n    }\n}\n\n\nstatic drwav_allocation_callbacks drwav_copy_allocation_callbacks_or_defaults(const drwav_allocation_callbacks* pAllocationCallbacks)\n{\n    if (pAllocationCallbacks != NULL) {\n        /* Copy. */\n        return *pAllocationCallbacks;\n    } else {\n        /* Defaults. */\n        drwav_allocation_callbacks allocationCallbacks;\n        allocationCallbacks.pUserData = NULL;\n        allocationCallbacks.onMalloc  = drwav__malloc_default;\n        allocationCallbacks.onRealloc = drwav__realloc_default;\n        allocationCallbacks.onFree    = drwav__free_default;\n        return allocationCallbacks;\n    }\n}\n\n\nstatic DRWAV_INLINE drwav_bool32 drwav__is_compressed_format_tag(drwav_uint16 formatTag)\n{\n    return\n        formatTag == DR_WAVE_FORMAT_ADPCM ||\n        formatTag == DR_WAVE_FORMAT_DVI_ADPCM;\n}\n\nstatic unsigned int drwav__chunk_padding_size_riff(drwav_uint64 chunkSize)\n{\n    return (unsigned int)(chunkSize % 2);\n}\n\nstatic unsigned int drwav__chunk_padding_size_w64(drwav_uint64 chunkSize)\n{\n    return (unsigned int)(chunkSize % 8);\n}\n\nstatic drwav_uint64 drwav_read_pcm_frames_s16__msadpcm(drwav* pWav, drwav_uint64 samplesToRead, drwav_int16* pBufferOut);\nstatic drwav_uint64 drwav_read_pcm_frames_s16__ima(drwav* pWav, drwav_uint64 samplesToRead, drwav_int16* pBufferOut);\nstatic drwav_bool32 drwav_init_write__internal(drwav* pWav, const drwav_data_format* pFormat, drwav_uint64 totalSampleCount);\n\nstatic drwav_result drwav__read_chunk_header(drwav_read_proc onRead, void* pUserData, drwav_container container, drwav_uint64* pRunningBytesReadOut, drwav_chunk_header* pHeaderOut)\n{\n    if (container == drwav_container_riff || container == drwav_container_rf64) {\n        drwav_uint8 sizeInBytes[4];\n\n        if (onRead(pUserData, pHeaderOut->id.fourcc, 4) != 4) {\n            return DRWAV_AT_END;\n        }\n\n        if (onRead(pUserData, sizeInBytes, 4) != 4) {\n            return DRWAV_INVALID_FILE;\n        }\n\n        pHeaderOut->sizeInBytes = drwav__bytes_to_u32(sizeInBytes);\n        pHeaderOut->paddingSize = drwav__chunk_padding_size_riff(pHeaderOut->sizeInBytes);\n        *pRunningBytesReadOut += 8;\n    } else {\n        drwav_uint8 sizeInBytes[8];\n\n        if (onRead(pUserData, pHeaderOut->id.guid, 16) != 16) {\n            return DRWAV_AT_END;\n        }\n\n        if (onRead(pUserData, sizeInBytes, 8) != 8) {\n            return DRWAV_INVALID_FILE;\n        }\n\n        pHeaderOut->sizeInBytes = drwav__bytes_to_u64(sizeInBytes) - 24;    /* <-- Subtract 24 because w64 includes the size of the header. */\n        pHeaderOut->paddingSize = drwav__chunk_padding_size_w64(pHeaderOut->sizeInBytes);\n        *pRunningBytesReadOut += 24;\n    }\n\n    return DRWAV_SUCCESS;\n}\n\nstatic drwav_bool32 drwav__seek_forward(drwav_seek_proc onSeek, drwav_uint64 offset, void* pUserData)\n{\n    drwav_uint64 bytesRemainingToSeek = offset;\n    while (bytesRemainingToSeek > 0) {\n        if (bytesRemainingToSeek > 0x7FFFFFFF) {\n            if (!onSeek(pUserData, 0x7FFFFFFF, drwav_seek_origin_current)) {\n                return DRWAV_FALSE;\n            }\n            bytesRemainingToSeek -= 0x7FFFFFFF;\n        } else {\n            if (!onSeek(pUserData, (int)bytesRemainingToSeek, drwav_seek_origin_current)) {\n                return DRWAV_FALSE;\n            }\n            bytesRemainingToSeek = 0;\n        }\n    }\n\n    return DRWAV_TRUE;\n}\n\nstatic drwav_bool32 drwav__seek_from_start(drwav_seek_proc onSeek, drwav_uint64 offset, void* pUserData)\n{\n    if (offset <= 0x7FFFFFFF) {\n        return onSeek(pUserData, (int)offset, drwav_seek_origin_start);\n    }\n\n    /* Larger than 32-bit seek. */\n    if (!onSeek(pUserData, 0x7FFFFFFF, drwav_seek_origin_start)) {\n        return DRWAV_FALSE;\n    }\n    offset -= 0x7FFFFFFF;\n\n    for (;;) {\n        if (offset <= 0x7FFFFFFF) {\n            return onSeek(pUserData, (int)offset, drwav_seek_origin_current);\n        }\n\n        if (!onSeek(pUserData, 0x7FFFFFFF, drwav_seek_origin_current)) {\n            return DRWAV_FALSE;\n        }\n        offset -= 0x7FFFFFFF;\n    }\n\n    /* Should never get here. */\n    /*return DRWAV_TRUE; */\n}\n\n\nstatic drwav_bool32 drwav__read_fmt(drwav_read_proc onRead, drwav_seek_proc onSeek, void* pUserData, drwav_container container, drwav_uint64* pRunningBytesReadOut, drwav_fmt* fmtOut)\n{\n    drwav_chunk_header header;\n    drwav_uint8 fmt[16];\n\n    if (drwav__read_chunk_header(onRead, pUserData, container, pRunningBytesReadOut, &header) != DRWAV_SUCCESS) {\n        return DRWAV_FALSE;\n    }\n\n\n    /* Skip non-fmt chunks. */\n    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))) {\n        if (!drwav__seek_forward(onSeek, header.sizeInBytes + header.paddingSize, pUserData)) {\n            return DRWAV_FALSE;\n        }\n        *pRunningBytesReadOut += header.sizeInBytes + header.paddingSize;\n\n        /* Try the next header. */\n        if (drwav__read_chunk_header(onRead, pUserData, container, pRunningBytesReadOut, &header) != DRWAV_SUCCESS) {\n            return DRWAV_FALSE;\n        }\n    }\n\n\n    /* Validation. */\n    if (container == drwav_container_riff || container == drwav_container_rf64) {\n        if (!drwav__fourcc_equal(header.id.fourcc, \"fmt \")) {\n            return DRWAV_FALSE;\n        }\n    } else {\n        if (!drwav__guid_equal(header.id.guid, drwavGUID_W64_FMT)) {\n            return DRWAV_FALSE;\n        }\n    }\n\n\n    if (onRead(pUserData, fmt, sizeof(fmt)) != sizeof(fmt)) {\n        return DRWAV_FALSE;\n    }\n    *pRunningBytesReadOut += sizeof(fmt);\n\n    fmtOut->formatTag      = drwav__bytes_to_u16(fmt + 0);\n    fmtOut->channels       = drwav__bytes_to_u16(fmt + 2);\n    fmtOut->sampleRate     = drwav__bytes_to_u32(fmt + 4);\n    fmtOut->avgBytesPerSec = drwav__bytes_to_u32(fmt + 8);\n    fmtOut->blockAlign     = drwav__bytes_to_u16(fmt + 12);\n    fmtOut->bitsPerSample  = drwav__bytes_to_u16(fmt + 14);\n\n    fmtOut->extendedSize       = 0;\n    fmtOut->validBitsPerSample = 0;\n    fmtOut->channelMask        = 0;\n    memset(fmtOut->subFormat, 0, sizeof(fmtOut->subFormat));\n\n    if (header.sizeInBytes > 16) {\n        drwav_uint8 fmt_cbSize[2];\n        int bytesReadSoFar = 0;\n\n        if (onRead(pUserData, fmt_cbSize, sizeof(fmt_cbSize)) != sizeof(fmt_cbSize)) {\n            return DRWAV_FALSE;    /* Expecting more data. */\n        }\n        *pRunningBytesReadOut += sizeof(fmt_cbSize);\n\n        bytesReadSoFar = 18;\n\n        fmtOut->extendedSize = drwav__bytes_to_u16(fmt_cbSize);\n        if (fmtOut->extendedSize > 0) {\n            /* Simple validation. */\n            if (fmtOut->formatTag == DR_WAVE_FORMAT_EXTENSIBLE) {\n                if (fmtOut->extendedSize != 22) {\n                    return DRWAV_FALSE;\n                }\n            }\n\n            if (fmtOut->formatTag == DR_WAVE_FORMAT_EXTENSIBLE) {\n                drwav_uint8 fmtext[22];\n                if (onRead(pUserData, fmtext, fmtOut->extendedSize) != fmtOut->extendedSize) {\n                    return DRWAV_FALSE;    /* Expecting more data. */\n                }\n\n                fmtOut->validBitsPerSample = drwav__bytes_to_u16(fmtext + 0);\n                fmtOut->channelMask        = drwav__bytes_to_u32(fmtext + 2);\n                drwav__bytes_to_guid(fmtext + 6, fmtOut->subFormat);\n            } else {\n                if (!onSeek(pUserData, fmtOut->extendedSize, drwav_seek_origin_current)) {\n                    return DRWAV_FALSE;\n                }\n            }\n            *pRunningBytesReadOut += fmtOut->extendedSize;\n\n            bytesReadSoFar += fmtOut->extendedSize;\n        }\n\n        /* Seek past any leftover bytes. For w64 the leftover will be defined based on the chunk size. */\n        if (!onSeek(pUserData, (int)(header.sizeInBytes - bytesReadSoFar), drwav_seek_origin_current)) {\n            return DRWAV_FALSE;\n        }\n        *pRunningBytesReadOut += (header.sizeInBytes - bytesReadSoFar);\n    }\n\n    if (header.paddingSize > 0) {\n        if (!onSeek(pUserData, header.paddingSize, drwav_seek_origin_current)) {\n            return DRWAV_FALSE;\n        }\n        *pRunningBytesReadOut += header.paddingSize;\n    }\n\n    return DRWAV_TRUE;\n}\n\n\nstatic size_t drwav__on_read(drwav_read_proc onRead, void* pUserData, void* pBufferOut, size_t bytesToRead, drwav_uint64* pCursor)\n{\n    size_t bytesRead;\n\n    DRWAV_ASSERT(onRead != NULL);\n    DRWAV_ASSERT(pCursor != NULL);\n\n    bytesRead = onRead(pUserData, pBufferOut, bytesToRead);\n    *pCursor += bytesRead;\n    return bytesRead;\n}\n\n#if 0\nstatic drwav_bool32 drwav__on_seek(drwav_seek_proc onSeek, void* pUserData, int offset, drwav_seek_origin origin, drwav_uint64* pCursor)\n{\n    DRWAV_ASSERT(onSeek != NULL);\n    DRWAV_ASSERT(pCursor != NULL);\n\n    if (!onSeek(pUserData, offset, origin)) {\n        return DRWAV_FALSE;\n    }\n\n    if (origin == drwav_seek_origin_start) {\n        *pCursor = offset;\n    } else {\n        *pCursor += offset;\n    }\n\n    return DRWAV_TRUE;\n}\n#endif\n\n\n\nstatic drwav_uint32 drwav_get_bytes_per_pcm_frame(drwav* pWav)\n{\n    /*\n    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\n    is that if the bits per sample is a multiple of 8, use floor(bitsPerSample*channels/8), otherwise fall back to the block align.\n    */\n    if ((pWav->bitsPerSample & 0x7) == 0) {\n        /* Bits per sample is a multiple of 8. */\n        return (pWav->bitsPerSample * pWav->fmt.channels) >> 3;\n    } else {\n        return pWav->fmt.blockAlign;\n    }\n}\n\nDRWAV_API drwav_uint16 drwav_fmt_get_format(const drwav_fmt* pFMT)\n{\n    if (pFMT == NULL) {\n        return 0;\n    }\n\n    if (pFMT->formatTag != DR_WAVE_FORMAT_EXTENSIBLE) {\n        return pFMT->formatTag;\n    } else {\n        return drwav__bytes_to_u16(pFMT->subFormat);    /* Only the first two bytes are required. */\n    }\n}\n\nstatic drwav_bool32 drwav_preinit(drwav* pWav, drwav_read_proc onRead, drwav_seek_proc onSeek, void* pReadSeekUserData, const drwav_allocation_callbacks* pAllocationCallbacks)\n{\n    if (pWav == NULL || onRead == NULL || onSeek == NULL) {\n        return DRWAV_FALSE;\n    }\n\n    DRWAV_ZERO_MEMORY(pWav, sizeof(*pWav));\n    pWav->onRead    = onRead;\n    pWav->onSeek    = onSeek;\n    pWav->pUserData = pReadSeekUserData;\n    pWav->allocationCallbacks = drwav_copy_allocation_callbacks_or_defaults(pAllocationCallbacks);\n\n    if (pWav->allocationCallbacks.onFree == NULL || (pWav->allocationCallbacks.onMalloc == NULL && pWav->allocationCallbacks.onRealloc == NULL)) {\n        return DRWAV_FALSE;    /* Invalid allocation callbacks. */\n    }\n\n    return DRWAV_TRUE;\n}\n\nstatic drwav_bool32 drwav_init__internal(drwav* pWav, drwav_chunk_proc onChunk, void* pChunkUserData, drwav_uint32 flags)\n{\n    /* This function assumes drwav_preinit() has been called beforehand. */\n\n    drwav_uint64 cursor;    /* <-- Keeps track of the byte position so we can seek to specific locations. */\n    drwav_bool32 sequential;\n    drwav_uint8 riff[4];\n    drwav_fmt fmt;\n    unsigned short translatedFormatTag;\n    drwav_bool32 foundDataChunk;\n    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. */\n    drwav_uint64 sampleCountFromFactChunk = 0;  /* Same as dataChunkSize - make sure this is the only place this is initialized to 0. */\n    drwav_uint64 chunkSize;\n\n    cursor = 0;\n    sequential = (flags & DRWAV_SEQUENTIAL) != 0;\n\n    /* The first 4 bytes should be the RIFF identifier. */\n    if (drwav__on_read(pWav->onRead, pWav->pUserData, riff, sizeof(riff), &cursor) != sizeof(riff)) {\n        return DRWAV_FALSE;\n    }\n\n    /*\n    The first 4 bytes can be used to identify the container. For RIFF files it will start with \"RIFF\" and for\n    w64 it will start with \"riff\".\n    */\n    if (drwav__fourcc_equal(riff, \"RIFF\")) {\n        pWav->container = drwav_container_riff;\n    } else if (drwav__fourcc_equal(riff, \"riff\")) {\n        int i;\n        drwav_uint8 riff2[12];\n\n        pWav->container = drwav_container_w64;\n\n        /* Check the rest of the GUID for validity. */\n        if (drwav__on_read(pWav->onRead, pWav->pUserData, riff2, sizeof(riff2), &cursor) != sizeof(riff2)) {\n            return DRWAV_FALSE;\n        }\n\n        for (i = 0; i < 12; ++i) {\n            if (riff2[i] != drwavGUID_W64_RIFF[i+4]) {\n                return DRWAV_FALSE;\n            }\n        }\n    } else if (drwav__fourcc_equal(riff, \"RF64\")) {\n        pWav->container = drwav_container_rf64;\n    } else {\n        return DRWAV_FALSE;   /* Unknown or unsupported container. */\n    }\n\n\n    if (pWav->container == drwav_container_riff || pWav->container == drwav_container_rf64) {\n        drwav_uint8 chunkSizeBytes[4];\n        drwav_uint8 wave[4];\n\n        /* RIFF/WAVE */\n        if (drwav__on_read(pWav->onRead, pWav->pUserData, chunkSizeBytes, sizeof(chunkSizeBytes), &cursor) != sizeof(chunkSizeBytes)) {\n            return DRWAV_FALSE;\n        }\n\n        if (pWav->container == drwav_container_riff) {\n            if (drwav__bytes_to_u32(chunkSizeBytes) < 36) {\n                return DRWAV_FALSE;    /* Chunk size should always be at least 36 bytes. */\n            }\n        } else {\n            if (drwav__bytes_to_u32(chunkSizeBytes) != 0xFFFFFFFF) {\n                return DRWAV_FALSE;    /* Chunk size should always be set to -1/0xFFFFFFFF for RF64. The actual size is retrieved later. */\n            }\n        }\n\n        if (drwav__on_read(pWav->onRead, pWav->pUserData, wave, sizeof(wave), &cursor) != sizeof(wave)) {\n            return DRWAV_FALSE;\n        }\n\n        if (!drwav__fourcc_equal(wave, \"WAVE\")) {\n            return DRWAV_FALSE;    /* Expecting \"WAVE\". */\n        }\n    } else {\n        drwav_uint8 chunkSizeBytes[8];\n        drwav_uint8 wave[16];\n\n        /* W64 */\n        if (drwav__on_read(pWav->onRead, pWav->pUserData, chunkSizeBytes, sizeof(chunkSizeBytes), &cursor) != sizeof(chunkSizeBytes)) {\n            return DRWAV_FALSE;\n        }\n\n        if (drwav__bytes_to_u64(chunkSizeBytes) < 80) {\n            return DRWAV_FALSE;\n        }\n\n        if (drwav__on_read(pWav->onRead, pWav->pUserData, wave, sizeof(wave), &cursor) != sizeof(wave)) {\n            return DRWAV_FALSE;\n        }\n\n        if (!drwav__guid_equal(wave, drwavGUID_W64_WAVE)) {\n            return DRWAV_FALSE;\n        }\n    }\n\n\n    /* For RF64, the \"ds64\" chunk must come next, before the \"fmt \" chunk. */\n    if (pWav->container == drwav_container_rf64) {\n        drwav_uint8 sizeBytes[8];\n        drwav_uint64 bytesRemainingInChunk;\n        drwav_chunk_header header;\n        drwav_result result = drwav__read_chunk_header(pWav->onRead, pWav->pUserData, pWav->container, &cursor, &header);\n        if (result != DRWAV_SUCCESS) {\n            return DRWAV_FALSE;\n        }\n\n        if (!drwav__fourcc_equal(header.id.fourcc, \"ds64\")) {\n            return DRWAV_FALSE; /* Expecting \"ds64\". */\n        }\n\n        bytesRemainingInChunk = header.sizeInBytes + header.paddingSize;\n\n        /* We don't care about the size of the RIFF chunk - skip it. */\n        if (!drwav__seek_forward(pWav->onSeek, 8, pWav->pUserData)) {\n            return DRWAV_FALSE;\n        }\n        bytesRemainingInChunk -= 8;\n        cursor += 8;\n\n\n        /* Next 8 bytes is the size of the \"data\" chunk. */\n        if (drwav__on_read(pWav->onRead, pWav->pUserData, sizeBytes, sizeof(sizeBytes), &cursor) != sizeof(sizeBytes)) {\n            return DRWAV_FALSE;\n        }\n        bytesRemainingInChunk -= 8;\n        dataChunkSize = drwav__bytes_to_u64(sizeBytes);\n\n\n        /* Next 8 bytes is the same count which we would usually derived from the FACT chunk if it was available. */\n        if (drwav__on_read(pWav->onRead, pWav->pUserData, sizeBytes, sizeof(sizeBytes), &cursor) != sizeof(sizeBytes)) {\n            return DRWAV_FALSE;\n        }\n        bytesRemainingInChunk -= 8;\n        sampleCountFromFactChunk = drwav__bytes_to_u64(sizeBytes);\n\n\n        /* Skip over everything else. */\n        if (!drwav__seek_forward(pWav->onSeek, bytesRemainingInChunk, pWav->pUserData)) {\n            return DRWAV_FALSE;\n        }\n        cursor += bytesRemainingInChunk;\n    }\n\n\n    /* The next bytes should be the \"fmt \" chunk. */\n    if (!drwav__read_fmt(pWav->onRead, pWav->onSeek, pWav->pUserData, pWav->container, &cursor, &fmt)) {\n        return DRWAV_FALSE;    /* Failed to read the \"fmt \" chunk. */\n    }\n\n    /* Basic validation. */\n    if ((fmt.sampleRate    == 0 || fmt.sampleRate    > DRWAV_MAX_SAMPLE_RATE)     ||\n        (fmt.channels      == 0 || fmt.channels      > DRWAV_MAX_CHANNELS)        ||\n        (fmt.bitsPerSample == 0 || fmt.bitsPerSample > DRWAV_MAX_BITS_PER_SAMPLE) ||\n        fmt.blockAlign == 0) {\n        return DRWAV_FALSE; /* Probably an invalid WAV file. */\n    }\n\n\n    /* Translate the internal format. */\n    translatedFormatTag = fmt.formatTag;\n    if (translatedFormatTag == DR_WAVE_FORMAT_EXTENSIBLE) {\n        translatedFormatTag = drwav__bytes_to_u16(fmt.subFormat + 0);\n    }\n\n\n    /*\n    We need to enumerate over each chunk for two reasons:\n      1) The \"data\" chunk may not be the next one\n      2) We may want to report each chunk back to the client\n    \n    In order to correctly report each chunk back to the client we will need to keep looping until the end of the file.\n    */\n    foundDataChunk = DRWAV_FALSE;\n\n    /* The next chunk we care about is the \"data\" chunk. This is not necessarily the next chunk so we'll need to loop. */\n    for (;;)\n    {\n        drwav_chunk_header header;\n        drwav_result result = drwav__read_chunk_header(pWav->onRead, pWav->pUserData, pWav->container, &cursor, &header);\n        if (result != DRWAV_SUCCESS) {\n            if (!foundDataChunk) {\n                return DRWAV_FALSE;\n            } else {\n                break;  /* Probably at the end of the file. Get out of the loop. */\n            }\n        }\n\n        /* Tell the client about this chunk. */\n        if (!sequential && onChunk != NULL) {\n            drwav_uint64 callbackBytesRead = onChunk(pChunkUserData, pWav->onRead, pWav->onSeek, pWav->pUserData, &header, pWav->container, &fmt);\n\n            /*\n            dr_wav may need to read the contents of the chunk, so we now need to seek back to the position before\n            we called the callback.\n            */\n            if (callbackBytesRead > 0) {\n                if (!drwav__seek_from_start(pWav->onSeek, cursor, pWav->pUserData)) {\n                    return DRWAV_FALSE;\n                }\n            }\n        }\n        \n\n        if (!foundDataChunk) {\n            pWav->dataChunkDataPos = cursor;\n        }\n\n        chunkSize = header.sizeInBytes;\n        if (pWav->container == drwav_container_riff || pWav->container == drwav_container_rf64) {\n            if (drwav__fourcc_equal(header.id.fourcc, \"data\")) {\n                foundDataChunk = DRWAV_TRUE;\n                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. */\n                    dataChunkSize = chunkSize;\n                }\n            }\n        } else {\n            if (drwav__guid_equal(header.id.guid, drwavGUID_W64_DATA)) {\n                foundDataChunk = DRWAV_TRUE;\n                dataChunkSize = chunkSize;\n            }\n        }\n\n        /*\n        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\n        this is that we would otherwise require a backwards seek which sequential mode forbids.\n        */\n        if (foundDataChunk && sequential) {\n            break;\n        }\n\n        /* Optional. Get the total sample count from the FACT chunk. This is useful for compressed formats. */\n        if (pWav->container == drwav_container_riff) {\n            if (drwav__fourcc_equal(header.id.fourcc, \"fact\")) {\n                drwav_uint32 sampleCount;\n                if (drwav__on_read(pWav->onRead, pWav->pUserData, &sampleCount, 4, &cursor) != 4) {\n                    return DRWAV_FALSE;\n                }\n                chunkSize -= 4;\n\n                if (!foundDataChunk) {\n                    pWav->dataChunkDataPos = cursor;\n                }\n\n                /*\n                The sample count in the \"fact\" chunk is either unreliable, or I'm not understanding it properly. For now I am only enabling this\n                for Microsoft ADPCM formats.\n                */\n                if (pWav->translatedFormatTag == DR_WAVE_FORMAT_ADPCM) {\n                    sampleCountFromFactChunk = sampleCount;\n                } else {\n                    sampleCountFromFactChunk = 0;\n                }\n            }\n        } else if (pWav->container == drwav_container_w64) {\n            if (drwav__guid_equal(header.id.guid, drwavGUID_W64_FACT)) {\n                if (drwav__on_read(pWav->onRead, pWav->pUserData, &sampleCountFromFactChunk, 8, &cursor) != 8) {\n                    return DRWAV_FALSE;\n                }\n                chunkSize -= 8;\n\n                if (!foundDataChunk) {\n                    pWav->dataChunkDataPos = cursor;\n                }\n            }\n        } else if (pWav->container == drwav_container_rf64) {\n            /* We retrieved the sample count from the ds64 chunk earlier so no need to do that here. */\n        }\n\n        /* \"smpl\" chunk. */\n        if (pWav->container == drwav_container_riff || pWav->container == drwav_container_rf64) {\n            if (drwav__fourcc_equal(header.id.fourcc, \"smpl\")) {\n                drwav_uint8 smplHeaderData[36];    /* 36 = size of the smpl header section, not including the loop data. */\n                if (chunkSize >= sizeof(smplHeaderData)) {\n                    drwav_uint64 bytesJustRead = drwav__on_read(pWav->onRead, pWav->pUserData, smplHeaderData, sizeof(smplHeaderData), &cursor);\n                    chunkSize -= bytesJustRead;\n\n                    if (bytesJustRead == sizeof(smplHeaderData)) {\n                        drwav_uint32 iLoop;\n\n                        pWav->smpl.manufacturer      = drwav__bytes_to_u32(smplHeaderData+0);\n                        pWav->smpl.product           = drwav__bytes_to_u32(smplHeaderData+4);\n                        pWav->smpl.samplePeriod      = drwav__bytes_to_u32(smplHeaderData+8);\n                        pWav->smpl.midiUnityNotes    = drwav__bytes_to_u32(smplHeaderData+12);\n                        pWav->smpl.midiPitchFraction = drwav__bytes_to_u32(smplHeaderData+16);\n                        pWav->smpl.smpteFormat       = drwav__bytes_to_u32(smplHeaderData+20);\n                        pWav->smpl.smpteOffset       = drwav__bytes_to_u32(smplHeaderData+24);\n                        pWav->smpl.numSampleLoops    = drwav__bytes_to_u32(smplHeaderData+28);\n                        pWav->smpl.samplerData       = drwav__bytes_to_u32(smplHeaderData+32);\n\n                        for (iLoop = 0; iLoop < pWav->smpl.numSampleLoops && iLoop < drwav_countof(pWav->smpl.loops); ++iLoop) {\n                            drwav_uint8 smplLoopData[24];  /* 24 = size of a loop section in the smpl chunk. */\n                            bytesJustRead = drwav__on_read(pWav->onRead, pWav->pUserData, smplLoopData, sizeof(smplLoopData), &cursor);\n                            chunkSize -= bytesJustRead;\n\n                            if (bytesJustRead == sizeof(smplLoopData)) {\n                                pWav->smpl.loops[iLoop].cuePointId = drwav__bytes_to_u32(smplLoopData+0);\n                                pWav->smpl.loops[iLoop].type       = drwav__bytes_to_u32(smplLoopData+4);\n                                pWav->smpl.loops[iLoop].start      = drwav__bytes_to_u32(smplLoopData+8);\n                                pWav->smpl.loops[iLoop].end        = drwav__bytes_to_u32(smplLoopData+12);\n                                pWav->smpl.loops[iLoop].fraction   = drwav__bytes_to_u32(smplLoopData+16);\n                                pWav->smpl.loops[iLoop].playCount  = drwav__bytes_to_u32(smplLoopData+20);\n                            } else {\n                                break;  /* Break from the smpl loop for loop. */\n                            }\n                        }\n                    }\n                } else {\n                    /* Looks like invalid data. Ignore the chunk. */\n                }\n            }\n        } else {\n            if (drwav__guid_equal(header.id.guid, drwavGUID_W64_SMPL)) {\n                /*\n                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\n                is welcome to add support for this.\n                */\n            }\n        }\n\n        /* Make sure we seek past the padding. */\n        chunkSize += header.paddingSize;\n        if (!drwav__seek_forward(pWav->onSeek, chunkSize, pWav->pUserData)) {\n            break;\n        }\n        cursor += chunkSize;\n\n        if (!foundDataChunk) {\n            pWav->dataChunkDataPos = cursor;\n        }\n    }\n\n    /* If we haven't found a data chunk, return an error. */\n    if (!foundDataChunk) {\n        return DRWAV_FALSE;\n    }\n\n    /* 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. */\n    if (!sequential) {\n        if (!drwav__seek_from_start(pWav->onSeek, pWav->dataChunkDataPos, pWav->pUserData)) {\n            return DRWAV_FALSE;\n        }\n        cursor = pWav->dataChunkDataPos;\n    }\n    \n\n    /* At this point we should be sitting on the first byte of the raw audio data. */\n\n    pWav->fmt                 = fmt;\n    pWav->sampleRate          = fmt.sampleRate;\n    pWav->channels            = fmt.channels;\n    pWav->bitsPerSample       = fmt.bitsPerSample;\n    pWav->bytesRemaining      = dataChunkSize;\n    pWav->translatedFormatTag = translatedFormatTag;\n    pWav->dataChunkDataSize   = dataChunkSize;\n\n    if (sampleCountFromFactChunk != 0) {\n        pWav->totalPCMFrameCount = sampleCountFromFactChunk;\n    } else {\n        pWav->totalPCMFrameCount = dataChunkSize / drwav_get_bytes_per_pcm_frame(pWav);\n\n        if (pWav->translatedFormatTag == DR_WAVE_FORMAT_ADPCM) {\n            drwav_uint64 totalBlockHeaderSizeInBytes;\n            drwav_uint64 blockCount = dataChunkSize / fmt.blockAlign;\n\n            /* Make sure any trailing partial block is accounted for. */\n            if ((blockCount * fmt.blockAlign) < dataChunkSize) {\n                blockCount += 1;\n            }\n\n            /* 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. */\n            totalBlockHeaderSizeInBytes = blockCount * (6*fmt.channels);\n            pWav->totalPCMFrameCount = ((dataChunkSize - totalBlockHeaderSizeInBytes) * 2) / fmt.channels;\n        }\n        if (pWav->translatedFormatTag == DR_WAVE_FORMAT_DVI_ADPCM) {\n            drwav_uint64 totalBlockHeaderSizeInBytes;\n            drwav_uint64 blockCount = dataChunkSize / fmt.blockAlign;\n\n            /* Make sure any trailing partial block is accounted for. */\n            if ((blockCount * fmt.blockAlign) < dataChunkSize) {\n                blockCount += 1;\n            }\n\n            /* 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. */\n            totalBlockHeaderSizeInBytes = blockCount * (4*fmt.channels);\n            pWav->totalPCMFrameCount = ((dataChunkSize - totalBlockHeaderSizeInBytes) * 2) / fmt.channels;\n\n            /* The header includes a decoded sample for each channel which acts as the initial predictor sample. */\n            pWav->totalPCMFrameCount += blockCount;\n        }\n    }\n\n    /* Some formats only support a certain number of channels. */\n    if (pWav->translatedFormatTag == DR_WAVE_FORMAT_ADPCM || pWav->translatedFormatTag == DR_WAVE_FORMAT_DVI_ADPCM) {\n        if (pWav->channels > 2) {\n            return DRWAV_FALSE;\n        }\n    }\n\n#ifdef DR_WAV_LIBSNDFILE_COMPAT\n    /*\n    I use libsndfile as a benchmark for testing, however in the version I'm using (from the Windows installer on the libsndfile website),\n    it appears the total sample count libsndfile uses for MS-ADPCM is incorrect. It would seem they are computing the total sample count\n    from the number of blocks, however this results in the inclusion of extra silent samples at the end of the last block. The correct\n    way to know the total sample count is to inspect the \"fact\" chunk, which should always be present for compressed formats, and should\n    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\n    correctness tests against libsndfile, and is disabled by default.\n    */\n    if (pWav->translatedFormatTag == DR_WAVE_FORMAT_ADPCM) {\n        drwav_uint64 blockCount = dataChunkSize / fmt.blockAlign;\n        pWav->totalPCMFrameCount = (((blockCount * (fmt.blockAlign - (6*pWav->channels))) * 2)) / fmt.channels;  /* x2 because two samples per byte. */\n    }\n    if (pWav->translatedFormatTag == DR_WAVE_FORMAT_DVI_ADPCM) {\n        drwav_uint64 blockCount = dataChunkSize / fmt.blockAlign;\n        pWav->totalPCMFrameCount = (((blockCount * (fmt.blockAlign - (4*pWav->channels))) * 2) + (blockCount * pWav->channels)) / fmt.channels;\n    }\n#endif\n\n    return DRWAV_TRUE;\n}\n\nDRWAV_API drwav_bool32 drwav_init(drwav* pWav, drwav_read_proc onRead, drwav_seek_proc onSeek, void* pUserData, const drwav_allocation_callbacks* pAllocationCallbacks)\n{\n    return drwav_init_ex(pWav, onRead, onSeek, NULL, pUserData, NULL, 0, pAllocationCallbacks);\n}\n\nDRWAV_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)\n{\n    if (!drwav_preinit(pWav, onRead, onSeek, pReadSeekUserData, pAllocationCallbacks)) {\n        return DRWAV_FALSE;\n    }\n\n    return drwav_init__internal(pWav, onChunk, pChunkUserData, flags);\n}\n\n\nstatic drwav_uint32 drwav__riff_chunk_size_riff(drwav_uint64 dataChunkSize)\n{\n    drwav_uint64 chunkSize = 4 + 24 + dataChunkSize + drwav__chunk_padding_size_riff(dataChunkSize); /* 4 = \"WAVE\". 24 = \"fmt \" chunk. */\n    if (chunkSize > 0xFFFFFFFFUL) {\n        chunkSize = 0xFFFFFFFFUL;\n    }\n\n    return (drwav_uint32)chunkSize; /* Safe cast due to the clamp above. */\n}\n\nstatic drwav_uint32 drwav__data_chunk_size_riff(drwav_uint64 dataChunkSize)\n{\n    if (dataChunkSize <= 0xFFFFFFFFUL) {\n        return (drwav_uint32)dataChunkSize;\n    } else {\n        return 0xFFFFFFFFUL;\n    }\n}\n\nstatic drwav_uint64 drwav__riff_chunk_size_w64(drwav_uint64 dataChunkSize)\n{\n    drwav_uint64 dataSubchunkPaddingSize = drwav__chunk_padding_size_w64(dataChunkSize);\n\n    return 80 + 24 + dataChunkSize + dataSubchunkPaddingSize;   /* +24 because W64 includes the size of the GUID and size fields. */\n}\n\nstatic drwav_uint64 drwav__data_chunk_size_w64(drwav_uint64 dataChunkSize)\n{\n    return 24 + dataChunkSize;        /* +24 because W64 includes the size of the GUID and size fields. */\n}\n\nstatic drwav_uint64 drwav__riff_chunk_size_rf64(drwav_uint64 dataChunkSize)\n{\n    drwav_uint64 chunkSize = 4 + 36 + 24 + dataChunkSize + drwav__chunk_padding_size_riff(dataChunkSize); /* 4 = \"WAVE\". 36 = \"ds64\" chunk. 24 = \"fmt \" chunk. */\n    if (chunkSize > 0xFFFFFFFFUL) {\n        chunkSize = 0xFFFFFFFFUL;\n    }\n\n    return chunkSize;\n}\n\nstatic drwav_uint64 drwav__data_chunk_size_rf64(drwav_uint64 dataChunkSize)\n{\n    return dataChunkSize;\n}\n\n\nstatic size_t drwav__write(drwav* pWav, const void* pData, size_t dataSize)\n{\n    DRWAV_ASSERT(pWav          != NULL);\n    DRWAV_ASSERT(pWav->onWrite != NULL);\n\n    /* Generic write. Assumes no byte reordering required. */\n    return pWav->onWrite(pWav->pUserData, pData, dataSize);\n}\n\nstatic size_t drwav__write_u16ne_to_le(drwav* pWav, drwav_uint16 value)\n{\n    DRWAV_ASSERT(pWav          != NULL);\n    DRWAV_ASSERT(pWav->onWrite != NULL);\n\n    if (!drwav__is_little_endian()) {\n        value = drwav__bswap16(value);\n    }\n\n    return drwav__write(pWav, &value, 2);\n}\n\nstatic size_t drwav__write_u32ne_to_le(drwav* pWav, drwav_uint32 value)\n{\n    DRWAV_ASSERT(pWav          != NULL);\n    DRWAV_ASSERT(pWav->onWrite != NULL);\n\n    if (!drwav__is_little_endian()) {\n        value = drwav__bswap32(value);\n    }\n\n    return drwav__write(pWav, &value, 4);\n}\n\nstatic size_t drwav__write_u64ne_to_le(drwav* pWav, drwav_uint64 value)\n{\n    DRWAV_ASSERT(pWav          != NULL);\n    DRWAV_ASSERT(pWav->onWrite != NULL);\n\n    if (!drwav__is_little_endian()) {\n        value = drwav__bswap64(value);\n    }\n\n    return drwav__write(pWav, &value, 8);\n}\n\n\nstatic 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)\n{\n    if (pWav == NULL || onWrite == NULL) {\n        return DRWAV_FALSE;\n    }\n\n    if (!isSequential && onSeek == NULL) {\n        return DRWAV_FALSE; /* <-- onSeek is required when in non-sequential mode. */\n    }\n\n    /* Not currently supporting compressed formats. Will need to add support for the \"fact\" chunk before we enable this. */\n    if (pFormat->format == DR_WAVE_FORMAT_EXTENSIBLE) {\n        return DRWAV_FALSE;\n    }\n    if (pFormat->format == DR_WAVE_FORMAT_ADPCM || pFormat->format == DR_WAVE_FORMAT_DVI_ADPCM) {\n        return DRWAV_FALSE;\n    }\n\n    DRWAV_ZERO_MEMORY(pWav, sizeof(*pWav));\n    pWav->onWrite   = onWrite;\n    pWav->onSeek    = onSeek;\n    pWav->pUserData = pUserData;\n    pWav->allocationCallbacks = drwav_copy_allocation_callbacks_or_defaults(pAllocationCallbacks);\n\n    if (pWav->allocationCallbacks.onFree == NULL || (pWav->allocationCallbacks.onMalloc == NULL && pWav->allocationCallbacks.onRealloc == NULL)) {\n        return DRWAV_FALSE;    /* Invalid allocation callbacks. */\n    }\n\n    pWav->fmt.formatTag = (drwav_uint16)pFormat->format;\n    pWav->fmt.channels = (drwav_uint16)pFormat->channels;\n    pWav->fmt.sampleRate = pFormat->sampleRate;\n    pWav->fmt.avgBytesPerSec = (drwav_uint32)((pFormat->bitsPerSample * pFormat->sampleRate * pFormat->channels) / 8);\n    pWav->fmt.blockAlign = (drwav_uint16)((pFormat->channels * pFormat->bitsPerSample) / 8);\n    pWav->fmt.bitsPerSample = (drwav_uint16)pFormat->bitsPerSample;\n    pWav->fmt.extendedSize = 0;\n    pWav->isSequentialWrite = isSequential;\n\n    return DRWAV_TRUE;\n}\n\nstatic drwav_bool32 drwav_init_write__internal(drwav* pWav, const drwav_data_format* pFormat, drwav_uint64 totalSampleCount)\n{\n    /* The function assumes drwav_preinit_write() was called beforehand. */\n\n    size_t runningPos = 0;\n    drwav_uint64 initialDataChunkSize = 0;\n    drwav_uint64 chunkSizeFMT;\n\n    /*\n    The initial values for the \"RIFF\" and \"data\" chunks depends on whether or not we are initializing in sequential mode or not. In\n    sequential mode we set this to its final values straight away since they can be calculated from the total sample count. In non-\n    sequential mode we initialize it all to zero and fill it out in drwav_uninit() using a backwards seek.\n    */\n    if (pWav->isSequentialWrite) {\n        initialDataChunkSize = (totalSampleCount * pWav->fmt.bitsPerSample) / 8;\n\n        /*\n        The RIFF container has a limit on the number of samples. drwav is not allowing this. There's no practical limits for Wave64\n        so for the sake of simplicity I'm not doing any validation for that.\n        */\n        if (pFormat->container == drwav_container_riff) {\n            if (initialDataChunkSize > (0xFFFFFFFFUL - 36)) {\n                return DRWAV_FALSE; /* Not enough room to store every sample. */\n            }\n        }\n    }\n\n    pWav->dataChunkDataSizeTargetWrite = initialDataChunkSize;\n\n\n    /* \"RIFF\" chunk. */\n    if (pFormat->container == drwav_container_riff) {\n        drwav_uint32 chunkSizeRIFF = 28 + (drwav_uint32)initialDataChunkSize;   /* +28 = \"WAVE\" + [sizeof \"fmt \" chunk] */\n        runningPos += drwav__write(pWav, \"RIFF\", 4);\n        runningPos += drwav__write_u32ne_to_le(pWav, chunkSizeRIFF);\n        runningPos += drwav__write(pWav, \"WAVE\", 4);\n    } else if (pFormat->container == drwav_container_w64) {\n        drwav_uint64 chunkSizeRIFF = 80 + 24 + initialDataChunkSize;            /* +24 because W64 includes the size of the GUID and size fields. */\n        runningPos += drwav__write(pWav, drwavGUID_W64_RIFF, 16);\n        runningPos += drwav__write_u64ne_to_le(pWav, chunkSizeRIFF);\n        runningPos += drwav__write(pWav, drwavGUID_W64_WAVE, 16);\n    } else if (pFormat->container == drwav_container_rf64) {\n        runningPos += drwav__write(pWav, \"RF64\", 4);\n        runningPos += drwav__write_u32ne_to_le(pWav, 0xFFFFFFFF);               /* Always 0xFFFFFFFF for RF64. Set to a proper value in the \"ds64\" chunk. */\n        runningPos += drwav__write(pWav, \"WAVE\", 4);\n    }\n\n    \n    /* \"ds64\" chunk (RF64 only). */\n    if (pFormat->container == drwav_container_rf64) {\n        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. */\n        drwav_uint64 initialRiffChunkSize = 8 + initialds64ChunkSize + initialDataChunkSize;    /* +8 for the ds64 header. */\n\n        runningPos += drwav__write(pWav, \"ds64\", 4);\n        runningPos += drwav__write_u32ne_to_le(pWav, initialds64ChunkSize);     /* Size of ds64. */\n        runningPos += drwav__write_u64ne_to_le(pWav, initialRiffChunkSize);     /* Size of RIFF. Set to true value at the end. */\n        runningPos += drwav__write_u64ne_to_le(pWav, initialDataChunkSize);     /* Size of DATA. Set to true value at the end. */\n        runningPos += drwav__write_u64ne_to_le(pWav, totalSampleCount);         /* Sample count. */\n        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\". */\n    }\n\n\n    /* \"fmt \" chunk. */\n    if (pFormat->container == drwav_container_riff || pFormat->container == drwav_container_rf64) {\n        chunkSizeFMT = 16;\n        runningPos += drwav__write(pWav, \"fmt \", 4);\n        runningPos += drwav__write_u32ne_to_le(pWav, (drwav_uint32)chunkSizeFMT);\n    } else if (pFormat->container == drwav_container_w64) {\n        chunkSizeFMT = 40;\n        runningPos += drwav__write(pWav, drwavGUID_W64_FMT, 16);\n        runningPos += drwav__write_u64ne_to_le(pWav, chunkSizeFMT);\n    }\n\n    runningPos += drwav__write_u16ne_to_le(pWav, pWav->fmt.formatTag);\n    runningPos += drwav__write_u16ne_to_le(pWav, pWav->fmt.channels);\n    runningPos += drwav__write_u32ne_to_le(pWav, pWav->fmt.sampleRate);\n    runningPos += drwav__write_u32ne_to_le(pWav, pWav->fmt.avgBytesPerSec);\n    runningPos += drwav__write_u16ne_to_le(pWav, pWav->fmt.blockAlign);\n    runningPos += drwav__write_u16ne_to_le(pWav, pWav->fmt.bitsPerSample);\n\n    pWav->dataChunkDataPos = runningPos;\n\n    /* \"data\" chunk. */\n    if (pFormat->container == drwav_container_riff) {\n        drwav_uint32 chunkSizeDATA = (drwav_uint32)initialDataChunkSize;\n        runningPos += drwav__write(pWav, \"data\", 4);\n        runningPos += drwav__write_u32ne_to_le(pWav, chunkSizeDATA);\n    } else if (pFormat->container == drwav_container_w64) {\n        drwav_uint64 chunkSizeDATA = 24 + initialDataChunkSize;     /* +24 because W64 includes the size of the GUID and size fields. */\n        runningPos += drwav__write(pWav, drwavGUID_W64_DATA, 16);\n        runningPos += drwav__write_u64ne_to_le(pWav, chunkSizeDATA);\n    } else if (pFormat->container == drwav_container_rf64) {\n        runningPos += drwav__write(pWav, \"data\", 4);\n        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. */\n    }\n\n    /*\n    The runningPos variable is incremented in the section above but is left unused which is causing some static analysis tools to detect it\n    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\n    keep track of the running position for whatever reason. The line below should silence the static analysis tools.\n    */\n    (void)runningPos;\n\n    /* Set some properties for the client's convenience. */\n    pWav->container = pFormat->container;\n    pWav->channels = (drwav_uint16)pFormat->channels;\n    pWav->sampleRate = pFormat->sampleRate;\n    pWav->bitsPerSample = (drwav_uint16)pFormat->bitsPerSample;\n    pWav->translatedFormatTag = (drwav_uint16)pFormat->format;\n\n    return DRWAV_TRUE;\n}\n\n\nDRWAV_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)\n{\n    if (!drwav_preinit_write(pWav, pFormat, DRWAV_FALSE, onWrite, onSeek, pUserData, pAllocationCallbacks)) {\n        return DRWAV_FALSE;\n    }\n\n    return drwav_init_write__internal(pWav, pFormat, 0);               /* DRWAV_FALSE = Not Sequential */\n}\n\nDRWAV_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)\n{\n    if (!drwav_preinit_write(pWav, pFormat, DRWAV_TRUE, onWrite, NULL, pUserData, pAllocationCallbacks)) {\n        return DRWAV_FALSE;\n    }\n\n    return drwav_init_write__internal(pWav, pFormat, totalSampleCount); /* DRWAV_TRUE = Sequential */\n}\n\nDRWAV_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)\n{\n    if (pFormat == NULL) {\n        return DRWAV_FALSE;\n    }\n\n    return drwav_init_write_sequential(pWav, pFormat, totalPCMFrameCount*pFormat->channels, onWrite, pUserData, pAllocationCallbacks);\n}\n\nDRWAV_API drwav_uint64 drwav_target_write_size_bytes(const drwav_data_format* pFormat, drwav_uint64 totalSampleCount)\n{\n    /* Casting totalSampleCount to drwav_int64 for VC6 compatibility. No issues in practice because nobody is going to exhaust the whole 63 bits. */\n    drwav_uint64 targetDataSizeBytes = (drwav_uint64)((drwav_int64)totalSampleCount * pFormat->channels * pFormat->bitsPerSample/8.0);\n    drwav_uint64 riffChunkSizeBytes;\n    drwav_uint64 fileSizeBytes = 0;\n\n    if (pFormat->container == drwav_container_riff) {\n        riffChunkSizeBytes = drwav__riff_chunk_size_riff(targetDataSizeBytes);\n        fileSizeBytes = (8 + riffChunkSizeBytes);   /* +8 because WAV doesn't include the size of the ChunkID and ChunkSize fields. */\n    } else if (pFormat->container == drwav_container_w64) {\n        riffChunkSizeBytes = drwav__riff_chunk_size_w64(targetDataSizeBytes);\n        fileSizeBytes = riffChunkSizeBytes;\n    } else if (pFormat->container == drwav_container_rf64) {\n        riffChunkSizeBytes = drwav__riff_chunk_size_rf64(targetDataSizeBytes);\n        fileSizeBytes = (8 + riffChunkSizeBytes);   /* +8 because WAV doesn't include the size of the ChunkID and ChunkSize fields. */\n    }\n\n    return fileSizeBytes;\n}\n\n\n#ifndef DR_WAV_NO_STDIO\n\n/* 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. */\n#include <errno.h>\nstatic drwav_result drwav_result_from_errno(int e)\n{\n    switch (e)\n    {\n        case 0: return DRWAV_SUCCESS;\n    #ifdef EPERM\n        case EPERM: return DRWAV_INVALID_OPERATION;\n    #endif\n    #ifdef ENOENT\n        case ENOENT: return DRWAV_DOES_NOT_EXIST;\n    #endif\n    #ifdef ESRCH\n        case ESRCH: return DRWAV_DOES_NOT_EXIST;\n    #endif\n    #ifdef EINTR\n        case EINTR: return DRWAV_INTERRUPT;\n    #endif\n    #ifdef EIO\n        case EIO: return DRWAV_IO_ERROR;\n    #endif\n    #ifdef ENXIO\n        case ENXIO: return DRWAV_DOES_NOT_EXIST;\n    #endif\n    #ifdef E2BIG\n        case E2BIG: return DRWAV_INVALID_ARGS;\n    #endif\n    #ifdef ENOEXEC\n        case ENOEXEC: return DRWAV_INVALID_FILE;\n    #endif\n    #ifdef EBADF\n        case EBADF: return DRWAV_INVALID_FILE;\n    #endif\n    #ifdef ECHILD\n        case ECHILD: return DRWAV_ERROR;\n    #endif\n    #ifdef EAGAIN\n        case EAGAIN: return DRWAV_UNAVAILABLE;\n    #endif\n    #ifdef ENOMEM\n        case ENOMEM: return DRWAV_OUT_OF_MEMORY;\n    #endif\n    #ifdef EACCES\n        case EACCES: return DRWAV_ACCESS_DENIED;\n    #endif\n    #ifdef EFAULT\n        case EFAULT: return DRWAV_BAD_ADDRESS;\n    #endif\n    #ifdef ENOTBLK\n        case ENOTBLK: return DRWAV_ERROR;\n    #endif\n    #ifdef EBUSY\n        case EBUSY: return DRWAV_BUSY;\n    #endif\n    #ifdef EEXIST\n        case EEXIST: return DRWAV_ALREADY_EXISTS;\n    #endif\n    #ifdef EXDEV\n        case EXDEV: return DRWAV_ERROR;\n    #endif\n    #ifdef ENODEV\n        case ENODEV: return DRWAV_DOES_NOT_EXIST;\n    #endif\n    #ifdef ENOTDIR\n        case ENOTDIR: return DRWAV_NOT_DIRECTORY;\n    #endif\n    #ifdef EISDIR\n        case EISDIR: return DRWAV_IS_DIRECTORY;\n    #endif\n    #ifdef EINVAL\n        case EINVAL: return DRWAV_INVALID_ARGS;\n    #endif\n    #ifdef ENFILE\n        case ENFILE: return DRWAV_TOO_MANY_OPEN_FILES;\n    #endif\n    #ifdef EMFILE\n        case EMFILE: return DRWAV_TOO_MANY_OPEN_FILES;\n    #endif\n    #ifdef ENOTTY\n        case ENOTTY: return DRWAV_INVALID_OPERATION;\n    #endif\n    #ifdef ETXTBSY\n        case ETXTBSY: return DRWAV_BUSY;\n    #endif\n    #ifdef EFBIG\n        case EFBIG: return DRWAV_TOO_BIG;\n    #endif\n    #ifdef ENOSPC\n        case ENOSPC: return DRWAV_NO_SPACE;\n    #endif\n    #ifdef ESPIPE\n        case ESPIPE: return DRWAV_BAD_SEEK;\n    #endif\n    #ifdef EROFS\n        case EROFS: return DRWAV_ACCESS_DENIED;\n    #endif\n    #ifdef EMLINK\n        case EMLINK: return DRWAV_TOO_MANY_LINKS;\n    #endif\n    #ifdef EPIPE\n        case EPIPE: return DRWAV_BAD_PIPE;\n    #endif\n    #ifdef EDOM\n        case EDOM: return DRWAV_OUT_OF_RANGE;\n    #endif\n    #ifdef ERANGE\n        case ERANGE: return DRWAV_OUT_OF_RANGE;\n    #endif\n    #ifdef EDEADLK\n        case EDEADLK: return DRWAV_DEADLOCK;\n    #endif\n    #ifdef ENAMETOOLONG\n        case ENAMETOOLONG: return DRWAV_PATH_TOO_LONG;\n    #endif\n    #ifdef ENOLCK\n        case ENOLCK: return DRWAV_ERROR;\n    #endif\n    #ifdef ENOSYS\n        case ENOSYS: return DRWAV_NOT_IMPLEMENTED;\n    #endif\n    #ifdef ENOTEMPTY\n        case ENOTEMPTY: return DRWAV_DIRECTORY_NOT_EMPTY;\n    #endif\n    #ifdef ELOOP\n        case ELOOP: return DRWAV_TOO_MANY_LINKS;\n    #endif\n    #ifdef ENOMSG\n        case ENOMSG: return DRWAV_NO_MESSAGE;\n    #endif\n    #ifdef EIDRM\n        case EIDRM: return DRWAV_ERROR;\n    #endif\n    #ifdef ECHRNG\n        case ECHRNG: return DRWAV_ERROR;\n    #endif\n    #ifdef EL2NSYNC\n        case EL2NSYNC: return DRWAV_ERROR;\n    #endif\n    #ifdef EL3HLT\n        case EL3HLT: return DRWAV_ERROR;\n    #endif\n    #ifdef EL3RST\n        case EL3RST: return DRWAV_ERROR;\n    #endif\n    #ifdef ELNRNG\n        case ELNRNG: return DRWAV_OUT_OF_RANGE;\n    #endif\n    #ifdef EUNATCH\n        case EUNATCH: return DRWAV_ERROR;\n    #endif\n    #ifdef ENOCSI\n        case ENOCSI: return DRWAV_ERROR;\n    #endif\n    #ifdef EL2HLT\n        case EL2HLT: return DRWAV_ERROR;\n    #endif\n    #ifdef EBADE\n        case EBADE: return DRWAV_ERROR;\n    #endif\n    #ifdef EBADR\n        case EBADR: return DRWAV_ERROR;\n    #endif\n    #ifdef EXFULL\n        case EXFULL: return DRWAV_ERROR;\n    #endif\n    #ifdef ENOANO\n        case ENOANO: return DRWAV_ERROR;\n    #endif\n    #ifdef EBADRQC\n        case EBADRQC: return DRWAV_ERROR;\n    #endif\n    #ifdef EBADSLT\n        case EBADSLT: return DRWAV_ERROR;\n    #endif\n    #ifdef EBFONT\n        case EBFONT: return DRWAV_INVALID_FILE;\n    #endif\n    #ifdef ENOSTR\n        case ENOSTR: return DRWAV_ERROR;\n    #endif\n    #ifdef ENODATA\n        case ENODATA: return DRWAV_NO_DATA_AVAILABLE;\n    #endif\n    #ifdef ETIME\n        case ETIME: return DRWAV_TIMEOUT;\n    #endif\n    #ifdef ENOSR\n        case ENOSR: return DRWAV_NO_DATA_AVAILABLE;\n    #endif\n    #ifdef ENONET\n        case ENONET: return DRWAV_NO_NETWORK;\n    #endif\n    #ifdef ENOPKG\n        case ENOPKG: return DRWAV_ERROR;\n    #endif\n    #ifdef EREMOTE\n        case EREMOTE: return DRWAV_ERROR;\n    #endif\n    #ifdef ENOLINK\n        case ENOLINK: return DRWAV_ERROR;\n    #endif\n    #ifdef EADV\n        case EADV: return DRWAV_ERROR;\n    #endif\n    #ifdef ESRMNT\n        case ESRMNT: return DRWAV_ERROR;\n    #endif\n    #ifdef ECOMM\n        case ECOMM: return DRWAV_ERROR;\n    #endif\n    #ifdef EPROTO\n        case EPROTO: return DRWAV_ERROR;\n    #endif\n    #ifdef EMULTIHOP\n        case EMULTIHOP: return DRWAV_ERROR;\n    #endif\n    #ifdef EDOTDOT\n        case EDOTDOT: return DRWAV_ERROR;\n    #endif\n    #ifdef EBADMSG\n        case EBADMSG: return DRWAV_BAD_MESSAGE;\n    #endif\n    #ifdef EOVERFLOW\n        case EOVERFLOW: return DRWAV_TOO_BIG;\n    #endif\n    #ifdef ENOTUNIQ\n        case ENOTUNIQ: return DRWAV_NOT_UNIQUE;\n    #endif\n    #ifdef EBADFD\n        case EBADFD: return DRWAV_ERROR;\n    #endif\n    #ifdef EREMCHG\n        case EREMCHG: return DRWAV_ERROR;\n    #endif\n    #ifdef ELIBACC\n        case ELIBACC: return DRWAV_ACCESS_DENIED;\n    #endif\n    #ifdef ELIBBAD\n        case ELIBBAD: return DRWAV_INVALID_FILE;\n    #endif\n    #ifdef ELIBSCN\n        case ELIBSCN: return DRWAV_INVALID_FILE;\n    #endif\n    #ifdef ELIBMAX\n        case ELIBMAX: return DRWAV_ERROR;\n    #endif\n    #ifdef ELIBEXEC\n        case ELIBEXEC: return DRWAV_ERROR;\n    #endif\n    #ifdef EILSEQ\n        case EILSEQ: return DRWAV_INVALID_DATA;\n    #endif\n    #ifdef ERESTART\n        case ERESTART: return DRWAV_ERROR;\n    #endif\n    #ifdef ESTRPIPE\n        case ESTRPIPE: return DRWAV_ERROR;\n    #endif\n    #ifdef EUSERS\n        case EUSERS: return DRWAV_ERROR;\n    #endif\n    #ifdef ENOTSOCK\n        case ENOTSOCK: return DRWAV_NOT_SOCKET;\n    #endif\n    #ifdef EDESTADDRREQ\n        case EDESTADDRREQ: return DRWAV_NO_ADDRESS;\n    #endif\n    #ifdef EMSGSIZE\n        case EMSGSIZE: return DRWAV_TOO_BIG;\n    #endif\n    #ifdef EPROTOTYPE\n        case EPROTOTYPE: return DRWAV_BAD_PROTOCOL;\n    #endif\n    #ifdef ENOPROTOOPT\n        case ENOPROTOOPT: return DRWAV_PROTOCOL_UNAVAILABLE;\n    #endif\n    #ifdef EPROTONOSUPPORT\n        case EPROTONOSUPPORT: return DRWAV_PROTOCOL_NOT_SUPPORTED;\n    #endif\n    #ifdef ESOCKTNOSUPPORT\n        case ESOCKTNOSUPPORT: return DRWAV_SOCKET_NOT_SUPPORTED;\n    #endif\n    #ifdef EOPNOTSUPP\n        case EOPNOTSUPP: return DRWAV_INVALID_OPERATION;\n    #endif\n    #ifdef EPFNOSUPPORT\n        case EPFNOSUPPORT: return DRWAV_PROTOCOL_FAMILY_NOT_SUPPORTED;\n    #endif\n    #ifdef EAFNOSUPPORT\n        case EAFNOSUPPORT: return DRWAV_ADDRESS_FAMILY_NOT_SUPPORTED;\n    #endif\n    #ifdef EADDRINUSE\n        case EADDRINUSE: return DRWAV_ALREADY_IN_USE;\n    #endif\n    #ifdef EADDRNOTAVAIL\n        case EADDRNOTAVAIL: return DRWAV_ERROR;\n    #endif\n    #ifdef ENETDOWN\n        case ENETDOWN: return DRWAV_NO_NETWORK;\n    #endif\n    #ifdef ENETUNREACH\n        case ENETUNREACH: return DRWAV_NO_NETWORK;\n    #endif\n    #ifdef ENETRESET\n        case ENETRESET: return DRWAV_NO_NETWORK;\n    #endif\n    #ifdef ECONNABORTED\n        case ECONNABORTED: return DRWAV_NO_NETWORK;\n    #endif\n    #ifdef ECONNRESET\n        case ECONNRESET: return DRWAV_CONNECTION_RESET;\n    #endif\n    #ifdef ENOBUFS\n        case ENOBUFS: return DRWAV_NO_SPACE;\n    #endif\n    #ifdef EISCONN\n        case EISCONN: return DRWAV_ALREADY_CONNECTED;\n    #endif\n    #ifdef ENOTCONN\n        case ENOTCONN: return DRWAV_NOT_CONNECTED;\n    #endif\n    #ifdef ESHUTDOWN\n        case ESHUTDOWN: return DRWAV_ERROR;\n    #endif\n    #ifdef ETOOMANYREFS\n        case ETOOMANYREFS: return DRWAV_ERROR;\n    #endif\n    #ifdef ETIMEDOUT\n        case ETIMEDOUT: return DRWAV_TIMEOUT;\n    #endif\n    #ifdef ECONNREFUSED\n        case ECONNREFUSED: return DRWAV_CONNECTION_REFUSED;\n    #endif\n    #ifdef EHOSTDOWN\n        case EHOSTDOWN: return DRWAV_NO_HOST;\n    #endif\n    #ifdef EHOSTUNREACH\n        case EHOSTUNREACH: return DRWAV_NO_HOST;\n    #endif\n    #ifdef EALREADY\n        case EALREADY: return DRWAV_IN_PROGRESS;\n    #endif\n    #ifdef EINPROGRESS\n        case EINPROGRESS: return DRWAV_IN_PROGRESS;\n    #endif\n    #ifdef ESTALE\n        case ESTALE: return DRWAV_INVALID_FILE;\n    #endif\n    #ifdef EUCLEAN\n        case EUCLEAN: return DRWAV_ERROR;\n    #endif\n    #ifdef ENOTNAM\n        case ENOTNAM: return DRWAV_ERROR;\n    #endif\n    #ifdef ENAVAIL\n        case ENAVAIL: return DRWAV_ERROR;\n    #endif\n    #ifdef EISNAM\n        case EISNAM: return DRWAV_ERROR;\n    #endif\n    #ifdef EREMOTEIO\n        case EREMOTEIO: return DRWAV_IO_ERROR;\n    #endif\n    #ifdef EDQUOT\n        case EDQUOT: return DRWAV_NO_SPACE;\n    #endif\n    #ifdef ENOMEDIUM\n        case ENOMEDIUM: return DRWAV_DOES_NOT_EXIST;\n    #endif\n    #ifdef EMEDIUMTYPE\n        case EMEDIUMTYPE: return DRWAV_ERROR;\n    #endif\n    #ifdef ECANCELED\n        case ECANCELED: return DRWAV_CANCELLED;\n    #endif\n    #ifdef ENOKEY\n        case ENOKEY: return DRWAV_ERROR;\n    #endif\n    #ifdef EKEYEXPIRED\n        case EKEYEXPIRED: return DRWAV_ERROR;\n    #endif\n    #ifdef EKEYREVOKED\n        case EKEYREVOKED: return DRWAV_ERROR;\n    #endif\n    #ifdef EKEYREJECTED\n        case EKEYREJECTED: return DRWAV_ERROR;\n    #endif\n    #ifdef EOWNERDEAD\n        case EOWNERDEAD: return DRWAV_ERROR;\n    #endif\n    #ifdef ENOTRECOVERABLE\n        case ENOTRECOVERABLE: return DRWAV_ERROR;\n    #endif\n    #ifdef ERFKILL\n        case ERFKILL: return DRWAV_ERROR;\n    #endif\n    #ifdef EHWPOISON\n        case EHWPOISON: return DRWAV_ERROR;\n    #endif\n        default: return DRWAV_ERROR;\n    }\n}\n\nstatic drwav_result drwav_fopen(FILE** ppFile, const char* pFilePath, const char* pOpenMode)\n{\n#if _MSC_VER && _MSC_VER >= 1400\n    errno_t err;\n#endif\n\n    if (ppFile != NULL) {\n        *ppFile = NULL;  /* Safety. */\n    }\n\n    if (pFilePath == NULL || pOpenMode == NULL || ppFile == NULL) {\n        return DRWAV_INVALID_ARGS;\n    }\n\n#if _MSC_VER && _MSC_VER >= 1400\n    err = fopen_s(ppFile, pFilePath, pOpenMode);\n    if (err != 0) {\n        return drwav_result_from_errno(err);\n    }\n#else\n#if defined(_WIN32) || defined(__APPLE__)\n    *ppFile = fopen(pFilePath, pOpenMode);\n#else\n    #if defined(_FILE_OFFSET_BITS) && _FILE_OFFSET_BITS == 64 && defined(_LARGEFILE64_SOURCE)\n        *ppFile = fopen64(pFilePath, pOpenMode);\n    #else\n        *ppFile = fopen(pFilePath, pOpenMode);\n    #endif\n#endif\n    if (*ppFile == NULL) {\n        drwav_result result = drwav_result_from_errno(errno);\n        if (result == DRWAV_SUCCESS) {\n            result = DRWAV_ERROR;   /* Just a safety check to make sure we never ever return success when pFile == NULL. */\n        }\n\n        return result;\n    }\n#endif\n\n    return DRWAV_SUCCESS;\n}\n\n/*\n_wfopen() isn't always available in all compilation environments.\n\n    * Windows only.\n    * MSVC seems to support it universally as far back as VC6 from what I can tell (haven't checked further back).\n    * MinGW-64 (both 32- and 64-bit) seems to support it.\n    * MinGW wraps it in !defined(__STRICT_ANSI__).\n    * OpenWatcom wraps it in !defined(_NO_EXT_KEYS).\n\nThis can be reviewed as compatibility issues arise. The preference is to use _wfopen_s() and _wfopen() as opposed to the wcsrtombs()\nfallback, so if you notice your compiler not detecting this properly I'm happy to look at adding support.\n*/\n#if defined(_WIN32)\n    #if defined(_MSC_VER) || defined(__MINGW64__) || (!defined(__STRICT_ANSI__) && !defined(_NO_EXT_KEYS))\n        #define DRWAV_HAS_WFOPEN\n    #endif\n#endif\n\nstatic drwav_result drwav_wfopen(FILE** ppFile, const wchar_t* pFilePath, const wchar_t* pOpenMode, const drwav_allocation_callbacks* pAllocationCallbacks)\n{\n    if (ppFile != NULL) {\n        *ppFile = NULL;  /* Safety. */\n    }\n\n    if (pFilePath == NULL || pOpenMode == NULL || ppFile == NULL) {\n        return DRWAV_INVALID_ARGS;\n    }\n\n#if defined(DRWAV_HAS_WFOPEN)\n    {\n        /* Use _wfopen() on Windows. */\n    #if defined(_MSC_VER) && _MSC_VER >= 1400\n        errno_t err = _wfopen_s(ppFile, pFilePath, pOpenMode);\n        if (err != 0) {\n            return drwav_result_from_errno(err);\n        }\n    #else\n        *ppFile = _wfopen(pFilePath, pOpenMode);\n        if (*ppFile == NULL) {\n            return drwav_result_from_errno(errno);\n        }\n    #endif\n        (void)pAllocationCallbacks;\n    }\n#else\n    /*\n    Use fopen() on anything other than Windows. Requires a conversion. This is annoying because fopen() is locale specific. The only real way I can\n    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\n    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.\n    */\n    {\n        mbstate_t mbs;\n        size_t lenMB;\n        const wchar_t* pFilePathTemp = pFilePath;\n        char* pFilePathMB = NULL;\n        char pOpenModeMB[32] = {0};\n\n        /* Get the length first. */\n        DRWAV_ZERO_OBJECT(&mbs);\n        lenMB = wcsrtombs(NULL, &pFilePathTemp, 0, &mbs);\n        if (lenMB == (size_t)-1) {\n            return drwav_result_from_errno(errno);\n        }\n\n        pFilePathMB = (char*)drwav__malloc_from_callbacks(lenMB + 1, pAllocationCallbacks);\n        if (pFilePathMB == NULL) {\n            return DRWAV_OUT_OF_MEMORY;\n        }\n\n        pFilePathTemp = pFilePath;\n        DRWAV_ZERO_OBJECT(&mbs);\n        wcsrtombs(pFilePathMB, &pFilePathTemp, lenMB + 1, &mbs);\n\n        /* The open mode should always consist of ASCII characters so we should be able to do a trivial conversion. */\n        {\n            size_t i = 0;\n            for (;;) {\n                if (pOpenMode[i] == 0) {\n                    pOpenModeMB[i] = '\\0';\n                    break;\n                }\n\n                pOpenModeMB[i] = (char)pOpenMode[i];\n                i += 1;\n            }\n        }\n\n        *ppFile = fopen(pFilePathMB, pOpenModeMB);\n\n        drwav__free_from_callbacks(pFilePathMB, pAllocationCallbacks);\n    }\n\n    if (*ppFile == NULL) {\n        return DRWAV_ERROR;\n    }\n#endif\n\n    return DRWAV_SUCCESS;\n}\n\n\nstatic size_t drwav__on_read_stdio(void* pUserData, void* pBufferOut, size_t bytesToRead)\n{\n    return fread(pBufferOut, 1, bytesToRead, (FILE*)pUserData);\n}\n\nstatic size_t drwav__on_write_stdio(void* pUserData, const void* pData, size_t bytesToWrite)\n{\n    return fwrite(pData, 1, bytesToWrite, (FILE*)pUserData);\n}\n\nstatic drwav_bool32 drwav__on_seek_stdio(void* pUserData, int offset, drwav_seek_origin origin)\n{\n    return fseek((FILE*)pUserData, offset, (origin == drwav_seek_origin_current) ? SEEK_CUR : SEEK_SET) == 0;\n}\n\nDRWAV_API drwav_bool32 drwav_init_file(drwav* pWav, const char* filename, const drwav_allocation_callbacks* pAllocationCallbacks)\n{\n    return drwav_init_file_ex(pWav, filename, NULL, NULL, 0, pAllocationCallbacks);\n}\n\n\nstatic 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)\n{\n    drwav_bool32 result;\n\n    result = drwav_preinit(pWav, drwav__on_read_stdio, drwav__on_seek_stdio, (void*)pFile, pAllocationCallbacks);\n    if (result != DRWAV_TRUE) {\n        fclose(pFile);\n        return result;\n    }\n\n    result = drwav_init__internal(pWav, onChunk, pChunkUserData, flags);\n    if (result != DRWAV_TRUE) {\n        fclose(pFile);\n        return result;\n    }\n\n    return DRWAV_TRUE;\n}\n\nDRWAV_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)\n{\n    FILE* pFile;\n    if (drwav_fopen(&pFile, filename, \"rb\") != DRWAV_SUCCESS) {\n        return DRWAV_FALSE;\n    }\n\n    /* This takes ownership of the FILE* object. */\n    return drwav_init_file__internal_FILE(pWav, pFile, onChunk, pChunkUserData, flags, pAllocationCallbacks);\n}\n\nDRWAV_API drwav_bool32 drwav_init_file_w(drwav* pWav, const wchar_t* filename, const drwav_allocation_callbacks* pAllocationCallbacks)\n{\n    return drwav_init_file_ex_w(pWav, filename, NULL, NULL, 0, pAllocationCallbacks);\n}\n\nDRWAV_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)\n{\n    FILE* pFile;\n    if (drwav_wfopen(&pFile, filename, L\"rb\", pAllocationCallbacks) != DRWAV_SUCCESS) {\n        return DRWAV_FALSE;\n    }\n\n    /* This takes ownership of the FILE* object. */\n    return drwav_init_file__internal_FILE(pWav, pFile, onChunk, pChunkUserData, flags, pAllocationCallbacks);\n}\n\n\nstatic 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)\n{\n    drwav_bool32 result;\n\n    result = drwav_preinit_write(pWav, pFormat, isSequential, drwav__on_write_stdio, drwav__on_seek_stdio, (void*)pFile, pAllocationCallbacks);\n    if (result != DRWAV_TRUE) {\n        fclose(pFile);\n        return result;\n    }\n\n    result = drwav_init_write__internal(pWav, pFormat, totalSampleCount);\n    if (result != DRWAV_TRUE) {\n        fclose(pFile);\n        return result;\n    }\n\n    return DRWAV_TRUE;\n}\n\nstatic 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)\n{\n    FILE* pFile;\n    if (drwav_fopen(&pFile, filename, \"wb\") != DRWAV_SUCCESS) {\n        return DRWAV_FALSE;\n    }\n\n    /* This takes ownership of the FILE* object. */\n    return drwav_init_file_write__internal_FILE(pWav, pFile, pFormat, totalSampleCount, isSequential, pAllocationCallbacks);\n}\n\nstatic 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)\n{\n    FILE* pFile;\n    if (drwav_wfopen(&pFile, filename, L\"wb\", pAllocationCallbacks) != DRWAV_SUCCESS) {\n        return DRWAV_FALSE;\n    }\n\n    /* This takes ownership of the FILE* object. */\n    return drwav_init_file_write__internal_FILE(pWav, pFile, pFormat, totalSampleCount, isSequential, pAllocationCallbacks);\n}\n\nDRWAV_API drwav_bool32 drwav_init_file_write(drwav* pWav, const char* filename, const drwav_data_format* pFormat, const drwav_allocation_callbacks* pAllocationCallbacks)\n{\n    return drwav_init_file_write__internal(pWav, filename, pFormat, 0, DRWAV_FALSE, pAllocationCallbacks);\n}\n\nDRWAV_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)\n{\n    return drwav_init_file_write__internal(pWav, filename, pFormat, totalSampleCount, DRWAV_TRUE, pAllocationCallbacks);\n}\n\nDRWAV_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)\n{\n    if (pFormat == NULL) {\n        return DRWAV_FALSE;\n    }\n\n    return drwav_init_file_write_sequential(pWav, filename, pFormat, totalPCMFrameCount*pFormat->channels, pAllocationCallbacks);\n}\n\nDRWAV_API drwav_bool32 drwav_init_file_write_w(drwav* pWav, const wchar_t* filename, const drwav_data_format* pFormat, const drwav_allocation_callbacks* pAllocationCallbacks)\n{\n    return drwav_init_file_write_w__internal(pWav, filename, pFormat, 0, DRWAV_FALSE, pAllocationCallbacks);\n}\n\nDRWAV_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)\n{\n    return drwav_init_file_write_w__internal(pWav, filename, pFormat, totalSampleCount, DRWAV_TRUE, pAllocationCallbacks);\n}\n\nDRWAV_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)\n{\n    if (pFormat == NULL) {\n        return DRWAV_FALSE;\n    }\n\n    return drwav_init_file_write_sequential_w(pWav, filename, pFormat, totalPCMFrameCount*pFormat->channels, pAllocationCallbacks);\n}\n#endif  /* DR_WAV_NO_STDIO */\n\n\nstatic size_t drwav__on_read_memory(void* pUserData, void* pBufferOut, size_t bytesToRead)\n{\n    drwav* pWav = (drwav*)pUserData;\n    size_t bytesRemaining;\n\n    DRWAV_ASSERT(pWav != NULL);\n    DRWAV_ASSERT(pWav->memoryStream.dataSize >= pWav->memoryStream.currentReadPos);\n\n    bytesRemaining = pWav->memoryStream.dataSize - pWav->memoryStream.currentReadPos;\n    if (bytesToRead > bytesRemaining) {\n        bytesToRead = bytesRemaining;\n    }\n\n    if (bytesToRead > 0) {\n        DRWAV_COPY_MEMORY(pBufferOut, pWav->memoryStream.data + pWav->memoryStream.currentReadPos, bytesToRead);\n        pWav->memoryStream.currentReadPos += bytesToRead;\n    }\n\n    return bytesToRead;\n}\n\nstatic drwav_bool32 drwav__on_seek_memory(void* pUserData, int offset, drwav_seek_origin origin)\n{\n    drwav* pWav = (drwav*)pUserData;\n    DRWAV_ASSERT(pWav != NULL);\n\n    if (origin == drwav_seek_origin_current) {\n        if (offset > 0) {\n            if (pWav->memoryStream.currentReadPos + offset > pWav->memoryStream.dataSize) {\n                return DRWAV_FALSE; /* Trying to seek too far forward. */\n            }\n        } else {\n            if (pWav->memoryStream.currentReadPos < (size_t)-offset) {\n                return DRWAV_FALSE; /* Trying to seek too far backwards. */\n            }\n        }\n\n        /* This will never underflow thanks to the clamps above. */\n        pWav->memoryStream.currentReadPos += offset;\n    } else {\n        if ((drwav_uint32)offset <= pWav->memoryStream.dataSize) {\n            pWav->memoryStream.currentReadPos = offset;\n        } else {\n            return DRWAV_FALSE; /* Trying to seek too far forward. */\n        }\n    }\n    \n    return DRWAV_TRUE;\n}\n\nstatic size_t drwav__on_write_memory(void* pUserData, const void* pDataIn, size_t bytesToWrite)\n{\n    drwav* pWav = (drwav*)pUserData;\n    size_t bytesRemaining;\n\n    DRWAV_ASSERT(pWav != NULL);\n    DRWAV_ASSERT(pWav->memoryStreamWrite.dataCapacity >= pWav->memoryStreamWrite.currentWritePos);\n\n    bytesRemaining = pWav->memoryStreamWrite.dataCapacity - pWav->memoryStreamWrite.currentWritePos;\n    if (bytesRemaining < bytesToWrite) {\n        /* Need to reallocate. */\n        void* pNewData;\n        size_t newDataCapacity = (pWav->memoryStreamWrite.dataCapacity == 0) ? 256 : pWav->memoryStreamWrite.dataCapacity * 2;\n\n        /* If doubling wasn't enough, just make it the minimum required size to write the data. */\n        if ((newDataCapacity - pWav->memoryStreamWrite.currentWritePos) < bytesToWrite) {\n            newDataCapacity = pWav->memoryStreamWrite.currentWritePos + bytesToWrite;\n        }\n\n        pNewData = drwav__realloc_from_callbacks(*pWav->memoryStreamWrite.ppData, newDataCapacity, pWav->memoryStreamWrite.dataCapacity, &pWav->allocationCallbacks);\n        if (pNewData == NULL) {\n            return 0;\n        }\n\n        *pWav->memoryStreamWrite.ppData = pNewData;\n        pWav->memoryStreamWrite.dataCapacity = newDataCapacity;\n    }\n\n    DRWAV_COPY_MEMORY(((drwav_uint8*)(*pWav->memoryStreamWrite.ppData)) + pWav->memoryStreamWrite.currentWritePos, pDataIn, bytesToWrite);\n\n    pWav->memoryStreamWrite.currentWritePos += bytesToWrite;\n    if (pWav->memoryStreamWrite.dataSize < pWav->memoryStreamWrite.currentWritePos) {\n        pWav->memoryStreamWrite.dataSize = pWav->memoryStreamWrite.currentWritePos;\n    }\n\n    *pWav->memoryStreamWrite.pDataSize = pWav->memoryStreamWrite.dataSize;\n\n    return bytesToWrite;\n}\n\nstatic drwav_bool32 drwav__on_seek_memory_write(void* pUserData, int offset, drwav_seek_origin origin)\n{\n    drwav* pWav = (drwav*)pUserData;\n    DRWAV_ASSERT(pWav != NULL);\n\n    if (origin == drwav_seek_origin_current) {\n        if (offset > 0) {\n            if (pWav->memoryStreamWrite.currentWritePos + offset > pWav->memoryStreamWrite.dataSize) {\n                offset = (int)(pWav->memoryStreamWrite.dataSize - pWav->memoryStreamWrite.currentWritePos);  /* Trying to seek too far forward. */\n            }\n        } else {\n            if (pWav->memoryStreamWrite.currentWritePos < (size_t)-offset) {\n                offset = -(int)pWav->memoryStreamWrite.currentWritePos;  /* Trying to seek too far backwards. */\n            }\n        }\n\n        /* This will never underflow thanks to the clamps above. */\n        pWav->memoryStreamWrite.currentWritePos += offset;\n    } else {\n        if ((drwav_uint32)offset <= pWav->memoryStreamWrite.dataSize) {\n            pWav->memoryStreamWrite.currentWritePos = offset;\n        } else {\n            pWav->memoryStreamWrite.currentWritePos = pWav->memoryStreamWrite.dataSize;  /* Trying to seek too far forward. */\n        }\n    }\n    \n    return DRWAV_TRUE;\n}\n\nDRWAV_API drwav_bool32 drwav_init_memory(drwav* pWav, const void* data, size_t dataSize, const drwav_allocation_callbacks* pAllocationCallbacks)\n{\n    return drwav_init_memory_ex(pWav, data, dataSize, NULL, NULL, 0, pAllocationCallbacks);\n}\n\nDRWAV_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)\n{\n    if (data == NULL || dataSize == 0) {\n        return DRWAV_FALSE;\n    }\n\n    if (!drwav_preinit(pWav, drwav__on_read_memory, drwav__on_seek_memory, pWav, pAllocationCallbacks)) {\n        return DRWAV_FALSE;\n    }\n\n    pWav->memoryStream.data = (const drwav_uint8*)data;\n    pWav->memoryStream.dataSize = dataSize;\n    pWav->memoryStream.currentReadPos = 0;\n\n    return drwav_init__internal(pWav, onChunk, pChunkUserData, flags);\n}\n\n\nstatic 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)\n{\n    if (ppData == NULL || pDataSize == NULL) {\n        return DRWAV_FALSE;\n    }\n\n    *ppData = NULL; /* Important because we're using realloc()! */\n    *pDataSize = 0;\n\n    if (!drwav_preinit_write(pWav, pFormat, isSequential, drwav__on_write_memory, drwav__on_seek_memory_write, pWav, pAllocationCallbacks)) {\n        return DRWAV_FALSE;\n    }\n\n    pWav->memoryStreamWrite.ppData = ppData;\n    pWav->memoryStreamWrite.pDataSize = pDataSize;\n    pWav->memoryStreamWrite.dataSize = 0;\n    pWav->memoryStreamWrite.dataCapacity = 0;\n    pWav->memoryStreamWrite.currentWritePos = 0;\n\n    return drwav_init_write__internal(pWav, pFormat, totalSampleCount);\n}\n\nDRWAV_API drwav_bool32 drwav_init_memory_write(drwav* pWav, void** ppData, size_t* pDataSize, const drwav_data_format* pFormat, const drwav_allocation_callbacks* pAllocationCallbacks)\n{\n    return drwav_init_memory_write__internal(pWav, ppData, pDataSize, pFormat, 0, DRWAV_FALSE, pAllocationCallbacks);\n}\n\nDRWAV_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)\n{\n    return drwav_init_memory_write__internal(pWav, ppData, pDataSize, pFormat, totalSampleCount, DRWAV_TRUE, pAllocationCallbacks);\n}\n\nDRWAV_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)\n{\n    if (pFormat == NULL) {\n        return DRWAV_FALSE;\n    }\n\n    return drwav_init_memory_write_sequential(pWav, ppData, pDataSize, pFormat, totalPCMFrameCount*pFormat->channels, pAllocationCallbacks);\n}\n\n\n\nDRWAV_API drwav_result drwav_uninit(drwav* pWav)\n{\n    drwav_result result = DRWAV_SUCCESS;\n\n    if (pWav == NULL) {\n        return DRWAV_INVALID_ARGS;\n    }\n\n    /*\n    If the drwav object was opened in write mode we'll need to finalize a few things:\n      - Make sure the \"data\" chunk is aligned to 16-bits for RIFF containers, or 64 bits for W64 containers.\n      - Set the size of the \"data\" chunk.\n    */\n    if (pWav->onWrite != NULL) {\n        drwav_uint32 paddingSize = 0;\n\n        /* Padding. Do not adjust pWav->dataChunkDataSize - this should not include the padding. */\n        if (pWav->container == drwav_container_riff || pWav->container == drwav_container_rf64) {\n            paddingSize = drwav__chunk_padding_size_riff(pWav->dataChunkDataSize);\n        } else {\n            paddingSize = drwav__chunk_padding_size_w64(pWav->dataChunkDataSize);\n        }\n        \n        if (paddingSize > 0) {\n            drwav_uint64 paddingData = 0;\n            drwav__write(pWav, &paddingData, paddingSize);  /* Byte order does not matter for this. */\n        }\n\n        /*\n        Chunk sizes. When using sequential mode, these will have been filled in at initialization time. We only need\n        to do this when using non-sequential mode.\n        */\n        if (pWav->onSeek && !pWav->isSequentialWrite) {\n            if (pWav->container == drwav_container_riff) {\n                /* The \"RIFF\" chunk size. */\n                if (pWav->onSeek(pWav->pUserData, 4, drwav_seek_origin_start)) {\n                    drwav_uint32 riffChunkSize = drwav__riff_chunk_size_riff(pWav->dataChunkDataSize);\n                    drwav__write_u32ne_to_le(pWav, riffChunkSize);\n                }\n\n                /* the \"data\" chunk size. */\n                if (pWav->onSeek(pWav->pUserData, (int)pWav->dataChunkDataPos + 4, drwav_seek_origin_start)) {\n                    drwav_uint32 dataChunkSize = drwav__data_chunk_size_riff(pWav->dataChunkDataSize);\n                    drwav__write_u32ne_to_le(pWav, dataChunkSize);\n                }\n            } else if (pWav->container == drwav_container_w64) {\n                /* The \"RIFF\" chunk size. */\n                if (pWav->onSeek(pWav->pUserData, 16, drwav_seek_origin_start)) {\n                    drwav_uint64 riffChunkSize = drwav__riff_chunk_size_w64(pWav->dataChunkDataSize);\n                    drwav__write_u64ne_to_le(pWav, riffChunkSize);\n                }\n\n                /* The \"data\" chunk size. */\n                if (pWav->onSeek(pWav->pUserData, (int)pWav->dataChunkDataPos + 16, drwav_seek_origin_start)) {\n                    drwav_uint64 dataChunkSize = drwav__data_chunk_size_w64(pWav->dataChunkDataSize);\n                    drwav__write_u64ne_to_le(pWav, dataChunkSize);\n                }\n            } else if (pWav->container == drwav_container_rf64) {\n                /* We only need to update the ds64 chunk. The \"RIFF\" and \"data\" chunks always have their sizes set to 0xFFFFFFFF for RF64. */\n                int ds64BodyPos = 12 + 8;\n\n                /* The \"RIFF\" chunk size. */\n                if (pWav->onSeek(pWav->pUserData, ds64BodyPos + 0, drwav_seek_origin_start)) {\n                    drwav_uint64 riffChunkSize = drwav__riff_chunk_size_rf64(pWav->dataChunkDataSize);\n                    drwav__write_u64ne_to_le(pWav, riffChunkSize);\n                }\n\n                /* The \"data\" chunk size. */\n                if (pWav->onSeek(pWav->pUserData, ds64BodyPos + 8, drwav_seek_origin_start)) {\n                    drwav_uint64 dataChunkSize = drwav__data_chunk_size_rf64(pWav->dataChunkDataSize);\n                    drwav__write_u64ne_to_le(pWav, dataChunkSize);\n                }\n            }\n        }\n\n        /* Validation for sequential mode. */\n        if (pWav->isSequentialWrite) {\n            if (pWav->dataChunkDataSize != pWav->dataChunkDataSizeTargetWrite) {\n                result = DRWAV_INVALID_FILE;\n            }\n        }\n    }\n\n#ifndef DR_WAV_NO_STDIO\n    /*\n    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()\n    was used by looking at the onRead and onSeek callbacks.\n    */\n    if (pWav->onRead == drwav__on_read_stdio || pWav->onWrite == drwav__on_write_stdio) {\n        fclose((FILE*)pWav->pUserData);\n    }\n#endif\n\n    return result;\n}\n\n\n\nDRWAV_API size_t drwav_read_raw(drwav* pWav, size_t bytesToRead, void* pBufferOut)\n{\n    size_t bytesRead;\n\n    if (pWav == NULL || bytesToRead == 0) {\n        return 0;\n    }\n\n    if (bytesToRead > pWav->bytesRemaining) {\n        bytesToRead = (size_t)pWav->bytesRemaining;\n    }\n\n    if (pBufferOut != NULL) {\n        bytesRead = pWav->onRead(pWav->pUserData, pBufferOut, bytesToRead);\n    } else {\n        /* We need to seek. If we fail, we need to read-and-discard to make sure we get a good byte count. */\n        bytesRead = 0;\n        while (bytesRead < bytesToRead) {\n            size_t bytesToSeek = (bytesToRead - bytesRead);\n            if (bytesToSeek > 0x7FFFFFFF) {\n                bytesToSeek = 0x7FFFFFFF;\n            }\n\n            if (pWav->onSeek(pWav->pUserData, (int)bytesToSeek, drwav_seek_origin_current) == DRWAV_FALSE) {\n                break;\n            }\n\n            bytesRead += bytesToSeek;\n        }\n\n        /* When we get here we may need to read-and-discard some data. */\n        while (bytesRead < bytesToRead) {\n            drwav_uint8 buffer[4096];\n            size_t bytesSeeked;\n            size_t bytesToSeek = (bytesToRead - bytesRead);\n            if (bytesToSeek > sizeof(buffer)) {\n                bytesToSeek = sizeof(buffer);\n            }\n\n            bytesSeeked = pWav->onRead(pWav->pUserData, buffer, bytesToSeek);\n            bytesRead += bytesSeeked;\n\n            if (bytesSeeked < bytesToSeek) {\n                break;  /* Reached the end. */\n            }\n        }\n    }\n\n    pWav->bytesRemaining -= bytesRead;\n    return bytesRead;\n}\n\n\n\nDRWAV_API drwav_uint64 drwav_read_pcm_frames_le(drwav* pWav, drwav_uint64 framesToRead, void* pBufferOut)\n{\n    drwav_uint32 bytesPerFrame;\n    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. */\n\n    if (pWav == NULL || framesToRead == 0) {\n        return 0;\n    }\n\n    /* Cannot use this function for compressed formats. */\n    if (drwav__is_compressed_format_tag(pWav->translatedFormatTag)) {\n        return 0;\n    }\n\n    bytesPerFrame = drwav_get_bytes_per_pcm_frame(pWav);\n    if (bytesPerFrame == 0) {\n        return 0;\n    }\n\n    /* Don't try to read more samples than can potentially fit in the output buffer. */\n    bytesToRead = framesToRead * bytesPerFrame;\n    if (bytesToRead > DRWAV_SIZE_MAX) {\n        bytesToRead = (DRWAV_SIZE_MAX / bytesPerFrame) * bytesPerFrame; /* Round the number of bytes to read to a clean frame boundary. */\n    }\n\n    /*\n    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\n    *could* be a time where it evaluates to 0 due to overflowing.\n    */\n    if (bytesToRead == 0) {\n        return 0;\n    }\n\n    return drwav_read_raw(pWav, (size_t)bytesToRead, pBufferOut) / bytesPerFrame;\n}\n\nDRWAV_API drwav_uint64 drwav_read_pcm_frames_be(drwav* pWav, drwav_uint64 framesToRead, void* pBufferOut)\n{\n    drwav_uint64 framesRead = drwav_read_pcm_frames_le(pWav, framesToRead, pBufferOut);\n\n    if (pBufferOut != NULL) {\n        drwav__bswap_samples(pBufferOut, framesRead*pWav->channels, drwav_get_bytes_per_pcm_frame(pWav)/pWav->channels, pWav->translatedFormatTag);\n    }\n\n    return framesRead;\n}\n\nDRWAV_API drwav_uint64 drwav_read_pcm_frames(drwav* pWav, drwav_uint64 framesToRead, void* pBufferOut)\n{\n    if (drwav__is_little_endian()) {\n        return drwav_read_pcm_frames_le(pWav, framesToRead, pBufferOut);\n    } else {\n        return drwav_read_pcm_frames_be(pWav, framesToRead, pBufferOut);\n    }\n}\n\n\n\nDRWAV_API drwav_bool32 drwav_seek_to_first_pcm_frame(drwav* pWav)\n{\n    if (pWav->onWrite != NULL) {\n        return DRWAV_FALSE; /* No seeking in write mode. */\n    }\n\n    if (!pWav->onSeek(pWav->pUserData, (int)pWav->dataChunkDataPos, drwav_seek_origin_start)) {\n        return DRWAV_FALSE;\n    }\n\n    if (drwav__is_compressed_format_tag(pWav->translatedFormatTag)) {\n        pWav->compressed.iCurrentPCMFrame = 0;\n\n        /* Cached data needs to be cleared for compressed formats. */\n        if (pWav->translatedFormatTag == DR_WAVE_FORMAT_ADPCM) {\n            DRWAV_ZERO_OBJECT(&pWav->msadpcm);\n        } else if (pWav->translatedFormatTag == DR_WAVE_FORMAT_DVI_ADPCM) {\n            DRWAV_ZERO_OBJECT(&pWav->ima);\n        } else {\n            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. */\n        }\n    }\n    \n    pWav->bytesRemaining = pWav->dataChunkDataSize;\n    return DRWAV_TRUE;\n}\n\nDRWAV_API drwav_bool32 drwav_seek_to_pcm_frame(drwav* pWav, drwav_uint64 targetFrameIndex)\n{\n    /* Seeking should be compatible with wave files > 2GB. */\n\n    if (pWav == NULL || pWav->onSeek == NULL) {\n        return DRWAV_FALSE;\n    }\n\n    /* No seeking in write mode. */\n    if (pWav->onWrite != NULL) {\n        return DRWAV_FALSE;\n    }\n\n    /* If there are no samples, just return DRWAV_TRUE without doing anything. */\n    if (pWav->totalPCMFrameCount == 0) {\n        return DRWAV_TRUE;\n    }\n\n    /* Make sure the sample is clamped. */\n    if (targetFrameIndex >= pWav->totalPCMFrameCount) {\n        targetFrameIndex  = pWav->totalPCMFrameCount - 1;\n    }\n\n    /*\n    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\n    to seek back to the start.\n    */\n    if (drwav__is_compressed_format_tag(pWav->translatedFormatTag)) {\n        /* TODO: This can be optimized. */\n        \n        /*\n        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,\n        we first need to seek back to the start and then just do the same thing as a forward seek.\n        */\n        if (targetFrameIndex < pWav->compressed.iCurrentPCMFrame) {\n            if (!drwav_seek_to_first_pcm_frame(pWav)) {\n                return DRWAV_FALSE;\n            }\n        }\n\n        if (targetFrameIndex > pWav->compressed.iCurrentPCMFrame) {\n            drwav_uint64 offsetInFrames = targetFrameIndex - pWav->compressed.iCurrentPCMFrame;\n\n            drwav_int16 devnull[2048];\n            while (offsetInFrames > 0) {\n                drwav_uint64 framesRead = 0;\n                drwav_uint64 framesToRead = offsetInFrames;\n                if (framesToRead > drwav_countof(devnull)/pWav->channels) {\n                    framesToRead = drwav_countof(devnull)/pWav->channels;\n                }\n\n                if (pWav->translatedFormatTag == DR_WAVE_FORMAT_ADPCM) {\n                    framesRead = drwav_read_pcm_frames_s16__msadpcm(pWav, framesToRead, devnull);\n                } else if (pWav->translatedFormatTag == DR_WAVE_FORMAT_DVI_ADPCM) {\n                    framesRead = drwav_read_pcm_frames_s16__ima(pWav, framesToRead, devnull);\n                } else {\n                    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. */\n                }\n\n                if (framesRead != framesToRead) {\n                    return DRWAV_FALSE;\n                }\n\n                offsetInFrames -= framesRead;\n            }\n        }\n    } else {\n        drwav_uint64 totalSizeInBytes;\n        drwav_uint64 currentBytePos;\n        drwav_uint64 targetBytePos;\n        drwav_uint64 offset;\n\n        totalSizeInBytes = pWav->totalPCMFrameCount * drwav_get_bytes_per_pcm_frame(pWav);\n        DRWAV_ASSERT(totalSizeInBytes >= pWav->bytesRemaining);\n\n        currentBytePos = totalSizeInBytes - pWav->bytesRemaining;\n        targetBytePos  = targetFrameIndex * drwav_get_bytes_per_pcm_frame(pWav);\n\n        if (currentBytePos < targetBytePos) {\n            /* Offset forwards. */\n            offset = (targetBytePos - currentBytePos);\n        } else {\n            /* Offset backwards. */\n            if (!drwav_seek_to_first_pcm_frame(pWav)) {\n                return DRWAV_FALSE;\n            }\n            offset = targetBytePos;\n        }\n\n        while (offset > 0) {\n            int offset32 = ((offset > INT_MAX) ? INT_MAX : (int)offset);\n            if (!pWav->onSeek(pWav->pUserData, offset32, drwav_seek_origin_current)) {\n                return DRWAV_FALSE;\n            }\n\n            pWav->bytesRemaining -= offset32;\n            offset -= offset32;\n        }\n    }\n\n    return DRWAV_TRUE;\n}\n\n\nDRWAV_API size_t drwav_write_raw(drwav* pWav, size_t bytesToWrite, const void* pData)\n{\n    size_t bytesWritten;\n\n    if (pWav == NULL || bytesToWrite == 0 || pData == NULL) {\n        return 0;\n    }\n\n    bytesWritten = pWav->onWrite(pWav->pUserData, pData, bytesToWrite);\n    pWav->dataChunkDataSize += bytesWritten;\n\n    return bytesWritten;\n}\n\n\nDRWAV_API drwav_uint64 drwav_write_pcm_frames_le(drwav* pWav, drwav_uint64 framesToWrite, const void* pData)\n{\n    drwav_uint64 bytesToWrite;\n    drwav_uint64 bytesWritten;\n    const drwav_uint8* pRunningData;\n\n    if (pWav == NULL || framesToWrite == 0 || pData == NULL) {\n        return 0;\n    }\n\n    bytesToWrite = ((framesToWrite * pWav->channels * pWav->bitsPerSample) / 8);\n    if (bytesToWrite > DRWAV_SIZE_MAX) {\n        return 0;\n    }\n\n    bytesWritten = 0;\n    pRunningData = (const drwav_uint8*)pData;\n\n    while (bytesToWrite > 0) {\n        size_t bytesJustWritten;\n        drwav_uint64 bytesToWriteThisIteration;\n\n        bytesToWriteThisIteration = bytesToWrite;\n        DRWAV_ASSERT(bytesToWriteThisIteration <= DRWAV_SIZE_MAX);  /* <-- This is checked above. */\n\n        bytesJustWritten = drwav_write_raw(pWav, (size_t)bytesToWriteThisIteration, pRunningData);\n        if (bytesJustWritten == 0) {\n            break;\n        }\n\n        bytesToWrite -= bytesJustWritten;\n        bytesWritten += bytesJustWritten;\n        pRunningData += bytesJustWritten;\n    }\n\n    return (bytesWritten * 8) / pWav->bitsPerSample / pWav->channels;\n}\n\nDRWAV_API drwav_uint64 drwav_write_pcm_frames_be(drwav* pWav, drwav_uint64 framesToWrite, const void* pData)\n{\n    drwav_uint64 bytesToWrite;\n    drwav_uint64 bytesWritten;\n    drwav_uint32 bytesPerSample;\n    const drwav_uint8* pRunningData;\n\n    if (pWav == NULL || framesToWrite == 0 || pData == NULL) {\n        return 0;\n    }\n\n    bytesToWrite = ((framesToWrite * pWav->channels * pWav->bitsPerSample) / 8);\n    if (bytesToWrite > DRWAV_SIZE_MAX) {\n        return 0;\n    }\n\n    bytesWritten = 0;\n    pRunningData = (const drwav_uint8*)pData;\n\n    bytesPerSample = drwav_get_bytes_per_pcm_frame(pWav) / pWav->channels;\n    \n    while (bytesToWrite > 0) {\n        drwav_uint8 temp[4096];\n        drwav_uint32 sampleCount;\n        size_t bytesJustWritten;\n        drwav_uint64 bytesToWriteThisIteration;\n\n        bytesToWriteThisIteration = bytesToWrite;\n        DRWAV_ASSERT(bytesToWriteThisIteration <= DRWAV_SIZE_MAX);  /* <-- This is checked above. */\n\n        /*\n        WAV files are always little-endian. We need to byte swap on big-endian architectures. Since our input buffer is read-only we need\n        to use an intermediary buffer for the conversion.\n        */\n        sampleCount = sizeof(temp)/bytesPerSample;\n\n        if (bytesToWriteThisIteration > ((drwav_uint64)sampleCount)*bytesPerSample) {\n            bytesToWriteThisIteration = ((drwav_uint64)sampleCount)*bytesPerSample;\n        }\n\n        DRWAV_COPY_MEMORY(temp, pRunningData, (size_t)bytesToWriteThisIteration);\n        drwav__bswap_samples(temp, sampleCount, bytesPerSample, pWav->translatedFormatTag);\n\n        bytesJustWritten = drwav_write_raw(pWav, (size_t)bytesToWriteThisIteration, temp);\n        if (bytesJustWritten == 0) {\n            break;\n        }\n\n        bytesToWrite -= bytesJustWritten;\n        bytesWritten += bytesJustWritten;\n        pRunningData += bytesJustWritten;\n    }\n\n    return (bytesWritten * 8) / pWav->bitsPerSample / pWav->channels;\n}\n\nDRWAV_API drwav_uint64 drwav_write_pcm_frames(drwav* pWav, drwav_uint64 framesToWrite, const void* pData)\n{\n    if (drwav__is_little_endian()) {\n        return drwav_write_pcm_frames_le(pWav, framesToWrite, pData);\n    } else {\n        return drwav_write_pcm_frames_be(pWav, framesToWrite, pData);\n    }\n}\n\n\nstatic drwav_uint64 drwav_read_pcm_frames_s16__msadpcm(drwav* pWav, drwav_uint64 framesToRead, drwav_int16* pBufferOut)\n{\n    drwav_uint64 totalFramesRead = 0;\n\n    DRWAV_ASSERT(pWav != NULL);\n    DRWAV_ASSERT(framesToRead > 0);\n\n    /* TODO: Lots of room for optimization here. */\n\n    while (framesToRead > 0 && pWav->compressed.iCurrentPCMFrame < pWav->totalPCMFrameCount) {\n        /* If there are no cached frames we need to load a new block. */\n        if (pWav->msadpcm.cachedFrameCount == 0 && pWav->msadpcm.bytesRemainingInBlock == 0) {\n            if (pWav->channels == 1) {\n                /* Mono. */\n                drwav_uint8 header[7];\n                if (pWav->onRead(pWav->pUserData, header, sizeof(header)) != sizeof(header)) {\n                    return totalFramesRead;\n                }\n                pWav->msadpcm.bytesRemainingInBlock = pWav->fmt.blockAlign - sizeof(header);\n\n                pWav->msadpcm.predictor[0]     = header[0];\n                pWav->msadpcm.delta[0]         = drwav__bytes_to_s16(header + 1);\n                pWav->msadpcm.prevFrames[0][1] = (drwav_int32)drwav__bytes_to_s16(header + 3);\n                pWav->msadpcm.prevFrames[0][0] = (drwav_int32)drwav__bytes_to_s16(header + 5);\n                pWav->msadpcm.cachedFrames[2]  = pWav->msadpcm.prevFrames[0][0];\n                pWav->msadpcm.cachedFrames[3]  = pWav->msadpcm.prevFrames[0][1];\n                pWav->msadpcm.cachedFrameCount = 2;\n            } else {\n                /* Stereo. */\n                drwav_uint8 header[14];\n                if (pWav->onRead(pWav->pUserData, header, sizeof(header)) != sizeof(header)) {\n                    return totalFramesRead;\n                }\n                pWav->msadpcm.bytesRemainingInBlock = pWav->fmt.blockAlign - sizeof(header);\n\n                pWav->msadpcm.predictor[0] = header[0];\n                pWav->msadpcm.predictor[1] = header[1];\n                pWav->msadpcm.delta[0] = drwav__bytes_to_s16(header + 2);\n                pWav->msadpcm.delta[1] = drwav__bytes_to_s16(header + 4);\n                pWav->msadpcm.prevFrames[0][1] = (drwav_int32)drwav__bytes_to_s16(header + 6);\n                pWav->msadpcm.prevFrames[1][1] = (drwav_int32)drwav__bytes_to_s16(header + 8);\n                pWav->msadpcm.prevFrames[0][0] = (drwav_int32)drwav__bytes_to_s16(header + 10);\n                pWav->msadpcm.prevFrames[1][0] = (drwav_int32)drwav__bytes_to_s16(header + 12);\n\n                pWav->msadpcm.cachedFrames[0] = pWav->msadpcm.prevFrames[0][0];\n                pWav->msadpcm.cachedFrames[1] = pWav->msadpcm.prevFrames[1][0];\n                pWav->msadpcm.cachedFrames[2] = pWav->msadpcm.prevFrames[0][1];\n                pWav->msadpcm.cachedFrames[3] = pWav->msadpcm.prevFrames[1][1];\n                pWav->msadpcm.cachedFrameCount = 2;\n            }\n        }\n\n        /* Output anything that's cached. */\n        while (framesToRead > 0 && pWav->msadpcm.cachedFrameCount > 0 && pWav->compressed.iCurrentPCMFrame < pWav->totalPCMFrameCount) {\n            if (pBufferOut != NULL) {\n                drwav_uint32 iSample = 0;\n                for (iSample = 0; iSample < pWav->channels; iSample += 1) {\n                    pBufferOut[iSample] = (drwav_int16)pWav->msadpcm.cachedFrames[(drwav_countof(pWav->msadpcm.cachedFrames) - (pWav->msadpcm.cachedFrameCount*pWav->channels)) + iSample];\n                }\n\n                pBufferOut += pWav->channels;\n            }\n\n            framesToRead    -= 1;\n            totalFramesRead += 1;\n            pWav->compressed.iCurrentPCMFrame += 1;\n            pWav->msadpcm.cachedFrameCount -= 1;\n        }\n\n        if (framesToRead == 0) {\n            return totalFramesRead;\n        }\n\n\n        /*\n        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\n        loop iteration which will trigger the loading of a new block.\n        */\n        if (pWav->msadpcm.cachedFrameCount == 0) {\n            if (pWav->msadpcm.bytesRemainingInBlock == 0) {\n                continue;\n            } else {\n                static drwav_int32 adaptationTable[] = { \n                    230, 230, 230, 230, 307, 409, 512, 614, \n                    768, 614, 512, 409, 307, 230, 230, 230 \n                };\n                static drwav_int32 coeff1Table[] = { 256, 512, 0, 192, 240, 460,  392 };\n                static drwav_int32 coeff2Table[] = { 0,  -256, 0, 64,  0,  -208, -232 };\n\n                drwav_uint8 nibbles;\n                drwav_int32 nibble0;\n                drwav_int32 nibble1;\n\n                if (pWav->onRead(pWav->pUserData, &nibbles, 1) != 1) {\n                    return totalFramesRead;\n                }\n                pWav->msadpcm.bytesRemainingInBlock -= 1;\n\n                /* TODO: Optimize away these if statements. */\n                nibble0 = ((nibbles & 0xF0) >> 4); if ((nibbles & 0x80)) { nibble0 |= 0xFFFFFFF0UL; }\n                nibble1 = ((nibbles & 0x0F) >> 0); if ((nibbles & 0x08)) { nibble1 |= 0xFFFFFFF0UL; }\n\n                if (pWav->channels == 1) {\n                    /* Mono. */\n                    drwav_int32 newSample0;\n                    drwav_int32 newSample1;\n\n                    newSample0  = ((pWav->msadpcm.prevFrames[0][1] * coeff1Table[pWav->msadpcm.predictor[0]]) + (pWav->msadpcm.prevFrames[0][0] * coeff2Table[pWav->msadpcm.predictor[0]])) >> 8;\n                    newSample0 += nibble0 * pWav->msadpcm.delta[0];\n                    newSample0  = drwav_clamp(newSample0, -32768, 32767);\n\n                    pWav->msadpcm.delta[0] = (adaptationTable[((nibbles & 0xF0) >> 4)] * pWav->msadpcm.delta[0]) >> 8;\n                    if (pWav->msadpcm.delta[0] < 16) {\n                        pWav->msadpcm.delta[0] = 16;\n                    }\n\n                    pWav->msadpcm.prevFrames[0][0] = pWav->msadpcm.prevFrames[0][1];\n                    pWav->msadpcm.prevFrames[0][1] = newSample0;\n\n\n                    newSample1  = ((pWav->msadpcm.prevFrames[0][1] * coeff1Table[pWav->msadpcm.predictor[0]]) + (pWav->msadpcm.prevFrames[0][0] * coeff2Table[pWav->msadpcm.predictor[0]])) >> 8;\n                    newSample1 += nibble1 * pWav->msadpcm.delta[0];\n                    newSample1  = drwav_clamp(newSample1, -32768, 32767);\n\n                    pWav->msadpcm.delta[0] = (adaptationTable[((nibbles & 0x0F) >> 0)] * pWav->msadpcm.delta[0]) >> 8;\n                    if (pWav->msadpcm.delta[0] < 16) {\n                        pWav->msadpcm.delta[0] = 16;\n                    }\n\n                    pWav->msadpcm.prevFrames[0][0] = pWav->msadpcm.prevFrames[0][1];\n                    pWav->msadpcm.prevFrames[0][1] = newSample1;\n\n\n                    pWav->msadpcm.cachedFrames[2] = newSample0;\n                    pWav->msadpcm.cachedFrames[3] = newSample1;\n                    pWav->msadpcm.cachedFrameCount = 2;\n                } else {\n                    /* Stereo. */\n                    drwav_int32 newSample0;\n                    drwav_int32 newSample1;\n\n                    /* Left. */\n                    newSample0  = ((pWav->msadpcm.prevFrames[0][1] * coeff1Table[pWav->msadpcm.predictor[0]]) + (pWav->msadpcm.prevFrames[0][0] * coeff2Table[pWav->msadpcm.predictor[0]])) >> 8;\n                    newSample0 += nibble0 * pWav->msadpcm.delta[0];\n                    newSample0  = drwav_clamp(newSample0, -32768, 32767);\n\n                    pWav->msadpcm.delta[0] = (adaptationTable[((nibbles & 0xF0) >> 4)] * pWav->msadpcm.delta[0]) >> 8;\n                    if (pWav->msadpcm.delta[0] < 16) {\n                        pWav->msadpcm.delta[0] = 16;\n                    }\n\n                    pWav->msadpcm.prevFrames[0][0] = pWav->msadpcm.prevFrames[0][1];\n                    pWav->msadpcm.prevFrames[0][1] = newSample0;\n\n\n                    /* Right. */\n                    newSample1  = ((pWav->msadpcm.prevFrames[1][1] * coeff1Table[pWav->msadpcm.predictor[1]]) + (pWav->msadpcm.prevFrames[1][0] * coeff2Table[pWav->msadpcm.predictor[1]])) >> 8;\n                    newSample1 += nibble1 * pWav->msadpcm.delta[1];\n                    newSample1  = drwav_clamp(newSample1, -32768, 32767);\n\n                    pWav->msadpcm.delta[1] = (adaptationTable[((nibbles & 0x0F) >> 0)] * pWav->msadpcm.delta[1]) >> 8;\n                    if (pWav->msadpcm.delta[1] < 16) {\n                        pWav->msadpcm.delta[1] = 16;\n                    }\n\n                    pWav->msadpcm.prevFrames[1][0] = pWav->msadpcm.prevFrames[1][1];\n                    pWav->msadpcm.prevFrames[1][1] = newSample1;\n\n                    pWav->msadpcm.cachedFrames[2] = newSample0;\n                    pWav->msadpcm.cachedFrames[3] = newSample1;\n                    pWav->msadpcm.cachedFrameCount = 1;\n                }\n            }\n        }\n    }\n\n    return totalFramesRead;\n}\n\n\nstatic drwav_uint64 drwav_read_pcm_frames_s16__ima(drwav* pWav, drwav_uint64 framesToRead, drwav_int16* pBufferOut)\n{\n    drwav_uint64 totalFramesRead = 0;\n    drwav_uint32 iChannel;\n\n    static drwav_int32 indexTable[16] = {\n        -1, -1, -1, -1, 2, 4, 6, 8,\n        -1, -1, -1, -1, 2, 4, 6, 8\n    };\n\n    static drwav_int32 stepTable[89] = {\n        7,     8,     9,     10,    11,    12,    13,    14,    16,    17, \n        19,    21,    23,    25,    28,    31,    34,    37,    41,    45, \n        50,    55,    60,    66,    73,    80,    88,    97,    107,   118, \n        130,   143,   157,   173,   190,   209,   230,   253,   279,   307,\n        337,   371,   408,   449,   494,   544,   598,   658,   724,   796,\n        876,   963,   1060,  1166,  1282,  1411,  1552,  1707,  1878,  2066, \n        2272,  2499,  2749,  3024,  3327,  3660,  4026,  4428,  4871,  5358,\n        5894,  6484,  7132,  7845,  8630,  9493,  10442, 11487, 12635, 13899, \n        15289, 16818, 18500, 20350, 22385, 24623, 27086, 29794, 32767 \n    };\n\n    DRWAV_ASSERT(pWav != NULL);\n    DRWAV_ASSERT(framesToRead > 0);\n\n    /* TODO: Lots of room for optimization here. */\n\n    while (framesToRead > 0 && pWav->compressed.iCurrentPCMFrame < pWav->totalPCMFrameCount) {\n        /* If there are no cached samples we need to load a new block. */\n        if (pWav->ima.cachedFrameCount == 0 && pWav->ima.bytesRemainingInBlock == 0) {\n            if (pWav->channels == 1) {\n                /* Mono. */\n                drwav_uint8 header[4];\n                if (pWav->onRead(pWav->pUserData, header, sizeof(header)) != sizeof(header)) {\n                    return totalFramesRead;\n                }\n                pWav->ima.bytesRemainingInBlock = pWav->fmt.blockAlign - sizeof(header);\n\n                if (header[2] >= drwav_countof(stepTable)) {\n                    pWav->onSeek(pWav->pUserData, pWav->ima.bytesRemainingInBlock, drwav_seek_origin_current);\n                    pWav->ima.bytesRemainingInBlock = 0;\n                    return totalFramesRead; /* Invalid data. */\n                }\n\n                pWav->ima.predictor[0] = drwav__bytes_to_s16(header + 0);\n                pWav->ima.stepIndex[0] = header[2];\n                pWav->ima.cachedFrames[drwav_countof(pWav->ima.cachedFrames) - 1] = pWav->ima.predictor[0];\n                pWav->ima.cachedFrameCount = 1;\n            } else {\n                /* Stereo. */\n                drwav_uint8 header[8];\n                if (pWav->onRead(pWav->pUserData, header, sizeof(header)) != sizeof(header)) {\n                    return totalFramesRead;\n                }\n                pWav->ima.bytesRemainingInBlock = pWav->fmt.blockAlign - sizeof(header);\n\n                if (header[2] >= drwav_countof(stepTable) || header[6] >= drwav_countof(stepTable)) {\n                    pWav->onSeek(pWav->pUserData, pWav->ima.bytesRemainingInBlock, drwav_seek_origin_current);\n                    pWav->ima.bytesRemainingInBlock = 0;\n                    return totalFramesRead; /* Invalid data. */\n                }\n\n                pWav->ima.predictor[0] = drwav__bytes_to_s16(header + 0);\n                pWav->ima.stepIndex[0] = header[2];\n                pWav->ima.predictor[1] = drwav__bytes_to_s16(header + 4);\n                pWav->ima.stepIndex[1] = header[6];\n\n                pWav->ima.cachedFrames[drwav_countof(pWav->ima.cachedFrames) - 2] = pWav->ima.predictor[0];\n                pWav->ima.cachedFrames[drwav_countof(pWav->ima.cachedFrames) - 1] = pWav->ima.predictor[1];\n                pWav->ima.cachedFrameCount = 1;\n            }\n        }\n\n        /* Output anything that's cached. */\n        while (framesToRead > 0 && pWav->ima.cachedFrameCount > 0 && pWav->compressed.iCurrentPCMFrame < pWav->totalPCMFrameCount) {\n            if (pBufferOut != NULL) {\n                drwav_uint32 iSample;\n                for (iSample = 0; iSample < pWav->channels; iSample += 1) {\n                    pBufferOut[iSample] = (drwav_int16)pWav->ima.cachedFrames[(drwav_countof(pWav->ima.cachedFrames) - (pWav->ima.cachedFrameCount*pWav->channels)) + iSample];\n                }\n                pBufferOut += pWav->channels;\n            }\n\n            framesToRead    -= 1;\n            totalFramesRead += 1;\n            pWav->compressed.iCurrentPCMFrame += 1;\n            pWav->ima.cachedFrameCount -= 1;\n        }\n\n        if (framesToRead == 0) {\n            return totalFramesRead;\n        }\n\n        /*\n        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\n        loop iteration which will trigger the loading of a new block.\n        */\n        if (pWav->ima.cachedFrameCount == 0) {\n            if (pWav->ima.bytesRemainingInBlock == 0) {\n                continue;\n            } else {\n                /*\n                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\n                left channel, 4 bytes for the right channel.\n                */\n                pWav->ima.cachedFrameCount = 8;\n                for (iChannel = 0; iChannel < pWav->channels; ++iChannel) {\n                    drwav_uint32 iByte;\n                    drwav_uint8 nibbles[4];\n                    if (pWav->onRead(pWav->pUserData, &nibbles, 4) != 4) {\n                        pWav->ima.cachedFrameCount = 0;\n                        return totalFramesRead;\n                    }\n                    pWav->ima.bytesRemainingInBlock -= 4;\n\n                    for (iByte = 0; iByte < 4; ++iByte) {\n                        drwav_uint8 nibble0 = ((nibbles[iByte] & 0x0F) >> 0);\n                        drwav_uint8 nibble1 = ((nibbles[iByte] & 0xF0) >> 4);\n\n                        drwav_int32 step      = stepTable[pWav->ima.stepIndex[iChannel]];\n                        drwav_int32 predictor = pWav->ima.predictor[iChannel];\n\n                        drwav_int32      diff  = step >> 3;\n                        if (nibble0 & 1) diff += step >> 2;\n                        if (nibble0 & 2) diff += step >> 1;\n                        if (nibble0 & 4) diff += step;\n                        if (nibble0 & 8) diff  = -diff;\n\n                        predictor = drwav_clamp(predictor + diff, -32768, 32767);\n                        pWav->ima.predictor[iChannel] = predictor;\n                        pWav->ima.stepIndex[iChannel] = drwav_clamp(pWav->ima.stepIndex[iChannel] + indexTable[nibble0], 0, (drwav_int32)drwav_countof(stepTable)-1);\n                        pWav->ima.cachedFrames[(drwav_countof(pWav->ima.cachedFrames) - (pWav->ima.cachedFrameCount*pWav->channels)) + (iByte*2+0)*pWav->channels + iChannel] = predictor;\n\n\n                        step      = stepTable[pWav->ima.stepIndex[iChannel]];\n                        predictor = pWav->ima.predictor[iChannel];\n\n                                         diff  = step >> 3;\n                        if (nibble1 & 1) diff += step >> 2;\n                        if (nibble1 & 2) diff += step >> 1;\n                        if (nibble1 & 4) diff += step;\n                        if (nibble1 & 8) diff  = -diff;\n\n                        predictor = drwav_clamp(predictor + diff, -32768, 32767);\n                        pWav->ima.predictor[iChannel] = predictor;\n                        pWav->ima.stepIndex[iChannel] = drwav_clamp(pWav->ima.stepIndex[iChannel] + indexTable[nibble1], 0, (drwav_int32)drwav_countof(stepTable)-1);\n                        pWav->ima.cachedFrames[(drwav_countof(pWav->ima.cachedFrames) - (pWav->ima.cachedFrameCount*pWav->channels)) + (iByte*2+1)*pWav->channels + iChannel] = predictor;\n                    }\n                }\n            }\n        }\n    }\n\n    return totalFramesRead;\n}\n\n\n#ifndef DR_WAV_NO_CONVERSION_API\nstatic unsigned short g_drwavAlawTable[256] = {\n    0xEA80, 0xEB80, 0xE880, 0xE980, 0xEE80, 0xEF80, 0xEC80, 0xED80, 0xE280, 0xE380, 0xE080, 0xE180, 0xE680, 0xE780, 0xE480, 0xE580, \n    0xF540, 0xF5C0, 0xF440, 0xF4C0, 0xF740, 0xF7C0, 0xF640, 0xF6C0, 0xF140, 0xF1C0, 0xF040, 0xF0C0, 0xF340, 0xF3C0, 0xF240, 0xF2C0, \n    0xAA00, 0xAE00, 0xA200, 0xA600, 0xBA00, 0xBE00, 0xB200, 0xB600, 0x8A00, 0x8E00, 0x8200, 0x8600, 0x9A00, 0x9E00, 0x9200, 0x9600, \n    0xD500, 0xD700, 0xD100, 0xD300, 0xDD00, 0xDF00, 0xD900, 0xDB00, 0xC500, 0xC700, 0xC100, 0xC300, 0xCD00, 0xCF00, 0xC900, 0xCB00, \n    0xFEA8, 0xFEB8, 0xFE88, 0xFE98, 0xFEE8, 0xFEF8, 0xFEC8, 0xFED8, 0xFE28, 0xFE38, 0xFE08, 0xFE18, 0xFE68, 0xFE78, 0xFE48, 0xFE58, \n    0xFFA8, 0xFFB8, 0xFF88, 0xFF98, 0xFFE8, 0xFFF8, 0xFFC8, 0xFFD8, 0xFF28, 0xFF38, 0xFF08, 0xFF18, 0xFF68, 0xFF78, 0xFF48, 0xFF58, \n    0xFAA0, 0xFAE0, 0xFA20, 0xFA60, 0xFBA0, 0xFBE0, 0xFB20, 0xFB60, 0xF8A0, 0xF8E0, 0xF820, 0xF860, 0xF9A0, 0xF9E0, 0xF920, 0xF960, \n    0xFD50, 0xFD70, 0xFD10, 0xFD30, 0xFDD0, 0xFDF0, 0xFD90, 0xFDB0, 0xFC50, 0xFC70, 0xFC10, 0xFC30, 0xFCD0, 0xFCF0, 0xFC90, 0xFCB0, \n    0x1580, 0x1480, 0x1780, 0x1680, 0x1180, 0x1080, 0x1380, 0x1280, 0x1D80, 0x1C80, 0x1F80, 0x1E80, 0x1980, 0x1880, 0x1B80, 0x1A80, \n    0x0AC0, 0x0A40, 0x0BC0, 0x0B40, 0x08C0, 0x0840, 0x09C0, 0x0940, 0x0EC0, 0x0E40, 0x0FC0, 0x0F40, 0x0CC0, 0x0C40, 0x0DC0, 0x0D40, \n    0x5600, 0x5200, 0x5E00, 0x5A00, 0x4600, 0x4200, 0x4E00, 0x4A00, 0x7600, 0x7200, 0x7E00, 0x7A00, 0x6600, 0x6200, 0x6E00, 0x6A00, \n    0x2B00, 0x2900, 0x2F00, 0x2D00, 0x2300, 0x2100, 0x2700, 0x2500, 0x3B00, 0x3900, 0x3F00, 0x3D00, 0x3300, 0x3100, 0x3700, 0x3500, \n    0x0158, 0x0148, 0x0178, 0x0168, 0x0118, 0x0108, 0x0138, 0x0128, 0x01D8, 0x01C8, 0x01F8, 0x01E8, 0x0198, 0x0188, 0x01B8, 0x01A8, \n    0x0058, 0x0048, 0x0078, 0x0068, 0x0018, 0x0008, 0x0038, 0x0028, 0x00D8, 0x00C8, 0x00F8, 0x00E8, 0x0098, 0x0088, 0x00B8, 0x00A8, \n    0x0560, 0x0520, 0x05E0, 0x05A0, 0x0460, 0x0420, 0x04E0, 0x04A0, 0x0760, 0x0720, 0x07E0, 0x07A0, 0x0660, 0x0620, 0x06E0, 0x06A0, \n    0x02B0, 0x0290, 0x02F0, 0x02D0, 0x0230, 0x0210, 0x0270, 0x0250, 0x03B0, 0x0390, 0x03F0, 0x03D0, 0x0330, 0x0310, 0x0370, 0x0350\n};\n\nstatic unsigned short g_drwavMulawTable[256] = {\n    0x8284, 0x8684, 0x8A84, 0x8E84, 0x9284, 0x9684, 0x9A84, 0x9E84, 0xA284, 0xA684, 0xAA84, 0xAE84, 0xB284, 0xB684, 0xBA84, 0xBE84, \n    0xC184, 0xC384, 0xC584, 0xC784, 0xC984, 0xCB84, 0xCD84, 0xCF84, 0xD184, 0xD384, 0xD584, 0xD784, 0xD984, 0xDB84, 0xDD84, 0xDF84, \n    0xE104, 0xE204, 0xE304, 0xE404, 0xE504, 0xE604, 0xE704, 0xE804, 0xE904, 0xEA04, 0xEB04, 0xEC04, 0xED04, 0xEE04, 0xEF04, 0xF004, \n    0xF0C4, 0xF144, 0xF1C4, 0xF244, 0xF2C4, 0xF344, 0xF3C4, 0xF444, 0xF4C4, 0xF544, 0xF5C4, 0xF644, 0xF6C4, 0xF744, 0xF7C4, 0xF844, \n    0xF8A4, 0xF8E4, 0xF924, 0xF964, 0xF9A4, 0xF9E4, 0xFA24, 0xFA64, 0xFAA4, 0xFAE4, 0xFB24, 0xFB64, 0xFBA4, 0xFBE4, 0xFC24, 0xFC64, \n    0xFC94, 0xFCB4, 0xFCD4, 0xFCF4, 0xFD14, 0xFD34, 0xFD54, 0xFD74, 0xFD94, 0xFDB4, 0xFDD4, 0xFDF4, 0xFE14, 0xFE34, 0xFE54, 0xFE74, \n    0xFE8C, 0xFE9C, 0xFEAC, 0xFEBC, 0xFECC, 0xFEDC, 0xFEEC, 0xFEFC, 0xFF0C, 0xFF1C, 0xFF2C, 0xFF3C, 0xFF4C, 0xFF5C, 0xFF6C, 0xFF7C, \n    0xFF88, 0xFF90, 0xFF98, 0xFFA0, 0xFFA8, 0xFFB0, 0xFFB8, 0xFFC0, 0xFFC8, 0xFFD0, 0xFFD8, 0xFFE0, 0xFFE8, 0xFFF0, 0xFFF8, 0x0000, \n    0x7D7C, 0x797C, 0x757C, 0x717C, 0x6D7C, 0x697C, 0x657C, 0x617C, 0x5D7C, 0x597C, 0x557C, 0x517C, 0x4D7C, 0x497C, 0x457C, 0x417C, \n    0x3E7C, 0x3C7C, 0x3A7C, 0x387C, 0x367C, 0x347C, 0x327C, 0x307C, 0x2E7C, 0x2C7C, 0x2A7C, 0x287C, 0x267C, 0x247C, 0x227C, 0x207C, \n    0x1EFC, 0x1DFC, 0x1CFC, 0x1BFC, 0x1AFC, 0x19FC, 0x18FC, 0x17FC, 0x16FC, 0x15FC, 0x14FC, 0x13FC, 0x12FC, 0x11FC, 0x10FC, 0x0FFC, \n    0x0F3C, 0x0EBC, 0x0E3C, 0x0DBC, 0x0D3C, 0x0CBC, 0x0C3C, 0x0BBC, 0x0B3C, 0x0ABC, 0x0A3C, 0x09BC, 0x093C, 0x08BC, 0x083C, 0x07BC, \n    0x075C, 0x071C, 0x06DC, 0x069C, 0x065C, 0x061C, 0x05DC, 0x059C, 0x055C, 0x051C, 0x04DC, 0x049C, 0x045C, 0x041C, 0x03DC, 0x039C, \n    0x036C, 0x034C, 0x032C, 0x030C, 0x02EC, 0x02CC, 0x02AC, 0x028C, 0x026C, 0x024C, 0x022C, 0x020C, 0x01EC, 0x01CC, 0x01AC, 0x018C, \n    0x0174, 0x0164, 0x0154, 0x0144, 0x0134, 0x0124, 0x0114, 0x0104, 0x00F4, 0x00E4, 0x00D4, 0x00C4, 0x00B4, 0x00A4, 0x0094, 0x0084, \n    0x0078, 0x0070, 0x0068, 0x0060, 0x0058, 0x0050, 0x0048, 0x0040, 0x0038, 0x0030, 0x0028, 0x0020, 0x0018, 0x0010, 0x0008, 0x0000\n};\n\nstatic DRWAV_INLINE drwav_int16 drwav__alaw_to_s16(drwav_uint8 sampleIn)\n{\n    return (short)g_drwavAlawTable[sampleIn];\n}\n\nstatic DRWAV_INLINE drwav_int16 drwav__mulaw_to_s16(drwav_uint8 sampleIn)\n{\n    return (short)g_drwavMulawTable[sampleIn];\n}\n\n\n\nstatic void drwav__pcm_to_s16(drwav_int16* pOut, const drwav_uint8* pIn, size_t totalSampleCount, unsigned int bytesPerSample)\n{\n    unsigned int i;\n\n    /* Special case for 8-bit sample data because it's treated as unsigned. */\n    if (bytesPerSample == 1) {\n        drwav_u8_to_s16(pOut, pIn, totalSampleCount);\n        return;\n    }\n\n\n    /* Slightly more optimal implementation for common formats. */\n    if (bytesPerSample == 2) {\n        for (i = 0; i < totalSampleCount; ++i) {\n           *pOut++ = ((const drwav_int16*)pIn)[i];\n        }\n        return;\n    }\n    if (bytesPerSample == 3) {\n        drwav_s24_to_s16(pOut, pIn, totalSampleCount);\n        return;\n    }\n    if (bytesPerSample == 4) {\n        drwav_s32_to_s16(pOut, (const drwav_int32*)pIn, totalSampleCount);\n        return;\n    }\n\n\n    /* Anything more than 64 bits per sample is not supported. */\n    if (bytesPerSample > 8) {\n        DRWAV_ZERO_MEMORY(pOut, totalSampleCount * sizeof(*pOut));\n        return;\n    }\n\n\n    /* Generic, slow converter. */\n    for (i = 0; i < totalSampleCount; ++i) {\n        drwav_uint64 sample = 0;\n        unsigned int shift  = (8 - bytesPerSample) * 8;\n\n        unsigned int j;\n        for (j = 0; j < bytesPerSample; j += 1) {\n            DRWAV_ASSERT(j < 8);\n            sample |= (drwav_uint64)(pIn[j]) << shift;\n            shift  += 8;\n        }\n\n        pIn += j;\n        *pOut++ = (drwav_int16)((drwav_int64)sample >> 48);\n    }\n}\n\nstatic void drwav__ieee_to_s16(drwav_int16* pOut, const drwav_uint8* pIn, size_t totalSampleCount, unsigned int bytesPerSample)\n{\n    if (bytesPerSample == 4) {\n        drwav_f32_to_s16(pOut, (const float*)pIn, totalSampleCount);\n        return;\n    } else if (bytesPerSample == 8) {\n        drwav_f64_to_s16(pOut, (const double*)pIn, totalSampleCount);\n        return;\n    } else {\n        /* Only supporting 32- and 64-bit float. Output silence in all other cases. Contributions welcome for 16-bit float. */\n        DRWAV_ZERO_MEMORY(pOut, totalSampleCount * sizeof(*pOut));\n        return;\n    }\n}\n\nstatic drwav_uint64 drwav_read_pcm_frames_s16__pcm(drwav* pWav, drwav_uint64 framesToRead, drwav_int16* pBufferOut)\n{\n    drwav_uint32 bytesPerFrame;\n    drwav_uint64 totalFramesRead;\n    drwav_uint8 sampleData[4096];\n\n    /* Fast path. */\n    if ((pWav->translatedFormatTag == DR_WAVE_FORMAT_PCM && pWav->bitsPerSample == 16) || pBufferOut == NULL) {\n        return drwav_read_pcm_frames(pWav, framesToRead, pBufferOut);\n    }\n    \n    bytesPerFrame = drwav_get_bytes_per_pcm_frame(pWav);\n    if (bytesPerFrame == 0) {\n        return 0;\n    }\n\n    totalFramesRead = 0;\n    \n    while (framesToRead > 0) {\n        drwav_uint64 framesRead = drwav_read_pcm_frames(pWav, drwav_min(framesToRead, sizeof(sampleData)/bytesPerFrame), sampleData);\n        if (framesRead == 0) {\n            break;\n        }\n\n        drwav__pcm_to_s16(pBufferOut, sampleData, (size_t)(framesRead*pWav->channels), bytesPerFrame/pWav->channels);\n\n        pBufferOut      += framesRead*pWav->channels;\n        framesToRead    -= framesRead;\n        totalFramesRead += framesRead;\n    }\n\n    return totalFramesRead;\n}\n\nstatic drwav_uint64 drwav_read_pcm_frames_s16__ieee(drwav* pWav, drwav_uint64 framesToRead, drwav_int16* pBufferOut)\n{\n    drwav_uint64 totalFramesRead;\n    drwav_uint8 sampleData[4096];\n    drwav_uint32 bytesPerFrame;\n\n    if (pBufferOut == NULL) {\n        return drwav_read_pcm_frames(pWav, framesToRead, NULL);\n    }\n\n    bytesPerFrame = drwav_get_bytes_per_pcm_frame(pWav);\n    if (bytesPerFrame == 0) {\n        return 0;\n    }\n\n    totalFramesRead = 0;\n    \n    while (framesToRead > 0) {\n        drwav_uint64 framesRead = drwav_read_pcm_frames(pWav, drwav_min(framesToRead, sizeof(sampleData)/bytesPerFrame), sampleData);\n        if (framesRead == 0) {\n            break;\n        }\n\n        drwav__ieee_to_s16(pBufferOut, sampleData, (size_t)(framesRead*pWav->channels), bytesPerFrame/pWav->channels);\n\n        pBufferOut      += framesRead*pWav->channels;\n        framesToRead    -= framesRead;\n        totalFramesRead += framesRead;\n    }\n\n    return totalFramesRead;\n}\n\nstatic drwav_uint64 drwav_read_pcm_frames_s16__alaw(drwav* pWav, drwav_uint64 framesToRead, drwav_int16* pBufferOut)\n{\n    drwav_uint64 totalFramesRead;\n    drwav_uint8 sampleData[4096];\n    drwav_uint32 bytesPerFrame;\n\n    if (pBufferOut == NULL) {\n        return drwav_read_pcm_frames(pWav, framesToRead, NULL);\n    }\n\n    bytesPerFrame = drwav_get_bytes_per_pcm_frame(pWav);\n    if (bytesPerFrame == 0) {\n        return 0;\n    }\n\n    totalFramesRead = 0;\n    \n    while (framesToRead > 0) {\n        drwav_uint64 framesRead = drwav_read_pcm_frames(pWav, drwav_min(framesToRead, sizeof(sampleData)/bytesPerFrame), sampleData);\n        if (framesRead == 0) {\n            break;\n        }\n\n        drwav_alaw_to_s16(pBufferOut, sampleData, (size_t)(framesRead*pWav->channels));\n\n        pBufferOut      += framesRead*pWav->channels;\n        framesToRead    -= framesRead;\n        totalFramesRead += framesRead;\n    }\n\n    return totalFramesRead;\n}\n\nstatic drwav_uint64 drwav_read_pcm_frames_s16__mulaw(drwav* pWav, drwav_uint64 framesToRead, drwav_int16* pBufferOut)\n{\n    drwav_uint64 totalFramesRead;\n    drwav_uint8 sampleData[4096];\n    drwav_uint32 bytesPerFrame;\n\n    if (pBufferOut == NULL) {\n        return drwav_read_pcm_frames(pWav, framesToRead, NULL);\n    }\n\n    bytesPerFrame = drwav_get_bytes_per_pcm_frame(pWav);\n    if (bytesPerFrame == 0) {\n        return 0;\n    }\n\n    totalFramesRead = 0;\n\n    while (framesToRead > 0) {\n        drwav_uint64 framesRead = drwav_read_pcm_frames(pWav, drwav_min(framesToRead, sizeof(sampleData)/bytesPerFrame), sampleData);\n        if (framesRead == 0) {\n            break;\n        }\n\n        drwav_mulaw_to_s16(pBufferOut, sampleData, (size_t)(framesRead*pWav->channels));\n\n        pBufferOut      += framesRead*pWav->channels;\n        framesToRead    -= framesRead;\n        totalFramesRead += framesRead;\n    }\n\n    return totalFramesRead;\n}\n\nDRWAV_API drwav_uint64 drwav_read_pcm_frames_s16(drwav* pWav, drwav_uint64 framesToRead, drwav_int16* pBufferOut)\n{\n    if (pWav == NULL || framesToRead == 0) {\n        return 0;\n    }\n\n    if (pBufferOut == NULL) {\n        return drwav_read_pcm_frames(pWav, framesToRead, NULL);\n    }\n\n    /* Don't try to read more samples than can potentially fit in the output buffer. */\n    if (framesToRead * pWav->channels * sizeof(drwav_int16) > DRWAV_SIZE_MAX) {\n        framesToRead = DRWAV_SIZE_MAX / sizeof(drwav_int16) / pWav->channels;\n    }\n\n    if (pWav->translatedFormatTag == DR_WAVE_FORMAT_PCM) {\n        return drwav_read_pcm_frames_s16__pcm(pWav, framesToRead, pBufferOut);\n    }\n\n    if (pWav->translatedFormatTag == DR_WAVE_FORMAT_IEEE_FLOAT) {\n        return drwav_read_pcm_frames_s16__ieee(pWav, framesToRead, pBufferOut);\n    }\n\n    if (pWav->translatedFormatTag == DR_WAVE_FORMAT_ALAW) {\n        return drwav_read_pcm_frames_s16__alaw(pWav, framesToRead, pBufferOut);\n    }\n\n    if (pWav->translatedFormatTag == DR_WAVE_FORMAT_MULAW) {\n        return drwav_read_pcm_frames_s16__mulaw(pWav, framesToRead, pBufferOut);\n    }\n\n    if (pWav->translatedFormatTag == DR_WAVE_FORMAT_ADPCM) {\n        return drwav_read_pcm_frames_s16__msadpcm(pWav, framesToRead, pBufferOut);\n    }\n\n    if (pWav->translatedFormatTag == DR_WAVE_FORMAT_DVI_ADPCM) {\n        return drwav_read_pcm_frames_s16__ima(pWav, framesToRead, pBufferOut);\n    }\n\n    return 0;\n}\n\nDRWAV_API drwav_uint64 drwav_read_pcm_frames_s16le(drwav* pWav, drwav_uint64 framesToRead, drwav_int16* pBufferOut)\n{\n    drwav_uint64 framesRead = drwav_read_pcm_frames_s16(pWav, framesToRead, pBufferOut);\n    if (pBufferOut != NULL && drwav__is_little_endian() == DRWAV_FALSE) {\n        drwav__bswap_samples_s16(pBufferOut, framesRead*pWav->channels);\n    }\n\n    return framesRead;\n}\n\nDRWAV_API drwav_uint64 drwav_read_pcm_frames_s16be(drwav* pWav, drwav_uint64 framesToRead, drwav_int16* pBufferOut)\n{\n    drwav_uint64 framesRead = drwav_read_pcm_frames_s16(pWav, framesToRead, pBufferOut);\n    if (pBufferOut != NULL && drwav__is_little_endian() == DRWAV_TRUE) {\n        drwav__bswap_samples_s16(pBufferOut, framesRead*pWav->channels);\n    }\n\n    return framesRead;\n}\n\n\nDRWAV_API void drwav_u8_to_s16(drwav_int16* pOut, const drwav_uint8* pIn, size_t sampleCount)\n{\n    int r;\n    size_t i;\n    for (i = 0; i < sampleCount; ++i) {\n        int x = pIn[i];\n        r = x << 8;\n        r = r - 32768;\n        pOut[i] = (short)r;\n    }\n}\n\nDRWAV_API void drwav_s24_to_s16(drwav_int16* pOut, const drwav_uint8* pIn, size_t sampleCount)\n{\n    int r;\n    size_t i;\n    for (i = 0; i < sampleCount; ++i) {\n        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;\n        r = x >> 8;\n        pOut[i] = (short)r;\n    }\n}\n\nDRWAV_API void drwav_s32_to_s16(drwav_int16* pOut, const drwav_int32* pIn, size_t sampleCount)\n{\n    int r;\n    size_t i;\n    for (i = 0; i < sampleCount; ++i) {\n        int x = pIn[i];\n        r = x >> 16;\n        pOut[i] = (short)r;\n    }\n}\n\nDRWAV_API void drwav_f32_to_s16(drwav_int16* pOut, const float* pIn, size_t sampleCount)\n{\n    int r;\n    size_t i;\n    for (i = 0; i < sampleCount; ++i) {\n        float x = pIn[i];\n        float c;\n        c = ((x < -1) ? -1 : ((x > 1) ? 1 : x));\n        c = c + 1;\n        r = (int)(c * 32767.5f);\n        r = r - 32768;\n        pOut[i] = (short)r;\n    }\n}\n\nDRWAV_API void drwav_f64_to_s16(drwav_int16* pOut, const double* pIn, size_t sampleCount)\n{\n    int r;\n    size_t i;\n    for (i = 0; i < sampleCount; ++i) {\n        double x = pIn[i];\n        double c;\n        c = ((x < -1) ? -1 : ((x > 1) ? 1 : x));\n        c = c + 1;\n        r = (int)(c * 32767.5);\n        r = r - 32768;\n        pOut[i] = (short)r;\n    }\n}\n\nDRWAV_API void drwav_alaw_to_s16(drwav_int16* pOut, const drwav_uint8* pIn, size_t sampleCount)\n{\n    size_t i;\n    for (i = 0; i < sampleCount; ++i) {\n        pOut[i] = drwav__alaw_to_s16(pIn[i]);\n    }\n}\n\nDRWAV_API void drwav_mulaw_to_s16(drwav_int16* pOut, const drwav_uint8* pIn, size_t sampleCount)\n{\n    size_t i;\n    for (i = 0; i < sampleCount; ++i) {\n        pOut[i] = drwav__mulaw_to_s16(pIn[i]);\n    }\n}\n\n\n\nstatic void drwav__pcm_to_f32(float* pOut, const drwav_uint8* pIn, size_t sampleCount, unsigned int bytesPerSample)\n{\n    unsigned int i;\n\n    /* Special case for 8-bit sample data because it's treated as unsigned. */\n    if (bytesPerSample == 1) {\n        drwav_u8_to_f32(pOut, pIn, sampleCount);\n        return;\n    }\n\n    /* Slightly more optimal implementation for common formats. */\n    if (bytesPerSample == 2) {\n        drwav_s16_to_f32(pOut, (const drwav_int16*)pIn, sampleCount);\n        return;\n    }\n    if (bytesPerSample == 3) {\n        drwav_s24_to_f32(pOut, pIn, sampleCount);\n        return;\n    }\n    if (bytesPerSample == 4) {\n        drwav_s32_to_f32(pOut, (const drwav_int32*)pIn, sampleCount);\n        return;\n    }\n\n\n    /* Anything more than 64 bits per sample is not supported. */\n    if (bytesPerSample > 8) {\n        DRWAV_ZERO_MEMORY(pOut, sampleCount * sizeof(*pOut));\n        return;\n    }\n\n\n    /* Generic, slow converter. */\n    for (i = 0; i < sampleCount; ++i) {\n        drwav_uint64 sample = 0;\n        unsigned int shift  = (8 - bytesPerSample) * 8;\n\n        unsigned int j;\n        for (j = 0; j < bytesPerSample; j += 1) {\n            DRWAV_ASSERT(j < 8);\n            sample |= (drwav_uint64)(pIn[j]) << shift;\n            shift  += 8;\n        }\n\n        pIn += j;\n        *pOut++ = (float)((drwav_int64)sample / 9223372036854775807.0);\n    }\n}\n\nstatic void drwav__ieee_to_f32(float* pOut, const drwav_uint8* pIn, size_t sampleCount, unsigned int bytesPerSample)\n{\n    if (bytesPerSample == 4) {\n        unsigned int i;\n        for (i = 0; i < sampleCount; ++i) {\n            *pOut++ = ((const float*)pIn)[i];\n        }\n        return;\n    } else if (bytesPerSample == 8) {\n        drwav_f64_to_f32(pOut, (const double*)pIn, sampleCount);\n        return;\n    } else {\n        /* Only supporting 32- and 64-bit float. Output silence in all other cases. Contributions welcome for 16-bit float. */\n        DRWAV_ZERO_MEMORY(pOut, sampleCount * sizeof(*pOut));\n        return;\n    }\n}\n\n\nstatic drwav_uint64 drwav_read_pcm_frames_f32__pcm(drwav* pWav, drwav_uint64 framesToRead, float* pBufferOut)\n{\n    drwav_uint64 totalFramesRead;\n    drwav_uint8 sampleData[4096];\n\n    drwav_uint32 bytesPerFrame = drwav_get_bytes_per_pcm_frame(pWav);\n    if (bytesPerFrame == 0) {\n        return 0;\n    }\n\n    totalFramesRead = 0;\n\n    while (framesToRead > 0) {\n        drwav_uint64 framesRead = drwav_read_pcm_frames(pWav, drwav_min(framesToRead, sizeof(sampleData)/bytesPerFrame), sampleData);\n        if (framesRead == 0) {\n            break;\n        }\n\n        drwav__pcm_to_f32(pBufferOut, sampleData, (size_t)framesRead*pWav->channels, bytesPerFrame/pWav->channels);\n\n        pBufferOut      += framesRead*pWav->channels;\n        framesToRead    -= framesRead;\n        totalFramesRead += framesRead;\n    }\n\n    return totalFramesRead;\n}\n\nstatic drwav_uint64 drwav_read_pcm_frames_f32__msadpcm(drwav* pWav, drwav_uint64 framesToRead, float* pBufferOut)\n{\n    /*\n    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\n    want to duplicate that code.\n    */\n    drwav_uint64 totalFramesRead = 0;\n    drwav_int16 samples16[2048];\n    while (framesToRead > 0) {\n        drwav_uint64 framesRead = drwav_read_pcm_frames_s16(pWav, drwav_min(framesToRead, drwav_countof(samples16)/pWav->channels), samples16);\n        if (framesRead == 0) {\n            break;\n        }\n\n        drwav_s16_to_f32(pBufferOut, samples16, (size_t)(framesRead*pWav->channels));   /* <-- Safe cast because we're clamping to 2048. */\n\n        pBufferOut      += framesRead*pWav->channels;\n        framesToRead    -= framesRead;\n        totalFramesRead += framesRead;\n    }\n\n    return totalFramesRead;\n}\n\nstatic drwav_uint64 drwav_read_pcm_frames_f32__ima(drwav* pWav, drwav_uint64 framesToRead, float* pBufferOut)\n{\n    /*\n    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\n    want to duplicate that code.\n    */\n    drwav_uint64 totalFramesRead = 0;\n    drwav_int16 samples16[2048];\n    while (framesToRead > 0) {\n        drwav_uint64 framesRead = drwav_read_pcm_frames_s16(pWav, drwav_min(framesToRead, drwav_countof(samples16)/pWav->channels), samples16);\n        if (framesRead == 0) {\n            break;\n        }\n\n        drwav_s16_to_f32(pBufferOut, samples16, (size_t)(framesRead*pWav->channels));   /* <-- Safe cast because we're clamping to 2048. */\n\n        pBufferOut      += framesRead*pWav->channels;\n        framesToRead    -= framesRead;\n        totalFramesRead += framesRead;\n    }\n\n    return totalFramesRead;\n}\n\nstatic drwav_uint64 drwav_read_pcm_frames_f32__ieee(drwav* pWav, drwav_uint64 framesToRead, float* pBufferOut)\n{\n    drwav_uint64 totalFramesRead;\n    drwav_uint8 sampleData[4096];\n    drwav_uint32 bytesPerFrame;\n\n    /* Fast path. */\n    if (pWav->translatedFormatTag == DR_WAVE_FORMAT_IEEE_FLOAT && pWav->bitsPerSample == 32) {\n        return drwav_read_pcm_frames(pWav, framesToRead, pBufferOut);\n    }\n    \n    bytesPerFrame = drwav_get_bytes_per_pcm_frame(pWav);\n    if (bytesPerFrame == 0) {\n        return 0;\n    }\n\n    totalFramesRead = 0;\n\n    while (framesToRead > 0) {\n        drwav_uint64 framesRead = drwav_read_pcm_frames(pWav, drwav_min(framesToRead, sizeof(sampleData)/bytesPerFrame), sampleData);\n        if (framesRead == 0) {\n            break;\n        }\n\n        drwav__ieee_to_f32(pBufferOut, sampleData, (size_t)(framesRead*pWav->channels), bytesPerFrame/pWav->channels);\n\n        pBufferOut      += framesRead*pWav->channels;\n        framesToRead    -= framesRead;\n        totalFramesRead += framesRead;\n    }\n\n    return totalFramesRead;\n}\n\nstatic drwav_uint64 drwav_read_pcm_frames_f32__alaw(drwav* pWav, drwav_uint64 framesToRead, float* pBufferOut)\n{\n    drwav_uint64 totalFramesRead;\n    drwav_uint8 sampleData[4096];\n    drwav_uint32 bytesPerFrame = drwav_get_bytes_per_pcm_frame(pWav);\n    if (bytesPerFrame == 0) {\n        return 0;\n    }\n\n    totalFramesRead = 0;\n\n    while (framesToRead > 0) {\n        drwav_uint64 framesRead = drwav_read_pcm_frames(pWav, drwav_min(framesToRead, sizeof(sampleData)/bytesPerFrame), sampleData);\n        if (framesRead == 0) {\n            break;\n        }\n\n        drwav_alaw_to_f32(pBufferOut, sampleData, (size_t)(framesRead*pWav->channels));\n\n        pBufferOut      += framesRead*pWav->channels;\n        framesToRead    -= framesRead;\n        totalFramesRead += framesRead;\n    }\n\n    return totalFramesRead;\n}\n\nstatic drwav_uint64 drwav_read_pcm_frames_f32__mulaw(drwav* pWav, drwav_uint64 framesToRead, float* pBufferOut)\n{\n    drwav_uint64 totalFramesRead;\n    drwav_uint8 sampleData[4096];\n\n    drwav_uint32 bytesPerFrame = drwav_get_bytes_per_pcm_frame(pWav);\n    if (bytesPerFrame == 0) {\n        return 0;\n    }\n\n    totalFramesRead = 0;\n\n    while (framesToRead > 0) {\n        drwav_uint64 framesRead = drwav_read_pcm_frames(pWav, drwav_min(framesToRead, sizeof(sampleData)/bytesPerFrame), sampleData);\n        if (framesRead == 0) {\n            break;\n        }\n\n        drwav_mulaw_to_f32(pBufferOut, sampleData, (size_t)(framesRead*pWav->channels));\n\n        pBufferOut      += framesRead*pWav->channels;\n        framesToRead    -= framesRead;\n        totalFramesRead += framesRead;\n    }\n\n    return totalFramesRead;\n}\n\nDRWAV_API drwav_uint64 drwav_read_pcm_frames_f32(drwav* pWav, drwav_uint64 framesToRead, float* pBufferOut)\n{\n    if (pWav == NULL || framesToRead == 0) {\n        return 0;\n    }\n\n    if (pBufferOut == NULL) {\n        return drwav_read_pcm_frames(pWav, framesToRead, NULL);\n    }\n\n    /* Don't try to read more samples than can potentially fit in the output buffer. */\n    if (framesToRead * pWav->channels * sizeof(float) > DRWAV_SIZE_MAX) {\n        framesToRead = DRWAV_SIZE_MAX / sizeof(float) / pWav->channels;\n    }\n\n    if (pWav->translatedFormatTag == DR_WAVE_FORMAT_PCM) {\n        return drwav_read_pcm_frames_f32__pcm(pWav, framesToRead, pBufferOut);\n    }\n\n    if (pWav->translatedFormatTag == DR_WAVE_FORMAT_ADPCM) {\n        return drwav_read_pcm_frames_f32__msadpcm(pWav, framesToRead, pBufferOut);\n    }\n\n    if (pWav->translatedFormatTag == DR_WAVE_FORMAT_IEEE_FLOAT) {\n        return drwav_read_pcm_frames_f32__ieee(pWav, framesToRead, pBufferOut);\n    }\n\n    if (pWav->translatedFormatTag == DR_WAVE_FORMAT_ALAW) {\n        return drwav_read_pcm_frames_f32__alaw(pWav, framesToRead, pBufferOut);\n    }\n\n    if (pWav->translatedFormatTag == DR_WAVE_FORMAT_MULAW) {\n        return drwav_read_pcm_frames_f32__mulaw(pWav, framesToRead, pBufferOut);\n    }\n\n    if (pWav->translatedFormatTag == DR_WAVE_FORMAT_DVI_ADPCM) {\n        return drwav_read_pcm_frames_f32__ima(pWav, framesToRead, pBufferOut);\n    }\n\n    return 0;\n}\n\nDRWAV_API drwav_uint64 drwav_read_pcm_frames_f32le(drwav* pWav, drwav_uint64 framesToRead, float* pBufferOut)\n{\n    drwav_uint64 framesRead = drwav_read_pcm_frames_f32(pWav, framesToRead, pBufferOut);\n    if (pBufferOut != NULL && drwav__is_little_endian() == DRWAV_FALSE) {\n        drwav__bswap_samples_f32(pBufferOut, framesRead*pWav->channels);\n    }\n\n    return framesRead;\n}\n\nDRWAV_API drwav_uint64 drwav_read_pcm_frames_f32be(drwav* pWav, drwav_uint64 framesToRead, float* pBufferOut)\n{\n    drwav_uint64 framesRead = drwav_read_pcm_frames_f32(pWav, framesToRead, pBufferOut);\n    if (pBufferOut != NULL && drwav__is_little_endian() == DRWAV_TRUE) {\n        drwav__bswap_samples_f32(pBufferOut, framesRead*pWav->channels);\n    }\n\n    return framesRead;\n}\n\n\nDRWAV_API void drwav_u8_to_f32(float* pOut, const drwav_uint8* pIn, size_t sampleCount)\n{\n    size_t i;\n\n    if (pOut == NULL || pIn == NULL) {\n        return;\n    }\n\n#ifdef DR_WAV_LIBSNDFILE_COMPAT\n    /*\n    It appears libsndfile uses slightly different logic for the u8 -> f32 conversion to dr_wav, which in my opinion is incorrect. It appears\n    libsndfile performs the conversion something like \"f32 = (u8 / 256) * 2 - 1\", however I think it should be \"f32 = (u8 / 255) * 2 - 1\" (note\n    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\n    correctness testing. This is disabled by default.\n    */\n    for (i = 0; i < sampleCount; ++i) {\n        *pOut++ = (pIn[i] / 256.0f) * 2 - 1;\n    }\n#else\n    for (i = 0; i < sampleCount; ++i) {\n        float x = pIn[i];\n        x = x * 0.00784313725490196078f;    /* 0..255 to 0..2 */\n        x = x - 1;                          /* 0..2 to -1..1 */\n\n        *pOut++ = x;\n    }\n#endif\n}\n\nDRWAV_API void drwav_s16_to_f32(float* pOut, const drwav_int16* pIn, size_t sampleCount)\n{\n    size_t i;\n\n    if (pOut == NULL || pIn == NULL) {\n        return;\n    }\n\n    for (i = 0; i < sampleCount; ++i) {\n        *pOut++ = pIn[i] * 0.000030517578125f;\n    }\n}\n\nDRWAV_API void drwav_s24_to_f32(float* pOut, const drwav_uint8* pIn, size_t sampleCount)\n{\n    size_t i;\n\n    if (pOut == NULL || pIn == NULL) {\n        return;\n    }\n\n    for (i = 0; i < sampleCount; ++i) {\n        double x;\n        drwav_uint32 a = ((drwav_uint32)(pIn[i*3+0]) <<  8);\n        drwav_uint32 b = ((drwav_uint32)(pIn[i*3+1]) << 16);\n        drwav_uint32 c = ((drwav_uint32)(pIn[i*3+2]) << 24);\n\n        x = (double)((drwav_int32)(a | b | c) >> 8);\n        *pOut++ = (float)(x * 0.00000011920928955078125);\n    }\n}\n\nDRWAV_API void drwav_s32_to_f32(float* pOut, const drwav_int32* pIn, size_t sampleCount)\n{\n    size_t i;\n    if (pOut == NULL || pIn == NULL) {\n        return;\n    }\n\n    for (i = 0; i < sampleCount; ++i) {\n        *pOut++ = (float)(pIn[i] / 2147483648.0);\n    }\n}\n\nDRWAV_API void drwav_f64_to_f32(float* pOut, const double* pIn, size_t sampleCount)\n{\n    size_t i;\n\n    if (pOut == NULL || pIn == NULL) {\n        return;\n    }\n\n    for (i = 0; i < sampleCount; ++i) {\n        *pOut++ = (float)pIn[i];\n    }\n}\n\nDRWAV_API void drwav_alaw_to_f32(float* pOut, const drwav_uint8* pIn, size_t sampleCount)\n{\n    size_t i;\n\n    if (pOut == NULL || pIn == NULL) {\n        return;\n    }\n\n    for (i = 0; i < sampleCount; ++i) {\n        *pOut++ = drwav__alaw_to_s16(pIn[i]) / 32768.0f;\n    }\n}\n\nDRWAV_API void drwav_mulaw_to_f32(float* pOut, const drwav_uint8* pIn, size_t sampleCount)\n{\n    size_t i;\n\n    if (pOut == NULL || pIn == NULL) {\n        return;\n    }\n\n    for (i = 0; i < sampleCount; ++i) {\n        *pOut++ = drwav__mulaw_to_s16(pIn[i]) / 32768.0f;\n    }\n}\n\n\n\nstatic void drwav__pcm_to_s32(drwav_int32* pOut, const drwav_uint8* pIn, size_t totalSampleCount, unsigned int bytesPerSample)\n{\n    unsigned int i;\n\n    /* Special case for 8-bit sample data because it's treated as unsigned. */\n    if (bytesPerSample == 1) {\n        drwav_u8_to_s32(pOut, pIn, totalSampleCount);\n        return;\n    }\n\n    /* Slightly more optimal implementation for common formats. */\n    if (bytesPerSample == 2) {\n        drwav_s16_to_s32(pOut, (const drwav_int16*)pIn, totalSampleCount);\n        return;\n    }\n    if (bytesPerSample == 3) {\n        drwav_s24_to_s32(pOut, pIn, totalSampleCount);\n        return;\n    }\n    if (bytesPerSample == 4) {\n        for (i = 0; i < totalSampleCount; ++i) {\n           *pOut++ = ((const drwav_int32*)pIn)[i];\n        }\n        return;\n    }\n\n\n    /* Anything more than 64 bits per sample is not supported. */\n    if (bytesPerSample > 8) {\n        DRWAV_ZERO_MEMORY(pOut, totalSampleCount * sizeof(*pOut));\n        return;\n    }\n\n\n    /* Generic, slow converter. */\n    for (i = 0; i < totalSampleCount; ++i) {\n        drwav_uint64 sample = 0;\n        unsigned int shift  = (8 - bytesPerSample) * 8;\n\n        unsigned int j;\n        for (j = 0; j < bytesPerSample; j += 1) {\n            DRWAV_ASSERT(j < 8);\n            sample |= (drwav_uint64)(pIn[j]) << shift;\n            shift  += 8;\n        }\n\n        pIn += j;\n        *pOut++ = (drwav_int32)((drwav_int64)sample >> 32);\n    }\n}\n\nstatic void drwav__ieee_to_s32(drwav_int32* pOut, const drwav_uint8* pIn, size_t totalSampleCount, unsigned int bytesPerSample)\n{\n    if (bytesPerSample == 4) {\n        drwav_f32_to_s32(pOut, (const float*)pIn, totalSampleCount);\n        return;\n    } else if (bytesPerSample == 8) {\n        drwav_f64_to_s32(pOut, (const double*)pIn, totalSampleCount);\n        return;\n    } else {\n        /* Only supporting 32- and 64-bit float. Output silence in all other cases. Contributions welcome for 16-bit float. */\n        DRWAV_ZERO_MEMORY(pOut, totalSampleCount * sizeof(*pOut));\n        return;\n    }\n}\n\n\nstatic drwav_uint64 drwav_read_pcm_frames_s32__pcm(drwav* pWav, drwav_uint64 framesToRead, drwav_int32* pBufferOut)\n{\n    drwav_uint64 totalFramesRead;\n    drwav_uint8 sampleData[4096];\n    drwav_uint32 bytesPerFrame;\n\n    /* Fast path. */\n    if (pWav->translatedFormatTag == DR_WAVE_FORMAT_PCM && pWav->bitsPerSample == 32) {\n        return drwav_read_pcm_frames(pWav, framesToRead, pBufferOut);\n    }\n    \n    bytesPerFrame = drwav_get_bytes_per_pcm_frame(pWav);\n    if (bytesPerFrame == 0) {\n        return 0;\n    }\n\n    totalFramesRead = 0;\n\n    while (framesToRead > 0) {\n        drwav_uint64 framesRead = drwav_read_pcm_frames(pWav, drwav_min(framesToRead, sizeof(sampleData)/bytesPerFrame), sampleData);\n        if (framesRead == 0) {\n            break;\n        }\n\n        drwav__pcm_to_s32(pBufferOut, sampleData, (size_t)(framesRead*pWav->channels), bytesPerFrame/pWav->channels);\n\n        pBufferOut      += framesRead*pWav->channels;\n        framesToRead    -= framesRead;\n        totalFramesRead += framesRead;\n    }\n\n    return totalFramesRead;\n}\n\nstatic drwav_uint64 drwav_read_pcm_frames_s32__msadpcm(drwav* pWav, drwav_uint64 framesToRead, drwav_int32* pBufferOut)\n{\n    /*\n    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\n    want to duplicate that code.\n    */\n    drwav_uint64 totalFramesRead = 0;\n    drwav_int16 samples16[2048];\n    while (framesToRead > 0) {\n        drwav_uint64 framesRead = drwav_read_pcm_frames_s16(pWav, drwav_min(framesToRead, drwav_countof(samples16)/pWav->channels), samples16);\n        if (framesRead == 0) {\n            break;\n        }\n\n        drwav_s16_to_s32(pBufferOut, samples16, (size_t)(framesRead*pWav->channels));   /* <-- Safe cast because we're clamping to 2048. */\n\n        pBufferOut      += framesRead*pWav->channels;\n        framesToRead    -= framesRead;\n        totalFramesRead += framesRead;\n    }\n\n    return totalFramesRead;\n}\n\nstatic drwav_uint64 drwav_read_pcm_frames_s32__ima(drwav* pWav, drwav_uint64 framesToRead, drwav_int32* pBufferOut)\n{\n    /*\n    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\n    want to duplicate that code.\n    */\n    drwav_uint64 totalFramesRead = 0;\n    drwav_int16 samples16[2048];\n    while (framesToRead > 0) {\n        drwav_uint64 framesRead = drwav_read_pcm_frames_s16(pWav, drwav_min(framesToRead, drwav_countof(samples16)/pWav->channels), samples16);\n        if (framesRead == 0) {\n            break;\n        }\n\n        drwav_s16_to_s32(pBufferOut, samples16, (size_t)(framesRead*pWav->channels));   /* <-- Safe cast because we're clamping to 2048. */\n\n        pBufferOut      += framesRead*pWav->channels;\n        framesToRead    -= framesRead;\n        totalFramesRead += framesRead;\n    }\n\n    return totalFramesRead;\n}\n\nstatic drwav_uint64 drwav_read_pcm_frames_s32__ieee(drwav* pWav, drwav_uint64 framesToRead, drwav_int32* pBufferOut)\n{\n    drwav_uint64 totalFramesRead;\n    drwav_uint8 sampleData[4096];\n\n    drwav_uint32 bytesPerFrame = drwav_get_bytes_per_pcm_frame(pWav);\n    if (bytesPerFrame == 0) {\n        return 0;\n    }\n\n    totalFramesRead = 0;\n\n    while (framesToRead > 0) {\n        drwav_uint64 framesRead = drwav_read_pcm_frames(pWav, drwav_min(framesToRead, sizeof(sampleData)/bytesPerFrame), sampleData);\n        if (framesRead == 0) {\n            break;\n        }\n\n        drwav__ieee_to_s32(pBufferOut, sampleData, (size_t)(framesRead*pWav->channels), bytesPerFrame/pWav->channels);\n\n        pBufferOut      += framesRead*pWav->channels;\n        framesToRead    -= framesRead;\n        totalFramesRead += framesRead;\n    }\n\n    return totalFramesRead;\n}\n\nstatic drwav_uint64 drwav_read_pcm_frames_s32__alaw(drwav* pWav, drwav_uint64 framesToRead, drwav_int32* pBufferOut)\n{\n    drwav_uint64 totalFramesRead;\n    drwav_uint8 sampleData[4096];\n\n    drwav_uint32 bytesPerFrame = drwav_get_bytes_per_pcm_frame(pWav);\n    if (bytesPerFrame == 0) {\n        return 0;\n    }\n\n    totalFramesRead = 0;\n\n    while (framesToRead > 0) {\n        drwav_uint64 framesRead = drwav_read_pcm_frames(pWav, drwav_min(framesToRead, sizeof(sampleData)/bytesPerFrame), sampleData);\n        if (framesRead == 0) {\n            break;\n        }\n\n        drwav_alaw_to_s32(pBufferOut, sampleData, (size_t)(framesRead*pWav->channels));\n\n        pBufferOut      += framesRead*pWav->channels;\n        framesToRead    -= framesRead;\n        totalFramesRead += framesRead;\n    }\n\n    return totalFramesRead;\n}\n\nstatic drwav_uint64 drwav_read_pcm_frames_s32__mulaw(drwav* pWav, drwav_uint64 framesToRead, drwav_int32* pBufferOut)\n{\n    drwav_uint64 totalFramesRead;\n    drwav_uint8 sampleData[4096];\n\n    drwav_uint32 bytesPerFrame = drwav_get_bytes_per_pcm_frame(pWav);\n    if (bytesPerFrame == 0) {\n        return 0;\n    }\n\n    totalFramesRead = 0;\n\n    while (framesToRead > 0) {\n        drwav_uint64 framesRead = drwav_read_pcm_frames(pWav, drwav_min(framesToRead, sizeof(sampleData)/bytesPerFrame), sampleData);\n        if (framesRead == 0) {\n            break;\n        }\n\n        drwav_mulaw_to_s32(pBufferOut, sampleData, (size_t)(framesRead*pWav->channels));\n\n        pBufferOut      += framesRead*pWav->channels;\n        framesToRead    -= framesRead;\n        totalFramesRead += framesRead;\n    }\n\n    return totalFramesRead;\n}\n\nDRWAV_API drwav_uint64 drwav_read_pcm_frames_s32(drwav* pWav, drwav_uint64 framesToRead, drwav_int32* pBufferOut)\n{\n    if (pWav == NULL || framesToRead == 0) {\n        return 0;\n    }\n\n    if (pBufferOut == NULL) {\n        return drwav_read_pcm_frames(pWav, framesToRead, NULL);\n    }\n\n    /* Don't try to read more samples than can potentially fit in the output buffer. */\n    if (framesToRead * pWav->channels * sizeof(drwav_int32) > DRWAV_SIZE_MAX) {\n        framesToRead = DRWAV_SIZE_MAX / sizeof(drwav_int32) / pWav->channels;\n    }\n\n    if (pWav->translatedFormatTag == DR_WAVE_FORMAT_PCM) {\n        return drwav_read_pcm_frames_s32__pcm(pWav, framesToRead, pBufferOut);\n    }\n\n    if (pWav->translatedFormatTag == DR_WAVE_FORMAT_ADPCM) {\n        return drwav_read_pcm_frames_s32__msadpcm(pWav, framesToRead, pBufferOut);\n    }\n\n    if (pWav->translatedFormatTag == DR_WAVE_FORMAT_IEEE_FLOAT) {\n        return drwav_read_pcm_frames_s32__ieee(pWav, framesToRead, pBufferOut);\n    }\n\n    if (pWav->translatedFormatTag == DR_WAVE_FORMAT_ALAW) {\n        return drwav_read_pcm_frames_s32__alaw(pWav, framesToRead, pBufferOut);\n    }\n\n    if (pWav->translatedFormatTag == DR_WAVE_FORMAT_MULAW) {\n        return drwav_read_pcm_frames_s32__mulaw(pWav, framesToRead, pBufferOut);\n    }\n\n    if (pWav->translatedFormatTag == DR_WAVE_FORMAT_DVI_ADPCM) {\n        return drwav_read_pcm_frames_s32__ima(pWav, framesToRead, pBufferOut);\n    }\n\n    return 0;\n}\n\nDRWAV_API drwav_uint64 drwav_read_pcm_frames_s32le(drwav* pWav, drwav_uint64 framesToRead, drwav_int32* pBufferOut)\n{\n    drwav_uint64 framesRead = drwav_read_pcm_frames_s32(pWav, framesToRead, pBufferOut);\n    if (pBufferOut != NULL && drwav__is_little_endian() == DRWAV_FALSE) {\n        drwav__bswap_samples_s32(pBufferOut, framesRead*pWav->channels);\n    }\n\n    return framesRead;\n}\n\nDRWAV_API drwav_uint64 drwav_read_pcm_frames_s32be(drwav* pWav, drwav_uint64 framesToRead, drwav_int32* pBufferOut)\n{\n    drwav_uint64 framesRead = drwav_read_pcm_frames_s32(pWav, framesToRead, pBufferOut);\n    if (pBufferOut != NULL && drwav__is_little_endian() == DRWAV_TRUE) {\n        drwav__bswap_samples_s32(pBufferOut, framesRead*pWav->channels);\n    }\n\n    return framesRead;\n}\n\n\nDRWAV_API void drwav_u8_to_s32(drwav_int32* pOut, const drwav_uint8* pIn, size_t sampleCount)\n{\n    size_t i;\n\n    if (pOut == NULL || pIn == NULL) {\n        return;\n    }\n\n    for (i = 0; i < sampleCount; ++i) {\n        *pOut++ = ((int)pIn[i] - 128) << 24;\n    }\n}\n\nDRWAV_API void drwav_s16_to_s32(drwav_int32* pOut, const drwav_int16* pIn, size_t sampleCount)\n{\n    size_t i;\n\n    if (pOut == NULL || pIn == NULL) {\n        return;\n    }\n\n    for (i = 0; i < sampleCount; ++i) {\n        *pOut++ = pIn[i] << 16;\n    }\n}\n\nDRWAV_API void drwav_s24_to_s32(drwav_int32* pOut, const drwav_uint8* pIn, size_t sampleCount)\n{\n    size_t i;\n\n    if (pOut == NULL || pIn == NULL) {\n        return;\n    }\n\n    for (i = 0; i < sampleCount; ++i) {\n        unsigned int s0 = pIn[i*3 + 0];\n        unsigned int s1 = pIn[i*3 + 1];\n        unsigned int s2 = pIn[i*3 + 2];\n\n        drwav_int32 sample32 = (drwav_int32)((s0 << 8) | (s1 << 16) | (s2 << 24));\n        *pOut++ = sample32;\n    }\n}\n\nDRWAV_API void drwav_f32_to_s32(drwav_int32* pOut, const float* pIn, size_t sampleCount)\n{\n    size_t i;\n\n    if (pOut == NULL || pIn == NULL) {\n        return;\n    }\n\n    for (i = 0; i < sampleCount; ++i) {\n        *pOut++ = (drwav_int32)(2147483648.0 * pIn[i]);\n    }\n}\n\nDRWAV_API void drwav_f64_to_s32(drwav_int32* pOut, const double* pIn, size_t sampleCount)\n{\n    size_t i;\n\n    if (pOut == NULL || pIn == NULL) {\n        return;\n    }\n\n    for (i = 0; i < sampleCount; ++i) {\n        *pOut++ = (drwav_int32)(2147483648.0 * pIn[i]);\n    }\n}\n\nDRWAV_API void drwav_alaw_to_s32(drwav_int32* pOut, const drwav_uint8* pIn, size_t sampleCount)\n{\n    size_t i;\n\n    if (pOut == NULL || pIn == NULL) {\n        return;\n    }\n\n    for (i = 0; i < sampleCount; ++i) {\n        *pOut++ = ((drwav_int32)drwav__alaw_to_s16(pIn[i])) << 16;\n    }\n}\n\nDRWAV_API void drwav_mulaw_to_s32(drwav_int32* pOut, const drwav_uint8* pIn, size_t sampleCount)\n{\n    size_t i;\n\n    if (pOut == NULL || pIn == NULL) {\n        return;\n    }\n\n    for (i= 0; i < sampleCount; ++i) {\n        *pOut++ = ((drwav_int32)drwav__mulaw_to_s16(pIn[i])) << 16;\n    }\n}\n\n\n\nstatic drwav_int16* drwav__read_pcm_frames_and_close_s16(drwav* pWav, unsigned int* channels, unsigned int* sampleRate, drwav_uint64* totalFrameCount)\n{\n    drwav_uint64 sampleDataSize;\n    drwav_int16* pSampleData;\n    drwav_uint64 framesRead;\n\n    DRWAV_ASSERT(pWav != NULL);\n\n    sampleDataSize = pWav->totalPCMFrameCount * pWav->channels * sizeof(drwav_int16);\n    if (sampleDataSize > DRWAV_SIZE_MAX) {\n        drwav_uninit(pWav);\n        return NULL;    /* File's too big. */\n    }\n\n    pSampleData = (drwav_int16*)drwav__malloc_from_callbacks((size_t)sampleDataSize, &pWav->allocationCallbacks); /* <-- Safe cast due to the check above. */\n    if (pSampleData == NULL) {\n        drwav_uninit(pWav);\n        return NULL;    /* Failed to allocate memory. */\n    }\n\n    framesRead = drwav_read_pcm_frames_s16(pWav, (size_t)pWav->totalPCMFrameCount, pSampleData);\n    if (framesRead != pWav->totalPCMFrameCount) {\n        drwav__free_from_callbacks(pSampleData, &pWav->allocationCallbacks);\n        drwav_uninit(pWav);\n        return NULL;    /* There was an error reading the samples. */\n    }\n\n    drwav_uninit(pWav);\n\n    if (sampleRate) {\n        *sampleRate = pWav->sampleRate;\n    }\n    if (channels) {\n        *channels = pWav->channels;\n    }\n    if (totalFrameCount) {\n        *totalFrameCount = pWav->totalPCMFrameCount;\n    }\n\n    return pSampleData;\n}\n\nstatic float* drwav__read_pcm_frames_and_close_f32(drwav* pWav, unsigned int* channels, unsigned int* sampleRate, drwav_uint64* totalFrameCount)\n{\n    drwav_uint64 sampleDataSize;\n    float* pSampleData;\n    drwav_uint64 framesRead;\n\n    DRWAV_ASSERT(pWav != NULL);\n\n    sampleDataSize = pWav->totalPCMFrameCount * pWav->channels * sizeof(float);\n    if (sampleDataSize > DRWAV_SIZE_MAX) {\n        drwav_uninit(pWav);\n        return NULL;    /* File's too big. */\n    }\n\n    pSampleData = (float*)drwav__malloc_from_callbacks((size_t)sampleDataSize, &pWav->allocationCallbacks); /* <-- Safe cast due to the check above. */\n    if (pSampleData == NULL) {\n        drwav_uninit(pWav);\n        return NULL;    /* Failed to allocate memory. */\n    }\n\n    framesRead = drwav_read_pcm_frames_f32(pWav, (size_t)pWav->totalPCMFrameCount, pSampleData);\n    if (framesRead != pWav->totalPCMFrameCount) {\n        drwav__free_from_callbacks(pSampleData, &pWav->allocationCallbacks);\n        drwav_uninit(pWav);\n        return NULL;    /* There was an error reading the samples. */\n    }\n\n    drwav_uninit(pWav);\n\n    if (sampleRate) {\n        *sampleRate = pWav->sampleRate;\n    }\n    if (channels) {\n        *channels = pWav->channels;\n    }\n    if (totalFrameCount) {\n        *totalFrameCount = pWav->totalPCMFrameCount;\n    }\n\n    return pSampleData;\n}\n\nstatic drwav_int32* drwav__read_pcm_frames_and_close_s32(drwav* pWav, unsigned int* channels, unsigned int* sampleRate, drwav_uint64* totalFrameCount)\n{\n    drwav_uint64 sampleDataSize;\n    drwav_int32* pSampleData;\n    drwav_uint64 framesRead;\n\n    DRWAV_ASSERT(pWav != NULL);\n\n    sampleDataSize = pWav->totalPCMFrameCount * pWav->channels * sizeof(drwav_int32);\n    if (sampleDataSize > DRWAV_SIZE_MAX) {\n        drwav_uninit(pWav);\n        return NULL;    /* File's too big. */\n    }\n\n    pSampleData = (drwav_int32*)drwav__malloc_from_callbacks((size_t)sampleDataSize, &pWav->allocationCallbacks); /* <-- Safe cast due to the check above. */\n    if (pSampleData == NULL) {\n        drwav_uninit(pWav);\n        return NULL;    /* Failed to allocate memory. */\n    }\n\n    framesRead = drwav_read_pcm_frames_s32(pWav, (size_t)pWav->totalPCMFrameCount, pSampleData);\n    if (framesRead != pWav->totalPCMFrameCount) {\n        drwav__free_from_callbacks(pSampleData, &pWav->allocationCallbacks);\n        drwav_uninit(pWav);\n        return NULL;    /* There was an error reading the samples. */\n    }\n\n    drwav_uninit(pWav);\n\n    if (sampleRate) {\n        *sampleRate = pWav->sampleRate;\n    }\n    if (channels) {\n        *channels = pWav->channels;\n    }\n    if (totalFrameCount) {\n        *totalFrameCount = pWav->totalPCMFrameCount;\n    }\n\n    return pSampleData;\n}\n\n\n\nDRWAV_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)\n{\n    drwav wav;\n\n    if (channelsOut) {\n        *channelsOut = 0;\n    }\n    if (sampleRateOut) {\n        *sampleRateOut = 0;\n    }\n    if (totalFrameCountOut) {\n        *totalFrameCountOut = 0;\n    }\n\n    if (!drwav_init(&wav, onRead, onSeek, pUserData, pAllocationCallbacks)) {\n        return NULL;\n    }\n\n    return drwav__read_pcm_frames_and_close_s16(&wav, channelsOut, sampleRateOut, totalFrameCountOut);\n}\n\nDRWAV_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)\n{\n    drwav wav;\n\n    if (channelsOut) {\n        *channelsOut = 0;\n    }\n    if (sampleRateOut) {\n        *sampleRateOut = 0;\n    }\n    if (totalFrameCountOut) {\n        *totalFrameCountOut = 0;\n    }\n\n    if (!drwav_init(&wav, onRead, onSeek, pUserData, pAllocationCallbacks)) {\n        return NULL;\n    }\n\n    return drwav__read_pcm_frames_and_close_f32(&wav, channelsOut, sampleRateOut, totalFrameCountOut);\n}\n\nDRWAV_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)\n{\n    drwav wav;\n\n    if (channelsOut) {\n        *channelsOut = 0;\n    }\n    if (sampleRateOut) {\n        *sampleRateOut = 0;\n    }\n    if (totalFrameCountOut) {\n        *totalFrameCountOut = 0;\n    }\n\n    if (!drwav_init(&wav, onRead, onSeek, pUserData, pAllocationCallbacks)) {\n        return NULL;\n    }\n\n    return drwav__read_pcm_frames_and_close_s32(&wav, channelsOut, sampleRateOut, totalFrameCountOut);\n}\n\n#ifndef DR_WAV_NO_STDIO\nDRWAV_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)\n{\n    drwav wav;\n\n    if (channelsOut) {\n        *channelsOut = 0;\n    }\n    if (sampleRateOut) {\n        *sampleRateOut = 0;\n    }\n    if (totalFrameCountOut) {\n        *totalFrameCountOut = 0;\n    }\n\n    if (!drwav_init_file(&wav, filename, pAllocationCallbacks)) {\n        return NULL;\n    }\n\n    return drwav__read_pcm_frames_and_close_s16(&wav, channelsOut, sampleRateOut, totalFrameCountOut);\n}\n\nDRWAV_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)\n{\n    drwav wav;\n\n    if (channelsOut) {\n        *channelsOut = 0;\n    }\n    if (sampleRateOut) {\n        *sampleRateOut = 0;\n    }\n    if (totalFrameCountOut) {\n        *totalFrameCountOut = 0;\n    }\n\n    if (!drwav_init_file(&wav, filename, pAllocationCallbacks)) {\n        return NULL;\n    }\n\n    return drwav__read_pcm_frames_and_close_f32(&wav, channelsOut, sampleRateOut, totalFrameCountOut);\n}\n\nDRWAV_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)\n{\n    drwav wav;\n\n    if (channelsOut) {\n        *channelsOut = 0;\n    }\n    if (sampleRateOut) {\n        *sampleRateOut = 0;\n    }\n    if (totalFrameCountOut) {\n        *totalFrameCountOut = 0;\n    }\n\n    if (!drwav_init_file(&wav, filename, pAllocationCallbacks)) {\n        return NULL;\n    }\n\n    return drwav__read_pcm_frames_and_close_s32(&wav, channelsOut, sampleRateOut, totalFrameCountOut);\n}\n\n\nDRWAV_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)\n{\n    drwav wav;\n\n    if (sampleRateOut) {\n        *sampleRateOut = 0;\n    }\n    if (channelsOut) {\n        *channelsOut = 0;\n    }\n    if (totalFrameCountOut) {\n        *totalFrameCountOut = 0;\n    }\n\n    if (!drwav_init_file_w(&wav, filename, pAllocationCallbacks)) {\n        return NULL;\n    }\n\n    return drwav__read_pcm_frames_and_close_s16(&wav, channelsOut, sampleRateOut, totalFrameCountOut);\n}\n\nDRWAV_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)\n{\n    drwav wav;\n\n    if (sampleRateOut) {\n        *sampleRateOut = 0;\n    }\n    if (channelsOut) {\n        *channelsOut = 0;\n    }\n    if (totalFrameCountOut) {\n        *totalFrameCountOut = 0;\n    }\n\n    if (!drwav_init_file_w(&wav, filename, pAllocationCallbacks)) {\n        return NULL;\n    }\n\n    return drwav__read_pcm_frames_and_close_f32(&wav, channelsOut, sampleRateOut, totalFrameCountOut);\n}\n\nDRWAV_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)\n{\n    drwav wav;\n\n    if (sampleRateOut) {\n        *sampleRateOut = 0;\n    }\n    if (channelsOut) {\n        *channelsOut = 0;\n    }\n    if (totalFrameCountOut) {\n        *totalFrameCountOut = 0;\n    }\n\n    if (!drwav_init_file_w(&wav, filename, pAllocationCallbacks)) {\n        return NULL;\n    }\n\n    return drwav__read_pcm_frames_and_close_s32(&wav, channelsOut, sampleRateOut, totalFrameCountOut);\n}\n#endif\n\nDRWAV_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)\n{\n    drwav wav;\n\n    if (channelsOut) {\n        *channelsOut = 0;\n    }\n    if (sampleRateOut) {\n        *sampleRateOut = 0;\n    }\n    if (totalFrameCountOut) {\n        *totalFrameCountOut = 0;\n    }\n\n    if (!drwav_init_memory(&wav, data, dataSize, pAllocationCallbacks)) {\n        return NULL;\n    }\n\n    return drwav__read_pcm_frames_and_close_s16(&wav, channelsOut, sampleRateOut, totalFrameCountOut);\n}\n\nDRWAV_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)\n{\n    drwav wav;\n\n    if (channelsOut) {\n        *channelsOut = 0;\n    }\n    if (sampleRateOut) {\n        *sampleRateOut = 0;\n    }\n    if (totalFrameCountOut) {\n        *totalFrameCountOut = 0;\n    }\n\n    if (!drwav_init_memory(&wav, data, dataSize, pAllocationCallbacks)) {\n        return NULL;\n    }\n\n    return drwav__read_pcm_frames_and_close_f32(&wav, channelsOut, sampleRateOut, totalFrameCountOut);\n}\n\nDRWAV_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)\n{\n    drwav wav;\n\n    if (channelsOut) {\n        *channelsOut = 0;\n    }\n    if (sampleRateOut) {\n        *sampleRateOut = 0;\n    }\n    if (totalFrameCountOut) {\n        *totalFrameCountOut = 0;\n    }\n\n    if (!drwav_init_memory(&wav, data, dataSize, pAllocationCallbacks)) {\n        return NULL;\n    }\n\n    return drwav__read_pcm_frames_and_close_s32(&wav, channelsOut, sampleRateOut, totalFrameCountOut);\n}\n#endif  /* DR_WAV_NO_CONVERSION_API */\n\n\nDRWAV_API void drwav_free(void* p, const drwav_allocation_callbacks* pAllocationCallbacks)\n{\n    if (pAllocationCallbacks != NULL) {\n        drwav__free_from_callbacks(p, pAllocationCallbacks);\n    } else {\n        drwav__free_default(p, NULL);\n    }\n}\n\nDRWAV_API drwav_uint16 drwav_bytes_to_u16(const drwav_uint8* data)\n{\n    return drwav__bytes_to_u16(data);\n}\n\nDRWAV_API drwav_int16 drwav_bytes_to_s16(const drwav_uint8* data)\n{\n    return drwav__bytes_to_s16(data);\n}\n\nDRWAV_API drwav_uint32 drwav_bytes_to_u32(const drwav_uint8* data)\n{\n    return drwav__bytes_to_u32(data);\n}\n\nDRWAV_API drwav_int32 drwav_bytes_to_s32(const drwav_uint8* data)\n{\n    return drwav__bytes_to_s32(data);\n}\n\nDRWAV_API drwav_uint64 drwav_bytes_to_u64(const drwav_uint8* data)\n{\n    return drwav__bytes_to_u64(data);\n}\n\nDRWAV_API drwav_int64 drwav_bytes_to_s64(const drwav_uint8* data)\n{\n    return drwav__bytes_to_s64(data);\n}\n\n\nDRWAV_API drwav_bool32 drwav_guid_equal(const drwav_uint8 a[16], const drwav_uint8 b[16])\n{\n    return drwav__guid_equal(a, b);\n}\n\nDRWAV_API drwav_bool32 drwav_fourcc_equal(const drwav_uint8* a, const char* b)\n{\n    return drwav__fourcc_equal(a, b);\n}\n\n#endif  /* dr_wav_c */\n#endif  /* DR_WAV_IMPLEMENTATION */\n\n/*\nRELEASE NOTES - v0.11.0\n=======================\nVersion 0.11.0 has breaking API changes.\n\nImproved Client-Defined Memory Allocation\n-----------------------------------------\nThe main change with this release is the addition of a more flexible way of implementing custom memory allocation routines. The\nexisting system of DRWAV_MALLOC, DRWAV_REALLOC and DRWAV_FREE are still in place and will be used by default when no custom\nallocation callbacks are specified.\n\nTo use the new system, you pass in a pointer to a drwav_allocation_callbacks object to drwav_init() and family, like this:\n\n    void* my_malloc(size_t sz, void* pUserData)\n    {\n        return malloc(sz);\n    }\n    void* my_realloc(void* p, size_t sz, void* pUserData)\n    {\n        return realloc(p, sz);\n    }\n    void my_free(void* p, void* pUserData)\n    {\n        free(p);\n    }\n\n    ...\n\n    drwav_allocation_callbacks allocationCallbacks;\n    allocationCallbacks.pUserData = &myData;\n    allocationCallbacks.onMalloc  = my_malloc;\n    allocationCallbacks.onRealloc = my_realloc;\n    allocationCallbacks.onFree    = my_free;\n    drwav_init_file(&wav, \"my_file.wav\", &allocationCallbacks);\n\nThe advantage of this new system is that it allows you to specify user data which will be passed in to the allocation routines.\n\nPassing in null for the allocation callbacks object will cause dr_wav to use defaults which is the same as DRWAV_MALLOC,\nDRWAV_REALLOC and DRWAV_FREE and the equivalent of how it worked in previous versions.\n\nEvery API that opens a drwav object now takes this extra parameter. These include the following:\n\n    drwav_init()\n    drwav_init_ex()\n    drwav_init_file()\n    drwav_init_file_ex()\n    drwav_init_file_w()\n    drwav_init_file_w_ex()\n    drwav_init_memory()\n    drwav_init_memory_ex()\n    drwav_init_write()\n    drwav_init_write_sequential()\n    drwav_init_write_sequential_pcm_frames()\n    drwav_init_file_write()\n    drwav_init_file_write_sequential()\n    drwav_init_file_write_sequential_pcm_frames()\n    drwav_init_file_write_w()\n    drwav_init_file_write_sequential_w()\n    drwav_init_file_write_sequential_pcm_frames_w()\n    drwav_init_memory_write()\n    drwav_init_memory_write_sequential()\n    drwav_init_memory_write_sequential_pcm_frames()\n    drwav_open_and_read_pcm_frames_s16()\n    drwav_open_and_read_pcm_frames_f32()\n    drwav_open_and_read_pcm_frames_s32()\n    drwav_open_file_and_read_pcm_frames_s16()\n    drwav_open_file_and_read_pcm_frames_f32()\n    drwav_open_file_and_read_pcm_frames_s32()\n    drwav_open_file_and_read_pcm_frames_s16_w()\n    drwav_open_file_and_read_pcm_frames_f32_w()\n    drwav_open_file_and_read_pcm_frames_s32_w()\n    drwav_open_memory_and_read_pcm_frames_s16()\n    drwav_open_memory_and_read_pcm_frames_f32()\n    drwav_open_memory_and_read_pcm_frames_s32()\n\nEndian Improvements\n-------------------\nPreviously, the following APIs returned little-endian audio data. These now return native-endian data. This improves compatibility\non big-endian architectures.\n\n    drwav_read_pcm_frames()\n    drwav_read_pcm_frames_s16()\n    drwav_read_pcm_frames_s32()\n    drwav_read_pcm_frames_f32()\n    drwav_open_and_read_pcm_frames_s16()\n    drwav_open_and_read_pcm_frames_s32()\n    drwav_open_and_read_pcm_frames_f32()\n    drwav_open_file_and_read_pcm_frames_s16()\n    drwav_open_file_and_read_pcm_frames_s32()\n    drwav_open_file_and_read_pcm_frames_f32()\n    drwav_open_file_and_read_pcm_frames_s16_w()\n    drwav_open_file_and_read_pcm_frames_s32_w()\n    drwav_open_file_and_read_pcm_frames_f32_w()\n    drwav_open_memory_and_read_pcm_frames_s16()\n    drwav_open_memory_and_read_pcm_frames_s32()\n    drwav_open_memory_and_read_pcm_frames_f32()\n\nAPIs have been added to give you explicit control over whether or not audio data is read or written in big- or little-endian byte\norder:\n\n    drwav_read_pcm_frames_le()\n    drwav_read_pcm_frames_be()\n    drwav_read_pcm_frames_s16le()\n    drwav_read_pcm_frames_s16be()\n    drwav_read_pcm_frames_f32le()\n    drwav_read_pcm_frames_f32be()\n    drwav_read_pcm_frames_s32le()\n    drwav_read_pcm_frames_s32be()\n    drwav_write_pcm_frames_le()\n    drwav_write_pcm_frames_be()\n\nRemoved APIs\n------------\nThe following APIs were deprecated in version 0.10.0 and have now been removed:\n\n    drwav_open()\n    drwav_open_ex()\n    drwav_open_write()\n    drwav_open_write_sequential()\n    drwav_open_file()\n    drwav_open_file_ex()\n    drwav_open_file_write()\n    drwav_open_file_write_sequential()\n    drwav_open_memory()\n    drwav_open_memory_ex()\n    drwav_open_memory_write()\n    drwav_open_memory_write_sequential()\n    drwav_close()\n\n\n\nRELEASE NOTES - v0.10.0\n=======================\nVersion 0.10.0 has breaking API changes. There are no significant bug fixes in this release, so if you are affected you do\nnot need to upgrade.\n\nRemoved APIs\n------------\nThe following APIs were deprecated in version 0.9.0 and have been completely removed in version 0.10.0:\n\n    drwav_read()\n    drwav_read_s16()\n    drwav_read_f32()\n    drwav_read_s32()\n    drwav_seek_to_sample()\n    drwav_write()\n    drwav_open_and_read_s16()\n    drwav_open_and_read_f32()\n    drwav_open_and_read_s32()\n    drwav_open_file_and_read_s16()\n    drwav_open_file_and_read_f32()\n    drwav_open_file_and_read_s32()\n    drwav_open_memory_and_read_s16()\n    drwav_open_memory_and_read_f32()\n    drwav_open_memory_and_read_s32()\n    drwav::totalSampleCount\n\nSee release notes for version 0.9.0 at the bottom of this file for replacement APIs.\n\nDeprecated APIs\n---------------\nThe following APIs have been deprecated. There is a confusing and completely arbitrary difference between drwav_init*() and\ndrwav_open*(), where drwav_init*() initializes a pre-allocated drwav object, whereas drwav_open*() will first allocated a\ndrwav object on the heap and then initialize it. drwav_open*() has been deprecated which means you must now use a pre-\nallocated drwav object with drwav_init*(). If you need the previous functionality, you can just do a malloc() followed by\na called to one of the drwav_init*() APIs.\n\n    drwav_open()\n    drwav_open_ex()\n    drwav_open_write()\n    drwav_open_write_sequential()\n    drwav_open_file()\n    drwav_open_file_ex()\n    drwav_open_file_write()\n    drwav_open_file_write_sequential()\n    drwav_open_memory()\n    drwav_open_memory_ex()\n    drwav_open_memory_write()\n    drwav_open_memory_write_sequential()\n    drwav_close()\n\nThese APIs will be removed completely in a future version. The rationale for this change is to remove confusion between the\ntwo different ways to initialize a drwav object.\n*/\n\n/*\nREVISION HISTORY\n================\nv0.12.16 - 2020-12-02\n  - Fix a bug when trying to read more bytes than can fit in a size_t.\n\nv0.12.15 - 2020-11-21\n  - Fix compilation with OpenWatcom.\n\nv0.12.14 - 2020-11-13\n  - Minor code clean up.\n\nv0.12.13 - 2020-11-01\n  - Improve compiler support for older versions of GCC.\n\nv0.12.12 - 2020-09-28\n  - Add support for RF64.\n  - Fix a bug in writing mode where the size of the RIFF chunk incorrectly includes the header section.\n\nv0.12.11 - 2020-09-08\n  - Fix a compilation error on older compilers.\n\nv0.12.10 - 2020-08-24\n  - Fix a bug when seeking with ADPCM formats.\n\nv0.12.9 - 2020-08-02\n  - Simplify sized types.\n\nv0.12.8 - 2020-07-25\n  - Fix a compilation warning.\n\nv0.12.7 - 2020-07-15\n  - Fix some bugs on big-endian architectures.\n  - Fix an error in s24 to f32 conversion.\n\nv0.12.6 - 2020-06-23\n  - Change drwav_read_*() to allow NULL to be passed in as the output buffer which is equivalent to a forward seek.\n  - Fix a buffer overflow when trying to decode invalid IMA-ADPCM files.\n  - Add include guard for the implementation section.\n\nv0.12.5 - 2020-05-27\n  - Minor documentation fix.\n\nv0.12.4 - 2020-05-16\n  - Replace assert() with DRWAV_ASSERT().\n  - Add compile-time and run-time version querying.\n    - DRWAV_VERSION_MINOR\n    - DRWAV_VERSION_MAJOR\n    - DRWAV_VERSION_REVISION\n    - DRWAV_VERSION_STRING\n    - drwav_version()\n    - drwav_version_string()\n\nv0.12.3 - 2020-04-30\n  - Fix compilation errors with VC6.\n\nv0.12.2 - 2020-04-21\n  - Fix a bug where drwav_init_file() does not close the file handle after attempting to load an erroneous file.\n\nv0.12.1 - 2020-04-13\n  - Fix some pedantic warnings.\n\nv0.12.0 - 2020-04-04\n  - API CHANGE: Add container and format parameters to the chunk callback.\n  - Minor documentation updates.\n\nv0.11.5 - 2020-03-07\n  - Fix compilation error with Visual Studio .NET 2003.\n\nv0.11.4 - 2020-01-29\n  - Fix some static analysis warnings.\n  - Fix a bug when reading f32 samples from an A-law encoded stream.\n\nv0.11.3 - 2020-01-12\n  - Minor changes to some f32 format conversion routines.\n  - Minor bug fix for ADPCM conversion when end of file is reached.\n\nv0.11.2 - 2019-12-02\n  - Fix a possible crash when using custom memory allocators without a custom realloc() implementation.\n  - Fix an integer overflow bug.\n  - Fix a null pointer dereference bug.\n  - Add limits to sample rate, channels and bits per sample to tighten up some validation.\n\nv0.11.1 - 2019-10-07\n  - Internal code clean up.\n\nv0.11.0 - 2019-10-06\n  - API CHANGE: Add support for user defined memory allocation routines. This system allows the program to specify their own memory allocation\n    routines with a user data pointer for client-specific contextual data. This adds an extra parameter to the end of the following APIs:\n    - drwav_init()\n    - drwav_init_ex()\n    - drwav_init_file()\n    - drwav_init_file_ex()\n    - drwav_init_file_w()\n    - drwav_init_file_w_ex()\n    - drwav_init_memory()\n    - drwav_init_memory_ex()\n    - drwav_init_write()\n    - drwav_init_write_sequential()\n    - drwav_init_write_sequential_pcm_frames()\n    - drwav_init_file_write()\n    - drwav_init_file_write_sequential()\n    - drwav_init_file_write_sequential_pcm_frames()\n    - drwav_init_file_write_w()\n    - drwav_init_file_write_sequential_w()\n    - drwav_init_file_write_sequential_pcm_frames_w()\n    - drwav_init_memory_write()\n    - drwav_init_memory_write_sequential()\n    - drwav_init_memory_write_sequential_pcm_frames()\n    - drwav_open_and_read_pcm_frames_s16()\n    - drwav_open_and_read_pcm_frames_f32()\n    - drwav_open_and_read_pcm_frames_s32()\n    - drwav_open_file_and_read_pcm_frames_s16()\n    - drwav_open_file_and_read_pcm_frames_f32()\n    - drwav_open_file_and_read_pcm_frames_s32()\n    - drwav_open_file_and_read_pcm_frames_s16_w()\n    - drwav_open_file_and_read_pcm_frames_f32_w()\n    - drwav_open_file_and_read_pcm_frames_s32_w()\n    - drwav_open_memory_and_read_pcm_frames_s16()\n    - drwav_open_memory_and_read_pcm_frames_f32()\n    - drwav_open_memory_and_read_pcm_frames_s32()\n    Set this extra parameter to NULL to use defaults which is the same as the previous behaviour. Setting this NULL will use\n    DRWAV_MALLOC, DRWAV_REALLOC and DRWAV_FREE.\n  - Add support for reading and writing PCM frames in an explicit endianness. New APIs:\n    - drwav_read_pcm_frames_le()\n    - drwav_read_pcm_frames_be()\n    - drwav_read_pcm_frames_s16le()\n    - drwav_read_pcm_frames_s16be()\n    - drwav_read_pcm_frames_f32le()\n    - drwav_read_pcm_frames_f32be()\n    - drwav_read_pcm_frames_s32le()\n    - drwav_read_pcm_frames_s32be()\n    - drwav_write_pcm_frames_le()\n    - drwav_write_pcm_frames_be()\n  - Remove deprecated APIs.\n  - API CHANGE: The following APIs now return native-endian data. Previously they returned little-endian data.\n    - drwav_read_pcm_frames()\n    - drwav_read_pcm_frames_s16()\n    - drwav_read_pcm_frames_s32()\n    - drwav_read_pcm_frames_f32()\n    - drwav_open_and_read_pcm_frames_s16()\n    - drwav_open_and_read_pcm_frames_s32()\n    - drwav_open_and_read_pcm_frames_f32()\n    - drwav_open_file_and_read_pcm_frames_s16()\n    - drwav_open_file_and_read_pcm_frames_s32()\n    - drwav_open_file_and_read_pcm_frames_f32()\n    - drwav_open_file_and_read_pcm_frames_s16_w()\n    - drwav_open_file_and_read_pcm_frames_s32_w()\n    - drwav_open_file_and_read_pcm_frames_f32_w()\n    - drwav_open_memory_and_read_pcm_frames_s16()\n    - drwav_open_memory_and_read_pcm_frames_s32()\n    - drwav_open_memory_and_read_pcm_frames_f32()\n\nv0.10.1 - 2019-08-31\n  - Correctly handle partial trailing ADPCM blocks.\n\nv0.10.0 - 2019-08-04\n  - Remove deprecated APIs.\n  - Add wchar_t variants for file loading APIs:\n      drwav_init_file_w()\n      drwav_init_file_ex_w()\n      drwav_init_file_write_w()\n      drwav_init_file_write_sequential_w()\n  - Add drwav_target_write_size_bytes() which calculates the total size in bytes of a WAV file given a format and sample count.\n  - Add APIs for specifying the PCM frame count instead of the sample count when opening in sequential write mode:\n      drwav_init_write_sequential_pcm_frames()\n      drwav_init_file_write_sequential_pcm_frames()\n      drwav_init_file_write_sequential_pcm_frames_w()\n      drwav_init_memory_write_sequential_pcm_frames()\n  - Deprecate drwav_open*() and drwav_close():\n      drwav_open()\n      drwav_open_ex()\n      drwav_open_write()\n      drwav_open_write_sequential()\n      drwav_open_file()\n      drwav_open_file_ex()\n      drwav_open_file_write()\n      drwav_open_file_write_sequential()\n      drwav_open_memory()\n      drwav_open_memory_ex()\n      drwav_open_memory_write()\n      drwav_open_memory_write_sequential()\n      drwav_close()\n  - Minor documentation updates.\n\nv0.9.2 - 2019-05-21\n  - Fix warnings.\n\nv0.9.1 - 2019-05-05\n  - Add support for C89.\n  - Change license to choice of public domain or MIT-0.\n\nv0.9.0 - 2018-12-16\n  - API CHANGE: Add new reading APIs for reading by PCM frames instead of samples. Old APIs have been deprecated and\n    will be removed in v0.10.0. Deprecated APIs and their replacements:\n      drwav_read()                     -> drwav_read_pcm_frames()\n      drwav_read_s16()                 -> drwav_read_pcm_frames_s16()\n      drwav_read_f32()                 -> drwav_read_pcm_frames_f32()\n      drwav_read_s32()                 -> drwav_read_pcm_frames_s32()\n      drwav_seek_to_sample()           -> drwav_seek_to_pcm_frame()\n      drwav_write()                    -> drwav_write_pcm_frames()\n      drwav_open_and_read_s16()        -> drwav_open_and_read_pcm_frames_s16()\n      drwav_open_and_read_f32()        -> drwav_open_and_read_pcm_frames_f32()\n      drwav_open_and_read_s32()        -> drwav_open_and_read_pcm_frames_s32()\n      drwav_open_file_and_read_s16()   -> drwav_open_file_and_read_pcm_frames_s16()\n      drwav_open_file_and_read_f32()   -> drwav_open_file_and_read_pcm_frames_f32()\n      drwav_open_file_and_read_s32()   -> drwav_open_file_and_read_pcm_frames_s32()\n      drwav_open_memory_and_read_s16() -> drwav_open_memory_and_read_pcm_frames_s16()\n      drwav_open_memory_and_read_f32() -> drwav_open_memory_and_read_pcm_frames_f32()\n      drwav_open_memory_and_read_s32() -> drwav_open_memory_and_read_pcm_frames_s32()\n      drwav::totalSampleCount          -> drwav::totalPCMFrameCount\n  - API CHANGE: Rename drwav_open_and_read_file_*() to drwav_open_file_and_read_*().\n  - API CHANGE: Rename drwav_open_and_read_memory_*() to drwav_open_memory_and_read_*().\n  - Add built-in support for smpl chunks.\n  - Add support for firing a callback for each chunk in the file at initialization time.\n    - This is enabled through the drwav_init_ex(), etc. family of APIs.\n  - Handle invalid FMT chunks more robustly.\n\nv0.8.5 - 2018-09-11\n  - Const correctness.\n  - Fix a potential stack overflow.\n\nv0.8.4 - 2018-08-07\n  - Improve 64-bit detection.\n\nv0.8.3 - 2018-08-05\n  - Fix C++ build on older versions of GCC.\n\nv0.8.2 - 2018-08-02\n  - Fix some big-endian bugs.\n\nv0.8.1 - 2018-06-29\n  - Add support for sequential writing APIs.\n  - Disable seeking in write mode.\n  - Fix bugs with Wave64.\n  - Fix typos.\n\nv0.8 - 2018-04-27\n  - Bug fix.\n  - Start using major.minor.revision versioning.\n\nv0.7f - 2018-02-05\n  - Restrict ADPCM formats to a maximum of 2 channels.\n\nv0.7e - 2018-02-02\n  - Fix a crash.\n\nv0.7d - 2018-02-01\n  - Fix a crash.\n\nv0.7c - 2018-02-01\n  - Set drwav.bytesPerSample to 0 for all compressed formats.\n  - Fix a crash when reading 16-bit floating point WAV files. In this case dr_wav will output silence for\n    all format conversion reading APIs (*_s16, *_s32, *_f32 APIs).\n  - Fix some divide-by-zero errors.\n\nv0.7b - 2018-01-22\n  - Fix errors with seeking of compressed formats.\n  - Fix compilation error when DR_WAV_NO_CONVERSION_API\n\nv0.7a - 2017-11-17\n  - Fix some GCC warnings.\n\nv0.7 - 2017-11-04\n  - Add writing APIs.\n\nv0.6 - 2017-08-16\n  - API CHANGE: Rename dr_* types to drwav_*.\n  - Add support for custom implementations of malloc(), realloc(), etc.\n  - Add support for Microsoft ADPCM.\n  - Add support for IMA ADPCM (DVI, format code 0x11).\n  - Optimizations to drwav_read_s16().\n  - Bug fixes.\n\nv0.5g - 2017-07-16\n  - Change underlying type for booleans to unsigned.\n\nv0.5f - 2017-04-04\n  - Fix a minor bug with drwav_open_and_read_s16() and family.\n\nv0.5e - 2016-12-29\n  - Added support for reading samples as signed 16-bit integers. Use the _s16() family of APIs for this.\n  - Minor fixes to documentation.\n\nv0.5d - 2016-12-28\n  - Use drwav_int* and drwav_uint* sized types to improve compiler support.\n\nv0.5c - 2016-11-11\n  - Properly handle JUNK chunks that come before the FMT chunk.\n\nv0.5b - 2016-10-23\n  - A minor change to drwav_bool8 and drwav_bool32 types.\n\nv0.5a - 2016-10-11\n  - Fixed a bug with drwav_open_and_read() and family due to incorrect argument ordering.\n  - Improve A-law and mu-law efficiency.\n\nv0.5 - 2016-09-29\n  - API CHANGE. Swap the order of \"channels\" and \"sampleRate\" parameters in drwav_open_and_read*(). Rationale for this is to\n    keep it consistent with dr_audio and dr_flac.\n\nv0.4b - 2016-09-18\n  - Fixed a typo in documentation.\n\nv0.4a - 2016-09-18\n  - Fixed a typo.\n  - Change date format to ISO 8601 (YYYY-MM-DD)\n\nv0.4 - 2016-07-13\n  - API CHANGE. Make onSeek consistent with dr_flac.\n  - API CHANGE. Rename drwav_seek() to drwav_seek_to_sample() for clarity and consistency with dr_flac.\n  - Added support for Sony Wave64.\n\nv0.3a - 2016-05-28\n  - API CHANGE. Return drwav_bool32 instead of int in onSeek callback.\n  - Fixed a memory leak.\n\nv0.3 - 2016-05-22\n  - Lots of API changes for consistency.\n\nv0.2a - 2016-05-16\n  - Fixed Linux/GCC build.\n\nv0.2 - 2016-05-11\n  - Added support for reading data as signed 32-bit PCM for consistency with dr_flac.\n\nv0.1a - 2016-05-07\n  - Fixed a bug in drwav_open_file() where the file handle would not be closed if the loader failed to initialize.\n\nv0.1 - 2016-05-04\n  - Initial versioned release.\n*/\n\n/*\nThis software is available as a choice of the following licenses. Choose\nwhichever you prefer.\n\n===============================================================================\nALTERNATIVE 1 - Public Domain (www.unlicense.org)\n===============================================================================\nThis is free and unencumbered software released into the public domain.\n\nAnyone is free to copy, modify, publish, use, compile, sell, or distribute this\nsoftware, either in source code form or as a compiled binary, for any purpose,\ncommercial or non-commercial, and by any means.\n\nIn jurisdictions that recognize copyright laws, the author or authors of this\nsoftware dedicate any and all copyright interest in the software to the public\ndomain. We make this dedication for the benefit of the public at large and to\nthe detriment of our heirs and successors. We intend this dedication to be an\novert act of relinquishment in perpetuity of all present and future rights to\nthis software under copyright law.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN\nACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\nWITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\nFor more information, please refer to <http://unlicense.org/>\n\n===============================================================================\nALTERNATIVE 2 - MIT No Attribution\n===============================================================================\nCopyright 2020 David Reid\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of\nthis software and associated documentation files (the \"Software\"), to deal in\nthe Software without restriction, including without limitation the rights to\nuse, copy, modify, merge, publish, distribute, sublicense, and/or sell copies\nof the Software, and to permit persons to whom the Software is furnished to do\nso.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n*/\n"
  },
  {
    "path": "examples/esp32-rx/.gitignore",
    "content": "ggwave\nggwave.cpp\nfft.h\nresampler.h\nresampler.cpp\nreed-solomon\n"
  },
  {
    "path": "examples/esp32-rx/CMakeLists.txt",
    "content": "#\n# esp32-rx\n\n#configure_file(${CMAKE_SOURCE_DIR}/include/ggwave/ggwave.h   ${CMAKE_CURRENT_SOURCE_DIR}/ggwave.h              COPYONLY)\n#configure_file(${CMAKE_SOURCE_DIR}/src/ggwave.cpp            ${CMAKE_CURRENT_SOURCE_DIR}/ggwave.cpp            COPYONLY)\n#configure_file(${CMAKE_SOURCE_DIR}/src/fft.h                 ${CMAKE_CURRENT_SOURCE_DIR}/fft.h                 COPYONLY)\n#configure_file(${CMAKE_SOURCE_DIR}/src/reed-solomon/gf.hpp   ${CMAKE_CURRENT_SOURCE_DIR}/reed-solomon/gf.hpp   COPYONLY)\n#configure_file(${CMAKE_SOURCE_DIR}/src/reed-solomon/rs.hpp   ${CMAKE_CURRENT_SOURCE_DIR}/reed-solomon/rs.hpp   COPYONLY)\n#configure_file(${CMAKE_SOURCE_DIR}/src/reed-solomon/poly.hpp ${CMAKE_CURRENT_SOURCE_DIR}/reed-solomon/poly.hpp COPYONLY)\n#configure_file(${CMAKE_SOURCE_DIR}/src/reed-solomon/LICENSE  ${CMAKE_CURRENT_SOURCE_DIR}/reed-solomon/LICENSE  COPYONLY)\n"
  },
  {
    "path": "examples/esp32-rx/README.md",
    "content": "# esp32-rx\n\nThis is a sample project for receiving audio data using [ESP32](https://www.espressif.com/en/products/socs/esp32) microcontroller.\nThe chip has a built-in 12-bit ADC which is used to process the analog audio from the external microphone module in real-time.\nThe program also support input from I2S MEMS microphones that does not require the usage of the ADC.\nThe received messages are optionally displayed on the attached OLED display.\n\n## Setup\n\n- NodeMCU-ESP32\n- OLED SSD1306\n- Microphone, tested with the following, but others could be also supported:\n  - Analog:\n    - MAX9814\n    - KY-037\n    - KY-038\n    - WS Sound sensor\n  - I2S MEMS:\n    - SPH0645\n\n## Pinout\n\n### Analog Microphone\n\n| MCU     | Mic       |\n| ------- | --------- |\n| GND     | GND       |\n| 3.3V    | VCC / VDD |\n| GPIO 35 | Out       |\n\n### Digital (I2S) Microphone\n\n| MCU     | Mic         |\n| ------- | ----------- |\n| GND     | GND         |\n| 3.3V    | VCC / VDD   |\n| GPIO 26 | BCLK        |\n| GPIO 33 | Data / DOUT |\n| GPIO 25 | LRCL        |\n\n### I2C Display (optional)\n\n| MCU     | Display     |\n| ------- | ----------- |\n| GND     | GND         |\n| 3.3V    | VCC / VDD   |\n| GPIO 21 | SDA         |\n| GPIO 22 | SCL         |\n\n![Sketch-Breadboard](fritzing-sketch_bb.png)\n\n![Sketch-photo](https://user-images.githubusercontent.com/1991296/177842221-411c77a4-09cd-43b7-988f-44eebbad8f8c.JPEG)\n\n## Demo\n\nhttps://user-images.githubusercontent.com/1991296/177211906-2102e9fa-8203-4b80-82e6-4839bf66f01f.mp4\n\n[Watch high quality on Youtube](https://youtu.be/38JoMwdpH6I)\n"
  },
  {
    "path": "examples/esp32-rx/esp32-rx.ino",
    "content": "// esp32-rx\n//\n// Sample sketch for receiving sound data using \"ggwave\"\n//\n// Tested MCU boards:\n//   - NodeMCU-ESP32-S\n//\n// Tested analog microphones:\n//   - MAX9814\n//   - KY-037\n//   - KY-038\n//   - WS Sound sensor\n//\n// Tested I2S microphones:\n//   - Adafruit I2S SPH0645\n//\n// The ESP32 microcontroller has a built-in 12-bit ADC which is used to digitalize the analog signal\n// from the external analog microphone. When I2S microphone is used, the ADC is not used.\n//\n// The sketch optionally supports displaying the received \"ggwave\" data on an OLED display.\n// Use the DISPLAY_OUTPUT macro to enable or disable this functionality.\n//\n// If you don't have a display, you can simply observe the decoded data in the serial monitor.\n//\n// If you want to perform a quick test, you can use the free \"Waver\" application:\n//   - Web:     https://waver.ggerganov.com\n//   - Android: https://play.google.com/store/apps/details?id=com.ggerganov.Waver\n//   - iOS:     https://apps.apple.com/us/app/waver-data-over-sound/id1543607865\n//\n// Make sure to enable the \"Fixed-length\" option in \"Waver\"'s settings and set the number of\n// bytes to be equal to \"payloadLength\" used in the sketch. Also, select a protocol that is\n// listed as Rx in the current sketch.\n//\n// Demo: https://youtu.be/38JoMwdpH6I\n//\n// Sketch: https://github.com/ggerganov/ggwave/tree/master/examples/esp32-rx\n//\n// ## Pinout\n//\n// ### Analog Microphone\n//\n// | MCU     | Mic       |\n// | ------- | --------- |\n// | GND     | GND       |\n// | 3.3V    | VCC / VDD |\n// | GPIO 35 | Out       |\n//\n// ### Digital (I2S) Microphone\n//\n// | MCU     | Mic         |\n// | ------- | ----------- |\n// | GND     | GND         |\n// | 3.3V    | VCC / VDD   |\n// | GPIO 26 | BCLK        |\n// | GPIO 33 | Data / DOUT |\n// | GPIO 25 | LRCL        |\n//\n// ### I2C Display (optional)\n//\n// | MCU     | Display   |\n// | ------- | --------- |\n// | GND     | GND       |\n// | 3.3V    | VCC / VDD |\n// | GPIO 21 | SDA       |\n// | GPIO 22 | SCL       |\n//\n\n// Uncomment the line coresponding to your microhpone\n#define MIC_ANALOG\n//#define MIC_I2S\n//#define MIC_I2S_SPH0645\n\n// Uncoment this line to enable SSD1306 display output\n//#define DISPLAY_OUTPUT 1\n\n// Uncoment this line to enable long-range transmission\n// These protocols are slower and use more memory to decode, but are much more robust\n//#define LONG_RANGE 1\n\n#include <ggwave.h>\n\n#include <soc/adc_channel.h>\n#include <driver/i2s.h>\n\n// Pin configuration\nconst int kPinLED0 = 2;\n\n// Global GGwave instance\nGGWave ggwave;\n\n// Audio capture configuration\nusing TSample      = int16_t;\n#if defined(MIC_ANALOG)\nusing TSampleInput = int16_t;\n#elif defined(MIC_I2S) || defined(MIC_I2S_SPH0645)\nusing TSampleInput = int32_t;\n#endif\n\nconst size_t kSampleSize_bytes = sizeof(TSample);\n\n// High sample rate - better quality, but more CPU/Memory usage\nconst int sampleRate = 24000;\nconst int samplesPerFrame = 512;\n\n// Low sample rate\n// Only MT protocols will work in this mode\n//const int sampleRate = 12000;\n//const int samplesPerFrame = 256;\n\nTSample sampleBuffer[samplesPerFrame];\n\n// helper buffer for data input in different formats:\n#if defined(MIC_ANALOG)\nTSampleInput * sampleBufferRaw = sampleBuffer;\n#elif defined(MIC_I2S) || defined(MIC_I2S_SPH0645)\nTSampleInput sampleBufferRaw[samplesPerFrame];\n#endif\n\nconst i2s_port_t i2s_port = I2S_NUM_0;\n\n#if defined(MIC_ANALOG)\n// ADC configuration\nconst adc_unit_t     adc_unit    = ADC_UNIT_1;\nconst adc1_channel_t adc_channel = ADC1_GPIO35_CHANNEL;\n\n// i2s config for using the internal ADC\nconst i2s_config_t i2s_config = {\n    .mode                 = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_RX | I2S_MODE_ADC_BUILT_IN),\n    .sample_rate          = sampleRate,\n    .bits_per_sample      = I2S_BITS_PER_SAMPLE_16BIT,\n    .channel_format       = I2S_CHANNEL_FMT_ONLY_RIGHT,\n    .communication_format = I2S_COMM_FORMAT_I2S_LSB,\n    .intr_alloc_flags     = ESP_INTR_FLAG_LEVEL1,\n    .dma_buf_count        = 4,\n    .dma_buf_len          = samplesPerFrame,\n    .use_apll             = false,\n    .tx_desc_auto_clear   = false,\n    .fixed_mclk           = 0\n};\n#endif\n\n#if defined(MIC_I2S) || defined(MIC_I2S_SPH0645)\n// i2s config for using I2S mic input from RIGHT channel\nconst i2s_config_t i2s_config = {\n    .mode                     = i2s_mode_t(I2S_MODE_MASTER | I2S_MODE_RX),\n    .sample_rate              = sampleRate,\n    .bits_per_sample          = I2S_BITS_PER_SAMPLE_32BIT,\n    .channel_format           = I2S_CHANNEL_FMT_ONLY_RIGHT,\n    .communication_format     = i2s_comm_format_t(I2S_COMM_FORMAT_I2S | I2S_COMM_FORMAT_I2S_MSB),\n    .intr_alloc_flags         = ESP_INTR_FLAG_LEVEL1,\n    .dma_buf_count            = 4,\n    .dma_buf_len              = samplesPerFrame,\n    .use_apll                 = false,\n    .tx_desc_auto_clear       = false,\n    .fixed_mclk               = 0\n};\n\n// The pin config as per the setup\nconst i2s_pin_config_t pin_config = {\n    .bck_io_num   = 26,   // Serial Clock (SCK)\n    .ws_io_num    = 25,    // Word Select (WS)\n    .data_out_num = I2S_PIN_NO_CHANGE, // not used (only for speakers)\n    .data_in_num  = 33   // Serial Data (SD)\n};\n#endif\n\n#ifdef DISPLAY_OUTPUT\n\n#include <SPI.h>\n#include <Wire.h>\n#include <Adafruit_GFX.h>\n#include <Adafruit_SSD1306.h>\n\n#define SCREEN_WIDTH 128 // OLED display width, in pixels\n#define SCREEN_HEIGHT 32 // OLED display height, in pixels\n\n// Declaration for an SSD1306 display connected to I2C (SDA, SCL pins)\n// The pins for I2C are defined by the Wire-library.\n// On an arduino UNO:       A4(SDA), A5(SCL)\n// On an arduino MEGA 2560: 20(SDA), 21(SCL)\n// On an arduino LEONARDO:   2(SDA),  3(SCL), ...\n#define OLED_RESET     -1 // Reset pin # (or -1 if sharing Arduino reset pin)\n#define SCREEN_ADDRESS 0x3C ///< See datasheet for Address; 0x3D for 128x64, 0x3C for 128x32\nAdafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);\n\n#endif\n\nvoid setup() {\n    Serial.begin(115200);\n    while (!Serial);\n\n    pinMode(kPinLED0, OUTPUT);\n    digitalWrite(kPinLED0, LOW);\n\n#ifdef DISPLAY_OUTPUT\n    {\n        Serial.println(F(\"Initializing display...\"));\n\n        // SSD1306_SWITCHCAPVCC = generate display voltage from 3.3V internally\n        if(!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) {\n            Serial.println(F(\"SSD1306 allocation failed\"));\n            for(;;); // Don't proceed, loop forever\n        }\n\n        // Show initial display buffer contents on the screen --\n        // the library initializes this with an Adafruit splash screen.\n        //display.display();\n        //delay(2000); // Pause for 2 seconds\n\n        // Clear the buffer\n        display.clearDisplay();\n\n        display.setTextSize(2);\n        display.setTextColor(SSD1306_WHITE); // Draw white text\n        display.setCursor(0, 0);     // Start at top-left corner\n        display.println(F(\"GGWave!\"));\n        display.setTextSize(1);\n        display.println(F(\"\"));\n        display.println(F(\"Listening...\"));\n\n        display.display();\n    }\n#endif\n\n    // Initialize \"ggwave\"\n    {\n        Serial.println(F(\"Trying to initialize the ggwave instance\"));\n\n        ggwave.setLogFile(nullptr);\n\n        auto p = GGWave::getDefaultParameters();\n\n        // Adjust the \"ggwave\" parameters to your needs.\n        // Make sure that the \"payloadLength\" parameter matches the one used on the transmitting side.\n#ifdef LONG_RANGE\n        // The \"FAST\" protocols require 2x more memory, so we reduce the payload length to compensate:\n        p.payloadLength   = 8;\n#else\n        p.payloadLength   = 16;\n#endif\n        Serial.print(F(\"Using payload length: \"));\n        Serial.println(p.payloadLength);\n\n        p.sampleRateInp   = sampleRate;\n        p.sampleRateOut   = sampleRate;\n        p.sampleRate      = sampleRate;\n        p.samplesPerFrame = samplesPerFrame;\n        p.sampleFormatInp = GGWAVE_SAMPLE_FORMAT_I16;\n        p.sampleFormatOut = GGWAVE_SAMPLE_FORMAT_U8;\n        p.operatingMode   = GGWAVE_OPERATING_MODE_RX | GGWAVE_OPERATING_MODE_TX | GGWAVE_OPERATING_MODE_USE_DSS | GGWAVE_OPERATING_MODE_TX_ONLY_TONES;\n\n        // Protocols to use for TX\n        // Remove the ones that you don't need to reduce memory usage\n        GGWave::Protocols::tx().disableAll();\n        //GGWave::Protocols::tx().toggle(GGWAVE_PROTOCOL_MT_NORMAL,  true);\n        //GGWave::Protocols::tx().toggle(GGWAVE_PROTOCOL_MT_FAST,    true);\n        GGWave::Protocols::tx().toggle(GGWAVE_PROTOCOL_MT_FASTEST, true);\n\n        // Protocols to use for RX\n        // Remove the ones that you don't need to reduce memory usage\n        GGWave::Protocols::rx().disableAll();\n        //GGWave::Protocols::rx().toggle(GGWAVE_PROTOCOL_DT_NORMAL,  true);\n#ifdef LONG_RANGE\n        GGWave::Protocols::rx().toggle(GGWAVE_PROTOCOL_DT_FAST,    true);\n#endif\n        GGWave::Protocols::rx().toggle(GGWAVE_PROTOCOL_DT_FASTEST, true);\n        //GGWave::Protocols::rx().toggle(GGWAVE_PROTOCOL_MT_NORMAL,  true);\n#ifdef LONG_RANGE\n        GGWave::Protocols::rx().toggle(GGWAVE_PROTOCOL_MT_FAST,    true);\n#endif\n        GGWave::Protocols::rx().toggle(GGWAVE_PROTOCOL_MT_FASTEST, true);\n\n        // Print the memory required for the \"ggwave\" instance:\n        ggwave.prepare(p, false);\n\n        Serial.print(F(\"Required memory by the ggwave instance: \"));\n        Serial.print(ggwave.heapSize());\n        Serial.println(F(\" bytes\"));\n\n        // Initialize the \"ggwave\" instance:\n        ggwave.prepare(p, true);\n        Serial.print(F(\"Instance initialized successfully! Memory used: \"));\n    }\n\n    // Start capturing audio\n    {\n        Serial.println(F(\"Initializing I2S interface\"));\n\n        // Install and start i2s driver\n        i2s_driver_install(i2s_port, &i2s_config, 0, NULL);\n\n#if defined(MIC_ANALOG)\n        Serial.println(F(\"Using analog input - initializing ADC\"));\n\n        // Init ADC pad\n        i2s_set_adc_mode(adc_unit, adc_channel);\n\n        // Enable the adc\n        i2s_adc_enable(i2s_port);\n\n        Serial.println(F(\"I2S ADC started\"));\n#endif\n\n#if defined(MIC_I2S) || defined(MIC_I2S_SPH0645)\n        Serial.println(F(\"Using I2S input\"));\n\n#if defined(MIC_I2S_SPH0645)\n        Serial.println(F(\"Applying fix for SPH0645\"));\n\n        // https://github.com/atomic14/esp32_audio/blob/d2ac3490c0836cb46a69c83b0570873de18f695e/i2s_sampling/src/I2SMEMSSampler.cpp#L17-L22\n        REG_SET_BIT(I2S_TIMING_REG(i2s_port), BIT(9));\n        REG_SET_BIT(I2S_CONF_REG(i2s_port), I2S_RX_MSB_SHIFT);\n#endif\n\n        i2s_set_pin(i2s_port, &pin_config);\n#endif\n    }\n}\n\nint niter = 0;\nint tLastReceive = -10000;\n\nGGWave::TxRxData result;\n\nvoid loop() {\n    // Read from i2s\n    {\n        size_t bytes_read = 0;\n        i2s_read(i2s_port, sampleBufferRaw, sizeof(TSampleInput)*samplesPerFrame, &bytes_read, portMAX_DELAY);\n\n        int nSamples = bytes_read/sizeof(TSampleInput);\n        if (nSamples != samplesPerFrame) {\n            Serial.println(\"Failed to read samples\");\n            return;\n        }\n\n#if defined(MIC_ANALOG)\n        // the ADC samples are 12-bit so we need to do some massaging to make them 16-bit\n        for (int i = 0; i < nSamples; i += 2) {\n            auto & s0 = sampleBuffer[i];\n            auto & s1 = sampleBuffer[i + 1];\n\n            s0 = s0 & 0x0fff;\n            s1 = s1 & 0x0fff;\n\n            s0 = s0 ^ s1;\n            s1 = s0 ^ s1;\n            s0 = s0 ^ s1;\n        }\n#endif\n\n#if defined(MIC_I2S) || defined(MIC_I2S_SPH0645)\n        for (int i = 0; i < nSamples; ++i) {\n            sampleBuffer[i] = (sampleBufferRaw[i] & 0xFFFFFFF0) >> 11;\n        }\n#endif\n    }\n\n    // Use this with the serial plotter to observe real-time audio signal\n    //for (int i = 0; i < nSamples; i++) {\n    //    Serial.println(sampleBuffer[i]);\n    //}\n\n    // Try to decode any \"ggwave\" data:\n    auto tStart = millis();\n\n    if (ggwave.decode(sampleBuffer, samplesPerFrame*kSampleSize_bytes) == false) {\n        Serial.println(\"Failed to decode\");\n    }\n\n    auto tEnd = millis();\n\n    if (++niter % 10 == 0) {\n        // print the time it took the last decode() call to complete\n        // should be smaller than samplesPerFrame/sampleRate seconds\n        // for example: samplesPerFrame = 128, sampleRate = 6000 => not more than 20 ms\n        Serial.println(tEnd - tStart);\n        if (tEnd - tStart > 1000*(float(samplesPerFrame)/sampleRate)) {\n            Serial.println(F(\"Warning: decode() took too long to execute!\"));\n        }\n    }\n\n    // Check if we have successfully decoded any data:\n    int nr = ggwave.rxTakeData(result);\n    if (nr > 0) {\n        Serial.println(tEnd - tStart);\n        Serial.print(F(\"Received data with length \"));\n        Serial.print(nr); // should be equal to p.payloadLength\n        Serial.println(F(\" bytes:\"));\n\n        Serial.println((char *) result.data());\n\n        tLastReceive = tEnd;\n    }\n\n#ifdef DISPLAY_OUTPUT\n    const auto t = millis();\n\n    static GGWave::Spectrum rxSpectrum;\n    if (ggwave.rxTakeSpectrum(rxSpectrum) && t > 2000) {\n        const bool isNew = t - tLastReceive < 2000;\n\n        if (isNew) {\n            digitalWrite(kPinLED0, HIGH);\n        } else {\n            digitalWrite(kPinLED0, LOW);\n        }\n\n        display.clearDisplay();\n\n        display.setTextSize(isNew ? 2 : 1);\n        display.setTextColor(SSD1306_WHITE);\n        display.setCursor(0, 0);\n        display.println((char *) result.data());\n\n        const int nBin0 = 16;\n        const int nBins = 64;\n        const int dX = SCREEN_WIDTH/nBins;\n\n        float smax = 0.0f;\n        for (int x = 0; x < nBins; x++) {\n            smax = std::max(smax, rxSpectrum[nBin0 + x]);\n        }\n        smax = smax == 0.0f ? 1.0f : 1.0f/smax;\n\n        const float h = isNew ? 0.25f: 0.75f;\n        for (int x = 0; x < nBins; x++) {\n            const int x0 = x*dX;\n            const int x1 = x0 + dX;\n            const int y = (int) (h*SCREEN_HEIGHT*(rxSpectrum[nBin0 + x]*smax));\n            display.fillRect(x0, SCREEN_HEIGHT - y, dX, y, SSD1306_WHITE);\n        }\n\n        display.display();\n    }\n#endif\n}\n"
  },
  {
    "path": "examples/ggwave-cli/CMakeLists.txt",
    "content": "add_executable(ggwave-cli main.cpp)\n\ntarget_include_directories(ggwave-cli PRIVATE\n    ..\n    ${SDL2_INCLUDE_DIRS}\n    )\n\ntarget_link_libraries(ggwave-cli PRIVATE\n    ggwave\n    ggwave-common\n    ggwave-common-sdl2\n    ${CMAKE_THREAD_LIBS_INIT}\n    )\n"
  },
  {
    "path": "examples/ggwave-cli/README.md",
    "content": "# ggwave-cli\n\nA basic command line tool for sending and receiving `ggwave` data.\n\n![ggwave-cli](https://i.imgur.com/fhNggnq.png)\n"
  },
  {
    "path": "examples/ggwave-cli/main.cpp",
    "content": "#include \"ggwave/ggwave.h\"\n\n#include \"ggwave-common.h\"\n#include \"ggwave-common-sdl2.h\"\n\n#include <SDL.h>\n\n#include <cstdio>\n#include <string>\n\n#include <mutex>\n#include <thread>\n#include <iostream>\n\nint main(int argc, char** argv) {\n    printf(\"Usage: %s [-cN] [-pN] [-tN] [-lN]\\n\", argv[0]);\n    printf(\"    -cN - select capture device N\\n\");\n    printf(\"    -pN - select playback device N\\n\");\n    printf(\"    -tN - transmission protocol\\n\");\n    printf(\"    -lN - fixed payload length of size N, N in [1, %d]\\n\", GGWave::kMaxLengthFixed);\n    printf(\"    -d  - use Direct Sequence Spread (DSS)\\n\");\n    printf(\"    -v  - print generated tones on resend\\n\");\n    printf(\"\\n\");\n\n    const auto argm          = parseCmdArguments(argc, argv);\n    const int  captureId     = argm.count(\"c\") == 0 ?  0 : std::stoi(argm.at(\"c\"));\n    const int  playbackId    = argm.count(\"p\") == 0 ?  0 : std::stoi(argm.at(\"p\"));\n    const int  txProtocolId  = argm.count(\"t\") == 0 ?  1 : std::stoi(argm.at(\"t\"));\n    const int  payloadLength = argm.count(\"l\") == 0 ? -1 : std::stoi(argm.at(\"l\"));\n    const bool useDSS        = argm.count(\"d\") >  0;\n    const bool printTones    = argm.count(\"v\") >  0;\n\n    if (GGWave_init(playbackId, captureId, payloadLength, 0.0f, useDSS) == false) {\n        fprintf(stderr, \"Failed to initialize GGWave\\n\");\n        return -1;\n    }\n\n    auto ggWave = GGWave_instance();\n\n    printf(\"Available Tx protocols:\\n\");\n    const auto & protocols = GGWave::Protocols::kDefault();\n    for (int i = 0; i < (int) protocols.size(); ++i) {\n        const auto & protocol = protocols[i];\n        if (protocol.enabled == false) {\n            continue;\n        }\n        printf(\"      %d - %s\\n\", i, protocol.name);\n    }\n\n    if (txProtocolId < 0) {\n        fprintf(stderr, \"Unknown Tx protocol %d\\n\", txProtocolId);\n        return -3;\n    }\n\n    printf(\"Selecting Tx protocol %d\\n\", txProtocolId);\n\n    std::mutex mutex;\n    std::thread inputThread([&]() {\n        std::string inputOld = \"\";\n        while (true) {\n            std::string input;\n            printf(\"Enter text: \");\n            fflush(stdout);\n            getline(std::cin, input);\n            if (input.empty()) {\n                printf(\"Re-sending ...\\n\");\n                input = inputOld;\n\n                if (printTones) {\n                    printf(\"Printing generated waveform tones (Hz):\\n\");\n                    const auto & protocol = protocols[txProtocolId];\n                    const auto tones = ggWave->txTones();\n                    for (int i = 0; i < (int) tones.size(); ++i) {\n                        if (tones[i] < 0) {\n                            printf(\" - end tx\\n\");\n                            continue;\n                        }\n                        const auto freq_hz = (protocol.freqStart + tones[i])*ggWave->hzPerSample();\n                        printf(\" - tone %3d: %f\\n\", i, freq_hz);\n                    }\n                }\n            } else {\n                printf(\"Sending ...\\n\");\n            }\n            {\n                std::lock_guard<std::mutex> lock(mutex);\n                ggWave->init(input.size(), input.data(), GGWave::TxProtocolId(txProtocolId), 10);\n            }\n            inputOld = input;\n        }\n    });\n\n    while (true) {\n        std::this_thread::sleep_for(std::chrono::milliseconds(1));\n        {\n            std::lock_guard<std::mutex> lock(mutex);\n            GGWave_mainLoop();\n        }\n    }\n\n    inputThread.join();\n\n    GGWave_deinit();\n\n    SDL_CloseAudio();\n    SDL_Quit();\n\n    return 0;\n}\n"
  },
  {
    "path": "examples/ggwave-common-sdl2.cpp",
    "content": "#include \"ggwave-common-sdl2.h\"\n\n#include \"ggwave-common.h\"\n\n#include \"ggwave/ggwave.h\"\n\n#include <SDL.h>\n#include <SDL_opengl.h>\n\n#include <chrono>\n\n#ifdef __EMSCRIPTEN__\n#include <emscripten.h>\n#else\n#define EMSCRIPTEN_KEEPALIVE\n#endif\n\nnamespace {\n\nstd::string g_defaultCaptureDeviceName = \"\";\n\nSDL_AudioDeviceID g_devIdInp = 0;\nSDL_AudioDeviceID g_devIdOut = 0;\n\nSDL_AudioSpec g_obtainedSpecInp;\nSDL_AudioSpec g_obtainedSpecOut;\n\nstd::shared_ptr<GGWave> g_ggWave = nullptr;\n\n}\n\n// JS interface\n\nextern \"C\" {\n    EMSCRIPTEN_KEEPALIVE\n        int sendData(int textLength, const char * text, int protocolId, int volume) {\n            g_ggWave->init(textLength, text, GGWave::TxProtocolId(protocolId), volume);\n            return 0;\n        }\n\n    EMSCRIPTEN_KEEPALIVE\n        int getText(char * text) {\n            std::copy(g_ggWave->rxData().begin(), g_ggWave->rxData().end(), text);\n            return 0;\n        }\n\n    EMSCRIPTEN_KEEPALIVE\n        float sampleRate()        { return g_ggWave->sampleRateInp(); }\n\n    EMSCRIPTEN_KEEPALIVE\n        int framesToRecord()      { return g_ggWave->rxFramesToRecord(); }\n\n    EMSCRIPTEN_KEEPALIVE\n        int framesLeftToRecord()  { return g_ggWave->rxFramesLeftToRecord(); }\n\n    EMSCRIPTEN_KEEPALIVE\n        int framesToAnalyze()     { return g_ggWave->rxFramesToAnalyze(); }\n\n    EMSCRIPTEN_KEEPALIVE\n        int framesLeftToAnalyze() { return g_ggWave->rxFramesLeftToAnalyze(); }\n\n    EMSCRIPTEN_KEEPALIVE\n        int hasDeviceOutput()     { return g_devIdOut; }\n\n    EMSCRIPTEN_KEEPALIVE\n        int hasDeviceCapture()    { return g_devIdInp; }\n\n    EMSCRIPTEN_KEEPALIVE\n        int doInit()              { return GGWave_init(-1, -1); }\n}\n\nvoid GGWave_setDefaultCaptureDeviceName(std::string name) {\n    g_defaultCaptureDeviceName = std::move(name);\n}\n\nbool GGWave_init(\n        const int playbackId,\n        const int captureId,\n        const int payloadLength,\n        const float sampleRateOffset,\n        const bool useDSS) {\n\n    if (g_devIdInp && g_devIdOut) {\n        return false;\n    }\n\n    if (g_devIdInp == 0 && g_devIdOut == 0) {\n        SDL_LogSetPriority(SDL_LOG_CATEGORY_APPLICATION, SDL_LOG_PRIORITY_INFO);\n\n        if (SDL_Init(SDL_INIT_AUDIO) < 0) {\n            SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, \"Couldn't initialize SDL: %s\\n\", SDL_GetError());\n            return (1);\n        }\n\n        SDL_SetHintWithPriority(SDL_HINT_AUDIO_RESAMPLING_MODE, \"medium\", SDL_HINT_OVERRIDE);\n\n        {\n            int nDevices = SDL_GetNumAudioDevices(SDL_FALSE);\n            printf(\"Found %d playback devices:\\n\", nDevices);\n            for (int i = 0; i < nDevices; i++) {\n                printf(\"    - Playback device #%d: '%s'\\n\", i, SDL_GetAudioDeviceName(i, SDL_FALSE));\n            }\n        }\n        {\n            int nDevices = SDL_GetNumAudioDevices(SDL_TRUE);\n            printf(\"Found %d capture devices:\\n\", nDevices);\n            for (int i = 0; i < nDevices; i++) {\n                printf(\"    - Capture device #%d: '%s'\\n\", i, SDL_GetAudioDeviceName(i, SDL_TRUE));\n            }\n        }\n    }\n\n    bool reinit = false;\n\n    if (g_devIdOut == 0) {\n        printf(\"Initializing playback ...\\n\");\n\n        SDL_AudioSpec playbackSpec;\n        SDL_zero(playbackSpec);\n\n        playbackSpec.freq = GGWave::kDefaultSampleRate + sampleRateOffset;\n        playbackSpec.format = AUDIO_S16SYS;\n        playbackSpec.channels = 1;\n        playbackSpec.samples = 16*1024;\n        playbackSpec.callback = NULL;\n\n        SDL_zero(g_obtainedSpecOut);\n\n        if (playbackId >= 0) {\n            printf(\"Attempt to open playback device %d : '%s' ...\\n\", playbackId, SDL_GetAudioDeviceName(playbackId, SDL_FALSE));\n            g_devIdOut = SDL_OpenAudioDevice(SDL_GetAudioDeviceName(playbackId, SDL_FALSE), SDL_FALSE, &playbackSpec, &g_obtainedSpecOut, 0);\n        } else {\n            printf(\"Attempt to open default playback device ...\\n\");\n            g_devIdOut = SDL_OpenAudioDevice(NULL, SDL_FALSE, &playbackSpec, &g_obtainedSpecOut, 0);\n        }\n\n        if (!g_devIdOut) {\n            printf(\"Couldn't open an audio device for playback: %s!\\n\", SDL_GetError());\n            g_devIdOut = 0;\n        } else {\n            printf(\"Obtained spec for output device (SDL Id = %d):\\n\", g_devIdOut);\n            printf(\"    - Sample rate:       %d (required: %d)\\n\", g_obtainedSpecOut.freq, playbackSpec.freq);\n            printf(\"    - Format:            %d (required: %d)\\n\", g_obtainedSpecOut.format, playbackSpec.format);\n            printf(\"    - Channels:          %d (required: %d)\\n\", g_obtainedSpecOut.channels, playbackSpec.channels);\n            printf(\"    - Samples per frame: %d (required: %d)\\n\", g_obtainedSpecOut.samples, playbackSpec.samples);\n\n            if (g_obtainedSpecOut.format != playbackSpec.format ||\n                g_obtainedSpecOut.channels != playbackSpec.channels ||\n                g_obtainedSpecOut.samples != playbackSpec.samples) {\n                g_devIdOut = 0;\n                SDL_CloseAudio();\n                fprintf(stderr, \"Failed to initialize playback SDL_OpenAudio!\");\n\n                return false;\n            }\n\n            reinit = true;\n        }\n    }\n\n    if (g_devIdInp == 0) {\n        SDL_AudioSpec captureSpec;\n        captureSpec = g_obtainedSpecOut;\n        captureSpec.freq = GGWave::kDefaultSampleRate + sampleRateOffset;\n        captureSpec.format = AUDIO_F32SYS;\n        captureSpec.samples = 1024;\n\n        SDL_zero(g_obtainedSpecInp);\n\n        if (captureId >= 0) {\n            printf(\"Attempt to open capture device %d : '%s' ...\\n\", captureId, SDL_GetAudioDeviceName(captureId, SDL_TRUE));\n            g_devIdInp = SDL_OpenAudioDevice(SDL_GetAudioDeviceName(captureId, SDL_TRUE), SDL_TRUE, &captureSpec, &g_obtainedSpecInp, 0);\n        } else {\n            printf(\"Attempt to open default capture device ...\\n\");\n            g_devIdInp = SDL_OpenAudioDevice(g_defaultCaptureDeviceName.empty() ? nullptr : g_defaultCaptureDeviceName.c_str(),\n                                            SDL_TRUE, &captureSpec, &g_obtainedSpecInp, 0);\n        }\n        if (!g_devIdInp) {\n            printf(\"Couldn't open an audio device for capture: %s!\\n\", SDL_GetError());\n            g_devIdInp = 0;\n        } else {\n            printf(\"Obtained spec for input device (SDL Id = %d):\\n\", g_devIdInp);\n            printf(\"    - Sample rate:       %d\\n\", g_obtainedSpecInp.freq);\n            printf(\"    - Format:            %d (required: %d)\\n\", g_obtainedSpecInp.format, captureSpec.format);\n            printf(\"    - Channels:          %d (required: %d)\\n\", g_obtainedSpecInp.channels, captureSpec.channels);\n            printf(\"    - Samples per frame: %d\\n\", g_obtainedSpecInp.samples);\n\n            reinit = true;\n        }\n    }\n\n    GGWave::SampleFormat sampleFormatInp = GGWAVE_SAMPLE_FORMAT_UNDEFINED;\n    GGWave::SampleFormat sampleFormatOut = GGWAVE_SAMPLE_FORMAT_UNDEFINED;\n\n    switch (g_obtainedSpecInp.format) {\n        case AUDIO_U8:      sampleFormatInp = GGWAVE_SAMPLE_FORMAT_U8;  break;\n        case AUDIO_S8:      sampleFormatInp = GGWAVE_SAMPLE_FORMAT_I8;  break;\n        case AUDIO_U16SYS:  sampleFormatInp = GGWAVE_SAMPLE_FORMAT_U16; break;\n        case AUDIO_S16SYS:  sampleFormatInp = GGWAVE_SAMPLE_FORMAT_I16; break;\n        case AUDIO_S32SYS:  sampleFormatInp = GGWAVE_SAMPLE_FORMAT_F32; break;\n        case AUDIO_F32SYS:  sampleFormatInp = GGWAVE_SAMPLE_FORMAT_F32; break;\n    }\n\n    switch (g_obtainedSpecOut.format) {\n        case AUDIO_U8:      sampleFormatOut = GGWAVE_SAMPLE_FORMAT_U8;  break;\n        case AUDIO_S8:      sampleFormatOut = GGWAVE_SAMPLE_FORMAT_I8;  break;\n        case AUDIO_U16SYS:  sampleFormatOut = GGWAVE_SAMPLE_FORMAT_U16; break;\n        case AUDIO_S16SYS:  sampleFormatOut = GGWAVE_SAMPLE_FORMAT_I16; break;\n        case AUDIO_S32SYS:  sampleFormatOut = GGWAVE_SAMPLE_FORMAT_F32; break;\n        case AUDIO_F32SYS:  sampleFormatOut = GGWAVE_SAMPLE_FORMAT_F32; break;\n            break;\n    }\n\n    if (reinit) {\n        GGWave::OperatingMode mode = GGWAVE_OPERATING_MODE_RX_AND_TX;\n        if (useDSS) mode |= GGWAVE_OPERATING_MODE_USE_DSS;\n\n        g_ggWave = std::make_shared<GGWave>(GGWave::Parameters {\n            payloadLength,\n            (float) g_obtainedSpecInp.freq,\n            (float) g_obtainedSpecOut.freq,\n            GGWave::kDefaultSampleRate,\n            GGWave::kDefaultSamplesPerFrame,\n            GGWave::kDefaultSoundMarkerThreshold,\n            sampleFormatInp,\n            sampleFormatOut,\n            mode,\n        });\n    }\n\n    return true;\n}\n\nstd::shared_ptr<GGWave> GGWave_instance() { return g_ggWave; }\n\nvoid GGWave_reset(void * parameters) {\n    g_ggWave = std::make_shared<GGWave>(*(GGWave::Parameters *)(parameters));\n}\n\nbool GGWave_mainLoop() {\n    if (g_devIdInp == 0 && g_devIdOut == 0) {\n        return false;\n    }\n\n    if (g_ggWave->txHasData() == false) {\n        SDL_PauseAudioDevice(g_devIdOut, SDL_FALSE);\n\n        static auto tLastNoData = std::chrono::high_resolution_clock::now();\n        auto tNow = std::chrono::high_resolution_clock::now();\n\n        if ((int) SDL_GetQueuedAudioSize(g_devIdOut) < g_ggWave->samplesPerFrame()*g_ggWave->sampleSizeOut()) {\n            SDL_PauseAudioDevice(g_devIdInp, SDL_FALSE);\n            const int nHave = (int) SDL_GetQueuedAudioSize(g_devIdInp);\n            const int nNeed = g_ggWave->samplesPerFrame()*g_ggWave->sampleSizeInp();\n            if (::getTime_ms(tLastNoData, tNow) > 500.0f && nHave >= nNeed) {\n                static std::vector<uint8_t> dataInp(nNeed);\n                SDL_DequeueAudio(g_devIdInp, dataInp.data(), nNeed);\n\n                if (g_ggWave->decode(dataInp.data(), dataInp.size()) == false) {\n                    fprintf(stderr, \"Warning: failed to decode input data!\\n\");\n                }\n\n                if (nHave > 32*nNeed) {\n                    fprintf(stderr, \"Warning: slow processing, clearing queued audio buffer of %d bytes ...\\n\", SDL_GetQueuedAudioSize(g_devIdInp));\n                    SDL_ClearQueuedAudio(g_devIdInp);\n                }\n            } else {\n                SDL_ClearQueuedAudio(g_devIdInp);\n            }\n        } else {\n            tLastNoData = tNow;\n        }\n    } else {\n        SDL_PauseAudioDevice(g_devIdOut, SDL_TRUE);\n        SDL_PauseAudioDevice(g_devIdInp, SDL_TRUE);\n\n        const auto nBytes = g_ggWave->encode();\n        SDL_QueueAudio(g_devIdOut, g_ggWave->txWaveform(), nBytes);\n    }\n\n    return true;\n}\n\nbool GGWave_deinit() {\n    if (g_devIdInp == 0 && g_devIdOut == 0) {\n        return false;\n    }\n\n    g_ggWave.reset();\n\n    SDL_PauseAudioDevice(g_devIdInp, 1);\n    SDL_CloseAudioDevice(g_devIdInp);\n    SDL_PauseAudioDevice(g_devIdOut, 1);\n    SDL_CloseAudioDevice(g_devIdOut);\n\n    g_devIdInp = 0;\n    g_devIdOut = 0;\n\n    return true;\n}\n"
  },
  {
    "path": "examples/ggwave-common-sdl2.h",
    "content": "#pragma once\n\n#include <string>\n#include <memory>\n\nclass GGWave;\n\n// GGWave helpers\n\nvoid GGWave_setDefaultCaptureDeviceName(std::string name);\nbool GGWave_init(const int playbackId, const int captureId, const int payloadLength = -1, const float sampleRateOffset = 0, const bool useDSS = false);\nstd::shared_ptr<GGWave> GGWave_instance();\nvoid GGWave_reset(void * parameters);\nbool GGWave_mainLoop();\nbool GGWave_deinit();\n"
  },
  {
    "path": "examples/ggwave-common.cpp",
    "content": "#include \"ggwave-common.h\"\n\n#if !defined(_WIN32)\n#include <dlfcn.h>\n#include <unistd.h>\n#endif\n\n#include <cstring>\n#include <fstream>\n#include <iterator>\n\nnamespace {\nvoid dummy() {}\n}\n\nstd::map<std::string, std::string> parseCmdArguments(int argc, char ** argv) {\n    int last = argc;\n    std::map<std::string, std::string> res;\n    for (int i = 1; i < last; ++i) {\n        if (argv[i][0] == '-') {\n            if (strlen(argv[i]) > 1) {\n                res[std::string(1, argv[i][1])] = strlen(argv[i]) > 2 ? argv[i] + 2 : \"\";\n            }\n        }\n    }\n\n    return res;\n}\n\nstd::vector<char> readFile(const char* filename) {\n    std::ifstream file(filename, std::ios::binary);\n    if (!file.is_open() || !file.good()) return {};\n\n    file.unsetf(std::ios::skipws);\n    std::streampos fileSize;\n\n    file.seekg(0, std::ios::end);\n    fileSize = file.tellg();\n    file.seekg(0, std::ios::beg);\n\n    std::vector<char> vec;\n    vec.reserve(fileSize);\n\n    vec.insert(vec.begin(),\n               std::istream_iterator<char>(file),\n               std::istream_iterator<char>());\n\n    return vec;\n}\n\nstd::string getBinaryPath() {\n#if defined(__EMSCRIPTEN__) || defined(_WIN32)\n    return \"\";\n#else\n    std::string result;\n    void* p = reinterpret_cast<void*>(dummy);\n\n    Dl_info info;\n    dladdr(p, &info);\n\n    if (*info.dli_fname == '/') {\n        result = info.dli_fname;\n    } else {\n        char buff[2048];\n        auto len = readlink(\"/proc/self/exe\", buff, sizeof(buff) - 1);\n        if (len > 0) {\n            buff[len] = 0;\n            result = buff;\n        }\n    }\n\n    auto slash = result.rfind('/');\n    if (slash != std::string::npos) {\n        result.erase(slash + 1);\n    }\n\n    return result;\n#endif\n}\n"
  },
  {
    "path": "examples/ggwave-common.h",
    "content": "#pragma once\n\n#include <chrono>\n#include <string>\n#include <map>\n#include <vector>\n\n// some basic helper methods for the examples\n\ntemplate <class T>\nfloat getTime_ms(const T & tStart, const T & tEnd) {\n    return ((float)(std::chrono::duration_cast<std::chrono::microseconds>(tEnd - tStart).count()))/1000.0;\n}\n\nstd::vector<char> readFile(const char* filename);\n\nstd::string getBinaryPath();\n\nstd::map<std::string, std::string> parseCmdArguments(int argc, char ** argv);\n"
  },
  {
    "path": "examples/ggwave-from-file/CMakeLists.txt",
    "content": "set(TARGET ggwave-from-file)\n\nadd_executable(${TARGET} main.cpp)\n\ntarget_include_directories(${TARGET} PRIVATE\n    ..\n    )\n\ntarget_link_libraries(${TARGET} PRIVATE\n    ggwave\n    ggwave-common\n    ${CMAKE_THREAD_LIBS_INIT}\n    )\n\ninstall(TARGETS ${TARGET} RUNTIME DESTINATION bin)\n"
  },
  {
    "path": "examples/ggwave-from-file/README.md",
    "content": "## ggwave-from-file\n\nDecode GGWave messages from an input WAV file\n\n```\nUsage: ./bin/ggwave-from-file [-lN] [-d]\n    -lN - fixed payload length of size N, N in [1, 64]\n    -d  - use Direct Sequence Spread (DSS)\n```\n\n### Examples\n\n- Basic usage with auto-detection of frequency and speed:\n\n  ```bash\n  echo \"Hello world\" | ./bin/ggwave-to-file > example.wav\n  ./bin/ggwave-from-file example.wav\n\n  Usage: ./bin/ggwave-from-file audio.wav [-lN] [-d]\n      -lN - fixed payload length of size N, N in [1, 64]\n      -d  - use Direct Sequence Spread (DSS)\n\n  [+] Number of channels: 1\n  [+] Sample rate: 48000\n  [+] Bits per sample: 16\n  [+] Total samples: 69632\n  [+] Decoding ..\n\n  [+] Decoded message with length 11: 'Hello world'\n\n  [+] Done\n  ```\n\n- Decoding fixed-length payload with DSS enabled:\n\n  ```bash\n  echo \"Hello world\" | ./bin/ggwave-to-file -l16 -d > example.wav\n  ./bin/ggwave-from-file example.wav -l16 -d\n  ```\n"
  },
  {
    "path": "examples/ggwave-from-file/main.cpp",
    "content": "#include \"ggwave/ggwave.h\"\n\n#define DR_WAV_IMPLEMENTATION\n#include \"dr_wav.h\"\n\n#include \"ggwave-common.h\"\n\n#include <cstdio>\n#include <cstring>\n#include <iostream>\n\nint main(int argc, char** argv) {\n    fprintf(stderr, \"Usage: %s audio.wav [-lN] [-d]\\n\", argv[0]);\n    fprintf(stderr, \"    -lN - fixed payload length of size N, N in [1, %d]\\n\", GGWave::kMaxLengthFixed);\n    fprintf(stderr, \"    -d  - use Direct Sequence Spread (DSS)\\n\");\n    fprintf(stderr, \"\\n\");\n\n    if (argc < 2) {\n        return -1;\n    }\n\n    const auto argm = parseCmdArguments(argc, argv);\n\n    if (argm.count(\"h\") > 0) {\n        return 0;\n    }\n\n    const int   payloadLength = argm.count(\"l\") == 0 ? -1 : std::stoi(argm.at(\"l\"));\n    const bool  useDSS        = argm.count(\"d\") >  0;\n\n    drwav wav;\n    if (!drwav_init_file(&wav, argv[1], nullptr)) {\n        fprintf(stderr, \"Failed to open WAV file\\n\");\n        return -4;\n    }\n\n    if (wav.channels != 1) {\n        fprintf(stderr, \"Only mono WAV files are supported\\n\");\n        return -5;\n    }\n\n    // Read WAV samples into a buffer\n    // Add 3 seconds of silence at the end\n    const size_t samplesSilence = 3*wav.sampleRate;\n    const size_t samplesCount = wav.totalPCMFrameCount;\n    const size_t samplesSize = wav.bitsPerSample/8;\n          size_t samplesTotal = samplesCount + samplesSilence;\n    std::vector<uint8_t> samples(samplesTotal*samplesSize*wav.channels, 0);\n\n    printf(\"[+] Number of channels: %d\\n\", wav.channels);\n    printf(\"[+] Sample rate: %d\\n\", wav.sampleRate);\n    printf(\"[+] Bits per sample: %d\\n\", wav.bitsPerSample);\n    printf(\"[+] Total samples: %zu\\n\", samplesCount);\n\n    printf(\"[+] Decoding .. \\n\\n\");\n\n    GGWave::Parameters parameters = GGWave::getDefaultParameters();\n\n    parameters.payloadLength = payloadLength;\n    parameters.sampleRateInp = wav.sampleRate;\n    parameters.operatingMode = GGWAVE_OPERATING_MODE_RX;\n    if (useDSS) parameters.operatingMode |= GGWAVE_OPERATING_MODE_USE_DSS;\n\n    switch (wav.bitsPerSample) {\n        case 16:\n            drwav_read_pcm_frames_s16(&wav, samplesCount, reinterpret_cast<int16_t*>(samples.data()));\n\n            if (wav.channels > 1) {\n                for (size_t i = 0; i < samplesCount; ++i) {\n                    int16_t sample = 0;\n                    for (size_t j = 0; j < wav.channels; ++j) {\n                        sample += reinterpret_cast<int16_t*>(samples.data())[i*wav.channels + j];\n                    }\n                    reinterpret_cast<int16_t*>(samples.data())[i] = sample / wav.channels;\n                }\n            }\n\n            parameters.sampleFormatInp = GGWAVE_SAMPLE_FORMAT_I16;\n\n            break;\n        case 32:\n            drwav_read_pcm_frames_f32(&wav, samplesCount, reinterpret_cast<float*>(samples.data()));\n\n            if (wav.channels > 1) {\n                for (size_t i = 0; i < samplesCount; ++i) {\n                    float sample = 0.0f;\n                    for (size_t j = 0; j < wav.channels; ++j) {\n                        sample += reinterpret_cast<float*>(samples.data())[i*wav.channels + j];\n                    }\n                    reinterpret_cast<float*>(samples.data())[i] = sample / wav.channels;\n                }\n            }\n\n            parameters.sampleFormatInp = GGWAVE_SAMPLE_FORMAT_F32;\n\n            break;\n        default:\n            fprintf(stderr, \"Unsupported WAV format\\n\");\n            return -6;\n    }\n\n    GGWave ggWave(parameters);\n    ggWave.setLogFile(nullptr);\n\n    GGWave::TxRxData data;\n    auto ptr = samples.data();\n    while ((int) samplesTotal >= parameters.samplesPerFrame) {\n        if (ggWave.decode(ptr, parameters.samplesPerFrame*samplesSize*wav.channels) == false) {\n            fprintf(stderr, \"Failed to decode the waveform in the WAV file\\n\");\n            return -7;\n        }\n\n        ptr += parameters.samplesPerFrame*samplesSize*wav.channels;\n        samplesTotal -= parameters.samplesPerFrame;\n\n        const int n = ggWave.rxTakeData(data);\n        if (n > 0) {\n            printf(\"[+] Decoded message with length %d: '\", n);\n            for (auto i = 0; i < n; ++i) {\n                printf(\"%c\", data[i]);\n            }\n            printf(\"'\\n\");\n        }\n\n    }\n\n    printf(\"\\n[+] Done\\n\");\n\n    return 0;\n}\n"
  },
  {
    "path": "examples/ggwave-js/CMakeLists.txt",
    "content": "set(TARGET ggwave-js)\n\nconfigure_file(${CMAKE_CURRENT_SOURCE_DIR}/index-tmpl.html       ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${TARGET}/index.html @ONLY)\nconfigure_file(${CMAKE_SOURCE_DIR}/bindings/javascript/ggwave.js ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${TARGET}/ggwave.js  COPYONLY)\n"
  },
  {
    "path": "examples/ggwave-js/README.md",
    "content": "# ggwave-js\n\nLive demo: https://ggwave-js.ggerganov.com\n"
  },
  {
    "path": "examples/ggwave-js/index-tmpl.html",
    "content": "<!doctype html>\n<html lang=\"en-us\">\n    <head>\n        <title>ggwave : javascript example</title>\n    </head>\n    <body>\n        <div id=\"main-container\">\n            Minimal <b>ggwave</b> example using Javascript bindings\n\n            <br><br>\n\n            <div>Tx Data:</div> <textarea name=\"textarea\" id=\"txData\" style=\"width:300px;height:100px;\">Hello javascript</textarea><br>\n\n            <button onclick=\"onSend();\">Send</button>\n\n            <br><br>\n\n            <div>Rx data:</div> <textarea name=\"textarea\" id=\"rxData\" style=\"width:300px;height:100px;\" disabled></textarea><br>\n\n            <button id=\"captureStart\">Start capturing</button>\n            <button id=\"captureStop\" hidden>Stop capturing</button>\n\n            <br><br>\n\n            <div class=\"cell-version\">\n                <span>\n                    |\n                    Build time: <span class=\"nav-link\">@GIT_DATE@</span> |\n                    Commit hash: <a class=\"nav-link\" href=\"https://github.com/ggerganov/ggwave/commit/@GIT_SHA1@\">@GIT_SHA1@</a> |\n                    Commit subject: <span class=\"nav-link\">@GIT_COMMIT_SUBJECT@</span> |\n                    <a class=\"nav-link\" href=\"https://github.com/ggerganov/ggwave/tree/master/examples/ggwave-js\">Source Code</a> |\n                </span>\n            </div>\n        </div>\n\n        <script type=\"text/javascript\" src=\"ggwave.js\"></script>\n        <script type='text/javascript'>\n            window.AudioContext = window.AudioContext || window.webkitAudioContext;\n            window.OfflineAudioContext = window.OfflineAudioContext || window.webkitOfflineAudioContext;\n\n            var context = null;\n            var recorder = null;\n\n            // the ggwave module instance\n            var ggwave = null;\n            var parameters = null;\n            var instance = null;\n\n            // instantiate the ggwave instance\n            // ggwave_factory comes from the ggwave.js module\n            ggwave_factory().then(function(obj) {\n                ggwave = obj;\n            });\n\n            var txData = document.getElementById(\"txData\");\n            var rxData = document.getElementById(\"rxData\");\n            var captureStart = document.getElementById(\"captureStart\");\n            var captureStop = document.getElementById(\"captureStop\");\n\n            // helper function\n            function convertTypedArray(src, type) {\n                var buffer = new ArrayBuffer(src.byteLength);\n                var baseView = new src.constructor(buffer).set(src);\n                return new type(buffer);\n            }\n\n            // initialize audio context and ggwave\n            function init() {\n                if (!context) {\n                    context = new AudioContext({sampleRate: 48000});\n\n                    parameters = ggwave.getDefaultParameters();\n                    parameters.sampleRateInp = context.sampleRate;\n                    parameters.sampleRateOut = context.sampleRate;\n                    instance = ggwave.init(parameters);\n                }\n            }\n\n            //\n            // Tx\n            //\n\n            function onSend() {\n                init();\n\n                // pause audio capture during transmission\n                captureStop.click();\n\n                // generate audio waveform\n                var waveform = ggwave.encode(instance, txData.value, ggwave.ProtocolId.GGWAVE_PROTOCOL_AUDIBLE_FAST, 10)\n\n                // play audio\n                var buf = convertTypedArray(waveform, Float32Array);\n                var buffer = context.createBuffer(1, buf.length, context.sampleRate);\n                buffer.getChannelData(0).set(buf);\n                var source = context.createBufferSource();\n                source.buffer = buffer;\n                source.connect(context.destination);\n                source.start(0);\n            }\n\n            //\n            // Rx\n            //\n\n            captureStart.addEventListener(\"click\", function () {\n                init();\n\n                let constraints = {\n                    audio: {\n                        // not sure if these are necessary to have\n                        echoCancellation: false,\n                        autoGainControl: false,\n                        noiseSuppression: false\n                    }\n                };\n\n                navigator.mediaDevices.getUserMedia(constraints).then(function (e) {\n                    mediaStream = context.createMediaStreamSource(e);\n\n                    var bufferSize = 1024;\n                    var numberOfInputChannels = 1;\n                    var numberOfOutputChannels = 1;\n\n                    if (context.createScriptProcessor) {\n                        recorder = context.createScriptProcessor(\n                                bufferSize,\n                                numberOfInputChannels,\n                                numberOfOutputChannels);\n                    } else {\n                        recorder = context.createJavaScriptNode(\n                                bufferSize,\n                                numberOfInputChannels,\n                                numberOfOutputChannels);\n                    }\n\n                    recorder.onaudioprocess = function (e) {\n                        var source = e.inputBuffer;\n                        var res = ggwave.decode(instance, convertTypedArray(new Float32Array(source.getChannelData(0)), Int8Array));\n\n                        if (res && res.length > 0) {\n                            res = new TextDecoder(\"utf-8\").decode(res);\n                            rxData.value = res;\n                        }\n\n                        // obsolete javascript resampling\n                        // since ggwave v0.2.0 the resampling is built-in ggwave\n                        //var offlineCtx = new OfflineAudioContext(source.numberOfChannels, 48000*source.duration, 48000);\n                        //var offlineSource = offlineCtx.createBufferSource();\n\n                        //offlineSource.buffer = source;\n                        //offlineSource.connect(offlineCtx.destination);\n                        //offlineSource.start();\n                        //offlineCtx.startRendering();\n                        //offlineCtx.oncomplete = function(e) {\n                        //    var resampled = e.renderedBuffer.getChannelData(0);\n                        //    var res = ggwave.decode(instance, convertTypedArray(new Float32Array(resampled), Int8Array));\n                        //    if (res) {\n                        //        rxData.value = res;\n                        //    }\n                        //};\n                    }\n\n                    mediaStream.connect(recorder);\n                    recorder.connect(context.destination);\n                }).catch(function (e) {\n                    console.error(e);\n                });\n\n                rxData.value = 'Listening ...';\n                captureStart.hidden = true;\n                captureStop.hidden = false;\n            });\n\n            captureStop.addEventListener(\"click\", function () {\n                if (recorder) {\n                    recorder.disconnect(context.destination);\n                    mediaStream.disconnect(recorder);\n                    recorder = null;\n                }\n\n                rxData.value = 'Audio capture is paused! Press the \"Start capturing\" button to analyze audio from the microphone';\n                captureStart.hidden = false;\n                captureStop.hidden = true;\n            });\n\n            captureStop.click();\n        </script>\n    </body>\n</html>\n"
  },
  {
    "path": "examples/ggwave-py/README.md",
    "content": "## ggwave-py\n\nPython examples using the `ggwave` python package\n\n### Install\n\n```bash\npip install ggwave\n```\n\nSome of the packages depend on `pyaudio`. To install it:\n\n```bash\nsudo apt-get install portaudio19-dev python-pyaudio python3-pyaudio\npip install pyaudio\n```\n"
  },
  {
    "path": "examples/ggwave-py/receive.py",
    "content": "import ggwave\nimport pyaudio\n\np = pyaudio.PyAudio()\n\nstream = p.open(format=pyaudio.paFloat32, channels=1, rate=48000, input=True, frames_per_buffer=1024)\n\nprint('Listening ... Press Ctrl+C to stop')\ninstance = ggwave.init()\n\ntry:\n    while True:\n        data = stream.read(1024, exception_on_overflow=False)\n        res = ggwave.decode(instance, data)\n        if (not res is None):\n            try:\n                print('Received text: ' + res.decode(\"utf-8\"))\n            except:\n                pass\nexcept KeyboardInterrupt:\n    pass\n\nggwave.free(instance)\n\nstream.stop_stream()\nstream.close()\n\np.terminate()\n"
  },
  {
    "path": "examples/ggwave-py/send.py",
    "content": "import ggwave\nimport pyaudio\n\np = pyaudio.PyAudio()\n\n# generate audio waveform for string \"hello python\"\nwaveform = ggwave.encode(\"hello python\", protocolId = 1, volume = 20)\n\nprint(\"Transmitting text 'hello python' ...\")\nstream = p.open(format=pyaudio.paFloat32, channels=1, rate=48000, output=True, frames_per_buffer=4096)\nstream.write(waveform, len(waveform)//4)\nstream.stop_stream()\nstream.close()\n\np.terminate()\n"
  },
  {
    "path": "examples/ggwave-rx/CMakeLists.txt",
    "content": "set(TARGET ggwave-rx)\n\nadd_executable(${TARGET} main.cpp)\n\ntarget_include_directories(${TARGET} PRIVATE\n    ..\n    ${SDL2_INCLUDE_DIRS}\n    )\n\ntarget_link_libraries(${TARGET} PRIVATE\n    ggwave\n    ggwave-common\n    ggwave-common-sdl2\n    )\n"
  },
  {
    "path": "examples/ggwave-rx/main.cpp",
    "content": "#include \"ggwave/ggwave.h\"\n\n#include \"ggwave-common.h\"\n#include \"ggwave-common-sdl2.h\"\n\n#include <SDL.h>\n\n#include <cstdio>\n#include <thread>\n\nint main(int argc, char** argv) {\n    printf(\"Usage: %s [-cN]\\n\", argv[0]);\n    printf(\"    -cN - select capture device N\\n\");\n    printf(\"\\n\");\n\n    auto argm = parseCmdArguments(argc, argv);\n    int captureId = argm[\"c\"].empty() ? 0 : std::stoi(argm[\"c\"]);\n\n    if (GGWave_init(0, captureId) == false) {\n        fprintf(stderr, \"Failed to initialize GGWave\\n\");\n        return -1;\n    }\n\n    while (true) {\n        std::this_thread::sleep_for(std::chrono::milliseconds(1));\n        GGWave_mainLoop();\n    }\n\n    GGWave_deinit();\n\n    SDL_CloseAudio();\n    SDL_Quit();\n\n    return 0;\n}\n"
  },
  {
    "path": "examples/ggwave-to-file/CMakeLists.txt",
    "content": "set(TARGET ggwave-to-file)\n\nadd_executable(${TARGET} main.cpp)\n\ntarget_include_directories(${TARGET} PRIVATE\n    ..\n    )\n\ntarget_link_libraries(${TARGET} PRIVATE\n    ggwave\n    ggwave-common\n    ${CMAKE_THREAD_LIBS_INIT}\n    )\n"
  },
  {
    "path": "examples/ggwave-to-file/README.md",
    "content": "## ggwave-to-file\n\nOutput a generated waveform to an uncompressed WAV file.\n\n```\nUsage: ./bin/ggwave-to-file [-vN] [-sN] [-pN] [-lN] [-d]\n    -vN - output volume, N in (0, 100], (default: 50)\n    -sN - output sample rate, N in [6000, 96000], (default: 48000)\n    -pN - select the transmission protocol id (default: 1)\n    -lN - fixed payload length of size N, N in [1, 16]\n    -d  - use Direct Sequence Spread (DSS)\n\n    Available protocols:\n      0  - Normal\n      1  - Fast\n      2  - Fastest\n      3  - [U] Normal\n      4  - [U] Fast\n      5  - [U] Fastest\n      6  - [DT] Normal\n      7  - [DT] Fast\n      8  - [DT] Fastest\n      9  - [MT] Normal\n      10 - [MT] Fast\n      11 - [MT] Fastest\n```\n\n### Examples\n\n- Generate waveform with default parameters\n\n  ```bash\n  echo \"Hello world!\" | ./bin/ggwave-to-file > example.wav\n  ```\n\n- Generate waveform at 24 kHz sample rate\n\n  ```bash\n  echo \"Hello world!\" | ./bin/ggwave-to-file -s24000 > example.wav\n  ```\n\n- Generate ultrasound waveform using the `[U] Fast` protocol\n\n  ```bash\n  echo \"Hello world!\" | ./bin/ggwave-to-file -p4 > example.wav\n  ```\n\n- Use fixed-length encoding (i.e. no sound markers)\n\n  ```bash\n  echo \"Hello world!\" | ./bin/ggwave-to-file -l12 > example.wav\n  ```\n\n- Use DSS when encoding the text\n\n  ```bash\n  echo \"aaaaaaaa\" | ./bin/ggwave-to-file -l8 -d > example.wav\n  ```\n\n- Play the generated waveform directly through the speakers\n\n  ```bash\n  echo \"Hello world!\" | ./bin/ggwave-to-file | play --ignore-length -t wav -\n  ```\n\n## HTTP service\n\nBased on this tool, there is an HTTP service available on the following link:\n\nhttps://ggwave-to-file.ggerganov.com/\n\nYou 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:\n\n### terminal\n\n```bash\n# audible example\ncurl -sS 'https://ggwave-to-file.ggerganov.com/?m=Hello%20world!' --output hello.wav\n\n# ultrasound example\ncurl -sS 'https://ggwave-to-file.ggerganov.com/?m=Hello%20world!&p=4' --output hello.wav\n```\n\n### browser\n\n- Audible example\n\n  https://ggwave-to-file.ggerganov.com/?m=Hello%20world%21\n\n- Ultrasound example\n\n  https://ggwave-to-file.ggerganov.com/?m=Hello%20world%21&p=4\n\n\n### python\n\n```python\nfrom typing import Dict, Union\nimport requests\nimport wave\n\ndef ggwave(message: str,\n           file: str,\n           protocolId: int = 1,\n           sampleRate: float = 48000,\n           volume: int = 50,\n           payloadLength: int = -1,\n           useDSS: int = 0) -> None:\n\n    url = 'https://ggwave-to-file.ggerganov.com/'\n\n    params: Dict[str, Union[str, int, float] = {\n        'm': message,        # message to encode\n        'p': protocolId,     # transmission protocol to use\n        's': sampleRate,     # output sample rate\n        'v': volume,         # output volume\n        'l': payloadLength,  # if positive - use fixed-length encoding\n        'd': useDSS,         # if positive - use DSS\n    }\n\n    response = requests.get(url, params=params)\n\n    if response == '' or b'Usage: ggwave-to-file' in response.content:\n        raise SyntaxError('Request failed')\n\n    with wave.open(file, 'wb') as f:\n        f.setnchannels(1)\n        f.setframerate(sampleRate)\n        f.setsampwidth(2)\n        f.writeframes(response.context)\n\n```\n\n...\n\n```python\n\n# query waveform from server and write to file\nggwave(\"Hello world!\", \"hello_world.wav\")\n\n\n```\n"
  },
  {
    "path": "examples/ggwave-to-file/ggwave-to-file-local.py",
    "content": "import ggwave\nimport wave\nimport numpy as np\n\n# Parameters\nvolume_ = 20\nsample_rate_ = 48000\nfilename = \"output.wav\"\n\n# Generate audio waveform for string \"hello python\"\nwaveform = ggwave.encode(\"hello python\", protocolId=5, volume=volume_)\n\n# Convert byte data into float32\nwaveform_float32 = np.frombuffer(waveform, dtype=np.float32)\n\n# Normalize the float32 data to the range of int16\nwaveform_int16 = np.int16(waveform_float32 * 32767)\n\n# Save the waveform to a .wav file\nwith wave.open(filename, \"wb\") as wf:\n    wf.setnchannels(1)                  # mono audio\n    wf.setsampwidth(2)                  # 2 bytes per sample (16-bit PCM)\n    wf.setframerate(sample_rate_)       # sample rate\n    wf.writeframes(waveform_int16.tobytes())  # write the waveform as bytes"
  },
  {
    "path": "examples/ggwave-to-file/ggwave-to-file.php",
    "content": "<?php\n\n$cmd = \"ggwave-to-file\";\n\nif (isset($_GET['s'])) { $cmd .= \" -s\".intval($_GET['s']); }\nif (isset($_GET['v'])) { $cmd .= \" -v\".intval($_GET['v']); }\nif (isset($_GET['p'])) { $cmd .= \" -p\".intval($_GET['p']); }\nif (isset($_GET['l'])) { $cmd .= \" -l\".intval($_GET['l']); }\nif (isset($_GET['d'])) { if (intval($_GET['d']) > 0) $cmd .= \" -d\"; }\n\n$descriptorspec = array(\n    0 => array(\"pipe\", \"r\"),\n    //1 => array(\"pipe\", \"w\"),\n    2 => array(\"pipe\", \"w\"),\n);\n\n$path_wav = tempnam(\"/tmp\", \"ggwave\");\n\n$cmd .= \" > $path_wav\";\n\n$process = proc_open($cmd, $descriptorspec, $pipes);\n\nif (is_resource($process)) {\n    $message = $_GET['m'];\n\n    fwrite($pipes[0], $message);\n    fclose($pipes[0]);\n\n    //$result = stream_get_contents($pipes[1]);\n    //fclose($pipes[1]);\n\n    $log = stream_get_contents($pipes[2]);\n    fclose($pipes[2]);\n\n    $return_value = proc_close($process);\n\n    //exec(\"ffmpeg -i \".$path_wav.\" \".$path_wav);\n\n    $result = file_get_contents($path_wav);\n    $size = filesize($path_wav);\n\n    if ($size == 0) {\n        header('Content-type: text/plain');\n        echo $log;\n    } else {\n        //header(\"Content-Type: audio/wav\");\n        header(\"Content-Type: \". mime_content_type($path_wav));\n        header(\"Content-Length: $size\");\n        header(\"Accept-Ranges: bytes\");\n        header('Content-Disposition: attachment; filename=\"output.wav\"');\n        header(\"Content-Transfer-Encoding: binary\");\n        header(\"Content-Range: bytes 0-\".$size.\"/\".$size);\n\n        echo $result;\n    }\n}\n\nunlink($path_wav);\n\n?>\n"
  },
  {
    "path": "examples/ggwave-to-file/ggwave-to-file.py",
    "content": "from typing import Dict, Union\nimport requests\nimport wave\n\ndef ggwave(message: str,\n           file: str,\n           protocolId: int = 1,\n           sampleRate: float = 48000,\n           volume: int = 50,\n           payloadLength: int = -1,\n           useDSS: int = 0) -> None:\n\n    url = 'https://ggwave-to-file.ggerganov.com/'\n\n    params: Dict[str, Union[str, int, float]] = {\n        'm': message,        # message to encode\n        'p': protocolId,     # transmission protocol to use\n        's': sampleRate,     # output sample rate\n        'v': volume,         # output volume\n        'l': payloadLength,  # if positive - use fixed-length encoding\n        'd': useDSS,         # if positive - use DSS\n    }\n\n    response = requests.get(url, params=params)\n\n    if response == '' or b'Usage: ggwave-to-file' in response.content:\n        raise SyntaxError('Request failed')\n\n    with wave.open(file, 'wb') as f:\n        f.setnchannels(1)\n        f.setframerate(sampleRate)\n        f.setsampwidth(2)\n        f.writeframes(response.content)\n\n\nif __name__ == \"__main__\":\n    ggwave(\"Hello world!\", \"hello_world.wav\")\n"
  },
  {
    "path": "examples/ggwave-to-file/main.cpp",
    "content": "#include \"ggwave/ggwave.h\"\n\n#define DR_WAV_IMPLEMENTATION\n#include \"dr_wav.h\"\n\n#include \"ggwave-common.h\"\n\n#include <cstdio>\n#include <cstring>\n#include <iostream>\n\nint main(int argc, char** argv) {\n\n    #if defined(_WIN32)\n    const std::string & defaultFile = \"audio.wav\";\n    #else\n    const std::string & defaultFile = \"/dev/stdout\";\n    #endif\n\n    fprintf(stderr, \"Usage: %s [-vN] [-sN] [-pN] [-lN] [-d]\\n\", argv[0]);\n    fprintf(stderr, \"    -fF - output filename, (default: %s)\\n\", defaultFile.c_str());\n    fprintf(stderr, \"    -vN - output volume, N in (0, 100], (default: 50)\\n\");\n    fprintf(stderr, \"    -sN - output sample rate, N in [%d, %d], (default: %d)\\n\", (int) GGWave::kSampleRateMin, (int) GGWave::kSampleRateMax, (int) GGWave::kDefaultSampleRate);\n    fprintf(stderr, \"    -pN - select the transmission protocol id (default: 1)\\n\");\n    fprintf(stderr, \"    -lN - fixed payload length of size N, N in [1, %d]\\n\", GGWave::kMaxLengthFixed);\n    fprintf(stderr, \"    -d  - use Direct Sequence Spread (DSS)\\n\");\n    fprintf(stderr, \"\\n\");\n    fprintf(stderr, \"    Available protocols:\\n\");\n\n    const auto & protocols = GGWave::Protocols::kDefault();\n    for (int i = 0; i < (int) protocols.size(); ++i) {\n        const auto & protocol = protocols[i];\n        if (protocol.enabled == false) {\n            continue;\n        }\n        fprintf(stderr, \"      %d - %s\\n\", i, protocol.name);\n    }\n    fprintf(stderr, \"\\n\");\n\n    if (argc < 1) {\n        return -1;\n    }\n\n    const auto argm = parseCmdArguments(argc, argv);\n\n    if (argm.count(\"h\") > 0) {\n        return 0;\n    }\n\n    const int   volume        = argm.count(\"v\") == 0 ? 50 : std::stoi(argm.at(\"v\"));\n    const std::string & file  = argm.count(\"f\") == 0 ? defaultFile : argm.at(\"f\");\n    const float sampleRateOut = argm.count(\"s\") == 0 ? GGWave::kDefaultSampleRate : std::stof(argm.at(\"s\"));\n    const int   protocolId    = argm.count(\"p\") == 0 ?  1 : std::stoi(argm.at(\"p\"));\n    const int   payloadLength = argm.count(\"l\") == 0 ? -1 : std::stoi(argm.at(\"l\"));\n    const bool  useDSS        = argm.count(\"d\") >  0;\n\n    if (volume <= 0 || volume > 100) {\n        fprintf(stderr, \"Invalid volume\\n\");\n        return -1;\n    }\n\n    if (sampleRateOut < GGWave::kSampleRateMin || sampleRateOut > GGWave::kSampleRateMax) {\n        fprintf(stderr, \"Invalid sample rate: %g\\n\", sampleRateOut);\n        return -1;\n    }\n\n    if (protocolId < 0 || protocolId >= (int) protocols.size()) {\n        fprintf(stderr, \"Invalid transmission protocol id\\n\");\n        return -1;\n    }\n\n    if (protocols[protocolId].enabled == false) {\n        fprintf(stderr, \"Protocol %d is not enabled\\n\", protocolId);\n        return -1;\n    }\n\n    fprintf(stderr, \"Enter a text message:\\n\");\n\n    std::string message;\n    std::getline(std::cin, message);\n\n    if (message.size() == 0) {\n        fprintf(stderr, \"Invalid message: size = 0\\n\");\n        return -2;\n    }\n\n    if (message.size() > 140) {\n        fprintf(stderr, \"Invalid message: size > 140\\n\");\n        return -3;\n    }\n\n    fprintf(stderr, \"Generating waveform for message '%s' ...\\n\", message.c_str());\n\n    GGWave::OperatingMode mode = GGWAVE_OPERATING_MODE_RX_AND_TX;\n    if (useDSS) mode |= GGWAVE_OPERATING_MODE_USE_DSS;\n\n    GGWave ggWave({\n        payloadLength,\n        GGWave::kDefaultSampleRate,\n        sampleRateOut,\n        GGWave::kDefaultSampleRate,\n        GGWave::kDefaultSamplesPerFrame,\n        GGWave::kDefaultSoundMarkerThreshold,\n        GGWAVE_SAMPLE_FORMAT_F32,\n        GGWAVE_SAMPLE_FORMAT_I16,\n        mode,\n    });\n    ggWave.init(message.size(), message.data(), GGWave::TxProtocolId(protocolId), volume);\n\n    const auto nBytes = ggWave.encode();\n    if (nBytes == 0) {\n        fprintf(stderr, \"Failed to generate waveform!\\n\");\n        return -4;\n    }\n\n    std::vector<char> bufferPCM(nBytes);\n    std::memcpy(bufferPCM.data(), ggWave.txWaveform(), nBytes);\n\n    fprintf(stderr, \"Output file = %s\\n\", file.c_str());\n    fprintf(stderr, \"Output size = %d bytes\\n\", (int) bufferPCM.size());\n\n    drwav_data_format format;\n    format.container = drwav_container_riff;\n    format.format = DR_WAVE_FORMAT_PCM;\n    format.channels = 1;\n    format.sampleRate = sampleRateOut;\n    format.bitsPerSample = 16;\n\n    fprintf(stderr, \"Writing WAV data ...\\n\");\n\n    drwav wav;\n    drwav_init_file_write(&wav, file.c_str(), &format, NULL);\n    drwav_uint64 framesWritten = drwav_write_pcm_frames(&wav, bufferPCM.size()/2, bufferPCM.data());\n\n    fprintf(stderr, \"WAV frames written = %d\\n\", (int) framesWritten);\n\n    drwav_uninit(&wav);\n\n    return 0;\n}\n"
  },
  {
    "path": "examples/ggwave-wasm/CMakeLists.txt",
    "content": "set(TARGET ggwave-wasm)\n\nconfigure_file(${CMAKE_CURRENT_SOURCE_DIR}/build_timestamp-tmpl.h ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${TARGET}/build_timestamp.h @ONLY)\n\nadd_executable(${TARGET}\n    main.cpp\n    )\n\ntarget_include_directories(${TARGET} PRIVATE\n    ..\n    ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${TARGET}/\n    )\n\ntarget_link_libraries(${TARGET} PRIVATE\n    ggwave\n    ggwave-common\n    ggwave-common-sdl2\n    )\n\nconfigure_file(${CMAKE_CURRENT_SOURCE_DIR}/index-tmpl.html ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${TARGET}/index.html @ONLY)\nconfigure_file(${CMAKE_CURRENT_SOURCE_DIR}/style.css ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${TARGET}/style.css COPYONLY)\nconfigure_file(${CMAKE_CURRENT_SOURCE_DIR}/main.js ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${TARGET}/main.js COPYONLY)\nconfigure_file(${CMAKE_CURRENT_SOURCE_DIR}/plucky.mp3 ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${TARGET}/plucky.mp3 COPYONLY)\n"
  },
  {
    "path": "examples/ggwave-wasm/build_timestamp-tmpl.h",
    "content": "static const char * BUILD_TIMESTAMP=\"@GIT_DATE@ (@GIT_SHA1@)\";\n"
  },
  {
    "path": "examples/ggwave-wasm/index-tmpl.html",
    "content": "<!doctype html>\n<html lang=\"en-us\">\n    <head>\n        <meta charset=\"utf-8\">\n        <title>ggwave : emscripten example</title>\n\n        <meta name=\"viewport\" content=\"width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no\"/>\n\n        <link rel=\"shortcut icon\" href=\"favicon.ico\">\n        <link rel=\"stylesheet\" href=\"style.css\">\n    </head>\n    <body>\n        <div id=\"main-container\">\n            <h1>ggwave</h1>\n\n            Open this page on multiple devices (computers, phones, tablets, etc.). <br>\n            Press the init button and broadcast some text. Make sure your speakers and microphones are enabled.\n\n            <br><br>\n\n            <section>\n                <div id=\"sound\"></div>\n\n                <table>\n                    <tr>\n                        <td colspan=3 align=\"center\">\n                            <button onClick=\"doInit()\" id=\"butInit\" disabled>Init</button>\n                        </td>\n                    </tr>\n                    <tr>\n                        <td colspan=3 align=\"center\">\n                            <div class=\"led-box\">\n                                <p>Output:</p>\n                            </div>\n                            <div class=\"led-box\">\n                                <p>Capture:</p>\n                            </div>\n                            <div class=\"led-box\">\n                                <p>Browser:</p>\n                            </div>\n                        </td>\n                    </tr>\n                    <tr>\n                        <td colspan=3 align=\"center\">\n                            <div class=\"led-box\" id=\"has-device-output\" title=\"Indicates if an audio output device is available\">\n                                <div class=\"led-yellow\"></div>\n                            </div>\n                            <div class=\"led-box\" id=\"has-device-capture\" title=\"Indicates if an audio capture device is available. Make sure this page has access to the microphone\">\n                                <div class=\"led-yellow\"></div>\n                            </div>\n                            <div class=\"led-box\" id=\"is-browser-supported\" title=\"Indicates if the browser is supported\">\n                                <div class=\"led-yellow\"></div>\n                            </div>\n                        </td>\n                    </tr>\n                </table>\n                <table id=\"main-controls\" hidden>\n                    <tr>\n                        <td colspan=1>\n                            Tx protocol:\n                            <select id=\"waveConfig\" onChange=\"selectConfig(this.value);\" title=\"Before broadcast/receive make sure this is set to the same value for all peers\">\n                                <option value=0>Normal</option>\n                                <option value=1 selected>Fast</option>\n                                <option value=2>Fastest</option>\n                                <option value=3>[Ultrasound] Normal</option>\n                                <option value=4>[Ultrasound] Fast</option>\n                                <option value=5>[Ultrasound] Fastest</option>\n                            </select>\n                        </td>\n                        <td colspan=1>\n                            Volume:\n                            <input type=\"range\" min=\"1\" max=\"100\" value=\"10\" class=\"slider\" id=\"paramVolume\" onInput=\"updateScroll('Volume');\">\n                            / <label id=\"paramVolumeScroll\"></label></td>\n                        </td>\n                    </tr>\n                    <tr>\n                        <td colspan=1>\n                            <div>Rx data:</div> <textarea name=\"textarea\" id=\"rxData\" style=\"width:300px;height:100px;\" disabled>\n                            </textarea>\n                        </td>\n                        <td colspan=1>\n                            <div>Tx Data:</div> <textarea name=\"textarea\" id=\"txData\" style=\"width:300px;height:100px;\">This is the message to transmit.</textarea><br>\n                        </td>\n                    </tr>\n                    <tr>\n                        <td colspan=1>\n                            <a id=\"peer-info\"></a>\n                        </td>\n                        <td colspan=1>\n                            <button onClick=\"lockoutSubmit(this); transmitText(document.getElementById('txData').value);\">Broadcast</button>\n                        </td>\n                    </tr>\n                </table>\n\n                <span id=\"status\"></span>\n            </section>\n\n            <br><hr>\n\n            <p>Standard output:</p>\n            <textarea id=\"output\" rows=\"8\"></textarea>\n\n            <div class=\"spinner\" id='spinnerEm'></div>\n            <div class=\"emscripten\" id=\"statusEm\">Downloading...</div>\n\n            <div class=\"emscripten\">\n                <progress value=\"0\" max=\"100\" id=\"progressEm\" hidden=1></progress>\n            </div>\n        </div>\n\n        <div class=\"cell-version\">\n            <span>\n                |\n                Build time: <span class=\"nav-link\">@GIT_DATE@</span> |\n                Commit hash: <a class=\"nav-link\" href=\"https://github.com/ggerganov/ggwave/commit/@GIT_SHA1@\">@GIT_SHA1@</a> |\n                Commit subject: <span class=\"nav-link\">@GIT_COMMIT_SUBJECT@</span> |\n            </span>\n        </div>\n        <div class=\"cell-about\">\n            <a class=\"nav-link\" href=\"https://github.com/ggerganov/ggwave/tree/master\"><span class=\"d-none d-sm-inline\">View on GitHub </span>\n                <svg version=\"1.1\" width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" class=\"octicon octicon-mark-github\" aria-hidden=\"true\"><path fill-rule=\"evenodd\" d=\"M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0 0 16 8c0-4.42-3.58-8-8-8z\"></path></svg>\n            </a>\n        </div>\n\n        <script type='text/javascript'>\n            var bufferRx = null;\n            var brx = new Uint8Array(256);\n\n            var volume = 10;\n            var protocolId = 1;\n\n            function lockoutSubmit(button) {\n                var oldValue = button.value;\n\n                button.setAttribute('disabled', true);\n                button.value = '...processing...';\n\n                setTimeout(function(){\n                    button.value = oldValue;\n                    button.removeAttribute('disabled');\n                }, 5000)\n            }\n\n            function selectConfig(configId) {\n                protocolId = parseInt(configId);\n            }\n\n            function updateScroll(sName) {\n                val = document.getElementById('param'+sName).value;\n                document.getElementById('param'+sName+'Scroll').innerHTML = val;\n                volume = val;\n            }\n\n            function getSampleRate() {\n                if (typeof Module === 'undefined') return;\n                var sampleRate = Module._getSampleRate();\n            }\n\n            var isiOS = /iPad|iPhone|iPod|CriOS/.test(navigator.userAgent) && !window.MSStream;\n            var isInitialized = false;\n            var isAudioContextUnlocked = true;\n\n            var htmlGreenLED  = \"<div class=\\\"led-green\\\"></div>\";\n            var htmlRedLED    = \"<div class=\\\"led-red\\\"></div>\";\n            var htmlYellowLED = \"<div class=\\\"led-yellow\\\"></div>\";\n\n            function checkStatus() {\n                var hasDeviceOutput = Module._hasDeviceOutput();\n                {\n                    var el = document.getElementById('has-device-output');\n                    if (hasDeviceOutput != 0 && isAudioContextUnlocked) {\n                        if (el.innerHTML != htmlGreenLED) {\n                            el.innerHTML = htmlGreenLED;\n                        }\n                    } else {\n                        if (el.innerHTML != htmlRedLED) {\n                            el.innerHTML = htmlRedLED;\n                        }\n                    }\n                }\n\n                var hasDeviceCapture = Module._hasDeviceCapture();\n                {\n                    var el = document.getElementById('has-device-capture');\n                    if (hasDeviceCapture != 0) {\n                        if (el.innerHTML != htmlGreenLED) {\n                            el.innerHTML = htmlGreenLED;\n                        }\n                    } else {\n                        if (el.innerHTML != htmlRedLED) {\n                            el.innerHTML = htmlRedLED;\n                        }\n                    }\n                }\n\n                var isBrowserSupported = true;\n                {\n                    var el = document.getElementById('is-browser-supported');\n                    if (isBrowserSupported) {\n                        if (el.innerHTML != htmlGreenLED) {\n                            el.innerHTML = htmlGreenLED;\n                        }\n                    } else {\n                        if (el.innerHTML != htmlYellowLED) {\n                            el.innerHTML = htmlYellowLED;\n                        }\n                    }\n                }\n            }\n\n            var statusElement = document.getElementById('statusEm');\n            var progressElement = document.getElementById('progressEm');\n            var spinnerElement = document.getElementById('spinnerEm');\n\n            var Module = {\n                doNotCaptureKeyboard: true,\n                pre: [],\n                preRun: [(function() {\n                    let constraints = {\n                        audio: {\n                            echoCancellation: false,\n                            autoGainControl: false,\n                            noiseSuppression: false\n                        }\n                    };\n\n                    let mediaInput = navigator.mediaDevices.getUserMedia( constraints );\n                }) ],\n                postRun: [ (function() { document.getElementById(\"butInit\").disabled = false; }) ],\n                print: (function() {\n                    var element = document.getElementById('output');\n                    if (element) element.value = ''; // clear browser cache\n                    return function(text) {\n                        if (arguments.length > 1) text = Array.prototype.slice.call(arguments).join(' ');\n                        console.log(text);\n                        if (element) {\n                            element.value += text + \"\\n\";\n                            element.scrollTop = element.scrollHeight; // focus on bottom\n                        }\n                    };\n                })(),\n                printErr: function(text) {\n                    if (arguments.length > 1) text = Array.prototype.slice.call(arguments).join(' ');\n                    console.error(text);\n                },\n                setStatus: function(text) {\n                    if (!Module.setStatus.last) Module.setStatus.last = { time: Date.now(), text: '' };\n                    if (text === Module.setStatus.text) return;\n                    var m = text.match(/([^(]+)\\((\\d+(\\.\\d+)?)\\/(\\d+)\\)/);\n                    var now = Date.now();\n                    if (m && now - Date.now() < 30) return; // if this is a progress update, skip it if too soon\n                    if (m) {\n                        text = m[1];\n                        progressElement.value = parseInt(m[2])*100;\n                        progressElement.max = parseInt(m[4])*100;\n                        progressElement.hidden = false;\n                        spinnerElement.hidden = false;\n                    } else {\n                        progressElement.value = null;\n                        progressElement.max = null;\n                        progressElement.hidden = true;\n                        if (!text) spinnerElement.style.display = 'none';\n                    }\n                    statusElement.innerHTML = text;\n                },\n                totalDependencies: 0,\n                monitorRunDependencies: function(left) {\n                    this.totalDependencies = Math.max(this.totalDependencies, left);\n                    Module.setStatus(left ? 'Preparing... (' + (this.totalDependencies-left) + '/' + this.totalDependencies + ')' : 'All downloads complete.');\n                }\n            };\n\n            function doInit() {\n                if (isInitialized == false) {\n                    Module._doInit();\n\n                    e = document.getElementById('waveConfig');\n                    selectConfig(e.options[e.selectedIndex].value);\n\n                    updateScroll('Volume');\n                    bufferRx = Module._malloc(256);\n                    setInterval(updatePeerInfo, 100);\n                    setInterval(updateRx, 1000);\n                    setInterval(checkStatus, 1000);\n                    isInitialized = true;\n                }\n\n                playSound(\"plucky\");\n                var x = document.getElementById(\"main-controls\");\n                //x.style.display = \"block\";\n                x.hidden = false;\n            }\n\n            Module.setStatus('Initializing...');\n            window.onerror = function(event) {\n                Module.setStatus('Exception thrown: ' + JSON.stringify(event));\n                spinnerElement.style.display = 'none';\n                Module.setStatus = function(text) {\n                    if (text) Module.printErr('[post-exception status] ' + text);\n                };\n            };\n\n            window.addEventListener('touchstart', function() {\n                //if (isAudioContextUnlocked == false && SDL2.audioContext) {\n                //    var buffer = SDL2.audioContext.createBuffer(1, 1, 22050);\n                //    var source = SDL2.audioContext.createBufferSource();\n                //    source.buffer = buffer;\n                //    source.connect(SDL2.audioContext.destination);\n                //    source.start();\n\n                //    setTimeout(function() {\n                //        if((source.playbackState === source.PLAYING_STATE || source.playbackState === source.FINISHED_STATE)) {\n                //            isAudioContextUnlocked = true;\n                //            Module.setStatus('Wab Audio API unlocked successfully!');\n                //        } else {\n                //            Module.setStatus('Failed to unlock Web Audio APIi. This browser seems to not be supported');\n                //        }\n                //    }, 0);\n                //}\n            }, false);\n\n            function playSound(filename){\n                document.getElementById(\"sound\").innerHTML='<audio id=\"soundInner\"><source src=\"' + filename + '.mp3\" type=\"audio/mpeg\" /><embed hidden=\"true\" autostart=\"true\" loop=\"false\" src=\"' + filename +'.mp3\" /></audio>';\n                document.getElementById(\"soundInner\").volume = paramVolume.value/100.0;\n                document.getElementById(\"soundInner\").play();\n            }\n\n        </script>\n\n        <script async type=\"text/javascript\" src=\"@TARGET@.js\"></script>\n        <script type=\"text/javascript\" src=\"main.js\"></script>\n    </body>\n</html>\n"
  },
  {
    "path": "examples/ggwave-wasm/main.cpp",
    "content": "#include \"build_timestamp.h\"\n\n#include \"ggwave-common-sdl2.h\"\n\n#include \"emscripten/emscripten.h\"\n\nvoid update() {\n    GGWave_mainLoop();\n}\n\nint main(int , char** argv) {\n    printf(\"Build time: %s\\n\", BUILD_TIMESTAMP);\n    printf(\"Press the Init button to start\\n\");\n\n    if (argv[1]) {\n        GGWave_setDefaultCaptureDeviceName(argv[1]);\n    }\n\n    emscripten_set_main_loop(update, 60, 1);\n\n    return 0;\n}\n"
  },
  {
    "path": "examples/ggwave-wasm/main.js",
    "content": "function transmitText(sText) {\n    var r = new Uint8Array(256);\n    for (var i = 0; i < sText.length; ++i) {\n        r[i] = sText.charCodeAt(i);\n    }\n\n    var buffer = Module._malloc(256);\n    Module.writeArrayToMemory(r, buffer, 256);\n    Module._sendData(sText.length, buffer, protocolId, volume);\n    Module._free(buffer);\n}\n\nvar firstTimeFail = false;\nvar peerInfo = document.querySelector('a#peer-info');\n\nfunction updatePeerInfo() {\n    if (typeof Module === 'undefined') return;\n    var framesLeftToRecord = Module._getFramesLeftToRecord();\n    var framesToRecord = Module._getFramesToRecord();\n    var framesLeftToAnalyze = Module._getFramesLeftToAnalyze();\n    var framesToAnalyze = Module._getFramesToAnalyze();\n\n    if (framesToAnalyze > 0) {\n        peerInfo.innerHTML=\n            \"Analyzing Rx data: <progress value=\" + (framesToAnalyze - framesLeftToAnalyze) +\n            \" max=\" + (framesToRecord) + \"></progress>\";\n        peerReceive.innerHTML= \"\";\n    } else if (framesLeftToRecord > Math.max(0, 0.05*framesToRecord)) {\n        firstTimeFail = true;\n        peerInfo.innerHTML=\n            \"Transmission in progress: <progress value=\" + (framesToRecord - framesLeftToRecord) +\n            \" max=\" + (framesToRecord) + \"></progress>\";\n    } else if (framesToRecord > 0) {\n        peerInfo.innerHTML= \"Analyzing Rx data ...\";\n    } else if (framesToRecord == 0) {\n        peerInfo.innerHTML= \"<p>Listening for waves ...</p>\";\n    } else if (framesToRecord == -1) {\n        if (firstTimeFail) {\n            playSound(\"/media/case-closed\");\n            firstTimeFail = false;\n        }\n        peerInfo.innerHTML= \"<p style=\\\"color:red\\\">Failed to decode Rx data</p>\";\n    }\n}\n\nfunction updateRx() {\n    if (typeof Module === 'undefined') return;\n    Module._getText(bufferRx);\n    var result = \"\";\n    for (var i = 0; i < 140; ++i){\n        result += (String.fromCharCode((Module.HEAPU8)[bufferRx + i]));\n        brx[i] = (Module.HEAPU8)[bufferRx + i];\n    }\n    document.getElementById('rxData').innerHTML = result;\n}\n"
  },
  {
    "path": "examples/ggwave-wasm/style.css",
    "content": "body {\n    margin: 0; background-color: white;\n    -webkit-font-smoothing: subpixel-antialiased;\n    font-smoothing: subpixel-antialiased;\n}\n#screen {\n    margin: 0;\n    padding: 0;\n    font-size: 13px;\n    height: 100%;\n    font: sans-serif;\n}\n.no-sel {\n    -moz-user-select: none;\n    -webkit-user-select: none;\n    -webkit-touch-callout: none;\n    -ms-user-select:none;\n    user-select:none;\n    -o-user-select:none;\n}\n.cell {\n    pointer-events: none;\n}\n.cell-version {\n    padding-left: 4px;\n    padding-top: 0.5em;\n    text-align: left;\n    display: inline-block;\n    float: left;\n    color: rgba(0, 0, 0, 0.75);\n}\n.cell-about {\n    padding-right: 24px;\n    padding-top: 0.5em;\n    text-align: right;\n    display: inline-block;\n    float: right;\n}\n.nav-link {\n    text-decoration: none;\n    color: rgba(0, 0, 0, 1.0);\n}\n\n#main-container {\n\tfont-size:12px;\n\tfont-family: monospace;\n}\n\ntextarea {\n\tfont-size:12px;\n\tfont-family: monospace;\n}\n\n.emscripten { padding-right: 0; margin-left: auto; margin-right: auto; display: block; }\ndiv.emscripten { text-align: center; }\ndiv.emscripten_border { border: 1px solid black; }\n\ncanvas.emscripten { border: 0px none; background-color: black; }\n\n.spinner {\n\theight: 30px;\n\twidth: 30px;\n\tmargin: 0;\n\tmargin-top: 20px;\n\tmargin-left: 20px;\n\tdisplay: inline-block;\n\tvertical-align: top;\n\n\t-webkit-animation: rotation .8s linear infinite;\n\t-moz-animation: rotation .8s linear infinite;\n\t-o-animation: rotation .8s linear infinite;\n\tanimation: rotation 0.8s linear infinite;\n\n\tborder-left: 5px solid rgb(235, 235, 235);\n\tborder-right: 5px solid rgb(235, 235, 235);\n\tborder-bottom: 5px solid rgb(235, 235, 235);\n\tborder-top: 5px solid rgb(120, 120, 120);\n\n\tborder-radius: 100%;\n\tbackground-color: rgb(189, 215, 46);\n}\n\n@-webkit-keyframes rotation {\n\tfrom {-webkit-transform: rotate(0deg);}\n\tto {-webkit-transform: rotate(360deg);}\n}\n@-moz-keyframes rotation {\n\tfrom {-moz-transform: rotate(0deg);}\n\tto {-moz-transform: rotate(360deg);}\n}\n@-o-keyframes rotation {\n\tfrom {-o-transform: rotate(0deg);}\n\tto {-o-transform: rotate(360deg);}\n}\n@keyframes rotation {\n\tfrom {transform: rotate(0deg);}\n\tto {transform: rotate(360deg);}\n}\n\n#status {\n\tdisplay: inline-block;\n\tvertical-align: top;\n\tmargin-top: 30px;\n\tmargin-left: 20px;\n\tfont-weight: bold;\n\tcolor: rgb(120, 120, 120);\n}\n\n#progress {\n\theight: 20px;\n\twidth: 30px;\n}\n\n#output {\n\twidth: 800px;\n\theight: 200px;\n\tmargin: 0 auto;\n\tmargin-top: 10px;\n\tborder-left: 0px;\n\tborder-right: 0px;\n\tpadding-left: 0px;\n\tpadding-right: 0px;\n\tbackground-color: black;\n\tcolor: white;\n\tfont-size:10px;\n\tfont-family: 'Lucida Console', Monaco, monospace;\n\toutline: none;\n}\n\n.led-box {\n\theight: 30px;\n\twidth: 25%;\n\tmargin: 10px 0;\n\tfloat: left;\n}\n\n.led-box p {\n\tfont-size: 12px;\n\ttext-align: center;\n\tmargin: 1em;\n}\n\n.led-red {\n\tmargin: 0 auto;\n\twidth: 12px;\n\theight: 12px;\n\tbackground-color: #F00;\n\tborder-radius: 50%;\n\tbox-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;\n\t-webkit-animation: blinkRed 0.5s infinite;\n\t-moz-animation: blinkRed 0.5s infinite;\n\t-ms-animation: blinkRed 0.5s infinite;\n\t-o-animation: blinkRed 0.5s infinite;\n\tanimation: blinkRed 0.5s infinite;\n}\n\n@-webkit-keyframes blinkRed {\n\tfrom { background-color: #F00; }\n\t50% { 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;}\n\tto { background-color: #F00; }\n}\n@-moz-keyframes blinkRed {\n\tfrom { background-color: #F00; }\n\t50% { 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;}\n\tto { background-color: #F00; }\n}\n@-ms-keyframes blinkRed {\n\tfrom { background-color: #F00; }\n\t50% { 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;}\n\tto { background-color: #F00; }\n}\n@-o-keyframes blinkRed {\n\tfrom { background-color: #F00; }\n\t50% { 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;}\n\tto { background-color: #F00; }\n}\n@keyframes blinkRed {\n\tfrom { background-color: #F00; }\n\t50% { 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;}\n\tto { background-color: #F00; }\n}\n\n.led-yellow {\n\tmargin: 0 auto;\n\twidth: 12px;\n\theight: 12px;\n\tbackground-color: #FF0;\n\tborder-radius: 50%;\n\tbox-shadow: rgba(0, 0, 0, 0.2) 0 -1px 7px 1px, inset #808002 0 -1px 9px, #FF0 0 2px 12px;\n\t-webkit-animation: blinkYellow 1s infinite;\n\t-moz-animation: blinkYellow 1s infinite;\n\t-ms-animation: blinkYellow 1s infinite;\n\t-o-animation: blinkYellow 1s infinite;\n\tanimation: blinkYellow 1s infinite;\n}\n\n@-webkit-keyframes blinkYellow {\n\tfrom { background-color: #FF0; }\n\t50% { background-color: #AA0; box-shadow: rgba(0, 0, 0, 0.2) 0 -1px 7px 1px, inset #808002 0 -1px 9px, #FF0 0 2px 0; }\n\tto { background-color: #FF0; }\n}\n@-moz-keyframes blinkYellow {\n\tfrom { background-color: #FF0; }\n\t50% { background-color: #AA0; box-shadow: rgba(0, 0, 0, 0.2) 0 -1px 7px 1px, inset #808002 0 -1px 9px, #FF0 0 2px 0; }\n\tto { background-color: #FF0; }\n}\n@-ms-keyframes blinkYellow {\n\tfrom { background-color: #FF0; }\n\t50% { background-color: #AA0; box-shadow: rgba(0, 0, 0, 0.2) 0 -1px 7px 1px, inset #808002 0 -1px 9px, #FF0 0 2px 0; }\n\tto { background-color: #FF0; }\n}\n@-o-keyframes blinkYellow {\n\tfrom { background-color: #FF0; }\n\t50% { background-color: #AA0; box-shadow: rgba(0, 0, 0, 0.2) 0 -1px 7px 1px, inset #808002 0 -1px 9px, #FF0 0 2px 0; }\n\tto { background-color: #FF0; }\n}\n@keyframes blinkYellow {\n\tfrom { background-color: #FF0; }\n\t50% { background-color: #AA0; box-shadow: rgba(0, 0, 0, 0.2) 0 -1px 7px 1px, inset #808002 0 -1px 9px, #FF0 0 2px 0; }\n\tto { background-color: #FF0; }\n}\n\n.led-green {\n\tmargin: 0 auto;\n\twidth: 12px;\n\theight: 12px;\n\tbackground-color: #ABFF00;\n\tborder-radius: 50%;\n\tbox-shadow: rgba(0, 0, 0, 0.2) 0 -1px 7px 1px, inset #304701 0 -1px 9px, #89FF00 0 2px 12px;\n}\n\n.led-blue {\n\tmargin: 0 auto;\n\twidth: 18px;\n\theight: 18px;\n\tbackground-color: #24E0FF;\n\tborder-radius: 50%;\n\tbox-shadow: rgba(0, 0, 0, 0.2) 0 -1px 7px 1px, inset #006 0 -1px 9px, #3F8CFF 0 2px 14px;\n}\n\ntable td {\n    border: 1px solid #e8e8e8;\n}\ntable th, table td {\n    padding: 10px 10px;\n}\ntable td {\n    border: 1px solid #e8e8e8;\n}\ntable th, table td {\n    padding: 10px 10px;\n}\ntd[Attributes Style] {\n    text-align: -webkit-center;\n}\ntd {\n    display: table-cell;\n    vertical-align: inherit;\n}\ntable {\n    margin-bottom: 30px;\n    width: 800px;\n    text-align: left;\n    color: #3f3f3f;\n    border-collapse: collapse;\n    border: 1px solid #e8e8e8;\n}\ntable {\n    margin-bottom: 30px;\n    width: 800px;\n    text-align: left;\n    color: #3f3f3f;\n    border-collapse: collapse;\n    border: 1px solid #e8e8e8;\n}\ntable {\n    border-collapse: separate;\n    border-spacing: 2px;\n}\n"
  },
  {
    "path": "examples/icons_font_awesome.h",
    "content": "// Generated by https://github.com/juliettef/IconFontCppHeaders script\n// GenerateIconFontCppHeaders.py for language C89 from\n// https://raw.githubusercontent.com/FortAwesome/Font-Awesome/master/metadata/icons.yml\n// for use with\n// https://github.com/FortAwesome/Font-Awesome/blob/master/webfonts/fa-solid-900.ttf,\n// https://github.com/FortAwesome/Font-Awesome/blob/master/webfonts/fa-regular-400.ttf,\n#pragma once\n\n#define FONT_ICON_FILE_NAME_FAR \"fa-regular-400.ttf\"\n#define FONT_ICON_FILE_NAME_FAS \"fa-solid-900.ttf\"\n\n#define ICON_MIN_FA 0xf000\n#define ICON_MAX_FA 0xf897\n#define ICON_FA_CLOUD_SHOWERS_HEAVY \"\\xEF\\x9D\\x80\"\n#define ICON_FA_CHEVRON_CIRCLE_RIGHT \"\\xEF\\x84\\xB8\"\n#define ICON_FA_DHARMACHAKRA \"\\xEF\\x99\\x95\"\n#define ICON_FA_BROADCAST_TOWER \"\\xEF\\x94\\x99\"\n#define ICON_FA_EXTERNAL_LINK_SQUARE_ALT \"\\xEF\\x8D\\xA0\"\n#define ICON_FA_SMOKING \"\\xEF\\x92\\x8D\"\n#define ICON_FA_PENCIL_ALT \"\\xEF\\x8C\\x83\"\n#define ICON_FA_CHESS_BISHOP \"\\xEF\\x90\\xBA\"\n#define ICON_FA_ICONS \"\\xEF\\xA1\\xAD\"\n#define ICON_FA_TV \"\\xEF\\x89\\xAC\"\n#define ICON_FA_CROP_ALT \"\\xEF\\x95\\xA5\"\n#define ICON_FA_LIST \"\\xEF\\x80\\xBA\"\n#define ICON_FA_BATTERY_QUARTER \"\\xEF\\x89\\x83\"\n#define ICON_FA_TH \"\\xEF\\x80\\x8A\"\n#define ICON_FA_RECYCLE \"\\xEF\\x86\\xB8\"\n#define ICON_FA_SMILE \"\\xEF\\x84\\x98\"\n#define ICON_FA_FAX \"\\xEF\\x86\\xAC\"\n#define ICON_FA_DRAFTING_COMPASS \"\\xEF\\x95\\xA8\"\n#define ICON_FA_USER_INJURED \"\\xEF\\x9C\\xA8\"\n#define ICON_FA_SCREWDRIVER \"\\xEF\\x95\\x8A\"\n#define ICON_FA_CROSSHAIRS \"\\xEF\\x81\\x9B\"\n#define ICON_FA_HAND_PEACE \"\\xEF\\x89\\x9B\"\n#define ICON_FA_FAN \"\\xEF\\xA1\\xA3\"\n#define ICON_FA_GOPURAM \"\\xEF\\x99\\xA4\"\n#define ICON_FA_CARET_UP \"\\xEF\\x83\\x98\"\n#define ICON_FA_SCHOOL \"\\xEF\\x95\\x89\"\n#define ICON_FA_FILE_PDF \"\\xEF\\x87\\x81\"\n#define ICON_FA_USERS_COG \"\\xEF\\x94\\x89\"\n#define ICON_FA_BALANCE_SCALE \"\\xEF\\x89\\x8E\"\n#define ICON_FA_UPLOAD \"\\xEF\\x82\\x93\"\n#define ICON_FA_LAPTOP_MEDICAL \"\\xEF\\xA0\\x92\"\n#define ICON_FA_VENUS \"\\xEF\\x88\\xA1\"\n#define ICON_FA_HEADING \"\\xEF\\x87\\x9C\"\n#define ICON_FA_ARROW_DOWN \"\\xEF\\x81\\xA3\"\n#define ICON_FA_BICYCLE \"\\xEF\\x88\\x86\"\n#define ICON_FA_TIRED \"\\xEF\\x97\\x88\"\n#define ICON_FA_COMMENT_MEDICAL \"\\xEF\\x9F\\xB5\"\n#define ICON_FA_BACON \"\\xEF\\x9F\\xA5\"\n#define ICON_FA_SYNC \"\\xEF\\x80\\xA1\"\n#define ICON_FA_PAPER_PLANE \"\\xEF\\x87\\x98\"\n#define ICON_FA_VOLLEYBALL_BALL \"\\xEF\\x91\\x9F\"\n#define ICON_FA_RIBBON \"\\xEF\\x93\\x96\"\n#define ICON_FA_SQUARE_ROOT_ALT \"\\xEF\\x9A\\x98\"\n#define ICON_FA_SUN \"\\xEF\\x86\\x85\"\n#define ICON_FA_FILE_POWERPOINT \"\\xEF\\x87\\x84\"\n#define ICON_FA_MICROCHIP \"\\xEF\\x8B\\x9B\"\n#define ICON_FA_TRASH_RESTORE_ALT \"\\xEF\\xA0\\xAA\"\n#define ICON_FA_GRADUATION_CAP \"\\xEF\\x86\\x9D\"\n#define ICON_FA_INFO_CIRCLE \"\\xEF\\x81\\x9A\"\n#define ICON_FA_TAGS \"\\xEF\\x80\\xAC\"\n#define ICON_FA_HAND_PAPER \"\\xEF\\x89\\x96\"\n#define ICON_FA_EQUALS \"\\xEF\\x94\\xAC\"\n#define ICON_FA_DIRECTIONS \"\\xEF\\x97\\xAB\"\n#define ICON_FA_FILE_INVOICE \"\\xEF\\x95\\xB0\"\n#define ICON_FA_SEARCH \"\\xEF\\x80\\x82\"\n#define ICON_FA_BIBLE \"\\xEF\\x99\\x87\"\n#define ICON_FA_WEIGHT_HANGING \"\\xEF\\x97\\x8D\"\n#define ICON_FA_CALENDAR_TIMES \"\\xEF\\x89\\xB3\"\n#define ICON_FA_GREATER_THAN_EQUAL \"\\xEF\\x94\\xB2\"\n#define ICON_FA_SLIDERS_H \"\\xEF\\x87\\x9E\"\n#define ICON_FA_EYE_SLASH \"\\xEF\\x81\\xB0\"\n#define ICON_FA_BIRTHDAY_CAKE \"\\xEF\\x87\\xBD\"\n#define ICON_FA_FEATHER_ALT \"\\xEF\\x95\\xAB\"\n#define ICON_FA_DNA \"\\xEF\\x91\\xB1\"\n#define ICON_FA_BASEBALL_BALL \"\\xEF\\x90\\xB3\"\n#define ICON_FA_HOSPITAL \"\\xEF\\x83\\xB8\"\n#define ICON_FA_COINS \"\\xEF\\x94\\x9E\"\n#define ICON_FA_TEMPERATURE_HIGH \"\\xEF\\x9D\\xA9\"\n#define ICON_FA_FONT_AWESOME_LOGO_FULL \"\\xEF\\x93\\xA6\"\n#define ICON_FA_PASSPORT \"\\xEF\\x96\\xAB\"\n#define ICON_FA_SHOPPING_CART \"\\xEF\\x81\\xBA\"\n#define ICON_FA_AWARD \"\\xEF\\x95\\x99\"\n#define ICON_FA_WINDOW_RESTORE \"\\xEF\\x8B\\x92\"\n#define ICON_FA_PHONE \"\\xEF\\x82\\x95\"\n#define ICON_FA_FLAG \"\\xEF\\x80\\xA4\"\n#define ICON_FA_FILE_INVOICE_DOLLAR \"\\xEF\\x95\\xB1\"\n#define ICON_FA_DICE_D6 \"\\xEF\\x9B\\x91\"\n#define ICON_FA_OUTDENT \"\\xEF\\x80\\xBB\"\n#define ICON_FA_LONG_ARROW_ALT_RIGHT \"\\xEF\\x8C\\x8B\"\n#define ICON_FA_PIZZA_SLICE \"\\xEF\\xA0\\x98\"\n#define ICON_FA_ADDRESS_CARD \"\\xEF\\x8A\\xBB\"\n#define ICON_FA_PARAGRAPH \"\\xEF\\x87\\x9D\"\n#define ICON_FA_MALE \"\\xEF\\x86\\x83\"\n#define ICON_FA_HISTORY \"\\xEF\\x87\\x9A\"\n#define ICON_FA_USER_TIE \"\\xEF\\x94\\x88\"\n#define ICON_FA_SEARCH_PLUS \"\\xEF\\x80\\x8E\"\n#define ICON_FA_LIFE_RING \"\\xEF\\x87\\x8D\"\n#define ICON_FA_STEP_FORWARD \"\\xEF\\x81\\x91\"\n#define ICON_FA_MOUSE_POINTER \"\\xEF\\x89\\x85\"\n#define ICON_FA_ALIGN_JUSTIFY \"\\xEF\\x80\\xB9\"\n#define ICON_FA_TOILET_PAPER \"\\xEF\\x9C\\x9E\"\n#define ICON_FA_BATTERY_THREE_QUARTERS \"\\xEF\\x89\\x81\"\n#define ICON_FA_OBJECT_UNGROUP \"\\xEF\\x89\\x88\"\n#define ICON_FA_BRIEFCASE \"\\xEF\\x82\\xB1\"\n#define ICON_FA_OIL_CAN \"\\xEF\\x98\\x93\"\n#define ICON_FA_THERMOMETER_FULL \"\\xEF\\x8B\\x87\"\n#define ICON_FA_SNOWBOARDING \"\\xEF\\x9F\\x8E\"\n#define ICON_FA_UNLINK \"\\xEF\\x84\\xA7\"\n#define ICON_FA_WINDOW_MAXIMIZE \"\\xEF\\x8B\\x90\"\n#define ICON_FA_YEN_SIGN \"\\xEF\\x85\\x97\"\n#define ICON_FA_SHARE_ALT_SQUARE \"\\xEF\\x87\\xA1\"\n#define ICON_FA_STEP_BACKWARD \"\\xEF\\x81\\x88\"\n#define ICON_FA_DRAGON \"\\xEF\\x9B\\x95\"\n#define ICON_FA_MICROPHONE_SLASH \"\\xEF\\x84\\xB1\"\n#define ICON_FA_USER_PLUS \"\\xEF\\x88\\xB4\"\n#define ICON_FA_WRENCH \"\\xEF\\x82\\xAD\"\n#define ICON_FA_AMBULANCE \"\\xEF\\x83\\xB9\"\n#define ICON_FA_ETHERNET \"\\xEF\\x9E\\x96\"\n#define ICON_FA_EGG \"\\xEF\\x9F\\xBB\"\n#define ICON_FA_WIND \"\\xEF\\x9C\\xAE\"\n#define ICON_FA_UNIVERSAL_ACCESS \"\\xEF\\x8A\\x9A\"\n#define ICON_FA_BURN \"\\xEF\\x91\\xAA\"\n#define ICON_FA_RADIATION \"\\xEF\\x9E\\xB9\"\n#define ICON_FA_DICE_ONE \"\\xEF\\x94\\xA5\"\n#define ICON_FA_KEYBOARD \"\\xEF\\x84\\x9C\"\n#define ICON_FA_CHECK_DOUBLE \"\\xEF\\x95\\xA0\"\n#define ICON_FA_HEADPHONES_ALT \"\\xEF\\x96\\x8F\"\n#define ICON_FA_BATTERY_HALF \"\\xEF\\x89\\x82\"\n#define ICON_FA_PROJECT_DIAGRAM \"\\xEF\\x95\\x82\"\n#define ICON_FA_PRAY \"\\xEF\\x9A\\x83\"\n#define ICON_FA_PHONE_ALT \"\\xEF\\xA1\\xB9\"\n#define ICON_FA_BABY_CARRIAGE \"\\xEF\\x9D\\xBD\"\n#define ICON_FA_TH_LIST \"\\xEF\\x80\\x8B\"\n#define ICON_FA_GRIN_TEARS \"\\xEF\\x96\\x88\"\n#define ICON_FA_SORT_AMOUNT_UP \"\\xEF\\x85\\xA1\"\n#define ICON_FA_COFFEE \"\\xEF\\x83\\xB4\"\n#define ICON_FA_TABLET_ALT \"\\xEF\\x8F\\xBA\"\n#define ICON_FA_GRIN_BEAM_SWEAT \"\\xEF\\x96\\x83\"\n#define ICON_FA_HAND_POINT_RIGHT \"\\xEF\\x82\\xA4\"\n#define ICON_FA_GRIN_STARS \"\\xEF\\x96\\x87\"\n#define ICON_FA_CHARGING_STATION \"\\xEF\\x97\\xA7\"\n#define ICON_FA_VOTE_YEA \"\\xEF\\x9D\\xB2\"\n#define ICON_FA_VOLUME_OFF \"\\xEF\\x80\\xA6\"\n#define ICON_FA_SAD_TEAR \"\\xEF\\x96\\xB4\"\n#define ICON_FA_CARET_RIGHT \"\\xEF\\x83\\x9A\"\n#define ICON_FA_BONG \"\\xEF\\x95\\x9C\"\n#define ICON_FA_BONE \"\\xEF\\x97\\x97\"\n#define ICON_FA_WEIGHT \"\\xEF\\x92\\x96\"\n#define ICON_FA_CARET_SQUARE_RIGHT \"\\xEF\\x85\\x92\"\n#define ICON_FA_FISH \"\\xEF\\x95\\xB8\"\n#define ICON_FA_SPIDER \"\\xEF\\x9C\\x97\"\n#define ICON_FA_QRCODE \"\\xEF\\x80\\xA9\"\n#define ICON_FA_SPINNER \"\\xEF\\x84\\x90\"\n#define ICON_FA_ELLIPSIS_H \"\\xEF\\x85\\x81\"\n#define ICON_FA_RUPEE_SIGN \"\\xEF\\x85\\x96\"\n#define ICON_FA_ASSISTIVE_LISTENING_SYSTEMS \"\\xEF\\x8A\\xA2\"\n#define ICON_FA_SMS \"\\xEF\\x9F\\x8D\"\n#define ICON_FA_POUND_SIGN \"\\xEF\\x85\\x94\"\n#define ICON_FA_HAND_POINT_DOWN \"\\xEF\\x82\\xA7\"\n#define ICON_FA_ADJUST \"\\xEF\\x81\\x82\"\n#define ICON_FA_PRINT \"\\xEF\\x80\\xAF\"\n#define ICON_FA_SURPRISE \"\\xEF\\x97\\x82\"\n#define ICON_FA_SORT_NUMERIC_UP \"\\xEF\\x85\\xA3\"\n#define ICON_FA_VIDEO_SLASH \"\\xEF\\x93\\xA2\"\n#define ICON_FA_SUBWAY \"\\xEF\\x88\\xB9\"\n#define ICON_FA_SORT_AMOUNT_DOWN \"\\xEF\\x85\\xA0\"\n#define ICON_FA_WINE_BOTTLE \"\\xEF\\x9C\\xAF\"\n#define ICON_FA_BOOK_READER \"\\xEF\\x97\\x9A\"\n#define ICON_FA_COOKIE \"\\xEF\\x95\\xA3\"\n#define ICON_FA_MONEY_BILL \"\\xEF\\x83\\x96\"\n#define ICON_FA_CHEVRON_DOWN \"\\xEF\\x81\\xB8\"\n#define ICON_FA_CAR_SIDE \"\\xEF\\x97\\xA4\"\n#define ICON_FA_FILTER \"\\xEF\\x82\\xB0\"\n#define ICON_FA_FOLDER_OPEN \"\\xEF\\x81\\xBC\"\n#define ICON_FA_SIGNATURE \"\\xEF\\x96\\xB7\"\n#define ICON_FA_HEARTBEAT \"\\xEF\\x88\\x9E\"\n#define ICON_FA_THUMBTACK \"\\xEF\\x82\\x8D\"\n#define ICON_FA_USER \"\\xEF\\x80\\x87\"\n#define ICON_FA_LAUGH_WINK \"\\xEF\\x96\\x9C\"\n#define ICON_FA_BREAD_SLICE \"\\xEF\\x9F\\xAC\"\n#define ICON_FA_TEXT_HEIGHT \"\\xEF\\x80\\xB4\"\n#define ICON_FA_VOLUME_MUTE \"\\xEF\\x9A\\xA9\"\n#define ICON_FA_GRIN_TONGUE \"\\xEF\\x96\\x89\"\n#define ICON_FA_CAMPGROUND \"\\xEF\\x9A\\xBB\"\n#define ICON_FA_MERCURY \"\\xEF\\x88\\xA3\"\n#define ICON_FA_USER_ASTRONAUT \"\\xEF\\x93\\xBB\"\n#define ICON_FA_HORSE \"\\xEF\\x9B\\xB0\"\n#define ICON_FA_SORT_DOWN \"\\xEF\\x83\\x9D\"\n#define ICON_FA_PERCENTAGE \"\\xEF\\x95\\x81\"\n#define ICON_FA_AIR_FRESHENER \"\\xEF\\x97\\x90\"\n#define ICON_FA_STORE \"\\xEF\\x95\\x8E\"\n#define ICON_FA_COMMENT_DOTS \"\\xEF\\x92\\xAD\"\n#define ICON_FA_SMILE_WINK \"\\xEF\\x93\\x9A\"\n#define ICON_FA_HOTEL \"\\xEF\\x96\\x94\"\n#define ICON_FA_PEPPER_HOT \"\\xEF\\xA0\\x96\"\n#define ICON_FA_CUBES \"\\xEF\\x86\\xB3\"\n#define ICON_FA_DUMPSTER_FIRE \"\\xEF\\x9E\\x94\"\n#define ICON_FA_CLOUD_SUN_RAIN \"\\xEF\\x9D\\x83\"\n#define ICON_FA_GLOBE_ASIA \"\\xEF\\x95\\xBE\"\n#define ICON_FA_VIAL \"\\xEF\\x92\\x92\"\n#define ICON_FA_STROOPWAFEL \"\\xEF\\x95\\x91\"\n#define ICON_FA_CALENDAR_MINUS \"\\xEF\\x89\\xB2\"\n#define ICON_FA_TREE \"\\xEF\\x86\\xBB\"\n#define ICON_FA_SHOWER \"\\xEF\\x8B\\x8C\"\n#define ICON_FA_DRUM_STEELPAN \"\\xEF\\x95\\xAA\"\n#define ICON_FA_FILE_UPLOAD \"\\xEF\\x95\\xB4\"\n#define ICON_FA_MEDKIT \"\\xEF\\x83\\xBA\"\n#define ICON_FA_MINUS \"\\xEF\\x81\\xA8\"\n#define ICON_FA_SHEKEL_SIGN \"\\xEF\\x88\\x8B\"\n#define ICON_FA_USER_NINJA \"\\xEF\\x94\\x84\"\n#define ICON_FA_KAABA \"\\xEF\\x99\\xAB\"\n#define ICON_FA_BELL_SLASH \"\\xEF\\x87\\xB6\"\n#define ICON_FA_SPELL_CHECK \"\\xEF\\xA2\\x91\"\n#define ICON_FA_MAIL_BULK \"\\xEF\\x99\\xB4\"\n#define ICON_FA_MOUNTAIN \"\\xEF\\x9B\\xBC\"\n#define ICON_FA_COUCH \"\\xEF\\x92\\xB8\"\n#define ICON_FA_CHESS \"\\xEF\\x90\\xB9\"\n#define ICON_FA_FILE_EXPORT \"\\xEF\\x95\\xAE\"\n#define ICON_FA_SIGN_LANGUAGE \"\\xEF\\x8A\\xA7\"\n#define ICON_FA_SNOWFLAKE \"\\xEF\\x8B\\x9C\"\n#define ICON_FA_PLAY \"\\xEF\\x81\\x8B\"\n#define ICON_FA_HEADSET \"\\xEF\\x96\\x90\"\n#define ICON_FA_CHART_BAR \"\\xEF\\x82\\x80\"\n#define ICON_FA_WAVE_SQUARE \"\\xEF\\xA0\\xBE\"\n#define ICON_FA_CHART_AREA \"\\xEF\\x87\\xBE\"\n#define ICON_FA_EURO_SIGN \"\\xEF\\x85\\x93\"\n#define ICON_FA_CHESS_KING \"\\xEF\\x90\\xBF\"\n#define ICON_FA_MOBILE \"\\xEF\\x84\\x8B\"\n#define ICON_FA_CLOCK \"\\xEF\\x80\\x97\"\n#define ICON_FA_BOX_OPEN \"\\xEF\\x92\\x9E\"\n#define ICON_FA_DOG \"\\xEF\\x9B\\x93\"\n#define ICON_FA_FUTBOL \"\\xEF\\x87\\xA3\"\n#define ICON_FA_LIRA_SIGN \"\\xEF\\x86\\x95\"\n#define ICON_FA_LIGHTBULB \"\\xEF\\x83\\xAB\"\n#define ICON_FA_BOMB \"\\xEF\\x87\\xA2\"\n#define ICON_FA_MITTEN \"\\xEF\\x9E\\xB5\"\n#define ICON_FA_TRUCK_MONSTER \"\\xEF\\x98\\xBB\"\n#define ICON_FA_RANDOM \"\\xEF\\x81\\xB4\"\n#define ICON_FA_CHESS_ROOK \"\\xEF\\x91\\x87\"\n#define ICON_FA_FIRE_EXTINGUISHER \"\\xEF\\x84\\xB4\"\n#define ICON_FA_ARROWS_ALT_V \"\\xEF\\x8C\\xB8\"\n#define ICON_FA_ICICLES \"\\xEF\\x9E\\xAD\"\n#define ICON_FA_FONT \"\\xEF\\x80\\xB1\"\n#define ICON_FA_CAMERA_RETRO \"\\xEF\\x82\\x83\"\n#define ICON_FA_BLENDER \"\\xEF\\x94\\x97\"\n#define ICON_FA_THUMBS_DOWN \"\\xEF\\x85\\xA5\"\n#define ICON_FA_ROCKET \"\\xEF\\x84\\xB5\"\n#define ICON_FA_COPYRIGHT \"\\xEF\\x87\\xB9\"\n#define ICON_FA_TRAM \"\\xEF\\x9F\\x9A\"\n#define ICON_FA_JEDI \"\\xEF\\x99\\xA9\"\n#define ICON_FA_HOCKEY_PUCK \"\\xEF\\x91\\x93\"\n#define ICON_FA_STOP_CIRCLE \"\\xEF\\x8A\\x8D\"\n#define ICON_FA_BEZIER_CURVE \"\\xEF\\x95\\x9B\"\n#define ICON_FA_FOLDER \"\\xEF\\x81\\xBB\"\n#define ICON_FA_CALENDAR_CHECK \"\\xEF\\x89\\xB4\"\n#define ICON_FA_YIN_YANG \"\\xEF\\x9A\\xAD\"\n#define ICON_FA_COLUMNS \"\\xEF\\x83\\x9B\"\n#define ICON_FA_GLASS_CHEERS \"\\xEF\\x9E\\x9F\"\n#define ICON_FA_GRIN_WINK \"\\xEF\\x96\\x8C\"\n#define ICON_FA_STOP \"\\xEF\\x81\\x8D\"\n#define ICON_FA_MONEY_CHECK_ALT \"\\xEF\\x94\\xBD\"\n#define ICON_FA_COMPASS \"\\xEF\\x85\\x8E\"\n#define ICON_FA_TOOLBOX \"\\xEF\\x95\\x92\"\n#define ICON_FA_LIST_OL \"\\xEF\\x83\\x8B\"\n#define ICON_FA_WINE_GLASS \"\\xEF\\x93\\xA3\"\n#define ICON_FA_HORSE_HEAD \"\\xEF\\x9E\\xAB\"\n#define ICON_FA_USER_ALT_SLASH \"\\xEF\\x93\\xBA\"\n#define ICON_FA_USER_TAG \"\\xEF\\x94\\x87\"\n#define ICON_FA_MICROSCOPE \"\\xEF\\x98\\x90\"\n#define ICON_FA_BRUSH \"\\xEF\\x95\\x9D\"\n#define ICON_FA_BAN \"\\xEF\\x81\\x9E\"\n#define ICON_FA_BARS \"\\xEF\\x83\\x89\"\n#define ICON_FA_CAR_CRASH \"\\xEF\\x97\\xA1\"\n#define ICON_FA_ARROW_ALT_CIRCLE_DOWN \"\\xEF\\x8D\\x98\"\n#define ICON_FA_MONEY_BILL_ALT \"\\xEF\\x8F\\x91\"\n#define ICON_FA_JOURNAL_WHILLS \"\\xEF\\x99\\xAA\"\n#define ICON_FA_CHALKBOARD_TEACHER \"\\xEF\\x94\\x9C\"\n#define ICON_FA_PORTRAIT \"\\xEF\\x8F\\xA0\"\n#define ICON_FA_BALANCE_SCALE_LEFT \"\\xEF\\x94\\x95\"\n#define ICON_FA_HAMMER \"\\xEF\\x9B\\xA3\"\n#define ICON_FA_RETWEET \"\\xEF\\x81\\xB9\"\n#define ICON_FA_HOURGLASS \"\\xEF\\x89\\x94\"\n#define ICON_FA_BORDER_NONE \"\\xEF\\xA1\\x90\"\n#define ICON_FA_FILE_ALT \"\\xEF\\x85\\x9C\"\n#define ICON_FA_SUBSCRIPT \"\\xEF\\x84\\xAC\"\n#define ICON_FA_DONATE \"\\xEF\\x92\\xB9\"\n#define ICON_FA_GLASS_MARTINI_ALT \"\\xEF\\x95\\xBB\"\n#define ICON_FA_CODE_BRANCH \"\\xEF\\x84\\xA6\"\n#define ICON_FA_MEH \"\\xEF\\x84\\x9A\"\n#define ICON_FA_LIST_ALT \"\\xEF\\x80\\xA2\"\n#define ICON_FA_USER_COG \"\\xEF\\x93\\xBE\"\n#define ICON_FA_PRESCRIPTION \"\\xEF\\x96\\xB1\"\n#define ICON_FA_TABLET \"\\xEF\\x84\\x8A\"\n#define ICON_FA_LAUGH_SQUINT \"\\xEF\\x96\\x9B\"\n#define ICON_FA_CREDIT_CARD \"\\xEF\\x82\\x9D\"\n#define ICON_FA_ARCHWAY \"\\xEF\\x95\\x97\"\n#define ICON_FA_HARD_HAT \"\\xEF\\xA0\\x87\"\n#define ICON_FA_TRAFFIC_LIGHT \"\\xEF\\x98\\xB7\"\n#define ICON_FA_COG \"\\xEF\\x80\\x93\"\n#define ICON_FA_HANUKIAH \"\\xEF\\x9B\\xA6\"\n#define ICON_FA_SHUTTLE_VAN \"\\xEF\\x96\\xB6\"\n#define ICON_FA_MONEY_CHECK \"\\xEF\\x94\\xBC\"\n#define ICON_FA_BELL \"\\xEF\\x83\\xB3\"\n#define ICON_FA_CALENDAR_DAY \"\\xEF\\x9E\\x83\"\n#define ICON_FA_TINT_SLASH \"\\xEF\\x97\\x87\"\n#define ICON_FA_PLANE_DEPARTURE \"\\xEF\\x96\\xB0\"\n#define ICON_FA_USER_CHECK \"\\xEF\\x93\\xBC\"\n#define ICON_FA_CHURCH \"\\xEF\\x94\\x9D\"\n#define ICON_FA_SEARCH_MINUS \"\\xEF\\x80\\x90\"\n#define ICON_FA_SHIPPING_FAST \"\\xEF\\x92\\x8B\"\n#define ICON_FA_TINT \"\\xEF\\x81\\x83\"\n#define ICON_FA_ALIGN_RIGHT \"\\xEF\\x80\\xB8\"\n#define ICON_FA_QUOTE_RIGHT \"\\xEF\\x84\\x8E\"\n#define ICON_FA_BEER \"\\xEF\\x83\\xBC\"\n#define ICON_FA_GRIN_ALT \"\\xEF\\x96\\x81\"\n#define ICON_FA_SORT_NUMERIC_DOWN \"\\xEF\\x85\\xA2\"\n#define ICON_FA_FIRE \"\\xEF\\x81\\xAD\"\n#define ICON_FA_FAST_FORWARD \"\\xEF\\x81\\x90\"\n#define ICON_FA_MAP_MARKED_ALT \"\\xEF\\x96\\xA0\"\n#define ICON_FA_CHILD \"\\xEF\\x86\\xAE\"\n#define ICON_FA_KISS_BEAM \"\\xEF\\x96\\x97\"\n#define ICON_FA_TRUCK_LOADING \"\\xEF\\x93\\x9E\"\n#define ICON_FA_EXPAND_ARROWS_ALT \"\\xEF\\x8C\\x9E\"\n#define ICON_FA_CARET_SQUARE_DOWN \"\\xEF\\x85\\x90\"\n#define ICON_FA_CRUTCH \"\\xEF\\x9F\\xB7\"\n#define ICON_FA_OBJECT_GROUP \"\\xEF\\x89\\x87\"\n#define ICON_FA_BIKING \"\\xEF\\xA1\\x8A\"\n#define ICON_FA_ANCHOR \"\\xEF\\x84\\xBD\"\n#define ICON_FA_HAND_POINT_LEFT \"\\xEF\\x82\\xA5\"\n#define ICON_FA_USER_TIMES \"\\xEF\\x88\\xB5\"\n#define ICON_FA_CALCULATOR \"\\xEF\\x87\\xAC\"\n#define ICON_FA_DIZZY \"\\xEF\\x95\\xA7\"\n#define ICON_FA_KISS_WINK_HEART \"\\xEF\\x96\\x98\"\n#define ICON_FA_FILE_MEDICAL \"\\xEF\\x91\\xB7\"\n#define ICON_FA_SWIMMING_POOL \"\\xEF\\x97\\x85\"\n#define ICON_FA_VR_CARDBOARD \"\\xEF\\x9C\\xA9\"\n#define ICON_FA_USER_FRIENDS \"\\xEF\\x94\\x80\"\n#define ICON_FA_FAST_BACKWARD \"\\xEF\\x81\\x89\"\n#define ICON_FA_SATELLITE \"\\xEF\\x9E\\xBF\"\n#define ICON_FA_MINUS_CIRCLE \"\\xEF\\x81\\x96\"\n#define ICON_FA_CHESS_PAWN \"\\xEF\\x91\\x83\"\n#define ICON_FA_DATABASE \"\\xEF\\x87\\x80\"\n#define ICON_FA_LANDMARK \"\\xEF\\x99\\xAF\"\n#define ICON_FA_SWATCHBOOK \"\\xEF\\x97\\x83\"\n#define ICON_FA_HOTDOG \"\\xEF\\xA0\\x8F\"\n#define ICON_FA_SNOWMAN \"\\xEF\\x9F\\x90\"\n#define ICON_FA_LAPTOP \"\\xEF\\x84\\x89\"\n#define ICON_FA_TORAH \"\\xEF\\x9A\\xA0\"\n#define ICON_FA_FROWN_OPEN \"\\xEF\\x95\\xBA\"\n#define ICON_FA_REDO_ALT \"\\xEF\\x8B\\xB9\"\n#define ICON_FA_AD \"\\xEF\\x99\\x81\"\n#define ICON_FA_USER_CIRCLE \"\\xEF\\x8A\\xBD\"\n#define ICON_FA_DIVIDE \"\\xEF\\x94\\xA9\"\n#define ICON_FA_HANDSHAKE \"\\xEF\\x8A\\xB5\"\n#define ICON_FA_CUT \"\\xEF\\x83\\x84\"\n#define ICON_FA_GAMEPAD \"\\xEF\\x84\\x9B\"\n#define ICON_FA_STREET_VIEW \"\\xEF\\x88\\x9D\"\n#define ICON_FA_GREATER_THAN \"\\xEF\\x94\\xB1\"\n#define ICON_FA_PASTAFARIANISM \"\\xEF\\x99\\xBB\"\n#define ICON_FA_MINUS_SQUARE \"\\xEF\\x85\\x86\"\n#define ICON_FA_SAVE \"\\xEF\\x83\\x87\"\n#define ICON_FA_COMMENT_DOLLAR \"\\xEF\\x99\\x91\"\n#define ICON_FA_TRASH_ALT \"\\xEF\\x8B\\xAD\"\n#define ICON_FA_PUZZLE_PIECE \"\\xEF\\x84\\xAE\"\n#define ICON_FA_SORT_ALPHA_UP_ALT \"\\xEF\\xA2\\x82\"\n#define ICON_FA_MENORAH \"\\xEF\\x99\\xB6\"\n#define ICON_FA_CLOUD_SUN \"\\xEF\\x9B\\x84\"\n#define ICON_FA_USER_EDIT \"\\xEF\\x93\\xBF\"\n#define ICON_FA_THEATER_MASKS \"\\xEF\\x98\\xB0\"\n#define ICON_FA_FILE_MEDICAL_ALT \"\\xEF\\x91\\xB8\"\n#define ICON_FA_BOXES \"\\xEF\\x91\\xA8\"\n#define ICON_FA_THERMOMETER_EMPTY \"\\xEF\\x8B\\x8B\"\n#define ICON_FA_EXCLAMATION_TRIANGLE \"\\xEF\\x81\\xB1\"\n#define ICON_FA_GIFT \"\\xEF\\x81\\xAB\"\n#define ICON_FA_COGS \"\\xEF\\x82\\x85\"\n#define ICON_FA_SIGNAL \"\\xEF\\x80\\x92\"\n#define ICON_FA_SHAPES \"\\xEF\\x98\\x9F\"\n#define ICON_FA_CLOUD_RAIN \"\\xEF\\x9C\\xBD\"\n#define ICON_FA_LESS_THAN_EQUAL \"\\xEF\\x94\\xB7\"\n#define ICON_FA_CHEVRON_CIRCLE_LEFT \"\\xEF\\x84\\xB7\"\n#define ICON_FA_MORTAR_PESTLE \"\\xEF\\x96\\xA7\"\n#define ICON_FA_DUMBBELL \"\\xEF\\x91\\x8B\"\n#define ICON_FA_SITEMAP \"\\xEF\\x83\\xA8\"\n#define ICON_FA_BUS_ALT \"\\xEF\\x95\\x9E\"\n#define ICON_FA_FILE_CODE \"\\xEF\\x87\\x89\"\n#define ICON_FA_BATTERY_FULL \"\\xEF\\x89\\x80\"\n#define ICON_FA_CROWN \"\\xEF\\x94\\xA1\"\n#define ICON_FA_EXCHANGE_ALT \"\\xEF\\x8D\\xA2\"\n#define ICON_FA_TRANSGENDER_ALT \"\\xEF\\x88\\xA5\"\n#define ICON_FA_STAR_OF_DAVID \"\\xEF\\x9A\\x9A\"\n#define ICON_FA_CASH_REGISTER \"\\xEF\\x9E\\x88\"\n#define ICON_FA_TOOLS \"\\xEF\\x9F\\x99\"\n#define ICON_FA_EXCLAMATION_CIRCLE \"\\xEF\\x81\\xAA\"\n#define ICON_FA_COMMENTS \"\\xEF\\x82\\x86\"\n#define ICON_FA_BRIEFCASE_MEDICAL \"\\xEF\\x91\\xA9\"\n#define ICON_FA_COMMENTS_DOLLAR \"\\xEF\\x99\\x93\"\n#define ICON_FA_BACKSPACE \"\\xEF\\x95\\x9A\"\n#define ICON_FA_SLASH \"\\xEF\\x9C\\x95\"\n#define ICON_FA_HOT_TUB \"\\xEF\\x96\\x93\"\n#define ICON_FA_SUITCASE_ROLLING \"\\xEF\\x97\\x81\"\n#define ICON_FA_BOLD \"\\xEF\\x80\\xB2\"\n#define ICON_FA_HANDS_HELPING \"\\xEF\\x93\\x84\"\n#define ICON_FA_SLEIGH \"\\xEF\\x9F\\x8C\"\n#define ICON_FA_BOLT \"\\xEF\\x83\\xA7\"\n#define ICON_FA_THERMOMETER_QUARTER \"\\xEF\\x8B\\x8A\"\n#define ICON_FA_TROPHY \"\\xEF\\x82\\x91\"\n#define ICON_FA_USER_ALT \"\\xEF\\x90\\x86\"\n#define ICON_FA_BRAILLE \"\\xEF\\x8A\\xA1\"\n#define ICON_FA_PLUS \"\\xEF\\x81\\xA7\"\n#define ICON_FA_LIST_UL \"\\xEF\\x83\\x8A\"\n#define ICON_FA_SMOKING_BAN \"\\xEF\\x95\\x8D\"\n#define ICON_FA_BOOK \"\\xEF\\x80\\xAD\"\n#define ICON_FA_VOLUME_DOWN \"\\xEF\\x80\\xA7\"\n#define ICON_FA_QUESTION_CIRCLE \"\\xEF\\x81\\x99\"\n#define ICON_FA_CARROT \"\\xEF\\x9E\\x87\"\n#define ICON_FA_BATH \"\\xEF\\x8B\\x8D\"\n#define ICON_FA_GAVEL \"\\xEF\\x83\\xA3\"\n#define ICON_FA_CANDY_CANE \"\\xEF\\x9E\\x86\"\n#define ICON_FA_NETWORK_WIRED \"\\xEF\\x9B\\xBF\"\n#define ICON_FA_CARET_SQUARE_LEFT \"\\xEF\\x86\\x91\"\n#define ICON_FA_PLANE_ARRIVAL \"\\xEF\\x96\\xAF\"\n#define ICON_FA_SHARE_SQUARE \"\\xEF\\x85\\x8D\"\n#define ICON_FA_MEDAL \"\\xEF\\x96\\xA2\"\n#define ICON_FA_THERMOMETER_HALF \"\\xEF\\x8B\\x89\"\n#define ICON_FA_QUESTION \"\\xEF\\x84\\xA8\"\n#define ICON_FA_CAR_BATTERY \"\\xEF\\x97\\x9F\"\n#define ICON_FA_DOOR_CLOSED \"\\xEF\\x94\\xAA\"\n#define ICON_FA_USER_MINUS \"\\xEF\\x94\\x83\"\n#define ICON_FA_MUSIC \"\\xEF\\x80\\x81\"\n#define ICON_FA_HOUSE_DAMAGE \"\\xEF\\x9B\\xB1\"\n#define ICON_FA_CHEVRON_RIGHT \"\\xEF\\x81\\x94\"\n#define ICON_FA_GRIP_HORIZONTAL \"\\xEF\\x96\\x8D\"\n#define ICON_FA_DICE_FOUR \"\\xEF\\x94\\xA4\"\n#define ICON_FA_DEAF \"\\xEF\\x8A\\xA4\"\n#define ICON_FA_MEH_BLANK \"\\xEF\\x96\\xA4\"\n#define ICON_FA_WINDOW_CLOSE \"\\xEF\\x90\\x90\"\n#define ICON_FA_LINK \"\\xEF\\x83\\x81\"\n#define ICON_FA_ATOM \"\\xEF\\x97\\x92\"\n#define ICON_FA_LESS_THAN \"\\xEF\\x94\\xB6\"\n#define ICON_FA_OTTER \"\\xEF\\x9C\\x80\"\n#define ICON_FA_DICE_TWO \"\\xEF\\x94\\xA8\"\n#define ICON_FA_SORT_ALPHA_DOWN_ALT \"\\xEF\\xA2\\x81\"\n#define ICON_FA_EJECT \"\\xEF\\x81\\x92\"\n#define ICON_FA_SKULL \"\\xEF\\x95\\x8C\"\n#define ICON_FA_GRIP_LINES \"\\xEF\\x9E\\xA4\"\n#define ICON_FA_SORT_AMOUNT_DOWN_ALT \"\\xEF\\xA2\\x84\"\n#define ICON_FA_HOSPITAL_SYMBOL \"\\xEF\\x91\\xBE\"\n#define ICON_FA_X_RAY \"\\xEF\\x92\\x97\"\n#define ICON_FA_ARROW_UP \"\\xEF\\x81\\xA2\"\n#define ICON_FA_MONEY_BILL_WAVE \"\\xEF\\x94\\xBA\"\n#define ICON_FA_DOT_CIRCLE \"\\xEF\\x86\\x92\"\n#define ICON_FA_IMAGES \"\\xEF\\x8C\\x82\"\n#define ICON_FA_STAR_HALF \"\\xEF\\x82\\x89\"\n#define ICON_FA_SPLOTCH \"\\xEF\\x96\\xBC\"\n#define ICON_FA_STAR_HALF_ALT \"\\xEF\\x97\\x80\"\n#define ICON_FA_SHIP \"\\xEF\\x88\\x9A\"\n#define ICON_FA_BOOK_DEAD \"\\xEF\\x9A\\xB7\"\n#define ICON_FA_CHECK \"\\xEF\\x80\\x8C\"\n#define ICON_FA_RAINBOW \"\\xEF\\x9D\\x9B\"\n#define ICON_FA_POWER_OFF \"\\xEF\\x80\\x91\"\n#define ICON_FA_LEMON \"\\xEF\\x82\\x94\"\n#define ICON_FA_GLOBE_AMERICAS \"\\xEF\\x95\\xBD\"\n#define ICON_FA_PEACE \"\\xEF\\x99\\xBC\"\n#define ICON_FA_THERMOMETER_THREE_QUARTERS \"\\xEF\\x8B\\x88\"\n#define ICON_FA_WAREHOUSE \"\\xEF\\x92\\x94\"\n#define ICON_FA_TRANSGENDER \"\\xEF\\x88\\xA4\"\n#define ICON_FA_PLUS_SQUARE \"\\xEF\\x83\\xBE\"\n#define ICON_FA_BULLSEYE \"\\xEF\\x85\\x80\"\n#define ICON_FA_COOKIE_BITE \"\\xEF\\x95\\xA4\"\n#define ICON_FA_USERS \"\\xEF\\x83\\x80\"\n#define ICON_FA_DRUMSTICK_BITE \"\\xEF\\x9B\\x97\"\n#define ICON_FA_ASTERISK \"\\xEF\\x81\\xA9\"\n#define ICON_FA_PLUS_CIRCLE \"\\xEF\\x81\\x95\"\n#define ICON_FA_CART_ARROW_DOWN \"\\xEF\\x88\\x98\"\n#define ICON_FA_LEAF \"\\xEF\\x81\\xAC\"\n#define ICON_FA_FLUSHED \"\\xEF\\x95\\xB9\"\n#define ICON_FA_STORE_ALT \"\\xEF\\x95\\x8F\"\n#define ICON_FA_PEOPLE_CARRY \"\\xEF\\x93\\x8E\"\n#define ICON_FA_CHESS_BOARD \"\\xEF\\x90\\xBC\"\n#define ICON_FA_LONG_ARROW_ALT_DOWN \"\\xEF\\x8C\\x89\"\n#define ICON_FA_SAD_CRY \"\\xEF\\x96\\xB3\"\n#define ICON_FA_DIGITAL_TACHOGRAPH \"\\xEF\\x95\\xA6\"\n#define ICON_FA_ANGLE_DOUBLE_DOWN \"\\xEF\\x84\\x83\"\n#define ICON_FA_FILE_EXCEL \"\\xEF\\x87\\x83\"\n#define ICON_FA_TEETH \"\\xEF\\x98\\xAE\"\n#define ICON_FA_HAND_SCISSORS \"\\xEF\\x89\\x97\"\n#define ICON_FA_STETHOSCOPE \"\\xEF\\x83\\xB1\"\n#define ICON_FA_BACKWARD \"\\xEF\\x81\\x8A\"\n#define ICON_FA_SCROLL \"\\xEF\\x9C\\x8E\"\n#define ICON_FA_IGLOO \"\\xEF\\x9E\\xAE\"\n#define ICON_FA_NOTES_MEDICAL \"\\xEF\\x92\\x81\"\n#define ICON_FA_CODE \"\\xEF\\x84\\xA1\"\n#define ICON_FA_SORT_NUMERIC_UP_ALT \"\\xEF\\xA2\\x87\"\n#define ICON_FA_NOT_EQUAL \"\\xEF\\x94\\xBE\"\n#define ICON_FA_SKIING \"\\xEF\\x9F\\x89\"\n#define ICON_FA_CHAIR \"\\xEF\\x9B\\x80\"\n#define ICON_FA_HAND_LIZARD \"\\xEF\\x89\\x98\"\n#define ICON_FA_QUIDDITCH \"\\xEF\\x91\\x98\"\n#define ICON_FA_ANGLE_DOUBLE_LEFT \"\\xEF\\x84\\x80\"\n#define ICON_FA_MOSQUE \"\\xEF\\x99\\xB8\"\n#define ICON_FA_PEN \"\\xEF\\x8C\\x84\"\n#define ICON_FA_HRYVNIA \"\\xEF\\x9B\\xB2\"\n#define ICON_FA_ANGLE_LEFT \"\\xEF\\x84\\x84\"\n#define ICON_FA_ATLAS \"\\xEF\\x95\\x98\"\n#define ICON_FA_PIGGY_BANK \"\\xEF\\x93\\x93\"\n#define ICON_FA_DOLLY_FLATBED \"\\xEF\\x91\\xB4\"\n#define ICON_FA_ARROWS_ALT_H \"\\xEF\\x8C\\xB7\"\n#define ICON_FA_PEN_ALT \"\\xEF\\x8C\\x85\"\n#define ICON_FA_PRAYING_HANDS \"\\xEF\\x9A\\x84\"\n#define ICON_FA_VOLUME_UP \"\\xEF\\x80\\xA8\"\n#define ICON_FA_CLIPBOARD_LIST \"\\xEF\\x91\\xAD\"\n#define ICON_FA_BORDER_ALL \"\\xEF\\xA1\\x8C\"\n#define ICON_FA_MAGIC \"\\xEF\\x83\\x90\"\n#define ICON_FA_FOLDER_MINUS \"\\xEF\\x99\\x9D\"\n#define ICON_FA_DEMOCRAT \"\\xEF\\x9D\\x87\"\n#define ICON_FA_MAGNET \"\\xEF\\x81\\xB6\"\n#define ICON_FA_VIHARA \"\\xEF\\x9A\\xA7\"\n#define ICON_FA_GRIMACE \"\\xEF\\x95\\xBF\"\n#define ICON_FA_CHECK_CIRCLE \"\\xEF\\x81\\x98\"\n#define ICON_FA_SEARCH_DOLLAR \"\\xEF\\x9A\\x88\"\n#define ICON_FA_LONG_ARROW_ALT_LEFT \"\\xEF\\x8C\\x8A\"\n#define ICON_FA_FILE_PRESCRIPTION \"\\xEF\\x95\\xB2\"\n#define ICON_FA_CROW \"\\xEF\\x94\\xA0\"\n#define ICON_FA_EYE_DROPPER \"\\xEF\\x87\\xBB\"\n#define ICON_FA_CROP \"\\xEF\\x84\\xA5\"\n#define ICON_FA_SIGN \"\\xEF\\x93\\x99\"\n#define ICON_FA_ARROW_CIRCLE_DOWN \"\\xEF\\x82\\xAB\"\n#define ICON_FA_VIDEO \"\\xEF\\x80\\xBD\"\n#define ICON_FA_DOWNLOAD \"\\xEF\\x80\\x99\"\n#define ICON_FA_CARET_DOWN \"\\xEF\\x83\\x97\"\n#define ICON_FA_CHEVRON_LEFT \"\\xEF\\x81\\x93\"\n#define ICON_FA_GLOBE_AFRICA \"\\xEF\\x95\\xBC\"\n#define ICON_FA_HAMSA \"\\xEF\\x99\\xA5\"\n#define ICON_FA_CART_PLUS \"\\xEF\\x88\\x97\"\n#define ICON_FA_CLIPBOARD \"\\xEF\\x8C\\xA8\"\n#define ICON_FA_SHOE_PRINTS \"\\xEF\\x95\\x8B\"\n#define ICON_FA_PHONE_SLASH \"\\xEF\\x8F\\x9D\"\n#define ICON_FA_REPLY \"\\xEF\\x8F\\xA5\"\n#define ICON_FA_HOURGLASS_HALF \"\\xEF\\x89\\x92\"\n#define ICON_FA_LONG_ARROW_ALT_UP \"\\xEF\\x8C\\x8C\"\n#define ICON_FA_CHESS_KNIGHT \"\\xEF\\x91\\x81\"\n#define ICON_FA_BARCODE \"\\xEF\\x80\\xAA\"\n#define ICON_FA_DRAW_POLYGON \"\\xEF\\x97\\xAE\"\n#define ICON_FA_WATER \"\\xEF\\x9D\\xB3\"\n#define ICON_FA_WINE_GLASS_ALT \"\\xEF\\x97\\x8E\"\n#define ICON_FA_PHONE_VOLUME \"\\xEF\\x8A\\xA0\"\n#define ICON_FA_GLASS_WHISKEY \"\\xEF\\x9E\\xA0\"\n#define ICON_FA_BOX \"\\xEF\\x91\\xA6\"\n#define ICON_FA_DIAGNOSES \"\\xEF\\x91\\xB0\"\n#define ICON_FA_FILE_IMAGE \"\\xEF\\x87\\x85\"\n#define ICON_FA_VENUS_MARS \"\\xEF\\x88\\xA8\"\n#define ICON_FA_TASKS \"\\xEF\\x82\\xAE\"\n#define ICON_FA_HIKING \"\\xEF\\x9B\\xAC\"\n#define ICON_FA_VECTOR_SQUARE \"\\xEF\\x97\\x8B\"\n#define ICON_FA_QUOTE_LEFT \"\\xEF\\x84\\x8D\"\n#define ICON_FA_MOBILE_ALT \"\\xEF\\x8F\\x8D\"\n#define ICON_FA_USER_SHIELD \"\\xEF\\x94\\x85\"\n#define ICON_FA_BLOG \"\\xEF\\x9E\\x81\"\n#define ICON_FA_MARKER \"\\xEF\\x96\\xA1\"\n#define ICON_FA_HAMBURGER \"\\xEF\\xA0\\x85\"\n#define ICON_FA_REDO \"\\xEF\\x80\\x9E\"\n#define ICON_FA_CLOUD \"\\xEF\\x83\\x82\"\n#define ICON_FA_HAND_HOLDING_USD \"\\xEF\\x93\\x80\"\n#define ICON_FA_CERTIFICATE \"\\xEF\\x82\\xA3\"\n#define ICON_FA_ANGRY \"\\xEF\\x95\\x96\"\n#define ICON_FA_FROG \"\\xEF\\x94\\xAE\"\n#define ICON_FA_CAMERA \"\\xEF\\x80\\xB0\"\n#define ICON_FA_DICE_THREE \"\\xEF\\x94\\xA7\"\n#define ICON_FA_MEMORY \"\\xEF\\x94\\xB8\"\n#define ICON_FA_PEN_SQUARE \"\\xEF\\x85\\x8B\"\n#define ICON_FA_SORT \"\\xEF\\x83\\x9C\"\n#define ICON_FA_PLUG \"\\xEF\\x87\\xA6\"\n#define ICON_FA_SHARE \"\\xEF\\x81\\xA4\"\n#define ICON_FA_ENVELOPE \"\\xEF\\x83\\xA0\"\n#define ICON_FA_LAYER_GROUP \"\\xEF\\x97\\xBD\"\n#define ICON_FA_TRAIN \"\\xEF\\x88\\xB8\"\n#define ICON_FA_BULLHORN \"\\xEF\\x82\\xA1\"\n#define ICON_FA_BABY \"\\xEF\\x9D\\xBC\"\n#define ICON_FA_CONCIERGE_BELL \"\\xEF\\x95\\xA2\"\n#define ICON_FA_CIRCLE \"\\xEF\\x84\\x91\"\n#define ICON_FA_I_CURSOR \"\\xEF\\x89\\x86\"\n#define ICON_FA_CAR \"\\xEF\\x86\\xB9\"\n#define ICON_FA_CAT \"\\xEF\\x9A\\xBE\"\n#define ICON_FA_WALLET \"\\xEF\\x95\\x95\"\n#define ICON_FA_BOOK_MEDICAL \"\\xEF\\x9F\\xA6\"\n#define ICON_FA_H_SQUARE \"\\xEF\\x83\\xBD\"\n#define ICON_FA_HEART \"\\xEF\\x80\\x84\"\n#define ICON_FA_LOCK_OPEN \"\\xEF\\x8F\\x81\"\n#define ICON_FA_STREAM \"\\xEF\\x95\\x90\"\n#define ICON_FA_LOCK \"\\xEF\\x80\\xA3\"\n#define ICON_FA_PARACHUTE_BOX \"\\xEF\\x93\\x8D\"\n#define ICON_FA_TAG \"\\xEF\\x80\\xAB\"\n#define ICON_FA_SMILE_BEAM \"\\xEF\\x96\\xB8\"\n#define ICON_FA_USER_NURSE \"\\xEF\\xA0\\xAF\"\n#define ICON_FA_MICROPHONE_ALT \"\\xEF\\x8F\\x89\"\n#define ICON_FA_SPA \"\\xEF\\x96\\xBB\"\n#define ICON_FA_CHEVRON_CIRCLE_DOWN \"\\xEF\\x84\\xBA\"\n#define ICON_FA_FOLDER_PLUS \"\\xEF\\x99\\x9E\"\n#define ICON_FA_TICKET_ALT \"\\xEF\\x8F\\xBF\"\n#define ICON_FA_BOOK_OPEN \"\\xEF\\x94\\x98\"\n#define ICON_FA_MAP \"\\xEF\\x89\\xB9\"\n#define ICON_FA_COCKTAIL \"\\xEF\\x95\\xA1\"\n#define ICON_FA_CLONE \"\\xEF\\x89\\x8D\"\n#define ICON_FA_ID_CARD_ALT \"\\xEF\\x91\\xBF\"\n#define ICON_FA_CHECK_SQUARE \"\\xEF\\x85\\x8A\"\n#define ICON_FA_CHART_LINE \"\\xEF\\x88\\x81\"\n#define ICON_FA_POO_STORM \"\\xEF\\x9D\\x9A\"\n#define ICON_FA_DOVE \"\\xEF\\x92\\xBA\"\n#define ICON_FA_MARS_STROKE \"\\xEF\\x88\\xA9\"\n#define ICON_FA_ENVELOPE_OPEN \"\\xEF\\x8A\\xB6\"\n#define ICON_FA_WHEELCHAIR \"\\xEF\\x86\\x93\"\n#define ICON_FA_ROBOT \"\\xEF\\x95\\x84\"\n#define ICON_FA_UNDO_ALT \"\\xEF\\x8B\\xAA\"\n#define ICON_FA_CLOUD_MEATBALL \"\\xEF\\x9C\\xBB\"\n#define ICON_FA_TRUCK \"\\xEF\\x83\\x91\"\n#define ICON_FA_FLASK \"\\xEF\\x83\\x83\"\n#define ICON_FA_WON_SIGN \"\\xEF\\x85\\x99\"\n#define ICON_FA_SUPERSCRIPT \"\\xEF\\x84\\xAB\"\n#define ICON_FA_TTY \"\\xEF\\x87\\xA4\"\n#define ICON_FA_USER_MD \"\\xEF\\x83\\xB0\"\n#define ICON_FA_BRAIN \"\\xEF\\x97\\x9C\"\n#define ICON_FA_TABLETS \"\\xEF\\x92\\x90\"\n#define ICON_FA_MOTORCYCLE \"\\xEF\\x88\\x9C\"\n#define ICON_FA_PHONE_SQUARE_ALT \"\\xEF\\xA1\\xBB\"\n#define ICON_FA_ANGLE_UP \"\\xEF\\x84\\x86\"\n#define ICON_FA_BROOM \"\\xEF\\x94\\x9A\"\n#define ICON_FA_DICE_D20 \"\\xEF\\x9B\\x8F\"\n#define ICON_FA_LEVEL_DOWN_ALT \"\\xEF\\x8E\\xBE\"\n#define ICON_FA_PAPERCLIP \"\\xEF\\x83\\x86\"\n#define ICON_FA_USER_CLOCK \"\\xEF\\x93\\xBD\"\n#define ICON_FA_MUG_HOT \"\\xEF\\x9E\\xB6\"\n#define ICON_FA_SORT_ALPHA_UP \"\\xEF\\x85\\x9E\"\n#define ICON_FA_AUDIO_DESCRIPTION \"\\xEF\\x8A\\x9E\"\n#define ICON_FA_FILE_CSV \"\\xEF\\x9B\\x9D\"\n#define ICON_FA_FILE_DOWNLOAD \"\\xEF\\x95\\xAD\"\n#define ICON_FA_SYNC_ALT \"\\xEF\\x8B\\xB1\"\n#define ICON_FA_ANGLE_DOUBLE_UP \"\\xEF\\x84\\x82\"\n#define ICON_FA_HANDS \"\\xEF\\x93\\x82\"\n#define ICON_FA_REPUBLICAN \"\\xEF\\x9D\\x9E\"\n#define ICON_FA_UNIVERSITY \"\\xEF\\x86\\x9C\"\n#define ICON_FA_KHANDA \"\\xEF\\x99\\xAD\"\n#define ICON_FA_GLASSES \"\\xEF\\x94\\xB0\"\n#define ICON_FA_SQUARE \"\\xEF\\x83\\x88\"\n#define ICON_FA_GRIN_SQUINT \"\\xEF\\x96\\x85\"\n#define ICON_FA_CLOSED_CAPTIONING \"\\xEF\\x88\\x8A\"\n#define ICON_FA_RECEIPT \"\\xEF\\x95\\x83\"\n#define ICON_FA_STRIKETHROUGH \"\\xEF\\x83\\x8C\"\n#define ICON_FA_UNLOCK \"\\xEF\\x82\\x9C\"\n#define ICON_FA_ARROW_LEFT \"\\xEF\\x81\\xA0\"\n#define ICON_FA_DICE_SIX \"\\xEF\\x94\\xA6\"\n#define ICON_FA_GRIP_VERTICAL \"\\xEF\\x96\\x8E\"\n#define ICON_FA_PILLS \"\\xEF\\x92\\x84\"\n#define ICON_FA_EXCLAMATION \"\\xEF\\x84\\xAA\"\n#define ICON_FA_PERSON_BOOTH \"\\xEF\\x9D\\x96\"\n#define ICON_FA_CALENDAR_PLUS \"\\xEF\\x89\\xB1\"\n#define ICON_FA_SMOG \"\\xEF\\x9D\\x9F\"\n#define ICON_FA_LOCATION_ARROW \"\\xEF\\x84\\xA4\"\n#define ICON_FA_UMBRELLA \"\\xEF\\x83\\xA9\"\n#define ICON_FA_QURAN \"\\xEF\\x9A\\x87\"\n#define ICON_FA_UNDO \"\\xEF\\x83\\xA2\"\n#define ICON_FA_DUMPSTER \"\\xEF\\x9E\\x93\"\n#define ICON_FA_FUNNEL_DOLLAR \"\\xEF\\x99\\xA2\"\n#define ICON_FA_INDENT \"\\xEF\\x80\\xBC\"\n#define ICON_FA_LANGUAGE \"\\xEF\\x86\\xAB\"\n#define ICON_FA_ARROW_ALT_CIRCLE_UP \"\\xEF\\x8D\\x9B\"\n#define ICON_FA_ROUTE \"\\xEF\\x93\\x97\"\n#define ICON_FA_HEADPHONES \"\\xEF\\x80\\xA5\"\n#define ICON_FA_TIMES \"\\xEF\\x80\\x8D\"\n#define ICON_FA_CLINIC_MEDICAL \"\\xEF\\x9F\\xB2\"\n#define ICON_FA_PLANE \"\\xEF\\x81\\xB2\"\n#define ICON_FA_TORII_GATE \"\\xEF\\x9A\\xA1\"\n#define ICON_FA_LEVEL_UP_ALT \"\\xEF\\x8E\\xBF\"\n#define ICON_FA_BLIND \"\\xEF\\x8A\\x9D\"\n#define ICON_FA_CHEESE \"\\xEF\\x9F\\xAF\"\n#define ICON_FA_PHONE_SQUARE \"\\xEF\\x82\\x98\"\n#define ICON_FA_SHOPPING_BASKET \"\\xEF\\x8A\\x91\"\n#define ICON_FA_ICE_CREAM \"\\xEF\\xA0\\x90\"\n#define ICON_FA_RING \"\\xEF\\x9C\\x8B\"\n#define ICON_FA_CITY \"\\xEF\\x99\\x8F\"\n#define ICON_FA_TEXT_WIDTH \"\\xEF\\x80\\xB5\"\n#define ICON_FA_RSS_SQUARE \"\\xEF\\x85\\x83\"\n#define ICON_FA_PAINT_BRUSH \"\\xEF\\x87\\xBC\"\n#define ICON_FA_BOOKMARK \"\\xEF\\x80\\xAE\"\n#define ICON_FA_PHOTO_VIDEO \"\\xEF\\xA1\\xBC\"\n#define ICON_FA_SIM_CARD \"\\xEF\\x9F\\x84\"\n#define ICON_FA_CLOUD_UPLOAD_ALT \"\\xEF\\x8E\\x82\"\n#define ICON_FA_COMPACT_DISC \"\\xEF\\x94\\x9F\"\n#define ICON_FA_SORT_UP \"\\xEF\\x83\\x9E\"\n#define ICON_FA_SIGN_OUT_ALT \"\\xEF\\x8B\\xB5\"\n#define ICON_FA_SIGN_IN_ALT \"\\xEF\\x8B\\xB6\"\n#define ICON_FA_FORWARD \"\\xEF\\x81\\x8E\"\n#define ICON_FA_SHARE_ALT \"\\xEF\\x87\\xA0\"\n#define ICON_FA_COPY \"\\xEF\\x83\\x85\"\n#define ICON_FA_RSS \"\\xEF\\x82\\x9E\"\n#define ICON_FA_PEN_FANCY \"\\xEF\\x96\\xAC\"\n#define ICON_FA_BIOHAZARD \"\\xEF\\x9E\\x80\"\n#define ICON_FA_BED \"\\xEF\\x88\\xB6\"\n#define ICON_FA_INFO \"\\xEF\\x84\\xA9\"\n#define ICON_FA_TOGGLE_OFF \"\\xEF\\x88\\x84\"\n#define ICON_FA_MAP_MARKER_ALT \"\\xEF\\x8F\\x85\"\n#define ICON_FA_TRACTOR \"\\xEF\\x9C\\xA2\"\n#define ICON_FA_CLOUD_DOWNLOAD_ALT \"\\xEF\\x8E\\x81\"\n#define ICON_FA_ID_BADGE \"\\xEF\\x8B\\x81\"\n#define ICON_FA_SORT_NUMERIC_DOWN_ALT \"\\xEF\\xA2\\x86\"\n#define ICON_FA_RULER_HORIZONTAL \"\\xEF\\x95\\x87\"\n#define ICON_FA_PAINT_ROLLER \"\\xEF\\x96\\xAA\"\n#define ICON_FA_HAT_WIZARD \"\\xEF\\x9B\\xA8\"\n#define ICON_FA_MAP_SIGNS \"\\xEF\\x89\\xB7\"\n#define ICON_FA_MICROPHONE \"\\xEF\\x84\\xB0\"\n#define ICON_FA_FOOTBALL_BALL \"\\xEF\\x91\\x8E\"\n#define ICON_FA_ALLERGIES \"\\xEF\\x91\\xA1\"\n#define ICON_FA_ID_CARD \"\\xEF\\x8B\\x82\"\n#define ICON_FA_USER_LOCK \"\\xEF\\x94\\x82\"\n#define ICON_FA_PLAY_CIRCLE \"\\xEF\\x85\\x84\"\n#define ICON_FA_REMOVE_FORMAT \"\\xEF\\xA1\\xBD\"\n#define ICON_FA_THERMOMETER \"\\xEF\\x92\\x91\"\n#define ICON_FA_REGISTERED \"\\xEF\\x89\\x9D\"\n#define ICON_FA_DOLLAR_SIGN \"\\xEF\\x85\\x95\"\n#define ICON_FA_DUNGEON \"\\xEF\\x9B\\x99\"\n#define ICON_FA_COMPRESS \"\\xEF\\x81\\xA6\"\n#define ICON_FA_SEARCH_LOCATION \"\\xEF\\x9A\\x89\"\n#define ICON_FA_UTENSILS \"\\xEF\\x8B\\xA7\"\n#define ICON_FA_BLENDER_PHONE \"\\xEF\\x9A\\xB6\"\n#define ICON_FA_ANGLE_RIGHT \"\\xEF\\x84\\x85\"\n#define ICON_FA_CHESS_QUEEN \"\\xEF\\x91\\x85\"\n#define ICON_FA_PAGER \"\\xEF\\xA0\\x95\"\n#define ICON_FA_SORT_AMOUNT_UP_ALT \"\\xEF\\xA2\\x85\"\n#define ICON_FA_CLIPBOARD_CHECK \"\\xEF\\x91\\xAC\"\n#define ICON_FA_HOURGLASS_END \"\\xEF\\x89\\x93\"\n#define ICON_FA_TOOTH \"\\xEF\\x97\\x89\"\n#define ICON_FA_BUSINESS_TIME \"\\xEF\\x99\\x8A\"\n#define ICON_FA_PLACE_OF_WORSHIP \"\\xEF\\x99\\xBF\"\n#define ICON_FA_GRIN_TONGUE_SQUINT \"\\xEF\\x96\\x8A\"\n#define ICON_FA_MEH_ROLLING_EYES \"\\xEF\\x96\\xA5\"\n#define ICON_FA_WALKING \"\\xEF\\x95\\x94\"\n#define ICON_FA_EDIT \"\\xEF\\x81\\x84\"\n#define ICON_FA_CARET_LEFT \"\\xEF\\x83\\x99\"\n#define ICON_FA_PAUSE \"\\xEF\\x81\\x8C\"\n#define ICON_FA_DICE \"\\xEF\\x94\\xA2\"\n#define ICON_FA_RUBLE_SIGN \"\\xEF\\x85\\x98\"\n#define ICON_FA_TERMINAL \"\\xEF\\x84\\xA0\"\n#define ICON_FA_RULER_VERTICAL \"\\xEF\\x95\\x88\"\n#define ICON_FA_HAND_POINTER \"\\xEF\\x89\\x9A\"\n#define ICON_FA_TAPE \"\\xEF\\x93\\x9B\"\n#define ICON_FA_SHOPPING_BAG \"\\xEF\\x8A\\x90\"\n#define ICON_FA_SKIING_NORDIC \"\\xEF\\x9F\\x8A\"\n#define ICON_FA_FIST_RAISED \"\\xEF\\x9B\\x9E\"\n#define ICON_FA_CUBE \"\\xEF\\x86\\xB2\"\n#define ICON_FA_CAPSULES \"\\xEF\\x91\\xAB\"\n#define ICON_FA_KIWI_BIRD \"\\xEF\\x94\\xB5\"\n#define ICON_FA_CHEVRON_CIRCLE_UP \"\\xEF\\x84\\xB9\"\n#define ICON_FA_MARS_STROKE_V \"\\xEF\\x88\\xAA\"\n#define ICON_FA_FILE_ARCHIVE \"\\xEF\\x87\\x86\"\n#define ICON_FA_JOINT \"\\xEF\\x96\\x95\"\n#define ICON_FA_MARS_STROKE_H \"\\xEF\\x88\\xAB\"\n#define ICON_FA_ADDRESS_BOOK \"\\xEF\\x8A\\xB9\"\n#define ICON_FA_PROCEDURES \"\\xEF\\x92\\x87\"\n#define ICON_FA_GEM \"\\xEF\\x8E\\xA5\"\n#define ICON_FA_RULER_COMBINED \"\\xEF\\x95\\x86\"\n#define ICON_FA_ALIGN_LEFT \"\\xEF\\x80\\xB6\"\n#define ICON_FA_STAR_AND_CRESCENT \"\\xEF\\x9A\\x99\"\n#define ICON_FA_FIGHTER_JET \"\\xEF\\x83\\xBB\"\n#define ICON_FA_SPACE_SHUTTLE \"\\xEF\\x86\\x97\"\n#define ICON_FA_MAP_PIN \"\\xEF\\x89\\xB6\"\n#define ICON_FA_GLOBE \"\\xEF\\x82\\xAC\"\n#define ICON_FA_ALIGN_CENTER \"\\xEF\\x80\\xB7\"\n#define ICON_FA_SORT_ALPHA_DOWN \"\\xEF\\x85\\x9D\"\n#define ICON_FA_PARKING \"\\xEF\\x95\\x80\"\n#define ICON_FA_CALENDAR \"\\xEF\\x84\\xB3\"\n#define ICON_FA_PALETTE \"\\xEF\\x94\\xBF\"\n#define ICON_FA_GLASS_MARTINI \"\\xEF\\x80\\x80\"\n#define ICON_FA_TIMES_CIRCLE \"\\xEF\\x81\\x97\"\n#define ICON_FA_EYE \"\\xEF\\x81\\xAE\"\n#define ICON_FA_MONUMENT \"\\xEF\\x96\\xA6\"\n#define ICON_FA_TRASH_RESTORE \"\\xEF\\xA0\\xA9\"\n#define ICON_FA_GUITAR \"\\xEF\\x9E\\xA6\"\n#define ICON_FA_GRIN_BEAM \"\\xEF\\x96\\x82\"\n#define ICON_FA_KEY \"\\xEF\\x82\\x84\"\n#define ICON_FA_FIRST_AID \"\\xEF\\x91\\xB9\"\n#define ICON_FA_UMBRELLA_BEACH \"\\xEF\\x97\\x8A\"\n#define ICON_FA_DRUM \"\\xEF\\x95\\xA9\"\n#define ICON_FA_FILE_CONTRACT \"\\xEF\\x95\\xAC\"\n#define ICON_FA_VOICEMAIL \"\\xEF\\xA2\\x97\"\n#define ICON_FA_RESTROOM \"\\xEF\\x9E\\xBD\"\n#define ICON_FA_UNLOCK_ALT \"\\xEF\\x84\\xBE\"\n#define ICON_FA_MICROPHONE_ALT_SLASH \"\\xEF\\x94\\xB9\"\n#define ICON_FA_USER_SECRET \"\\xEF\\x88\\x9B\"\n#define ICON_FA_ARROW_RIGHT \"\\xEF\\x81\\xA1\"\n#define ICON_FA_FILE_VIDEO \"\\xEF\\x87\\x88\"\n#define ICON_FA_ARROW_ALT_CIRCLE_RIGHT \"\\xEF\\x8D\\x9A\"\n#define ICON_FA_CALENDAR_WEEK \"\\xEF\\x9E\\x84\"\n#define ICON_FA_USER_GRADUATE \"\\xEF\\x94\\x81\"\n#define ICON_FA_HAND_MIDDLE_FINGER \"\\xEF\\xA0\\x86\"\n#define ICON_FA_POO \"\\xEF\\x8B\\xBE\"\n#define ICON_FA_LAUGH \"\\xEF\\x96\\x99\"\n#define ICON_FA_TABLE \"\\xEF\\x83\\x8E\"\n#define ICON_FA_POLL \"\\xEF\\x9A\\x81\"\n#define ICON_FA_CAR_ALT \"\\xEF\\x97\\x9E\"\n#define ICON_FA_THUMBS_UP \"\\xEF\\x85\\xA4\"\n#define ICON_FA_SWIMMER \"\\xEF\\x97\\x84\"\n#define ICON_FA_TRADEMARK \"\\xEF\\x89\\x9C\"\n#define ICON_FA_CLOUD_MOON_RAIN \"\\xEF\\x9C\\xBC\"\n#define ICON_FA_VIALS \"\\xEF\\x92\\x93\"\n#define ICON_FA_ERASER \"\\xEF\\x84\\xAD\"\n#define ICON_FA_MARS \"\\xEF\\x88\\xA2\"\n#define ICON_FA_HELICOPTER \"\\xEF\\x94\\xB3\"\n#define ICON_FA_FEATHER \"\\xEF\\x94\\xAD\"\n#define ICON_FA_SQUARE_FULL \"\\xEF\\x91\\x9C\"\n#define ICON_FA_DOLLY \"\\xEF\\x91\\xB2\"\n#define ICON_FA_HAND_HOLDING \"\\xEF\\x92\\xBD\"\n#define ICON_FA_HOURGLASS_START \"\\xEF\\x89\\x91\"\n#define ICON_FA_GRIN_HEARTS \"\\xEF\\x96\\x84\"\n#define ICON_FA_VENUS_DOUBLE \"\\xEF\\x88\\xA6\"\n#define ICON_FA_HASHTAG \"\\xEF\\x8A\\x92\"\n#define ICON_FA_FINGERPRINT \"\\xEF\\x95\\xB7\"\n#define ICON_FA_SEEDLING \"\\xEF\\x93\\x98\"\n#define ICON_FA_HAYKAL \"\\xEF\\x99\\xA6\"\n#define ICON_FA_TSHIRT \"\\xEF\\x95\\x93\"\n#define ICON_FA_PENCIL_RULER \"\\xEF\\x96\\xAE\"\n#define ICON_FA_HDD \"\\xEF\\x82\\xA0\"\n#define ICON_FA_NEWSPAPER \"\\xEF\\x87\\xAA\"\n#define ICON_FA_HOSPITAL_ALT \"\\xEF\\x91\\xBD\"\n#define ICON_FA_USER_SLASH \"\\xEF\\x94\\x86\"\n#define ICON_FA_FILE_WORD \"\\xEF\\x87\\x82\"\n#define ICON_FA_ENVELOPE_SQUARE \"\\xEF\\x86\\x99\"\n#define ICON_FA_GENDERLESS \"\\xEF\\x88\\xAD\"\n#define ICON_FA_DICE_FIVE \"\\xEF\\x94\\xA3\"\n#define ICON_FA_SYNAGOGUE \"\\xEF\\x9A\\x9B\"\n#define ICON_FA_PAW \"\\xEF\\x86\\xB0\"\n#define ICON_FA_HAND_HOLDING_HEART \"\\xEF\\x92\\xBE\"\n#define ICON_FA_CROSS \"\\xEF\\x99\\x94\"\n#define ICON_FA_ARCHIVE \"\\xEF\\x86\\x87\"\n#define ICON_FA_SOLAR_PANEL \"\\xEF\\x96\\xBA\"\n#define ICON_FA_INFINITY \"\\xEF\\x94\\xB4\"\n#define ICON_FA_ANKH \"\\xEF\\x99\\x84\"\n#define ICON_FA_MAP_MARKER \"\\xEF\\x81\\x81\"\n#define ICON_FA_CALENDAR_ALT \"\\xEF\\x81\\xB3\"\n#define ICON_FA_AMERICAN_SIGN_LANGUAGE_INTERPRETING \"\\xEF\\x8A\\xA3\"\n#define ICON_FA_BINOCULARS \"\\xEF\\x87\\xA5\"\n#define ICON_FA_STICKY_NOTE \"\\xEF\\x89\\x89\"\n#define ICON_FA_RUNNING \"\\xEF\\x9C\\x8C\"\n#define ICON_FA_PEN_NIB \"\\xEF\\x96\\xAD\"\n#define ICON_FA_MAP_MARKED \"\\xEF\\x96\\x9F\"\n#define ICON_FA_EXPAND \"\\xEF\\x81\\xA5\"\n#define ICON_FA_TRUCK_PICKUP \"\\xEF\\x98\\xBC\"\n#define ICON_FA_HOLLY_BERRY \"\\xEF\\x9E\\xAA\"\n#define ICON_FA_PRESCRIPTION_BOTTLE \"\\xEF\\x92\\x85\"\n#define ICON_FA_LAPTOP_CODE \"\\xEF\\x97\\xBC\"\n#define ICON_FA_GOLF_BALL \"\\xEF\\x91\\x90\"\n#define ICON_FA_SKULL_CROSSBONES \"\\xEF\\x9C\\x94\"\n#define ICON_FA_TAXI \"\\xEF\\x86\\xBA\"\n#define ICON_FA_COMMENT \"\\xEF\\x81\\xB5\"\n#define ICON_FA_KISS \"\\xEF\\x96\\x96\"\n#define ICON_FA_HIPPO \"\\xEF\\x9B\\xAD\"\n#define ICON_FA_ARROWS_ALT \"\\xEF\\x82\\xB2\"\n#define ICON_FA_UNDERLINE \"\\xEF\\x83\\x8D\"\n#define ICON_FA_ARROW_CIRCLE_UP \"\\xEF\\x82\\xAA\"\n#define ICON_FA_BASKETBALL_BALL \"\\xEF\\x90\\xB4\"\n#define ICON_FA_DESKTOP \"\\xEF\\x84\\x88\"\n#define ICON_FA_PALLET \"\\xEF\\x92\\x82\"\n#define ICON_FA_TOGGLE_ON \"\\xEF\\x88\\x85\"\n#define ICON_FA_STOPWATCH \"\\xEF\\x8B\\xB2\"\n#define ICON_FA_ARROW_ALT_CIRCLE_LEFT \"\\xEF\\x8D\\x99\"\n#define ICON_FA_GAS_PUMP \"\\xEF\\x94\\xAF\"\n#define ICON_FA_EXTERNAL_LINK_ALT \"\\xEF\\x8D\\x9D\"\n#define ICON_FA_FROWN \"\\xEF\\x84\\x99\"\n#define ICON_FA_RULER \"\\xEF\\x95\\x85\"\n#define ICON_FA_FLAG_USA \"\\xEF\\x9D\\x8D\"\n#define ICON_FA_GRIN \"\\xEF\\x96\\x80\"\n#define ICON_FA_ARROW_CIRCLE_LEFT \"\\xEF\\x82\\xA8\"\n#define ICON_FA_HIGHLIGHTER \"\\xEF\\x96\\x91\"\n#define ICON_FA_POLL_H \"\\xEF\\x9A\\x82\"\n#define ICON_FA_SERVER \"\\xEF\\x88\\xB3\"\n#define ICON_FA_BATTERY_EMPTY \"\\xEF\\x89\\x84\"\n#define ICON_FA_SPRAY_CAN \"\\xEF\\x96\\xBD\"\n#define ICON_FA_BOWLING_BALL \"\\xEF\\x90\\xB6\"\n#define ICON_FA_GRIP_LINES_VERTICAL \"\\xEF\\x9E\\xA5\"\n#define ICON_FA_GLOBE_EUROPE \"\\xEF\\x9E\\xA2\"\n#define ICON_FA_WINDOW_MINIMIZE \"\\xEF\\x8B\\x91\"\n#define ICON_FA_MARS_DOUBLE \"\\xEF\\x88\\xA7\"\n#define ICON_FA_PAUSE_CIRCLE \"\\xEF\\x8A\\x8B\"\n#define ICON_FA_HOME \"\\xEF\\x80\\x95\"\n#define ICON_FA_COMMENT_ALT \"\\xEF\\x89\\xBA\"\n#define ICON_FA_UTENSIL_SPOON \"\\xEF\\x8B\\xA5\"\n#define ICON_FA_APPLE_ALT \"\\xEF\\x97\\x91\"\n#define ICON_FA_MOON \"\\xEF\\x86\\x86\"\n#define ICON_FA_CANNABIS \"\\xEF\\x95\\x9F\"\n#define ICON_FA_LAUGH_BEAM \"\\xEF\\x96\\x9A\"\n#define ICON_FA_TEETH_OPEN \"\\xEF\\x98\\xAF\"\n#define ICON_FA_CHART_PIE \"\\xEF\\x88\\x80\"\n#define ICON_FA_SOCKS \"\\xEF\\x9A\\x96\"\n#define ICON_FA_SD_CARD \"\\xEF\\x9F\\x82\"\n#define ICON_FA_ARROW_CIRCLE_RIGHT \"\\xEF\\x82\\xA9\"\n#define ICON_FA_PASTE \"\\xEF\\x83\\xAA\"\n#define ICON_FA_OM \"\\xEF\\x99\\xB9\"\n#define ICON_FA_LUGGAGE_CART \"\\xEF\\x96\\x9D\"\n#define ICON_FA_INDUSTRY \"\\xEF\\x89\\xB5\"\n#define ICON_FA_STAMP \"\\xEF\\x96\\xBF\"\n#define ICON_FA_RADIATION_ALT \"\\xEF\\x9E\\xBA\"\n#define ICON_FA_COMPRESS_ARROWS_ALT \"\\xEF\\x9E\\x8C\"\n#define ICON_FA_ROAD \"\\xEF\\x80\\x98\"\n#define ICON_FA_IMAGE \"\\xEF\\x80\\xBE\"\n#define ICON_FA_BALANCE_SCALE_RIGHT \"\\xEF\\x94\\x96\"\n#define ICON_FA_ANGLE_DOUBLE_RIGHT \"\\xEF\\x84\\x81\"\n#define ICON_FA_CLOUD_MOON \"\\xEF\\x9B\\x83\"\n#define ICON_FA_DOOR_OPEN \"\\xEF\\x94\\xAB\"\n#define ICON_FA_GRIN_TONGUE_WINK \"\\xEF\\x96\\x8B\"\n#define ICON_FA_REPLY_ALL \"\\xEF\\x84\\xA2\"\n#define ICON_FA_TEMPERATURE_LOW \"\\xEF\\x9D\\xAB\"\n#define ICON_FA_INBOX \"\\xEF\\x80\\x9C\"\n#define ICON_FA_FEMALE \"\\xEF\\x86\\x82\"\n#define ICON_FA_SYRINGE \"\\xEF\\x92\\x8E\"\n#define ICON_FA_CIRCLE_NOTCH \"\\xEF\\x87\\x8E\"\n#define ICON_FA_ELLIPSIS_V \"\\xEF\\x85\\x82\"\n#define ICON_FA_SNOWPLOW \"\\xEF\\x9F\\x92\"\n#define ICON_FA_TABLE_TENNIS \"\\xEF\\x91\\x9D\"\n#define ICON_FA_LOW_VISION \"\\xEF\\x8A\\xA8\"\n#define ICON_FA_FILE_IMPORT \"\\xEF\\x95\\xAF\"\n#define ICON_FA_ITALIC \"\\xEF\\x80\\xB3\"\n#define ICON_FA_FILE_SIGNATURE \"\\xEF\\x95\\xB3\"\n#define ICON_FA_CHALKBOARD \"\\xEF\\x94\\x9B\"\n#define ICON_FA_GHOST \"\\xEF\\x9B\\xA2\"\n#define ICON_FA_TACHOMETER_ALT \"\\xEF\\x8F\\xBD\"\n#define ICON_FA_BUS \"\\xEF\\x88\\x87\"\n#define ICON_FA_ANGLE_DOWN \"\\xEF\\x84\\x87\"\n#define ICON_FA_HAND_ROCK \"\\xEF\\x89\\x95\"\n#define ICON_FA_BORDER_STYLE \"\\xEF\\xA1\\x93\"\n#define ICON_FA_STAR_OF_LIFE \"\\xEF\\x98\\xA1\"\n#define ICON_FA_PODCAST \"\\xEF\\x8B\\x8E\"\n#define ICON_FA_TRUCK_MOVING \"\\xEF\\x93\\x9F\"\n#define ICON_FA_BUG \"\\xEF\\x86\\x88\"\n#define ICON_FA_SHIELD_ALT \"\\xEF\\x8F\\xAD\"\n#define ICON_FA_FILL_DRIP \"\\xEF\\x95\\xB6\"\n#define ICON_FA_COMMENT_SLASH \"\\xEF\\x92\\xB3\"\n#define ICON_FA_SUITCASE \"\\xEF\\x83\\xB2\"\n#define ICON_FA_SKATING \"\\xEF\\x9F\\x85\"\n#define ICON_FA_TOILET \"\\xEF\\x9F\\x98\"\n#define ICON_FA_ENVELOPE_OPEN_TEXT \"\\xEF\\x99\\x98\"\n#define ICON_FA_HEART_BROKEN \"\\xEF\\x9E\\xA9\"\n#define ICON_FA_CARET_SQUARE_UP \"\\xEF\\x85\\x91\"\n#define ICON_FA_TH_LARGE \"\\xEF\\x80\\x89\"\n#define ICON_FA_AT \"\\xEF\\x87\\xBA\"\n#define ICON_FA_FILE \"\\xEF\\x85\\x9B\"\n#define ICON_FA_TENGE \"\\xEF\\x9F\\x97\"\n#define ICON_FA_FLAG_CHECKERED \"\\xEF\\x84\\x9E\"\n#define ICON_FA_FILM \"\\xEF\\x80\\x88\"\n#define ICON_FA_FILL \"\\xEF\\x95\\xB5\"\n#define ICON_FA_GRIN_SQUINT_TEARS \"\\xEF\\x96\\x86\"\n#define ICON_FA_PERCENT \"\\xEF\\x8A\\x95\"\n#define ICON_FA_METEOR \"\\xEF\\x9D\\x93\"\n#define ICON_FA_TRASH \"\\xEF\\x87\\xB8\"\n#define ICON_FA_FILE_AUDIO \"\\xEF\\x87\\x87\"\n#define ICON_FA_SATELLITE_DISH \"\\xEF\\x9F\\x80\"\n#define ICON_FA_POOP \"\\xEF\\x98\\x99\"\n#define ICON_FA_STAR \"\\xEF\\x80\\x85\"\n#define ICON_FA_GIFTS \"\\xEF\\x9E\\x9C\"\n#define ICON_FA_FIRE_ALT \"\\xEF\\x9F\\xA4\"\n#define ICON_FA_BUILDING \"\\xEF\\x86\\xAD\"\n#define ICON_FA_PRESCRIPTION_BOTTLE_ALT \"\\xEF\\x92\\x86\"\n#define ICON_FA_MONEY_BILL_WAVE_ALT \"\\xEF\\x94\\xBB\"\n#define ICON_FA_NEUTER \"\\xEF\\x88\\xAC\"\n#define ICON_FA_BAND_AID \"\\xEF\\x91\\xA2\"\n#define ICON_FA_WIFI \"\\xEF\\x87\\xAB\"\n#define ICON_FA_MASK \"\\xEF\\x9B\\xBA\"\n#define ICON_FA_CHEVRON_UP \"\\xEF\\x81\\xB7\"\n#define ICON_FA_HAND_SPOCK \"\\xEF\\x89\\x99\"\n#define ICON_FA_HAND_POINT_UP \"\\xEF\\x82\\xA6\""
  },
  {
    "path": "examples/pfd/COPYING",
    "content": "            DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE\n                    Version 2, December 2004\n\n Copyright (C) 2004 Sam Hocevar\n\n Everyone is permitted to copy and distribute verbatim or modified\n copies of this license document, and changing it is allowed as long\n as the name is changed.\n\n            DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE\n   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION\n\n  0. You just DO WHAT THE FUCK YOU WANT TO.\n\n"
  },
  {
    "path": "examples/pfd/pfd.h",
    "content": "//\n//  Portable File Dialogs\n//\n//  Copyright © 2018—2020 Sam Hocevar <sam@hocevar.net>\n//\n//  This library is free software. It comes without any warranty, to\n//  the extent permitted by applicable law. You can redistribute it\n//  and/or modify it under the terms of the Do What the Fuck You Want\n//  to Public License, Version 2, as published by the WTFPL Task Force.\n//  See http://www.wtfpl.net/ for more details.\n//\n\n#pragma once\n\n#if _WIN32\n#ifndef WIN32_LEAN_AND_MEAN\n#   define WIN32_LEAN_AND_MEAN 1\n#endif\n#include <Windows.h>\n#include <commdlg.h>\n#include <ShlObj.h>\n#include <ShObjIdl.h> // IFileDialog\n#include <shellapi.h>\n#include <strsafe.h>\n#include <future>     // std::async\n\n#elif __EMSCRIPTEN__\n#include <emscripten.h>\n\n#else\n#ifndef _POSIX_C_SOURCE\n#   define _POSIX_C_SOURCE 2 // for popen()\n#endif\n#include <cstdio>     // popen()\n#include <cstdlib>    // std::getenv()\n#include <fcntl.h>    // fcntl()\n#include <unistd.h>   // read(), pipe(), dup2()\n#include <csignal>    // ::kill, std::signal\n#include <sys/wait.h> // waitpid()\n#endif\n\n#include <string>   // std::string\n#include <memory>   // std::shared_ptr\n#include <iostream> // std::ostream\n#include <map>      // std::map\n#include <set>      // std::set\n#include <regex>    // std::regex\n#include <thread>   // std::mutex, std::this_thread\n#include <chrono>   // std::chrono\n\nnamespace pfd\n{\n\nenum class button\n{\n    cancel = -1,\n    ok,\n    yes,\n    no,\n    abort,\n    retry,\n    ignore,\n};\n\nenum class choice\n{\n    ok = 0,\n    ok_cancel,\n    yes_no,\n    yes_no_cancel,\n    retry_cancel,\n    abort_retry_ignore,\n};\n\nenum class icon\n{\n    info = 0,\n    warning,\n    error,\n    question,\n};\n\n// Additional option flags for various dialog constructors\nenum class opt : uint8_t\n{\n    none = 0,\n    // For file open, allow multiselect.\n    multiselect     = 0x1,\n    // For file save, force overwrite and disable the confirmation dialog.\n    force_overwrite = 0x2,\n    // For folder select, force path to be the provided argument instead\n    // of the last opened directory, which is the Microsoft-recommended,\n    // user-friendly behaviour.\n    force_path      = 0x4,\n};\n\ninline opt operator |(opt a, opt b) { return opt(uint8_t(a) | uint8_t(b)); }\ninline bool operator &(opt a, opt b) { return bool(uint8_t(a) & uint8_t(b)); }\n\n// The settings class, only exposing to the user a way to set verbose mode\n// and to force a rescan of installed desktop helpers (zenity, kdialog…).\nclass settings\n{\npublic:\n    static bool available();\n\n    static void verbose(bool value);\n    static void rescan();\n\nprotected:\n    explicit settings(bool resync = false);\n\n    bool check_program(std::string const &program);\n\n    inline bool is_osascript() const;\n    inline bool is_zenity() const;\n    inline bool is_kdialog() const;\n\n    enum class flag\n    {\n        is_scanned = 0,\n        is_verbose,\n\n        has_zenity,\n        has_matedialog,\n        has_qarma,\n        has_kdialog,\n        is_vista,\n\n        max_flag,\n    };\n\n    // Static array of flags for internal state\n    bool const &flags(flag in_flag) const;\n\n    // Non-const getter for the static array of flags\n    bool &flags(flag in_flag);\n};\n\n// Internal classes, not to be used by client applications\nnamespace internal\n{\n\n// Process wait timeout, in milliseconds\nstatic int const default_wait_timeout = 20;\n\nclass executor\n{\n    friend class dialog;\n\npublic:\n    // High level function to get the result of a command\n    std::string result(int *exit_code = nullptr);\n\n    // High level function to abort\n    bool kill();\n\n#if _WIN32\n    void start_func(std::function<std::string(int *)> const &fun);\n    static BOOL CALLBACK enum_windows_callback(HWND hwnd, LPARAM lParam);\n#elif __EMSCRIPTEN__\n    void start(int exit_code);\n#else\n    void start_process(std::vector<std::string> const &command);\n#endif\n\n    ~executor();\n\nprotected:\n    bool ready(int timeout = default_wait_timeout);\n    void stop();\n\nprivate:\n    bool m_running = false;\n    std::string m_stdout;\n    int m_exit_code = -1;\n#if _WIN32\n    std::future<std::string> m_future;\n    std::set<HWND> m_windows;\n    std::condition_variable m_cond;\n    std::mutex m_mutex;\n    DWORD m_tid;\n#elif __EMSCRIPTEN__ || __NX__\n    // FIXME: do something\n#else\n    pid_t m_pid = 0;\n    int m_fd = -1;\n#endif\n};\n\nclass platform\n{\nprotected:\n#if _WIN32\n    // Helper class around LoadLibraryA() and GetProcAddress() with some safety\n    class dll\n    {\n    public:\n        dll(std::string const &name);\n        ~dll();\n\n        template<typename T> class proc\n        {\n        public:\n            proc(dll const &lib, std::string const &sym)\n              : m_proc(reinterpret_cast<T *>(::GetProcAddress(lib.handle, sym.c_str())))\n            {}\n\n            operator bool() const { return m_proc != nullptr; }\n            operator T *() const { return m_proc; }\n\n        private:\n            T *m_proc;\n        };\n\n    private:\n        HMODULE handle;\n    };\n\n    // Helper class around CoInitialize() and CoUnInitialize()\n    class ole32_dll : public dll\n    {\n    public:\n        ole32_dll();\n        ~ole32_dll();\n        bool is_initialized();\n\n    private:\n        HRESULT m_state;\n    };\n\n    // Helper class around CreateActCtx() and ActivateActCtx()\n    class new_style_context\n    {\n    public:\n        new_style_context();\n        ~new_style_context();\n\n    private:\n        HANDLE create();\n        ULONG_PTR m_cookie = 0;\n    };\n#endif\n};\n\nclass dialog : protected settings, protected platform\n{\npublic:\n    bool ready(int timeout = default_wait_timeout) const;\n    bool kill() const;\n\nprotected:\n    explicit dialog();\n\n    std::vector<std::string> desktop_helper() const;\n    static std::string buttons_to_name(choice _choice);\n    static std::string get_icon_name(icon _icon);\n\n    std::string powershell_quote(std::string const &str) const;\n    std::string osascript_quote(std::string const &str) const;\n    std::string shell_quote(std::string const &str) const;\n\n    // Keep handle to executing command\n    std::shared_ptr<executor> m_async;\n};\n\nclass file_dialog : public dialog\n{\nprotected:\n    enum type\n    {\n        open,\n        save,\n        folder,\n    };\n\n    file_dialog(type in_type,\n                std::string const &title,\n                std::string const &default_path = \"\",\n                std::vector<std::string> const &filters = {},\n                opt options = opt::none);\n\nprotected:\n    std::string string_result();\n    std::vector<std::string> vector_result();\n\n#if _WIN32\n    static int CALLBACK bffcallback(HWND hwnd, UINT uMsg, LPARAM, LPARAM pData);\n    std::string select_folder_vista(IFileDialog *ifd, bool force_path);\n\n    std::wstring m_wtitle;\n    std::wstring m_wdefault_path;\n\n    std::vector<std::string> m_vector_result;\n#endif\n};\n\n} // namespace internal\n\n//\n// The notify widget\n//\n\nclass notify : public internal::dialog\n{\npublic:\n    notify(std::string const &title,\n           std::string const &message,\n           icon _icon = icon::info);\n};\n\n//\n// The message widget\n//\n\nclass message : public internal::dialog\n{\npublic:\n    message(std::string const &title,\n            std::string const &text,\n            choice _choice = choice::ok_cancel,\n            icon _icon = icon::info);\n\n    button result();\n\nprivate:\n    // Some extra logic to map the exit code to button number\n    std::map<int, button> m_mappings;\n};\n\n//\n// The open_file, save_file, and open_folder widgets\n//\n\nclass open_file : public internal::file_dialog\n{\npublic:\n    open_file(std::string const &title,\n              std::string const &default_path = \"\",\n              std::vector<std::string> const &filters = { \"All Files\", \"*\" },\n              opt options = opt::none);\n\n#if defined(__has_cpp_attribute)\n#if __has_cpp_attribute(deprecated)\n    // Backwards compatibility\n    //[[deprecated(\"Use pfd::opt::multiselect instead of allow_multiselect\")]]\n#endif\n#endif\n    open_file(std::string const &title,\n              std::string const &default_path,\n              std::vector<std::string> const &filters,\n              bool allow_multiselect);\n\n    std::vector<std::string> result();\n};\n\nclass save_file : public internal::file_dialog\n{\npublic:\n    save_file(std::string const &title,\n              std::string const &default_path = \"\",\n              std::vector<std::string> const &filters = { \"All Files\", \"*\" },\n              opt options = opt::none);\n\n#if defined(__has_cpp_attribute)\n#if __has_cpp_attribute(deprecated)\n    // Backwards compatibility\n    //[[deprecated(\"Use pfd::opt::force_overwrite instead of confirm_overwrite\")]]\n#endif\n#endif\n    save_file(std::string const &title,\n              std::string const &default_path,\n              std::vector<std::string> const &filters,\n              bool confirm_overwrite);\n\n    std::string result();\n};\n\nclass select_folder : public internal::file_dialog\n{\npublic:\n    select_folder(std::string const &title,\n                  std::string const &default_path = \"\",\n                  opt options = opt::none);\n\n    std::string result();\n};\n\n//\n// Below this are all the method implementations. You may choose to define the\n// macro PFD_SKIP_IMPLEMENTATION everywhere before including this header except\n// in one place. This may reduce compilation times.\n//\n\n#if !defined PFD_SKIP_IMPLEMENTATION\n\n// internal free functions implementations\n\nnamespace internal\n{\n\n#if _WIN32\nstatic inline std::wstring str2wstr(std::string const &str)\n{\n    int len = MultiByteToWideChar(CP_UTF8, 0, str.c_str(), (int)str.size(), nullptr, 0);\n    std::wstring ret(len, '\\0');\n    MultiByteToWideChar(CP_UTF8, 0, str.c_str(), (int)str.size(), (LPWSTR)ret.data(), (int)ret.size());\n    return ret;\n}\n\nstatic inline std::string wstr2str(std::wstring const &str)\n{\n    int len = WideCharToMultiByte(CP_UTF8, 0, str.c_str(), (int)str.size(), nullptr, 0, nullptr, nullptr);\n    std::string ret(len, '\\0');\n    WideCharToMultiByte(CP_UTF8, 0, str.c_str(), (int)str.size(), (LPSTR)ret.data(), (int)ret.size(), nullptr, nullptr);\n    return ret;\n}\n\nstatic inline bool is_vista()\n{\n    OSVERSIONINFOEXW osvi;\n    memset(&osvi, 0, sizeof(osvi));\n    DWORDLONG const mask = VerSetConditionMask(\n            VerSetConditionMask(\n                    VerSetConditionMask(\n                            0, VER_MAJORVERSION, VER_GREATER_EQUAL),\n                    VER_MINORVERSION, VER_GREATER_EQUAL),\n            VER_SERVICEPACKMAJOR, VER_GREATER_EQUAL);\n    osvi.dwOSVersionInfoSize = sizeof(osvi);\n    osvi.dwMajorVersion = HIBYTE(_WIN32_WINNT_VISTA);\n    osvi.dwMinorVersion = LOBYTE(_WIN32_WINNT_VISTA);\n    osvi.wServicePackMajor = 0;\n\n    return VerifyVersionInfoW(&osvi, VER_MAJORVERSION | VER_MINORVERSION | VER_SERVICEPACKMAJOR, mask) != FALSE;\n}\n#endif\n\n// This is necessary until C++20 which will have std::string::ends_with() etc.\n\nstatic inline bool ends_with(std::string const &str, std::string const &suffix)\n{\n    return suffix.size() <= str.size() &&\n        str.compare(str.size() - suffix.size(), suffix.size(), suffix) == 0;\n}\n\nstatic inline bool starts_with(std::string const &str, std::string const &prefix)\n{\n    return prefix.size() <= str.size() &&\n        str.compare(0, prefix.size(), prefix) == 0;\n}\n\n} // namespace internal\n\n// settings implementation\n\ninline settings::settings(bool resync)\n{\n    flags(flag::is_scanned) &= !resync;\n\n    if (flags(flag::is_scanned))\n        return;\n\n#if _WIN32\n    flags(flag::is_vista) = internal::is_vista();\n#elif !__APPLE__\n    flags(flag::has_zenity) = check_program(\"zenity\");\n    flags(flag::has_matedialog) = check_program(\"matedialog\");\n    flags(flag::has_qarma) = check_program(\"qarma\");\n    flags(flag::has_kdialog) = check_program(\"kdialog\");\n\n    // If multiple helpers are available, try to default to the best one\n    if (flags(flag::has_zenity) && flags(flag::has_kdialog))\n    {\n        auto desktop_name = std::getenv(\"XDG_SESSION_DESKTOP\");\n        if (desktop_name && desktop_name == std::string(\"gnome\"))\n            flags(flag::has_kdialog) = false;\n        else if (desktop_name && desktop_name == std::string(\"KDE\"))\n            flags(flag::has_zenity) = false;\n    }\n#endif\n\n    flags(flag::is_scanned) = true;\n}\n\ninline bool settings::available()\n{\n#if _WIN32\n    return true;\n#elif __APPLE__\n    return true;\n#else\n    settings tmp;\n    return tmp.flags(flag::has_zenity) ||\n           tmp.flags(flag::has_matedialog) ||\n           tmp.flags(flag::has_qarma) ||\n           tmp.flags(flag::has_kdialog);\n#endif\n}\n\ninline void settings::verbose(bool value)\n{\n    settings().flags(flag::is_verbose) = value;\n}\n\ninline void settings::rescan()\n{\n    settings(/* resync = */ true);\n}\n\n// Check whether a program is present using “which”.\ninline bool settings::check_program(std::string const &program)\n{\n#if _WIN32\n    (void)program;\n    return false;\n#elif __EMSCRIPTEN__\n    (void)program;\n    return false;\n#else\n    int exit_code = -1;\n    internal::executor async;\n    async.start_process({\"/bin/sh\", \"-c\", \"which \" + program});\n    async.result(&exit_code);\n    return exit_code == 0;\n#endif\n}\n\ninline bool settings::is_osascript() const\n{\n#if __APPLE__\n    return true;\n#else\n    return false;\n#endif\n}\n\ninline bool settings::is_zenity() const\n{\n    return flags(flag::has_zenity) ||\n           flags(flag::has_matedialog) ||\n           flags(flag::has_qarma);\n}\n\ninline bool settings::is_kdialog() const\n{\n    return flags(flag::has_kdialog);\n}\n\ninline bool const &settings::flags(flag in_flag) const\n{\n    static bool flags[size_t(flag::max_flag)];\n    return flags[size_t(in_flag)];\n}\n\ninline bool &settings::flags(flag in_flag)\n{\n    return const_cast<bool &>(static_cast<settings const *>(this)->flags(in_flag));\n}\n\n// executor implementation\n\ninline std::string internal::executor::result(int *exit_code /* = nullptr */)\n{\n    stop();\n    if (exit_code)\n        *exit_code = m_exit_code;\n    return m_stdout;\n}\n\ninline bool internal::executor::kill()\n{\n#if _WIN32\n    if (m_future.valid())\n    {\n        // Close all windows that weren’t open when we started the future\n        auto previous_windows = m_windows;\n        EnumWindows(&enum_windows_callback, (LPARAM)this);\n        for (auto hwnd : m_windows)\n            if (previous_windows.find(hwnd) == previous_windows.end())\n                SendMessage(hwnd, WM_CLOSE, 0, 0);\n    }\n#elif __EMSCRIPTEN__ || __NX__\n    // FIXME: do something\n    (void)timeout;\n    return false; // cannot kill\n#else\n    ::kill(m_pid, SIGKILL);\n#endif\n    stop();\n    return true;\n}\n\n#if _WIN32\ninline BOOL CALLBACK internal::executor::enum_windows_callback(HWND hwnd, LPARAM lParam)\n{\n    auto that = (executor *)lParam;\n\n    DWORD pid;\n    auto tid = GetWindowThreadProcessId(hwnd, &pid);\n    if (tid == that->m_tid)\n        that->m_windows.insert(hwnd);\n    return TRUE;\n}\n#endif\n\n#if _WIN32\ninline void internal::executor::start_func(std::function<std::string(int *)> const &fun)\n{\n    stop();\n\n    auto trampoline = [fun, this]()\n    {\n        // Save our thread id so that the caller can cancel us\n        m_tid = GetCurrentThreadId();\n        EnumWindows(&enum_windows_callback, (LPARAM)this);\n        m_cond.notify_all();\n        return fun(&m_exit_code);\n    };\n\n    std::unique_lock<std::mutex> lock(m_mutex);\n    m_future = std::async(std::launch::async, trampoline);\n    m_cond.wait(lock);\n    m_running = true;\n}\n\n#elif __EMSCRIPTEN__\ninline void internal::executor::start(int exit_code)\n{\n    m_exit_code = exit_code;\n}\n\n#else\ninline void internal::executor::start_process(std::vector<std::string> const &command)\n{\n    stop();\n    m_stdout.clear();\n    m_exit_code = -1;\n\n    int in[2], out[2];\n    if (pipe(in) != 0 || pipe(out) != 0)\n        return;\n\n    m_pid = fork();\n    if (m_pid < 0)\n        return;\n\n    close(in[m_pid ? 0 : 1]);\n    close(out[m_pid ? 1 : 0]);\n\n    if (m_pid == 0)\n    {\n        dup2(in[0], STDIN_FILENO);\n        dup2(out[1], STDOUT_FILENO);\n\n        // Ignore stderr so that it doesn’t pollute the console (e.g. GTK+ errors from zenity)\n        int fd = open(\"/dev/null\", O_WRONLY);\n        dup2(fd, STDERR_FILENO);\n        close(fd);\n\n        std::vector<char *> args;\n        std::transform(command.cbegin(), command.cend(), std::back_inserter(args),\n                       [](std::string const &s) { return const_cast<char *>(s.c_str()); });\n        args.push_back(nullptr); // null-terminate argv[]\n\n        execvp(args[0], args.data());\n        exit(1);\n    }\n\n    close(in[1]);\n    m_fd = out[0];\n    auto flags = fcntl(m_fd, F_GETFL);\n    fcntl(m_fd, F_SETFL, flags | O_NONBLOCK);\n\n    m_running = true;\n}\n#endif\n\ninline internal::executor::~executor()\n{\n    stop();\n}\n\ninline bool internal::executor::ready(int timeout /* = default_wait_timeout */)\n{\n    if (!m_running)\n        return true;\n\n#if _WIN32\n    if (m_future.valid())\n    {\n        auto status = m_future.wait_for(std::chrono::milliseconds(timeout));\n        if (status != std::future_status::ready)\n        {\n            // On Windows, we need to run the message pump. If the async\n            // thread uses a Windows API dialog, it may be attached to the\n            // main thread and waiting for messages that only we can dispatch.\n            MSG msg;\n            while (PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE))\n            {\n                TranslateMessage(&msg);\n                DispatchMessage(&msg);\n            }\n            return false;\n        }\n\n        m_stdout = m_future.get();\n    }\n#elif __EMSCRIPTEN__ || __NX__\n    // FIXME: do something\n    (void)timeout;\n#else\n    char buf[BUFSIZ];\n    ssize_t received = read(m_fd, buf, BUFSIZ); // Flawfinder: ignore\n    if (received > 0)\n    {\n        m_stdout += std::string(buf, received);\n        return false;\n    }\n\n    // Reap child process if it is dead. It is possible that the system has already reaped it\n    // (this happens when the calling application handles or ignores SIG_CHLD) and results in\n    // waitpid() failing with ECHILD. Otherwise we assume the child is running and we sleep for\n    // a little while.\n    int status;\n    pid_t child = waitpid(m_pid, &status, WNOHANG);\n    if (child != m_pid && (child >= 0 || errno != ECHILD))\n    {\n        // FIXME: this happens almost always at first iteration\n        std::this_thread::sleep_for(std::chrono::milliseconds(timeout));\n        return false;\n    }\n\n    close(m_fd);\n    m_exit_code = WEXITSTATUS(status);\n#endif\n\n    m_running = false;\n    return true;\n}\n\ninline void internal::executor::stop()\n{\n    // Loop until the user closes the dialog\n    while (!ready())\n        ;\n}\n\n// dll implementation\n\n#if _WIN32\ninline internal::platform::dll::dll(std::string const &name)\n  : handle(::LoadLibraryA(name.c_str()))\n{}\n\ninline internal::platform::dll::~dll()\n{\n    if (handle)\n        ::FreeLibrary(handle);\n}\n#endif // _WIN32\n\n// ole32_dll implementation\n\n#if _WIN32\ninline internal::platform::ole32_dll::ole32_dll()\n    : dll(\"ole32.dll\")\n{\n    // Use COINIT_MULTITHREADED because COINIT_APARTMENTTHREADED causes crashes.\n    // See https://github.com/samhocevar/portable-file-dialogs/issues/51\n    auto coinit = proc<HRESULT WINAPI (LPVOID, DWORD)>(*this, \"CoInitializeEx\");\n    m_state = coinit(nullptr, COINIT_MULTITHREADED);\n}\n\ninline internal::platform::ole32_dll::~ole32_dll()\n{\n    if (is_initialized())\n        proc<void WINAPI ()>(*this, \"CoUninitialize\")();\n}\n\ninline bool internal::platform::ole32_dll::is_initialized()\n{\n    return m_state == S_OK || m_state == S_FALSE;\n}\n#endif\n\n// new_style_context implementation\n\n#if _WIN32\ninline internal::platform::new_style_context::new_style_context()\n{\n    // Only create one activation context for the whole app lifetime.\n    static HANDLE hctx = create();\n\n    if (hctx != INVALID_HANDLE_VALUE)\n        ActivateActCtx(hctx, &m_cookie);\n}\n\ninline internal::platform::new_style_context::~new_style_context()\n{\n    DeactivateActCtx(0, m_cookie);\n}\n\ninline HANDLE internal::platform::new_style_context::create()\n{\n    // This “hack” seems to be necessary for this code to work on windows XP.\n    // Without it, dialogs do not show and close immediately. GetError()\n    // returns 0 so I don’t know what causes this. I was not able to reproduce\n    // this behavior on Windows 7 and 10 but just in case, let it be here for\n    // those versions too.\n    // This hack is not required if other dialogs are used (they load comdlg32\n    // automatically), only if message boxes are used.\n    dll comdlg32(\"comdlg32.dll\");\n\n    // Using approach as shown here: https://stackoverflow.com/a/10444161\n    UINT len = ::GetSystemDirectoryA(nullptr, 0);\n    std::string sys_dir(len, '\\0');\n    ::GetSystemDirectoryA(&sys_dir[0], len);\n\n    ACTCTXA act_ctx =\n    {\n        // Do not set flag ACTCTX_FLAG_SET_PROCESS_DEFAULT, since it causes a\n        // crash with error “default context is already set”.\n        sizeof(act_ctx),\n        ACTCTX_FLAG_RESOURCE_NAME_VALID | ACTCTX_FLAG_ASSEMBLY_DIRECTORY_VALID,\n        \"shell32.dll\", 0, 0, sys_dir.c_str(), (LPCSTR)124,\n    };\n\n    return ::CreateActCtxA(&act_ctx);\n}\n#endif // _WIN32\n\n// dialog implementation\n\ninline bool internal::dialog::ready(int timeout /* = default_wait_timeout */) const\n{\n    return m_async->ready(timeout);\n}\n\ninline bool internal::dialog::kill() const\n{\n    return m_async->kill();\n}\n\ninline internal::dialog::dialog()\n  : m_async(std::make_shared<executor>())\n{\n}\n\ninline std::vector<std::string> internal::dialog::desktop_helper() const\n{\n#if __APPLE__\n    return { \"osascript\" };\n#else\n    return { flags(flag::has_zenity) ? \"zenity\"\n           : flags(flag::has_matedialog) ? \"matedialog\"\n           : flags(flag::has_qarma) ? \"qarma\"\n           : flags(flag::has_kdialog) ? \"kdialog\"\n           : \"echo\" };\n#endif\n}\n\ninline std::string internal::dialog::buttons_to_name(choice _choice)\n{\n    switch (_choice)\n    {\n        case choice::ok_cancel: return \"okcancel\";\n        case choice::yes_no: return \"yesno\";\n        case choice::yes_no_cancel: return \"yesnocancel\";\n        case choice::retry_cancel: return \"retrycancel\";\n        case choice::abort_retry_ignore: return \"abortretryignore\";\n        /* case choice::ok: */ default: return \"ok\";\n    }\n}\n\ninline std::string internal::dialog::get_icon_name(icon _icon)\n{\n    switch (_icon)\n    {\n        case icon::warning: return \"warning\";\n        case icon::error: return \"error\";\n        case icon::question: return \"question\";\n        // Zenity wants \"information\" but WinForms wants \"info\"\n        /* case icon::info: */ default:\n#if _WIN32\n            return \"info\";\n#else\n            return \"information\";\n#endif\n    }\n}\n\n// THis is only used for debugging purposes\ninline std::ostream& operator <<(std::ostream &s, std::vector<std::string> const &v)\n{\n    int not_first = 0;\n    for (auto &e : v)\n        s << (not_first++ ? \" \" : \"\") << e;\n    return s;\n}\n\n// Properly quote a string for Powershell: replace ' or \" with '' or \"\"\n// FIXME: we should probably get rid of newlines!\n// FIXME: the \\\" sequence seems unsafe, too!\n// XXX: this is no longer used but I would like to keep it around just in case\ninline std::string internal::dialog::powershell_quote(std::string const &str) const\n{\n    return \"'\" + std::regex_replace(str, std::regex(\"['\\\"]\"), \"$&$&\") + \"'\";\n}\n\n// Properly quote a string for osascript: replace \\ or \" with \\\\ or \\\"\n// XXX: this also used to replace ' with \\' when popen was used, but it would be\n// smarter to do shell_quote(osascript_quote(...)) if this is needed again.\ninline std::string internal::dialog::osascript_quote(std::string const &str) const\n{\n    return \"\\\"\" + std::regex_replace(str, std::regex(\"[\\\\\\\\\\\"]\"), \"\\\\$&\") + \"\\\"\";\n}\n\n// Properly quote a string for the shell: just replace ' with '\\''\n// XXX: this is no longer used but I would like to keep it around just in case\ninline std::string internal::dialog::shell_quote(std::string const &str) const\n{\n    return \"'\" + std::regex_replace(str, std::regex(\"'\"), \"'\\\\''\") + \"'\";\n}\n\n// file_dialog implementation\n\ninline internal::file_dialog::file_dialog(type in_type,\n            std::string const &title,\n            std::string const &default_path /* = \"\" */,\n            std::vector<std::string> const &filters /* = {} */,\n            opt options /* = opt::none */)\n{\n#if _WIN32\n    std::string filter_list;\n    std::regex whitespace(\"  *\");\n    for (size_t i = 0; i + 1 < filters.size(); i += 2)\n    {\n        filter_list += filters[i] + '\\0';\n        filter_list += std::regex_replace(filters[i + 1], whitespace, \";\") + '\\0';\n    }\n    filter_list += '\\0';\n\n    m_async->start_func([this, in_type, title, default_path, filter_list,\n                         options](int *exit_code) -> std::string\n    {\n        (void)exit_code;\n        m_wtitle = internal::str2wstr(title);\n        m_wdefault_path = internal::str2wstr(default_path);\n        auto wfilter_list = internal::str2wstr(filter_list);\n\n        // Initialise COM. This is required for the new folder selection window,\n        // (see https://github.com/samhocevar/portable-file-dialogs/pull/21)\n        // and to avoid random crashes with GetOpenFileNameW() (see\n        // https://github.com/samhocevar/portable-file-dialogs/issues/51)\n        ole32_dll ole32;\n\n        // Folder selection uses a different method\n        if (in_type == type::folder)\n        {\n            if (flags(flag::is_vista))\n            {\n                // On Vista and higher we should be able to use IFileDialog for folder selection\n                IFileDialog *ifd;\n                HRESULT hr = dll::proc<HRESULT WINAPI (REFCLSID, LPUNKNOWN, DWORD, REFIID, LPVOID *)>(ole32, \"CoCreateInstance\")\n                                 (CLSID_FileOpenDialog, nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&ifd));\n\n                // In case CoCreateInstance fails (which it should not), try legacy approach\n                if (SUCCEEDED(hr))\n                    return select_folder_vista(ifd, options & opt::force_path);\n            }\n\n            BROWSEINFOW bi;\n            memset(&bi, 0, sizeof(bi));\n\n            bi.lpfn = &bffcallback;\n            bi.lParam = (LPARAM)this;\n\n            if (flags(flag::is_vista))\n            {\n                if (ole32.is_initialized())\n                    bi.ulFlags |= BIF_NEWDIALOGSTYLE;\n                bi.ulFlags |= BIF_EDITBOX;\n                bi.ulFlags |= BIF_STATUSTEXT;\n            }\n\n            auto *list = SHBrowseForFolderW(&bi);\n            std::string ret;\n            if (list)\n            {\n                auto buffer = new wchar_t[MAX_PATH];\n                SHGetPathFromIDListW(list, buffer);\n                dll::proc<void WINAPI (LPVOID)>(ole32, \"CoTaskMemFree\")(list);\n                ret = internal::wstr2str(buffer);\n                delete[] buffer;\n            }\n            return ret;\n        }\n\n        OPENFILENAMEW ofn;\n        memset(&ofn, 0, sizeof(ofn));\n        ofn.lStructSize = sizeof(OPENFILENAMEW);\n        ofn.hwndOwner = GetActiveWindow();\n\n        ofn.lpstrFilter = wfilter_list.c_str();\n\n        auto woutput = std::wstring(MAX_PATH * 256, L'\\0');\n        ofn.lpstrFile = (LPWSTR)woutput.data();\n        ofn.nMaxFile = (DWORD)woutput.size();\n        if (!m_wdefault_path.empty())\n        {\n            // If a directory was provided, use it as the initial directory. If\n            // a valid path was provided, use it as the initial file. Otherwise,\n            // let the Windows API decide.\n            auto path_attr = GetFileAttributesW(m_wdefault_path.c_str());\n            if (path_attr != INVALID_FILE_ATTRIBUTES && (path_attr & FILE_ATTRIBUTE_DIRECTORY))\n                ofn.lpstrInitialDir = m_wdefault_path.c_str();\n            else if (m_wdefault_path.size() <= woutput.size())\n                //second argument is size of buffer, not length of string\n                StringCchCopyW(ofn.lpstrFile, MAX_PATH*256+1, m_wdefault_path.c_str());\n            else\n            {\n                ofn.lpstrFileTitle = (LPWSTR)m_wdefault_path.data();\n                ofn.nMaxFileTitle = (DWORD)m_wdefault_path.size();\n            }\n        }\n        ofn.lpstrTitle = m_wtitle.c_str();\n        ofn.Flags = OFN_NOCHANGEDIR | OFN_EXPLORER;\n\n        dll comdlg32(\"comdlg32.dll\");\n\n        // Apply new visual style (required for windows XP)\n        new_style_context ctx;\n\n        if (in_type == type::save)\n        {\n            if (!(options & opt::force_overwrite))\n                ofn.Flags |= OFN_OVERWRITEPROMPT;\n\n            dll::proc<BOOL WINAPI (LPOPENFILENAMEW)> get_save_file_name(comdlg32, \"GetSaveFileNameW\");\n            if (get_save_file_name(&ofn) == 0)\n                return \"\";\n            return internal::wstr2str(woutput.c_str());\n        }\n        else\n        {\n            if (options & opt::multiselect)\n                ofn.Flags |= OFN_ALLOWMULTISELECT;\n            ofn.Flags |= OFN_PATHMUSTEXIST;\n\n            dll::proc<BOOL WINAPI (LPOPENFILENAMEW)> get_open_file_name(comdlg32, \"GetOpenFileNameW\");\n            if (get_open_file_name(&ofn) == 0)\n                return \"\";\n        }\n\n        std::string prefix;\n        for (wchar_t const *p = woutput.c_str(); *p; )\n        {\n            auto filename = internal::wstr2str(p);\n            p += wcslen(p);\n            // In multiselect mode, we advance p one wchar further and\n            // check for another filename. If there is one and the\n            // prefix is empty, it means we just read the prefix.\n            if ((options & opt::multiselect) && *++p && prefix.empty())\n            {\n                prefix = filename + \"/\";\n                continue;\n            }\n\n            m_vector_result.push_back(prefix + filename);\n        }\n\n        return \"\";\n    });\n#else\n    auto command = desktop_helper();\n\n    if (is_osascript())\n    {\n        std::string script = \"set ret to choose\";\n        switch (in_type)\n        {\n            case type::save:\n                script += \" file name\";\n                break;\n            case type::open: default:\n                script += \" file\";\n                if (options & opt::multiselect)\n                    script += \" with multiple selections allowed\";\n                break;\n            case type::folder:\n                script += \" folder\";\n                break;\n        }\n\n        if (default_path.size())\n            script += \" default location \" + osascript_quote(default_path);\n        script += \" with prompt \" + osascript_quote(title);\n\n        if (in_type == type::open)\n        {\n            // Concatenate all user-provided filter patterns\n            std::string patterns;\n            for (size_t i = 0; i < filters.size() / 2; ++i)\n                patterns += \" \" + filters[2 * i + 1];\n\n            // Split the pattern list to check whether \"*\" is in there; if it\n            // is, we have to disable filters because there is no mechanism in\n            // OS X for the user to override the filter.\n            std::regex sep(\"\\\\s+\");\n            std::string filter_list;\n            bool has_filter = true;\n            std::sregex_token_iterator iter(patterns.begin(), patterns.end(), sep, -1);\n            std::sregex_token_iterator end;\n            for ( ; iter != end; ++iter)\n            {\n                auto pat = iter->str();\n                if (pat == \"*\" || pat == \"*.*\")\n                    has_filter = false;\n                else if (internal::starts_with(pat, \"*.\"))\n                    filter_list += (filter_list.size() == 0 ? \"\" : \",\") +\n                                   osascript_quote(pat.substr(2, pat.size() - 2));\n            }\n            if (has_filter && filter_list.size() > 0)\n                script += \" of type {\" + filter_list + \"}\";\n        }\n\n        if (in_type == type::open && (options & opt::multiselect))\n        {\n            script += \"\\nset s to \\\"\\\"\";\n            script += \"\\nrepeat with i in ret\";\n            script += \"\\n  set s to s & (POSIX path of i) & \\\"\\\\n\\\"\";\n            script += \"\\nend repeat\";\n            script += \"\\ncopy s to stdout\";\n        }\n        else\n        {\n            script += \"\\nPOSIX path of ret\";\n        }\n\n        command.push_back(\"-e\");\n        command.push_back(script);\n    }\n    else if (is_zenity())\n    {\n        command.push_back(\"--file-selection\");\n        command.push_back(\"--filename=\" + default_path);\n        command.push_back(\"--title\");\n        command.push_back(title);\n        command.push_back(\"--separator=\\n\");\n\n        for (size_t i = 0; i < filters.size() / 2; ++i)\n        {\n            command.push_back(\"--file-filter\");\n            command.push_back(filters[2 * i] + \"|\" + filters[2 * i + 1]);\n        }\n\n        if (in_type == type::save)\n            command.push_back(\"--save\");\n        if (in_type == type::folder)\n            command.push_back(\"--directory\");\n        if (!(options & opt::force_overwrite))\n            command.push_back(\"--confirm-overwrite\");\n        if (options & opt::multiselect)\n            command.push_back(\"--multiple\");\n    }\n    else if (is_kdialog())\n    {\n        switch (in_type)\n        {\n            case type::save: command.push_back(\"--getsavefilename\"); break;\n            case type::open: command.push_back(\"--getopenfilename\"); break;\n            case type::folder: command.push_back(\"--getexistingdirectory\"); break;\n        }\n        if (options & opt::multiselect)\n            command.push_back(\" --multiple\");\n\n        command.push_back(default_path);\n\n        std::string filter;\n        for (size_t i = 0; i < filters.size() / 2; ++i)\n            filter += (i == 0 ? \"\" : \" | \") + filters[2 * i] + \"(\" + filters[2 * i + 1] + \")\";\n        command.push_back(filter);\n\n        command.push_back(\"--title\");\n        command.push_back(title);\n    }\n\n    if (flags(flag::is_verbose))\n        std::cerr << \"pfd: \" << command << std::endl;\n\n    m_async->start_process(command);\n#endif\n}\n\ninline std::string internal::file_dialog::string_result()\n{\n#if _WIN32\n    return m_async->result();\n#else\n    auto ret = m_async->result();\n    // Strip potential trailing newline (zenity). Also strip trailing slash\n    // added by osascript for consistency with other backends.\n    while (ret.back() == '\\n' || ret.back() == '/')\n        ret = ret.substr(0, ret.size() - 1);\n    return ret;\n#endif\n}\n\ninline std::vector<std::string> internal::file_dialog::vector_result()\n{\n#if _WIN32\n    m_async->result();\n    return m_vector_result;\n#else\n    std::vector<std::string> ret;\n    auto result = m_async->result();\n    for (;;)\n    {\n        // Split result along newline characters\n        auto i = result.find('\\n');\n        if (i == 0 || i == std::string::npos)\n            break;\n        ret.push_back(result.substr(0, i));\n        result = result.substr(i + 1, result.size());\n    }\n    return ret;\n#endif\n}\n\n#if _WIN32\n// Use a static function to pass as BFFCALLBACK for legacy folder select\ninline int CALLBACK internal::file_dialog::bffcallback(HWND hwnd, UINT uMsg,\n                                                       LPARAM, LPARAM pData)\n{\n    auto inst = (file_dialog *)pData;\n    switch (uMsg)\n    {\n        case BFFM_INITIALIZED:\n            SendMessage(hwnd, BFFM_SETSELECTIONW, TRUE, (LPARAM)inst->m_wdefault_path.c_str());\n            break;\n    }\n    return 0;\n}\n\ninline std::string internal::file_dialog::select_folder_vista(IFileDialog *ifd, bool force_path)\n{\n    std::string result;\n\n    IShellItem *folder;\n\n    // Load library at runtime so app doesn't link it at load time (which will fail on windows XP)\n    dll shell32(\"shell32.dll\");\n    dll::proc<HRESULT WINAPI (PCWSTR, IBindCtx*, REFIID, void**)>\n        create_item(shell32, \"SHCreateItemFromParsingName\");\n\n    if (!create_item)\n        return \"\";\n\n    auto hr = create_item(m_wdefault_path.c_str(),\n                          nullptr,\n                          IID_PPV_ARGS(&folder));\n\n    // Set default folder if found. This only sets the default folder. If\n    // Windows has any info about the most recently selected folder, it\n    // will display it instead. Generally, calling SetFolder() to set the\n    // current directory “is not a good or expected user experience and\n    // should therefore be avoided”:\n    // https://docs.microsoft.com/windows/win32/api/shobjidl_core/nf-shobjidl_core-ifiledialog-setfolder\n    if (SUCCEEDED(hr))\n    {\n        if (force_path)\n            ifd->SetFolder(folder);\n        else\n            ifd->SetDefaultFolder(folder);\n        folder->Release();\n    }\n\n    // Set the dialog title and option to select folders\n    ifd->SetOptions(FOS_PICKFOLDERS);\n    ifd->SetTitle(m_wtitle.c_str());\n\n    hr = ifd->Show(GetActiveWindow());\n    if (SUCCEEDED(hr))\n    {\n        IShellItem* item;\n        hr = ifd->GetResult(&item);\n        if (SUCCEEDED(hr))\n        {\n            wchar_t* wselected = nullptr;\n            item->GetDisplayName(SIGDN_FILESYSPATH, &wselected);\n            item->Release();\n\n            if (wselected)\n            {\n                result = internal::wstr2str(std::wstring(wselected));\n                dll::proc<void WINAPI (LPVOID)>(ole32_dll(), \"CoTaskMemFree\")(wselected);\n            }\n        }\n    }\n\n    ifd->Release();\n\n    return result;\n}\n#endif\n\n// notify implementation\n\ninline notify::notify(std::string const &title,\n                      std::string const &message,\n                      icon _icon /* = icon::info */)\n{\n    if (_icon == icon::question) // Not supported by notifications\n        _icon = icon::info;\n\n#if _WIN32\n    // Use a static shared pointer for notify_icon so that we can delete\n    // it whenever we need to display a new one, and we can also wait\n    // until the program has finished running.\n    struct notify_icon_data : public NOTIFYICONDATAW\n    {\n        ~notify_icon_data() { Shell_NotifyIconW(NIM_DELETE, this); }\n    };\n\n    static std::shared_ptr<notify_icon_data> nid;\n\n    // Release the previous notification icon, if any, and allocate a new\n    // one. Note that std::make_shared() does value initialization, so there\n    // is no need to memset the structure.\n    nid = nullptr;\n    nid = std::make_shared<notify_icon_data>();\n\n    // For XP support\n    nid->cbSize = NOTIFYICONDATAW_V2_SIZE;\n    nid->hWnd = nullptr;\n    nid->uID = 0;\n\n    // Flag Description:\n    // - NIF_ICON    The hIcon member is valid.\n    // - NIF_MESSAGE The uCallbackMessage member is valid.\n    // - NIF_TIP     The szTip member is valid.\n    // - NIF_STATE   The dwState and dwStateMask members are valid.\n    // - NIF_INFO    Use a balloon ToolTip instead of a standard ToolTip. The szInfo, uTimeout, szInfoTitle, and dwInfoFlags members are valid.\n    // - NIF_GUID    Reserved.\n    nid->uFlags = NIF_MESSAGE | NIF_ICON | NIF_INFO;\n\n    // Flag Description\n    // - NIIF_ERROR     An error icon.\n    // - NIIF_INFO      An information icon.\n    // - NIIF_NONE      No icon.\n    // - NIIF_WARNING   A warning icon.\n    // - NIIF_ICON_MASK Version 6.0. Reserved.\n    // - NIIF_NOSOUND   Version 6.0. Do not play the associated sound. Applies only to balloon ToolTips\n    switch (_icon)\n    {\n        case icon::warning: nid->dwInfoFlags = NIIF_WARNING; break;\n        case icon::error: nid->dwInfoFlags = NIIF_ERROR; break;\n        /* case icon::info: */ default: nid->dwInfoFlags = NIIF_INFO; break;\n    }\n\n    ENUMRESNAMEPROC icon_enum_callback = [](HMODULE, LPCTSTR, LPTSTR lpName, LONG_PTR lParam) -> BOOL\n    {\n        ((NOTIFYICONDATAW *)lParam)->hIcon = ::LoadIcon(GetModuleHandle(nullptr), lpName);\n        return false;\n    };\n\n    nid->hIcon = ::LoadIcon(nullptr, IDI_APPLICATION);\n    ::EnumResourceNames(nullptr, RT_GROUP_ICON, icon_enum_callback, (LONG_PTR)nid.get());\n\n    nid->uTimeout = 5000;\n\n    StringCchCopyW(nid->szInfoTitle, ARRAYSIZE(nid->szInfoTitle), internal::str2wstr(title).c_str());\n    StringCchCopyW(nid->szInfo, ARRAYSIZE(nid->szInfo), internal::str2wstr(message).c_str());\n\n    // Display the new icon\n    Shell_NotifyIconW(NIM_ADD, nid.get());\n#else\n    auto command = desktop_helper();\n\n    if (is_osascript())\n    {\n        command.push_back(\"-e\");\n        command.push_back(\"display notification \" + osascript_quote(message) +\n                          \" with title \" + osascript_quote(title));\n    }\n    else if (is_zenity())\n    {\n        command.push_back(\"--notification\");\n        command.push_back(\"--window-icon\");\n        command.push_back(get_icon_name(_icon));\n        command.push_back(\"--text\");\n        command.push_back(title + \"\\n\" + message);\n    }\n    else if (is_kdialog())\n    {\n        command.push_back(\"--icon\");\n        command.push_back(get_icon_name(_icon));\n        command.push_back(\"--title\");\n        command.push_back(title);\n        command.push_back(\"--passivepopup\");\n        command.push_back(message);\n        command.push_back(\"5\");\n    }\n\n    if (flags(flag::is_verbose))\n        std::cerr << \"pfd: \" << command << std::endl;\n\n    m_async->start_process(command);\n#endif\n}\n\n// message implementation\n\ninline message::message(std::string const &title,\n                        std::string const &text,\n                        choice _choice /* = choice::ok_cancel */,\n                        icon _icon /* = icon::info */)\n{\n#if _WIN32\n    UINT style = MB_TOPMOST;\n    switch (_icon)\n    {\n        case icon::warning: style |= MB_ICONWARNING; break;\n        case icon::error: style |= MB_ICONERROR; break;\n        case icon::question: style |= MB_ICONQUESTION; break;\n        /* case icon::info: */ default: style |= MB_ICONINFORMATION; break;\n    }\n\n    switch (_choice)\n    {\n        case choice::ok_cancel: style |= MB_OKCANCEL; break;\n        case choice::yes_no: style |= MB_YESNO; break;\n        case choice::yes_no_cancel: style |= MB_YESNOCANCEL; break;\n        case choice::retry_cancel: style |= MB_RETRYCANCEL; break;\n        case choice::abort_retry_ignore: style |= MB_ABORTRETRYIGNORE; break;\n        /* case choice::ok: */ default: style |= MB_OK; break;\n    }\n\n    m_mappings[IDCANCEL] = button::cancel;\n    m_mappings[IDOK] = button::ok;\n    m_mappings[IDYES] = button::yes;\n    m_mappings[IDNO] = button::no;\n    m_mappings[IDABORT] = button::abort;\n    m_mappings[IDRETRY] = button::retry;\n    m_mappings[IDIGNORE] = button::ignore;\n\n    m_async->start_func([this, text, title, style](int* exit_code) -> std::string\n    {\n        auto wtext = internal::str2wstr(text);\n        auto wtitle = internal::str2wstr(title);\n        // Apply new visual style (required for all Windows versions)\n        new_style_context ctx;\n        *exit_code = MessageBoxW(GetActiveWindow(), wtext.c_str(), wtitle.c_str(), style);\n        return \"\";\n    });\n\n#elif __EMSCRIPTEN__\n    std::string full_message;\n    switch (_icon)\n    {\n        case icon::warning: full_message = \"⚠️\"; break;\n        case icon::error: full_message = \"⛔\"; break;\n        case icon::question: full_message = \"❓\"; break;\n        /* case icon::info: */ default: full_message = \"ℹ\"; break;\n    }\n\n    full_message += ' ' + title + \"\\n\\n\" + text;\n\n    // This does not really start an async task; it just passes the\n    // EM_ASM_INT return value to a fake start() function.\n    m_async->start(EM_ASM_INT(\n    {\n        if ($1)\n            return window.confirm(UTF8ToString($0)) ? 0 : -1;\n        alert(UTF8ToString($0));\n        return 0;\n    }, full_message.c_str(), _choice == choice::ok_cancel));\n#else\n    auto command = desktop_helper();\n\n    if (is_osascript())\n    {\n        std::string script = \"display dialog \" + osascript_quote(text) +\n                             \" with title \" + osascript_quote(title);\n        switch (_choice)\n        {\n            case choice::ok_cancel:\n                script += \"buttons {\\\"OK\\\", \\\"Cancel\\\"}\"\n                          \" default button \\\"OK\\\"\"\n                          \" cancel button \\\"Cancel\\\"\";\n                m_mappings[256] = button::cancel;\n                break;\n            case choice::yes_no:\n                script += \"buttons {\\\"Yes\\\", \\\"No\\\"}\"\n                          \" default button \\\"Yes\\\"\"\n                          \" cancel button \\\"No\\\"\";\n                m_mappings[256] = button::no;\n                break;\n            case choice::yes_no_cancel:\n                script += \"buttons {\\\"Yes\\\", \\\"No\\\", \\\"Cancel\\\"}\"\n                          \" default button \\\"Yes\\\"\"\n                          \" cancel button \\\"Cancel\\\"\";\n                m_mappings[256] = button::cancel;\n                break;\n            case choice::retry_cancel:\n                script += \"buttons {\\\"Retry\\\", \\\"Cancel\\\"}\"\n                          \" default button \\\"Retry\\\"\"\n                          \" cancel button \\\"Cancel\\\"\";\n                m_mappings[256] = button::cancel;\n                break;\n            case choice::abort_retry_ignore:\n                script += \"buttons {\\\"Abort\\\", \\\"Retry\\\", \\\"Ignore\\\"}\"\n                          \" default button \\\"Retry\\\"\"\n                          \" cancel button \\\"Retry\\\"\";\n                m_mappings[256] = button::cancel;\n                break;\n            case choice::ok: default:\n                script += \"buttons {\\\"OK\\\"}\"\n                          \" default button \\\"OK\\\"\"\n                          \" cancel button \\\"OK\\\"\";\n                m_mappings[256] = button::ok;\n                break;\n        }\n        script += \" with icon \";\n        switch (_icon)\n        {\n            #define PFD_OSX_ICON(n) \"alias ((path to library folder from system domain) as text \" \\\n                \"& \\\"CoreServices:CoreTypes.bundle:Contents:Resources:\" n \".icns\\\")\"\n            case icon::info: default: script += PFD_OSX_ICON(\"ToolBarInfo\"); break;\n            case icon::warning: script += \"caution\"; break;\n            case icon::error: script += \"stop\"; break;\n            case icon::question: script += PFD_OSX_ICON(\"GenericQuestionMarkIcon\"); break;\n            #undef PFD_OSX_ICON\n        }\n\n        command.push_back(\"-e\");\n        command.push_back(script);\n    }\n    else if (is_zenity())\n    {\n        switch (_choice)\n        {\n            case choice::ok_cancel:\n                command.insert(command.end(), { \"--question\", \"--cancel-label=Cancel\", \"--ok-label=OK\" }); break;\n            case choice::yes_no:\n                // Do not use standard --question because it causes “No” to return -1,\n                // which is inconsistent with the “Yes/No/Cancel” mode below.\n                command.insert(command.end(), { \"--question\", \"--switch\", \"--extra-button=No\", \"--extra-button=Yes\" }); break;\n            case choice::yes_no_cancel:\n                command.insert(command.end(), { \"--question\", \"--switch\", \"--extra-button=Cancel\", \"--extra-button=No\", \"--extra-button=Yes\" }); break;\n            case choice::retry_cancel:\n                command.insert(command.end(), { \"--question\", \"--switch\", \"--extra-button=Cancel\", \"--extra-button=Retry\" }); break;\n            case choice::abort_retry_ignore:\n                command.insert(command.end(), { \"--question\", \"--switch\", \"--extra-button=Ignore\", \"--extra-button=Abort\", \"--extra-button=Retry\" }); break;\n            case choice::ok:\n            default:\n                switch (_icon)\n                {\n                    case icon::error: command.push_back(\"--error\"); break;\n                    case icon::warning: command.push_back(\"--warning\"); break;\n                    default: command.push_back(\"--info\"); break;\n                }\n        }\n\n        command.insert(command.end(), { \"--title\", title,\n                                        \"--width=300\", \"--height=0\", // sensible defaults\n                                        \"--text\", text,\n                                        \"--icon-name=dialog-\" + get_icon_name(_icon) });\n    }\n    else if (is_kdialog())\n    {\n        if (_choice == choice::ok)\n        {\n            switch (_icon)\n            {\n                case icon::error: command.push_back(\"--error\"); break;\n                case icon::warning: command.push_back(\"--sorry\"); break;\n                default: command.push_back(\"--msgbox\"); break;\n            }\n        }\n        else\n        {\n            std::string flag = \"--\";\n            if (_icon == icon::warning || _icon == icon::error)\n                flag += \"warning\";\n            flag += \"yesno\";\n            if (_choice == choice::yes_no_cancel)\n                flag += \"cancel\";\n            command.push_back(flag);\n            if (_choice == choice::yes_no || _choice == choice::yes_no_cancel)\n            {\n                m_mappings[0] = button::yes;\n                m_mappings[256] = button::no;\n            }\n        }\n\n        command.push_back(text);\n        command.push_back(\"--title\");\n        command.push_back(title);\n\n        // Must be after the above part\n        if (_choice == choice::ok_cancel)\n            command.insert(command.end(), { \"--yes-label\", \"OK\", \"--no-label\", \"Cancel\" });\n    }\n\n    if (flags(flag::is_verbose))\n        std::cerr << \"pfd: \" << command << std::endl;\n\n    m_async->start_process(command);\n#endif\n}\n\ninline button message::result()\n{\n    int exit_code;\n    auto ret = m_async->result(&exit_code);\n    // osascript will say \"button returned:Cancel\\n\"\n    // and others will just say \"Cancel\\n\"\n    if (exit_code < 0 || // this means cancel\n        internal::ends_with(ret, \"Cancel\\n\"))\n        return button::cancel;\n    if (internal::ends_with(ret, \"OK\\n\"))\n        return button::ok;\n    if (internal::ends_with(ret, \"Yes\\n\"))\n        return button::yes;\n    if (internal::ends_with(ret, \"No\\n\"))\n        return button::no;\n    if (internal::ends_with(ret, \"Abort\\n\"))\n        return button::abort;\n    if (internal::ends_with(ret, \"Retry\\n\"))\n        return button::retry;\n    if (internal::ends_with(ret, \"Ignore\\n\"))\n        return button::ignore;\n    if (m_mappings.count(exit_code) != 0)\n        return m_mappings[exit_code];\n    return exit_code == 0 ? button::ok : button::cancel;\n}\n\n// open_file implementation\n\ninline open_file::open_file(std::string const &title,\n                            std::string const &default_path /* = \"\" */,\n                            std::vector<std::string> const &filters /* = { \"All Files\", \"*\" } */,\n                            opt options /* = opt::none */)\n  : file_dialog(type::open, title, default_path, filters, options)\n{\n}\n\ninline open_file::open_file(std::string const &title,\n                            std::string const &default_path,\n                            std::vector<std::string> const &filters,\n                            bool allow_multiselect)\n  : open_file(title, default_path, filters,\n              (allow_multiselect ? opt::multiselect : opt::none))\n{\n}\n\ninline std::vector<std::string> open_file::result()\n{\n    return vector_result();\n}\n\n// save_file implementation\n\ninline save_file::save_file(std::string const &title,\n                            std::string const &default_path /* = \"\" */,\n                            std::vector<std::string> const &filters /* = { \"All Files\", \"*\" } */,\n                            opt options /* = opt::none */)\n  : file_dialog(type::save, title, default_path, filters, options)\n{\n}\n\ninline save_file::save_file(std::string const &title,\n                            std::string const &default_path,\n                            std::vector<std::string> const &filters,\n                            bool confirm_overwrite)\n  : save_file(title, default_path, filters,\n              (confirm_overwrite ? opt::none : opt::force_overwrite))\n{\n}\n\ninline std::string save_file::result()\n{\n    return string_result();\n}\n\n// select_folder implementation\n\ninline select_folder::select_folder(std::string const &title,\n                                    std::string const &default_path /* = \"\" */,\n                                    opt options /* = opt::none */)\n  : file_dialog(type::folder, title, default_path, {}, options)\n{\n}\n\ninline std::string select_folder::result()\n{\n    return string_result();\n}\n\n#endif // PFD_SKIP_IMPLEMENTATION\n\n} // namespace pfd\n\n"
  },
  {
    "path": "examples/r2t2/CMakeLists.txt",
    "content": "#\n# r2t2\n\nset(TARGET r2t2)\n\nif (NOT EMSCRIPTEN)\n    add_executable(${TARGET}\n        main.cpp\n        )\n\n    target_include_directories(${TARGET} PRIVATE\n        ..\n        )\n\n    target_link_libraries(${TARGET} PRIVATE\n        ggwave-common\n        ggwave\n        )\nendif()\n\n#\n# r2t2-rx\n\nset(TARGET r2t2-rx)\n\nif (NOT EMSCRIPTEN)\n    add_executable(${TARGET}\n        r2t2-rx.cpp\n        )\n\n    target_include_directories(${TARGET} PRIVATE\n        ..\n        ${SDL2_INCLUDE_DIRS}\n        )\n\n    target_link_libraries(${TARGET} PRIVATE\n        ggwave-common\n        ggwave\n        ${SDL2_LIBRARIES}\n        )\nelse()\n    configure_file(${CMAKE_CURRENT_SOURCE_DIR}/build_timestamp-tmpl.h ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${TARGET}/build_timestamp.h @ONLY)\n\n    add_executable(${TARGET}\n        r2t2-rx.cpp\n        )\n\n    target_include_directories(${TARGET} PRIVATE\n        ..\n        ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${TARGET}/\n        )\n\n    target_link_libraries(${TARGET} PRIVATE\n        ggwave-common\n        ggwave\n        )\n\n    configure_file(${CMAKE_CURRENT_SOURCE_DIR}/index-tmpl.html ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${TARGET}/index.html @ONLY)\n    configure_file(${CMAKE_CURRENT_SOURCE_DIR}/style.css ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${TARGET}/style.css COPYONLY)\n    configure_file(${CMAKE_CURRENT_SOURCE_DIR}/main.js ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${TARGET}/main.js COPYONLY)\n    configure_file(${CMAKE_CURRENT_SOURCE_DIR}/plucky.mp3 ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${TARGET}/plucky.mp3 COPYONLY)\nendif()\n"
  },
  {
    "path": "examples/r2t2/README.md",
    "content": "# r2t2\n\nTransmit data with the PC speaker\n\n<a href=\"https://user-images.githubusercontent.com/1991296/115141782-cba9f480-a046-11eb-9462-791477b856f5.mp4\"><img width=\"100%\" src=\"https://user-images.githubusercontent.com/1991296/115141739-a1583700-a046-11eb-94e7-a411d52ecf30.png\"></img></a>\n<p align=\"center\">\n  <i>Vid. <b>r2t2</b> demonstration</i>\n</p>\n\nThis 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.\n\nYou can then run the following commands:\n\n```bash\n# transmit the message \"test\" with default protocol \"[R2T2] Normal\"\necho test | sudo r2t2\n\n# transmit the message \"hello\" with protocol \"[R2T2] Fast\"\necho hello | sudo r2t2 -t10\n\n# transmit the message \"foo bar\" with protocol \"[R2T2] Fastest\"\necho \"foo bar\" | sudo r2t2 -t11\n```\n\nTo receive the transmitted message, open the following page on your phone and place it near the speaker:\n\nhttps://r2t2.ggerganov.com\n\n## Applications\n\nThis 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.\n\n## Requirements\n\n- [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.\n  \n  Here are the ones that I use:\n  \n<p align=\"center\">\n    <table border=0>\n<tr>\n<td>\n    <img width=\"100%\" alt=\"Talking buttons\" src=\"https://user-images.githubusercontent.com/1991296/115141260-ee86d980-a043-11eb-9699-587e0af53af9.jpg\"></img>\n</td>\n<td>\n <img width=\"100%\" alt=\"Talking buttons\" src=\"https://user-images.githubusercontent.com/1991296/115141261-f0509d00-a043-11eb-82cf-a89040b51f13.jpg\"></img>\n</td>\n</tr>\n</table>\n</p>\n<p align=\"center\">\n  <i>Img. Left: PC speaker plugged into a motherboard. Right: two PC speakers with a coin for size comparison</i>\n</p>\n\n- Unix operating system\n- Add the `pcspkr` kernel module: `sudo modprobe pcspkr`\n- The program requires to run as `sudo` in order to access the PC speaker\n\n## Build\n\n```bash\ngit clone https://github.com/ggerganov/ggwave --recursive\ncd ggwave\nmkdir build && cd build\nmake\n./bin/r2t2\n```\n\n## Acknowledgements\n\nThanks to [Radoslav Gerganov](https://github.com/rgerganov) for this cool idea!\n"
  },
  {
    "path": "examples/r2t2/build_timestamp-tmpl.h",
    "content": "static const char * BUILD_TIMESTAMP=\"@GIT_DATE@ (@GIT_SHA1@)\";\n"
  },
  {
    "path": "examples/r2t2/index-tmpl.html",
    "content": "<!doctype html>\n<html lang=\"en-us\">\n    <head>\n        <meta charset=\"utf-8\">\n        <title>r2t2</title>\n\n        <meta name=\"viewport\" content=\"width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no\"/>\n\n        <link rel=\"shortcut icon\" href=\"favicon.ico\">\n        <link rel=\"stylesheet\" href=\"style.css\">\n    </head>\n    <body>\n        <div id=\"main-container\">\n            <h1>r2t2</h1>\n\n            Press the Init button and place the microphone near the PC speaker to receive messages\n\n            <br><br>\n\n            <button onClick=\"doInit()\" id=\"butInit\" disabled>Init</button>\n\n            <div id=\"sound\"></div>\n\n            <br><hr>\n\n            <p>Standard output:</p>\n            <textarea id=\"output\" rows=\"8\"></textarea>\n\n            <div class=\"spinner\" id='spinnerEm'></div>\n            <div class=\"emscripten\" id=\"statusEm\">Downloading...</div>\n\n            <div class=\"emscripten\">\n                <progress value=\"0\" max=\"100\" id=\"progressEm\" hidden=1></progress>\n            </div>\n        </div>\n\n        <div class=\"cell-version\">\n            <span>\n                |\n                Build time: <span class=\"nav-link\">@GIT_DATE@</span> |\n                Commit hash: <a class=\"nav-link\" href=\"https://github.com/ggerganov/ggwave/commit/@GIT_SHA1@\">@GIT_SHA1@</a> |\n                Commit subject: <span class=\"nav-link\">@GIT_COMMIT_SUBJECT@</span> |\n            </span>\n        </div>\n        <div class=\"cell-about\">\n            <a class=\"nav-link\" href=\"https://github.com/ggerganov/ggwave/tree/master/examples/r2t2\"><span class=\"d-none d-sm-inline\">View on GitHub </span>\n                <svg version=\"1.1\" width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" class=\"octicon octicon-mark-github\" aria-hidden=\"true\"><path fill-rule=\"evenodd\" d=\"M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0 0 16 8c0-4.42-3.58-8-8-8z\"></path></svg>\n            </a>\n        </div>\n\n        <script type='text/javascript'>\n            var isInitialized = false;\n            var isAudioContextUnlocked = false;\n\n            var statusElement = document.getElementById('statusEm');\n            var progressElement = document.getElementById('progressEm');\n            var spinnerElement = document.getElementById('spinnerEm');\n\n            var Module = {\n                doNotCaptureKeyboard: true,\n                pre: [],\n                preRun: [(function() {\n                    let constraints = {\n                        audio: {\n                            echoCancellation: false,\n                            autoGainControl: false,\n                            noiseSuppression: false\n                        }\n                    };\n\n                    let mediaInput = navigator.mediaDevices.getUserMedia( constraints );\n                }) ],\n                postRun: [ (function() { document.getElementById(\"butInit\").disabled = false; }) ],\n                print: (function() {\n                    var element = document.getElementById('output');\n                    if (element) element.value = ''; // clear browser cache\n                    return function(text) {\n                        if (arguments.length > 1) text = Array.prototype.slice.call(arguments).join(' ');\n                        console.log(text);\n                        if (element) {\n                            element.value += text + \"\\n\";\n                            element.scrollTop = element.scrollHeight; // focus on bottom\n                        }\n                    };\n                })(),\n                printErr: (function() {\n                    var element = document.getElementById('output');\n                    if (element) element.value = ''; // clear browser cache\n                    return function(text) {\n                        if (arguments.length > 1) text = Array.prototype.slice.call(arguments).join(' ');\n                        console.error(text);\n                        if (element) {\n                            element.value += text + \"\\n\";\n                            element.scrollTop = element.scrollHeight; // focus on bottom\n                        }\n                    };\n                })(),\n                setStatus: function(text) {\n                    if (!Module.setStatus.last) Module.setStatus.last = { time: Date.now(), text: '' };\n                    if (text === Module.setStatus.text) return;\n                    var m = text.match(/([^(]+)\\((\\d+(\\.\\d+)?)\\/(\\d+)\\)/);\n                    var now = Date.now();\n                    if (m && now - Date.now() < 30) return; // if this is a progress update, skip it if too soon\n                    if (m) {\n                        text = m[1];\n                        progressElement.value = parseInt(m[2])*100;\n                        progressElement.max = parseInt(m[4])*100;\n                        progressElement.hidden = false;\n                        spinnerElement.hidden = false;\n                    } else {\n                        progressElement.value = null;\n                        progressElement.max = null;\n                        progressElement.hidden = true;\n                        if (!text) spinnerElement.style.display = 'none';\n                    }\n                    statusElement.innerHTML = text;\n                },\n                totalDependencies: 0,\n                monitorRunDependencies: function(left) {\n                    this.totalDependencies = Math.max(this.totalDependencies, left);\n                    Module.setStatus(left ? 'Preparing... (' + (this.totalDependencies-left) + '/' + this.totalDependencies + ')' : 'All downloads complete.');\n                }\n            };\n\n            function doInit() {\n                if (isInitialized == false) {\n                    Module._doInit();\n\n                    isInitialized = true;\n                }\n\n                playSound(\"plucky\");\n            }\n\n            Module.setStatus('Initializing...');\n            window.onerror = function(event) {\n                Module.setStatus('Exception thrown: ' + JSON.stringify(event));\n                spinnerElement.style.display = 'none';\n                Module.setStatus = function(text) {\n                    if (text) Module.printErr('[post-exception status] ' + text);\n                };\n            };\n\n            window.addEventListener('touchstart', function() {\n                //if (isAudioContextUnlocked == false && SDL2.audioContext) {\n                //    var buffer = SDL2.audioContext.createBuffer(1, 1, 22050);\n                //    var source = SDL2.audioContext.createBufferSource();\n                //    source.buffer = buffer;\n                //    source.connect(SDL2.audioContext.destination);\n                //    source.start();\n\n                //    setTimeout(function() {\n                //        if((source.playbackState === source.PLAYING_STATE || source.playbackState === source.FINISHED_STATE)) {\n                //            isAudioContextUnlocked = true;\n                //            Module.setStatus('Wab Audio API unlocked successfully!');\n                //        } else {\n                //            Module.setStatus('Failed to unlock Web Audio APIi. This browser seems to not be supported');\n                //        }\n                //    }, 0);\n                //}\n            }, false);\n\n            function playSound(filename){\n                document.getElementById(\"sound\").innerHTML='<audio id=\"soundInner\"><source src=\"' + filename + '.mp3\" type=\"audio/mpeg\" /><embed hidden=\"true\" autostart=\"true\" loop=\"false\" src=\"' + filename +'.mp3\" /></audio>';\n                document.getElementById(\"soundInner\").volume = 0.1;\n                document.getElementById(\"soundInner\").play();\n            }\n\n        </script>\n\n        <script async type=\"text/javascript\" src=\"@TARGET@.js\"></script>\n        <script type=\"text/javascript\" src=\"main.js\"></script>\n    </body>\n</html>\n"
  },
  {
    "path": "examples/r2t2/main.cpp",
    "content": "#include \"ggwave/ggwave.h\"\n\n#include \"ggwave-common.h\"\n\n#include <fcntl.h>\n#include <unistd.h>\n#include <sys/ioctl.h>\n#include <sys/types.h>\n#include <sys/stat.h>\n#include <linux/kd.h>\n\n#define CONSOLE \"/dev/tty0\"\n\n#include <cmath>\n#include <cstdio>\n#include <string>\n#include <iostream>\n\nvoid processTone(int fd, double freq_hz, long duration_ms, bool useBeep, bool printTones, bool printArduino) {\n    if (printTones) {\n        printf(\"TONE %8.2f Hz %5ld ms\\n\", freq_hz, duration_ms);\n        return;\n    }\n\n    if (printArduino) {\n        printf(\"tone(kPinSpeaker, %8.2f); delay(%4ld);\\n\", freq_hz, duration_ms);\n        return;\n    }\n\n    if (useBeep) {\n        static char cmd[128];\n        snprintf(cmd, 128, \"beep -f %g -l %ld\", freq_hz, duration_ms);\n        int ret = system(cmd);\n        if (ret != 0) {\n            printf(\"system(\\\"%s\\\") failed with %d\\n\", cmd, ret);\n        }\n        return;\n    }\n\n    long pitch = std::round(1193180.0/freq_hz);\n    long ms = std::round(duration_ms);\n    ioctl(fd, KDMKTONE, (ms<<16)|pitch);\n    usleep(ms*1000);\n}\n\nint main(int argc, char** argv) {\n    printf(\"Usage: %s [-p] [-b] [-tN] [-lN]\\n\", argv[0]);\n    printf(\"    -p  - print tones, no playback\\n\");\n    printf(\"    -A  - print Arduino code\\n\");\n    printf(\"    -b  - use 'beep' command\\n\");\n    printf(\"    -s  - use Direct Sequence Spread (DSS)\\n\");\n    printf(\"    -tN - transmission protocol\\n\");\n    printf(\"    -lN - fixed payload length of size N, N in [1, %d]\\n\", GGWave::kMaxLengthFixed);\n    printf(\"\\n\");\n\n    auto & protocols = GGWave::Protocols::tx();\n    protocols = { {\n        { \"[R2T2] Normal\",      64,  9, 1, 2, true, },\n        { \"[R2T2] Fast\",        64,  6, 1, 2, true, },\n        { \"[R2T2] Fastest\",     64,  3, 1, 2, true, },\n        { \"[R2T2] Low Normal\",  16,  9, 1, 2, true, },\n        { \"[R2T2] Low Fast\",    16,  6, 1, 2, true, },\n        { \"[R2T2] Low Fastest\", 16,  3, 1, 2, true, },\n    } };\n\n    const auto argm         = parseCmdArguments(argc, argv);\n    const bool printTones   = argm.count(\"p\") > 0;\n    const bool printArduino = argm.count(\"A\") > 0;\n    const bool useBeep      = argm.count(\"b\") > 0;\n    const bool useDSS       = argm.count(\"s\") > 0;\n    const int txProtocolId  = argm.count(\"t\") == 0 ? 0 : std::stoi(argm.at(\"t\"));\n    const int payloadLength = argm.count(\"l\") == 0 ? 16 : std::stoi(argm.at(\"l\"));\n\n    GGWave::OperatingMode mode = GGWAVE_OPERATING_MODE_TX | GGWAVE_OPERATING_MODE_TX_ONLY_TONES;\n    if (useDSS) mode |= GGWAVE_OPERATING_MODE_USE_DSS;\n\n    GGWave ggWave({\n        payloadLength,\n        GGWave::kDefaultSampleRate,\n        GGWave::kDefaultSampleRate,\n        GGWave::kDefaultSampleRate,\n        GGWave::kDefaultSamplesPerFrame,\n        GGWave::kDefaultSoundMarkerThreshold,\n        GGWAVE_SAMPLE_FORMAT_F32,\n        GGWAVE_SAMPLE_FORMAT_F32,\n        mode,\n    });\n\n    printf(\"Available Tx protocols:\\n\");\n    for (int i = 0; i < (int) protocols.size(); ++i) {\n        const auto & protocol = protocols[i];\n        if (protocol.enabled && protocol.name) {\n            printf(\"    -t%-2d : %-16s\\n\", i, protocol.name);\n        }\n    }\n    printf(\"\\n\");\n\n    if (txProtocolId < 0 || txProtocolId >= (int) protocols.size()) {\n        fprintf(stderr, \"Unknown Tx protocol %d\\n\", txProtocolId);\n        return -3;\n    }\n\n    printf(\"Selecting Tx protocol %d\\n\", txProtocolId);\n\n    int fd = 1;\n    if (useBeep == false && printTones == false && printArduino == false) {\n        if (ioctl(fd, KDMKTONE, 0)) {\n            fd = open(CONSOLE, O_RDONLY);\n        }\n        if (fd < 0) {\n            perror(CONSOLE);\n            fprintf(stderr, \"This program must be run as root\\n\");\n            return 1;\n        }\n    }\n\n    fprintf(stderr, \"Enter a text message:\\n\");\n\n    std::string message;\n    std::getline(std::cin, message);\n\n    printf(\"\\n\");\n\n    if (message.size() == 0) {\n        fprintf(stderr, \"Invalid message: size = 0\\n\");\n        return -2;\n    }\n\n    if ((int) message.size() > payloadLength) {\n        fprintf(stderr, \"Invalid message: size > %d\\n\", payloadLength);\n        return -3;\n    }\n\n    ggWave.init(message.size(), message.data(), GGWave::TxProtocolId(txProtocolId), 10);\n    ggWave.encode();\n\n    const auto & protocol = protocols[txProtocolId];\n    const auto tones = ggWave.txTones();\n    const auto duration_ms = protocol.txDuration_ms(ggWave.samplesPerFrame(), ggWave.sampleRateOut());\n    for (auto & tone : tones) {\n        const auto freq_hz = (protocol.freqStart + tone)*ggWave.hzPerSample();\n        processTone(fd, freq_hz, duration_ms, useBeep, printTones, printArduino);\n    }\n\n    return 0;\n}\n"
  },
  {
    "path": "examples/r2t2/main.js",
    "content": "function transmitText(sText) {\n    var r = new Uint8Array(256);\n    for (var i = 0; i < sText.length; ++i) {\n        r[i] = sText.charCodeAt(i);\n    }\n\n    var buffer = Module._malloc(256);\n    Module.writeArrayToMemory(r, buffer, 256);\n    Module._sendData(sText.length, buffer, protocolId, volume);\n    Module._free(buffer);\n}\n\nvar firstTimeFail = false;\nvar peerInfo = document.querySelector('a#peer-info');\n\nfunction updatePeerInfo() {\n    if (typeof Module === 'undefined') return;\n    var framesLeftToRecord = Module._getFramesLeftToRecord();\n    var framesToRecord = Module._getFramesToRecord();\n    var framesLeftToAnalyze = Module._getFramesLeftToAnalyze();\n    var framesToAnalyze = Module._getFramesToAnalyze();\n\n    if (framesToAnalyze > 0) {\n        peerInfo.innerHTML=\n            \"Analyzing Rx data: <progress value=\" + (framesToAnalyze - framesLeftToAnalyze) +\n            \" max=\" + (framesToRecord) + \"></progress>\";\n        peerReceive.innerHTML= \"\";\n    } else if (framesLeftToRecord > Math.max(0, 0.05*framesToRecord)) {\n        firstTimeFail = true;\n        peerInfo.innerHTML=\n            \"Transmission in progress: <progress value=\" + (framesToRecord - framesLeftToRecord) +\n            \" max=\" + (framesToRecord) + \"></progress>\";\n    } else if (framesToRecord > 0) {\n        peerInfo.innerHTML= \"Analyzing Rx data ...\";\n    } else if (framesToRecord == 0) {\n        peerInfo.innerHTML= \"<p>Listening for waves ...</p>\";\n    } else if (framesToRecord == -1) {\n        if (firstTimeFail) {\n            playSound(\"/media/case-closed\");\n            firstTimeFail = false;\n        }\n        peerInfo.innerHTML= \"<p style=\\\"color:red\\\">Failed to decode Rx data</p>\";\n    }\n}\n\nfunction updateRx() {\n    if (typeof Module === 'undefined') return;\n    Module._getText(bufferRx);\n    var result = \"\";\n    for (var i = 0; i < 140; ++i){\n        result += (String.fromCharCode((Module.HEAPU8)[bufferRx + i]));\n        brx[i] = (Module.HEAPU8)[bufferRx + i];\n    }\n    document.getElementById('rxData').innerHTML = result;\n}\n"
  },
  {
    "path": "examples/r2t2/r2t2-rx.cpp",
    "content": "#include \"ggwave-common.h\"\n\n#include \"ggwave/ggwave.h\"\n\n#ifdef __EMSCRIPTEN__\n#include \"build_timestamp.h\"\n#include <emscripten.h>\n#else\n#define EMSCRIPTEN_KEEPALIVE\n#endif\n\n#include <SDL.h>\n#include <SDL_opengl.h>\n\n#include <chrono>\n#include <string>\n#include <thread>\n#include <functional>\n\nnamespace {\n\nstd::string g_defaultCaptureDeviceName = \"\";\n\nSDL_AudioDeviceID g_devIdInp = 0;\nSDL_AudioDeviceID g_devIdOut = 0;\n\nSDL_AudioSpec g_obtainedSpecInp;\nSDL_AudioSpec g_obtainedSpecOut;\n\nGGWave *g_ggWave = nullptr;\n\n}\n\nstatic std::function<bool()> g_doInit;\nstatic std::function<void(int, int)> g_setWindowSize;\nstatic std::function<bool()> g_mainUpdate;\n\nvoid mainUpdate(void *) {\n    g_mainUpdate();\n}\n\n// JS interface\n\nextern \"C\" {\n    EMSCRIPTEN_KEEPALIVE\n        int sendData(int textLength, const char * text, int protocolId, int volume) {\n            g_ggWave->init(textLength, text, GGWave::TxProtocolId(protocolId), volume);\n            return 0;\n        }\n\n    EMSCRIPTEN_KEEPALIVE\n        int getText(char * text) {\n            std::copy(g_ggWave->rxData().begin(), g_ggWave->rxData().end(), text);\n            return 0;\n        }\n\n    EMSCRIPTEN_KEEPALIVE\n        float sampleRate()        { return g_ggWave->sampleRateInp(); }\n\n    EMSCRIPTEN_KEEPALIVE\n        int framesToRecord()      { return g_ggWave->rxFramesToRecord(); }\n\n    EMSCRIPTEN_KEEPALIVE\n        int framesLeftToRecord()  { return g_ggWave->rxFramesLeftToRecord(); }\n\n    EMSCRIPTEN_KEEPALIVE\n        int framesToAnalyze()     { return g_ggWave->rxFramesToAnalyze(); }\n\n    EMSCRIPTEN_KEEPALIVE\n        int framesLeftToAnalyze() { return g_ggWave->rxFramesLeftToAnalyze(); }\n\n    EMSCRIPTEN_KEEPALIVE\n        int hasDeviceOutput()     { return g_devIdOut; }\n\n    EMSCRIPTEN_KEEPALIVE\n        int hasDeviceCapture()    { return g_devIdInp; }\n\n    EMSCRIPTEN_KEEPALIVE\n        int doInit()              { return g_doInit(); }\n}\n\nvoid GGWave_setDefaultCaptureDeviceName(std::string name) {\n    g_defaultCaptureDeviceName = std::move(name);\n}\n\nbool GGWave_init(\n        const int playbackId,\n        const int captureId,\n        const int payloadLength,\n        const float sampleRateOffset,\n        const bool useDSS) {\n\n    if (g_devIdInp && g_devIdOut) {\n        return false;\n    }\n\n    if (g_devIdInp == 0 && g_devIdOut == 0) {\n        SDL_LogSetPriority(SDL_LOG_CATEGORY_APPLICATION, SDL_LOG_PRIORITY_INFO);\n\n        if (SDL_Init(SDL_INIT_AUDIO) < 0) {\n            SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, \"Couldn't initialize SDL: %s\\n\", SDL_GetError());\n            return (1);\n        }\n\n        SDL_SetHintWithPriority(SDL_HINT_AUDIO_RESAMPLING_MODE, \"medium\", SDL_HINT_OVERRIDE);\n\n        {\n            int nDevices = SDL_GetNumAudioDevices(SDL_FALSE);\n            printf(\"Found %d playback devices:\\n\", nDevices);\n            for (int i = 0; i < nDevices; i++) {\n                printf(\"    - Playback device #%d: '%s'\\n\", i, SDL_GetAudioDeviceName(i, SDL_FALSE));\n            }\n        }\n        {\n            int nDevices = SDL_GetNumAudioDevices(SDL_TRUE);\n            printf(\"Found %d capture devices:\\n\", nDevices);\n            for (int i = 0; i < nDevices; i++) {\n                printf(\"    - Capture device #%d: '%s'\\n\", i, SDL_GetAudioDeviceName(i, SDL_TRUE));\n            }\n        }\n    }\n\n    bool reinit = false;\n\n    if (g_devIdOut == 0) {\n        printf(\"Initializing playback ...\\n\");\n\n        SDL_AudioSpec playbackSpec;\n        SDL_zero(playbackSpec);\n\n        playbackSpec.freq = GGWave::kDefaultSampleRate + sampleRateOffset;\n        playbackSpec.format = AUDIO_S16SYS;\n        playbackSpec.channels = 1;\n        playbackSpec.samples = 16*1024;\n        playbackSpec.callback = NULL;\n\n        SDL_zero(g_obtainedSpecOut);\n\n        if (playbackId >= 0) {\n            printf(\"Attempt to open playback device %d : '%s' ...\\n\", playbackId, SDL_GetAudioDeviceName(playbackId, SDL_FALSE));\n            g_devIdOut = SDL_OpenAudioDevice(SDL_GetAudioDeviceName(playbackId, SDL_FALSE), SDL_FALSE, &playbackSpec, &g_obtainedSpecOut, 0);\n        } else {\n            printf(\"Attempt to open default playback device ...\\n\");\n            g_devIdOut = SDL_OpenAudioDevice(NULL, SDL_FALSE, &playbackSpec, &g_obtainedSpecOut, 0);\n        }\n\n        if (!g_devIdOut) {\n            printf(\"Couldn't open an audio device for playback: %s!\\n\", SDL_GetError());\n            g_devIdOut = 0;\n        } else {\n            printf(\"Obtained spec for output device (SDL Id = %d):\\n\", g_devIdOut);\n            printf(\"    - Sample rate:       %d (required: %d)\\n\", g_obtainedSpecOut.freq, playbackSpec.freq);\n            printf(\"    - Format:            %d (required: %d)\\n\", g_obtainedSpecOut.format, playbackSpec.format);\n            printf(\"    - Channels:          %d (required: %d)\\n\", g_obtainedSpecOut.channels, playbackSpec.channels);\n            printf(\"    - Samples per frame: %d (required: %d)\\n\", g_obtainedSpecOut.samples, playbackSpec.samples);\n\n            if (g_obtainedSpecOut.format != playbackSpec.format ||\n                g_obtainedSpecOut.channels != playbackSpec.channels ||\n                g_obtainedSpecOut.samples != playbackSpec.samples) {\n                g_devIdOut = 0;\n                SDL_CloseAudio();\n                fprintf(stderr, \"Failed to initialize playback SDL_OpenAudio!\");\n\n                return false;\n            }\n\n            reinit = true;\n        }\n    }\n\n    if (g_devIdInp == 0) {\n        SDL_AudioSpec captureSpec;\n        captureSpec = g_obtainedSpecOut;\n        captureSpec.freq = GGWave::kDefaultSampleRate + sampleRateOffset;\n        captureSpec.format = AUDIO_F32SYS;\n        captureSpec.samples = 1024;\n\n        SDL_zero(g_obtainedSpecInp);\n\n        if (captureId >= 0) {\n            printf(\"Attempt to open capture device %d : '%s' ...\\n\", captureId, SDL_GetAudioDeviceName(captureId, SDL_TRUE));\n            g_devIdInp = SDL_OpenAudioDevice(SDL_GetAudioDeviceName(captureId, SDL_TRUE), SDL_TRUE, &captureSpec, &g_obtainedSpecInp, 0);\n        } else {\n            printf(\"Attempt to open default capture device ...\\n\");\n            g_devIdInp = SDL_OpenAudioDevice(g_defaultCaptureDeviceName.empty() ? nullptr : g_defaultCaptureDeviceName.c_str(),\n                                            SDL_TRUE, &captureSpec, &g_obtainedSpecInp, 0);\n        }\n        if (!g_devIdInp) {\n            printf(\"Couldn't open an audio device for capture: %s!\\n\", SDL_GetError());\n            g_devIdInp = 0;\n        } else {\n            printf(\"Obtained spec for input device (SDL Id = %d):\\n\", g_devIdInp);\n            printf(\"    - Sample rate:       %d\\n\", g_obtainedSpecInp.freq);\n            printf(\"    - Format:            %d (required: %d)\\n\", g_obtainedSpecInp.format, captureSpec.format);\n            printf(\"    - Channels:          %d (required: %d)\\n\", g_obtainedSpecInp.channels, captureSpec.channels);\n            printf(\"    - Samples per frame: %d\\n\", g_obtainedSpecInp.samples);\n\n            reinit = true;\n        }\n    }\n\n    GGWave::SampleFormat sampleFormatInp = GGWAVE_SAMPLE_FORMAT_UNDEFINED;\n    GGWave::SampleFormat sampleFormatOut = GGWAVE_SAMPLE_FORMAT_UNDEFINED;\n\n    switch (g_obtainedSpecInp.format) {\n        case AUDIO_U8:      sampleFormatInp = GGWAVE_SAMPLE_FORMAT_U8;  break;\n        case AUDIO_S8:      sampleFormatInp = GGWAVE_SAMPLE_FORMAT_I8;  break;\n        case AUDIO_U16SYS:  sampleFormatInp = GGWAVE_SAMPLE_FORMAT_U16; break;\n        case AUDIO_S16SYS:  sampleFormatInp = GGWAVE_SAMPLE_FORMAT_I16; break;\n        case AUDIO_S32SYS:  sampleFormatInp = GGWAVE_SAMPLE_FORMAT_F32; break;\n        case AUDIO_F32SYS:  sampleFormatInp = GGWAVE_SAMPLE_FORMAT_F32; break;\n    }\n\n    switch (g_obtainedSpecOut.format) {\n        case AUDIO_U8:      sampleFormatOut = GGWAVE_SAMPLE_FORMAT_U8;  break;\n        case AUDIO_S8:      sampleFormatOut = GGWAVE_SAMPLE_FORMAT_I8;  break;\n        case AUDIO_U16SYS:  sampleFormatOut = GGWAVE_SAMPLE_FORMAT_U16; break;\n        case AUDIO_S16SYS:  sampleFormatOut = GGWAVE_SAMPLE_FORMAT_I16; break;\n        case AUDIO_S32SYS:  sampleFormatOut = GGWAVE_SAMPLE_FORMAT_F32; break;\n        case AUDIO_F32SYS:  sampleFormatOut = GGWAVE_SAMPLE_FORMAT_F32; break;\n            break;\n    }\n\n    if (reinit) {\n        if (g_ggWave) delete g_ggWave;\n\n        GGWave::OperatingMode mode = GGWAVE_OPERATING_MODE_RX;\n        if (useDSS) mode |= GGWAVE_OPERATING_MODE_USE_DSS;\n\n        g_ggWave = new GGWave({\n            payloadLength,\n            (float) g_obtainedSpecInp.freq,\n            (float) g_obtainedSpecOut.freq,\n            GGWave::kDefaultSampleRate,\n            GGWave::kDefaultSamplesPerFrame,\n            GGWave::kDefaultSoundMarkerThreshold,\n            sampleFormatInp,\n            sampleFormatOut,\n            mode,\n        });\n    }\n\n    return true;\n}\n\nGGWave *& GGWave_instance() { return g_ggWave; }\n\nbool GGWave_mainLoop() {\n    if (g_devIdInp == 0 && g_devIdOut == 0) {\n        return false;\n    }\n\n    if (g_ggWave->txHasData() == false) {\n        SDL_PauseAudioDevice(g_devIdOut, SDL_FALSE);\n\n        static auto tLastNoData = std::chrono::high_resolution_clock::now();\n        auto tNow = std::chrono::high_resolution_clock::now();\n\n        if ((int) SDL_GetQueuedAudioSize(g_devIdOut) < g_ggWave->samplesPerFrame()*g_ggWave->sampleSizeOut()) {\n            SDL_PauseAudioDevice(g_devIdInp, SDL_FALSE);\n            const int nHave = (int) SDL_GetQueuedAudioSize(g_devIdInp);\n            const int nNeed = g_ggWave->samplesPerFrame()*g_ggWave->sampleSizeInp();\n            if (::getTime_ms(tLastNoData, tNow) > 500.0f && nHave >= nNeed) {\n                static std::vector<uint8_t> dataInp(nNeed);\n                SDL_DequeueAudio(g_devIdInp, dataInp.data(), nNeed);\n\n                if (g_ggWave->decode(dataInp.data(), dataInp.size()) == false) {\n                    fprintf(stderr, \"Warning: failed to decode input data!\\n\");\n                }\n\n                if (nHave > 32*nNeed) {\n                    fprintf(stderr, \"Warning: slow processing, clearing queued audio buffer of %d bytes ...\\n\", SDL_GetQueuedAudioSize(g_devIdInp));\n                    SDL_ClearQueuedAudio(g_devIdInp);\n                }\n            } else {\n                SDL_ClearQueuedAudio(g_devIdInp);\n            }\n        } else {\n            tLastNoData = tNow;\n        }\n    } else {\n        SDL_PauseAudioDevice(g_devIdOut, SDL_TRUE);\n        SDL_PauseAudioDevice(g_devIdInp, SDL_TRUE);\n\n        const auto nBytes = g_ggWave->encode();\n        SDL_QueueAudio(g_devIdOut, g_ggWave->txWaveform(), nBytes);\n    }\n\n    return true;\n}\n\nbool GGWave_deinit() {\n    if (g_devIdInp == 0 && g_devIdOut == 0) {\n        return false;\n    }\n\n    delete g_ggWave;\n    g_ggWave = nullptr;\n\n    SDL_PauseAudioDevice(g_devIdInp, 1);\n    SDL_CloseAudioDevice(g_devIdInp);\n    SDL_PauseAudioDevice(g_devIdOut, 1);\n    SDL_CloseAudioDevice(g_devIdOut);\n\n    g_devIdInp = 0;\n    g_devIdOut = 0;\n\n    return true;\n}\n\nint main(int argc, char** argv) {\n#ifdef __EMSCRIPTEN__\n    printf(\"Build time: %s\\n\", BUILD_TIMESTAMP);\n    printf(\"Press the Init button to start\\n\");\n\n    if (argv[1]) {\n        GGWave_setDefaultCaptureDeviceName(argv[1]);\n    }\n#else\n    printf(\"Usage: %s [-cN] [-lN]\\n\", argv[0]);\n    printf(\"    -cN - select capture device N\\n\");\n    printf(\"    -lN - fixed payload length of size N, N in [1, %d]\\n\", GGWave::kMaxLengthFixed);\n    printf(\"    -s  - use Direct Sequence Spread (DSS)\\n\");\n    printf(\"\\n\");\n#endif\n\n    GGWave::Protocols::rx() = { {\n        { \"[R2T2] Normal\",      64,  9, 1, 2, true, },\n        { \"[R2T2] Fast\",        64,  6, 1, 2, true, },\n        { \"[R2T2] Fastest\",     64,  3, 1, 2, true, },\n        { \"[R2T2] Low Normal\",  16,  9, 1, 2, true, },\n        { \"[R2T2] Low Fast\",    16,  6, 1, 2, true, },\n        { \"[R2T2] Low Fastest\", 16,  3, 1, 2, true, },\n    } };\n\n    const auto argm         = parseCmdArguments(argc, argv);\n    const int captureId     = argm.count(\"c\") == 0 ? 0 : std::stoi(argm.at(\"c\"));\n    const int payloadLength = argm.count(\"l\") == 0 ? 16 : std::stoi(argm.at(\"l\"));\n    const bool useDSS       = argm.count(\"s\") > 0;\n\n    bool isInitialized = false;\n\n    g_doInit = [&]() {\n        if (GGWave_init(0, captureId, payloadLength, 0, useDSS) == false) {\n            fprintf(stderr, \"Failed to initialize GGWave\\n\");\n            return false;\n        }\n\n        isInitialized = true;\n        printf(\"Listening for payload with length = %d bytes ..\\n\", payloadLength);\n\n        return true;\n    };\n\n    g_mainUpdate = [&]() {\n        if (isInitialized == false) {\n            return true;\n        }\n\n        GGWave_mainLoop();\n\n        return true;\n    };\n\n#ifdef __EMSCRIPTEN__\n    emscripten_set_main_loop_arg(mainUpdate, NULL, 60, true);\n#else\n    if (g_doInit() == false) {\n        printf(\"Error: failed to initialize audio\\n\");\n        return -2;\n    }\n\n    while (true) {\n        std::this_thread::sleep_for(std::chrono::milliseconds(1));\n        if (g_mainUpdate() == false) break;\n    }\n\n    GGWave_deinit();\n\n    // Cleanup\n    SDL_CloseAudio();\n    SDL_Quit();\n#endif\n\n    return 0;\n}\n"
  },
  {
    "path": "examples/r2t2/style.css",
    "content": "body {\n    margin: 0; background-color: white;\n    -webkit-font-smoothing: subpixel-antialiased;\n    font-smoothing: subpixel-antialiased;\n}\n#screen {\n    margin: 0;\n    padding: 0;\n    font-size: 13px;\n    height: 100%;\n    font: sans-serif;\n}\n.no-sel {\n    -moz-user-select: none;\n    -webkit-user-select: none;\n    -webkit-touch-callout: none;\n    -ms-user-select:none;\n    user-select:none;\n    -o-user-select:none;\n}\n.cell {\n    pointer-events: none;\n}\n.cell-version {\n    padding-left: 4px;\n    padding-top: 0.5em;\n    text-align: left;\n    display: inline-block;\n    float: left;\n    color: rgba(0, 0, 0, 0.75);\n}\n.cell-about {\n    padding-right: 24px;\n    padding-top: 0.5em;\n    text-align: right;\n    display: inline-block;\n    float: right;\n}\n.nav-link {\n    text-decoration: none;\n    color: rgba(0, 0, 0, 1.0);\n}\n\n#main-container {\n\tfont-size:12px;\n\tfont-family: monospace;\n}\n\ntextarea {\n\tfont-size:12px;\n\tfont-family: monospace;\n}\n\n.emscripten { padding-right: 0; margin-left: auto; margin-right: auto; display: block; }\ndiv.emscripten { text-align: center; }\ndiv.emscripten_border { border: 1px solid black; }\n\ncanvas.emscripten { border: 0px none; background-color: black; }\n\n.spinner {\n\theight: 30px;\n\twidth: 30px;\n\tmargin: 0;\n\tmargin-top: 20px;\n\tmargin-left: 20px;\n\tdisplay: inline-block;\n\tvertical-align: top;\n\n\t-webkit-animation: rotation .8s linear infinite;\n\t-moz-animation: rotation .8s linear infinite;\n\t-o-animation: rotation .8s linear infinite;\n\tanimation: rotation 0.8s linear infinite;\n\n\tborder-left: 5px solid rgb(235, 235, 235);\n\tborder-right: 5px solid rgb(235, 235, 235);\n\tborder-bottom: 5px solid rgb(235, 235, 235);\n\tborder-top: 5px solid rgb(120, 120, 120);\n\n\tborder-radius: 100%;\n\tbackground-color: rgb(189, 215, 46);\n}\n\n@-webkit-keyframes rotation {\n\tfrom {-webkit-transform: rotate(0deg);}\n\tto {-webkit-transform: rotate(360deg);}\n}\n@-moz-keyframes rotation {\n\tfrom {-moz-transform: rotate(0deg);}\n\tto {-moz-transform: rotate(360deg);}\n}\n@-o-keyframes rotation {\n\tfrom {-o-transform: rotate(0deg);}\n\tto {-o-transform: rotate(360deg);}\n}\n@keyframes rotation {\n\tfrom {transform: rotate(0deg);}\n\tto {transform: rotate(360deg);}\n}\n\n#status {\n\tdisplay: inline-block;\n\tvertical-align: top;\n\tmargin-top: 30px;\n\tmargin-left: 20px;\n\tfont-weight: bold;\n\tcolor: rgb(120, 120, 120);\n}\n\n#progress {\n\theight: 20px;\n\twidth: 30px;\n}\n\n#output {\n\twidth: 100%;\n\theight: 200px;\n\tmargin: 0 auto;\n\tmargin-top: 10px;\n\tborder-left: 0px;\n\tborder-right: 0px;\n\tpadding-left: 0px;\n\tpadding-right: 0px;\n\tbackground-color: black;\n\tcolor: white;\n\tfont-size:10px;\n\tfont-family: 'Lucida Console', Monaco, monospace;\n\toutline: none;\n}\n\n.led-box {\n\theight: 30px;\n\twidth: 25%;\n\tmargin: 10px 0;\n\tfloat: left;\n}\n\n.led-box p {\n\tfont-size: 12px;\n\ttext-align: center;\n\tmargin: 1em;\n}\n\n.led-red {\n\tmargin: 0 auto;\n\twidth: 12px;\n\theight: 12px;\n\tbackground-color: #F00;\n\tborder-radius: 50%;\n\tbox-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;\n\t-webkit-animation: blinkRed 0.5s infinite;\n\t-moz-animation: blinkRed 0.5s infinite;\n\t-ms-animation: blinkRed 0.5s infinite;\n\t-o-animation: blinkRed 0.5s infinite;\n\tanimation: blinkRed 0.5s infinite;\n}\n\n@-webkit-keyframes blinkRed {\n\tfrom { background-color: #F00; }\n\t50% { 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;}\n\tto { background-color: #F00; }\n}\n@-moz-keyframes blinkRed {\n\tfrom { background-color: #F00; }\n\t50% { 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;}\n\tto { background-color: #F00; }\n}\n@-ms-keyframes blinkRed {\n\tfrom { background-color: #F00; }\n\t50% { 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;}\n\tto { background-color: #F00; }\n}\n@-o-keyframes blinkRed {\n\tfrom { background-color: #F00; }\n\t50% { 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;}\n\tto { background-color: #F00; }\n}\n@keyframes blinkRed {\n\tfrom { background-color: #F00; }\n\t50% { 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;}\n\tto { background-color: #F00; }\n}\n\n.led-yellow {\n\tmargin: 0 auto;\n\twidth: 12px;\n\theight: 12px;\n\tbackground-color: #FF0;\n\tborder-radius: 50%;\n\tbox-shadow: rgba(0, 0, 0, 0.2) 0 -1px 7px 1px, inset #808002 0 -1px 9px, #FF0 0 2px 12px;\n\t-webkit-animation: blinkYellow 1s infinite;\n\t-moz-animation: blinkYellow 1s infinite;\n\t-ms-animation: blinkYellow 1s infinite;\n\t-o-animation: blinkYellow 1s infinite;\n\tanimation: blinkYellow 1s infinite;\n}\n\n@-webkit-keyframes blinkYellow {\n\tfrom { background-color: #FF0; }\n\t50% { background-color: #AA0; box-shadow: rgba(0, 0, 0, 0.2) 0 -1px 7px 1px, inset #808002 0 -1px 9px, #FF0 0 2px 0; }\n\tto { background-color: #FF0; }\n}\n@-moz-keyframes blinkYellow {\n\tfrom { background-color: #FF0; }\n\t50% { background-color: #AA0; box-shadow: rgba(0, 0, 0, 0.2) 0 -1px 7px 1px, inset #808002 0 -1px 9px, #FF0 0 2px 0; }\n\tto { background-color: #FF0; }\n}\n@-ms-keyframes blinkYellow {\n\tfrom { background-color: #FF0; }\n\t50% { background-color: #AA0; box-shadow: rgba(0, 0, 0, 0.2) 0 -1px 7px 1px, inset #808002 0 -1px 9px, #FF0 0 2px 0; }\n\tto { background-color: #FF0; }\n}\n@-o-keyframes blinkYellow {\n\tfrom { background-color: #FF0; }\n\t50% { background-color: #AA0; box-shadow: rgba(0, 0, 0, 0.2) 0 -1px 7px 1px, inset #808002 0 -1px 9px, #FF0 0 2px 0; }\n\tto { background-color: #FF0; }\n}\n@keyframes blinkYellow {\n\tfrom { background-color: #FF0; }\n\t50% { background-color: #AA0; box-shadow: rgba(0, 0, 0, 0.2) 0 -1px 7px 1px, inset #808002 0 -1px 9px, #FF0 0 2px 0; }\n\tto { background-color: #FF0; }\n}\n\n.led-green {\n\tmargin: 0 auto;\n\twidth: 12px;\n\theight: 12px;\n\tbackground-color: #ABFF00;\n\tborder-radius: 50%;\n\tbox-shadow: rgba(0, 0, 0, 0.2) 0 -1px 7px 1px, inset #304701 0 -1px 9px, #89FF00 0 2px 12px;\n}\n\n.led-blue {\n\tmargin: 0 auto;\n\twidth: 18px;\n\theight: 18px;\n\tbackground-color: #24E0FF;\n\tborder-radius: 50%;\n\tbox-shadow: rgba(0, 0, 0, 0.2) 0 -1px 7px 1px, inset #006 0 -1px 9px, #3F8CFF 0 2px 14px;\n}\n\ntable td {\n    border: 1px solid #e8e8e8;\n}\ntable th, table td {\n    padding: 10px 10px;\n}\ntable td {\n    border: 1px solid #e8e8e8;\n}\ntable th, table td {\n    padding: 10px 10px;\n}\ntd[Attributes Style] {\n    text-align: -webkit-center;\n}\ntd {\n    display: table-cell;\n    vertical-align: inherit;\n}\ntable {\n    margin-bottom: 30px;\n    width: 800px;\n    text-align: left;\n    color: #3f3f3f;\n    border-collapse: collapse;\n    border: 1px solid #e8e8e8;\n}\ntable {\n    margin-bottom: 30px;\n    width: 800px;\n    text-align: left;\n    color: #3f3f3f;\n    border-collapse: collapse;\n    border: 1px solid #e8e8e8;\n}\ntable {\n    border-collapse: separate;\n    border-spacing: 2px;\n}\n"
  },
  {
    "path": "examples/rp2040-rx/CMakeLists.txt",
    "content": "#\n# rp2040-rx\n"
  },
  {
    "path": "examples/rp2040-rx/README.md",
    "content": "# rp2040-rx\n\nThis is a sample project for receiving audio data using [RP2040](https://www.espressif.com/en/products/socs/esp32) microcontroller.\nThe chip has a built-in 12-bit ADC which is used to process the analog audio from the external microphone module in real-time.\n\n## Setup\n\n- Raspberry Pi Pico (or other RP2040 board)\n- Microphone, tested with the following, but others could be also supported:\n  - Analog:\n    - MAX9814\n    - KY-037\n    - KY-038\n    - WS Sound sensor\n\n## Pinout\n\n### Analog Microphone\n\n| MCU     | Mic       |\n| ------- | --------- |\n| GND     | GND       |\n| 3.3V    | VCC / VDD |\n| GPIO 26 | Out       |\n\n![Sketch-Breadboard](fritzing-sketch_bb.png)\n\n![1658510571716150](https://user-images.githubusercontent.com/1991296/180506853-01954beb-ccd4-4b71-ac20-232899d99abf.jpg)\n"
  },
  {
    "path": "examples/rp2040-rx/mic-analog.cpp",
    "content": "/*\n * Copyright (c) 2021 Arm Limited and Contributors. All rights reserved.\n *\n * SPDX-License-Identifier: Apache-2.0\n *\n */\n\n#include \"mic-analog.h\"\n\n#include \"hardware/adc.h\"\n#include \"hardware/clocks.h\"\n#include \"hardware/dma.h\"\n#include \"hardware/irq.h\"\n\n#include <string.h>\n#include <stdlib.h>\n\n#define ANALOG_RAW_BUFFER_COUNT 2\n\nstatic struct {\n    int       dma_channel;\n    uint16_t* raw_buffer[ANALOG_RAW_BUFFER_COUNT];\n    uint32_t  buffer_size;\n    int16_t   bias;\n    uint32_t  dma_irq;\n\n    volatile int raw_buffer_write_index;\n    volatile int raw_buffer_read_index;\n\n    analog_microphone_config config;\n    analog_samples_ready_handler_t samples_ready_handler;\n} analog_mic;\n\nstatic void analog_dma_handler();\n\nint analog_microphone_init(const struct analog_microphone_config* config) {\n    memset(&analog_mic, 0x00, sizeof(analog_mic));\n    memcpy(&analog_mic.config, config, sizeof(analog_mic.config));\n\n    if (config->gpio < 26 || config->gpio > 29) {\n        return -1;\n    }\n\n    size_t raw_buffer_size = config->sample_buffer_size * sizeof(analog_mic.raw_buffer[0][0]);\n\n    analog_mic.buffer_size = config->sample_buffer_size;\n    analog_mic.bias = ((int16_t)((config->bias_voltage * 4095) / 3.3));\n\n    for (int i = 0; i < ANALOG_RAW_BUFFER_COUNT; i++) {\n        analog_mic.raw_buffer[i] = (uint16_t* )malloc(raw_buffer_size);\n        if (analog_mic.raw_buffer[i] == NULL) {\n            analog_microphone_deinit();\n\n            return -1;\n        }\n    }\n\n    analog_mic.dma_channel = dma_claim_unused_channel(true);\n    if (analog_mic.dma_channel < 0) {\n        analog_microphone_deinit();\n\n        return -1;\n    }\n\n    float clk_div = (clock_get_hz(clk_adc) / (1.0 * config->sample_rate)) - 1;\n\n    dma_channel_config dma_channel_cfg = dma_channel_get_default_config(analog_mic.dma_channel);\n\n    channel_config_set_transfer_data_size(&dma_channel_cfg, DMA_SIZE_16);\n    channel_config_set_read_increment(&dma_channel_cfg, false);\n    channel_config_set_write_increment(&dma_channel_cfg, true);\n    channel_config_set_dreq(&dma_channel_cfg, DREQ_ADC);\n\n    analog_mic.dma_irq = DMA_IRQ_0;\n\n    dma_channel_configure(\n        analog_mic.dma_channel,\n        &dma_channel_cfg,\n        analog_mic.raw_buffer[0],\n        &adc_hw->fifo,\n        analog_mic.buffer_size,\n        false\n    );\n\n    adc_gpio_init(config->gpio);\n\n    adc_init();\n    adc_select_input(config->gpio - 26);\n    adc_fifo_setup(\n        true,    // Write each completed conversion to the sample FIFO\n        true,    // Enable DMA data request (DREQ)\n        1,       // DREQ (and IRQ) asserted when at least 1 sample present\n        false,   // We won't see the ERR bit because of 8 bit reads; disable.\n        false    // Don't shift each sample to 8 bits when pushing to FIFO\n    );\n\n    adc_set_clkdiv(clk_div);\n\n    return 0;\n}\n\nvoid analog_microphone_deinit() {\n    for (int i = 0; i < ANALOG_RAW_BUFFER_COUNT; i++) {\n        if (analog_mic.raw_buffer[i]) {\n            free(analog_mic.raw_buffer[i]);\n\n            analog_mic.raw_buffer[i] = NULL;\n        }\n    }\n\n    if (analog_mic.dma_channel > -1) {\n        dma_channel_unclaim(analog_mic.dma_channel);\n\n        analog_mic.dma_channel = -1;\n    }\n}\n\nint analog_microphone_start() {\n    irq_set_enabled(analog_mic.dma_irq, true);\n    irq_set_exclusive_handler(analog_mic.dma_irq, analog_dma_handler);\n\n    if (analog_mic.dma_irq == DMA_IRQ_0) {\n        dma_channel_set_irq0_enabled(analog_mic.dma_channel, true);\n    } else if (analog_mic.dma_irq == DMA_IRQ_1) {\n        dma_channel_set_irq1_enabled(analog_mic.dma_channel, true);\n    } else {\n        return -1;\n    }\n\n    analog_mic.raw_buffer_write_index = 0;\n    analog_mic.raw_buffer_read_index = 0;\n\n    dma_channel_transfer_to_buffer_now(\n        analog_mic.dma_channel,\n        analog_mic.raw_buffer[0],\n        analog_mic.buffer_size\n    );\n\n    adc_run(true); // start running the adc\n                   //\n    return 0;\n}\n\nvoid analog_microphone_stop() {\n    adc_run(false); // stop running the adc\n\n    dma_channel_abort(analog_mic.dma_channel);\n\n    if (analog_mic.dma_irq == DMA_IRQ_0) {\n        dma_channel_set_irq0_enabled(analog_mic.dma_channel, false);\n    } else if (analog_mic.dma_irq == DMA_IRQ_1) {\n        dma_channel_set_irq1_enabled(analog_mic.dma_channel, false);\n    }\n\n    irq_set_enabled(analog_mic.dma_irq, false);\n}\n\nstatic void analog_dma_handler() {\n    // clear IRQ\n    if (analog_mic.dma_irq == DMA_IRQ_0) {\n        dma_hw->ints0 = (1u << analog_mic.dma_channel);\n    } else if (analog_mic.dma_irq == DMA_IRQ_1) {\n        dma_hw->ints1 = (1u << analog_mic.dma_channel);\n    }\n\n    // get the current buffer index\n    analog_mic.raw_buffer_read_index = analog_mic.raw_buffer_write_index;\n\n    // get the next capture index to send the dma to start\n    analog_mic.raw_buffer_write_index = (analog_mic.raw_buffer_write_index + 1) % ANALOG_RAW_BUFFER_COUNT;\n\n    // give the channel a new buffer to write to and re-trigger it\n    dma_channel_transfer_to_buffer_now(\n        analog_mic.dma_channel,\n        analog_mic.raw_buffer[analog_mic.raw_buffer_write_index],\n        analog_mic.buffer_size\n    );\n\n    if (analog_mic.samples_ready_handler) {\n        analog_mic.samples_ready_handler();\n    }\n}\n\nvoid analog_microphone_set_samples_ready_handler(analog_samples_ready_handler_t handler) {\n    analog_mic.samples_ready_handler = handler;\n}\n\nint analog_microphone_read(int16_t* buffer, size_t samples) {\n    if (samples > analog_mic.config.sample_buffer_size) {\n        samples = analog_mic.config.sample_buffer_size;\n    }\n\n    if (analog_mic.raw_buffer_write_index == analog_mic.raw_buffer_read_index) {\n        return 0;\n    }\n\n    uint16_t* in = analog_mic.raw_buffer[analog_mic.raw_buffer_read_index];\n    int16_t* out = buffer;\n    int16_t bias = analog_mic.bias;\n\n    analog_mic.raw_buffer_read_index++;\n\n    for (int i = 0; i < samples; i++) {\n        *out++ = *in++ - bias;\n    }\n\n    return samples;\n}\n"
  },
  {
    "path": "examples/rp2040-rx/mic-analog.h",
    "content": "/*\n * Copyright (c) 2021 Arm Limited and Contributors. All rights reserved.\n *\n * SPDX-License-Identifier: Apache-2.0\n *\n */\n\n#ifndef _PICO_ANALOG_MICROPHONE_H_\n#define _PICO_ANALOG_MICROPHONE_H_\n\n#include <stdint.h>\n#include <stddef.h>\n\ntypedef void (*analog_samples_ready_handler_t)(void);\n\nstruct analog_microphone_config {\n    uint32_t gpio;\n    float    bias_voltage;\n    uint32_t sample_rate;\n    uint32_t sample_buffer_size;\n};\n\nint analog_microphone_init(const struct analog_microphone_config* config);\nvoid analog_microphone_deinit();\n\nint analog_microphone_start();\nvoid analog_microphone_stop();\n\nvoid analog_microphone_set_samples_ready_handler(analog_samples_ready_handler_t handler);\n\nint analog_microphone_read(int16_t* buffer, size_t samples);\n\n#endif\n"
  },
  {
    "path": "examples/rp2040-rx/rp2040-rx.ino",
    "content": "// rp2040-rx\n//\n// Sample sketch for receiving sound data using \"ggwave\"\n//\n// Tested MCU boards:\n//   - Raspberry Pi Pico\n//   - Arduino Nano RP2040 Connect\n//\n// Tested analog microphones:\n//   - MAX9814\n//   - KY-037\n//   - KY-038\n//   - WS Sound sensor\n//\n// The RP2040 microcontroller has a built-in 12-bit ADC which is used to digitalize the analog signal\n// from the external analog microphone. The MCU supports sampling rates up to 500kHz which makes it\n// capable of even recording audio in the ultrasound range, given that your microphone's sensitivity\n// supports it.\n//\n// If you want to perform a quick test, you can use the free \"Waver\" application:\n//   - Web:     https://waver.ggerganov.com\n//   - Android: https://play.google.com/store/apps/details?id=com.ggerganov.Waver\n//   - iOS:     https://apps.apple.com/us/app/waver-data-over-sound/id1543607865\n//\n// Make sure to enable the \"Fixed-length\" option in \"Waver\"'s settings and set the number of\n// bytes to be equal to \"payloadLength\" used in the sketch. Also, select a protocol that is\n// listed as Rx in the current sketch.\n//\n// Sketch: https://github.com/ggerganov/ggwave/tree/master/examples/rp2040-rx\n//\n// ## Pinout\n//\n// ### Analog Microphone\n//\n// | MCU     | Mic       |\n// | ------- | --------- |\n// | GND     | GND       |\n// | 3.3V    | VCC / VDD |\n// | GPIO 26 | Out       |\n//\n\n// Uncomment the line coresponding to your microhpone\n#define MIC_ANALOG\n\n// Uncoment this line to enable long-range transmission\n// The used protocols are slower and use more memory to decode, but are much more robust\n//#define LONG_RANGE 1\n\n#include <ggwave.h>\n\n// Audio capture configuration\nusing TSample = int16_t;\n\nconst size_t kSampleSize_bytes = sizeof(TSample);\n\n// High sample rate - better quality, but more CPU/Memory usage\nconst int sampleRate = 48000;\nconst int samplesPerFrame = 1024;\n\n// Low sample rate\n//const int sampleRate = 24000;\n//const int samplesPerFrame = 512;\n\nTSample sampleBuffer[samplesPerFrame];\n\n#if defined(MIC_ANALOG)\n\n#include \"mic-analog.h\"\n\nvolatile int samplesRead = 0;\n\nconst struct analog_microphone_config config = {\n    // GPIO to use for input, must be ADC compatible (GPIO 26 - 28)\n    .gpio = 26,\n\n    // bias voltage of microphone in volts\n    .bias_voltage = 1.25,\n\n    // sample rate in Hz\n    .sample_rate = sampleRate,\n\n    // number of samples to buffer\n    .sample_buffer_size = samplesPerFrame,\n};\n\nvoid on_analog_samples_ready()\n{\n    // callback from library when all the samples in the library\n    // internal sample buffer are ready for reading\n    samplesRead = analog_microphone_read(sampleBuffer, samplesPerFrame);\n}\n\n#endif\n\n// Global GGwave instance\nGGWave ggwave;\n\nvoid setup() {\n    Serial.begin(115200);\n    while (!Serial);\n\n    // Initialize \"ggwave\"\n    {\n        Serial.println(F(\"Trying to initialize the ggwave instance\"));\n\n        ggwave.setLogFile(nullptr);\n\n        auto p = GGWave::getDefaultParameters();\n\n        // Adjust the \"ggwave\" parameters to your needs.\n        // Make sure that the \"payloadLength\" parameter matches the one used on the transmitting side.\n#ifdef LONG_RANGE\n        // The \"FAST\" protocols require 2x more memory, so we reduce the payload length to compensate:\n        p.payloadLength   = 8;\n#else\n        p.payloadLength   = 16;\n#endif\n        Serial.print(F(\"Using payload length: \"));\n        Serial.println(p.payloadLength);\n\n        p.sampleRateInp   = sampleRate;\n        p.sampleRateOut   = sampleRate;\n        p.sampleRate      = sampleRate;\n        p.samplesPerFrame = samplesPerFrame;\n        p.sampleFormatInp = GGWAVE_SAMPLE_FORMAT_I16;\n        p.sampleFormatOut = GGWAVE_SAMPLE_FORMAT_U8;\n        p.operatingMode   = GGWAVE_OPERATING_MODE_RX | GGWAVE_OPERATING_MODE_TX | GGWAVE_OPERATING_MODE_USE_DSS | GGWAVE_OPERATING_MODE_TX_ONLY_TONES;\n\n        // Protocols to use for TX\n        // Remove the ones that you don't need to reduce memory usage\n        GGWave::Protocols::tx().disableAll();\n        //GGWave::Protocols::tx().toggle(GGWAVE_PROTOCOL_MT_NORMAL,  true);\n        //GGWave::Protocols::tx().toggle(GGWAVE_PROTOCOL_MT_FAST,    true);\n        GGWave::Protocols::tx().toggle(GGWAVE_PROTOCOL_MT_FASTEST, true);\n\n        // Protocols to use for RX\n        // Remove the ones that you don't need to reduce memory and CPU usage\n        GGWave::Protocols::rx().disableAll();\n\n        //GGWave::Protocols::rx().toggle(GGWAVE_PROTOCOL_AUDIBLE_NORMAL, true);\n        //GGWave::Protocols::rx().toggle(GGWAVE_PROTOCOL_DT_NORMAL,      true);\n        //GGWave::Protocols::rx().toggle(GGWAVE_PROTOCOL_MT_NORMAL,      true);\n\n        //GGWave::Protocols::rx().toggle(GGWAVE_PROTOCOL_AUDIBLE_NORMAL, true);\n        //GGWave::Protocols::rx().toggle(GGWAVE_PROTOCOL_DT_NORMAL,      true);\n        //GGWave::Protocols::rx().toggle(GGWAVE_PROTOCOL_MT_NORMAL,      true);\n\n        //GGWave::Protocols::rx().toggle(GGWAVE_PROTOCOL_ULTRASOUND_NORMAL,  true);\n        //GGWave::Protocols::rx().toggle(GGWAVE_PROTOCOL_ULTRASOUND_FAST,    true);\n        //GGWave::Protocols::rx().toggle(GGWAVE_PROTOCOL_ULTRASOUND_FASTEST, true);\n\n#ifdef LONG_RANGE\n        GGWave::Protocols::rx().toggle(GGWAVE_PROTOCOL_AUDIBLE_FAST, true);\n        GGWave::Protocols::rx().toggle(GGWAVE_PROTOCOL_DT_FAST,      true);\n        GGWave::Protocols::rx().toggle(GGWAVE_PROTOCOL_MT_FAST,      true);\n#endif\n\n        GGWave::Protocols::rx().toggle(GGWAVE_PROTOCOL_AUDIBLE_FASTEST, true);\n        GGWave::Protocols::rx().toggle(GGWAVE_PROTOCOL_DT_FASTEST,      true);\n        GGWave::Protocols::rx().toggle(GGWAVE_PROTOCOL_MT_FASTEST,      true);\n\n        // Print the memory required for the \"ggwave\" instance:\n        ggwave.prepare(p, false);\n\n        Serial.print(F(\"Required memory by the ggwave instance: \"));\n        Serial.print(ggwave.heapSize());\n        Serial.println(F(\" bytes\"));\n\n        // Initialize the \"ggwave\" instance:\n        ggwave.prepare(p, true);\n        Serial.print(F(\"Instance initialized successfully! Memory used: \"));\n    }\n\n    // initialize the analog microphone\n    if (analog_microphone_init(&config) < 0) {\n        Serial.println(F(\"analog microphone initialization failed!\"));\n        while (1) { tight_loop_contents(); }\n    }\n\n    // set callback that is called when all the samples in the library\n    // internal sample buffer are ready for reading\n    analog_microphone_set_samples_ready_handler(on_analog_samples_ready);\n\n    // start capturing data from the analog microphone\n    if (analog_microphone_start() < 0) {\n        Serial.println(F(\"Analog microphone start failed!\"));\n        while (1) { tight_loop_contents();  }\n    }\n\n    Serial.println(F(\"setup() done\"));\n}\n\nint niter = 0;\nint tLastReceive = -10000;\n\nGGWave::TxRxData result;\n\nvoid loop() {\n    // wait for new samples\n    while (samplesRead == 0) { tight_loop_contents(); }\n\n    // store and clear the samples read from the callback\n    int nSamples = samplesRead;\n    samplesRead = 0;\n\n    // Use this with the serial plotter to observe real-time audio signal\n    //for (int i = 0; i < nSamples; i++) {\n    //    Serial.printf(\"%d\\n\", sampleBuffer[i]);\n    //}\n\n    // Try to decode any \"ggwave\" data:\n    auto tStart = millis();\n\n    if (ggwave.decode(sampleBuffer, samplesPerFrame*kSampleSize_bytes) == false) {\n        Serial.println(\"Failed to decode\");\n    }\n\n    auto tEnd = millis();\n\n    if (++niter % 10 == 0) {\n        // print the time it took the last decode() call to complete\n        // should be smaller than samplesPerFrame/sampleRate seconds\n        // for example: samplesPerFrame = 128, sampleRate = 6000 => not more than 20 ms\n        Serial.println(tEnd - tStart);\n        if (tEnd - tStart > 1000*(float(samplesPerFrame)/sampleRate)) {\n            Serial.println(F(\"Warning: decode() took too long to execute!\"));\n        }\n    }\n\n    // Check if we have successfully decoded any data:\n    int nr = ggwave.rxTakeData(result);\n    if (nr > 0) {\n        Serial.println(tEnd - tStart);\n        Serial.print(F(\"Received data with length \"));\n        Serial.print(nr); // should be equal to p.payloadLength\n        Serial.println(F(\" bytes:\"));\n\n        Serial.println((char *) result.data());\n\n        tLastReceive = tEnd;\n    }\n}\n"
  },
  {
    "path": "examples/spectrogram/CMakeLists.txt",
    "content": "set(TARGET spectrogram)\n\nif (EMSCRIPTEN)\n    add_executable(${TARGET} main.cpp)\n\n    target_include_directories(${TARGET} PRIVATE\n        ..\n        ${SDL2_INCLUDE_DIRS}\n        ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${TARGET}/\n        )\n\n    target_link_libraries(${TARGET} PRIVATE\n        ggwave\n        ggwave-common\n        ggwave-common-sdl2\n        ggsock\n        imgui-sdl2\n        ${CMAKE_THREAD_LIBS_INIT}\n        )\n\n    set_target_properties(${TARGET} PROPERTIES LINK_FLAGS \" \\\n        -s FORCE_FILESYSTEM=1 \\\n        --preload-file ${CMAKE_CURRENT_SOURCE_DIR}/../assets/fonts@/ \\\n        \")\n\n    configure_file(${CMAKE_CURRENT_SOURCE_DIR}/build_timestamp-tmpl.h ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${TARGET}/build_timestamp.h @ONLY)\n    configure_file(${CMAKE_CURRENT_SOURCE_DIR}/index-tmpl.html ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${TARGET}/index.html @ONLY)\n    configure_file(${CMAKE_CURRENT_SOURCE_DIR}/style.css ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${TARGET}/style.css COPYONLY)\nelse()\n    add_executable(${TARGET} main.cpp)\n\n    target_include_directories(${TARGET} PRIVATE\n        ..\n        ${SDL2_INCLUDE_DIRS}\n        )\n\n    target_link_libraries(${TARGET} PRIVATE\n        ggwave\n        ggwave-common\n        ggwave-common-sdl2\n        imgui-sdl2\n        ${CMAKE_THREAD_LIBS_INIT}\n        )\nendif()\n"
  },
  {
    "path": "examples/spectrogram/README.md",
    "content": "# spectrogram\n\nReal-time audio spectrum visualizer\n\nWASM port: https://spectrogram.ggerganov.com\n\n![image](https://user-images.githubusercontent.com/1991296/109423222-50817600-79e7-11eb-9373-0e50aaf6376b.png)\n"
  },
  {
    "path": "examples/spectrogram/build_timestamp-tmpl.h",
    "content": "static const char * BUILD_TIMESTAMP=\"@GIT_DATE@ (@GIT_SHA1@)\";\n"
  },
  {
    "path": "examples/spectrogram/index-tmpl.html",
    "content": "<!doctype html>\n<html lang=\"en-us\">\n    <head>\n        <meta charset=\"utf-8\">\n        <title>Spectrogram</title>\n\n        <meta name=\"viewport\" content=\"width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no\"/>\n\n        <meta name=\"twitter:card\" content=\"summary_large_image\">\n        <meta name=\"twitter:title\" content=\"Spectrogram\" />\n        <meta name=\"twitter:description\" content=\"Spectrogram\" />\n        <meta name=\"twitter:image\" content=\"https://spectrogram.ggerganov.com/spectrogram.png\" />\n\n        <link rel=\"apple-touch-icon\" sizes=\"57x57\" href=\"/apple-icon-57x57.png\">\n        <link rel=\"apple-touch-icon\" sizes=\"60x60\" href=\"/apple-icon-60x60.png\">\n        <link rel=\"apple-touch-icon\" sizes=\"72x72\" href=\"/apple-icon-72x72.png\">\n        <link rel=\"apple-touch-icon\" sizes=\"76x76\" href=\"/apple-icon-76x76.png\">\n        <link rel=\"apple-touch-icon\" sizes=\"114x114\" href=\"/apple-icon-114x114.png\">\n        <link rel=\"apple-touch-icon\" sizes=\"120x120\" href=\"/apple-icon-120x120.png\">\n        <link rel=\"apple-touch-icon\" sizes=\"144x144\" href=\"/apple-icon-144x144.png\">\n        <link rel=\"apple-touch-icon\" sizes=\"152x152\" href=\"/apple-icon-152x152.png\">\n        <link rel=\"apple-touch-icon\" sizes=\"180x180\" href=\"/apple-icon-180x180.png\">\n        <link rel=\"icon\" type=\"image/png\" sizes=\"192x192\"  href=\"/android-icon-192x192.png\">\n        <link rel=\"icon\" type=\"image/png\" sizes=\"32x32\" href=\"/favicon-32x32.png\">\n        <link rel=\"icon\" type=\"image/png\" sizes=\"96x96\" href=\"/favicon-96x96.png\">\n        <link rel=\"icon\" type=\"image/png\" sizes=\"16x16\" href=\"/favicon-16x16.png\">\n        <link rel=\"manifest\" href=\"/manifest.json\">\n        <meta name=\"msapplication-TileColor\" content=\"#ffffff\">\n        <meta name=\"msapplication-TileImage\" content=\"/ms-icon-144x144.png\">\n        <meta name=\"theme-color\" content=\"#ffffff\">\n\n        <link rel=\"stylesheet\" href=\"style.css\">\n    </head>\n    <body>\n        <div id=\"main-controls\">\n            <div id=\"description\" align=\"center\">\n                <h2>Spectrogram</h2>\n\n                <div id=\"container_status\" align=\"center\">\n                    Loading WebAssembly module - please wait ...\n                </div>\n                <div id=\"container_button\" align=\"center\">\n                    <br>\n                    <button onClick=\"doInit()\" id=\"butInit\" style=\"width:60px;height:30px;\" disabled>Init</button>\n                </div>\n            </div>\n            <div id=\"container_canvas\" hidden>\n                <canvas class=\"emscripten\" id=\"canvas\" oncontextmenu=\"event.preventDefault()\"></canvas>\n            </div>\n        </div>\n\n        <div id=\"footer\" class=\"cell-version\">\n            <span>\n                Build time: <span class=\"nav-link\">@GIT_DATE@</span> |\n                Commit hash: <a class=\"nav-link\" href=\"https://github.com/ggerganov/ggwave/commit/@GIT_SHA1@\">@GIT_SHA1@</a> |\n                Commit subject: <span class=\"nav-link\">@GIT_COMMIT_SUBJECT@</span> |\n            </span>\n        </div>\n        <div class=\"cell-about\">\n            <a class=\"nav-link\" href=\"https://github.com/ggerganov/ggwave/tree/master/examples/spectrogram\"><span class=\"d-none d-sm-inline\">View on GitHub </span>\n                <svg version=\"1.1\" width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" class=\"octicon octicon-mark-github\" aria-hidden=\"true\"><path fill-rule=\"evenodd\" d=\"M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0 0 16 8c0-4.42-3.58-8-8-8z\"></path></svg>\n            </a>\n        </div>\n\n        <script type='text/javascript'>\n            window.mobilecheck = function() {\n                var check = false;\n                (function(a){if(/(android|bb\\d+|meego).+mobile|avantgo|bada\\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\\.(browser|link)|vodafone|wap|windows ce|xda|xiino/i.test(a)||/1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\\-(n|u)|c55\\/|capi|ccwa|cdm\\-|cell|chtm|cldc|cmd\\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\\-s|devi|dica|dmob|do(c|p)o|ds(12|\\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\\-|_)|g1 u|g560|gene|gf\\-5|g\\-mo|go(\\.w|od)|gr(ad|un)|haie|hcit|hd\\-(m|p|t)|hei\\-|hi(pt|ta)|hp( i|ip)|hs\\-c|ht(c(\\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\\-(20|go|ma)|i230|iac( |\\-|\\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\\/)|klon|kpt |kwc\\-|kyo(c|k)|le(no|xi)|lg( g|\\/(k|l|u)|50|54|\\-[a-w])|libw|lynx|m1\\-w|m3ga|m50\\/|ma(te|ui|xo)|mc(01|21|ca)|m\\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\\-2|po(ck|rt|se)|prox|psio|pt\\-g|qa\\-a|qc(07|12|21|32|60|\\-[2-7]|i\\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\\-|oo|p\\-)|sdk\\/|se(c(\\-|0|1)|47|mc|nd|ri)|sgh\\-|shar|sie(\\-|m)|sk\\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\\-|v\\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\\-|tdg\\-|tel(i|m)|tim\\-|t\\-mo|to(pl|sh)|ts(70|m\\-|m3|m5)|tx\\-9|up(\\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\\-|your|zeto|zte\\-/i.test(a.substr(0,4))) check = true;})(navigator.userAgent||navigator.vendor||window.opera);\n                return check;\n            };\n\n            var isMobile = mobilecheck();\n            var isInitialized = false;\n\n            window.setInterval(function(){\n                    if (isInitialized == false) return;\n                    var w = window,\n                        d = document,\n                        e = d.documentElement,\n                        g = d.getElementsByTagName('body')[0],\n                        x = w.innerWidth || e.clientWidth || g.clientWidth,\n                        y = w.innerHeight|| e.clientHeight|| g.clientHeight;\n                    Module._set_window_size(0.99*x, y - 1.40*document.getElementById('footer').clientHeight);\n            }, 500);\n\n            function checkLoop() {\n                setTimeout(checkLoop, 100);\n            }\n\n            function checkSharedArrayBuffer() {\n                return true;\n                try {\n                    var a = SharedArrayBuffer;\n                } catch (e) {\n                    console.log(e instanceof ReferenceError); // true\n                    return false;\n                }\n\n                return true;\n            }\n\n            function onkeydown(event) {\n                if (event.keyCode >= 112 && event.keyCode <= 123) {\n                    event.stopImmediatePropagation();\n                }\n            }\n\n            function init() {\n                document.getElementById('container_status').innerHTML = \"WebAssembly module initialized successfully! Press the Init button to start:\"\n                document.getElementById('container_status').style.color = \"#00ff00\";\n\n                document.getElementById(\"butInit\").disabled = false;\n\n                window.addEventListener('keydown', onkeydown, true);\n\n                setTimeout(checkLoop, 100);\n            }\n\n            function doInit() {\n                let constraints = {\n                    audio: {\n                        sampleRate: 16000,\n                        channelCount: 1,\n                        echoCancellation: false,\n                        autoGainControl: false,\n                        noiseSuppression: false\n                    }\n                };\n\n                if (isInitialized == false) {\n                    Module._do_init();\n                    var el = document.getElementById(\"container_status\");\n                    el.parentNode.removeChild(el);\n                    el = document.getElementById(\"container_button\");\n                    el.parentNode.removeChild(el);\n                    isInitialized = true;\n                }\n\n                document.getElementById(\"description\").hidden = true;\n                document.getElementById(\"container_canvas\").hidden = false;\n            }\n\n            var Module = {\n                arguments: [],\n                preRun: [(function() {\n                }) ],\n                postRun: [(function () {\n                    init();\n                })\n                ],\n                canvas: (function() {\n                    var canvas = document.getElementById('canvas');\n                    canvas.addEventListener(\"webglcontextlost\", function(e) { alert('WebGL context lost. You will need to reload the page.'); e.preventDefault(); }, false);\n\n                    return canvas;\n                })(),\n                print: (function() {\n                    return function(text) {\n                        text = Array.prototype.slice.call(arguments).join(' ');\n                        console.log(text);\n                    };\n                })(),\n                printErr: function(text) {\n                    text = Array.prototype.slice.call(arguments).join(' ');\n                    console.error(text);\n                },\n                setStatus: function(text) {\n                    console.log(\"status: \" + text);\n                },\n                monitorRunDependencies: function(left) {\n                    // no run dependencies to log\n                }\n            };\n            window.onerror = function() {\n                console.log(\"onerror: \" + JSON.stringify(event));\n                if (checkSharedArrayBuffer() == false) {\n                    document.getElementById('container_status').innerHTML = \"Failed to load the WebAssembly module.<br><br>Try openning this page on a PC with latest Chrome browser.\";\n                    document.getElementById('container_status').style.color = \"#ff0000\";\n                    document.getElementById('container_button').hidden = true;\n                }\n            };\n\n        </script>\n\n        <script async type=\"text/javascript\" src=\"spectrogram.js\"></script>\n    </body>\n</html>\n"
  },
  {
    "path": "examples/spectrogram/main.cpp",
    "content": "#include \"ggwave/ggwave.h\"\n#include \"ggwave-common.h\"\n\n#ifdef __EMSCRIPTEN__\n#include \"build_timestamp.h\"\n#include \"emscripten/emscripten.h\"\n#else\n#define EMSCRIPTEN_KEEPALIVE\n#endif\n\n#include <imgui-extra/imgui_impl.h>\n\n#include <SDL.h>\n#include <SDL_opengl.h>\n\n#include <cmath>\n#include <fstream>\n#include <vector>\n#include <functional>\n\nnamespace {\n\nstd::string g_defaultCaptureDeviceName = \"\";\n\nSDL_AudioDeviceID g_devIdInp = 0;\nSDL_AudioDeviceID g_devIdOut = 0;\n\nSDL_AudioSpec g_obtainedSpecInp;\nSDL_AudioSpec g_obtainedSpecOut;\n\nstruct FreqData {\n    float freq;\n\n    std::vector<float> mag;\n};\n\nbool g_isCapturing = true;\nconstexpr int g_nSamplesPerFrame = 1024;\nconstexpr int g_nBins = g_nSamplesPerFrame/2;\n\nint g_binMin = 0;\nint g_binMax = g_nBins;\n\nfloat g_scale = 30.0;\n\nbool g_filter0 = false;\nbool g_filter1 = false;\nbool g_filter2 = false;\n\nbool g_showControls = true;\n\nint g_freqDataHead = 0;\nint g_freqDataSize = 0;\nstd::vector<FreqData> g_freqData;\n\nfloat g_sampleRateOffset = 0;\n\n}\n\nvoid GGWave_setDefaultCaptureDeviceName(std::string name) {\n    g_defaultCaptureDeviceName = std::move(name);\n}\n\nbool GGWave_init(\n        const int playbackId,\n        const int captureId) {\n\n    if (g_devIdInp && g_devIdOut) {\n        return false;\n    }\n\n    if (g_devIdInp == 0 && g_devIdOut == 0) {\n        SDL_LogSetPriority(SDL_LOG_CATEGORY_APPLICATION, SDL_LOG_PRIORITY_INFO);\n\n        if (SDL_Init(SDL_INIT_AUDIO) < 0) {\n            SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, \"Couldn't initialize SDL: %s\\n\", SDL_GetError());\n            return (1);\n        }\n\n        SDL_SetHintWithPriority(SDL_HINT_AUDIO_RESAMPLING_MODE, \"medium\", SDL_HINT_OVERRIDE);\n\n        {\n            int nDevices = SDL_GetNumAudioDevices(SDL_FALSE);\n            printf(\"Found %d playback devices:\\n\", nDevices);\n            for (int i = 0; i < nDevices; i++) {\n                printf(\"    - Playback device #%d: '%s'\\n\", i, SDL_GetAudioDeviceName(i, SDL_FALSE));\n            }\n        }\n        {\n            int nDevices = SDL_GetNumAudioDevices(SDL_TRUE);\n            printf(\"Found %d capture devices:\\n\", nDevices);\n            for (int i = 0; i < nDevices; i++) {\n                printf(\"    - Capture device #%d: '%s'\\n\", i, SDL_GetAudioDeviceName(i, SDL_TRUE));\n            }\n        }\n    }\n\n    if (g_devIdOut == 0) {\n        printf(\"Initializing playback ...\\n\");\n\n        SDL_AudioSpec playbackSpec;\n        SDL_zero(playbackSpec);\n\n        playbackSpec.freq = GGWave::kDefaultSampleRate + g_sampleRateOffset;\n        playbackSpec.format = AUDIO_S16SYS;\n        playbackSpec.channels = 1;\n        playbackSpec.samples = 16*1024;\n        playbackSpec.callback = NULL;\n\n        SDL_zero(g_obtainedSpecOut);\n\n        if (playbackId >= 0) {\n            printf(\"Attempt to open playback device %d : '%s' ...\\n\", playbackId, SDL_GetAudioDeviceName(playbackId, SDL_FALSE));\n            g_devIdOut = SDL_OpenAudioDevice(SDL_GetAudioDeviceName(playbackId, SDL_FALSE), SDL_FALSE, &playbackSpec, &g_obtainedSpecOut, 0);\n        } else {\n            printf(\"Attempt to open default playback device ...\\n\");\n            g_devIdOut = SDL_OpenAudioDevice(NULL, SDL_FALSE, &playbackSpec, &g_obtainedSpecOut, 0);\n        }\n\n        if (!g_devIdOut) {\n            printf(\"Couldn't open an audio device for playback: %s!\\n\", SDL_GetError());\n            g_devIdOut = 0;\n        } else {\n            printf(\"Obtained spec for output device (SDL Id = %d):\\n\", g_devIdOut);\n            printf(\"    - Sample rate:       %d (required: %d)\\n\", g_obtainedSpecOut.freq, playbackSpec.freq);\n            printf(\"    - Format:            %d (required: %d)\\n\", g_obtainedSpecOut.format, playbackSpec.format);\n            printf(\"    - Channels:          %d (required: %d)\\n\", g_obtainedSpecOut.channels, playbackSpec.channels);\n            printf(\"    - Samples per frame: %d (required: %d)\\n\", g_obtainedSpecOut.samples, playbackSpec.samples);\n\n            if (g_obtainedSpecOut.format != playbackSpec.format ||\n                g_obtainedSpecOut.channels != playbackSpec.channels ||\n                g_obtainedSpecOut.samples != playbackSpec.samples) {\n                g_devIdOut = 0;\n                SDL_CloseAudio();\n                fprintf(stderr, \"Failed to initialize playback SDL_OpenAudio!\");\n\n                return false;\n            }\n        }\n    }\n\n    if (g_devIdInp == 0) {\n        SDL_AudioSpec captureSpec;\n        captureSpec = g_obtainedSpecOut;\n        captureSpec.freq = GGWave::kDefaultSampleRate + g_sampleRateOffset;\n        captureSpec.format = AUDIO_F32SYS;\n        captureSpec.samples = g_nSamplesPerFrame;\n\n        SDL_zero(g_obtainedSpecInp);\n\n        if (captureId >= 0) {\n            printf(\"Attempt to open capture device %d : '%s' ...\\n\", captureId, SDL_GetAudioDeviceName(captureId, SDL_FALSE));\n            g_devIdInp = SDL_OpenAudioDevice(SDL_GetAudioDeviceName(captureId, SDL_TRUE), SDL_TRUE, &captureSpec, &g_obtainedSpecInp, 0);\n        } else {\n            printf(\"Attempt to open default capture device ...\\n\");\n            g_devIdInp = SDL_OpenAudioDevice(g_defaultCaptureDeviceName.empty() ? nullptr : g_defaultCaptureDeviceName.c_str(),\n                                            SDL_TRUE, &captureSpec, &g_obtainedSpecInp, 0);\n        }\n        if (!g_devIdInp) {\n            printf(\"Couldn't open an audio device for capture: %s!\\n\", SDL_GetError());\n            g_devIdInp = 0;\n        } else {\n            printf(\"Obtained spec for input device (SDL Id = %d):\\n\", g_devIdInp);\n            printf(\"    - Sample rate:       %d\\n\", g_obtainedSpecInp.freq);\n            printf(\"    - Format:            %d (required: %d)\\n\", g_obtainedSpecInp.format, captureSpec.format);\n            printf(\"    - Channels:          %d (required: %d)\\n\", g_obtainedSpecInp.channels, captureSpec.channels);\n            printf(\"    - Samples per frame: %d\\n\", g_obtainedSpecInp.samples);\n        }\n    }\n\n    return true;\n}\n\nbool GGWave_mainLoop() {\n    if (g_devIdInp == 0 && g_devIdOut == 0) {\n        return false;\n    }\n\n    SDL_PauseAudioDevice(g_devIdInp, SDL_FALSE);\n    if (!g_isCapturing) {\n        SDL_ClearQueuedAudio(g_devIdInp);\n    }\n\n    static bool isInitialzed = false;\n\n    static float data[g_nSamplesPerFrame];\n    static float out [2*g_nSamplesPerFrame];\n\n    static int   workI[2*g_nSamplesPerFrame];\n    static float workF[g_nSamplesPerFrame/2];\n\n    static float workF0[g_nSamplesPerFrame];\n    static float workF1[g_nSamplesPerFrame];\n    static float workF2[11];\n\n    if (!isInitialzed) {\n        memset(data, 0, sizeof(data));\n        memset(out,  0, sizeof(out));\n\n        memset(workI, 0, sizeof(workI));\n        memset(workF, 0, sizeof(workF));\n\n        memset(workF0, 0, sizeof(workF0));\n        memset(workF1, 0, sizeof(workF1));\n        memset(workF2, 0, sizeof(workF2));\n\n        isInitialzed = true;\n    }\n\n    int n = 0;\n\n    do {\n        n = SDL_DequeueAudio(g_devIdInp, data, sizeof(float)*g_nSamplesPerFrame);\n        if (n <= 0) break;\n\n        if (g_filter2) {\n            GGWave::filter(GGWAVE_FILTER_FIRST_ORDER_HIGH_PASS, data, g_nSamplesPerFrame, 250.0f, GGWave::kDefaultSampleRate, workF2);\n        }\n\n        if (g_filter0) {\n            GGWave::filter(GGWAVE_FILTER_HANN, data, g_nSamplesPerFrame, 250.0f, GGWave::kDefaultSampleRate, workF0);\n        }\n\n        if (g_filter1) {\n            GGWave::filter(GGWAVE_FILTER_HAMMING, data, g_nSamplesPerFrame, 250.0f, GGWave::kDefaultSampleRate, workF1);\n        }\n\n        if (GGWave::computeFFTR(data, out, g_nSamplesPerFrame, workI, workF) == false) {\n            fprintf(stderr, \"Failed to compute FFT!\\n\");\n            return false;\n        }\n\n        for (int i = 0; i < g_nSamplesPerFrame; ++i) {\n            out[i]  = std::sqrt(out[2*i + 0]*out[2*i + 0] + out[2*i + 1]*out[2*i + 1]);\n        }\n        for (int i = 1; i < g_nSamplesPerFrame/2; ++i) {\n            out[i]  += out[g_nSamplesPerFrame - i];\n        }\n\n        for (int i = 0; i < (int) g_freqData.size(); ++i) {\n            g_freqData[i].mag[g_freqDataHead] = out[i];\n        }\n        if (++g_freqDataHead == g_freqDataSize) {\n            g_freqDataHead = 0;\n        }\n    } while (n > 0);\n\n    return true;\n}\n\nbool GGWave_deinit() {\n    if (g_devIdInp == 0 && g_devIdOut == 0) {\n        return false;\n    }\n\n    SDL_PauseAudioDevice(g_devIdInp, 1);\n    SDL_CloseAudioDevice(g_devIdInp);\n    SDL_PauseAudioDevice(g_devIdOut, 1);\n    SDL_CloseAudioDevice(g_devIdOut);\n\n    g_devIdInp = 0;\n    g_devIdOut = 0;\n\n    return true;\n}\n\nbool ImGui_BeginFrame(SDL_Window * window) {\n    SDL_Event event;\n    while (SDL_PollEvent(&event))\n    {\n        ImGui_ProcessEvent(&event);\n        if (event.type == SDL_QUIT) return false;\n        if (event.type == SDL_WINDOWEVENT && event.window.event == SDL_WINDOWEVENT_CLOSE && event.window.windowID == SDL_GetWindowID(window)) return false;\n    }\n\n    ImGui_NewFrame(window);\n\n    return true;\n}\n\nbool ImGui_EndFrame(SDL_Window * window) {\n    // Rendering\n    int display_w, display_h;\n    SDL_GetWindowSize(window, &display_w, &display_h);\n    glViewport(0, 0, display_w, display_h);\n    glClearColor(0.0f, 0.0f, 0.0f, 0.4f);\n    glClear(GL_COLOR_BUFFER_BIT);\n\n    ImGui::Render();\n    ImGui_RenderDrawData(ImGui::GetDrawData());\n\n    SDL_GL_SwapWindow(window);\n\n    return true;\n}\n\nbool ImGui_SetStyle() {\n    ImGuiStyle & style = ImGui::GetStyle();\n\n    style.AntiAliasedFill = true;\n    style.AntiAliasedLines = true;\n    style.WindowRounding = 0.0f;\n\n    style.WindowPadding = ImVec2(8, 8);\n    style.WindowRounding = 0.0f;\n    style.FramePadding = ImVec2(4, 3);\n    style.FrameRounding = 0.0f;\n    style.ItemSpacing = ImVec2(8, 4);\n    style.ItemInnerSpacing = ImVec2(4, 4);\n    style.IndentSpacing = 21.0f;\n    style.ScrollbarSize = 16.0f;\n    style.ScrollbarRounding = 9.0f;\n    style.GrabMinSize = 10.0f;\n    style.GrabRounding = 3.0f;\n\n    style.Colors[ImGuiCol_Text]                  = ImVec4(1.00f, 1.00f, 1.00f, 1.00f);\n    style.Colors[ImGuiCol_TextDisabled]          = ImVec4(0.24f, 0.41f, 0.41f, 1.00f);\n    style.Colors[ImGuiCol_WindowBg]              = ImVec4(0.08f, 0.08f, 0.08f, 0.94f);\n    //style.Colors[ImGuiCol_ChildWindowBg]         = ImVec4(0.07f, 0.07f, 0.09f, 1.00f);\n    style.Colors[ImGuiCol_PopupBg]               = ImVec4(0.07f, 0.07f, 0.09f, 1.00f);\n    style.Colors[ImGuiCol_Border]                = ImVec4(0.31f, 0.31f, 0.31f, 0.71f);\n    style.Colors[ImGuiCol_BorderShadow]          = ImVec4(0.00f, 0.00f, 0.00f, 0.00f);\n    style.Colors[ImGuiCol_FrameBg]               = ImVec4(0.00f, 0.39f, 0.39f, 0.39f);\n    style.Colors[ImGuiCol_FrameBgHovered]        = ImVec4(0.26f, 1.00f, 1.00f, 0.39f);\n    style.Colors[ImGuiCol_FrameBgActive]         = ImVec4(0.00f, 0.78f, 0.00f, 1.00f);\n    style.Colors[ImGuiCol_TitleBg]               = ImVec4(0.00f, 0.50f, 0.50f, 0.70f);\n    style.Colors[ImGuiCol_TitleBgCollapsed]      = ImVec4(0.00f, 0.50f, 0.50f, 1.00f);\n    style.Colors[ImGuiCol_TitleBgActive]         = ImVec4(0.00f, 0.70f, 0.70f, 1.00f);\n    style.Colors[ImGuiCol_MenuBarBg]             = ImVec4(0.00f, 0.70f, 0.70f, 1.00f);\n    style.Colors[ImGuiCol_ScrollbarBg]           = ImVec4(0.10f, 0.27f, 0.27f, 1.00f);\n    style.Colors[ImGuiCol_ScrollbarGrab]         = ImVec4(0.80f, 0.80f, 0.83f, 0.31f);\n    style.Colors[ImGuiCol_ScrollbarGrabHovered]  = ImVec4(0.26f, 1.00f, 1.00f, 0.39f);\n    style.Colors[ImGuiCol_ScrollbarGrabActive]   = ImVec4(0.00f, 0.78f, 0.00f, 1.00f);\n    //style.Colors[ImGuiCol_ComboBg]               = ImVec4(0.00f, 0.39f, 0.39f, 1.00f);\n    style.Colors[ImGuiCol_CheckMark]             = ImVec4(0.80f, 0.80f, 0.83f, 0.39f);\n    style.Colors[ImGuiCol_SliderGrab]            = ImVec4(0.80f, 0.80f, 0.83f, 0.39f);\n    style.Colors[ImGuiCol_SliderGrabActive]      = ImVec4(0.00f, 0.78f, 0.00f, 1.00f);\n    style.Colors[ImGuiCol_Button]                = ImVec4(0.13f, 0.55f, 0.55f, 1.00f);\n    style.Colors[ImGuiCol_ButtonHovered]         = ImVec4(0.61f, 1.00f, 0.00f, 0.51f);\n    style.Colors[ImGuiCol_ButtonActive]          = ImVec4(0.00f, 0.78f, 0.00f, 1.00f);\n    style.Colors[ImGuiCol_Header]                = ImVec4(0.79f, 0.51f, 0.00f, 0.51f);\n    style.Colors[ImGuiCol_HeaderHovered]         = ImVec4(0.79f, 0.51f, 0.00f, 0.67f);\n    style.Colors[ImGuiCol_HeaderActive]          = ImVec4(0.79f, 0.51f, 0.00f, 0.67f);\n    //style.Colors[ImGuiCol_Column]                = ImVec4(0.79f, 0.51f, 0.00f, 0.67f);\n    //style.Colors[ImGuiCol_ColumnHovered]         = ImVec4(0.25f, 1.00f, 0.00f, 1.00f);\n    //style.Colors[ImGuiCol_ColumnActive]          = ImVec4(0.79f, 0.51f, 0.00f, 0.67f);\n    style.Colors[ImGuiCol_ResizeGrip]            = ImVec4(0.00f, 0.00f, 0.00f, 0.00f);\n    style.Colors[ImGuiCol_ResizeGripHovered]     = ImVec4(0.26f, 1.00f, 1.00f, 0.39f);\n    style.Colors[ImGuiCol_ResizeGripActive]      = ImVec4(0.00f, 0.78f, 0.00f, 1.00f);\n    //style.Colors[ImGuiCol_CloseButton]           = ImVec4(0.40f, 0.39f, 0.38f, 0.16f);\n    //style.Colors[ImGuiCol_CloseButtonHovered]    = ImVec4(0.26f, 1.00f, 1.00f, 0.39f);\n    //style.Colors[ImGuiCol_CloseButtonActive]     = ImVec4(0.79f, 0.51f, 0.00f, 0.67f);\n    style.Colors[ImGuiCol_PlotLines]             = ImVec4(1.00f, 0.65f, 0.38f, 0.67f);\n    style.Colors[ImGuiCol_PlotLinesHovered]      = ImVec4(0.25f, 1.00f, 0.00f, 1.00f);\n    style.Colors[ImGuiCol_PlotHistogram]         = ImVec4(1.00f, 0.65f, 0.38f, 0.67f);\n    style.Colors[ImGuiCol_PlotHistogramHovered]  = ImVec4(0.25f, 1.00f, 0.00f, 1.00f);\n    style.Colors[ImGuiCol_TextSelectedBg]        = ImVec4(0.25f, 1.00f, 0.00f, 0.43f);\n    style.Colors[ImGuiCol_ModalWindowDarkening]  = ImVec4(1.00f, 0.98f, 0.95f, 0.78f);\n\n    return true;\n}\n\nstatic std::function<bool()> g_doInit;\nstatic std::function<void(int, int)> g_setWindowSize;\nstatic std::function<bool()> g_mainUpdate;\n\nvoid mainUpdate(void *) {\n    g_mainUpdate();\n}\n\n// JS interface\n\nextern \"C\" {\n    EMSCRIPTEN_KEEPALIVE\n        int do_init() {\n            return g_doInit();\n        }\n\n    EMSCRIPTEN_KEEPALIVE\n        void set_window_size(int sizeX, int sizeY) {\n            g_setWindowSize(sizeX, sizeY);\n        }\n}\n\nint main(int argc, char** argv) {\n#ifdef __EMSCRIPTEN__\n    printf(\"Build time: %s\\n\", BUILD_TIMESTAMP);\n    printf(\"Press the Init button to start\\n\");\n\n    if (argv[1]) {\n        GGWave_setDefaultCaptureDeviceName(argv[1]);\n    }\n#endif\n\n    auto argm = parseCmdArguments(argc, argv);\n    int captureId = argm[\"c\"].empty() ? 0 : std::stoi(argm[\"c\"]);\n    int playbackId = argm[\"p\"].empty() ? 0 : std::stoi(argm[\"p\"]);\n\n    if (SDL_Init(SDL_INIT_VIDEO) != 0) {\n        fprintf(stderr, \"Error: %s\\n\", SDL_GetError());\n        return -1;\n    }\n\n    ImGui_PreInit();\n\n    int windowX = 1600;\n    int windowY = 1200;\n\n    const char * windowTitle = \"spectrogram\";\n\n#ifdef __EMSCRIPTEN__\n    SDL_Renderer * renderer;\n    SDL_Window * window;\n    SDL_CreateWindowAndRenderer(windowX, windowY, SDL_WINDOW_OPENGL, &window, &renderer);\n#else\n    SDL_WindowFlags window_flags = (SDL_WindowFlags)(SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI);\n    SDL_Window * window = SDL_CreateWindow(windowTitle, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, windowX, windowY, window_flags);\n#endif\n\n    void * gl_context = SDL_GL_CreateContext(window);\n\n    SDL_GL_MakeCurrent(window, gl_context);\n    SDL_GL_SetSwapInterval(1); // Enable vsync\n\n    ImGui_Init(window, gl_context);\n    ImGui::GetIO().IniFilename = nullptr;\n\n    ImGui_SetStyle();\n\n    ImGui_NewFrame(window);\n    ImGui::Render();\n\n    bool isInitialized = false;\n\n    g_doInit = [&]() {\n        if (GGWave_init(playbackId, captureId) == false) {\n            fprintf(stderr, \"Failed to initialize GGWave\\n\");\n            return false;\n        }\n\n        g_freqDataSize = (3*GGWave::kDefaultSampleRate)/g_nSamplesPerFrame;\n\n        float df = float(GGWave::kDefaultSampleRate)/g_nSamplesPerFrame;\n        g_freqData.resize(g_nSamplesPerFrame/2);\n        for (int i = 0; i < g_nSamplesPerFrame/2; ++i) {\n            g_freqData[i].freq = df*i;\n            g_freqData[i].mag.resize(g_freqDataSize);\n        }\n\n        isInitialized = true;\n\n        return true;\n    };\n\n    g_setWindowSize = [&](int sizeX, int sizeY) {\n        SDL_SetWindowSize(window, sizeX, sizeY);\n    };\n\n    g_mainUpdate = [&]() {\n        if (isInitialized == false) {\n            return true;\n        }\n\n        if (ImGui_BeginFrame(window) == false) {\n            return false;\n        }\n\n        const auto& displaySize = ImGui::GetIO().DisplaySize;\n\n        ImGui::SetNextWindowPos({ 0, 0, });\n        ImGui::SetNextWindowSize(displaySize);\n        ImGui::Begin(\"Main\", nullptr,\n                     ImGuiWindowFlags_NoMove |\n                     ImGuiWindowFlags_NoTitleBar |\n                     ImGuiWindowFlags_NoScrollbar |\n                     ImGuiWindowFlags_NoResize |\n                     ImGuiWindowFlags_NoScrollWithMouse |\n                     ImGuiWindowFlags_NoSavedSettings);\n\n        auto & style = ImGui::GetStyle();\n\n        auto itemSpacingSave = style.ItemSpacing;\n        style.ItemSpacing.x = 0.0f;\n        style.ItemSpacing.y = 0.0f;\n\n        auto windowPaddingSave = style.WindowPadding;\n        style.WindowPadding.x = 0.0f;\n        style.WindowPadding.y = 0.0f;\n\n        auto childBorderSizeSave = style.ChildBorderSize;\n        style.ChildBorderSize = 0.0f;\n\n        {\n            float sum = 0.0;\n            for (int i = g_binMin; i < g_binMax; ++i) {\n                for (int j = 0; j < g_freqDataSize; ++j) {\n                    sum += g_freqData[i].mag[j];\n                }\n            }\n\n            int nf = g_binMax - g_binMin;\n            sum /= (nf*g_freqDataSize);\n\n            const auto wSize = ImGui::GetContentRegionAvail();\n\n            const float dx = wSize.x/nf;\n            const float dy = wSize.y/g_freqDataSize;\n\n            auto p0 = ImGui::GetCursorScreenPos();\n\n            int nChildWindows = 0;\n            int nFreqPerChild = 32;\n            ImGui::PushID(nChildWindows++);\n            ImGui::BeginChild(\"Spectrogram\", { wSize.x, (nFreqPerChild + 1)*dy }, true);\n            auto drawList = ImGui::GetWindowDrawList();\n\n            for (int j = 0; j < g_freqDataSize; ++j) {\n                if (j > 0 && j % nFreqPerChild == 0) {\n                    ImGui::EndChild();\n                    ImGui::PopID();\n\n                    ImGui::PushID(nChildWindows++);\n                    ImGui::SetCursorScreenPos({ p0.x, p0.y + nFreqPerChild*int(j/nFreqPerChild)*dy });\n                    ImGui::BeginChild(\"Spectrogram\", { wSize.x, (nFreqPerChild + 1)*dy }, true);\n                    drawList = ImGui::GetWindowDrawList();\n                }\n                for (int i = 0; i < nf; ++i) {\n                    int k = g_freqDataHead + j;\n                    if (k >= g_freqDataSize) k -= g_freqDataSize;\n                    auto v = g_freqData[g_binMin + i].mag[k];\n                    ImVec4 c = { 0.0f, 1.0f, 0.0, 0.0f };\n                    c.w = v/(g_scale*sum);\n\n                    const ImVec2 rp0 = { p0.x + i*dx     , p0.y + j*dy };\n                    const ImVec2 rp1 = { p0.x + i*dx + dx, p0.y + j*dy + dy };\n\n                    drawList->AddRectFilled(rp0, rp1, ImGui::ColorConvertFloat4ToU32(c));\n\n                    // if hovering -> tooltip\n                    if (ImGui::IsMouseHoveringRect(rp0, rp1)) {\n                        ImGui::BeginTooltip();\n                        ImGui::Text(\"%.2f Hz\", g_freqData[g_binMin + i].freq);\n                        ImGui::Text(\"%.2f\", v);\n                        ImGui::EndTooltip();\n                    }\n                }\n            }\n\n            ImGui::EndChild();\n            ImGui::PopID();\n        }\n\n        style.ItemSpacing = itemSpacingSave;\n        style.WindowPadding = windowPaddingSave;\n        style.ChildBorderSize = childBorderSizeSave;\n\n        ImGui::End();\n\n        bool togglePause = false;\n\n        if (g_showControls) {\n            ImGui::SetNextWindowFocus();\n            ImGui::SetNextWindowPos({ std::max(20.0f, displaySize.x - 400.0f - 20.0f), 20.0f });\n            ImGui::SetNextWindowSize({ std::min(displaySize.x - 40.0f, 400.0f), 210.0f });\n            ImGui::Begin(\"Controls\", &g_showControls);\n            ImGui::Text(\"Press 'c' to hide/show this window\");\n            {\n                static char buf[64];\n                snprintf(buf, 64, \"Bin: %3d, Freq: %5.2f Hz\", g_binMin, 0.5*g_binMin*g_obtainedSpecInp.freq/g_nBins);\n                ImGui::DragInt(\"Min\", &g_binMin, 1, 0, g_binMax - 2, buf);\n                snprintf(buf, 64, \"Bin: %3d, Freq: %5.2f Hz\", g_binMax, 0.5*g_binMax*g_obtainedSpecInp.freq/g_nBins);\n                ImGui::DragInt(\"Max\", &g_binMax, 1, g_binMin + 1, g_nBins, buf);\n            }\n            ImGui::DragFloat(\"Scale\", &g_scale, 1.0f, 1.0f, 1000.0f);\n\n            if (ImGui::Checkbox(\"High-pass\", &g_filter2)) {\n            }\n            ImGui::SameLine();\n            if (ImGui::Checkbox(\"Hann\", &g_filter0)) {\n            }\n            ImGui::SameLine();\n            if (ImGui::Checkbox(\"Hamming\", &g_filter1)) {\n            }\n\n            ImGui::Text(\"%s\", \"\");\n#ifndef __EMSCRIPTEN__\n            if (ImGui::SliderFloat(\"Offset\", &g_sampleRateOffset, -2048, 2048)) {\n                GGWave_deinit();\n                GGWave_init(0, 0);\n            }\n#endif\n            if (ImGui::Button(\"Pause [Enter]\")) {\n                togglePause = true;\n            }\n            if (ImGui::IsKeyPressed(40)) {\n                togglePause = true;\n            }\n            ImGui::End();\n        }\n\n        if (togglePause) {\n            g_isCapturing = !g_isCapturing;\n        }\n\n        if (ImGui::IsKeyPressed(6)) {\n            g_showControls = !g_showControls;\n        }\n\n        GGWave_mainLoop();\n\n        ImGui_EndFrame(window);\n\n        return true;\n    };\n\n#ifdef __EMSCRIPTEN__\n    emscripten_set_main_loop_arg(mainUpdate, NULL, 60, true);\n#else\n    if (g_doInit() == false) {\n        printf(\"Error: failed to initialize audio\\n\");\n        return -2;\n    }\n\n    while (true) {\n        if (g_mainUpdate() == false) break;\n    }\n\n    GGWave_deinit();\n\n    // Cleanup\n    ImGui_Shutdown();\n    ImGui::DestroyContext();\n\n    SDL_GL_DeleteContext(gl_context);\n    SDL_DestroyWindow(window);\n    SDL_CloseAudio();\n    SDL_Quit();\n#endif\n\n    return 0;\n}\n"
  },
  {
    "path": "examples/spectrogram/style.css",
    "content": "body {\n    margin: 0; background-color: black;\n    -webkit-font-smoothing: subpixel-antialiased;\n    font-smoothing: subpixel-antialiased;\n}\n#screen {\n    margin: 0;\n    padding: 0;\n    font-size: 13px;\n    height: 100%;\n    font: sans-serif;\n}\n.no-sel {\n    -moz-user-select: none;\n    -webkit-user-select: none;\n    -webkit-touch-callout: none;\n    -ms-user-select:none;\n    user-select:none;\n    -o-user-select:none;\n}\n.cell {\n    pointer-events: none;\n}\n.cell-version {\n    padding-left: 4px;\n    padding-top: 0.5em;\n    text-align: left;\n    display: inline-block;\n    float: left;\n    color: rgba(0, 0, 0, 0.75);\n}\n.cell-about {\n    padding-right: 24px;\n    padding-top: 0.5em;\n    text-align: right;\n    display: inline-block;\n    float: right;\n}\n.nav-link {\n    text-decoration: none;\n    color: rgba(0, 0, 0, 1.0);\n}\n\n#main-container {\n\tfont-size:12px;\n    font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Helvetica, Arial, sans-serif, \"Apple Color Emoji\", \"Segoe UI Emoji\", \"Segoe UI Symbol\";\n}\n\ntextarea {\n\tfont-size:12px;\n\tfont-family: monospace;\n}\n\n.emscripten { padding-right: 0; margin-left: auto; margin-right: auto; display: block; }\ndiv.emscripten_border { border: 1px solid black; }\n\ncanvas.emscripten {\n    border: 0px none;\n    background-color: black;\n    text-shadow: 4px 4px #1f1f1fb4;\n}\n\n.spinner {\n\theight: 30px;\n\twidth: 30px;\n\tmargin: 0;\n\tmargin-top: 20px;\n\tmargin-left: 20px;\n\tdisplay: inline-block;\n\tvertical-align: top;\n\n\t-webkit-animation: rotation .8s linear infinite;\n\t-moz-animation: rotation .8s linear infinite;\n\t-o-animation: rotation .8s linear infinite;\n\tanimation: rotation 0.8s linear infinite;\n\n\tborder-left: 5px solid rgb(235, 235, 235);\n\tborder-right: 5px solid rgb(235, 235, 235);\n\tborder-bottom: 5px solid rgb(235, 235, 235);\n\tborder-top: 5px solid rgb(120, 120, 120);\n\n\tborder-radius: 100%;\n\tbackground-color: rgb(189, 215, 46);\n}\n\n@-webkit-keyframes rotation {\n\tfrom {-webkit-transform: rotate(0deg);}\n\tto {-webkit-transform: rotate(360deg);}\n}\n@-moz-keyframes rotation {\n\tfrom {-moz-transform: rotate(0deg);}\n\tto {-moz-transform: rotate(360deg);}\n}\n@-o-keyframes rotation {\n\tfrom {-o-transform: rotate(0deg);}\n\tto {-o-transform: rotate(360deg);}\n}\n@keyframes rotation {\n\tfrom {transform: rotate(0deg);}\n\tto {transform: rotate(360deg);}\n}\n\n#status {\n\tdisplay: inline-block;\n\tvertical-align: top;\n\tmargin-top: 30px;\n\tmargin-left: 20px;\n\tfont-weight: bold;\n\tcolor: rgb(120, 120, 120);\n}\n\n#progress {\n\theight: 20px;\n\twidth: 30px;\n}\n\n#output {\n\twidth: 800px;\n\theight: 200px;\n\tmargin: 0 auto;\n\tmargin-top: 10px;\n\tborder-left: 0px;\n\tborder-right: 0px;\n\tpadding-left: 0px;\n\tpadding-right: 0px;\n\tbackground-color: black;\n\tcolor: white;\n\tfont-size:10px;\n\tfont-family: 'Lucida Console', Monaco, monospace;\n\toutline: none;\n}\n\n.led-box {\n\theight: 30px;\n\twidth: 25%;\n\tmargin: 10px 0;\n\tfloat: left;\n}\n\n.led-box p {\n\tfont-size: 12px;\n\ttext-align: center;\n\tmargin: 1em;\n}\n\n.led-red {\n\tmargin: 0 auto;\n\twidth: 12px;\n\theight: 12px;\n\tbackground-color: #F00;\n\tborder-radius: 50%;\n\tbox-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;\n\t-webkit-animation: blinkRed 0.5s infinite;\n\t-moz-animation: blinkRed 0.5s infinite;\n\t-ms-animation: blinkRed 0.5s infinite;\n\t-o-animation: blinkRed 0.5s infinite;\n\tanimation: blinkRed 0.5s infinite;\n}\n\n@-webkit-keyframes blinkRed {\n\tfrom { background-color: #F00; }\n\t50% { 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;}\n\tto { background-color: #F00; }\n}\n@-moz-keyframes blinkRed {\n\tfrom { background-color: #F00; }\n\t50% { 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;}\n\tto { background-color: #F00; }\n}\n@-ms-keyframes blinkRed {\n\tfrom { background-color: #F00; }\n\t50% { 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;}\n\tto { background-color: #F00; }\n}\n@-o-keyframes blinkRed {\n\tfrom { background-color: #F00; }\n\t50% { 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;}\n\tto { background-color: #F00; }\n}\n@keyframes blinkRed {\n\tfrom { background-color: #F00; }\n\t50% { 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;}\n\tto { background-color: #F00; }\n}\n\n.led-yellow {\n\tmargin: 0 auto;\n\twidth: 12px;\n\theight: 12px;\n\tbackground-color: #FF0;\n\tborder-radius: 50%;\n\tbox-shadow: rgba(0, 0, 0, 0.2) 0 -1px 7px 1px, inset #808002 0 -1px 9px, #FF0 0 2px 12px;\n\t-webkit-animation: blinkYellow 1s infinite;\n\t-moz-animation: blinkYellow 1s infinite;\n\t-ms-animation: blinkYellow 1s infinite;\n\t-o-animation: blinkYellow 1s infinite;\n\tanimation: blinkYellow 1s infinite;\n}\n\n@-webkit-keyframes blinkYellow {\n\tfrom { background-color: #FF0; }\n\t50% { background-color: #AA0; box-shadow: rgba(0, 0, 0, 0.2) 0 -1px 7px 1px, inset #808002 0 -1px 9px, #FF0 0 2px 0; }\n\tto { background-color: #FF0; }\n}\n@-moz-keyframes blinkYellow {\n\tfrom { background-color: #FF0; }\n\t50% { background-color: #AA0; box-shadow: rgba(0, 0, 0, 0.2) 0 -1px 7px 1px, inset #808002 0 -1px 9px, #FF0 0 2px 0; }\n\tto { background-color: #FF0; }\n}\n@-ms-keyframes blinkYellow {\n\tfrom { background-color: #FF0; }\n\t50% { background-color: #AA0; box-shadow: rgba(0, 0, 0, 0.2) 0 -1px 7px 1px, inset #808002 0 -1px 9px, #FF0 0 2px 0; }\n\tto { background-color: #FF0; }\n}\n@-o-keyframes blinkYellow {\n\tfrom { background-color: #FF0; }\n\t50% { background-color: #AA0; box-shadow: rgba(0, 0, 0, 0.2) 0 -1px 7px 1px, inset #808002 0 -1px 9px, #FF0 0 2px 0; }\n\tto { background-color: #FF0; }\n}\n@keyframes blinkYellow {\n\tfrom { background-color: #FF0; }\n\t50% { background-color: #AA0; box-shadow: rgba(0, 0, 0, 0.2) 0 -1px 7px 1px, inset #808002 0 -1px 9px, #FF0 0 2px 0; }\n\tto { background-color: #FF0; }\n}\n\n.led-green {\n\tmargin: 0 auto;\n\twidth: 12px;\n\theight: 12px;\n\tbackground-color: #ABFF00;\n\tborder-radius: 50%;\n\tbox-shadow: rgba(0, 0, 0, 0.2) 0 -1px 7px 1px, inset #304701 0 -1px 9px, #89FF00 0 2px 12px;\n}\n\n.led-blue {\n\tmargin: 0 auto;\n\twidth: 18px;\n\theight: 18px;\n\tbackground-color: #24E0FF;\n\tborder-radius: 50%;\n\tbox-shadow: rgba(0, 0, 0, 0.2) 0 -1px 7px 1px, inset #006 0 -1px 9px, #3F8CFF 0 2px 14px;\n}\n\ntable td {\n    border: 1px solid #e8e8e8;\n}\ntable th, table td {\n    padding: 10px 10px;\n}\ntable td {\n    border: 1px solid #e8e8e8;\n}\ntable th, table td {\n    padding: 10px 10px;\n}\ntd[Attributes Style] {\n    text-align: -webkit-center;\n}\ntd {\n    display: table-cell;\n    vertical-align: inherit;\n}\ntable {\n    margin-bottom: 30px;\n    width: 800px;\n    text-align: left;\n    color: #3f3f3f;\n    border-collapse: collapse;\n    border: 1px solid #e8e8e8;\n}\ntable {\n    margin-bottom: 30px;\n    width: 800px;\n    text-align: left;\n    color: #3f3f3f;\n    border-collapse: collapse;\n    border: 1px solid #e8e8e8;\n}\ntable {\n    border-collapse: separate;\n    border-spacing: 2px;\n}\n\n#description {\n    margin: 10px;\n    padding: 10px;\n    color: rgba(255, 255, 255, 1.00);\n    text-shadow: 2px 2px #1f1f1fb4;\n}\n.text-body {\n    color: rgba(255, 255, 255, 1.00);\n    text-shadow: 2px 2px #1f1f1fb4;\n}\n.cell-version {\n    padding-left: 4px;\n    padding-top: 0.5em;\n    text-align: left;\n    display: inline-block;\n    float: left;\n    color: rgba(255, 255, 255, 0.75);\n    text-shadow: 2px 2px #1f1f1fb4;\n}\n.cell-about {\n    padding-right: 24px;\n    padding-top: 0.5em;\n    text-align: right;\n    display: inline-block;\n    float: right;\n}\n.nav-link {\n    text-decoration: none;\n    color: rgba(255, 255, 255, 1.0);\n    text-shadow: 2px 2px #1f1f1fb4;\n}\n.nav-link2 {\n    text-decoration: none;\n    color: rgba(0, 255, 0, 1.0);\n    text-shadow: 2px 2px #1f1f1fb4;\n}\nsvg {\n    -webkit-filter: invert(100%); /* safari 6.0 - 9.0 */\n    filter: invert(100%);\n}\n"
  },
  {
    "path": "examples/third-party/CMakeLists.txt",
    "content": "if (NOT EMSCRIPTEN)\nendif()\n\nadd_subdirectory(imgui)\nadd_subdirectory(ggsock)\n"
  },
  {
    "path": "examples/third-party/imgui/CMakeLists.txt",
    "content": "if (GGWAVE_ALL_WARNINGS_3RD_PARTY)\n    if (CMAKE_COMPILER_IS_GNUCC OR CMAKE_CXX_COMPILER_ID MATCHES \"Clang\")\n        set(CMAKE_CXX_FLAGS \"${CMAKE_CXX_FLAGS} -Wall -Wextra -pedantic\")\n    else()\n        # todo : windows\n    endif()\nendif()\n\nif (APPLE)\n    set(ADDITIONAL_LIBRARIES \"-framework Cocoa\")\nelse (APPLE)\n    unset(ADDITIONAL_LIBRARIES)\nendif (APPLE)\n\nadd_library(imgui\n    imgui/imgui.cpp\n    imgui/imgui_draw.cpp\n    imgui/imgui_demo.cpp\n    imgui/imgui_widgets.cpp\n    )\n\ntarget_include_directories(imgui INTERFACE\n    .\n    )\n\ntarget_include_directories(imgui PRIVATE\n    imgui\n    )\n\ntarget_link_libraries(imgui PRIVATE\n    ${ADDITIONAL_LIBRARIES}\n    )\n\nif (MINGW)\n    target_link_libraries(imgui PUBLIC\n        stdc++\n        )\nendif()\n\nif (GGWAVE_SUPPORT_SDL2)\n    if (MINGW)\n        find_package(PkgConfig REQUIRED)\n        pkg_search_module(SDL2 REQUIRED sdl2)\n\n        add_library(imgui-sdl2\n            imgui/examples/libs/gl3w/GL/gl3w.c\n            imgui-extra/imgui_impl.cpp\n            imgui-extra/imgui_impl_sdl.cpp\n            imgui-extra/imgui_impl_opengl3.cpp\n            )\n\n        target_include_directories(imgui-sdl2 PUBLIC\n            imgui/examples/libs/gl3w\n            ${SDL2_INCLUDE_DIRS}\n            )\n\n        target_include_directories(imgui-sdl2 PRIVATE\n            imgui\n            imgui-extra\n            )\n\n        target_link_libraries(imgui-sdl2 PUBLIC\n            imgui\n            opengl32\n            ${SDL2_LIBRARIES}\n            )\n\n        target_link_libraries(imgui-sdl2 PRIVATE\n            ${CMAKE_DL_LIBS}\n            ${CMAKE_THREAD_LIBS_INIT}\n            ${ADDITIONAL_LIBRARIES}\n            )\n    elseif (EMSCRIPTEN)\n        add_library(imgui-sdl2\n            imgui-extra/imgui_impl.cpp\n            imgui-extra/imgui_impl_sdl.cpp\n            imgui-extra/imgui_impl_opengl3.cpp\n            )\n\n        target_include_directories(imgui-sdl2 PRIVATE\n            imgui\n            imgui-extra\n            )\n\n        target_link_libraries(imgui-sdl2 PUBLIC\n            imgui\n            )\n    else()\n        set(OpenGL_GL_PREFERENCE GLVND)\n        find_package(OpenGL REQUIRED)\n\n        add_library(imgui-sdl2\n            imgui/examples/libs/gl3w/GL/gl3w.c\n            imgui-extra/imgui_impl.cpp\n            imgui-extra/imgui_impl_sdl.cpp\n            imgui-extra/imgui_impl_opengl3.cpp\n            )\n\n        # force GL3W loader\n        target_compile_definitions(imgui-sdl2 PUBLIC\n            IMGUI_IMPL_OPENGL_LOADER_GL3W=1\n            )\n\n        target_include_directories(imgui-sdl2 PUBLIC\n            imgui/examples/libs/gl3w\n            ${SDL2_INCLUDE_DIRS}\n            )\n\n        target_include_directories(imgui-sdl2 PRIVATE\n            imgui\n            imgui-extra\n            )\n\n        target_link_libraries(imgui-sdl2 PUBLIC\n            imgui\n            ${OPENGL_LIBRARIES}\n            ${SDL2_LIBRARIES}\n            )\n\n        target_link_libraries(imgui-sdl2 PRIVATE\n            ${CMAKE_DL_LIBS}\n            ${CMAKE_THREAD_LIBS_INIT}\n            ${ADDITIONAL_LIBRARIES}\n            )\n    endif()\nendif()\n\ninstall(TARGETS imgui\n    LIBRARY DESTINATION lib\n    ARCHIVE DESTINATION lib/static\n    )\n\nif (GGWAVE_SUPPORT_SDL2)\n    install(TARGETS imgui-sdl2\n        LIBRARY DESTINATION lib\n        ARCHIVE DESTINATION lib/static\n        )\nendif()\n"
  },
  {
    "path": "examples/third-party/imgui/imgui-extra/imgui_impl.cpp",
    "content": "#include \"imgui-extra/imgui_impl.h\"\n\n#include \"imgui-extra/imgui_impl_sdl.h\"\n#include \"imgui-extra/imgui_impl_opengl3.h\"\n\n#if defined(IMGUI_IMPL_OPENGL_LOADER_GL3W)\n#include <GL/gl3w.h>    // Initialize with gl3wInit()\n#elif defined(IMGUI_IMPL_OPENGL_LOADER_GLEW)\n#include <GL/glew.h>    // Initialize with glewInit()\n#elif defined(IMGUI_IMPL_OPENGL_LOADER_GLAD)\n#include <glad/glad.h>  // Initialize with gladLoadGL()\n#else\n#include IMGUI_IMPL_OPENGL_LOADER_CUSTOM\n#endif\n\n#include <SDL.h>\n\n#include <cstdio>\n\nbool ImGui_PreInit() {\n    // Decide GL+GLSL versions\n#if __APPLE__\n    // GL 3.2 Core + GLSL 150\n    SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_FORWARD_COMPATIBLE_FLAG); // Always required on Mac\n    SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);\n    SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);\n    SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 2);\n#elif __EMSCRIPTEN__\n    const char* glsl_version = \"#version 100\";\n    //const char* glsl_version = \"#version 300 es\";\n    SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, 0);\n    SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES);\n    SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2);\n    SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0);\n#else\n    // GL 3.0 + GLSL 130\n    SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, 0);\n    SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);\n    SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);\n    SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0);\n#endif\n\n    // Create window with graphics context\n    SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);\n    SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);\n    SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8);\n\n    return true;\n}\n\nImGuiContext* ImGui_Init(SDL_Window* window, SDL_GLContext gl_context) {\n    // Decide GL+GLSL versions\n#if __APPLE__\n    // GL 3.2 Core + GLSL 150\n    const char* glsl_version = \"#version 150\";\n#elif __EMSCRIPTEN__\n    const char* glsl_version = \"#version 100\";\n#else\n    // GL 3.0 + GLSL 130\n    const char* glsl_version = \"#version 130\";\n#endif\n\n    static bool isInitialized = false;\n    if (!isInitialized) {\n        // Initialize OpenGL loader\n#if defined(IMGUI_IMPL_OPENGL_LOADER_GL3W)\n        bool err = gl3wInit() != 0;\n#elif defined(IMGUI_IMPL_OPENGL_LOADER_GLEW)\n        bool err = glewInit() != GLEW_OK;\n#elif defined(IMGUI_IMPL_OPENGL_LOADER_GLAD)\n        bool err = gladLoadGL() == 0;\n#else\n        bool err = false; // If you use IMGUI_IMPL_OPENGL_LOADER_CUSTOM, your loader is likely to requires some form of initialization.\n#endif\n\n        if (err) {\n            fprintf(stderr, \"Failed to initialize OpenGL loader!\\n\");\n            return nullptr;\n        }\n        isInitialized = true;\n    }\n\n    // Setup Dear ImGui context\n    IMGUI_CHECKVERSION();\n    auto ctx = ImGui::CreateContext();\n    ImGui::SetCurrentContext(ctx);\n\n    ImGuiIO& io = ImGui::GetIO(); (void)io;\n    //io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;  // Enable Keyboard Controls\n    //io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad;   // Enable Gamepad Controls\n\n    // Setup Platform/Renderer bindings\n    bool res = true;\n    res &= ImGui_ImplSDL2_InitForOpenGL(window, gl_context);\n    res &= ImGui_ImplOpenGL3_Init(glsl_version);\n\n    return res ? ctx : nullptr;\n}\n\nvoid ImGui_Shutdown() { ImGui_ImplOpenGL3_Shutdown(); ImGui_ImplSDL2_Shutdown(); }\nvoid ImGui_NewFrame(SDL_Window* window) { ImGui_ImplOpenGL3_NewFrame(); ImGui_ImplSDL2_NewFrame(window); ImGui::NewFrame(); }\nbool ImGui_ProcessEvent(const SDL_Event* event) { return ImGui_ImplSDL2_ProcessEvent(event); }\n\nvoid ImGui_RenderDrawData(ImDrawData* draw_data)    { ImGui_ImplOpenGL3_RenderDrawData(draw_data); }\n\nbool ImGui_CreateFontsTexture()     { return ImGui_ImplOpenGL3_CreateFontsTexture(); }\nvoid ImGui_DestroyFontsTexture()    { ImGui_ImplOpenGL3_DestroyFontsTexture(); }\nbool ImGui_CreateDeviceObjects()    { return ImGui_ImplOpenGL3_CreateDeviceObjects(); }\nvoid ImGui_DestroyDeviceObjects()   { ImGui_ImplOpenGL3_DestroyDeviceObjects(); }\n\nvoid ImGui_SaveState(int id) { ImGui_ImplSDL2_SaveState(id); ImGui_ImplOpenGL3_SaveState(id); }\nvoid ImGui_LoadState(int id) { ImGui_ImplSDL2_LoadState(id); ImGui_ImplOpenGL3_LoadState(id); }\n"
  },
  {
    "path": "examples/third-party/imgui/imgui-extra/imgui_impl.h",
    "content": "/*! \\file imgui_impl.h\n *  \\brief Enter description here.\n */\n\n#pragma once\n\n#if (defined(__EMSCRIPTEN__))\n#define IMGUI_IMPL_OPENGL_LOADER_GLEW\n#endif\n\n#include \"imgui/imgui.h\"\n\nstruct SDL_Window;\ntypedef void * SDL_GLContext;\ntypedef union SDL_Event SDL_Event;\n\nIMGUI_API bool ImGui_PreInit();\nIMGUI_API ImGuiContext* ImGui_Init(SDL_Window* window, SDL_GLContext gl_context);\n\nvoid IMGUI_API ImGui_Shutdown();\nvoid IMGUI_API ImGui_NewFrame(SDL_Window* window);\nbool IMGUI_API ImGui_ProcessEvent(const SDL_Event* event);\n\nvoid IMGUI_API ImGui_RenderDrawData(ImDrawData* draw_data);\n\nbool IMGUI_API ImGui_CreateFontsTexture();\nvoid IMGUI_API ImGui_DestroyFontsTexture();\nbool IMGUI_API ImGui_CreateDeviceObjects();\nvoid IMGUI_API ImGui_DestroyDeviceObjects();\n\nvoid IMGUI_API ImGui_SaveState(int id);\nvoid IMGUI_API ImGui_LoadState(int id);\n"
  },
  {
    "path": "examples/third-party/imgui/imgui-extra/imgui_impl_opengl3.cpp",
    "content": "// dear imgui: Renderer for modern OpenGL with shaders / programmatic pipeline\n// - Desktop GL: 2.x 3.x 4.x\n// - Embedded GL: ES 2.0 (WebGL 1.0), ES 3.0 (WebGL 2.0)\n// This needs to be used along with a Platform Binding (e.g. GLFW, SDL, Win32, custom..)\n\n// Implemented features:\n//  [X] Renderer: User texture binding. Use 'GLuint' OpenGL texture identifier as void*/ImTextureID. Read the FAQ about ImTextureID!\n//  [x] Renderer: Desktop GL only: Support for large meshes (64k+ vertices) with 16-bit indices.\n\n// You can copy and use unmodified imgui_impl_* files in your project. See main.cpp for an example of using this.\n// If you are new to dear imgui, read examples/README.txt and read the documentation at the top of imgui.cpp.\n// https://github.com/ocornut/imgui\n\n// CHANGELOG\n// (minor and older changes stripped away, please see git history for details)\n//  2020-01-07: OpenGL: Added support for glbindings OpenGL loader.\n//  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.\n//  2019-09-22: OpenGL: Detect default GL loader using __has_include compiler facility.\n//  2019-09-16: OpenGL: Tweak initialization code to allow application calling ImGui_ImplOpenGL3_CreateFontsTexture() before the first NewFrame() call.\n//  2019-05-29: OpenGL: Desktop GL only: Added support for large mesh (64K+ vertices), enable ImGuiBackendFlags_RendererHasVtxOffset flag.\n//  2019-04-30: OpenGL: Added support for special ImDrawCallback_ResetRenderState callback to reset render state.\n//  2019-03-29: OpenGL: Not calling glBindBuffer more than necessary in the render loop.\n//  2019-03-15: OpenGL: Added a dummy GL call + comments in ImGui_ImplOpenGL3_Init() to detect uninitialized GL function loaders early.\n//  2019-03-03: OpenGL: Fix support for ES 2.0 (WebGL 1.0).\n//  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.\n//  2019-02-11: OpenGL: Projecting clipping rectangles correctly using draw_data->FramebufferScale to allow multi-viewports for retina display.\n//  2019-02-01: OpenGL: Using GLSL 410 shaders for any version over 410 (e.g. 430, 450).\n//  2018-11-30: Misc: Setting up io.BackendRendererName so it can be displayed in the About Window.\n//  2018-11-13: OpenGL: Support for GL 4.5's glClipControl(GL_UPPER_LEFT) / GL_CLIP_ORIGIN.\n//  2018-08-29: OpenGL: Added support for more OpenGL loaders: glew and glad, with comments indicative that any loader can be used.\n//  2018-08-09: OpenGL: Default to OpenGL ES 3 on iOS and Android. GLSL version default to \"#version 300 ES\".\n//  2018-07-30: OpenGL: Support for GLSL 300 ES and 410 core. Fixes for Emscripten compilation.\n//  2018-07-10: OpenGL: Support for more GLSL versions (based on the GLSL version string). Added error output when shaders fail to compile/link.\n//  2018-06-08: Misc: Extracted imgui_impl_opengl3.cpp/.h away from the old combined GLFW/SDL+OpenGL3 examples.\n//  2018-06-08: OpenGL: Use draw_data->DisplayPos and draw_data->DisplaySize to setup projection matrix and clipping rectangle.\n//  2018-05-25: OpenGL: Removed unnecessary backup/restore of GL_ELEMENT_ARRAY_BUFFER_BINDING since this is part of the VAO state.\n//  2018-05-14: OpenGL: Making the call to glBindSampler() optional so 3.2 context won't fail if the function is a NULL pointer.\n//  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\".\n//  2018-02-23: OpenGL: Create the VAO in the render function so the setup can more easily be used with multiple shared GL context.\n//  2018-02-16: Misc: Obsoleted the io.RenderDrawListsFn callback and exposed ImGui_ImplSdlGL3_RenderDrawData() in the .h file so you can call it yourself.\n//  2018-01-07: OpenGL: Changed GLSL shader version from 330 to 150.\n//  2017-09-01: OpenGL: Save and restore current bound sampler. Save and restore current polygon mode.\n//  2017-05-01: OpenGL: Fixed save and restore of current blend func state.\n//  2017-05-01: OpenGL: Fixed save and restore of current GL_ACTIVE_TEXTURE.\n//  2016-09-05: OpenGL: Fixed save and restore of current scissor rectangle.\n//  2016-07-29: OpenGL: Explicitly setting GL_UNPACK_ROW_LENGTH to reduce issues because SDL changes it. (#752)\n\n//----------------------------------------\n// OpenGL    GLSL      GLSL\n// version   version   string\n//----------------------------------------\n//  2.0       110       \"#version 110\"\n//  2.1       120       \"#version 120\"\n//  3.0       130       \"#version 130\"\n//  3.1       140       \"#version 140\"\n//  3.2       150       \"#version 150\"\n//  3.3       330       \"#version 330 core\"\n//  4.0       400       \"#version 400 core\"\n//  4.1       410       \"#version 410 core\"\n//  4.2       420       \"#version 410 core\"\n//  4.3       430       \"#version 430 core\"\n//  ES 2.0    100       \"#version 100\"      = WebGL 1.0\n//  ES 3.0    300       \"#version 300 es\"   = WebGL 2.0\n//----------------------------------------\n\n#if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS)\n#define _CRT_SECURE_NO_WARNINGS\n#endif\n\n#include \"imgui.h\"\n#include \"imgui_impl_opengl3.h\"\n#include <stdio.h>\n#if defined(_MSC_VER) && _MSC_VER <= 1500 // MSVC 2008 or earlier\n#include <stddef.h>     // intptr_t\n#else\n#include <stdint.h>     // intptr_t\n#endif\n#if defined(__APPLE__)\n#include \"TargetConditionals.h\"\n#endif\n\n// Auto-enable GLES on matching platforms\n#if !defined(IMGUI_IMPL_OPENGL_ES2) && !defined(IMGUI_IMPL_OPENGL_ES3)\n#if (defined(__APPLE__) && (TARGET_OS_IOS || TARGET_OS_TV)) || (defined(__ANDROID__))\n#define IMGUI_IMPL_OPENGL_ES3           // iOS, Android  -> GL ES 3, \"#version 300 es\"\n#undef IMGUI_IMPL_OPENGL_LOADER_GL3W\n#undef IMGUI_IMPL_OPENGL_LOADER_GLEW\n#undef IMGUI_IMPL_OPENGL_LOADER_GLAD\n#undef IMGUI_IMPL_OPENGL_LOADER_GLBINDING\n#undef IMGUI_IMPL_OPENGL_LOADER_CUSTOM\n#elif defined(__EMSCRIPTEN__)\n#define IMGUI_IMPL_OPENGL_ES2           // Emscripten    -> GL ES 2, \"#version 100\"\n#undef IMGUI_IMPL_OPENGL_LOADER_GL3W\n#undef IMGUI_IMPL_OPENGL_LOADER_GLEW\n#undef IMGUI_IMPL_OPENGL_LOADER_GLAD\n#undef IMGUI_IMPL_OPENGL_LOADER_GLBINDING\n#undef IMGUI_IMPL_OPENGL_LOADER_CUSTOM\n#endif\n#endif\n\n// GL includes\n#if defined(IMGUI_IMPL_OPENGL_ES2)\n#include <GLES2/gl2.h>\n#elif defined(IMGUI_IMPL_OPENGL_ES3)\n#if (defined(__APPLE__) && (TARGET_OS_IOS || TARGET_OS_TV))\n#include <OpenGLES/ES3/gl.h>    // Use GL ES 3\n#else\n#include <GLES3/gl3.h>          // Use GL ES 3\n#endif\n#else\n// About Desktop OpenGL function loaders:\n//  Modern desktop OpenGL doesn't have a standard portable header file to load OpenGL function pointers.\n//  Helper libraries are often used for this purpose! Here we are supporting a few common ones (gl3w, glew, glad).\n//  You may use another loader/header of your choice (glext, glLoadGen, etc.), or chose to manually implement your own.\n#if defined(IMGUI_IMPL_OPENGL_LOADER_GL3W)\n#include <GL/gl3w.h>    // Needs to be initialized with gl3wInit() in user's code\n#elif defined(IMGUI_IMPL_OPENGL_LOADER_GLEW)\n#include <GL/glew.h>    // Needs to be initialized with glewInit() in user's code\n#elif defined(IMGUI_IMPL_OPENGL_LOADER_GLAD)\n#include <glad/glad.h>  // Needs to be initialized with gladLoadGL() in user's code\n#elif defined(IMGUI_IMPL_OPENGL_LOADER_GLBINDING)\n#include <glbinding/gl/gl.h>  // Initialize with glbinding::initialize()\n#include <glbinding/glbinding.h>\nusing namespace gl;\n#else\n#include IMGUI_IMPL_OPENGL_LOADER_CUSTOM\n#endif\n#endif\n\n// Desktop GL 3.2+ has glDrawElementsBaseVertex() which GL ES and WebGL don't have.\n#if defined(IMGUI_IMPL_OPENGL_ES2) || defined(IMGUI_IMPL_OPENGL_ES3) || !defined(GL_VERSION_3_2)\n#define IMGUI_IMPL_OPENGL_MAY_HAVE_VTX_OFFSET   0\n#else\n#define IMGUI_IMPL_OPENGL_MAY_HAVE_VTX_OFFSET   1\n#endif\n\n// OpenGL Data\nstatic GLuint       g_GlVersion = 0;                // Extracted at runtime using GL_MAJOR_VERSION, GL_MINOR_VERSION queries.\nstatic char         g_GlslVersionString[32] = \"\";   // Specified by user or detected based on compile time GL settings.\nstatic GLuint       g_FontTexture = 0;\nstatic GLuint       g_ShaderHandle = 0, g_VertHandle = 0, g_FragHandle = 0;\nstatic int          g_AttribLocationTex = 0, g_AttribLocationProjMtx = 0;                                // Uniforms location\nstatic int          g_AttribLocationVtxPos = 0, g_AttribLocationVtxUV = 0, g_AttribLocationVtxColor = 0; // Vertex attributes location\nstatic unsigned int g_VboHandle = 0, g_ElementsHandle = 0;\n\n#include <map>\n\nstruct State {\n    GLuint       FontTexture = 0;\n    GLuint       ShaderHandle = 0, VertHandle = 0, FragHandle = 0;\n    int          AttribLocationTex = 0, AttribLocationProjMtx = 0;\n    int          AttribLocationVtxPos = 0, AttribLocationVtxUV = 0, AttribLocationVtxColor = 0;\n    unsigned int VboHandle = 0, ElementsHandle = 0;\n};\n\nstatic std::map<int, State> g_state;\n\nvoid ImGui_ImplOpenGL3_SaveState(int id) {\n    auto & dst = g_state[id];\n\n    dst.FontTexture             = g_FontTexture;\n    dst.FontTexture             = g_FontTexture;\n    dst.ShaderHandle            = g_ShaderHandle;\n    dst.VertHandle              = g_VertHandle;\n    dst.FragHandle              = g_FragHandle;\n    dst.AttribLocationTex       = g_AttribLocationTex;\n    dst.AttribLocationProjMtx   = g_AttribLocationProjMtx;\n    dst.AttribLocationVtxPos    = g_AttribLocationVtxPos;\n    dst.AttribLocationVtxUV     = g_AttribLocationVtxUV;\n    dst.AttribLocationVtxColor  = g_AttribLocationVtxColor;\n    dst.VboHandle               = g_VboHandle;\n    dst.ElementsHandle          = g_ElementsHandle;\n}\n\nvoid ImGui_ImplOpenGL3_LoadState(int id) {\n    const auto & src = g_state[id];\n\n    g_FontTexture               = src.FontTexture;\n    g_ShaderHandle              = src.ShaderHandle;\n    g_VertHandle                = src.VertHandle;\n    g_FragHandle                = src.FragHandle;\n    g_AttribLocationTex         = src.AttribLocationTex;\n    g_AttribLocationProjMtx     = src.AttribLocationProjMtx;\n    g_AttribLocationVtxPos      = src.AttribLocationVtxPos;\n    g_AttribLocationVtxUV       = src.AttribLocationVtxUV;\n    g_AttribLocationVtxColor    = src.AttribLocationVtxColor;\n    g_VboHandle                 = src.VboHandle;\n    g_ElementsHandle            = src.ElementsHandle;\n}\n\n// Functions\nbool    ImGui_ImplOpenGL3_Init(const char* glsl_version)\n{\n    // Query for GL version\n#if !defined(IMGUI_IMPL_OPENGL_ES2)\n    GLint major, minor;\n    glGetIntegerv(GL_MAJOR_VERSION, &major);\n    glGetIntegerv(GL_MINOR_VERSION, &minor);\n    g_GlVersion = major * 1000 + minor;\n#else\n    g_GlVersion = 2000; // GLES 2\n#endif\n\n    // Setup back-end capabilities flags\n    ImGuiIO& io = ImGui::GetIO();\n    io.BackendRendererName = \"imgui_impl_opengl3\";\n#if IMGUI_IMPL_OPENGL_MAY_HAVE_VTX_OFFSET\n    if (g_GlVersion >= 3200)\n        io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset;  // We can honor the ImDrawCmd::VtxOffset field, allowing for large meshes.\n#endif\n\n    // Store GLSL version string so we can refer to it later in case we recreate shaders.\n    // Note: GLSL version is NOT the same as GL version. Leave this to NULL if unsure.\n#if defined(IMGUI_IMPL_OPENGL_ES2)\n    if (glsl_version == NULL)\n        glsl_version = \"#version 100\";\n#elif defined(IMGUI_IMPL_OPENGL_ES3)\n    if (glsl_version == NULL)\n        glsl_version = \"#version 300 es\";\n#else\n    if (glsl_version == NULL)\n        glsl_version = \"#version 130\";\n#endif\n    IM_ASSERT((int)strlen(glsl_version) + 2 < IM_ARRAYSIZE(g_GlslVersionString));\n    strcpy(g_GlslVersionString, glsl_version);\n    strcat(g_GlslVersionString, \"\\n\");\n\n    // Dummy construct to make it easily visible in the IDE and debugger which GL loader has been selected.\n    // The code actually never uses the 'gl_loader' variable! It is only here so you can read it!\n    // If auto-detection fails or doesn't select the same GL loader file as used by your application,\n    // you are likely to get a crash below.\n    // You can explicitly select a loader by using '#define IMGUI_IMPL_OPENGL_LOADER_XXX' in imconfig.h or compiler command-line.\n    const char* gl_loader = \"Unknown\";\n    IM_UNUSED(gl_loader);\n#if defined(IMGUI_IMPL_OPENGL_LOADER_GL3W)\n    gl_loader = \"GL3W\";\n#elif defined(IMGUI_IMPL_OPENGL_LOADER_GLEW)\n    gl_loader = \"GLEW\";\n#elif defined(IMGUI_IMPL_OPENGL_LOADER_GLAD)\n    gl_loader = \"GLAD\";\n#elif defined(IMGUI_IMPL_OPENGL_LOADER_GLBINDING)\n    gl_loader = \"glbinding\";\n#else // IMGUI_IMPL_OPENGL_LOADER_CUSTOM\n    gl_loader = \"Custom\";\n#endif\n\n    // Make a dummy GL call (we don't actually need the result)\n    // IF YOU GET A CRASH HERE: it probably means that you haven't initialized the OpenGL function loader used by this code.\n    // Desktop OpenGL 3/4 need a function loader. See the IMGUI_IMPL_OPENGL_LOADER_xxx explanation above.\n    GLint current_texture;\n    glGetIntegerv(GL_TEXTURE_BINDING_2D, &current_texture);\n\n    return true;\n}\n\nvoid    ImGui_ImplOpenGL3_Shutdown()\n{\n    ImGui_ImplOpenGL3_DestroyDeviceObjects();\n}\n\nvoid    ImGui_ImplOpenGL3_NewFrame()\n{\n    if (!g_ShaderHandle)\n        ImGui_ImplOpenGL3_CreateDeviceObjects();\n}\n\nstatic void ImGui_ImplOpenGL3_SetupRenderState(ImDrawData* draw_data, int fb_width, int fb_height, GLuint vertex_array_object)\n{\n    // Setup render state: alpha-blending enabled, no face culling, no depth testing, scissor enabled, polygon fill\n    glEnable(GL_BLEND);\n    glBlendEquation(GL_FUNC_ADD);\n    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);\n    glDisable(GL_CULL_FACE);\n    glDisable(GL_DEPTH_TEST);\n    glEnable(GL_SCISSOR_TEST);\n#ifdef GL_POLYGON_MODE\n    glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);\n#endif\n\n    // Setup viewport, orthographic projection matrix\n    // 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.\n    glViewport(0, 0, (GLsizei)fb_width, (GLsizei)fb_height);\n    float L = draw_data->DisplayPos.x;\n    float R = draw_data->DisplayPos.x + draw_data->DisplaySize.x;\n    float T = draw_data->DisplayPos.y;\n    float B = draw_data->DisplayPos.y + draw_data->DisplaySize.y;\n    const float ortho_projection[4][4] =\n    {\n        { 2.0f/(R-L),   0.0f,         0.0f,   0.0f },\n        { 0.0f,         2.0f/(T-B),   0.0f,   0.0f },\n        { 0.0f,         0.0f,        -1.0f,   0.0f },\n        { (R+L)/(L-R),  (T+B)/(B-T),  0.0f,   1.0f },\n    };\n    glUseProgram(g_ShaderHandle);\n    glUniform1i(g_AttribLocationTex, 0);\n    glUniformMatrix4fv(g_AttribLocationProjMtx, 1, GL_FALSE, &ortho_projection[0][0]);\n#ifdef GL_SAMPLER_BINDING\n    glBindSampler(0, 0); // We use combined texture/sampler state. Applications using GL 3.3 may set that otherwise.\n#endif\n\n    (void)vertex_array_object;\n#ifndef IMGUI_IMPL_OPENGL_ES2\n    glBindVertexArray(vertex_array_object);\n#endif\n\n    // Bind vertex/index buffers and setup attributes for ImDrawVert\n    glBindBuffer(GL_ARRAY_BUFFER, g_VboHandle);\n    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, g_ElementsHandle);\n    glEnableVertexAttribArray(g_AttribLocationVtxPos);\n    glEnableVertexAttribArray(g_AttribLocationVtxUV);\n    glEnableVertexAttribArray(g_AttribLocationVtxColor);\n    glVertexAttribPointer(g_AttribLocationVtxPos,   2, GL_FLOAT,         GL_FALSE, sizeof(ImDrawVert), (GLvoid*)IM_OFFSETOF(ImDrawVert, pos));\n    glVertexAttribPointer(g_AttribLocationVtxUV,    2, GL_FLOAT,         GL_FALSE, sizeof(ImDrawVert), (GLvoid*)IM_OFFSETOF(ImDrawVert, uv));\n    glVertexAttribPointer(g_AttribLocationVtxColor, 4, GL_UNSIGNED_BYTE, GL_TRUE,  sizeof(ImDrawVert), (GLvoid*)IM_OFFSETOF(ImDrawVert, col));\n}\n\n// OpenGL3 Render function.\n// (this used to be set in io.RenderDrawListsFn and called by ImGui::Render(), but you can now call this directly from your main loop)\n// 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.\nvoid    ImGui_ImplOpenGL3_RenderDrawData(ImDrawData* draw_data)\n{\n    // Avoid rendering when minimized, scale coordinates for retina displays (screen coordinates != framebuffer coordinates)\n    int fb_width = (int)(draw_data->DisplaySize.x * draw_data->FramebufferScale.x);\n    int fb_height = (int)(draw_data->DisplaySize.y * draw_data->FramebufferScale.y);\n    if (fb_width <= 0 || fb_height <= 0)\n        return;\n\n    // Backup GL state\n    GLenum last_active_texture; glGetIntegerv(GL_ACTIVE_TEXTURE, (GLint*)&last_active_texture);\n    glActiveTexture(GL_TEXTURE0);\n    GLint last_program; glGetIntegerv(GL_CURRENT_PROGRAM, &last_program);\n    GLint last_texture; glGetIntegerv(GL_TEXTURE_BINDING_2D, &last_texture);\n#ifdef GL_SAMPLER_BINDING\n    GLint last_sampler; glGetIntegerv(GL_SAMPLER_BINDING, &last_sampler);\n#endif\n    GLint last_array_buffer; glGetIntegerv(GL_ARRAY_BUFFER_BINDING, &last_array_buffer);\n#ifndef IMGUI_IMPL_OPENGL_ES2\n    GLint last_vertex_array_object; glGetIntegerv(GL_VERTEX_ARRAY_BINDING, &last_vertex_array_object);\n#endif\n#ifdef GL_POLYGON_MODE\n    GLint last_polygon_mode[2]; glGetIntegerv(GL_POLYGON_MODE, last_polygon_mode);\n#endif\n    GLint last_viewport[4]; glGetIntegerv(GL_VIEWPORT, last_viewport);\n    GLint last_scissor_box[4]; glGetIntegerv(GL_SCISSOR_BOX, last_scissor_box);\n    GLenum last_blend_src_rgb; glGetIntegerv(GL_BLEND_SRC_RGB, (GLint*)&last_blend_src_rgb);\n    GLenum last_blend_dst_rgb; glGetIntegerv(GL_BLEND_DST_RGB, (GLint*)&last_blend_dst_rgb);\n    GLenum last_blend_src_alpha; glGetIntegerv(GL_BLEND_SRC_ALPHA, (GLint*)&last_blend_src_alpha);\n    GLenum last_blend_dst_alpha; glGetIntegerv(GL_BLEND_DST_ALPHA, (GLint*)&last_blend_dst_alpha);\n    GLenum last_blend_equation_rgb; glGetIntegerv(GL_BLEND_EQUATION_RGB, (GLint*)&last_blend_equation_rgb);\n    GLenum last_blend_equation_alpha; glGetIntegerv(GL_BLEND_EQUATION_ALPHA, (GLint*)&last_blend_equation_alpha);\n    GLboolean last_enable_blend = glIsEnabled(GL_BLEND);\n    GLboolean last_enable_cull_face = glIsEnabled(GL_CULL_FACE);\n    GLboolean last_enable_depth_test = glIsEnabled(GL_DEPTH_TEST);\n    GLboolean last_enable_scissor_test = glIsEnabled(GL_SCISSOR_TEST);\n    bool clip_origin_lower_left = true;\n#if defined(GL_CLIP_ORIGIN) && !defined(__APPLE__)\n    GLenum last_clip_origin = 0; glGetIntegerv(GL_CLIP_ORIGIN, (GLint*)&last_clip_origin); // Support for GL 4.5's glClipControl(GL_UPPER_LEFT)\n    if (last_clip_origin == GL_UPPER_LEFT)\n        clip_origin_lower_left = false;\n#endif\n\n    // Setup desired GL state\n    // Recreate the VAO every time (this is to easily allow multiple GL contexts to be rendered to. VAO are not shared among GL contexts)\n    // The renderer would actually work without any VAO bound, but then our VertexAttrib calls would overwrite the default one currently bound.\n    GLuint vertex_array_object = 0;\n#ifndef IMGUI_IMPL_OPENGL_ES2\n    glGenVertexArrays(1, &vertex_array_object);\n#endif\n    ImGui_ImplOpenGL3_SetupRenderState(draw_data, fb_width, fb_height, vertex_array_object);\n\n    // Will project scissor/clipping rectangles into framebuffer space\n    ImVec2 clip_off = draw_data->DisplayPos;         // (0,0) unless using multi-viewports\n    ImVec2 clip_scale = draw_data->FramebufferScale; // (1,1) unless using retina display which are often (2,2)\n\n    // Render command lists\n    for (int n = 0; n < draw_data->CmdListsCount; n++)\n    {\n        const ImDrawList* cmd_list = draw_data->CmdLists[n];\n\n        // Upload vertex/index buffers\n        glBufferData(GL_ARRAY_BUFFER, (GLsizeiptr)cmd_list->VtxBuffer.Size * sizeof(ImDrawVert), (const GLvoid*)cmd_list->VtxBuffer.Data, GL_STREAM_DRAW);\n        glBufferData(GL_ELEMENT_ARRAY_BUFFER, (GLsizeiptr)cmd_list->IdxBuffer.Size * sizeof(ImDrawIdx), (const GLvoid*)cmd_list->IdxBuffer.Data, GL_STREAM_DRAW);\n\n        for (int cmd_i = 0; cmd_i < cmd_list->CmdBuffer.Size; cmd_i++)\n        {\n            const ImDrawCmd* pcmd = &cmd_list->CmdBuffer[cmd_i];\n            if (pcmd->UserCallback != NULL)\n            {\n                // User callback, registered via ImDrawList::AddCallback()\n                // (ImDrawCallback_ResetRenderState is a special callback value used by the user to request the renderer to reset render state.)\n                if (pcmd->UserCallback == ImDrawCallback_ResetRenderState)\n                    ImGui_ImplOpenGL3_SetupRenderState(draw_data, fb_width, fb_height, vertex_array_object);\n                else\n                    pcmd->UserCallback(cmd_list, pcmd);\n            }\n            else\n            {\n                // Project scissor/clipping rectangles into framebuffer space\n                ImVec4 clip_rect;\n                clip_rect.x = (pcmd->ClipRect.x - clip_off.x) * clip_scale.x;\n                clip_rect.y = (pcmd->ClipRect.y - clip_off.y) * clip_scale.y;\n                clip_rect.z = (pcmd->ClipRect.z - clip_off.x) * clip_scale.x;\n                clip_rect.w = (pcmd->ClipRect.w - clip_off.y) * clip_scale.y;\n\n                if (clip_rect.x < fb_width && clip_rect.y < fb_height && clip_rect.z >= 0.0f && clip_rect.w >= 0.0f)\n                {\n                    // Apply scissor/clipping rectangle\n                    if (clip_origin_lower_left)\n                        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));\n                    else\n                        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)\n\n                    // Bind texture, Draw\n                    glBindTexture(GL_TEXTURE_2D, (GLuint)(intptr_t)pcmd->TextureId);\n#if IMGUI_IMPL_OPENGL_MAY_HAVE_VTX_OFFSET\n                    if (g_GlVersion >= 3200)\n                        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);\n                    else\n#endif\n                    glDrawElements(GL_TRIANGLES, (GLsizei)pcmd->ElemCount, sizeof(ImDrawIdx) == 2 ? GL_UNSIGNED_SHORT : GL_UNSIGNED_INT, (void*)(intptr_t)(pcmd->IdxOffset * sizeof(ImDrawIdx)));\n                }\n            }\n        }\n    }\n\n    // Destroy the temporary VAO\n#ifndef IMGUI_IMPL_OPENGL_ES2\n    glDeleteVertexArrays(1, &vertex_array_object);\n#endif\n\n    // Restore modified GL state\n    glUseProgram(last_program);\n    glBindTexture(GL_TEXTURE_2D, last_texture);\n#ifdef GL_SAMPLER_BINDING\n    glBindSampler(0, last_sampler);\n#endif\n    glActiveTexture(last_active_texture);\n#ifndef IMGUI_IMPL_OPENGL_ES2\n    glBindVertexArray(last_vertex_array_object);\n#endif\n    glBindBuffer(GL_ARRAY_BUFFER, last_array_buffer);\n    glBlendEquationSeparate(last_blend_equation_rgb, last_blend_equation_alpha);\n    glBlendFuncSeparate(last_blend_src_rgb, last_blend_dst_rgb, last_blend_src_alpha, last_blend_dst_alpha);\n    if (last_enable_blend) glEnable(GL_BLEND); else glDisable(GL_BLEND);\n    if (last_enable_cull_face) glEnable(GL_CULL_FACE); else glDisable(GL_CULL_FACE);\n    if (last_enable_depth_test) glEnable(GL_DEPTH_TEST); else glDisable(GL_DEPTH_TEST);\n    if (last_enable_scissor_test) glEnable(GL_SCISSOR_TEST); else glDisable(GL_SCISSOR_TEST);\n#ifdef GL_POLYGON_MODE\n    glPolygonMode(GL_FRONT_AND_BACK, (GLenum)last_polygon_mode[0]);\n#endif\n    glViewport(last_viewport[0], last_viewport[1], (GLsizei)last_viewport[2], (GLsizei)last_viewport[3]);\n    glScissor(last_scissor_box[0], last_scissor_box[1], (GLsizei)last_scissor_box[2], (GLsizei)last_scissor_box[3]);\n}\n\nbool ImGui_ImplOpenGL3_CreateFontsTexture()\n{\n    // Build texture atlas\n    ImGuiIO& io = ImGui::GetIO();\n    unsigned char* pixels;\n    int width, height;\n    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.\n\n    // Upload texture to graphics system\n    GLint last_texture;\n    glGetIntegerv(GL_TEXTURE_BINDING_2D, &last_texture);\n    glGenTextures(1, &g_FontTexture);\n    glBindTexture(GL_TEXTURE_2D, g_FontTexture);\n    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);\n    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);\n#ifdef GL_UNPACK_ROW_LENGTH\n    glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);\n#endif\n    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels);\n\n    // Store our identifier\n    io.Fonts->TexID = (ImTextureID)(intptr_t)g_FontTexture;\n\n    // Restore state\n    glBindTexture(GL_TEXTURE_2D, last_texture);\n\n    return true;\n}\n\nvoid ImGui_ImplOpenGL3_DestroyFontsTexture()\n{\n    if (g_FontTexture)\n    {\n        ImGuiIO& io = ImGui::GetIO();\n        glDeleteTextures(1, &g_FontTexture);\n        io.Fonts->TexID = 0;\n        g_FontTexture = 0;\n    }\n}\n\n// 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.\nstatic bool CheckShader(GLuint handle, const char* desc)\n{\n    GLint status = 0, log_length = 0;\n    glGetShaderiv(handle, GL_COMPILE_STATUS, &status);\n    glGetShaderiv(handle, GL_INFO_LOG_LENGTH, &log_length);\n    if ((GLboolean)status == GL_FALSE)\n        fprintf(stderr, \"ERROR: ImGui_ImplOpenGL3_CreateDeviceObjects: failed to compile %s!\\n\", desc);\n    if (log_length > 1)\n    {\n        ImVector<char> buf;\n        buf.resize((int)(log_length + 1));\n        glGetShaderInfoLog(handle, log_length, NULL, (GLchar*)buf.begin());\n        fprintf(stderr, \"%s\\n\", buf.begin());\n    }\n    return (GLboolean)status == GL_TRUE;\n}\n\n// If you get an error please report on GitHub. You may try different GL context version or GLSL version.\nstatic bool CheckProgram(GLuint handle, const char* desc)\n{\n    GLint status = 0, log_length = 0;\n    glGetProgramiv(handle, GL_LINK_STATUS, &status);\n    glGetProgramiv(handle, GL_INFO_LOG_LENGTH, &log_length);\n    if ((GLboolean)status == GL_FALSE)\n        fprintf(stderr, \"ERROR: ImGui_ImplOpenGL3_CreateDeviceObjects: failed to link %s! (with GLSL '%s')\\n\", desc, g_GlslVersionString);\n    if (log_length > 1)\n    {\n        ImVector<char> buf;\n        buf.resize((int)(log_length + 1));\n        glGetProgramInfoLog(handle, log_length, NULL, (GLchar*)buf.begin());\n        fprintf(stderr, \"%s\\n\", buf.begin());\n    }\n    return (GLboolean)status == GL_TRUE;\n}\n\nbool    ImGui_ImplOpenGL3_CreateDeviceObjects()\n{\n    // Backup GL state\n    GLint last_texture, last_array_buffer;\n    glGetIntegerv(GL_TEXTURE_BINDING_2D, &last_texture);\n    glGetIntegerv(GL_ARRAY_BUFFER_BINDING, &last_array_buffer);\n#ifndef IMGUI_IMPL_OPENGL_ES2\n    GLint last_vertex_array;\n    glGetIntegerv(GL_VERTEX_ARRAY_BINDING, &last_vertex_array);\n#endif\n\n    // Parse GLSL version string\n    int glsl_version = 130;\n    sscanf(g_GlslVersionString, \"#version %d\", &glsl_version);\n\n    const GLchar* vertex_shader_glsl_120 =\n        \"uniform mat4 ProjMtx;\\n\"\n        \"attribute vec2 Position;\\n\"\n        \"attribute vec2 UV;\\n\"\n        \"attribute vec4 Color;\\n\"\n        \"varying vec2 Frag_UV;\\n\"\n        \"varying vec4 Frag_Color;\\n\"\n        \"void main()\\n\"\n        \"{\\n\"\n        \"    Frag_UV = UV;\\n\"\n        \"    Frag_Color = Color;\\n\"\n        \"    gl_Position = ProjMtx * vec4(Position.xy,0,1);\\n\"\n        \"}\\n\";\n\n    const GLchar* vertex_shader_glsl_130 =\n        \"uniform mat4 ProjMtx;\\n\"\n        \"in vec2 Position;\\n\"\n        \"in vec2 UV;\\n\"\n        \"in vec4 Color;\\n\"\n        \"out vec2 Frag_UV;\\n\"\n        \"out vec4 Frag_Color;\\n\"\n        \"void main()\\n\"\n        \"{\\n\"\n        \"    Frag_UV = UV;\\n\"\n        \"    Frag_Color = Color;\\n\"\n        \"    gl_Position = ProjMtx * vec4(Position.xy,0,1);\\n\"\n        \"}\\n\";\n\n    const GLchar* vertex_shader_glsl_300_es =\n        \"precision mediump float;\\n\"\n        \"layout (location = 0) in vec2 Position;\\n\"\n        \"layout (location = 1) in vec2 UV;\\n\"\n        \"layout (location = 2) in vec4 Color;\\n\"\n        \"uniform mat4 ProjMtx;\\n\"\n        \"out vec2 Frag_UV;\\n\"\n        \"out vec4 Frag_Color;\\n\"\n        \"void main()\\n\"\n        \"{\\n\"\n        \"    Frag_UV = UV;\\n\"\n        \"    Frag_Color = Color;\\n\"\n        \"    gl_Position = ProjMtx * vec4(Position.xy,0,1);\\n\"\n        \"}\\n\";\n\n    const GLchar* vertex_shader_glsl_410_core =\n        \"layout (location = 0) in vec2 Position;\\n\"\n        \"layout (location = 1) in vec2 UV;\\n\"\n        \"layout (location = 2) in vec4 Color;\\n\"\n        \"uniform mat4 ProjMtx;\\n\"\n        \"out vec2 Frag_UV;\\n\"\n        \"out vec4 Frag_Color;\\n\"\n        \"void main()\\n\"\n        \"{\\n\"\n        \"    Frag_UV = UV;\\n\"\n        \"    Frag_Color = Color;\\n\"\n        \"    gl_Position = ProjMtx * vec4(Position.xy,0,1);\\n\"\n        \"}\\n\";\n\n    const GLchar* fragment_shader_glsl_120 =\n        \"#ifdef GL_ES\\n\"\n        \"    precision mediump float;\\n\"\n        \"#endif\\n\"\n        \"uniform sampler2D Texture;\\n\"\n        \"varying vec2 Frag_UV;\\n\"\n        \"varying vec4 Frag_Color;\\n\"\n        \"void main()\\n\"\n        \"{\\n\"\n        \"    gl_FragColor = Frag_Color * texture2D(Texture, Frag_UV.st);\\n\"\n        \"}\\n\";\n\n    const GLchar* fragment_shader_glsl_130 =\n        \"uniform sampler2D Texture;\\n\"\n        \"in vec2 Frag_UV;\\n\"\n        \"in vec4 Frag_Color;\\n\"\n        \"out vec4 Out_Color;\\n\"\n        \"void main()\\n\"\n        \"{\\n\"\n        \"    Out_Color = Frag_Color * texture(Texture, Frag_UV.st);\\n\"\n        \"}\\n\";\n\n    const GLchar* fragment_shader_glsl_300_es =\n        \"precision mediump float;\\n\"\n        \"uniform sampler2D Texture;\\n\"\n        \"in vec2 Frag_UV;\\n\"\n        \"in vec4 Frag_Color;\\n\"\n        \"layout (location = 0) out vec4 Out_Color;\\n\"\n        \"void main()\\n\"\n        \"{\\n\"\n        \"    Out_Color = Frag_Color * texture(Texture, Frag_UV.st);\\n\"\n        \"}\\n\";\n\n    const GLchar* fragment_shader_glsl_410_core =\n        \"in vec2 Frag_UV;\\n\"\n        \"in vec4 Frag_Color;\\n\"\n        \"uniform sampler2D Texture;\\n\"\n        \"layout (location = 0) out vec4 Out_Color;\\n\"\n        \"void main()\\n\"\n        \"{\\n\"\n        \"    Out_Color = Frag_Color * texture(Texture, Frag_UV.st);\\n\"\n        \"}\\n\";\n\n    // Select shaders matching our GLSL versions\n    const GLchar* vertex_shader = NULL;\n    const GLchar* fragment_shader = NULL;\n    if (glsl_version < 130)\n    {\n        vertex_shader = vertex_shader_glsl_120;\n        fragment_shader = fragment_shader_glsl_120;\n    }\n    else if (glsl_version >= 410)\n    {\n        vertex_shader = vertex_shader_glsl_410_core;\n        fragment_shader = fragment_shader_glsl_410_core;\n    }\n    else if (glsl_version == 300)\n    {\n        vertex_shader = vertex_shader_glsl_300_es;\n        fragment_shader = fragment_shader_glsl_300_es;\n    }\n    else\n    {\n        vertex_shader = vertex_shader_glsl_130;\n        fragment_shader = fragment_shader_glsl_130;\n    }\n\n    // Create shaders\n    const GLchar* vertex_shader_with_version[2] = { g_GlslVersionString, vertex_shader };\n    g_VertHandle = glCreateShader(GL_VERTEX_SHADER);\n    glShaderSource(g_VertHandle, 2, vertex_shader_with_version, NULL);\n    glCompileShader(g_VertHandle);\n    CheckShader(g_VertHandle, \"vertex shader\");\n\n    const GLchar* fragment_shader_with_version[2] = { g_GlslVersionString, fragment_shader };\n    g_FragHandle = glCreateShader(GL_FRAGMENT_SHADER);\n    glShaderSource(g_FragHandle, 2, fragment_shader_with_version, NULL);\n    glCompileShader(g_FragHandle);\n    CheckShader(g_FragHandle, \"fragment shader\");\n\n    g_ShaderHandle = glCreateProgram();\n    glAttachShader(g_ShaderHandle, g_VertHandle);\n    glAttachShader(g_ShaderHandle, g_FragHandle);\n    glLinkProgram(g_ShaderHandle);\n    CheckProgram(g_ShaderHandle, \"shader program\");\n\n    g_AttribLocationTex = glGetUniformLocation(g_ShaderHandle, \"Texture\");\n    g_AttribLocationProjMtx = glGetUniformLocation(g_ShaderHandle, \"ProjMtx\");\n    g_AttribLocationVtxPos = glGetAttribLocation(g_ShaderHandle, \"Position\");\n    g_AttribLocationVtxUV = glGetAttribLocation(g_ShaderHandle, \"UV\");\n    g_AttribLocationVtxColor = glGetAttribLocation(g_ShaderHandle, \"Color\");\n\n    // Create buffers\n    glGenBuffers(1, &g_VboHandle);\n    glGenBuffers(1, &g_ElementsHandle);\n\n    ImGui_ImplOpenGL3_CreateFontsTexture();\n\n    // Restore modified GL state\n    glBindTexture(GL_TEXTURE_2D, last_texture);\n    glBindBuffer(GL_ARRAY_BUFFER, last_array_buffer);\n#ifndef IMGUI_IMPL_OPENGL_ES2\n    glBindVertexArray(last_vertex_array);\n#endif\n\n    return true;\n}\n\nvoid    ImGui_ImplOpenGL3_DestroyDeviceObjects()\n{\n    if (g_VboHandle)        { glDeleteBuffers(1, &g_VboHandle); g_VboHandle = 0; }\n    if (g_ElementsHandle)   { glDeleteBuffers(1, &g_ElementsHandle); g_ElementsHandle = 0; }\n    if (g_ShaderHandle && g_VertHandle) { glDetachShader(g_ShaderHandle, g_VertHandle); }\n    if (g_ShaderHandle && g_FragHandle) { glDetachShader(g_ShaderHandle, g_FragHandle); }\n    if (g_VertHandle)       { glDeleteShader(g_VertHandle); g_VertHandle = 0; }\n    if (g_FragHandle)       { glDeleteShader(g_FragHandle); g_FragHandle = 0; }\n    if (g_ShaderHandle)     { glDeleteProgram(g_ShaderHandle); g_ShaderHandle = 0; }\n\n    ImGui_ImplOpenGL3_DestroyFontsTexture();\n}\n"
  },
  {
    "path": "examples/third-party/imgui/imgui-extra/imgui_impl_opengl3.h",
    "content": "// dear imgui: Renderer for modern OpenGL with shaders / programmatic pipeline\n// - Desktop GL: 2.x 3.x 4.x\n// - Embedded GL: ES 2.0 (WebGL 1.0), ES 3.0 (WebGL 2.0)\n// This needs to be used along with a Platform Binding (e.g. GLFW, SDL, Win32, custom..)\n\n// Implemented features:\n//  [X] Renderer: User texture binding. Use 'GLuint' OpenGL texture identifier as void*/ImTextureID. Read the FAQ about ImTextureID!\n//  [x] Renderer: Desktop GL only: Support for large meshes (64k+ vertices) with 16-bit indices.\n\n// You can copy and use unmodified imgui_impl_* files in your project. See main.cpp for an example of using this.\n// If you are new to dear imgui, read examples/README.txt and read the documentation at the top of imgui.cpp.\n// https://github.com/ocornut/imgui\n\n// About Desktop OpenGL function loaders:\n//  Modern desktop OpenGL doesn't have a standard portable header file to load OpenGL function pointers.\n//  Helper libraries are often used for this purpose! Here we are supporting a few common ones (gl3w, glew, glad).\n//  You may use another loader/header of your choice (glext, glLoadGen, etc.), or chose to manually implement your own.\n\n// About GLSL version:\n//  The 'glsl_version' initialization parameter should be NULL (default) or a \"#version XXX\" string.\n//  On computer platform the GLSL version default to \"#version 130\". On OpenGL ES 3 platform it defaults to \"#version 300 es\"\n//  Only override if your GL version doesn't handle this GLSL version. See GLSL version table at the top of imgui_impl_opengl3.cpp.\n\n#pragma once\n\n// Backend API\nIMGUI_IMPL_API bool     ImGui_ImplOpenGL3_Init(const char* glsl_version = NULL);\nIMGUI_IMPL_API void     ImGui_ImplOpenGL3_Shutdown();\nIMGUI_IMPL_API void     ImGui_ImplOpenGL3_NewFrame();\nIMGUI_IMPL_API void     ImGui_ImplOpenGL3_RenderDrawData(ImDrawData* draw_data);\n\n// (Optional) Called by Init/NewFrame/Shutdown\nIMGUI_IMPL_API bool     ImGui_ImplOpenGL3_CreateFontsTexture();\nIMGUI_IMPL_API void     ImGui_ImplOpenGL3_DestroyFontsTexture();\nIMGUI_IMPL_API bool     ImGui_ImplOpenGL3_CreateDeviceObjects();\nIMGUI_IMPL_API void     ImGui_ImplOpenGL3_DestroyDeviceObjects();\n\nIMGUI_IMPL_API void     ImGui_ImplOpenGL3_SaveState(int id);\nIMGUI_IMPL_API void     ImGui_ImplOpenGL3_LoadState(int id);\n\n// Specific OpenGL versions\n//#define IMGUI_IMPL_OPENGL_ES2     // Auto-detected on Emscripten\n//#define IMGUI_IMPL_OPENGL_ES3     // Auto-detected on iOS/Android\n\n// Desktop OpenGL: attempt to detect default GL loader based on available header files.\n// If auto-detection fails or doesn't select the same GL loader file as used by your application,\n// you are likely to get a crash in ImGui_ImplOpenGL3_Init().\n// You can explicitly select a loader by using '#define IMGUI_IMPL_OPENGL_LOADER_XXX' in imconfig.h or compiler command-line.\n#if !defined(IMGUI_IMPL_OPENGL_LOADER_GL3W) \\\n && !defined(IMGUI_IMPL_OPENGL_LOADER_GLEW) \\\n && !defined(IMGUI_IMPL_OPENGL_LOADER_GLAD) \\\n && !defined(IMGUI_IMPL_OPENGL_LOADER_GLBINDING) \\\n && !defined(IMGUI_IMPL_OPENGL_LOADER_CUSTOM)\n    #if defined(__has_include)\n        #if __has_include(<GL/glew.h>)\n            #define IMGUI_IMPL_OPENGL_LOADER_GLEW\n        #elif __has_include(<glad/glad.h>)\n            #define IMGUI_IMPL_OPENGL_LOADER_GLAD\n        #elif __has_include(<GL/gl3w.h>)\n            #define IMGUI_IMPL_OPENGL_LOADER_GL3W\n        #elif __has_include(<glbinding/gl/gl.h>)\n            #define IMGUI_IMPL_OPENGL_LOADER_GLBINDING\n        #else\n            #error \"Cannot detect OpenGL loader!\"\n        #endif\n    #else\n        #define IMGUI_IMPL_OPENGL_LOADER_GL3W       // Default to GL3W\n    #endif\n#endif\n\n"
  },
  {
    "path": "examples/third-party/imgui/imgui-extra/imgui_impl_sdl.cpp",
    "content": "// dear imgui: Platform Binding for SDL2\n// This needs to be used along with a Renderer (e.g. DirectX11, OpenGL3, Vulkan..)\n// (Info: SDL2 is a cross-platform general purpose library for handling windows, inputs, graphics context creation, etc.)\n// (Requires: SDL 2.0. Prefer SDL 2.0.4+ for full feature support.)\n\n// Implemented features:\n//  [X] Platform: Mouse cursor shape and visibility. Disable with 'io.ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange'.\n//  [X] Platform: Clipboard support.\n//  [X] Platform: Keyboard arrays indexed using SDL_SCANCODE_* codes, e.g. ImGui::IsKeyPressed(SDL_SCANCODE_SPACE).\n//  [X] Platform: Gamepad support. Enabled with 'io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad'.\n// Missing features:\n//  [ ] 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.\n\n// You can copy and use unmodified imgui_impl_* files in your project. See main.cpp for an example of using this.\n// If you are new to dear imgui, read examples/README.txt and read the documentation at the top of imgui.cpp.\n// https://github.com/ocornut/imgui\n\n// CHANGELOG\n// (minor and older changes stripped away, please see git history for details)\n//  2020-02-20: Inputs: Fixed mapping for ImGuiKey_KeyPadEnter (using SDL_SCANCODE_KP_ENTER instead of SDL_SCANCODE_RETURN2).\n//  2019-12-17: Inputs: On Wayland, use SDL_GetMouseState (because there is no global mouse state).\n//  2019-12-05: Inputs: Added support for ImGuiMouseCursor_NotAllowed mouse cursor.\n//  2019-07-21: Inputs: Added mapping for ImGuiKey_KeyPadEnter.\n//  2019-04-23: Inputs: Added support for SDL_GameController (if ImGuiConfigFlags_NavEnableGamepad is set by user application).\n//  2019-03-12: Misc: Preserve DisplayFramebufferScale when main window is minimized.\n//  2018-12-21: Inputs: Workaround for Android/iOS which don't seem to handle focus related calls.\n//  2018-11-30: Misc: Setting up io.BackendPlatformName so it can be displayed in the About Window.\n//  2018-11-14: Changed the signature of ImGui_ImplSDL2_ProcessEvent() to take a 'const SDL_Event*'.\n//  2018-08-01: Inputs: Workaround for Emscripten which doesn't seem to handle focus related calls.\n//  2018-06-29: Inputs: Added support for the ImGuiMouseCursor_Hand cursor.\n//  2018-06-08: Misc: Extracted imgui_impl_sdl.cpp/.h away from the old combined SDL2+OpenGL/Vulkan examples.\n//  2018-06-08: Misc: ImGui_ImplSDL2_InitForOpenGL() now takes a SDL_GLContext parameter.\n//  2018-05-09: Misc: Fixed clipboard paste memory leak (we didn't call SDL_FreeMemory on the data returned by SDL_GetClipboardText).\n//  2018-03-20: Misc: Setup io.BackendFlags ImGuiBackendFlags_HasMouseCursors flag + honor ImGuiConfigFlags_NoMouseCursorChange flag.\n//  2018-02-16: Inputs: Added support for mouse cursors, honoring ImGui::GetMouseCursor() value.\n//  2018-02-06: Misc: Removed call to ImGui::Shutdown() which is not available from 1.60 WIP, user needs to call CreateContext/DestroyContext themselves.\n//  2018-02-06: Inputs: Added mapping for ImGuiKey_Space.\n//  2018-02-05: Misc: Using SDL_GetPerformanceCounter() instead of SDL_GetTicks() to be able to handle very high framerate (1000+ FPS).\n//  2018-02-05: Inputs: Keyboard mapping is using scancodes everywhere instead of a confusing mixture of keycodes and scancodes.\n//  2018-01-20: Inputs: Added Horizontal Mouse Wheel support.\n//  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.\n//  2018-01-18: Inputs: Added mapping for ImGuiKey_Insert.\n//  2017-08-25: Inputs: MousePos set to -FLT_MAX,-FLT_MAX when mouse is unavailable/missing (instead of -1,-1).\n//  2016-10-15: Misc: Added a void* user_data parameter to Clipboard function handlers.\n\n#include \"imgui.h\"\n#include \"imgui_impl_sdl.h\"\n\n// SDL\n#include <SDL.h>\n#include <SDL_syswm.h>\n#if defined(__APPLE__)\n#include \"TargetConditionals.h\"\n#endif\n\n#define SDL_HAS_CAPTURE_AND_GLOBAL_MOUSE    SDL_VERSION_ATLEAST(2,0,4)\n#define SDL_HAS_VULKAN                      SDL_VERSION_ATLEAST(2,0,6)\n\n// Data\nstatic SDL_Window*  g_Window = NULL;\nstatic Uint64       g_Time = 0;\nstatic bool         g_MousePressed[3] = { false, false, false };\nstatic SDL_Cursor*  g_MouseCursors[ImGuiMouseCursor_COUNT] = {};\nstatic char*        g_ClipboardTextData = NULL;\nstatic bool         g_MouseCanUseGlobalState = true;\n\n#include <map>\n\nstruct State {\n    SDL_Window*  Window = NULL;\n    Uint64       Time = 0;\n    bool         MousePressed[3] = { false, false, false };\n    SDL_Cursor*  MouseCursors[ImGuiMouseCursor_COUNT] = {};\n    char*        ClipboardTextData = NULL;\n    bool         MouseCanUseGlobalState = true;\n};\n\nstatic std::map<int, State> g_state;\n\nvoid ImGui_ImplSDL2_SaveState(int id) {\n    auto & dst = g_state[id];\n\n    dst.Window                          = g_Window;\n    dst.Time                            = g_Time;\n    dst.MousePressed[0]                 = g_MousePressed[0];\n    dst.MousePressed[1]                 = g_MousePressed[1];\n    dst.MousePressed[2]                 = g_MousePressed[2];\n    for (int i = 0; i < ImGuiMouseCursor_COUNT; ++i) {\n        dst.MouseCursors[i]                 = g_MouseCursors[i];\n    }\n    dst.ClipboardTextData               = g_ClipboardTextData;\n    dst.MouseCanUseGlobalState          = g_MouseCanUseGlobalState;\n}\n\nvoid ImGui_ImplSDL2_LoadState(int id) {\n    const auto & src = g_state[id];\n\n    g_Window                          = src.Window;\n    g_Time                            = src.Time;\n    g_MousePressed[0]                 = src.MousePressed[0];\n    g_MousePressed[1]                 = src.MousePressed[1];\n    g_MousePressed[2]                 = src.MousePressed[2];\n    for (int i = 0; i < ImGuiMouseCursor_COUNT; ++i) {\n        g_MouseCursors[i]                 = src.MouseCursors[i];\n    }\n    g_ClipboardTextData               = src.ClipboardTextData;\n    g_MouseCanUseGlobalState          = src.MouseCanUseGlobalState;\n}\n\nstatic const char* ImGui_ImplSDL2_GetClipboardText(void*)\n{\n    if (g_ClipboardTextData)\n        SDL_free(g_ClipboardTextData);\n    g_ClipboardTextData = SDL_GetClipboardText();\n    return g_ClipboardTextData;\n}\n\nstatic void ImGui_ImplSDL2_SetClipboardText(void*, const char* text)\n{\n    SDL_SetClipboardText(text);\n}\n\n// You can read the io.WantCaptureMouse, io.WantCaptureKeyboard flags to tell if dear imgui wants to use your inputs.\n// - When io.WantCaptureMouse is true, do not dispatch mouse input data to your main application.\n// - When io.WantCaptureKeyboard is true, do not dispatch keyboard input data to your main application.\n// Generally you may always pass all inputs to dear imgui, and hide them from your application based on those two flags.\n// 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.\nbool ImGui_ImplSDL2_ProcessEvent(const SDL_Event* event)\n{\n    ImGuiIO& io = ImGui::GetIO();\n    switch (event->type)\n    {\n    case SDL_MOUSEWHEEL:\n        {\n            if (event->wheel.x > 0) io.MouseWheelH += 1;\n            if (event->wheel.x < 0) io.MouseWheelH -= 1;\n            if (event->wheel.y > 0) io.MouseWheel += 1;\n            if (event->wheel.y < 0) io.MouseWheel -= 1;\n            return true;\n        }\n    case SDL_MOUSEBUTTONDOWN:\n        {\n            if (event->button.button == SDL_BUTTON_LEFT) g_MousePressed[0] = true;\n            if (event->button.button == SDL_BUTTON_RIGHT) g_MousePressed[1] = true;\n            if (event->button.button == SDL_BUTTON_MIDDLE) g_MousePressed[2] = true;\n            return true;\n        }\n    case SDL_TEXTINPUT:\n        {\n            io.AddInputCharactersUTF8(event->text.text);\n            return true;\n        }\n    case SDL_KEYDOWN:\n    case SDL_KEYUP:\n        {\n            int key = event->key.keysym.scancode;\n            IM_ASSERT(key >= 0 && key < IM_ARRAYSIZE(io.KeysDown));\n            io.KeysDown[key] = (event->type == SDL_KEYDOWN);\n            io.KeyShift = ((SDL_GetModState() & KMOD_SHIFT) != 0);\n            io.KeyCtrl = ((SDL_GetModState() & KMOD_CTRL) != 0);\n            io.KeyAlt = ((SDL_GetModState() & KMOD_ALT) != 0);\n#ifdef _WIN32\n            io.KeySuper = false;\n#else\n            io.KeySuper = ((SDL_GetModState() & KMOD_GUI) != 0);\n#endif\n            return true;\n        }\n    }\n    return false;\n}\n\nstatic bool ImGui_ImplSDL2_Init(SDL_Window* window)\n{\n    g_Window = window;\n\n    // Setup back-end capabilities flags\n    ImGuiIO& io = ImGui::GetIO();\n    io.BackendFlags |= ImGuiBackendFlags_HasMouseCursors;       // We can honor GetMouseCursor() values (optional)\n    io.BackendFlags |= ImGuiBackendFlags_HasSetMousePos;        // We can honor io.WantSetMousePos requests (optional, rarely used)\n    io.BackendPlatformName = \"imgui_impl_sdl\";\n\n    // Keyboard mapping. ImGui will use those indices to peek into the io.KeysDown[] array.\n    io.KeyMap[ImGuiKey_Tab] = SDL_SCANCODE_TAB;\n    io.KeyMap[ImGuiKey_LeftArrow] = SDL_SCANCODE_LEFT;\n    io.KeyMap[ImGuiKey_RightArrow] = SDL_SCANCODE_RIGHT;\n    io.KeyMap[ImGuiKey_UpArrow] = SDL_SCANCODE_UP;\n    io.KeyMap[ImGuiKey_DownArrow] = SDL_SCANCODE_DOWN;\n    io.KeyMap[ImGuiKey_PageUp] = SDL_SCANCODE_PAGEUP;\n    io.KeyMap[ImGuiKey_PageDown] = SDL_SCANCODE_PAGEDOWN;\n    io.KeyMap[ImGuiKey_Home] = SDL_SCANCODE_HOME;\n    io.KeyMap[ImGuiKey_End] = SDL_SCANCODE_END;\n    io.KeyMap[ImGuiKey_Insert] = SDL_SCANCODE_INSERT;\n    io.KeyMap[ImGuiKey_Delete] = SDL_SCANCODE_DELETE;\n    io.KeyMap[ImGuiKey_Backspace] = SDL_SCANCODE_BACKSPACE;\n    io.KeyMap[ImGuiKey_Space] = SDL_SCANCODE_SPACE;\n    io.KeyMap[ImGuiKey_Enter] = SDL_SCANCODE_RETURN;\n    io.KeyMap[ImGuiKey_Escape] = SDL_SCANCODE_ESCAPE;\n    io.KeyMap[ImGuiKey_KeyPadEnter] = SDL_SCANCODE_KP_ENTER;\n    io.KeyMap[ImGuiKey_A] = SDL_SCANCODE_A;\n    io.KeyMap[ImGuiKey_C] = SDL_SCANCODE_C;\n    io.KeyMap[ImGuiKey_V] = SDL_SCANCODE_V;\n    io.KeyMap[ImGuiKey_X] = SDL_SCANCODE_X;\n    io.KeyMap[ImGuiKey_Y] = SDL_SCANCODE_Y;\n    io.KeyMap[ImGuiKey_Z] = SDL_SCANCODE_Z;\n\n    io.SetClipboardTextFn = ImGui_ImplSDL2_SetClipboardText;\n    io.GetClipboardTextFn = ImGui_ImplSDL2_GetClipboardText;\n    io.ClipboardUserData = NULL;\n\n    // Load mouse cursors\n    g_MouseCursors[ImGuiMouseCursor_Arrow] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_ARROW);\n    g_MouseCursors[ImGuiMouseCursor_TextInput] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_IBEAM);\n    g_MouseCursors[ImGuiMouseCursor_ResizeAll] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZEALL);\n    g_MouseCursors[ImGuiMouseCursor_ResizeNS] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZENS);\n    g_MouseCursors[ImGuiMouseCursor_ResizeEW] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZEWE);\n    g_MouseCursors[ImGuiMouseCursor_ResizeNESW] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZENESW);\n    g_MouseCursors[ImGuiMouseCursor_ResizeNWSE] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZENWSE);\n    g_MouseCursors[ImGuiMouseCursor_Hand] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_HAND);\n    g_MouseCursors[ImGuiMouseCursor_NotAllowed] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_NO);\n\n    // Check and store if we are on Wayland\n    g_MouseCanUseGlobalState = strncmp(SDL_GetCurrentVideoDriver(), \"wayland\", 7) != 0;\n\n#ifdef _WIN32\n    SDL_SysWMinfo wmInfo;\n    SDL_VERSION(&wmInfo.version);\n    SDL_GetWindowWMInfo(window, &wmInfo);\n    io.ImeWindowHandle = wmInfo.info.win.window;\n#else\n    (void)window;\n#endif\n\n    return true;\n}\n\nbool ImGui_ImplSDL2_InitForOpenGL(SDL_Window* window, void* sdl_gl_context)\n{\n    (void)sdl_gl_context; // Viewport branch will need this.\n    return ImGui_ImplSDL2_Init(window);\n}\n\nbool ImGui_ImplSDL2_InitForVulkan(SDL_Window* window)\n{\n#if !SDL_HAS_VULKAN\n    IM_ASSERT(0 && \"Unsupported\");\n#endif\n    return ImGui_ImplSDL2_Init(window);\n}\n\nbool ImGui_ImplSDL2_InitForD3D(SDL_Window* window)\n{\n#if !defined(_WIN32)\n    IM_ASSERT(0 && \"Unsupported\");\n#endif\n    return ImGui_ImplSDL2_Init(window);\n}\n\nbool ImGui_ImplSDL2_InitForMetal(SDL_Window* window)\n{\n    return ImGui_ImplSDL2_Init(window);\n}\n\nvoid ImGui_ImplSDL2_Shutdown()\n{\n    g_Window = NULL;\n\n    // Destroy last known clipboard data\n    if (g_ClipboardTextData)\n        SDL_free(g_ClipboardTextData);\n    g_ClipboardTextData = NULL;\n\n    // Destroy SDL mouse cursors\n    for (ImGuiMouseCursor cursor_n = 0; cursor_n < ImGuiMouseCursor_COUNT; cursor_n++)\n        SDL_FreeCursor(g_MouseCursors[cursor_n]);\n    memset(g_MouseCursors, 0, sizeof(g_MouseCursors));\n}\n\nstatic void ImGui_ImplSDL2_UpdateMousePosAndButtons()\n{\n    ImGuiIO& io = ImGui::GetIO();\n\n    // Set OS mouse position if requested (rarely used, only when ImGuiConfigFlags_NavEnableSetMousePos is enabled by user)\n    if (io.WantSetMousePos)\n        SDL_WarpMouseInWindow(g_Window, (int)io.MousePos.x, (int)io.MousePos.y);\n    else\n        io.MousePos = ImVec2(-FLT_MAX, -FLT_MAX);\n\n    int mx, my;\n    Uint32 mouse_buttons = SDL_GetMouseState(&mx, &my);\n    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.\n    io.MouseDown[1] = g_MousePressed[1] || (mouse_buttons & SDL_BUTTON(SDL_BUTTON_RIGHT)) != 0;\n    io.MouseDown[2] = g_MousePressed[2] || (mouse_buttons & SDL_BUTTON(SDL_BUTTON_MIDDLE)) != 0;\n    g_MousePressed[0] = g_MousePressed[1] = g_MousePressed[2] = false;\n\n#if SDL_HAS_CAPTURE_AND_GLOBAL_MOUSE && !defined(__EMSCRIPTEN__) && !defined(__ANDROID__) && !(defined(__APPLE__) && TARGET_OS_IOS)\n    SDL_Window* focused_window = SDL_GetKeyboardFocus();\n    if (g_Window == focused_window)\n    {\n        if (g_MouseCanUseGlobalState)\n        {\n            // SDL_GetMouseState() gives mouse position seemingly based on the last window entered/focused(?)\n            // 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.\n            // Won't use this workaround when on Wayland, as there is no global mouse position.\n            int wx, wy;\n            SDL_GetWindowPosition(focused_window, &wx, &wy);\n            SDL_GetGlobalMouseState(&mx, &my);\n            mx -= wx;\n            my -= wy;\n        }\n        io.MousePos = ImVec2((float)mx, (float)my);\n    }\n\n    // 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.\n    // The function is only supported from SDL 2.0.4 (released Jan 2016)\n    bool any_mouse_button_down = ImGui::IsAnyMouseDown();\n    SDL_CaptureMouse(any_mouse_button_down ? SDL_TRUE : SDL_FALSE);\n#else\n    if (SDL_GetWindowFlags(g_Window) & SDL_WINDOW_INPUT_FOCUS)\n        io.MousePos = ImVec2((float)mx, (float)my);\n#endif\n}\n\nstatic void ImGui_ImplSDL2_UpdateMouseCursor()\n{\n    ImGuiIO& io = ImGui::GetIO();\n    if (io.ConfigFlags & ImGuiConfigFlags_NoMouseCursorChange)\n        return;\n\n    ImGuiMouseCursor imgui_cursor = ImGui::GetMouseCursor();\n    if (io.MouseDrawCursor || imgui_cursor == ImGuiMouseCursor_None)\n    {\n        // Hide OS mouse cursor if imgui is drawing it or if it wants no cursor\n        SDL_ShowCursor(SDL_FALSE);\n    }\n    else\n    {\n        // Show OS mouse cursor\n        SDL_SetCursor(g_MouseCursors[imgui_cursor] ? g_MouseCursors[imgui_cursor] : g_MouseCursors[ImGuiMouseCursor_Arrow]);\n        SDL_ShowCursor(SDL_TRUE);\n    }\n}\n\nstatic void ImGui_ImplSDL2_UpdateGamepads()\n{\n    ImGuiIO& io = ImGui::GetIO();\n    memset(io.NavInputs, 0, sizeof(io.NavInputs));\n    if ((io.ConfigFlags & ImGuiConfigFlags_NavEnableGamepad) == 0)\n        return;\n\n    // Get gamepad\n    SDL_GameController* game_controller = SDL_GameControllerOpen(0);\n    if (!game_controller)\n    {\n        io.BackendFlags &= ~ImGuiBackendFlags_HasGamepad;\n        return;\n    }\n\n    // Update gamepad inputs\n    #define MAP_BUTTON(NAV_NO, BUTTON_NO)       { io.NavInputs[NAV_NO] = (SDL_GameControllerGetButton(game_controller, BUTTON_NO) != 0) ? 1.0f : 0.0f; }\n    #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; }\n    const int thumb_dead_zone = 8000;           // SDL_gamecontroller.h suggests using this value.\n    MAP_BUTTON(ImGuiNavInput_Activate,      SDL_CONTROLLER_BUTTON_A);               // Cross / A\n    MAP_BUTTON(ImGuiNavInput_Cancel,        SDL_CONTROLLER_BUTTON_B);               // Circle / B\n    MAP_BUTTON(ImGuiNavInput_Menu,          SDL_CONTROLLER_BUTTON_X);               // Square / X\n    MAP_BUTTON(ImGuiNavInput_Input,         SDL_CONTROLLER_BUTTON_Y);               // Triangle / Y\n    MAP_BUTTON(ImGuiNavInput_DpadLeft,      SDL_CONTROLLER_BUTTON_DPAD_LEFT);       // D-Pad Left\n    MAP_BUTTON(ImGuiNavInput_DpadRight,     SDL_CONTROLLER_BUTTON_DPAD_RIGHT);      // D-Pad Right\n    MAP_BUTTON(ImGuiNavInput_DpadUp,        SDL_CONTROLLER_BUTTON_DPAD_UP);         // D-Pad Up\n    MAP_BUTTON(ImGuiNavInput_DpadDown,      SDL_CONTROLLER_BUTTON_DPAD_DOWN);       // D-Pad Down\n    MAP_BUTTON(ImGuiNavInput_FocusPrev,     SDL_CONTROLLER_BUTTON_LEFTSHOULDER);    // L1 / LB\n    MAP_BUTTON(ImGuiNavInput_FocusNext,     SDL_CONTROLLER_BUTTON_RIGHTSHOULDER);   // R1 / RB\n    MAP_BUTTON(ImGuiNavInput_TweakSlow,     SDL_CONTROLLER_BUTTON_LEFTSHOULDER);    // L1 / LB\n    MAP_BUTTON(ImGuiNavInput_TweakFast,     SDL_CONTROLLER_BUTTON_RIGHTSHOULDER);   // R1 / RB\n    MAP_ANALOG(ImGuiNavInput_LStickLeft,    SDL_CONTROLLER_AXIS_LEFTX, -thumb_dead_zone, -32768);\n    MAP_ANALOG(ImGuiNavInput_LStickRight,   SDL_CONTROLLER_AXIS_LEFTX, +thumb_dead_zone, +32767);\n    MAP_ANALOG(ImGuiNavInput_LStickUp,      SDL_CONTROLLER_AXIS_LEFTY, -thumb_dead_zone, -32767);\n    MAP_ANALOG(ImGuiNavInput_LStickDown,    SDL_CONTROLLER_AXIS_LEFTY, +thumb_dead_zone, +32767);\n\n    io.BackendFlags |= ImGuiBackendFlags_HasGamepad;\n    #undef MAP_BUTTON\n    #undef MAP_ANALOG\n}\n\nvoid ImGui_ImplSDL2_NewFrame(SDL_Window* window)\n{\n    ImGuiIO& io = ImGui::GetIO();\n    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().\");\n\n    // Setup display size (every frame to accommodate for window resizing)\n    int w, h;\n    int display_w, display_h;\n    SDL_GetWindowSize(window, &w, &h);\n    SDL_GL_GetDrawableSize(window, &display_w, &display_h);\n    io.DisplaySize = ImVec2((float)w, (float)h);\n    if (w > 0 && h > 0)\n        io.DisplayFramebufferScale = ImVec2((float)display_w / w, (float)display_h / h);\n\n    // Setup time step (we don't use SDL_GetTicks() because it is using millisecond resolution)\n    static Uint64 frequency = SDL_GetPerformanceFrequency();\n    Uint64 current_time = SDL_GetPerformanceCounter();\n    io.DeltaTime = g_Time > 0 ? (float)((double)(current_time - g_Time) / frequency) : (float)(1.0f / 60.0f);\n    g_Time = current_time;\n\n    ImGui_ImplSDL2_UpdateMousePosAndButtons();\n    ImGui_ImplSDL2_UpdateMouseCursor();\n\n    // Update game controllers (if enabled and available)\n    ImGui_ImplSDL2_UpdateGamepads();\n}\n"
  },
  {
    "path": "examples/third-party/imgui/imgui-extra/imgui_impl_sdl.h",
    "content": "// dear imgui: Platform Binding for SDL2\n// This needs to be used along with a Renderer (e.g. DirectX11, OpenGL3, Vulkan..)\n// (Info: SDL2 is a cross-platform general purpose library for handling windows, inputs, graphics context creation, etc.)\n\n// Implemented features:\n//  [X] Platform: Mouse cursor shape and visibility. Disable with 'io.ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange'.\n//  [X] Platform: Clipboard support.\n//  [X] Platform: Keyboard arrays indexed using SDL_SCANCODE_* codes, e.g. ImGui::IsKeyPressed(SDL_SCANCODE_SPACE).\n//  [X] Platform: Gamepad support. Enabled with 'io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad'.\n// Missing features:\n//  [ ] 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.\n\n// You can copy and use unmodified imgui_impl_* files in your project. See main.cpp for an example of using this.\n// If you are new to dear imgui, read examples/README.txt and read the documentation at the top of imgui.cpp.\n// https://github.com/ocornut/imgui\n\n#pragma once\n\nstruct SDL_Window;\ntypedef union SDL_Event SDL_Event;\n\nIMGUI_IMPL_API bool     ImGui_ImplSDL2_InitForOpenGL(SDL_Window* window, void* sdl_gl_context);\nIMGUI_IMPL_API bool     ImGui_ImplSDL2_InitForVulkan(SDL_Window* window);\nIMGUI_IMPL_API bool     ImGui_ImplSDL2_InitForD3D(SDL_Window* window);\nIMGUI_IMPL_API bool     ImGui_ImplSDL2_InitForMetal(SDL_Window* window);\nIMGUI_IMPL_API void     ImGui_ImplSDL2_Shutdown();\nIMGUI_IMPL_API void     ImGui_ImplSDL2_NewFrame(SDL_Window* window);\nIMGUI_IMPL_API bool     ImGui_ImplSDL2_ProcessEvent(const SDL_Event* event);\n\nIMGUI_IMPL_API void     ImGui_ImplSDL2_SaveState(int id);\nIMGUI_IMPL_API void     ImGui_ImplSDL2_LoadState(int id);\n"
  },
  {
    "path": "examples/waver/CMakeLists.txt",
    "content": "set(TARGET waver)\n\nif (EMSCRIPTEN)\n    add_executable(${TARGET} main.cpp common.cpp interface.cpp interface-emscripten.cpp)\n\n    target_include_directories(${TARGET} PRIVATE\n        ..\n        ${SDL2_INCLUDE_DIRS}\n        ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${TARGET}/\n        )\n\n    target_link_libraries(${TARGET} PRIVATE\n        ggwave\n        ggwave-common\n        ggwave-common-sdl2\n        ggsock\n        imgui-sdl2\n        ${CMAKE_THREAD_LIBS_INIT}\n        )\n\n    set_target_properties(${TARGET} PROPERTIES LINK_FLAGS \" \\\n        -s FORCE_FILESYSTEM=1 \\\n        --preload-file ${CMAKE_CURRENT_SOURCE_DIR}/../assets/fonts@/ \\\n        \")\n\n    configure_file(${CMAKE_CURRENT_SOURCE_DIR}/build_timestamp-tmpl.h ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${TARGET}/build_timestamp.h @ONLY)\n    configure_file(${CMAKE_CURRENT_SOURCE_DIR}/index-tmpl.html ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${TARGET}/index.html @ONLY)\n    configure_file(${CMAKE_CURRENT_SOURCE_DIR}/style.css ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${TARGET}/style.css COPYONLY)\n    configure_file(${CMAKE_CURRENT_SOURCE_DIR}/background-0.png ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${TARGET}/background-0.png COPYONLY)\nelse()\n    add_executable(${TARGET} main.cpp common.cpp interface.cpp interface-unix.cpp)\n\n    target_include_directories(${TARGET} PRIVATE\n        ..\n        ${SDL2_INCLUDE_DIRS}\n        )\n\n    target_link_libraries(${TARGET} PRIVATE\n        ggwave\n        ggwave-common\n        ggwave-common-sdl2\n        ggsock\n        imgui-sdl2\n        ${CMAKE_THREAD_LIBS_INIT}\n        )\n\n    install(FILES ${PROJECT_SOURCE_DIR}/examples/assets/fonts/DroidSans.ttf DESTINATION bin)\n    install(FILES ${PROJECT_SOURCE_DIR}/examples/assets/fonts/fontawesome-webfont.ttf DESTINATION bin)\nendif()\n\ninstall(TARGETS ${TARGET} RUNTIME DESTINATION bin)\n"
  },
  {
    "path": "examples/waver/README.md",
    "content": "# Waver\n\nWaver allows you to send and receive text messages from nearby devices through sound waves.\n\nThis 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.\n\nThe 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.\n\n### Install \n\n<a href=\"https://apps.apple.com/us/app/waver-data-over-sound/id1543607865?itsct=apps_box&amp;itscg=30200&ign-itsct=apps_box#?platform=iphone\" style=\"display: inline-block; overflow: hidden; border-radius: 13px; width: 250px; height: 83px;\"><img height=\"60px\" src=\"https://tools.applemediaservices.com/api/badges/download-on-the-app-store/white/en-US?size=250x83&amp;releaseDate=1607558400&h=8e5fafc57929918f684abc83ff8311ef\" alt=\"Download on the App Store\"></a>\n<a href='https://play.google.com/store/apps/details?id=com.ggerganov.Waver&pcampaignid=pcampaignidMKT-Other-global-all-co-prtnr-py-PartBadge-Mar2515-1'><img alt='Get it on Google Play' src='https://i.imgur.com/BKDCbKv.png' height=\"60px\"/></a>\n<a href=\"https://snapcraft.io/waver\">\n<img alt=\"Get it from the Snap Store\" src=\"https://snapcraft.io/static/images/badges/en/snap-store-black.svg\" height=\"60px\"/>\n</a>\n\n#### Linux\n\n```bash\nsudo snap install waver\nsudo snap connect waver:audio-record :audio-record\n```\n\n#### Mac OS\n\n```bash\nbrew install ggerganov/ggerganov/waver\n```\n\n#### Run directly in the browser\n\nhttps://waver.ggerganov.com\n\n## How to use\n\nClick on the gif to watch a ~2 min Youtube video:\n\n<a href=\"https://youtu.be/Zcgf77T71QM\"><img width=\"100%\" src=\"../../media/waver-preview1.gif\"></img></a>\n\n- 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\n- To send a message - tap on \"Messages\", enter some text at the bottom of the screen and click \"Send\"\n- Any nearby device that is also running this application can capture the emitted sound and display the received message\n- 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\n- Tap on \"Spectrum\" to see a real-time frequency spectrum of the currently captured audio by your device's microphone\n\n\n## File sharing in a local network\n\nAs of v1.3.0 Waver supports file sharing. It works like this:\n\n- Add files that you would like to transmit by sharing them with Waver\n- In the \"Files\" menu, click on \"Broadcast\". This plays an audio message that contains a file broadcast offer\n- Nearby devices in the same local network can receive this offer and initiate a TCP/IP connection to your device\n- The files are transmitted over TCP/IP. The sound message is used only to initiate the network connections between the devices\n- Waver allows sharing multiple files to multiple devices at once\n\n## Known issues\n\n- 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\n- In some cases utlrasound transmission is not supported (see [#5](https://github.com/ggerganov/ggwave/issues/5))\n"
  },
  {
    "path": "examples/waver/build_timestamp-tmpl.h",
    "content": "static const char * BUILD_TIMESTAMP=\"@GIT_DATE@ (@GIT_SHA1@)\";\n"
  },
  {
    "path": "examples/waver/common.cpp",
    "content": "#include \"common.h\"\n\n#include \"ggwave-common.h\"\n\n#include \"ggwave/ggwave.h\"\n\n#include \"ggsock/communicator.h\"\n#include \"ggsock/file-server.h\"\n#include \"ggsock/serialization.h\"\n\n#include <imgui/imgui.h>\n#include <imgui/imgui_internal.h>\n\n#include <SDL.h>\n\n#include <array>\n#include <algorithm>\n#include <atomic>\n#include <cmath>\n#include <cstdio>\n#include <cstring>\n#include <ctime>\n#include <mutex>\n#include <sstream>\n#include <string>\n#include <thread>\n#include <vector>\n\n#if defined(IOS) || defined(ANDROID)\n#include \"imgui-wrapper/icons_font_awesome.h\"\n#endif\n\n#ifndef ICON_FA_COGS\n#include \"icons_font_awesome.h\"\n#endif\n\nnamespace {\n\nstd::mutex g_mutex;\nchar * toTimeString(const std::chrono::system_clock::time_point & tp) {\n    std::lock_guard<std::mutex> lock(g_mutex);\n\n    time_t t = std::chrono::system_clock::to_time_t(tp);\n    std::tm * ptm = std::localtime(&t);\n    static char buffer[32];\n    std::strftime(buffer, 32, \"%H:%M:%S\", ptm);\n    return buffer;\n}\n\nbool ScrollWhenDraggingOnVoid(const ImVec2& delta, ImGuiMouseButton mouse_button) {\n    ImGuiContext& g = *ImGui::GetCurrentContext();\n    ImGuiWindow* window = g.CurrentWindow;\n    bool hovered = false;\n    bool held = false;\n    bool dragging = false;\n    ImGuiButtonFlags button_flags = (mouse_button == 0) ? ImGuiButtonFlags_MouseButtonLeft : (mouse_button == 1) ? ImGuiButtonFlags_MouseButtonRight : ImGuiButtonFlags_MouseButtonMiddle;\n    if (g.HoveredId == 0) // If nothing hovered so far in the frame (not same as IsAnyItemHovered()!)\n        ImGui::ButtonBehavior(window->Rect(), window->GetID(\"##scrolldraggingoverlay\"), &hovered, &held, button_flags);\n    if (held && delta.x != 0.0f) {\n        ImGui::SetScrollX(window, window->Scroll.x + delta.x);\n    }\n    if (held && delta.y != 0.0f) {\n        ImGui::SetScrollY(window, window->Scroll.y + delta.y);\n        dragging = true;\n    }\n    return dragging;\n}\n\n}\n\nnamespace ImGui {\nbool ButtonDisabled(const char* label, const ImVec2& size = ImVec2(0, 0)) {\n    {\n        auto col = ImGui::GetStyleColorVec4(ImGuiCol_TextDisabled);\n        col.x *= 0.8;\n        col.y *= 0.8;\n        col.z *= 0.8;\n        PushStyleColor(ImGuiCol_Button, col);\n        PushStyleColor(ImGuiCol_ButtonHovered, col);\n        PushStyleColor(ImGuiCol_ButtonActive, col);\n    }\n    {\n        auto col = ImGui::GetStyleColorVec4(ImGuiCol_Text);\n        col.x *= 0.75;\n        col.y *= 0.75;\n        col.z *= 0.75;\n        PushStyleColor(ImGuiCol_Text, col);\n    }\n    bool result = Button(label, size);\n    PopStyleColor(4);\n    return result;\n}\n\nbool ButtonDisablable(const char* label, const ImVec2& size = ImVec2(0, 0), bool isDisabled = false) {\n    if (isDisabled) {\n        ButtonDisabled(label, size);\n        return false;\n    }\n    return Button(label, size);\n}\n\nbool ButtonSelected(const char* label, const ImVec2& size = ImVec2(0, 0)) {\n    auto col = ImGui::GetStyleColorVec4(ImGuiCol_ButtonActive);\n    PushStyleColor(ImGuiCol_Button, col);\n    PushStyleColor(ImGuiCol_ButtonHovered, col);\n    bool result = Button(label, size);\n    PopStyleColor(2);\n    return result;\n}\n\nbool ButtonSelectable(const char* label, const ImVec2& size = ImVec2(0, 0), bool isSelected = false) {\n    if (isSelected) return ButtonSelected(label, size);\n    return Button(label, size);\n}\n\n}\n\nstatic const char * kFileBroadcastPrefix = \"\\xba\\xbc\\xbb\";\nstatic const int kMaxSimultaneousChunkRequests = 4;\nstatic const float kBroadcastTime_sec = 60.0f;\n\nstruct Message {\n    enum Type {\n        Error,\n        Text,\n        FileBroadcast,\n    };\n\n    bool received;\n    std::chrono::system_clock::time_point timestamp;\n    std::string data;\n    int protocolId;\n    bool dss;\n    float volume;\n    Type type;\n};\n\nstruct GGWaveStats {\n    bool receiving;\n    bool analyzing;\n    int framesToRecord;\n    int framesLeftToRecord;\n    int framesToAnalyze;\n    int framesLeftToAnalyze;\n    int samplesPerFrame;\n    float sampleRateInp;\n    float sampleRateOut;\n    float sampleRate;\n    int sampleSizeInp;\n    int sampleSizeOut;\n};\n\nstruct State {\n    bool update = false;\n\n    struct Flags {\n        bool newMessage = false;\n        bool newSpectrum = false;\n        bool newTxAmplitude = false;\n        bool newStats = false;\n        bool newTxProtocols = false;\n\n        void clear() { memset(this, 0, sizeof(Flags)); }\n    } flags;\n\n    void apply(State & dst) {\n        if (update == false) return;\n\n        if (this->flags.newMessage) {\n            dst.update = true;\n            dst.flags.newMessage = true;\n            dst.message = std::move(this->message);\n        }\n\n        if (this->flags.newSpectrum) {\n            dst.update = true;\n            dst.flags.newSpectrum = true;\n            dst.rxSpectrum = std::move(this->rxSpectrum);\n        }\n\n        if (this->flags.newTxAmplitude) {\n            dst.update = true;\n            dst.flags.newTxAmplitude = true;\n            dst.txAmplitude.assign(this->txAmplitude);\n        }\n\n        if (this->flags.newStats) {\n            dst.update = true;\n            dst.flags.newStats = true;\n            dst.stats = std::move(this->stats);\n        }\n\n        if (this->flags.newTxProtocols) {\n            dst.update = true;\n            dst.flags.newTxProtocols = true;\n            dst.txProtocols = std::move(this->txProtocols);\n        }\n\n        flags.clear();\n        update = false;\n    }\n\n    Message message;\n\n    std::vector<float>   rxSpectrum;\n    GGWave::Amplitude    rxAmplitude;\n    GGWave::AmplitudeI16 txAmplitude;\n\n    GGWaveStats stats;\n    GGWave::TxProtocols txProtocols;\n};\n\nstruct Input {\n    bool update = true;\n\n    struct Flags {\n        bool newMessage = false;\n        bool needReinit = false;\n        bool changeNeedSpectrum = false;\n        bool stopReceiving = false;\n        bool changeRxProtocols = false;\n\n        void clear() { memset(this, 0, sizeof(Flags)); }\n    } flags;\n\n    void apply(Input & dst) {\n        if (update == false) return;\n\n        if (this->flags.newMessage) {\n            dst.update = true;\n            dst.flags.newMessage = true;\n            dst.message = std::move(this->message);\n        }\n\n        if (this->flags.needReinit) {\n            dst.update = true;\n            dst.flags.needReinit = true;\n            dst.sampleRateOffset = std::move(this->sampleRateOffset);\n            dst.freqStartShift = std::move(this->freqStartShift);\n            dst.payloadLength = std::move(this->payloadLength);\n            dst.directSequenceSpread = std::move(this->directSequenceSpread);\n        }\n\n        if (this->flags.changeNeedSpectrum) {\n            dst.update = true;\n            dst.flags.changeNeedSpectrum = true;\n            dst.needSpectrum = std::move(this->needSpectrum);\n        }\n\n        if (this->flags.stopReceiving) {\n            dst.update = true;\n            dst.flags.stopReceiving = true;\n        }\n\n        if (this->flags.changeRxProtocols) {\n            dst.update = true;\n            dst.flags.changeRxProtocols = true;\n            dst.rxProtocols = std::move(this->rxProtocols);\n        }\n\n        flags.clear();\n        update = false;\n    }\n\n    // message\n    Message message;\n\n    // reinit\n    float sampleRateOffset = 0;\n    int freqStartShift = 0;\n    int payloadLength = -1;\n\n    // spectrum\n    bool needSpectrum = false;\n\n    // other\n    bool directSequenceSpread = false;\n\n    // rx protocols\n    GGWave::RxProtocols rxProtocols;\n};\n\nstruct Buffer {\n    std::mutex mutex;\n\n    State stateCore;\n    Input inputCore;\n\n    State stateUI;\n    Input inputUI;\n};\n\nstd::atomic<bool> g_isRunning;\nBuffer g_buffer;\n\n// file send data\nstruct BroadcastInfo {\n    std::string ip;\n    int port;\n    int key;\n};\n\nbool g_focusFileSend = false;\nfloat g_tLastBroadcast = -100.0f;\nGGSock::FileServer g_fileServer;\n\n// file received data\nstruct FileInfoExtended {\n    bool receiving = false;\n    bool readyToShare = false;\n    bool requestToShare = false;\n    int nReceivedChunks = 0;\n    int nRequestedChunks = 0;\n    std::vector<bool> isChunkRequested;\n    std::vector<bool> isChunkReceived;\n};\n\nbool g_hasRemoteInfo = false;\nint g_remotePort = 23045;\nstd::string g_remoteIP = \"127.0.0.1\";\n\nbool g_hasReceivedFileInfos = false;\nbool g_hasRequestedFileInfos = false;\nbool g_hasReceivedFiles = false;\nGGSock::FileServer::TFileInfos g_receivedFileInfos;\nstd::map<GGSock::FileServer::TURI, GGSock::FileServer::FileData> g_receivedFiles;\nstd::map<GGSock::FileServer::TURI, FileInfoExtended> g_receivedFileInfosExtended;\n\nGGSock::Communicator g_fileClient(false);\n\n// external api\n\nint g_shareId = 0;\nShareInfo g_shareInfo;\n\nint g_openId = 0;\nOpenInfo g_openInfo;\n\nint g_deleteId = 0;\nDeleteInfo g_deleteInfo;\n\nint g_receivedId = 0;\n\nint getShareId() {\n    return g_shareId;\n}\n\nShareInfo getShareInfo() {\n    return g_shareInfo;\n}\n\nint getOpenId() {\n    return g_openId;\n}\n\nOpenInfo getOpenInfo() {\n    return g_openInfo;\n}\n\nint getDeleteId() {\n    return g_deleteId;\n}\n\nDeleteInfo getDeleteInfo() {\n    return g_deleteInfo;\n}\n\nint getReceivedId() {\n    return g_receivedId;\n}\n\nstd::vector<ReceiveInfo> getReceiveInfos() {\n    std::vector<ReceiveInfo> result;\n\n    for (const auto & file : g_receivedFiles) {\n        if (g_receivedFileInfosExtended[file.second.info.uri].requestToShare == false ||\n            g_receivedFileInfosExtended[file.second.info.uri].readyToShare == true) {\n            continue;\n        }\n        result.push_back({\n            file.second.info.uri.c_str(),\n            file.second.info.filename.c_str(),\n            file.second.data.data(),\n            file.second.data.size(),\n        });\n    }\n\n    return result;\n}\n\nbool confirmReceive(const char * uri) {\n    if (g_receivedFiles.find(uri) == g_receivedFiles.end()) {\n        return false;\n    }\n\n    g_receivedFileInfosExtended[uri].readyToShare = true;\n    g_receivedFiles.erase(uri);\n\n    return true;\n}\n\nvoid clearAllFiles() {\n    g_fileServer.clearAllFiles();\n}\n\nvoid clearFile(const char * uri) {\n    g_fileServer.clearFile(uri);\n}\n\nvoid addFile(\n        const char * uri,\n        const char * filename,\n        const char * dataBuffer,\n        size_t dataSize,\n        bool focus) {\n    GGSock::FileServer::FileData file;\n    file.info.uri = uri;\n    file.info.filename = filename;\n    file.data.resize(dataSize);\n    std::memcpy(file.data.data(), dataBuffer, dataSize);\n\n    g_fileServer.addFile(std::move(file));\n\n    if (focus) {\n        g_focusFileSend = true;\n    }\n}\n\nvoid addFile(\n        const char * uri,\n        const char * filename,\n        std::vector<char> && data,\n        bool focus) {\n    GGSock::FileServer::FileData file;\n    file.info.uri = uri;\n    file.info.filename = filename;\n    file.data = std::move(data);\n\n    g_fileServer.addFile(std::move(file));\n\n    if (focus) {\n        g_focusFileSend = true;\n    }\n}\n\nstd::string generateFileBroadcastMessage() {\n    // todo : to binary\n    std::string result;\n\n    int plen = (int) strlen(kFileBroadcastPrefix);\n    result.resize(plen + 4 + 2 + 2);\n\n    char *p = &result[0];\n    for (int i = 0; i < (int) plen; ++i) {\n        *p++ = kFileBroadcastPrefix[i];\n    }\n\n    {\n        auto ip = GGSock::Communicator::getLocalAddress();\n        std::replace(ip.begin(), ip.end(), '.', ' ');\n        std::stringstream ss(ip);\n\n        { int b; ss >> b; *p++ = b; }\n        { int b; ss >> b; *p++ = b; }\n        { int b; ss >> b; *p++ = b; }\n        { int b; ss >> b; *p++ = b; }\n    }\n\n    {\n        uint16_t port = g_fileServer.getParameters().listenPort;\n\n        { int b = port/256; *p++ = b; }\n        { int b = port%256; *p++ = b; }\n    }\n\n    {\n        uint16_t key = rand()%65536;\n\n        { int b = key/256; *p++ = b; }\n        { int b = key%256; *p++ = b; }\n    }\n\n    return result;\n}\n\nBroadcastInfo parseBroadcastInfo(const std::string & message) {\n    BroadcastInfo result;\n\n    const uint8_t *p = (uint8_t *) message.data();\n    p += strlen(kFileBroadcastPrefix);\n\n    result.ip += std::to_string((uint8_t)(*p++));\n    result.ip += '.';\n    result.ip += std::to_string((uint8_t)(*p++));\n    result.ip += '.';\n    result.ip += std::to_string((uint8_t)(*p++));\n    result.ip += '.';\n    result.ip += std::to_string((uint8_t)(*p++));\n\n    result.port  = 256*((int)(*p++));\n    result.port +=     ((int)(*p++));\n\n    result.key  = 256*((int)(*p++));\n    result.key +=     ((int)(*p++));\n\n    return result;\n}\n\nbool isFileBroadcastMessage(const std::string & message) {\n    if (message.size() != strlen(kFileBroadcastPrefix) + 4 + 2 + 2) {\n        return false;\n    }\n\n    bool result = true;\n\n    auto pSrc = kFileBroadcastPrefix;\n    auto pDst = message.data();\n\n    while (*pSrc != 0) {\n        if (*pDst == 0 || *pSrc++ != *pDst++) {\n            result = false;\n            break;\n        }\n    }\n\n    return result;\n}\n\nstd::thread initMainAndRunCore() {\n    initMain();\n\n    return std::thread([&]() {\n        while (g_isRunning) {\n            updateCore();\n\n            std::this_thread::sleep_for(std::chrono::milliseconds(1));\n        }\n    });\n}\n\nvoid initMain() {\n    g_isRunning = true;\n\n    GGSock::FileServer::Parameters p;\n#ifdef __EMSCRIPTEN__\n    p.nWorkerThreads = 0;\n#else\n    p.nWorkerThreads = 2;\n#endif\n    g_fileServer.init(p);\n\n    g_fileClient.setErrorCallback([](GGSock::Communicator::TErrorCode code) {\n        printf(\"Disconnected with code = %d\\n\", code);\n\n        g_hasReceivedFileInfos = false;\n        g_hasRequestedFileInfos = false;\n        g_hasReceivedFiles = false;\n    });\n\n    g_fileClient.setMessageCallback(GGSock::FileServer::MsgFileInfosResponse, [&](const char * dataBuffer, size_t dataSize) {\n        printf(\"Received message %d, size = %d\\n\", GGSock::FileServer::MsgFileInfosResponse, (int) dataSize);\n\n        size_t offset = 0;\n        GGSock::Unserialize()(g_receivedFileInfos, dataBuffer, dataSize, offset);\n\n        for (const auto & info : g_receivedFileInfos) {\n            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);\n            g_receivedFiles[info.second.uri].info = info.second;\n            g_receivedFiles[info.second.uri].data.resize(info.second.filesize);\n\n            g_receivedFileInfosExtended[info.second.uri] = {};\n        }\n\n        g_hasReceivedFileInfos = true;\n\n        return 0;\n    });\n\n    g_fileClient.setMessageCallback(GGSock::FileServer::MsgFileChunkResponse, [&](const char * dataBuffer, size_t dataSize) {\n        GGSock::FileServer::FileChunkResponseData data;\n\n        size_t offset = 0;\n        GGSock::Unserialize()(data, dataBuffer, dataSize, offset);\n\n        //printf(\"Received chunk %d for file '%s', size = %d\\n\", data.chunkId, data.uri.c_str(), (int) data.data.size());\n        std::memcpy(g_receivedFiles[data.uri].data.data() + data.pStart, data.data.data(), data.pLen);\n\n        g_receivedFileInfosExtended[data.uri].nReceivedChunks++;\n        g_receivedFileInfosExtended[data.uri].nRequestedChunks--;\n        g_receivedFileInfosExtended[data.uri].isChunkReceived[data.chunkId] = true;\n\n        return 0;\n    });\n}\n\nvoid updateCore() {\n    static Input inputCurrent;\n\n    static bool isFirstCall = true;\n    static bool needSpectrum = false;\n    static int rxDataLengthLast = 0;\n    static float rxTimestampLast = 0.0f;\n    static GGWave::TxRxData rxDataLast;\n\n    auto ggWave = GGWave_instance();\n    if (ggWave == nullptr) {\n        return;\n    }\n\n    {\n        std::lock_guard<std::mutex> lock(g_buffer.mutex);\n        g_buffer.inputCore.apply(inputCurrent);\n    }\n\n    if (inputCurrent.update) {\n        if (inputCurrent.flags.newMessage) {\n            int n = (int) inputCurrent.message.data.size();\n\n            if (ggWave->init(\n                    n, inputCurrent.message.data.data(),\n                    GGWave::TxProtocolId(inputCurrent.message.protocolId),\n                    100*inputCurrent.message.volume) == false) {\n                g_buffer.stateCore.update = true;\n                g_buffer.stateCore.flags.newMessage = true;\n                g_buffer.stateCore.message = {\n                    false,\n                    std::chrono::system_clock::now(),\n                    (GGWave::Protocols::tx()[inputCurrent.message.protocolId].extra == 2 && inputCurrent.payloadLength <= 0) ?\n                        \"MT protocols require fixed-length mode\" : \"Failed to transmit\",\n                    ggWave->rxProtocolId(),\n                    ggWave->isDSSEnabled(),\n                    0,\n                    Message::Error,\n                };\n            }\n        }\n\n        if (inputCurrent.flags.needReinit) {\n            static auto sampleRateInpOld = ggWave->sampleRateInp();\n            static auto sampleRateOutOld = ggWave->sampleRateOut();\n            static auto freqStartShiftOld = 0;\n\n            auto sampleFormatInpOld = ggWave->sampleFormatInp();\n            auto sampleFormatOutOld = ggWave->sampleFormatOut();\n            auto rxProtocolsOld = ggWave->rxProtocols();\n\n            for (int i = 0; i < GGWAVE_PROTOCOL_COUNT; ++i) {\n                GGWave::Protocols::tx()[i].freqStart = std::max(1, GGWave::Protocols::tx()[i].freqStart + inputCurrent.freqStartShift - freqStartShiftOld);\n                rxProtocolsOld[i].freqStart = std::max(1, rxProtocolsOld[i].freqStart + inputCurrent.freqStartShift - freqStartShiftOld);\n            }\n\n            freqStartShiftOld = inputCurrent.freqStartShift;\n\n            GGWave::OperatingMode mode = GGWAVE_OPERATING_MODE_RX_AND_TX;\n            if (inputCurrent.directSequenceSpread) mode |= GGWAVE_OPERATING_MODE_USE_DSS;\n\n            GGWave::Parameters parameters {\n                inputCurrent.payloadLength,\n                sampleRateInpOld,\n                sampleRateOutOld + inputCurrent.sampleRateOffset,\n                GGWave::kDefaultSampleRate,\n                GGWave::kDefaultSamplesPerFrame,\n                GGWave::kDefaultSoundMarkerThreshold,\n                sampleFormatInpOld,\n                sampleFormatOutOld,\n                mode,\n            };\n\n            GGWave_reset(&parameters);\n            ggWave = GGWave_instance();\n\n            ggWave->rxProtocols() = rxProtocolsOld;\n        }\n\n        if (inputCurrent.flags.changeNeedSpectrum) {\n            needSpectrum = inputCurrent.needSpectrum;\n        }\n\n        if (inputCurrent.flags.stopReceiving) {\n            ggWave->rxStopReceiving();\n        }\n\n        if (inputCurrent.flags.changeRxProtocols) {\n            ggWave->rxProtocols() = inputCurrent.rxProtocols;\n        }\n\n        inputCurrent.flags.clear();\n        inputCurrent.update = false;\n    }\n\n    GGWave_mainLoop();\n\n    rxDataLengthLast = ggWave->rxTakeData(rxDataLast);\n    if (rxDataLengthLast == -1) {\n        g_buffer.stateCore.update = true;\n        g_buffer.stateCore.flags.newMessage = true;\n        g_buffer.stateCore.message = {\n            true,\n            std::chrono::system_clock::now(),\n            \"Failed to decode\",\n            ggWave->rxProtocolId(),\n            ggWave->isDSSEnabled(),\n            0,\n            Message::Error,\n        };\n    } else if (rxDataLengthLast > 0 && ImGui::GetTime() - rxTimestampLast > 0.5f) {\n        auto message = std::string((char *) rxDataLast.data(), rxDataLengthLast);\n\n        const Message::Type type = isFileBroadcastMessage(message) ? Message::FileBroadcast : Message::Text;\n        g_buffer.stateCore.update = true;\n        g_buffer.stateCore.flags.newMessage = true;\n        g_buffer.stateCore.message = {\n            true,\n            std::chrono::system_clock::now(),\n            std::move(message),\n            ggWave->rxProtocolId(),\n            ggWave->isDSSEnabled(),\n            0,\n            type,\n        };\n\n        rxTimestampLast = ImGui::GetTime();\n    }\n\n    if (needSpectrum) {\n        if (ggWave->rxTakeAmplitude(g_buffer.stateCore.rxAmplitude)) {\n            static const int NMax = GGWave::kMaxSamplesPerFrame;\n            static float tmp[2*NMax];\n\n            const int N = ggWave->samplesPerFrame();\n            ggWave->computeFFTR(g_buffer.stateCore.rxAmplitude.data(), tmp, N);\n\n            g_buffer.stateCore.rxSpectrum.resize(N);\n            for (int i = 0; i < N; ++i) {\n                g_buffer.stateCore.rxSpectrum[i] = (tmp[2*i + 0]*tmp[2*i + 0] + tmp[2*i + 1]*tmp[2*i + 1]);\n            }\n            for (int i = 1; i < N/2; ++i) {\n                g_buffer.stateCore.rxSpectrum[i] += g_buffer.stateCore.rxSpectrum[N - i];\n            }\n\n            g_buffer.stateCore.update = true;\n            g_buffer.stateCore.flags.newSpectrum = true;\n        }\n    }\n\n    if (ggWave->txTakeAmplitudeI16(g_buffer.stateCore.txAmplitude)) {\n        g_buffer.stateCore.update = true;\n        g_buffer.stateCore.flags.newTxAmplitude = true;\n    }\n\n    if (true) {\n        g_buffer.stateCore.update = true;\n        g_buffer.stateCore.flags.newStats = true;\n        g_buffer.stateCore.stats.receiving           = ggWave->rxReceiving();\n        g_buffer.stateCore.stats.analyzing           = ggWave->rxAnalyzing();\n        g_buffer.stateCore.stats.framesToRecord      = ggWave->rxFramesToRecord();\n        g_buffer.stateCore.stats.framesLeftToRecord  = ggWave->rxFramesLeftToRecord();\n        g_buffer.stateCore.stats.framesToAnalyze     = ggWave->rxFramesToAnalyze();\n        g_buffer.stateCore.stats.framesLeftToAnalyze = ggWave->rxFramesLeftToAnalyze();\n        g_buffer.stateCore.stats.samplesPerFrame     = ggWave->samplesPerFrame();\n        g_buffer.stateCore.stats.sampleRateInp       = ggWave->sampleRateInp();\n        g_buffer.stateCore.stats.sampleRateOut       = ggWave->sampleRateOut();\n        g_buffer.stateCore.stats.sampleRate          = GGWave::kDefaultSampleRate;\n        g_buffer.stateCore.stats.sampleSizeInp       = ggWave->sampleSizeInp();\n        g_buffer.stateCore.stats.sampleSizeOut       = ggWave->sampleSizeOut();\n    }\n\n    if (isFirstCall) {\n        g_buffer.stateCore.update = true;\n        g_buffer.stateCore.flags.newTxProtocols = true;\n        g_buffer.stateCore.txProtocols = GGWave::Protocols::tx();\n\n        isFirstCall = false;\n    }\n\n    {\n        std::lock_guard<std::mutex> lock(g_buffer.mutex);\n        g_buffer.stateCore.apply(g_buffer.stateUI);\n    }\n}\n\nvoid renderMain() {\n    g_fileServer.update();\n\n    if (ImGui::GetTime() - g_tLastBroadcast > kBroadcastTime_sec && g_fileServer.isListening()) {\n        g_fileServer.stopListening();\n    }\n\n    if (g_fileClient.isConnected()) {\n        if (!g_hasRequestedFileInfos) {\n            g_receivedFileInfos.clear();\n            g_receivedFiles.clear();\n            g_receivedFileInfosExtended.clear();\n\n            g_fileClient.send(GGSock::FileServer::MsgFileInfosRequest);\n            g_hasRequestedFileInfos = true;\n        } else {\n            for (const auto & fileInfo : g_receivedFileInfos) {\n                const auto & uri = fileInfo.second.uri;\n                auto & fileInfoExtended = g_receivedFileInfosExtended[uri];\n\n                if (fileInfoExtended.receiving == false) {\n                    continue;\n                }\n\n                if (fileInfoExtended.nReceivedChunks == fileInfo.second.nChunks) {\n                    continue;\n                }\n\n                int nextChunkId = 0;\n                while (fileInfoExtended.nRequestedChunks < kMaxSimultaneousChunkRequests) {\n                    if (fileInfoExtended.nReceivedChunks + fileInfoExtended.nRequestedChunks == fileInfo.second.nChunks) {\n                        break;\n                    }\n\n                    while (fileInfoExtended.isChunkRequested[nextChunkId] == true) {\n                        ++nextChunkId;\n                    }\n                    fileInfoExtended.isChunkRequested[nextChunkId] = true;\n\n                    GGSock::FileServer::FileChunkRequestData data;\n                    data.uri = fileInfo.second.uri;\n                    data.chunkId = nextChunkId;\n                    data.nChunksHave = 0;\n                    data.nChunksExpected = fileInfo.second.nChunks;\n\n                    GGSock::SerializationBuffer buffer;\n                    GGSock::Serialize()(data, buffer);\n                    g_fileClient.send(GGSock::FileServer::MsgFileChunkRequest, buffer.data(), (int32_t) buffer.size());\n\n                    ++fileInfoExtended.nRequestedChunks;\n                }\n            }\n        }\n    }\n\n    g_fileClient.update();\n\n    static State stateCurrent;\n\n    {\n        std::lock_guard<std::mutex> lock(g_buffer.mutex);\n        g_buffer.stateUI.apply(stateCurrent);\n    }\n\n    enum class WindowId {\n        Settings,\n        Messages,\n        Files,\n        Spectrum,\n    };\n\n    enum class SubWindowIdFiles {\n        Send,\n        Receive,\n    };\n\n    enum class SubWindowIdSpectrum {\n        Spectrum,\n        Spectrogram,\n    };\n\n    struct Settings {\n        int protocolId = GGWAVE_PROTOCOL_AUDIBLE_FAST;\n        bool isSampleRateOffset = false;\n        float sampleRateOffset = -512.0f;\n        bool isFreqStartShift = false;\n        int freqStartShift = 48;\n        bool isFixedLength = false;\n        bool directSequenceSpread = false;\n        int payloadLength = 16;\n        float volume = 0.10f;\n\n        GGWave::TxProtocols txProtocols;\n        GGWave::RxProtocols rxProtocols;\n    };\n\n    static WindowId windowId = WindowId::Messages;\n    static WindowId windowIdLast = windowId;\n    static SubWindowIdFiles subWindowIdFiles = SubWindowIdFiles::Send;\n    static SubWindowIdSpectrum subWindowIdSpectrum = SubWindowIdSpectrum::Spectrum;\n\n    static Settings settings;\n\n    const double tHoldContextPopup = 0.2f;\n\n    const int kMaxInputSize = 140;\n    static char inputBuf[kMaxInputSize];\n\n    static bool doInputFocus = false;\n    static bool doSendMessage = false;\n    static bool mouseButtonLeftLast = 0;\n    static bool isTextInput = false;\n    static bool scrollMessagesToBottom = true;\n    static bool hasAudioCaptureData = false;\n    static bool hasNewMessages = false;\n    static bool hasNewSpectrum = false;\n    static bool showSpectrumSettings = true;\n#ifdef __EMSCRIPTEN__\n    static bool hasFileSharingSupport = false;\n#else\n    static bool hasFileSharingSupport = true;\n#endif\n\n#if defined(IOS) || defined(ANDROID)\n    static double tStartInput = 0.0f;\n    static double tEndInput = -100.0f;\n#endif\n    static double tStartTx = 0.0f;\n    static double tLengthTx = 0.0f;\n\n    static GGWaveStats statsCurrent;\n    static std::vector<float> spectrumCurrent;\n    static std::vector<int16_t> txAmplitudeCurrent;\n    static std::vector<Message> messageHistory;\n    static std::string inputLast = \"\";\n\n    // keyboard shortcuts:\n    if (ImGui::IsKeyPressed(62)) {\n        printf(\"F5 pressed : clear message history\\n\");\n        messageHistory.clear();\n    }\n\n    if (ImGui::IsKeyPressed(63)) {\n        if (messageHistory.size() > 0) {\n            printf(\"F6 pressed : delete last message\\n\");\n            messageHistory.erase(messageHistory.end() - 1);\n        }\n    }\n\n    if (stateCurrent.update) {\n        if (stateCurrent.flags.newMessage) {\n            scrollMessagesToBottom = true;\n            messageHistory.push_back(std::move(stateCurrent.message));\n            hasNewMessages = true;\n        }\n        if (stateCurrent.flags.newSpectrum) {\n            spectrumCurrent = std::move(stateCurrent.rxSpectrum);\n            hasNewSpectrum = true;\n            hasAudioCaptureData = !spectrumCurrent.empty();\n        }\n        if (stateCurrent.flags.newTxAmplitude) {\n            txAmplitudeCurrent.resize(stateCurrent.txAmplitude.size());\n            std::copy(stateCurrent.txAmplitude.begin(), stateCurrent.txAmplitude.end(), txAmplitudeCurrent.begin());\n\n            tStartTx = ImGui::GetTime() + (16.0f*1024.0f)/statsCurrent.sampleRateOut;\n            tLengthTx = txAmplitudeCurrent.size()/statsCurrent.sampleRateOut;\n            {\n                auto & ampl = txAmplitudeCurrent;\n                int nBins = 512;\n                int nspb = (int) ampl.size()/nBins;\n                for (int i = 0; i < nBins; ++i) {\n                    double sum = 0.0;\n                    for (int j = 0; j < nspb; ++j) {\n                        sum += std::abs(ampl[i*nspb + j]);\n                    }\n                    ampl[i] = sum/nspb;\n                }\n                ampl.resize(nBins);\n            }\n        }\n        if (stateCurrent.flags.newStats) {\n            statsCurrent = std::move(stateCurrent.stats);\n        }\n        if (stateCurrent.flags.newTxProtocols) {\n            settings.txProtocols = std::move(stateCurrent.txProtocols);\n            settings.rxProtocols = settings.txProtocols;\n        }\n        stateCurrent.flags.clear();\n        stateCurrent.update = false;\n    }\n\n    if (settings.txProtocols.empty()) {\n        printf(\"No Tx Protocols available\\n\");\n        return;\n    }\n\n    if (g_focusFileSend) {\n        windowId = WindowId::Files;\n        subWindowIdFiles = SubWindowIdFiles::Send;\n        g_focusFileSend = false;\n    }\n\n    if (mouseButtonLeftLast == 0 && ImGui::GetIO().MouseDown[0] == 1) {\n        ImGui::GetIO().MouseDelta = { 0.0, 0.0 };\n    }\n    mouseButtonLeftLast = ImGui::GetIO().MouseDown[0];\n\n    const auto& displaySize = ImGui::GetIO().DisplaySize;\n    auto& style = ImGui::GetStyle();\n\n    const auto sendButtonText = ICON_FA_PLAY_CIRCLE \" Send\";\n#if defined(IOS) || defined(ANDROID)\n    const double tShowKeyboard = 0.2f;\n#endif\n#if defined(IOS)\n    const float statusBarHeight = displaySize.x < displaySize.y ? 2.0f*style.ItemSpacing.y : 0.1f;\n#else\n    const float statusBarHeight = 0.1f;\n#endif\n    const float menuButtonHeight = 24.0f + 2.0f*style.ItemSpacing.y;\n\n    const auto & mouse_delta = ImGui::GetIO().MouseDelta;\n\n    ImGui::SetNextWindowPos({ 0, 0, });\n    ImGui::SetNextWindowSize(displaySize);\n    ImGui::Begin(\"Main\", nullptr,\n                 ImGuiWindowFlags_NoMove |\n                 ImGuiWindowFlags_NoTitleBar |\n                 ImGuiWindowFlags_NoScrollbar |\n                 ImGuiWindowFlags_NoResize |\n                 ImGuiWindowFlags_NoSavedSettings);\n\n    ImGui::InvisibleButton(\"StatusBar\", { ImGui::GetContentRegionAvailWidth(), statusBarHeight });\n\n    if (ImGui::ButtonSelectable(ICON_FA_COGS, { menuButtonHeight, menuButtonHeight }, windowId == WindowId::Settings )) {\n        windowId = WindowId::Settings;\n    }\n    ImGui::SameLine();\n\n    {\n        auto posSave = ImGui::GetCursorScreenPos();\n        if (ImGui::ButtonSelectable(ICON_FA_COMMENT_ALT \"  Messages\", { 0.35f*ImGui::GetContentRegionAvailWidth(), menuButtonHeight }, windowId == WindowId::Messages)) {\n            windowId = WindowId::Messages;\n        }\n        auto radius = 0.3f*ImGui::GetTextLineHeight();\n        posSave.x += 2.0f*radius;\n        posSave.y += 2.0f*radius;\n        if (hasNewMessages) {\n            ImGui::GetWindowDrawList()->AddCircleFilled(posSave, radius, ImGui::ColorConvertFloat4ToU32({ 1.0f, 0.0f, 0.0f, 1.0f }), 16);\n        }\n    }\n    ImGui::SameLine();\n\n    if (!hasFileSharingSupport) {\n        ImGui::ButtonDisabled(ICON_FA_FILE \"  Files\", { 0.40f*ImGui::GetContentRegionAvailWidth(), menuButtonHeight });\n        if (ImGui::IsItemHovered()) {\n            ImGui::BeginTooltip();\n            ImGui::Text(\"File sharing is not supported on this platform!\");\n            ImGui::EndTooltip();\n        }\n    } else if (ImGui::ButtonSelectable(ICON_FA_FILE \"  Files\", { 0.40f*ImGui::GetContentRegionAvailWidth(), menuButtonHeight }, windowId == WindowId::Files)) {\n        windowId = WindowId::Files;\n    }\n    ImGui::SameLine();\n\n    {\n        auto posSave = ImGui::GetCursorScreenPos();\n        if (ImGui::ButtonSelectable(ICON_FA_SIGNAL \"  Spectrum\", { 1.0f*ImGui::GetContentRegionAvailWidth(), menuButtonHeight }, windowId == WindowId::Spectrum)) {\n            if (windowId == WindowId::Spectrum) {\n                showSpectrumSettings = !showSpectrumSettings;\n            }\n            windowId = WindowId::Spectrum;\n        }\n        auto radius = 0.3f*ImGui::GetTextLineHeight();\n        posSave.x += 2.0f*radius;\n        posSave.y += 2.0f*radius;\n        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);\n    }\n\n    if ((windowIdLast != windowId) || (hasAudioCaptureData == false)) {\n        g_buffer.inputUI.update = true;\n        g_buffer.inputUI.flags.changeNeedSpectrum = true;\n        g_buffer.inputUI.needSpectrum = (windowId == WindowId::Spectrum) || (hasAudioCaptureData == false);\n\n        windowIdLast = windowId;\n    }\n\n    if (windowId == WindowId::Settings) {\n        ImGui::BeginChild(\"Settings:main\", ImGui::GetContentRegionAvail(), true, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse);\n        ImGui::Text(\"Waver v1.5.3\");\n        ImGui::Separator();\n\n        ImGui::Text(\"%s\", \"\");\n        ImGui::Text(\"Sample rate (capture):  %g, %d B/sample\", statsCurrent.sampleRateInp, statsCurrent.sampleSizeInp);\n        ImGui::Text(\"Sample rate (playback): %g, %d B/sample\", statsCurrent.sampleRateOut, statsCurrent.sampleSizeOut);\n\n        const float kLabelWidth = ImGui::CalcTextSize(\"Inp. SR Offset:  \").x;\n\n        // volume\n        ImGui::Text(\"%s\", \"\");\n        {\n            auto posSave = ImGui::GetCursorScreenPos();\n            ImGui::Text(\"%s\", \"\");\n            ImGui::SetCursorScreenPos({ posSave.x + kLabelWidth, posSave.y });\n            if (settings.volume < 0.2f) {\n                ImGui::TextColored({ 0.0f, 1.0f, 0.0f, 0.5f }, \"Normal volume\");\n            } else if (settings.volume < 0.5f) {\n                ImGui::TextColored({ 1.0f, 1.0f, 0.0f, 0.5f }, \"Intermediate volume\");\n            } else {\n                ImGui::TextColored({ 1.0f, 0.0f, 0.0f, 0.5f }, \"Warning: high volume!\");\n            }\n        }\n        {\n            auto posSave = ImGui::GetCursorScreenPos();\n            ImGui::Text(\"Volume: \");\n            ImGui::SetCursorScreenPos({ posSave.x + kLabelWidth, posSave.y });\n        }\n        {\n            auto p0 = ImGui::GetCursorScreenPos();\n\n            {\n                auto & cols = ImGui::GetStyle().Colors;\n                ImGui::PushStyleColor(ImGuiCol_FrameBg, cols[ImGuiCol_WindowBg]);\n                ImGui::PushStyleColor(ImGuiCol_FrameBgActive, cols[ImGuiCol_WindowBg]);\n                ImGui::PushStyleColor(ImGuiCol_FrameBgHovered, cols[ImGuiCol_WindowBg]);\n                ImGui::SliderFloat(\"##volume\", &settings.volume, 0.0f, 1.0f);\n                ImGui::PopStyleColor(3);\n            }\n\n            auto posSave = ImGui::GetCursorScreenPos();\n            ImGui::SameLine();\n            auto p1 = ImGui::GetCursorScreenPos();\n            p1.x -= ImGui::CalcTextSize(\" \").x;\n            p1.y += ImGui::GetTextLineHeightWithSpacing() + 0.5f*style.ItemInnerSpacing.y;\n            ImGui::GetWindowDrawList()->AddRectFilledMultiColor(\n                    p0, { 0.35f*(p0.x + p1.x), p1.y },\n                    ImGui::ColorConvertFloat4ToU32({0.0f, 1.0f, 0.0f, 0.5f}),\n                    ImGui::ColorConvertFloat4ToU32({1.0f, 1.0f, 0.0f, 0.3f}),\n                    ImGui::ColorConvertFloat4ToU32({1.0f, 1.0f, 0.0f, 0.3f}),\n                    ImGui::ColorConvertFloat4ToU32({0.0f, 1.0f, 0.0f, 0.5f})\n                    );\n            ImGui::GetWindowDrawList()->AddRectFilledMultiColor(\n                    { 0.35f*(p0.x + p1.x), p0.y }, p1,\n                    ImGui::ColorConvertFloat4ToU32({1.0f, 1.0f, 0.0f, 0.3f}),\n                    ImGui::ColorConvertFloat4ToU32({1.0f, 0.0f, 0.0f, 0.5f}),\n                    ImGui::ColorConvertFloat4ToU32({1.0f, 0.0f, 0.0f, 0.5f}),\n                    ImGui::ColorConvertFloat4ToU32({1.0f, 1.0f, 0.0f, 0.3f})\n                    );\n            ImGui::SetCursorScreenPos(posSave);\n        }\n\n        // tx protocol\n        ImGui::Text(\"%s\", \"\");\n        {\n            auto posSave = ImGui::GetCursorScreenPos();\n            ImGui::Text(\"%s\", \"\");\n            ImGui::SetCursorScreenPos({ posSave.x + kLabelWidth, posSave.y });\n            ImGui::TextDisabled(\"[U] = ultrasound\");\n        }\n        {\n            auto posSave = ImGui::GetCursorScreenPos();\n            ImGui::Text(\"%s\", \"\");\n            ImGui::SetCursorScreenPos({ posSave.x + kLabelWidth, posSave.y });\n            ImGui::TextDisabled(\"[DT] = dual-tone\");\n        }\n        {\n            auto posSave = ImGui::GetCursorScreenPos();\n            ImGui::Text(\"%s\", \"\");\n            ImGui::SetCursorScreenPos({ posSave.x + kLabelWidth, posSave.y });\n            ImGui::TextDisabled(\"[MT] = mono-tone\");\n        }\n        {\n            auto posSave = ImGui::GetCursorScreenPos();\n            ImGui::Text(\"Tx Protocol: \");\n            ImGui::SetCursorScreenPos({ posSave.x + kLabelWidth, posSave.y });\n        }\n        if (ImGui::BeginCombo(\"##txProtocol\", settings.txProtocols[settings.protocolId].name)) {\n            for (int i = 0; i < (int) settings.txProtocols.size(); ++i) {\n                const auto & txProtocol = settings.txProtocols[i];\n                if (txProtocol.name == nullptr) continue;\n                const bool isSelected = (settings.protocolId == i);\n                if (ImGui::Selectable(txProtocol.name, isSelected)) {\n                    settings.protocolId = i;\n                }\n\n                if (isSelected) {\n                    ImGui::SetItemDefaultFocus();\n                }\n            }\n            ImGui::EndCombo();\n        }\n\n        {\n            auto posSave = ImGui::GetCursorScreenPos();\n            ImGui::Text(\"Bandwidth: \");\n            ImGui::SetCursorScreenPos({ posSave.x + kLabelWidth, posSave.y });\n        }\n        {\n            const auto & protocol = settings.txProtocols[settings.protocolId];\n            const float bandwidth = ((float(0.715f*protocol.bytesPerTx)/(protocol.framesPerTx*statsCurrent.samplesPerFrame))*statsCurrent.sampleRate)/protocol.extra;\n            ImGui::Text(\"%4.2f B/s\", bandwidth);\n        }\n\n        {\n            auto posSave = ImGui::GetCursorScreenPos();\n            ImGui::Text(\"Frequencies: \");\n            ImGui::SetCursorScreenPos({ posSave.x + kLabelWidth, posSave.y });\n        }\n        {\n            const float df = statsCurrent.sampleRate/statsCurrent.samplesPerFrame;\n            const auto & protocol = settings.txProtocols[settings.protocolId];\n            const auto freqStart = std::max(1, protocol.freqStart + (settings.isFreqStartShift ? settings.freqStartShift : 0));\n            const float f0 = df*freqStart;\n            const float f1 = df*(freqStart + float(2*16*protocol.bytesPerTx)/protocol.extra);\n            ImGui::Text(\"%6.2f Hz - %6.2f Hz\", f0, f1);\n        }\n\n        // fixed-length\n        ImGui::Text(\"%s\", \"\");\n        {\n            auto posSave = ImGui::GetCursorScreenPos();\n            ImGui::Text(\"%s\", \"\");\n            ImGui::SetCursorScreenPos({ posSave.x + kLabelWidth, posSave.y });\n            ImGui::PushTextWrapPos();\n            ImGui::TextDisabled(\"Fixed-length Tx/Rx does not use sound-markers\");\n            ImGui::PopTextWrapPos();\n        }\n        {\n            auto posSave = ImGui::GetCursorScreenPos();\n            ImGui::Text(\"Fixed-length: \");\n            ImGui::SetCursorScreenPos({ posSave.x + kLabelWidth, posSave.y });\n        }\n        if (ImGui::Checkbox(\"##fixed-length\", &settings.isFixedLength)) {\n            g_buffer.inputUI.update = true;\n            g_buffer.inputUI.flags.needReinit = true;\n            g_buffer.inputUI.payloadLength = settings.isFixedLength ? settings.payloadLength : -1;\n        }\n\n        if (settings.isFixedLength) {\n            ImGui::SameLine();\n            ImGui::PushItemWidth(0.5*ImGui::GetContentRegionAvailWidth());\n            if (ImGui::DragInt(\"Bytes\", &settings.payloadLength, 1, 1, GGWave::kMaxLengthFixed)) {\n                g_buffer.inputUI.update = true;\n                g_buffer.inputUI.flags.needReinit = true;\n                g_buffer.inputUI.payloadLength = settings.isFixedLength ? settings.payloadLength : -1;\n            }\n            ImGui::PopItemWidth();\n        }\n\n        // Direct-sequence spread\n        //ImGui::Text(\"%s\", \"\");\n        {\n            auto posSave = ImGui::GetCursorScreenPos();\n            ImGui::Text(\"%s\", \"\");\n            ImGui::SetCursorScreenPos({ posSave.x + kLabelWidth, posSave.y });\n            ImGui::PushTextWrapPos();\n            ImGui::TextDisabled(\"Direct-sequence spread\");\n            ImGui::PopTextWrapPos();\n        }\n        {\n            auto posSave = ImGui::GetCursorScreenPos();\n            ImGui::Text(\"Use DSS: \");\n            ImGui::SetCursorScreenPos({ posSave.x + kLabelWidth, posSave.y });\n        }\n        if (ImGui::Checkbox(\"##direct-sequence-spread\", &settings.directSequenceSpread)) {\n            g_buffer.inputUI.update = true;\n            g_buffer.inputUI.flags.needReinit = true;\n            g_buffer.inputUI.directSequenceSpread = settings.directSequenceSpread;\n        }\n\n        // FreqStart offset\n        //ImGui::Text(\"%s\", \"\");\n        {\n            auto posSave = ImGui::GetCursorScreenPos();\n            ImGui::Text(\"%s\", \"\");\n            ImGui::SetCursorScreenPos({ posSave.x + kLabelWidth, posSave.y });\n            ImGui::PushTextWrapPos();\n            ImGui::TextDisabled(\"Apply tx/rx frequency shift\");\n            ImGui::PopTextWrapPos();\n        }\n        {\n            auto posSave = ImGui::GetCursorScreenPos();\n            ImGui::Text(\"Freq shift: \");\n            ImGui::SetCursorScreenPos({ posSave.x + kLabelWidth, posSave.y });\n        }\n        if (ImGui::Checkbox(\"##freq-start-offset\", &settings.isFreqStartShift)) {\n            g_buffer.inputUI.update = true;\n            g_buffer.inputUI.flags.needReinit = true;\n            g_buffer.inputUI.freqStartShift = settings.isFreqStartShift ? settings.freqStartShift : 0;\n        }\n\n        if (settings.isFreqStartShift) {\n            ImGui::SameLine();\n            ImGui::PushItemWidth(0.5*ImGui::GetContentRegionAvailWidth());\n            if (ImGui::DragInt(\"bins\", &settings.freqStartShift, 1, -64, 64, \"%d\")) {\n                g_buffer.inputUI.update = true;\n                g_buffer.inputUI.flags.needReinit = true;\n                g_buffer.inputUI.freqStartShift = settings.isFreqStartShift ? settings.freqStartShift : 0;\n            }\n            ImGui::PopItemWidth();\n\n            {\n                auto posSave = ImGui::GetCursorScreenPos();\n                ImGui::Text(\"\");\n                ImGui::SetCursorScreenPos({ posSave.x + kLabelWidth, posSave.y });\n            }\n            {\n                const float df = statsCurrent.sampleRate/statsCurrent.samplesPerFrame;\n                ImGui::Text(\"%6.2f Hz\", df*settings.freqStartShift);\n            }\n        }\n\n        // Output sample-rate offset\n        //ImGui::Text(\"%s\", \"\");\n        {\n            auto posSave = ImGui::GetCursorScreenPos();\n            ImGui::Text(\"%s\", \"\");\n            ImGui::SetCursorScreenPos({ posSave.x + kLabelWidth, posSave.y });\n            ImGui::PushTextWrapPos();\n            ImGui::TextDisabled(\"Modify the output Sampling Rate\");\n            ImGui::PopTextWrapPos();\n        }\n        {\n            auto posSave = ImGui::GetCursorScreenPos();\n            ImGui::Text(\"Pitch shift: \");\n            ImGui::SetCursorScreenPos({ posSave.x + kLabelWidth, posSave.y });\n        }\n        if (ImGui::Checkbox(\"##output-sample-rate-offset\", &settings.isSampleRateOffset)) {\n            g_buffer.inputUI.update = true;\n            g_buffer.inputUI.flags.needReinit = true;\n            g_buffer.inputUI.sampleRateOffset = settings.isSampleRateOffset ? settings.sampleRateOffset : 0;\n        }\n\n        if (settings.isSampleRateOffset) {\n            ImGui::SameLine();\n            ImGui::PushItemWidth(0.5*ImGui::GetContentRegionAvailWidth());\n            if (ImGui::SliderFloat(\"Samples\", &settings.sampleRateOffset, -1000, 1000, \"%.0f\")) {\n                g_buffer.inputUI.update = true;\n                g_buffer.inputUI.flags.needReinit = true;\n                g_buffer.inputUI.sampleRateOffset = settings.isSampleRateOffset ? settings.sampleRateOffset : 0;\n            }\n            ImGui::PopItemWidth();\n        }\n\n        // rx protocols\n        bool updateRxProtocols = false;\n        ImGui::Text(\"%s\", \"\");\n        {\n            auto posSave = ImGui::GetCursorScreenPos();\n            ImGui::Text(\"%s\", \"\");\n            ImGui::SetCursorScreenPos({ posSave.x + kLabelWidth, posSave.y });\n            ImGui::PushTextWrapPos();\n            ImGui::TextDisabled(\"Waver will receive only the selected protocols:\");\n            ImGui::PopTextWrapPos();\n        }\n        {\n            auto posSave = ImGui::GetCursorScreenPos();\n            ImGui::Text(\"Rx Protocols: \");\n            ImGui::SetCursorScreenPos(posSave);\n        }\n        {\n            ImGui::PushID(\"RxProtocols\");\n            for (int i = 0; i < settings.rxProtocols.size(); ++i) {\n                auto & rxProtocol = settings.rxProtocols[i];\n                if (rxProtocol.name == nullptr) continue;\n                auto posSave = ImGui::GetCursorScreenPos();\n                ImGui::Text(\"%s\", \"\");\n                ImGui::SetCursorScreenPos({ posSave.x + kLabelWidth, posSave.y });\n                if (ImGui::Checkbox(rxProtocol.name, &rxProtocol.enabled)) {\n                    updateRxProtocols = true;\n                }\n            }\n            ImGui::PopID();\n        }\n\n        if (updateRxProtocols) {\n            g_buffer.inputUI.update = true;\n            g_buffer.inputUI.flags.changeRxProtocols = true;\n            g_buffer.inputUI.rxProtocols = settings.rxProtocols;\n        }\n\n        ScrollWhenDraggingOnVoid(ImVec2(0.0f, -mouse_delta.y), ImGuiMouseButton_Left);\n\n        ImGui::EndChild();\n    }\n\n    if (windowId == WindowId::Messages) {\n        const float messagesInputHeight = 2*ImGui::GetTextLineHeightWithSpacing();\n        const float messagesHistoryHeigthMax = ImGui::GetContentRegionAvail().y - messagesInputHeight - 2.0f*style.ItemSpacing.x;\n        float messagesHistoryHeigth = messagesHistoryHeigthMax;\n\n        hasNewMessages = false;\n\n        // no automatic screen resize support for iOS\n#if defined(IOS) || defined(ANDROID)\n        if (displaySize.x < displaySize.y) {\n            if (isTextInput) {\n                messagesHistoryHeigth -= 0.5f*messagesHistoryHeigthMax*std::min(tShowKeyboard, ImGui::GetTime() - tStartInput) / tShowKeyboard;\n            } else {\n                messagesHistoryHeigth -= 0.5f*messagesHistoryHeigthMax - 0.5f*messagesHistoryHeigthMax*std::min(tShowKeyboard, ImGui::GetTime() - tEndInput) / tShowKeyboard;\n            }\n        } else {\n            if (isTextInput) {\n                messagesHistoryHeigth -= 0.5f*displaySize.y*std::min(tShowKeyboard, ImGui::GetTime() - tStartInput) / tShowKeyboard;\n            } else {\n                messagesHistoryHeigth -= 0.5f*displaySize.y - 0.5f*displaySize.y*std::min(tShowKeyboard, ImGui::GetTime() - tEndInput) / tShowKeyboard;\n            }\n        }\n#endif\n\n        bool showScrollToBottom = false;\n        const auto wPos0 = ImGui::GetCursorScreenPos();\n        const auto wSize = ImVec2 { ImGui::GetContentRegionAvailWidth(), messagesHistoryHeigth };\n\n        ImGui::BeginChild(\"Messages:history\", wSize, true, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse);\n\n        static bool isDragging = false;\n        static bool isHoldingMessage = false;\n        static bool isHoldingInput = false;\n        static int messageIdHolding = 0;\n\n        const float tMessageFlyIn = 0.3f;\n\n        // we need this because we push messages in the next loop\n        if (messageHistory.capacity() == messageHistory.size()) {\n            messageHistory.reserve(messageHistory.size() + 16);\n        }\n\n        for (int i = 0; i < (int) messageHistory.size(); ++i) {\n            ImGui::PushID(i);\n            const auto & message = messageHistory[i];\n            const float tRecv = 0.001f*std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now() - message.timestamp).count();\n            const float interp = std::min(tRecv, tMessageFlyIn)/tMessageFlyIn;\n            const float xoffset = std::max(0.0f, (1.0f - interp)*ImGui::GetContentRegionAvailWidth());\n\n            if (xoffset > 0.0f) {\n                ImGui::Indent(xoffset);\n            } else {\n                ImGui::PushTextWrapPos();\n            }\n\n            const auto msgStatus = message.received ? \"Recv\" : \"Send\";\n            const auto msgColor = message.received ? ImVec4 { 0.0f, 1.0f, 0.0f, interp } : ImVec4 { 1.0f, 1.0f, 0.0f, interp };\n\n            ImGui::TextDisabled(\"%s |\", ::toTimeString(message.timestamp));\n            ImGui::SameLine();\n            ImGui::TextColored(msgColor, \"%s\", msgStatus);\n            ImGui::SameLine();\n            ImGui::TextDisabled(\"|\");\n            ImGui::SameLine();\n            ImGui::TextColored({ 0.0f, 0.6f, 0.4f, interp }, \"%s\", settings.txProtocols[message.protocolId].name);\n            ImGui::SameLine();\n            if (message.dss) {\n                ImGui::TextColored({ 0.4f, 0.6f, 0.4f, interp }, \"DSS\");\n                if (ImGui::IsItemHovered()) {\n                    ImGui::SetTooltip(\"Direct Sequence Spread\");\n                }\n                ImGui::SameLine();\n            }\n            ImGui::TextDisabled(\"|\");\n\n            {\n                auto p0 = ImGui::GetCursorScreenPos();\n                auto p00 = p0;\n                p00.y -= ImGui::GetTextLineHeightWithSpacing();\n                p0.x -= style.ItemSpacing.x;\n                p0.y -= 0.5f*style.ItemSpacing.y;\n\n                switch (message.type) {\n                    case Message::Error:\n                        {\n                            auto col = ImVec4 { 1.0f, 0.0f, 0.0f, 1.0f };\n                            col.w = interp;\n                            ImGui::TextColored(col, \"Error: %s\", message.data.c_str());\n                        }\n                        break;\n                    case Message::Text:\n                        {\n                            auto col = style.Colors[ImGuiCol_Text];\n                            col.w = interp;\n                            ImGui::TextColored(col, \"%s\", message.data.c_str());\n                        }\n                        break;\n                    case Message::FileBroadcast:\n                        {\n                            auto col = ImVec4 { 0.0f, 1.0f, 1.0f, 1.0f };\n                            col.w = interp;\n                            auto broadcastInfo = parseBroadcastInfo(message.data);\n                            ImGui::TextColored(col, \"-=[ File Broadcast from %s:%d ]=-\", broadcastInfo.ip.c_str(), broadcastInfo.port);\n                        }\n                        break;\n                }\n\n                auto p1 = ImGui::GetCursorScreenPos();\n                p1.x = p00.x + ImGui::GetContentRegionAvailWidth();\n\n                if (xoffset == 0.0f) {\n                    if (ImGui::IsMouseHoveringRect(p00, p1, true)) {\n                        auto col = ImGui::GetStyleColorVec4(ImGuiCol_TextDisabled);\n                        col.w *= ImGui::GetIO().MouseDownDuration[0] > tHoldContextPopup ? 0.25f : 0.10f;\n                        ImGui::GetWindowDrawList()->AddRectFilled(p0, p1, ImGui::ColorConvertFloat4ToU32(col), 12.0f);\n\n                        if (ImGui::GetIO().MouseDownDuration[0] > tHoldContextPopup) {\n                            isHoldingMessage = true;\n                            messageIdHolding = i;\n                        }\n                    }\n                }\n            }\n\n            if (xoffset == 0.0f) {\n                ImGui::PopTextWrapPos();\n            }\n            ImGui::Text(\"%s\", \"\");\n            ImGui::PopID();\n        }\n\n        if (ImGui::IsMouseReleased(0) && isHoldingMessage && isDragging == false) {\n            auto pos = ImGui::GetMousePos();\n            pos.x -= 1.0f*ImGui::CalcTextSize(\"Resend | Copy\").x;\n            pos.y -= 1.0f*ImGui::GetTextLineHeightWithSpacing();\n            ImGui::SetNextWindowPos(pos);\n\n            ImGui::OpenPopup(\"Message options\");\n            isHoldingMessage = false;\n        }\n\n        if (ImGui::BeginPopup(\"Message options\")) {\n            const auto & messageSelected = messageHistory[messageIdHolding];\n\n            if (ImGui::ButtonDisablable(\"Resend\", {}, messageSelected.type != Message::Text)) {\n                g_buffer.inputUI.update = true;\n                g_buffer.inputUI.flags.newMessage = true;\n                g_buffer.inputUI.message = { false, std::chrono::system_clock::now(), messageSelected.data, messageSelected.protocolId, messageSelected.dss, settings.volume, Message::Text };\n\n                messageHistory.push_back(g_buffer.inputUI.message);\n                ImGui::CloseCurrentPopup();\n            }\n\n            ImGui::SameLine();\n            ImGui::TextDisabled(\"|\");\n\n            ImGui::SameLine();\n            if (ImGui::ButtonDisablable(\"Copy\", {}, messageSelected.type != Message::Text)) {\n                SDL_SetClipboardText(messageSelected.data.c_str());\n                ImGui::CloseCurrentPopup();\n            }\n\n            if (messageSelected.type == Message::FileBroadcast) {\n                ImGui::SameLine();\n                ImGui::TextDisabled(\"|\");\n\n                ImGui::SameLine();\n                if (ImGui::ButtonDisablable(\"Receive\", {}, !messageSelected.received || messageSelected.type != Message::FileBroadcast || !hasFileSharingSupport)) {\n                    auto broadcastInfo = parseBroadcastInfo(messageSelected.data);\n\n                    g_remoteIP = broadcastInfo.ip;\n                    g_remotePort = broadcastInfo.port;\n                    g_hasRemoteInfo = true;\n\n                    g_fileClient.disconnect();\n                    g_hasReceivedFileInfos = false;\n                    g_hasRequestedFileInfos = false;\n                    g_hasReceivedFiles = false;\n\n                    windowId = WindowId::Files;\n                    subWindowIdFiles = SubWindowIdFiles::Receive;\n\n                    ImGui::CloseCurrentPopup();\n                }\n            }\n\n            ImGui::EndPopup();\n        }\n\n        if (scrollMessagesToBottom) {\n            ImGui::SetScrollHereY();\n            scrollMessagesToBottom = false;\n        }\n\n        if (ImGui::GetScrollY() < ImGui::GetScrollMaxY() - 10) {\n            showScrollToBottom = true;\n        }\n\n        if (showScrollToBottom) {\n            auto posSave = ImGui::GetCursorScreenPos();\n            auto butSize = ImGui::CalcTextSize(ICON_FA_ARROW_CIRCLE_DOWN);\n            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 });\n            if (ImGui::Button(ICON_FA_ARROW_CIRCLE_DOWN)) {\n                scrollMessagesToBottom = true;\n            }\n            ImGui::SetCursorScreenPos(posSave);\n        }\n\n        {\n            bool isDraggingCur = ScrollWhenDraggingOnVoid(ImVec2(0.0f, -mouse_delta.y), ImGuiMouseButton_Left);\n            if (isDraggingCur && ImGui::IsMouseDown(0)) {\n                isDragging = true;\n            } else if (isDraggingCur == false && ImGui::IsMouseDown(0) == false) {\n                isDragging = false;\n            }\n        }\n        ImGui::EndChild();\n\n        if (statsCurrent.receiving) {\n            if (statsCurrent.analyzing) {\n                ImGui::TextColored({ 0.0f, 1.0f, 0.0f, 1.0f }, \"Analyzing ...\");\n                ImGui::SameLine();\n                ImGui::ProgressBar(1.0f - float(statsCurrent.framesLeftToAnalyze)/statsCurrent.framesToAnalyze,\n                                   { ImGui::GetContentRegionAvailWidth() - ImGui::CalcTextSize(sendButtonText).x - 2*style.ItemSpacing.x, ImGui::GetTextLineHeight() });\n            } else {\n                ImGui::TextColored({ 0.0f, 1.0f, 0.0f, 1.0f }, \"Receiving ...\");\n                ImGui::SameLine();\n                if (ImGui::SmallButton(\"Stop\")) {\n                    g_buffer.inputUI.update = true;\n                    g_buffer.inputUI.flags.stopReceiving = true;\n                }\n                ImGui::SameLine();\n                ImGui::ProgressBar(1.0f - float(statsCurrent.framesLeftToRecord)/statsCurrent.framesToRecord,\n                                   { ImGui::GetContentRegionAvailWidth() - ImGui::CalcTextSize(sendButtonText).x - 2*style.ItemSpacing.x, ImGui::GetTextLineHeight() });\n            }\n        } else {\n            static float amax = 0.0f;\n            static float frac = 0.0f;\n\n            amax = 0.0f;\n            frac = (ImGui::GetTime() - tStartTx)/tLengthTx;\n\n            if (txAmplitudeCurrent.size() && frac <= 1.0f) {\n                struct Funcs {\n                    static float Sample(void * data, int i) {\n                        auto res = std::fabs(((int16_t *)(data))[i]) ;\n                        if (res > amax) amax = res;\n                        return res;\n                    }\n\n                    static float SampleFrac(void * data, int i) {\n                        if (i > frac*txAmplitudeCurrent.size()) {\n                            return 0.0f;\n                        }\n                        return std::fabs(((int16_t *)(data))[i]);\n                    }\n                };\n\n                auto posSave = ImGui::GetCursorScreenPos();\n                auto wSize = ImGui::GetContentRegionAvail();\n                wSize.x = ImGui::GetContentRegionAvailWidth() - ImGui::CalcTextSize(sendButtonText).x - 2*style.ItemSpacing.x;\n                wSize.y = ImGui::GetTextLineHeight();\n\n                ImGui::PushStyleColor(ImGuiCol_FrameBg, { 0.3f, 0.3f, 0.3f, 0.3f });\n                ImGui::PushStyleColor(ImGuiCol_PlotHistogram, ImGui::GetStyleColorVec4(ImGuiCol_TextDisabled));\n                ImGui::PlotHistogram(\"##plotSpectrumCurrent\",\n                                     Funcs::Sample,\n                                     txAmplitudeCurrent.data(),\n                                     (int) txAmplitudeCurrent.size(), 0,\n                                     (std::string(\"\")).c_str(),\n                                     0.0f, FLT_MAX, wSize);\n                ImGui::PopStyleColor(2);\n\n                ImGui::SetCursorScreenPos(posSave);\n\n                ImGui::PushStyleColor(ImGuiCol_FrameBg, { 0.0f, 0.0f, 0.0f, 0.0f });\n                ImGui::PushStyleColor(ImGuiCol_PlotHistogram, { 0.0f, 1.0f, 0.0f, 1.0f });\n                ImGui::PlotHistogram(\"##plotSpectrumCurrent\",\n                                     Funcs::SampleFrac,\n                                     txAmplitudeCurrent.data(),\n                                     (int) txAmplitudeCurrent.size(), 0,\n                                     (std::string(\"\")).c_str(),\n                                     0.0f, amax, wSize);\n                ImGui::PopStyleColor(2);\n            } else {\n                if (settings.isFixedLength) {\n                    ImGui::TextDisabled(\"Listening for waves (fixed-length %d bytes)\", settings.payloadLength);\n                } else {\n                    ImGui::TextDisabled(\"Listening for waves (variable-length)\");\n                }\n            }\n        }\n\n        if (doInputFocus) {\n            ImGui::SetKeyboardFocusHere();\n            doInputFocus = false;\n        }\n\n        doSendMessage = false;\n        {\n            auto pos0 = ImGui::GetCursorScreenPos();\n            ImGui::PushItemWidth(ImGui::GetContentRegionAvailWidth() - ImGui::CalcTextSize(sendButtonText).x - 2*style.ItemSpacing.x);\n            if (ImGui::InputText(\"##Messages:Input\", inputBuf, kMaxInputSize, ImGuiInputTextFlags_EnterReturnsTrue)) {\n                doSendMessage = true;\n            }\n            ImGui::PopItemWidth();\n\n            if (isTextInput == false && inputBuf[0] == 0) {\n                auto drawList = ImGui::GetWindowDrawList();\n                pos0.x += style.ItemInnerSpacing.x;\n                pos0.y += 0.5*style.ItemInnerSpacing.y;\n                static char tmp[128];\n                snprintf(tmp, 128, \"Send message using '%s'\", settings.txProtocols[settings.protocolId].name);\n                drawList->AddText(pos0, ImGui::ColorConvertFloat4ToU32({0.0f, 0.6f, 0.4f, 1.0f}), tmp);\n            }\n        }\n\n        if (ImGui::IsItemActive() && isTextInput == false) {\n            SDL_StartTextInput();\n            isTextInput = true;\n#if defined(IOS) || defined(ANDROID)\n            tStartInput = ImGui::GetTime();\n#endif\n        }\n        bool requestStopTextInput = false;\n        if (ImGui::IsItemDeactivated()) {\n            requestStopTextInput = true;\n        }\n\n        if (isTextInput) {\n            if (ImGui::IsItemHovered() && ImGui::GetIO().MouseDownDuration[0] > tHoldContextPopup) {\n                isHoldingInput = true;\n            }\n        }\n\n        if (ImGui::IsMouseReleased(0) && isHoldingInput) {\n            auto pos = ImGui::GetMousePos();\n            pos.x -= 2.0f*ImGui::CalcTextSize(\"Paste\").x;\n            pos.y -= 1.0f*ImGui::GetTextLineHeightWithSpacing();\n            ImGui::SetNextWindowPos(pos);\n\n            ImGui::OpenPopup(\"Input options\");\n            isHoldingInput = false;\n        }\n\n        if (ImGui::BeginPopup(\"Input options\")) {\n            if (ImGui::Button(\"Paste\")) {\n                for (int i = 0; i < kMaxInputSize; ++i) inputBuf[i] = 0;\n                strncpy(inputBuf, SDL_GetClipboardText(), kMaxInputSize - 1);\n                ImGui::CloseCurrentPopup();\n            }\n\n            ImGui::EndPopup();\n        }\n\n        ImGui::SameLine();\n        {\n            auto posCur = ImGui::GetCursorScreenPos();\n            posCur.y -= ImGui::GetTextLineHeightWithSpacing();\n            ImGui::SetCursorScreenPos(posCur);\n        }\n        if ((ImGui::Button(sendButtonText, { 0, 2*ImGui::GetTextLineHeightWithSpacing() }) || doSendMessage)) {\n            if (inputBuf[0] == 0) {\n                strncpy(inputBuf, inputLast.data(), kMaxInputSize - 1);\n            }\n            if (inputBuf[0] != 0) {\n                inputLast = std::string(inputBuf);\n                g_buffer.inputUI.update = true;\n                g_buffer.inputUI.flags.newMessage = true;\n                g_buffer.inputUI.message = { false, std::chrono::system_clock::now(), std::string(inputBuf), settings.protocolId, settings.directSequenceSpread, settings.volume, Message::Text };\n\n                messageHistory.push_back(g_buffer.inputUI.message);\n\n                inputBuf[0] = 0;\n                doInputFocus = true;\n                scrollMessagesToBottom = true;\n            }\n        }\n        if (!ImGui::IsItemHovered() && requestStopTextInput) {\n            SDL_StopTextInput();\n            isTextInput = false;\n#if defined(IOS) || defined(ANDROID)\n            tEndInput = ImGui::GetTime();\n#endif\n        }\n    }\n\n    if (windowId == WindowId::Files) {\n        const float subWindowButtonHeight = menuButtonHeight;\n\n        if (ImGui::ButtonSelectable(\"Send\", { 0.50f*ImGui::GetContentRegionAvailWidth(), subWindowButtonHeight }, subWindowIdFiles == SubWindowIdFiles::Send)) {\n            subWindowIdFiles = SubWindowIdFiles::Send;\n        }\n        ImGui::SameLine();\n\n        if (ImGui::ButtonSelectable(\"Receive\", { 1.0f*ImGui::GetContentRegionAvailWidth(), subWindowButtonHeight }, subWindowIdFiles == SubWindowIdFiles::Receive)) {\n            subWindowIdFiles = SubWindowIdFiles::Receive;\n        }\n\n        switch (subWindowIdFiles) {\n            case SubWindowIdFiles::Send:\n                {\n                    const float statusWindowHeight = 2*style.ItemInnerSpacing.y + 4*ImGui::GetTextLineHeightWithSpacing();\n\n                    bool hasAtLeastOneFile = false;\n                    {\n                        const auto wSize = ImVec2 { ImGui::GetContentRegionAvailWidth(), ImGui::GetContentRegionAvail().y - subWindowButtonHeight - statusWindowHeight - 2*style.ItemInnerSpacing.y };\n\n                        ImGui::BeginChild(\"Files:Send:fileInfos\", wSize, true, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse);\n\n                        auto fileInfos = g_fileServer.getFileInfos();\n                        for (const auto & fileInfo : fileInfos) {\n                            hasAtLeastOneFile = true;\n                            ImGui::PushID(fileInfo.first);\n                            ImGui::Text(\"File: '%s' (%4.2f MB)\\n\", fileInfo.second.filename.c_str(), float(fileInfo.second.filesize)/1024.0f/1024.0f);\n                            if (ImGui::Button(ICON_FA_SHARE_ALT \"  Share\")) {\n                                g_shareInfo.uri = fileInfo.second.uri;\n                                g_shareInfo.filename = fileInfo.second.filename;\n                                const auto & fileData = g_fileServer.getFileData(g_shareInfo.uri);\n                                g_shareInfo.dataBuffer = fileData.data.data();\n                                g_shareInfo.dataSize = fileData.data.size();\n                                g_shareId++;\n                            }\n                            ImGui::SameLine();\n\n#ifdef ANDROID\n                            if (ImGui::Button(ICON_FA_EYE \"  VIEW\")) {\n                                g_openInfo.uri = fileInfo.second.uri;\n                                g_openInfo.filename = fileInfo.second.filename;\n                                const auto & fileData = g_fileServer.getFileData(g_openInfo.uri);\n                                g_openInfo.dataBuffer = fileData.data.data();\n                                g_openInfo.dataSize = fileData.data.size();\n                                g_openId++;\n                            }\n                            ImGui::SameLine();\n#endif\n\n                            if (ImGui::Button(ICON_FA_TRASH \"  Clear\")) {\n                                g_deleteInfo.uri = fileInfo.second.uri.data();\n                                g_deleteInfo.filename = fileInfo.second.filename.data();\n                                g_deleteId++;\n                            }\n\n                            ImGui::PopID();\n                        }\n\n                        ImGui::PushTextWrapPos();\n                        if (hasAtLeastOneFile == false) {\n                            ImGui::TextColored({ 1.0f, 1.0f, 0.0f, 1.0f }, \"There are currently no files availble to share.\");\n#if defined(IOS) || defined(ANDROID)\n                            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.\");\n#else\n                            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.\");\n#endif\n                            ImGui::TextColored({ 1.0f, 0.6f, 0.0f, 1.0f }, \"File sharing works only between peers in the same local network!\");\n                        }\n                        ImGui::PopTextWrapPos();\n\n                        ScrollWhenDraggingOnVoid(ImVec2(-mouse_delta.x, -mouse_delta.y), ImGuiMouseButton_Left);\n                        ImGui::EndChild();\n                    }\n\n                    {\n                        const auto wSize = ImVec2 { ImGui::GetContentRegionAvailWidth(), ImGui::GetContentRegionAvail().y - subWindowButtonHeight - style.ItemInnerSpacing.y };\n\n                        ImGui::BeginChild(\"Files:Send:clientInfos\", wSize, true, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse);\n\n                        if (g_fileServer.isListening() == false) {\n                            ImGui::TextColored({ 1.0f, 1.0f, 0.0f, 1.0f }, \"Not accepting new connections.\");\n                        } else {\n                            ImGui::TextColored({ 0.0f, 1.0f, 0.0f, 1.0f }, \"Accepting new connections at: %s:%d (%4.1f sec)\",\n                                               GGSock::Communicator::getLocalAddress().c_str(), g_fileServer.getParameters().listenPort, kBroadcastTime_sec - ImGui::GetTime() + g_tLastBroadcast);\n                        }\n\n                        auto clientInfos = g_fileServer.getClientInfos();\n                        if (clientInfos.empty()) {\n                            ImGui::Text(\"No peers currently connected ..\");\n                        } else {\n                            for (const auto & clientInfo : clientInfos) {\n                                ImGui::Text(\"Peer: %s\\n\", clientInfo.second.address.c_str());\n                            }\n                        }\n\n                        ScrollWhenDraggingOnVoid(ImVec2(-mouse_delta.x, -mouse_delta.y), ImGuiMouseButton_Left);\n                        ImGui::EndChild();\n                    }\n\n                    {\n                        if (ImGui::Button(\"Broadcast\", { 0.40f*ImGui::GetContentRegionAvailWidth(), subWindowButtonHeight })) {\n                            g_buffer.inputUI.update = true;\n                            g_buffer.inputUI.flags.newMessage = true;\n                            g_buffer.inputUI.message = {\n                                false,\n                                std::chrono::system_clock::now(),\n                                ::generateFileBroadcastMessage(),\n                                settings.protocolId,\n                                settings.directSequenceSpread,\n                                settings.volume,\n                                Message::FileBroadcast\n                            };\n\n                            messageHistory.push_back(g_buffer.inputUI.message);\n\n                            g_tLastBroadcast = ImGui::GetTime();\n                            g_fileServer.startListening();\n                        }\n                        ImGui::SameLine();\n\n                        if (ImGui::ButtonDisablable(\"Stop\", { 0.50f*ImGui::GetContentRegionAvailWidth(), subWindowButtonHeight }, !g_fileServer.isListening())) {\n                            g_fileServer.stopListening();\n                        }\n                        ImGui::SameLine();\n\n                        if (ImGui::ButtonDisablable(\"Clear\", { 1.0f*ImGui::GetContentRegionAvailWidth(), subWindowButtonHeight }, !hasAtLeastOneFile)) {\n                            g_deleteInfo.uri = \"###ALL-FILES###\";\n                            g_deleteInfo.filename = \"\";\n                            g_deleteId++;\n                        }\n                    }\n                }\n                break;\n            case SubWindowIdFiles::Receive:\n                {\n                    const float statusWindowHeight = 2*style.ItemInnerSpacing.y + 4*ImGui::GetTextLineHeightWithSpacing();\n                    {\n                        const auto wSize = ImVec2 { ImGui::GetContentRegionAvailWidth(), ImGui::GetContentRegionAvail().y - subWindowButtonHeight - statusWindowHeight - 2*style.ItemInnerSpacing.y };\n\n                        ImGui::BeginChild(\"Files:Receive:fileInfos\", wSize, true, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse);\n\n                        int toErase = -1;\n\n                        auto fileInfos = g_receivedFileInfos;\n                        for (const auto & fileInfo : fileInfos) {\n                            ImGui::PushID(fileInfo.first);\n                            ImGui::Text(\"File: '%s' (%4.2f MB)\\n\", fileInfo.second.filename.c_str(), float(fileInfo.second.filesize)/1024.0f/1024.0f);\n\n                            const auto & uri = fileInfo.second.uri;\n                            auto & fileInfoExtended = g_receivedFileInfosExtended[uri];\n\n                            const bool isReceived = fileInfo.second.nChunks == fileInfoExtended.nReceivedChunks;\n\n                            if (isReceived) {\n                                if (fileInfoExtended.requestToShare == false) {\n                                    if (ImGui::Button(ICON_FA_FOLDER \"  To Send\")) {\n                                        fileInfoExtended.requestToShare = true;\n                                        g_receivedId++;\n                                    }\n                                }\n\n                                if (fileInfoExtended.readyToShare) {\n                                    ImGui::TextColored({ 0.0f, 1.0f, 0.0f, 1.0f }, \"Ready to share!\");\n                                }\n                            } else if (g_fileClient.isConnected() && (fileInfoExtended.receiving || fileInfoExtended.nReceivedChunks > 0)) {\n                                if (fileInfoExtended.receiving) {\n                                    if (ImGui::Button(ICON_FA_PAUSE \"  Pause\")) {\n                                        fileInfoExtended.receiving = false;\n                                    }\n                                } else {\n                                    if (ImGui::Button(ICON_FA_PLAY \"  Resume\")) {\n                                        fileInfoExtended.receiving = true;\n                                    }\n                                }\n\n                                ImGui::SameLine();\n                                ImGui::ProgressBar(float(fileInfoExtended.nReceivedChunks)/fileInfo.second.nChunks);\n                            } else if (g_fileClient.isConnected()) {\n                                if (ImGui::Button(ICON_FA_DOWNLOAD \"  Receive\")) {\n                                    fileInfoExtended.receiving = true;\n                                    fileInfoExtended.isChunkReceived.resize(fileInfo.second.nChunks);\n                                    fileInfoExtended.isChunkRequested.resize(fileInfo.second.nChunks);\n                                }\n                            } else {\n                                ImGui::Text(\"%s\", \"\");\n                            }\n\n                            if ((fileInfoExtended.receiving == false || isReceived) && fileInfoExtended.requestToShare == false) {\n                                ImGui::SameLine();\n                                if (ImGui::Button(ICON_FA_TRASH \"  Clear\")) {\n                                    toErase = fileInfo.first;\n                                }\n                            }\n\n                            ImGui::PopID();\n                        }\n\n                        if (toErase != -1) {\n                            g_receivedFiles.erase(g_receivedFileInfos[toErase].uri);\n                            g_receivedFileInfosExtended.erase(g_receivedFileInfos[toErase].uri);\n                            g_receivedFileInfos.erase(toErase);\n                        }\n\n                        ImGui::EndChild();\n                    }\n\n                    {\n                        const auto wSize = ImVec2 { ImGui::GetContentRegionAvailWidth(), ImGui::GetContentRegionAvail().y - subWindowButtonHeight - style.ItemInnerSpacing.y };\n\n                        ImGui::BeginChild(\"Files:Receive:status\", wSize, true, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse);\n\n                        ImGui::PushTextWrapPos();\n                        if (g_hasRemoteInfo == false) {\n                            ImGui::TextColored({ 1.0f, 0.0f, 0.0f, 1.0f }, \"There is no broadcast offer selected yet.\");\n                            ImGui::TextColored({ 1.0f, 0.0f, 0.0f, 1.0f }, \"Wait for a broadcast message to be received first.\");\n                        } else {\n                            if (g_fileClient.isConnected()) {\n                                ImGui::TextColored({ 0.0f, 1.0f, 0.0f, 1.0f }, \"Successfully connected to peer:\");\n                            } else {\n                                ImGui::TextColored({ 1.0f, 1.0f, 0.0f, 1.0f }, \"Broadcast offer from the following peer:\");\n                            }\n                            ImGui::Text(\"Remote IP:   %s\", g_remoteIP.c_str());\n                            ImGui::Text(\"Remote Port: %d\", g_remotePort);\n                            if (g_fileClient.isConnecting()) {\n                                ImGui::TextColored({ 1.0f, 1.0f, 0.0f, 1.0f }, \"Attempting to connect ...\");\n                            }\n                        }\n\n                        ImGui::PopTextWrapPos();\n\n                        ScrollWhenDraggingOnVoid(ImVec2(-mouse_delta.x, -mouse_delta.y), ImGuiMouseButton_Left);\n                        ImGui::EndChild();\n                    }\n\n                    {\n                        if (g_fileClient.isConnecting() == false && g_fileClient.isConnected() == false) {\n                            if (ImGui::ButtonDisablable(\"Connect\", { 1.00f*ImGui::GetContentRegionAvailWidth(), subWindowButtonHeight }, !g_hasRemoteInfo)) {\n                                g_fileClient.connect(g_remoteIP, g_remotePort, 0);\n                            }\n                        }\n\n                        if (g_fileClient.isConnecting() || g_fileClient.isConnected()) {\n                            if (ImGui::Button(\"Disconnect\", { 1.00f*ImGui::GetContentRegionAvailWidth(), subWindowButtonHeight })) {\n                                g_fileClient.disconnect();\n                                g_hasReceivedFileInfos = false;\n                                g_hasRequestedFileInfos = false;\n                                g_hasReceivedFiles = false;\n                            }\n                        }\n                    }\n                }\n                break;\n        };\n    }\n\n    if (windowId == WindowId::Spectrum) {\n        if (hasAudioCaptureData == false) {\n            ImGui::Text(\"%s\", \"\");\n            ImGui::TextColored({ 1.0f, 0.0f, 0.0f, 1.0f }, \"No capture data available!\");\n            ImGui::TextColored({ 1.0f, 0.0f, 0.0f, 1.0f }, \"Please make sure you have allowed microphone access for this app.\");\n        } else {\n            const int nBins = statsCurrent.samplesPerFrame/2;\n\n            static int binMin = 20;\n            static int binMax = 100;\n\n            static float scale = 30.0f;\n\n            static bool showFPS = false;\n            static bool showRx = true;\n            static bool showSpectrogram = false;\n\n            static const float kSpectrogramTime_s = 3.0f;\n\n            // 3 seconds data\n            static int freqDataSize = (kSpectrogramTime_s*statsCurrent.sampleRateInp)/statsCurrent.samplesPerFrame;\n            static int freqDataHead = 0;\n\n            struct FreqData {\n                float freq;\n\n                std::vector<float> mag;\n            };\n\n            static std::vector<FreqData> freqData;\n            if (freqData.empty()) {\n                float df = statsCurrent.sampleRateInp/statsCurrent.samplesPerFrame;\n                freqData.resize(nBins);\n                for (int i = 0; i < nBins; ++i) {\n                    freqData[i].freq = df*i;\n                    freqData[i].mag.resize(freqDataSize);\n                }\n            }\n\n            if (hasNewSpectrum) {\n                for (int i = 0; i < (int) freqData.size(); ++i) {\n                    freqData[i].mag[freqDataHead] = spectrumCurrent[i];\n                }\n                if (++freqDataHead == freqDataSize) {\n                    freqDataHead = 0;\n                }\n\n                hasNewSpectrum = false;\n            }\n\n            if (showSpectrumSettings) {\n                auto width = ImGui::GetContentRegionAvailWidth();\n                ImGui::PushItemWidth(0.5*width);\n                static char buf[64];\n                snprintf(buf, 64, \"Bin: %3d, Freq: %5.2f Hz\", binMin, 0.5*binMin*statsCurrent.sampleRateInp/nBins);\n                ImGui::DragInt(\"##binMin\", &binMin, 1, 0, binMax - 2, buf);\n                ImGui::SameLine();\n                ImGui::Checkbox(\"FPS\", &showFPS);\n                ImGui::SameLine();\n                ImGui::Checkbox(\"Spectrogram\", &showSpectrogram);\n                snprintf(buf, 64, \"Bin: %3d, Freq: %5.2f Hz\", binMax, 0.5*binMax*statsCurrent.sampleRateInp/nBins);\n                ImGui::DragInt(\"##binMax\", &binMax, 1, binMin + 1, nBins, buf);\n                ImGui::SameLine();\n                ImGui::Checkbox(\"Rx\", &showRx);\n                ImGui::SameLine();\n                ImGui::DragFloat(\"##scale\", &scale, 1.0f, 1.0f, 1000.0f);\n                ImGui::PopItemWidth();\n            }\n\n            if (showSpectrogram) {\n                subWindowIdSpectrum = SubWindowIdSpectrum::Spectrogram;\n            } else {\n                subWindowIdSpectrum = SubWindowIdSpectrum::Spectrum;\n            }\n\n            auto p0 = ImGui::GetCursorScreenPos();\n            auto p1 = ImGui::GetContentRegionAvail();\n            p1.x += p0.x;\n            p1.y += p0.y;\n            if (ImGui::IsMouseHoveringRect(p0, p1) && ImGui::IsMouseClicked(0)) {\n                g_buffer.inputUI.update = true;\n                g_buffer.inputUI.flags.changeNeedSpectrum = true;\n                g_buffer.inputUI.needSpectrum = !g_buffer.inputUI.needSpectrum;\n            }\n\n            auto itemSpacingSave = style.ItemSpacing;\n            style.ItemSpacing.x = 0.0f;\n            style.ItemSpacing.y = 0.0f;\n\n            auto windowPaddingSave = style.WindowPadding;\n            style.WindowPadding.x = 0.0f;\n            style.WindowPadding.y = 0.0f;\n\n            auto childBorderSizeSave = style.ChildBorderSize;\n            style.ChildBorderSize = 0.0f;\n\n            ImGui::BeginChild(\"Spectrum:main\", ImGui::GetContentRegionAvail(), true, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse);\n            switch (subWindowIdSpectrum) {\n                case SubWindowIdSpectrum::Spectrum:\n                    {\n                        ImGui::PushTextWrapPos();\n                        if (showFPS) {\n                            auto posSave = ImGui::GetCursorScreenPos();\n                            ImGui::Text(\"FPS: %4.2f\\n\", ImGui::GetIO().Framerate);\n                            ImGui::SetCursorScreenPos(posSave);\n                        }\n                        auto wSize = ImGui::GetContentRegionAvail();\n                        ImGui::PushStyleColor(ImGuiCol_FrameBg, { 0.3f, 0.3f, 0.3f, 0.3f });\n                        if (statsCurrent.receiving) {\n                            ImGui::PushStyleColor(ImGuiCol_PlotHistogram, { 1.0f, 0.0f, 0.0f, 1.0f });\n                        } else {\n                            ImGui::PushStyleColor(ImGuiCol_PlotHistogram, { 0.0f, 1.0f, 0.0f, 1.0f });\n                        }\n                        ImGui::PlotHistogram(\"##plotSpectrumCurrent\",\n                                             spectrumCurrent.data() + binMin, binMax - binMin, 0,\n                                             (std::string(\"Current Spectrum\")).c_str(),\n                                             0.0f, FLT_MAX, wSize);\n                        ImGui::PopStyleColor(2);\n                        ImGui::PopTextWrapPos();\n                    }\n                    break;\n                case SubWindowIdSpectrum::Spectrogram:\n                    {\n                        if (showFPS) {\n                            auto posSave = ImGui::GetCursorScreenPos();\n                            ImGui::Text(\"FPS: %4.2f\\n\", ImGui::GetIO().Framerate);\n                            ImGui::SetCursorScreenPos(posSave);\n                        }\n\n                        float sum = 0.0;\n                        for (int i = binMin; i < binMax; ++i) {\n                            for (int j = 0; j < freqDataSize; ++j) {\n                                sum += freqData[i].mag[j];\n                            }\n                        }\n\n                        int nf = binMax - binMin;\n                        sum /= (nf*freqDataSize);\n\n                        const auto wSize = ImGui::GetContentRegionAvail();\n\n                        const float dx = wSize.x/nf;\n                        const float dy = wSize.y/freqDataSize;\n\n                        auto p0 = ImGui::GetCursorScreenPos();\n\n                        int nChildWindows = 0;\n                        int nFreqPerChild = 32;\n                        ImGui::PushID(nChildWindows++);\n                        ImGui::BeginChild(\"Spectrogram\", { wSize.x, (nFreqPerChild + 1)*dy }, true);\n                        auto drawList = ImGui::GetWindowDrawList();\n\n                        for (int j = 0; j < freqDataSize; ++j) {\n                            if (j > 0 && j % nFreqPerChild == 0) {\n                                ImGui::EndChild();\n                                ImGui::PopID();\n\n                                ImGui::PushID(nChildWindows++);\n                                ImGui::SetCursorScreenPos({ p0.x, p0.y + nFreqPerChild*int(j/nFreqPerChild)*dy });\n                                ImGui::BeginChild(\"Spectrogram\", { wSize.x, (nFreqPerChild + 1)*dy }, true);\n                                drawList = ImGui::GetWindowDrawList();\n                            }\n                            for (int i = 0; i < nf; ++i) {\n                                int k = freqDataHead + j;\n                                if (k >= freqDataSize) k -= freqDataSize;\n                                auto v = freqData[binMin + i].mag[k];\n                                ImVec4 c = { 0.0f, 1.0f, 0.0, 0.0f };\n                                c.w = v/(scale*sum);\n                                drawList->AddRectFilled({ p0.x + i*dx, p0.y + j*dy }, { p0.x + i*dx + dx, p0.y + j*dy + dy }, ImGui::ColorConvertFloat4ToU32(c));\n                            }\n                        }\n\n                        ImGui::EndChild();\n                        ImGui::PopID();\n\n                        while (showRx && messageHistory.size() > 0) {\n                            const auto& msg = messageHistory.back();\n                            static float tRecv = 0.0;\n                            if (g_buffer.inputUI.needSpectrum) {\n                                tRecv = 0.001f*std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now() - msg.timestamp).count();\n                            }\n\n                            if (tRecv > 2.0f*kSpectrogramTime_s || msg.received == false) {\n                                break;\n                            }\n\n                            const auto & protocol = settings.txProtocols[msg.protocolId];\n                            const int msgLength_bytes = settings.isFixedLength ? 1.4f*settings.payloadLength : 1.4f*msg.data.size() + GGWave::kDefaultEncodedDataOffset;\n                            const int msgLength_frames = settings.isFixedLength ?\n                                ((msgLength_bytes + protocol.bytesPerTx - 1)/protocol.bytesPerTx)*protocol.framesPerTx :\n                                ((msgLength_bytes + protocol.bytesPerTx - 1)/protocol.bytesPerTx)*protocol.framesPerTx + 2*GGWave::kDefaultMarkerFrames;\n                            const float frameLength_s = (float(statsCurrent.samplesPerFrame)/statsCurrent.sampleRateInp);\n\n                            const float x0 = protocol.freqStart - binMin;\n                            const float x1 = x0 + 32*protocol.bytesPerTx;\n                            const float y1 = freqDataSize - tRecv/frameLength_s + (settings.isFixedLength ? 0.0f : 0.5*GGWave::kDefaultMarkerFrames);\n                            const float y0 = y1 - msgLength_frames;\n\n                            ImVec4 c = { 1.0f, 0.0f, 0.0, 1.0f };\n                            drawList->AddRect({ p0.x + x0*dx, p0.y + y0*dy }, { p0.x + x1*dx + dx, p0.y + y1*dy }, ImGui::ColorConvertFloat4ToU32(c));\n\n                            break;\n                        }\n\n                    }\n                    break;\n            };\n            ImGui::EndChild();\n\n            style.ItemSpacing = itemSpacingSave;\n            style.WindowPadding = windowPaddingSave;\n            style.ChildBorderSize = childBorderSizeSave;\n        }\n    }\n\n    ImGui::End();\n\n    ImGui::GetIO().KeysDown[ImGui::GetIO().KeyMap[ImGuiKey_Backspace]] = false;\n    ImGui::GetIO().KeysDown[ImGui::GetIO().KeyMap[ImGuiKey_Enter]] = false;\n\n    {\n        std::lock_guard<std::mutex> lock(g_buffer.mutex);\n        g_buffer.inputUI.apply(g_buffer.inputCore);\n    }\n}\n\nvoid deinitMain() {\n    g_isRunning = false;\n}\n"
  },
  {
    "path": "examples/waver/common.h",
    "content": "#pragma once\n\n#include \"ggwave-common-sdl2.h\"\n\n#include <thread>\n#include <vector>\n\nstd::thread initMainAndRunCore();\n\nvoid initMain();\nvoid updateCore();\nvoid renderMain();\nvoid deinitMain();\n\n// share info\n\nstruct ShareInfo {\n    std::string uri;\n    std::string filename;\n    const char * dataBuffer;\n    size_t dataSize;\n};\n\nint getShareId();\nShareInfo getShareInfo();\n\n// open info\n\nstruct OpenInfo {\n    std::string uri;\n    std::string filename;\n    const char * dataBuffer;\n    size_t dataSize;\n};\n\nint getOpenId();\nOpenInfo getOpenInfo();\n\n// delete file\n\nstruct DeleteInfo {\n    std::string uri;\n    std::string filename;\n};\n\nint getDeleteId();\nDeleteInfo getDeleteInfo();\n\n// receive\n\nstruct ReceiveInfo {\n    const char * uri;\n    const char * filename;\n    const char * dataBuffer;\n    size_t dataSize;\n};\n\nint getReceivedId();\nstd::vector<ReceiveInfo> getReceiveInfos();\nbool confirmReceive(const char * uri);\n\n// input\n\nvoid clearAllFiles();\nvoid clearFile(const char * uri);\n\nvoid addFile(\n        const char * uri,\n        const char * filename,\n        const char * dataBuffer,\n        size_t dataSize,\n        bool focus);\n\nvoid addFile(\n        const char * uri,\n        const char * filename,\n        std::vector<char> && data,\n        bool focus);\n"
  },
  {
    "path": "examples/waver/index-tmpl.html",
    "content": "<!doctype html>\n<html lang=\"en-us\">\n    <head>\n        <meta charset=\"utf-8\">\n        <title>Waver</title>\n\n        <meta name=\"viewport\" content=\"width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no\"/>\n\n        <meta name=\"twitter:card\" content=\"summary_large_image\">\n        <meta name=\"twitter:title\" content=\"waver\" />\n        <meta name=\"twitter:description\" content=\"Emscripten port of Waver\" />\n        <meta name=\"twitter:image\" content=\"https://waver.ggerganov.com/waver.png\" />\n\n        <link rel=\"apple-touch-icon\" sizes=\"57x57\" href=\"/apple-icon-57x57.png\">\n        <link rel=\"apple-touch-icon\" sizes=\"60x60\" href=\"/apple-icon-60x60.png\">\n        <link rel=\"apple-touch-icon\" sizes=\"72x72\" href=\"/apple-icon-72x72.png\">\n        <link rel=\"apple-touch-icon\" sizes=\"76x76\" href=\"/apple-icon-76x76.png\">\n        <link rel=\"apple-touch-icon\" sizes=\"114x114\" href=\"/apple-icon-114x114.png\">\n        <link rel=\"apple-touch-icon\" sizes=\"120x120\" href=\"/apple-icon-120x120.png\">\n        <link rel=\"apple-touch-icon\" sizes=\"144x144\" href=\"/apple-icon-144x144.png\">\n        <link rel=\"apple-touch-icon\" sizes=\"152x152\" href=\"/apple-icon-152x152.png\">\n        <link rel=\"apple-touch-icon\" sizes=\"180x180\" href=\"/apple-icon-180x180.png\">\n        <link rel=\"icon\" type=\"image/png\" sizes=\"192x192\"  href=\"/android-icon-192x192.png\">\n        <link rel=\"icon\" type=\"image/png\" sizes=\"32x32\" href=\"/favicon-32x32.png\">\n        <link rel=\"icon\" type=\"image/png\" sizes=\"96x96\" href=\"/favicon-96x96.png\">\n        <link rel=\"icon\" type=\"image/png\" sizes=\"16x16\" href=\"/favicon-16x16.png\">\n        <link rel=\"manifest\" href=\"/manifest.json\">\n        <meta name=\"msapplication-TileColor\" content=\"#ffffff\">\n        <meta name=\"msapplication-TileImage\" content=\"/ms-icon-144x144.png\">\n        <meta name=\"theme-color\" content=\"#ffffff\">\n\n        <link rel=\"stylesheet\" href=\"style.css\">\n    </head>\n    <body>\n        <div id=\"main-controls\">\n            <div id=\"description\" align=\"center\">\n                <h2>Waver: Data Over Sound</h2>\n\n                <div id=\"container_status\" align=\"center\">\n                    Loading WebAssembly module - please wait ...\n                </div>\n                <div id=\"container_button\" align=\"center\">\n                    <br>\n                    <button onClick=\"doInit()\" id=\"butInit\" style=\"width:60px;height:30px;\" disabled>Init</button>\n                </div>\n            </div>\n            <div id=\"container_canvas\" hidden>\n                <canvas class=\"emscripten\" id=\"canvas\" oncontextmenu=\"event.preventDefault()\"></canvas>\n            </div>\n            <br>\n        </div>\n\n        <div id=\"footer\" class=\"cell-version\">\n            <span>\n                Build time: <span class=\"nav-link\">@GIT_DATE@</span> |\n                Commit hash: <a class=\"nav-link\" href=\"https://github.com/ggerganov/ggwave/commit/@GIT_SHA1@\">@GIT_SHA1@</a> |\n                Commit subject: <span class=\"nav-link\">@GIT_COMMIT_SUBJECT@</span> |\n            </span>\n        </div>\n        <div class=\"cell-about\">\n            <a class=\"nav-link\" href=\"https://github.com/ggerganov/ggwave/tree/master/examples/waver\"><span class=\"d-none d-sm-inline\">View on GitHub </span>\n                <svg version=\"1.1\" width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" class=\"octicon octicon-mark-github\" aria-hidden=\"true\"><path fill-rule=\"evenodd\" d=\"M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0 0 16 8c0-4.42-3.58-8-8-8z\"></path></svg>\n            </a>\n        </div>\n\n        <script type='text/javascript'>\n            window.mobilecheck = function() {\n                var check = false;\n                (function(a){if(/(android|bb\\d+|meego).+mobile|avantgo|bada\\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\\.(browser|link)|vodafone|wap|windows ce|xda|xiino/i.test(a)||/1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\\-(n|u)|c55\\/|capi|ccwa|cdm\\-|cell|chtm|cldc|cmd\\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\\-s|devi|dica|dmob|do(c|p)o|ds(12|\\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\\-|_)|g1 u|g560|gene|gf\\-5|g\\-mo|go(\\.w|od)|gr(ad|un)|haie|hcit|hd\\-(m|p|t)|hei\\-|hi(pt|ta)|hp( i|ip)|hs\\-c|ht(c(\\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\\-(20|go|ma)|i230|iac( |\\-|\\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\\/)|klon|kpt |kwc\\-|kyo(c|k)|le(no|xi)|lg( g|\\/(k|l|u)|50|54|\\-[a-w])|libw|lynx|m1\\-w|m3ga|m50\\/|ma(te|ui|xo)|mc(01|21|ca)|m\\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\\-2|po(ck|rt|se)|prox|psio|pt\\-g|qa\\-a|qc(07|12|21|32|60|\\-[2-7]|i\\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\\-|oo|p\\-)|sdk\\/|se(c(\\-|0|1)|47|mc|nd|ri)|sgh\\-|shar|sie(\\-|m)|sk\\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\\-|v\\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\\-|tdg\\-|tel(i|m)|tim\\-|t\\-mo|to(pl|sh)|ts(70|m\\-|m3|m5)|tx\\-9|up(\\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\\-|your|zeto|zte\\-/i.test(a.substr(0,4))) check = true;})(navigator.userAgent||navigator.vendor||window.opera);\n                return check;\n            };\n\n            var isMobile = mobilecheck();\n            var isInitialized = false;\n\n            window.setInterval(function(){\n                    if (isInitialized == false) return;\n                    //var w = window,\n                    //    d = document,\n                    //    e = d.documentElement,\n                    //    g = d.getElementsByTagName('body')[0],\n                    //    x = w.innerWidth || e.clientWidth || g.clientWidth,\n                    //    y = w.innerHeight|| e.clientHeight|| g.clientHeight;\n                    //Module._set_window_size(0.99*x, y - 1.40*document.getElementById('footer').clientHeight);\n            }, 500);\n\n            function checkLoop() {\n                setTimeout(checkLoop, 100);\n            }\n\n            function checkSharedArrayBuffer() {\n                return true;\n                try {\n                    var a = SharedArrayBuffer;\n                } catch (e) {\n                    console.log(e instanceof ReferenceError); // true\n                    return false;\n                }\n\n                return true;\n            }\n\n            function onkeydown(event) {\n                if (event.keyCode >= 112 && event.keyCode <= 123) {\n                    event.stopImmediatePropagation();\n                }\n            }\n\n            function init() {\n                document.getElementById('container_status').innerHTML = \"WebAssembly module initialized successfully! Press the Init button to start:\"\n                document.getElementById('container_status').style.color = \"#00ff00\";\n\n                document.getElementById(\"butInit\").disabled = false;\n\n                window.addEventListener('keydown', onkeydown, true);\n\n                setTimeout(checkLoop, 100);\n            }\n\n            function doInit() {\n                let constraints = {\n                    audio: {\n                        sampleRate: 16000,\n                        channelCount: 1,\n                        echoCancellation: false,\n                        autoGainControl: false,\n                        noiseSuppression: false\n                    }\n                };\n\n                if (isInitialized == false) {\n                    Module._do_init();\n                    var el = document.getElementById(\"container_status\");\n                    el.parentNode.removeChild(el);\n                    el = document.getElementById(\"container_button\");\n                    el.parentNode.removeChild(el);\n                    isInitialized = true;\n                }\n\n                var x = document.getElementById(\"container_canvas\");\n                x.hidden = false;\n            }\n\n            var Module = {\n                arguments: [],\n                preRun: [(function() {\n                }) ],\n                postRun: [(function () {\n                    init();\n                })\n                ],\n                canvas: (function() {\n                    var canvas = document.getElementById('canvas');\n                    canvas.addEventListener(\"webglcontextlost\", function(e) { alert('WebGL context lost. You will need to reload the page.'); e.preventDefault(); }, false);\n\n                    return canvas;\n                })(),\n                print: (function() {\n                    return function(text) {\n                        text = Array.prototype.slice.call(arguments).join(' ');\n                        console.log(text);\n                    };\n                })(),\n                printErr: function(text) {\n                    text = Array.prototype.slice.call(arguments).join(' ');\n                    console.error(text);\n                },\n                setStatus: function(text) {\n                    console.log(\"status: \" + text);\n                },\n                monitorRunDependencies: function(left) {\n                    // no run dependencies to log\n                }\n            };\n            window.onerror = function() {\n                console.log(\"onerror: \" + JSON.stringify(event));\n                if (checkSharedArrayBuffer() == false) {\n                    document.getElementById('container_status').innerHTML = \"Failed to load the WebAssembly module.<br><br>Try openning this page on a PC with latest Chrome browser.\";\n                    document.getElementById('container_status').style.color = \"#ff0000\";\n                    document.getElementById('container_button').hidden = true;\n                }\n            };\n\n        </script>\n\n        <script async type=\"text/javascript\" src=\"waver.js\"></script>\n    </body>\n</html>\n"
  },
  {
    "path": "examples/waver/interface-emscripten.cpp",
    "content": "#include \"interface.h\"\n\n#include <fstream>\n\nvoid interface_addFile(\n        const char * ,\n        const char * ,\n        const char * ,\n        size_t ) {\n}\n\nvoid interface_loadAllFiles() {\n}\n\nvoid interface_shareFile(\n        const char * ,\n        const char * ,\n        const char * ,\n        size_t ) {\n}\n\nvoid interface_openFile(\n        const char * ,\n        const char * ,\n        const char * ,\n        size_t ) {\n}\n\nvoid interface_deleteFile(\n        const char * ,\n        const char * ) {\n}\n\nvoid interface_receiveFile(\n        const char * ,\n        const char * ,\n        const char * ,\n        size_t ) {\n}\n\nbool interface_needReloadFiles() {\n    return false;\n}\n"
  },
  {
    "path": "examples/waver/interface-unix.cpp",
    "content": "#include \"interface.h\"\n\n#include \"pfd/pfd.h\"\n\n#include <fstream>\n\nvoid interface_addFile(\n        const char * ,\n        const char * ,\n        const char * ,\n        size_t ) {\n}\n\nvoid interface_loadAllFiles() {\n}\n\nvoid interface_shareFile(\n        const char * ,\n        const char * filename,\n        const char * dataBuffer,\n        size_t dataSize) {\n    auto f = pfd::save_file(\"Save file\", filename, { \"All Files\", \"*\" }, pfd::opt::none);\n\n    if (f.result().empty() == false) {\n        printf(\"Saving to: %s\\n\", f.result().c_str());\n        std::ofstream fout(f.result(), std::ios::binary);\n        if (fout.is_open() && fout.good()) {\n            fout.write(dataBuffer, dataSize);\n            fout.close();\n        }\n    }\n}\n\nvoid interface_openFile(\n        const char * ,\n        const char * ,\n        const char * ,\n        size_t ) {\n}\n\nvoid interface_deleteFile(\n        const char * ,\n        const char * ) {\n}\n\nvoid interface_receiveFile(\n        const char * uri,\n        const char * filename,\n        const char * dataBuffer,\n        size_t dataSize) {\n    addFile(uri, filename, dataBuffer, dataSize, false);\n}\n\nbool interface_needReloadFiles() {\n    return false;\n}\n"
  },
  {
    "path": "examples/waver/interface.cpp",
    "content": "#include \"interface.h\"\n\nint g_lastShareId = 0;\nint g_lastOpenId = 0;\nint g_lastDeleteId = 0;\nint g_lastReceivedId = 0;\nint g_frameCount = 0;\n\nvoid updateMain() {\n    auto curShareId = getShareId();\n    if (curShareId != g_lastShareId) {\n        auto shareInfo = getShareInfo();\n        interface_shareFile(\n                shareInfo.uri.data(),\n                shareInfo.filename.data(),\n                shareInfo.dataBuffer,\n                shareInfo.dataSize);\n\n        g_lastShareId = curShareId;\n    }\n\n    auto curOpenId = getOpenId();\n    if (curOpenId != g_lastOpenId) {\n        auto openInfo = getOpenInfo();\n        interface_openFile(\n                openInfo.uri.data(),\n                openInfo.filename.data(),\n                openInfo.dataBuffer,\n                openInfo.dataSize);\n\n        g_lastOpenId = curOpenId;\n    }\n\n    auto curDeleteId = getDeleteId();\n    if (curDeleteId != g_lastDeleteId) {\n        auto deleteInfo = getDeleteInfo();\n        interface_deleteFile(deleteInfo.uri.c_str(), deleteInfo.filename.c_str());\n\n        bool isRemoveAll = std::string(deleteInfo.uri) == \"###ALL-FILES###\";\n\n        if (interface_needReloadFiles() || isRemoveAll) {\n            clearAllFiles();\n            interface_loadAllFiles();\n        } else {\n            clearFile(deleteInfo.uri.c_str());\n        }\n\n        g_lastDeleteId = curDeleteId;\n    }\n\n    auto curReceivedId = getReceivedId();\n    if (curReceivedId != g_lastReceivedId) {\n        auto receiveInfos = getReceiveInfos();\n\n        int n = (int) receiveInfos.size();\n\n        for (int i = 0; i < n; ++i) {\n            interface_receiveFile(\n                    receiveInfos[i].uri,\n                    receiveInfos[i].filename,\n                    receiveInfos[i].dataBuffer,\n                    receiveInfos[i].dataSize);\n            confirmReceive(receiveInfos[i].uri);\n        }\n\n        if (interface_needReloadFiles()) {\n            clearAllFiles();\n            interface_loadAllFiles();\n        }\n\n        g_lastReceivedId = curReceivedId;\n    }\n}\n"
  },
  {
    "path": "examples/waver/interface.h",
    "content": "#ifndef interface_h\n#define interface_h\n\n#ifdef __cplusplus\n#include \"common.h\"\n\nextern \"C\" {\n#endif\n\nvoid interface_addFile(\n        const char * uri,\n        const char * filename,\n        const char * dataBuffer,\n        size_t dataSize);\n\nvoid interface_loadAllFiles();\n\nvoid interface_shareFile(\n        const char * uri,\n        const char * filename,\n        const char * dataBuffer,\n        size_t dataSize);\n\nvoid interface_openFile(\n        const char * uri,\n        const char * filename,\n        const char * dataBuffer,\n        size_t dataSize);\n\nvoid interface_deleteFile(\n        const char * uri,\n        const char * filename);\n\nvoid interface_receiveFile(\n        const char * uri,\n        const char * filename,\n        const char * dataBuffer,\n        size_t dataSize);\n\nbool interface_needReloadFiles();\n\nvoid updateMain();\n\n#ifdef __cplusplus\n}\n#endif\n\n#endif /* interface_h */\n"
  },
  {
    "path": "examples/waver/main.cpp",
    "content": "#include \"interface.h\"\n\n#include \"ggwave-common.h\"\n\n#ifdef __EMSCRIPTEN__\n#include \"build_timestamp.h\"\n#include \"emscripten/emscripten.h\"\n#else\n#define EMSCRIPTEN_KEEPALIVE\n#endif\n\n#include <imgui-extra/imgui_impl.h>\n\n#include <SDL.h>\n#include <SDL_opengl.h>\n\n#include <fstream>\n#include <vector>\n#include <functional>\n\nconst float kGlobalImGuiScale = 1.25f;\n\n// ImGui helpers\n\nbool ImGui_tryLoadFont(const std::string & filename, float size = 14.0f, bool merge = false) {\n    printf(\"Trying to load font from '%s' ..\\n\", filename.c_str());\n    std::ifstream f(filename);\n    if (f.good() == false) {\n        printf(\" - failed\\n\");\n        return false;\n    }\n    printf(\" - success\\n\");\n    if (merge) {\n        // todo : ugly static !!!\n        static ImWchar ranges[] = { 0xf000, 0xf3ff, 0 };\n        static ImFontConfig config;\n\n        config.MergeMode = true;\n        config.GlyphOffset = { 0.0f, 0.0f };\n\n        ImGui::GetIO().Fonts->AddFontFromFileTTF(filename.c_str(), size, &config, ranges);\n    } else {\n        ImGui::GetIO().Fonts->AddFontFromFileTTF(filename.c_str(), size);\n    }\n    return true;\n}\n\nbool ImGui_BeginFrame(SDL_Window * window) {\n    SDL_Event event;\n    while (SDL_PollEvent(&event))\n    {\n        ImGui_ProcessEvent(&event);\n        if (event.type == SDL_QUIT) return false;\n        if (event.type == SDL_WINDOWEVENT && event.window.event == SDL_WINDOWEVENT_CLOSE && event.window.windowID == SDL_GetWindowID(window)) return false;\n        if (event.type == SDL_DROPFILE) {\n            printf(\"Dropped file: '%s'\\n\", event.drop.file);\n            auto data = readFile(event.drop.file);\n            if (data.empty()) {\n                fprintf(stderr, \"Unable to access file. Probably missing permissions\\n\");\n                continue;\n            }\n\n            std::string uri = event.drop.file;\n            std::string filename = event.drop.file;\n            if (uri.find(\"/\") || uri.find(\"\\\\\")) {\n                filename = uri.substr(uri.find_last_of(\"/\\\\\") + 1);\n            }\n            addFile(uri.c_str(), filename.c_str(), std::move(data), true);\n        }\n    }\n\n    ImGui_NewFrame(window);\n\n    return true;\n}\n\nbool ImGui_EndFrame(SDL_Window * window) {\n    // Rendering\n    int display_w, display_h;\n    SDL_GetWindowSize(window, &display_w, &display_h);\n    glViewport(0, 0, display_w, display_h);\n    glClearColor(0.0f, 0.0f, 0.0f, 0.4f);\n    glClear(GL_COLOR_BUFFER_BIT);\n\n    ImGui::Render();\n    ImGui_RenderDrawData(ImGui::GetDrawData());\n\n    SDL_GL_SwapWindow(window);\n\n    return true;\n}\n\nbool ImGui_SetStyle() {\n    ImGuiStyle & style = ImGui::GetStyle();\n\n    style.AntiAliasedFill = true;\n    style.AntiAliasedLines = true;\n    style.WindowRounding = 0.0f;\n\n    style.WindowPadding = ImVec2(8, 8);\n    style.WindowRounding = 0.0f;\n    style.FramePadding = ImVec2(4, 3);\n    style.FrameRounding = 0.0f;\n    style.ItemSpacing = ImVec2(8, 4);\n    style.ItemInnerSpacing = ImVec2(4, 4);\n    style.IndentSpacing = 21.0f;\n    style.ScrollbarSize = 16.0f;\n    style.ScrollbarRounding = 9.0f;\n    style.GrabMinSize = 10.0f;\n    style.GrabRounding = 3.0f;\n\n    style.Colors[ImGuiCol_Text]                  = ImVec4(1.00f, 1.00f, 1.00f, 1.00f);\n    style.Colors[ImGuiCol_TextDisabled]          = ImVec4(0.24f, 0.41f, 0.41f, 1.00f);\n    style.Colors[ImGuiCol_WindowBg]              = ImVec4(0.11f, 0.15f, 0.20f, 0.60f);\n    //style.Colors[ImGuiCol_ChildWindowBg]         = ImVec4(0.07f, 0.07f, 0.09f, 1.00f);\n    style.Colors[ImGuiCol_PopupBg]               = ImVec4(0.07f, 0.07f, 0.09f, 1.00f);\n    style.Colors[ImGuiCol_Border]                = ImVec4(0.31f, 0.31f, 0.31f, 0.71f);\n    style.Colors[ImGuiCol_BorderShadow]          = ImVec4(0.00f, 0.00f, 0.00f, 0.00f);\n    style.Colors[ImGuiCol_FrameBg]               = ImVec4(0.00f, 0.39f, 0.39f, 0.39f);\n    style.Colors[ImGuiCol_FrameBgHovered]        = ImVec4(0.26f, 1.00f, 1.00f, 0.39f);\n    style.Colors[ImGuiCol_FrameBgActive]         = ImVec4(0.00f, 0.78f, 0.00f, 1.00f);\n    style.Colors[ImGuiCol_TitleBg]               = ImVec4(0.00f, 0.50f, 0.50f, 0.70f);\n    style.Colors[ImGuiCol_TitleBgCollapsed]      = ImVec4(0.00f, 0.50f, 0.50f, 1.00f);\n    style.Colors[ImGuiCol_TitleBgActive]         = ImVec4(0.00f, 0.70f, 0.70f, 1.00f);\n    style.Colors[ImGuiCol_MenuBarBg]             = ImVec4(0.00f, 0.70f, 0.70f, 1.00f);\n    style.Colors[ImGuiCol_ScrollbarBg]           = ImVec4(0.10f, 0.27f, 0.27f, 1.00f);\n    style.Colors[ImGuiCol_ScrollbarGrab]         = ImVec4(0.80f, 0.80f, 0.83f, 0.31f);\n    style.Colors[ImGuiCol_ScrollbarGrabHovered]  = ImVec4(0.26f, 1.00f, 1.00f, 0.39f);\n    style.Colors[ImGuiCol_ScrollbarGrabActive]   = ImVec4(0.00f, 0.78f, 0.00f, 1.00f);\n    //style.Colors[ImGuiCol_ComboBg]               = ImVec4(0.00f, 0.39f, 0.39f, 1.00f);\n    style.Colors[ImGuiCol_CheckMark]             = ImVec4(0.80f, 0.80f, 0.83f, 0.39f);\n    style.Colors[ImGuiCol_SliderGrab]            = ImVec4(0.80f, 0.80f, 0.83f, 0.39f);\n    style.Colors[ImGuiCol_SliderGrabActive]      = ImVec4(0.00f, 0.78f, 0.00f, 1.00f);\n    style.Colors[ImGuiCol_Button]                = ImVec4(0.13f, 0.55f, 0.55f, 1.00f);\n    style.Colors[ImGuiCol_ButtonHovered]         = ImVec4(0.61f, 1.00f, 0.00f, 0.51f);\n    style.Colors[ImGuiCol_ButtonActive]          = ImVec4(0.00f, 0.78f, 0.00f, 1.00f);\n    style.Colors[ImGuiCol_Header]                = ImVec4(0.79f, 0.51f, 0.00f, 0.51f);\n    style.Colors[ImGuiCol_HeaderHovered]         = ImVec4(0.79f, 0.51f, 0.00f, 0.67f);\n    style.Colors[ImGuiCol_HeaderActive]          = ImVec4(0.79f, 0.51f, 0.00f, 0.67f);\n    //style.Colors[ImGuiCol_Column]                = ImVec4(0.79f, 0.51f, 0.00f, 0.67f);\n    //style.Colors[ImGuiCol_ColumnHovered]         = ImVec4(0.25f, 1.00f, 0.00f, 1.00f);\n    //style.Colors[ImGuiCol_ColumnActive]          = ImVec4(0.79f, 0.51f, 0.00f, 0.67f);\n    style.Colors[ImGuiCol_ResizeGrip]            = ImVec4(0.00f, 0.00f, 0.00f, 0.00f);\n    style.Colors[ImGuiCol_ResizeGripHovered]     = ImVec4(0.26f, 1.00f, 1.00f, 0.39f);\n    style.Colors[ImGuiCol_ResizeGripActive]      = ImVec4(0.00f, 0.78f, 0.00f, 1.00f);\n    //style.Colors[ImGuiCol_CloseButton]           = ImVec4(0.40f, 0.39f, 0.38f, 0.16f);\n    //style.Colors[ImGuiCol_CloseButtonHovered]    = ImVec4(0.26f, 1.00f, 1.00f, 0.39f);\n    //style.Colors[ImGuiCol_CloseButtonActive]     = ImVec4(0.79f, 0.51f, 0.00f, 0.67f);\n    style.Colors[ImGuiCol_PlotLines]             = ImVec4(1.00f, 0.65f, 0.38f, 0.67f);\n    style.Colors[ImGuiCol_PlotLinesHovered]      = ImVec4(0.25f, 1.00f, 0.00f, 1.00f);\n    style.Colors[ImGuiCol_PlotHistogram]         = ImVec4(1.00f, 0.65f, 0.38f, 0.67f);\n    style.Colors[ImGuiCol_PlotHistogramHovered]  = ImVec4(0.25f, 1.00f, 0.00f, 1.00f);\n    style.Colors[ImGuiCol_TextSelectedBg]        = ImVec4(0.25f, 1.00f, 0.00f, 0.43f);\n    style.Colors[ImGuiCol_ModalWindowDarkening]  = ImVec4(1.00f, 0.98f, 0.95f, 0.78f);\n\n    style.ScaleAllSizes(kGlobalImGuiScale);\n\n    return true;\n}\n\nstatic std::function<bool()> g_doInit;\nstatic std::function<void(int, int)> g_setWindowSize;\nstatic std::function<bool()> g_mainUpdate;\n\nvoid mainUpdate(void *) {\n    g_mainUpdate();\n}\n\n// JS interface\n\nextern \"C\" {\n    EMSCRIPTEN_KEEPALIVE\n        int do_init() {\n            return g_doInit();\n        }\n\n    EMSCRIPTEN_KEEPALIVE\n        void set_window_size(int sizeX, int sizeY) {\n            g_setWindowSize(sizeX, sizeY);\n        }\n}\n\nint main(int argc, char** argv) {\n#ifdef __EMSCRIPTEN__\n    printf(\"Build time: %s\\n\", BUILD_TIMESTAMP);\n    printf(\"Press the Init button to start\\n\");\n\n    if (argv[1]) {\n        GGWave_setDefaultCaptureDeviceName(argv[1]);\n    }\n#endif\n\n    auto argm = parseCmdArguments(argc, argv);\n    int captureId = argm[\"c\"].empty() ? 0 : std::stoi(argm[\"c\"]);\n    int playbackId = argm[\"p\"].empty() ? 0 : std::stoi(argm[\"p\"]);\n\n    if (SDL_Init(SDL_INIT_VIDEO) != 0) {\n        fprintf(stderr, \"Error: %s\\n\", SDL_GetError());\n        return -1;\n    }\n\n    ImGui_PreInit();\n\n    int windowX = 400;\n    int windowY = 600;\n\n    // Waver video settings\n    //float scale = 0.65;\n\n    //int windowX = scale*570;\n    //int windowY = scale*917;\n\n    const char * windowTitle = \"Waver\";\n\n#ifdef __EMSCRIPTEN__\n    SDL_Renderer * renderer;\n    SDL_Window * window;\n    SDL_CreateWindowAndRenderer(windowX, windowY, SDL_WINDOW_OPENGL, &window, &renderer);\n#else\n    SDL_WindowFlags window_flags = (SDL_WindowFlags)(SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI);\n    SDL_Window * window = SDL_CreateWindow(windowTitle, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, windowX, windowY, window_flags);\n#endif\n\n    void * gl_context = SDL_GL_CreateContext(window);\n\n    SDL_GL_MakeCurrent(window, gl_context);\n    SDL_GL_SetSwapInterval(1); // Enable vsync\n\n    ImGui_Init(window, gl_context);\n    ImGui::GetIO().IniFilename = nullptr;\n\n    {\n        bool isNotLoaded = true;\n        isNotLoaded = isNotLoaded && !ImGui_tryLoadFont(getBinaryPath() + \"DroidSans.ttf\", kGlobalImGuiScale*14.0f, false);\n        isNotLoaded = isNotLoaded && !ImGui_tryLoadFont(getBinaryPath() + \"../bin/DroidSans.ttf\", kGlobalImGuiScale*14.0f, false);\n        isNotLoaded = isNotLoaded && !ImGui_tryLoadFont(getBinaryPath() + \"../examples/assets/fonts/DroidSans.ttf\", kGlobalImGuiScale*14.0f, false);\n        isNotLoaded = isNotLoaded && !ImGui_tryLoadFont(getBinaryPath() + \"../../examples/assets/fonts/DroidSans.ttf\", kGlobalImGuiScale*14.0f, false);\n    }\n\n    {\n        bool isNotLoaded = true;\n        isNotLoaded = isNotLoaded && !ImGui_tryLoadFont(getBinaryPath() + \"fontawesome-webfont.ttf\", kGlobalImGuiScale*14.0f, true);\n        isNotLoaded = isNotLoaded && !ImGui_tryLoadFont(getBinaryPath() + \"../bin/fontawesome-webfont.ttf\", kGlobalImGuiScale*14.0f, true);\n        isNotLoaded = isNotLoaded && !ImGui_tryLoadFont(getBinaryPath() + \"../examples/assets/fonts/fontawesome-webfont.ttf\", kGlobalImGuiScale*14.0f, true);\n        isNotLoaded = isNotLoaded && !ImGui_tryLoadFont(getBinaryPath() + \"../../examples/assets/fonts/fontawesome-webfont.ttf\", kGlobalImGuiScale*14.0f, true);\n    }\n\n    ImGui_SetStyle();\n\n    SDL_EventState(SDL_DROPFILE, SDL_ENABLE);\n\n    ImGui_NewFrame(window);\n    ImGui::Render();\n\n    // tmp\n    //addFile(\"test0.raw\", \"test0.raw\", std::vector<char>(1024));\n    //addFile(\"test1.jpg\", \"test0.jpg\", std::vector<char>(1024*1024 + 624));\n    //addFile(\"test2.mpv\", \"test0.mov\", std::vector<char>(1024*1024*234 + 53827));\n\n    bool isInitialized = false;\n    std::thread worker;\n\n    g_doInit = [&]() {\n        if (GGWave_init(playbackId, captureId) == false) {\n            fprintf(stderr, \"Failed to initialize GGWave\\n\");\n            return false;\n        }\n\n#ifdef __EMSCRIPTEN__\n        initMain();\n#else\n        worker = initMainAndRunCore();\n#endif\n\n        isInitialized = true;\n\n        return true;\n    };\n\n    g_setWindowSize = [&](int sizeX, int sizeY) {\n        SDL_SetWindowSize(window, sizeX, sizeY);\n    };\n\n    g_mainUpdate = [&]() {\n        if (isInitialized == false) {\n            return true;\n        }\n\n        if (ImGui_BeginFrame(window) == false) {\n            return false;\n        }\n\n        renderMain();\n        updateMain();\n\n        ImGui_EndFrame(window);\n\n#ifdef __EMSCRIPTEN__\n        updateCore();\n#endif\n\n        return true;\n    };\n\n#ifdef __EMSCRIPTEN__\n    emscripten_set_main_loop_arg(mainUpdate, NULL, 60, true);\n#else\n    if (g_doInit() == false) {\n        printf(\"Error: failed to initialize audio\\n\");\n        return -2;\n    }\n\n    while (true) {\n        if (g_mainUpdate() == false) break;\n    }\n\n    deinitMain();\n    worker.join();\n\n    GGWave_deinit();\n\n    // Cleanup\n    ImGui_Shutdown();\n    ImGui::DestroyContext();\n\n    SDL_GL_DeleteContext(gl_context);\n    SDL_DestroyWindow(window);\n    SDL_CloseAudio();\n    SDL_Quit();\n#endif\n\n    return 0;\n}\n"
  },
  {
    "path": "examples/waver/style.css",
    "content": "body {\n    background-image: url('background-0.png');\n    margin: 0; background-color: white;\n    -webkit-font-smoothing: subpixel-antialiased;\n    font-smoothing: subpixel-antialiased;\n}\n#screen {\n    margin: 0;\n    padding: 0;\n    font-size: 13px;\n    height: 100%;\n    font: sans-serif;\n}\n.no-sel {\n    -moz-user-select: none;\n    -webkit-user-select: none;\n    -webkit-touch-callout: none;\n    -ms-user-select:none;\n    user-select:none;\n    -o-user-select:none;\n}\n.cell {\n    pointer-events: none;\n}\n.cell-version {\n    padding-left: 4px;\n    padding-top: 0.5em;\n    text-align: left;\n    display: inline-block;\n    float: left;\n    color: rgba(0, 0, 0, 0.75);\n}\n.cell-about {\n    padding-right: 24px;\n    padding-top: 0.5em;\n    text-align: right;\n    display: inline-block;\n    float: right;\n}\n.nav-link {\n    text-decoration: none;\n    color: rgba(0, 0, 0, 1.0);\n}\n\n#main-container {\n\tfont-size:12px;\n    font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Helvetica, Arial, sans-serif, \"Apple Color Emoji\", \"Segoe UI Emoji\", \"Segoe UI Symbol\";\n}\n\ntextarea {\n\tfont-size:12px;\n\tfont-family: monospace;\n}\n\n.emscripten { padding-right: 0; margin-left: auto; margin-right: auto; display: block; }\ndiv.emscripten_border { border: 1px solid black; }\n\ncanvas.emscripten {\n    border: 0px none;\n    background-color: black;\n    text-shadow: 4px 4px #1f1f1fb4;\n}\n\n.spinner {\n\theight: 30px;\n\twidth: 30px;\n\tmargin: 0;\n\tmargin-top: 20px;\n\tmargin-left: 20px;\n\tdisplay: inline-block;\n\tvertical-align: top;\n\n\t-webkit-animation: rotation .8s linear infinite;\n\t-moz-animation: rotation .8s linear infinite;\n\t-o-animation: rotation .8s linear infinite;\n\tanimation: rotation 0.8s linear infinite;\n\n\tborder-left: 5px solid rgb(235, 235, 235);\n\tborder-right: 5px solid rgb(235, 235, 235);\n\tborder-bottom: 5px solid rgb(235, 235, 235);\n\tborder-top: 5px solid rgb(120, 120, 120);\n\n\tborder-radius: 100%;\n\tbackground-color: rgb(189, 215, 46);\n}\n\n@-webkit-keyframes rotation {\n\tfrom {-webkit-transform: rotate(0deg);}\n\tto {-webkit-transform: rotate(360deg);}\n}\n@-moz-keyframes rotation {\n\tfrom {-moz-transform: rotate(0deg);}\n\tto {-moz-transform: rotate(360deg);}\n}\n@-o-keyframes rotation {\n\tfrom {-o-transform: rotate(0deg);}\n\tto {-o-transform: rotate(360deg);}\n}\n@keyframes rotation {\n\tfrom {transform: rotate(0deg);}\n\tto {transform: rotate(360deg);}\n}\n\n#status {\n\tdisplay: inline-block;\n\tvertical-align: top;\n\tmargin-top: 30px;\n\tmargin-left: 20px;\n\tfont-weight: bold;\n\tcolor: rgb(120, 120, 120);\n}\n\n#progress {\n\theight: 20px;\n\twidth: 30px;\n}\n\n#output {\n\twidth: 800px;\n\theight: 200px;\n\tmargin: 0 auto;\n\tmargin-top: 10px;\n\tborder-left: 0px;\n\tborder-right: 0px;\n\tpadding-left: 0px;\n\tpadding-right: 0px;\n\tbackground-color: black;\n\tcolor: white;\n\tfont-size:10px;\n\tfont-family: 'Lucida Console', Monaco, monospace;\n\toutline: none;\n}\n\n.led-box {\n\theight: 30px;\n\twidth: 25%;\n\tmargin: 10px 0;\n\tfloat: left;\n}\n\n.led-box p {\n\tfont-size: 12px;\n\ttext-align: center;\n\tmargin: 1em;\n}\n\n.led-red {\n\tmargin: 0 auto;\n\twidth: 12px;\n\theight: 12px;\n\tbackground-color: #F00;\n\tborder-radius: 50%;\n\tbox-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;\n\t-webkit-animation: blinkRed 0.5s infinite;\n\t-moz-animation: blinkRed 0.5s infinite;\n\t-ms-animation: blinkRed 0.5s infinite;\n\t-o-animation: blinkRed 0.5s infinite;\n\tanimation: blinkRed 0.5s infinite;\n}\n\n@-webkit-keyframes blinkRed {\n\tfrom { background-color: #F00; }\n\t50% { 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;}\n\tto { background-color: #F00; }\n}\n@-moz-keyframes blinkRed {\n\tfrom { background-color: #F00; }\n\t50% { 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;}\n\tto { background-color: #F00; }\n}\n@-ms-keyframes blinkRed {\n\tfrom { background-color: #F00; }\n\t50% { 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;}\n\tto { background-color: #F00; }\n}\n@-o-keyframes blinkRed {\n\tfrom { background-color: #F00; }\n\t50% { 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;}\n\tto { background-color: #F00; }\n}\n@keyframes blinkRed {\n\tfrom { background-color: #F00; }\n\t50% { 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;}\n\tto { background-color: #F00; }\n}\n\n.led-yellow {\n\tmargin: 0 auto;\n\twidth: 12px;\n\theight: 12px;\n\tbackground-color: #FF0;\n\tborder-radius: 50%;\n\tbox-shadow: rgba(0, 0, 0, 0.2) 0 -1px 7px 1px, inset #808002 0 -1px 9px, #FF0 0 2px 12px;\n\t-webkit-animation: blinkYellow 1s infinite;\n\t-moz-animation: blinkYellow 1s infinite;\n\t-ms-animation: blinkYellow 1s infinite;\n\t-o-animation: blinkYellow 1s infinite;\n\tanimation: blinkYellow 1s infinite;\n}\n\n@-webkit-keyframes blinkYellow {\n\tfrom { background-color: #FF0; }\n\t50% { background-color: #AA0; box-shadow: rgba(0, 0, 0, 0.2) 0 -1px 7px 1px, inset #808002 0 -1px 9px, #FF0 0 2px 0; }\n\tto { background-color: #FF0; }\n}\n@-moz-keyframes blinkYellow {\n\tfrom { background-color: #FF0; }\n\t50% { background-color: #AA0; box-shadow: rgba(0, 0, 0, 0.2) 0 -1px 7px 1px, inset #808002 0 -1px 9px, #FF0 0 2px 0; }\n\tto { background-color: #FF0; }\n}\n@-ms-keyframes blinkYellow {\n\tfrom { background-color: #FF0; }\n\t50% { background-color: #AA0; box-shadow: rgba(0, 0, 0, 0.2) 0 -1px 7px 1px, inset #808002 0 -1px 9px, #FF0 0 2px 0; }\n\tto { background-color: #FF0; }\n}\n@-o-keyframes blinkYellow {\n\tfrom { background-color: #FF0; }\n\t50% { background-color: #AA0; box-shadow: rgba(0, 0, 0, 0.2) 0 -1px 7px 1px, inset #808002 0 -1px 9px, #FF0 0 2px 0; }\n\tto { background-color: #FF0; }\n}\n@keyframes blinkYellow {\n\tfrom { background-color: #FF0; }\n\t50% { background-color: #AA0; box-shadow: rgba(0, 0, 0, 0.2) 0 -1px 7px 1px, inset #808002 0 -1px 9px, #FF0 0 2px 0; }\n\tto { background-color: #FF0; }\n}\n\n.led-green {\n\tmargin: 0 auto;\n\twidth: 12px;\n\theight: 12px;\n\tbackground-color: #ABFF00;\n\tborder-radius: 50%;\n\tbox-shadow: rgba(0, 0, 0, 0.2) 0 -1px 7px 1px, inset #304701 0 -1px 9px, #89FF00 0 2px 12px;\n}\n\n.led-blue {\n\tmargin: 0 auto;\n\twidth: 18px;\n\theight: 18px;\n\tbackground-color: #24E0FF;\n\tborder-radius: 50%;\n\tbox-shadow: rgba(0, 0, 0, 0.2) 0 -1px 7px 1px, inset #006 0 -1px 9px, #3F8CFF 0 2px 14px;\n}\n\ntable td {\n    border: 1px solid #e8e8e8;\n}\ntable th, table td {\n    padding: 10px 10px;\n}\ntable td {\n    border: 1px solid #e8e8e8;\n}\ntable th, table td {\n    padding: 10px 10px;\n}\ntd[Attributes Style] {\n    text-align: -webkit-center;\n}\ntd {\n    display: table-cell;\n    vertical-align: inherit;\n}\ntable {\n    margin-bottom: 30px;\n    width: 800px;\n    text-align: left;\n    color: #3f3f3f;\n    border-collapse: collapse;\n    border: 1px solid #e8e8e8;\n}\ntable {\n    margin-bottom: 30px;\n    width: 800px;\n    text-align: left;\n    color: #3f3f3f;\n    border-collapse: collapse;\n    border: 1px solid #e8e8e8;\n}\ntable {\n    border-collapse: separate;\n    border-spacing: 2px;\n}\n\n#description {\n    margin: 10px;\n    padding: 10px;\n    color: rgba(255, 255, 255, 1.00);\n    text-shadow: 2px 2px #1f1f1fb4;\n}\n.text-body {\n    color: rgba(255, 255, 255, 1.00);\n    text-shadow: 2px 2px #1f1f1fb4;\n}\n.cell-version {\n    padding-left: 4px;\n    padding-top: 0.5em;\n    text-align: left;\n    display: inline-block;\n    float: left;\n    color: rgba(255, 255, 255, 0.75);\n    text-shadow: 2px 2px #1f1f1fb4;\n}\n.cell-about {\n    padding-right: 24px;\n    padding-top: 0.5em;\n    text-align: right;\n    display: inline-block;\n    float: right;\n}\n.nav-link {\n    text-decoration: none;\n    color: rgba(255, 255, 255, 1.0);\n    text-shadow: 2px 2px #1f1f1fb4;\n}\n.nav-link2 {\n    text-decoration: none;\n    color: rgba(0, 255, 0, 1.0);\n    text-shadow: 2px 2px #1f1f1fb4;\n}\nsvg {\n    -webkit-filter: invert(100%); /* safari 6.0 - 9.0 */\n    filter: invert(100%);\n}\n"
  },
  {
    "path": "include/ggwave/ggwave.h",
    "content": "#ifndef GGWAVE_H\n#define GGWAVE_H\n\n#ifdef GGWAVE_SHARED\n#    ifdef _WIN32\n#        ifdef GGWAVE_BUILD\n#            define GGWAVE_API __declspec(dllexport)\n#        else\n#            define GGWAVE_API __declspec(dllimport)\n#        endif\n#    else\n#        define GGWAVE_API __attribute__ ((visibility (\"default\")))\n#    endif\n#else\n#    define GGWAVE_API\n#endif\n\n#if defined(ARDUINO_UNO)\n#define GGWAVE_CONFIG_FEW_PROTOCOLS\n#endif\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\n    //\n    // C interface\n    //\n\n#define GGWAVE_MAX_INSTANCES 4\n\n    // Data format of the audio samples\n    typedef enum {\n        GGWAVE_SAMPLE_FORMAT_UNDEFINED,\n        GGWAVE_SAMPLE_FORMAT_U8,\n        GGWAVE_SAMPLE_FORMAT_I8,\n        GGWAVE_SAMPLE_FORMAT_U16,\n        GGWAVE_SAMPLE_FORMAT_I16,\n        GGWAVE_SAMPLE_FORMAT_F32,\n    } ggwave_SampleFormat;\n\n    // Protocol ids\n    typedef enum {\n#ifndef GGWAVE_CONFIG_FEW_PROTOCOLS\n        GGWAVE_PROTOCOL_AUDIBLE_NORMAL,\n        GGWAVE_PROTOCOL_AUDIBLE_FAST,\n        GGWAVE_PROTOCOL_AUDIBLE_FASTEST,\n        GGWAVE_PROTOCOL_ULTRASOUND_NORMAL,\n        GGWAVE_PROTOCOL_ULTRASOUND_FAST,\n        GGWAVE_PROTOCOL_ULTRASOUND_FASTEST,\n#endif\n        GGWAVE_PROTOCOL_DT_NORMAL,\n        GGWAVE_PROTOCOL_DT_FAST,\n        GGWAVE_PROTOCOL_DT_FASTEST,\n        GGWAVE_PROTOCOL_MT_NORMAL,\n        GGWAVE_PROTOCOL_MT_FAST,\n        GGWAVE_PROTOCOL_MT_FASTEST,\n\n#ifndef GGWAVE_CONFIG_FEW_PROTOCOLS\n        GGWAVE_PROTOCOL_CUSTOM_0,\n        GGWAVE_PROTOCOL_CUSTOM_1,\n        GGWAVE_PROTOCOL_CUSTOM_2,\n        GGWAVE_PROTOCOL_CUSTOM_3,\n        GGWAVE_PROTOCOL_CUSTOM_4,\n        GGWAVE_PROTOCOL_CUSTOM_5,\n        GGWAVE_PROTOCOL_CUSTOM_6,\n        GGWAVE_PROTOCOL_CUSTOM_7,\n        GGWAVE_PROTOCOL_CUSTOM_8,\n        GGWAVE_PROTOCOL_CUSTOM_9,\n\n#endif\n        GGWAVE_PROTOCOL_COUNT,\n    } ggwave_ProtocolId;\n\n    typedef enum {\n        GGWAVE_FILTER_HANN,\n        GGWAVE_FILTER_HAMMING,\n        GGWAVE_FILTER_FIRST_ORDER_HIGH_PASS,\n    } ggwave_Filter;\n\n    // Operating modes of ggwave\n    //\n    //   GGWAVE_OPERATING_MODE_RX:\n    //     The instance will be able to receive audio data\n    //\n    //   GGWAVE_OPERATING_MODE_TX:\n    //     The instance will be able generate audio waveforms for transmission\n    //\n    //   GGWAVE_OPERATING_MODE_TX_ONLY_TONES:\n    //     The encoding process generates only a list of tones instead of full audio\n    //     waveform. This is useful for low-memory devices and embedded systems.\n    //\n    //   GGWAVE_OPERATING_MODE_USE_DSS:\n    //     Enable the built-in Direct Sequence Spread (DSS) algorithm\n    //\n    enum {\n        GGWAVE_OPERATING_MODE_RX            = 1 << 1,\n        GGWAVE_OPERATING_MODE_TX            = 1 << 2,\n        GGWAVE_OPERATING_MODE_RX_AND_TX     = (GGWAVE_OPERATING_MODE_RX |\n                                               GGWAVE_OPERATING_MODE_TX),\n        GGWAVE_OPERATING_MODE_TX_ONLY_TONES = 1 << 3,\n        GGWAVE_OPERATING_MODE_USE_DSS       = 1 << 4,\n    };\n\n    // GGWave instance parameters\n    //\n    //   If payloadLength <= 0, then GGWave will transmit with variable payload length\n    //   depending on the provided payload. Sound markers are used to identify the\n    //   start and end of the transmission.\n    //\n    //   If payloadLength > 0, then the transmitted payload will be of the specified\n    //   fixed length. In this case, no sound markers are emitted and a slightly\n    //   different decoding scheme is applied. This is useful in cases where the\n    //   length of the payload is known in advance.\n    //\n    //   The sample rates are values typically between 1000 and 96000.\n    //   Default value: GGWave::kDefaultSampleRate\n    //\n    //   The captured audio is resampled to the specified sampleRate if sampleRatInp\n    //   is different from sampleRate. Same applies to the transmitted audio.\n    //\n    //   The samplesPerFrame is the number of samples on which ggwave performs FFT.\n    //   This affects the number of bins in the Fourier spectrum.\n    //   Default value: GGWave::kDefaultSamplesPerFrame\n    //\n    //   The operatingMode controls which functions of the ggwave instance are enabled.\n    //   Use this parameter to reduce the memory footprint of the ggwave instance. For\n    //   example, if only Rx is enabled, then the memory buffers needed for the Tx will\n    //   not be allocated.\n    //\n    typedef struct {\n        int                 payloadLength;        // payload length\n        float               sampleRateInp;        // capture sample rate\n        float               sampleRateOut;        // playback sample rate\n        float               sampleRate;           // the operating sample rate\n        int                 samplesPerFrame;      // number of samples per audio frame\n        float               soundMarkerThreshold; // sound marker detection threshold\n        ggwave_SampleFormat sampleFormatInp;      // format of the captured audio samples\n        ggwave_SampleFormat sampleFormatOut;      // format of the playback audio samples\n        int                 operatingMode;        // operating mode\n    } ggwave_Parameters;\n\n    // GGWave instances are identified with an integer and are stored\n    // in a private map container. Using void * caused some issues with\n    // the python module and unfortunately had to do it this way\n    typedef int ggwave_Instance;\n\n    // Change file stream for internal ggwave logging. NULL - disable logging\n    //\n    //   Intentionally passing it as void * instead of FILE * to avoid including a header\n    //\n    //     // log to standard error\n    //     ggwave_setLogFile(stderr);\n    //\n    //     // log to standard output\n    //     ggwave_setLogFile(stdout);\n    //\n    //     // disable logging\n    //     ggwave_setLogFile(NULL);\n    //\n    //  Note: not thread-safe. Do not call while any GGWave instances are running\n    //\n    GGWAVE_API void ggwave_setLogFile(void * fptr);\n\n    // Helper method to get default instance parameters\n    GGWAVE_API ggwave_Parameters ggwave_getDefaultParameters(void);\n\n    // Create a new GGWave instance with the specified parameters\n    //\n    //   The newly created instance is added to the internal map container.\n    //   This function returns an id that can be used to identify this instance.\n    //   Make sure to deallocate the instance at the end by calling ggwave_free()\n    //\n    GGWAVE_API ggwave_Instance ggwave_init(ggwave_Parameters parameters);\n\n    // Free a GGWave instance\n    GGWAVE_API void ggwave_free(ggwave_Instance instance);\n\n    // Encode data into audio waveform\n    //\n    //   instance       - the GGWave instance to use\n    //   payloadBuffer  - the data to encode\n    //   payloadSize    - number of bytes in the input payloadBuffer\n    //   protocolId     - the protocol to use for encoding\n    //   volume         - the volume of the generated waveform [0, 100]\n    //                    usually 25 is OK and you should not go over 50\n    //   waveformBuffer - the generated audio waveform. must be big enough to fit the generated data\n    //   query          - if == 0, encode data in to waveformBuffer, returns number of bytes\n    //                    if != 0, do not perform encoding.\n    //                    if == 1, return waveform size in bytes\n    //                    if != 1, return waveform size in samples\n    //\n    //   returns the number of generated bytes or samples (see query)\n    //\n    //   returns -1 if there was an error\n    //\n    //   This function can be used to encode some binary data (payload) into an audio waveform.\n    //\n    //     payload -> waveform\n    //\n    //   When calling it, make sure that the waveformBuffer is big enough to store the\n    //   generated waveform. This means that its size must be at least:\n    //\n    //     nSamples*sizeOfSample_bytes\n    //\n    //   Where nSamples is the number of audio samples in the waveform and sizeOfSample_bytes\n    //   is the size of a single sample in bytes based on the sampleFormatOut parameter\n    //   specified during the initialization of the GGWave instance.\n    //\n    //   If query != 0, then this function does not perform the actual encoding and just\n    //   outputs the expected size of the waveform that would be generated if you call it\n    //   with query == 0. This mechanism can be used to ask ggwave how much memory to\n    //   allocate for the waveformBuffer. For example:\n    //\n    //     // this is the data to encode\n    //     const char * payload = \"test\";\n    //\n    //     // query the number of bytes in the waveform\n    //     int n = ggwave_encode(instance, payload, 4, GGWAVE_PROTOCOL_AUDIBLE_FAST, 25, NULL, 1);\n    //\n    //     // allocate the output buffer\n    //     char waveform[n];\n    //\n    //     // generate the waveform\n    //     ggwave_encode(instance, payload, 4, GGWAVE_PROTOCOL_AUDIBLE_FAST, 25, waveform, 0);\n    //\n    //   The payloadBuffer can be any binary data that you would like to transmit (i.e. the payload).\n    //   Usually, this is some text, but it can be any sequence of bytes.\n    //\n    GGWAVE_API int ggwave_encode(\n            ggwave_Instance instance,\n            const void * payloadBuffer,\n            int payloadSize,\n            ggwave_ProtocolId protocolId,\n            int volume,\n            void * waveformBuffer,\n            int query);\n\n    // Decode an audio waveform into data\n    //\n    //   instance       - the GGWave instance to use\n    //   waveformBuffer - the audio waveform\n    //   waveformSize   - number of bytes in the input waveformBuffer\n    //   payloadBuffer  - stores the decoded data on success\n    //                    the maximum size of the output is GGWave::kMaxDataSize\n    //\n    //   returns the number of decoded bytes\n    //\n    //   Use this function to continuously provide audio samples to a GGWave instance.\n    //   On each call, GGWave will analyze the provided data and if it detects a payload,\n    //   it will return a non-zero result.\n    //\n    //     waveform -> payload\n    //\n    //   If the return value is -1 then there was an error during the decoding process.\n    //   Usually can occur if there is a lot of background noise in the audio.\n    //\n    //   If the return value is greater than 0, then there are that number of bytes decoded.\n    //\n    //   IMPORTANT:\n    //   Notice that the decoded data written to the payloadBuffer is NOT null terminated.\n    //\n    //   Example:\n    //\n    //     char payload[256];\n    //\n    //     while (true) {\n    //         ... capture samplesPerFrame audio samples into waveform ...\n    //\n    //         int ret = ggwave_decode(instance, waveform, samplesPerFrame*sizeOfSample_bytes, payload);\n    //         if (ret > 0) {\n    //             payload[ret] = 0; // null terminate the string\n    //             printf(\"Received payload: '%s'\\n\", payload);\n    //         }\n    //     }\n    //\n    GGWAVE_API int ggwave_decode(\n            ggwave_Instance instance,\n            const void * waveformBuffer,\n            int waveformSize,\n            void * payloadBuffer);\n\n    // Memory-safe overload of ggwave_decode\n    //\n    //   payloadSize - optionally specify the size of the output buffer\n    //\n    //   If the return value is -2 then the provided payloadBuffer was not big enough to\n    //   store the decoded data.\n    //\n    //   See ggwave_decode for more information\n    //\n    GGWAVE_API int ggwave_ndecode(\n            ggwave_Instance instance,\n            const void * waveformBuffer,\n            int waveformSize,\n            void * payloadBuffer,\n            int payloadSize);\n\n    // Toggle Rx protocols on and off\n    //\n    //   protocolId - Id of the Rx protocol to modify\n    //   state      - 0 - disable, 1 - enable\n    //\n    //   If an Rx protocol is enabled, newly constructued GGWave instances will attempt to decode\n    //   received data using this protocol. By default, all protocols are enabled.\n    //   Use this function to restrict the number of Rx protocols used in the decoding\n    //   process. This helps to reduce the number of false positives and improves the transmission\n    //   accuracy, especially when the Tx/Rx protocol is known in advance.\n    //\n    //   Note that this function does not affect the decoding process of instances that have\n    //   already been created.\n    //\n    GGWAVE_API void ggwave_rxToggleProtocol(\n            ggwave_ProtocolId protocolId,\n            int state);\n\n    // Toggle Tx protocols on and off\n    //\n    //   protocolId - Id of the Tx protocol to modify\n    //   state      - 0 - disable, 1 - enable\n    //\n    //   If an Tx protocol is enabled, newly constructued GGWave instances will be able to transmit\n    //   data using this protocol. By default, all protocols are enabled.\n    //   Use this function to restrict the number of Tx protocols used for transmission.\n    //   This can reduce the required memory by the GGWave instance.\n    //\n    //   Note that this function does not affect instances that have already been created.\n    //\n    GGWAVE_API void ggwave_txToggleProtocol(\n            ggwave_ProtocolId protocolId,\n            int state);\n\n    // Set freqStart for an Rx protocol\n    GGWAVE_API void ggwave_rxProtocolSetFreqStart(\n            ggwave_ProtocolId protocolId,\n            int freqStart);\n\n    // Set freqStart for a Tx protocol\n    GGWAVE_API void ggwave_txProtocolSetFreqStart(\n            ggwave_ProtocolId protocolId,\n            int freqStart);\n\n    // Return recvDuration_frames value for a rx protocol\n    GGWAVE_API int ggwave_rxDurationFrames(\n            ggwave_Instance instance);\n\n#ifdef __cplusplus\n}\n\n//\n// C++ interface\n//\n\ntemplate <typename T>\nstruct ggvector {\nprivate:\n    T * m_data;\n    int m_size;\n\npublic:\n    using value_type = T;\n\n    ggvector() : m_data(nullptr), m_size(0) {}\n    ggvector(T * data, int size) : m_data(data), m_size(size) {}\n\n    ggvector(const ggvector<T> & other) = default;\n\n    // delete operator=\n    ggvector & operator=(const ggvector &) = delete;\n    ggvector & operator=(ggvector &&) = delete;\n\n    T & operator[](int i) { return m_data[i]; }\n    const T & operator[](int i) const { return m_data[i]; }\n\n    int size() const { return m_size; }\n    T * data() const { return m_data; }\n\n    T * begin() const { return m_data; }\n    T * end() const { return m_data + m_size; }\n\n    void assign(const ggvector & other);\n    void copy(const ggvector & other);\n\n    void zero();\n    void zero(int n);\n};\n\ntemplate <typename T>\nstruct ggmatrix {\nprivate:\n    T * m_data;\n    int m_size0;\n    int m_size1;\n\npublic:\n    using value_type = T;\n\n    ggmatrix() : m_data(nullptr), m_size0(0), m_size1(0) {}\n    ggmatrix(T * data, int size0, int size1) : m_data(data), m_size0(size0), m_size1(size1) {}\n\n    ggvector<T> operator[](int i) {\n        return ggvector<T>(m_data + i*m_size1, m_size1);\n    }\n\n    int size() const { return m_size0; }\n\n    void zero();\n};\n\n#include <stdint.h>\n#include <stdio.h>\n\n#ifdef ARDUINO\n#if defined(ARDUINO_ARCH_AVR) || defined(ARDUINO_ARCH_SAMD) || defined(ARDUINO_ARDUINO_NANO33BLE) || defined(ARDUINO_ARCH_MBED_RP2040) || defined(ARDUINO_ARCH_RP2040)\n#include <avr/pgmspace.h>\n#else\n#include <pgmspace.h>\n#endif\n#endif\n\nclass GGWave {\npublic:\n    static constexpr auto kSampleRateMin               = 1000.0f;\n    static constexpr auto kSampleRateMax               = 96000.0f;\n    static constexpr auto kDefaultSampleRate           = 48000.0f;\n    static constexpr auto kDefaultSamplesPerFrame      = 1024;\n    static constexpr auto kDefaultVolume               = 10;\n    static constexpr auto kDefaultSoundMarkerThreshold = 3.0f;\n    static constexpr auto kDefaultMarkerFrames         = 16;\n    static constexpr auto kDefaultEncodedDataOffset    = 3;\n    static constexpr auto kMaxSamplesPerFrame          = 1024;\n    static constexpr auto kMaxDataSize                 = 256;\n    static constexpr auto kMaxLengthVariable           = 140;\n    static constexpr auto kMaxLengthFixed              = 64;\n    static constexpr auto kMaxSpectrumHistory          = 4;\n    static constexpr auto kMaxRecordedFrames           = 2048;\n\n    using Parameters    = ggwave_Parameters;\n    using SampleFormat  = ggwave_SampleFormat;\n    using ProtocolId    = ggwave_ProtocolId;\n    using TxProtocolId  = ggwave_ProtocolId;\n    using RxProtocolId  = ggwave_ProtocolId;\n    using OperatingMode = int; // ggwave_OperatingMode;\n\n    struct Protocol {\n        const char * name;  // string identifier of the protocol\n\n        int16_t freqStart;   // FFT bin index of the lowest frequency\n        int8_t  framesPerTx; // number of frames to transmit a single chunk of data\n        int8_t  bytesPerTx;  // number of bytes in a chunk of data\n        int8_t  extra;       // 2 if this is a mono-tone protocol, 1 otherwise\n\n        bool enabled;\n\n        int nTones() const { return (2*bytesPerTx)/extra; }\n        int nDataBitsPerTx() const { return 8*bytesPerTx; }\n        int txDuration_ms(int samplesPerFrame, float sampleRate) const {\n            return framesPerTx*((1000.0f*samplesPerFrame)/sampleRate);\n        }\n    };\n\n    using TxProtocol = Protocol;\n    using RxProtocol = Protocol;\n\n    struct Protocols;\n\n    using TxProtocols = Protocols;\n    using RxProtocols = Protocols;\n\n    struct Protocols {\n        Protocol data[GGWAVE_PROTOCOL_COUNT];\n\n        int size() const {\n            return GGWAVE_PROTOCOL_COUNT;\n        }\n\n        bool empty() const {\n            return size() == 0;\n        }\n\n        Protocol & operator[](ProtocolId id) {\n            return data[id];\n        }\n\n        Protocol & operator[](int id) {\n            return data[id];\n        }\n\n        const Protocol & operator[](ProtocolId id) const {\n            return data[id];\n        }\n\n        const Protocol & operator[](int id) const {\n            return data[id];\n        }\n\n        void enableAll();\n        void disableAll();\n\n        void toggle(ProtocolId id, bool state);\n        void only(ProtocolId id);\n\n        static Protocols & kDefault() {\n            static Protocols protocols;\n\n            static bool initialized = false;\n            if (initialized == false) {\n                for (int i = 0; i < GGWAVE_PROTOCOL_COUNT; ++i) {\n                    protocols.data[i].name = nullptr;\n                    protocols.data[i].enabled = false;\n                }\n\n#if defined(ARDUINO_AVR_UNO)\n// For Arduino Uno, we put the strings in PROGMEM to save as much RAM as possible:\n#define GGWAVE_PSTR PSTR\n#else\n#define GGWAVE_PSTR(str) (str)\n#endif\n\n#ifndef GGWAVE_CONFIG_FEW_PROTOCOLS\n                protocols.data[GGWAVE_PROTOCOL_AUDIBLE_NORMAL]     = { GGWAVE_PSTR(\"Normal\"),       40,  9, 3, 1, true, };\n                protocols.data[GGWAVE_PROTOCOL_AUDIBLE_FAST]       = { GGWAVE_PSTR(\"Fast\"),         40,  6, 3, 1, true, };\n                protocols.data[GGWAVE_PROTOCOL_AUDIBLE_FASTEST]    = { GGWAVE_PSTR(\"Fastest\"),      40,  3, 3, 1, true, };\n                protocols.data[GGWAVE_PROTOCOL_ULTRASOUND_NORMAL]  = { GGWAVE_PSTR(\"[U] Normal\"),   320, 9, 3, 1, true, };\n                protocols.data[GGWAVE_PROTOCOL_ULTRASOUND_FAST]    = { GGWAVE_PSTR(\"[U] Fast\"),     320, 6, 3, 1, true, };\n                protocols.data[GGWAVE_PROTOCOL_ULTRASOUND_FASTEST] = { GGWAVE_PSTR(\"[U] Fastest\"),  320, 3, 3, 1, true, };\n#endif\n                protocols.data[GGWAVE_PROTOCOL_DT_NORMAL]          = { GGWAVE_PSTR(\"[DT] Normal\"),  24,  9, 1, 1, true, };\n                protocols.data[GGWAVE_PROTOCOL_DT_FAST]            = { GGWAVE_PSTR(\"[DT] Fast\"),    24,  6, 1, 1, true, };\n                protocols.data[GGWAVE_PROTOCOL_DT_FASTEST]         = { GGWAVE_PSTR(\"[DT] Fastest\"), 24,  3, 1, 1, true, };\n                protocols.data[GGWAVE_PROTOCOL_MT_NORMAL]          = { GGWAVE_PSTR(\"[MT] Normal\"),  24,  9, 1, 2, true, };\n                protocols.data[GGWAVE_PROTOCOL_MT_FAST]            = { GGWAVE_PSTR(\"[MT] Fast\"),    24,  6, 1, 2, true, };\n                protocols.data[GGWAVE_PROTOCOL_MT_FASTEST]         = { GGWAVE_PSTR(\"[MT] Fastest\"), 24,  3, 1, 2, true, };\n\n#undef GGWAVE_PSTR\n                initialized = true;\n            }\n\n            return protocols;\n        }\n\n        static TxProtocols & tx();\n        static RxProtocols & rx();\n    };\n\n    using Tone = int8_t;\n\n    // Tone data structure\n    //\n    //   Each Tone element is the bin index of the tone frequency.\n    //   For protocol p:\n    //     - freq_hz = (p.freqStart + Tone) * hzPerSample\n    //     - duration_ms = p.txDuration_ms(samplesPerFrame, sampleRate)\n    //\n    //   If the protocol is mono-tone, each element of the vector corresponds to a single tone.\n    //   Otherwise, the tones within a single Tx are separated by value of -1\n    //\n    using Tones = ggvector<Tone>;\n\n    using Amplitude    = ggvector<float>;\n    using AmplitudeArr = ggmatrix<float>;\n    using AmplitudeI16 = ggvector<int16_t>;\n    using Spectrum     = ggvector<float>;\n    using RecordedData = ggvector<float>;\n    using TxRxData     = ggvector<uint8_t>;\n\n    // Default constructor\n    //\n    //   The GGWave object is not ready to use until you call prepare()\n    //   No memory is allocated with this constructor.\n    //\n    GGWave() = default;\n\n    // Constructor with parameters\n    //\n    //  Construct and prepare the GGWave object using the given parameters.\n    //  This constructor calls prepare() for you.\n    //\n    GGWave(const Parameters & parameters);\n\n    ~GGWave();\n\n    // Prepare the GGWave object\n    //\n    //   All memory buffers used by the GGWave instance are allocated with this function.\n    //   No memory allocations occur after that.\n    //\n    //   Call this method if you used the default constructor.\n    //   Do not call this method if you used the constructor with parameters.\n    //\n    //   The encode() and decode() methods will not work until this method is called.\n    //\n    //   The sizes of the buffers are determined by the parameters and the contents of:\n    //\n    //     - GGWave::Protocols::rx()\n    //     - GGWave::Protocols::tx()\n    //\n    //   For optimal performance and minimum memory usage, make sure to enable only the\n    //   Rx and Tx protocols that you need.\n    //\n    //   For example, using a single protocol for Tx is achieved like this:\n    //\n    //     Parameters parameters;\n    //     parameters.operatingMode = GGWAVE_OPERATING_MODE_TX;\n    //     GGWave::Protocols::tx().only(GGWave::ProtocolId::GGWAVE_PROTOCOL_AUDIBLE_NORMAL);\n    //     GGWave instance(parameters);\n    //     instance.init(...);\n    //     instance.encode();\n    //\n    //   The created instance will only be able to transmit data using the \"Normal\"\n    //   protocol. Rx will be disabled.\n    //\n    //   To create a corresponding Rx-only instance, use the following:\n    //\n    //     Parameters parameters;\n    //     parameters.operatingMode = GGWAVE_OPERATING_MODE_RX;\n    //     GGWave::Protocols::rx().only(GGWave::ProtocolId::GGWAVE_PROTOCOL_AUDIBLE_NORMAL);\n    //     GGWave instance(parameters);\n    //     instance.decode(...);\n    //\n    //   If \"allocate\" is false, the memory buffers are not allocated and only the required size\n    //   is computed. This is useful if you want to just see how much memory is needed for the\n    //   specific set of parameters and protocols. Do not use this function after you have already\n    //   prepared the instance. Instead, use the heapSize() method to see how much memory is used.\n    //\n    bool prepare(const Parameters & parameters, bool allocate = true);\n\n    // Set file stream for the internal ggwave logging\n    //\n    //   By default, ggwave prints internal log messages to stderr.\n    //   To disable logging all together, call this method with nullptr.\n    //\n    //   Note: not thread-safe. Do not call while any GGWave instances are running\n    //\n    static void setLogFile(FILE * fptr);\n\n    static const Parameters & getDefaultParameters();\n\n    // Set Tx data to encode into sound\n    //\n    //   This prepares the GGWave instance for transmission.\n    //   To perform the actual encoding, call the encode() method.\n    //\n    //   Returns false upon invalid parameters or failure to initialize the transmission\n    //\n    bool init(const char * text, TxProtocolId protocolId, const int volume = kDefaultVolume);\n    bool init(int dataSize, const char * dataBuffer, TxProtocolId protocolId, const int volume = kDefaultVolume);\n\n    // Expected waveform size of the encoded Tx data in bytes\n    //\n    //   When the output sampling rate is not equal to operating sample rate the result of this method is overestimation\n    //   of the actual number of bytes that would be produced\n    //\n    uint32_t encodeSize_bytes() const;\n\n    // Expected waveform size of the encoded Tx data in samples\n    //\n    //   When the output sampling rate is not equal to operating sample rate the result of this method is overestimation\n    //   of the actual number of samples that would be produced\n    //\n    uint32_t encodeSize_samples() const;\n\n    // Encode Tx data into an audio waveform\n    //\n    //   After calling this method, use the Tx methods to get the encoded audio data.\n    //\n    //   The generated waveform is available through the txWaveform() method\n    //   The tone frequencies are available through the txTones() method\n    //\n    //   Returns the number of bytes in the generated waveform\n    //\n    uint32_t encode();\n\n    // Decode an audio waveform\n    //\n    //   data   - pointer to the waveform data\n    //   nBytes - number of bytes in the waveform\n    //\n    //   The samples pointed to by \"data\" should be in the format given by sampleFormatInp().\n    //   After calling this method, use the Rx methods to check if any data was decoded successfully.\n    //\n    //   Returns false if the provided waveform is somehow invalid\n    //\n    bool decode(const void * data, uint32_t nBytes);\n\n    //\n    // Instance state\n    //\n\n    bool isDSSEnabled() const;\n\n    int samplesPerFrame() const;\n    int sampleSizeInp()   const;\n    int sampleSizeOut()   const;\n\n    float hzPerSample()   const;\n    float sampleRateInp() const;\n    float sampleRateOut() const;\n    SampleFormat sampleFormatInp() const;\n    SampleFormat sampleFormatOut() const;\n\n    int heapSize() const;\n\n    //\n    // Tx\n    //\n\n    // Get the generated Wavform samples for the last encode() call\n    //\n    //   Call this method after calling encode() to get the generated waveform. The format of the samples pointed to by\n    //   the returned pointer is determined by the sampleFormatOut() method.\n    //\n    const void * txWaveform() const;\n\n    // Get a list of the tones generated for the last encode() call\n    //\n    //   Call this method after calling encode() to get a list of the tones participating in the generated waveform\n    //\n    const Tones txTones() const;\n\n    // true if there is data pending to be transmitted\n    bool txHasData() const;\n\n    // Consume the amplitude data from the last generated waveform\n    bool txTakeAmplitudeI16(AmplitudeI16 & dst);\n\n    // The instance will allow Tx only with these protocols. They are determined upon construction or when calling the\n    // prepare() method, base on the contents of the global GGWave::Protocols::tx()\n    const TxProtocols & txProtocols() const;\n\n    //\n    // Rx\n    //\n\n    bool rxReceiving() const;\n    bool rxAnalyzing() const;\n\n    int rxSamplesNeeded()       const;\n    int rxFramesToRecord()      const;\n    int rxFramesLeftToRecord()  const;\n    int rxFramesToAnalyze()     const;\n    int rxFramesLeftToAnalyze() const;\n    int rxDurationFrames()      const;\n\n    bool rxStopReceiving();\n\n    // The instance will attempt to decode only these protocols.\n    // They are determined upon construction or when calling the prepare() method, base on the contents of the global\n    // GGWave::Protocols::rx()\n    //\n    // Note: do not enable protocols that were not enabled upon preparation of the GGWave instance, or the decoding\n    // will likely crash\n    //\n    RxProtocols & rxProtocols();\n\n    // Information about last received data\n    int                  rxDataLength() const;\n    const TxRxData &     rxData()       const;\n    const RxProtocol &   rxProtocol()   const;\n    const RxProtocolId & rxProtocolId() const;\n    const Spectrum &     rxSpectrum()   const;\n    const Amplitude &    rxAmplitude()  const;\n\n    // Consume the received data\n    //\n    //   Returns the data length in bytes\n    //\n    int rxTakeData(TxRxData & dst);\n\n    // Consume the received spectrum / amplitude data\n    //\n    //   Returns true if there was new data available\n    //\n    bool rxTakeSpectrum(Spectrum & dst);\n    bool rxTakeAmplitude(Amplitude & dst);\n\n    //\n    // Utils\n    //\n\n    // Compute FFT of real values\n    //\n    //   src - input real-valued data, size is N\n    //   dst - output complex-valued data, size is 2*N\n    //\n    //   N must be == samplesPerFrame()\n    //\n    bool computeFFTR(const float * src, float * dst, int N);\n\n    // Compute FFT of real values (static)\n    //\n    //   src - input real-valued data, size is N\n    //   dst - output complex-valued data, size is 2*N\n    //   wi  - work buffer, with size 2*N\n    //   wf  - work buffer, with size 3 + sqrt(N/2)\n    //\n    //   First time calling this function, make sure that wi[0] == 0\n    //   This will initialize some internal coefficients and store them in wi and wf for\n    //   future usage.\n    //\n    //   If wi == nullptr                   - returns the needed size for wi\n    //   If wi != nullptr and wf == nullptr - returns the needed size for wf\n    //   If wi != nullptr and wf != nullptr - returns 1 on success, 0 on failure\n    //\n    static int computeFFTR(const float * src, float * dst, int N, int * wi, float * wf);\n\n    // Filter the waveform\n    //\n    //   filter   - filter to use\n    //   waveform - input waveform, size is N\n    //   N        - number of samples in the waveform\n    //   p0       - parameter\n    //   p1       - parameter\n    //   w        - work buffer\n    //\n    //   Filter is applied in-place.\n    //   First time calling this function, make sure that w[0] == 0 and w[1] == 0\n    //   This will initialize some internal coefficients and store them in w for\n    //   future usage.\n    //\n    //   For GGWAVE_FILTER_FIRST_ORDER_HIGH_PASS:\n    //     - p0 = cutoff frequency in Hz\n    //     - p1 = sample rate in Hz\n    //\n    //   If w == nullptr - returns the needed size for w for the specified filter\n    //   If w != nullptr - returns 1 on success, 0 on failure\n    //\n    static int filter(ggwave_Filter filter, float * waveform, int N, float p0, float p1, float * w);\n\n    // Resample audio waveforms from one sample rate to another using sinc interpolation\n    class Resampler {\n    public:\n        // this controls the number of neighboring samples\n        // which are used to interpolate the new samples. The\n        // processing time is linearly related to this width\n        static const int kWidth = 64;\n\n        Resampler();\n\n        bool alloc(void * p, int & n);\n\n        void reset();\n\n        int nSamplesTotal() const { return m_state.nSamplesTotal; }\n\n        int resample(\n                float factor,\n                int nSamples,\n                const float * samplesInp,\n                float * samplesOut);\n\n    private:\n        float getData(int j) const;\n        void newData(float data);\n        void makeSinc();\n        double sinc(double x) const;\n\n        static const int kDelaySize = 140;\n\n        // this defines how finely the sinc function is sampled for storage in the table\n        static const int kSamplesPerZeroCrossing = 32;\n\n        ggvector<float> m_sincTable;\n        ggvector<float> m_delayBuffer;\n        ggvector<float> m_edgeSamples;\n        ggvector<float> m_samplesInp;\n\n        struct State {\n            int nSamplesTotal = 0;\n            int timeInt       = 0;\n            int timeLast      = 0;\n            double timeNow    = 0.0;\n        };\n\n        State m_state;\n    };\n\nprivate:\n    bool alloc(void * p, int & n);\n\n    void decode_fixed();\n    void decode_variable();\n\n    int maxFramesPerTx(const Protocols & protocols, bool excludeMT) const;\n    int minBytesPerTx(const Protocols & protocols) const;\n    int maxBytesPerTx(const Protocols & protocols) const;\n    int maxTonesPerTx(const Protocols & protocols) const;\n    int minFreqStart(const Protocols & protocols) const;\n\n    double bitFreq(const Protocol & p, int bit) const;\n\n    // Initialized via prepare()\n    float        m_sampleRateInp        = -1.0f;\n    float        m_sampleRateOut        = -1.0f;\n    float        m_sampleRate           = -1.0f;\n    int          m_samplesPerFrame      = -1;\n    float        m_isamplesPerFrame     = -1.0f;\n    int          m_sampleSizeInp        = -1;\n    int          m_sampleSizeOut        = -1;\n    SampleFormat m_sampleFormatInp      = GGWAVE_SAMPLE_FORMAT_UNDEFINED;\n    SampleFormat m_sampleFormatOut      = GGWAVE_SAMPLE_FORMAT_UNDEFINED;\n\n    float        m_hzPerSample          = -1.0f;\n    float        m_ihzPerSample         = -1.0f;\n\n    int          m_freqDelta_bin        = -1;\n    float        m_freqDelta_hz         = -1.0f;\n\n    int          m_nBitsInMarker        = -1;\n    int          m_nMarkerFrames        = -1;\n    int          m_encodedDataOffset    = -1;\n\n    float        m_soundMarkerThreshold = -1.0f;\n\n    bool         m_isFixedPayloadLength = false;\n    int          m_payloadLength        = -1;\n\n    bool         m_isRxEnabled          = false;\n    bool         m_isTxEnabled          = false;\n    bool         m_needResampling       = false;\n    bool         m_txOnlyTones          = false;\n    bool         m_isDSSEnabled         = false;\n\n    // Common\n    TxRxData m_dataEncoded;\n    TxRxData m_workRSLength; // Reed-Solomon work buffers\n    TxRxData m_workRSData;\n\n    // Impl\n\n    struct Rx {\n        bool receiving = false;\n        bool analyzing = false;\n\n        int nMarkersSuccess     = 0;\n        int markerFreqStart     = 0;\n        int recvDuration_frames = 0;\n        int minFreqStart        = 0;\n\n        int framesLeftToAnalyze = 0;\n        int framesLeftToRecord  = 0;\n        int framesToAnalyze     = 0;\n        int framesToRecord      = 0;\n        int samplesNeeded       = 0;\n\n        ggvector<float> fftOut; // complex\n        ggvector<int>   fftWorkI;\n        ggvector<float> fftWorkF;\n\n        bool hasNewRxData    = false;\n        bool hasNewSpectrum  = false;\n        bool hasNewAmplitude = false;\n\n        Spectrum  spectrum;\n        Amplitude amplitude;\n        Amplitude amplitudeResampled;\n        TxRxData  amplitudeTmp;\n\n        int dataLength = 0;\n\n        TxRxData     data;\n        RxProtocol   protocol;\n        RxProtocolId protocolId;\n        RxProtocols  protocols;\n\n        // variable-length decoding\n        int historyId = 0;\n\n        Amplitude    amplitudeAverage;\n        AmplitudeArr amplitudeHistory;\n        RecordedData amplitudeRecorded;\n\n        // fixed-length decoding\n        int historyIdFixed = 0;\n\n        ggmatrix<uint8_t> spectrumHistoryFixed;\n        ggvector<uint8_t> detectedBins;\n        ggvector<uint8_t> detectedTones;\n    } m_rx;\n\n    struct Tx {\n        bool hasData = false;\n\n        float sendVolume = 0.1f;\n\n        int dataLength = 0;\n        int lastAmplitudeSize = 0;\n\n        ggvector<bool> dataBits;\n        ggvector<double> phaseOffsets;\n\n        AmplitudeArr bit1Amplitude;\n        AmplitudeArr bit0Amplitude;\n\n        TxRxData    data;\n        TxProtocol  protocol;\n        TxProtocols protocols;\n\n        Amplitude    output;\n        Amplitude    outputResampled;\n        TxRxData     outputTmp;\n        AmplitudeI16 outputI16;\n\n        int nTones = 0;\n        Tones tones;\n    } m_tx;\n\n    mutable Resampler m_resampler;\n\n    void * m_heap  = nullptr;\n    int m_heapSize = 0;\n};\n\n#endif\n\n#endif\n"
  },
  {
    "path": "media/favicons-waver/browserconfig.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<browserconfig><msapplication><tile><square70x70logo src=\"/ms-icon-70x70.png\"/><square150x150logo src=\"/ms-icon-150x150.png\"/><square310x310logo src=\"/ms-icon-310x310.png\"/><TileColor>#ffffff</TileColor></tile></msapplication></browserconfig>"
  },
  {
    "path": "media/favicons-waver/manifest.json",
    "content": "{\n \"name\": \"App\",\n \"icons\": [\n  {\n   \"src\": \"\\/android-icon-36x36.png\",\n   \"sizes\": \"36x36\",\n   \"type\": \"image\\/png\",\n   \"density\": \"0.75\"\n  },\n  {\n   \"src\": \"\\/android-icon-48x48.png\",\n   \"sizes\": \"48x48\",\n   \"type\": \"image\\/png\",\n   \"density\": \"1.0\"\n  },\n  {\n   \"src\": \"\\/android-icon-72x72.png\",\n   \"sizes\": \"72x72\",\n   \"type\": \"image\\/png\",\n   \"density\": \"1.5\"\n  },\n  {\n   \"src\": \"\\/android-icon-96x96.png\",\n   \"sizes\": \"96x96\",\n   \"type\": \"image\\/png\",\n   \"density\": \"2.0\"\n  },\n  {\n   \"src\": \"\\/android-icon-144x144.png\",\n   \"sizes\": \"144x144\",\n   \"type\": \"image\\/png\",\n   \"density\": \"3.0\"\n  },\n  {\n   \"src\": \"\\/android-icon-192x192.png\",\n   \"sizes\": \"192x192\",\n   \"type\": \"image\\/png\",\n   \"density\": \"4.0\"\n  }\n ]\n}"
  },
  {
    "path": "snap/snapcraft.yaml",
    "content": "\n  name: waver\n  version: '1.5.2'\n  summary: Data over sound\n  description: |\n    Waver allows you to send and receive text messages from nearby devices through sound waves.\n\n    This application can be useful for communicating with multiple nearby devices at once.\n    Both audible and ultrasound communication protocols are available.\n    The app does not connect to the internet and all information is transmitted only through sound.\n    In order to receive incoming messages you only need to allow access to your device's microphone so that it can record nearby sounds.\n\n    How to use:\n    - 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\n    - To send a message - tap on \"Messages\", enter some text at the bottom of the screen and click \"Send\"\n    - Any nearby device that is also running this application can capture the emitted sound and display the received message\n    - 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\n    - Tap on \"Spectrum\" to see a real-time frequency spectrum of the currently captured audio by your device's microphone\n\n    File sharing in a local network:\n\n    As of v1.3.0 Waver supports file sharing. It works like this:\n    - Add files that you would like to transmit by drag and dropping them in Waver\n    - In the \"Files\" menu, click on \"Broadcast\". This plays an audio message that contains a file broadcast offer\n    - Nearby devices in the same local network can receive this offer and initiate a TCP/IP connection to your device\n    - The files are transmitted over TCP/IP. The sound message is used only to initiate the network connections between the devices\n    - Waver allows sharing multiple files to multiple devices at once\n\n  base: core18\n  grade: stable\n  confinement: strict\n\n  parts:\n    alsa-mixin:\n      plugin: dump\n      source: https://github.com/diddlesnaps/snapcraft-alsa.git\n      source-subdir: snapcraft-assets\n      build-packages:\n        - libasound2-dev\n      stage-packages:\n        - libasound2\n        - libasound2-plugins\n        - yad\n\n    waver:\n      source: https://github.com/ggerganov/ggwave\n      source-type: git\n      plugin: cmake\n      #configflags: [-DBUILD_SHARED_LIBS=OFF]\n      build-packages:\n        - g++\n        - make\n        - libsdl2-dev\n      stage-packages:\n        - libopengl0\n        - libsdl2-2.0-0\n        - libgl1\n        - libglx0\n        - libglu1-mesa\n        - libgl1-mesa-dri\n      after: [alsa-mixin]\n\n    waver-fonts:\n      plugin: dump\n      source: ./examples/assets/fonts/\n      organize:\n        '*.ttf' : examples/assets/fonts/\n      stage:\n        - examples/assets/fonts/\n\n  apps:\n    waver:\n      command-chain: [\"snap/command-chain/alsa-launch\"]\n      command: bin/waver\n      plugs: [unity7, opengl, alsa, audio-playback, audio-record, network, network-bind, home]\n\n  environment:\n    ALWAYS_USE_PULSEAUDIO: '1'\n"
  },
  {
    "path": "src/CMakeLists.txt",
    "content": "# core\n\nset(TARGET ggwave)\n\nadd_library(${TARGET}\n    ggwave.cpp\n    )\n\ntarget_include_directories(${TARGET} PUBLIC\n    .\n    ../include\n    )\n\nif (BUILD_SHARED_LIBS)\n    target_link_libraries(${TARGET} PUBLIC\n        ${CMAKE_DL_LIBS}\n        )\n\n    target_compile_definitions(${TARGET} PUBLIC\n        GGWAVE_SHARED\n        )\nendif()\n\nif (MINGW)\n    target_link_libraries(${TARGET} PUBLIC\n        stdc++\n        )\nendif()\n\ninstall(DIRECTORY ../include/ggwave\n    DESTINATION include\n    )\n\ninstall(TARGETS ${TARGET}\n    LIBRARY DESTINATION lib\n    ARCHIVE DESTINATION lib/static\n    )\n"
  },
  {
    "path": "src/fft.h",
    "content": "#pragma once\n\n/*\n\nThe FFT routines below are taken from:\n\nhttps://www.kurims.kyoto-u.ac.jp/~ooura/fft.html\n\nLicense\nCopyright Takuya OOURA, 1996-2001\n\n*/\n\n\n/*\nFast Fourier/Cosine/Sine Transform\n    dimension   :one\n    data length :power of 2\n    decimation  :frequency\n    radix       :4, 2\n    data        :inplace\n    table       :use\nfunctions\n    rdft: Real Discrete Fourier Transform\nfunction prototypes\n    void rdft(int, int, float *, int *, float *);\n\n\n-------- Real DFT / Inverse of Real DFT --------\n    [definition]\n        <case1> RDFT\n            R[k] = sum_j=0^n-1 a[j]*cos(2*pi*j*k/n), 0<=k<=n/2\n            I[k] = sum_j=0^n-1 a[j]*sin(2*pi*j*k/n), 0<k<n/2\n        <case2> IRDFT (excluding scale)\n            a[k] = (R[0] + R[n/2]*cos(pi*k))/2 +\n                   sum_j=1^n/2-1 R[j]*cos(2*pi*j*k/n) +\n                   sum_j=1^n/2-1 I[j]*sin(2*pi*j*k/n), 0<=k<n\n    [usage]\n        <case1>\n            ip[0] = 0; // first time only\n            rdft(n, 1, a, ip, w);\n        <case2>\n            ip[0] = 0; // first time only\n            rdft(n, -1, a, ip, w);\n    [parameters]\n        n              :data length (int)\n                        n >= 2, n = power of 2\n        a[0...n-1]     :input/output data (float *)\n                        <case1>\n                            output data\n                                a[2*k] = R[k], 0<=k<n/2\n                                a[2*k+1] = I[k], 0<k<n/2\n                                a[1] = R[n/2]\n                        <case2>\n                            input data\n                                a[2*j] = R[j], 0<=j<n/2\n                                a[2*j+1] = I[j], 0<j<n/2\n                                a[1] = R[n/2]\n        ip[0...*]      :work area for bit reversal (int *)\n                        length of ip >= 2+sqrt(n/2)\n                        strictly,\n                        length of ip >=\n                            2+(1<<(int)(log(n/2+0.5)/log(2))/2).\n                        ip[0],ip[1] are pointers of the cos/sin table.\n        w[0...n/2-1]   :cos/sin table (float *)\n                        w[],ip[] are initialized if ip[0] == 0.\n    [remark]\n        Inverse of\n            rdft(n, 1, a, ip, w);\n        is\n            rdft(n, -1, a, ip, w);\n            for (j = 0; j <= n - 1; j++) {\n                a[j] *= 2.0 / n;\n            }\n        .\n\n\nAppendix :\n    The cos/sin table is recalculated when the larger table required.\n    w[] and ip[] are compatible with all routines.\n*/\n\nvoid rdft(int n, int isgn, float *a, int *ip, float *w)\n{\n    void makewt(int nw, int *ip, float *w);\n    void makect(int nc, int *ip, float *c);\n    void bitrv2(int n, int *ip, float *a);\n    void cftfsub(int n, float *a, float *w);\n    void cftbsub(int n, float *a, float *w);\n    void rftfsub(int n, float *a, int nc, float *c);\n    void rftbsub(int n, float *a, int nc, float *c);\n    int nw, nc;\n    float xi;\n\n    nw = ip[0];\n    if (n > (nw << 2)) {\n        nw = n >> 2;\n        makewt(nw, ip, w);\n    }\n    nc = ip[1];\n    if (n > (nc << 2)) {\n        nc = n >> 2;\n        makect(nc, ip, w + nw);\n    }\n    if (isgn >= 0) {\n        if (n > 4) {\n            bitrv2(n, ip + 2, a);\n            cftfsub(n, a, w);\n            rftfsub(n, a, nc, w + nw);\n        } else if (n == 4) {\n            cftfsub(n, a, w);\n        }\n        xi = a[0] - a[1];\n        a[0] += a[1];\n        a[1] = xi;\n    } else {\n        a[1] = 0.5 * (a[0] - a[1]);\n        a[0] -= a[1];\n        if (n > 4) {\n            rftbsub(n, a, nc, w + nw);\n            bitrv2(n, ip + 2, a);\n            cftbsub(n, a, w);\n        } else if (n == 4) {\n            cftfsub(n, a, w);\n        }\n    }\n}\n\n/* -------- initializing routines -------- */\n\n#include <math.h>\n\nvoid makewt(int nw, int *ip, float *w)\n{\n    void bitrv2(int n, int *ip, float *a);\n    int j, nwh;\n    float delta, x, y;\n\n    ip[0] = nw;\n    ip[1] = 1;\n    if (nw > 2) {\n        nwh = nw >> 1;\n        delta = atan(1.0) / nwh;\n        w[0] = 1;\n        w[1] = 0;\n        w[nwh] = cos(delta * nwh);\n        w[nwh + 1] = w[nwh];\n        if (nwh > 2) {\n            for (j = 2; j < nwh; j += 2) {\n                x = cos(delta * j);\n                y = sin(delta * j);\n                w[j] = x;\n                w[j + 1] = y;\n                w[nw - j] = y;\n                w[nw - j + 1] = x;\n            }\n            bitrv2(nw, ip + 2, w);\n        }\n    }\n}\n\n\nvoid makect(int nc, int *ip, float *c)\n{\n    int j, nch;\n    float delta;\n\n    ip[1] = nc;\n    if (nc > 1) {\n        nch = nc >> 1;\n        delta = atan(1.0) / nch;\n        c[0] = cos(delta * nch);\n        c[nch] = 0.5 * c[0];\n        for (j = 1; j < nch; j++) {\n            c[j] = 0.5 * cos(delta * j);\n            c[nc - j] = 0.5 * sin(delta * j);\n        }\n    }\n}\n\n\n/* -------- child routines -------- */\n\n\nvoid bitrv2(int n, int *ip, float *a)\n{\n    int j, j1, k, k1, l, m, m2;\n    float xr, xi, yr, yi;\n\n    ip[0] = 0;\n    l = n;\n    m = 1;\n    while ((m << 3) < l) {\n        l >>= 1;\n        for (j = 0; j < m; j++) {\n            ip[m + j] = ip[j] + l;\n        }\n        m <<= 1;\n    }\n    m2 = 2 * m;\n    if ((m << 3) == l) {\n        for (k = 0; k < m; k++) {\n            for (j = 0; j < k; j++) {\n                j1 = 2 * j + ip[k];\n                k1 = 2 * k + ip[j];\n                xr = a[j1];\n                xi = a[j1 + 1];\n                yr = a[k1];\n                yi = a[k1 + 1];\n                a[j1] = yr;\n                a[j1 + 1] = yi;\n                a[k1] = xr;\n                a[k1 + 1] = xi;\n                j1 += m2;\n                k1 += 2 * m2;\n                xr = a[j1];\n                xi = a[j1 + 1];\n                yr = a[k1];\n                yi = a[k1 + 1];\n                a[j1] = yr;\n                a[j1 + 1] = yi;\n                a[k1] = xr;\n                a[k1 + 1] = xi;\n                j1 += m2;\n                k1 -= m2;\n                xr = a[j1];\n                xi = a[j1 + 1];\n                yr = a[k1];\n                yi = a[k1 + 1];\n                a[j1] = yr;\n                a[j1 + 1] = yi;\n                a[k1] = xr;\n                a[k1 + 1] = xi;\n                j1 += m2;\n                k1 += 2 * m2;\n                xr = a[j1];\n                xi = a[j1 + 1];\n                yr = a[k1];\n                yi = a[k1 + 1];\n                a[j1] = yr;\n                a[j1 + 1] = yi;\n                a[k1] = xr;\n                a[k1 + 1] = xi;\n            }\n            j1 = 2 * k + m2 + ip[k];\n            k1 = j1 + m2;\n            xr = a[j1];\n            xi = a[j1 + 1];\n            yr = a[k1];\n            yi = a[k1 + 1];\n            a[j1] = yr;\n            a[j1 + 1] = yi;\n            a[k1] = xr;\n            a[k1 + 1] = xi;\n        }\n    } else {\n        for (k = 1; k < m; k++) {\n            for (j = 0; j < k; j++) {\n                j1 = 2 * j + ip[k];\n                k1 = 2 * k + ip[j];\n                xr = a[j1];\n                xi = a[j1 + 1];\n                yr = a[k1];\n                yi = a[k1 + 1];\n                a[j1] = yr;\n                a[j1 + 1] = yi;\n                a[k1] = xr;\n                a[k1 + 1] = xi;\n                j1 += m2;\n                k1 += m2;\n                xr = a[j1];\n                xi = a[j1 + 1];\n                yr = a[k1];\n                yi = a[k1 + 1];\n                a[j1] = yr;\n                a[j1 + 1] = yi;\n                a[k1] = xr;\n                a[k1 + 1] = xi;\n            }\n        }\n    }\n}\n\n\nvoid bitrv2conj(int n, int *ip, float *a)\n{\n    int j, j1, k, k1, l, m, m2;\n    float xr, xi, yr, yi;\n\n    ip[0] = 0;\n    l = n;\n    m = 1;\n    while ((m << 3) < l) {\n        l >>= 1;\n        for (j = 0; j < m; j++) {\n            ip[m + j] = ip[j] + l;\n        }\n        m <<= 1;\n    }\n    m2 = 2 * m;\n    if ((m << 3) == l) {\n        for (k = 0; k < m; k++) {\n            for (j = 0; j < k; j++) {\n                j1 = 2 * j + ip[k];\n                k1 = 2 * k + ip[j];\n                xr = a[j1];\n                xi = -a[j1 + 1];\n                yr = a[k1];\n                yi = -a[k1 + 1];\n                a[j1] = yr;\n                a[j1 + 1] = yi;\n                a[k1] = xr;\n                a[k1 + 1] = xi;\n                j1 += m2;\n                k1 += 2 * m2;\n                xr = a[j1];\n                xi = -a[j1 + 1];\n                yr = a[k1];\n                yi = -a[k1 + 1];\n                a[j1] = yr;\n                a[j1 + 1] = yi;\n                a[k1] = xr;\n                a[k1 + 1] = xi;\n                j1 += m2;\n                k1 -= m2;\n                xr = a[j1];\n                xi = -a[j1 + 1];\n                yr = a[k1];\n                yi = -a[k1 + 1];\n                a[j1] = yr;\n                a[j1 + 1] = yi;\n                a[k1] = xr;\n                a[k1 + 1] = xi;\n                j1 += m2;\n                k1 += 2 * m2;\n                xr = a[j1];\n                xi = -a[j1 + 1];\n                yr = a[k1];\n                yi = -a[k1 + 1];\n                a[j1] = yr;\n                a[j1 + 1] = yi;\n                a[k1] = xr;\n                a[k1 + 1] = xi;\n            }\n            k1 = 2 * k + ip[k];\n            a[k1 + 1] = -a[k1 + 1];\n            j1 = k1 + m2;\n            k1 = j1 + m2;\n            xr = a[j1];\n            xi = -a[j1 + 1];\n            yr = a[k1];\n            yi = -a[k1 + 1];\n            a[j1] = yr;\n            a[j1 + 1] = yi;\n            a[k1] = xr;\n            a[k1 + 1] = xi;\n            k1 += m2;\n            a[k1 + 1] = -a[k1 + 1];\n        }\n    } else {\n        a[1] = -a[1];\n        a[m2 + 1] = -a[m2 + 1];\n        for (k = 1; k < m; k++) {\n            for (j = 0; j < k; j++) {\n                j1 = 2 * j + ip[k];\n                k1 = 2 * k + ip[j];\n                xr = a[j1];\n                xi = -a[j1 + 1];\n                yr = a[k1];\n                yi = -a[k1 + 1];\n                a[j1] = yr;\n                a[j1 + 1] = yi;\n                a[k1] = xr;\n                a[k1 + 1] = xi;\n                j1 += m2;\n                k1 += m2;\n                xr = a[j1];\n                xi = -a[j1 + 1];\n                yr = a[k1];\n                yi = -a[k1 + 1];\n                a[j1] = yr;\n                a[j1 + 1] = yi;\n                a[k1] = xr;\n                a[k1 + 1] = xi;\n            }\n            k1 = 2 * k + ip[k];\n            a[k1 + 1] = -a[k1 + 1];\n            a[k1 + m2 + 1] = -a[k1 + m2 + 1];\n        }\n    }\n}\n\n\nvoid cftfsub(int n, float *a, float *w)\n{\n    void cft1st(int n, float *a, float *w);\n    void cftmdl(int n, int l, float *a, float *w);\n    int j, j1, j2, j3, l;\n    float x0r, x0i, x1r, x1i, x2r, x2i, x3r, x3i;\n\n    l = 2;\n    if (n > 8) {\n        cft1st(n, a, w);\n        l = 8;\n        while ((l << 2) < n) {\n            cftmdl(n, l, a, w);\n            l <<= 2;\n        }\n    }\n    if ((l << 2) == n) {\n        for (j = 0; j < l; j += 2) {\n            j1 = j + l;\n            j2 = j1 + l;\n            j3 = j2 + l;\n            x0r = a[j] + a[j1];\n            x0i = a[j + 1] + a[j1 + 1];\n            x1r = a[j] - a[j1];\n            x1i = a[j + 1] - a[j1 + 1];\n            x2r = a[j2] + a[j3];\n            x2i = a[j2 + 1] + a[j3 + 1];\n            x3r = a[j2] - a[j3];\n            x3i = a[j2 + 1] - a[j3 + 1];\n            a[j] = x0r + x2r;\n            a[j + 1] = x0i + x2i;\n            a[j2] = x0r - x2r;\n            a[j2 + 1] = x0i - x2i;\n            a[j1] = x1r - x3i;\n            a[j1 + 1] = x1i + x3r;\n            a[j3] = x1r + x3i;\n            a[j3 + 1] = x1i - x3r;\n        }\n    } else {\n        for (j = 0; j < l; j += 2) {\n            j1 = j + l;\n            x0r = a[j] - a[j1];\n            x0i = a[j + 1] - a[j1 + 1];\n            a[j] += a[j1];\n            a[j + 1] += a[j1 + 1];\n            a[j1] = x0r;\n            a[j1 + 1] = x0i;\n        }\n    }\n}\n\n\nvoid cftbsub(int n, float *a, float *w)\n{\n    void cft1st(int n, float *a, float *w);\n    void cftmdl(int n, int l, float *a, float *w);\n    int j, j1, j2, j3, l;\n    float x0r, x0i, x1r, x1i, x2r, x2i, x3r, x3i;\n\n    l = 2;\n    if (n > 8) {\n        cft1st(n, a, w);\n        l = 8;\n        while ((l << 2) < n) {\n            cftmdl(n, l, a, w);\n            l <<= 2;\n        }\n    }\n    if ((l << 2) == n) {\n        for (j = 0; j < l; j += 2) {\n            j1 = j + l;\n            j2 = j1 + l;\n            j3 = j2 + l;\n            x0r = a[j] + a[j1];\n            x0i = -a[j + 1] - a[j1 + 1];\n            x1r = a[j] - a[j1];\n            x1i = -a[j + 1] + a[j1 + 1];\n            x2r = a[j2] + a[j3];\n            x2i = a[j2 + 1] + a[j3 + 1];\n            x3r = a[j2] - a[j3];\n            x3i = a[j2 + 1] - a[j3 + 1];\n            a[j] = x0r + x2r;\n            a[j + 1] = x0i - x2i;\n            a[j2] = x0r - x2r;\n            a[j2 + 1] = x0i + x2i;\n            a[j1] = x1r - x3i;\n            a[j1 + 1] = x1i - x3r;\n            a[j3] = x1r + x3i;\n            a[j3 + 1] = x1i + x3r;\n        }\n    } else {\n        for (j = 0; j < l; j += 2) {\n            j1 = j + l;\n            x0r = a[j] - a[j1];\n            x0i = -a[j + 1] + a[j1 + 1];\n            a[j] += a[j1];\n            a[j + 1] = -a[j + 1] - a[j1 + 1];\n            a[j1] = x0r;\n            a[j1 + 1] = x0i;\n        }\n    }\n}\n\n\nvoid cft1st(int n, float *a, float *w)\n{\n    int j, k1, k2;\n    float wk1r, wk1i, wk2r, wk2i, wk3r, wk3i;\n    float x0r, x0i, x1r, x1i, x2r, x2i, x3r, x3i;\n\n    x0r = a[0] + a[2];\n    x0i = a[1] + a[3];\n    x1r = a[0] - a[2];\n    x1i = a[1] - a[3];\n    x2r = a[4] + a[6];\n    x2i = a[5] + a[7];\n    x3r = a[4] - a[6];\n    x3i = a[5] - a[7];\n    a[0] = x0r + x2r;\n    a[1] = x0i + x2i;\n    a[4] = x0r - x2r;\n    a[5] = x0i - x2i;\n    a[2] = x1r - x3i;\n    a[3] = x1i + x3r;\n    a[6] = x1r + x3i;\n    a[7] = x1i - x3r;\n    wk1r = w[2];\n    x0r = a[8] + a[10];\n    x0i = a[9] + a[11];\n    x1r = a[8] - a[10];\n    x1i = a[9] - a[11];\n    x2r = a[12] + a[14];\n    x2i = a[13] + a[15];\n    x3r = a[12] - a[14];\n    x3i = a[13] - a[15];\n    a[8] = x0r + x2r;\n    a[9] = x0i + x2i;\n    a[12] = x2i - x0i;\n    a[13] = x0r - x2r;\n    x0r = x1r - x3i;\n    x0i = x1i + x3r;\n    a[10] = wk1r * (x0r - x0i);\n    a[11] = wk1r * (x0r + x0i);\n    x0r = x3i + x1r;\n    x0i = x3r - x1i;\n    a[14] = wk1r * (x0i - x0r);\n    a[15] = wk1r * (x0i + x0r);\n    k1 = 0;\n    for (j = 16; j < n; j += 16) {\n        k1 += 2;\n        k2 = 2 * k1;\n        wk2r = w[k1];\n        wk2i = w[k1 + 1];\n        wk1r = w[k2];\n        wk1i = w[k2 + 1];\n        wk3r = wk1r - 2 * wk2i * wk1i;\n        wk3i = 2 * wk2i * wk1r - wk1i;\n        x0r = a[j] + a[j + 2];\n        x0i = a[j + 1] + a[j + 3];\n        x1r = a[j] - a[j + 2];\n        x1i = a[j + 1] - a[j + 3];\n        x2r = a[j + 4] + a[j + 6];\n        x2i = a[j + 5] + a[j + 7];\n        x3r = a[j + 4] - a[j + 6];\n        x3i = a[j + 5] - a[j + 7];\n        a[j] = x0r + x2r;\n        a[j + 1] = x0i + x2i;\n        x0r -= x2r;\n        x0i -= x2i;\n        a[j + 4] = wk2r * x0r - wk2i * x0i;\n        a[j + 5] = wk2r * x0i + wk2i * x0r;\n        x0r = x1r - x3i;\n        x0i = x1i + x3r;\n        a[j + 2] = wk1r * x0r - wk1i * x0i;\n        a[j + 3] = wk1r * x0i + wk1i * x0r;\n        x0r = x1r + x3i;\n        x0i = x1i - x3r;\n        a[j + 6] = wk3r * x0r - wk3i * x0i;\n        a[j + 7] = wk3r * x0i + wk3i * x0r;\n        wk1r = w[k2 + 2];\n        wk1i = w[k2 + 3];\n        wk3r = wk1r - 2 * wk2r * wk1i;\n        wk3i = 2 * wk2r * wk1r - wk1i;\n        x0r = a[j + 8] + a[j + 10];\n        x0i = a[j + 9] + a[j + 11];\n        x1r = a[j + 8] - a[j + 10];\n        x1i = a[j + 9] - a[j + 11];\n        x2r = a[j + 12] + a[j + 14];\n        x2i = a[j + 13] + a[j + 15];\n        x3r = a[j + 12] - a[j + 14];\n        x3i = a[j + 13] - a[j + 15];\n        a[j + 8] = x0r + x2r;\n        a[j + 9] = x0i + x2i;\n        x0r -= x2r;\n        x0i -= x2i;\n        a[j + 12] = -wk2i * x0r - wk2r * x0i;\n        a[j + 13] = -wk2i * x0i + wk2r * x0r;\n        x0r = x1r - x3i;\n        x0i = x1i + x3r;\n        a[j + 10] = wk1r * x0r - wk1i * x0i;\n        a[j + 11] = wk1r * x0i + wk1i * x0r;\n        x0r = x1r + x3i;\n        x0i = x1i - x3r;\n        a[j + 14] = wk3r * x0r - wk3i * x0i;\n        a[j + 15] = wk3r * x0i + wk3i * x0r;\n    }\n}\n\n\nvoid cftmdl(int n, int l, float *a, float *w)\n{\n    int j, j1, j2, j3, k, k1, k2, m, m2;\n    float wk1r, wk1i, wk2r, wk2i, wk3r, wk3i;\n    float x0r, x0i, x1r, x1i, x2r, x2i, x3r, x3i;\n\n    m = l << 2;\n    for (j = 0; j < l; j += 2) {\n        j1 = j + l;\n        j2 = j1 + l;\n        j3 = j2 + l;\n        x0r = a[j] + a[j1];\n        x0i = a[j + 1] + a[j1 + 1];\n        x1r = a[j] - a[j1];\n        x1i = a[j + 1] - a[j1 + 1];\n        x2r = a[j2] + a[j3];\n        x2i = a[j2 + 1] + a[j3 + 1];\n        x3r = a[j2] - a[j3];\n        x3i = a[j2 + 1] - a[j3 + 1];\n        a[j] = x0r + x2r;\n        a[j + 1] = x0i + x2i;\n        a[j2] = x0r - x2r;\n        a[j2 + 1] = x0i - x2i;\n        a[j1] = x1r - x3i;\n        a[j1 + 1] = x1i + x3r;\n        a[j3] = x1r + x3i;\n        a[j3 + 1] = x1i - x3r;\n    }\n    wk1r = w[2];\n    for (j = m; j < l + m; j += 2) {\n        j1 = j + l;\n        j2 = j1 + l;\n        j3 = j2 + l;\n        x0r = a[j] + a[j1];\n        x0i = a[j + 1] + a[j1 + 1];\n        x1r = a[j] - a[j1];\n        x1i = a[j + 1] - a[j1 + 1];\n        x2r = a[j2] + a[j3];\n        x2i = a[j2 + 1] + a[j3 + 1];\n        x3r = a[j2] - a[j3];\n        x3i = a[j2 + 1] - a[j3 + 1];\n        a[j] = x0r + x2r;\n        a[j + 1] = x0i + x2i;\n        a[j2] = x2i - x0i;\n        a[j2 + 1] = x0r - x2r;\n        x0r = x1r - x3i;\n        x0i = x1i + x3r;\n        a[j1] = wk1r * (x0r - x0i);\n        a[j1 + 1] = wk1r * (x0r + x0i);\n        x0r = x3i + x1r;\n        x0i = x3r - x1i;\n        a[j3] = wk1r * (x0i - x0r);\n        a[j3 + 1] = wk1r * (x0i + x0r);\n    }\n    k1 = 0;\n    m2 = 2 * m;\n    for (k = m2; k < n; k += m2) {\n        k1 += 2;\n        k2 = 2 * k1;\n        wk2r = w[k1];\n        wk2i = w[k1 + 1];\n        wk1r = w[k2];\n        wk1i = w[k2 + 1];\n        wk3r = wk1r - 2 * wk2i * wk1i;\n        wk3i = 2 * wk2i * wk1r - wk1i;\n        for (j = k; j < l + k; j += 2) {\n            j1 = j + l;\n            j2 = j1 + l;\n            j3 = j2 + l;\n            x0r = a[j] + a[j1];\n            x0i = a[j + 1] + a[j1 + 1];\n            x1r = a[j] - a[j1];\n            x1i = a[j + 1] - a[j1 + 1];\n            x2r = a[j2] + a[j3];\n            x2i = a[j2 + 1] + a[j3 + 1];\n            x3r = a[j2] - a[j3];\n            x3i = a[j2 + 1] - a[j3 + 1];\n            a[j] = x0r + x2r;\n            a[j + 1] = x0i + x2i;\n            x0r -= x2r;\n            x0i -= x2i;\n            a[j2] = wk2r * x0r - wk2i * x0i;\n            a[j2 + 1] = wk2r * x0i + wk2i * x0r;\n            x0r = x1r - x3i;\n            x0i = x1i + x3r;\n            a[j1] = wk1r * x0r - wk1i * x0i;\n            a[j1 + 1] = wk1r * x0i + wk1i * x0r;\n            x0r = x1r + x3i;\n            x0i = x1i - x3r;\n            a[j3] = wk3r * x0r - wk3i * x0i;\n            a[j3 + 1] = wk3r * x0i + wk3i * x0r;\n        }\n        wk1r = w[k2 + 2];\n        wk1i = w[k2 + 3];\n        wk3r = wk1r - 2 * wk2r * wk1i;\n        wk3i = 2 * wk2r * wk1r - wk1i;\n        for (j = k + m; j < l + (k + m); j += 2) {\n            j1 = j + l;\n            j2 = j1 + l;\n            j3 = j2 + l;\n            x0r = a[j] + a[j1];\n            x0i = a[j + 1] + a[j1 + 1];\n            x1r = a[j] - a[j1];\n            x1i = a[j + 1] - a[j1 + 1];\n            x2r = a[j2] + a[j3];\n            x2i = a[j2 + 1] + a[j3 + 1];\n            x3r = a[j2] - a[j3];\n            x3i = a[j2 + 1] - a[j3 + 1];\n            a[j] = x0r + x2r;\n            a[j + 1] = x0i + x2i;\n            x0r -= x2r;\n            x0i -= x2i;\n            a[j2] = -wk2i * x0r - wk2r * x0i;\n            a[j2 + 1] = -wk2i * x0i + wk2r * x0r;\n            x0r = x1r - x3i;\n            x0i = x1i + x3r;\n            a[j1] = wk1r * x0r - wk1i * x0i;\n            a[j1 + 1] = wk1r * x0i + wk1i * x0r;\n            x0r = x1r + x3i;\n            x0i = x1i - x3r;\n            a[j3] = wk3r * x0r - wk3i * x0i;\n            a[j3 + 1] = wk3r * x0i + wk3i * x0r;\n        }\n    }\n}\n\n\nvoid rftfsub(int n, float *a, int nc, float *c)\n{\n    int j, k, kk, ks, m;\n    float wkr, wki, xr, xi, yr, yi;\n\n    m = n >> 1;\n    ks = 2 * nc / m;\n    kk = 0;\n    for (j = 2; j < m; j += 2) {\n        k = n - j;\n        kk += ks;\n        wkr = 0.5 - c[nc - kk];\n        wki = c[kk];\n        xr = a[j] - a[k];\n        xi = a[j + 1] + a[k + 1];\n        yr = wkr * xr - wki * xi;\n        yi = wkr * xi + wki * xr;\n        a[j] -= yr;\n        a[j + 1] -= yi;\n        a[k] += yr;\n        a[k + 1] -= yi;\n    }\n}\n\n\nvoid rftbsub(int n, float *a, int nc, float *c)\n{\n    int j, k, kk, ks, m;\n    float wkr, wki, xr, xi, yr, yi;\n\n    a[1] = -a[1];\n    m = n >> 1;\n    ks = 2 * nc / m;\n    kk = 0;\n    for (j = 2; j < m; j += 2) {\n        k = n - j;\n        kk += ks;\n        wkr = 0.5 - c[nc - kk];\n        wki = c[kk];\n        xr = a[j] - a[k];\n        xi = a[j + 1] + a[k + 1];\n        yr = wkr * xr + wki * xi;\n        yi = wkr * xi - wki * xr;\n        a[j] -= yr;\n        a[j + 1] = yi - a[j + 1];\n        a[k] += yr;\n        a[k + 1] = yi - a[k + 1];\n    }\n    a[m + 1] = -a[m + 1];\n}\n"
  },
  {
    "path": "src/ggwave.cpp",
    "content": "#include \"ggwave/ggwave.h\"\n\n#if !defined(ARDUINO) && !defined(PROGMEM)\n#define PROGMEM\n#endif\n\n#include \"fft.h\"\n#include \"reed-solomon/rs.hpp\"\n\n#include <math.h>\n#include <stdio.h>\n//#include <random>\n\n#ifndef M_PI\n#define M_PI 3.14159265358979323846\n#endif\n\n#ifdef GGWAVE_DISABLE_LOG\n#define ggprintf(...)\n#else\n#ifdef ARDUINO\n#define ggprintf(...)\n#else\n#define ggprintf(...) \\\n    g_fptr && fprintf(g_fptr, __VA_ARGS__)\n#endif\n#endif\n\n#define GG_MIN(A, B) (((A) < (B)) ? (A) : (B))\n#define GG_MAX(A, B) (((A) >= (B)) ? (A) : (B))\n\n//\n// C interface\n//\n\nnamespace {\n\nFILE * g_fptr = stderr;\nGGWave * g_instances[GGWAVE_MAX_INSTANCES];\n\ndouble linear_interp(double first_number, double second_number, double fraction) {\n    return (first_number + ((second_number - first_number)*fraction));\n}\n\n}\n\nextern \"C\"\nvoid ggwave_setLogFile(void * fptr) {\n    GGWave::setLogFile((FILE *) fptr);\n}\n\nextern \"C\"\nggwave_Parameters ggwave_getDefaultParameters(void) {\n    return GGWave::getDefaultParameters();\n}\n\nextern \"C\"\nggwave_Instance ggwave_init(ggwave_Parameters parameters) {\n    for (ggwave_Instance id = 0; id < GGWAVE_MAX_INSTANCES; ++id) {\n        if (g_instances[id] == nullptr) {\n            g_instances[id] = new GGWave({\n                parameters.payloadLength,\n                parameters.sampleRateInp,\n                parameters.sampleRateOut,\n                parameters.sampleRate,\n                parameters.samplesPerFrame,\n                parameters.soundMarkerThreshold,\n                parameters.sampleFormatInp,\n                parameters.sampleFormatOut,\n                parameters.operatingMode});\n\n            return id;\n        }\n    }\n\n    ggprintf(\"Failed to create GGWave instance - reached maximum number of instances (%d)\\n\", GGWAVE_MAX_INSTANCES);\n\n    return -1;\n}\n\nextern \"C\"\nvoid ggwave_free(ggwave_Instance id) {\n    if (id >= 0 && id < GGWAVE_MAX_INSTANCES && g_instances[id]) {\n        delete (GGWave *) g_instances[id];\n        g_instances[id] = nullptr;\n\n        return;\n    }\n\n    ggprintf(\"Failed to free GGWave instance - invalid GGWave instance id %d\\n\", id);\n}\n\nextern \"C\"\nint ggwave_encode(\n        ggwave_Instance id,\n        const void * payloadBuffer,\n        int payloadSize,\n        ggwave_ProtocolId protocolId,\n        int volume,\n        void * waveformBuffer,\n        int query) {\n    GGWave * ggWave = (GGWave *) g_instances[id];\n\n    if (ggWave == nullptr) {\n        ggprintf(\"Invalid GGWave instance %d\\n\", id);\n        return -1;\n    }\n\n    if (ggWave->init(payloadSize, (const char *) payloadBuffer, protocolId, volume) == false) {\n        ggprintf(\"Failed to initialize Tx transmission for GGWave instance %d\\n\", id);\n        return -1;\n    }\n\n    if (query != 0) {\n        if (query == 1) {\n            return ggWave->encodeSize_bytes();\n        }\n\n        return ggWave->encodeSize_samples();\n    }\n\n    const int nBytes = ggWave->encode();\n    if (nBytes == 0) {\n        ggprintf(\"Failed to encode data - GGWave instance %d\\n\", id);\n        return -1;\n    }\n\n    {\n        auto pSrc = (const char *) ggWave->txWaveform();\n        auto pDst = (      char *) waveformBuffer;\n        memcpy(pDst, pSrc, nBytes);\n    }\n\n    return nBytes;\n}\n\nextern \"C\"\nint ggwave_decode(\n        ggwave_Instance id,\n        const void * waveformBuffer,\n        int waveformSize,\n        void * payloadBuffer) {\n    GGWave * ggWave = (GGWave *) g_instances[id];\n\n    if (ggWave->decode(waveformBuffer, waveformSize) == false) {\n        ggprintf(\"Failed to decode data - GGWave instance %d\\n\", id);\n        return -1;\n    }\n\n    static thread_local GGWave::TxRxData data;\n\n    const auto dataLength = ggWave->rxTakeData(data);\n    if (dataLength == -1) {\n        // failed to decode message\n        return -1;\n    } else if (dataLength > 0) {\n        memcpy(payloadBuffer, data.data(), dataLength);\n    }\n\n    return dataLength;\n}\n\nextern \"C\"\nint ggwave_ndecode(\n        ggwave_Instance id,\n        const void * waveformBuffer,\n        int waveformSize,\n        void * payloadBuffer,\n        int payloadSize) {\n    GGWave * ggWave = (GGWave *) g_instances[id];\n\n    if (ggWave->decode(waveformBuffer, waveformSize) == false) {\n        ggprintf(\"Failed to decode data - GGWave instance %d\\n\", id);\n        return -1;\n    }\n\n    static thread_local GGWave::TxRxData data;\n\n    const auto dataLength = ggWave->rxTakeData(data);\n    if (dataLength == -1) {\n        // failed to decode message\n        return -1;\n    } else if (dataLength > payloadSize) {\n        // the payloadBuffer is not big enough to store the data\n        return -2;\n    } else if (dataLength > 0) {\n        memcpy(payloadBuffer, data.data(), dataLength);\n    }\n\n    return dataLength;\n}\n\nextern \"C\"\nvoid ggwave_rxToggleProtocol(\n        ggwave_ProtocolId protocolId,\n        int state) {\n    GGWave::Protocols::rx().toggle(protocolId, state != 0);\n}\n\nextern \"C\"\nvoid ggwave_txToggleProtocol(\n        ggwave_ProtocolId protocolId,\n        int state) {\n    GGWave::Protocols::tx().toggle(protocolId, state != 0);\n}\n\nextern \"C\"\nvoid ggwave_rxProtocolSetFreqStart(\n        ggwave_ProtocolId protocolId,\n        int freqStart) {\n    GGWave::Protocols::rx()[protocolId].freqStart = freqStart;\n}\n\nextern \"C\"\nvoid ggwave_txProtocolSetFreqStart(\n        ggwave_ProtocolId protocolId,\n        int freqStart) {\n    GGWave::Protocols::tx()[protocolId].freqStart = freqStart;\n}\n\nextern \"C\"\nint ggwave_rxDurationFrames(ggwave_Instance id) {\n    GGWave * ggWave = (GGWave *) g_instances[id];\n    return ggWave->rxDurationFrames();\n}\n\n//\n// C++ implementation\n//\n\nnamespace {\n\n// magic numbers used to XOR the Rx / Tx data\n// this achieves more homogeneous distribution of the sound energy across the spectrum\nconstexpr int kDSSMagicSize = 64;\nconst uint8_t kDSSMagic[kDSSMagicSize] PROGMEM = {\n    0x96, 0x9f, 0xb4, 0xaf, 0x1b, 0x91, 0xde, 0xc5, 0x45, 0x75, 0xe8, 0x2e, 0x0f, 0x32, 0x4a, 0x5f,\n    0xb4, 0x56, 0x95, 0xcb, 0x7f, 0x6a, 0x54, 0x6a, 0x48, 0xf2, 0x0b, 0x7b, 0xcd, 0xfb, 0x93, 0x6d,\n    0x3c, 0x77, 0x5e, 0xc3, 0x33, 0x47, 0xc0, 0xf1, 0x71, 0x32, 0x33, 0x27, 0x35, 0x68, 0x47, 0x1f,\n    0x4e, 0xac, 0x23, 0x42, 0x5f, 0x00, 0x37, 0xa4, 0x50, 0x6d, 0x48, 0x24, 0x91, 0x7c, 0xa1, 0x4e,\n};\n\nuint8_t getDSSMagic(int i) {\n#ifdef ARDUINO\n    return pgm_read_byte(&kDSSMagic[i % kDSSMagicSize]);\n#else\n    return kDSSMagic[i % kDSSMagicSize];\n#endif\n}\n\nvoid FFT(float * f, int N, int * wi, float * wf) {\n    rdft(N, 1, f, wi, wf);\n}\n\nvoid FFT(const float * src, float * dst, int N, int * wi, float * wf) {\n    memcpy(dst, src, N * sizeof(float));\n\n    FFT(dst, N, wi, wf);\n}\n\ninline void addAmplitudeSmooth(\n        const GGWave::Amplitude & src,\n        GGWave::Amplitude & dst,\n        float scalar, int startId, int finalId, int cycleMod, int nPerCycle) {\n    const int nTotal = nPerCycle*finalId;\n    const float frac = 0.15f;\n    const float ds = frac*nTotal;\n    const float ids = 1.0f/ds;\n    const int nBegin = frac*nTotal;\n    const int nEnd = (1.0f - frac)*nTotal;\n\n    for (int i = startId; i < finalId; i++) {\n        const float k = cycleMod*finalId + i;\n        if (k < nBegin) {\n            dst[i] += scalar*src[i]*(k*ids);\n        } else if (k > nEnd) {\n            dst[i] += scalar*src[i]*(((float)(nTotal) - k)*ids);\n        } else {\n            dst[i] += scalar*src[i];\n        }\n    }\n}\n\nint getECCBytesForLength(int len) {\n    return len < 4 ? 2 : GG_MAX(4, 2*(len/5));\n}\n\nint bytesForSampleFormat(GGWave::SampleFormat sampleFormat) {\n    switch (sampleFormat) {\n        case GGWAVE_SAMPLE_FORMAT_UNDEFINED:    return 0;                   break;\n        case GGWAVE_SAMPLE_FORMAT_U8:           return sizeof(uint8_t);     break;\n        case GGWAVE_SAMPLE_FORMAT_I8:           return sizeof(int8_t);      break;\n        case GGWAVE_SAMPLE_FORMAT_U16:          return sizeof(uint16_t);    break;\n        case GGWAVE_SAMPLE_FORMAT_I16:          return sizeof(int16_t);     break;\n        case GGWAVE_SAMPLE_FORMAT_F32:          return sizeof(float);       break;\n    };\n\n    ggprintf(\"Invalid sample format: %d\\n\", (int) sampleFormat);\n\n    return 0;\n}\n\n}\n\n//\n// ggvector\n//\n\ntemplate<typename T>\nvoid ggvector<T>::assign(const ggvector & other) {\n    m_data = other.m_data;\n    m_size = other.m_size;\n}\n\ntemplate<typename T>\nvoid ggvector<T>::copy(const ggvector & other) {\n    if (this == &other) {\n        assert(false);\n    }\n\n    memcpy(m_data, other.m_data, GG_MIN(m_size, other.m_size)*sizeof(T));\n}\n\ntemplate<typename T>\nvoid ggvector<T>::zero() {\n    memset(m_data, 0, m_size*sizeof(T));\n}\n\ntemplate<typename T>\nvoid ggvector<T>::zero(int n) {\n    memset(m_data, 0, n*sizeof(T));\n}\n\ntemplate struct ggvector<int16_t>;\n\n//\n// ggmatrix\n//\n\ntemplate <typename T>\nvoid ggmatrix<T>::zero() {\n    if (m_size0 > 0 && m_size1 > 0) {\n        memset(m_data, 0, m_size0*m_size1*sizeof(T));\n    }\n}\n\n//\n// Protocols\n//\n\nvoid GGWave::Protocols::enableAll() {\n    for (int i = 0; i < GGWAVE_PROTOCOL_COUNT; i++) {\n        auto & p = this->data[i];\n        if (p.name) {\n            p.enabled = true;\n        }\n    }\n}\n\nvoid GGWave::Protocols::disableAll() {\n    for (int i = 0; i < GGWAVE_PROTOCOL_COUNT; i++) {\n        auto & p = this->data[i];\n        p.enabled = false;\n    }\n}\n\nvoid GGWave::Protocols::toggle(ProtocolId id, bool state) {\n    if (state) {\n        // enable protocol\n        data[id].enabled = true;\n    } else {\n        // disable protocol\n        data[id].enabled = false;\n    }\n}\n\nvoid GGWave::Protocols::only(ProtocolId id) {\n    disableAll();\n    data[id].enabled = true;\n}\n\nGGWave::TxProtocols & GGWave::Protocols::tx() {\n    static TxProtocols protocols = kDefault();\n\n    return protocols;\n}\n\nGGWave::RxProtocols & GGWave::Protocols::rx() {\n    static RxProtocols protocols = kDefault();\n\n    return protocols;\n}\n\n// this probably does not matter, but adding it anyway\n#ifdef ARDUINO\nconst int kAlignment = 4;\n#else\nconst int kAlignment = 8;\n#endif\n\n//template <typename T>\n//void ggalloc(std::vector<T> & v, int n, void * buf, int & bufSize) {\n//    if (buf == nullptr) {\n//        bufSize += n*sizeof(T);\n//        bufSize = ((bufSize + kAlignment - 1)/kAlignment)*kAlignment;\n//        return;\n//    }\n//\n//    v.resize(n);\n//    bufSize += n*sizeof(T);\n//    bufSize = ((bufSize + kAlignment - 1)/kAlignment)*kAlignment;\n//}\n//\n//template <typename T>\n//void ggalloc(std::vector<std::vector<T>> & v, int n, int m, void * buf, int & bufSize) {\n//    if (buf == nullptr) {\n//        bufSize += n*m*sizeof(T);\n//        bufSize = ((bufSize + kAlignment - 1)/kAlignment)*kAlignment;\n//        return;\n//    }\n//\n//    v.resize(n);\n//    for (int i = 0; i < n; i++) {\n//        v[i].resize(m);\n//    }\n//    bufSize += n*m*sizeof(T);\n//    bufSize = ((bufSize + kAlignment - 1)/kAlignment)*kAlignment;\n//}\n\ntemplate <typename T>\nvoid ggalloc(ggvector<T> & v, int n, void * buf, int & bufSize) {\n    if (buf == nullptr) {\n        bufSize += n*sizeof(T);\n        bufSize = ((bufSize + kAlignment - 1)/kAlignment)*kAlignment;\n        return;\n    }\n\n    v.assign(ggvector<T>((T *)((char *) buf + bufSize), n));\n    bufSize += n*sizeof(T);\n    bufSize = ((bufSize + kAlignment - 1)/kAlignment)*kAlignment;\n}\n\ntemplate <typename T>\nvoid ggalloc(ggmatrix<T> & v, int n, int m, void * buf, int & bufSize) {\n    if (buf == nullptr) {\n        bufSize += n*m*sizeof(T);\n        bufSize = ((bufSize + kAlignment - 1) / kAlignment)*kAlignment;\n        return;\n    }\n\n    v = ggmatrix<T>((T *)((char *) buf + bufSize), n, m);\n    bufSize += n*m*sizeof(T);\n    bufSize = ((bufSize + kAlignment - 1)/kAlignment)*kAlignment;\n}\n\n//\n// GGWave\n//\n\nGGWave::GGWave(const Parameters & parameters) {\n    prepare(parameters);\n}\n\nGGWave::~GGWave() {\n    if (m_heap) {\n        free(m_heap);\n    }\n}\n\nbool GGWave::prepare(const Parameters & parameters, bool allocate) {\n    if (m_heap) {\n        free(m_heap);\n        m_heap = nullptr;\n        m_heapSize = 0;\n    }\n\n    // parameter initialization:\n\n    m_sampleRateInp        = parameters.sampleRateInp;\n    m_sampleRateOut        = parameters.sampleRateOut;\n    m_sampleRate           = parameters.sampleRate;\n    m_samplesPerFrame      = parameters.samplesPerFrame;\n    m_isamplesPerFrame     = 1.0f/m_samplesPerFrame;\n    m_sampleSizeInp        = bytesForSampleFormat(parameters.sampleFormatInp);\n    m_sampleSizeOut        = bytesForSampleFormat(parameters.sampleFormatOut);\n    m_sampleFormatInp      = parameters.sampleFormatInp;\n    m_sampleFormatOut      = parameters.sampleFormatOut;\n    m_hzPerSample          = m_sampleRate/m_samplesPerFrame;\n    m_ihzPerSample         = 1.0f/m_hzPerSample;\n    m_freqDelta_bin        = 1;\n    m_freqDelta_hz         = 2*m_hzPerSample;\n    m_nBitsInMarker        = 16;\n    m_nMarkerFrames        = parameters.payloadLength > 0 ? 0 : kDefaultMarkerFrames;\n    m_encodedDataOffset    = parameters.payloadLength > 0 ? 0 : kDefaultEncodedDataOffset;\n    m_soundMarkerThreshold = parameters.soundMarkerThreshold;\n    m_isFixedPayloadLength = parameters.payloadLength > 0;\n    m_payloadLength        = parameters.payloadLength;\n    m_isRxEnabled          = parameters.operatingMode & GGWAVE_OPERATING_MODE_RX;\n    m_isTxEnabled          = parameters.operatingMode & GGWAVE_OPERATING_MODE_TX;\n    m_needResampling       = m_sampleRateInp != m_sampleRate || m_sampleRateOut != m_sampleRate;\n    m_txOnlyTones          = parameters.operatingMode & GGWAVE_OPERATING_MODE_TX_ONLY_TONES;\n    m_isDSSEnabled         = parameters.operatingMode & GGWAVE_OPERATING_MODE_USE_DSS;\n\n    if (m_sampleSizeInp == 0) {\n        ggprintf(\"Invalid or unsupported capture sample format: %d\\n\", (int) parameters.sampleFormatInp);\n        return false;\n    }\n\n    if (m_sampleSizeOut == 0) {\n        ggprintf(\"Invalid or unsupported playback sample format: %d\\n\", (int) parameters.sampleFormatOut);\n        return false;\n    }\n\n    if (parameters.samplesPerFrame > kMaxSamplesPerFrame) {\n        ggprintf(\"Invalid samples per frame: %d, max: %d\\n\", parameters.samplesPerFrame, kMaxSamplesPerFrame);\n        return false;\n    }\n\n    if (m_sampleRateInp < kSampleRateMin) {\n        ggprintf(\"Error: capture sample rate (%g Hz) must be >= %g Hz\\n\", m_sampleRateInp, kSampleRateMin);\n        return false;\n    }\n\n    if (m_sampleRateInp > kSampleRateMax) {\n        ggprintf(\"Error: capture sample rate (%g Hz) must be <= %g Hz\\n\", m_sampleRateInp, kSampleRateMax);\n        return false;\n    }\n\n    // memory allocation:\n\n    m_heap = nullptr;\n    m_heapSize = 0;\n\n    if (this->alloc(m_heap, m_heapSize) == false) {\n        ggprintf(\"Error: failed to compute the size of the required memory\\n\");\n        return false;\n    }\n\n    if (allocate == false) {\n        return true;\n    }\n\n    const auto heapSize0 = m_heapSize;\n\n    m_heap = calloc(m_heapSize, 1);\n\n    m_heapSize = 0;\n    if (this->alloc(m_heap, m_heapSize) == false) {\n        ggprintf(\"Error: failed to allocate the required memory: %d\\n\", m_heapSize);\n        return false;\n    }\n\n    if (heapSize0 != m_heapSize) {\n        ggprintf(\"Error: failed to allocate memory - heapSize0: %d, heapSize: %d\\n\", heapSize0, m_heapSize);\n        return false;\n    }\n\n    if (m_isRxEnabled) {\n        m_rx.samplesNeeded = m_samplesPerFrame;\n\n        m_rx.fftWorkI[0] = 0;\n\n        m_rx.protocol   = {};\n        m_rx.protocolId = GGWAVE_PROTOCOL_COUNT;\n        m_rx.protocols  = Protocols::rx();\n\n        m_rx.minFreqStart = minFreqStart(m_rx.protocols);\n    }\n\n    if (m_isTxEnabled) {\n        m_tx.protocols = Protocols::tx();\n    }\n\n    return init(\"\", {}, 0);\n}\n\nbool GGWave::alloc(void * p, int & n) {\n    const int maxLength   = m_isFixedPayloadLength ? m_payloadLength : kMaxLengthVariable;\n    const int totalLength = maxLength + getECCBytesForLength(maxLength);\n    const int totalTxs    = (totalLength + minBytesPerTx(Protocols::rx()) - 1)/minBytesPerTx(Protocols::tx());\n\n    if (totalLength > kMaxDataSize) {\n        ggprintf(\"Error: total length %d (payload %d + ECC %d bytes) is too large ( > %d)\\n\",\n                 totalLength, maxLength, getECCBytesForLength(maxLength), kMaxDataSize);\n        return false;\n    }\n\n    // common\n    ::ggalloc(m_dataEncoded, totalLength + m_encodedDataOffset, p, n);\n\n    if (m_isRxEnabled) {\n        ::ggalloc(m_rx.fftOut,   2*m_samplesPerFrame, p, n);\n        ::ggalloc(m_rx.fftWorkI, 3 + sqrt(m_samplesPerFrame/2), p, n);\n        ::ggalloc(m_rx.fftWorkF, m_samplesPerFrame/2, p, n);\n\n        ::ggalloc(m_rx.spectrum,           m_samplesPerFrame, p, n);\n        // small extra space because sometimes resampling needs a few more samples:\n        ::ggalloc(m_rx.amplitude,          m_needResampling ? m_samplesPerFrame + 128 : m_samplesPerFrame, p, n);\n        // min input sampling rate is 0.125*m_sampleRate:\n        ::ggalloc(m_rx.amplitudeResampled, m_needResampling ? 8*m_samplesPerFrame : m_samplesPerFrame, p, n);\n        ::ggalloc(m_rx.amplitudeTmp,       m_needResampling ? 8*m_samplesPerFrame*m_sampleSizeInp : m_samplesPerFrame*m_sampleSizeInp, p, n);\n\n        ::ggalloc(m_rx.data, maxLength + 1, p, n); // extra byte for null-termination\n\n        if (m_isFixedPayloadLength) {\n            if (m_payloadLength > kMaxLengthFixed) {\n                ggprintf(\"Invalid payload length: %d, max: %d\\n\", m_payloadLength, kMaxLengthFixed);\n                return false;\n            }\n\n            ::ggalloc(m_rx.spectrumHistoryFixed, totalTxs*maxFramesPerTx(Protocols::rx(), false), m_samplesPerFrame, p, n);\n            ::ggalloc(m_rx.detectedBins,         2*totalLength, p, n);\n            ::ggalloc(m_rx.detectedTones,        2*16*maxBytesPerTx(Protocols::rx()), p, n);\n        } else {\n            // variable payload length\n            ::ggalloc(m_rx.amplitudeRecorded, kMaxRecordedFrames*m_samplesPerFrame, p, n);\n            ::ggalloc(m_rx.amplitudeAverage,  m_samplesPerFrame, p, n);\n            ::ggalloc(m_rx.amplitudeHistory,  kMaxSpectrumHistory, m_samplesPerFrame, p, n);\n        }\n    }\n\n    if (m_isTxEnabled) {\n        const int maxDataBits = 2*16*maxBytesPerTx(Protocols::tx());\n\n        if (m_txOnlyTones == false) {\n            ::ggalloc(m_tx.phaseOffsets,    maxDataBits, p, n);\n            ::ggalloc(m_tx.bit0Amplitude,   maxDataBits, m_samplesPerFrame, p, n);\n            ::ggalloc(m_tx.bit1Amplitude,   maxDataBits, m_samplesPerFrame, p, n);\n            ::ggalloc(m_tx.output,          m_samplesPerFrame, p, n);\n            ::ggalloc(m_tx.outputResampled, 2*m_samplesPerFrame, p, n);\n            ::ggalloc(m_tx.outputTmp,       kMaxRecordedFrames*m_samplesPerFrame*m_sampleSizeOut, p, n);\n            ::ggalloc(m_tx.outputI16,       kMaxRecordedFrames*m_samplesPerFrame, p, n);\n        }\n\n        const int maxTones    = m_isFixedPayloadLength ? maxTonesPerTx(Protocols::tx()) : m_nBitsInMarker;\n\n        ::ggalloc(m_tx.data,     maxLength + 1, p, n); // first byte stores the length\n        ::ggalloc(m_tx.dataBits, maxDataBits, p, n);\n        ::ggalloc(m_tx.tones,    maxTones*totalTxs + (maxTones > 1 ? totalTxs : 0), p, n);\n    }\n\n    // pre-allocate Reed-Solomon memory buffers\n    {\n        const auto maxLength = m_isFixedPayloadLength ? m_payloadLength : kMaxLengthVariable;\n\n        if (m_isFixedPayloadLength == false) {\n            ::ggalloc(m_workRSLength, RS::ReedSolomon::getWorkSize_bytes(1, m_encodedDataOffset - 1), p, n);\n        }\n        ::ggalloc(m_workRSData, RS::ReedSolomon::getWorkSize_bytes(maxLength, getECCBytesForLength(maxLength)), p, n);\n    }\n\n    if (m_needResampling) {\n        m_resampler.alloc(p, n);\n    }\n\n    return true;\n}\n\nvoid GGWave::setLogFile(FILE * fptr) {\n    g_fptr = fptr;\n}\n\nconst GGWave::Parameters & GGWave::getDefaultParameters() {\n    static ggwave_Parameters result {\n        -1, // vaiable payload length\n        kDefaultSampleRate,\n        kDefaultSampleRate,\n        kDefaultSampleRate,\n        kDefaultSamplesPerFrame,\n        kDefaultSoundMarkerThreshold,\n        GGWAVE_SAMPLE_FORMAT_F32,\n        GGWAVE_SAMPLE_FORMAT_F32,\n        GGWAVE_OPERATING_MODE_RX | GGWAVE_OPERATING_MODE_TX,\n    };\n\n    return result;\n}\n\nbool GGWave::init(const char * text, TxProtocolId protocolId, const int volume) {\n    return init(strlen(text), text, protocolId, volume);\n}\n\nbool GGWave::init(int dataSize, const char * dataBuffer, TxProtocolId protocolId, const int volume) {\n    if (dataSize < 0) {\n        ggprintf(\"Negative data size: %d\\n\", dataSize);\n        return false;\n    }\n\n    // Tx\n    if (m_isTxEnabled) {\n        const auto maxLength = m_isFixedPayloadLength ? m_payloadLength : kMaxLengthVariable;\n\n        if (dataSize > maxLength) {\n            ggprintf(\"Truncating data from %d to %d bytes\\n\", dataSize, maxLength);\n            dataSize = maxLength;\n        }\n\n        if (volume < 0 || volume > 100) {\n            ggprintf(\"Invalid volume: %d\\n\", volume);\n            return false;\n        }\n\n        m_tx.hasData = false;\n        m_tx.data.zero();\n        m_dataEncoded.zero();\n\n        if (dataSize > 0) {\n            if (protocolId < 0 || protocolId >= m_tx.protocols.size()) {\n                ggprintf(\"Invalid protocol ID: %d\\n\", protocolId);\n                return false;\n            }\n\n            const auto & protocol = m_tx.protocols[protocolId];\n\n            if (protocol.enabled == false) {\n                ggprintf(\"Protocol %d is not enabled - make sure to enable it before creating the instance\\n\", protocolId);\n                return false;\n            }\n\n            if (protocol.extra == 2 && m_isFixedPayloadLength == false) {\n                ggprintf(\"Mono-tone protocols with variable length are not supported\\n\");\n                return false;\n            }\n\n            m_tx.protocol   = protocol;\n            m_tx.dataLength = m_isFixedPayloadLength ? m_payloadLength : dataSize;\n            m_tx.sendVolume = ((double)(volume))/100.0f;\n\n            m_tx.data[0] = m_tx.dataLength;\n            for (int i = 0; i < m_tx.dataLength; ++i) {\n                m_tx.data[i + 1] = i < dataSize ? dataBuffer[i] : 0;\n                if (m_isDSSEnabled) {\n                    m_tx.data[i + 1] ^= getDSSMagic(i);\n                }\n            }\n\n            m_tx.hasData = true;\n        }\n    } else {\n        if (dataSize > 0) {\n            ggprintf(\"Tx is disabled - cannot transmit data with this GGWave instance\\n\");\n        }\n    }\n\n    // Rx\n    if (m_isRxEnabled) {\n        m_rx.receiving = false;\n        m_rx.analyzing = false;\n\n        m_rx.framesToAnalyze = 0;\n        m_rx.framesLeftToAnalyze = 0;\n        m_rx.framesToRecord = 0;\n        m_rx.framesLeftToRecord = 0;\n\n        m_rx.spectrum.zero();\n        m_rx.amplitude.zero();\n        m_rx.amplitudeHistory.zero();\n\n        m_rx.data.zero();\n\n        m_rx.spectrumHistoryFixed.zero();\n    }\n\n    return true;\n}\n\nuint32_t GGWave::encodeSize_bytes() const {\n    return encodeSize_samples()*m_sampleSizeOut;\n}\n\nuint32_t GGWave::encodeSize_samples() const {\n    if (m_tx.hasData == false) {\n        return 0;\n    }\n\n    float factor = 1.0f;\n    int samplesPerFrameOut = m_samplesPerFrame;\n    if (m_needResampling) {\n        factor = m_sampleRate/m_sampleRateOut;\n        // note : +1 extra sample in order to overestimate the buffer size\n        samplesPerFrameOut = m_resampler.resample(factor, m_samplesPerFrame, m_tx.output.data(), nullptr) + 1;\n    }\n    const int nECCBytesPerTx = getECCBytesForLength(m_tx.dataLength);\n    const int sendDataLength = m_tx.dataLength + m_encodedDataOffset;\n    const int totalBytes = sendDataLength + nECCBytesPerTx;\n    const int totalDataFrames = m_tx.protocol.extra*((totalBytes + m_tx.protocol.bytesPerTx - 1)/m_tx.protocol.bytesPerTx)*m_tx.protocol.framesPerTx;\n\n    return (\n            m_nMarkerFrames + totalDataFrames + m_nMarkerFrames\n           )*samplesPerFrameOut;\n}\n\nuint32_t GGWave::encode() {\n    if (m_isTxEnabled == false) {\n        ggprintf(\"Tx is disabled - cannot transmit data with this GGWave instance\\n\");\n        return 0;\n    }\n\n    if (m_needResampling) {\n        m_resampler.reset();\n    }\n\n    const int nECCBytesPerTx = getECCBytesForLength(m_tx.dataLength);\n    const int sendDataLength = m_tx.dataLength + m_encodedDataOffset;\n    const int totalBytes = sendDataLength + nECCBytesPerTx;\n    const int totalDataFrames = m_tx.protocol.extra*((totalBytes + m_tx.protocol.bytesPerTx - 1)/m_tx.protocol.bytesPerTx)*m_tx.protocol.framesPerTx;\n\n    if (m_isFixedPayloadLength == false) {\n        RS::ReedSolomon rsLength(1, m_encodedDataOffset - 1, m_workRSLength.data());\n        rsLength.Encode(m_tx.data.data(), m_dataEncoded.data());\n    }\n\n    // first byte of m_tx.data contains the length of the payload, so we skip it:\n    RS::ReedSolomon rsData = RS::ReedSolomon(m_tx.dataLength, nECCBytesPerTx, m_workRSData.data());\n    rsData.Encode(m_tx.data.data() + 1, m_dataEncoded.data() + m_encodedDataOffset);\n\n    // generate tones\n    {\n        int frameId = 0;\n        bool hasData = m_tx.hasData;\n\n        m_tx.nTones = 0;\n        while (hasData) {\n            if (frameId < m_nMarkerFrames) {\n                for (int i = 0; i < m_nBitsInMarker; ++i) {\n                    m_tx.tones[m_tx.nTones++] = 2*i + i%2;\n                }\n            } else if (frameId < m_nMarkerFrames + totalDataFrames) {\n                int dataOffset = frameId - m_nMarkerFrames;\n                dataOffset /= m_tx.protocol.framesPerTx;\n                dataOffset *= m_tx.protocol.bytesPerTx;\n\n                m_tx.dataBits.zero();\n\n                for (int j = 0; j < m_tx.protocol.bytesPerTx; ++j) {\n                    if (m_tx.protocol.extra == 1) {\n                        {\n                            uint8_t d = m_dataEncoded[dataOffset + j] & 15;\n                            m_tx.dataBits[(2*j + 0)*16 + d] = 1;\n                        }\n                        {\n                            uint8_t d = m_dataEncoded[dataOffset + j] & 240;\n                            m_tx.dataBits[(2*j + 1)*16 + (d >> 4)] = 1;\n                        }\n                    } else {\n                        if (dataOffset % m_tx.protocol.extra == 0) {\n                            uint8_t d = m_dataEncoded[dataOffset/m_tx.protocol.extra + j] & 15;\n                            m_tx.dataBits[(2*j + 0)*16 + d] = 1;\n                        } else {\n                            uint8_t d = m_dataEncoded[dataOffset/m_tx.protocol.extra + j] & 240;\n                            m_tx.dataBits[(2*j + 0)*16 + (d >> 4)] = 1;\n                        }\n                    }\n                }\n\n                for (int k = 0; k < 2*m_tx.protocol.bytesPerTx*16; ++k) {\n                    if (m_tx.dataBits[k] == 0) continue;\n\n                    m_tx.tones[m_tx.nTones++] = k;\n                }\n            } else if (frameId < m_nMarkerFrames + totalDataFrames + m_nMarkerFrames) {\n                for (int i = 0; i < m_nBitsInMarker; ++i) {\n                    m_tx.tones[m_tx.nTones++] = 2*i + (1 - i%2);\n                }\n            } else {\n                hasData = false;\n                break;\n            }\n\n            if (m_tx.protocol.nTones() > 1) {\n                m_tx.tones[m_tx.nTones++] = -1;\n            }\n\n            frameId += m_tx.protocol.framesPerTx;\n        }\n\n        if (m_txOnlyTones) {\n            m_tx.hasData = false;\n            return true;\n        }\n    }\n\n    // compute Tx data\n    {\n        for (int k = 0; k < (int) m_tx.phaseOffsets.size(); ++k) {\n            m_tx.phaseOffsets[k] = (M_PI*k)/(m_tx.protocol.nDataBitsPerTx());\n        }\n\n        // note : what is the purpose of this shuffle ? I forgot .. :(\n        //std::random_device rd;\n        //std::mt19937 g(rd());\n\n        //std::shuffle(phaseOffsets.begin(), phaseOffsets.end(), g);\n\n        for (int k = 0; k < (int) m_tx.dataBits.size(); ++k) {\n            const double freq = bitFreq(m_tx.protocol, k);\n\n            const double phaseOffset = m_tx.phaseOffsets[k];\n            const double curHzPerSample = m_hzPerSample;\n            const double curIHzPerSample = 1.0/curHzPerSample;\n\n            for (int i = 0; i < m_samplesPerFrame; i++) {\n                const double curi = i;\n                m_tx.bit1Amplitude[k][i] = sin((2.0*M_PI)*(curi*m_isamplesPerFrame)*(freq*curIHzPerSample) + phaseOffset);\n            }\n\n            for (int i = 0; i < m_samplesPerFrame; i++) {\n                const double curi = i;\n                m_tx.bit0Amplitude[k][i] = sin((2.0*M_PI)*(curi*m_isamplesPerFrame)*((freq + m_hzPerSample*m_freqDelta_bin)*curIHzPerSample) + phaseOffset);\n            }\n        }\n    }\n\n    int frameId = 0;\n    uint32_t offset = 0;\n    const float factor = m_sampleRate/m_sampleRateOut;\n\n    while (m_tx.hasData) {\n        m_tx.output.zero();\n\n        uint16_t nFreq = 0;\n        if (frameId < m_nMarkerFrames) {\n            nFreq = m_nBitsInMarker;\n\n            for (int i = 0; i < m_nBitsInMarker; ++i) {\n                if (i%2 == 0) {\n                    ::addAmplitudeSmooth(m_tx.bit1Amplitude[i], m_tx.output, m_tx.sendVolume, 0, m_samplesPerFrame, frameId, m_nMarkerFrames);\n                } else {\n                    ::addAmplitudeSmooth(m_tx.bit0Amplitude[i], m_tx.output, m_tx.sendVolume, 0, m_samplesPerFrame, frameId, m_nMarkerFrames);\n                }\n            }\n        } else if (frameId < m_nMarkerFrames + totalDataFrames) {\n            int dataOffset = frameId - m_nMarkerFrames;\n            int cycleModMain = dataOffset%m_tx.protocol.framesPerTx;\n            dataOffset /= m_tx.protocol.framesPerTx;\n            dataOffset *= m_tx.protocol.bytesPerTx;\n\n            m_tx.dataBits.zero();\n\n            for (int j = 0; j < m_tx.protocol.bytesPerTx; ++j) {\n                if (m_tx.protocol.extra == 1) {\n                    {\n                        uint8_t d = m_dataEncoded[dataOffset + j] & 15;\n                        m_tx.dataBits[(2*j + 0)*16 + d] = 1;\n                    }\n                    {\n                        uint8_t d = m_dataEncoded[dataOffset + j] & 240;\n                        m_tx.dataBits[(2*j + 1)*16 + (d >> 4)] = 1;\n                    }\n                } else {\n                    if (dataOffset % m_tx.protocol.extra == 0) {\n                        uint8_t d = m_dataEncoded[dataOffset/m_tx.protocol.extra + j] & 15;\n                        m_tx.dataBits[(2*j + 0)*16 + d] = 1;\n                    } else {\n                        uint8_t d = m_dataEncoded[dataOffset/m_tx.protocol.extra + j] & 240;\n                        m_tx.dataBits[(2*j + 0)*16 + (d >> 4)] = 1;\n                    }\n                }\n            }\n\n            for (int k = 0; k < 2*m_tx.protocol.bytesPerTx*16; ++k) {\n                if (m_tx.dataBits[k] == 0) continue;\n\n                ++nFreq;\n                if (k%2) {\n                    ::addAmplitudeSmooth(m_tx.bit0Amplitude[k/2], m_tx.output, m_tx.sendVolume, 0, m_samplesPerFrame, cycleModMain, m_tx.protocol.framesPerTx);\n                } else {\n                    ::addAmplitudeSmooth(m_tx.bit1Amplitude[k/2], m_tx.output, m_tx.sendVolume, 0, m_samplesPerFrame, cycleModMain, m_tx.protocol.framesPerTx);\n                }\n            }\n        } else if (frameId < m_nMarkerFrames + totalDataFrames + m_nMarkerFrames) {\n            nFreq = m_nBitsInMarker;\n\n            const int fId = frameId - (m_nMarkerFrames + totalDataFrames);\n            for (int i = 0; i < m_nBitsInMarker; ++i) {\n                if (i%2 == 0) {\n                    addAmplitudeSmooth(m_tx.bit0Amplitude[i], m_tx.output, m_tx.sendVolume, 0, m_samplesPerFrame, fId, m_nMarkerFrames);\n                } else {\n                    addAmplitudeSmooth(m_tx.bit1Amplitude[i], m_tx.output, m_tx.sendVolume, 0, m_samplesPerFrame, fId, m_nMarkerFrames);\n                }\n            }\n        } else {\n            m_tx.hasData = false;\n            break;\n        }\n\n        if (nFreq == 0) nFreq = 1;\n        const float scale = 1.0f/nFreq;\n        for (int i = 0; i < m_samplesPerFrame; ++i) {\n            m_tx.output[i] *= scale;\n        }\n\n        int samplesPerFrameOut = m_samplesPerFrame;\n        if (m_needResampling) {\n            samplesPerFrameOut = m_resampler.resample(factor, m_samplesPerFrame, m_tx.output.data(), m_tx.outputResampled.data());\n        } else {\n            m_tx.outputResampled.copy(m_tx.output);\n        }\n\n        // default output is in 16-bit signed int so we always compute it\n        for (int i = 0; i < samplesPerFrameOut; ++i) {\n            m_tx.outputI16[offset + i] = 32768*m_tx.outputResampled[i];\n        }\n\n        // convert from 32-bit float\n        switch (m_sampleFormatOut) {\n            case GGWAVE_SAMPLE_FORMAT_UNDEFINED: break;\n            case GGWAVE_SAMPLE_FORMAT_U8:\n                {\n                    auto p = reinterpret_cast<uint8_t *>(m_tx.outputTmp.data());\n                    for (int i = 0; i < samplesPerFrameOut; ++i) {\n                        p[offset + i] = 128*(m_tx.outputResampled[i] + 1.0f);\n                    }\n                } break;\n            case GGWAVE_SAMPLE_FORMAT_I8:\n                {\n                    auto p = reinterpret_cast<uint8_t *>(m_tx.outputTmp.data());\n                    for (int i = 0; i < samplesPerFrameOut; ++i) {\n                        p[offset + i] = 128*m_tx.outputResampled[i];\n                    }\n                } break;\n            case GGWAVE_SAMPLE_FORMAT_U16:\n                {\n                    auto p = reinterpret_cast<uint16_t *>(m_tx.outputTmp.data());\n                    for (int i = 0; i < samplesPerFrameOut; ++i) {\n                        p[offset + i] = 32768*(m_tx.outputResampled[i] + 1.0f);\n                    }\n                } break;\n            case GGWAVE_SAMPLE_FORMAT_I16:\n                {\n                    // skip because we already have the data in m_tx.outputI16\n                    //auto p = reinterpret_cast<uint16_t *>(m_tx.outputTmp.data());\n                    //for (int i = 0; i < samplesPerFrameOut; ++i) {\n                    //    p[offset + i] = 32768*m_tx.outputResampled[i];\n                    //}\n                } break;\n            case GGWAVE_SAMPLE_FORMAT_F32:\n                {\n                    auto p = reinterpret_cast<float *>(m_tx.outputTmp.data());\n                    for (int i = 0; i < samplesPerFrameOut; ++i) {\n                        p[offset + i] = m_tx.outputResampled[i];\n                    }\n                } break;\n        }\n\n        ++frameId;\n        offset += samplesPerFrameOut;\n    }\n\n    m_tx.lastAmplitudeSize = offset;\n\n    // the encoded waveform can be accessed via the txWaveform() method\n    // we return the size of the waveform in bytes:\n    return offset*m_sampleSizeOut;\n}\n\nbool GGWave::decode(const void * data, uint32_t nBytes) {\n    if (m_isRxEnabled == false) {\n        ggprintf(\"Rx is disabled - cannot receive data with this GGWave instance\\n\");\n        return false;\n    }\n\n    if (m_tx.hasData) {\n        ggprintf(\"Cannot decode while transmitting\\n\");\n        return false;\n    }\n\n    auto dataBuffer = (uint8_t *) data;\n    const float factor = m_sampleRateInp/m_sampleRate;\n\n    while (true) {\n        // read capture data\n        uint32_t nBytesNeeded = m_rx.samplesNeeded*m_sampleSizeInp;\n\n        if (m_needResampling) {\n            // note : predict 4 extra samples just to make sure we have enough data\n            nBytesNeeded = (m_resampler.resample(1.0f/factor, m_rx.samplesNeeded, m_rx.amplitudeResampled.data(), nullptr) + 4)*m_sampleSizeInp;\n        }\n\n        const uint32_t nBytesRecorded = GG_MIN(nBytes, nBytesNeeded);\n\n        if (nBytesRecorded == 0) {\n            break;\n        }\n\n        switch (m_sampleFormatInp) {\n            case GGWAVE_SAMPLE_FORMAT_UNDEFINED: break;\n            case GGWAVE_SAMPLE_FORMAT_U8:\n            case GGWAVE_SAMPLE_FORMAT_I8:\n            case GGWAVE_SAMPLE_FORMAT_U16:\n            case GGWAVE_SAMPLE_FORMAT_I16:\n                {\n                    memcpy(m_rx.amplitudeTmp.data(), dataBuffer, nBytesRecorded);\n                } break;\n            case GGWAVE_SAMPLE_FORMAT_F32:\n                {\n                    memcpy(m_rx.amplitudeResampled.data(), dataBuffer, nBytesRecorded);\n                } break;\n        }\n\n        dataBuffer += nBytesRecorded;\n        nBytes -= nBytesRecorded;\n\n        if (nBytesRecorded % m_sampleSizeInp != 0) {\n            ggprintf(\"Failure during capture - provided bytes (%d) are not multiple of sample size (%d)\\n\",\n                    nBytesRecorded, m_sampleSizeInp);\n            m_rx.samplesNeeded = m_samplesPerFrame;\n            break;\n        }\n\n        // convert to 32-bit float\n        int nSamplesRecorded = nBytesRecorded/m_sampleSizeInp;\n        switch (m_sampleFormatInp) {\n            case GGWAVE_SAMPLE_FORMAT_UNDEFINED: break;\n            case GGWAVE_SAMPLE_FORMAT_U8:\n                {\n                    constexpr float scale = 1.0f/128;\n                    auto p = reinterpret_cast<uint8_t *>(m_rx.amplitudeTmp.data());\n                    for (int i = 0; i < nSamplesRecorded; ++i) {\n                        m_rx.amplitudeResampled[i] = float(int16_t(*(p + i)) - 128)*scale;\n                    }\n                } break;\n            case GGWAVE_SAMPLE_FORMAT_I8:\n                {\n                    constexpr float scale = 1.0f/128;\n                    auto p = reinterpret_cast<int8_t *>(m_rx.amplitudeTmp.data());\n                    for (int i = 0; i < nSamplesRecorded; ++i) {\n                        m_rx.amplitudeResampled[i] = float(*(p + i))*scale;\n                    }\n                } break;\n            case GGWAVE_SAMPLE_FORMAT_U16:\n                {\n                    constexpr float scale = 1.0f/32768;\n                    auto p = reinterpret_cast<uint16_t *>(m_rx.amplitudeTmp.data());\n                    for (int i = 0; i < nSamplesRecorded; ++i) {\n                        m_rx.amplitudeResampled[i] = float(int32_t(*(p + i)) - 32768)*scale;\n                    }\n                } break;\n            case GGWAVE_SAMPLE_FORMAT_I16:\n                {\n                    constexpr float scale = 1.0f/32768;\n                    auto p = reinterpret_cast<int16_t *>(m_rx.amplitudeTmp.data());\n                    for (int i = 0; i < nSamplesRecorded; ++i) {\n                        m_rx.amplitudeResampled[i] = float(*(p + i))*scale;\n                    }\n                } break;\n            case GGWAVE_SAMPLE_FORMAT_F32: break;\n        }\n\n        uint32_t offset = m_samplesPerFrame - m_rx.samplesNeeded;\n\n        if (m_needResampling) {\n            if (nSamplesRecorded <= 2*Resampler::kWidth) {\n                m_rx.samplesNeeded = m_samplesPerFrame;\n                break;\n            }\n\n            // reset resampler state every minute\n            if (!m_rx.receiving && m_resampler.nSamplesTotal() > 60.0f*factor*m_sampleRate) {\n                m_resampler.reset();\n            }\n\n            int nSamplesResampled = offset + m_resampler.resample(factor, nSamplesRecorded, m_rx.amplitudeResampled.data(), m_rx.amplitude.data() + offset);\n            nSamplesRecorded = nSamplesResampled;\n        } else {\n            for (int i = 0; i < nSamplesRecorded; ++i) {\n                m_rx.amplitude[offset + i] = m_rx.amplitudeResampled[i];\n            }\n        }\n\n        // we have enough bytes to do analysis\n        if (nSamplesRecorded >= m_samplesPerFrame) {\n            m_rx.hasNewAmplitude = true;\n\n            if (m_isFixedPayloadLength) {\n                decode_fixed();\n            } else {\n                decode_variable();\n            }\n\n            int nExtraSamples = nSamplesRecorded - m_samplesPerFrame;\n            for (int i = 0; i < nExtraSamples; ++i) {\n                m_rx.amplitude[i] = m_rx.amplitude[m_samplesPerFrame + i];\n            }\n\n            m_rx.samplesNeeded = m_samplesPerFrame - nExtraSamples;\n        } else {\n            m_rx.samplesNeeded = m_samplesPerFrame - nSamplesRecorded;\n            break;\n        }\n    }\n\n    return true;\n}\n\n//\n// instance state\n//\n\nbool GGWave::isDSSEnabled() const { return m_isDSSEnabled; }\n\nint GGWave::samplesPerFrame() const { return m_samplesPerFrame; }\nint GGWave::sampleSizeInp()   const { return m_sampleSizeInp; }\nint GGWave::sampleSizeOut()   const { return m_sampleSizeOut; }\n\nfloat GGWave::hzPerSample()   const { return m_hzPerSample; }\nfloat GGWave::sampleRateInp() const { return m_sampleRateInp; }\nfloat GGWave::sampleRateOut() const { return m_sampleRateOut; }\nGGWave::SampleFormat GGWave::sampleFormatInp() const { return m_sampleFormatInp; }\nGGWave::SampleFormat GGWave::sampleFormatOut() const { return m_sampleFormatOut; }\n\nint GGWave::heapSize() const { return m_heapSize; }\n\n//\n// Tx\n//\n\nconst void * GGWave::txWaveform() const {\n    switch (m_sampleFormatOut) {\n        case GGWAVE_SAMPLE_FORMAT_UNDEFINED: break;\n        case GGWAVE_SAMPLE_FORMAT_I16:\n            {\n                return m_tx.outputI16.data();\n            } break;\n        case GGWAVE_SAMPLE_FORMAT_U8:\n        case GGWAVE_SAMPLE_FORMAT_I8:\n        case GGWAVE_SAMPLE_FORMAT_U16:\n        case GGWAVE_SAMPLE_FORMAT_F32:\n            {\n                return m_tx.outputTmp.data();\n            } break;\n    }\n\n    return nullptr;\n}\n\nconst GGWave::Tones GGWave::txTones() const { return { m_tx.tones.data(), m_tx.nTones }; }\n\nbool GGWave::txHasData() const { return m_tx.hasData; }\n\nbool GGWave::txTakeAmplitudeI16(AmplitudeI16 & dst) {\n    if (m_tx.lastAmplitudeSize == 0) return false;\n\n    dst.assign({ m_tx.outputI16.data(), m_tx.lastAmplitudeSize });\n    m_tx.lastAmplitudeSize = 0;\n\n    return true;\n}\n\nconst GGWave::RxProtocols & GGWave::txProtocols() const { return m_tx.protocols; }\n\n//\n// Rx\n//\n\nbool GGWave::rxReceiving() const { return m_rx.receiving; }\nbool GGWave::rxAnalyzing() const { return m_rx.analyzing; }\n\nint GGWave::rxSamplesNeeded()       const { return m_rx.samplesNeeded; }\nint GGWave::rxFramesToRecord()      const { return m_rx.framesToRecord; }\nint GGWave::rxFramesLeftToRecord()  const { return m_rx.framesLeftToRecord; }\nint GGWave::rxFramesToAnalyze()     const { return m_rx.framesToAnalyze; }\nint GGWave::rxFramesLeftToAnalyze() const { return m_rx.framesLeftToAnalyze; }\nint GGWave::rxDurationFrames()      const { return m_rx.recvDuration_frames; }\n\nbool GGWave::rxStopReceiving() {\n    if (m_rx.receiving == false) {\n        return false;\n    }\n\n    m_rx.receiving = false;\n\n    return true;\n}\n\nGGWave::RxProtocols & GGWave::rxProtocols() { return m_rx.protocols; }\n\nint GGWave::rxDataLength() const { return m_rx.dataLength; }\n\nconst GGWave::TxRxData &      GGWave::rxData()       const { return m_rx.data; }\nconst GGWave::RxProtocol &    GGWave::rxProtocol()   const { return m_rx.protocol; }\nconst GGWave::RxProtocolId &  GGWave::rxProtocolId() const { return m_rx.protocolId; }\nconst GGWave::Spectrum &      GGWave::rxSpectrum()   const { return m_rx.spectrum; }\nconst GGWave::Amplitude &     GGWave::rxAmplitude()  const { return m_rx.amplitude; }\n\nint GGWave::rxTakeData(TxRxData & dst) {\n    if (m_rx.dataLength == 0) return 0;\n\n    auto res = m_rx.dataLength;\n    m_rx.dataLength = 0;\n\n    if (res != -1) {\n        dst.assign({ m_rx.data.data(), res });\n    }\n\n    return res;\n}\n\nbool GGWave::rxTakeSpectrum(Spectrum & dst) {\n    if (m_rx.hasNewSpectrum == false) return false;\n\n    m_rx.hasNewSpectrum = false;\n    dst.assign(m_rx.spectrum);\n\n    return true;\n}\n\nbool GGWave::rxTakeAmplitude(Amplitude & dst) {\n    if (m_rx.hasNewAmplitude == false) return false;\n\n    m_rx.hasNewAmplitude = false;\n    dst.assign(m_rx.amplitude);\n\n    return true;\n}\n\nbool GGWave::computeFFTR(const float * src, float * dst, int N) {\n    if (N != m_samplesPerFrame) {\n        ggprintf(\"computeFFTR: N (%d) must be equal to 'samplesPerFrame' %d\\n\", N, m_samplesPerFrame);\n        return false;\n    }\n\n    FFT(src, dst, N, m_rx.fftWorkI.data(), m_rx.fftWorkF.data());\n\n    return true;\n}\n\nint GGWave::computeFFTR(const float * src, float * dst, int N, int * wi, float * wf) {\n    if (wi == nullptr) return 2*N;\n    if (wf == nullptr) return 3 + sqrt(N/2);\n\n    FFT(src, dst, N, wi, wf);\n\n    return 1;\n}\n\nint GGWave::filter(ggwave_Filter filter, float * waveform, int N, float p0, float p1, float * w) {\n    if (w == nullptr) {\n        switch (filter) {\n            case GGWAVE_FILTER_HANN:                  return N;\n            case GGWAVE_FILTER_HAMMING:               return N;\n            case GGWAVE_FILTER_FIRST_ORDER_HIGH_PASS: return 11;\n        };\n    }\n\n    if (w[0] == 0.0f && w[1] == 0.0f) {\n        switch (filter) {\n            case GGWAVE_FILTER_HANN:\n                {\n                    const float f = 2.0f*M_PI/(float)N;\n                    for (int i = 0; i < N; i++) {\n                        w[i] = 0.5f - 0.5f*cosf(f*(float)i);\n                    }\n                } break;\n            case GGWAVE_FILTER_HAMMING:\n                {\n                    const float f = 2.0f*M_PI/(float)N;\n                    for (int i = 0; i < N; i++) {\n                        w[i] = 0.54f - 0.46f*cosf(f*(float)i);\n                    }\n                } break;\n            case GGWAVE_FILTER_FIRST_ORDER_HIGH_PASS:\n                {\n                    const float th = 2.0f*M_PI*p0/p1;\n                    const float g = cos(th)/(1.0f + sin(th));\n                    w[0] = (1.0f + g)/2.0f;\n                    w[1] = -((1.0f + g)/2.0f);\n                    w[2] = 0.0f;\n                    w[3] = -g;\n                    w[4] = 0.0f;\n\n                    w[5] = 0.0f;\n                    w[6] = 0.0f;\n                    w[7] = 0.0f;\n                    w[8] = 0.0f;\n                } break;\n        };\n    }\n\n    switch (filter) {\n        case GGWAVE_FILTER_HANN:\n        case GGWAVE_FILTER_HAMMING:\n            {\n                for (int i = 0; i < N; i++) {\n                    waveform[i] *= w[i];\n                }\n            } break;\n        case GGWAVE_FILTER_FIRST_ORDER_HIGH_PASS:\n            {\n                for (int i = 0; i < N; i++) {\n                    float xn = waveform[i];\n                    float yn = w[0]*xn + w[1]*w[5] + w[2]*w[6] + w[3]*w[7] + w[4]*w[8];\n                    w[6] = w[5];\n                    w[5] = xn;\n                    w[8] = w[7];\n                    w[7] = yn;\n\n                    waveform[i] = yn;\n                }\n            } break;\n    };\n\n    return 1;\n}\n\n//\n// GGWave::Resampler\n//\n\nGGWave::Resampler::Resampler() {}\n\nbool GGWave::Resampler::alloc(void * p, int & n) {\n    ggalloc(m_sincTable,   kWidth*kSamplesPerZeroCrossing, p, n);\n    ggalloc(m_delayBuffer, 3*kWidth, p, n);\n    ggalloc(m_edgeSamples, kWidth, p, n);\n    ggalloc(m_samplesInp,  4096, p, n);\n\n    if (p) {\n        makeSinc();\n        reset();\n    }\n\n    return true;\n}\n\nvoid GGWave::Resampler::reset() {\n    m_state = {};\n    m_edgeSamples.zero();\n    m_delayBuffer.zero();\n    m_samplesInp.zero();\n}\n\nint GGWave::Resampler::resample(\n        float factor,\n        int nSamples,\n        const float * samplesInp,\n        float * samplesOut) {\n    int idxInp = -1;\n    int idxOut = 0;\n    int notDone = 1;\n    float data_in = 0.0f;\n    float data_out = 0.0f;\n    double one_over_factor = 1.0;\n\n    auto stateSave = m_state;\n\n    m_state.nSamplesTotal += nSamples;\n\n    if (samplesOut) {\n        assert(nSamples > kWidth);\n        assert((int) m_samplesInp.size() >= nSamples + kWidth);\n        //if ((int) m_samplesInp.size() < nSamples + kWidth) {\n        //    m_samplesInp.resize(nSamples + kWidth);\n        //}\n        for (int i = 0; i < kWidth; ++i) {\n            m_samplesInp[i] = m_edgeSamples[i];\n            m_edgeSamples[i] = samplesInp[nSamples - kWidth + i];\n        }\n        for (int i = 0; i < nSamples; ++i) {\n            m_samplesInp[i + kWidth] = samplesInp[i];\n        }\n        samplesInp = m_samplesInp.data();\n    }\n\n    while (notDone) {\n        while (m_state.timeLast < m_state.timeInt) {\n            if (++idxInp >= nSamples) {\n                notDone = 0;\n                break;\n            } else {\n                data_in = samplesInp[idxInp];\n            }\n            //printf(\"xxxx idxInp = %d\\n\", idxInp);\n            if (samplesOut) newData(data_in);\n            m_state.timeLast += 1;\n        }\n\n        if (notDone == false) break;\n\n        double temp1 = 0.0;\n        int left_limit = m_state.timeNow - kWidth + 1; /* leftmost neighboring sample used for interp.*/\n        int right_limit = m_state.timeNow + kWidth;    /* rightmost leftmost neighboring sample used for interp.*/\n        if (left_limit < 0) left_limit = 0;\n        if (right_limit > m_state.nSamplesTotal + kWidth) right_limit = m_state.nSamplesTotal + kWidth;\n        if (factor < 1.0) {\n            for (int j = left_limit; j < right_limit; j++) {\n                temp1 += getData(j - m_state.timeInt)*sinc(m_state.timeNow - (double) j);\n            }\n            data_out = temp1;\n        }\n        else {\n            one_over_factor = 1.0 / factor;\n            for (int j = left_limit; j < right_limit; j++) {\n                temp1 += getData(j - m_state.timeInt)*one_over_factor*sinc(one_over_factor*(m_state.timeNow - (double) j));\n            }\n            data_out = temp1;\n        }\n\n        if (samplesOut) {\n            //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);\n            samplesOut[idxOut] = data_out;\n        }\n        ++idxOut;\n\n        m_state.timeNow += factor;\n        m_state.timeLast = m_state.timeInt;\n        m_state.timeInt = m_state.timeNow;\n        while (m_state.timeLast < m_state.timeInt) {\n            if (++idxInp >= nSamples) {\n                notDone = 0;\n                break;\n            } else {\n                data_in = samplesInp[idxInp];\n            }\n            if (samplesOut) newData(data_in);\n            m_state.timeLast += 1;\n        }\n        //printf(\"last idxInp = %d, nSamples = %d\\n\", idxInp, nSamples);\n    }\n\n    if (samplesOut == nullptr) {\n        m_state = stateSave;\n    }\n\n    return idxOut;\n}\n\nfloat GGWave::Resampler::getData(int j) const {\n    return m_delayBuffer[(int) j + kWidth];\n}\n\nvoid GGWave::Resampler::newData(float data) {\n    for (int i = 0; i < kDelaySize - 5; i++) {\n        m_delayBuffer[i] = m_delayBuffer[i + 1];\n    }\n    m_delayBuffer[kDelaySize - 5] = data;\n}\n\nvoid GGWave::Resampler::makeSinc() {\n    double temp, win_freq, win;\n    win_freq = M_PI/kWidth/kSamplesPerZeroCrossing;\n    m_sincTable[0] = 1.0;\n    for (int i = 1; i < kWidth*kSamplesPerZeroCrossing; i++) {\n        temp = (double) i*M_PI/kSamplesPerZeroCrossing;\n        m_sincTable[i] = sin(temp)/temp;\n        win = 0.5 + 0.5*cos(win_freq*i);\n        m_sincTable[i] *= win;\n    }\n}\n\ndouble GGWave::Resampler::sinc(double x) const {\n    int low;\n    double temp, delta;\n    if (fabs(x) >= kWidth - 1) {\n        return 0.0;\n    } else {\n        temp = fabs(x)*(double) kSamplesPerZeroCrossing;\n        low = temp;          /* these are interpolation steps */\n        delta = temp - low;  /* and can be ommited if desired */\n        return linear_interp(m_sincTable[low], m_sincTable[low + 1], delta);\n    }\n}\n\n//\n// Variable payload length\n//\n\nvoid GGWave::decode_variable() {\n    m_rx.amplitudeHistory[m_rx.historyId].copy(m_rx.amplitude);\n\n    if (++m_rx.historyId >= kMaxSpectrumHistory) {\n        m_rx.historyId = 0;\n    }\n\n    if (m_rx.historyId == 0 || m_rx.receiving) {\n        m_rx.hasNewSpectrum = true;\n\n        m_rx.amplitudeAverage.zero();\n        for (int j = 0; j < (int) m_rx.amplitudeHistory.size(); ++j) {\n            auto s = m_rx.amplitudeHistory[j];\n            for (int i = 0; i < m_samplesPerFrame; ++i) {\n                m_rx.amplitudeAverage[i] += s[i];\n            }\n        }\n\n        float norm = 1.0f/kMaxSpectrumHistory;\n        for (int i = 0; i < m_samplesPerFrame; ++i) {\n            m_rx.amplitudeAverage[i] *= norm;\n        }\n\n        // calculate spectrum\n        FFT(m_rx.amplitudeAverage.data(), m_rx.fftOut.data(), m_samplesPerFrame, m_rx.fftWorkI.data(), m_rx.fftWorkF.data());\n\n        for (int i = 0; i < m_samplesPerFrame; ++i) {\n            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]);\n        }\n        for (int i = 1; i < m_samplesPerFrame/2; ++i) {\n            m_rx.spectrum[i] += m_rx.spectrum[m_samplesPerFrame - i];\n        }\n    }\n\n    if (m_rx.framesLeftToRecord > 0) {\n        memcpy(m_rx.amplitudeRecorded.data() + (m_rx.framesToRecord - m_rx.framesLeftToRecord)*m_samplesPerFrame,\n               m_rx.amplitude.data(),\n               m_samplesPerFrame*sizeof(float));\n\n        if (--m_rx.framesLeftToRecord <= 0) {\n            m_rx.analyzing = true;\n        }\n    }\n\n    if (m_rx.analyzing) {\n        ggprintf(\"Analyzing captured data ..\\n\");\n\n        const int stepsPerFrame = 16;\n        const int step = m_samplesPerFrame/stepsPerFrame;\n\n        bool isValid = false;\n        for (int protocolId = 0; protocolId < (int) m_rx.protocols.size(); ++protocolId) {\n            const auto & protocol = m_rx.protocols[protocolId];\n            if (protocol.enabled == false) {\n                continue;\n            }\n\n            // skip Rx protocol if it is mono-tone\n            if (protocol.extra == 2) {\n                continue;\n            }\n\n            // skip Rx protocol if start frequency is different from detected one\n            if (protocol.freqStart != m_rx.markerFreqStart) {\n                continue;\n            }\n\n            m_rx.spectrum.zero();\n\n            m_rx.framesToAnalyze = m_nMarkerFrames*stepsPerFrame;\n            m_rx.framesLeftToAnalyze = m_rx.framesToAnalyze;\n\n            // note : not sure if looping backwards here is more meaningful than looping forwards\n            for (int ii = m_nMarkerFrames*stepsPerFrame - 1; ii >= 0; --ii) {\n                bool knownLength = false;\n\n                int decodedLength = 0;\n                const int offsetStart = ii;\n                for (int itx = 0; itx < 1024; ++itx) {\n                    int offsetTx = offsetStart + itx*protocol.framesPerTx*stepsPerFrame;\n                    if (offsetTx >= m_rx.recvDuration_frames*stepsPerFrame || (itx + 1)*protocol.bytesPerTx >= (int) m_dataEncoded.size()) {\n                        break;\n                    }\n\n                    memcpy(m_rx.fftOut.data(),\n                           m_rx.amplitudeRecorded.data() + offsetTx*step,\n                           m_samplesPerFrame*sizeof(float));\n\n                    // note : should we skip the first and last frame here as they are amplitude-smoothed?\n                    for (int k = 1; k < protocol.framesPerTx; ++k) {\n                        for (int i = 0; i < m_samplesPerFrame; ++i) {\n                            m_rx.fftOut[i] += m_rx.amplitudeRecorded[(offsetTx + k*stepsPerFrame)*step + i];\n                        }\n                    }\n\n                    FFT(m_rx.fftOut.data(), m_samplesPerFrame, m_rx.fftWorkI.data(), m_rx.fftWorkF.data());\n\n                    for (int i = 0; i < m_samplesPerFrame; ++i) {\n                        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]);\n                    }\n                    for (int i = 1; i < m_samplesPerFrame/2; ++i) {\n                        m_rx.spectrum[i] += m_rx.spectrum[m_samplesPerFrame - i];\n                    }\n\n                    uint8_t curByte = 0;\n                    for (int i = 0; i < 2*protocol.bytesPerTx; ++i) {\n                        double freq = m_hzPerSample*protocol.freqStart;\n                        int bin = round(freq*m_ihzPerSample) + 16*i;\n\n                        int kmax = 0;\n                        double amax = 0.0;\n                        for (int k = 0; k < 16; ++k) {\n                            if (m_rx.spectrum[bin + k] > amax) {\n                                kmax = k;\n                                amax = m_rx.spectrum[bin + k];\n                            }\n                        }\n\n                        if (i%2) {\n                            curByte += (kmax << 4);\n                            m_dataEncoded[itx*protocol.bytesPerTx + i/2] = curByte;\n                            curByte = 0;\n                        } else {\n                            curByte = kmax;\n                        }\n                    }\n\n                    if (itx*protocol.bytesPerTx > m_encodedDataOffset && knownLength == false) {\n                        RS::ReedSolomon rsLength(1, m_encodedDataOffset - 1, m_workRSLength.data());\n                        if ((rsLength.Decode(m_dataEncoded.data(), m_rx.data.data()) == 0) && (m_rx.data[0] > 0 && m_rx.data[0] <= 140)) {\n                            knownLength = true;\n                            decodedLength = m_rx.data[0];\n                            //printf(\"decoded length = %d, recvDuration_frames = %d\\n\", decodedLength, m_rx.recvDuration_frames);\n\n                            const int nTotalBytesExpected = m_encodedDataOffset + decodedLength + ::getECCBytesForLength(decodedLength);\n                            const int nTotalFramesExpected = 2*m_nMarkerFrames + ((nTotalBytesExpected + protocol.bytesPerTx - 1)/protocol.bytesPerTx)*protocol.framesPerTx;\n                            if (m_rx.recvDuration_frames > nTotalFramesExpected ||\n                                m_rx.recvDuration_frames < nTotalFramesExpected - 2*m_nMarkerFrames) {\n                                //printf(\"  - invalid number of frames: %d (expected %d)\\n\", m_rx.recvDuration_frames, nTotalFramesExpected);\n                                knownLength = false;\n                                break;\n                            }\n                        } else {\n                            break;\n                        }\n                    }\n\n                    {\n                        const int nTotalBytesExpected = m_encodedDataOffset + decodedLength + ::getECCBytesForLength(decodedLength);\n                        if (knownLength && itx*protocol.bytesPerTx > nTotalBytesExpected + 1) {\n                            break;\n                        }\n                    }\n                }\n\n                if (knownLength) {\n                    RS::ReedSolomon rsData(decodedLength, ::getECCBytesForLength(decodedLength), m_workRSData.data());\n\n                    if (rsData.Decode(m_dataEncoded.data() + m_encodedDataOffset, m_rx.data.data()) == 0) {\n                        if (decodedLength > 0) {\n                            if (m_isDSSEnabled) {\n                                for (int i = 0; i < decodedLength; ++i) {\n                                    m_rx.data[i] = m_rx.data[i] ^ getDSSMagic(i);\n                                }\n                            }\n\n                            ggprintf(\"Decoded length = %d, protocol = '%s' (%d)\\n\", decodedLength, protocol.name, protocolId);\n                            ggprintf(\"Received sound data successfully: '%s'\\n\", m_rx.data.data());\n\n                            isValid = true;\n                            m_rx.hasNewRxData = true;\n                            m_rx.dataLength = decodedLength;\n                            m_rx.protocol = protocol;\n                            m_rx.protocolId = RxProtocolId(protocolId);\n                        }\n                    }\n                }\n\n                if (isValid) {\n                    break;\n                }\n                --m_rx.framesLeftToAnalyze;\n            }\n\n            if (isValid) break;\n        }\n\n        m_rx.framesToRecord = 0;\n\n        if (isValid == false) {\n            ggprintf(\"Failed to capture sound data. Please try again (length = %d)\\n\", m_rx.data[0]);\n            m_rx.dataLength = -1;\n            m_rx.framesToRecord = -1;\n        }\n\n        m_rx.receiving = false;\n        m_rx.analyzing = false;\n\n        m_rx.spectrum.zero();\n\n        m_rx.framesToAnalyze = 0;\n        m_rx.framesLeftToAnalyze = 0;\n    }\n\n    // check if receiving data\n    if (m_rx.receiving == false) {\n        bool isReceiving = false;\n\n        for (int i = 0; i < m_rx.protocols.size(); ++i) {\n            const auto & protocol = m_rx.protocols[i];\n            if (protocol.enabled == false) {\n                continue;\n            }\n\n            int nDetectedMarkerBits = m_nBitsInMarker;\n\n            for (int i = 0; i < m_nBitsInMarker; ++i) {\n                double freq = bitFreq(protocol, i);\n                int bin = round(freq*m_ihzPerSample);\n\n                if (i%2 == 0) {\n                    if (m_rx.spectrum[bin] <= m_soundMarkerThreshold*m_rx.spectrum[bin + m_freqDelta_bin]) --nDetectedMarkerBits;\n                } else {\n                    if (m_rx.spectrum[bin] >= m_soundMarkerThreshold*m_rx.spectrum[bin + m_freqDelta_bin]) --nDetectedMarkerBits;\n                }\n            }\n\n            if (nDetectedMarkerBits == m_nBitsInMarker) {\n                m_rx.markerFreqStart = protocol.freqStart;\n                isReceiving = true;\n                break;\n            }\n        }\n\n        if (isReceiving) {\n            if (++m_rx.nMarkersSuccess >= 1) {\n            } else {\n                isReceiving = false;\n            }\n        } else {\n            m_rx.nMarkersSuccess = 0;\n        }\n\n        if (isReceiving) {\n            ggprintf(\"Receiving sound data ...\\n\");\n\n            m_rx.receiving = true;\n            m_rx.data.zero();\n\n            // max recieve duration\n            m_rx.recvDuration_frames =\n                2*m_nMarkerFrames +\n                maxFramesPerTx(m_rx.protocols, true)*(\n                        (kMaxLengthVariable + ::getECCBytesForLength(kMaxLengthVariable))/minBytesPerTx(m_rx.protocols) + 1\n                        );\n\n            m_rx.nMarkersSuccess = 0;\n            m_rx.framesToRecord = m_rx.recvDuration_frames;\n            m_rx.framesLeftToRecord = m_rx.recvDuration_frames;\n        }\n    } else {\n        bool isEnded = false;\n\n        for (int i = 0; i < m_rx.protocols.size(); ++i) {\n            const auto & protocol = m_rx.protocols[i];\n            if (protocol.enabled == false) {\n                continue;\n            }\n\n            int nDetectedMarkerBits = m_nBitsInMarker;\n\n            for (int i = 0; i < m_nBitsInMarker; ++i) {\n                double freq = bitFreq(protocol, i);\n                int bin = round(freq*m_ihzPerSample);\n\n                if (i%2 == 0) {\n                    if (m_rx.spectrum[bin] >= m_soundMarkerThreshold*m_rx.spectrum[bin + m_freqDelta_bin]) nDetectedMarkerBits--;\n                } else {\n                    if (m_rx.spectrum[bin] <= m_soundMarkerThreshold*m_rx.spectrum[bin + m_freqDelta_bin]) nDetectedMarkerBits--;\n                }\n            }\n\n            if (nDetectedMarkerBits == m_nBitsInMarker) {\n                isEnded = true;\n                break;\n            }\n        }\n\n        if (isEnded) {\n            if (++m_rx.nMarkersSuccess >= 1) {\n            } else {\n                isEnded = false;\n            }\n        } else {\n            m_rx.nMarkersSuccess = 0;\n        }\n\n        if (isEnded && m_rx.framesToRecord > 1) {\n            m_rx.recvDuration_frames -= m_rx.framesLeftToRecord - 1;\n            ggprintf(\"Received end marker. Frames left = %d, recorded = %d\\n\", m_rx.framesLeftToRecord, m_rx.recvDuration_frames);\n            m_rx.nMarkersSuccess = 0;\n            m_rx.framesLeftToRecord = 1;\n        }\n    }\n}\n\n//\n// Fixed payload length\n\nvoid GGWave::decode_fixed() {\n    m_rx.hasNewSpectrum = true;\n\n    // calculate spectrum\n    FFT(m_rx.amplitude.data(), m_rx.fftOut.data(), m_samplesPerFrame, m_rx.fftWorkI.data(), m_rx.fftWorkF.data());\n\n    float amax = 0.0f;\n    for (int i = 0; i < m_samplesPerFrame; ++i) {\n        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]);\n    }\n    for (int i = 1; i < m_samplesPerFrame/2; ++i) {\n        m_rx.spectrum[i] += m_rx.spectrum[m_samplesPerFrame - i];\n        if (i >= m_rx.minFreqStart) {\n            amax = GG_MAX(amax, m_rx.spectrum[i]);\n        }\n    }\n\n    // original, floating-point version\n    //m_rx.spectrumHistoryFixed[m_rx.historyIdFixed].copy(m_rx.spectrum);\n\n    // float -> uint8_t\n    amax = 255.0f/(amax == 0.0f ? 1.0f : amax);\n    for (int i = 0; i < m_samplesPerFrame; ++i) {\n        m_rx.spectrumHistoryFixed[m_rx.historyIdFixed][i] = GG_MIN(255.0f, GG_MAX(0.0f, (float) round(m_rx.spectrum[i]*amax)));\n    }\n\n    // float -> uint16_t\n    //amax = 65535.0f/(amax == 0.0f ? 1.0f : amax);\n    //for (int i = 0; i < m_samplesPerFrame; ++i) {\n    //    m_rx.spectrumHistoryFixed[m_rx.historyIdFixed][i] = GG_MIN(65535.0f, GG_MAX(0.0f, (float) round(m_rx.spectrum[i]*amax)));\n    //}\n\n    if (++m_rx.historyIdFixed >= (int) m_rx.spectrumHistoryFixed.size()) {\n        m_rx.historyIdFixed = 0;\n    }\n\n    bool isValid = false;\n    for (int protocolId = 0; protocolId < (int) m_rx.protocols.size(); ++protocolId) {\n        const auto & protocol = m_rx.protocols[protocolId];\n        if (protocol.enabled == false) {\n            continue;\n        }\n\n        const int binStart = protocol.freqStart;\n        const int binDelta = 16;\n        const int binOffset = protocol.extra == 1 ? binDelta : 0;\n\n        if (binStart > m_samplesPerFrame) {\n            continue;\n        }\n\n        const int totalLength = m_payloadLength + getECCBytesForLength(m_payloadLength);\n        const int totalTxs = protocol.extra*((totalLength + protocol.bytesPerTx - 1)/protocol.bytesPerTx);\n\n        int historyStartId = m_rx.historyIdFixed - totalTxs*protocol.framesPerTx;\n        if (historyStartId < 0) {\n            historyStartId += m_rx.spectrumHistoryFixed.size();\n        }\n\n        const int nTones = 2*protocol.bytesPerTx;\n        m_rx.detectedBins.zero();\n\n        int txNeededTotal   = 0;\n        int txDetectedTotal = 0;\n        bool detectedSignal = true;\n\n        for (int k = 0; k < totalTxs; ++k) {\n            if (k % protocol.extra == 0) {\n                m_rx.detectedTones.zero(16*nTones);\n            }\n\n            for (int i = 0; i < protocol.framesPerTx; ++i) {\n                int historyId = historyStartId + k*protocol.framesPerTx + i;\n                if (historyId >= (int) m_rx.spectrumHistoryFixed.size()) {\n                    historyId -= m_rx.spectrumHistoryFixed.size();\n                }\n\n                for (int j = 0; j < protocol.bytesPerTx; ++j) {\n                    int f0bin = 0;\n                    auto f0max = m_rx.spectrumHistoryFixed[historyId][binStart + 2*j*binDelta];\n\n                    for (int b = 1; b < 16; ++b) {\n                        {\n                            const auto & v = m_rx.spectrumHistoryFixed[historyId][binStart + 2*j*binDelta + b];\n\n                            if (f0max <= v) {\n                                f0max = v;\n                                f0bin = b;\n                            }\n                        }\n                    }\n\n                    int f1bin = 0;\n                    if (protocol.extra == 1) {\n                        auto f1max = m_rx.spectrumHistoryFixed[historyId][binStart + 2*j*binDelta + binOffset];\n                        for (int b = 1; b < 16; ++b) {\n                            const auto & v = m_rx.spectrumHistoryFixed[historyId][binStart + 2*j*binDelta + binOffset + b];\n\n                            if (f1max <= v) {\n                                f1max = v;\n                                f1bin = b;\n                            }\n                        }\n                    } else {\n                        f1bin = f0bin;\n                    }\n\n                    if ((k + 0)%protocol.extra == 0) m_rx.detectedTones[(2*j + 0)*16 + f0bin]++;\n                    if ((k + 1)%protocol.extra == 0) m_rx.detectedTones[(2*j + 1)*16 + f1bin]++;\n                }\n            }\n\n            if (protocol.extra > 1 && (k % protocol.extra == 0)) continue;\n\n            int txNeeded = 0;\n            int txDetected = 0;\n            for (int j = 0; j < protocol.bytesPerTx; ++j) {\n                if ((k/protocol.extra)*protocol.bytesPerTx + j >= totalLength) break;\n                txNeeded += 2;\n                for (int b = 0; b < 16; ++b) {\n                    if (m_rx.detectedTones[(2*j + 0)*16 + b] > protocol.framesPerTx/2) {\n                        m_rx.detectedBins[2*((k/protocol.extra)*protocol.bytesPerTx + j) + 0] = b;\n                        txDetected++;\n                    }\n                    if (m_rx.detectedTones[(2*j + 1)*16 + b] > protocol.framesPerTx/2) {\n                        m_rx.detectedBins[2*((k/protocol.extra)*protocol.bytesPerTx + j) + 1] = b;\n                        txDetected++;\n                    }\n                }\n            }\n\n            txDetectedTotal += txDetected;\n            txNeededTotal += txNeeded;\n        }\n\n        if (txDetectedTotal < 0.75*txNeededTotal) {\n            detectedSignal = false;\n        }\n\n        if (detectedSignal) {\n            RS::ReedSolomon rsData(m_payloadLength, getECCBytesForLength(m_payloadLength), m_workRSData.data());\n\n            for (int j = 0; j < totalLength; ++j) {\n                m_dataEncoded[j] = (m_rx.detectedBins[2*j + 1] << 4) + m_rx.detectedBins[2*j + 0];\n            }\n\n            if (rsData.Decode(m_dataEncoded.data(), m_rx.data.data()) == 0) {\n                if (m_isDSSEnabled) {\n                    for (int i = 0; i < m_payloadLength; ++i) {\n                        m_rx.data[i] = m_rx.data[i] ^ getDSSMagic(i);\n                    }\n                }\n\n                ggprintf(\"Decoded length = %d, protocol = '%s' (%d)\\n\", m_payloadLength, protocol.name, protocolId);\n                ggprintf(\"Received sound data successfully: '%s'\\n\", m_rx.data.data());\n\n                isValid = true;\n                m_rx.hasNewRxData = true;\n                m_rx.dataLength = m_payloadLength;\n                m_rx.protocol = protocol;\n                m_rx.protocolId = RxProtocolId(protocolId);\n            }\n        }\n\n        if (isValid) {\n            break;\n        }\n    }\n}\n\nint GGWave::maxFramesPerTx(const Protocols & protocols, bool excludeMT) const {\n    int res = 0;\n    for (int i = 0; i < protocols.size(); ++i) {\n        const auto & protocol = protocols[i];\n        if (protocol.enabled == false) {\n            continue;\n        }\n        if (excludeMT && protocol.extra > 1) {\n            continue;\n        }\n        res = GG_MAX(res, protocol.framesPerTx*protocol.extra);\n    }\n    return res;\n}\n\nint GGWave::minBytesPerTx(const Protocols & protocols) const {\n    int res = 1;\n    for (int i = 0; i < protocols.size(); ++i) {\n        const auto & protocol = protocols[i];\n        if (protocol.enabled == false) {\n            continue;\n        }\n        res = GG_MIN(res, (int) protocol.bytesPerTx);\n    }\n    return res;\n}\n\nint GGWave::maxBytesPerTx(const Protocols & protocols) const {\n    int res = 1;\n    for (int i = 0; i < protocols.size(); ++i) {\n        const auto & protocol = protocols[i];\n        if (protocol.enabled == false) {\n            continue;\n        }\n        res = GG_MAX(res, (int) protocol.bytesPerTx);\n    }\n    return res;\n}\n\nint GGWave::maxTonesPerTx(const Protocols & protocols) const {\n    int res = 1;\n    for (int i = 0; i < protocols.size(); ++i) {\n        const auto & protocol = protocols[i];\n        if (protocol.enabled == false) {\n            continue;\n        }\n        res = GG_MAX(res, protocol.nTones());\n    }\n    return res;\n}\n\nint GGWave::minFreqStart(const Protocols & protocols) const {\n    int res = m_samplesPerFrame;\n    for (int i = 0; i < protocols.size(); ++i) {\n        const auto & protocol = protocols[i];\n        if (protocol.enabled == false) {\n            continue;\n        }\n        res = GG_MIN(res, protocol.freqStart);\n    }\n    return res;\n}\n\ndouble GGWave::bitFreq(const Protocol & p, int bit) const {\n    return m_hzPerSample*p.freqStart + m_freqDelta_hz*bit;\n}\n"
  },
  {
    "path": "src/reed-solomon/LICENSE",
    "content": "Copyright © 2015 Mike Lubinets, github.com/mersinvald\n\nPermission is hereby granted, free of charge, to any person \nobtaining a copy of this software and associated documentation files \n(the “Software”), to deal in the Software without restriction, \nincluding without limitation the rights to use, copy, modify, merge, \npublish, distribute, sublicense, and/or sell copies of the Software, \nand to permit persons to whom the Software is furnished to do so, \nsubject to the following conditions:\n\nThe above copyright notice and this permission notice shall be \nincluded in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, \nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF \nMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND \nNONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS \nBE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN \nACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN \nCONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE \nSOFTWARE.\n"
  },
  {
    "path": "src/reed-solomon/gf.hpp",
    "content": "/* Author: Mike Lubinets (aka mersinvald)\r\n * Date: 29.12.15\r\n *\r\n * See LICENSE */\r\n\r\n#ifndef GF_H\r\n#define GF_H\r\n\r\n#include \"poly.hpp\"\r\n\r\n#include <stdint.h>\r\n#include <string.h>\r\n#include <assert.h>\r\n\r\nnamespace RS {\r\n\r\nnamespace gf {\r\n\r\n\r\n/* GF tables pre-calculated for 0x11d primitive polynomial */\r\n\r\nconst uint8_t exp[512] PROGMEM = {\r\n    0x1, 0x2, 0x4, 0x8, 0x10, 0x20, 0x40, 0x80, 0x1d, 0x3a, 0x74, 0xe8, 0xcd, 0x87, 0x13, 0x26, 0x4c,\r\n    0x98, 0x2d, 0x5a, 0xb4, 0x75, 0xea, 0xc9, 0x8f, 0x3, 0x6, 0xc, 0x18, 0x30, 0x60, 0xc0, 0x9d,\r\n    0x27, 0x4e, 0x9c, 0x25, 0x4a, 0x94, 0x35, 0x6a, 0xd4, 0xb5, 0x77, 0xee, 0xc1, 0x9f, 0x23, 0x46,\r\n    0x8c, 0x5, 0xa, 0x14, 0x28, 0x50, 0xa0, 0x5d, 0xba, 0x69, 0xd2, 0xb9, 0x6f, 0xde, 0xa1, 0x5f,\r\n    0xbe, 0x61, 0xc2, 0x99, 0x2f, 0x5e, 0xbc, 0x65, 0xca, 0x89, 0xf, 0x1e, 0x3c, 0x78, 0xf0, 0xfd,\r\n    0xe7, 0xd3, 0xbb, 0x6b, 0xd6, 0xb1, 0x7f, 0xfe, 0xe1, 0xdf, 0xa3, 0x5b, 0xb6, 0x71, 0xe2, 0xd9,\r\n    0xaf, 0x43, 0x86, 0x11, 0x22, 0x44, 0x88, 0xd, 0x1a, 0x34, 0x68, 0xd0, 0xbd, 0x67, 0xce, 0x81,\r\n    0x1f, 0x3e, 0x7c, 0xf8, 0xed, 0xc7, 0x93, 0x3b, 0x76, 0xec, 0xc5, 0x97, 0x33, 0x66, 0xcc, 0x85,\r\n    0x17, 0x2e, 0x5c, 0xb8, 0x6d, 0xda, 0xa9, 0x4f, 0x9e, 0x21, 0x42, 0x84, 0x15, 0x2a, 0x54, 0xa8,\r\n    0x4d, 0x9a, 0x29, 0x52, 0xa4, 0x55, 0xaa, 0x49, 0x92, 0x39, 0x72, 0xe4, 0xd5, 0xb7, 0x73, 0xe6,\r\n    0xd1, 0xbf, 0x63, 0xc6, 0x91, 0x3f, 0x7e, 0xfc, 0xe5, 0xd7, 0xb3, 0x7b, 0xf6, 0xf1, 0xff, 0xe3,\r\n    0xdb, 0xab, 0x4b, 0x96, 0x31, 0x62, 0xc4, 0x95, 0x37, 0x6e, 0xdc, 0xa5, 0x57, 0xae, 0x41, 0x82,\r\n    0x19, 0x32, 0x64, 0xc8, 0x8d, 0x7, 0xe, 0x1c, 0x38, 0x70, 0xe0, 0xdd, 0xa7, 0x53, 0xa6, 0x51,\r\n    0xa2, 0x59, 0xb2, 0x79, 0xf2, 0xf9, 0xef, 0xc3, 0x9b, 0x2b, 0x56, 0xac, 0x45, 0x8a, 0x9, 0x12,\r\n    0x24, 0x48, 0x90, 0x3d, 0x7a, 0xf4, 0xf5, 0xf7, 0xf3, 0xfb, 0xeb, 0xcb, 0x8b, 0xb, 0x16, 0x2c,\r\n    0x58, 0xb0, 0x7d, 0xfa, 0xe9, 0xcf, 0x83, 0x1b, 0x36, 0x6c, 0xd8, 0xad, 0x47, 0x8e, 0x1, 0x2,\r\n    0x4, 0x8, 0x10, 0x20, 0x40, 0x80, 0x1d, 0x3a, 0x74, 0xe8, 0xcd, 0x87, 0x13, 0x26, 0x4c, 0x98,\r\n    0x2d, 0x5a, 0xb4, 0x75, 0xea, 0xc9, 0x8f, 0x3, 0x6, 0xc, 0x18, 0x30, 0x60, 0xc0, 0x9d, 0x27,\r\n    0x4e, 0x9c, 0x25, 0x4a, 0x94, 0x35, 0x6a, 0xd4, 0xb5, 0x77, 0xee, 0xc1, 0x9f, 0x23, 0x46, 0x8c,\r\n    0x5, 0xa, 0x14, 0x28, 0x50, 0xa0, 0x5d, 0xba, 0x69, 0xd2, 0xb9, 0x6f, 0xde, 0xa1, 0x5f, 0xbe,\r\n    0x61, 0xc2, 0x99, 0x2f, 0x5e, 0xbc, 0x65, 0xca, 0x89, 0xf, 0x1e, 0x3c, 0x78, 0xf0, 0xfd, 0xe7,\r\n    0xd3, 0xbb, 0x6b, 0xd6, 0xb1, 0x7f, 0xfe, 0xe1, 0xdf, 0xa3, 0x5b, 0xb6, 0x71, 0xe2, 0xd9, 0xaf,\r\n    0x43, 0x86, 0x11, 0x22, 0x44, 0x88, 0xd, 0x1a, 0x34, 0x68, 0xd0, 0xbd, 0x67, 0xce, 0x81, 0x1f,\r\n    0x3e, 0x7c, 0xf8, 0xed, 0xc7, 0x93, 0x3b, 0x76, 0xec, 0xc5, 0x97, 0x33, 0x66, 0xcc, 0x85, 0x17,\r\n    0x2e, 0x5c, 0xb8, 0x6d, 0xda, 0xa9, 0x4f, 0x9e, 0x21, 0x42, 0x84, 0x15, 0x2a, 0x54, 0xa8, 0x4d,\r\n    0x9a, 0x29, 0x52, 0xa4, 0x55, 0xaa, 0x49, 0x92, 0x39, 0x72, 0xe4, 0xd5, 0xb7, 0x73, 0xe6, 0xd1,\r\n    0xbf, 0x63, 0xc6, 0x91, 0x3f, 0x7e, 0xfc, 0xe5, 0xd7, 0xb3, 0x7b, 0xf6, 0xf1, 0xff, 0xe3, 0xdb,\r\n    0xab, 0x4b, 0x96, 0x31, 0x62, 0xc4, 0x95, 0x37, 0x6e, 0xdc, 0xa5, 0x57, 0xae, 0x41, 0x82, 0x19,\r\n    0x32, 0x64, 0xc8, 0x8d, 0x7, 0xe, 0x1c, 0x38, 0x70, 0xe0, 0xdd, 0xa7, 0x53, 0xa6, 0x51, 0xa2,\r\n    0x59, 0xb2, 0x79, 0xf2, 0xf9, 0xef, 0xc3, 0x9b, 0x2b, 0x56, 0xac, 0x45, 0x8a, 0x9, 0x12, 0x24,\r\n    0x48, 0x90, 0x3d, 0x7a, 0xf4, 0xf5, 0xf7, 0xf3, 0xfb, 0xeb, 0xcb, 0x8b, 0xb, 0x16, 0x2c, 0x58,\r\n    0xb0, 0x7d, 0xfa, 0xe9, 0xcf, 0x83, 0x1b, 0x36, 0x6c, 0xd8, 0xad, 0x47, 0x8e, 0x1, 0x2\r\n};\r\n\r\nconst uint8_t log[256] PROGMEM = {\r\n    0x0, 0x0, 0x1, 0x19, 0x2, 0x32, 0x1a, 0xc6, 0x3, 0xdf, 0x33, 0xee, 0x1b, 0x68, 0xc7, 0x4b, 0x4,\r\n    0x64, 0xe0, 0xe, 0x34, 0x8d, 0xef, 0x81, 0x1c, 0xc1, 0x69, 0xf8, 0xc8, 0x8, 0x4c, 0x71, 0x5,\r\n    0x8a, 0x65, 0x2f, 0xe1, 0x24, 0xf, 0x21, 0x35, 0x93, 0x8e, 0xda, 0xf0, 0x12, 0x82, 0x45, 0x1d,\r\n    0xb5, 0xc2, 0x7d, 0x6a, 0x27, 0xf9, 0xb9, 0xc9, 0x9a, 0x9, 0x78, 0x4d, 0xe4, 0x72, 0xa6, 0x6,\r\n    0xbf, 0x8b, 0x62, 0x66, 0xdd, 0x30, 0xfd, 0xe2, 0x98, 0x25, 0xb3, 0x10, 0x91, 0x22, 0x88, 0x36,\r\n    0xd0, 0x94, 0xce, 0x8f, 0x96, 0xdb, 0xbd, 0xf1, 0xd2, 0x13, 0x5c, 0x83, 0x38, 0x46, 0x40, 0x1e,\r\n    0x42, 0xb6, 0xa3, 0xc3, 0x48, 0x7e, 0x6e, 0x6b, 0x3a, 0x28, 0x54, 0xfa, 0x85, 0xba, 0x3d, 0xca,\r\n    0x5e, 0x9b, 0x9f, 0xa, 0x15, 0x79, 0x2b, 0x4e, 0xd4, 0xe5, 0xac, 0x73, 0xf3, 0xa7, 0x57, 0x7,\r\n    0x70, 0xc0, 0xf7, 0x8c, 0x80, 0x63, 0xd, 0x67, 0x4a, 0xde, 0xed, 0x31, 0xc5, 0xfe, 0x18, 0xe3,\r\n    0xa5, 0x99, 0x77, 0x26, 0xb8, 0xb4, 0x7c, 0x11, 0x44, 0x92, 0xd9, 0x23, 0x20, 0x89, 0x2e, 0x37,\r\n    0x3f, 0xd1, 0x5b, 0x95, 0xbc, 0xcf, 0xcd, 0x90, 0x87, 0x97, 0xb2, 0xdc, 0xfc, 0xbe, 0x61, 0xf2,\r\n    0x56, 0xd3, 0xab, 0x14, 0x2a, 0x5d, 0x9e, 0x84, 0x3c, 0x39, 0x53, 0x47, 0x6d, 0x41, 0xa2, 0x1f,\r\n    0x2d, 0x43, 0xd8, 0xb7, 0x7b, 0xa4, 0x76, 0xc4, 0x17, 0x49, 0xec, 0x7f, 0xc, 0x6f, 0xf6, 0x6c,\r\n    0xa1, 0x3b, 0x52, 0x29, 0x9d, 0x55, 0xaa, 0xfb, 0x60, 0x86, 0xb1, 0xbb, 0xcc, 0x3e, 0x5a, 0xcb,\r\n    0x59, 0x5f, 0xb0, 0x9c, 0xa9, 0xa0, 0x51, 0xb, 0xf5, 0x16, 0xeb, 0x7a, 0x75, 0x2c, 0xd7, 0x4f,\r\n    0xae, 0xd5, 0xe9, 0xe6, 0xe7, 0xad, 0xe8, 0x74, 0xd6, 0xf4, 0xea, 0xa8, 0x50, 0x58, 0xaf\r\n};\r\n\r\n\r\n\r\n/* ################################\r\n * # OPERATIONS OVER GALUA FIELDS #\r\n * ################################ */\r\n\r\n/* @brief Addition in Galua Fields\r\n * @param x - left operand\r\n * @param y - right operand\r\n * @return x + y */\r\ninline uint8_t add(uint8_t x, uint8_t y) {\r\n    return x^y;\r\n}\r\n\r\n/* ##### GF substraction ###### */\r\n/* @brief Substraction in Galua Fields\r\n * @param x - left operand\r\n * @param y - right operand\r\n * @return x - y */\r\ninline uint8_t sub(uint8_t x, uint8_t y) {\r\n    return x^y;\r\n}\r\n\r\n/* @brief Multiplication in Galua Fields\r\n * @param x - left operand\r\n * @param y - rifht operand\r\n * @return x * y */\r\ninline uint8_t mul(uint16_t x, uint16_t y){\r\n    if (x == 0 || y == 0)\r\n        return 0;\r\n#ifdef ARDUINO\r\n    return pgm_read_byte(exp + pgm_read_byte(log + x) + pgm_read_byte(log + y));\r\n#else\r\n    return exp[log[x] + log[y]];\r\n#endif\r\n}\r\n\r\n/* @brief Division in Galua Fields\r\n * @param x - dividend\r\n * @param y - divisor\r\n * @return x / y */\r\ninline uint8_t div(uint8_t x, uint8_t y){\r\n    assert(y != 0);\r\n    if(x == 0) return 0;\r\n#ifdef ARDUINO\r\n    return pgm_read_byte(exp + (pgm_read_byte(log + x) + 255 - pgm_read_byte(log + y)) % 255);\r\n#else\r\n    return exp[(log[x] + 255 - log[y]) % 255];\r\n#endif\r\n}\r\n\r\n/* @brief X in power Y w\r\n * @param x     - operand\r\n * @param power - power\r\n * @return x^power */\r\ninline uint8_t pow(uint8_t x, intmax_t power){\r\n#ifdef ARDUINO\r\n    intmax_t i = pgm_read_byte(log + x);\r\n#else\r\n    intmax_t i = log[x];\r\n#endif\r\n    i *= power;\r\n    i %= 255;\r\n    if(i < 0) i = i + 255;\r\n#ifdef ARDUINO\r\n    return pgm_read_byte(exp + i);\r\n#else\r\n    return exp[i];\r\n#endif\r\n}\r\n\r\n/* @brief Inversion in Galua Fields\r\n * @param x - number\r\n * @return inversion of x */\r\ninline uint8_t inverse(uint8_t x){\r\n#ifdef ARDUINO\r\n    return pgm_read_byte(exp + 255 - pgm_read_byte(log + x)); /* == div(1, x); */\r\n#else\r\n    return exp[255 - log[x]]; /* == div(1, x); */\r\n#endif\r\n}\r\n\r\n/* ##########################\r\n * # POLYNOMIALS OPERATIONS #\r\n * ########################## */\r\n\r\n/* @brief Multiplication polynomial by scalar\r\n * @param &p    - source polynomial\r\n * @param &newp - destination polynomial\r\n * @param x     - scalar */\r\ninline void\r\npoly_scale(const Poly *p, Poly *newp, uint16_t x) {\r\n    newp->length = p->length;\r\n    for(uint16_t i = 0; i < p->length; i++){\r\n        newp->at(i) = mul(p->at(i), x);\r\n    }\r\n}\r\n\r\n/* @brief Addition of two polynomials\r\n * @param &p    - right operand polynomial\r\n * @param &q    - left operand polynomial\r\n * @param &newp - destination polynomial */\r\ninline void\r\npoly_add(const Poly *p, const Poly *q, Poly *newp) {\r\n    newp->length = poly_max(p->length, q->length);\r\n    memset(newp->ptr(), 0, newp->length * sizeof(uint8_t));\r\n\r\n    for(uint8_t i = 0; i < p->length; i++){\r\n        newp->at(i + newp->length - p->length) = p->at(i);\r\n    }\r\n\r\n    for(uint8_t i = 0; i < q->length; i++){\r\n        newp->at(i + newp->length - q->length) ^= q->at(i);\r\n    }\r\n}\r\n\r\n\r\n/* @brief Multiplication of two polynomials\r\n * @param &p    - right operand polynomial\r\n * @param &q    - left operand polynomial\r\n * @param &newp - destination polynomial */\r\ninline void\r\npoly_mul(const Poly *p, const Poly *q, Poly *newp) {\r\n    newp->length = p->length + q->length - 1;\r\n    memset(newp->ptr(), 0, newp->length * sizeof(uint8_t));\r\n    /* Compute the polynomial multiplication (just like the outer product of two vectors,\r\n     * we multiply each coefficients of p with all coefficients of q) */\r\n    for(uint8_t j = 0; j < q->length; j++){\r\n        for(uint8_t i = 0; i < p->length; i++){\r\n            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])) */\r\n        }\r\n    }\r\n}\r\n\r\n/* @brief Division of two polynomials\r\n * @param &p    - right operand polynomial\r\n * @param &q    - left operand polynomial\r\n * @param &newp - destination polynomial */\r\ninline void\r\npoly_div(const Poly *p, const Poly *q, Poly *newp) {\r\n    if(p->ptr() != newp->ptr()) {\r\n        memcpy(newp->ptr(), p->ptr(), p->length*sizeof(uint8_t));\r\n    }\r\n\r\n    newp->length = p->length;\r\n\r\n    uint8_t coef;\r\n\r\n    for(int i = 0; i < (p->length-(q->length-1)); i++){\r\n        coef = newp->at(i);\r\n        if(coef != 0){\r\n            for(uint8_t j = 1; j < q->length; j++){\r\n                if(q->at(j) != 0)\r\n                    newp->at(i+j) ^= mul(q->at(j), coef);\r\n            }\r\n        }\r\n    }\r\n\r\n    size_t sep = p->length-(q->length-1);\r\n    memmove(newp->ptr(), newp->ptr()+sep, (newp->length-sep) * sizeof(uint8_t));\r\n    newp->length = newp->length-sep;\r\n}\r\n\r\n/* @brief Evaluation of polynomial in x\r\n * @param &p - polynomial to evaluate\r\n * @param x  - evaluation point */\r\ninline int8_t\r\npoly_eval(const Poly *p, uint16_t x) {\r\n    uint8_t y = p->at(0);\r\n    for(uint8_t i = 1; i < p->length; i++){\r\n        y = mul(y, x) ^ p->at(i);\r\n    }\r\n    return y;\r\n}\r\n\r\n} /* end of gf namespace */\r\n\r\n}\r\n#endif // GF_H\r\n\r\n"
  },
  {
    "path": "src/reed-solomon/poly.hpp",
    "content": "/* Author: Mike Lubinets (aka mersinvald)\r\n * Date: 29.12.15\r\n *\r\n * See LICENSE */\r\n\r\n#ifndef POLY_H\r\n#define POLY_H\r\n\r\n#include <stdint.h>\r\n#include <string.h>\r\n#include <assert.h>\r\n\r\nnamespace RS {\r\n\r\nstruct Poly {\r\n    Poly()\r\n        : length(0), _memory(NULL) {}\r\n\r\n    Poly(uint8_t id, uint16_t offset, uint8_t size) \\\r\n        : length(0), _id(id), _size(size), _offset(offset), _memory(NULL) {}\r\n\r\n    /* @brief Append number at the end of polynomial\r\n     * @param num - number to append\r\n     * @return false if polynomial can't be stretched */\r\n    inline bool Append(uint8_t num) {\r\n        assert(length < _size);\r\n        ptr()[length++] = num;\r\n        return true;\r\n    }\r\n\r\n    /* @brief Polynomial initialization */\r\n    inline void Init(uint8_t id, uint16_t offset, uint8_t size, uint8_t** memory_ptr) {\r\n        this->_id     = id;\r\n        this->_offset = offset;\r\n        this->_size   = size;\r\n        this->length  = 0;\r\n        this->_memory = memory_ptr;\r\n    }\r\n\r\n    /* @brief Polynomial memory zeroing */\r\n    inline void Reset() {\r\n        memset((void*)ptr(), 0, this->_size);\r\n    }\r\n\r\n    /* @brief Copy polynomial to memory\r\n     * @param src    - source byte-sequence\r\n     * @param size   - size of polynomial\r\n     * @param offset - write offset */\r\n    inline void Set(const uint8_t* src, uint8_t len, uint8_t offset = 0) {\r\n        assert(src && len <= this->_size-offset);\r\n        memcpy(ptr()+offset, src, len * sizeof(uint8_t));\r\n        length = len + offset;\r\n    }\r\n\r\n    #define poly_max(a, b) ((a > b) ? (a) : (b))\r\n\r\n    inline void Copy(const Poly* src) {\r\n        length = poly_max(length, src->length);\r\n        Set(src->ptr(), length);\r\n    }\r\n\r\n    inline uint8_t& at(uint8_t i) const {\r\n        assert(i < _size);\r\n        return ptr()[i];\r\n    }\r\n\r\n    inline uint8_t id() const {\r\n        return _id;\r\n    }\r\n\r\n    inline uint8_t size() const {\r\n        return _size;\r\n    }\r\n\r\n    // Returns pointer to memory of this polynomial\r\n    inline uint8_t* ptr() const {\r\n        assert(_memory && *_memory);\r\n        return (*_memory) + _offset;\r\n    }\r\n\r\n    uint8_t length;\r\n\r\nprotected:\r\n\r\n    uint8_t   _id;\r\n    uint8_t   _size;    // Size of reserved memory for this polynomial\r\n    uint16_t  _offset;  // Offset in memory\r\n    uint8_t** _memory;  // Pointer to pointer to memory\r\n};\r\n\r\n\r\n}\r\n\r\n#endif // POLY_H\r\n"
  },
  {
    "path": "src/reed-solomon/rs.hpp",
    "content": "/* Author: Mike Lubinets (aka mersinvald)\r\n * Date: 29.12.15\r\n *\r\n * See LICENSE */\r\n\r\n#ifndef RS_HPP\r\n#define RS_HPP\r\n\r\n#include \"poly.hpp\"\r\n#include \"gf.hpp\"\r\n\r\n#include <assert.h>\r\n#include <string.h>\r\n#include <stdint.h>\r\n#include <stdlib.h>\r\n\r\nnamespace RS {\r\n\r\n#define MSG_CNT 3   // message-length polynomials count\r\n#define POLY_CNT 14 // (ecc_length*2)-length polynomialc count\r\n\r\nclass ReedSolomon {\r\npublic:\r\n    const uint8_t msg_length;\r\n    const uint8_t ecc_length;\r\n\r\n    uint8_t * heap_memory = nullptr;\r\n    uint8_t * generator_cache = nullptr;\r\n    bool owns_heap_memory = false;\r\n    bool generator_cached = false;\r\n\r\n    // used to pre-allocate a memory buffer for the Reed-Solomon class in order to avoid memory allocations\r\n    static size_t getWorkSize_bytes(uint8_t msg_length, uint8_t ecc_length) {\r\n        return ecc_length + 1 + MSG_CNT * msg_length + POLY_CNT * ecc_length * 2;\r\n    }\r\n\r\n    ReedSolomon(uint8_t msg_length_p, uint8_t ecc_length_p, uint8_t * heap_memory_p = nullptr) :\r\n        msg_length(msg_length_p), ecc_length(ecc_length_p) {\r\n        if (heap_memory_p) {\r\n            heap_memory = heap_memory_p;\r\n            owns_heap_memory = false;\r\n        } else {\r\n            heap_memory = (uint8_t *) malloc(getWorkSize_bytes(msg_length, ecc_length));\r\n            owns_heap_memory = true;\r\n        }\r\n        generator_cache = heap_memory;\r\n\r\n        const uint8_t   enc_len  = msg_length + ecc_length;\r\n        const uint8_t   poly_len = ecc_length * 2;\r\n        uint8_t** memptr   = &memory;\r\n        uint16_t  offset   = 0;\r\n\r\n        /* Initialize first six polys manually cause their amount depends on template parameters */\r\n\r\n        polynoms[0].Init(ID_MSG_IN, offset, enc_len, memptr);\r\n        offset += enc_len;\r\n\r\n        polynoms[1].Init(ID_MSG_OUT, offset, enc_len, memptr);\r\n        offset += enc_len;\r\n\r\n        for(uint8_t i = ID_GENERATOR; i < ID_MSG_E; i++) {\r\n            polynoms[i].Init(i, offset, poly_len, memptr);\r\n            offset += poly_len;\r\n        }\r\n\r\n        polynoms[5].Init(ID_MSG_E, offset, enc_len, memptr);\r\n        offset += enc_len;\r\n\r\n        for(uint8_t i = ID_TPOLY3; i < ID_ERR_EVAL+2; i++) {\r\n            polynoms[i].Init(i, offset, poly_len, memptr);\r\n            offset += poly_len;\r\n        }\r\n    }\r\n\r\n    ~ReedSolomon() {\r\n        if (owns_heap_memory) {\r\n            delete[] heap_memory;\r\n        }\r\n        // Dummy destructor, gcc-generated one crashes programm\r\n        memory = NULL;\r\n    }\r\n\r\n    /* @brief Message block encoding\r\n     * @param *src - input message buffer      (msg_lenth size)\r\n     * @param *dst - output buffer for ecc     (ecc_length size at least) */\r\n     void EncodeBlock(const void* src, void* dst) {\r\n        assert(msg_length + ecc_length < 256);\r\n\r\n        ///* Allocating memory on stack for polynomials storage */\r\n        //uint8_t stack_memory[MSG_CNT * msg_length + POLY_CNT * ecc_length * 2];\r\n        //this->memory = stack_memory;\r\n\r\n        // gg : allocation is now on the heap\r\n        this->memory = heap_memory + ecc_length + 1;\r\n\r\n        const uint8_t* src_ptr = (const uint8_t*) src;\r\n        uint8_t* dst_ptr = (uint8_t*) dst;\r\n\r\n        Poly *msg_in  = &polynoms[ID_MSG_IN];\r\n        Poly *msg_out = &polynoms[ID_MSG_OUT];\r\n        Poly *gen     = &polynoms[ID_GENERATOR];\r\n\r\n        // Weird shit, but without reseting msg_in it simply doesn't work\r\n        msg_in->Reset();\r\n        msg_out->Reset();\r\n\r\n        // Using cached generator or generating new one\r\n        if(generator_cached) {\r\n            gen->Set(generator_cache, ecc_length + 1);\r\n        } else {\r\n            GeneratorPoly();\r\n            memcpy(generator_cache, gen->ptr(), gen->length);\r\n            generator_cached = true;\r\n        }\r\n\r\n        // Copying input message to internal polynomial\r\n        msg_in->Set(src_ptr, msg_length);\r\n        msg_out->Set(src_ptr, msg_length);\r\n        msg_out->length = msg_in->length + ecc_length;\r\n\r\n        // Here all the magic happens\r\n        uint8_t coef = 0; // cache\r\n        for(uint8_t i = 0; i < msg_length; i++){\r\n            coef = msg_out->at(i);\r\n            if(coef != 0){\r\n                for(uint32_t j = 1; j < gen->length; j++){\r\n                    msg_out->at(i+j) ^= gf::mul(gen->at(j), coef);\r\n                }\r\n            }\r\n        }\r\n\r\n        // Copying ECC to the output buffer\r\n        memcpy(dst_ptr, msg_out->ptr()+msg_length, ecc_length * sizeof(uint8_t));\r\n    }\r\n\r\n    /* @brief Message encoding\r\n     * @param *src - input message buffer      (msg_lenth size)\r\n     * @param *dst - output buffer             (msg_length + ecc_length size at least) */\r\n    void Encode(const void* src, void* dst) {\r\n        uint8_t* dst_ptr = (uint8_t*) dst;\r\n\r\n        // Copying message to the output buffer\r\n        memcpy(dst_ptr, src, msg_length * sizeof(uint8_t));\r\n\r\n        // Calling EncodeBlock to write ecc to out[ut buffer\r\n        EncodeBlock(src, dst_ptr+msg_length);\r\n    }\r\n\r\n    /* @brief Message block decoding\r\n     * @param *src         - encoded message buffer   (msg_length size)\r\n     * @param *ecc         - ecc buffer               (ecc_length size)\r\n     * @param *msg_out     - output buffer            (msg_length size at least)\r\n     * @param *erase_pos   - known errors positions\r\n     * @param erase_count  - count of known errors\r\n     * @return RESULT_SUCCESS if successfull, error code otherwise */\r\n     int DecodeBlock(const void* src, const void* ecc, void* dst, uint8_t* erase_pos = NULL, size_t erase_count = 0) {\r\n        assert(msg_length + ecc_length < 256);\r\n\r\n        const uint8_t *src_ptr = (const uint8_t*) src;\r\n        const uint8_t *ecc_ptr = (const uint8_t*) ecc;\r\n        uint8_t *dst_ptr = (uint8_t*) dst;\r\n\r\n        const uint8_t src_len = msg_length + ecc_length;\r\n        const uint8_t dst_len = msg_length;\r\n\r\n        bool ok;\r\n\r\n        ///* Allocation memory on stack  */\r\n        //uint8_t stack_memory[MSG_CNT * msg_length + POLY_CNT * ecc_length * 2];\r\n        //this->memory = stack_memory;\r\n\r\n        // gg : allocation is now on the heap\r\n        this->memory = heap_memory + ecc_length + 1;\r\n\r\n        Poly *msg_in  = &polynoms[ID_MSG_IN];\r\n        Poly *msg_out = &polynoms[ID_MSG_OUT];\r\n        Poly *epos    = &polynoms[ID_ERASURES];\r\n\r\n        // Copying message to polynomials memory\r\n        msg_in->Set(src_ptr, msg_length);\r\n        msg_in->Set(ecc_ptr, ecc_length, msg_length);\r\n        msg_out->Copy(msg_in);\r\n\r\n        // Copying known errors to polynomial\r\n        if(erase_pos == NULL) {\r\n            epos->length = 0;\r\n        } else {\r\n            epos->Set(erase_pos, erase_count);\r\n            for(uint8_t i = 0; i < epos->length; i++){\r\n                msg_in->at(epos->at(i)) = 0;\r\n            }\r\n        }\r\n\r\n        // Too many errors\r\n        if(epos->length > ecc_length) return 1;\r\n\r\n        Poly *synd   = &polynoms[ID_SYNDROMES];\r\n        Poly *eloc   = &polynoms[ID_ERRORS_LOC];\r\n        Poly *reloc  = &polynoms[ID_TPOLY1];\r\n        Poly *err    = &polynoms[ID_ERRORS];\r\n        Poly *forney = &polynoms[ID_FORNEY];\r\n\r\n        // Calculating syndrome\r\n        CalcSyndromes(msg_in);\r\n\r\n        // Checking for errors\r\n        bool has_errors = false;\r\n        for(uint8_t i = 0; i < synd->length; i++) {\r\n            if(synd->at(i) != 0) {\r\n                has_errors = true;\r\n                break;\r\n            }\r\n        }\r\n\r\n        // Going to exit if no errors\r\n        if(!has_errors) goto return_corrected_msg;\r\n\r\n        CalcForneySyndromes(synd, epos, src_len);\r\n        FindErrorLocator(forney, NULL, epos->length);\r\n\r\n        // Reversing syndrome\r\n        // TODO optimize through special Poly flag\r\n        reloc->length = eloc->length;\r\n        for(int8_t i = eloc->length-1, j = 0; i >= 0; i--, j++){\r\n            reloc->at(j) = eloc->at(i);\r\n        }\r\n\r\n        // Fing errors\r\n        ok = FindErrors(reloc, src_len);\r\n        if(!ok) return 1;\r\n\r\n        // Error happened while finding errors (so helpfull :D)\r\n        if(err->length == 0) return 1;\r\n\r\n        /* Adding found errors with known */\r\n        for(uint8_t i = 0; i < err->length; i++) {\r\n            epos->Append(err->at(i));\r\n        }\r\n\r\n        // Correcting errors\r\n        CorrectErrata(synd, epos, msg_in);\r\n\r\n    return_corrected_msg:\r\n        // Wrighting corrected message to output buffer\r\n        msg_out->length = dst_len;\r\n        memcpy(dst_ptr, msg_out->ptr(), msg_out->length * sizeof(uint8_t));\r\n        return 0;\r\n    }\r\n\r\n    /* @brief Message block decoding\r\n     * @param *src         - encoded message buffer   (msg_length + ecc_length size)\r\n     * @param *msg_out     - output buffer            (msg_length size at least)\r\n     * @param *erase_pos   - known errors positions\r\n     * @param erase_count  - count of known errors\r\n     * @return RESULT_SUCCESS if successfull, error code otherwise */\r\n     int Decode(const void* src, void* dst, uint8_t* erase_pos = NULL, size_t erase_count = 0) {\r\n         const uint8_t *src_ptr = (const uint8_t*) src;\r\n         const uint8_t *ecc_ptr = src_ptr + msg_length;\r\n\r\n         return DecodeBlock(src, ecc_ptr, dst, erase_pos, erase_count);\r\n     }\r\n\r\n#ifndef DEBUG\r\nprivate:\r\n#endif\r\n\r\n    enum POLY_ID {\r\n        ID_MSG_IN = 0,\r\n        ID_MSG_OUT,\r\n        ID_GENERATOR,   // 3\r\n        ID_TPOLY1,      // T for Temporary\r\n        ID_TPOLY2,\r\n\r\n        ID_MSG_E,       // 5\r\n\r\n        ID_TPOLY3,     // 6\r\n        ID_TPOLY4,\r\n\r\n        ID_SYNDROMES,\r\n        ID_FORNEY,\r\n\r\n        ID_ERASURES_LOC,\r\n        ID_ERRORS_LOC,\r\n\r\n        ID_ERASURES,\r\n        ID_ERRORS,\r\n\r\n        ID_COEF_POS,\r\n        ID_ERR_EVAL\r\n    };\r\n\r\n    // Pointer for polynomials memory on stack\r\n    uint8_t* memory;\r\n    Poly polynoms[MSG_CNT + POLY_CNT];\r\n\r\n    void GeneratorPoly() {\r\n        Poly *gen = polynoms + ID_GENERATOR;\r\n        gen->at(0) = 1;\r\n        gen->length = 1;\r\n\r\n        Poly *mulp = polynoms + ID_TPOLY1;\r\n        Poly *temp = polynoms + ID_TPOLY2;\r\n        mulp->length = 2;\r\n\r\n        for(int8_t i = 0; i < ecc_length; i++){\r\n            mulp->at(0) = 1;\r\n            mulp->at(1) = gf::pow(2, i);\r\n\r\n            gf::poly_mul(gen, mulp, temp);\r\n\r\n            gen->Copy(temp);\r\n        }\r\n    }\r\n\r\n    void CalcSyndromes(const Poly *msg) {\r\n        Poly *synd = &polynoms[ID_SYNDROMES];\r\n        synd->length = ecc_length+1;\r\n        synd->at(0) = 0;\r\n        for(uint8_t i = 1; i < ecc_length+1; i++){\r\n            synd->at(i) = gf::poly_eval(msg, gf::pow(2, i-1));\r\n        }\r\n    }\r\n\r\n    void FindErrataLocator(const Poly *epos) {\r\n        Poly *errata_loc = &polynoms[ID_ERASURES_LOC];\r\n        Poly *mulp = &polynoms[ID_TPOLY1];\r\n        Poly *addp = &polynoms[ID_TPOLY2];\r\n        Poly *apol = &polynoms[ID_TPOLY3];\r\n        Poly *temp = &polynoms[ID_TPOLY4];\r\n\r\n        errata_loc->length = 1;\r\n        errata_loc->at(0)  = 1;\r\n\r\n        mulp->length = 1;\r\n        addp->length = 2;\r\n\r\n        for(uint8_t i = 0; i < epos->length; i++){\r\n            mulp->at(0) = 1;\r\n            addp->at(0) = gf::pow(2, epos->at(i));\r\n            addp->at(1) = 0;\r\n\r\n            gf::poly_add(mulp, addp, apol);\r\n            gf::poly_mul(errata_loc, apol, temp);\r\n\r\n            errata_loc->Copy(temp);\r\n        }\r\n    }\r\n\r\n    void FindErrorEvaluator(const Poly *synd, const Poly *errata_loc, Poly *dst, uint8_t ecclen) {\r\n        Poly *mulp = &polynoms[ID_TPOLY1];\r\n        gf::poly_mul(synd, errata_loc, mulp);\r\n\r\n        Poly *divisor = &polynoms[ID_TPOLY2];\r\n        divisor->length = ecclen+2;\r\n\r\n        divisor->Reset();\r\n        divisor->at(0) = 1;\r\n\r\n        gf::poly_div(mulp, divisor, dst);\r\n    }\r\n\r\n    void CorrectErrata(const Poly *synd, const Poly *err_pos, const Poly *msg_in) {\r\n        Poly *c_pos     = &polynoms[ID_COEF_POS];\r\n        Poly *corrected = &polynoms[ID_MSG_OUT];\r\n        c_pos->length = err_pos->length;\r\n\r\n        for(uint8_t i = 0; i < err_pos->length; i++)\r\n            c_pos->at(i) = msg_in->length - 1 - err_pos->at(i);\r\n\r\n        /* uses t_poly 1, 2, 3, 4 */\r\n        FindErrataLocator(c_pos);\r\n        Poly *errata_loc = &polynoms[ID_ERASURES_LOC];\r\n\r\n        /* reversing syndromes */\r\n        Poly *rsynd = &polynoms[ID_TPOLY3];\r\n        rsynd->length = synd->length;\r\n\r\n        for(int8_t i = synd->length-1, j = 0; i >= 0; i--, j++) {\r\n            rsynd->at(j) = synd->at(i);\r\n        }\r\n\r\n        /* getting reversed error evaluator polynomial */\r\n        Poly *re_eval = &polynoms[ID_TPOLY4];\r\n\r\n        /* uses T_POLY 1, 2 */\r\n        FindErrorEvaluator(rsynd, errata_loc, re_eval, errata_loc->length-1);\r\n\r\n        /* reversing it back */\r\n        Poly *e_eval = &polynoms[ID_ERR_EVAL];\r\n        e_eval->length = re_eval->length;\r\n        for(int8_t i = re_eval->length-1, j = 0; i >= 0; i--, j++) {\r\n            e_eval->at(j) = re_eval->at(i);\r\n        }\r\n\r\n        Poly *X = &polynoms[ID_TPOLY1]; /* this will store errors positions */\r\n        X->length = 0;\r\n\r\n        int16_t l;\r\n        for(uint8_t i = 0; i < c_pos->length; i++){\r\n            l = 255 - c_pos->at(i);\r\n            X->Append(gf::pow(2, -l));\r\n        }\r\n\r\n        /* Magnitude polynomial\r\n           Shit just got real */\r\n        Poly *E = &polynoms[ID_MSG_E];\r\n        E->Reset();\r\n        E->length = msg_in->length;\r\n\r\n        uint8_t Xi_inv;\r\n\r\n        Poly *err_loc_prime_temp = &polynoms[ID_TPOLY2];\r\n\r\n        uint8_t err_loc_prime;\r\n        uint8_t y;\r\n\r\n        for(uint8_t i = 0; i < X->length; i++){\r\n            Xi_inv = gf::inverse(X->at(i));\r\n\r\n            err_loc_prime_temp->length = 0;\r\n            for(uint8_t j = 0; j < X->length; j++){\r\n                if(j != i){\r\n                    err_loc_prime_temp->Append(gf::sub(1, gf::mul(Xi_inv, X->at(j))));\r\n                }\r\n            }\r\n\r\n            err_loc_prime = 1;\r\n            for(uint8_t j = 0; j < err_loc_prime_temp->length; j++){\r\n                err_loc_prime = gf::mul(err_loc_prime, err_loc_prime_temp->at(j));\r\n            }\r\n\r\n            y = gf::poly_eval(re_eval, Xi_inv);\r\n            y = gf::mul(gf::pow(X->at(i), 1), y);\r\n\r\n            E->at(err_pos->at(i)) = gf::div(y, err_loc_prime);\r\n        }\r\n\r\n        gf::poly_add(msg_in, E, corrected);\r\n    }\r\n\r\n    bool FindErrorLocator(const Poly *synd, Poly *erase_loc = NULL, size_t erase_count = 0) {\r\n        Poly *error_loc = &polynoms[ID_ERRORS_LOC];\r\n        Poly *err_loc   = &polynoms[ID_TPOLY1];\r\n        Poly *old_loc   = &polynoms[ID_TPOLY2];\r\n        Poly *temp      = &polynoms[ID_TPOLY3];\r\n        Poly *temp2     = &polynoms[ID_TPOLY4];\r\n\r\n        if(erase_loc != NULL) {\r\n            err_loc->Copy(erase_loc);\r\n            old_loc->Copy(erase_loc);\r\n        } else {\r\n            err_loc->length = 1;\r\n            old_loc->length = 1;\r\n            err_loc->at(0)  = 1;\r\n            old_loc->at(0)  = 1;\r\n        }\r\n\r\n        uint8_t synd_shift = 0;\r\n        if(synd->length > ecc_length) {\r\n            synd_shift = synd->length - ecc_length;\r\n        }\r\n\r\n        uint8_t K = 0;\r\n        uint8_t delta = 0;\r\n        uint8_t index;\r\n\r\n        for(uint8_t i = 0; i < ecc_length - erase_count; i++){\r\n            if(erase_loc != NULL)\r\n                K = erase_count + i + synd_shift;\r\n            else\r\n                K = i + synd_shift;\r\n\r\n            delta = synd->at(K);\r\n            for(uint8_t j = 1; j < err_loc->length; j++) {\r\n                index = err_loc->length - j - 1;\r\n                delta ^= gf::mul(err_loc->at(index), synd->at(K-j));\r\n            }\r\n\r\n            old_loc->Append(0);\r\n\r\n            if(delta != 0) {\r\n                if(old_loc->length > err_loc->length) {\r\n                    gf::poly_scale(old_loc, temp, delta);\r\n                    gf::poly_scale(err_loc, old_loc, gf::inverse(delta));\r\n                    err_loc->Copy(temp);\r\n                }\r\n                gf::poly_scale(old_loc, temp, delta);\r\n                gf::poly_add(err_loc, temp, temp2);\r\n                err_loc->Copy(temp2);\r\n            }\r\n        }\r\n\r\n        uint32_t shift = 0;\r\n        while(err_loc->length && err_loc->at(shift) == 0) shift++;\r\n\r\n        uint32_t errs = err_loc->length - shift - 1;\r\n        if(((errs - erase_count) * 2 + erase_count) > ecc_length){\r\n            return false; /* Error count is greater then we can fix! */\r\n        }\r\n\r\n        memcpy(error_loc->ptr(), err_loc->ptr() + shift, (err_loc->length - shift) * sizeof(uint8_t));\r\n        error_loc->length = (err_loc->length - shift);\r\n        return true;\r\n    }\r\n\r\n    bool FindErrors(const Poly *error_loc, size_t msg_in_size) {\r\n        Poly *err = &polynoms[ID_ERRORS];\r\n\r\n        uint8_t errs = error_loc->length - 1;\r\n        err->length = 0;\r\n\r\n        for(uint8_t i = 0; i < msg_in_size; i++) {\r\n            if(gf::poly_eval(error_loc, gf::pow(2, i)) == 0) {\r\n                err->Append(msg_in_size - 1 - i);\r\n            }\r\n        }\r\n\r\n        /* Sanity check:\r\n         * the number of err/errata positions found\r\n         * should be exactly the same as the length of the errata locator polynomial */\r\n        if(err->length != errs)\r\n            /* couldn't find error locations */\r\n            return false;\r\n        return true;\r\n    }\r\n\r\n    void CalcForneySyndromes(const Poly *synd, const Poly *erasures_pos, size_t msg_in_size) {\r\n        Poly *erase_pos_reversed = &polynoms[ID_TPOLY1];\r\n        Poly *forney_synd = &polynoms[ID_FORNEY];\r\n        erase_pos_reversed->length = 0;\r\n\r\n        for(uint8_t i = 0; i < erasures_pos->length; i++){\r\n            erase_pos_reversed->Append(msg_in_size - 1 - erasures_pos->at(i));\r\n        }\r\n\r\n        forney_synd->Reset();\r\n        forney_synd->Set(synd->ptr()+1, synd->length-1);\r\n\r\n        uint8_t x;\r\n        for(uint8_t i = 0; i < erasures_pos->length; i++) {\r\n            x = gf::pow(2, erase_pos_reversed->at(i));\r\n            for(int8_t j = 0; j < forney_synd->length - 1; j++){\r\n                forney_synd->at(j) = gf::mul(forney_synd->at(j), x) ^ forney_synd->at(j+1);\r\n            }\r\n        }\r\n    }\r\n};\r\n\r\n}\r\n\r\n#endif // RS_HPP\r\n\r\n"
  },
  {
    "path": "tests/CMakeLists.txt",
    "content": "if (EMSCRIPTEN)\n    #\n    # test-ggwave-js\n\n    set(TEST_TARGET test-ggwave-js)\n\n    add_test(NAME ${TEST_TARGET}\n        COMMAND node test-ggwave.js\n        WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}\n        )\n\n    return()\nendif()\n\n#\n# test-ggwave-c\n\nset(TEST_TARGET test-ggwave-c)\n\nadd_executable(${TEST_TARGET}\n    test-ggwave.c\n    )\n\ntarget_link_libraries(${TEST_TARGET} PRIVATE\n    ggwave\n    )\n\nadd_test(NAME ${TEST_TARGET} COMMAND $<TARGET_FILE:${TEST_TARGET}>)\n\n#\n# test-ggwave-cpp\n\nset(TEST_TARGET test-ggwave-cpp)\n\nadd_executable(${TEST_TARGET}\n    test-ggwave.cpp\n    )\n\ntarget_link_libraries(${TEST_TARGET} PRIVATE\n    ggwave\n    )\n\nadd_test(NAME ${TEST_TARGET} COMMAND $<TARGET_FILE:${TEST_TARGET}>)\n\nif (GGWAVE_SUPPORT_PYTHON)\n    #\n    # test-ggwave-py\n\n    set(TEST_TARGET test-ggwave-py)\n\n    add_test(NAME ${TEST_TARGET}\n        COMMAND python test-ggwave.py\n        WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}\n        )\n\n    set_tests_properties(${TEST_TARGET}\n        PROPERTIES ENVIRONMENT \"PYTHONPATH=${PROJECT_SOURCE_DIR}/bindings/python\")\nendif()\n"
  },
  {
    "path": "tests/test-ggwave.c",
    "content": "#include \"ggwave/ggwave.h\"\n\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n\n#define CHECK(cond) \\\n    if (!(cond)) { \\\n        fprintf(stderr, \"[%s:%d] Check failed: %s\\n\", __FILE__, __LINE__, #cond); \\\n        exit(1); \\\n    }\n\n#define CHECK_T(cond) CHECK(cond)\n#define CHECK_F(cond) CHECK(!(cond))\n\nint main() {\n    //ggwave_setLogFile(NULL); // disable logging\n    ggwave_setLogFile(stdout);\n\n    ggwave_Parameters parameters = ggwave_getDefaultParameters();\n    parameters.sampleFormatInp = GGWAVE_SAMPLE_FORMAT_I16;\n    parameters.sampleFormatOut = GGWAVE_SAMPLE_FORMAT_I16;\n\n    ggwave_Instance instance = ggwave_init(parameters);\n\n    int ret;\n    const char * payload = \"test\";\n    char decoded[16];\n\n    int n = ggwave_encode(instance, payload, 4, GGWAVE_PROTOCOL_AUDIBLE_FASTEST, 50, NULL, 1);\n    char *waveform = malloc(n);\n    CHECK(waveform != NULL);\n\n    int ne = ggwave_encode(instance, payload, 4, GGWAVE_PROTOCOL_AUDIBLE_FASTEST, 50, waveform, 0);\n    CHECK(ne > 0);\n\n    // not enough output buffer size to store the decoded message\n    ret = ggwave_ndecode(instance, waveform, ne, decoded, 3);\n    CHECK(ret == -2); // fail\n\n    // just enough size to store it\n    ret = ggwave_ndecode(instance, waveform, ne, decoded, 4);\n    CHECK(ret == 4); // success\n\n    // unsafe method - will write the decoded output to the output buffer regardless of the size\n    ret = ggwave_decode(instance, waveform, ne, decoded);\n    CHECK(ret == 4);\n\n    // disable Rx protocol\n    {\n        ggwave_rxToggleProtocol(GGWAVE_PROTOCOL_AUDIBLE_FASTEST, 0);\n        ggwave_Instance instanceTmp = ggwave_init(parameters);\n\n        ret = ggwave_ndecode(instanceTmp, waveform, ne, decoded, 4);\n        CHECK(ret == -1); // fail\n\n        ggwave_free(instanceTmp);\n    }\n\n    // enable Rx protocol\n    {\n        ggwave_rxToggleProtocol(GGWAVE_PROTOCOL_AUDIBLE_FASTEST, 1);\n        ggwave_Instance instanceTmp = ggwave_init(parameters);\n\n        ret = ggwave_ndecode(instanceTmp, waveform, ne, decoded, 4);\n        CHECK(ret == 4); // success\n\n        ggwave_free(instanceTmp);\n    }\n\n    decoded[ret] = 0; // null-terminate the received data\n    CHECK(strcmp(decoded, payload) == 0);\n\n    ggwave_free(instance);\n    free(waveform);\n\n    return 0;\n}\n"
  },
  {
    "path": "tests/test-ggwave.cpp",
    "content": "#include \"ggwave/ggwave.h\"\n\n#include <cstring>\n#include <limits>\n#include <string>\n#include <typeinfo>\n#include <typeindex>\n#include <vector>\n#include <set>\n#include <cstdint>\n#include <map>\n\nconstexpr float iRandMax = 1.0f/float(RAND_MAX);\nfloat frand() { return float(rand()%RAND_MAX)*iRandMax; }\n\n#define CHECK(cond) \\\n    if (!(cond)) { \\\n        fprintf(stderr, \"[%s:%d] Check failed: %s\\n\", __FILE__, __LINE__, #cond); \\\n        exit(1); \\\n    }\n\n#define CHECK_T(cond) CHECK(cond)\n#define CHECK_F(cond) CHECK(!(cond))\n\nconst std::map<std::type_index, float> kSampleScale = {\n    { typeid(uint8_t),  std::numeric_limits<uint8_t>::max()  },\n    { typeid(int8_t),   std::numeric_limits<int8_t>::max()   },\n    { typeid(uint16_t), std::numeric_limits<uint16_t>::max() },\n    { typeid(int16_t),  std::numeric_limits<int16_t>::max()  },\n    { typeid(float),    1.0f                                 },\n};\n\nconst std::map<std::type_index, float> kSampleOffset = {\n    { typeid(uint8_t),  0.5f*std::numeric_limits<uint8_t>::max()  },\n    { typeid(int8_t),   0.0f                                      },\n    { typeid(uint16_t), 0.5f*std::numeric_limits<uint16_t>::max() },\n    { typeid(int16_t),  0.0f                                      },\n    { typeid(float),    0.0f                                      },\n};\n\nconst std::set<GGWave::SampleFormat> kFormats = {\n    GGWAVE_SAMPLE_FORMAT_U8,\n    GGWAVE_SAMPLE_FORMAT_I8,\n    GGWAVE_SAMPLE_FORMAT_U16,\n    GGWAVE_SAMPLE_FORMAT_I16,\n    GGWAVE_SAMPLE_FORMAT_F32,\n};\n\ntemplate <typename S, typename D>\nvoid convert(std::vector<uint8_t> & src) {\n    const int n = src.size()/sizeof(S);\n    std::vector<D> dst(n);\n    S v;\n    for (int i = 0; i < n; ++i) {\n        std::memcpy(&v, &src[i*sizeof(S)], sizeof(S));\n        dst[i] = ((float(v) - kSampleOffset.at(typeid(S)))/kSampleScale.at(typeid(S)))*kSampleScale.at(typeid(D)) + kSampleOffset.at(typeid(D));\n    }\n\n    src.resize(n*sizeof(D));\n    std::memcpy(&src[0], &dst[0], n*sizeof(D));\n}\n\nint main(int argc, char ** argv) {\n    bool full = false;\n    if (argc > 1) {\n        if (strcmp(argv[1], \"--full\") == 0) {\n            full = true;\n        }\n    }\n\n    std::vector<uint8_t> buffer;\n\n    auto convertHelper = [&](GGWave::SampleFormat formatOut, GGWave::SampleFormat formatInp) {\n        switch (formatOut) {\n            case GGWAVE_SAMPLE_FORMAT_UNDEFINED: CHECK(false); break;\n            case GGWAVE_SAMPLE_FORMAT_U8:\n                {\n                    switch (formatInp) {\n                        case GGWAVE_SAMPLE_FORMAT_UNDEFINED: CHECK(false); break;\n                        case GGWAVE_SAMPLE_FORMAT_U8:        break;\n                        case GGWAVE_SAMPLE_FORMAT_I8:        convert<uint8_t, int8_t>  (buffer); break;\n                        case GGWAVE_SAMPLE_FORMAT_U16:       convert<uint8_t, uint16_t>(buffer); break;\n                        case GGWAVE_SAMPLE_FORMAT_I16:       convert<uint8_t, int16_t> (buffer); break;\n                        case GGWAVE_SAMPLE_FORMAT_F32:       convert<uint8_t, float>   (buffer); break;\n                    };\n                } break;\n            case GGWAVE_SAMPLE_FORMAT_I8:\n                {\n                    switch (formatInp) {\n                        case GGWAVE_SAMPLE_FORMAT_UNDEFINED: CHECK(false); break;\n                        case GGWAVE_SAMPLE_FORMAT_U8:        convert<int8_t, uint8_t> (buffer); break;\n                        case GGWAVE_SAMPLE_FORMAT_I8:        break;\n                        case GGWAVE_SAMPLE_FORMAT_U16:       convert<int8_t, uint16_t>(buffer); break;\n                        case GGWAVE_SAMPLE_FORMAT_I16:       convert<int8_t, int16_t> (buffer); break;\n                        case GGWAVE_SAMPLE_FORMAT_F32:       convert<int8_t, float>   (buffer); break;\n                    };\n                } break;\n            case GGWAVE_SAMPLE_FORMAT_U16:\n                {\n                    switch (formatInp) {\n                        case GGWAVE_SAMPLE_FORMAT_UNDEFINED: CHECK(false); break;\n                        case GGWAVE_SAMPLE_FORMAT_U8:        convert<uint16_t, uint8_t>(buffer); break;\n                        case GGWAVE_SAMPLE_FORMAT_I8:        convert<uint16_t, int8_t> (buffer); break;\n                        case GGWAVE_SAMPLE_FORMAT_U16:       break;\n                        case GGWAVE_SAMPLE_FORMAT_I16:       convert<uint16_t, int16_t>(buffer); break;\n                        case GGWAVE_SAMPLE_FORMAT_F32:       convert<uint16_t, float>  (buffer); break;\n                    };\n                } break;\n            case GGWAVE_SAMPLE_FORMAT_I16:\n                {\n                    switch (formatInp) {\n                        case GGWAVE_SAMPLE_FORMAT_UNDEFINED: CHECK(false); break;\n                        case GGWAVE_SAMPLE_FORMAT_U8:        convert<int16_t, uint8_t> (buffer); break;\n                        case GGWAVE_SAMPLE_FORMAT_I8:        convert<int16_t, int8_t>  (buffer); break;\n                        case GGWAVE_SAMPLE_FORMAT_U16:       convert<int16_t, uint16_t>(buffer); break;\n                        case GGWAVE_SAMPLE_FORMAT_I16:       break;\n                        case GGWAVE_SAMPLE_FORMAT_F32:       convert<int16_t, float>   (buffer); break;\n                    };\n                } break;\n            case GGWAVE_SAMPLE_FORMAT_F32:\n                {\n                    switch (formatInp) {\n                        case GGWAVE_SAMPLE_FORMAT_UNDEFINED: CHECK(false); break;\n                        case GGWAVE_SAMPLE_FORMAT_U8:        convert<float, uint8_t> (buffer); break;\n                        case GGWAVE_SAMPLE_FORMAT_I8:        convert<float, int8_t>  (buffer); break;\n                        case GGWAVE_SAMPLE_FORMAT_U16:       convert<float, uint16_t>(buffer); break;\n                        case GGWAVE_SAMPLE_FORMAT_I16:       convert<float, int16_t> (buffer); break;\n                        case GGWAVE_SAMPLE_FORMAT_F32:       break;\n                    };\n                } break;\n        };\n    };\n\n    auto addNoiseHelper = [&](float level, GGWave::SampleFormat format) {\n        switch (format) {\n            case GGWAVE_SAMPLE_FORMAT_UNDEFINED: CHECK(false); break;\n            case GGWAVE_SAMPLE_FORMAT_U8:\n                {\n                    const int n = buffer.size()/sizeof(uint8_t);\n                    auto p = (uint8_t *) buffer.data();\n                    for (int i = 0; i < n; ++i) {\n                        p[i] = std::max(0.0f, std::min(255.0f, (float) p[i] + (frand() - 0.5f)*(level*256)));\n                    }\n                } break;\n            case GGWAVE_SAMPLE_FORMAT_I8:\n                {\n                    const int n = buffer.size()/sizeof(int8_t);\n                    auto p = (int8_t *) buffer.data();\n                    for (int i = 0; i < n; ++i) {\n                        p[i] = std::max(-128.0f, std::min(127.0f, (float) p[i] + (frand() - 0.5f)*(level*256)));\n                    }\n                } break;\n            case GGWAVE_SAMPLE_FORMAT_U16:\n                {\n                    const int n = buffer.size()/sizeof(uint16_t);\n                    auto p = (uint16_t *) buffer.data();\n                    for (int i = 0; i < n; ++i) {\n                        p[i] = std::max(0.0f, std::min(65535.0f, (float) p[i] + (frand() - 0.5f)*(level*65536)));\n                    }\n                } break;\n            case GGWAVE_SAMPLE_FORMAT_I16:\n                {\n                    const int n = buffer.size()/sizeof(int16_t);\n                    auto p = (int16_t *) buffer.data();\n                    for (int i = 0; i < n; ++i) {\n                        p[i] = std::max(-32768.0f, std::min(32767.0f, (float) p[i] + (frand() - 0.5f)*(level*65536)));\n                    }\n                } break;\n            case GGWAVE_SAMPLE_FORMAT_F32:\n                {\n                    const int n = buffer.size()/sizeof(float);\n                    auto p = (float *) buffer.data();\n                    for (int i = 0; i < n; ++i) {\n                        p[i] = std::max(-1.0f, std::min(1.0f, p[i] + (frand() - 0.5f)*(level)));\n                    }\n                } break;\n        };\n    };\n\n    {\n        GGWave instance(GGWave::getDefaultParameters());\n\n        std::string payload = \"hello\";\n\n        CHECK(instance.init(payload.c_str(), GGWAVE_PROTOCOL_AUDIBLE_FAST));\n\n        // data\n        CHECK_F(instance.init(-1, \"asd\",   GGWAVE_PROTOCOL_AUDIBLE_FAST));\n        CHECK_T(instance.init(0,  nullptr, GGWAVE_PROTOCOL_AUDIBLE_FAST));\n        CHECK_T(instance.init(0,  \"asd\",   GGWAVE_PROTOCOL_AUDIBLE_FAST));\n        CHECK_T(instance.init(1,  \"asd\",   GGWAVE_PROTOCOL_AUDIBLE_FAST));\n        CHECK_T(instance.init(2,  \"asd\",   GGWAVE_PROTOCOL_AUDIBLE_FAST));\n        CHECK_T(instance.init(3,  \"asd\",   GGWAVE_PROTOCOL_AUDIBLE_FAST));\n\n        // volume\n        CHECK_F(instance.init(payload.size(), payload.c_str(), GGWAVE_PROTOCOL_AUDIBLE_FAST, -1));\n        CHECK_T(instance.init(payload.size(), payload.c_str(), GGWAVE_PROTOCOL_AUDIBLE_FAST, 0));\n        CHECK_T(instance.init(payload.size(), payload.c_str(), GGWAVE_PROTOCOL_AUDIBLE_FAST, 50));\n        CHECK_T(instance.init(payload.size(), payload.c_str(), GGWAVE_PROTOCOL_AUDIBLE_FAST, 100));\n        CHECK_F(instance.init(payload.size(), payload.c_str(), GGWAVE_PROTOCOL_AUDIBLE_FAST, 101));\n    }\n\n    // playback / capture at different sample rates\n    for (int srInp = GGWave::kDefaultSampleRate/6; srInp <= 2*GGWave::kDefaultSampleRate; srInp += 1371) {\n        printf(\"Testing: sample rate = %d\\n\", srInp);\n\n        auto parameters = GGWave::getDefaultParameters();\n        parameters.soundMarkerThreshold = 3.0f;\n\n        const std::string payload = \"hello123\";\n\n        // encode\n        {\n            parameters.sampleRateOut = srInp;\n            GGWave instanceOut(parameters);\n\n            instanceOut.init(payload.c_str(), GGWAVE_PROTOCOL_DT_FASTEST, 25);\n            const auto expectedSize = instanceOut.encodeSize_bytes();\n            const auto nBytes = instanceOut.encode();\n            printf(\"Expected = %d, actual = %d\\n\", expectedSize, nBytes);\n            CHECK(expectedSize >= nBytes);\n            { auto p = (const uint8_t *)(instanceOut.txWaveform()); buffer.resize(nBytes); memcpy(buffer.data(), p, nBytes); }\n            addNoiseHelper(0.01, parameters.sampleFormatOut); // add some artificial noise\n            convertHelper(parameters.sampleFormatOut, parameters.sampleFormatInp);\n        }\n\n        // decode\n        {\n            parameters.sampleRateInp = srInp;\n            GGWave instanceInp(parameters);\n            instanceInp.rxProtocols().only(GGWAVE_PROTOCOL_DT_FASTEST);\n\n            instanceInp.decode(buffer.data(), buffer.size());\n\n            GGWave::TxRxData result;\n            CHECK(instanceInp.rxTakeData(result) == (int) payload.size());\n            for (int i = 0; i < (int) payload.size(); ++i) {\n                CHECK(payload[i] == result[i]);\n            }\n        }\n    }\n\n    const std::string payload = \"a0Z5kR2g\";\n\n    // encode / decode using different sample formats and Tx protocols\n    for (const auto & formatOut : kFormats) {\n        for (const auto & formatInp : kFormats) {\n            if (full == false) {\n                if (formatOut != GGWAVE_SAMPLE_FORMAT_I16) continue;\n                if (formatInp != GGWAVE_SAMPLE_FORMAT_F32) continue;\n            }\n            for (int protocolId = 0; protocolId < GGWAVE_PROTOCOL_COUNT; ++protocolId) {\n                const auto & protocol = GGWave::Protocols::kDefault()[protocolId];\n                if (protocol.enabled == false) continue;\n                printf(\"Testing: protocol = %s, in = %d, out = %d\\n\", protocol.name, formatInp, formatOut);\n\n                for (int length = 1; length <= (int) payload.size(); ++length) {\n                    // mono-tone protocols with variable length are not supported\n                    if (protocol.extra == 2) {\n                        break;\n                    }\n\n                    // variable payload length\n                    {\n                        auto parameters = GGWave::getDefaultParameters();\n                        parameters.sampleFormatInp = formatInp;\n                        parameters.sampleFormatOut = formatOut;\n                        // it seems DSS is not suitable for \"variable-length\" transmission\n                        // sometimes, the decoder incorrectly detects an early \"end\" marker when DSS is enabled\n                        //if (rand() % 2 == 0) parameters.operatingMode |= GGWAVE_OPERATING_MODE_USE_DSS;\n                        GGWave instance(parameters);\n                        instance.rxProtocols().only(GGWave::ProtocolId(protocolId));\n\n                        instance.init(length, payload.data(), GGWave::ProtocolId(protocolId), 25);\n                        const auto expectedSize = instance.encodeSize_bytes();\n                        const auto nBytes = instance.encode();\n                        printf(\"Expected = %d, actual = %d\\n\", expectedSize, nBytes);\n                        CHECK(expectedSize == nBytes);\n                        { auto p = (const uint8_t *)(instance.txWaveform()); buffer.resize(nBytes); memcpy(buffer.data(), p, nBytes); }\n                        addNoiseHelper(0.02, parameters.sampleFormatOut); // add some artificial noise\n                        convertHelper(formatOut, formatInp);\n                        instance.decode(buffer.data(), buffer.size());\n\n                        GGWave::TxRxData result;\n                        CHECK(instance.rxTakeData(result) == length);\n                        for (int i = 0; i < length; ++i) {\n                            CHECK(payload[i] == result[i]);\n                        }\n                    }\n                }\n\n                for (int length = 1; length <= (int) payload.size(); ++length) {\n                    // fixed payload length\n                    {\n                        auto parameters = GGWave::getDefaultParameters();\n                        parameters.payloadLength = length;\n                        parameters.sampleFormatInp = formatInp;\n                        parameters.sampleFormatOut = formatOut;\n                        if (rand() % 2 == 0) parameters.operatingMode |= GGWAVE_OPERATING_MODE_USE_DSS;\n                        GGWave instance(parameters);\n                        instance.rxProtocols().only(GGWave::ProtocolId(protocolId));\n\n                        instance.init(length, payload.data(), GGWave::ProtocolId(protocolId), 10);\n                        const auto expectedSize = instance.encodeSize_bytes();\n                        const auto nBytes = instance.encode();\n                        printf(\"Expected = %d, actual = %d\\n\", expectedSize, nBytes);\n                        CHECK(expectedSize == nBytes);\n                        { auto p = (const uint8_t *)(instance.txWaveform()); buffer.resize(nBytes); memcpy(buffer.data(), p, nBytes); }\n                        addNoiseHelper(0.10, parameters.sampleFormatOut); // add some artificial noise\n                        convertHelper(formatOut, formatInp);\n                        instance.decode(buffer.data(), buffer.size());\n\n                        GGWave::TxRxData result;\n                        CHECK(instance.rxTakeData(result) == length);\n                        for (int i = 0; i < length; ++i) {\n                            CHECK(payload[i] == result[i]);\n                        }\n                    }\n                }\n            }\n        }\n    }\n\n    return 0;\n}\n"
  },
  {
    "path": "tests/test-ggwave.js",
    "content": "var factory = require('../bindings/javascript/ggwave.js')\n\nfactory().then(function(ggwave) {\n    // create ggwave instance with default parameters\n    var parameters = ggwave.getDefaultParameters();\n\n    parameters.operatingMode |= ggwave.GGWAVE_OPERATING_MODE_USE_DSS;\n\n    var instance = ggwave.init(parameters);\n    console.log('instance: ' + instance);\n\n    var payload = 'hello js';\n\n    // generate audio waveform for string \"hello js\"\n    var waveform = ggwave.encode(instance, payload, ggwave.ProtocolId.GGWAVE_PROTOCOL_AUDIBLE_FAST, 10);\n\n    // decode the audio waveform back to text\n    var res = ggwave.decode(instance, waveform);\n\n    if (new TextDecoder(\"utf-8\").decode(res) != payload) {\n        process.exit(1);\n    }\n});\n"
  },
  {
    "path": "tests/test-ggwave.py",
    "content": "import sys\nimport ggwave\n\n# optionally disable logging\n#ggwave.disableLog()\n\n# create ggwave instance with default parameters\ninstance = ggwave.init()\n\npayload = 'hello python'\n\n# generate audio waveform for string \"hello python\"\nwaveform = ggwave.encode(payload, protocolId = 1, volume = 20, instance = instance)\n\n# decode the audio waveform back to text\nres = ggwave.decode(instance, waveform)\n\nif res != payload.encode():\n    sys.exit(1)\n\n# disable the Rx protocol - the decoding should fail\nggwave.rxToggleProtocol(protocolId = 1, state = 0)\ninstanceTmp = ggwave.init()\n\nres = ggwave.decode(instanceTmp, waveform)\nif res != None:\n    sys.exit(1)\n\nggwave.free(instanceTmp);\n\n# re-enable the Rx protocol - the decoding should succeed\nggwave.rxToggleProtocol( protocolId = 1, state = 1)\ninstanceTmp = ggwave.init()\n\nres = ggwave.decode(instance, waveform)\nif res != payload.encode():\n    sys.exit(1)\n\nggwave.free(instanceTmp);\n\nggwave.free(instance);\n"
  }
]