diff --git a/.gitignore b/.gitignore index 7abb4c40..8f4dc499 100644 --- a/.gitignore +++ b/.gitignore @@ -19,6 +19,8 @@ compile_commands.json clients/sfizz_jack clients/sfzprint +/vst/download/ + # gh-pages unstaged files: _api/ _site/ @@ -32,3 +34,5 @@ node_modules/ *.lock *.sublime-* *.code-* +.kak.tags.namecache +.clangd diff --git a/.gitmodules b/.gitmodules index 04936207..1ed52774 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,18 +1,19 @@ [submodule "external/abseil-cpp"] path = external/abseil-cpp url = https://github.com/abseil/abseil-cpp - branch = lts_2020_02_25 + branch = lts_2020_02_25 + shallow = true [submodule "vst/external/VST_SDK/VST3_SDK/base"] path = vst/external/VST_SDK/VST3_SDK/base url = https://github.com/steinbergmedia/vst3_base.git shallow = true [submodule "vst/external/VST_SDK/VST3_SDK/pluginterfaces"] path = vst/external/VST_SDK/VST3_SDK/pluginterfaces - url = https://github.com/steinbergmedia/vst3_pluginterfaces.git + url = https://github.com/sfztools/vst3_pluginterfaces.git shallow = true [submodule "vst/external/VST_SDK/VST3_SDK/public.sdk"] path = vst/external/VST_SDK/VST3_SDK/public.sdk - url = https://github.com/steinbergmedia/vst3_public_sdk.git + url = https://github.com/sfztools/vst3_public_sdk.git shallow = true [submodule "vst/external/VST_SDK/VST3_SDK/vstgui4"] path = vst/external/VST_SDK/VST3_SDK/vstgui4 diff --git a/.travis.yml b/.travis.yml index 6753d573..8b92e35a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,6 +2,10 @@ language: cpp os: linux dist: bionic +cache: + directories: + - vst/download/ + jobs: include: - name: "clang-tidy checks" @@ -27,7 +31,7 @@ jobs: script: .travis/script_test.sh - name: "Linux arm64 tests" - arch: amd64 + arch: arm64 addons: apt: packages: @@ -129,7 +133,7 @@ jobs: - INSTALL_DIR="sfizz-${TRAVIS_BRANCH}-${TRAVIS_OS_NAME}-${TRAVIS_CPU_ARCH}" install: .travis/install_osx.sh script: .travis/script_osx.sh - after_success: .travis/prepare_tarball.sh + after_success: .travis/prepare_osx.sh - name: "MOD devices arm" stage: "Build" @@ -174,23 +178,12 @@ after_failure: - bash ${TRAVIS_BUILD_DIR}/.travis/discord_webhook.sh failure deploy: - provider: releases - skip_cleanup: true - file: "${INSTALL_DIR}.tar.gz" + edge: true + provider: releases + file: "${INSTALL_DIR}.tar.gz" token: secure: jBIMWMPva8OazC8Wnqc2vqATa3exjvGDGvejEu6fgwt/BMDkgJCsJ7gS3ITpYFuO0HoHtLGfTVrFcZIE+agGVRdRGRni5xZRp5Vuf9hvLf8hCTaxkS/HYoliYYPp9FLY6J4yq9mp/R/QyGrwgssQEjhYlI2GL4+lJzt750AcmYibnQo2AMe5SSOp3q8KdLBG1vca1o1BZWBhW/S7l2hA1aJ7y1ytwm0ETIdrTZHxgfz7bqdqQcw/Ytqjm1rgzty6iQKX3B/InaZNB6CqXy6FoGLf771lery8nWJbjTjKYD4QK2NelExldyp1vrdHGvknWguFk+8vlQ16Dt0+R7Byr/LOPRLTCf/T+IehMQGtVA9/gjrkH8LqHy9oVFB+33p11gGu3h6hvg7yB6z3sSck0u4FHjrlpNd5XmmCsKQVQ9vI1cPPkVkbMHIewc7uOu+bD6cmotFj1vJ9UYesvN6n4siyCiPOIhgV9++bjcyiLqX3DWP5UWyZ9/VT8bz5VcxUdJnEYtdNPx4x5pRW4081VUjIf2EmHhQTrjb1iz6FAGwNU/fpefA8x+bBxXP9MkFFgU0tbCDKw2y6o0GOXX82mZr/IB0/DIBg7UllTzCBKTuiJQV1prFPlZLc6V22H5ozAXHgu3E+qNjvHoMsoYjGssX3+AYHwu8isBINo/gGo6Y= on: repo: "sfztools/sfizz" tags: true condition: ${TRAVIS_TAG} =~ ^v?[0-9] - -notifications: - irc: - channels: - - "ircs://chat.freenode.net:7070/#sfztools" - - template: - - "[%{repository_name}:%{branch}] Build #%{build_number}: %{message}" - - "%{author} : %{commit_subject}" - - "Commit URL : https://github.com/%{repository_slug}/commit/%{commit}" - - "Build Details : %{build_url}" diff --git a/.travis/install_mingw.sh b/.travis/install_mingw.sh index f97ee3d8..882b5d52 100755 --- a/.travis/install_mingw.sh +++ b/.travis/install_mingw.sh @@ -3,6 +3,6 @@ set -ex . .travis/docker_container.sh -buildenv_as_root pacman -Sqy --noconfirm +buildenv_as_root pacman -Sqyu --noconfirm buildenv_as_root pacman -Sq --noconfirm base-devel wget mingw-w64-cmake mingw-w64-gcc mingw-w64-pkg-config mingw-w64-libsndfile buildenv i686-w64-mingw32-gcc -v && buildenv i686-w64-mingw32-g++ -v && buildenv i686-w64-mingw32-cmake --version diff --git a/.travis/install_osx.sh b/.travis/install_osx.sh index c04bc695..d69b8249 100755 --- a/.travis/install_osx.sh +++ b/.travis/install_osx.sh @@ -5,4 +5,6 @@ set -ex sudo ln -s /usr/local /opt/local brew update brew upgrade cmake +brew install python || brew link --overwrite python brew install jack +brew install dylibbundler diff --git a/.travis/patches/vstsdk-3.6.14-mingw-atomic-add.diff b/.travis/patches/vstsdk-3.6.14-mingw-atomic-add.diff deleted file mode 100644 index 1425b31c..00000000 --- a/.travis/patches/vstsdk-3.6.14-mingw-atomic-add.diff +++ /dev/null @@ -1,12 +0,0 @@ -diff -Nru VST_SDK.orig/VST3_SDK/pluginterfaces/base/funknown.cpp VST_SDK/VST3_SDK/pluginterfaces/base/funknown.cpp ---- VST_SDK.orig/VST3_SDK/pluginterfaces/base/funknown.cpp 2019-12-02 13:58:41.000000000 +0100 -+++ VST_SDK/VST3_SDK/pluginterfaces/base/funknown.cpp 2020-03-10 22:02:26.220911670 +0100 -@@ -84,7 +84,7 @@ - int32 PLUGIN_API atomicAdd (int32& var, int32 d) - { - #if SMTG_OS_WINDOWS -- return InterlockedExchangeAdd (&var, d) + d; -+ return InterlockedExchangeAdd ((LONG*)&var, d) + d; - #elif SMTG_OS_MACOS - #if SMTG_MACOS_USE_STDATOMIC - return atomic_fetch_add (reinterpret_cast (&var), d) + d; diff --git a/.travis/prepare_osx.sh b/.travis/prepare_osx.sh new file mode 100755 index 00000000..8fc891c9 --- /dev/null +++ b/.travis/prepare_osx.sh @@ -0,0 +1,39 @@ +#!/bin/bash + +set -ex + +test -z "$CONTAINER" + +if ! [ -z "$CONTAINER" ]; then + . .travis/docker_container.sh +else + . .travis/no_container.sh +fi + +cd build +buildenv make DESTDIR=${PWD}/${INSTALL_DIR} install + +# Bundle LV2 dependencies +cd "${INSTALL_DIR}"/Library/Audio/Plug-Ins/LV2 +dylibbundler -od -b -x sfizz.lv2/sfizz.so -d sfizz.lv2/libs/ -p @loader_path/libs/ +cd "${TRAVIS_BUILD_DIR}/build" + +# Bundle VST3 dependencies +cd "${INSTALL_DIR}"/Library/Audio/Plug-Ins/VST3 +dylibbundler -od -b -x sfizz.vst3/Contents/MacOS/sfizz -d sfizz.vst3/Contents/libs/ -p @loader_path/../libs/ +cd "${TRAVIS_BUILD_DIR}/build" + +# Bundle AU dependencies +cd "${INSTALL_DIR}"/Library/Audio/Plug-Ins/Components +dylibbundler -od -b -x sfizz.component/Contents/Resources/plugin.vst3/Contents/MacOS/sfizz -d sfizz.component/Contents/Resources/plugin.vst3/Contents/libs/ -p @loader_path/../libs/ +cd "${TRAVIS_BUILD_DIR}/build" + +# +tar -zcvf "${INSTALL_DIR}.tar.gz" ${INSTALL_DIR} + +# Only release a tarball if there is a tag +if [[ ${TRAVIS_TAG} != "" ]]; then + mv "${INSTALL_DIR}.tar.gz" ${TRAVIS_BUILD_DIR} +fi + +cd "${TRAVIS_BUILD_DIR}" diff --git a/.travis/prepare_tarball.sh b/.travis/prepare_tarball.sh index 001e235f..6e111406 100755 --- a/.travis/prepare_tarball.sh +++ b/.travis/prepare_tarball.sh @@ -8,13 +8,13 @@ else . .travis/no_container.sh fi -# Do not prepare a tarball without a tag -if [[ ${TRAVIS_TAG} == "" ]]; then - exit 0 -fi - cd build buildenv make DESTDIR=${PWD}/${INSTALL_DIR} install tar -zcvf "${INSTALL_DIR}.tar.gz" ${INSTALL_DIR} -mv "${INSTALL_DIR}.tar.gz" ${TRAVIS_BUILD_DIR} + +# Only release a tarball if there is a tag +if [[ ${TRAVIS_TAG} != "" ]]; then + mv "${INSTALL_DIR}.tar.gz" ${TRAVIS_BUILD_DIR} +fi + cd .. diff --git a/.travis/script_mingw.sh b/.travis/script_mingw.sh index 6ca765b6..2f1f545f 100755 --- a/.travis/script_mingw.sh +++ b/.travis/script_mingw.sh @@ -12,7 +12,6 @@ if [[ ${CROSS_COMPILE} == "mingw32" ]]; then -DSFIZZ_STATIC_LIBSNDFILE=ON \ -DCMAKE_CXX_STANDARD=17 \ .. - buildenv patch -p1 -i "$(pwd)"/../.travis/patches/vstsdk-3.6.14-mingw-atomic-add.diff -d ../vst/external/VST_SDK buildenv make -j$(nproc) elif [[ ${CROSS_COMPILE} == "mingw64" ]]; then buildenv x86_64-w64-mingw32-cmake -DCMAKE_BUILD_TYPE=Release \ @@ -22,6 +21,5 @@ elif [[ ${CROSS_COMPILE} == "mingw64" ]]; then -DSFIZZ_STATIC_LIBSNDFILE=ON \ -DCMAKE_CXX_STANDARD=17 \ .. - buildenv patch -p1 -i "$(pwd)"/../.travis/patches/vstsdk-3.6.14-mingw-atomic-add.diff -d ../vst/external/VST_SDK buildenv make -j$(nproc) fi diff --git a/.travis/script_osx.sh b/.travis/script_osx.sh index 557259a6..0385f8c3 100755 --- a/.travis/script_osx.sh +++ b/.travis/script_osx.sh @@ -4,8 +4,12 @@ set -ex mkdir -p build/${INSTALL_DIR} && cd build cmake -DCMAKE_BUILD_TYPE=Release \ -DSFIZZ_VST=ON \ + -DSFIZZ_AU=ON \ -DSFIZZ_TESTS=OFF \ - -DCMAKE_CXX_STANDARD=17 \ + -DCMAKE_CXX_STANDARD=14 \ + -DLV2PLUGIN_INSTALL_DIR=/Library/Audio/Plug-Ins/LV2 \ + -DVSTPLUGIN_INSTALL_DIR=/Library/Audio/Plug-Ins/VST3 \ + -DAUPLUGIN_INSTALL_DIR=/Library/Audio/Plug-Ins/Components \ .. make -j$(sysctl -n hw.ncpu) # Xcode not currently supported, see https://gitlab.kitware.com/cmake/cmake/issues/18088 diff --git a/.travis/update_dox.sh b/.travis/update_dox.sh index e838dc05..55ea08f4 100755 --- a/.travis/update_dox.sh +++ b/.travis/update_dox.sh @@ -5,11 +5,13 @@ set -x # No fail, we need to go back to the original branch at the end mkdir build && cd build && cmake -DSFIZZ_JACK=OFF -DSFIZZ_SHARED=OFF -DSFIZZ_LV2=OFF .. && cd .. doxygen Doxyfile +./doxygen/scripts/generate_api_index.sh git fetch --depth=1 https://github.com/${TRAVIS_REPO_SLUG}.git refs/heads/gh-pages:refs/remotes/origin/gh-pages git checkout origin/gh-pages git checkout -b gh-pages mv _api api/${TRAVIS_TAG} -git add api/${TRAVIS_TAG} && git commit -m "Release ${TRAVIS_TAG} (Travis build: ${TRAVIS_BUILD_NUMBER})" +mv api_index.md api/index.md +git add api && git commit -m "Release ${TRAVIS_TAG} (Travis build: ${TRAVIS_BUILD_NUMBER})" git remote add origin-pages https://${GITHUB_TOKEN}@github.com/${TRAVIS_REPO_SLUG}.git > /dev/null 2>&1 git push --quiet --set-upstream origin-pages gh-pages git checkout ${TRAVIS_BRANCH} diff --git a/AUTHORS.md b/AUTHORS.md index 38861574..20318099 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -7,3 +7,4 @@ Contributors to `sfizz`, in chronologic order: - Alexander Mitchell (2020) - Michael Willis (2020) - Jean-Pierre Cimalando (2020) +- Tobiasz "unfa" Karoń (2020) diff --git a/CMakeLists.txt b/CMakeLists.txt index 51199d34..205671ac 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -8,11 +8,12 @@ else() endif() endif() -project (sfizz VERSION 0.3.2 LANGUAGES CXX C) +project (sfizz VERSION 0.4.0 LANGUAGES CXX C) set (PROJECT_DESCRIPTION "A library to load SFZ description files and use them to render music.") # External configuration CMake scripts set (CMAKE_MODULE_PATH "${CMAKE_MODULE_PATH};${CMAKE_CURRENT_SOURCE_DIR}/cmake") +include (BuildType) include (SfizzConfig) # Build Options @@ -26,14 +27,17 @@ endif() option (ENABLE_LTO "Enable Link Time Optimization [default: ON]" ON) option (SFIZZ_JACK "Enable JACK stand-alone build [default: ON]" ON) +option (SFIZZ_RENDER "Enable renderer of SMF files [default: ON]" ON) option (SFIZZ_LV2 "Enable LV2 plug-in build [default: ON]" ON) option (SFIZZ_VST "Enable VST plug-in build [default: OFF]" OFF) +option (SFIZZ_AU "Enable AU plug-in build [default: OFF]" OFF) option (SFIZZ_BENCHMARKS "Enable benchmarks build [default: OFF]" OFF) option (SFIZZ_TESTS "Enable tests build [default: OFF]" OFF) option (SFIZZ_DEVTOOLS "Enable developer tools build [default: OFF]" OFF) option (SFIZZ_SHARED "Enable shared library build [default: ON]" ON) option (SFIZZ_USE_VCPKG "Assume that sfizz is build using vcpkg [default: OFF]" OFF) option (SFIZZ_STATIC_LIBSNDFILE "Link libsndfile statically [default: OFF]" OFF) +option (SFIZZ_RELEASE_ASSERTS "Forced assertions in release builds [default: OFF]" OFF) # Don't use IPO in non Release builds include (CheckIPO) @@ -45,9 +49,7 @@ add_subdirectory (external/abseil-cpp EXCLUDE_FROM_ALL) add_subdirectory (src) # Optional targets -if (SFIZZ_JACK) - add_subdirectory (clients) -endif() +add_subdirectory (clients) if (SFIZZ_LV2) add_subdirectory (lv2) @@ -55,6 +57,10 @@ endif() if (SFIZZ_VST) add_subdirectory (vst) +else() + if (SFIZZ_AU) + message(WARNING "Audio Unit requires VST to be enabled") + endif() endif() if (SFIZZ_BENCHMARKS) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..5f3823e3 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,223 @@ +# Contributing to sfizz + +Thanks for considering contributing! +There is actually many things anyone can do, programming-related or music-related. + +## Using the software for your projects + +One of the best and probably simplest thing to do is simply to use sfizz in a number of situations. +There is only so much we can test as far as sfz libraries go, or differents hosts for the plugin versions. +If during the course of your usage you find out that sfizz does not behave as it should with respect to what the SFZ spec dictates, or what other players do, please file in a bug reports. +If there is some feature that would be fantastic for you, feel free to post a feature requests in the issues. +This will help us track and remember everything. +Refer to (#filing-bugs-and-feature-requests) for more precision. + +### Filing bugs and feature requests + +When filing a bug, try to be as precise as possible. +Obviously bugs that are easily reproducible are easier to solve. + +- If sfizz fails to play a library correctly, please give us a link to the library and ideally an example of what you except. It helps **tremendously** if you can provide a *minimal* example in the form of a simple custom sfz library that pinpoints the problem exactly, along with instruction or midi files to render. +- If the plugins crashes, please try to reproduce and detail everything you did up to the crash, as well as the host and sfz library. It can be really helpful to provide session files, again with a minimal example. +- If the behavior of sfizz is clumsy and annoying, and the user experience feels subpar, please tell us and try to propose some solutions on how it could be improved. As developers, we are viewing and using sfizz in specific ways which may actually be quite far from what users would like, but we're all ears :) + +For feature requests, provide details about the expected behavior and possibly example sfz files that will benefit from the new feature. +Note that we aim at implementing the sfz spec as a whole, and possibly some extensions by e.g. ARIA so normally all sfz features should come at some point. + +### Using the C or C++ library + +Sfizz is liberally licensed and you can freely use it in any project under the term of its license and its dependencies. +We provide a somewhat simple C and C++ API that covers our use cases within plugins mostly, and should be useful. +The API is documented over at the [api] page. +You can find example of using the C API in the `lv2` directory which contains sfizz's LV2 plugin. +You can find example of using the C++ API in the `vst3` directory which contains sfizz's VST3 plugin. +In both cases sfizz is built in the plugins as a static library. +The project [sfizz-render] illustrates how one might use sfizz as a shared library through a `make`- and `pkg-config`-based process. +Other projects that bundle or use sfizz are: + +- (https://github.com/sfztools/beatbox): a live drum machine + +If you use the library, maybe you will have specific needs and requests that could be useful to you. +Be sure to post an issue for this so we can improve the API, especially because we have to queue API changes so it stays a bit stable over time. + +## Developing for sfizz + +We welcome all code contributions to sfizz, whatever your coding level. +If there is a specific feature you would like, and you are willing to put in the time (and have some form of initial background in modern C++ code), we can mentor you and provide breadcrumbs through the codebase. +Sfizz is still very much alpha, and we're refactoring large parts of the code on a regular basis. +We try to keep the public API documented, and most of the internal APIs are also documented, but this can be in flux from time to time. +If you want to add things we encourage you to ask around through an issue, on Discord, IRC, or by email, so that we may be able to guide you and discuss about how and what to implement. + +### Handling the repository and its submodules + +Sfizz uses a number of submodules, namely abseil and the VST sdk. +These submodules are purely consumed by sfizz, and the maintainers will manage updating them when needed. +As such, they should *not* pose too much issues. +They are all pinned to specific branches or commits in their original repository. + +To clone the sfizz repository, the recommended way is to do +```bash +git clone https://github.com/sfztools/sfizz.git --recursive +# Other possibility +git clone https://github.com/sfztools/sfizz.git +cd sfizz +git submodule update --init +``` + +If after some time you happen to see something like this: +``` +❯ git status +On branch develop +Your branch is up to date with 'origin/develop'. + +Changes not staged for commit: + (use "git add ..." to update what will be committed) + (use "git checkout -- ..." to discard changes in working directory) + + modified: external/abseil-cpp (new commits) + +no changes added to commit (use "git add" and/or "git commit -a") +``` +Then it means that the abseil submodule *on your disk* differs from the one expected by sfizz. +This usually happens if i) you modified one of abseil's files, in which case the repository will be dirty, or ii) sfizz updated abseil's and pinned it to another commit. +Assuming you were not adding relevant changes to abseil in order to push them upstream, you can usually resolve this issue through +```bash +git submodule update +``` +**All changes in the submodule will be lost**. +If you were in fact modifying abseil for some reason, then consider either pushing your changes upstream or find an alternative. +Same goes for the VST SDK. + +### Building sfizz from source + +For details on the dependencies and platform-specific information for building sfizz, please refer to the [build] page. +Note that for development you will probably want all dependencies, which means using debian's lingo: + +- `cmake` +- `g++` or `clang++` +- `clang-format` +- `libjack-jackd2-dev` +- `libsndfile-dev` + +We also recommend building [benchmark] from source and installing it as a static library. +You will also want to configure using the `-DSFIZZ_TESTS=ON` flag and `-DSFIZZ_BENCHMARKS=ON` flag to be able to run the tests and possibly the micro-benchmarks. + +### Forking and creating pull requests + +We do development on the git `develop` branch, and distribute using the `master` branch. +We mostly avoid directly committing to the `develop` branch and go through pull requests and issues for development-related discussion and choices. +Try to have public and private discussion about development, and if such discussion happen on IRC or Discord, try to sum them up in a pull request or an issue that we can then refer to. +Obviously, light technical questions can be asked freely in private, but the goal is to open the discussion for everyone to see. + +To start contributing, we encourage you to fork the main sfizz repository on your personal account and clone it locally on your machine +In the clone of your fork, and add the `sfztools` repository as a remote as follows +```bash +# Clone your own fork in a specific directory +git clone https://github.com/MY_GITHUB_NAME/sfizz.git sfizz-fork +cd sfizz-fork +# Add the upstream sfztools remote +git remote add upstream https://github.com/sfztools/sfizz.git +``` +Once done, the contribution process is as follows: + +1. Fetch the latest changes from the `upstream` remote, in particular the `develop` branch. +2. Checkout the `upstream/develop` branch, and immediately checkout to a new local branch to start adding changes +3. Add commits to your local branch +4. Push the local branch to your own fork +5. Go back to Github web interface and create a pull request + +In command language, this translates to +```bash +# Fetch the latest status from upstrea +git fetch upstream +# Checkout the upstream develop branch +git checkout upstream/develop +# Create a new local branch +git checkout -b my-new-contribution +# Do things as usual for git: git add, git commit +# ... +# Once ready, push the branch to your fork. Github will tell you where to go for a pull request from there. +git push --set-upstream origin my-new-contribution +``` + +### Adding files to the build system + +We use CMake for the build system. +For the main sfizz library, the main file is `src/CMakeLists.txt`. +In particular if you add a new `.cpp` file to sfizz, you should add it to the `SFIZZ_SOURCES` list in this file so it gets compiled and linked with the rest of the library. + +### Coding style and guidelines + +We have continuous integration targets down to gcc 4.9, so the code has to be fully C++11 compliant. +Sadly this means some bonuses from C++14 are not available, although `absl` compensates many things from a standard library point of view. +All modern C++ guidelines apply, as well as rules related to audio programming: + +- Raw pointers are non-owning observers; use `unique_ptr` and `shared_ptr` for owning references, and `absl::optional` for nullable/optional/maybe values returned from functions. +- Use move semantics when appropriate. +- Try to use [RAII] wrappers and write them if necessary. +- Avoid allocating memory, calling functions that throw, using system calls, or any launching any blocking operation in the audio thread; for sfizz, that means anything that is called at some point from `Synth::renderBlock` and `Synth::noteOn`, `noteOff`, ... and all midi events. +- We have a helper class that helps us to track allocation in the form of the `sfz::Buffer` class and the `sfz::AudioBuffer` class, so use it for heap memory in lieu of naked `new` or `unique_ptr` when relevant. +- Most things in the [CppCoreGuidelines] are good to take, although be careful as many proposed things may break the *no allocation* rule. + +We also have a `clang-tidy` CI step that may annoy you from time to time. + +We have a `clang-format` coding style in that is loosely based on the WebKit style. +We encourage you to use it, as well as `git-clang-format` to format your PR. +If possible, find a plugin for your text editor than handles `.editorconfig` files. Most editors have one. +Some notes : + +- Class and type names start by a capital letter and follow the `PascalCase`, function names and variables in the C++ part follow the `camelCase`, and function names and variables in the C part follows a `snake_case` to blend with the surroundings. +- Try to keep names meaningful, and code clear if possible. +- Comment your classes APIs, in a terse and clear way. Comments inside the code are usually not needed *if the code is written clearly with meaningful names*, but if you feel that it's needed please do. +- You can use `_` in front of private variables, but we do not have a consistent naming for these yet. + +All these rules are subject to change upon discussion and integration. + +### Testing your changes + +To build and run the tests, you need to have configured sfizz with the `-DSFIZZ_TESTS=ON` flag. +You can then use the `make sfizz_tests` command from your build directory. +The tests are located in `tests/sfizz_tests` from the same build directory. + +Note that these tests mostly cover the parsing and observable invariants of sfizz, as well as all helper methods as much as possible. +The processing and rendering is usually harder to test as it produces alot of data. +We are in the process of making a testing platform using rendered wave files, but it is not yet here. +If you write anything testable however, we encourage you to write the tests. +We use the [Catch] library for testing, and you can check out the files in `tests/` for examples on how to add some. + +To actually try your changes, you can use the `clients/sfizz_jack` client to instantiate a JACK application, or use `jalv` for a similar purpose by launching from your build directory +```bash +env LV2_PATH=$PWD jalv.gtk3 http://sfztools.github.io/sfizz +``` +Similarly, you can launch e.g. Ardour with suitable reference to your version of the sfizz LV2 plugin with +```bash +env LV2_PATH=$PWD Ardour6 +``` + +### Running micro-benchmarks + +For micro-optimization purposes we have a number of benchmarks written in `benchmarks/`, build against the [benchmark] library. +If you wonder about the performance of something, feel free to write some too and add them to the build system. +We tend to name the benchmarks with the `bm_` prefix. +It can be as simple as adding a line +```cmake +sfizz_add_benchmark(bm_wavfile BM_wavfile.cpp) +``` +to the `benchmarks/CMakeLists.txt` file but you might need to link to other libraries depending on what you are benchmarking. +The `CMakeLists.txt` file contains example that you can copy and try. + +### Running in-use benchmarks + +We have logging facilities for the processing inside of sfizz, that we use to track how the additions impact the rendering speed. +For now the usage is a bit hand-made, as it requires the [sfizz-render] program and manually pre-loading the development `.so` library. +The logger outputs CSV files for the rendering and file-loading processes. +The file `scripts/performance_report.py` is made to process and produce a nice interactive report from this data. + +[build]: https://sfz.tools/sfizz/development/build/ +[benchmark]: https://github.com/google/benchmark +[sfizz-render]: https://github.com/sfztools/sfizz-render +[GOVERNANCE]: GOVERNANCE.md +[api]: https://sfz.tools/sfizz/api/ +[RAII]: https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#S-resource +[CppCoreGuidelines]: https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines +[Catch]: https://github.com/catchorg/Catch2 diff --git a/FUNDING.yml b/FUNDING.yml new file mode 100644 index 00000000..f80ae432 --- /dev/null +++ b/FUNDING.yml @@ -0,0 +1 @@ +open_collective: sfztools diff --git a/GOVERNANCE.md b/GOVERNANCE.md new file mode 100644 index 00000000..3fc7d31c --- /dev/null +++ b/GOVERNANCE.md @@ -0,0 +1,50 @@ +## Leadership + +The current maintainers of sfizz and the sfz tools organization are : + +- Paul "@paulfd" Ferrand +- Jean-Pierre "@jpcima" Cimalando +- Andrea "@redtide" Zanellato + +1. Sfizz does not follow a so-called "Benevolent Dictatorship for Life" model. +Given the current size of the project, we believe an horizontal organization model to be the most relevant. +2. Decisions regarding the technical part or the organizational part of the project are taken unanimously between the maintainers, where anyone can obviously abstain on any decision. +3. We use Git as a global supporting platform for all decisions regarding the project, in the form of either pull requests or issues. +This guarantees that a trace is kept for any contribution or change, and that users may interject and discuss on the subject at hand. + +## Donations and financial contributions + +In most jurisdictions, handling money requires bank accounts and identification as a physical person, or be incorporated as a non-profit or for-profit organization. +We wish to: + +- keep things simple; +- support the horizontal governance model we chose; +- facilitate a transparent handling of the finances of the project from the maintainers; +- abide by international laws for the transfer and handling of money in support of a project. + +Considering these constraints, we chose to host the SFZ tools organization within the [SFZTools Open Collective]. +The Open Collective fiscal host holds the money of unincorporated partnerships such as SFZ tools, providing legal channels and accounting, as well as tools to support a transparent budget management by members of the collective. + +We apply the following ruleset to handle the project's financial resources: + +1. As everything in SFZ tools, usage of the project's financial resources is subject to unanimous approval by all maintainers. +2. The money is primarily directed towards expenses to support hosting the project---namely the website, and continuous testing and integration platforms. +3. At any time, a maintainer may submit an expense request regarding the development of sfizz, for example but not limited to software resources, commercial SFZ libraries, or hardware devices. +4. Every 3 months, during the months of March, June, September and December, the maintainers will gather in a budget meeting. The goal of the budget meeting decide in goodwill how to handle the money left over between contributors to the project. +5. The partition should take into account individual technical and organizational contributions. +The partition may also include other project that sfizz uses or wish to support. +6. Any contributor may waive its own share to benefit either the project---pooling back resources for the next round---or the other contributors. +7. Failure to reach a unanimous agreement will see the donations pooled back until the next budget meeting. +8. The budget and expenses are kept public so all donators can keep track of how their donations are used. + +Note that the Open Collective platform charges a 10% administrative fee on all donations; subsequent fees may be charged by credit card or banks as the money is moved from the collective to individual accounts. + +## Changes to the governance model + +1. This document is subject to changes and version control. +It is the role of maintainers to best adapt the governance model to the evolution of the project. +2. Change to the governance model are mapped to changes to this document through the same decision process, e.g. a pull request with approval from all maintainers. +3. Maintainers can be invited from regular contributors at any point in time; maintainers wishing to leave the governance of the project may inform the other maintainers. +In both cases, updates to this document reflect the evolution of the governance team and rules. + +[Open-source Open Collective]: https://opencollective.com/sfztools diff --git a/README.md b/README.md index 60ab196c..4bb0e1e6 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,30 @@ SFZ library and LV2 plugin, please check [our website] for more details. ![Screenshot](screenshot.png) +## Using sfizz + +Sfizz can be used most easily within an LV2 host such as [Carla] or [Ardour]. +It can also be integrated as a library within your own program; check out our [API] bindings for C and C++. +Our [releases] are an immediate way to get a working library or LV2/VST plugin, although you might prefer to [build from source]! + +## Contributing to sfizz + +There is actually many things anyone can do, programming-related or music-related. +Please check out the [CONTRIBUTING](CONTRIBUTING.md) document for information about filing bug reports or feature requests, and helping the development of sfizz + +## Donating to sfizz + +Sfizz and the work in the SFZ tools organization is purely driven by hobbyists who choose to use their free time to benefit this project. +We firmly believe in the honesty and goodwill of users as a whole, and we want to promote a healthy relationship to software and to the cost of producing quality software. + +No financial returns is explicitely required from using sfizz in any shape. +However, if you feel that sfizz produces value for you or your products, and if you find that your financial situation allows for it, we put together ways to donate to the project. +You are never compelled to do so, the [CONTRIBUTING](CONTRIBUTING.md) file contains different ways to contribute. + +In all of sfizz's governance model, we strive to live in the open. +Finances are no different, and we put in place a process so that the use of donations is as transparent as possible through our [Open Collective]. +We invite you to check out the [GOVERNANCE](GOVERNANCE.md) file to see how the organization is governed and how are donations handled. + ## Dependencies and licenses The sfizz library makes primary use of: @@ -21,8 +45,6 @@ The sfizz library also uses in some subprojects: - [benchmark], licensed under the Apache License 2.0 - [LV2], licensed under the ISC license - [JACK], licensed under the GNU Lesser General Public License v2.1 -- `neon_mathfun.h` and `sse_mathfun.h` by Julien Pommier, - licensed under the zlib license [Abseil]: https://github.com/abseil/abseil-cpp [atomic_queue]: https://github.com/max0x7ba/atomic_queue @@ -34,5 +56,11 @@ The sfizz library also uses in some subprojects: [libsndfile]: https://github.com/erikd/libsndfile/ [LV2]: https://lv2plug.in/ [our website]: https://sfz.tools/sfizz +[releases]: https://github.com/sfztools/sfizz/releases +[Carla]: https://kx.studio/Applications:Carla +[Ardour]: https://ardour.org/ +[API]: https://sfz.tools/sfizz/api/ +[Open Collective]: https://opencollective.com/sfztools +[build from source]: https://sfz.tools/sfizz/development/build/ [AppVeyor Build Status]: https://img.shields.io/appveyor/ci/sfztools/sfizz.svg?label=Windows&style=popout&logo=appveyor [Travis Build Status]: https://img.shields.io/travis/com/sfztools/sfizz.svg?label=Linux&style=popout&logo=travis diff --git a/benchmarks/BM_ADSR.cpp b/benchmarks/BM_ADSR.cpp index e2d077c0..c17a1a31 100644 --- a/benchmarks/BM_ADSR.cpp +++ b/benchmarks/BM_ADSR.cpp @@ -35,7 +35,7 @@ public: } sfz::MidiState midiState; - sfz::Region region{midiState}; + sfz::Region region{0, midiState}; sfz::ADSREnvelope envelope; std::vector output; }; diff --git a/benchmarks/BM_add.cpp b/benchmarks/BM_add.cpp index 893b2392..85277c1c 100644 --- a/benchmarks/BM_add.cpp +++ b/benchmarks/BM_add.cpp @@ -36,56 +36,64 @@ public: BENCHMARK_DEFINE_F(AddArray, Value_Scalar)(benchmark::State& state) { for (auto _ : state) { - sfz::add(1.1f, absl::MakeSpan(output)); + sfz::setSIMDOpStatus(sfz::SIMDOps::add1, false); + sfz::add1(1.1f, absl::MakeSpan(output)); } } BENCHMARK_DEFINE_F(AddArray, Value_SIMD)(benchmark::State& state) { for (auto _ : state) { - sfz::add(1.1f, absl::MakeSpan(output)); + sfz::setSIMDOpStatus(sfz::SIMDOps::add1, true); + sfz::add1(1.1f, absl::MakeSpan(output)); } } BENCHMARK_DEFINE_F(AddArray, Value_Scalar_Unaligned)(benchmark::State& state) { for (auto _ : state) { - sfz::add(1.1f, absl::MakeSpan(output).subspan(1)); + sfz::setSIMDOpStatus(sfz::SIMDOps::add1, false); + sfz::add1(1.1f, absl::MakeSpan(output).subspan(1)); } } BENCHMARK_DEFINE_F(AddArray, Value_SIMD_Unaligned)(benchmark::State& state) { for (auto _ : state) { - sfz::add(1.1f, absl::MakeSpan(output).subspan(1)); + sfz::setSIMDOpStatus(sfz::SIMDOps::add1, true); + sfz::add1(1.1f, absl::MakeSpan(output).subspan(1)); } } BENCHMARK_DEFINE_F(AddArray, Scalar)(benchmark::State& state) { for (auto _ : state) { - sfz::add(input, absl::MakeSpan(output)); + sfz::setSIMDOpStatus(sfz::SIMDOps::add, false); + sfz::add(input, absl::MakeSpan(output)); } } BENCHMARK_DEFINE_F(AddArray, SIMD)(benchmark::State& state) { for (auto _ : state) { - sfz::add(input, absl::MakeSpan(output)); + sfz::setSIMDOpStatus(sfz::SIMDOps::add, true); + sfz::add(input, absl::MakeSpan(output)); } } BENCHMARK_DEFINE_F(AddArray, Scalar_Unaligned)(benchmark::State& state) { for (auto _ : state) { - sfz::add(absl::MakeSpan(input).subspan(1), absl::MakeSpan(output).subspan(1)); + sfz::setSIMDOpStatus(sfz::SIMDOps::add, false); + sfz::add(absl::MakeSpan(input).subspan(1), absl::MakeSpan(output).subspan(1)); } } BENCHMARK_DEFINE_F(AddArray, SIMD_Unaligned)(benchmark::State& state) { for (auto _ : state) { - sfz::add(absl::MakeSpan(input).subspan(1), absl::MakeSpan(output).subspan(1)); + sfz::setSIMDOpStatus(sfz::SIMDOps::add, true); + sfz::add(absl::MakeSpan(input).subspan(1), absl::MakeSpan(output).subspan(1)); } } diff --git a/benchmarks/BM_copy.cpp b/benchmarks/BM_copy.cpp index 20d4f088..4f371b8a 100644 --- a/benchmarks/BM_copy.cpp +++ b/benchmarks/BM_copy.cpp @@ -43,14 +43,16 @@ BENCHMARK_DEFINE_F(CopyArray, StdCopy)(benchmark::State& state) { BENCHMARK_DEFINE_F(CopyArray, Scalar)(benchmark::State& state) { for (auto _ : state) { - sfz::copy(input, absl::MakeSpan(output)); + sfz::setSIMDOpStatus(sfz::SIMDOps::copy, false); + sfz::copy(input, absl::MakeSpan(output)); } } BENCHMARK_DEFINE_F(CopyArray, SIMD)(benchmark::State& state) { for (auto _ : state) { - sfz::copy(input, absl::MakeSpan(output)); + sfz::setSIMDOpStatus(sfz::SIMDOps::copy, true); + sfz::copy(input, absl::MakeSpan(output)); } } @@ -64,14 +66,16 @@ BENCHMARK_DEFINE_F(CopyArray, StdCopy_Unaligned)(benchmark::State& state) { BENCHMARK_DEFINE_F(CopyArray, Scalar_Unaligned)(benchmark::State& state) { for (auto _ : state) { - sfz::copy(absl::MakeSpan(input).subspan(1), absl::MakeSpan(output).subspan(1)); + sfz::setSIMDOpStatus(sfz::SIMDOps::copy, false); + sfz::copy(absl::MakeSpan(input).subspan(1), absl::MakeSpan(output).subspan(1)); } } BENCHMARK_DEFINE_F(CopyArray, SIMD_Unaligned)(benchmark::State& state) { for (auto _ : state) { - sfz::copy(absl::MakeSpan(input).subspan(1), absl::MakeSpan(output).subspan(1)); + sfz::setSIMDOpStatus(sfz::SIMDOps::copy, true); + sfz::copy(absl::MakeSpan(input).subspan(1), absl::MakeSpan(output).subspan(1)); } } diff --git a/benchmarks/BM_cumsum.cpp b/benchmarks/BM_cumsum.cpp index c8626848..a30d14d2 100644 --- a/benchmarks/BM_cumsum.cpp +++ b/benchmarks/BM_cumsum.cpp @@ -35,28 +35,32 @@ public: BENCHMARK_DEFINE_F(CumArray, Sum_Scalar)(benchmark::State& state) { for (auto _ : state) { - sfz::cumsum(input, absl::MakeSpan(output)); + sfz::setSIMDOpStatus(sfz::SIMDOps::cumsum, false); + sfz::cumsum(input, absl::MakeSpan(output)); } } BENCHMARK_DEFINE_F(CumArray, Sum_SIMD)(benchmark::State& state) { for (auto _ : state) { - sfz::cumsum(input, absl::MakeSpan(output)); + sfz::setSIMDOpStatus(sfz::SIMDOps::cumsum, true); + sfz::cumsum(input, absl::MakeSpan(output)); } } BENCHMARK_DEFINE_F(CumArray, Sum_Scalar_Unaligned)(benchmark::State& state) { for (auto _ : state) { - sfz::cumsum(absl::MakeSpan(input).subspan(1), absl::MakeSpan(output).subspan(1)); + sfz::setSIMDOpStatus(sfz::SIMDOps::cumsum, false); + sfz::cumsum(absl::MakeSpan(input).subspan(1), absl::MakeSpan(output).subspan(1)); } } BENCHMARK_DEFINE_F(CumArray, Sum_SIMD_Unaligned)(benchmark::State& state) { for (auto _ : state) { - sfz::cumsum(absl::MakeSpan(input).subspan(1), absl::MakeSpan(output).subspan(1)); + sfz::setSIMDOpStatus(sfz::SIMDOps::cumsum, true); + sfz::cumsum(absl::MakeSpan(input).subspan(1), absl::MakeSpan(output).subspan(1)); } } diff --git a/benchmarks/BM_diff.cpp b/benchmarks/BM_diff.cpp index af50441d..c0fd4924 100644 --- a/benchmarks/BM_diff.cpp +++ b/benchmarks/BM_diff.cpp @@ -22,7 +22,7 @@ public: input = std::vector(state.range(0)); output = std::vector(state.range(0)); std::generate(input.begin(), input.end(), [&]() { return dist(gen); }); - sfz::cumsum(input, absl::MakeSpan(input)); + sfz::cumsum(input, absl::MakeSpan(input)); } void TearDown(const ::benchmark::State& /* state */) { @@ -37,28 +37,32 @@ public: BENCHMARK_DEFINE_F(DiffArray, Diff_Scalar)(benchmark::State& state) { for (auto _ : state) { - sfz::diff(input, absl::MakeSpan(output)); + sfz::setSIMDOpStatus(sfz::SIMDOps::diff, false); + sfz::diff(input, absl::MakeSpan(output)); } } BENCHMARK_DEFINE_F(DiffArray, Diff_SIMD)(benchmark::State& state) { for (auto _ : state) { - sfz::diff(input, absl::MakeSpan(output)); + sfz::setSIMDOpStatus(sfz::SIMDOps::diff, true); + sfz::diff(input, absl::MakeSpan(output)); } } BENCHMARK_DEFINE_F(DiffArray, Diff_Scalar_Unaligned)(benchmark::State& state) { for (auto _ : state) { - sfz::diff(absl::MakeSpan(input).subspan(1), absl::MakeSpan(output).subspan(1)); + sfz::setSIMDOpStatus(sfz::SIMDOps::diff, false); + sfz::diff(absl::MakeSpan(input).subspan(1), absl::MakeSpan(output).subspan(1)); } } BENCHMARK_DEFINE_F(DiffArray, Diff_SIMD_Unaligned)(benchmark::State& state) { for (auto _ : state) { - sfz::diff(absl::MakeSpan(input).subspan(1), absl::MakeSpan(output).subspan(1)); + sfz::setSIMDOpStatus(sfz::SIMDOps::diff, true); + sfz::diff(absl::MakeSpan(input).subspan(1), absl::MakeSpan(output).subspan(1)); } } diff --git a/benchmarks/BM_divide.cpp b/benchmarks/BM_divide.cpp index b4fed561..b3580439 100644 --- a/benchmarks/BM_divide.cpp +++ b/benchmarks/BM_divide.cpp @@ -46,28 +46,32 @@ BENCHMARK_DEFINE_F(Divide, Straight)(benchmark::State& state) { BENCHMARK_DEFINE_F(Divide, Scalar)(benchmark::State& state) { for (auto _ : state) { - sfz::divide(input, divisor, absl::MakeSpan(output)); + sfz::setSIMDOpStatus(sfz::SIMDOps::divide, false); + sfz::divide(input, divisor, absl::MakeSpan(output)); } } BENCHMARK_DEFINE_F(Divide, SIMD)(benchmark::State& state) { for (auto _ : state) { - sfz::divide(input, divisor, absl::MakeSpan(output)); + sfz::setSIMDOpStatus(sfz::SIMDOps::divide, true); + sfz::divide(input, divisor, absl::MakeSpan(output)); } } BENCHMARK_DEFINE_F(Divide, Scalar_Unaligned)(benchmark::State& state) { for (auto _ : state) { - sfz::divide(absl::MakeSpan(input).subspan(1), absl::MakeSpan(divisor).subspan(1), absl::MakeSpan(output).subspan(1)); + sfz::setSIMDOpStatus(sfz::SIMDOps::divide, false); + sfz::divide(absl::MakeSpan(input).subspan(1), absl::MakeSpan(divisor).subspan(1), absl::MakeSpan(output).subspan(1)); } } BENCHMARK_DEFINE_F(Divide, SIMD_Unaligned)(benchmark::State& state) { for (auto _ : state) { - sfz::divide(absl::MakeSpan(input).subspan(1), absl::MakeSpan(divisor).subspan(1), absl::MakeSpan(output).subspan(1)); + sfz::setSIMDOpStatus(sfz::SIMDOps::divide, true); + sfz::divide(absl::MakeSpan(input).subspan(1), absl::MakeSpan(divisor).subspan(1), absl::MakeSpan(output).subspan(1)); } } diff --git a/benchmarks/BM_envelopes.cpp b/benchmarks/BM_envelopes.cpp index 836d6b0a..bbb75fb6 100644 --- a/benchmarks/BM_envelopes.cpp +++ b/benchmarks/BM_envelopes.cpp @@ -10,7 +10,7 @@ #include #include #include -#include "SfzHelpers.h" +#include "ModifierHelpers.h" #include "absl/types/span.h" class EnvelopeFixture : public benchmark::Fixture { @@ -20,7 +20,7 @@ public: input = std::vector(state.range(0)); output = std::vector(state.range(0)); std::generate(input.begin(), input.end(), [&]() { return dist(gen); }); - sfz::cumsum(input, absl::MakeSpan(input)); + sfz::cumsum(input, absl::MakeSpan(input)); } void TearDown(const ::benchmark::State& /* state */) @@ -42,7 +42,16 @@ BENCHMARK_DEFINE_F(EnvelopeFixture, Linear)(benchmark::State& state) { { 0, 0.0f }, { static_cast(state.range(0) - 1), dist(gen) } }; - linearEnvelope(events, absl::MakeSpan(output), [](float x) { return x; }); + sfz::linearEnvelope(events, absl::MakeSpan(output), [](float x) { return x; }); + } +} + +BENCHMARK_DEFINE_F(EnvelopeFixture, LinearNoEvent) (benchmark::State& state) { + for (auto _ : state) { + sfz::EventVector events { + { 0, dist(gen) } + }; + sfz::linearEnvelope(events, absl::MakeSpan(output), [](float x) { return x; }); } } @@ -53,7 +62,7 @@ BENCHMARK_DEFINE_F(EnvelopeFixture, LinearQuantized)(benchmark::State& state) { { 0, 0.0f }, { static_cast(state.range(0) - 1), dist(gen) } }; - linearEnvelope( + sfz::linearEnvelope( events, absl::MakeSpan(output), [](float x) { return x; }, 0.5); } } @@ -65,7 +74,16 @@ BENCHMARK_DEFINE_F(EnvelopeFixture, Multiplicative)(benchmark::State& state) { { 0, 1.0f }, { static_cast(state.range(0) - 1), dist(gen) } }; - multiplicativeEnvelope(events, absl::MakeSpan(output), [](float x) { return x; }); + sfz::multiplicativeEnvelope(events, absl::MakeSpan(output), [](float x) { return x; }); + } +} + +BENCHMARK_DEFINE_F(EnvelopeFixture, MultiplicativeNoEvent) (benchmark::State& state) { + for (auto _ : state) { + sfz::EventVector events { + { 0, dist(gen) } + }; + sfz::multiplicativeEnvelope(events, absl::MakeSpan(output), [](float x) { return x; }); } } @@ -76,14 +94,16 @@ BENCHMARK_DEFINE_F(EnvelopeFixture, MultiplicativeQuantized)(benchmark::State& s { 0, 1.0f }, { static_cast(state.range(0) - 1), dist(gen) } }; - multiplicativeEnvelope( + sfz::multiplicativeEnvelope( events, absl::MakeSpan(output), [](float x) { return x; }, 2.0f); } } BENCHMARK_REGISTER_F(EnvelopeFixture, Linear)->RangeMultiplier(4)->Range(1 << 2, 1 << 12); +BENCHMARK_REGISTER_F(EnvelopeFixture, LinearNoEvent)->RangeMultiplier(4)->Range(1 << 2, 1 << 12); BENCHMARK_REGISTER_F(EnvelopeFixture, LinearQuantized)->RangeMultiplier(4)->Range(1 << 2, 1 << 12); BENCHMARK_REGISTER_F(EnvelopeFixture, Multiplicative)->RangeMultiplier(4)->Range(1 << 2, 1 << 12); +BENCHMARK_REGISTER_F(EnvelopeFixture, MultiplicativeNoEvent)->RangeMultiplier(4)->Range(1 << 2, 1 << 12); BENCHMARK_REGISTER_F(EnvelopeFixture, MultiplicativeQuantized)->RangeMultiplier(4)->Range(1 << 2, 1 << 12); BENCHMARK_MAIN(); diff --git a/benchmarks/BM_fill.cpp b/benchmarks/BM_fill.cpp deleted file mode 100644 index 86e0e7cf..00000000 --- a/benchmarks/BM_fill.cpp +++ /dev/null @@ -1,70 +0,0 @@ -// SPDX-License-Identifier: BSD-2-Clause - -// This code is part of the sfizz library and is licensed under a BSD 2-clause -// license. You should have receive a LICENSE.md file along with the code. -// If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz - -#include "SIMDHelpers.h" -#include -#include "Buffer.h" -#include -#include -#include - -static void Dummy(benchmark::State& state) { - sfz::Buffer buffer (state.range(0)); - std::random_device rd { }; - std::mt19937 gen { rd() }; - std::uniform_real_distribution dist { 1, 2 }; - for (auto _ : state) { - auto fillValue = dist(gen); - benchmark::DoNotOptimize(fillValue); - } -} - -static void FillScalar(benchmark::State& state) { - sfz::Buffer buffer (state.range(0)); - std::random_device rd { }; - std::mt19937 gen { rd() }; - std::uniform_real_distribution dist { 1, 2 }; - for (auto _ : state) { - sfz::fill(absl::MakeSpan(buffer), dist(gen)); - } -} - -static void FillScalar_unaligned(benchmark::State& state) { - sfz::Buffer buffer (state.range(0)); - std::random_device rd { }; - std::mt19937 gen { rd() }; - std::uniform_real_distribution dist { 1, 2 }; - for (auto _ : state) { - sfz::fill(absl::MakeSpan(buffer).subspan(1), dist(gen)); - } -} - -static void FillSIMD(benchmark::State& state) { - sfz::Buffer buffer (state.range(0)); - std::random_device rd { }; - std::mt19937 gen { rd() }; - std::uniform_real_distribution dist { 1, 2 }; - for (auto _ : state) { - sfz::fill(absl::MakeSpan(buffer), dist(gen)); - } -} - -static void FillSIMD_unaligned(benchmark::State& state) { - sfz::Buffer buffer (state.range(0)); - std::random_device rd { }; - std::mt19937 gen { rd() }; - std::uniform_real_distribution dist { 1, 2 }; - for (auto _ : state) { - sfz::fill(absl::MakeSpan(buffer).subspan(1), dist(gen)); - } -} - -BENCHMARK(Dummy)->RangeMultiplier(4)->Range((1<<2), (1<<12)); -BENCHMARK(FillScalar)->RangeMultiplier(4)->Range((1<<2), (1<<12)); -BENCHMARK(FillSIMD)->RangeMultiplier(4)->Range((1<<2), (1<<12)); -BENCHMARK(FillScalar_unaligned)->RangeMultiplier(4)->Range((1<<2), (1<<12)); -BENCHMARK(FillSIMD_unaligned)->RangeMultiplier(4)->Range((1<<2), (1<<12)); -BENCHMARK_MAIN(); diff --git a/benchmarks/BM_filterModulation.cpp b/benchmarks/BM_filterModulation.cpp index 9326e21f..caa4eebf 100644 --- a/benchmarks/BM_filterModulation.cpp +++ b/benchmarks/BM_filterModulation.cpp @@ -7,7 +7,9 @@ #include "SIMDHelpers.h" #include "OnePoleFilter.h" #include "SfzFilter.h" +#include "SfzHelpers.h" #include "ScopedFTZ.h" +#include "SfzHelpers.h" #include #include #include @@ -57,7 +59,7 @@ BENCHMARK_DEFINE_F(FilterFixture, OnePole_VA)(benchmark::State& state) { const auto sentinel = cutoff.data() + blockSize; while (cutoffPtr < sentinel) { - const auto gain = sfz::OnePoleFilter::normalizedGain(*cutoffPtr, sampleRate); + const auto gain = sfz::vaGain(*cutoffPtr, sampleRate); filter.setGain(gain); filter.processLowpass({ inputPtr, step }, { outputPtr, step } ); cutoffPtr += step; diff --git a/benchmarks/BM_gain.cpp b/benchmarks/BM_gain.cpp index cb355f1f..d47ae719 100644 --- a/benchmarks/BM_gain.cpp +++ b/benchmarks/BM_gain.cpp @@ -66,14 +66,16 @@ BENCHMARK_DEFINE_F(GainSingle, Straight)(benchmark::State& state) { BENCHMARK_DEFINE_F(GainSingle, Scalar)(benchmark::State& state) { for (auto _ : state) { - sfz::applyGain(gain, input, absl::MakeSpan(output)); + sfz::setSIMDOpStatus(sfz::SIMDOps::gain1, false); + sfz::applyGain1(gain, input, absl::MakeSpan(output)); } } BENCHMARK_DEFINE_F(GainSingle, SIMD)(benchmark::State& state) { for (auto _ : state) { - sfz::applyGain(gain, input, absl::MakeSpan(output)); + sfz::setSIMDOpStatus(sfz::SIMDOps::gain1, true); + sfz::applyGain1(gain, input, absl::MakeSpan(output)); } } @@ -88,28 +90,32 @@ BENCHMARK_DEFINE_F(GainArray, Straight)(benchmark::State& state) { BENCHMARK_DEFINE_F(GainArray, Scalar)(benchmark::State& state) { for (auto _ : state) { - sfz::applyGain(gain, input, absl::MakeSpan(output)); + sfz::setSIMDOpStatus(sfz::SIMDOps::gain, false); + sfz::applyGain(gain, input, absl::MakeSpan(output)); } } BENCHMARK_DEFINE_F(GainArray, SIMD)(benchmark::State& state) { for (auto _ : state) { - sfz::applyGain(gain, input, absl::MakeSpan(output)); + sfz::setSIMDOpStatus(sfz::SIMDOps::gain, true); + sfz::applyGain(gain, input, absl::MakeSpan(output)); } } BENCHMARK_DEFINE_F(GainArray, Scalar_Unaligned)(benchmark::State& state) { for (auto _ : state) { - sfz::applyGain(absl::MakeSpan(gain).subspan(1), absl::MakeSpan(input).subspan(1), absl::MakeSpan(output).subspan(1)); + sfz::setSIMDOpStatus(sfz::SIMDOps::gain, false); + sfz::applyGain(absl::MakeSpan(gain).subspan(1), absl::MakeSpan(input).subspan(1), absl::MakeSpan(output).subspan(1)); } } BENCHMARK_DEFINE_F(GainArray, SIMD_Unaligned)(benchmark::State& state) { for (auto _ : state) { - sfz::applyGain(absl::MakeSpan(gain).subspan(1), absl::MakeSpan(input).subspan(1), absl::MakeSpan(output).subspan(1)); + sfz::setSIMDOpStatus(sfz::SIMDOps::gain, true); + sfz::applyGain(absl::MakeSpan(gain).subspan(1), absl::MakeSpan(input).subspan(1), absl::MakeSpan(output).subspan(1)); } } diff --git a/benchmarks/BM_interpolationCast.cpp b/benchmarks/BM_interpolationCast.cpp deleted file mode 100644 index ea12845a..00000000 --- a/benchmarks/BM_interpolationCast.cpp +++ /dev/null @@ -1,76 +0,0 @@ -// SPDX-License-Identifier: BSD-2-Clause - -// This code is part of the sfizz library and is licensed under a BSD 2-clause -// license. You should have receive a LICENSE.md file along with the code. -// If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz - -#include "SIMDHelpers.h" -#include -#include -#include -#include -#include - -// In this one we have an array of jumps - -constexpr float maxJump { 4 }; - -class InterpolationCast : public benchmark::Fixture { -public: - void SetUp(const ::benchmark::State& state) { - std::random_device rd { }; - std::mt19937 gen { rd() }; - std::uniform_real_distribution dist { 0, maxJump }; - jumps = std::vector(state.range(0)); - leftCoeffs = std::vector(state.range(0)); - rightCoeffs = std::vector(state.range(0)); - floatJumps = std::vector(state.range(0)); - absl::c_generate(floatJumps, [&]() { return dist(gen); }); - } - - void TearDown(const ::benchmark::State& /* state */) { - - } - - std::vector jumps; - std::vector leftCoeffs; - std::vector rightCoeffs; - std::vector floatJumps; -}; - - -BENCHMARK_DEFINE_F(InterpolationCast, Scalar)(benchmark::State& state) { - for (auto _ : state) - { - sfz::sfzInterpolationCast(floatJumps, absl::MakeSpan(jumps), absl::MakeSpan(leftCoeffs), absl::MakeSpan(rightCoeffs)); - } -} - -BENCHMARK_DEFINE_F(InterpolationCast, SIMD)(benchmark::State& state) { - for (auto _ : state) - { - sfz::sfzInterpolationCast(floatJumps, absl::MakeSpan(jumps), absl::MakeSpan(leftCoeffs), absl::MakeSpan(rightCoeffs)); - } -} - -BENCHMARK_DEFINE_F(InterpolationCast, Scalar_Unaligned)(benchmark::State& state) { - for (auto _ : state) - { - sfz::sfzInterpolationCast(absl::MakeSpan(floatJumps).subspan(1), absl::MakeSpan(jumps).subspan(3), absl::MakeSpan(leftCoeffs).subspan(2), absl::MakeSpan(rightCoeffs).subspan(1)); - } -} - -BENCHMARK_DEFINE_F(InterpolationCast, SIMD_Unaligned)(benchmark::State& state) { - for (auto _ : state) - { - sfz::sfzInterpolationCast(absl::MakeSpan(floatJumps).subspan(1), absl::MakeSpan(jumps).subspan(3), absl::MakeSpan(leftCoeffs).subspan(2), absl::MakeSpan(rightCoeffs).subspan(1)); - } -} - - -// Register the function as a benchmark -BENCHMARK_REGISTER_F(InterpolationCast, Scalar)->RangeMultiplier(2)->Range((2<<6), (2<<12)); -BENCHMARK_REGISTER_F(InterpolationCast, SIMD)->RangeMultiplier(2)->Range((2<<6), (2<<12)); -BENCHMARK_REGISTER_F(InterpolationCast, Scalar_Unaligned)->RangeMultiplier(2)->Range((2<<6), (2<<12)); -BENCHMARK_REGISTER_F(InterpolationCast, SIMD_Unaligned)->RangeMultiplier(2)->Range((2<<6), (2<<12)); -BENCHMARK_MAIN(); diff --git a/benchmarks/BM_interpolators.cpp b/benchmarks/BM_interpolators.cpp new file mode 100644 index 00000000..77337576 --- /dev/null +++ b/benchmarks/BM_interpolators.cpp @@ -0,0 +1,87 @@ +// SPDX-License-Identifier: BSD-2-Clause + +// This code is part of the sfizz library and is licensed under a BSD 2-clause +// license. You should have receive a LICENSE.md file along with the code. +// If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz + +#include "Config.h" +#include "Interpolators.h" +#include "ScopedFTZ.h" +#include "absl/types/span.h" +#include +#include + +class Interpolators : public benchmark::Fixture { +public: + void SetUp(const ::benchmark::State& state) + { + std::random_device rd { }; + std::mt19937 gen { rd() }; + std::uniform_real_distribution dist { -1.0f, 1.0f }; + + const size_t numFramesIn = state.range(0); + inputBuffer = std::vector(numFramesIn + 2 * sfz::config::excessFileFrames); + + // any ratio will do, compute time will be proportional + static constexpr float ratio = 1.234; + + const size_t numFramesOut = static_cast(std::ceil(numFramesIn * ratio)); + input = absl::MakeSpan(inputBuffer).subspan(sfz::config::excessFileFrames, numFramesIn); + output = std::vector(numFramesOut); + std::generate(input.begin(), input.end(), [&]() { return dist(gen); }); + } + + void TearDown(const ::benchmark::State& /* state */) + { + } + + std::vector inputBuffer; + absl::Span input; + std::vector output; + + enum { excessFrames = 8 }; +}; + +template +static void doInterpolation(absl::Span input, absl::Span output) +{ + const float kOutToIn = static_cast(input.size()) / output.size(); + + for (size_t iOut = 0; iOut < output.size(); ++iOut) { + float posIn = iOut * kOutToIn; + unsigned dec = static_cast(posIn); + float frac = posIn - dec; + output[iOut] = sfz::interpolate(&input[dec], frac); + } +} + +BENCHMARK_DEFINE_F(Interpolators, Linear)(benchmark::State& state) +{ + ScopedFTZ ftz; + + for (auto _ : state) { + doInterpolation(input, absl::MakeSpan(output)); + } +} + +BENCHMARK_DEFINE_F(Interpolators, Hermite3)(benchmark::State& state) +{ + ScopedFTZ ftz; + + for (auto _ : state) { + doInterpolation(input, absl::MakeSpan(output)); + } +} + +BENCHMARK_DEFINE_F(Interpolators, Bspline3)(benchmark::State& state) +{ + ScopedFTZ ftz; + + for (auto _ : state) { + doInterpolation(input, absl::MakeSpan(output)); + } +} + +BENCHMARK_REGISTER_F(Interpolators, Linear)->RangeMultiplier(4)->Range(1 << 4, 1 << 12); +BENCHMARK_REGISTER_F(Interpolators, Hermite3)->RangeMultiplier(4)->Range(1 << 4, 1 << 12); +BENCHMARK_REGISTER_F(Interpolators, Bspline3)->RangeMultiplier(4)->Range(1 << 4, 1 << 12); diff --git a/benchmarks/BM_looping.cpp b/benchmarks/BM_looping.cpp deleted file mode 100644 index 6a465ffc..00000000 --- a/benchmarks/BM_looping.cpp +++ /dev/null @@ -1,78 +0,0 @@ -// SPDX-License-Identifier: BSD-2-Clause - -// This code is part of the sfizz library and is licensed under a BSD 2-clause -// license. You should have receive a LICENSE.md file along with the code. -// If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz - -#include -#include "SIMDHelpers.h" -#include -#include -#include -#include - -// In this one we have an array of indices - -constexpr int loopStart { 5 }; -constexpr int loopEnd { 1076 }; -constexpr float maxJump { 4 }; - -class LoopingFixture : public benchmark::Fixture { -public: - void SetUp(const ::benchmark::State& state) { - std::random_device rd { }; - std::mt19937 gen { rd() }; - std::uniform_real_distribution dist { 0, maxJump }; - indices = std::vector(state.range(0)); - leftCoeffs = std::vector(state.range(0)); - rightCoeffs = std::vector(state.range(0)); - jumps = std::vector(state.range(0)); - absl::c_generate(jumps, [&]() { return dist(gen); }); - } - - void TearDown(const ::benchmark::State& /* state */) { - - } - - std::vector indices; - std::vector leftCoeffs; - std::vector rightCoeffs; - std::vector jumps; -}; - - -BENCHMARK_DEFINE_F(LoopingFixture, Scalar)(benchmark::State& state) { - for (auto _ : state) - { - sfz::loopingSFZIndex(jumps, absl::MakeSpan(leftCoeffs), absl::MakeSpan(rightCoeffs), absl::MakeSpan(indices), 2.5f, loopEnd, loopStart); - } -} - -BENCHMARK_DEFINE_F(LoopingFixture, SIMD)(benchmark::State& state) { - for (auto _ : state) - { - sfz::loopingSFZIndex(jumps, absl::MakeSpan(leftCoeffs), absl::MakeSpan(rightCoeffs), absl::MakeSpan(indices), 2.5f, loopEnd, loopStart); - } -} - -BENCHMARK_DEFINE_F(LoopingFixture, Scalar_Unaligned)(benchmark::State& state) { - for (auto _ : state) - { - sfz::loopingSFZIndex(absl::MakeSpan(jumps).subspan(1), absl::MakeSpan(leftCoeffs).subspan(2), absl::MakeSpan(rightCoeffs).subspan(1), absl::MakeSpan(indices).subspan(3), 2.5f, loopEnd, loopStart); - } -} - -BENCHMARK_DEFINE_F(LoopingFixture, SIMD_Unaligned)(benchmark::State& state) { - for (auto _ : state) - { - sfz::loopingSFZIndex(absl::MakeSpan(jumps).subspan(1), absl::MakeSpan(leftCoeffs).subspan(2), absl::MakeSpan(rightCoeffs).subspan(1), absl::MakeSpan(indices).subspan(3), 2.5f, loopEnd, loopStart); - } -} - - -// Register the function as a benchmark -BENCHMARK_REGISTER_F(LoopingFixture, Scalar)->RangeMultiplier(2)->Range((2<<6), (2<<12)); -BENCHMARK_REGISTER_F(LoopingFixture, SIMD)->RangeMultiplier(2)->Range((2<<6), (2<<12)); -BENCHMARK_REGISTER_F(LoopingFixture, Scalar_Unaligned)->RangeMultiplier(2)->Range((2<<6), (2<<12)); -BENCHMARK_REGISTER_F(LoopingFixture, SIMD_Unaligned)->RangeMultiplier(2)->Range((2<<6), (2<<12)); -BENCHMARK_MAIN(); diff --git a/benchmarks/BM_mathfuns.cpp b/benchmarks/BM_mathfuns.cpp index aa11b966..6cf78e9a 100644 --- a/benchmarks/BM_mathfuns.cpp +++ b/benchmarks/BM_mathfuns.cpp @@ -45,96 +45,6 @@ BENCHMARK_DEFINE_F(MyFixture, Dummy) } } -BENCHMARK_DEFINE_F(MyFixture, ScalarExp) -(benchmark::State& state) -{ - for (auto _ : state) { - sfz::exp(source, absl::MakeSpan(result)); - benchmark::DoNotOptimize(result); - } -} - -BENCHMARK_DEFINE_F(MyFixture, SIMDExp) -(benchmark::State& state) -{ - for (auto _ : state) { - sfz::exp(source, absl::MakeSpan(result)); - benchmark::DoNotOptimize(result); - } -} - -BENCHMARK_DEFINE_F(MyFixture, ScalarExp_Unaligned) -(benchmark::State& state) -{ - for (auto _ : state) { - sfz::exp(absl::MakeSpan(source).subspan(1), absl::MakeSpan(result).subspan(1)); - benchmark::DoNotOptimize(result); - } -} - -BENCHMARK_DEFINE_F(MyFixture, SIMDExp_Unaligned) -(benchmark::State& state) -{ - for (auto _ : state) { - sfz::exp(absl::MakeSpan(source).subspan(1), absl::MakeSpan(result).subspan(1)); - benchmark::DoNotOptimize(result); - } -} - -BENCHMARK_DEFINE_F(MyFixture, ScalarLog) -(benchmark::State& state) -{ - for (auto _ : state) { - sfz::log(source, absl::MakeSpan(result)); - benchmark::DoNotOptimize(result); - } -} - -BENCHMARK_DEFINE_F(MyFixture, SIMDLog) -(benchmark::State& state) -{ - for (auto _ : state) { - sfz::log(source, absl::MakeSpan(result)); - benchmark::DoNotOptimize(result); - } -} - -BENCHMARK_DEFINE_F(MyFixture, ScalarSin) -(benchmark::State& state) -{ - for (auto _ : state) { - sfz::sin(source, absl::MakeSpan(result)); - benchmark::DoNotOptimize(result); - } -} - -BENCHMARK_DEFINE_F(MyFixture, SIMDSin) -(benchmark::State& state) -{ - for (auto _ : state) { - sfz::sin(source, absl::MakeSpan(result)); - benchmark::DoNotOptimize(result); - } -} - -BENCHMARK_DEFINE_F(MyFixture, ScalarCos) -(benchmark::State& state) -{ - for (auto _ : state) { - sfz::cos(source, absl::MakeSpan(result)); - benchmark::DoNotOptimize(result); - } -} - -BENCHMARK_DEFINE_F(MyFixture, SIMDCos) -(benchmark::State& state) -{ - for (auto _ : state) { - sfz::cos(source, absl::MakeSpan(result)); - benchmark::DoNotOptimize(result); - } -} - BENCHMARK_DEFINE_F(MyFixture, ScalarLibmFloorLog2) (benchmark::State& state) { @@ -159,16 +69,6 @@ BENCHMARK_DEFINE_F(MyFixture, ScalarFastFloorLog2) } BENCHMARK_REGISTER_F(MyFixture, Dummy)->RangeMultiplier(4)->Range(1 << 6, 1 << 10); -BENCHMARK_REGISTER_F(MyFixture, ScalarExp)->RangeMultiplier(4)->Range(1 << 6, 1 << 10); -BENCHMARK_REGISTER_F(MyFixture, SIMDExp)->RangeMultiplier(4)->Range(1 << 6, 1 << 10); -BENCHMARK_REGISTER_F(MyFixture, ScalarExp_Unaligned)->RangeMultiplier(4)->Range(1 << 6, 1 << 10); -BENCHMARK_REGISTER_F(MyFixture, SIMDExp_Unaligned)->RangeMultiplier(4)->Range(1 << 6, 1 << 10); -BENCHMARK_REGISTER_F(MyFixture, ScalarLog)->RangeMultiplier(4)->Range(1 << 6, 1 << 10); -BENCHMARK_REGISTER_F(MyFixture, SIMDLog)->RangeMultiplier(4)->Range(1 << 6, 1 << 10); -BENCHMARK_REGISTER_F(MyFixture, ScalarSin)->RangeMultiplier(4)->Range(1 << 6, 1 << 10); -BENCHMARK_REGISTER_F(MyFixture, SIMDSin)->RangeMultiplier(4)->Range(1 << 6, 1 << 10); -BENCHMARK_REGISTER_F(MyFixture, ScalarCos)->RangeMultiplier(4)->Range(1 << 6, 1 << 10); -BENCHMARK_REGISTER_F(MyFixture, SIMDCos)->RangeMultiplier(4)->Range(1 << 6, 1 << 10); BENCHMARK_REGISTER_F(MyFixture, ScalarLibmFloorLog2)->RangeMultiplier(4)->Range(1 << 6, 1 << 10); BENCHMARK_REGISTER_F(MyFixture, ScalarFastFloorLog2)->RangeMultiplier(4)->Range(1 << 6, 1 << 10); diff --git a/benchmarks/BM_mean.cpp b/benchmarks/BM_mean.cpp index 5ea0cc93..7ac9e59c 100644 --- a/benchmarks/BM_mean.cpp +++ b/benchmarks/BM_mean.cpp @@ -34,7 +34,8 @@ BENCHMARK_DEFINE_F(MeanArray, Scalar) (benchmark::State& state) { for (auto _ : state) { - auto result = sfz::mean(input); + sfz::setSIMDOpStatus(sfz::SIMDOps::mean, false); + auto result = sfz::mean(input); benchmark::DoNotOptimize(result); } } @@ -43,7 +44,8 @@ BENCHMARK_DEFINE_F(MeanArray, SIMD) (benchmark::State& state) { for (auto _ : state) { - auto result = sfz::mean(input); + sfz::setSIMDOpStatus(sfz::SIMDOps::mean, true); + auto result = sfz::mean(input); benchmark::DoNotOptimize(result); } } @@ -52,7 +54,8 @@ BENCHMARK_DEFINE_F(MeanArray, Scalar_Unaligned) (benchmark::State& state) { for (auto _ : state) { - auto result = sfz::mean(absl::MakeSpan(input).subspan(1)); + sfz::setSIMDOpStatus(sfz::SIMDOps::mean, false); + auto result = sfz::mean(absl::MakeSpan(input).subspan(1)); benchmark::DoNotOptimize(result); } } @@ -61,7 +64,8 @@ BENCHMARK_DEFINE_F(MeanArray, SIMD_Unaligned) (benchmark::State& state) { for (auto _ : state) { - auto result = sfz::mean(absl::MakeSpan(input).subspan(1)); + sfz::setSIMDOpStatus(sfz::SIMDOps::mean, true); + auto result = sfz::mean(absl::MakeSpan(input).subspan(1)); benchmark::DoNotOptimize(result); } } diff --git a/benchmarks/BM_meanSquared.cpp b/benchmarks/BM_meanSquared.cpp index 41468cc0..0b1c159e 100644 --- a/benchmarks/BM_meanSquared.cpp +++ b/benchmarks/BM_meanSquared.cpp @@ -34,7 +34,8 @@ BENCHMARK_DEFINE_F(MeanSquaredArray, Scalar) (benchmark::State& state) { for (auto _ : state) { - auto result = sfz::meanSquared(input); + sfz::setSIMDOpStatus(sfz::SIMDOps::meanSquared, false); + auto result = sfz::meanSquared(input); benchmark::DoNotOptimize(result); } } @@ -43,7 +44,8 @@ BENCHMARK_DEFINE_F(MeanSquaredArray, SIMD) (benchmark::State& state) { for (auto _ : state) { - auto result = sfz::meanSquared(input); + sfz::setSIMDOpStatus(sfz::SIMDOps::meanSquared, true); + auto result = sfz::meanSquared(input); benchmark::DoNotOptimize(result); } } @@ -52,7 +54,8 @@ BENCHMARK_DEFINE_F(MeanSquaredArray, Scalar_Unaligned) (benchmark::State& state) { for (auto _ : state) { - auto result = sfz::meanSquared(absl::MakeSpan(input).subspan(1)); + sfz::setSIMDOpStatus(sfz::SIMDOps::meanSquared, false); + auto result = sfz::meanSquared(absl::MakeSpan(input).subspan(1)); benchmark::DoNotOptimize(result); } } @@ -61,7 +64,8 @@ BENCHMARK_DEFINE_F(MeanSquaredArray, SIMD_Unaligned) (benchmark::State& state) { for (auto _ : state) { - auto result = sfz::meanSquared(absl::MakeSpan(input).subspan(1)); + sfz::setSIMDOpStatus(sfz::SIMDOps::meanSquared, true); + auto result = sfz::meanSquared(absl::MakeSpan(input).subspan(1)); benchmark::DoNotOptimize(result); } } diff --git a/benchmarks/BM_multiplyAdd.cpp b/benchmarks/BM_multiplyAdd.cpp index 4bb01e3b..d4da2b36 100644 --- a/benchmarks/BM_multiplyAdd.cpp +++ b/benchmarks/BM_multiplyAdd.cpp @@ -46,28 +46,32 @@ BENCHMARK_DEFINE_F(MultiplyAdd, Straight)(benchmark::State& state) { BENCHMARK_DEFINE_F(MultiplyAdd, Scalar)(benchmark::State& state) { for (auto _ : state) { - sfz::multiplyAdd(gain, input, absl::MakeSpan(output)); + sfz::setSIMDOpStatus(sfz::SIMDOps::multiplyAdd, false); + sfz::multiplyAdd(gain, input, absl::MakeSpan(output)); } } BENCHMARK_DEFINE_F(MultiplyAdd, SIMD)(benchmark::State& state) { for (auto _ : state) { - sfz::multiplyAdd(gain, input, absl::MakeSpan(output)); + sfz::setSIMDOpStatus(sfz::SIMDOps::multiplyAdd, true); + sfz::multiplyAdd(gain, input, absl::MakeSpan(output)); } } BENCHMARK_DEFINE_F(MultiplyAdd, Scalar_Unaligned)(benchmark::State& state) { for (auto _ : state) { - sfz::multiplyAdd(absl::MakeSpan(gain).subspan(1), absl::MakeSpan(input).subspan(1), absl::MakeSpan(output).subspan(1)); + sfz::setSIMDOpStatus(sfz::SIMDOps::multiplyAdd, false); + sfz::multiplyAdd(absl::MakeSpan(gain).subspan(1), absl::MakeSpan(input).subspan(1), absl::MakeSpan(output).subspan(1)); } } BENCHMARK_DEFINE_F(MultiplyAdd, SIMD_Unaligned)(benchmark::State& state) { for (auto _ : state) { - sfz::multiplyAdd(absl::MakeSpan(gain).subspan(1), absl::MakeSpan(input).subspan(1), absl::MakeSpan(output).subspan(1)); + sfz::setSIMDOpStatus(sfz::SIMDOps::multiplyAdd, true); + sfz::multiplyAdd(absl::MakeSpan(gain).subspan(1), absl::MakeSpan(input).subspan(1), absl::MakeSpan(output).subspan(1)); } } diff --git a/benchmarks/BM_multiplyAddFixedGain.cpp b/benchmarks/BM_multiplyAddFixedGain.cpp index e05635ba..7bbd4595 100644 --- a/benchmarks/BM_multiplyAddFixedGain.cpp +++ b/benchmarks/BM_multiplyAddFixedGain.cpp @@ -48,7 +48,8 @@ BENCHMARK_DEFINE_F(MultiplyAddFixedGain, Scalar) (benchmark::State& state) { for (auto _ : state) { - sfz::multiplyAdd(gain, input, absl::MakeSpan(output)); + sfz::setSIMDOpStatus(sfz::SIMDOps::multiplyAdd1, false); + sfz::multiplyAdd1(gain, input, absl::MakeSpan(output)); } } @@ -56,7 +57,8 @@ BENCHMARK_DEFINE_F(MultiplyAddFixedGain, SIMD) (benchmark::State& state) { for (auto _ : state) { - sfz::multiplyAdd(gain, input, absl::MakeSpan(output)); + sfz::setSIMDOpStatus(sfz::SIMDOps::multiplyAdd1, true); + sfz::multiplyAdd1(gain, input, absl::MakeSpan(output)); } } @@ -64,7 +66,8 @@ BENCHMARK_DEFINE_F(MultiplyAddFixedGain, Scalar_Unaligned) (benchmark::State& state) { for (auto _ : state) { - sfz::multiplyAdd(gain, absl::MakeSpan(input).subspan(1), absl::MakeSpan(output).subspan(1)); + sfz::setSIMDOpStatus(sfz::SIMDOps::multiplyAdd1, false); + sfz::multiplyAdd1(gain, absl::MakeSpan(input).subspan(1), absl::MakeSpan(output).subspan(1)); } } @@ -72,7 +75,8 @@ BENCHMARK_DEFINE_F(MultiplyAddFixedGain, SIMD_Unaligned) (benchmark::State& state) { for (auto _ : state) { - sfz::multiplyAdd(gain, absl::MakeSpan(input).subspan(1), absl::MakeSpan(output).subspan(1)); + sfz::setSIMDOpStatus(sfz::SIMDOps::multiplyAdd1, true); + sfz::multiplyAdd1(gain, absl::MakeSpan(input).subspan(1), absl::MakeSpan(output).subspan(1)); } } diff --git a/benchmarks/BM_pan.cpp b/benchmarks/BM_pan.cpp deleted file mode 100644 index 8cafb079..00000000 --- a/benchmarks/BM_pan.cpp +++ /dev/null @@ -1,83 +0,0 @@ -// SPDX-License-Identifier: BSD-2-Clause - -// This code is part of the sfizz library and is licensed under a BSD 2-clause -// license. You should have receive a LICENSE.md file along with the code. -// If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz - -#include "SIMDHelpers.h" -#include -#include -#include -#include -#include -#include -#include "Config.h" -#include "ScopedFTZ.h" -#include "absl/types/span.h" - -class PanArray : public benchmark::Fixture { -public: - void SetUp(const ::benchmark::State& state) { - std::random_device rd { }; - std::mt19937 gen { rd() }; - std::uniform_real_distribution dist { 0.001f, 1.0f }; - pan = std::vector(state.range(0)); - left = std::vector(state.range(0)); - right = std::vector(state.range(0)); - std::generate(pan.begin(), pan.end(), [&]() { return dist(gen); }); - std::generate(right.begin(), right.end(), [&]() { return dist(gen); }); - std::generate(left.begin(), left.end(), [&]() { return dist(gen); }); - temp1 = std::vector(state.range(0)); - temp2 = std::vector(state.range(0)); - span1 = absl::MakeSpan(temp1); - span2 = absl::MakeSpan(temp2); - } - - void TearDown(const ::benchmark::State& /* state */) { - - } - - std::vector pan; - std::vector left; - std::vector right; - std::vector temp1; - std::vector temp2; - absl::Span span1; - absl::Span span2; -}; - - -BENCHMARK_DEFINE_F(PanArray, Scalar)(benchmark::State& state) { - ScopedFTZ ftz; - for (auto _ : state) - { - sfz::pan(pan, absl::MakeSpan(left), absl::MakeSpan(right)); - } -} - -BENCHMARK_DEFINE_F(PanArray, SIMD)(benchmark::State& state) { - ScopedFTZ ftz; - for (auto _ : state) - { - sfz::pan(pan, absl::MakeSpan(left), absl::MakeSpan(right)); - } -} - -BENCHMARK_DEFINE_F(PanArray, BlockOps)(benchmark::State& state) { - ScopedFTZ ftz; - for (auto _ : state) - { - sfz::fill(span2, 1.0f); - sfz::add(span1, span2); - sfz::applyGain(piFour(), span2); - sfz::cos(span2, span1); - sfz::sin(span2, span2); - sfz::applyGain(span1, absl::MakeSpan(left)); - sfz::applyGain(span2, absl::MakeSpan(right)); - } -} - -BENCHMARK_REGISTER_F(PanArray, Scalar)->RangeMultiplier(4)->Range(1 << 2, 1 << 12); -BENCHMARK_REGISTER_F(PanArray, SIMD)->RangeMultiplier(4)->Range(1 << 2, 1 << 12); -BENCHMARK_REGISTER_F(PanArray, BlockOps)->RangeMultiplier(4)->Range(1 << 2, 1 << 12); -BENCHMARK_MAIN(); diff --git a/benchmarks/BM_ramp.cpp b/benchmarks/BM_ramp.cpp index 3d7ee14e..b5a5209f 100644 --- a/benchmarks/BM_ramp.cpp +++ b/benchmarks/BM_ramp.cpp @@ -30,7 +30,8 @@ static void LinearScalar(benchmark::State& state) { for (auto _ : state) { auto value = dist(gen); - sfz::linearRamp(absl::MakeSpan(output), 0.0f, value); + sfz::setSIMDOpStatus(sfz::SIMDOps::linearRamp, false); + sfz::linearRamp(absl::MakeSpan(output), 0.0f, value); } } @@ -42,7 +43,8 @@ static void LinearSIMD(benchmark::State& state) { for (auto _ : state) { auto value = dist(gen); - sfz::linearRamp(absl::MakeSpan(output), 0.0f, value); + sfz::setSIMDOpStatus(sfz::SIMDOps::linearRamp, true); + sfz::linearRamp(absl::MakeSpan(output), 0.0f, value); } } static void LinearScalarUnaligned(benchmark::State& state) { @@ -53,7 +55,8 @@ static void LinearScalarUnaligned(benchmark::State& state) { for (auto _ : state) { auto value = dist(gen); - sfz::linearRamp(absl::MakeSpan(output).subspan(1), 0.0f, value); + sfz::setSIMDOpStatus(sfz::SIMDOps::linearRamp, false); + sfz::linearRamp(absl::MakeSpan(output).subspan(1), 0.0f, value); } } @@ -65,7 +68,8 @@ static void LinearSIMDUnaligned(benchmark::State& state) { for (auto _ : state) { auto value = dist(gen); - sfz::linearRamp(absl::MakeSpan(output).subspan(1), 0.0f, value); + sfz::setSIMDOpStatus(sfz::SIMDOps::linearRamp, true); + sfz::linearRamp(absl::MakeSpan(output).subspan(1), 0.0f, value); } } @@ -77,7 +81,8 @@ static void MulScalar(benchmark::State& state) { for (auto _ : state) { auto value = dist(gen); - sfz::multiplicativeRamp(absl::MakeSpan(output), 1.0f, value); + sfz::setSIMDOpStatus(sfz::SIMDOps::multiplicativeRamp, false); + sfz::multiplicativeRamp(absl::MakeSpan(output), 1.0f, value); } } @@ -89,7 +94,8 @@ static void MulSIMD(benchmark::State& state) { for (auto _ : state) { auto value = dist(gen); - sfz::multiplicativeRamp(absl::MakeSpan(output), 1.0f, value); + sfz::setSIMDOpStatus(sfz::SIMDOps::multiplicativeRamp, true); + sfz::multiplicativeRamp(absl::MakeSpan(output), 1.0f, value); } } static void MulScalarUnaligned(benchmark::State& state) { @@ -100,7 +106,8 @@ static void MulScalarUnaligned(benchmark::State& state) { for (auto _ : state) { auto value = dist(gen); - sfz::multiplicativeRamp(absl::MakeSpan(output).subspan(1), 1.0f, value); + sfz::setSIMDOpStatus(sfz::SIMDOps::multiplicativeRamp, false); + sfz::multiplicativeRamp(absl::MakeSpan(output).subspan(1), 1.0f, value); } } @@ -112,64 +119,8 @@ static void MulSIMDUnaligned(benchmark::State& state) { for (auto _ : state) { auto value = dist(gen); - sfz::multiplicativeRamp(absl::MakeSpan(output).subspan(1), 1.0f, value); - } -} - -static void LogDomainScalar(benchmark::State& state) { - sfz::Buffer output(state.range(0)); - std::random_device rd { }; - std::mt19937 gen { rd() }; - std::uniform_real_distribution dist { 1, 2 }; - for (auto _ : state) - { - auto value = dist(gen); - sfz::linearRamp(absl::MakeSpan(output), 1.0f, value); - sfz::applyGain(std::log(2.0f), absl::MakeSpan(output)); - sfz::exp(output, absl::MakeSpan(output)); - } -} - -static void LogDomainSIMD(benchmark::State& state) { - sfz::Buffer output(state.range(0)); - std::random_device rd { }; - std::mt19937 gen { rd() }; - std::uniform_real_distribution dist { 1, 2 }; - for (auto _ : state) - { - auto value = dist(gen); - sfz::linearRamp(absl::MakeSpan(output), 1.0f, value); - sfz::applyGain(std::log(2.0f), absl::MakeSpan(output)); - sfz::exp(output, absl::MakeSpan(output)); - } -} -static void LogDomainScalarUnaligned(benchmark::State& state) { - sfz::Buffer output(state.range(0)); - std::random_device rd { }; - std::mt19937 gen { rd() }; - std::uniform_real_distribution dist { 1, 2 }; - for (auto _ : state) - { - auto value = dist(gen); - auto outputSpan = absl::MakeSpan(output).subspan(1); - sfz::linearRamp(outputSpan, 1.0f, value); - sfz::applyGain(std::log(2.0f), outputSpan); - sfz::exp(outputSpan, outputSpan); - } -} - -static void LogDomainSIMDUnaligned(benchmark::State& state) { - sfz::Buffer output(state.range(0)); - std::random_device rd { }; - std::mt19937 gen { rd() }; - std::uniform_real_distribution dist { 1, 2 }; - for (auto _ : state) - { - auto value = dist(gen); - auto outputSpan = absl::MakeSpan(output).subspan(1); - sfz::linearRamp(outputSpan, 1.0f, value); - sfz::applyGain(std::log(2.0f), outputSpan); - sfz::exp(outputSpan, outputSpan); + sfz::setSIMDOpStatus(sfz::SIMDOps::multiplicativeRamp, true); + sfz::multiplicativeRamp(absl::MakeSpan(output).subspan(1), 1.0f, value); } } @@ -183,8 +134,4 @@ BENCHMARK(MulScalar)->RangeMultiplier(4)->Range((1 << 2), (1 << 12)); BENCHMARK(MulSIMD)->RangeMultiplier(4)->Range((1 << 2), (1 << 12)); BENCHMARK(MulScalarUnaligned)->RangeMultiplier(4)->Range((1 << 2), (1 << 12)); BENCHMARK(MulSIMDUnaligned)->RangeMultiplier(4)->Range((1 << 2), (1 << 12)); -BENCHMARK(LogDomainScalar)->RangeMultiplier(4)->Range((1 << 2), (1 << 12)); -BENCHMARK(LogDomainSIMD)->RangeMultiplier(4)->Range((1 << 2), (1 << 12)); -BENCHMARK(LogDomainScalarUnaligned)->RangeMultiplier(4)->Range((1 << 2), (1 << 12)); -BENCHMARK(LogDomainSIMDUnaligned)->RangeMultiplier(4)->Range((1 << 2), (1 << 12)); BENCHMARK_MAIN(); diff --git a/benchmarks/BM_random.cpp b/benchmarks/BM_random.cpp index e69de29b..d5220322 100644 --- a/benchmarks/BM_random.cpp +++ b/benchmarks/BM_random.cpp @@ -0,0 +1,94 @@ +// SPDX-License-Identifier: BSD-2-Clause + +// This code is part of the sfizz library and is licensed under a BSD 2-clause +// license. You should have receive a LICENSE.md file along with the code. +// If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz + +#include "MathHelpers.h" +#include +#include +#include + +class RandomFill : public benchmark::Fixture { +public: + void SetUp(const ::benchmark::State& state) { + output = std::vector(state.range(0)); + } + + void TearDown(const ::benchmark::State& state) { + UNUSED(state); + } + + std::vector output; +}; + +BENCHMARK_DEFINE_F(RandomFill, StdRandom)(benchmark::State& state) { + std::minstd_rand prng; + std::uniform_real_distribution dist(0.0f, 1.0f); + + for (auto _ : state) + { + for (float &out : output) + out = dist(prng); + } +} + +BENCHMARK_DEFINE_F(RandomFill, FastRandom)(benchmark::State& state) { + fast_rand prng; + fast_real_distribution dist(0.0f, 1.0f); + + for (auto _ : state) + { + for (float &out : output) + out = dist(prng); + } +} +BENCHMARK_DEFINE_F(RandomFill, StdRandomBipolar)(benchmark::State& state) { + std::minstd_rand prng; + std::uniform_real_distribution dist(-1.0f, 1.0f); + + for (auto _ : state) + { + for (float &out : output) + out = dist(prng); + } +} + +BENCHMARK_DEFINE_F(RandomFill, FastRandomBipolar)(benchmark::State& state) { + fast_rand prng; + fast_real_distribution dist(-1.0f, 1.0f); + + for (auto _ : state) + { + for (float &out : output) + out = dist(prng); + } +} + +BENCHMARK_DEFINE_F(RandomFill, StdNormal)(benchmark::State& state) { + std::minstd_rand prng; + std::normal_distribution dist(0.0f, 0.25f); + + for (auto _ : state) + { + for (float &out : output) + out = dist(prng); + } +} + +BENCHMARK_DEFINE_F(RandomFill, FastNormal)(benchmark::State& state) { + fast_gaussian_generator generator(0.0f, 0.25f); + + for (auto _ : state) + { + for (float &out : output) + out = generator(); + } +} + +BENCHMARK_REGISTER_F(RandomFill, StdRandom)->RangeMultiplier(4)->Range(1 << 2, 1 << 12); +BENCHMARK_REGISTER_F(RandomFill, StdRandomBipolar)->RangeMultiplier(4)->Range(1 << 2, 1 << 12); +BENCHMARK_REGISTER_F(RandomFill, FastRandom)->RangeMultiplier(4)->Range(1 << 2, 1 << 12); +BENCHMARK_REGISTER_F(RandomFill, FastRandomBipolar)->RangeMultiplier(4)->Range(1 << 2, 1 << 12); +BENCHMARK_REGISTER_F(RandomFill, StdNormal)->RangeMultiplier(4)->Range(1 << 2, 1 << 12); +BENCHMARK_REGISTER_F(RandomFill, FastNormal)->RangeMultiplier(4)->Range(1 << 2, 1 << 12); diff --git a/benchmarks/BM_readChunk.cpp b/benchmarks/BM_readChunk.cpp index 4cdc2068..01b93aaa 100644 --- a/benchmarks/BM_readChunk.cpp +++ b/benchmarks/BM_readChunk.cpp @@ -61,7 +61,7 @@ BENCHMARK_DEFINE_F(FileFixture, JustRead)(benchmark::State& state) { { sfz::Buffer buffer { numFrames * sndfile.channels() }; sndfile.readf(buffer.data(), numFrames); - sfz::readInterleaved(buffer, output->getSpan(0), output->getSpan(1)); + sfz::readInterleaved(buffer, output->getSpan(0), output->getSpan(1)); } } @@ -75,7 +75,7 @@ BENCHMARK_DEFINE_F(FileFixture, AllocInside)(benchmark::State& state) { { sfz::Buffer buffer { chunkSize * sndfile.channels() }; auto read = sndfile.readf(buffer.data(), chunkSize); - sfz::readInterleaved( + sfz::readInterleaved( absl::MakeSpan(buffer).first(read), output->getSpan(0).subspan(framesRead), output->getSpan(1).subspan(framesRead) @@ -95,7 +95,7 @@ BENCHMARK_DEFINE_F(FileFixture, AllocOutside)(benchmark::State& state) { while(framesRead < numFrames) { auto read = sndfile.readf(buffer.data(), chunkSize); - sfz::readInterleaved( + sfz::readInterleaved( absl::MakeSpan(buffer).first(read), output->getSpan(0).subspan(framesRead), output->getSpan(1).subspan(framesRead) @@ -124,7 +124,7 @@ BENCHMARK_DEFINE_F(FileFixture, DrWavChunked)(benchmark::State& state) { while(framesRead < numFrames) { auto read = drwav_read_pcm_frames_f32(&wav, chunkSize, buffer.data()); - sfz::readInterleaved( + sfz::readInterleaved( absl::MakeSpan(buffer).first(read), output->getSpan(0).subspan(framesRead), output->getSpan(1).subspan(framesRead) diff --git a/benchmarks/BM_readChunkFlac.cpp b/benchmarks/BM_readChunkFlac.cpp new file mode 100644 index 00000000..df77f637 --- /dev/null +++ b/benchmarks/BM_readChunkFlac.cpp @@ -0,0 +1,120 @@ +// SPDX-License-Identifier: BSD-2-Clause + +// This code is part of the sfizz library and is licensed under a BSD 2-clause +// license. You should have receive a LICENSE.md file along with the code. +// If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz + +#include "SIMDHelpers.h" +#include "Buffer.h" +#include +#include +#include "ghc/filesystem.hpp" +#define DR_FLAC_IMPLEMENTATION +#include "dr_flac.h" +#include "AudioBuffer.h" +#include "absl/memory/memory.h" +#ifndef NDEBUG +#include +#endif + +class FileFixture : public benchmark::Fixture { +public: + void SetUp(const ::benchmark::State& /* state */) { + rootPath = getPath() / "sample1.flac"; + if (!ghc::filesystem::exists(rootPath)) { + #ifndef NDEBUG + std::cerr << "Can't find path" << '\n'; + #endif + std::terminate(); + } + + sndfile = SndfileHandle(rootPath.c_str()); + numFrames = static_cast(sndfile.frames()); + output = absl::make_unique>(sndfile.channels(), sndfile.frames()); + } + + void TearDown(const ::benchmark::State& /* state */) { + } + + ghc::filesystem::path getPath() + { + #ifdef __linux__ + char buf[PATH_MAX + 1]; + if (readlink("/proc/self/exe", buf, sizeof(buf) - 1) == -1) + return {}; + std::string str { buf }; + return str.substr(0, str.rfind('/')); + #elif _WIN32 + return ghc::filesystem::current_path(); + #endif + } + + std::unique_ptr> output; + SndfileHandle sndfile; + ghc::filesystem::path rootPath; + size_t numFrames; +}; + + +BENCHMARK_DEFINE_F(FileFixture, SndFileOnce)(benchmark::State& state) { + for (auto _ : state) + { + sfz::Buffer buffer { numFrames * sndfile.channels() }; + sndfile.readf(buffer.data(), numFrames); + sfz::readInterleaved(buffer, output->getSpan(0), output->getSpan(1)); + } +} + +BENCHMARK_DEFINE_F(FileFixture, SndFileChunked)(benchmark::State& state) { + for (auto _ : state) + { + auto chunkSize = static_cast(state.range(0)); + size_t framesRead = 0; + sndfile.seek(0, SEEK_SET); + while(framesRead < numFrames) + { + sfz::Buffer buffer { chunkSize * sndfile.channels() }; + auto read = sndfile.readf(buffer.data(), chunkSize); + sfz::readInterleaved( + absl::MakeSpan(buffer).first(read), + output->getSpan(0).subspan(framesRead), + output->getSpan(1).subspan(framesRead) + ); + framesRead += read; + } + } +} + + +BENCHMARK_DEFINE_F(FileFixture, DrWavChunked)(benchmark::State& state) { + auto chunkSize = static_cast(state.range(0)); + auto* flac = drflac_open_file(rootPath.c_str(), nullptr); + if (!flac) { + #ifndef NDEBUG + std::cerr << "Can't find path" << '\n'; + #endif + std::terminate(); + } + sfz::Buffer buffer { chunkSize * flac->channels }; + + for (auto _ : state) + { + size_t framesRead = 0; + drflac_seek_to_pcm_frame(flac, 0); + while(framesRead < numFrames) + { + auto read = drflac_read_pcm_frames_f32(flac, chunkSize, buffer.data()); + sfz::readInterleaved( + absl::MakeSpan(buffer).first(read), + output->getSpan(0).subspan(framesRead), + output->getSpan(1).subspan(framesRead) + ); + framesRead += read; + } + } +} + +BENCHMARK_REGISTER_F(FileFixture, SndFileOnce); +BENCHMARK_REGISTER_F(FileFixture, SndFileChunked)->RangeMultiplier(4)->Range((1 << 10), (1 << 16)); +BENCHMARK_REGISTER_F(FileFixture, DrWavChunked)->RangeMultiplier(4)->Range((1 << 10), (1 << 16)); +BENCHMARK_MAIN(); diff --git a/benchmarks/BM_readInterleaved.cpp b/benchmarks/BM_readInterleaved.cpp index 65bfcc8d..5d91b888 100644 --- a/benchmarks/BM_readInterleaved.cpp +++ b/benchmarks/BM_readInterleaved.cpp @@ -18,7 +18,8 @@ static void Scalar(benchmark::State& state) { std::iota(input.begin(), input.end(), 1.0f); for (auto _ : state) { - sfz::readInterleaved(input, absl::MakeSpan(outputLeft), absl::MakeSpan(outputRight)); + sfz::setSIMDOpStatus(sfz::SIMDOps::readInterleaved, false); + sfz::readInterleaved(input, absl::MakeSpan(outputLeft), absl::MakeSpan(outputRight)); } } @@ -29,7 +30,8 @@ static void SSE(benchmark::State& state) { std::iota(input.begin(), input.end(), 1.0f); for (auto _ : state) { - sfz::readInterleaved(input, absl::MakeSpan(outputLeft), absl::MakeSpan(outputRight)); + sfz::setSIMDOpStatus(sfz::SIMDOps::readInterleaved, true); + sfz::readInterleaved(input, absl::MakeSpan(outputLeft), absl::MakeSpan(outputRight)); } } @@ -39,7 +41,12 @@ static void Scalar_Unaligned(benchmark::State& state) { sfz::Buffer outputRight (state.range(0)); std::iota(input.begin(), input.end(), 1.0f); for (auto _ : state) { - sfz::readInterleaved(absl::MakeSpan(input).subspan(2), absl::MakeSpan(outputLeft), absl::MakeSpan(outputRight)); + sfz::setSIMDOpStatus(sfz::SIMDOps::readInterleaved, false); + sfz::readInterleaved( + absl::MakeSpan(input).subspan(2), + absl::MakeSpan(outputLeft), + absl::MakeSpan(outputRight) + ); } } @@ -49,7 +56,12 @@ static void SSE_Unaligned(benchmark::State& state) { sfz::Buffer outputRight (state.range(0)); std::iota(input.begin(), input.end(), 1.0f); for (auto _ : state) { - sfz::readInterleaved(absl::MakeSpan(input).subspan(2), absl::MakeSpan(outputLeft), absl::MakeSpan(outputRight)); + sfz::setSIMDOpStatus(sfz::SIMDOps::readInterleaved, true); + sfz::readInterleaved( + absl::MakeSpan(input).subspan(2), + absl::MakeSpan(outputLeft), + absl::MakeSpan(outputRight) + ); } } @@ -59,7 +71,12 @@ static void Scalar_Unaligned_2(benchmark::State& state) { sfz::Buffer outputRight (state.range(0)); std::iota(input.begin(), input.end(), 1.0f); for (auto _ : state) { - sfz::readInterleaved(absl::MakeSpan(input).subspan(2), absl::MakeSpan(outputLeft).subspan(1), absl::MakeSpan(outputRight).subspan(3)); + sfz::setSIMDOpStatus(sfz::SIMDOps::readInterleaved, false); + sfz::readInterleaved( + absl::MakeSpan(input).subspan(2), + absl::MakeSpan(outputLeft).subspan(1), + absl::MakeSpan(outputRight).subspan(3) + ); } } @@ -69,7 +86,12 @@ static void SSE_Unaligned_2(benchmark::State& state) { sfz::Buffer outputRight (state.range(0)); std::iota(input.begin(), input.end(), 1.0f); for (auto _ : state) { - sfz::readInterleaved(absl::MakeSpan(input).subspan(2), absl::MakeSpan(outputLeft).subspan(1), absl::MakeSpan(outputRight).subspan(3)); + sfz::setSIMDOpStatus(sfz::SIMDOps::readInterleaved, true); + sfz::readInterleaved( + absl::MakeSpan(input).subspan(2), + absl::MakeSpan(outputLeft).subspan(1), + absl::MakeSpan(outputRight).subspan(3) + ); } } diff --git a/benchmarks/BM_resample.cpp b/benchmarks/BM_resample.cpp index 75101ac3..ffd7f902 100644 --- a/benchmarks/BM_resample.cpp +++ b/benchmarks/BM_resample.cpp @@ -232,7 +232,7 @@ BENCHMARK_DEFINE_F(SndFile, HIIR2X_scalar)(benchmark::State& state) { for (auto _ : state) { auto baseBuffer = absl::make_unique>(numChannels, numFrames); - sfz::readInterleaved(*interleavedBuffer, baseBuffer->getSpan(0), baseBuffer->getSpan(1)); + sfz::readInterleaved(*interleavedBuffer, baseBuffer->getSpan(0), baseBuffer->getSpan(1)); auto outBuffer = upsample2x(*baseBuffer); benchmark::DoNotOptimize(outBuffer); } @@ -242,7 +242,7 @@ BENCHMARK_DEFINE_F(SndFile, HIIR4X_scalar)(benchmark::State& state) { for (auto _ : state) { auto baseBuffer = absl::make_unique>(numChannels, numFrames); - sfz::readInterleaved(*interleavedBuffer, baseBuffer->getSpan(0), baseBuffer->getSpan(1)); + sfz::readInterleaved(*interleavedBuffer, baseBuffer->getSpan(0), baseBuffer->getSpan(1)); auto outBuffer = upsample4x(*baseBuffer); benchmark::DoNotOptimize(outBuffer); } @@ -252,7 +252,7 @@ BENCHMARK_DEFINE_F(SndFile, HIIR8X_scalar)(benchmark::State& state) { for (auto _ : state) { auto baseBuffer = absl::make_unique>(numChannels, numFrames); - sfz::readInterleaved(*interleavedBuffer, baseBuffer->getSpan(0), baseBuffer->getSpan(1)); + sfz::readInterleaved(*interleavedBuffer, baseBuffer->getSpan(0), baseBuffer->getSpan(1)); auto outBuffer = upsample8x(*baseBuffer); benchmark::DoNotOptimize(outBuffer); } @@ -262,7 +262,7 @@ BENCHMARK_DEFINE_F(SndFile, HIIR2X_vector)(benchmark::State& state) { for (auto _ : state) { auto baseBuffer = absl::make_unique>(numChannels, numFrames); - sfz::readInterleaved(*interleavedBuffer, baseBuffer->getSpan(0), baseBuffer->getSpan(1)); + sfz::readInterleaved(*interleavedBuffer, baseBuffer->getSpan(0), baseBuffer->getSpan(1)); auto outBuffer = upsample2x(*baseBuffer); benchmark::DoNotOptimize(outBuffer); } @@ -272,7 +272,7 @@ BENCHMARK_DEFINE_F(SndFile, HIIR4X_vector)(benchmark::State& state) { for (auto _ : state) { auto baseBuffer = absl::make_unique>(numChannels, numFrames); - sfz::readInterleaved(*interleavedBuffer, baseBuffer->getSpan(0), baseBuffer->getSpan(1)); + sfz::readInterleaved(*interleavedBuffer, baseBuffer->getSpan(0), baseBuffer->getSpan(1)); auto outBuffer = upsample4x(*baseBuffer); benchmark::DoNotOptimize(outBuffer); } @@ -282,7 +282,7 @@ BENCHMARK_DEFINE_F(SndFile, HIIR8X_vector)(benchmark::State& state) { for (auto _ : state) { auto baseBuffer = absl::make_unique>(numChannels, numFrames); - sfz::readInterleaved(*interleavedBuffer, baseBuffer->getSpan(0), baseBuffer->getSpan(1)); + sfz::readInterleaved(*interleavedBuffer, baseBuffer->getSpan(0), baseBuffer->getSpan(1)); auto outBuffer = upsample8x(*baseBuffer); benchmark::DoNotOptimize(outBuffer); } @@ -300,7 +300,7 @@ BENCHMARK_DEFINE_F(SndFile, SRC2x_BEST)(benchmark::State& state) srcData.output_frames = static_cast(2 * numFrames); src_simple(&srcData, SRC_SINC_BEST_QUALITY, static_cast(numChannels)); auto outBuffer = absl::make_unique>(numChannels, 2 * numFrames); - sfz::readInterleaved(*intermediateBuffer, outBuffer->getSpan(0), outBuffer->getSpan(1)); + sfz::readInterleaved(*intermediateBuffer, outBuffer->getSpan(0), outBuffer->getSpan(1)); benchmark::DoNotOptimize(outBuffer); } } @@ -317,7 +317,7 @@ BENCHMARK_DEFINE_F(SndFile, SRC2x_MEDIUM)(benchmark::State& state) srcData.output_frames = static_cast(2 * numFrames); src_simple(&srcData, SRC_SINC_MEDIUM_QUALITY, static_cast(numChannels)); auto outBuffer = absl::make_unique>(numChannels, 2 * numFrames); - sfz::readInterleaved(*intermediateBuffer, outBuffer->getSpan(0), outBuffer->getSpan(1)); + sfz::readInterleaved(*intermediateBuffer, outBuffer->getSpan(0), outBuffer->getSpan(1)); benchmark::DoNotOptimize(outBuffer); } } @@ -334,7 +334,7 @@ BENCHMARK_DEFINE_F(SndFile, SRC2x_FASTEST)(benchmark::State& state) srcData.output_frames = static_cast(2 * numFrames); src_simple(&srcData, SRC_SINC_FASTEST, static_cast(numChannels)); auto outBuffer = absl::make_unique>(numChannels, 2 * numFrames); - sfz::readInterleaved(*intermediateBuffer, outBuffer->getSpan(0), outBuffer->getSpan(1)); + sfz::readInterleaved(*intermediateBuffer, outBuffer->getSpan(0), outBuffer->getSpan(1)); benchmark::DoNotOptimize(outBuffer); } } @@ -352,7 +352,7 @@ BENCHMARK_DEFINE_F(SndFile, SRC4x_BEST)(benchmark::State& state) srcData.output_frames = static_cast(2 * numFrames); src_simple(&srcData, SRC_SINC_BEST_QUALITY, static_cast(numChannels)); auto outBuffer = absl::make_unique>(numChannels, 2 * numFrames); - sfz::readInterleaved(*intermediateBuffer, outBuffer->getSpan(0), outBuffer->getSpan(1)); + sfz::readInterleaved(*intermediateBuffer, outBuffer->getSpan(0), outBuffer->getSpan(1)); benchmark::DoNotOptimize(outBuffer); } } @@ -369,7 +369,7 @@ BENCHMARK_DEFINE_F(SndFile, SRC4x_MEDIUM)(benchmark::State& state) srcData.output_frames = static_cast(2 * numFrames); src_simple(&srcData, SRC_SINC_MEDIUM_QUALITY, static_cast(numChannels)); auto outBuffer = absl::make_unique>(numChannels, 2 * numFrames); - sfz::readInterleaved(*intermediateBuffer, outBuffer->getSpan(0), outBuffer->getSpan(1)); + sfz::readInterleaved(*intermediateBuffer, outBuffer->getSpan(0), outBuffer->getSpan(1)); benchmark::DoNotOptimize(outBuffer); } } @@ -386,7 +386,7 @@ BENCHMARK_DEFINE_F(SndFile, SRC4x_FASTEST)(benchmark::State& state) srcData.output_frames = static_cast(2 * numFrames); src_simple(&srcData, SRC_SINC_FASTEST, static_cast(numChannels)); auto outBuffer = absl::make_unique>(numChannels, 2 * numFrames); - sfz::readInterleaved(*intermediateBuffer, outBuffer->getSpan(0), outBuffer->getSpan(1)); + sfz::readInterleaved(*intermediateBuffer, outBuffer->getSpan(0), outBuffer->getSpan(1)); benchmark::DoNotOptimize(outBuffer); } } @@ -403,7 +403,7 @@ BENCHMARK_DEFINE_F(SndFile, SRC8x_BEST)(benchmark::State& state) srcData.output_frames = static_cast(2 * numFrames); src_simple(&srcData, SRC_SINC_BEST_QUALITY, static_cast(numChannels)); auto outBuffer = absl::make_unique>(numChannels, 2 * numFrames); - sfz::readInterleaved(*intermediateBuffer, outBuffer->getSpan(0), outBuffer->getSpan(1)); + sfz::readInterleaved(*intermediateBuffer, outBuffer->getSpan(0), outBuffer->getSpan(1)); benchmark::DoNotOptimize(outBuffer); } } @@ -420,7 +420,7 @@ BENCHMARK_DEFINE_F(SndFile, SRC8x_MEDIUM)(benchmark::State& state) srcData.output_frames = static_cast(2 * numFrames); src_simple(&srcData, SRC_SINC_MEDIUM_QUALITY, static_cast(numChannels)); auto outBuffer = absl::make_unique>(numChannels, 2 * numFrames); - sfz::readInterleaved(*intermediateBuffer, outBuffer->getSpan(0), outBuffer->getSpan(1)); + sfz::readInterleaved(*intermediateBuffer, outBuffer->getSpan(0), outBuffer->getSpan(1)); benchmark::DoNotOptimize(outBuffer); } } @@ -437,7 +437,7 @@ BENCHMARK_DEFINE_F(SndFile, SRC8x_FASTEST)(benchmark::State& state) srcData.output_frames = static_cast(2 * numFrames); src_simple(&srcData, SRC_SINC_FASTEST, static_cast(numChannels)); auto outBuffer = absl::make_unique>(numChannels, 2 * numFrames); - sfz::readInterleaved(*intermediateBuffer, outBuffer->getSpan(0), outBuffer->getSpan(1)); + sfz::readInterleaved(*intermediateBuffer, outBuffer->getSpan(0), outBuffer->getSpan(1)); benchmark::DoNotOptimize(outBuffer); } } @@ -446,7 +446,7 @@ BENCHMARK_DEFINE_F(SndFile, HIIR8X_default)(benchmark::State& state) { for (auto _ : state) { auto baseBuffer = absl::make_unique>(numChannels, numFrames); - sfz::readInterleaved(*interleavedBuffer, baseBuffer->getSpan(0), baseBuffer->getSpan(1)); + sfz::readInterleaved(*interleavedBuffer, baseBuffer->getSpan(0), baseBuffer->getSpan(1)); auto outBuffer = upsample8x(*baseBuffer); benchmark::DoNotOptimize(outBuffer); } diff --git a/benchmarks/BM_resampleChunk.cpp b/benchmarks/BM_resampleChunk.cpp index a75c06c5..59ba3434 100644 --- a/benchmarks/BM_resampleChunk.cpp +++ b/benchmarks/BM_resampleChunk.cpp @@ -105,7 +105,7 @@ BENCHMARK_DEFINE_F(FileFixture, NoResampling)(benchmark::State& state) { { sfz::Buffer buffer { numFrames * sndfile.channels() }; sndfile.readf(buffer.data(), sndfile.frames()); - sfz::readInterleaved(buffer, output->getSpan(0), output->getSpan(1)); + sfz::readInterleaved(buffer, output->getSpan(0), output->getSpan(1)); } } @@ -121,7 +121,7 @@ BENCHMARK_DEFINE_F(FileFixture, ResampleAtOnce)(benchmark::State& state) { upsampler4x.set_coefs(coeffsStage4x.data()); sndfile.readf(buffer.data(), numFrames); - sfz::readInterleaved(buffer, output->getSpan(0), output->getSpan(1)); + sfz::readInterleaved(buffer, output->getSpan(0), output->getSpan(1)); upsampler2x.process_block(temp.data(), output->channelReader(0), static_cast(numFrames)); upsampler4x.process_block(output->channelWriter(0), temp.data(), static_cast(numFrames * 2)); @@ -171,7 +171,7 @@ BENCHMARK_DEFINE_F(FileFixture, ResampleInChunks)(benchmark::State& state) { thisChunkSize * sndfile.channels() ); - sfz::readInterleaved(bufferChunk, leftSpan, rightSpan); + sfz::readInterleaved(bufferChunk, leftSpan, rightSpan); upsampler2xLeft.process_block(chunkSpan.data(), leftSpan.data(), static_cast(thisChunkSize)); upsampler4xLeft.process_block(output->channelWriter(0) + outputFrameCounter, chunkSpan.data(), static_cast(thisChunkSize * 2)); diff --git a/benchmarks/BM_saturating.cpp b/benchmarks/BM_saturating.cpp deleted file mode 100644 index d6bb2427..00000000 --- a/benchmarks/BM_saturating.cpp +++ /dev/null @@ -1,77 +0,0 @@ -// SPDX-License-Identifier: BSD-2-Clause - -// This code is part of the sfizz library and is licensed under a BSD 2-clause -// license. You should have receive a LICENSE.md file along with the code. -// If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz - -#include "SIMDHelpers.h" -#include -#include -#include -#include -#include - -// In this one we have an array of indices - -constexpr int loopEnd { 1076 }; -constexpr float maxJump { 4 }; - -class SaturatingFixture : public benchmark::Fixture { -public: - void SetUp(const ::benchmark::State& state) { - std::random_device rd { }; - std::mt19937 gen { rd() }; - std::uniform_real_distribution dist { 0, maxJump }; - indices = std::vector(state.range(0)); - leftCoeffs = std::vector(state.range(0)); - rightCoeffs = std::vector(state.range(0)); - jumps = std::vector(state.range(0)); - absl::c_generate(jumps, [&]() { return dist(gen); }); - } - - void TearDown(const ::benchmark::State& /* state */) { - - } - - std::vector indices; - std::vector leftCoeffs; - std::vector rightCoeffs; - std::vector jumps; -}; - - -BENCHMARK_DEFINE_F(SaturatingFixture, Scalar)(benchmark::State& state) { - for (auto _ : state) - { - sfz::saturatingSFZIndex(jumps, absl::MakeSpan(leftCoeffs), absl::MakeSpan(rightCoeffs), absl::MakeSpan(indices), 2.5f, loopEnd); - } -} - -BENCHMARK_DEFINE_F(SaturatingFixture, SIMD)(benchmark::State& state) { - for (auto _ : state) - { - sfz::saturatingSFZIndex(jumps, absl::MakeSpan(leftCoeffs), absl::MakeSpan(rightCoeffs), absl::MakeSpan(indices), 2.5f, loopEnd); - } -} - -BENCHMARK_DEFINE_F(SaturatingFixture, Scalar_Unaligned)(benchmark::State& state) { - for (auto _ : state) - { - sfz::saturatingSFZIndex(absl::MakeSpan(jumps).subspan(1), absl::MakeSpan(leftCoeffs).subspan(2), absl::MakeSpan(rightCoeffs).subspan(1), absl::MakeSpan(indices).subspan(3), 2.5f, loopEnd); - } -} - -BENCHMARK_DEFINE_F(SaturatingFixture, SIMD_Unaligned)(benchmark::State& state) { - for (auto _ : state) - { - sfz::saturatingSFZIndex(absl::MakeSpan(jumps).subspan(1), absl::MakeSpan(leftCoeffs).subspan(2), absl::MakeSpan(rightCoeffs).subspan(1), absl::MakeSpan(indices).subspan(3), 2.5f, loopEnd); - } -} - - -// Register the function as a benchmark -BENCHMARK_REGISTER_F(SaturatingFixture, Scalar)->RangeMultiplier(2)->Range((2<<6), (2<<12)); -BENCHMARK_REGISTER_F(SaturatingFixture, SIMD)->RangeMultiplier(2)->Range((2<<6), (2<<12)); -BENCHMARK_REGISTER_F(SaturatingFixture, Scalar_Unaligned)->RangeMultiplier(2)->Range((2<<6), (2<<12)); -BENCHMARK_REGISTER_F(SaturatingFixture, SIMD_Unaligned)->RangeMultiplier(2)->Range((2<<6), (2<<12)); -BENCHMARK_MAIN(); diff --git a/benchmarks/BM_smoothers.cpp b/benchmarks/BM_smoothers.cpp new file mode 100644 index 00000000..caca19b8 --- /dev/null +++ b/benchmarks/BM_smoothers.cpp @@ -0,0 +1,57 @@ +// SPDX-License-Identifier: BSD-2-Clause + +// This code is part of the sfizz library and is licensed under a BSD 2-clause +// license. You should have receive a LICENSE.md file along with the code. +// If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz +#include "SIMDHelpers.h" +#include +#include +#include +#include +#include +#include +#include "ModifierHelpers.h" +#include "Smoothers.h" +#include "absl/types/span.h" + +class SmootherFixture : public benchmark::Fixture { +public: + void SetUp(const ::benchmark::State& state) + { + input = std::vector(state.range(0)); + output = std::vector(state.range(0)); + std::generate(input.begin(), input.end(), [&]() { return dist(gen); }); + sfz::cumsum(input, absl::MakeSpan(input)); + } + + void TearDown(const ::benchmark::State& /* state */) + { + } + std::random_device rd {}; + std::mt19937 gen { rd() }; + std::uniform_real_distribution dist { 0.5, 4 }; + std::vector input; + std::vector output; +}; + +BENCHMARK_DEFINE_F(SmootherFixture, Linear) (benchmark::State& state) +{ + sfz::Smoother smoother; + smoother.setSmoothing(10, sfz::config::defaultSampleRate); + for (auto _ : state) { + smoother.process(input, absl::MakeSpan(output)); + } +} + +// BENCHMARK_DEFINE_F(SmootherFixture, Multiplicative)(benchmark::State& state) { +// sfz::MultiplicativeSmoother smoother; +// smoother.setSmoothing(10, sfz::config::defaultSampleRate); +// for (auto _ : state) +// { +// smoother.process(input, absl::MakeSpan(output)); +// } +// } + +BENCHMARK_REGISTER_F(SmootherFixture, Linear)->RangeMultiplier(4)->Range(1 << 2, 1 << 12); +// BENCHMARK_REGISTER_F(SmootherFixture, Multiplicative)->RangeMultiplier(4)->Range(1 << 2, 1 << 12); +BENCHMARK_MAIN(); diff --git a/benchmarks/BM_subtract.cpp b/benchmarks/BM_subtract.cpp index afc8fc95..e500d9e9 100644 --- a/benchmarks/BM_subtract.cpp +++ b/benchmarks/BM_subtract.cpp @@ -36,28 +36,32 @@ public: BENCHMARK_DEFINE_F(SubArray, Scalar)(benchmark::State& state) { for (auto _ : state) { - sfz::subtract(input, absl::MakeSpan(output)); + sfz::setSIMDOpStatus(sfz::SIMDOps::subtract, false); + sfz::subtract(input, absl::MakeSpan(output)); } } BENCHMARK_DEFINE_F(SubArray, SIMD)(benchmark::State& state) { for (auto _ : state) { - sfz::subtract(input, absl::MakeSpan(output)); + sfz::setSIMDOpStatus(sfz::SIMDOps::subtract, true); + sfz::subtract(input, absl::MakeSpan(output)); } } BENCHMARK_DEFINE_F(SubArray, Scalar_Unaligned)(benchmark::State& state) { for (auto _ : state) { - sfz::subtract(absl::MakeSpan(input).subspan(1), absl::MakeSpan(output).subspan(1)); + sfz::setSIMDOpStatus(sfz::SIMDOps::subtract, false); + sfz::subtract(absl::MakeSpan(input).subspan(1), absl::MakeSpan(output).subspan(1)); } } BENCHMARK_DEFINE_F(SubArray, SIMD_Unaligned)(benchmark::State& state) { for (auto _ : state) { - sfz::subtract(absl::MakeSpan(input).subspan(1), absl::MakeSpan(output).subspan(1)); + sfz::setSIMDOpStatus(sfz::SIMDOps::subtract, true); + sfz::subtract(absl::MakeSpan(input).subspan(1), absl::MakeSpan(output).subspan(1)); } } diff --git a/benchmarks/BM_vaFilters.cpp b/benchmarks/BM_vaFilters.cpp new file mode 100644 index 00000000..2a04fe63 --- /dev/null +++ b/benchmarks/BM_vaFilters.cpp @@ -0,0 +1,59 @@ +// SPDX-License-Identifier: BSD-2-Clause + +// This code is part of the sfizz library and is licensed under a BSD 2-clause +// license. You should have receive a LICENSE.md file along with the code. +// If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz + +#include "SIMDHelpers.h" +#include "OnePoleFilter.h" +#include "SfzFilter.h" +#include "ScopedFTZ.h" +#include +#include +#include +#include +#include +#include + +constexpr int blockSize { 256 }; +constexpr float sampleRate { 48000.0f }; + +class FilterFixture : public benchmark::Fixture { +public: + void SetUp(const ::benchmark::State&) + { + input = std::vector(blockSize); + output = std::vector(blockSize); + std::generate(input.begin(), input.end(), [&]() { return dist(gen); }); + } + + void TearDown(const ::benchmark::State& /* state */) + { + } + std::random_device rd {}; + std::mt19937 gen { rd() }; + std::normal_distribution dist { 1, 0.2 }; + std::vector input; + std::vector output; +}; + +BENCHMARK_DEFINE_F(FilterFixture, OnePole_VA) (benchmark::State& state) +{ + ScopedFTZ ftz; + sfz::OnePoleFilter filter; + for (auto _ : state) { + filter.processLowpass(input, absl::MakeSpan(output)); + } +} + +BENCHMARK_DEFINE_F(FilterFixture, OnePoleMul_VA) (benchmark::State& state) +{ + ScopedFTZ ftz; + sfz::OnePoleFilterMul filter; + for (auto _ : state) { + filter.processLowpass(input, absl::MakeSpan(output)); + } +} +BENCHMARK_REGISTER_F(FilterFixture, OnePole_VA); +BENCHMARK_REGISTER_F(FilterFixture, OnePoleMul_VA); +BENCHMARK_MAIN(); diff --git a/benchmarks/BM_widthPos.cpp b/benchmarks/BM_widthPos.cpp deleted file mode 100644 index 1ab9fa7d..00000000 --- a/benchmarks/BM_widthPos.cpp +++ /dev/null @@ -1,80 +0,0 @@ -// SPDX-License-Identifier: BSD-2-Clause - -// This code is part of the sfizz library and is licensed under a BSD 2-clause -// license. You should have receive a LICENSE.md file along with the code. -// If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz - -#include "SIMDHelpers.h" -#include -#include -#include -#include -#include -#include -#include "Config.h" -#include "ScopedFTZ.h" -#include "absl/types/span.h" - -class WidthPosArray : public benchmark::Fixture { -public: - void SetUp(const ::benchmark::State& state) { - std::random_device rd { }; - std::mt19937 gen { rd() }; - std::uniform_real_distribution dist { 0.001f, 1.0f }; - width = std::vector(state.range(0)); - position = std::vector(state.range(0)); - left = std::vector(state.range(0)); - right = std::vector(state.range(0)); - std::generate(width.begin(), width.end(), [&]() { return dist(gen); }); - std::generate(position.begin(), position.end(), [&]() { return dist(gen); }); - std::generate(right.begin(), right.end(), [&]() { return dist(gen); }); - std::generate(left.begin(), left.end(), [&]() { return dist(gen); }); - temp1 = std::vector(state.range(0)); - temp2 = std::vector(state.range(0)); - temp3 = std::vector(state.range(0)); - span1 = absl::MakeSpan(temp1); - span2 = absl::MakeSpan(temp2); - span3 = absl::MakeSpan(temp3); - } - - void TearDown(const ::benchmark::State& /* state */) { - - } - - std::vector width; - std::vector position; - std::vector left; - std::vector right; - std::vector temp1; - std::vector temp2; - std::vector temp3; - absl::Span span1; - absl::Span span2; - absl::Span span3; -}; - -BENCHMARK_DEFINE_F(WidthPosArray, Scalar)(benchmark::State& state) { - ScopedFTZ ftz; - const auto leftBuffer = absl::MakeSpan(left); - const auto rightBuffer = absl::MakeSpan(right); - for (auto _ : state) - { - sfz::width(width, leftBuffer, rightBuffer); - sfz::pan(position, leftBuffer, rightBuffer); - } -} - -BENCHMARK_DEFINE_F(WidthPosArray, SIMD)(benchmark::State& state) { - ScopedFTZ ftz; - const auto leftBuffer = absl::MakeSpan(left); - const auto rightBuffer = absl::MakeSpan(right); - for (auto _ : state) - { - sfz::width(width, leftBuffer, rightBuffer); - sfz::pan(position, leftBuffer, rightBuffer); - } -} - -BENCHMARK_REGISTER_F(WidthPosArray, Scalar)->RangeMultiplier(4)->Range(1 << 2, 1 << 12); -BENCHMARK_REGISTER_F(WidthPosArray, SIMD)->RangeMultiplier(4)->Range(1 << 2, 1 << 12); -BENCHMARK_MAIN(); diff --git a/benchmarks/BM_writeInterleaved.cpp b/benchmarks/BM_writeInterleaved.cpp index e22f3d7f..7313adb7 100644 --- a/benchmarks/BM_writeInterleaved.cpp +++ b/benchmarks/BM_writeInterleaved.cpp @@ -19,7 +19,8 @@ static void Interleaved_Write(benchmark::State& state) { std::iota(inputRight.begin(), inputRight.end(), 1.0f); for (auto _ : state) { - sfz::writeInterleaved(inputLeft, inputRight, absl::MakeSpan(output)); + sfz::setSIMDOpStatus(sfz::SIMDOps::writeInterleaved, false); + sfz::writeInterleaved(inputLeft, inputRight, absl::MakeSpan(output)); } } @@ -30,8 +31,8 @@ static void Interleaved_Write_SSE(benchmark::State& state) { std::iota(inputLeft.begin(), inputLeft.end(), 1.0f); std::iota(inputRight.begin(), inputRight.end(), 1.0f); for (auto _ : state) { - sfz::writeInterleaved(inputLeft, inputRight, absl::MakeSpan(output)); - benchmark::DoNotOptimize(output); + sfz::setSIMDOpStatus(sfz::SIMDOps::writeInterleaved, true); + sfz::writeInterleaved(inputLeft, inputRight, absl::MakeSpan(output)); } } @@ -42,8 +43,12 @@ static void Unaligned_Interleaved_Write(benchmark::State& state) { std::iota(inputLeft.begin(), inputLeft.end(), 1.0f); std::iota(inputRight.begin(), inputRight.end(), 1.0f); for (auto _ : state) { - sfz::writeInterleaved(absl::MakeSpan(inputLeft).subspan(1) , absl::MakeSpan(inputRight).subspan(1), absl::MakeSpan(output).subspan(2)); - benchmark::DoNotOptimize(output); + sfz::setSIMDOpStatus(sfz::SIMDOps::writeInterleaved, false); + sfz::writeInterleaved( + absl::MakeSpan(inputLeft).subspan(1), + absl::MakeSpan(inputRight).subspan(1), + absl::MakeSpan(output).subspan(2) + ); } } @@ -54,8 +59,12 @@ static void Unaligned_Interleaved_Write_SSE(benchmark::State& state) { std::iota(inputLeft.begin(), inputLeft.end(), 1.0f); std::iota(inputRight.begin(), inputRight.end(), 1.0f); for (auto _ : state) { - sfz::writeInterleaved(absl::MakeSpan(inputLeft).subspan(1) , absl::MakeSpan(inputRight).subspan(1), absl::MakeSpan(output).subspan(2)); - benchmark::DoNotOptimize(output); + sfz::setSIMDOpStatus(sfz::SIMDOps::writeInterleaved, true); + sfz::writeInterleaved( + absl::MakeSpan(inputLeft).subspan(1), + absl::MakeSpan(inputRight).subspan(1), + absl::MakeSpan(output).subspan(2) + ); } } @@ -66,8 +75,12 @@ static void Unaligned_Interleaved_Write_2(benchmark::State& state) { std::iota(inputLeft.begin(), inputLeft.end(), 1.0f); std::iota(inputRight.begin(), inputRight.end(), 1.0f); for (auto _ : state) { - sfz::writeInterleaved(absl::MakeSpan(inputLeft) , absl::MakeSpan(inputRight).subspan(1), absl::MakeSpan(output).subspan(2)); - benchmark::DoNotOptimize(output); + sfz::setSIMDOpStatus(sfz::SIMDOps::writeInterleaved, false); + sfz::writeInterleaved( + absl::MakeSpan(inputLeft), + absl::MakeSpan(inputRight).subspan(1), + absl::MakeSpan(output).subspan(2) + ); } } @@ -78,8 +91,12 @@ static void Unaligned_Interleaved_Write_SSE_2(benchmark::State& state) { std::iota(inputLeft.begin(), inputLeft.end(), 1.0f); std::iota(inputRight.begin(), inputRight.end(), 1.0f); for (auto _ : state) { - sfz::writeInterleaved(absl::MakeSpan(inputLeft) , absl::MakeSpan(inputRight).subspan(1), absl::MakeSpan(output).subspan(2)); - benchmark::DoNotOptimize(output); + sfz::setSIMDOpStatus(sfz::SIMDOps::writeInterleaved, true); + sfz::writeInterleaved( + absl::MakeSpan(inputLeft), + absl::MakeSpan(inputRight).subspan(1), + absl::MakeSpan(output).subspan(2) + ); } } diff --git a/benchmarks/CMakeLists.txt b/benchmarks/CMakeLists.txt index 493f450e..3c0082a1 100644 --- a/benchmarks/CMakeLists.txt +++ b/benchmarks/CMakeLists.txt @@ -18,7 +18,7 @@ if(SAMPLERATE_LIBRARY AND SAMPLERATE_INCLUDE_DIR) endif() add_library(bm_simd STATIC ${BENCHMARK_SIMD_SOURCES}) -target_link_libraries(bm_simd PRIVATE absl::span) +target_link_libraries(bm_simd PRIVATE absl::span sfizz-cpuid) target_include_directories(bm_simd PRIVATE ../src/external) add_library(bm_ftz STATIC ../src/sfizz/ScopedFTZ.cpp) @@ -38,12 +38,9 @@ sfizz_add_benchmark(bm_opf_high_vs_low BM_OPF_high_vs_low.cpp) sfizz_add_benchmark(bm_clock BM_clock.cpp) sfizz_add_benchmark(bm_write BM_writeInterleaved.cpp) sfizz_add_benchmark(bm_read BM_readInterleaved.cpp) -sfizz_add_benchmark(bm_fill BM_fill.cpp) sfizz_add_benchmark(bm_mathfuns BM_mathfuns.cpp) sfizz_add_benchmark(bm_gain BM_gain.cpp) sfizz_add_benchmark(bm_divide BM_divide.cpp) -sfizz_add_benchmark(bm_looping BM_looping.cpp) -sfizz_add_benchmark(bm_saturating BM_saturating.cpp) sfizz_add_benchmark(bm_ramp BM_ramp.cpp) sfizz_add_benchmark(bm_ADSR BM_ADSR.cpp) target_link_libraries(bm_ADSR PRIVATE sfizz::sfizz) @@ -53,24 +50,24 @@ sfizz_add_benchmark(bm_multiplyAdd BM_multiplyAdd.cpp) sfizz_add_benchmark(bm_multiplyAddFixedGain BM_multiplyAddFixedGain.cpp) sfizz_add_benchmark(bm_subtract BM_subtract.cpp) sfizz_add_benchmark(bm_copy BM_copy.cpp) -sfizz_add_benchmark(bm_pan BM_pan.cpp) sfizz_add_benchmark(bm_mean BM_mean.cpp) sfizz_add_benchmark(bm_meanSquared BM_meanSquared.cpp) sfizz_add_benchmark(bm_cumsum BM_cumsum.cpp) sfizz_add_benchmark(bm_diff BM_diff.cpp) -sfizz_add_benchmark(bm_widthPos BM_widthPos.cpp) -sfizz_add_benchmark(bm_interpolationCast BM_interpolationCast.cpp) sfizz_add_benchmark(bm_pointerIterationOrOffsets BM_pointerIterationOrOffsets.cpp) sfizz_add_benchmark(bm_maps BM_maps.cpp) target_link_libraries(bm_maps PRIVATE absl::flat_hash_map) sfizz_add_benchmark(bm_mapVsArray BM_mapVsArray.cpp) +sfizz_add_benchmark(bm_random BM_random.cpp) sfizz_add_benchmark(bm_logger BM_logger.cpp) target_link_libraries(bm_logger PRIVATE sfizz::sfizz) +sfizz_add_benchmark(bm_smoothers BM_smoothers.cpp) +target_link_libraries(bm_smoothers PRIVATE sfizz::sfizz) if (TARGET sfizz-samplerate) sfizz_add_benchmark(bm_resample BM_resample.cpp ${BENCHMARK_SIMD_SOURCES}) -target_link_libraries(bm_resample PRIVATE sfizz-samplerate sfizz-sndfile) +target_link_libraries(bm_resample PRIVATE sfizz-samplerate sfizz-sndfile sfizz-cpuid) endif() sfizz_add_benchmark(bm_envelopes BM_envelopes.cpp) @@ -83,10 +80,14 @@ target_link_libraries(bm_flacfile PRIVATE sfizz-sndfile) sfizz_add_benchmark(bm_readChunk BM_readChunk.cpp) target_link_libraries(bm_readChunk PRIVATE sfizz-sndfile) +sfizz_add_benchmark(bm_readChunkFlac BM_readChunkFlac.cpp) +target_link_libraries(bm_readChunkFlac PRIVATE sfizz-sndfile) sfizz_add_benchmark(bm_resampleChunk BM_resampleChunk.cpp) target_link_libraries(bm_resampleChunk PRIVATE sfizz-sndfile) +sfizz_add_benchmark(bm_interpolators BM_interpolators.cpp) + sfizz_add_benchmark(bm_filterModulation BM_filterModulation.cpp ../src/sfizz/SfzFilter.cpp) target_link_libraries(bm_filterModulation PRIVATE sfizz-sndfile) @@ -111,27 +112,21 @@ add_dependencies(sfizz_benchmarks bm_read bm_mean bm_meanSquared - bm_fill bm_cumsum bm_diff - bm_interpolationCast bm_mathfuns bm_gain bm_divide - bm_looping - bm_saturating bm_ramp bm_ADSR bm_add bm_logger - bm_pan bm_subtract bm_multiplyAdd bm_readChunk bm_resampleChunk bm_envelopes bm_wavfile - bm_widthPos bm_flacfile bm_filterModulation bm_filterStereoMono @@ -142,9 +137,9 @@ if (TARGET bm_resample) add_dependencies(sfizz_benchmarks bm_resample) endif() -file(COPY "sample1.wav" DESTINATION ${CMAKE_BINARY_DIR}/benchmarks) -file(COPY "sample2.wav" DESTINATION ${CMAKE_BINARY_DIR}/benchmarks) -file(COPY "sample3.wav" DESTINATION ${CMAKE_BINARY_DIR}/benchmarks) -file(COPY "sample1.flac" DESTINATION ${CMAKE_BINARY_DIR}/benchmarks) -file(COPY "sample2.flac" DESTINATION ${CMAKE_BINARY_DIR}/benchmarks) -file(COPY "sample3.flac" DESTINATION ${CMAKE_BINARY_DIR}/benchmarks) +configure_file("sample.wav" "${CMAKE_BINARY_DIR}/benchmarks/sample1.wav" COPYONLY) +configure_file("sample.wav" "${CMAKE_BINARY_DIR}/benchmarks/sample2.wav" COPYONLY) +configure_file("sample.wav" "${CMAKE_BINARY_DIR}/benchmarks/sample3.wav" COPYONLY) +configure_file("sample.flac" "${CMAKE_BINARY_DIR}/benchmarks/sample1.flac" COPYONLY) +configure_file("sample.flac" "${CMAKE_BINARY_DIR}/benchmarks/sample2.flac" COPYONLY) +configure_file("sample.flac" "${CMAKE_BINARY_DIR}/benchmarks/sample3.flac" COPYONLY) diff --git a/benchmarks/ride.wav b/benchmarks/ride.wav deleted file mode 100755 index 2a960228..00000000 Binary files a/benchmarks/ride.wav and /dev/null differ diff --git a/benchmarks/ride2.wav b/benchmarks/ride2.wav deleted file mode 100755 index 2a960228..00000000 Binary files a/benchmarks/ride2.wav and /dev/null differ diff --git a/benchmarks/sample1.flac b/benchmarks/sample.flac similarity index 100% rename from benchmarks/sample1.flac rename to benchmarks/sample.flac diff --git a/benchmarks/sample1.wav b/benchmarks/sample.wav similarity index 100% rename from benchmarks/sample1.wav rename to benchmarks/sample.wav diff --git a/benchmarks/sample2.flac b/benchmarks/sample2.flac deleted file mode 100755 index 3b84350a..00000000 Binary files a/benchmarks/sample2.flac and /dev/null differ diff --git a/benchmarks/sample2.wav b/benchmarks/sample2.wav deleted file mode 100755 index 2acb8351..00000000 Binary files a/benchmarks/sample2.wav and /dev/null differ diff --git a/benchmarks/sample3.flac b/benchmarks/sample3.flac deleted file mode 100755 index 3b84350a..00000000 Binary files a/benchmarks/sample3.flac and /dev/null differ diff --git a/benchmarks/sample3.wav b/benchmarks/sample3.wav deleted file mode 100755 index 2acb8351..00000000 Binary files a/benchmarks/sample3.wav and /dev/null differ diff --git a/clients/CMakeLists.txt b/clients/CMakeLists.txt index 6eb78e2a..859005c0 100644 --- a/clients/CMakeLists.txt +++ b/clients/CMakeLists.txt @@ -1,10 +1,26 @@ project (sfizz) -find_package(PkgConfig REQUIRED) -pkg_check_modules(JACK "jack" REQUIRED) +if (SFIZZ_JACK) + find_package(PkgConfig REQUIRED) + pkg_check_modules(JACK "jack" REQUIRED) -add_executable (sfizz_jack jack_client.cpp) -target_include_directories (sfizz_jack PRIVATE ${JACK_INCLUDE_DIRS}) -target_link_libraries (sfizz_jack PRIVATE sfizz::sfizz jack absl::flags_parse ${JACK_LIBRARIES}) -sfizz_enable_lto_if_needed (sfizz_jack) -install (TARGETS sfizz_jack DESTINATION ${CMAKE_INSTALL_BINDIR} OPTIONAL) + add_executable (sfizz_jack MidiHelpers.h jack_client.cpp) + target_include_directories (sfizz_jack PRIVATE ${JACK_INCLUDE_DIRS}) + target_link_libraries (sfizz_jack PRIVATE sfizz::sfizz jack absl::flags_parse ${JACK_LIBRARIES}) + sfizz_enable_lto_if_needed (sfizz_jack) + install (TARGETS sfizz_jack DESTINATION ${CMAKE_INSTALL_BINDIR} + COMPONENT "jack" OPTIONAL) +endif() + +if (SFIZZ_RENDER) + add_library(sfizz-fmidi STATIC + "external/fmidi/sources/fmidi/fmidi.h" + "external/fmidi/sources/fmidi/fmidi_mini.cpp") + target_include_directories(sfizz-fmidi PUBLIC "external/fmidi/sources") + target_compile_definitions(sfizz-fmidi PUBLIC "FMIDI_STATIC=1" "FMIDI_DISABLE_DESCRIBE_API=1") + + add_executable(sfizz_render MidiHelpers.h sfizz_render.cpp) + target_link_libraries(sfizz_render PRIVATE sfizz::sfizz sfizz-fmidi sfizz-sndfile) + sfizz_enable_lto_if_needed (sfizz_render) + install (TARGETS sfizz_render DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT "render" OPTIONAL) +endif() diff --git a/clients/MidiHelpers.h b/clients/MidiHelpers.h new file mode 100644 index 00000000..10168229 --- /dev/null +++ b/clients/MidiHelpers.h @@ -0,0 +1,29 @@ +#pragma once +#include + +namespace midi { +constexpr uint8_t statusMask { 0b11110000 }; +constexpr uint8_t channelMask { 0b00001111 }; +constexpr uint8_t noteOff { 0x80 }; +constexpr uint8_t noteOn { 0x90 }; +constexpr uint8_t polyphonicPressure { 0xA0 }; +constexpr uint8_t controlChange { 0xB0 }; +constexpr uint8_t programChange { 0xC0 }; +constexpr uint8_t channelPressure { 0xD0 }; +constexpr uint8_t pitchBend { 0xE0 }; +constexpr uint8_t systemMessage { 0xF0 }; + +constexpr uint8_t status(uint8_t midiStatusByte) +{ + return midiStatusByte & statusMask; +} +constexpr uint8_t channel(uint8_t midiStatusByte) +{ + return midiStatusByte & channelMask; +} + +constexpr int buildAndCenterPitch(uint8_t firstByte, uint8_t secondByte) +{ + return (int)(((unsigned int)secondByte << 7) + (unsigned int)firstByte) - 8192; +} +} diff --git a/clients/cxxopts.hpp b/clients/cxxopts.hpp new file mode 100644 index 00000000..16446922 --- /dev/null +++ b/clients/cxxopts.hpp @@ -0,0 +1,2215 @@ +/* + +Copyright (c) 2014, 2015, 2016, 2017 Jarryd Beck + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +*/ + +#ifndef CXXOPTS_HPP_INCLUDED +#define CXXOPTS_HPP_INCLUDED + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef __cpp_lib_optional +#include +#define CXXOPTS_HAS_OPTIONAL +#endif + +#ifndef CXXOPTS_VECTOR_DELIMITER +#define CXXOPTS_VECTOR_DELIMITER ',' +#endif + +#define CXXOPTS__VERSION_MAJOR 2 +#define CXXOPTS__VERSION_MINOR 2 +#define CXXOPTS__VERSION_PATCH 0 + +namespace cxxopts +{ + static constexpr struct { + uint8_t major, minor, patch; + } version = { + CXXOPTS__VERSION_MAJOR, + CXXOPTS__VERSION_MINOR, + CXXOPTS__VERSION_PATCH + }; +} + +//when we ask cxxopts to use Unicode, help strings are processed using ICU, +//which results in the correct lengths being computed for strings when they +//are formatted for the help output +//it is necessary to make sure that can be found by the +//compiler, and that icu-uc is linked in to the binary. + +#ifdef CXXOPTS_USE_UNICODE +#include + +namespace cxxopts +{ + typedef icu::UnicodeString String; + + inline + String + toLocalString(std::string s) + { + return icu::UnicodeString::fromUTF8(std::move(s)); + } + + class UnicodeStringIterator : public + std::iterator + { + public: + + UnicodeStringIterator(const icu::UnicodeString* string, int32_t pos) + : s(string) + , i(pos) + { + } + + value_type + operator*() const + { + return s->char32At(i); + } + + bool + operator==(const UnicodeStringIterator& rhs) const + { + return s == rhs.s && i == rhs.i; + } + + bool + operator!=(const UnicodeStringIterator& rhs) const + { + return !(*this == rhs); + } + + UnicodeStringIterator& + operator++() + { + ++i; + return *this; + } + + UnicodeStringIterator + operator+(int32_t v) + { + return UnicodeStringIterator(s, i + v); + } + + private: + const icu::UnicodeString* s; + int32_t i; + }; + + inline + String& + stringAppend(String&s, String a) + { + return s.append(std::move(a)); + } + + inline + String& + stringAppend(String& s, int n, UChar32 c) + { + for (int i = 0; i != n; ++i) + { + s.append(c); + } + + return s; + } + + template + String& + stringAppend(String& s, Iterator begin, Iterator end) + { + while (begin != end) + { + s.append(*begin); + ++begin; + } + + return s; + } + + inline + size_t + stringLength(const String& s) + { + return s.length(); + } + + inline + std::string + toUTF8String(const String& s) + { + std::string result; + s.toUTF8String(result); + + return result; + } + + inline + bool + empty(const String& s) + { + return s.isEmpty(); + } +} + +namespace std +{ + inline + cxxopts::UnicodeStringIterator + begin(const icu::UnicodeString& s) + { + return cxxopts::UnicodeStringIterator(&s, 0); + } + + inline + cxxopts::UnicodeStringIterator + end(const icu::UnicodeString& s) + { + return cxxopts::UnicodeStringIterator(&s, s.length()); + } +} + +//ifdef CXXOPTS_USE_UNICODE +#else + +namespace cxxopts +{ + typedef std::string String; + + template + T + toLocalString(T&& t) + { + return std::forward(t); + } + + inline + size_t + stringLength(const String& s) + { + return s.length(); + } + + inline + String& + stringAppend(String&s, String a) + { + return s.append(std::move(a)); + } + + inline + String& + stringAppend(String& s, size_t n, char c) + { + return s.append(n, c); + } + + template + String& + stringAppend(String& s, Iterator begin, Iterator end) + { + return s.append(begin, end); + } + + template + std::string + toUTF8String(T&& t) + { + return std::forward(t); + } + + inline + bool + empty(const std::string& s) + { + return s.empty(); + } +} + +//ifdef CXXOPTS_USE_UNICODE +#endif + +namespace cxxopts +{ + namespace + { +#ifdef _WIN32 + const std::string LQUOTE("\'"); + const std::string RQUOTE("\'"); +#else + const std::string LQUOTE("‘"); + const std::string RQUOTE("’"); +#endif + } + + class Value : public std::enable_shared_from_this + { + public: + + virtual ~Value() = default; + + virtual + std::shared_ptr + clone() const = 0; + + virtual void + parse(const std::string& text) const = 0; + + virtual void + parse() const = 0; + + virtual bool + has_default() const = 0; + + virtual bool + is_container() const = 0; + + virtual bool + has_implicit() const = 0; + + virtual std::string + get_default_value() const = 0; + + virtual std::string + get_implicit_value() const = 0; + + virtual std::shared_ptr + default_value(const std::string& value) = 0; + + virtual std::shared_ptr + implicit_value(const std::string& value) = 0; + + virtual std::shared_ptr + no_implicit_value() = 0; + + virtual bool + is_boolean() const = 0; + }; + + class OptionException : public std::exception + { + public: + OptionException(const std::string& message) + : m_message(message) + { + } + + virtual const char* + what() const noexcept + { + return m_message.c_str(); + } + + private: + std::string m_message; + }; + + class OptionSpecException : public OptionException + { + public: + + OptionSpecException(const std::string& message) + : OptionException(message) + { + } + }; + + class OptionParseException : public OptionException + { + public: + OptionParseException(const std::string& message) + : OptionException(message) + { + } + }; + + class option_exists_error : public OptionSpecException + { + public: + option_exists_error(const std::string& option) + : OptionSpecException("Option " + LQUOTE + option + RQUOTE + " already exists") + { + } + }; + + class invalid_option_format_error : public OptionSpecException + { + public: + invalid_option_format_error(const std::string& format) + : OptionSpecException("Invalid option format " + LQUOTE + format + RQUOTE) + { + } + }; + + class option_syntax_exception : public OptionParseException { + public: + option_syntax_exception(const std::string& text) + : OptionParseException("Argument " + LQUOTE + text + RQUOTE + + " starts with a - but has incorrect syntax") + { + } + }; + + class option_not_exists_exception : public OptionParseException + { + public: + option_not_exists_exception(const std::string& option) + : OptionParseException("Option " + LQUOTE + option + RQUOTE + " does not exist") + { + } + }; + + class missing_argument_exception : public OptionParseException + { + public: + missing_argument_exception(const std::string& option) + : OptionParseException( + "Option " + LQUOTE + option + RQUOTE + " is missing an argument" + ) + { + } + }; + + class option_requires_argument_exception : public OptionParseException + { + public: + option_requires_argument_exception(const std::string& option) + : OptionParseException( + "Option " + LQUOTE + option + RQUOTE + " requires an argument" + ) + { + } + }; + + class option_not_has_argument_exception : public OptionParseException + { + public: + option_not_has_argument_exception + ( + const std::string& option, + const std::string& arg + ) + : OptionParseException( + "Option " + LQUOTE + option + RQUOTE + + " does not take an argument, but argument " + + LQUOTE + arg + RQUOTE + " given" + ) + { + } + }; + + class option_not_present_exception : public OptionParseException + { + public: + option_not_present_exception(const std::string& option) + : OptionParseException("Option " + LQUOTE + option + RQUOTE + " not present") + { + } + }; + + class argument_incorrect_type : public OptionParseException + { + public: + argument_incorrect_type + ( + const std::string& arg + ) + : OptionParseException( + "Argument " + LQUOTE + arg + RQUOTE + " failed to parse" + ) + { + } + }; + + class option_required_exception : public OptionParseException + { + public: + option_required_exception(const std::string& option) + : OptionParseException( + "Option " + LQUOTE + option + RQUOTE + " is required but not present" + ) + { + } + }; + + template + void throw_or_mimic(const std::string& text) + { + static_assert(std::is_base_of::value, + "throw_or_mimic only works on std::exception and " + "deriving classes"); + +#ifndef CXXOPTS_NO_EXCEPTIONS + // If CXXOPTS_NO_EXCEPTIONS is not defined, just throw + throw T{text}; +#else + // Otherwise manually instantiate the exception, print what() to stderr, + // and abort + T exception{text}; + std::cerr << exception.what() << std::endl; + std::cerr << "Aborting (exceptions disabled)..." << std::endl; + std::abort(); +#endif + } + + namespace values + { + namespace + { + std::basic_regex integer_pattern + ("(-)?(0x)?([0-9a-zA-Z]+)|((0x)?0)"); + std::basic_regex truthy_pattern + ("(t|T)(rue)?|1"); + std::basic_regex falsy_pattern + ("(f|F)(alse)?|0"); + } + + namespace detail + { + template + struct SignedCheck; + + template + struct SignedCheck + { + template + void + operator()(bool negative, U u, const std::string& text) + { + if (negative) + { + if (u > static_cast((std::numeric_limits::min)())) + { + throw_or_mimic(text); + } + } + else + { + if (u > static_cast((std::numeric_limits::max)())) + { + throw_or_mimic(text); + } + } + } + }; + + template + struct SignedCheck + { + template + void + operator()(bool, U, const std::string&) {} + }; + + template + void + check_signed_range(bool negative, U value, const std::string& text) + { + SignedCheck::is_signed>()(negative, value, text); + } + } + + template + R + checked_negate(T&& t, const std::string&, std::true_type) + { + // if we got to here, then `t` is a positive number that fits into + // `R`. So to avoid MSVC C4146, we first cast it to `R`. + // See https://github.com/jarro2783/cxxopts/issues/62 for more details. + return static_cast(-static_cast(t-1)-1); + } + + template + T + checked_negate(T&& t, const std::string& text, std::false_type) + { + throw_or_mimic(text); + return t; + } + + template + void + integer_parser(const std::string& text, T& value) + { + std::smatch match; + std::regex_match(text, match, integer_pattern); + + if (match.length() == 0) + { + throw_or_mimic(text); + } + + if (match.length(4) > 0) + { + value = 0; + return; + } + + using US = typename std::make_unsigned::type; + + constexpr bool is_signed = std::numeric_limits::is_signed; + const bool negative = match.length(1) > 0; + const uint8_t base = match.length(2) > 0 ? 16 : 10; + + auto value_match = match[3]; + + US result = 0; + + for (auto iter = value_match.first; iter != value_match.second; ++iter) + { + US digit = 0; + + if (*iter >= '0' && *iter <= '9') + { + digit = static_cast(*iter - '0'); + } + else if (base == 16 && *iter >= 'a' && *iter <= 'f') + { + digit = static_cast(*iter - 'a' + 10); + } + else if (base == 16 && *iter >= 'A' && *iter <= 'F') + { + digit = static_cast(*iter - 'A' + 10); + } + else + { + throw_or_mimic(text); + } + + const US next = static_cast(result * base + digit); + if (result > next) + { + throw_or_mimic(text); + } + + result = next; + } + + detail::check_signed_range(negative, result, text); + + if (negative) + { + value = checked_negate(result, + text, + std::integral_constant()); + } + else + { + value = static_cast(result); + } + } + + template + void stringstream_parser(const std::string& text, T& value) + { + std::stringstream in(text); + in >> value; + if (!in) { + throw_or_mimic(text); + } + } + + inline + void + parse_value(const std::string& text, uint8_t& value) + { + integer_parser(text, value); + } + + inline + void + parse_value(const std::string& text, int8_t& value) + { + integer_parser(text, value); + } + + inline + void + parse_value(const std::string& text, uint16_t& value) + { + integer_parser(text, value); + } + + inline + void + parse_value(const std::string& text, int16_t& value) + { + integer_parser(text, value); + } + + inline + void + parse_value(const std::string& text, uint32_t& value) + { + integer_parser(text, value); + } + + inline + void + parse_value(const std::string& text, int32_t& value) + { + integer_parser(text, value); + } + + inline + void + parse_value(const std::string& text, uint64_t& value) + { + integer_parser(text, value); + } + + inline + void + parse_value(const std::string& text, int64_t& value) + { + integer_parser(text, value); + } + + inline + void + parse_value(const std::string& text, bool& value) + { + std::smatch result; + std::regex_match(text, result, truthy_pattern); + + if (!result.empty()) + { + value = true; + return; + } + + std::regex_match(text, result, falsy_pattern); + if (!result.empty()) + { + value = false; + return; + } + + throw_or_mimic(text); + } + + inline + void + parse_value(const std::string& text, std::string& value) + { + value = text; + } + + // The fallback parser. It uses the stringstream parser to parse all types + // that have not been overloaded explicitly. It has to be placed in the + // source code before all other more specialized templates. + template + void + parse_value(const std::string& text, T& value) { + stringstream_parser(text, value); + } + + template + void + parse_value(const std::string& text, std::vector& value) + { + std::stringstream in(text); + std::string token; + while(in.eof() == false && std::getline(in, token, CXXOPTS_VECTOR_DELIMITER)) { + T v; + parse_value(token, v); + value.emplace_back(std::move(v)); + } + } + +#ifdef CXXOPTS_HAS_OPTIONAL + template + void + parse_value(const std::string& text, std::optional& value) + { + T result; + parse_value(text, result); + value = std::move(result); + } +#endif + + inline + void parse_value(const std::string& text, char& c) + { + if (text.length() != 1) + { + throw_or_mimic(text); + } + + c = text[0]; + } + + template + struct type_is_container + { + static constexpr bool value = false; + }; + + template + struct type_is_container> + { + static constexpr bool value = true; + }; + + template + class abstract_value : public Value + { + using Self = abstract_value; + + public: + abstract_value() + : m_result(std::make_shared()) + , m_store(m_result.get()) + { + } + + abstract_value(T* t) + : m_store(t) + { + } + + virtual ~abstract_value() = default; + + abstract_value(const abstract_value& rhs) + { + if (rhs.m_result) + { + m_result = std::make_shared(); + m_store = m_result.get(); + } + else + { + m_store = rhs.m_store; + } + + m_default = rhs.m_default; + m_implicit = rhs.m_implicit; + m_default_value = rhs.m_default_value; + m_implicit_value = rhs.m_implicit_value; + } + + void + parse(const std::string& text) const + { + parse_value(text, *m_store); + } + + bool + is_container() const + { + return type_is_container::value; + } + + void + parse() const + { + parse_value(m_default_value, *m_store); + } + + bool + has_default() const + { + return m_default; + } + + bool + has_implicit() const + { + return m_implicit; + } + + std::shared_ptr + default_value(const std::string& value) + { + m_default = true; + m_default_value = value; + return shared_from_this(); + } + + std::shared_ptr + implicit_value(const std::string& value) + { + m_implicit = true; + m_implicit_value = value; + return shared_from_this(); + } + + std::shared_ptr + no_implicit_value() + { + m_implicit = false; + return shared_from_this(); + } + + std::string + get_default_value() const + { + return m_default_value; + } + + std::string + get_implicit_value() const + { + return m_implicit_value; + } + + bool + is_boolean() const + { + return std::is_same::value; + } + + const T& + get() const + { + if (m_store == nullptr) + { + return *m_result; + } + else + { + return *m_store; + } + } + + protected: + std::shared_ptr m_result; + T* m_store; + + bool m_default = false; + bool m_implicit = false; + + std::string m_default_value; + std::string m_implicit_value; + }; + + template + class standard_value : public abstract_value + { + public: + using abstract_value::abstract_value; + + std::shared_ptr + clone() const + { + return std::make_shared>(*this); + } + }; + + template <> + class standard_value : public abstract_value + { + public: + ~standard_value() = default; + + standard_value() + { + set_default_and_implicit(); + } + + standard_value(bool* b) + : abstract_value(b) + { + set_default_and_implicit(); + } + + std::shared_ptr + clone() const + { + return std::make_shared>(*this); + } + + private: + + void + set_default_and_implicit() + { + m_default = true; + m_default_value = "false"; + m_implicit = true; + m_implicit_value = "true"; + } + }; + } + + template + std::shared_ptr + value() + { + return std::make_shared>(); + } + + template + std::shared_ptr + value(T& t) + { + return std::make_shared>(&t); + } + + class OptionAdder; + + class OptionDetails + { + public: + OptionDetails + ( + const std::string& short_, + const std::string& long_, + const String& desc, + std::shared_ptr val + ) + : m_short(short_) + , m_long(long_) + , m_desc(desc) + , m_value(val) + , m_count(0) + { + } + + OptionDetails(const OptionDetails& rhs) + : m_desc(rhs.m_desc) + , m_count(rhs.m_count) + { + m_value = rhs.m_value->clone(); + } + + OptionDetails(OptionDetails&& rhs) = default; + + const String& + description() const + { + return m_desc; + } + + const Value& value() const { + return *m_value; + } + + std::shared_ptr + make_storage() const + { + return m_value->clone(); + } + + const std::string& + short_name() const + { + return m_short; + } + + const std::string& + long_name() const + { + return m_long; + } + + private: + std::string m_short; + std::string m_long; + String m_desc; + std::shared_ptr m_value; + int m_count; + }; + + struct HelpOptionDetails + { + std::string s; + std::string l; + String desc; + bool has_default; + std::string default_value; + bool has_implicit; + std::string implicit_value; + std::string arg_help; + bool is_container; + bool is_boolean; + }; + + struct HelpGroupDetails + { + std::string name; + std::string description; + std::vector options; + }; + + class OptionValue + { + public: + void + parse + ( + std::shared_ptr details, + const std::string& text + ) + { + ensure_value(details); + ++m_count; + m_value->parse(text); + } + + void + parse_default(std::shared_ptr details) + { + ensure_value(details); + m_default = true; + m_value->parse(); + } + + size_t + count() const noexcept + { + return m_count; + } + + // TODO: maybe default options should count towards the number of arguments + bool + has_default() const noexcept + { + return m_default; + } + + template + const T& + as() const + { + if (m_value == nullptr) { + throw_or_mimic("No value"); + } + +#ifdef CXXOPTS_NO_RTTI + return static_cast&>(*m_value).get(); +#else + return dynamic_cast&>(*m_value).get(); +#endif + } + + private: + void + ensure_value(std::shared_ptr details) + { + if (m_value == nullptr) + { + m_value = details->make_storage(); + } + } + + std::shared_ptr m_value; + size_t m_count = 0; + bool m_default = false; + }; + + class KeyValue + { + public: + KeyValue(std::string key_, std::string value_) + : m_key(std::move(key_)) + , m_value(std::move(value_)) + { + } + + const + std::string& + key() const + { + return m_key; + } + + const + std::string& + value() const + { + return m_value; + } + + template + T + as() const + { + T result; + values::parse_value(m_value, result); + return result; + } + + private: + std::string m_key; + std::string m_value; + }; + + class ParseResult + { + public: + + ParseResult( + const std::shared_ptr< + std::unordered_map> + >, + std::vector, + bool allow_unrecognised, + int&, char**&); + + size_t + count(const std::string& o) const + { + auto iter = m_options->find(o); + if (iter == m_options->end()) + { + return 0; + } + + auto riter = m_results.find(iter->second); + + return riter->second.count(); + } + + const OptionValue& + operator[](const std::string& option) const + { + auto iter = m_options->find(option); + + if (iter == m_options->end()) + { + throw_or_mimic(option); + } + + auto riter = m_results.find(iter->second); + + return riter->second; + } + + const std::vector& + arguments() const + { + return m_sequential; + } + + private: + + void + parse(int& argc, char**& argv); + + void + add_to_option(const std::string& option, const std::string& arg); + + bool + consume_positional(std::string a); + + void + parse_option + ( + std::shared_ptr value, + const std::string& name, + const std::string& arg = "" + ); + + void + parse_default(std::shared_ptr details); + + void + checked_parse_arg + ( + int argc, + char* argv[], + int& current, + std::shared_ptr value, + const std::string& name + ); + + const std::shared_ptr< + std::unordered_map> + > m_options; + std::vector m_positional; + std::vector::iterator m_next_positional; + std::unordered_set m_positional_set; + std::unordered_map, OptionValue> m_results; + + bool m_allow_unrecognised; + + std::vector m_sequential; + }; + + struct Option + { + Option + ( + const std::string& opts, + const std::string& desc, + const std::shared_ptr& value = ::cxxopts::value(), + const std::string& arg_help = "" + ) + : opts_(opts) + , desc_(desc) + , value_(value) + , arg_help_(arg_help) + { + } + + std::string opts_; + std::string desc_; + std::shared_ptr value_; + std::string arg_help_; + }; + + class Options + { + typedef std::unordered_map> + OptionMap; + public: + + Options(std::string program, std::string help_string = "") + : m_program(std::move(program)) + , m_help_string(toLocalString(std::move(help_string))) + , m_custom_help("[OPTION...]") + , m_positional_help("positional parameters") + , m_show_positional(false) + , m_allow_unrecognised(false) + , m_options(std::make_shared()) + , m_next_positional(m_positional.end()) + { + } + + Options& + positional_help(std::string help_text) + { + m_positional_help = std::move(help_text); + return *this; + } + + Options& + custom_help(std::string help_text) + { + m_custom_help = std::move(help_text); + return *this; + } + + Options& + show_positional_help() + { + m_show_positional = true; + return *this; + } + + Options& + allow_unrecognised_options() + { + m_allow_unrecognised = true; + return *this; + } + + ParseResult + parse(int& argc, char**& argv); + + OptionAdder + add_options(std::string group = ""); + + void + add_options + ( + const std::string& group, + std::initializer_list