diff --git a/.appveyor.yml b/.appveyor.yml index a7a6cee27..cf383f9d3 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -42,9 +42,9 @@ for: - ${APPVEYOR_BUILD_FOLDER}/scripts/appveyor/before_build.sh build_script: - cd build - - make -j2 sfizz_tests + - cmake --build . -j2 --target sfizz_tests - tests/sfizz_tests - - make -j2 + - cmake --build . -j2 after_build: - chmod +x ${APPVEYOR_BUILD_FOLDER}/scripts/appveyor/after_build.sh - ${APPVEYOR_BUILD_FOLDER}/scripts/appveyor/after_build.sh diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 510768370..3ffc726a8 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -44,6 +44,7 @@ jobs: run: | sudo apt-get update && \ sudo apt-get install \ + ninja-build \ libjack-jackd2-dev \ libsndfile1-dev \ libcairo2-dev \ @@ -64,10 +65,12 @@ jobs: shell: bash working-directory: ${{runner.workspace}}/build run: | - cmake "$GITHUB_WORKSPACE" -DCMAKE_BUILD_TYPE="$BUILD_TYPE" \ + cmake "$GITHUB_WORKSPACE" -G Ninja \ + -DCMAKE_BUILD_TYPE="$BUILD_TYPE" \ -DSFIZZ_JACK=ON \ -DSFIZZ_VST=ON \ -DSFIZZ_LV2_UI=ON \ + -DSFIZZ_PUREDATA=ON \ -DSFIZZ_TESTS=ON \ -DSFIZZ_SHARED=OFF \ -DSFIZZ_STATIC_DEPENDENCIES=OFF \ @@ -137,15 +140,24 @@ jobs: name: MOD devices tarball path: ${{runner.workspace}}/build/${{env.install_name}}.tar.gz - build_for_win32: + build_for_windows: runs-on: windows-2019 + strategy: + matrix: + include: + - platform: x86 + pkg_platform: Win32 + release_arch: Win32 + bits: 32 + - platform: x64 + pkg_platform: Win64 + release_arch: x64 + bits: 64 steps: - name: Set install name run: | - echo platform=x86 >> "${Env:GITHUB_ENV}" - echo release_arch=Win32 >> "${Env:GITHUB_ENV}" echo "install_ref=$(${Env:GITHUB_REF}.split('/')[-1])" >> "${Env:GITHUB_ENV}" - echo "install_name=sfizz-$(${Env:GITHUB_REF}.split('/')[-1])-win32" >> "${Env:GITHUB_ENV}" + echo "install_name=sfizz-$(${Env:GITHUB_REF}.split('/')[-1])-win${{matrix.bits}}" >> "${Env:GITHUB_ENV}" - uses: actions/checkout@v2 with: submodules: recursive @@ -155,42 +167,7 @@ jobs: - name: Configure CMake working-directory: ${{runner.workspace}}/build run: | - cmake "${Env:GITHUB_WORKSPACE}" -G"Visual Studio 16 2019" -A"${Env:release_arch}" -DCMAKE_BUILD_TYPE="${Env:BUILD_TYPE}" -DCMAKE_CXX_STANDARD=17 -DSFIZZ_TESTS=ON -DSFIZZ_VST=ON -DSFIZZ_LV2=ON - - name: Build tests - working-directory: ${{runner.workspace}}/build - run: cmake --build . --config "${Env:BUILD_TYPE}" -j 2 --target sfizz_tests - - name: Test - run: ${{runner.workspace}}/build/tests/Release/sfizz_tests - - name: Build all - working-directory: ${{runner.workspace}}/build - run: cmake --build . --config "${Env:BUILD_TYPE}" -j 2 - - name: Create installer - working-directory: ${{runner.workspace}}/build - run: iscc /O"." /F"${Env:install_name}" /dARCH="${Env:platform}" innosetup.iss - - uses: actions/upload-artifact@v2 - with: - name: Win32 installer - path: ${{runner.workspace}}/build/${{env.install_name}}.exe - - build_for_win64: - runs-on: windows-2019 - steps: - - name: Set install name - run: | - echo platform=x64 >> "${Env:GITHUB_ENV}" - echo release_arch=x64 >> "${Env:GITHUB_ENV}" - echo "install_ref=$(${Env:GITHUB_REF}.split('/')[-1])" >> "${Env:GITHUB_ENV}" - echo "install_name=sfizz-$(${Env:GITHUB_REF}.split('/')[-1])-win64" >> "${Env:GITHUB_ENV}" - - uses: actions/checkout@v2 - with: - submodules: recursive - - name: Create Build Environment - working-directory: ${{runner.workspace}} - run: cmake -E make_directory build - - name: Configure CMake - working-directory: ${{runner.workspace}}/build - run: | - cmake "${Env:GITHUB_WORKSPACE}" -G"Visual Studio 16 2019" -A"${Env:release_arch}" -DCMAKE_BUILD_TYPE="${Env:BUILD_TYPE}" -DCMAKE_CXX_STANDARD=17 -DSFIZZ_TESTS=ON -DSFIZZ_VST=ON -DSFIZZ_LV2=ON + cmake "${Env:GITHUB_WORKSPACE}" -G"Visual Studio 16 2019" -A"${{matrix.release_arch}}" -DCMAKE_BUILD_TYPE="${Env:BUILD_TYPE}" -DCMAKE_CXX_STANDARD=17 -DSFIZZ_TESTS=ON -DSFIZZ_VST=ON -DSFIZZ_LV2=ON -DSFIZZ_PUREDATA=ON - name: Build tests working-directory: ${{runner.workspace}}/build run: cmake --build . --config "${Env:BUILD_TYPE}" -j 2 --target sfizz_tests @@ -200,31 +177,42 @@ jobs: working-directory: ${{runner.workspace}}/build run: cmake --build . --config "${Env:BUILD_TYPE}" -j 2 - name: Install pluginval + if: ${{ matrix.platform == 'x64' }} run: | Invoke-WebRequest https://github.com/Tracktion/pluginval/releases/download/latest_release/pluginval_Windows.zip -OutFile pluginval.zip Expand-Archive pluginval.zip -DestinationPath pluginval echo "$(Get-Location)\pluginval" | Out-File -FilePath ${Env:GITHUB_PATH} -Encoding utf8 -Append pluginval\pluginval --version - name: Validate VST3 + if: ${{ matrix.platform == 'x64' }} working-directory: ${{runner.workspace}}/build run: pluginval --validate-in-process --validate sfizz.vst3 - name: Create installer working-directory: ${{runner.workspace}}/build - run: iscc /O"." /F"${Env:install_name}" /dARCH="${Env:platform}" innosetup.iss + run: iscc /O"." /F"${Env:install_name}" /dARCH="${{matrix.platform}}" innosetup.iss - uses: actions/upload-artifact@v2 with: - name: Win64 installer + name: ${{matrix.pkg_platform}} installer path: ${{runner.workspace}}/build/${{env.install_name}}.exe - build_for_mingw32: + build_for_mingw: runs-on: ubuntu-18.04 + strategy: + matrix: + include: + - platform: i686 + pkg_platform: Win32 + bits: 32 + - platform: x86_64 + pkg_platform: Win64 + bits: 64 container: image: archlinux steps: - name: Set install name run: | echo "install_ref=${GITHUB_REF##*/}" >> "$GITHUB_ENV" - echo "install_name=sfizz-${GITHUB_REF##*/}-mingw32" >> "$GITHUB_ENV" + echo "install_name=sfizz-${GITHUB_REF##*/}-mingw${{matrix.bits}}" >> "$GITHUB_ENV" - name: Configure pacman repositories shell: bash run: | @@ -239,7 +227,7 @@ jobs: shell: bash run: | pacman -Sqyu --noconfirm - pacman -Sq --needed --noconfirm base-devel git wget mingw-w64-cmake mingw-w64-gcc mingw-w64-pkg-config mingw-w64-libsndfile + pacman -Sq --needed --noconfirm base-devel git wget ninja mingw-w64-cmake mingw-w64-gcc mingw-w64-pkg-config mingw-w64-libsndfile - uses: actions/checkout@v2 with: submodules: recursive @@ -247,14 +235,7 @@ jobs: shell: bash run: | cp -vf "$GITHUB_WORKSPACE"/scripts/mingw_dwrite_3.h \ - /usr/i686-w64-mingw32/include/dwrite_3.h - - name: Fix VST sources - shell: bash - # need to convert some includes to lower case (as of VST 3.7.1) - run: | - find "$GITHUB_WORKSPACE"/plugins/vst/external/VST_SDK -type d -name source -exec \ - find {} -type f -name '*.[hc]' -o -name '*.[hc]pp' -print0 \; | \ - xargs -0 sed -i 's///' + /usr/${{matrix.platform}}-w64-mingw32/include/dwrite_3.h - name: Create Build Environment shell: bash working-directory: ${{runner.workspace}} @@ -263,95 +244,27 @@ jobs: shell: bash working-directory: ${{runner.workspace}}/build run: | - i686-w64-mingw32-cmake "$GITHUB_WORKSPACE" \ + ${{matrix.platform}}-w64-mingw32-cmake "$GITHUB_WORKSPACE" -G Ninja \ -DCMAKE_BUILD_TYPE="$BUILD_TYPE" \ -DENABLE_LTO=OFF \ -DSFIZZ_JACK=OFF \ -DSFIZZ_VST=ON \ + -DSFIZZ_PUREDATA=ON \ -DSFIZZ_STATIC_DEPENDENCIES=ON \ -DCMAKE_CXX_STANDARD=17 - name: Build shell: bash working-directory: ${{runner.workspace}}/build - run: i686-w64-mingw32-cmake --build . --config "$BUILD_TYPE" -j 2 + run: ${{matrix.platform}}-w64-mingw32-cmake --build . --config "$BUILD_TYPE" -j 2 - name: Install working-directory: ${{runner.workspace}}/build shell: bash run: | - DESTDIR="$(pwd)/$install_name" i686-w64-mingw32-cmake --build . --config "$BUILD_TYPE" --target install + DESTDIR="$(pwd)/$install_name" ${{matrix.platform}}-w64-mingw32-cmake --build . --config "$BUILD_TYPE" --target install tar czvf "$install_name".tar.gz "$install_name" - uses: actions/upload-artifact@v2 with: - name: Win32 MinGW tarball - path: ${{runner.workspace}}/build/${{env.install_name}}.tar.gz - - build_for_mingw64: - runs-on: ubuntu-18.04 - container: - image: archlinux - steps: - - name: Set install name - run: | - echo "install_ref=${GITHUB_REF##*/}" >> "$GITHUB_ENV" - echo "install_name=sfizz-${GITHUB_REF##*/}-mingw64" >> "$GITHUB_ENV" - - name: Configure pacman repositories - shell: bash - run: | - cat >>/etc/pacman.conf <//' - - name: Create Build Environment - shell: bash - working-directory: ${{runner.workspace}} - run: cmake -E make_directory build - - name: Configure CMake - shell: bash - working-directory: ${{runner.workspace}}/build - run: | - x86_64-w64-mingw32-cmake "$GITHUB_WORKSPACE" \ - -DCMAKE_BUILD_TYPE="$BUILD_TYPE" \ - -DENABLE_LTO=OFF \ - -DSFIZZ_JACK=OFF \ - -DSFIZZ_VST=ON \ - -DSFIZZ_STATIC_DEPENDENCIES=ON \ - -DCMAKE_CXX_STANDARD=17 - - name: Build - shell: bash - working-directory: ${{runner.workspace}}/build - run: x86_64-w64-mingw32-cmake --build . --config "$BUILD_TYPE" -j 2 - - name: Install - working-directory: ${{runner.workspace}}/build - shell: bash - run: | - DESTDIR="$(pwd)/$install_name" x86_64-w64-mingw32-cmake --build . --config "$BUILD_TYPE" --target install - tar czvf "$install_name".tar.gz "$install_name" - - uses: actions/upload-artifact@v2 - with: - name: Win64 MinGW tarball + name: ${{matrix.pkg_platform}} MinGW tarball path: ${{runner.workspace}}/build/${{env.install_name}}.tar.gz build_with_makefile: @@ -417,16 +330,16 @@ jobs: needs: - build_for_linux - build_for_mod - - build_for_mingw32 - - build_for_mingw64 + - build_for_mingw + - build_for_windows - archive_source_code steps: - name: Set install name run: | echo "install_ref=${GITHUB_REF##*/}" >> "$GITHUB_ENV" - - uses: actions/download-artifact@v2 - with: - name: Linux tarball + # - uses: actions/download-artifact@v2 + # with: + # name: Linux tarball - uses: actions/download-artifact@v2 with: name: MOD devices tarball @@ -436,6 +349,12 @@ jobs: - uses: actions/download-artifact@v2 with: name: Win64 MinGW tarball + - uses: actions/download-artifact@v2 + with: + name: Win32 installer + - uses: actions/download-artifact@v2 + with: + name: Win64 installer - uses: actions/download-artifact@v2 with: name: Source code tarball diff --git a/.gitignore b/.gitignore index f1c7bda93..6b888b16a 100644 --- a/.gitignore +++ b/.gitignore @@ -15,6 +15,8 @@ compile_commands.json *.autosave /Doxyfile .DS_Store +.cache/ +.cmake/ clients/sfizz_jack clients/sfzprint diff --git a/CMakeLists.txt b/CMakeLists.txt index b45737c0c..49bde5d2e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -28,6 +28,7 @@ option_ex (SFIZZ_LV2_UI "Enable LV2 plug-in user interface" ON) option_ex (SFIZZ_VST "Enable VST plug-in build" ON) option_ex (SFIZZ_AU "Enable AU plug-in build" APPLE) option_ex (SFIZZ_VST2 "Enable VST2 plug-in build (unsupported)" OFF) +option_ex (SFIZZ_PUREDATA "Enable Puredata plug-in build" OFF) option_ex (SFIZZ_BENCHMARKS "Enable benchmarks build" OFF) option_ex (SFIZZ_TESTS "Enable tests build" OFF) option_ex (SFIZZ_DEMOS "Enable feature demos build" OFF) @@ -37,8 +38,12 @@ option_ex (SFIZZ_USE_SNDFILE "Enable use of the sndfile library" OFF) option_ex (SFIZZ_USE_VCPKG "Assume that sfizz is build using vcpkg" OFF) option_ex (SFIZZ_USE_SYSTEM_ABSEIL "Use Abseil libraries preinstalled on system" OFF) option_ex (SFIZZ_USE_SYSTEM_SIMDE "Use SIMDe libraries preinstalled on system" OFF) +option_ex (SFIZZ_USE_SYSTEM_KISS_FFT "Use KISS FFT libraries preinstalled on system" OFF) +option_ex (SFIZZ_USE_SYSTEM_PUGIXML "Use pugixml libraries preinstalled on system" OFF) +option_ex (SFIZZ_USE_SYSTEM_CXXOPTS "Use CXXOPTS libraries preinstalled on system" OFF) option_ex (SFIZZ_STATIC_DEPENDENCIES "Link dependencies statically" OFF) option_ex (SFIZZ_RELEASE_ASSERTS "Forced assertions in release builds" OFF) +option_ex (SFIZZ_PROFILE_BUILD "Profile the build time" OFF) # The fixed number of controller parameters set(SFIZZ_NUM_CCS 512) diff --git a/LICENSE.md b/LICENSE similarity index 89% rename from LICENSE.md rename to LICENSE index d3f769aef..7334143fc 100644 --- a/LICENSE.md +++ b/LICENSE @@ -1,10 +1,6 @@ BSD 2-Clause License -The code is copyrighted by their respective authors, as indicated by the -source control mechanism. - -Please refer to AUTHORS.md for the list of contributors. - +Copyright (c) 2021, sfizz contributors (detailed in AUTHORS.md) All rights reserved. Redistribution and use in source and binary forms, with or without diff --git a/README.md b/README.md index da36e532e..079e2be50 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,10 @@ [![AppVeyor Build Status]](https://ci.appveyor.com/project/sfztools/sfizz) [![Discord Badge Image]](https://discord.gg/3ArE9Mw) +[![SFZv1 Status Image]](https://sfz.tools/sfizz/development/status/opcodes/?v=1) +[![SFZv2 Status Image]](https://sfz.tools/sfizz/development/status/opcodes/?v=2) +[![ARIA Status Image]](https://sfz.tools/sfizz/development/status/opcodes/?v=aria) +[![Cakewalk Status Image]](https://sfz.tools/sfizz/development/status/opcodes/?v=cakewalk) SFZ parser and synth c++ library, providing AU / LV2 / VST3 plugins and JACK standalone client, please check [our website] for more details. @@ -36,9 +40,14 @@ We invite you to check out the [GOVERNANCE](GOVERNANCE.md) file to see how the o ## Dependencies and licenses +The sfizz library has the option to be compiled against either the `dr_libs` +audio libraries, which is now the default option, or `libsndfile`. + +- [dr_libs] is licensed under the MIT No Attribution license +- [libsndfile] is licensed under the GNU Lesser General Public License v2.1 + The sfizz library makes primary use of: -- [libsndfile], licensed under the GNU Lesser General Public License v2.1 - [Abseil], licensed under the Apache License 2.0 - [atomic_queue] by Maxim Egorushkin, licensed under the MIT license - [filesystem] by Steffen Schümann, licensed under the BSD 3-Clause license @@ -71,6 +80,7 @@ The sfizz library also uses in some subprojects: [pugixml]: https://pugixml.org/ [cephes]: https://www.netlib.org/cephes/ [cpuid]: https://github.com/steinwurf/cpuid +[dr_libs]: https://github.com/mackron/dr_libs [faust-libraries]: https://github.com/grame-cncm/faustlibraries [hiir]: http://ldesoras.free.fr/prod.html#src_hiir [KISS FFT]: http://kissfft.sourceforge.net/ @@ -92,3 +102,8 @@ The sfizz library also uses in some subprojects: [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 [Discord Badge Image]: https://img.shields.io/discord/587748534321807416?label=discord&logo=discord + +[SFZv1 Status Image]: https://sfz.tools/assets/img/sfizz/badge_sfz1.svg +[SFZv2 Status Image]: https://sfz.tools/assets/img/sfizz/badge_sfz2.svg +[ARIA Status Image]: https://sfz.tools/assets/img/sfizz/badge_aria.svg +[Cakewalk Status Image]: https://sfz.tools/assets/img/sfizz/badge_cakewalk.svg diff --git a/clients/CMakeLists.txt b/clients/CMakeLists.txt index 37e6ed774..cf10957b3 100644 --- a/clients/CMakeLists.txt +++ b/clients/CMakeLists.txt @@ -1,14 +1,22 @@ +string(TIMESTAMP MAN_TODAY "%Y-%m-%d") + if(SFIZZ_JACK) add_executable(sfizz_jack MidiHelpers.h jack_client.cpp) - target_link_libraries(sfizz_jack PRIVATE sfizz::sfizz sfizz::jack sfizz::spin_mutex absl::flags_parse) + target_link_libraries(sfizz_jack PRIVATE sfizz::import sfizz::sfizz sfizz::jack sfizz::spin_mutex absl::flags_parse) sfizz_enable_lto_if_needed(sfizz_jack) + configure_file(sfizz_jack.man.in sfizz_jack.man @ONLY) install(TARGETS sfizz_jack DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT "jack" OPTIONAL) + install(FILES ${PROJECT_BINARY_DIR}/clients/sfizz_jack.man DESTINATION ${CMAKE_INSTALL_MANDIR}/man1 + RENAME sfizz_jack COMPONENT "jack" OPTIONAL) endif() if(SFIZZ_RENDER) add_executable(sfizz_render MidiHelpers.h sfizz_render.cpp) target_link_libraries(sfizz_render PRIVATE sfizz::internal sfizz::fmidi sfizz::cxxopts st_audiofile_formats) sfizz_enable_lto_if_needed(sfizz_render) + configure_file(sfizz_render.man.in sfizz_render.man @ONLY) install(TARGETS sfizz_render DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT "render" OPTIONAL) + install(FILES ${PROJECT_BINARY_DIR}/clients/sfizz_render.man DESTINATION ${CMAKE_INSTALL_MANDIR}/man1 + RENAME sfizz_render COMPONENT "render" OPTIONAL) endif() diff --git a/clients/jack_client.cpp b/clients/jack_client.cpp index cd5f8fd74..20d6e6d5f 100644 --- a/clients/jack_client.cpp +++ b/clients/jack_client.cpp @@ -22,6 +22,7 @@ // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "sfizz.hpp" +#include "sfizz/import/sfizz_import.h" #include "MidiHelpers.h" #include #include @@ -41,6 +42,9 @@ #include #include #include +#include + +sfz::Sfizz synth; static jack_port_t* midiInputPort; static jack_port_t* outputPort1; @@ -148,30 +152,148 @@ static void done(int sig) // exit(0); } +bool loadInstrument(const char* fpath) +{ + const char* importFormat = nullptr; + if (!sfizz_load_or_import_file(synth.handle(), fpath, &importFormat)) { + std::cout << "Could not load the instrument file: " << fpath << '\n'; + return false; + } + + std::cout << "Instrument loaded: " << fpath << '\n'; + std::cout << "===========================" << '\n'; + std::cout << "Total:" << '\n'; + std::cout << "\tMasters: " << synth.getNumMasters() << '\n'; + std::cout << "\tGroups: " << synth.getNumGroups() << '\n'; + std::cout << "\tRegions: " << synth.getNumRegions() << '\n'; + std::cout << "\tCurves: " << synth.getNumCurves() << '\n'; + std::cout << "\tPreloadedSamples: " << synth.getNumPreloadedSamples() << '\n'; +#if 0 // not currently in public API + std::cout << "===========================" << '\n'; + std::cout << "Included files:" << '\n'; + for (auto& file : synth.getParser().getIncludedFiles()) + std::cout << '\t' << file << '\n'; + std::cout << "===========================" << '\n'; + std::cout << "Defines:" << '\n'; + for (auto& define : synth.getParser().getDefines()) + std::cout << '\t' << define.first << '=' << define.second << '\n'; +#endif + std::cout << "===========================" << '\n'; + std::cout << "Unknown opcodes:"; + for (auto& opcode : synth.getUnknownOpcodes()) + std::cout << opcode << ','; + std::cout << '\n'; + if (importFormat) { + std::cout << "===========================" << '\n'; + std::cout << "Import format: " << importFormat << '\n'; + } + // std::cout << std::flush; + + return true; +} + +std::vector stringTokenize(const std::string& str) +{ + std::vector tokens; + std::string part = ""; + for (size_t i = 0; i < str.length(); i++) { + char c = str[i]; + if (c == ' ' && part != "") { + tokens.push_back(part); + part = ""; + } else if (c == '\"') { + i++; + while (str[i] != '\"') { + part += str[i]; + i++; + } + tokens.push_back(part); + part = ""; + } else { + part += c; + } + } + if (part != "") { + tokens.push_back(part); + } + return tokens; +} + +void cliThreadProc() +{ + while (!shouldClose) { + std::cout << "\n> "; + + std::string command; + std::getline(std::cin, command); + std::size_t pos = command.find(" "); + std::string kw = command.substr(0, pos); + std::string args = command.substr(pos + 1); + std::vector tokens = stringTokenize(args); + + if (kw == "load_instrument") { + try { + std::lock_guard lock { processMutex }; + loadInstrument(tokens[0].c_str()); + } catch (...) { + std::cout << "ERROR: Can't load instrument!\n"; + } + } else if (kw == "set_oversampling") { + try { + std::lock_guard lock { processMutex }; + synth.setOversamplingFactor(stoi(args)); + } catch (...) { + std::cout << "ERROR: Can't set oversampling!\n"; + } + } else if (kw == "set_preload_size") { + try { + std::lock_guard lock { processMutex }; + synth.setPreloadSize(stoi(args)); + } catch (...) { + std::cout << "ERROR: Can't set preload size!\n"; + } + } else if (kw == "set_voices") { + try { + std::lock_guard lock { processMutex }; + synth.setNumVoices(stoi(args)); + } catch (...) { + std::cout << "ERROR: Can't set num of voices!\n"; + } + } else if (kw == "quit") { + shouldClose = true; + } else if (kw.size() > 0) { + std::cout << "ERROR: Unknown command '" << kw << "'!\n"; + } + } +} + ABSL_FLAG(std::string, client_name, "sfizz", "Jack client name"); ABSL_FLAG(std::string, oversampling, "1x", "Internal oversampling factor (value values are x1, x2, x4, x8)"); -ABSL_FLAG(uint32_t, preload_size, 8192, "Preloaded value"); +ABSL_FLAG(uint32_t, preload_size, 8192, "Preloaded size"); +ABSL_FLAG(uint32_t, num_voices, 32, "Num of voices"); +ABSL_FLAG(bool, jack_autoconnect, false, "Autoconnect audio output"); ABSL_FLAG(bool, state, false, "Output the synth state in the jack loop"); int main(int argc, char** argv) { - // std::ios::sync_with_stdio(false); auto arguments = absl::ParseCommandLine(argc, argv); - if (arguments.size() < 2) { - std::cout << "You need to specify an SFZ file to load." << '\n'; - return -1; - } auto filesToParse = absl::MakeConstSpan(arguments).subspan(1); const std::string clientName = absl::GetFlag(FLAGS_client_name); const std::string oversampling = absl::GetFlag(FLAGS_oversampling); const uint32_t preload_size = absl::GetFlag(FLAGS_preload_size); + const uint32_t num_voices = absl::GetFlag(FLAGS_num_voices); + const bool jack_autoconnect = absl::GetFlag(FLAGS_jack_autoconnect); const bool verboseState = absl::GetFlag(FLAGS_state); std::cout << "Flags" << '\n'; std::cout << "- Client name: " << clientName << '\n'; std::cout << "- Oversampling: " << oversampling << '\n'; - std::cout << "- Preloaded Size: " << preload_size << '\n'; + std::cout << "- Preloaded size: " << preload_size << '\n'; + std::cout << "- Num of voices: " << num_voices << '\n'; + std::cout << "- Audio Autoconnect: " << jack_autoconnect << '\n'; + std::cout << "- Verbose State: " << verboseState << '\n'; + const auto factor = [&]() { if (oversampling == "x1") return 1; if (oversampling == "x2") return 2; @@ -185,33 +307,9 @@ int main(int argc, char** argv) std::cout << " " << file << ','; std::cout << '\n'; - sfz::Sfizz synth; synth.setOversamplingFactor(factor); synth.setPreloadSize(preload_size); - synth.loadSfzFile(filesToParse[0]); - std::cout << "==========" << '\n'; - std::cout << "Total:" << '\n'; - std::cout << "\tMasters: " << synth.getNumMasters() << '\n'; - std::cout << "\tGroups: " << synth.getNumGroups() << '\n'; - std::cout << "\tRegions: " << synth.getNumRegions() << '\n'; - std::cout << "\tCurves: " << synth.getNumCurves() << '\n'; - std::cout << "\tPreloadedSamples: " << synth.getNumPreloadedSamples() << '\n'; -#if 0 // not currently in public API - std::cout << "==========" << '\n'; - std::cout << "Included files:" << '\n'; - for (auto& file : synth.getParser().getIncludedFiles()) - std::cout << '\t' << file << '\n'; - std::cout << "==========" << '\n'; - std::cout << "Defines:" << '\n'; - for (auto& define : synth.getParser().getDefines()) - std::cout << '\t' << define.first << '=' << define.second << '\n'; -#endif - std::cout << "==========" << '\n'; - std::cout << "Unknown opcodes:"; - for (auto& opcode : synth.getUnknownOpcodes()) - std::cout << opcode << ','; - std::cout << '\n'; - // std::cout << std::flush; + synth.setNumVoices(num_voices); jack_status_t status; client = jack_client_open(clientName.c_str(), JackNullOption, &status); @@ -254,32 +352,40 @@ int main(int argc, char** argv) return 1; } - auto systemPorts = jack_get_ports(client, nullptr, nullptr, JackPortIsPhysical | JackPortIsInput); - if (systemPorts == nullptr) { - std::cerr << "No physical output ports found" << '\n'; - return 1; - } + if (jack_autoconnect) { + auto systemPorts = jack_get_ports(client, nullptr, nullptr, JackPortIsPhysical | JackPortIsInput); + if (systemPorts == nullptr) { + std::cerr << "No physical output ports found" << '\n'; + return 1; + } - if (jack_connect(client, jack_port_name(outputPort1), systemPorts[0])) { - std::cerr << "Cannot connect to physical output ports (0)" << '\n'; + if (jack_connect(client, jack_port_name(outputPort1), systemPorts[0])) { + std::cerr << "Cannot connect to physical output ports (0)" << '\n'; + } + + if (jack_connect(client, jack_port_name(outputPort2), systemPorts[1])) { + std::cerr << "Cannot connect to physical output ports (1)" << '\n'; + } + jack_free(systemPorts); } - if (jack_connect(client, jack_port_name(outputPort2), systemPorts[1])) { - std::cerr << "Cannot connect to physical output ports (1)" << '\n'; + if (!filesToParse.empty() && filesToParse[0]) { + loadInstrument(filesToParse[0]); } - jack_free(systemPorts); + + std::thread cli_thread(cliThreadProc); signal(SIGHUP, done); signal(SIGINT, done); signal(SIGTERM, done); signal(SIGQUIT, done); - while (!shouldClose){ + while (!shouldClose) { if (verboseState) { std::cout << "Active voices: " << synth.getNumActiveVoices() << '\n'; #ifndef NDEBUG - std::cout << "Allocated buffers: " << synth.getAllocatedBuffers() << '\n'; - std::cout << "Total size: " << synth.getAllocatedBytes() << '\n'; + std::cout << "Allocated buffers: " << synth.getAllocatedBuffers() << '\n'; + std::cout << "Total size: " << synth.getAllocatedBytes() << '\n'; #endif } std::this_thread::sleep_for(std::chrono::seconds(1)); diff --git a/clients/sfizz_jack.man.in b/clients/sfizz_jack.man.in new file mode 100644 index 000000000..35815adc1 --- /dev/null +++ b/clients/sfizz_jack.man.in @@ -0,0 +1,37 @@ +.TH sfizz_jack 1 "@MAN_TODAY@" "@CMAKE_PROJECT_VERSION@" "sfizz_jack man page" +.SH NAME +sfizz_jack \- Launch a sfizz instance as a JACK client +.SH SYNOPSIS +sfizz_jack [FILE] [OPTIONS...] +.SH DESCRIPTION +sfizz_jack wraps the sfizz SFZ library in a JACK client that can be controlled by a text interface. +.SH OPTIONS +.IP FILE +An optional SFZ file name to load at firsts +.IP "--client_name" +Name for the JACK client +.IP "--num_voices NUMBER" +Change the maximum polyphony +.IP "--preload_size NUMBER" +Bytes to preload in cache for samples +.IP "--state" +Output the state in the JACK loop +.IP "--jack_autoconnect" +Autoconnect the JACK outputs +.SH TEXT INTERFACE +It is possible it interact with the JACK client through the standard input. +The possible commands are +.IP "load_instrument FILE" +Load an instrument +.IP "set_preload_size NUMBER" +Set the number of bytes to preload in cache for samples +.IP "set_voices NUMBER" +Change the maximum polyphony +.IP quit +Quit +.SH SEE ALSO +sfizz_render(1) +.SH BUGS +See the main repository at @SFIZZ_REPOSITORY@. +.SH AUTHOR +Contributors can be seen on the main repository at @SFIZZ_REPOSITORY@. diff --git a/clients/sfizz_render.cpp b/clients/sfizz_render.cpp index 48b09bc1e..8bb84ea3b 100644 --- a/clients/sfizz_render.cpp +++ b/clients/sfizz_render.cpp @@ -66,6 +66,7 @@ int main(int argc, char** argv) bool help { false }; bool useEOT { false }; int quality { 2 }; + int polyphony { 64 }; options.add_options() ("sfz", "SFZ file", cxxopts::value()) @@ -74,6 +75,7 @@ int main(int argc, char** argv) ("b,blocksize", "Block size for the sfizz callbacks", cxxopts::value(blockSize)) ("s,samplerate", "Output sample rate", cxxopts::value(sampleRate)) ("q,quality", "Resampling quality", cxxopts::value(quality)) + ("p,polyphony", "Polyphony max", cxxopts::value(polyphony)) ("v,verbose", "Verbose output", cxxopts::value(verbose)) ("log", "Produce logs", cxxopts::value()) ("use-eot", "End the rendering at the last End of Track Midi message", cxxopts::value(useEOT)) @@ -114,11 +116,13 @@ int main(int argc, char** argv) LOG_INFO("Output file: " << outputPath.string()); LOG_INFO("Block size: " << blockSize); LOG_INFO("Sample rate: " << sampleRate); + LOG_INFO("Polyphony Max: " << polyphony); sfz::Synth synth; synth.setSamplesPerBlock(blockSize); synth.setSampleRate(sampleRate); synth.setSampleQuality(sfz::Synth::ProcessMode::ProcessFreewheeling, quality); + synth.setNumVoices(polyphony); synth.enableFreeWheeling(); if (params.count("log") > 0) diff --git a/clients/sfizz_render.man.in b/clients/sfizz_render.man.in new file mode 100644 index 000000000..73a12e128 --- /dev/null +++ b/clients/sfizz_render.man.in @@ -0,0 +1,30 @@ +.TH sfizz_render 1 "@MAN_TODAY@" "@CMAKE_PROJECT_VERSION@" "sfizz_render man page" +.SH NAME +sfizz_render \- Render a MIDI file as a WAV file using an SFZ instrument description. +.SH SYNOPSIS +sfizz_render --sfz FILE --wav FILE --midi FILE [OPTIONS...] +.SH DESCRIPTION +sfizz_render wraps the sfizz SFZ library and can be used to render midi file as sound files using an SFZ description file and its associated samples. +.SH OPTIONS +.IP "-b, --blocksize NUMBER" +Block size for the sfizz callbacks +.IP "-s, --samplerate NUMBER" +Output sample rate +.IP "-q, --quality NUMBER" +Resampling quality, like the SFZ sample_quality opcode. A value of 1 will use a linear interpolation of source samples, while higher value will use increasingly better algorithms. +.IP "-p, --polyphony NUMBER" +Maximum polyphony +.IP "-v, --verbose" +Verbose output +.IP "--log PREFIX" +Produce logs +.IP "--use-eot" +End the rendering at the last End of Track Midi message +.IP "-h, --help" +Show help +.SH SEE ALSO +sfizz_jack(1) +.SH BUGS +See the main repository at @SFIZZ_REPOSITORY@. +.SH AUTHOR +Contributors can be seen on the main repository at @SFIZZ_REPOSITORY@. diff --git a/plugins/editor/cmake/GitBuildID.cmake b/cmake/GitBuildID.cmake similarity index 100% rename from plugins/editor/cmake/GitBuildID.cmake rename to cmake/GitBuildID.cmake diff --git a/cmake/LV2Config.cmake b/cmake/LV2Config.cmake index 5a6fb0440..da2bd40ff 100644 --- a/cmake/LV2Config.cmake +++ b/cmake/LV2Config.cmake @@ -8,7 +8,7 @@ set(LV2PLUGIN_VERSION_MICRO 0) set(LV2PLUGIN_NAME "sfizz") set(LV2PLUGIN_COMMENT "SFZ sampler") set(LV2PLUGIN_URI "http://sfztools.github.io/sfizz") -set(LV2PLUGIN_REPOSITORY "https://github.com/sfztools/sfizz") +set(LV2PLUGIN_REPOSITORY SFIZZ_REPOSITORY) set(LV2PLUGIN_AUTHOR "SFZTools") set(LV2PLUGIN_EMAIL "paul@ferrand.cc") if(SFIZZ_USE_VCPKG) @@ -62,7 +62,9 @@ function(sfizz_lv2_generate_controllers_ttl FILE) sfizz:cc${_i} a lv2:Parameter ; rdfs:label \"Controller ${_i}\" ; - rdfs:range atom:Float") + rdfs:range atom:Float ; + lv2:minimum 0.0 ; + lv2:maximum 1.0") if(_i LESS 128 AND NOT SFIZZ_LV2_PSA) math(EXPR _digit1 "${_i}>>4") diff --git a/cmake/PuredataConfig.cmake b/cmake/PuredataConfig.cmake new file mode 100644 index 000000000..11eab2fb1 --- /dev/null +++ b/cmake/PuredataConfig.cmake @@ -0,0 +1,70 @@ +find_path(PD_INCLUDE_BASEDIR "m_pd.h" PATH_SUFFIXES "pd") +set(PD_IMP_DEF "${PROJECT_SOURCE_DIR}/plugins/puredata/external/pd/bin/pd.def") + +if(PD_INCLUDE_BASEDIR) + message(STATUS "Puredata headers: ${PUREDATA_INCLUDE_DIR}") +else() + message(STATUS "Puredata headers not found, using our own") + set(PD_INCLUDE_BASEDIR "${PROJECT_SOURCE_DIR}/plugins/puredata/external/pd/include") +endif() + +if(WIN32) + set(PUREDATA_SUFFIX ".dll") +elseif(APPLE) + set(PUREDATA_SUFFIX ".pd_darwin") +else() + set(PUREDATA_SUFFIX ".pd_linux") +endif() + +if(APPLE) + set(PDPLUGIN_INSTALL_DIR "$ENV{HOME}/Library/Pd" CACHE STRING + "Install destination for Puredata bundle [default: $ENV{HOME}/Library/Pd]") +elseif(MSVC) + set(PDPLUGIN_INSTALL_DIR "${CMAKE_INSTALL_PREFIX}/Pd/extra" CACHE STRING + "Install destination for Puredata bundle [default: ${CMAKE_INSTALL_PREFIX}/Pd/extra]") +else() + set(PDPLUGIN_INSTALL_DIR "${CMAKE_INSTALL_PREFIX}/lib/pd/extra" CACHE STRING + "Install destination for Puredata bundle [default: ${CMAKE_INSTALL_PREFIX}/lib/pd/extra]") +endif() + +if(WIN32) + add_library(pdex-implib STATIC IMPORTED) + if(MSVC) + add_custom_command( + OUTPUT "pd.lib" + COMMAND "lib" "/out:pd.lib" "/def:${PD_IMP_DEF}" "/machine:${MSVC_C_ARCHITECTURE_ID}" + DEPENDS "${PD_IMP_DEF}") + add_custom_target(pdex-implib-generated + DEPENDS "pd.lib") + set_target_properties(pdex-implib PROPERTIES + IMPORTED_LOCATION "${CMAKE_CURRENT_BINARY_DIR}/pd.lib") + else() + find_program(PD_DLLTOOL_PROGRAM "dlltool") + if(NOT PD_DLLTOOL_PROGRAM) + message(FATAL_ERROR "Cannot find dlltool") + endif() + add_custom_command( + OUTPUT "libpd.dll.a" + COMMAND "${PD_DLLTOOL_PROGRAM}" "-l" "libpd.dll.a" "-d" "${PD_IMP_DEF}" + DEPENDS "${PD_IMP_DEF}") + add_custom_target(pdex-implib-generated + DEPENDS "libpd.dll.a") + set_target_properties(pdex-implib PROPERTIES + IMPORTED_LOCATION "${CMAKE_CURRENT_BINARY_DIR}/libpd.dll.a") + endif() + add_dependencies(pdex-implib pdex-implib-generated) +endif() + +function(add_pd_external TARGET) + add_library("${TARGET}" MODULE ${ARGN}) + target_include_directories("${TARGET}" PRIVATE "${PD_INCLUDE_BASEDIR}") + set_target_properties("${TARGET}" PROPERTIES + PREFIX "" + SUFFIX "${PUREDATA_SUFFIX}") + if(APPLE) + set_property(TARGET "${TARGET}" APPEND_STRING + PROPERTY LINK_FLAGS " -Wl,-undefined,suppress,-flat_namespace") + elseif(WIN32) + target_link_libraries("${TARGET}" PRIVATE pdex-implib) + endif() +endfunction() diff --git a/cmake/SfizzConfig.cmake b/cmake/SfizzConfig.cmake index 21d715852..74c0e6a9c 100644 --- a/cmake/SfizzConfig.cmake +++ b/cmake/SfizzConfig.cmake @@ -1,5 +1,7 @@ include(CMakeDependentOption) +include(CheckCCompilerFlag) include(CheckCXXCompilerFlag) +include(CheckCXXSourceCompiles) include(GNUWarnings) set(CMAKE_CXX_STANDARD 11 CACHE STRING "C++ standard to be used") @@ -17,9 +19,21 @@ set(CMAKE_VISIBILITY_INLINES_HIDDEN ON) # Set C++ compatibility level if(CMAKE_CXX_COMPILER_ID MATCHES "MSVC" AND CMAKE_CXX_STANDARD LESS 17) set(CMAKE_CXX_STANDARD 17) -elseif((SFIZZ_LV2_UI OR SFIZZ_VST OR SFIZZ_AU OR SFIZZ_VST2) AND CMAKE_CXX_STANDARD LESS 14) - # if the UI is part of the build, make it 14 - set(CMAKE_CXX_STANDARD 14) +elseif((SFIZZ_LV2_UI OR SFIZZ_VST OR SFIZZ_AU OR SFIZZ_VST2) AND CMAKE_CXX_STANDARD LESS 17) + # if the UI is part of the build, make it 17 + set(CMAKE_CXX_STANDARD 17) +endif() + +# Set build profiling options +if(SFIZZ_PROFILE_BUILD) + check_c_compiler_flag("-ftime-trace" SFIZZ_HAVE_CFLAG_FTIME_TRACE) + check_cxx_compiler_flag("-ftime-trace" SFIZZ_HAVE_CXXFLAG_FTIME_TRACE) + if(SFIZZ_HAVE_CFLAG_FTIME_TRACE) + add_compile_options("$<$:-ftime-trace>") + endif() + if(SFIZZ_HAVE_CXXFLAG_FTIME_TRACE) + add_compile_options("$<$:-ftime-trace>") + endif() endif() # Process sources as UTF-8 @@ -32,11 +46,39 @@ if(WIN32) add_compile_definitions(_WIN32_WINNT=0x601) endif() +# Define the math constants everywhere +if(WIN32) + add_compile_definitions(_USE_MATH_DEFINES) +endif() + # Set macOS compatibility level if(APPLE) set(CMAKE_OSX_DEPLOYMENT_TARGET "10.9") endif() +# If using C++17, check if aligned-new has runtime support on the platform; +# on macOS, this depends on the deployment target. +if(CMAKE_CXX_STANDARD LESS 17) + # not necessary on older C++, it will call ordinary new and delete + set(SFIZZ_IMPLEMENT_CXX17_ALIGNED_NEW_SUPPORT FALSE) +else() + check_cxx_source_compiles(" +struct Test { alignas(1024) int z; }; +int main() { new Test; return 0; } +" SFIZZ_HAVE_CXX17_ALIGNED_NEW) + # if support is absent, sfizz will provide a substitute implementation + if(SFIZZ_HAVE_CXX17_ALIGNED_NEW) + set(SFIZZ_IMPLEMENT_CXX17_ALIGNED_NEW_SUPPORT FALSE) + else() + # on macOS, this mandatory flag tells that allocation functions are user-provided + check_cxx_compiler_flag("-faligned-allocation" SFIZZ_HAVE_CXXFLAG_FALIGNED_ALLOCATION) + if(SFIZZ_HAVE_CXXFLAG_FALIGNED_ALLOCATION) + add_compile_options("$<$:-faligned-allocation>") + endif() + set(SFIZZ_IMPLEMENT_CXX17_ALIGNED_NEW_SUPPORT TRUE) + endif() +endif() + # Do not define macros `min` and `max` if(WIN32) add_compile_definitions(NOMINMAX) @@ -87,6 +129,9 @@ if(USE_LIBCPP) add_link_options(-lc++abi) # New command on CMake master, not in 3.12 release endif() +set(SFIZZ_REPOSITORY https://github.com/sfztools/sfizz) +include(GNUInstallDirs) + # Don't show build information when building a different project function(show_build_info_if_needed) if(CMAKE_PROJECT_NAME STREQUAL "sfizz") diff --git a/cmake/SfizzDeps.cmake b/cmake/SfizzDeps.cmake index 1e4bd556e..85c9dbae4 100644 --- a/cmake/SfizzDeps.cmake +++ b/cmake/SfizzDeps.cmake @@ -1,3 +1,5 @@ +include(CheckCXXSourceCompiles) + # Find system threads find_package(Threads REQUIRED) @@ -65,9 +67,19 @@ add_library(sfizz::jsl ALIAS sfizz_jsl) target_include_directories(sfizz_jsl INTERFACE "external/jsl/include") # The cxxopts library -add_library(sfizz_cxxopts INTERFACE) +if(SFIZZ_USE_SYSTEM_CXXOPTS) + find_path(CXXOPTS_INCLUDE_DIR "cxxopts.hpp") + if(NOT CXXOPTS_INCLUDE_DIR) + message(FATAL_ERROR "Cannot find cxxopts") + endif() + add_library(sfizz_cxxopts INTERFACE) + target_include_directories(sfizz_cxxopts INTERFACE "${CXXOPTS_INCLUDE_DIR}") +else() + add_library(sfizz_cxxopts INTERFACE) + add_library(sfizz::cxxopts ALIAS sfizz_cxxopts) + target_include_directories(sfizz_cxxopts INTERFACE "external/cxxopts") +endif() add_library(sfizz::cxxopts ALIAS sfizz_cxxopts) -target_include_directories(sfizz_cxxopts INTERFACE "external/cxxopts") # The sndfile library if(SFIZZ_USE_SNDFILE OR SFIZZ_DEMOS OR SFIZZ_DEVTOOLS OR SFIZZ_BENCHMARKS) @@ -105,10 +117,25 @@ add_subdirectory("external/st_audiofile" EXCLUDE_FROM_ALL) add_library(sfizz_simde INTERFACE) add_library(sfizz::simde ALIAS sfizz_simde) if(SFIZZ_USE_SYSTEM_SIMDE) - find_package(PkgConfig REQUIRED) - pkg_check_modules(SIMDE "simde" REQUIRED) - target_include_directories(sfizz_simde INTERFACE "${SIMDE_INCLUDE_DIRS}") - if(NOT SIMDE_VERSION OR SIMDE_VERSION VERSION_LESS_EQUAL "0.7.2") + find_path(SIMDE_INCLUDE_DIR "simde/simde-features.h") + if(NOT SIMDE_INCLUDE_DIR) + message(FATAL_ERROR "Cannot find simde") + endif() + target_include_directories(sfizz_simde INTERFACE "${SIMDE_INCLUDE_DIR}") + + function(sfizz_ensure_simde_version result major minor micro) + set(CMAKE_REQUIRED_INCLUDES "${SIMDE_INCLUDE_DIR}") + check_cxx_source_compiles( + "#include +#if SIMDE_VERSION < HEDLEY_VERSION_ENCODE(${major}, ${minor}, ${micro}) +# error Version check failed +#endif +int main() { return 0; }" + "${result}") + endfunction() + + sfizz_ensure_simde_version(SFIZZ_SIMDE_AT_LEAST_0_7_3 0 7 3) + if(NOT SFIZZ_SIMDE_AT_LEAST_0_7_3) message(WARNING "The version of SIMDe on this system has known issues. \ It is recommended to either update if a newer version is available, or use the \ version bundled with this package. Refer to following issues: \ @@ -122,9 +149,18 @@ if(TARGET sfizz::openmp) endif() # The pugixml library -add_library(sfizz_pugixml STATIC "src/external/pugixml/src/pugixml.cpp") +if(SFIZZ_USE_SYSTEM_PUGIXML) + find_package(PkgConfig REQUIRED) + pkg_check_modules(PUGIXML "pugixml" REQUIRED) + add_library(sfizz_pugixml INTERFACE) + target_include_directories(sfizz_pugixml INTERFACE ${PUGIXML_INCLUDE_DIRS}) + target_link_libraries(sfizz_pugixml INTERFACE ${PUGIXML_LIBRARIES}) + link_directories(${PUGIXML_LIBRARY_DIRS}) +else() + add_library(sfizz_pugixml STATIC "src/external/pugixml/src/pugixml.cpp") + target_include_directories(sfizz_pugixml PUBLIC "src/external/pugixml/src") +endif() add_library(sfizz::pugixml ALIAS sfizz_pugixml) -target_include_directories(sfizz_pugixml PUBLIC "src/external/pugixml/src") # The spline library add_library(sfizz_spline STATIC "src/external/spline/spline/spline.cpp") @@ -148,13 +184,31 @@ add_library(sfizz::hiir_polyphase_iir2designer ALIAS sfizz_hiir_polyphase_iir2de target_link_libraries(sfizz_hiir_polyphase_iir2designer PUBLIC sfizz::hiir) # The kissfft library -add_library(sfizz_kissfft STATIC - "src/external/kiss_fft/kiss_fft.c" - "src/external/kiss_fft/tools/kiss_fftr.c") +if (SFIZZ_USE_SYSTEM_KISS_FFT) + find_path(KISSFFT_INCLUDE_DIR "kiss_fft.h" PATH_SUFFIXES "kissfft") + find_path(KISSFFTR_INCLUDE_DIR "kiss_fftr.h" PATH_SUFFIXES "kissfft") + find_library(KISSFFT_FFTR_LIBRARY "kiss_fftr_float" KISSFFTR_INCLUDE_DIR) + find_library(KISSFFT_FFT_LIBRARY "kiss_fft_float" KISSFFT_INCLUDE_DIR) + add_library(sfizz_kissfft INTERFACE) + if(NOT KISSFFT_FFT_LIBRARY) + message(FATAL_ERROR "Cannot find kiss fft") + endif() + if(NOT KISSFFT_FFTR_LIBRARY) + message(FATAL_ERROR "Cannot find kiss fftr") + endif() + target_include_directories(sfizz_kissfft INTERFACE "${KISSFFTR_INCLUDE_DIR}") + target_include_directories(sfizz_kissfft INTERFACE "${KISSFFT_INCLUDE_DIR}") + target_link_libraries(sfizz_kissfft INTERFACE "${KISSFFT_FFTR_LIBRARY}") + target_link_libraries(sfizz_kissfft INTERFACE "${KISSFFT_FFT_LIBRARY}") +else() + add_library(sfizz_kissfft STATIC + "src/external/kiss_fft/kiss_fft.c" + "src/external/kiss_fft/tools/kiss_fftr.c") + target_include_directories(sfizz_kissfft + PUBLIC "src/external/kiss_fft" + PUBLIC "src/external/kiss_fft/tools") +endif() add_library(sfizz::kissfft ALIAS sfizz_kissfft) -target_include_directories(sfizz_kissfft - PUBLIC "src/external/kiss_fft" - PUBLIC "src/external/kiss_fft/tools") # The cephes library add_library(sfizz_cephes STATIC @@ -183,9 +237,27 @@ add_library(sfizz::atomic_queue ALIAS sfizz_atomic_queue) target_include_directories(sfizz_atomic_queue INTERFACE "external/atomic_queue/include") # The ghc::filesystem library -add_library(sfizz_filesystem INTERFACE) +if(FALSE) + # header-only + add_library(sfizz_filesystem INTERFACE) + target_include_directories(sfizz_filesystem INTERFACE "external/filesystem/include") +else() + # static library + file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/fs_std_impl.cpp" "#include ") + add_library(sfizz_filesystem_impl STATIC "${CMAKE_CURRENT_BINARY_DIR}/fs_std_impl.cpp") + target_include_directories(sfizz_filesystem_impl PUBLIC "external/filesystem/include") + # Add the needed linker option for GCC 8 + if (CMAKE_CXX_COMPILER_ID MATCHES "GNU" + AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER 8.0 + AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS 9.0) + target_link_libraries(sfizz_filesystem_impl PUBLIC stdc++fs) + endif() + # + add_library(sfizz_filesystem INTERFACE) + target_compile_definitions(sfizz_filesystem INTERFACE "GHC_FILESYSTEM_FWD") + target_link_libraries(sfizz_filesystem INTERFACE sfizz_filesystem_impl) +endif() add_library(sfizz::filesystem ALIAS sfizz_filesystem) -target_include_directories(sfizz_filesystem INTERFACE "external/filesystem/include") # The atomic library add_library(sfizz_atomic INTERFACE) diff --git a/common.mk b/common.mk index 722287259..24a261a89 100644 --- a/common.mk +++ b/common.mk @@ -107,6 +107,8 @@ SFIZZ_SOURCES = \ src/sfizz/PowerFollower.cpp \ src/sfizz/Region.cpp \ src/sfizz/RegionSet.cpp \ + src/sfizz/RegionStateful.cpp \ + src/sfizz/Resources.cpp \ src/sfizz/RTSemaphore.cpp \ src/sfizz/ScopedFTZ.cpp \ src/sfizz/sfizz.cpp \ diff --git a/demos/DemoParser.cpp b/demos/DemoParser.cpp index 424dcceea..6875af7c1 100644 --- a/demos/DemoParser.cpp +++ b/demos/DemoParser.cpp @@ -1,5 +1,6 @@ #include "ui_DemoParser.h" #include "parser/Parser.h" +#include "parser/ParserListener.h" #include #include #include diff --git a/demos/PlotCurve.cpp b/demos/PlotCurve.cpp index 954a79468..f2e53d4be 100644 --- a/demos/PlotCurve.cpp +++ b/demos/PlotCurve.cpp @@ -17,6 +17,7 @@ #include "sfizz/Curve.h" #include "sfizz/parser/Parser.h" +#include "sfizz/parser/ParserListener.h" #include "absl/strings/numbers.h" #include "absl/types/span.h" #include diff --git a/devtools/Preprocessor.cpp b/devtools/Preprocessor.cpp index 43ca80471..a053d4341 100644 --- a/devtools/Preprocessor.cpp +++ b/devtools/Preprocessor.cpp @@ -13,6 +13,7 @@ */ #include "parser/Parser.h" +#include "parser/ParserListener.h" #include #include #include diff --git a/external/filesystem b/external/filesystem index 2a8b380f8..7bc5c1730 160000 --- a/external/filesystem +++ b/external/filesystem @@ -1 +1 @@ -Subproject commit 2a8b380f8d4e77b389c42a194ab9c70d8e3a0f1e +Subproject commit 7bc5c17305fe70518ca72162e244af1d12455a91 diff --git a/external/jsl/include/jsl/bits/memory/aligned_unique_ptr.tcc b/external/jsl/include/jsl/bits/memory/aligned_unique_ptr.tcc new file mode 100644 index 000000000..168c934ab --- /dev/null +++ b/external/jsl/include/jsl/bits/memory/aligned_unique_ptr.tcc @@ -0,0 +1,39 @@ +// -*- C++ -*- +// SPDX-License-Identifier: BSL-1.0 +// +// Copyright Jean Pierre Cimalando 2018-2020. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// +#pragma once +#include "../../memory" + +namespace jsl { + +template +void aligned_ptr_delete::operator()(T *p) const noexcept +{ + aligned_allocator allocator; + allocator.destroy(p); + allocator.deallocate(p, 0); +} + +template +aligned_unique_ptr::single_element, Al> +make_aligned(Args &&... args) +{ + struct allocation_delete { + void operator()(T *p) const noexcept + { + aligned_allocator allocator; + allocator.destroy(p); + } + }; + aligned_allocator allocator; + std::unique_ptr allocation(allocator.allocate(1, nullptr)); + allocator.construct(allocation.get(), std::forward(args)...); + return aligned_unique_ptr(allocation.release()); +} + +} // namespace jsl diff --git a/external/jsl/include/jsl/memory b/external/jsl/include/jsl/memory new file mode 100644 index 000000000..a5ff5ec50 --- /dev/null +++ b/external/jsl/include/jsl/memory @@ -0,0 +1,42 @@ +// -*- C++ -*- +// SPDX-License-Identifier: BSL-1.0 +// +// Copyright Jean Pierre Cimalando 2018-2020. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// +#pragma once +#include "./allocator" +#include + +namespace jsl { + +template +struct aligned_ptr_delete { + void operator()(T *p) const noexcept; +}; + +template +using aligned_unique_ptr = std::unique_ptr>; + +//------------------------------------------------------------------------------ + +template struct make_aligned_traits; +template struct make_aligned_traits { struct invalid {}; }; +template struct make_aligned_traits { struct invalid {}; }; +template struct make_aligned_traits { using single_element = T; }; + +//------------------------------------------------------------------------------ + +template +aligned_unique_ptr::single_element, Al> +make_aligned(Args &&... args); + +template +typename make_aligned_traits::invalid +make_aligned(Args &&... args) = delete; + +} // namespace jsl + +#include "bits/memory/aligned_unique_ptr.tcc" diff --git a/external/simde b/external/simde index 5c2f423b4..98075d059 160000 --- a/external/simde +++ b/external/simde @@ -1 +1 @@ -Subproject commit 5c2f423b41c06228e4be0cce0010a252297da4e7 +Subproject commit 98075d0593f539762125dbb215d95e782a6ae344 diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index e9c211046..d3f1070e4 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -1,4 +1,5 @@ add_library(plugins-common STATIC EXCLUDE_FROM_ALL + "common/plugin/RMSFollower.h" "common/plugin/MessageUtils.h" "common/plugin/MessageUtils.cpp" "common/plugin/InstrumentDescription.h" @@ -28,7 +29,7 @@ endif() target_include_directories(plugins-common PUBLIC "common") target_link_libraries(plugins-common PUBLIC sfizz::spin_mutex - PUBLIC sfizz::filesystem absl::strings + PUBLIC sfizz::simde sfizz::filesystem absl::strings PRIVATE sfizz::pugixml PRIVATE sfizz::internal sfizz::sfizz) add_library(sfizz::plugins-common ALIAS plugins-common) @@ -40,10 +41,6 @@ elseif(APPLE) target_link_libraries(plugins-common PRIVATE "${APPLE_FOUNDATION_LIBRARY}") else() - find_package(PkgConfig REQUIRED) - pkg_check_modules(GLIB REQUIRED glib-2.0) - target_include_directories(plugins-common PRIVATE ${GLIB_INCLUDE_DIRS}) - target_link_libraries(plugins-common PRIVATE ${GLIB_LIBRARIES}) endif() if((SFIZZ_LV2 AND SFIZZ_LV2_UI) OR SFIZZ_VST OR SFIZZ_AU OR SFIZZ_VST2) @@ -54,6 +51,10 @@ if(SFIZZ_LV2) add_subdirectory(lv2) endif() -if(SFIZZ_VST OR SFIZZ_AU OR SFIZZ_VST2) +if((SFIZZ_VST OR SFIZZ_AU OR SFIZZ_VST2) AND NOT (SFIZZ_SYSTEM_PROCESSOR MATCHES "^(powerpc|ppc).*")) add_subdirectory(vst) endif() + +if(SFIZZ_PUREDATA) + add_subdirectory(puredata) +endif() diff --git a/plugins/common/plugin/InstrumentDescription.cpp b/plugins/common/plugin/InstrumentDescription.cpp index 7378fd03c..071655b10 100644 --- a/plugins/common/plugin/InstrumentDescription.cpp +++ b/plugins/common/plugin/InstrumentDescription.cpp @@ -103,6 +103,7 @@ std::string getDescriptionBlob(sfizz_synth_t* handle) synth.sendMessage(*client, 0, "/key/slots", "", nullptr); synth.sendMessage(*client, 0, "/sw/last/slots", "", nullptr); synth.sendMessage(*client, 0, "/cc/slots", "", nullptr); + synth.sendMessage(*client, 0, "/sustain_or_sostenuto/slots", "", nullptr); blob.shrink_to_fit(); return blob; @@ -152,6 +153,8 @@ InstrumentDescription parseDescriptionBlob(absl::string_view blob) copyArgToBitSpan(args[0], desc.keyswitchUsed.span()); else if (Messages::matchOSC("/cc/slots", path, indices) && !strcmp(sig, "b")) copyArgToBitSpan(args[0], desc.ccUsed.span()); + else if (Messages::matchOSC("/sustain_or_sostenuto/slots", path, indices) && !strcmp(sig, "b")) + copyArgToBitSpan(args[0], desc.sustainOrSostenuto.span()); else if (Messages::matchOSC("/key&/label", path, indices) && !strcmp(sig, "s")) desc.keyLabel[indices[0]] = args[0].s; else if (Messages::matchOSC("/sw/last/&/label", path, indices) && !strcmp(sig, "s")) diff --git a/plugins/common/plugin/InstrumentDescription.h b/plugins/common/plugin/InstrumentDescription.h index b943dcba7..094d06b3b 100644 --- a/plugins/common/plugin/InstrumentDescription.h +++ b/plugins/common/plugin/InstrumentDescription.h @@ -26,6 +26,7 @@ struct InstrumentDescription { std::string image; BitArray<128> keyUsed {}; BitArray<128> keyswitchUsed {}; + BitArray<128> sustainOrSostenuto {}; BitArray ccUsed {}; std::array keyLabel {}; std::array keyswitchLabel {}; diff --git a/plugins/common/plugin/NativeHelpers.cpp b/plugins/common/plugin/NativeHelpers.cpp index 3ada4d939..8f7f0aee9 100644 --- a/plugins/common/plugin/NativeHelpers.cpp +++ b/plugins/common/plugin/NativeHelpers.cpp @@ -5,8 +5,12 @@ // If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz #include "NativeHelpers.h" +#include +#include +#include #include #include +#include #if defined(_WIN32) #include @@ -22,24 +26,119 @@ const fs::path& getUserDocumentsDirectory() }(); return directory; } + +wchar_t *stringToWideChar(const char *str, int strCch) +{ + unsigned strSize = MultiByteToWideChar(CP_UTF8, 0, str, strCch, nullptr, 0); + if (strSize == 0) + return {}; + std::unique_ptr strW(new wchar_t[strSize]); + if (MultiByteToWideChar(CP_UTF8, 0, str, strCch, strW.get(), strSize) == 0) + return {}; + return strW.release(); +} + +char* stringToUTF8(const wchar_t *strW, int strWCch) +{ + unsigned strSize = WideCharToMultiByte(CP_UTF8, 0, strW, strWCch, nullptr, 0, nullptr, nullptr); + if (strSize == 0) + return {}; + std::unique_ptr str(new char[strSize]); + if (WideCharToMultiByte(CP_UTF8, 0, strW, strWCch, str.get(), strSize, nullptr, nullptr) == 0) + return {}; + return str.release(); +} #elif defined(__APPLE__) // implemented in NativeHelpers.mm #else -#include - const fs::path& getUserDocumentsDirectory() { static const fs::path directory = []() -> fs::path { - const gchar* path = g_get_user_special_dir(G_USER_DIRECTORY_DOCUMENTS); - if (path) - return fs::path(path); - else { - const char* home = getenv("HOME"); - if (home && home[0] == '/') - return fs::path(home) / "Documents"; - throw std::runtime_error("Cannot get the document directory."); + for (const XdgUserDirsEntry& ent : + parseXdgUserDirs(getXdgConfigHome() / "user-dirs.dirs")) { + if (ent.name == "XDG_DOCUMENTS_DIR") + return ent.value; } + return getUserHomeDirectory() / "Documents"; + }(); + return directory; +} + +const fs::path& getUserHomeDirectory() +{ + static const fs::path directory = []() -> fs::path { + const char* home = getenv("HOME"); + if (home && home[0] == '/') + return fs::u8path(home); + else + throw std::runtime_error("Cannot get the home directory."); + }(); + return directory; +} + +const fs::path& getXdgConfigHome() +{ + static const fs::path directory = []() -> fs::path { + const char* config = getenv("XDG_CONFIG_HOME"); + if (config && config[0] == '/') + return fs::u8path(config); + else + return getUserHomeDirectory() / ".config"; + }(); return directory; } + +std::vector parseXdgUserDirs(const fs::path& userDirsPath) +{ + // from user-dirs.dirs(5) + // This file contains lines of the form `XDG_NAME_DIR=VALUE` + // VALUE must be of the form "$HOME/Path" or "/Path". + // Lines beginning with a # character are ignored. + + std::vector ents; + const fs::path& home = getUserHomeDirectory(); + + fs::ifstream in(userDirsPath); + std::string lineBuf; + + lineBuf.reserve(256); + while (std::getline(in, lineBuf)) { + absl::string_view line(lineBuf); + + line = absl::StripLeadingAsciiWhitespace(line); + if (line.empty() || line.front() == '#') + continue; + + size_t pos = line.find('='); + if (pos == line.npos) + continue; + + XdgUserDirsEntry ent; + ent.name = std::string(line.substr(0, pos)); + + absl::string_view rawValue = line.substr(pos + 1); + + rawValue = absl::StripTrailingAsciiWhitespace(rawValue); + + if (rawValue.size() < 2 || rawValue.front() != '"' || rawValue.back() != '"') + continue; + + rawValue.remove_prefix(1); + rawValue.remove_suffix(1); + + if (!rawValue.empty() && rawValue.front() == '/') + ent.value = fs::u8path(rawValue.begin(), rawValue.end()); + else if (absl::StartsWith(rawValue, "$HOME")) { + absl::string_view part = rawValue.substr(5); + ent.value = home / fs::u8path(part.begin(), part.end()).relative_path(); + } + else + continue; + + ents.push_back(std::move(ent)); + } + + return ents; +} #endif diff --git a/plugins/common/plugin/NativeHelpers.h b/plugins/common/plugin/NativeHelpers.h index 766d01d74..0b534a197 100644 --- a/plugins/common/plugin/NativeHelpers.h +++ b/plugins/common/plugin/NativeHelpers.h @@ -6,5 +6,21 @@ #pragma once #include +#include +#if defined(_WIN32) +#include +#endif const fs::path& getUserDocumentsDirectory(); + +#if !defined(_WIN32) && !defined(__APPLE__) +const fs::path& getUserHomeDirectory(); +const fs::path& getXdgConfigHome(); +struct XdgUserDirsEntry { std::string name; fs::path value; }; +std::vector parseXdgUserDirs(const fs::path& userDirsPath); +#endif + +#if defined(_WIN32) +wchar_t *stringToWideChar(const char *str, int strCch = -1); +char* stringToUTF8(const wchar_t *strW, int strWCch = -1); +#endif diff --git a/plugins/common/plugin/RMSFollower.h b/plugins/common/plugin/RMSFollower.h new file mode 100644 index 000000000..43cc7ac27 --- /dev/null +++ b/plugins/common/plugin/RMSFollower.h @@ -0,0 +1,73 @@ +// 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 + +#pragma once +#include +#include +#include + +class RMSFollower { +public: + RMSFollower() + { + updatePole(); + } + + void clear() + { + mem_ = simde_mm_setzero_ps(); + } + + void init(float sampleRate) + { + sampleRate_ = sampleRate; + updatePole(); + clear(); + } + + void setT60(float t60) + { + t60_ = t60; + updatePole(); + } + + void process(const float* leftBlock, const float* rightBlock, size_t numFrames) + { + simde__m128 mem = mem_; + const simde__m128 pole = simde_mm_load1_ps(&pole_); + for (size_t i = 0; i < numFrames; ++i) { + float left = leftBlock[i]; + float right = rightBlock[i]; + simde__m128 input = simde_mm_setr_ps(left, right, 0.0f, 0.0f); + input = simde_mm_mul_ps(input, input); + simde__m128 output = simde_mm_add_ps(input, simde_mm_mul_ps(pole, simde_mm_sub_ps(mem, input))); + mem = output; + } + mem_ = mem; + } + + simde__m128 getMS() const + { + return mem_; + } + + simde__m128 getRMS() const + { + return simde_mm_sqrt_ps(mem_); + } + +private: + void updatePole() + { + pole_ = std::exp(float(-2.0 * M_PI) / (t60_ * sampleRate_)); + } + +private: + simde__m128 mem_ {}; + float pole_ {}; + float t60_ = 300e-3; + float sampleRate_ = 44100; +}; diff --git a/plugins/common/plugin/SfizzSettings.cpp b/plugins/common/plugin/SfizzSettings.cpp index 10e987ab5..091ad6f75 100644 --- a/plugins/common/plugin/SfizzSettings.cpp +++ b/plugins/common/plugin/SfizzSettings.cpp @@ -5,6 +5,7 @@ // If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz #include "SfizzSettings.h" +#include "NativeHelpers.h" #include #include @@ -36,28 +37,6 @@ static HKEY openRegistryKey() return key; } -static WCHAR* stringToWideChar(const char *str, int strCch = -1) -{ - unsigned strSize = MultiByteToWideChar(CP_UTF8, 0, str, strCch, nullptr, 0); - if (strSize == 0) - return {}; - std::unique_ptr strW(new WCHAR[strSize]); - if (MultiByteToWideChar(CP_UTF8, 0, str, strCch, strW.get(), strSize) == 0) - return {}; - return strW.release(); -} - -static char* stringToUTF8(const wchar_t *strW, int strWCch = -1) -{ - unsigned strSize = WideCharToMultiByte(CP_UTF8, 0, strW, strWCch, nullptr, 0, nullptr, nullptr); - if (strSize == 0) - return {}; - std::unique_ptr str(new char[strSize]); - if (WideCharToMultiByte(CP_UTF8, 0, strW, strWCch, str.get(), strSize, nullptr, nullptr) == 0) - return {}; - return str.release(); -} - absl::optional SfizzSettings::load(const char* name) { std::unique_ptr nameW { stringToWideChar(name) }; @@ -112,16 +91,7 @@ bool SfizzSettings::store(const char* name, absl::string_view value) static const fs::path getSettingsPath() { - fs::path dirPath; - const char* env; - if ((env = getenv("XDG_CONFIG_HOME")) && env[0] == '/') - dirPath = fs::path(env); - else if ((env = getenv("HOME")) && env[0] == '/') - dirPath = fs::path(env) / ".config"; - else - return {}; - dirPath /= "SFZTools"; - dirPath /= "sfizz"; + const fs::path dirPath = getXdgConfigHome() / "SFZTools" / "sfizz"; std::error_code ec; fs::create_directories(dirPath, ec); if (ec) diff --git a/plugins/editor/CMakeLists.txt b/plugins/editor/CMakeLists.txt index 0b6ee3bd7..4eda6a9be 100644 --- a/plugins/editor/CMakeLists.txt +++ b/plugins/editor/CMakeLists.txt @@ -21,6 +21,7 @@ set(EDITOR_RESOURCES Fonts/sfizz-misc-icons.ttf Fonts/Roboto-Regular.ttf Themes/Default/theme.xml + Themes/Dark/theme.xml PARENT_SCOPE) function(copy_editor_resources TARGET SOURCE_DIR DESTINATION_DIR) @@ -73,9 +74,7 @@ add_library(sfizz_editor STATIC EXCLUDE_FROM_ALL src/editor/layout/about.hpp src/editor/utility/vstgui_after.h src/editor/utility/vstgui_before.h - src/editor/GitBuildId.h - ${UI_FILES} - "${CMAKE_CURRENT_BINARY_DIR}/git-build-id/GitBuildId.c") + ${UI_FILES}) add_library(sfizz::editor ALIAS sfizz_editor) target_include_directories(sfizz_editor PUBLIC "src") target_link_libraries(sfizz_editor PUBLIC sfizz::messaging sfizz::plugins-common) @@ -137,11 +136,5 @@ if(NOT CMAKE_CROSSCOMPILING) endforeach() endif() -# git build identifier -add_custom_target(sfizz_editor_git_build_id - COMMAND "${CMAKE_COMMAND}" - "-DSOURCE_DIR=${PROJECT_SOURCE_DIR}" - "-DOUTPUT_FILE=${CMAKE_CURRENT_BINARY_DIR}/git-build-id/GitBuildId.c" - "-P" "${CMAKE_CURRENT_SOURCE_DIR}/cmake/GitBuildID.cmake" - BYPRODUCTS "git-build-id/GitBuildId.c") -add_dependencies(sfizz_editor sfizz_editor_git_build_id) +# Git build identifier +target_link_libraries(sfizz_editor PRIVATE sfizz-git-build-id) diff --git a/plugins/editor/cmake/Vstgui.cmake b/plugins/editor/cmake/Vstgui.cmake index 0fb111ddb..8f7ebe252 100644 --- a/plugins/editor/cmake/Vstgui.cmake +++ b/plugins/editor/cmake/Vstgui.cmake @@ -39,6 +39,7 @@ add_library(sfizz_vstgui STATIC EXCLUDE_FROM_ALL "${VSTGUI_BASEDIR}/vstgui/lib/cfileselector.cpp" "${VSTGUI_BASEDIR}/vstgui/lib/cfont.cpp" "${VSTGUI_BASEDIR}/vstgui/lib/cframe.cpp" + "${VSTGUI_BASEDIR}/vstgui/lib/cgradient.cpp" "${VSTGUI_BASEDIR}/vstgui/lib/cgradientview.cpp" "${VSTGUI_BASEDIR}/vstgui/lib/cgraphicspath.cpp" "${VSTGUI_BASEDIR}/vstgui/lib/clayeredviewcontainer.cpp" @@ -57,6 +58,7 @@ add_library(sfizz_vstgui STATIC EXCLUDE_FROM_ALL "${VSTGUI_BASEDIR}/vstgui/lib/cview.cpp" "${VSTGUI_BASEDIR}/vstgui/lib/cviewcontainer.cpp" "${VSTGUI_BASEDIR}/vstgui/lib/cvstguitimer.cpp" + "${VSTGUI_BASEDIR}/vstgui/lib/events.cpp" "${VSTGUI_BASEDIR}/vstgui/lib/genericstringlistdatabrowsersource.cpp" "${VSTGUI_BASEDIR}/vstgui/lib/pixelbuffer.cpp" "${VSTGUI_BASEDIR}/vstgui/lib/vstguidebug.cpp" @@ -69,6 +71,7 @@ if(WIN32) "${VSTGUI_BASEDIR}/vstgui/lib/platform/win32/direct2d/d2dbitmap.cpp" "${VSTGUI_BASEDIR}/vstgui/lib/platform/win32/direct2d/d2ddrawcontext.cpp" "${VSTGUI_BASEDIR}/vstgui/lib/platform/win32/direct2d/d2dfont.cpp" + "${VSTGUI_BASEDIR}/vstgui/lib/platform/win32/direct2d/d2dgradient.cpp" "${VSTGUI_BASEDIR}/vstgui/lib/platform/win32/direct2d/d2dgraphicspath.cpp" "${VSTGUI_BASEDIR}/vstgui/lib/platform/win32/win32datapackage.cpp" "${VSTGUI_BASEDIR}/vstgui/lib/platform/win32/win32dragging.cpp" diff --git a/plugins/editor/external/vstgui4 b/plugins/editor/external/vstgui4 index 9a116d181..0db8738ae 160000 --- a/plugins/editor/external/vstgui4 +++ b/plugins/editor/external/vstgui4 @@ -1 +1 @@ -Subproject commit 9a116d1812aa274ccf61d68f4c2bae612b3871ad +Subproject commit 0db8738ae73ff76842eb0bfe90cdf2039690a5b5 diff --git a/plugins/editor/layout/main.fl b/plugins/editor/layout/main.fl index c5ac97085..e862d4322 100644 --- a/plugins/editor/layout/main.fl +++ b/plugins/editor/layout/main.fl @@ -1,13 +1,13 @@ # data file for the Fltk User Interface Designer (fluid) -version 1.0305 +version 1.0306 header_name {.h} code_name {.cxx} widget_class mainView {open - xywh {439 94 800 475} type Double + xywh {108 91 800 475} type Double class LogicalGroup visible } { Fl_Box imageContainer_ { - image {../resources/background.png} xywh {190 110 600 280} + image {../resources/background.png} xywh {5 110 790 285} class Background } Fl_Group {} { @@ -15,53 +15,58 @@ widget_class mainView {open xywh {0 0 800 110} class LogicalGroup } { - Fl_Group {} {open + Fl_Group {} { xywh {5 4 175 101} box ROUNDED_BOX align 0 class RoundedGroup } { Fl_Box {} { - comment {tag=kTagAbout} selected + comment {tag=kTagAbout} image {../resources/logo_text_shaded.png} xywh {32 9 120 60} class AboutButton } - Fl_Button {panelButtons_[kPanelGeneral]} { - comment {tag=kTagFirstChangePanel+kPanelGeneral} - xywh {36 73 32 32} labelsize 30 - class HomeButton + Fl_Button {panelButtons_[kPanelInfo]} { + comment {tag=kTagFirstChangePanel+kPanelInfo} + xywh {56 73 32 32} labelsize 30 + class InfoButton } Fl_Button {panelButtons_[kPanelControls]} { comment {tag=kTagFirstChangePanel+kPanelControls} - xywh {76 73 32 32} labelsize 30 + xywh {97 73 32 32} labelsize 30 class CCButton } Fl_Button {panelButtons_[kPanelSettings]} { comment {tag=kTagFirstChangePanel+kPanelSettings} - xywh {116 73 32 32} labelsize 30 + xywh {137 73 32 32} labelsize 30 class SettingsButton } + Fl_Button {panelButtons_[kPanelGeneral]} { + comment {tag=kTagFirstChangePanel+kPanelGeneral} + xywh {16 73 32 32} labelsize 30 + class HomeButton + } } - Fl_Group {} {open - xywh {185 5 380 100} box ROUNDED_BOX + Fl_Group {} { + xywh {185 5 418 100} box ROUNDED_BOX class RoundedGroup } { Fl_Box {} { label {Separator 1} - xywh {195 41 360 5} box BORDER_BOX labeltype NO_LABEL + xywh {195 41 395 5} box BORDER_BOX labeltype NO_LABEL class HLine } Fl_Box {} { label {Separator 2} - xywh {195 73 360 5} box BORDER_BOX labeltype NO_LABEL + xywh {195 73 395 5} box BORDER_BOX labeltype NO_LABEL class HLine } Fl_Box sfzFileLabel_ { label {DefaultInstrument.sfz} comment {tag=kTagLoadSfzFile} - xywh {195 13 250 30} labelsize 20 align 20 + xywh {195 13 320 30} labelsize 20 align 20 class ClickableLabel } Fl_Box keyswitchLabel_ { - xywh {265 45 290 30} labelsize 20 align 20 + xywh {265 45 325 35} labelsize 20 align 20 class Label } Fl_Box keyswitchBadge_ { @@ -80,17 +85,17 @@ widget_class mainView {open } Fl_Button {} { comment {tag=kTagPreviousSfzFile} - xywh {480 18 25 25} labelsize 24 + xywh {515 18 25 25} labelsize 24 class PreviousFileButton } Fl_Button {} { comment {tag=kTagNextSfzFile} - xywh {505 18 25 25} labelsize 24 + xywh {540 18 25 25} labelsize 24 class NextFileButton } Fl_Button fileOperationsMenu_ { comment {tag=kTagFileOperations} - xywh {530 18 25 25} labelsize 24 + xywh {565 18 25 25} labelsize 24 class ChevronDropDown } Fl_Box infoVoicesLabel_ { @@ -99,30 +104,30 @@ widget_class mainView {open } Fl_Box {} { label {Max:} - xywh {315 78 40 25} labelsize 12 align 24 + xywh {332 78 40 25} labelsize 12 align 24 class Label } Fl_Box numVoicesLabel_ { - xywh {360 78 35 25} labelsize 12 align 16 + xywh {378 78 35 25} labelsize 12 align 16 class Label } Fl_Box {} { label {Memory:} - xywh {425 78 60 25} labelsize 12 align 24 + xywh {459 78 60 25} labelsize 12 align 24 class Label } Fl_Box memoryLabel_ { - xywh {490 78 60 25} labelsize 12 align 16 + xywh {525 78 60 25} labelsize 12 align 16 class Label } Fl_Button numVoicesSlider_ { comment {tag=kTagSetNumVoices} - xywh {395 82 20 20} labelsize 16 + xywh {420 82 20 20} labelsize 16 class ChevronValueDropDown } } - Fl_Group {} {open - xywh {570 5 225 100} box ROUNDED_BOX + Fl_Group {} { + xywh {608 5 187 100} box ROUNDED_BOX class RoundedGroup } { Fl_Dial {} { @@ -134,30 +139,34 @@ widget_class mainView {open xywh {610 70 60 5} labelsize 12 hide class ValueLabel } - Fl_Box {} { - xywh {745 20 35 55} box BORDER_BOX - class VMeter - } Fl_Box volumeCCKnob_ { label Volume comment {tag=kTagSetCCVolume} - xywh {580 10 70 90} box BORDER_BOX labelsize 12 align 17 + xywh {614 10 70 90} box BORDER_BOX labelsize 12 align 17 class KnobCCBox } Fl_Box panCCKnob_ { label Pan comment {tag=kTagSetCCPan} - xywh {655 10 70 90} box BORDER_BOX labelsize 12 align 17 + xywh {691 10 70 90} box BORDER_BOX labelsize 12 align 17 class KnobCCBox } + Fl_Box leftMeter_ { + xywh {767 10 9 90} box BORDER_BOX + class VMeter + } + Fl_Box rightMeter_ { + xywh {779 10 9 90} box BORDER_BOX + class VMeter + } } } - Fl_Group {subPanels_[kPanelGeneral]} { - xywh {5 110 791 285} hide + Fl_Group {subPanels_[kPanelInfo]} {open + xywh {5 110 790 285} hide class LogicalGroup } { Fl_Group {} {open - xywh {5 110 175 280} box ROUNDED_BOX + xywh {5 110 790 285} box ROUNDED_BOX class RoundedGroup } { Fl_Box {} { @@ -226,8 +235,8 @@ widget_class mainView {open } {} } } - Fl_Group {subPanels_[kPanelSettings]} {open - xywh {5 109 790 316} + Fl_Group {subPanels_[kPanelSettings]} { + xywh {5 110 790 285} class LogicalGroup } { Fl_Group {} { @@ -357,8 +366,8 @@ widget_class mainView {open } } Fl_Group {} { - label Theme open - xywh {40 270 105 100} box ROUNDED_BOX labelsize 12 align 17 hide + label Appearance open + xywh {40 270 105 100} box ROUNDED_BOX labelsize 12 align 17 class TitleGroup } { Fl_Spinner themeMenu_ { @@ -366,10 +375,19 @@ widget_class mainView {open xywh {60 330 65 25} labelsize 12 textsize 12 class OptionMenu } + Fl_Box {} { + label Theme + xywh {50 295 80 25} labelsize 12 + class ValueLabel + } } } Fl_Box piano_ { xywh {5 400 790 70} labelsize 12 class Piano } + Fl_Group {subPanels_[kPanelGeneral]} {open selected + xywh {5 110 790 285} hide + class LogicalGroup + } {} } diff --git a/plugins/editor/resources/Themes/Dark/theme.xml b/plugins/editor/resources/Themes/Dark/theme.xml new file mode 100644 index 000000000..b5e9152f6 --- /dev/null +++ b/plugins/editor/resources/Themes/Dark/theme.xml @@ -0,0 +1,40 @@ + + + #121212 + + #1d1d1d + #e3e3e3 + #a2a2a2 + #e9e9e9 + #e3e3e3 + #2d2d2d + #e3e3e3 + #121212 + #e3e3e3 + #ff6000 + #006b0b + #6f6f6f + #ffffff + #ffffff + #ffffff + #006b0b + + + #2d2d2d + #9e9e9e + #a2a2a2 + #e9e9e9 + #9e9e9e + #2d2d2d + #9e9e9e + #121212 + #9e9e9e + #ff6000 + #006b0b + #6f6f6f + #ffffff + #ffffff + #ffffff + #006b0b + + diff --git a/plugins/editor/src/editor/EditIds.h b/plugins/editor/src/editor/EditIds.h index 3f55d31b3..c0070ed6c 100644 --- a/plugins/editor/src/editor/EditIds.h +++ b/plugins/editor/src/editor/EditIds.h @@ -38,6 +38,9 @@ enum class EditId : int { CC_RANGE(ControllerDefault), CC_RANGE(ControllerLabel), // + LeftLevel, + RightLevel, + // UINumCurves, UINumMasters, UINumGroups, diff --git a/plugins/editor/src/editor/Editor.cpp b/plugins/editor/src/editor/Editor.cpp index 1e5850aaf..bc7b4d71b 100644 --- a/plugins/editor/src/editor/Editor.cpp +++ b/plugins/editor/src/editor/Editor.cpp @@ -66,6 +66,7 @@ struct Editor::Impl : EditorController::Receiver, enum { kPanelGeneral, + kPanelInfo, kPanelControls, kPanelSettings, kNumPanels, @@ -153,16 +154,19 @@ struct Editor::Impl : EditorController::Receiver, SKnobCCBox* volumeCCKnob_ = nullptr; SKnobCCBox* panCCKnob_ = nullptr; + SLevelMeter* leftMeter_ = nullptr; + SLevelMeter* rightMeter_ = nullptr; + SAboutDialog* aboutDialog_ = nullptr; SharedPointer backgroundBitmap_; SharedPointer defaultBackgroundBitmap_; - CControl* getSecondaryCCControl(unsigned cc) + SKnobCCBox* getSecondaryCCKnob(unsigned cc) { switch (cc) { - case 7: return volumeCCKnob_ ? volumeCCKnob_->getControl() : nullptr; - case 10: return panCCKnob_ ? panCCKnob_->getControl() : nullptr; + case 7: return volumeCCKnob_ ? volumeCCKnob_ : nullptr; + case 10: return panCCKnob_ ? panCCKnob_ : nullptr; default: return nullptr; } } @@ -504,6 +508,20 @@ void Editor::Impl::uiReceiveValue(EditId id, const EditValue& v) updateBackgroundImage(value.c_str()); } break; + case EditId::LeftLevel: + { + const float value = v.to_float(); + if (SLevelMeter* meter = leftMeter_) + meter->setValue(value); + } + break; + case EditId::RightLevel: + { + const float value = v.to_float(); + if (SLevelMeter* meter = rightMeter_) + meter->setValue(value); + } + break; default: if (editIdIsKey(id)) { const int key = keyForEditId(id); @@ -703,11 +721,15 @@ void Editor::Impl::createFrameContents() lbl->setFont(font); return lbl; }; - auto createVMeter = [](const CRect& bounds, int, const char*, CHoriTxtAlign, int) { - // TODO the volume meter... - CViewContainer* container = new CViewContainer(bounds); - container->setBackgroundColor(CColor(0x00, 0x00, 0x00, 0x00)); - return container; + auto createVMeter = [this, &palette](const CRect& bounds, int, const char*, CHoriTxtAlign, int) { + SLevelMeter* meter = new SLevelMeter(bounds); + meter->setFrameColor(CColor(0x00, 0x00, 0x00, 0x00)); + meter->setNormalFillColor(CColor(0x00, 0xaa, 0x11)); + meter->setDangerFillColor(CColor(0xaa, 0x00, 0x00)); + OnThemeChanged.push_back([meter, palette]() { + meter->setBackColor(palette->knobInactiveTrack); + }); + return meter; }; #if 0 auto createButton = [this](const CRect& bounds, int tag, const char* label, CHoriTxtAlign align, int fontsize) { @@ -796,6 +818,9 @@ void Editor::Impl::createFrameContents() auto createHomeButton = [&createGlyphButton](const CRect& bounds, int tag, const char*, CHoriTxtAlign, int fontsize) { return createGlyphButton(u8"\ue1d6", bounds, tag, fontsize); }; + auto createInfoButton = [&createGlyphButton](const CRect& bounds, int tag, const char*, CHoriTxtAlign, int fontsize) { + return createGlyphButton(u8"\ue1e7", bounds, tag, fontsize); + }; auto createCCButton = [&createGlyphButton](const CRect& bounds, int tag, const char*, CHoriTxtAlign, int fontsize) { // return createGlyphButton(u8"\ue240", bounds, tag, fontsize); return createGlyphButton(u8"\ue253", bounds, tag, fontsize); @@ -881,6 +906,10 @@ void Editor::Impl::createFrameContents() box->setCCLabelFont(font); OnThemeChanged.push_back([box, palette]() { box->setNameLabelFontColor(palette->knobText); + box->setValueEditFontColor(palette->knobText); + auto shadingColor = palette->knobText; + shadingColor.alpha = 70; + box->setShadingRectangleColor(shadingColor); box->setCCLabelFontColor(palette->knobLabelText); box->setCCLabelBackColor(palette->knobLabelBackground); box->setKnobFontColor(palette->knobText); @@ -888,10 +917,6 @@ void Editor::Impl::createFrameContents() box->setKnobActiveTrackColor(palette->knobActiveTrack); box->setKnobInactiveTrackColor(palette->knobInactiveTrack); }); - box->setValueToStringFunction([](float value, std::string& text) -> bool { - text = std::to_string(std::lround(value * 127)); - return true; - }); return box; }; auto createBackground = [&background](const CRect& bounds, int, const char*, CHoriTxtAlign, int) { @@ -907,6 +932,10 @@ void Editor::Impl::createFrameContents() panel->setCCLabelFont(font); OnThemeChanged.push_back([panel, palette]() { panel->setNameLabelFontColor(palette->knobText); + panel->setValueEditFontColor(palette->knobText); + auto shadingColor = palette->knobText; + shadingColor.alpha = 70; + panel->setShadingRectangleColor(shadingColor); panel->setCCLabelFontColor(palette->knobLabelText); panel->setCCLabelBackColor(palette->knobLabelBackground); panel->setKnobFontColor(palette->knobText); @@ -1640,7 +1669,7 @@ void Editor::Impl::updateCCValue(unsigned cc, float value) if (SControlsPanel* panel = controlsPanel_) panel->setControlValue(cc, value); - if (CControl* other = getSecondaryCCControl(cc)) { + if (SKnobCCBox* other = getSecondaryCCKnob(cc)) { other->setValue(value); other->invalid(); } @@ -1651,7 +1680,7 @@ void Editor::Impl::updateCCDefaultValue(unsigned cc, float value) if (SControlsPanel* panel = controlsPanel_) panel->setControlDefaultValue(cc, value); - if (CControl* other = getSecondaryCCControl(cc)) + if (SKnobCCBox* other = getSecondaryCCKnob(cc)) other->setDefaultValue(value); } @@ -1977,4 +2006,6 @@ void Editor::Impl::onThemeChanged() if (function) function(); } + if (CFrame* frame = frame_) + frame->invalid(); } diff --git a/plugins/editor/src/editor/GUIComponents.cpp b/plugins/editor/src/editor/GUIComponents.cpp index a70f70e2c..752e6b7e8 100644 --- a/plugins/editor/src/editor/GUIComponents.cpp +++ b/plugins/editor/src/editor/GUIComponents.cpp @@ -15,6 +15,7 @@ #include "vstgui/lib/cvstguitimer.h" #include "vstgui/lib/cframe.h" #include "utility/vstgui_after.h" +#include "absl/strings/numbers.h" /// SBoxContainer::SBoxContainer(const CRect& size) @@ -582,7 +583,7 @@ void SStyledKnob::draw(CDrawContext* dc) dc->drawLine(p1, p2); } - if (valueToStringFunction_ && fontColor_.alpha > 0) { + if (valueToStringFunction_ && fontColor_.alpha > 0 && !hideValue_) { std::string text; if (valueToStringFunction_(getValue(), text)) { dc->setFont(font_); @@ -592,12 +593,29 @@ void SStyledKnob::draw(CDrawContext* dc) } } +void CFilledRect::draw(CDrawContext* dc) +{ + CRect bounds = getViewSize(); + dc->setFillColor(color_); + bool isRounded = radius_ > 0.0; + if (isRounded) { + auto roundRect = owned(dc->createRoundRectGraphicsPath(bounds, radius_)); + dc->drawGraphicsPath(roundRect, CDrawContext::kPathFilled); + } else { + dc->drawRect(bounds, kDrawFilled); + } +} + /// SKnobCCBox::SKnobCCBox(const CRect& size, IControlListener* listener, int32_t tag) : CViewContainer(size), label_(makeOwned(CRect())), + valueEdit_(makeOwned(CRect(), listener, tag)), knob_(makeOwned(CRect(), listener, tag)), - ccLabel_(makeOwned(CRect())) + ccLabel_(makeOwned(CRect())), + shadingRectangle_(makeOwned(CRect())), + menuEntry_(makeOwned("Use HDCC", tag)), + menuListener_(owned(new MenuListener(*this))) { setBackgroundColor(CColor(0x00, 0x00, 0x00, 0x00)); @@ -614,17 +632,129 @@ SKnobCCBox::SKnobCCBox(const CRect& size, IControlListener* listener, int32_t ta ccLabel_->setFrameColor(CColor(0x00, 0x00, 0x00, 0x00)); ccLabel_->setFontColor(CColor(0xff, 0xff, 0xff)); + valueEdit_->setBackColor(CColor(0x00, 0x00, 0x00, 0x00)); + valueEdit_->setFrameColor(CColor(0x00, 0x00, 0x00, 0x00)); + valueEdit_->setFontColor(CColor(0x00, 0x00, 0x00, 0xff)); + valueEdit_->registerViewListener(this); + setHDMode(false); + valueEdit_->setVisible(false); + + shadingRectangle_->setVisible(false); + addView(label_); label_->remember(); addView(knob_); knob_->remember(); + addView(shadingRectangle_); + shadingRectangle_->remember(); + addView(valueEdit_); + valueEdit_->remember(); addView(ccLabel_); ccLabel_->remember(); - updateViewColors(); updateViewSizes(); } +SKnobCCBox::~SKnobCCBox() +{ + valueEdit_->unregisterViewListener(this); +} + +void SKnobCCBox::setHDMode(bool mode) +{ + if (mode) { + auto valueToString = [](float value, std::string& text, VSTGUI::CParamDisplay*) -> bool { + std::string s = std::to_string(value + 0.005f); + text = s.substr(0, 4); + return true; + }; + knob_->setValueToStringFunction([valueToString](float value, std::string& text) { + return valueToString(value, text, nullptr); + }); + valueEdit_->setValueToStringFunction2(valueToString); + + valueEdit_->setStringToValueFunction([](UTF8StringPtr txt, float& result, CTextEdit*) -> bool { + float value; + if (absl::SimpleAtof(txt, &value)) { + result = value; + return true; + } + + return false; + }); + menuEntry_->setTitle("Use low-res. CC"); + } else { + auto valueToString = [](float value, std::string& text, VSTGUI::CParamDisplay*) -> bool { + text = std::to_string(std::lround(value * 127)); + return true; + }; + knob_->setValueToStringFunction([valueToString](float value, std::string& text) { + return valueToString(value, text, nullptr); + }); + valueEdit_->setValueToStringFunction2(valueToString); + + valueEdit_->setStringToValueFunction([](UTF8StringPtr txt, float& result, CTextEdit*) -> bool { + float value; + if (absl::SimpleAtof(txt, &value)) { + result = value / 127.0f; + return true; + } + + return false; + }); + menuEntry_->setTitle("Use high-res. CC"); + } + + hdMode_ = mode; + valueEdit_->setValue(valueEdit_->getValue()); + invalid(); +} + +CMouseEventResult SKnobCCBox::onMouseDown(CPoint& where, const CButtonState& buttons) +{ + if (buttons.isRightButton()) { + CFrame* frame = getFrame(); + CPoint frameWhere = where; + frameWhere.offset(-getViewSize().left, -getViewSize().top); + this->localToFrame(frameWhere); + + auto self = shared(this); + frame->doAfterEventProcessing([self, frameWhere]() { + if (CFrame* frame = self->getFrame()) { + SharedPointer menu = + owned(new COptionMenu(CRect(), self->menuListener_, -1, nullptr, nullptr, COptionMenu::kPopupStyle)); + menu->addEntry(self->menuEntry_); + self->menuEntry_->remember(); // above call does not increment refcount + + menu->setFont(self->getValueEditFont()); + menu->setFontColor(self->getValueEditFontColor()); + menu->setBackColor(self->getValueEditBackColor()); + menu->popup(frame, frameWhere); + } + }); + return kMouseEventHandled; + } else if (buttons.isDoubleClick() && !valueEdit_->isVisible()) { + valueEdit_->setVisible(true); + shadingRectangle_->setVisible(true); + knob_->setHideValue(true); + valueEdit_->takeFocus(); + invalid(); + return kMouseEventHandled; + } + + return CViewContainer::onMouseDown(where, buttons); +} + +void SKnobCCBox::viewLostFocus (CView* view) +{ + if (view == valueEdit_.get()) { + shadingRectangle_->setVisible(false); + valueEdit_->setVisible(false); + knob_->setHideValue(false); + invalid(); + } +} + void SKnobCCBox::setHue(float hue) { hue_ = hue; @@ -637,12 +767,33 @@ void SKnobCCBox::setNameLabelFont(CFontRef font) updateViewSizes(); } +void SKnobCCBox::setValueEditFont(CFontRef font) +{ + label_->setFont(font); + updateViewSizes(); +} + void SKnobCCBox::setCCLabelFont(CFontRef font) { ccLabel_->setFont(font); updateViewSizes(); } +void SKnobCCBox::setValue(float value) +{ + float oldValue = knob_->getValue(); + knob_->setValue(value); + valueEdit_->setValue(value); + if (value != oldValue) + invalid(); +} + +void SKnobCCBox::setDefaultValue(float value) +{ + knob_->setDefaultValue(value); + valueEdit_->setDefaultValue(value); +} + void SKnobCCBox::updateViewSizes() { const CRect size = getViewSize(); @@ -650,19 +801,30 @@ void SKnobCCBox::updateViewSizes() const CFontRef nameFont = label_->getFont(); const CFontRef ccFont = ccLabel_->getFont(); + const CFontRef valueFont = valueEdit_->getFont(); nameLabelSize_ = CRect(0.0, 0.0, size.getWidth(), nameFont->getSize() + 2 * ypad); ccLabelSize_ = CRect(0.0, size.getHeight() - ccFont->getSize() - 2 * ypad, size.getWidth(), size.getHeight()); knobSize_ = CRect(0.0, nameLabelSize_.bottom, size.getWidth(), ccLabelSize_.top); + valueEditSize_ = CRect( + size.getWidth() / 2 - valueFont->getSize(), + size.getHeight() / 2 - valueFont->getSize() / 2, + size.getWidth() / 2 + valueFont->getSize(), + size.getHeight() / 2 + valueFont->getSize() / 2 + ); // remove knob side areas CCoord side = std::max(0.0, knobSize_.getWidth() - knobSize_.getHeight()); knobSize_.extend(-0.5 * side, 0.0); + shadingRectangleSize_ = knobSize_; + shadingRectangleSize_.bottom -= ypad; // label_->setViewSize(nameLabelSize_); knob_->setViewSize(knobSize_); ccLabel_->setViewSize(ccLabelSize_); + valueEdit_->setViewSize(valueEditSize_); + shadingRectangle_->setViewSize(shadingRectangleSize_); invalid(); } @@ -746,11 +908,6 @@ SControlsPanel::ControlSlot* SControlsPanel::getOrCreateSlot(uint32_t index) slot->box = box; slot->box->setCCLabelText(("CC " + std::to_string(index)).c_str()); - slot->box->setValueToStringFunction([](float value, std::string& text) -> bool { - text = std::to_string(std::lround(value * 127)); - return true; - }); - syncSlotStyle(index); return slot; @@ -760,10 +917,9 @@ void SControlsPanel::setControlValue(uint32_t index, float value) { ControlSlot* slot = getOrCreateSlot(index); SKnobCCBox* box = slot->box; - auto* control = box->getControl(); - float oldValue = control->getValue(); - control->setValue(value); - if (control->getValue() != oldValue) + float oldValue = box->getValue(); + box->setValue(value); + if (box->getValue() != oldValue) box->invalid(); } @@ -771,7 +927,7 @@ void SControlsPanel::setControlDefaultValue(uint32_t index, float value) { ControlSlot* slot = getOrCreateSlot(index); SKnobCCBox* box = slot->box; - box->getControl()->setDefaultValue(value); + box->setDefaultValue(value); } void SControlsPanel::setControlLabelText(uint32_t index, UTF8StringPtr text) @@ -788,12 +944,14 @@ void SControlsPanel::setControlLabelText(uint32_t index, UTF8StringPtr text) void SControlsPanel::setNameLabelFont(CFontRef font) { slots_[0]->box->setNameLabelFont(font); + slots_[0]->box->setValueEditFont(font); syncAllSlotStyles(); } void SControlsPanel::setNameLabelFontColor(CColor color) { slots_[0]->box->setNameLabelFontColor(color); + slots_[0]->box->setValueEditFontColor(color); syncAllSlotStyles(); } @@ -815,6 +973,24 @@ void SControlsPanel::setCCLabelFontColor(CColor color) syncAllSlotStyles(); } +void SControlsPanel::setValueEditBackColor(CColor color) +{ + slots_[0]->box->setValueEditBackColor(color); + syncAllSlotStyles(); +} + +void SControlsPanel::setShadingRectangleColor(CColor color) +{ + slots_[0]->box->setShadingRectangleColor(color); + syncAllSlotStyles(); +} + +void SControlsPanel::setValueEditFontColor(CColor color) +{ + slots_[0]->box->setValueEditFontColor(color); + syncAllSlotStyles(); +} + void SControlsPanel::setKnobActiveTrackColor(CColor color) { slots_[0]->box->setKnobActiveTrackColor(color); @@ -947,6 +1123,11 @@ void SControlsPanel::syncSlotStyle(uint32_t index) cur->setNameLabelFont(ref->getNameLabelFont()); cur->setNameLabelFontColor(ref->getNameLabelFontColor()); + cur->setValueEditFont(ref->getValueEditFont()); + cur->setValueEditFontColor(ref->getValueEditFontColor()); + + cur->setShadingRectangleColor(ref->getShadingRectangleColor()); + cur->setCCLabelFont(ref->getCCLabelFont()); cur->setCCLabelFontColor(ref->getCCLabelFontColor()); cur->setCCLabelBackColor(ref->getCCLabelBackColor()); @@ -979,6 +1160,113 @@ void SControlsPanel::ControlSlotListener::controlEndEdit(CControl* pControl) panel_->EndEditFunction(pControl->getTag()); } +/// +SLevelMeter::SLevelMeter(const CRect& size) + : CView(size) +{ +} + +void SLevelMeter::setValue(float value) +{ + if (value_ == value) + return; + + value_ = value; + + // instantiate the timer lazily + if (!timer_) { + const uint32_t interval = 10; + timer_ = makeOwned( + [this](CVSTGUITimer* timer) { + timer->stop(); + timerArmed_ = false; + invalid(); + }, interval, false); + } + + // defer the update, but do not rearm the timer + if (!timerArmed_) { + timerArmed_ = true; + timer_->start(); + } +} + +void SLevelMeter::draw(CDrawContext* dc) +{ + float dbValue = 20.0f * std::log10(value_); + float fill = (dbValue - dbMin_) / (dbMax_ - dbMin_); + fill = (fill < 0.0f) ? 0.0f : fill; + fill = (fill > 1.0f) ? 1.0f : fill; + + CRect largeBounds = getViewSize(); + CRect fillBounds = largeBounds; + fillBounds.top = largeBounds.bottom - fill * largeBounds.getHeight(); + + const CColor safeColor = safeFillColor_; + const CColor dangerColor = dangerFillColor_; + + CColor fillColor; + if (safeColor == dangerColor) { + fillColor = safeColor; + } + else { + float thres = dangerThreshold_; + float mix = (fill - thres) / (1.0f - thres); + mix = (mix < 0.0f) ? 0.0f : mix; + + CCoord safeH, safeS, safeV, safeA; + CCoord dangerH, dangerS, dangerV, dangerA; + safeColor.toHSV(safeH, safeS, safeV); + dangerColor.toHSV(dangerH, dangerS, dangerV); + safeA = safeColor.alpha / 255.0; + dangerA = dangerColor.alpha / 255.0; + + CCoord H, S, V, A; + H = safeH + mix * (dangerH - safeH); + S = safeS + mix * (dangerS - safeS); + V = safeV + mix * (dangerV - safeV); + A = safeA + mix * (dangerA - safeA); + + fillColor.fromHSV(H, S, V); + fillColor.alpha = static_cast(A * 255.0); + } + + CCoord radius = radius_; + bool isRounded = radius > 0.0; + + dc->setDrawMode(isRounded ? kAntiAliasing : kAliasing); + + SharedPointer largeRoundRect; + SharedPointer fillRoundRect; + + if (isRounded) { + largeRoundRect = owned(dc->createRoundRectGraphicsPath(largeBounds, radius)); + fillRoundRect = owned(dc->createRoundRectGraphicsPath(fillBounds, radius)); + } + + if (backColor_.alpha > 0) { + dc->setFillColor(backColor_); + if (!isRounded) + dc->drawRect(largeBounds, kDrawFilled); + else + dc->drawGraphicsPath(largeRoundRect, CDrawContext::kPathFilled); + } + + dc->setFrameColor(frameColor_); + dc->setFillColor(fillColor); + + if (!isRounded) { + if (fill > 0) + dc->drawRect(fillBounds, kDrawFilled); + dc->drawRect(largeBounds); + } + else { + if (fill > 0 && fillBounds.getHeight() >= radius) + dc->drawGraphicsPath(fillRoundRect, CDrawContext::kPathFilled); + dc->drawGraphicsPath(largeRoundRect, CDrawContext::kPathStroked); + } +} + /// SPlaceHolder::SPlaceHolder(const CRect& size, const CColor& color) : CView(size), color_(color) diff --git a/plugins/editor/src/editor/GUIComponents.h b/plugins/editor/src/editor/GUIComponents.h index b3cbc0569..c8cbd162d 100644 --- a/plugins/editor/src/editor/GUIComponents.h +++ b/plugins/editor/src/editor/GUIComponents.h @@ -14,6 +14,7 @@ #include "vstgui/lib/controls/cslider.h" #include "vstgui/lib/controls/cknob.h" #include "vstgui/lib/controls/ctextlabel.h" +#include "vstgui/lib/controls/ctextedit.h" #include "vstgui/lib/controls/cbuttons.h" #include "vstgui/lib/controls/coptionmenu.h" #include "vstgui/lib/controls/cscrollbar.h" @@ -233,6 +234,9 @@ class SStyledKnob : public CKnobBase { using ValueToStringFunction = std::function; void setValueToStringFunction(ValueToStringFunction func); + void setHideValue(bool hide) { hideValue_ = hide; invalid(); } + bool getHideValue() const { return hideValue_; } + CLASS_METHODS(SStyledKnob, CKnobBase) protected: void draw(CDrawContext* dc) override; @@ -241,6 +245,7 @@ class SStyledKnob : public CKnobBase { CColor activeTrackColor_; CColor inactiveTrackColor_; CColor lineIndicatorColor_; + bool hideValue_ { false }; SharedPointer font_ = kNormalFont; CColor fontColor_ { 0x00, 0x00, 0x00 }; @@ -248,12 +253,35 @@ class SStyledKnob : public CKnobBase { ValueToStringFunction valueToStringFunction_; }; +class CFilledRect : public CView +{ +public: + explicit CFilledRect(const CRect& size) + : CView(size) {} + + void setRadius(CCoord radius) { radius_ = radius; invalid(); } + CCoord getRadius() const { return radius_; } + + void setColor(CColor color){ color_ = color; invalid(); } + CColor getColor() { return color_; } +protected: + void draw(CDrawContext* dc) override; +private: + CCoord radius_ { 5.0 }; + CColor color_ { 0, 0, 0, 70 }; +}; + /// -class SKnobCCBox : public CViewContainer { +class SKnobCCBox : public CViewContainer, ViewListenerAdapter { public: SKnobCCBox(const CRect& size, IControlListener* listener, int32_t tag); + ~SKnobCCBox(); void setHue(float hue); - SStyledKnob* getControl() const { return knob_; } + + float getValue() const { return knob_->getValue(); } + float getDefaultValue() const { return knob_->getDefaultValue(); } + void setValue(float value); + void setDefaultValue(float value); void setNameLabelText(const UTF8String& name) { label_->setText(name); label_->invalid(); } void setCCLabelText(const UTF8String& name) { ccLabel_->setText(name); ccLabel_->invalid(); } @@ -264,6 +292,18 @@ class SKnobCCBox : public CViewContainer { void setNameLabelFontColor(CColor color) { label_->setFontColor(color); label_->invalid(); } CColor getNameLabelFontColor() const { return label_->getFontColor(); } + void setValueEditFont(CFontRef font); + CFontRef getValueEditFont() const { return label_->getFont(); } + + void setValueEditFontColor(CColor color) { valueEdit_->setFontColor(color); valueEdit_->invalid(); } + CColor getValueEditFontColor() const { return valueEdit_->getFontColor(); } + + void setValueEditBackColor(CColor color) { valueEdit_->setBackColor(color); valueEdit_->invalid(); } + CColor getValueEditBackColor() const { return valueEdit_->getBackColor(); } + + void setShadingRectangleColor(CColor color) { shadingRectangle_->setColor(color); shadingRectangle_->invalid(); } + CColor getShadingRectangleColor() const { return shadingRectangle_->getColor(); } + void setCCLabelFont(CFontRef font); CFontRef getCCLabelFont() const { return ccLabel_->getFont(); } @@ -288,21 +328,44 @@ class SKnobCCBox : public CViewContainer { void setKnobFontColor(CColor color) { knob_->setFontColor(color); knob_->invalid(); } CColor getKnobFontColor() const { return knob_->getFontColor(); } - using ValueToStringFunction = SStyledKnob::ValueToStringFunction; - void setValueToStringFunction(ValueToStringFunction f) { knob_->setValueToStringFunction(std::move(f)); knob_->invalid(); } + // Edit box listener + void viewLostFocus (CView* view) override; + + bool isHD() const noexcept { return hdMode_; } + void setHDMode(bool mode); + +protected: + CMouseEventResult onMouseDown(CPoint& where, const CButtonState& buttons) override; private: void updateViewSizes(); void updateViewColors(); - -private: SharedPointer label_; + SharedPointer valueEdit_; SharedPointer knob_; SharedPointer ccLabel_; + SharedPointer shadingRectangle_; + SharedPointer menuEntry_; CRect nameLabelSize_; CRect knobSize_; + CRect shadingRectangleSize_; CRect ccLabelSize_; + CRect valueEditSize_; + CRect rectangleSize_; float hue_ = 0.35; + + class MenuListener : public IControlListener, public NonAtomicReferenceCounted { + public: + explicit MenuListener(SKnobCCBox& box) : box_(box) {} + void valueChanged(CControl*) override + { + box_.setHDMode(!box_.isHD()); + } + private: + SKnobCCBox& box_; + }; + SharedPointer menuListener_; + bool hdMode_ { false }; }; /// @@ -320,6 +383,9 @@ class SControlsPanel : public CScrollView { void setCCLabelFont(CFontRef font); void setCCLabelBackColor(CColor color); void setCCLabelFontColor(CColor color); + void setValueEditBackColor(CColor color); + void setValueEditFontColor(CColor color); + void setShadingRectangleColor(CColor color); void setKnobActiveTrackColor(CColor color); void setKnobInactiveTrackColor(CColor color); void setKnobLineIndicatorColor(CColor color); @@ -365,6 +431,47 @@ class SControlsPanel : public CScrollView { SharedPointer relayoutTrigger_; }; +/// +class SLevelMeter : public CView { +public: + explicit SLevelMeter(const CRect& size); + + float getValue() const { return value_; } + void setValue(float value); + + float getDangerThreshold() const { return dangerThreshold_; } + void setDangerThreshold(float thres) { dangerThreshold_ = thres; invalid(); } + + CColor getFrameColor() const { return frameColor_; } + void setFrameColor(CColor color) { frameColor_ = color; invalid(); } + CColor getBackColor() const { return backColor_; } + void setBackColor(CColor color) { backColor_ = color; invalid(); } + + CColor getNormalFillColor() const { return safeFillColor_; } + void setNormalFillColor(CColor color) { safeFillColor_ = color; invalid(); } + CColor getDangerFillColor() const { return dangerFillColor_; } + void setDangerFillColor(CColor color) { dangerFillColor_ = color; invalid(); } + + CCoord getRoundRectRadius() const { return radius_; } + void setRoundRectRadius(CCoord radius) { radius_ = radius; invalid(); } + +protected: + void draw(CDrawContext* dc) override; + +private: + float value_ = 0; + float dangerThreshold_ = 0.5; + float dbMin_ = -40; + float dbMax_ = 0; + CColor frameColor_; + CColor safeFillColor_; + CColor dangerFillColor_; + CColor backColor_; + CCoord radius_ = 5.0; + SharedPointer timer_; + bool timerArmed_ = false; +}; + /// class SPlaceHolder : public CView { public: diff --git a/plugins/editor/src/editor/GUIHelpers.cpp b/plugins/editor/src/editor/GUIHelpers.cpp index b5da74431..901891b5c 100644 --- a/plugins/editor/src/editor/GUIHelpers.cpp +++ b/plugins/editor/src/editor/GUIHelpers.cpp @@ -5,6 +5,9 @@ // If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz #include "GUIHelpers.h" +#include "utility/vstgui_before.h" +#include +#include "utility/vstgui_after.h" class SFrameDisabler::KeyAndMouseHook : public CBaseObject, public IKeyboardHook, @@ -13,12 +16,10 @@ class SFrameDisabler::KeyAndMouseHook : public CBaseObject, void setEnabled(bool value) { enabled_ = value; } protected: - int32_t onKeyDown(const VstKeyCode&, CFrame*) { return enabled_ ? -1 : 1; } - int32_t onKeyUp(const VstKeyCode&, CFrame*) { return enabled_ ? -1 : 1; } - void onMouseEntered(CView*, CFrame*) {} - void onMouseExited(CView*, CFrame*) {} - CMouseEventResult onMouseMoved(CFrame*, const CPoint&, const CButtonState&) { return enabled_ ? kMouseEventNotHandled : kMouseEventHandled; } - CMouseEventResult onMouseDown(CFrame*, const CPoint&, const CButtonState&) { return enabled_ ? kMouseEventNotHandled : kMouseEventHandled; } + void onKeyboardEvent(KeyboardEvent& event, CFrame* frame) override; + void onMouseEntered(CView* view, CFrame* frame) override {} + void onMouseExited(CView* view, CFrame* frame) override {} + void onMouseEvent(MouseEvent& event, CFrame* frame) override; private: bool enabled_ = true; @@ -51,3 +52,15 @@ void SFrameDisabler::disable() hook_->setEnabled(false); delayedEnabler_->stop(); } + +void SFrameDisabler::KeyAndMouseHook::onKeyboardEvent(KeyboardEvent& event, CFrame* frame) +{ + if (!enabled_) + event.consumed = true; +} + +void SFrameDisabler::KeyAndMouseHook::onMouseEvent(MouseEvent& event, CFrame* frame) +{ + if (!enabled_) + event.consumed = true; +} diff --git a/plugins/editor/src/editor/GUIPiano.cpp b/plugins/editor/src/editor/GUIPiano.cpp index 4091ef602..132044751 100644 --- a/plugins/editor/src/editor/GUIPiano.cpp +++ b/plugins/editor/src/editor/GUIPiano.cpp @@ -164,7 +164,6 @@ void SPiano::draw(CDrawContext* dc) const Dimensions dim = getDimensions(false); const unsigned octs = impl.octs_; const unsigned keyCount = octs * 12; - const bool allKeysUsed = impl.keyUsed_.all(); dc->setDrawMode(kAntiAliasing); @@ -184,14 +183,12 @@ void SPiano::draw(CDrawContext* dc) switch (getKeyRole(key)) { case KeyRole::Note: - if (allKeysUsed) - goto whiteKeyDefault; hcy.h = impl.keyUsedHue_; break; case KeyRole::Switch: hcy.h = impl.keySwitchHue_; break; - default: whiteKeyDefault: + default: hcy.y = 1.0; if (impl.keyval_[key]) hcy.c = 0.0; @@ -224,14 +221,12 @@ void SPiano::draw(CDrawContext* dc) switch (getKeyRole(key)) { case KeyRole::Note: - if (allKeysUsed) - goto blackKeyDefault; hcy.h = impl.keyUsedHue_; break; case KeyRole::Switch: hcy.h = impl.keySwitchHue_; break; - default: blackKeyDefault: + default: hcy.c = 0.0; break; } diff --git a/plugins/editor/src/editor/NativeHelpers.cpp b/plugins/editor/src/editor/NativeHelpers.cpp index e4f0b3201..f089b258d 100644 --- a/plugins/editor/src/editor/NativeHelpers.cpp +++ b/plugins/editor/src/editor/NativeHelpers.cpp @@ -5,34 +5,13 @@ // If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz #include "NativeHelpers.h" +#include "plugin/NativeHelpers.h" #if defined(_WIN32) #include "ghc/fs_std.hpp" #include #include -static WCHAR *stringToWideChar(const char *str, int strCch = -1) -{ - unsigned strSize = MultiByteToWideChar(CP_UTF8, 0, str, strCch, nullptr, 0); - if (strSize == 0) - return {}; - std::unique_ptr strW(new WCHAR[strSize]); - if (MultiByteToWideChar(CP_UTF8, 0, str, strCch, strW.get(), strSize) == 0) - return {}; - return strW.release(); -} - -static char* stringToUTF8(const wchar_t *strW, int strWCch = -1) -{ - unsigned strSize = WideCharToMultiByte(CP_UTF8, 0, strW, strWCch, nullptr, 0, nullptr, nullptr); - if (strSize == 0) - return {}; - std::unique_ptr str(new char[strSize]); - if (WideCharToMultiByte(CP_UTF8, 0, strW, strWCch, str.get(), strSize, nullptr, nullptr) == 0) - return {}; - return str.release(); -} - bool openFileInExternalEditor(const char *filename) { std::wstring path = stringToWideChar(filename); @@ -163,8 +142,9 @@ std::string getCurrentProcessName() #include #include #include +#include +#include #include -#include #include #include #include @@ -218,12 +198,21 @@ static std::vector createForkEnviron() return newEnv; } -static constexpr char zenityPath[] = "/usr/bin/zenity"; +static const std::string zenityPath = [] { + auto glibPath = g_find_program_in_path("zenity"); + if (glibPath) { + std::string s { glibPath }; + g_free(glibPath); + return s; + } else { + return std::string("/usr/bin/zenity"); + } +}(); bool askQuestion(const char *text) { char *argv[] = { - const_cast(zenityPath), + const_cast(zenityPath.c_str()), const_cast("--question"), const_cast("--text"), const_cast(text), @@ -256,7 +245,7 @@ bool askQuestion(const char *text) bool isZenityAvailable() { - return access(zenityPath, X_OK) == 0; + return access(zenityPath.c_str(), X_OK) == 0; } std::string getOperatingSystemName() @@ -305,14 +294,22 @@ std::string getProcessorName() std::string name; std::string line; std::ifstream in("/proc/cpuinfo", std::ios::binary); - std::regex re("^model name\\s*:\\s*(.*)"); line.reserve(256); while (name.empty() && std::getline(in, line) && !line.empty()) { - std::smatch match; - if (std::regex_match(line, match, re)) - name = match[1]; + size_t pos = line.find(':'); + if (pos == line.npos) + continue; + + absl::string_view left = absl::string_view(line).substr(0, pos); + absl::string_view right = absl::string_view(line).substr(pos + 1); + + left = absl::StripAsciiWhitespace(left); + right = absl::StripAsciiWhitespace(right); + + if (left == "model name") + name = std::string(right); } if (name.empty()) diff --git a/plugins/editor/src/editor/NativeHelpers.mm b/plugins/editor/src/editor/NativeHelpers.mm index da2eb1e48..e2d539908 100644 --- a/plugins/editor/src/editor/NativeHelpers.mm +++ b/plugins/editor/src/editor/NativeHelpers.mm @@ -24,11 +24,30 @@ static bool openFileWithApplication(const char *fileName, NSString *application) bool openFileInExternalEditor(const char *fileName) { - NSURL* applicationURL = (__bridge_transfer NSURL*)LSCopyDefaultApplicationURLForContentType( - kUTTypePlainText, kLSRolesEditor, nil); - if (!applicationURL || ![applicationURL isFileURL]) + NSURL* appURL = nil; + NSURL* fileURL = [NSURL fileURLWithPath:[NSString stringWithUTF8String:fileName]]; + const LSRolesMask roles = kLSRolesEditor; + + NSArray* editorApps = (__bridge_transfer NSArray*)LSCopyApplicationURLsForURL( + (__bridge CFURLRef)fileURL, roles); + + for (NSUInteger i = 0, n = [editorApps count]; i < n && !appURL; ++i) { + NSURL* url = [editorApps objectAtIndex:i]; + if (url && [url isFileURL]) + appURL = url; + } + + if (!appURL) { + NSURL* url = (__bridge_transfer NSURL*)LSCopyDefaultApplicationURLForContentType( + kUTTypePlainText, roles, nil); + if (url && [url isFileURL]) + appURL = url; + } + + if (!appURL) return false; - return openFileWithApplication(fileName, [applicationURL path]); + + return openFileWithApplication(fileName, [appURL path]); } bool openDirectoryInExplorer(const char *fileName) diff --git a/plugins/editor/src/editor/layout/main.hpp b/plugins/editor/src/editor/layout/main.hpp index 7c0ebf314..a14cb9515 100644 --- a/plugins/editor/src/editor/layout/main.hpp +++ b/plugins/editor/src/editor/layout/main.hpp @@ -1,7 +1,7 @@ /* This file is generated by the layout maker tool. */ auto* const view__0 = createLogicalGroup(CRect(0, 0, 800, 475), -1, "", kCenterText, 14); mainView = view__0; -auto* const view__1 = createBackground(CRect(190, 110, 790, 390), -1, "", kCenterText, 14); +auto* const view__1 = createBackground(CRect(5, 110, 795, 395), -1, "", kCenterText, 14); imageContainer_ = view__1; view__0->addView(view__1); enterPalette(invertedPalette); @@ -11,185 +11,197 @@ auto* const view__3 = createRoundedGroup(CRect(5, 4, 180, 105), -1, "", kCenterT view__2->addView(view__3); auto* const view__4 = createAboutButton(CRect(27, 5, 147, 65), kTagAbout, "", kCenterText, 14); view__3->addView(view__4); -auto* const view__5 = createHomeButton(CRect(31, 69, 63, 101), kTagFirstChangePanel+kPanelGeneral, "", kCenterText, 30); -panelButtons_[kPanelGeneral] = view__5; +auto* const view__5 = createInfoButton(CRect(51, 69, 83, 101), kTagFirstChangePanel+kPanelInfo, "", kCenterText, 30); +panelButtons_[kPanelInfo] = view__5; view__3->addView(view__5); -auto* const view__6 = createCCButton(CRect(71, 69, 103, 101), kTagFirstChangePanel+kPanelControls, "", kCenterText, 30); +auto* const view__6 = createCCButton(CRect(92, 69, 124, 101), kTagFirstChangePanel+kPanelControls, "", kCenterText, 30); panelButtons_[kPanelControls] = view__6; view__3->addView(view__6); -auto* const view__7 = createSettingsButton(CRect(111, 69, 143, 101), kTagFirstChangePanel+kPanelSettings, "", kCenterText, 30); +auto* const view__7 = createSettingsButton(CRect(132, 69, 164, 101), kTagFirstChangePanel+kPanelSettings, "", kCenterText, 30); panelButtons_[kPanelSettings] = view__7; view__3->addView(view__7); -auto* const view__8 = createRoundedGroup(CRect(185, 5, 565, 105), -1, "", kCenterText, 14); -view__2->addView(view__8); -auto* const view__9 = createHLine(CRect(10, 36, 370, 41), -1, "", kCenterText, 14); -view__8->addView(view__9); -auto* const view__10 = createHLine(CRect(10, 68, 370, 73), -1, "", kCenterText, 14); -view__8->addView(view__10); -auto* const view__11 = createClickableLabel(CRect(10, 8, 260, 38), kTagLoadSfzFile, "DefaultInstrument.sfz", kLeftText, 20); -sfzFileLabel_ = view__11; -view__8->addView(view__11); -auto* const view__12 = createLabel(CRect(80, 40, 370, 70), -1, "", kLeftText, 20); -keyswitchLabel_ = view__12; -view__8->addView(view__12); -auto* const view__13 = createBadge(CRect(10, 42, 70, 68), -1, "", kCenterText, 20); -keyswitchBadge_ = view__13; -view__8->addView(view__13); -auto* const view__14 = createInactiveLabel(CRect(10, 40, 370, 70), -1, "No key switch", kLeftText, 20); -keyswitchInactiveLabel_ = view__14; -view__8->addView(view__14); -view__14->setVisible(false); -auto* const view__15 = createLabel(CRect(10, 73, 70, 98), -1, "Voices:", kRightText, 12); -view__8->addView(view__15); -auto* const view__16 = createPreviousFileButton(CRect(295, 13, 320, 38), kTagPreviousSfzFile, "", kCenterText, 24); -view__8->addView(view__16); -auto* const view__17 = createNextFileButton(CRect(320, 13, 345, 38), kTagNextSfzFile, "", kCenterText, 24); -view__8->addView(view__17); -auto* const view__18 = createChevronDropDown(CRect(345, 13, 370, 38), kTagFileOperations, "", kCenterText, 24); -fileOperationsMenu_ = view__18; -view__8->addView(view__18); -auto* const view__19 = createLabel(CRect(75, 73, 115, 98), -1, "", kCenterText, 12); -infoVoicesLabel_ = view__19; -view__8->addView(view__19); -auto* const view__20 = createLabel(CRect(130, 73, 170, 98), -1, "Max:", kRightText, 12); -view__8->addView(view__20); -auto* const view__21 = createLabel(CRect(175, 73, 210, 98), -1, "", kCenterText, 12); -numVoicesLabel_ = view__21; -view__8->addView(view__21); -auto* const view__22 = createLabel(CRect(240, 73, 300, 98), -1, "Memory:", kRightText, 12); -view__8->addView(view__22); -auto* const view__23 = createLabel(CRect(305, 73, 365, 98), -1, "", kCenterText, 12); -memoryLabel_ = view__23; -view__8->addView(view__23); -auto* const view__24 = createChevronValueDropDown(CRect(210, 77, 230, 97), kTagSetNumVoices, "", kCenterText, 16); -numVoicesSlider_ = view__24; -view__8->addView(view__24); -auto* const view__25 = createRoundedGroup(CRect(570, 5, 795, 105), -1, "", kCenterText, 14); -view__2->addView(view__25); -auto* const view__26 = createKnob48(CRect(45, 15, 93, 63), -1, "", kCenterText, 14); -view__25->addView(view__26); -view__26->setVisible(false); -auto* const view__27 = createValueLabel(CRect(40, 65, 100, 70), -1, "Center", kCenterText, 12); -view__25->addView(view__27); +auto* const view__8 = createHomeButton(CRect(11, 69, 43, 101), kTagFirstChangePanel+kPanelGeneral, "", kCenterText, 30); +panelButtons_[kPanelGeneral] = view__8; +view__3->addView(view__8); +auto* const view__9 = createRoundedGroup(CRect(185, 5, 603, 105), -1, "", kCenterText, 14); +view__2->addView(view__9); +auto* const view__10 = createHLine(CRect(10, 36, 405, 41), -1, "", kCenterText, 14); +view__9->addView(view__10); +auto* const view__11 = createHLine(CRect(10, 68, 405, 73), -1, "", kCenterText, 14); +view__9->addView(view__11); +auto* const view__12 = createClickableLabel(CRect(10, 8, 330, 38), kTagLoadSfzFile, "DefaultInstrument.sfz", kLeftText, 20); +sfzFileLabel_ = view__12; +view__9->addView(view__12); +auto* const view__13 = createLabel(CRect(80, 40, 405, 75), -1, "", kLeftText, 20); +keyswitchLabel_ = view__13; +view__9->addView(view__13); +auto* const view__14 = createBadge(CRect(10, 42, 70, 68), -1, "", kCenterText, 20); +keyswitchBadge_ = view__14; +view__9->addView(view__14); +auto* const view__15 = createInactiveLabel(CRect(10, 40, 370, 70), -1, "No key switch", kLeftText, 20); +keyswitchInactiveLabel_ = view__15; +view__9->addView(view__15); +view__15->setVisible(false); +auto* const view__16 = createLabel(CRect(10, 73, 70, 98), -1, "Voices:", kRightText, 12); +view__9->addView(view__16); +auto* const view__17 = createPreviousFileButton(CRect(330, 13, 355, 38), kTagPreviousSfzFile, "", kCenterText, 24); +view__9->addView(view__17); +auto* const view__18 = createNextFileButton(CRect(355, 13, 380, 38), kTagNextSfzFile, "", kCenterText, 24); +view__9->addView(view__18); +auto* const view__19 = createChevronDropDown(CRect(380, 13, 405, 38), kTagFileOperations, "", kCenterText, 24); +fileOperationsMenu_ = view__19; +view__9->addView(view__19); +auto* const view__20 = createLabel(CRect(75, 73, 115, 98), -1, "", kCenterText, 12); +infoVoicesLabel_ = view__20; +view__9->addView(view__20); +auto* const view__21 = createLabel(CRect(147, 73, 187, 98), -1, "Max:", kRightText, 12); +view__9->addView(view__21); +auto* const view__22 = createLabel(CRect(193, 73, 228, 98), -1, "", kCenterText, 12); +numVoicesLabel_ = view__22; +view__9->addView(view__22); +auto* const view__23 = createLabel(CRect(274, 73, 334, 98), -1, "Memory:", kRightText, 12); +view__9->addView(view__23); +auto* const view__24 = createLabel(CRect(340, 73, 400, 98), -1, "", kCenterText, 12); +memoryLabel_ = view__24; +view__9->addView(view__24); +auto* const view__25 = createChevronValueDropDown(CRect(235, 77, 255, 97), kTagSetNumVoices, "", kCenterText, 16); +numVoicesSlider_ = view__25; +view__9->addView(view__25); +auto* const view__26 = createRoundedGroup(CRect(608, 5, 795, 105), -1, "", kCenterText, 14); +view__2->addView(view__26); +auto* const view__27 = createKnob48(CRect(7, 15, 55, 63), -1, "", kCenterText, 14); +view__26->addView(view__27); view__27->setVisible(false); -auto* const view__28 = createVMeter(CRect(175, 15, 210, 70), -1, "", kCenterText, 14); -view__25->addView(view__28); -auto* const view__29 = createKnobCCBox(CRect(10, 5, 80, 95), kTagSetCCVolume, "Volume", kCenterText, 12); +auto* const view__28 = createValueLabel(CRect(2, 65, 62, 70), -1, "Center", kCenterText, 12); +view__26->addView(view__28); +view__28->setVisible(false); +auto* const view__29 = createKnobCCBox(CRect(6, 5, 76, 95), kTagSetCCVolume, "Volume", kCenterText, 12); volumeCCKnob_ = view__29; -view__25->addView(view__29); -auto* const view__30 = createKnobCCBox(CRect(85, 5, 155, 95), kTagSetCCPan, "Pan", kCenterText, 12); +view__26->addView(view__29); +auto* const view__30 = createKnobCCBox(CRect(83, 5, 153, 95), kTagSetCCPan, "Pan", kCenterText, 12); panCCKnob_ = view__30; -view__25->addView(view__30); +view__26->addView(view__30); +auto* const view__31 = createVMeter(CRect(159, 5, 168, 95), -1, "", kCenterText, 14); +leftMeter_ = view__31; +view__26->addView(view__31); +auto* const view__32 = createVMeter(CRect(171, 5, 180, 95), -1, "", kCenterText, 14); +rightMeter_ = view__32; +view__26->addView(view__32); enterPalette(defaultPalette); -auto* const view__31 = createLogicalGroup(CRect(5, 110, 796, 395), -1, "", kCenterText, 14); -subPanels_[kPanelGeneral] = view__31; -view__0->addView(view__31); -view__31->setVisible(false); -auto* const view__32 = createRoundedGroup(CRect(0, 0, 175, 280), -1, "", kCenterText, 14); -view__31->addView(view__32); -auto* const view__33 = createLabel(CRect(15, 10, 75, 35), -1, "Curves:", kLeftText, 14); -view__32->addView(view__33); -auto* const view__34 = createLabel(CRect(15, 35, 75, 60), -1, "Masters:", kLeftText, 14); -view__32->addView(view__34); -auto* const view__35 = createLabel(CRect(15, 60, 75, 85), -1, "Groups:", kLeftText, 14); -view__32->addView(view__35); -auto* const view__36 = createLabel(CRect(15, 85, 75, 110), -1, "Regions:", kLeftText, 14); -view__32->addView(view__36); -auto* const view__37 = createLabel(CRect(15, 110, 75, 135), -1, "Samples:", kLeftText, 14); -view__32->addView(view__37); -auto* const view__38 = createLabel(CRect(115, 10, 155, 35), -1, "0", kCenterText, 14); -infoCurvesLabel_ = view__38; -view__32->addView(view__38); -auto* const view__39 = createLabel(CRect(115, 35, 155, 60), -1, "0", kCenterText, 14); -infoMastersLabel_ = view__39; -view__32->addView(view__39); -auto* const view__40 = createLabel(CRect(115, 60, 155, 85), -1, "0", kCenterText, 14); -infoGroupsLabel_ = view__40; -view__32->addView(view__40); -auto* const view__41 = createLabel(CRect(115, 85, 155, 110), -1, "0", kCenterText, 14); -infoRegionsLabel_ = view__41; -view__32->addView(view__41); -auto* const view__42 = createLabel(CRect(115, 110, 155, 135), -1, "0", kCenterText, 14); -infoSamplesLabel_ = view__42; -view__32->addView(view__42); -auto* const view__43 = createLogicalGroup(CRect(5, 110, 795, 395), -1, "", kCenterText, 14); -subPanels_[kPanelControls] = view__43; -view__0->addView(view__43); -view__43->setVisible(false); -auto* const view__44 = createRoundedGroup(CRect(0, 0, 790, 285), -1, "", kCenterText, 14); -view__43->addView(view__44); -auto* const view__45 = createControlsPanel(CRect(0, 0, 790, 285), -1, "", kCenterText, 12); -controlsPanel_ = view__45; -view__44->addView(view__45); -auto* const view__46 = createLogicalGroup(CRect(5, 109, 795, 425), -1, "", kCenterText, 14); -subPanels_[kPanelSettings] = view__46; -view__0->addView(view__46); -auto* const view__47 = createTitleGroup(CRect(300, 26, 495, 126), -1, "Engine", kCenterText, 12); +auto* const view__33 = createLogicalGroup(CRect(5, 110, 795, 395), -1, "", kCenterText, 14); +subPanels_[kPanelInfo] = view__33; +view__0->addView(view__33); +view__33->setVisible(false); +auto* const view__34 = createRoundedGroup(CRect(0, 0, 790, 285), -1, "", kCenterText, 14); +view__33->addView(view__34); +auto* const view__35 = createLabel(CRect(15, 10, 75, 35), -1, "Curves:", kLeftText, 14); +view__34->addView(view__35); +auto* const view__36 = createLabel(CRect(15, 35, 75, 60), -1, "Masters:", kLeftText, 14); +view__34->addView(view__36); +auto* const view__37 = createLabel(CRect(15, 60, 75, 85), -1, "Groups:", kLeftText, 14); +view__34->addView(view__37); +auto* const view__38 = createLabel(CRect(15, 85, 75, 110), -1, "Regions:", kLeftText, 14); +view__34->addView(view__38); +auto* const view__39 = createLabel(CRect(15, 110, 75, 135), -1, "Samples:", kLeftText, 14); +view__34->addView(view__39); +auto* const view__40 = createLabel(CRect(115, 10, 155, 35), -1, "0", kCenterText, 14); +infoCurvesLabel_ = view__40; +view__34->addView(view__40); +auto* const view__41 = createLabel(CRect(115, 35, 155, 60), -1, "0", kCenterText, 14); +infoMastersLabel_ = view__41; +view__34->addView(view__41); +auto* const view__42 = createLabel(CRect(115, 60, 155, 85), -1, "0", kCenterText, 14); +infoGroupsLabel_ = view__42; +view__34->addView(view__42); +auto* const view__43 = createLabel(CRect(115, 85, 155, 110), -1, "0", kCenterText, 14); +infoRegionsLabel_ = view__43; +view__34->addView(view__43); +auto* const view__44 = createLabel(CRect(115, 110, 155, 135), -1, "0", kCenterText, 14); +infoSamplesLabel_ = view__44; +view__34->addView(view__44); +auto* const view__45 = createLogicalGroup(CRect(5, 110, 795, 395), -1, "", kCenterText, 14); +subPanels_[kPanelControls] = view__45; +view__0->addView(view__45); +view__45->setVisible(false); +auto* const view__46 = createRoundedGroup(CRect(0, 0, 790, 285), -1, "", kCenterText, 14); +view__45->addView(view__46); +auto* const view__47 = createControlsPanel(CRect(0, 0, 790, 285), -1, "", kCenterText, 12); +controlsPanel_ = view__47; view__46->addView(view__47); -auto* const view__48 = createValueMenu(CRect(25, 60, 85, 85), kTagSetOversampling, "", kCenterText, 12); -oversamplingSlider_ = view__48; -view__47->addView(view__48); -auto* const view__49 = createValueLabel(CRect(15, 20, 95, 45), -1, "Oversampling", kCenterText, 12); -view__47->addView(view__49); -auto* const view__50 = createValueLabel(CRect(100, 20, 180, 45), -1, "Preload size", kCenterText, 12); -view__47->addView(view__50); -auto* const view__51 = createValueMenu(CRect(110, 60, 170, 85), kTagSetPreloadSize, "", kCenterText, 12); -preloadSizeSlider_ = view__51; -view__47->addView(view__51); -auto* const view__52 = createTitleGroup(CRect(170, 161, 585, 261), -1, "Tuning", kCenterText, 12); -view__46->addView(view__52); -auto* const view__53 = createValueLabel(CRect(155, 20, 235, 45), -1, "Root key", kCenterText, 12); -view__52->addView(view__53); -auto* const view__54 = createValueMenu(CRect(250, 60, 310, 85), kTagSetTuningFrequency, "", kCenterText, 12); -tuningFrequencySlider_ = view__54; -view__52->addView(view__54); -auto* const view__55 = createValueLabel(CRect(240, 20, 320, 45), -1, "Frequency", kCenterText, 12); -view__52->addView(view__55); -auto* const view__56 = createStyledKnob(CRect(340, 45, 388, 93), kTagSetStretchedTuning, "", kCenterText, 14); -stretchedTuningSlider_ = view__56; -view__52->addView(view__56); -auto* const view__57 = createValueLabel(CRect(325, 20, 405, 45), -1, "Stretch", kCenterText, 12); -view__52->addView(view__57); -auto* const view__58 = createValueLabel(CRect(20, 20, 120, 45), -1, "Scala file", kCenterText, 12); -view__52->addView(view__58); -auto* const view__59 = createValueButton(CRect(20, 60, 120, 85), kTagLoadScalaFile, "DefaultScale", kCenterText, 12); -scalaFileButton_ = view__59; -view__52->addView(view__59); -auto* const view__60 = createValueMenu(CRect(165, 60, 200, 85), kTagSetScalaRootKey, "", kCenterText, 12); -scalaRootKeySlider_ = view__60; -view__52->addView(view__60); -auto* const view__61 = createValueMenu(CRect(200, 60, 230, 85), kTagSetScalaRootKey, "", kCenterText, 12); -scalaRootOctaveSlider_ = view__61; -view__52->addView(view__61); -auto* const view__62 = createResetSomethingButton(CRect(120, 60, 145, 85), kTagResetScalaFile, "", kCenterText, 12); -scalaResetButton_ = view__62; -view__52->addView(view__62); -auto* const view__63 = createTitleGroup(CRect(615, 161, 754, 261), -1, "Files", kCenterText, 12); -userFilesGroup_ = view__63; -view__46->addView(view__63); -auto* const view__64 = createValueLabel(CRect(20, 20, 120, 45), -1, "User SFZ folder", kCenterText, 12); -view__63->addView(view__64); -auto* const view__65 = createValueButton(CRect(20, 60, 120, 85), kTagChooseUserFilesDir, "DefaultPath", kCenterText, 12); -userFilesDirButton_ = view__65; -view__63->addView(view__65); -auto* const view__66 = createTitleGroup(CRect(525, 26, 720, 126), -1, "Quality", kCenterText, 12); -view__46->addView(view__66); -auto* const view__67 = createValueMenu(CRect(15, 60, 95, 85), kTagSetSampleQuality, "", kCenterText, 12); -sampleQualitySlider_ = view__67; -view__66->addView(view__67); -auto* const view__68 = createValueLabel(CRect(15, 20, 95, 45), -1, "Sample", kCenterText, 12); -view__66->addView(view__68); -auto* const view__69 = createValueLabel(CRect(100, 20, 180, 45), -1, "Oscillator", kCenterText, 12); -view__66->addView(view__69); -auto* const view__70 = createValueMenu(CRect(100, 60, 180, 85), kTagSetOscillatorQuality, "", kCenterText, 12); -oscillatorQualitySlider_ = view__70; -view__66->addView(view__70); -auto* const view__71 = createTitleGroup(CRect(35, 161, 140, 261), -1, "Theme", kCenterText, 12); -view__46->addView(view__71); -view__71->setVisible(false); -auto* const view__72 = createOptionMenu(CRect(20, 60, 85, 85), kTagThemeMenu, "", kCenterText, 12); -themeMenu_ = view__72; -view__71->addView(view__72); -auto* const view__73 = createPiano(CRect(5, 400, 795, 470), -1, "", kCenterText, 12); -piano_ = view__73; -view__0->addView(view__73); +auto* const view__48 = createLogicalGroup(CRect(5, 110, 795, 395), -1, "", kCenterText, 14); +subPanels_[kPanelSettings] = view__48; +view__0->addView(view__48); +auto* const view__49 = createTitleGroup(CRect(300, 25, 495, 125), -1, "Engine", kCenterText, 12); +view__48->addView(view__49); +auto* const view__50 = createValueMenu(CRect(25, 60, 85, 85), kTagSetOversampling, "", kCenterText, 12); +oversamplingSlider_ = view__50; +view__49->addView(view__50); +auto* const view__51 = createValueLabel(CRect(15, 20, 95, 45), -1, "Oversampling", kCenterText, 12); +view__49->addView(view__51); +auto* const view__52 = createValueLabel(CRect(100, 20, 180, 45), -1, "Preload size", kCenterText, 12); +view__49->addView(view__52); +auto* const view__53 = createValueMenu(CRect(110, 60, 170, 85), kTagSetPreloadSize, "", kCenterText, 12); +preloadSizeSlider_ = view__53; +view__49->addView(view__53); +auto* const view__54 = createTitleGroup(CRect(170, 160, 585, 260), -1, "Tuning", kCenterText, 12); +view__48->addView(view__54); +auto* const view__55 = createValueLabel(CRect(155, 20, 235, 45), -1, "Root key", kCenterText, 12); +view__54->addView(view__55); +auto* const view__56 = createValueMenu(CRect(250, 60, 310, 85), kTagSetTuningFrequency, "", kCenterText, 12); +tuningFrequencySlider_ = view__56; +view__54->addView(view__56); +auto* const view__57 = createValueLabel(CRect(240, 20, 320, 45), -1, "Frequency", kCenterText, 12); +view__54->addView(view__57); +auto* const view__58 = createStyledKnob(CRect(340, 45, 388, 93), kTagSetStretchedTuning, "", kCenterText, 14); +stretchedTuningSlider_ = view__58; +view__54->addView(view__58); +auto* const view__59 = createValueLabel(CRect(325, 20, 405, 45), -1, "Stretch", kCenterText, 12); +view__54->addView(view__59); +auto* const view__60 = createValueLabel(CRect(20, 20, 120, 45), -1, "Scala file", kCenterText, 12); +view__54->addView(view__60); +auto* const view__61 = createValueButton(CRect(20, 60, 120, 85), kTagLoadScalaFile, "DefaultScale", kCenterText, 12); +scalaFileButton_ = view__61; +view__54->addView(view__61); +auto* const view__62 = createValueMenu(CRect(165, 60, 200, 85), kTagSetScalaRootKey, "", kCenterText, 12); +scalaRootKeySlider_ = view__62; +view__54->addView(view__62); +auto* const view__63 = createValueMenu(CRect(200, 60, 230, 85), kTagSetScalaRootKey, "", kCenterText, 12); +scalaRootOctaveSlider_ = view__63; +view__54->addView(view__63); +auto* const view__64 = createResetSomethingButton(CRect(120, 60, 145, 85), kTagResetScalaFile, "", kCenterText, 12); +scalaResetButton_ = view__64; +view__54->addView(view__64); +auto* const view__65 = createTitleGroup(CRect(615, 160, 754, 260), -1, "Files", kCenterText, 12); +userFilesGroup_ = view__65; +view__48->addView(view__65); +auto* const view__66 = createValueLabel(CRect(20, 20, 120, 45), -1, "User SFZ folder", kCenterText, 12); +view__65->addView(view__66); +auto* const view__67 = createValueButton(CRect(20, 60, 120, 85), kTagChooseUserFilesDir, "DefaultPath", kCenterText, 12); +userFilesDirButton_ = view__67; +view__65->addView(view__67); +auto* const view__68 = createTitleGroup(CRect(525, 25, 720, 125), -1, "Quality", kCenterText, 12); +view__48->addView(view__68); +auto* const view__69 = createValueMenu(CRect(15, 60, 95, 85), kTagSetSampleQuality, "", kCenterText, 12); +sampleQualitySlider_ = view__69; +view__68->addView(view__69); +auto* const view__70 = createValueLabel(CRect(15, 20, 95, 45), -1, "Sample", kCenterText, 12); +view__68->addView(view__70); +auto* const view__71 = createValueLabel(CRect(100, 20, 180, 45), -1, "Oscillator", kCenterText, 12); +view__68->addView(view__71); +auto* const view__72 = createValueMenu(CRect(100, 60, 180, 85), kTagSetOscillatorQuality, "", kCenterText, 12); +oscillatorQualitySlider_ = view__72; +view__68->addView(view__72); +auto* const view__73 = createTitleGroup(CRect(35, 160, 140, 260), -1, "Appearance", kCenterText, 12); +view__48->addView(view__73); +auto* const view__74 = createOptionMenu(CRect(20, 60, 85, 85), kTagThemeMenu, "", kCenterText, 12); +themeMenu_ = view__74; +view__73->addView(view__74); +auto* const view__75 = createValueLabel(CRect(10, 25, 90, 50), -1, "Theme", kCenterText, 12); +view__73->addView(view__75); +auto* const view__76 = createPiano(CRect(5, 400, 795, 470), -1, "", kCenterText, 12); +piano_ = view__76; +view__0->addView(view__76); +auto* const view__77 = createLogicalGroup(CRect(5, 110, 795, 395), -1, "", kCenterText, 14); +subPanels_[kPanelGeneral] = view__77; +view__0->addView(view__77); +view__77->setVisible(false); diff --git a/plugins/lv2/CMakeLists.txt b/plugins/lv2/CMakeLists.txt index 828be7384..4bf680516 100644 --- a/plugins/lv2/CMakeLists.txt +++ b/plugins/lv2/CMakeLists.txt @@ -61,6 +61,12 @@ if(SFIZZ_LV2_UI) endif() endif() +# Define a preprocessor variable to indicate a build with UI enabled +if(SFIZZ_LV2_UI) + target_compile_definitions(${LV2PLUGIN_PRJ_NAME} PRIVATE "SFIZZ_LV2_UI=1") + target_compile_definitions(${LV2PLUGIN_PRJ_NAME}_ui PRIVATE "SFIZZ_LV2_UI=1") +endif() + # Remove the "lib" prefix, rename the target name and build it in the .lv build dir # /lv2/_lv2. to # /lv2/.lv2/. @@ -83,9 +89,6 @@ if(SFIZZ_LV2_UI) configure_file(${PROJECT_NAME}_ui.ttl.in ${PROJECT_BINARY_DIR}/${PROJECT_NAME}_ui.ttl) endif() configure_file(LICENSE.md.in ${PROJECT_BINARY_DIR}/LICENSE.md) -if(SFIZZ_USE_VCPKG OR SFIZZ_STATIC_DEPENDENCIES OR CMAKE_CXX_COMPILER_ID MATCHES "MSVC") - file(COPY "lgpl-3.0.txt" DESTINATION ${PROJECT_BINARY_DIR}) -endif() # Generate controllers.ttl sfizz_lv2_generate_controllers_ttl("${PROJECT_BINARY_DIR}/controllers.ttl") @@ -114,7 +117,8 @@ endif() # Installation if(NOT MSVC) install(DIRECTORY ${PROJECT_BINARY_DIR} DESTINATION ${LV2PLUGIN_INSTALL_DIR} - COMPONENT "lv2") + COMPONENT "lv2" + USE_SOURCE_PERMISSIONS) bundle_dylibs(lv2 "${LV2PLUGIN_INSTALL_DIR}/${PROJECT_NAME}.lv2/Contents/Binary/sfizz.so" COMPONENT "lv2") diff --git a/plugins/lv2/LICENSE.md.in b/plugins/lv2/LICENSE.md.in index bae47b17b..1d0b134e9 100644 --- a/plugins/lv2/LICENSE.md.in +++ b/plugins/lv2/LICENSE.md.in @@ -1,19 +1,12 @@ -The `sfizz` LV2 plugin is able to be built statically against all its components, -which is a desirable property for audio plugin hosts. These components include -`libsndfile` from Erik de Castro Lopo (http://www.mega-nerd.com/libsndfile). -`libsndfile` is distributed under the terms of the LGPL v2 or v3. As such, -statically-linked LV2 version of `sfizz` are to be distributed under the terms -of the LGPL-v3.0 license. You should be able to rebuild from source the sfizz LV2 -plugin using any `libsndfile` version you wish to use. - -See the file `lgpl-3.0.txt` or (https://www.gnu.org/licenses/lgpl-3.0.txt) for more -information about the LGPL v3 license. - Most of the code in the plug comes from various examples in the LV2 distribution and example plugins. As such, the source code of the LV2 plugin version of `sfizz` is distributed under the same terms as the LV2 specification, which is the ISC license. -Binaries of this LV2 plugin dynamically linked against e.g. a system installation -of `libsndfile` thus have to respect the terms of the ISC license and BSD 2-clause -license. Check the LICENSE.md at (@LV2PLUGIN_REPOSITORY@) file for more +Check the LICENSE.md at (@LV2PLUGIN_REPOSITORY@) file for more information about the license of the source code of the `sfizz` library and its contributors. + +The `sfizz` LV2 plugin can be optionally statically built against `libsndfile` +from Erik de Castro Lopo (http://www.mega-nerd.com/libsndfile). `libsndfile` +is distributed under the terms of the LGPL v2 or v3. If you distribute a +statically-linked LV2 version of `sfizz` built against `libsndfile`, you need +to respect the terms of the LGPL v2 or v3 license. diff --git a/plugins/lv2/lgpl-3.0.txt b/plugins/lv2/lgpl-3.0.txt deleted file mode 100644 index 0a041280b..000000000 --- a/plugins/lv2/lgpl-3.0.txt +++ /dev/null @@ -1,165 +0,0 @@ - GNU LESSER GENERAL PUBLIC LICENSE - Version 3, 29 June 2007 - - Copyright (C) 2007 Free Software Foundation, Inc. - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - - This version of the GNU Lesser General Public License incorporates -the terms and conditions of version 3 of the GNU General Public -License, supplemented by the additional permissions listed below. - - 0. Additional Definitions. - - As used herein, "this License" refers to version 3 of the GNU Lesser -General Public License, and the "GNU GPL" refers to version 3 of the GNU -General Public License. - - "The Library" refers to a covered work governed by this License, -other than an Application or a Combined Work as defined below. - - An "Application" is any work that makes use of an interface provided -by the Library, but which is not otherwise based on the Library. -Defining a subclass of a class defined by the Library is deemed a mode -of using an interface provided by the Library. - - A "Combined Work" is a work produced by combining or linking an -Application with the Library. The particular version of the Library -with which the Combined Work was made is also called the "Linked -Version". - - The "Minimal Corresponding Source" for a Combined Work means the -Corresponding Source for the Combined Work, excluding any source code -for portions of the Combined Work that, considered in isolation, are -based on the Application, and not on the Linked Version. - - The "Corresponding Application Code" for a Combined Work means the -object code and/or source code for the Application, including any data -and utility programs needed for reproducing the Combined Work from the -Application, but excluding the System Libraries of the Combined Work. - - 1. Exception to Section 3 of the GNU GPL. - - You may convey a covered work under sections 3 and 4 of this License -without being bound by section 3 of the GNU GPL. - - 2. Conveying Modified Versions. - - If you modify a copy of the Library, and, in your modifications, a -facility refers to a function or data to be supplied by an Application -that uses the facility (other than as an argument passed when the -facility is invoked), then you may convey a copy of the modified -version: - - a) under this License, provided that you make a good faith effort to - ensure that, in the event an Application does not supply the - function or data, the facility still operates, and performs - whatever part of its purpose remains meaningful, or - - b) under the GNU GPL, with none of the additional permissions of - this License applicable to that copy. - - 3. Object Code Incorporating Material from Library Header Files. - - The object code form of an Application may incorporate material from -a header file that is part of the Library. You may convey such object -code under terms of your choice, provided that, if the incorporated -material is not limited to numerical parameters, data structure -layouts and accessors, or small macros, inline functions and templates -(ten or fewer lines in length), you do both of the following: - - a) Give prominent notice with each copy of the object code that the - Library is used in it and that the Library and its use are - covered by this License. - - b) Accompany the object code with a copy of the GNU GPL and this license - document. - - 4. Combined Works. - - You may convey a Combined Work under terms of your choice that, -taken together, effectively do not restrict modification of the -portions of the Library contained in the Combined Work and reverse -engineering for debugging such modifications, if you also do each of -the following: - - a) Give prominent notice with each copy of the Combined Work that - the Library is used in it and that the Library and its use are - covered by this License. - - b) Accompany the Combined Work with a copy of the GNU GPL and this license - document. - - c) For a Combined Work that displays copyright notices during - execution, include the copyright notice for the Library among - these notices, as well as a reference directing the user to the - copies of the GNU GPL and this license document. - - d) Do one of the following: - - 0) Convey the Minimal Corresponding Source under the terms of this - License, and the Corresponding Application Code in a form - suitable for, and under terms that permit, the user to - recombine or relink the Application with a modified version of - the Linked Version to produce a modified Combined Work, in the - manner specified by section 6 of the GNU GPL for conveying - Corresponding Source. - - 1) Use a suitable shared library mechanism for linking with the - Library. A suitable mechanism is one that (a) uses at run time - a copy of the Library already present on the user's computer - system, and (b) will operate properly with a modified version - of the Library that is interface-compatible with the Linked - Version. - - e) Provide Installation Information, but only if you would otherwise - be required to provide such information under section 6 of the - GNU GPL, and only to the extent that such information is - necessary to install and execute a modified version of the - Combined Work produced by recombining or relinking the - Application with a modified version of the Linked Version. (If - you use option 4d0, the Installation Information must accompany - the Minimal Corresponding Source and Corresponding Application - Code. If you use option 4d1, you must provide the Installation - Information in the manner specified by section 6 of the GNU GPL - for conveying Corresponding Source.) - - 5. Combined Libraries. - - You may place library facilities that are a work based on the -Library side by side in a single library together with other library -facilities that are not Applications and are not covered by this -License, and convey such a combined library under terms of your -choice, if you do both of the following: - - a) Accompany the combined library with a copy of the same work based - on the Library, uncombined with any other library facilities, - conveyed under the terms of this License. - - b) Give prominent notice with the combined library that part of it - is a work based on the Library, and explaining where to find the - accompanying uncombined form of the same work. - - 6. Revised Versions of the GNU Lesser General Public License. - - The Free Software Foundation may publish revised and/or new versions -of the GNU Lesser General Public License from time to time. Such new -versions will be similar in spirit to the present version, but may -differ in detail to address new problems or concerns. - - Each version is given a distinguishing version number. If the -Library as you received it specifies that a certain numbered version -of the GNU Lesser General Public License "or any later version" -applies to it, you have the option of following the terms and -conditions either of that published version or of any later version -published by the Free Software Foundation. If the Library as you -received it does not specify a version number of the GNU Lesser -General Public License, you may choose any version of the GNU Lesser -General Public License ever published by the Free Software Foundation. - - If the Library as you received it specifies that a proxy can decide -whether future versions of the GNU Lesser General Public License shall -apply, that proxy's public statement of acceptance of any version is -permanent authorization for you to choose that version for the -Library. diff --git a/plugins/lv2/sfizz.cpp b/plugins/lv2/sfizz.cpp index ec8e15186..407ed8042 100644 --- a/plugins/lv2/sfizz.cpp +++ b/plugins/lv2/sfizz.cpp @@ -35,7 +35,7 @@ #include "sfizz_lv2.h" #include "sfizz_lv2_plugin.h" -#include "sfizz/import/ForeignInstrument.h" +#include "sfizz/import/sfizz_import.h" #include "plugin/InstrumentDescription.h" #include @@ -120,10 +120,12 @@ sfizz_lv2_map_required_uris(sfizz_plugin_t *self) self->sfizz_num_voices_uri = map->map(map->handle, SFIZZ__numVoices); self->sfizz_preload_size_uri = map->map(map->handle, SFIZZ__preloadSize); self->sfizz_oversampling_uri = map->map(map->handle, SFIZZ__oversampling); - self->sfizz_log_status_uri = map->map(map->handle, SFIZZ__logStatus); + self->sfizz_last_keyswitch_uri = map->map(map->handle, SFIZZ__lastKeyswitch); self->sfizz_log_status_uri = map->map(map->handle, SFIZZ__logStatus); self->sfizz_check_modification_uri = map->map(map->handle, SFIZZ__checkModification); self->sfizz_osc_blob_uri = map->map(map->handle, SFIZZ__OSCBlob); + self->sfizz_notify_uri = map->map(map->handle, SFIZZ__Notify); + self->sfizz_audio_level_uri = map->map(map->handle, SFIZZ__AudioLevel); self->time_position_uri = map->map(map->handle, LV2_TIME__Position); self->time_bar_uri = map->map(map->handle, LV2_TIME__bar); self->time_bar_beat_uri = map->map(map->handle, LV2_TIME__barBeat); @@ -200,9 +202,6 @@ connect_port(LV2_Handle instance, case SFIZZ_CONTROL: self->control_port = (const LV2_Atom_Sequence *)data; break; - case SFIZZ_NOTIFY: - self->notify_port = (LV2_Atom_Sequence *)data; - break; case SFIZZ_AUTOMATE: self->automate_port = (LV2_Atom_Sequence *)data; break; @@ -325,13 +324,21 @@ sfizz_lv2_receive_message(void* data, int delay, const char* path, const char* s sfizz_plugin_t *self = (sfizz_plugin_t *)data; + if (!strcmp(path, "/sw/last/current") && sig) + { + if (sig[0] == 'i') + self->last_keyswitch = args[0].i; + else if (sig[0] == 'N') + self->last_keyswitch = -1; + } + // transmit to UI as OSC blob uint8_t *osc_temp = self->osc_temp; uint32_t osc_size = sfizz_prepare_message(osc_temp, OSC_TEMP_SIZE, path, sig, args); if (osc_size > OSC_TEMP_SIZE) return; - LV2_Atom_Forge* forge = &self->forge_notify; + LV2_Atom_Forge* forge = &self->forge_automate; bool write_ok = lv2_atom_forge_frame_time(forge, 0) && lv2_atom_forge_atom(forge, osc_size, self->sfizz_osc_blob_uri) && @@ -439,7 +446,6 @@ instantiate(const LV2_Descriptor *descriptor, sfizz_lv2_map_required_uris(self); // Initialize the forge - lv2_atom_forge_init(&self->forge_notify, self->map); lv2_atom_forge_init(&self->forge_automate, self->map); lv2_atom_forge_init(&self->forge_secondary, self->map); @@ -478,7 +484,7 @@ instantiate(const LV2_Descriptor *descriptor, else { lv2_log_warning(&self->logger, - "No option array was given upon instantiation; will use default values\n."); + "No option array was given upon instantiation; will use default values.\n"); } // We need _some_ information on the block size @@ -532,6 +538,9 @@ activate(LV2_Handle instance) sfizz_set_samples_per_block(self->synth, self->max_block_size); sfizz_set_sample_rate(self->synth, self->sample_rate); self->must_update_midnam.store(1); +#if defined(SFIZZ_LV2_UI) + self->rms_follower.init(self->sample_rate); +#endif } static void @@ -542,13 +551,14 @@ deactivate(LV2_Handle instance) } static void -sfizz_lv2_send_file_path(sfizz_plugin_t *self, LV2_Atom_Forge* forge, LV2_URID urid, const char *path) +sfizz_lv2_send_file_path(sfizz_plugin_t *self, LV2_URID verb_uri, LV2_URID urid, const char *path) { LV2_Atom_Forge_Frame frame; + LV2_Atom_Forge* forge = &self->forge_automate; bool write_ok = lv2_atom_forge_frame_time(forge, 0) && - lv2_atom_forge_object(forge, &frame, 0, self->patch_set_uri) && + lv2_atom_forge_object(forge, &frame, 0, verb_uri) && lv2_atom_forge_key(forge, self->patch_property_uri) && lv2_atom_forge_urid(forge, urid) && lv2_atom_forge_key(forge, self->patch_value_uri) && @@ -559,14 +569,15 @@ sfizz_lv2_send_file_path(sfizz_plugin_t *self, LV2_Atom_Forge* forge, LV2_URID u } static void -sfizz_lv2_send_controller(sfizz_plugin_t *self, LV2_Atom_Forge* forge, unsigned cc, float value) +sfizz_lv2_send_controller(sfizz_plugin_t *self, LV2_URID verb_uri, unsigned cc, float value) { LV2_URID urid = sfizz_lv2_ccmap_map(self->ccmap, int(cc)); LV2_Atom_Forge_Frame frame; + LV2_Atom_Forge* forge = &self->forge_automate; bool write_ok = lv2_atom_forge_frame_time(forge, 0) && - lv2_atom_forge_object(forge, &frame, 0, self->patch_set_uri) && + lv2_atom_forge_object(forge, &frame, 0, verb_uri) && lv2_atom_forge_key(forge, self->patch_property_uri) && lv2_atom_forge_urid(forge, urid) && lv2_atom_forge_key(forge, self->patch_value_uri) && @@ -576,6 +587,31 @@ sfizz_lv2_send_controller(sfizz_plugin_t *self, LV2_Atom_Forge* forge, unsigned lv2_atom_forge_pop(forge, &frame); } +#if defined(SFIZZ_LV2_UI) +static void +sfizz_lv2_send_levels(sfizz_plugin_t *self, float left, float right) +{ + const float levels[] = {left, right}; + uint32_t num_levels = sizeof(levels) / sizeof(levels[0]); + + LV2_Atom_Forge* forge = &self->forge_automate; + bool write_ok = lv2_atom_forge_frame_time(forge, 0); + + LV2_Atom_Vector *vector = nullptr; + if (write_ok) { + LV2_Atom_Forge_Ref ref = lv2_atom_forge_vector(forge, sizeof(float), self->atom_float_uri, num_levels, levels); + write_ok = ref; + if (write_ok) + vector = (LV2_Atom_Vector *)lv2_atom_forge_deref(forge, ref); + } + + if (write_ok) + vector->atom.type = self->sfizz_audio_level_uri; + + (void)write_ok; +} +#endif + static void sfizz_lv2_handle_atom_object(sfizz_plugin_t *self, int delay, const LV2_Atom_Object *obj) { @@ -618,7 +654,7 @@ sfizz_lv2_handle_atom_object(sfizz_plugin_t *self, int delay, const LV2_Atom_Obj if (cc != -1) { if (atom->type == self->atom_float_uri && atom->size == sizeof(float)) { float value = *(const float *)LV2_ATOM_BODY_CONST(atom); - sfizz_send_hdcc(self->synth, delay, cc, value); + sfizz_automate_hdcc(self->synth, delay, cc, value); self->cc_current[cc] = value; self->ccauto[cc] = absl::nullopt; } @@ -656,6 +692,12 @@ sfizz_lv2_handle_atom_object(sfizz_plugin_t *self, int delay, const LV2_Atom_Obj } } +static bool +sfizz_is_sustain_or_sostenuto(sfizz_plugin_t* self, unsigned cc) +{ + return (self->sustain_or_sostenuto[cc / 8] & (1 << (cc % 8))); +} + static void sfizz_lv2_process_midi_event(sfizz_plugin_t *self, const LV2_Atom_Event *ev) { @@ -677,23 +719,47 @@ sfizz_lv2_process_midi_event(sfizz_plugin_t *self, const LV2_Atom_Event *ev) (int)msg[1], msg[2]); break; - // Note(jpc) CC must be mapped by host, not handled here. - // See LV2 midi:binding. -#if defined(SFIZZ_LV2_PSA) case LV2_MIDI_MSG_CONTROLLER: { unsigned cc = msg[1]; float value = float(msg[2]) * (1.0f / 127.0f); - sfizz_send_hdcc(self->synth, - (int)ev->time.frames, - (int)cc, - value); - self->cc_current[cc] = value; - self->ccauto[cc] = value; - self->have_ccauto = true; + + // Send + if (sfizz_is_sustain_or_sostenuto(self, cc)) { + sfizz_automate_hdcc(self->synth, + (int)ev->time.frames, + (int)cc, + value); + break; + } + +// Note(jpc) CC must be mapped by host, not handled here. +// See LV2 midi:binding. +#if defined(SFIZZ_LV2_PSA) + switch (cc) + { + default: + { + sfizz_automate_hdcc(self->synth, + (int)ev->time.frames, + (int)cc, + value); + self->cc_current[cc] = value; + self->ccauto[cc] = value; + self->have_ccauto = true; + } + break; + case LV2_MIDI_CTL_ALL_NOTES_OFF: + // TODO implemented as all-sound-off + sfizz_all_sound_off(self->synth); + break; + case LV2_MIDI_CTL_ALL_SOUNDS_OFF: + sfizz_all_sound_off(self->synth); + break; + } +#endif } break; -#endif case LV2_MIDI_MSG_CHANNEL_PRESSURE: sfizz_send_channel_aftertouch(self->synth, (int)ev->time.frames, @@ -829,7 +895,7 @@ static void run(LV2_Handle instance, uint32_t sample_count) { sfizz_plugin_t *self = (sfizz_plugin_t *)instance; - assert(self->control_port && self->notify_port && self->automate_port); + assert(self->control_port && self->automate_port); if (!spin_mutex_trylock(self->synth_mutex)) { @@ -839,15 +905,10 @@ run(LV2_Handle instance, uint32_t sample_count) } // Set up dedicated forges to write on their respective ports. - const size_t notify_capacity = self->notify_port->atom.size; - lv2_atom_forge_set_buffer(&self->forge_notify, (uint8_t *)self->notify_port, notify_capacity); const size_t automate_capacity = self->automate_port->atom.size; lv2_atom_forge_set_buffer(&self->forge_automate, (uint8_t *)self->automate_port, automate_capacity); // Start sequences in the respective output ports. - LV2_Atom_Forge_Frame notify_frame; - if (!lv2_atom_forge_sequence_head(&self->forge_notify, ¬ify_frame, 0)) - assert(false); LV2_Atom_Forge_Frame automate_frame; if (!lv2_atom_forge_sequence_head(&self->forge_automate, &automate_frame, 0)) assert(false); @@ -871,25 +932,25 @@ run(LV2_Handle instance, uint32_t sample_count) lv2_atom_object_get(obj, self->patch_property_uri, &property, 0); if (!property) // Send the full state { - sfizz_lv2_send_file_path(self, &self->forge_notify, self->sfizz_sfz_file_uri, self->sfz_file_path); - sfizz_lv2_send_file_path(self, &self->forge_notify, self->sfizz_scala_file_uri, self->scala_file_path); + sfizz_lv2_send_file_path(self, self->sfizz_notify_uri, self->sfizz_sfz_file_uri, self->sfz_file_path); + sfizz_lv2_send_file_path(self, self->sfizz_notify_uri, self->sfizz_scala_file_uri, self->scala_file_path); for (unsigned cc = 0; cc < sfz::config::numCCs; ++cc) - sfizz_lv2_send_controller(self, &self->forge_notify, cc, self->cc_current[cc]); + sfizz_lv2_send_controller(self, self->sfizz_notify_uri, cc, self->cc_current[cc]); } else if (property->body == self->sfizz_sfz_file_uri) { - sfizz_lv2_send_file_path(self, &self->forge_notify, self->sfizz_sfz_file_uri, self->sfz_file_path); + sfizz_lv2_send_file_path(self, self->sfizz_notify_uri, self->sfizz_sfz_file_uri, self->sfz_file_path); } else if (property->body == self->sfizz_scala_file_uri) { - sfizz_lv2_send_file_path(self, &self->forge_notify, self->sfizz_scala_file_uri, self->scala_file_path); + sfizz_lv2_send_file_path(self, self->sfizz_notify_uri, self->sfizz_scala_file_uri, self->scala_file_path); } else { int cc = sfizz_lv2_ccmap_unmap(self->ccmap, property->body); if (cc != -1) - sfizz_lv2_send_controller(self, &self->forge_notify, unsigned(cc), self->cc_current[cc]); + sfizz_lv2_send_controller(self, self->sfizz_notify_uri, unsigned(cc), self->cc_current[cc]); } } else if (obj->body.otype == self->time_position_uri) @@ -1037,7 +1098,7 @@ run(LV2_Handle instance, uint32_t sample_count) for (unsigned cc = 0; cc < sfz::config::numCCs; ++cc) { absl::optional value = self->ccauto[cc]; if (value) { - sfizz_lv2_send_controller(self, &self->forge_automate, cc, *value); + sfizz_lv2_send_controller(self, self->patch_set_uri, cc, *value); self->ccauto[cc] = absl::nullopt; } } @@ -1046,7 +1107,20 @@ run(LV2_Handle instance, uint32_t sample_count) spin_mutex_unlock(self->synth_mutex); - lv2_atom_forge_pop(&self->forge_notify, ¬ify_frame); +#if defined(SFIZZ_LV2_UI) + if (self->ui_active) + { + self->rms_follower.process(self->output_buffers[0], self->output_buffers[1], sample_count); + const simde__m128 rms = self->rms_follower.getRMS(); + const float *levels = (const float *)&rms; + sfizz_lv2_send_levels(self, levels[0], levels[1]); + } + else + { + self->rms_follower.clear(); + } +#endif + lv2_atom_forge_pop(&self->forge_automate, &automate_frame); } @@ -1168,7 +1242,7 @@ sfizz_lv2_update_sfz_info(sfizz_plugin_t *self) // const InstrumentDescription desc = parseDescriptionBlob(blob); for (unsigned cc = 0; cc < sfz::config::numCCs; ++cc) { - if (desc.ccUsed.test(cc)) { + if (desc.ccUsed.test(cc) && !desc.sustainOrSostenuto.test(cc)) { // Mark all the used CCs for automation with default values self->ccauto[cc] = desc.ccDefault[cc]; self->have_ccauto = true; @@ -1176,13 +1250,14 @@ sfizz_lv2_update_sfz_info(sfizz_plugin_t *self) self->cc_current[cc] = desc.ccDefault[cc]; } } + + memcpy(self->sustain_or_sostenuto, desc.sustainOrSostenuto.data(), + sizeof(self->sustain_or_sostenuto)); } static bool sfizz_lv2_load_file(sfizz_plugin_t *self, const char *file_path) { - bool status; - char buf[MAX_PATH_SIZE]; if (file_path[0] == '\0') { @@ -1191,18 +1266,7 @@ sfizz_lv2_load_file(sfizz_plugin_t *self, const char *file_path) } /// - const sfz::InstrumentFormatRegistry& formatRegistry = sfz::InstrumentFormatRegistry::getInstance(); - const sfz::InstrumentFormat* format = formatRegistry.getMatchingFormat(file_path); - - if (!format) - status = sfizz_load_file(self->synth, file_path); - else { - auto importer = format->createImporter(); - std::string virtual_path = std::string(file_path) + ".sfz"; - std::string sfz_text = importer->convertToSfz(file_path); - status = sfizz_load_string(self->synth, virtual_path.c_str(), sfz_text.c_str()); - } - + bool status = sfizz_load_or_import_file(self->synth, file_path, nullptr); sfizz_lv2_update_sfz_info(self); sfizz_lv2_update_file_info(self, file_path); return status; @@ -1246,11 +1310,9 @@ restore(LV2_Handle instance, } // Set default values + self->last_keyswitch = -1; sfizz_lv2_get_default_sfz_path(self, self->sfz_file_path, MAX_PATH_SIZE); sfizz_lv2_get_default_scala_path(self, self->scala_file_path, MAX_PATH_SIZE); - self->num_voices = DEFAULT_VOICES; - self->preload_size = DEFAULT_PRELOAD; - self->oversampling = DEFAULT_OVERSAMPLING; // Fetch back the saved file path, if any size_t size; @@ -1299,26 +1361,11 @@ restore(LV2_Handle instance, } } - value = retrieve(handle, self->sfizz_num_voices_uri, &size, &type, &val_flags); - if (value) - { - int num_voices = *(const int *)value; - if (num_voices > 0 && num_voices <= MAX_VOICES) - self->num_voices = num_voices; - } - - value = retrieve(handle, self->sfizz_preload_size_uri, &size, &type, &val_flags); - if (value) - { - unsigned int preload_size = *(const unsigned int *)value; - self->preload_size = preload_size; - } - - value = retrieve(handle, self->sfizz_oversampling_uri, &size, &type, &val_flags); + value = retrieve(handle, self->sfizz_last_keyswitch_uri, &size, &type, &val_flags); if (value) { - sfizz_oversampling_factor_t oversampling = *(const sfizz_oversampling_factor_t *)value; - self->oversampling = oversampling; + int last_keyswitch = *(const int*)value; + self->last_keyswitch = last_keyswitch; } // Collect all CC values present in the state @@ -1361,21 +1408,12 @@ restore(LV2_Handle instance, "[sfizz] Error while restoring the scale %s\n", self->scala_file_path); } - lv2_log_note(&self->logger, "[sfizz] Restoring the number of voices to %d\n", self->num_voices); - sfizz_set_num_voices(self->synth, self->num_voices); - - lv2_log_note(&self->logger, "[sfizz] Restoring the preload size to %d\n", self->preload_size); - sfizz_set_preload_size(self->synth, self->preload_size); - - lv2_log_note(&self->logger, "[sfizz] Restoring the oversampling to %d\n", self->oversampling); - sfizz_set_oversampling_factor(self->synth, self->oversampling); - // Override default automation values with these from the state file for (unsigned cc = 0; cc < sfz::config::numCCs; ++cc) { absl::optional value = cc_values[cc]; if (value) { // Set CC in the synth - sfizz_send_hdcc(self->synth, 0, int(cc), *value); + sfizz_automate_hdcc(self->synth, 0, int(cc), *value); // Mark CCs for automation with state values self->ccauto[cc] = *value; self->have_ccauto = true; @@ -1384,6 +1422,11 @@ restore(LV2_Handle instance, } } + if (self->last_keyswitch >= 0 && self->last_keyswitch <= 127) { + sfizz_send_hd_note_on(self->synth, 0, self->last_keyswitch, 1.0f); + sfizz_send_hd_note_off(self->synth, 1, self->last_keyswitch, 0.0f); + } + spin_mutex_unlock(self->synth_mutex); return status; @@ -1447,38 +1490,23 @@ save(LV2_Handle instance, if (map_path) free_path->free_path(free_path->handle, (char *)path); - // Save the number of voices - store(handle, - self->sfizz_num_voices_uri, - &self->num_voices, - sizeof(int), - self->atom_int_uri, - LV2_STATE_IS_POD); - - // Save the preload size - store(handle, - self->sfizz_preload_size_uri, - &self->preload_size, - sizeof(unsigned int), - self->atom_int_uri, - LV2_STATE_IS_POD); - - // Save the preload size - store(handle, - self->sfizz_oversampling_uri, - &self->oversampling, - sizeof(int), - self->atom_int_uri, - LV2_STATE_IS_POD); - // Save the CCs (used only) self->sfz_blob_mutex->lock(); const InstrumentDescription desc = parseDescriptionBlob( absl::string_view((const char*)self->sfz_blob_data, self->sfz_blob_size)); self->sfz_blob_mutex->unlock(); + if (self->last_keyswitch >= 0 && self->last_keyswitch <= 127) { + store(handle, + self->sfizz_last_keyswitch_uri, + &self->last_keyswitch, + sizeof(int), + self->atom_int_uri, + LV2_STATE_IS_POD); + } + for (unsigned cc = 0; cc < sfz::config::numCCs; ++cc) { - if (desc.ccUsed.test(cc)) { + if (desc.ccUsed.test(cc) && !desc.sustainOrSostenuto.test(cc)) { LV2_URID urid = sfizz_lv2_ccmap_map(self->ccmap, int(cc)); store(handle, urid, diff --git a/plugins/lv2/sfizz.ttl.in b/plugins/lv2/sfizz.ttl.in index 38868a564..fd8609903 100644 --- a/plugins/lv2/sfizz.ttl.in +++ b/plugins/lv2/sfizz.ttl.in @@ -23,6 +23,13 @@ midnam:interface a lv2:ExtensionData . midnam:update a lv2:Feature . +<@LV2PLUGIN_URI@#stereo_output> + a pg:StereoGroup, pg:OutputGroup ; + lv2:symbol "stereo_output" ; + lv2:name "Stereo output", + "Sortie stéréo"@fr , + "Uscita stereo"@it . + <@LV2PLUGIN_URI@#config> a pg:Group ; lv2:symbol "config" ; @@ -90,10 +97,12 @@ midnam:update a lv2:Feature . patch:writable <@LV2PLUGIN_URI@:sfzfile> , <@LV2PLUGIN_URI@:tuningfile> ; + pg:mainOutput <@LV2PLUGIN_URI@#stereo_output> ; + lv2:port [ a lv2:InputPort, atom:AtomPort ; atom:bufferType atom:Sequence ; - atom:supports patch:Message, midi:MidiEvent, time:Position, <@LV2PLUGIN_URI@:OSCBlob> ; + atom:supports patch:Message, midi:MidiEvent, time:Position, <@LV2PLUGIN_URI@:OSCBlob>, <@LV2PLUGIN_URI@:Notify> ; lv2:designation lv2:control ; lv2:index 0 ; lv2:symbol "control" ; @@ -104,38 +113,33 @@ midnam:update a lv2:Feature . a lv2:OutputPort, atom:AtomPort ; atom:bufferType atom:Sequence ; atom:supports patch:Message, <@LV2PLUGIN_URI@:OSCBlob> ; - lv2:index 1 ; - lv2:symbol "notify" ; - lv2:name "Notify", - "Notification"@fr ; - rsz:minimumSize 524288 ; - ] , [ - a lv2:OutputPort, atom:AtomPort ; - atom:bufferType atom:Sequence ; - atom:supports patch:Message ; lv2:designation lv2:control ; - lv2:index 2 ; + lv2:index 1 ; lv2:symbol "automate" ; lv2:name "Automate", "Automatisation"@fr ; rsz:minimumSize 524288 ; ] , [ a lv2:AudioPort, lv2:OutputPort ; - lv2:index 3 ; + lv2:index 2 ; lv2:symbol "out_left" ; lv2:name "Left Output", "Sortie gauche"@fr , - "Uscita Sinistra"@it + "Uscita Sinistra"@it ; + pg:group <@LV2PLUGIN_URI@#stereo_output> ; + lv2:designation pg:left ] , [ a lv2:AudioPort, lv2:OutputPort ; - lv2:index 4 ; + lv2:index 3 ; lv2:symbol "out_right" ; lv2:name "Right Output", "Sortie droite"@fr , - "Uscita Destra"@it + "Uscita Destra"@it ; + pg:group <@LV2PLUGIN_URI@#stereo_output> ; + lv2:designation pg:right ] , [ a lv2:InputPort, lv2:ControlPort ; - lv2:index 5 ; + lv2:index 4 ; lv2:symbol "volume" ; lv2:name "Volume" ; lv2:default 0.0 ; @@ -144,7 +148,7 @@ midnam:update a lv2:Feature . units:unit units:db ] , [ a lv2:InputPort, lv2:ControlPort ; - lv2:index 6 ; + lv2:index 5 ; lv2:symbol "num_voices" ; lv2:name "Polyphony", "Polyphonie"@fr , @@ -189,7 +193,7 @@ midnam:update a lv2:Feature . ] ; ] , [ a lv2:InputPort, lv2:ControlPort ; - lv2:index 7 ; + lv2:index 6 ; lv2:symbol "oversampling" ; lv2:name "Oversampling factor", "Facteur de suréchantillonnage"@fr , @@ -224,7 +228,7 @@ midnam:update a lv2:Feature . ] ; ] , [ a lv2:InputPort, lv2:ControlPort ; - lv2:index 8 ; + lv2:index 7 ; lv2:symbol "preload_size" ; lv2:name "Preload size", "Taille préchargée"@fr , @@ -267,7 +271,7 @@ midnam:update a lv2:Feature . ] ; ] , [ a lv2:InputPort, lv2:ControlPort ; - lv2:index 9 ; + lv2:index 8 ; lv2:symbol "freewheeling" ; lv2:name "Freewheeling", "En roue libre (freewheeling)"@fr , @@ -279,7 +283,7 @@ midnam:update a lv2:Feature . lv2:maximum 1 ; ] , [ a lv2:InputPort, lv2:ControlPort ; - lv2:index 10 ; + lv2:index 9 ; lv2:symbol "scala_root_key" ; lv2:name "Scala root key", "Tonalité de base Scala"@fr , @@ -292,7 +296,7 @@ midnam:update a lv2:Feature . units:unit units:midiNote ] , [ a lv2:InputPort, lv2:ControlPort ; - lv2:index 11 ; + lv2:index 10 ; lv2:symbol "tuning_frequency" ; lv2:name "Tuning frequency", "Fréquence d'accordage"@fr , @@ -304,7 +308,7 @@ midnam:update a lv2:Feature . units:unit units:hz ] , [ a lv2:InputPort, lv2:ControlPort ; - lv2:index 12 ; + lv2:index 11 ; lv2:symbol "stretched_tuning" ; lv2:name "Stretched tuning", "Accordage étiré"@fr , @@ -316,7 +320,7 @@ midnam:update a lv2:Feature . units:unit units:coef ] , [ a lv2:InputPort, lv2:ControlPort ; - lv2:index 13 ; + lv2:index 12 ; lv2:symbol "sample_quality" ; lv2:name "Sample quality", "Qualité des échantillons"@fr , @@ -327,7 +331,7 @@ midnam:update a lv2:Feature . lv2:maximum 10.0 ] , [ a lv2:InputPort, lv2:ControlPort ; - lv2:index 14 ; + lv2:index 13 ; lv2:symbol "oscillator_quality" ; lv2:name "Oscillator quality", "Qualité des oscillateurs"@fr , @@ -338,7 +342,7 @@ midnam:update a lv2:Feature . lv2:maximum 3.0 ] , [ a lv2:OutputPort, lv2:ControlPort ; - lv2:index 15 ; + lv2:index 14 ; lv2:symbol "active_voices" ; lv2:name "Active voices", "Voix utilisées"@fr ; @@ -349,7 +353,7 @@ midnam:update a lv2:Feature . lv2:maximum 256 ; ] , [ a lv2:OutputPort, lv2:ControlPort ; - lv2:index 16 ; + lv2:index 15 ; lv2:symbol "num_curves" ; lv2:name "Number of curves", "Nombre de courbes"@fr ; @@ -360,7 +364,7 @@ midnam:update a lv2:Feature . lv2:maximum 65535 ; ] , [ a lv2:OutputPort, lv2:ControlPort ; - lv2:index 17 ; + lv2:index 16 ; lv2:symbol "num_masters" ; lv2:name "Number of masters", "Nombre de maîtres"@fr ; @@ -371,7 +375,7 @@ midnam:update a lv2:Feature . lv2:maximum 65535 ; ] , [ a lv2:OutputPort, lv2:ControlPort ; - lv2:index 18 ; + lv2:index 17 ; lv2:symbol "num_groups" ; lv2:name "Number of groups", "Nombre de groupes"@fr ; @@ -382,7 +386,7 @@ midnam:update a lv2:Feature . lv2:maximum 65535 ; ] , [ a lv2:OutputPort, lv2:ControlPort ; - lv2:index 19 ; + lv2:index 18 ; lv2:symbol "num_regions" ; lv2:name "Number of regions", "Nombre de régions"@fr ; @@ -393,7 +397,7 @@ midnam:update a lv2:Feature . lv2:maximum 65535 ; ] , [ a lv2:OutputPort, lv2:ControlPort ; - lv2:index 20 ; + lv2:index 19 ; lv2:symbol "num_samples" ; lv2:name "Number of samples", "Nombre d'échantillons"@fr ; diff --git a/plugins/lv2/sfizz_lv2.h b/plugins/lv2/sfizz_lv2.h index 51a53896b..c9935ec08 100644 --- a/plugins/lv2/sfizz_lv2.h +++ b/plugins/lv2/sfizz_lv2.h @@ -39,35 +39,38 @@ #define SFIZZ__numVoices SFIZZ_URI ":" "numvoices" #define SFIZZ__preloadSize SFIZZ_URI ":" "preload_size" #define SFIZZ__oversampling SFIZZ_URI ":" "oversampling" +#define SFIZZ__lastKeyswitch SFIZZ_URI ":" "last_keyswitch" // These ones are just for the worker #define SFIZZ__logStatus SFIZZ_URI ":" "log_status" #define SFIZZ__checkModification SFIZZ_URI ":" "check_modification" // OSC atoms #define SFIZZ__OSCBlob SFIZZ_URI ":" "OSCBlob" +#define SFIZZ__Notify SFIZZ_URI ":" "Notify" +// Level atoms +#define SFIZZ__AudioLevel SFIZZ_URI ":" "AudioLevel" enum { SFIZZ_CONTROL = 0, - SFIZZ_NOTIFY = 1, - SFIZZ_AUTOMATE = 2, - SFIZZ_LEFT = 3, - SFIZZ_RIGHT = 4, - SFIZZ_VOLUME = 5, - SFIZZ_POLYPHONY = 6, - SFIZZ_OVERSAMPLING = 7, - SFIZZ_PRELOAD = 8, - SFIZZ_FREEWHEELING = 9, - SFIZZ_SCALA_ROOT_KEY = 10, - SFIZZ_TUNING_FREQUENCY = 11, - SFIZZ_STRETCH_TUNING = 12, - SFIZZ_SAMPLE_QUALITY = 13, - SFIZZ_OSCILLATOR_QUALITY = 14, - SFIZZ_ACTIVE_VOICES = 15, - SFIZZ_NUM_CURVES = 16, - SFIZZ_NUM_MASTERS = 17, - SFIZZ_NUM_GROUPS = 18, - SFIZZ_NUM_REGIONS = 19, - SFIZZ_NUM_SAMPLES = 20, + SFIZZ_AUTOMATE = 1, + SFIZZ_LEFT = 2, + SFIZZ_RIGHT = 3, + SFIZZ_VOLUME = 4, + SFIZZ_POLYPHONY = 5, + SFIZZ_OVERSAMPLING = 6, + SFIZZ_PRELOAD = 7, + SFIZZ_FREEWHEELING = 8, + SFIZZ_SCALA_ROOT_KEY = 9, + SFIZZ_TUNING_FREQUENCY = 10, + SFIZZ_STRETCH_TUNING = 11, + SFIZZ_SAMPLE_QUALITY = 12, + SFIZZ_OSCILLATOR_QUALITY = 13, + SFIZZ_ACTIVE_VOICES = 14, + SFIZZ_NUM_CURVES = 15, + SFIZZ_NUM_MASTERS = 16, + SFIZZ_NUM_GROUPS = 17, + SFIZZ_NUM_REGIONS = 18, + SFIZZ_NUM_SAMPLES = 19, }; // For use with instance-access @@ -89,6 +92,10 @@ bool sfizz_lv2_fetch_description( sfizz_plugin_t *self, const int *serial, uint8_t **descp, uint32_t *sizep, int *serialp); +#if defined(SFIZZ_LV2_UI) +void sfizz_lv2_set_ui_active(sfizz_plugin_t *self, bool ui_active); +#endif + // Mapping URID to CC and vice-versa struct sfizz_lv2_ccmap; sfizz_lv2_ccmap *sfizz_lv2_ccmap_create(LV2_URID_Map* map); diff --git a/plugins/lv2/sfizz_lv2_common.cpp b/plugins/lv2/sfizz_lv2_common.cpp index 7881db1d6..30bf4b63f 100644 --- a/plugins/lv2/sfizz_lv2_common.cpp +++ b/plugins/lv2/sfizz_lv2_common.cpp @@ -31,6 +31,13 @@ bool sfizz_lv2_fetch_description( return true; } +#if defined(SFIZZ_LV2_UI) +void sfizz_lv2_set_ui_active(sfizz_plugin_t *self, bool ui_active) +{ + self->ui_active = ui_active; +} +#endif + struct sfizz_lv2_ccmap { LV2_URID *cc_to_urid; int *urid_to_cc; diff --git a/plugins/lv2/sfizz_lv2_plugin.h b/plugins/lv2/sfizz_lv2_plugin.h index 2eed6d529..fad8342ac 100644 --- a/plugins/lv2/sfizz_lv2_plugin.h +++ b/plugins/lv2/sfizz_lv2_plugin.h @@ -7,6 +7,9 @@ #pragma once #include "sfizz_lv2.h" +#if defined(SFIZZ_LV2_UI) +#include "plugin/RMSFollower.h" +#endif #include #include #include @@ -31,7 +34,6 @@ struct sfizz_plugin_t // Ports const LV2_Atom_Sequence *control_port {}; - LV2_Atom_Sequence *notify_port {}; LV2_Atom_Sequence *automate_port {}; float *output_buffers[2] {}; const float *volume_port {}; @@ -52,7 +54,6 @@ struct sfizz_plugin_t float *num_samples_port {}; // Atom forge - LV2_Atom_Forge forge_notify {}; ///< Forge for writing notification atoms in run thread LV2_Atom_Forge forge_automate {}; ///< Forge for writing automation atoms in run thread LV2_Atom_Forge forge_secondary {}; ///< Forge for writing into other buffers @@ -85,10 +86,13 @@ struct sfizz_plugin_t LV2_URID sfizz_num_voices_uri {}; LV2_URID sfizz_preload_size_uri {}; LV2_URID sfizz_oversampling_uri {}; + LV2_URID sfizz_last_keyswitch_uri {}; LV2_URID sfizz_log_status_uri {}; LV2_URID sfizz_check_modification_uri {}; LV2_URID sfizz_active_voices_uri {}; LV2_URID sfizz_osc_blob_uri {}; + LV2_URID sfizz_notify_uri {}; + LV2_URID sfizz_audio_level_uri {}; LV2_URID time_position_uri {}; LV2_URID time_bar_uri {}; LV2_URID time_bar_beat_uri {}; @@ -117,6 +121,7 @@ struct sfizz_plugin_t float sample_rate {}; std::atomic must_update_midnam {}; volatile bool must_automate_cc {}; + int last_keyswitch { -1 }; // Current instrument description std::mutex *sfz_blob_mutex {}; @@ -124,6 +129,9 @@ struct sfizz_plugin_t const uint8_t *volatile sfz_blob_data {}; volatile uint32_t sfz_blob_size {}; + // Sostenuto or sustain + char sustain_or_sostenuto[16] {}; + // Current CC values in the synth (synchronized by `synth_mutex`) // updated by hdcc or file load float *cc_current {}; @@ -145,4 +153,10 @@ struct sfizz_plugin_t // OSC uint8_t osc_temp[OSC_TEMP_SIZE] {}; + +#if defined(SFIZZ_LV2_UI) + // UI + volatile bool ui_active = false; + RMSFollower rms_follower; +#endif }; diff --git a/plugins/lv2/sfizz_ui.cpp b/plugins/lv2/sfizz_ui.cpp index 93a90813b..eaf5219db 100644 --- a/plugins/lv2/sfizz_ui.cpp +++ b/plugins/lv2/sfizz_ui.cpp @@ -101,7 +101,7 @@ struct sfizz_ui_t : EditorController, VSTGUIEditorInterface { /// VSTGUIEditorInterface CFrame* getFrame() const override { return uiFrame.get(); } - + int32_t getKnobMode () const override { return kLinearMode; } LV2_Atom_Forge atom_forge; LV2_URID atom_event_transfer_uri; LV2_URID atom_object_uri; @@ -116,6 +116,8 @@ struct sfizz_ui_t : EditorController, VSTGUIEditorInterface { LV2_URID sfizz_sfz_file_uri; LV2_URID sfizz_scala_file_uri; LV2_URID sfizz_osc_blob_uri; + LV2_URID sfizz_notify_uri; + LV2_URID sfizz_audio_level_uri; std::unique_ptr ccmap; uint8_t osc_temp[OSC_TEMP_SIZE]; @@ -217,6 +219,8 @@ instantiate(const LV2UI_Descriptor *descriptor, self->sfizz_sfz_file_uri = map->map(map->handle, SFIZZ__sfzFile); self->sfizz_scala_file_uri = map->map(map->handle, SFIZZ__tuningfile); self->sfizz_osc_blob_uri = map->map(map->handle, SFIZZ__OSCBlob); + self->sfizz_notify_uri = map->map(map->handle, SFIZZ__Notify); + self->sfizz_audio_level_uri = map->map(map->handle, SFIZZ__AudioLevel); self->ccmap.reset(sfizz_lv2_ccmap_create(map)); // set up the resource path @@ -287,6 +291,7 @@ static void cleanup(LV2UI_Handle ui) { sfizz_ui_t *self = (sfizz_ui_t *)ui; + sfizz_lv2_set_ui_active(self->plugin, false); delete self; } @@ -391,6 +396,27 @@ port_event(LV2UI_Handle ui, if (sfizz_extract_message(LV2_ATOM_BODY_CONST(atom), atom->size, buffer, sizeof(buffer), &path, &sig, &args) > 0) self->uiReceiveMessage(path, sig, args); } + else if (atom->type == self->sfizz_audio_level_uri) { + const LV2_Atom_Vector *vector = reinterpret_cast(atom); + + if (vector->body.child_type == self->atom_float_uri && + vector->body.child_size == sizeof(float)) + { + const uint8_t *vec_body = reinterpret_cast( + LV2_ATOM_CONTENTS_CONST(LV2_Atom_Vector, vector)); + const uint8_t *vec_end = reinterpret_cast( + LV2_ATOM_BODY_CONST(&vector->atom)) + vector->atom.size; + + const float *levels = reinterpret_cast(vec_body); + uint32_t count = static_cast((vec_end - vec_body) / sizeof(float)); + + float left = (count > 0) ? levels[0] : 0.0f; + float right = (count > 1) ? levels[1] : 0.0f; + + self->uiReceiveValue(EditId::LeftLevel, left); + self->uiReceiveValue(EditId::RightLevel, right); + } + } } (void)buffer_size; @@ -421,7 +447,7 @@ sfizz_ui_update_description(sfizz_ui_t *self, const InstrumentDescription& desc) } for (unsigned cc = 0; cc < sfz::config::numCCs; ++cc) { - bool ccUsed = desc.ccUsed.test(cc); + bool ccUsed = desc.ccUsed.test(cc) && !desc.sustainOrSostenuto.test(cc); self->uiReceiveValue(editIdForCCUsed(cc), float(ccUsed)); if (ccUsed) { self->uiReceiveValue(editIdForCCDefault(cc), desc.ccDefault[cc]); @@ -466,6 +492,8 @@ idle(LV2UI_Handle ui) (void)self; #endif + sfizz_lv2_set_ui_active(self->plugin, self->uiFrame->isVisible()); + return 0; } diff --git a/plugins/lv2/sfizz_ui.ttl.in b/plugins/lv2/sfizz_ui.ttl.in index 06ee2e319..a0150653b 100644 --- a/plugins/lv2/sfizz_ui.ttl.in +++ b/plugins/lv2/sfizz_ui.ttl.in @@ -12,4 +12,4 @@ lv2:optionalFeature ui:touch ; lv2:requiredFeature urid:map ; lv2:requiredFeature urid:unmap ; - lv2:requiredFeature . + lv2:requiredFeature . diff --git a/plugins/puredata/CMakeLists.txt b/plugins/puredata/CMakeLists.txt new file mode 100644 index 000000000..c8c6fd1f6 --- /dev/null +++ b/plugins/puredata/CMakeLists.txt @@ -0,0 +1,57 @@ +# Puredata plugin specific settings +include(PuredataConfig) + +set(PUREDATA_BINARY_DIR "${PROJECT_BINARY_DIR}/pd/sfizz") + +set(PUREDATA_RESOURCES + "sfizz~-help.pd" + "example.sfz") + +function(copy_puredata_resources TARGET SOURCE_DIR DESTINATION_DIR) + set(_deps) + foreach(res ${PUREDATA_RESOURCES}) + get_filename_component(_dir "${res}" DIRECTORY) + file(MAKE_DIRECTORY "${DESTINATION_DIR}/${_dir}") + add_custom_command( + OUTPUT "${DESTINATION_DIR}/${res}" + COMMAND "${CMAKE_COMMAND}" "-E" "copy" + "${SOURCE_DIR}/${res}" "${DESTINATION_DIR}/${res}" + DEPENDS "${SOURCE_DIR}/${res}") + list(APPEND _deps "${DESTINATION_DIR}/${res}") + endforeach() + add_custom_target("${TARGET}_puredata_resources" DEPENDS ${_deps}) + add_dependencies("${TARGET}" "${TARGET}_puredata_resources") +endfunction() + +add_pd_external(sfizz_puredata "sfizz_puredata.c") +target_compile_definitions(sfizz_puredata PRIVATE + "SFIZZ_NUM_CCS=${SFIZZ_NUM_CCS}" + "SFIZZ_VERSION=\"${CMAKE_PROJECT_VERSION}\"") +target_link_libraries(sfizz_puredata PRIVATE sfizz::import sfizz::sfizz) + +set_target_properties(sfizz_puredata PROPERTIES + OUTPUT_NAME "sfizz" + LIBRARY_OUTPUT_DIRECTORY "${PUREDATA_BINARY_DIR}/$<0:>") + +if(MINGW) + set_property(TARGET sfizz_puredata APPEND_STRING + PROPERTY LINK_FLAGS " -static") +endif() + +# Git build identifier +target_link_libraries(sfizz_puredata PRIVATE sfizz-git-build-id) + +# Copy resources +copy_puredata_resources(sfizz_puredata + "${CMAKE_CURRENT_SOURCE_DIR}" + "${PUREDATA_BINARY_DIR}") + +# Installation +if(NOT MSVC) + install(DIRECTORY "${PUREDATA_BINARY_DIR}" DESTINATION "${PDPLUGIN_INSTALL_DIR}" + COMPONENT "puredata" + USE_SOURCE_PERMISSIONS) + bundle_dylibs(puredata + "${PDPLUGIN_INSTALL_DIR}/sfizz/sfizz${PUREDATA_SUFFIX}" + COMPONENT "puredata") +endif() diff --git a/plugins/puredata/example.sfz b/plugins/puredata/example.sfz new file mode 100644 index 000000000..6e64db979 --- /dev/null +++ b/plugins/puredata/example.sfz @@ -0,0 +1,22 @@ + +ampeg_attack=0.25 +ampeg_decay=5 +ampeg_sustain=50 +ampeg_release=2 +fil_type=lpf_4p +resonance=3 +cutoff=3000 +oscillator_phase=-1 +cutoff_oncc300=3600 +cutoff_curvecc300=1 + + +sample=*saw +oscillator_multi=3 +oscillator_detune=10 +lfo1_freq=5 +lfo1_pitch=30 + + +sample=*triangle +transpose=-12 diff --git a/plugins/puredata/external/pd/LICENSE.txt b/plugins/puredata/external/pd/LICENSE.txt new file mode 100644 index 000000000..a56a51eb5 --- /dev/null +++ b/plugins/puredata/external/pd/LICENSE.txt @@ -0,0 +1,30 @@ +This software is copyrighted by Miller Puckette and others. The following +terms (the "Standard Improved BSD License") apply to all files associated with +the software unless explicitly disclaimed in individual files: + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. +3. The name of the author may not be used to endorse or promote + products derived from this software without specific prior + written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY +EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR +BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED +TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING +IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +THE POSSIBILITY OF SUCH DAMAGE. diff --git a/plugins/puredata/external/pd/bin/pd.def b/plugins/puredata/external/pd/bin/pd.def new file mode 100644 index 000000000..607bd9905 --- /dev/null +++ b/plugins/puredata/external/pd/bin/pd.def @@ -0,0 +1,601 @@ +LIBRARY pd +EXPORTS + ABORT + array_free + array_getcoordinate + array_getfields + array_new + array_redraw + array_resize + array_resize_and_redraw + atom_gensym + atom_getfloat + atom_getfloatarg + atom_getint + atom_getintarg + atom_getsymbol + atom_getsymbolarg + atom_string + audio_isopen + audio_shouldkeepopen + binbuf_add + binbuf_addbinbuf + binbuf_addsemi + binbuf_addv + binbuf_clear + binbuf_duplicate + binbuf_eval + binbuf_evalfile + binbuf_free + binbuf_getnatom + binbuf_gettext + binbuf_getvec + binbuf_new + binbuf_print + binbuf_read + binbuf_read_via_canvas + binbuf_read_via_path + binbuf_realizedollsym + binbuf_resize + binbuf_restore + binbuf_text + binbuf_write + bug + canvas_addinlet + canvas_addoutlet + canvas_class DATA + canvas_closebang + canvas_connect + canvas_create_editor + canvas_dataproperties + canvas_deletelinesfor + canvas_deletelinesforio + canvas_destroy_editor + canvas_dirty + canvas_disconnect + canvas_dspstate DATA + canvas_editmode + canvas_fixlinesfor + canvas_free + canvas_getargs + canvas_getcurrent + canvas_getcurrentdir + canvas_getdir + canvas_getdollarzero + canvas_getenv + canvas_getindex + canvas_getrootfor + canvas_hitbox + canvas_initbang + canvas_isabstraction + canvas_isconnected + canvas_istable + canvas_loadbang + canvas_makebindsym + canvas_makefilename + canvas_new + canvas_noundo + canvas_open + canvas_path_iterate + canvas_readscalar + canvas_realizedollar + canvas_redraw + canvas_redrawallfortemplate + canvas_redrawallfortemplatecanvas + canvas_rename + canvas_resortinlets + canvas_resortoutlets + canvas_restoreconnections + canvas_resume_dsp + canvas_rminlet + canvas_rmoutlet + canvas_selectinrect + canvas_setargs + canvas_setcurrent + canvas_setcursor + canvas_setdeleting + canvas_setundo + canvas_showtext + canvas_stowconnections + canvas_suspend_dsp + canvas_unsetcurrent + canvas_update_dsp + canvas_updatewindowlist + canvas_vis + canvas_whichfind DATA + canvas_writescalar + class_addanything + class_addbang + class_addcreator + class_addlist + class_addmethod + class_addpointer + class_addsymbol + class_doaddfloat + class_domainsignalin + class_gethelpdir + class_gethelpname + class_getname + class_getpropertiesfn + class_getsavefn + class_isdrawcommand + class_new + class_new64 + class_set_extern_dir + class_setdrawcommand + class_sethelpsymbol + class_setparentwidget + class_setpropertiesfn + class_setsavefn + class_setwidget + clock_delay + clock_free + clock_getlogicaltime + clock_getsystime + clock_getsystimeafter + clock_gettimesince + clock_gettimesincewithunits + clock_new + clock_set + clock_setunit + clock_unset + clone_class DATA + copy_perform + copybytes + cos_table DATA + dbtopow + dbtorms + dsp_add + dsp_add_copy + dsp_add_plus + dsp_add_scalarcopy + dsp_add_zero + dsp_addv + endpost + error + ex_Avg + ex_Sum + ex_avg + ex_funcs DATA + ex_getsym + ex_mkvector + ex_size + ex_store + ex_sum + ex_symname + fielddesc_cvtfromcoord + fielddesc_cvttocoord + fielddesc_getcoord + fielddesc_setcoord + floatinlet_new + freebytes + ftom + g_editor_freepdinstance + g_editor_newpdinstance + g_template_freepdinstance + g_template_newpdinstance + garray_class DATA + garray_getarray + garray_getfloatarray + garray_getfloatwords + garray_getglist + garray_npoints + garray_redraw + garray_resize + garray_resize_long + garray_setsaveit + garray_template + garray_usedindsp + garray_vec + gensym + get_sys_dacsr + get_sys_main_advance + get_sys_schedadvance + get_sys_schedblocksize + get_sys_sleepgrain + get_sys_soundin + get_sys_soundout + get_sys_time + get_sys_time_per_dsp_tick + getbytes + getfn + getzbytes + gfxstub_deleteforkey + gfxstub_new + glist_add + glist_addglist + glist_arraydialog + glist_clear + glist_delete + glist_deselect + glist_dpixtodx + glist_dpixtody + glist_drawiofor + glist_eraseiofor + glist_findgraph + glist_findrtext + glist_fontheight + glist_fontwidth + glist_getcanvas + glist_getfont + glist_getnextxy + glist_getzoom + glist_glist + glist_grab + glist_init + glist_isgraph + glist_isselected + glist_istoplevel + glist_isvisible + glist_mergefile + glist_noselect + glist_pixelstox + glist_pixelstoy + glist_read + glist_redraw + glist_retext + glist_select + glist_selectall + glist_sort + glist_valid DATA + glist_writetobinbuf + glist_xtopixels + glist_ytopixels + glob_evalfile + glob_initfromgui + glob_pdobject DATA + glob_quit + glob_setfilename + gobj_activate + gobj_click + gobj_delete + gobj_displace + gobj_getrect + gobj_save + gobj_select + gobj_shouldvis + gobj_vis + gpointer_check + gpointer_copy + gpointer_init + gpointer_setarray + gpointer_setglist + gpointer_unset + graph_array + gstub_cutoff + gstub_new + gtemplate_get + guiconnect_new + guiconnect_notarget + iem_fstyletoint + iem_inttofstyle + iem_inttosymargs + iem_symargstoint + iemgui_all_dollar2raute + iemgui_all_dollararg2sym + iemgui_all_loadcolors + iemgui_all_raute2dollar + iemgui_all_sym2dollararg + iemgui_clip_font + iemgui_clip_size + iemgui_color + iemgui_color_hex DATA + iemgui_delete + iemgui_delta + iemgui_dialog + iemgui_displace + iemgui_dollar2raute + iemgui_label + iemgui_label_font + iemgui_label_pos + iemgui_new_dogetname + iemgui_new_getnames + iemgui_newzoom + iemgui_pos + iemgui_properties + iemgui_raute2dollar + iemgui_receive + iemgui_save + iemgui_select + iemgui_send + iemgui_size + iemgui_verify_snd_ne_rcv + iemgui_vis + iemgui_vu_col DATA + iemgui_vu_db2i DATA + iemgui_vu_scale_str DATA + iemgui_zoom + ilog2 + inlet_free + inlet_new + inmidi_aftertouch + inmidi_byte + inmidi_controlchange + inmidi_noteon + inmidi_pitchbend + inmidi_polyaftertouch + inmidi_programchange + inmidi_realtimein + inmidi_sysex + linetraverser_next + linetraverser_skipobject + linetraverser_start + logpost + max_ex_tab + max_ex_tab_store + max_ex_var + max_ex_var_store + mayer_fft + mayer_fht + mayer_ifft + mayer_realfft + mayer_realifft + midi_getdevs + mmio_close_audio + mmio_getdevs + mmio_open_audio + mmio_reportidle + mmio_send_dacs + mtof + namelist_append + namelist_append_files + namelist_free + namelist_get + nullfn + obj_connect + obj_disconnect + obj_issignalinlet + obj_issignaloutlet + obj_list + obj_nexttraverseoutlet + obj_ninlets + obj_noutlets + obj_nsiginlets + obj_nsigoutlets + obj_saveformat + obj_siginletindex + obj_sigoutletindex + obj_starttraverseoutlet + open_via_helppath + open_via_path + outlet_anything + outlet_bang + outlet_float + outlet_free + outlet_getsymbol + outlet_list + outlet_new + outlet_pointer + outlet_setstacklim + outlet_symbol + pa_close_audio + pa_getdevs + pa_open_audio + pa_send_dacs + pd_bang + pd_bind + pd_canvasmaker DATA + pd_checkglist + pd_checkobject + pd_compatibilitylevel DATA + pd_emptylist + pd_error + pd_fft + pd_findbyclass + pd_float + pd_forwardmess + pd_free + pd_getcanvaslist + pd_getdspstate + pd_getparentwidget + pd_globallock + pd_globalunlock + pd_list + pd_maininstance DATA + pd_new + pd_newest + pd_objectmaker DATA + pd_pointer + pd_popsym + pd_pushsym + pd_symbol + pd_typedmess + pd_unbind + pd_vmess + plus_perform + pointerinlet_new + post + postatom + postfloat + poststring + powtodb + q8_rsqrt + q8_sqrt + qrsqrt + qsqrt + resample_dsp + resample_free + resample_init + resamplefrom_dsp + resampleto_dsp + resizebytes + rmstodb + rtext_activate + rtext_displace + rtext_draw + rtext_erase + rtext_free + rtext_getseltext + rtext_gettag + rtext_gettext + rtext_height + rtext_key + rtext_mouse + rtext_new + rtext_retext + rtext_select + rtext_width + s_ DATA + s__N DATA + s__X DATA + s_anything DATA + s_bang DATA + s_float DATA + s_list DATA + s_pointer DATA + s_signal DATA + s_symbol DATA + s_x DATA + s_y DATA + scalar_class DATA + scalar_getbasexy + scalar_new + scalar_redraw + sched_geteventno + sched_set_using_audio + sched_tick + signalinlet_new + socketreceiver_new + socketreceiver_read + startpost + symbolinlet_new + sys_addhist + sys_addpollfn + sys_advance_samples DATA + sys_audioapi DATA + sys_audiodevnametonumber + sys_audiodevnumbertoname + sys_bail + sys_bashfilename + sys_clearhist + sys_close + sys_close_audio + sys_close_midi + sys_closesocket + sys_debuglevel DATA + sys_decodedialog + sys_defaultfont DATA + sys_defeatrt DATA + sys_do_open_midi + sys_externalschedlib DATA + sys_fclose + sys_flags DATA + sys_font DATA + sys_fontheight + sys_fontweight DATA + sys_fontwidth + sys_fopen + sys_get_audio_apis + sys_get_audio_devs + sys_get_audio_params + sys_get_inchannels + sys_get_midi_apis + sys_get_midi_devs + sys_get_midi_params + sys_get_outchannels + sys_getblksize + sys_getmeters + sys_getrealtime + sys_getsr + sys_getversion + sys_gui + sys_guicmd DATA + sys_havegui + sys_hipriority DATA + sys_hostfontsize + sys_idlehook DATA + sys_init_fdpoll + sys_initmidiqueue + sys_isabsolutepath + sys_libdir DATA + sys_listdevs + sys_listmididevs + sys_load_lib + sys_loadpreferences + sys_lock + sys_log_error + sys_logerror + sys_microsleep + sys_midiapi DATA + sys_midibytein + sys_mididevnametonumber + sys_mididevnumbertoname + sys_midiindevlist DATA + sys_midioutdevlist DATA + sys_nearestfontsize + sys_nmidiin DATA + sys_nmidiout DATA + sys_noloadbang DATA + sys_open + sys_open_absolute + sys_open_midi + sys_ouch + sys_poll_midi + sys_pollgui + sys_pollmidiqueue + sys_pretendguibytes + sys_printhook DATA + sys_printtostderr DATA + sys_putmidibyte + sys_putmidimess + sys_queuegui + sys_register_loader + sys_reopen_audio + sys_reopen_midi + sys_reportidle + sys_rmpollfn + sys_save_audio_params + sys_savepreferences + sys_schedadvance DATA + sys_send_dacs + sys_set_audio_api + sys_set_audio_settings + sys_set_audio_settings_reopen + sys_set_audio_state + sys_set_midi_api + sys_setchsr + sys_setextrapath + sys_setmiditimediff + sys_sleepgrain DATA + sys_sockerror + sys_trylock + sys_trytoopenone + sys_unbashfilename + sys_unixerror + sys_unlock + sys_unqueuegui + sys_usestdpath DATA + sys_verbose DATA + sys_vgui + sys_zoom_open DATA + sys_zoomfontheight + sys_zoomfontwidth + template_find_field + template_findbyname + template_findcanvas + template_free + template_getfloat + template_getsymbol + template_match + template_new + template_notify + template_setfloat + template_setsymbol + text_drawborder + text_eraseborder + text_setto + text_widgetbehavior DATA + text_xpix + text_ypix + value_get + value_getfloat + value_release + value_setfloat + verbose + vinlet_class DATA + voutlet_class DATA + word_free + word_init + word_restore + zero_perform + zgetfn diff --git a/plugins/puredata/external/pd/include/m_pd.h b/plugins/puredata/external/pd/include/m_pd.h new file mode 100644 index 000000000..1bcbfff49 --- /dev/null +++ b/plugins/puredata/external/pd/include/m_pd.h @@ -0,0 +1,911 @@ +/* Copyright (c) 1997-1999 Miller Puckette. +* For information on usage and redistribution, and for a DISCLAIMER OF ALL +* WARRANTIES, see the file, "LICENSE.txt," in this distribution. */ + +#ifndef __m_pd_h_ + +#if defined(_LANGUAGE_C_PLUS_PLUS) || defined(__cplusplus) +extern "C" { +#endif + +#define PD_MAJOR_VERSION 0 +#define PD_MINOR_VERSION 51 +#define PD_BUGFIX_VERSION 4 +#define PD_TEST_VERSION "" +extern int pd_compatibilitylevel; /* e.g., 43 for pd 0.43 compatibility */ + +/* old name for "MSW" flag -- we have to take it for the sake of many old +"nmakefiles" for externs, which will define NT and not MSW */ +#if defined(NT) && !defined(MSW) +#define MSW +#endif + +/* These pragmas are only used for MSVC, not MinGW or Cygwin */ +#ifdef _MSC_VER +/* #pragma warning( disable : 4091 ) */ +#pragma warning( disable : 4305 ) /* uncast const double to float */ +#pragma warning( disable : 4244 ) /* uncast float/int conversion etc. */ +#pragma warning( disable : 4101 ) /* unused automatic variables */ +#endif /* _MSC_VER */ + + /* the external storage class is "extern" in UNIX; in MSW it's ugly. */ +#ifdef _WIN32 +#ifdef PD_INTERNAL +#define EXTERN __declspec(dllexport) extern +#else +#define EXTERN __declspec(dllimport) extern +#endif /* PD_INTERNAL */ +#else +#define EXTERN extern +#endif /* _WIN32 */ + + /* On most c compilers, you can just say "struct foo;" to declare a + structure whose elements are defined elsewhere. On MSVC, when compiling + C (but not C++) code, you have to say "extern struct foo;". So we make + a stupid macro: */ +#if defined(_MSC_VER) && !defined(_LANGUAGE_C_PLUS_PLUS) \ + && !defined(__cplusplus) +#define EXTERN_STRUCT extern struct +#else +#define EXTERN_STRUCT struct +#endif + +/* Define some attributes, specific to the compiler */ +#if defined(__GNUC__) +#define ATTRIBUTE_FORMAT_PRINTF(a, b) __attribute__ ((format (printf, a, b))) +#else +#define ATTRIBUTE_FORMAT_PRINTF(a, b) +#endif + +#if !defined(_SIZE_T) && !defined(_SIZE_T_) +#include /* just for size_t -- how lame! */ +#endif + +/* Microsoft Visual Studio is not C99, but since VS2015 has included most C99 headers: + https://docs.microsoft.com/en-us/previous-versions/hh409293(v=vs.140)#c-runtime-library + These definitions recreate stdint.h types, but only in pre-2015 Visual Studio: */ +#if defined(_MSC_VER) && _MSC_VER < 1900 +typedef signed __int8 int8_t; +typedef signed __int16 int16_t; +typedef signed __int32 int32_t; +typedef signed __int64 int64_t; +typedef unsigned __int8 uint8_t; +typedef unsigned __int16 uint16_t; +typedef unsigned __int32 uint32_t; +typedef unsigned __int64 uint64_t; +#else +# include +#endif + +/* for FILE, needed by sys_fopen() and sys_fclose() only */ +#include + +#define MAXPDSTRING 1000 /* use this for anything you want */ +#define MAXPDARG 5 /* max number of args we can typecheck today */ + +/* signed and unsigned integer types the size of a pointer: */ +#if !defined(PD_LONGINTTYPE) +#if defined(_WIN32) && defined(_WIN64) +#define PD_LONGINTTYPE long long +#else +#define PD_LONGINTTYPE long +#endif +#endif + +#if !defined(PD_FLOATSIZE) + /* normally, our floats (t_float, t_sample,...) are 32bit */ +# define PD_FLOATSIZE 32 +#endif + +#if PD_FLOATSIZE == 32 +# define PD_FLOATTYPE float +/* an unsigned int of the same size as FLOATTYPE: */ +# define PD_FLOATUINTTYPE uint32_t + +#elif PD_FLOATSIZE == 64 +# define PD_FLOATTYPE double +# define PD_FLOATUINTTYPE uint64_t +#else +# error invalid FLOATSIZE: must be 32 or 64 +#endif + +typedef PD_LONGINTTYPE t_int; /* pointer-size integer */ +typedef PD_FLOATTYPE t_float; /* a float type at most the same size */ +typedef PD_FLOATTYPE t_floatarg; /* float type for function calls */ + +typedef struct _symbol +{ + const char *s_name; + struct _class **s_thing; + struct _symbol *s_next; +} t_symbol; + +EXTERN_STRUCT _array; +#define t_array struct _array /* g_canvas.h */ + +/* pointers to glist and array elements go through a "stub" which sticks +around after the glist or array is freed. The stub itself is deleted when +both the glist/array is gone and the refcount is zero, ensuring that no +gpointers are pointing here. */ + +#define GP_NONE 0 /* the stub points nowhere (has been cut off) */ +#define GP_GLIST 1 /* the stub points to a glist element */ +#define GP_ARRAY 2 /* ... or array */ + +typedef struct _gstub +{ + union + { + struct _glist *gs_glist; /* glist we're in */ + struct _array *gs_array; /* array we're in */ + } gs_un; + int gs_which; /* GP_GLIST/GP_ARRAY */ + int gs_refcount; /* number of gpointers pointing here */ +} t_gstub; + +typedef struct _gpointer /* pointer to a gobj in a glist */ +{ + union + { + struct _scalar *gp_scalar; /* scalar we're in (if glist) */ + union word *gp_w; /* raw data (if array) */ + } gp_un; + int gp_valid; /* number which must match gpointee */ + t_gstub *gp_stub; /* stub which points to glist/array */ +} t_gpointer; + +typedef union word +{ + t_float w_float; + t_symbol *w_symbol; + t_gpointer *w_gpointer; + t_array *w_array; + struct _binbuf *w_binbuf; + int w_index; +} t_word; + +typedef enum +{ + A_NULL, + A_FLOAT, + A_SYMBOL, + A_POINTER, + A_SEMI, + A_COMMA, + A_DEFFLOAT, + A_DEFSYM, + A_DOLLAR, + A_DOLLSYM, + A_GIMME, + A_CANT +} t_atomtype; + +#define A_DEFSYMBOL A_DEFSYM /* better name for this */ + +typedef struct _atom +{ + t_atomtype a_type; + union word a_w; +} t_atom; + +EXTERN_STRUCT _class; +#define t_class struct _class + +EXTERN_STRUCT _outlet; +#define t_outlet struct _outlet + +EXTERN_STRUCT _inlet; +#define t_inlet struct _inlet + +EXTERN_STRUCT _binbuf; +#define t_binbuf struct _binbuf + +EXTERN_STRUCT _clock; +#define t_clock struct _clock + +EXTERN_STRUCT _outconnect; +#define t_outconnect struct _outconnect + +EXTERN_STRUCT _glist; +#define t_glist struct _glist +#define t_canvas struct _glist /* LATER lose this */ + +EXTERN_STRUCT _template; + +typedef t_class *t_pd; /* pure datum: nothing but a class pointer */ + +typedef struct _gobj /* a graphical object */ +{ + t_pd g_pd; /* pure datum header (class) */ + struct _gobj *g_next; /* next in list */ +} t_gobj; + +typedef struct _scalar /* a graphical object holding data */ +{ + t_gobj sc_gobj; /* header for graphical object */ + t_symbol *sc_template; /* template name (LATER replace with pointer) */ + t_word sc_vec[1]; /* indeterminate-length array of words */ +} t_scalar; + +typedef struct _text /* patchable object - graphical, with text */ +{ + t_gobj te_g; /* header for graphical object */ + t_binbuf *te_binbuf; /* holder for the text */ + t_outlet *te_outlet; /* linked list of outlets */ + t_inlet *te_inlet; /* linked list of inlets */ + short te_xpix; /* x&y location (within the toplevel) */ + short te_ypix; + short te_width; /* requested width in chars, 0 if auto */ + unsigned int te_type:2; /* from defs below */ +} t_text; + +#define T_TEXT 0 /* just a textual comment */ +#define T_OBJECT 1 /* a MAX style patchable object */ +#define T_MESSAGE 2 /* a MAX type message */ +#define T_ATOM 3 /* a cell to display a number or symbol */ + +#define te_pd te_g.g_pd + + /* t_object is synonym for t_text (LATER unify them) */ + +typedef struct _text t_object; + +#define ob_outlet te_outlet +#define ob_inlet te_inlet +#define ob_binbuf te_binbuf +#define ob_pd te_g.g_pd +#define ob_g te_g + +typedef void (*t_method)(void); +typedef void *(*t_newmethod)(void); + +/* in ARM 64 a varargs prototype generates a different function call sequence +from a fixed one, so in that special case we make a more restrictive +definition for t_gotfn. This will break some code in the "chaos" package +in Pd extended. (that code will run incorrectly anyhow so why not catch it +at compile time anyhow.) */ +#if defined(__APPLE__) && defined(__aarch64__) +typedef void (*t_gotfn)(void *x); +#else +typedef void (*t_gotfn)(void *x, ...); +#endif + +/* ---------------- pre-defined objects and symbols --------------*/ +EXTERN t_pd pd_objectmaker; /* factory for creating "object" boxes */ +EXTERN t_pd pd_canvasmaker; /* factory for creating canvases */ + +/* --------- prototypes from the central message system ----------- */ +EXTERN void pd_typedmess(t_pd *x, t_symbol *s, int argc, t_atom *argv); +EXTERN void pd_forwardmess(t_pd *x, int argc, t_atom *argv); +EXTERN t_symbol *gensym(const char *s); +EXTERN t_gotfn getfn(const t_pd *x, t_symbol *s); +EXTERN t_gotfn zgetfn(const t_pd *x, t_symbol *s); +EXTERN void nullfn(void); +EXTERN void pd_vmess(t_pd *x, t_symbol *s, const char *fmt, ...); + +/* the following macros are for sending non-type-checkable messages, i.e., +using function lookup but circumventing type checking on arguments. Only +use for internal messaging protected by A_CANT so that the message can't +be generated at patch level. */ +#define mess0(x, s) ((*getfn((x), (s)))((x))) +typedef void (*t_gotfn1)(void *x, void *arg1); +#define mess1(x, s, a) ((*(t_gotfn1)getfn((x), (s)))((x), (a))) +typedef void (*t_gotfn2)(void *x, void *arg1, void *arg2); +#define mess2(x, s, a,b) ((*(t_gotfn2)getfn((x), (s)))((x), (a),(b))) +typedef void (*t_gotfn3)(void *x, void *arg1, void *arg2, void *arg3); +#define mess3(x, s, a,b,c) ((*(t_gotfn3)getfn((x), (s)))((x), (a),(b),(c))) +typedef void (*t_gotfn4)(void *x, + void *arg1, void *arg2, void *arg3, void *arg4); +#define mess4(x, s, a,b,c,d) \ + ((*(t_gotfn4)getfn((x), (s)))((x), (a),(b),(c),(d))) +typedef void (*t_gotfn5)(void *x, + void *arg1, void *arg2, void *arg3, void *arg4, void *arg5); +#define mess5(x, s, a,b,c,d,e) \ + ((*(t_gotfn5)getfn((x), (s)))((x), (a),(b),(c),(d),(e))) + +EXTERN void obj_list(t_object *x, t_symbol *s, int argc, t_atom *argv); +EXTERN t_pd *pd_newest(void); + +/* --------------- memory management -------------------- */ +EXTERN void *getbytes(size_t nbytes); +EXTERN void *getzbytes(size_t nbytes); +EXTERN void *copybytes(const void *src, size_t nbytes); +EXTERN void freebytes(void *x, size_t nbytes); +EXTERN void *resizebytes(void *x, size_t oldsize, size_t newsize); + +/* -------------------- atoms ----------------------------- */ + +#define SETSEMI(atom) ((atom)->a_type = A_SEMI, (atom)->a_w.w_index = 0) +#define SETCOMMA(atom) ((atom)->a_type = A_COMMA, (atom)->a_w.w_index = 0) +#define SETPOINTER(atom, gp) ((atom)->a_type = A_POINTER, \ + (atom)->a_w.w_gpointer = (gp)) +#define SETFLOAT(atom, f) ((atom)->a_type = A_FLOAT, (atom)->a_w.w_float = (f)) +#define SETSYMBOL(atom, s) ((atom)->a_type = A_SYMBOL, \ + (atom)->a_w.w_symbol = (s)) +#define SETDOLLAR(atom, n) ((atom)->a_type = A_DOLLAR, \ + (atom)->a_w.w_index = (n)) +#define SETDOLLSYM(atom, s) ((atom)->a_type = A_DOLLSYM, \ + (atom)->a_w.w_symbol= (s)) + +EXTERN t_float atom_getfloat(const t_atom *a); +EXTERN t_int atom_getint(const t_atom *a); +EXTERN t_symbol *atom_getsymbol(const t_atom *a); +EXTERN t_symbol *atom_gensym(const t_atom *a); +EXTERN t_float atom_getfloatarg(int which, int argc, const t_atom *argv); +EXTERN t_int atom_getintarg(int which, int argc, const t_atom *argv); +EXTERN t_symbol *atom_getsymbolarg(int which, int argc, const t_atom *argv); + +EXTERN void atom_string(const t_atom *a, char *buf, unsigned int bufsize); + +/* ------------------ binbufs --------------- */ + +EXTERN t_binbuf *binbuf_new(void); +EXTERN void binbuf_free(t_binbuf *x); +EXTERN t_binbuf *binbuf_duplicate(const t_binbuf *y); + +EXTERN void binbuf_text(t_binbuf *x, const char *text, size_t size); +EXTERN void binbuf_gettext(const t_binbuf *x, char **bufp, int *lengthp); +EXTERN void binbuf_clear(t_binbuf *x); +EXTERN void binbuf_add(t_binbuf *x, int argc, const t_atom *argv); +EXTERN void binbuf_addv(t_binbuf *x, const char *fmt, ...); +EXTERN void binbuf_addbinbuf(t_binbuf *x, const t_binbuf *y); +EXTERN void binbuf_addsemi(t_binbuf *x); +EXTERN void binbuf_restore(t_binbuf *x, int argc, const t_atom *argv); +EXTERN void binbuf_print(const t_binbuf *x); +EXTERN int binbuf_getnatom(const t_binbuf *x); +EXTERN t_atom *binbuf_getvec(const t_binbuf *x); +EXTERN int binbuf_resize(t_binbuf *x, int newsize); +EXTERN void binbuf_eval(const t_binbuf *x, t_pd *target, int argc, const t_atom *argv); +EXTERN int binbuf_read(t_binbuf *b, const char *filename, const char *dirname, + int crflag); +EXTERN int binbuf_read_via_canvas(t_binbuf *b, const char *filename, const t_canvas *canvas, + int crflag); +EXTERN int binbuf_read_via_path(t_binbuf *b, const char *filename, const char *dirname, + int crflag); +EXTERN int binbuf_write(const t_binbuf *x, const char *filename, const char *dir, + int crflag); +EXTERN void binbuf_evalfile(t_symbol *name, t_symbol *dir); +EXTERN t_symbol *binbuf_realizedollsym(t_symbol *s, int ac, const t_atom *av, + int tonew); + +/* ------------------ clocks --------------- */ + +EXTERN t_clock *clock_new(void *owner, t_method fn); +EXTERN void clock_set(t_clock *x, double systime); +EXTERN void clock_delay(t_clock *x, double delaytime); +EXTERN void clock_unset(t_clock *x); +EXTERN void clock_setunit(t_clock *x, double timeunit, int sampflag); +EXTERN double clock_getlogicaltime(void); +EXTERN double clock_getsystime(void); /* OBSOLETE; use clock_getlogicaltime() */ +EXTERN double clock_gettimesince(double prevsystime); +EXTERN double clock_gettimesincewithunits(double prevsystime, + double units, int sampflag); +EXTERN double clock_getsystimeafter(double delaytime); +EXTERN void clock_free(t_clock *x); + +/* ----------------- pure data ---------------- */ +EXTERN t_pd *pd_new(t_class *cls); +EXTERN void pd_free(t_pd *x); +EXTERN void pd_bind(t_pd *x, t_symbol *s); +EXTERN void pd_unbind(t_pd *x, t_symbol *s); +EXTERN t_pd *pd_findbyclass(t_symbol *s, const t_class *c); +EXTERN void pd_pushsym(t_pd *x); +EXTERN void pd_popsym(t_pd *x); +EXTERN void pd_bang(t_pd *x); +EXTERN void pd_pointer(t_pd *x, t_gpointer *gp); +EXTERN void pd_float(t_pd *x, t_float f); +EXTERN void pd_symbol(t_pd *x, t_symbol *s); +EXTERN void pd_list(t_pd *x, t_symbol *s, int argc, t_atom *argv); +EXTERN void pd_anything(t_pd *x, t_symbol *s, int argc, t_atom *argv); +#define pd_class(x) (*(x)) + +/* ----------------- pointers ---------------- */ +EXTERN void gpointer_init(t_gpointer *gp); +EXTERN void gpointer_copy(const t_gpointer *gpfrom, t_gpointer *gpto); +EXTERN void gpointer_unset(t_gpointer *gp); +EXTERN int gpointer_check(const t_gpointer *gp, int headok); + +/* ----------------- patchable "objects" -------------- */ +EXTERN t_inlet *inlet_new(t_object *owner, t_pd *dest, t_symbol *s1, + t_symbol *s2); +EXTERN t_inlet *pointerinlet_new(t_object *owner, t_gpointer *gp); +EXTERN t_inlet *floatinlet_new(t_object *owner, t_float *fp); +EXTERN t_inlet *symbolinlet_new(t_object *owner, t_symbol **sp); +EXTERN t_inlet *signalinlet_new(t_object *owner, t_float f); +EXTERN void inlet_free(t_inlet *x); + +EXTERN t_outlet *outlet_new(t_object *owner, t_symbol *s); +EXTERN void outlet_bang(t_outlet *x); +EXTERN void outlet_pointer(t_outlet *x, t_gpointer *gp); +EXTERN void outlet_float(t_outlet *x, t_float f); +EXTERN void outlet_symbol(t_outlet *x, t_symbol *s); +EXTERN void outlet_list(t_outlet *x, t_symbol *s, int argc, t_atom *argv); +EXTERN void outlet_anything(t_outlet *x, t_symbol *s, int argc, t_atom *argv); +EXTERN t_symbol *outlet_getsymbol(t_outlet *x); +EXTERN void outlet_free(t_outlet *x); +EXTERN t_object *pd_checkobject(t_pd *x); + + +/* -------------------- canvases -------------- */ + +EXTERN void glob_setfilename(void *dummy, t_symbol *name, t_symbol *dir); + +EXTERN void canvas_setargs(int argc, const t_atom *argv); +EXTERN void canvas_getargs(int *argcp, t_atom **argvp); +EXTERN t_symbol *canvas_getcurrentdir(void); +EXTERN t_glist *canvas_getcurrent(void); +EXTERN void canvas_makefilename(const t_glist *c, const char *file, + char *result, int resultsize); +EXTERN t_symbol *canvas_getdir(const t_glist *x); +EXTERN char sys_font[]; /* default typeface set in s_main.c */ +EXTERN char sys_fontweight[]; /* default font weight set in s_main.c */ +EXTERN int sys_hostfontsize(int fontsize, int zoom); +EXTERN int sys_zoomfontwidth(int fontsize, int zoom, int worstcase); +EXTERN int sys_zoomfontheight(int fontsize, int zoom, int worstcase); +EXTERN int sys_fontwidth(int fontsize); +EXTERN int sys_fontheight(int fontsize); +EXTERN void canvas_dataproperties(t_glist *x, t_scalar *sc, t_binbuf *b); +EXTERN int canvas_open(const t_canvas *x, const char *name, const char *ext, + char *dirresult, char **nameresult, unsigned int size, int bin); + +/* ---------------- widget behaviors ---------------------- */ + +EXTERN_STRUCT _widgetbehavior; +#define t_widgetbehavior struct _widgetbehavior + +EXTERN_STRUCT _parentwidgetbehavior; +#define t_parentwidgetbehavior struct _parentwidgetbehavior +EXTERN const t_parentwidgetbehavior *pd_getparentwidget(t_pd *x); + +/* -------------------- classes -------------- */ + +#define CLASS_DEFAULT 0 /* flags for new classes below */ +#define CLASS_PD 1 +#define CLASS_GOBJ 2 +#define CLASS_PATCHABLE 3 +#define CLASS_NOINLET 8 + +#define CLASS_TYPEMASK 3 + +EXTERN t_class *class_new(t_symbol *name, t_newmethod newmethod, + t_method freemethod, size_t size, int flags, t_atomtype arg1, ...); + +EXTERN t_class *class_new64(t_symbol *name, t_newmethod newmethod, + t_method freemethod, size_t size, int flags, t_atomtype arg1, ...); + +EXTERN void class_free(t_class *c); + +#ifdef PDINSTANCE +EXTERN t_class *class_getfirst(void); +#endif + +EXTERN void class_addcreator(t_newmethod newmethod, t_symbol *s, + t_atomtype type1, ...); +EXTERN void class_addmethod(t_class *c, t_method fn, t_symbol *sel, + t_atomtype arg1, ...); +EXTERN void class_addbang(t_class *c, t_method fn); +EXTERN void class_addpointer(t_class *c, t_method fn); +EXTERN void class_doaddfloat(t_class *c, t_method fn); +EXTERN void class_addsymbol(t_class *c, t_method fn); +EXTERN void class_addlist(t_class *c, t_method fn); +EXTERN void class_addanything(t_class *c, t_method fn); +EXTERN void class_sethelpsymbol(t_class *c, t_symbol *s); +EXTERN void class_setwidget(t_class *c, const t_widgetbehavior *w); +EXTERN void class_setparentwidget(t_class *c, const t_parentwidgetbehavior *w); +EXTERN const char *class_getname(const t_class *c); +EXTERN const char *class_gethelpname(const t_class *c); +EXTERN const char *class_gethelpdir(const t_class *c); +EXTERN void class_setdrawcommand(t_class *c); +EXTERN int class_isdrawcommand(const t_class *c); +EXTERN void class_domainsignalin(t_class *c, int onset); +EXTERN void class_set_extern_dir(t_symbol *s); +#define CLASS_MAINSIGNALIN(c, type, field) \ + class_domainsignalin(c, (char *)(&((type *)0)->field) - (char *)0) + + /* prototype for functions to save Pd's to a binbuf */ +typedef void (*t_savefn)(t_gobj *x, t_binbuf *b); +EXTERN void class_setsavefn(t_class *c, t_savefn f); +EXTERN t_savefn class_getsavefn(const t_class *c); +EXTERN void obj_saveformat(const t_object *x, t_binbuf *bb); /* add format to bb */ + + /* prototype for functions to open properties dialogs */ +typedef void (*t_propertiesfn)(t_gobj *x, struct _glist *glist); +EXTERN void class_setpropertiesfn(t_class *c, t_propertiesfn f); +EXTERN t_propertiesfn class_getpropertiesfn(const t_class *c); + +typedef void (*t_classfreefn)(t_class *); +EXTERN void class_setfreefn(t_class *c, t_classfreefn fn); + +#ifndef PD_CLASS_DEF +#define class_addbang(x, y) class_addbang((x), (t_method)(y)) +#define class_addpointer(x, y) class_addpointer((x), (t_method)(y)) +#define class_addfloat(x, y) class_doaddfloat((x), (t_method)(y)) +#define class_addsymbol(x, y) class_addsymbol((x), (t_method)(y)) +#define class_addlist(x, y) class_addlist((x), (t_method)(y)) +#define class_addanything(x, y) class_addanything((x), (t_method)(y)) +#endif + +#if PD_FLOATSIZE == 64 +# define class_new class_new64 +#endif + +/* ------------ printing --------------------------------- */ +EXTERN void post(const char *fmt, ...); +EXTERN void startpost(const char *fmt, ...); +EXTERN void poststring(const char *s); +EXTERN void postfloat(t_floatarg f); +EXTERN void postatom(int argc, const t_atom *argv); +EXTERN void endpost(void); +EXTERN void error(const char *fmt, ...) ATTRIBUTE_FORMAT_PRINTF(1, 2); +EXTERN void verbose(int level, const char *fmt, ...) ATTRIBUTE_FORMAT_PRINTF(2, 3); +EXTERN void bug(const char *fmt, ...) ATTRIBUTE_FORMAT_PRINTF(1, 2); +EXTERN void pd_error(const void *object, const char *fmt, ...) ATTRIBUTE_FORMAT_PRINTF(2, 3); +EXTERN void logpost(const void *object, const int level, const char *fmt, ...) + ATTRIBUTE_FORMAT_PRINTF(3, 4); + +/* ------------ system interface routines ------------------- */ +EXTERN int sys_isabsolutepath(const char *dir); +EXTERN void sys_bashfilename(const char *from, char *to); +EXTERN void sys_unbashfilename(const char *from, char *to); +EXTERN int open_via_path(const char *dir, const char *name, const char *ext, + char *dirresult, char **nameresult, unsigned int size, int bin); +EXTERN int sched_geteventno(void); +EXTERN double sys_getrealtime(void); +EXTERN int (*sys_idlehook)(void); /* hook to add idle time computation */ + +/* Win32's open()/fopen() do not handle UTF-8 filenames so we need + * these internal versions that handle UTF-8 filenames the same across + * all platforms. They are recommended for use in external + * objectclasses as well so they work with Unicode filenames on Windows */ +EXTERN int sys_open(const char *path, int oflag, ...); +EXTERN int sys_close(int fd); +EXTERN FILE *sys_fopen(const char *filename, const char *mode); +EXTERN int sys_fclose(FILE *stream); + +/* ------------ threading ------------------- */ +EXTERN void sys_lock(void); +EXTERN void sys_unlock(void); +EXTERN int sys_trylock(void); + + +/* --------------- signals ----------------------------------- */ + +typedef PD_FLOATTYPE t_sample; +typedef union _sampleint_union { + t_sample f; + PD_FLOATUINTTYPE i; +} t_sampleint_union; +#define MAXLOGSIG 32 +#define MAXSIGSIZE (1 << MAXLOGSIG) + +typedef struct _signal +{ + int s_n; /* number of points in the array */ + t_sample *s_vec; /* the array */ + t_float s_sr; /* sample rate */ + int s_refcount; /* number of times used */ + int s_isborrowed; /* whether we're going to borrow our array */ + struct _signal *s_borrowedfrom; /* signal to borrow it from */ + struct _signal *s_nextfree; /* next in freelist */ + struct _signal *s_nextused; /* next in used list */ + int s_vecsize; /* allocated size of array in points */ +} t_signal; + +typedef t_int *(*t_perfroutine)(t_int *args); + +EXTERN t_int *plus_perform(t_int *args); +EXTERN t_int *zero_perform(t_int *args); +EXTERN t_int *copy_perform(t_int *args); + +EXTERN void dsp_add_plus(t_sample *in1, t_sample *in2, t_sample *out, int n); +EXTERN void dsp_add_copy(t_sample *in, t_sample *out, int n); +EXTERN void dsp_add_scalarcopy(t_float *in, t_sample *out, int n); +EXTERN void dsp_add_zero(t_sample *out, int n); + +EXTERN int sys_getblksize(void); +EXTERN t_float sys_getsr(void); +EXTERN int sys_get_inchannels(void); +EXTERN int sys_get_outchannels(void); + +EXTERN void dsp_add(t_perfroutine f, int n, ...); +EXTERN void dsp_addv(t_perfroutine f, int n, t_int *vec); +EXTERN void pd_fft(t_float *buf, int npoints, int inverse); +EXTERN int ilog2(int n); + +EXTERN void mayer_fht(t_sample *fz, int n); +EXTERN void mayer_fft(int n, t_sample *real, t_sample *imag); +EXTERN void mayer_ifft(int n, t_sample *real, t_sample *imag); +EXTERN void mayer_realfft(int n, t_sample *real); +EXTERN void mayer_realifft(int n, t_sample *real); + +EXTERN float *cos_table; +#define LOGCOSTABSIZE 9 +#define COSTABSIZE (1<> 1) & 0x20000000)); +} + +#elif PD_FLOATSIZE == 64 + +typedef union +{ + t_float f; + unsigned int ui[2]; +}t_bigorsmall64; + +static inline int PD_BADFLOAT(t_float f) /* malformed double */ +{ + t_bigorsmall64 pun; + pun.f = f; + pun.ui[1] &= 0x7ff00000; + return((pun.ui[1] == 0) | (pun.ui[1] == 0x7ff00000)); +} + +static inline int PD_BIGORSMALL(t_float f) /* exponent outside (-512,512) */ +{ + t_bigorsmall64 pun; + pun.f = f; + return((pun.ui[1] & 0x20000000) == ((pun.ui[1] >> 1) & 0x20000000)); +} + +#endif /* PD_FLOATSIZE */ +#else /* not INTEL or ARM */ +#define PD_BADFLOAT(f) 0 +#define PD_BIGORSMALL(f) 0 +#endif + +#else /* _MSC_VER */ +#if PD_FLOATSIZE == 32 +#define PD_BADFLOAT(f) ((((*(unsigned int*)&(f))&0x7f800000)==0) || \ + (((*(unsigned int*)&(f))&0x7f800000)==0x7f800000)) +/* more stringent test: anything not between 1e-19 and 1e19 in absolute val */ +#define PD_BIGORSMALL(f) ((((*(unsigned int*)&(f))&0x60000000)==0) || \ + (((*(unsigned int*)&(f))&0x60000000)==0x60000000)) +#else /* 64 bits... don't know what to do here */ +#define PD_BADFLOAT(f) (!(((f) >= 0) || ((f) <= 0))) +#define PD_BIGORSMALL(f) ((f) > 1e150 || (f) < -1e150 \ + || (f) > -1e-150 && (f) < 1e-150 ) +#endif +#endif /* _MSC_VER */ + /* get version number at run time */ +EXTERN void sys_getversion(int *major, int *minor, int *bugfix); + +EXTERN_STRUCT _instancemidi; +#define t_instancemidi struct _instancemidi + +EXTERN_STRUCT _instanceinter; +#define t_instanceinter struct _instanceinter + +EXTERN_STRUCT _instancecanvas; +#define t_instancecanvas struct _instancecanvas + +EXTERN_STRUCT _instanceugen; +#define t_instanceugen struct _instanceugen + +EXTERN_STRUCT _instancestuff; +#define t_instancestuff struct _instancestuff + +#ifndef PDTHREADS +#define PDTHREADS 1 +#endif + +struct _pdinstance +{ + double pd_systime; /* global time in Pd ticks */ + t_clock *pd_clock_setlist; /* list of set clocks */ + t_canvas *pd_canvaslist; /* list of all root canvases */ + struct _template *pd_templatelist; /* list of all templates */ + int pd_instanceno; /* ordinal number of this instance */ + t_symbol **pd_symhash; /* symbol table hash table */ + t_instancemidi *pd_midi; /* private stuff for x_midi.c */ + t_instanceinter *pd_inter; /* private stuff for s_inter.c */ + t_instanceugen *pd_ugen; /* private stuff for d_ugen.c */ + t_instancecanvas *pd_gui; /* semi-private stuff in g_canvas.h */ + t_instancestuff *pd_stuff; /* semi-private stuff in s_stuff.h */ + t_pd *pd_newest; /* most recently created object */ +#ifdef PDINSTANCE + t_symbol pd_s_pointer; + t_symbol pd_s_float; + t_symbol pd_s_symbol; + t_symbol pd_s_bang; + t_symbol pd_s_list; + t_symbol pd_s_anything; + t_symbol pd_s_signal; + t_symbol pd_s__N; + t_symbol pd_s__X; + t_symbol pd_s_x; + t_symbol pd_s_y; + t_symbol pd_s_; +#endif +#if PDTHREADS + int pd_islocked; +#endif +}; +#define t_pdinstance struct _pdinstance +EXTERN t_pdinstance pd_maininstance; + +/* m_pd.c */ +#ifdef PDINSTANCE +EXTERN t_pdinstance *pdinstance_new(void); +EXTERN void pd_setinstance(t_pdinstance *x); +EXTERN void pdinstance_free(t_pdinstance *x); +#endif /* PDINSTANCE */ + +#if defined(PDTHREADS) && defined(PDINSTANCE) +#ifdef _MSC_VER +#define PERTHREAD __declspec(thread) +#else +#define PERTHREAD __thread +#endif /* _MSC_VER */ +#else +#define PERTHREAD +#endif + +#ifdef PDINSTANCE +extern PERTHREAD t_pdinstance *pd_this; +EXTERN t_pdinstance **pd_instances; +EXTERN int pd_ninstances; +#else +#define pd_this (&pd_maininstance) +#endif /* PDINSTANCE */ + +#ifdef PDINSTANCE +#define s_pointer (pd_this->pd_s_pointer) +#define s_float (pd_this->pd_s_float) +#define s_symbol (pd_this->pd_s_symbol) +#define s_bang (pd_this->pd_s_bang) +#define s_list (pd_this->pd_s_list) +#define s_anything (pd_this->pd_s_anything) +#define s_signal (pd_this->pd_s_signal) +#define s__N (pd_this->pd_s__N) +#define s__X (pd_this->pd_s__X) +#define s_x (pd_this->pd_s_x) +#define s_y (pd_this->pd_s_y) +#define s_ (pd_this->pd_s_) +#else +EXTERN t_symbol s_pointer, s_float, s_symbol, s_bang, s_list, s_anything, + s_signal, s__N, s__X, s_x, s_y, s_; +#endif + +EXTERN t_canvas *pd_getcanvaslist(void); +EXTERN int pd_getdspstate(void); + +/* x_text.c */ +EXTERN t_binbuf *text_getbufbyname(t_symbol *s); /* get binbuf from text obj */ +EXTERN void text_notifybyname(t_symbol *s); /* notify it was modified */ + +#if defined(_LANGUAGE_C_PLUS_PLUS) || defined(__cplusplus) +} +#endif + +#define __m_pd_h_ +#endif /* __m_pd_h_ */ diff --git a/plugins/puredata/sfizz_puredata.c b/plugins/puredata/sfizz_puredata.c new file mode 100644 index 000000000..4043b6cae --- /dev/null +++ b/plugins/puredata/sfizz_puredata.c @@ -0,0 +1,336 @@ +// 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 "GitBuildId.h" +#include +#include +#include +#include +#include +#include +#include + +static t_class* cls_sfizz_tilde; + +typedef struct _sfizz_tilde { + t_object obj; + t_outlet* outputs[2]; + t_inlet* input_cc; + t_inlet* input_bend; + t_inlet* input_touch; + t_inlet* input_polytouch; + sfizz_synth_t* synth; + int midi[3]; + int midinum; + t_symbol* dir; + char* filepath; +} t_sfizz_tilde; + +static t_float clamp01(t_float x) +{ + x = (x > 0) ? x : 0; + x = (x < 1) ? x : 1; + return x; +} + +static t_float clampB1(t_float x) +{ + x = (x > -1) ? x : -1; + x = (x < 1) ? x : 1; + return x; +} + +static void sfizz_tilde_set_file(t_sfizz_tilde* self, const char* file) +{ + const char* dir = self->dir->s_name; + char* filepath; + if (file[0] != '\0') { + filepath = malloc(strlen(dir) + 1 + strlen(file) + 1); + sprintf(filepath, "%s/%s", dir, file); + } + else { + filepath = malloc(1); + *filepath = '\0'; + } + free(self->filepath); + self->filepath = filepath; +} + +static bool sfizz_tilde_do_load(t_sfizz_tilde* self) +{ + bool loaded; + if (self->filepath[0] != '\0') + loaded = sfizz_load_or_import_file(self->synth, self->filepath, NULL); + else + loaded = sfizz_load_string(self->synth, "default.sfz", "sample=*sine"); + return loaded; +} + +static void* sfizz_tilde_new(t_symbol* sym, int argc, t_atom argv[]) +{ + (void)sym; + t_sfizz_tilde* self = (t_sfizz_tilde*)pd_new(cls_sfizz_tilde); + + const char* file; + if (argc == 0) + file = ""; + else if (argc == 1 && argv[0].a_type == A_SYMBOL) + file = argv[0].a_w.w_symbol->s_name; + else { + pd_free((t_pd*)self); + return NULL; + } + + self->dir = canvas_getcurrentdir(); + + self->outputs[0] = outlet_new(&self->obj, &s_signal); + self->outputs[1] = outlet_new(&self->obj, &s_signal); + + self->input_cc = inlet_new(&self->obj, &self->obj.ob_pd, &s_float, gensym("cc")); + self->input_bend = inlet_new(&self->obj, &self->obj.ob_pd, &s_float, gensym("bend")); + self->input_touch = inlet_new(&self->obj, &self->obj.ob_pd, &s_float, gensym("touch")); + self->input_polytouch = inlet_new(&self->obj, &self->obj.ob_pd, &s_float, gensym("polytouch")); + + sfizz_synth_t* synth = sfizz_create_synth(); + self->synth = synth; + + sfizz_set_sample_rate(synth, sys_getsr()); + sfizz_set_samples_per_block(synth, sys_getblksize()); + + sfizz_tilde_set_file(self, file); + if (!sfizz_tilde_do_load(self)) { + pd_free((t_pd*)self); + return NULL; + } + + return self; +} + +static void sfizz_tilde_free(t_sfizz_tilde* self) +{ + if (self->filepath) + free(self->filepath); + if (self->synth) + sfizz_free(self->synth); + if (self->outputs[0]) + outlet_free(self->outputs[0]); + if (self->outputs[1]) + outlet_free(self->outputs[1]); + if (self->input_cc) + inlet_free(self->input_cc); + if (self->input_bend) + inlet_free(self->input_bend); + if (self->input_touch) + inlet_free(self->input_touch); + if (self->input_polytouch) + inlet_free(self->input_polytouch); +} + +static t_int* sfizz_tilde_perform(t_int* w) +{ + t_sfizz_tilde* self; + t_sample* outputs[2]; + t_int nframes; + + w++; + self = (t_sfizz_tilde*)*w++; + outputs[0] = (t_sample*)*w++; + outputs[1] = (t_sample*)*w++; + nframes = (t_int)*w++; + + sfizz_render_block(self->synth, outputs, 2, nframes); + + return w; +} + +static void sfizz_tilde_dsp(t_sfizz_tilde* self, t_signal** sp) +{ + dsp_add( + &sfizz_tilde_perform, 4, (t_int)self, + (t_int)sp[0]->s_vec, (t_int)sp[1]->s_vec, (t_int)sp[0]->s_n); +} + +static void sfizz_tilde_list(t_sfizz_tilde* self, t_symbol* sym, int argc, t_atom* argv) +{ + (void)sym; + + if (argc == 2 && argv[0].a_type == A_FLOAT && argv[1].a_type == A_FLOAT) { + int key = (int)argv[0].a_w.w_float; + if (key < 0 || key > 127) + return; + t_float vel = clamp01(argv[1].a_w.w_float / 127); + if (vel > 0) + sfizz_send_hd_note_on(self->synth, 0, key, vel); + else + sfizz_send_hd_note_off(self->synth, 0, key, 0); + } +} + +static void sfizz_tilde_midiin(t_sfizz_tilde* self, t_float f) +{ + int byte = (int)f; + bool isstatus = (byte & 0x80) != 0; + + int* midi = self->midi; + int midinum = self->midinum; + + // + if (isstatus) { + midi[0] = byte; + midinum = 1; + } + else if (midinum != -1 && midinum < 3) + midi[midinum++] = byte; + else + midinum = -1; + + // + switch (midinum) { + case 2: + switch (midi[0] & 0xf0) { + case 0xd0: // channel aftertouch + sfizz_send_channel_aftertouch(self->synth, 0, midi[1]); + break; + } + break; + case 3: + switch (midi[0] & 0xf0) { + case 0x90: // note on + if (midi[2] == 0) + goto noteoff; + sfizz_send_note_on(self->synth, 0, midi[1], midi[2]); + break; + case 0x80: // note off + noteoff: + sfizz_send_note_off(self->synth, 0, midi[1], midi[2]); + break; + case 0xb0: // controller + sfizz_send_cc(self->synth, 0, midi[1], midi[2]); + break; + case 0xa0: // key aftertouch + sfizz_send_poly_aftertouch(self->synth, 0, midi[1], midi[2]); + break; + case 0xe0: // pitch bend + sfizz_send_pitch_wheel(self->synth, 0, (midi[1] + (midi[2] << 7)) - 8192); + break; + } + break; + } + + self->midinum = midinum; +} + +static void sfizz_tilde_load(t_sfizz_tilde* self, t_symbol* sym) +{ + sfizz_tilde_set_file(self, sym->s_name); + sfizz_tilde_do_load(self); +} + +static void sfizz_tilde_reload(t_sfizz_tilde* self, t_float value) +{ + (void)value; + sfizz_tilde_do_load(self); +} + +static void sfizz_tilde_hdcc(t_sfizz_tilde* self, t_float f1, t_float f2) +{ + int cc = (int)f1; + if (cc < 0 || cc >= SFIZZ_NUM_CCS) + return; + sfizz_automate_hdcc(self->synth, 0, (int)cc, clamp01(f2)); +} + +static void sfizz_tilde_cc(t_sfizz_tilde* self, t_float f1, t_float f2) +{ + sfizz_tilde_hdcc(self, f1, f2 / 127); +} + +static void sfizz_tilde_hdbend(t_sfizz_tilde* self, t_float f1) +{ + sfizz_send_hd_pitch_wheel(self->synth, 0, clampB1(f1)); +} + +static void sfizz_tilde_bend(t_sfizz_tilde* self, t_float f1) +{ + return sfizz_tilde_hdbend(self, f1 / 8191); +} + +static void sfizz_tilde_hdtouch(t_sfizz_tilde* self, t_float f1) +{ + sfizz_send_hd_channel_aftertouch(self->synth, 0, clamp01(f1)); +} + +static void sfizz_tilde_touch(t_sfizz_tilde* self, t_float f1) +{ + sfizz_tilde_hdtouch(self, f1 / 127); +} + +static void sfizz_tilde_hdpolytouch(t_sfizz_tilde* self, t_float key, t_float f2) +{ + if (key < 0 || key > 127) + return; + sfizz_send_hd_poly_aftertouch(self->synth, 0, (int)key, clamp01(f2)); +} + +static void sfizz_tilde_polytouch(t_sfizz_tilde* self, t_float f1, t_float f2) +{ + sfizz_tilde_hdpolytouch(self, f1, f2 / 127); +} + +static void sfizz_tilde_voices(t_sfizz_tilde* self, t_float f1) +{ + int numvoices = (int)f1; + numvoices = (numvoices < 1) ? 1 : numvoices; + sfizz_set_num_voices(self->synth, numvoices); +} + +#if defined(_WIN32) +__declspec(dllexport) +#else +__attribute__((visibility("default"))) +#endif +void sfizz_setup() +{ + if (GitBuildId[0]) + post("sfizz external for Puredata, version '%s.%s'", SFIZZ_VERSION, GitBuildId); + else + post("sfizz external for Puredata, version '%s'", SFIZZ_VERSION); + + cls_sfizz_tilde = class_new( + gensym("sfizz~"), + (t_newmethod)&sfizz_tilde_new, + (t_method)&sfizz_tilde_free, + sizeof(t_sfizz_tilde), + CLASS_DEFAULT, A_GIMME, A_NULL); + class_addmethod( + cls_sfizz_tilde, (t_method)&sfizz_tilde_dsp, gensym("dsp"), A_CANT, A_NULL); + class_addlist( + cls_sfizz_tilde, (t_method)&sfizz_tilde_list); + class_addmethod( + cls_sfizz_tilde, (t_method)&sfizz_tilde_midiin, &s_float, A_FLOAT, A_NULL); + class_addmethod( + cls_sfizz_tilde, (t_method)&sfizz_tilde_load, gensym("load"), A_DEFSYM, A_NULL); + class_addmethod( + cls_sfizz_tilde, (t_method)&sfizz_tilde_reload, gensym("reload"), A_DEFFLOAT, A_NULL); + class_addmethod( + cls_sfizz_tilde, (t_method)&sfizz_tilde_cc, gensym("cc"), A_FLOAT, A_FLOAT, A_NULL); + class_addmethod( + cls_sfizz_tilde, (t_method)&sfizz_tilde_hdcc, gensym("hdcc"), A_FLOAT, A_FLOAT, A_NULL); + class_addmethod( + cls_sfizz_tilde, (t_method)&sfizz_tilde_bend, gensym("bend"), A_FLOAT, A_NULL); + class_addmethod( + cls_sfizz_tilde, (t_method)&sfizz_tilde_hdbend, gensym("hdbend"), A_FLOAT, A_NULL); + class_addmethod( + cls_sfizz_tilde, (t_method)&sfizz_tilde_touch, gensym("touch"), A_FLOAT, A_NULL); + class_addmethod( + cls_sfizz_tilde, (t_method)&sfizz_tilde_hdtouch, gensym("hdtouch"), A_FLOAT, A_NULL); + class_addmethod( + cls_sfizz_tilde, (t_method)&sfizz_tilde_polytouch, gensym("polytouch"), A_FLOAT, A_FLOAT, A_NULL); + class_addmethod( + cls_sfizz_tilde, (t_method)&sfizz_tilde_hdpolytouch, gensym("hdpolytouch"), A_FLOAT, A_FLOAT, A_NULL); + class_addmethod( + cls_sfizz_tilde, (t_method)&sfizz_tilde_voices, gensym("voices"), A_FLOAT, A_NULL); +} diff --git a/plugins/puredata/sfizz~-help.pd b/plugins/puredata/sfizz~-help.pd new file mode 100644 index 000000000..c3024d327 --- /dev/null +++ b/plugins/puredata/sfizz~-help.pd @@ -0,0 +1,60 @@ +#N canvas 614 464 709 426 12; +#X declare -lib sfizz; +#X obj 4 3 cnv 15 700 80 empty empty sfizz 20 30 0 40 -261682 -66577 +0; +#X text 45 54 A synthesizer for instruments in SFZ format; +#X text 33 167 create a SFZ instrument; +#X obj 26 323 midiin; +#X text 23 300 connect MIDI-in; +#X obj 162 223 dac~; +#X msg 177 345 voices \$1; +#X text 175 301 modify the polyphony; +#X obj 376 326 hsl 127 15 0 1 0 1 empty empty empty -2 -8 0 10 -262144 +-1 -1 6300 1; +#X floatatom 466 346 5 0 0 0 - - -; +#X text 372 302 modulate a parameter; +#X msg 373 346 hdcc 300 \$1; +#X obj 27 115 declare -lib sfizz; +#X obj 163 192 sfizz~ example.sfz; +#X text 18 93 load the sfizz library; +#X msg 221 115 \; pd dsp 1; +#X text 218 94 click to turn on DSP; +#X text 153 247 output stereo audio; +#X msg 398 223 reload; +#X msg 498 223 load example.sfz; +#X text 396 200 reload the instrument or load another; +#X obj 241 165 r \$0-input; +#X obj 26 348 s \$0-input; +#X obj 177 370 s \$0-input; +#X obj 373 371 s \$0-input; +#X obj 398 248 s \$0-input; +#X obj 498 248 s \$0-input; +#X obj 177 326 nbx 5 14 1 256 0 1 empty empty empty 0 -8 0 10 -262144 +-1 -1 64 256; +#X text 567 302 send a note; +#X obj 568 348 makenote 100 500; +#X obj 568 373 list; +#X obj 568 398 s \$0-input; +#X msg 568 323 60; +#X obj 386 92 cnv 15 300 100 empty empty Inlets 20 12 0 14 -233017 +-66577 0; +#X text 395 140 3: pitch bend; +#X text 395 154 4: channel aftertouch; +#X text 395 168 5: polyphonic aftertouch and key; +#X text 395 126 2: controller and value; +#X text 395 112 1: note and velocity; +#X connect 3 0 22 0; +#X connect 6 0 23 0; +#X connect 8 0 11 0; +#X connect 8 0 9 0; +#X connect 11 0 24 0; +#X connect 13 0 5 0; +#X connect 13 1 5 1; +#X connect 18 0 25 0; +#X connect 19 0 26 0; +#X connect 21 0 13 0; +#X connect 27 0 6 0; +#X connect 29 0 30 0; +#X connect 29 1 30 1; +#X connect 30 0 31 0; +#X connect 32 0 29 0; diff --git a/plugins/vst/CMakeLists.txt b/plugins/vst/CMakeLists.txt index a92125ad3..635658098 100644 --- a/plugins/vst/CMakeLists.txt +++ b/plugins/vst/CMakeLists.txt @@ -33,6 +33,7 @@ add_library(sfizz-vst3-core STATIC EXCLUDE_FROM_ALL SfizzVstState.h SfizzVstParameters.h SfizzVstUpdates.h + SfizzVstUpdates.hpp SfizzVstUpdates.cpp SfizzVstIDs.h OrderedEventProcessor.h @@ -135,7 +136,8 @@ if(SFIZZ_VST) if(NOT MSVC) install(DIRECTORY "${PROJECT_BINARY_DIR}/${VSTPLUGIN_BUNDLE_NAME}" DESTINATION "${VSTPLUGIN_INSTALL_DIR}" - COMPONENT "vst") + COMPONENT "vst" + USE_SOURCE_PERMISSIONS) bundle_dylibs(vst "${VSTPLUGIN_INSTALL_DIR}/${VSTPLUGIN_BUNDLE_NAME}/Contents/MacOS/sfizz" COMPONENT "vst") @@ -283,7 +285,8 @@ elseif(SFIZZ_AU) if(AUPLUGIN_INSTALL_DIR) install(DIRECTORY "${PROJECT_BINARY_DIR}/${AUPLUGIN_BUNDLE_NAME}" DESTINATION "${AUPLUGIN_INSTALL_DIR}" - COMPONENT "au") + COMPONENT "au" + USE_SOURCE_PERMISSIONS) bundle_dylibs(au "${AUPLUGIN_INSTALL_DIR}/${AUPLUGIN_BUNDLE_NAME}/Contents/MacOS/sfizz" COMPONENT "au") @@ -354,7 +357,8 @@ if(SFIZZ_VST2) if(VST2PLUGIN_INSTALL_DIR) install(DIRECTORY "${PROJECT_BINARY_DIR}/${VST2PLUGIN_BUNDLE_NAME}" DESTINATION "${VST2PLUGIN_INSTALL_DIR}" - COMPONENT "vst2") + COMPONENT "vst2" + USE_SOURCE_PERMISSIONS) if(APPLE) bundle_dylibs(vst2 "${VST2PLUGIN_INSTALL_DIR}/${VST2PLUGIN_BUNDLE_NAME}/Contents/Binary/sfizz.${CMAKE_SHARED_MODULE_SUFFIX}" diff --git a/plugins/vst/SfizzVstController.cpp b/plugins/vst/SfizzVstController.cpp index 4e92f8b43..19cac7cc4 100644 --- a/plugins/vst/SfizzVstController.cpp +++ b/plugins/vst/SfizzVstController.cpp @@ -8,26 +8,38 @@ #include "SfizzVstEditor.h" #include "SfizzVstParameters.h" #include "SfizzVstIDs.h" +#include "plugin/InstrumentDescription.h" #include "base/source/fstreamer.h" #include "base/source/updatehandler.h" +#include +#include +#include +#include + +enum { kProgramListID = 0 }; tresult PLUGIN_API SfizzVstControllerNoUi::initialize(FUnknown* context) { - tresult result = EditController::initialize(context); + tresult result = EditControllerEx1::initialize(context); if (result != kResultTrue) return result; // initialize the update handler Steinberg::UpdateHandler::instance(); + // initialize the thread checker + threadChecker_ = Vst::ThreadChecker::create(); + // create update objects - oscUpdate_ = Steinberg::owned(new OSCUpdate); - noteUpdate_ = Steinberg::owned(new NoteUpdate); + queuedUpdates_ = Steinberg::owned(new QueuedUpdates); sfzUpdate_ = Steinberg::owned(new SfzUpdate); sfzDescriptionUpdate_ = Steinberg::owned(new SfzDescriptionUpdate); scalaUpdate_ = Steinberg::owned(new ScalaUpdate); playStateUpdate_ = Steinberg::owned(new PlayStateUpdate); + // Unit + addUnit(new Vst::Unit(Steinberg::String("Root"), Vst::kRootUnitId, Vst::kNoParentUnitId, kProgramListID)); + // Parameters Vst::ParamID pid = 0; @@ -92,6 +104,22 @@ tresult PLUGIN_API SfizzVstControllerNoUi::initialize(FUnknown* context) Vst::kRootUnitId, shortTitle)); } + // Volume levels + parameters.addParameter( + SfizzRange::getForParameter(kPidLeftLevel).createParameter( + Steinberg::String("Left level"), pid++, nullptr, + 0, Vst::ParameterInfo::kIsReadOnly|Vst::ParameterInfo::kIsHidden, Vst::kRootUnitId)); + parameters.addParameter( + SfizzRange::getForParameter(kPidRightLevel).createParameter( + Steinberg::String("Right level"), pid++, nullptr, + 0, Vst::ParameterInfo::kIsReadOnly|Vst::ParameterInfo::kIsHidden, Vst::kRootUnitId)); + + // Editor status + parameters.addParameter( + SfizzRange::getForParameter(kPidEditorOpen).createParameter( + Steinberg::String("Editor open"), pid++, nullptr, + 0, Vst::ParameterInfo::kIsReadOnly|Vst::ParameterInfo::kIsHidden, Vst::kRootUnitId)); + // Initial MIDI mapping for (int32 i = 0; i < Vst::kCountCtrlNumber; ++i) { Vst::ParamID id = Vst::kNoParamId; @@ -110,12 +138,22 @@ tresult PLUGIN_API SfizzVstControllerNoUi::initialize(FUnknown* context) midiMapping_[i] = id; } + // Program list + IPtr list = Steinberg::owned( + new Vst::ProgramListWithPitchNames(Steinberg::String("Programs"), kProgramListID, Vst::kRootUnitId)); + list->addProgram(Steinberg::String("Default")); + addProgramList(list); + list->addRef(); + + // Use linear knobs + setKnobMode(kLinearMode); + return kResultTrue; } tresult PLUGIN_API SfizzVstControllerNoUi::terminate() { - return EditController::terminate(); + return EditControllerEx1::terminate(); } tresult PLUGIN_API SfizzVstControllerNoUi::getMidiControllerAssignment(int32 busIndex, int16 channel, Vst::CtrlNumber midiControllerNumber, Vst::ParamID& id) @@ -132,6 +170,44 @@ tresult PLUGIN_API SfizzVstControllerNoUi::getMidiControllerAssignment(int32 bus return kResultTrue; } +int32 PLUGIN_API SfizzVstControllerNoUi::getKeyswitchCount(int32 busIndex, int16 channel) +{ + (void)channel; + + if (busIndex != 0) + return 0; + + return keyswitches_.size(); +} + +tresult PLUGIN_API SfizzVstControllerNoUi::getKeyswitchInfo(int32 busIndex, int16 channel, int32 keySwitchIndex, Vst::KeyswitchInfo& info) +{ + (void)channel; + + if (busIndex != 0) + return kResultFalse; + + if (keySwitchIndex < 0 || keySwitchIndex >= keyswitches_.size()) + return kResultFalse; + + info = keyswitches_[keySwitchIndex]; + return kResultTrue; +} + +tresult PLUGIN_API SfizzVstControllerNoUi::beginEditFromHost(Vst::ParamID paramID) +{ + // Note(jpc) implementing this interface is a workaround to make + // non-automatable parameters editable in Ardour (as of 6.6) + (void)paramID; + return kResultTrue; +} + +tresult PLUGIN_API SfizzVstControllerNoUi::endEditFromHost(Vst::ParamID paramID) +{ + (void)paramID; + return kResultTrue; +} + tresult PLUGIN_API SfizzVstControllerNoUi::getParamStringByValue(Vst::ParamID tag, Vst::ParamValue valueNormalized, Vst::String128 string) { switch (tag) { @@ -146,7 +222,7 @@ tresult PLUGIN_API SfizzVstControllerNoUi::getParamStringByValue(Vst::ParamID ta } } - return EditController::getParamStringByValue(tag, valueNormalized, string); + return EditControllerEx1::getParamStringByValue(tag, valueNormalized, string); } tresult PLUGIN_API SfizzVstControllerNoUi::getParamValueByString(Vst::ParamID tag, Vst::TChar* string, Vst::ParamValue& valueNormalized) @@ -164,7 +240,7 @@ tresult PLUGIN_API SfizzVstControllerNoUi::getParamValueByString(Vst::ParamID ta } } - return EditController::getParamValueByString(tag, string, valueNormalized); + return EditControllerEx1::getParamValueByString(tag, string, valueNormalized); } tresult SfizzVstControllerNoUi::setParam(Vst::ParamID tag, float value) @@ -207,109 +283,157 @@ tresult PLUGIN_API SfizzVstControllerNoUi::setComponentState(IBStream* stream) tresult SfizzVstControllerNoUi::notify(Vst::IMessage* message) { - // Note: may be called from any thread (Reaper) + // Note: is expected to be called from the controller thread only - tresult result = EditController::notify(message); + tresult result = EditControllerEx1::notify(message); if (result != kResultFalse) return result; - const char* id = message->getMessageID(); - Vst::IAttributeList* attr = message->getAttributes(); + if (!threadChecker_->test()) { + static std::atomic_bool warn_once_flag { false }; + if (!warn_once_flag.exchange(true)) + fprintf(stderr, "[sfizz] controller notification arrives from the wrong thread\n"); + } - /// - auto stringFromBinaryAttribute = [attr](const char* id, absl::string_view& string) -> tresult { - const void* data = nullptr; - uint32 size = 0; - tresult result = attr->getBinary(id, data, size); - if (result == kResultTrue) - string = absl::string_view(reinterpret_cast(data), size); - return result; - }; + const char* id = message->getMessageID(); /// - if (!strcmp(id, "LoadedSfz")) { - absl::string_view sfzFile; - absl::string_view sfzDescriptionBlob; + if (!strcmp(id, SfzUpdate::getFClassID())) { + if (!sfzUpdate_->convertFromMessage(*message)) { + assert(false); + return kResultFalse; + } - result = stringFromBinaryAttribute("File", sfzFile); - if (result != kResultTrue) - return result; + // update the program name and notify + std::string name = fs::u8path(sfzUpdate_->getPath()).filename().u8string(); + if (absl::EndsWithIgnoreCase(name, ".sfz")) + name.resize(name.size() - 4); + setProgramName(kProgramListID, 0, Steinberg::String(name.c_str())); - result = stringFromBinaryAttribute("Description", sfzDescriptionBlob); - if (result != kResultTrue) - return result; + FUnknownPtr unitHandler(getComponentHandler()); + if (unitHandler) + unitHandler->notifyProgramListChange(kProgramListID, 0); - sfzUpdate_->setPath(std::string(sfzFile)); + // sfzUpdate_->deferUpdate(); - sfzDescriptionUpdate_->setDescription(std::string(sfzDescriptionBlob)); - sfzDescriptionUpdate_->deferUpdate(); } - else if (!strcmp(id, "LoadedScala")) { - absl::string_view scalaFile; + else if (!strcmp(id, SfzDescriptionUpdate::getFClassID())) { + if (!sfzDescriptionUpdate_->convertFromMessage(*message)) { + assert(false); + return kResultFalse; + } - result = stringFromBinaryAttribute("File", scalaFile); - if (result != kResultTrue) - return result; + // parse the description blob + const InstrumentDescription desc = parseDescriptionBlob( + sfzDescriptionUpdate_->getDescription()); + + // update pitch names and notify + Vst::ProgramListWithPitchNames* list = + static_cast(getProgramList(kProgramListID)); + for (int16 pitch = 0; pitch < 128; ++pitch) { + Steinberg::String pitchName; + if (desc.keyUsed.test(pitch) && !desc.keyLabel[pitch].empty()) + pitchName = Steinberg::String(desc.keyLabel[pitch].c_str()); + else if (desc.keyswitchUsed.test(pitch) && !desc.keyswitchLabel[pitch].empty()) + pitchName = Steinberg::String(desc.keyswitchLabel[pitch].c_str()); + + list->setPitchName(0, pitch, pitchName); + } - scalaUpdate_->setPath(std::string(scalaFile)); - scalaUpdate_->deferUpdate(); - } - else if (!strcmp(id, "NotifiedPlayState")) { - const void* data = nullptr; - uint32 size = 0; - result = attr->getBinary("PlayState", data, size); + FUnknownPtr unitHandler(getComponentHandler()); + if (unitHandler) + unitHandler->notifyProgramListChange(kProgramListID, 0); + + // update the key switches and notify + size_t idKeyswitch = 0; + for (int16 pitch = 0; pitch < 128; ++pitch) + idKeyswitch += desc.keyswitchUsed.test(pitch); + keyswitches_.resize(idKeyswitch); + + idKeyswitch = 0; + for (int16 pitch = 0; pitch < 128; ++pitch) { + if (!desc.keyswitchUsed.test(pitch)) + continue; + Vst::KeyswitchInfo info {}; + info.typeId = Vst::kNoteOnKeyswitchTypeID; + Steinberg::String(desc.keyswitchLabel[pitch].c_str()).copyTo(info.title); + Steinberg::String(desc.keyswitchLabel[pitch].c_str()).copyTo(info.shortTitle); + info.keyswitchMin = pitch; // TODO reexamine this when supporting keyswitch groups + info.keyswitchMax = pitch; // TODO reexamine this when supporting keyswitch groups + info.keyRemapped = pitch; + info.unitId = Vst::kRootUnitId; + info.flags = 0; + keyswitches_[idKeyswitch++] = info; + } + + if (Vst::IComponentHandler* componentHandler = getComponentHandler()) + componentHandler->restartComponent(Vst::kKeyswitchChanged); + + // update the parameter titles and notify + for (uint32 cc = 0; cc < sfz::config::numCCs; ++cc) { + Vst::ParamID pid = kPidCC0 + cc; + Vst::Parameter* param = getParameterObject(pid); + Vst::ParameterInfo& info = param->getInfo(); + Steinberg::String title; + Steinberg::String shortTitle; + if (!desc.ccLabel[cc].empty()) { + title = desc.ccLabel[cc].c_str(); + shortTitle = title; + } + else { + title.printf("Controller %u", cc); + shortTitle.printf("CC%u", cc); + } + title.copyTo(info.title); + shortTitle.copyTo(info.shortTitle); + } - if (result != kResultTrue) - return result; + if (Vst::IComponentHandler* componentHandler = getComponentHandler()) + componentHandler->restartComponent(Vst::kParamTitlesChanged); - playStateUpdate_->setState(*static_cast(data)); + // + sfzDescriptionUpdate_->deferUpdate(); + } + else if (!strcmp(id, ScalaUpdate::getFClassID())) { + if (!scalaUpdate_->convertFromMessage(*message)) { + assert(false); + return kResultFalse; + } + scalaUpdate_->deferUpdate(); + } + else if (!strcmp(id, PlayStateUpdate::getFClassID())) { + if (!playStateUpdate_->convertFromMessage(*message)) { + assert(false); + return kResultFalse; + } playStateUpdate_->deferUpdate(); } - else if (!strcmp(id, "ReceivedMessage")) { - const void* data = nullptr; - uint32 size = 0; - result = attr->getBinary("Message", data, size); - - if (result != kResultTrue) - return result; - - // this is a synchronous send, because the update object gets reused - oscUpdate_->setMessage(data, size, false); - oscUpdate_->changed(); - oscUpdate_->clear(); + else if (!strcmp(id, OSCUpdate::getFClassID())) { + IPtr update = OSCUpdate::createFromMessage(*message); + if (!update) { + assert(false); + return kResultFalse; + } + queuedUpdates_->enqueue(update); + queuedUpdates_->deferUpdate(); } - else if (!strcmp(id, "NoteEvents")) { - const void* data = nullptr; - uint32 size = 0; - result = attr->getBinary("Events", data, size); - - const auto* events = reinterpret_cast< - const std::pair*>(data); - uint32 numEvents = size / sizeof(events[0]); - - // this is a synchronous send, because the update object gets reused - noteUpdate_->setEvents(events, numEvents, false); - noteUpdate_->changed(); - noteUpdate_->clear(); + else if (!strcmp(id, NoteUpdate::getFClassID())) { + IPtr update = NoteUpdate::createFromMessage(*message); + if (!update) { + assert(false); + return kResultFalse; + } + queuedUpdates_->enqueue(update); + queuedUpdates_->deferUpdate(); } - else if (!strcmp(id, "Automate")) { - const void* data = nullptr; - uint32 size = 0; - result = attr->getBinary("Data", data, size); - - if (result != kResultTrue) - return result; - - const uint8* pos = reinterpret_cast(data); - const uint8* end = pos + size; - - while (static_cast(end - pos) >= sizeof(uint32) + sizeof(float)) { - Vst::ParamID pid = *reinterpret_cast(pos); - pos += sizeof(uint32); - float value = *reinterpret_cast(pos); - pos += sizeof(float); - setParam(pid, value); + else if (!strcmp(id, AutomationUpdate::getFClassID())) { + IPtr update = AutomationUpdate::createFromMessage(*message); + if (!update) { + assert(false); + return kResultFalse; } + for (AutomationUpdate::Item item : update->getItems()) + setParam(item.first, item.second); } return result; @@ -326,20 +450,17 @@ IPlugView* PLUGIN_API SfizzVstController::createView(FIDString _name) if (name != Vst::ViewType::kEditor) return nullptr; - std::vector continuousUpdates; - continuousUpdates.push_back(sfzUpdate_); - continuousUpdates.push_back(sfzDescriptionUpdate_); - continuousUpdates.push_back(scalaUpdate_); - continuousUpdates.push_back(playStateUpdate_); + std::vector updates; + updates.push_back(queuedUpdates_); + updates.push_back(sfzUpdate_); + updates.push_back(sfzDescriptionUpdate_); + updates.push_back(scalaUpdate_); + updates.push_back(playStateUpdate_); for (uint32 i = 0, n = parameters.getParameterCount(); i < n; ++i) - continuousUpdates.push_back(parameters.getParameterByIndex(i)); - - std::vector triggerUpdates; - triggerUpdates.push_back(oscUpdate_); - triggerUpdates.push_back(noteUpdate_); + updates.push_back(parameters.getParameterByIndex(i)); IPtr editor = Steinberg::owned( - new SfizzVstEditor(this, absl::MakeSpan(continuousUpdates), absl::MakeSpan(triggerUpdates))); + new SfizzVstEditor(this, absl::MakeSpan(updates))); editor->remember(); return editor; diff --git a/plugins/vst/SfizzVstController.h b/plugins/vst/SfizzVstController.h index b8d11cd8c..c26e4b18d 100644 --- a/plugins/vst/SfizzVstController.h +++ b/plugins/vst/SfizzVstController.h @@ -9,7 +9,9 @@ #include "SfizzVstUpdates.h" #include "public.sdk/source/vst/vsteditcontroller.h" #include "public.sdk/source/vst/vstparameters.h" +#include "public.sdk/source/common/threadchecker.h" #include "pluginterfaces/vst/ivstmidicontrollers.h" +#include "pluginterfaces/vst/ivstnoteexpression.h" #include "vstgui/plugin-bindings/vst3editor.h" #include #include @@ -19,8 +21,10 @@ class SfizzVstEditor; using namespace Steinberg; using namespace VSTGUI; -class SfizzVstControllerNoUi : public Vst::EditController, - public Vst::IMidiMapping { +class SfizzVstControllerNoUi : public Vst::EditControllerEx1, + public Vst::IMidiMapping, + public Vst::IKeyswitchController, + public Vst::IEditControllerHostEditing { public: virtual ~SfizzVstControllerNoUi() {} @@ -29,6 +33,12 @@ class SfizzVstControllerNoUi : public Vst::EditController, tresult PLUGIN_API getMidiControllerAssignment(int32 busIndex, int16 channel, Vst::CtrlNumber midiControllerNumber, Vst::ParamID& id) override; + int32 PLUGIN_API getKeyswitchCount (int32 busIndex, int16 channel) override; + tresult PLUGIN_API getKeyswitchInfo (int32 busIndex, int16 channel, int32 keySwitchIndex, Vst::KeyswitchInfo& info) override; + + tresult PLUGIN_API beginEditFromHost(Vst::ParamID paramID) override; + tresult PLUGIN_API endEditFromHost(Vst::ParamID paramID) override; + tresult PLUGIN_API getParamStringByValue(Vst::ParamID tag, Vst::ParamValue valueNormalized, Vst::String128 string) override; tresult PLUGIN_API getParamValueByString(Vst::ParamID tag, Vst::TChar* string, Vst::ParamValue& valueNormalized) override; @@ -37,20 +47,23 @@ class SfizzVstControllerNoUi : public Vst::EditController, tresult PLUGIN_API notify(Vst::IMessage* message) override; // interfaces - OBJ_METHODS(SfizzVstControllerNoUi, Vst::EditController) + OBJ_METHODS(SfizzVstControllerNoUi, Vst::EditControllerEx1) DEFINE_INTERFACES DEF_INTERFACE(Vst::IMidiMapping) - END_DEFINE_INTERFACES(Vst::EditController) - REFCOUNT_METHODS(Vst::EditController) + DEF_INTERFACE(Vst::IKeyswitchController) + DEF_INTERFACE(Vst::IEditControllerHostEditing) + END_DEFINE_INTERFACES(Vst::EditControllerEx1) + REFCOUNT_METHODS(Vst::EditControllerEx1) protected: - Steinberg::IPtr oscUpdate_; - Steinberg::IPtr noteUpdate_; + std::unique_ptr threadChecker_; + Steinberg::IPtr queuedUpdates_; Steinberg::IPtr sfzUpdate_; Steinberg::IPtr sfzDescriptionUpdate_; Steinberg::IPtr scalaUpdate_; Steinberg::IPtr playStateUpdate_; Vst::ParamID midiMapping_[Vst::kCountCtrlNumber] {}; + std::vector keyswitches_; }; class SfizzVstController : public SfizzVstControllerNoUi, public VSTGUI::VST3EditorDelegate { diff --git a/plugins/vst/SfizzVstEditor.cpp b/plugins/vst/SfizzVstEditor.cpp index 8a95afa74..2e1506ca9 100644 --- a/plugins/vst/SfizzVstEditor.cpp +++ b/plugins/vst/SfizzVstEditor.cpp @@ -18,6 +18,7 @@ #include "X11RunLoop.h" #endif #include +#include using namespace VSTGUI; @@ -29,14 +30,10 @@ enum { kNoteEventQueueSize = 8192, }; -SfizzVstEditor::SfizzVstEditor( - SfizzVstController* controller, - absl::Span continuousUpdates, - absl::Span triggerUpdates) +SfizzVstEditor::SfizzVstEditor(SfizzVstController* controller, absl::Span updates) : VSTGUIEditor(controller, &sfizzUiViewRect), oscTemp_(new uint8_t[kOscTempSize]), - continuousUpdates_(continuousUpdates.begin(), continuousUpdates.end()), - triggerUpdates_(triggerUpdates.begin(), triggerUpdates.end()) + updates_(updates.begin(), updates.end()) { } @@ -62,24 +59,8 @@ bool PLUGIN_API SfizzVstEditor::open(void* parent, const VSTGUI::PlatformType& p config = &x11config; #endif - Editor* editor = editor_.get(); - if (!editor) { - editor = new Editor(*this); - editor_.reset(editor); - } - - { - std::lock_guard lock(oscQueueMutex_); - OscByteVec* queue = new OscByteVec; - oscQueue_.reset(queue); - queue->reserve(kOscQueueSize); - } - { - std::lock_guard lock(noteEventQueueMutex_); - NoteEventsVec* queue = new NoteEventsVec; - noteEventQueue_.reset(queue); - queue->reserve(kNoteEventQueueSize); - } + Editor* editor = new Editor(*this); + editor_.reset(editor); if (!frame->open(parent, platformType, config)) { fprintf(stderr, "[sfizz] error opening frame\n"); @@ -88,14 +69,16 @@ bool PLUGIN_API SfizzVstEditor::open(void* parent, const VSTGUI::PlatformType& p editor->open(*frame); - for (FObject* update : continuousUpdates_) - update->addDependent(this); - for (FObject* update : triggerUpdates_) + for (FObject* update : updates_) update->addDependent(this); + threadChecker_ = Vst::ThreadChecker::create(); + + parametersToUpdate_.clear(); + Steinberg::IdleUpdateHandler::start(); - for (FObject* update : continuousUpdates_) + for (FObject* update : updates_) update->deferUpdate(); // let the editor know about plugin format @@ -122,6 +105,8 @@ bool PLUGIN_API SfizzVstEditor::open(void* parent, const VSTGUI::PlatformType& p uiReceiveValue(EditId::UserFilesDir, userFilesDir.value_or(fs::path()).u8string()); uiReceiveValue(EditId::FallbackFilesDir, SfizzPaths::getSfzFallbackDefaultPath().u8string()); + updateEditorIsOpenParameter(); + return true; } @@ -131,28 +116,36 @@ void PLUGIN_API SfizzVstEditor::close() if (frame) { Steinberg::IdleUpdateHandler::stop(); - for (FObject* update : continuousUpdates_) - update->removeDependent(this); - for (FObject* update : triggerUpdates_) + for (FObject* update : updates_) update->removeDependent(this); - if (editor_) + if (editor_) { editor_->close(); + editor_ = nullptr; + } + if (frame->getNbReference() != 1) frame->forget(); - else + else { frame->close(); +#if !defined(__APPLE__) && !defined(_WIN32) + // if vstgui is done using the runloop, destroy it + if (!RunLoop::get()) + _runLoop = nullptr; +#endif + } this->frame = nullptr; } - { - std::lock_guard lock(oscQueueMutex_); - oscQueue_.reset(); - } - { - std::lock_guard lock(noteEventQueueMutex_); - noteEventQueue_.reset(); - } + updateEditorIsOpenParameter(); +} + +void SfizzVstEditor::updateEditorIsOpenParameter() +{ + SfizzVstController* ctrl = getController(); + bool editorIsOpen = frame && frame->isVisible(); + ctrl->setParamNormalized(kPidEditorOpen, editorIsOpen); + ctrl->performEdit(kPidEditorOpen, editorIsOpen); } /// @@ -171,15 +164,13 @@ CMessageResult SfizzVstEditor::notify(CBaseObject* sender, const char* message) // notifier of X11 events is working. If there is, remove this and // avoid polluting Linux hosts which implement the loop correctly. runLoop->processSomeEvents(); - - runLoop->cleanupDeadHandlers(); } } #endif if (message == CVSTGUITimer::kMsgTimer) { - processOscQueue(); - processNoteEventQueue(); + processParameterUpdates(); + updateEditorIsOpenParameter(); // Note(jpc) for Reaper, it can fail at open time } return result; @@ -187,34 +178,51 @@ CMessageResult SfizzVstEditor::notify(CBaseObject* sender, const char* message) void PLUGIN_API SfizzVstEditor::update(FUnknown* changedUnknown, int32 message) { + if (processUpdate(changedUnknown, message)) + return; + + Vst::VSTGUIEditor::update(changedUnknown, message); +} + +bool SfizzVstEditor::processUpdate(FUnknown* changedUnknown, int32 message) +{ + if (QueuedUpdates* update = FCast(changedUnknown)) { + for (FObject* queuedUpdate : update->getUpdates(this)) + processUpdate(queuedUpdate, message); + return true; + } + if (OSCUpdate* update = FCast(changedUnknown)) { - // this update is synchronous: may happen from non-UI thread - uint32 size = update->size(); - if (size > 0) { - const uint8_t* bytes = reinterpret_cast(update->data()); - std::lock_guard lock(oscQueueMutex_); - if (OscByteVec* queue = oscQueue_.get()) - std::copy(bytes, bytes + size, std::back_inserter(*queue)); + const uint8* oscData = update->data(); + uint32 oscSize = update->size(); + + const char* path; + const char* sig; + const sfizz_arg_t* args; + uint8_t buffer[1024]; + + uint32_t msgSize; + while ((msgSize = sfizz_extract_message(oscData, oscSize, buffer, sizeof(buffer), &path, &sig, &args)) > 0) { + uiReceiveMessage(path, sig, args); + oscData += msgSize; + oscSize -= msgSize; } - return; + + return true; } if (NoteUpdate* update = FCast(changedUnknown)) { - // this update is synchronous: may happen from non-UI thread + const NoteUpdate::Item* events = update->events(); uint32 count = update->count(); - if (count > 0) { - const auto* events = update->events(); - std::lock_guard lock(noteEventQueueMutex_); - if (NoteEventsVec* queue = noteEventQueue_.get()) - std::copy(events, events + count, std::back_inserter(*queue)); - } - return; + for (uint32 i = 0; i < count; ++i) + uiReceiveValue(editIdForKey(events[i].first), events[i].second); + return true; } if (SfzUpdate* update = FCast(changedUnknown)) { const std::string path = update->getPath(); uiReceiveValue(EditId::SfzFile, path); - return; + return true; } if (SfzDescriptionUpdate* update = FCast(changedUnknown)) { @@ -242,31 +250,70 @@ void PLUGIN_API SfizzVstEditor::update(FUnknown* changedUnknown, int32 message) } for (unsigned cc = 0; cc < sfz::config::numCCs; ++cc) { - bool ccUsed = desc.ccUsed.test(cc); + bool ccUsed = desc.ccUsed.test(cc) && !desc.sustainOrSostenuto.test(cc); uiReceiveValue(editIdForCCUsed(int(cc)), float(ccUsed)); if (ccUsed) { uiReceiveValue(editIdForCCDefault(int(cc)), desc.ccDefault[cc]); uiReceiveValue(editIdForCCLabel(int(cc)), desc.ccLabel[cc]); } } - return; + return true; } if (ScalaUpdate* update = FCast(changedUnknown)) { const std::string path = update->getPath(); uiReceiveValue(EditId::ScalaFile, path); - return; + return true; } if (PlayStateUpdate* update = FCast(changedUnknown)) { const SfizzPlayState playState = update->getState(); uiReceiveValue(EditId::UINumActiveVoices, playState.activeVoices); - return; + return true; } if (Vst::RangeParameter* param = Steinberg::FCast(changedUnknown)) { - const Vst::ParamValue value = param->getNormalized(); + // Note(jpc) some hosts send us the parameters in the wrong thread... + // store these parameters thread-safely and let the idle + // callback process them later + if (threadChecker_->test()) + updateParameter(param); + else { + static std::atomic_bool warn_once_flag { false }; + if (!warn_once_flag.exchange(true)) + fprintf(stderr, "[sfizz] using a thread-safety workaround for parameter updates\n"); + const Vst::ParamID id = param->getInfo().id; + std::lock_guard lock(parametersToUpdateMutex_); + parametersToUpdate_.insert(id); + } + return true; + } + + return false; +} + +void SfizzVstEditor::processParameterUpdates() +{ + auto extractNextParamID = [this]() -> Vst::ParamID { + Vst::ParamID id = Vst::kNoParamId; + std::lock_guard lock(parametersToUpdateMutex_); + auto it = parametersToUpdate_.begin(); + if (it != parametersToUpdate_.end()) { + id = *it; + parametersToUpdate_.erase(it); + } + return id; + }; + + for (Vst::ParamID id; (id = extractNextParamID()) != Vst::kNoParamId; ) + updateParameter(getController()->getParameterObject(id)); +} + +void SfizzVstEditor::updateParameter(Vst::Parameter* parameterToUpdate) +{ + if (Vst::RangeParameter* param = FCast(parameterToUpdate)) { const Vst::ParamID id = param->getInfo().id; + const Vst::ParamValue value = param->getNormalized(); const SfizzRange range = SfizzRange::getForParameter(id); switch (id) { case kPidVolume: @@ -296,6 +343,12 @@ void PLUGIN_API SfizzVstEditor::update(FUnknown* changedUnknown, int32 message) case kPidOscillatorQuality: uiReceiveValue(EditId::OscillatorQuality, range.denormalize(value)); break; + case kPidLeftLevel: + uiReceiveValue(EditId::LeftLevel, range.denormalize(value)); + break; + case kPidRightLevel: + uiReceiveValue(EditId::RightLevel, range.denormalize(value)); + break; default: if (id >= kPidCC0 && id <= kPidCCLast) { int cc = int(id - kPidCC0); @@ -303,50 +356,7 @@ void PLUGIN_API SfizzVstEditor::update(FUnknown* changedUnknown, int32 message) } break; } - return; } - - Vst::VSTGUIEditor::update(changedUnknown, message); -} - -void SfizzVstEditor::processOscQueue() -{ - std::lock_guard lock(oscQueueMutex_); - - OscByteVec* queue = oscQueue_.get(); - if (!queue) - return; - - const uint8_t* oscData = queue->data(); - size_t oscSize = queue->size(); - - const char* path; - const char* sig; - const sfizz_arg_t* args; - uint8_t buffer[1024]; - - uint32_t msgSize; - while ((msgSize = sfizz_extract_message(oscData, oscSize, buffer, sizeof(buffer), &path, &sig, &args)) > 0) { - uiReceiveMessage(path, sig, args); - oscData += msgSize; - oscSize -= msgSize; - } - - queue->clear(); -} - -void SfizzVstEditor::processNoteEventQueue() -{ - std::lock_guard lock(noteEventQueueMutex_); - - NoteEventsVec* queue = noteEventQueue_.get(); - if (!queue) - return; - - for (std::pair event : *queue) - uiReceiveValue(editIdForKey(event.first), event.second); - - queue->clear(); } /// diff --git a/plugins/vst/SfizzVstEditor.h b/plugins/vst/SfizzVstEditor.h index 780e2b2fd..9ced76591 100644 --- a/plugins/vst/SfizzVstEditor.h +++ b/plugins/vst/SfizzVstEditor.h @@ -8,8 +8,10 @@ #include "SfizzVstController.h" #include "editor/EditorController.h" #include "public.sdk/source/vst/vstguieditor.h" +#include "public.sdk/source/common/threadchecker.h" #include #include +#include class Editor; #if !defined(__APPLE__) && !defined(_WIN32) namespace VSTGUI { class RunLoop; } @@ -23,10 +25,7 @@ class SfizzVstEditor : public Vst::VSTGUIEditor, public: using Self = SfizzVstEditor; - SfizzVstEditor( - SfizzVstController* controller, - absl::Span continuousUpdates, - absl::Span triggerUpdates); + SfizzVstEditor(SfizzVstController* controller, absl::Span updates); ~SfizzVstEditor(); bool PLUGIN_API open(void* parent, const VSTGUI::PlatformType& platformType) override; @@ -37,18 +36,18 @@ class SfizzVstEditor : public Vst::VSTGUIEditor, return static_cast(Vst::VSTGUIEditor::getController()); } + void updateEditorIsOpenParameter(); + // VSTGUIEditor CMessageResult notify(CBaseObject* sender, const char* message) override; // FObject void PLUGIN_API update(FUnknown* changedUnknown, int32 message) override; // - void updateState(const SfizzVstState& state); - void updatePlayState(const SfizzPlayState& playState); - private: - void processOscQueue(); - void processNoteEventQueue(); + bool processUpdate(FUnknown* changedUnknown, int32 message); + void processParameterUpdates(); + void updateParameter(Vst::Parameter* parameterToUpdate); protected: // EditorController @@ -73,16 +72,14 @@ class SfizzVstEditor : public Vst::VSTGUIEditor, // messaging std::unique_ptr oscTemp_; - // editor state - // note: might be updated from a non-UI thread - typedef std::vector OscByteVec; - std::unique_ptr oscQueue_; - std::mutex oscQueueMutex_; - typedef std::vector> NoteEventsVec; - std::unique_ptr noteEventQueue_; - std::mutex noteEventQueueMutex_; - // subscribed updates - std::vector> continuousUpdates_; - std::vector> triggerUpdates_; + std::vector> updates_; + + // thread safety + std::unique_ptr threadChecker_; + + // parameters to process, whose values have received changes + // Note(jpc) it's because hosts send us parameter updates in the wrong thread.. + std::set parametersToUpdate_; + std::mutex parametersToUpdateMutex_; }; diff --git a/plugins/vst/SfizzVstParameters.h b/plugins/vst/SfizzVstParameters.h index f1791653a..cfe1c4737 100644 --- a/plugins/vst/SfizzVstParameters.h +++ b/plugins/vst/SfizzVstParameters.h @@ -26,6 +26,9 @@ enum { kPidPitchBend, kPidCC0, kPidCCLast = kPidCC0 + sfz::config::numCCs - 1, + kPidLeftLevel, + kPidRightLevel, + kPidEditorOpen, /* Reserved */ kNumParameters, }; @@ -78,6 +81,12 @@ struct SfizzRange { return {0.0, 0.0, 1.0}; case kPidPitchBend: return {0.0, -1.0, 1.0}; + case kPidLeftLevel: + return {0.0, 0.0, 1.0}; + case kPidRightLevel: + return {0.0, 0.0, 1.0}; + case kPidEditorOpen: + return {0.0, 0.0, 1.0}; default: if (id >= kPidCC0 && id <= kPidCCLast) return {0.0, 0.0, 1.0}; diff --git a/plugins/vst/SfizzVstProcessor.cpp b/plugins/vst/SfizzVstProcessor.cpp index 7ae97bc3e..e50801faa 100644 --- a/plugins/vst/SfizzVstProcessor.cpp +++ b/plugins/vst/SfizzVstProcessor.cpp @@ -9,10 +9,11 @@ #include "SfizzVstState.h" #include "SfizzVstParameters.h" #include "SfizzVstIDs.h" -#include "sfizz/import/ForeignInstrument.h" +#include "sfizz/import/sfizz_import.h" #include "plugin/SfizzFileScan.h" #include "plugin/InstrumentDescription.h" #include "base/source/fstreamer.h" +#include "base/source/updatehandler.h" #include "pluginterfaces/vst/ivstevents.h" #include "pluginterfaces/vst/ivstparameterchanges.h" #include @@ -34,10 +35,10 @@ static const char* kRingIdOsc = "Osc"; static const char* kMsgIdSetNumVoices = "SetNumVoices"; static const char* kMsgIdSetOversampling = "SetOversampling"; static const char* kMsgIdSetPreloadSize = "SetPreloadSize"; -static const char* kMsgIdReceiveMessage = "ReceiveMessage"; +static const char* kMsgIdReceiveOSC = "ReceiveOSC"; static const char* kMsgIdNoteEvents = "NoteEvents"; -static constexpr std::chrono::milliseconds kBackgroundIdleInterval { 50 }; +static constexpr std::chrono::milliseconds kBackgroundIdleInterval { 20 }; SfizzVstProcessor::SfizzVstProcessor() : _oscTemp(new uint8_t[kOscTempSize]), @@ -74,6 +75,23 @@ tresult PLUGIN_API SfizzVstProcessor::initialize(FUnknown* context) if (result != kResultTrue) return result; + // initialize the update handler + Steinberg::UpdateHandler::instance(); + + _queuedMessages = Steinberg::owned(new QueuedUpdates); + _playStateUpdate = Steinberg::owned(new PlayStateUpdate); + _sfzUpdate = Steinberg::owned(new SfzUpdate); + _sfzDescriptionUpdate = Steinberg::owned(new SfzDescriptionUpdate); + _scalaUpdate = Steinberg::owned(new ScalaUpdate); + _automationUpdate = Steinberg::owned(new AutomationUpdate); + + _queuedMessages->addDependent(this); + _playStateUpdate->addDependent(this); + _sfzUpdate->addDependent(this); + _sfzDescriptionUpdate->addDependent(this); + _scalaUpdate->addDependent(this); + _automationUpdate->addDependent(this); + addAudioOutput(STR16("Audio Output"), Vst::SpeakerArr::kStereo); addEventInput(STR16("Event Input"), 1); @@ -88,7 +106,7 @@ tresult PLUGIN_API SfizzVstProcessor::initialize(FUnknown* context) auto onMessage = +[](void* data, int delay, const char* path, const char* sig, const sfizz_arg_t* args) { auto *self = reinterpret_cast(data); - self->receiveMessage(delay, path, sig, args); + self->receiveOSC(delay, path, sig, args); }; _client = _synth->createClient(this); _synth->setReceiveCallback(*_client, onMessage); @@ -106,9 +124,23 @@ tresult PLUGIN_API SfizzVstProcessor::initialize(FUnknown* context) _noteEventsCurrentCycle.fill(-1.0f); + _editorIsOpen = false; + return result; } +tresult PLUGIN_API SfizzVstProcessor::terminate() +{ + _queuedMessages->removeDependent(this); + _playStateUpdate->removeDependent(this); + _sfzUpdate->removeDependent(this); + _sfzDescriptionUpdate->removeDependent(this); + _scalaUpdate->removeDependent(this); + _automationUpdate->removeDependent(this); + + return AudioEffect::terminate(); +} + tresult PLUGIN_API SfizzVstProcessor::setBusArrangements(Vst::SpeakerArrangement* inputs, int32 numIns, Vst::SpeakerArrangement* outputs, int32 numOuts) { bool isStereo = numIns == 0 && numOuts == 1 && outputs[0] == Vst::SpeakerArr::kStereo; @@ -126,10 +158,7 @@ tresult PLUGIN_API SfizzVstProcessor::connect(IConnectionPoint* other) return result; // when controller connects, send these messages that we couldn't earlier - if (_loadedSfzMessage) - sendMessage(_loadedSfzMessage); - if (_automateMessage) - sendMessage(_automateMessage); + _queuedMessages->deferUpdate(); return kResultTrue; } @@ -198,6 +227,10 @@ void SfizzVstProcessor::syncStateToSynth() synth->setScalaRootKey(_state.scalaRootKey); synth->setTuningFrequency(_state.tuningFrequency); synth->loadStretchTuningByRatio(_state.stretchedTuning); + if (_state.lastKeyswitch >= 0 && _state.lastKeyswitch <= 127) { + synth->hdNoteOn(0, _state.lastKeyswitch, 1.0f); + synth->hdNoteOff(1, _state.lastKeyswitch, 0.0f); + } } tresult PLUGIN_API SfizzVstProcessor::canProcessSampleSize(int32 symbolicSampleSize) @@ -221,6 +254,7 @@ tresult PLUGIN_API SfizzVstProcessor::setActive(TBool state) if (state) { synth->setSampleRate(processSetup.sampleRate); synth->setSamplesPerBlock(processSetup.maxSamplesPerBlock); + _rmsFollower.init(processSetup.sampleRate); initializeEventProcessor(processSetup, kNumParameters); startBackgroundWork(); } else { @@ -286,12 +320,30 @@ tresult PLUGIN_API SfizzVstProcessor::process(Vst::ProcessData& data) synth.renderBlock(outputs, numFrames, numChannels); + // Update levels, if editor is open, otherwise skip + RMSFollower& rmsFollower = _rmsFollower; + if (_editorIsOpen) { + rmsFollower.process(outputs[0], outputs[1], numFrames); + simde__m128 rms = _rmsFollower.getRMS(); + float left = reinterpret_cast(&rms)[0]; + float right = reinterpret_cast(&rms)[1]; + if (Vst::IParameterChanges* pcs = data.outputParameterChanges) { + int32 index; + if (Vst::IParamValueQueue* vq = pcs->addParameterData(kPidLeftLevel, index)) + vq->addPoint(0, left, index); + if (Vst::IParamValueQueue* vq = pcs->addParameterData(kPidRightLevel, index)) + vq->addPoint(0, right, index); + } + } + else + rmsFollower.clear(); + // Request OSC updates sfz::Client& client = *_client; synth.sendMessage(client, 0, "/sw/last/current", "", nullptr); // - std::pair noteEvents[128]; + NoteUpdate::Item noteEvents[128]; size_t numNoteEvents = 0; for (uint32 key = 0; key < 128; ++key) { float value = _noteEventsCurrentCycle[key]; @@ -388,10 +440,13 @@ void SfizzVstProcessor::playOrderedParameter(int32 sampleOffset, Vst::ParamID id case kPidPitchBend: synth.hdPitchWheel(sampleOffset, range.denormalize(value)); break; + case kPidEditorOpen: + _editorIsOpen = value != 0; + break; default: if (id >= kPidCC0 && id <= kPidCCLast) { int32 ccNumber = static_cast(id - kPidCC0); - synth.hdcc(sampleOffset, ccNumber, value); + synth.automateHdcc(sampleOffset, ccNumber, value); _state.controllers[ccNumber] = value; } break; @@ -467,7 +522,7 @@ void SfizzVstProcessor::processMessagesFromUi() synth.noteOn(0, data[1] & 0x7f, data[2] & 0x7f); break; case 0xb0: - synth.cc(0, data[1] & 0x7f, data[2] & 0x7f); + synth.automateHdcc(0, data[1] & 0x7f, static_cast(data[2] & 0x7f) / 127.0f); break; case 0xe0: synth.pitchWheel(0, (data[2] << 7) + data[1] - 8192); @@ -500,7 +555,7 @@ void SfizzVstProcessor::processMessagesFromUi() tresult PLUGIN_API SfizzVstProcessor::notify(Vst::IMessage* message) { - // Note(jpc) this notification is not necessarily handled by the RT thread + // Note(jpc) this notification is not handled by the RT thread tresult result = AudioEffect::notify(message); if (result != kResultFalse) @@ -535,10 +590,8 @@ tresult PLUGIN_API SfizzVstProcessor::notify(Vst::IMessage* message) _synth->loadScalaFile(_state.scalaFile); lock.unlock(); - Steinberg::OPtr reply { allocateMessage() }; - reply->setMessageID("LoadedScala"); - reply->getAttributes()->setBinary("File", _state.scalaFile.data(), _state.scalaFile.size()); - sendMessage(reply); + _scalaUpdate->setPath(_state.scalaFile); + _scalaUpdate->deferUpdate(); } else if (!std::strcmp(id, "MidiMessage")) { const void* data = nullptr; @@ -557,12 +610,81 @@ tresult PLUGIN_API SfizzVstProcessor::notify(Vst::IMessage* message) return result; } -void SfizzVstProcessor::receiveMessage(int delay, const char* path, const char* sig, const sfizz_arg_t* args) +void PLUGIN_API SfizzVstProcessor::update(FUnknown* changedUnknown, int32 message) +{ + if (processUpdate(changedUnknown, message)) + return; + + AudioEffect::update(changedUnknown, message); +} + +bool SfizzVstProcessor::processUpdate(FUnknown* changedUnknown, int32 message) { + if (QueuedUpdates* update = FCast(changedUnknown)) { + for (FObject* queuedUpdate : update->getUpdates(this)) + processUpdate(queuedUpdate, message); + return true; + } + + if (OSCUpdate* update = FCast(changedUnknown)) { + if (IPtr message = update->convertToMessage(this)) + sendMessage(message); + return true; + } + + if (PlayStateUpdate* update = FCast(changedUnknown)) { + if (IPtr message = update->convertToMessage(this)) + sendMessage(message); + return true; + } + + if (NoteUpdate* update = FCast(changedUnknown)) { + if (IPtr message = update->convertToMessage(this)) + sendMessage(message); + return true; + } + + if (SfzUpdate* update = FCast(changedUnknown)) { + if (IPtr message = update->convertToMessage(this)) + sendMessage(message); + return true; + } + + if (SfzDescriptionUpdate* update = FCast(changedUnknown)) { + if (IPtr message = update->convertToMessage(this)) + sendMessage(message); + return true; + } + + if (ScalaUpdate* update = FCast(changedUnknown)) { + if (IPtr message = update->convertToMessage(this)) + sendMessage(message); + return true; + } + + if (AutomationUpdate* update = FCast(changedUnknown)) { + if (IPtr message = update->convertToMessage(this)) + sendMessage(message); + return true; + } + + return false; +} + +void SfizzVstProcessor::receiveOSC(int delay, const char* path, const char* sig, const sfizz_arg_t* args) +{ + if (!strcmp(path, "/sw/last/current") && sig) + { + if (sig[0] == 'i') + _state.lastKeyswitch = args[0].i; + else if (sig[0] == 'N') + _state.lastKeyswitch = -1; + } + uint8_t* oscTemp = _oscTemp.get(); uint32 oscSize = sfizz_prepare_message(oscTemp, kOscTempSize, path, sig, args); if (oscSize <= kOscTempSize) { - if (writeWorkerMessage(kMsgIdReceiveMessage, oscTemp, oscSize)) + if (writeWorkerMessage(kMsgIdReceiveOSC, oscTemp, oscSize)) _semaToWorker.post(); } } @@ -572,16 +694,7 @@ void SfizzVstProcessor::loadSfzFileOrDefault(const std::string& filePath, bool i sfz::Sfizz& synth = *_synth; if (!filePath.empty()) { - const sfz::InstrumentFormatRegistry& formatRegistry = sfz::InstrumentFormatRegistry::getInstance(); - const sfz::InstrumentFormat* format = formatRegistry.getMatchingFormat(filePath); - if (!format) - synth.loadSfzFile(filePath); - else { - auto importer = format->createImporter(); - std::string virtualPath = filePath + ".sfz"; - std::string sfzText = importer->convertToSfz(filePath); - synth.loadSfzString(virtualPath, sfzText); - } + sfizz_load_or_import_file(synth.handle(), filePath.c_str(), nullptr); } else { synth.loadSfzString("default.sfz", defaultSfzText); @@ -589,12 +702,6 @@ void SfizzVstProcessor::loadSfzFileOrDefault(const std::string& filePath, bool i const std::string descBlob = getDescriptionBlob(synth.handle()); - Steinberg::OPtr loadMessage { allocateMessage() }; - loadMessage->setMessageID("LoadedSfz"); - Vst::IAttributeList* loadAttrs = loadMessage->getAttributes(); - loadAttrs->setBinary("File", filePath.data(), filePath.size()); - loadAttrs->setBinary("Description", descBlob.data(), descBlob.size()); - { std::vector> newControllers(sfz::config::numCCs); const std::vector> oldControllers = std::move(_state.controllers); @@ -609,7 +716,7 @@ void SfizzVstProcessor::loadSfzFileOrDefault(const std::string& filePath, bool i for (uint32 cc = 0; cc < sfz::config::numCCs; ++cc) { if (absl::optional value = oldControllers[cc]) { newControllers[cc] = *value; - synth.hdcc(0, int(cc), *value); + synth.automateHdcc(0, int(cc), *value); } } } @@ -617,26 +724,21 @@ void SfizzVstProcessor::loadSfzFileOrDefault(const std::string& filePath, bool i } // create a message which requests the controller to automate initial parameters - Steinberg::OPtr automateMessage { allocateMessage() }; - automateMessage->setMessageID("Automate"); - Vst::IAttributeList* automateAttrs = automateMessage->getAttributes(); - std::string automateBlob; - automateBlob.reserve(sfz::config::numCCs * (sizeof(uint32) + sizeof(float))); + std::vector automationItems; + automationItems.reserve(sfz::config::numCCs); for (uint32 cc = 0; cc < sfz::config::numCCs; ++cc) { - uint32 pid = kPidCC0 + cc; + Vst::ParamID pid = kPidCC0 + cc; float value = _state.controllers[cc].value_or(0.0f); - automateBlob.append(reinterpret_cast(&pid), sizeof(uint32)); - automateBlob.append(reinterpret_cast(&value), sizeof(float)); + automationItems.emplace_back(pid, value); } - automateAttrs->setBinary("Data", automateBlob.data(), uint32(automateBlob.size())); - - // sending can fail if controller is not connected yet, so keep it around - _loadedSfzMessage = loadMessage; - _automateMessage = automateMessage; // send message - sendMessage(loadMessage); - sendMessage(automateMessage); + _sfzUpdate->setPath(filePath); + _sfzUpdate->deferUpdate(); + _sfzDescriptionUpdate->setDescription(descBlob); + _sfzDescriptionUpdate->deferUpdate(); + _automationUpdate->setItems(std::move(automationItems)); + _automationUpdate->deferUpdate(); } void SfizzVstProcessor::doBackgroundWork() @@ -650,8 +752,13 @@ void SfizzVstProcessor::doBackgroundWork() for (;;) { bool isNotified = _semaToWorker.timed_wait(kBackgroundIdleInterval.count()); - if (!_workRunning) + if (!_workRunning) { + // if the quit signal is sent, the semaphore is also signaled + // make sure the count is kept consistent + if (!isNotified) + _semaToWorker.wait(); break; + } const char* id = nullptr; RTMessagePtr msg; @@ -680,17 +787,17 @@ void SfizzVstProcessor::doBackgroundWork() std::lock_guard lock(_processMutex); _synth->setPreloadSize(value); } - else if (id == kMsgIdReceiveMessage) { - Steinberg::OPtr notification { allocateMessage() }; - notification->setMessageID("ReceivedMessage"); - notification->getAttributes()->setBinary("Message", msg->payload(), msg->size); - sendMessage(notification); + else if (id == kMsgIdReceiveOSC) { + IPtr update = Steinberg::owned( + new OSCUpdate(msg->payload(), msg->size)); + _queuedMessages->enqueue(update); + _queuedMessages->deferUpdate(); } else if (id == kMsgIdNoteEvents) { - Steinberg::OPtr notification { allocateMessage() }; - notification->setMessageID("NoteEvents"); - notification->getAttributes()->setBinary("Events", msg->payload(), msg->size); - sendMessage(notification); + IPtr update = Steinberg::owned( + new NoteUpdate(msg->payload(), msg->size / sizeof(NoteUpdate::Item))); + _queuedMessages->enqueue(update); + _queuedMessages->deferUpdate(); } Clock::time_point currentTime = Clock::now(); @@ -707,13 +814,11 @@ void SfizzVstProcessor::doBackgroundIdle(size_t idleCounter) { SfizzPlayState ps; ps.activeVoices = _synth->getNumActiveVoices(); - Steinberg::OPtr notification { allocateMessage() }; - notification->setMessageID("NotifiedPlayState"); - notification->getAttributes()->setBinary("PlayState", &ps, sizeof(ps)); - sendMessage(notification); + _playStateUpdate->setState(ps); + _playStateUpdate->deferUpdate(); } - if (idleCounter % 10 == 0) { + if (idleCounter % 25 == 0) { if (_synth->shouldReloadFile()) { fprintf(stderr, "[Sfizz] sfz file has changed, reloading\n"); std::lock_guard lock(_processMutex); diff --git a/plugins/vst/SfizzVstProcessor.h b/plugins/vst/SfizzVstProcessor.h index e686b86ef..a41016e88 100644 --- a/plugins/vst/SfizzVstProcessor.h +++ b/plugins/vst/SfizzVstProcessor.h @@ -6,7 +6,9 @@ #pragma once #include "SfizzVstState.h" +#include "SfizzVstUpdates.h" #include "OrderedEventProcessor.h" +#include "plugin/RMSFollower.h" #include "sfizz/RTSemaphore.h" #include "ring_buffer/ring_buffer.h" #include "public.sdk/source/vst/vstaudioeffect.h" @@ -26,6 +28,7 @@ class SfizzVstProcessor : public Vst::AudioEffect, ~SfizzVstProcessor(); tresult PLUGIN_API initialize(FUnknown* context) override; + tresult PLUGIN_API terminate() override; tresult PLUGIN_API setBusArrangements(Vst::SpeakerArrangement* inputs, int32 numIns, Vst::SpeakerArrangement* outputs, int32 numOuts) override; tresult PLUGIN_API connect(IConnectionPoint* other) override; @@ -45,6 +48,7 @@ class SfizzVstProcessor : public Vst::AudioEffect, void processMessagesFromUi(); tresult PLUGIN_API notify(Vst::IMessage* message) override; + void PLUGIN_API update(FUnknown* changedUnknown, int32 message) override; static FUnknown* createInstance(void*); @@ -54,8 +58,6 @@ class SfizzVstProcessor : public Vst::AudioEffect, private: // synth state. acquire processMutex before accessing std::unique_ptr _synth; - Steinberg::IPtr _loadedSfzMessage; - Steinberg::IPtr _automateMessage; bool _isActive = false; SfizzVstState _state; float _currentStretchedTuning = 0; @@ -63,10 +65,23 @@ class SfizzVstProcessor : public Vst::AudioEffect, // whether allowed to perform events (owns the processing lock) bool _canPerformEventsAndParameters {}; + // level meters + RMSFollower _rmsFollower; + bool _editorIsOpen = false; + + // updates + IPtr _queuedMessages; + IPtr _playStateUpdate; + IPtr _sfzUpdate; + IPtr _sfzDescriptionUpdate; + IPtr _scalaUpdate; + IPtr _automationUpdate; + bool processUpdate(FUnknown* changedUnknown, int32 message); + // client sfz::ClientPtr _client; std::unique_ptr _oscTemp; - void receiveMessage(int delay, const char* path, const char* sig, const sfizz_arg_t* args); + void receiveOSC(int delay, const char* path, const char* sig, const sfizz_arg_t* args); // misc void loadSfzFileOrDefault(const std::string& filePath, bool initParametersFromState); diff --git a/plugins/vst/SfizzVstState.cpp b/plugins/vst/SfizzVstState.cpp index 1783f817f..160d7abbe 100644 --- a/plugins/vst/SfizzVstState.cpp +++ b/plugins/vst/SfizzVstState.cpp @@ -73,6 +73,14 @@ tresult SfizzVstState::load(IBStream* state) oscillatorQuality = defaults.oscillatorQuality; } + if (version >= 4) { + if (!s.readInt32(lastKeyswitch)) + return kResultFalse; + } + else { + lastKeyswitch = -1; + } + controllers.clear(); if (version >= 2) { uint32 count; @@ -135,6 +143,9 @@ tresult SfizzVstState::store(IBStream* state) const if (!s.writeInt32(oscillatorQuality)) return kResultFalse; + if (!s.writeInt32(lastKeyswitch)) + return kResultFalse; + { uint32 ccCount = 0; uint32 ccLimit = uint32(std::min(controllers.size(), size_t(0x10000))); diff --git a/plugins/vst/SfizzVstState.h b/plugins/vst/SfizzVstState.h index 7f50f4c57..93a7a4ec4 100644 --- a/plugins/vst/SfizzVstState.h +++ b/plugins/vst/SfizzVstState.h @@ -27,9 +27,10 @@ class SfizzVstState { float stretchedTuning = 0.0; int32 sampleQuality = 2; int32 oscillatorQuality = 1; + int32 lastKeyswitch = -1; std::vector> controllers; - static constexpr uint64 currentStateVersion = 3; + static constexpr uint64 currentStateVersion = 4; tresult load(IBStream* state); tresult store(IBStream* state) const; diff --git a/plugins/vst/SfizzVstUpdates.cpp b/plugins/vst/SfizzVstUpdates.cpp index 20e7c1e41..5dfcfd3a6 100644 --- a/plugins/vst/SfizzVstUpdates.cpp +++ b/plugins/vst/SfizzVstUpdates.cpp @@ -8,61 +8,167 @@ #include #include -OSCUpdate::~OSCUpdate() +void QueuedUpdates::enqueue(IPtr update) { - clear(); + std::lock_guard lock(mutex_); + for (std::pair& item : updates_) + item.second.push_back(update); } -void OSCUpdate::clear() +auto QueuedUpdates::getUpdates(IDependent* dep) -> List { - if (allocated_) - delete[] reinterpret_cast(data_); - data_ = nullptr; - size_ = 0; - allocated_ = false; + std::lock_guard lock(mutex_); + List list; + auto it = updates_.find(dep); + if (it != updates_.end()) + std::swap(list, it->second); + return list; } -void OSCUpdate::setMessage(const void* data, uint32_t size, bool copy) +void QueuedUpdates::addDependent(IDependent* dep) { - clear(); + std::lock_guard lock(mutex_); + FObject::addDependent(dep); + updates_.emplace(dep, List()); +} - if (copy) { - uint8_t *buffer = new uint8_t[size]; - std::memcpy(buffer, data, size); - data = buffer; - } +void QueuedUpdates::removeDependent(IDependent* dep) +{ + std::lock_guard lock(mutex_); + FObject::removeDependent(dep); + updates_.erase(dep); +} - data_ = data; - size_ = size; - allocated_ = copy; +/// +bool OSCUpdate::saveToAttributes(Vst::IAttributeList* attrs) const +{ + return attrs->setBinary("Data", data(), size()) == kResultTrue; +} + +bool OSCUpdate::loadFromAttributes(Vst::IAttributeList* attrs) +{ + const void* data; + uint32 size; + if (attrs->getBinary("Data", data, size) != kResultTrue) + return false; + const uint8* data8 = reinterpret_cast(data); + data_.assign(data8, data8 + size); + return true; } /// -NoteUpdate::~NoteUpdate() +bool NoteUpdate::saveToAttributes(Vst::IAttributeList* attrs) const +{ + return attrs->setBinary("Events", events_.data(), events_.size() * sizeof(Item)) == kResultTrue; +} + +bool NoteUpdate::loadFromAttributes(Vst::IAttributeList* attrs) { - clear(); + const void* binData = nullptr; + uint32 binSize = 0; + if (attrs->getBinary("Events", binData, binSize) != kResultTrue) + return false; + + const Item* events = reinterpret_cast(binData); + uint32 numEvents = binSize / sizeof(Item); + + events_.assign(events, events + numEvents); + return true; } -void NoteUpdate::clear() +/// +bool FilePathUpdate::saveFilePathAttributes_(Vst::IAttributeList* attrs) const +{ + std::lock_guard lock(mutex_); + return attrs->setBinary("Path", path_.data(), path_.size()) == kResultTrue; +} + +bool FilePathUpdate::loadFilePathAttributes_(Vst::IAttributeList* attrs) +{ + const void* binData = nullptr; + uint32 binSize = 0; + if (attrs->getBinary("Path", binData, binSize) != kResultTrue) + return false; + std::lock_guard lock(mutex_); + path_.assign(reinterpret_cast(binData), binSize); + return true; +} + +/// +bool SfzUpdate::saveToAttributes(Vst::IAttributeList* attrs) const +{ + return saveFilePathAttributes_(attrs); +} + +bool SfzUpdate::loadFromAttributes(Vst::IAttributeList* attrs) +{ + return loadFilePathAttributes_(attrs); +} + +/// +bool ScalaUpdate::saveToAttributes(Vst::IAttributeList* attrs) const +{ + return saveFilePathAttributes_(attrs); +} + +bool ScalaUpdate::loadFromAttributes(Vst::IAttributeList* attrs) +{ + return loadFilePathAttributes_(attrs); +} + +/// +bool SfzDescriptionUpdate::saveToAttributes(Vst::IAttributeList* attrs) const +{ + std::lock_guard lock(mutex_); + return attrs->setBinary("Blob", description_.data(), description_.size()) == kResultTrue; +} + +bool SfzDescriptionUpdate::loadFromAttributes(Vst::IAttributeList* attrs) +{ + const void* binData = nullptr; + uint32 binSize = 0; + if (attrs->getBinary("Blob", binData, binSize) != kResultTrue) + return false; + std::lock_guard lock(mutex_); + description_.assign(reinterpret_cast(binData), binSize); + return true; +} + +/// +bool PlayStateUpdate::saveToAttributes(Vst::IAttributeList* attrs) const +{ + std::lock_guard lock(mutex_); + return attrs->setInt("ActiveVoices", state_.activeVoices) == kResultTrue; +} + +bool PlayStateUpdate::loadFromAttributes(Vst::IAttributeList* attrs) +{ + int64 activeVoices; + if (attrs->getInt("ActiveVoices", activeVoices) != kResultTrue) + return false; + std::lock_guard lock(mutex_); + state_.activeVoices = static_cast(activeVoices); + return true; +} + +/// +bool AutomationUpdate::saveToAttributes(Vst::IAttributeList* attrs) const { - if (allocated_) - delete[] events_; - events_ = nullptr; - count_ = 0; - allocated_ = false; + std::lock_guard lock(mutex_); + return attrs->setBinary("Items", items_.data(), items_.size() * sizeof(Item)) == kResultTrue; } -void NoteUpdate::setEvents(const std::pair* events, uint32_t count, bool copy) +bool AutomationUpdate::loadFromAttributes(Vst::IAttributeList* attrs) { - clear(); + const void* binData = nullptr; + uint32 binSize = 0; + if (attrs->getBinary("Items", binData, binSize) != kResultTrue) + return false; - if (copy) { - auto *buffer = new std::pair[count]; - std::copy_n(events, count, buffer); - events = buffer; - } + const Item* events = reinterpret_cast(binData); + uint32 numEvents = binSize / sizeof(Item); - events_ = events; - count_ = count; - allocated_ = copy; + std::lock_guard lock(mutex_); + items_.assign(events, events + numEvents); + return true; } diff --git a/plugins/vst/SfizzVstUpdates.h b/plugins/vst/SfizzVstUpdates.h index add4107ed..76357d5f3 100644 --- a/plugins/vst/SfizzVstUpdates.h +++ b/plugins/vst/SfizzVstUpdates.h @@ -6,70 +6,102 @@ #pragma once #include "SfizzVstState.h" +#include +#include #include #include +#include #include +#include #include +#include #include +/** + * @brief Update which is convertible with Vst::IMessage back and forth. + */ +template class IConvertibleToMessage { +public: + virtual ~IConvertibleToMessage() {} + + IPtr convertToMessage(Vst::ComponentBase* sender) const; + bool convertFromMessage(Vst::IMessage& message); + static IPtr createFromMessage(Vst::IMessage& message); + +protected: + virtual bool saveToAttributes(Vst::IAttributeList* attrs) const = 0; + virtual bool loadFromAttributes(Vst::IAttributeList* attrs) = 0; +}; + +/** + * @brief Update which notifies a FIFO queue of one-time updates + */ +class QueuedUpdates : public Steinberg::FObject { +public: + using List = std::vector>; + + void enqueue(IPtr update); + List getUpdates(IDependent* dep); + + void addDependent(IDependent* dep) override; + void removeDependent(IDependent* dep) override; + + OBJ_METHODS(QueuedUpdates, FObject) + +private: + std::mutex mutex_; + std::map updates_; +}; + /** * @brief Update which notifies a single OSC message - * Is is supposed to be used synchronously. - * (ie. FObject::changed or UpdateHandler::triggerUpdates) */ -class OSCUpdate : public Steinberg::FObject { +class OSCUpdate : public Steinberg::FObject, + public IConvertibleToMessage { public: OSCUpdate() = default; - ~OSCUpdate(); - void clear(); - void setMessage(const void* data, uint32_t size, bool copy); + OSCUpdate(const uint8* data, uint32 size) : data_(data, data + size) {} + const uint8* data() const noexcept { return data_.data(); } + uint32_t size() const noexcept { return static_cast(data_.size()); } - const void* data() const noexcept { return data_; } - const uint32_t size() const noexcept { return size_; } + bool saveToAttributes(Vst::IAttributeList* attrs) const override; + bool loadFromAttributes(Vst::IAttributeList* attrs) override; OBJ_METHODS(OSCUpdate, FObject) private: - const void* data_ = nullptr; - uint32_t size_ = 0; - bool allocated_ = false; - -private: - OSCUpdate(const OSCUpdate&) = delete; - OSCUpdate& operator=(const OSCUpdate&) = delete; + std::vector data_; }; /** * @brief Update which notifies one or more note on/off events - * Is is supposed to be used synchronously. - * (ie. FObject::changed or UpdateHandler::triggerUpdates) */ -class NoteUpdate : public Steinberg::FObject { +class NoteUpdate : public Steinberg::FObject, + public IConvertibleToMessage { public: + using Item = std::pair; + NoteUpdate() = default; - ~NoteUpdate(); - void clear(); - void setEvents(const std::pair* events, uint32_t count, bool copy); + NoteUpdate(const Item* items, uint32 count) : events_(items, items + count) {} + const Item* events() const noexcept { return events_.data(); } + const uint32 count() const noexcept { return static_cast(events_.size()); } - const std::pair* events() const noexcept { return events_; } - const uint32_t count() const noexcept { return count_; } + bool saveToAttributes(Vst::IAttributeList* attrs) const override; + bool loadFromAttributes(Vst::IAttributeList* attrs) override; OBJ_METHODS(NoteUpdate, FObject) private: - const std::pair* events_ = nullptr; - uint32_t count_ = 0; - bool allocated_ = false; - -private: - NoteUpdate(const NoteUpdate&) = delete; - NoteUpdate& operator=(const NoteUpdate&) = delete; + std::vector events_; }; /** - * @brief Update which notifies a change of SFZ file. + * @brief Abstract update which notifies change of a certain file path. */ -class SfzUpdate : public Steinberg::FObject { +class FilePathUpdate : public Steinberg::FObject { +protected: + FilePathUpdate() = default; + public: void setPath(std::string newPath) { @@ -83,17 +115,46 @@ class SfzUpdate : public Steinberg::FObject { return path_; } - OBJ_METHODS(SfzUpdate, FObject) + OBJ_METHODS(FilePathUpdate, FObject) + +protected: + bool saveFilePathAttributes_(Vst::IAttributeList* attrs) const; + bool loadFilePathAttributes_(Vst::IAttributeList* attrs); private: std::string path_; mutable std::mutex mutex_; }; +/** + * @brief Update which notifies a change of SFZ file. + */ +class SfzUpdate : public FilePathUpdate, + public IConvertibleToMessage { +public: + OBJ_METHODS(SfzUpdate, FilePathUpdate) + + bool saveToAttributes(Vst::IAttributeList* attrs) const override; + bool loadFromAttributes(Vst::IAttributeList* attrs) override; +}; + +/** + * @brief Update which notifies a change of scala file. + */ +class ScalaUpdate : public FilePathUpdate, + public IConvertibleToMessage { +public: + OBJ_METHODS(ScalaUpdate, FilePathUpdate) + + bool saveToAttributes(Vst::IAttributeList* attrs) const override; + bool loadFromAttributes(Vst::IAttributeList* attrs) override; +}; + /** * @brief Update which notifies a change of SFZ description. */ -class SfzDescriptionUpdate : public Steinberg::FObject { +class SfzDescriptionUpdate : public Steinberg::FObject, + public IConvertibleToMessage { public: void setDescription(std::string newDescription) { @@ -107,6 +168,9 @@ class SfzDescriptionUpdate : public Steinberg::FObject { return description_; } + bool saveToAttributes(Vst::IAttributeList* attrs) const override; + bool loadFromAttributes(Vst::IAttributeList* attrs) override; + OBJ_METHODS(SfzDescriptionUpdate, FObject) private: @@ -115,49 +179,63 @@ class SfzDescriptionUpdate : public Steinberg::FObject { }; /** - * @brief Update which notifies a change of scala file. + * @brief Update which indicates the playing SFZ status. */ -class ScalaUpdate : public Steinberg::FObject { +class PlayStateUpdate : public Steinberg::FObject, + public IConvertibleToMessage { public: - void setPath(std::string newPath) + void setState(SfizzPlayState newState) { std::lock_guard lock(mutex_); - path_ = std::move(newPath); + state_ = std::move(newState); } - std::string getPath() const + SfizzPlayState getState() const { std::lock_guard lock(mutex_); - return path_; + return state_; } - OBJ_METHODS(ScalaUpdate, FObject) + bool saveToAttributes(Vst::IAttributeList* attrs) const override; + virtual bool loadFromAttributes(Vst::IAttributeList* attrs) override; + + OBJ_METHODS(PlayStateUpdate, FObject) private: - std::string path_; + SfizzPlayState state_ {}; mutable std::mutex mutex_; }; /** - * @brief Update which indicates the playing SFZ status. + * @brief Update which automates a pack of parameters */ -class PlayStateUpdate : public Steinberg::FObject { +class AutomationUpdate : public Steinberg::FObject, + public IConvertibleToMessage { public: - void setState(SfizzPlayState newState) + using Item = std::pair; + + AutomationUpdate() = default; + + void setItems(std::vector newItems) { std::lock_guard lock(mutex_); - state_ = std::move(newState); + items_ = std::move(newItems); } - SfizzPlayState getState() const + std::vector getItems() const { std::lock_guard lock(mutex_); - return state_; + return items_; } - OBJ_METHODS(PlayStateUpdate, FObject) + bool saveToAttributes(Vst::IAttributeList* attrs) const override; + bool loadFromAttributes(Vst::IAttributeList* attrs) override; + + OBJ_METHODS(AutomationUpdate, FObject) private: - SfizzPlayState state_ {}; + std::vector items_; mutable std::mutex mutex_; }; + +#include "SfizzVstUpdates.hpp" diff --git a/plugins/vst/SfizzVstUpdates.hpp b/plugins/vst/SfizzVstUpdates.hpp new file mode 100644 index 000000000..5b1ce1f93 --- /dev/null +++ b/plugins/vst/SfizzVstUpdates.hpp @@ -0,0 +1,41 @@ +// 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 + +#pragma once +#include "SfizzVstUpdates.h" + +template +IPtr IConvertibleToMessage::convertToMessage(Vst::ComponentBase* sender) const +{ + IPtr message = Steinberg::owned(sender->allocateMessage()); + if (!message) + return nullptr; + message->setMessageID(static_cast(this)->isA()); + if (!saveToAttributes(message->getAttributes())) + return nullptr; + return message; +} + +template +IPtr IConvertibleToMessage::createFromMessage(Vst::IMessage& message) +{ + IPtr object; + if (!strcmp(T::getFClassID(), message.getMessageID())) { + object = Steinberg::owned(new T); + if (!object->loadFromAttributes(message.getAttributes())) + object = nullptr; + } + return object; +} + +template +bool IConvertibleToMessage::convertFromMessage(Vst::IMessage& message) +{ + bool success = false; + if (!strcmp(T::getFClassID(), message.getMessageID())) + success = loadFromAttributes(message.getAttributes()); + return success; +} diff --git a/plugins/vst/X11RunLoop.cpp b/plugins/vst/X11RunLoop.cpp index 990778468..2d8ded27e 100644 --- a/plugins/vst/X11RunLoop.cpp +++ b/plugins/vst/X11RunLoop.cpp @@ -1,33 +1,38 @@ -// SPDX-License-Identifier: GPL-3.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 #if !defined(__APPLE__) && !defined(_WIN32) #include "X11RunLoop.h" #include "vstgui/lib/platform/linux/x11platform.h" #include "base/source/fobject.h" +#include +#include +#include +#include namespace VSTGUI { -RunLoop::RunLoop(Steinberg::FUnknown* runLoop) - : runLoop(runLoop) -{ -} +struct RunLoop::Impl { + struct EventHandler; + struct TimerHandler; -RunLoop::~RunLoop() {} + using EventHandlers = std::vector>; + using TimerHandlers = std::vector>; -SharedPointer RunLoop::get() -{ - return X11::RunLoop::get().cast(); -} + EventHandlers eventHandlers; + TimerHandlers timerHandlers; + Steinberg::FUnknownPtr runLoop; +}; -struct RunLoop::EventHandler final : Steinberg::Linux::IEventHandler, public Steinberg::FObject { +//------------------------------------------------------------------------------ +struct RunLoop::Impl::EventHandler final : Steinberg::Linux::IEventHandler, public Steinberg::FObject { X11::IEventHandler* handler { nullptr }; bool alive { false }; - void PLUGIN_API onFDIsSet(Steinberg::Linux::FileDescriptor) override - { - if (alive && handler) - handler->onEvent(); - } + void PLUGIN_API onFDIsSet(Steinberg::Linux::FileDescriptor) override; DELEGATE_REFCOUNT(Steinberg::FObject) DEFINE_INTERFACES @@ -35,15 +40,11 @@ struct RunLoop::EventHandler final : Steinberg::Linux::IEventHandler, public Ste END_DEFINE_INTERFACES(Steinberg::FObject) }; -struct RunLoop::TimerHandler final : Steinberg::Linux::ITimerHandler, public Steinberg::FObject { +struct RunLoop::Impl::TimerHandler final : Steinberg::Linux::ITimerHandler, public Steinberg::FObject { X11::ITimerHandler* handler { nullptr }; bool alive { false }; - void PLUGIN_API onTimer() override - { - if (alive && handler) - handler->onTimer(); - } + void PLUGIN_API onTimer() override; DELEGATE_REFCOUNT(Steinberg::FObject) DEFINE_INTERFACES @@ -51,45 +52,126 @@ struct RunLoop::TimerHandler final : Steinberg::Linux::ITimerHandler, public Ste END_DEFINE_INTERFACES(Steinberg::FObject) }; +//------------------------------------------------------------------------------ +void PLUGIN_API RunLoop::Impl::EventHandler::onFDIsSet(Steinberg::Linux::FileDescriptor) +{ + SharedPointer runLoop = RunLoop::get(); + if (!runLoop) { + fprintf(stderr, "[x11] event has fired without active runloop\n"); + return; + } + + if (alive && handler) + handler->onEvent(); +} + +void PLUGIN_API RunLoop::Impl::TimerHandler::onTimer() +{ + SharedPointer runLoop = RunLoop::get(); + if (!runLoop) { + fprintf(stderr, "[x11] timer has fired without active runloop\n"); + return; + } + + if (alive && handler) + handler->onTimer(); +} + +//------------------------------------------------------------------------------ +RunLoop::RunLoop(Steinberg::FUnknown* runLoop) + : impl(new Impl) +{ + impl->runLoop = runLoop; +} + +RunLoop::~RunLoop() +{ + //dumpCurrentState(); + + if (0) { + // remove any leftover handlers + for (size_t i = 0; i < impl->eventHandlers.size(); ++i) { + const auto& eh = impl->eventHandlers[i]; + if (eh->alive && eh->handler) { + impl->runLoop->unregisterEventHandler(eh.get()); + } + } + for (size_t i = 0; i < impl->timerHandlers.size(); ++i) { + const auto& th = impl->timerHandlers[i]; + if (th->alive && th->handler) { + impl->runLoop->unregisterTimer(th.get()); + } + } + } +} + +SharedPointer RunLoop::get() +{ + return X11::RunLoop::get().cast(); +} void RunLoop::processSomeEvents() { - for (size_t i = 0; i < eventHandlers.size(); ++i) { - const auto& eh = eventHandlers[i]; + for (size_t i = 0; i < impl->eventHandlers.size(); ++i) { + const auto& eh = impl->eventHandlers[i]; if (eh->alive && eh->handler) { eh->handler->onEvent(); } } } -void RunLoop::cleanupDeadHandlers() +void RunLoop::dumpCurrentState() { - for (size_t i = 0; i < eventHandlers.size(); ++i) { - const auto& eh = eventHandlers[i]; - if (!eh->alive) { - runLoop->unregisterEventHandler(eh); - eventHandlers.erase(eventHandlers.begin() + i--); - } + fprintf(stderr, "=== X11 runloop ===\n"); + + fprintf(stderr, "\t" "Event slots:\n"); + for (size_t i = 0, n = impl->eventHandlers.size(); i < n; ++i) { + Impl::EventHandler *eh = impl->eventHandlers[i].get(); + fprintf(stderr, "\t\t" "(%lu) alive=%d handler=%p type=%s\n", i, eh->alive, eh->handler, (eh->alive && eh->handler) ? typeid(*eh->handler).name() : ""); } - for (size_t i = 0; i < timerHandlers.size(); ++i) { - const auto& th = timerHandlers[i]; - if (!th->alive) { - runLoop->unregisterTimer(th); - timerHandlers.erase(timerHandlers.begin() + i--); - } + + fprintf(stderr, "\t" "Timer slots:\n"); + for (size_t i = 0, n = impl->timerHandlers.size(); i < n; ++i) { + Impl::TimerHandler *th = impl->timerHandlers[i].get(); + fprintf(stderr, "\t\t" "(%lu) alive=%d handler=%p type=%s\n", i, th->alive, th->handler, (th->alive && th->handler) ? typeid(*th->handler).name() : ""); + } + + fprintf(stderr, "===/X11 runloop ===\n"); +} + +template +static void insertHandler(std::vector>& list, Steinberg::IPtr handler) +{ + size_t i = 0; + size_t n = list.size(); + while (i < n && list[i]->alive) + ++i; + if (i < n) + list[i] = handler; + else + list.emplace_back(handler); +} + +template +static size_t findHandler(const std::vector>& list, U* handler) +{ + for (size_t i = 0, n = list.size(); i < n; ++i) { + if (list[i]->alive && list[i]->handler == handler) + return i; } + return ~size_t(0); } bool RunLoop::registerEventHandler(int fd, X11::IEventHandler* handler) { - if (!runLoop) + if (!impl->runLoop) return false; - auto smtgHandler = Steinberg::owned(new EventHandler()); + auto smtgHandler = Steinberg::owned(new Impl::EventHandler); smtgHandler->handler = handler; smtgHandler->alive = true; - if (runLoop->registerEventHandler(smtgHandler, fd) == Steinberg::kResultTrue) { - eventHandlers.push_back(smtgHandler); + if (impl->runLoop->registerEventHandler(smtgHandler, fd) == Steinberg::kResultTrue) { + insertHandler(impl->eventHandlers, smtgHandler); return true; } return false; @@ -97,29 +179,31 @@ bool RunLoop::registerEventHandler(int fd, X11::IEventHandler* handler) bool RunLoop::unregisterEventHandler(X11::IEventHandler* handler) { - if (!runLoop) + if (!impl->runLoop) return false; - for (size_t i = 0; i < eventHandlers.size(); ++i) { - const auto& eh = eventHandlers[i]; - if (eh->alive && eh->handler == handler) { - eh->alive = false; - return true; - } - } - return false; + size_t index = findHandler(impl->eventHandlers, handler); + if (index == ~size_t(0)) + return false; + + Impl::EventHandler *eh = impl->eventHandlers[index].get(); + if (impl->runLoop->unregisterEventHandler(eh) != Steinberg::kResultTrue) + return false; + + eh->alive = false; + return true; } bool RunLoop::registerTimer(uint64_t interval, X11::ITimerHandler* handler) { - if (!runLoop) + if (!impl->runLoop) return false; - auto smtgHandler = Steinberg::owned(new TimerHandler()); + auto smtgHandler = Steinberg::owned(new Impl::TimerHandler); smtgHandler->handler = handler; smtgHandler->alive = true; - if (runLoop->registerTimer(smtgHandler, interval) == Steinberg::kResultTrue) { - timerHandlers.push_back(smtgHandler); + if (impl->runLoop->registerTimer(smtgHandler, interval) == Steinberg::kResultTrue) { + insertHandler(impl->timerHandlers, smtgHandler); return true; } return false; @@ -127,17 +211,19 @@ bool RunLoop::registerTimer(uint64_t interval, X11::ITimerHandler* handler) bool RunLoop::unregisterTimer(X11::ITimerHandler* handler) { - if (!runLoop) + if (!impl->runLoop) return false; - for (size_t i = 0; i < timerHandlers.size(); ++i) { - const auto& th = timerHandlers[i]; - if (th->alive && th->handler == handler) { - th->alive = false; - return true; - } - } - return false; + size_t index = findHandler(impl->timerHandlers, handler); + if (index == ~size_t(0)) + return false; + + Impl::TimerHandler *th = impl->timerHandlers[index].get(); + if (impl->runLoop->unregisterTimer(th) != Steinberg::kResultTrue) + return false; + + th->alive = false; + return true; } } // namespace VSTGUI diff --git a/plugins/vst/X11RunLoop.h b/plugins/vst/X11RunLoop.h index cb47b34c3..5755a06ba 100644 --- a/plugins/vst/X11RunLoop.h +++ b/plugins/vst/X11RunLoop.h @@ -1,16 +1,28 @@ -// SPDX-License-Identifier: GPL-3.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 + /* - This is a modified version the X11 run loop from vst3editor.cpp. + This runloop connects to X11 VSTGUI, it connects VST3 and VSTGUI together. + The Windows and macOS runloops do not need this, the OS-provided + functionality is used instead. - This version is edited to add more safeguards to protect against host bugs. - It also permits to call event processing externally in case the host has a - defective X11 event loop notifier. + Previously, this was based on VSTGUI code provided by Steinberg. + This is replaced with a rewrite, because the original code has too many + issues. For example, it has no robustness in case handlers get added or + removed within the execution of the handler. + + This version allows to call event processing externally, in case the host + has a defective X11 event loop notifier. (some versions of Bitwig do) */ #pragma once #if !defined(__APPLE__) && !defined(_WIN32) #include "vstgui/lib/platform/linux/x11frame.h" #include "pluginterfaces/gui/iplugview.h" +#include namespace VSTGUI { @@ -22,24 +34,16 @@ class RunLoop final : public X11::IRunLoop, public AtomicReferenceCounted { static SharedPointer get(); void processSomeEvents(); - void cleanupDeadHandlers(); + void dumpCurrentState(); // X11::IRunLoop - bool registerEventHandler(int fd, X11::IEventHandler* handler); - bool unregisterEventHandler(X11::IEventHandler* handler); - bool registerTimer(uint64_t interval, X11::ITimerHandler* handler); - bool unregisterTimer(X11::ITimerHandler* handler); - -private: - struct EventHandler; - struct TimerHandler; - -private: - using EventHandlers = std::vector>; - using TimerHandlers = std::vector>; - EventHandlers eventHandlers; - TimerHandlers timerHandlers; - Steinberg::FUnknownPtr runLoop; + bool registerEventHandler(int fd, X11::IEventHandler* handler) override; + bool unregisterEventHandler(X11::IEventHandler* handler) override; + bool registerTimer(uint64_t interval, X11::ITimerHandler* handler) override; + bool unregisterTimer(X11::ITimerHandler* handler) override; + + struct Impl; + std::unique_ptr impl; }; } // namespace VSTGUI diff --git a/plugins/vst/cmake/Vst3.cmake b/plugins/vst/cmake/Vst3.cmake index 644ce82bc..f461453fb 100644 --- a/plugins/vst/cmake/Vst3.cmake +++ b/plugins/vst/cmake/Vst3.cmake @@ -30,11 +30,14 @@ add_library(vst3sdk STATIC EXCLUDE_FROM_ALL "${VST3SDK_BASEDIR}/public.sdk/source/vst/vstnoteexpressiontypes.cpp" "${VST3SDK_BASEDIR}/public.sdk/source/vst/vstparameters.cpp" "${VST3SDK_BASEDIR}/public.sdk/source/vst/vstpresetfile.cpp" - "${VST3SDK_BASEDIR}/public.sdk/source/vst/vstrepresentation.cpp") + "${VST3SDK_BASEDIR}/public.sdk/source/vst/vstrepresentation.cpp" + "${VST3SDK_BASEDIR}/public.sdk/source/vst/utility/stringconvert.cpp") if(WIN32) target_sources(vst3sdk PRIVATE "${VST3SDK_BASEDIR}/public.sdk/source/common/threadchecker_win32.cpp") elseif(APPLE) + target_sources(vst3sdk PRIVATE + "${VST3SDK_BASEDIR}/public.sdk/source/common/threadchecker_mac.mm") else() target_sources(vst3sdk PRIVATE "${VST3SDK_BASEDIR}/public.sdk/source/common/threadchecker_linux.cpp") diff --git a/plugins/vst/external/VST_SDK/VST3_SDK/pluginterfaces b/plugins/vst/external/VST_SDK/VST3_SDK/pluginterfaces index b8566ef3b..93cef1afb 160000 --- a/plugins/vst/external/VST_SDK/VST3_SDK/pluginterfaces +++ b/plugins/vst/external/VST_SDK/VST3_SDK/pluginterfaces @@ -1 +1 @@ -Subproject commit b8566ef3b2a0cba60a96e3ef2001224c865c8b36 +Subproject commit 93cef1afb7061e488625045ba5a82abaa83d27fe diff --git a/plugins/vst/external/VST_SDK/VST3_SDK/public.sdk b/plugins/vst/external/VST_SDK/VST3_SDK/public.sdk index c3948deb4..9589800ed 160000 --- a/plugins/vst/external/VST_SDK/VST3_SDK/public.sdk +++ b/plugins/vst/external/VST_SDK/VST3_SDK/public.sdk @@ -1 +1 @@ -Subproject commit c3948deb407bdbff89de8fb6ab8500ea4df9d6d9 +Subproject commit 9589800ed94573354bc29de45eec5744523fbfcb diff --git a/scripts/appveyor/after_build.sh b/scripts/appveyor/after_build.sh index fd6863834..27718a3f1 100755 --- a/scripts/appveyor/after_build.sh +++ b/scripts/appveyor/after_build.sh @@ -31,6 +31,13 @@ else codesign --sign "${CODESIGN_IDENTITY}" --keychain build.keychain --force --verbose \ "${INSTALL_DIR}"/sfizz.lv2/Contents/Frameworks/*.dylib fi + # code-sign Puredata and dylibs + codesign --sign "${CODESIGN_IDENTITY}" --keychain build.keychain --force --verbose \ + "${INSTALL_DIR}"/Puredata/*/*.pd_darwin + if ls "${INSTALL_DIR}"/Puredata/*/*.dylib &> /dev/null; then + codesign --sign "${CODESIGN_IDENTITY}" --keychain build.keychain --force --verbose \ + "${INSTALL_DIR}"/Puredata/*/*.dylib + fi fi # Create the DMG @@ -39,12 +46,13 @@ cat > sfizz-dmg.json << EOF "title": "sfizz", "background": "${APPVEYOR_BUILD_FOLDER}/mac/dmg-back.png", "window": { - "size": { "width": 500, "height": 500 } + "size": { "width": 650, "height": 500 } }, "contents": [ { "x": 100, "y": 50, "type": "file", "path": "${INSTALL_DIR}/sfizz.vst3" }, { "x": 250, "y": 50, "type": "file", "path": "${INSTALL_DIR}/sfizz.component" }, { "x": 400, "y": 50, "type": "file", "path": "${INSTALL_DIR}/sfizz.lv2" }, + { "x": 550, "y": 50, "type": "file", "path": "${INSTALL_DIR}/Puredata" }, { "x": 100, "y": 400, "type": "link", "path": "/Library/Audio/Plug-Ins/VST3" }, { "x": 250, "y": 400, "type": "link", "path": "/Library/Audio/Plug-Ins/Components" }, { "x": 400, "y": 400, "type": "link", "path": "/Library/Audio/Plug-Ins/LV2" } diff --git a/scripts/appveyor/before_build.sh b/scripts/appveyor/before_build.sh index 5989ba146..479b10181 100644 --- a/scripts/appveyor/before_build.sh +++ b/scripts/appveyor/before_build.sh @@ -7,6 +7,7 @@ cmake -DCMAKE_BUILD_TYPE=Release \ -DCMAKE_OSX_ARCHITECTURES="arm64;x86_64" \ -DSFIZZ_VST=ON \ -DSFIZZ_AU=ON \ + -DSFIZZ_PUREDATA=ON \ -DSFIZZ_RENDER=OFF \ -DSFIZZ_SHARED=OFF \ -DSFIZZ_TESTS=ON \ @@ -14,4 +15,5 @@ cmake -DCMAKE_BUILD_TYPE=Release \ -DLV2PLUGIN_INSTALL_DIR=/ \ -DVSTPLUGIN_INSTALL_DIR=/ \ -DAUPLUGIN_INSTALL_DIR=/ \ + -DPDPLUGIN_INSTALL_DIR=/Puredata \ .. diff --git a/scripts/doxygen/Doxyfile.in b/scripts/doxygen/Doxyfile.in index baccc1518..584d6f692 100644 --- a/scripts/doxygen/Doxyfile.in +++ b/scripts/doxygen/Doxyfile.in @@ -259,9 +259,9 @@ TAB_SIZE = 4 # commands \{ and \} for these it is advised to use the version @{ and @} or use # a double escape (\\{ and \\}) -ALIASES = "true=true" \ - "false=false" \ - "null=NULL" +ALIASES = "true=\true\" \ + "false=\false\" \ + "null=\nullptr\" # Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources # only. Doxygen will then generate output that is more tailored for C. For diff --git a/scripts/doxygen/doxy2json.py b/scripts/doxygen/doxy2json.py index 4ca1aa695..a6d0332cf 100644 --- a/scripts/doxygen/doxy2json.py +++ b/scripts/doxygen/doxy2json.py @@ -22,7 +22,11 @@ - Tags in ALIASES list needs to be escaped (parsed by tag_list_as_string()) to avoid Doxygen convert them in its own tag structure, by loosing some - details (e.g.: true becomes true). + details (e.g.: true becomes true, + use \ as escaping and replace double quotes with apostrophe instead). + + - Some link references are lost like in + `sfizz_export_midnam` -> `sfizz_free_memory` - Merge the work done previously to be able to fully automate the process to be used in various CIs. diff --git a/scripts/innosetup.iss.in b/scripts/innosetup.iss.in index 577376fa4..ae23ab8c7 100644 --- a/scripts/innosetup.iss.in +++ b/scripts/innosetup.iss.in @@ -36,8 +36,8 @@ LicenseFile="sfizz.lv2\LICENSE.md" OutputBaseFileName={#MyAppName}-{#MyAppVersion}-msvc-{#Arch}-setup OutputDir=. UninstallFilesDir={app} -WizardImageFile="C:\Program Files (x86)\Inno Setup 6\WizModernImage-IS.bmp" -WizardSmallImageFile="C:\Program Files (x86)\Inno Setup 6\WizModernSmallImage-IS.bmp" +WizardImageFile=compiler:WizClassicImage-IS.bmp +WizardSmallImageFile=compiler:WizClassicSmallImage-IS.bmp [Languages] Name: "english"; MessagesFile: "compiler:Default.isl" @@ -46,6 +46,7 @@ Name: "english"; MessagesFile: "compiler:Default.isl" Name: "main"; Description: "Shared files"; Types: full custom; Flags: fixed Name: "lv2"; Description: "LV2 plugin"; Types: full custom; Name: "vst3"; Description: "VST3 plugin"; Types: full custom; +Name: "puredata"; Description: "Puredata external"; Types: full custom; [Files] Source: "sfizz.lv2\Contents\Binary\sfizz.dll"; Components: lv2; DestDir: "{commoncf}\LV2\sfizz.lv2\Contents\Binary"; Flags: ignoreversion @@ -54,13 +55,13 @@ Source: "sfizz.lv2\Contents\Resources\*"; Components: lv2; DestDir: "{commoncf}\ Source: "sfizz.lv2\manifest.ttl"; Components: lv2; DestDir: "{commoncf}\LV2\sfizz.lv2" Source: "sfizz.lv2\sfizz.ttl"; Components: lv2; DestDir: "{commoncf}\LV2\sfizz.lv2" Source: "sfizz.lv2\sfizz_ui.ttl"; Components: lv2; DestDir: "{commoncf}\LV2\sfizz.lv2" -Source: "sfizz.lv2\lgpl-3.0.txt"; Components: main; DestDir: "{app}" Source: "sfizz.lv2\LICENSE.md"; Components: main; DestDir: "{app}" Source: "sfizz.vst3\desktop.ini"; Components: vst3; DestDir: "{commoncf}\VST3\sfizz.vst3" Source: "sfizz.vst3\Contents\@VST3_PACKAGE_ARCHITECTURE@-win\sfizz.vst3"; Components: vst3; DestDir: "{commoncf}\VST3\sfizz.vst3\Contents\@VST3_PACKAGE_ARCHITECTURE@-win"; Flags: ignoreversion Source: "sfizz.vst3\Contents\Resources\*"; Components: vst3; DestDir: "{commoncf}\VST3\sfizz.vst3\Contents\Resources"; Flags: recursesubdirs Source: "sfizz.vst3\Plugin.ico"; Components: vst3; DestDir: "{commoncf}\VST3\sfizz.vst3" Source: "sfizz.vst3\gpl-3.0.txt"; Components: main; DestDir: "{app}" +Source: "pd\*"; Components: puredata; DestDir: "{commoncf}\Pd"; Flags: recursesubdirs ; Note(sfizz): OS older than Windows 10 require UI fonts to be installed system-wide Source: "sfizz.vst3\Contents\Resources\Fonts\sfizz-fluentui-system-r20.ttf"; DestDir: "{fonts}"; FontInstall: "Sfizz Fluent System R20"; Flags: uninsneveruninstall; OnlyBelowVersion: 6.4 Source: "sfizz.vst3\Contents\Resources\Fonts\sfizz-fluentui-system-f20.ttf"; DestDir: "{fonts}"; FontInstall: "Sfizz Fluent System F20"; Flags: uninsneveruninstall; OnlyBelowVersion: 6.4 diff --git a/scripts/run_clang_tidy.sh b/scripts/run_clang_tidy.sh index e970a75f4..1072c5384 100755 --- a/scripts/run_clang_tidy.sh +++ b/scripts/run_clang_tidy.sh @@ -14,6 +14,7 @@ clang-tidy \ src/sfizz/Panning.cpp \ src/sfizz/sfizz.cpp \ src/sfizz/Region.cpp \ + src/sfizz/RegionStateful.cpp \ src/sfizz/SIMDHelpers.cpp \ src/sfizz/simd/HelpersSSE.cpp \ src/sfizz/simd/HelpersAVX.cpp \ diff --git a/scripts/sfizz.pc.in b/scripts/sfizz.pc.in index 145f9db84..0d294c590 100644 --- a/scripts/sfizz.pc.in +++ b/scripts/sfizz.pc.in @@ -1,7 +1,7 @@ prefix=@CMAKE_INSTALL_PREFIX@ exec_prefix=@CMAKE_INSTALL_PREFIX@ -libdir=${exec_prefix}/@CMAKE_INSTALL_LIBDIR@ -includedir=${prefix}/@CMAKE_INSTALL_INCLUDEDIR@ +libdir=@CMAKE_INSTALL_FULL_LIBDIR@ +includedir=@CMAKE_INSTALL_FULL_INCLUDEDIR@ Name: @PROJECT_NAME@ Description: @PROJECT_DESCRIPTION@ diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 6c36e909e..ad678666f 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,5 +1,3 @@ -include(GNUInstallDirs) - set(FAUST_FILES sfizz/dsp/filters/filters_modulable.dsp sfizz/dsp/filters/rbj_filters.dsp @@ -96,6 +94,7 @@ set(SFIZZ_HEADERS sfizz/railsback/4-1.h sfizz/railsback/4-2.h sfizz/Region.h + sfizz/RegionStateful.h sfizz/RegionSet.h sfizz/Resources.h sfizz/RTSemaphore.h @@ -131,6 +130,7 @@ set(SFIZZ_SOURCES sfizz/AudioReader.cpp sfizz/FilterPool.cpp sfizz/EQPool.cpp + sfizz/RegionStateful.cpp sfizz/Region.cpp sfizz/Voice.cpp sfizz/ScopedFTZ.cpp @@ -161,6 +161,7 @@ set(SFIZZ_SOURCES sfizz/WindowedSinc.cpp sfizz/Interpolators.cpp sfizz/Layer.cpp + sfizz/Resources.cpp sfizz/modulations/ModId.cpp sfizz/modulations/ModKey.cpp sfizz/modulations/ModKeyHash.cpp @@ -190,7 +191,8 @@ set(SFIZZ_SOURCES sfizz/effects/impl/ResonantStringAVX.cpp sfizz/effects/impl/ResonantArray.cpp sfizz/effects/impl/ResonantArraySSE.cpp - sfizz/effects/impl/ResonantArrayAVX.cpp) + sfizz/effects/impl/ResonantArrayAVX.cpp + sfizz/utility/c++17/AlignedMemorySupport.cpp) include(SfizzSIMDSourceFiles) sfizz_add_simd_sources(SFIZZ_SOURCES ".") @@ -201,6 +203,7 @@ set(SFIZZ_PARSER_HEADERS sfizz/Range.h sfizz/Opcode.h sfizz/parser/Parser.h + sfizz/parser/ParserListener.h sfizz/parser/ParserPrivate.h sfizz/parser/ParserPrivate.hpp sfizz/SfzHelpers.h) @@ -249,11 +252,13 @@ endif() # Import library set(SFIZZ_IMPORT_HEADERS + sfizz/import/sfizz_import.h sfizz/import/ForeignInstrument.h sfizz/import/foreign_instruments/AudioFile.h sfizz/import/foreign_instruments/DecentSampler.h) set(SFIZZ_IMPORT_SOURCES + sfizz/import/sfizz_import.cpp sfizz/import/ForeignInstrument.cpp sfizz/import/foreign_instruments/AudioFile.cpp sfizz/import/foreign_instruments/DecentSampler.cpp) @@ -294,12 +299,12 @@ if(SFIZZ_USE_SNDFILE) target_compile_definitions(sfizz_internal PUBLIC "SFIZZ_USE_SNDFILE=1") target_link_libraries(sfizz_internal PUBLIC st_audiofile) endif() -if(WIN32) - target_compile_definitions(sfizz_internal PRIVATE _USE_MATH_DEFINES) -endif() if(SFIZZ_RELEASE_ASSERTS) target_compile_definitions(sfizz_internal PUBLIC "SFIZZ_ENABLE_RELEASE_ASSERT=1") endif() +if(SFIZZ_IMPLEMENT_CXX17_ALIGNED_NEW_SUPPORT) + target_compile_definitions(sfizz_internal PRIVATE "SFIZZ_IMPLEMENT_CXX17_ALIGNED_NEW_SUPPORT=1") +endif() sfizz_enable_fast_math(sfizz_internal) # Check that sfizz and cmake-side definitions are matching @@ -315,7 +320,12 @@ add_library(sfizz_static STATIC sfizz/sfizz_wrapper.cpp sfizz/sfizz.cpp sfizz/sf add_library(sfizz::static ALIAS sfizz_static) target_include_directories(sfizz_static PUBLIC .) target_link_libraries(sfizz_static PRIVATE sfizz::internal) -set_target_properties(sfizz_static PROPERTIES OUTPUT_NAME "sfizz" PUBLIC_HEADER "sfizz.h;sfizz.hpp;sfizz_message.h") +set_target_properties(sfizz_static PROPERTIES PUBLIC_HEADER "sfizz.h;sfizz.hpp;sfizz_message.h") +if(MSVC) + set_target_properties(sfizz_static PROPERTIES OUTPUT_NAME "sfizz_static") +else() + set_target_properties(sfizz_static PROPERTIES OUTPUT_NAME "sfizz") +endif() # Shared library and installation target if(SFIZZ_SHARED) @@ -324,7 +334,7 @@ if(SFIZZ_SHARED) target_include_directories(sfizz_shared PUBLIC .) target_link_libraries(sfizz_shared PRIVATE sfizz::internal) target_compile_definitions(sfizz_shared PRIVATE SFIZZ_EXPORT_SYMBOLS) - set_target_properties(sfizz_shared PROPERTIES SOVERSION "${PROJECT_VERSION_MAJOR}" OUTPUT_NAME "sfizz" PUBLIC_HEADER "sfizz.h;sfizz.hpp;sfizz_message.h") + set_target_properties(sfizz_shared PROPERTIES VERSION "${PROJECT_VERSION}" SOVERSION "${PROJECT_VERSION_MAJOR}" OUTPUT_NAME "sfizz" PUBLIC_HEADER "sfizz.h;sfizz.hpp;sfizz_message.h") sfizz_enable_lto_if_needed(sfizz_shared) if(NOT MSVC) @@ -347,6 +357,19 @@ endif() # Generic library alias add_library(sfizz::sfizz ALIAS sfizz_static) +# Git build identifier +add_custom_target(sfizz-generate-git-build-id + COMMAND "${CMAKE_COMMAND}" + "-DSOURCE_DIR=${PROJECT_SOURCE_DIR}" + "-DOUTPUT_FILE=${PROJECT_BINARY_DIR}/git-build-id/GitBuildId.c" + "-P" "${PROJECT_SOURCE_DIR}/cmake/GitBuildID.cmake" + BYPRODUCTS "${PROJECT_BINARY_DIR}/git-build-id/GitBuildId.c") +add_library(sfizz-git-build-id STATIC EXCLUDE_FROM_ALL + "sfizz/git-build-id/GitBuildId.h" + "${PROJECT_BINARY_DIR}/git-build-id/GitBuildId.c") +target_include_directories(sfizz-git-build-id PUBLIC "sfizz/git-build-id") +add_dependencies(sfizz-git-build-id sfizz-generate-git-build-id) + # Preserve generated files (Faust) set_directory_properties(PROPERTIES CLEAN_NO_CUSTOM TRUE) diff --git a/src/sfizz.h b/src/sfizz.h index 1c1526f1b..bc578a912 100644 --- a/src/sfizz.h +++ b/src/sfizz.h @@ -510,7 +510,7 @@ SFIZZ_EXPORTED_API void sfizz_send_hd_pitch_wheel(sfizz_synth_t* synth, int dela * * @param synth The synth. * @param delay The delay at which the event occurs; this should be lower - * than the size of the block in the next call to renderBlock(). + * than the size of the block in the next call to sfizz_render_block(). * @param aftertouch The aftertouch value, in domain 0 to 127. * * @par Thread-safety constraints @@ -528,7 +528,7 @@ SFIZZ_EXPORTED_API SFIZZ_DEPRECATED_API void sfizz_send_aftertouch(sfizz_synth_t * * @param synth The synth. * @param delay The delay at which the event occurs; this should be lower - * than the size of the block in the next call to renderBlock(). + * than the size of the block in the next call to sfizz_render_block(). * @param aftertouch The aftertouch value, in domain 0 to 127. * * @par Thread-safety constraints @@ -546,7 +546,7 @@ SFIZZ_EXPORTED_API void sfizz_send_channel_aftertouch(sfizz_synth_t* synth, int * * @param synth The synth. * @param delay The delay at which the event occurs; this should be lower - * than the size of the block in the next call to renderBlock(). + * than the size of the block in the next call to sfizz_render_block(). * @param aftertouch The normalized aftertouch value, in domain 0 to 1. * * @par Thread-safety constraints @@ -565,7 +565,7 @@ SFIZZ_EXPORTED_API void sfizz_send_hd_channel_aftertouch(sfizz_synth_t* synth, i * * @param synth The synth. * @param delay The delay at which the event occurs; this should be lower - * than the size of the block in the next call to renderBlock(). + * than the size of the block in the next call to sfizz_render_block(). * @param note_number The note number, in domain 0 to 127. * @param aftertouch The aftertouch value, in domain 0 to 127. * @@ -585,7 +585,7 @@ SFIZZ_EXPORTED_API void sfizz_send_poly_aftertouch(sfizz_synth_t* synth, int del * * @param synth The synth. * @param delay The delay at which the event occurs; this should be lower - * than the size of the block in the next call to renderBlock(). + * than the size of the block in the next call to sfizz_render_block(). * @param note_number The note number, in domain 0 to 127. * @param aftertouch The normalized aftertouch value, in domain 0 to 1. * @@ -598,8 +598,8 @@ SFIZZ_EXPORTED_API void sfizz_send_hd_poly_aftertouch(sfizz_synth_t* synth, int * @brief Send a tempo event. * * This command should be delay-ordered with all other time/signature commands, namely - * tempo(), timeSignature(), timePosition(), and playbackState(), otherwise the behavior - * of the synth is undefined. + * sfizz_send_tempo(), sfizz_send_time_signature(), sfizz_send_time_position(), + * and sfizz_send_playback_state(), otherwise the behavior of the synth is undefined. * * @since 0.2.0 * @@ -616,8 +616,8 @@ SFIZZ_EXPORTED_API SFIZZ_DEPRECATED_API void sfizz_send_tempo(sfizz_synth_t* syn * @brief Send a tempo event. * * This command should be delay-ordered with all other time/signature commands, namely - * tempo(), timeSignature(), timePosition(), and playbackState(), otherwise the behavior - * of the synth is undefined. + * sfizz_send_tempo(), sfizz_send_time_signature(), sfizz_send_time_position(), + * and sfizz_send_playback_state(), otherwise the behavior of the synth is undefined. * * @since 1.0.0 * @@ -634,8 +634,8 @@ SFIZZ_EXPORTED_API void sfizz_send_bpm_tempo(sfizz_synth_t* synth, int delay, fl * @brief Send the time signature. * * This command should be delay-ordered with all other time/signature commands, namely - * tempo(), timeSignature(), timePosition(), and playbackState(), otherwise the behavior - * of the synth is undefined. + * sfizz_send_tempo(), sfizz_send_time_signature(), sfizz_send_time_position(), + * and sfizz_send_playback_state(), otherwise the behavior of the synth is undefined. * * @since 0.5.0 * @@ -653,8 +653,8 @@ SFIZZ_EXPORTED_API void sfizz_send_time_signature(sfizz_synth_t* synth, int dela * @brief Send the time position. * * This command should be delay-ordered with all other time/signature commands, namely - * tempo(), timeSignature(), timePosition(), and playbackState(), otherwise the behavior - * of the synth is undefined. + * sfizz_send_tempo(), sfizz_send_time_signature(), sfizz_send_time_position(), + * and sfizz_send_playback_state(), otherwise the behavior of the synth is undefined. * * @since 0.5.0 * @@ -672,8 +672,8 @@ SFIZZ_EXPORTED_API void sfizz_send_time_position(sfizz_synth_t* synth, int delay * @brief Send the playback state. * * This command should be delay-ordered with all other time/signature commands, namely - * tempo(), timeSignature(), timePosition(), and playbackState(), otherwise the behavior - * of the synth is undefined. + * sfizz_send_tempo(), sfizz_send_time_signature(), sfizz_send_time_position(), + * and sfizz_send_playback_state(), otherwise the behavior of the synth is undefined. * * @since 0.5.0 * diff --git a/src/sfizz/ADSREnvelope.cpp b/src/sfizz/ADSREnvelope.cpp index daf0872ab..6167b6896 100644 --- a/src/sfizz/ADSREnvelope.cpp +++ b/src/sfizz/ADSREnvelope.cpp @@ -38,30 +38,54 @@ Float ADSREnvelope::secondsToExpRate(Float timeInSeconds) const noexcept return std::exp(Float(-9.0) / (timeInSeconds * sampleRate)); }; -void ADSREnvelope::reset(const EGDescription& desc, const Region& region, const MidiState& state, int delay, float velocity, float sampleRate) noexcept +void ADSREnvelope::reset(const EGDescription& desc, const Region& region, int delay, float velocity, float sampleRate) noexcept { this->sampleRate = sampleRate; - - this->delay = delay + secondsToSamples(desc.getDelay(state, velocity)); - this->attackStep = secondsToLinRate(desc.getAttack(state, velocity)); - this->decayRate = secondsToExpRate(desc.getDecay(state, velocity)); - this->releaseRate = secondsToExpRate(desc.getRelease(state, velocity)); - this->hold = secondsToSamples(desc.getHold(state, velocity)); - this->sustain = clamp(desc.getSustain(state, velocity), 0.0f, 1.0f); - this->start = clamp(desc.getStart(state, velocity), 0.0f, 1.0f); - + desc_ = &desc; + triggerVelocity_ = velocity; + currentState = State::Delay; // Has to be before the update + updateValues(delay); releaseDelay = 0; - sustainThreshold = this->sustain + config::virtuallyZero; shouldRelease = false; freeRunning = ( (this->sustain <= Float(config::sustainFreeRunningThreshold)) || (region.loopMode == LoopMode::one_shot && region.isOscillator()) ); currentValue = this->start; - currentState = State::Delay; +} + +void ADSREnvelope::updateValues(int delay) noexcept +{ + if (currentState == State::Delay) + this->delay = delay + secondsToSamples(desc_->getDelay(midiState_, triggerVelocity_, delay)); + + this->attackStep = secondsToLinRate(desc_->getAttack(midiState_, triggerVelocity_, delay)); + this->decayRate = secondsToExpRate(desc_->getDecay(midiState_, triggerVelocity_, delay)); + this->releaseRate = secondsToExpRate(desc_->getRelease(midiState_, triggerVelocity_, delay)); + this->hold = secondsToSamples(desc_->getHold(midiState_, triggerVelocity_, delay)); + this->sustain = clamp(desc_->getSustain(midiState_, triggerVelocity_, delay), 0.0f, 1.0f); + this->start = clamp(desc_->getStart(midiState_, triggerVelocity_, delay), 0.0f, 1.0f); + sustainThreshold = this->sustain + config::virtuallyZero; } void ADSREnvelope::getBlock(absl::Span output) noexcept +{ + if (desc_ && desc_->dynamic) { + int processed = 0; + int remaining = static_cast(output.size()); + while(remaining > 0) { + updateValues(processed); + int chunkSize = min(config::processChunkSize, remaining); + getBlockInternal(output.subspan(processed, chunkSize)); + processed += chunkSize; + remaining -= chunkSize; + } + } else { + getBlockInternal(output); + } +} + +void ADSREnvelope::getBlockInternal(absl::Span output) noexcept { State currentState = this->currentState; Float currentValue = this->currentValue; @@ -122,7 +146,8 @@ void ADSREnvelope::getBlock(absl::Span output) noexcept break; } while (count < size) { - currentValue = std::max(sustain, currentValue + transitionDelta); + if (currentValue > sustain) + currentValue += transitionDelta; output[count++] = currentValue; } break; @@ -173,6 +198,14 @@ void ADSREnvelope::startRelease(int releaseDelay) noexcept this->releaseDelay = releaseDelay; } +void ADSREnvelope::cancelRelease(int delay) noexcept +{ + (void)delay; + currentState = State::Sustain; + shouldRelease = false; + this->releaseDelay = -1; +} + void ADSREnvelope::setReleaseTime(Float timeInSeconds) noexcept { releaseRate = secondsToExpRate(timeInSeconds); diff --git a/src/sfizz/ADSREnvelope.h b/src/sfizz/ADSREnvelope.h index f1d3d6686..a970a1d42 100644 --- a/src/sfizz/ADSREnvelope.h +++ b/src/sfizz/ADSREnvelope.h @@ -18,7 +18,8 @@ class ADSREnvelope { public: using Float = float; - ADSREnvelope() = default; + ADSREnvelope(const MidiState& state) + : midiState_(state) {} /** * @brief Resets the ADSR envelope given a Region, the current midi state, and a delay and * trigger velocity @@ -29,10 +30,9 @@ class ADSREnvelope { * @param delay * @param velocity */ - void reset(const EGDescription& desc, const Region& region, const MidiState& state, int delay, float velocity, float sampleRate) noexcept; + void reset(const EGDescription& desc, const Region& region, int delay, float velocity, float sampleRate) noexcept; /** - * @brief Get a block of values for the envelope. This method tries hard to be efficient - * and hopefully it is. + * @brief Get the next block of values for the envelope. * * @param output */ @@ -49,6 +49,12 @@ class ADSREnvelope { * @param releaseDelay the delay before releasing in samples */ void startRelease(int releaseDelay) noexcept; + /** + * @brief Cancel a release and get back into sustain. + * + * @param delay + */ + void cancelRelease(int delay) noexcept; /** * @brief Is the envelope smoothing? * @@ -75,6 +81,8 @@ class ADSREnvelope { int secondsToSamples(Float timeInSeconds) const noexcept; Float secondsToLinRate(Float timeInSeconds) const noexcept; Float secondsToExpRate(Float timeInSeconds) const noexcept; + void updateValues(int delay = 0) noexcept; + void getBlockInternal(absl::Span output) noexcept; enum class State { Delay, @@ -88,6 +96,9 @@ class ADSREnvelope { }; State currentState { State::Done }; Float currentValue { 0.0 }; + const EGDescription* desc_ { nullptr }; + const MidiState& midiState_; + float triggerVelocity_ { 0.0f }; int delay { 0 }; Float attackStep { 0 }; Float decayRate { 0 }; diff --git a/src/sfizz/Buffer.h b/src/sfizz/Buffer.h index 399e04ae5..c522f7e12 100644 --- a/src/sfizz/Buffer.h +++ b/src/sfizz/Buffer.h @@ -26,10 +26,12 @@ #pragma once #include "Config.h" #include "utility/LeakDetector.h" +#include #include #include #include #include +#include #include #include @@ -230,7 +232,7 @@ class Buffer { * * @param other */ - Buffer(const Buffer& other) + Buffer(const Buffer& other) { resize(other.size()); std::memcpy(this->data(), other.data(), other.size() * sizeof(value_type)); @@ -241,7 +243,7 @@ class Buffer { * * @param other */ - Buffer(Buffer&& other) noexcept + Buffer(Buffer&& other) noexcept : largerSize(other.largerSize), alignedSize(other.alignedSize), normalData(other.normalData), @@ -252,7 +254,7 @@ class Buffer { other._clear(); } - Buffer& operator=(const Buffer& other) + Buffer& operator=(const Buffer& other) { if (this != &other) { resize(other.size()); @@ -261,7 +263,7 @@ class Buffer { return *this; } - Buffer& operator=(Buffer&& other) noexcept + Buffer& operator=(Buffer&& other) noexcept { if (this != &other) { if (largerSize > 0) diff --git a/src/sfizz/Config.h b/src/sfizz/Config.h index 3631e6c7f..a458a8376 100644 --- a/src/sfizz/Config.h +++ b/src/sfizz/Config.h @@ -70,7 +70,8 @@ namespace config { constexpr float powerFollowerReleaseTime { 200e-3f }; constexpr uint16_t numCCs { 512 }; constexpr int maxCurves { 256 }; - constexpr int chunkSize { 1024 }; + constexpr int fileChunkSize { 1024 }; + constexpr int processChunkSize { 16 }; constexpr unsigned int defaultAlignment { 16 }; constexpr int filtersInPool { maxVoices * 2 }; constexpr int excessFileFrames { 64 }; diff --git a/src/sfizz/Defaults.cpp b/src/sfizz/Defaults.cpp index 95d131458..bb17b1bfd 100644 --- a/src/sfizz/Defaults.cpp +++ b/src/sfizz/Defaults.cpp @@ -41,23 +41,27 @@ FloatSpec oscillatorDetuneMod { 0.0f, {-12000.0f, 12000.0f}, kPermissiveBounds } FloatSpec oscillatorModDepth { 0.0f, {0.0f, 10000.0f}, kNormalizePercent|kPermissiveBounds }; FloatSpec oscillatorModDepthMod { 0.0f, {0.0f, 10000.0f}, kNormalizePercent|kPermissiveBounds }; Int32Spec oscillatorQuality { 1, {0, 3}, 0 }; -UInt32Spec group { 0, {0, uint32_t_max}, 0 }; +Int64Spec group { 0, {-int32_t_max, uint32_t_max}, 0 }; FloatSpec offTime { 6e-3f, {0.0f, 100.0f}, kPermissiveBounds }; UInt32Spec polyphony { config::maxVoices, {0, config::maxVoices}, kEnforceBounds }; -UInt32Spec notePolyphony { config::maxVoices, {0, config::maxVoices}, kEnforceBounds }; +UInt32Spec notePolyphony { config::maxVoices, {1, config::maxVoices}, kEnforceBounds }; UInt8Spec key { 60, {0, 127}, kCanBeNote }; UInt8Spec loKey { 0, {0, 127}, kCanBeNote }; UInt8Spec hiKey { 127, {0, 127}, kCanBeNote }; FloatSpec loCC { 0, {0.0f, 127.0f}, kNormalizeMidi|kPermissiveBounds }; -FloatSpec hiCC { 127, {0.0f, 127.0f}, kNormalizeMidi|kPermissiveBounds }; +FloatSpec hiCC { 127, {0.0f, 127.0f}, kNormalizeMidi|kFillGap|kPermissiveBounds }; +FloatSpec xfoutLo { 127.0f, {0.0f, 127.0f}, kNormalizeMidi|kPermissiveBounds }; +FloatSpec xfoutHi { 127.0f, {0.0f, 127.0f}, kNormalizeMidi|kFillGap|kPermissiveBounds }; +FloatSpec xfinLo { 0.0f, {0.0f, 127.0f}, kNormalizeMidi|kPermissiveBounds }; +FloatSpec xfinHi { 0.0f, {0.0f, 127.0f}, kNormalizeMidi|kFillGap|kPermissiveBounds }; FloatSpec loVel { 0, {0.0f, 127.0f}, kNormalizeMidi|kPermissiveBounds }; -FloatSpec hiVel { 127, {0.0f, 127.0f}, kNormalizeMidi|kPermissiveBounds }; +FloatSpec hiVel { 127, {0.0f, 127.0f}, kNormalizeMidi|kFillGap|kPermissiveBounds }; FloatSpec loChannelAftertouch { 0, {0, 127}, kNormalizeMidi|kPermissiveBounds }; -FloatSpec hiChannelAftertouch { 127, {0, 127}, kNormalizeMidi|kPermissiveBounds }; +FloatSpec hiChannelAftertouch { 127, {0, 127}, kNormalizeMidi|kFillGap|kPermissiveBounds }; FloatSpec loPolyAftertouch { 0, {0, 127}, kNormalizeMidi|kPermissiveBounds }; -FloatSpec hiPolyAftertouch { 127, {0, 127}, kNormalizeMidi|kPermissiveBounds }; +FloatSpec hiPolyAftertouch { 127, {0, 127}, kNormalizeMidi|kFillGap|kPermissiveBounds }; FloatSpec loBend { -8191, {-8192.0f, 8191.0f}, kNormalizeBend|kPermissiveBounds }; -FloatSpec hiBend { 8191, {-8192.0f, 8191.0f}, kNormalizeBend|kPermissiveBounds }; +FloatSpec hiBend { 8191, {-8192.0f, 8191.0f}, kNormalizeBend|kFillGap|kPermissiveBounds }; FloatSpec loNormalized { 0.0f, {0.0f, 1.0f}, kPermissiveBounds }; FloatSpec hiNormalized { 1.0f, {0.0f, 1.0f}, kPermissiveBounds }; FloatSpec loBipolar { -1.0f, {-1.0f, 1.0f}, kPermissiveBounds }; @@ -86,6 +90,7 @@ FloatSpec width { 100.0f, {-100.0f, 100.0f}, kNormalizePercent|kPermissiveBounds FloatSpec widthMod { 0.0f, {-200.0f, 200.0f}, kNormalizePercent|kPermissiveBounds }; FloatSpec ampKeytrack { 0.0f, {-96.0f, 12.0f}, kPermissiveBounds }; FloatSpec ampVeltrack { 100.0f, {-100.0f, 100.0f}, kNormalizePercent|kPermissiveBounds }; +FloatSpec ampVeltrackMod { 0.0f, {-100.0f, 100.0f}, kNormalizePercent|kPermissiveBounds }; FloatSpec ampVelcurve { 0.0f, {0.0f, 1.0f}, kPermissiveBounds }; FloatSpec ampRandom { 0.0f, {-24.0f, 24.0f}, kPermissiveBounds }; BoolSpec rtDead { false, {false, true}, kEnforceBounds }; @@ -99,6 +104,7 @@ FloatSpec filterGainMod { 0.0f, {-96.0f, 96.0f}, kPermissiveBounds }; FloatSpec filterRandom { 0.0f, {-12000.0f, 12000.0f}, kPermissiveBounds }; FloatSpec filterKeytrack { 0, {0, 1200}, kPermissiveBounds }; FloatSpec filterVeltrack { 0, {-12000, 12000}, kPermissiveBounds }; +FloatSpec filterVeltrackMod { 0.0f, {-12000, 12000}, kPermissiveBounds }; FloatSpec eqBandwidth { 1.0f, {0.001f, 4.0f}, kPermissiveBounds }; FloatSpec eqBandwidthMod { 0.0f, {-4.0f, 4.0f}, kPermissiveBounds }; FloatSpec eqFrequency { 0.0f, {0.0f, 20000.0f}, kPermissiveBounds }; @@ -110,6 +116,7 @@ FloatSpec eqVel2Gain { 0.0f, {-96.0f, 96.0f}, kPermissiveBounds }; FloatSpec pitchKeytrack { 100, {-1200, 1200}, kPermissiveBounds }; FloatSpec pitchRandom { 0.0f, {-12000.0f, 12000.0f}, kPermissiveBounds }; FloatSpec pitchVeltrack { 0, {-12000, 12000}, kPermissiveBounds }; +FloatSpec pitchVeltrackMod { 0.0f, {-12000, 12000}, kPermissiveBounds }; FloatSpec transpose { 0, {-127, 127}, kPermissiveBounds }; FloatSpec pitch { 0.0f, {-2400.0f, 2400.0f}, kPermissiveBounds }; FloatSpec pitchMod { 0.0f, {-2400.0f, 2400.0f}, kPermissiveBounds }; @@ -144,11 +151,14 @@ FloatSpec egPercent { 0.0f, {0.0f, 100.0f}, kNormalizePercent|kPermissiveBounds FloatSpec egPercentMod { 0.0f, {-100.0f, 100.0f}, kNormalizePercent|kPermissiveBounds }; FloatSpec egDepth { 0.0f, {-12000.0f, 12000.0f}, kPermissiveBounds }; FloatSpec egVel2Depth { 0.0f, {-12000.0f, 12000.0f}, kPermissiveBounds }; +BoolSpec egDynamic { 0, {0, 1}, kEnforceBounds }; BoolSpec flexEGAmpeg { false, {0, 1}, kEnforceBounds }; BoolSpec flexEGDynamic { 0, {0, 1}, kEnforceBounds }; Int32Spec flexEGSustain { 0, {0, 100}, kEnforceLowerBound|kPermissiveUpperBound }; FloatSpec flexEGPointTime { 0.0f, {0.0f, 100.0f}, kPermissiveBounds }; +FloatSpec flexEGPointTimeMod { 0.0f, {-100.0f, 100.0f}, kPermissiveBounds }; FloatSpec flexEGPointLevel { 0.0f, {-1.0f, 1.0f}, kPermissiveBounds }; +FloatSpec flexEGPointLevelMod { 0.0f, {-1.0f, 1.0f}, kPermissiveBounds }; FloatSpec flexEGPointShape { 0.0f, {-100.0f, 100.0f}, kPermissiveBounds }; Int32Spec sampleQuality { 2, {0, 10}, 0 }; Int32Spec octaveOffset { 0, {-10, 10}, kPermissiveBounds }; @@ -183,6 +193,7 @@ FloatSpec lofiBitred { 0.0f, {0.0f, 100.0f}, 0 }; FloatSpec lofiDecim { 0.0f, {0.0f, 100.0f}, 0 }; FloatSpec rectify { 0.0f, {0.0f, 100.0f}, 0 }; UInt32Spec stringsNumber { maxStrings, {0, maxStrings}, 0 }; +BoolSpec sustainCancelsRelease { false, {0, 1}, kEnforceBounds }; ESpec trigger { Trigger::attack, {Trigger::attack, Trigger::release_key}, 0}; ESpec crossfadeCurve { CrossfadeCurve::power, {CrossfadeCurve::gain, CrossfadeCurve::power}, 0}; diff --git a/src/sfizz/Defaults.h b/src/sfizz/Defaults.h index bee569770..dccba31ce 100644 --- a/src/sfizz/Defaults.h +++ b/src/sfizz/Defaults.h @@ -60,6 +60,7 @@ enum OpcodeFlags : int { kNormalizeBend = 1 << 7, kWrapPhase = 1 << 8, kDb2Mag = 1 << 9, + kFillGap = 1 << 10, // Fill in the gap when converting from discrete midi values to float, so that 13 is actually 13.999999... }; template @@ -97,8 +98,12 @@ struct OpcodeSpec return input; else if (flags & kNormalizePercent) return static_cast(input / U(100)); - else if (flags & kNormalizeMidi) - return static_cast(input / U(127)); + else if (flags & kNormalizeMidi) { + if ((flags & kFillGap) && (input <= U(126)) && input >= 0) + return std::nextafter(static_cast((input + 1.0f) / U(127)), 0.0f); + else + return static_cast(input / U(127)); + } else if (flags & kNormalizeBend) return static_cast(input / U(8191)); else if (flags & kDb2Mag) @@ -148,7 +153,7 @@ namespace Default extern const OpcodeSpec oscillatorModDepth; extern const OpcodeSpec oscillatorModDepthMod; extern const OpcodeSpec oscillatorQuality; - extern const OpcodeSpec group; + extern const OpcodeSpec group; extern const OpcodeSpec offTime; extern const OpcodeSpec polyphony; extern const OpcodeSpec notePolyphony; @@ -159,6 +164,10 @@ namespace Default extern const OpcodeSpec hiVel; extern const OpcodeSpec loCC; extern const OpcodeSpec hiCC; + extern const OpcodeSpec xfoutLo; + extern const OpcodeSpec xfoutHi; + extern const OpcodeSpec xfinHi; + extern const OpcodeSpec xfinLo; extern const OpcodeSpec loBend; extern const OpcodeSpec hiBend; extern const OpcodeSpec loNormalized; @@ -193,6 +202,7 @@ namespace Default extern const OpcodeSpec widthMod; extern const OpcodeSpec ampKeytrack; extern const OpcodeSpec ampVeltrack; + extern const OpcodeSpec ampVeltrackMod; extern const OpcodeSpec ampVelcurve; extern const OpcodeSpec ampRandom; extern const OpcodeSpec rtDead; @@ -206,6 +216,7 @@ namespace Default extern const OpcodeSpec filterRandom; extern const OpcodeSpec filterKeytrack; extern const OpcodeSpec filterVeltrack; + extern const OpcodeSpec filterVeltrackMod; extern const OpcodeSpec eqBandwidth; extern const OpcodeSpec eqBandwidthMod; extern const OpcodeSpec eqFrequency; @@ -217,6 +228,7 @@ namespace Default extern const OpcodeSpec pitchKeytrack; extern const OpcodeSpec pitchRandom; extern const OpcodeSpec pitchVeltrack; + extern const OpcodeSpec pitchVeltrackMod; extern const OpcodeSpec transpose; extern const OpcodeSpec pitch; extern const OpcodeSpec pitchMod; @@ -251,11 +263,14 @@ namespace Default extern const OpcodeSpec egPercentMod; extern const OpcodeSpec egDepth; extern const OpcodeSpec egVel2Depth; + extern const OpcodeSpec egDynamic; extern const OpcodeSpec flexEGAmpeg; extern const OpcodeSpec flexEGDynamic; extern const OpcodeSpec flexEGSustain; extern const OpcodeSpec flexEGPointTime; + extern const OpcodeSpec flexEGPointTimeMod; extern const OpcodeSpec flexEGPointLevel; + extern const OpcodeSpec flexEGPointLevelMod; extern const OpcodeSpec flexEGPointShape; extern const OpcodeSpec sampleQuality; extern const OpcodeSpec octaveOffset; @@ -296,6 +311,7 @@ namespace Default extern const OpcodeSpec selfMask; extern const OpcodeSpec filter; extern const OpcodeSpec eq; + extern const OpcodeSpec sustainCancelsRelease; // Default/max count for objects constexpr int numEQs { 3 }; diff --git a/src/sfizz/EGDescription.h b/src/sfizz/EGDescription.h index 26202ff14..fce5fc757 100644 --- a/src/sfizz/EGDescription.h +++ b/src/sfizz/EGDescription.h @@ -42,22 +42,6 @@ namespace sfz { * */ -/** - * @brief If a cc switch exists for the value, returns the value with the CC modifier, otherwise returns the value alone. - * - * @param ccValues - * @param ccSwitch - * @param value - * @return float - */ -inline float ccSwitchedValue(const MidiState& state, const absl::optional>& ccSwitch, float value) noexcept -{ - if (ccSwitch) - return value + ccSwitch->data * state.getCCValue(ccSwitch->cc); - else - return value; -} - struct EGDescription { EGDescription() = default; EGDescription(const EGDescription&) = default; @@ -89,6 +73,7 @@ struct EGDescription { CCMap ccRelease; CCMap ccStart; CCMap ccSustain; + bool dynamic { false }; /** * @brief Get the attack with possibly a CC modifier and a velocity modifier @@ -97,12 +82,12 @@ struct EGDescription { * @param velocity * @return float */ - float getAttack(const MidiState& state, float velocity) const noexcept + float getAttack(const MidiState& state, float velocity, int delay = 0) const noexcept { ASSERT(velocity >= 0.0f && velocity <= 1.0f); float returnedValue { attack + velocity * vel2attack }; for (auto& mod: ccAttack) { - returnedValue += state.getCCValue(mod.cc) * mod.data; + returnedValue += state.getCCValueAt(mod.cc, delay) * mod.data; } return returnedValue; } @@ -113,12 +98,12 @@ struct EGDescription { * @param velocity * @return float */ - float getDecay(const MidiState& state, float velocity) const noexcept + float getDecay(const MidiState& state, float velocity, int delay = 0) const noexcept { ASSERT(velocity >= 0.0f && velocity <= 1.0f); float returnedValue { decay + velocity * vel2decay }; for (auto& mod: ccDecay) { - returnedValue += state.getCCValue(mod.cc) * mod.data; + returnedValue += state.getCCValueAt(mod.cc, delay) * mod.data; } return returnedValue; } @@ -129,12 +114,12 @@ struct EGDescription { * @param velocity * @return float */ - float getDelay(const MidiState& state, float velocity) const noexcept + float getDelay(const MidiState& state, float velocity, int delay = 0) const noexcept { ASSERT(velocity >= 0.0f && velocity <= 1.0f); - float returnedValue { delay + velocity * vel2delay }; + float returnedValue { this->delay + velocity * vel2delay }; for (auto& mod: ccDelay) { - returnedValue += state.getCCValue(mod.cc) * mod.data; + returnedValue += state.getCCValueAt(mod.cc, delay) * mod.data; } return returnedValue; } @@ -145,12 +130,12 @@ struct EGDescription { * @param velocity * @return float */ - float getHold(const MidiState& state, float velocity) const noexcept + float getHold(const MidiState& state, float velocity, int delay = 0) const noexcept { ASSERT(velocity >= 0.0f && velocity <= 1.0f); float returnedValue { hold + velocity * vel2hold }; for (auto& mod: ccHold) { - returnedValue += state.getCCValue(mod.cc) * mod.data; + returnedValue += state.getCCValueAt(mod.cc, delay) * mod.data; } return returnedValue; } @@ -161,12 +146,12 @@ struct EGDescription { * @param velocity * @return float */ - float getRelease(const MidiState& state, float velocity) const noexcept + float getRelease(const MidiState& state, float velocity, int delay = 0) const noexcept { ASSERT(velocity >= 0.0f && velocity <= 1.0f); float returnedValue { release + velocity * vel2release }; for (auto& mod: ccRelease) { - returnedValue += state.getCCValue(mod.cc) * mod.data; + returnedValue += state.getCCValueAt(mod.cc, delay) * mod.data; } return returnedValue; } @@ -177,12 +162,12 @@ struct EGDescription { * @param velocity * @return float */ - float getStart(const MidiState& state, float velocity) const noexcept + float getStart(const MidiState& state, float velocity, int delay = 0) const noexcept { UNUSED(velocity); float returnedValue { start }; for (auto& mod: ccStart) { - returnedValue += state.getCCValue(mod.cc) * mod.data; + returnedValue += state.getCCValueAt(mod.cc, delay) * mod.data; } return returnedValue; } @@ -193,12 +178,12 @@ struct EGDescription { * @param velocity * @return float */ - float getSustain(const MidiState& state, float velocity) const noexcept + float getSustain(const MidiState& state, float velocity, int delay = 0) const noexcept { ASSERT(velocity >= 0.0f && velocity <= 1.0f); float returnedValue { sustain + velocity * vel2sustain }; for (auto& mod: ccSustain) { - returnedValue += state.getCCValue(mod.cc) * mod.data; + returnedValue += state.getCCValueAt(mod.cc, delay) * mod.data; } return returnedValue; } diff --git a/src/sfizz/EQPool.cpp b/src/sfizz/EQPool.cpp index 88b00e622..28675eb2a 100644 --- a/src/sfizz/EQPool.cpp +++ b/src/sfizz/EQPool.cpp @@ -1,4 +1,7 @@ #include "EQPool.h" +#include "Region.h" +#include "Resources.h" +#include "BufferPool.h" #include "SIMDHelpers.h" #include "utility/SwapAndPop.h" #include @@ -31,9 +34,10 @@ void sfz::EQHolder::setup(const Region& region, unsigned eqId, float velocity) baseBandwidth = description->bandwidth; baseGain = description->gain + velocity * description->vel2gain; - gainTarget = resources.modMatrix.findTarget(ModKey::createNXYZ(ModId::EqGain, region.id, eqId)); - bandwidthTarget = resources.modMatrix.findTarget(ModKey::createNXYZ(ModId::EqBandwidth, region.id, eqId)); - frequencyTarget = resources.modMatrix.findTarget(ModKey::createNXYZ(ModId::EqFrequency, region.id, eqId)); + const ModMatrix& mm = resources.getModMatrix(); + gainTarget = mm.findTarget(ModKey::createNXYZ(ModId::EqGain, region.id, eqId)); + bandwidthTarget = mm.findTarget(ModKey::createNXYZ(ModId::EqBandwidth, region.id, eqId)); + frequencyTarget = mm.findTarget(ModKey::createNXYZ(ModId::EqFrequency, region.id, eqId)); // Disables smoothing of the parameters on the first call prepared = false; @@ -47,10 +51,11 @@ void sfz::EQHolder::process(const float** inputs, float** outputs, unsigned numF return; } - ModMatrix& mm = resources.modMatrix; - auto frequencySpan = resources.bufferPool.getBuffer(numFrames); - auto bandwidthSpan = resources.bufferPool.getBuffer(numFrames); - auto gainSpan = resources.bufferPool.getBuffer(numFrames); + ModMatrix& mm = resources.getModMatrix(); + BufferPool& bufferPool = resources.getBufferPool(); + auto frequencySpan = bufferPool.getBuffer(numFrames); + auto bandwidthSpan = bufferPool.getBuffer(numFrames); + auto gainSpan = bufferPool.getBuffer(numFrames); if (!frequencySpan || !bandwidthSpan || !gainSpan) return; diff --git a/src/sfizz/EQPool.h b/src/sfizz/EQPool.h index 54567b2fa..c7e44b81e 100644 --- a/src/sfizz/EQPool.h +++ b/src/sfizz/EQPool.h @@ -1,12 +1,14 @@ #pragma once #include "SfzFilter.h" -#include "Region.h" -#include "Resources.h" +#include "Defaults.h" +#include "modulations/ModMatrix.h" #include #include -namespace sfz -{ +namespace sfz { +struct Region; +class Resources; +struct EQDescription; class EQHolder { @@ -52,4 +54,4 @@ class EQHolder ModMatrix::TargetId bandwidthTarget; }; -} +} // namespace sfz diff --git a/src/sfizz/FileMetadata.cpp b/src/sfizz/FileMetadata.cpp index 65ba9677c..541734a9b 100644 --- a/src/sfizz/FileMetadata.cpp +++ b/src/sfizz/FileMetadata.cpp @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include diff --git a/src/sfizz/FilePool.cpp b/src/sfizz/FilePool.cpp index 6bd4e35ae..cce2941f9 100644 --- a/src/sfizz/FilePool.cpp +++ b/src/sfizz/FilePool.cpp @@ -101,7 +101,7 @@ void streamFromFile(sfz::AudioReader& reader, sfz::FileAudioBuffer& output, std: { const auto numFrames = static_cast(reader.frames()); const auto numChannels = reader.channels(); - const auto chunkSize = static_cast(sfz::config::chunkSize); + const auto chunkSize = static_cast(sfz::config::fileChunkSize); output.reset(); output.addChannels(reader.channels()); diff --git a/src/sfizz/FilterDescription.h b/src/sfizz/FilterDescription.h index 4674477c1..59eadab9c 100644 --- a/src/sfizz/FilterDescription.h +++ b/src/sfizz/FilterDescription.h @@ -20,6 +20,7 @@ struct FilterDescription float keytrack { Default::filterKeytrack }; uint8_t keycenter { Default::key }; float veltrack { Default::filterVeltrack }; + CCMap> veltrackCC { ModifierCurvePair{ Default::filterVeltrackMod, Default::curveCC } }; float random { Default::filterRandom }; FilterType type { FilterType::kFilterLpf2p }; }; diff --git a/src/sfizz/FilterPool.cpp b/src/sfizz/FilterPool.cpp index 1fd77988c..8fbd356d5 100644 --- a/src/sfizz/FilterPool.cpp +++ b/src/sfizz/FilterPool.cpp @@ -1,4 +1,7 @@ #include "FilterPool.h" +#include "Region.h" +#include "Resources.h" +#include "BufferPool.h" #include "SIMDHelpers.h" #include "utility/SwapAndPop.h" #include @@ -35,14 +38,21 @@ void sfz::FilterHolder::setup(const Region& region, unsigned filterId, int noteN } const auto keytrack = description->keytrack * float(noteNumber - description->keycenter); baseCutoff *= centsFactor(keytrack); - const auto veltrack = description->veltrack * velocity; - baseCutoff *= centsFactor(veltrack); + auto veltrack = description->veltrack; + + for (const auto& mod : description->veltrackCC) { + const auto& curve = resources.getCurves().getCurve(mod.data.curve); + const float value = resources.getMidiState().getCCValue(mod.cc); + veltrack += curve.evalNormalized(value) * mod.data.modifier; + } + + baseCutoff *= centsFactor(veltrack * velocity); baseCutoff = Default::filterCutoff.bounds.clamp(baseCutoff); baseGain = description->gain; baseResonance = description->resonance; - ModMatrix& mm = resources.modMatrix; + ModMatrix& mm = resources.getModMatrix(); gainTarget = mm.findTarget(ModKey::createNXYZ(ModId::FilGain, region.id, filterId)); cutoffTarget = mm.findTarget(ModKey::createNXYZ(ModId::FilCutoff, region.id, filterId)); resonanceTarget = mm.findTarget(ModKey::createNXYZ(ModId::FilResonance, region.id, filterId)); @@ -62,10 +72,11 @@ void sfz::FilterHolder::process(const float** inputs, float** outputs, unsigned return; } - ModMatrix& mm = resources.modMatrix; - auto cutoffSpan = resources.bufferPool.getBuffer(numFrames); - auto resonanceSpan = resources.bufferPool.getBuffer(numFrames); - auto gainSpan = resources.bufferPool.getBuffer(numFrames); + ModMatrix& mm = resources.getModMatrix(); + BufferPool& bufferPool = resources.getBufferPool(); + auto cutoffSpan = bufferPool.getBuffer(numFrames); + auto resonanceSpan = bufferPool.getBuffer(numFrames); + auto gainSpan = bufferPool.getBuffer(numFrames); if (!cutoffSpan || !resonanceSpan || !gainSpan) return; diff --git a/src/sfizz/FilterPool.h b/src/sfizz/FilterPool.h index ab7611240..1ab12b003 100644 --- a/src/sfizz/FilterPool.h +++ b/src/sfizz/FilterPool.h @@ -1,13 +1,14 @@ #pragma once #include "SfzFilter.h" -#include "Region.h" -#include "Resources.h" #include "Defaults.h" +#include "modulations/ModMatrix.h" #include #include -namespace sfz -{ +namespace sfz { +struct Region; +class Resources; +struct FilterDescription; class FilterHolder { @@ -54,4 +55,4 @@ class FilterHolder bool prepared { false }; }; -} +} // namespace sfz diff --git a/src/sfizz/FlexEGDescription.cpp b/src/sfizz/FlexEGDescription.cpp index 18ea92e36..98d99d219 100644 --- a/src/sfizz/FlexEGDescription.cpp +++ b/src/sfizz/FlexEGDescription.cpp @@ -6,6 +6,7 @@ #include "FlexEGDescription.h" #include "Curve.h" +#include "MidiState.h" #include #include @@ -84,4 +85,21 @@ void FlexEGs::clearUnusedCurves() } } +/// +float FlexEGPoint::getTime(const MidiState& state, int delay) const noexcept +{ + float returnedValue { time }; + for (const CCData& mod : ccTime) + returnedValue += state.getCCValueAt(mod.cc, delay) * mod.data; + return returnedValue; +} + +float FlexEGPoint::getLevel(const MidiState& state, int delay) const noexcept +{ + float returnedValue { level }; + for (const CCData& mod : ccLevel) + returnedValue += state.getCCValueAt(mod.cc, delay) * mod.data; + return returnedValue; +} + } // namespace sfz diff --git a/src/sfizz/FlexEGDescription.h b/src/sfizz/FlexEGDescription.h index a4cc75ef4..8965746f4 100644 --- a/src/sfizz/FlexEGDescription.h +++ b/src/sfizz/FlexEGDescription.h @@ -6,11 +6,13 @@ #pragma once #include "Defaults.h" +#include "CCMap.h" #include #include namespace sfz { class Curve; +class MidiState; namespace FlexEGs { std::shared_ptr getShapeCurve(float shape); @@ -21,6 +23,12 @@ struct FlexEGPoint { float time { Default::flexEGPointTime }; // duration until next step (s) float level { Default::flexEGPointLevel }; // normalized amplitude + CCMap ccTime; + CCMap ccLevel; + + float getTime(const MidiState& state, int delay = 0) const noexcept; + float getLevel(const MidiState& state, int delay = 0) const noexcept; + void setShape(float shape); float shape() const noexcept { return shape_; } const Curve& curve() const; diff --git a/src/sfizz/FlexEnvelope.cpp b/src/sfizz/FlexEnvelope.cpp index 4158978f4..2a6c3b253 100644 --- a/src/sfizz/FlexEnvelope.cpp +++ b/src/sfizz/FlexEnvelope.cpp @@ -9,8 +9,11 @@ - [ ] egN_points (purpose unknown) - [x] egN_timeX +- [x] egN_timeX_onccY - [x] egN_levelX +- [x] egN_levelX_onccY - [x] egN_shapeX +- [ ] egN_shapeX_onccY - [x] egN_sustain - [ ] egN_dynamic - [ ] egN_loop @@ -21,6 +24,8 @@ #include "FlexEnvelope.h" #include "FlexEGDescription.h" #include "Curve.h" +#include "MidiState.h" +#include "Resources.h" #include "Config.h" #include "SIMDHelpers.h" #include @@ -28,6 +33,7 @@ namespace sfz { struct FlexEnvelope::Impl { + const Resources* resources_ { nullptr }; const FlexEGDescription* desc_ { nullptr }; float samplePeriod_ { 1.0 / config::defaultSampleRate }; size_t delayFramesLeft_ { 0 }; @@ -49,12 +55,16 @@ struct FlexEnvelope::Impl { // void process(absl::Span out); + bool advanceToStage(unsigned stageNumber); bool advanceToNextStage(); + void updateCurrentTimeAndLevel(int delay = 0); }; -FlexEnvelope::FlexEnvelope() +FlexEnvelope::FlexEnvelope(Resources &resources) : impl_(new Impl) { + Impl& impl = *impl_; + impl.resources_ = &resources; } FlexEnvelope::~FlexEnvelope() @@ -85,21 +95,9 @@ void FlexEnvelope::configure(const FlexEGDescription* desc) void FlexEnvelope::start(unsigned triggerDelay) { Impl& impl = *impl_; - const FlexEGDescription& desc = *impl.desc_; - impl.delayFramesLeft_ = triggerDelay; - - FlexEGPoint point; - if (!desc.points.empty()) - point = desc.points[0]; - - // - impl.stageSourceLevel_ = 0.0; - impl.stageTargetLevel_ = point.level; - impl.stageTime_ = point.time; - impl.stageSustained_ = desc.sustain == 0; - impl.stageCurve_ = &point.curve(); impl.currentFramesUntilRelease_ = absl::nullopt; + impl.advanceToStage(0); } void FlexEnvelope::setFreeRunning(bool freeRunning) @@ -114,6 +112,26 @@ void FlexEnvelope::release(unsigned releaseDelay) impl.currentFramesUntilRelease_ = releaseDelay; } +void FlexEnvelope::cancelRelease(unsigned delay) +{ + UNUSED(delay); + Impl& impl = *impl_; + const FlexEGDescription& desc = *impl.desc_; + + if (impl.currentFramesUntilRelease_) { + // Cancel the future release + impl.currentFramesUntilRelease_ = absl::nullopt; + return; + } + + if (!impl.isReleased_) + return; + + impl.isReleased_ = false; + impl.advanceToStage(desc.sustain); + impl.stageTargetLevel_ = impl.currentLevel_; +} + unsigned FlexEnvelope::getRemainingDelay() const noexcept { const Impl& impl = *impl_; @@ -136,7 +154,19 @@ bool FlexEnvelope::isFinished() const noexcept void FlexEnvelope::process(absl::Span out) { Impl& impl = *impl_; - impl.process(out); + if (impl.desc_->dynamic) { + int processed = 0; + int remaining = static_cast(out.size()); + while(remaining > 0) { + impl.updateCurrentTimeAndLevel(processed); + int chunkSize = min(config::processChunkSize, remaining); + impl.process(out.subspan(processed, chunkSize)); + processed += chunkSize; + remaining -= chunkSize; + } + } else { + impl.process(out); + } } void FlexEnvelope::Impl::process(absl::Span out) @@ -144,6 +174,7 @@ void FlexEnvelope::Impl::process(absl::Span out) const FlexEGDescription& desc = *desc_; size_t numFrames = out.size(); const float samplePeriod = samplePeriod_; + // Skip the initial delay, for frame-accurate trigger size_t skipFrames = std::min(numFrames, delayFramesLeft_); if (skipFrames > 0) { @@ -226,25 +257,39 @@ void FlexEnvelope::Impl::process(absl::Span out) } } -bool FlexEnvelope::Impl::advanceToNextStage() +bool FlexEnvelope::Impl::advanceToStage(unsigned stageNumber) { const FlexEGDescription& desc = *desc_; + currentStageNumber_ = stageNumber; - unsigned nextStageNo = currentStageNumber_ + 1; - currentStageNumber_ = nextStageNo; - - if (nextStageNo >= desc.points.size()) + if (stageNumber >= desc.points.size()) return false; - const FlexEGPoint& point = desc.points[nextStageNo]; + const FlexEGPoint& point = desc.points[stageNumber]; stageSourceLevel_ = currentLevel_; - stageTargetLevel_ = point.level; - stageTime_ = point.time; - stageSustained_ = int(nextStageNo) == desc.sustain; + currentTime_ = 0.0f; + updateCurrentTimeAndLevel(); + stageSustained_ = int(stageNumber) == desc.sustain; stageCurve_ = &point.curve(); - currentTime_ = 0; return true; }; +bool FlexEnvelope::Impl::advanceToNextStage() +{ + return advanceToStage(currentStageNumber_ + 1); +} + +void FlexEnvelope::Impl::updateCurrentTimeAndLevel(int delay) +{ + const FlexEGDescription& desc = *desc_; + if (currentStageNumber_ >= desc.points.size()) + return; + + const FlexEGPoint& point = desc.points[currentStageNumber_]; + const MidiState& midiState = resources_->getMidiState(); + stageTargetLevel_ = point.getLevel(midiState, delay); + stageTime_ = point.getTime(midiState, delay); +} + } // namespace sfz diff --git a/src/sfizz/FlexEnvelope.h b/src/sfizz/FlexEnvelope.h index d8cc2bf07..a72d280f1 100644 --- a/src/sfizz/FlexEnvelope.h +++ b/src/sfizz/FlexEnvelope.h @@ -10,13 +10,14 @@ namespace sfz { struct FlexEGDescription; +class Resources; /** Flex envelope generator (according to ARIA) */ class FlexEnvelope { public: - FlexEnvelope(); + explicit FlexEnvelope(Resources &resources); ~FlexEnvelope(); /** @@ -47,6 +48,11 @@ class FlexEnvelope { */ void release(unsigned releaseDelay); + /** + Cancel the release + */ + void cancelRelease(unsigned delay); + /** Get the remaining delay samples */ diff --git a/src/sfizz/LFO.cpp b/src/sfizz/LFO.cpp index 5ec9aab70..0564f2b77 100644 --- a/src/sfizz/LFO.cpp +++ b/src/sfizz/LFO.cpp @@ -10,6 +10,10 @@ #include "SIMDHelpers.h" #include "Config.h" #include "Resources.h" +#include "BufferPool.h" +#include "BeatClock.h" +#include "MidiState.h" +#include "modulations/ModMatrix.h" #include "modulations/ModKey.h" #include "modulations/ModId.h" #include @@ -61,7 +65,7 @@ void LFO::setSampleRate(double sampleRate) void LFO::configure(const LFODescription* desc) { Impl& impl = *impl_; - ModMatrix& modMatrix = impl.resources_.modMatrix; + ModMatrix& modMatrix = impl.resources_.getModMatrix(); impl.desc_ = desc ? desc : &LFODescription::getDefault(); impl.beatsKeyId = modMatrix.findTarget(desc->beatsKey); impl.freqKeyId = modMatrix.findTarget(desc->freqKey); @@ -73,7 +77,7 @@ void LFO::start(unsigned triggerDelay) Impl& impl = *impl_; const LFODescription& desc = *impl.desc_; const float sampleRate = impl.sampleRate_; - const MidiState& state = impl.resources_.midiState; + const MidiState& state = impl.resources_.getMidiState(); impl.subPhases_.fill(0.0f); impl.sampleHoldMem_.fill(0.0f); @@ -230,7 +234,7 @@ void LFO::process(absl::Span out) { Impl& impl = *impl_; const LFODescription& desc = *impl.desc_; - BufferPool& pool = impl.resources_.bufferPool; + BufferPool& pool = impl.resources_.getBufferPool(); size_t numFrames = out.size(); fill(out, 0.0f); @@ -323,9 +327,9 @@ void LFO::processFadeIn(absl::Span out) void LFO::generatePhase(unsigned nth, absl::Span phases) { Impl& impl = *impl_; - BufferPool& bufferPool = impl.resources_.bufferPool; - BeatClock& beatClock = impl.resources_.beatClock; - ModMatrix& modMatrix = impl.resources_.modMatrix; + BufferPool& bufferPool = impl.resources_.getBufferPool(); + BeatClock& beatClock = impl.resources_.getBeatClock(); + ModMatrix& modMatrix = impl.resources_.getModMatrix(); const LFODescription& desc = *impl.desc_; const LFODescription::Sub& sub = desc.sub[nth]; const float samplePeriod = 1.0f / impl.sampleRate_; diff --git a/src/sfizz/LFO.h b/src/sfizz/LFO.h index 9ffa25d54..226fd6791 100644 --- a/src/sfizz/LFO.h +++ b/src/sfizz/LFO.h @@ -10,7 +10,7 @@ #include namespace sfz { -struct Resources; +class Resources; struct Region; enum class LFOWave : int; diff --git a/src/sfizz/Layer.cpp b/src/sfizz/Layer.cpp index 16a1b745c..b06c2f713 100644 --- a/src/sfizz/Layer.cpp +++ b/src/sfizz/Layer.cpp @@ -69,7 +69,7 @@ bool Layer::registerNoteOn(int noteNumber, float velocity, float randValue) noex return false; if (region.velocityOverride == VelocityOverride::previous) - velocity = midiState_.getLastVelocity(); + velocity = midiState_.getVelocityOverride(); const bool velOk = region.velocityRange.containsWithEnd(velocity); const bool randOk = region.randRange.contains(randValue) || (randValue >= 1.0f && region.randRange.isValid() && region.randRange.getEnd() >= 1.0f); @@ -129,10 +129,8 @@ bool Layer::registerNoteOff(int noteNumber, float velocity, float randValue) noe return false; } -bool Layer::registerCC(int ccNumber, float ccValue) noexcept +void Layer::updateCCState(int ccNumber, float ccValue) noexcept { - ASSERT(ccValue >= 0.0f && ccValue <= 1.0f); - const Region& region = region_; if (ccNumber == region.sustainCC) @@ -153,15 +151,30 @@ bool Layer::registerCC(int ccNumber, float ccValue) noexcept ccSwitched_.set(ccNumber, true); else ccSwitched_.set(ccNumber, false); +} - if (!isSwitchedOn()) - return false; +bool Layer::registerCC(int ccNumber, float ccValue, float randValue) noexcept +{ + const Region& region = region_; + + updateCCState(ccNumber, ccValue); if (!region.triggerOnCC) return false; + const bool randOk = region.randRange.contains(randValue) + || (randValue >= 1.0f && region.randRange.isValid() && region.randRange.getEnd() >= 1.0f); + + if (!randOk) + return false; + if (auto triggerRange = region.ccTriggers.get(ccNumber)) { - if (triggerRange->containsWithEnd(ccValue)) + if (!triggerRange->containsWithEnd(ccValue)) + return false; + + sequenceSwitched_ = + ((sequenceCounter_++ % region.sequenceLength) == region.sequencePosition - 1); + if (isSwitchedOn()) return true; } diff --git a/src/sfizz/Layer.h b/src/sfizz/Layer.h index ede1490a1..d049e2c01 100644 --- a/src/sfizz/Layer.h +++ b/src/sfizz/Layer.h @@ -80,15 +80,26 @@ struct Layer { */ bool registerNoteOff(int noteNumber, float velocity, float randValue) noexcept; /** - * @brief Register a new CC event. The region may be switched on or off using CCs so - * this function checks if it indeeds need to activate or not. + * @brief Update the internal state of the layer with respect to CC events (sustain, CC + * switch, etc). * * @param ccNumber * @param ccValue - * @return true if the region should trigger on this event * @return false */ - bool registerCC(int ccNumber, float ccValue) noexcept; + void updateCCState(int ccNumber, float ccValue) noexcept; + /** + * @brief Register a new CC event,. This method updates the internal CC state with respect + * to CC events (sustain, CC switch, etc) and checks if the region should trigger on this + * event. + * + * @param ccNumber + * @param ccValue + * @param randValue + * @return true if the region should trigger on this event + * @return false otherwise + */ + bool registerCC(int ccNumber, float ccValue, float randValue) noexcept; /** * @brief Register a new pitch wheel event. * diff --git a/src/sfizz/MathHelpers.h b/src/sfizz/MathHelpers.h index 0d2fa1b3c..556526da0 100644 --- a/src/sfizz/MathHelpers.h +++ b/src/sfizz/MathHelpers.h @@ -26,7 +26,7 @@ #include #endif -#if __cplusplus >= 201703L +#if __cplusplus >= 201703L && defined(__cpp_lib_math_special_functions) static double i0(double x) { return std::cyl_bessel_i(0.0, x); } #else // external Bessel function from cephes diff --git a/src/sfizz/MidiState.cpp b/src/sfizz/MidiState.cpp index e077dfad1..4d0d45ebe 100644 --- a/src/sfizz/MidiState.cpp +++ b/src/sfizz/MidiState.cpp @@ -19,11 +19,19 @@ void sfz::MidiState::noteOnEvent(int delay, int noteNumber, float velocity) noex ASSERT(velocity >= 0 && velocity <= 1.0); if (noteNumber >= 0 && noteNumber < 128) { + velocityOverride = lastNoteVelocities[lastNotePlayed]; lastNoteVelocities[noteNumber] = velocity; noteOnTimes[noteNumber] = internalClock + static_cast(delay); lastNotePlayed = noteNumber; - activeNotes++; noteStates[noteNumber] = true; + ccEvent(delay, ExtendedCCs::noteOnVelocity, velocity); + ccEvent(delay, ExtendedCCs::keyboardNoteNumber, normalize7Bits(noteNumber)); + ccEvent(delay, ExtendedCCs::unipolarRandom, unipolarDist(Random::randomGenerator)); + ccEvent(delay, ExtendedCCs::bipolarRandom, bipolarDist(Random::randomGenerator)); + ccEvent(delay, ExtendedCCs::keyboardNoteGate, activeNotes > 0 ? 1.0f : 0.0f); + activeNotes++; + + ccEvent(delay, ExtendedCCs::alternate, alternate); alternate = alternate == 0.0f ? 1.0f : 0.0f; } @@ -37,6 +45,10 @@ void sfz::MidiState::noteOffEvent(int delay, int noteNumber, float velocity) noe UNUSED(velocity); if (noteNumber >= 0 && noteNumber < 128) { noteOffTimes[noteNumber] = internalClock + static_cast(delay); + ccEvent(delay, ExtendedCCs::noteOffVelocity, velocity); + ccEvent(delay, ExtendedCCs::keyboardNoteNumber, normalize7Bits(noteNumber)); + ccEvent(delay, ExtendedCCs::unipolarRandom, unipolarDist(Random::randomGenerator)); + ccEvent(delay, ExtendedCCs::bipolarRandom, bipolarDist(Random::randomGenerator)); if (activeNotes > 0) activeNotes--; noteStates[noteNumber] = false; @@ -107,8 +119,10 @@ float sfz::MidiState::getNoteDuration(int noteNumber, int delay) const if (noteNumber < 0 || noteNumber >= 128) return 0.0f; +#if 0 if (!noteStates[noteNumber]) return 0.0f; +#endif const unsigned timeInSamples = internalClock + static_cast(delay) - noteOnTimes[noteNumber]; return static_cast(timeInSamples) / sampleRate; @@ -121,9 +135,9 @@ float sfz::MidiState::getNoteVelocity(int noteNumber) const noexcept return lastNoteVelocities[noteNumber]; } -float sfz::MidiState::getLastVelocity() const noexcept +float sfz::MidiState::getVelocityOverride() const noexcept { - return lastNoteVelocities[lastNotePlayed]; + return velocityOverride; } void sfz::MidiState::insertEventInVector(EventVector& events, int delay, float value) @@ -172,7 +186,7 @@ float sfz::MidiState::getPolyAftertouch(int noteNumber) const noexcept { if (noteNumber < 0 || noteNumber > 127) return 0.0f; - + ASSERT(polyAftertouchEvents[noteNumber].size() > 0); return polyAftertouchEvents[noteNumber].back().value; } @@ -188,10 +202,21 @@ float sfz::MidiState::getCCValue(int ccNumber) const noexcept return ccEvents[ccNumber].back().value; } +float sfz::MidiState::getCCValueAt(int ccNumber, int delay) const noexcept +{ + ASSERT(ccNumber >= 0 && ccNumber < config::numCCs); + const auto ccEvent = absl::c_lower_bound( + ccEvents[ccNumber], delay, MidiEventDelayComparator {}); + if (ccEvent != ccEvents[ccNumber].end()) + return ccEvent->value; + else + return ccEvents[ccNumber].back().value; +} + void sfz::MidiState::reset() noexcept { for (auto& velocity: lastNoteVelocities) - velocity = 0; + velocity = 0.0f; auto clearEvents = [] (EventVector& events) { events.clear(); @@ -207,6 +232,7 @@ void sfz::MidiState::reset() noexcept clearEvents(pitchEvents); clearEvents(channelAftertouchEvents); + velocityOverride = 0.0f; activeNotes = 0; internalClock = 0; lastNotePlayed = 0; @@ -215,14 +241,6 @@ void sfz::MidiState::reset() noexcept absl::c_fill(noteOffTimes, 0); } -void sfz::MidiState::resetAllControllers(int delay) noexcept -{ - for (int ccIdx = 0; ccIdx < config::numCCs; ++ccIdx) - ccEvent(delay, ccIdx, 0.0f); - - pitchBendEvent(delay, 0.0f); -} - const sfz::EventVector& sfz::MidiState::getCCEvents(int ccIdx) const noexcept { if (ccIdx < 0 || ccIdx >= config::numCCs) diff --git a/src/sfizz/MidiState.h b/src/sfizz/MidiState.h index 3dab87bfb..eaf947c60 100644 --- a/src/sfizz/MidiState.h +++ b/src/sfizz/MidiState.h @@ -86,11 +86,11 @@ class MidiState float getNoteVelocity(int noteNumber) const noexcept; /** - * @brief Get the velocity of the last note played + * @brief Get the velocity override value (sw_vel in SFZ) * * @return float */ - float getLastVelocity() const noexcept; + float getVelocityOverride() const noexcept; /** * @brief Register a pitch bend event @@ -167,7 +167,7 @@ class MidiState bool isNotePressed(int noteNumber) const noexcept { return noteStates[noteNumber]; } /** - * @brief Get the CC value for CC number + * @brief Get the last CC value for CC number * * @param ccNumber * @return float @@ -175,28 +175,25 @@ class MidiState float getCCValue(int ccNumber) const noexcept; /** - * @brief Reset the midi state (does not impact the last note on time) + * @brief Get the CC value for CC number * + * @param ccNumber + * @param delay + * @return float */ - void reset() noexcept; + float getCCValueAt(int ccNumber, int delay) const noexcept; /** - * @brief Reset all the controllers + * @brief Reset the midi state (does not impact the last note on time) + * */ - void resetAllControllers(int delay) noexcept; + void reset() noexcept; const EventVector& getCCEvents(int ccIdx) const noexcept; const EventVector& getPolyAftertouchEvents(int noteNumber) const noexcept; const EventVector& getPitchEvents() const noexcept; const EventVector& getChannelAftertouchEvents() const noexcept; - /** - * @brief Get the alternate state value, for extended CC 137 - * - * @return float - */ - float getAlternateState() const noexcept { return alternate; } - private: /** @@ -236,6 +233,11 @@ class MidiState */ MidiNoteArray lastNoteVelocities; + /** + * @brief Velocity override value (sw_vel in SFZ) + */ + float velocityOverride; + /** * @brief Last note played */ @@ -272,5 +274,7 @@ class MidiState int samplesPerBlock { config::defaultSamplesPerBlock }; float alternate { 0.0f }; unsigned internalClock { 0 }; + fast_real_distribution unipolarDist { 0.0f, 1.0f }; + fast_real_distribution bipolarDist { -1.0f, 1.0f }; }; } diff --git a/src/sfizz/ModifierHelpers.h b/src/sfizz/ModifierHelpers.h index e233009db..810a16dde 100644 --- a/src/sfizz/ModifierHelpers.h +++ b/src/sfizz/ModifierHelpers.h @@ -18,15 +18,17 @@ namespace sfz { template float crossfadeIn(const sfz::Range& crossfadeRange, U value, CrossfadeCurve curve) { + constexpr float gapOffset { static_cast(normalize7Bits(1)) }; if (value < crossfadeRange.getStart()) return 0.0f; - const auto length = static_cast(crossfadeRange.length()); + const auto length = static_cast(crossfadeRange.length()) - gapOffset; if (length <= 0.0f) return 1.0f; else if (value < crossfadeRange.getEnd()) { - const auto crossfadePosition = static_cast(value - crossfadeRange.getStart()) / length; + const auto distanceFromStart = static_cast(value - crossfadeRange.getStart()); + const auto crossfadePosition = distanceFromStart / length; if (curve == CrossfadeCurve::power) return sqrt(crossfadePosition); if (curve == CrossfadeCurve::gain) @@ -42,15 +44,16 @@ float crossfadeIn(const sfz::Range& crossfadeRange, U value, CrossfadeCurv template float crossfadeOut(const sfz::Range& crossfadeRange, U value, CrossfadeCurve curve) { - if (value > crossfadeRange.getEnd()) - return 0.0f; - - const auto length = static_cast(crossfadeRange.length()); + constexpr float gapOffset { static_cast(normalize7Bits(1)) }; + const auto length = static_cast(crossfadeRange.length()) - gapOffset; if (length <= 0.0f) return 1.0f; else if (value > crossfadeRange.getStart()) { - const auto crossfadePosition = static_cast(value - crossfadeRange.getStart()) / length; + const auto distanceFromStart = static_cast(value - crossfadeRange.getStart()); + const auto crossfadePosition = distanceFromStart / length; + if (crossfadePosition > 1.0f) + return 0.0f; if (curve == CrossfadeCurve::power) return std::sqrt(1 - crossfadePosition); if (curve == CrossfadeCurve::gain) diff --git a/src/sfizz/Opcode.cpp b/src/sfizz/Opcode.cpp index e860c5368..f27a6685b 100644 --- a/src/sfizz/Opcode.cpp +++ b/src/sfizz/Opcode.cpp @@ -327,6 +327,9 @@ absl::optional readBoolean(absl::string_view value) template <> absl::optional Opcode::readOptional(OpcodeSpec, absl::string_view value) { + if (value == "auto") + return OscillatorEnabled::Auto; + auto v = readBoolean(value); if (!v) return absl::nullopt; diff --git a/src/sfizz/OpcodeCleanup.cpp b/src/sfizz/OpcodeCleanup.cpp index 1d7531613..6282c2d29 100644 --- a/src/sfizz/OpcodeCleanup.cpp +++ b/src/sfizz/OpcodeCleanup.cpp @@ -1,4 +1,4 @@ -/* Generated by re2c 2.0.3 on Sat Dec 12 13:32:03 2020 */ +/* Generated by re2c 1.3 on Tue Jul 20 11:23:08 2021 */ #line 1 "src/sfizz/OpcodeCleanup.re" /* -*- mode: c++; -*- */ // SPDX-License-Identifier: BSD-2-Clause @@ -219,7 +219,7 @@ static std::string cleanUpOpcodeName(absl::string_view rawOpcode, OpcodeScope sc yy19: ++YYCURSOR; yy20: -#line 195 "src/sfizz/OpcodeCleanup.re" +#line 210 "src/sfizz/OpcodeCleanup.re" { goto end_region; } @@ -685,39 +685,40 @@ static std::string cleanUpOpcodeName(absl::string_view rawOpcode, OpcodeScope sc yy82: yych = *++YYCURSOR; switch (yych) { + case '_': goto yy114; case 'e': yyt1 = YYCURSOR; - goto yy114; + goto yy115; case 'm': yyt1 = YYCURSOR; - goto yy115; + goto yy116; case 's': yyt1 = YYCURSOR; - goto yy116; + goto yy117; default: goto yy34; } yy83: yych = *++YYCURSOR; switch (yych) { - case 'y': goto yy117; + case 'y': goto yy118; default: goto yy34; } yy84: yych = *++YYCURSOR; switch (yych) { - case 'o': goto yy118; + case 'o': goto yy119; default: goto yy34; } yy85: yych = *++YYCURSOR; switch (yych) { - case 'i': goto yy119; + case 'i': goto yy120; default: goto yy34; } yy86: yych = *++YYCURSOR; switch (yych) { - case 'o': goto yy119; + case 'o': goto yy120; default: goto yy34; } yy87: @@ -729,13 +730,13 @@ static std::string cleanUpOpcodeName(absl::string_view rawOpcode, OpcodeScope sc yy88: yych = *++YYCURSOR; switch (yych) { - case 'p': goto yy120; + case 'p': goto yy121; default: goto yy34; } yy89: yych = *++YYCURSOR; switch (yych) { - case 'n': goto yy121; + case 'n': goto yy122; default: goto yy34; } yy90: @@ -743,88 +744,88 @@ static std::string cleanUpOpcodeName(absl::string_view rawOpcode, OpcodeScope sc switch (yych) { case 0x00: yyt2 = yyt3 = NULL; - goto yy122; + goto yy123; case '_': yyt3 = YYCURSOR; - goto yy124; + goto yy125; default: goto yy34; } yy91: yych = *++YYCURSOR; switch (yych) { - case '_': goto yy126; + case '_': goto yy127; default: goto yy34; } yy92: yych = *++YYCURSOR; switch (yych) { - case 'o': goto yy127; + case 'o': goto yy128; default: goto yy34; } yy93: yych = *++YYCURSOR; switch (yych) { - case 'o': goto yy128; + case 'o': goto yy129; default: goto yy34; } yy94: yych = *++YYCURSOR; switch (yych) { - case 't': goto yy129; + case 't': goto yy130; default: goto yy34; } yy95: yych = *++YYCURSOR; switch (yych) { - case 'p': goto yy130; + case 'p': goto yy131; default: goto yy34; } yy96: yych = *++YYCURSOR; switch (yych) { - case 'f': goto yy131; + case 'f': goto yy132; default: goto yy34; } yy97: yych = *++YYCURSOR; switch (yych) { - case 'u': goto yy132; + case 'u': goto yy133; default: goto yy34; } yy98: yych = *++YYCURSOR; switch (yych) { - case 'e': goto yy133; + case 'e': goto yy134; default: goto yy34; } yy99: yych = *++YYCURSOR; switch (yych) { - case 'w': goto yy134; + case 'w': goto yy135; default: goto yy34; } yy100: yych = *++YYCURSOR; switch (yych) { - case 'r': goto yy135; + case 'r': goto yy136; default: goto yy34; } yy101: yych = *++YYCURSOR; switch (yych) { - case 'a': goto yy136; + case 'a': goto yy137; default: goto yy34; } yy102: yych = *++YYCURSOR; switch (yych) { - case 'e': goto yy137; + case 'e': goto yy138; default: goto yy34; } yy103: yych = *++YYCURSOR; switch (yych) { - case 'y': goto yy138; + case 'y': goto yy139; default: goto yy34; } yy104: @@ -839,7 +840,7 @@ static std::string cleanUpOpcodeName(absl::string_view rawOpcode, OpcodeScope sc opcode = absl::StrCat("fil1_", group(1)); goto end_region; } -#line 843 "src/sfizz/OpcodeCleanup.cpp" +#line 844 "src/sfizz/OpcodeCleanup.cpp" yy106: yych = *++YYCURSOR; yy107: @@ -848,7 +849,7 @@ static std::string cleanUpOpcodeName(absl::string_view rawOpcode, OpcodeScope sc yy108: yych = *++YYCURSOR; switch (yych) { - case 'p': goto yy139; + case 'p': goto yy140; default: goto yy34; } yy109: @@ -863,17 +864,17 @@ static std::string cleanUpOpcodeName(absl::string_view rawOpcode, OpcodeScope sc opcode = absl::StrCat("volume", group(1)); goto end_region; } -#line 867 "src/sfizz/OpcodeCleanup.cpp" +#line 868 "src/sfizz/OpcodeCleanup.cpp" yy111: yych = *++YYCURSOR; switch (yych) { - case 'r': goto yy142; - default: goto yy141; + case 'r': goto yy143; + default: goto yy142; } yy112: yych = *++YYCURSOR; switch (yych) { - case 'l': goto yy143; + case 'l': goto yy144; default: goto yy34; } yy113: @@ -881,69 +882,76 @@ static std::string cleanUpOpcodeName(absl::string_view rawOpcode, OpcodeScope sc switch (yych) { case 'c': yyt2 = YYCURSOR; - goto yy144; + goto yy145; case 'o': yyt2 = YYCURSOR; - goto yy145; + goto yy146; case 'r': yyt2 = YYCURSOR; - goto yy146; + goto yy147; case 's': yyt2 = YYCURSOR; - goto yy147; + goto yy148; case 'w': yyt2 = YYCURSOR; - goto yy148; + goto yy149; default: goto yy34; } yy114: yych = *++YYCURSOR; switch (yych) { - case 'n': goto yy149; + case 'l': goto yy150; + case 's': goto yy151; default: goto yy34; } yy115: yych = *++YYCURSOR; switch (yych) { - case 'o': goto yy150; + case 'n': goto yy152; default: goto yy34; } yy116: yych = *++YYCURSOR; switch (yych) { - case 't': goto yy151; + case 'o': goto yy153; default: goto yy34; } yy117: - yych = *++YYCURSOR; - if (yych <= 0x00) goto yy152; - goto yy34; -yy118: yych = *++YYCURSOR; switch (yych) { - case 'd': goto yy154; + case 't': goto yy154; default: goto yy34; } +yy118: + yych = *++YYCURSOR; + if (yych <= 0x00) goto yy155; + goto yy34; yy119: yych = *++YYCURSOR; switch (yych) { - case 'c': goto yy155; - case 'h': goto yy156; + case 'd': goto yy157; default: goto yy34; } yy120: yych = *++YYCURSOR; switch (yych) { - case 'h': goto yy157; + case 'c': goto yy158; + case 'h': goto yy159; default: goto yy34; } yy121: yych = *++YYCURSOR; switch (yych) { - case 'a': goto yy158; + case 'h': goto yy160; default: goto yy34; } yy122: + yych = *++YYCURSOR; + switch (yych) { + case 'a': goto yy161; + default: goto yy34; + } +yy123: ++YYCURSOR; yynmatch = 2; yypmatch[0] = yyt1; @@ -955,63 +963,63 @@ static std::string cleanUpOpcodeName(absl::string_view rawOpcode, OpcodeScope sc opcode = absl::StrCat("pitch", group(1)); goto end_region; } -#line 959 "src/sfizz/OpcodeCleanup.cpp" -yy124: +#line 967 "src/sfizz/OpcodeCleanup.cpp" +yy125: yych = *++YYCURSOR; if (yych <= 0x00) { yyt2 = YYCURSOR; - goto yy122; + goto yy123; } - goto yy124; -yy126: + goto yy125; +yy127: yych = *++YYCURSOR; switch (yych) { case 'a': yyt2 = YYCURSOR; - goto yy159; + goto yy162; case 'd': yyt2 = YYCURSOR; - goto yy160; + goto yy163; case 'h': yyt2 = YYCURSOR; - goto yy161; + goto yy164; case 'r': yyt2 = YYCURSOR; - goto yy162; + goto yy165; case 's': yyt2 = YYCURSOR; - goto yy163; - case 'v': goto yy164; + goto yy166; + case 'v': goto yy167; default: goto yy34; } -yy127: +yy128: yych = *++YYCURSOR; switch (yych) { - case '_': goto yy165; + case '_': goto yy168; default: goto yy34; } -yy128: +yy129: yych = *++YYCURSOR; switch (yych) { - case 'w': goto yy166; + case 'w': goto yy169; default: goto yy34; } -yy129: +yy130: yych = *++YYCURSOR; switch (yych) { case 'e': goto yy95; default: goto yy34; } -yy130: +yy131: yych = *++YYCURSOR; - if (yych <= 0x00) goto yy167; + if (yych <= 0x00) goto yy170; goto yy34; -yy131: +yy132: yych = *++YYCURSOR; switch (yych) { case 0x00: yyt2 = yyt3 = NULL; - goto yy169; + goto yy172; case '0': case '1': case '2': @@ -1023,131 +1031,143 @@ static std::string cleanUpOpcodeName(absl::string_view rawOpcode, OpcodeScope sc case '8': case '9': yyt2 = YYCURSOR; - goto yy171; + goto yy174; case '_': yyt2 = yyt4 = NULL; yyt3 = YYCURSOR; - goto yy173; - default: goto yy34; - } -yy132: - yych = *++YYCURSOR; - switch (yych) { - case 't': goto yy174; + goto yy176; default: goto yy34; } yy133: yych = *++YYCURSOR; switch (yych) { - case 's': goto yy175; + case 't': goto yy177; default: goto yy34; } yy134: yych = *++YYCURSOR; switch (yych) { - case 'c': goto yy176; + case 's': goto yy178; default: goto yy34; } yy135: yych = *++YYCURSOR; switch (yych) { - case 'e': goto yy177; + case 'c': goto yy179; default: goto yy34; } yy136: yych = *++YYCURSOR; switch (yych) { - case 'i': goto yy178; + case 'e': goto yy180; default: goto yy34; } yy137: yych = *++YYCURSOR; switch (yych) { - case 'l': goto yy179; + case 'i': goto yy181; default: goto yy34; } yy138: yych = *++YYCURSOR; switch (yych) { - case 'p': goto yy180; + case 'l': goto yy182; default: goto yy34; } yy139: yych = *++YYCURSOR; switch (yych) { - case 'e': goto yy181; + case 'p': goto yy183; default: goto yy34; } yy140: yych = *++YYCURSOR; + switch (yych) { + case 'e': goto yy184; + default: goto yy34; + } yy141: + yych = *++YYCURSOR; +yy142: if (yych <= 0x00) { yyt2 = YYCURSOR; goto yy109; } - goto yy140; -yy142: - yych = *++YYCURSOR; - switch (yych) { - case 'a': goto yy182; - default: goto yy141; - } + goto yy141; yy143: yych = *++YYCURSOR; switch (yych) { - case 'c': goto yy183; - default: goto yy34; + case 'a': goto yy185; + default: goto yy142; } yy144: yych = *++YYCURSOR; switch (yych) { - case 'u': goto yy184; + case 'c': goto yy186; default: goto yy34; } yy145: yych = *++YYCURSOR; switch (yych) { - case 'f': goto yy185; + case 'u': goto yy187; default: goto yy34; } yy146: yych = *++YYCURSOR; switch (yych) { - case 'a': goto yy186; - case 'e': goto yy187; + case 'f': goto yy188; default: goto yy34; } yy147: yych = *++YYCURSOR; switch (yych) { - case 'c': goto yy188; + case 'a': goto yy189; + case 'e': goto yy190; default: goto yy34; } yy148: yych = *++YYCURSOR; switch (yych) { - case 'a': goto yy189; + case 'c': goto yy191; default: goto yy34; } yy149: yych = *++YYCURSOR; switch (yych) { - case 'd': goto yy190; + case 'a': goto yy192; default: goto yy34; } yy150: yych = *++YYCURSOR; switch (yych) { - case 'd': goto yy191; + case 'e': goto yy193; default: goto yy34; } yy151: yych = *++YYCURSOR; switch (yych) { - case 'a': goto yy192; + case 't': goto yy194; default: goto yy34; } yy152: + yych = *++YYCURSOR; + switch (yych) { + case 'd': goto yy195; + default: goto yy34; + } +yy153: + yych = *++YYCURSOR; + switch (yych) { + case 'd': goto yy196; + default: goto yy34; + } +yy154: + yych = *++YYCURSOR; + switch (yych) { + case 'a': goto yy197; + default: goto yy34; + } +yy155: ++YYCURSOR; yynmatch = 2; yypmatch[2] = yyt1; @@ -1159,92 +1179,92 @@ static std::string cleanUpOpcodeName(absl::string_view rawOpcode, OpcodeScope sc opcode = absl::StrCat("off_", group(1)); goto end_region; } -#line 1163 "src/sfizz/OpcodeCleanup.cpp" -yy154: +#line 1183 "src/sfizz/OpcodeCleanup.cpp" +yy157: yych = *++YYCURSOR; switch (yych) { - case 'e': goto yy117; + case 'e': goto yy118; default: goto yy34; } -yy155: +yy158: yych = *++YYCURSOR; switch (yych) { - case 'c': goto yy193; + case 'c': goto yy198; default: goto yy34; } -yy156: +yy159: yych = *++YYCURSOR; switch (yych) { - case 'd': goto yy194; + case 'd': goto yy199; default: goto yy34; } -yy157: +yy160: yych = *++YYCURSOR; switch (yych) { - case 'o': goto yy195; + case 'o': goto yy200; default: goto yy34; } -yy158: +yy161: yych = *++YYCURSOR; switch (yych) { - case 'n': goto yy196; + case 'n': goto yy201; default: goto yy34; } -yy159: +yy162: yych = *++YYCURSOR; switch (yych) { - case 't': goto yy197; + case 't': goto yy202; default: goto yy34; } -yy160: +yy163: yych = *++YYCURSOR; switch (yych) { - case 'e': goto yy198; + case 'e': goto yy203; default: goto yy34; } -yy161: +yy164: yych = *++YYCURSOR; switch (yych) { - case 'o': goto yy199; + case 'o': goto yy204; default: goto yy34; } -yy162: +yy165: yych = *++YYCURSOR; switch (yych) { - case 'e': goto yy200; + case 'e': goto yy205; default: goto yy34; } -yy163: +yy166: yych = *++YYCURSOR; switch (yych) { - case 't': goto yy201; - case 'u': goto yy202; + case 't': goto yy206; + case 'u': goto yy207; default: goto yy34; } -yy164: +yy167: yych = *++YYCURSOR; switch (yych) { - case 'e': goto yy203; + case 'e': goto yy208; default: goto yy34; } -yy165: +yy168: yych = *++YYCURSOR; switch (yych) { case 'd': yyt2 = YYCURSOR; - goto yy204; + goto yy209; case 'f': yyt2 = YYCURSOR; - goto yy205; + goto yy210; default: goto yy34; } -yy166: +yy169: yych = *++YYCURSOR; switch (yych) { - case 'n': goto yy130; + case 'n': goto yy131; default: goto yy34; } -yy167: +yy170: ++YYCURSOR; yynmatch = 2; yypmatch[2] = yyt1; @@ -1256,8 +1276,8 @@ static std::string cleanUpOpcodeName(absl::string_view rawOpcode, OpcodeScope sc opcode = absl::StrCat("bend_", group(1)); goto end_region; } -#line 1260 "src/sfizz/OpcodeCleanup.cpp" -yy169: +#line 1280 "src/sfizz/OpcodeCleanup.cpp" +yy172: ++YYCURSOR; yynmatch = 2; yypmatch[0] = yyt1; @@ -1269,8 +1289,8 @@ static std::string cleanUpOpcodeName(absl::string_view rawOpcode, OpcodeScope sc opcode = absl::StrCat("cutoff1", group(1)); goto end_region; } -#line 1273 "src/sfizz/OpcodeCleanup.cpp" -yy171: +#line 1293 "src/sfizz/OpcodeCleanup.cpp" +yy174: yych = *++YYCURSOR; switch (yych) { case '0': @@ -1282,253 +1302,265 @@ static std::string cleanUpOpcodeName(absl::string_view rawOpcode, OpcodeScope sc case '6': case '7': case '8': - case '9': goto yy171; + case '9': goto yy174; case '_': yyt4 = YYCURSOR; - goto yy206; - default: goto yy34; - } -yy173: - yych = *++YYCURSOR; - switch (yych) { - case 'r': goto yy209; - default: goto yy208; - } -yy174: - yych = *++YYCURSOR; - switch (yych) { - case 'o': goto yy210; - default: goto yy34; - } -yy175: - yych = *++YYCURSOR; - switch (yych) { - case 'o': goto yy211; + goto yy211; default: goto yy34; } yy176: yych = *++YYCURSOR; switch (yych) { - case 'c': goto yy212; - default: goto yy34; + case 'r': goto yy214; + default: goto yy213; } yy177: yych = *++YYCURSOR; switch (yych) { - case 'q': goto yy134; + case 'o': goto yy215; default: goto yy34; } yy178: yych = *++YYCURSOR; switch (yych) { - case 'n': goto yy134; + case 'o': goto yy216; default: goto yy34; } yy179: yych = *++YYCURSOR; switch (yych) { - case '2': goto yy213; + case 'c': goto yy217; default: goto yy34; } yy180: yych = *++YYCURSOR; switch (yych) { - case 'e': goto yy214; + case 'q': goto yy135; default: goto yy34; } yy181: yych = *++YYCURSOR; - if (yych <= 0x00) goto yy215; - goto yy34; + switch (yych) { + case 'n': goto yy135; + default: goto yy34; + } yy182: yych = *++YYCURSOR; switch (yych) { - case 'n': goto yy217; - default: goto yy141; + case '2': goto yy218; + default: goto yy34; } yy183: yych = *++YYCURSOR; switch (yych) { - case 'c': goto yy218; + case 'e': goto yy219; default: goto yy34; } yy184: yych = *++YYCURSOR; - switch (yych) { - case 't': goto yy219; - default: goto yy34; - } + if (yych <= 0x00) goto yy220; + goto yy34; yy185: yych = *++YYCURSOR; switch (yych) { - case 'f': goto yy220; - default: goto yy34; + case 'n': goto yy222; + default: goto yy142; } yy186: yych = *++YYCURSOR; switch (yych) { - case 't': goto yy221; + case 'c': goto yy223; default: goto yy34; } yy187: yych = *++YYCURSOR; switch (yych) { - case 's': goto yy222; + case 't': goto yy224; default: goto yy34; } yy188: yych = *++YYCURSOR; switch (yych) { - case 'a': goto yy223; + case 'f': goto yy225; default: goto yy34; } yy189: yych = *++YYCURSOR; switch (yych) { - case 'v': goto yy224; + case 't': goto yy226; default: goto yy34; } yy190: yych = *++YYCURSOR; - if (yych <= 0x00) goto yy225; - goto yy34; + switch (yych) { + case 's': goto yy227; + default: goto yy34; + } yy191: yych = *++YYCURSOR; switch (yych) { - case 'e': goto yy190; + case 'a': goto yy228; default: goto yy34; } yy192: yych = *++YYCURSOR; switch (yych) { - case 'r': goto yy227; + case 'v': goto yy229; default: goto yy34; } yy193: yych = *++YYCURSOR; switch (yych) { - case '0': - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - case '8': - case '9': - yyt1 = YYCURSOR; - goto yy228; + case 'n': goto yy230; default: goto yy34; } yy194: yych = *++YYCURSOR; switch (yych) { - case 'c': goto yy230; + case 'a': goto yy231; default: goto yy34; } yy195: yych = *++YYCURSOR; - switch (yych) { - case 'n': goto yy231; - default: goto yy34; - } + if (yych <= 0x00) goto yy232; + goto yy34; yy196: yych = *++YYCURSOR; switch (yych) { - case 'c': goto yy232; + case 'e': goto yy195; default: goto yy34; } yy197: yych = *++YYCURSOR; switch (yych) { - case 't': goto yy233; + case 'r': goto yy234; default: goto yy34; } yy198: yych = *++YYCURSOR; switch (yych) { - case 'c': - case 'l': goto yy234; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + yyt1 = YYCURSOR; + goto yy235; default: goto yy34; } yy199: yych = *++YYCURSOR; switch (yych) { - case 'l': goto yy235; + case 'c': goto yy237; default: goto yy34; } yy200: yych = *++YYCURSOR; switch (yych) { - case 'l': goto yy236; + case 'n': goto yy238; default: goto yy34; } yy201: yych = *++YYCURSOR; switch (yych) { - case 'a': goto yy237; + case 'c': goto yy239; default: goto yy34; } yy202: yych = *++YYCURSOR; switch (yych) { - case 's': goto yy238; + case 't': goto yy240; default: goto yy34; } yy203: yych = *++YYCURSOR; switch (yych) { - case 'l': goto yy239; + case 'c': + case 'l': goto yy241; default: goto yy34; } yy204: yych = *++YYCURSOR; switch (yych) { - case 'e': goto yy240; + case 'l': goto yy242; default: goto yy34; } yy205: yych = *++YYCURSOR; switch (yych) { - case 'a': goto yy241; - case 'r': goto yy242; + case 'l': goto yy243; default: goto yy34; } yy206: yych = *++YYCURSOR; switch (yych) { - case 'r': goto yy243; + case 'a': goto yy244; default: goto yy34; } yy207: yych = *++YYCURSOR; + switch (yych) { + case 's': goto yy245; + default: goto yy34; + } yy208: - if (yych <= 0x00) { - yyt2 = YYCURSOR; - goto yy169; + yych = *++YYCURSOR; + switch (yych) { + case 'l': goto yy246; + default: goto yy34; } - goto yy207; yy209: yych = *++YYCURSOR; switch (yych) { - case 'a': goto yy244; - default: goto yy208; + case 'e': goto yy247; + default: goto yy34; } yy210: yych = *++YYCURSOR; switch (yych) { - case 'f': goto yy245; + case 'a': goto yy248; + case 'r': goto yy249; default: goto yy34; } yy211: yych = *++YYCURSOR; switch (yych) { - case 'n': goto yy246; + case 'r': goto yy250; default: goto yy34; } yy212: yych = *++YYCURSOR; +yy213: + if (yych <= 0x00) { + yyt2 = YYCURSOR; + goto yy172; + } + goto yy212; +yy214: + yych = *++YYCURSOR; + switch (yych) { + case 'a': goto yy251; + default: goto yy213; + } +yy215: + yych = *++YYCURSOR; + switch (yych) { + case 'f': goto yy252; + default: goto yy34; + } +yy216: + yych = *++YYCURSOR; + switch (yych) { + case 'n': goto yy253; + default: goto yy34; + } +yy217: + yych = *++YYCURSOR; switch (yych) { case '0': case '1': @@ -1541,18 +1573,18 @@ static std::string cleanUpOpcodeName(absl::string_view rawOpcode, OpcodeScope sc case '8': case '9': yyt3 = YYCURSOR; - goto yy247; + goto yy254; default: goto yy34; } -yy213: +yy218: yych = *++YYCURSOR; yyt2 = YYCURSOR; - goto yy252; -yy214: + goto yy259; +yy219: yych = *++YYCURSOR; - if (yych <= 0x00) goto yy253; + if (yych <= 0x00) goto yy260; goto yy34; -yy215: +yy220: ++YYCURSOR; yynmatch = 1; yypmatch[0] = YYCURSOR - 8; @@ -1562,14 +1594,14 @@ static std::string cleanUpOpcodeName(absl::string_view rawOpcode, OpcodeScope sc opcode = absl::StrCat("fil1_type"); goto end_region; } -#line 1566 "src/sfizz/OpcodeCleanup.cpp" -yy217: +#line 1598 "src/sfizz/OpcodeCleanup.cpp" +yy222: yych = *++YYCURSOR; switch (yych) { - case 'd': goto yy255; - default: goto yy141; + case 'd': goto yy262; + default: goto yy142; } -yy218: +yy223: yych = *++YYCURSOR; switch (yych) { case '0': @@ -1583,46 +1615,58 @@ static std::string cleanUpOpcodeName(absl::string_view rawOpcode, OpcodeScope sc case '8': case '9': yyt1 = YYCURSOR; - goto yy256; + goto yy263; default: goto yy34; } -yy219: +yy224: yych = *++YYCURSOR; switch (yych) { - case 'o': goto yy258; + case 'o': goto yy265; default: goto yy34; } -yy220: +yy225: yych = *++YYCURSOR; switch (yych) { - case 's': goto yy259; + case 's': goto yy266; default: goto yy34; } -yy221: +yy226: yych = *++YYCURSOR; switch (yych) { - case 'i': goto yy260; + case 'i': goto yy267; default: goto yy34; } -yy222: +yy227: yych = *++YYCURSOR; switch (yych) { - case 'o': goto yy261; + case 'o': goto yy268; default: goto yy34; } -yy223: +yy228: yych = *++YYCURSOR; switch (yych) { - case 'l': goto yy224; + case 'l': goto yy229; default: goto yy34; } -yy224: +yy229: yych = *++YYCURSOR; switch (yych) { - case 'e': goto yy262; + case 'e': goto yy269; default: goto yy34; } -yy225: +yy230: + yych = *++YYCURSOR; + switch (yych) { + case 'g': goto yy270; + default: goto yy34; + } +yy231: + yych = *++YYCURSOR; + switch (yych) { + case 'r': goto yy271; + default: goto yy34; + } +yy232: ++YYCURSOR; yynmatch = 2; yypmatch[2] = yyt1; @@ -1634,17 +1678,17 @@ static std::string cleanUpOpcodeName(absl::string_view rawOpcode, OpcodeScope sc opcode = absl::StrCat("loop_", group(1)); goto end_region; } -#line 1638 "src/sfizz/OpcodeCleanup.cpp" -yy227: +#line 1682 "src/sfizz/OpcodeCleanup.cpp" +yy234: yych = *++YYCURSOR; switch (yych) { - case 't': goto yy190; + case 't': goto yy195; default: goto yy34; } -yy228: +yy235: yych = *++YYCURSOR; switch (yych) { - case 0x00: goto yy263; + case 0x00: goto yy272; case '0': case '1': case '2': @@ -1654,115 +1698,115 @@ static std::string cleanUpOpcodeName(absl::string_view rawOpcode, OpcodeScope sc case '6': case '7': case '8': - case '9': goto yy228; + case '9': goto yy235; default: goto yy34; } -yy230: +yy237: yych = *++YYCURSOR; switch (yych) { - case 'c': goto yy265; + case 'c': goto yy274; default: goto yy34; } -yy231: +yy238: yych = *++YYCURSOR; switch (yych) { - case 'y': goto yy266; + case 'y': goto yy275; default: goto yy34; } -yy232: +yy239: yych = *++YYCURSOR; switch (yych) { - case 'e': goto yy267; + case 'e': goto yy276; default: goto yy34; } -yy233: +yy240: yych = *++YYCURSOR; switch (yych) { - case 'a': goto yy268; + case 'a': goto yy277; default: goto yy34; } -yy234: +yy241: yych = *++YYCURSOR; switch (yych) { - case 'a': goto yy269; + case 'a': goto yy278; default: goto yy34; } -yy235: +yy242: yych = *++YYCURSOR; switch (yych) { - case 'd': goto yy270; + case 'd': goto yy279; default: goto yy34; } -yy236: +yy243: yych = *++YYCURSOR; switch (yych) { - case 'e': goto yy271; + case 'e': goto yy280; default: goto yy34; } -yy237: +yy244: yych = *++YYCURSOR; switch (yych) { - case 'r': goto yy272; + case 'r': goto yy281; default: goto yy34; } -yy238: +yy245: yych = *++YYCURSOR; switch (yych) { - case 't': goto yy273; + case 't': goto yy282; default: goto yy34; } -yy239: +yy246: yych = *++YYCURSOR; switch (yych) { - case '2': goto yy274; + case '2': goto yy283; default: goto yy34; } -yy240: +yy247: yych = *++YYCURSOR; switch (yych) { - case 'p': goto yy275; + case 'p': goto yy284; default: goto yy34; } -yy241: +yy248: yych = *++YYCURSOR; switch (yych) { - case 'd': goto yy276; + case 'd': goto yy285; default: goto yy34; } -yy242: +yy249: yych = *++YYCURSOR; switch (yych) { - case 'e': goto yy277; + case 'e': goto yy286; default: goto yy34; } -yy243: +yy250: yych = *++YYCURSOR; switch (yych) { - case 'a': goto yy278; + case 'a': goto yy287; default: goto yy34; } -yy244: +yy251: yych = *++YYCURSOR; switch (yych) { - case 'n': goto yy279; - default: goto yy208; + case 'n': goto yy288; + default: goto yy213; } -yy245: +yy252: yych = *++YYCURSOR; switch (yych) { - case 'f': goto yy280; + case 'f': goto yy289; default: goto yy34; } -yy246: +yy253: yych = *++YYCURSOR; switch (yych) { - case 'a': goto yy281; + case 'a': goto yy290; default: goto yy34; } -yy247: +yy254: yych = *++YYCURSOR; switch (yych) { - case 0x00: goto yy282; + case 0x00: goto yy291; case '0': case '1': case '2': @@ -1772,10 +1816,10 @@ static std::string cleanUpOpcodeName(absl::string_view rawOpcode, OpcodeScope sc case '6': case '7': case '8': - case '9': goto yy247; + case '9': goto yy254; default: goto yy34; } -yy249: +yy256: ++YYCURSOR; yynmatch = 3; yypmatch[2] = yyt1; @@ -1789,13 +1833,13 @@ static std::string cleanUpOpcodeName(absl::string_view rawOpcode, OpcodeScope sc opcode = absl::StrCat(group(1), "_velto", group(2)); goto end_region; } -#line 1793 "src/sfizz/OpcodeCleanup.cpp" -yy251: +#line 1837 "src/sfizz/OpcodeCleanup.cpp" +yy258: yych = *++YYCURSOR; -yy252: - if (yych <= 0x00) goto yy249; - goto yy251; -yy253: +yy259: + if (yych <= 0x00) goto yy256; + goto yy258; +yy260: ++YYCURSOR; yynmatch = 2; yypmatch[2] = yyt1; @@ -1807,17 +1851,17 @@ static std::string cleanUpOpcodeName(absl::string_view rawOpcode, OpcodeScope sc opcode = absl::StrCat("fil", group(1), "_type"); goto end_region; } -#line 1811 "src/sfizz/OpcodeCleanup.cpp" -yy255: +#line 1855 "src/sfizz/OpcodeCleanup.cpp" +yy262: yych = *++YYCURSOR; switch (yych) { - case 'o': goto yy284; - default: goto yy141; + case 'o': goto yy293; + default: goto yy142; } -yy256: +yy263: yych = *++YYCURSOR; switch (yych) { - case 0x00: goto yy285; + case 0x00: goto yy294; case '0': case '1': case '2': @@ -1827,38 +1871,50 @@ static std::string cleanUpOpcodeName(absl::string_view rawOpcode, OpcodeScope sc case '6': case '7': case '8': - case '9': goto yy256; + case '9': goto yy263; default: goto yy34; } -yy258: +yy265: yych = *++YYCURSOR; switch (yych) { - case 'f': goto yy287; + case 'f': goto yy296; default: goto yy34; } -yy259: +yy266: yych = *++YYCURSOR; switch (yych) { - case 'e': goto yy288; + case 'e': goto yy297; default: goto yy34; } -yy260: +yy267: yych = *++YYCURSOR; switch (yych) { - case 'o': goto yy262; + case 'o': goto yy269; default: goto yy34; } -yy261: +yy268: yych = *++YYCURSOR; switch (yych) { - case 'n': goto yy289; + case 'n': goto yy298; default: goto yy34; } -yy262: +yy269: yych = *++YYCURSOR; - if (yych <= 0x00) goto yy290; + if (yych <= 0x00) goto yy299; goto yy34; -yy263: +yy270: + yych = *++YYCURSOR; + switch (yych) { + case 't': goto yy301; + default: goto yy34; + } +yy271: + yych = *++YYCURSOR; + switch (yych) { + case 't': goto yy302; + default: goto yy34; + } +yy272: ++YYCURSOR; yynmatch = 3; yypmatch[4] = yyt1; @@ -1872,8 +1928,8 @@ static std::string cleanUpOpcodeName(absl::string_view rawOpcode, OpcodeScope sc opcode = absl::StrCat("start_", group(1), "cc", group(2)); goto end_region; } -#line 1876 "src/sfizz/OpcodeCleanup.cpp" -yy265: +#line 1932 "src/sfizz/OpcodeCleanup.cpp" +yy274: yych = *++YYCURSOR; switch (yych) { case '0': @@ -1887,115 +1943,115 @@ static std::string cleanUpOpcodeName(absl::string_view rawOpcode, OpcodeScope sc case '8': case '9': yyt1 = YYCURSOR; - goto yy292; + goto yy303; default: goto yy34; } -yy266: +yy275: yych = *++YYCURSOR; switch (yych) { - case '_': goto yy294; + case '_': goto yy305; default: goto yy34; } -yy267: +yy276: yych = *++YYCURSOR; switch (yych) { case 0x00: yyt2 = yyt3 = NULL; - goto yy295; + goto yy306; case '_': yyt3 = YYCURSOR; - goto yy297; + goto yy308; default: goto yy34; } -yy268: +yy277: yych = *++YYCURSOR; switch (yych) { - case 'c': goto yy299; + case 'c': goto yy310; default: goto yy34; } -yy269: +yy278: yych = *++YYCURSOR; switch (yych) { - case 'y': goto yy270; + case 'y': goto yy279; default: goto yy34; } -yy270: +yy279: yych = *++YYCURSOR; switch (yych) { - case 'c': goto yy300; + case 'c': goto yy311; default: goto yy34; } -yy271: +yy280: yych = *++YYCURSOR; switch (yych) { - case 'a': goto yy301; + case 'a': goto yy312; default: goto yy34; } -yy272: +yy281: yych = *++YYCURSOR; switch (yych) { - case 't': goto yy270; + case 't': goto yy279; default: goto yy34; } -yy273: +yy282: yych = *++YYCURSOR; switch (yych) { - case 'a': goto yy302; + case 'a': goto yy313; default: goto yy34; } -yy274: +yy283: yych = *++YYCURSOR; yyt2 = YYCURSOR; - goto yy306; -yy275: + goto yy317; +yy284: yych = *++YYCURSOR; switch (yych) { - case 't': goto yy307; + case 't': goto yy318; default: goto yy34; } -yy276: +yy285: yych = *++YYCURSOR; switch (yych) { - case 'e': goto yy308; + case 'e': goto yy319; default: goto yy34; } -yy277: +yy286: yych = *++YYCURSOR; switch (yych) { - case 'q': goto yy308; + case 'q': goto yy319; default: goto yy34; } -yy278: +yy287: yych = *++YYCURSOR; switch (yych) { - case 'n': goto yy309; + case 'n': goto yy320; default: goto yy34; } -yy279: +yy288: yych = *++YYCURSOR; switch (yych) { - case 'd': goto yy310; - default: goto yy208; + case 'd': goto yy321; + default: goto yy213; } -yy280: +yy289: yych = *++YYCURSOR; switch (yych) { case 0x00: yyt4 = yyt5 = NULL; yyt3 = YYCURSOR; - goto yy311; + goto yy322; case '_': yyt3 = yyt5 = YYCURSOR; - goto yy313; + goto yy324; default: goto yy34; } -yy281: +yy290: yych = *++YYCURSOR; switch (yych) { - case 'n': goto yy315; + case 'n': goto yy326; default: goto yy34; } -yy282: +yy291: ++YYCURSOR; yynmatch = 4; yypmatch[2] = yyt1; @@ -2011,14 +2067,14 @@ static std::string cleanUpOpcodeName(absl::string_view rawOpcode, OpcodeScope sc opcode = absl::StrCat(group(1), "_", group(2), "_oncc", group(3)); goto end_region; } -#line 2015 "src/sfizz/OpcodeCleanup.cpp" -yy284: +#line 2071 "src/sfizz/OpcodeCleanup.cpp" +yy293: yych = *++YYCURSOR; switch (yych) { - case 'm': goto yy316; - default: goto yy141; + case 'm': goto yy327; + default: goto yy142; } -yy285: +yy294: ++YYCURSOR; yynmatch = 3; yypmatch[4] = yyt1; @@ -2032,26 +2088,26 @@ static std::string cleanUpOpcodeName(absl::string_view rawOpcode, OpcodeScope sc opcode = absl::StrCat(group(1), "hdcc", group(2)); goto end_region; } -#line 2036 "src/sfizz/OpcodeCleanup.cpp" -yy287: +#line 2092 "src/sfizz/OpcodeCleanup.cpp" +yy296: yych = *++YYCURSOR; switch (yych) { - case 'f': goto yy317; + case 'f': goto yy328; default: goto yy34; } -yy288: +yy297: yych = *++YYCURSOR; switch (yych) { - case 't': goto yy262; + case 't': goto yy269; default: goto yy34; } -yy289: +yy298: yych = *++YYCURSOR; switch (yych) { - case 'a': goto yy318; + case 'a': goto yy329; default: goto yy34; } -yy290: +yy299: ++YYCURSOR; yynmatch = 3; yypmatch[2] = yyt1; @@ -2065,11 +2121,23 @@ static std::string cleanUpOpcodeName(absl::string_view rawOpcode, OpcodeScope sc opcode = absl::StrCat(group(1), "_", group(2), "1"); goto end_region; } -#line 2069 "src/sfizz/OpcodeCleanup.cpp" -yy292: +#line 2125 "src/sfizz/OpcodeCleanup.cpp" +yy301: + yych = *++YYCURSOR; + switch (yych) { + case 'h': goto yy330; + default: goto yy34; + } +yy302: + yych = *++YYCURSOR; + switch (yych) { + case 'c': goto yy331; + default: goto yy34; + } +yy303: yych = *++YYCURSOR; switch (yych) { - case 0x00: goto yy319; + case 0x00: goto yy332; case '0': case '1': case '2': @@ -2079,16 +2147,16 @@ static std::string cleanUpOpcodeName(absl::string_view rawOpcode, OpcodeScope sc case '6': case '7': case '8': - case '9': goto yy292; + case '9': goto yy303; default: goto yy34; } -yy294: +yy305: yych = *++YYCURSOR; switch (yych) { - case 'g': goto yy321; + case 'g': goto yy334; default: goto yy34; } -yy295: +yy306: ++YYCURSOR; yynmatch = 2; yypmatch[0] = yyt1; @@ -2100,39 +2168,39 @@ static std::string cleanUpOpcodeName(absl::string_view rawOpcode, OpcodeScope sc opcode = absl::StrCat("resonance1", group(1)); goto end_region; } -#line 2104 "src/sfizz/OpcodeCleanup.cpp" -yy297: +#line 2172 "src/sfizz/OpcodeCleanup.cpp" +yy308: yych = *++YYCURSOR; if (yych <= 0x00) { yyt2 = YYCURSOR; - goto yy295; + goto yy306; } - goto yy297; -yy299: + goto yy308; +yy310: yych = *++YYCURSOR; switch (yych) { - case 'k': goto yy270; + case 'k': goto yy279; default: goto yy34; } -yy300: +yy311: yych = *++YYCURSOR; switch (yych) { - case 'c': goto yy322; + case 'c': goto yy335; default: goto yy34; } -yy301: +yy312: yych = *++YYCURSOR; switch (yych) { - case 's': goto yy323; + case 's': goto yy336; default: goto yy34; } -yy302: +yy313: yych = *++YYCURSOR; switch (yych) { - case 'i': goto yy324; + case 'i': goto yy337; default: goto yy34; } -yy303: +yy314: ++YYCURSOR; yynmatch = 3; yypmatch[2] = yyt1; @@ -2146,37 +2214,37 @@ static std::string cleanUpOpcodeName(absl::string_view rawOpcode, OpcodeScope sc opcode = absl::StrCat(group(1), "_velto", group(2)); goto end_region; } -#line 2150 "src/sfizz/OpcodeCleanup.cpp" -yy305: +#line 2218 "src/sfizz/OpcodeCleanup.cpp" +yy316: yych = *++YYCURSOR; -yy306: - if (yych <= 0x00) goto yy303; - goto yy305; -yy307: +yy317: + if (yych <= 0x00) goto yy314; + goto yy316; +yy318: yych = *++YYCURSOR; switch (yych) { - case 'h': goto yy308; + case 'h': goto yy319; default: goto yy34; } -yy308: +yy319: yych = *++YYCURSOR; switch (yych) { - case 'c': goto yy325; + case 'c': goto yy338; default: goto yy34; } -yy309: +yy320: yych = *++YYCURSOR; switch (yych) { - case 'd': goto yy326; + case 'd': goto yy339; default: goto yy34; } -yy310: +yy321: yych = *++YYCURSOR; switch (yych) { - case 'o': goto yy327; - default: goto yy208; + case 'o': goto yy340; + default: goto yy213; } -yy311: +yy322: ++YYCURSOR; yynmatch = 4; yypmatch[2] = yyt1; @@ -2192,43 +2260,56 @@ static std::string cleanUpOpcodeName(absl::string_view rawOpcode, OpcodeScope sc opcode = absl::StrCat(group(1), "_", group(2), "1", group(3)); goto end_region; } -#line 2196 "src/sfizz/OpcodeCleanup.cpp" -yy313: +#line 2264 "src/sfizz/OpcodeCleanup.cpp" +yy324: yych = *++YYCURSOR; if (yych <= 0x00) { yyt4 = YYCURSOR; - goto yy311; + goto yy322; } - goto yy313; -yy315: + goto yy324; +yy326: yych = *++YYCURSOR; switch (yych) { - case 'c': goto yy328; + case 'c': goto yy341; default: goto yy34; } -yy316: +yy327: yych = *++YYCURSOR; - if (yych <= 0x00) goto yy329; - goto yy140; -yy317: + if (yych <= 0x00) goto yy342; + goto yy141; +yy328: yych = *++YYCURSOR; switch (yych) { case 0x00: yyt4 = yyt5 = NULL; yyt3 = YYCURSOR; - goto yy331; + goto yy344; case '_': yyt3 = yyt5 = YYCURSOR; - goto yy333; + goto yy346; default: goto yy34; } -yy318: +yy329: yych = *++YYCURSOR; switch (yych) { - case 'n': goto yy335; + case 'n': goto yy348; default: goto yy34; } -yy319: +yy330: + yych = *++YYCURSOR; + switch (yych) { + case '_': goto yy349; + case 'c': goto yy350; + default: goto yy34; + } +yy331: + yych = *++YYCURSOR; + switch (yych) { + case 'c': goto yy351; + default: goto yy34; + } +yy332: ++YYCURSOR; yynmatch = 3; yypmatch[4] = yyt1; @@ -2242,14 +2323,14 @@ static std::string cleanUpOpcodeName(absl::string_view rawOpcode, OpcodeScope sc opcode = absl::StrCat("start_", group(1), "hdcc", group(2)); goto end_region; } -#line 2246 "src/sfizz/OpcodeCleanup.cpp" -yy321: +#line 2327 "src/sfizz/OpcodeCleanup.cpp" +yy334: yych = *++YYCURSOR; switch (yych) { - case 'r': goto yy336; + case 'r': goto yy352; default: goto yy34; } -yy322: +yy335: yych = *++YYCURSOR; switch (yych) { case '0': @@ -2263,46 +2344,46 @@ static std::string cleanUpOpcodeName(absl::string_view rawOpcode, OpcodeScope sc case '8': case '9': yyt3 = YYCURSOR; - goto yy337; + goto yy353; default: goto yy34; } -yy323: +yy336: yych = *++YYCURSOR; switch (yych) { - case 'e': goto yy270; + case 'e': goto yy279; default: goto yy34; } -yy324: +yy337: yych = *++YYCURSOR; switch (yych) { - case 'n': goto yy270; + case 'n': goto yy279; default: goto yy34; } -yy325: +yy338: yych = *++YYCURSOR; switch (yych) { - case 'c': goto yy339; + case 'c': goto yy355; default: goto yy34; } -yy326: +yy339: yych = *++YYCURSOR; switch (yych) { - case 'o': goto yy340; + case 'o': goto yy356; default: goto yy34; } -yy327: +yy340: yych = *++YYCURSOR; switch (yych) { - case 'm': goto yy341; - default: goto yy208; + case 'm': goto yy357; + default: goto yy213; } -yy328: +yy341: yych = *++YYCURSOR; switch (yych) { - case 'e': goto yy280; + case 'e': goto yy289; default: goto yy34; } -yy329: +yy342: ++YYCURSOR; yynmatch = 1; yypmatch[0] = YYCURSOR - 12; @@ -2312,8 +2393,8 @@ static std::string cleanUpOpcodeName(absl::string_view rawOpcode, OpcodeScope sc opcode = "amp_random"; goto end_region; } -#line 2316 "src/sfizz/OpcodeCleanup.cpp" -yy331: +#line 2397 "src/sfizz/OpcodeCleanup.cpp" +yy344: ++YYCURSOR; yynmatch = 4; yypmatch[2] = yyt1; @@ -2329,30 +2410,35 @@ static std::string cleanUpOpcodeName(absl::string_view rawOpcode, OpcodeScope sc opcode = absl::StrCat(group(1), "_", group(2), "1", group(3)); goto end_region; } -#line 2333 "src/sfizz/OpcodeCleanup.cpp" -yy333: +#line 2414 "src/sfizz/OpcodeCleanup.cpp" +yy346: yych = *++YYCURSOR; if (yych <= 0x00) { yyt4 = YYCURSOR; - goto yy331; + goto yy344; } - goto yy333; -yy335: + goto yy346; +yy348: yych = *++YYCURSOR; switch (yych) { - case 'c': goto yy342; + case 'c': goto yy358; default: goto yy34; } -yy336: +yy349: yych = *++YYCURSOR; switch (yych) { - case 'o': goto yy343; + case 'o': goto yy359; default: goto yy34; } -yy337: +yy350: + yych = *++YYCURSOR; + switch (yych) { + case 'c': goto yy360; + default: goto yy34; + } +yy351: yych = *++YYCURSOR; switch (yych) { - case 0x00: goto yy344; case '0': case '1': case '2': @@ -2362,10 +2448,34 @@ static std::string cleanUpOpcodeName(absl::string_view rawOpcode, OpcodeScope sc case '6': case '7': case '8': - case '9': goto yy337; + case '9': + yyt1 = YYCURSOR; + goto yy361; default: goto yy34; } -yy339: +yy352: + yych = *++YYCURSOR; + switch (yych) { + case 'o': goto yy363; + default: goto yy34; + } +yy353: + yych = *++YYCURSOR; + switch (yych) { + case 0x00: goto yy364; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': goto yy353; + default: goto yy34; + } +yy355: yych = *++YYCURSOR; switch (yych) { case '0': @@ -2379,32 +2489,71 @@ static std::string cleanUpOpcodeName(absl::string_view rawOpcode, OpcodeScope sc case '8': case '9': yyt3 = YYCURSOR; - goto yy346; + goto yy366; default: goto yy34; } -yy340: +yy356: yych = *++YYCURSOR; switch (yych) { - case 'm': goto yy348; + case 'm': goto yy368; default: goto yy34; } -yy341: +yy357: yych = *++YYCURSOR; - if (yych <= 0x00) goto yy349; - goto yy207; -yy342: + if (yych <= 0x00) goto yy369; + goto yy212; +yy358: yych = *++YYCURSOR; switch (yych) { - case 'e': goto yy317; + case 'e': goto yy328; default: goto yy34; } -yy343: +yy359: yych = *++YYCURSOR; switch (yych) { - case 'u': goto yy351; + case 'n': goto yy371; default: goto yy34; } -yy344: +yy360: + yych = *++YYCURSOR; + switch (yych) { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + yyt1 = YYCURSOR; + goto yy372; + default: goto yy34; + } +yy361: + yych = *++YYCURSOR; + switch (yych) { + case 0x00: goto yy374; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': goto yy361; + default: goto yy34; + } +yy363: + yych = *++YYCURSOR; + switch (yych) { + case 'u': goto yy376; + default: goto yy34; + } +yy364: ++YYCURSOR; yynmatch = 4; yypmatch[2] = yyt1; @@ -2420,11 +2569,11 @@ static std::string cleanUpOpcodeName(absl::string_view rawOpcode, OpcodeScope sc opcode = absl::StrCat(group(1), "_", group(2), "_oncc", group(3)); goto end_region; } -#line 2424 "src/sfizz/OpcodeCleanup.cpp" -yy346: +#line 2573 "src/sfizz/OpcodeCleanup.cpp" +yy366: yych = *++YYCURSOR; switch (yych) { - case 0x00: goto yy352; + case 0x00: goto yy377; case '0': case '1': case '2': @@ -2434,13 +2583,13 @@ static std::string cleanUpOpcodeName(absl::string_view rawOpcode, OpcodeScope sc case '6': case '7': case '8': - case '9': goto yy346; + case '9': goto yy366; default: goto yy34; } -yy348: +yy368: yych = *++YYCURSOR; if (yych >= 0x01) goto yy34; -yy349: +yy369: ++YYCURSOR; yynmatch = 2; yypmatch[0] = yyt1; @@ -2452,14 +2601,49 @@ static std::string cleanUpOpcodeName(absl::string_view rawOpcode, OpcodeScope sc opcode = absl::StrCat("fil", group(1), "_random"); goto again_region; } -#line 2456 "src/sfizz/OpcodeCleanup.cpp" -yy351: +#line 2605 "src/sfizz/OpcodeCleanup.cpp" +yy371: yych = *++YYCURSOR; switch (yych) { - case 'p': goto yy354; + case 'c': goto yy379; default: goto yy34; } -yy352: +yy372: + yych = *++YYCURSOR; + switch (yych) { + case 0x00: goto yy380; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': goto yy372; + default: goto yy34; + } +yy374: + ++YYCURSOR; + yynmatch = 2; + yypmatch[2] = yyt1; + yypmatch[0] = yyt1 - 12; + yypmatch[1] = YYCURSOR; + yypmatch[3] = YYCURSOR - 1; +#line 195 "src/sfizz/OpcodeCleanup.re" + { + opcode = absl::StrCat("loop_start_oncc", group(1)); + goto end_region; + } +#line 2640 "src/sfizz/OpcodeCleanup.cpp" +yy376: + yych = *++YYCURSOR; + switch (yych) { + case 'p': goto yy382; + default: goto yy34; + } +yy377: ++YYCURSOR; yynmatch = 4; yypmatch[2] = yyt1; @@ -2475,10 +2659,48 @@ static std::string cleanUpOpcodeName(absl::string_view rawOpcode, OpcodeScope sc opcode = absl::StrCat(group(1), "_", group(2), "_oncc", group(3)); goto end_region; } -#line 2479 "src/sfizz/OpcodeCleanup.cpp" -yy354: +#line 2663 "src/sfizz/OpcodeCleanup.cpp" +yy379: yych = *++YYCURSOR; - if (yych >= 0x01) goto yy34; + switch (yych) { + case 'c': goto yy383; + default: goto yy34; + } +yy380: + ++YYCURSOR; + yynmatch = 2; + yypmatch[2] = yyt1; + yypmatch[0] = yyt1 - 13; + yypmatch[1] = YYCURSOR; + yypmatch[3] = YYCURSOR - 1; +#line 200 "src/sfizz/OpcodeCleanup.re" + { + opcode = absl::StrCat("loop_end_oncc", group(1)); + goto end_region; + } +#line 2682 "src/sfizz/OpcodeCleanup.cpp" +yy382: + yych = *++YYCURSOR; + if (yych <= 0x00) goto yy384; + goto yy34; +yy383: + yych = *++YYCURSOR; + switch (yych) { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + yyt1 = YYCURSOR; + goto yy386; + default: goto yy34; + } +yy384: ++YYCURSOR; yynmatch = 1; yypmatch[0] = YYCURSOR - 16; @@ -2488,9 +2710,38 @@ static std::string cleanUpOpcodeName(absl::string_view rawOpcode, OpcodeScope sc opcode = "group"; goto end_region; } -#line 2492 "src/sfizz/OpcodeCleanup.cpp" +#line 2714 "src/sfizz/OpcodeCleanup.cpp" +yy386: + yych = *++YYCURSOR; + switch (yych) { + case 0x00: goto yy388; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': goto yy386; + default: goto yy34; + } +yy388: + ++YYCURSOR; + yynmatch = 2; + yypmatch[2] = yyt1; + yypmatch[0] = yyt1 - 16; + yypmatch[1] = YYCURSOR; + yypmatch[3] = YYCURSOR - 1; +#line 205 "src/sfizz/OpcodeCleanup.re" + { + opcode = absl::StrCat("loop_end_oncc", group(1)); + goto end_region; + } +#line 2743 "src/sfizz/OpcodeCleanup.cpp" } -#line 199 "src/sfizz/OpcodeCleanup.re" +#line 214 "src/sfizz/OpcodeCleanup.re" end_region: @@ -2505,80 +2756,80 @@ static std::string cleanUpOpcodeName(absl::string_view rawOpcode, OpcodeScope sc YYCURSOR = opcode.c_str(); -#line 2509 "src/sfizz/OpcodeCleanup.cpp" +#line 2760 "src/sfizz/OpcodeCleanup.cpp" { char yych; yych = *YYCURSOR; switch (yych) { - case 's': goto yy361; - default: goto yy359; + case 's': goto yy394; + default: goto yy392; } -yy359: +yy392: ++YYCURSOR; -yy360: -#line 219 "src/sfizz/OpcodeCleanup.re" +yy393: +#line 234 "src/sfizz/OpcodeCleanup.re" { goto end_control; } -#line 2524 "src/sfizz/OpcodeCleanup.cpp" -yy361: +#line 2775 "src/sfizz/OpcodeCleanup.cpp" +yy394: yych = *(YYMARKER = ++YYCURSOR); switch (yych) { - case 'e': goto yy362; - default: goto yy360; + case 'e': goto yy395; + default: goto yy393; } -yy362: +yy395: yych = *++YYCURSOR; switch (yych) { - case 't': goto yy364; - default: goto yy363; + case 't': goto yy397; + default: goto yy396; } -yy363: +yy396: YYCURSOR = YYMARKER; - goto yy360; -yy364: + goto yy393; +yy397: yych = *++YYCURSOR; switch (yych) { - case '_': goto yy365; - default: goto yy363; + case '_': goto yy398; + default: goto yy396; } -yy365: +yy398: yych = *++YYCURSOR; switch (yych) { - case 'r': goto yy366; - default: goto yy363; + case 'r': goto yy399; + default: goto yy396; } -yy366: +yy399: yych = *++YYCURSOR; switch (yych) { - case 'e': goto yy367; - default: goto yy363; + case 'e': goto yy400; + default: goto yy396; } -yy367: +yy400: yych = *++YYCURSOR; switch (yych) { - case 'a': goto yy368; - default: goto yy363; + case 'a': goto yy401; + default: goto yy396; } -yy368: +yy401: yych = *++YYCURSOR; switch (yych) { - case 'l': goto yy369; - default: goto yy363; + case 'l': goto yy402; + default: goto yy396; } -yy369: +yy402: yych = *++YYCURSOR; switch (yych) { - case 'c': goto yy370; - default: goto yy363; + case 'c': goto yy403; + default: goto yy396; } -yy370: +yy403: yych = *++YYCURSOR; switch (yych) { - case 'c': goto yy371; - default: goto yy363; + case 'c': goto yy404; + default: goto yy396; } -yy371: +yy404: yych = *++YYCURSOR; switch (yych) { case '0': @@ -2592,13 +2843,13 @@ static std::string cleanUpOpcodeName(absl::string_view rawOpcode, OpcodeScope sc case '8': case '9': yyt1 = YYCURSOR; - goto yy372; - default: goto yy363; + goto yy405; + default: goto yy396; } -yy372: +yy405: yych = *++YYCURSOR; switch (yych) { - case 0x00: goto yy374; + case 0x00: goto yy407; case '0': case '1': case '2': @@ -2608,24 +2859,24 @@ static std::string cleanUpOpcodeName(absl::string_view rawOpcode, OpcodeScope sc case '6': case '7': case '8': - case '9': goto yy372; - default: goto yy363; + case '9': goto yy405; + default: goto yy396; } -yy374: +yy407: ++YYCURSOR; yynmatch = 2; yypmatch[2] = yyt1; yypmatch[0] = yyt1 - 10; yypmatch[1] = YYCURSOR; yypmatch[3] = YYCURSOR - 1; -#line 214 "src/sfizz/OpcodeCleanup.re" +#line 229 "src/sfizz/OpcodeCleanup.re" { opcode = absl::StrCat("set_hdcc", group(1)); goto end_control; } -#line 2627 "src/sfizz/OpcodeCleanup.cpp" +#line 2878 "src/sfizz/OpcodeCleanup.cpp" } -#line 223 "src/sfizz/OpcodeCleanup.re" +#line 238 "src/sfizz/OpcodeCleanup.re" end_control: diff --git a/src/sfizz/OpcodeCleanup.re b/src/sfizz/OpcodeCleanup.re index d3bdb12e3..27ea2e22b 100644 --- a/src/sfizz/OpcodeCleanup.re +++ b/src/sfizz/OpcodeCleanup.re @@ -192,6 +192,21 @@ end_region_oncc: goto end_region; } + "loop_startcc" (number) END { + opcode = absl::StrCat("loop_start_oncc", group(1)); + goto end_region; + } + + "loop_lengthcc" (number) END { + opcode = absl::StrCat("loop_end_oncc", group(1)); + goto end_region; + } + + "loop_length_oncc" (number) END { + opcode = absl::StrCat("loop_end_oncc", group(1)); + goto end_region; + } + * { goto end_region; } diff --git a/src/sfizz/Oversampler.h b/src/sfizz/Oversampler.h index e109384c2..745a44790 100644 --- a/src/sfizz/Oversampler.h +++ b/src/sfizz/Oversampler.h @@ -39,7 +39,7 @@ class Oversampler * @param factor * @param chunkSize */ - Oversampler(Oversampling factor = Oversampling::x1, size_t chunkSize = config::chunkSize); + Oversampler(Oversampling factor = Oversampling::x1, size_t chunkSize = config::fileChunkSize); /** * @brief Stream the oversampling of an input AudioBuffer into an output * one, possibly signaling the caller along the way of the number of diff --git a/src/sfizz/PolyphonyGroup.cpp b/src/sfizz/PolyphonyGroup.cpp index 4d3ceb748..2e8ba56a4 100644 --- a/src/sfizz/PolyphonyGroup.cpp +++ b/src/sfizz/PolyphonyGroup.cpp @@ -30,6 +30,6 @@ void sfz::PolyphonyGroup::removeAllVoices() noexcept unsigned sfz::PolyphonyGroup::numPlayingVoices() const noexcept { return absl::c_count_if(voices, [](const Voice* v) { - return !v->releasedOrFree(); + return !v->offedOrFree(); }); } diff --git a/src/sfizz/Region.cpp b/src/sfizz/Region.cpp index 05e73b678..e7481101b 100644 --- a/src/sfizz/Region.cpp +++ b/src/sfizz/Region.cpp @@ -6,13 +6,11 @@ #include "Region.h" #include "Opcode.h" -#include "MidiState.h" #include "MathHelpers.h" #include "utility/SwapAndPop.h" #include "utility/StringViewHelpers.h" #include "utility/Macros.h" #include "utility/Debug.h" -#include "ModifierHelpers.h" #include "modulations/ModId.h" #include "absl/strings/str_replace.h" #include "absl/strings/str_cat.h" @@ -130,13 +128,13 @@ bool sfz::Region::parseOpcode(const Opcode& rawOpcode, bool cleanOpcode) case hash("loop_start"): // also loopstart loopRange.setStart(opcode.read(Default::loopStart)); break; - case hash("loop_start_oncc&"): // also loop_start_cc& + case hash("loop_start_oncc&"): // also loop_start_cc&, loop_startcc& if (opcode.parameters.back() > config::numCCs) return false; loopStartCC[opcode.parameters.back()] = opcode.read(Default::loopMod); break; - case hash("loop_end_oncc&"): // also loop_end_cc& + case hash("loop_end_oncc&"): // also loop_end_cc&, loop_lengthcc&, loop_length_oncc&, loop_length_cc& if (opcode.parameters.back() > config::numCCs) return false; @@ -208,8 +206,12 @@ bool sfz::Region::parseOpcode(const Opcode& rawOpcode, bool cleanOpcode) break; // Region logic: key mapping case hash("lokey"): - triggerOnNote = true; - keyRange.setStart(opcode.read(Default::loKey)); + { + absl::optional optValue = opcode.readOptional(Default::loKey); + triggerOnNote = optValue != absl::nullopt; + uint8_t value = optValue.value_or(Default::loKey); + keyRange.setStart(value); + } break; case hash("hikey"): { @@ -449,6 +451,18 @@ bool sfz::Region::parseOpcode(const Opcode& rawOpcode, bool cleanOpcode) case hash("amp_veltrack"): ampVeltrack = opcode.read(Default::ampVeltrack); break; + case hash("amp_veltrack_oncc&"): + if (opcode.parameters.back() >= config::numCCs) + return false; + + ampVeltrackCC[opcode.parameters.back()].modifier = opcode.read(Default::ampVeltrackMod); + break; + case hash("amp_veltrack_curvecc&"): + if (opcode.parameters.back() >= config::numCCs) + return false; + + ampVeltrackCC[opcode.parameters.back()].curve = opcode.read(Default::curveCC); + break; case hash("amp_random"): ampRandom = opcode.read(Default::ampRandom); break; @@ -474,16 +488,16 @@ bool sfz::Region::parseOpcode(const Opcode& rawOpcode, bool cleanOpcode) crossfadeKeyOutRange.setEnd(opcode.read(Default::hiKey)); break; case hash("xfin_lovel"): - crossfadeVelInRange.setStart(opcode.read(Default::loVel)); + crossfadeVelInRange.setStart(opcode.read(Default::xfinLo)); break; case hash("xfin_hivel"): - crossfadeVelInRange.setEnd(opcode.read(Default::loVel)); // loVel for the proper default + crossfadeVelInRange.setEnd(opcode.read(Default::xfinHi)); break; case hash("xfout_lovel"): - crossfadeVelOutRange.setStart(opcode.read(Default::hiVel)); // hiVel for the proper default + crossfadeVelOutRange.setStart(opcode.read(Default::xfoutLo)); break; case hash("xfout_hivel"): - crossfadeVelOutRange.setEnd(opcode.read(Default::hiVel)); + crossfadeVelOutRange.setEnd(opcode.read(Default::xfoutHi)); break; case hash("xf_keycurve"): crossfadeKeyCurve = opcode.read(Default::crossfadeCurve); @@ -495,28 +509,28 @@ bool sfz::Region::parseOpcode(const Opcode& rawOpcode, bool cleanOpcode) if (opcode.parameters.back() >= config::numCCs) return false; crossfadeCCInRange[opcode.parameters.back()].setStart( - opcode.read(Default::loCC) + opcode.read(Default::xfinLo) ); break; case hash("xfin_hicc&"): if (opcode.parameters.back() >= config::numCCs) return false; crossfadeCCInRange[opcode.parameters.back()].setEnd( - opcode.read(Default::loCC) // loCC for the proper default + opcode.read(Default::xfinHi) ); break; case hash("xfout_locc&"): if (opcode.parameters.back() >= config::numCCs) return false; crossfadeCCOutRange[opcode.parameters.back()].setStart( - opcode.read(Default::hiCC) // hiCC for the proper default + opcode.read(Default::xfoutLo) ); break; case hash("xfout_hicc&"): if (opcode.parameters.back() >= config::numCCs) return false; crossfadeCCOutRange[opcode.parameters.back()].setEnd( - opcode.read(Default::hiCC) + opcode.read(Default::xfoutHi) ); break; case hash("xf_cccurve"): @@ -625,6 +639,32 @@ bool sfz::Region::parseOpcode(const Opcode& rawOpcode, bool cleanOpcode) filters[filterIndex].veltrack = opcode.read(Default::filterVeltrack); } break; + case hash("fil&_veltrack_oncc&"): + { + const auto filterIndex = opcode.parameters.front() - 1; + if (!extendIfNecessary(filters, filterIndex + 1, Default::numFilters)) + return false; + + const auto cc = opcode.parameters.back(); + if (cc >= config::numCCs) + return false; + + filters[filterIndex].veltrackCC[cc].modifier = opcode.read(Default::filterVeltrackMod); + } + break; + case hash("fil&_veltrack_curvecc&"): + { + const auto filterIndex = opcode.parameters.front() - 1; + if (!extendIfNecessary(filters, filterIndex + 1, Default::numFilters)) + return false; + + const auto cc = opcode.parameters.back(); + if (cc >= config::numCCs) + return false; + + filters[filterIndex].veltrackCC[cc].curve = opcode.read(Default::curveCC); + } + break; case hash("fil&_random"): // also fil_random, cutoff_random, cutoff&_random { const auto filterIndex = opcode.parameters.front() - 1; @@ -755,6 +795,18 @@ bool sfz::Region::parseOpcode(const Opcode& rawOpcode, bool cleanOpcode) case hash("pitch_veltrack"): pitchVeltrack = opcode.read(Default::pitchVeltrack); break; + case hash("pitch_veltrack_oncc&"): + if (opcode.parameters.back() >= config::numCCs) + return false; + + pitchVeltrackCC[opcode.parameters.back()].modifier = opcode.read(Default::pitchVeltrackMod); + break; + case hash("pitch_veltrack_curvecc&"): + if (opcode.parameters.back() >= config::numCCs) + return false; + + pitchVeltrackCC[opcode.parameters.back()].curve = opcode.read(Default::curveCC); + break; case hash("pitch_random"): pitchRandom = opcode.read(Default::pitchRandom); break; @@ -1091,6 +1143,10 @@ bool sfz::Region::parseEGOpcode(const Opcode& opcode, EGDescription& eg) break; + case_any_eg("dynamic"): + eg.dynamic = opcode.read(Default::egDynamic); + break; + case hash("pitcheg_depth"): getOrCreateConnection( ModKey::createNXYZ(ModId::PitchEG, id), @@ -1452,12 +1508,32 @@ bool sfz::Region::parseEGOpcodeV2(const Opcode& opcode) else return false; break; + case hash("eg&_time&_oncc&"): + if (FlexEGPoint* point = getOrCreateEGPoint()) { + auto ccNumber = opcode.parameters.back(); + if (ccNumber >= config::numCCs) + return false; + point->ccTime[ccNumber] = opcode.read(Default::flexEGPointTimeMod); + } + else + return false; + break; case hash("eg&_level&"): if (FlexEGPoint* point = getOrCreateEGPoint()) point->level = opcode.read(Default::flexEGPointLevel); else return false; break; + case hash("eg&_level&_oncc&"): + if (FlexEGPoint* point = getOrCreateEGPoint()) { + auto ccNumber = opcode.parameters.back(); + if (ccNumber >= config::numCCs) + return false; + point->ccLevel[ccNumber] = opcode.read(Default::flexEGPointLevelMod); + } + else + return false; + break; case hash("eg&_shape&"): if (FlexEGPoint* point = getOrCreateEGPoint()) point->setShape(opcode.read(Default::flexEGPointShape)); @@ -1656,31 +1732,6 @@ bool sfz::Region::processGenericCc(const Opcode& opcode, OpcodeSpec spec, return true; } -float sfz::Region::getBasePitchVariation(float noteNumber, float velocity) const noexcept -{ - ASSERT(velocity >= 0.0f && velocity <= 1.0f); - - fast_real_distribution pitchDistribution { 0.0f, pitchRandom }; - float pitchVariationInCents = pitchKeytrack * (noteNumber - float(pitchKeycenter)); // note difference with pitch center - pitchVariationInCents += pitch; // sample tuning - pitchVariationInCents += config::centPerSemitone * transpose; // sample transpose - pitchVariationInCents += velocity * pitchVeltrack; // track velocity - pitchVariationInCents += pitchDistribution(Random::randomGenerator); // random pitch changes - return centsFactor(pitchVariationInCents); -} - -float sfz::Region::getBaseVolumedB(const MidiState& midiState, int noteNumber) const noexcept -{ - fast_real_distribution volumeDistribution { 0.0f, ampRandom }; - auto baseVolumedB = volume + volumeDistribution(Random::randomGenerator); - baseVolumedB += globalVolume; - baseVolumedB += masterVolume; - baseVolumedB += groupVolume; - if (trigger == Trigger::release || trigger == Trigger::release_key) - baseVolumedB -= rtDecay * midiState.getNoteDuration(noteNumber); - return baseVolumedB; -} - float sfz::Region::getBaseGain() const noexcept { float baseGain = amplitude; @@ -1704,115 +1755,6 @@ float sfz::Region::getPhase() const noexcept return phase; } -uint64_t sfz::Region::getOffset(const MidiState& midiState) const noexcept -{ - std::uniform_int_distribution offsetDistribution { 0, offsetRandom }; - uint64_t finalOffset = offset + offsetDistribution(Random::randomGenerator); - for (const auto& mod: offsetCC) - finalOffset += static_cast(mod.data * midiState.getCCValue(mod.cc)); - return Default::offset.bounds.clamp(finalOffset); -} - -float sfz::Region::getDelay(const MidiState& midiState) const noexcept -{ - fast_real_distribution delayDistribution { 0, delayRandom }; - float finalDelay { delay }; - finalDelay += delayDistribution(Random::randomGenerator); - for (const auto& mod: delayCC) - finalDelay += mod.data * midiState.getCCValue(mod.cc); - - return Default::delay.bounds.clamp(finalDelay); -} - -uint32_t sfz::Region::getSampleEnd(MidiState& midiState) const noexcept -{ - int64_t end = sampleEnd; - for (const auto& mod: endCC) - end += static_cast(mod.data * midiState.getCCValue(mod.cc)); - - end = clamp(end, int64_t { 0 }, sampleEnd); - return static_cast(end); -} - -uint32_t sfz::Region::loopStart(MidiState& midiState) const noexcept -{ - auto start = loopRange.getStart(); - for (const auto& mod: loopStartCC) - start += static_cast(mod.data * midiState.getCCValue(mod.cc)); - - start = clamp(start, int64_t { 0 }, sampleEnd); - return static_cast(start); -} - -uint32_t sfz::Region::loopEnd(MidiState& midiState) const noexcept -{ - auto end = loopRange.getEnd(); - for (const auto& mod: loopEndCC) - end += static_cast(mod.data * midiState.getCCValue(mod.cc)); - - end = clamp(end, int64_t { 0 }, sampleEnd); - return static_cast(end); -} - -float sfz::Region::getNoteGain(int noteNumber, float velocity) const noexcept -{ - ASSERT(velocity >= 0.0f && velocity <= 1.0f); - - float baseGain { 1.0f }; - - // Amplitude key tracking - baseGain *= db2mag(ampKeytrack * static_cast(noteNumber - ampKeycenter)); - - // Crossfades related to the note number - baseGain *= crossfadeIn(crossfadeKeyInRange, noteNumber, crossfadeKeyCurve); - baseGain *= crossfadeOut(crossfadeKeyOutRange, noteNumber, crossfadeKeyCurve); - - // Amplitude velocity tracking - baseGain *= velocityCurve(velocity); - - // Crossfades related to velocity - baseGain *= crossfadeIn(crossfadeVelInRange, velocity, crossfadeVelCurve); - baseGain *= crossfadeOut(crossfadeVelOutRange, velocity, crossfadeVelCurve); - - return baseGain; -} - -float sfz::Region::getCrossfadeGain(const MidiState& midiState) const noexcept -{ - float gain { 1.0f }; - - // Crossfades due to CC states - for (const auto& ccData : crossfadeCCInRange) { - const auto ccValue = midiState.getCCValue(ccData.cc); - const auto crossfadeRange = ccData.data; - gain *= crossfadeIn(crossfadeRange, ccValue, crossfadeCCCurve); - } - - for (const auto& ccData : crossfadeCCOutRange) { - const auto ccValue = midiState.getCCValue(ccData.cc); - const auto crossfadeRange = ccData.data; - gain *= crossfadeOut(crossfadeRange, ccValue, crossfadeCCCurve); - } - - return gain; -} - -float sfz::Region::velocityCurve(float velocity) const noexcept -{ - ASSERT(velocity >= 0.0f && velocity <= 1.0f); - - float gain; - if (velCurve) - gain = velCurve->evalNormalized(velocity); - else - gain = velocity * velocity; - - gain = std::fabs(ampVeltrack) * (1.0f - gain); - gain = (ampVeltrack < 0) ? gain : (1.0f - gain); - - return gain; -} - void sfz::Region::offsetAllKeys(int offset) noexcept { // Offset key range diff --git a/src/sfizz/Region.h b/src/sfizz/Region.h index fe4070652..f30a4c12f 100644 --- a/src/sfizz/Region.h +++ b/src/sfizz/Region.h @@ -99,41 +99,6 @@ struct Region { */ bool shouldLoop() const noexcept { return (loopMode == LoopMode::loop_continuous || loopMode == LoopMode::loop_sustain); } - /** - * @brief Get the base pitch of the region depending on which note has been - * pressed and at which velocity. - * - * @param noteNumber - * @param velocity - * @return float - */ - float getBasePitchVariation(float noteNumber, float velocity) const noexcept; - /** - * @brief Get the note-related gain of the region depending on which note has been - * pressed and at which velocity. - * - * @param noteNumber - * @param velocity - * @return float - */ - float getNoteGain(int noteNumber, float velocity) const noexcept; - /** - * @brief Get the additional crossfade gain of the region depending on the - * CC values - * - * @param midiState - * @return float - */ - float getCrossfadeGain(const MidiState& midiState) const noexcept; - /** - * @brief Get the base volume of the region depending on which note has been - * pressed to trigger the region. - * - * @param midiState - * @param noteNumber - * @return float - */ - float getBaseVolumedB(const MidiState& midiState, int noteNumber) const noexcept; /** * @brief Get the base gain of the region. * @@ -146,12 +111,6 @@ struct Region { * @return float */ float getPhase() const noexcept; - /** - * @brief Computes the gain value related to the velocity of the note - * - * @return float - */ - float velocityCurve(float velocity) const noexcept; /** * @brief Get the detuning in cents for a given bend value between -1 and 1 * @@ -160,27 +119,6 @@ struct Region { */ float getBendInCents(float bend) const noexcept; - /** - * @brief Get the region offset in samples - * - * @param midiState - * @return uint32_t - */ - uint64_t getOffset(const MidiState& midiState) const noexcept; - /** - * @brief Get the region delay in seconds - * - * @param midiState - * @return float - */ - float getDelay(const MidiState& midiState) const noexcept; - /** - * @brief Get the index of the sample end, either natural end or forced - * loop. - * - * @return uint32_t - */ - uint32_t getSampleEnd(MidiState& midiState) const noexcept; /** * @brief Parse a new opcode into the region to fill in the proper parameters. * This must be called multiple times for each opcode applying to this region. @@ -260,9 +198,6 @@ struct Region { void offsetAllKeys(int offset) noexcept; - uint32_t loopStart(MidiState& midiState) const noexcept; - uint32_t loopEnd(MidiState& midiState) const noexcept; - /** * @brief Get the gain this region contributes into the input of the Nth * effect bus @@ -327,8 +262,8 @@ struct Region { absl::optional oscillatorQuality; // Instrument settings: voice lifecycle - uint32_t group { Default::group }; // group - absl::optional offBy {}; // off_by + int64_t group { Default::group }; // group + absl::optional offBy {}; // off_by OffMode offMode { Default::offMode }; // off_mode float offTime { Default::offTime }; // off_mode absl::optional notePolyphony {}; // note_polyphony @@ -384,6 +319,7 @@ struct Region { uint8_t ampKeycenter { Default::key }; // amp_keycenter float ampKeytrack { Default::ampKeytrack }; // amp_keytrack float ampVeltrack { Default::ampVeltrack }; // amp_veltrack + CCMap> ampVeltrackCC { ModifierCurvePair{ Default::ampVeltrackMod, Default::curveCC } }; std::vector> velocityPoints; // amp_velcurve_N absl::optional velCurve {}; float ampRandom { Default::ampRandom }; // amp_random @@ -415,6 +351,7 @@ struct Region { float pitchKeytrack { Default::pitchKeytrack }; // pitch_keytrack float pitchRandom { Default::pitchRandom }; // pitch_random float pitchVeltrack { Default::pitchVeltrack }; // pitch_veltrack + CCMap> pitchVeltrackCC { ModifierCurvePair{ Default::pitchVeltrackMod, Default::curveCC } }; float transpose { Default::transpose }; // transpose float pitch { Default::pitch }; // tune float bendUp { Default::bendUp }; diff --git a/src/sfizz/RegionSet.cpp b/src/sfizz/RegionSet.cpp index dfd858724..35369faec 100644 --- a/src/sfizz/RegionSet.cpp +++ b/src/sfizz/RegionSet.cpp @@ -57,7 +57,7 @@ void sfz::RegionSet::removeVoiceFromHierarchy(const Region* region, const Voice* unsigned sfz::RegionSet::numPlayingVoices() const noexcept { return absl::c_count_if(voices, [](const Voice* v) { - return !v->releasedOrFree(); + return !v->offedOrFree(); }); } diff --git a/src/sfizz/RegionStateful.cpp b/src/sfizz/RegionStateful.cpp new file mode 100644 index 000000000..e0f7bf865 --- /dev/null +++ b/src/sfizz/RegionStateful.cpp @@ -0,0 +1,164 @@ +// 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 "RegionStateful.h" +#include "ModifierHelpers.h" + +namespace sfz { + +float baseVolumedB(const Region& region, const MidiState& midiState, int noteNumber) noexcept +{ + fast_real_distribution volumeDistribution { 0.0f, region.ampRandom }; + auto baseVolumedB = region.volume + volumeDistribution(Random::randomGenerator); + baseVolumedB += region.globalVolume; + baseVolumedB += region.masterVolume; + baseVolumedB += region.groupVolume; + if (region.trigger == Trigger::release || region.trigger == Trigger::release_key) + baseVolumedB -= region.rtDecay * midiState.getNoteDuration(noteNumber); + return baseVolumedB; +} + + +uint64_t sampleOffset(const Region& region, const MidiState& midiState) noexcept +{ + std::uniform_int_distribution offsetDistribution { 0, region.offsetRandom }; + uint64_t finalOffset = region.offset + offsetDistribution(Random::randomGenerator); + for (const auto& mod: region.offsetCC) + finalOffset += static_cast(mod.data * midiState.getCCValue(mod.cc)); + return Default::offset.bounds.clamp(finalOffset); +} + +float regionDelay(const Region& region, const MidiState& midiState) noexcept +{ + fast_real_distribution delayDistribution { 0, region.delayRandom }; + float finalDelay { region.delay }; + finalDelay += delayDistribution(Random::randomGenerator); + for (const auto& mod: region.delayCC) + finalDelay += mod.data * midiState.getCCValue(mod.cc); + + return Default::delay.bounds.clamp(finalDelay); +} + +uint32_t sampleEnd(const Region& region, MidiState& midiState) noexcept +{ + int64_t end = region.sampleEnd; + for (const auto& mod: region.endCC) + end += static_cast(mod.data * midiState.getCCValue(mod.cc)); + + end = clamp(end, int64_t { 0 }, region.sampleEnd); + return static_cast(end); +} + +uint32_t loopStart(const Region& region, MidiState& midiState) noexcept +{ + auto start = region.loopRange.getStart(); + for (const auto& mod: region.loopStartCC) + start += static_cast(mod.data * midiState.getCCValue(mod.cc)); + + start = clamp(start, int64_t { 0 }, region.sampleEnd); + return static_cast(start); +} + +uint32_t loopEnd(const Region& region, MidiState& midiState) noexcept +{ + auto end = region.loopRange.getEnd(); + for (const auto& mod: region.loopEndCC) + end += static_cast(mod.data * midiState.getCCValue(mod.cc)); + + end = clamp(end, int64_t { 0 }, region.sampleEnd); + return static_cast(end); +} + +float noteGain(const Region& region, int noteNumber, float velocity, const MidiState& midiState, const CurveSet& curveSet) noexcept +{ + ASSERT(velocity >= 0.0f && velocity <= 1.0f); + + float baseGain { 1.0f }; + + // Amplitude key tracking + baseGain *= db2mag(region.ampKeytrack * static_cast(noteNumber - region.ampKeycenter)); + + // Crossfades related to the note number + baseGain *= crossfadeIn(region.crossfadeKeyInRange, noteNumber, region.crossfadeKeyCurve); + baseGain *= crossfadeOut(region.crossfadeKeyOutRange, noteNumber, region.crossfadeKeyCurve); + + // Amplitude velocity tracking + baseGain *= velocityCurve(region, velocity, midiState, curveSet); + + // Crossfades related to velocity + baseGain *= crossfadeIn(region.crossfadeVelInRange, velocity, region.crossfadeVelCurve); + baseGain *= crossfadeOut(region.crossfadeVelOutRange, velocity, region.crossfadeVelCurve); + + return baseGain; +} + +float crossfadeGain(const Region& region, const MidiState& midiState) noexcept +{ + float gain { 1.0f }; + + // Crossfades due to CC states + for (const auto& ccData : region.crossfadeCCInRange) { + const auto ccValue = midiState.getCCValue(ccData.cc); + const auto crossfadeRange = ccData.data; + gain *= crossfadeIn(crossfadeRange, ccValue, region.crossfadeCCCurve); + } + + for (const auto& ccData : region.crossfadeCCOutRange) { + const auto ccValue = midiState.getCCValue(ccData.cc); + const auto crossfadeRange = ccData.data; + gain *= crossfadeOut(crossfadeRange, ccValue, region.crossfadeCCCurve); + } + + return gain; +} + +float velocityCurve(const Region& region, float velocity, const MidiState& midiState, const CurveSet& curveSet) noexcept +{ + ASSERT(velocity >= 0.0f && velocity <= 1.0f); + + float gain; + if (region.velCurve) + gain = region.velCurve->evalNormalized(velocity); + else + gain = velocity * velocity; + + float veltrack = region.ampVeltrack; + + for (const auto& mod : region.ampVeltrackCC) { + const auto& curve = curveSet.getCurve(mod.data.curve); + const float value = midiState.getCCValue(mod.cc); + veltrack += curve.evalNormalized(value) * mod.data.modifier; + } + + gain = std::fabs(veltrack) * (1.0f - gain); + gain = (veltrack < 0) ? gain : (1.0f - gain); + + return gain; +} + +float basePitchVariation(const Region& region, float noteNumber, float velocity, const MidiState& midiState, const CurveSet& curveSet) noexcept +{ + ASSERT(velocity >= 0.0f && velocity <= 1.0f); + + fast_real_distribution pitchDistribution { 0.0f, region.pitchRandom }; + float pitchVariationInCents = region.pitchKeytrack * (noteNumber - float(region.pitchKeycenter)); // note difference with pitch center + pitchVariationInCents += region.pitch; // sample tuning + pitchVariationInCents += config::centPerSemitone * region.transpose; // sample transpose + + float veltrack = region.pitchVeltrack; + + for (const auto& mod : region.pitchVeltrackCC) { + const auto& curve = curveSet.getCurve(mod.data.curve); + const float value = midiState.getCCValue(mod.cc); + veltrack += curve.evalNormalized(value) * mod.data.modifier; + } + + pitchVariationInCents += velocity * veltrack; // track velocity + pitchVariationInCents += pitchDistribution(Random::randomGenerator); // random pitch changes + return centsFactor(pitchVariationInCents); +} + +} diff --git a/src/sfizz/RegionStateful.h b/src/sfizz/RegionStateful.h new file mode 100644 index 000000000..830312653 --- /dev/null +++ b/src/sfizz/RegionStateful.h @@ -0,0 +1,110 @@ +// 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 "Region.h" +#include "MidiState.h" +#include "Curve.h" + +namespace sfz { +/** + * @brief Get the note-related gain of the region depending on which note has been + * pressed and at which velocity. + * + * @param region + * @param noteNumber + * @param velocity + * @param midiState + * @param curveSet + * @return float + */ +float noteGain(const Region& region, int noteNumber, float velocity, const MidiState& midiState, const CurveSet& curveSet) noexcept; + +/** + * @brief Get the additional crossfade gain of the region depending on the + * CC values + * + * @param region + * @param midiState + * @return float + */ +float crossfadeGain(const Region& region, const MidiState& midiState) noexcept; + +/** + * @brief Get the base volume of the region depending on which note has been + * pressed to trigger the region. + * + * @param region + * @param midiState + * @param noteNumber + * @return float + */ +float baseVolumedB(const Region& region, const MidiState& midiState, int noteNumber) noexcept; + +/** + * @brief Get the region offset in samples + * + * @param region + * @param midiState + * @return uint32_t + */ +uint64_t sampleOffset(const Region& region, const MidiState& midiState) noexcept; +/** + * @brief Get the region delay in seconds + * + * @param region + * @param midiState + * @return float + */ +float regionDelay(const Region& region, const MidiState& midiState) noexcept; +/** + * @brief Get the index of the sample end, either natural end or forced + * loop. + * + * @param region + * @param midiState + * @return uint32_t + */ +uint32_t sampleEnd(const Region& region, MidiState& midiState) noexcept; + +/** + * @brief Computes the gain value related to the velocity of the note + * + * @return float + */ +float velocityCurve(const Region& region, float velocity, const MidiState& midiState, const CurveSet& curveSet) noexcept; + +/** + * @brief Returns the start of the loop for a given region + * + * @param region + * @param midiState + * @return uint32_t + */ +uint32_t loopStart(const Region& region, MidiState& midiState) noexcept; + +/** + * @brief Returns the end of the loop for a given region + * + * @param region + * @param midiState + * @return uint32_t + */ +uint32_t loopEnd(const Region& region, MidiState& midiState) noexcept; + +/** + * @brief Get the base pitch of the region depending on which note has been + * pressed and at which velocity. + * + * @param region + * @param noteNumber + * @param velocity + * @param midiState + * @param curveSet + * @return float + */ +float basePitchVariation(const Region& region, float noteNumber, float velocity, const MidiState& midiState, const CurveSet& curveSet) noexcept; + +} diff --git a/src/sfizz/Resources.cpp b/src/sfizz/Resources.cpp new file mode 100644 index 000000000..8770e60f1 --- /dev/null +++ b/src/sfizz/Resources.cpp @@ -0,0 +1,137 @@ +// 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 "Resources.h" +#include "SynthConfig.h" +#include "MidiState.h" +#include "FilePool.h" +#include "BufferPool.h" +#include "Logger.h" +#include "Wavetables.h" +#include "Curve.h" +#include "Tuning.h" +#include "BeatClock.h" +#include "Metronome.h" +#include "modulations/ModMatrix.h" + +namespace sfz { + +struct Resources::Impl { + SynthConfig synthConfig; + BufferPool bufferPool; + MidiState midiState; + Logger logger; + CurveSet curves; + FilePool filePool { logger }; + WavetablePool wavePool; + Tuning tuning; + absl::optional stretch; + ModMatrix modMatrix; + BeatClock beatClock; + Metronome metronome; +}; + +Resources::Resources() + : impl_(new Impl) +{ +} + +Resources::~Resources() +{ +} + +void Resources::setSampleRate(float samplerate) +{ + Impl& impl = *impl_; + impl.midiState.setSampleRate(samplerate); + impl.modMatrix.setSampleRate(samplerate); + impl.beatClock.setSampleRate(samplerate); + impl.metronome.init(samplerate); +} + +void Resources::setSamplesPerBlock(int samplesPerBlock) +{ + Impl& impl = *impl_; + impl.bufferPool.setBufferSize(samplesPerBlock); + impl.midiState.setSamplesPerBlock(samplesPerBlock); + impl.modMatrix.setSamplesPerBlock(samplesPerBlock); + impl.beatClock.setSamplesPerBlock(samplesPerBlock); +} + +void Resources::clear() +{ + Impl& impl = *impl_; + impl.curves = CurveSet::createPredefined(); + impl.filePool.clear(); + impl.wavePool.clearFileWaves(); + impl.logger.clear(); + impl.midiState.reset(); + impl.modMatrix.clear(); + impl.beatClock.clear(); + impl.metronome.clear(); +} + +const SynthConfig& Resources::getSynthConfig() const noexcept +{ + return impl_->synthConfig; +} + +const BufferPool& Resources::getBufferPool() const noexcept +{ + return impl_->bufferPool; +} + +const MidiState& Resources::getMidiState() const noexcept +{ + return impl_->midiState; +} + +const Logger& Resources::getLogger() const noexcept +{ + return impl_->logger; +} + +const CurveSet& Resources::getCurves() const noexcept +{ + return impl_->curves; +} + +const FilePool& Resources::getFilePool() const noexcept +{ + return impl_->filePool; +} + +const WavetablePool& Resources::getWavePool() const noexcept +{ + return impl_->wavePool; +} + +const Tuning& Resources::getTuning() const noexcept +{ + return impl_->tuning; +} + +const absl::optional& Resources::getStretch() const noexcept +{ + return impl_->stretch; +} + +const ModMatrix& Resources::getModMatrix() const noexcept +{ + return impl_->modMatrix; +} + +const BeatClock& Resources::getBeatClock() const noexcept +{ + return impl_->beatClock; +} + +const Metronome& Resources::getMetronome() const noexcept +{ + return impl_->metronome; +} + +} // namespace sfz diff --git a/src/sfizz/Resources.h b/src/sfizz/Resources.h index 27de6f1c2..b1928a8c4 100644 --- a/src/sfizz/Resources.h +++ b/src/sfizz/Resources.h @@ -5,64 +5,56 @@ // If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz #pragma once -#include "SynthConfig.h" -#include "MidiState.h" -#include "FilePool.h" -#include "BufferPool.h" -#include "Logger.h" -#include "Wavetables.h" -#include "Curve.h" -#include "Tuning.h" -#include "BeatClock.h" -#include "Metronome.h" -#include "modulations/ModMatrix.h" #include "absl/types/optional.h" +#include -namespace sfz -{ -class WavetableMulti; +namespace sfz { + +struct SynthConfig; +class BufferPool; +class MidiState; +class Logger; +class CurveSet; +class FilePool; +struct WavetablePool; +class Tuning; +class StretchTuning; +class ModMatrix; +class BeatClock; +class Metronome; -struct Resources +class Resources { - SynthConfig synthConfig; - BufferPool bufferPool; - MidiState midiState; - Logger logger; - CurveSet curves; - FilePool filePool { logger }; - WavetablePool wavePool; - Tuning tuning; - absl::optional stretch; - ModMatrix modMatrix; - BeatClock beatClock; - Metronome metronome; +public: + Resources(); + ~Resources(); + + void setSampleRate(float samplerate); + void setSamplesPerBlock(int samplesPerBlock); + void clear(); - void setSampleRate(float samplerate) - { - midiState.setSampleRate(samplerate); - modMatrix.setSampleRate(samplerate); - beatClock.setSampleRate(samplerate); - metronome.init(samplerate); - } + #define ACCESSOR_RW(Accessor, RetTy) \ + RetTy const& Accessor() const noexcept; \ + RetTy& Accessor() noexcept { return const_cast(const_cast(this)->Accessor()); } - void setSamplesPerBlock(int samplesPerBlock) - { - bufferPool.setBufferSize(samplesPerBlock); - midiState.setSamplesPerBlock(samplesPerBlock); - modMatrix.setSamplesPerBlock(samplesPerBlock); - beatClock.setSamplesPerBlock(samplesPerBlock); - } + ACCESSOR_RW(getSynthConfig, SynthConfig); + ACCESSOR_RW(getBufferPool, BufferPool); + ACCESSOR_RW(getMidiState, MidiState); + ACCESSOR_RW(getLogger, Logger); + ACCESSOR_RW(getCurves, CurveSet); + ACCESSOR_RW(getFilePool, FilePool); + ACCESSOR_RW(getWavePool, WavetablePool); + ACCESSOR_RW(getTuning, Tuning); + ACCESSOR_RW(getStretch, absl::optional); + ACCESSOR_RW(getModMatrix, ModMatrix); + ACCESSOR_RW(getBeatClock, BeatClock); + ACCESSOR_RW(getMetronome, Metronome); - void clear() - { - curves = CurveSet::createPredefined(); - filePool.clear(); - wavePool.clearFileWaves(); - logger.clear(); - midiState.reset(); - modMatrix.clear(); - beatClock.clear(); - metronome.clear(); - } + #undef ACCESSOR_RW + +private: + struct Impl; + std::unique_ptr impl_; }; -} + +} // namespace sfz diff --git a/src/sfizz/SfzHelpers.h b/src/sfizz/SfzHelpers.h index 46a7e1661..079835f37 100644 --- a/src/sfizz/SfzHelpers.h +++ b/src/sfizz/SfzHelpers.h @@ -22,6 +22,15 @@ namespace sfz { using CCNamePair = std::pair; using NoteNamePair = std::pair; +template +struct ModifierCurvePair +{ + ModifierCurvePair(const T& modifier, uint8_t curve) + : modifier(modifier), curve(curve) {} + T modifier {}; + uint8_t curve {}; +}; + template using MidiNoteArray = std::array; template diff --git a/src/sfizz/Synth.cpp b/src/sfizz/Synth.cpp index 77173a4ed..fbf5f1589 100644 --- a/src/sfizz/Synth.cpp +++ b/src/sfizz/Synth.cpp @@ -16,11 +16,19 @@ #include "Region.h" #include "RegionSet.h" #include "Resources.h" +#include "BufferPool.h" +#include "FilePool.h" +#include "Wavetables.h" +#include "Tuning.h" +#include "BeatClock.h" +#include "Metronome.h" +#include "SynthConfig.h" #include "ScopedFTZ.h" #include "utility/StringViewHelpers.h" #include "utility/XmlHelpers.h" #include "Voice.h" #include "Interpolators.h" +#include "parser/Parser.h" #include #include #include @@ -59,18 +67,19 @@ Synth::Impl::Impl() resetVoices(config::numVoices); // modulation sources + MidiState& midiState = resources_.getMidiState(); genController_.reset(new ControllerSource(resources_, voiceManager_)); genLFO_.reset(new LFOSource(voiceManager_)); genFlexEnvelope_.reset(new FlexEnvelopeSource(voiceManager_)); - genADSREnvelope_.reset(new ADSREnvelopeSource(voiceManager_, resources_.midiState)); - genChannelAftertouch_.reset(new ChannelAftertouchSource(voiceManager_, resources_.midiState)); - genPolyAftertouch_.reset(new PolyAftertouchSource(voiceManager_, resources_.midiState)); + genADSREnvelope_.reset(new ADSREnvelopeSource(voiceManager_)); + genChannelAftertouch_.reset(new ChannelAftertouchSource(voiceManager_, midiState)); + genPolyAftertouch_.reset(new PolyAftertouchSource(voiceManager_, midiState)); } Synth::Impl::~Impl() { voiceManager_.reset(); - resources_.filePool.emptyFileLoadingQueues(); + resources_.getFilePool().emptyFileLoadingQueues(); } void Synth::Impl::onParseFullBlock(const std::string& header, const std::vector& members) @@ -113,7 +122,7 @@ void Synth::Impl::onParseFullBlock(const std::string& header, const std::vector< buildRegion(members); break; case hash("curve"): - resources_.curves.addCurveFromHeader(members); + resources_.getCurves().addCurveFromHeader(members); break; case hash("effect"): handleEffectOpcodes(members); @@ -138,7 +147,8 @@ void Synth::Impl::onParseWarning(const SourceRange& range, const std::string& me void Synth::Impl::buildRegion(const std::vector& regionOpcodes) { int regionNumber = static_cast(layers_.size()); - Layer* lastLayer = new Layer(regionNumber, defaultPath_, resources_.midiState); + MidiState& midiState = resources_.getMidiState(); + Layer* lastLayer = new Layer(regionNumber, defaultPath_, midiState); layers_.emplace_back(lastLayer); Region* lastRegion = &lastLayer->getRegion(); @@ -221,8 +231,11 @@ void Synth::Impl::buildRegion(const std::vector& regionOpcodes) void Synth::Impl::clear() { + FilePool& filePool = resources_.getFilePool(); + MidiState& midiState = resources_.getMidiState(); + // Clear the background queues before removing everyone - resources_.filePool.waitForBackgroundLoading(); + filePool.waitForBackgroundLoading(); voiceManager_.reset(); for (auto& list : lastKeyswitchLists_) @@ -250,14 +263,17 @@ void Synth::Impl::clear() rootPath_.clear(); numGroups_ = 0; numMasters_ = 0; + noteOffset_ = 0; + octaveOffset_ = 0; currentSwitch_ = absl::nullopt; defaultPath_ = ""; image_ = ""; - resources_.midiState.reset(); - resources_.filePool.clear(); - resources_.filePool.setRamLoading(config::loadInRam); + midiState.reset(); + filePool.clear(); + filePool.setRamLoading(config::loadInRam); clearCCLabels(); currentUsedCCs_.clear(); + sustainOrSostenuto_.clear(); changedCCsThisCycle_.clear(); changedCCsLastCycle_.clear(); clearKeyLabels(); @@ -324,7 +340,7 @@ void Synth::Impl::handleGlobalOpcodes(const std::vector& members) void Synth::Impl::handleGroupOpcodes(const std::vector& members, const std::vector& masterMembers) { - absl::optional groupIdx; + absl::optional groupIdx; absl::optional maxPolyphony; const auto parseOpcode = [&](const Opcode& rawMember) { @@ -399,13 +415,16 @@ void Synth::Impl::handleControlOpcodes(const std::vector& members) octaveOffset_ = member.read(Default::octaveOffset); break; case hash("hint_ram_based"): + { + FilePool& filePool = resources_.getFilePool(); if (member.value == "1") - resources_.filePool.setRamLoading(true); + filePool.setRamLoading(true); else if (member.value == "0") - resources_.filePool.setRamLoading(false); + filePool.setRamLoading(false); else DBG("Unsupported value for hint_ram_based: " << member.value); break; + } case hash("hint_stealing"): switch(hash(member.value)) { case hash("first"): @@ -421,6 +440,12 @@ void Synth::Impl::handleControlOpcodes(const std::vector& members) DBG("Unsupported value for hint_stealing: " << member.value); } break; + case hash("hint_sustain_cancels_release"): + { + SynthConfig& config = resources_.getSynthConfig(); + config.sustainCancelsRelease = member.read(Default::sustainCancelsRelease); + } + break; default: // Unsupported control opcode DBG("Unsupported control opcode: " << member.name); @@ -550,7 +575,8 @@ bool Synth::loadSfzString(const fs::path& path, absl::string_view text) void Synth::Impl::finalizeSfzLoad() { const fs::path& rootDirectory = parser_.originalDirectory(); - resources_.filePool.setRootDirectory(rootDirectory); + FilePool& filePool = resources_.getFilePool(); + filePool.setRootDirectory(rootDirectory); // a string representation used for OSC purposes rootPath_ = rootDirectory.u8string(); @@ -558,6 +584,8 @@ void Synth::Impl::finalizeSfzLoad() size_t currentRegionIndex = 0; size_t currentRegionCount = layers_.size(); + absl::flat_hash_map filesToLoad; + auto removeCurrentRegion = [this, ¤tRegionIndex, ¤tRegionCount]() { const Region& region = layers_[currentRegionIndex]->getRegion(); DBG("Removing the region with sample " << *region.sampleId); @@ -583,13 +611,16 @@ void Synth::Impl::finalizeSfzLoad() absl::optional fileInformation; + FilePool& filePool = resources_.getFilePool(); + WavetablePool& wavePool = resources_.getWavePool(); + if (!region.isGenerator()) { - if (!resources_.filePool.checkSampleId(*region.sampleId)) { + if (!filePool.checkSampleId(*region.sampleId)) { removeCurrentRegion(); continue; } - fileInformation = resources_.filePool.getFileInformation(*region.sampleId); + fileInformation = filePool.getFileInformation(*region.sampleId); if (!fileInformation) { removeCurrentRegion(); continue; @@ -598,7 +629,7 @@ void Synth::Impl::finalizeSfzLoad() region.hasWavetableSample = fileInformation->wavetable.has_value(); if (fileInformation->end < config::wavetableMaxFrames) { - auto sample = resources_.filePool.loadFile(*region.sampleId); + auto sample = filePool.loadFile(*region.sampleId); bool allZeros = true; int numChannels = sample->information.numChannels; for (int i = 0; i < numChannels; ++i) { @@ -609,8 +640,6 @@ void Synth::Impl::finalizeSfzLoad() if (allZeros) { region.sampleId.reset(new FileId("*silence")); region.hasWavetableSample = false; - } else { - region.hasWavetableSample |= true; } } } @@ -653,13 +682,11 @@ void Synth::Impl::finalizeSfzLoad() return Default::offsetMod.bounds.clamp(sumOffsetCC); }(); - if (!resources_.filePool.preloadFile(*region.sampleId, maxOffset)) { - removeCurrentRegion(); - continue; - } + auto& toLoad = filesToLoad[*region.sampleId]; + toLoad = max(toLoad, maxOffset); } else if (!region.isGenerator()) { - if (!resources_.wavePool.createFileWave(resources_.filePool, std::string(region.sampleId->filename()))) { + if (!wavePool.createFileWave(filePool, std::string(region.sampleId->filename()))) { removeCurrentRegion(); continue; } @@ -698,8 +725,9 @@ void Synth::Impl::finalizeSfzLoad() } // Defaults + MidiState& midiState = resources_.getMidiState(); for (int cc = 0; cc < config::numCCs; cc++) { - layer.registerCC(cc, resources_.midiState.getCCValue(cc)); + layer.updateCCState(cc, midiState.getCCValue(cc)); } @@ -736,6 +764,11 @@ void Synth::Impl::finalizeSfzLoad() ++currentRegionIndex; } + + for (const auto& toLoad: filesToLoad) { + filePool.preloadFile(toLoad.first, toLoad.second); + } + if (currentRegionCount < layers_.size()) { DBG("Removing " << (layers_.size() - currentRegionCount) << " out of " << layers_.size() << " regions"); @@ -819,37 +852,37 @@ void Synth::Impl::finalizeSfzLoad() bool Synth::loadScalaFile(const fs::path& path) { Impl& impl = *impl_; - return impl.resources_.tuning.loadScalaFile(path); + return impl.resources_.getTuning().loadScalaFile(path); } bool Synth::loadScalaString(const std::string& text) { Impl& impl = *impl_; - return impl.resources_.tuning.loadScalaString(text); + return impl.resources_.getTuning().loadScalaString(text); } void Synth::setScalaRootKey(int rootKey) { Impl& impl = *impl_; - impl.resources_.tuning.setScalaRootKey(rootKey); + impl.resources_.getTuning().setScalaRootKey(rootKey); } int Synth::getScalaRootKey() const { Impl& impl = *impl_; - return impl.resources_.tuning.getScalaRootKey(); + return impl.resources_.getTuning().getScalaRootKey(); } void Synth::setTuningFrequency(float frequency) { Impl& impl = *impl_; - impl.resources_.tuning.setTuningFrequency(frequency); + impl.resources_.getTuning().setTuningFrequency(frequency); } float Synth::getTuningFrequency() const { Impl& impl = *impl_; - return impl.resources_.tuning.getTuningFrequency(); + return impl.resources_.getTuning().getTuningFrequency(); } void Synth::loadStretchTuningByRatio(float ratio) @@ -858,10 +891,11 @@ void Synth::loadStretchTuningByRatio(float ratio) SFIZZ_CHECK(ratio >= 0.0f && ratio <= 1.0f); ratio = clamp(ratio, 0.0f, 1.0f); + absl::optional& stretch = impl.resources_.getStretch(); if (ratio > 0.0f) - impl.resources_.stretch = StretchTuning::createRailsbackFromRatio(ratio); + stretch = StretchTuning::createRailsbackFromRatio(ratio); else - impl.resources_.stretch.reset(); + stretch.reset(); } int Synth::getNumActiveVoices() const noexcept @@ -931,8 +965,12 @@ void Synth::renderBlock(AudioSpan buffer) noexcept return; } - if (impl.resources_.synthConfig.freeWheeling) - impl.resources_.filePool.waitForBackgroundLoading(); + const SynthConfig& synthConfig = impl.resources_.getSynthConfig(); + FilePool& filePool = impl.resources_.getFilePool(); + BufferPool& bufferPool = impl.resources_.getBufferPool(); + + if (synthConfig.freeWheeling) + filePool.waitForBackgroundLoading(); const auto now = std::chrono::high_resolution_clock::now(); const auto timeSinceLastCollection = @@ -940,25 +978,27 @@ void Synth::renderBlock(AudioSpan buffer) noexcept if (timeSinceLastCollection.count() > config::fileClearingPeriod) { impl.lastGarbageCollection_ = now; - impl.resources_.filePool.triggerGarbageCollection(); + filePool.triggerGarbageCollection(); } - auto tempSpan = impl.resources_.bufferPool.getStereoBuffer(numFrames); - auto tempMixSpan = impl.resources_.bufferPool.getStereoBuffer(numFrames); - auto rampSpan = impl.resources_.bufferPool.getBuffer(numFrames); + auto tempSpan = bufferPool.getStereoBuffer(numFrames); + auto tempMixSpan = bufferPool.getStereoBuffer(numFrames); + auto rampSpan = bufferPool.getBuffer(numFrames); if (!tempSpan || !tempMixSpan || !rampSpan) { DBG("[sfizz] Could not get a temporary buffer; exiting callback... "); return; } - ModMatrix& mm = impl.resources_.modMatrix; + ModMatrix& mm = impl.resources_.getModMatrix(); mm.beginCycle(numFrames); - BeatClock& bc = impl.resources_.beatClock; + BeatClock& bc = impl.resources_.getBeatClock(); bc.beginCycle(numFrames); - if (impl.playheadMoved_ && impl.resources_.beatClock.isPlaying()) { - impl.resources_.midiState.flushEvents(); + MidiState& midiState = impl.resources_.getMidiState(); + + if (impl.playheadMoved_ && bc.isPlaying()) { + midiState.flushEvents(); impl.genController_->resetSmoothers(); impl.playheadMoved_ = false; } @@ -1028,7 +1068,8 @@ void Synth::renderBlock(AudioSpan buffer) noexcept // Process the metronome (debugging tool for host time info) constexpr bool metronomeEnabled = false; if (metronomeEnabled) { - impl.resources_.metronome.processAdding( + Metronome& metro = impl.resources_.getMetronome(); + metro.processAdding( bc.getRunningBeatNumber().data(), bc.getRunningBeatsPerBar().data(), buffer.getChannel(0), buffer.getChannel(1), numFrames); } @@ -1045,11 +1086,12 @@ void Synth::renderBlock(AudioSpan buffer) noexcept { // Clear events and advance midi time ScopedTiming logger { impl.dispatchDuration_, ScopedTiming::Operation::addToDuration }; - impl.resources_.midiState.advanceTime(buffer.getNumFrames()); + midiState.advanceTime(buffer.getNumFrames()); } callbackBreakdown.dispatch = impl.dispatchDuration_; - impl.resources_.logger.logCallbackTime( + Logger& logger = impl.resources_.getLogger(); + logger.logCallbackTime( callbackBreakdown, impl.voiceManager_.getNumActiveVoices(), numFrames); // Reset the dispatch counter @@ -1073,8 +1115,8 @@ void Synth::hdNoteOn(int delay, int noteNumber, float normalizedVelocity) noexce ASSERT(noteNumber >= 0); Impl& impl = *impl_; ScopedTiming logger { impl.dispatchDuration_, ScopedTiming::Operation::addToDuration }; + impl.resources_.getMidiState().noteOnEvent(delay, noteNumber, normalizedVelocity); impl.noteOnDispatch(delay, noteNumber, normalizedVelocity); - impl.resources_.midiState.noteOnEvent(delay, noteNumber, normalizedVelocity); } void Synth::noteOff(int delay, int noteNumber, int velocity) noexcept @@ -1087,20 +1129,20 @@ void Synth::hdNoteOff(int delay, int noteNumber, float normalizedVelocity) noexc { ASSERT(noteNumber < 128); ASSERT(noteNumber >= 0); - UNUSED(normalizedVelocity); Impl& impl = *impl_; ScopedTiming logger { impl.dispatchDuration_, ScopedTiming::Operation::addToDuration }; // FIXME: Some keyboards (e.g. Casio PX5S) can send a real note-off velocity. In this case, do we have a // way in sfz to specify that a release trigger should NOT use the note-on velocity? // auto replacedVelocity = (velocity == 0 ? getNoteVelocity(noteNumber) : velocity); - const auto replacedVelocity = impl.resources_.midiState.getNoteVelocity(noteNumber); + MidiState& midiState = impl.resources_.getMidiState(); + midiState.noteOffEvent(delay, noteNumber, normalizedVelocity); + const auto replacedVelocity = midiState.getNoteVelocity(noteNumber); for (auto& voice : impl.voiceManager_) voice.registerNoteOff(delay, noteNumber, replacedVelocity); impl.noteOffDispatch(delay, noteNumber, replacedVelocity); - impl.resources_.midiState.noteOffEvent(delay, noteNumber, normalizedVelocity); } void Synth::Impl::startVoice(Layer* layer, int delay, const TriggerEvent& triggerEvent, SisterVoiceRingBuilder& ring) noexcept @@ -1122,7 +1164,8 @@ void Synth::Impl::checkOffGroups(const Region* region, int delay, int number) for (auto& voice : voiceManager_) { if (voice.checkOffGroup(region, delay, number)) { const TriggerEvent& event = voice.getTriggerEvent(); - noteOffDispatch(delay, event.number, event.value); + if (event.type == TriggerEventType::NoteOn) + noteOffDispatch(delay, event.number, event.value); } } } @@ -1231,6 +1274,7 @@ void Synth::Impl::ccDispatch(int delay, int ccNumber, float value) noexcept { SisterVoiceRingBuilder ring; TriggerEvent triggerEvent { TriggerEventType::CC, ccNumber, value }; + const auto randValue = randNoteDistribution_(Random::randomGenerator); for (Layer* layer : ccActivationLists_[ccNumber]) { const Region& region = layer->getRegion(); @@ -1248,7 +1292,7 @@ void Synth::Impl::ccDispatch(int delay, int ccNumber, float value) noexcept } } - if (layer->registerCC(ccNumber, value)) { + if (layer->registerCC(ccNumber, value, randValue)) { checkOffGroups(®ion, delay, ccNumber); startVoice(layer, delay, triggerEvent, ring); } @@ -1276,6 +1320,8 @@ void Synth::Impl::performHdcc(int delay, int ccNumber, float normValue, bool asM changedCCsThisCycle_.set(ccNumber); + MidiState& midiState = resources_.getMidiState(); + if (asMidi) { if (ccNumber == config::resetCC) { resetAllControllers(delay); @@ -1285,7 +1331,7 @@ void Synth::Impl::performHdcc(int delay, int ccNumber, float normValue, bool asM if (ccNumber == config::allNotesOffCC || ccNumber == config::allSoundOffCC) { for (auto& voice : voiceManager_) voice.reset(); - resources_.midiState.allNotesOff(delay); + midiState.allNotesOff(delay); return; } } @@ -1294,7 +1340,7 @@ void Synth::Impl::performHdcc(int delay, int ccNumber, float normValue, bool asM voice.registerCC(delay, ccNumber, normValue); ccDispatch(delay, ccNumber, normValue); - resources_.midiState.ccEvent(delay, ccNumber, normValue); + midiState.ccEvent(delay, ccNumber, normValue); } void Synth::Impl::setDefaultHdcc(int ccNumber, float value) @@ -1302,7 +1348,7 @@ void Synth::Impl::setDefaultHdcc(int ccNumber, float value) ASSERT(ccNumber >= 0); ASSERT(ccNumber < config::numCCs); defaultCCValues_[ccNumber] = value; - resources_.midiState.ccEvent(0, ccNumber, value); + resources_.getMidiState().ccEvent(0, ccNumber, value); } float Synth::getHdcc(int ccNumber) @@ -1310,7 +1356,7 @@ float Synth::getHdcc(int ccNumber) ASSERT(ccNumber >= 0); ASSERT(ccNumber < config::numCCs); Impl& impl = *impl_; - return impl.resources_.midiState.getCCValue(ccNumber); + return impl.resources_.getMidiState().getCCValue(ccNumber); } float Synth::getDefaultHdcc(int ccNumber) @@ -1332,7 +1378,7 @@ void Synth::hdPitchWheel(int delay, float normalizedPitch) noexcept Impl& impl = *impl_; ScopedTiming logger { impl.dispatchDuration_, ScopedTiming::Operation::addToDuration }; - impl.resources_.midiState.pitchBendEvent(delay, normalizedPitch); + impl.resources_.getMidiState().pitchBendEvent(delay, normalizedPitch); for (const Impl::LayerPtr& layer : impl.layers_) { layer->registerPitchWheel(normalizedPitch); @@ -1356,7 +1402,7 @@ void Synth::hdChannelAftertouch(int delay, float normAftertouch) noexcept Impl& impl = *impl_; ScopedTiming logger { impl.dispatchDuration_, ScopedTiming::Operation::addToDuration }; - impl.resources_.midiState.channelAftertouchEvent(delay, normAftertouch); + impl.resources_.getMidiState().channelAftertouchEvent(delay, normAftertouch); for (const Impl::LayerPtr& layerPtr : impl.layers_) { layerPtr->registerAftertouch(normAftertouch); @@ -1380,7 +1426,7 @@ void Synth::hdPolyAftertouch(int delay, int noteNumber, float normAftertouch) no Impl& impl = *impl_; ScopedTiming logger { impl.dispatchDuration_, ScopedTiming::Operation::addToDuration }; - impl.resources_.midiState.polyAftertouchEvent(delay, noteNumber, normAftertouch); + impl.resources_.getMidiState().polyAftertouchEvent(delay, noteNumber, normAftertouch); for (auto& voice : impl.voiceManager_) voice.registerPolyAftertouch(delay, noteNumber, normAftertouch); @@ -1394,7 +1440,7 @@ void Synth::tempo(int delay, float secondsPerBeat) noexcept Impl& impl = *impl_; ScopedTiming logger { impl.dispatchDuration_, ScopedTiming::Operation::addToDuration }; - impl.resources_.beatClock.setTempo(delay, secondsPerBeat); + impl.resources_.getBeatClock().setTempo(delay, secondsPerBeat); } void Synth::bpmTempo(int delay, float beatsPerMinute) noexcept @@ -1408,7 +1454,7 @@ void Synth::timeSignature(int delay, int beatsPerBar, int beatUnit) Impl& impl = *impl_; ScopedTiming logger { impl.dispatchDuration_, ScopedTiming::Operation::addToDuration }; - impl.resources_.beatClock.setTimeSignature(delay, TimeSignature(beatsPerBar, beatUnit)); + impl.resources_.getBeatClock().setTimeSignature(delay, TimeSignature(beatsPerBar, beatUnit)); } void Synth::timePosition(int delay, int bar, double barBeat) @@ -1416,16 +1462,18 @@ void Synth::timePosition(int delay, int bar, double barBeat) Impl& impl = *impl_; ScopedTiming logger { impl.dispatchDuration_, ScopedTiming::Operation::addToDuration }; + BeatClock& beatClock = impl.resources_.getBeatClock(); + const auto newPosition = BBT(bar, barBeat); - const auto newBeatPosition = newPosition.toBeats(impl.resources_.beatClock.getTimeSignature()); - const auto currentBeatPosition = impl.resources_.beatClock.getLastBeatPosition(); + const auto newBeatPosition = newPosition.toBeats(beatClock.getTimeSignature()); + const auto currentBeatPosition = beatClock.getLastBeatPosition(); const auto positionDifference = std::abs(newBeatPosition - currentBeatPosition); - const auto threshold = config::playheadMovedFrames * impl.resources_.beatClock.getBeatsPerFrame(); + const auto threshold = config::playheadMovedFrames * beatClock.getBeatsPerFrame(); if (positionDifference > threshold) impl.playheadMoved_ = true; - impl.resources_.beatClock.setTimePosition(delay, newPosition); + beatClock.setTimePosition(delay, newPosition); } void Synth::playbackState(int delay, int playbackState) @@ -1433,7 +1481,7 @@ void Synth::playbackState(int delay, int playbackState) Impl& impl = *impl_; ScopedTiming logger { impl.dispatchDuration_, ScopedTiming::Operation::addToDuration }; - impl.resources_.beatClock.setPlaying(delay, playbackState == 1); + impl.resources_.getBeatClock().setPlaying(delay, playbackState == 1); } int Synth::getNumRegions() const noexcept @@ -1457,7 +1505,7 @@ int Synth::getNumMasters() const noexcept int Synth::getNumCurves() const noexcept { Impl& impl = *impl_; - return static_cast(impl.resources_.curves.getNumCurves()); + return static_cast(impl.resources_.getCurves().getNumCurves()); } std::string Synth::exportMidnam(absl::string_view model) const @@ -1639,17 +1687,18 @@ const std::vector& Synth::getUnknownOpcodes() const noexcept size_t Synth::getNumPreloadedSamples() const noexcept { Impl& impl = *impl_; - return impl.resources_.filePool.getNumPreloadedSamples(); + return impl.resources_.getFilePool().getNumPreloadedSamples(); } int Synth::getSampleQuality(ProcessMode mode) { Impl& impl = *impl_; + SynthConfig& synthConfig = impl.resources_.getSynthConfig(); switch (mode) { case ProcessLive: - return impl.resources_.synthConfig.liveSampleQuality; + return synthConfig.liveSampleQuality; case ProcessFreewheeling: - return impl.resources_.synthConfig.freeWheelingSampleQuality; + return synthConfig.freeWheelingSampleQuality; default: SFIZZ_CHECK(false); return 0; @@ -1661,13 +1710,14 @@ void Synth::setSampleQuality(ProcessMode mode, int quality) SFIZZ_CHECK(quality >= 0 && quality <= 10); Impl& impl = *impl_; quality = clamp(quality, 0, 10); + SynthConfig& synthConfig = impl.resources_.getSynthConfig(); switch (mode) { case ProcessLive: - impl.resources_.synthConfig.liveSampleQuality = quality; + synthConfig.liveSampleQuality = quality; break; case ProcessFreewheeling: - impl.resources_.synthConfig.freeWheelingSampleQuality = quality; + synthConfig.freeWheelingSampleQuality = quality; break; default: SFIZZ_CHECK(false); @@ -1678,11 +1728,12 @@ void Synth::setSampleQuality(ProcessMode mode, int quality) int Synth::getOscillatorQuality(ProcessMode mode) { Impl& impl = *impl_; + SynthConfig& synthConfig = impl.resources_.getSynthConfig(); switch (mode) { case ProcessLive: - return impl.resources_.synthConfig.liveOscillatorQuality; + return synthConfig.liveOscillatorQuality; case ProcessFreewheeling: - return impl.resources_.synthConfig.freeWheelingOscillatorQuality; + return synthConfig.freeWheelingOscillatorQuality; default: SFIZZ_CHECK(false); return 0; @@ -1694,13 +1745,14 @@ void Synth::setOscillatorQuality(ProcessMode mode, int quality) SFIZZ_CHECK(quality >= 0 && quality <= 3); Impl& impl = *impl_; quality = clamp(quality, 0, 3); + SynthConfig& synthConfig = impl.resources_.getSynthConfig(); switch (mode) { case ProcessLive: - impl.resources_.synthConfig.liveOscillatorQuality = quality; + synthConfig.liveOscillatorQuality = quality; break; case ProcessFreewheeling: - impl.resources_.synthConfig.freeWheelingOscillatorQuality = quality; + synthConfig.freeWheelingOscillatorQuality = quality; break; default: SFIZZ_CHECK(false); @@ -1771,7 +1823,7 @@ void Synth::Impl::applySettingsPerVoice() void Synth::Impl::setupModMatrix() { - ModMatrix& mm = resources_.modMatrix; + ModMatrix& mm = resources_.getModMatrix(); for (const LayerPtr& layerPtr : layers_) { const Region& region = layerPtr->getRegion(); @@ -1852,51 +1904,57 @@ void Synth::Impl::setupModMatrix() void Synth::setPreloadSize(uint32_t preloadSize) noexcept { Impl& impl = *impl_; + FilePool& filePool = impl.resources_.getFilePool(); // fast path - if (preloadSize == impl.resources_.filePool.getPreloadSize()) + if (preloadSize == filePool.getPreloadSize()) return; - impl.resources_.filePool.setPreloadSize(preloadSize); + filePool.setPreloadSize(preloadSize); } uint32_t Synth::getPreloadSize() const noexcept { Impl& impl = *impl_; - return impl.resources_.filePool.getPreloadSize(); + return impl.resources_.getFilePool().getPreloadSize(); } void Synth::enableFreeWheeling() noexcept { Impl& impl = *impl_; - if (!impl.resources_.synthConfig.freeWheeling) { - impl.resources_.synthConfig.freeWheeling = true; + SynthConfig& synthConfig = impl.resources_.getSynthConfig(); + if (!synthConfig.freeWheeling) { + synthConfig.freeWheeling = true; DBG("Enabling freewheeling"); } } void Synth::disableFreeWheeling() noexcept { Impl& impl = *impl_; - if (impl.resources_.synthConfig.freeWheeling) { - impl.resources_.synthConfig.freeWheeling = false; + SynthConfig& synthConfig = impl.resources_.getSynthConfig(); + if (synthConfig.freeWheeling) { + synthConfig.freeWheeling = false; DBG("Disabling freewheeling"); } } void Synth::Impl::resetAllControllers(int delay) noexcept { - resources_.midiState.resetAllControllers(delay); + MidiState& midiState = resources_.getMidiState(); + midiState.pitchBendEvent(delay, 0.0f); + for (int cc = 0; cc < config::numCCs; ++cc) + midiState.ccEvent(delay, cc, defaultCCValues_[cc]); for (auto& voice : voiceManager_) { voice.registerPitchWheel(delay, 0); for (int cc = 0; cc < config::numCCs; ++cc) - voice.registerCC(delay, cc, 0.0f); + voice.registerCC(delay, cc, defaultCCValues_[cc]); } for (const LayerPtr& layerPtr : layers_) { Layer& layer = *layerPtr; for (int cc = 0; cc < config::numCCs; ++cc) - layer.registerCC(cc, 0.0f); + layer.updateCCState(cc, defaultCCValues_[cc]); } } @@ -1932,19 +1990,19 @@ bool Synth::shouldReloadFile() bool Synth::shouldReloadScala() { Impl& impl = *impl_; - return impl.resources_.tuning.shouldReloadScala(); + return impl.resources_.getTuning().shouldReloadScala(); } void Synth::enableLogging(absl::string_view prefix) noexcept { Impl& impl = *impl_; - impl.resources_.logger.enableLogging(prefix); + impl.resources_.getLogger().enableLogging(prefix); } void Synth::disableLogging() noexcept { Impl& impl = *impl_; - impl.resources_.logger.disableLogging(); + impl.resources_.getLogger().disableLogging(); } void Synth::allSoundOff() noexcept @@ -1956,6 +2014,18 @@ void Synth::allSoundOff() noexcept effectBus->clear(); } +void Synth::addExternalDefinition(const std::string& id, const std::string& value) +{ + Impl& impl = *impl_; + impl.parser_.addExternalDefinition(id, value); +} + +void Synth::clearExternalDefinitions() +{ + Impl& impl = *impl_; + impl.parser_.clearExternalDefinitions(); +} + const BitArray& Synth::getUsedCCs() const noexcept { Impl& impl = *impl_; @@ -1983,6 +2053,7 @@ void Synth::Impl::collectUsedCCsFromRegion(BitArray& usedCCs, co collectUsedCCsFromCCMap(usedCCs, region.amplitudeEG.ccHold); collectUsedCCsFromCCMap(usedCCs, region.amplitudeEG.ccStart); collectUsedCCsFromCCMap(usedCCs, region.amplitudeEG.ccSustain); + if (region.pitchEG) { collectUsedCCsFromCCMap(usedCCs, region.pitchEG->ccAttack); collectUsedCCsFromCCMap(usedCCs, region.pitchEG->ccRelease); @@ -1992,6 +2063,7 @@ void Synth::Impl::collectUsedCCsFromRegion(BitArray& usedCCs, co collectUsedCCsFromCCMap(usedCCs, region.pitchEG->ccStart); collectUsedCCsFromCCMap(usedCCs, region.pitchEG->ccSustain); } + if (region.filterEG) { collectUsedCCsFromCCMap(usedCCs, region.filterEG->ccAttack); collectUsedCCsFromCCMap(usedCCs, region.filterEG->ccRelease); @@ -2001,11 +2073,18 @@ void Synth::Impl::collectUsedCCsFromRegion(BitArray& usedCCs, co collectUsedCCsFromCCMap(usedCCs, region.filterEG->ccStart); collectUsedCCsFromCCMap(usedCCs, region.filterEG->ccSustain); } + for (const LFODescription& lfo : region.lfos) { collectUsedCCsFromCCMap(usedCCs, lfo.phaseCC); collectUsedCCsFromCCMap(usedCCs, lfo.delayCC); collectUsedCCsFromCCMap(usedCCs, lfo.fadeCC); } + for (const FlexEGDescription& flexEG : region.flexEGs) { + for (const FlexEGPoint& point : flexEG.points) { + collectUsedCCsFromCCMap(usedCCs, point.ccTime); + collectUsedCCsFromCCMap(usedCCs, point.ccLevel); + } + } collectUsedCCsFromCCMap(usedCCs, region.ccConditions); collectUsedCCsFromCCMap(usedCCs, region.ccTriggers); collectUsedCCsFromCCMap(usedCCs, region.crossfadeCCInRange); @@ -2037,9 +2116,12 @@ void Synth::Impl::collectUsedCCsFromModulations(BitArray& usedCC BitArray Synth::Impl::collectAllUsedCCs() { BitArray used; - for (const LayerPtr& layerPtr : layers_) + for (const LayerPtr& layerPtr : layers_) { collectUsedCCsFromRegion(used, layerPtr->getRegion()); - collectUsedCCsFromModulations(used, resources_.modMatrix); + sustainOrSostenuto_.set(layerPtr->region_.sustainCC); + sustainOrSostenuto_.set(layerPtr->region_.sostenutoCC); + } + collectUsedCCsFromModulations(used, resources_.getModMatrix()); return used; } diff --git a/src/sfizz/Synth.h b/src/sfizz/Synth.h index 0722ecd46..3ffaedab3 100644 --- a/src/sfizz/Synth.h +++ b/src/sfizz/Synth.h @@ -10,7 +10,6 @@ #include "Messaging.h" #include "utility/NumericId.h" #include "utility/LeakDetector.h" -#include "parser/Parser.h" #include #include #include @@ -22,6 +21,7 @@ template class BitArray; namespace sfz { // Forward declarations for the introspection methods +class Parser; class RegionSet; class PolyphonyGroup; class EffectBus; @@ -29,6 +29,9 @@ struct Region; struct Layer; class Voice; +using CCNamePair = std::pair; +using NoteNamePair = std::pair; + /** * @brief This class is the core of the sfizz library. In C++ it is the main point * of entry and in C the interface basically maps the functions of the class into @@ -642,6 +645,18 @@ class Synth final { */ void allSoundOff() noexcept; + /** + * @brief Add external definitions prior to loading. + * + */ + void addExternalDefinition(const std::string& id, const std::string& value); + + /** + * @brief Clears external definitions for the next file loading. + * + */ + void clearExternalDefinitions(); + /** * @brief Get the parser. * diff --git a/src/sfizz/SynthConfig.h b/src/sfizz/SynthConfig.h index dbd9414b5..f5f38d89c 100644 --- a/src/sfizz/SynthConfig.h +++ b/src/sfizz/SynthConfig.h @@ -28,5 +28,7 @@ struct SynthConfig { return freeWheeling ? freeWheelingOscillatorQuality : liveOscillatorQuality; } + + bool sustainCancelsRelease { Default::sustainCancelsRelease }; }; } diff --git a/src/sfizz/SynthMessaging.cpp b/src/sfizz/SynthMessaging.cpp index 14353f88d..e2ff06f9a 100644 --- a/src/sfizz/SynthMessaging.cpp +++ b/src/sfizz/SynthMessaging.cpp @@ -5,6 +5,9 @@ // If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz #include "SynthPrivate.h" +#include "FilePool.h" +#include "Curve.h" +#include "MidiState.h" #include "utility/StringViewHelpers.h" #include #include @@ -48,6 +51,16 @@ void sfz::Synth::dispatchMessage(Client& client, int delay, const char* path, co break; \ const auto& lfo = region.lfos[idx]; + #define GET_EG_OR_BREAK(idx) \ + if (idx >= region.flexEGs.size()) \ + break; \ + auto& eg = region.flexEGs[idx]; + + #define GET_EG_POINT_OR_BREAK(idx) \ + if (idx >= eg.points.size()) \ + break; \ + auto& point = eg.points[idx]; + MATCH("/hello", "") { client.receive(delay, "/hello", "", nullptr); } break; @@ -67,11 +80,19 @@ void sfz::Synth::dispatchMessage(Client& client, int delay, const char* path, co } break; MATCH("/num_curves", "") { - client.receive<'i'>(delay, path, int(impl.resources_.curves.getNumCurves())); + client.receive<'i'>(delay, path, int(impl.resources_.getCurves().getNumCurves())); } break; MATCH("/num_samples", "") { - client.receive<'i'>(delay, path, int(impl.resources_.filePool.getNumPreloadedSamples())); + client.receive<'i'>(delay, path, int(impl.resources_.getFilePool().getNumPreloadedSamples())); + } break; + + MATCH("/octave_offset", "") { + client.receive<'i'>(delay, path, impl.octaveOffset_); + } break; + + MATCH("/note_offset", "") { + client.receive<'i'>(delay, path, impl.noteOffset_); } break; //---------------------------------------------------------------------- @@ -139,13 +160,13 @@ void sfz::Synth::dispatchMessage(Client& client, int delay, const char* path, co if (indices[0] >= config::numCCs) break; // Note: result value is not frame-exact - client.receive<'f'>(delay, path, impl.resources_.midiState.getCCValue(indices[0])); + client.receive<'f'>(delay, path, impl.resources_.getMidiState().getCCValue(indices[0])); } break; MATCH("/cc&/value", "f") { if (indices[0] >= config::numCCs) break; - impl.resources_.midiState.ccEvent(delay, indices[0], args[0].f); + impl.resources_.getMidiState().ccEvent(delay, indices[0], args[0].f); } break; MATCH("/cc&/label", "") { @@ -167,6 +188,13 @@ void sfz::Synth::dispatchMessage(Client& client, int delay, const char* path, co client.receive<'b'>(delay, path, &blob); } break; + MATCH("/sustain_or_sostenuto/slots", "") { + const BitArray<128>& sustainOrSostenuto = impl.sustainOrSostenuto_; + sfizz_blob_t blob { sustainOrSostenuto.data(), + static_cast(sustainOrSostenuto.byte_size()) }; + client.receive<'b'>(delay, path, &blob); + } break; + //---------------------------------------------------------------------- MATCH("/mem/buffers", "") { @@ -769,6 +797,26 @@ void sfz::Synth::dispatchMessage(Client& client, int delay, const char* path, co client.receive<'f'>(delay, path, region.ampVeltrack * 100.0f); } break; + MATCH("/region&/amp_veltrack_cc&", "") { + GET_REGION_OR_BREAK(indices[0]) + if (region.ampVeltrackCC.contains(indices[1])) { + const auto& cc = region.ampVeltrackCC.getWithDefault(indices[1]); + client.receive<'f'>(delay, path, cc.modifier * 100.0f); + } else { + client.receive<'N'>(delay, path, {}); + } + } break; + + MATCH("/region&/amp_veltrack_curvecc&", "") { + GET_REGION_OR_BREAK(indices[0]) + if (region.ampVeltrackCC.contains(indices[1])) { + const auto& cc = region.ampVeltrackCC.getWithDefault(indices[1]); + client.receive<'i'>(delay, path, cc.curve ); + } else { + client.receive<'N'>(delay, path, {}); + } + } break; + MATCH("/region&/amp_random", "") { GET_REGION_OR_BREAK(indices[0]) client.receive<'f'>(delay, path, region.ampRandom); @@ -908,6 +956,26 @@ void sfz::Synth::dispatchMessage(Client& client, int delay, const char* path, co client.receive<'i'>(delay, path, region.pitchVeltrack); } break; + MATCH("/region&/pitch_veltrack_cc&", "") { + GET_REGION_OR_BREAK(indices[0]) + if (region.pitchVeltrackCC.contains(indices[1])) { + const auto& cc = region.pitchVeltrackCC.getWithDefault(indices[1]); + client.receive<'f'>(delay, path, cc.modifier); + } else { + client.receive<'N'>(delay, path, {}); + } + } break; + + MATCH("/region&/pitch_veltrack_curvecc&", "") { + GET_REGION_OR_BREAK(indices[0]) + if (region.pitchVeltrackCC.contains(indices[1])) { + const auto& cc = region.pitchVeltrackCC.getWithDefault(indices[1]); + client.receive<'i'>(delay, path, cc.curve ); + } else { + client.receive<'N'>(delay, path, {}); + } + } break; + MATCH("/region&/pitch_random", "") { GET_REGION_OR_BREAK(indices[0]) client.receive<'f'>(delay, path, region.pitchRandom); @@ -1072,6 +1140,33 @@ void sfz::Synth::dispatchMessage(Client& client, int delay, const char* path, co client.receive<'f'>(delay, path, region.amplitudeEG.vel2depth); } break; + MATCH("/region&/ampeg_dynamic", "") { + GET_REGION_OR_BREAK(indices[0]) + if (region.amplitudeEG.dynamic) { + client.receive<'T'>(delay, path, {}); + } else { + client.receive<'F'>(delay, path, {}); + } + } break; + + MATCH("/region&/fileg_dynamic", "") { + GET_REGION_OR_BREAK(indices[0]) + if (region.filterEG && region.filterEG->dynamic) { + client.receive<'T'>(delay, path, {}); + } else { + client.receive<'F'>(delay, path, {}); + } + } break; + + MATCH("/region&/pitcheg_dynamic", "") { + GET_REGION_OR_BREAK(indices[0]) + if (region.pitchEG && region.pitchEG->dynamic) { + client.receive<'T'>(delay, path, {}); + } else { + client.receive<'F'>(delay, path, {}); + } + } break; + MATCH("/region&/note_polyphony", "") { GET_REGION_OR_BREAK(indices[0]) if (region.notePolyphony) { @@ -1264,6 +1359,28 @@ void sfz::Synth::dispatchMessage(Client& client, int delay, const char* path, co client.receive<'i'>(delay, path, filter.veltrack); } break; + MATCH("/region&/filter&/veltrack_cc&", "") { + GET_REGION_OR_BREAK(indices[0]) + GET_FILTER_OR_BREAK(indices[1]) + if (filter.veltrackCC.contains(indices[2])) { + const auto& cc = filter.veltrackCC.getWithDefault(indices[2]); + client.receive<'f'>(delay, path, cc.modifier); + } else { + client.receive<'N'>(delay, path, {}); + } + } break; + + MATCH("/region&/filter&/veltrack_curvecc&", "") { + GET_REGION_OR_BREAK(indices[0]) + GET_FILTER_OR_BREAK(indices[1]) + if (filter.veltrackCC.contains(indices[2])) { + const auto& cc = filter.veltrackCC.getWithDefault(indices[2]); + client.receive<'i'>(delay, path, cc.curve ); + } else { + client.receive<'N'>(delay, path, {}); + } + } break; + MATCH("/region&/filter&/type", "") { GET_REGION_OR_BREAK(indices[0]) GET_FILTER_OR_BREAK(indices[1]) @@ -1349,10 +1466,44 @@ void sfz::Synth::dispatchMessage(Client& client, int delay, const char* path, co client.receive<'i'>(delay, path, static_cast(lfo.sub[0].wave)); } break; + MATCH("/region&/eg&/point&/time", "") { + GET_REGION_OR_BREAK(indices[0]) + GET_EG_OR_BREAK(indices[1]) + GET_EG_POINT_OR_BREAK(indices[2] + 1) + + client.receive<'f'>(delay, path, point.time); + } break; + + MATCH("/region&/eg&/point&/time_cc&", "") { + GET_REGION_OR_BREAK(indices[0]) + GET_EG_OR_BREAK(indices[1]) + GET_EG_POINT_OR_BREAK(indices[2] + 1) + + client.receive<'f'>(delay, path, point.ccTime.getWithDefault(indices[3])); + } break; + + MATCH("/region&/eg&/point&/level", "") { + GET_REGION_OR_BREAK(indices[0]) + GET_EG_OR_BREAK(indices[1]) + GET_EG_POINT_OR_BREAK(indices[2] + 1) + + client.receive<'f'>(delay, path, point.level); + } break; + + MATCH("/region&/eg&/point&/level_cc&", "") { + GET_REGION_OR_BREAK(indices[0]) + GET_EG_OR_BREAK(indices[1]) + GET_EG_POINT_OR_BREAK(indices[2] + 1) + + client.receive<'f'>(delay, path, point.ccLevel.getWithDefault(indices[3])); + } break; + #undef GET_REGION_OR_BREAK #undef GET_FILTER_OR_BREAK #undef GET_EQ_OR_BREAK #undef GET_LFO_OR_BREAK + #undef GET_EG_OR_BREAK + #undef GET_EG_POINT_OR_BREAK //---------------------------------------------------------------------- // Setting values @@ -1454,6 +1605,16 @@ void sfz::Synth::dispatchMessage(Client& client, int delay, const char* path, co } break; + MATCH("/voice&/remaining_delay", "") { + GET_VOICE_OR_BREAK(indices[0]) + client.receive<'i'>(delay, path, voice.getRemainingDelay()); + } break; + + MATCH("/voice&/source_position", "") { + GET_VOICE_OR_BREAK(indices[0]) + client.receive<'i'>(delay, path, voice.getSourcePosition()); + } break; + #undef MATCH // TODO... } diff --git a/src/sfizz/SynthPrivate.h b/src/sfizz/SynthPrivate.h index 157bba4b2..44fd3b542 100644 --- a/src/sfizz/SynthPrivate.h +++ b/src/sfizz/SynthPrivate.h @@ -13,6 +13,8 @@ #include "modulations/sources/ChannelAftertouch.h" #include "modulations/sources/PolyAftertouch.h" #include "modulations/sources/LFO.h" +#include "parser/Parser.h" +#include "parser/ParserListener.h" namespace sfz { @@ -246,6 +248,7 @@ struct Synth::Impl final: public Parser::Listener { std::map keyLabelsMap_; BitArray<128> keySlots_; BitArray<128> swLastSlots_; + BitArray<128> sustainOrSostenuto_; std::vector keyswitchLabels_; std::map keyswitchLabelsMap_; @@ -325,7 +328,7 @@ struct Synth::Impl final: public Parser::Listener { Parser parser_; absl::optional modificationTime_ { }; - std::array defaultCCValues_; + std::array defaultCCValues_ { }; BitArray currentUsedCCs_; BitArray changedCCsThisCycle_; BitArray changedCCsLastCycle_; diff --git a/src/sfizz/Voice.cpp b/src/sfizz/Voice.cpp index 64e6589a9..f08def4e0 100644 --- a/src/sfizz/Voice.cpp +++ b/src/sfizz/Voice.cpp @@ -16,6 +16,7 @@ #include "LFO.h" #include "MathHelpers.h" #include "ModifierHelpers.h" +#include "RegionStateful.h" #include "TriggerEvent.h" #include "modulations/ModId.h" #include "modulations/ModKey.h" @@ -26,6 +27,11 @@ #include "SfzHelpers.h" #include "SIMDHelpers.h" #include "Smoothers.h" +#include "FilePool.h" +#include "Wavetables.h" +#include "Tuning.h" +#include "BufferPool.h" +#include "SynthConfig.h" #include "utility/Macros.h" #include #include @@ -216,6 +222,7 @@ struct Voice::Impl State state_ { State::idle }; bool noteIsOff_ { false }; + bool offed_ { false }; enum class SustainState { Up, Sustaining }; SustainState sustainState_ { SustainState::Up }; enum class SostenutoState { Up, Sustaining, PreviouslyDown }; @@ -265,7 +272,7 @@ struct Voice::Impl std::unique_ptr lfoPitch_; std::unique_ptr lfoFilter_; - ADSREnvelope egAmplitude_; + ADSREnvelope egAmplitude_ { resources_.getMidiState() }; std::unique_ptr egPitch_; std::unique_ptr egFilter_; @@ -304,8 +311,6 @@ struct Voice::Impl PowerFollower powerFollower_; ExtendedCCValues extendedCCValues_; - fast_real_distribution unipolarDist { 0.0f, 1.0f }; - fast_real_distribution bipolarDist { -1.0f, 1.0f }; }; Voice::Voice(int voiceNumber, Resources& resources) @@ -389,10 +394,11 @@ const ExtendedCCValues& Voice::getExtendedCCValues() const noexcept void Voice::Impl::updateExtendedCCValues() noexcept { - extendedCCValues_.unipolar = unipolarDist(Random::randomGenerator); - extendedCCValues_.bipolar = bipolarDist(Random::randomGenerator); - extendedCCValues_.alternate = resources_.midiState.getAlternateState(); - extendedCCValues_.noteGate = resources_.midiState.getActiveNotes() > 0 ? 1.0f : 0.0f; + MidiState& midiState = resources_.getMidiState(); + extendedCCValues_.unipolar = midiState.getCCValue(ExtendedCCs::unipolarRandom); + extendedCCValues_.bipolar = midiState.getCCValue(ExtendedCCs::bipolarRandom); + extendedCCValues_.alternate = midiState.getCCValue(ExtendedCCs::alternate); + extendedCCValues_.noteGate = midiState.getCCValue(ExtendedCCs::keyboardNoteGate); } bool Voice::startVoice(Layer* layer, int delay, const TriggerEvent& event) noexcept @@ -401,6 +407,8 @@ bool Voice::startVoice(Layer* layer, int delay, const TriggerEvent& event) noexc ASSERT(event.value >= 0.0f && event.value <= 1.0f); Resources& resources = impl.resources_; + MidiState& midiState = resources.getMidiState(); + CurveSet& curveSet = resources.getCurves(); const Region& region = layer->getRegion(); impl.region_ = ®ion; @@ -410,7 +418,7 @@ bool Voice::startVoice(Layer* layer, int delay, const TriggerEvent& event) noexc impl.triggerEvent_.number = region.pitchKeycenter; if (region.velocityOverride == VelocityOverride::previous) - impl.triggerEvent_.value = resources.midiState.getLastVelocity(); + impl.triggerEvent_.value = midiState.getVelocityOverride(); if (region.disabled()) { impl.switchState(State::cleanMeUp); @@ -426,26 +434,27 @@ bool Voice::startVoice(Layer* layer, int delay, const TriggerEvent& event) noexc delay = 0; if (region.isOscillator()) { + WavetablePool& wavePool = resources.getWavePool(); const WavetableMulti* wave = nullptr; if (!region.isGenerator()) - wave = resources.wavePool.getFileWave(region.sampleId->filename()); + wave = wavePool.getFileWave(region.sampleId->filename()); else { switch (hash(region.sampleId->filename())) { default: case hash("*silence"): break; case hash("*sine"): - wave = resources.wavePool.getWaveSin(); + wave = wavePool.getWaveSin(); break; case hash("*triangle"): // fallthrough case hash("*tri"): - wave = resources.wavePool.getWaveTriangle(); + wave = wavePool.getWaveTriangle(); break; case hash("*square"): - wave = resources.wavePool.getWaveSquare(); + wave = wavePool.getWaveSquare(); break; case hash("*saw"): - wave = resources.wavePool.getWaveSaw(); + wave = wavePool.getWaveSaw(); break; } } @@ -459,30 +468,32 @@ bool Voice::startVoice(Layer* layer, int delay, const TriggerEvent& event) noexc } impl.setupOscillatorUnison(); } else { - impl.currentPromise_ = resources.filePool.getFilePromise(region.sampleId); + FilePool& filePool = resources.getFilePool(); + impl.currentPromise_ = filePool.getFilePromise(region.sampleId); if (!impl.currentPromise_) { impl.switchState(State::cleanMeUp); return false; } impl.updateLoopInformation(); impl.speedRatio_ = static_cast(impl.currentPromise_->information.sampleRate / impl.sampleRate_); - impl.sourcePosition_ = region.getOffset(resources.midiState); + impl.sourcePosition_ = sampleOffset(region, midiState); } // do Scala retuning and reconvert the frequency into a 12TET key number - const float numberRetuned = resources.tuning.getKeyFractional12TET(impl.triggerEvent_.number); + Tuning& tuning = resources.getTuning(); + const float numberRetuned = tuning.getKeyFractional12TET(impl.triggerEvent_.number); - impl.pitchRatio_ = region.getBasePitchVariation(numberRetuned, impl.triggerEvent_.value); + impl.pitchRatio_ = basePitchVariation(region, numberRetuned, impl.triggerEvent_.value, midiState, curveSet); // apply stretch tuning if set - if (resources.stretch) - impl.pitchRatio_ *= resources.stretch->getRatioForFractionalKey(numberRetuned); + if (absl::optional& stretch = resources.getStretch()) + impl.pitchRatio_ *= stretch->getRatioForFractionalKey(numberRetuned); impl.pitchKeycenter_ = region.pitchKeycenter; - impl.baseVolumedB_ = region.getBaseVolumedB(resources.midiState, impl.triggerEvent_.number); + impl.baseVolumedB_ = baseVolumedB(region, midiState, impl.triggerEvent_.number); impl.baseGain_ = region.getBaseGain(); if (impl.triggerEvent_.type != TriggerEventType::CC || region.velocityOverride == VelocityOverride::previous) - impl.baseGain_ *= region.getNoteGain(impl.triggerEvent_.number, impl.triggerEvent_.value); + impl.baseGain_ *= noteGain(region, impl.triggerEvent_.number, impl.triggerEvent_.value, midiState, curveSet); impl.gainSmoother_.reset(); impl.resetCrossfades(); @@ -496,26 +507,27 @@ bool Voice::startVoice(Layer* layer, int delay, const TriggerEvent& event) noexc } impl.triggerDelay_ = delay; - impl.initialDelay_ = delay + static_cast(region.getDelay(resources.midiState) * impl.sampleRate_); - impl.baseFrequency_ = resources.tuning.getFrequencyOfKey(impl.triggerEvent_.number); - impl.sampleEnd_ = int(region.getSampleEnd(resources.midiState)); + impl.initialDelay_ = delay + static_cast(regionDelay(region, midiState) * impl.sampleRate_); + impl.baseFrequency_ = tuning.getFrequencyOfKey(impl.triggerEvent_.number); + impl.sampleEnd_ = int(sampleEnd(region, midiState)); impl.sampleSize_ = impl.sampleEnd_- impl.sourcePosition_ - 1; impl.bendSmoother_.setSmoothing(region.bendSmooth, impl.sampleRate_); - impl.bendSmoother_.reset(region.getBendInCents(resources.midiState.getPitchBend())); + impl.bendSmoother_.reset(region.getBendInCents(midiState.getPitchBend())); - resources.modMatrix.initVoice(impl.id_, region.getId(), impl.initialDelay_); + ModMatrix& modMatrix = resources.getModMatrix(); + modMatrix.initVoice(impl.id_, region.getId(), impl.initialDelay_); impl.saveModulationTargets(®ion); if (region.checkSustain) { const bool sustainPressed = - resources.midiState.getCCValue(region.sustainCC) >= region.sustainThreshold; + midiState.getCCValue(region.sustainCC) >= region.sustainThreshold; impl.sustainState_ = sustainPressed ? Impl::SustainState::Sustaining : Impl::SustainState::Up; } if (region.checkSostenuto) { const bool sostenutoPressed = - resources.midiState.getCCValue(region.sostenutoCC) >= region.sostenutoThreshold; + midiState.getCCValue(region.sostenutoCC) >= region.sostenutoThreshold; impl.sostenutoState_ = sostenutoPressed ? Impl::SostenutoState::PreviouslyDown : Impl::SostenutoState::Up; } @@ -526,7 +538,7 @@ bool Voice::startVoice(Layer* layer, int delay, const TriggerEvent& event) noexc int Voice::Impl::getCurrentSampleQuality() const noexcept { return (region_ && region_->sampleQuality) ? - *region_->sampleQuality : resources_.synthConfig.currentSampleQuality(); + *region_->sampleQuality : resources_.getSynthConfig().currentSampleQuality(); } int Voice::getCurrentSampleQuality() const noexcept @@ -538,7 +550,7 @@ int Voice::getCurrentSampleQuality() const noexcept int Voice::Impl::getCurrentOscillatorQuality() const noexcept { return (region_ && region_->oscillatorQuality) ? - *region_->oscillatorQuality : resources_.synthConfig.currentOscillatorQuality(); + *region_->oscillatorQuality : resources_.getSynthConfig().currentOscillatorQuality(); } int Voice::getCurrentOscillatorQuality() const noexcept @@ -573,7 +585,8 @@ void Voice::Impl::release(int delay) noexcept switchState(State::cleanMeUp); } - resources_.modMatrix.releaseVoice(id_, region_->getId(), delay); + ModMatrix& modMatrix = resources_.getModMatrix(); + modMatrix.releaseVoice(id_, region_->getId(), delay); } void Voice::off(int delay, bool fast) noexcept @@ -595,6 +608,7 @@ void Voice::Impl::off(int delay, bool fast) noexcept // TODO(jpc): Flex AmpEG } + offed_ = true; release(delay); } @@ -666,6 +680,13 @@ void Voice::registerCC(int delay, int ccNumber, float ccValue) noexcept if (impl.noteIsOff_ && region.loopMode != LoopMode::one_shot && sostenutoPedalReleaseCondition && sustainPedalReleaseCondition) release(delay); + + if (region.checkSustain && (impl.sustainState_ == Impl::SustainState::Sustaining) + && impl.resources_.getSynthConfig().sustainCancelsRelease + && impl.released() && (region.trigger != Trigger::release && region.trigger != Trigger::release_key) ) { + ModMatrix& modMatrix = impl.resources_.getModMatrix(); + modMatrix.cancelRelease(impl.id_, impl.region_->getId(), delay); + } } void Voice::registerPitchWheel(int delay, float pitch) noexcept @@ -807,13 +828,15 @@ void Voice::Impl::resetCrossfades() noexcept float xfadeValue { 1.0f }; const auto xfCurve = region_->crossfadeCCCurve; + MidiState& midiState = resources_.getMidiState(); + for (const auto& mod : region_->crossfadeCCInRange) { - const auto value = resources_.midiState.getCCValue(mod.cc); + const auto value = midiState.getCCValue(mod.cc); xfadeValue *= crossfadeIn(mod.data, value, xfCurve); } for (const auto& mod : region_->crossfadeCCOutRange) { - const auto value = resources_.midiState.getCCValue(mod.cc); + const auto value = midiState.getCCValue(mod.cc); xfadeValue *= crossfadeOut(mod.data, value, xfCurve); } @@ -825,8 +848,11 @@ void Voice::Impl::applyCrossfades(absl::Span modulationSpan) noexcept const auto numSamples = modulationSpan.size(); const auto xfCurve = region_->crossfadeCCCurve; - auto tempSpan = resources_.bufferPool.getBuffer(numSamples); - auto xfadeSpan = resources_.bufferPool.getBuffer(numSamples); + MidiState& midiState = resources_.getMidiState(); + BufferPool& bufferPool = resources_.getBufferPool(); + + auto tempSpan = bufferPool.getBuffer(numSamples); + auto xfadeSpan = bufferPool.getBuffer(numSamples); if (!tempSpan || !xfadeSpan) return; @@ -835,7 +861,7 @@ void Voice::Impl::applyCrossfades(absl::Span modulationSpan) noexcept bool canShortcut = true; for (const auto& mod : region_->crossfadeCCInRange) { - const auto& events = resources_.midiState.getCCEvents(mod.cc); + const auto& events = midiState.getCCEvents(mod.cc); canShortcut &= (events.size() == 1); linearEnvelope(events, *tempSpan, [&](float x) { return crossfadeIn(mod.data, x, xfCurve); @@ -844,7 +870,7 @@ void Voice::Impl::applyCrossfades(absl::Span modulationSpan) noexcept } for (const auto& mod : region_->crossfadeCCOutRange) { - const auto& events = resources_.midiState.getCCEvents(mod.cc); + const auto& events = midiState.getCCEvents(mod.cc); canShortcut &= (events.size() == 1); linearEnvelope(events, *tempSpan, [&](float x) { return crossfadeOut(mod.data, x, xfCurve); @@ -861,7 +887,7 @@ void Voice::Impl::amplitudeEnvelope(absl::Span modulationSpan) noexcept { const auto numSamples = modulationSpan.size(); - ModMatrix& mm = resources_.modMatrix; + ModMatrix& mm = resources_.getModMatrix(); // Amplitude EG absl::Span ampegOut(mm.getModulation(masterAmplitudeTarget_), numSamples); @@ -893,7 +919,9 @@ void Voice::Impl::ampStageMono(AudioSpan buffer) noexcept const auto numSamples = buffer.getNumFrames(); const auto leftBuffer = buffer.getSpan(0); - auto modulationSpan = resources_.bufferPool.getBuffer(numSamples); + BufferPool& bufferPool = resources_.getBufferPool(); + + auto modulationSpan = bufferPool.getBuffer(numSamples); if (!modulationSpan) return; @@ -906,8 +934,10 @@ void Voice::Impl::ampStageStereo(AudioSpan buffer) noexcept { ScopedTiming logger { amplitudeDuration_ }; + BufferPool& bufferPool = resources_.getBufferPool(); + const auto numSamples = buffer.getNumFrames(); - auto modulationSpan = resources_.bufferPool.getBuffer(numSamples); + auto modulationSpan = bufferPool.getBuffer(numSamples); if (!modulationSpan) return; @@ -924,11 +954,13 @@ void Voice::Impl::panStageMono(AudioSpan buffer) noexcept const auto leftBuffer = buffer.getSpan(0); const auto rightBuffer = buffer.getSpan(1); - auto modulationSpan = resources_.bufferPool.getBuffer(numSamples); + BufferPool& bufferPool = resources_.getBufferPool(); + + auto modulationSpan = bufferPool.getBuffer(numSamples); if (!modulationSpan) return; - ModMatrix& mm = resources_.modMatrix; + ModMatrix& mm = resources_.getModMatrix(); // Prepare for stereo output copy(leftBuffer, rightBuffer); @@ -949,11 +981,13 @@ void Voice::Impl::panStageStereo(AudioSpan buffer) noexcept const auto leftBuffer = buffer.getSpan(0); const auto rightBuffer = buffer.getSpan(1); - auto modulationSpan = resources_.bufferPool.getBuffer(numSamples); + BufferPool& bufferPool = resources_.getBufferPool(); + + auto modulationSpan = bufferPool.getBuffer(numSamples); if (!modulationSpan) return; - ModMatrix& mm = resources_.modMatrix; + ModMatrix& mm = resources_.getModMatrix(); // Apply panning fill(*modulationSpan, region_->pan); @@ -1031,15 +1065,18 @@ void Voice::Impl::fillWithData(AudioSpan buffer) noexcept auto source = currentPromise_->getData(); + BufferPool& bufferPool = resources_.getBufferPool(); + const CurveSet& curves = resources_.getCurves(); + // calculate interpolation data // indices: integral position in the source audio // coeffs: fractional position normalized 0-1 - auto coeffs = resources_.bufferPool.getBuffer(numSamples); - auto indices = resources_.bufferPool.getIndexBuffer(numSamples); + auto coeffs = bufferPool.getBuffer(numSamples); + auto indices = bufferPool.getIndexBuffer(numSamples); if (!indices || !coeffs) return; { - auto jumps = resources_.bufferPool.getBuffer(numSamples); + auto jumps = bufferPool.getBuffer(numSamples); if (!jumps) return; @@ -1093,7 +1130,7 @@ void Voice::Impl::fillWithData(AudioSpan buffer) noexcept SpanHolder> partitionBuffers[2]; if (shouldLoop) { for (auto& buf : partitionBuffers) { - buf = resources_.bufferPool.getIndexBuffer(numSamples); + buf = bufferPool.getIndexBuffer(numSamples); if (!buf) return; } @@ -1200,9 +1237,9 @@ void Voice::Impl::fillWithData(AudioSpan buffer) noexcept source, ptBuffer, ptIndices, ptCoeffs, {}, quality); if (ptType == kPartitionLoopXfade) { - auto xfTemp1 = resources_.bufferPool.getBuffer(numSamples); - auto xfTemp2 = resources_.bufferPool.getBuffer(numSamples); - auto xfIndicesTemp = resources_.bufferPool.getIndexBuffer(numSamples); + auto xfTemp1 = bufferPool.getBuffer(numSamples); + auto xfTemp2 = bufferPool.getBuffer(numSamples); + auto xfIndicesTemp = bufferPool.getIndexBuffer(numSamples); if (!xfTemp1 || !xfTemp2 || !xfIndicesTemp) return; @@ -1226,7 +1263,7 @@ void Voice::Impl::fillWithData(AudioSpan buffer) noexcept xfCurve[i] = xfIn.evalNormalized(1.0f - xfCurvePos[i]); } else IF_CONSTEXPR (config::loopXfadeCurve == 1) { - const Curve& xfOut = resources_.curves.getCurve(6); + const Curve& xfOut = curves.getCurve(6); for (unsigned i = 0; i < ptSize; ++i) xfCurve[i] = xfOut.evalNormalized(xfCurvePos[i]); } @@ -1276,7 +1313,7 @@ void Voice::Impl::fillWithData(AudioSpan buffer) noexcept xfCurve[i] = xfIn.evalNormalized(xfInCurvePos[i]); } else IF_CONSTEXPR (config::loopXfadeCurve == 1) { - const Curve& xfIn = resources_.curves.getCurve(5); + const Curve& xfIn = curves.getCurve(5); for (unsigned i = 0; i < applySize; ++i) xfCurve[i] = xfIn.evalNormalized(xfInCurvePos[i]); } @@ -1464,7 +1501,10 @@ void Voice::Impl::fillWithGenerator(AudioSpan buffer) noexcept } else { const size_t numFrames = buffer.getNumFrames(); - auto frequencies = resources_.bufferPool.getBuffer(numFrames); + BufferPool& bufferPool = resources_.getBufferPool(); + ModMatrix& modMatrix = resources_.getModMatrix(); + + auto frequencies = bufferPool.getBuffer(numFrames); if (!frequencies) return; @@ -1477,7 +1517,7 @@ void Voice::Impl::fillWithGenerator(AudioSpan buffer) noexcept for (size_t i = 0; i < numFrames; ++i) (*frequencies)[i] = baseRatio * centsFactor(pitch[i]); - auto detuneSpan = resources_.bufferPool.getBuffer(numFrames); + auto detuneSpan = bufferPool.getBuffer(numFrames); if (!detuneSpan) return; @@ -1487,7 +1527,7 @@ void Voice::Impl::fillWithGenerator(AudioSpan buffer) noexcept if (oscillatorMode <= 0 && oscillatorMulti < 2) { // single oscillator - auto tempSpan = resources_.bufferPool.getBuffer(numFrames); + auto tempSpan = bufferPool.getBuffer(numFrames); if (!tempSpan) return; @@ -1500,13 +1540,13 @@ void Voice::Impl::fillWithGenerator(AudioSpan buffer) noexcept } else if (oscillatorMode <= 0 && oscillatorMulti >= 3) { // unison oscillator - auto tempSpan = resources_.bufferPool.getBuffer(numFrames); - auto tempLeftSpan = resources_.bufferPool.getBuffer(numFrames); - auto tempRightSpan = resources_.bufferPool.getBuffer(numFrames); + auto tempSpan = bufferPool.getBuffer(numFrames); + auto tempLeftSpan = bufferPool.getBuffer(numFrames); + auto tempRightSpan = bufferPool.getBuffer(numFrames); if (!tempSpan || !tempLeftSpan || !tempRightSpan) return; - const float* detuneMod = resources_.modMatrix.getModulation(oscillatorDetuneTarget_); + const float* detuneMod = modMatrix.getModulation(oscillatorDetuneTarget_); for (unsigned u = 0, uSize = waveUnisonSize_; u < uSize; ++u) { WavetableOscillator& osc = waveOscillators_[u]; osc.setQuality(quality); @@ -1533,7 +1573,7 @@ void Voice::Impl::fillWithGenerator(AudioSpan buffer) noexcept } else { // modulated oscillator - auto tempSpan = resources_.bufferPool.getBuffer(numFrames); + auto tempSpan = bufferPool.getBuffer(numFrames); if (!tempSpan) return; @@ -1543,11 +1583,11 @@ void Voice::Impl::fillWithGenerator(AudioSpan buffer) noexcept oscMod.setQuality(quality); // compute the modulator - auto modulatorSpan = resources_.bufferPool.getBuffer(numFrames); + auto modulatorSpan = bufferPool.getBuffer(numFrames); if (!modulatorSpan) return; - const float* detuneMod = resources_.modMatrix.getModulation(oscillatorDetuneTarget_); + const float* detuneMod = modMatrix.getModulation(oscillatorDetuneTarget_); if (!detuneMod) fill(*detuneSpan, waveDetuneRatio_[1]); else { @@ -1562,7 +1602,7 @@ void Voice::Impl::fillWithGenerator(AudioSpan buffer) noexcept const float oscillatorModDepth = region_->oscillatorModDepth; if (oscillatorModDepth != 1.0f) applyGain1(oscillatorModDepth, *modulatorSpan); - const float* modDepthMod = resources_.modMatrix.getModulation(oscillatorModDepthTarget_); + const float* modDepthMod = modMatrix.getModulation(oscillatorModDepthTarget_); if (modDepthMod) applyGain(absl::MakeConstSpan(modDepthMod, numFrames), *modulatorSpan); @@ -1601,8 +1641,18 @@ void Voice::Impl::fillWithGenerator(AudioSpan buffer) noexcept #endif } +bool Voice::released() const noexcept +{ + Impl& impl = *impl_; + return impl.released(); +} + bool Voice::Impl::released() const noexcept { + if (!region_ || state_ != State::playing) + return true; + + if (!region_->flexAmpEG) return egAmplitude_.isReleased(); else @@ -1616,10 +1666,11 @@ bool Voice::checkOffGroup(const Region* other, int delay, int noteNumber) noexce if (region == nullptr || other == nullptr) return false; - if (impl.released()) + if (impl.offed_) return false; - if (impl.triggerEvent_.type == TriggerEventType::NoteOn + if ((impl.triggerEvent_.type == TriggerEventType::NoteOn + || impl.triggerEvent_.type == TriggerEventType::CC) && region->offBy && *region->offBy == other->group && (region->group != other->group || noteNumber != impl.triggerEvent_.number)) { off(delay); @@ -1641,6 +1692,7 @@ void Voice::reset() noexcept impl.floatPositionOffset_ = 0.0f; impl.noteIsOff_ = false; impl.sostenutoState_ = Impl::SostenutoState::Up; + impl.offed_ = false; impl.resetLoopInformation(); @@ -1674,14 +1726,15 @@ void Voice::Impl::updateLoopInformation() noexcept if (!region_->shouldLoop()) return; - Resources& resources = resources_; + const Region& region = *region_; + MidiState& midiState = resources_.getMidiState(); const FileInformation& info = currentPromise_->information; const double rate = info.sampleRate; - loop_.start = static_cast(region_->loopStart(resources.midiState)); - loop_.end = max(static_cast(region_->loopEnd(resources.midiState)), loop_.start); + loop_.start = static_cast(loopStart(region, midiState)); + loop_.end = max(static_cast(loopEnd(region, midiState)), loop_.start); loop_.size = loop_.end + 1 - loop_.start; - loop_.xfSize = static_cast(lroundPositive(region_->loopCrossfade * rate)); + loop_.xfSize = static_cast(lroundPositive(region.loopCrossfade * rate)); // Clamp the crossfade to the part available before the loop starts loop_.xfSize = min(loop_.start, loop_.xfSize); loop_.xfOutStart = loop_.end + 1 - loop_.xfSize; @@ -1719,13 +1772,13 @@ float Voice::getAveragePower() const noexcept return 0.0f; } -bool Voice::releasedOrFree() const noexcept +bool Voice::offedOrFree() const noexcept { Impl& impl = *impl_; if (impl.state_ != State::playing) return true; - return impl.released(); + return impl.offed_; } void Voice::setMaxFiltersPerVoice(size_t numFilters) @@ -1767,10 +1820,12 @@ void Voice::setMaxLFOsPerVoice(size_t numLFOs) void Voice::setMaxFlexEGsPerVoice(size_t numFlexEGs) { Impl& impl = *impl_; + Resources& resources = impl.resources_; + impl.flexEGs_.resize(numFlexEGs); for (size_t i = 0; i < numFlexEGs; ++i) { - auto eg = absl::make_unique(); + auto eg = absl::make_unique(resources); eg->setSampleRate(impl.sampleRate_); impl.flexEGs_[i] = std::move(eg); } @@ -1780,7 +1835,7 @@ void Voice::setPitchEGEnabledPerVoice(bool havePitchEG) { Impl& impl = *impl_; if (havePitchEG) - impl.egPitch_.reset(new ADSREnvelope); + impl.egPitch_.reset(new ADSREnvelope(impl.resources_.getMidiState())); else impl.egPitch_.reset(); } @@ -1789,7 +1844,7 @@ void Voice::setFilterEGEnabledPerVoice(bool haveFilterEG) { Impl& impl = *impl_; if (haveFilterEG) - impl.egFilter_.reset(new ADSREnvelope); + impl.egFilter_.reset(new ADSREnvelope(impl.resources_.getMidiState())); else impl.egFilter_.reset(); } @@ -1908,7 +1963,8 @@ void Voice::Impl::pitchEnvelope(absl::Span pitchSpan) noexcept { const size_t numFrames = pitchSpan.size(); - const EventVector& events = resources_.midiState.getPitchEvents(); + const MidiState& midiState = resources_.getMidiState(); + const EventVector& events = midiState.getPitchEvents(); const auto bendLambda = [this](float bend) { return region_->getBendInCents(bend); }; @@ -1919,7 +1975,7 @@ void Voice::Impl::pitchEnvelope(absl::Span pitchSpan) noexcept linearEnvelope(events, pitchSpan, bendLambda); bendSmoother_.process(pitchSpan, pitchSpan); - ModMatrix& mm = resources_.modMatrix; + ModMatrix& mm = resources_.getModMatrix(); if (float* mod = mm.getModulation(pitchTarget_)) add(absl::MakeSpan(mod, numFrames), pitchSpan); @@ -1933,7 +1989,7 @@ void Voice::Impl::resetSmoothers() noexcept void Voice::Impl::saveModulationTargets(const Region* region) noexcept { - ModMatrix& mm = resources_.modMatrix; + ModMatrix& mm = resources_.getModMatrix(); masterAmplitudeTarget_ = mm.findTarget(ModKey::createNXYZ(ModId::MasterAmplitude, region->getId())); amplitudeTarget_ = mm.findTarget(ModKey::createNXYZ(ModId::Amplitude, region->getId())); volumeTarget_ = mm.findTarget(ModKey::createNXYZ(ModId::Volume, region->getId())); @@ -2000,6 +2056,18 @@ const Region* Voice::getRegion() const noexcept return impl.region_; } +int Voice::getRemainingDelay() const noexcept +{ + Impl& impl = *impl_; + return impl.initialDelay_; +} + +int Voice::getSourcePosition() const noexcept +{ + Impl& impl = *impl_; + return impl.sourcePosition_; +} + LFO* Voice::getLFO(size_t index) { Impl& impl = *impl_; diff --git a/src/sfizz/Voice.h b/src/sfizz/Voice.h index 9e7f3593c..5b8d42c6e 100644 --- a/src/sfizz/Voice.h +++ b/src/sfizz/Voice.h @@ -10,6 +10,7 @@ #include "Region.h" #include "Resources.h" #include "AudioSpan.h" +#include "Logger.h" #include "utility/NumericId.h" #include "utility/LeakDetector.h" #include @@ -205,12 +206,19 @@ class Voice { */ bool isFree() const noexcept; /** - * @brief Can the voice be reused (i.e. is it releasing or free) + * @brief Is the voice released? * * @return true * @return false */ - bool releasedOrFree() const noexcept; + bool released() const noexcept; + /** + * @brief Can the voice be reused (i.e. is it releasing after being killed or free) + * + * @return true + * @return false + */ + bool offedOrFree() const noexcept; /** * @brief Get the event that triggered the voice * @@ -416,6 +424,19 @@ class Voice { */ const ExtendedCCValues& getExtendedCCValues() const noexcept; + /** + * @brief Get the remaining delay before the sample starts, in samples + * + * @return int + */ + int getRemainingDelay() const noexcept; + /** + * @brief Get the current position in the source sample + * + * @return int + */ + int getSourcePosition() const noexcept; + public: /** * @brief Check if the voice already belongs to a sister ring diff --git a/src/sfizz/VoiceManager.cpp b/src/sfizz/VoiceManager.cpp index eb663fd6f..7440f5cf8 100644 --- a/src/sfizz/VoiceManager.cpp +++ b/src/sfizz/VoiceManager.cpp @@ -19,7 +19,7 @@ void VoiceManager::onVoiceStateChanging(NumericId id, Voice::State state) const uint32_t group = region->group; RegionSet::removeVoiceFromHierarchy(region, voice); swapAndPopFirst(activeVoices_, [voice](const Voice* v) { return v == voice; }); - ASSERT(group < polyphonyGroups_.size()); + ASSERT(polyphonyGroups_.contains(group)); polyphonyGroups_[group].removeVoice(voice); } else if (state == Voice::State::playing) { Voice* voice = getVoiceById(id); @@ -27,7 +27,7 @@ void VoiceManager::onVoiceStateChanging(NumericId id, Voice::State state) const uint32_t group = region->group; activeVoices_.push_back(voice); RegionSet::registerVoiceInHierarchy(region, voice); - ASSERT(group < polyphonyGroups_.size()); + ASSERT(polyphonyGroups_.contains(group)); polyphonyGroups_[group].registerVoice(voice); } } @@ -61,8 +61,7 @@ void VoiceManager::reset() voice.reset(); polyphonyGroups_.clear(); - polyphonyGroups_.emplace_back(); - polyphonyGroups_.back().setPolyphonyLimit(config::maxVoices); + polyphonyGroups_.emplace(0, PolyphonyGroup{}); setStealingAlgorithm(StealingAlgorithm::Oldest); } @@ -84,29 +83,31 @@ bool VoiceManager::playingAttackVoice(const Region* releaseRegion) noexcept return true; } -void VoiceManager::ensureNumPolyphonyGroups(unsigned groupIdx) noexcept +void VoiceManager::ensureNumPolyphonyGroups(int groupIdx) noexcept { - size_t neededSize = static_cast(groupIdx) + 1; - if (polyphonyGroups_.size() < neededSize) - polyphonyGroups_.resize(neededSize); + if (!polyphonyGroups_.contains(groupIdx)) + polyphonyGroups_.emplace(groupIdx, PolyphonyGroup{}); } -void VoiceManager::setGroupPolyphony(unsigned groupIdx, unsigned polyphony) noexcept +void VoiceManager::setGroupPolyphony(int groupIdx, unsigned polyphony) noexcept { ensureNumPolyphonyGroups(groupIdx); polyphonyGroups_[groupIdx].setPolyphonyLimit(polyphony); } -const PolyphonyGroup* VoiceManager::getPolyphonyGroupView(int idx) const noexcept +const PolyphonyGroup* VoiceManager::getPolyphonyGroupView(int idx) noexcept { - return (size_t)idx < polyphonyGroups_.size() ? &polyphonyGroups_[idx] : nullptr; + if (!polyphonyGroups_.contains(idx)) + return {}; + + return &polyphonyGroups_[idx]; } void VoiceManager::clear() { - for (PolyphonyGroup& pg : polyphonyGroups_) - pg.removeAllVoices(); + for (auto& pg : polyphonyGroups_) + pg.second.removeAllVoices(); list_.clear(); activeVoices_.clear(); } @@ -164,6 +165,7 @@ void VoiceManager::requireNumVoices(int numVoices, Resources& resources) clear(); list_.reserve(numEffectiveVoices); + temp_.reserve(numEffectiveVoices); activeVoices_.reserve(numEffectiveVoices); for (int i = 0; i < numEffectiveVoices; ++i) { @@ -185,33 +187,42 @@ void VoiceManager::checkNotePolyphony(const Region* region, int delay, const Tri return; unsigned notePolyphonyCounter { 0 }; - Voice* selfMaskCandidate { nullptr }; + temp_.clear(); for (Voice* voice : activeVoices_) { const TriggerEvent& voiceTriggerEvent = voice->getTriggerEvent(); - if (!voice->releasedOrFree() + if (!voice->offedOrFree() && voice->getRegion()->group == region->group && voiceTriggerEvent.number == triggerEvent.number) { notePolyphonyCounter += 1; - switch (region->selfMask) { - case SelfMask::mask: - if (voiceTriggerEvent.value <= triggerEvent.value) { - if (!selfMaskCandidate - || selfMaskCandidate->getTriggerEvent().value > voiceTriggerEvent.value) { - selfMaskCandidate = voice; - } - } - break; - case SelfMask::dontMask: - if (!selfMaskCandidate || selfMaskCandidate->getAge() < voice->getAge()) - selfMaskCandidate = voice; - break; - } + if (region->selfMask == SelfMask::dontMask || voiceTriggerEvent.value <= triggerEvent.value) + temp_.push_back(voice); } } - if (notePolyphonyCounter >= *region->notePolyphony) { - SisterVoiceRing::offAllSisters(selfMaskCandidate, delay); + if (region->selfMask == SelfMask::mask) { + absl::c_sort(temp_, [](const Voice* lhs, const Voice* rhs) { + const auto lhsTrigger = lhs->getTriggerEvent(); + const auto rhsTrigger = rhs->getTriggerEvent(); + return lhsTrigger.value < rhsTrigger.value; + }); + } else if (region->selfMask == SelfMask::dontMask) { + absl::c_sort(temp_, [](const Voice* lhs, const Voice* rhs) { + return lhs->getAge() > rhs->getAge(); + }); + } else { + ASSERTFALSE; + } + + auto it = temp_.begin(); + unsigned targetPolyphony { *region->notePolyphony - 1 }; + while (notePolyphonyCounter > targetPolyphony && it < temp_.end()) { + Voice* voice = *it; + if (!voice->offedOrFree()) + SisterVoiceRing::offAllSisters(voice, delay); + + notePolyphonyCounter--; + it++; } } diff --git a/src/sfizz/VoiceManager.h b/src/sfizz/VoiceManager.h index e924ae96c..785f271cd 100644 --- a/src/sfizz/VoiceManager.h +++ b/src/sfizz/VoiceManager.h @@ -6,6 +6,7 @@ #pragma once +#include "absl/container/flat_hash_map.h" #include "Config.h" #include "PolyphonyGroup.h" #include "Region.h" @@ -59,7 +60,7 @@ struct VoiceManager final : public Voice::StateListener * * @param groupIdx */ - void ensureNumPolyphonyGroups(unsigned groupIdx) noexcept; + void ensureNumPolyphonyGroups(int groupIdx) noexcept; /** * @brief Set the polyphony for a given group @@ -69,7 +70,7 @@ struct VoiceManager final : public Voice::StateListener * @param groupIdx * @param polyphony */ - void setGroupPolyphony(unsigned groupIdx, unsigned polyphony) noexcept; + void setGroupPolyphony(int groupIdx, unsigned polyphony) noexcept; /** * @brief Get a view into a given polyphony group @@ -77,7 +78,7 @@ struct VoiceManager final : public Voice::StateListener * @param idx * @return const PolyphonyGroup* */ - const PolyphonyGroup* getPolyphonyGroupView(int idx) const noexcept; + const PolyphonyGroup* getPolyphonyGroupView(int idx) noexcept; /** * @brief Clear all voices and polyphony groups. @@ -136,8 +137,9 @@ struct VoiceManager final : public Voice::StateListener int getNumEffectiveVoices() const noexcept { return config::calculateActualVoices(numRequiredVoices_); } std::vector list_; std::vector activeVoices_; + std::vector temp_; // These are the `group=` groups where you can off voices - std::vector polyphonyGroups_; + absl::flat_hash_map polyphonyGroups_; std::unique_ptr stealer_ { absl::make_unique() }; /** diff --git a/src/sfizz/VoiceStealing.cpp b/src/sfizz/VoiceStealing.cpp index d99b447df..13c5dd565 100644 --- a/src/sfizz/VoiceStealing.cpp +++ b/src/sfizz/VoiceStealing.cpp @@ -42,7 +42,7 @@ Voice* genericPolyphonyCheck(absl::Span candidates, unsigned polyphony, */ constexpr bool ignoreVoice(const Voice* voice) { - return (voice == nullptr || voice->releasedOrFree()); + return (voice == nullptr || voice->offedOrFree()); } Voice* FirstStealer::checkRegionPolyphony(const Region* region, absl::Span candidates) diff --git a/src/sfizz/dsp/filters/sfz_filters.dsp b/src/sfizz/dsp/filters/sfz_filters.dsp index 72da63629..73cbed75b 100644 --- a/src/sfizz/dsp/filters/sfz_filters.dsp +++ b/src/sfizz/dsp/filters/sfz_filters.dsp @@ -154,7 +154,7 @@ sfz2chEqHshelf = par(i,2,sfzEqHshelf); // Filter parameters cutoff = hslider("[01] Cutoff [unit:Hz] [scale:log]", 440.0, 50.0, 10000.0, 1.0) : max(1.0) : min(20000.0); -Q = vslider("[02] Resonance [unit:dB]", 0.0, 0.0, 40.0, 0.1) : max(0.0) : min(60.0) : ba.db2linear; +Q = vslider("[02] Resonance [unit:dB]", 0.0, 0.0, 40.0, 0.1) : max(-60.0) : min(60.0) : ba.db2linear; pkShGain = vslider("[03] Peak/shelf gain [unit:dB]", 0.0, 0.0, 40.0, 0.1) : max(-120.0) : min(60.0); bandwidthOrSlope = vslider("[04] Bandwidth [unit:octave]", 1.0, 0.1, 10.0, 0.01); bandwidth = bandwidthOrSlope : max(1e-2) : min(12.0); diff --git a/src/sfizz/gen/filters/sfz2chBpf2p.hxx b/src/sfizz/gen/filters/sfz2chBpf2p.hxx index 5c591b9ec..9d6451e6f 100644 --- a/src/sfizz/gen/filters/sfz2chBpf2p.hxx +++ b/src/sfizz/gen/filters/sfz2chBpf2p.hxx @@ -171,7 +171,7 @@ class faust2chBpf2p : public sfzFilterDsp { double fSlow0 = (fSmoothEnable ? fConst1 : 0.0); double fSlow1 = (fConst2 * std::max(0.0, std::min(20000.0, std::max(1.0, double(fHslider0))))); double fSlow2 = std::sin(fSlow1); - double fSlow3 = std::max(0.001, std::pow(10.0, (0.050000000000000003 * std::min(60.0, std::max(0.0, double(fVslider0)))))); + double fSlow3 = std::max(0.001, std::pow(10.0, (0.050000000000000003 * std::min(60.0, std::max(-60.0, double(fVslider0)))))); double fSlow4 = (0.5 * (fSlow2 / fSlow3)); double fSlow5 = (fSlow4 + 1.0); double fSlow6 = (0.5 * (fSlow2 / (fSlow3 * fSlow5))); diff --git a/src/sfizz/gen/filters/sfz2chBpf2pSv.hxx b/src/sfizz/gen/filters/sfz2chBpf2pSv.hxx index 398dfec1e..9feadd358 100644 --- a/src/sfizz/gen/filters/sfz2chBpf2pSv.hxx +++ b/src/sfizz/gen/filters/sfz2chBpf2pSv.hxx @@ -139,7 +139,7 @@ class faust2chBpf2pSv : public sfzFilterDsp { double fSlow0 = (fSmoothEnable ? fConst1 : 0.0); double fSlow1 = (1.0 - fSlow0); double fSlow2 = (std::tan((fConst2 * std::min(20000.0, std::max(1.0, double(fHslider0))))) * fSlow1); - double fSlow3 = (1.0 / std::pow(10.0, (0.050000000000000003 * std::min(60.0, std::max(0.0, double(fVslider0)))))); + double fSlow3 = (1.0 / std::pow(10.0, (0.050000000000000003 * std::min(60.0, std::max(-60.0, double(fVslider0)))))); for (int i = 0; (i < count); i = (i + 1)) { double fTemp0 = double(input0[i]); double fTemp1 = double(input1[i]); diff --git a/src/sfizz/gen/filters/sfz2chBpf4p.hxx b/src/sfizz/gen/filters/sfz2chBpf4p.hxx index cb7113d0b..ca2bea90e 100644 --- a/src/sfizz/gen/filters/sfz2chBpf4p.hxx +++ b/src/sfizz/gen/filters/sfz2chBpf4p.hxx @@ -211,7 +211,7 @@ class faust2chBpf4p : public sfzFilterDsp { double fSlow0 = (fSmoothEnable ? fConst1 : 0.0); double fSlow1 = (fConst2 * std::max(0.0, std::min(20000.0, std::max(1.0, double(fHslider0))))); double fSlow2 = std::sin(fSlow1); - double fSlow3 = std::max(0.001, std::pow(10.0, (0.050000000000000003 * std::min(60.0, std::max(0.0, double(fVslider0)))))); + double fSlow3 = std::max(0.001, std::pow(10.0, (0.050000000000000003 * std::min(60.0, std::max(-60.0, double(fVslider0)))))); double fSlow4 = (0.5 * (fSlow2 / fSlow3)); double fSlow5 = (fSlow4 + 1.0); double fSlow6 = (0.5 * (fSlow2 / (fSlow3 * fSlow5))); diff --git a/src/sfizz/gen/filters/sfz2chBpf6p.hxx b/src/sfizz/gen/filters/sfz2chBpf6p.hxx index 78495246b..14c89256e 100644 --- a/src/sfizz/gen/filters/sfz2chBpf6p.hxx +++ b/src/sfizz/gen/filters/sfz2chBpf6p.hxx @@ -251,7 +251,7 @@ class faust2chBpf6p : public sfzFilterDsp { double fSlow0 = (fSmoothEnable ? fConst1 : 0.0); double fSlow1 = (fConst2 * std::max(0.0, std::min(20000.0, std::max(1.0, double(fHslider0))))); double fSlow2 = std::sin(fSlow1); - double fSlow3 = std::max(0.001, std::pow(10.0, (0.050000000000000003 * std::min(60.0, std::max(0.0, double(fVslider0)))))); + double fSlow3 = std::max(0.001, std::pow(10.0, (0.050000000000000003 * std::min(60.0, std::max(-60.0, double(fVslider0)))))); double fSlow4 = (0.5 * (fSlow2 / fSlow3)); double fSlow5 = (fSlow4 + 1.0); double fSlow6 = (0.5 * (fSlow2 / (fSlow3 * fSlow5))); diff --git a/src/sfizz/gen/filters/sfz2chBrf2p.hxx b/src/sfizz/gen/filters/sfz2chBrf2p.hxx index b802972f9..4b73e3e3e 100644 --- a/src/sfizz/gen/filters/sfz2chBrf2p.hxx +++ b/src/sfizz/gen/filters/sfz2chBrf2p.hxx @@ -162,7 +162,7 @@ class faust2chBrf2p : public sfzFilterDsp { FAUSTFLOAT* output1 = outputs[1]; double fSlow0 = (fSmoothEnable ? fConst1 : 0.0); double fSlow1 = (fConst2 * std::max(0.0, std::min(20000.0, std::max(1.0, double(fHslider0))))); - double fSlow2 = (0.5 * (std::sin(fSlow1) / std::max(0.001, std::pow(10.0, (0.050000000000000003 * std::min(60.0, std::max(0.0, double(fVslider0)))))))); + double fSlow2 = (0.5 * (std::sin(fSlow1) / std::max(0.001, std::pow(10.0, (0.050000000000000003 * std::min(60.0, std::max(-60.0, double(fVslider0)))))))); double fSlow3 = (fSlow2 + 1.0); double fSlow4 = (1.0 - fSlow0); double fSlow5 = (((0.0 - (2.0 * std::cos(fSlow1))) / fSlow3) * fSlow4); diff --git a/src/sfizz/gen/filters/sfz2chBrf2pSv.hxx b/src/sfizz/gen/filters/sfz2chBrf2pSv.hxx index c9b75ee2f..abc664379 100644 --- a/src/sfizz/gen/filters/sfz2chBrf2pSv.hxx +++ b/src/sfizz/gen/filters/sfz2chBrf2pSv.hxx @@ -139,7 +139,7 @@ class faust2chBrf2pSv : public sfzFilterDsp { double fSlow0 = (fSmoothEnable ? fConst1 : 0.0); double fSlow1 = (1.0 - fSlow0); double fSlow2 = (std::tan((fConst2 * std::min(20000.0, std::max(1.0, double(fHslider0))))) * fSlow1); - double fSlow3 = (1.0 / std::pow(10.0, (0.050000000000000003 * std::min(60.0, std::max(0.0, double(fVslider0)))))); + double fSlow3 = (1.0 / std::pow(10.0, (0.050000000000000003 * std::min(60.0, std::max(-60.0, double(fVslider0)))))); for (int i = 0; (i < count); i = (i + 1)) { double fTemp0 = double(input0[i]); double fTemp1 = double(input1[i]); diff --git a/src/sfizz/gen/filters/sfz2chHpf2p.hxx b/src/sfizz/gen/filters/sfz2chHpf2p.hxx index 490592935..48f45545d 100644 --- a/src/sfizz/gen/filters/sfz2chHpf2p.hxx +++ b/src/sfizz/gen/filters/sfz2chHpf2p.hxx @@ -167,7 +167,7 @@ class faust2chHpf2p : public sfzFilterDsp { double fSlow0 = (fSmoothEnable ? fConst1 : 0.0); double fSlow1 = (fConst2 * std::max(0.0, std::min(20000.0, std::max(1.0, double(fHslider0))))); double fSlow2 = std::cos(fSlow1); - double fSlow3 = (0.5 * (std::sin(fSlow1) / std::max(0.001, std::pow(10.0, (0.050000000000000003 * std::min(60.0, std::max(0.0, double(fVslider0)))))))); + double fSlow3 = (0.5 * (std::sin(fSlow1) / std::max(0.001, std::pow(10.0, (0.050000000000000003 * std::min(60.0, std::max(-60.0, double(fVslider0)))))))); double fSlow4 = (fSlow3 + 1.0); double fSlow5 = (1.0 - fSlow0); double fSlow6 = (((-1.0 - fSlow2) / fSlow4) * fSlow5); diff --git a/src/sfizz/gen/filters/sfz2chHpf2pSv.hxx b/src/sfizz/gen/filters/sfz2chHpf2pSv.hxx index 6631a8988..ceb828404 100644 --- a/src/sfizz/gen/filters/sfz2chHpf2pSv.hxx +++ b/src/sfizz/gen/filters/sfz2chHpf2pSv.hxx @@ -139,7 +139,7 @@ class faust2chHpf2pSv : public sfzFilterDsp { double fSlow0 = (fSmoothEnable ? fConst1 : 0.0); double fSlow1 = (1.0 - fSlow0); double fSlow2 = (std::tan((fConst2 * std::min(20000.0, std::max(1.0, double(fHslider0))))) * fSlow1); - double fSlow3 = (1.0 / std::pow(10.0, (0.050000000000000003 * std::min(60.0, std::max(0.0, double(fVslider0)))))); + double fSlow3 = (1.0 / std::pow(10.0, (0.050000000000000003 * std::min(60.0, std::max(-60.0, double(fVslider0)))))); for (int i = 0; (i < count); i = (i + 1)) { double fTemp0 = double(input0[i]); double fTemp1 = double(input1[i]); diff --git a/src/sfizz/gen/filters/sfz2chHpf4p.hxx b/src/sfizz/gen/filters/sfz2chHpf4p.hxx index 85ddb7853..950aa163f 100644 --- a/src/sfizz/gen/filters/sfz2chHpf4p.hxx +++ b/src/sfizz/gen/filters/sfz2chHpf4p.hxx @@ -207,7 +207,7 @@ class faust2chHpf4p : public sfzFilterDsp { double fSlow0 = (fSmoothEnable ? fConst1 : 0.0); double fSlow1 = (fConst2 * std::max(0.0, std::min(20000.0, std::max(1.0, double(fHslider0))))); double fSlow2 = std::cos(fSlow1); - double fSlow3 = (0.5 * (std::sin(fSlow1) / std::max(0.001, std::pow(10.0, (0.050000000000000003 * std::min(60.0, std::max(0.0, double(fVslider0)))))))); + double fSlow3 = (0.5 * (std::sin(fSlow1) / std::max(0.001, std::pow(10.0, (0.050000000000000003 * std::min(60.0, std::max(-60.0, double(fVslider0)))))))); double fSlow4 = (fSlow3 + 1.0); double fSlow5 = (1.0 - fSlow0); double fSlow6 = (((-1.0 - fSlow2) / fSlow4) * fSlow5); diff --git a/src/sfizz/gen/filters/sfz2chHpf6p.hxx b/src/sfizz/gen/filters/sfz2chHpf6p.hxx index 4c848e818..c078bb41d 100644 --- a/src/sfizz/gen/filters/sfz2chHpf6p.hxx +++ b/src/sfizz/gen/filters/sfz2chHpf6p.hxx @@ -247,7 +247,7 @@ class faust2chHpf6p : public sfzFilterDsp { double fSlow0 = (fSmoothEnable ? fConst1 : 0.0); double fSlow1 = (fConst2 * std::max(0.0, std::min(20000.0, std::max(1.0, double(fHslider0))))); double fSlow2 = std::cos(fSlow1); - double fSlow3 = (0.5 * (std::sin(fSlow1) / std::max(0.001, std::pow(10.0, (0.050000000000000003 * std::min(60.0, std::max(0.0, double(fVslider0)))))))); + double fSlow3 = (0.5 * (std::sin(fSlow1) / std::max(0.001, std::pow(10.0, (0.050000000000000003 * std::min(60.0, std::max(-60.0, double(fVslider0)))))))); double fSlow4 = (fSlow3 + 1.0); double fSlow5 = (1.0 - fSlow0); double fSlow6 = (((-1.0 - fSlow2) / fSlow4) * fSlow5); diff --git a/src/sfizz/gen/filters/sfz2chHsh.hxx b/src/sfizz/gen/filters/sfz2chHsh.hxx index 7bb9fde36..68a2a387a 100644 --- a/src/sfizz/gen/filters/sfz2chHsh.hxx +++ b/src/sfizz/gen/filters/sfz2chHsh.hxx @@ -175,7 +175,7 @@ class faust2chHsh : public sfzFilterDsp { double fSlow2 = (fConst2 * std::max(0.0, std::min(20000.0, std::max(1.0, double(fHslider0))))); double fSlow3 = std::cos(fSlow2); double fSlow4 = ((fSlow1 + 1.0) * fSlow3); - double fSlow5 = ((std::sqrt(fSlow1) * std::sin(fSlow2)) / std::max(0.001, std::pow(10.0, (0.050000000000000003 * std::min(60.0, std::max(0.0, double(fVslider1))))))); + double fSlow5 = ((std::sqrt(fSlow1) * std::sin(fSlow2)) / std::max(0.001, std::pow(10.0, (0.050000000000000003 * std::min(60.0, std::max(-60.0, double(fVslider1))))))); double fSlow6 = ((fSlow1 + -1.0) * fSlow3); double fSlow7 = ((fSlow1 + fSlow5) + (1.0 - fSlow6)); double fSlow8 = (1.0 - fSlow0); diff --git a/src/sfizz/gen/filters/sfz2chLpf2p.hxx b/src/sfizz/gen/filters/sfz2chLpf2p.hxx index 1ee9a6f8c..d71352b77 100644 --- a/src/sfizz/gen/filters/sfz2chLpf2p.hxx +++ b/src/sfizz/gen/filters/sfz2chLpf2p.hxx @@ -166,7 +166,7 @@ class faust2chLpf2p : public sfzFilterDsp { FAUSTFLOAT* output1 = outputs[1]; double fSlow0 = (fConst1 * std::max(0.0, std::min(20000.0, std::max(1.0, double(fHslider0))))); double fSlow1 = std::cos(fSlow0); - double fSlow2 = (0.5 * (std::sin(fSlow0) / std::max(0.001, std::pow(10.0, (0.050000000000000003 * std::min(60.0, std::max(0.0, double(fVslider0)))))))); + double fSlow2 = (0.5 * (std::sin(fSlow0) / std::max(0.001, std::pow(10.0, (0.050000000000000003 * std::min(60.0, std::max(-60.0, double(fVslider0)))))))); double fSlow3 = (fSlow2 + 1.0); double fSlow4 = ((1.0 - fSlow1) / fSlow3); double fSlow5 = (fSmoothEnable ? fConst2 : 0.0); diff --git a/src/sfizz/gen/filters/sfz2chLpf2pSv.hxx b/src/sfizz/gen/filters/sfz2chLpf2pSv.hxx index 6c42c4ed8..a822345ff 100644 --- a/src/sfizz/gen/filters/sfz2chLpf2pSv.hxx +++ b/src/sfizz/gen/filters/sfz2chLpf2pSv.hxx @@ -139,7 +139,7 @@ class faust2chLpf2pSv : public sfzFilterDsp { double fSlow0 = (fSmoothEnable ? fConst1 : 0.0); double fSlow1 = (1.0 - fSlow0); double fSlow2 = (std::tan((fConst2 * std::min(20000.0, std::max(1.0, double(fHslider0))))) * fSlow1); - double fSlow3 = (1.0 / std::pow(10.0, (0.050000000000000003 * std::min(60.0, std::max(0.0, double(fVslider0)))))); + double fSlow3 = (1.0 / std::pow(10.0, (0.050000000000000003 * std::min(60.0, std::max(-60.0, double(fVslider0)))))); for (int i = 0; (i < count); i = (i + 1)) { double fTemp0 = double(input0[i]); double fTemp1 = double(input1[i]); diff --git a/src/sfizz/gen/filters/sfz2chLpf4p.hxx b/src/sfizz/gen/filters/sfz2chLpf4p.hxx index 7fabed760..ba0d9c6d5 100644 --- a/src/sfizz/gen/filters/sfz2chLpf4p.hxx +++ b/src/sfizz/gen/filters/sfz2chLpf4p.hxx @@ -206,7 +206,7 @@ class faust2chLpf4p : public sfzFilterDsp { FAUSTFLOAT* output1 = outputs[1]; double fSlow0 = (fConst1 * std::max(0.0, std::min(20000.0, std::max(1.0, double(fHslider0))))); double fSlow1 = std::cos(fSlow0); - double fSlow2 = (0.5 * (std::sin(fSlow0) / std::max(0.001, std::pow(10.0, (0.050000000000000003 * std::min(60.0, std::max(0.0, double(fVslider0)))))))); + double fSlow2 = (0.5 * (std::sin(fSlow0) / std::max(0.001, std::pow(10.0, (0.050000000000000003 * std::min(60.0, std::max(-60.0, double(fVslider0)))))))); double fSlow3 = (fSlow2 + 1.0); double fSlow4 = ((1.0 - fSlow1) / fSlow3); double fSlow5 = (fSmoothEnable ? fConst2 : 0.0); diff --git a/src/sfizz/gen/filters/sfz2chLpf6p.hxx b/src/sfizz/gen/filters/sfz2chLpf6p.hxx index b2ce9fcaa..b3fca7358 100644 --- a/src/sfizz/gen/filters/sfz2chLpf6p.hxx +++ b/src/sfizz/gen/filters/sfz2chLpf6p.hxx @@ -246,7 +246,7 @@ class faust2chLpf6p : public sfzFilterDsp { FAUSTFLOAT* output1 = outputs[1]; double fSlow0 = (fConst1 * std::max(0.0, std::min(20000.0, std::max(1.0, double(fHslider0))))); double fSlow1 = std::cos(fSlow0); - double fSlow2 = (0.5 * (std::sin(fSlow0) / std::max(0.001, std::pow(10.0, (0.050000000000000003 * std::min(60.0, std::max(0.0, double(fVslider0)))))))); + double fSlow2 = (0.5 * (std::sin(fSlow0) / std::max(0.001, std::pow(10.0, (0.050000000000000003 * std::min(60.0, std::max(-60.0, double(fVslider0)))))))); double fSlow3 = (fSlow2 + 1.0); double fSlow4 = ((1.0 - fSlow1) / fSlow3); double fSlow5 = (fSmoothEnable ? fConst2 : 0.0); diff --git a/src/sfizz/gen/filters/sfz2chLsh.hxx b/src/sfizz/gen/filters/sfz2chLsh.hxx index b3ec808f0..3d32d1a51 100644 --- a/src/sfizz/gen/filters/sfz2chLsh.hxx +++ b/src/sfizz/gen/filters/sfz2chLsh.hxx @@ -176,7 +176,7 @@ class faust2chLsh : public sfzFilterDsp { double fSlow3 = std::cos(fSlow2); double fSlow4 = ((fSlow1 + 1.0) * fSlow3); double fSlow5 = ((fSlow1 + -1.0) * fSlow3); - double fSlow6 = ((std::sqrt(fSlow1) * std::sin(fSlow2)) / std::max(0.001, std::pow(10.0, (0.050000000000000003 * std::min(60.0, std::max(0.0, double(fVslider1))))))); + double fSlow6 = ((std::sqrt(fSlow1) * std::sin(fSlow2)) / std::max(0.001, std::pow(10.0, (0.050000000000000003 * std::min(60.0, std::max(-60.0, double(fVslider1))))))); double fSlow7 = (fSlow5 + fSlow6); double fSlow8 = ((fSlow1 + fSlow7) + 1.0); double fSlow9 = (1.0 - fSlow0); diff --git a/src/sfizz/gen/filters/sfz2chPeq.hxx b/src/sfizz/gen/filters/sfz2chPeq.hxx index 6126a8fdf..4b5de6f3c 100644 --- a/src/sfizz/gen/filters/sfz2chPeq.hxx +++ b/src/sfizz/gen/filters/sfz2chPeq.hxx @@ -169,7 +169,7 @@ class faust2chPeq : public sfzFilterDsp { double fSlow0 = (fSmoothEnable ? fConst1 : 0.0); double fSlow1 = (fConst2 * std::max(0.0, std::min(20000.0, std::max(1.0, double(fHslider0))))); double fSlow2 = std::sin(fSlow1); - double fSlow3 = std::max(0.001, std::pow(10.0, (0.050000000000000003 * std::min(60.0, std::max(0.0, double(fVslider0)))))); + double fSlow3 = std::max(0.001, std::pow(10.0, (0.050000000000000003 * std::min(60.0, std::max(-60.0, double(fVslider0)))))); double fSlow4 = std::pow(10.0, (0.025000000000000001 * std::min(60.0, std::max(-120.0, double(fVslider1))))); double fSlow5 = (0.5 * (fSlow2 / (fSlow3 * fSlow4))); double fSlow6 = (fSlow5 + 1.0); diff --git a/src/sfizz/gen/filters/sfzBpf2p.hxx b/src/sfizz/gen/filters/sfzBpf2p.hxx index 9a0c61dc0..e31555b3e 100644 --- a/src/sfizz/gen/filters/sfzBpf2p.hxx +++ b/src/sfizz/gen/filters/sfzBpf2p.hxx @@ -149,7 +149,7 @@ class faustBpf2p : public sfzFilterDsp { double fSlow0 = (fSmoothEnable ? fConst1 : 0.0); double fSlow1 = (fConst2 * std::max(0.0, std::min(20000.0, std::max(1.0, double(fHslider0))))); double fSlow2 = std::sin(fSlow1); - double fSlow3 = std::max(0.001, std::pow(10.0, (0.050000000000000003 * std::min(60.0, std::max(0.0, double(fVslider0)))))); + double fSlow3 = std::max(0.001, std::pow(10.0, (0.050000000000000003 * std::min(60.0, std::max(-60.0, double(fVslider0)))))); double fSlow4 = (0.5 * (fSlow2 / fSlow3)); double fSlow5 = (fSlow4 + 1.0); double fSlow6 = (0.5 * (fSlow2 / (fSlow3 * fSlow5))); diff --git a/src/sfizz/gen/filters/sfzBpf2pSv.hxx b/src/sfizz/gen/filters/sfzBpf2pSv.hxx index 19b66dec1..995601515 100644 --- a/src/sfizz/gen/filters/sfzBpf2pSv.hxx +++ b/src/sfizz/gen/filters/sfzBpf2pSv.hxx @@ -129,7 +129,7 @@ class faustBpf2pSv : public sfzFilterDsp { double fSlow0 = (fSmoothEnable ? fConst1 : 0.0); double fSlow1 = (1.0 - fSlow0); double fSlow2 = (std::tan((fConst2 * std::min(20000.0, std::max(1.0, double(fHslider0))))) * fSlow1); - double fSlow3 = (1.0 / std::pow(10.0, (0.050000000000000003 * std::min(60.0, std::max(0.0, double(fVslider0)))))); + double fSlow3 = (1.0 / std::pow(10.0, (0.050000000000000003 * std::min(60.0, std::max(-60.0, double(fVslider0)))))); for (int i = 0; (i < count); i = (i + 1)) { double fTemp0 = double(input0[i]); fRec3[0] = ((fSlow0 * fRec3[1]) + fSlow2); diff --git a/src/sfizz/gen/filters/sfzBpf4p.hxx b/src/sfizz/gen/filters/sfzBpf4p.hxx index e51efc1b1..4d74e7fea 100644 --- a/src/sfizz/gen/filters/sfzBpf4p.hxx +++ b/src/sfizz/gen/filters/sfzBpf4p.hxx @@ -169,7 +169,7 @@ class faustBpf4p : public sfzFilterDsp { double fSlow0 = (fSmoothEnable ? fConst1 : 0.0); double fSlow1 = (fConst2 * std::max(0.0, std::min(20000.0, std::max(1.0, double(fHslider0))))); double fSlow2 = std::sin(fSlow1); - double fSlow3 = std::max(0.001, std::pow(10.0, (0.050000000000000003 * std::min(60.0, std::max(0.0, double(fVslider0)))))); + double fSlow3 = std::max(0.001, std::pow(10.0, (0.050000000000000003 * std::min(60.0, std::max(-60.0, double(fVslider0)))))); double fSlow4 = (0.5 * (fSlow2 / fSlow3)); double fSlow5 = (fSlow4 + 1.0); double fSlow6 = (0.5 * (fSlow2 / (fSlow3 * fSlow5))); diff --git a/src/sfizz/gen/filters/sfzBpf6p.hxx b/src/sfizz/gen/filters/sfzBpf6p.hxx index 98fc4b1ed..0ec70dd06 100644 --- a/src/sfizz/gen/filters/sfzBpf6p.hxx +++ b/src/sfizz/gen/filters/sfzBpf6p.hxx @@ -189,7 +189,7 @@ class faustBpf6p : public sfzFilterDsp { double fSlow0 = (fSmoothEnable ? fConst1 : 0.0); double fSlow1 = (fConst2 * std::max(0.0, std::min(20000.0, std::max(1.0, double(fHslider0))))); double fSlow2 = std::sin(fSlow1); - double fSlow3 = std::max(0.001, std::pow(10.0, (0.050000000000000003 * std::min(60.0, std::max(0.0, double(fVslider0)))))); + double fSlow3 = std::max(0.001, std::pow(10.0, (0.050000000000000003 * std::min(60.0, std::max(-60.0, double(fVslider0)))))); double fSlow4 = (0.5 * (fSlow2 / fSlow3)); double fSlow5 = (fSlow4 + 1.0); double fSlow6 = (0.5 * (fSlow2 / (fSlow3 * fSlow5))); diff --git a/src/sfizz/gen/filters/sfzBrf2p.hxx b/src/sfizz/gen/filters/sfzBrf2p.hxx index 210366e51..49317e687 100644 --- a/src/sfizz/gen/filters/sfzBrf2p.hxx +++ b/src/sfizz/gen/filters/sfzBrf2p.hxx @@ -140,7 +140,7 @@ class faustBrf2p : public sfzFilterDsp { FAUSTFLOAT* output0 = outputs[0]; double fSlow0 = (fSmoothEnable ? fConst1 : 0.0); double fSlow1 = (fConst2 * std::max(0.0, std::min(20000.0, std::max(1.0, double(fHslider0))))); - double fSlow2 = (0.5 * (std::sin(fSlow1) / std::max(0.001, std::pow(10.0, (0.050000000000000003 * std::min(60.0, std::max(0.0, double(fVslider0)))))))); + double fSlow2 = (0.5 * (std::sin(fSlow1) / std::max(0.001, std::pow(10.0, (0.050000000000000003 * std::min(60.0, std::max(-60.0, double(fVslider0)))))))); double fSlow3 = (fSlow2 + 1.0); double fSlow4 = (1.0 - fSlow0); double fSlow5 = (((0.0 - (2.0 * std::cos(fSlow1))) / fSlow3) * fSlow4); diff --git a/src/sfizz/gen/filters/sfzBrf2pSv.hxx b/src/sfizz/gen/filters/sfzBrf2pSv.hxx index 4a50a451f..1024b9aa7 100644 --- a/src/sfizz/gen/filters/sfzBrf2pSv.hxx +++ b/src/sfizz/gen/filters/sfzBrf2pSv.hxx @@ -129,7 +129,7 @@ class faustBrf2pSv : public sfzFilterDsp { double fSlow0 = (fSmoothEnable ? fConst1 : 0.0); double fSlow1 = (1.0 - fSlow0); double fSlow2 = (std::tan((fConst2 * std::min(20000.0, std::max(1.0, double(fHslider0))))) * fSlow1); - double fSlow3 = (1.0 / std::pow(10.0, (0.050000000000000003 * std::min(60.0, std::max(0.0, double(fVslider0)))))); + double fSlow3 = (1.0 / std::pow(10.0, (0.050000000000000003 * std::min(60.0, std::max(-60.0, double(fVslider0)))))); for (int i = 0; (i < count); i = (i + 1)) { double fTemp0 = double(input0[i]); fRec5[0] = ((fSlow0 * fRec5[1]) + fSlow2); diff --git a/src/sfizz/gen/filters/sfzHpf2p.hxx b/src/sfizz/gen/filters/sfzHpf2p.hxx index 9934aabf7..18e3f6484 100644 --- a/src/sfizz/gen/filters/sfzHpf2p.hxx +++ b/src/sfizz/gen/filters/sfzHpf2p.hxx @@ -145,7 +145,7 @@ class faustHpf2p : public sfzFilterDsp { double fSlow0 = (fSmoothEnable ? fConst1 : 0.0); double fSlow1 = (fConst2 * std::max(0.0, std::min(20000.0, std::max(1.0, double(fHslider0))))); double fSlow2 = std::cos(fSlow1); - double fSlow3 = (0.5 * (std::sin(fSlow1) / std::max(0.001, std::pow(10.0, (0.050000000000000003 * std::min(60.0, std::max(0.0, double(fVslider0)))))))); + double fSlow3 = (0.5 * (std::sin(fSlow1) / std::max(0.001, std::pow(10.0, (0.050000000000000003 * std::min(60.0, std::max(-60.0, double(fVslider0)))))))); double fSlow4 = (fSlow3 + 1.0); double fSlow5 = (1.0 - fSlow0); double fSlow6 = (((-1.0 - fSlow2) / fSlow4) * fSlow5); diff --git a/src/sfizz/gen/filters/sfzHpf2pSv.hxx b/src/sfizz/gen/filters/sfzHpf2pSv.hxx index 2dc0420ac..7d0e4507a 100644 --- a/src/sfizz/gen/filters/sfzHpf2pSv.hxx +++ b/src/sfizz/gen/filters/sfzHpf2pSv.hxx @@ -129,7 +129,7 @@ class faustHpf2pSv : public sfzFilterDsp { double fSlow0 = (fSmoothEnable ? fConst1 : 0.0); double fSlow1 = (1.0 - fSlow0); double fSlow2 = (std::tan((fConst2 * std::min(20000.0, std::max(1.0, double(fHslider0))))) * fSlow1); - double fSlow3 = (1.0 / std::pow(10.0, (0.050000000000000003 * std::min(60.0, std::max(0.0, double(fVslider0)))))); + double fSlow3 = (1.0 / std::pow(10.0, (0.050000000000000003 * std::min(60.0, std::max(-60.0, double(fVslider0)))))); for (int i = 0; (i < count); i = (i + 1)) { double fTemp0 = double(input0[i]); fRec4[0] = ((fSlow0 * fRec4[1]) + fSlow2); diff --git a/src/sfizz/gen/filters/sfzHpf4p.hxx b/src/sfizz/gen/filters/sfzHpf4p.hxx index 1ffeed51a..4f02e2882 100644 --- a/src/sfizz/gen/filters/sfzHpf4p.hxx +++ b/src/sfizz/gen/filters/sfzHpf4p.hxx @@ -165,7 +165,7 @@ class faustHpf4p : public sfzFilterDsp { double fSlow0 = (fSmoothEnable ? fConst1 : 0.0); double fSlow1 = (fConst2 * std::max(0.0, std::min(20000.0, std::max(1.0, double(fHslider0))))); double fSlow2 = std::cos(fSlow1); - double fSlow3 = (0.5 * (std::sin(fSlow1) / std::max(0.001, std::pow(10.0, (0.050000000000000003 * std::min(60.0, std::max(0.0, double(fVslider0)))))))); + double fSlow3 = (0.5 * (std::sin(fSlow1) / std::max(0.001, std::pow(10.0, (0.050000000000000003 * std::min(60.0, std::max(-60.0, double(fVslider0)))))))); double fSlow4 = (fSlow3 + 1.0); double fSlow5 = (1.0 - fSlow0); double fSlow6 = (((-1.0 - fSlow2) / fSlow4) * fSlow5); diff --git a/src/sfizz/gen/filters/sfzHpf6p.hxx b/src/sfizz/gen/filters/sfzHpf6p.hxx index ab3763878..ffcd8bdf9 100644 --- a/src/sfizz/gen/filters/sfzHpf6p.hxx +++ b/src/sfizz/gen/filters/sfzHpf6p.hxx @@ -185,7 +185,7 @@ class faustHpf6p : public sfzFilterDsp { double fSlow0 = (fSmoothEnable ? fConst1 : 0.0); double fSlow1 = (fConst2 * std::max(0.0, std::min(20000.0, std::max(1.0, double(fHslider0))))); double fSlow2 = std::cos(fSlow1); - double fSlow3 = (0.5 * (std::sin(fSlow1) / std::max(0.001, std::pow(10.0, (0.050000000000000003 * std::min(60.0, std::max(0.0, double(fVslider0)))))))); + double fSlow3 = (0.5 * (std::sin(fSlow1) / std::max(0.001, std::pow(10.0, (0.050000000000000003 * std::min(60.0, std::max(-60.0, double(fVslider0)))))))); double fSlow4 = (fSlow3 + 1.0); double fSlow5 = (1.0 - fSlow0); double fSlow6 = (((-1.0 - fSlow2) / fSlow4) * fSlow5); diff --git a/src/sfizz/gen/filters/sfzHsh.hxx b/src/sfizz/gen/filters/sfzHsh.hxx index fc80a9463..e4b69cc36 100644 --- a/src/sfizz/gen/filters/sfzHsh.hxx +++ b/src/sfizz/gen/filters/sfzHsh.hxx @@ -153,7 +153,7 @@ class faustHsh : public sfzFilterDsp { double fSlow2 = (fConst2 * std::max(0.0, std::min(20000.0, std::max(1.0, double(fHslider0))))); double fSlow3 = std::cos(fSlow2); double fSlow4 = ((fSlow1 + 1.0) * fSlow3); - double fSlow5 = ((std::sqrt(fSlow1) * std::sin(fSlow2)) / std::max(0.001, std::pow(10.0, (0.050000000000000003 * std::min(60.0, std::max(0.0, double(fVslider1))))))); + double fSlow5 = ((std::sqrt(fSlow1) * std::sin(fSlow2)) / std::max(0.001, std::pow(10.0, (0.050000000000000003 * std::min(60.0, std::max(-60.0, double(fVslider1))))))); double fSlow6 = ((fSlow1 + -1.0) * fSlow3); double fSlow7 = ((fSlow1 + fSlow5) + (1.0 - fSlow6)); double fSlow8 = (1.0 - fSlow0); diff --git a/src/sfizz/gen/filters/sfzLpf2p.hxx b/src/sfizz/gen/filters/sfzLpf2p.hxx index 438804673..871be36a7 100644 --- a/src/sfizz/gen/filters/sfzLpf2p.hxx +++ b/src/sfizz/gen/filters/sfzLpf2p.hxx @@ -144,7 +144,7 @@ class faustLpf2p : public sfzFilterDsp { FAUSTFLOAT* output0 = outputs[0]; double fSlow0 = (fConst1 * std::max(0.0, std::min(20000.0, std::max(1.0, double(fHslider0))))); double fSlow1 = std::cos(fSlow0); - double fSlow2 = (0.5 * (std::sin(fSlow0) / std::max(0.001, std::pow(10.0, (0.050000000000000003 * std::min(60.0, std::max(0.0, double(fVslider0)))))))); + double fSlow2 = (0.5 * (std::sin(fSlow0) / std::max(0.001, std::pow(10.0, (0.050000000000000003 * std::min(60.0, std::max(-60.0, double(fVslider0)))))))); double fSlow3 = (fSlow2 + 1.0); double fSlow4 = ((1.0 - fSlow1) / fSlow3); double fSlow5 = (fSmoothEnable ? fConst2 : 0.0); diff --git a/src/sfizz/gen/filters/sfzLpf2pSv.hxx b/src/sfizz/gen/filters/sfzLpf2pSv.hxx index f303bb73a..ee51668a4 100644 --- a/src/sfizz/gen/filters/sfzLpf2pSv.hxx +++ b/src/sfizz/gen/filters/sfzLpf2pSv.hxx @@ -129,7 +129,7 @@ class faustLpf2pSv : public sfzFilterDsp { double fSlow0 = (fSmoothEnable ? fConst1 : 0.0); double fSlow1 = (1.0 - fSlow0); double fSlow2 = (std::tan((fConst2 * std::min(20000.0, std::max(1.0, double(fHslider0))))) * fSlow1); - double fSlow3 = (1.0 / std::pow(10.0, (0.050000000000000003 * std::min(60.0, std::max(0.0, double(fVslider0)))))); + double fSlow3 = (1.0 / std::pow(10.0, (0.050000000000000003 * std::min(60.0, std::max(-60.0, double(fVslider0)))))); for (int i = 0; (i < count); i = (i + 1)) { double fTemp0 = double(input0[i]); fRec3[0] = ((fSlow0 * fRec3[1]) + fSlow2); diff --git a/src/sfizz/gen/filters/sfzLpf4p.hxx b/src/sfizz/gen/filters/sfzLpf4p.hxx index d0435d560..88e8ae7ce 100644 --- a/src/sfizz/gen/filters/sfzLpf4p.hxx +++ b/src/sfizz/gen/filters/sfzLpf4p.hxx @@ -164,7 +164,7 @@ class faustLpf4p : public sfzFilterDsp { FAUSTFLOAT* output0 = outputs[0]; double fSlow0 = (fConst1 * std::max(0.0, std::min(20000.0, std::max(1.0, double(fHslider0))))); double fSlow1 = std::cos(fSlow0); - double fSlow2 = (0.5 * (std::sin(fSlow0) / std::max(0.001, std::pow(10.0, (0.050000000000000003 * std::min(60.0, std::max(0.0, double(fVslider0)))))))); + double fSlow2 = (0.5 * (std::sin(fSlow0) / std::max(0.001, std::pow(10.0, (0.050000000000000003 * std::min(60.0, std::max(-60.0, double(fVslider0)))))))); double fSlow3 = (fSlow2 + 1.0); double fSlow4 = ((1.0 - fSlow1) / fSlow3); double fSlow5 = (fSmoothEnable ? fConst2 : 0.0); diff --git a/src/sfizz/gen/filters/sfzLpf6p.hxx b/src/sfizz/gen/filters/sfzLpf6p.hxx index 95f702288..1df6f79c9 100644 --- a/src/sfizz/gen/filters/sfzLpf6p.hxx +++ b/src/sfizz/gen/filters/sfzLpf6p.hxx @@ -184,7 +184,7 @@ class faustLpf6p : public sfzFilterDsp { FAUSTFLOAT* output0 = outputs[0]; double fSlow0 = (fConst1 * std::max(0.0, std::min(20000.0, std::max(1.0, double(fHslider0))))); double fSlow1 = std::cos(fSlow0); - double fSlow2 = (0.5 * (std::sin(fSlow0) / std::max(0.001, std::pow(10.0, (0.050000000000000003 * std::min(60.0, std::max(0.0, double(fVslider0)))))))); + double fSlow2 = (0.5 * (std::sin(fSlow0) / std::max(0.001, std::pow(10.0, (0.050000000000000003 * std::min(60.0, std::max(-60.0, double(fVslider0)))))))); double fSlow3 = (fSlow2 + 1.0); double fSlow4 = ((1.0 - fSlow1) / fSlow3); double fSlow5 = (fSmoothEnable ? fConst2 : 0.0); diff --git a/src/sfizz/gen/filters/sfzLsh.hxx b/src/sfizz/gen/filters/sfzLsh.hxx index b614b5589..92e93f9dc 100644 --- a/src/sfizz/gen/filters/sfzLsh.hxx +++ b/src/sfizz/gen/filters/sfzLsh.hxx @@ -154,7 +154,7 @@ class faustLsh : public sfzFilterDsp { double fSlow3 = std::cos(fSlow2); double fSlow4 = ((fSlow1 + 1.0) * fSlow3); double fSlow5 = ((fSlow1 + -1.0) * fSlow3); - double fSlow6 = ((std::sqrt(fSlow1) * std::sin(fSlow2)) / std::max(0.001, std::pow(10.0, (0.050000000000000003 * std::min(60.0, std::max(0.0, double(fVslider1))))))); + double fSlow6 = ((std::sqrt(fSlow1) * std::sin(fSlow2)) / std::max(0.001, std::pow(10.0, (0.050000000000000003 * std::min(60.0, std::max(-60.0, double(fVslider1))))))); double fSlow7 = (fSlow5 + fSlow6); double fSlow8 = ((fSlow1 + fSlow7) + 1.0); double fSlow9 = (1.0 - fSlow0); diff --git a/src/sfizz/gen/filters/sfzPeq.hxx b/src/sfizz/gen/filters/sfzPeq.hxx index 50ce20845..bf7f770ce 100644 --- a/src/sfizz/gen/filters/sfzPeq.hxx +++ b/src/sfizz/gen/filters/sfzPeq.hxx @@ -147,7 +147,7 @@ class faustPeq : public sfzFilterDsp { double fSlow0 = (fSmoothEnable ? fConst1 : 0.0); double fSlow1 = (fConst2 * std::max(0.0, std::min(20000.0, std::max(1.0, double(fHslider0))))); double fSlow2 = std::sin(fSlow1); - double fSlow3 = std::max(0.001, std::pow(10.0, (0.050000000000000003 * std::min(60.0, std::max(0.0, double(fVslider0)))))); + double fSlow3 = std::max(0.001, std::pow(10.0, (0.050000000000000003 * std::min(60.0, std::max(-60.0, double(fVslider0)))))); double fSlow4 = std::pow(10.0, (0.025000000000000001 * std::min(60.0, std::max(-120.0, double(fVslider1))))); double fSlow5 = (0.5 * (fSlow2 / (fSlow3 * fSlow4))); double fSlow6 = (fSlow5 + 1.0); diff --git a/plugins/editor/src/editor/GitBuildId.h b/src/sfizz/git-build-id/GitBuildId.h similarity index 88% rename from plugins/editor/src/editor/GitBuildId.h rename to src/sfizz/git-build-id/GitBuildId.h index 1d2e3edf9..827f93a2f 100644 --- a/plugins/editor/src/editor/GitBuildId.h +++ b/src/sfizz/git-build-id/GitBuildId.h @@ -6,7 +6,9 @@ #pragma once +#if defined(__cplusplus) extern "C" { +#endif /** * @brief Short identifier of the current head commit. @@ -14,4 +16,6 @@ extern "C" { */ extern const char* GitBuildId; +#if defined(__cplusplus) } // extern "C" +#endif diff --git a/src/sfizz/import/foreign_instruments/DecentSampler.cpp b/src/sfizz/import/foreign_instruments/DecentSampler.cpp index e20f3ab74..149ba86d1 100644 --- a/src/sfizz/import/foreign_instruments/DecentSampler.cpp +++ b/src/sfizz/import/foreign_instruments/DecentSampler.cpp @@ -202,7 +202,7 @@ void DecentSamplerInstrumentImporter::emitRegionalOpcodes(std::ostream& os, pugi break; case hash("loopEnabled"): os << "loop_mode=" - << ((xmlOpcode.value == "true") ? "loop_continuous" : "one_shot") << "\n"; + << ((xmlOpcode.value == "true") ? "loop_continuous" : "no_loop") << "\n"; break; case hash("attack"): convertToReal("ampeg_attack"); diff --git a/src/sfizz/import/sfizz_import.cpp b/src/sfizz/import/sfizz_import.cpp new file mode 100644 index 000000000..f73fc67dd --- /dev/null +++ b/src/sfizz/import/sfizz_import.cpp @@ -0,0 +1,32 @@ +// 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 "sfizz_import.h" +#include "ForeignInstrument.h" + +bool sfizz_load_or_import_file(sfizz_synth_t* synth, const char* path, const char** format) +{ + const sfz::InstrumentFormatRegistry& ireg = sfz::InstrumentFormatRegistry::getInstance(); + const sfz::InstrumentFormat* ifmt = ireg.getMatchingFormat(path); + + if (!ifmt) { + if (!sfizz_load_file(synth, path)) + return false; + if (format) + *format = nullptr; + } + else { + auto importer = ifmt->createImporter(); + std::string virtualPath = std::string(path) + ".sfz"; + std::string sfzText = importer->convertToSfz(path); + if (!sfizz_load_string(synth, virtualPath.c_str(), sfzText.c_str())) + return false; + if (format) + *format = ifmt->name(); + } + + return true; +} diff --git a/src/sfizz/import/sfizz_import.h b/src/sfizz/import/sfizz_import.h new file mode 100644 index 000000000..7ae1184b1 --- /dev/null +++ b/src/sfizz/import/sfizz_import.h @@ -0,0 +1,38 @@ +// 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 + +#pragma once +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Loads or imports an instrument file. + * + * The file path can be absolute or relative. + * @since 1.0.1 + * + * @param synth The synth. + * @param path A null-terminated string representing a path to an instrument + * in SFZ format, or another format which can be imported. + * @param format An optional pointer to a string pointer, which receives the + * null-terminated name of the format if the file was imported, + * or null if the file was loaded directly as SFZ. + * + * @return @true when file loading went OK, + * @false if some error occured while loading. + * + * @par Thread-safety constraints + * - @b CT: the function must be invoked from the Control thread + * - @b OFF: the function cannot be invoked while a thread is calling @b RT functions + */ +bool sfizz_load_or_import_file(sfizz_synth_t* synth, const char* path, const char** format); + +#ifdef __cplusplus +} // extern "C" +#endif diff --git a/src/sfizz/modulations/ModGenerator.h b/src/sfizz/modulations/ModGenerator.h index 7bb6c3291..55fe46ba0 100644 --- a/src/sfizz/modulations/ModGenerator.h +++ b/src/sfizz/modulations/ModGenerator.h @@ -49,6 +49,15 @@ class ModGenerator { */ virtual void release(const ModKey& sourceKey, NumericId voiceId, unsigned delay) { (void)sourceKey; (void)voiceId; (void)delay; } + /** + * @brief Cancel the release and get back into sustain + * + * @param sourceKey identifier of the source to release + * @param voiceId the particular voice to initialize, if per-voice + * @param delay the frame time when it happens + */ + virtual void cancelRelease(const ModKey& sourceKey, NumericId voiceId, unsigned delay) { (void)sourceKey; (void)voiceId; (void)delay; } + /** * @brief Generate a cycle of the modulator * diff --git a/src/sfizz/modulations/ModMatrix.cpp b/src/sfizz/modulations/ModMatrix.cpp index 35ab05f7b..98a92c866 100644 --- a/src/sfizz/modulations/ModMatrix.cpp +++ b/src/sfizz/modulations/ModMatrix.cpp @@ -281,6 +281,19 @@ void ModMatrix::releaseVoice(NumericId voiceId, NumericId regionI } } +void ModMatrix::cancelRelease(NumericId voiceId, NumericId regionId, unsigned delay) +{ + Impl& impl = *impl_; + + ASSERT(regionId); + + const auto idNumber = static_cast(regionId.number()); + for (auto idx: impl.sourceIndicesForRegion_[idNumber]) { + const Impl::Source& source = impl.sources_[idx]; + source.gen->cancelRelease(source.key, voiceId, delay); + } +} + void ModMatrix::beginCycle(unsigned numFrames) { Impl& impl = *impl_; diff --git a/src/sfizz/modulations/ModMatrix.h b/src/sfizz/modulations/ModMatrix.h index 2cef2c0ed..2aa59fe4a 100644 --- a/src/sfizz/modulations/ModMatrix.h +++ b/src/sfizz/modulations/ModMatrix.h @@ -116,6 +116,11 @@ class ModMatrix { */ void releaseVoice(NumericId voiceId, NumericId regionId, unsigned delay); + /** + * @brief Cancel release for a given voice. + */ + void cancelRelease(NumericId voiceId, NumericId regionId, unsigned delay); + /** * @brief Start modulation processing for the entire cycle. * This clears all the buffers. diff --git a/src/sfizz/modulations/sources/ADSREnvelope.cpp b/src/sfizz/modulations/sources/ADSREnvelope.cpp index 1189791bb..703b5d433 100644 --- a/src/sfizz/modulations/sources/ADSREnvelope.cpp +++ b/src/sfizz/modulations/sources/ADSREnvelope.cpp @@ -13,47 +13,73 @@ namespace sfz { -ADSREnvelopeSource::ADSREnvelopeSource(VoiceManager& manager, MidiState& state) - : voiceManager_(manager), midiState_(state) +ADSREnvelopeSource::ADSREnvelopeSource(VoiceManager& manager) + : voiceManager_(manager) { } -void ADSREnvelopeSource::init(const ModKey& sourceKey, NumericId voiceId, unsigned delay) +ADSREnvelope* getEG(Voice* voice, const ModKey& key) { - Voice* voice = voiceManager_.getVoiceById(voiceId); - if (!voice) { - ASSERTFALSE; - return; + ADSREnvelope* eg = nullptr; + if (!voice) + return eg; + + switch (key.id()) { + case ModId::AmpEG: + eg = voice->getAmplitudeEG(); + break; + case ModId::PitchEG: + eg = voice->getPitchEG(); + break; + case ModId::FilEG: + eg = voice->getFilterEG(); + break; + default: + return eg; } - const Region* region = voice->getRegion(); - ADSREnvelope* eg = nullptr; + return eg; +} + +const EGDescription* getEGDescription(const Region* region, const ModKey& key) +{ const EGDescription* desc = nullptr; + if (!region) + return desc; - switch (sourceKey.id()) { + switch (key.id()) { case ModId::AmpEG: - eg = voice->getAmplitudeEG(); - ASSERT(eg); desc = ®ion->amplitudeEG; break; case ModId::PitchEG: - eg = voice->getPitchEG(); - ASSERT(eg); desc = &*region->pitchEG; break; case ModId::FilEG: - eg = voice->getFilterEG(); - ASSERT(eg); desc = &*region->filterEG; break; default: + return desc; + } + + return desc; +} + +void ADSREnvelopeSource::init(const ModKey& sourceKey, NumericId voiceId, unsigned delay) +{ + Voice* voice = voiceManager_.getVoiceById(voiceId); + if (!voice) { ASSERTFALSE; return; } + const Region* region = voice->getRegion(); + ADSREnvelope* eg = getEG(voice, sourceKey); + const EGDescription* desc = getEGDescription(region, sourceKey); + ASSERT(eg); + const TriggerEvent& triggerEvent = voice->getTriggerEvent(); const float sampleRate = voice->getSampleRate(); - eg->reset(*desc, *region, midiState_, delay, triggerEvent.value, sampleRate); + eg->reset(*desc, *region, delay, triggerEvent.value, sampleRate); } void ADSREnvelopeSource::release(const ModKey& sourceKey, NumericId voiceId, unsigned delay) @@ -64,30 +90,13 @@ void ADSREnvelopeSource::release(const ModKey& sourceKey, NumericId voice return; } - ADSREnvelope* eg = nullptr; - - switch (sourceKey.id()) { - case ModId::AmpEG: - eg = voice->getAmplitudeEG(); - ASSERT(eg); - break; - case ModId::PitchEG: - eg = voice->getPitchEG(); - ASSERT(eg); - break; - case ModId::FilEG: - eg = voice->getFilterEG(); - ASSERT(eg); - break; - default: - ASSERTFALSE; - return; - } + ADSREnvelope* eg = getEG(voice, sourceKey); + ASSERT(eg); eg->startRelease(delay); } -void ADSREnvelopeSource::generate(const ModKey& sourceKey, NumericId voiceId, absl::Span buffer) +void ADSREnvelopeSource::cancelRelease(const ModKey& sourceKey, NumericId voiceId, unsigned delay) { Voice* voice = voiceManager_.getVoiceById(voiceId); if (!voice) { @@ -95,26 +104,23 @@ void ADSREnvelopeSource::generate(const ModKey& sourceKey, NumericId voic return; } - ADSREnvelope* eg = nullptr; + ADSREnvelope* eg = getEG(voice, sourceKey); + ASSERT(eg); - switch (sourceKey.id()) { - case ModId::AmpEG: - eg = voice->getAmplitudeEG(); - ASSERT(eg); - break; - case ModId::PitchEG: - eg = voice->getPitchEG(); - ASSERT(eg); - break; - case ModId::FilEG: - eg = voice->getFilterEG(); - ASSERT(eg); - break; - default: + eg->cancelRelease(delay); +} + +void ADSREnvelopeSource::generate(const ModKey& sourceKey, NumericId voiceId, absl::Span buffer) +{ + Voice* voice = voiceManager_.getVoiceById(voiceId); + if (!voice) { ASSERTFALSE; return; } + ADSREnvelope* eg = getEG(voice, sourceKey); + ASSERT(eg); + eg->getBlock(buffer); } diff --git a/src/sfizz/modulations/sources/ADSREnvelope.h b/src/sfizz/modulations/sources/ADSREnvelope.h index d52a9ca0d..f43722b91 100644 --- a/src/sfizz/modulations/sources/ADSREnvelope.h +++ b/src/sfizz/modulations/sources/ADSREnvelope.h @@ -7,21 +7,20 @@ #pragma once #include "../ModGenerator.h" #include "../../VoiceManager.h" -#include "../../MidiState.h" namespace sfz { class Synth; class ADSREnvelopeSource : public ModGenerator { public: - explicit ADSREnvelopeSource(VoiceManager &manager, MidiState& state); + explicit ADSREnvelopeSource(VoiceManager &manager); void init(const ModKey& sourceKey, NumericId voiceId, unsigned delay) override; void release(const ModKey& sourceKey, NumericId voiceId, unsigned delay) override; + void cancelRelease(const ModKey& sourceKey, NumericId voiceId, unsigned delay) override; void generate(const ModKey& sourceKey, NumericId voiceId, absl::Span buffer) override; private: VoiceManager& voiceManager_; - MidiState& midiState_; }; } // namespace sfz diff --git a/src/sfizz/modulations/sources/ChannelAftertouch.cpp b/src/sfizz/modulations/sources/ChannelAftertouch.cpp index 96658aee0..4f54c67bb 100644 --- a/src/sfizz/modulations/sources/ChannelAftertouch.cpp +++ b/src/sfizz/modulations/sources/ChannelAftertouch.cpp @@ -23,13 +23,6 @@ void ChannelAftertouchSource::init(const ModKey& sourceKey, NumericId voi UNUSED(delay); } -void ChannelAftertouchSource::release(const ModKey& sourceKey, NumericId voiceId, unsigned delay) -{ - UNUSED(sourceKey); - UNUSED(voiceId); - UNUSED(delay); -} - void ChannelAftertouchSource::generate(const ModKey& sourceKey, NumericId voiceId, absl::Span buffer) { UNUSED(sourceKey); diff --git a/src/sfizz/modulations/sources/ChannelAftertouch.h b/src/sfizz/modulations/sources/ChannelAftertouch.h index d162b7ecd..f6ddd5a7d 100644 --- a/src/sfizz/modulations/sources/ChannelAftertouch.h +++ b/src/sfizz/modulations/sources/ChannelAftertouch.h @@ -16,7 +16,6 @@ class ChannelAftertouchSource : public ModGenerator { public: explicit ChannelAftertouchSource(VoiceManager &manager, MidiState& state); void init(const ModKey& sourceKey, NumericId voiceId, unsigned delay) override; - void release(const ModKey& sourceKey, NumericId voiceId, unsigned delay) override; void generate(const ModKey& sourceKey, NumericId voiceId, absl::Span buffer) override; private: diff --git a/src/sfizz/modulations/sources/Controller.cpp b/src/sfizz/modulations/sources/Controller.cpp index f6b16fb03..eafffd56c 100644 --- a/src/sfizz/modulations/sources/Controller.cpp +++ b/src/sfizz/modulations/sources/Controller.cpp @@ -37,8 +37,8 @@ ControllerSource::~ControllerSource() float ControllerSource::Impl::getLastTransformedValue(uint16_t cc, uint8_t curveIndex) const noexcept { ASSERT(res_); - const Curve& curve = res_->curves.getCurve(curveIndex); - const auto lastCCValue = res_->midiState.getCCValue(cc); + const Curve& curve = res_->getCurves().getCurve(curveIndex); + const auto lastCCValue = res_->getMidiState().getCCValue(cc); return curve.evalNormalized(lastCCValue); } @@ -89,16 +89,15 @@ void ControllerSource::generate(const ModKey& sourceKey, NumericId voiceI { const ModKey::Parameters p = sourceKey.parameters(); const Resources& res = *impl_->res_; - const Curve& curve = res.curves.getCurve(p.curve); - const MidiState& ms = res.midiState; + const Curve& curve = res.getCurves().getCurve(p.curve); + const MidiState& ms = res.getMidiState(); bool canShortcut = false; - auto transformValue = [p, &curve](float x) { + auto transformValue = [&] (float x) { return curve.evalNormalized(x); }; - // Ignore the eventual curve for the extended CCs - auto quantize = [p](float x) { + auto quantize = [&] (float x) { if (p.step > 0.0f) return std::trunc(x / p.step) * p.step; @@ -110,9 +109,9 @@ void ControllerSource::generate(const ModKey& sourceKey, NumericId voiceI const auto voice = impl_->voiceManager_->getVoiceById(voiceId); const float fillValue = voice && voice->getTriggerEvent().type == TriggerEventType::NoteOn ? - impl_->res_->midiState.getPolyAftertouch(voice->getTriggerEvent().number) : 0.0f; + impl_->res_->getMidiState().getPolyAftertouch(voice->getTriggerEvent().number) : 0.0f; - sfz::fill(buffer, quantize(fillValue)); + sfz::fill(buffer, quantize(transformValue(fillValue))); canShortcut = true; break; } @@ -122,7 +121,7 @@ void ControllerSource::generate(const ModKey& sourceKey, NumericId voiceI voice && voice->getTriggerEvent().type == TriggerEventType::NoteOn ? voice->getTriggerEvent().value : 0.0f; - sfz::fill(buffer, quantize(fillValue)); + sfz::fill(buffer, quantize(transformValue(fillValue))); canShortcut = true; break; } @@ -132,42 +131,42 @@ void ControllerSource::generate(const ModKey& sourceKey, NumericId voiceI voice && voice->getTriggerEvent().type == TriggerEventType::NoteOff ? voice->getTriggerEvent().value : 0.0f; - sfz::fill(buffer, quantize(fillValue)); + sfz::fill(buffer, quantize(transformValue(fillValue))); canShortcut = true; break; } case ExtendedCCs::keyboardNoteNumber: { const auto voice = impl_->voiceManager_->getVoiceById(voiceId); const float fillValue = voice ? normalize7Bits(voice->getTriggerEvent().number) : 0.0f; - sfz::fill(buffer, quantize(fillValue)); + sfz::fill(buffer, quantize(transformValue(fillValue))); canShortcut = true; break; } case ExtendedCCs::keyboardNoteGate: { const auto voice = impl_->voiceManager_->getVoiceById(voiceId); const float fillValue = voice ? voice->getExtendedCCValues().noteGate : 0.0f; - sfz::fill(buffer, quantize(fillValue)); + sfz::fill(buffer, quantize(transformValue(fillValue))); canShortcut = true; break; } case ExtendedCCs::unipolarRandom: { const auto voice = impl_->voiceManager_->getVoiceById(voiceId); const float fillValue = voice ? voice->getExtendedCCValues().unipolar : 0.0f; - sfz::fill(buffer, quantize(fillValue)); + sfz::fill(buffer, quantize(transformValue(fillValue))); canShortcut = true; break; } case ExtendedCCs::bipolarRandom: { const auto voice = impl_->voiceManager_->getVoiceById(voiceId); const float fillValue = voice ? voice->getExtendedCCValues().bipolar : 0.0f; - sfz::fill(buffer, quantize(fillValue)); + sfz::fill(buffer, quantize(transformValue(fillValue))); canShortcut = true; break; } case ExtendedCCs::alternate: { const auto voice = impl_->voiceManager_->getVoiceById(voiceId); const float fillValue = voice ? voice->getExtendedCCValues().alternate : 0.0f; - sfz::fill(buffer, quantize(fillValue)); + sfz::fill(buffer, quantize(transformValue(fillValue))); canShortcut = true; break; } diff --git a/src/sfizz/modulations/sources/Controller.h b/src/sfizz/modulations/sources/Controller.h index 2736bb5be..695b36df4 100644 --- a/src/sfizz/modulations/sources/Controller.h +++ b/src/sfizz/modulations/sources/Controller.h @@ -11,7 +11,7 @@ namespace sfz { -struct Resources; +class Resources; class ControllerSource : public ModGenerator { public: diff --git a/src/sfizz/modulations/sources/FlexEnvelope.cpp b/src/sfizz/modulations/sources/FlexEnvelope.cpp index 5184bc5ee..347886aaa 100644 --- a/src/sfizz/modulations/sources/FlexEnvelope.cpp +++ b/src/sfizz/modulations/sources/FlexEnvelope.cpp @@ -17,6 +17,7 @@ namespace sfz { FlexEnvelopeSource::FlexEnvelopeSource(VoiceManager& manager) : voiceManager_(manager) { + } void FlexEnvelopeSource::init(const ModKey& sourceKey, NumericId voiceId, unsigned delay) @@ -66,6 +67,26 @@ void FlexEnvelopeSource::release(const ModKey& sourceKey, NumericId voice eg->release(delay); } +void FlexEnvelopeSource::cancelRelease(const ModKey& sourceKey, NumericId voiceId, unsigned delay) +{ + unsigned egIndex = sourceKey.parameters().N; + + Voice* voice = voiceManager_.getVoiceById(voiceId); + if (!voice) { + ASSERTFALSE; + return; + } + + const Region* region = voice->getRegion(); + if (egIndex >= region->flexEGs.size()) { + ASSERTFALSE; + return; + } + + FlexEnvelope* eg = voice->getFlexEG(egIndex); + eg->cancelRelease(delay); +} + void FlexEnvelopeSource::generate(const ModKey& sourceKey, NumericId voiceId, absl::Span buffer) { unsigned egIndex = sourceKey.parameters().N; diff --git a/src/sfizz/modulations/sources/FlexEnvelope.h b/src/sfizz/modulations/sources/FlexEnvelope.h index 1868ab109..81b7a2f78 100644 --- a/src/sfizz/modulations/sources/FlexEnvelope.h +++ b/src/sfizz/modulations/sources/FlexEnvelope.h @@ -16,6 +16,7 @@ class FlexEnvelopeSource : public ModGenerator { explicit FlexEnvelopeSource(VoiceManager& manager); void init(const ModKey& sourceKey, NumericId voiceId, unsigned delay) override; void release(const ModKey& sourceKey, NumericId voiceId, unsigned delay) override; + void cancelRelease(const ModKey& sourceKey, NumericId voiceId, unsigned delay) override; void generate(const ModKey& sourceKey, NumericId voiceId, absl::Span buffer) override; private: diff --git a/src/sfizz/modulations/sources/PolyAftertouch.cpp b/src/sfizz/modulations/sources/PolyAftertouch.cpp index ce91f7bbb..5c4ef22c3 100644 --- a/src/sfizz/modulations/sources/PolyAftertouch.cpp +++ b/src/sfizz/modulations/sources/PolyAftertouch.cpp @@ -23,13 +23,6 @@ void PolyAftertouchSource::init(const ModKey& sourceKey, NumericId voiceI UNUSED(delay); } -void PolyAftertouchSource::release(const ModKey& sourceKey, NumericId voiceId, unsigned delay) -{ - UNUSED(sourceKey); - UNUSED(voiceId); - UNUSED(delay); -} - void PolyAftertouchSource::generate(const ModKey& sourceKey, NumericId voiceId, absl::Span buffer) { UNUSED(sourceKey); diff --git a/src/sfizz/modulations/sources/PolyAftertouch.h b/src/sfizz/modulations/sources/PolyAftertouch.h index c70ea360b..5d37ee2a1 100644 --- a/src/sfizz/modulations/sources/PolyAftertouch.h +++ b/src/sfizz/modulations/sources/PolyAftertouch.h @@ -16,7 +16,6 @@ class PolyAftertouchSource : public ModGenerator { public: explicit PolyAftertouchSource(VoiceManager &manager, MidiState& state); void init(const ModKey& sourceKey, NumericId voiceId, unsigned delay) override; - void release(const ModKey& sourceKey, NumericId voiceId, unsigned delay) override; void generate(const ModKey& sourceKey, NumericId voiceId, absl::Span buffer) override; private: diff --git a/src/sfizz/parser/Parser.cpp b/src/sfizz/parser/Parser.cpp index 40306431c..5ae4aef81 100644 --- a/src/sfizz/parser/Parser.cpp +++ b/src/sfizz/parser/Parser.cpp @@ -5,6 +5,7 @@ // If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz #include "Parser.h" +#include "ParserListener.h" #include "ParserPrivate.h" #include "absl/memory/memory.h" #include diff --git a/src/sfizz/parser/Parser.h b/src/sfizz/parser/Parser.h index 1be513611..e83fc1ada 100644 --- a/src/sfizz/parser/Parser.h +++ b/src/sfizz/parser/Parser.h @@ -16,6 +16,7 @@ namespace sfz { class Reader; +class ParserListener; struct SourceLocation; struct SourceRange; @@ -50,19 +51,7 @@ class Parser { size_t getErrorCount() const noexcept { return _errorCount; } size_t getWarningCount() const noexcept { return _warningCount; } - class Listener { - public: - // low-level parsing - virtual void onParseBegin() {} - virtual void onParseEnd() {} - virtual void onParseHeader(const SourceRange& /*range*/, const std::string& /*header*/) {} - virtual void onParseOpcode(const SourceRange& /*rangeOpcode*/, const SourceRange& /*rangeValue*/, const std::string& /*name*/, const std::string& /*value*/) {} - virtual void onParseError(const SourceRange& /*range*/, const std::string& /*message*/) {} - virtual void onParseWarning(const SourceRange& /*range*/, const std::string& /*message*/) {} - - // high-level parsing - virtual void onParseFullBlock(const std::string& /*header*/, const std::vector& /*opcodes*/) {} - }; + using Listener = ParserListener; void setListener(Listener* listener) noexcept { _listener = listener; } diff --git a/src/sfizz/parser/ParserListener.h b/src/sfizz/parser/ParserListener.h new file mode 100644 index 000000000..fa208ff97 --- /dev/null +++ b/src/sfizz/parser/ParserListener.h @@ -0,0 +1,29 @@ +// 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 + +#pragma once +#include +#include + +namespace sfz { +struct SourceRange; +struct Opcode; + +class ParserListener { +public: + // low-level parsing + virtual void onParseBegin() {} + virtual void onParseEnd() {} + virtual void onParseHeader(const SourceRange& /*range*/, const std::string& /*header*/) {} + virtual void onParseOpcode(const SourceRange& /*rangeOpcode*/, const SourceRange& /*rangeValue*/, const std::string& /*name*/, const std::string& /*value*/) {} + virtual void onParseError(const SourceRange& /*range*/, const std::string& /*message*/) {} + virtual void onParseWarning(const SourceRange& /*range*/, const std::string& /*message*/) {} + + // high-level parsing + virtual void onParseFullBlock(const std::string& /*header*/, const std::vector& /*opcodes*/) {} +}; + +} // namespace sfz diff --git a/src/sfizz/sfizz.cpp b/src/sfizz/sfizz.cpp index 2a1d2d9d8..2d7d58c0a 100644 --- a/src/sfizz/sfizz.cpp +++ b/src/sfizz/sfizz.cpp @@ -353,12 +353,12 @@ void sfz::Sfizz::allSoundOff() noexcept void sfz::Sfizz::addExternalDefinition(const std::string& id, const std::string& value) { - synth->synth.getParser().addExternalDefinition(id, value); + synth->synth.addExternalDefinition(id, value); } void sfz::Sfizz::clearExternalDefinitions() { - synth->synth.getParser().clearExternalDefinitions(); + synth->synth.clearExternalDefinitions(); } std::string sfz::Sfizz::exportMidnam(const std::string& model) const diff --git a/src/sfizz/sfizz_wrapper.cpp b/src/sfizz/sfizz_wrapper.cpp index 5166081d1..4b191ab50 100644 --- a/src/sfizz/sfizz_wrapper.cpp +++ b/src/sfizz/sfizz_wrapper.cpp @@ -335,12 +335,12 @@ void sfizz_all_sound_off(sfizz_synth_t* synth) void sfizz_add_external_definitions(sfizz_synth_t* synth, const char* id, const char* value) { - synth->synth.getParser().addExternalDefinition(id, value); + synth->synth.addExternalDefinition(id, value); } void sfizz_clear_external_definitions(sfizz_synth_t* synth) { - synth->synth.getParser().clearExternalDefinitions(); + synth->synth.clearExternalDefinitions(); } unsigned int sfizz_get_num_key_labels(sfizz_synth_t* synth) diff --git a/src/sfizz/utility/c++17/AlignedMemorySupport.cpp b/src/sfizz/utility/c++17/AlignedMemorySupport.cpp new file mode 100644 index 000000000..834f207f0 --- /dev/null +++ b/src/sfizz/utility/c++17/AlignedMemorySupport.cpp @@ -0,0 +1,74 @@ +// 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 + +#if defined(SFIZZ_IMPLEMENT_CXX17_ALIGNED_NEW_SUPPORT) +#include +#if defined(_WIN32) +# include +#else +# include +#endif + +void* operator new(std::size_t count, std::align_val_t al, const std::nothrow_t&) noexcept +{ + void *ptr; +#if defined(_WIN32) + ptr = ::_aligned_malloc(count, static_cast(al)); +#else + if (::posix_memalign(&ptr, static_cast(al), count) != 0) + ptr = nullptr; +#endif + return ptr; +} + +void operator delete(void* ptr, std::align_val_t, const std::nothrow_t&) noexcept +{ +#if defined(_WIN32) + ::_aligned_free(ptr); +#else + ::free(ptr); +#endif +} + +//------------------------------------------------------------------------------ + +void* operator new(std::size_t count, std::align_val_t al) +{ + void *ptr = operator new(count, al, std::nothrow); + if (!ptr) + throw std::bad_alloc(); + return ptr; +} + +void operator delete(void* ptr, std::align_val_t al) noexcept +{ + operator delete(ptr, al, std::nothrow); +} + +//------------------------------------------------------------------------------ + +void* operator new[](std::size_t count, std::align_val_t al) +{ + return operator new(count, al); +} + +void* operator new[](std::size_t count, std::align_val_t al, const std::nothrow_t& tag) noexcept +{ + return operator new(count, al, tag); +} + +void operator delete[](void* ptr, std::align_val_t al) noexcept +{ + operator delete(ptr, al); +} + +void operator delete[](void* ptr, std::align_val_t al, const std::nothrow_t& tag) noexcept +{ + operator delete(ptr, al, tag); +} + + +#endif // defined(SFIZZ_IMPLEMENT_CXX17_ALIGNED_NEW_SUPPORT) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 2e5f70a9d..fa7084584 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -49,12 +49,17 @@ set(SFIZZ_TEST_SOURCES LFOT.cpp MessagingT.cpp OversamplerT.cpp + MemoryT.cpp DataHelpers.h DataHelpers.cpp ) add_executable(sfizz_tests ${SFIZZ_TEST_SOURCES}) target_link_libraries(sfizz_tests PRIVATE sfizz::internal sfizz::static sfizz::spin_mutex sfizz::jsl sfizz::filesystem) +if(APPLE AND CMAKE_OSX_DEPLOYMENT_TARGET VERSION_LESS "10.12") + # workaround for incomplete C++17 runtime on macOS + target_compile_definitions(sfizz_tests PRIVATE "CATCH_CONFIG_NO_CPP17_UNCAUGHT_EXCEPTIONS") +endif() sfizz_enable_lto_if_needed(sfizz_tests) sfizz_enable_fast_math(sfizz_tests) catch_discover_tests(sfizz_tests) diff --git a/tests/DirectRegionT.cpp b/tests/DirectRegionT.cpp index f54bbfdc8..b86ff5f44 100644 --- a/tests/DirectRegionT.cpp +++ b/tests/DirectRegionT.cpp @@ -43,7 +43,7 @@ TEST_CASE("[Direct Region Tests] Release and release key") Layer layer { region, midiState }; layer.delayedSustainReleases_.reserve(config::delayedReleaseVoices); midiState.ccEvent(0, 64, 0.0f); - layer.registerCC(64, 0.0f); + layer.updateCCState(64, 0.0f); REQUIRE( !layer.registerNoteOn(63, 0.5f, 0.0f) ); REQUIRE( layer.registerNoteOff(63, 0.5f, 0.0f) ); } @@ -53,8 +53,7 @@ TEST_CASE("[Direct Region Tests] Release and release key") Layer layer { region, midiState }; layer.delayedSustainReleases_.reserve(config::delayedReleaseVoices); midiState.ccEvent(0, 64, 1.0f); - layer.registerCC(64, 1.0f); - REQUIRE( !layer.registerCC(64, 1.0f) ); + layer.updateCCState(64, 1.0f); REQUIRE( !layer.registerNoteOn(63, 0.5f, 0.0f) ); REQUIRE( layer.registerNoteOff(63, 0.5f, 0.0f) ); } @@ -65,7 +64,7 @@ TEST_CASE("[Direct Region Tests] Release and release key") Layer layer { region, midiState }; layer.delayedSustainReleases_.reserve(config::delayedReleaseVoices); midiState.ccEvent(0, 64, 0.0f); - layer.registerCC(64, 0.0f); + layer.updateCCState(64, 0.0f); REQUIRE( !layer.registerNoteOn(63, 0.5f, 0.0f) ); REQUIRE( layer.registerNoteOff(63, 0.5f, 0.0f) ); } @@ -76,7 +75,7 @@ TEST_CASE("[Direct Region Tests] Release and release key") Layer layer { region, midiState }; layer.delayedSustainReleases_.reserve(config::delayedReleaseVoices); midiState.ccEvent(0, 64, 1.0f); - layer.registerCC(64, 1.0f); + layer.updateCCState(64, 1.0f); midiState.noteOnEvent(0, 63, 0.5f); REQUIRE( !layer.registerNoteOn(63, 0.5f, 0.0f) ); REQUIRE( !layer.registerNoteOff(63, 0.5f, 0.0f) ); @@ -93,7 +92,7 @@ TEST_CASE("[Direct Region Tests] Release and release key") Layer layer { region, midiState }; layer.delayedSustainReleases_.reserve(config::delayedReleaseVoices); midiState.ccEvent(0, 64, 1.0f); - layer.registerCC(64, 1.0f); + layer.updateCCState(64, 1.0f); midiState.noteOnEvent(0, 63, 0.5f); REQUIRE( !layer.registerNoteOn(63, 0.5f, 0.0f) ); midiState.noteOnEvent(0, 64, 0.6f); @@ -114,7 +113,7 @@ TEST_CASE("[Direct Region Tests] Release and release key") Layer layer { region, midiState }; layer.delayedSustainReleases_.reserve(config::delayedReleaseVoices); midiState.ccEvent(0, 64, 1.0f); - layer.registerCC(64, 1.0f); + layer.updateCCState(64, 1.0f); midiState.noteOnEvent(0, 63, 0.5f); REQUIRE( !layer.registerNoteOn(63, 0.5f, 0.0f) ); midiState.noteOnEvent(0, 66, 0.6f); diff --git a/tests/EGDescriptionT.cpp b/tests/EGDescriptionT.cpp index ba0902e21..5813ce972 100644 --- a/tests/EGDescriptionT.cpp +++ b/tests/EGDescriptionT.cpp @@ -45,7 +45,7 @@ TEST_CASE("[EGDescription] Delay range") //REQUIRE(eg.getDelay(state, 127_norm) == 0.0f); state.ccEvent(0, 63, 127_norm); REQUIRE(eg.getDelay(state, 127_norm) == 1.0f); - REQUIRE(eg.getDelay(state, 0_norm) == 2.27f); + REQUIRE(eg.getDelay(state, 0_norm, 1) == 2.27f); //eg.ccDelay[63] = 127.0f; //REQUIRE(eg.getDelay(state, 0_norm) == 100.0f); eg.ccDelay[63] = 1.27f; diff --git a/tests/FilesT.cpp b/tests/FilesT.cpp index c1d568b21..b65b9d8bc 100644 --- a/tests/FilesT.cpp +++ b/tests/FilesT.cpp @@ -8,6 +8,7 @@ #include "sfizz/Synth.h" #include "sfizz/Voice.h" #include "sfizz/SfzHelpers.h" +#include "sfizz/parser/Parser.h" #include "sfizz/modulations/ModId.h" #include "sfizz/modulations/ModKey.h" #include "catch2/catch.hpp" @@ -148,10 +149,10 @@ TEST_CASE("[Files] Group from AVL") REQUIRE(synth.getRegionView(i)->keyRange == Range(36, 36)); } - almostEqualRanges(synth.getRegionView(0)->velocityRange, { 1_norm, 26_norm }); - almostEqualRanges(synth.getRegionView(1)->velocityRange, { 27_norm, 52_norm }); - almostEqualRanges(synth.getRegionView(2)->velocityRange, { 53_norm, 77_norm }); - almostEqualRanges(synth.getRegionView(3)->velocityRange, { 78_norm, 102_norm }); + almostEqualRanges(synth.getRegionView(0)->velocityRange, { 1_norm, std::nextafter(27_norm, 0.0f) }); + almostEqualRanges(synth.getRegionView(1)->velocityRange, { 27_norm, std::nextafter(53_norm, 0.0f) }); + almostEqualRanges(synth.getRegionView(2)->velocityRange, { 53_norm, std::nextafter(78_norm, 0.0f) }); + almostEqualRanges(synth.getRegionView(3)->velocityRange, { 78_norm, std::nextafter(103_norm, 0.0f) }); almostEqualRanges(synth.getRegionView(4)->velocityRange, { 103_norm, 127_norm }); } @@ -245,7 +246,8 @@ TEST_CASE("[Files] Pizz basic") REQUIRE(synth.getRegionView(i)->keyRange == Range(12, 22)); almostEqualRanges(synth.getRegionView(i)->velocityRange, { 97_norm, 127_norm }); REQUIRE(synth.getRegionView(i)->pitchKeycenter == 21); - almostEqualRanges(synth.getRegionView(i)->ccConditions.getWithDefault(107), { 0_norm, 13_norm }); + almostEqualRanges(synth.getRegionView(i)->ccConditions.getWithDefault(107), + { 0_norm, std::nextafter(14_norm, 0.0f) }); // Fill in the gap from 13_norm to "almost 14_norm" } almostEqualRanges(synth.getRegionView(0)->randRange, { 0, 0.25 }); almostEqualRanges(synth.getRegionView(1)->randRange, { 0.25, 0.5 }); @@ -272,7 +274,7 @@ TEST_CASE("[Files] Channels (channels_multi.sfz)") { Synth synth; synth.loadSfzFile(fs::current_path() / "tests/TestFiles/channels_multi.sfz"); - REQUIRE(synth.getNumRegions() == 10); + REQUIRE(synth.getNumRegions() == 12); int regionNumber = 0; const Region* region = nullptr; @@ -325,14 +327,27 @@ TEST_CASE("[Files] Channels (channels_multi.sfz)") REQUIRE(!region->isOscillator()); REQUIRE(region->oscillatorEnabled == OscillatorEnabled::Off); - // implicit wavetable (sound file < 3000 frames) + // implicit wavetable (sound file < 3000 frames and wavetable tags) region = synth.getRegionView(regionNumber++); - REQUIRE(region->sampleId->filename() == "ramp_wave.wav"); - REQUIRE(!region->isStereo()); + REQUIRE(region->sampleId->filename() == "wavetables/surge.wav"); + REQUIRE(!region->isGenerator()); + REQUIRE(region->isOscillator()); + REQUIRE(region->oscillatorEnabled == OscillatorEnabled::Auto); + + // Parse oscillator=auto and same as above + region = synth.getRegionView(regionNumber++); + REQUIRE(region->sampleId->filename() == "wavetables/surge.wav"); REQUIRE(!region->isGenerator()); REQUIRE(region->isOscillator()); REQUIRE(region->oscillatorEnabled == OscillatorEnabled::Auto); + // implicit non wavetable (sound file < 3000 frames but no wavetable tags) + region = synth.getRegionView(regionNumber++); + REQUIRE(region->sampleId->filename() == "short_non_wavetable.wav"); + REQUIRE(!region->isGenerator()); + REQUIRE(!region->isOscillator()); + REQUIRE(region->oscillatorEnabled == OscillatorEnabled::Auto); + // implicit non-wavetable (sound file >= 3000 frames) region = synth.getRegionView(regionNumber++); REQUIRE(region->sampleId->filename() == "snare.wav"); @@ -421,7 +436,7 @@ TEST_CASE("[Files] Default path is ignored for generators") TEST_CASE("[Files] Set CC applies properly") { Synth synth; - const auto& midiState = synth.getResources().midiState; + const MidiState& midiState = synth.getResources().getMidiState(); synth.loadSfzFile(fs::current_path() / "tests/TestFiles/set_cc.sfz"); REQUIRE(midiState.getCCValue(142) == 63_norm); REQUIRE(midiState.getCCValue(61) == 122_norm); @@ -430,7 +445,7 @@ TEST_CASE("[Files] Set CC applies properly") TEST_CASE("[Files] Set HDCC applies properly") { sfz::Synth synth; - const auto& midiState = synth.getResources().midiState; + const MidiState& midiState = synth.getResources().getMidiState(); synth.loadSfzFile(fs::current_path() / "tests/TestFiles/set_hdcc.sfz"); REQUIRE(midiState.getCCValue(142) == Approx(0.5678)); REQUIRE(midiState.getCCValue(61) == Approx(0.1234)); @@ -439,7 +454,7 @@ TEST_CASE("[Files] Set HDCC applies properly") TEST_CASE("[Files] Set RealCC applies properly") { sfz::Synth synth; - const auto& midiState = synth.getResources().midiState; + const MidiState& midiState = synth.getResources().getMidiState(); synth.loadSfzFile(fs::current_path() / "tests/TestFiles/set_realcc.sfz"); REQUIRE(midiState.getCCValue(142) == Approx(0.5678)); REQUIRE(midiState.getCCValue(61) == Approx(0.1234)); diff --git a/tests/FlexEGT.cpp b/tests/FlexEGT.cpp index a7a2e634e..a35711e65 100644 --- a/tests/FlexEGT.cpp +++ b/tests/FlexEGT.cpp @@ -8,6 +8,7 @@ #include "sfizz/Synth.h" #include "sfizz/AudioBuffer.h" #include "sfizz/FlexEnvelope.h" +#include "sfizz/modulations/ModMatrix.h" #include "catch2/catch.hpp" #include "TestHelpers.h" #include @@ -42,7 +43,7 @@ TEST_CASE("[FlexEG] Values") REQUIRE( egDescription.points[4].time == .4_a ); REQUIRE( egDescription.points[4].level == 1.0_a ); REQUIRE( egDescription.sustain == 3 ); - REQUIRE(synth.getResources().modMatrix.toDotGraph() == createDefaultGraph({ + REQUIRE(synth.getResources().getModMatrix().toDotGraph() == createDefaultGraph({ R"("EG 1 {0}" -> "Amplitude {0}")", })); } @@ -68,7 +69,7 @@ TEST_CASE("[FlexEG] Default values") REQUIRE( egDescription.points[1].level == 0.0_a ); REQUIRE( egDescription.points[2].time == .1_a ); REQUIRE( egDescription.points[2].level == .25_a ); - REQUIRE( synth.getResources().modMatrix.toDotGraph() == createDefaultGraph({}) ); + REQUIRE( synth.getResources().getModMatrix().toDotGraph() == createDefaultGraph({}) ); } TEST_CASE("[FlexEG] Connections") @@ -86,7 +87,7 @@ TEST_CASE("[FlexEG] Connections") REQUIRE(synth.getNumRegions() == 6); REQUIRE( synth.getRegionView(0)->flexEGs.size() == 1 ); REQUIRE( synth.getRegionView(0)->flexEGs[0].points.size() == 2 ); - REQUIRE( synth.getResources().modMatrix.toDotGraph() == createDefaultGraph({ + REQUIRE( synth.getResources().getModMatrix().toDotGraph() == createDefaultGraph({ R"("EG 1 {0}" -> "Amplitude {0}")", R"("EG 1 {1}" -> "Pan {1}")", R"("EG 1 {2}" -> "Width {2}")", @@ -106,7 +107,7 @@ TEST_CASE("[FlexEG] Coarse numerical envelope test (No release)") eg1_time2=0.5 eg1_level2=1 eg1_sustain=2 )"); - sfz::FlexEnvelope envelope; + sfz::FlexEnvelope envelope(synth.getResources()); REQUIRE(synth.getNumRegions() == 1); REQUIRE( synth.getRegionView(0)->flexEGs.size() == 1 ); envelope.configure(&synth.getRegionView(0)->flexEGs[0]); @@ -131,7 +132,7 @@ TEST_CASE("[FlexEG] Detailed numerical envelope test") eg1_time2=0.5 eg1_level2=1 eg1_sustain=2 )"); - sfz::FlexEnvelope envelope; + sfz::FlexEnvelope envelope(synth.getResources()); REQUIRE(synth.getNumRegions() == 1); REQUIRE( synth.getRegionView(0)->flexEGs.size() == 1 ); envelope.configure(&synth.getRegionView(0)->flexEGs[0]); @@ -154,7 +155,7 @@ TEST_CASE("[FlexEG] Coarse numerical envelope test (with release)") eg1_time2=0.5 eg1_level2=1 eg1_sustain=2 )"); - sfz::FlexEnvelope envelope; + sfz::FlexEnvelope envelope(synth.getResources()); REQUIRE(synth.getNumRegions() == 1); REQUIRE( synth.getRegionView(0)->flexEGs.size() == 1 ); envelope.configure(&synth.getRegionView(0)->flexEGs[0]); @@ -183,7 +184,7 @@ TEST_CASE("[FlexEG] Detailed numerical envelope test (with release and release r eg1_time3=0.5 eg1_level3=0 eg1_sustain=2 )"); - sfz::FlexEnvelope envelope; + sfz::FlexEnvelope envelope(synth.getResources()); REQUIRE(synth.getNumRegions() == 1); REQUIRE( synth.getRegionView(0)->flexEGs.size() == 1 ); envelope.configure(&synth.getRegionView(0)->flexEGs[0]); @@ -215,7 +216,7 @@ TEST_CASE("[FlexEG] Coarse numerical envelope test (with shapes)") eg1_sustain=2 eg1_time3=0.5 eg1_level3=0 eg1_shape3=4 )"); - sfz::FlexEnvelope envelope; + sfz::FlexEnvelope envelope(synth.getResources()); REQUIRE(synth.getNumRegions() == 1); REQUIRE( synth.getRegionView(0)->flexEGs.size() == 1 ); envelope.configure(&synth.getRegionView(0)->flexEGs[0]); @@ -243,7 +244,7 @@ TEST_CASE("[FlexEG] Detailed numerical envelope test (with shapes)") eg1_time3=0.5 eg1_level3=0 eg1_shape3=4 eg1_sustain=2 )"); - sfz::FlexEnvelope envelope; + sfz::FlexEnvelope envelope(synth.getResources()); REQUIRE(synth.getNumRegions() == 1); REQUIRE( synth.getRegionView(0)->flexEGs.size() == 1 ); envelope.configure(&synth.getRegionView(0)->flexEGs[0]); @@ -275,7 +276,7 @@ TEST_CASE("[FlexEG] Zero delay transitions") eg1_time3=1 eg1_level3=.5 eg1_sustain=3 eg1_time4=1 eg1_level4=1 )"); - sfz::FlexEnvelope envelope; + sfz::FlexEnvelope envelope(synth.getResources()); REQUIRE(synth.getNumRegions() == 1); REQUIRE(synth.getRegionView(0)->flexEGs.size() == 1); envelope.configure(&synth.getRegionView(0)->flexEGs[0]); @@ -302,7 +303,7 @@ TEST_CASE("[FlexEG] Early release") eg1_time2=1.0 eg1_level2=1.0 eg1_sustain=2 eg1_time3=1.0 eg1_level3=0.0 )"); - sfz::FlexEnvelope envelope; + sfz::FlexEnvelope envelope(synth.getResources()); REQUIRE(synth.getNumRegions() == 1); REQUIRE(synth.getRegionView(0)->flexEGs.size() == 1); envelope.configure(&synth.getRegionView(0)->flexEGs[0]); @@ -416,3 +417,43 @@ TEST_CASE("[FlexEG] Free-running flex AmpEG (no sustain)") synth.renderBlock(buffer); REQUIRE( synth.getNumActiveVoices() == 0 ); } + +TEST_CASE("[FlexEG] Modulation of time and level") +{ + sfz::Synth synth; + + synth.loadSfzString(fs::current_path(), R"( + sample=*noise + eg1_time1=0 eg1_level1=1 + eg1_time2=0.7 eg1_level2=0.5 + eg1_time2_oncc1=-0.7 eg1_level2_oncc2=0.5 + eg1_time3=0.3 eg1_level3=0.0 + )"); + + REQUIRE( synth.getNumRegions() == 1 ); + const sfz::Region* region = synth.getRegionView(0); + REQUIRE( region->flexEGs.size() == 1 ); + const sfz::FlexEGDescription& desc = synth.getRegionView(0)->flexEGs[0]; + REQUIRE( desc.points.size() == 4 ); + + REQUIRE( desc.points[2].time == Approx(0.7f) ); + REQUIRE( desc.points[2].level == Approx(0.5f) ); + + sfz::MidiState state; + + REQUIRE( desc.points[2].getTime(state) == Approx(0.7f) ); + state.ccEvent(0, 1, 0.0f); + REQUIRE( desc.points[2].getTime(state) == Approx(0.7f) ); + state.ccEvent(0, 1, 0.5f); + REQUIRE( desc.points[2].getTime(state) == Approx(0.35f) ); + state.ccEvent(0, 1, 1.0f); + REQUIRE( desc.points[2].getTime(state) == Approx(0.0f) ); + + REQUIRE( desc.points[2].getLevel(state) == Approx(0.5f) ); + state.ccEvent(0, 2, 0.0f); + REQUIRE( desc.points[2].getLevel(state) == Approx(0.5f) ); + state.ccEvent(0, 2, 0.5f); + REQUIRE( desc.points[2].getLevel(state) == Approx(0.75f) ); + state.ccEvent(0, 2, 1.0f); + REQUIRE( desc.points[2].getLevel(state) == Approx(1.0f) ); +} diff --git a/tests/MemoryT.cpp b/tests/MemoryT.cpp new file mode 100644 index 000000000..bf7b04975 --- /dev/null +++ b/tests/MemoryT.cpp @@ -0,0 +1,41 @@ +// 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 "catch2/catch.hpp" +#include +#include +#include + +TEST_CASE("[Memory] Aligned unique pointers") +{ + constexpr size_t numAllocations = 128; + constexpr size_t alignment = 1024; + + static size_t numLiveObjects; + numLiveObjects = 0; + + struct Object { + explicit Object(size_t id) : id(id) { ++numLiveObjects; } + ~Object() { --numLiveObjects; } + size_t id {}; + }; + + std::vector> ptrs(numAllocations); + + for (size_t i = 0; i < numAllocations; ++i) { + ptrs[i] = jsl::make_aligned(i); + REQUIRE(numLiveObjects == i + 1); + + Object *obj = ptrs[i].get(); + REQUIRE(obj->id == i); + + uintptr_t addr = reinterpret_cast(obj); + REQUIRE(addr % alignment == 0); + } + + ptrs.clear(); + REQUIRE(numLiveObjects == 0); +} diff --git a/tests/MidiStateT.cpp b/tests/MidiStateT.cpp index 5bec78eeb..f29c71983 100644 --- a/tests/MidiStateT.cpp +++ b/tests/MidiStateT.cpp @@ -11,9 +11,11 @@ */ #include "sfizz/MidiState.h" +#include "sfizz/Synth.h" #include "sfizz/SfzHelpers.h" #include "catch2/catch.hpp" #include "absl/strings/string_view.h" +#include "TestHelpers.h" using namespace Catch::literals; using namespace sfz::literals; @@ -55,19 +57,6 @@ TEST_CASE("[MidiState] Reset") REQUIRE(state.getCCValue(123) == 0_norm); } -TEST_CASE("[MidiState] Reset all controllers") -{ - sfz::MidiState state; - state.pitchBendEvent(20, 0.7f); - state.ccEvent(10, 122, 124_norm); - REQUIRE(state.getPitchBend() == 0.7f); - REQUIRE(state.getCCValue(122) == 124_norm); - state.resetAllControllers(30); - REQUIRE(state.getPitchBend() == 0.0f); - REQUIRE(state.getCCValue(122) == 0_norm); - REQUIRE(state.getCCValue(4) == 0_norm); -} - TEST_CASE("[MidiState] Set and get note velocities") { sfz::MidiState state; @@ -88,5 +77,150 @@ TEST_CASE("[MidiState] Last note velocity") sfz::MidiState state; state.noteOnEvent(0, 62, 64_norm); state.noteOnEvent(0, 60, 10_norm); - REQUIRE(state.getLastVelocity() == 10_norm); + REQUIRE(state.getVelocityOverride() == 64_norm); +} + + +TEST_CASE("[CC] Extended CCs on offset and delay") +{ + sfz::Synth synth; + std::vector messageList; + sfz::Client client(&messageList); + client.setReceiveCallback(&simpleMessageReceiver); + sfz::AudioBuffer buffer { 2, static_cast(synth.getSamplesPerBlock()) }; + synth.setSampleRate(48000); + + SECTION("CC131 - Note on velocity") + { + synth.loadSfzString(fs::current_path() / "tests/TestFiles/extended_ccs.sfz", R"( + key=60 delay_cc131=1 sample=kick.wav + key=61 offset_cc131=100 sample=snare.wav + )"); + synth.hdNoteOn(0, 60, 0.0f); + synth.hdNoteOn(0, 60, 0.5f); + synth.hdNoteOn(0, 61, 0.0f); + synth.hdNoteOn(0, 61, 0.5f); + synth.dispatchMessage(client, 0, "/voice0/remaining_delay", "", nullptr); + synth.dispatchMessage(client, 0, "/voice1/remaining_delay", "", nullptr); + synth.dispatchMessage(client, 0, "/voice2/source_position", "", nullptr); + synth.dispatchMessage(client, 0, "/voice3/source_position", "", nullptr); + std::vector expected { + "/voice0/remaining_delay,i : { 0 }", + "/voice1/remaining_delay,i : { 24000 }", + "/voice2/source_position,i : { 0 }", + "/voice3/source_position,i : { 50 }", + }; + REQUIRE(messageList == expected); + } + + SECTION("CC132 - Note off velocity") + { + synth.loadSfzString(fs::current_path() / "tests/TestFiles/extended_ccs.sfz", R"( + key=60 sample=*silence + key=60 delay_cc132=1 sample=kick.wav trigger=release + key=61 sample=snare.wav + key=61 offset_cc132=100 sample=snare.wav trigger=release + )"); + synth.hdNoteOn(0, 60, 1.0f); + synth.hdNoteOff(1, 60, 0.0f); + synth.hdNoteOn(2, 60, 1.0f); + synth.hdNoteOff(3, 60, 0.5f); + synth.hdNoteOn(4, 61, 1.0f); + synth.hdNoteOff(5, 61, 0.0f); + synth.hdNoteOn(6, 61, 1.0f); + synth.hdNoteOff(7, 61, 0.5f); + synth.dispatchMessage(client, 10, "/voice1/remaining_delay", "", nullptr); + synth.dispatchMessage(client, 10, "/voice3/remaining_delay", "", nullptr); + synth.dispatchMessage(client, 10, "/voice5/source_position", "", nullptr); + synth.dispatchMessage(client, 10, "/voice7/source_position", "", nullptr); + std::vector expected { + "/voice1/remaining_delay,i : { 1 }", // 1 is the note off event delay + "/voice3/remaining_delay,i : { 24003 }", // 3 is the note off event delay + "/voice5/source_position,i : { 0 }", + "/voice7/source_position,i : { 50 }", + }; + REQUIRE(messageList == expected); + } + + SECTION("CC133 - Note number") + { + synth.loadSfzString(fs::current_path() / "tests/TestFiles/extended_ccs.sfz", R"( + delay_cc133=1 offset_cc133=100 sample=kick.wav + )"); + synth.hdNoteOn(0, 0, 1.0f); + synth.hdNoteOn(0, 127, 1.0f); + synth.dispatchMessage(client, 0, "/voice0/remaining_delay", "", nullptr); + synth.dispatchMessage(client, 0, "/voice1/remaining_delay", "", nullptr); + synth.dispatchMessage(client, 0, "/voice0/source_position", "", nullptr); + synth.dispatchMessage(client, 0, "/voice1/source_position", "", nullptr); + std::vector expected { + "/voice0/remaining_delay,i : { 0 }", + "/voice1/remaining_delay,i : { 48000 }", + "/voice0/source_position,i : { 0 }", + "/voice1/source_position,i : { 100 }", + }; + REQUIRE(messageList == expected); + } + + SECTION("CC134 - Note gate") + { + synth.loadSfzString(fs::current_path() / "tests/TestFiles/extended_ccs.sfz", R"( + delay_cc134=1 offset_cc134=100 sample=kick.wav + )"); + synth.hdNoteOn(0, 60, 1.0f); + synth.hdNoteOn(0, 127, 1.0f); + synth.hdNoteOff(1, 60, 1.0f); + synth.hdNoteOff(1, 127, 1.0f); + synth.hdNoteOn(2, 60, 1.0f); + synth.hdNoteOn(2, 127, 1.0f); + synth.dispatchMessage(client, 0, "/voice0/remaining_delay", "", nullptr); + synth.dispatchMessage(client, 0, "/voice1/remaining_delay", "", nullptr); + synth.dispatchMessage(client, 0, "/voice2/remaining_delay", "", nullptr); + synth.dispatchMessage(client, 0, "/voice3/remaining_delay", "", nullptr); + synth.dispatchMessage(client, 0, "/voice0/source_position", "", nullptr); + synth.dispatchMessage(client, 0, "/voice1/source_position", "", nullptr); + synth.dispatchMessage(client, 0, "/voice2/source_position", "", nullptr); + synth.dispatchMessage(client, 0, "/voice3/source_position", "", nullptr); + std::vector expected { + "/voice0/remaining_delay,i : { 0 }", + "/voice1/remaining_delay,i : { 48000 }", + "/voice2/remaining_delay,i : { 2 }", // 2 is the event delay + "/voice3/remaining_delay,i : { 48002 }", // 2 is the event delay + "/voice0/source_position,i : { 0 }", + "/voice1/source_position,i : { 100 }", + "/voice2/source_position,i : { 0 }", + "/voice3/source_position,i : { 100 }", + }; + REQUIRE(messageList == expected); + } + + SECTION("CC137 - Alternate") + { + synth.loadSfzString(fs::current_path() / "tests/TestFiles/extended_ccs.sfz", R"( + delay_cc137=1 offset_cc137=100 sample=kick.wav + )"); + synth.hdNoteOn(0, 60, 1.0f); + synth.hdNoteOn(0, 127, 1.0f); + synth.hdNoteOn(0, 54, 1.0f); + synth.hdNoteOn(0, 12, 1.0f); + synth.dispatchMessage(client, 0, "/voice0/remaining_delay", "", nullptr); + synth.dispatchMessage(client, 0, "/voice1/remaining_delay", "", nullptr); + synth.dispatchMessage(client, 0, "/voice2/remaining_delay", "", nullptr); + synth.dispatchMessage(client, 0, "/voice3/remaining_delay", "", nullptr); + synth.dispatchMessage(client, 0, "/voice0/source_position", "", nullptr); + synth.dispatchMessage(client, 0, "/voice1/source_position", "", nullptr); + synth.dispatchMessage(client, 0, "/voice2/source_position", "", nullptr); + synth.dispatchMessage(client, 0, "/voice3/source_position", "", nullptr); + std::vector expected { + "/voice0/remaining_delay,i : { 0 }", + "/voice1/remaining_delay,i : { 48000 }", + "/voice2/remaining_delay,i : { 0 }", + "/voice3/remaining_delay,i : { 48000 }", + "/voice0/source_position,i : { 0 }", + "/voice1/source_position,i : { 100 }", + "/voice2/source_position,i : { 0 }", + "/voice3/source_position,i : { 100 }", + }; + REQUIRE(messageList == expected); + } } diff --git a/tests/ModulationsT.cpp b/tests/ModulationsT.cpp index 48ede30b1..cc9adfcb4 100644 --- a/tests/ModulationsT.cpp +++ b/tests/ModulationsT.cpp @@ -4,6 +4,7 @@ // 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 "sfizz/modulations/ModMatrix.h" #include "sfizz/modulations/ModId.h" #include "sfizz/modulations/ModKey.h" #include "sfizz/Synth.h" @@ -89,7 +90,7 @@ pan_oncc36=12.5 pan_stepcc36=0.5 width_oncc425=29 )"); - const std::string graph = synth.getResources().modMatrix.toDotGraph(); + const std::string graph = synth.getResources().getModMatrix().toDotGraph(); REQUIRE(graph == createDefaultGraph({ R"("Controller 20 {curve=3, smooth=0, step=0}" -> "Amplitude {0}")", R"("Controller 42 {curve=0, smooth=32, step=0}" -> "Pitch {0}")", @@ -108,7 +109,7 @@ TEST_CASE("[Modulations] Filter CC connections") resonance3=-1 resonance3_oncc1=2 resonance3_smoothcc1=10 )"); - const std::string graph = synth.getResources().modMatrix.toDotGraph(); + const std::string graph = synth.getResources().getModMatrix().toDotGraph(); REQUIRE(graph == createDefaultGraph({ R"("Controller 1 {curve=0, smooth=10, step=0}" -> "FilterResonance {0, N=3}")", R"("Controller 2 {curve=2, smooth=0, step=0}" -> "FilterCutoff {0, N=2}")", @@ -126,7 +127,7 @@ TEST_CASE("[Modulations] EQ CC connections") eq3_bw_oncc1=2 eq3_bw_smoothcc1=10 )"); - const std::string graph = synth.getResources().modMatrix.toDotGraph(); + const std::string graph = synth.getResources().getModMatrix().toDotGraph(); REQUIRE(graph == createDefaultGraph({ R"("Controller 1 {curve=0, smooth=10, step=0}" -> "EqBandwidth {0, N=3}")", R"("Controller 2 {curve=0, smooth=0, step=0.1}" -> "EqGain {0, N=1}")", @@ -147,7 +148,7 @@ TEST_CASE("[Modulations] LFO Filter connections") lfo6_freq=3 lfo6_fil1gain=-1 )"); - const std::string graph = synth.getResources().modMatrix.toDotGraph(); + const std::string graph = synth.getResources().getModMatrix().toDotGraph(); REQUIRE(graph == createDefaultGraph({ R"("LFO 1 {0}" -> "FilterCutoff {0, N=1}")", R"("LFO 2 {0}" -> "FilterCutoff {0, N=1}")", @@ -171,7 +172,7 @@ TEST_CASE("[Modulations] EG Filter connections") eg6_time1=3 eg6_fil1gain=-1 )"); - const std::string graph = synth.getResources().modMatrix.toDotGraph(); + const std::string graph = synth.getResources().getModMatrix().toDotGraph(); REQUIRE(graph == createDefaultGraph({ R"("EG 1 {0}" -> "FilterCutoff {0, N=1}")", R"("EG 2 {0}" -> "FilterCutoff {0, N=1}")", @@ -195,7 +196,7 @@ TEST_CASE("[Modulations] LFO EQ connections") lfo6_freq=3 lfo6_eq1freq=-1 )"); - const std::string graph = synth.getResources().modMatrix.toDotGraph(); + const std::string graph = synth.getResources().getModMatrix().toDotGraph(); REQUIRE(graph == createDefaultGraph({ R"("LFO 1 {0}" -> "EqBandwidth {0, N=1}")", R"("LFO 2 {0}" -> "EqFrequency {0, N=2}")", @@ -219,7 +220,7 @@ TEST_CASE("[Modulations] EG EQ connections") eg6_freq=3 eg6_eq1freq=-1 )"); - const std::string graph = synth.getResources().modMatrix.toDotGraph(); + const std::string graph = synth.getResources().getModMatrix().toDotGraph(); REQUIRE(graph == createDefaultGraph({ R"("EG 1 {0}" -> "EqBandwidth {0, N=1}")", R"("EG 2 {0}" -> "EqFrequency {0, N=2}")", @@ -244,7 +245,7 @@ TEST_CASE("[Modulations] FlexEG Ampeg target") eg1_ampeg=1 )"); - const std::string graph = synth.getResources().modMatrix.toDotGraph(); + const std::string graph = synth.getResources().getModMatrix().toDotGraph(); REQUIRE(graph == createModulationDotGraph({ R"("Controller 10 {curve=1, smooth=10, step=0}" -> "Pan {0}")", R"("Controller 11 {curve=4, smooth=10, step=0}" -> "Amplitude {0}")", @@ -269,7 +270,7 @@ TEST_CASE("[Modulations] FlexEG Ampeg target with 2 FlexEGs") eg2_ampeg=1 )"); - const std::string graph = synth.getResources().modMatrix.toDotGraph(); + const std::string graph = synth.getResources().getModMatrix().toDotGraph(); REQUIRE(graph == createModulationDotGraph({ R"("Controller 10 {curve=1, smooth=10, step=0}" -> "Pan {0}")", R"("Controller 11 {curve=4, smooth=10, step=0}" -> "Amplitude {0}")", @@ -296,7 +297,7 @@ TEST_CASE("[Modulations] FlexEG Ampeg target with multiple EGs targeting ampeg") eg2_ampeg=1 )"); - const std::string graph = synth.getResources().modMatrix.toDotGraph(); + const std::string graph = synth.getResources().getModMatrix().toDotGraph(); REQUIRE(graph == createModulationDotGraph({ R"("Controller 10 {curve=1, smooth=10, step=0}" -> "Pan {0}")", R"("Controller 11 {curve=4, smooth=10, step=0}" -> "Amplitude {0}")", @@ -313,7 +314,7 @@ TEST_CASE("[Modulations] Override the default volume controller") sample=*sine tune_oncc7=1200 )"); - const std::string graph = synth.getResources().modMatrix.toDotGraph(); + const std::string graph = synth.getResources().getModMatrix().toDotGraph(); REQUIRE(graph == createModulationDotGraph({ R"("AmplitudeEG {0}" -> "MasterAmplitude {0}")", R"("Controller 10 {curve=1, smooth=10, step=0}" -> "Pan {0}")", @@ -330,7 +331,7 @@ TEST_CASE("[Modulations] Override the default pan controller") sample=*sine on_locc10=127 on_hicc10=127 )"); - const std::string graph = synth.getResources().modMatrix.toDotGraph(); + const std::string graph = synth.getResources().getModMatrix().toDotGraph(); REQUIRE(graph == createModulationDotGraph({ R"("AmplitudeEG {0}" -> "MasterAmplitude {0}")", R"("Controller 11 {curve=4, smooth=10, step=0}" -> "Amplitude {0}")", @@ -346,7 +347,7 @@ TEST_CASE("[Modulations] Aftertouch connections") sample=*sine cutoff2_chanaft=1000 )"); - const std::string graph = synth.getResources().modMatrix.toDotGraph(); + const std::string graph = synth.getResources().getModMatrix().toDotGraph(); REQUIRE(graph == createDefaultGraph({ R"("ChannelAftertouch" -> "FilterCutoff {0, N=1}")", R"("ChannelAftertouch" -> "FilterCutoff {1, N=2}")", @@ -362,7 +363,7 @@ TEST_CASE("[Modulations] LFO v1 connections") sample=*sine fillfo_freq=1.0 )"); - const std::string graph = synth.getResources().modMatrix.toDotGraph(); + const std::string graph = synth.getResources().getModMatrix().toDotGraph(); REQUIRE(graph == createDefaultGraph({ R"("AmplitudeLFO {0}" -> "Volume {0}")", R"("PitchLFO {1}" -> "Pitch {1}")", @@ -379,7 +380,7 @@ TEST_CASE("[Modulations] LFO v1 CC connections") sample=*sine fillfo_depth_oncc3=-3600 )"); - const std::string graph = synth.getResources().modMatrix.toDotGraph(); + const std::string graph = synth.getResources().getModMatrix().toDotGraph(); REQUIRE(graph == createDefaultGraph({ R"("Controller 1 {curve=0, smooth=0, step=0}" -> "AmplitudeLFODepth {0}")", R"("Controller 2 {curve=0, smooth=0, step=0}" -> "PitchLFODepth {1}")", @@ -399,7 +400,7 @@ TEST_CASE("[Modulations] LFO v1 CC frequency connections") sample=*sine fillfo_freqcc3=-3600 )"); - const std::string graph = synth.getResources().modMatrix.toDotGraph(); + const std::string graph = synth.getResources().getModMatrix().toDotGraph(); REQUIRE(graph == createDefaultGraph({ R"("Controller 1 {curve=0, smooth=0, step=0}" -> "AmplitudeLFOFrequency {0}")", R"("Controller 2 {curve=0, smooth=0, step=0}" -> "PitchLFOFrequency {1}")", @@ -419,7 +420,7 @@ TEST_CASE("[Modulations] LFO v1 aftertouch connections") sample=*sine fillfo_depthchanaft=-3600 )"); - const std::string graph = synth.getResources().modMatrix.toDotGraph(); + const std::string graph = synth.getResources().getModMatrix().toDotGraph(); REQUIRE(graph == createDefaultGraph({ R"("ChannelAftertouch" -> "AmplitudeLFODepth {0}")", R"("ChannelAftertouch" -> "PitchLFODepth {1}")", @@ -439,7 +440,7 @@ TEST_CASE("[Modulations] LFO v1 aftertouch frequency connections") sample=*sine fillfo_freqchanaft=-3600 )"); - const std::string graph = synth.getResources().modMatrix.toDotGraph(); + const std::string graph = synth.getResources().getModMatrix().toDotGraph(); REQUIRE(graph == createDefaultGraph({ R"("ChannelAftertouch" -> "AmplitudeLFOFrequency {0}")", R"("ChannelAftertouch" -> "PitchLFOFrequency {1}")", @@ -459,7 +460,7 @@ TEST_CASE("[Modulations] LFO v1 poly aftertouch connections") sample=*sine fillfo_depthpolyaft=-3600 )"); - const std::string graph = synth.getResources().modMatrix.toDotGraph(); + const std::string graph = synth.getResources().getModMatrix().toDotGraph(); REQUIRE(graph == createDefaultGraph({ R"("PolyAftertouch" -> "AmplitudeLFODepth {0}")", R"("PolyAftertouch" -> "PitchLFODepth {1}")", @@ -479,7 +480,7 @@ TEST_CASE("[Modulations] LFO v1 poly aftertouch frequency connections") sample=*sine fillfo_freqpolyaft=-3600 )"); - const std::string graph = synth.getResources().modMatrix.toDotGraph(); + const std::string graph = synth.getResources().getModMatrix().toDotGraph(); REQUIRE(graph == createDefaultGraph({ R"("PolyAftertouch" -> "AmplitudeLFOFrequency {0}")", R"("PolyAftertouch" -> "PitchLFOFrequency {1}")", @@ -498,7 +499,7 @@ TEST_CASE("[Modulations] EG v1 CC connections") sample=*sine fileg_depth_oncc3=-3600 )"); - const std::string graph = synth.getResources().modMatrix.toDotGraph(); + const std::string graph = synth.getResources().getModMatrix().toDotGraph(); REQUIRE(graph == createDefaultGraph({ R"("Controller 2 {curve=0, smooth=0, step=0}" -> "PitchEGDepth {0}")", R"("Controller 3 {curve=0, smooth=0, step=0}" -> "FilterEGDepth {1}")", @@ -523,7 +524,7 @@ TEST_CASE("[Modulations] LFO CC connections") pitch_oncc137=1200 )"); - const std::string graph = synth.getResources().modMatrix.toDotGraph(); + const std::string graph = synth.getResources().getModMatrix().toDotGraph(); REQUIRE(graph == createDefaultGraph({ R"("Controller 128 {curve=0, smooth=0, step=0}" -> "Pitch {0}")", R"("Controller 129 {curve=0, smooth=0, step=0}" -> "Pitch {0}")", @@ -547,7 +548,7 @@ TEST_CASE("[Modulations] Extended CCs connections") lfo3_freq=0.1 lfo3_phase_cc1=2 lfo3_phase_smoothcc1=10 lfo3_phase_stepcc1=0.2 lfo3_phase_curvecc1=1 lfo3_amplitude=50 )"); - const std::string graph = synth.getResources().modMatrix.toDotGraph(); + const std::string graph = synth.getResources().getModMatrix().toDotGraph(); REQUIRE(graph == createDefaultGraph({ R"("LFO 1 {0}" -> "Volume {0}")", R"("LFO 2 {0}" -> "Pitch {0}")", diff --git a/tests/ParsingT.cpp b/tests/ParsingT.cpp index cb1096ce5..3b6859cf4 100644 --- a/tests/ParsingT.cpp +++ b/tests/ParsingT.cpp @@ -6,6 +6,7 @@ #include "sfizz/SfzHelpers.h" #include "sfizz/parser/Parser.h" +#include "sfizz/parser/ParserListener.h" #include #include "catch2/catch.hpp" #include "absl/strings/string_view.h" diff --git a/tests/PolyphonyT.cpp b/tests/PolyphonyT.cpp index f2c56bf70..50e8265a3 100644 --- a/tests/PolyphonyT.cpp +++ b/tests/PolyphonyT.cpp @@ -57,7 +57,7 @@ TEST_CASE("[Polyphony] Polyphony groups") group=4 key=62 sample=*sine )"); - REQUIRE( synth.getNumPolyphonyGroups() == 5 ); + REQUIRE( synth.getNumPolyphonyGroups() == 4 ); REQUIRE( synth.getNumRegions() == 5 ); REQUIRE( synth.getRegionView(0)->group == 0 ); REQUIRE( synth.getRegionView(1)->group == 1 ); @@ -67,7 +67,7 @@ TEST_CASE("[Polyphony] Polyphony groups") REQUIRE( synth.getRegionView(4)->group == 4 ); REQUIRE( synth.getPolyphonyGroupView(1)->getPolyphonyLimit() == 3 ); REQUIRE( synth.getPolyphonyGroupView(2)->getPolyphonyLimit() == 4 ); - REQUIRE( synth.getPolyphonyGroupView(3)->getPolyphonyLimit() == sfz::config::maxVoices ); + REQUIRE( !synth.getPolyphonyGroupView(3) ); REQUIRE( synth.getPolyphonyGroupView(4)->getPolyphonyLimit() == 5 ); } @@ -222,18 +222,18 @@ TEST_CASE("[Polyphony] Self-masking") synth.loadSfzString(fs::current_path() / "tests/TestFiles/polyphony.sfz", R"( sample=*sine key=64 note_polyphony=2 )"); - synth.noteOn(0, 64, 63 ); - synth.noteOn(1, 64, 62 ); + synth.noteOn(0, 64, 63); + synth.noteOn(1, 64, 62); synth.noteOn(2, 64, 64); synth.renderBlock(buffer); REQUIRE( synth.getNumActiveVoices() == 3 ); // One of these is releasing REQUIRE( numPlayingVoices(synth) == 2 ); REQUIRE( synth.getVoiceView(0)->getTriggerEvent().value == 63_norm); - REQUIRE(!synth.getVoiceView(0)->releasedOrFree()); + REQUIRE(!synth.getVoiceView(0)->offedOrFree()); REQUIRE( synth.getVoiceView(1)->getTriggerEvent().value == 62_norm); - REQUIRE( synth.getVoiceView(1)->releasedOrFree()); // The lowest velocity voice is the masking candidate + REQUIRE( synth.getVoiceView(1)->offedOrFree()); // The lowest velocity voice is the masking candidate REQUIRE( synth.getVoiceView(2)->getTriggerEvent().value == 64_norm); - REQUIRE(!synth.getVoiceView(2)->releasedOrFree()); + REQUIRE(!synth.getVoiceView(2)->offedOrFree()); } TEST_CASE("[Polyphony] Not self-masking") @@ -250,11 +250,11 @@ TEST_CASE("[Polyphony] Not self-masking") REQUIRE( synth.getNumActiveVoices() == 3 ); // One of these is releasing REQUIRE( numPlayingVoices(synth) == 2 ); REQUIRE( synth.getVoiceView(0)->getTriggerEvent().value == 63_norm); - REQUIRE( synth.getVoiceView(0)->releasedOrFree()); + REQUIRE( synth.getVoiceView(0)->offedOrFree()); REQUIRE( synth.getVoiceView(1)->getTriggerEvent().value == 62_norm); - REQUIRE(!synth.getVoiceView(1)->releasedOrFree()); + REQUIRE(!synth.getVoiceView(1)->offedOrFree()); REQUIRE( synth.getVoiceView(2)->getTriggerEvent().value == 64_norm); - REQUIRE(!synth.getVoiceView(2)->releasedOrFree()); + REQUIRE(!synth.getVoiceView(2)->offedOrFree()); } TEST_CASE("[Polyphony] Self-masking with the exact same velocity") @@ -271,11 +271,11 @@ TEST_CASE("[Polyphony] Self-masking with the exact same velocity") REQUIRE( synth.getNumActiveVoices() == 3 ); // One of these is releasing REQUIRE( numPlayingVoices(synth) == 2 ); REQUIRE( synth.getVoiceView(0)->getTriggerEvent().value == 64_norm); - REQUIRE(!synth.getVoiceView(0)->releasedOrFree()); + REQUIRE(!synth.getVoiceView(0)->offedOrFree()); REQUIRE( synth.getVoiceView(1)->getTriggerEvent().value == 63_norm); - REQUIRE( synth.getVoiceView(1)->releasedOrFree()); // The first one is the masking candidate since they have the same velocity + REQUIRE( synth.getVoiceView(1)->offedOrFree()); // The first one is the masking candidate since they have the same velocity REQUIRE( synth.getVoiceView(2)->getTriggerEvent().value == 63_norm); - REQUIRE(!synth.getVoiceView(2)->releasedOrFree()); + REQUIRE(!synth.getVoiceView(2)->offedOrFree()); } TEST_CASE("[Polyphony] Self-masking only works from low to high") @@ -291,9 +291,9 @@ TEST_CASE("[Polyphony] Self-masking only works from low to high") REQUIRE( synth.getNumActiveVoices() == 2 ); // Both notes are playing REQUIRE( numPlayingVoices(synth) == 2 ); // id REQUIRE( synth.getVoiceView(0)->getTriggerEvent().value == 63_norm); - REQUIRE(!synth.getVoiceView(0)->releasedOrFree()); + REQUIRE(!synth.getVoiceView(0)->offedOrFree()); REQUIRE( synth.getVoiceView(1)->getTriggerEvent().value == 62_norm); - REQUIRE(!synth.getVoiceView(1)->releasedOrFree()); + REQUIRE(!synth.getVoiceView(1)->offedOrFree()); } TEST_CASE("[Polyphony] Note polyphony checks works across regions in the same polyphony group (default)") @@ -309,13 +309,13 @@ TEST_CASE("[Polyphony] Note polyphony checks works across regions in the same po synth.renderBlock(buffer); REQUIRE( numPlayingVoices(synth) == 1 ); REQUIRE( synth.getVoiceView(0)->getTriggerEvent().value == 62_norm); - REQUIRE( synth.getVoiceView(0)->releasedOrFree()); // got killed + REQUIRE( synth.getVoiceView(0)->offedOrFree()); // got killed REQUIRE( synth.getVoiceView(1)->getTriggerEvent().value == 62_norm); - REQUIRE( synth.getVoiceView(1)->releasedOrFree()); // got killed + REQUIRE( synth.getVoiceView(1)->offedOrFree()); // got killed REQUIRE( synth.getVoiceView(2)->getTriggerEvent().value == 63_norm); - REQUIRE( synth.getVoiceView(2)->releasedOrFree()); // got killed + REQUIRE( synth.getVoiceView(2)->offedOrFree()); // got killed REQUIRE( synth.getVoiceView(3)->getTriggerEvent().value == 63_norm); - REQUIRE(!synth.getVoiceView(3)->releasedOrFree()); + REQUIRE(!synth.getVoiceView(3)->offedOrFree()); } TEST_CASE("[Polyphony] Note polyphony checks works across regions in the same polyphony group (default, with keyswitches)") @@ -338,9 +338,9 @@ TEST_CASE("[Polyphony] Note polyphony checks works across regions in the same po REQUIRE( synth.getNumActiveVoices() == 2 ); REQUIRE( numPlayingVoices(synth) == 1 ); REQUIRE( synth.getVoiceView(0)->getTriggerEvent().value == 63_norm); - REQUIRE( synth.getVoiceView(0)->releasedOrFree()); + REQUIRE( synth.getVoiceView(0)->offedOrFree()); REQUIRE( synth.getVoiceView(1)->getTriggerEvent().value == 64_norm); - REQUIRE(!synth.getVoiceView(1)->releasedOrFree()); + REQUIRE(!synth.getVoiceView(1)->offedOrFree()); } @@ -358,13 +358,13 @@ TEST_CASE("[Polyphony] Note polyphony do not operate across polyphony groups") REQUIRE( synth.getNumActiveVoices() == 4); // Both notes are playing REQUIRE(numPlayingVoices(synth) == 2 ); REQUIRE( synth.getVoiceView(0)->getTriggerEvent().value == 62_norm); - REQUIRE( synth.getVoiceView(0)->releasedOrFree()); // got killed + REQUIRE( synth.getVoiceView(0)->offedOrFree()); // got killed REQUIRE( synth.getVoiceView(1)->getTriggerEvent().value == 62_norm); - REQUIRE( synth.getVoiceView(1)->releasedOrFree()); // got killed + REQUIRE( synth.getVoiceView(1)->offedOrFree()); // got killed REQUIRE( synth.getVoiceView(2)->getTriggerEvent().value == 63_norm); - REQUIRE(!synth.getVoiceView(2)->releasedOrFree()); + REQUIRE(!synth.getVoiceView(2)->offedOrFree()); REQUIRE( synth.getVoiceView(3)->getTriggerEvent().value == 63_norm); - REQUIRE(!synth.getVoiceView(3)->releasedOrFree()); + REQUIRE(!synth.getVoiceView(3)->offedOrFree()); } TEST_CASE("[Polyphony] Note polyphony do not operate across polyphony groups (with keyswitches)") @@ -387,9 +387,9 @@ TEST_CASE("[Polyphony] Note polyphony do not operate across polyphony groups (wi REQUIRE( synth.getNumActiveVoices() == 2 ); REQUIRE(numPlayingVoices(synth) == 2 ); REQUIRE( synth.getVoiceView(0)->getTriggerEvent().value == 63_norm); - REQUIRE(!synth.getVoiceView(0)->releasedOrFree()); + REQUIRE(!synth.getVoiceView(0)->offedOrFree()); REQUIRE( synth.getVoiceView(1)->getTriggerEvent().value == 64_norm); - REQUIRE(!synth.getVoiceView(1)->releasedOrFree()); + REQUIRE(!synth.getVoiceView(1)->offedOrFree()); } TEST_CASE("[Polyphony] Note polyphony operates on release voices") @@ -409,9 +409,9 @@ TEST_CASE("[Polyphony] Note polyphony operates on release voices") REQUIRE( synth.getNumActiveVoices() == 2 ); REQUIRE(numPlayingVoices(synth) == 1 ); REQUIRE( synth.getVoiceView(0)->getTriggerEvent().value == 63_norm); - REQUIRE( synth.getVoiceView(0)->releasedOrFree()); + REQUIRE( synth.getVoiceView(0)->offedOrFree()); REQUIRE( synth.getVoiceView(1)->getTriggerEvent().value == 65_norm); - REQUIRE(!synth.getVoiceView(1)->releasedOrFree()); + REQUIRE(!synth.getVoiceView(1)->offedOrFree()); } TEST_CASE("[Polyphony] Note polyphony operates on release voices (masking works from low to high but takes into account the replaced velocity)") @@ -432,9 +432,9 @@ TEST_CASE("[Polyphony] Note polyphony operates on release voices (masking works REQUIRE( synth.getNumActiveVoices() == 2 ); REQUIRE( numPlayingVoices(synth) == 2 ); REQUIRE( synth.getVoiceView(0)->getTriggerEvent().value == 63_norm); - REQUIRE(!synth.getVoiceView(0)->releasedOrFree()); + REQUIRE(!synth.getVoiceView(0)->offedOrFree()); REQUIRE( synth.getVoiceView(1)->getTriggerEvent().value == 61_norm); - REQUIRE(!synth.getVoiceView(1)->releasedOrFree()); + REQUIRE(!synth.getVoiceView(1)->offedOrFree()); } TEST_CASE("[Polyphony] Note polyphony operates on release voices and sustain pedal") @@ -459,8 +459,8 @@ TEST_CASE("[Polyphony] Note polyphony operates on release voices and sustain ped synth.renderBlock(buffer); std::vector expectedSamples2 { "*saw" }; std::vector expectedVelocities { 63_norm }; - REQUIRE( playingSamples(synth) == expectedSamples2 ); REQUIRE( playingVelocities(synth) == expectedVelocities ); + REQUIRE( playingSamples(synth) == expectedSamples2 ); } TEST_CASE("[Polyphony] Note polyphony operates on release voices and sustain pedal (masking)") @@ -528,3 +528,45 @@ TEST_CASE("[Polyphony] Bi-directional choking (with note_polyphony)") synth.renderBlock(buffer); REQUIRE( playingSamples(synth) == std::vector { "kick.wav" } ); } + +TEST_CASE("[Polyphony] Choke long release tails") +{ + sfz::Synth synth; + sfz::AudioBuffer buffer { 2, static_cast(synth.getSamplesPerBlock()) }; + synth.loadSfzString(fs::current_path() / "tests/TestFiles/polyphony.sfz", R"( + sample=*saw ampeg_attack=0.1 ampeg_release=10 polyphony=1 + )"); + int attackBlocks = static_cast(0.1f / synth.getSamplesPerBlock() * 48000.0f) + 1; + synth.noteOn(0, 60, 63 ); + for (int i = 0; i < attackBlocks; ++i) + synth.renderBlock(buffer); + synth.noteOff(10, 60, 63 ); + synth.renderBlock(buffer); + REQUIRE( numPlayingVoices(synth) == 0 ); // Released + REQUIRE( numActiveVoices(synth) == 1 ); + synth.noteOn(0, 60, 63 ); + synth.renderBlock(buffer); + REQUIRE( numPlayingVoices(synth) == 1 ); // Not released, attack phase + REQUIRE( numActiveVoices(synth) == 1 ); +} + +TEST_CASE("[Polyphony] Choke long release tails with note_polyphony") +{ + sfz::Synth synth; + sfz::AudioBuffer buffer { 2, static_cast(synth.getSamplesPerBlock()) }; + synth.loadSfzString(fs::current_path() / "tests/TestFiles/polyphony.sfz", R"( + sample=*saw ampeg_attack=0.1 ampeg_release=10 note_polyphony=1 + )"); + int attackBlocks = static_cast(0.1f / synth.getSamplesPerBlock() * 48000.0f) + 1; + synth.noteOn(0, 60, 63 ); + for (int i = 0; i < attackBlocks; ++i) + synth.renderBlock(buffer); + synth.noteOff(10, 60, 63 ); + synth.renderBlock(buffer); + REQUIRE( numPlayingVoices(synth) == 0 ); // Released + REQUIRE( numActiveVoices(synth) == 1 ); + synth.noteOn(0, 60, 63 ); + synth.renderBlock(buffer); + REQUIRE( numPlayingVoices(synth) == 1 ); // Not released, attack phase + REQUIRE( numActiveVoices(synth) == 1 ); +} diff --git a/tests/RegionActivationT.cpp b/tests/RegionActivationT.cpp index fb69c9845..b8ae82186 100644 --- a/tests/RegionActivationT.cpp +++ b/tests/RegionActivationT.cpp @@ -21,7 +21,7 @@ TEST_CASE("Region activation", "Region tests") SECTION("Basic state") { sfz::Layer layer { region, midiState }; - layer.registerCC(4, 0_norm); + layer.updateCCState(4, 0_norm); REQUIRE(layer.isSwitchedOn()); } @@ -30,19 +30,19 @@ TEST_CASE("Region activation", "Region tests") region.parseOpcode({ "locc4", "56" }); region.parseOpcode({ "hicc4", "59" }); sfz::Layer layer { region, midiState }; - layer.registerCC(4, 0_norm); + layer.updateCCState(4, 0_norm); REQUIRE(!layer.isSwitchedOn()); - layer.registerCC(4, 57_norm); + layer.updateCCState(4, 57_norm); REQUIRE(layer.isSwitchedOn()); - layer.registerCC(4, 56_norm); + layer.updateCCState(4, 56_norm); REQUIRE(layer.isSwitchedOn()); - layer.registerCC(4, 59_norm); + layer.updateCCState(4, 59_norm); REQUIRE(layer.isSwitchedOn()); - layer.registerCC(4, 43_norm); + layer.updateCCState(4, 43_norm); REQUIRE(!layer.isSwitchedOn()); - layer.registerCC(4, 65_norm); + layer.updateCCState(4, 65_norm); REQUIRE(!layer.isSwitchedOn()); - layer.registerCC(6, 57_norm); + layer.updateCCState(6, 57_norm); REQUIRE(!layer.isSwitchedOn()); } @@ -53,26 +53,26 @@ TEST_CASE("Region activation", "Region tests") region.parseOpcode({ "locc54", "18" }); region.parseOpcode({ "hicc54", "27" }); sfz::Layer layer { region, midiState }; - layer.registerCC(4, 0_norm); - layer.registerCC(54, 0_norm); + layer.updateCCState(4, 0_norm); + layer.updateCCState(54, 0_norm); REQUIRE(!layer.isSwitchedOn()); - layer.registerCC(4, 57_norm); + layer.updateCCState(4, 57_norm); REQUIRE(!layer.isSwitchedOn()); - layer.registerCC(54, 19_norm); + layer.updateCCState(54, 19_norm); REQUIRE(layer.isSwitchedOn()); - layer.registerCC(54, 17_norm); + layer.updateCCState(54, 17_norm); REQUIRE(!layer.isSwitchedOn()); - layer.registerCC(54, 27_norm); + layer.updateCCState(54, 27_norm); REQUIRE(layer.isSwitchedOn()); - layer.registerCC(4, 56_norm); + layer.updateCCState(4, 56_norm); REQUIRE(layer.isSwitchedOn()); - layer.registerCC(4, 59_norm); + layer.updateCCState(4, 59_norm); REQUIRE(layer.isSwitchedOn()); - layer.registerCC(54, 2_norm); + layer.updateCCState(54, 2_norm); REQUIRE(!layer.isSwitchedOn()); - layer.registerCC(54, 26_norm); + layer.updateCCState(54, 26_norm); REQUIRE(layer.isSwitchedOn()); - layer.registerCC(4, 65_norm); + layer.updateCCState(4, 65_norm); REQUIRE(!layer.isSwitchedOn()); } diff --git a/tests/RegionTriggersT.cpp b/tests/RegionTriggersT.cpp index 5e5931a30..b25486831 100644 --- a/tests/RegionTriggersT.cpp +++ b/tests/RegionTriggersT.cpp @@ -27,7 +27,7 @@ TEST_CASE("Basic triggers", "Region triggers") REQUIRE(layer.registerNoteOn(40, 64_norm, 0.5f)); REQUIRE(!layer.registerNoteOff(40, 64_norm, 0.5f)); REQUIRE(!layer.registerNoteOn(41, 64_norm, 0.5f)); - REQUIRE(!layer.registerCC(63, 64_norm)); + REQUIRE(!layer.registerCC(63, 64_norm, 0.0f)); } SECTION("lokey and hikey") { @@ -42,7 +42,7 @@ TEST_CASE("Basic triggers", "Region triggers") REQUIRE(!layer.registerNoteOn(43, 64_norm, 0.5f)); REQUIRE(!layer.registerNoteOff(42, 64_norm, 0.5f)); REQUIRE(!layer.registerNoteOff(42, 64_norm, 0.5f)); - REQUIRE(!layer.registerCC(63, 64_norm)); + REQUIRE(!layer.registerCC(63, 64_norm, 0.0f)); } SECTION("key and release trigger") { @@ -53,7 +53,7 @@ TEST_CASE("Basic triggers", "Region triggers") REQUIRE(layer.registerNoteOff(40, 64_norm, 0.5f)); REQUIRE(!layer.registerNoteOn(41, 64_norm, 0.5f)); REQUIRE(!layer.registerNoteOff(41, 64_norm, 0.5f)); - REQUIRE(!layer.registerCC(63, 64_norm)); + REQUIRE(!layer.registerCC(63, 64_norm, 0.0f)); } SECTION("key and release_key trigger") { @@ -64,7 +64,7 @@ TEST_CASE("Basic triggers", "Region triggers") REQUIRE(layer.registerNoteOff(40, 64_norm, 0.5f)); REQUIRE(!layer.registerNoteOn(41, 64_norm, 0.5f)); REQUIRE(!layer.registerNoteOff(41, 64_norm, 0.5f)); - REQUIRE(!layer.registerCC(63, 64_norm)); + REQUIRE(!layer.registerCC(63, 64_norm, 0.0f)); } // TODO: first and legato triggers SECTION("lovel and hivel") @@ -130,18 +130,36 @@ TEST_CASE("Basic triggers", "Region triggers") region.parseOpcode({ "on_locc47", "64" }); region.parseOpcode({ "on_hicc47", "68" }); Layer layer1 { region, midiState }; - REQUIRE(!layer1.registerCC(47, 63_norm)); - REQUIRE(layer1.registerCC(47, 64_norm)); - REQUIRE(layer1.registerCC(47, 65_norm)); + REQUIRE(!layer1.registerCC(47, 63_norm, 0.0f)); + REQUIRE(layer1.registerCC(47, 64_norm, 0.0f)); + REQUIRE(layer1.registerCC(47, 65_norm, 0.0f)); region.parseOpcode({ "hikey", "-1" }); Layer layer2 { region, midiState }; - REQUIRE(layer2.registerCC(47, 64_norm)); - REQUIRE(layer2.registerCC(47, 65_norm)); - REQUIRE(layer2.registerCC(47, 66_norm)); - REQUIRE(layer2.registerCC(47, 67_norm)); - REQUIRE(layer2.registerCC(47, 68_norm)); - REQUIRE(!layer2.registerCC(47, 69_norm)); - REQUIRE(!layer2.registerCC(40, 64_norm)); + REQUIRE(layer2.registerCC(47, 64_norm, 0.0f)); + REQUIRE(layer2.registerCC(47, 65_norm, 0.0f)); + REQUIRE(layer2.registerCC(47, 66_norm, 0.0f)); + REQUIRE(layer2.registerCC(47, 67_norm, 0.0f)); + REQUIRE(layer2.registerCC(47, 68_norm, 0.0f)); + REQUIRE(!layer2.registerCC(47, 69_norm, 0.0f)); + REQUIRE(!layer2.registerCC(40, 64_norm, 0.0f)); + } + + SECTION("lorand and hirand with CC triggers") + { + region.parseOpcode({ "on_locc47", "64" }); + region.parseOpcode({ "on_hicc47", "68" }); + region.parseOpcode({ "hikey", "-1" }); + region.parseOpcode({ "lorand", "0.35" }); + region.parseOpcode({ "hirand", "0.40" }); + Layer layer { region, midiState }; + REQUIRE(!layer.registerCC(47, 64_norm, 0.0f)); + REQUIRE(!layer.registerCC(47, 64_norm, 0.34f)); + REQUIRE(layer.registerCC(47, 64_norm, 0.35f)); + REQUIRE(layer.registerCC(47, 64_norm, 0.36f)); + REQUIRE(layer.registerCC(47, 64_norm, 0.37f)); + REQUIRE(layer.registerCC(47, 64_norm, 0.38f)); + REQUIRE(layer.registerCC(47, 64_norm, 0.39f)); + REQUIRE(!layer.registerCC(47, 64_norm, 0.40f)); } SECTION("on_loccN does not disable key triggering") @@ -150,9 +168,9 @@ TEST_CASE("Basic triggers", "Region triggers") region.parseOpcode({ "on_locc1", "127" }); region.parseOpcode({ "on_hicc1", "127" }); Layer layer { region, midiState }; - REQUIRE(!layer.registerCC(1, 126_norm)); - REQUIRE(!layer.registerCC(2, 127_norm)); - REQUIRE(layer.registerCC(1, 127_norm)); + REQUIRE(!layer.registerCC(1, 126_norm, 0.0f)); + REQUIRE(!layer.registerCC(2, 127_norm, 0.0f)); + REQUIRE(layer.registerCC(1, 127_norm, 0.0f)); REQUIRE(layer.registerNoteOn(64, 127_norm, 0.5f)); } @@ -163,8 +181,8 @@ TEST_CASE("Basic triggers", "Region triggers") region.parseOpcode({ "on_hicc1", "127" }); region.parseOpcode({ "key", "-1" }); Layer layer { region, midiState }; - REQUIRE(!layer.registerCC(1, 126_norm)); - REQUIRE(layer.registerCC(1, 127_norm)); + REQUIRE(!layer.registerCC(1, 126_norm, 0.0f)); + REQUIRE(layer.registerCC(1, 127_norm, 0.0f)); REQUIRE(!layer.registerNoteOn(64, 127_norm, 0.5f)); } @@ -175,9 +193,9 @@ TEST_CASE("Basic triggers", "Region triggers") region.parseOpcode({ "on_hicc1", "127" }); region.parseOpcode({ "hikey", "-1" }); Layer layer { region, midiState }; - REQUIRE(!layer.registerCC(1, 126_norm)); - REQUIRE(!layer.registerCC(2, 127_norm)); - REQUIRE(layer.registerCC(1, 127_norm)); + REQUIRE(!layer.registerCC(1, 126_norm, 0.0f)); + REQUIRE(!layer.registerCC(2, 127_norm, 0.0f)); + REQUIRE(layer.registerCC(1, 127_norm, 0.0f)); REQUIRE(!layer.registerNoteOn(64, 127_norm, 0.5f)); } } @@ -371,3 +389,40 @@ TEST_CASE("[Triggers] sw_vel, consider the previous velocity for triggers") REQUIRE(messageList == expected); } } + +TEST_CASE("[Triggers] Honor lorand/hirand on CC triggers") +{ + Synth synth; + sfz::AudioBuffer buffer { 2, static_cast(synth.getSamplesPerBlock()) }; + std::vector messageList; + Client client(&messageList); + client.setReceiveCallback(&simpleMessageReceiver); + synth.loadSfzString(fs::current_path() / "tests/TestFiles/sw_vel.sfz", R"( + sample=*sine hikey=-1 start_locc64=63 start_hicc64=127 lorand=0 hirand=0.5 + sample=*saw hikey=-1 start_locc64=63 start_hicc64=127 lorand=0.5 hirand=1 + )"); + + synth.hdcc(0, 64, 10_norm); + REQUIRE(numPlayingVoices(synth) == 0); + synth.hdcc(0, 64, 100_norm); + REQUIRE(numPlayingVoices(synth) == 1); +} + +TEST_CASE("[Triggers] Offed voices with CC triggers do not activate release triggers") +{ + Synth synth; + sfz::AudioBuffer buffer { 2, static_cast(synth.getSamplesPerBlock()) }; + std::vector messageList; + Client client(&messageList); + client.setReceiveCallback(&simpleMessageReceiver); + synth.loadSfzString(fs::current_path() / "tests/TestFiles/sw_vel.sfz", R"( + sample=*sine hikey=-1 start_locc64=63 start_hicc64=127 group=1 off_by=2 + sample=*saw hikey=-1 start_locc64=0 start_hicc64=62 group=2 + sample=*noise trigger=release_key + )"); + + synth.hdcc(0, 64, 100_norm); + REQUIRE(playingSamples(synth) == std::vector { "*sine" }); + synth.hdcc(10, 64, 10_norm); + REQUIRE(playingSamples(synth) == std::vector { "*saw" }); +} diff --git a/tests/RegionValueComputationsT.cpp b/tests/RegionValueComputationsT.cpp index c7516830e..30eef25b6 100644 --- a/tests/RegionValueComputationsT.cpp +++ b/tests/RegionValueComputationsT.cpp @@ -6,6 +6,7 @@ #include "sfizz/Defaults.h" #include "sfizz/Region.h" +#include "sfizz/RegionStateful.h" #include "sfizz/MidiState.h" #include "sfizz/SfzHelpers.h" #include "catch2/catch.hpp" @@ -19,137 +20,155 @@ constexpr int numRandomTests { 64 }; TEST_CASE("[Region] Crossfade in on key") { Region region { 0 }; + MidiState midiState; + CurveSet curveSet { CurveSet::createPredefined() }; region.parseOpcode({ "sample", "*sine" }); region.parseOpcode({ "xfin_lokey", "1" }); region.parseOpcode({ "xfin_hikey", "3" }); - REQUIRE(region.getNoteGain(2, 127_norm) == 0.70711_a); - REQUIRE(region.getNoteGain(1, 127_norm) == 0.0_a); - REQUIRE(region.getNoteGain(3, 127_norm) == 1.0_a); + REQUIRE(noteGain(region, 2, 127_norm, midiState, curveSet) == 0.70711_a); + REQUIRE(noteGain(region, 1, 127_norm, midiState, curveSet) == 0.0_a); + REQUIRE(noteGain(region, 3, 127_norm, midiState, curveSet) == 1.0_a); } TEST_CASE("[Region] Crossfade in on key - 2") { Region region { 0 }; + MidiState midiState; + CurveSet curveSet { CurveSet::createPredefined() }; region.parseOpcode({ "sample", "*sine" }); region.parseOpcode({ "xfin_lokey", "1" }); region.parseOpcode({ "xfin_hikey", "5" }); - REQUIRE(region.getNoteGain(1, 127_norm) == 0.0_a); - REQUIRE(region.getNoteGain(2, 127_norm) == 0.5_a); - REQUIRE(region.getNoteGain(3, 127_norm) == 0.70711_a); - REQUIRE(region.getNoteGain(4, 127_norm) == 0.86603_a); - REQUIRE(region.getNoteGain(5, 127_norm) == 1.0_a); - REQUIRE(region.getNoteGain(6, 127_norm) == 1.0_a); + REQUIRE(noteGain(region, 1, 127_norm, midiState, curveSet) == 0.0_a); + REQUIRE(noteGain(region, 2, 127_norm, midiState, curveSet) == 0.5_a); + REQUIRE(noteGain(region, 3, 127_norm, midiState, curveSet) == 0.70711_a); + REQUIRE(noteGain(region, 4, 127_norm, midiState, curveSet) == 0.86603_a); + REQUIRE(noteGain(region, 5, 127_norm, midiState, curveSet) == 1.0_a); + REQUIRE(noteGain(region, 6, 127_norm, midiState, curveSet) == 1.0_a); } TEST_CASE("[Region] Crossfade in on key - gain") { Region region { 0 }; + MidiState midiState; + CurveSet curveSet { CurveSet::createPredefined() }; region.parseOpcode({ "sample", "*sine" }); region.parseOpcode({ "xfin_lokey", "1" }); region.parseOpcode({ "xfin_hikey", "5" }); region.parseOpcode({ "xf_keycurve", "gain" }); - REQUIRE(region.getNoteGain(1, 127_norm) == 0.0_a); - REQUIRE(region.getNoteGain(2, 127_norm) == 0.25_a); - REQUIRE(region.getNoteGain(3, 127_norm) == 0.5_a); - REQUIRE(region.getNoteGain(4, 127_norm) == 0.75_a); - REQUIRE(region.getNoteGain(5, 127_norm) == 1.0_a); + REQUIRE(noteGain(region, 1, 127_norm, midiState, curveSet) == 0.0_a); + REQUIRE(noteGain(region, 2, 127_norm, midiState, curveSet) == 0.25_a); + REQUIRE(noteGain(region, 3, 127_norm, midiState, curveSet) == 0.5_a); + REQUIRE(noteGain(region, 4, 127_norm, midiState, curveSet) == 0.75_a); + REQUIRE(noteGain(region, 5, 127_norm, midiState, curveSet) == 1.0_a); } TEST_CASE("[Region] Crossfade out on key") { Region region { 0 }; + MidiState midiState; + CurveSet curveSet { CurveSet::createPredefined() }; region.parseOpcode({ "sample", "*sine" }); region.parseOpcode({ "xfout_lokey", "51" }); region.parseOpcode({ "xfout_hikey", "55" }); - REQUIRE(region.getNoteGain(50, 127_norm) == 1.0_a); - REQUIRE(region.getNoteGain(51, 127_norm) == 1.0_a); - REQUIRE(region.getNoteGain(52, 127_norm) == 0.86603_a); - REQUIRE(region.getNoteGain(53, 127_norm) == 0.70711_a); - REQUIRE(region.getNoteGain(54, 127_norm) == 0.5_a); - REQUIRE(region.getNoteGain(55, 127_norm) == 0.0_a); - REQUIRE(region.getNoteGain(56, 127_norm) == 0.0_a); + REQUIRE(noteGain(region, 50, 127_norm, midiState, curveSet) == 1.0_a); + REQUIRE(noteGain(region, 51, 127_norm, midiState, curveSet) == 1.0_a); + REQUIRE(noteGain(region, 52, 127_norm, midiState, curveSet) == 0.86603_a); + REQUIRE(noteGain(region, 53, 127_norm, midiState, curveSet) == 0.70711_a); + REQUIRE(noteGain(region, 54, 127_norm, midiState, curveSet) == 0.5_a); + REQUIRE(noteGain(region, 55, 127_norm, midiState, curveSet) == 0.0_a); + REQUIRE(noteGain(region, 56, 127_norm, midiState, curveSet) == 0.0_a); } TEST_CASE("[Region] Crossfade out on key - gain") { Region region { 0 }; + MidiState midiState; + CurveSet curveSet { CurveSet::createPredefined() }; region.parseOpcode({ "sample", "*sine" }); region.parseOpcode({ "xfout_lokey", "51" }); region.parseOpcode({ "xfout_hikey", "55" }); region.parseOpcode({ "xf_keycurve", "gain" }); - REQUIRE(region.getNoteGain(50, 127_norm) == 1.0_a); - REQUIRE(region.getNoteGain(51, 127_norm) == 1.0_a); - REQUIRE(region.getNoteGain(52, 127_norm) == 0.75_a); - REQUIRE(region.getNoteGain(53, 127_norm) == 0.5_a); - REQUIRE(region.getNoteGain(54, 127_norm) == 0.25_a); - REQUIRE(region.getNoteGain(55, 127_norm) == 0.0_a); - REQUIRE(region.getNoteGain(56, 127_norm) == 0.0_a); + REQUIRE(noteGain(region, 50, 127_norm, midiState, curveSet) == 1.0_a); + REQUIRE(noteGain(region, 51, 127_norm, midiState, curveSet) == 1.0_a); + REQUIRE(noteGain(region, 52, 127_norm, midiState, curveSet) == 0.75_a); + REQUIRE(noteGain(region, 53, 127_norm, midiState, curveSet) == 0.5_a); + REQUIRE(noteGain(region, 54, 127_norm, midiState, curveSet) == 0.25_a); + REQUIRE(noteGain(region, 55, 127_norm, midiState, curveSet) == 0.0_a); + REQUIRE(noteGain(region, 56, 127_norm, midiState, curveSet) == 0.0_a); } TEST_CASE("[Region] Crossfade in on velocity") { Region region { 0 }; + MidiState midiState; + CurveSet curveSet { CurveSet::createPredefined() }; region.parseOpcode({ "sample", "*sine" }); region.parseOpcode({ "xfin_lovel", "20" }); region.parseOpcode({ "xfin_hivel", "24" }); region.parseOpcode({ "amp_veltrack", "0" }); - REQUIRE(region.getNoteGain(1, 19_norm) == 0.0_a); - REQUIRE(region.getNoteGain(1, 20_norm) == 0.0_a); - REQUIRE(region.getNoteGain(2, 21_norm) == 0.5_a); - REQUIRE(region.getNoteGain(3, 22_norm) == 0.70711_a); - REQUIRE(region.getNoteGain(4, 23_norm) == 0.86603_a); - REQUIRE(region.getNoteGain(5, 24_norm) == 1.0_a); - REQUIRE(region.getNoteGain(6, 25_norm) == 1.0_a); + REQUIRE(noteGain(region, 1, 19_norm, midiState, curveSet) == 0.0_a); + REQUIRE(noteGain(region, 1, 20_norm, midiState, curveSet) == 0.0_a); + REQUIRE(noteGain(region, 2, 21_norm, midiState, curveSet) == 0.5_a); + REQUIRE(noteGain(region, 3, 22_norm, midiState, curveSet) == 0.70711_a); + REQUIRE(noteGain(region, 4, 23_norm, midiState, curveSet) == 0.86603_a); + REQUIRE(noteGain(region, 5, 24_norm, midiState, curveSet) == 1.0_a); + REQUIRE(noteGain(region, 6, 25_norm, midiState, curveSet) == 1.0_a); } TEST_CASE("[Region] Crossfade in on vel - gain") { Region region { 0 }; + MidiState midiState; + CurveSet curveSet { CurveSet::createPredefined() }; region.parseOpcode({ "sample", "*sine" }); region.parseOpcode({ "xfin_lovel", "20" }); region.parseOpcode({ "xfin_hivel", "24" }); region.parseOpcode({ "xf_velcurve", "gain" }); region.parseOpcode({ "amp_veltrack", "0" }); - REQUIRE(region.getNoteGain(1, 19_norm) == 0.0_a); - REQUIRE(region.getNoteGain(1, 20_norm) == 0.0_a); - REQUIRE(region.getNoteGain(2, 21_norm) == 0.25_a); - REQUIRE(region.getNoteGain(3, 22_norm) == 0.5_a); - REQUIRE(region.getNoteGain(4, 23_norm) == 0.75_a); - REQUIRE(region.getNoteGain(5, 24_norm) == 1.0_a); - REQUIRE(region.getNoteGain(5, 25_norm) == 1.0_a); + REQUIRE(noteGain(region, 1, 19_norm, midiState, curveSet) == 0.0_a); + REQUIRE(noteGain(region, 1, 20_norm, midiState, curveSet) == 0.0_a); + REQUIRE(noteGain(region, 2, 21_norm, midiState, curveSet) == 0.25_a); + REQUIRE(noteGain(region, 3, 22_norm, midiState, curveSet) == 0.5_a); + REQUIRE(noteGain(region, 4, 23_norm, midiState, curveSet) == 0.75_a); + REQUIRE(noteGain(region, 5, 24_norm, midiState, curveSet) == 1.0_a); + REQUIRE(noteGain(region, 5, 25_norm, midiState, curveSet) == 1.0_a); } TEST_CASE("[Region] Crossfade out on vel") { Region region { 0 }; + MidiState midiState; + CurveSet curveSet { CurveSet::createPredefined() }; region.parseOpcode({ "sample", "*sine" }); region.parseOpcode({ "xfout_lovel", "51" }); region.parseOpcode({ "xfout_hivel", "55" }); region.parseOpcode({ "amp_veltrack", "0" }); - REQUIRE(region.getNoteGain(5, 50_norm) == 1.0_a); - REQUIRE(region.getNoteGain(5, 51_norm) == 1.0_a); - REQUIRE(region.getNoteGain(5, 52_norm) == 0.86603_a); - REQUIRE(region.getNoteGain(5, 53_norm) == 0.70711_a); - REQUIRE(region.getNoteGain(5, 54_norm) == 0.5_a); - REQUIRE(region.getNoteGain(5, 55_norm) == 0.0_a); - REQUIRE(region.getNoteGain(5, 56_norm) == 0.0_a); + REQUIRE(noteGain(region, 5, 50_norm, midiState, curveSet) == 1.0_a); + REQUIRE(noteGain(region, 5, 51_norm, midiState, curveSet) == 1.0_a); + REQUIRE(noteGain(region, 5, 52_norm, midiState, curveSet) == 0.86603_a); + REQUIRE(noteGain(region, 5, 53_norm, midiState, curveSet) == 0.70711_a); + REQUIRE(noteGain(region, 5, 54_norm, midiState, curveSet) == 0.5_a); + REQUIRE(noteGain(region, 5, 55_norm, midiState, curveSet) == 0.0_a); + REQUIRE(noteGain(region, 5, 56_norm, midiState, curveSet) == 0.0_a); } TEST_CASE("[Region] Crossfade out on vel - gain") { Region region { 0 }; + MidiState midiState; + CurveSet curveSet { CurveSet::createPredefined() }; region.parseOpcode({ "sample", "*sine" }); region.parseOpcode({ "xfout_lovel", "51" }); region.parseOpcode({ "xfout_hivel", "55" }); region.parseOpcode({ "xf_velcurve", "gain" }); region.parseOpcode({ "amp_veltrack", "0" }); - REQUIRE(region.getNoteGain(56, 50_norm) == 1.0_a); - REQUIRE(region.getNoteGain(56, 51_norm) == 1.0_a); - REQUIRE(region.getNoteGain(56, 52_norm) == 0.75_a); - REQUIRE(region.getNoteGain(56, 53_norm) == 0.5_a); - REQUIRE(region.getNoteGain(56, 54_norm) == 0.25_a); - REQUIRE(region.getNoteGain(56, 55_norm) == 0.0_a); - REQUIRE(region.getNoteGain(56, 56_norm) == 0.0_a); + REQUIRE(noteGain(region, 56, 50_norm, midiState, curveSet) == 1.0_a); + REQUIRE(noteGain(region, 56, 51_norm, midiState, curveSet) == 1.0_a); + REQUIRE(noteGain(region, 56, 52_norm, midiState, curveSet) == 0.75_a); + REQUIRE(noteGain(region, 56, 53_norm, midiState, curveSet) == 0.5_a); + REQUIRE(noteGain(region, 56, 54_norm, midiState, curveSet) == 0.25_a); + REQUIRE(noteGain(region, 56, 55_norm, midiState, curveSet) == 0.0_a); + REQUIRE(noteGain(region, 56, 56_norm, midiState, curveSet) == 0.0_a); } TEST_CASE("[Region] Crossfade in on CC") @@ -161,19 +180,19 @@ TEST_CASE("[Region] Crossfade in on CC") region.parseOpcode({ "xfin_hicc24", "24" }); region.parseOpcode({ "amp_veltrack", "0" }); midiState.ccEvent(0, 24, 19_norm); - REQUIRE(region.getCrossfadeGain(midiState) == 0.0_a); + REQUIRE(crossfadeGain(region, midiState) == 0.0_a); midiState.ccEvent(0, 24, 20_norm); - REQUIRE(region.getCrossfadeGain(midiState) == 0.0_a); + REQUIRE(crossfadeGain(region, midiState) == 0.0_a); midiState.ccEvent(0, 24, 21_norm); - REQUIRE(region.getCrossfadeGain(midiState) == 0.5_a); + REQUIRE(crossfadeGain(region, midiState) == 0.5_a); midiState.ccEvent(0, 24, 22_norm); - REQUIRE(region.getCrossfadeGain(midiState) == 0.70711_a); + REQUIRE(crossfadeGain(region, midiState) == 0.70711_a); midiState.ccEvent(0, 24, 23_norm); - REQUIRE(region.getCrossfadeGain(midiState) == 0.86603_a); + REQUIRE(crossfadeGain(region, midiState) == 0.86603_a); midiState.ccEvent(0, 24, 24_norm); - REQUIRE(region.getCrossfadeGain(midiState) == 1.0_a); + REQUIRE(crossfadeGain(region, midiState) == 1.0_a); midiState.ccEvent(0, 24, 25_norm); - REQUIRE(region.getCrossfadeGain(midiState) == 1.0_a); + REQUIRE(crossfadeGain(region, midiState) == 1.0_a); } TEST_CASE("[Region] Crossfade in on CC - gain") @@ -186,19 +205,19 @@ TEST_CASE("[Region] Crossfade in on CC - gain") region.parseOpcode({ "amp_veltrack", "0" }); region.parseOpcode({ "xf_cccurve", "gain" }); midiState.ccEvent(0, 24, 19_norm); - REQUIRE(region.getCrossfadeGain(midiState) == 0.0_a); + REQUIRE(crossfadeGain(region, midiState) == 0.0_a); midiState.ccEvent(0, 24, 20_norm); - REQUIRE(region.getCrossfadeGain(midiState) == 0.0_a); + REQUIRE(crossfadeGain(region, midiState) == 0.0_a); midiState.ccEvent(0, 24, 21_norm); - REQUIRE(region.getCrossfadeGain(midiState) == 0.25_a); + REQUIRE(crossfadeGain(region, midiState) == 0.25_a); midiState.ccEvent(0, 24, 22_norm); - REQUIRE(region.getCrossfadeGain(midiState) == 0.5_a); + REQUIRE(crossfadeGain(region, midiState) == 0.5_a); midiState.ccEvent(0, 24, 23_norm); - REQUIRE(region.getCrossfadeGain(midiState) == 0.75_a); + REQUIRE(crossfadeGain(region, midiState) == 0.75_a); midiState.ccEvent(0, 24, 24_norm); - REQUIRE(region.getCrossfadeGain(midiState) == 1.0_a); + REQUIRE(crossfadeGain(region, midiState) == 1.0_a); midiState.ccEvent(0, 24, 25_norm); - REQUIRE(region.getCrossfadeGain(midiState) == 1.0_a); + REQUIRE(crossfadeGain(region, midiState) == 1.0_a); } TEST_CASE("[Region] Crossfade out on CC") { @@ -209,19 +228,19 @@ TEST_CASE("[Region] Crossfade out on CC") region.parseOpcode({ "xfout_hicc24", "24" }); region.parseOpcode({ "amp_veltrack", "0" }); midiState.ccEvent(0, 24, 19_norm); - REQUIRE(region.getCrossfadeGain(midiState) == 1.0_a); + REQUIRE(crossfadeGain(region, midiState) == 1.0_a); midiState.ccEvent(0, 24, 20_norm); - REQUIRE(region.getCrossfadeGain(midiState) == 1.0_a); + REQUIRE(crossfadeGain(region, midiState) == 1.0_a); midiState.ccEvent(0, 24, 21_norm); - REQUIRE(region.getCrossfadeGain(midiState) == 0.86603_a); + REQUIRE(crossfadeGain(region, midiState) == 0.86603_a); midiState.ccEvent(0, 24, 22_norm); - REQUIRE(region.getCrossfadeGain(midiState) == 0.70711_a); + REQUIRE(crossfadeGain(region, midiState) == 0.70711_a); midiState.ccEvent(0, 24, 23_norm); - REQUIRE(region.getCrossfadeGain(midiState) == 0.5_a); + REQUIRE(crossfadeGain(region, midiState) == 0.5_a); midiState.ccEvent(0, 24, 24_norm); - REQUIRE(region.getCrossfadeGain(midiState) == 0.0_a); + REQUIRE(crossfadeGain(region, midiState) == 0.0_a); midiState.ccEvent(0, 24, 25_norm); - REQUIRE(region.getCrossfadeGain(midiState) == 0.0_a); + REQUIRE(crossfadeGain(region, midiState) == 0.0_a); } TEST_CASE("[Region] Crossfade out on CC - gain") @@ -234,47 +253,53 @@ TEST_CASE("[Region] Crossfade out on CC - gain") region.parseOpcode({ "amp_veltrack", "0" }); region.parseOpcode({ "xf_cccurve", "gain" }); midiState.ccEvent(0, 24, 19_norm); - REQUIRE(region.getCrossfadeGain(midiState) == 1.0_a); + REQUIRE(crossfadeGain(region, midiState) == 1.0_a); midiState.ccEvent(0, 24, 20_norm); - REQUIRE(region.getCrossfadeGain(midiState) == 1.0_a); + REQUIRE(crossfadeGain(region, midiState) == 1.0_a); midiState.ccEvent(0, 24, 21_norm); - REQUIRE(region.getCrossfadeGain(midiState) == 0.75_a); + REQUIRE(crossfadeGain(region, midiState) == 0.75_a); midiState.ccEvent(0, 24, 22_norm); - REQUIRE(region.getCrossfadeGain(midiState) == 0.5_a); + REQUIRE(crossfadeGain(region, midiState) == 0.5_a); midiState.ccEvent(0, 24, 23_norm); - REQUIRE(region.getCrossfadeGain(midiState) == 0.25_a); + REQUIRE(crossfadeGain(region, midiState) == 0.25_a); midiState.ccEvent(0, 24, 24_norm); - REQUIRE(region.getCrossfadeGain(midiState) == 0.0_a); + REQUIRE(crossfadeGain(region, midiState) == 0.0_a); midiState.ccEvent(0, 24, 25_norm); - REQUIRE(region.getCrossfadeGain(midiState) == 0.0_a); + REQUIRE(crossfadeGain(region, midiState) == 0.0_a); } TEST_CASE("[Region] Velocity bug for extreme values - veltrack at 0") { Region region { 0 }; + MidiState midiState; + CurveSet curveSet { CurveSet::createPredefined() }; region.parseOpcode({ "sample", "*sine" }); region.parseOpcode({ "amp_veltrack", "0" }); - REQUIRE(region.getNoteGain(64, 127_norm) == 1.0_a); - REQUIRE(region.getNoteGain(64, 0_norm) == 1.0_a); + REQUIRE(noteGain(region, 64, 127_norm, midiState, curveSet) == 1.0_a); + REQUIRE(noteGain(region, 64, 0_norm, midiState, curveSet) == 1.0_a); } TEST_CASE("[Region] Velocity bug for extreme values - positive veltrack") { Region region { 0 }; + MidiState midiState; + CurveSet curveSet { CurveSet::createPredefined() }; region.parseOpcode({ "sample", "*sine" }); region.parseOpcode({ "amp_veltrack", "100" }); - REQUIRE(region.getNoteGain(64, 127_norm) == 1.0_a); - REQUIRE(region.getNoteGain(64, 0_norm) == Approx(0.0).margin(0.0001)); + REQUIRE(noteGain(region, 64, 127_norm, midiState, curveSet) == 1.0_a); + REQUIRE(noteGain(region, 64, 0_norm, midiState, curveSet) == Approx(0.0).margin(0.0001)); } TEST_CASE("[Region] Velocity bug for extreme values - negative veltrack") { Region region { 0 }; + MidiState midiState; + CurveSet curveSet { CurveSet::createPredefined() }; region.parseOpcode({ "sample", "*sine" }); region.parseOpcode({ "amp_veltrack", "-100" }); - REQUIRE(region.getNoteGain(64, 127_norm) == Approx(0.0).margin(0.0001)); - REQUIRE(region.getNoteGain(64, 0_norm) == 1.0_a); + REQUIRE(noteGain(region, 64, 127_norm, midiState, curveSet) == Approx(0.0).margin(0.0001)); + REQUIRE(noteGain(region, 64, 0_norm, midiState, curveSet) == 1.0_a); } TEST_CASE("[Region] rt_decay") @@ -287,15 +312,15 @@ TEST_CASE("[Region] rt_decay") region.parseOpcode({ "rt_decay", "10" }); midiState.noteOnEvent(0, 64, 64_norm); midiState.advanceTime(100); - REQUIRE( region.getBaseVolumedB(midiState, 64) == Approx(Default::volume - 1.0f).margin(0.1) ); + REQUIRE( baseVolumedB(region, midiState, 64) == Approx(Default::volume - 1.0f).margin(0.1) ); region.parseOpcode({ "rt_decay", "20" }); midiState.noteOnEvent(0, 64, 64_norm); midiState.advanceTime(100); - REQUIRE( region.getBaseVolumedB(midiState, 64) == Approx(Default::volume - 2.0f).margin(0.1) ); + REQUIRE( baseVolumedB(region, midiState, 64) == Approx(Default::volume - 2.0f).margin(0.1) ); region.parseOpcode({ "trigger", "attack" }); midiState.noteOnEvent(0, 64, 64_norm); midiState.advanceTime(100); - REQUIRE( region.getBaseVolumedB(midiState, 64) == Approx(Default::volume).margin(0.1) ); + REQUIRE( baseVolumedB(region, midiState, 64) == Approx(Default::volume).margin(0.1) ); } TEST_CASE("[Region] Base delay") @@ -304,12 +329,12 @@ TEST_CASE("[Region] Base delay") Region region { 0 }; region.parseOpcode({ "sample", "*sine" }); region.parseOpcode({ "delay", "10" }); - REQUIRE( region.getDelay(midiState) == 10.0f ); + REQUIRE( regionDelay(region, midiState) == 10.0f ); region.parseOpcode({ "delay_random", "10" }); Random::randomGenerator.seed(42); for (int i = 0; i < numRandomTests; ++i) { - auto delay = region.getDelay(midiState); + auto delay = regionDelay(region, midiState); REQUIRE( (delay >= 10.0 && delay <= 20.0) ); } } @@ -321,26 +346,133 @@ TEST_CASE("[Region] Offsets with CCs") region.parseOpcode({ "offset_cc4", "255" }); region.parseOpcode({ "offset", "10" }); - REQUIRE( region.getOffset(midiState) == 10 ); + REQUIRE( sampleOffset(region, midiState) == 10 ); midiState.ccEvent(0, 4, 127_norm); - REQUIRE( region.getOffset(midiState) == 265 ); + REQUIRE( sampleOffset(region, midiState) == 265 ); midiState.ccEvent(0, 4, 100_norm); - REQUIRE( region.getOffset(midiState) == 210 ); + REQUIRE( sampleOffset(region, midiState) == 210 ); midiState.ccEvent(0, 4, 10_norm); - REQUIRE( region.getOffset(midiState) == 30 ); + REQUIRE( sampleOffset(region, midiState) == 30 ); midiState.ccEvent(0, 4, 0); - REQUIRE( region.getOffset(midiState) == 10 ); + REQUIRE( sampleOffset(region, midiState) == 10 ); } TEST_CASE("[Region] Pitch variation with veltrack") { Region region { 0 }; + MidiState midiState; + CurveSet curveSet { CurveSet::createPredefined() }; - REQUIRE(region.getBasePitchVariation(60.0, 0_norm) == 1.0); - REQUIRE(region.getBasePitchVariation(60.0, 64_norm) == 1.0); - REQUIRE(region.getBasePitchVariation(60.0, 127_norm) == 1.0); + REQUIRE(basePitchVariation(region, 60.0, 0_norm, midiState, curveSet) == 1.0); + REQUIRE(basePitchVariation(region, 60.0, 64_norm, midiState, curveSet) == 1.0); + REQUIRE(basePitchVariation(region, 60.0, 127_norm, midiState, curveSet) == 1.0); region.parseOpcode({ "pitch_veltrack", "1200" }); - REQUIRE(region.getBasePitchVariation(60.0, 0_norm) == 1.0); - REQUIRE(region.getBasePitchVariation(60.0, 64_norm) == Approx(centsFactor(600.0)).margin(0.01f)); - REQUIRE(region.getBasePitchVariation(60.0, 127_norm) == Approx(centsFactor(1200.0)).margin(0.01f)); + REQUIRE(basePitchVariation(region, 60.0, 0_norm, midiState, curveSet) == 1.0); + REQUIRE(basePitchVariation(region, 60.0, 64_norm, midiState, curveSet) == Approx(centsFactor(600.0)).margin(0.01f)); + REQUIRE(basePitchVariation(region, 60.0, 127_norm, midiState, curveSet) == Approx(centsFactor(1200.0)).margin(0.01f)); +} + +TEST_CASE("[Synth] velcurve") +{ + MidiState midiState; + CurveSet curveSet { CurveSet::createPredefined() }; + + struct VelocityData { float velocity, gain; bool exact; }; + static const VelocityData veldata[] = { + { 0_norm, 0.0, true }, + { 32_norm, 0.5f, false }, + { 64_norm, 1.0, true }, + { 96_norm, 1.0, true }, + { 127_norm, 1.0, true }, + }; + + SECTION("Default veltrack") + { + sfz::Region region { 0 }; + region.parseOpcode({ "sample", "*sine" }); + region.parseOpcode({ "amp_velcurve_064", "1" }); + region.velCurve = Curve::buildFromVelcurvePoints( + region.velocityPoints, Curve::Interpolator::Linear); + for (const VelocityData& vd : veldata) { + if (vd.exact) { + REQUIRE(velocityCurve(region, vd.velocity, midiState, curveSet) == vd.gain); + } else { + REQUIRE(velocityCurve(region, vd.velocity, midiState, curveSet) == Approx(vd.gain).margin(1e-2)); + } + } + } + + SECTION("Inverted veltrack") + { + sfz::Region region { 0 }; + region.parseOpcode({ "sample", "*sine" }); + region.parseOpcode({ "amp_velcurve_064", "1" }); + region.parseOpcode({ "amp_veltrack", "-100" }); + region.velCurve = Curve::buildFromVelcurvePoints( + region.velocityPoints, Curve::Interpolator::Linear); + for (const VelocityData& vd : veldata) { + if (vd.exact) { + REQUIRE(velocityCurve(region, vd.velocity, midiState, curveSet) == 1.0f - vd.gain); + } else { + REQUIRE(velocityCurve(region, vd.velocity, midiState, curveSet) == Approx( 1.0f - vd.gain).margin(1e-2)); + } + } + } +} + +TEST_CASE("[Synth] veltrack") +{ + struct VelocityData { float velocity, dBGain; }; + struct VeltrackData { float veltrack; absl::Span veldata; }; + + MidiState midiState; + CurveSet curveSet { CurveSet::createPredefined() }; + + // measured on ARIA + const VelocityData veldata25[] = { + { 127_norm, 0.0 }, + { 96_norm, -1 }, + { 64_norm, -1.8 }, + { 32_norm, -2.3 }, + { 1_norm, -2.5 }, + }; + const VelocityData veldata50[] = { + { 127_norm, 0.0 }, + { 96_norm, -2.1 }, + { 64_norm, -4.1 }, + { 32_norm, -5.5 }, + { 1_norm, -6.0 }, + }; + const VelocityData veldata75[] = { + { 127_norm, 0.0 }, + { 96_norm, -3.4 }, + { 64_norm, -7.2 }, + { 32_norm, -10.5 }, + { 1_norm, -12.0 }, + }; + const VelocityData veldata100[] = { + { 127_norm, 0.0 }, + { 96_norm, -4.9 }, + { 64_norm, -12.0 }, + { 32_norm, -24.0 }, + { 1_norm, -84.1 }, + }; + + const VeltrackData veltrackdata[] = { + { 25, absl::MakeConstSpan(veldata25) }, + { 50, absl::MakeConstSpan(veldata50) }, + { 75, absl::MakeConstSpan(veldata75) }, + { 100, absl::MakeConstSpan(veldata100) }, + }; + + for (const VeltrackData& vt : veltrackdata) { + sfz::Region region { 0 }; + region.parseOpcode({ "sample", "*sine" }); + region.parseOpcode({ "amp_veltrack", std::to_string(vt.veltrack) }); + + for (const VelocityData& vd : vt.veldata) { + float dBGain = 20.0f * std::log10(velocityCurve(region, vd.velocity, midiState, curveSet)); + REQUIRE(dBGain == Approx(vd.dBGain).margin(0.1)); + } + } } diff --git a/tests/RegionValuesT.cpp b/tests/RegionValuesT.cpp index 9628d3a68..28e5e0a91 100644 --- a/tests/RegionValuesT.cpp +++ b/tests/RegionValuesT.cpp @@ -356,6 +356,9 @@ TEST_CASE("[Values] Loop range") sample=kick.wav sample=kick.wav loop_start_cc12=10 loop_end_cc14=-100 sample=kick.wav loop_start_oncc12=-10 loop_end_oncc14=100 + sample=kick.wav loop_startcc12=-10 loop_lengthcc14=100 + sample=kick.wav loop_length_oncc14=100 + sample=kick.wav loop_length_cc14=100 )"); synth.dispatchMessage(client, 0, "/region0/loop_start_cc12", "", nullptr); synth.dispatchMessage(client, 0, "/region0/loop_end_cc14", "", nullptr); @@ -363,6 +366,10 @@ TEST_CASE("[Values] Loop range") synth.dispatchMessage(client, 0, "/region1/loop_end_cc14", "", nullptr); synth.dispatchMessage(client, 0, "/region2/loop_start_cc12", "", nullptr); synth.dispatchMessage(client, 0, "/region2/loop_end_cc14", "", nullptr); + synth.dispatchMessage(client, 0, "/region3/loop_start_cc12", "", nullptr); + synth.dispatchMessage(client, 0, "/region3/loop_end_cc14", "", nullptr); + synth.dispatchMessage(client, 0, "/region4/loop_end_cc14", "", nullptr); + synth.dispatchMessage(client, 0, "/region5/loop_end_cc14", "", nullptr); std::vector expected { "/region0/loop_start_cc12,h : { 0 }", "/region0/loop_end_cc14,h : { 0 }", @@ -370,6 +377,10 @@ TEST_CASE("[Values] Loop range") "/region1/loop_end_cc14,h : { -100 }", "/region2/loop_start_cc12,h : { -10 }", "/region2/loop_end_cc14,h : { 100 }", + "/region3/loop_start_cc12,h : { -10 }", + "/region3/loop_end_cc14,h : { 100 }", + "/region4/loop_end_cc14,h : { 100 }", + "/region5/loop_end_cc14,h : { 100 }", }; REQUIRE(messageList == expected); } @@ -426,7 +437,7 @@ TEST_CASE("[Values] Group") synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( sample=kick.wav sample=kick.wav group=5 - sample=kick.wav group=-1 + sample=kick.wav group=-2 )"); synth.dispatchMessage(client, 0, "/region0/group", "", nullptr); synth.dispatchMessage(client, 0, "/region1/group", "", nullptr); @@ -434,7 +445,7 @@ TEST_CASE("[Values] Group") std::vector expected { "/region0/group,h : { 0 }", "/region1/group,h : { 5 }", - "/region2/group,h : { 0 }", + "/region2/group,h : { -2 }", }; REQUIRE(messageList == expected); } @@ -448,7 +459,7 @@ TEST_CASE("[Values] Off by") synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( sample=kick.wav sample=kick.wav off_by=5 - sample=kick.wav off_by=-1 + sample=kick.wav off_by=-2 )"); synth.dispatchMessage(client, 0, "/region0/off_by", "", nullptr); synth.dispatchMessage(client, 0, "/region1/off_by", "", nullptr); @@ -456,7 +467,7 @@ TEST_CASE("[Values] Off by") std::vector expected { "/region0/off_by,N : { }", "/region1/off_by,h : { 5 }", - "/region2/off_by,N : { }", + "/region2/off_by,h : { -2 }", }; REQUIRE(messageList == expected); } @@ -566,17 +577,20 @@ TEST_CASE("[Values] Triggers on note") sample=kick.wav hikey=-1 sample=kick.wav key=-1 sample=kick.wav hikey=-1 lokey=12 + sample=kick.wav hikey=-1 lokey=-1 )"); synth.dispatchMessage(client, 0, "/region0/trigger_on_note", "", nullptr); synth.dispatchMessage(client, 0, "/region1/trigger_on_note", "", nullptr); synth.dispatchMessage(client, 0, "/region2/trigger_on_note", "", nullptr); // TODO: Double check with Sforzando/rgc synth.dispatchMessage(client, 0, "/region3/trigger_on_note", "", nullptr); + synth.dispatchMessage(client, 0, "/region4/trigger_on_note", "", nullptr); std::vector expected { "/region0/trigger_on_note,T : { }", "/region1/trigger_on_note,F : { }", "/region2/trigger_on_note,F : { }", "/region3/trigger_on_note,T : { }", + "/region4/trigger_on_note,F : { }", }; REQUIRE(messageList == expected); } @@ -599,8 +613,8 @@ TEST_CASE("[Values] Velocity range") synth.dispatchMessage(client, 0, "/region3/vel_range", "", nullptr); std::vector expected { "/region0/vel_range,ff : { 0, 1 }", - "/region1/vel_range,ff : { 0.267717, 0.472441 }", - "/region2/vel_range,ff : { -0.023622, 0.472441 }", + "/region1/vel_range,ff : { 0.267717, 0.480315 }", + "/region2/vel_range,ff : { -0.023622, 0.480315 }", "/region3/vel_range,ff : { 0, -0.00787402 }", }; REQUIRE(messageList == expected); @@ -653,9 +667,9 @@ TEST_CASE("[Values] CC condition range") synth.dispatchMessage(client, 0, "/region3/cc_range1", "", nullptr); std::vector expected { "/region0/cc_range1,ff : { 0, 1 }", - "/region1/cc_range1,ff : { 0, 0.425197 }", - "/region2/cc_range1,ff : { 0, 0.425197 }", - "/region2/cc_range2,ff : { 0.015748, 0.0787402 }", + "/region1/cc_range1,ff : { 0, 0.433071 }", + "/region2/cc_range1,ff : { 0, 0.433071 }", + "/region2/cc_range2,ff : { 0.015748, 0.0866142 }", "/region3/cc_range1,ff : { 0.0787402, -0.00787402 }", }; REQUIRE(messageList == expected); @@ -914,10 +928,10 @@ TEST_CASE("[Values] Aftertouch range") synth.dispatchMessage(client, 0, "/region4/chanaft_range", "", nullptr); std::vector expected { "/region0/chanaft_range,ff : { 0, 1 }", - "/region1/chanaft_range,ff : { 0.267717, 0.472441 }", - "/region2/chanaft_range,ff : { -0.023622, 0.472441 }", + "/region1/chanaft_range,ff : { 0.267717, 0.480315 }", + "/region2/chanaft_range,ff : { -0.023622, 0.480315 }", "/region3/chanaft_range,ff : { 0.15748, -0.00787402 }", - "/region4/chanaft_range,ff : { 0.15748, 0.0787402 }", + "/region4/chanaft_range,ff : { 0.15748, 0.0866142 }", }; REQUIRE(messageList == expected); } @@ -942,10 +956,10 @@ TEST_CASE("[Values] Polyaftertouch range") synth.dispatchMessage(client, 0, "/region4/polyaft_range", "", nullptr); std::vector expected { "/region0/polyaft_range,ff : { 0, 1 }", - "/region1/polyaft_range,ff : { 0.267717, 0.472441 }", - "/region2/polyaft_range,ff : { -0.023622, 0.472441 }", + "/region1/polyaft_range,ff : { 0.267717, 0.480315 }", + "/region2/polyaft_range,ff : { -0.023622, 0.480315 }", "/region3/polyaft_range,ff : { 0.15748, -0.00787402 }", - "/region4/polyaft_range,ff : { 0.15748, 0.0787402 }", + "/region4/polyaft_range,ff : { 0.15748, 0.0866142 }", }; REQUIRE(messageList == expected); } @@ -1123,8 +1137,8 @@ TEST_CASE("[Values] Start on cc range") "/region0/start_cc_range1,N : { }", "/region0/start_cc_range2,N : { }", "/region1/start_cc_range1,ff : { 0.11811, 1 }", - "/region2/start_cc_range1,ff : { 0, 0.661417 }", - "/region3/start_cc_range1,ff : { 0.11811, 0.661417 }", + "/region2/start_cc_range1,ff : { 0, 0.669291 }", + "/region3/start_cc_range1,ff : { 0.11811, 0.669291 }", "/region4/start_cc_range2,ff : { 0.1, 1 }", "/region5/start_cc_range2,ff : { 0, 0.4 }", "/region6/start_cc_range2,ff : { 0.1, 0.4 }", @@ -1629,20 +1643,47 @@ TEST_CASE("[Values] Amp Veltrack") Client client(&messageList); client.setReceiveCallback(&simpleMessageReceiver); - synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( - sample=kick.wav - sample=kick.wav amp_veltrack=10.1 - sample=kick.wav amp_veltrack=-132 - )"); - synth.dispatchMessage(client, 0, "/region0/amp_veltrack", "", nullptr); - synth.dispatchMessage(client, 0, "/region1/amp_veltrack", "", nullptr); - synth.dispatchMessage(client, 0, "/region2/amp_veltrack", "", nullptr); - std::vector expected { - "/region0/amp_veltrack,f : { 100 }", - "/region1/amp_veltrack,f : { 10.1 }", - "/region2/amp_veltrack,f : { -132 }", - }; - REQUIRE(messageList == expected); + SECTION("Basic") + { + synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + sample=kick.wav + sample=kick.wav amp_veltrack=10.1 + sample=kick.wav amp_veltrack=-132 + )"); + synth.dispatchMessage(client, 0, "/region0/amp_veltrack", "", nullptr); + synth.dispatchMessage(client, 0, "/region1/amp_veltrack", "", nullptr); + synth.dispatchMessage(client, 0, "/region2/amp_veltrack", "", nullptr); + std::vector expected { + "/region0/amp_veltrack,f : { 100 }", + "/region1/amp_veltrack,f : { 10.1 }", + "/region2/amp_veltrack,f : { -132 }", + }; + REQUIRE(messageList == expected); + } + + SECTION("CC") + { + synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + sample=kick.wav + sample=kick.wav amp_veltrack_cc1=10.1 amp_veltrack_curvecc1=3 + sample=kick.wav amp_veltrack_oncc2=-40 amp_veltrack_curvecc3=4 + )"); + synth.dispatchMessage(client, 0, "/region0/amp_veltrack_cc1", "", nullptr); + synth.dispatchMessage(client, 0, "/region1/amp_veltrack_cc1", "", nullptr); + synth.dispatchMessage(client, 0, "/region1/amp_veltrack_curvecc1", "", nullptr); + synth.dispatchMessage(client, 0, "/region2/amp_veltrack_cc2", "", nullptr); + synth.dispatchMessage(client, 0, "/region2/amp_veltrack_curvecc3", "", nullptr); + // TODO: activate for the new region parser ; accept oob + // synth.dispatchMessage(client, 0, "/region2/amp_veltrack", "", nullptr); + std::vector expected { + "/region0/amp_veltrack_cc1,N : { }", + "/region1/amp_veltrack_cc1,f : { 10.1 }", + "/region1/amp_veltrack_curvecc1,i : { 3 }", + "/region2/amp_veltrack_cc2,f : { -40 }", + "/region2/amp_veltrack_curvecc3,i : { 4 }", + }; + REQUIRE(messageList == expected); + } } TEST_CASE("[Values] Amp Random") @@ -1747,8 +1788,8 @@ TEST_CASE("[Values] Crossfade velocity range") synth.dispatchMessage(client, 0, "/region3/xfin_vel_range", "", nullptr); std::vector expected { "/region0/xfin_vel_range,ff : { 0, 0 }", - "/region1/xfin_vel_range,ff : { 0.0787402, 0.314961 }", - "/region2/xfin_vel_range,ff : { -0.0787402, 0.314961 }", + "/region1/xfin_vel_range,ff : { 0.0787402, 0.322835 }", + "/region2/xfin_vel_range,ff : { -0.0787402, 0.322835 }", "/region3/xfin_vel_range,ff : { 0.0787402, 1.10236 }", }; REQUIRE(messageList == expected); @@ -1768,8 +1809,8 @@ TEST_CASE("[Values] Crossfade velocity range") synth.dispatchMessage(client, 0, "/region3/xfout_vel_range", "", nullptr); std::vector expected { "/region0/xfout_vel_range,ff : { 1, 1 }", - "/region1/xfout_vel_range,ff : { 0.0787402, 0.314961 }", - "/region2/xfout_vel_range,ff : { -0.0787402, 0.314961 }", + "/region1/xfout_vel_range,ff : { 0.0787402, 0.322835 }", + "/region2/xfout_vel_range,ff : { -0.0787402, 0.322835 }", "/region3/xfout_vel_range,ff : { 0.0787402, 1.10236 }", }; REQUIRE(messageList == expected); @@ -1868,8 +1909,8 @@ TEST_CASE("[Values] Crossfade CC range") synth.dispatchMessage(client, 0, "/region3/xfin_cc_range4", "", nullptr); std::vector expected { "/region0/xfin_cc_range4,N : { }", - "/region1/xfin_cc_range4,ff : { 0.0787402, 0.314961 }", - "/region2/xfin_cc_range4,ff : { -0.0787402, 0.314961 }", + "/region1/xfin_cc_range4,ff : { 0.0787402, 0.322835 }", + "/region2/xfin_cc_range4,ff : { -0.0787402, 0.322835 }", "/region3/xfin_cc_range4,ff : { 0.0787402, 1.10236 }", }; REQUIRE(messageList == expected); @@ -1889,8 +1930,8 @@ TEST_CASE("[Values] Crossfade CC range") synth.dispatchMessage(client, 0, "/region3/xfout_cc_range4", "", nullptr); std::vector expected { "/region0/xfout_cc_range4,N : { }", - "/region1/xfout_cc_range4,ff : { 0.0787402, 0.314961 }", - "/region2/xfout_cc_range4,ff : { -0.0787402, 0.314961 }", + "/region1/xfout_cc_range4,ff : { 0.0787402, 0.322835 }", + "/region2/xfout_cc_range4,ff : { -0.0787402, 0.322835 }", "/region3/xfout_cc_range4,ff : { 0.0787402, 1.10236 }", }; REQUIRE(messageList == expected); @@ -1987,20 +2028,47 @@ TEST_CASE("[Values] Pitch Veltrack") Client client(&messageList); client.setReceiveCallback(&simpleMessageReceiver); - synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( - sample=kick.wav - sample=kick.wav pitch_veltrack=10 - sample=kick.wav pitch_veltrack=-132 - )"); - synth.dispatchMessage(client, 0, "/region0/pitch_veltrack", "", nullptr); - synth.dispatchMessage(client, 0, "/region1/pitch_veltrack", "", nullptr); - synth.dispatchMessage(client, 0, "/region2/pitch_veltrack", "", nullptr); - std::vector expected { - "/region0/pitch_veltrack,i : { 0 }", - "/region1/pitch_veltrack,i : { 10 }", - "/region2/pitch_veltrack,i : { -132 }", - }; - REQUIRE(messageList == expected); + SECTION("Basic") + { + synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + sample=kick.wav + sample=kick.wav pitch_veltrack=10 + sample=kick.wav pitch_veltrack=-132 + )"); + synth.dispatchMessage(client, 0, "/region0/pitch_veltrack", "", nullptr); + synth.dispatchMessage(client, 0, "/region1/pitch_veltrack", "", nullptr); + synth.dispatchMessage(client, 0, "/region2/pitch_veltrack", "", nullptr); + std::vector expected { + "/region0/pitch_veltrack,i : { 0 }", + "/region1/pitch_veltrack,i : { 10 }", + "/region2/pitch_veltrack,i : { -132 }", + }; + REQUIRE(messageList == expected); + } + + SECTION("CC") + { + synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + sample=kick.wav + sample=kick.wav pitch_veltrack_cc1=10.1 pitch_veltrack_curvecc1=3 + sample=kick.wav pitch_veltrack_oncc2=-40 pitch_veltrack_curvecc3=4 + )"); + synth.dispatchMessage(client, 0, "/region0/pitch_veltrack_cc1", "", nullptr); + synth.dispatchMessage(client, 0, "/region1/pitch_veltrack_cc1", "", nullptr); + synth.dispatchMessage(client, 0, "/region1/pitch_veltrack_curvecc1", "", nullptr); + synth.dispatchMessage(client, 0, "/region2/pitch_veltrack_cc2", "", nullptr); + synth.dispatchMessage(client, 0, "/region2/pitch_veltrack_curvecc3", "", nullptr); + // TODO: activate for the new region parser ; accept oob + // synth.dispatchMessage(client, 0, "/region2/pitch_veltrack", "", nullptr); + std::vector expected { + "/region0/pitch_veltrack_cc1,N : { }", + "/region1/pitch_veltrack_cc1,f : { 10.1 }", + "/region1/pitch_veltrack_curvecc1,i : { 3 }", + "/region2/pitch_veltrack_cc2,f : { -40 }", + "/region2/pitch_veltrack_curvecc3,i : { 4 }", + }; + REQUIRE(messageList == expected); + } } TEST_CASE("[Values] Pitch Random") @@ -2992,6 +3060,7 @@ TEST_CASE("[Values] Filter dispatching") sample=kick.wav cutoff3=50 resonance2=3 fil2_gain=-5 fil3_keytrack=100 fil_gain=5 fil1_gain=-5 fil2_veltrack=-100 + fil4_veltrack_cc7=-100 fil5_veltrack_curvecc2=2 )"); synth.dispatchMessage(client, 0, "/region0/filter2/cutoff", "", nullptr); @@ -3000,6 +3069,8 @@ TEST_CASE("[Values] Filter dispatching") synth.dispatchMessage(client, 0, "/region0/filter2/keytrack", "", nullptr); synth.dispatchMessage(client, 0, "/region0/filter0/gain", "", nullptr); synth.dispatchMessage(client, 0, "/region0/filter1/veltrack", "", nullptr); + synth.dispatchMessage(client, 0, "/region0/filter3/veltrack_cc7", "", nullptr); + synth.dispatchMessage(client, 0, "/region0/filter4/veltrack_curvecc2", "", nullptr); std::vector expected { "/region0/filter2/cutoff,f : { 50 }", "/region0/filter1/resonance,f : { 3 }", @@ -3007,6 +3078,8 @@ TEST_CASE("[Values] Filter dispatching") "/region0/filter2/keytrack,i : { 100 }", "/region0/filter0/gain,f : { -5 }", "/region0/filter1/veltrack,i : { -100 }", + "/region0/filter3/veltrack_cc7,f : { -100 }", + "/region0/filter4/veltrack_curvecc2,i : { 2 }", }; REQUIRE(messageList == expected); } @@ -3251,3 +3324,81 @@ TEST_CASE("[Values] EQ value bounds") REQUIRE(messageList == expected); } } + +TEST_CASE("[Values] Flex EGs") +{ + Synth synth; + std::vector messageList; + Client client(&messageList); + client.setReceiveCallback(&simpleMessageReceiver); + + synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + sample=kick.wav eg1_time1=0.1 eg1_level1=0.5 eg1_time2=0.4 eg1_level2=2 eg2_time1=4 eg2_level1=0.1 + )"); + synth.dispatchMessage(client, 0, "/region0/eg0/point0/time", "", nullptr); + synth.dispatchMessage(client, 0, "/region0/eg0/point0/level", "", nullptr); + synth.dispatchMessage(client, 0, "/region0/eg0/point1/time", "", nullptr); + synth.dispatchMessage(client, 0, "/region0/eg0/point1/level", "", nullptr); + synth.dispatchMessage(client, 0, "/region0/eg1/point0/time", "", nullptr); + synth.dispatchMessage(client, 0, "/region0/eg1/point0/level", "", nullptr); + std::vector expected { + "/region0/eg0/point0/time,f : { 0.1 }", + "/region0/eg0/point0/level,f : { 0.5 }", + "/region0/eg0/point1/time,f : { 0.4 }", + "/region0/eg0/point1/level,f : { 2 }", + "/region0/eg1/point0/time,f : { 4 }", + "/region0/eg1/point0/level,f : { 0.1 }", + }; + REQUIRE(messageList == expected); +} + +TEST_CASE("[Values] Flex EGs CC") +{ + Synth synth; + std::vector messageList; + Client client(&messageList); + client.setReceiveCallback(&simpleMessageReceiver); + + synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + sample=kick.wav eg1_time1_cc2=0.1 eg1_level1_oncc3=0.5 + )"); + synth.dispatchMessage(client, 0, "/region0/eg0/point0/time_cc2", "", nullptr); + synth.dispatchMessage(client, 0, "/region0/eg0/point0/time_cc4", "", nullptr); + synth.dispatchMessage(client, 0, "/region0/eg0/point0/level_cc3", "", nullptr); + synth.dispatchMessage(client, 0, "/region0/eg0/point0/level_cc12", "", nullptr); + std::vector expected { + "/region0/eg0/point0/time_cc2,f : { 0.1 }", + "/region0/eg0/point0/time_cc4,f : { 0 }", + "/region0/eg0/point0/level_cc3,f : { 0.5 }", + "/region0/eg0/point0/level_cc12,f : { 0 }", + }; + REQUIRE(messageList == expected); +} + +TEST_CASE("[Values] Dynamic EGs") +{ + Synth synth; + std::vector messageList; + Client client(&messageList); + client.setReceiveCallback(&simpleMessageReceiver); + + synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + sample=kick.wav + sample=kick.wav ampeg_dynamic=1 pitcheg_dynamic=1 fileg_dynamic=1 + )"); + synth.dispatchMessage(client, 0, "/region0/ampeg_dynamic", "", nullptr); + synth.dispatchMessage(client, 0, "/region0/pitcheg_dynamic", "", nullptr); + synth.dispatchMessage(client, 0, "/region0/fileg_dynamic", "", nullptr); + synth.dispatchMessage(client, 0, "/region1/ampeg_dynamic", "", nullptr); + synth.dispatchMessage(client, 0, "/region1/pitcheg_dynamic", "", nullptr); + synth.dispatchMessage(client, 0, "/region1/fileg_dynamic", "", nullptr); + std::vector expected { + "/region0/ampeg_dynamic,F : { }", + "/region0/pitcheg_dynamic,F : { }", + "/region0/fileg_dynamic,F : { }", + "/region1/ampeg_dynamic,T : { }", + "/region1/pitcheg_dynamic,T : { }", + "/region1/fileg_dynamic,T : { }", + }; + REQUIRE(messageList == expected); +} diff --git a/tests/SynthT.cpp b/tests/SynthT.cpp index 2e71c21f3..b0f3feda5 100644 --- a/tests/SynthT.cpp +++ b/tests/SynthT.cpp @@ -141,7 +141,7 @@ TEST_CASE("[Synth] Reset all controllers") { sfz::Synth synth; sfz::AudioBuffer buffer { 2, static_cast(synth.getSamplesPerBlock()) }; - const auto& midiState = synth.getResources().midiState; + const sfz::MidiState& midiState = synth.getResources().getMidiState(); synth.cc(0, 12, 64); synth.renderBlock(buffer); REQUIRE(midiState.getCCValue(12) == 64_norm); @@ -205,7 +205,7 @@ TEST_CASE("[Synth] Trigger=release and an envelope properly kills the voice at t synth.renderBlock(buffer); // Decay (0.02) synth.renderBlock(buffer); synth.renderBlock(buffer); // Release (0.1) - REQUIRE(synth.getVoiceView(0)->releasedOrFree()); + REQUIRE(synth.getVoiceView(0)->offedOrFree()); // Release is 0.1s for (int i = 0; i < 10; ++i) synth.renderBlock(buffer); @@ -232,7 +232,7 @@ TEST_CASE("[Synth] Trigger=release_key and an envelope properly kills the voice synth.renderBlock(buffer); // Decay (0.02) synth.renderBlock(buffer); synth.renderBlock(buffer); // Release (0.1) - REQUIRE(synth.getVoiceView(0)->releasedOrFree()); + REQUIRE(synth.getVoiceView(0)->released()); // Release is 0.1s for (int i = 0; i < 10; ++i) synth.renderBlock(buffer); @@ -259,7 +259,7 @@ TEST_CASE("[Synth] loopmode=one_shot and an envelope properly kills the voice at synth.renderBlock(buffer); // Decay (0.02) synth.renderBlock(buffer); synth.renderBlock(buffer); // Release (0.1) - REQUIRE(synth.getVoiceView(0)->releasedOrFree()); + REQUIRE(synth.getVoiceView(0)->released()); // Release is 0.1s for (int i = 0; i < 10; ++i) synth.renderBlock(buffer); @@ -401,7 +401,7 @@ TEST_CASE("[Synth] Gain to mix") TEST_CASE("[Synth] Basic curves") { sfz::Synth synth; - const auto& curves = synth.getResources().curves; + const sfz::CurveSet& curves = synth.getResources().getCurves(); synth.loadSfzString(fs::current_path() / "tests/TestFiles/curves.sfz", R"( sample=*sine curve_index=18 v000=0 v095=0.5 v127=1 @@ -431,98 +431,6 @@ TEST_CASE("[Synth] Velocity points") REQUIRE( synth.getRegionView(1)->velocityPoints[0].second == 1.0_a ); } -TEST_CASE("[Synth] velcurve") -{ - sfz::Synth synth; - synth.loadSfzString(fs::current_path() / "tests/TestFiles/velocity_endpoints.sfz", R"( - amp_velcurve_064=1 sample=*sine - amp_velcurve_064=1 amp_veltrack=-100 sample=*sine - )"); - - struct VelocityData { float velocity, gain; bool exact; }; - - static const VelocityData veldata[] = { - { 0_norm, 0.0, true }, - { 32_norm, 0.5f, false }, - { 64_norm, 1.0, true }, - { 96_norm, 1.0, true }, - { 127_norm, 1.0, true }, - }; - - REQUIRE(synth.getNumRegions() == 2); - const sfz::Region* r1 = synth.getRegionView(0); - const sfz::Region* r2 = synth.getRegionView(1); - - for (const VelocityData& vd : veldata) { - if (vd.exact) { - REQUIRE(r1->velocityCurve(vd.velocity) == vd.gain); - REQUIRE(r2->velocityCurve(vd.velocity) == 1.0f - vd.gain); - } - else { - REQUIRE(r1->velocityCurve(vd.velocity) == Approx(vd.gain).margin(1e-2)); - REQUIRE(r2->velocityCurve(vd.velocity) == Approx(1.0f - vd.gain).margin(1e-2)); - } - } -} - -TEST_CASE("[Synth] veltrack") -{ - struct VelocityData { float velocity, dBGain; }; - struct VeltrackData { float veltrack; absl::Span veldata; }; - - // measured on ARIA - const VelocityData veldata25[] = { - { 127_norm, 0.0 }, - { 96_norm, -1 }, - { 64_norm, -1.8 }, - { 32_norm, -2.3 }, - { 1_norm, -2.5 }, - }; - const VelocityData veldata50[] = { - { 127_norm, 0.0 }, - { 96_norm, -2.1 }, - { 64_norm, -4.1 }, - { 32_norm, -5.5 }, - { 1_norm, -6.0 }, - }; - const VelocityData veldata75[] = { - { 127_norm, 0.0 }, - { 96_norm, -3.4 }, - { 64_norm, -7.2 }, - { 32_norm, -10.5 }, - { 1_norm, -12.0 }, - }; - const VelocityData veldata100[] = { - { 127_norm, 0.0 }, - { 96_norm, -4.9 }, - { 64_norm, -12.0 }, - { 32_norm, -24.0 }, - { 1_norm, -84.1 }, - }; - - const VeltrackData veltrackdata[] = { - { 25, absl::MakeConstSpan(veldata25) }, - { 50, absl::MakeConstSpan(veldata50) }, - { 75, absl::MakeConstSpan(veldata75) }, - { 100, absl::MakeConstSpan(veldata100) }, - }; - - for (const VeltrackData& vt : veltrackdata) { - sfz::Synth synth; - const std::string sfzCode = "sample=*sine amp_veltrack=" + - std::to_string(vt.veltrack); - synth.loadSfzString(fs::current_path() / "tests/TestFiles/veltrack.sfz", sfzCode); - - REQUIRE(synth.getNumRegions() == 1); - const sfz::Region* r = synth.getRegionView(0); - - for (const VelocityData& vd : vt.veldata) { - float dBGain = 20.0f * std::log10(r->velocityCurve(vd.velocity)); - REQUIRE(dBGain == Approx(vd.dBGain).margin(0.1)); - } - } -} - TEST_CASE("[Synth] Region by identifier") { sfz::Synth synth; @@ -1595,6 +1503,29 @@ TEST_CASE("[Synth] Off by standard") REQUIRE( playingVoices.front()->getRegion()->keyRange.containsWithEnd(60) ); } +TEST_CASE("[Synth] Off by negative groups") +{ + sfz::Synth synth; + sfz::AudioBuffer buffer { 2, static_cast(synth.getSamplesPerBlock()) }; + + synth.loadSfzString(fs::current_path(), R"( + group=-1 off_by=-2 sample=*saw transpose=12 key=60 + group=-2 off_by=-1 sample=*triangle key=62 + )"); + synth.noteOn(0, 60, 85); + synth.renderBlock(buffer); + REQUIRE( numPlayingVoices(synth) == 1 ); + synth.noteOn(10, 62, 85); + synth.renderBlock(buffer); + REQUIRE( numPlayingVoices(synth) == 1 ); + auto playingVoices = getPlayingVoices(synth); + REQUIRE( playingVoices.front()->getRegion()->keyRange.containsWithEnd(62) ); + synth.noteOn(10, 60, 85); + synth.renderBlock(buffer); + playingVoices = getPlayingVoices(synth); + REQUIRE( playingVoices.front()->getRegion()->keyRange.containsWithEnd(60) ); +} + TEST_CASE("[Synth] Off by same group") { sfz::Synth synth; @@ -1637,6 +1568,26 @@ TEST_CASE("[Synth] Off by alone and repeated") REQUIRE( numPlayingVoices(synth) == 3 ); } +TEST_CASE("[Synth] Off by with staccato notes") +{ + sfz::Synth synth; + sfz::AudioBuffer buffer { 2, static_cast(synth.getSamplesPerBlock()) }; + + synth.loadSfzString(fs::current_path(), R"( + group=1 off_by=1 sample=*sine ampeg_release=2 + )"); + synth.noteOn(0, 60, 85); + synth.renderBlock(buffer); + REQUIRE( numPlayingVoices(synth) == 1 ); + synth.noteOff(0, 60, 85); + synth.renderBlock(buffer); + REQUIRE( numPlayingVoices(synth) == 0 ); + REQUIRE( numActiveVoices(synth) == 1 ); + synth.noteOn(0, 62, 85); + synth.renderBlock(buffer); + REQUIRE( numActiveVoices(synth) == 1 ); +} + TEST_CASE("[Synth] Off by same note and group") { @@ -1693,6 +1644,23 @@ TEST_CASE("[Synth] Off by a CC event") REQUIRE( playingSamples(synth) == std::vector { "*sine" }); } +TEST_CASE("[Synth] CC triggered off by a CC event") +{ + sfz::Synth synth; + sfz::AudioBuffer buffer { 2, static_cast(synth.getSamplesPerBlock()) }; + + synth.loadSfzString(fs::current_path(), R"( + group=1 off_by=2 sample=*saw hikey=-1 on_locc64=126 on_hicc64=127 + group=2 sample=*triangle hikey=-1 on_locc64=0 on_hicc64=1 + )"); + synth.cc(0, 64, 127); + synth.renderBlock(buffer); + REQUIRE( playingSamples(synth) == std::vector { "*saw" }); + synth.cc(0, 64, 0); + synth.renderBlock(buffer); + REQUIRE( playingSamples(synth) == std::vector { "*triangle" }); +} + TEST_CASE("[Synth] Off by a note-off event") { sfz::Synth synth; @@ -1828,3 +1796,171 @@ TEST_CASE("[Synth] Short empty files are turned into *silence") }; REQUIRE(messageList == expected); } + +TEST_CASE("[Synth] Sustain cancels release (Flex EG)") +{ + sfz::Synth synth; + sfz::AudioBuffer buffer { 2, static_cast(synth.getSamplesPerBlock()) }; + synth.loadSfzString(fs::current_path() / "tests/TestFiles/polyphony.sfz", R"( + hint_sustain_cancels_release=1 + sample=*sine + eg01_ampeg=1 eg01_sustain=2 + eg01_time1=0 eg01_level1=0.00 + eg01_time2=0.06 eg01_level2=0.92 + eg01_time3=1.00 eg01_level3=0.00 eg01_shape3=-3 + )"); + synth.noteOn(0, 60, 63 ); + synth.renderBlock(buffer); + REQUIRE( playingSamples(synth) == std::vector { "*sine" } ); + synth.noteOff(0, 60, 0 ); + synth.renderBlock(buffer); + REQUIRE( playingSamples(synth) == std::vector { } ); + synth.renderBlock(buffer); + synth.renderBlock(buffer); + synth.cc(0, 64, 127); + synth.renderBlock(buffer); + REQUIRE( playingSamples(synth) == std::vector { "*sine" } ); +} + +TEST_CASE("[Synth] Sustain cancels release (Flex EG) is off by default") +{ + sfz::Synth synth; + sfz::AudioBuffer buffer { 2, static_cast(synth.getSamplesPerBlock()) }; + synth.loadSfzString(fs::current_path() / "tests/TestFiles/polyphony.sfz", R"( + sample=*sine + eg01_ampeg=1 eg01_sustain=2 + eg01_time1=0 eg01_level1=0.00 + eg01_time2=0.06 eg01_level2=0.92 + eg01_time3=1.00 eg01_level3=0.00 eg01_shape3=-3 + )"); + synth.noteOn(0, 60, 63 ); + synth.renderBlock(buffer); + REQUIRE( playingSamples(synth) == std::vector { "*sine" } ); + synth.noteOff(0, 60, 0 ); + synth.renderBlock(buffer); + REQUIRE( playingSamples(synth) == std::vector { } ); + synth.renderBlock(buffer); + synth.renderBlock(buffer); + synth.cc(0, 64, 127); + synth.renderBlock(buffer); + REQUIRE( playingSamples(synth) == std::vector { } ); +} + +TEST_CASE("[Synth] Sustain cancels release") +{ + sfz::Synth synth; + sfz::AudioBuffer buffer { 2, static_cast(synth.getSamplesPerBlock()) }; + synth.loadSfzString(fs::current_path() / "tests/TestFiles/polyphony.sfz", R"( + hint_sustain_cancels_release=1 + sample=*sine ampeg_release=10 + )"); + synth.noteOn(0, 60, 63 ); + synth.renderBlock(buffer); + REQUIRE( playingSamples(synth) == std::vector { "*sine" } ); + synth.noteOff(0, 60, 0 ); + synth.renderBlock(buffer); + REQUIRE( playingSamples(synth) == std::vector { } ); + synth.renderBlock(buffer); + synth.renderBlock(buffer); + synth.cc(0, 64, 127); + synth.renderBlock(buffer); + REQUIRE( playingSamples(synth) == std::vector { "*sine" } ); +} + +TEST_CASE("[Synth] Sustain cancels release is off by default") +{ + sfz::Synth synth; + sfz::AudioBuffer buffer { 2, static_cast(synth.getSamplesPerBlock()) }; + synth.loadSfzString(fs::current_path() / "tests/TestFiles/polyphony.sfz", R"( + sample=*sine ampeg_release=10 + )"); + synth.noteOn(0, 60, 63 ); + synth.renderBlock(buffer); + REQUIRE( playingSamples(synth) == std::vector { "*sine" } ); + synth.noteOff(0, 60, 0 ); + synth.renderBlock(buffer); + REQUIRE( playingSamples(synth) == std::vector { } ); + synth.renderBlock(buffer); + synth.renderBlock(buffer); + synth.cc(0, 64, 127); + synth.renderBlock(buffer); + REQUIRE( playingSamples(synth) == std::vector { } ); +} + +TEST_CASE("[Synth] Resets all controllers to default values") +{ + sfz::Synth synth; + std::vector messageList; + sfz::Client client(&messageList); + client.setReceiveCallback(&simpleMessageReceiver); + sfz::AudioBuffer buffer { 2, static_cast(synth.getSamplesPerBlock()) }; + synth.loadSfzString(fs::current_path() / "tests/TestFiles/default_cc.sfz", R"( + set_cc56=64 + sample=*sine + )"); + REQUIRE( synth.getHdcc(56) == 64_norm ); + REQUIRE( synth.getHdcc(78) == 0.0f ); + synth.cc(0, 56, 10); + synth.cc(0, 78, 100); + synth.renderBlock(buffer); + REQUIRE( synth.getHdcc(56) == 10_norm ); + REQUIRE( synth.getHdcc(78) == 100_norm ); + synth.cc(0, 121, 127); + synth.cc(0, 121, 0); + synth.renderBlock(buffer); + REQUIRE( synth.getHdcc(56) == 64_norm ); + REQUIRE( synth.getHdcc(78) == 0.0f ); +} + +TEST_CASE("[Synth] Sequences also work on cc triggers") +{ + sfz::Synth synth; + std::vector messageList; + sfz::Client client(&messageList); + client.setReceiveCallback(&simpleMessageReceiver); + sfz::AudioBuffer buffer { 2, static_cast(synth.getSamplesPerBlock()) }; + synth.loadSfzString(fs::current_path() / "tests/TestFiles/sequence_cc_triggers.sfz", R"( + seq_length=3 + sample=*sine hikey=-1 start_locc61=0 start_hicc61=64 seq_position=1 + sample=*saw hikey=-1 start_locc61=0 start_hicc61=64 seq_position=2 + )"); + synth.cc(0, 61, 10); + synth.renderBlock(buffer); + REQUIRE( playingSamples(synth) == std::vector { "*sine" } ); + synth.cc(0, 61, 20); + synth.renderBlock(buffer); + REQUIRE( playingSamples(synth) == std::vector { "*sine", "*saw" } ); + synth.cc(0, 61, 20); + synth.renderBlock(buffer); + REQUIRE( playingSamples(synth) == std::vector { "*sine", "*saw" } ); + synth.cc(0, 61, 20); + synth.renderBlock(buffer); + REQUIRE( playingSamples(synth) == std::vector { "*sine", "*saw", "*sine" } ); +} + +TEST_CASE("[Synth] Loading resets note and octave offsets") +{ + sfz::Synth synth; + std::vector messageList; + sfz::Client client(&messageList); + client.setReceiveCallback(&simpleMessageReceiver); + sfz::AudioBuffer buffer { 2, static_cast(synth.getSamplesPerBlock()) }; + synth.loadSfzString(fs::current_path() / "tests/TestFiles/octave_offset.sfz", R"( + note_offset=1 octave_offset=-1 + sample=*sine + )"); + synth.dispatchMessage(client, 0, "/note_offset", "", nullptr); + synth.dispatchMessage(client, 0, "/octave_offset", "", nullptr); + synth.loadSfzString(fs::current_path() / "tests/TestFiles/octave_offset.sfz", R"( + sample=*sine + )"); + synth.dispatchMessage(client, 0, "/note_offset", "", nullptr); + synth.dispatchMessage(client, 0, "/octave_offset", "", nullptr); + std::vector expected { + "/note_offset,i : { 1 }", + "/octave_offset,i : { -1 }", + "/note_offset,i : { 0 }", + "/octave_offset,i : { 0 }", + }; + REQUIRE(messageList == expected); +} diff --git a/tests/TestFiles/channels_multi.sfz b/tests/TestFiles/channels_multi.sfz index ee6d6acf6..9921b152c 100644 --- a/tests/TestFiles/channels_multi.sfz +++ b/tests/TestFiles/channels_multi.sfz @@ -4,7 +4,9 @@ sample=ramp_wave.wav oscillator=on oscillator_multi=3 sample=ramp_wave.wav oscillator=off sample=ramp_wave.wav oscillator=off oscillator_multi=3 - sample=ramp_wave.wav + sample=wavetables/surge.wav + oscillator=auto sample=wavetables/surge.wav + sample=short_non_wavetable.wav sample=snare.wav sample=*sine oscillator_multi=1 sample=*sine oscillator_multi=2 diff --git a/tests/TestFiles/short_non_wavetable.wav b/tests/TestFiles/short_non_wavetable.wav new file mode 100644 index 000000000..e71973d35 Binary files /dev/null and b/tests/TestFiles/short_non_wavetable.wav differ diff --git a/tests/TestHelpers.cpp b/tests/TestHelpers.cpp index 3e1f77157..4a81175e8 100644 --- a/tests/TestHelpers.cpp +++ b/tests/TestHelpers.cpp @@ -6,6 +6,7 @@ #include "TestHelpers.h" #include "sfizz/modulations/ModId.h" +#include size_t RegionCCView::size() const { @@ -68,7 +69,7 @@ const std::vector getPlayingVoices(const sfz::Synth& synth) std::vector playingVoices; for (int i = 0; i < synth.getNumVoices(); ++i) { const auto* voice = synth.getVoiceView(i); - if (!voice->releasedOrFree()) + if (!voice->released()) playingVoices.push_back(voice); } return playingVoices; @@ -77,7 +78,14 @@ const std::vector getPlayingVoices(const sfz::Synth& synth) unsigned numPlayingVoices(const sfz::Synth& synth) { return absl::c_count_if(getActiveVoices(synth), [](const sfz::Voice* v) { - return !v->releasedOrFree(); + return !v->released(); + }); +} + +unsigned numActiveVoices(const sfz::Synth& synth) +{ + return absl::c_count_if(getActiveVoices(synth), [](const sfz::Voice* v) { + return !v->offedOrFree(); }); } @@ -86,7 +94,7 @@ const std::vector playingSamples(const sfz::Synth& synth) std::vector samples; for (int i = 0; i < synth.getNumVoices(); ++i) { const auto* voice = synth.getVoiceView(i); - if (!voice->releasedOrFree()) { + if (!voice->released()) { if (auto region = voice->getRegion()) samples.push_back(region->sampleId->filename()); } @@ -99,7 +107,7 @@ const std::vector playingVelocities(const sfz::Synth& synth) std::vector velocities; for (int i = 0; i < synth.getNumVoices(); ++i) { const auto* voice = synth.getVoiceView(i); - if (!voice->releasedOrFree()) + if (!voice->released()) velocities.push_back(voice->getTriggerEvent().value); } return velocities; diff --git a/tests/TestHelpers.h b/tests/TestHelpers.h index 454f221da..2145555eb 100644 --- a/tests/TestHelpers.h +++ b/tests/TestHelpers.h @@ -78,6 +78,14 @@ const std::vector getPlayingVoices(const sfz::Synth& synth); */ unsigned numPlayingVoices(const sfz::Synth& synth); +/** + * @brief Count the number of active (not free or offed) voices from the synth + * + * @param synth + * @return unsigned + */ +unsigned numActiveVoices(const sfz::Synth& synth); + /** * @brief Get the playing samples *