From c2783f1853067af933cdc8cc6e523d12a296b59d Mon Sep 17 00:00:00 2001 From: redtide Date: Sat, 26 Aug 2023 15:29:49 +0200 Subject: [PATCH 01/65] Typo in CHANGELOG --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0aa5b19cc..63af4edd0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -34,7 +34,7 @@ This project tries to adhere to [Semantic Versioning][2]. - Fixed build on Windows - Fixed modulation matrix when CC modulations are per voice (#1173 #1179 by @PythonBlue) - Fixed and reenabled tests -- modifications to support univeral x86_64/ARM builds on macOS (#1183 by @essej) +- Modifications to support universal x86_64/ARM builds on macOS (#1183 by @essej) ### Removed From b27cc4e5360aeb79f63ab0d5b217f4772f6d1afd Mon Sep 17 00:00:00 2001 From: redtide Date: Sat, 26 Aug 2023 16:06:10 +0200 Subject: [PATCH 02/65] CI: Fixed Windows release zip package --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c3b66414d..8f5c0ebed 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -294,7 +294,7 @@ jobs: --verbose ` - name: Create zip package if: ${{ github.ref_type == 'tag' }} - run: 7z a "${{ env.install_name }}.zip" ".\build\src\${{ env.build_type }}\sfizz.*" + run: 7z a "${{ env.install_name }}.zip" ".\build\library\*" - name: Upload if: ${{ github.ref_type == 'tag' }} uses: actions/upload-artifact@v3 From d2a4ded7386ccf30aa37f0c1a27199c897e222ac Mon Sep 17 00:00:00 2001 From: redtide Date: Tue, 29 Aug 2023 20:56:21 +0200 Subject: [PATCH 03/65] Updated wavpack submodule this fixes an upstream CMake minimum version compatibility issue --- external/st_audiofile/thirdparty/wavpack | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/external/st_audiofile/thirdparty/wavpack b/external/st_audiofile/thirdparty/wavpack index 36b08dbcb..78e75a852 160000 --- a/external/st_audiofile/thirdparty/wavpack +++ b/external/st_audiofile/thirdparty/wavpack @@ -1 +1 @@ -Subproject commit 36b08dbcb1de136e9ae477e9f1e2b57c958fff18 +Subproject commit 78e75a8527fffe2f87cdd56d5989ff01c0723ef7 From afc6dbaa3bc6d75678dd0d096f8207828282e939 Mon Sep 17 00:00:00 2001 From: redtide Date: Tue, 29 Aug 2023 22:31:01 +0200 Subject: [PATCH 04/65] Explicitly disabled unwanted WavPack options --- external/st_audiofile/CMakeLists.txt | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/external/st_audiofile/CMakeLists.txt b/external/st_audiofile/CMakeLists.txt index 4fcceabdc..fa327ef87 100644 --- a/external/st_audiofile/CMakeLists.txt +++ b/external/st_audiofile/CMakeLists.txt @@ -22,6 +22,14 @@ target_include_directories(st_audiofile_formats add_subdirectory("thirdparty/libaiff" EXCLUDE_FROM_ALL) target_link_libraries(st_audiofile_formats PUBLIC aiff::aiff) +set(WAVPACK_BUILD_COOLEDIT_PLUGIN OFF) +set(WAVPACK_BUILD_PROGRAMS OFF) +set(WAVPACK_BUILD_WINAMP_PLUGIN OFF) +set(WAVPACK_ENABLE_LIBCRYPTO OFF) +set(WAVPACK_INSTALL_CMAKE_MODULE OFF) +set(WAVPACK_INSTALL_DOCS OFF) +set(WAVPACK_INSTALL_PKGCONFIG_MODULE OFF) + add_subdirectory("thirdparty/wavpack" EXCLUDE_FROM_ALL) target_link_libraries(st_audiofile_formats PUBLIC wavpack) From 3e2952b69b9f5f9e4332b74928948ccb207ce8de Mon Sep 17 00:00:00 2001 From: Paul Ferrand Date: Fri, 29 Sep 2023 10:02:06 +0200 Subject: [PATCH 05/65] Fix some Apple build inconsistencies --- cmake/SfizzConfig.cmake | 2 +- external/st_audiofile/CMakeLists.txt | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/cmake/SfizzConfig.cmake b/cmake/SfizzConfig.cmake index be2eb87c2..ff0ab5ebb 100644 --- a/cmake/SfizzConfig.cmake +++ b/cmake/SfizzConfig.cmake @@ -87,7 +87,7 @@ endif() if(NOT PROJECT_SYSTEM_PROCESSOR) if(MSVC) set(PROJECT_SYSTEM_PROCESSOR "${MSVC_CXX_ARCHITECTURE_ID}" CACHE STRING "" FORCE) - elseif(CMAKE_OSX_ARCHITECTURES) + elseif(APPLE AND CMAKE_OSX_ARCHITECTURES) set(PROJECT_SYSTEM_PROCESSOR "${CMAKE_OSX_ARCHITECTURES}" CACHE STRING "" FORCE) else() set(PROJECT_SYSTEM_PROCESSOR "${CMAKE_SYSTEM_PROCESSOR}" CACHE STRING "" FORCE) diff --git a/external/st_audiofile/CMakeLists.txt b/external/st_audiofile/CMakeLists.txt index fa327ef87..8ce6cfbdc 100644 --- a/external/st_audiofile/CMakeLists.txt +++ b/external/st_audiofile/CMakeLists.txt @@ -29,6 +29,9 @@ set(WAVPACK_ENABLE_LIBCRYPTO OFF) set(WAVPACK_INSTALL_CMAKE_MODULE OFF) set(WAVPACK_INSTALL_DOCS OFF) set(WAVPACK_INSTALL_PKGCONFIG_MODULE OFF) +if(PROJECT_SYSTEM_PROCESSOR MATCHES "(arm.*)") + SET(WAVPACK_ENABLE_ASM OFF) +endif() add_subdirectory("thirdparty/wavpack" EXCLUDE_FROM_ALL) target_link_libraries(st_audiofile_formats PUBLIC wavpack) From 1a7785f34d3a98499938db078bb06f8ed4c45dae Mon Sep 17 00:00:00 2001 From: Paul Ferrand Date: Fri, 29 Sep 2023 14:19:40 +0200 Subject: [PATCH 06/65] Further wavpack tweaks --- external/st_audiofile/CMakeLists.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/external/st_audiofile/CMakeLists.txt b/external/st_audiofile/CMakeLists.txt index 8ce6cfbdc..e3349676e 100644 --- a/external/st_audiofile/CMakeLists.txt +++ b/external/st_audiofile/CMakeLists.txt @@ -29,7 +29,8 @@ set(WAVPACK_ENABLE_LIBCRYPTO OFF) set(WAVPACK_INSTALL_CMAKE_MODULE OFF) set(WAVPACK_INSTALL_DOCS OFF) set(WAVPACK_INSTALL_PKGCONFIG_MODULE OFF) -if(PROJECT_SYSTEM_PROCESSOR MATCHES "(arm.*)") +# FIXME: remove when WavPack updates their build script +if(APPLE AND PROJECT_SYSTEM_PROCESSOR MATCHES "(arm.*)") SET(WAVPACK_ENABLE_ASM OFF) endif() From 7cf5dd22a5c05cf41694bb537edd1d0e52f67cf3 Mon Sep 17 00:00:00 2001 From: redtide Date: Sat, 21 Oct 2023 11:56:15 +0200 Subject: [PATCH 07/65] Keep track of wavpack arm configuration issue --- external/st_audiofile/CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/external/st_audiofile/CMakeLists.txt b/external/st_audiofile/CMakeLists.txt index e3349676e..0527eedd3 100644 --- a/external/st_audiofile/CMakeLists.txt +++ b/external/st_audiofile/CMakeLists.txt @@ -30,6 +30,7 @@ set(WAVPACK_INSTALL_CMAKE_MODULE OFF) set(WAVPACK_INSTALL_DOCS OFF) set(WAVPACK_INSTALL_PKGCONFIG_MODULE OFF) # FIXME: remove when WavPack updates their build script +# see https://github.com/dbry/WavPack/issues/93 if(APPLE AND PROJECT_SYSTEM_PROCESSOR MATCHES "(arm.*)") SET(WAVPACK_ENABLE_ASM OFF) endif() From 276ab2ce68d0c09716578cd576d9f18b8ac85593 Mon Sep 17 00:00:00 2001 From: redtide Date: Tue, 14 Nov 2023 11:25:45 +0100 Subject: [PATCH 08/65] Updated Doxyfile.in template --- scripts/doxygen/Doxyfile.in | 641 +++++++++++++++++++++++------------- 1 file changed, 420 insertions(+), 221 deletions(-) diff --git a/scripts/doxygen/Doxyfile.in b/scripts/doxygen/Doxyfile.in index 584d6f692..36011e3fe 100644 --- a/scripts/doxygen/Doxyfile.in +++ b/scripts/doxygen/Doxyfile.in @@ -12,6 +12,16 @@ # For lists, items can also be appended using: # TAG += value [value, ...] # Values that contain spaces should be placed between quotes (\" \"). +# +# Note: +# +# Use doxygen to compare the used configuration file with the template +# configuration file: +# doxygen -x [configFile] +# Use doxygen to compare the used configuration file with the template +# configuration file without replacing the environment variables or CMake type +# replacement variables: +# doxygen -x_noenv [configFile] #--------------------------------------------------------------------------- # Project related configuration options @@ -60,16 +70,28 @@ PROJECT_LOGO = OUTPUT_DIRECTORY = . -# If the CREATE_SUBDIRS tag is set to YES then doxygen will create 4096 sub- -# directories (in 2 levels) under the output directory of each output format and -# will distribute the generated files over these directories. Enabling this +# If the CREATE_SUBDIRS tag is set to YES then doxygen will create up to 4096 +# sub-directories (in 2 levels) under the output directory of each output format +# and will distribute the generated files over these directories. Enabling this # option can be useful when feeding doxygen a huge amount of source files, where # putting all generated files in the same directory would otherwise causes -# performance problems for the file system. +# performance problems for the file system. Adapt CREATE_SUBDIRS_LEVEL to +# control the number of sub-directories. # The default value is: NO. CREATE_SUBDIRS = NO +# Controls the number of sub-directories that will be created when +# CREATE_SUBDIRS tag is set to YES. Level 0 represents 16 directories, and every +# level increment doubles the number of directories, resulting in 4096 +# directories at level 8 which is the default and also the maximum value. The +# sub-directories are organized in 2 levels, the first level always has a fixed +# number of 16 directories. +# Minimum value: 0, maximum value: 8, default value: 8. +# This tag requires that the tag CREATE_SUBDIRS is set to YES. + +CREATE_SUBDIRS_LEVEL = 8 + # If the ALLOW_UNICODE_NAMES tag is set to YES, doxygen will allow non-ASCII # characters to appear in the names of generated files. If set to NO, non-ASCII # characters will be escaped, for example _xE3_x81_x84 will be used for Unicode @@ -81,26 +103,18 @@ ALLOW_UNICODE_NAMES = NO # The OUTPUT_LANGUAGE tag is used to specify the language in which all # documentation generated by doxygen is written. Doxygen will use this # information to generate all constant output in the proper language. -# Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Catalan, Chinese, -# Chinese-Traditional, Croatian, Czech, Danish, Dutch, English (United States), -# Esperanto, Farsi (Persian), Finnish, French, German, Greek, Hungarian, -# Indonesian, Italian, Japanese, Japanese-en (Japanese with English messages), -# Korean, Korean-en (Korean with English messages), Latvian, Lithuanian, -# Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, Romanian, Russian, -# Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, Swedish, Turkish, -# Ukrainian and Vietnamese. +# Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Bulgarian, +# Catalan, Chinese, Chinese-Traditional, Croatian, Czech, Danish, Dutch, English +# (United States), Esperanto, Farsi (Persian), Finnish, French, German, Greek, +# Hindi, Hungarian, Indonesian, Italian, Japanese, Japanese-en (Japanese with +# English messages), Korean, Korean-en (Korean with English messages), Latvian, +# Lithuanian, Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, +# Romanian, Russian, Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, +# Swedish, Turkish, Ukrainian and Vietnamese. # The default value is: English. OUTPUT_LANGUAGE = English -# The OUTPUT_TEXT_DIRECTION tag is used to specify the direction in which all -# documentation generated by doxygen is written. Doxygen will use this -# information to generate all generated output in the proper direction. -# Possible values are: None, LTR, RTL and Context. -# The default value is: None. - -OUTPUT_TEXT_DIRECTION = None - # If the BRIEF_MEMBER_DESC tag is set to YES, doxygen will include brief member # descriptions after the members that are listed in the file and class # documentation (similar to Javadoc). Set to NO to disable this. @@ -248,16 +262,16 @@ TAB_SIZE = 4 # the documentation. An alias has the form: # name=value # For example adding -# "sideeffect=@par Side Effects:\n" +# "sideeffect=@par Side Effects:^^" # will allow you to put the command \sideeffect (or @sideeffect) in the # documentation, which will result in a user-defined paragraph with heading -# "Side Effects:". You can put \n's in the value part of an alias to insert -# newlines (in the resulting output). You can put ^^ in the value part of an -# alias to insert a newline as if a physical newline was in the original file. -# When you need a literal { or } or , in the value part of an alias you have to -# escape them by means of a backslash (\), this can lead to conflicts with the -# commands \{ and \} for these it is advised to use the version @{ and @} or use -# a double escape (\\{ and \\}) +# "Side Effects:". Note that you cannot put \n's in the value part of an alias +# to insert newlines (in the resulting output). You can put ^^ in the value part +# of an alias to insert a newline as if a physical newline was in the original +# file. When you need a literal { or } or , in the value part of an alias you +# have to escape them by means of a backslash (\), this can lead to conflicts +# with the commands \{ and \} for these it is advised to use the version @{ and +# @} or use a double escape (\\{ and \\}) ALIASES = "true=\true\" \ "false=\false\" \ @@ -304,8 +318,8 @@ OPTIMIZE_OUTPUT_SLICE = NO # extension. Doxygen has a built-in mapping, but you can override or extend it # using this tag. The format is ext=language, where ext is a file extension, and # language is one of the parsers supported by doxygen: IDL, Java, JavaScript, -# Csharp (C#), C, C++, D, PHP, md (Markdown), Objective-C, Python, Slice, VHDL, -# Fortran (fixed format Fortran: FortranFixed, free formatted Fortran: +# Csharp (C#), C, C++, Lex, D, PHP, md (Markdown), Objective-C, Python, Slice, +# VHDL, Fortran (fixed format Fortran: FortranFixed, free formatted Fortran: # FortranFree, unknown formatted Fortran: Fortran. In the later case the parser # tries to guess whether the code is fixed or free formatted code, this is the # default for Fortran type files). For instance to make doxygen treat .inc files @@ -341,6 +355,17 @@ MARKDOWN_SUPPORT = YES TOC_INCLUDE_HEADINGS = 0 +# The MARKDOWN_ID_STYLE tag can be used to specify the algorithm used to +# generate identifiers for the Markdown headings. Note: Every identifier is +# unique. +# Possible values are: DOXYGEN use a fixed 'autotoc_md' string followed by a +# sequence number starting at 0 and GITHUB use the lower case version of title +# with any whitespace replaced by '-' and punctuation characters removed. +# The default value is: DOXYGEN. +# This tag requires that the tag MARKDOWN_SUPPORT is set to YES. + +MARKDOWN_ID_STYLE = DOXYGEN + # When enabled doxygen tries to link words that correspond to documented # classes, or namespaces to their corresponding documentation. Such a link can # be prevented in individual cases by putting a % sign in front of the word or @@ -452,19 +477,27 @@ TYPEDEF_HIDES_STRUCT = NO LOOKUP_CACHE_SIZE = 0 -# The NUM_PROC_THREADS specifies the number threads doxygen is allowed to use +# The NUM_PROC_THREADS specifies the number of threads doxygen is allowed to use # during processing. When set to 0 doxygen will based this on the number of # cores available in the system. You can set it explicitly to a value larger # than 0 to get more control over the balance between CPU load and processing # speed. At this moment only the input processing can be done using multiple # threads. Since this is still an experimental feature the default is set to 1, -# which efficively disables parallel processing. Please report any issues you +# which effectively disables parallel processing. Please report any issues you # encounter. Generating dot graphs in parallel is controlled by the # DOT_NUM_THREADS setting. # Minimum value: 0, maximum value: 32, default value: 1. NUM_PROC_THREADS = 1 +# If the TIMESTAMP tag is set different from NO then each generated page will +# contain the date or date and time when the page was generated. Setting this to +# NO can help when comparing the output of multiple runs. +# Possible values are: YES, NO, DATETIME and DATE. +# The default value is: NO. + +TIMESTAMP = NO + #--------------------------------------------------------------------------- # Build related configuration options #--------------------------------------------------------------------------- @@ -546,7 +579,8 @@ HIDE_UNDOC_MEMBERS = NO # If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all # undocumented classes that are normally visible in the class hierarchy. If set # to NO, these classes will be included in the various overviews. This option -# has no effect if EXTRACT_ALL is enabled. +# will also hide undocumented C++ concepts if enabled. This option has no effect +# if EXTRACT_ALL is enabled. # The default value is: NO. HIDE_UNDOC_CLASSES = NO @@ -577,14 +611,15 @@ INTERNAL_DOCS = NO # filesystem is case sensitive (i.e. it supports files in the same directory # whose names only differ in casing), the option must be set to YES to properly # deal with such files in case they appear in the input. For filesystems that -# are not case sensitive the option should be be set to NO to properly deal with +# are not case sensitive the option should be set to NO to properly deal with # output files written for symbols that only differ in casing, such as for two # classes, one named CLASS and the other named Class, and to also support # references to files without having to specify the exact matching casing. On # Windows (including Cygwin) and MacOS, users should typically set this option # to NO, whereas on Linux or other Unix flavors it should typically be set to # YES. -# The default value is: system dependent. +# Possible values are: SYSTEM, NO and YES. +# The default value is: SYSTEM. CASE_SENSE_NAMES = NO @@ -602,6 +637,12 @@ HIDE_SCOPE_NAMES = NO HIDE_COMPOUND_REFERENCE= NO +# If the SHOW_HEADERFILE tag is set to YES then the documentation for a class +# will show which file needs to be included to use the class. +# The default value is: YES. + +SHOW_HEADERFILE = YES + # If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of # the files that are included by a file in the documentation of that file. # The default value is: YES. @@ -759,7 +800,8 @@ FILE_VERSION_FILTER = # output files in an output format independent way. To create the layout file # that represents doxygen's defaults, run doxygen with the -l option. You can # optionally specify a file name after the option, if omitted DoxygenLayout.xml -# will be used as the name of the layout file. +# will be used as the name of the layout file. See also section "Changing the +# layout of pages" for information. # # Note that if you run doxygen from a directory containing a file called # DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE @@ -805,27 +847,50 @@ WARNINGS = YES WARN_IF_UNDOCUMENTED = YES # If the WARN_IF_DOC_ERROR tag is set to YES, doxygen will generate warnings for -# potential errors in the documentation, such as not documenting some parameters -# in a documented function, or documenting parameters that don't exist or using -# markup commands wrongly. +# potential errors in the documentation, such as documenting some parameters in +# a documented function twice, or documenting parameters that don't exist or +# using markup commands wrongly. # The default value is: YES. WARN_IF_DOC_ERROR = YES +# If WARN_IF_INCOMPLETE_DOC is set to YES, doxygen will warn about incomplete +# function parameter documentation. If set to NO, doxygen will accept that some +# parameters have no documentation without warning. +# The default value is: YES. + +WARN_IF_INCOMPLETE_DOC = YES + # This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that # are documented, but have no documentation for their parameters or return -# value. If set to NO, doxygen will only warn about wrong or incomplete -# parameter documentation, but not about the absence of documentation. If -# EXTRACT_ALL is set to YES then this flag will automatically be disabled. +# value. If set to NO, doxygen will only warn about wrong parameter +# documentation, but not about the absence of documentation. If EXTRACT_ALL is +# set to YES then this flag will automatically be disabled. See also +# WARN_IF_INCOMPLETE_DOC # The default value is: NO. WARN_NO_PARAMDOC = NO +# If WARN_IF_UNDOC_ENUM_VAL option is set to YES, doxygen will warn about +# undocumented enumeration values. If set to NO, doxygen will accept +# undocumented enumeration values. If EXTRACT_ALL is set to YES then this flag +# will automatically be disabled. +# The default value is: NO. + +WARN_IF_UNDOC_ENUM_VAL = NO + # If the WARN_AS_ERROR tag is set to YES then doxygen will immediately stop when # a warning is encountered. If the WARN_AS_ERROR tag is set to FAIL_ON_WARNINGS # then doxygen will continue running as if WARN_AS_ERROR tag is set to NO, but # at the end of the doxygen process doxygen will return with a non-zero status. -# Possible values are: NO, YES and FAIL_ON_WARNINGS. +# If the WARN_AS_ERROR tag is set to FAIL_ON_WARNINGS_PRINT then doxygen behaves +# like FAIL_ON_WARNINGS but in case no WARN_LOGFILE is defined doxygen will not +# write the warning messages in between other messages but write them at the end +# of a run, in case a WARN_LOGFILE is defined the warning messages will be +# besides being in the defined file also be shown at the end of a run, unless +# the WARN_LOGFILE is defined as - i.e. standard output (stdout) in that case +# the behavior will remain as with the setting FAIL_ON_WARNINGS. +# Possible values are: NO, YES, FAIL_ON_WARNINGS and FAIL_ON_WARNINGS_PRINT. # The default value is: NO. WARN_AS_ERROR = NO @@ -836,13 +901,27 @@ WARN_AS_ERROR = NO # and the warning text. Optionally the format may contain $version, which will # be replaced by the version of the file (if it could be obtained via # FILE_VERSION_FILTER) +# See also: WARN_LINE_FORMAT # The default value is: $file:$line: $text. WARN_FORMAT = "$file:$line: $text" +# In the $text part of the WARN_FORMAT command it is possible that a reference +# to a more specific place is given. To make it easier to jump to this place +# (outside of doxygen) the user can define a custom "cut" / "paste" string. +# Example: +# WARN_LINE_FORMAT = "'vi $file +$line'" +# See also: WARN_FORMAT +# The default value is: at line $line of file $file. + +WARN_LINE_FORMAT = "at line $line of file $file" + # The WARN_LOGFILE tag can be used to specify a file to which warning and error # messages should be written. If left blank the output is written to standard -# error (stderr). +# error (stderr). In case the file specified cannot be opened for writing the +# warning and error messages are written to standard error. When as file - is +# specified the warning and error messages are written to standard output +# (stdout). WARN_LOGFILE = @@ -863,10 +942,21 @@ INPUT = src # libiconv (or the iconv built into libc) for the transcoding. See the libiconv # documentation (see: # https://www.gnu.org/software/libiconv/) for the list of possible encodings. +# See also: INPUT_FILE_ENCODING # The default value is: UTF-8. INPUT_ENCODING = UTF-8 +# This tag can be used to specify the character encoding of the source files +# that doxygen parses The INPUT_FILE_ENCODING tag can be used to specify +# character encoding on a per file pattern basis. Doxygen will compare the file +# name with each pattern and apply the encoding instead of the default +# INPUT_ENCODING) if there is a match. The character encodings are a list of the +# form: pattern=encoding (like *.php=ISO-8859-1). See cfg_input_encoding +# "INPUT_ENCODING" for further information on supported encodings. + +INPUT_FILE_ENCODING = + # If the value of the INPUT tag contains directories, you can use the # FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and # *.h) to filter out the source-files in the directories. @@ -878,12 +968,12 @@ INPUT_ENCODING = UTF-8 # Note the list of default checked file patterns might differ from the list of # default file extension mappings. # -# If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cpp, -# *.c++, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h, -# *.hh, *.hxx, *.hpp, *.h++, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, *.inc, -# *.m, *.markdown, *.md, *.mm, *.dox (to be provided as doxygen C comment), -# *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, *.f18, *.f, *.for, *.vhd, *.vhdl, -# *.ucf, *.qsf and *.ice. +# If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cxxm, +# *.cpp, *.cppm, *.c++, *.c++m, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, *.idl, +# *.ddl, *.odl, *.h, *.hh, *.hxx, *.hpp, *.h++, *.ixx, *.l, *.cs, *.d, *.php, +# *.php4, *.php5, *.phtml, *.inc, *.m, *.markdown, *.md, *.mm, *.dox (to be +# provided as doxygen C comment), *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, +# *.f18, *.f, *.for, *.vhd, *.vhdl, *.ucf, *.qsf and *.ice. FILE_PATTERNS = *.h \ *.md \ @@ -924,10 +1014,7 @@ EXCLUDE_PATTERNS = # (namespaces, classes, functions, etc.) that should be excluded from the # output. The symbol name can be a fully qualified name, a word, or if the # wildcard * is used, a substring. Examples: ANamespace, AClass, -# AClass::ANamespace, ANamespace::*Test -# -# Note that the wildcards are matched against the file with absolute path, so to -# exclude all test directories use the pattern */test/* +# ANamespace::AClass, ANamespace::*Test EXCLUDE_SYMBOLS = SFIZZ_EXPORTED_API @@ -972,6 +1059,11 @@ IMAGE_PATH = # code is scanned, but not when the output code is generated. If lines are added # or removed, the anchors will not be placed correctly. # +# Note that doxygen will use the data processed and written to standard output +# for further processing, therefore nothing else, like debug statements or used +# commands (so in case of a Windows batch file always use @echo OFF), should be +# written to standard output. +# # Note that for custom extensions or not directly supported extensions you also # need to set EXTENSION_MAPPING for the extension otherwise the files are not # properly processed by doxygen. @@ -1013,6 +1105,15 @@ FILTER_SOURCE_PATTERNS = USE_MDFILE_AS_MAINPAGE = index.md +# The Fortran standard specifies that for fixed formatted Fortran code all +# characters from position 72 are to be considered as comment. A common +# extension is to allow longer lines before the automatic comment starts. The +# setting FORTRAN_COMMENT_AFTER will also make it possible that longer lines can +# be processed before the automatic comment starts. +# Minimum value: 7, maximum value: 10000, default value: 72. + +FORTRAN_COMMENT_AFTER = 72 + #--------------------------------------------------------------------------- # Configuration options related to source browsing #--------------------------------------------------------------------------- @@ -1110,10 +1211,11 @@ VERBATIM_HEADERS = NO ALPHABETICAL_INDEX = YES -# In case all classes in a project start with a common prefix, all classes will -# be put under the same header in the alphabetical index. The IGNORE_PREFIX tag -# can be used to specify a prefix (or a list of prefixes) that should be ignored -# while generating the index headers. +# The IGNORE_PREFIX tag can be used to specify a prefix (or a list of prefixes) +# that should be ignored while generating the index headers. The IGNORE_PREFIX +# tag works for classes, function and member names. The entity will be placed in +# the alphabetical list under the first letter of the entity name that remains +# after removing the prefix. # This tag requires that the tag ALPHABETICAL_INDEX is set to YES. IGNORE_PREFIX = @@ -1192,7 +1294,12 @@ HTML_STYLESHEET = # Doxygen will copy the style sheet files to the output directory. # Note: The order of the extra style sheet files is of importance (e.g. the last # style sheet in the list overrules the setting of the previous ones in the -# list). For an example see the documentation. +# list). +# Note: Since the styling of scrollbars can currently not be overruled in +# Webkit/Chromium, the styling will be left out of the default doxygen.css if +# one or more extra stylesheets have been specified. So if scrollbar +# customization is desired it has to be added explicitly. For an example see the +# documentation. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_EXTRA_STYLESHEET = @@ -1207,9 +1314,22 @@ HTML_EXTRA_STYLESHEET = HTML_EXTRA_FILES = +# The HTML_COLORSTYLE tag can be used to specify if the generated HTML output +# should be rendered with a dark or light theme. +# Possible values are: LIGHT always generate light mode output, DARK always +# generate dark mode output, AUTO_LIGHT automatically set the mode according to +# the user preference, use light mode if no preference is set (the default), +# AUTO_DARK automatically set the mode according to the user preference, use +# dark mode if no preference is set and TOGGLE allow to user to switch between +# light and dark mode via a button. +# The default value is: AUTO_LIGHT. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE = AUTO_LIGHT + # The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen # will adjust the colors in the style sheet and background images according to -# this color. Hue is specified as an angle on a colorwheel, see +# this color. Hue is specified as an angle on a color-wheel, see # https://en.wikipedia.org/wiki/Hue for more information. For instance the value # 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300 # purple, and 360 is red again. @@ -1219,7 +1339,7 @@ HTML_EXTRA_FILES = HTML_COLORSTYLE_HUE = 210 # The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors -# in the HTML output. For a value of 0 the output will use grayscales only. A +# in the HTML output. For a value of 0 the output will use gray-scales only. A # value of 255 will produce the most vivid colors. # Minimum value: 0, maximum value: 255, default value: 100. # This tag requires that the tag GENERATE_HTML is set to YES. @@ -1237,15 +1357,6 @@ HTML_COLORSTYLE_SAT = 150 HTML_COLORSTYLE_GAMMA = 90 -# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML -# page will contain the date and time when the page was generated. Setting this -# to YES can help to show when doxygen was last run and thus if the -# documentation is up to date. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_TIMESTAMP = YES - # If the HTML_DYNAMIC_MENUS tag is set to YES then the generated HTML # documentation will contain a main index with vertical navigation menus that # are dynamically created via JavaScript. If disabled, the navigation index will @@ -1265,6 +1376,13 @@ HTML_DYNAMIC_MENUS = YES HTML_DYNAMIC_SECTIONS = YES +# If the HTML_CODE_FOLDING tag is set to YES then classes and functions can be +# dynamically folded and expanded in the generated HTML source code. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_CODE_FOLDING = YES + # With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries # shown in the various tree structured indices initially; the user can expand # and collapse entries dynamically later on. Doxygen will expand the tree to @@ -1301,6 +1419,13 @@ GENERATE_DOCSET = YES DOCSET_FEEDNAME = sfizz +# This tag determines the URL of the docset feed. A documentation feed provides +# an umbrella under which multiple documentation sets from a single provider +# (such as a company or product suite) can be grouped. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_FEEDURL = + # This tag specifies a string that should uniquely identify the documentation # set bundle. This should be a reverse domain-name style string, e.g. # com.mycompany.MyDocSet. Doxygen will append .docset to the name. @@ -1326,8 +1451,12 @@ DOCSET_PUBLISHER_NAME = SFZTools # If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three # additional HTML index files: index.hhp, index.hhc, and index.hhk. The # index.hhp is a project file that can be read by Microsoft's HTML Help Workshop -# (see: -# https://www.microsoft.com/en-us/download/details.aspx?id=21138) on Windows. +# on Windows. In the beginning of 2021 Microsoft took the original page, with +# a.o. the download links, offline the HTML help workshop was already many years +# in maintenance mode). You can download the HTML help workshop from the web +# archives at Installation executable (see: +# http://web.archive.org/web/20160201063255/http://download.microsoft.com/downlo +# ad/0/A/9/0A939EF6-E31C-430F-A3DF-DFAE7960D564/htmlhelp.exe). # # The HTML Help Workshop contains a compiler that can convert all HTML output # generated by doxygen into a single compiled HTML file (.chm). Compiled HTML @@ -1384,6 +1513,16 @@ BINARY_TOC = NO TOC_EXPAND = NO +# The SITEMAP_URL tag is used to specify the full URL of the place where the +# generated documentation will be placed on the server by the user during the +# deployment of the documentation. The generated sitemap is called sitemap.xml +# and placed on the directory specified by HTML_OUTPUT. In case no SITEMAP_URL +# is specified no sitemap is generated. For information about the sitemap +# protocol see https://www.sitemaps.org +# This tag requires that the tag GENERATE_HTML is set to YES. + +SITEMAP_URL = + # If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and # QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that # can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help @@ -1486,16 +1625,28 @@ DISABLE_INDEX = NO # to work a browser that supports JavaScript, DHTML, CSS and frames is required # (i.e. any modern browser). Windows users are probably better off using the # HTML help feature. Via custom style sheets (see HTML_EXTRA_STYLESHEET) one can -# further fine-tune the look of the index. As an example, the default style -# sheet generated by doxygen has an example that shows how to put an image at -# the root of the tree instead of the PROJECT_NAME. Since the tree basically has -# the same information as the tab index, you could consider setting -# DISABLE_INDEX to YES when enabling this option. +# further fine tune the look of the index (see "Fine-tuning the output"). As an +# example, the default style sheet generated by doxygen has an example that +# shows how to put an image at the root of the tree instead of the PROJECT_NAME. +# Since the tree basically has the same information as the tab index, you could +# consider setting DISABLE_INDEX to YES when enabling this option. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. GENERATE_TREEVIEW = NO +# When both GENERATE_TREEVIEW and DISABLE_INDEX are set to YES, then the +# FULL_SIDEBAR option determines if the side bar is limited to only the treeview +# area (value NO) or if it should extend to the full height of the window (value +# YES). Setting this to YES gives a layout similar to +# https://docs.readthedocs.io with more room for contents, but less room for the +# project logo, title, and description. If either GENERATE_TREEVIEW or +# DISABLE_INDEX is set to NO, this option has no effect. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +FULL_SIDEBAR = NO + # The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that # doxygen will group on one line in the generated HTML documentation. # @@ -1520,6 +1671,13 @@ TREEVIEW_WIDTH = 250 EXT_LINKS_IN_WINDOW = NO +# If the OBFUSCATE_EMAILS tag is set to YES, doxygen will obfuscate email +# addresses. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +OBFUSCATE_EMAILS = YES + # If the HTML_FORMULA_FORMAT option is set to svg, doxygen will use the pdf2svg # tool (see https://github.com/dawbarton/pdf2svg) or inkscape (see # https://inkscape.org) to generate formulas as SVG images instead of PNGs for @@ -1540,17 +1698,6 @@ HTML_FORMULA_FORMAT = png FORMULA_FONTSIZE = 10 -# Use the FORMULA_TRANSPARENT tag to determine whether or not the images -# generated for formulas are transparent PNGs. Transparent PNGs are not -# supported properly for IE 6.0, but are supported on all modern browsers. -# -# Note that when changing this option you need to delete any form_*.png files in -# the HTML output directory before the changes have effect. -# The default value is: YES. -# This tag requires that the tag GENERATE_HTML is set to YES. - -FORMULA_TRANSPARENT = YES - # The FORMULA_MACROFILE can contain LaTeX \newcommand and \renewcommand commands # to create new LaTeX commands to be used in formulas as building blocks. See # the section "Including formulas" for details. @@ -1568,11 +1715,29 @@ FORMULA_MACROFILE = USE_MATHJAX = NO +# With MATHJAX_VERSION it is possible to specify the MathJax version to be used. +# Note that the different versions of MathJax have different requirements with +# regards to the different settings, so it is possible that also other MathJax +# settings have to be changed when switching between the different MathJax +# versions. +# Possible values are: MathJax_2 and MathJax_3. +# The default value is: MathJax_2. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_VERSION = MathJax_2 + # When MathJax is enabled you can set the default output format to be used for -# the MathJax output. See the MathJax site (see: -# http://docs.mathjax.org/en/v2.7-latest/output.html) for more details. +# the MathJax output. For more details about the output format see MathJax +# version 2 (see: +# http://docs.mathjax.org/en/v2.7-latest/output.html) and MathJax version 3 +# (see: +# http://docs.mathjax.org/en/latest/web/components/output.html). # Possible values are: HTML-CSS (which is slower, but has the best -# compatibility), NativeMML (i.e. MathML) and SVG. +# compatibility. This is the name for Mathjax version 2, for MathJax version 3 +# this will be translated into chtml), NativeMML (i.e. MathML. Only supported +# for NathJax 2. For MathJax version 3 chtml will be used instead.), chtml (This +# is the name for Mathjax version 3, for MathJax version 2 this will be +# translated into HTML-CSS) and SVG. # The default value is: HTML-CSS. # This tag requires that the tag USE_MATHJAX is set to YES. @@ -1585,15 +1750,21 @@ MATHJAX_FORMAT = HTML-CSS # MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax # Content Delivery Network so you can quickly see the result without installing # MathJax. However, it is strongly recommended to install a local copy of -# MathJax from https://www.mathjax.org before deployment. -# The default value is: https://cdn.jsdelivr.net/npm/mathjax@2. +# MathJax from https://www.mathjax.org before deployment. The default value is: +# - in case of MathJax version 2: https://cdn.jsdelivr.net/npm/mathjax@2 +# - in case of MathJax version 3: https://cdn.jsdelivr.net/npm/mathjax@3 # This tag requires that the tag USE_MATHJAX is set to YES. MATHJAX_RELPATH = https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.2/ # The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax # extension names that should be enabled during MathJax rendering. For example +# for MathJax version 2 (see +# https://docs.mathjax.org/en/v2.7-latest/tex.html#tex-and-latex-extensions): # MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols +# For example for MathJax version 3 (see +# http://docs.mathjax.org/en/latest/input/tex/extensions/index.html): +# MATHJAX_EXTENSIONS = ams # This tag requires that the tag USE_MATHJAX is set to YES. MATHJAX_EXTENSIONS = @@ -1773,29 +1944,31 @@ PAPER_TYPE = a4 EXTRA_PACKAGES = -# The LATEX_HEADER tag can be used to specify a personal LaTeX header for the -# generated LaTeX document. The header should contain everything until the first -# chapter. If it is left blank doxygen will generate a standard header. See -# section "Doxygen usage" for information on how to let doxygen write the -# default header to a separate file. +# The LATEX_HEADER tag can be used to specify a user-defined LaTeX header for +# the generated LaTeX document. The header should contain everything until the +# first chapter. If it is left blank doxygen will generate a standard header. It +# is highly recommended to start with a default header using +# doxygen -w latex new_header.tex new_footer.tex new_stylesheet.sty +# and then modify the file new_header.tex. See also section "Doxygen usage" for +# information on how to generate the default header that doxygen normally uses. # -# Note: Only use a user-defined header if you know what you are doing! The -# following commands have a special meaning inside the header: $title, -# $datetime, $date, $doxygenversion, $projectname, $projectnumber, -# $projectbrief, $projectlogo. Doxygen will replace $title with the empty -# string, for the replacement values of the other commands the user is referred -# to HTML_HEADER. +# Note: Only use a user-defined header if you know what you are doing! +# Note: The header is subject to change so you typically have to regenerate the +# default header when upgrading to a newer version of doxygen. The following +# commands have a special meaning inside the header (and footer): For a +# description of the possible markers and block names see the documentation. # This tag requires that the tag GENERATE_LATEX is set to YES. LATEX_HEADER = -# The LATEX_FOOTER tag can be used to specify a personal LaTeX footer for the -# generated LaTeX document. The footer should contain everything after the last -# chapter. If it is left blank doxygen will generate a standard footer. See +# The LATEX_FOOTER tag can be used to specify a user-defined LaTeX footer for +# the generated LaTeX document. The footer should contain everything after the +# last chapter. If it is left blank doxygen will generate a standard footer. See # LATEX_HEADER for more information on how to generate a default footer and what -# special commands can be used inside the footer. -# -# Note: Only use a user-defined footer if you know what you are doing! +# special commands can be used inside the footer. See also section "Doxygen +# usage" for information on how to generate the default footer that doxygen +# normally uses. Note: Only use a user-defined footer if you know what you are +# doing! # This tag requires that the tag GENERATE_LATEX is set to YES. LATEX_FOOTER = @@ -1838,10 +2011,16 @@ PDF_HYPERLINKS = YES USE_PDFLATEX = YES -# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \batchmode -# command to the generated LaTeX files. This will instruct LaTeX to keep running -# if errors occur, instead of asking the user for help. This option is also used -# when generating formulas in HTML. +# The LATEX_BATCHMODE tag signals the behavior of LaTeX in case of an error. +# Possible values are: NO same as ERROR_STOP, YES same as BATCH, BATCH In batch +# mode nothing is printed on the terminal, errors are scrolled as if is +# hit at every error; missing files that TeX tries to input or request from +# keyboard input (\read on a not open input stream) cause the job to abort, +# NON_STOP In nonstop mode the diagnostic message will appear on the terminal, +# but there is no possibility of user interaction just like in batch mode, +# SCROLL In scroll mode, TeX will stop only for missing files to input or if +# keyboard input is necessary and ERROR_STOP In errorstop mode, TeX will stop at +# each error, asking for user intervention. # The default value is: NO. # This tag requires that the tag GENERATE_LATEX is set to YES. @@ -1854,16 +2033,6 @@ LATEX_BATCHMODE = NO LATEX_HIDE_INDICES = NO -# If the LATEX_SOURCE_CODE tag is set to YES then doxygen will include source -# code with syntax highlighting in the LaTeX output. -# -# Note that which sources are shown also depends on other settings such as -# SOURCE_BROWSER. -# The default value is: NO. -# This tag requires that the tag GENERATE_LATEX is set to YES. - -LATEX_SOURCE_CODE = NO - # The LATEX_BIB_STYLE tag can be used to specify the style to use for the # bibliography, e.g. plainnat, or ieeetr. See # https://en.wikipedia.org/wiki/BibTeX and \cite for more info. @@ -1872,14 +2041,6 @@ LATEX_SOURCE_CODE = NO LATEX_BIB_STYLE = plain -# If the LATEX_TIMESTAMP tag is set to YES then the footer of each generated -# page will contain the date and time when the page was generated. Setting this -# to NO can help when comparing the output of multiple runs. -# The default value is: NO. -# This tag requires that the tag GENERATE_LATEX is set to YES. - -LATEX_TIMESTAMP = NO - # The LATEX_EMOJI_DIRECTORY tag is used to specify the (relative or absolute) # path from which the emoji images will be read. If a relative path is entered, # it will be relative to the LATEX_OUTPUT directory. If left blank the @@ -1944,16 +2105,6 @@ RTF_STYLESHEET_FILE = RTF_EXTENSIONS_FILE = -# If the RTF_SOURCE_CODE tag is set to YES then doxygen will include source code -# with syntax highlighting in the RTF output. -# -# Note that which sources are shown also depends on other settings such as -# SOURCE_BROWSER. -# The default value is: NO. -# This tag requires that the tag GENERATE_RTF is set to YES. - -RTF_SOURCE_CODE = NO - #--------------------------------------------------------------------------- # Configuration options related to the man page output #--------------------------------------------------------------------------- @@ -2050,27 +2201,44 @@ GENERATE_DOCBOOK = NO DOCBOOK_OUTPUT = docbook -# If the DOCBOOK_PROGRAMLISTING tag is set to YES, doxygen will include the -# program listings (including syntax highlighting and cross-referencing -# information) to the DOCBOOK output. Note that enabling this will significantly -# increase the size of the DOCBOOK output. -# The default value is: NO. -# This tag requires that the tag GENERATE_DOCBOOK is set to YES. - -DOCBOOK_PROGRAMLISTING = NO - #--------------------------------------------------------------------------- # Configuration options for the AutoGen Definitions output #--------------------------------------------------------------------------- # If the GENERATE_AUTOGEN_DEF tag is set to YES, doxygen will generate an -# AutoGen Definitions (see http://autogen.sourceforge.net/) file that captures +# AutoGen Definitions (see https://autogen.sourceforge.net/) file that captures # the structure of the code including all documentation. Note that this feature # is still experimental and incomplete at the moment. # The default value is: NO. GENERATE_AUTOGEN_DEF = NO +#--------------------------------------------------------------------------- +# Configuration options related to Sqlite3 output +#--------------------------------------------------------------------------- + +# If the GENERATE_SQLITE3 tag is set to YES doxygen will generate a Sqlite3 +# database with symbols found by doxygen stored in tables. +# The default value is: NO. + +GENERATE_SQLITE3 = NO + +# The SQLITE3_OUTPUT tag is used to specify where the Sqlite3 database will be +# put. If a relative path is entered the value of OUTPUT_DIRECTORY will be put +# in front of it. +# The default directory is: sqlite3. +# This tag requires that the tag GENERATE_SQLITE3 is set to YES. + +SQLITE3_OUTPUT = sqlite3 + +# The SQLITE3_OVERWRITE_DB tag is set to YES, the existing doxygen_sqlite3.db +# database file will be recreated with each doxygen run. If set to NO, doxygen +# will warn if an a database file is already found and not modify it. +# The default value is: YES. +# This tag requires that the tag GENERATE_SQLITE3 is set to YES. + +SQLITE3_RECREATE_DB = YES + #--------------------------------------------------------------------------- # Configuration options related to the Perl module output #--------------------------------------------------------------------------- @@ -2145,7 +2313,8 @@ SEARCH_INCLUDES = YES # The INCLUDE_PATH tag can be used to specify one or more directories that # contain include files that are not input files but should be processed by the -# preprocessor. +# preprocessor. Note that the INCLUDE_PATH is not recursive, so the setting of +# RECURSIVE has no effect here. # This tag requires that the tag SEARCH_INCLUDES is set to YES. INCLUDE_PATH = @@ -2212,15 +2381,15 @@ TAGFILES = GENERATE_TAGFILE = -# If the ALLEXTERNALS tag is set to YES, all external class will be listed in -# the class index. If set to NO, only the inherited external classes will be -# listed. +# If the ALLEXTERNALS tag is set to YES, all external classes and namespaces +# will be listed in the class and namespace index. If set to NO, only the +# inherited external classes will be listed. # The default value is: NO. ALLEXTERNALS = NO # If the EXTERNAL_GROUPS tag is set to YES, all external groups will be listed -# in the modules index. If set to NO, only the current project's groups will be +# in the topic index. If set to NO, only the current project's groups will be # listed. # The default value is: YES. @@ -2234,25 +2403,9 @@ EXTERNAL_GROUPS = YES EXTERNAL_PAGES = YES #--------------------------------------------------------------------------- -# Configuration options related to the dot tool +# Configuration options related to diagram generator tools #--------------------------------------------------------------------------- -# If the CLASS_DIAGRAMS tag is set to YES, doxygen will generate a class diagram -# (in HTML and LaTeX) for classes with base or super classes. Setting the tag to -# NO turns the diagrams off. Note that this option also works with HAVE_DOT -# disabled, but it is recommended to install and use dot, since it yields more -# powerful graphs. -# The default value is: YES. - -CLASS_DIAGRAMS = YES - -# You can include diagrams made with dia in doxygen documentation. Doxygen will -# then run dia to produce the diagram and insert it in the documentation. The -# DIA_PATH tag allows you to specify the directory where the dia binary resides. -# If left empty dia is assumed to be found in the default search path. - -DIA_PATH = - # If set to YES the inheritance and collaboration graphs will hide inheritance # and usage relations if the target is undocumented or is not a class. # The default value is: YES. @@ -2261,7 +2414,7 @@ HIDE_UNDOC_RELATIONS = YES # If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is # available from the path. This tool is part of Graphviz (see: -# http://www.graphviz.org/), a graph visualization toolkit from AT&T and Lucent +# https://www.graphviz.org/), a graph visualization toolkit from AT&T and Lucent # Bell Labs. The other options in this section have no effect if this option is # set to NO # The default value is: NO. @@ -2278,49 +2431,73 @@ HAVE_DOT = NO DOT_NUM_THREADS = 0 -# When you want a differently looking font in the dot files that doxygen -# generates you can specify the font name using DOT_FONTNAME. You need to make -# sure dot is able to find the font, which can be done by putting it in a -# standard location or by setting the DOTFONTPATH environment variable or by -# setting DOT_FONTPATH to the directory containing the font. -# The default value is: Helvetica. +# DOT_COMMON_ATTR is common attributes for nodes, edges and labels of +# subgraphs. When you want a differently looking font in the dot files that +# doxygen generates you can specify fontname, fontcolor and fontsize attributes. +# For details please see Node, +# Edge and Graph Attributes specification You need to make sure dot is able +# to find the font, which can be done by putting it in a standard location or by +# setting the DOTFONTPATH environment variable or by setting DOT_FONTPATH to the +# directory containing the font. Default graphviz fontsize is 14. +# The default value is: fontname=Helvetica,fontsize=10. # This tag requires that the tag HAVE_DOT is set to YES. -DOT_FONTNAME = Helvetica +DOT_COMMON_ATTR = "fontname=Helvetica,fontsize=10" -# The DOT_FONTSIZE tag can be used to set the size (in points) of the font of -# dot graphs. -# Minimum value: 4, maximum value: 24, default value: 10. +# DOT_EDGE_ATTR is concatenated with DOT_COMMON_ATTR. For elegant style you can +# add 'arrowhead=open, arrowtail=open, arrowsize=0.5'. Complete documentation about +# arrows shapes. +# The default value is: labelfontname=Helvetica,labelfontsize=10. # This tag requires that the tag HAVE_DOT is set to YES. -DOT_FONTSIZE = 10 +DOT_EDGE_ATTR = "labelfontname=Helvetica,labelfontsize=10" -# By default doxygen will tell dot to use the default font as specified with -# DOT_FONTNAME. If you specify a different font using DOT_FONTNAME you can set -# the path where dot can find it using this tag. +# DOT_NODE_ATTR is concatenated with DOT_COMMON_ATTR. For view without boxes +# around nodes set 'shape=plain' or 'shape=plaintext' Shapes specification +# The default value is: shape=box,height=0.2,width=0.4. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_NODE_ATTR = "shape=box,height=0.2,width=0.4" + +# You can set the path where dot can find font specified with fontname in +# DOT_COMMON_ATTR and others dot attributes. # This tag requires that the tag HAVE_DOT is set to YES. DOT_FONTPATH = -# If the CLASS_GRAPH tag is set to YES then doxygen will generate a graph for -# each documented class showing the direct and indirect inheritance relations. -# Setting this tag to YES will force the CLASS_DIAGRAMS tag to NO. +# If the CLASS_GRAPH tag is set to YES or GRAPH or BUILTIN then doxygen will +# generate a graph for each documented class showing the direct and indirect +# inheritance relations. In case the CLASS_GRAPH tag is set to YES or GRAPH and +# HAVE_DOT is enabled as well, then dot will be used to draw the graph. In case +# the CLASS_GRAPH tag is set to YES and HAVE_DOT is disabled or if the +# CLASS_GRAPH tag is set to BUILTIN, then the built-in generator will be used. +# If the CLASS_GRAPH tag is set to TEXT the direct and indirect inheritance +# relations will be shown as texts / links. +# Possible values are: NO, YES, TEXT, GRAPH and BUILTIN. # The default value is: YES. -# This tag requires that the tag HAVE_DOT is set to YES. CLASS_GRAPH = YES # If the COLLABORATION_GRAPH tag is set to YES then doxygen will generate a # graph for each documented class showing the direct and indirect implementation # dependencies (inheritance, containment, and class references variables) of the -# class with other documented classes. +# class with other documented classes. Explicit enabling a collaboration graph, +# when COLLABORATION_GRAPH is set to NO, can be accomplished by means of the +# command \collaborationgraph. Disabling a collaboration graph can be +# accomplished by means of the command \hidecollaborationgraph. # The default value is: YES. # This tag requires that the tag HAVE_DOT is set to YES. COLLABORATION_GRAPH = YES # If the GROUP_GRAPHS tag is set to YES then doxygen will generate a graph for -# groups, showing the direct groups dependencies. +# groups, showing the direct groups dependencies. Explicit enabling a group +# dependency graph, when GROUP_GRAPHS is set to NO, can be accomplished by means +# of the command \groupgraph. Disabling a directory graph can be accomplished by +# means of the command \hidegroupgraph. See also the chapter Grouping in the +# manual. # The default value is: YES. # This tag requires that the tag HAVE_DOT is set to YES. @@ -2380,7 +2557,9 @@ TEMPLATE_RELATIONS = NO # If the INCLUDE_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are set to # YES then doxygen will generate a graph for each documented file showing the # direct and indirect include dependencies of the file with other documented -# files. +# files. Explicit enabling an include graph, when INCLUDE_GRAPH is is set to NO, +# can be accomplished by means of the command \includegraph. Disabling an +# include graph can be accomplished by means of the command \hideincludegraph. # The default value is: YES. # This tag requires that the tag HAVE_DOT is set to YES. @@ -2389,7 +2568,10 @@ INCLUDE_GRAPH = YES # If the INCLUDED_BY_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are # set to YES then doxygen will generate a graph for each documented file showing # the direct and indirect include dependencies of the file with other documented -# files. +# files. Explicit enabling an included by graph, when INCLUDED_BY_GRAPH is set +# to NO, can be accomplished by means of the command \includedbygraph. Disabling +# an included by graph can be accomplished by means of the command +# \hideincludedbygraph. # The default value is: YES. # This tag requires that the tag HAVE_DOT is set to YES. @@ -2429,16 +2611,26 @@ GRAPHICAL_HIERARCHY = YES # If the DIRECTORY_GRAPH tag is set to YES then doxygen will show the # dependencies a directory has on other directories in a graphical way. The # dependency relations are determined by the #include relations between the -# files in the directories. +# files in the directories. Explicit enabling a directory graph, when +# DIRECTORY_GRAPH is set to NO, can be accomplished by means of the command +# \directorygraph. Disabling a directory graph can be accomplished by means of +# the command \hidedirectorygraph. # The default value is: YES. # This tag requires that the tag HAVE_DOT is set to YES. DIRECTORY_GRAPH = YES +# The DIR_GRAPH_MAX_DEPTH tag can be used to limit the maximum number of levels +# of child directories generated in directory dependency graphs by dot. +# Minimum value: 1, maximum value: 25, default value: 1. +# This tag requires that the tag DIRECTORY_GRAPH is set to YES. + +DIR_GRAPH_MAX_DEPTH = 1 + # The DOT_IMAGE_FORMAT tag can be used to set the image format of the images # generated by dot. For an explanation of the image formats see the section # output formats in the documentation of the dot tool (Graphviz (see: -# http://www.graphviz.org/)). +# https://www.graphviz.org/)). # Note: If you choose svg you need to set HTML_FILE_EXTENSION to xhtml in order # to make the SVG files visible in IE 9+ (other browsers do not have this # requirement). @@ -2475,11 +2667,12 @@ DOT_PATH = DOTFILE_DIRS = -# The MSCFILE_DIRS tag can be used to specify one or more directories that -# contain msc files that are included in the documentation (see the \mscfile -# command). +# You can include diagrams made with dia in doxygen documentation. Doxygen will +# then run dia to produce the diagram and insert it in the documentation. The +# DIA_PATH tag allows you to specify the directory where the dia binary resides. +# If left empty dia is assumed to be found in the default search path. -MSCFILE_DIRS = +DIA_PATH = # The DIAFILE_DIRS tag can be used to specify one or more directories that # contain dia files that are included in the documentation (see the \diafile @@ -2488,10 +2681,10 @@ MSCFILE_DIRS = DIAFILE_DIRS = # When using plantuml, the PLANTUML_JAR_PATH tag should be used to specify the -# path where java can find the plantuml.jar file. If left blank, it is assumed -# PlantUML is not used or called during a preprocessing step. Doxygen will -# generate a warning when it encounters a \startuml command in this case and -# will not generate output for the diagram. +# path where java can find the plantuml.jar file or to the filename of jar file +# to be used. If left blank, it is assumed PlantUML is not used or called during +# a preprocessing step. Doxygen will generate a warning when it encounters a +# \startuml command in this case and will not generate output for the diagram. PLANTUML_JAR_PATH = @@ -2529,18 +2722,6 @@ DOT_GRAPH_MAX_NODES = 50 MAX_DOT_GRAPH_DEPTH = 0 -# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent -# background. This is disabled by default, because dot on Windows does not seem -# to support this out of the box. -# -# Warning: Depending on the platform used, enabling this option may lead to -# badly anti-aliased labels on the edges of a graph (i.e. they become hard to -# read). -# The default value is: NO. -# This tag requires that the tag HAVE_DOT is set to YES. - -DOT_TRANSPARENT = NO - # Set the DOT_MULTI_TARGETS tag to YES to allow dot to generate multiple output # files in one run (i.e. multiple -o and -T options on the command line). This # makes dot run faster, but since only newer versions of dot (>1.8.10) support @@ -2553,6 +2734,8 @@ DOT_MULTI_TARGETS = NO # If the GENERATE_LEGEND tag is set to YES doxygen will generate a legend page # explaining the meaning of the various boxes and arrows in the dot generated # graphs. +# Note: This tag requires that UML_LOOK isn't set, i.e. the doxygen internal +# graphical representation for inheritance and collaboration diagrams is used. # The default value is: YES. # This tag requires that the tag HAVE_DOT is set to YES. @@ -2561,8 +2744,24 @@ GENERATE_LEGEND = YES # If the DOT_CLEANUP tag is set to YES, doxygen will remove the intermediate # files that are used to generate the various graphs. # -# Note: This setting is not only used for dot files but also for msc and -# plantuml temporary files. +# Note: This setting is not only used for dot files but also for msc temporary +# files. # The default value is: YES. DOT_CLEANUP = YES + +# You can define message sequence charts within doxygen comments using the \msc +# command. If the MSCGEN_TOOL tag is left empty (the default), then doxygen will +# use a built-in version of mscgen tool to produce the charts. Alternatively, +# the MSCGEN_TOOL tag can also specify the name an external tool. For instance, +# specifying prog as the value, doxygen will call the tool as prog -T +# -o . The external tool should support +# output file formats "png", "eps", "svg", and "ismap". + +MSCGEN_TOOL = + +# The MSCFILE_DIRS tag can be used to specify one or more directories that +# contain msc files that are included in the documentation (see the \mscfile +# command). + +MSCFILE_DIRS = From 621b4c2415139008c659a21a4f2376589848668c Mon Sep 17 00:00:00 2001 From: essej Date: Fri, 25 Aug 2023 18:16:25 -0400 Subject: [PATCH 09/65] Add support for opcodes lotimer/hitimer While not widely supported or well documented, is very useful especially when triggering a note with on_locc/on_hicc to prevent a bunch of retriggers. The region will trigger as long as the elapsed time from the note-on of the most recently triggered region in the current polyphony group is between lotimer and hitimer amount of seconds. lotimer default 0, hitimer default is max_float. Example use: to preventing a region from retriggering too quickly, set lotimer=0.1 --- src/sfizz/Defaults.cpp | 2 ++ src/sfizz/Defaults.h | 2 ++ src/sfizz/MidiState.h | 6 ++++++ src/sfizz/PolyphonyGroup.cpp | 1 + src/sfizz/PolyphonyGroup.h | 6 ++++++ src/sfizz/Region.cpp | 9 +++++++++ src/sfizz/Region.h | 2 ++ src/sfizz/Synth.cpp | 8 ++++++++ src/sfizz/SynthMessaging.cpp | 8 ++++++++ src/sfizz/Voice.cpp | 21 +++++++++++++++------ src/sfizz/Voice.h | 7 +++++++ src/sfizz/VoiceManager.cpp | 10 ++++++++++ src/sfizz/VoiceManager.h | 10 ++++++++++ 13 files changed, 86 insertions(+), 6 deletions(-) diff --git a/src/sfizz/Defaults.cpp b/src/sfizz/Defaults.cpp index 04982371d..5df7cb40c 100644 --- a/src/sfizz/Defaults.cpp +++ b/src/sfizz/Defaults.cpp @@ -199,6 +199,8 @@ 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 }; +FloatSpec loTimer { 0.0f, {0.0f, float_max}, 0 }; +FloatSpec hiTimer { float_max, {0.0f, float_max}, 0 }; 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 fc4ef5464..21868260d 100644 --- a/src/sfizz/Defaults.h +++ b/src/sfizz/Defaults.h @@ -317,6 +317,8 @@ namespace Default extern const OpcodeSpec filter; extern const OpcodeSpec eq; extern const OpcodeSpec sustainCancelsRelease; + extern const OpcodeSpec loTimer; + extern const OpcodeSpec hiTimer; // Default/max count for objects constexpr int numEQs { 3 }; diff --git a/src/sfizz/MidiState.h b/src/sfizz/MidiState.h index fb1870059..76d978412 100644 --- a/src/sfizz/MidiState.h +++ b/src/sfizz/MidiState.h @@ -165,6 +165,12 @@ class MidiState */ void advanceTime(int numSamples) noexcept; + /** + * @brief Returns current internal sample clock + * + */ + unsigned getInternalClock() const noexcept { return internalClock; } + /** * @brief Flush events in all states, keeping only the last one as the "base" state * diff --git a/src/sfizz/PolyphonyGroup.cpp b/src/sfizz/PolyphonyGroup.cpp index 2e8ba56a4..ded4fac56 100644 --- a/src/sfizz/PolyphonyGroup.cpp +++ b/src/sfizz/PolyphonyGroup.cpp @@ -15,6 +15,7 @@ void sfz::PolyphonyGroup::registerVoice(Voice* voice) noexcept { if (absl::c_find(voices, voice) == voices.end()) voices.push_back(voice); + mostRecentStartStamp_ = voice->getStartTimestampSamples(); } void sfz::PolyphonyGroup::removeVoice(const Voice* voice) noexcept diff --git a/src/sfizz/PolyphonyGroup.h b/src/sfizz/PolyphonyGroup.h index a39254434..63be170d5 100644 --- a/src/sfizz/PolyphonyGroup.h +++ b/src/sfizz/PolyphonyGroup.h @@ -62,9 +62,15 @@ class PolyphonyGroup { * @return std::vector& */ std::vector& getActiveVoices() noexcept { return voices; } + /** + * @brief Returns the start timestamp in samples of the most recently activated voice + */ + unsigned getMostRecentStartTimestamp() const noexcept { return mostRecentStartStamp_; } + private: unsigned polyphonyLimit { config::maxVoices }; std::vector voices; + unsigned mostRecentStartStamp_ { 0 }; }; } diff --git a/src/sfizz/Region.cpp b/src/sfizz/Region.cpp index fe274fe86..8c260e22a 100644 --- a/src/sfizz/Region.cpp +++ b/src/sfizz/Region.cpp @@ -562,6 +562,15 @@ bool sfz::Region::parseOpcode(const Opcode& rawOpcode, bool cleanOpcode) groupVolume = opcode.read(Default::volume); break; + case hash("lotimer"): + timerRange.setStart(opcode.read(Default::loTimer)); + useTimerRange = useTimerRange || timerRange.getStart() != Default::loTimer; + break; + case hash("hitimer"): + timerRange.setEnd(opcode.read(Default::hiTimer)); + useTimerRange = useTimerRange || timerRange.getEnd() != Default::hiTimer; + break; + // Performance parameters: filters case hash("cutoff&"): // also cutoff { diff --git a/src/sfizz/Region.h b/src/sfizz/Region.h index 43165d1e5..de7cc37f1 100644 --- a/src/sfizz/Region.h +++ b/src/sfizz/Region.h @@ -335,6 +335,8 @@ struct Region { CCMap> crossfadeCCInRange { Default::crossfadeCCInRange }; // xfin_loccN xfin_hiccN CCMap> crossfadeCCOutRange { Default::crossfadeCCOutRange }; // xfout_loccN xfout_hiccN float rtDecay { Default::rtDecay }; // rt_decay + UncheckedRange timerRange { Default::loTimer, Default::hiTimer }; // hitimer and lotimer (seconds) + bool useTimerRange { false }; // to optimize having to check the timer range, because this is very rare opcode float globalAmplitude { 1.0 }; // global_amplitude float masterAmplitude { 1.0 }; // master_amplitude diff --git a/src/sfizz/Synth.cpp b/src/sfizz/Synth.cpp index e1228778e..e4b532bd9 100644 --- a/src/sfizz/Synth.cpp +++ b/src/sfizz/Synth.cpp @@ -1353,6 +1353,7 @@ void Synth::Impl::noteOnDispatch(int delay, int noteNumber, float velocity) noex { const auto randValue = randNoteDistribution_(Random::randomGenerator); SisterVoiceRingBuilder ring; + MidiState& midiState = resources_.getMidiState(); if (!lastKeyswitchLists_[noteNumber].empty()) { if (currentSwitch_ && *currentSwitch_ != noteNumber) { @@ -1374,6 +1375,9 @@ void Synth::Impl::noteOnDispatch(int delay, int noteNumber, float velocity) noex for (Layer* layer : noteActivationLists_[noteNumber]) { if (layer->registerNoteOn(noteNumber, velocity, randValue)) { const Region& region = layer->getRegion(); + if (region.useTimerRange && !voiceManager_.withinValidTimerRange(®ion, midiState.getInternalClock() + delay, sampleRate_)) + continue; + checkOffGroups(®ion, delay, noteNumber); TriggerEvent triggerEvent { TriggerEventType::NoteOn, noteNumber, velocity }; startVoice(layer, delay, triggerEvent, ring); @@ -1430,6 +1434,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); + MidiState& midiState = resources_.getMidiState(); for (Layer* layer : ccActivationLists_[ccNumber]) { const Region& region = layer->getRegion(); @@ -1448,6 +1453,9 @@ void Synth::Impl::ccDispatch(int delay, int ccNumber, float value) noexcept } if (layer->registerCC(ccNumber, value, randValue)) { + if (region.useTimerRange && ! voiceManager_.withinValidTimerRange(®ion, midiState.getInternalClock() + delay, sampleRate_)) + continue; + checkOffGroups(®ion, delay, ccNumber); startVoice(layer, delay, triggerEvent, ring); } diff --git a/src/sfizz/SynthMessaging.cpp b/src/sfizz/SynthMessaging.cpp index e48ed637d..29dae245e 100644 --- a/src/sfizz/SynthMessaging.cpp +++ b/src/sfizz/SynthMessaging.cpp @@ -730,6 +730,14 @@ void sfz::Synth::dispatchMessage(Client& client, int delay, const char* path, co } } break; + MATCH("/region&/timer_range", "") { + GET_REGION_OR_BREAK(indices[0]) + sfizz_arg_t args[2]; + args[0].f = region.timerRange.getStart(); + args[1].f = region.timerRange.getEnd(); + client.receive(delay, path, "ff", args); + } break; + MATCH("/region&/position", "") { GET_REGION_OR_BREAK(indices[0]) client.receive<'f'>(delay, path, region.position * 100.0f); diff --git a/src/sfizz/Voice.cpp b/src/sfizz/Voice.cpp index 0df6d42ea..1a0823fa3 100644 --- a/src/sfizz/Voice.cpp +++ b/src/sfizz/Voice.cpp @@ -262,6 +262,7 @@ struct Voice::Impl int samplesPerBlock_ { config::defaultSamplesPerBlock }; float sampleRate_ { config::defaultSampleRate }; + unsigned startTimestamp_ { 0 }; Resources& resources_; @@ -429,14 +430,18 @@ bool Voice::startVoice(Layer* layer, int delay, const TriggerEvent& event) noexc return false; } - impl.switchState(State::playing); - - impl.updateExtendedCCValues(); - ASSERT(delay >= 0); if (delay < 0) delay = 0; + impl.triggerDelay_ = delay; + impl.initialDelay_ = delay + static_cast(regionDelay(region, midiState) * impl.sampleRate_); + impl.startTimestamp_ = midiState.getInternalClock() + impl.initialDelay_; // need to set this before switchState + + impl.switchState(State::playing); + + impl.updateExtendedCCValues(); + if (region.isOscillator()) { WavetablePool& wavePool = resources.getWavePool(); const WavetableMulti* wave = nullptr; @@ -510,8 +515,6 @@ bool Voice::startVoice(Layer* layer, int delay, const TriggerEvent& event) noexc impl.equalizers_[i].setup(region, i, impl.triggerEvent_.value); } - impl.triggerDelay_ = delay; - 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; @@ -2079,6 +2082,12 @@ int Voice::getSourcePosition() const noexcept return impl.sourcePosition_; } +unsigned Voice::getStartTimestampSamples() const noexcept +{ + Impl& impl = *impl_; + return impl.startTimestamp_; +} + LFO* Voice::getLFO(size_t index) { Impl& impl = *impl_; diff --git a/src/sfizz/Voice.h b/src/sfizz/Voice.h index 58774562a..3cebdeeb3 100644 --- a/src/sfizz/Voice.h +++ b/src/sfizz/Voice.h @@ -437,6 +437,13 @@ class Voice { */ int getSourcePosition() const noexcept; + /** + * @brief Get the timestamp in midistate transport sample time when the voice was started, in samples + * + * @return int + */ + unsigned getStartTimestampSamples() 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 73e7476b3..47dbbefe4 100644 --- a/src/sfizz/VoiceManager.cpp +++ b/src/sfizz/VoiceManager.cpp @@ -244,6 +244,16 @@ void VoiceManager::checkNotePolyphony(const Region* region, int delay, const Tri } } +bool VoiceManager::withinValidTimerRange(const Region* region, unsigned timestampSamples, float sampleRate) const noexcept +{ + auto found = polyphonyGroups_.find(static_cast(region->group)); + if (found != polyphonyGroups_.end()) { + // convert timestamp to seconds + return region->timerRange.contains((timestampSamples - found->second.getMostRecentStartTimestamp()) / sampleRate); + } + return true; +} + void VoiceManager::checkGroupPolyphony(const Region* region, int delay) noexcept { auto& group = polyphonyGroups_[region->group]; diff --git a/src/sfizz/VoiceManager.h b/src/sfizz/VoiceManager.h index 354976b5e..b14e083c4 100644 --- a/src/sfizz/VoiceManager.h +++ b/src/sfizz/VoiceManager.h @@ -139,6 +139,16 @@ struct VoiceManager final : public Voice::StateListener */ void requireNumVoices(int numVoices, Resources& resources); + /** + * @brief Is this timestamp within the lo/hitimer range for this region based on current group activity + * + * @param region + * @param timestampSamples in samples + * @param sampleRate + * @return bool + */ + bool withinValidTimerRange(const Region* region, unsigned timestampSamples, float sampleRate) const noexcept; + private: int numRequiredVoices_ { config::numVoices }; std::vector list_; From 9b38c0bbc2b1f16e6e55bcd5e2d4007e321e6132 Mon Sep 17 00:00:00 2001 From: Paul Fd Date: Sat, 21 Oct 2023 23:08:13 +0200 Subject: [PATCH 10/65] Add lotimer/hitimer tests --- src/sfizz/SynthMessaging.cpp | 9 +++ tests/RegionTriggersT.cpp | 124 +++++++++++++++++++++++++++++++++++ tests/RegionValuesT.cpp | 33 ++++++++++ 3 files changed, 166 insertions(+) diff --git a/src/sfizz/SynthMessaging.cpp b/src/sfizz/SynthMessaging.cpp index 29dae245e..f065a4a70 100644 --- a/src/sfizz/SynthMessaging.cpp +++ b/src/sfizz/SynthMessaging.cpp @@ -309,6 +309,15 @@ void sfz::Synth::dispatchMessage(Client& client, int delay, const char* path, co } } break; + MATCH("/region&/use_timer_range", "") { + GET_REGION_OR_BREAK(indices[0]) + if (region.useTimerRange) { + client.receive<'T'>(delay, path, {}); + } else { + client.receive<'F'>(delay, path, {}); + } + } break; + MATCH("/region&/count", "") { GET_REGION_OR_BREAK(indices[0]) if (region.sampleCount) diff --git a/tests/RegionTriggersT.cpp b/tests/RegionTriggersT.cpp index b25486831..a53a700a7 100644 --- a/tests/RegionTriggersT.cpp +++ b/tests/RegionTriggersT.cpp @@ -426,3 +426,127 @@ TEST_CASE("[Triggers] Offed voices with CC triggers do not activate release trig synth.hdcc(10, 64, 10_norm); REQUIRE(playingSamples(synth) == std::vector { "*saw" }); } + +TEST_CASE("[Triggers] lotimer") +{ + Synth synth; + synth.setSampleRate(48000); + synth.setSamplesPerBlock(480); + sfz::AudioBuffer buffer { 2, 480 }; + synth.loadSfzString(fs::current_path() / "tests/TestFiles/sw_vel.sfz", R"( + key=40 lotimer=0.1 sample=kick.wav loop_mode=one_shot + )"); + + // Advance time, t = 0.2s + for (int i = 0; i < 20; ++i) + synth.renderBlock(buffer); + + synth.noteOn(0, 40, 100); + REQUIRE(playingSamples(synth) == std::vector { "kick.wav" }); + + // Advance time by 0.02s and send a note off + synth.renderBlock(buffer); + synth.noteOff(0, 40, 100); + synth.renderBlock(buffer); + + // t = 0.22s, not allowed to retrigger + REQUIRE(playingSamples(synth) == std::vector { "kick.wav" }); + synth.noteOn(0, 40, 100); + REQUIRE(playingSamples(synth) == std::vector { "kick.wav" }); + + // Advance time by 0.08s and send a note off + synth.renderBlock(buffer); + synth.noteOff(0, 40, 100); + for (int i = 0; i < 7; ++i) + synth.renderBlock(buffer); + + // t = 0.3s, retriggering OK + REQUIRE(playingSamples(synth) == std::vector { "kick.wav" }); + synth.noteOn(0, 40, 100); + REQUIRE(playingSamples(synth) == std::vector { "kick.wav", "kick.wav" }); +} + +TEST_CASE("[Triggers] lotimer on CC") +{ + Synth synth; + synth.setSampleRate(48000); + synth.setSamplesPerBlock(480); + sfz::AudioBuffer buffer { 2, 480 }; + synth.loadSfzString(fs::current_path() / "tests/TestFiles/sw_vel.sfz", R"( + start_locc4=100 start_hicc4=100 lotimer=0.1 sample=kick.wav loop_mode=one_shot + )"); + + // Advance time, t = 0.2s + for (int i = 0; i < 20; ++i) + synth.renderBlock(buffer); + + REQUIRE(playingSamples(synth) == std::vector { }); + synth.hdcc(0, 4, 100_norm); + REQUIRE(playingSamples(synth) == std::vector { "kick.wav" }); + + // Advance time by 0.02s and reset CC + synth.renderBlock(buffer); + synth.hdcc(0, 4, 0); + synth.renderBlock(buffer); + + // t = 0.22s, not allowed to retrigger + REQUIRE(playingSamples(synth) == std::vector { "kick.wav" }); + synth.hdcc(0, 4, 100_norm); + REQUIRE(playingSamples(synth) == std::vector { "kick.wav" }); + + // Advance time by 0.08s and reset CC + synth.renderBlock(buffer); + synth.hdcc(0, 4, 0); + for (int i = 0; i < 7; ++i) + synth.renderBlock(buffer); + + // t = 0.3s, retriggering OK + REQUIRE(playingSamples(synth) == std::vector { "kick.wav" }); + synth.hdcc(0, 4, 100_norm); + REQUIRE(playingSamples(synth) == std::vector { "kick.wav", "kick.wav" }); +} + +TEST_CASE("[Triggers] hitimer (with group)") +{ + Synth synth; + synth.setSampleRate(48000); + synth.setSamplesPerBlock(480); + sfz::AudioBuffer buffer { 2, 480 }; + synth.loadSfzString(fs::current_path() / "tests/TestFiles/sw_vel.sfz", R"( + group=1 key=40 sample=snare.wav loop_mode=one_shot + group=1 key=41 hitimer=0.1 sample=kick.wav loop_mode=one_shot + )"); + + // Advance time to 0.1s + for (int i = 0; i < 10; ++i) + synth.renderBlock(buffer); + + // Not triggering before snare + synth.noteOn(0, 41, 100); + REQUIRE(playingSamples(synth) == std::vector { }); + synth.noteOn(0, 40, 100); + REQUIRE(playingSamples(synth) == std::vector { "snare.wav" }); + + // OK to trigger now + REQUIRE(playingSamples(synth) == std::vector { "snare.wav" }); + synth.noteOn(0, 41, 100); + REQUIRE(playingSamples(synth) == std::vector { "snare.wav", "kick.wav" }); + + // Advance time + for (int i = 0; i < 5; ++i) + synth.renderBlock(buffer); + + // t = 0.15s, still OK to trigger now + REQUIRE(playingSamples(synth) == std::vector { "snare.wav", "kick.wav" }); + synth.noteOn(0, 41, 100); + REQUIRE(playingSamples(synth) == std::vector { "snare.wav", "kick.wav", "kick.wav" }); + + // Advance time (it has to be 0.1s because playing the kick resets the timer) + for (int i = 0; i < 10; ++i) + synth.renderBlock(buffer); + + // t = 0.25s, not triggering a new kick + REQUIRE(playingSamples(synth) == std::vector { "snare.wav", "kick.wav", "kick.wav" }); + synth.noteOn(0, 41, 100); + REQUIRE(playingSamples(synth) == std::vector { "snare.wav", "kick.wav", "kick.wav" }); +} diff --git a/tests/RegionValuesT.cpp b/tests/RegionValuesT.cpp index f6bb21ba8..57dcb5436 100644 --- a/tests/RegionValuesT.cpp +++ b/tests/RegionValuesT.cpp @@ -1106,6 +1106,39 @@ TEST_CASE("[Values] Rand range") REQUIRE(messageList == expected); } +TEST_CASE("[Values] Timer range") +{ + 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 lotimer=0.2 hitimer=0.4 + sample=kick.wav lotimer=-0.1 hitimer=0.4 + sample=kick.wav lotimer=0.2 hitimer=-0.1 + )"); + synth.dispatchMessage(client, 0, "/region0/timer_range", "", nullptr); + synth.dispatchMessage(client, 0, "/region0/use_timer_range", "", nullptr); + synth.dispatchMessage(client, 0, "/region1/timer_range", "", nullptr); + synth.dispatchMessage(client, 0, "/region1/use_timer_range", "", nullptr); + synth.dispatchMessage(client, 0, "/region2/timer_range", "", nullptr); + synth.dispatchMessage(client, 0, "/region2/use_timer_range", "", nullptr); + synth.dispatchMessage(client, 0, "/region3/timer_range", "", nullptr); + synth.dispatchMessage(client, 0, "/region3/use_timer_range", "", nullptr); + std::vector expected { + "/region0/timer_range,ff : { 0, 3.40282e+38 }", + "/region0/use_timer_range,F : { }", + "/region1/timer_range,ff : { 0.2, 0.4 }", + "/region1/use_timer_range,T : { }", + "/region2/timer_range,ff : { 0, 0.4 }", + "/region2/use_timer_range,T : { }", + "/region3/timer_range,ff : { 0.2, 3.40282e+38 }", + "/region3/use_timer_range,T : { }", + }; + REQUIRE(messageList == expected); +} + TEST_CASE("[Values] Sequence length") { Synth synth; From 9dac3cbd6089b95b1ad17b29c39440d597f163a0 Mon Sep 17 00:00:00 2001 From: cvde <4895293+cvde@users.noreply.github.com> Date: Sun, 29 Oct 2023 13:07:50 +0100 Subject: [PATCH 11/65] Adjusted the gain compensation to not lose gain due to applied pan stages (Issue #1086) --- src/sfizz/Voice.cpp | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/sfizz/Voice.cpp b/src/sfizz/Voice.cpp index 1a0823fa3..3315fb694 100644 --- a/src/sfizz/Voice.cpp +++ b/src/sfizz/Voice.cpp @@ -980,6 +980,10 @@ void Voice::Impl::panStageMono(AudioSpan buffer) noexcept (*modulationSpan)[i] += mod[i]; } pan(*modulationSpan, leftBuffer, rightBuffer); + + // add +3dB (10^(3/20)) to compensate for the pan stage (-3dB per stage) + applyGain1(1.4125375446227544f, leftBuffer); + applyGain1(1.4125375446227544f, rightBuffer); } void Voice::Impl::panStageStereo(AudioSpan buffer) noexcept @@ -1020,9 +1024,9 @@ void Voice::Impl::panStageStereo(AudioSpan buffer) noexcept } pan(*modulationSpan, leftBuffer, rightBuffer); - // add +3dB to compensate for the 2 pan stages (-3dB each stage) - applyGain1(1.4125375446227544f, leftBuffer); - applyGain1(1.4125375446227544f, rightBuffer); + // add +6dB (10^(6/20)) to compensate for the 2 pan stages (-3dB per stage) + applyGain1(1.9952623149688797f, leftBuffer); + applyGain1(1.9952623149688797f, rightBuffer); } void Voice::Impl::filterStageMono(AudioSpan buffer) noexcept From c6287a121da6d97318e563aea9035ec11adf469f Mon Sep 17 00:00:00 2001 From: "Ivan A. Melnikov" Date: Tue, 17 Oct 2023 10:51:48 +0400 Subject: [PATCH 12/65] synth: Reset the voice before starting it The voice manager may select a voice that is currently audible, so its state has to be cleaned up before starting over. Fixes: https://github.com/sfztools/sfizz/issues/1180 --- src/sfizz/Synth.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/sfizz/Synth.cpp b/src/sfizz/Synth.cpp index e4b532bd9..6b75d2773 100644 --- a/src/sfizz/Synth.cpp +++ b/src/sfizz/Synth.cpp @@ -1310,6 +1310,7 @@ void Synth::Impl::startVoice(Layer* layer, int delay, const TriggerEvent& trigge if (selectedVoice == nullptr) return; + selectedVoice->reset(); if (selectedVoice->startVoice(layer, delay, triggerEvent)) ring.addVoiceToRing(selectedVoice); } From d91b338e1b6dcc600b481cfd79a011e174624913 Mon Sep 17 00:00:00 2001 From: redtide Date: Mon, 16 Oct 2023 22:33:02 +0200 Subject: [PATCH 13/65] Added GitSubmoduleCheck.cmake module --- CMakeLists.txt | 12 ++++++++++++ cmake/GitSubmoduleCheck.cmake | 13 +++++++++++++ 2 files changed, 25 insertions(+) create mode 100644 cmake/GitSubmoduleCheck.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index 66172807f..04f361476 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -15,6 +15,18 @@ set(PROJECT_REPOSITORY https://github.com/sfztools/sfizz) # External configuration CMake scripts set(CMAKE_MODULE_PATH "${CMAKE_MODULE_PATH};${CMAKE_CURRENT_SOURCE_DIR}/cmake") + +# Ensure presence of Git submodules (when not using the source tarball) +if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/.git") + include(GitSubmoduleCheck) + git_submodule_check(external/abseil-cpp) + git_submodule_check(external/filesystem) + git_submodule_check(external/simde) + git_submodule_check(external/st_audiofile/thirdparty/dr_libs) + git_submodule_check(external/st_audiofile/thirdparty/libaiff) + git_submodule_check(external/st_audiofile/thirdparty/wavpack) +endif() + include(BuildType) # Build Options diff --git a/cmake/GitSubmoduleCheck.cmake b/cmake/GitSubmoduleCheck.cmake new file mode 100644 index 000000000..49d79b270 --- /dev/null +++ b/cmake/GitSubmoduleCheck.cmake @@ -0,0 +1,13 @@ +find_package(Git REQUIRED) + +# https://gist.github.com/scivision/bb1d47a9529e153617414e91ff5390af + +function(git_submodule_check dir) + if(NOT EXISTS "${dir}/CMakeLists.txt") + execute_process(COMMAND ${GIT_EXECUTABLE} submodule update --init --recursive -- ${dir} + WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} + COMMAND_ERROR_IS_FATAL ANY) + endif() +# Add a Git submodule directory to CMake, assuming the Git submodule directory is a CMake project. +# add_subdirectory(${dir} EXCLUDE_FROM_ALL) +endfunction() From be202a3b8a7006d354db99b09b6dbe171852a084 Mon Sep 17 00:00:00 2001 From: KIRA Ryouta Date: Wed, 1 Nov 2023 17:12:23 +0900 Subject: [PATCH 14/65] fix tuning root --- src/sfizz/Tuning.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/sfizz/Tuning.cpp b/src/sfizz/Tuning.cpp index 886f86fb2..f77b9ecd8 100644 --- a/src/sfizz/Tuning.cpp +++ b/src/sfizz/Tuning.cpp @@ -146,8 +146,7 @@ Tunings::KeyboardMapping Tuning::Impl::mappingFromParameters(int rootKey, float rootKey = std::max(0, rootKey - 12); #endif // fixed frequency of the root note - const double rootFrequency = tuningFrequency * std::exp2((rootKey - 69) / 12.0); - return Tunings::tuneNoteTo(rootKey, rootFrequency); + return Tunings::startScaleOnAndTuneNoteTo(rootKey, 69, tuningFrequency); } /// From 652ccb9059603d244b104568a9abf829f63f1d93 Mon Sep 17 00:00:00 2001 From: KIRA Ryouta Date: Tue, 24 Oct 2023 10:03:19 +0900 Subject: [PATCH 15/65] Fix a bug that st_read_s16 and st_read_f32 reterned the number of frames multiplied by the number of the WavPack file's channels. --- external/st_audiofile/src/st_audiofile.c | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/external/st_audiofile/src/st_audiofile.c b/external/st_audiofile/src/st_audiofile.c index b4cb7df47..b99602a4a 100644 --- a/external/st_audiofile/src/st_audiofile.c +++ b/external/st_audiofile/src/st_audiofile.c @@ -516,12 +516,13 @@ uint64_t st_read_s16(st_audio_file* af, int16_t* buffer, uint64_t count) if (!buf_i32) { return 0; } - count = channels * WavpackUnpackSamples(af->wv, buf_i32, (uint32_t)count); + count = WavpackUnpackSamples(af->wv, buf_i32, (uint32_t)count); + uint64_t buf_size = channels * count; if (af->cache.wv.mode & MODE_FLOAT) { - drwav_f32_to_s16((drwav_int16*)buffer, (float*)buf_i32, (size_t)count); + drwav_f32_to_s16((drwav_int16*)buffer, (float*)buf_i32, (size_t)buf_size); } else { int d = af->cache.wv.bitrate - 16; - for (uint64_t i = 0; i < count; i++) { + for (uint64_t i = 0; i < buf_size; i++) { buffer[i] = (int16_t)(buf_i32[i] >> d); } } @@ -566,15 +567,16 @@ uint64_t st_read_f32(st_audio_file* af, float* buffer, uint64_t count) if (!buf_i32) { return 0; } - count = channels * WavpackUnpackSamples(af->wv, buf_i32, (uint32_t)count); - if (!(af->cache.wv.mode & MODE_FLOAT)) { + count = WavpackUnpackSamples(af->wv, buf_i32, (uint32_t)count); + { + uint64_t buf_size = count * channels; if (af->cache.wv.bitrate < 32) { int d = 32 - af->cache.wv.bitrate; - for (uint64_t i = 0; i < count; i++) { + for (uint64_t i = 0; i < buf_size; i++) { buf_i32[i] <<= d; } } - drwav_s32_to_f32(buffer, (drwav_int32*)buf_i32, (size_t)count); + drwav_s32_to_f32(buffer, (drwav_int32*)buf_i32, (size_t)buf_size); } free(buf_i32); } From cf2d49ccfc3dc8488dbdb3dc60389f6a8319f79c Mon Sep 17 00:00:00 2001 From: redtide Date: Mon, 18 Dec 2023 18:38:16 +0100 Subject: [PATCH 16/65] README: Removed Cakewalk opcodes support badge Cakewalk opcodes have been merged with sfz v2 opcodes into the sfzformat upstream database --- README.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/README.md b/README.md index 8f2f29ba7..e2b06855c 100644 --- a/README.md +++ b/README.md @@ -2,12 +2,11 @@ [![build actions]](https://github.com/sfztools/sfizz/actions) [![build obs]](https://build.opensuse.org/package/show/home:sfztools:sfizz:develop/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 and JACK standalone client, please check [our website] for more details, or [our wiki] for further information. @@ -123,7 +122,6 @@ The sfizz library also uses in some subprojects: [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 [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 From be7c5264951bd0bbccc7dee102732bc7ceb67dc2 Mon Sep 17 00:00:00 2001 From: redtide Date: Mon, 18 Dec 2023 20:06:11 +0100 Subject: [PATCH 17/65] README: fixed broken links --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index e2b06855c..999ed7b02 100644 --- a/README.md +++ b/README.md @@ -119,8 +119,8 @@ The sfizz library also uses in some subprojects: [build actions]: https://github.com/sfztools/sfizz/actions/workflows/build.yml/badge.svg?branch=develop [build obs]: https://build.opensuse.org/projects/home:sfztools:sfizz:develop/packages/sfizz/badge.svg [OBS]: https://software.opensuse.org//download.html?project=home%3Asfztools%3Asfizz&package=sfizz -[SFZv1 Status Image]: https://sfz.tools/assets/img/sfizz/badge_sfz1.svg -[SFZv2 Status Image]: https://sfz.tools/assets/img/sfizz/badge_sfz2.svg +[SFZv1 Status Image]: https://sfz.tools/assets/img/sfizz/badge_sfzv1.svg +[SFZv2 Status Image]: https://sfz.tools/assets/img/sfizz/badge_sfzv2.svg [ARIA Status Image]: https://sfz.tools/assets/img/sfizz/badge_aria.svg [AppVeyor Build Status]: https://img.shields.io/appveyor/ci/sfztools/sfizz.svg?label=Windows&style=popout&logo=appveyor From 1c77b96d2f86376e68fd1196649d04347d8aac13 Mon Sep 17 00:00:00 2001 From: Paul Ferrand Date: Mon, 25 Dec 2023 21:46:57 +0100 Subject: [PATCH 18/65] Don't start loading jobs if hint_ram_based is set --- src/sfizz/FilePool.cpp | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/src/sfizz/FilePool.cpp b/src/sfizz/FilePool.cpp index 76fd69b7b..2afcd688c 100644 --- a/src/sfizz/FilePool.cpp +++ b/src/sfizz/FilePool.cpp @@ -434,15 +434,18 @@ sfz::FileDataHolder sfz::FilePool::getFilePromise(const std::shared_ptr& DBG("[sfizz] File not found in the preloaded files: " << fileId->filename()); return {}; } - QueuedFileData queuedData { fileId, &preloaded->second }; - if (!filesToLoad->try_push(queuedData)) { - DBG("[sfizz] Could not enqueue the file to load for " << fileId << " (queue capacity " << filesToLoad->capacity() << ")"); - return {}; - } - std::error_code ec; - dispatchBarrier.post(ec); - ASSERT(!ec); + if (!loadInRam) { + QueuedFileData queuedData { fileId, &preloaded->second }; + if (!filesToLoad->try_push(queuedData)) { + DBG("[sfizz] Could not enqueue the file to load for " << fileId << " (queue capacity " << filesToLoad->capacity() << ")"); + return {}; + } + + std::error_code ec; + dispatchBarrier.post(ec); + ASSERT(!ec); + } return { &preloaded->second }; } From 5a51e0b985bee3db501bf631f341aef81d5bf591 Mon Sep 17 00:00:00 2001 From: Paul Ferrand Date: Tue, 26 Dec 2023 11:33:04 +0100 Subject: [PATCH 19/65] Use the standard opcode value read for `hint_ram_based` --- src/sfizz/Defaults.cpp | 1 + src/sfizz/Defaults.h | 1 + src/sfizz/Synth.cpp | 8 +------- 3 files changed, 3 insertions(+), 7 deletions(-) diff --git a/src/sfizz/Defaults.cpp b/src/sfizz/Defaults.cpp index 5df7cb40c..4382cd36a 100644 --- a/src/sfizz/Defaults.cpp +++ b/src/sfizz/Defaults.cpp @@ -201,6 +201,7 @@ UInt32Spec stringsNumber { maxStrings, {0, maxStrings}, 0 }; BoolSpec sustainCancelsRelease { false, {0, 1}, kEnforceBounds }; FloatSpec loTimer { 0.0f, {0.0f, float_max}, 0 }; FloatSpec hiTimer { float_max, {0.0f, float_max}, 0 }; +BoolSpec ramBased { 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 21868260d..23c06cda5 100644 --- a/src/sfizz/Defaults.h +++ b/src/sfizz/Defaults.h @@ -319,6 +319,7 @@ namespace Default extern const OpcodeSpec sustainCancelsRelease; extern const OpcodeSpec loTimer; extern const OpcodeSpec hiTimer; + extern const OpcodeSpec ramBased; // Default/max count for objects constexpr int numEQs { 3 }; diff --git a/src/sfizz/Synth.cpp b/src/sfizz/Synth.cpp index 6b75d2773..a8b7dc855 100644 --- a/src/sfizz/Synth.cpp +++ b/src/sfizz/Synth.cpp @@ -441,13 +441,7 @@ void Synth::Impl::handleControlOpcodes(const std::vector& members) case hash("hint_ram_based"): { FilePool& filePool = resources_.getFilePool(); - if (member.value == "1") - filePool.setRamLoading(true); - else if (member.value == "0") - filePool.setRamLoading(false); - else - DBG("Unsupported value for hint_ram_based: " << member.value); - break; + filePool.setRamLoading(member.read(Default::ramBased)); } case hash("hint_stealing"): switch(hash(member.value)) { From 85832f2baf3b44bdcd11e5a4a79560a1c891bd8b Mon Sep 17 00:00:00 2001 From: Paul Ferrand Date: Tue, 26 Dec 2023 17:39:43 +0100 Subject: [PATCH 20/65] Allow simultaneous notes to choke properly --- src/sfizz/ADSREnvelope.cpp | 16 ++++++++------ tests/PolyphonyT.cpp | 44 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 53 insertions(+), 7 deletions(-) diff --git a/src/sfizz/ADSREnvelope.cpp b/src/sfizz/ADSREnvelope.cpp index 819434de1..21a19ed2f 100644 --- a/src/sfizz/ADSREnvelope.cpp +++ b/src/sfizz/ADSREnvelope.cpp @@ -97,13 +97,15 @@ void ADSREnvelope::getBlockInternal(absl::Span output) noexcept size_t count = 0; size_t size = output.size(); - if (shouldRelease && releaseDelay == 0) { - // release takes effect this frame - currentState = State::Release; - releaseDelay = -1; - } else if (shouldRelease && releaseDelay > 0) { - // prevent computing the segment further than release point - size = std::min(size, releaseDelay); + if (shouldRelease) { + if (releaseDelay > 0) { + // prevent computing the segment further than release point + size = std::min(size, releaseDelay); + } else if (releaseDelay == 0 && delay < 0) { + // release takes effect this frame + currentState = State::Release; + releaseDelay = -1; + } } Float previousValue; diff --git a/tests/PolyphonyT.cpp b/tests/PolyphonyT.cpp index 76738d7d1..b1b10eeea 100644 --- a/tests/PolyphonyT.cpp +++ b/tests/PolyphonyT.cpp @@ -588,3 +588,47 @@ TEST_CASE("[Polyphony] Choke same group and note if the region is switched off ( synth.renderBlock(buffer); REQUIRE( playingSamples(synth) == std::vector { "*sine" } ); } + +TEST_CASE("[Polyphony] A note coming at the same time as another can choke it") +{ + sfz::Synth synth; + sfz::AudioBuffer buffer { 2, static_cast(synth.getSamplesPerBlock()) }; + synth.loadSfzString(fs::current_path() / "tests/TestFiles/polyphony.sfz", R"( + key=69 off_by=2 off_mode=normal tune=-3200 ampeg_release=1 sample=snare.wav + key=81 group=2 sample=kick.wav + )"); + synth.noteOn(1, 69, 63); + synth.noteOn(1, 81, 127); + synth.renderBlock(buffer); + REQUIRE( activeSamples(synth) == std::vector { "snare.wav", "kick.wav" } ); + REQUIRE( playingSamples(synth) == std::vector { "kick.wav" } ); +} + +TEST_CASE("[Polyphony] A note coming one sample after another note can choke it") +{ + sfz::Synth synth; + sfz::AudioBuffer buffer { 2, static_cast(synth.getSamplesPerBlock()) }; + synth.loadSfzString(fs::current_path() / "tests/TestFiles/polyphony.sfz", R"( + key=69 off_by=2 off_mode=normal tune=-3200 ampeg_release=1 sample=snare.wav + key=81 group=2 sample=kick.wav + )"); + synth.noteOn(1, 69, 63); + synth.noteOn(2, 81, 127); + synth.renderBlock(buffer); + REQUIRE( activeSamples(synth) == std::vector { "snare.wav", "kick.wav" } ); + REQUIRE( playingSamples(synth) == std::vector { "kick.wav" } ); +} + +TEST_CASE("[Polyphony] A note coming one sample before another note cannot choke it") +{ + sfz::Synth synth; + sfz::AudioBuffer buffer { 2, static_cast(synth.getSamplesPerBlock()) }; + synth.loadSfzString(fs::current_path() / "tests/TestFiles/polyphony.sfz", R"( + key=69 off_by=2 off_mode=normal tune=-3200 ampeg_release=1 sample=snare.wav + key=81 group=2 sample=kick.wav + )"); + synth.noteOn(1, 81, 127); + synth.noteOn(2, 69, 63); + synth.renderBlock(buffer); + REQUIRE( playingSamples(synth) == std::vector { "kick.wav", "snare.wav" } ); +} From c4224b57924e121eb3a5b4d4b5f853c629b1a4ea Mon Sep 17 00:00:00 2001 From: essej Date: Tue, 29 Aug 2023 22:22:35 -0400 Subject: [PATCH 21/65] allow polyaftertouch extended CC (130) to respect note num, especially useful when used as trigger with on_lo/hicc130 opcodes, so that the key range can be used select appropriate regions independently. --- src/sfizz/Layer.cpp | 7 ++++++- src/sfizz/Layer.h | 3 ++- src/sfizz/Synth.cpp | 11 +++++------ src/sfizz/SynthPrivate.h | 6 ++++-- 4 files changed, 17 insertions(+), 10 deletions(-) diff --git a/src/sfizz/Layer.cpp b/src/sfizz/Layer.cpp index 9e06c081f..1e3a43d09 100644 --- a/src/sfizz/Layer.cpp +++ b/src/sfizz/Layer.cpp @@ -157,7 +157,7 @@ void Layer::updateCCState(int ccNumber, float ccValue) noexcept ccSwitched_.set(ccNumber, conditions->containsWithEnd(ccValue)); } -bool Layer::registerCC(int ccNumber, float ccValue, float randValue) noexcept +bool Layer::registerCC(int ccNumber, float ccValue, float randValue, int extendedArg) noexcept { const Region& region = region_; @@ -176,6 +176,11 @@ bool Layer::registerCC(int ccNumber, float ccValue, float randValue) noexcept if (!triggerRange->containsWithEnd(ccValue)) return false; + // only respect this polyAT trigger if the note number is one of ours + if (ccNumber == ExtendedCCs::polyphonicAftertouch && extendedArg >= 0 && !region.keyRange.containsWithEnd(extendedArg)) { + return false; + } + sequenceSwitched_ = ((sequenceCounter_++ % region.sequenceLength) == region.sequencePosition - 1); diff --git a/src/sfizz/Layer.h b/src/sfizz/Layer.h index e675ec44e..0afae1f68 100644 --- a/src/sfizz/Layer.h +++ b/src/sfizz/Layer.h @@ -96,10 +96,11 @@ struct Layer { * @param ccNumber * @param ccValue * @param randValue + * @param extendedArg is used for special extendedCCs (eg. polyaftertouch to represent note num, etc) * @return true if the region should trigger on this event * @return false otherwise */ - bool registerCC(int ccNumber, float ccValue, float randValue) noexcept; + bool registerCC(int ccNumber, float ccValue, float randValue, int extendedArg=-1) noexcept; /** * @brief Register a new pitch wheel event. * diff --git a/src/sfizz/Synth.cpp b/src/sfizz/Synth.cpp index a8b7dc855..d4caaaba1 100644 --- a/src/sfizz/Synth.cpp +++ b/src/sfizz/Synth.cpp @@ -1424,7 +1424,7 @@ void Synth::cc(int delay, int ccNumber, int ccValue) noexcept hdcc(delay, ccNumber, normalizedCC); } -void Synth::Impl::ccDispatch(int delay, int ccNumber, float value) noexcept +void Synth::Impl::ccDispatch(int delay, int ccNumber, float value, int extendedArg) noexcept { SisterVoiceRingBuilder ring; TriggerEvent triggerEvent { TriggerEventType::CC, ccNumber, value }; @@ -1447,7 +1447,7 @@ void Synth::Impl::ccDispatch(int delay, int ccNumber, float value) noexcept } } - if (layer->registerCC(ccNumber, value, randValue)) { + if (layer->registerCC(ccNumber, value, randValue, extendedArg)) { if (region.useTimerRange && ! voiceManager_.withinValidTimerRange(®ion, midiState.getInternalClock() + delay, sampleRate_)) continue; @@ -1469,7 +1469,7 @@ void Synth::automateHdcc(int delay, int ccNumber, float normValue) noexcept impl.performHdcc(delay, ccNumber, normValue, false); } -void Synth::Impl::performHdcc(int delay, int ccNumber, float normValue, bool asMidi) noexcept +void Synth::Impl::performHdcc(int delay, int ccNumber, float normValue, bool asMidi, int extendedArg) noexcept { ASSERT(ccNumber < config::numCCs); ASSERT(ccNumber >= 0); @@ -1497,7 +1497,7 @@ void Synth::Impl::performHdcc(int delay, int ccNumber, float normValue, bool asM for (auto& voice : voiceManager_) voice.registerCC(delay, ccNumber, normValue); - ccDispatch(delay, ccNumber, normValue); + ccDispatch(delay, ccNumber, normValue, extendedArg); midiState.ccEvent(delay, ccNumber, normValue); } @@ -1597,8 +1597,7 @@ void Synth::hdPolyAftertouch(int delay, int noteNumber, float normAftertouch) no for (auto& voice : impl.voiceManager_) voice.registerPolyAftertouch(delay, noteNumber, normAftertouch); - // Note information is lost on this CC - impl.performHdcc(delay, ExtendedCCs::polyphonicAftertouch, normAftertouch, false); + impl.performHdcc(delay, ExtendedCCs::polyphonicAftertouch, normAftertouch, false, noteNumber); } void Synth::tempo(int delay, float secondsPerBeat) noexcept diff --git a/src/sfizz/SynthPrivate.h b/src/sfizz/SynthPrivate.h index 3b051f0d2..c7687954d 100644 --- a/src/sfizz/SynthPrivate.h +++ b/src/sfizz/SynthPrivate.h @@ -150,8 +150,9 @@ struct Synth::Impl final: public Parser::Listener { * @param delay * @param ccNumber * @param value + * @param extendedArg used for some extendedCC (eg. polyaftertouch note num, etc) */ - void ccDispatch(int delay, int ccNumber, float value) noexcept; + void ccDispatch(int delay, int ccNumber, float value, int extendedArg=-1) noexcept; /** * @brief Start a voice for a specific region. @@ -239,8 +240,9 @@ struct Synth::Impl final: public Parser::Listener { * @param ccNumber The CC number * @param normValue The normalized value * @param asMidi Whether to process as a MIDI event + * @param extendedArg for some extendedCC (eg. polyaftertouch: note num, etc) */ - void performHdcc(int delay, int ccNumber, float normValue, bool asMidi) noexcept; + void performHdcc(int delay, int ccNumber, float normValue, bool asMidi, int extendedArg=-1) noexcept; /** * @brief Set the default value for a CC From 689200f8107efb195e93f6a8177c41dcc9860a78 Mon Sep 17 00:00:00 2001 From: Paul Ferrand Date: Sun, 15 Oct 2023 20:50:20 +0200 Subject: [PATCH 22/65] Add tests for note-aware polyaft - Test triggering with note information - Test choking - Fix logic: don't check that the new CC value changed on polyaftertouch cc130 messages (hopefully e-drums don't send multiple polyafts for the same note like some controllers do for the pedals...) --- src/sfizz/Layer.cpp | 2 +- tests/PolyphonyT.cpp | 25 +++++++++++++++++++++++++ tests/RegionTriggersT.cpp | 20 ++++++++++++++++++++ 3 files changed, 46 insertions(+), 1 deletion(-) diff --git a/src/sfizz/Layer.cpp b/src/sfizz/Layer.cpp index 1e3a43d09..183339c92 100644 --- a/src/sfizz/Layer.cpp +++ b/src/sfizz/Layer.cpp @@ -184,7 +184,7 @@ bool Layer::registerCC(int ccNumber, float ccValue, float randValue, int extende sequenceSwitched_ = ((sequenceCounter_++ % region.sequenceLength) == region.sequencePosition - 1); - if (isSwitchedOn() && (ccValue != midiState_.getCCValue(ccNumber))) + if (isSwitchedOn() && (ccNumber == ExtendedCCs::polyphonicAftertouch || ccValue != midiState_.getCCValue(ccNumber))) return true; } diff --git a/tests/PolyphonyT.cpp b/tests/PolyphonyT.cpp index b1b10eeea..4dec7381c 100644 --- a/tests/PolyphonyT.cpp +++ b/tests/PolyphonyT.cpp @@ -632,3 +632,28 @@ TEST_CASE("[Polyphony] A note coming one sample before another note cannot choke synth.renderBlock(buffer); REQUIRE( playingSamples(synth) == std::vector { "kick.wav", "snare.wav" } ); } + +TEST_CASE("[Polyphony] Choking on poly-aftertouch respects the note number") +{ + sfz::Synth synth; + sfz::AudioBuffer buffer { 2, static_cast(synth.getSamplesPerBlock()) }; + synth.loadSfzString(fs::current_path() / "tests/TestFiles/polyat_choke.sfz", R"( + key=55 group=1 off_by=2 sample=*saw + key=55 group=2 on_locc130=127 on_hicc130=127 trigger=release polyphony=1 sample=*sine + )"); + synth.noteOn(0, 55, 63 ); + synth.renderBlock(buffer); + REQUIRE( playingSamples(synth) == std::vector { "*saw" } ); + synth.hdPolyAftertouch(0, 54, 1.0f); + synth.renderBlock(buffer); + REQUIRE( playingSamples(synth) == std::vector { "*saw" } ); + synth.hdPolyAftertouch(0, 57, 1.0f); + synth.renderBlock(buffer); + REQUIRE( playingSamples(synth) == std::vector { "*saw" } ); + synth.hdPolyAftertouch(0, 55, 1.0f); + synth.renderBlock(buffer); + REQUIRE( playingSamples(synth) == std::vector { "*sine"} ); + synth.hdPolyAftertouch(0, 55, 0.0f); + synth.renderBlock(buffer); + REQUIRE( playingSamples(synth) == std::vector { "*sine"} ); +} diff --git a/tests/RegionTriggersT.cpp b/tests/RegionTriggersT.cpp index a53a700a7..ec6d0cd8f 100644 --- a/tests/RegionTriggersT.cpp +++ b/tests/RegionTriggersT.cpp @@ -550,3 +550,23 @@ TEST_CASE("[Triggers] hitimer (with group)") synth.noteOn(0, 41, 100); REQUIRE(playingSamples(synth) == std::vector { "snare.wav", "kick.wav", "kick.wav" }); } + +TEST_CASE("[Triggers] Respect poly-aftertouch note values when triggering on cc130") +{ + Synth synth; + synth.loadSfzString(fs::current_path() / "tests/TestFiles/poly_at_trigger.sfz", R"( + key=55 on_locc130=127 on_hicc130=127 sample=*saw + key=57 on_locc130=127 on_hicc130=127 sample=*sine + )"); + + synth.polyAftertouch(0, 54, 127); + REQUIRE(playingSamples(synth) == std::vector { }); + synth.polyAftertouch(0, 56, 127); + REQUIRE(playingSamples(synth) == std::vector { }); + synth.polyAftertouch(0, 58, 127); + REQUIRE(playingSamples(synth) == std::vector { }); + synth.polyAftertouch(0, 55, 127); + REQUIRE(playingSamples(synth) == std::vector { "*saw" }); + synth.polyAftertouch(10, 57, 127); + REQUIRE(playingSamples(synth) == std::vector { "*saw", "*sine" }); +} From 000b26334418836840f0e8917b2a025da19462cc Mon Sep 17 00:00:00 2001 From: Paul Ferrand Date: Tue, 26 Dec 2023 21:41:38 +0100 Subject: [PATCH 23/65] Don't send note offs when CCs are choking --- src/sfizz/Synth.cpp | 6 ++--- src/sfizz/SynthPrivate.h | 2 +- tests/PolyphonyT.cpp | 50 ++++++++++++++++++++-------------------- 3 files changed, 29 insertions(+), 29 deletions(-) diff --git a/src/sfizz/Synth.cpp b/src/sfizz/Synth.cpp index d4caaaba1..a484cb0c4 100644 --- a/src/sfizz/Synth.cpp +++ b/src/sfizz/Synth.cpp @@ -1309,12 +1309,12 @@ void Synth::Impl::startVoice(Layer* layer, int delay, const TriggerEvent& trigge ring.addVoiceToRing(selectedVoice); } -void Synth::Impl::checkOffGroups(const Region* region, int delay, int number) +void Synth::Impl::checkOffGroups(const Region* region, int delay, int number, bool chokedByCC) { for (auto& voice : voiceManager_) { if (voice.checkOffGroup(region, delay, number)) { const TriggerEvent& event = voice.getTriggerEvent(); - if (event.type == TriggerEventType::NoteOn) + if (event.type == TriggerEventType::NoteOn && !chokedByCC) noteOffDispatch(delay, event.number, event.value); } } @@ -1451,7 +1451,7 @@ void Synth::Impl::ccDispatch(int delay, int ccNumber, float value, int extendedA if (region.useTimerRange && ! voiceManager_.withinValidTimerRange(®ion, midiState.getInternalClock() + delay, sampleRate_)) continue; - checkOffGroups(®ion, delay, ccNumber); + checkOffGroups(®ion, delay, ccNumber, true); startVoice(layer, delay, triggerEvent, ring); } } diff --git a/src/sfizz/SynthPrivate.h b/src/sfizz/SynthPrivate.h index c7687954d..9989a6ca1 100644 --- a/src/sfizz/SynthPrivate.h +++ b/src/sfizz/SynthPrivate.h @@ -260,7 +260,7 @@ struct Synth::Impl final: public Parser::Listener { * @param delay * @param number */ - void checkOffGroups(const Region* region, int delay, int number); + void checkOffGroups(const Region* region, int delay, int number, bool chokedByCC = false); /** * @brief Resets the callback duration breakdown to 0 diff --git a/tests/PolyphonyT.cpp b/tests/PolyphonyT.cpp index 4dec7381c..e9eb6f4a3 100644 --- a/tests/PolyphonyT.cpp +++ b/tests/PolyphonyT.cpp @@ -589,6 +589,31 @@ TEST_CASE("[Polyphony] Choke same group and note if the region is switched off ( REQUIRE( playingSamples(synth) == std::vector { "*sine" } ); } +TEST_CASE("[Polyphony] Choking on poly-aftertouch respects the note number") +{ + sfz::Synth synth; + sfz::AudioBuffer buffer { 2, static_cast(synth.getSamplesPerBlock()) }; + synth.loadSfzString(fs::current_path() / "tests/TestFiles/polyat_choke.sfz", R"( + key=55 group=1 off_by=2 sample=*saw + key=55 group=2 on_locc130=127 on_hicc130=127 trigger=release sample=*sine + )"); + synth.noteOn(0, 55, 63 ); + synth.renderBlock(buffer); + REQUIRE( playingSamples(synth) == std::vector { "*saw" } ); + synth.hdPolyAftertouch(0, 54, 1.0f); + synth.renderBlock(buffer); + REQUIRE( playingSamples(synth) == std::vector { "*saw" } ); + synth.hdPolyAftertouch(0, 57, 1.0f); + synth.renderBlock(buffer); + REQUIRE( playingSamples(synth) == std::vector { "*saw" } ); + synth.hdPolyAftertouch(0, 55, 1.0f); + synth.renderBlock(buffer); + REQUIRE( playingSamples(synth) == std::vector { "*sine"} ); + synth.hdPolyAftertouch(0, 55, 0.0f); + synth.renderBlock(buffer); + REQUIRE( playingSamples(synth) == std::vector { "*sine"} ); +} + TEST_CASE("[Polyphony] A note coming at the same time as another can choke it") { sfz::Synth synth; @@ -632,28 +657,3 @@ TEST_CASE("[Polyphony] A note coming one sample before another note cannot choke synth.renderBlock(buffer); REQUIRE( playingSamples(synth) == std::vector { "kick.wav", "snare.wav" } ); } - -TEST_CASE("[Polyphony] Choking on poly-aftertouch respects the note number") -{ - sfz::Synth synth; - sfz::AudioBuffer buffer { 2, static_cast(synth.getSamplesPerBlock()) }; - synth.loadSfzString(fs::current_path() / "tests/TestFiles/polyat_choke.sfz", R"( - key=55 group=1 off_by=2 sample=*saw - key=55 group=2 on_locc130=127 on_hicc130=127 trigger=release polyphony=1 sample=*sine - )"); - synth.noteOn(0, 55, 63 ); - synth.renderBlock(buffer); - REQUIRE( playingSamples(synth) == std::vector { "*saw" } ); - synth.hdPolyAftertouch(0, 54, 1.0f); - synth.renderBlock(buffer); - REQUIRE( playingSamples(synth) == std::vector { "*saw" } ); - synth.hdPolyAftertouch(0, 57, 1.0f); - synth.renderBlock(buffer); - REQUIRE( playingSamples(synth) == std::vector { "*saw" } ); - synth.hdPolyAftertouch(0, 55, 1.0f); - synth.renderBlock(buffer); - REQUIRE( playingSamples(synth) == std::vector { "*sine"} ); - synth.hdPolyAftertouch(0, 55, 0.0f); - synth.renderBlock(buffer); - REQUIRE( playingSamples(synth) == std::vector { "*sine"} ); -} From adefb7d60e7d183a24317d33736828dfcd91a734 Mon Sep 17 00:00:00 2001 From: Python Blue Date: Thu, 8 Jun 2023 22:19:09 -0400 Subject: [PATCH 24/65] Add support for `curvecc` opcodes on v1 EGs --- external/st_audiofile/thirdparty/wavpack | 2 +- src/sfizz/ADSREnvelope.cpp | 14 +- src/sfizz/ADSREnvelope.h | 6 +- src/sfizz/EGDescription.h | 60 ++++-- src/sfizz/Region.cpp | 63 +++++- src/sfizz/SynthMessaging.cpp | 252 +++++++++++++++++++++-- src/sfizz/Voice.cpp | 6 +- tests/EGDescriptionT.cpp | 183 ++++++++-------- 8 files changed, 442 insertions(+), 144 deletions(-) diff --git a/external/st_audiofile/thirdparty/wavpack b/external/st_audiofile/thirdparty/wavpack index 78e75a852..36b08dbcb 160000 --- a/external/st_audiofile/thirdparty/wavpack +++ b/external/st_audiofile/thirdparty/wavpack @@ -1 +1 @@ -Subproject commit 78e75a8527fffe2f87cdd56d5989ff01c0723ef7 +Subproject commit 36b08dbcb1de136e9ae477e9f1e2b57c958fff18 diff --git a/src/sfizz/ADSREnvelope.cpp b/src/sfizz/ADSREnvelope.cpp index 21a19ed2f..479075603 100644 --- a/src/sfizz/ADSREnvelope.cpp +++ b/src/sfizz/ADSREnvelope.cpp @@ -58,13 +58,13 @@ void ADSREnvelope::reset(const EGDescription& desc, const Region& region, int de 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); + this->delay = delay + secondsToSamples(desc_->getDelay(midiState_, curveSet_, triggerVelocity_, delay)); + this->attackStep = secondsToLinRate(desc_->getAttack(midiState_, curveSet_, triggerVelocity_, delay)); + this->decayRate = secondsToExpRate(desc_->getDecay(midiState_, curveSet_, triggerVelocity_, delay)); + this->releaseRate = secondsToExpRate(desc_->getRelease(midiState_, curveSet_, triggerVelocity_, delay)); + this->hold = secondsToSamples(desc_->getHold(midiState_, curveSet_, triggerVelocity_, delay)); + this->sustain = clamp(desc_->getSustain(midiState_, curveSet_, triggerVelocity_, delay), 0.0f, 1.0f); + this->start = clamp(desc_->getStart(midiState_, curveSet_, triggerVelocity_, delay), 0.0f, 1.0f); sustainThreshold = this->sustain + config::virtuallyZero; } diff --git a/src/sfizz/ADSREnvelope.h b/src/sfizz/ADSREnvelope.h index 17007d95d..33a4d1faa 100644 --- a/src/sfizz/ADSREnvelope.h +++ b/src/sfizz/ADSREnvelope.h @@ -14,12 +14,13 @@ namespace sfz { * @brief Describe an attack/delay/sustain/release envelope that can * produce its coefficient in a blockwise manner for SIMD-type operations. */ + class ADSREnvelope { public: using Float = float; - ADSREnvelope(const MidiState& state) - : midiState_(state) {} + ADSREnvelope(const MidiState& state, CurveSet& curveSet) + : midiState_(state), curveSet_(curveSet) {} /** * @brief Resets the ADSR envelope given a Region, the current midi state, and a delay and * trigger velocity @@ -104,6 +105,7 @@ class ADSREnvelope { Float currentValue { 0.0 }; const EGDescription* desc_ { nullptr }; const MidiState& midiState_; + CurveSet& curveSet_; float triggerVelocity_ { 0.0f }; bool dynamic_ { false }; int delay { 0 }; diff --git a/src/sfizz/EGDescription.h b/src/sfizz/EGDescription.h index fce5fc757..0337a3059 100644 --- a/src/sfizz/EGDescription.h +++ b/src/sfizz/EGDescription.h @@ -32,6 +32,8 @@ #include "utility/LeakDetector.h" #include +#include "Curve.h" + namespace sfz { /** * @brief A description for an SFZ envelope generator, with its envelope parameters @@ -66,13 +68,20 @@ struct EGDescription { float vel2sustain { Default::egPercentMod }; float vel2depth { Default::egVel2Depth }; - CCMap ccAttack; - CCMap ccDecay; - CCMap ccDelay; - CCMap ccHold; - CCMap ccRelease; - CCMap ccStart; - CCMap ccSustain; + CCMap> ccAttack { ModifierCurvePair{ Default::egTime, Default::curveCC } }; + CCMap> ccDecay { ModifierCurvePair{ Default::egTime, Default::curveCC } }; + CCMap> ccDelay { ModifierCurvePair{ Default::egTime, Default::curveCC } }; + CCMap> ccHold { ModifierCurvePair{ Default::egTime, Default::curveCC } }; + CCMap> ccRelease { ModifierCurvePair{ Default::egTime, Default::curveCC } }; + CCMap> ccStart { ModifierCurvePair{ Default::egPercentMod, Default::curveCC } }; + CCMap> ccSustain { ModifierCurvePair{ Default:: egPercentMod, Default::curveCC } }; + //CCMap ccAttack; + //CCMap ccDecay; + //CCMap ccDelay; + //CCMap ccHold; + //CCMap ccRelease; + //CCMap ccStart; + //CCMap ccSustain; bool dynamic { false }; /** @@ -82,12 +91,13 @@ struct EGDescription { * @param velocity * @return float */ - float getAttack(const MidiState& state, float velocity, int delay = 0) const noexcept + float getAttack(const MidiState& state, CurveSet& curveSet, 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.getCCValueAt(mod.cc, delay) * mod.data; + const auto& curve = curveSet.getCurve(mod.data.curve); + returnedValue += curve.evalNormalized(state.getCCValueAt(mod.cc, delay)) * mod.data.modifier; } return returnedValue; } @@ -98,12 +108,13 @@ struct EGDescription { * @param velocity * @return float */ - float getDecay(const MidiState& state, float velocity, int delay = 0) const noexcept + float getDecay(const MidiState& state, CurveSet& curveSet, 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.getCCValueAt(mod.cc, delay) * mod.data; + const auto& curve = curveSet.getCurve(mod.data.curve); + returnedValue += curve.evalNormalized(state.getCCValueAt(mod.cc, delay)) * mod.data.modifier; } return returnedValue; } @@ -114,12 +125,13 @@ struct EGDescription { * @param velocity * @return float */ - float getDelay(const MidiState& state, float velocity, int delay = 0) const noexcept + float getDelay(const MidiState& state, CurveSet& curveSet, float velocity, int delay = 0) const noexcept { ASSERT(velocity >= 0.0f && velocity <= 1.0f); float returnedValue { this->delay + velocity * vel2delay }; for (auto& mod: ccDelay) { - returnedValue += state.getCCValueAt(mod.cc, delay) * mod.data; + const auto& curve = curveSet.getCurve(mod.data.curve); + returnedValue += curve.evalNormalized(state.getCCValueAt(mod.cc, delay)) * mod.data.modifier; } return returnedValue; } @@ -130,12 +142,13 @@ struct EGDescription { * @param velocity * @return float */ - float getHold(const MidiState& state, float velocity, int delay = 0) const noexcept + float getHold(const MidiState& state, CurveSet& curveSet, 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.getCCValueAt(mod.cc, delay) * mod.data; + const auto& curve = curveSet.getCurve(mod.data.curve); + returnedValue += curve.evalNormalized(state.getCCValueAt(mod.cc, delay)) * mod.data.modifier; } return returnedValue; } @@ -146,12 +159,13 @@ struct EGDescription { * @param velocity * @return float */ - float getRelease(const MidiState& state, float velocity, int delay = 0) const noexcept + float getRelease(const MidiState& state, CurveSet& curveSet, 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.getCCValueAt(mod.cc, delay) * mod.data; + const auto& curve = curveSet.getCurve(mod.data.curve); + returnedValue += curve.evalNormalized(state.getCCValueAt(mod.cc, delay)) * mod.data.modifier; } return returnedValue; } @@ -162,12 +176,13 @@ struct EGDescription { * @param velocity * @return float */ - float getStart(const MidiState& state, float velocity, int delay = 0) const noexcept + float getStart(const MidiState& state, CurveSet& curveSet, float velocity, int delay = 0) const noexcept { - UNUSED(velocity); + ASSERT(velocity >= 0.0f && velocity <= 1.0f); float returnedValue { start }; for (auto& mod: ccStart) { - returnedValue += state.getCCValueAt(mod.cc, delay) * mod.data; + const auto& curve = curveSet.getCurve(mod.data.curve); + returnedValue += curve.evalNormalized(state.getCCValueAt(mod.cc, delay)) * mod.data.modifier; } return returnedValue; } @@ -178,12 +193,13 @@ struct EGDescription { * @param velocity * @return float */ - float getSustain(const MidiState& state, float velocity, int delay = 0) const noexcept + float getSustain(const MidiState& state, CurveSet& curveSet, 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.getCCValueAt(mod.cc, delay) * mod.data; + const auto& curve = curveSet.getCurve(mod.data.curve); + returnedValue += curve.evalNormalized(state.getCCValueAt(mod.cc, delay)) * mod.data.modifier; } return returnedValue; } diff --git a/src/sfizz/Region.cpp b/src/sfizz/Region.cpp index 8c260e22a..8fe46f57a 100644 --- a/src/sfizz/Region.cpp +++ b/src/sfizz/Region.cpp @@ -1110,49 +1110,98 @@ bool sfz::Region::parseEGOpcode(const Opcode& opcode, EGDescription& eg) if (opcode.parameters.back() >= config::numCCs) return false; - eg.ccAttack[opcode.parameters.back()] = opcode.read(Default::egTimeMod); + eg.ccAttack[opcode.parameters.back()].modifier = opcode.read(Default::egTimeMod); + + break; + case_any_eg("attack_curvecc&"): // also attackcc& + if (opcode.parameters.back() >= config::numCCs) + return false; + + eg.ccAttack[opcode.parameters.back()].curve = opcode.read(Default::curveCC); break; case_any_eg("decay_oncc&"): // also decaycc& if (opcode.parameters.back() >= config::numCCs) return false; - eg.ccDecay[opcode.parameters.back()] = opcode.read(Default::egTimeMod); + eg.ccDecay[opcode.parameters.back()].modifier = opcode.read(Default::egTimeMod); + + break; + case_any_eg("decay_curvecc&"): // also attackcc& + if (opcode.parameters.back() >= config::numCCs) + return false; + + eg.ccDecay[opcode.parameters.back()].curve = opcode.read(Default::curveCC); break; case_any_eg("delay_oncc&"): // also delaycc& if (opcode.parameters.back() >= config::numCCs) return false; - eg.ccDelay[opcode.parameters.back()] = opcode.read(Default::egTimeMod); + eg.ccDelay[opcode.parameters.back()].modifier = opcode.read(Default::egTimeMod); + + break; + case_any_eg("delay_curvecc&"): // also attackcc& + if (opcode.parameters.back() >= config::numCCs) + return false; + + eg.ccDelay[opcode.parameters.back()].curve = opcode.read(Default::curveCC); break; case_any_eg("hold_oncc&"): // also holdcc& if (opcode.parameters.back() >= config::numCCs) return false; - eg.ccHold[opcode.parameters.back()] = opcode.read(Default::egTimeMod); + eg.ccHold[opcode.parameters.back()].modifier = opcode.read(Default::egTimeMod); + + break; + case_any_eg("hold_curvecc&"): // also attackcc& + if (opcode.parameters.back() >= config::numCCs) + return false; + + eg.ccHold[opcode.parameters.back()].curve = opcode.read(Default::curveCC); break; case_any_eg("release_oncc&"): // also releasecc& if (opcode.parameters.back() >= config::numCCs) return false; - eg.ccRelease[opcode.parameters.back()] = opcode.read(Default::egTimeMod); + eg.ccRelease[opcode.parameters.back()].modifier = opcode.read(Default::egTimeMod); + + break; + case_any_eg("release_curvecc&"): // also attackcc& + if (opcode.parameters.back() >= config::numCCs) + return false; + + eg.ccRelease[opcode.parameters.back()].curve = opcode.read(Default::curveCC); break; case_any_eg("start_oncc&"): // also startcc& if (opcode.parameters.back() >= config::numCCs) return false; - eg.ccStart[opcode.parameters.back()] = opcode.read(Default::egPercentMod); + eg.ccStart[opcode.parameters.back()].modifier = opcode.read(Default::egPercentMod); + + break; + case_any_eg("start_curvecc&"): // also startcc& + if (opcode.parameters.back() >= config::numCCs) + return false; + + eg.ccStart[opcode.parameters.back()].curve = opcode.read(Default::curveCC); break; case_any_eg("sustain_oncc&"): // also sustaincc& if (opcode.parameters.back() >= config::numCCs) return false; - eg.ccSustain[opcode.parameters.back()] = opcode.read(Default::egPercentMod); + eg.ccSustain[opcode.parameters.back()].modifier = opcode.read(Default::egPercentMod); + + break; + case_any_eg("sustain_curvecc&"): // also attackcc& + if (opcode.parameters.back() >= config::numCCs) + return false; + + eg.ccSustain[opcode.parameters.back()].curve = opcode.read(Default::curveCC); break; diff --git a/src/sfizz/SynthMessaging.cpp b/src/sfizz/SynthMessaging.cpp index f065a4a70..889fba52d 100644 --- a/src/sfizz/SynthMessaging.cpp +++ b/src/sfizz/SynthMessaging.cpp @@ -1222,6 +1222,90 @@ void sfz::Synth::dispatchMessage(Client& client, int delay, const char* path, co } } break; + MATCH("/region&/pitcheg_attack_cc&", "") { + GET_REGION_OR_BREAK(indices[0]) + const auto& cc = region.pitchEG->ccAttack.getWithDefault(indices[1]); + client.receive<'f'>(delay, path, cc.modifier); + } break; + + MATCH("/region&/pitcheg_decay_cc&", "") { + GET_REGION_OR_BREAK(indices[0]) + const auto& cc = region.pitchEG->ccDecay.getWithDefault(indices[1]); + client.receive<'f'>(delay, path, cc.modifier); + } break; + + MATCH("/region&/pitcheg_delay_cc&", "") { + GET_REGION_OR_BREAK(indices[0]) + const auto& cc = region.pitchEG->ccDelay.getWithDefault(indices[1]); + client.receive<'f'>(delay, path, cc.modifier); + } break; + + MATCH("/region&/pitcheg_hold_cc&", "") { + GET_REGION_OR_BREAK(indices[0]) + const auto& cc = region.pitchEG->ccHold.getWithDefault(indices[1]); + client.receive<'f'>(delay, path, cc.modifier); + } break; + + MATCH("/region&/pitcheg_release_cc&", "") { + GET_REGION_OR_BREAK(indices[0]) + const auto& cc = region.pitchEG->ccRelease.getWithDefault(indices[1]); + client.receive<'f'>(delay, path, cc.modifier); + } break; + + MATCH("/region&/pitcheg_start_cc&", "") { + GET_REGION_OR_BREAK(indices[0]) + const auto& cc = region.pitchEG->ccStart.getWithDefault(indices[1]); + client.receive<'f'>(delay, path, cc.modifier * 100.0f); + } break; + + MATCH("/region&/pitcheg_sustain_cc&", "") { + GET_REGION_OR_BREAK(indices[0]) + const auto& cc = region.pitchEG->ccSustain.getWithDefault(indices[1]); + client.receive<'f'>(delay, path, cc.modifier * 100.0f); + } break; + + MATCH("/region&/pitcheg_attack_curvecc&", "") { + GET_REGION_OR_BREAK(indices[0]) + const auto& cc = region.pitchEG->ccAttack.getWithDefault(indices[1]); + client.receive<'f'>(delay, path, cc.curve); + } break; + + MATCH("/region&/pitcheg_decay_curvecc&", "") { + GET_REGION_OR_BREAK(indices[0]) + const auto& cc = region.pitchEG->ccDecay.getWithDefault(indices[1]); + client.receive<'f'>(delay, path, cc.curve); + } break; + + MATCH("/region&/pitcheg_delay_curvecc&", "") { + GET_REGION_OR_BREAK(indices[0]) + const auto& cc = region.pitchEG->ccDelay.getWithDefault(indices[1]); + client.receive<'f'>(delay, path, cc.curve); + } break; + + MATCH("/region&/pitcheg_hold_curvecc&", "") { + GET_REGION_OR_BREAK(indices[0]) + const auto& cc = region.pitchEG->ccHold.getWithDefault(indices[1]); + client.receive<'f'>(delay, path, cc.curve); + } break; + + MATCH("/region&/pitcheg_release_curvecc&", "") { + GET_REGION_OR_BREAK(indices[0]) + const auto& cc = region.pitchEG->ccRelease.getWithDefault(indices[1]); + client.receive<'f'>(delay, path, cc.curve); + } break; + + MATCH("/region&/pitcheg_start_curvecc&", "") { + GET_REGION_OR_BREAK(indices[0]) + const auto& cc = region.pitchEG->ccStart.getWithDefault(indices[1]); + client.receive<'f'>(delay, path, cc.curve); + } break; + + MATCH("/region&/pitcheg_sustain_curvecc&", "") { + GET_REGION_OR_BREAK(indices[0]) + const auto& cc = region.pitchEG->ccSustain.getWithDefault(indices[1]); + client.receive<'f'>(delay, path, cc.curve); + } break; + MATCH("/region&/note_polyphony", "") { GET_REGION_OR_BREAK(indices[0]) if (region.notePolyphony) { @@ -1338,44 +1422,86 @@ void sfz::Synth::dispatchMessage(Client& client, int delay, const char* path, co MATCH("/region&/ampeg_attack_cc&", "") { GET_REGION_OR_BREAK(indices[0]) - float value = region.amplitudeEG.ccAttack.getWithDefault(indices[1]); - client.receive<'f'>(delay, path, value); + const auto& cc = region.amplitudeEG.ccAttack.getWithDefault(indices[1]); + client.receive<'f'>(delay, path, cc.modifier); } break; MATCH("/region&/ampeg_decay_cc&", "") { GET_REGION_OR_BREAK(indices[0]) - float value = region.amplitudeEG.ccDecay.getWithDefault(indices[1]); - client.receive<'f'>(delay, path, value); + const auto& cc = region.amplitudeEG.ccDecay.getWithDefault(indices[1]); + client.receive<'f'>(delay, path, cc.modifier); } break; MATCH("/region&/ampeg_delay_cc&", "") { GET_REGION_OR_BREAK(indices[0]) - float value = region.amplitudeEG.ccDelay.getWithDefault(indices[1]); - client.receive<'f'>(delay, path, value); + const auto& cc = region.amplitudeEG.ccDelay.getWithDefault(indices[1]); + client.receive<'f'>(delay, path, cc.modifier); } break; MATCH("/region&/ampeg_hold_cc&", "") { GET_REGION_OR_BREAK(indices[0]) - float value = region.amplitudeEG.ccHold.getWithDefault(indices[1]); - client.receive<'f'>(delay, path, value); + const auto& cc = region.amplitudeEG.ccHold.getWithDefault(indices[1]); + client.receive<'f'>(delay, path, cc.modifier); } break; MATCH("/region&/ampeg_release_cc&", "") { GET_REGION_OR_BREAK(indices[0]) - float value = region.amplitudeEG.ccRelease.getWithDefault(indices[1]); - client.receive<'f'>(delay, path, value); + const auto& cc = region.amplitudeEG.ccRelease.getWithDefault(indices[1]); + client.receive<'f'>(delay, path, cc.modifier); } break; MATCH("/region&/ampeg_start_cc&", "") { GET_REGION_OR_BREAK(indices[0]) - float value = region.amplitudeEG.ccStart.getWithDefault(indices[1]); - client.receive<'f'>(delay, path, value * 100.0f); + const auto& cc = region.amplitudeEG.ccStart.getWithDefault(indices[1]); + client.receive<'f'>(delay, path, cc.modifier * 100.0f); } break; MATCH("/region&/ampeg_sustain_cc&", "") { GET_REGION_OR_BREAK(indices[0]) - float value = region.amplitudeEG.ccSustain.getWithDefault(indices[1]); - client.receive<'f'>(delay, path, value * 100.0f); + const auto& cc = region.amplitudeEG.ccSustain.getWithDefault(indices[1]); + client.receive<'f'>(delay, path, cc.modifier * 100.0f); + } break; + + MATCH("/region&/ampeg_attack_curvecc&", "") { + GET_REGION_OR_BREAK(indices[0]) + const auto& cc = region.amplitudeEG.ccAttack.getWithDefault(indices[1]); + client.receive<'f'>(delay, path, cc.curve); + } break; + + MATCH("/region&/ampeg_decay_curvecc&", "") { + GET_REGION_OR_BREAK(indices[0]) + const auto& cc = region.amplitudeEG.ccDecay.getWithDefault(indices[1]); + client.receive<'f'>(delay, path, cc.curve); + } break; + + MATCH("/region&/ampeg_delay_curvecc&", "") { + GET_REGION_OR_BREAK(indices[0]) + const auto& cc = region.amplitudeEG.ccDelay.getWithDefault(indices[1]); + client.receive<'f'>(delay, path, cc.curve); + } break; + + MATCH("/region&/ampeg_hold_curvecc&", "") { + GET_REGION_OR_BREAK(indices[0]) + const auto& cc = region.amplitudeEG.ccHold.getWithDefault(indices[1]); + client.receive<'f'>(delay, path, cc.curve); + } break; + + MATCH("/region&/ampeg_release_curvecc&", "") { + GET_REGION_OR_BREAK(indices[0]) + const auto& cc = region.amplitudeEG.ccRelease.getWithDefault(indices[1]); + client.receive<'f'>(delay, path, cc.curve); + } break; + + MATCH("/region&/ampeg_start_curvecc&", "") { + GET_REGION_OR_BREAK(indices[0]) + const auto& cc = region.amplitudeEG.ccStart.getWithDefault(indices[1]); + client.receive<'f'>(delay, path, cc.curve); + } break; + + MATCH("/region&/ampeg_sustain_curvecc&", "") { + GET_REGION_OR_BREAK(indices[0]) + const auto& cc = region.amplitudeEG.ccSustain.getWithDefault(indices[1]); + client.receive<'f'>(delay, path, cc.curve); } break; MATCH("/region&/filter&/cutoff", "") { @@ -1495,6 +1621,104 @@ void sfz::Synth::dispatchMessage(Client& client, int delay, const char* path, co } } break; + MATCH("/region&/filter&/fileg_attack_cc&", "") { + GET_REGION_OR_BREAK(indices[0]) + GET_FILTER_OR_BREAK(indices[1]) + const auto& cc = region.filterEG->ccAttack.getWithDefault(indices[2]); + client.receive<'f'>(delay, path, cc.modifier); + } break; + + MATCH("/region&/filter&/fileg_decay_cc&", "") { + GET_REGION_OR_BREAK(indices[0]) + GET_FILTER_OR_BREAK(indices[1]) + const auto& cc = region.filterEG->ccDecay.getWithDefault(indices[2]); + client.receive<'f'>(delay, path, cc.modifier); + } break; + + MATCH("/region&/filter&/fileg_delay_cc&", "") { + GET_REGION_OR_BREAK(indices[0]) + GET_FILTER_OR_BREAK(indices[1]) + const auto& cc = region.filterEG->ccDelay.getWithDefault(indices[2]); + client.receive<'f'>(delay, path, cc.modifier); + } break; + + MATCH("/region&/filter&/fileg_hold_cc&", "") { + GET_REGION_OR_BREAK(indices[0]) + GET_FILTER_OR_BREAK(indices[1]) + const auto& cc = region.filterEG->ccHold.getWithDefault(indices[2]); + client.receive<'f'>(delay, path, cc.modifier); + } break; + + MATCH("/region&/filter&/fileg_release_cc&", "") { + GET_REGION_OR_BREAK(indices[0]) + GET_FILTER_OR_BREAK(indices[1]) + const auto& cc = region.filterEG->ccRelease.getWithDefault(indices[2]); + client.receive<'f'>(delay, path, cc.modifier); + } break; + + MATCH("/region&/filter&/fileg_start_cc&", "") { + GET_REGION_OR_BREAK(indices[0]) + GET_FILTER_OR_BREAK(indices[1]) + const auto& cc = region.filterEG->ccStart.getWithDefault(indices[2]); + client.receive<'f'>(delay, path, cc.modifier * 100.0f); + } break; + + MATCH("/region&/filter&/fileg_sustain_cc&", "") { + GET_REGION_OR_BREAK(indices[0]) + GET_FILTER_OR_BREAK(indices[1]) + const auto& cc = region.filterEG->ccSustain.getWithDefault(indices[2]); + client.receive<'f'>(delay, path, cc.modifier * 100.0f); + } break; + + MATCH("/region&/filter&/fileg_attack_curvecc&", "") { + GET_REGION_OR_BREAK(indices[0]) + GET_FILTER_OR_BREAK(indices[1]) + const auto& cc = region.filterEG->ccAttack.getWithDefault(indices[2]); + client.receive<'f'>(delay, path, cc.curve); + } break; + + MATCH("/region&/filter&/fileg_decay_curvecc&", "") { + GET_REGION_OR_BREAK(indices[0]) + GET_FILTER_OR_BREAK(indices[1]) + const auto& cc = region.filterEG->ccDecay.getWithDefault(indices[2]); + client.receive<'f'>(delay, path, cc.curve); + } break; + + MATCH("/region&/filter&/fileg_delay_curvecc&", "") { + GET_REGION_OR_BREAK(indices[0]) + GET_FILTER_OR_BREAK(indices[1]) + const auto& cc = region.filterEG->ccDelay.getWithDefault(indices[2]); + client.receive<'f'>(delay, path, cc.curve); + } break; + + MATCH("/region&/filter&/fileg_hold_curvecc&", "") { + GET_REGION_OR_BREAK(indices[0]) + GET_FILTER_OR_BREAK(indices[1]) + const auto& cc = region.filterEG->ccHold.getWithDefault(indices[2]); + client.receive<'f'>(delay, path, cc.curve); + } break; + + MATCH("/region&/filter&/fileg_release_curvecc&", "") { + GET_REGION_OR_BREAK(indices[0]) + GET_FILTER_OR_BREAK(indices[1]) + const auto& cc = region.filterEG->ccRelease.getWithDefault(indices[2]); + client.receive<'f'>(delay, path, cc.curve); + } break; + + MATCH("/region&/filter&/fileg_start_curvecc&", "") { + GET_REGION_OR_BREAK(indices[0]) + GET_FILTER_OR_BREAK(indices[1]) + const auto& cc = region.filterEG->ccStart.getWithDefault(indices[2]); + client.receive<'f'>(delay, path, cc.curve); + } break; + + MATCH("/region&/filter&/fileg_sustain_curvecc&", "") { + GET_REGION_OR_BREAK(indices[0]) + GET_FILTER_OR_BREAK(indices[1]) + const auto& cc = region.filterEG->ccSustain.getWithDefault(indices[2]); + client.receive<'f'>(delay, path, cc.curve); + } break; + MATCH("/region&/eq&/gain", "") { GET_REGION_OR_BREAK(indices[0]) GET_EQ_OR_BREAK(indices[1]) diff --git a/src/sfizz/Voice.cpp b/src/sfizz/Voice.cpp index 3315fb694..63db7cdbe 100644 --- a/src/sfizz/Voice.cpp +++ b/src/sfizz/Voice.cpp @@ -275,7 +275,7 @@ struct Voice::Impl std::unique_ptr lfoPitch_; std::unique_ptr lfoFilter_; - ADSREnvelope egAmplitude_ { resources_.getMidiState() }; + ADSREnvelope egAmplitude_ { resources_.getMidiState(), resources_.getCurves() }; std::unique_ptr egPitch_; std::unique_ptr egFilter_; @@ -1853,7 +1853,7 @@ void Voice::setPitchEGEnabledPerVoice(bool havePitchEG) { Impl& impl = *impl_; if (havePitchEG) - impl.egPitch_.reset(new ADSREnvelope(impl.resources_.getMidiState())); + impl.egPitch_.reset(new ADSREnvelope(impl.resources_.getMidiState(), impl.resources_.getCurves())); else impl.egPitch_.reset(); } @@ -1862,7 +1862,7 @@ void Voice::setFilterEGEnabledPerVoice(bool haveFilterEG) { Impl& impl = *impl_; if (haveFilterEG) - impl.egFilter_.reset(new ADSREnvelope(impl.resources_.getMidiState())); + impl.egFilter_.reset(new ADSREnvelope(impl.resources_.getMidiState(), impl.resources_.getCurves())); else impl.egFilter_.reset(); } diff --git a/tests/EGDescriptionT.cpp b/tests/EGDescriptionT.cpp index 5813ce972..b28abd708 100644 --- a/tests/EGDescriptionT.cpp +++ b/tests/EGDescriptionT.cpp @@ -15,158 +15,165 @@ TEST_CASE("[EGDescription] Attack range") { sfz::EGDescription eg; sfz::MidiState state; + sfz::CurveSet curveSet; eg.attack = 1; eg.vel2attack = -1.27f; - eg.ccAttack[63] = 1.27f; - REQUIRE(eg.getAttack(state, 0_norm) == 1.0f); - //REQUIRE(eg.getAttack(state, 127_norm) == 0.0f); + eg.ccAttack[63].modifier = 1.27f; + REQUIRE(eg.getAttack(state, curveSet, 0_norm) == 1.0f); + //REQUIRE(eg.getAttack(state, curveSet, 127_norm) == 0.0f); state.ccEvent(0, 63, 127_norm); - REQUIRE(eg.getAttack(state, 127_norm) == 1.0f); - REQUIRE(eg.getAttack(state, 0_norm) == 2.27f); - //eg.ccAttack[63] = 127.0f; - //REQUIRE(eg.getAttack(state, 0_norm) == 100.0f); - eg.ccAttack[63] = 1.27f; - eg.ccAttack[65] = 1.0f; - REQUIRE(eg.getAttack(state, 0_norm) == 2.27f); - REQUIRE(eg.getAttack(state, 127_norm) == 1.0f); + REQUIRE(eg.getAttack(state, curveSet, 127_norm) == 1.0f); + REQUIRE(eg.getAttack(state, curveSet, 0_norm) == 2.27f); + //eg.ccAttack[63].modifier = 127.0f; + //REQUIRE(eg.getAttack(state, curveSet, 0_norm) == 100.0f); + eg.ccAttack[63].modifier = 1.27f; + eg.ccAttack[65].modifier = 1.0f; + REQUIRE(eg.getAttack(state, curveSet, 0_norm) == 2.27f); + REQUIRE(eg.getAttack(state, curveSet, 127_norm) == 1.0f); state.ccEvent(0, 65, 127_norm); - REQUIRE(eg.getAttack(state, 0_norm) == 3.27f); - REQUIRE(eg.getAttack(state, 127_norm) == 2.0f); + REQUIRE(eg.getAttack(state, curveSet, 0_norm) == 3.27f); + REQUIRE(eg.getAttack(state, curveSet, 127_norm) == 2.0f); } TEST_CASE("[EGDescription] Delay range") { sfz::EGDescription eg; sfz::MidiState state; + sfz::CurveSet curveSet; eg.delay = 1; eg.vel2delay = -1.27f; - eg.ccDelay[63] = 1.27f; - REQUIRE(eg.getDelay(state, 0_norm) == 1.0f); - //REQUIRE(eg.getDelay(state, 127_norm) == 0.0f); + eg.ccDelay[63].modifier = 1.27f; + REQUIRE(eg.getDelay(state, curveSet, 0_norm) == 1.0f); + //REQUIRE(eg.getDelay(state, curveSet, 127_norm) == 0.0f); state.ccEvent(0, 63, 127_norm); - REQUIRE(eg.getDelay(state, 127_norm) == 1.0f); - 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; - eg.ccDelay[65] = 1.0f; - REQUIRE(eg.getDelay(state, 0_norm) == 2.27f); - REQUIRE(eg.getDelay(state, 127_norm) == 1.0f); + REQUIRE(eg.getDelay(state, curveSet, 127_norm) == 1.0f); + REQUIRE(eg.getDelay(state, curveSet, 0_norm, 1) == 2.27f); + //eg.ccDelay[63].modifier = 127.0f; + //REQUIRE(eg.getDelay(state, curveSet, 0_norm) == 100.0f); + eg.ccDelay[63].modifier = 1.27f; + eg.ccDelay[65].modifier = 1.0f; + REQUIRE(eg.getDelay(state, curveSet, 0_norm) == 2.27f); + REQUIRE(eg.getDelay(state, curveSet, 127_norm) == 1.0f); state.ccEvent(0, 65, 127_norm); - REQUIRE(eg.getDelay(state, 0_norm) == 3.27f); - REQUIRE(eg.getDelay(state, 127_norm) == 2.0f); + REQUIRE(eg.getDelay(state, curveSet, 0_norm) == 3.27f); + REQUIRE(eg.getDelay(state, curveSet, 127_norm) == 2.0f); } TEST_CASE("[EGDescription] Decay range") { sfz::EGDescription eg; sfz::MidiState state; + sfz::CurveSet curveSet; eg.decay = 1.0f; eg.vel2decay = -1.27f; - eg.ccDecay[63] = 1.27f; - REQUIRE(eg.getDecay(state, 0_norm) == 1.0f); - //REQUIRE(eg.getDecay(state, 127_norm) == 0.0f); + eg.ccDecay[63].modifier = 1.27f; + REQUIRE(eg.getDecay(state, curveSet, 0_norm) == 1.0f); + //REQUIRE(eg.getDecay(state, curveSet, 127_norm) == 0.0f); state.ccEvent(0, 63, 127_norm); - REQUIRE(eg.getDecay(state, 127_norm) == 1.0f); - REQUIRE(eg.getDecay(state, 0_norm) == 2.27f); - //eg.ccDecay[63] = 127.0f; - //REQUIRE(eg.getDecay(state, 0_norm) == 100.0f); - eg.ccDecay[63] = 1.27f; - eg.ccDecay[65] = 1.0f; - REQUIRE(eg.getDecay(state, 0_norm) == 2.27f); - REQUIRE(eg.getDecay(state, 127_norm) == 1.0f); + REQUIRE(eg.getDecay(state, curveSet, 127_norm) == 1.0f); + REQUIRE(eg.getDecay(state, curveSet, 0_norm) == 2.27f); + //eg.ccDecay[63].modifier = 127.0f; + //REQUIRE(eg.getDecay(state, curveSet, 0_norm) == 100.0f); + eg.ccDecay[63].modifier = 1.27f; + eg.ccDecay[65].modifier = 1.0f; + REQUIRE(eg.getDecay(state, curveSet, 0_norm) == 2.27f); + REQUIRE(eg.getDecay(state, curveSet, 127_norm) == 1.0f); state.ccEvent(0, 65, 127_norm); - REQUIRE(eg.getDecay(state, 0_norm) == 3.27f); - REQUIRE(eg.getDecay(state, 127_norm) == 2.0f); + REQUIRE(eg.getDecay(state, curveSet, 0_norm) == 3.27f); + REQUIRE(eg.getDecay(state, curveSet, 127_norm) == 2.0f); } TEST_CASE("[EGDescription] Release range") { sfz::EGDescription eg; sfz::MidiState state; + sfz::CurveSet curveSet; eg.release = 1; eg.vel2release = -1.27f; - eg.ccRelease[63] = 1.27f; - REQUIRE(eg.getRelease(state, 0_norm) == 1.0f); - //REQUIRE(eg.getRelease(state, 127_norm) == 0.0f); + eg.ccRelease[63].modifier = 1.27f; + REQUIRE(eg.getRelease(state, curveSet, 0_norm) == 1.0f); + //REQUIRE(eg.getRelease(state, curveSet, 127_norm) == 0.0f); state.ccEvent(0, 63, 127_norm); - REQUIRE(eg.getRelease(state, 127_norm) == 1.0f); - REQUIRE(eg.getRelease(state, 0_norm) == 2.27f); - //eg.ccRelease[63] = 127.0f; - //REQUIRE(eg.getRelease(state, 0_norm) == 100.0f); - eg.ccRelease[63] = 1.27f; - eg.ccRelease[65] = 1.0f; - REQUIRE(eg.getRelease(state, 0_norm) == 2.27f); - REQUIRE(eg.getRelease(state, 127_norm) == 1.0f); + REQUIRE(eg.getRelease(state, curveSet, 127_norm) == 1.0f); + REQUIRE(eg.getRelease(state, curveSet, 0_norm) == 2.27f); + //eg.ccRelease[63].modifier = 127.0f; + //REQUIRE(eg.getRelease(state, curveSet, 0_norm) == 100.0f); + eg.ccRelease[63].modifier = 1.27f; + eg.ccRelease[65].modifier = 1.0f; + REQUIRE(eg.getRelease(state, curveSet, 0_norm) == 2.27f); + REQUIRE(eg.getRelease(state, curveSet, 127_norm) == 1.0f); state.ccEvent(0, 65, 127_norm); - REQUIRE(eg.getRelease(state, 0_norm) == 3.27f); - REQUIRE(eg.getRelease(state, 127_norm) == 2.0f); + REQUIRE(eg.getRelease(state, curveSet, 0_norm) == 3.27f); + REQUIRE(eg.getRelease(state, curveSet, 127_norm) == 2.0f); } TEST_CASE("[EGDescription] Hold range") { sfz::EGDescription eg; sfz::MidiState state; + sfz::CurveSet curveSet; eg.hold = 1; eg.vel2hold = -1.27f; - eg.ccHold[63] = 1.27f; - REQUIRE(eg.getHold(state, 0_norm) == 1.0f); - //REQUIRE(eg.getHold(state, 127_norm) == 0.0f); + eg.ccHold[63].modifier = 1.27f; + REQUIRE(eg.getHold(state, curveSet, 0_norm) == 1.0f); + //REQUIRE(eg.getHold(state, curveSet, 127_norm) == 0.0f); state.ccEvent(0, 63, 127_norm); - REQUIRE(eg.getHold(state, 127_norm) == 1.0f); - REQUIRE(eg.getHold(state, 0_norm) == 2.27f); - //eg.ccHold[63] = 127.0f; - //REQUIRE(eg.getHold(state, 0_norm) == 100.0f); - eg.ccHold[63] = 1.27f; - eg.ccHold[65] = 1.0f; - REQUIRE(eg.getHold(state, 0_norm) == 2.27f); - REQUIRE(eg.getHold(state, 127_norm) == 1.0f); + REQUIRE(eg.getHold(state, curveSet, 127_norm) == 1.0f); + REQUIRE(eg.getHold(state, curveSet, 0_norm) == 2.27f); + //eg.ccHold[63].modifier = 127.0f; + //REQUIRE(eg.getHold(state, curveSet, 0_norm) == 100.0f); + eg.ccHold[63].modifier = 1.27f; + eg.ccHold[65].modifier = 1.0f; + REQUIRE(eg.getHold(state, curveSet, 0_norm) == 2.27f); + REQUIRE(eg.getHold(state, curveSet, 127_norm) == 1.0f); state.ccEvent(0, 65, 127_norm); - REQUIRE(eg.getHold(state, 0_norm) == 3.27f); - REQUIRE(eg.getHold(state, 127_norm) == 2.0f); + REQUIRE(eg.getHold(state, curveSet, 0_norm) == 3.27f); + REQUIRE(eg.getHold(state, curveSet, 127_norm) == 2.0f); } TEST_CASE("[EGDescription] Sustain level") { sfz::EGDescription eg; sfz::MidiState state; + sfz::CurveSet curveSet; eg.sustain = 50; eg.vel2sustain = -100; - eg.ccSustain[63] = 100.0f; - REQUIRE(eg.getSustain(state, 0_norm) == 50.0f); - //REQUIRE(eg.getSustain(state, 127_norm) == 0.0f); + eg.ccSustain[63].modifier = 100.0f; + REQUIRE(eg.getSustain(state, curveSet, 0_norm) == 50.0f); + //REQUIRE(eg.getSustain(state, curveSet, 127_norm) == 0.0f); state.ccEvent(0, 63, 127_norm); - REQUIRE(eg.getSustain(state, 127_norm) == 50.0f); - //eg.ccSustain[63] = 200.0f; - //REQUIRE(eg.getSustain(state, 0_norm) == 100.0f); + REQUIRE(eg.getSustain(state, curveSet, 127_norm) == 50.0f); + //eg.ccSustain[63].modifier = 200.0f; + //REQUIRE(eg.getSustain(state, curveSet, 0_norm) == 100.0f); eg.sustain = 0; - eg.ccSustain[63] = 50.0f; - eg.ccSustain[65] = 50.0f; - REQUIRE(eg.getSustain(state, 0_norm) == 50.0f); - //REQUIRE(eg.getSustain(state, 127_norm) == 0.0f); + eg.ccSustain[63].modifier = 50.0f; + eg.ccSustain[65].modifier = 50.0f; + REQUIRE(eg.getSustain(state, curveSet, 0_norm) == 50.0f); + //REQUIRE(eg.getSustain(state, curveSet, 127_norm) == 0.0f); state.ccEvent(0, 65, 127_norm); - REQUIRE(eg.getSustain(state, 0_norm) == 100.0f); - REQUIRE(eg.getSustain(state, 127_norm) == 0.0f); + REQUIRE(eg.getSustain(state, curveSet, 0_norm) == 100.0f); + REQUIRE(eg.getSustain(state, curveSet, 127_norm) == 0.0f); } TEST_CASE("[EGDescription] Start level") { sfz::EGDescription eg; sfz::MidiState state; + sfz::CurveSet curveSet; eg.start = 0; - eg.ccStart[63] = 127.0f; - REQUIRE(eg.getStart(state, 0_norm) == 0.0f); - REQUIRE(eg.getStart(state, 127_norm) == 0.0f); + eg.ccStart[63].modifier = 127.0f; + REQUIRE(eg.getStart(state, curveSet, 0_norm) == 0.0f); + REQUIRE(eg.getStart(state, curveSet, 127_norm) == 0.0f); state.ccEvent(0, 63, 127_norm); - //REQUIRE(eg.getStart(state, 0_norm) == 100.0f); + //REQUIRE(eg.getStart(state, curveSet, 0_norm) == 100.0f); //eg.ccStart[63] = -127.0f; - //REQUIRE(eg.getStart(state, 0_norm) == 0.0f); + //REQUIRE(eg.getStart(state, curveSet, 0_norm) == 0.0f); eg.start = 0; - eg.ccStart[63] = 50.0f; - eg.ccStart[65] = 50.0f; - REQUIRE(eg.getStart(state, 0_norm) == 50.0f); - REQUIRE(eg.getStart(state, 127_norm) == 50.0f); + eg.ccStart[63].modifier = 50.0f; + eg.ccStart[65].modifier = 50.0f; + REQUIRE(eg.getStart(state, curveSet, 0_norm) == 50.0f); + REQUIRE(eg.getStart(state, curveSet, 127_norm) == 50.0f); state.ccEvent(0, 65, 127_norm); - REQUIRE(eg.getStart(state, 0_norm) == 100.0f); - REQUIRE(eg.getStart(state, 127_norm) == 100.0f); + REQUIRE(eg.getStart(state, curveSet, 0_norm) == 100.0f); + REQUIRE(eg.getStart(state, curveSet, 127_norm) == 100.0f); } From 4b45dcc70cfe7d403d23b68f9f737a33e328e8a3 Mon Sep 17 00:00:00 2001 From: Paul Ferrand Date: Sun, 31 Dec 2023 07:10:02 +0100 Subject: [PATCH 25/65] Cleanup region parsing --- src/sfizz/EGDescription.h | 8 +--- src/sfizz/Region.cpp | 95 +++++++++++++++------------------------ 2 files changed, 37 insertions(+), 66 deletions(-) diff --git a/src/sfizz/EGDescription.h b/src/sfizz/EGDescription.h index 0337a3059..16feb5ca0 100644 --- a/src/sfizz/EGDescription.h +++ b/src/sfizz/EGDescription.h @@ -75,13 +75,7 @@ struct EGDescription { CCMap> ccRelease { ModifierCurvePair{ Default::egTime, Default::curveCC } }; CCMap> ccStart { ModifierCurvePair{ Default::egPercentMod, Default::curveCC } }; CCMap> ccSustain { ModifierCurvePair{ Default:: egPercentMod, Default::curveCC } }; - //CCMap ccAttack; - //CCMap ccDecay; - //CCMap ccDelay; - //CCMap ccHold; - //CCMap ccRelease; - //CCMap ccStart; - //CCMap ccSustain; + bool dynamic { false }; /** diff --git a/src/sfizz/Region.cpp b/src/sfizz/Region.cpp index 8fe46f57a..cd9510981 100644 --- a/src/sfizz/Region.cpp +++ b/src/sfizz/Region.cpp @@ -5,6 +5,7 @@ // If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz #include "Region.h" +#include "Defaults.h" #include "Opcode.h" #include "MathHelpers.h" #include "utility/SwapAndPop.h" @@ -1059,6 +1060,16 @@ bool sfz::Region::parseLFOOpcode(const Opcode& opcode, absl::optional +bool parseEGModifierCurveHelper(const sfz::Opcode& opcode, sfz::CCMap>& ccMap, sfz::OpcodeSpec spec, Member member) +{ + if (opcode.parameters.back() >= sfz::config::numCCs) + return false; + + ccMap[opcode.parameters.back()].*member = opcode.read(spec); + return true; +} + bool sfz::Region::parseEGOpcode(const Opcode& opcode, EGDescription& eg) { #define case_any_eg(param) \ @@ -1066,6 +1077,14 @@ bool sfz::Region::parseEGOpcode(const Opcode& opcode, EGDescription& eg) case hash("pitcheg_" param): \ case hash("fileg_" param) \ + auto setModifier = [&opcode] (auto& ccMap, auto& spec) -> bool { + return parseEGModifierCurveHelper(opcode, ccMap, spec, &ModifierCurvePair::modifier); + }; + + auto setCurve = [&opcode] (auto& ccMap) -> bool { + return parseEGModifierCurveHelper(opcode, ccMap, Default::curveCC, &ModifierCurvePair::curve); + }; + switch (opcode.lettersOnlyHash) { case_any_eg("attack"): eg.attack = opcode.read(Default::egTime); @@ -1107,102 +1126,60 @@ bool sfz::Region::parseEGOpcode(const Opcode& opcode, EGDescription& eg) eg.vel2sustain = opcode.read(Default::egPercentMod); break; case_any_eg("attack_oncc&"): // also attackcc& - if (opcode.parameters.back() >= config::numCCs) + if (!setModifier(eg.ccAttack, Default::egTimeMod)) return false; - - eg.ccAttack[opcode.parameters.back()].modifier = opcode.read(Default::egTimeMod); - break; - case_any_eg("attack_curvecc&"): // also attackcc& - if (opcode.parameters.back() >= config::numCCs) + case_any_eg("attack_curvecc&"): + if (!setCurve(eg.ccAttack)) return false; - - eg.ccAttack[opcode.parameters.back()].curve = opcode.read(Default::curveCC); - break; case_any_eg("decay_oncc&"): // also decaycc& - if (opcode.parameters.back() >= config::numCCs) + if (!setModifier(eg.ccDecay, Default::egTimeMod)) return false; - - eg.ccDecay[opcode.parameters.back()].modifier = opcode.read(Default::egTimeMod); - break; - case_any_eg("decay_curvecc&"): // also attackcc& - if (opcode.parameters.back() >= config::numCCs) + case_any_eg("decay_curvecc&"): + if (!setCurve(eg.ccDecay)) return false; - - eg.ccDecay[opcode.parameters.back()].curve = opcode.read(Default::curveCC); - break; case_any_eg("delay_oncc&"): // also delaycc& - if (opcode.parameters.back() >= config::numCCs) + if (!setModifier(eg.ccDelay, Default::egTimeMod)) return false; - - eg.ccDelay[opcode.parameters.back()].modifier = opcode.read(Default::egTimeMod); - break; - case_any_eg("delay_curvecc&"): // also attackcc& - if (opcode.parameters.back() >= config::numCCs) + case_any_eg("delay_curvecc&"): + if (!setCurve(eg.ccDelay)) return false; - - eg.ccDelay[opcode.parameters.back()].curve = opcode.read(Default::curveCC); - break; case_any_eg("hold_oncc&"): // also holdcc& - if (opcode.parameters.back() >= config::numCCs) + if (!setModifier(eg.ccHold, Default::egTimeMod)) return false; - - eg.ccHold[opcode.parameters.back()].modifier = opcode.read(Default::egTimeMod); - break; case_any_eg("hold_curvecc&"): // also attackcc& - if (opcode.parameters.back() >= config::numCCs) + if (!setCurve(eg.ccHold)) return false; - - eg.ccHold[opcode.parameters.back()].curve = opcode.read(Default::curveCC); - break; case_any_eg("release_oncc&"): // also releasecc& - if (opcode.parameters.back() >= config::numCCs) + if (!setModifier(eg.ccRelease, Default::egTimeMod)) return false; - - eg.ccRelease[opcode.parameters.back()].modifier = opcode.read(Default::egTimeMod); - break; case_any_eg("release_curvecc&"): // also attackcc& - if (opcode.parameters.back() >= config::numCCs) + if (!setCurve(eg.ccRelease)) return false; - - eg.ccRelease[opcode.parameters.back()].curve = opcode.read(Default::curveCC); - break; case_any_eg("start_oncc&"): // also startcc& - if (opcode.parameters.back() >= config::numCCs) + if (!setModifier(eg.ccStart, Default::egPercentMod)) return false; - - eg.ccStart[opcode.parameters.back()].modifier = opcode.read(Default::egPercentMod); - break; case_any_eg("start_curvecc&"): // also startcc& - if (opcode.parameters.back() >= config::numCCs) + if (!setCurve(eg.ccStart)) return false; - - eg.ccStart[opcode.parameters.back()].curve = opcode.read(Default::curveCC); - break; case_any_eg("sustain_oncc&"): // also sustaincc& - if (opcode.parameters.back() >= config::numCCs) + if (!setModifier(eg.ccSustain, Default::egPercentMod)) return false; - - eg.ccSustain[opcode.parameters.back()].modifier = opcode.read(Default::egPercentMod); - break; case_any_eg("sustain_curvecc&"): // also attackcc& - if (opcode.parameters.back() >= config::numCCs) + if (!setCurve(eg.ccSustain)) return false; - - eg.ccSustain[opcode.parameters.back()].curve = opcode.read(Default::curveCC); - break; case_any_eg("dynamic"): From 49699de043d911f46a6c01c8f09a12e39ea870bc Mon Sep 17 00:00:00 2001 From: Paul Ferrand Date: Sun, 31 Dec 2023 10:10:35 +0100 Subject: [PATCH 26/65] Add parsing tests --- src/sfizz/SynthMessaging.cpp | 98 ++++++----- tests/RegionValuesT.cpp | 322 ++++++++++++++++++++++++++++++++++- 2 files changed, 377 insertions(+), 43 deletions(-) diff --git a/src/sfizz/SynthMessaging.cpp b/src/sfizz/SynthMessaging.cpp index 889fba52d..dbb0c06b4 100644 --- a/src/sfizz/SynthMessaging.cpp +++ b/src/sfizz/SynthMessaging.cpp @@ -1224,84 +1224,98 @@ void sfz::Synth::dispatchMessage(Client& client, int delay, const char* path, co MATCH("/region&/pitcheg_attack_cc&", "") { GET_REGION_OR_BREAK(indices[0]) + if (!region.pitchEG) break; const auto& cc = region.pitchEG->ccAttack.getWithDefault(indices[1]); client.receive<'f'>(delay, path, cc.modifier); } break; MATCH("/region&/pitcheg_decay_cc&", "") { GET_REGION_OR_BREAK(indices[0]) + if (!region.pitchEG) break; const auto& cc = region.pitchEG->ccDecay.getWithDefault(indices[1]); client.receive<'f'>(delay, path, cc.modifier); } break; MATCH("/region&/pitcheg_delay_cc&", "") { GET_REGION_OR_BREAK(indices[0]) + if (!region.pitchEG) break; const auto& cc = region.pitchEG->ccDelay.getWithDefault(indices[1]); client.receive<'f'>(delay, path, cc.modifier); } break; MATCH("/region&/pitcheg_hold_cc&", "") { GET_REGION_OR_BREAK(indices[0]) + if (!region.pitchEG) break; const auto& cc = region.pitchEG->ccHold.getWithDefault(indices[1]); client.receive<'f'>(delay, path, cc.modifier); } break; MATCH("/region&/pitcheg_release_cc&", "") { GET_REGION_OR_BREAK(indices[0]) + if (!region.pitchEG) break; const auto& cc = region.pitchEG->ccRelease.getWithDefault(indices[1]); client.receive<'f'>(delay, path, cc.modifier); } break; MATCH("/region&/pitcheg_start_cc&", "") { GET_REGION_OR_BREAK(indices[0]) + if (!region.pitchEG) break; const auto& cc = region.pitchEG->ccStart.getWithDefault(indices[1]); client.receive<'f'>(delay, path, cc.modifier * 100.0f); } break; MATCH("/region&/pitcheg_sustain_cc&", "") { GET_REGION_OR_BREAK(indices[0]) + if (!region.pitchEG) break; const auto& cc = region.pitchEG->ccSustain.getWithDefault(indices[1]); client.receive<'f'>(delay, path, cc.modifier * 100.0f); } break; MATCH("/region&/pitcheg_attack_curvecc&", "") { GET_REGION_OR_BREAK(indices[0]) + if (!region.pitchEG) break; const auto& cc = region.pitchEG->ccAttack.getWithDefault(indices[1]); client.receive<'f'>(delay, path, cc.curve); } break; MATCH("/region&/pitcheg_decay_curvecc&", "") { GET_REGION_OR_BREAK(indices[0]) + if (!region.pitchEG) break; const auto& cc = region.pitchEG->ccDecay.getWithDefault(indices[1]); client.receive<'f'>(delay, path, cc.curve); } break; MATCH("/region&/pitcheg_delay_curvecc&", "") { GET_REGION_OR_BREAK(indices[0]) + if (!region.pitchEG) break; const auto& cc = region.pitchEG->ccDelay.getWithDefault(indices[1]); client.receive<'f'>(delay, path, cc.curve); } break; MATCH("/region&/pitcheg_hold_curvecc&", "") { GET_REGION_OR_BREAK(indices[0]) + if (!region.pitchEG) break; const auto& cc = region.pitchEG->ccHold.getWithDefault(indices[1]); client.receive<'f'>(delay, path, cc.curve); } break; MATCH("/region&/pitcheg_release_curvecc&", "") { GET_REGION_OR_BREAK(indices[0]) + if (!region.pitchEG) break; const auto& cc = region.pitchEG->ccRelease.getWithDefault(indices[1]); client.receive<'f'>(delay, path, cc.curve); } break; MATCH("/region&/pitcheg_start_curvecc&", "") { GET_REGION_OR_BREAK(indices[0]) + if (!region.pitchEG) break; const auto& cc = region.pitchEG->ccStart.getWithDefault(indices[1]); client.receive<'f'>(delay, path, cc.curve); } break; MATCH("/region&/pitcheg_sustain_curvecc&", "") { GET_REGION_OR_BREAK(indices[0]) + if (!region.pitchEG) break; const auto& cc = region.pitchEG->ccSustain.getWithDefault(indices[1]); client.receive<'f'>(delay, path, cc.curve); } break; @@ -1621,101 +1635,101 @@ void sfz::Synth::dispatchMessage(Client& client, int delay, const char* path, co } } break; - MATCH("/region&/filter&/fileg_attack_cc&", "") { + MATCH("/region&/fileg_attack_cc&", "") { GET_REGION_OR_BREAK(indices[0]) - GET_FILTER_OR_BREAK(indices[1]) - const auto& cc = region.filterEG->ccAttack.getWithDefault(indices[2]); + if (!region.filterEG) break; + const auto& cc = region.filterEG->ccAttack.getWithDefault(indices[1]); client.receive<'f'>(delay, path, cc.modifier); } break; - MATCH("/region&/filter&/fileg_decay_cc&", "") { + MATCH("/region&/fileg_decay_cc&", "") { GET_REGION_OR_BREAK(indices[0]) - GET_FILTER_OR_BREAK(indices[1]) - const auto& cc = region.filterEG->ccDecay.getWithDefault(indices[2]); + if (!region.filterEG) break; + const auto& cc = region.filterEG->ccDecay.getWithDefault(indices[1]); client.receive<'f'>(delay, path, cc.modifier); } break; - MATCH("/region&/filter&/fileg_delay_cc&", "") { + MATCH("/region&/fileg_delay_cc&", "") { GET_REGION_OR_BREAK(indices[0]) - GET_FILTER_OR_BREAK(indices[1]) - const auto& cc = region.filterEG->ccDelay.getWithDefault(indices[2]); + if (!region.filterEG) break; + const auto& cc = region.filterEG->ccDelay.getWithDefault(indices[1]); client.receive<'f'>(delay, path, cc.modifier); } break; - MATCH("/region&/filter&/fileg_hold_cc&", "") { + MATCH("/region&/fileg_hold_cc&", "") { GET_REGION_OR_BREAK(indices[0]) - GET_FILTER_OR_BREAK(indices[1]) - const auto& cc = region.filterEG->ccHold.getWithDefault(indices[2]); + if (!region.filterEG) break; + const auto& cc = region.filterEG->ccHold.getWithDefault(indices[1]); client.receive<'f'>(delay, path, cc.modifier); } break; - MATCH("/region&/filter&/fileg_release_cc&", "") { + MATCH("/region&/fileg_release_cc&", "") { GET_REGION_OR_BREAK(indices[0]) - GET_FILTER_OR_BREAK(indices[1]) - const auto& cc = region.filterEG->ccRelease.getWithDefault(indices[2]); + if (!region.filterEG) break; + const auto& cc = region.filterEG->ccRelease.getWithDefault(indices[1]); client.receive<'f'>(delay, path, cc.modifier); } break; - MATCH("/region&/filter&/fileg_start_cc&", "") { + MATCH("/region&/fileg_start_cc&", "") { GET_REGION_OR_BREAK(indices[0]) - GET_FILTER_OR_BREAK(indices[1]) - const auto& cc = region.filterEG->ccStart.getWithDefault(indices[2]); + if (!region.filterEG) break; + const auto& cc = region.filterEG->ccStart.getWithDefault(indices[1]); client.receive<'f'>(delay, path, cc.modifier * 100.0f); } break; - MATCH("/region&/filter&/fileg_sustain_cc&", "") { + MATCH("/region&/fileg_sustain_cc&", "") { GET_REGION_OR_BREAK(indices[0]) - GET_FILTER_OR_BREAK(indices[1]) - const auto& cc = region.filterEG->ccSustain.getWithDefault(indices[2]); + if (!region.filterEG) break; + const auto& cc = region.filterEG->ccSustain.getWithDefault(indices[1]); client.receive<'f'>(delay, path, cc.modifier * 100.0f); } break; - MATCH("/region&/filter&/fileg_attack_curvecc&", "") { + MATCH("/region&/fileg_attack_curvecc&", "") { GET_REGION_OR_BREAK(indices[0]) - GET_FILTER_OR_BREAK(indices[1]) - const auto& cc = region.filterEG->ccAttack.getWithDefault(indices[2]); + if (!region.filterEG) break; + const auto& cc = region.filterEG->ccAttack.getWithDefault(indices[1]); client.receive<'f'>(delay, path, cc.curve); } break; - MATCH("/region&/filter&/fileg_decay_curvecc&", "") { + MATCH("/region&/fileg_decay_curvecc&", "") { GET_REGION_OR_BREAK(indices[0]) - GET_FILTER_OR_BREAK(indices[1]) - const auto& cc = region.filterEG->ccDecay.getWithDefault(indices[2]); + if (!region.filterEG) break; + const auto& cc = region.filterEG->ccDecay.getWithDefault(indices[1]); client.receive<'f'>(delay, path, cc.curve); } break; - MATCH("/region&/filter&/fileg_delay_curvecc&", "") { + MATCH("/region&/fileg_delay_curvecc&", "") { GET_REGION_OR_BREAK(indices[0]) - GET_FILTER_OR_BREAK(indices[1]) - const auto& cc = region.filterEG->ccDelay.getWithDefault(indices[2]); + if (!region.filterEG) break; + const auto& cc = region.filterEG->ccDelay.getWithDefault(indices[1]); client.receive<'f'>(delay, path, cc.curve); } break; - MATCH("/region&/filter&/fileg_hold_curvecc&", "") { + MATCH("/region&/fileg_hold_curvecc&", "") { GET_REGION_OR_BREAK(indices[0]) - GET_FILTER_OR_BREAK(indices[1]) - const auto& cc = region.filterEG->ccHold.getWithDefault(indices[2]); + if (!region.filterEG) break; + const auto& cc = region.filterEG->ccHold.getWithDefault(indices[1]); client.receive<'f'>(delay, path, cc.curve); } break; - MATCH("/region&/filter&/fileg_release_curvecc&", "") { + MATCH("/region&/fileg_release_curvecc&", "") { GET_REGION_OR_BREAK(indices[0]) - GET_FILTER_OR_BREAK(indices[1]) - const auto& cc = region.filterEG->ccRelease.getWithDefault(indices[2]); + if (!region.filterEG) break; + const auto& cc = region.filterEG->ccRelease.getWithDefault(indices[1]); client.receive<'f'>(delay, path, cc.curve); } break; - MATCH("/region&/filter&/fileg_start_curvecc&", "") { + MATCH("/region&/fileg_start_curvecc&", "") { GET_REGION_OR_BREAK(indices[0]) - GET_FILTER_OR_BREAK(indices[1]) - const auto& cc = region.filterEG->ccStart.getWithDefault(indices[2]); + if (!region.filterEG) break; + const auto& cc = region.filterEG->ccStart.getWithDefault(indices[1]); client.receive<'f'>(delay, path, cc.curve); } break; - MATCH("/region&/filter&/fileg_sustain_curvecc&", "") { + MATCH("/region&/fileg_sustain_curvecc&", "") { GET_REGION_OR_BREAK(indices[0]) - GET_FILTER_OR_BREAK(indices[1]) - const auto& cc = region.filterEG->ccSustain.getWithDefault(indices[2]); + if (!region.filterEG) break; + const auto& cc = region.filterEG->ccSustain.getWithDefault(indices[1]); client.receive<'f'>(delay, path, cc.curve); } break; diff --git a/tests/RegionValuesT.cpp b/tests/RegionValuesT.cpp index 57dcb5436..01c596e24 100644 --- a/tests/RegionValuesT.cpp +++ b/tests/RegionValuesT.cpp @@ -2954,7 +2954,7 @@ TEST_CASE("[Values] ampeg CC") REQUIRE(messageList == expected); } - SECTION("Basic") + SECTION("Negative values") { synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( sample=kick.wav @@ -2982,6 +2982,326 @@ TEST_CASE("[Values] ampeg CC") } } +TEST_CASE("[Values] fileg CC") +{ + Synth synth; + std::vector messageList; + Client client(&messageList); + client.setReceiveCallback(&simpleMessageReceiver); + + SECTION("Basic") + { + synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + sample=kick.wav + )"); + synth.dispatchMessage(client, 0, "/region0/fileg_attack_cc1", "", nullptr); + synth.dispatchMessage(client, 0, "/region0/fileg_delay_cc2", "", nullptr); + synth.dispatchMessage(client, 0, "/region0/fileg_decay_cc3", "", nullptr); + synth.dispatchMessage(client, 0, "/region0/fileg_hold_cc4", "", nullptr); + synth.dispatchMessage(client, 0, "/region0/fileg_release_cc5", "", nullptr); + synth.dispatchMessage(client, 0, "/region0/fileg_start_cc6", "", nullptr); + synth.dispatchMessage(client, 0, "/region0/fileg_sustain_cc7", "", nullptr); + std::vector expected { }; + REQUIRE(messageList == expected); + } + + SECTION("Positive values") + { + synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + sample=kick.wav + fileg_attack_oncc1=1 fileg_delay_oncc2=2 fileg_decay_oncc3=3 + fileg_hold_oncc4=4 fileg_release_oncc5=5 fileg_start_oncc6=6 + fileg_sustain_oncc7=7 + )"); + synth.dispatchMessage(client, 0, "/region0/fileg_attack_cc1", "", nullptr); + synth.dispatchMessage(client, 0, "/region0/fileg_delay_cc2", "", nullptr); + synth.dispatchMessage(client, 0, "/region0/fileg_decay_cc3", "", nullptr); + synth.dispatchMessage(client, 0, "/region0/fileg_hold_cc4", "", nullptr); + synth.dispatchMessage(client, 0, "/region0/fileg_release_cc5", "", nullptr); + synth.dispatchMessage(client, 0, "/region0/fileg_start_cc6", "", nullptr); + synth.dispatchMessage(client, 0, "/region0/fileg_sustain_cc7", "", nullptr); + std::vector expected { + "/region0/fileg_attack_cc1,f : { 1 }", + "/region0/fileg_delay_cc2,f : { 2 }", + "/region0/fileg_decay_cc3,f : { 3 }", + "/region0/fileg_hold_cc4,f : { 4 }", + "/region0/fileg_release_cc5,f : { 5 }", + "/region0/fileg_start_cc6,f : { 6 }", + "/region0/fileg_sustain_cc7,f : { 7 }", + }; + REQUIRE(messageList == expected); + } + + SECTION("Negative values") + { + synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + sample=kick.wav + fileg_attack_cc1=-1 fileg_delay_cc2=-2 fileg_decay_cc3=-3 + fileg_hold_cc4=-4 fileg_release_cc5=-5 fileg_start_cc6=-6 + fileg_sustain_cc7=-7 + )"); + synth.dispatchMessage(client, 0, "/region0/fileg_attack_cc1", "", nullptr); + synth.dispatchMessage(client, 0, "/region0/fileg_delay_cc2", "", nullptr); + synth.dispatchMessage(client, 0, "/region0/fileg_decay_cc3", "", nullptr); + synth.dispatchMessage(client, 0, "/region0/fileg_hold_cc4", "", nullptr); + synth.dispatchMessage(client, 0, "/region0/fileg_release_cc5", "", nullptr); + synth.dispatchMessage(client, 0, "/region0/fileg_start_cc6", "", nullptr); + synth.dispatchMessage(client, 0, "/region0/fileg_sustain_cc7", "", nullptr); + std::vector expected { + "/region0/fileg_attack_cc1,f : { -1 }", + "/region0/fileg_delay_cc2,f : { -2 }", + "/region0/fileg_decay_cc3,f : { -3 }", + "/region0/fileg_hold_cc4,f : { -4 }", + "/region0/fileg_release_cc5,f : { -5 }", + "/region0/fileg_start_cc6,f : { -6 }", + "/region0/fileg_sustain_cc7,f : { -7 }", + }; + REQUIRE(messageList == expected); + } +} + +TEST_CASE("[Values] pitcheg CC") +{ + Synth synth; + std::vector messageList; + Client client(&messageList); + client.setReceiveCallback(&simpleMessageReceiver); + + SECTION("Basic") + { + synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + sample=kick.wav + )"); + synth.dispatchMessage(client, 0, "/region0/pitcheg_attack_cc1", "", nullptr); + synth.dispatchMessage(client, 0, "/region0/pitcheg_delay_cc2", "", nullptr); + synth.dispatchMessage(client, 0, "/region0/pitcheg_decay_cc3", "", nullptr); + synth.dispatchMessage(client, 0, "/region0/pitcheg_hold_cc4", "", nullptr); + synth.dispatchMessage(client, 0, "/region0/pitcheg_release_cc5", "", nullptr); + synth.dispatchMessage(client, 0, "/region0/pitcheg_start_cc6", "", nullptr); + synth.dispatchMessage(client, 0, "/region0/pitcheg_sustain_cc7", "", nullptr); + std::vector expected { }; + REQUIRE(messageList == expected); + } + + SECTION("Positive values") + { + synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + sample=kick.wav + pitcheg_attack_oncc1=1 pitcheg_delay_oncc2=2 pitcheg_decay_oncc3=3 + pitcheg_hold_oncc4=4 pitcheg_release_oncc5=5 pitcheg_start_oncc6=6 + pitcheg_sustain_oncc7=7 + )"); + synth.dispatchMessage(client, 0, "/region0/pitcheg_attack_cc1", "", nullptr); + synth.dispatchMessage(client, 0, "/region0/pitcheg_delay_cc2", "", nullptr); + synth.dispatchMessage(client, 0, "/region0/pitcheg_decay_cc3", "", nullptr); + synth.dispatchMessage(client, 0, "/region0/pitcheg_hold_cc4", "", nullptr); + synth.dispatchMessage(client, 0, "/region0/pitcheg_release_cc5", "", nullptr); + synth.dispatchMessage(client, 0, "/region0/pitcheg_start_cc6", "", nullptr); + synth.dispatchMessage(client, 0, "/region0/pitcheg_sustain_cc7", "", nullptr); + std::vector expected { + "/region0/pitcheg_attack_cc1,f : { 1 }", + "/region0/pitcheg_delay_cc2,f : { 2 }", + "/region0/pitcheg_decay_cc3,f : { 3 }", + "/region0/pitcheg_hold_cc4,f : { 4 }", + "/region0/pitcheg_release_cc5,f : { 5 }", + "/region0/pitcheg_start_cc6,f : { 6 }", + "/region0/pitcheg_sustain_cc7,f : { 7 }", + }; + REQUIRE(messageList == expected); + } + + SECTION("Negative values") + { + synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + sample=kick.wav + pitcheg_attack_cc1=-1 pitcheg_delay_cc2=-2 pitcheg_decay_cc3=-3 + pitcheg_hold_cc4=-4 pitcheg_release_cc5=-5 pitcheg_start_cc6=-6 + pitcheg_sustain_cc7=-7 + )"); + synth.dispatchMessage(client, 0, "/region0/pitcheg_attack_cc1", "", nullptr); + synth.dispatchMessage(client, 0, "/region0/pitcheg_delay_cc2", "", nullptr); + synth.dispatchMessage(client, 0, "/region0/pitcheg_decay_cc3", "", nullptr); + synth.dispatchMessage(client, 0, "/region0/pitcheg_hold_cc4", "", nullptr); + synth.dispatchMessage(client, 0, "/region0/pitcheg_release_cc5", "", nullptr); + synth.dispatchMessage(client, 0, "/region0/pitcheg_start_cc6", "", nullptr); + synth.dispatchMessage(client, 0, "/region0/pitcheg_sustain_cc7", "", nullptr); + std::vector expected { + "/region0/pitcheg_attack_cc1,f : { -1 }", + "/region0/pitcheg_delay_cc2,f : { -2 }", + "/region0/pitcheg_decay_cc3,f : { -3 }", + "/region0/pitcheg_hold_cc4,f : { -4 }", + "/region0/pitcheg_release_cc5,f : { -5 }", + "/region0/pitcheg_start_cc6,f : { -6 }", + "/region0/pitcheg_sustain_cc7,f : { -7 }", + }; + REQUIRE(messageList == expected); + } +} + +TEST_CASE("[Values] ampeg curve CC") +{ + Synth synth; + std::vector messageList; + Client client(&messageList); + client.setReceiveCallback(&simpleMessageReceiver); + + SECTION("Basic") + { + synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + sample=kick.wav + )"); + synth.dispatchMessage(client, 0, "/region0/ampeg_attack_curvecc1", "", nullptr); + synth.dispatchMessage(client, 0, "/region0/ampeg_delay_curvecc2", "", nullptr); + synth.dispatchMessage(client, 0, "/region0/ampeg_decay_curvecc3", "", nullptr); + synth.dispatchMessage(client, 0, "/region0/ampeg_hold_curvecc4", "", nullptr); + synth.dispatchMessage(client, 0, "/region0/ampeg_release_curvecc5", "", nullptr); + synth.dispatchMessage(client, 0, "/region0/ampeg_start_curvecc6", "", nullptr); + synth.dispatchMessage(client, 0, "/region0/ampeg_sustain_curvecc7", "", nullptr); + std::vector expected { + "/region0/ampeg_attack_curvecc1,f : { 0 }", + "/region0/ampeg_delay_curvecc2,f : { 0 }", + "/region0/ampeg_decay_curvecc3,f : { 0 }", + "/region0/ampeg_hold_curvecc4,f : { 0 }", + "/region0/ampeg_release_curvecc5,f : { 0 }", + "/region0/ampeg_start_curvecc6,f : { 0 }", + "/region0/ampeg_sustain_curvecc7,f : { 0 }", + }; + REQUIRE(messageList == expected); + } + + SECTION("Change curves") + { + synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + sample=kick.wav + ampeg_attack_curvecc1=1 ampeg_delay_curvecc2=2 ampeg_decay_curvecc3=3 + ampeg_hold_curvecc4=4 ampeg_release_curvecc5=5 ampeg_start_curvecc6=6 + ampeg_sustain_curvecc7=7 + )"); + synth.dispatchMessage(client, 0, "/region0/ampeg_attack_curvecc1", "", nullptr); + synth.dispatchMessage(client, 0, "/region0/ampeg_delay_curvecc2", "", nullptr); + synth.dispatchMessage(client, 0, "/region0/ampeg_decay_curvecc3", "", nullptr); + synth.dispatchMessage(client, 0, "/region0/ampeg_hold_curvecc4", "", nullptr); + synth.dispatchMessage(client, 0, "/region0/ampeg_release_curvecc5", "", nullptr); + synth.dispatchMessage(client, 0, "/region0/ampeg_start_curvecc6", "", nullptr); + synth.dispatchMessage(client, 0, "/region0/ampeg_sustain_curvecc7", "", nullptr); + std::vector expected { + "/region0/ampeg_attack_curvecc1,f : { 1 }", + "/region0/ampeg_delay_curvecc2,f : { 2 }", + "/region0/ampeg_decay_curvecc3,f : { 3 }", + "/region0/ampeg_hold_curvecc4,f : { 4 }", + "/region0/ampeg_release_curvecc5,f : { 5 }", + "/region0/ampeg_start_curvecc6,f : { 6 }", + "/region0/ampeg_sustain_curvecc7,f : { 7 }", + }; + REQUIRE(messageList == expected); + } + +} + +TEST_CASE("[Values] fileg curve CC") +{ + Synth synth; + std::vector messageList; + Client client(&messageList); + client.setReceiveCallback(&simpleMessageReceiver); + + SECTION("Basic") + { + synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + sample=kick.wav + )"); + synth.dispatchMessage(client, 0, "/region0/fileg_attack_curvecc1", "", nullptr); + synth.dispatchMessage(client, 0, "/region0/fileg_delay_curvecc2", "", nullptr); + synth.dispatchMessage(client, 0, "/region0/fileg_decay_curvecc3", "", nullptr); + synth.dispatchMessage(client, 0, "/region0/fileg_hold_curvecc4", "", nullptr); + synth.dispatchMessage(client, 0, "/region0/fileg_release_curvecc5", "", nullptr); + synth.dispatchMessage(client, 0, "/region0/fileg_start_curvecc6", "", nullptr); + synth.dispatchMessage(client, 0, "/region0/fileg_sustain_curvecc7", "", nullptr); + std::vector expected { }; + REQUIRE(messageList == expected); + } + + SECTION("Change curves") + { + synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + sample=kick.wav + fileg_attack_curvecc1=1 fileg_delay_curvecc2=2 fileg_decay_curvecc3=3 + fileg_hold_curvecc4=4 fileg_release_curvecc5=5 fileg_start_curvecc6=6 + fileg_sustain_curvecc7=7 + )"); + synth.dispatchMessage(client, 0, "/region0/fileg_attack_curvecc1", "", nullptr); + synth.dispatchMessage(client, 0, "/region0/fileg_delay_curvecc2", "", nullptr); + synth.dispatchMessage(client, 0, "/region0/fileg_decay_curvecc3", "", nullptr); + synth.dispatchMessage(client, 0, "/region0/fileg_hold_curvecc4", "", nullptr); + synth.dispatchMessage(client, 0, "/region0/fileg_release_curvecc5", "", nullptr); + synth.dispatchMessage(client, 0, "/region0/fileg_start_curvecc6", "", nullptr); + synth.dispatchMessage(client, 0, "/region0/fileg_sustain_curvecc7", "", nullptr); + std::vector expected { + "/region0/fileg_attack_curvecc1,f : { 1 }", + "/region0/fileg_delay_curvecc2,f : { 2 }", + "/region0/fileg_decay_curvecc3,f : { 3 }", + "/region0/fileg_hold_curvecc4,f : { 4 }", + "/region0/fileg_release_curvecc5,f : { 5 }", + "/region0/fileg_start_curvecc6,f : { 6 }", + "/region0/fileg_sustain_curvecc7,f : { 7 }", + }; + REQUIRE(messageList == expected); + } + +} + +TEST_CASE("[Values] pitcheg curve CC") +{ + Synth synth; + std::vector messageList; + Client client(&messageList); + client.setReceiveCallback(&simpleMessageReceiver); + + SECTION("Basic") + { + synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + sample=kick.wav + )"); + synth.dispatchMessage(client, 0, "/region0/pitcheg_attack_curvecc1", "", nullptr); + synth.dispatchMessage(client, 0, "/region0/pitcheg_delay_curvecc2", "", nullptr); + synth.dispatchMessage(client, 0, "/region0/pitcheg_decay_curvecc3", "", nullptr); + synth.dispatchMessage(client, 0, "/region0/pitcheg_hold_curvecc4", "", nullptr); + synth.dispatchMessage(client, 0, "/region0/pitcheg_release_curvecc5", "", nullptr); + synth.dispatchMessage(client, 0, "/region0/pitcheg_start_curvecc6", "", nullptr); + synth.dispatchMessage(client, 0, "/region0/pitcheg_sustain_curvecc7", "", nullptr); + std::vector expected { }; + REQUIRE(messageList == expected); + } + + SECTION("Change curves") + { + synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + sample=kick.wav + pitcheg_attack_curvecc1=1 pitcheg_delay_curvecc2=2 pitcheg_decay_curvecc3=3 + pitcheg_hold_curvecc4=4 pitcheg_release_curvecc5=5 pitcheg_start_curvecc6=6 + pitcheg_sustain_curvecc7=7 + )"); + synth.dispatchMessage(client, 0, "/region0/pitcheg_attack_curvecc1", "", nullptr); + synth.dispatchMessage(client, 0, "/region0/pitcheg_delay_curvecc2", "", nullptr); + synth.dispatchMessage(client, 0, "/region0/pitcheg_decay_curvecc3", "", nullptr); + synth.dispatchMessage(client, 0, "/region0/pitcheg_hold_curvecc4", "", nullptr); + synth.dispatchMessage(client, 0, "/region0/pitcheg_release_curvecc5", "", nullptr); + synth.dispatchMessage(client, 0, "/region0/pitcheg_start_curvecc6", "", nullptr); + synth.dispatchMessage(client, 0, "/region0/pitcheg_sustain_curvecc7", "", nullptr); + std::vector expected { + "/region0/pitcheg_attack_curvecc1,f : { 1 }", + "/region0/pitcheg_delay_curvecc2,f : { 2 }", + "/region0/pitcheg_decay_curvecc3,f : { 3 }", + "/region0/pitcheg_hold_curvecc4,f : { 4 }", + "/region0/pitcheg_release_curvecc5,f : { 5 }", + "/region0/pitcheg_start_curvecc6,f : { 6 }", + "/region0/pitcheg_sustain_curvecc7,f : { 7 }", + }; + REQUIRE(messageList == expected); + } + +} + TEST_CASE("[Values] Filter stacking and cutoffs") { Synth synth; From 749e5c284556dca4d5485b726ec4639b4614fed3 Mon Sep 17 00:00:00 2001 From: Paul Fd Date: Sat, 30 Dec 2023 10:04:32 +0100 Subject: [PATCH 27/65] Fix CMake for older versions --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 04f361476..f8559861a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -17,7 +17,7 @@ set(PROJECT_REPOSITORY https://github.com/sfztools/sfizz) set(CMAKE_MODULE_PATH "${CMAKE_MODULE_PATH};${CMAKE_CURRENT_SOURCE_DIR}/cmake") # Ensure presence of Git submodules (when not using the source tarball) -if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/.git") +if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/.git" AND NOT CMAKE_VERSION VERSION_LESS "3.19.0") include(GitSubmoduleCheck) git_submodule_check(external/abseil-cpp) git_submodule_check(external/filesystem) From cd042c84e83b4075819d8ba992e34e7ca47dfcfd Mon Sep 17 00:00:00 2001 From: redtide Date: Wed, 3 Jan 2024 23:53:44 +0100 Subject: [PATCH 28/65] Updated wavpack --- external/st_audiofile/thirdparty/wavpack | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/external/st_audiofile/thirdparty/wavpack b/external/st_audiofile/thirdparty/wavpack index 36b08dbcb..07ffb331d 160000 --- a/external/st_audiofile/thirdparty/wavpack +++ b/external/st_audiofile/thirdparty/wavpack @@ -1 +1 @@ -Subproject commit 36b08dbcb1de136e9ae477e9f1e2b57c958fff18 +Subproject commit 07ffb331d23c16e2d2979370441f63ea722760bc From a2490ef63a2e4c7c3c8315f88628cad0b022fc03 Mon Sep 17 00:00:00 2001 From: redtide Date: Thu, 4 Jan 2024 09:35:50 +0100 Subject: [PATCH 29/65] Added SFIZZ_GIT_SUBMODULE_CHECK flag this flag it's necessary when updating a submodule, to avoid CMake to restore the previous state when configuring before a test build. --- CMakeLists.txt | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index f8559861a..2f08e6bd9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -16,17 +16,6 @@ set(PROJECT_REPOSITORY https://github.com/sfztools/sfizz) # External configuration CMake scripts set(CMAKE_MODULE_PATH "${CMAKE_MODULE_PATH};${CMAKE_CURRENT_SOURCE_DIR}/cmake") -# Ensure presence of Git submodules (when not using the source tarball) -if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/.git" AND NOT CMAKE_VERSION VERSION_LESS "3.19.0") - include(GitSubmoduleCheck) - git_submodule_check(external/abseil-cpp) - git_submodule_check(external/filesystem) - git_submodule_check(external/simde) - git_submodule_check(external/st_audiofile/thirdparty/dr_libs) - git_submodule_check(external/st_audiofile/thirdparty/libaiff) - git_submodule_check(external/st_audiofile/thirdparty/wavpack) -endif() - include(BuildType) # Build Options @@ -54,10 +43,23 @@ option_ex(SFIZZ_RELEASE_ASSERTS "Forced assertions in release builds" OFF) option_ex(SFIZZ_PROFILE_BUILD "Profile the build time" OFF) option_ex(SFIZZ_SNDFILE_STATIC "Link the sndfile library statically" OFF) option_ex(SFIZZ_ASAN "Use address sanitizer on all sfizz targets" OFF) +option_ex(SFIZZ_GIT_SUBMODULE_CHECK "Check Git submodules presence" ON) # Continuous Controller count (0 to 511) set(MIDI_CC_COUNT 512 CACHE STRING "Maximum number of managed Control Change messages") +# Ensure presence of Git submodules (when not using the source tarball) +if(SFIZZ_GIT_SUBMODULE_CHECK AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/.git" + AND NOT CMAKE_VERSION VERSION_LESS "3.19.0") + include(GitSubmoduleCheck) + git_submodule_check(external/abseil-cpp) + git_submodule_check(external/filesystem) + git_submodule_check(external/simde) + git_submodule_check(external/st_audiofile/thirdparty/dr_libs) + git_submodule_check(external/st_audiofile/thirdparty/libaiff) + git_submodule_check(external/st_audiofile/thirdparty/wavpack) +endif() + include(SfizzConfig) include(SfizzDeps) include(SfizzFaust) From 7633785567dfd9bd10c01250a7550292f5762f03 Mon Sep 17 00:00:00 2001 From: redtide Date: Thu, 4 Jan 2024 09:38:55 +0100 Subject: [PATCH 30/65] Added a PREFIX optional parameter to GitBuildId to use different constants names --- cmake/GitBuildID.cmake | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cmake/GitBuildID.cmake b/cmake/GitBuildID.cmake index a5f71c728..5419ca500 100644 --- a/cmake/GitBuildID.cmake +++ b/cmake/GitBuildID.cmake @@ -4,6 +4,7 @@ # Arguments: # SOURCE_DIR The root directory of the project, expected to be a Git repo # OUTPUT_FILE The file which gets written +# PREFIX An optional prefix for the constant name get_filename_component(OUTPUT_NAME "${OUTPUT_FILE}" NAME) get_filename_component(OUTPUT_DIR "${OUTPUT_FILE}" DIRECTORY) @@ -24,6 +25,6 @@ else() message("(Git Build ID) Error: could not find Git") endif() -file(WRITE "${OUTPUT_FILE}.temp" "const char* GitBuildId = \"${GIT_COMMIT_ID}\";\n") +file(WRITE "${OUTPUT_FILE}.temp" "const char* ${PREFIX}GitBuildId = \"${GIT_COMMIT_ID}\";\n") execute_process(COMMAND "${CMAKE_COMMAND}" "-E" "copy_if_different" "${OUTPUT_FILE}.temp" "${OUTPUT_FILE}") file(REMOVE "${OUTPUT_FILE}.temp") From a2f62eab0435e67f42f2dfc8746dce7944cc0f57 Mon Sep 17 00:00:00 2001 From: redtide Date: Thu, 4 Jan 2024 13:00:26 +0100 Subject: [PATCH 31/65] Changed GitBuildID.cmake to work as function --- cmake/GitBuildID.cmake | 38 +++++++++++++++++++++----------------- src/CMakeLists.txt | 20 ++++++++++---------- 2 files changed, 31 insertions(+), 27 deletions(-) diff --git a/cmake/GitBuildID.cmake b/cmake/GitBuildID.cmake index 5419ca500..d615bdb77 100644 --- a/cmake/GitBuildID.cmake +++ b/cmake/GitBuildID.cmake @@ -6,25 +6,29 @@ # OUTPUT_FILE The file which gets written # PREFIX An optional prefix for the constant name -get_filename_component(OUTPUT_NAME "${OUTPUT_FILE}" NAME) -get_filename_component(OUTPUT_DIR "${OUTPUT_FILE}" DIRECTORY) +find_package(Git QUIET) -message("(Git Build ID) Generating ${OUTPUT_NAME}") +function(make_git_build_id) + set(oneValueArgs SOURCE_DIR OUTPUT_FILE PREFIX) + cmake_parse_arguments(GBI "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) -find_package(Git QUIET) + get_filename_component(OUTPUT_NAME "${GBI_OUTPUT_FILE}" NAME) + get_filename_component(OUTPUT_DIR "${GBI_OUTPUT_FILE}" DIRECTORY) + message("(Git Build ID) Generating ${OUTPUT_NAME}") -file(MAKE_DIRECTORY "${OUTPUT_DIR}") + file(MAKE_DIRECTORY "${OUTPUT_DIR}") -if(GIT_FOUND) - execute_process(COMMAND "${GIT_EXECUTABLE}" "rev-parse" "--short" "HEAD" - WORKING_DIRECTORY "${SOURCE_DIR}" - OUTPUT_VARIABLE GIT_COMMIT_ID - OUTPUT_STRIP_TRAILING_WHITESPACE) -else() - set(GIT_COMMIT_ID "") - message("(Git Build ID) Error: could not find Git") -endif() + if(GIT_FOUND) + execute_process(COMMAND "${GIT_EXECUTABLE}" "rev-parse" "--short" "HEAD" + WORKING_DIRECTORY "${GBI_SOURCE_DIR}" + OUTPUT_VARIABLE GIT_COMMIT_ID + OUTPUT_STRIP_TRAILING_WHITESPACE) + else() + set(GIT_COMMIT_ID "") + message("(Git Build ID) Error: could not find Git") + endif() -file(WRITE "${OUTPUT_FILE}.temp" "const char* ${PREFIX}GitBuildId = \"${GIT_COMMIT_ID}\";\n") -execute_process(COMMAND "${CMAKE_COMMAND}" "-E" "copy_if_different" "${OUTPUT_FILE}.temp" "${OUTPUT_FILE}") -file(REMOVE "${OUTPUT_FILE}.temp") + file(WRITE "${GBI_OUTPUT_FILE}.temp" "const char* ${GBI_PREFIX}GitBuildId = \"${GIT_COMMIT_ID}\";\n") + execute_process(COMMAND "${CMAKE_COMMAND}" "-E" "copy_if_different" "${GBI_OUTPUT_FILE}.temp" "${GBI_OUTPUT_FILE}") + file(REMOVE "${GBI_OUTPUT_FILE}.temp") +endfunction() diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index b68e750c7..0a808e8f8 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -358,17 +358,17 @@ endif() 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 +include(GitBuildID) +make_git_build_id( + SOURCE_DIR "${PROJECT_SOURCE_DIR}" + OUTPUT_FILE "${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) + "${PROJECT_BINARY_DIR}/git-build-id/GitBuildId.c" +) +target_include_directories(sfizz_git_build_id PUBLIC "sfizz/git-build-id") +target_link_libraries(sfizz_static PRIVATE sfizz_git_build_id) # Preserve generated files (Faust) set_directory_properties(PROPERTIES CLEAN_NO_CUSTOM TRUE) From a927c5982101f00c90b67983e146265c9ce79de3 Mon Sep 17 00:00:00 2001 From: redtide Date: Sun, 7 Jan 2024 18:21:43 +0100 Subject: [PATCH 32/65] Revert "Changed GitBuildID.cmake to work as function" This reverts commit a2f62eab0435e67f42f2dfc8746dce7944cc0f57. --- cmake/GitBuildID.cmake | 38 +++++++++++++++++--------------------- src/CMakeLists.txt | 20 ++++++++++---------- 2 files changed, 27 insertions(+), 31 deletions(-) diff --git a/cmake/GitBuildID.cmake b/cmake/GitBuildID.cmake index d615bdb77..5419ca500 100644 --- a/cmake/GitBuildID.cmake +++ b/cmake/GitBuildID.cmake @@ -6,29 +6,25 @@ # OUTPUT_FILE The file which gets written # PREFIX An optional prefix for the constant name -find_package(Git QUIET) +get_filename_component(OUTPUT_NAME "${OUTPUT_FILE}" NAME) +get_filename_component(OUTPUT_DIR "${OUTPUT_FILE}" DIRECTORY) -function(make_git_build_id) - set(oneValueArgs SOURCE_DIR OUTPUT_FILE PREFIX) - cmake_parse_arguments(GBI "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) +message("(Git Build ID) Generating ${OUTPUT_NAME}") - get_filename_component(OUTPUT_NAME "${GBI_OUTPUT_FILE}" NAME) - get_filename_component(OUTPUT_DIR "${GBI_OUTPUT_FILE}" DIRECTORY) - message("(Git Build ID) Generating ${OUTPUT_NAME}") +find_package(Git QUIET) - file(MAKE_DIRECTORY "${OUTPUT_DIR}") +file(MAKE_DIRECTORY "${OUTPUT_DIR}") - if(GIT_FOUND) - execute_process(COMMAND "${GIT_EXECUTABLE}" "rev-parse" "--short" "HEAD" - WORKING_DIRECTORY "${GBI_SOURCE_DIR}" - OUTPUT_VARIABLE GIT_COMMIT_ID - OUTPUT_STRIP_TRAILING_WHITESPACE) - else() - set(GIT_COMMIT_ID "") - message("(Git Build ID) Error: could not find Git") - endif() +if(GIT_FOUND) + execute_process(COMMAND "${GIT_EXECUTABLE}" "rev-parse" "--short" "HEAD" + WORKING_DIRECTORY "${SOURCE_DIR}" + OUTPUT_VARIABLE GIT_COMMIT_ID + OUTPUT_STRIP_TRAILING_WHITESPACE) +else() + set(GIT_COMMIT_ID "") + message("(Git Build ID) Error: could not find Git") +endif() - file(WRITE "${GBI_OUTPUT_FILE}.temp" "const char* ${GBI_PREFIX}GitBuildId = \"${GIT_COMMIT_ID}\";\n") - execute_process(COMMAND "${CMAKE_COMMAND}" "-E" "copy_if_different" "${GBI_OUTPUT_FILE}.temp" "${GBI_OUTPUT_FILE}") - file(REMOVE "${GBI_OUTPUT_FILE}.temp") -endfunction() +file(WRITE "${OUTPUT_FILE}.temp" "const char* ${PREFIX}GitBuildId = \"${GIT_COMMIT_ID}\";\n") +execute_process(COMMAND "${CMAKE_COMMAND}" "-E" "copy_if_different" "${OUTPUT_FILE}.temp" "${OUTPUT_FILE}") +file(REMOVE "${OUTPUT_FILE}.temp") diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 0a808e8f8..b68e750c7 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -358,17 +358,17 @@ endif() add_library(sfizz::sfizz ALIAS sfizz_static) # Git build identifier -include(GitBuildID) -make_git_build_id( - SOURCE_DIR "${PROJECT_SOURCE_DIR}" - OUTPUT_FILE "${PROJECT_BINARY_DIR}/git-build-id/GitBuildId.c" -) -add_library(sfizz_git_build_id STATIC EXCLUDE_FROM_ALL +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") -target_link_libraries(sfizz_static PRIVATE sfizz_git_build_id) + "${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) From 6a4add37cbdabe4cf3a40a9a971d5a5e61071a1a Mon Sep 17 00:00:00 2001 From: Paul Ferrand Date: Sun, 14 Jan 2024 21:54:39 +0100 Subject: [PATCH 33/65] Update CHANGELOG.md --- CHANGELOG.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 63af4edd0..2174e1975 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,25 @@ This project tries to adhere to [Semantic Versioning][2]. ## [Unreleased] +### Added + +- Support for curvecc opcodes on v1 EGs (@PythonBlue) +- Support for `lotimer/hitimer` (@essej) + +### Changed + +- Allow polyphonic aftertouch (cc 130) to respect the note number, which improves e.g. the choking logic (@essej) + +### Fixed + +- Adjusted the gain compensation on pan stages (@cvde) +- Ensure that voices are cleaned up before being force-reused (@iv-m) +- Fixed a bug in the wavpack wrapper for multichannel files (@KKQ-KKQ) +- Fixed a bug in the way the tuning root was computed (@KKQ-KKQ) +- Fixed a bug where samples would be loaded in RAM twice when using `hint_ram_based=1` +- Fixed a bug where quasi-simultaneous notes would not properly choke each other +- Don't send a note off even if a note was choked by a CC event. + ## [1.2.2] - 2023-08-25 ### Added From 4d029431ae9d88d3477cc7f4196860f68c0a620e Mon Sep 17 00:00:00 2001 From: Paul Ferrand Date: Sun, 14 Jan 2024 21:56:30 +0100 Subject: [PATCH 34/65] Added release and date to changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2174e1975..3ea9f8c9b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ This project tries to adhere to [Semantic Versioning][2]. ## [Unreleased] +## [1.2.3] - 2024-01-15 + ### Added - Support for curvecc opcodes on v1 EGs (@PythonBlue) From 4e70dc0bef53b41f2853ed46e26f5911114c92d0 Mon Sep 17 00:00:00 2001 From: Paul Fd Date: Sun, 14 Jan 2024 21:23:08 +0100 Subject: [PATCH 35/65] Update version --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 2f08e6bd9..6b45c307b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -8,7 +8,7 @@ endif() project(libsfizz LANGUAGES CXX C - VERSION 1.2.2 + VERSION 1.2.3 ) set(PROJECT_DESCRIPTION "A library to load SFZ description files and use them to render music.") set(PROJECT_REPOSITORY https://github.com/sfztools/sfizz) From bb50adc882363d6e9230ab3320b6cd8a8bedafcd Mon Sep 17 00:00:00 2001 From: Paul Ferrand Date: Tue, 2 Jan 2024 18:54:00 +0100 Subject: [PATCH 36/65] Refactor the messaging system Trading off complexity when parsing/replying to the messages for complexity in some templated and overloaded code blocks. --- src/sfizz/Defaults.h | 45 + src/sfizz/Synth.h | 2 +- src/sfizz/SynthMessaging.cpp | 2634 ++++++++++------------------------ src/sfizz/utility/Size.h | 25 + tests/OpcodeT.cpp | 9 + tests/RegionValuesT.cpp | 165 ++- 6 files changed, 942 insertions(+), 1938 deletions(-) create mode 100644 src/sfizz/utility/Size.h diff --git a/src/sfizz/Defaults.h b/src/sfizz/Defaults.h index 23c06cda5..a3c1a8b62 100644 --- a/src/sfizz/Defaults.h +++ b/src/sfizz/Defaults.h @@ -126,6 +126,51 @@ struct OpcodeSpec } operator T() const { return normalizeInput(defaultInputValue); } + + + /** + * @brief Demormalizes an input as needed for the spec + * + * @tparam U + * @param input + * @return U + */ + template + typename std::enable_if::value, U>::type denormalizeInput(U input) const + { + constexpr int needsOperation { + kNormalizePercent | + kNormalizeMidi | + kNormalizeBend | + kDb2Mag + }; + + if (!(flags & needsOperation)) + 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 & kNormalizeBend) + return static_cast(input * U(8191)); + else if (flags & kDb2Mag) + return static_cast(mag2db(input)); + + return input; + } + + /** + * @brief Normalizes an input as needed for the spec + * + * @tparam U + * @param input + * @return U + */ + template + typename std::enable_if::value, U>::type denormalizeInput(U input) const + { + return input; + } }; namespace Default diff --git a/src/sfizz/Synth.h b/src/sfizz/Synth.h index eb98a4535..7725090a9 100644 --- a/src/sfizz/Synth.h +++ b/src/sfizz/Synth.h @@ -740,8 +740,8 @@ class Synth final { */ void setBroadcastCallback(sfizz_receive_t* broadcast, void* data); -private: struct Impl; +private: std::unique_ptr impl_; LEAK_DETECTOR(Synth); diff --git a/src/sfizz/SynthMessaging.cpp b/src/sfizz/SynthMessaging.cpp index dbb0c06b4..8b69c679e 100644 --- a/src/sfizz/SynthMessaging.cpp +++ b/src/sfizz/SynthMessaging.cpp @@ -4,1829 +4,842 @@ // 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 "CCMap.h" +#include "Config.h" +#include "Defaults.h" +#include "EQDescription.h" +#include "FileId.h" +#include "FilterDescription.h" +#include "FlexEGDescription.h" +#include "LFOCommon.h" +#include "LFODescription.h" +#include "Range.h" +#include "SfzFilter.h" +#include "SfzHelpers.h" #include "SynthPrivate.h" #include "FilePool.h" #include "Curve.h" #include "MidiState.h" #include "SynthConfig.h" +#include "TriggerEvent.h" +#include "absl/strings/string_view.h" +#include "absl/types/optional.h" +#include "absl/types/span.h" +#include "modulations/ModId.h" +#include "modulations/ModKey.h" +#include "modulations/ModKeyHash.h" #include "utility/StringViewHelpers.h" #include +#include "utility/Size.h" +#include #include +#include +#include // TODO: `ccModDepth` and `ccModParameters` are O(N), need better implementation namespace sfz { static constexpr unsigned maxIndices = 8; -static bool extractMessage(const char* pattern, const char* path, unsigned* indices); -static uint64_t hashMessagePath(const char* path, const char* sig); - -void sfz::Synth::dispatchMessage(Client& client, int delay, const char* path, const char* sig, const sfizz_arg_t* args) +static uint64_t hashMessagePath(const char* path, const char* sig) { - UNUSED(args); - Impl& impl = *impl_; - unsigned indices[maxIndices]; - - switch (hashMessagePath(path, sig)) { - #define MATCH(p, s) case hash(p "," s): \ - if (extractMessage(p, path, indices) && !strcmp(sig, s)) - - #define GET_REGION_OR_BREAK(idx) \ - if (idx >= impl.layers_.size()) \ - break; \ - Layer& layer = *impl.layers_[idx]; \ - const Region& region = layer.getRegion(); - - #define GET_FILTER_OR_BREAK(idx) \ - if (idx >= region.filters.size()) \ - break; \ - const auto& filter = region.filters[idx]; - - #define GET_EQ_OR_BREAK(idx) \ - if (idx >= region.equalizers.size()) \ - break; \ - const auto& eq = region.equalizers[idx]; - - #define GET_LFO_OR_BREAK(idx) \ - if (idx >= region.lfos.size()) \ - 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; - - //---------------------------------------------------------------------- - - MATCH("/num_regions", "") { - client.receive<'i'>(delay, path, int(impl.layers_.size())); - } break; - - MATCH("/num_groups", "") { - client.receive<'i'>(delay, path, impl.numGroups_); - } break; - - MATCH("/num_masters", "") { - client.receive<'i'>(delay, path, impl.numMasters_); - } break; - - MATCH("/num_curves", "") { - client.receive<'i'>(delay, path, int(impl.resources_.getCurves().getNumCurves())); - } break; - - MATCH("/num_samples", "") { - 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; - - MATCH("/num_outputs", "") { - client.receive<'i'>(delay, path, impl.numOutputs_); - } break; - - //---------------------------------------------------------------------- - - MATCH("/key/slots", "") { - const BitArray<128>& keys = impl.keySlots_; - sfizz_blob_t blob { keys.data(), static_cast(keys.byte_size()) }; - client.receive<'b'>(delay, path, &blob); - } break; - - MATCH("/key&/label", "") { - if (indices[0] >= 128) - break; - const std::string* label = impl.getKeyLabel(indices[0]); - client.receive<'s'>(delay, path, label ? label->c_str() : ""); - } break; - - //---------------------------------------------------------------------- - - MATCH("/root_path", "") { - client.receive<'s'>(delay, path, impl.rootPath_.c_str()); - } break; - - MATCH("/image", "") { - client.receive<'s'>(delay, path, impl.image_.c_str()); - } break; - - MATCH("/image_controls", "") { - client.receive<'s'>(delay, path, impl.image_controls_.c_str()); - } break; - - //---------------------------------------------------------------------- - - MATCH("/sw/last/slots", "") { - const BitArray<128>& switches = impl.swLastSlots_; - sfizz_blob_t blob { switches.data(), static_cast(switches.byte_size()) }; - client.receive<'b'>(delay, path, &blob); - } break; - - MATCH("/sw/last/current", "") { - if (impl.currentSwitch_) - client.receive<'i'>(delay, path, *impl.currentSwitch_); - else - client.receive<'N'>(delay, path, {}); - } break; - - MATCH("/sw/last/&/label", "") { - if (indices[0] >= 128) - break; - const std::string* label = impl.getKeyswitchLabel(indices[0]); - client.receive<'s'>(delay, path, label ? label->c_str() : ""); - } break; - - //---------------------------------------------------------------------- - - MATCH("/cc/slots", "") { - const BitArray& ccs = impl.currentUsedCCs_; - sfizz_blob_t blob { ccs.data(), static_cast(ccs.byte_size()) }; - client.receive<'b'>(delay, path, &blob); - } break; - - MATCH("/cc&/default", "") { - if (indices[0] >= config::numCCs) - break; - client.receive<'f'>(delay, path, impl.defaultCCValues_[indices[0]]); - } break; - - MATCH("/cc&/value", "") { - if (indices[0] >= config::numCCs) - break; - // Note: result value is not frame-exact - client.receive<'f'>(delay, path, impl.resources_.getMidiState().getCCValue(indices[0])); - } break; - - MATCH("/cc&/value", "f") { - if (indices[0] >= config::numCCs) - break; - impl.resources_.getMidiState().ccEvent(delay, indices[0], args[0].f); - } break; - - MATCH("/cc&/label", "") { - if (indices[0] >= config::numCCs) - break; - const std::string* label = impl.getCCLabel(indices[0]); - client.receive<'s'>(delay, path, label ? label->c_str() : ""); - } break; - - MATCH("/cc/changed", "") { - const BitArray& changedCCs = impl.changedCCsThisCycle_; - sfizz_blob_t blob { changedCCs.data(), static_cast(changedCCs.byte_size()) }; - client.receive<'b'>(delay, path, &blob); - } break; - - MATCH("/cc/changed~", "") { - const BitArray& changedCCs = impl.changedCCsLastCycle_; - sfizz_blob_t blob { changedCCs.data(), static_cast(changedCCs.byte_size()) }; - 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("/aftertouch", "") { - client.receive<'f'>(delay, path, impl.resources_.getMidiState().getChannelAftertouch()); - } break; - - MATCH("/poly_aftertouch/&", "") { - if (indices[0] > 127) - break; - // Note: result value is not frame-exact - client.receive<'f'>(delay, path, impl.resources_.getMidiState().getPolyAftertouch(indices[0])); - } break; - - MATCH("/pitch_bend", "") { - // Note: result value is not frame-exact - client.receive<'f'>(delay, path, impl.resources_.getMidiState().getPitchBend()); - } break; - - //---------------------------------------------------------------------- - - MATCH("/mem/buffers", "") { - uint64_t total = BufferCounter::counter().getTotalBytes(); - client.receive<'h'>(delay, path, total); - } break; - - //---------------------------------------------------------------------- - - MATCH("/region&/delay", "") { - GET_REGION_OR_BREAK(indices[0]) - client.receive<'f'>(delay, path, region.delay); - } break; - - MATCH("/region&/sample", "") { - GET_REGION_OR_BREAK(indices[0]) - client.receive<'s'>(delay, path, region.sampleId->filename().c_str()); - } break; - - MATCH("/region&/direction", "") { - GET_REGION_OR_BREAK(indices[0]) - if (region.sampleId->isReverse()) - client.receive<'s'>(delay, path, "reverse"); - else - client.receive<'s'>(delay, path, "forward"); - } break; - - MATCH("/region&/delay_random", "") { - GET_REGION_OR_BREAK(indices[0]) - client.receive<'f'>(delay, path, region.delayRandom); - } break; - - MATCH("/region&/delay_cc&", "") { - GET_REGION_OR_BREAK(indices[0]) - client.receive<'f'>(delay, path, region.delayCC.getWithDefault(indices[1])); - } break; - - MATCH("/region&/offset", "") { - GET_REGION_OR_BREAK(indices[0]) - client.receive<'h'>(delay, path, region.offset); - } break; - - MATCH("/region&/offset_random", "") { - GET_REGION_OR_BREAK(indices[0]) - client.receive<'h'>(delay, path, region.offsetRandom); - } break; - - MATCH("/region&/offset_cc&", "") { - GET_REGION_OR_BREAK(indices[0]) - client.receive<'h'>(delay, path, region.offsetCC.getWithDefault(indices[1])); - } break; - - MATCH("/region&/end", "") { - GET_REGION_OR_BREAK(indices[0]) - client.receive<'h'>(delay, path, region.sampleEnd); - } break; - - MATCH("/region&/end_cc&", "") { - GET_REGION_OR_BREAK(indices[0]) - client.receive<'h'>(delay, path, region.endCC.getWithDefault(indices[1])); - } break; - - MATCH("/region&/enabled", "") { - GET_REGION_OR_BREAK(indices[0]) - if (region.disabled()) { - client.receive<'F'>(delay, path, {}); - } else { - client.receive<'T'>(delay, path, {}); - } - } break; - - MATCH("/region&/trigger_on_note", "") { - GET_REGION_OR_BREAK(indices[0]) - if (region.triggerOnNote) { - client.receive<'T'>(delay, path, {}); - } else { - client.receive<'F'>(delay, path, {}); - } - } break; - - MATCH("/region&/trigger_on_cc", "") { - GET_REGION_OR_BREAK(indices[0]) - if (region.triggerOnCC) { - client.receive<'T'>(delay, path, {}); - } else { - client.receive<'F'>(delay, path, {}); - } - } break; - - MATCH("/region&/use_timer_range", "") { - GET_REGION_OR_BREAK(indices[0]) - if (region.useTimerRange) { - client.receive<'T'>(delay, path, {}); - } else { - client.receive<'F'>(delay, path, {}); - } - } break; - - MATCH("/region&/count", "") { - GET_REGION_OR_BREAK(indices[0]) - if (region.sampleCount) - client.receive<'h'>(delay, path, *region.sampleCount); - else - client.receive<'N'>(delay, path, {}); - } break; - - MATCH("/region&/loop_range", "") { - GET_REGION_OR_BREAK(indices[0]) - sfizz_arg_t args[2]; - args[0].h = region.loopRange.getStart(); - args[1].h = region.loopRange.getEnd(); - client.receive(delay, path, "hh", args); - } break; - - MATCH("/region&/loop_start_cc&", "") { - GET_REGION_OR_BREAK(indices[0]) - client.receive<'h'>(delay, path, region.loopStartCC.getWithDefault(indices[1])); - } break; - - MATCH("/region&/loop_end_cc&", "") { - GET_REGION_OR_BREAK(indices[0]) - client.receive<'h'>(delay, path, region.loopEndCC.getWithDefault(indices[1])); - } break; - - MATCH("/region&/loop_mode", "") { - GET_REGION_OR_BREAK(indices[0]) - if (!region.loopMode) { - client.receive<'s'>(delay, path, "no_loop"); - break; - } - - switch (*region.loopMode) { - case LoopMode::no_loop: - client.receive<'s'>(delay, path, "no_loop"); - break; - case LoopMode::loop_continuous: - client.receive<'s'>(delay, path, "loop_continuous"); - break; - case LoopMode::loop_sustain: - client.receive<'s'>(delay, path, "loop_sustain"); - break; - case LoopMode::one_shot: - client.receive<'s'>(delay, path, "one_shot"); - break; - } - } break; - - MATCH("/region&/loop_crossfade", "") { - GET_REGION_OR_BREAK(indices[0]) - client.receive<'f'>(delay, path, region.loopCrossfade); - } break; - - MATCH("/region&/loop_count", "") { - GET_REGION_OR_BREAK(indices[0]) - if (region.loopCount) - client.receive<'h'>(delay, path, *region.loopCount); - else - client.receive<'N'>(delay, path, {}); - } break; - - MATCH("/region&/output", "") { - GET_REGION_OR_BREAK(indices[0]) - client.receive<'i'>(delay, path, region.output); - } break; - - MATCH("/region&/group", "") { - GET_REGION_OR_BREAK(indices[0]) - client.receive<'h'>(delay, path, region.group); - } break; - - MATCH("/region&/off_by", "") { - GET_REGION_OR_BREAK(indices[0]) - if (!region.offBy) { - client.receive<'N'>(delay, path, {}); - } else { - client.receive<'h'>(delay, path, *region.offBy); - } - } break; - - MATCH("/region&/off_mode", "") { - GET_REGION_OR_BREAK(indices[0]) - switch (region.offMode) { - case OffMode::time: - client.receive<'s'>(delay, path, "time"); - break; - case OffMode::normal: - client.receive<'s'>(delay, path, "normal"); - break; - case OffMode::fast: - client.receive<'s'>(delay, path, "fast"); - break; - } - } break; - - MATCH("/region&/key_range", "") { - GET_REGION_OR_BREAK(indices[0]) - sfizz_arg_t args[2]; - args[0].i = region.keyRange.getStart(); - args[1].i = region.keyRange.getEnd(); - client.receive(delay, path, "ii", args); - } break; - - MATCH("/region&/off_time", "") { - GET_REGION_OR_BREAK(indices[0]) - client.receive<'f'>(delay, path, region.offTime); - } break; - - MATCH("/region&/pitch_keycenter", "") { - GET_REGION_OR_BREAK(indices[0]) - client.receive<'i'>(delay, path, region.pitchKeycenter); - } break; - - MATCH("/region&/vel_range", "") { - GET_REGION_OR_BREAK(indices[0]) - sfizz_arg_t args[2]; - args[0].f = region.velocityRange.getStart(); - args[1].f = region.velocityRange.getEnd(); - client.receive(delay, path, "ff", args); - } break; - - MATCH("/region&/bend_range", "") { - GET_REGION_OR_BREAK(indices[0]) - sfizz_arg_t args[2]; - args[0].f = region.bendRange.getStart(); - args[1].f = region.bendRange.getEnd(); - client.receive(delay, path, "ff", args); - } break; - - MATCH("/region&/program_range", "") { - GET_REGION_OR_BREAK(indices[0]) - sfizz_arg_t args[2]; - args[0].i = region.programRange.getStart(); - args[1].i = region.programRange.getEnd(); - client.receive(delay, path, "ii", args); - } break; - - MATCH("/region&/cc_range&", "") { - GET_REGION_OR_BREAK(indices[0]) - sfizz_arg_t args[2]; - const auto& conditions = region.ccConditions.getWithDefault(indices[1]); - args[0].f = conditions.getStart(); - args[1].f = conditions.getEnd(); - client.receive(delay, path, "ff", args); - } break; - - MATCH("/region&/sw_last", "") { - GET_REGION_OR_BREAK(indices[0]) - if (region.lastKeyswitch) { - client.receive<'i'>(delay, path, *region.lastKeyswitch); - } else if (region.lastKeyswitchRange) { - sfizz_arg_t args[2]; - args[0].i = region.lastKeyswitchRange->getStart(); - args[1].i = region.lastKeyswitchRange->getEnd(); - client.receive(delay, path, "ii", args); - } else { - client.receive<'N'>(delay, path, {}); - } - - } break; - - MATCH("/region&/sw_label", "") { - GET_REGION_OR_BREAK(indices[0]) - if (region.keyswitchLabel) { - client.receive<'s'>(delay, path, region.keyswitchLabel->c_str()); - } else { - client.receive<'N'>(delay, path, {}); - } - } break; - - MATCH("/region&/sw_up", "") { - GET_REGION_OR_BREAK(indices[0]) - if (region.upKeyswitch) { - client.receive<'i'>(delay, path, *region.upKeyswitch); - } else { - client.receive<'N'>(delay, path, {}); - } - } break; - - MATCH("/region&/sw_down", "") { - GET_REGION_OR_BREAK(indices[0]) - if (region.downKeyswitch) { - client.receive<'i'>(delay, path, *region.downKeyswitch); - } else { - client.receive<'N'>(delay, path, {}); - } - } break; - - MATCH("/region&/sw_previous", "") { - GET_REGION_OR_BREAK(indices[0]) - if (region.previousKeyswitch) { - client.receive<'i'>(delay, path, *region.previousKeyswitch); - } else { - client.receive<'N'>(delay, path, {}); - } - } break; - - MATCH("/region&/sw_vel", "") { - GET_REGION_OR_BREAK(indices[0]) - switch (region.velocityOverride) { - case VelocityOverride::current: - client.receive<'s'>(delay, path, "current"); - break; - case VelocityOverride::previous: - client.receive<'s'>(delay, path, "previous"); - break; - } - } break; - - MATCH("/region&/chanaft_range", "") { - GET_REGION_OR_BREAK(indices[0]) - sfizz_arg_t args[2]; - args[0].f = region.aftertouchRange.getStart(); - args[1].f = region.aftertouchRange.getEnd(); - client.receive(delay, path, "ff", args); - } break; - - MATCH("/region&/polyaft_range", "") { - GET_REGION_OR_BREAK(indices[0]) - sfizz_arg_t args[2]; - args[0].f = region.polyAftertouchRange.getStart(); - args[1].f = region.polyAftertouchRange.getEnd(); - client.receive(delay, path, "ff", args); - } break; - - MATCH("/region&/bpm_range", "") { - GET_REGION_OR_BREAK(indices[0]) - sfizz_arg_t args[2]; - args[0].f = region.bpmRange.getStart(); - args[1].f = region.bpmRange.getEnd(); - client.receive(delay, path, "ff", args); - } break; - - MATCH("/region&/rand_range", "") { - GET_REGION_OR_BREAK(indices[0]) - sfizz_arg_t args[2]; - args[0].f = region.randRange.getStart(); - args[1].f = region.randRange.getEnd(); - client.receive(delay, path, "ff", args); - } break; - - MATCH("/region&/seq_length", "") { - GET_REGION_OR_BREAK(indices[0]) - client.receive<'h'>(delay, path, region.sequenceLength); - } break; - - MATCH("/region&/seq_position", "") { - GET_REGION_OR_BREAK(indices[0]) - client.receive<'h'>(delay, path, region.sequencePosition); - } break; - - MATCH("/region&/trigger", "") { - GET_REGION_OR_BREAK(indices[0]) - switch (region.trigger) { - case Trigger::attack: - client.receive<'s'>(delay, path, "attack"); - break; - case Trigger::first: - client.receive<'s'>(delay, path, "first"); - break; - case Trigger::release: - client.receive<'s'>(delay, path, "release"); - break; - case Trigger::release_key: - client.receive<'s'>(delay, path, "release_key"); - break; - case Trigger::legato: - client.receive<'s'>(delay, path, "legato"); - break; - } - } break; - - MATCH("/region&/start_cc_range&", "") { - GET_REGION_OR_BREAK(indices[0]) - auto trigger = region.ccTriggers.get(indices[1]); - if (trigger) { - sfizz_arg_t args[2]; - args[0].f = trigger->getStart(); - args[1].f = trigger->getEnd(); - client.receive(delay, path, "ff", args); - } else { - client.receive<'N'>(delay, path, {}); - } - } break; - - MATCH("/region&/volume", "") { - GET_REGION_OR_BREAK(indices[0]) - client.receive<'f'>(delay, path, region.volume); - } break; - - MATCH("/region&/volume_cc&", "") { - GET_REGION_OR_BREAK(indices[0]) - auto value = region.ccModDepth(indices[1], ModId::Volume); - if (value) { - client.receive<'f'>(delay, path, *value); - } else { - client.receive<'N'>(delay, path, {}); - } - } break; - - MATCH("/region&/volume_stepcc&", "") { - GET_REGION_OR_BREAK(indices[0]) - auto params = region.ccModParameters(indices[1], ModId::Volume); - if (params) { - client.receive<'f'>(delay, path, params->step); - } else { - client.receive<'N'>(delay, path, {}); - } - } break; - - MATCH("/region&/volume_smoothcc&", "") { - GET_REGION_OR_BREAK(indices[0]) - auto params = region.ccModParameters(indices[1], ModId::Volume); - if (params) { - client.receive<'i'>(delay, path, params->smooth); - } else { - client.receive<'N'>(delay, path, {}); - } - } break; - - MATCH("/region&/volume_curvecc&", "") { - GET_REGION_OR_BREAK(indices[0]) - auto params = region.ccModParameters(indices[1], ModId::Volume); - if (params) { - client.receive<'i'>(delay, path, params->curve); - } else { - client.receive<'N'>(delay, path, {}); - } - } break; - - MATCH("/region&/pan", "") { - GET_REGION_OR_BREAK(indices[0]) - client.receive<'f'>(delay, path, region.pan * 100.0f); - } break; - - MATCH("/region&/pan_cc&", "") { - GET_REGION_OR_BREAK(indices[0]) - auto value = region.ccModDepth(indices[1], ModId::Pan); - if (value) { - client.receive<'f'>(delay, path, *value * 100.0f); - } else { - client.receive<'N'>(delay, path, {}); - } - } break; - - MATCH("/region&/pan_stepcc&", "") { - GET_REGION_OR_BREAK(indices[0]) - auto params = region.ccModParameters(indices[1], ModId::Pan); - if (params) { - client.receive<'f'>(delay, path, params->step * 100.0f); - } else { - client.receive<'N'>(delay, path, {}); - } - } break; - - MATCH("/region&/pan_smoothcc&", "") { - GET_REGION_OR_BREAK(indices[0]) - auto params = region.ccModParameters(indices[1], ModId::Pan); - if (params) { - client.receive<'i'>(delay, path, params->smooth); - } else { - client.receive<'N'>(delay, path, {}); - } - } break; - - MATCH("/region&/pan_curvecc&", "") { - GET_REGION_OR_BREAK(indices[0]) - auto params = region.ccModParameters(indices[1], ModId::Pan); - if (params) { - client.receive<'i'>(delay, path, params->curve); - } else { - client.receive<'N'>(delay, path, {}); - } - } break; - - MATCH("/region&/width", "") { - GET_REGION_OR_BREAK(indices[0]) - client.receive<'f'>(delay, path, region.width * 100.0f); - } break; - - MATCH("/region&/width_cc&", "") { - GET_REGION_OR_BREAK(indices[0]) - auto value = region.ccModDepth(indices[1], ModId::Width); - if (value) { - client.receive<'f'>(delay, path, *value * 100.0f); - } else { - client.receive<'N'>(delay, path, {}); - } - } break; - - MATCH("/region&/width_stepcc&", "") { - GET_REGION_OR_BREAK(indices[0]) - auto params = region.ccModParameters(indices[1], ModId::Width); - if (params) { - client.receive<'f'>(delay, path, params->step * 100.0f); - } else { - client.receive<'N'>(delay, path, {}); - } - } break; - - MATCH("/region&/width_smoothcc&", "") { - GET_REGION_OR_BREAK(indices[0]) - auto params = region.ccModParameters(indices[1], ModId::Width); - if (params) { - client.receive<'i'>(delay, path, params->smooth); - } else { - client.receive<'N'>(delay, path, {}); - } - } break; - - MATCH("/region&/width_curvecc&", "") { - GET_REGION_OR_BREAK(indices[0]) - auto params = region.ccModParameters(indices[1], ModId::Width); - if (params) { - client.receive<'i'>(delay, path, params->curve); - } else { - client.receive<'N'>(delay, path, {}); - } - } break; - - MATCH("/region&/timer_range", "") { - GET_REGION_OR_BREAK(indices[0]) - sfizz_arg_t args[2]; - args[0].f = region.timerRange.getStart(); - args[1].f = region.timerRange.getEnd(); - client.receive(delay, path, "ff", args); - } break; - - MATCH("/region&/position", "") { - GET_REGION_OR_BREAK(indices[0]) - client.receive<'f'>(delay, path, region.position * 100.0f); - } break; - - MATCH("/region&/position_cc&", "") { - GET_REGION_OR_BREAK(indices[0]) - auto value = region.ccModDepth(indices[1], ModId::Position); - if (value) { - client.receive<'f'>(delay, path, *value * 100.0f); - } else { - client.receive<'N'>(delay, path, {}); - } - } break; - - MATCH("/region&/position_stepcc&", "") { - GET_REGION_OR_BREAK(indices[0]) - auto params = region.ccModParameters(indices[1], ModId::Position); - if (params) { - client.receive<'f'>(delay, path, params->step * 100.0f); - } else { - client.receive<'N'>(delay, path, {}); - } - } break; - - MATCH("/region&/position_smoothcc&", "") { - GET_REGION_OR_BREAK(indices[0]) - auto params = region.ccModParameters(indices[1], ModId::Position); - if (params) { - client.receive<'i'>(delay, path, params->smooth); - } else { - client.receive<'N'>(delay, path, {}); - } - } break; - - MATCH("/region&/position_curvecc&", "") { - GET_REGION_OR_BREAK(indices[0]) - auto params = region.ccModParameters(indices[1], ModId::Position); - if (params) { - client.receive<'i'>(delay, path, params->curve); - } else { - client.receive<'N'>(delay, path, {}); - } - } break; - - MATCH("/region&/amplitude", "") { - GET_REGION_OR_BREAK(indices[0]) - client.receive<'f'>(delay, path, region.amplitude * 100.0f); - } break; - - MATCH("/region&/amplitude_cc&", "") { - GET_REGION_OR_BREAK(indices[0]) - auto value = region.ccModDepth(indices[1], ModId::Amplitude); - if (value) { - client.receive<'f'>(delay, path, *value * 100.0f); - } else { - client.receive<'N'>(delay, path, {}); - } - } break; - - MATCH("/region&/amplitude_stepcc&", "") { - GET_REGION_OR_BREAK(indices[0]) - auto params = region.ccModParameters(indices[1], ModId::Amplitude); - if (params) { - client.receive<'f'>(delay, path, params->step * 100.0f); - } else { - client.receive<'N'>(delay, path, {}); - } - } break; - - MATCH("/region&/amplitude_smoothcc&", "") { - GET_REGION_OR_BREAK(indices[0]) - auto params = region.ccModParameters(indices[1], ModId::Amplitude); - if (params) { - client.receive<'i'>(delay, path, params->smooth); - } else { - client.receive<'N'>(delay, path, {}); - } - } break; - - MATCH("/region&/amplitude_curvecc&", "") { - GET_REGION_OR_BREAK(indices[0]) - auto params = region.ccModParameters(indices[1], ModId::Amplitude); - if (params) { - client.receive<'i'>(delay, path, params->curve); - } else { - client.receive<'N'>(delay, path, {}); - } - } break; - - MATCH("/region&/amp_keycenter", "") { - GET_REGION_OR_BREAK(indices[0]) - client.receive<'i'>(delay, path, region.ampKeycenter); - } break; - - MATCH("/region&/amp_keytrack", "") { - GET_REGION_OR_BREAK(indices[0]) - client.receive<'f'>(delay, path, region.ampKeytrack); - } break; - - MATCH("/region&/amp_veltrack", "") { - GET_REGION_OR_BREAK(indices[0]) - 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); - } break; - - MATCH("/region&/xfin_key_range", "") { - GET_REGION_OR_BREAK(indices[0]) - sfizz_arg_t args[2]; - args[0].i = region.crossfadeKeyInRange.getStart(); - args[1].i = region.crossfadeKeyInRange.getEnd(); - client.receive(delay, path, "ii", args); - } break; - - MATCH("/region&/xfout_key_range", "") { - GET_REGION_OR_BREAK(indices[0]) - sfizz_arg_t args[2]; - args[0].i = region.crossfadeKeyOutRange.getStart(); - args[1].i = region.crossfadeKeyOutRange.getEnd(); - client.receive(delay, path, "ii", args); - } break; - - MATCH("/region&/xfin_vel_range", "") { - GET_REGION_OR_BREAK(indices[0]) - sfizz_arg_t args[2]; - args[0].f = region.crossfadeVelInRange.getStart(); - args[1].f = region.crossfadeVelInRange.getEnd(); - client.receive(delay, path, "ff", args); - } break; - - MATCH("/region&/xfout_vel_range", "") { - GET_REGION_OR_BREAK(indices[0]) - sfizz_arg_t args[2]; - args[0].f = region.crossfadeVelOutRange.getStart(); - args[1].f = region.crossfadeVelOutRange.getEnd(); - client.receive(delay, path, "ff", args); - } break; - - MATCH("/region&/xfin_cc_range&", "") { - GET_REGION_OR_BREAK(indices[0]) - auto range = region.crossfadeCCInRange.get(indices[1]); - if (range) { - sfizz_arg_t args[2]; - args[0].f = range->getStart(); - args[1].f = range->getEnd(); - client.receive(delay, path, "ff", args); - } else { - client.receive<'N'>(delay, path, {}); - } - } break; - - MATCH("/region&/xfout_cc_range&", "") { - GET_REGION_OR_BREAK(indices[0]) - auto range = region.crossfadeCCOutRange.get(indices[1]); - if (range) { - sfizz_arg_t args[2]; - args[0].f = range->getStart(); - args[1].f = range->getEnd(); - client.receive(delay, path, "ff", args); - } else { - client.receive<'N'>(delay, path, {}); - } - } break; - - MATCH("/region&/xf_keycurve", "") { - GET_REGION_OR_BREAK(indices[0]) - switch (region.crossfadeKeyCurve) { - case CrossfadeCurve::gain: - client.receive<'s'>(delay, path, "gain"); - break; - case CrossfadeCurve::power: - client.receive<'s'>(delay, path, "power"); - break; - } - } break; - - MATCH("/region&/xf_velcurve", "") { - GET_REGION_OR_BREAK(indices[0]) - switch (region.crossfadeVelCurve) { - case CrossfadeCurve::gain: - client.receive<'s'>(delay, path, "gain"); - break; - case CrossfadeCurve::power: - client.receive<'s'>(delay, path, "power"); - break; - } - } break; - - MATCH("/region&/xf_cccurve", "") { - GET_REGION_OR_BREAK(indices[0]) - switch (region.crossfadeCCCurve) { - case CrossfadeCurve::gain: - client.receive<'s'>(delay, path, "gain"); - break; - case CrossfadeCurve::power: - client.receive<'s'>(delay, path, "power"); - break; - } - } break; - - MATCH("/region&/global_volume", "") { - GET_REGION_OR_BREAK(indices[0]) - client.receive<'f'>(delay, path, region.globalVolume); - } break; - - MATCH("/region&/master_volume", "") { - GET_REGION_OR_BREAK(indices[0]) - client.receive<'f'>(delay, path, region.masterVolume); - } break; - - MATCH("/region&/group_volume", "") { - GET_REGION_OR_BREAK(indices[0]) - client.receive<'f'>(delay, path, region.groupVolume); - } break; - - MATCH("/region&/global_amplitude", "") { - GET_REGION_OR_BREAK(indices[0]) - client.receive<'f'>(delay, path, region.globalAmplitude * 100.0f); - } break; - - MATCH("/region&/master_amplitude", "") { - GET_REGION_OR_BREAK(indices[0]) - client.receive<'f'>(delay, path, region.masterAmplitude * 100.0f); - } break; - - MATCH("/region&/group_amplitude", "") { - GET_REGION_OR_BREAK(indices[0]) - client.receive<'f'>(delay, path, region.groupAmplitude * 100.0f); - } break; - - MATCH("/region&/pitch_keytrack", "") { - GET_REGION_OR_BREAK(indices[0]) - client.receive<'i'>(delay, path, region.pitchKeytrack); - } break; - - MATCH("/region&/pitch_veltrack", "") { - GET_REGION_OR_BREAK(indices[0]) - 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); - } break; - - MATCH("/region&/transpose", "") { - GET_REGION_OR_BREAK(indices[0]) - client.receive<'i'>(delay, path, region.transpose); - } break; - - MATCH("/region&/pitch", "") { - GET_REGION_OR_BREAK(indices[0]) - client.receive<'f'>(delay, path, region.pitch); - } break; - - MATCH("/region&/pitch_cc&", "") { - GET_REGION_OR_BREAK(indices[0]) - auto value = region.ccModDepth(indices[1], ModId::Pitch); - if (value) { - client.receive<'f'>(delay, path, *value); - } else { - client.receive<'N'>(delay, path, {}); - } - } break; - - MATCH("/region&/pitch_stepcc&", "") { - GET_REGION_OR_BREAK(indices[0]) - auto params = region.ccModParameters(indices[1], ModId::Pitch); - if (params) { - client.receive<'f'>(delay, path, params->step); - } else { - client.receive<'N'>(delay, path, {}); - } - } break; - - MATCH("/region&/pitch_smoothcc&", "") { - GET_REGION_OR_BREAK(indices[0]) - auto params = region.ccModParameters(indices[1], ModId::Pitch); - if (params) { - client.receive<'i'>(delay, path, params->smooth); - } else { - client.receive<'N'>(delay, path, {}); - } - } break; - - MATCH("/region&/pitch_curvecc&", "") { - GET_REGION_OR_BREAK(indices[0]) - auto params = region.ccModParameters(indices[1], ModId::Pitch); - if (params) { - client.receive<'i'>(delay, path, params->curve); - } else { - client.receive<'N'>(delay, path, {}); - } - } break; - - MATCH("/region&/bend_up", "") { - GET_REGION_OR_BREAK(indices[0]) - client.receive<'f'>(delay, path, region.bendUp); - } break; - - MATCH("/region&/bend_down", "") { - GET_REGION_OR_BREAK(indices[0]) - client.receive<'f'>(delay, path, region.bendDown); - } break; - - MATCH("/region&/bend_step", "") { - GET_REGION_OR_BREAK(indices[0]) - client.receive<'f'>(delay, path, region.bendStep); - } break; - - MATCH("/region&/bend_smooth", "") { - GET_REGION_OR_BREAK(indices[0]) - client.receive<'i'>(delay, path, region.bendSmooth); - } break; - - MATCH("/region&/ampeg_attack", "") { - GET_REGION_OR_BREAK(indices[0]) - client.receive<'f'>(delay, path, region.amplitudeEG.attack); - } break; - - MATCH("/region&/ampeg_delay", "") { - GET_REGION_OR_BREAK(indices[0]) - client.receive<'f'>(delay, path, region.amplitudeEG.delay); - } break; - - MATCH("/region&/ampeg_decay", "") { - GET_REGION_OR_BREAK(indices[0]) - client.receive<'f'>(delay, path, region.amplitudeEG.decay); - } break; - - MATCH("/region&/ampeg_hold", "") { - GET_REGION_OR_BREAK(indices[0]) - client.receive<'f'>(delay, path, region.amplitudeEG.hold); - } break; - - MATCH("/region&/ampeg_release", "") { - GET_REGION_OR_BREAK(indices[0]) - client.receive<'f'>(delay, path, region.amplitudeEG.release); - } break; - - MATCH("/region&/ampeg_start", "") { - GET_REGION_OR_BREAK(indices[0]) - client.receive<'f'>(delay, path, region.amplitudeEG.start * 100.0f); - } break; - - MATCH("/region&/ampeg_sustain", "") { - GET_REGION_OR_BREAK(indices[0]) - client.receive<'f'>(delay, path, region.amplitudeEG.sustain * 100.0f); - } break; - - MATCH("/region&/ampeg_depth", "") { - GET_REGION_OR_BREAK(indices[0]) - client.receive<'f'>(delay, path, region.amplitudeEG.depth); - } break; - - MATCH("/region&/ampeg_vel&attack", "") { - GET_REGION_OR_BREAK(indices[0]) - if (indices[1] != 2) - break; - client.receive<'f'>(delay, path, region.amplitudeEG.vel2attack); - } break; - - MATCH("/region&/ampeg_vel&delay", "") { - GET_REGION_OR_BREAK(indices[0]) - if (indices[1] != 2) - break; - client.receive<'f'>(delay, path, region.amplitudeEG.vel2delay); - } break; - - MATCH("/region&/ampeg_vel&decay", "") { - GET_REGION_OR_BREAK(indices[0]) - if (indices[1] != 2) - break; - client.receive<'f'>(delay, path, region.amplitudeEG.vel2decay); - } break; - - MATCH("/region&/ampeg_vel&hold", "") { - GET_REGION_OR_BREAK(indices[0]) - if (indices[1] != 2) - break; - client.receive<'f'>(delay, path, region.amplitudeEG.vel2hold); - } break; - - MATCH("/region&/ampeg_vel&release", "") { - GET_REGION_OR_BREAK(indices[0]) - if (indices[1] != 2) - break; - client.receive<'f'>(delay, path, region.amplitudeEG.vel2release); - } break; - - MATCH("/region&/ampeg_vel&sustain", "") { - GET_REGION_OR_BREAK(indices[0]) - if (indices[1] != 2) - break; - client.receive<'f'>(delay, path, region.amplitudeEG.vel2sustain * 100.0f); - } break; - - MATCH("/region&/ampeg_vel&depth", "") { - GET_REGION_OR_BREAK(indices[0]) - if (indices[1] != 2) - break; - 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; + uint64_t h = Fnv1aBasis; + while (unsigned char c = *path++) { + if (!absl::ascii_isdigit(c)) + h = hashByte(c, h); + else { + h = hashByte('&', h); + while (absl::ascii_isdigit(*path)) + ++path; + } + } + h = hashByte(',', h); + while (unsigned char c = *sig++) + h = hashByte(c, h); + return h; +} - MATCH("/region&/pitcheg_attack_cc&", "") { - GET_REGION_OR_BREAK(indices[0]) - if (!region.pitchEG) break; - const auto& cc = region.pitchEG->ccAttack.getWithDefault(indices[1]); - client.receive<'f'>(delay, path, cc.modifier); - } break; +class MessagingHelper +{ +public: + MessagingHelper(Client& client, int delay, const char* path, const char* sig, sfz::Synth::Impl& impl) + : client(client), impl(impl), delay(delay), path(path), sig(sig) + { + indices.reserve(maxIndices); + } - MATCH("/region&/pitcheg_decay_cc&", "") { - GET_REGION_OR_BREAK(indices[0]) - if (!region.pitchEG) break; - const auto& cc = region.pitchEG->ccDecay.getWithDefault(indices[1]); - client.receive<'f'>(delay, path, cc.modifier); - } break; + enum class ModParam { Depth, Curve, Smooth, Step }; - MATCH("/region&/pitcheg_delay_cc&", "") { - GET_REGION_OR_BREAK(indices[0]) - if (!region.pitchEG) break; - const auto& cc = region.pitchEG->ccDelay.getWithDefault(indices[1]); - client.receive<'f'>(delay, path, cc.modifier); - } break; + bool match(const char* pattern, const char* sig) + { + indices.clear(); + const char* path_ = this->path; - MATCH("/region&/pitcheg_hold_cc&", "") { - GET_REGION_OR_BREAK(indices[0]) - if (!region.pitchEG) break; - const auto& cc = region.pitchEG->ccHold.getWithDefault(indices[1]); - client.receive<'f'>(delay, path, cc.modifier); - } break; + while (const char *endp = strchr(pattern, '&')) { + if (indices.size() == maxIndices) + return false; - MATCH("/region&/pitcheg_release_cc&", "") { - GET_REGION_OR_BREAK(indices[0]) - if (!region.pitchEG) break; - const auto& cc = region.pitchEG->ccRelease.getWithDefault(indices[1]); - client.receive<'f'>(delay, path, cc.modifier); - } break; + size_t length = endp - pattern; + if (strncmp(pattern, path_, length) != 0) + return false; + pattern += length; + path_ += length; - MATCH("/region&/pitcheg_start_cc&", "") { - GET_REGION_OR_BREAK(indices[0]) - if (!region.pitchEG) break; - const auto& cc = region.pitchEG->ccStart.getWithDefault(indices[1]); - client.receive<'f'>(delay, path, cc.modifier * 100.0f); - } break; + length = 0; + while (absl::ascii_isdigit(path_[length])) + ++length; - MATCH("/region&/pitcheg_sustain_cc&", "") { - GET_REGION_OR_BREAK(indices[0]) - if (!region.pitchEG) break; - const auto& cc = region.pitchEG->ccSustain.getWithDefault(indices[1]); - client.receive<'f'>(delay, path, cc.modifier * 100.0f); - } break; + indices.push_back(0); + if (!absl::SimpleAtoi(absl::string_view(path_, length), &indices.back())) + return false; - MATCH("/region&/pitcheg_attack_curvecc&", "") { - GET_REGION_OR_BREAK(indices[0]) - if (!region.pitchEG) break; - const auto& cc = region.pitchEG->ccAttack.getWithDefault(indices[1]); - client.receive<'f'>(delay, path, cc.curve); - } break; + pattern += 1; + path_ += length; + } - MATCH("/region&/pitcheg_decay_curvecc&", "") { - GET_REGION_OR_BREAK(indices[0]) - if (!region.pitchEG) break; - const auto& cc = region.pitchEG->ccDecay.getWithDefault(indices[1]); - client.receive<'f'>(delay, path, cc.curve); - } break; + return !strcmp(path_, pattern) && !strcmp(this->sig, sig); + } - MATCH("/region&/pitcheg_delay_curvecc&", "") { - GET_REGION_OR_BREAK(indices[0]) - if (!region.pitchEG) break; - const auto& cc = region.pitchEG->ccDelay.getWithDefault(indices[1]); - client.receive<'f'>(delay, path, cc.curve); - } break; + // These are the reply/reply2 overloads for the (almost) concrete types, that should + // translate to actual calls to the client.receive(...) method. - MATCH("/region&/pitcheg_hold_curvecc&", "") { - GET_REGION_OR_BREAK(indices[0]) - if (!region.pitchEG) break; - const auto& cc = region.pitchEG->ccHold.getWithDefault(indices[1]); - client.receive<'f'>(delay, path, cc.curve); - } break; + void reply(const std::string& value) { client.receive<'s'>(delay, path, value.data()); } + void reply(const char* value) { client.receive<'s'>(delay, path, value); } + void reply(float value) { client.receive<'f'>(delay, path, value); } + void reply(absl::nullopt_t) { client.receive<'N'>(delay, path, {}); } - MATCH("/region&/pitcheg_release_curvecc&", "") { - GET_REGION_OR_BREAK(indices[0]) - if (!region.pitchEG) break; - const auto& cc = region.pitchEG->ccRelease.getWithDefault(indices[1]); - client.receive<'f'>(delay, path, cc.curve); - } break; + void reply(bool value) + { + if (value) + client.receive<'T'>(delay, path, {}); + else + client.receive<'F'>(delay, path, {}); + } - MATCH("/region&/pitcheg_start_curvecc&", "") { - GET_REGION_OR_BREAK(indices[0]) - if (!region.pitchEG) break; - const auto& cc = region.pitchEG->ccStart.getWithDefault(indices[1]); - client.receive<'f'>(delay, path, cc.curve); - } break; + template::value>> + void reply(T value) + { + if (sizeof(value) <= 4) + client.receive<'i'>(delay, path, static_cast(value)); + else + client.receive<'h'>(delay, path, static_cast(value)); + } + template + void reply(const BitArray& array) + { + sfizz_blob_t blob { array.data(), static_cast(array.byte_size()) }; + client.receive<'b'>(delay, path, &blob); + } - MATCH("/region&/pitcheg_sustain_curvecc&", "") { - GET_REGION_OR_BREAK(indices[0]) - if (!region.pitchEG) break; - const auto& cc = region.pitchEG->ccSustain.getWithDefault(indices[1]); - client.receive<'f'>(delay, path, cc.curve); - } break; + // Call reply but denormalizes the input if needed by the opcode spec + template + void reply(T value, OpcodeSpec spec) { reply(spec.denormalizeInput(value)); } + + // sfizz specific types + void reply(sfz::LFOWave wave) { reply(static_cast(wave)); } + void reply(sfz::SelfMask mode) { reply(mode == SelfMask::mask); } + void reply(sfz::LoopMode mode) + { + switch (mode) { + case LoopMode::no_loop: reply("no_loop"); break; + case LoopMode::loop_continuous: reply("loop_continuous"); break; + case LoopMode::loop_sustain: reply("loop_sustain"); break; + case LoopMode::one_shot: reply("one_shot"); break; + } + } - MATCH("/region&/note_polyphony", "") { - GET_REGION_OR_BREAK(indices[0]) - if (region.notePolyphony) { - client.receive<'i'>(delay, path, *region.notePolyphony); - } else { - client.receive<'N'>(delay, path, {}); - } - } break; + void reply(sfz::CrossfadeCurve curve) + { + switch (curve) { + case CrossfadeCurve::gain: reply("gain"); break; + case CrossfadeCurve::power: reply("power"); break; + } + } - MATCH("/region&/note_selfmask", "") { - GET_REGION_OR_BREAK(indices[0]) - switch(region.selfMask) { - case SelfMask::mask: - client.receive(delay, path, "T", nullptr); - break; - case SelfMask::dontMask: - client.receive(delay, path, "F", nullptr); - break; - } - } break; + void reply(sfz::Trigger mode) + { + switch (mode) { + case Trigger::attack: reply("attack"); break; + case Trigger::first: reply("first"); break; + case Trigger::legato: reply("legato"); break; + case Trigger::release: reply("release"); break; + case Trigger::release_key: reply("release_key"); break; + } + } - MATCH("/region&/rt_dead", "") { - GET_REGION_OR_BREAK(indices[0]) - if (region.rtDead) { - client.receive(delay, path, "T", nullptr); - } else { - client.receive(delay, path, "F", nullptr); + void reply(sfz::VelocityOverride mode) + { + switch (mode) { + case VelocityOverride::current: reply("current"); break; + case VelocityOverride::previous: reply("previous"); break; } - } break; + } - MATCH("/region&/sustain_sw", "") { - GET_REGION_OR_BREAK(indices[0]) - if (region.checkSustain) { - client.receive(delay, path, "T", nullptr); - } else { - client.receive(delay, path, "F", nullptr); - } - } break; + void reply(sfz::OffMode mode) + { + switch (mode) { + case OffMode::fast: reply("fast"); break; + case OffMode::time: reply("time"); break; + case OffMode::normal: reply("normal"); break; + } + } - MATCH("/region&/sostenuto_sw", "") { - GET_REGION_OR_BREAK(indices[0]) - if (region.checkSostenuto) { - client.receive(delay, path, "T", nullptr); - } else { - client.receive(delay, path, "F", nullptr); + void reply(sfz::FilterType type) + { + switch (type) { + case FilterType::kFilterLpf1p: reply("lpf_1p"); break; + case FilterType::kFilterHpf1p: reply("hpf_1p"); break; + case FilterType::kFilterLpf2p: reply("lpf_2p"); break; + case FilterType::kFilterHpf2p: reply("hpf_2p"); break; + case FilterType::kFilterBpf2p: reply("bpf_2p"); break; + case FilterType::kFilterBrf2p: reply("brf_2p"); break; + case FilterType::kFilterBpf1p: reply("bpf_1p"); break; + case FilterType::kFilterBrf1p: reply("brf_1p"); break; + case FilterType::kFilterApf1p: reply("apf_1p"); break; + case FilterType::kFilterLpf2pSv: reply("lpf_2p_sv"); break; + case FilterType::kFilterHpf2pSv: reply("hpf_2p_sv"); break; + case FilterType::kFilterBpf2pSv: reply("bpf_2p_sv"); break; + case FilterType::kFilterBrf2pSv: reply("brf_2p_sv"); break; + case FilterType::kFilterLpf4p: reply("lpf_4p"); break; + case FilterType::kFilterHpf4p: reply("hpf_4p"); break; + case FilterType::kFilterLpf6p: reply("lpf_6p"); break; + case FilterType::kFilterHpf6p: reply("hpf_6p"); break; + case FilterType::kFilterPink: reply("pink"); break; + case FilterType::kFilterLsh: reply("lsh"); break; + case FilterType::kFilterHsh: reply("hsh"); break; + case FilterType::kFilterPeq: reply("peq"); break; + case FilterType::kFilterBpf4p: reply("bpf_4p"); break; + case FilterType::kFilterBpf6p: reply("bpf_6p"); break; + case FilterType::kFilterNone: reply("none"); break; } - } break; - - MATCH("/region&/sustain_cc", "") { - GET_REGION_OR_BREAK(indices[0]) - client.receive<'i'>(delay, path, region.sustainCC); - } break; + } - MATCH("/region&/sostenuto_cc", "") { - GET_REGION_OR_BREAK(indices[0]) - client.receive<'i'>(delay, path, region.sostenutoCC); - } break; + void reply(sfz::EqType type) + { + switch(type) { + case EqType::kEqNone: reply("none"); break; + case EqType::kEqPeak: reply("peak"); break; + case EqType::kEqLshelf: reply("lshelf"); break; + case EqType::kEqHshelf: reply("hshelf"); break; + } + } - MATCH("/region&/sustain_lo", "") { - GET_REGION_OR_BREAK(indices[0]) - client.receive<'f'>(delay, path, region.sustainThreshold); - } break; + void reply(sfz::TriggerEventType type) + { + switch(type) { + case TriggerEventType::NoteOff: reply("note_off"); break; + case TriggerEventType::NoteOn: reply("note_on"); break; + case TriggerEventType::CC: reply("cc"); break; + } + } - MATCH("/region&/sostenuto_lo", "") { - GET_REGION_OR_BREAK(indices[0]) - client.receive<'f'>(delay, path, region.sostenutoThreshold); - } break; + // reply2 for pairs (Usually only for float/int ranges) + template::value>> + void reply2(T value1, T value2) + { + sfizz_arg_t args[2]; + if (sizeof(value1) <= 4) { + args[0].i = value1; + args[1].i = value2; + client.receive(delay, path, "ii", args); + } else { + args[0].h = value1; + args[1].h = value2; + client.receive(delay, path, "hh", args); + } + } - MATCH("/region&/oscillator_phase", "") { - GET_REGION_OR_BREAK(indices[0]) - client.receive<'f'>(delay, path, region.oscillatorPhase); - } break; + void reply2(float value1, float value2) + { + sfizz_arg_t args[2]; + args[0].f = value1; + args[1].f = value2; + client.receive(delay, path, "ff", args); + } - MATCH("/region&/oscillator_quality", "") { - GET_REGION_OR_BREAK(indices[0]) - if (region.oscillatorQuality) { - client.receive<'i'>(delay, path, *region.oscillatorQuality); - } else { + // Now we have some templated reply overloads that decay into "concrete" reply calls + // but add some logic (e.g. an optional can either reply the value, or send 'N' for + // null) + template + void reply(absl::optional value, Args... args) + { + if (!value) { client.receive<'N'>(delay, path, {}); - } - } break; - - MATCH("/region&/oscillator_mode", "") { - GET_REGION_OR_BREAK(indices[0]) - client.receive<'i'>(delay, path, region.oscillatorMode); - } break; - - MATCH("/region&/oscillator_multi", "") { - GET_REGION_OR_BREAK(indices[0]) - client.receive<'i'>(delay, path, region.oscillatorMulti); - } break; - - MATCH("/region&/oscillator_detune", "") { - GET_REGION_OR_BREAK(indices[0]) - client.receive<'f'>(delay, path, region.oscillatorDetune); - } break; - - MATCH("/region&/oscillator_mod_depth", "") { - GET_REGION_OR_BREAK(indices[0]) - client.receive<'f'>(delay, path, region.oscillatorModDepth * 100.0f); - } break; - - // TODO: detune cc, mod depth cc - - MATCH("/region&/effect&", "") { - GET_REGION_OR_BREAK(indices[0]) - auto effectIdx = indices[1]; - if (indices[1] == 0) - break; - - if (effectIdx < region.gainToEffect.size()) - client.receive<'f'>(delay, path, region.gainToEffect[effectIdx] * 100.0f); - } break; - - MATCH("/region&/ampeg_attack_cc&", "") { - GET_REGION_OR_BREAK(indices[0]) - const auto& cc = region.amplitudeEG.ccAttack.getWithDefault(indices[1]); - client.receive<'f'>(delay, path, cc.modifier); - } break; - - MATCH("/region&/ampeg_decay_cc&", "") { - GET_REGION_OR_BREAK(indices[0]) - const auto& cc = region.amplitudeEG.ccDecay.getWithDefault(indices[1]); - client.receive<'f'>(delay, path, cc.modifier); - } break; - - MATCH("/region&/ampeg_delay_cc&", "") { - GET_REGION_OR_BREAK(indices[0]) - const auto& cc = region.amplitudeEG.ccDelay.getWithDefault(indices[1]); - client.receive<'f'>(delay, path, cc.modifier); - } break; - - MATCH("/region&/ampeg_hold_cc&", "") { - GET_REGION_OR_BREAK(indices[0]) - const auto& cc = region.amplitudeEG.ccHold.getWithDefault(indices[1]); - client.receive<'f'>(delay, path, cc.modifier); - } break; - - MATCH("/region&/ampeg_release_cc&", "") { - GET_REGION_OR_BREAK(indices[0]) - const auto& cc = region.amplitudeEG.ccRelease.getWithDefault(indices[1]); - client.receive<'f'>(delay, path, cc.modifier); - } break; - - MATCH("/region&/ampeg_start_cc&", "") { - GET_REGION_OR_BREAK(indices[0]) - const auto& cc = region.amplitudeEG.ccStart.getWithDefault(indices[1]); - client.receive<'f'>(delay, path, cc.modifier * 100.0f); - } break; - - MATCH("/region&/ampeg_sustain_cc&", "") { - GET_REGION_OR_BREAK(indices[0]) - const auto& cc = region.amplitudeEG.ccSustain.getWithDefault(indices[1]); - client.receive<'f'>(delay, path, cc.modifier * 100.0f); - } break; + return; + } - MATCH("/region&/ampeg_attack_curvecc&", "") { - GET_REGION_OR_BREAK(indices[0]) - const auto& cc = region.amplitudeEG.ccAttack.getWithDefault(indices[1]); - client.receive<'f'>(delay, path, cc.curve); - } break; + reply(*value, args...); + } - MATCH("/region&/ampeg_decay_curvecc&", "") { - GET_REGION_OR_BREAK(indices[0]) - const auto& cc = region.amplitudeEG.ccDecay.getWithDefault(indices[1]); - client.receive<'f'>(delay, path, cc.curve); - } break; - MATCH("/region&/ampeg_delay_curvecc&", "") { - GET_REGION_OR_BREAK(indices[0]) - const auto& cc = region.amplitudeEG.ccDelay.getWithDefault(indices[1]); - client.receive<'f'>(delay, path, cc.curve); - } break; + template + void reply(T* value, Args... args) + { + if (!value) { + client.receive<'N'>(delay, path, {}); + return; + } - MATCH("/region&/ampeg_hold_curvecc&", "") { - GET_REGION_OR_BREAK(indices[0]) - const auto& cc = region.amplitudeEG.ccHold.getWithDefault(indices[1]); - client.receive<'f'>(delay, path, cc.curve); - } break; + reply(*value, args...); + } - MATCH("/region&/ampeg_release_curvecc&", "") { - GET_REGION_OR_BREAK(indices[0]) - const auto& cc = region.amplitudeEG.ccRelease.getWithDefault(indices[1]); - client.receive<'f'>(delay, path, cc.curve); - } break; + template + void reply(std::shared_ptr value) { reply(value.get()); } - MATCH("/region&/ampeg_start_curvecc&", "") { - GET_REGION_OR_BREAK(indices[0]) - const auto& cc = region.amplitudeEG.ccStart.getWithDefault(indices[1]); - client.receive<'f'>(delay, path, cc.curve); - } break; + template + void reply(sfz::UncheckedRange range) { reply2(range.getStart(), range.getEnd()); } - MATCH("/region&/ampeg_sustain_curvecc&", "") { - GET_REGION_OR_BREAK(indices[0]) - const auto& cc = region.amplitudeEG.ccSustain.getWithDefault(indices[1]); - client.receive<'f'>(delay, path, cc.curve); - } break; + template + void reply(absl::optional value, T def) { reply(value.value_or(def)); } - MATCH("/region&/filter&/cutoff", "") { - GET_REGION_OR_BREAK(indices[0]) - GET_FILTER_OR_BREAK(indices[1]) - client.receive<'f'>(delay, path, filter.cutoff); - } break; + template + void reply(const sfz::ModifierCurvePair& modCurve, ModParam which, Args...args) + { + if (auto region = getRegion()) { + switch (which) { + case ModParam::Curve: reply(modCurve.curve); break; + default: reply(modCurve.modifier, args...); break; + } + } + } - MATCH("/region&/filter&/cutoff_cc&", "") { - GET_REGION_OR_BREAK(indices[0]) - const auto depth = region.ccModDepth(indices[2], ModId::FilCutoff, indices[1]); - if (depth) - client.receive<'f'>(delay, path, *depth); - } break; + template + void reply(const sfz::ModKey::Parameters& params, ModParam which, Args...args) + { + if (auto region = getRegion()) { + switch (which) { + case ModParam::Depth: break; + case ModParam::Curve: reply(params.curve); break; + case ModParam::Smooth: reply(params.smooth); break; + case ModParam::Step: reply(params.step, args...); break; + } + } + } - MATCH("/region&/filter&/cutoff_curvecc&", "") { - GET_REGION_OR_BREAK(indices[0]) - const auto params = region.ccModParameters(indices[2], ModId::FilCutoff, indices[1]); - if (params) - client.receive<'i'>(delay, path, params->curve); - } break; + template + void reply(CCMap map, Args...args) { reply(map, true, args...); } + + template + void reply(CCMap map, bool useDefault, Args...args) + { + if (useDefault) + reply(map.getWithDefault(indices.back()), args...); + else if (map.contains(indices.back())) + reply(map.get(indices.back()), args...); + else + client.receive<'N'>(delay, path, {}); + } - MATCH("/region&/filter&/cutoff_stepcc&", "") { - GET_REGION_OR_BREAK(indices[0]) - const auto params = region.ccModParameters(indices[2], ModId::FilCutoff, indices[1]); - if (params) - client.receive<'i'>(delay, path, params->step); - } break; + // Below are all methods that will fetch various data structure elements (regions, + // egs, ...), check that they exist, and call an actual "concrete" reply + // implementation. They use pointer-to-member variables, which allow the compiler to + // dispatch to the correct logic. For example, if the pointer-to-member is + // `sfz::Region::*`, we aim at sending the value of a member of the sfz::Region + // struct. The first thing is to read the index of the region and fetch it from the + // sfizz data structure using `getRegion(...)`, and then apply the pointer-to-member + // to this particular region to send the value. + + // Adding new dispatching overloads seemed simple enough to this point, although I + // haven't found a nice way to the same with pointer-to-member function. + // TODO: maybe a semi-templated overload that check that if the member is a function, + // TODO: and call it with `args...` could be an approach to try. + + template + void reply(T sfz::Region::* member, Args... args) + { + if (auto region = getRegion()) + reply((*region).*member, args...); + } - MATCH("/region&/filter&/cutoff_smoothcc&", "") { - GET_REGION_OR_BREAK(indices[0]) - const auto params = region.ccModParameters(indices[2], ModId::FilCutoff, indices[1]); - if (params) - client.receive<'i'>(delay, path, params->smooth); - } break; + template + void reply(const sfz::EGDescription& eg, T sfz::EGDescription::* member, Args...args) + { + reply(eg.*member, args...); + } - MATCH("/region&/filter&/resonance", "") { - GET_REGION_OR_BREAK(indices[0]) - GET_FILTER_OR_BREAK(indices[1]) - client.receive<'f'>(delay, path, filter.resonance); - } break; + template + void reply(T sfz::FilterDescription::* member, Args... args) + { + if (auto region = getRegion()) + if (auto filter = getFilter(*region)) + reply((*filter).*member, args...); + } - MATCH("/region&/filter&/gain", "") { - GET_REGION_OR_BREAK(indices[0]) - GET_FILTER_OR_BREAK(indices[1]) - client.receive<'f'>(delay, path, filter.gain); - } break; + template + void reply(T sfz::EQDescription::* member, Args... args) + { + if (auto region = getRegion()) + if (auto eq = getEQ(*region)) + reply((*eq).*member, args...); + } - MATCH("/region&/filter&/keycenter", "") { - GET_REGION_OR_BREAK(indices[0]) - GET_FILTER_OR_BREAK(indices[1]) - client.receive<'i'>(delay, path, filter.keycenter); - } break; + template + void reply(T sfz::LFODescription::* member, Args... args) + { + if (auto region = getRegion()) + if (auto lfo = getLFO(*region)) + reply((*lfo).*member, args...); + } - MATCH("/region&/filter&/keytrack", "") { - GET_REGION_OR_BREAK(indices[0]) - GET_FILTER_OR_BREAK(indices[1]) - client.receive<'i'>(delay, path, filter.keytrack); - } break; + template + void reply(T sfz::LFODescription::Sub::* member, Args... args) + { + if (auto region = getRegion()) + if (auto lfo = getLFO(*region)) + if (!lfo->sub.empty()) + reply((lfo->sub[0]).*member, args...); + } - MATCH("/region&/filter&/veltrack", "") { - GET_REGION_OR_BREAK(indices[0]) - GET_FILTER_OR_BREAK(indices[1]) - client.receive<'i'>(delay, path, filter.veltrack); - } break; + template + void reply(T sfz::FlexEGPoint::* member, Args... args) + { + if (auto region = getRegion()) + if (auto eg = getEG(*region)) + if (auto point = getEGPoint(*eg)) + reply((*point).*member, args...); + } - 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; + template + void reply(T sfz::TriggerEvent::* member, Args... args) + { + if (auto voice = getVoice()) + reply((*voice).getTriggerEvent().*member, args...); + } - 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; + template + void reply(T sfz::Voice::* member, Args... args) + { + if (auto voice = getVoice()) { + reply((*voice.*member)(args...)); // Voice only has callables + } + } - MATCH("/region&/filter&/type", "") { - GET_REGION_OR_BREAK(indices[0]) - GET_FILTER_OR_BREAK(indices[1]) - switch (filter.type) { - case FilterType::kFilterLpf1p: client.receive<'s'>(delay, path, "lpf_1p"); break; - case FilterType::kFilterHpf1p: client.receive<'s'>(delay, path, "hpf_1p"); break; - case FilterType::kFilterLpf2p: client.receive<'s'>(delay, path, "lpf_2p"); break; - case FilterType::kFilterHpf2p: client.receive<'s'>(delay, path, "hpf_2p"); break; - case FilterType::kFilterBpf2p: client.receive<'s'>(delay, path, "bpf_2p"); break; - case FilterType::kFilterBrf2p: client.receive<'s'>(delay, path, "brf_2p"); break; - case FilterType::kFilterBpf1p: client.receive<'s'>(delay, path, "bpf_1p"); break; - case FilterType::kFilterBrf1p: client.receive<'s'>(delay, path, "brf_1p"); break; - case FilterType::kFilterApf1p: client.receive<'s'>(delay, path, "apf_1p"); break; - case FilterType::kFilterLpf2pSv: client.receive<'s'>(delay, path, "lpf_2p_sv"); break; - case FilterType::kFilterHpf2pSv: client.receive<'s'>(delay, path, "hpf_2p_sv"); break; - case FilterType::kFilterBpf2pSv: client.receive<'s'>(delay, path, "bpf_2p_sv"); break; - case FilterType::kFilterBrf2pSv: client.receive<'s'>(delay, path, "brf_2p_sv"); break; - case FilterType::kFilterLpf4p: client.receive<'s'>(delay, path, "lpf_4p"); break; - case FilterType::kFilterHpf4p: client.receive<'s'>(delay, path, "hpf_4p"); break; - case FilterType::kFilterLpf6p: client.receive<'s'>(delay, path, "lpf_6p"); break; - case FilterType::kFilterHpf6p: client.receive<'s'>(delay, path, "hpf_6p"); break; - case FilterType::kFilterPink: client.receive<'s'>(delay, path, "pink"); break; - case FilterType::kFilterLsh: client.receive<'s'>(delay, path, "lsh"); break; - case FilterType::kFilterHsh: client.receive<'s'>(delay, path, "hsh"); break; - case FilterType::kFilterPeq: client.receive<'s'>(delay, path, "peq"); break; - case FilterType::kFilterBpf4p: client.receive<'s'>(delay, path, "bpf_4p"); break; - case FilterType::kFilterBpf6p: client.receive<'s'>(delay, path, "bpf_6p"); break; - case FilterType::kFilterNone: client.receive<'s'>(delay, path, "none"); break; + template + void reply(ModId id, ModParam param, Args...args) + { + if (auto region = getRegion()) { + int cc = static_cast (indices.back()); + switch (id){ + case ModId::FilCutoff: + case ModId::FilGain: + switch (param) { + case ModParam::Depth: reply(region->ccModDepth(cc, id, indices[1]), args...); break; + default: reply(region->ccModParameters(cc, id, indices[1]), param, args...); break; + } break; + default: + switch (param) { + case ModParam::Depth: reply(region->ccModDepth(cc, id), args...); break; + default: reply(region->ccModParameters(cc, id), param, args...); break; + } } - } break; - - MATCH("/region&/fileg_attack_cc&", "") { - GET_REGION_OR_BREAK(indices[0]) - if (!region.filterEG) break; - const auto& cc = region.filterEG->ccAttack.getWithDefault(indices[1]); - client.receive<'f'>(delay, path, cc.modifier); - } break; - - MATCH("/region&/fileg_decay_cc&", "") { - GET_REGION_OR_BREAK(indices[0]) - if (!region.filterEG) break; - const auto& cc = region.filterEG->ccDecay.getWithDefault(indices[1]); - client.receive<'f'>(delay, path, cc.modifier); - } break; - - MATCH("/region&/fileg_delay_cc&", "") { - GET_REGION_OR_BREAK(indices[0]) - if (!region.filterEG) break; - const auto& cc = region.filterEG->ccDelay.getWithDefault(indices[1]); - client.receive<'f'>(delay, path, cc.modifier); - } break; - - MATCH("/region&/fileg_hold_cc&", "") { - GET_REGION_OR_BREAK(indices[0]) - if (!region.filterEG) break; - const auto& cc = region.filterEG->ccHold.getWithDefault(indices[1]); - client.receive<'f'>(delay, path, cc.modifier); - } break; - - MATCH("/region&/fileg_release_cc&", "") { - GET_REGION_OR_BREAK(indices[0]) - if (!region.filterEG) break; - const auto& cc = region.filterEG->ccRelease.getWithDefault(indices[1]); - client.receive<'f'>(delay, path, cc.modifier); - } break; - - MATCH("/region&/fileg_start_cc&", "") { - GET_REGION_OR_BREAK(indices[0]) - if (!region.filterEG) break; - const auto& cc = region.filterEG->ccStart.getWithDefault(indices[1]); - client.receive<'f'>(delay, path, cc.modifier * 100.0f); - } break; - - MATCH("/region&/fileg_sustain_cc&", "") { - GET_REGION_OR_BREAK(indices[0]) - if (!region.filterEG) break; - const auto& cc = region.filterEG->ccSustain.getWithDefault(indices[1]); - client.receive<'f'>(delay, path, cc.modifier * 100.0f); - } break; + } + } - MATCH("/region&/fileg_attack_curvecc&", "") { - GET_REGION_OR_BREAK(indices[0]) - if (!region.filterEG) break; - const auto& cc = region.filterEG->ccAttack.getWithDefault(indices[1]); - client.receive<'f'>(delay, path, cc.curve); - } break; + // Validate and fetch elements from the sfizz data structures. By default, we kind of + // assume that regions/voices will be the first index, CCs will be the last, and + // EQ/Filter/.. will be in-between. + sfz::Region* getRegion(absl::optional index = {}) + { + const auto idx = index.value_or(indices[0]); + if (idx >= impl.layers_.size()) + return {}; + + Layer& layer = *impl.layers_[idx]; + return &layer.getRegion(); + } - MATCH("/region&/fileg_decay_curvecc&", "") { - GET_REGION_OR_BREAK(indices[0]) - if (!region.filterEG) break; - const auto& cc = region.filterEG->ccDecay.getWithDefault(indices[1]); - client.receive<'f'>(delay, path, cc.curve); - } break; + sfz::FilterDescription* getFilter(sfz::Region& region, absl::optional index = {}) + { + const auto idx = index.value_or(indices[1]); + if (region.filters.size() <= idx) + return {}; - MATCH("/region&/fileg_delay_curvecc&", "") { - GET_REGION_OR_BREAK(indices[0]) - if (!region.filterEG) break; - const auto& cc = region.filterEG->ccDelay.getWithDefault(indices[1]); - client.receive<'f'>(delay, path, cc.curve); - } break; + return ®ion.filters[idx]; + } - MATCH("/region&/fileg_hold_curvecc&", "") { - GET_REGION_OR_BREAK(indices[0]) - if (!region.filterEG) break; - const auto& cc = region.filterEG->ccHold.getWithDefault(indices[1]); - client.receive<'f'>(delay, path, cc.curve); - } break; + sfz::EQDescription* getEQ(sfz::Region& region, absl::optional index = {}) + { + const auto idx = index.value_or(indices[1]); + if (region.equalizers.size() <= idx) + return {}; - MATCH("/region&/fileg_release_curvecc&", "") { - GET_REGION_OR_BREAK(indices[0]) - if (!region.filterEG) break; - const auto& cc = region.filterEG->ccRelease.getWithDefault(indices[1]); - client.receive<'f'>(delay, path, cc.curve); - } break; + return ®ion.equalizers[idx]; + } - MATCH("/region&/fileg_start_curvecc&", "") { - GET_REGION_OR_BREAK(indices[0]) - if (!region.filterEG) break; - const auto& cc = region.filterEG->ccStart.getWithDefault(indices[1]); - client.receive<'f'>(delay, path, cc.curve); - } break; + sfz::LFODescription* getLFO(sfz::Region& region, absl::optional index = {}) + { + const auto idx = index.value_or(indices[1]); + if (region.lfos.size() <= idx) + return {}; - MATCH("/region&/fileg_sustain_curvecc&", "") { - GET_REGION_OR_BREAK(indices[0]) - if (!region.filterEG) break; - const auto& cc = region.filterEG->ccSustain.getWithDefault(indices[1]); - client.receive<'f'>(delay, path, cc.curve); - } break; + return ®ion.lfos[idx]; + } - MATCH("/region&/eq&/gain", "") { - GET_REGION_OR_BREAK(indices[0]) - GET_EQ_OR_BREAK(indices[1]) - client.receive<'f'>(delay, path, eq.gain); - } break; + sfz::LFODescription::Sub* getLFOSub(sfz::LFODescription& lfo, absl::optional index = {}) + { + const auto idx = index.value_or(indices[2]); + if (lfo.sub.size() <= idx) + return {}; - MATCH("/region&/eq&/bandwidth", "") { - GET_REGION_OR_BREAK(indices[0]) - GET_EQ_OR_BREAK(indices[1]) - client.receive<'f'>(delay, path, eq.bandwidth); - } break; + return &lfo.sub[idx]; + } - MATCH("/region&/eq&/frequency", "") { - GET_REGION_OR_BREAK(indices[0]) - GET_EQ_OR_BREAK(indices[1]) - client.receive<'f'>(delay, path, eq.frequency); - } break; + sfz::FlexEGDescription* getEG(sfz::Region& region, absl::optional index = {}) + { + const auto idx = index.value_or(indices[1]); + if (region.flexEGs.size() <= idx) + return {}; - MATCH("/region&/eq&/vel&freq", "") { - GET_REGION_OR_BREAK(indices[0]) - GET_EQ_OR_BREAK(indices[1]) - if (indices[2] != 2) - break; - client.receive<'f'>(delay, path, eq.vel2frequency); - } break; + return ®ion.flexEGs[idx]; + } - MATCH("/region&/eq&/vel&gain", "") { - GET_REGION_OR_BREAK(indices[0]) - GET_EQ_OR_BREAK(indices[1]) - if (indices[2] != 2) - break; - client.receive<'f'>(delay, path, eq.vel2gain); - } break; + sfz::FlexEGPoint* getEGPoint(sfz::FlexEGDescription& desc, absl::optional index = {}) + { + const auto idx = index.value_or(indices[2]) + 1; + if (desc.points.size() <= idx) + return {}; - MATCH("/region&/eq&/type", "") { - GET_REGION_OR_BREAK(indices[0]) - GET_EQ_OR_BREAK(indices[1]) - switch (eq.type) { - case EqType::kEqNone: client.receive<'s'>(delay, path, "none"); break; - case EqType::kEqPeak: client.receive<'s'>(delay, path, "peak"); break; - case EqType::kEqLshelf: client.receive<'s'>(delay, path, "lshelf"); break; - case EqType::kEqHshelf: client.receive<'s'>(delay, path, "hshelf"); break; - } - } break; + return &desc.points[idx]; + } - MATCH("/region&/lfo&/wave", "") { - GET_REGION_OR_BREAK(indices[0]) - GET_LFO_OR_BREAK(indices[1]) - if (lfo.sub.size() == 0) - break; + sfz::Voice* getVoice(absl::optional index = {}) + { + const auto idx = index.value_or(indices[0]); + if (static_cast(idx) >= impl.numVoices_) + return {}; - client.receive<'i'>(delay, path, static_cast(lfo.sub[0].wave)); - } break; + auto& voice = impl.voiceManager_[idx]; + if (voice.isFree()) + return {}; - 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) + return &voice; + } - client.receive<'f'>(delay, path, point.time); - } break; + // Helpers to get and check the values of the indices + template + absl::optional index(int i) + { + if (i <= ssize(indices)) + return static_cast(indices[i]); - 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) + return absl::nullopt; + } - client.receive<'f'>(delay, path, point.ccTime.getWithDefault(indices[3])); - } break; + absl::optional sindex(int i) { return index(i); } - 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) + absl::optional checkCC(int i = 0) + { + auto cc = index(i); + return cc <= config::numCCs ? cc : absl::nullopt; + } - client.receive<'f'>(delay, path, point.level); - } break; + absl::optional checkNote(int i = 0) + { + auto note = sindex(i); + return note <= 127 ? note : absl::nullopt; + } - 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) +private: + Client& client; + std::vector indices; + sfz::Synth::Impl& impl; + int delay; + const char* path; + const char* sig; +}; - client.receive<'f'>(delay, path, point.ccLevel.getWithDefault(indices[3])); - } break; +void sfz::Synth::dispatchMessage(Client& client, int delay, const char* path, const char* sig, const sfizz_arg_t* args) +{ + Impl& impl = *impl_; + MessagingHelper m {client, delay, path, sig, impl}; + using ModParam = MessagingHelper::ModParam; - #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 + switch (hashMessagePath(path, sig)) { + #define MATCH(p, s) case hash(p "," s): if (m.match(p, s)) + MATCH("/hello", "") { m.reply(""); } break; + //---------------------------------------------------------------------- + MATCH("/num_regions", "") { m.reply(impl.layers_.size()); } break; + MATCH("/num_groups", "") { m.reply(impl.numGroups_); } break; + MATCH("/num_masters", "") { m.reply(impl.numMasters_); } break; + MATCH("/num_curves", "") { m.reply(impl.resources_.getCurves().getNumCurves()); } break; + MATCH("/num_samples", "") { m.reply(impl.resources_.getFilePool().getNumPreloadedSamples()); } break; + MATCH("/octave_offset", "") { m.reply(impl.octaveOffset_); } break; + MATCH("/note_offset", "") { m.reply(impl.noteOffset_); } break; + MATCH("/num_outputs", "") { m.reply(impl.numOutputs_); } break; + MATCH("/num_active_voices", "") { m.reply(uint32_t(impl.voiceManager_.getNumActiveVoices())); } break; + //---------------------------------------------------------------------- + MATCH("/key/slots", "") { m.reply(impl.keySlots_); } break; + MATCH("/key&/label", "") { if (auto k = m.sindex(0)) m.reply(impl.getKeyLabel(*k)); } break; + //---------------------------------------------------------------------- + MATCH("/root_path", "") { m.reply(impl.rootPath_); } break; + MATCH("/image", "") { m.reply(impl.image_.c_str()); } break; + MATCH("/image_controls", "") { m.reply(impl.image_controls_.c_str()); } break; + //---------------------------------------------------------------------- + MATCH("/sw/last/slots", "") { m.reply(impl.swLastSlots_); } break; + MATCH("/sw/last/current", "") { m.reply(impl.currentSwitch_); } break; + MATCH("/sw/last/&/label", "") { if (auto k = m.sindex(0)) m.reply(impl.getKeyswitchLabel(*k)); } break; + //---------------------------------------------------------------------- + MATCH("/cc/slots", "") { m.reply(impl.currentUsedCCs_); } break; + MATCH("/cc&/default", "") { if (auto cc = m.checkCC()) m.reply(impl.defaultCCValues_[*cc]); } break; + MATCH("/cc&/value", "") { if (auto cc = m.checkCC()) m.reply(impl.resources_.getMidiState().getCCValue(*cc)); } break; + MATCH("/cc&/value", "f") { if (auto cc = m.checkCC()) impl.resources_.getMidiState().ccEvent(delay, *cc, args[0].f); } break; + MATCH("/cc&/label", "") { if (auto cc = m.checkCC()) m.reply(impl.getCCLabel(*cc)); } break; + MATCH("/cc/changed", "") { m.reply(impl.changedCCsThisCycle_); } break; + MATCH("/cc/changed~", "") { m.reply(impl.changedCCsLastCycle_); } break; + MATCH("/sustain_or_sostenuto/slots", "") { m.reply(impl.sustainOrSostenuto_); } break; + MATCH("/aftertouch", "") { m.reply(impl.resources_.getMidiState().getChannelAftertouch()); } break; + MATCH("/poly_aftertouch/&", "") { if (auto note = m.checkNote()) m.reply(impl.resources_.getMidiState().getPolyAftertouch(*note)); } break; + MATCH("/pitch_bend", "") { m.reply(impl.resources_.getMidiState().getPitchBend()); } break; + //---------------------------------------------------------------------- + MATCH("/mem/buffers", "") { m.reply(BufferCounter::counter().getTotalBytes()); } break; + //---------------------------------------------------------------------- + MATCH("/region&/delay", "") { m.reply(&Region::delay); } break; + MATCH("/region&/delay_random", "") { m.reply(&Region::delayRandom); } break; + MATCH("/region&/sample", "") { if (auto region = m.getRegion()) { m.reply(region->sampleId->filename()); } } break; + MATCH("/region&/direction", "") { if (auto region = m.getRegion()) { m.reply(region->sampleId->isReverse() ? "reverse" : "forward"); } } break; + MATCH("/region&/delay_cc&", "") { m.reply(&Region::delayCC); } break; + MATCH("/region&/offset", "") { m.reply(&Region::offset); } break; + MATCH("/region&/offset_random", "") { m.reply(&Region::offsetRandom); } break; + MATCH("/region&/offset_cc&", "") { m.reply(&Region::offsetCC); } break; + MATCH("/region&/end", "") { m.reply(&Region::sampleEnd); } break; + MATCH("/region&/end_cc&", "") { m.reply(&Region::endCC); } break; + MATCH("/region&/enabled", "") { if (auto region = m.getRegion()) { m.reply(!region->disabled()); } } break; + MATCH("/region&/trigger_on_note", "") { m.reply(&Region::triggerOnNote); } break; + MATCH("/region&/trigger_on_cc", "") { m.reply(&Region::triggerOnCC); } break; + MATCH("/region&/use_timer_range", "") { m.reply(&Region::useTimerRange); } break; + MATCH("/region&/count", "") { m.reply(&Region::sampleCount); } break; + MATCH("/region&/loop_range", "") { m.reply(&Region::loopRange); } break; + MATCH("/region&/loop_start_cc&", "") { m.reply(&Region::loopStartCC); } break; + MATCH("/region&/loop_end_cc&", "") { m.reply(&Region::loopEndCC); } break; + MATCH("/region&/loop_mode", "") { m.reply(&Region::loopMode, LoopMode::no_loop); } break; + MATCH("/region&/loop_crossfade", "") { m.reply(&Region::loopCrossfade); } break; + MATCH("/region&/loop_count", "") { m.reply(&Region::loopCount); } break; + MATCH("/region&/output", "") { m.reply(&Region::output); } break; + MATCH("/region&/group", "") { m.reply(&Region::group); } break; + MATCH("/region&/off_by", "") { m.reply(&Region::offBy); } break; + MATCH("/region&/off_mode", "") { m.reply(&Region::offMode); } break; + MATCH("/region&/key_range", "") { m.reply(&Region::keyRange); } break; + MATCH("/region&/off_time", "") { m.reply(&Region::offTime); } break; + MATCH("/region&/pitch_keycenter", "") { m.reply(&Region::pitchKeycenter); } break; + MATCH("/region&/vel_range", "") { m.reply(&Region::velocityRange); } break; + MATCH("/region&/bend_range", "") { m.reply(&Region::bendRange); } break; + MATCH("/region&/program_range", "") { m.reply(&Region::programRange); } break; + MATCH("/region&/cc_range&", "") { m.reply(&Region::ccConditions); } break; + MATCH("/region&/sw_last", "") { + if (auto region = m.getRegion()) { + if (region->lastKeyswitch) m.reply(region->lastKeyswitch); + else m.reply(region->lastKeyswitchRange); + } + } break; + MATCH("/region&/sw_label", "") { m.reply(&Region::keyswitchLabel); } break; + MATCH("/region&/sw_up", "") { m.reply(&Region::upKeyswitch); } break; + MATCH("/region&/sw_down", "") { m.reply(&Region::downKeyswitch); } break; + MATCH("/region&/sw_previous", "") { m.reply(&Region::previousKeyswitch); } break; + MATCH("/region&/sw_vel", "") { m.reply(&Region::velocityOverride); } break; + MATCH("/region&/chanaft_range", "") { m.reply(&Region::aftertouchRange); } break; + MATCH("/region&/polyaft_range", "") { m.reply(&Region::polyAftertouchRange); } break; + MATCH("/region&/bpm_range", "") { m.reply(&Region::bpmRange); } break; + MATCH("/region&/rand_range", "") { m.reply(&Region::randRange); } break; + MATCH("/region&/seq_length", "") { m.reply(&Region::sequenceLength); } break; + MATCH("/region&/seq_position", "") { m.reply(&Region::sequencePosition); } break; + MATCH("/region&/trigger", "") { m.reply(&Region::trigger); } break; + MATCH("/region&/start_cc_range&", "") { m.reply(&Region::ccTriggers, false); } break; + MATCH("/region&/volume", "") { m.reply(&Region::volume); } break; + MATCH("/region&/volume_cc&", "") { m.reply(ModId::Volume, ModParam::Depth); } break; + MATCH("/region&/volume_stepcc&", "") { m.reply(ModId::Volume, ModParam::Step); } break; + MATCH("/region&/volume_smoothcc&", "") { m.reply(ModId::Volume, ModParam::Smooth); } break; + MATCH("/region&/volume_curvecc&", "") { m.reply(ModId::Volume, ModParam::Curve); } break; + MATCH("/region&/pan", "") { m.reply(&Region::pan, Default::pan); } break; + MATCH("/region&/pan_cc&", "") { m.reply(ModId::Pan, ModParam::Depth, Default::pan); } break; + MATCH("/region&/pan_stepcc&", "") { m.reply(ModId::Pan, ModParam::Step, Default::pan); } break; + MATCH("/region&/pan_smoothcc&", "") { m.reply(ModId::Pan, ModParam::Smooth, Default::pan); } break; + MATCH("/region&/pan_curvecc&", "") { m.reply(ModId::Pan, ModParam::Curve, Default::pan); } break; + MATCH("/region&/width", "") { m.reply(&Region::width, Default::width); } break; + MATCH("/region&/width_cc&", "") { m.reply(ModId::Width, ModParam::Depth, Default::width); } break; + MATCH("/region&/width_stepcc&", "") { m.reply(ModId::Width, ModParam::Step, Default::width); } break; + MATCH("/region&/width_smoothcc&", "") { m.reply(ModId::Width, ModParam::Smooth, Default::width); } break; + MATCH("/region&/width_curvecc&", "") { m.reply(ModId::Width, ModParam::Curve, Default::width); } break; + MATCH("/region&/timer_range", "") { m.reply(&Region::timerRange); } break; + MATCH("/region&/position", "") { m.reply(&Region::position, Default::position); } break; + MATCH("/region&/position_cc&", "") { m.reply(ModId::Position, ModParam::Depth, Default::position); } break; + MATCH("/region&/position_stepcc&", "") { m.reply(ModId::Position, ModParam::Step, Default::position); } break; + MATCH("/region&/position_smoothcc&", "") { m.reply(ModId::Position, ModParam::Smooth, Default::position); } break; + MATCH("/region&/position_curvecc&", "") { m.reply(ModId::Position, ModParam::Curve, Default::position); } break; + MATCH("/region&/amplitude", "") { m.reply(&Region::amplitude, Default::amplitude); } break; + MATCH("/region&/amplitude_cc&", "") { m.reply(ModId::Amplitude, ModParam::Depth, Default::amplitude); } break; + MATCH("/region&/amplitude_stepcc&", "") { m.reply(ModId::Amplitude, ModParam::Step, Default::amplitude); } break; + MATCH("/region&/amplitude_smoothcc&", "") { m.reply(ModId::Amplitude, ModParam::Smooth, Default::amplitude); } break; + MATCH("/region&/amplitude_curvecc&", "") { m.reply(ModId::Amplitude, ModParam::Curve, Default::amplitude); } break; + MATCH("/region&/amp_keycenter", "") { m.reply(&Region::ampKeycenter); } break; + MATCH("/region&/amp_keytrack", "") { m.reply(&Region::ampKeytrack); } break; + MATCH("/region&/amp_veltrack", "") { m.reply(&Region::ampVeltrack, Default::ampVeltrack); } break; + MATCH("/region&/amp_veltrack_cc&", "") { m.reply(&Region::ampVeltrackCC, false, ModParam::Depth, Default::ampVeltrackMod); } break; + MATCH("/region&/amp_veltrack_curvecc&", "") { m.reply(&Region::ampVeltrackCC, false, ModParam::Curve, Default::ampVeltrackMod); } break; + MATCH("/region&/amp_random", "") { m.reply(&Region::ampRandom); } break; + MATCH("/region&/xfin_key_range", "") { m.reply(&Region::crossfadeKeyInRange); } break; + MATCH("/region&/xfout_key_range", "") { m.reply(&Region::crossfadeKeyOutRange); } break; + MATCH("/region&/xfin_vel_range", "") { m.reply(&Region::crossfadeVelInRange); } break; + MATCH("/region&/xfout_vel_range", "") { m.reply(&Region::crossfadeVelOutRange); } break; + MATCH("/region&/xfin_cc_range&", "") { m.reply(&Region::crossfadeCCInRange, false); } break; + MATCH("/region&/xfout_cc_range&", "") { m.reply(&Region::crossfadeCCOutRange, false); } break; + MATCH("/region&/xf_keycurve", "") { m.reply(&Region::crossfadeKeyCurve); } break; + MATCH("/region&/xf_velcurve", "") { m.reply(&Region::crossfadeVelCurve); } break; + MATCH("/region&/xf_cccurve", "") { m.reply(&Region::crossfadeCCCurve); } break; + MATCH("/region&/global_volume", "") { m.reply(&Region::globalVolume); } break; + MATCH("/region&/master_volume", "") { m.reply(&Region::masterVolume); } break; + MATCH("/region&/group_volume", "") { m.reply(&Region::groupVolume); } break; + MATCH("/region&/global_amplitude", "") { m.reply(&Region::globalAmplitude, Default::amplitude); } break; + MATCH("/region&/master_amplitude", "") { m.reply(&Region::masterAmplitude, Default::amplitude); } break; + MATCH("/region&/group_amplitude", "") { m.reply(&Region::groupAmplitude, Default::amplitude); } break; + MATCH("/region&/pitch_keytrack", "") { m.reply(&Region::pitchKeytrack); } break; + MATCH("/region&/pitch_veltrack", "") { m.reply(&Region::pitchVeltrack); } break; + MATCH("/region&/pitch_veltrack_cc&", "") { m.reply(&Region::pitchVeltrackCC, false, ModParam::Depth); } break; + MATCH("/region&/pitch_veltrack_curvecc&", "") { m.reply(&Region::pitchVeltrackCC, false, ModParam::Curve); } break; + MATCH("/region&/pitch_random", "") { m.reply(&Region::pitchRandom); } break; + MATCH("/region&/transpose", "") { m.reply(&Region::transpose); } break; + MATCH("/region&/pitch", "") { m.reply(&Region::pitch); } break; + MATCH("/region&/pitch_cc&", "") { m.reply(ModId::Pitch, ModParam::Depth, Default::pitch); } break; + MATCH("/region&/pitch_stepcc&", "") { m.reply(ModId::Pitch, ModParam::Step, Default::pitch); } break; + MATCH("/region&/pitch_smoothcc&", "") { m.reply(ModId::Pitch, ModParam::Smooth, Default::pitch); } break; + MATCH("/region&/pitch_curvecc&", "") { m.reply(ModId::Pitch, ModParam::Curve, Default::pitch); } break; + MATCH("/region&/bend_up", "") { m.reply(&Region::bendUp); } break; + MATCH("/region&/bend_down", "") { m.reply(&Region::bendDown); } break; + MATCH("/region&/bend_step", "") { m.reply(&Region::bendStep); } break; + MATCH("/region&/bend_smooth", "") { m.reply(&Region::bendSmooth); } break; + MATCH("/region&/ampeg_attack", "") { m.reply(&Region::amplitudeEG, &EGDescription::attack); } break; + MATCH("/region&/ampeg_delay", "") { m.reply(&Region::amplitudeEG, &EGDescription::delay); } break; + MATCH("/region&/ampeg_decay", "") { m.reply(&Region::amplitudeEG, &EGDescription::decay); } break; + MATCH("/region&/ampeg_hold", "") { m.reply(&Region::amplitudeEG, &EGDescription::hold); } break; + MATCH("/region&/ampeg_release", "") { m.reply(&Region::amplitudeEG, &EGDescription::release); } break; + MATCH("/region&/ampeg_start", "") { m.reply(&Region::amplitudeEG, &EGDescription::start, Default::egPercentMod); } break; + MATCH("/region&/ampeg_sustain", "") { m.reply(&Region::amplitudeEG, &EGDescription::sustain, Default::egPercentMod); } break; + MATCH("/region&/ampeg_depth", "") { m.reply(&Region::amplitudeEG, &EGDescription::depth); } break; + MATCH("/region&/ampeg_attack_cc&", "") { m.reply(&Region::amplitudeEG, &EGDescription::ccAttack, ModParam::Depth); } break; + MATCH("/region&/ampeg_attack_curvecc&", "") { m.reply(&Region::amplitudeEG, &EGDescription::ccAttack, ModParam::Curve); } break; + MATCH("/region&/ampeg_decay_cc&", "") { m.reply(&Region::amplitudeEG, &EGDescription::ccDecay, ModParam::Depth); } break; + MATCH("/region&/ampeg_decay_curvecc&", "") { m.reply(&Region::amplitudeEG, &EGDescription::ccDecay, ModParam::Curve); } break; + MATCH("/region&/ampeg_delay_cc&", "") { m.reply(&Region::amplitudeEG, &EGDescription::ccDelay, ModParam::Depth); } break; + MATCH("/region&/ampeg_delay_curvecc&", "") { m.reply(&Region::amplitudeEG, &EGDescription::ccDelay, ModParam::Curve); } break; + MATCH("/region&/ampeg_hold_cc&", "") { m.reply(&Region::amplitudeEG, &EGDescription::ccHold, ModParam::Depth); } break; + MATCH("/region&/ampeg_hold_curvecc&", "") { m.reply(&Region::amplitudeEG, &EGDescription::ccHold, ModParam::Curve); } break; + MATCH("/region&/ampeg_release_cc&", "") { m.reply(&Region::amplitudeEG, &EGDescription::ccRelease, ModParam::Depth); } break; + MATCH("/region&/ampeg_release_curvecc&", "") { m.reply(&Region::amplitudeEG, &EGDescription::ccRelease, ModParam::Curve); } break; + MATCH("/region&/ampeg_sustain_cc&", "") { m.reply(&Region::amplitudeEG, &EGDescription::ccSustain, ModParam::Depth, Default::egPercentMod); } break; + MATCH("/region&/ampeg_sustain_curvecc&", "") { m.reply(&Region::amplitudeEG, &EGDescription::ccSustain, ModParam::Curve, Default::egPercentMod); } break; + MATCH("/region&/ampeg_start_cc&", "") { m.reply(&Region::amplitudeEG, &EGDescription::ccStart, ModParam::Depth, Default::egPercentMod); } break; + MATCH("/region&/ampeg_start_curvecc&", "") { m.reply(&Region::amplitudeEG, &EGDescription::ccStart, ModParam::Curve, Default::egPercentMod); } break; + MATCH("/region&/ampeg_vel&attack", "") { m.reply(&Region::amplitudeEG, &EGDescription::vel2attack); } break; + MATCH("/region&/ampeg_vel&delay", "") { m.reply(&Region::amplitudeEG, &EGDescription::vel2delay); } break; + MATCH("/region&/ampeg_vel&decay", "") { m.reply(&Region::amplitudeEG, &EGDescription::vel2decay); } break; + MATCH("/region&/ampeg_vel&hold", "") { m.reply(&Region::amplitudeEG, &EGDescription::vel2hold); } break; + MATCH("/region&/ampeg_vel&release", "") { m.reply(&Region::amplitudeEG, &EGDescription::vel2release); } break; + MATCH("/region&/ampeg_vel&sustain", "") { m.reply(&Region::amplitudeEG, &EGDescription::vel2sustain, Default::egPercentMod); } break; + MATCH("/region&/ampeg_vel&depth", "") { m.reply(&Region::amplitudeEG, &EGDescription::vel2depth); } break; + MATCH("/region&/ampeg_dynamic", "") { m.reply(&Region::amplitudeEG, &EGDescription::dynamic); } break; + MATCH("/region&/fileg_attack", "") { m.reply(&Region::filterEG, &EGDescription::attack); } break; + MATCH("/region&/fileg_delay", "") { m.reply(&Region::filterEG, &EGDescription::delay); } break; + MATCH("/region&/fileg_decay", "") { m.reply(&Region::filterEG, &EGDescription::decay); } break; + MATCH("/region&/fileg_hold", "") { m.reply(&Region::filterEG, &EGDescription::hold); } break; + MATCH("/region&/fileg_release", "") { m.reply(&Region::filterEG, &EGDescription::release); } break; + MATCH("/region&/fileg_start", "") { m.reply(&Region::filterEG, &EGDescription::start, Default::egPercentMod); } break; + MATCH("/region&/fileg_sustain", "") { m.reply(&Region::filterEG, &EGDescription::sustain, Default::egPercentMod); } break; + MATCH("/region&/fileg_depth", "") { m.reply(&Region::filterEG, &EGDescription::depth); } break; + MATCH("/region&/fileg_attack_cc&", "") { m.reply(&Region::filterEG, &EGDescription::ccAttack, ModParam::Depth); } break; + MATCH("/region&/fileg_attack_curvecc&", "") { m.reply(&Region::filterEG, &EGDescription::ccAttack, ModParam::Curve); } break; + MATCH("/region&/fileg_decay_cc&", "") { m.reply(&Region::filterEG, &EGDescription::ccDecay, ModParam::Depth); } break; + MATCH("/region&/fileg_decay_curvecc&", "") { m.reply(&Region::filterEG, &EGDescription::ccDecay, ModParam::Curve); } break; + MATCH("/region&/fileg_delay_cc&", "") { m.reply(&Region::filterEG, &EGDescription::ccDelay, ModParam::Depth); } break; + MATCH("/region&/fileg_delay_curvecc&", "") { m.reply(&Region::filterEG, &EGDescription::ccDelay, ModParam::Curve); } break; + MATCH("/region&/fileg_hold_cc&", "") { m.reply(&Region::filterEG, &EGDescription::ccHold, ModParam::Depth); } break; + MATCH("/region&/fileg_hold_curvecc&", "") { m.reply(&Region::filterEG, &EGDescription::ccHold, ModParam::Curve); } break; + MATCH("/region&/fileg_release_cc&", "") { m.reply(&Region::filterEG, &EGDescription::ccRelease, ModParam::Depth); } break; + MATCH("/region&/fileg_release_curvecc&", "") { m.reply(&Region::filterEG, &EGDescription::ccRelease, ModParam::Curve); } break; + MATCH("/region&/fileg_sustain_cc&", "") { m.reply(&Region::filterEG, &EGDescription::ccSustain, ModParam::Depth, Default::egPercentMod); } break; + MATCH("/region&/fileg_sustain_curvecc&", "") { m.reply(&Region::filterEG, &EGDescription::ccSustain, ModParam::Curve, Default::egPercentMod); } break; + MATCH("/region&/fileg_start_cc&", "") { m.reply(&Region::filterEG, &EGDescription::ccStart, ModParam::Depth, Default::egPercentMod); } break; + MATCH("/region&/fileg_start_curvecc&", "") { m.reply(&Region::filterEG, &EGDescription::ccStart, ModParam::Curve, Default::egPercentMod); } break; + MATCH("/region&/fileg_dynamic", "") { m.reply(&Region::filterEG, &EGDescription::dynamic); } break; + MATCH("/region&/pitcheg_attack", "") { m.reply(&Region::pitchEG, &EGDescription::attack); } break; + MATCH("/region&/pitcheg_delay", "") { m.reply(&Region::pitchEG, &EGDescription::delay); } break; + MATCH("/region&/pitcheg_decay", "") { m.reply(&Region::pitchEG, &EGDescription::decay); } break; + MATCH("/region&/pitcheg_hold", "") { m.reply(&Region::pitchEG, &EGDescription::hold); } break; + MATCH("/region&/pitcheg_release", "") { m.reply(&Region::pitchEG, &EGDescription::release); } break; + MATCH("/region&/pitcheg_start", "") { m.reply(&Region::pitchEG, &EGDescription::start, Default::egPercentMod); } break; + MATCH("/region&/pitcheg_sustain", "") { m.reply(&Region::pitchEG, &EGDescription::sustain, Default::egPercentMod); } break; + MATCH("/region&/pitcheg_depth", "") { m.reply(&Region::pitchEG, &EGDescription::depth); } break; + MATCH("/region&/pitcheg_attack_cc&", "") { m.reply(&Region::pitchEG, &EGDescription::ccAttack, ModParam::Depth); } break; + MATCH("/region&/pitcheg_attack_curvecc&", "") { m.reply(&Region::pitchEG, &EGDescription::ccAttack, ModParam::Curve); } break; + MATCH("/region&/pitcheg_decay_cc&", "") { m.reply(&Region::pitchEG, &EGDescription::ccDecay, ModParam::Depth); } break; + MATCH("/region&/pitcheg_decay_curvecc&", "") { m.reply(&Region::pitchEG, &EGDescription::ccDecay, ModParam::Curve); } break; + MATCH("/region&/pitcheg_delay_cc&", "") { m.reply(&Region::pitchEG, &EGDescription::ccDelay, ModParam::Depth); } break; + MATCH("/region&/pitcheg_delay_curvecc&", "") { m.reply(&Region::pitchEG, &EGDescription::ccDelay, ModParam::Curve); } break; + MATCH("/region&/pitcheg_hold_cc&", "") { m.reply(&Region::pitchEG, &EGDescription::ccHold, ModParam::Depth); } break; + MATCH("/region&/pitcheg_hold_curvecc&", "") { m.reply(&Region::pitchEG, &EGDescription::ccHold, ModParam::Curve); } break; + MATCH("/region&/pitcheg_release_cc&", "") { m.reply(&Region::pitchEG, &EGDescription::ccRelease, ModParam::Depth); } break; + MATCH("/region&/pitcheg_release_curvecc&", "") { m.reply(&Region::pitchEG, &EGDescription::ccRelease, ModParam::Curve); } break; + MATCH("/region&/pitcheg_sustain_cc&", "") { m.reply(&Region::pitchEG, &EGDescription::ccSustain, ModParam::Depth, Default::egPercentMod); } break; + MATCH("/region&/pitcheg_sustain_curvecc&", "") { m.reply(&Region::pitchEG, &EGDescription::ccSustain, ModParam::Curve, Default::egPercentMod); } break; + MATCH("/region&/pitcheg_start_cc&", "") { m.reply(&Region::pitchEG, &EGDescription::ccStart, ModParam::Depth, Default::egPercentMod); } break; + MATCH("/region&/pitcheg_start_curvecc&", "") { m.reply(&Region::pitchEG, &EGDescription::ccStart, ModParam::Curve, Default::egPercentMod); } break; + MATCH("/region&/pitcheg_dynamic", "") { m.reply(&Region::pitchEG, &EGDescription::dynamic); } break; + MATCH("/region&/note_polyphony", "") { m.reply(&Region::notePolyphony); } break; + MATCH("/region&/rt_dead", "") { m.reply(&Region::rtDead); } break; + MATCH("/region&/sustain_sw", "") { m.reply(&Region::checkSustain); } break; + MATCH("/region&/sostenuto_sw", "") { m.reply(&Region::checkSostenuto); } break; + MATCH("/region&/sustain_cc", "") { m.reply(&Region::sustainCC); } break; + MATCH("/region&/sostenuto_cc", "") { m.reply(&Region::sostenutoCC); } break; + MATCH("/region&/sustain_lo", "") { m.reply(&Region::sustainThreshold); } break; + MATCH("/region&/sostenuto_lo", "") { m.reply(&Region::sostenutoThreshold); } break; + MATCH("/region&/note_selfmask", "") { m.reply(&Region::selfMask); } break; + MATCH("/region&/oscillator_phase", "") { m.reply(&Region::oscillatorPhase); } break; + MATCH("/region&/oscillator_quality", "") { m.reply(&Region::oscillatorQuality); } break; + MATCH("/region&/oscillator_mode", "") { m.reply(&Region::oscillatorMode); } break; + MATCH("/region&/oscillator_multi", "") { m.reply(&Region::oscillatorMulti); } break; + MATCH("/region&/oscillator_detune", "") { m.reply(&Region::oscillatorDetune); } break; + MATCH("/region&/oscillator_mod_depth", "") { m.reply(&Region::oscillatorModDepth, Default::oscillatorModDepth); } break; + // TODO: detune cc, mod depth cc + MATCH("/region&/effect&", "") { + if (auto region = m.getRegion()) + if (auto effectIdx = m.sindex(1)) + if (effectIdx > 0 && effectIdx < ssize(region->gainToEffect)) + m.reply(region->gainToEffect[*effectIdx], Default::effect); + } break; + MATCH("/region&/filter&/cutoff", "") { m.reply(&FilterDescription::cutoff); } break; + MATCH("/region&/filter&/cutoff_cc&", "") { m.reply(ModId::FilCutoff, ModParam::Depth); } break; + MATCH("/region&/filter&/cutoff_curvecc&", "") { m.reply(ModId::FilCutoff, ModParam::Curve); } break; + MATCH("/region&/filter&/cutoff_stepcc&", "") { m.reply(ModId::FilCutoff, ModParam::Step); } break; + MATCH("/region&/filter&/cutoff_smoothcc&", "") { m.reply(ModId::FilCutoff, ModParam::Smooth); } break; + MATCH("/region&/filter&/resonance", "") { m.reply(&FilterDescription::resonance); } break; + MATCH("/region&/filter&/gain", "") { m.reply(&FilterDescription::gain); } break; + MATCH("/region&/filter&/keycenter", "") { m.reply(&FilterDescription::keycenter); } break; + MATCH("/region&/filter&/keytrack", "") { m.reply(&FilterDescription::keytrack); } break; + MATCH("/region&/filter&/veltrack", "") { m.reply(&FilterDescription::veltrack); } break; + MATCH("/region&/filter&/veltrack_cc&", "") { m.reply(&FilterDescription::veltrackCC, ModParam::Depth); } break; + MATCH("/region&/filter&/veltrack_curvecc&", "") { m.reply(&FilterDescription::veltrackCC, ModParam::Curve); } break; + MATCH("/region&/filter&/type", "") { m.reply(&FilterDescription::type); } break; + //---------------------------------------------------------------------- + MATCH("/region&/eq&/gain", "") { m.reply(&EQDescription::gain); } break; + MATCH("/region&/eq&/bandwidth", "") { m.reply(&EQDescription::bandwidth); } break; + MATCH("/region&/eq&/frequency", "") { m.reply(&EQDescription::frequency); } break; + MATCH("/region&/eq&/vel&freq", "") { m.reply(&EQDescription::vel2frequency); } break; + MATCH("/region&/eq&/vel&gain", "") { m.reply(&EQDescription::vel2gain); } break; + MATCH("/region&/eq&/type", "") { m.reply(&EQDescription::type); } break; + //---------------------------------------------------------------------- + MATCH("/region&/lfo&/wave", "") { m.reply(&LFODescription::Sub::wave); } break; + //---------------------------------------------------------------------- + MATCH("/region&/eg&/point&/time", "") { m.reply(&FlexEGPoint::time); } break; + MATCH("/region&/eg&/point&/time_cc&", "") { m.reply(&FlexEGPoint::ccTime); } break; + MATCH("/region&/eg&/point&/level", "") { m.reply(&FlexEGPoint::level); } break; + MATCH("/region&/eg&/point&/level_cc&", "") { m.reply(&FlexEGPoint::ccLevel); } break; //---------------------------------------------------------------------- + MATCH("/voice&/trigger_value", "") { m.reply(&TriggerEvent::value); } break; + MATCH("/voice&/trigger_number", "") { m.reply(&TriggerEvent::number); } break; + MATCH("/voice&/trigger_type", "") { m.reply(&TriggerEvent::type); } break; + MATCH("/voice&/remaining_delay", "") { m.reply(&Voice::getRemainingDelay); } break; + MATCH("/voice&/source_position", "") { m.reply(&Voice::getSourcePosition); } break; + //---------------------------------------------------------------------- + // Setting values // Note: all these must be rt-safe within the parseOpcode method in region @@ -1858,161 +871,38 @@ void sfz::Synth::dispatchMessage(Client& client, int delay, const char* path, co impl.resources_.getSynthConfig().sustainCancelsRelease = false; } break; - #define GET_REGION_OR_BREAK(idx) \ - if (idx >= impl.layers_.size()) \ - break; \ - Layer& layer = *impl.layers_[idx]; \ - Region& region = layer.getRegion(); - - #define GET_FILTER_OR_BREAK(idx) \ - if (idx >= region.filters.size()) \ - break; \ - auto& filter = region.filters[idx]; - - #define GET_LFO_OR_BREAK(idx) \ - if (idx >= region.lfos.size()) \ - break; \ - auto& lfo = region.lfos[idx]; - - #define GET_LFO_SUB_OR_BREAK(idx) \ - if (idx >= lfo.sub.size()) \ - break; \ - auto& sub = lfo.sub[idx]; - MATCH("/region&/pitch_keycenter", "i") { - GET_REGION_OR_BREAK(indices[0]) - region.pitchKeycenter = Opcode::transform(Default::key, args[0].i); + if (auto region = m.getRegion()) + region->pitchKeycenter = Opcode::transform(Default::key, args[0].i); } break; MATCH("/region&/loop_mode", "s") { - GET_REGION_OR_BREAK(indices[0]) - region.loopMode = Opcode::readOptional(Default::loopMode, args[0].s); + if (auto region = m.getRegion()) + region->loopMode = Opcode::readOptional(Default::loopMode, args[0].s); } break; MATCH("/region&/filter&/type", "s") { - GET_REGION_OR_BREAK(indices[0]) - GET_FILTER_OR_BREAK(indices[1]) - filter.type = Opcode::read(Default::filter, args[0].s); + if (auto region = m.getRegion()) + if (auto filter = m.getFilter(*region)) + filter->type = Opcode::read(Default::filter, args[0].s); } break; MATCH("/region&/lfo&/wave", "i") { - indices[2] = 0; - goto set_lfoN_wave; + if (auto region = m.getRegion()) + if (auto lfo = m.getLFO(*region)) + if (!lfo->sub.empty()) + lfo->sub[0].wave = Opcode::transform(Default::lfoWave, args[0].i); } break; MATCH("/region&/lfo&/wave&", "i") { - set_lfoN_wave: - GET_REGION_OR_BREAK(indices[0]) - GET_LFO_OR_BREAK(indices[1]) - GET_LFO_SUB_OR_BREAK(indices[2]) - sub.wave = Opcode::transform(Default::lfoWave, args[0].i); - } break; - - #undef GET_REGION_OR_BREAK - #undef GET_FILTER_OR_BREAK - #undef GET_LFO_OR_BREAK - #undef GET_LFO_SUB_OR_BREAK - - //---------------------------------------------------------------------- - // Voices - - MATCH("/num_active_voices", "") { - client.receive<'i'>(delay, path, impl.voiceManager_.getNumActiveVoices()); - } break; - - #define GET_VOICE_OR_BREAK(idx) \ - if (static_cast(idx) >= impl.numVoices_) \ - break; \ - const auto& voice = impl.voiceManager_[idx]; \ - if (voice.isFree()) \ - break; - - MATCH("/voice&/trigger_value", "") { - GET_VOICE_OR_BREAK(indices[0]) - client.receive<'f'>(delay, path, voice.getTriggerEvent().value); - } break; - - MATCH("/voice&/trigger_number", "") { - GET_VOICE_OR_BREAK(indices[0]) - client.receive<'i'>(delay, path, voice.getTriggerEvent().number); - } break; - - MATCH("/voice&/trigger_type", "") { - GET_VOICE_OR_BREAK(indices[0]) - const auto& event = voice.getTriggerEvent(); - switch (event.type) { - case TriggerEventType::CC: - client.receive<'s'>(delay, path, "cc"); - break; - case TriggerEventType::NoteOn: - client.receive<'s'>(delay, path, "note_on"); - break; - case TriggerEventType::NoteOff: - client.receive<'s'>(delay, path, "note_on"); - break; - } - - } 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()); + if (auto region = m.getRegion()) + if (auto lfo = m.getLFO(*region)) + if (auto sub = m.getLFOSub(*lfo)) + sub->wave = Opcode::transform(Default::lfoWave, args[0].i); } break; #undef MATCH - // TODO... - } -} - -static bool extractMessage(const char* pattern, const char* path, unsigned* indices) -{ - unsigned nthIndex = 0; - - while (const char *endp = strchr(pattern, '&')) { - if (nthIndex == maxIndices) - return false; - - size_t length = endp - pattern; - if (strncmp(pattern, path, length)) - return false; - pattern += length; - path += length; - - length = 0; - while (absl::ascii_isdigit(path[length])) - ++length; - - if (!absl::SimpleAtoi(absl::string_view(path, length), &indices[nthIndex++])) - return false; - - pattern += 1; - path += length; - } - - return !strcmp(path, pattern); -} - -static uint64_t hashMessagePath(const char* path, const char* sig) -{ - uint64_t h = Fnv1aBasis; - while (unsigned char c = *path++) { - if (!absl::ascii_isdigit(c)) - h = hashByte(c, h); - else { - h = hashByte('&', h); - while (absl::ascii_isdigit(*path)) - ++path; - } } - h = hashByte(',', h); - while (unsigned char c = *sig++) - h = hashByte(c, h); - return h; } } // namespace sfz diff --git a/src/sfizz/utility/Size.h b/src/sfizz/utility/Size.h new file mode 100644 index 000000000..f7d5277ca --- /dev/null +++ b/src/sfizz/utility/Size.h @@ -0,0 +1,25 @@ +// 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 + +template +constexpr auto ssize(const C& c) + -> std::common_type_t> +{ + using R = std::common_type_t>; + return static_cast(c.size()); +} + +template +constexpr std::ptrdiff_t ssize(const T (&)[N]) noexcept +{ + return N; +} diff --git a/tests/OpcodeT.cpp b/tests/OpcodeT.cpp index 5423d9af1..837be6af5 100644 --- a/tests/OpcodeT.cpp +++ b/tests/OpcodeT.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 "Defaults.h" #include "sfizz/Region.h" #include "catch2/catch.hpp" using namespace Catch::literals; @@ -573,3 +574,11 @@ TEST_CASE("[Opcode] readBooleanFromOpcode") REQUIRE(readBoolean({"On"}) == true); REQUIRE(readBoolean({"oFf"}) == false); } + +TEST_CASE("[Opcode] denormalize") +{ + REQUIRE(Default::egTimeMod.denormalizeInput(0.0f) == 0.0f); + REQUIRE(Default::egTimeMod.denormalizeInput(1.0f) == 1.0f); + REQUIRE(Default::egPercentMod.denormalizeInput(0.0f) == 0.0f); + REQUIRE(Default::egPercentMod.denormalizeInput(0.01f) == 1.0f); +} diff --git a/tests/RegionValuesT.cpp b/tests/RegionValuesT.cpp index 01c596e24..2e0a09887 100644 --- a/tests/RegionValuesT.cpp +++ b/tests/RegionValuesT.cpp @@ -256,7 +256,7 @@ TEST_CASE("[Values] Count") synth.dispatchMessage(client, 0, "/region2/count", "", nullptr); std::vector expected { "/region0/count,N : { }", - "/region1/count,h : { 2 }", + "/region1/count,i : { 2 }", "/region2/count,N : { }", }; REQUIRE(messageList == expected); @@ -421,7 +421,7 @@ TEST_CASE("[Values] Loop count") synth.dispatchMessage(client, 0, "/region2/loop_count", "", nullptr); std::vector expected { "/region0/loop_count,N : { }", - "/region1/loop_count,h : { 2 }", + "/region1/loop_count,i : { 2 }", "/region2/loop_count,N : { }", }; REQUIRE(messageList == expected); @@ -1158,9 +1158,9 @@ TEST_CASE("[Values] Sequence length") // TODO: activate for the new region parser ; ignore the second value // synth.dispatchMessage(client, 0, "/region3/seq_length", "", nullptr); std::vector expected { - "/region0/seq_length,h : { 1 }", - "/region1/seq_length,h : { 12 }", - "/region2/seq_length,h : { 1 }", + "/region0/seq_length,i : { 1 }", + "/region1/seq_length,i : { 12 }", + "/region2/seq_length,i : { 1 }", // TODO: activate for the new region parser ; ignore the second value // "/region3/seq_length,f : { 12 }", }; @@ -1186,9 +1186,9 @@ TEST_CASE("[Values] Sequence position") // TODO: activate for the new region parser ; ignore the second value // synth.dispatchMessage(client, 0, "/region3/seq_position", "", nullptr); std::vector expected { - "/region0/seq_position,h : { 1 }", - "/region1/seq_position,h : { 12 }", - "/region2/seq_position,h : { 1 }", + "/region0/seq_position,i : { 1 }", + "/region1/seq_position,i : { 12 }", + "/region2/seq_position,i : { 1 }", // TODO: activate for the new region parser ; ignore the second value // "/region3/seq_position,f : { 12 }", }; @@ -2133,9 +2133,9 @@ TEST_CASE("[Values] Pitch Keytrack") synth.dispatchMessage(client, 0, "/region1/pitch_keytrack", "", nullptr); synth.dispatchMessage(client, 0, "/region2/pitch_keytrack", "", nullptr); std::vector expected { - "/region0/pitch_keytrack,i : { 100 }", - "/region1/pitch_keytrack,i : { 1000 }", - "/region2/pitch_keytrack,i : { -100 }", + "/region0/pitch_keytrack,f : { 100 }", + "/region1/pitch_keytrack,f : { 1000 }", + "/region2/pitch_keytrack,f : { -100 }", }; REQUIRE(messageList == expected); } @@ -2158,9 +2158,9 @@ TEST_CASE("[Values] Pitch Veltrack") 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 }", + "/region0/pitch_veltrack,f : { 0 }", + "/region1/pitch_veltrack,f : { 10 }", + "/region2/pitch_veltrack,f : { -132 }", }; REQUIRE(messageList == expected); } @@ -2233,11 +2233,11 @@ TEST_CASE("[Values] Transpose") synth.dispatchMessage(client, 0, "/region3/transpose", "", nullptr); synth.dispatchMessage(client, 0, "/region4/transpose", "", nullptr); std::vector expected { - "/region0/transpose,i : { 0 }", - "/region1/transpose,i : { 10 }", - "/region2/transpose,i : { -4 }", - "/region3/transpose,i : { -400 }", - "/region4/transpose,i : { 400 }", + "/region0/transpose,f : { 0 }", + "/region1/transpose,f : { 10 }", + "/region2/transpose,f : { -4 }", + "/region3/transpose,f : { -400 }", + "/region4/transpose,f : { 400 }", }; REQUIRE(messageList == expected); } @@ -2891,7 +2891,7 @@ TEST_CASE("[Values] Support floating point for int values") synth.dispatchMessage(client, 0, "/region1/pitch_keytrack", "", nullptr); std::vector expected { "/region0/offset,h : { 1042 }", - "/region1/pitch_keytrack,i : { -2 }", + "/region1/pitch_keytrack,f : { -2.1 }", }; REQUIRE(messageList == expected); } @@ -3001,7 +3001,15 @@ TEST_CASE("[Values] fileg CC") synth.dispatchMessage(client, 0, "/region0/fileg_release_cc5", "", nullptr); synth.dispatchMessage(client, 0, "/region0/fileg_start_cc6", "", nullptr); synth.dispatchMessage(client, 0, "/region0/fileg_sustain_cc7", "", nullptr); - std::vector expected { }; + std::vector expected { + "/region0/fileg_attack_cc1,N : { }", + "/region0/fileg_delay_cc2,N : { }", + "/region0/fileg_decay_cc3,N : { }", + "/region0/fileg_hold_cc4,N : { }", + "/region0/fileg_release_cc5,N : { }", + "/region0/fileg_start_cc6,N : { }", + "/region0/fileg_sustain_cc7,N : { }", + }; REQUIRE(messageList == expected); } @@ -3079,7 +3087,15 @@ TEST_CASE("[Values] pitcheg CC") synth.dispatchMessage(client, 0, "/region0/pitcheg_release_cc5", "", nullptr); synth.dispatchMessage(client, 0, "/region0/pitcheg_start_cc6", "", nullptr); synth.dispatchMessage(client, 0, "/region0/pitcheg_sustain_cc7", "", nullptr); - std::vector expected { }; + std::vector expected { + "/region0/pitcheg_attack_cc1,N : { }", + "/region0/pitcheg_delay_cc2,N : { }", + "/region0/pitcheg_decay_cc3,N : { }", + "/region0/pitcheg_hold_cc4,N : { }", + "/region0/pitcheg_release_cc5,N : { }", + "/region0/pitcheg_start_cc6,N : { }", + "/region0/pitcheg_sustain_cc7,N : { }", + }; REQUIRE(messageList == expected); } @@ -3158,13 +3174,13 @@ TEST_CASE("[Values] ampeg curve CC") synth.dispatchMessage(client, 0, "/region0/ampeg_start_curvecc6", "", nullptr); synth.dispatchMessage(client, 0, "/region0/ampeg_sustain_curvecc7", "", nullptr); std::vector expected { - "/region0/ampeg_attack_curvecc1,f : { 0 }", - "/region0/ampeg_delay_curvecc2,f : { 0 }", - "/region0/ampeg_decay_curvecc3,f : { 0 }", - "/region0/ampeg_hold_curvecc4,f : { 0 }", - "/region0/ampeg_release_curvecc5,f : { 0 }", - "/region0/ampeg_start_curvecc6,f : { 0 }", - "/region0/ampeg_sustain_curvecc7,f : { 0 }", + "/region0/ampeg_attack_curvecc1,i : { 0 }", + "/region0/ampeg_delay_curvecc2,i : { 0 }", + "/region0/ampeg_decay_curvecc3,i : { 0 }", + "/region0/ampeg_hold_curvecc4,i : { 0 }", + "/region0/ampeg_release_curvecc5,i : { 0 }", + "/region0/ampeg_start_curvecc6,i : { 0 }", + "/region0/ampeg_sustain_curvecc7,i : { 0 }", }; REQUIRE(messageList == expected); } @@ -3185,13 +3201,13 @@ TEST_CASE("[Values] ampeg curve CC") synth.dispatchMessage(client, 0, "/region0/ampeg_start_curvecc6", "", nullptr); synth.dispatchMessage(client, 0, "/region0/ampeg_sustain_curvecc7", "", nullptr); std::vector expected { - "/region0/ampeg_attack_curvecc1,f : { 1 }", - "/region0/ampeg_delay_curvecc2,f : { 2 }", - "/region0/ampeg_decay_curvecc3,f : { 3 }", - "/region0/ampeg_hold_curvecc4,f : { 4 }", - "/region0/ampeg_release_curvecc5,f : { 5 }", - "/region0/ampeg_start_curvecc6,f : { 6 }", - "/region0/ampeg_sustain_curvecc7,f : { 7 }", + "/region0/ampeg_attack_curvecc1,i : { 1 }", + "/region0/ampeg_delay_curvecc2,i : { 2 }", + "/region0/ampeg_decay_curvecc3,i : { 3 }", + "/region0/ampeg_hold_curvecc4,i : { 4 }", + "/region0/ampeg_release_curvecc5,i : { 5 }", + "/region0/ampeg_start_curvecc6,i : { 6 }", + "/region0/ampeg_sustain_curvecc7,i : { 7 }", }; REQUIRE(messageList == expected); } @@ -3217,7 +3233,15 @@ TEST_CASE("[Values] fileg curve CC") synth.dispatchMessage(client, 0, "/region0/fileg_release_curvecc5", "", nullptr); synth.dispatchMessage(client, 0, "/region0/fileg_start_curvecc6", "", nullptr); synth.dispatchMessage(client, 0, "/region0/fileg_sustain_curvecc7", "", nullptr); - std::vector expected { }; + std::vector expected { + "/region0/fileg_attack_curvecc1,N : { }", + "/region0/fileg_delay_curvecc2,N : { }", + "/region0/fileg_decay_curvecc3,N : { }", + "/region0/fileg_hold_curvecc4,N : { }", + "/region0/fileg_release_curvecc5,N : { }", + "/region0/fileg_start_curvecc6,N : { }", + "/region0/fileg_sustain_curvecc7,N : { }", + }; REQUIRE(messageList == expected); } @@ -3237,13 +3261,13 @@ TEST_CASE("[Values] fileg curve CC") synth.dispatchMessage(client, 0, "/region0/fileg_start_curvecc6", "", nullptr); synth.dispatchMessage(client, 0, "/region0/fileg_sustain_curvecc7", "", nullptr); std::vector expected { - "/region0/fileg_attack_curvecc1,f : { 1 }", - "/region0/fileg_delay_curvecc2,f : { 2 }", - "/region0/fileg_decay_curvecc3,f : { 3 }", - "/region0/fileg_hold_curvecc4,f : { 4 }", - "/region0/fileg_release_curvecc5,f : { 5 }", - "/region0/fileg_start_curvecc6,f : { 6 }", - "/region0/fileg_sustain_curvecc7,f : { 7 }", + "/region0/fileg_attack_curvecc1,i : { 1 }", + "/region0/fileg_delay_curvecc2,i : { 2 }", + "/region0/fileg_decay_curvecc3,i : { 3 }", + "/region0/fileg_hold_curvecc4,i : { 4 }", + "/region0/fileg_release_curvecc5,i : { 5 }", + "/region0/fileg_start_curvecc6,i : { 6 }", + "/region0/fileg_sustain_curvecc7,i : { 7 }", }; REQUIRE(messageList == expected); } @@ -3269,7 +3293,15 @@ TEST_CASE("[Values] pitcheg curve CC") synth.dispatchMessage(client, 0, "/region0/pitcheg_release_curvecc5", "", nullptr); synth.dispatchMessage(client, 0, "/region0/pitcheg_start_curvecc6", "", nullptr); synth.dispatchMessage(client, 0, "/region0/pitcheg_sustain_curvecc7", "", nullptr); - std::vector expected { }; + std::vector expected { + "/region0/pitcheg_attack_curvecc1,N : { }", + "/region0/pitcheg_delay_curvecc2,N : { }", + "/region0/pitcheg_decay_curvecc3,N : { }", + "/region0/pitcheg_hold_curvecc4,N : { }", + "/region0/pitcheg_release_curvecc5,N : { }", + "/region0/pitcheg_start_curvecc6,N : { }", + "/region0/pitcheg_sustain_curvecc7,N : { }", + }; REQUIRE(messageList == expected); } @@ -3289,17 +3321,16 @@ TEST_CASE("[Values] pitcheg curve CC") synth.dispatchMessage(client, 0, "/region0/pitcheg_start_curvecc6", "", nullptr); synth.dispatchMessage(client, 0, "/region0/pitcheg_sustain_curvecc7", "", nullptr); std::vector expected { - "/region0/pitcheg_attack_curvecc1,f : { 1 }", - "/region0/pitcheg_delay_curvecc2,f : { 2 }", - "/region0/pitcheg_decay_curvecc3,f : { 3 }", - "/region0/pitcheg_hold_curvecc4,f : { 4 }", - "/region0/pitcheg_release_curvecc5,f : { 5 }", - "/region0/pitcheg_start_curvecc6,f : { 6 }", - "/region0/pitcheg_sustain_curvecc7,f : { 7 }", + "/region0/pitcheg_attack_curvecc1,i : { 1 }", + "/region0/pitcheg_delay_curvecc2,i : { 2 }", + "/region0/pitcheg_decay_curvecc3,i : { 3 }", + "/region0/pitcheg_hold_curvecc4,i : { 4 }", + "/region0/pitcheg_release_curvecc5,i : { 5 }", + "/region0/pitcheg_start_curvecc6,i : { 6 }", + "/region0/pitcheg_sustain_curvecc7,i : { 7 }", }; REQUIRE(messageList == expected); } - } TEST_CASE("[Values] Filter stacking and cutoffs") @@ -3358,8 +3389,8 @@ TEST_CASE("[Values] Filter stacking and cutoffs") "/region1/filter0/gain,f : { 0 }", "/region1/filter0/resonance,f : { 0 }", "/region1/filter0/keycenter,i : { 60 }", - "/region1/filter0/keytrack,i : { 0 }", - "/region1/filter0/veltrack,i : { 0 }", + "/region1/filter0/keytrack,f : { 0 }", + "/region1/filter0/veltrack,f : { 0 }", "/region1/filter0/type,s : { lpf_2p }", // No second filter }; @@ -3388,15 +3419,15 @@ TEST_CASE("[Values] Filter stacking and cutoffs") "/region2/filter0/gain,f : { 0 }", "/region2/filter0/resonance,f : { 0 }", "/region2/filter0/keycenter,i : { 60 }", - "/region2/filter0/keytrack,i : { 0 }", - "/region2/filter0/veltrack,i : { 0 }", + "/region2/filter0/keytrack,f : { 0 }", + "/region2/filter0/veltrack,f : { 0 }", "/region2/filter0/type,s : { lpf_2p }", "/region2/filter1/cutoff,f : { 500 }", "/region2/filter1/gain,f : { 0 }", "/region2/filter1/resonance,f : { 0 }", "/region2/filter1/keycenter,i : { 60 }", - "/region2/filter1/keytrack,i : { 0 }", - "/region2/filter1/veltrack,i : { 0 }", + "/region2/filter1/keytrack,f : { 0 }", + "/region2/filter1/veltrack,f : { 0 }", "/region2/filter1/type,s : { lpf_2p }", // No second filter }; @@ -3429,12 +3460,16 @@ TEST_CASE("[Values] Cutoff modifiers") synth.dispatchMessage(client, 0, "/region1/filter1/cutoff_smoothcc3", "", nullptr); synth.dispatchMessage(client, 0, "/region1/filter1/cutoff_curvecc3", "", nullptr); std::vector expected { + "/region0/filter0/cutoff_cc1,N : { }", + "/region0/filter0/cutoff_stepcc1,N : { }", + "/region0/filter0/cutoff_smoothcc1,N : { }", + "/region0/filter0/cutoff_curvecc1,N : { }", "/region0/filter0/cutoff_cc2,f : { 1000 }", - "/region0/filter0/cutoff_stepcc2,i : { 10 }", + "/region0/filter0/cutoff_stepcc2,f : { 10 }", "/region0/filter0/cutoff_smoothcc2,i : { 2 }", "/region0/filter0/cutoff_curvecc2,i : { 4 }", "/region1/filter1/cutoff_cc3,f : { 100 }", - "/region1/filter1/cutoff_stepcc3,i : { 1 }", + "/region1/filter1/cutoff_stepcc3,f : { 1 }", "/region1/filter1/cutoff_smoothcc3,i : { 20 }", "/region1/filter1/cutoff_curvecc3,i : { 3 }", }; @@ -3551,9 +3586,9 @@ TEST_CASE("[Values] Filter dispatching") "/region0/filter2/cutoff,f : { 50 }", "/region0/filter1/resonance,f : { 3 }", "/region0/filter1/gain,f : { -5 }", - "/region0/filter2/keytrack,i : { 100 }", + "/region0/filter2/keytrack,f : { 100 }", "/region0/filter0/gain,f : { -5 }", - "/region0/filter1/veltrack,i : { -100 }", + "/region0/filter1/veltrack,f : { -100 }", "/region0/filter3/veltrack_cc7,f : { -100 }", "/region0/filter4/veltrack_curvecc2,i : { 2 }", }; @@ -3870,8 +3905,8 @@ TEST_CASE("[Values] Dynamic EGs") synth.dispatchMessage(client, 0, "/region1/fileg_dynamic", "", nullptr); std::vector expected { "/region0/ampeg_dynamic,F : { }", - "/region0/pitcheg_dynamic,F : { }", - "/region0/fileg_dynamic,F : { }", + "/region0/pitcheg_dynamic,N : { }", + "/region0/fileg_dynamic,N : { }", "/region1/ampeg_dynamic,T : { }", "/region1/pitcheg_dynamic,T : { }", "/region1/fileg_dynamic,T : { }", From df0806f1576c1a9eae529cf8fea9edb80506552c Mon Sep 17 00:00:00 2001 From: essej Date: Thu, 18 Jan 2024 21:38:49 +0100 Subject: [PATCH 37/65] Truncate MIDI values when denormalizing --- src/sfizz/Defaults.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sfizz/Defaults.h b/src/sfizz/Defaults.h index a3c1a8b62..87614b6cd 100644 --- a/src/sfizz/Defaults.h +++ b/src/sfizz/Defaults.h @@ -150,7 +150,7 @@ struct OpcodeSpec else if (flags & kNormalizePercent) return static_cast(input * U(100)); else if (flags & kNormalizeMidi) - return static_cast(input * U(127)); + return static_cast(std::trunc(input * U(127))); else if (flags & kNormalizeBend) return static_cast(input * U(8191)); else if (flags & kDb2Mag) From ad9c857513f5aeea88ee5793b2fff8a203f94ec0 Mon Sep 17 00:00:00 2001 From: Paul Fd Date: Thu, 18 Jan 2024 23:22:10 +0100 Subject: [PATCH 38/65] Fix a fallthrough? --- src/sfizz/Synth.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sfizz/Synth.cpp b/src/sfizz/Synth.cpp index a484cb0c4..162a0bc74 100644 --- a/src/sfizz/Synth.cpp +++ b/src/sfizz/Synth.cpp @@ -442,7 +442,7 @@ void Synth::Impl::handleControlOpcodes(const std::vector& members) { FilePool& filePool = resources_.getFilePool(); filePool.setRamLoading(member.read(Default::ramBased)); - } + } break; case hash("hint_stealing"): switch(hash(member.value)) { case hash("first"): From 3d7bcb49681bf47fbaa34c2bb27bfdf117c63b4e Mon Sep 17 00:00:00 2001 From: Paul Fd Date: Thu, 18 Jan 2024 23:22:23 +0100 Subject: [PATCH 39/65] Add setters --- .gitmodules | 3 + cmake/SfizzConfig.cmake | 2 +- cmake/SfizzDeps.cmake | 5 + common.mk | 10 +- external/abseil-cpp | 2 +- external/invoke.hpp | 1 + src/CMakeLists.txt | 2 +- src/sfizz/Defaults.cpp | 1 + src/sfizz/Defaults.h | 1 + src/sfizz/Region.cpp | 31 +- src/sfizz/Region.h | 4 +- src/sfizz/SfzHelpers.h | 25 +- src/sfizz/Synth.cpp | 1 + src/sfizz/SynthMessaging.cpp | 886 ++--- src/sfizz/SynthMessagingHelper.hpp | 708 ++++ src/sfizz/modulations/ModMatrix.cpp | 1 + tests/ParsingT.cpp | 1 + tests/RegionValuesSetT.cpp | 604 +++- tests/RegionValuesT.cpp | 4780 +++++++++------------------ tests/SynthDiscussion.h | 187 ++ 20 files changed, 3255 insertions(+), 4000 deletions(-) create mode 160000 external/invoke.hpp create mode 100644 src/sfizz/SynthMessagingHelper.hpp create mode 100644 tests/SynthDiscussion.h diff --git a/.gitmodules b/.gitmodules index c3f223211..a2184e24a 100644 --- a/.gitmodules +++ b/.gitmodules @@ -16,3 +16,6 @@ [submodule "external/st_audiofile/thirdparty/wavpack"] path = external/st_audiofile/thirdparty/wavpack url = https://github.com/dbry/WavPack.git +[submodule "external/invoke.hpp"] + path = external/invoke.hpp + url = https://github.com/BlackMATov/invoke.hpp.git diff --git a/cmake/SfizzConfig.cmake b/cmake/SfizzConfig.cmake index ff0ab5ebb..1b43ed78b 100644 --- a/cmake/SfizzConfig.cmake +++ b/cmake/SfizzConfig.cmake @@ -51,7 +51,7 @@ endif() # Set macOS compatibility level if(APPLE) - set(CMAKE_OSX_DEPLOYMENT_TARGET "10.9") + set(CMAKE_OSX_DEPLOYMENT_TARGET "10.13") endif() # If using C++17, check if aligned-new has runtime support on the platform; diff --git a/cmake/SfizzDeps.cmake b/cmake/SfizzDeps.cmake index daf3cdee6..94e5418b6 100644 --- a/cmake/SfizzDeps.cmake +++ b/cmake/SfizzDeps.cmake @@ -83,6 +83,11 @@ else() sfizz_add_vendor_abseil() endif() +# C++14 std::invoke +# add_subdirectory("external/invoke.hpp") +add_library(invoke_hpp INTERFACE) +target_include_directories(invoke_hpp INTERFACE "external/invoke.hpp/headers") + # The jsl utility library for C++ add_library(sfizz_jsl INTERFACE) add_library(sfizz::jsl ALIAS sfizz_jsl) diff --git a/common.mk b/common.mk index 27576171d..2475a9b90 100644 --- a/common.mk +++ b/common.mk @@ -243,6 +243,10 @@ SFIZZ_CXX_FLAGS += -I$(SFIZZ_DIR)/external/atomic_queue/include SFIZZ_CXX_FLAGS += -I$(SFIZZ_DIR)/external/filesystem/include +# invoke.hpp dependency + +SFIZZ_CXX_FLAGS += -I$(SFIZZ_DIR)/external/invoke.hpp/headers + ### Abseil dependency SFIZZ_C_FLAGS += -I$(SFIZZ_DIR)/external/abseil-cpp @@ -293,7 +297,11 @@ SFIZZ_SOURCES += \ external/abseil-cpp/absl/synchronization/blocking_counter.cc \ external/abseil-cpp/absl/synchronization/internal/create_thread_identity.cc \ external/abseil-cpp/absl/synchronization/internal/per_thread_sem.cc \ - external/abseil-cpp/absl/synchronization/internal/waiter.cc \ + external/abseil-cpp/absl/synchronization/internal/waiter_base.cc \ + external/abseil-cpp/absl/synchronization/internal/win32_waiter.cc \ + external/abseil-cpp/absl/synchronization/internal/stdcpp_waiter.cc \ + external/abseil-cpp/absl/synchronization/internal/sem_waiter.cc \ + external/abseil-cpp/absl/synchronization/internal/pthread_waiter.cc \ external/abseil-cpp/absl/synchronization/notification.cc \ external/abseil-cpp/absl/synchronization/mutex.cc # absl::graphcycles_internal diff --git a/external/abseil-cpp b/external/abseil-cpp index c2435f834..ad73c6dc1 160000 --- a/external/abseil-cpp +++ b/external/abseil-cpp @@ -1 +1 @@ -Subproject commit c2435f8342c2d0ed8101cb43adfd605fdc52dca2 +Subproject commit ad73c6dc1a253203c1c8b529cda18f2138d49df0 diff --git a/external/invoke.hpp b/external/invoke.hpp new file mode 160000 index 000000000..2c1eabc2e --- /dev/null +++ b/external/invoke.hpp @@ -0,0 +1 @@ +Subproject commit 2c1eabc2e20ab02961f95c704ff0c0818671ddd1 diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index b68e750c7..f7aab0f7d 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -295,7 +295,7 @@ target_sources(sfizz_internal PRIVATE ${SFIZZ_HEADERS} ${SFIZZ_SOURCES} ${FAUST_ target_include_directories(sfizz_internal PUBLIC "." "sfizz") target_link_libraries(sfizz_internal PUBLIC absl::strings absl::span sfizz::filesystem sfizz::atomic_queue sfizz::spin_mutex sfizz::bit_array sfizz::simde sfizz::hiir sfizz::jsl - PRIVATE sfizz::parser sfizz::messaging absl::flat_hash_map Threads::Threads st_audiofile sfizz::pugixml sfizz::spline sfizz::tunings sfizz::kissfft sfizz::cephes sfizz::cpuid sfizz::threadpool sfizz::atomic) + PRIVATE sfizz::parser sfizz::messaging absl::flat_hash_map Threads::Threads st_audiofile sfizz::pugixml sfizz::spline sfizz::tunings sfizz::kissfft sfizz::cephes sfizz::cpuid sfizz::threadpool sfizz::atomic invoke_hpp) if(SFIZZ_USE_SNDFILE) target_compile_definitions(sfizz_internal PUBLIC "SFIZZ_USE_SNDFILE=1") target_link_libraries(sfizz_internal PUBLIC st_audiofile) diff --git a/src/sfizz/Defaults.cpp b/src/sfizz/Defaults.cpp index 4382cd36a..acd774672 100644 --- a/src/sfizz/Defaults.cpp +++ b/src/sfizz/Defaults.cpp @@ -70,6 +70,7 @@ FloatSpec loBipolar { -1.0f, {-1.0f, 1.0f}, kPermissiveBounds }; FloatSpec hiBipolar { 1.0f, {-1.0f, 1.0f}, kPermissiveBounds }; UInt16Spec ccNumber { 0, {0, config::numCCs}, 0 }; UInt16Spec smoothCC { 0, {0, 100}, kPermissiveUpperBound }; +FloatSpec stepCC { 0.0f, {0.0f, 127.0f}, kPermissiveUpperBound }; UInt8Spec curveCC { 0, {0, 255}, 0 }; UInt8Spec sustainCC { 64, {0, 127}, 0 }; UInt8Spec sostenutoCC { 66, {0, 127}, 0 }; diff --git a/src/sfizz/Defaults.h b/src/sfizz/Defaults.h index 87614b6cd..b3fac82e1 100644 --- a/src/sfizz/Defaults.h +++ b/src/sfizz/Defaults.h @@ -228,6 +228,7 @@ namespace Default extern const OpcodeSpec hiPolyAftertouch; extern const OpcodeSpec ccNumber; extern const OpcodeSpec curveCC; + extern const OpcodeSpec stepCC; extern const OpcodeSpec smoothCC; extern const OpcodeSpec sustainCC; extern const OpcodeSpec sostenutoCC; diff --git a/src/sfizz/Region.cpp b/src/sfizz/Region.cpp index cd9510981..be799ae61 100644 --- a/src/sfizz/Region.cpp +++ b/src/sfizz/Region.cpp @@ -1126,11 +1126,11 @@ bool sfz::Region::parseEGOpcode(const Opcode& opcode, EGDescription& eg) eg.vel2sustain = opcode.read(Default::egPercentMod); break; case_any_eg("attack_oncc&"): // also attackcc& - if (!setModifier(eg.ccAttack, Default::egTimeMod)) + if (!setModifier(eg.ccAttack, Default::egTimeMod)) return false; break; case_any_eg("attack_curvecc&"): - if (!setCurve(eg.ccAttack)) + if (!setCurve(eg.ccAttack)) return false; break; case_any_eg("decay_oncc&"): // also decaycc& @@ -1138,47 +1138,47 @@ bool sfz::Region::parseEGOpcode(const Opcode& opcode, EGDescription& eg) return false; break; case_any_eg("decay_curvecc&"): - if (!setCurve(eg.ccDecay)) + if (!setCurve(eg.ccDecay)) return false; break; case_any_eg("delay_oncc&"): // also delaycc& - if (!setModifier(eg.ccDelay, Default::egTimeMod)) + if (!setModifier(eg.ccDelay, Default::egTimeMod)) return false; break; case_any_eg("delay_curvecc&"): - if (!setCurve(eg.ccDelay)) + if (!setCurve(eg.ccDelay)) return false; break; case_any_eg("hold_oncc&"): // also holdcc& - if (!setModifier(eg.ccHold, Default::egTimeMod)) + if (!setModifier(eg.ccHold, Default::egTimeMod)) return false; break; case_any_eg("hold_curvecc&"): // also attackcc& - if (!setCurve(eg.ccHold)) + if (!setCurve(eg.ccHold)) return false; break; case_any_eg("release_oncc&"): // also releasecc& - if (!setModifier(eg.ccRelease, Default::egTimeMod)) + if (!setModifier(eg.ccRelease, Default::egTimeMod)) return false; break; case_any_eg("release_curvecc&"): // also attackcc& - if (!setCurve(eg.ccRelease)) + if (!setCurve(eg.ccRelease)) return false; break; case_any_eg("start_oncc&"): // also startcc& - if (!setModifier(eg.ccStart, Default::egPercentMod)) + if (!setModifier(eg.ccStart, Default::egPercentMod)) return false; break; case_any_eg("start_curvecc&"): // also startcc& - if (!setCurve(eg.ccStart)) + if (!setCurve(eg.ccStart)) return false; break; case_any_eg("sustain_oncc&"): // also sustaincc& - if (!setModifier(eg.ccSustain, Default::egPercentMod)) + if (!setModifier(eg.ccSustain, Default::egPercentMod)) return false; break; case_any_eg("sustain_curvecc&"): // also attackcc& - if (!setCurve(eg.ccSustain)) + if (!setCurve(eg.ccSustain)) return false; break; @@ -1757,10 +1757,7 @@ bool sfz::Region::processGenericCc(const Opcode& opcode, OpcodeSpec spec, p.curve = opcode.read(Default::curveCC); break; case kOpcodeStepCcN: - { - const OpcodeSpec stepCC { 0.0f, {}, kPermissiveBounds }; - p.step = spec.normalizeInput(opcode.read(stepCC)); - } + p.step = spec.normalizeInput(opcode.read(Default::stepCC)); break; case kOpcodeSmoothCcN: p.smooth = opcode.read(Default::smoothCC); diff --git a/src/sfizz/Region.h b/src/sfizz/Region.h index de7cc37f1..b882c44ab 100644 --- a/src/sfizz/Region.h +++ b/src/sfizz/Region.h @@ -290,8 +290,8 @@ struct Region { VelocityOverride velocityOverride { Default::velocityOverride }; // sw_vel bool checkSustain { Default::checkSustain }; // sustain_sw bool checkSostenuto { Default::checkSostenuto }; // sostenuto_sw - uint16_t sustainCC { Default::sustainCC }; // sustain_cc - uint16_t sostenutoCC { Default::sostenutoCC }; // sustain_cc + uint8_t sustainCC { Default::sustainCC }; // sustain_cc + uint8_t sostenutoCC { Default::sostenutoCC }; // sostenuto_cc float sustainThreshold { Default::sustainThreshold }; // sustain_cc float sostenutoThreshold { Default::sostenutoThreshold }; // sustain_cc diff --git a/src/sfizz/SfzHelpers.h b/src/sfizz/SfzHelpers.h index d6bfc670d..b871c24ae 100644 --- a/src/sfizz/SfzHelpers.h +++ b/src/sfizz/SfzHelpers.h @@ -283,22 +283,21 @@ inline CXX14_CONSTEXPR bool ccModulationIsPerVoice(int cc) { } namespace literals { - inline float operator""_norm(unsigned long long int value) + inline float operator""_norm(unsigned long long int value) { - if (value > 127) - value = 127; - - return normalize7Bits(value); + return static_cast(value) / 127.0f; } - - inline float operator""_norm(long double value) + inline float operator""_norm(long double value) { - if (value < 0) - value = 0; - if (value > 127) - value = 127; - - return normalize7Bits(value); + return static_cast(value) / 127.0f; + } + inline float operator""_bend(unsigned long long int value) + { + return static_cast(value) / 8191.0f; + } + inline float operator""_bend(long double value) + { + return static_cast(value) / 8191.0f; } } diff --git a/src/sfizz/Synth.cpp b/src/sfizz/Synth.cpp index 162a0bc74..4ad783dca 100644 --- a/src/sfizz/Synth.cpp +++ b/src/sfizz/Synth.cpp @@ -33,6 +33,7 @@ #include "parser/Parser.h" #include #include +#include #include #include #include diff --git a/src/sfizz/SynthMessaging.cpp b/src/sfizz/SynthMessaging.cpp index 8b69c679e..a605420ce 100644 --- a/src/sfizz/SynthMessaging.cpp +++ b/src/sfizz/SynthMessaging.cpp @@ -4,554 +4,16 @@ // 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 "CCMap.h" -#include "Config.h" #include "Defaults.h" -#include "EQDescription.h" -#include "FileId.h" -#include "FilterDescription.h" -#include "FlexEGDescription.h" -#include "LFOCommon.h" -#include "LFODescription.h" -#include "Range.h" -#include "SfzFilter.h" -#include "SfzHelpers.h" -#include "SynthPrivate.h" -#include "FilePool.h" -#include "Curve.h" -#include "MidiState.h" -#include "SynthConfig.h" -#include "TriggerEvent.h" -#include "absl/strings/string_view.h" -#include "absl/types/optional.h" -#include "absl/types/span.h" -#include "modulations/ModId.h" -#include "modulations/ModKey.h" -#include "modulations/ModKeyHash.h" -#include "utility/StringViewHelpers.h" -#include -#include "utility/Size.h" -#include -#include -#include -#include - -// TODO: `ccModDepth` and `ccModParameters` are O(N), need better implementation +#include "Region.h" +#include "SynthMessagingHelper.hpp" namespace sfz { -static constexpr unsigned maxIndices = 8; - -static uint64_t hashMessagePath(const char* path, const char* sig) -{ - uint64_t h = Fnv1aBasis; - while (unsigned char c = *path++) { - if (!absl::ascii_isdigit(c)) - h = hashByte(c, h); - else { - h = hashByte('&', h); - while (absl::ascii_isdigit(*path)) - ++path; - } - } - h = hashByte(',', h); - while (unsigned char c = *sig++) - h = hashByte(c, h); - return h; -} - -class MessagingHelper -{ -public: - MessagingHelper(Client& client, int delay, const char* path, const char* sig, sfz::Synth::Impl& impl) - : client(client), impl(impl), delay(delay), path(path), sig(sig) - { - indices.reserve(maxIndices); - } - - enum class ModParam { Depth, Curve, Smooth, Step }; - - bool match(const char* pattern, const char* sig) - { - indices.clear(); - const char* path_ = this->path; - - while (const char *endp = strchr(pattern, '&')) { - if (indices.size() == maxIndices) - return false; - - size_t length = endp - pattern; - if (strncmp(pattern, path_, length) != 0) - return false; - pattern += length; - path_ += length; - - length = 0; - while (absl::ascii_isdigit(path_[length])) - ++length; - - indices.push_back(0); - if (!absl::SimpleAtoi(absl::string_view(path_, length), &indices.back())) - return false; - - pattern += 1; - path_ += length; - } - - return !strcmp(path_, pattern) && !strcmp(this->sig, sig); - } - - // These are the reply/reply2 overloads for the (almost) concrete types, that should - // translate to actual calls to the client.receive(...) method. - - void reply(const std::string& value) { client.receive<'s'>(delay, path, value.data()); } - void reply(const char* value) { client.receive<'s'>(delay, path, value); } - void reply(float value) { client.receive<'f'>(delay, path, value); } - void reply(absl::nullopt_t) { client.receive<'N'>(delay, path, {}); } - - void reply(bool value) - { - if (value) - client.receive<'T'>(delay, path, {}); - else - client.receive<'F'>(delay, path, {}); - } - - template::value>> - void reply(T value) - { - if (sizeof(value) <= 4) - client.receive<'i'>(delay, path, static_cast(value)); - else - client.receive<'h'>(delay, path, static_cast(value)); - } - template - void reply(const BitArray& array) - { - sfizz_blob_t blob { array.data(), static_cast(array.byte_size()) }; - client.receive<'b'>(delay, path, &blob); - } - - // Call reply but denormalizes the input if needed by the opcode spec - template - void reply(T value, OpcodeSpec spec) { reply(spec.denormalizeInput(value)); } - - // sfizz specific types - void reply(sfz::LFOWave wave) { reply(static_cast(wave)); } - void reply(sfz::SelfMask mode) { reply(mode == SelfMask::mask); } - void reply(sfz::LoopMode mode) - { - switch (mode) { - case LoopMode::no_loop: reply("no_loop"); break; - case LoopMode::loop_continuous: reply("loop_continuous"); break; - case LoopMode::loop_sustain: reply("loop_sustain"); break; - case LoopMode::one_shot: reply("one_shot"); break; - } - } - - void reply(sfz::CrossfadeCurve curve) - { - switch (curve) { - case CrossfadeCurve::gain: reply("gain"); break; - case CrossfadeCurve::power: reply("power"); break; - } - } - - void reply(sfz::Trigger mode) - { - switch (mode) { - case Trigger::attack: reply("attack"); break; - case Trigger::first: reply("first"); break; - case Trigger::legato: reply("legato"); break; - case Trigger::release: reply("release"); break; - case Trigger::release_key: reply("release_key"); break; - } - } - - void reply(sfz::VelocityOverride mode) - { - switch (mode) { - case VelocityOverride::current: reply("current"); break; - case VelocityOverride::previous: reply("previous"); break; - } - } - - void reply(sfz::OffMode mode) - { - switch (mode) { - case OffMode::fast: reply("fast"); break; - case OffMode::time: reply("time"); break; - case OffMode::normal: reply("normal"); break; - } - } - - void reply(sfz::FilterType type) - { - switch (type) { - case FilterType::kFilterLpf1p: reply("lpf_1p"); break; - case FilterType::kFilterHpf1p: reply("hpf_1p"); break; - case FilterType::kFilterLpf2p: reply("lpf_2p"); break; - case FilterType::kFilterHpf2p: reply("hpf_2p"); break; - case FilterType::kFilterBpf2p: reply("bpf_2p"); break; - case FilterType::kFilterBrf2p: reply("brf_2p"); break; - case FilterType::kFilterBpf1p: reply("bpf_1p"); break; - case FilterType::kFilterBrf1p: reply("brf_1p"); break; - case FilterType::kFilterApf1p: reply("apf_1p"); break; - case FilterType::kFilterLpf2pSv: reply("lpf_2p_sv"); break; - case FilterType::kFilterHpf2pSv: reply("hpf_2p_sv"); break; - case FilterType::kFilterBpf2pSv: reply("bpf_2p_sv"); break; - case FilterType::kFilterBrf2pSv: reply("brf_2p_sv"); break; - case FilterType::kFilterLpf4p: reply("lpf_4p"); break; - case FilterType::kFilterHpf4p: reply("hpf_4p"); break; - case FilterType::kFilterLpf6p: reply("lpf_6p"); break; - case FilterType::kFilterHpf6p: reply("hpf_6p"); break; - case FilterType::kFilterPink: reply("pink"); break; - case FilterType::kFilterLsh: reply("lsh"); break; - case FilterType::kFilterHsh: reply("hsh"); break; - case FilterType::kFilterPeq: reply("peq"); break; - case FilterType::kFilterBpf4p: reply("bpf_4p"); break; - case FilterType::kFilterBpf6p: reply("bpf_6p"); break; - case FilterType::kFilterNone: reply("none"); break; - } - } - - void reply(sfz::EqType type) - { - switch(type) { - case EqType::kEqNone: reply("none"); break; - case EqType::kEqPeak: reply("peak"); break; - case EqType::kEqLshelf: reply("lshelf"); break; - case EqType::kEqHshelf: reply("hshelf"); break; - } - } - - void reply(sfz::TriggerEventType type) - { - switch(type) { - case TriggerEventType::NoteOff: reply("note_off"); break; - case TriggerEventType::NoteOn: reply("note_on"); break; - case TriggerEventType::CC: reply("cc"); break; - } - } - - // reply2 for pairs (Usually only for float/int ranges) - template::value>> - void reply2(T value1, T value2) - { - sfizz_arg_t args[2]; - if (sizeof(value1) <= 4) { - args[0].i = value1; - args[1].i = value2; - client.receive(delay, path, "ii", args); - } else { - args[0].h = value1; - args[1].h = value2; - client.receive(delay, path, "hh", args); - } - } - - void reply2(float value1, float value2) - { - sfizz_arg_t args[2]; - args[0].f = value1; - args[1].f = value2; - client.receive(delay, path, "ff", args); - } - - // Now we have some templated reply overloads that decay into "concrete" reply calls - // but add some logic (e.g. an optional can either reply the value, or send 'N' for - // null) - template - void reply(absl::optional value, Args... args) - { - if (!value) { - client.receive<'N'>(delay, path, {}); - return; - } - - reply(*value, args...); - } - - - template - void reply(T* value, Args... args) - { - if (!value) { - client.receive<'N'>(delay, path, {}); - return; - } - - reply(*value, args...); - } - - template - void reply(std::shared_ptr value) { reply(value.get()); } - - template - void reply(sfz::UncheckedRange range) { reply2(range.getStart(), range.getEnd()); } - - template - void reply(absl::optional value, T def) { reply(value.value_or(def)); } - - template - void reply(const sfz::ModifierCurvePair& modCurve, ModParam which, Args...args) - { - if (auto region = getRegion()) { - switch (which) { - case ModParam::Curve: reply(modCurve.curve); break; - default: reply(modCurve.modifier, args...); break; - } - } - } - - template - void reply(const sfz::ModKey::Parameters& params, ModParam which, Args...args) - { - if (auto region = getRegion()) { - switch (which) { - case ModParam::Depth: break; - case ModParam::Curve: reply(params.curve); break; - case ModParam::Smooth: reply(params.smooth); break; - case ModParam::Step: reply(params.step, args...); break; - } - } - } - - template - void reply(CCMap map, Args...args) { reply(map, true, args...); } - - template - void reply(CCMap map, bool useDefault, Args...args) - { - if (useDefault) - reply(map.getWithDefault(indices.back()), args...); - else if (map.contains(indices.back())) - reply(map.get(indices.back()), args...); - else - client.receive<'N'>(delay, path, {}); - } - - // Below are all methods that will fetch various data structure elements (regions, - // egs, ...), check that they exist, and call an actual "concrete" reply - // implementation. They use pointer-to-member variables, which allow the compiler to - // dispatch to the correct logic. For example, if the pointer-to-member is - // `sfz::Region::*`, we aim at sending the value of a member of the sfz::Region - // struct. The first thing is to read the index of the region and fetch it from the - // sfizz data structure using `getRegion(...)`, and then apply the pointer-to-member - // to this particular region to send the value. - - // Adding new dispatching overloads seemed simple enough to this point, although I - // haven't found a nice way to the same with pointer-to-member function. - // TODO: maybe a semi-templated overload that check that if the member is a function, - // TODO: and call it with `args...` could be an approach to try. - - template - void reply(T sfz::Region::* member, Args... args) - { - if (auto region = getRegion()) - reply((*region).*member, args...); - } - - template - void reply(const sfz::EGDescription& eg, T sfz::EGDescription::* member, Args...args) - { - reply(eg.*member, args...); - } - - template - void reply(T sfz::FilterDescription::* member, Args... args) - { - if (auto region = getRegion()) - if (auto filter = getFilter(*region)) - reply((*filter).*member, args...); - } - - template - void reply(T sfz::EQDescription::* member, Args... args) - { - if (auto region = getRegion()) - if (auto eq = getEQ(*region)) - reply((*eq).*member, args...); - } - - template - void reply(T sfz::LFODescription::* member, Args... args) - { - if (auto region = getRegion()) - if (auto lfo = getLFO(*region)) - reply((*lfo).*member, args...); - } - - template - void reply(T sfz::LFODescription::Sub::* member, Args... args) - { - if (auto region = getRegion()) - if (auto lfo = getLFO(*region)) - if (!lfo->sub.empty()) - reply((lfo->sub[0]).*member, args...); - } - - template - void reply(T sfz::FlexEGPoint::* member, Args... args) - { - if (auto region = getRegion()) - if (auto eg = getEG(*region)) - if (auto point = getEGPoint(*eg)) - reply((*point).*member, args...); - } - - template - void reply(T sfz::TriggerEvent::* member, Args... args) - { - if (auto voice = getVoice()) - reply((*voice).getTriggerEvent().*member, args...); - } - - template - void reply(T sfz::Voice::* member, Args... args) - { - if (auto voice = getVoice()) { - reply((*voice.*member)(args...)); // Voice only has callables - } - } - - template - void reply(ModId id, ModParam param, Args...args) - { - if (auto region = getRegion()) { - int cc = static_cast (indices.back()); - switch (id){ - case ModId::FilCutoff: - case ModId::FilGain: - switch (param) { - case ModParam::Depth: reply(region->ccModDepth(cc, id, indices[1]), args...); break; - default: reply(region->ccModParameters(cc, id, indices[1]), param, args...); break; - } break; - default: - switch (param) { - case ModParam::Depth: reply(region->ccModDepth(cc, id), args...); break; - default: reply(region->ccModParameters(cc, id), param, args...); break; - } - } - } - } - - // Validate and fetch elements from the sfizz data structures. By default, we kind of - // assume that regions/voices will be the first index, CCs will be the last, and - // EQ/Filter/.. will be in-between. - sfz::Region* getRegion(absl::optional index = {}) - { - const auto idx = index.value_or(indices[0]); - if (idx >= impl.layers_.size()) - return {}; - - Layer& layer = *impl.layers_[idx]; - return &layer.getRegion(); - } - - sfz::FilterDescription* getFilter(sfz::Region& region, absl::optional index = {}) - { - const auto idx = index.value_or(indices[1]); - if (region.filters.size() <= idx) - return {}; - - return ®ion.filters[idx]; - } - - sfz::EQDescription* getEQ(sfz::Region& region, absl::optional index = {}) - { - const auto idx = index.value_or(indices[1]); - if (region.equalizers.size() <= idx) - return {}; - - return ®ion.equalizers[idx]; - } - - sfz::LFODescription* getLFO(sfz::Region& region, absl::optional index = {}) - { - const auto idx = index.value_or(indices[1]); - if (region.lfos.size() <= idx) - return {}; - - return ®ion.lfos[idx]; - } - - sfz::LFODescription::Sub* getLFOSub(sfz::LFODescription& lfo, absl::optional index = {}) - { - const auto idx = index.value_or(indices[2]); - if (lfo.sub.size() <= idx) - return {}; - - return &lfo.sub[idx]; - } - - sfz::FlexEGDescription* getEG(sfz::Region& region, absl::optional index = {}) - { - const auto idx = index.value_or(indices[1]); - if (region.flexEGs.size() <= idx) - return {}; - - return ®ion.flexEGs[idx]; - } - - sfz::FlexEGPoint* getEGPoint(sfz::FlexEGDescription& desc, absl::optional index = {}) - { - const auto idx = index.value_or(indices[2]) + 1; - if (desc.points.size() <= idx) - return {}; - - return &desc.points[idx]; - } - - sfz::Voice* getVoice(absl::optional index = {}) - { - const auto idx = index.value_or(indices[0]); - if (static_cast(idx) >= impl.numVoices_) - return {}; - - auto& voice = impl.voiceManager_[idx]; - if (voice.isFree()) - return {}; - - return &voice; - } - - // Helpers to get and check the values of the indices - template - absl::optional index(int i) - { - if (i <= ssize(indices)) - return static_cast(indices[i]); - - return absl::nullopt; - } - - absl::optional sindex(int i) { return index(i); } - - absl::optional checkCC(int i = 0) - { - auto cc = index(i); - return cc <= config::numCCs ? cc : absl::nullopt; - } - - absl::optional checkNote(int i = 0) - { - auto note = sindex(i); - return note <= 127 ? note : absl::nullopt; - } - -private: - Client& client; - std::vector indices; - sfz::Synth::Impl& impl; - int delay; - const char* path; - const char* sig; -}; void sfz::Synth::dispatchMessage(Client& client, int delay, const char* path, const char* sig, const sfizz_arg_t* args) { Impl& impl = *impl_; - MessagingHelper m {client, delay, path, sig, impl}; + MessagingHelper m {client, delay, path, sig, args, impl}; using ModParam = MessagingHelper::ModParam; switch (hashMessagePath(path, sig)) { @@ -567,6 +29,18 @@ void sfz::Synth::dispatchMessage(Client& client, int delay, const char* path, co MATCH("/note_offset", "") { m.reply(impl.noteOffset_); } break; MATCH("/num_outputs", "") { m.reply(impl.numOutputs_); } break; MATCH("/num_active_voices", "") { m.reply(uint32_t(impl.voiceManager_.getNumActiveVoices())); } break; + MATCH("/sustain_cancels_release", "") { m.reply(&SynthConfig::sustainCancelsRelease); } break; + MATCH("/sample_quality", "") { m.reply(&SynthConfig::liveSampleQuality); } break; + MATCH("/sustain_cancels_release", "s") { m.set(&SynthConfig::sustainCancelsRelease, Default::sustainCancelsRelease); } break; + MATCH("/sustain_cancels_release", "T") { m.set(&SynthConfig::sustainCancelsRelease, Default::sustainCancelsRelease); } break; + MATCH("/sustain_cancels_release", "F") { m.set(&SynthConfig::sustainCancelsRelease, Default::sustainCancelsRelease); } break; + MATCH("/sample_quality", "i") { m.set(&SynthConfig::liveSampleQuality, Default::sampleQuality); } break; + MATCH("/oscillator_quality", "") { m.reply(&SynthConfig::liveOscillatorQuality); } break; + MATCH("/oscillator_quality", "i") { m.set(&SynthConfig::liveOscillatorQuality, Default::oscillatorQuality); } break; + MATCH("/freewheeling_sample_quality", "") { m.reply(&SynthConfig::freeWheelingSampleQuality); } break; + MATCH("/freewheeling_sample_quality", "i") { m.set(&SynthConfig::freeWheelingSampleQuality, Default::sampleQuality); } break; + MATCH("/freewheeling_oscillator_quality", "") { m.reply(&SynthConfig::freeWheelingOscillatorQuality); } break; + MATCH("/freewheeling_oscillator_quality", "i") { m.set(&SynthConfig::freeWheelingOscillatorQuality, Default::oscillatorQuality); } break; //---------------------------------------------------------------------- MATCH("/key/slots", "") { m.reply(impl.keySlots_); } break; MATCH("/key&/label", "") { if (auto k = m.sindex(0)) m.reply(impl.getKeyLabel(*k)); } break; @@ -594,125 +68,220 @@ void sfz::Synth::dispatchMessage(Client& client, int delay, const char* path, co MATCH("/mem/buffers", "") { m.reply(BufferCounter::counter().getTotalBytes()); } break; //---------------------------------------------------------------------- MATCH("/region&/delay", "") { m.reply(&Region::delay); } break; + MATCH("/region&/delay", "f") { m.set(&Region::delay, Default::delay); } break; MATCH("/region&/delay_random", "") { m.reply(&Region::delayRandom); } break; + MATCH("/region&/delay_random", "f") { m.set(&Region::delayRandom, Default::delayRandom); } break; MATCH("/region&/sample", "") { if (auto region = m.getRegion()) { m.reply(region->sampleId->filename()); } } break; MATCH("/region&/direction", "") { if (auto region = m.getRegion()) { m.reply(region->sampleId->isReverse() ? "reverse" : "forward"); } } break; MATCH("/region&/delay_cc&", "") { m.reply(&Region::delayCC); } break; + MATCH("/region&/delay_cc&", "f") { m.set(&Region::delayCC, Default::delayMod); } break; MATCH("/region&/offset", "") { m.reply(&Region::offset); } break; + MATCH("/region&/offset", "h") { m.set(&Region::offset, Default::offset); } break; MATCH("/region&/offset_random", "") { m.reply(&Region::offsetRandom); } break; + MATCH("/region&/offset_random", "h") { m.set(&Region::offsetRandom, Default::offsetRandom); } break; MATCH("/region&/offset_cc&", "") { m.reply(&Region::offsetCC); } break; + MATCH("/region&/offset_cc&", "h") { m.set(&Region::offsetCC, Default::offsetMod); } break; MATCH("/region&/end", "") { m.reply(&Region::sampleEnd); } break; + MATCH("/region&/end", "h") { m.set(&Region::sampleEnd, Default::sampleEnd); } break; MATCH("/region&/end_cc&", "") { m.reply(&Region::endCC); } break; + MATCH("/region&/end_cc&", "h") { m.set(&Region::endCC, Default::sampleEndMod); } break; MATCH("/region&/enabled", "") { if (auto region = m.getRegion()) { m.reply(!region->disabled()); } } break; MATCH("/region&/trigger_on_note", "") { m.reply(&Region::triggerOnNote); } break; MATCH("/region&/trigger_on_cc", "") { m.reply(&Region::triggerOnCC); } break; MATCH("/region&/use_timer_range", "") { m.reply(&Region::useTimerRange); } break; MATCH("/region&/count", "") { m.reply(&Region::sampleCount); } break; + MATCH("/region&/count", "i") { m.set(&Region::sampleCount, Default::sampleCount); } break; + MATCH("/region&/count", "N") { m.set(&Region::sampleCount, Default::sampleCount); } break; MATCH("/region&/loop_range", "") { m.reply(&Region::loopRange); } break; + MATCH("/region&/loop_range", "hh") { m.set(&Region::loopRange, Default::loopStart, Default::loopEnd); } break; MATCH("/region&/loop_start_cc&", "") { m.reply(&Region::loopStartCC); } break; + MATCH("/region&/loop_start_cc&", "h") { m.set(&Region::loopStartCC, Default::loopStart); } break; MATCH("/region&/loop_end_cc&", "") { m.reply(&Region::loopEndCC); } break; + MATCH("/region&/loop_end_cc&", "h") { m.set(&Region::loopEndCC, Default::loopEnd); } break; MATCH("/region&/loop_mode", "") { m.reply(&Region::loopMode, LoopMode::no_loop); } break; + MATCH("/region&/loop_mode", "s") { m.set(&Region::loopMode, Default::loopMode); } break; MATCH("/region&/loop_crossfade", "") { m.reply(&Region::loopCrossfade); } break; + MATCH("/region&/loop_crossfade", "f") { m.set(&Region::loopCrossfade, Default::loopCrossfade); } break; MATCH("/region&/loop_count", "") { m.reply(&Region::loopCount); } break; + MATCH("/region&/loop_count", "i") { m.set(&Region::loopCount, Default::loopCount); } break; + MATCH("/region&/loop_count", "N") { m.set(&Region::loopCount, Default::loopCount); } break; MATCH("/region&/output", "") { m.reply(&Region::output); } break; + MATCH("/region&/output", "i") { m.set(&Region::output, Default::output); } break; MATCH("/region&/group", "") { m.reply(&Region::group); } break; + MATCH("/region&/group", "h") { m.set(&Region::group, Default::group); } break; MATCH("/region&/off_by", "") { m.reply(&Region::offBy); } break; + MATCH("/region&/off_by", "h") { m.set(&Region::offBy, Default::group); } break; + MATCH("/region&/off_by", "N") { m.set(&Region::offBy, Default::group); } break; MATCH("/region&/off_mode", "") { m.reply(&Region::offMode); } break; + MATCH("/region&/off_mode", "s") { m.set(&Region::offMode, Default::offMode); } break; MATCH("/region&/key_range", "") { m.reply(&Region::keyRange); } break; + MATCH("/region&/key_range", "ii") { m.set(&Region::keyRange, Default::loKey, Default::hiKey); } break; MATCH("/region&/off_time", "") { m.reply(&Region::offTime); } break; + MATCH("/region&/off_time", "f") { m.set(&Region::offTime, Default::offTime); } break; MATCH("/region&/pitch_keycenter", "") { m.reply(&Region::pitchKeycenter); } break; + MATCH("/region&/pitch_keycenter", "i") { m.set(&Region::pitchKeycenter, Default::key); } break; MATCH("/region&/vel_range", "") { m.reply(&Region::velocityRange); } break; + MATCH("/region&/vel_range", "ff") { m.set(&Region::velocityRange); } break; MATCH("/region&/bend_range", "") { m.reply(&Region::bendRange); } break; + MATCH("/region&/bend_range", "ff") { m.set(&Region::bendRange); } break; MATCH("/region&/program_range", "") { m.reply(&Region::programRange); } break; + MATCH("/region&/program_range", "ii") { m.set(&Region::programRange); } break; MATCH("/region&/cc_range&", "") { m.reply(&Region::ccConditions); } break; + MATCH("/region&/cc_range&", "ff") { m.set(&Region::ccConditions); } break; MATCH("/region&/sw_last", "") { if (auto region = m.getRegion()) { if (region->lastKeyswitch) m.reply(region->lastKeyswitch); else m.reply(region->lastKeyswitchRange); } } break; + MATCH("/region&/sw_last", "i") { + if (auto region = m.getRegion()) { + m.set(&Region::lastKeyswitch, Default::key); + region->lastKeyswitchRange.reset(); + } + } break; + MATCH("/region&/sw_last", "ii") { + if (auto region = m.getRegion()) { + region->lastKeyswitch.reset(); + region->lastKeyswitchRange.emplace(args[0].i, args[1].i); + } + } break; MATCH("/region&/sw_label", "") { m.reply(&Region::keyswitchLabel); } break; + MATCH("/region&/sw_label", "s") { m.set(&Region::keyswitchLabel); } break; MATCH("/region&/sw_up", "") { m.reply(&Region::upKeyswitch); } break; + MATCH("/region&/sw_up", "i") { m.set(&Region::upKeyswitch, Default::key); } break; + MATCH("/region&/sw_up", "s") { m.set(&Region::upKeyswitch, Default::key); } break; MATCH("/region&/sw_down", "") { m.reply(&Region::downKeyswitch); } break; + MATCH("/region&/sw_down", "i") { m.set(&Region::downKeyswitch, Default::key); } break; + MATCH("/region&/sw_down", "s") { m.set(&Region::downKeyswitch, Default::key); } break; MATCH("/region&/sw_previous", "") { m.reply(&Region::previousKeyswitch); } break; + MATCH("/region&/sw_previous", "i") { m.set(&Region::previousKeyswitch, Default::key); } break; + MATCH("/region&/sw_previous", "s") { m.set(&Region::previousKeyswitch, Default::key); } break; MATCH("/region&/sw_vel", "") { m.reply(&Region::velocityOverride); } break; + MATCH("/region&/sw_vel", "s") { m.set(&Region::velocityOverride, Default::velocityOverride); } break; MATCH("/region&/chanaft_range", "") { m.reply(&Region::aftertouchRange); } break; + MATCH("/region&/chanaft_range", "ff") { m.set(&Region::aftertouchRange); } break; MATCH("/region&/polyaft_range", "") { m.reply(&Region::polyAftertouchRange); } break; + MATCH("/region&/polyaft_range", "ff") { m.set(&Region::polyAftertouchRange); } break; MATCH("/region&/bpm_range", "") { m.reply(&Region::bpmRange); } break; + MATCH("/region&/bpm_range", "ff") { m.set(&Region::bpmRange, Default::loBPM, Default::hiBPM); } break; MATCH("/region&/rand_range", "") { m.reply(&Region::randRange); } break; + MATCH("/region&/rand_range", "ff") { m.set(&Region::randRange, Default::loNormalized, Default::hiNormalized); } break; MATCH("/region&/seq_length", "") { m.reply(&Region::sequenceLength); } break; + MATCH("/region&/seq_length", "i") { m.set(&Region::sequenceLength, Default::sequence); } break; MATCH("/region&/seq_position", "") { m.reply(&Region::sequencePosition); } break; + MATCH("/region&/seq_position", "i") { m.set(&Region::sequencePosition, Default::sequence); } break; MATCH("/region&/trigger", "") { m.reply(&Region::trigger); } break; + MATCH("/region&/trigger", "s") { m.set(&Region::trigger, Default::trigger); } break; MATCH("/region&/start_cc_range&", "") { m.reply(&Region::ccTriggers, false); } break; + MATCH("/region&/start_cc_range&", "ff") { m.set(&Region::ccTriggers); } break; MATCH("/region&/volume", "") { m.reply(&Region::volume); } break; + MATCH("/region&/volume", "f") { m.set(&Region::volume, Default::volume); } break; + // Probably need to rethink the way we set these in both the Region parsing and here before making changes MATCH("/region&/volume_cc&", "") { m.reply(ModId::Volume, ModParam::Depth); } break; MATCH("/region&/volume_stepcc&", "") { m.reply(ModId::Volume, ModParam::Step); } break; MATCH("/region&/volume_smoothcc&", "") { m.reply(ModId::Volume, ModParam::Smooth); } break; MATCH("/region&/volume_curvecc&", "") { m.reply(ModId::Volume, ModParam::Curve); } break; MATCH("/region&/pan", "") { m.reply(&Region::pan, Default::pan); } break; + MATCH("/region&/pan", "f") { m.set(&Region::pan, Default::pan); } break; MATCH("/region&/pan_cc&", "") { m.reply(ModId::Pan, ModParam::Depth, Default::pan); } break; MATCH("/region&/pan_stepcc&", "") { m.reply(ModId::Pan, ModParam::Step, Default::pan); } break; MATCH("/region&/pan_smoothcc&", "") { m.reply(ModId::Pan, ModParam::Smooth, Default::pan); } break; MATCH("/region&/pan_curvecc&", "") { m.reply(ModId::Pan, ModParam::Curve, Default::pan); } break; MATCH("/region&/width", "") { m.reply(&Region::width, Default::width); } break; + MATCH("/region&/width", "f") { m.set(&Region::width, Default::width); } break; MATCH("/region&/width_cc&", "") { m.reply(ModId::Width, ModParam::Depth, Default::width); } break; MATCH("/region&/width_stepcc&", "") { m.reply(ModId::Width, ModParam::Step, Default::width); } break; MATCH("/region&/width_smoothcc&", "") { m.reply(ModId::Width, ModParam::Smooth, Default::width); } break; MATCH("/region&/width_curvecc&", "") { m.reply(ModId::Width, ModParam::Curve, Default::width); } break; MATCH("/region&/timer_range", "") { m.reply(&Region::timerRange); } break; MATCH("/region&/position", "") { m.reply(&Region::position, Default::position); } break; + MATCH("/region&/position", "f") { m.set(&Region::position, Default::position); } break; MATCH("/region&/position_cc&", "") { m.reply(ModId::Position, ModParam::Depth, Default::position); } break; MATCH("/region&/position_stepcc&", "") { m.reply(ModId::Position, ModParam::Step, Default::position); } break; MATCH("/region&/position_smoothcc&", "") { m.reply(ModId::Position, ModParam::Smooth, Default::position); } break; MATCH("/region&/position_curvecc&", "") { m.reply(ModId::Position, ModParam::Curve, Default::position); } break; MATCH("/region&/amplitude", "") { m.reply(&Region::amplitude, Default::amplitude); } break; + MATCH("/region&/amplitude", "f") { m.set(&Region::amplitude, Default::amplitude); } break; MATCH("/region&/amplitude_cc&", "") { m.reply(ModId::Amplitude, ModParam::Depth, Default::amplitude); } break; MATCH("/region&/amplitude_stepcc&", "") { m.reply(ModId::Amplitude, ModParam::Step, Default::amplitude); } break; MATCH("/region&/amplitude_smoothcc&", "") { m.reply(ModId::Amplitude, ModParam::Smooth, Default::amplitude); } break; MATCH("/region&/amplitude_curvecc&", "") { m.reply(ModId::Amplitude, ModParam::Curve, Default::amplitude); } break; MATCH("/region&/amp_keycenter", "") { m.reply(&Region::ampKeycenter); } break; + MATCH("/region&/amp_keycenter", "i") { m.set(&Region::ampKeycenter, Default::key); } break; MATCH("/region&/amp_keytrack", "") { m.reply(&Region::ampKeytrack); } break; + MATCH("/region&/amp_keytrack", "f") { m.set(&Region::ampKeytrack, Default::ampKeytrack); } break; MATCH("/region&/amp_veltrack", "") { m.reply(&Region::ampVeltrack, Default::ampVeltrack); } break; + MATCH("/region&/amp_veltrack", "f") { m.set(&Region::ampVeltrack, Default::ampVeltrack); } break; MATCH("/region&/amp_veltrack_cc&", "") { m.reply(&Region::ampVeltrackCC, false, ModParam::Depth, Default::ampVeltrackMod); } break; + MATCH("/region&/amp_veltrack_cc&", "f") { m.set(&Region::ampVeltrackCC, ModParam::Depth, Default::ampVeltrackMod); } break; MATCH("/region&/amp_veltrack_curvecc&", "") { m.reply(&Region::ampVeltrackCC, false, ModParam::Curve, Default::ampVeltrackMod); } break; + MATCH("/region&/amp_veltrack_curvecc&", "i") { m.set(&Region::ampVeltrackCC, ModParam::Curve, Default::ampVeltrackMod); } break; MATCH("/region&/amp_random", "") { m.reply(&Region::ampRandom); } break; + MATCH("/region&/amp_random", "f") { m.set(&Region::ampRandom, Default::ampRandom); } break; MATCH("/region&/xfin_key_range", "") { m.reply(&Region::crossfadeKeyInRange); } break; + MATCH("/region&/xfin_key_range", "ii") { m.set(&Region::crossfadeKeyInRange, Default::loKey, Default::hiKey); } break; MATCH("/region&/xfout_key_range", "") { m.reply(&Region::crossfadeKeyOutRange); } break; + MATCH("/region&/xfout_key_range", "ii") { m.set(&Region::crossfadeKeyOutRange, Default::loKey, Default::hiKey); } break; MATCH("/region&/xfin_vel_range", "") { m.reply(&Region::crossfadeVelInRange); } break; + MATCH("/region&/xfin_vel_range", "ff") { m.set(&Region::crossfadeVelInRange); } break; MATCH("/region&/xfout_vel_range", "") { m.reply(&Region::crossfadeVelOutRange); } break; + MATCH("/region&/xfout_vel_range", "ff") { m.set(&Region::crossfadeVelOutRange); } break; MATCH("/region&/xfin_cc_range&", "") { m.reply(&Region::crossfadeCCInRange, false); } break; + MATCH("/region&/xfin_cc_range&", "ff") { m.set(&Region::crossfadeCCInRange); } break; MATCH("/region&/xfout_cc_range&", "") { m.reply(&Region::crossfadeCCOutRange, false); } break; + MATCH("/region&/xfout_cc_range&", "ff") { m.set(&Region::crossfadeCCOutRange); } break; MATCH("/region&/xf_keycurve", "") { m.reply(&Region::crossfadeKeyCurve); } break; + MATCH("/region&/xf_keycurve", "s") { m.set(&Region::crossfadeKeyCurve, Default::crossfadeCurve); } break; MATCH("/region&/xf_velcurve", "") { m.reply(&Region::crossfadeVelCurve); } break; + MATCH("/region&/xf_velcurve", "s") { m.set(&Region::crossfadeVelCurve, Default::crossfadeCurve); } break; MATCH("/region&/xf_cccurve", "") { m.reply(&Region::crossfadeCCCurve); } break; + MATCH("/region&/xf_cccurve", "s") { m.set(&Region::crossfadeCCCurve, Default::crossfadeCurve); } break; MATCH("/region&/global_volume", "") { m.reply(&Region::globalVolume); } break; + MATCH("/region&/global_volume", "f") { m.set(&Region::globalVolume, Default::volume); } break; MATCH("/region&/master_volume", "") { m.reply(&Region::masterVolume); } break; + MATCH("/region&/master_volume", "f") { m.set(&Region::masterVolume, Default::volume); } break; MATCH("/region&/group_volume", "") { m.reply(&Region::groupVolume); } break; + MATCH("/region&/group_volume", "f") { m.set(&Region::groupVolume, Default::volume); } break; MATCH("/region&/global_amplitude", "") { m.reply(&Region::globalAmplitude, Default::amplitude); } break; + MATCH("/region&/global_amplitude", "f") { m.set(&Region::globalAmplitude, Default::amplitude); } break; MATCH("/region&/master_amplitude", "") { m.reply(&Region::masterAmplitude, Default::amplitude); } break; + MATCH("/region&/master_amplitude", "f") { m.set(&Region::masterAmplitude, Default::amplitude); } break; MATCH("/region&/group_amplitude", "") { m.reply(&Region::groupAmplitude, Default::amplitude); } break; + MATCH("/region&/group_amplitude", "f") { m.set(&Region::groupAmplitude, Default::amplitude); } break; MATCH("/region&/pitch_keytrack", "") { m.reply(&Region::pitchKeytrack); } break; + MATCH("/region&/pitch_keytrack", "f") { m.set(&Region::pitchKeytrack, Default::pitchKeytrack); } break; MATCH("/region&/pitch_veltrack", "") { m.reply(&Region::pitchVeltrack); } break; + MATCH("/region&/pitch_veltrack", "f") { m.set(&Region::pitchVeltrack, Default::pitchVeltrack); } break; MATCH("/region&/pitch_veltrack_cc&", "") { m.reply(&Region::pitchVeltrackCC, false, ModParam::Depth); } break; + MATCH("/region&/pitch_veltrack_cc&", "f") { m.set(&Region::pitchVeltrackCC, ModParam::Depth, Default::pitchVeltrackMod); } break; MATCH("/region&/pitch_veltrack_curvecc&", "") { m.reply(&Region::pitchVeltrackCC, false, ModParam::Curve); } break; + MATCH("/region&/pitch_veltrack_curvecc&", "i") { m.set(&Region::pitchVeltrackCC, ModParam::Curve, Default::pitchVeltrackMod); } break; MATCH("/region&/pitch_random", "") { m.reply(&Region::pitchRandom); } break; + MATCH("/region&/pitch_random", "f") { m.set(&Region::pitchRandom, Default::pitchRandom); } break; MATCH("/region&/transpose", "") { m.reply(&Region::transpose); } break; + MATCH("/region&/transpose", "f") { m.set(&Region::transpose, Default::transpose); } break; MATCH("/region&/pitch", "") { m.reply(&Region::pitch); } break; + MATCH("/region&/pitch", "f") { m.set(&Region::pitch, Default::pitch); } break; MATCH("/region&/pitch_cc&", "") { m.reply(ModId::Pitch, ModParam::Depth, Default::pitch); } break; MATCH("/region&/pitch_stepcc&", "") { m.reply(ModId::Pitch, ModParam::Step, Default::pitch); } break; MATCH("/region&/pitch_smoothcc&", "") { m.reply(ModId::Pitch, ModParam::Smooth, Default::pitch); } break; MATCH("/region&/pitch_curvecc&", "") { m.reply(ModId::Pitch, ModParam::Curve, Default::pitch); } break; MATCH("/region&/bend_up", "") { m.reply(&Region::bendUp); } break; + MATCH("/region&/bend_up", "f") { m.set(&Region::bendUp, Default::bendUp); } break; MATCH("/region&/bend_down", "") { m.reply(&Region::bendDown); } break; + MATCH("/region&/bend_down", "f") { m.set(&Region::bendDown, Default::bendDown); } break; MATCH("/region&/bend_step", "") { m.reply(&Region::bendStep); } break; + MATCH("/region&/bend_step", "f") { m.set(&Region::bendStep, Default::bendStep); } break; MATCH("/region&/bend_smooth", "") { m.reply(&Region::bendSmooth); } break; + MATCH("/region&/bend_smooth", "i") { m.set(&Region::bendSmooth, Default::smoothCC); } break; MATCH("/region&/ampeg_attack", "") { m.reply(&Region::amplitudeEG, &EGDescription::attack); } break; MATCH("/region&/ampeg_delay", "") { m.reply(&Region::amplitudeEG, &EGDescription::delay); } break; MATCH("/region&/ampeg_decay", "") { m.reply(&Region::amplitudeEG, &EGDescription::decay); } break; MATCH("/region&/ampeg_hold", "") { m.reply(&Region::amplitudeEG, &EGDescription::hold); } break; MATCH("/region&/ampeg_release", "") { m.reply(&Region::amplitudeEG, &EGDescription::release); } break; - MATCH("/region&/ampeg_start", "") { m.reply(&Region::amplitudeEG, &EGDescription::start, Default::egPercentMod); } break; - MATCH("/region&/ampeg_sustain", "") { m.reply(&Region::amplitudeEG, &EGDescription::sustain, Default::egPercentMod); } break; + MATCH("/region&/ampeg_start", "") { m.reply(&Region::amplitudeEG, &EGDescription::start, Default::egPercent); } break; + MATCH("/region&/ampeg_sustain", "") { m.reply(&Region::amplitudeEG, &EGDescription::sustain, Default::egPercent); } break; MATCH("/region&/ampeg_depth", "") { m.reply(&Region::amplitudeEG, &EGDescription::depth); } break; MATCH("/region&/ampeg_attack_cc&", "") { m.reply(&Region::amplitudeEG, &EGDescription::ccAttack, ModParam::Depth); } break; MATCH("/region&/ampeg_attack_curvecc&", "") { m.reply(&Region::amplitudeEG, &EGDescription::ccAttack, ModParam::Curve); } break; @@ -736,6 +305,38 @@ void sfz::Synth::dispatchMessage(Client& client, int delay, const char* path, co MATCH("/region&/ampeg_vel&sustain", "") { m.reply(&Region::amplitudeEG, &EGDescription::vel2sustain, Default::egPercentMod); } break; MATCH("/region&/ampeg_vel&depth", "") { m.reply(&Region::amplitudeEG, &EGDescription::vel2depth); } break; MATCH("/region&/ampeg_dynamic", "") { m.reply(&Region::amplitudeEG, &EGDescription::dynamic); } break; + MATCH("/region&/ampeg_attack", "f") { m.set(&Region::amplitudeEG, &EGDescription::attack, Default::egTime); } break; + MATCH("/region&/ampeg_delay", "f") { m.set(&Region::amplitudeEG, &EGDescription::delay, Default::egTime); } break; + MATCH("/region&/ampeg_decay", "f") { m.set(&Region::amplitudeEG, &EGDescription::decay, Default::egTime); } break; + MATCH("/region&/ampeg_hold", "f") { m.set(&Region::amplitudeEG, &EGDescription::hold, Default::egTime); } break; + MATCH("/region&/ampeg_release", "f") { m.set(&Region::amplitudeEG, &EGDescription::release, Default::egTime); } break; + MATCH("/region&/ampeg_start", "f") { m.set(&Region::amplitudeEG, &EGDescription::start, Default::egPercent); } break; + MATCH("/region&/ampeg_sustain", "f") { m.set(&Region::amplitudeEG, &EGDescription::sustain, Default::egPercent); } break; + MATCH("/region&/ampeg_depth", "f") { m.set(&Region::amplitudeEG, &EGDescription::depth, Default::egDepth); } break; + MATCH("/region&/ampeg_attack_cc&", "f") { m.set(&Region::amplitudeEG, &EGDescription::ccAttack, ModParam::Depth, Default::egTimeMod); } break; + MATCH("/region&/ampeg_attack_curvecc&", "i") { m.set(&Region::amplitudeEG, &EGDescription::ccAttack, ModParam::Curve, Default::egTimeMod); } break; + MATCH("/region&/ampeg_decay_cc&", "f") { m.set(&Region::amplitudeEG, &EGDescription::ccDecay, ModParam::Depth, Default::egTimeMod); } break; + MATCH("/region&/ampeg_decay_curvecc&", "i") { m.set(&Region::amplitudeEG, &EGDescription::ccDecay, ModParam::Curve, Default::egTimeMod); } break; + MATCH("/region&/ampeg_delay_cc&", "f") { m.set(&Region::amplitudeEG, &EGDescription::ccDelay, ModParam::Depth, Default::egTimeMod); } break; + MATCH("/region&/ampeg_delay_curvecc&", "i") { m.set(&Region::amplitudeEG, &EGDescription::ccDelay, ModParam::Curve, Default::egTimeMod); } break; + MATCH("/region&/ampeg_hold_cc&", "f") { m.set(&Region::amplitudeEG, &EGDescription::ccHold, ModParam::Depth, Default::egTimeMod); } break; + MATCH("/region&/ampeg_hold_curvecc&", "i") { m.set(&Region::amplitudeEG, &EGDescription::ccHold, ModParam::Curve, Default::egTimeMod); } break; + MATCH("/region&/ampeg_release_cc&", "f") { m.set(&Region::amplitudeEG, &EGDescription::ccRelease, ModParam::Depth, Default::egTimeMod); } break; + MATCH("/region&/ampeg_release_curvecc&", "i") { m.set(&Region::amplitudeEG, &EGDescription::ccRelease, ModParam::Curve, Default::egTimeMod); } break; + MATCH("/region&/ampeg_sustain_cc&", "f") { m.set(&Region::amplitudeEG, &EGDescription::ccSustain, ModParam::Depth, Default::egPercentMod); } break; + MATCH("/region&/ampeg_sustain_curvecc&", "i") { m.set(&Region::amplitudeEG, &EGDescription::ccSustain, ModParam::Curve, Default::egPercentMod); } break; + MATCH("/region&/ampeg_start_cc&", "f") { m.set(&Region::amplitudeEG, &EGDescription::ccStart, ModParam::Depth, Default::egPercentMod); } break; + MATCH("/region&/ampeg_start_curvecc&", "i") { m.set(&Region::amplitudeEG, &EGDescription::ccStart, ModParam::Curve, Default::egPercentMod); } break; + MATCH("/region&/ampeg_vel&attack", "f") { m.set(&Region::amplitudeEG, &EGDescription::vel2attack, Default::egTimeMod); } break; + MATCH("/region&/ampeg_vel&delay", "f") { m.set(&Region::amplitudeEG, &EGDescription::vel2delay, Default::egTimeMod); } break; + MATCH("/region&/ampeg_vel&decay", "f") { m.set(&Region::amplitudeEG, &EGDescription::vel2decay, Default::egTimeMod); } break; + MATCH("/region&/ampeg_vel&hold", "f") { m.set(&Region::amplitudeEG, &EGDescription::vel2hold, Default::egTimeMod); } break; + MATCH("/region&/ampeg_vel&release", "f") { m.set(&Region::amplitudeEG, &EGDescription::vel2release, Default::egTimeMod); } break; + MATCH("/region&/ampeg_vel&sustain", "f") { m.set(&Region::amplitudeEG, &EGDescription::vel2sustain, Default::egPercentMod); } break; + MATCH("/region&/ampeg_vel&depth", "f") { m.set(&Region::amplitudeEG, &EGDescription::vel2depth, Default::egDepth); } break; + MATCH("/region&/ampeg_dynamic", "T") { m.set(&Region::amplitudeEG, &EGDescription::dynamic, Default::egDynamic); } break; + MATCH("/region&/ampeg_dynamic", "F") { m.set(&Region::amplitudeEG, &EGDescription::dynamic, Default::egDynamic); } break; + MATCH("/region&/ampeg_dynamic", "s") { m.set(&Region::amplitudeEG, &EGDescription::dynamic, Default::egDynamic); } break; MATCH("/region&/fileg_attack", "") { m.reply(&Region::filterEG, &EGDescription::attack); } break; MATCH("/region&/fileg_delay", "") { m.reply(&Region::filterEG, &EGDescription::delay); } break; MATCH("/region&/fileg_decay", "") { m.reply(&Region::filterEG, &EGDescription::decay); } break; @@ -759,6 +360,31 @@ void sfz::Synth::dispatchMessage(Client& client, int delay, const char* path, co MATCH("/region&/fileg_start_cc&", "") { m.reply(&Region::filterEG, &EGDescription::ccStart, ModParam::Depth, Default::egPercentMod); } break; MATCH("/region&/fileg_start_curvecc&", "") { m.reply(&Region::filterEG, &EGDescription::ccStart, ModParam::Curve, Default::egPercentMod); } break; MATCH("/region&/fileg_dynamic", "") { m.reply(&Region::filterEG, &EGDescription::dynamic); } break; + MATCH("/region&/fileg_attack", "f") { m.set(&Region::filterEG, &EGDescription::attack, Default::egTime); } break; + MATCH("/region&/fileg_delay", "f") { m.set(&Region::filterEG, &EGDescription::delay, Default::egTime); } break; + MATCH("/region&/fileg_decay", "f") { m.set(&Region::filterEG, &EGDescription::decay, Default::egTime); } break; + MATCH("/region&/fileg_hold", "f") { m.set(&Region::filterEG, &EGDescription::hold, Default::egTime); } break; + MATCH("/region&/fileg_release", "f") { m.set(&Region::filterEG, &EGDescription::release, Default::egTime); } break; + MATCH("/region&/fileg_start", "f") { m.set(&Region::filterEG, &EGDescription::start, Default::egPercent); } break; + MATCH("/region&/fileg_sustain", "f") { m.set(&Region::filterEG, &EGDescription::sustain, Default::egPercent); } break; + MATCH("/region&/fileg_depth", "f") { m.set(&Region::filterEG, &EGDescription::depth, Default::egDepth); } break; + MATCH("/region&/fileg_attack_cc&", "f") { m.set(&Region::filterEG, &EGDescription::ccAttack, ModParam::Depth, Default::egTimeMod); } break; + MATCH("/region&/fileg_attack_curvecc&", "i") { m.set(&Region::filterEG, &EGDescription::ccAttack, ModParam::Curve, Default::egTimeMod); } break; + MATCH("/region&/fileg_decay_cc&", "f") { m.set(&Region::filterEG, &EGDescription::ccDecay, ModParam::Depth, Default::egTimeMod); } break; + MATCH("/region&/fileg_decay_curvecc&", "i") { m.set(&Region::filterEG, &EGDescription::ccDecay, ModParam::Curve, Default::egTimeMod); } break; + MATCH("/region&/fileg_delay_cc&", "f") { m.set(&Region::filterEG, &EGDescription::ccDelay, ModParam::Depth, Default::egTimeMod); } break; + MATCH("/region&/fileg_delay_curvecc&", "i") { m.set(&Region::filterEG, &EGDescription::ccDelay, ModParam::Curve, Default::egTimeMod); } break; + MATCH("/region&/fileg_hold_cc&", "f") { m.set(&Region::filterEG, &EGDescription::ccHold, ModParam::Depth, Default::egTimeMod); } break; + MATCH("/region&/fileg_hold_curvecc&", "i") { m.set(&Region::filterEG, &EGDescription::ccHold, ModParam::Curve, Default::egTimeMod); } break; + MATCH("/region&/fileg_release_cc&", "f") { m.set(&Region::filterEG, &EGDescription::ccRelease, ModParam::Depth, Default::egTimeMod); } break; + MATCH("/region&/fileg_release_curvecc&", "i") { m.set(&Region::filterEG, &EGDescription::ccRelease, ModParam::Curve, Default::egTimeMod); } break; + MATCH("/region&/fileg_sustain_cc&", "f") { m.set(&Region::filterEG, &EGDescription::ccSustain, ModParam::Depth, Default::egPercentMod); } break; + MATCH("/region&/fileg_sustain_curvecc&", "i") { m.set(&Region::filterEG, &EGDescription::ccSustain, ModParam::Curve, Default::egPercentMod); } break; + MATCH("/region&/fileg_start_cc&", "f") { m.set(&Region::filterEG, &EGDescription::ccStart, ModParam::Depth, Default::egPercentMod); } break; + MATCH("/region&/fileg_start_curvecc&", "i") { m.set(&Region::filterEG, &EGDescription::ccStart, ModParam::Curve, Default::egPercentMod); } break; + MATCH("/region&/fileg_dynamic", "T") { m.set(&Region::filterEG, &EGDescription::dynamic, Default::egDynamic); } break; + MATCH("/region&/fileg_dynamic", "F") { m.set(&Region::filterEG, &EGDescription::dynamic, Default::egDynamic); } break; + MATCH("/region&/fileg_dynamic", "s") { m.set(&Region::filterEG, &EGDescription::dynamic, Default::egDynamic); } break; MATCH("/region&/pitcheg_attack", "") { m.reply(&Region::pitchEG, &EGDescription::attack); } break; MATCH("/region&/pitcheg_delay", "") { m.reply(&Region::pitchEG, &EGDescription::delay); } break; MATCH("/region&/pitcheg_decay", "") { m.reply(&Region::pitchEG, &EGDescription::decay); } break; @@ -782,21 +408,67 @@ void sfz::Synth::dispatchMessage(Client& client, int delay, const char* path, co MATCH("/region&/pitcheg_start_cc&", "") { m.reply(&Region::pitchEG, &EGDescription::ccStart, ModParam::Depth, Default::egPercentMod); } break; MATCH("/region&/pitcheg_start_curvecc&", "") { m.reply(&Region::pitchEG, &EGDescription::ccStart, ModParam::Curve, Default::egPercentMod); } break; MATCH("/region&/pitcheg_dynamic", "") { m.reply(&Region::pitchEG, &EGDescription::dynamic); } break; + MATCH("/region&/pitcheg_attack", "f") { m.set(&Region::pitchEG, &EGDescription::attack, Default::egTime); } break; + MATCH("/region&/pitcheg_delay", "f") { m.set(&Region::pitchEG, &EGDescription::delay, Default::egTime); } break; + MATCH("/region&/pitcheg_decay", "f") { m.set(&Region::pitchEG, &EGDescription::decay, Default::egTime); } break; + MATCH("/region&/pitcheg_hold", "f") { m.set(&Region::pitchEG, &EGDescription::hold, Default::egTime); } break; + MATCH("/region&/pitcheg_release", "f") { m.set(&Region::pitchEG, &EGDescription::release, Default::egTime); } break; + MATCH("/region&/pitcheg_start", "f") { m.set(&Region::pitchEG, &EGDescription::start, Default::egPercent); } break; + MATCH("/region&/pitcheg_sustain", "f") { m.set(&Region::pitchEG, &EGDescription::sustain, Default::egPercent); } break; + MATCH("/region&/pitcheg_depth", "f") { m.set(&Region::pitchEG, &EGDescription::depth, Default::egDepth); } break; + MATCH("/region&/pitcheg_attack_cc&", "f") { m.set(&Region::pitchEG, &EGDescription::ccAttack, ModParam::Depth, Default::egTimeMod); } break; + MATCH("/region&/pitcheg_attack_curvecc&", "i") { m.set(&Region::pitchEG, &EGDescription::ccAttack, ModParam::Curve, Default::egTimeMod); } break; + MATCH("/region&/pitcheg_decay_cc&", "f") { m.set(&Region::pitchEG, &EGDescription::ccDecay, ModParam::Depth, Default::egTimeMod); } break; + MATCH("/region&/pitcheg_decay_curvecc&", "i") { m.set(&Region::pitchEG, &EGDescription::ccDecay, ModParam::Curve, Default::egTimeMod); } break; + MATCH("/region&/pitcheg_delay_cc&", "f") { m.set(&Region::pitchEG, &EGDescription::ccDelay, ModParam::Depth, Default::egTimeMod); } break; + MATCH("/region&/pitcheg_delay_curvecc&", "i") { m.set(&Region::pitchEG, &EGDescription::ccDelay, ModParam::Curve, Default::egTimeMod); } break; + MATCH("/region&/pitcheg_hold_cc&", "f") { m.set(&Region::pitchEG, &EGDescription::ccHold, ModParam::Depth, Default::egTimeMod); } break; + MATCH("/region&/pitcheg_hold_curvecc&", "i") { m.set(&Region::pitchEG, &EGDescription::ccHold, ModParam::Curve, Default::egTimeMod); } break; + MATCH("/region&/pitcheg_release_cc&", "f") { m.set(&Region::pitchEG, &EGDescription::ccRelease, ModParam::Depth, Default::egTimeMod); } break; + MATCH("/region&/pitcheg_release_curvecc&", "i") { m.set(&Region::pitchEG, &EGDescription::ccRelease, ModParam::Curve, Default::egTimeMod); } break; + MATCH("/region&/pitcheg_sustain_cc&", "f") { m.set(&Region::pitchEG, &EGDescription::ccSustain, ModParam::Depth, Default::egPercentMod); } break; + MATCH("/region&/pitcheg_sustain_curvecc&", "i") { m.set(&Region::pitchEG, &EGDescription::ccSustain, ModParam::Curve, Default::egPercentMod); } break; + MATCH("/region&/pitcheg_start_cc&", "f") { m.set(&Region::pitchEG, &EGDescription::ccStart, ModParam::Depth, Default::egPercentMod); } break; + MATCH("/region&/pitcheg_start_curvecc&", "i") { m.set(&Region::pitchEG, &EGDescription::ccStart, ModParam::Curve, Default::egPercentMod); } break; + MATCH("/region&/pitcheg_dynamic", "T") { if (auto region = m.getRegion()) { if (region->pitchEG) { region->pitchEG->dynamic = true;} } } break; + MATCH("/region&/pitcheg_dynamic", "F") { if (auto region = m.getRegion()) { if (region->pitchEG) { region->pitchEG->dynamic = false;} } } break; + MATCH("/region&/pitcheg_dynamic", "s") { m.set(&Region::pitchEG, &EGDescription::dynamic, Default::egDynamic); } break; MATCH("/region&/note_polyphony", "") { m.reply(&Region::notePolyphony); } break; + MATCH("/region&/note_polyphony", "i") { m.set(&Region::notePolyphony, Default::notePolyphony); } break; MATCH("/region&/rt_dead", "") { m.reply(&Region::rtDead); } break; + MATCH("/region&/rt_dead", "s") { m.set(&Region::rtDead, Default::rtDead); } break; + MATCH("/region&/rt_dead", "T") { m.set(&Region::rtDead, Default::rtDead); } break; + MATCH("/region&/rt_dead", "F") { m.set(&Region::rtDead, Default::rtDead); } break; MATCH("/region&/sustain_sw", "") { m.reply(&Region::checkSustain); } break; + MATCH("/region&/sustain_sw", "s") { m.set(&Region::checkSustain, Default::checkSustain); } break; + MATCH("/region&/sustain_sw", "T") { m.set(&Region::checkSustain, Default::checkSustain); } break; + MATCH("/region&/sustain_sw", "F") { m.set(&Region::checkSustain, Default::checkSustain); } break; MATCH("/region&/sostenuto_sw", "") { m.reply(&Region::checkSostenuto); } break; + MATCH("/region&/sostenuto_sw", "s") { m.set(&Region::checkSostenuto, Default::checkSostenuto); } break; + MATCH("/region&/sostenuto_sw", "T") { m.set(&Region::checkSostenuto, Default::checkSostenuto); } break; + MATCH("/region&/sostenuto_sw", "F") { m.set(&Region::checkSostenuto, Default::checkSostenuto); } break; MATCH("/region&/sustain_cc", "") { m.reply(&Region::sustainCC); } break; + MATCH("/region&/sustain_cc", "i") { m.set(&Region::sustainCC, Default::sustainCC); } break; MATCH("/region&/sostenuto_cc", "") { m.reply(&Region::sostenutoCC); } break; + MATCH("/region&/sostenuto_cc", "i") { m.set(&Region::sostenutoCC, Default::sostenutoCC); } break; MATCH("/region&/sustain_lo", "") { m.reply(&Region::sustainThreshold); } break; + MATCH("/region&/sustain_lo", "f") { m.set(&Region::sustainThreshold); } break; MATCH("/region&/sostenuto_lo", "") { m.reply(&Region::sostenutoThreshold); } break; + MATCH("/region&/sostenuto_lo", "f") { m.set(&Region::sostenutoThreshold); } break; MATCH("/region&/note_selfmask", "") { m.reply(&Region::selfMask); } break; + MATCH("/region&/note_selfmask", "s") { m.set(&Region::selfMask, Default::selfMask); } break; MATCH("/region&/oscillator_phase", "") { m.reply(&Region::oscillatorPhase); } break; + MATCH("/region&/oscillator_phase", "f") { m.set(&Region::oscillatorPhase, Default::oscillatorPhase); } break; MATCH("/region&/oscillator_quality", "") { m.reply(&Region::oscillatorQuality); } break; + MATCH("/region&/oscillator_quality", "i") { m.set(&Region::oscillatorQuality, Default::oscillatorQuality); } break; MATCH("/region&/oscillator_mode", "") { m.reply(&Region::oscillatorMode); } break; + MATCH("/region&/oscillator_mode", "i") { m.set(&Region::oscillatorMode, Default::oscillatorMode); } break; MATCH("/region&/oscillator_multi", "") { m.reply(&Region::oscillatorMulti); } break; + MATCH("/region&/oscillator_multi", "i") { m.set(&Region::oscillatorMulti, Default::oscillatorMulti); } break; MATCH("/region&/oscillator_detune", "") { m.reply(&Region::oscillatorDetune); } break; + MATCH("/region&/oscillator_detune", "f") { m.set(&Region::oscillatorDetune, Default::oscillatorDetune); } break; MATCH("/region&/oscillator_mod_depth", "") { m.reply(&Region::oscillatorModDepth, Default::oscillatorModDepth); } break; + MATCH("/region&/oscillator_mod_depth", "f") { m.set(&Region::oscillatorModDepth, Default::oscillatorModDepth); } break; // TODO: detune cc, mod depth cc MATCH("/region&/effect&", "") { @@ -805,102 +477,104 @@ void sfz::Synth::dispatchMessage(Client& client, int delay, const char* path, co if (effectIdx > 0 && effectIdx < ssize(region->gainToEffect)) m.reply(region->gainToEffect[*effectIdx], Default::effect); } break; + MATCH("/region&/effect&", "f") { + if (auto region = m.getRegion()) + if (auto effectIdx = m.sindex(1)) + if (effectIdx > 0 && effectIdx < ssize(region->gainToEffect)) + m.set(region->gainToEffect[*effectIdx], Default::effect); + } break; + MATCH("/region&/add_filter", "") { + if (auto region = m.getRegion()) { + region->filters.emplace_back(); + int32_t index = region->filters.size() - 1; + impl.settingsPerVoice_.maxFilters = max(impl.settingsPerVoice_.maxFilters, region->flexEGs.size()); + impl.applySettingsPerVoice(); + m.reply(index); + } + } break; MATCH("/region&/filter&/cutoff", "") { m.reply(&FilterDescription::cutoff); } break; + MATCH("/region&/filter&/cutoff", "f") { m.set(&FilterDescription::cutoff, Default::filterCutoff); } break; MATCH("/region&/filter&/cutoff_cc&", "") { m.reply(ModId::FilCutoff, ModParam::Depth); } break; MATCH("/region&/filter&/cutoff_curvecc&", "") { m.reply(ModId::FilCutoff, ModParam::Curve); } break; MATCH("/region&/filter&/cutoff_stepcc&", "") { m.reply(ModId::FilCutoff, ModParam::Step); } break; MATCH("/region&/filter&/cutoff_smoothcc&", "") { m.reply(ModId::FilCutoff, ModParam::Smooth); } break; MATCH("/region&/filter&/resonance", "") { m.reply(&FilterDescription::resonance); } break; + MATCH("/region&/filter&/resonance", "f") { m.set(&FilterDescription::resonance, Default::filterResonance); } break; MATCH("/region&/filter&/gain", "") { m.reply(&FilterDescription::gain); } break; + MATCH("/region&/filter&/gain", "f") { m.set(&FilterDescription::gain, Default::filterGain); } break; MATCH("/region&/filter&/keycenter", "") { m.reply(&FilterDescription::keycenter); } break; + MATCH("/region&/filter&/keycenter", "i") { m.set(&FilterDescription::keycenter, Default::key); } break; MATCH("/region&/filter&/keytrack", "") { m.reply(&FilterDescription::keytrack); } break; + MATCH("/region&/filter&/keytrack", "f") { m.set(&FilterDescription::keytrack, Default::filterKeytrack); } break; MATCH("/region&/filter&/veltrack", "") { m.reply(&FilterDescription::veltrack); } break; + MATCH("/region&/filter&/veltrack", "f") { m.set(&FilterDescription::veltrack, Default::filterVeltrack); } break; MATCH("/region&/filter&/veltrack_cc&", "") { m.reply(&FilterDescription::veltrackCC, ModParam::Depth); } break; + MATCH("/region&/filter&/veltrack_cc&", "f") { m.set(&FilterDescription::veltrackCC, ModParam::Depth, Default::filterVeltrackMod); } break; MATCH("/region&/filter&/veltrack_curvecc&", "") { m.reply(&FilterDescription::veltrackCC, ModParam::Curve); } break; + MATCH("/region&/filter&/veltrack_curvecc&", "i") { m.set(&FilterDescription::veltrackCC, ModParam::Curve, Default::filterVeltrackMod); } break; MATCH("/region&/filter&/type", "") { m.reply(&FilterDescription::type); } break; + MATCH("/region&/filter&/type", "s") { m.set(&FilterDescription::type, Default::filter); } break; //---------------------------------------------------------------------- + MATCH("/region&/add_eq", "") { + if (auto region = m.getRegion()) { + region->equalizers.emplace_back(); + int32_t index = region->equalizers.size() - 1; + impl.settingsPerVoice_.maxEQs = max(impl.settingsPerVoice_.maxEQs, region->flexEGs.size()); + impl.applySettingsPerVoice(); + m.reply(index); + } + } break; MATCH("/region&/eq&/gain", "") { m.reply(&EQDescription::gain); } break; + MATCH("/region&/eq&/gain", "f") { m.set(&EQDescription::gain, Default::eqGain); } break; MATCH("/region&/eq&/bandwidth", "") { m.reply(&EQDescription::bandwidth); } break; + MATCH("/region&/eq&/bandwidth", "f") { m.set(&EQDescription::bandwidth, Default::eqBandwidth); } break; MATCH("/region&/eq&/frequency", "") { m.reply(&EQDescription::frequency); } break; + MATCH("/region&/eq&/frequency", "f") { m.set(&EQDescription::frequency, Default::eqFrequency); } break; MATCH("/region&/eq&/vel&freq", "") { m.reply(&EQDescription::vel2frequency); } break; + MATCH("/region&/eq&/vel&freq", "f") { m.set(&EQDescription::vel2frequency, Default::eqVel2Frequency); } break; MATCH("/region&/eq&/vel&gain", "") { m.reply(&EQDescription::vel2gain); } break; + MATCH("/region&/eq&/vel&gain", "f") { m.set(&EQDescription::vel2gain, Default::eqVel2Gain); } break; MATCH("/region&/eq&/type", "") { m.reply(&EQDescription::type); } break; + MATCH("/region&/eq&/type", "s") { m.set(&EQDescription::type, Default::eq); } break; //---------------------------------------------------------------------- MATCH("/region&/lfo&/wave", "") { m.reply(&LFODescription::Sub::wave); } break; + MATCH("/region&/lfo&/wave", "i") { m.set(&LFODescription::Sub::wave, Default::lfoWave); } break; + MATCH("/region&/lfo&/wave&", "") { m.reply(&LFODescription::Sub::wave); } break; + MATCH("/region&/lfo&/wave&", "i") { m.set(&LFODescription::Sub::wave, Default::lfoWave); } break; //---------------------------------------------------------------------- + MATCH("/region&/add_eg", "") { + if (auto region = m.getRegion()) { + region->flexEGs.emplace_back(); + region->flexEGs.back().points.emplace_back(); + int32_t index = region->flexEGs.size() - 1; + impl.settingsPerVoice_.maxFlexEGs = max(impl.settingsPerVoice_.maxFlexEGs, region->flexEGs.size()); + impl.applySettingsPerVoice(); + m.reply(index); + } + } break; + MATCH("/region&/eg&/add_point", "") { + if (auto region = m.getRegion()) { + if (auto eg = m.getEG(*region)) { + eg->points.emplace_back(); + int32_t index = eg->points.size() - 2; + m.reply(index); + } + } + } break; MATCH("/region&/eg&/point&/time", "") { m.reply(&FlexEGPoint::time); } break; + MATCH("/region&/eg&/point&/time", "f") { m.set(&FlexEGPoint::time, Default::flexEGPointTime); } break; MATCH("/region&/eg&/point&/time_cc&", "") { m.reply(&FlexEGPoint::ccTime); } break; + MATCH("/region&/eg&/point&/time_cc&", "f") { m.set(&FlexEGPoint::ccTime, Default::flexEGPointTimeMod); } break; MATCH("/region&/eg&/point&/level", "") { m.reply(&FlexEGPoint::level); } break; + MATCH("/region&/eg&/point&/level", "f") { m.set(&FlexEGPoint::level, Default::flexEGPointLevel); } break; MATCH("/region&/eg&/point&/level_cc&", "") { m.reply(&FlexEGPoint::ccLevel); } break; + MATCH("/region&/eg&/point&/level_cc&", "f") { m.set(&FlexEGPoint::ccLevel, Default::flexEGPointLevelMod); } break; //---------------------------------------------------------------------- MATCH("/voice&/trigger_value", "") { m.reply(&TriggerEvent::value); } break; MATCH("/voice&/trigger_number", "") { m.reply(&TriggerEvent::number); } break; MATCH("/voice&/trigger_type", "") { m.reply(&TriggerEvent::type); } break; MATCH("/voice&/remaining_delay", "") { m.reply(&Voice::getRemainingDelay); } break; MATCH("/voice&/source_position", "") { m.reply(&Voice::getSourcePosition); } break; - //---------------------------------------------------------------------- - - // Setting values - // Note: all these must be rt-safe within the parseOpcode method in region - - MATCH("/sample_quality", "i") { - impl.resources_.getSynthConfig().liveSampleQuality = - Opcode::transform(Default::sampleQuality, static_cast(args[0].i)); - } break; - - MATCH("/oscillator_quality", "i") { - impl.resources_.getSynthConfig().liveOscillatorQuality = - Opcode::transform(Default::oscillatorQuality, static_cast(args[0].i)); - } break; - - MATCH("/freewheeling_sample_quality", "i") { - impl.resources_.getSynthConfig().freeWheelingSampleQuality = - Opcode::transform(Default::freewheelingSampleQuality, static_cast(args[0].i)); - } break; - - MATCH("/freewheeling_oscillator_quality", "i") { - impl.resources_.getSynthConfig().freeWheelingOscillatorQuality = - Opcode::transform(Default::freewheelingOscillatorQuality, static_cast(args[0].i)); - } break; - - MATCH("/sustain_cancels_release", "T") { - impl.resources_.getSynthConfig().sustainCancelsRelease = true; - } break; - - MATCH("/sustain_cancels_release", "F") { - impl.resources_.getSynthConfig().sustainCancelsRelease = false; - } break; - - MATCH("/region&/pitch_keycenter", "i") { - if (auto region = m.getRegion()) - region->pitchKeycenter = Opcode::transform(Default::key, args[0].i); - } break; - - MATCH("/region&/loop_mode", "s") { - if (auto region = m.getRegion()) - region->loopMode = Opcode::readOptional(Default::loopMode, args[0].s); - } break; - - MATCH("/region&/filter&/type", "s") { - if (auto region = m.getRegion()) - if (auto filter = m.getFilter(*region)) - filter->type = Opcode::read(Default::filter, args[0].s); - } break; - - MATCH("/region&/lfo&/wave", "i") { - if (auto region = m.getRegion()) - if (auto lfo = m.getLFO(*region)) - if (!lfo->sub.empty()) - lfo->sub[0].wave = Opcode::transform(Default::lfoWave, args[0].i); - } break; - - MATCH("/region&/lfo&/wave&", "i") { - if (auto region = m.getRegion()) - if (auto lfo = m.getLFO(*region)) - if (auto sub = m.getLFOSub(*lfo)) - sub->wave = Opcode::transform(Default::lfoWave, args[0].i); - } break; - #undef MATCH } } diff --git a/src/sfizz/SynthMessagingHelper.hpp b/src/sfizz/SynthMessagingHelper.hpp new file mode 100644 index 000000000..81916504c --- /dev/null +++ b/src/sfizz/SynthMessagingHelper.hpp @@ -0,0 +1,708 @@ +#pragma once + +#include "CCMap.h" +#include "Config.h" +#include "Defaults.h" +#include "EQDescription.h" +#include "FileId.h" +#include "FilterDescription.h" +#include "FlexEGDescription.h" +#include "LFOCommon.h" +#include "LFODescription.h" +#include "Opcode.h" +#include "Range.h" +#include "SfzFilter.h" +#include "SfzHelpers.h" +#include "SynthPrivate.h" +#include "FilePool.h" +#include "Curve.h" +#include "MidiState.h" +#include "SynthConfig.h" +#include "TriggerEvent.h" +#include "SynthPrivate.h" +#include "utility/Size.h" +#include +#include + +namespace inv = invoke_hpp; + +static uint64_t hashMessagePath(const char* path, const char* sig) +{ + uint64_t h = Fnv1aBasis; + while (unsigned char c = *path++) { + if (!absl::ascii_isdigit(c)) + h = hashByte(c, h); + else { + h = hashByte('&', h); + while (absl::ascii_isdigit(*path)) + ++path; + } + } + h = hashByte(',', h); + while (unsigned char c = *sig++) + h = hashByte(c, h); + return h; +} + +template +using IntegralNotBool = std::enable_if_t::value && !std::is_same::value>; + +template +using BoolOrNotIntegral = std::enable_if_t::value || std::is_same::value>; + +namespace sfz { + +static constexpr unsigned maxIndices = 8; + +class MessagingHelper { +public: + MessagingHelper(Client& client, int delay, const char* path, const char* sig, const sfizz_arg_t* args, Synth::Impl& impl) + : client(client) + , impl(impl) + , delay(delay) + , path(path) + , sig(sig) + , request_args(args) + { + indices.reserve(maxIndices); + } + + enum class ModParam { Depth, Curve, Smooth, Step }; + + bool match(const char* pattern, const char* sig) + { + indices.clear(); + const char* path_ = this->path; + + while (const char* endp = strchr(pattern, '&')) { + if (indices.size() == maxIndices) + return false; + + size_t length = endp - pattern; + if (strncmp(pattern, path_, length) != 0) + return false; + pattern += length; + path_ += length; + + length = 0; + while (absl::ascii_isdigit(path_[length])) + ++length; + + indices.push_back(0); + if (!absl::SimpleAtoi(absl::string_view(path_, length), &indices.back())) + return false; + + pattern += 1; + path_ += length; + } + + return !strcmp(path_, pattern) && !strcmp(this->sig, sig); + } + + // These are the reply/reply2 overloads for the (almost) concrete types, that should + // translate to actual calls to the client.receive(...) method. + + void reply(const std::string& value) { client.receive<'s'>(delay, path, value.data()); } + void reply(const char* value) { client.receive<'s'>(delay, path, value); } + // void reply(float value) { client.receive<'f'>(delay, path, value); } + void reply(const float& value) { client.receive<'f'>(delay, path, value); } + void reply(absl::nullopt_t) { client.receive<'N'>(delay, path, {}); } + + void reply(const bool& value) + { + if (value) + client.receive<'T'>(delay, path, {}); + else + client.receive<'F'>(delay, path, {}); + } + + template ::value>> + void reply(const T& value) + { + if (sizeof(value) <= 4) + client.receive<'i'>(delay, path, static_cast(value)); + else + client.receive<'h'>(delay, path, static_cast(value)); + } + template + void reply(const BitArray& array) + { + sfizz_blob_t blob { array.data(), static_cast(array.byte_size()) }; + client.receive<'b'>(delay, path, &blob); + } + + // Call reply but denormalizes the input if needed by the opcode spec + template + void reply(const T& value, const OpcodeSpec& spec) { reply(spec.denormalizeInput(value)); } + + template + BoolOrNotIntegral set(T& target, const OpcodeSpec& spec) { target = Opcode::read(spec, request_args[0].s); } + + // sfizz specific types + void reply(const LFOWave& wave) { reply(static_cast(wave)); } + void reply(const SelfMask& mode) { reply(mode == SelfMask::mask); } + void reply(const LoopMode& mode) + { + switch (mode) { + case LoopMode::no_loop: reply("no_loop"); break; + case LoopMode::loop_continuous: reply("loop_continuous"); break; + case LoopMode::loop_sustain: reply("loop_sustain"); break; + case LoopMode::one_shot: reply("one_shot"); break; + } + } + + void reply(const CrossfadeCurve& curve) + { + switch (curve) { + case CrossfadeCurve::gain: reply("gain"); break; + case CrossfadeCurve::power: reply("power"); break; + } + } + + void reply(const Trigger& mode) + { + switch (mode) { + case Trigger::attack: reply("attack"); break; + case Trigger::first: reply("first"); break; + case Trigger::legato: reply("legato"); break; + case Trigger::release: reply("release"); break; + case Trigger::release_key: reply("release_key"); break; + } + } + + void reply(const VelocityOverride& mode) + { + switch (mode) { + case VelocityOverride::current: reply("current"); break; + case VelocityOverride::previous: reply("previous"); break; + } + } + + void reply(const OffMode& mode) + { + switch (mode) { + case OffMode::fast: reply("fast"); break; + case OffMode::time: reply("time"); break; + case OffMode::normal: reply("normal"); break; + } + } + + void reply(const FilterType& type) + { + switch (type) { + case FilterType::kFilterLpf1p: reply("lpf_1p"); break; + case FilterType::kFilterHpf1p: reply("hpf_1p"); break; + case FilterType::kFilterLpf2p: reply("lpf_2p"); break; + case FilterType::kFilterHpf2p: reply("hpf_2p"); break; + case FilterType::kFilterBpf2p: reply("bpf_2p"); break; + case FilterType::kFilterBrf2p: reply("brf_2p"); break; + case FilterType::kFilterBpf1p: reply("bpf_1p"); break; + case FilterType::kFilterBrf1p: reply("brf_1p"); break; + case FilterType::kFilterApf1p: reply("apf_1p"); break; + case FilterType::kFilterLpf2pSv: reply("lpf_2p_sv"); break; + case FilterType::kFilterHpf2pSv: reply("hpf_2p_sv"); break; + case FilterType::kFilterBpf2pSv: reply("bpf_2p_sv"); break; + case FilterType::kFilterBrf2pSv: reply("brf_2p_sv"); break; + case FilterType::kFilterLpf4p: reply("lpf_4p"); break; + case FilterType::kFilterHpf4p: reply("hpf_4p"); break; + case FilterType::kFilterLpf6p: reply("lpf_6p"); break; + case FilterType::kFilterHpf6p: reply("hpf_6p"); break; + case FilterType::kFilterPink: reply("pink"); break; + case FilterType::kFilterLsh: reply("lsh"); break; + case FilterType::kFilterHsh: reply("hsh"); break; + case FilterType::kFilterPeq: reply("peq"); break; + case FilterType::kFilterBpf4p: reply("bpf_4p"); break; + case FilterType::kFilterBpf6p: reply("bpf_6p"); break; + case FilterType::kFilterNone: reply("none"); break; + } + } + + void reply(const EqType& type) + { + switch (type) { + case EqType::kEqNone: reply("none"); break; + case EqType::kEqPeak: reply("peak"); break; + case EqType::kEqLshelf: reply("lshelf"); break; + case EqType::kEqHshelf: reply("hshelf"); break; + } + } + + void reply(const TriggerEventType& type) + { + switch (type) { + case TriggerEventType::NoteOff: reply("note_off"); break; + case TriggerEventType::NoteOn: reply("note_on"); break; + case TriggerEventType::CC: reply("cc"); break; + } + } + + // reply2 for pairs (Usually only for float/int ranges) + template ::value>> + void reply2(T value1, T value2) + { + sfizz_arg_t response_args[2]; + if (sizeof(value1) <= 4) { + response_args[0].i = value1; + response_args[1].i = value2; + client.receive(delay, path, "ii", response_args); + } else { + response_args[0].h = value1; + response_args[1].h = value2; + client.receive(delay, path, "hh", response_args); + } + } + + void reply2(float value1, float value2) + { + sfizz_arg_t response_args[2]; + response_args[0].f = value1; + response_args[1].f = value2; + client.receive(delay, path, "ff", response_args); + } + + void set(std::string& target) { target = request_args[0].s; } + void set(float& target, const OpcodeSpec& spec) { target = Opcode::transform(spec, request_args[0].f); } + void set(float& target) { target = request_args[0].f; } + void set(LFOWave& target, const OpcodeSpec& spec) { target = Opcode::transform(spec, request_args[0].i); } + void set(bool& target, const OpcodeSpec& spec) + { + if (sig[0] == 'T') { + target = true; + } else if (sig[0] == 'F') { + target = false; + } else { + target = Opcode::read(spec, request_args[0].s); + } + } + + template + IntegralNotBool set(T& target, const OpcodeSpec& spec) + { + if (sizeof(target) <= 4) + target = Opcode::transform(spec, request_args[0].i); + else + target = Opcode::transform(spec, request_args[0].h); + } + + // Now we have some templated reply overloads that decay into "concrete" reply calls + // but add some logic (e.g. an optional can either reply the value, or send 'N' for + // null) + template + void reply(const absl::optional& value, Args... args) + { + if (!value) { + client.receive<'N'>(delay, path, {}); + return; + } + + reply(*value, std::forward(args)...); + } + + template + void reply(T* value, Args... args) + { + if (!value) { + client.receive<'N'>(delay, path, {}); + return; + } + + reply(*value, std::forward(args)...); + } + + template + void reply(const std::shared_ptr& value) { reply(value.get()); } + + template + void reply(const UncheckedRange& range) { reply2(range.getStart(), range.getEnd()); } + template + void reply(const UncheckedRange& range, const OpcodeSpec& startSpec, const OpcodeSpec& endSpec) + { + reply2(startSpec.denormalizeInput(range.getStart()), endSpec.denormalizeInput(range.getEnd())); + } + + template + IntegralNotBool set(UncheckedRange& range, const OpcodeSpec& startSpec, const OpcodeSpec& endSpec) + { + if (sizeof(range.getStart()) <= 4) { + range.setStart(Opcode::transform(startSpec, request_args[0].i)); + range.setEnd(Opcode::transform(endSpec, request_args[1].i)); + } else { + range.setStart(Opcode::transform(startSpec, request_args[0].h)); + range.setEnd(Opcode::transform(endSpec, request_args[1].h)); + } + } + + template + BoolOrNotIntegral set(UncheckedRange& range, const OpcodeSpec& startSpec, const OpcodeSpec& endSpec) + { + if (sizeof(range.getStart()) <= 4) { + range.setStart(Opcode::transform(startSpec, request_args[0].f)); + range.setEnd(Opcode::transform(endSpec, request_args[1].f)); + } else { + range.setStart(Opcode::transform(startSpec, request_args[0].d)); + range.setEnd(Opcode::transform(endSpec, request_args[1].d)); + } + } + + template + IntegralNotBool set(UncheckedRange& range) + { + if (sizeof(range.getStart()) <= 4) { + range.setStart(request_args[0].i); + range.setEnd(request_args[1].i); + } else { + range.setStart(request_args[0].h); + range.setEnd(request_args[1].h); + } + } + + template + BoolOrNotIntegral set(UncheckedRange& range) + { + if (sizeof(range.getStart()) <= 4) { + range.setStart(request_args[0].f); + range.setEnd(request_args[1].f); + } else { + range.setStart(request_args[0].d); + range.setEnd(request_args[1].d); + } + } + + template + void reply(const absl::optional& value, T&& def) { reply(value.value_or(def)); } + + template + void set(absl::optional& member, Args&&...args) + { + if (sig[0] == 'N') { + member.reset(); + } else if (member.has_value()) { + set(*member, std::forward(args)...); + } else { + set(member.emplace(), std::forward(args)...); + } + } + + template + void reply(const ModifierCurvePair& modCurve, ModParam which, Args&&... args) + { + switch (which) { + case ModParam::Curve: reply(modCurve.curve); break; + default: reply(modCurve.modifier, std::forward(args)...); break; + } + } + + template + void set(ModifierCurvePair& modCurve, ModParam which, Args&&... args) + { + switch (which) { + case ModParam::Curve: modCurve.curve = request_args[0].i; break; + default: set(modCurve.modifier, args...); break; + } + } + + template + void reply(const ModKey::Parameters& params, ModParam which, Args&&... args) + { + switch (which) { + case ModParam::Depth: break; + case ModParam::Curve: reply(params.curve); break; + case ModParam::Smooth: reply(params.smooth); break; + case ModParam::Step: reply(params.step, std::forward(args)...); break; + } + } + + template + void reply(const CCMap& map, Args... args) { reply(map, true, args...); } + + template + void reply(const CCMap& map, bool&& useDefault, Args... args) + { + if (useDefault) + reply(map.getWithDefault(indices.back()), args...); + else + reply(map.get(indices.back()), args...); + } + + template + void set(const CCMap& map, Args... args) + { + set(map[indices.back()], args...); + } + + template + void reply(const EGDescription& eg, T EGDescription::*&& member, Args&&... args) + { + reply(eg.*member, std::forward(args)...); + } + + template + void set(EGDescription& eg, T EGDescription::*&& member, Args&&... args) + { + set(eg.*member, std::forward(args)...); + } + + template + void set(CCMap& map, Args&&... args) { set(map[indices.back()], std::forward(args)...); } + + // Below are all methods that will fetch various data structure elements (regions, + // egs, ...), check that they exist, and call an actual "concrete" reply + // implementation. They use pointer-to-member variables, which allow the compiler to + // dispatch to the correct logic. For example, if the pointer-to-member is + // `Region::*`, we aim at sending the value of a member of the Region + // struct. The first thing is to read the index of the region and fetch it from the + // sfizz data structure using `getRegion(...)`, and then apply the pointer-to-member + // to this particular region to send the value. + + // Adding new dispatching overloads seemed simple enough to this point, although I + // haven't found a nice way to the same with pointer-to-member function. + // TODO: could do something with the c++14 compatible `invoke` maybe? + + template + void set(T M::*member, Args&&... args) + { + dispatch( + static_cast(&MessagingHelper::set), + member, + std::forward(args)...); + } + + // MSVC require this because it finds T M::* and T Voice::* ambiguous in the overload + // resolution of `reply`, so we "disable" the reply/set dispatching for Voice and + // TriggerEvent since they're read-only components. + template + using DispatchedType = typename std::enable_if< + !std::is_same::value && !std::is_same::value, void>::type; + + template + DispatchedType reply(T M::*member, Args&&... args) + { + dispatch( + static_cast(&MessagingHelper::reply), + member, + std::forward(args)...); + } + + + template + void dispatch(F&& f, T FilterDescription::*member, Args&&... args) + { + if (auto region = getRegion()) + if (auto filter = getFilter(*region)) + inv::invoke(std::forward(f), this, (*filter).*member, std::forward(args)...); + } + + template + void dispatch(F&& f, T SynthConfig::*member, Args&&... args) + { + inv::invoke(std::forward(f), this, (impl.resources_.getSynthConfig()).*member, std::forward(args)...); + } + + template + void dispatch(F&& f, T LFODescription::Sub::*member, Args&&... args) + { + if (auto region = getRegion()) + if (auto lfo = getLFO(*region)) + if (auto sub = getLFOSub(*lfo)) + inv::invoke(std::forward(f), this, (*sub).*member, std::forward(args)...); + } + + template + void dispatch(F&& f, T Region::*member, Args&&... args) + { + if (auto region = getRegion()) + inv::invoke(std::forward(f), this, (*region).*member, std::forward(args)...); + } + + template + void dispatch(F&& f, T EQDescription::*member, Args&&... args) + { + if (auto region = getRegion()) + if (auto eq = getEQ(*region)) + inv::invoke(std::forward(f), this, (*eq).*member, std::forward(args)...); + } + + template + void dispatch(F&& f, T LFODescription::*member, Args&&... args) + { + if (auto region = getRegion()) + if (auto lfo = getLFO(*region)) + inv::invoke(std::forward(f), this, (*lfo).*member, std::forward(args)...); + } + + template + void dispatch(F&& f, T FlexEGPoint::*member, Args&&... args) + { + if (auto region = getRegion()) + if (auto eg = getEG(*region)) + if (auto point = getEGPoint(*eg)) + inv::invoke(std::forward(f), this, (*point).*member, std::forward(args)...); + } + + // No need to dispatch for voices, they are read-only for now + + template + void reply(T TriggerEvent::*member, Args&&... args) + { + if (auto voice = getVoice()) + reply((*voice).getTriggerEvent().*member, std::forward(args)...); + } + + template + void reply(T Voice::*member, Args&&... args) + { + if (auto voice = getVoice()) { + reply((*voice.*member)(std::forward(args)...)); // Voice only has callables + } + } + + template + void reply(ModId id, ModParam param, Args&&... args) + { + if (auto region = getRegion()) { + int cc = static_cast(indices.back()); + switch (id) { + case ModId::FilCutoff: + case ModId::FilGain: + switch (param) { + case ModParam::Depth: reply(region->ccModDepth(cc, id, indices[1]), std::forward(args)...); + break; + default: + reply(region->ccModParameters(cc, id, indices[1]), param, std::forward(args)...); + break; + } + break; + default: + switch (param) { + case ModParam::Depth: reply(region->ccModDepth(cc, id), std::forward(args)...); + break; + default: + reply(region->ccModParameters(cc, id), param, std::forward(args)...); + break; + } + } + } + } + + // Validate and fetch elements from the sfizz data structures. By default, we kind of + // assume that regions/voices will be the first index, CCs will be the last, and + // EQ/Filter/.. will be in-between. + Region* getRegion(absl::optional index = {}) + { + const auto idx = index.value_or(indices[0]); + if (idx >= impl.layers_.size()) + return {}; + + Layer& layer = *impl.layers_[idx]; + return &layer.getRegion(); + } + + FilterDescription* getFilter(Region& region, absl::optional index = {}) + { + const auto idx = index.value_or(indices[1]); + if (region.filters.size() <= idx) + return {}; + + return ®ion.filters[idx]; + } + + EQDescription* getEQ(Region& region, absl::optional index = {}) + { + const auto idx = index.value_or(indices[1]); + if (region.equalizers.size() <= idx) + return {}; + + return ®ion.equalizers[idx]; + } + + LFODescription* getLFO(Region& region, absl::optional index = {}) + { + const auto idx = index.value_or(indices[1]); + if (region.lfos.size() <= idx) + return {}; + + return ®ion.lfos[idx]; + } + + LFODescription::Sub* getLFOSub(LFODescription& lfo, absl::optional index = {}) + { + if (indices.size() == 2) { + if (lfo.sub.empty()) + return {}; + else + return &lfo.sub.front(); + } + + const auto idx = index.value_or(indices[2]); + if (lfo.sub.size() <= idx) + return {}; + + return &lfo.sub[idx]; + } + + FlexEGDescription* getEG(Region& region, absl::optional index = {}) + { + const auto idx = index.value_or(indices[1]); + if (region.flexEGs.size() <= idx) + return {}; + + return ®ion.flexEGs[idx]; + } + + FlexEGPoint* getEGPoint(FlexEGDescription& desc, absl::optional index = {}) + { + const auto idx = index.value_or(indices[2]) + 1; + if (desc.points.size() <= idx) + return {}; + + return &desc.points[idx]; + } + + Voice* getVoice(absl::optional index = {}) + { + const auto idx = index.value_or(indices[0]); + if (static_cast(idx) >= impl.numVoices_) + return {}; + + auto& voice = impl.voiceManager_[idx]; + if (voice.isFree()) + return {}; + + return &voice; + } + + // Helpers to get and check the values of the indices + template + absl::optional index(int i) + { + if (i <= ssize(indices)) + return static_cast(indices[i]); + + return absl::nullopt; + } + + absl::optional sindex(int i) { return index(i); } + + absl::optional checkCC(int i = 0) + { + auto cc = index(i); + return cc <= config::numCCs ? cc : absl::nullopt; + } + + absl::optional checkNote(int i = 0) + { + auto note = sindex(i); + return note <= 127 ? note : absl::nullopt; + } + +private: + Client& client; + std::vector indices; + Synth::Impl& impl; + int delay; + const char* path; + const char* sig; + const sfizz_arg_t* request_args; +}; + +} diff --git a/src/sfizz/modulations/ModMatrix.cpp b/src/sfizz/modulations/ModMatrix.cpp index 98a92c866..54e7a0131 100644 --- a/src/sfizz/modulations/ModMatrix.cpp +++ b/src/sfizz/modulations/ModMatrix.cpp @@ -13,6 +13,7 @@ #include "SIMDHelpers.h" #include "utility/Debug.h" #include +#include #include #include #include diff --git a/tests/ParsingT.cpp b/tests/ParsingT.cpp index 36f01dd4a..6e3877641 100644 --- a/tests/ParsingT.cpp +++ b/tests/ParsingT.cpp @@ -10,6 +10,7 @@ #include #include "catch2/catch.hpp" #include "absl/strings/string_view.h" +#include "absl/strings/str_cat.h" using namespace Catch::literals; struct ParsingMocker: sfz::Parser::Listener diff --git a/tests/RegionValuesSetT.cpp b/tests/RegionValuesSetT.cpp index e3af3497a..c565dde77 100644 --- a/tests/RegionValuesSetT.cpp +++ b/tests/RegionValuesSetT.cpp @@ -4,119 +4,509 @@ // 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 "TestHelpers.h" -#include "sfizz/Synth.h" +#include "SynthDiscussion.h" +#include "SfzHelpers.h" #include "catch2/catch.hpp" -#include -#include -#include + using namespace Catch::literals; using namespace sfz; +using namespace sfz::literals; +using namespace std::literals; -TEST_CASE("[Set values] Pitch keycenter") -{ - Synth synth; - std::vector messageList; - Client client(&messageList); - client.setReceiveCallback(&simpleMessageReceiver); - AudioBuffer buffer { 2, 256 }; - - synth.loadSfzString(fs::current_path() / "tests/TestFiles/values_set.sfz", R"( - sample=*sine pitch_keycenter=48 - )"); - synth.dispatchMessage(client, 0, "/region0/pitch_keycenter", "", nullptr); - - // Update value - sfizz_arg_t args; - args.i = 60; - synth.dispatchMessage(client, 1, "/region0/pitch_keycenter", "i", &args); - synth.renderBlock(buffer); - - synth.dispatchMessage(client, 0, "/region0/pitch_keycenter", "", nullptr); - std::vector expected { - "/region0/pitch_keycenter,i : { 48 }", - "/region0/pitch_keycenter,i : { 60 }", - }; - REQUIRE(messageList == expected); -} +using OSC = OSCValueLess; -TEST_CASE("[Set values] LFO wave") +TEST_CASE("Set values", "[parsing][OSC]") { - Synth synth; - std::vector messageList; - Client client(&messageList); - client.setReceiveCallback(&simpleMessageReceiver); - AudioBuffer buffer { 2, 256 }; - - synth.loadSfzString(fs::current_path() / "tests/TestFiles/values_set.sfz", R"( - sample=*sine lfo1_wave=5 - )"); - synth.dispatchMessage(client, 0, "/region0/lfo0/wave", "", nullptr); - - // Update value - sfizz_arg_t args; - args.i = 2; - synth.dispatchMessage(client, 1, "/region0/lfo0/wave", "i", &args); - synth.renderBlock(buffer); - - synth.dispatchMessage(client, 0, "/region0/lfo0/wave", "", nullptr); - std::vector expected { - "/region0/lfo0/wave,i : { 5 }", - "/region0/lfo0/wave,i : { 2 }", - }; - REQUIRE(messageList == expected); -} + SynthDiscussion d; -TEST_CASE("[Set values] Filter type") -{ - Synth synth; - std::vector messageList; - Client client(&messageList); - client.setReceiveCallback(&simpleMessageReceiver); - AudioBuffer buffer { 2, 256 }; - - synth.loadSfzString(fs::current_path() / "tests/TestFiles/values_set.sfz", R"( - sample=*sine fil2_type=lpf_1p - )"); - synth.dispatchMessage(client, 0, "/region0/filter1/type", "", nullptr); - - // Update value - sfizz_arg_t args; - args.s = "hpf_2p"; - synth.dispatchMessage(client, 1, "/region0/filter1/type", "s", &args); - synth.renderBlock(buffer); - - synth.dispatchMessage(client, 0, "/region0/filter1/type", "", nullptr); - std::vector expected { - "/region0/filter1/type,s : { lpf_1p }", - "/region0/filter1/type,s : { hpf_2p }", - }; - REQUIRE(messageList == expected); -} + SECTION("Pitch keycenter") { + d.load(R"( sample=*sine pitch_keycenter=48 )"); + REQUIRE( d.read("/region0/pitch_keycenter") == 48); + REQUIRE( d.sendAndRead("/region0/pitch_keycenter", 60) == 60); + } -TEST_CASE("[Set values] Loop mode") -{ - Synth synth; - std::vector messageList; - Client client(&messageList); - client.setReceiveCallback(&simpleMessageReceiver); - AudioBuffer buffer { 2, 256 }; - - synth.loadSfzString(fs::current_path() / "tests/TestFiles/values_set.sfz", R"( - sample=looped_flute.wav - )"); - synth.dispatchMessage(client, 0, "/region0/loop_mode", "", nullptr); - - // Update value - sfizz_arg_t args; - args.s = "one_shot"; - synth.dispatchMessage(client, 1, "/region0/loop_mode", "s", &args); - synth.renderBlock(buffer); - - synth.dispatchMessage(client, 0, "/region0/loop_mode", "", nullptr); - std::vector expected { - "/region0/loop_mode,s : { loop_continuous }", - "/region0/loop_mode,s : { one_shot }", - }; - REQUIRE(messageList == expected); + SECTION("LFO Wave") { + d.load(R"( sample=*sine lfo1_wave=5 lfo1_wave2=4 )"); + REQUIRE( d.read("/region0/lfo0/wave") == 5); + REQUIRE( d.read("/region0/lfo0/wave1") == 4); + REQUIRE( d.sendAndRead("/region0/lfo0/wave", 3) == 3); + REQUIRE( d.sendAndRead("/region0/lfo0/wave1", 2) == 2); + } + + SECTION("Loop mode") { + d.load(R"( sample=looped_flute.wav )"); + REQUIRE( d.read("/region0/loop_mode") == "loop_continuous"); + REQUIRE( d.sendAndRead("/region0/loop_mode", "one_shot") == "one_shot"); + } + + SECTION("Sample quality") { + d.load(R"( sample=kick.wav )"); + REQUIRE( d.read("/sample_quality") == 2); + REQUIRE( d.read("/oscillator_quality") == 1); + REQUIRE( d.read("/freewheeling_sample_quality") == 10); + REQUIRE( d.read("/freewheeling_oscillator_quality") == 3); + REQUIRE( d.sendAndRead("/sample_quality", 3) == 3); + REQUIRE( d.sendAndRead("/oscillator_quality", 2) == 2); + REQUIRE( d.sendAndRead("/freewheeling_sample_quality", 6) == 6); + REQUIRE( d.sendAndRead("/freewheeling_oscillator_quality", 2) == 2); + } + + SECTION("Sustain cancels release") { + d.load(R"( sample=kick.wav )"); + REQUIRE( d.read("/sustain_cancels_release") == OSC::False ); + d.send("/sustain_cancels_release", true); + REQUIRE( d.read("/sustain_cancels_release") == OSC::True ); + d.send("/sustain_cancels_release", false); + REQUIRE( d.read("/sustain_cancels_release") == OSC::False ); + d.send("/sustain_cancels_release", "on"s); + REQUIRE( d.read("/sustain_cancels_release") == OSC::True ); + } + + SECTION("Delay") { + d.load(R"( sample=kick.wav )"); + REQUIRE( d.sendAndRead("/region0/delay", 10.0f) == 10.0f); + REQUIRE( d.sendAndRead("/region0/delay_random", 10.0f) == 10.0f); + REQUIRE( d.sendAndRead("/region0/delay_cc1", 10.0f) == 10.0f); + } + + SECTION("Offset") { + d.load(R"( sample=kick.wav )"); + REQUIRE( d.sendAndRead("/region0/offset", 10) == 10); + REQUIRE( d.sendAndRead("/region0/offset_random", 10) == 10); + REQUIRE( d.sendAndRead("/region0/offset_cc1", 10) == 10); + } + + SECTION("End") { + d.load(R"( sample=kick.wav )"); + REQUIRE( d.sendAndRead("/region0/end", 10) == 10); + REQUIRE( d.sendAndRead("/region0/end_cc1", 10) == 10); + } + + SECTION("Count") { + d.load(R"( sample=kick.wav )"); + REQUIRE( d.sendAndRead("/region0/count", 3) == 3); + } + + SECTION("Loop range") { + d.load(R"( sample=kick.wav )"); + std::vector v {13, 2000}; + REQUIRE( d.sendAndReadAll("/region0/loop_range", v) == v); + REQUIRE( d.sendAndRead("/region0/loop_start_cc1", 10) == 10); + REQUIRE( d.sendAndRead("/region0/loop_end_cc1", 1000) == 1000); + } + + SECTION("Loop count") { + d.load(R"( sample=kick.wav )"); + REQUIRE( d.sendAndRead("/region0/loop_count", 3) == 3); + d.send("/region0/loop_count", nullptr); + REQUIRE( d.read("/region0/loop_count") == OSC::None); + } + + SECTION("Output") { + d.load(R"( sample=kick.wav )"); + REQUIRE( d.sendAndRead("/region0/output", 3) == 3); + } + + SECTION("Off by") { + d.load(R"( sample=kick.wav )"); + REQUIRE( d.read("/region0/off_by") == OSC::None); + REQUIRE( d.sendAndRead("/region0/off_by", 2) == 2); + d.send("/region0/off_by", nullptr); + REQUIRE( d.read("/region0/off_by") == OSC::None); + } + + SECTION("Off mode") { + d.load(R"( sample=kick.wav )"); + REQUIRE( d.sendAndRead("/region0/off_mode", "time") =="time"); + REQUIRE( d.sendAndRead("/region0/off_mode", "fast") =="fast"); + } + + SECTION("Key range") { + d.load(R"( sample=kick.wav )"); + std::vector v {5, 67}; + REQUIRE( d.sendAndReadAll("/region0/key_range", v) == v); + } + + SECTION("Off time") { + d.load(R"( sample=kick.wav )"); + REQUIRE( d.sendAndRead("/region0/off_time", 0.1f) == 0.1f); + } + + SECTION("Velocity range") { + d.load(R"( sample=kick.wav )"); + std::vector v {5_norm, 67_norm}; + REQUIRE( d.sendAndReadAll("/region0/vel_range", v) == v); + } + + SECTION("Bend range") { + d.load(R"( sample=kick.wav )"); + std::vector v {5_bend, 67_bend}; + REQUIRE( d.sendAndReadAll("/region0/bend_range", v) == v); + } + + SECTION("Program range") { + d.load(R"( sample=kick.wav )"); + std::vector v {2, 10}; + REQUIRE( d.sendAndReadAll("/region0/program_range", v) == v); + } + + SECTION("CC range") { + d.load(R"( sample=kick.wav )"); + std::vector v {5_norm, 67_norm}; + REQUIRE( d.sendAndReadAll("/region0/cc_range2", v) == v); + } + + SECTION("Last keyswitch") { + d.load(R"( sample=kick.wav )"); + REQUIRE( d.sendAndRead("/region0/sw_last", 24) == 24); + std::vector v {10, 15}; + REQUIRE( d.sendAndReadAll("/region0/sw_last", v) == v); + } + + SECTION("Keyswitch label") { + d.load(R"( sample=kick.wav )"); + REQUIRE( d.sendAndRead("/region0/sw_label", "hello") == "hello"); + } + + SECTION("Keyswitch up") { + d.load(R"( sample=kick.wav )"); + REQUIRE( d.sendAndRead("/region0/sw_up", 12) == 12); + d.send("/region0/sw_up", "c4"s); + REQUIRE( d.read("/region0/sw_up") == 60); + } + + SECTION("Keyswitch down") { + d.load(R"( sample=kick.wav )"); + REQUIRE( d.sendAndRead("/region0/sw_down", 12) == 12); + d.send("/region0/sw_down", "c4"s); + REQUIRE( d.read("/region0/sw_down") == 60); + } + + SECTION("Keyswitch down") { + d.load(R"( sample=kick.wav )"); + REQUIRE( d.sendAndRead("/region0/sw_previous", 12) == 12); + d.send("/region0/sw_previous", "c4"s); + REQUIRE( d.read("/region0/sw_previous") == 60); + } + + SECTION("Velocity override") { + d.load(R"( sample=kick.wav )"); + REQUIRE( d.sendAndRead("/region0/sw_vel", "previous") == "previous"); + } + + SECTION("Channel aftertouch range") { + d.load(R"( sample=kick.wav )"); + std::vector v {5_norm, 67_norm}; + REQUIRE( d.sendAndReadAll("/region0/chanaft_range", v) == v); + } + + SECTION("Poly aftertouch range") { + d.load(R"( sample=kick.wav )"); + std::vector v {5_norm, 67_norm}; + REQUIRE( d.sendAndReadAll("/region0/polyaft_range", v) == v); + } + + SECTION("BPM range") { + d.load(R"( sample=kick.wav )"); + std::vector v {5, 67}; + REQUIRE( d.sendAndReadAll("/region0/bpm_range", v) == v); + } + + SECTION("Rand range") { + d.load(R"( sample=kick.wav )"); + std::vector v {5_norm, 67_norm}; + REQUIRE( d.sendAndReadAll("/region0/rand_range", v) == v); + } + + SECTION("Sequences") { + d.load(R"( sample=kick.wav )"); + REQUIRE( d.sendAndRead("/region0/seq_length", 2) == 2); + REQUIRE( d.sendAndRead("/region0/seq_position", 2) == 2); + } + + SECTION("Trigger") { + d.load(R"( sample=kick.wav )"); + REQUIRE( d.sendAndRead("/region0/trigger", "release") == "release"); + } + + SECTION("Start CC range") { + d.load(R"( sample=kick.wav )"); + std::vector v {5_norm, 67_norm}; + REQUIRE( d.sendAndReadAll("/region0/start_cc_range2", v) == v); + } + + SECTION("Volume etc") { + d.load(R"( sample=kick.wav )"); + REQUIRE( d.sendAndRead("/region0/volume", 10.0f) == 10.0f); + REQUIRE( d.sendAndRead("/region0/pan", 10.0f) == 10.0f); + REQUIRE( d.sendAndRead("/region0/width", 10.0f) == 10.0f); + REQUIRE( d.sendAndRead("/region0/position", 10.0f) == 10.0f); + REQUIRE( d.sendAndRead("/region0/amplitude", 10.0f) == 10.0f); + } + + SECTION("Amp key something") { + d.load(R"( sample=kick.wav )"); + REQUIRE( d.sendAndRead("/region0/amp_keycenter", 48) == 48); + REQUIRE( d.sendAndRead("/region0/amp_keytrack", 10.0f) == 10.0f); + REQUIRE( d.sendAndRead("/region0/amp_veltrack", 10.0f) == 10.0f); + REQUIRE( d.sendAndRead("/region0/amp_veltrack_cc3", 10.0f) == 10.0f); + REQUIRE( d.sendAndRead("/region0/amp_veltrack_curvecc3", 2) == 2); + } + + SECTION("Amp random") { + d.load(R"( sample=kick.wav )"); + REQUIRE( d.sendAndRead("/region0/amp_random", 10.0f) == 10.0f); + } + + SECTION("Crossfade key range") { + d.load(R"( sample=kick.wav )"); + std::vector v {5, 67}; + REQUIRE( d.sendAndReadAll("/region0/xfin_key_range", v) == v); + REQUIRE( d.sendAndReadAll("/region0/xfout_key_range", v) == v); + } + + SECTION("Other crossfade range") { + d.load(R"( sample=kick.wav )"); + std::vector v {5_norm, 67_norm}; + REQUIRE( d.sendAndReadAll("/region0/xfin_vel_range", v) == v); + REQUIRE( d.sendAndReadAll("/region0/xfout_vel_range", v) == v); + REQUIRE( d.sendAndReadAll("/region0/xfin_cc_range3", v) == v); + REQUIRE( d.sendAndReadAll("/region0/xfout_cc_range3", v) == v); + } + + SECTION("Crossfade curves") { + d.load(R"( sample=kick.wav )"); + REQUIRE( d.sendAndRead("/region0/xf_keycurve", "power") == "power"); + REQUIRE( d.sendAndRead("/region0/xf_velcurve", "power") == "power"); + REQUIRE( d.sendAndRead("/region0/xf_cccurve", "power") == "power"); + } + + SECTION("Global amps and volumes curves") { + d.load(R"( sample=kick.wav )"); + REQUIRE( d.sendAndRead("/region0/global_volume", 10.0f) == 10.0f); + REQUIRE( d.sendAndRead("/region0/master_volume", 10.0f) == 10.0f); + REQUIRE( d.sendAndRead("/region0/group_volume", 10.0f) == 10.0f); + REQUIRE( d.sendAndRead("/region0/global_amplitude", 10.0f) == 10.0f); + REQUIRE( d.sendAndRead("/region0/master_amplitude", 10.0f) == 10.0f); + REQUIRE( d.sendAndRead("/region0/group_amplitude", 10.0f) == 10.0f); + } + + SECTION("Pitch and transpose") { + d.load(R"( sample=kick.wav )"); + REQUIRE( d.sendAndRead("/region0/pitch", 10.0f) == 10.0f); + REQUIRE( d.sendAndRead("/region0/transpose", 10.0f) == 10.0f); + REQUIRE( d.sendAndRead("/region0/pitch_random", 10.0f) == 10.0f); + } + + SECTION("Pitch key something") { + d.load(R"( sample=kick.wav )"); + REQUIRE( d.sendAndRead("/region0/pitch_keycenter", 48) == 48); + REQUIRE( d.sendAndRead("/region0/pitch_keytrack", 10.0f) == 10.0f); + REQUIRE( d.sendAndRead("/region0/pitch_veltrack", 10.0f) == 10.0f); + REQUIRE( d.sendAndRead("/region0/pitch_veltrack_cc3", 10.0f) == 10.0f); + REQUIRE( d.sendAndRead("/region0/pitch_veltrack_curvecc3", 2) == 2); + } + + SECTION("Bends") { + d.load(R"( sample=kick.wav )"); + REQUIRE( d.sendAndRead("/region0/bend_up", 10.0f) == 10.0f); + REQUIRE( d.sendAndRead("/region0/bend_down", 10.0f) == 10.0f); + REQUIRE( d.sendAndRead("/region0/bend_step", 10.0f) == 10.0f); + REQUIRE( d.sendAndRead("/region0/bend_smooth", 10) == 10); + } + + SECTION("Ampeg") { + d.load(R"( sample=kick.wav )"); + REQUIRE( d.sendAndRead("/region0/ampeg_attack", 1.0f) == 1.0f); + REQUIRE( d.sendAndRead("/region0/ampeg_delay", 2.0f) == 2.0f); + REQUIRE( d.sendAndRead("/region0/ampeg_decay", 2.0f) == 2.0f); + REQUIRE( d.sendAndRead("/region0/ampeg_hold", 2.0f) == 2.0f); + REQUIRE( d.sendAndRead("/region0/ampeg_release", 2.0f) == 2.0f); + REQUIRE( d.sendAndRead("/region0/ampeg_start", 2.0f) == 2.0f); + REQUIRE( d.sendAndRead("/region0/ampeg_sustain", 2.0f) == 2.0f); + REQUIRE( d.sendAndRead("/region0/ampeg_depth", 2.0f) == 2.0f); + REQUIRE( d.sendAndRead("/region0/ampeg_attack_cc1", 1.0f) == 1.0f); + REQUIRE( d.sendAndRead("/region0/ampeg_decay_cc2", 1.0f) == 1.0f); + REQUIRE( d.sendAndRead("/region0/ampeg_delay_cc3", 1.0f) == 1.0f); + REQUIRE( d.sendAndRead("/region0/ampeg_hold_cc4", 1.0f) == 1.0f); + REQUIRE( d.sendAndRead("/region0/ampeg_release_cc5", 1.0f) == 1.0f); + REQUIRE( d.sendAndRead("/region0/ampeg_sustain_cc6", 1.0f) == 1.0f); + REQUIRE( d.sendAndRead("/region0/ampeg_start_cc7", 1.0f) == 1.0f); + REQUIRE( d.sendAndRead("/region0/ampeg_attack_curvecc1", 2) == 2); + REQUIRE( d.sendAndRead("/region0/ampeg_decay_curvecc2", 2) == 2); + REQUIRE( d.sendAndRead("/region0/ampeg_delay_curvecc3", 2) == 2); + REQUIRE( d.sendAndRead("/region0/ampeg_hold_curvecc4", 2) == 2); + REQUIRE( d.sendAndRead("/region0/ampeg_release_curvecc5", 2) == 2); + REQUIRE( d.sendAndRead("/region0/ampeg_sustain_curvecc6", 2) == 2); + REQUIRE( d.sendAndRead("/region0/ampeg_start_curvecc7", 2) == 2); + REQUIRE( d.sendAndRead("/region0/ampeg_vel2attack", 1.0f) == 1.0f); + REQUIRE( d.sendAndRead("/region0/ampeg_vel2delay", 1.0f) == 1.0f); + REQUIRE( d.sendAndRead("/region0/ampeg_vel2decay", 1.0f) == 1.0f); + REQUIRE( d.sendAndRead("/region0/ampeg_vel2hold", 1.0f) == 1.0f); + REQUIRE( d.sendAndRead("/region0/ampeg_vel2release", 1.0f) == 1.0f); + REQUIRE( d.sendAndRead("/region0/ampeg_vel2sustain", 1.0f) == 1.0f); + REQUIRE( d.sendAndRead("/region0/ampeg_vel2depth", 1.0f) == 1.0f); + REQUIRE( d.read("/region0/ampeg_dynamic") == OSC::False); + d.send("/region0/ampeg_dynamic", true); + REQUIRE( d.read("/region0/ampeg_dynamic") == OSC::True); + d.send("/region0/ampeg_dynamic", "off"s); + REQUIRE( d.read("/region0/ampeg_dynamic") == OSC::False); + } + + SECTION("Fileg") { + d.load(R"( sample=kick.wav )"); + REQUIRE( d.sendAndRead("/region0/fileg_attack", 1.0f) == 1.0f); + REQUIRE( d.sendAndRead("/region0/fileg_delay", 2.0f) == 2.0f); + REQUIRE( d.sendAndRead("/region0/fileg_decay", 2.0f) == 2.0f); + REQUIRE( d.sendAndRead("/region0/fileg_hold", 2.0f) == 2.0f); + REQUIRE( d.sendAndRead("/region0/fileg_release", 2.0f) == 2.0f); + REQUIRE( d.sendAndRead("/region0/fileg_start", 2.0f) == 2.0f); + REQUIRE( d.sendAndRead("/region0/fileg_sustain", 2.0f) == 2.0f); + REQUIRE( d.sendAndRead("/region0/fileg_depth", 2.0f) == 2.0f); + REQUIRE( d.sendAndRead("/region0/fileg_attack_cc1", 1.0f) == 1.0f); + REQUIRE( d.sendAndRead("/region0/fileg_decay_cc2", 1.0f) == 1.0f); + REQUIRE( d.sendAndRead("/region0/fileg_delay_cc3", 1.0f) == 1.0f); + REQUIRE( d.sendAndRead("/region0/fileg_hold_cc4", 1.0f) == 1.0f); + REQUIRE( d.sendAndRead("/region0/fileg_release_cc5", 1.0f) == 1.0f); + REQUIRE( d.sendAndRead("/region0/fileg_sustain_cc6", 1.0f) == 1.0f); + REQUIRE( d.sendAndRead("/region0/fileg_start_cc7", 1.0f) == 1.0f); + REQUIRE( d.sendAndRead("/region0/fileg_attack_curvecc1", 2) == 2); + REQUIRE( d.sendAndRead("/region0/fileg_decay_curvecc2", 2) == 2); + REQUIRE( d.sendAndRead("/region0/fileg_delay_curvecc3", 2) == 2); + REQUIRE( d.sendAndRead("/region0/fileg_hold_curvecc4", 2) == 2); + REQUIRE( d.sendAndRead("/region0/fileg_release_curvecc5", 2) == 2); + REQUIRE( d.sendAndRead("/region0/fileg_sustain_curvecc6", 2) == 2); + REQUIRE( d.sendAndRead("/region0/fileg_start_curvecc7", 2) == 2); + REQUIRE( d.read("/region0/fileg_dynamic") == OSC::False); + d.send("/region0/fileg_dynamic", true); + REQUIRE( d.read("/region0/fileg_dynamic") == OSC::True); + d.send("/region0/fileg_dynamic", "off"s); + REQUIRE( d.read("/region0/fileg_dynamic") == OSC::False); + } + + SECTION("PitchEG") { + d.load(R"( sample=kick.wav )"); + REQUIRE( d.sendAndRead("/region0/pitcheg_attack", 1.0f) == 1.0f); + REQUIRE( d.sendAndRead("/region0/pitcheg_delay", 2.0f) == 2.0f); + REQUIRE( d.sendAndRead("/region0/pitcheg_decay", 2.0f) == 2.0f); + REQUIRE( d.sendAndRead("/region0/pitcheg_hold", 2.0f) == 2.0f); + REQUIRE( d.sendAndRead("/region0/pitcheg_release", 2.0f) == 2.0f); + REQUIRE( d.sendAndRead("/region0/pitcheg_start", 2.0f) == 2.0f); + REQUIRE( d.sendAndRead("/region0/pitcheg_sustain", 2.0f) == 2.0f); + REQUIRE( d.sendAndRead("/region0/pitcheg_depth", 2.0f) == 2.0f); + REQUIRE( d.sendAndRead("/region0/pitcheg_attack_cc1", 1.0f) == 1.0f); + REQUIRE( d.sendAndRead("/region0/pitcheg_decay_cc2", 1.0f) == 1.0f); + REQUIRE( d.sendAndRead("/region0/pitcheg_delay_cc3", 1.0f) == 1.0f); + REQUIRE( d.sendAndRead("/region0/pitcheg_hold_cc4", 1.0f) == 1.0f); + REQUIRE( d.sendAndRead("/region0/pitcheg_release_cc5", 1.0f) == 1.0f); + REQUIRE( d.sendAndRead("/region0/pitcheg_sustain_cc6", 1.0f) == 1.0f); + REQUIRE( d.sendAndRead("/region0/pitcheg_start_cc7", 1.0f) == 1.0f); + REQUIRE( d.sendAndRead("/region0/pitcheg_attack_curvecc1", 2) == 2); + REQUIRE( d.sendAndRead("/region0/pitcheg_decay_curvecc2", 2) == 2); + REQUIRE( d.sendAndRead("/region0/pitcheg_delay_curvecc3", 2) == 2); + REQUIRE( d.sendAndRead("/region0/pitcheg_hold_curvecc4", 2) == 2); + REQUIRE( d.sendAndRead("/region0/pitcheg_release_curvecc5", 2) == 2); + REQUIRE( d.sendAndRead("/region0/pitcheg_sustain_curvecc6", 2) == 2); + REQUIRE( d.sendAndRead("/region0/pitcheg_start_curvecc7", 2) == 2); + REQUIRE( d.read("/region0/pitcheg_dynamic") == OSC::False); + d.send("/region0/pitcheg_dynamic", true); + REQUIRE( d.read("/region0/pitcheg_dynamic") == OSC::True); + d.send("/region0/pitcheg_dynamic", "off"s); + REQUIRE( d.read("/region0/pitcheg_dynamic") == OSC::False); + } + + SECTION("Note polyphony") { + d.load(R"( sample=kick.wav )"); + REQUIRE( d.sendAndRead("/region0/note_polyphony", 3) == 3); + } + + SECTION("RT dead") { + d.load(R"( sample=kick.wav )"); + REQUIRE( d.read("/region0/rt_dead") == OSC::False ); + d.send("/region0/rt_dead", true); + REQUIRE( d.read("/region0/rt_dead") == OSC::True ); + d.send("/region0/rt_dead", false); + REQUIRE( d.read("/region0/rt_dead") == OSC::False ); + d.send("/region0/rt_dead", "on"s); + REQUIRE( d.read("/region0/rt_dead") == OSC::True ); + } + + SECTION("Sustain/sostenuto") { + d.load(R"( sample=kick.wav )"); + REQUIRE( d.read("/region0/sustain_sw") == OSC::True ); + d.send("/region0/sustain_sw", false); + REQUIRE( d.read("/region0/sustain_sw") == OSC::False ); + d.send("/region0/sustain_sw", true); + REQUIRE( d.read("/region0/sustain_sw") == OSC::True ); + d.send("/region0/sustain_sw", "off"s); + REQUIRE( d.read("/region0/sustain_sw") == OSC::False ); + REQUIRE( d.read("/region0/sostenuto_sw") == OSC::True ); + d.send("/region0/sostenuto_sw", false); + REQUIRE( d.read("/region0/sostenuto_sw") == OSC::False ); + d.send("/region0/sostenuto_sw", true); + REQUIRE( d.read("/region0/sostenuto_sw") == OSC::True ); + d.send("/region0/sostenuto_sw", "off"s); + REQUIRE( d.read("/region0/sostenuto_sw") == OSC::False ); + REQUIRE( d.sendAndRead("/region0/sustain_cc", 23) == 23); + REQUIRE( d.sendAndRead("/region0/sostenuto_cc", 23) == 23); + REQUIRE( d.sendAndRead("/region0/sustain_lo", 0.1f) == 0.1f); + REQUIRE( d.sendAndRead("/region0/sostenuto_lo", 0.1f) == 0.1f); + } + + SECTION("Note selfmask") { + d.load(R"( sample=kick.wav )"); + REQUIRE( d.read("/region0/note_selfmask") == OSC::True ); + d.send("/region0/note_selfmask", "off"s); + REQUIRE( d.read("/region0/note_selfmask") == OSC::False ); + d.send("/region0/note_selfmask", "mask"s); + REQUIRE( d.read("/region0/note_selfmask") == OSC::True ); + } + + SECTION("Oscillator stuff") { + d.load(R"( sample=kick.wav )"); + REQUIRE( d.sendAndRead("/region0/oscillator_phase", 0.1f) == 0.1f); + REQUIRE( d.sendAndRead("/region0/oscillator_quality", 2) == 2); + REQUIRE( d.sendAndRead("/region0/oscillator_mode", 1) == 1); + REQUIRE( d.sendAndRead("/region0/oscillator_multi", 5) == 5); + REQUIRE( d.sendAndRead("/region0/oscillator_detune", 0.2f) == 0.2f); + REQUIRE( d.sendAndRead("/region0/oscillator_mod_depth", 0.2f) == 0.2f); + } + + SECTION("Effect") { + d.load(R"( sample=kick.wav effect1=10)"); + REQUIRE( d.sendAndRead("/region0/effect1", 1.0f) == 1.0f); + } + + SECTION("Filters") { + d.load(R"( sample=kick.wav)"); + REQUIRE(d.read("/region0/add_filter") == 0); + REQUIRE( d.sendAndRead("/region0/filter0/cutoff", 100.0f) == 100.0f); + REQUIRE( d.sendAndRead("/region0/filter0/resonance", 10.0f) == 10.0f); + REQUIRE( d.sendAndRead("/region0/filter0/gain", 4.0f) == 4.0f); + REQUIRE( d.sendAndRead("/region0/filter0/keycenter", 42) == 42); + REQUIRE( d.sendAndRead("/region0/filter0/keytrack", 10.0f) == 10.0f); + REQUIRE( d.sendAndRead("/region0/filter0/veltrack", 10.0f) == 10.0f); + REQUIRE( d.sendAndRead("/region0/filter0/veltrack_cc1", 10.0f) == 10.0f); + REQUIRE( d.sendAndRead("/region0/filter0/veltrack_curvecc2", 3) == 3); + REQUIRE( d.sendAndRead("/region0/filter0/type", "lpf_2p") == "lpf_2p"); + } + + SECTION("EQs") { + d.load(R"( sample=kick.wav)"); + REQUIRE(d.read("/region0/add_eq") == 0); + REQUIRE( d.sendAndRead("/region0/eq0/gain", 10.0f) == 10.0f); + REQUIRE( d.sendAndRead("/region0/eq0/bandwidth", 100.0f) == 100.0f); + REQUIRE( d.sendAndRead("/region0/eq0/frequency", 500.0f) == 500.0f); + REQUIRE( d.sendAndRead("/region0/eq0/vel2freq", 10.0f) == 10.0f); + REQUIRE( d.sendAndRead("/region0/eq0/vel2gain", 10.0f) == 10.0f); + REQUIRE( d.sendAndRead("/region0/eq0/type", "hshelf") == "hshelf"); + } + + SECTION("EGs") { + d.load(R"( sample=kick.wav)"); + REQUIRE(d.read("/region0/add_eg") == 0); + REQUIRE(d.read("/region0/eg0/add_point") == 0); + REQUIRE( d.sendAndRead("/region0/eg0/point0/time", 1.0f) == 1.0f); + REQUIRE( d.sendAndRead("/region0/eg0/point0/level", 0.5f) == 0.5f); + } } diff --git a/tests/RegionValuesT.cpp b/tests/RegionValuesT.cpp index 2e0a09887..b40ff82a9 100644 --- a/tests/RegionValuesT.cpp +++ b/tests/RegionValuesT.cpp @@ -10,349 +10,196 @@ #include #include #include +#include "SynthDiscussion.h" + using namespace Catch::literals; using namespace sfz; +using namespace sfz::literals; +using OSC = OSCValueLess; -TEST_CASE("[Values] Delay") +TEST_CASE("Read values", "[parsing][OSC]") { - Synth synth; - std::vector messageList; - Client client(&messageList); - client.setReceiveCallback(&simpleMessageReceiver); + SynthDiscussion d; - SECTION("Basic") + SECTION("Delay basic") { - synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + d.load(R"( sample=*sine sample=*sine delay=1 sample=*sine delay=-1 sample=*sine delay=1 delay=-1 )"); - synth.dispatchMessage(client, 0, "/region0/delay", "", nullptr); - synth.dispatchMessage(client, 0, "/region1/delay", "", nullptr); - synth.dispatchMessage(client, 0, "/region2/delay", "", nullptr); - synth.dispatchMessage(client, 0, "/region3/delay", "", nullptr); - std::vector expected { - "/region0/delay,f : { 0 }", - "/region1/delay,f : { 1 }", - "/region2/delay,f : { -1 }", - "/region3/delay,f : { -1 }", - }; - REQUIRE(messageList == expected); + REQUIRE(d.read("/region0/delay") == 0); + REQUIRE(d.read("/region1/delay") == 1); + REQUIRE(d.read("/region2/delay") == -1); + REQUIRE(d.read("/region3/delay") == -1); } - SECTION("Random") + SECTION("Delay random") { - synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + d.load(R"( sample=*sine sample=*sine delay_random=1 sample=*sine delay_random=-1 sample=*sine delay_random=1 delay_random=-1 )"); - synth.dispatchMessage(client, 0, "/region0/delay_random", "", nullptr); - synth.dispatchMessage(client, 0, "/region1/delay_random", "", nullptr); - synth.dispatchMessage(client, 0, "/region2/delay_random", "", nullptr); - synth.dispatchMessage(client, 0, "/region3/delay_random", "", nullptr); - std::vector expected { - "/region0/delay_random,f : { 0 }", - "/region1/delay_random,f : { 1 }", - "/region2/delay_random,f : { -1 }", - "/region3/delay_random,f : { -1 }", - }; - REQUIRE(messageList == expected); + REQUIRE(d.read("/region0/delay_random") == 0); + REQUIRE(d.read("/region1/delay_random") == 1); + REQUIRE(d.read("/region2/delay_random") == -1); + REQUIRE(d.read("/region3/delay_random") == -1); } - SECTION("CC") + SECTION("Delay CC") { - synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + d.load(R"( sample=kick.wav sample=kick.wav delay_cc12=1.5 sample=kick.wav delay_cc12=-1.5 sample=kick.wav delay_cc14=3 delay_cc12=2 delay_cc12=-12 )"); - synth.dispatchMessage(client, 0, "/region0/delay_cc12", "", nullptr); - synth.dispatchMessage(client, 0, "/region1/delay_cc12", "", nullptr); - synth.dispatchMessage(client, 0, "/region2/delay_cc12", "", nullptr); - synth.dispatchMessage(client, 0, "/region3/delay_cc14", "", nullptr); - synth.dispatchMessage(client, 0, "/region3/delay_cc12", "", nullptr); - std::vector expected { - "/region0/delay_cc12,f : { 0 }", - "/region1/delay_cc12,f : { 1.5 }", - "/region2/delay_cc12,f : { -1.5 }", - "/region3/delay_cc14,f : { 3 }", - "/region3/delay_cc12,f : { -12 }", - }; - REQUIRE(messageList == expected); + REQUIRE( d.read("/region0/delay_cc12") == 0 ); + REQUIRE( d.read("/region1/delay_cc12") == 1.5 ); + REQUIRE( d.read("/region2/delay_cc12") == -1.5 ); + REQUIRE( d.read("/region3/delay_cc14") == 3 ); + REQUIRE( d.read("/region3/delay_cc12") == -12 ); } -} - -TEST_CASE("[Values] Sample and direction") -{ - Synth synth; - std::vector messageList; - Client client(&messageList); - client.setReceiveCallback(&simpleMessageReceiver); - synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( - sample=*sine - sample=kick.wav - sample=kick.wav direction=reverse - )"); - synth.dispatchMessage(client, 0, "/region0/sample", "", nullptr); - synth.dispatchMessage(client, 0, "/region1/sample", "", nullptr); - synth.dispatchMessage(client, 0, "/region1/direction", "", nullptr); - synth.dispatchMessage(client, 0, "/region2/direction", "", nullptr); - std::vector expected { - "/region0/sample,s : { *sine }", - "/region1/sample,s : { kick.wav }", - "/region1/direction,s : { forward }", - "/region2/direction,s : { reverse }", - }; - REQUIRE(messageList == expected); -} -TEST_CASE("[Values] Offset") -{ - Synth synth; - std::vector messageList; - Client client(&messageList); - client.setReceiveCallback(&simpleMessageReceiver); + SECTION("Sample and direction") + { + d.load(R"( + sample=*sine + sample=kick.wav + sample=kick.wav direction=reverse + )"); + REQUIRE(d.read("/region0/sample") == "*sine"); + REQUIRE(d.read("/region1/sample") == "kick.wav"); + REQUIRE(d.read("/region1/direction") == "forward"); + REQUIRE(d.read("/region2/direction") == "reverse"); + } - SECTION("Basic") + SECTION("Offset basic") { - synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + d.load(R"( sample=kick.wav sample=kick.wav offset=12 sample=kick.wav offset=-1 sample=kick.wav offset=12 offset=-1 )"); - synth.dispatchMessage(client, 0, "/region0/offset", "", nullptr); - synth.dispatchMessage(client, 0, "/region1/offset", "", nullptr); - synth.dispatchMessage(client, 0, "/region2/offset", "", nullptr); - synth.dispatchMessage(client, 0, "/region3/offset", "", nullptr); - std::vector expected { - "/region0/offset,h : { 0 }", - "/region1/offset,h : { 12 }", - "/region2/offset,h : { -1 }", - "/region3/offset,h : { -1 }", - }; - REQUIRE(messageList == expected); + REQUIRE(d.read("/region0/offset") == 0); + REQUIRE(d.read("/region1/offset") == 12); + REQUIRE(d.read("/region2/offset") == -1); + REQUIRE(d.read("/region3/offset") == -1); } - SECTION("Random") + SECTION("Offset random") { - synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + d.load(R"( sample=kick.wav sample=kick.wav offset_random=1 sample=kick.wav offset_random=-1 sample=kick.wav offset_random=1 offset_random=-1 )"); - synth.dispatchMessage(client, 0, "/region0/offset_random", "", nullptr); - synth.dispatchMessage(client, 0, "/region1/offset_random", "", nullptr); - synth.dispatchMessage(client, 0, "/region2/offset_random", "", nullptr); - synth.dispatchMessage(client, 0, "/region3/offset_random", "", nullptr); - std::vector expected { - "/region0/offset_random,h : { 0 }", - "/region1/offset_random,h : { 1 }", - "/region2/offset_random,h : { -1 }", - "/region3/offset_random,h : { -1 }", - }; - REQUIRE(messageList == expected); + REQUIRE(d.read("/region0/offset_random") == 0); + REQUIRE(d.read("/region1/offset_random") == 1); + REQUIRE(d.read("/region2/offset_random") == -1); + REQUIRE(d.read("/region3/offset_random") == -1); } - SECTION("CC") + SECTION("Offset CC") { - synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + d.load(R"( sample=kick.wav sample=kick.wav offset_cc12=12 sample=kick.wav offset_cc12=-12 sample=kick.wav offset_cc14=14 offset_cc12=12 offset_cc12=-12 )"); - synth.dispatchMessage(client, 0, "/region0/offset_cc12", "", nullptr); - synth.dispatchMessage(client, 0, "/region1/offset_cc12", "", nullptr); - synth.dispatchMessage(client, 0, "/region2/offset_cc12", "", nullptr); - synth.dispatchMessage(client, 0, "/region3/offset_cc14", "", nullptr); - synth.dispatchMessage(client, 0, "/region3/offset_cc12", "", nullptr); - std::vector expected { - "/region0/offset_cc12,h : { 0 }", - "/region1/offset_cc12,h : { 12 }", - "/region2/offset_cc12,h : { -12 }", - "/region3/offset_cc14,h : { 14 }", - "/region3/offset_cc12,h : { -12 }", - }; - REQUIRE(messageList == expected); + REQUIRE(d.read("/region0/offset_cc12") == 0); + REQUIRE(d.read("/region1/offset_cc12") == 12); + REQUIRE(d.read("/region2/offset_cc12") == -12); + REQUIRE(d.read("/region3/offset_cc14") == 14); + REQUIRE(d.read("/region3/offset_cc12") == -12); } -} -TEST_CASE("[Values] End") -{ - Synth synth; - std::vector messageList; - Client client(&messageList); - client.setReceiveCallback(&simpleMessageReceiver); - SECTION("Basic") + SECTION("Sample end basic") { - synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + d.load(R"( sample=kick.wav end=194 sample=kick.wav end=-1 sample=kick.wav end=0 sample=kick.wav end=194 end=-1 sample=kick.wav end=0 end=194 )"); - synth.dispatchMessage(client, 0, "/region0/end", "", nullptr); - synth.dispatchMessage(client, 0, "/region0/enabled", "", nullptr); - synth.dispatchMessage(client, 0, "/region1/enabled", "", nullptr); - synth.dispatchMessage(client, 0, "/region2/enabled", "", nullptr); - synth.dispatchMessage(client, 0, "/region3/enabled", "", nullptr); - synth.dispatchMessage(client, 0, "/region4/enabled", "", nullptr); - synth.dispatchMessage(client, 0, "/region4/end", "", nullptr); - std::vector expected { - "/region0/end,h : { 194 }", - "/region0/enabled,T : { }", - "/region1/enabled,F : { }", - "/region2/enabled,F : { }", - "/region3/enabled,F : { }", - "/region4/enabled,T : { }", - "/region4/end,h : { 194 }", - }; - REQUIRE(messageList == expected); + REQUIRE(d.read("/region0/end") == 194); + REQUIRE(d.read("/region0/enabled") == OSC::True); + REQUIRE(d.read("/region1/enabled") == OSC::False); + REQUIRE(d.read("/region2/enabled") == OSC::False); + REQUIRE(d.read("/region3/enabled") == OSC::False); + REQUIRE(d.read("/region4/enabled") == OSC::True); + REQUIRE(d.read("/region4/end") == 194); } - SECTION("CC") + + SECTION("Sample end CC") { - synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + d.load(R"( sample=kick.wav sample=kick.wav end_cc12=12 sample=kick.wav end_oncc12=-12 sample=kick.wav end_cc14=14 end_cc12=12 end_oncc12=-12 )"); - synth.dispatchMessage(client, 0, "/region0/end_cc12", "", nullptr); - synth.dispatchMessage(client, 0, "/region1/end_cc12", "", nullptr); - synth.dispatchMessage(client, 0, "/region2/end_cc12", "", nullptr); - synth.dispatchMessage(client, 0, "/region3/end_cc14", "", nullptr); - synth.dispatchMessage(client, 0, "/region3/end_cc12", "", nullptr); - std::vector expected { - "/region0/end_cc12,h : { 0 }", - "/region1/end_cc12,h : { 12 }", - "/region2/end_cc12,h : { -12 }", - "/region3/end_cc14,h : { 14 }", - "/region3/end_cc12,h : { -12 }", - }; - REQUIRE(messageList == expected); + REQUIRE(d.read("/region0/end_cc12") == 0); + REQUIRE(d.read("/region1/end_cc12") == 12); + REQUIRE(d.read("/region2/end_cc12") == -12); + REQUIRE(d.read("/region3/end_cc14") == 14); + REQUIRE(d.read("/region3/end_cc12") == -12); + } + SECTION("Count") + { + d.load(R"( + sample=kick.wav + sample=kick.wav count=2 + sample=kick.wav count=-1 + )"); + REQUIRE(d.read("/region0/count") == OSC::None); + REQUIRE(d.read("/region1/count") == 2); + REQUIRE(d.read("/region2/count") == OSC::None); } -} - -TEST_CASE("[Values] Count") -{ - 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 count=2 - sample=kick.wav count=-1 - )"); - synth.dispatchMessage(client, 0, "/region0/count", "", nullptr); - synth.dispatchMessage(client, 0, "/region1/count", "", nullptr); - synth.dispatchMessage(client, 0, "/region2/count", "", nullptr); - std::vector expected { - "/region0/count,N : { }", - "/region1/count,i : { 2 }", - "/region2/count,N : { }", - }; - REQUIRE(messageList == expected); -} - -TEST_CASE("[Values] Loop mode") -{ - 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 loop_mode=one_shot - sample=kick.wav loopmode=one_shot - sample=kick.wav loop_mode=loop_sustain - sample=kick.wav loop_mode=loop_continuous - sample=kick.wav loop_mode=loop_continuous loop_mode=no_loop - )"); - synth.dispatchMessage(client, 0, "/region0/loop_mode", "", nullptr); - synth.dispatchMessage(client, 0, "/region1/loop_mode", "", nullptr); - synth.dispatchMessage(client, 0, "/region2/loop_mode", "", nullptr); - synth.dispatchMessage(client, 0, "/region3/loop_mode", "", nullptr); - synth.dispatchMessage(client, 0, "/region4/loop_mode", "", nullptr); - synth.dispatchMessage(client, 0, "/region5/loop_mode", "", nullptr); - std::vector expected { - "/region0/loop_mode,s : { no_loop }", - "/region1/loop_mode,s : { one_shot }", - "/region2/loop_mode,s : { one_shot }", - "/region3/loop_mode,s : { loop_sustain }", - "/region4/loop_mode,s : { loop_continuous }", - "/region5/loop_mode,s : { no_loop }", - }; - REQUIRE(messageList == expected); -} -TEST_CASE("[Values] Loops") -{ - 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 loop_mode=one_shot - sample=kick.wav loopmode=one_shot - sample=kick.wav loop_mode=loop_sustain - sample=kick.wav loop_mode=loop_continuous - sample=kick.wav loop_mode=loop_continuous loop_mode=no_loop - )"); - synth.dispatchMessage(client, 0, "/region0/loop_mode", "", nullptr); - synth.dispatchMessage(client, 0, "/region1/loop_mode", "", nullptr); - synth.dispatchMessage(client, 0, "/region2/loop_mode", "", nullptr); - synth.dispatchMessage(client, 0, "/region3/loop_mode", "", nullptr); - synth.dispatchMessage(client, 0, "/region4/loop_mode", "", nullptr); - synth.dispatchMessage(client, 0, "/region5/loop_mode", "", nullptr); - std::vector expected { - "/region0/loop_mode,s : { no_loop }", - "/region1/loop_mode,s : { one_shot }", - "/region2/loop_mode,s : { one_shot }", - "/region3/loop_mode,s : { loop_sustain }", - "/region4/loop_mode,s : { loop_continuous }", - "/region5/loop_mode,s : { no_loop }", - }; - REQUIRE(messageList == expected); -} + SECTION("Loop mode") + { + SynthDiscussion d; + d.load(R"( + sample=kick.wav + sample=kick.wav loop_mode=one_shot + sample=kick.wav loopmode=one_shot + sample=kick.wav loop_mode=loop_sustain + sample=kick.wav loop_mode=loop_continuous + sample=kick.wav loop_mode=loop_continuous loop_mode=no_loop + )"); + REQUIRE(d.read("/region0/loop_mode") == "no_loop"); + REQUIRE(d.read("/region1/loop_mode") == "one_shot"); + REQUIRE(d.read("/region2/loop_mode") == "one_shot"); + REQUIRE(d.read("/region3/loop_mode") == "loop_sustain"); + REQUIRE(d.read("/region4/loop_mode") == "loop_continuous"); + REQUIRE(d.read("/region5/loop_mode") == "no_loop"); + } -TEST_CASE("[Values] Loop range") -{ - Synth synth; - std::vector messageList; - Client client(&messageList); - client.setReceiveCallback(&simpleMessageReceiver); - SECTION("Basic") + SECTION("Loop range basic") { - synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + d.load(R"( sample=kick.wav sample=kick.wav loop_start=10 loop_end=100 sample=kick.wav loopstart=10 loopend=100 sample=kick.wav loop_start=-1 loopend=-100 )"); - synth.dispatchMessage(client, 0, "/region0/loop_range", "", nullptr); - synth.dispatchMessage(client, 0, "/region1/loop_range", "", nullptr); - synth.dispatchMessage(client, 0, "/region2/loop_range", "", nullptr); - synth.dispatchMessage(client, 0, "/region3/loop_range", "", nullptr); - std::vector expected { - "/region0/loop_range,hh : { 0, 44011 }", // Default loop points in the file - "/region1/loop_range,hh : { 10, 100 }", - "/region2/loop_range,hh : { 10, 100 }", - "/region3/loop_range,hh : { 0, 44011 }", - }; - REQUIRE(messageList == expected); + REQUIRE_THAT(d.readAll("/region0/loop_range"), Catch::Approx(std::vector{ 0, 44011 })); + REQUIRE_THAT(d.readAll("/region1/loop_range"), Catch::Approx(std::vector{ 10, 100 })); + REQUIRE_THAT(d.readAll("/region2/loop_range"), Catch::Approx(std::vector{ 10, 100 })); + REQUIRE_THAT(d.readAll("/region3/loop_range"), Catch::Approx(std::vector{ 0, 44011 })); } - SECTION("CC") + + SECTION("Loop range CC") { - synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + d.load(R"( 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 @@ -360,464 +207,259 @@ TEST_CASE("[Values] Loop range") 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); - synth.dispatchMessage(client, 0, "/region1/loop_start_cc12", "", nullptr); - 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 }", - "/region1/loop_start_cc12,h : { 10 }", - "/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); + REQUIRE(d.read("/region0/loop_start_cc12") == 0); + REQUIRE(d.read("/region0/loop_end_cc14") == 0); + REQUIRE(d.read("/region1/loop_start_cc12") == 10); + REQUIRE(d.read("/region1/loop_end_cc14") == -100); + REQUIRE(d.read("/region2/loop_start_cc12") == -10); + REQUIRE(d.read("/region2/loop_end_cc14") == 100); + REQUIRE(d.read("/region3/loop_start_cc12") == -10); + REQUIRE(d.read("/region3/loop_end_cc14") == 100); + REQUIRE(d.read("/region4/loop_end_cc14") == 100); + REQUIRE(d.read("/region5/loop_end_cc14") == 100); } -} - -TEST_CASE("[Values] Loop crossfade") -{ - 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 loop_crossfade=0.5 - sample=kick.wav loop_crossfade=-1 - )"); - synth.dispatchMessage(client, 0, "/region0/loop_crossfade", "", nullptr); - synth.dispatchMessage(client, 0, "/region1/loop_crossfade", "", nullptr); - std::vector expected { - "/region0/loop_crossfade,f : { 0.5 }", - "/region1/loop_crossfade,f : { 0.001 }", - }; - REQUIRE(messageList == expected); -} -TEST_CASE("[Values] Loop count") -{ - 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 loop_count=2 - sample=kick.wav loop_count=-1 - )"); - synth.dispatchMessage(client, 0, "/region0/loop_count", "", nullptr); - synth.dispatchMessage(client, 0, "/region1/loop_count", "", nullptr); - synth.dispatchMessage(client, 0, "/region2/loop_count", "", nullptr); - std::vector expected { - "/region0/loop_count,N : { }", - "/region1/loop_count,i : { 2 }", - "/region2/loop_count,N : { }", - }; - REQUIRE(messageList == expected); -} + SECTION("Loop crossfade") + { + d.load(R"( + sample=kick.wav loop_crossfade=0.5 + sample=kick.wav loop_crossfade=-1 + )"); + REQUIRE(d.read("/region0/loop_crossfade") == 0.5f); + REQUIRE(d.read("/region1/loop_crossfade") == 0.001f); + } -TEST_CASE("[Values] Output") -{ - Synth synth; - std::vector messageList; - Client client(&messageList); - client.setReceiveCallback(&simpleMessageReceiver); + SECTION("Loop count") + { + d.load(R"( + sample=kick.wav + sample=kick.wav loop_count=2 + sample=kick.wav loop_count=-1 + )"); + REQUIRE(d.read("/region0/loop_count") == OSC::None); + REQUIRE(d.read("/region1/loop_count") == 2); + REQUIRE(d.read("/region2/loop_count") == OSC::None); + } SECTION("No special outputs") { - synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + d.load(R"( sample=kick.wav )"); - synth.dispatchMessage(client, 0, "/region0/output", "", nullptr); - synth.dispatchMessage(client, 0, "/num_outputs", "", nullptr); - std::vector expected { - "/region0/output,i : { 0 }", - "/num_outputs,i : { 1 }", - }; - REQUIRE(messageList == expected); + REQUIRE(d.read("/region0/output") == 0); + REQUIRE(d.read("/num_outputs") == 1); } SECTION("1 output") { - synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + d.load(R"( sample=kick.wav sample=kick.wav output=1 sample=kick.wav output=-1 )"); - synth.dispatchMessage(client, 0, "/region0/output", "", nullptr); - synth.dispatchMessage(client, 0, "/region1/output", "", nullptr); - synth.dispatchMessage(client, 0, "/region2/output", "", nullptr); - synth.dispatchMessage(client, 0, "/num_outputs", "", nullptr); - std::vector expected { - "/region0/output,i : { 0 }", - "/region1/output,i : { 1 }", - "/region2/output,i : { 0 }", - "/num_outputs,i : { 2 }", - }; - REQUIRE(messageList == expected); + REQUIRE(d.read("/region0/output") == 0); + REQUIRE(d.read("/region1/output") == 1); + REQUIRE(d.read("/region2/output") == 0); + REQUIRE(d.read("/num_outputs") == 2); } SECTION("More than 1 output") { - synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + d.load(R"( sample=kick.wav sample=kick.wav output=1 sample=kick.wav output=3 )"); - synth.dispatchMessage(client, 0, "/region0/output", "", nullptr); - synth.dispatchMessage(client, 0, "/region1/output", "", nullptr); - synth.dispatchMessage(client, 0, "/region2/output", "", nullptr); - synth.dispatchMessage(client, 0, "/num_outputs", "", nullptr); - std::vector expected { - "/region0/output,i : { 0 }", - "/region1/output,i : { 1 }", - "/region2/output,i : { 3 }", - "/num_outputs,i : { 4 }", - }; - REQUIRE(messageList == expected); + REQUIRE(d.read("/region0/output") == 0); + REQUIRE(d.read("/region1/output") == 1); + REQUIRE(d.read("/region2/output") == 3); + REQUIRE(d.read("/num_outputs") == 4); } -} - -TEST_CASE("[Values] Group") -{ - 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 group=5 - sample=kick.wav group=-2 - )"); - synth.dispatchMessage(client, 0, "/region0/group", "", nullptr); - synth.dispatchMessage(client, 0, "/region1/group", "", nullptr); - synth.dispatchMessage(client, 0, "/region2/group", "", nullptr); - std::vector expected { - "/region0/group,h : { 0 }", - "/region1/group,h : { 5 }", - "/region2/group,h : { -2 }", - }; - REQUIRE(messageList == expected); -} -TEST_CASE("[Values] Off by") -{ - 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 off_by=5 - sample=kick.wav off_by=-2 - )"); - synth.dispatchMessage(client, 0, "/region0/off_by", "", nullptr); - synth.dispatchMessage(client, 0, "/region1/off_by", "", nullptr); - synth.dispatchMessage(client, 0, "/region2/off_by", "", nullptr); - std::vector expected { - "/region0/off_by,N : { }", - "/region1/off_by,h : { 5 }", - "/region2/off_by,h : { -2 }", - }; - REQUIRE(messageList == expected); -} + SECTION("Group") + { + d.load(R"( + sample=kick.wav + sample=kick.wav group=5 + sample=kick.wav group=-2 + )"); + REQUIRE(d.read("/region0/group") == 0); + REQUIRE(d.read("/region1/group") == 5); + REQUIRE(d.read("/region2/group") == -2); + } -TEST_CASE("[Values] Off mode") -{ - 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 off_mode=fast - sample=kick.wav off_mode=normal - sample=kick.wav off_mode=time - sample=kick.wav off_mode=time off_mode=normal - sample=kick.wav off_mode=nothing - )"); - synth.dispatchMessage(client, 0, "/region0/off_mode", "", nullptr); - synth.dispatchMessage(client, 0, "/region1/off_mode", "", nullptr); - synth.dispatchMessage(client, 0, "/region2/off_mode", "", nullptr); - synth.dispatchMessage(client, 0, "/region3/off_mode", "", nullptr); - synth.dispatchMessage(client, 0, "/region4/off_mode", "", nullptr); - synth.dispatchMessage(client, 0, "/region5/off_mode", "", nullptr); - std::vector expected { - "/region0/off_mode,s : { fast }", - "/region1/off_mode,s : { fast }", - "/region2/off_mode,s : { normal }", - "/region3/off_mode,s : { time }", - "/region4/off_mode,s : { normal }", - "/region5/off_mode,s : { fast }", - }; - REQUIRE(messageList == expected); -} + SECTION("Off by") + { + d.load(R"( + sample=kick.wav + sample=kick.wav off_by=5 + sample=kick.wav off_by=-2 + )"); + REQUIRE( d.read("/region0/off_by") == OSC::None ); + REQUIRE( d.read("/region1/off_by") == 5 ); + REQUIRE( d.read("/region2/off_by") == -2 ); + } -TEST_CASE("[Values] Off time") -{ - 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 off_time=0.1 - sample=kick.wav off_time=-1 - )"); - synth.dispatchMessage(client, 0, "/region0/off_time", "", nullptr); - synth.dispatchMessage(client, 0, "/region1/off_time", "", nullptr); - synth.dispatchMessage(client, 0, "/region2/off_time", "", nullptr); - std::vector expected { - "/region0/off_time,f : { 0.006 }", - "/region1/off_time,f : { 0.1 }", - "/region2/off_time,f : { -1 }", - }; - REQUIRE(messageList == expected); -} + SECTION("Off mode") + { + d.load(R"( + sample=kick.wav + sample=kick.wav off_mode=fast + sample=kick.wav off_mode=normal + sample=kick.wav off_mode=time + sample=kick.wav off_mode=time off_mode=normal + sample=kick.wav off_mode=nothing + )"); + REQUIRE( d.read("/region0/off_mode") == "fast" ); + REQUIRE( d.read("/region1/off_mode") == "fast" ); + REQUIRE( d.read("/region2/off_mode") == "normal" ); + REQUIRE( d.read("/region3/off_mode") == "time" ); + REQUIRE( d.read("/region4/off_mode") == "normal" ); + REQUIRE( d.read("/region5/off_mode") == "fast" ); + } -TEST_CASE("[Values] Key range") -{ - 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 lokey=34 hikey=60 - sample=kick.wav lokey=c4 hikey=b5 - sample=kick.wav lokey=-3 hikey=60 - sample=kick.wav hikey=-1 - sample=kick.wav pitch_keycenter=32 - sample=kick.wav pitch_keycenter=-1 - sample=kick.wav key=26 - )"); - synth.dispatchMessage(client, 0, "/region0/key_range", "", nullptr); - synth.dispatchMessage(client, 0, "/region1/key_range", "", nullptr); - synth.dispatchMessage(client, 0, "/region2/key_range", "", nullptr); - synth.dispatchMessage(client, 0, "/region3/key_range", "", nullptr); - synth.dispatchMessage(client, 0, "/region4/key_range", "", nullptr); - synth.dispatchMessage(client, 0, "/region0/pitch_keycenter", "", nullptr); - synth.dispatchMessage(client, 0, "/region5/pitch_keycenter", "", nullptr); - synth.dispatchMessage(client, 0, "/region6/pitch_keycenter", "", nullptr); - synth.dispatchMessage(client, 0, "/region7/key_range", "", nullptr); - synth.dispatchMessage(client, 0, "/region7/pitch_keycenter", "", nullptr); - std::vector expected { - "/region0/key_range,ii : { 0, 127 }", - "/region1/key_range,ii : { 34, 60 }", - "/region2/key_range,ii : { 60, 83 }", - "/region3/key_range,ii : { 0, 60 }", - "/region4/key_range,ii : { 0, 127 }", - "/region0/pitch_keycenter,i : { 60 }", - "/region5/pitch_keycenter,i : { 32 }", - "/region6/pitch_keycenter,i : { 60 }", - "/region7/key_range,ii : { 26, 26 }", - "/region7/pitch_keycenter,i : { 26 }", - }; - REQUIRE(messageList == expected); -} + SECTION("Off time") + { + d.load(R"( + sample=kick.wav + sample=kick.wav off_time=0.1 + sample=kick.wav off_time=-1 + )"); + REQUIRE( d.read("/region0/off_time") == 0.006f); + REQUIRE( d.read("/region1/off_time") == 0.1f); + REQUIRE( d.read("/region2/off_time") == -1.0f); + } -TEST_CASE("[Values] Triggers on note") -{ - 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 hikey=-1 - sample=kick.wav key=-1 - sample=kick.wav hikey=-1 lokey=12 - sample=kick.wav hikey=-1 lokey=-1 - sample=kick.wav hikey=0 lokey=12 - )"); - 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); - synth.dispatchMessage(client, 0, "/region5/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,F : { }", - "/region4/trigger_on_note,F : { }", - "/region5/trigger_on_note,T : { }", - }; - REQUIRE(messageList == expected); -} + SECTION("Key range") + { + d.load(R"( + sample=kick.wav + sample=kick.wav lokey=34 hikey=60 + sample=kick.wav lokey=c4 hikey=b5 + sample=kick.wav lokey=-3 hikey=60 + sample=kick.wav hikey=-1 + sample=kick.wav pitch_keycenter=32 + sample=kick.wav pitch_keycenter=-1 + sample=kick.wav key=26 + )"); + REQUIRE_THAT( d.readAll("/region0/key_range"), Catch::Approx(std::vector{ 0, 127 })); + REQUIRE_THAT( d.readAll("/region1/key_range"), Catch::Approx(std::vector{ 34, 60 })); + REQUIRE_THAT( d.readAll("/region2/key_range"), Catch::Approx(std::vector{ 60, 83 })); + REQUIRE_THAT( d.readAll("/region3/key_range"), Catch::Approx(std::vector{ 0, 60 })); + REQUIRE_THAT( d.readAll("/region4/key_range"), Catch::Approx(std::vector{ 0, 127 })); + REQUIRE( d.read("/region0/pitch_keycenter") == 60); + REQUIRE( d.read("/region5/pitch_keycenter") == 32); + REQUIRE( d.read("/region6/pitch_keycenter") == 60); + REQUIRE_THAT( d.readAll("/region7/key_range"), Catch::Approx(std::vector{ 26, 26 })); + REQUIRE( d.read("/region7/pitch_keycenter") == 26); + } -TEST_CASE("[Values] Velocity range") -{ - 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 lovel=34 hivel=60 - sample=kick.wav lovel=-3 hivel=60 - sample=kick.wav hivel=-1 - )"); - synth.dispatchMessage(client, 0, "/region0/vel_range", "", nullptr); - synth.dispatchMessage(client, 0, "/region1/vel_range", "", nullptr); - synth.dispatchMessage(client, 0, "/region2/vel_range", "", nullptr); - synth.dispatchMessage(client, 0, "/region3/vel_range", "", nullptr); - std::vector expected { - "/region0/vel_range,ff : { 0, 1 }", - "/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); -} + SECTION("Triggers on note") + { + d.load(R"( + sample=kick.wav + sample=kick.wav hikey=-1 + sample=kick.wav key=-1 + sample=kick.wav hikey=-1 lokey=12 + sample=kick.wav hikey=-1 lokey=-1 + sample=kick.wav hikey=0 lokey=12 + )"); + REQUIRE( d.read("/region0/trigger_on_note") == OSC::True); + REQUIRE( d.read("/region1/trigger_on_note") == OSC::False); + REQUIRE( d.read("/region2/trigger_on_note") == OSC::False); + // TODO: Double check with Sforzando/rgc + REQUIRE( d.read("/region3/trigger_on_note") == OSC::False); + REQUIRE( d.read("/region4/trigger_on_note") == OSC::False); + REQUIRE( d.read("/region5/trigger_on_note") == OSC::True); + } -TEST_CASE("[Values] Bend range") -{ - 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 lobend=891 hibend=2000 - sample=kick.wav lobend=-891 hibend=891 - sample=kick.wav hibend=-10000 - )"); - synth.dispatchMessage(client, 0, "/region0/bend_range", "", nullptr); - synth.dispatchMessage(client, 0, "/region1/bend_range", "", nullptr); - synth.dispatchMessage(client, 0, "/region2/bend_range", "", nullptr); - synth.dispatchMessage(client, 0, "/region3/bend_range", "", nullptr); - std::vector expected { - "/region0/bend_range,ff : { -1, 1 }", - "/region1/bend_range,ff : { 0.108778, 0.24417 }", - "/region2/bend_range,ff : { -0.108778, 0.108778 }", - "/region3/bend_range,ff : { -1, -1.22085 }", - }; - REQUIRE(messageList == expected); -} + SECTION("Velocity range") + { + d.load(R"( + sample=kick.wav + sample=kick.wav lovel=34 hivel=60 + sample=kick.wav lovel=-3 hivel=60 + sample=kick.wav hivel=-1 + )"); + REQUIRE_THAT( d.readAll("/region0/vel_range"), Catch::Approx(std::vector{ 0.0f, 1.0f })); + REQUIRE_THAT( d.readAll("/region1/vel_range"), Catch::Approx(std::vector{ 34_norm, 61_norm })); + REQUIRE_THAT( d.readAll("/region2/vel_range"), Catch::Approx(std::vector{ -3_norm, 61_norm })); + REQUIRE_THAT( d.readAll("/region3/vel_range"), Catch::Approx(std::vector{ 0.0f, -1_norm })); + } -TEST_CASE("[Values] Program range") -{ - 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 loprog=1 hiprog=45 - sample=kick.wav loprog=-1 hiprog=555 - sample=kick.wav hiprog=-1 - )"); - synth.dispatchMessage(client, 0, "/region0/program_range", "", nullptr); - synth.dispatchMessage(client, 0, "/region1/program_range", "", nullptr); - synth.dispatchMessage(client, 0, "/region2/program_range", "", nullptr); - synth.dispatchMessage(client, 0, "/region3/program_range", "", nullptr); - std::vector expected { - "/region0/program_range,ii : { 0, 127 }", - "/region1/program_range,ii : { 1, 45 }", - "/region2/program_range,ii : { 0, 127 }", - "/region3/program_range,ii : { 0, 127 }", - }; - REQUIRE(messageList == expected); -} + SECTION("Bend range") + { + d.load(R"( + sample=kick.wav + sample=kick.wav lobend=891 hibend=2000 + sample=kick.wav lobend=-891 hibend=891 + sample=kick.wav hibend=-10000 + )"); + REQUIRE_THAT( d.readAll("/region0/bend_range"), Catch::Approx(std::vector{ -1.0f, 1.0f })); + REQUIRE_THAT( d.readAll("/region1/bend_range"), Catch::Approx(std::vector{ 891.0_bend, 2000.0_bend })); + REQUIRE_THAT( d.readAll("/region2/bend_range"), Catch::Approx(std::vector{ -891.0_bend, 891.0_bend })); + REQUIRE_THAT( d.readAll("/region3/bend_range"), Catch::Approx(std::vector{ -1.0f, -10000.0_bend })); + } -TEST_CASE("[Values] CC condition range") -{ - Synth synth; - std::vector messageList; - Client client(&messageList); - client.setReceiveCallback(&simpleMessageReceiver); + SECTION("Program range") + { + d.load(R"( + sample=kick.wav + sample=kick.wav loprog=1 hiprog=45 + sample=kick.wav loprog=-1 hiprog=555 + sample=kick.wav hiprog=-1 + )"); + REQUIRE_THAT( d.readAll("/region0/program_range"), Catch::Approx(std::vector{ 0, 127 })); + REQUIRE_THAT( d.readAll("/region1/program_range"), Catch::Approx(std::vector{ 1, 45 })); + REQUIRE_THAT( d.readAll("/region2/program_range"), Catch::Approx(std::vector{ 0, 127 })); + REQUIRE_THAT( d.readAll("/region3/program_range"), Catch::Approx(std::vector{ 0, 127 })); + } - SECTION("Basic") + SECTION("CC condition basic") { - synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + d.load(R"( sample=kick.wav sample=kick.wav locc1=0 hicc1=54 sample=kick.wav locc1=0 hicc1=54 locc2=2 hicc2=10 sample=kick.wav locc1=10 hicc1=-1 )"); - synth.dispatchMessage(client, 0, "/region0/cc_range1", "", nullptr); - synth.dispatchMessage(client, 0, "/region1/cc_range1", "", nullptr); - synth.dispatchMessage(client, 0, "/region2/cc_range1", "", nullptr); - synth.dispatchMessage(client, 0, "/region2/cc_range2", "", nullptr); - synth.dispatchMessage(client, 0, "/region3/cc_range1", "", nullptr); - std::vector expected { - "/region0/cc_range1,ff : { 0, 1 }", - "/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); + REQUIRE_THAT( d.readAll("/region0/cc_range1"), Catch::Approx(std::vector{ 0.0f, 1.0f })); + REQUIRE_THAT( d.readAll("/region1/cc_range1"), Catch::Approx(std::vector{ 0.0f, 55_norm })); + REQUIRE_THAT( d.readAll("/region2/cc_range1"), Catch::Approx(std::vector{ 0.0f, 55_norm })); + REQUIRE_THAT( d.readAll("/region2/cc_range2"), Catch::Approx(std::vector{ 2_norm, 11_norm })); + REQUIRE_THAT( d.readAll("/region3/cc_range1"), Catch::Approx(std::vector{ 10_norm, -1_norm })); } - SECTION("hdcc") + SECTION("HDCC conditions") { - synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + d.load(R"( sample=kick.wav sample=kick.wav lohdcc1=0 hihdcc1=0.1 sample=kick.wav lohdcc1=0 hihdcc1=0.1 lohdcc2=0.1 hihdcc2=0.2 sample=kick.wav lohdcc1=0.1 hihdcc1=-0.1 )"); - synth.dispatchMessage(client, 0, "/region0/cc_range1", "", nullptr); - synth.dispatchMessage(client, 0, "/region1/cc_range1", "", nullptr); - synth.dispatchMessage(client, 0, "/region2/cc_range1", "", nullptr); - synth.dispatchMessage(client, 0, "/region2/cc_range2", "", nullptr); - synth.dispatchMessage(client, 0, "/region3/cc_range1", "", nullptr); - std::vector expected { - "/region0/cc_range1,ff : { 0, 1 }", - "/region1/cc_range1,ff : { 0, 0.1 }", - "/region2/cc_range1,ff : { 0, 0.1 }", - "/region2/cc_range2,ff : { 0.1, 0.2 }", - "/region3/cc_range1,ff : { 0.1, -0.1 }", - }; - REQUIRE(messageList == expected); + REQUIRE_THAT( d.readAll("/region0/cc_range1"), Catch::Approx(std::vector{ 0.0f, 1.0f })); + REQUIRE_THAT( d.readAll("/region1/cc_range1"), Catch::Approx(std::vector{ 0.0f, 0.1f })); + REQUIRE_THAT( d.readAll("/region2/cc_range1"), Catch::Approx(std::vector{ 0.0f, 0.1f })); + REQUIRE_THAT( d.readAll("/region2/cc_range2"), Catch::Approx(std::vector{ 0.1f, 0.2f })); + REQUIRE_THAT( d.readAll("/region3/cc_range1"), Catch::Approx(std::vector{ 0.1f, -0.1f })); } - SECTION("realcc") + SECTION("RealCC conditions ") { - synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + d.load(R"( sample=kick.wav sample=kick.wav lorealcc1=0 hirealcc1=0.1 sample=kick.wav lorealcc1=0 hirealcc1=0.1 lorealcc2=0.1 hirealcc2=0.2 sample=kick.wav lorealcc1=0.1 hirealcc1=-0.1 )"); - synth.dispatchMessage(client, 0, "/region0/cc_range1", "", nullptr); - synth.dispatchMessage(client, 0, "/region1/cc_range1", "", nullptr); - synth.dispatchMessage(client, 0, "/region2/cc_range1", "", nullptr); - synth.dispatchMessage(client, 0, "/region2/cc_range2", "", nullptr); - synth.dispatchMessage(client, 0, "/region3/cc_range1", "", nullptr); - std::vector expected { - "/region0/cc_range1,ff : { 0, 1 }", - "/region1/cc_range1,ff : { 0, 0.1 }", - "/region2/cc_range1,ff : { 0, 0.1 }", - "/region2/cc_range2,ff : { 0.1, 0.2 }", - "/region3/cc_range1,ff : { 0.1, -0.1 }", - }; - REQUIRE(messageList == expected); + REQUIRE_THAT( d.readAll("/region0/cc_range1"), Catch::Approx(std::vector{ 0.0f, 1.0f })); + REQUIRE_THAT( d.readAll("/region1/cc_range1"), Catch::Approx(std::vector{ 0.0f, 0.1f })); + REQUIRE_THAT( d.readAll("/region2/cc_range1"), Catch::Approx(std::vector{ 0.0f, 0.1f })); + REQUIRE_THAT( d.readAll("/region2/cc_range2"), Catch::Approx(std::vector{ 0.1f, 0.2f })); + REQUIRE_THAT( d.readAll("/region3/cc_range1"), Catch::Approx(std::vector{ 0.1f, -0.1f })); } -} - -TEST_CASE("[Values] Last keyswitch") -{ - Synth synth; - std::vector messageList; - Client client(&messageList); - client.setReceiveCallback(&simpleMessageReceiver); - SECTION("Basic") + SECTION("Last keyswitch basic") { - synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + d.load(R"( sample=kick.wav sample=kick.wav sw_last=12 sample=kick.wav sw_last=c4 @@ -825,493 +467,287 @@ TEST_CASE("[Values] Last keyswitch") sample=kick.wav sw_lolast=c4 sw_hilast=b5 sample=kick.wav sw_last=-1 )"); - synth.dispatchMessage(client, 0, "/region0/sw_last", "", nullptr); - synth.dispatchMessage(client, 0, "/region1/sw_last", "", nullptr); - synth.dispatchMessage(client, 0, "/region2/sw_last", "", nullptr); - synth.dispatchMessage(client, 0, "/region3/sw_last", "", nullptr); + REQUIRE( d.read("/region0/sw_last") == OSC::None); + REQUIRE( d.read("/region1/sw_last") == 12); + REQUIRE( d.read("/region2/sw_last") == 60); + REQUIRE_THAT( d.readAll("/region3/sw_last"), Catch::Approx(std::vector{14, 16} )); // TODO: activate for the new region parser ; can handle note names - // synth.dispatchMessage(client, 0, "/region4/sw_last", "", nullptr); + // REQUIRE( d.readAll("/region4/sw_last") == ); // TODO: activate for the new region parser ; ignore the second value - // synth.dispatchMessage(client, 0, "/region5/sw_last", "", nullptr); - std::vector expected { - "/region0/sw_last,N : { }", - "/region1/sw_last,i : { 12 }", - "/region2/sw_last,i : { 60 }", - "/region3/sw_last,ii : { 14, 16 }", - // "/region4/sw_last,ii : { 60, 83 }", - // "/region5/sw_last,ii : { 0, 0 }", - }; - REQUIRE(messageList == expected); + // REQUIRE( d.readAll("/region5/sw_last") == ); } SECTION("sw_lolast disables sw_last over the whole region") { - synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + d.load(R"( sample=kick.wav sw_last=12 sw_lolast=14 sw_last=16 )"); - synth.dispatchMessage(client, 0, "/region0/sw_last", "", nullptr); + REQUIRE_THAT( d.readAll("/region0/sw_last"), Catch::Approx(std::vector{14, 14} )); std::vector expected { "/region0/sw_last,ii : { 14, 14 }", }; - REQUIRE(messageList == expected); } -} -TEST_CASE("[Values] Keyswitch label") -{ - 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 sw_label=hello - )"); - synth.dispatchMessage(client, 0, "/region0/sw_label", "", nullptr); - synth.dispatchMessage(client, 0, "/region1/sw_label", "", nullptr); - std::vector expected { - "/region0/sw_label,N : { }", - "/region1/sw_label,s : { hello }", - }; - REQUIRE(messageList == expected); -} + SECTION("Keyswitch label") + { + d.load(R"( + sample=kick.wav + sample=kick.wav sw_label=hello + )"); + REQUIRE( d.read("/region0/sw_label") == OSC::None ); + REQUIRE( d.read("/region1/sw_label") == "hello" ); + } -TEST_CASE("[Values] Upswitch") -{ - 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 sw_up=16 - sample=kick.wav sw_up=-1 - sample=kick.wav sw_up=128 - sample=kick.wav sw_up=c4 - sample=kick.wav sw_up=64 sw_up=-1 - )"); - synth.dispatchMessage(client, 0, "/region0/sw_up", "", nullptr); - synth.dispatchMessage(client, 0, "/region1/sw_up", "", nullptr); - synth.dispatchMessage(client, 0, "/region2/sw_up", "", nullptr); - synth.dispatchMessage(client, 0, "/region3/sw_up", "", nullptr); - synth.dispatchMessage(client, 0, "/region4/sw_up", "", nullptr); - synth.dispatchMessage(client, 0, "/region5/sw_up", "", nullptr); - std::vector expected { - "/region0/sw_up,N : { }", - "/region1/sw_up,i : { 16 }", - "/region2/sw_up,N : { }", - "/region3/sw_up,N : { }", - "/region4/sw_up,i : { 60 }", - "/region5/sw_up,N : { }", - }; - REQUIRE(messageList == expected); -} + SECTION("Upswitch") + { + d.load(R"( + sample=kick.wav + sample=kick.wav sw_up=16 + sample=kick.wav sw_up=-1 + sample=kick.wav sw_up=128 + sample=kick.wav sw_up=c4 + sample=kick.wav sw_up=64 sw_up=-1 + )"); + REQUIRE( d.read("/region0/sw_up") == OSC::None); + REQUIRE( d.read("/region1/sw_up") == 16); + REQUIRE( d.read("/region2/sw_up") == OSC::None); + REQUIRE( d.read("/region3/sw_up") == OSC::None); + REQUIRE( d.read("/region4/sw_up") == 60); + REQUIRE( d.read("/region5/sw_up") == OSC::None); + } -TEST_CASE("[Values] Downswitch") -{ - 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 sw_down=16 - sample=kick.wav sw_down=-1 - sample=kick.wav sw_down=128 - sample=kick.wav sw_down=c4 - sample=kick.wav sw_down=64 sw_down=-1 - )"); - synth.dispatchMessage(client, 0, "/region0/sw_down", "", nullptr); - synth.dispatchMessage(client, 0, "/region1/sw_down", "", nullptr); - synth.dispatchMessage(client, 0, "/region2/sw_down", "", nullptr); - synth.dispatchMessage(client, 0, "/region3/sw_down", "", nullptr); - synth.dispatchMessage(client, 0, "/region4/sw_down", "", nullptr); - synth.dispatchMessage(client, 0, "/region5/sw_down", "", nullptr); - std::vector expected { - "/region0/sw_down,N : { }", - "/region1/sw_down,i : { 16 }", - "/region2/sw_down,N : { }", - "/region3/sw_down,N : { }", - "/region4/sw_down,i : { 60 }", - "/region5/sw_down,N : { }", - }; - REQUIRE(messageList == expected); -} + SECTION("Downswitch") + { + d.load(R"( + sample=kick.wav + sample=kick.wav sw_down=16 + sample=kick.wav sw_down=-1 + sample=kick.wav sw_down=128 + sample=kick.wav sw_down=c4 + sample=kick.wav sw_down=64 sw_down=-1 + )"); + REQUIRE( d.read("/region0/sw_down") == OSC::None); + REQUIRE( d.read("/region1/sw_down") == 16); + REQUIRE( d.read("/region2/sw_down") == OSC::None); + REQUIRE( d.read("/region3/sw_down") == OSC::None); + REQUIRE( d.read("/region4/sw_down") == 60); + REQUIRE( d.read("/region5/sw_down") == OSC::None); + } -TEST_CASE("[Values] Previous keyswitch") -{ - 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 sw_previous=16 - sample=kick.wav sw_previous=-1 - sample=kick.wav sw_previous=128 - sample=kick.wav sw_previous=c4 - sample=kick.wav sw_previous=64 sw_previous=-1 - )"); - synth.dispatchMessage(client, 0, "/region0/sw_previous", "", nullptr); - synth.dispatchMessage(client, 0, "/region1/sw_previous", "", nullptr); - synth.dispatchMessage(client, 0, "/region2/sw_previous", "", nullptr); - synth.dispatchMessage(client, 0, "/region3/sw_previous", "", nullptr); - synth.dispatchMessage(client, 0, "/region4/sw_previous", "", nullptr); - synth.dispatchMessage(client, 0, "/region5/sw_previous", "", nullptr); - std::vector expected { - "/region0/sw_previous,N : { }", - "/region1/sw_previous,i : { 16 }", - "/region2/sw_previous,N : { }", - "/region3/sw_previous,N : { }", - "/region4/sw_previous,i : { 60 }", - "/region5/sw_previous,N : { }", - }; - REQUIRE(messageList == expected); -} + SECTION("Previous keyswitch") + { + d.load(R"( + sample=kick.wav + sample=kick.wav sw_previous=16 + sample=kick.wav sw_previous=-1 + sample=kick.wav sw_previous=128 + sample=kick.wav sw_previous=c4 + sample=kick.wav sw_previous=64 sw_previous=-1 + )"); + REQUIRE( d.read("/region0/sw_previous") == OSC::None); + REQUIRE( d.read("/region1/sw_previous") == 16); + REQUIRE( d.read("/region2/sw_previous") == OSC::None); + REQUIRE( d.read("/region3/sw_previous") == OSC::None); + REQUIRE( d.read("/region4/sw_previous") == 60); + REQUIRE( d.read("/region5/sw_previous") == OSC::None); + } -TEST_CASE("[Values] Velocity override") -{ - 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 sw_vel=current - sample=kick.wav sw_vel=previous - sample=kick.wav sw_vel=previous sw_vel=current - )"); - synth.dispatchMessage(client, 0, "/region0/sw_vel", "", nullptr); - synth.dispatchMessage(client, 0, "/region1/sw_vel", "", nullptr); - synth.dispatchMessage(client, 0, "/region2/sw_vel", "", nullptr); - synth.dispatchMessage(client, 0, "/region3/sw_vel", "", nullptr); - std::vector expected { - "/region0/sw_vel,s : { current }", - "/region1/sw_vel,s : { current }", - "/region2/sw_vel,s : { previous }", - "/region3/sw_vel,s : { current }", - }; - REQUIRE(messageList == expected); -} + SECTION("Velocity override") + { + d.load(R"( + sample=kick.wav + sample=kick.wav sw_vel=current + sample=kick.wav sw_vel=previous + sample=kick.wav sw_vel=previous sw_vel=current + )"); + REQUIRE( d.read("/region0/sw_vel") == "current"); + REQUIRE( d.read("/region1/sw_vel") == "current"); + REQUIRE( d.read("/region2/sw_vel") == "previous"); + REQUIRE( d.read("/region3/sw_vel") == "current"); + } -TEST_CASE("[Values] Aftertouch range") -{ - 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 lochanaft=34 hichanaft=60 - sample=kick.wav lochanaft=-3 hichanaft=60 - sample=kick.wav lochanaft=20 hichanaft=-1 - sample=kick.wav lochanaft=20 hichanaft=10 - )"); - synth.dispatchMessage(client, 0, "/region0/chanaft_range", "", nullptr); - synth.dispatchMessage(client, 0, "/region1/chanaft_range", "", nullptr); - synth.dispatchMessage(client, 0, "/region2/chanaft_range", "", nullptr); - synth.dispatchMessage(client, 0, "/region3/chanaft_range", "", nullptr); - synth.dispatchMessage(client, 0, "/region4/chanaft_range", "", nullptr); - std::vector expected { - "/region0/chanaft_range,ff : { 0, 1 }", - "/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.0866142 }", - }; - REQUIRE(messageList == expected); -} + SECTION("Aftertouch range") + { + d.load(R"( + sample=kick.wav + sample=kick.wav lochanaft=34 hichanaft=60 + sample=kick.wav lochanaft=-3 hichanaft=60 + sample=kick.wav lochanaft=20 hichanaft=-1 + sample=kick.wav lochanaft=20 hichanaft=10 + )"); + REQUIRE_THAT( d.readAll("/region0/chanaft_range"), Catch::Approx(std::vector{ 0, 1 })); + REQUIRE_THAT( d.readAll("/region1/chanaft_range"), Catch::Approx(std::vector{ 34_norm, 61_norm })); + REQUIRE_THAT( d.readAll("/region2/chanaft_range"), Catch::Approx(std::vector{ -3_norm, 61_norm })); + REQUIRE_THAT( d.readAll("/region3/chanaft_range"), Catch::Approx(std::vector{ 20_norm, -1_norm })); + REQUIRE_THAT( d.readAll("/region4/chanaft_range"), Catch::Approx(std::vector{ 20_norm, 11_norm })); + } -TEST_CASE("[Values] Polyaftertouch range") -{ - 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 lopolyaft=34 hipolyaft=60 - sample=kick.wav lopolyaft=-3 hipolyaft=60 - sample=kick.wav lopolyaft=20 hipolyaft=-1 - sample=kick.wav lopolyaft=20 hipolyaft=10 - )"); - synth.dispatchMessage(client, 0, "/region0/polyaft_range", "", nullptr); - synth.dispatchMessage(client, 0, "/region1/polyaft_range", "", nullptr); - synth.dispatchMessage(client, 0, "/region2/polyaft_range", "", nullptr); - synth.dispatchMessage(client, 0, "/region3/polyaft_range", "", nullptr); - synth.dispatchMessage(client, 0, "/region4/polyaft_range", "", nullptr); - std::vector expected { - "/region0/polyaft_range,ff : { 0, 1 }", - "/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.0866142 }", - }; - REQUIRE(messageList == expected); -} + SECTION("Polyaftertouch range") + { + d.load(R"( + sample=kick.wav + sample=kick.wav lopolyaft=34 hipolyaft=60 + sample=kick.wav lopolyaft=-3 hipolyaft=60 + sample=kick.wav lopolyaft=20 hipolyaft=-1 + sample=kick.wav lopolyaft=20 hipolyaft=10 + )"); + REQUIRE_THAT( d.readAll("/region0/polyaft_range"), Catch::Approx(std::vector{ 0, 1 })); + REQUIRE_THAT( d.readAll("/region1/polyaft_range"), Catch::Approx(std::vector{ 34_norm, 61_norm })); + REQUIRE_THAT( d.readAll("/region2/polyaft_range"), Catch::Approx(std::vector{ -3_norm, 61_norm })); + REQUIRE_THAT( d.readAll("/region3/polyaft_range"), Catch::Approx(std::vector{ 20_norm, -1_norm })); + REQUIRE_THAT( d.readAll("/region4/polyaft_range"), Catch::Approx(std::vector{ 20_norm, 11_norm })); + } -TEST_CASE("[Values] BPM range") -{ - 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 lobpm=34.1 hibpm=60.2 - sample=kick.wav lobpm=-3 hibpm=60 - sample=kick.wav lobpm=20 hibpm=-1 - sample=kick.wav lobpm=20 hibpm=10 - )"); - synth.dispatchMessage(client, 0, "/region0/bpm_range", "", nullptr); - synth.dispatchMessage(client, 0, "/region1/bpm_range", "", nullptr); - synth.dispatchMessage(client, 0, "/region2/bpm_range", "", nullptr); - synth.dispatchMessage(client, 0, "/region3/bpm_range", "", nullptr); - synth.dispatchMessage(client, 0, "/region4/bpm_range", "", nullptr); - std::vector expected { - "/region0/bpm_range,ff : { 0, 500 }", - "/region1/bpm_range,ff : { 34.1, 60.2 }", - "/region2/bpm_range,ff : { -3, 60 }", - "/region3/bpm_range,ff : { 20, -1 }", - "/region4/bpm_range,ff : { 20, 10 }", - }; - REQUIRE(messageList == expected); -} + SECTION("BPM range") + { + d.load(R"( + sample=kick.wav + sample=kick.wav lobpm=34.1 hibpm=60.2 + sample=kick.wav lobpm=-3 hibpm=60 + sample=kick.wav lobpm=20 hibpm=-1 + sample=kick.wav lobpm=20 hibpm=10 + )"); + REQUIRE_THAT( d.readAll("/region0/bpm_range"), Catch::Approx(std::vector{ 0, 500 })); + REQUIRE_THAT( d.readAll("/region1/bpm_range"), Catch::Approx(std::vector{ 34.1, 60.2 })); + REQUIRE_THAT( d.readAll("/region2/bpm_range"), Catch::Approx(std::vector{ -3, 60 })); + REQUIRE_THAT( d.readAll("/region3/bpm_range"), Catch::Approx(std::vector{ 20, -1 })); + REQUIRE_THAT( d.readAll("/region4/bpm_range"), Catch::Approx(std::vector{ 20, 10 })); + } -TEST_CASE("[Values] Rand range") -{ - 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 lorand=0.2 hirand=0.4 - sample=kick.wav lorand=-0.1 hirand=0.4 - sample=kick.wav lorand=0.2 hirand=-0.1 - sample=kick.wav lorand=0.2 hirand=0.1 - )"); - synth.dispatchMessage(client, 0, "/region0/rand_range", "", nullptr); - synth.dispatchMessage(client, 0, "/region1/rand_range", "", nullptr); - synth.dispatchMessage(client, 0, "/region2/rand_range", "", nullptr); - synth.dispatchMessage(client, 0, "/region3/rand_range", "", nullptr); - synth.dispatchMessage(client, 0, "/region4/rand_range", "", nullptr); - std::vector expected { - "/region0/rand_range,ff : { 0, 1 }", - "/region1/rand_range,ff : { 0.2, 0.4 }", - "/region2/rand_range,ff : { -0.1, 0.4 }", - "/region3/rand_range,ff : { 0.2, -0.1 }", - "/region4/rand_range,ff : { 0.2, 0.1 }", - }; - REQUIRE(messageList == expected); -} + SECTION("Rand range") + { + d.load(R"( + sample=kick.wav + sample=kick.wav lorand=0.2 hirand=0.4 + sample=kick.wav lorand=-0.1 hirand=0.4 + sample=kick.wav lorand=0.2 hirand=-0.1 + sample=kick.wav lorand=0.2 hirand=0.1 + )"); -TEST_CASE("[Values] Timer range") -{ - 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 lotimer=0.2 hitimer=0.4 - sample=kick.wav lotimer=-0.1 hitimer=0.4 - sample=kick.wav lotimer=0.2 hitimer=-0.1 - )"); - synth.dispatchMessage(client, 0, "/region0/timer_range", "", nullptr); - synth.dispatchMessage(client, 0, "/region0/use_timer_range", "", nullptr); - synth.dispatchMessage(client, 0, "/region1/timer_range", "", nullptr); - synth.dispatchMessage(client, 0, "/region1/use_timer_range", "", nullptr); - synth.dispatchMessage(client, 0, "/region2/timer_range", "", nullptr); - synth.dispatchMessage(client, 0, "/region2/use_timer_range", "", nullptr); - synth.dispatchMessage(client, 0, "/region3/timer_range", "", nullptr); - synth.dispatchMessage(client, 0, "/region3/use_timer_range", "", nullptr); - std::vector expected { - "/region0/timer_range,ff : { 0, 3.40282e+38 }", - "/region0/use_timer_range,F : { }", - "/region1/timer_range,ff : { 0.2, 0.4 }", - "/region1/use_timer_range,T : { }", - "/region2/timer_range,ff : { 0, 0.4 }", - "/region2/use_timer_range,T : { }", - "/region3/timer_range,ff : { 0.2, 3.40282e+38 }", - "/region3/use_timer_range,T : { }", - }; - REQUIRE(messageList == expected); -} + REQUIRE_THAT( d.readAll("/region0/rand_range"), Catch::Approx(std::vector{ 0, 1 })); + REQUIRE_THAT( d.readAll("/region1/rand_range"), Catch::Approx(std::vector{ 0.2, 0.4 })); + REQUIRE_THAT( d.readAll("/region2/rand_range"), Catch::Approx(std::vector{ -0.1, 0.4 })); + REQUIRE_THAT( d.readAll("/region3/rand_range"), Catch::Approx(std::vector{ 0.2, -0.1 })); + REQUIRE_THAT( d.readAll("/region4/rand_range"), Catch::Approx(std::vector{ 0.2, 0.1 })); + } -TEST_CASE("[Values] Sequence length") -{ - 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 seq_length=12 - sample=kick.wav seq_length=-1 - sample=kick.wav seq_length=12 seq_length=-1 - )"); - synth.dispatchMessage(client, 0, "/region0/seq_length", "", nullptr); - synth.dispatchMessage(client, 0, "/region1/seq_length", "", nullptr); - synth.dispatchMessage(client, 0, "/region2/seq_length", "", nullptr); - // TODO: activate for the new region parser ; ignore the second value - // synth.dispatchMessage(client, 0, "/region3/seq_length", "", nullptr); - std::vector expected { - "/region0/seq_length,i : { 1 }", - "/region1/seq_length,i : { 12 }", - "/region2/seq_length,i : { 1 }", - // TODO: activate for the new region parser ; ignore the second value - // "/region3/seq_length,f : { 12 }", - }; - REQUIRE(messageList == expected); -} + SECTION("Timer range") + { + d.load(R"( + sample=kick.wav + sample=kick.wav lotimer=0.2 hitimer=0.4 + sample=kick.wav lotimer=-0.1 hitimer=0.4 + sample=kick.wav lotimer=0.2 hitimer=-0.1 + )"); + REQUIRE_THAT( d.readAll("/region0/timer_range"), Catch::Approx(std::vector{ 0, 3.40282e+38 })); + REQUIRE_THAT( d.readAll("/region1/timer_range"), Catch::Approx(std::vector{ 0.2, 0.4 })); + REQUIRE_THAT( d.readAll("/region2/timer_range"), Catch::Approx(std::vector{ 0, 0.4 })); + REQUIRE_THAT( d.readAll("/region3/timer_range"), Catch::Approx(std::vector{ 0.2, 3.40282e+38 })); + REQUIRE( d.read("/region0/use_timer_range") == OSC::False); + REQUIRE( d.read("/region1/use_timer_range") == OSC::True); + REQUIRE( d.read("/region2/use_timer_range") == OSC::True); + REQUIRE( d.read("/region3/use_timer_range") == OSC::True); + } -TEST_CASE("[Values] Sequence position") -{ - 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 seq_position=12 - sample=kick.wav seq_position=-1 - sample=kick.wav seq_position=12 seq_position=-1 - )"); - synth.dispatchMessage(client, 0, "/region0/seq_position", "", nullptr); - synth.dispatchMessage(client, 0, "/region1/seq_position", "", nullptr); - synth.dispatchMessage(client, 0, "/region2/seq_position", "", nullptr); - // TODO: activate for the new region parser ; ignore the second value - // synth.dispatchMessage(client, 0, "/region3/seq_position", "", nullptr); - std::vector expected { - "/region0/seq_position,i : { 1 }", - "/region1/seq_position,i : { 12 }", - "/region2/seq_position,i : { 1 }", + SECTION("Sequence length") + { + d.load(R"( + sample=kick.wav + sample=kick.wav seq_length=12 + sample=kick.wav seq_length=-1 + sample=kick.wav seq_length=12 seq_length=-1 + )"); + REQUIRE( d.read("/region0/seq_length") == 1); + REQUIRE( d.read("/region1/seq_length") == 12); + REQUIRE( d.read("/region2/seq_length") == 1); // TODO: activate for the new region parser ; ignore the second value - // "/region3/seq_position,f : { 12 }", - }; - REQUIRE(messageList == expected); -} + // REQUIRE( d.read("/region3/seq_length") == 12); + } -TEST_CASE("[Values] Trigger type") -{ - 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 trigger=release - sample=kick.wav trigger=release_key - sample=kick.wav trigger=legato - sample=kick.wav trigger=first - sample=kick.wav trigger=nothing - sample=kick.wav trigger=release trigger=attack - )"); - synth.dispatchMessage(client, 0, "/region0/trigger", "", nullptr); - synth.dispatchMessage(client, 0, "/region1/trigger", "", nullptr); - synth.dispatchMessage(client, 0, "/region2/trigger", "", nullptr); - synth.dispatchMessage(client, 0, "/region3/trigger", "", nullptr); - synth.dispatchMessage(client, 0, "/region4/trigger", "", nullptr); - synth.dispatchMessage(client, 0, "/region5/trigger", "", nullptr); - synth.dispatchMessage(client, 0, "/region6/trigger", "", nullptr); - std::vector expected { - "/region0/trigger,s : { attack }", - "/region1/trigger,s : { release }", - "/region2/trigger,s : { release_key }", - "/region3/trigger,s : { legato }", - "/region4/trigger,s : { first }", - "/region5/trigger,s : { attack }", - "/region6/trigger,s : { attack }", - }; - REQUIRE(messageList == expected); -} + SECTION("Sequence position") + { + d.load(R"( + sample=kick.wav + sample=kick.wav seq_position=12 + sample=kick.wav seq_position=-1 + sample=kick.wav seq_position=12 seq_position=-1 + )"); + REQUIRE( d.read("/region0/seq_position") == 1); + REQUIRE( d.read("/region1/seq_position") == 12); + REQUIRE( d.read("/region2/seq_position") == 1); + // TODO: activate for the new region parser ; ignore the second value + // REQUIRE( d.read("/region3/seq_position") == ); + } -TEST_CASE("[Values] Start on cc range") -{ - 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 on_locc1=15 - sample=kick.wav on_hicc1=84 - sample=kick.wav on_locc1=15 on_hicc1=84 - sample=kick.wav on_lohdcc2=0.1 - sample=kick.wav on_hihdcc2=0.4 - sample=kick.wav on_lohdcc2=0.1 on_hihdcc2=0.4 - )"); - synth.dispatchMessage(client, 0, "/region0/start_cc_range1", "", nullptr); - synth.dispatchMessage(client, 0, "/region0/start_cc_range2", "", nullptr); - synth.dispatchMessage(client, 0, "/region1/start_cc_range1", "", nullptr); - synth.dispatchMessage(client, 0, "/region2/start_cc_range1", "", nullptr); - synth.dispatchMessage(client, 0, "/region3/start_cc_range1", "", nullptr); - synth.dispatchMessage(client, 0, "/region4/start_cc_range2", "", nullptr); - synth.dispatchMessage(client, 0, "/region5/start_cc_range2", "", nullptr); - synth.dispatchMessage(client, 0, "/region6/start_cc_range2", "", nullptr); - std::vector expected { - "/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.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 }", - }; - REQUIRE(messageList == expected); -} + SECTION("Trigger type") + { + d.load(R"( + sample=kick.wav + sample=kick.wav trigger=release + sample=kick.wav trigger=release_key + sample=kick.wav trigger=legato + sample=kick.wav trigger=first + sample=kick.wav trigger=nothing + sample=kick.wav trigger=release trigger=attack + )"); + REQUIRE( d.read("/region0/trigger") == "attack"); + REQUIRE( d.read("/region1/trigger") == "release"); + REQUIRE( d.read("/region2/trigger") == "release_key"); + REQUIRE( d.read("/region3/trigger") == "legato"); + REQUIRE( d.read("/region4/trigger") == "first"); + REQUIRE( d.read("/region5/trigger") == "attack"); + REQUIRE( d.read("/region6/trigger") == "attack"); + } -TEST_CASE("[Values] Volume") -{ - Synth synth; - std::vector messageList; - Client client(&messageList); - client.setReceiveCallback(&simpleMessageReceiver); + SECTION("Start on cc range") + { + d.load(R"( + sample=kick.wav + sample=kick.wav on_locc1=15 + sample=kick.wav on_hicc1=84 + sample=kick.wav on_locc1=15 on_hicc1=84 + sample=kick.wav on_lohdcc2=0.1 + sample=kick.wav on_hihdcc2=0.4 + sample=kick.wav on_lohdcc2=0.1 on_hihdcc2=0.4 + )"); + REQUIRE( d.read("/region0/start_cc_range1") == OSC::None); + REQUIRE( d.read("/region0/start_cc_range2") == OSC::None); + REQUIRE_THAT( d.readAll("/region1/start_cc_range1"), Catch::Approx(std::vector{ 15_norm, 1 })); + REQUIRE_THAT( d.readAll("/region2/start_cc_range1"), Catch::Approx(std::vector{ 0, 85_norm })); + REQUIRE_THAT( d.readAll("/region3/start_cc_range1"), Catch::Approx(std::vector{ 15_norm, 85_norm })); + REQUIRE_THAT( d.readAll("/region4/start_cc_range2"), Catch::Approx(std::vector{ 0.1, 1 })); + REQUIRE_THAT( d.readAll("/region5/start_cc_range2"), Catch::Approx(std::vector{ 0, 0.4f })); + REQUIRE_THAT( d.readAll("/region6/start_cc_range2"), Catch::Approx(std::vector{ 0.1, 0.4f })); + } - SECTION("Basic") + SECTION("Volume Basic") { - synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + d.load(R"( sample=kick.wav sample=kick.wav volume=4.2 sample=kick.wav gain=-200 )"); - synth.dispatchMessage(client, 0, "/region0/volume", "", nullptr); - synth.dispatchMessage(client, 0, "/region1/volume", "", nullptr); + REQUIRE( d.read("/region0/volume") == 0); + REQUIRE( d.read("/region1/volume") == 4.2f); // TODO: activate for the new region parser ; allow oob - // synth.dispatchMessage(client, 0, "/region2/volume", "", nullptr); - std::vector expected { - "/region0/volume,f : { 0 }", - "/region1/volume,f : { 4.2 }", - // "/region2/volume,f : { -200 }", - }; - REQUIRE(messageList == expected); + // REQUIRE( d.read<>("/region2/volume") == ); } - SECTION("CC Depth") + SECTION("Volume CC Depth") { - synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + d.load(R"( sample=kick.wav sample=kick.wav volume_oncc42=4.2 sample=kick.wav gain_oncc2=-10 )"); - synth.dispatchMessage(client, 0, "/region0/volume_cc42", "", nullptr); - synth.dispatchMessage(client, 0, "/region1/volume_cc42", "", nullptr); - synth.dispatchMessage(client, 0, "/region2/volume_cc2", "", nullptr); - std::vector expected { - "/region0/volume_cc42,N : { }", - "/region1/volume_cc42,f : { 4.2 }", - "/region2/volume_cc2,f : { -10 }", - }; - REQUIRE(messageList == expected); + REQUIRE( d.read("/region0/volume_cc42") == OSC::None); + REQUIRE( d.read("/region1/volume_cc42") == 4.2f); + REQUIRE( d.read("/region2/volume_cc2") == -10.0f); } - SECTION("CC Params") + SECTION("Volume CC Params") { - synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + d.load(R"( sample=kick.wav sample=kick.wav volume_stepcc42=4.2 sample=kick.wav volume_smoothcc42=4 @@ -1320,33 +756,21 @@ TEST_CASE("[Values] Volume") sample=kick.wav volume_smoothcc42=-4 sample=kick.wav volume_curvecc42=300 )"); - synth.dispatchMessage(client, 0, "/region0/volume_stepcc42", "", nullptr); - synth.dispatchMessage(client, 0, "/region0/volume_smoothcc42", "", nullptr); - synth.dispatchMessage(client, 0, "/region0/volume_curvecc42", "", nullptr); - synth.dispatchMessage(client, 0, "/region1/volume_stepcc42", "", nullptr); - synth.dispatchMessage(client, 0, "/region2/volume_smoothcc42", "", nullptr); - synth.dispatchMessage(client, 0, "/region3/volume_curvecc42", "", nullptr); + REQUIRE( d.read("/region0/volume_stepcc42") == OSC::None); + REQUIRE( d.read("/region0/volume_smoothcc42") == OSC::None); + REQUIRE( d.read("/region0/volume_curvecc42") == OSC::None); + REQUIRE( d.read("/region1/volume_stepcc42") == 4.2f); + REQUIRE( d.read("/region2/volume_smoothcc42") == 4 ); + REQUIRE( d.read("/region3/volume_curvecc42") == 2); // TODO: activate for the new region parser ; ignore oob - // synth.dispatchMessage(client, 0, "/region4/volume_stepcc42", "", nullptr); - // synth.dispatchMessage(client, 0, "/region5/volume_smoothcc42", "", nullptr); - // synth.dispatchMessage(client, 0, "/region6/volume_curvecc42", "", nullptr); - std::vector expected { - "/region0/volume_stepcc42,N : { }", - "/region0/volume_smoothcc42,N : { }", - "/region0/volume_curvecc42,N : { }", - "/region1/volume_stepcc42,f : { 4.2 }", - "/region2/volume_smoothcc42,i : { 4 }", - "/region3/volume_curvecc42,i : { 2 }", - // "/region4/volume_stepcc42,N : { }", - // "/region5/volume_smoothcc42,N : { }", - // "/region6/volume_curvecc42,N : { }", - }; - REQUIRE(messageList == expected); + // REQUIRE( d.read("/region4/volume_stepcc42") == OSC::None); + // REQUIRE( d.read("/region5/volume_smoothcc42") == OSC::None); + // REQUIRE( d.read("/region6/volume_curvecc42") == OSC::None); } - SECTION("CC Params (with gain_)") + SECTION("Volume CC Params (with gain_)") { - synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + d.load(R"( sample=kick.wav sample=kick.wav gain_stepcc42=4.2 sample=kick.wav gain_smoothcc42=4 @@ -1355,79 +779,46 @@ TEST_CASE("[Values] Volume") sample=kick.wav gain_smoothcc42=-4 sample=kick.wav gain_curvecc42=300 )"); - synth.dispatchMessage(client, 0, "/region0/volume_stepcc42", "", nullptr); - synth.dispatchMessage(client, 0, "/region0/volume_smoothcc42", "", nullptr); - synth.dispatchMessage(client, 0, "/region0/volume_curvecc42", "", nullptr); - synth.dispatchMessage(client, 0, "/region1/volume_stepcc42", "", nullptr); - synth.dispatchMessage(client, 0, "/region2/volume_smoothcc42", "", nullptr); - synth.dispatchMessage(client, 0, "/region3/volume_curvecc42", "", nullptr); + REQUIRE( d.read("/region0/volume_stepcc42") == OSC::None); + REQUIRE( d.read("/region0/volume_smoothcc42") == OSC::None); + REQUIRE( d.read("/region0/volume_curvecc42") == OSC::None); + REQUIRE( d.read("/region1/volume_stepcc42") == 4.2f); + REQUIRE( d.read("/region2/volume_smoothcc42") == 4 ); + REQUIRE( d.read("/region3/volume_curvecc42") == 2); // TODO: activate for the new region parser ; ignore oob - // synth.dispatchMessage(client, 0, "/region4/volume_stepcc42", "", nullptr); - // synth.dispatchMessage(client, 0, "/region5/volume_smoothcc42", "", nullptr); - // synth.dispatchMessage(client, 0, "/region6/volume_curvecc42", "", nullptr); - std::vector expected { - "/region0/volume_stepcc42,N : { }", - "/region0/volume_smoothcc42,N : { }", - "/region0/volume_curvecc42,N : { }", - "/region1/volume_stepcc42,f : { 4.2 }", - "/region2/volume_smoothcc42,i : { 4 }", - "/region3/volume_curvecc42,i : { 2 }", - // "/region4/volume_stepcc42,N : { }", - // "/region5/volume_smoothcc42,N : { }", - // "/region6/volume_curvecc42,N : { }", - }; - REQUIRE(messageList == expected); + // REQUIRE( d.read("/region4/volume_stepcc42") == OSC::None); + // REQUIRE( d.read("/region5/volume_smoothcc42") == OSC::None); + // REQUIRE( d.read("/region6/volume_curvecc42") == OSC::None); } -} - -TEST_CASE("[Values] Pan") -{ - Synth synth; - std::vector messageList; - Client client(&messageList); - client.setReceiveCallback(&simpleMessageReceiver); - SECTION("Basic") + SECTION("Pan Basic") { - synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + d.load(R"( sample=kick.wav sample=kick.wav pan=4.2 sample=kick.wav pan=-200 )"); - synth.dispatchMessage(client, 0, "/region0/pan", "", nullptr); - synth.dispatchMessage(client, 0, "/region1/pan", "", nullptr); - // TODO: activate for the new region parser ; accept oob - // synth.dispatchMessage(client, 0, "/region2/pan", "", nullptr); - std::vector expected { - "/region0/pan,f : { 0 }", - "/region1/pan,f : { 4.2 }", - // TODO: activate for the new region parser ; accept oob - // "/region2/pan,f : { -200 }", - }; - REQUIRE(messageList == expected); + REQUIRE( d.read("/region0/pan") == 0); + REQUIRE( d.read("/region1/pan") == 4.2f); + // TODO: activate for the new region parser ; allow oob + // REQUIRE( d.read<>("/region2/pan") == ); } - SECTION("CC Depth") + SECTION("Pan CC Depth") { - synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + d.load(R"( sample=kick.wav sample=kick.wav pan_oncc42=4.2 sample=kick.wav pan_oncc2=-10 )"); - synth.dispatchMessage(client, 0, "/region0/pan_cc42", "", nullptr); - synth.dispatchMessage(client, 0, "/region1/pan_cc42", "", nullptr); - synth.dispatchMessage(client, 0, "/region2/pan_cc2", "", nullptr); - std::vector expected { - "/region0/pan_cc42,N : { }", - "/region1/pan_cc42,f : { 4.2 }", - "/region2/pan_cc2,f : { -10 }", - }; - REQUIRE(messageList == expected); + REQUIRE( d.read("/region0/pan_cc42") == OSC::None); + REQUIRE( d.read("/region1/pan_cc42") == 4.2f); + REQUIRE( d.read("/region2/pan_cc2") == -10.0f); } - SECTION("CC Params") + SECTION("Pan CC Params") { - synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + d.load(R"( sample=kick.wav sample=kick.wav pan_stepcc42=4.2 sample=kick.wav pan_smoothcc42=4 @@ -1436,79 +827,46 @@ TEST_CASE("[Values] Pan") sample=kick.wav pan_smoothcc42=-4 sample=kick.wav pan_curvecc42=300 )"); - synth.dispatchMessage(client, 0, "/region0/pan_stepcc42", "", nullptr); - synth.dispatchMessage(client, 0, "/region0/pan_smoothcc42", "", nullptr); - synth.dispatchMessage(client, 0, "/region0/pan_curvecc42", "", nullptr); - synth.dispatchMessage(client, 0, "/region1/pan_stepcc42", "", nullptr); - synth.dispatchMessage(client, 0, "/region2/pan_smoothcc42", "", nullptr); - synth.dispatchMessage(client, 0, "/region3/pan_curvecc42", "", nullptr); + REQUIRE( d.read("/region0/pan_stepcc42") == OSC::None); + REQUIRE( d.read("/region0/pan_smoothcc42") == OSC::None); + REQUIRE( d.read("/region0/pan_curvecc42") == OSC::None); + REQUIRE( d.read("/region1/pan_stepcc42") == 4.2f); + REQUIRE( d.read("/region2/pan_smoothcc42") == 4 ); + REQUIRE( d.read("/region3/pan_curvecc42") == 2); // TODO: activate for the new region parser ; ignore oob - // synth.dispatchMessage(client, 0, "/region4/pan_stepcc42", "", nullptr); - // synth.dispatchMessage(client, 0, "/region5/pan_smoothcc42", "", nullptr); - // synth.dispatchMessage(client, 0, "/region6/pan_curvecc42", "", nullptr); - std::vector expected { - "/region0/pan_stepcc42,N : { }", - "/region0/pan_smoothcc42,N : { }", - "/region0/pan_curvecc42,N : { }", - "/region1/pan_stepcc42,f : { 4.2 }", - "/region2/pan_smoothcc42,i : { 4 }", - "/region3/pan_curvecc42,i : { 2 }", - // "/region4/pan_stepcc42,N : { }", - // "/region5/pan_smoothcc42,N : { }", - // "/region6/pan_curvecc42,N : { }", - }; - REQUIRE(messageList == expected); + // REQUIRE( d.read("/region4/pan_stepcc42") == OSC::None); + // REQUIRE( d.read("/region5/pan_smoothcc42") == OSC::None); + // REQUIRE( d.read("/region6/pan_curvecc42") == OSC::None); } -} - -TEST_CASE("[Values] Width") -{ - Synth synth; - std::vector messageList; - Client client(&messageList); - client.setReceiveCallback(&simpleMessageReceiver); - SECTION("Basic") + SECTION("Width Basic") { - synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + d.load(R"( sample=kick.wav sample=kick.wav width=4.2 sample=kick.wav width=-200 )"); - synth.dispatchMessage(client, 0, "/region0/width", "", nullptr); - synth.dispatchMessage(client, 0, "/region1/width", "", nullptr); - // TODO: activate for the new region parser ; accept oob - // synth.dispatchMessage(client, 0, "/region2/width", "", nullptr); - std::vector expected { - "/region0/width,f : { 100 }", - "/region1/width,f : { 4.2 }", - // TODO: activate for the new region parser ; accept oob - // "/region2/width,f : { -200 }", - }; - REQUIRE(messageList == expected); + REQUIRE( d.read("/region0/width") == 100.0f); + REQUIRE( d.read("/region1/width") == 4.2f); + // TODO: activate for the new region parser ; allow oob + // REQUIRE( d.read<>("/region2/width") == -200.0f); } - SECTION("CC Depth") + SECTION("Width CC Depth") { - synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + d.load(R"( sample=kick.wav sample=kick.wav width_oncc42=4.2 sample=kick.wav width_oncc2=-10 )"); - synth.dispatchMessage(client, 0, "/region0/width_cc42", "", nullptr); - synth.dispatchMessage(client, 0, "/region1/width_cc42", "", nullptr); - synth.dispatchMessage(client, 0, "/region2/width_cc2", "", nullptr); - std::vector expected { - "/region0/width_cc42,N : { }", - "/region1/width_cc42,f : { 4.2 }", - "/region2/width_cc2,f : { -10 }", - }; - REQUIRE(messageList == expected); + REQUIRE( d.read("/region0/width_cc42") == OSC::None); + REQUIRE( d.read("/region1/width_cc42") == 4.2f); + REQUIRE( d.read("/region2/width_cc2") == -10.0f); } - SECTION("CC Params") + SECTION("Width CC Params") { - synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + d.load(R"( sample=kick.wav sample=kick.wav width_stepcc42=4.2 sample=kick.wav width_smoothcc42=4 @@ -1517,79 +875,46 @@ TEST_CASE("[Values] Width") sample=kick.wav width_smoothcc42=-4 sample=kick.wav width_curvecc42=300 )"); - synth.dispatchMessage(client, 0, "/region0/width_stepcc42", "", nullptr); - synth.dispatchMessage(client, 0, "/region0/width_smoothcc42", "", nullptr); - synth.dispatchMessage(client, 0, "/region0/width_curvecc42", "", nullptr); - synth.dispatchMessage(client, 0, "/region1/width_stepcc42", "", nullptr); - synth.dispatchMessage(client, 0, "/region2/width_smoothcc42", "", nullptr); - synth.dispatchMessage(client, 0, "/region3/width_curvecc42", "", nullptr); + REQUIRE( d.read("/region0/width_stepcc42") == OSC::None); + REQUIRE( d.read("/region0/width_smoothcc42") == OSC::None); + REQUIRE( d.read("/region0/width_curvecc42") == OSC::None); + REQUIRE( d.read("/region1/width_stepcc42") == 4.2f); + REQUIRE( d.read("/region2/width_smoothcc42") == 4 ); + REQUIRE( d.read("/region3/width_curvecc42") == 2); // TODO: activate for the new region parser ; ignore oob - // synth.dispatchMessage(client, 0, "/region4/width_stepcc42", "", nullptr); - // synth.dispatchMessage(client, 0, "/region5/width_smoothcc42", "", nullptr); - // synth.dispatchMessage(client, 0, "/region6/width_curvecc42", "", nullptr); - std::vector expected { - "/region0/width_stepcc42,N : { }", - "/region0/width_smoothcc42,N : { }", - "/region0/width_curvecc42,N : { }", - "/region1/width_stepcc42,f : { 4.2 }", - "/region2/width_smoothcc42,i : { 4 }", - "/region3/width_curvecc42,i : { 2 }", - // "/region4/width_stepcc42,N : { }", - // "/region5/width_smoothcc42,N : { }", - // "/region6/width_curvecc42,N : { }", - }; - REQUIRE(messageList == expected); + // REQUIRE( d.read("/region4/width_stepcc42") == OSC::None); + // REQUIRE( d.read("/region5/width_smoothcc42") == OSC::None); + // REQUIRE( d.read("/region6/width_curvecc42") == OSC::None); } -} - -TEST_CASE("[Values] Position") -{ - Synth synth; - std::vector messageList; - Client client(&messageList); - client.setReceiveCallback(&simpleMessageReceiver); - SECTION("Basic") + SECTION("Position Basic") { - synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + d.load(R"( sample=kick.wav sample=kick.wav position=4.2 sample=kick.wav position=-200 )"); - synth.dispatchMessage(client, 0, "/region0/position", "", nullptr); - synth.dispatchMessage(client, 0, "/region1/position", "", nullptr); - // TODO: activate for the new region parser; accept oob - // synth.dispatchMessage(client, 0, "/region2/position", "", nullptr); - std::vector expected { - "/region0/position,f : { 0 }", - "/region1/position,f : { 4.2 }", - // TODO: activate for the new region parser; accept oob - // "/region2/position,f : { -200 }", - }; - REQUIRE(messageList == expected); + REQUIRE( d.read("/region0/position") == 0); + REQUIRE( d.read("/region1/position") == 4.2f); + // TODO: activate for the new region parser ; allow oob + // REQUIRE( d.read<>("/region2/position") == ); } - SECTION("CC Depth") + SECTION("Position CC Depth") { - synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + d.load(R"( sample=kick.wav sample=kick.wav position_oncc42=4.2 sample=kick.wav position_oncc2=-10 )"); - synth.dispatchMessage(client, 0, "/region0/position_cc42", "", nullptr); - synth.dispatchMessage(client, 0, "/region1/position_cc42", "", nullptr); - synth.dispatchMessage(client, 0, "/region2/position_cc2", "", nullptr); - std::vector expected { - "/region0/position_cc42,N : { }", - "/region1/position_cc42,f : { 4.2 }", - "/region2/position_cc2,f : { -10 }", - }; - REQUIRE(messageList == expected); + REQUIRE( d.read("/region0/position_cc42") == OSC::None); + REQUIRE( d.read("/region1/position_cc42") == 4.2f); + REQUIRE( d.read("/region2/position_cc2") == -10.0f); } - SECTION("CC Params") + SECTION("Position CC Params") { - synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + d.load(R"( sample=kick.wav sample=kick.wav position_stepcc42=4.2 sample=kick.wav position_smoothcc42=4 @@ -1598,79 +923,46 @@ TEST_CASE("[Values] Position") sample=kick.wav position_smoothcc42=-4 sample=kick.wav position_curvecc42=300 )"); - synth.dispatchMessage(client, 0, "/region0/position_stepcc42", "", nullptr); - synth.dispatchMessage(client, 0, "/region0/position_smoothcc42", "", nullptr); - synth.dispatchMessage(client, 0, "/region0/position_curvecc42", "", nullptr); - synth.dispatchMessage(client, 0, "/region1/position_stepcc42", "", nullptr); - synth.dispatchMessage(client, 0, "/region2/position_smoothcc42", "", nullptr); - synth.dispatchMessage(client, 0, "/region3/position_curvecc42", "", nullptr); + REQUIRE( d.read("/region0/position_stepcc42") == OSC::None); + REQUIRE( d.read("/region0/position_smoothcc42") == OSC::None); + REQUIRE( d.read("/region0/position_curvecc42") == OSC::None); + REQUIRE( d.read("/region1/position_stepcc42") == 4.2f); + REQUIRE( d.read("/region2/position_smoothcc42") == 4 ); + REQUIRE( d.read("/region3/position_curvecc42") == 2); // TODO: activate for the new region parser ; ignore oob - // synth.dispatchMessage(client, 0, "/region4/position_stepcc42", "", nullptr); - // synth.dispatchMessage(client, 0, "/region5/position_smoothcc42", "", nullptr); - // synth.dispatchMessage(client, 0, "/region6/position_curvecc42", "", nullptr); - std::vector expected { - "/region0/position_stepcc42,N : { }", - "/region0/position_smoothcc42,N : { }", - "/region0/position_curvecc42,N : { }", - "/region1/position_stepcc42,f : { 4.2 }", - "/region2/position_smoothcc42,i : { 4 }", - "/region3/position_curvecc42,i : { 2 }", - // "/region4/position_stepcc42,N : { }", - // "/region5/position_smoothcc42,N : { }", - // "/region6/position_curvecc42,N : { }", - }; - REQUIRE(messageList == expected); + // REQUIRE( d.read("/region4/position_stepcc42") == OSC::None); + // REQUIRE( d.read("/region5/position_smoothcc42") == OSC::None); + // REQUIRE( d.read("/region6/position_curvecc42") == OSC::None); } -} -TEST_CASE("[Values] Amplitude") -{ - Synth synth; - std::vector messageList; - Client client(&messageList); - client.setReceiveCallback(&simpleMessageReceiver); - - SECTION("Basic") + SECTION("Amplitude Basic") { - synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + d.load(R"( sample=kick.wav sample=kick.wav amplitude=4.2 sample=kick.wav amplitude=-200 )"); - synth.dispatchMessage(client, 0, "/region0/amplitude", "", nullptr); - synth.dispatchMessage(client, 0, "/region1/amplitude", "", nullptr); - // TODO: activate for the new region parser; ignore oob - // synth.dispatchMessage(client, 0, "/region2/amplitude", "", nullptr); - std::vector expected { - "/region0/amplitude,f : { 100 }", - "/region1/amplitude,f : { 4.2 }", - // "/region2/amplitude,f : { 100 }", - }; - REQUIRE(messageList == expected); + REQUIRE( d.read("/region0/amplitude") == 100.0f); + REQUIRE( d.read("/region1/amplitude") == 4.2f); + // TODO: activate for the new region parser ; allow oob + // REQUIRE( d.read<>("/region2/amplitude") == ); } - SECTION("CC Depth") + SECTION("Amplitude CC Depth") { - synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + d.load(R"( sample=kick.wav sample=kick.wav amplitude_oncc42=4.2 sample=kick.wav amplitude_oncc2=-10 )"); - synth.dispatchMessage(client, 0, "/region0/amplitude_cc42", "", nullptr); - synth.dispatchMessage(client, 0, "/region1/amplitude_cc42", "", nullptr); - // TODO: activate for the new region parser ; ignore oob - // synth.dispatchMessage(client, 0, "/region2/amplitude_cc2", "", nullptr); - std::vector expected { - "/region0/amplitude_cc42,N : { }", - "/region1/amplitude_cc42,f : { 4.2 }", - // "/region2/amplitude_cc2,N : { }", - }; - REQUIRE(messageList == expected); + REQUIRE( d.read("/region0/amplitude_cc42") == OSC::None); + REQUIRE( d.read("/region1/amplitude_cc42") == 4.2f); + REQUIRE( d.read("/region2/amplitude_cc2") == -10.0f); } - SECTION("CC Params") + SECTION("Amplitude CC Params") { - synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + d.load(R"( sample=kick.wav sample=kick.wav amplitude_stepcc42=4.2 sample=kick.wav amplitude_smoothcc42=4 @@ -1679,615 +971,348 @@ TEST_CASE("[Values] Amplitude") sample=kick.wav amplitude_smoothcc42=-4 sample=kick.wav amplitude_curvecc42=300 )"); - synth.dispatchMessage(client, 0, "/region0/amplitude_stepcc42", "", nullptr); - synth.dispatchMessage(client, 0, "/region0/amplitude_smoothcc42", "", nullptr); - synth.dispatchMessage(client, 0, "/region0/amplitude_curvecc42", "", nullptr); - synth.dispatchMessage(client, 0, "/region1/amplitude_stepcc42", "", nullptr); - synth.dispatchMessage(client, 0, "/region2/amplitude_smoothcc42", "", nullptr); - synth.dispatchMessage(client, 0, "/region3/amplitude_curvecc42", "", nullptr); + REQUIRE( d.read("/region0/amplitude_stepcc42") == OSC::None); + REQUIRE( d.read("/region0/amplitude_smoothcc42") == OSC::None); + REQUIRE( d.read("/region0/amplitude_curvecc42") == OSC::None); + REQUIRE( d.read("/region1/amplitude_stepcc42") == 4.2f); + REQUIRE( d.read("/region2/amplitude_smoothcc42") == 4 ); + REQUIRE( d.read("/region3/amplitude_curvecc42") == 2); // TODO: activate for the new region parser ; ignore oob - // synth.dispatchMessage(client, 0, "/region4/amplitude_stepcc42", "", nullptr); - // synth.dispatchMessage(client, 0, "/region5/amplitude_smoothcc42", "", nullptr); - // synth.dispatchMessage(client, 0, "/region6/amplitude_curvecc42", "", nullptr); - std::vector expected { - "/region0/amplitude_stepcc42,N : { }", - "/region0/amplitude_smoothcc42,N : { }", - "/region0/amplitude_curvecc42,N : { }", - "/region1/amplitude_stepcc42,f : { 4.2 }", - "/region2/amplitude_smoothcc42,i : { 4 }", - "/region3/amplitude_curvecc42,i : { 2 }", - // "/region4/amplitude_stepcc42,N : { }", - // "/region5/amplitude_smoothcc42,N : { }", - // "/region6/amplitude_curvecc42,N : { }", - }; - REQUIRE(messageList == expected); + // REQUIRE( d.read("/region4/amplitude_stepcc42") == OSC::None); + // REQUIRE( d.read("/region5/amplitude_smoothcc42") == OSC::None); + // REQUIRE( d.read("/region6/amplitude_curvecc42") == OSC::None); } -} -TEST_CASE("[Values] Amp Keycenter") -{ - 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 amp_keycenter=40 - sample=kick.wav amp_keycenter=-1 - sample=kick.wav amp_keycenter=c3 - )"); - synth.dispatchMessage(client, 0, "/region0/amp_keycenter", "", nullptr); - synth.dispatchMessage(client, 0, "/region1/amp_keycenter", "", nullptr); - // TODO: activate for the new region parser ; ignore oob and parse note - // synth.dispatchMessage(client, 0, "/region2/amp_keycenter", "", nullptr); - // synth.dispatchMessage(client, 0, "/region3/amp_keycenter", "", nullptr); - std::vector expected { - "/region0/amp_keycenter,i : { 60 }", - "/region1/amp_keycenter,i : { 40 }", - // "/region2/amp_keycenter,i : { 60 }", - // "/region3/amp_keycenter,i : { 48 }", - }; - REQUIRE(messageList == expected); -} + SECTION("Amp Keycenter") + { + d.load(R"( + sample=kick.wav + sample=kick.wav amp_keycenter=40 + sample=kick.wav amp_keycenter=-1 + sample=kick.wav amp_keycenter=c3 + )"); + REQUIRE( d.read("/region0/amp_keycenter") == 60 ); + REQUIRE( d.read("/region1/amp_keycenter") == 40 ); + REQUIRE( d.read("/region2/amp_keycenter") == 60 ); + REQUIRE( d.read("/region3/amp_keycenter") == 48 ); + } -TEST_CASE("[Values] Amp Keytrack") -{ - 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 amp_keytrack=10.1 - sample=kick.wav amp_keytrack=40 - )"); - synth.dispatchMessage(client, 0, "/region0/amp_keytrack", "", nullptr); - synth.dispatchMessage(client, 0, "/region1/amp_keytrack", "", nullptr); - // TODO: activate for the new region parser ; accept oob - // synth.dispatchMessage(client, 0, "/region2/amp_keytrack", "", nullptr); - std::vector expected { - "/region0/amp_keytrack,f : { 0 }", - "/region1/amp_keytrack,f : { 10.1 }", - // "/region2/amp_keytrack,f : { 40 }", - }; - REQUIRE(messageList == expected); -} + SECTION("Amp Keytrack") + { + d.load(R"( + sample=kick.wav + sample=kick.wav amp_keytrack=10.1 + sample=kick.wav amp_keytrack=40 + )"); + REQUIRE( d.read("/region0/amp_keytrack") == 0.0f); + REQUIRE( d.read("/region1/amp_keytrack") == 10.1f); + REQUIRE( d.read("/region2/amp_keytrack") == 40.0f); + std::vector expected { + "/region0/amp_keytrack,f : {}", + "/region1/amp_keytrack,f : {}", + // "/region2/amp_keytrack,f : {}", + }; + } -TEST_CASE("[Values] Amp Veltrack") +SECTION("Amp Veltrack") { - Synth synth; - std::vector messageList; - Client client(&messageList); - client.setReceiveCallback(&simpleMessageReceiver); - - SECTION("Basic") + SECTION("Amp veltrack basic") { - synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + d.load(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); + REQUIRE( d.read("/region0/amp_veltrack") == 100.0f); + REQUIRE( d.read("/region1/amp_veltrack") == 10.1f); + REQUIRE( d.read("/region2/amp_veltrack") == -132.0f); } - SECTION("CC") + SECTION("Amp veltrack CC") { - synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + d.load(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); + REQUIRE( d.read("/region0/amp_veltrack_cc1") == OSC::None); + REQUIRE( d.read("/region1/amp_veltrack_cc1") == 10.1f); + REQUIRE( d.read("/region1/amp_veltrack_curvecc1") == 3); + REQUIRE( d.read("/region2/amp_veltrack_cc2") == -40.0f); + REQUIRE( d.read("/region2/amp_veltrack_curvecc3") == 4); } -} - -TEST_CASE("[Values] Amp Random") -{ - 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 amp_random=10.1 - sample=kick.wav amp_random=-4 - )"); - synth.dispatchMessage(client, 0, "/region0/amp_random", "", nullptr); - synth.dispatchMessage(client, 0, "/region1/amp_random", "", nullptr); - // TODO: activate for the new region parser ; ignore oob - // synth.dispatchMessage(client, 0, "/region2/amp_random", "", nullptr); - std::vector expected { - "/region0/amp_random,f : { 0 }", - "/region1/amp_random,f : { 10.1 }", - // "/region2/amp_random,f : { 0 }", - }; - REQUIRE(messageList == expected); -} -TEST_CASE("[Values] Crossfade key range") -{ - Synth synth; - std::vector messageList; - Client client(&messageList); - client.setReceiveCallback(&simpleMessageReceiver); + SECTION("Amp Random") + { + d.load(R"( + sample=kick.wav + sample=kick.wav amp_random=10.1 + sample=kick.wav amp_random=-4 + )"); + REQUIRE( d.read("/region0/amp_random") == 0.0f); + REQUIRE( d.read("/region1/amp_random") == 10.1f); + REQUIRE( d.read("/region2/amp_random") == -4.0f); + } - SECTION("Xfin") + SECTION("Key Xfin") { - synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + d.load(R"( sample=kick.wav sample=kick.wav xfin_lokey=10 xfin_hikey=40 sample=kick.wav xfin_lokey=c4 xfin_hikey=b5 sample=kick.wav xfin_lokey=-10 xfin_hikey=40 sample=kick.wav xfin_lokey=10 xfin_hikey=140 )"); - synth.dispatchMessage(client, 0, "/region0/xfin_key_range", "", nullptr); - synth.dispatchMessage(client, 0, "/region1/xfin_key_range", "", nullptr); - synth.dispatchMessage(client, 0, "/region2/xfin_key_range", "", nullptr); - synth.dispatchMessage(client, 0, "/region3/xfin_key_range", "", nullptr); - synth.dispatchMessage(client, 0, "/region4/xfin_key_range", "", nullptr); - std::vector expected { - "/region0/xfin_key_range,ii : { 0, 0 }", - "/region1/xfin_key_range,ii : { 10, 40 }", - "/region2/xfin_key_range,ii : { 60, 83 }", - "/region3/xfin_key_range,ii : { 0, 40 }", - "/region4/xfin_key_range,ii : { 10, 0 }", - }; - REQUIRE(messageList == expected); + REQUIRE_THAT( d.readAll("/region0/xfin_key_range"), Catch::Approx(std::vector{ 0, 0 })); + REQUIRE_THAT( d.readAll("/region1/xfin_key_range"), Catch::Approx(std::vector{ 10, 40 })); + REQUIRE_THAT( d.readAll("/region2/xfin_key_range"), Catch::Approx(std::vector{ 60, 83 })); + REQUIRE_THAT( d.readAll("/region3/xfin_key_range"), Catch::Approx(std::vector{ 0, 40 })); + REQUIRE_THAT( d.readAll("/region4/xfin_key_range"), Catch::Approx(std::vector{ 10, 0 })); } - SECTION("Xfout") + SECTION("Key Xfout") { - synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + d.load(R"( sample=kick.wav sample=kick.wav xfout_lokey=10 xfout_hikey=40 sample=kick.wav xfout_lokey=c4 xfout_hikey=b5 sample=kick.wav xfout_lokey=-10 xfout_hikey=40 sample=kick.wav xfout_lokey=10 xfout_hikey=140 )"); - synth.dispatchMessage(client, 0, "/region0/xfout_key_range", "", nullptr); - synth.dispatchMessage(client, 0, "/region1/xfout_key_range", "", nullptr); - synth.dispatchMessage(client, 0, "/region2/xfout_key_range", "", nullptr); - synth.dispatchMessage(client, 0, "/region3/xfout_key_range", "", nullptr); - synth.dispatchMessage(client, 0, "/region4/xfout_key_range", "", nullptr); - std::vector expected { - "/region0/xfout_key_range,ii : { 127, 127 }", - "/region1/xfout_key_range,ii : { 10, 40 }", - "/region2/xfout_key_range,ii : { 60, 83 }", - "/region3/xfout_key_range,ii : { 127, 40 }", - "/region4/xfout_key_range,ii : { 10, 127 }", - }; - REQUIRE(messageList == expected); + REQUIRE_THAT( d.readAll("/region0/xfout_key_range"), Catch::Approx(std::vector{ 127, 127 })); + REQUIRE_THAT( d.readAll("/region1/xfout_key_range"), Catch::Approx(std::vector{ 10, 40 })); + REQUIRE_THAT( d.readAll("/region2/xfout_key_range"), Catch::Approx(std::vector{ 60, 83 })); + REQUIRE_THAT( d.readAll("/region3/xfout_key_range"), Catch::Approx(std::vector{ 127, 40 })); + REQUIRE_THAT( d.readAll("/region4/xfout_key_range"), Catch::Approx(std::vector{ 10, 127 })); } } - - -TEST_CASE("[Values] Crossfade velocity range") -{ - Synth synth; - std::vector messageList; - Client client(&messageList); - client.setReceiveCallback(&simpleMessageReceiver); - - SECTION("Xfin") + SECTION("Velocity Xfin") { - synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + d.load(R"( sample=kick.wav sample=kick.wav xfin_lovel=10 xfin_hivel=40 sample=kick.wav xfin_lovel=-10 xfin_hivel=40 sample=kick.wav xfin_lovel=10 xfin_hivel=140 )"); - synth.dispatchMessage(client, 0, "/region0/xfin_vel_range", "", nullptr); - synth.dispatchMessage(client, 0, "/region1/xfin_vel_range", "", nullptr); - synth.dispatchMessage(client, 0, "/region2/xfin_vel_range", "", nullptr); - 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.322835 }", - "/region2/xfin_vel_range,ff : { -0.0787402, 0.322835 }", - "/region3/xfin_vel_range,ff : { 0.0787402, 1.10236 }", - }; - REQUIRE(messageList == expected); + REQUIRE_THAT( d.readAll("/region0/xfin_vel_range"), Catch::Approx(std::vector{ 0, 0 })); + REQUIRE_THAT( d.readAll("/region1/xfin_vel_range"), Catch::Approx(std::vector{ 10_norm, 41_norm })); + REQUIRE_THAT( d.readAll("/region2/xfin_vel_range"), Catch::Approx(std::vector{ -10_norm, 41_norm })); + REQUIRE_THAT( d.readAll("/region3/xfin_vel_range"), Catch::Approx(std::vector{ 10_norm, 140_norm })); } - SECTION("Xfout") + SECTION("Velocity Xfout") { - synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + d.load(R"( sample=kick.wav sample=kick.wav xfout_lovel=10 xfout_hivel=40 sample=kick.wav xfout_lovel=-10 xfout_hivel=40 sample=kick.wav xfout_lovel=10 xfout_hivel=140 )"); - synth.dispatchMessage(client, 0, "/region0/xfout_vel_range", "", nullptr); - synth.dispatchMessage(client, 0, "/region1/xfout_vel_range", "", nullptr); - synth.dispatchMessage(client, 0, "/region2/xfout_vel_range", "", nullptr); - 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.322835 }", - "/region2/xfout_vel_range,ff : { -0.0787402, 0.322835 }", - "/region3/xfout_vel_range,ff : { 0.0787402, 1.10236 }", - }; - REQUIRE(messageList == expected); + REQUIRE_THAT( d.readAll("/region0/xfout_vel_range"), Catch::Approx(std::vector{ 1, 1 })); + REQUIRE_THAT( d.readAll("/region1/xfout_vel_range"), Catch::Approx(std::vector{ 10_norm, 41_norm })); + REQUIRE_THAT( d.readAll("/region2/xfout_vel_range"), Catch::Approx(std::vector{ -10_norm, 41_norm })); + REQUIRE_THAT( d.readAll("/region3/xfout_vel_range"), Catch::Approx(std::vector{ 10_norm, 140_norm })); } -} - -TEST_CASE("[Values] Crossfade curves") -{ - Synth synth; - std::vector messageList; - Client client(&messageList); - client.setReceiveCallback(&simpleMessageReceiver); - SECTION("Key") + SECTION("Crossfade key curve") { - synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + d.load(R"( sample=kick.wav sample=kick.wav xf_keycurve=gain sample=kick.wav xf_keycurve=something sample=kick.wav xf_keycurve=gain xf_keycurve=power )"); - synth.dispatchMessage(client, 0, "/region0/xf_keycurve", "", nullptr); - synth.dispatchMessage(client, 0, "/region1/xf_keycurve", "", nullptr); - synth.dispatchMessage(client, 0, "/region2/xf_keycurve", "", nullptr); - synth.dispatchMessage(client, 0, "/region3/xf_keycurve", "", nullptr); - std::vector expected { - "/region0/xf_keycurve,s : { power }", - "/region1/xf_keycurve,s : { gain }", - "/region2/xf_keycurve,s : { power }", - "/region3/xf_keycurve,s : { power }", - }; - REQUIRE(messageList == expected); + REQUIRE( d.read("/region0/xf_keycurve") == "power"); + REQUIRE( d.read("/region1/xf_keycurve") == "gain"); + REQUIRE( d.read("/region2/xf_keycurve") == "power"); + REQUIRE( d.read("/region3/xf_keycurve") == "power"); } SECTION("Velocity") { - synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + d.load(R"( sample=kick.wav sample=kick.wav xf_velcurve=gain sample=kick.wav xf_velcurve=something sample=kick.wav xf_velcurve=gain xf_velcurve=power )"); - synth.dispatchMessage(client, 0, "/region0/xf_velcurve", "", nullptr); - synth.dispatchMessage(client, 0, "/region1/xf_velcurve", "", nullptr); - synth.dispatchMessage(client, 0, "/region2/xf_velcurve", "", nullptr); - synth.dispatchMessage(client, 0, "/region3/xf_velcurve", "", nullptr); - std::vector expected { - "/region0/xf_velcurve,s : { power }", - "/region1/xf_velcurve,s : { gain }", - "/region2/xf_velcurve,s : { power }", - "/region3/xf_velcurve,s : { power }", - }; - REQUIRE(messageList == expected); + REQUIRE( d.read("/region0/xf_velcurve") == "power"); + REQUIRE( d.read("/region1/xf_velcurve") == "gain"); + REQUIRE( d.read("/region2/xf_velcurve") == "power"); + REQUIRE( d.read("/region3/xf_velcurve") == "power"); } SECTION("CC") { - synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + d.load(R"( sample=kick.wav sample=kick.wav xf_cccurve=gain sample=kick.wav xf_cccurve=something sample=kick.wav xf_cccurve=gain xf_cccurve=power )"); - synth.dispatchMessage(client, 0, "/region0/xf_cccurve", "", nullptr); - synth.dispatchMessage(client, 0, "/region1/xf_cccurve", "", nullptr); - synth.dispatchMessage(client, 0, "/region2/xf_cccurve", "", nullptr); - synth.dispatchMessage(client, 0, "/region3/xf_cccurve", "", nullptr); - std::vector expected { - "/region0/xf_cccurve,s : { power }", - "/region1/xf_cccurve,s : { gain }", - "/region2/xf_cccurve,s : { power }", - "/region3/xf_cccurve,s : { power }", - }; - REQUIRE(messageList == expected); + REQUIRE( d.read("/region0/xf_cccurve") == "power"); + REQUIRE( d.read("/region1/xf_cccurve") == "gain"); + REQUIRE( d.read("/region2/xf_cccurve") == "power"); + REQUIRE( d.read("/region3/xf_cccurve") == "power"); } -} - -TEST_CASE("[Values] Crossfade CC range") -{ - Synth synth; - std::vector messageList; - Client client(&messageList); - client.setReceiveCallback(&simpleMessageReceiver); - SECTION("Xfin") + SECTION("CC Xfin") { - synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + d.load(R"( sample=kick.wav sample=kick.wav xfin_locc4=10 xfin_hicc4=40 sample=kick.wav xfin_locc4=-10 xfin_hicc4=40 sample=kick.wav xfin_locc4=10 xfin_hicc4=140 )"); - synth.dispatchMessage(client, 0, "/region0/xfin_cc_range4", "", nullptr); - synth.dispatchMessage(client, 0, "/region1/xfin_cc_range4", "", nullptr); - synth.dispatchMessage(client, 0, "/region2/xfin_cc_range4", "", nullptr); - 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.322835 }", - "/region2/xfin_cc_range4,ff : { -0.0787402, 0.322835 }", - "/region3/xfin_cc_range4,ff : { 0.0787402, 1.10236 }", - }; - REQUIRE(messageList == expected); + REQUIRE( d.read("/region0/xfin_cc_range4") == OSC::None ); + REQUIRE_THAT( d.readAll("/region1/xfin_cc_range4"), Catch::Approx(std::vector{ 10_norm, 41_norm })); + REQUIRE_THAT( d.readAll("/region2/xfin_cc_range4"), Catch::Approx(std::vector{ -10_norm, 41_norm })); + REQUIRE_THAT( d.readAll("/region3/xfin_cc_range4"), Catch::Approx(std::vector{ 10_norm, 140_norm })); } - SECTION("Xfout") + SECTION("CC Xfout") { - synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + d.load(R"( sample=kick.wav sample=kick.wav xfout_locc4=10 xfout_hicc4=40 sample=kick.wav xfout_locc4=-10 xfout_hicc4=40 sample=kick.wav xfout_locc4=10 xfout_hicc4=140 )"); - synth.dispatchMessage(client, 0, "/region0/xfout_cc_range4", "", nullptr); - synth.dispatchMessage(client, 0, "/region1/xfout_cc_range4", "", nullptr); - synth.dispatchMessage(client, 0, "/region2/xfout_cc_range4", "", nullptr); - 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.322835 }", - "/region2/xfout_cc_range4,ff : { -0.0787402, 0.322835 }", - "/region3/xfout_cc_range4,ff : { 0.0787402, 1.10236 }", - }; - REQUIRE(messageList == expected); + REQUIRE( d.read("/region0/xfout_cc_range4") == OSC::None ); + REQUIRE_THAT( d.readAll("/region1/xfout_cc_range4"), Catch::Approx(std::vector{ 10_norm, 41_norm })); + REQUIRE_THAT( d.readAll("/region2/xfout_cc_range4"), Catch::Approx(std::vector{ -10_norm, 41_norm })); + REQUIRE_THAT( d.readAll("/region3/xfout_cc_range4"), Catch::Approx(std::vector{ 10_norm, 140_norm })); } -} -TEST_CASE("[Values] Global volumes and amplitudes") -{ - Synth synth; - std::vector messageList; - Client client(&messageList); - client.setReceiveCallback(&simpleMessageReceiver); - - SECTION("Volumes") + SECTION("Global Volume") { - synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + d.load(R"( sample=kick.wav global_volume=4.4 master_volume=5.5 group_volume=6.6 sample=kick.wav )"); - synth.dispatchMessage(client, 0, "/region0/global_volume", "", nullptr); - synth.dispatchMessage(client, 0, "/region0/master_volume", "", nullptr); - synth.dispatchMessage(client, 0, "/region0/group_volume", "", nullptr); - synth.dispatchMessage(client, 0, "/region1/global_volume", "", nullptr); - synth.dispatchMessage(client, 0, "/region1/master_volume", "", nullptr); - synth.dispatchMessage(client, 0, "/region1/group_volume", "", nullptr); - std::vector expected { - "/region0/global_volume,f : { 0 }", - "/region0/master_volume,f : { 0 }", - "/region0/group_volume,f : { 0 }", - "/region1/global_volume,f : { 4.4 }", - "/region1/master_volume,f : { 5.5 }", - "/region1/group_volume,f : { 6.6 }", - }; - REQUIRE(messageList == expected); + REQUIRE( d.read("/region0/global_volume") == 0.0f); + REQUIRE( d.read("/region0/master_volume") == 0.0f); + REQUIRE( d.read("/region0/group_volume") == 0.0f); + REQUIRE( d.read("/region1/global_volume") == 4.4f); + REQUIRE( d.read("/region1/master_volume") == 5.5f); + REQUIRE( d.read("/region1/group_volume") == 6.6f); } SECTION("Amplitudes") { - synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + d.load(R"( sample=kick.wav global_amplitude=4.4 master_amplitude=5.5 group_amplitude=6.6 sample=kick.wav )"); - synth.dispatchMessage(client, 0, "/region0/global_amplitude", "", nullptr); - synth.dispatchMessage(client, 0, "/region0/master_amplitude", "", nullptr); - synth.dispatchMessage(client, 0, "/region0/group_amplitude", "", nullptr); - synth.dispatchMessage(client, 0, "/region1/global_amplitude", "", nullptr); - synth.dispatchMessage(client, 0, "/region1/master_amplitude", "", nullptr); - synth.dispatchMessage(client, 0, "/region1/group_amplitude", "", nullptr); - std::vector expected { - "/region0/global_amplitude,f : { 100 }", - "/region0/master_amplitude,f : { 100 }", - "/region0/group_amplitude,f : { 100 }", - "/region1/global_amplitude,f : { 4.4 }", - "/region1/master_amplitude,f : { 5.5 }", - "/region1/group_amplitude,f : { 6.6 }", - }; - REQUIRE(messageList == expected); + REQUIRE( d.read("/region0/global_amplitude") == 100.0f); + REQUIRE( d.read("/region0/master_amplitude") == 100.0f); + REQUIRE( d.read("/region0/group_amplitude") == 100.0f); + REQUIRE( d.read("/region1/global_amplitude") == 4.4f); + REQUIRE( d.read("/region1/master_amplitude") == 5.5f); + REQUIRE( d.read("/region1/group_amplitude") == 6.6f); } -} - -TEST_CASE("[Values] Pitch Keytrack") -{ - 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 pitch_keytrack=1000 - sample=kick.wav pitch_keytrack=-100 - )"); - synth.dispatchMessage(client, 0, "/region0/pitch_keytrack", "", nullptr); - synth.dispatchMessage(client, 0, "/region1/pitch_keytrack", "", nullptr); - synth.dispatchMessage(client, 0, "/region2/pitch_keytrack", "", nullptr); - std::vector expected { - "/region0/pitch_keytrack,f : { 100 }", - "/region1/pitch_keytrack,f : { 1000 }", - "/region2/pitch_keytrack,f : { -100 }", - }; - REQUIRE(messageList == expected); -} -TEST_CASE("[Values] Pitch Veltrack") -{ - Synth synth; - std::vector messageList; - Client client(&messageList); - client.setReceiveCallback(&simpleMessageReceiver); + SECTION("Pitch Keytrack") + { + d.load(R"( + sample=kick.wav + sample=kick.wav pitch_keytrack=1000 + sample=kick.wav pitch_keytrack=-100 + )"); + REQUIRE( d.read("/region0/pitch_keytrack") == 100.0f); + REQUIRE( d.read("/region1/pitch_keytrack") == 1000.0f); + REQUIRE( d.read("/region2/pitch_keytrack") == -100.0f); + } - SECTION("Basic") + SECTION("Pitch veltrack basic") { - synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + d.load(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,f : { 0 }", - "/region1/pitch_veltrack,f : { 10 }", - "/region2/pitch_veltrack,f : { -132 }", - }; - REQUIRE(messageList == expected); + REQUIRE( d.read("/region0/pitch_veltrack") == 0.0f); + REQUIRE( d.read("/region1/pitch_veltrack") == 10.0f); + REQUIRE( d.read("/region2/pitch_veltrack") == -132.0f); } - SECTION("CC") + SECTION("Pitch veltrack CC") { - synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + d.load(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); + REQUIRE( d.read("/region0/pitch_veltrack_cc1") == OSC::None); + REQUIRE( d.read("/region1/pitch_veltrack_cc1") == 10.1f); + REQUIRE( d.read("/region1/pitch_veltrack_curvecc1") == 3); + REQUIRE( d.read("/region2/pitch_veltrack_cc2") == -40.0f); + REQUIRE( d.read("/region2/pitch_veltrack_curvecc3") == 4); // 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); + // REQUIRE( d.read<>("/region2/pitch_veltrack") == ); } -} - -TEST_CASE("[Values] Pitch Random") -{ - 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 pitch_random=10 - sample=kick.wav pitch_random=-4 - )"); - synth.dispatchMessage(client, 0, "/region0/pitch_random", "", nullptr); - synth.dispatchMessage(client, 0, "/region1/pitch_random", "", nullptr); - synth.dispatchMessage(client, 0, "/region2/pitch_random", "", nullptr); - std::vector expected { - "/region0/pitch_random,f : { 0 }", - "/region1/pitch_random,f : { 10 }", - "/region2/pitch_random,f : { -4 }", - }; - REQUIRE(messageList == expected); -} -TEST_CASE("[Values] Transpose") -{ - 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 transpose=10 - sample=kick.wav transpose=-4 - sample=kick.wav transpose=-400 - sample=kick.wav transpose=400 - )"); - synth.dispatchMessage(client, 0, "/region0/transpose", "", nullptr); - synth.dispatchMessage(client, 0, "/region1/transpose", "", nullptr); - synth.dispatchMessage(client, 0, "/region2/transpose", "", nullptr); - synth.dispatchMessage(client, 0, "/region3/transpose", "", nullptr); - synth.dispatchMessage(client, 0, "/region4/transpose", "", nullptr); - std::vector expected { - "/region0/transpose,f : { 0 }", - "/region1/transpose,f : { 10 }", - "/region2/transpose,f : { -4 }", - "/region3/transpose,f : { -400 }", - "/region4/transpose,f : { 400 }", - }; - REQUIRE(messageList == expected); -} + SECTION("Pitch Random") + { + d.load(R"( + sample=kick.wav + sample=kick.wav pitch_random=10 + sample=kick.wav pitch_random=-4 + )"); + REQUIRE( d.read("/region0/pitch_random") == 0.0f); + REQUIRE( d.read("/region1/pitch_random") == 10.0f); + REQUIRE( d.read("/region2/pitch_random") == -4.0f); + } -TEST_CASE("[Values] Pitch/Tune") -{ - Synth synth; - std::vector messageList; - Client client(&messageList); - client.setReceiveCallback(&simpleMessageReceiver); + SECTION("Transpose") + { + d.load(R"( + sample=kick.wav + sample=kick.wav transpose=10 + sample=kick.wav transpose=-4 + sample=kick.wav transpose=-400 + sample=kick.wav transpose=400 + )"); + REQUIRE( d.read("/region0/transpose") == 0.0f); + REQUIRE( d.read("/region1/transpose") == 10.0f); + REQUIRE( d.read("/region2/transpose") == -4.0f); + REQUIRE( d.read("/region3/transpose") == -400.0f); + REQUIRE( d.read("/region4/transpose") == 400.0f); + } - SECTION("Basic") + SECTION("Pitch/tune basic") { - synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + d.load(R"( sample=kick.wav sample=kick.wav pitch=4.2 sample=kick.wav tune=-200 )"); - synth.dispatchMessage(client, 0, "/region0/pitch", "", nullptr); - synth.dispatchMessage(client, 0, "/region1/pitch", "", nullptr); - synth.dispatchMessage(client, 0, "/region2/pitch", "", nullptr); - std::vector expected { - "/region0/pitch,f : { 0 }", - "/region1/pitch,f : { 4.2 }", - "/region2/pitch,f : { -200 }", - }; - REQUIRE(messageList == expected); + REQUIRE( d.read("/region0/pitch") == 0.0f); + REQUIRE( d.read("/region1/pitch") == 4.2f); + REQUIRE( d.read("/region2/pitch") == -200.0f); } - SECTION("CC Depth") + SECTION("Pitch/tune CC Depth") { - synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + d.load(R"( sample=kick.wav sample=kick.wav pitch_oncc42=4.2 sample=kick.wav pitch_oncc2=-10 )"); - synth.dispatchMessage(client, 0, "/region0/pitch_cc42", "", nullptr); - synth.dispatchMessage(client, 0, "/region1/pitch_cc42", "", nullptr); - synth.dispatchMessage(client, 0, "/region2/pitch_cc2", "", nullptr); - std::vector expected { - "/region0/pitch_cc42,N : { }", - "/region1/pitch_cc42,f : { 4.2 }", - "/region2/pitch_cc2,f : { -10 }", - }; - REQUIRE(messageList == expected); + REQUIRE( d.read("/region0/pitch_cc42") == OSC::None); + REQUIRE( d.read("/region1/pitch_cc42") == 4.2f); + REQUIRE( d.read("/region2/pitch_cc2") == -10.0f); } - SECTION("CC Params") + SECTION("Pitch/tune CC Params") { - synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + d.load(R"( sample=kick.wav sample=kick.wav pitch_stepcc42=4.2 sample=kick.wav pitch_smoothcc42=4 @@ -2296,33 +1321,21 @@ TEST_CASE("[Values] Pitch/Tune") sample=kick.wav pitch_smoothcc42=-4 sample=kick.wav pitch_curvecc42=300 )"); - synth.dispatchMessage(client, 0, "/region0/pitch_stepcc42", "", nullptr); - synth.dispatchMessage(client, 0, "/region0/pitch_smoothcc42", "", nullptr); - synth.dispatchMessage(client, 0, "/region0/pitch_curvecc42", "", nullptr); - synth.dispatchMessage(client, 0, "/region1/pitch_stepcc42", "", nullptr); - synth.dispatchMessage(client, 0, "/region2/pitch_smoothcc42", "", nullptr); - synth.dispatchMessage(client, 0, "/region3/pitch_curvecc42", "", nullptr); + REQUIRE( d.read("/region0/pitch_stepcc42") == OSC::None); + REQUIRE( d.read("/region0/pitch_smoothcc42") == OSC::None); + REQUIRE( d.read("/region0/pitch_curvecc42") == OSC::None); + REQUIRE( d.read("/region1/pitch_stepcc42") == 4.2f); + REQUIRE( d.read("/region2/pitch_smoothcc42") == 4 ); + REQUIRE( d.read("/region3/pitch_curvecc42") == 2); // TODO: activate for the new region parser ; ignore oob - // synth.dispatchMessage(client, 0, "/region4/pitch_stepcc42", "", nullptr); - // synth.dispatchMessage(client, 0, "/region5/pitch_smoothcc42", "", nullptr); - // synth.dispatchMessage(client, 0, "/region6/pitch_curvecc42", "", nullptr); - std::vector expected { - "/region0/pitch_stepcc42,N : { }", - "/region0/pitch_smoothcc42,N : { }", - "/region0/pitch_curvecc42,N : { }", - "/region1/pitch_stepcc42,f : { 4.2 }", - "/region2/pitch_smoothcc42,i : { 4 }", - "/region3/pitch_curvecc42,i : { 2 }", - // "/region4/pitch_stepcc42,N : { }", - // "/region5/pitch_smoothcc42,N : { }", - // "/region6/pitch_curvecc42,N : { }", - }; - REQUIRE(messageList == expected); + // REQUIRE( d.read("/region4/pitch_stepcc42") == OSC::None); + // REQUIRE( d.read("/region5/pitch_smoothcc42") == OSC::None); + // REQUIRE( d.read("/region6/pitch_curvecc42") == OSC::None); } - SECTION("CC Params (with pitch_)") + SECTION("Pitch/tune CC Params (with pitch_)") { - synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + d.load(R"( sample=kick.wav sample=kick.wav pitch_stepcc42=4.2 sample=kick.wav pitch_smoothcc42=4 @@ -2331,82 +1344,42 @@ TEST_CASE("[Values] Pitch/Tune") sample=kick.wav pitch_smoothcc42=-4 sample=kick.wav pitch_curvecc42=300 )"); - synth.dispatchMessage(client, 0, "/region0/pitch_stepcc42", "", nullptr); - synth.dispatchMessage(client, 0, "/region0/pitch_smoothcc42", "", nullptr); - synth.dispatchMessage(client, 0, "/region0/pitch_curvecc42", "", nullptr); - synth.dispatchMessage(client, 0, "/region1/pitch_stepcc42", "", nullptr); - synth.dispatchMessage(client, 0, "/region2/pitch_smoothcc42", "", nullptr); - synth.dispatchMessage(client, 0, "/region3/pitch_curvecc42", "", nullptr); + REQUIRE( d.read("/region0/pitch_stepcc42") == OSC::None); + REQUIRE( d.read("/region0/pitch_smoothcc42") == OSC::None); + REQUIRE( d.read("/region0/pitch_curvecc42") == OSC::None); + REQUIRE( d.read("/region1/pitch_stepcc42") == 4.2f); + REQUIRE( d.read("/region2/pitch_smoothcc42") == 4 ); + REQUIRE( d.read("/region3/pitch_curvecc42") == 2); // TODO: activate for the new region parser ; ignore oob - // synth.dispatchMessage(client, 0, "/region4/pitch_stepcc42", "", nullptr); - // synth.dispatchMessage(client, 0, "/region5/pitch_smoothcc42", "", nullptr); - // synth.dispatchMessage(client, 0, "/region6/pitch_curvecc42", "", nullptr); - std::vector expected { - "/region0/pitch_stepcc42,N : { }", - "/region0/pitch_smoothcc42,N : { }", - "/region0/pitch_curvecc42,N : { }", - "/region1/pitch_stepcc42,f : { 4.2 }", - "/region2/pitch_smoothcc42,i : { 4 }", - "/region3/pitch_curvecc42,i : { 2 }", - // "/region4/pitch_stepcc42,N : { }", - // "/region5/pitch_smoothcc42,N : { }", - // "/region6/pitch_curvecc42,N : { }", - }; - REQUIRE(messageList == expected); + // REQUIRE( d.read("/region4/pitch_stepcc42") == OSC::None); + // REQUIRE( d.read("/region5/pitch_smoothcc42") == OSC::None); + // REQUIRE( d.read("/region6/pitch_curvecc42") == OSC::None); } -} - -TEST_CASE("[Values] Bend behavior") -{ - 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 bend_up=100 bend_down=-400 bend_step=10 bend_smooth=10 - sample=kick.wav bend_up=-100 bend_down=400 bend_step=-10 bend_smooth=-10 - )"); - synth.dispatchMessage(client, 0, "/region0/bend_up", "", nullptr); - synth.dispatchMessage(client, 0, "/region0/bend_down", "", nullptr); - synth.dispatchMessage(client, 0, "/region0/bend_step", "", nullptr); - synth.dispatchMessage(client, 0, "/region0/bend_smooth", "", nullptr); - synth.dispatchMessage(client, 0, "/region1/bend_up", "", nullptr); - synth.dispatchMessage(client, 0, "/region1/bend_down", "", nullptr); - synth.dispatchMessage(client, 0, "/region1/bend_step", "", nullptr); - synth.dispatchMessage(client, 0, "/region1/bend_smooth", "", nullptr); - synth.dispatchMessage(client, 0, "/region2/bend_up", "", nullptr); - synth.dispatchMessage(client, 0, "/region2/bend_down", "", nullptr); - synth.dispatchMessage(client, 0, "/region2/bend_step", "", nullptr); - synth.dispatchMessage(client, 0, "/region2/bend_smooth", "", nullptr); - std::vector expected { - "/region0/bend_up,f : { 200 }", - "/region0/bend_down,f : { -200 }", - "/region0/bend_step,f : { 1 }", - "/region0/bend_smooth,i : { 0 }", - "/region1/bend_up,f : { 100 }", - "/region1/bend_down,f : { -400 }", - "/region1/bend_step,f : { 10 }", - "/region1/bend_smooth,i : { 10 }", - "/region2/bend_up,f : { -100 }", - "/region2/bend_down,f : { 400 }", - "/region2/bend_step,f : { 1 }", - "/region2/bend_smooth,i : { 0 }", - }; - REQUIRE(messageList == expected); -} -TEST_CASE("[Values] ampeg") -{ - Synth synth; - std::vector messageList; - Client client(&messageList); - client.setReceiveCallback(&simpleMessageReceiver); + SECTION("Bend behavior") + { + d.load(R"( + sample=kick.wav + sample=kick.wav bend_up=100 bend_down=-400 bend_step=10 bend_smooth=10 + sample=kick.wav bend_up=-100 bend_down=400 bend_step=-10 bend_smooth=-10 + )"); + REQUIRE( d.read("/region0/bend_up") == 200.0f); + REQUIRE( d.read("/region0/bend_down") == -200.0f); + REQUIRE( d.read("/region0/bend_step") == 1.0f); + REQUIRE( d.read("/region0/bend_smooth") == 0); + REQUIRE( d.read("/region1/bend_up") == 100.0f); + REQUIRE( d.read("/region1/bend_down") == -400.0f); + REQUIRE( d.read("/region1/bend_step") == 10.0f); + REQUIRE( d.read("/region1/bend_smooth") == 10); + REQUIRE( d.read("/region2/bend_up") == -100.0f); + REQUIRE( d.read("/region2/bend_down") == 400.0f); + REQUIRE( d.read("/region2/bend_step") == 1.0f); + REQUIRE( d.read("/region2/bend_smooth") == 0); + } - SECTION("Basic") + SECTION("Ampeg basic") { - synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + d.load(R"( sample=kick.wav sample=kick.wav ampeg_attack=1 ampeg_delay=2 ampeg_decay=3 @@ -2417,1499 +1390,804 @@ TEST_CASE("[Values] ampeg") ampeg_hold=-4 ampeg_release=-5 ampeg_start=-6 ampeg_sustain=-7 ampeg_depth=-8 )"); - synth.dispatchMessage(client, 0, "/region0/ampeg_attack", "", nullptr); - synth.dispatchMessage(client, 0, "/region0/ampeg_delay", "", nullptr); - synth.dispatchMessage(client, 0, "/region0/ampeg_decay", "", nullptr); - synth.dispatchMessage(client, 0, "/region0/ampeg_hold", "", nullptr); - synth.dispatchMessage(client, 0, "/region0/ampeg_release", "", nullptr); - synth.dispatchMessage(client, 0, "/region0/ampeg_start", "", nullptr); - synth.dispatchMessage(client, 0, "/region0/ampeg_sustain", "", nullptr); - synth.dispatchMessage(client, 0, "/region0/ampeg_depth", "", nullptr); - synth.dispatchMessage(client, 0, "/region1/ampeg_attack", "", nullptr); - synth.dispatchMessage(client, 0, "/region1/ampeg_delay", "", nullptr); - synth.dispatchMessage(client, 0, "/region1/ampeg_decay", "", nullptr); - synth.dispatchMessage(client, 0, "/region1/ampeg_hold", "", nullptr); - synth.dispatchMessage(client, 0, "/region1/ampeg_release", "", nullptr); - synth.dispatchMessage(client, 0, "/region1/ampeg_start", "", nullptr); - synth.dispatchMessage(client, 0, "/region1/ampeg_sustain", "", nullptr); - synth.dispatchMessage(client, 0, "/region1/ampeg_depth", "", nullptr); + REQUIRE( d.read("/region0/ampeg_attack") == 0.0f); + REQUIRE( d.read("/region0/ampeg_delay") == 0.0f); + REQUIRE( d.read("/region0/ampeg_decay") == 0.0f); + REQUIRE( d.read("/region0/ampeg_hold") == 0.0f); + REQUIRE( d.read("/region0/ampeg_release") == 0.001f); + REQUIRE( d.read("/region0/ampeg_start") == 0.0f); + REQUIRE( d.read("/region0/ampeg_sustain") == 100.0f); + REQUIRE( d.read("/region0/ampeg_depth") == 0.0f); + REQUIRE( d.read("/region1/ampeg_attack") == 1.0f); + REQUIRE( d.read("/region1/ampeg_delay") == 2.0f); + REQUIRE( d.read("/region1/ampeg_decay") == 3.0f); + REQUIRE( d.read("/region1/ampeg_hold") == 4.0f); + REQUIRE( d.read("/region1/ampeg_release") == 5.0f); + REQUIRE( d.read("/region1/ampeg_start") == 6.0f); + REQUIRE( d.read("/region1/ampeg_sustain") == 7.0f); + REQUIRE( d.read("/region1/ampeg_depth") == 0.0f); // TODO after new parser : ignore oob - // synth.dispatchMessage(client, 0, "/region2/ampeg_attack", "", nullptr); - // synth.dispatchMessage(client, 0, "/region2/ampeg_delay", "", nullptr); - // synth.dispatchMessage(client, 0, "/region2/ampeg_decay", "", nullptr); - // synth.dispatchMessage(client, 0, "/region2/ampeg_hold", "", nullptr); - // synth.dispatchMessage(client, 0, "/region2/ampeg_release", "", nullptr); - // synth.dispatchMessage(client, 0, "/region2/ampeg_start", "", nullptr); - // synth.dispatchMessage(client, 0, "/region2/ampeg_sustain", "", nullptr); - // synth.dispatchMessage(client, 0, "/region2/ampeg_depth", "", nullptr); - std::vector expected { - "/region0/ampeg_attack,f : { 0 }", - "/region0/ampeg_delay,f : { 0 }", - "/region0/ampeg_decay,f : { 0 }", - "/region0/ampeg_hold,f : { 0 }", - "/region0/ampeg_release,f : { 0.001 }", - "/region0/ampeg_start,f : { 0 }", - "/region0/ampeg_sustain,f : { 100 }", - "/region0/ampeg_depth,f : { 0 }", - "/region1/ampeg_attack,f : { 1 }", - "/region1/ampeg_delay,f : { 2 }", - "/region1/ampeg_decay,f : { 3 }", - "/region1/ampeg_hold,f : { 4 }", - "/region1/ampeg_release,f : { 5 }", - "/region1/ampeg_start,f : { 6 }", - "/region1/ampeg_sustain,f : { 7 }", - "/region1/ampeg_depth,f : { 0 }", - // "/region2/ampeg_attack,f : { 0 }", - // "/region2/ampeg_delay,f : { 0 }", - // "/region2/ampeg_decay,f : { 0 }", - // "/region2/ampeg_hold,f : { 0 }", - // "/region2/ampeg_release,f : { 0.001 }", - // "/region2/ampeg_start,f : { 0 }", - // "/region2/ampeg_sustain,f : { 100 }", - // "/region2/ampeg_depth,f : { 0 }", - }; - REQUIRE(messageList == expected); + // REQUIRE( d.read("/region2/ampeg_attack") == 0.0f); + // REQUIRE( d.read("/region2/ampeg_delay") == 0.0f); + // REQUIRE( d.read("/region2/ampeg_decay") == 0.0f); + // REQUIRE( d.read("/region2/ampeg_hold") == 0.0f); + // REQUIRE( d.read("/region2/ampeg_release") == 0.001f); + // REQUIRE( d.read("/region2/ampeg_start") == 0.0f); + // REQUIRE( d.read("/region2/ampeg_sustain") == 100.0f); + // REQUIRE( d.read("/region2/ampeg_depth") == 0.0f); } - SECTION("Velocity") + SECTION("Ampeg velocity") { - synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + d.load(R"( sample=kick.wav sample=kick.wav ampeg_vel2attack=1 ampeg_vel2delay=2 ampeg_vel2decay=3 ampeg_vel2hold=4 ampeg_vel2release=5 ampeg_vel2sustain=7 ampeg_vel2depth=8 )"); - synth.dispatchMessage(client, 0, "/region0/ampeg_vel2attack", "", nullptr); - synth.dispatchMessage(client, 0, "/region0/ampeg_vel2delay", "", nullptr); - synth.dispatchMessage(client, 0, "/region0/ampeg_vel2decay", "", nullptr); - synth.dispatchMessage(client, 0, "/region0/ampeg_vel2hold", "", nullptr); - synth.dispatchMessage(client, 0, "/region0/ampeg_vel2release", "", nullptr); - synth.dispatchMessage(client, 0, "/region0/ampeg_vel2sustain", "", nullptr); - synth.dispatchMessage(client, 0, "/region0/ampeg_vel2depth", "", nullptr); - synth.dispatchMessage(client, 0, "/region1/ampeg_vel2attack", "", nullptr); - synth.dispatchMessage(client, 0, "/region1/ampeg_vel2delay", "", nullptr); - synth.dispatchMessage(client, 0, "/region1/ampeg_vel2decay", "", nullptr); - synth.dispatchMessage(client, 0, "/region1/ampeg_vel2hold", "", nullptr); - synth.dispatchMessage(client, 0, "/region1/ampeg_vel2release", "", nullptr); - synth.dispatchMessage(client, 0, "/region1/ampeg_vel2sustain", "", nullptr); - synth.dispatchMessage(client, 0, "/region1/ampeg_vel2depth", "", nullptr); - std::vector expected { - "/region0/ampeg_vel2attack,f : { 0 }", - "/region0/ampeg_vel2delay,f : { 0 }", - "/region0/ampeg_vel2decay,f : { 0 }", - "/region0/ampeg_vel2hold,f : { 0 }", - "/region0/ampeg_vel2release,f : { 0 }", - "/region0/ampeg_vel2sustain,f : { 0 }", - "/region0/ampeg_vel2depth,f : { 0 }", - "/region1/ampeg_vel2attack,f : { 1 }", - "/region1/ampeg_vel2delay,f : { 2 }", - "/region1/ampeg_vel2decay,f : { 3 }", - "/region1/ampeg_vel2hold,f : { 4 }", - "/region1/ampeg_vel2release,f : { 5 }", - "/region1/ampeg_vel2sustain,f : { 7 }", - "/region1/ampeg_vel2depth,f : { 0 }", - }; - REQUIRE(messageList == expected); + REQUIRE( d.read("/region0/ampeg_vel2attack") == 0.0f ); + REQUIRE( d.read("/region0/ampeg_vel2delay") == 0.0f ); + REQUIRE( d.read("/region0/ampeg_vel2decay") == 0.0f ); + REQUIRE( d.read("/region0/ampeg_vel2hold") == 0.0f ); + REQUIRE( d.read("/region0/ampeg_vel2release") == 0.0f ); + REQUIRE( d.read("/region0/ampeg_vel2sustain") == 0.0f ); + REQUIRE( d.read("/region0/ampeg_vel2depth") == 0.0f ); + REQUIRE( d.read("/region1/ampeg_vel2attack") == 1.0f ); + REQUIRE( d.read("/region1/ampeg_vel2delay") == 2.0f ); + REQUIRE( d.read("/region1/ampeg_vel2decay") == 3.0f ); + REQUIRE( d.read("/region1/ampeg_vel2hold") == 4.0f ); + REQUIRE( d.read("/region1/ampeg_vel2release") == 5.0f ); + REQUIRE( d.read("/region1/ampeg_vel2sustain") == 7.0f ); + REQUIRE( d.read("/region1/ampeg_vel2depth") == 0.0f ); } -} - -TEST_CASE("[Values] Note polyphony") -{ - 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 note_polyphony=10 - sample=kick.wav note_polyphony=-4 - sample=kick.wav note_polyphony=10 note_polyphony=-4 - )"); - synth.dispatchMessage(client, 0, "/region0/note_polyphony", "", nullptr); - synth.dispatchMessage(client, 0, "/region1/note_polyphony", "", nullptr); - // TODO: activate for the new region parser ; ignore oob - // synth.dispatchMessage(client, 0, "/region2/note_polyphony", "", nullptr); - // synth.dispatchMessage(client, 0, "/region3/note_polyphony", "", nullptr); - std::vector expected { - "/region0/note_polyphony,N : { }", - "/region1/note_polyphony,i : { 10 }", - // "/region2/note_polyphony,N : { }", - // "/region3/note_polyphony,i : { 10 }", - }; - REQUIRE(messageList == expected); -} -TEST_CASE("[Values] Self-mask") -{ - 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 note_selfmask=off - sample=kick.wav note_selfmask=off note_selfmask=on - sample=kick.wav note_selfmask=off note_selfmask=garbage - )"); - synth.dispatchMessage(client, 0, "/region0/note_selfmask", "", nullptr); - synth.dispatchMessage(client, 0, "/region1/note_selfmask", "", nullptr); - synth.dispatchMessage(client, 0, "/region2/note_selfmask", "", nullptr); - synth.dispatchMessage(client, 0, "/region3/note_selfmask", "", nullptr); - std::vector expected { - "/region0/note_selfmask,T : { }", - "/region1/note_selfmask,F : { }", - "/region2/note_selfmask,T : { }", - "/region3/note_selfmask,T : { }", - }; - REQUIRE(messageList == expected); -} + SECTION("Note polyphony") + { + d.load(R"( + sample=kick.wav + sample=kick.wav note_polyphony=10 + sample=kick.wav note_polyphony=-4 + sample=kick.wav note_polyphony=10 note_polyphony=-4 + )"); + REQUIRE( d.read("/region0/note_polyphony") == OSC::None ); + REQUIRE( d.read("/region1/note_polyphony") == 10); + // TODO: activate for the new region parser ; ignore oob + // REQUIRE( d.read("/region2/note_polyphony") == OSC::None ); + // REQUIRE( d.read("/region3/note_polyphony") == 10); + } -TEST_CASE("[Values] RT dead") -{ - 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 rt_dead=on - sample=kick.wav rt_dead=on rt_dead=off - sample=kick.wav rt_dead=on rt_dead=garbage - )"); - synth.dispatchMessage(client, 0, "/region0/rt_dead", "", nullptr); - synth.dispatchMessage(client, 0, "/region1/rt_dead", "", nullptr); - synth.dispatchMessage(client, 0, "/region2/rt_dead", "", nullptr); - synth.dispatchMessage(client, 0, "/region3/rt_dead", "", nullptr); - std::vector expected { - "/region0/rt_dead,F : { }", - "/region1/rt_dead,T : { }", - "/region2/rt_dead,F : { }", - "/region3/rt_dead,F : { }", - }; - REQUIRE(messageList == expected); -} + SECTION("Self-mask") + { + d.load(R"( + sample=kick.wav + sample=kick.wav note_selfmask=off + sample=kick.wav note_selfmask=off note_selfmask=on + sample=kick.wav note_selfmask=off note_selfmask=garbage + )"); + REQUIRE( d.read("/region0/note_selfmask") == OSC::True); + REQUIRE( d.read("/region1/note_selfmask") == OSC::False); + REQUIRE( d.read("/region2/note_selfmask") == OSC::True); + REQUIRE( d.read("/region3/note_selfmask") == OSC::True); + } -TEST_CASE("[Values] Sustain switch") -{ - 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 sustain_sw=off - sample=kick.wav sustain_sw=off sustain_sw=on - sample=kick.wav sustain_sw=off sustain_sw=garbage - )"); - synth.dispatchMessage(client, 0, "/region0/sustain_sw", "", nullptr); - synth.dispatchMessage(client, 0, "/region1/sustain_sw", "", nullptr); - synth.dispatchMessage(client, 0, "/region2/sustain_sw", "", nullptr); - synth.dispatchMessage(client, 0, "/region3/sustain_sw", "", nullptr); - std::vector expected { - "/region0/sustain_sw,T : { }", - "/region1/sustain_sw,F : { }", - "/region2/sustain_sw,T : { }", - "/region3/sustain_sw,T : { }", - }; - REQUIRE(messageList == expected); -} + SECTION("RT dead") + { + d.load(R"( + sample=kick.wav + sample=kick.wav rt_dead=on + sample=kick.wav rt_dead=on rt_dead=off + sample=kick.wav rt_dead=on rt_dead=garbage + )"); + REQUIRE( d.read("/region0/rt_dead") == OSC::False); + REQUIRE( d.read("/region1/rt_dead") == OSC::True); + REQUIRE( d.read("/region2/rt_dead") == OSC::False); + REQUIRE( d.read("/region3/rt_dead") == OSC::False); + } -TEST_CASE("[Values] Sostenuto switch") -{ - 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 sostenuto_sw=off - sample=kick.wav sostenuto_sw=off sostenuto_sw=on - sample=kick.wav sostenuto_sw=off sostenuto_sw=garbage - )"); - synth.dispatchMessage(client, 0, "/region0/sostenuto_sw", "", nullptr); - synth.dispatchMessage(client, 0, "/region1/sostenuto_sw", "", nullptr); - synth.dispatchMessage(client, 0, "/region2/sostenuto_sw", "", nullptr); - synth.dispatchMessage(client, 0, "/region3/sostenuto_sw", "", nullptr); - std::vector expected { - "/region0/sostenuto_sw,T : { }", - "/region1/sostenuto_sw,F : { }", - "/region2/sostenuto_sw,T : { }", - "/region3/sostenuto_sw,T : { }", - }; - REQUIRE(messageList == expected); -} + SECTION("Sustain switch") + { + d.load(R"( + sample=kick.wav + sample=kick.wav sustain_sw=off + sample=kick.wav sustain_sw=off sustain_sw=on + sample=kick.wav sustain_sw=off sustain_sw=garbage + )"); + REQUIRE( d.read("/region0/sustain_sw") == OSC::True); + REQUIRE( d.read("/region1/sustain_sw") == OSC::False); + REQUIRE( d.read("/region2/sustain_sw") == OSC::True); + REQUIRE( d.read("/region3/sustain_sw") == OSC::True); + } -TEST_CASE("[Values] Sustain 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 - sample=kick.wav sustain_cc=10 - sample=kick.wav sustain_cc=20 sustain_cc=-1 - )"); - synth.dispatchMessage(client, 0, "/region0/sustain_cc", "", nullptr); - synth.dispatchMessage(client, 0, "/region1/sustain_cc", "", nullptr); - synth.dispatchMessage(client, 0, "/region2/sustain_cc", "", nullptr); - std::vector expected { - "/region0/sustain_cc,i : { 64 }", - "/region1/sustain_cc,i : { 10 }", - "/region2/sustain_cc,i : { 64 }", - }; - REQUIRE(messageList == expected); -} + SECTION("Sostenuto switch") + { + d.load(R"( + sample=kick.wav + sample=kick.wav sostenuto_sw=off + sample=kick.wav sostenuto_sw=off sostenuto_sw=on + sample=kick.wav sostenuto_sw=off sostenuto_sw=garbage + )"); + REQUIRE( d.read("/region0/sostenuto_sw") == OSC::True); + REQUIRE( d.read("/region1/sostenuto_sw") == OSC::False); + REQUIRE( d.read("/region2/sostenuto_sw") == OSC::True); + REQUIRE( d.read("/region3/sostenuto_sw") == OSC::True); + } -TEST_CASE("[Values] Sustain low") -{ - 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 sustain_lo=10 - sample=kick.wav sustain_lo=10 sustain_lo=-1 - )"); - synth.dispatchMessage(client, 0, "/region0/sustain_lo", "", nullptr); - synth.dispatchMessage(client, 0, "/region1/sustain_lo", "", nullptr); - synth.dispatchMessage(client, 0, "/region2/sustain_lo", "", nullptr); - std::vector expected { - "/region0/sustain_lo,f : { 0.00787402 }", - "/region1/sustain_lo,f : { 0.0787402 }", - "/region2/sustain_lo,f : { -0.00787402 }", - }; - REQUIRE(messageList == expected); -} + SECTION("Sustain CC") + { + d.load(R"( + sample=kick.wav + sample=kick.wav sustain_cc=10 + sample=kick.wav sustain_cc=20 sustain_cc=-1 + )"); + REQUIRE( d.read("/region0/sustain_cc") == 64); + REQUIRE( d.read("/region1/sustain_cc") == 10); + REQUIRE( d.read("/region2/sustain_cc") == 64); + } -TEST_CASE("[Values] Sostenuto 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 - sample=kick.wav sostenuto_cc=10 - sample=kick.wav sostenuto_cc=20 sostenuto_cc=-1 - )"); - synth.dispatchMessage(client, 0, "/region0/sostenuto_cc", "", nullptr); - synth.dispatchMessage(client, 0, "/region1/sostenuto_cc", "", nullptr); - synth.dispatchMessage(client, 0, "/region2/sostenuto_cc", "", nullptr); - std::vector expected { - "/region0/sostenuto_cc,i : { 66 }", - "/region1/sostenuto_cc,i : { 10 }", - "/region2/sostenuto_cc,i : { 66 }", - }; - REQUIRE(messageList == expected); -} + SECTION("Sustain low") + { + d.load(R"( + sample=kick.wav + sample=kick.wav sustain_lo=10 + sample=kick.wav sustain_lo=10 sustain_lo=-1 + )"); + REQUIRE( d.read("/region0/sustain_lo") == 1_norm); + REQUIRE( d.read("/region1/sustain_lo") == 10_norm); + REQUIRE( d.read("/region2/sustain_lo") == -1_norm); + } -TEST_CASE("[Values] Sostenuto low") -{ - 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 sostenuto_lo=10 - sample=kick.wav sostenuto_lo=10 sostenuto_lo=-1 - )"); - synth.dispatchMessage(client, 0, "/region0/sostenuto_lo", "", nullptr); - synth.dispatchMessage(client, 0, "/region1/sostenuto_lo", "", nullptr); - synth.dispatchMessage(client, 0, "/region2/sostenuto_lo", "", nullptr); - std::vector expected { - "/region0/sostenuto_lo,f : { 0.00787402 }", - "/region1/sostenuto_lo,f : { 0.0787402 }", - "/region2/sostenuto_lo,f : { -0.00787402 }", - }; - REQUIRE(messageList == expected); -} + SECTION("Sostenuto CC") + { + d.load(R"( + sample=kick.wav + sample=kick.wav sostenuto_cc=10 + sample=kick.wav sostenuto_cc=20 sostenuto_cc=-1 + )"); + REQUIRE( d.read("/region0/sostenuto_cc") == 66); + REQUIRE( d.read("/region1/sostenuto_cc") == 10); + REQUIRE( d.read("/region2/sostenuto_cc") == 66); + } -TEST_CASE("[Values] Oscillator phase") -{ - 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 oscillator_phase=0.1 - sample=kick.wav oscillator_phase=1.1 - sample=kick.wav oscillator_phase=-1.2 - )"); - synth.dispatchMessage(client, 0, "/region0/oscillator_phase", "", nullptr); - synth.dispatchMessage(client, 0, "/region1/oscillator_phase", "", nullptr); - synth.dispatchMessage(client, 0, "/region2/oscillator_phase", "", nullptr); - synth.dispatchMessage(client, 0, "/region3/oscillator_phase", "", nullptr); - std::vector expected { - "/region0/oscillator_phase,f : { 0 }", - "/region1/oscillator_phase,f : { 0.1 }", - "/region2/oscillator_phase,f : { 0.1 }", - "/region3/oscillator_phase,f : { -1 }", - }; - REQUIRE(messageList == expected); -} + SECTION("Sostenuto low") + { + d.load(R"( + sample=kick.wav + sample=kick.wav sostenuto_lo=10 + sample=kick.wav sostenuto_lo=10 sostenuto_lo=-1 + )"); + REQUIRE( d.read("/region0/sostenuto_lo") == 1_norm); + REQUIRE( d.read("/region1/sostenuto_lo") == 10_norm); + REQUIRE( d.read("/region2/sostenuto_lo") == -1_norm); + } -TEST_CASE("[Values] Oscillator quality") -{ - 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 oscillator_quality=2 - sample=kick.wav oscillator_quality=0 oscillator_quality=-2 - )"); - synth.dispatchMessage(client, 0, "/region0/oscillator_quality", "", nullptr); - synth.dispatchMessage(client, 0, "/region1/oscillator_quality", "", nullptr); - synth.dispatchMessage(client, 0, "/region2/oscillator_quality", "", nullptr); - std::vector expected { - "/region0/oscillator_quality,N : { }", - "/region1/oscillator_quality,i : { 2 }", - "/region2/oscillator_quality,N : { }", - }; - REQUIRE(messageList == expected); -} + SECTION("Oscillator phase") + { + d.load(R"( + sample=kick.wav + sample=kick.wav oscillator_phase=0.1 + sample=kick.wav oscillator_phase=1.1 + sample=kick.wav oscillator_phase=-1.2 + )"); + REQUIRE( d.read("/region0/oscillator_phase") == 0.0f); + REQUIRE_THAT( d.read("/region1/oscillator_phase"), Catch::WithinRel(0.1f)); + REQUIRE_THAT( d.read("/region2/oscillator_phase"), Catch::WithinRel(0.1f)); + REQUIRE( d.read("/region3/oscillator_phase") == -1.0f); + } -TEST_CASE("[Values] Oscillator mode/multi") -{ - 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 oscillator_mode=2 - sample=kick.wav oscillator_mode=1 oscillator_mode=-2 - sample=kick.wav oscillator_multi=9 - sample=kick.wav oscillator_multi=-2 - )"); - synth.dispatchMessage(client, 0, "/region0/oscillator_mode", "", nullptr); - synth.dispatchMessage(client, 0, "/region1/oscillator_mode", "", nullptr); - synth.dispatchMessage(client, 0, "/region2/oscillator_mode", "", nullptr); - synth.dispatchMessage(client, 0, "/region0/oscillator_multi", "", nullptr); - synth.dispatchMessage(client, 0, "/region3/oscillator_multi", "", nullptr); - synth.dispatchMessage(client, 0, "/region4/oscillator_multi", "", nullptr); - std::vector expected { - "/region0/oscillator_mode,i : { 0 }", - "/region1/oscillator_mode,i : { 2 }", - "/region2/oscillator_mode,i : { 0 }", - "/region0/oscillator_multi,i : { 1 }", - "/region3/oscillator_multi,i : { 9 }", - "/region4/oscillator_multi,i : { 1 }", - }; - REQUIRE(messageList == expected); -} + SECTION("Oscillator quality") + { + d.load(R"( + sample=kick.wav + sample=kick.wav oscillator_quality=2 + sample=kick.wav oscillator_quality=0 oscillator_quality=-2 + )"); + REQUIRE( d.read("/region0/oscillator_quality") == OSC::None); + REQUIRE( d.read("/region1/oscillator_quality") == 2); + REQUIRE( d.read("/region2/oscillator_quality") == OSC::None); + } -TEST_CASE("[Values] Oscillator detune/mod depth") -{ - 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 oscillator_detune=9.2 - sample=kick.wav oscillator_detune=-1200.2 - sample=kick.wav oscillator_mod_depth=1564.75 - sample=kick.wav oscillator_mod_depth=-2.2 - )"); - synth.dispatchMessage(client, 0, "/region0/oscillator_detune", "", nullptr); - synth.dispatchMessage(client, 0, "/region1/oscillator_detune", "", nullptr); - synth.dispatchMessage(client, 0, "/region2/oscillator_detune", "", nullptr); - synth.dispatchMessage(client, 0, "/region0/oscillator_mod_depth", "", nullptr); - synth.dispatchMessage(client, 0, "/region3/oscillator_mod_depth", "", nullptr); - synth.dispatchMessage(client, 0, "/region4/oscillator_mod_depth", "", nullptr); - std::vector expected { - "/region0/oscillator_detune,f : { 0 }", - "/region1/oscillator_detune,f : { 9.2 }", - "/region2/oscillator_detune,f : { -1200.2 }", - "/region0/oscillator_mod_depth,f : { 0 }", - "/region3/oscillator_mod_depth,f : { 1564.75 }", - "/region4/oscillator_mod_depth,f : { -2.2 }", - }; - REQUIRE(messageList == expected); -} + SECTION("Oscillator mode/multi") + { + d.load(R"( + sample=kick.wav + sample=kick.wav oscillator_mode=2 + sample=kick.wav oscillator_mode=1 oscillator_mode=-2 + sample=kick.wav oscillator_multi=9 + sample=kick.wav oscillator_multi=-2 + )"); + REQUIRE( d.read("/region0/oscillator_mode") == 0); + REQUIRE( d.read("/region1/oscillator_mode") == 2); + REQUIRE( d.read("/region2/oscillator_mode") == 0); + REQUIRE( d.read("/region0/oscillator_multi") == 1); + REQUIRE( d.read("/region3/oscillator_multi") == 9); + REQUIRE( d.read("/region4/oscillator_multi") == 1); + } -TEST_CASE("[Values] Effect sends") -{ - 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 effect1=10 - sample=kick.wav effect2=50.4 - sample=kick.wav effect1=-1 - )"); - synth.dispatchMessage(client, 0, "/region0/effect1", "", nullptr); - synth.dispatchMessage(client, 0, "/region1/effect1", "", nullptr); - synth.dispatchMessage(client, 0, "/region2/effect1", "", nullptr); - synth.dispatchMessage(client, 0, "/region2/effect2", "", nullptr); - synth.dispatchMessage(client, 0, "/region4/effect1", "", nullptr); - std::vector expected { - // No reply to the first question - "/region1/effect1,f : { 10 }", - "/region2/effect1,f : { 0 }", - "/region2/effect2,f : { 50.4 }", - // No reply to the last question - }; - REQUIRE(messageList == expected); -} + SECTION("Oscillator detune/mod depth") + { + d.load(R"( + sample=kick.wav + sample=kick.wav oscillator_detune=9.2 + sample=kick.wav oscillator_detune=-1200.2 + sample=kick.wav oscillator_mod_depth=1564.75 + sample=kick.wav oscillator_mod_depth=-2.2 + )"); + REQUIRE( d.read("/region0/oscillator_detune") == 0.0f); + REQUIRE( d.read("/region1/oscillator_detune") == 9.2f); + REQUIRE( d.read("/region2/oscillator_detune") == -1200.2f); + REQUIRE( d.read("/region0/oscillator_mod_depth") == 0.0f); + REQUIRE( d.read("/region3/oscillator_mod_depth") == 1564.75f); + REQUIRE( d.read("/region4/oscillator_mod_depth") == -2.2f); + } -TEST_CASE("[Values] Support floating point for int values") -{ - 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 offset=1042.5 - sample=kick.wav pitch_keytrack=-2.1 - )"); - synth.dispatchMessage(client, 0, "/region0/offset", "", nullptr); - synth.dispatchMessage(client, 0, "/region1/pitch_keytrack", "", nullptr); - std::vector expected { - "/region0/offset,h : { 1042 }", - "/region1/pitch_keytrack,f : { -2.1 }", - }; - REQUIRE(messageList == expected); -} + SECTION("Effect sends") + { + d.load(R"( + sample=kick.wav + sample=kick.wav effect1=10 + sample=kick.wav effect2=50.4 + sample=kick.wav effect1=-1 + )"); + REQUIRE( !d.replied("/region0/effect1") ); + REQUIRE( d.read("/region1/effect1") == 10.0f); + REQUIRE( d.read("/region2/effect1") == 0.0f); + REQUIRE( d.read("/region2/effect2") == 50.4f); + REQUIRE( !d.replied("/region4/effect1") ); + } -TEST_CASE("[Values] ampeg CC") -{ - Synth synth; - std::vector messageList; - Client client(&messageList); - client.setReceiveCallback(&simpleMessageReceiver); + SECTION("Support floating point for int values") + { + d.load(R"( + sample=kick.wav offset=1042.5 + sample=kick.wav pitch_keytrack=-2.1 + )"); + REQUIRE( d.read("/region0/offset") == 1042); + REQUIRE( d.read("/region1/pitch_keytrack") == -2.1f); + } - SECTION("Basic") + SECTION("ampeg CC Basic") { - synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + d.load(R"( sample=kick.wav )"); - synth.dispatchMessage(client, 0, "/region0/ampeg_attack_cc1", "", nullptr); - synth.dispatchMessage(client, 0, "/region0/ampeg_delay_cc2", "", nullptr); - synth.dispatchMessage(client, 0, "/region0/ampeg_decay_cc3", "", nullptr); - synth.dispatchMessage(client, 0, "/region0/ampeg_hold_cc4", "", nullptr); - synth.dispatchMessage(client, 0, "/region0/ampeg_release_cc5", "", nullptr); - synth.dispatchMessage(client, 0, "/region0/ampeg_start_cc6", "", nullptr); - synth.dispatchMessage(client, 0, "/region0/ampeg_sustain_cc7", "", nullptr); - std::vector expected { - "/region0/ampeg_attack_cc1,f : { 0 }", - "/region0/ampeg_delay_cc2,f : { 0 }", - "/region0/ampeg_decay_cc3,f : { 0 }", - "/region0/ampeg_hold_cc4,f : { 0 }", - "/region0/ampeg_release_cc5,f : { 0 }", - "/region0/ampeg_start_cc6,f : { 0 }", - "/region0/ampeg_sustain_cc7,f : { 0 }", - }; - REQUIRE(messageList == expected); + REQUIRE( d.read("/region0/ampeg_attack_cc1") == 0.0f); + REQUIRE( d.read("/region0/ampeg_delay_cc2") == 0.0f); + REQUIRE( d.read("/region0/ampeg_decay_cc3") == 0.0f); + REQUIRE( d.read("/region0/ampeg_hold_cc4") == 0.0f); + REQUIRE( d.read("/region0/ampeg_release_cc5") == 0.0f); + REQUIRE( d.read("/region0/ampeg_start_cc6") == 0.0f); + REQUIRE( d.read("/region0/ampeg_sustain_cc7") == 0.0f); } - SECTION("Positive values") + SECTION("ampeg CC Positive values") { - synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + d.load(R"( sample=kick.wav ampeg_attack_oncc1=1 ampeg_delay_oncc2=2 ampeg_decay_oncc3=3 ampeg_hold_oncc4=4 ampeg_release_oncc5=5 ampeg_start_oncc6=6 ampeg_sustain_oncc7=7 )"); - synth.dispatchMessage(client, 0, "/region0/ampeg_attack_cc1", "", nullptr); - synth.dispatchMessage(client, 0, "/region0/ampeg_delay_cc2", "", nullptr); - synth.dispatchMessage(client, 0, "/region0/ampeg_decay_cc3", "", nullptr); - synth.dispatchMessage(client, 0, "/region0/ampeg_hold_cc4", "", nullptr); - synth.dispatchMessage(client, 0, "/region0/ampeg_release_cc5", "", nullptr); - synth.dispatchMessage(client, 0, "/region0/ampeg_start_cc6", "", nullptr); - synth.dispatchMessage(client, 0, "/region0/ampeg_sustain_cc7", "", nullptr); - std::vector expected { - "/region0/ampeg_attack_cc1,f : { 1 }", - "/region0/ampeg_delay_cc2,f : { 2 }", - "/region0/ampeg_decay_cc3,f : { 3 }", - "/region0/ampeg_hold_cc4,f : { 4 }", - "/region0/ampeg_release_cc5,f : { 5 }", - "/region0/ampeg_start_cc6,f : { 6 }", - "/region0/ampeg_sustain_cc7,f : { 7 }", - }; - REQUIRE(messageList == expected); + REQUIRE( d.read("/region0/ampeg_attack_cc1") == 1.0f); + REQUIRE( d.read("/region0/ampeg_delay_cc2") == 2.0f); + REQUIRE( d.read("/region0/ampeg_decay_cc3") == 3.0f); + REQUIRE( d.read("/region0/ampeg_hold_cc4") == 4.0f); + REQUIRE( d.read("/region0/ampeg_release_cc5") == 5.0f); + REQUIRE( d.read("/region0/ampeg_start_cc6") == 6.0f); + REQUIRE( d.read("/region0/ampeg_sustain_cc7") == 7.0f); } - SECTION("Negative values") + SECTION("ampeg CC Negative values") { - synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + d.load(R"( sample=kick.wav ampeg_attack_cc1=-1 ampeg_delay_cc2=-2 ampeg_decay_cc3=-3 ampeg_hold_cc4=-4 ampeg_release_cc5=-5 ampeg_start_cc6=-6 ampeg_sustain_cc7=-7 )"); - synth.dispatchMessage(client, 0, "/region0/ampeg_attack_cc1", "", nullptr); - synth.dispatchMessage(client, 0, "/region0/ampeg_delay_cc2", "", nullptr); - synth.dispatchMessage(client, 0, "/region0/ampeg_decay_cc3", "", nullptr); - synth.dispatchMessage(client, 0, "/region0/ampeg_hold_cc4", "", nullptr); - synth.dispatchMessage(client, 0, "/region0/ampeg_release_cc5", "", nullptr); - synth.dispatchMessage(client, 0, "/region0/ampeg_start_cc6", "", nullptr); - synth.dispatchMessage(client, 0, "/region0/ampeg_sustain_cc7", "", nullptr); - std::vector expected { - "/region0/ampeg_attack_cc1,f : { -1 }", - "/region0/ampeg_delay_cc2,f : { -2 }", - "/region0/ampeg_decay_cc3,f : { -3 }", - "/region0/ampeg_hold_cc4,f : { -4 }", - "/region0/ampeg_release_cc5,f : { -5 }", - "/region0/ampeg_start_cc6,f : { -6 }", - "/region0/ampeg_sustain_cc7,f : { -7 }", - }; - REQUIRE(messageList == expected); + REQUIRE( d.read("/region0/ampeg_attack_cc1") == -1.0f); + REQUIRE( d.read("/region0/ampeg_delay_cc2") == -2.0f); + REQUIRE( d.read("/region0/ampeg_decay_cc3") == -3.0f); + REQUIRE( d.read("/region0/ampeg_hold_cc4") == -4.0f); + REQUIRE( d.read("/region0/ampeg_release_cc5") == -5.0f); + REQUIRE( d.read("/region0/ampeg_start_cc6") == -6.0f); + REQUIRE( d.read("/region0/ampeg_sustain_cc7") == -7.0f); } -} -TEST_CASE("[Values] fileg CC") -{ - Synth synth; - std::vector messageList; - Client client(&messageList); - client.setReceiveCallback(&simpleMessageReceiver); - - SECTION("Basic") + SECTION("fileg basic") { - synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + d.load(R"( sample=kick.wav )"); - synth.dispatchMessage(client, 0, "/region0/fileg_attack_cc1", "", nullptr); - synth.dispatchMessage(client, 0, "/region0/fileg_delay_cc2", "", nullptr); - synth.dispatchMessage(client, 0, "/region0/fileg_decay_cc3", "", nullptr); - synth.dispatchMessage(client, 0, "/region0/fileg_hold_cc4", "", nullptr); - synth.dispatchMessage(client, 0, "/region0/fileg_release_cc5", "", nullptr); - synth.dispatchMessage(client, 0, "/region0/fileg_start_cc6", "", nullptr); - synth.dispatchMessage(client, 0, "/region0/fileg_sustain_cc7", "", nullptr); - std::vector expected { - "/region0/fileg_attack_cc1,N : { }", - "/region0/fileg_delay_cc2,N : { }", - "/region0/fileg_decay_cc3,N : { }", - "/region0/fileg_hold_cc4,N : { }", - "/region0/fileg_release_cc5,N : { }", - "/region0/fileg_start_cc6,N : { }", - "/region0/fileg_sustain_cc7,N : { }", - }; - REQUIRE(messageList == expected); + REQUIRE( d.read("/region0/fileg_attack_cc1") == OSC::None); + REQUIRE( d.read("/region0/fileg_delay_cc2") == OSC::None); + REQUIRE( d.read("/region0/fileg_decay_cc3") == OSC::None); + REQUIRE( d.read("/region0/fileg_hold_cc4") == OSC::None); + REQUIRE( d.read("/region0/fileg_release_cc5") == OSC::None); + REQUIRE( d.read("/region0/fileg_start_cc6") == OSC::None); + REQUIRE( d.read("/region0/fileg_sustain_cc7") == OSC::None); } - SECTION("Positive values") + SECTION("fileg positive values") { - synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + d.load(R"( sample=kick.wav fileg_attack_oncc1=1 fileg_delay_oncc2=2 fileg_decay_oncc3=3 fileg_hold_oncc4=4 fileg_release_oncc5=5 fileg_start_oncc6=6 fileg_sustain_oncc7=7 )"); - synth.dispatchMessage(client, 0, "/region0/fileg_attack_cc1", "", nullptr); - synth.dispatchMessage(client, 0, "/region0/fileg_delay_cc2", "", nullptr); - synth.dispatchMessage(client, 0, "/region0/fileg_decay_cc3", "", nullptr); - synth.dispatchMessage(client, 0, "/region0/fileg_hold_cc4", "", nullptr); - synth.dispatchMessage(client, 0, "/region0/fileg_release_cc5", "", nullptr); - synth.dispatchMessage(client, 0, "/region0/fileg_start_cc6", "", nullptr); - synth.dispatchMessage(client, 0, "/region0/fileg_sustain_cc7", "", nullptr); - std::vector expected { - "/region0/fileg_attack_cc1,f : { 1 }", - "/region0/fileg_delay_cc2,f : { 2 }", - "/region0/fileg_decay_cc3,f : { 3 }", - "/region0/fileg_hold_cc4,f : { 4 }", - "/region0/fileg_release_cc5,f : { 5 }", - "/region0/fileg_start_cc6,f : { 6 }", - "/region0/fileg_sustain_cc7,f : { 7 }", - }; - REQUIRE(messageList == expected); + REQUIRE( d.read("/region0/fileg_attack_cc1") == 1.0f); + REQUIRE( d.read("/region0/fileg_delay_cc2") == 2.0f); + REQUIRE( d.read("/region0/fileg_decay_cc3") == 3.0f); + REQUIRE( d.read("/region0/fileg_hold_cc4") == 4.0f); + REQUIRE( d.read("/region0/fileg_release_cc5") == 5.0f); + REQUIRE( d.read("/region0/fileg_start_cc6") == 6.0f); + REQUIRE( d.read("/region0/fileg_sustain_cc7") == 7.0f); } - SECTION("Negative values") + SECTION("fileg negative values") { - synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + d.load(R"( sample=kick.wav fileg_attack_cc1=-1 fileg_delay_cc2=-2 fileg_decay_cc3=-3 fileg_hold_cc4=-4 fileg_release_cc5=-5 fileg_start_cc6=-6 fileg_sustain_cc7=-7 )"); - synth.dispatchMessage(client, 0, "/region0/fileg_attack_cc1", "", nullptr); - synth.dispatchMessage(client, 0, "/region0/fileg_delay_cc2", "", nullptr); - synth.dispatchMessage(client, 0, "/region0/fileg_decay_cc3", "", nullptr); - synth.dispatchMessage(client, 0, "/region0/fileg_hold_cc4", "", nullptr); - synth.dispatchMessage(client, 0, "/region0/fileg_release_cc5", "", nullptr); - synth.dispatchMessage(client, 0, "/region0/fileg_start_cc6", "", nullptr); - synth.dispatchMessage(client, 0, "/region0/fileg_sustain_cc7", "", nullptr); - std::vector expected { - "/region0/fileg_attack_cc1,f : { -1 }", - "/region0/fileg_delay_cc2,f : { -2 }", - "/region0/fileg_decay_cc3,f : { -3 }", - "/region0/fileg_hold_cc4,f : { -4 }", - "/region0/fileg_release_cc5,f : { -5 }", - "/region0/fileg_start_cc6,f : { -6 }", - "/region0/fileg_sustain_cc7,f : { -7 }", - }; - REQUIRE(messageList == expected); + REQUIRE( d.read("/region0/fileg_attack_cc1") == -1.0f); + REQUIRE( d.read("/region0/fileg_delay_cc2") == -2.0f); + REQUIRE( d.read("/region0/fileg_decay_cc3") == -3.0f); + REQUIRE( d.read("/region0/fileg_hold_cc4") == -4.0f); + REQUIRE( d.read("/region0/fileg_release_cc5") == -5.0f); + REQUIRE( d.read("/region0/fileg_start_cc6") == -6.0f); + REQUIRE( d.read("/region0/fileg_sustain_cc7") == -7.0f); } -} -TEST_CASE("[Values] pitcheg CC") -{ - Synth synth; - std::vector messageList; - Client client(&messageList); - client.setReceiveCallback(&simpleMessageReceiver); - - SECTION("Basic") + SECTION("pitcheg basic") { - synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + d.load(R"( sample=kick.wav )"); - synth.dispatchMessage(client, 0, "/region0/pitcheg_attack_cc1", "", nullptr); - synth.dispatchMessage(client, 0, "/region0/pitcheg_delay_cc2", "", nullptr); - synth.dispatchMessage(client, 0, "/region0/pitcheg_decay_cc3", "", nullptr); - synth.dispatchMessage(client, 0, "/region0/pitcheg_hold_cc4", "", nullptr); - synth.dispatchMessage(client, 0, "/region0/pitcheg_release_cc5", "", nullptr); - synth.dispatchMessage(client, 0, "/region0/pitcheg_start_cc6", "", nullptr); - synth.dispatchMessage(client, 0, "/region0/pitcheg_sustain_cc7", "", nullptr); - std::vector expected { - "/region0/pitcheg_attack_cc1,N : { }", - "/region0/pitcheg_delay_cc2,N : { }", - "/region0/pitcheg_decay_cc3,N : { }", - "/region0/pitcheg_hold_cc4,N : { }", - "/region0/pitcheg_release_cc5,N : { }", - "/region0/pitcheg_start_cc6,N : { }", - "/region0/pitcheg_sustain_cc7,N : { }", - }; - REQUIRE(messageList == expected); + REQUIRE( d.read("/region0/pitcheg_attack_cc1") == OSC::None); + REQUIRE( d.read("/region0/pitcheg_delay_cc2") == OSC::None); + REQUIRE( d.read("/region0/pitcheg_decay_cc3") == OSC::None); + REQUIRE( d.read("/region0/pitcheg_hold_cc4") == OSC::None); + REQUIRE( d.read("/region0/pitcheg_release_cc5") == OSC::None); + REQUIRE( d.read("/region0/pitcheg_start_cc6") == OSC::None); + REQUIRE( d.read("/region0/pitcheg_sustain_cc7") == OSC::None); } - SECTION("Positive values") + SECTION("pitcheg positive values") { - synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + d.load(R"( sample=kick.wav pitcheg_attack_oncc1=1 pitcheg_delay_oncc2=2 pitcheg_decay_oncc3=3 pitcheg_hold_oncc4=4 pitcheg_release_oncc5=5 pitcheg_start_oncc6=6 pitcheg_sustain_oncc7=7 )"); - synth.dispatchMessage(client, 0, "/region0/pitcheg_attack_cc1", "", nullptr); - synth.dispatchMessage(client, 0, "/region0/pitcheg_delay_cc2", "", nullptr); - synth.dispatchMessage(client, 0, "/region0/pitcheg_decay_cc3", "", nullptr); - synth.dispatchMessage(client, 0, "/region0/pitcheg_hold_cc4", "", nullptr); - synth.dispatchMessage(client, 0, "/region0/pitcheg_release_cc5", "", nullptr); - synth.dispatchMessage(client, 0, "/region0/pitcheg_start_cc6", "", nullptr); - synth.dispatchMessage(client, 0, "/region0/pitcheg_sustain_cc7", "", nullptr); - std::vector expected { - "/region0/pitcheg_attack_cc1,f : { 1 }", - "/region0/pitcheg_delay_cc2,f : { 2 }", - "/region0/pitcheg_decay_cc3,f : { 3 }", - "/region0/pitcheg_hold_cc4,f : { 4 }", - "/region0/pitcheg_release_cc5,f : { 5 }", - "/region0/pitcheg_start_cc6,f : { 6 }", - "/region0/pitcheg_sustain_cc7,f : { 7 }", - }; - REQUIRE(messageList == expected); + REQUIRE( d.read("/region0/pitcheg_attack_cc1") == 1.0f); + REQUIRE( d.read("/region0/pitcheg_delay_cc2") == 2.0f); + REQUIRE( d.read("/region0/pitcheg_decay_cc3") == 3.0f); + REQUIRE( d.read("/region0/pitcheg_hold_cc4") == 4.0f); + REQUIRE( d.read("/region0/pitcheg_release_cc5") == 5.0f); + REQUIRE( d.read("/region0/pitcheg_start_cc6") == 6.0f); + REQUIRE( d.read("/region0/pitcheg_sustain_cc7") == 7.0f); } - SECTION("Negative values") + SECTION("pitcheg negative values") { - synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + d.load(R"( sample=kick.wav pitcheg_attack_cc1=-1 pitcheg_delay_cc2=-2 pitcheg_decay_cc3=-3 pitcheg_hold_cc4=-4 pitcheg_release_cc5=-5 pitcheg_start_cc6=-6 pitcheg_sustain_cc7=-7 )"); - synth.dispatchMessage(client, 0, "/region0/pitcheg_attack_cc1", "", nullptr); - synth.dispatchMessage(client, 0, "/region0/pitcheg_delay_cc2", "", nullptr); - synth.dispatchMessage(client, 0, "/region0/pitcheg_decay_cc3", "", nullptr); - synth.dispatchMessage(client, 0, "/region0/pitcheg_hold_cc4", "", nullptr); - synth.dispatchMessage(client, 0, "/region0/pitcheg_release_cc5", "", nullptr); - synth.dispatchMessage(client, 0, "/region0/pitcheg_start_cc6", "", nullptr); - synth.dispatchMessage(client, 0, "/region0/pitcheg_sustain_cc7", "", nullptr); - std::vector expected { - "/region0/pitcheg_attack_cc1,f : { -1 }", - "/region0/pitcheg_delay_cc2,f : { -2 }", - "/region0/pitcheg_decay_cc3,f : { -3 }", - "/region0/pitcheg_hold_cc4,f : { -4 }", - "/region0/pitcheg_release_cc5,f : { -5 }", - "/region0/pitcheg_start_cc6,f : { -6 }", - "/region0/pitcheg_sustain_cc7,f : { -7 }", - }; - REQUIRE(messageList == expected); + REQUIRE( d.read("/region0/pitcheg_attack_cc1") == -1.0f); + REQUIRE( d.read("/region0/pitcheg_delay_cc2") == -2.0f); + REQUIRE( d.read("/region0/pitcheg_decay_cc3") == -3.0f); + REQUIRE( d.read("/region0/pitcheg_hold_cc4") == -4.0f); + REQUIRE( d.read("/region0/pitcheg_release_cc5") == -5.0f); + REQUIRE( d.read("/region0/pitcheg_start_cc6") == -6.0f); + REQUIRE( d.read("/region0/pitcheg_sustain_cc7") == -7.0f); } -} -TEST_CASE("[Values] ampeg curve CC") -{ - Synth synth; - std::vector messageList; - Client client(&messageList); - client.setReceiveCallback(&simpleMessageReceiver); - - SECTION("Basic") + SECTION("ampeg curve CC basic") { - synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + d.load(R"( sample=kick.wav )"); - synth.dispatchMessage(client, 0, "/region0/ampeg_attack_curvecc1", "", nullptr); - synth.dispatchMessage(client, 0, "/region0/ampeg_delay_curvecc2", "", nullptr); - synth.dispatchMessage(client, 0, "/region0/ampeg_decay_curvecc3", "", nullptr); - synth.dispatchMessage(client, 0, "/region0/ampeg_hold_curvecc4", "", nullptr); - synth.dispatchMessage(client, 0, "/region0/ampeg_release_curvecc5", "", nullptr); - synth.dispatchMessage(client, 0, "/region0/ampeg_start_curvecc6", "", nullptr); - synth.dispatchMessage(client, 0, "/region0/ampeg_sustain_curvecc7", "", nullptr); - std::vector expected { - "/region0/ampeg_attack_curvecc1,i : { 0 }", - "/region0/ampeg_delay_curvecc2,i : { 0 }", - "/region0/ampeg_decay_curvecc3,i : { 0 }", - "/region0/ampeg_hold_curvecc4,i : { 0 }", - "/region0/ampeg_release_curvecc5,i : { 0 }", - "/region0/ampeg_start_curvecc6,i : { 0 }", - "/region0/ampeg_sustain_curvecc7,i : { 0 }", - }; - REQUIRE(messageList == expected); + REQUIRE( d.read("/region0/ampeg_attack_curvecc1") == 0); + REQUIRE( d.read("/region0/ampeg_delay_curvecc2") == 0); + REQUIRE( d.read("/region0/ampeg_decay_curvecc3") == 0); + REQUIRE( d.read("/region0/ampeg_hold_curvecc4") == 0); + REQUIRE( d.read("/region0/ampeg_release_curvecc5") == 0); + REQUIRE( d.read("/region0/ampeg_start_curvecc6") == 0); + REQUIRE( d.read("/region0/ampeg_sustain_curvecc7") == 0); } - SECTION("Change curves") + SECTION("ampeg curve CC change curves") { - synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + d.load(R"( sample=kick.wav ampeg_attack_curvecc1=1 ampeg_delay_curvecc2=2 ampeg_decay_curvecc3=3 ampeg_hold_curvecc4=4 ampeg_release_curvecc5=5 ampeg_start_curvecc6=6 ampeg_sustain_curvecc7=7 )"); - synth.dispatchMessage(client, 0, "/region0/ampeg_attack_curvecc1", "", nullptr); - synth.dispatchMessage(client, 0, "/region0/ampeg_delay_curvecc2", "", nullptr); - synth.dispatchMessage(client, 0, "/region0/ampeg_decay_curvecc3", "", nullptr); - synth.dispatchMessage(client, 0, "/region0/ampeg_hold_curvecc4", "", nullptr); - synth.dispatchMessage(client, 0, "/region0/ampeg_release_curvecc5", "", nullptr); - synth.dispatchMessage(client, 0, "/region0/ampeg_start_curvecc6", "", nullptr); - synth.dispatchMessage(client, 0, "/region0/ampeg_sustain_curvecc7", "", nullptr); - std::vector expected { - "/region0/ampeg_attack_curvecc1,i : { 1 }", - "/region0/ampeg_delay_curvecc2,i : { 2 }", - "/region0/ampeg_decay_curvecc3,i : { 3 }", - "/region0/ampeg_hold_curvecc4,i : { 4 }", - "/region0/ampeg_release_curvecc5,i : { 5 }", - "/region0/ampeg_start_curvecc6,i : { 6 }", - "/region0/ampeg_sustain_curvecc7,i : { 7 }", - }; - REQUIRE(messageList == expected); + REQUIRE( d.read("/region0/ampeg_attack_curvecc1") == 1); + REQUIRE( d.read("/region0/ampeg_delay_curvecc2") == 2); + REQUIRE( d.read("/region0/ampeg_decay_curvecc3") == 3); + REQUIRE( d.read("/region0/ampeg_hold_curvecc4") == 4); + REQUIRE( d.read("/region0/ampeg_release_curvecc5") == 5); + REQUIRE( d.read("/region0/ampeg_start_curvecc6") == 6); + REQUIRE( d.read("/region0/ampeg_sustain_curvecc7") == 7); } -} - -TEST_CASE("[Values] fileg curve CC") -{ - Synth synth; - std::vector messageList; - Client client(&messageList); - client.setReceiveCallback(&simpleMessageReceiver); - - SECTION("Basic") - { - synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( - sample=kick.wav - )"); - synth.dispatchMessage(client, 0, "/region0/fileg_attack_curvecc1", "", nullptr); - synth.dispatchMessage(client, 0, "/region0/fileg_delay_curvecc2", "", nullptr); - synth.dispatchMessage(client, 0, "/region0/fileg_decay_curvecc3", "", nullptr); - synth.dispatchMessage(client, 0, "/region0/fileg_hold_curvecc4", "", nullptr); - synth.dispatchMessage(client, 0, "/region0/fileg_release_curvecc5", "", nullptr); - synth.dispatchMessage(client, 0, "/region0/fileg_start_curvecc6", "", nullptr); - synth.dispatchMessage(client, 0, "/region0/fileg_sustain_curvecc7", "", nullptr); - std::vector expected { - "/region0/fileg_attack_curvecc1,N : { }", - "/region0/fileg_delay_curvecc2,N : { }", - "/region0/fileg_decay_curvecc3,N : { }", - "/region0/fileg_hold_curvecc4,N : { }", - "/region0/fileg_release_curvecc5,N : { }", - "/region0/fileg_start_curvecc6,N : { }", - "/region0/fileg_sustain_curvecc7,N : { }", - }; - REQUIRE(messageList == expected); + SECTION("fileg curve CC basic") + { + d.load(R"( + sample=kick.wav + )"); + REQUIRE( d.read("/region0/fileg_attack_curvecc1") == OSC::None); + REQUIRE( d.read("/region0/fileg_delay_curvecc2") == OSC::None); + REQUIRE( d.read("/region0/fileg_decay_curvecc3") == OSC::None); + REQUIRE( d.read("/region0/fileg_hold_curvecc4") == OSC::None); + REQUIRE( d.read("/region0/fileg_release_curvecc5") == OSC::None); + REQUIRE( d.read("/region0/fileg_start_curvecc6") == OSC::None); + REQUIRE( d.read("/region0/fileg_sustain_curvecc7") == OSC::None); } - SECTION("Change curves") + SECTION("fileg curve CC change curves") { - synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + d.load(R"( sample=kick.wav fileg_attack_curvecc1=1 fileg_delay_curvecc2=2 fileg_decay_curvecc3=3 fileg_hold_curvecc4=4 fileg_release_curvecc5=5 fileg_start_curvecc6=6 fileg_sustain_curvecc7=7 )"); - synth.dispatchMessage(client, 0, "/region0/fileg_attack_curvecc1", "", nullptr); - synth.dispatchMessage(client, 0, "/region0/fileg_delay_curvecc2", "", nullptr); - synth.dispatchMessage(client, 0, "/region0/fileg_decay_curvecc3", "", nullptr); - synth.dispatchMessage(client, 0, "/region0/fileg_hold_curvecc4", "", nullptr); - synth.dispatchMessage(client, 0, "/region0/fileg_release_curvecc5", "", nullptr); - synth.dispatchMessage(client, 0, "/region0/fileg_start_curvecc6", "", nullptr); - synth.dispatchMessage(client, 0, "/region0/fileg_sustain_curvecc7", "", nullptr); - std::vector expected { - "/region0/fileg_attack_curvecc1,i : { 1 }", - "/region0/fileg_delay_curvecc2,i : { 2 }", - "/region0/fileg_decay_curvecc3,i : { 3 }", - "/region0/fileg_hold_curvecc4,i : { 4 }", - "/region0/fileg_release_curvecc5,i : { 5 }", - "/region0/fileg_start_curvecc6,i : { 6 }", - "/region0/fileg_sustain_curvecc7,i : { 7 }", - }; - REQUIRE(messageList == expected); + REQUIRE( d.read("/region0/fileg_attack_curvecc1") == 1); + REQUIRE( d.read("/region0/fileg_delay_curvecc2") == 2); + REQUIRE( d.read("/region0/fileg_decay_curvecc3") == 3); + REQUIRE( d.read("/region0/fileg_hold_curvecc4") == 4); + REQUIRE( d.read("/region0/fileg_release_curvecc5") == 5); + REQUIRE( d.read("/region0/fileg_start_curvecc6") == 6); + REQUIRE( d.read("/region0/fileg_sustain_curvecc7") == 7); } -} - -TEST_CASE("[Values] pitcheg curve CC") -{ - Synth synth; - std::vector messageList; - Client client(&messageList); - client.setReceiveCallback(&simpleMessageReceiver); - - SECTION("Basic") - { - synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( - sample=kick.wav - )"); - synth.dispatchMessage(client, 0, "/region0/pitcheg_attack_curvecc1", "", nullptr); - synth.dispatchMessage(client, 0, "/region0/pitcheg_delay_curvecc2", "", nullptr); - synth.dispatchMessage(client, 0, "/region0/pitcheg_decay_curvecc3", "", nullptr); - synth.dispatchMessage(client, 0, "/region0/pitcheg_hold_curvecc4", "", nullptr); - synth.dispatchMessage(client, 0, "/region0/pitcheg_release_curvecc5", "", nullptr); - synth.dispatchMessage(client, 0, "/region0/pitcheg_start_curvecc6", "", nullptr); - synth.dispatchMessage(client, 0, "/region0/pitcheg_sustain_curvecc7", "", nullptr); - std::vector expected { - "/region0/pitcheg_attack_curvecc1,N : { }", - "/region0/pitcheg_delay_curvecc2,N : { }", - "/region0/pitcheg_decay_curvecc3,N : { }", - "/region0/pitcheg_hold_curvecc4,N : { }", - "/region0/pitcheg_release_curvecc5,N : { }", - "/region0/pitcheg_start_curvecc6,N : { }", - "/region0/pitcheg_sustain_curvecc7,N : { }", - }; - REQUIRE(messageList == expected); + SECTION("pitcheg curve CC basic") + { + d.load(R"( + sample=kick.wav + )"); + REQUIRE( d.read("/region0/pitcheg_attack_curvecc1") == OSC::None); + REQUIRE( d.read("/region0/pitcheg_delay_curvecc2") == OSC::None); + REQUIRE( d.read("/region0/pitcheg_decay_curvecc3") == OSC::None); + REQUIRE( d.read("/region0/pitcheg_hold_curvecc4") == OSC::None); + REQUIRE( d.read("/region0/pitcheg_release_curvecc5") == OSC::None); + REQUIRE( d.read("/region0/pitcheg_start_curvecc6") == OSC::None); + REQUIRE( d.read("/region0/pitcheg_sustain_curvecc7") == OSC::None); } - SECTION("Change curves") + SECTION("pitcheg curve CC change curves") { - synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + d.load(R"( sample=kick.wav pitcheg_attack_curvecc1=1 pitcheg_delay_curvecc2=2 pitcheg_decay_curvecc3=3 pitcheg_hold_curvecc4=4 pitcheg_release_curvecc5=5 pitcheg_start_curvecc6=6 pitcheg_sustain_curvecc7=7 )"); - synth.dispatchMessage(client, 0, "/region0/pitcheg_attack_curvecc1", "", nullptr); - synth.dispatchMessage(client, 0, "/region0/pitcheg_delay_curvecc2", "", nullptr); - synth.dispatchMessage(client, 0, "/region0/pitcheg_decay_curvecc3", "", nullptr); - synth.dispatchMessage(client, 0, "/region0/pitcheg_hold_curvecc4", "", nullptr); - synth.dispatchMessage(client, 0, "/region0/pitcheg_release_curvecc5", "", nullptr); - synth.dispatchMessage(client, 0, "/region0/pitcheg_start_curvecc6", "", nullptr); - synth.dispatchMessage(client, 0, "/region0/pitcheg_sustain_curvecc7", "", nullptr); - std::vector expected { - "/region0/pitcheg_attack_curvecc1,i : { 1 }", - "/region0/pitcheg_delay_curvecc2,i : { 2 }", - "/region0/pitcheg_decay_curvecc3,i : { 3 }", - "/region0/pitcheg_hold_curvecc4,i : { 4 }", - "/region0/pitcheg_release_curvecc5,i : { 5 }", - "/region0/pitcheg_start_curvecc6,i : { 6 }", - "/region0/pitcheg_sustain_curvecc7,i : { 7 }", - }; - REQUIRE(messageList == expected); - } -} - -TEST_CASE("[Values] Filter stacking and cutoffs") -{ - 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 cutoff=50 - sample=kick.wav cutoff2=500 - )"); - - SECTION("Test first region") - { - synth.dispatchMessage(client, 0, "/region0/filter0/cutoff", "", nullptr); - synth.dispatchMessage(client, 0, "/region0/filter0/gain", "", nullptr); - synth.dispatchMessage(client, 0, "/region0/filter0/resonance", "", nullptr); - synth.dispatchMessage(client, 0, "/region0/filter0/keycenter", "", nullptr); - synth.dispatchMessage(client, 0, "/region0/filter0/keytrack", "", nullptr); - synth.dispatchMessage(client, 0, "/region0/filter0/veltrack", "", nullptr); - synth.dispatchMessage(client, 0, "/region0/filter0/type", "", nullptr); - synth.dispatchMessage(client, 0, "/region0/filter1/cutoff", "", nullptr); - synth.dispatchMessage(client, 0, "/region0/filter1/gain", "", nullptr); - synth.dispatchMessage(client, 0, "/region0/filter1/resonance", "", nullptr); - synth.dispatchMessage(client, 0, "/region0/filter1/keycenter", "", nullptr); - synth.dispatchMessage(client, 0, "/region0/filter1/keytrack", "", nullptr); - synth.dispatchMessage(client, 0, "/region0/filter1/veltrack", "", nullptr); - synth.dispatchMessage(client, 0, "/region0/filter1/type", "", nullptr); - std::vector expected { - // No filters - }; - REQUIRE(messageList == expected); - } - - SECTION("Test second region") - { - synth.dispatchMessage(client, 0, "/region1/filter0/cutoff", "", nullptr); - synth.dispatchMessage(client, 0, "/region1/filter0/gain", "", nullptr); - synth.dispatchMessage(client, 0, "/region1/filter0/resonance", "", nullptr); - synth.dispatchMessage(client, 0, "/region1/filter0/keycenter", "", nullptr); - synth.dispatchMessage(client, 0, "/region1/filter0/keytrack", "", nullptr); - synth.dispatchMessage(client, 0, "/region1/filter0/veltrack", "", nullptr); - synth.dispatchMessage(client, 0, "/region1/filter0/type", "", nullptr); - synth.dispatchMessage(client, 0, "/region1/filter1/cutoff", "", nullptr); - synth.dispatchMessage(client, 0, "/region1/filter1/gain", "", nullptr); - synth.dispatchMessage(client, 0, "/region1/filter1/resonance", "", nullptr); - synth.dispatchMessage(client, 0, "/region1/filter1/keycenter", "", nullptr); - synth.dispatchMessage(client, 0, "/region1/filter1/keytrack", "", nullptr); - synth.dispatchMessage(client, 0, "/region1/filter1/veltrack", "", nullptr); - synth.dispatchMessage(client, 0, "/region1/filter1/type", "", nullptr); - std::vector expected { - "/region1/filter0/cutoff,f : { 50 }", - "/region1/filter0/gain,f : { 0 }", - "/region1/filter0/resonance,f : { 0 }", - "/region1/filter0/keycenter,i : { 60 }", - "/region1/filter0/keytrack,f : { 0 }", - "/region1/filter0/veltrack,f : { 0 }", - "/region1/filter0/type,s : { lpf_2p }", - // No second filter - }; - REQUIRE(messageList == expected); - } - - SECTION("Test third region") - { - synth.dispatchMessage(client, 0, "/region2/filter0/cutoff", "", nullptr); - synth.dispatchMessage(client, 0, "/region2/filter0/gain", "", nullptr); - synth.dispatchMessage(client, 0, "/region2/filter0/resonance", "", nullptr); - synth.dispatchMessage(client, 0, "/region2/filter0/keycenter", "", nullptr); - synth.dispatchMessage(client, 0, "/region2/filter0/keytrack", "", nullptr); - synth.dispatchMessage(client, 0, "/region2/filter0/veltrack", "", nullptr); - synth.dispatchMessage(client, 0, "/region2/filter0/type", "", nullptr); - synth.dispatchMessage(client, 0, "/region2/filter1/cutoff", "", nullptr); - synth.dispatchMessage(client, 0, "/region2/filter1/gain", "", nullptr); - synth.dispatchMessage(client, 0, "/region2/filter1/resonance", "", nullptr); - synth.dispatchMessage(client, 0, "/region2/filter1/keycenter", "", nullptr); - synth.dispatchMessage(client, 0, "/region2/filter1/keytrack", "", nullptr); - synth.dispatchMessage(client, 0, "/region2/filter1/veltrack", "", nullptr); - synth.dispatchMessage(client, 0, "/region2/filter1/type", "", nullptr); - std::vector expected { - // The first filter is default-filled - "/region2/filter0/cutoff,f : { 0 }", - "/region2/filter0/gain,f : { 0 }", - "/region2/filter0/resonance,f : { 0 }", - "/region2/filter0/keycenter,i : { 60 }", - "/region2/filter0/keytrack,f : { 0 }", - "/region2/filter0/veltrack,f : { 0 }", - "/region2/filter0/type,s : { lpf_2p }", - "/region2/filter1/cutoff,f : { 500 }", - "/region2/filter1/gain,f : { 0 }", - "/region2/filter1/resonance,f : { 0 }", - "/region2/filter1/keycenter,i : { 60 }", - "/region2/filter1/keytrack,f : { 0 }", - "/region2/filter1/veltrack,f : { 0 }", - "/region2/filter1/type,s : { lpf_2p }", - // No second filter - }; - REQUIRE(messageList == expected); - } -} - -TEST_CASE("[Values] Cutoff modifiers") -{ - 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 cutoff_cc2=1000 cutoff_stepcc2=10 cutoff_smoothcc2=2 cutoff_curvecc2=4 - sample=kick.wav cutoff2_cc3=100 cutoff2_stepcc3=1 cutoff2_smoothcc3=20 cutoff2_curvecc3=3 - )"); - - synth.dispatchMessage(client, 0, "/region0/filter0/cutoff_cc1", "", nullptr); - synth.dispatchMessage(client, 0, "/region0/filter0/cutoff_stepcc1", "", nullptr); - synth.dispatchMessage(client, 0, "/region0/filter0/cutoff_smoothcc1", "", nullptr); - synth.dispatchMessage(client, 0, "/region0/filter0/cutoff_curvecc1", "", nullptr); - synth.dispatchMessage(client, 0, "/region0/filter0/cutoff_cc2", "", nullptr); - synth.dispatchMessage(client, 0, "/region0/filter0/cutoff_stepcc2", "", nullptr); - synth.dispatchMessage(client, 0, "/region0/filter0/cutoff_smoothcc2", "", nullptr); - synth.dispatchMessage(client, 0, "/region0/filter0/cutoff_curvecc2", "", nullptr); - synth.dispatchMessage(client, 0, "/region1/filter1/cutoff_cc3", "", nullptr); - synth.dispatchMessage(client, 0, "/region1/filter1/cutoff_stepcc3", "", nullptr); - synth.dispatchMessage(client, 0, "/region1/filter1/cutoff_smoothcc3", "", nullptr); - synth.dispatchMessage(client, 0, "/region1/filter1/cutoff_curvecc3", "", nullptr); - std::vector expected { - "/region0/filter0/cutoff_cc1,N : { }", - "/region0/filter0/cutoff_stepcc1,N : { }", - "/region0/filter0/cutoff_smoothcc1,N : { }", - "/region0/filter0/cutoff_curvecc1,N : { }", - "/region0/filter0/cutoff_cc2,f : { 1000 }", - "/region0/filter0/cutoff_stepcc2,f : { 10 }", - "/region0/filter0/cutoff_smoothcc2,i : { 2 }", - "/region0/filter0/cutoff_curvecc2,i : { 4 }", - "/region1/filter1/cutoff_cc3,f : { 100 }", - "/region1/filter1/cutoff_stepcc3,f : { 1 }", - "/region1/filter1/cutoff_smoothcc3,i : { 20 }", - "/region1/filter1/cutoff_curvecc3,i : { 3 }", - }; - REQUIRE(messageList == expected); -} - -TEST_CASE("[Values] Filter types") -{ - 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 fil_type=lpf_1p - sample=kick.wav fil_type=hpf_1p - sample=kick.wav fil_type=lpf_2p - sample=kick.wav fil_type=hpf_2p - sample=kick.wav fil_type=bpf_2p - sample=kick.wav fil_type=brf_2p - sample=kick.wav fil_type=bpf_1p - sample=kick.wav fil_type=brf_1p - sample=kick.wav fil_type=apf_1p - sample=kick.wav fil_type=lpf_2p_sv - sample=kick.wav fil_type=hpf_2p_sv - sample=kick.wav fil_type=bpf_2p_sv - sample=kick.wav fil_type=brf_2p_sv - sample=kick.wav fil_type=lpf_4p - sample=kick.wav fil_type=hpf_4p - sample=kick.wav fil_type=lpf_6p - sample=kick.wav fil_type=hpf_6p - sample=kick.wav fil_type=pink - sample=kick.wav fil_type=lsh - sample=kick.wav fil_type=hsh - sample=kick.wav fil_type=peq - sample=kick.wav fil2_type=peq - sample=kick.wav fil2_type=something - )"); - - synth.dispatchMessage(client, 0, "/region0/filter0/type", "", nullptr); - synth.dispatchMessage(client, 0, "/region1/filter0/type", "", nullptr); - synth.dispatchMessage(client, 0, "/region2/filter0/type", "", nullptr); - synth.dispatchMessage(client, 0, "/region3/filter0/type", "", nullptr); - synth.dispatchMessage(client, 0, "/region4/filter0/type", "", nullptr); - synth.dispatchMessage(client, 0, "/region5/filter0/type", "", nullptr); - synth.dispatchMessage(client, 0, "/region6/filter0/type", "", nullptr); - synth.dispatchMessage(client, 0, "/region7/filter0/type", "", nullptr); - synth.dispatchMessage(client, 0, "/region8/filter0/type", "", nullptr); - synth.dispatchMessage(client, 0, "/region9/filter0/type", "", nullptr); - synth.dispatchMessage(client, 0, "/region10/filter0/type", "", nullptr); - synth.dispatchMessage(client, 0, "/region11/filter0/type", "", nullptr); - synth.dispatchMessage(client, 0, "/region12/filter0/type", "", nullptr); - synth.dispatchMessage(client, 0, "/region13/filter0/type", "", nullptr); - synth.dispatchMessage(client, 0, "/region14/filter0/type", "", nullptr); - synth.dispatchMessage(client, 0, "/region15/filter0/type", "", nullptr); - synth.dispatchMessage(client, 0, "/region16/filter0/type", "", nullptr); - synth.dispatchMessage(client, 0, "/region17/filter0/type", "", nullptr); - synth.dispatchMessage(client, 0, "/region18/filter0/type", "", nullptr); - synth.dispatchMessage(client, 0, "/region19/filter0/type", "", nullptr); - synth.dispatchMessage(client, 0, "/region20/filter0/type", "", nullptr); - synth.dispatchMessage(client, 0, "/region21/filter1/type", "", nullptr); - synth.dispatchMessage(client, 0, "/region22/filter1/type", "", nullptr); - std::vector expected { - "/region0/filter0/type,s : { lpf_1p }", - "/region1/filter0/type,s : { hpf_1p }", - "/region2/filter0/type,s : { lpf_2p }", - "/region3/filter0/type,s : { hpf_2p }", - "/region4/filter0/type,s : { bpf_2p }", - "/region5/filter0/type,s : { brf_2p }", - "/region6/filter0/type,s : { bpf_1p }", - "/region7/filter0/type,s : { brf_2p }", // If we have a 1-pole brf at one point, change it back - "/region8/filter0/type,s : { none }", // If the apf 1-pole works, change it back - "/region9/filter0/type,s : { lpf_2p_sv }", - "/region10/filter0/type,s : { hpf_2p_sv }", - "/region11/filter0/type,s : { bpf_2p_sv }", - "/region12/filter0/type,s : { brf_2p_sv }", - "/region13/filter0/type,s : { lpf_4p }", - "/region14/filter0/type,s : { hpf_4p }", - "/region15/filter0/type,s : { lpf_6p }", - "/region16/filter0/type,s : { hpf_6p }", - "/region17/filter0/type,s : { pink }", - "/region18/filter0/type,s : { lsh }", - "/region19/filter0/type,s : { hsh }", - "/region20/filter0/type,s : { peq }", - "/region21/filter1/type,s : { peq }", - "/region22/filter1/type,s : { none }", - }; - REQUIRE(messageList == expected); -} - -TEST_CASE("[Values] Filter dispatching") -{ - 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 - 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); - synth.dispatchMessage(client, 0, "/region0/filter1/resonance", "", nullptr); - synth.dispatchMessage(client, 0, "/region0/filter1/gain", "", nullptr); - 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 }", - "/region0/filter1/gain,f : { -5 }", - "/region0/filter2/keytrack,f : { 100 }", - "/region0/filter0/gain,f : { -5 }", - "/region0/filter1/veltrack,f : { -100 }", - "/region0/filter3/veltrack_cc7,f : { -100 }", - "/region0/filter4/veltrack_curvecc2,i : { 2 }", - }; - REQUIRE(messageList == expected); -} - -TEST_CASE("[Values] Filter value bounds") -{ - Synth synth; - std::vector messageList; - Client client(&messageList); - client.setReceiveCallback(&simpleMessageReceiver); - - SECTION("Cutoff") - { - synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + REQUIRE( d.read("/region0/pitcheg_attack_curvecc1") == 1); + REQUIRE( d.read("/region0/pitcheg_delay_curvecc2") == 2); + REQUIRE( d.read("/region0/pitcheg_decay_curvecc3") == 3); + REQUIRE( d.read("/region0/pitcheg_hold_curvecc4") == 4); + REQUIRE( d.read("/region0/pitcheg_release_curvecc5") == 5); + REQUIRE( d.read("/region0/pitcheg_start_curvecc6") == 6); + REQUIRE( d.read("/region0/pitcheg_sustain_curvecc7") == 7); + } + + SECTION("Filter stacking and cutoffs") + { + d.load(R"( + sample=kick.wav + sample=kick.wav cutoff=50 + sample=kick.wav cutoff2=500 + )"); + REQUIRE( !d.replied("/region0/filter0/cutoff") ); + REQUIRE( !d.replied("/region0/filter0/gain") ); + REQUIRE( !d.replied("/region0/filter0/resonance") ); + REQUIRE( !d.replied("/region0/filter0/keycenter") ); + REQUIRE( !d.replied("/region0/filter0/keytrack") ); + REQUIRE( !d.replied("/region0/filter0/veltrack") ); + REQUIRE( !d.replied("/region0/filter0/type") ); + REQUIRE( !d.replied("/region0/filter1/cutoff") ); + REQUIRE( !d.replied("/region0/filter1/gain") ); + REQUIRE( !d.replied("/region0/filter1/resonance") ); + REQUIRE( !d.replied("/region0/filter1/keycenter") ); + REQUIRE( !d.replied("/region0/filter1/keytrack") ); + REQUIRE( !d.replied("/region0/filter1/veltrack") ); + REQUIRE( !d.replied("/region0/filter1/type") );\ + + // Second region + REQUIRE( d.read("/region1/filter0/cutoff") == 50.0f); + REQUIRE( d.read("/region1/filter0/gain") == 0.0f); + REQUIRE( d.read("/region1/filter0/resonance") == 0.0f); + REQUIRE( d.read("/region1/filter0/keycenter") == 60); + REQUIRE( d.read("/region1/filter0/keytrack") == 0.0f); + REQUIRE( d.read("/region1/filter0/veltrack") == 0.0f); + REQUIRE( d.read("/region1/filter0/type") == "lpf_2p"); + // No second filter on the second region + REQUIRE( !d.replied("/region1/filter1/cutoff")); + REQUIRE( !d.replied("/region1/filter1/gain")); + REQUIRE( !d.replied("/region1/filter1/resonance")); + REQUIRE( !d.replied("/region1/filter1/keycenter")); + REQUIRE( !d.replied("/region1/filter1/keytrack")); + REQUIRE( !d.replied("/region1/filter1/veltrack")); + REQUIRE( !d.replied("/region1/filter1/type")); + + // Third region + REQUIRE( d.read("/region2/filter0/cutoff") == 0.0f); + REQUIRE( d.read("/region2/filter0/gain") == 0.0f); + REQUIRE( d.read("/region2/filter0/resonance") == 0.0f); + REQUIRE( d.read("/region2/filter0/keycenter") == 60); + REQUIRE( d.read("/region2/filter0/keytrack") == 0.0f); + REQUIRE( d.read("/region2/filter0/veltrack") == 0.0f); + REQUIRE( d.read("/region2/filter0/type") == "lpf_2p"); + REQUIRE( d.read("/region2/filter1/cutoff") == 500.0f); + REQUIRE( d.read("/region2/filter1/gain") == 0.0f); + REQUIRE( d.read("/region2/filter1/resonance") == 0.0f); + REQUIRE( d.read("/region2/filter1/keycenter") == 60); + REQUIRE( d.read("/region2/filter1/keytrack") == 0.0f); + REQUIRE( d.read("/region2/filter1/veltrack") == 0.0f); + REQUIRE( d.read("/region2/filter1/type") == "lpf_2p"); + } + + SECTION("Cutoff modifiers") + { + d.load(R"( + sample=kick.wav cutoff_cc2=1000 cutoff_stepcc2=10 cutoff_smoothcc2=2 cutoff_curvecc2=4 + sample=kick.wav cutoff2_cc3=100 cutoff2_stepcc3=1 cutoff2_smoothcc3=20 cutoff2_curvecc3=3 + )"); + + REQUIRE( d.read("/region0/filter0/cutoff_cc1") == OSC::None); + REQUIRE( d.read("/region0/filter0/cutoff_stepcc1") == OSC::None); + REQUIRE( d.read("/region0/filter0/cutoff_smoothcc1") == OSC::None); + REQUIRE( d.read("/region0/filter0/cutoff_curvecc1") == OSC::None); + REQUIRE( d.read("/region0/filter0/cutoff_cc2") == 1000.0f); + REQUIRE( d.read("/region0/filter0/cutoff_stepcc2") == 10.0f); + REQUIRE( d.read("/region0/filter0/cutoff_smoothcc2") == 2); + REQUIRE( d.read("/region0/filter0/cutoff_curvecc2") == 4); + REQUIRE( d.read("/region1/filter1/cutoff_cc3") == 100.0f); + REQUIRE( d.read("/region1/filter1/cutoff_stepcc3") == 1.0f); + REQUIRE( d.read("/region1/filter1/cutoff_smoothcc3") == 20); + REQUIRE( d.read("/region1/filter1/cutoff_curvecc3") == 3); + } + + SECTION("Filter types") + { + d.load(R"( + sample=kick.wav fil_type=lpf_1p + sample=kick.wav fil_type=hpf_1p + sample=kick.wav fil_type=lpf_2p + sample=kick.wav fil_type=hpf_2p + sample=kick.wav fil_type=bpf_2p + sample=kick.wav fil_type=brf_2p + sample=kick.wav fil_type=bpf_1p + sample=kick.wav fil_type=brf_1p + sample=kick.wav fil_type=apf_1p + sample=kick.wav fil_type=lpf_2p_sv + sample=kick.wav fil_type=hpf_2p_sv + sample=kick.wav fil_type=bpf_2p_sv + sample=kick.wav fil_type=brf_2p_sv + sample=kick.wav fil_type=lpf_4p + sample=kick.wav fil_type=hpf_4p + sample=kick.wav fil_type=lpf_6p + sample=kick.wav fil_type=hpf_6p + sample=kick.wav fil_type=pink + sample=kick.wav fil_type=lsh + sample=kick.wav fil_type=hsh + sample=kick.wav fil_type=peq + sample=kick.wav fil2_type=peq + sample=kick.wav fil2_type=something + )"); + + REQUIRE( d.read("/region0/filter0/type") == "lpf_1p" ); + REQUIRE( d.read("/region1/filter0/type") == "hpf_1p" ); + REQUIRE( d.read("/region2/filter0/type") == "lpf_2p" ); + REQUIRE( d.read("/region3/filter0/type") == "hpf_2p" ); + REQUIRE( d.read("/region4/filter0/type") == "bpf_2p" ); + REQUIRE( d.read("/region5/filter0/type") == "brf_2p" ); + REQUIRE( d.read("/region6/filter0/type") == "bpf_1p" ); + REQUIRE( d.read("/region7/filter0/type") == "brf_2p" ); // If we have a 1-pole brf at one point, change it back + REQUIRE( d.read("/region8/filter0/type") == "none" ); // If the apf 1-pole works, change it back + REQUIRE( d.read("/region9/filter0/type") == "lpf_2p_sv" ); + REQUIRE( d.read("/region10/filter0/type") == "hpf_2p_sv" ); + REQUIRE( d.read("/region11/filter0/type") == "bpf_2p_sv" ); + REQUIRE( d.read("/region12/filter0/type") == "brf_2p_sv" ); + REQUIRE( d.read("/region13/filter0/type") == "lpf_4p" ); + REQUIRE( d.read("/region14/filter0/type") == "hpf_4p" ); + REQUIRE( d.read("/region15/filter0/type") == "lpf_6p" ); + REQUIRE( d.read("/region16/filter0/type") == "hpf_6p" ); + REQUIRE( d.read("/region17/filter0/type") == "pink" ); + REQUIRE( d.read("/region18/filter0/type") == "lsh" ); + REQUIRE( d.read("/region19/filter0/type") == "hsh" ); + REQUIRE( d.read("/region20/filter0/type") == "peq" ); + REQUIRE( d.read("/region21/filter1/type") == "peq" ); + REQUIRE( d.read("/region22/filter1/type") == "none" ); + } + + SECTION("Filter dispatching") + { + d.load(R"( + 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 + )"); + + REQUIRE( d.read("/region0/filter2/cutoff") == 50.0f); + REQUIRE( d.read("/region0/filter1/resonance") == 3.0f); + REQUIRE( d.read("/region0/filter1/gain") == -5.0f); + REQUIRE( d.read("/region0/filter2/keytrack") == 100.0f); + REQUIRE( d.read("/region0/filter0/gain") == -5.0f); + REQUIRE( d.read("/region0/filter1/veltrack") == -100.0f); + REQUIRE( d.read("/region0/filter3/veltrack_cc7") == -100.0f); + REQUIRE( d.read("/region0/filter4/veltrack_curvecc2") == 2); + } + + SECTION("Filter value bounds") + { + d.load(R"( sample=kick.wav cutoff=100000 sample=kick.wav cutoff=50 cutoff=-100 )"); - synth.dispatchMessage(client, 0, "/region0/filter0/cutoff", "", nullptr); - synth.dispatchMessage(client, 0, "/region1/filter0/cutoff", "", nullptr); - std::vector expected { - "/region0/filter0/cutoff,f : { 100000 }", - "/region1/filter0/cutoff,f : { -100 }", - }; - REQUIRE(messageList == expected); - } - - SECTION("Cutoff") - { - synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + REQUIRE( d.read("/region0/filter0/cutoff") == 100000.0f); + REQUIRE( d.read("/region1/filter0/cutoff") == -100.0f); + + d.load(R"( sample=kick.wav resonance=5 resonance=-5 )"); - synth.dispatchMessage(client, 0, "/region0/filter0/resonance", "", nullptr); - std::vector expected { - "/region0/filter0/resonance,f : { -5 }", - }; - REQUIRE(messageList == expected); - } - - SECTION("Keycenter") - { - synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + REQUIRE( d.read("/region0/filter0/resonance") == -5.0f); + + d.load(R"( sample=kick.wav fil_keycenter=40 sample=kick.wav fil_keycenter=40 fil_keycenter=1000 sample=kick.wav fil_keycenter=c3 )"); - synth.dispatchMessage(client, 0, "/region0/filter0/keycenter", "", nullptr); - synth.dispatchMessage(client, 0, "/region1/filter0/keycenter", "", nullptr); - synth.dispatchMessage(client, 0, "/region2/filter0/keycenter", "", nullptr); - std::vector expected { - "/region0/filter0/keycenter,i : { 40 }", - "/region1/filter0/keycenter,i : { 60 }", - "/region2/filter0/keycenter,i : { 48 }", - }; - REQUIRE(messageList == expected); + REQUIRE( d.read("/region0/filter0/keycenter") == 40); + REQUIRE( d.read("/region1/filter0/keycenter") == 60); + REQUIRE( d.read("/region2/filter0/keycenter") == 48); } -} -TEST_CASE("[Values] EQ stacking and gains") -{ - 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 eq1_gain=3 - sample=kick.wav eq4_gain=6 - )"); - - SECTION("Test first region") - { - synth.dispatchMessage(client, 0, "/region0/eq0/gain", "", nullptr); - synth.dispatchMessage(client, 0, "/region0/eq0/type", "", nullptr); - synth.dispatchMessage(client, 0, "/region0/eq0/bandwidth", "", nullptr); - synth.dispatchMessage(client, 0, "/region0/eq0/frequency", "", nullptr); - synth.dispatchMessage(client, 0, "/region0/eq0/vel2gain", "", nullptr); - synth.dispatchMessage(client, 0, "/region0/eq0/vel2freq", "", nullptr); - synth.dispatchMessage(client, 0, "/region0/eq1/gain", "", nullptr); - synth.dispatchMessage(client, 0, "/region0/eq1/type", "", nullptr); - synth.dispatchMessage(client, 0, "/region0/eq1/bandwidth", "", nullptr); - synth.dispatchMessage(client, 0, "/region0/eq1/frequency", "", nullptr); - synth.dispatchMessage(client, 0, "/region0/eq1/vel2gain", "", nullptr); - synth.dispatchMessage(client, 0, "/region0/eq1/vel2freq", "", nullptr); - std::vector expected { - // No eqs - }; - REQUIRE(messageList == expected); - } - - SECTION("Test second region") - { - synth.dispatchMessage(client, 0, "/region1/eq0/gain", "", nullptr); - synth.dispatchMessage(client, 0, "/region1/eq0/type", "", nullptr); - synth.dispatchMessage(client, 0, "/region1/eq0/bandwidth", "", nullptr); - synth.dispatchMessage(client, 0, "/region1/eq0/frequency", "", nullptr); - synth.dispatchMessage(client, 0, "/region1/eq0/vel2gain", "", nullptr); - synth.dispatchMessage(client, 0, "/region1/eq0/vel2freq", "", nullptr); - synth.dispatchMessage(client, 0, "/region1/eq1/gain", "", nullptr); - synth.dispatchMessage(client, 0, "/region1/eq1/type", "", nullptr); - synth.dispatchMessage(client, 0, "/region1/eq1/bandwidth", "", nullptr); - synth.dispatchMessage(client, 0, "/region1/eq1/frequency", "", nullptr); - synth.dispatchMessage(client, 0, "/region1/eq1/vel2gain", "", nullptr); - synth.dispatchMessage(client, 0, "/region1/eq1/vel2freq", "", nullptr); - std::vector expected { - "/region1/eq0/gain,f : { 3 }", - "/region1/eq0/type,s : { peak }", - "/region1/eq0/bandwidth,f : { 1 }", - "/region1/eq0/frequency,f : { 50 }", - "/region1/eq0/vel2gain,f : { 0 }", - "/region1/eq0/vel2freq,f : { 0 }", - // No second eq - }; - REQUIRE(messageList == expected); - } - - SECTION("Test third region") - { - synth.dispatchMessage(client, 0, "/region2/eq0/gain", "", nullptr); - synth.dispatchMessage(client, 0, "/region2/eq0/type", "", nullptr); - synth.dispatchMessage(client, 0, "/region2/eq0/bandwidth", "", nullptr); - synth.dispatchMessage(client, 0, "/region2/eq0/frequency", "", nullptr); - synth.dispatchMessage(client, 0, "/region2/eq0/vel2gain", "", nullptr); - synth.dispatchMessage(client, 0, "/region2/eq0/vel2freq", "", nullptr); - synth.dispatchMessage(client, 0, "/region2/eq3/gain", "", nullptr); - synth.dispatchMessage(client, 0, "/region2/eq3/type", "", nullptr); - synth.dispatchMessage(client, 0, "/region2/eq3/bandwidth", "", nullptr); - synth.dispatchMessage(client, 0, "/region2/eq3/frequency", "", nullptr); - synth.dispatchMessage(client, 0, "/region2/eq3/vel2gain", "", nullptr); - synth.dispatchMessage(client, 0, "/region2/eq3/vel2freq", "", nullptr); - synth.dispatchMessage(client, 0, "/region2/eq1/frequency", "", nullptr); - synth.dispatchMessage(client, 0, "/region2/eq2/frequency", "", nullptr); - std::vector expected { - // The first eq is default-filled - "/region2/eq0/gain,f : { 0 }", - "/region2/eq0/type,s : { peak }", - "/region2/eq0/bandwidth,f : { 1 }", - "/region2/eq0/frequency,f : { 50 }", - "/region2/eq0/vel2gain,f : { 0 }", - "/region2/eq0/vel2freq,f : { 0 }", - "/region2/eq3/gain,f : { 6 }", - "/region2/eq3/type,s : { peak }", - "/region2/eq3/bandwidth,f : { 1 }", - "/region2/eq3/frequency,f : { 0 }", - "/region2/eq3/vel2gain,f : { 0 }", - "/region2/eq3/vel2freq,f : { 0 }", - "/region2/eq1/frequency,f : { 500 }", - "/region2/eq2/frequency,f : { 5000 }", - }; - REQUIRE(messageList == expected); + SECTION("EQ stacking and gains") + { + d.load(R"( + sample=kick.wav + sample=kick.wav eq1_gain=3 + sample=kick.wav eq4_gain=6 + )"); + + REQUIRE( !d.replied("/region0/eq0/gain") ); + REQUIRE( !d.replied("/region0/eq0/type") ); + REQUIRE( !d.replied("/region0/eq0/bandwidth") ); + REQUIRE( !d.replied("/region0/eq0/frequency") ); + REQUIRE( !d.replied("/region0/eq0/vel2gain") ); + REQUIRE( !d.replied("/region0/eq0/vel2freq") ); + REQUIRE( !d.replied("/region0/eq1/gain") ); + REQUIRE( !d.replied("/region0/eq1/type") ); + REQUIRE( !d.replied("/region0/eq1/bandwidth") ); + REQUIRE( !d.replied("/region0/eq1/frequency") ); + REQUIRE( !d.replied("/region0/eq1/vel2gain") ); + REQUIRE( !d.replied("/region0/eq1/vel2freq") ); + + REQUIRE( d.read("/region1/eq0/gain") == 3.0f); + REQUIRE( d.read("/region1/eq0/type") == "peak"); + REQUIRE( d.read("/region1/eq0/bandwidth") == 1.0f); + REQUIRE( d.read("/region1/eq0/frequency") == 50.0f); + REQUIRE( d.read("/region1/eq0/vel2gain") == 0.0f); + REQUIRE( d.read("/region1/eq0/vel2freq") == 0.0f); + REQUIRE( !d.replied("/region1/eq1/gain") ); + REQUIRE( !d.replied("/region1/eq1/type") ); + REQUIRE( !d.replied("/region1/eq1/bandwidth") ); + REQUIRE( !d.replied("/region1/eq1/frequency") ); + REQUIRE( !d.replied("/region1/eq1/vel2gain") ); + REQUIRE( !d.replied("/region1/eq1/vel2freq") ); + + // The first eq is default-filled + REQUIRE( d.read("/region2/eq0/gain") == 0.0f); + REQUIRE( d.read("/region2/eq0/type") == "peak"); + REQUIRE( d.read("/region2/eq0/bandwidth") == 1.0f); + REQUIRE( d.read("/region2/eq0/frequency") == 50.0f); + REQUIRE( d.read("/region2/eq0/vel2gain") == 0.0f); + REQUIRE( d.read("/region2/eq0/vel2freq") == 0.0f); + REQUIRE( d.read("/region2/eq3/gain") == 6.0f); + REQUIRE( d.read("/region2/eq3/type") == "peak"); + REQUIRE( d.read("/region2/eq3/bandwidth") == 1.0f); + REQUIRE( d.read("/region2/eq3/frequency") == 0.0f); + REQUIRE( d.read("/region2/eq3/vel2gain") == 0.0f); + REQUIRE( d.read("/region2/eq3/vel2freq") == 0.0f); + REQUIRE( d.read("/region2/eq1/frequency") == 500.0f); + REQUIRE( d.read("/region2/eq2/frequency") == 5000.0f); } -} -TEST_CASE("[Values] EQ types") -{ - 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 eq1_type=hshelf - sample=kick.wav eq1_type=lshelf - sample=kick.wav eq1_type=hshelf eq1_type=peak - sample=kick.wav eq1_type=something - )"); - - synth.dispatchMessage(client, 0, "/region0/eq0/type", "", nullptr); - synth.dispatchMessage(client, 0, "/region1/eq0/type", "", nullptr); - synth.dispatchMessage(client, 0, "/region2/eq0/type", "", nullptr); - synth.dispatchMessage(client, 0, "/region3/eq0/type", "", nullptr); - std::vector expected { - "/region0/eq0/type,s : { hshelf }", - "/region1/eq0/type,s : { lshelf }", - "/region2/eq0/type,s : { peak }", - "/region3/eq0/type,s : { none }", - }; - REQUIRE(messageList == expected); -} + SECTION("EQ types") + { + d.load(R"( + sample=kick.wav eq1_type=hshelf + sample=kick.wav eq1_type=lshelf + sample=kick.wav eq1_type=hshelf eq1_type=peak + sample=kick.wav eq1_type=something + )"); -TEST_CASE("[Values] EQ dispatching") -{ - 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 - eq3_bw=2 eq1_gain=-25 eq2_freq=300 eq3_type=lshelf - eq3_vel2gain=10 eq1_vel2freq=100 - )"); - - synth.dispatchMessage(client, 0, "/region0/eq2/bandwidth", "", nullptr); - synth.dispatchMessage(client, 0, "/region0/eq0/gain", "", nullptr); - synth.dispatchMessage(client, 0, "/region0/eq1/frequency", "", nullptr); - synth.dispatchMessage(client, 0, "/region0/eq2/type", "", nullptr); - synth.dispatchMessage(client, 0, "/region0/eq2/vel2gain", "", nullptr); - synth.dispatchMessage(client, 0, "/region0/eq0/vel2freq", "", nullptr); - std::vector expected { - "/region0/eq2/bandwidth,f : { 2 }", - "/region0/eq0/gain,f : { -25 }", - "/region0/eq1/frequency,f : { 300 }", - "/region0/eq2/type,s : { lshelf }", - "/region0/eq2/vel2gain,f : { 10 }", - "/region0/eq0/vel2freq,f : { 100 }", - }; - REQUIRE(messageList == expected); -} + REQUIRE( d.read("/region0/eq0/type") == "hshelf"); + REQUIRE( d.read("/region1/eq0/type") == "lshelf"); + REQUIRE( d.read("/region2/eq0/type") == "peak"); + REQUIRE( d.read("/region3/eq0/type") == "none"); + } -TEST_CASE("[Values] EQ value bounds") -{ - Synth synth; - std::vector messageList; - Client client(&messageList); - client.setReceiveCallback(&simpleMessageReceiver); + SECTION("EQ dispatching") + { + d.load(R"( + sample=kick.wav + eq3_bw=2 eq1_gain=-25 eq2_freq=300 eq3_type=lshelf + eq3_vel2gain=10 eq1_vel2freq=100 + )"); - SECTION("Frequency") + REQUIRE( d.read("/region0/eq2/bandwidth") == 2.0f); + REQUIRE( d.read("/region0/eq0/gain") == -25.0f); + REQUIRE( d.read("/region0/eq1/frequency") == 300.0f); + REQUIRE( d.read("/region0/eq2/type") == "lshelf"); + REQUIRE( d.read("/region0/eq2/vel2gain") == 10.0f); + REQUIRE( d.read("/region0/eq0/vel2freq") == 100.0f); + } + + SECTION("EQ value bounds") { - synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( + d.load(R"( sample=kick.wav eq1_freq=100000 sample=kick.wav eq1_freq=50 eq1_freq=-100 )"); - synth.dispatchMessage(client, 0, "/region0/eq0/frequency", "", nullptr); - synth.dispatchMessage(client, 0, "/region1/eq0/frequency", "", nullptr); - std::vector expected { - "/region0/eq0/frequency,f : { 100000 }", - "/region1/eq0/frequency,f : { -100 }", - }; - REQUIRE(messageList == expected); + REQUIRE( d.read("/region0/eq0/frequency") == 100000.0f); + REQUIRE( d.read("/region1/eq0/frequency") == -100.0f); + + d.load(R"( + sample=kick.wav eq1_bw=5 eq1_bw=-5 + )"); + REQUIRE( d.read("/region0/eq0/bandwidth") == -5.0f); } - SECTION("Bandwidth") + SECTION("Flex EGs") { - synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"( - sample=kick.wav eq1_bw=5 eq1_bw=-5 + d.load(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/eq0/bandwidth", "", nullptr); - std::vector expected { - "/region0/eq0/bandwidth,f : { -5 }", - }; - REQUIRE(messageList == expected); + REQUIRE( d.read("/region0/eg0/point0/time") == 0.1f); + REQUIRE( d.read("/region0/eg0/point0/level") == 0.5f); + REQUIRE( d.read("/region0/eg0/point1/time") == 0.4f); + REQUIRE( d.read("/region0/eg0/point1/level") == 1.0f); // Level values in EGs are clamped in Sforzando + REQUIRE( d.read("/region0/eg1/point0/time") == 4.0f); + REQUIRE( d.read("/region0/eg1/point0/level") == 0.1f); } -} -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 : { 1 }", // Level values in EGs are clamped in Sforzando - "/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); -} + SECTION("Flex EGs CC") + { + d.load(R"( + sample=kick.wav eg1_time1_cc2=0.1 eg1_level1_oncc3=0.5 + )"); + REQUIRE( d.read("/region0/eg0/point0/time_cc2") == 0.1f); + REQUIRE( d.read("/region0/eg0/point0/time_cc4") == 0.0f); + REQUIRE( d.read("/region0/eg0/point0/level_cc3") == 0.5f); + REQUIRE( d.read("/region0/eg0/point0/level_cc12") == 0.0f); + } -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,N : { }", - "/region0/fileg_dynamic,N : { }", - "/region1/ampeg_dynamic,T : { }", - "/region1/pitcheg_dynamic,T : { }", - "/region1/fileg_dynamic,T : { }", - }; - REQUIRE(messageList == expected); + SECTION("Dynamic EGs") + { + d.load(R"( + sample=kick.wav + sample=kick.wav ampeg_dynamic=1 pitcheg_dynamic=1 fileg_dynamic=1 + )"); + REQUIRE( d.read("/region0/ampeg_dynamic") == OSC::False); + REQUIRE( d.read("/region0/pitcheg_dynamic") == OSC::None); + REQUIRE( d.read("/region0/fileg_dynamic") == OSC::None); + REQUIRE( d.read("/region1/ampeg_dynamic") == OSC::True); + REQUIRE( d.read("/region1/pitcheg_dynamic") == OSC::True); + REQUIRE( d.read("/region1/fileg_dynamic") == OSC::True); + } } diff --git a/tests/SynthDiscussion.h b/tests/SynthDiscussion.h new file mode 100644 index 000000000..0381a0089 --- /dev/null +++ b/tests/SynthDiscussion.h @@ -0,0 +1,187 @@ +// 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 +#include +#include "absl/strings/string_view.h" +#include "sfizz/Synth.h" +#include "sfizz_message.h" +#include "catch2/catch.hpp" +#include "utility/Size.h" + +using namespace sfz; +constexpr int maxArgs = 8; + +enum class OSCValueLess { True, False, None }; +using OSCVariant = absl::variant; + +// template +// struct vecToTuple +// { +// template +// static void rewrap(std::tuple &tup, const std::vector& variants) +// { +// std::get(tup) = std::get(tup))>(variants[I-1]); +// rewrap(tup, variants); +// } +// template <> +// static void rewrap<0>(std::tuple &, const std::vector&) { } +// }; + +// template +struct SynthDiscussion +{ + SynthDiscussion() + { + client.setReceiveCallback(&SynthDiscussion::receiver); + } + + static void receiver(void* data, int, const char* path, const char* sig, const sfizz_arg_t* args) + { + SynthDiscussion* self = (SynthDiscussion*)data; + self->recv_path = path; + self->recv_sig = sig; + self->recv_args.clear(); + for (int i = 0, n = self->recv_sig.size(); i < n; ++i) { + switch (self->recv_sig[i]) { + case 'i': self->recv_args.emplace_back(args[i].i); break; + case 'f': self->recv_args.emplace_back(args[i].f); break; + case 'd': self->recv_args.emplace_back(args[i].d); break; + case 'h': self->recv_args.emplace_back(args[i].h); break; + case 's': self->recv_args.emplace_back(args[i].s); break; + case 'T': self->recv_args.emplace_back(OSCValueLess::True); break; + case 'F': self->recv_args.emplace_back(OSCValueLess::False); break; + case 'N': self->recv_args.emplace_back(OSCValueLess::None); break; + default: + ASSERTFALSE; + } + } + } + + void load(absl::string_view sfz) + { + synth.loadSfzString(fs::current_path() / "tests/TestFiles/discussion.sfz", sfz); + } + + void send(absl::string_view path, int32_t value) + { + sent_args[0].i = value; + synth.dispatchMessage(client, 0, path.data(), "i", sent_args); + } + + void send(absl::string_view path, int64_t value) + { + sent_args[0].h = value; + synth.dispatchMessage(client, 0, path.data(), "h", sent_args); + } + + void send(absl::string_view path, float value) + { + sent_args[0].f = value; + synth.dispatchMessage(client, 0, path.data(), "f", sent_args); + } + + void send(absl::string_view path, const std::string& value) + { + sent_args[0].s = value.c_str(); + synth.dispatchMessage(client, 0, path.data(), "s", sent_args); + } + + void send(absl::string_view path, std::nullptr_t) + { + synth.dispatchMessage(client, 0, path.data(), "N", nullptr); + } + + void send(absl::string_view path, bool value) + { + synth.dispatchMessage(client, 0, path.data(), value ? "T" : "F", nullptr); + } + + template T read(absl::string_view path) + { + synth.dispatchMessage(client, 0, path.data(), "", nullptr); + return absl::get(recv_args[0]); + } + + bool replied(absl::string_view path) + { + recv_path = ""; + synth.dispatchMessage(client, 0, path.data(), "", nullptr); + return !recv_path.empty(); + } + + template std::vector readAll(absl::string_view path) + { + synth.dispatchMessage(client, 0, path.data(), "", nullptr); + std::vector returned; + for (const auto& arg: recv_args) + returned.push_back(absl::get(arg)); + + return returned; + } + + void sendAll(absl::string_view path, const std::vector& value) + { + std::string signature(value.size(), 'f'); + for (int i = 0; i < ssize(value) && i < maxArgs; ++i) + sent_args[i].f = value[i]; + synth.dispatchMessage(client, 0, path.data(), signature.c_str(), sent_args); + } + + void sendAll(absl::string_view path, const std::vector& value) + { + std::string signature(value.size(), 'i'); + for (int i = 0; i < ssize(value) && i < maxArgs; ++i) + sent_args[i].i = value[i]; + synth.dispatchMessage(client, 0, path.data(), signature.c_str(), sent_args); + } + + void sendAll(absl::string_view path, const std::vector& value) + { + std::string signature(value.size(), 'h'); + for (int i = 0; i < ssize(value) && i < maxArgs; ++i) + sent_args[i].h = value[i]; + synth.dispatchMessage(client, 0, path.data(), signature.c_str(), sent_args); + } + + template + T sendAndRead(absl::string_view path, T value) + { + send(path, value); + return read(path); + } + + template + std::vector sendAndReadAll(absl::string_view path, const std::vector& value) + { + sendAll(path, value); + return readAll(path); + } + + std::string formatLast() const + { + std::string newMessage = absl::StrCat(recv_path, ",", recv_sig, " : { "); + for (unsigned i = 0, n = recv_sig.size(); i < n; ++i) { + absl::visit([&](auto&& arg){ absl::StrAppend(&newMessage, arg); }, recv_args[i]); + if (i == (n - 1)) + absl::StrAppend(&newMessage, " }"); + else + absl::StrAppend(&newMessage, ", "); + } + + return newMessage; + } + + Synth synth; + sfizz_arg_t sent_args[maxArgs]; + std::vector recv_args; + std::string recv_path; + std::string recv_sig; + AudioBuffer buffer { 2, 256 }; + Client client { this }; +}; From f8fc628b3e6e2714add00379333d7c1002c3182c Mon Sep 17 00:00:00 2001 From: KIRA Ryouta Date: Sat, 3 Feb 2024 18:30:31 +0100 Subject: [PATCH 40/65] Improved note-on performance - Compute the hash value on ModKey construction and memorize it - Fix missing region ID in the hash value of ModKey --- src/sfizz/modulations/ModKey.cpp | 2 +- src/sfizz/modulations/ModKey.h | 14 +++++++++++-- src/sfizz/modulations/ModKeyHash.cpp | 30 ++++++++++++++++++++++----- src/sfizz/utility/StringViewHelpers.h | 7 ++++++- 4 files changed, 44 insertions(+), 9 deletions(-) diff --git a/src/sfizz/modulations/ModKey.cpp b/src/sfizz/modulations/ModKey.cpp index 61b55859f..af38defda 100644 --- a/src/sfizz/modulations/ModKey.cpp +++ b/src/sfizz/modulations/ModKey.cpp @@ -228,7 +228,7 @@ std::string ModKey::toString() const } } -ModKey ModKey::getSourceDepthKey(ModKey source, ModKey target) +ModKey ModKey::getSourceDepthKey(const ModKey& source, const ModKey& target) { const NumericId region = source.region(); const ModKey::Parameters& tp = target.parameters(); diff --git a/src/sfizz/modulations/ModKey.h b/src/sfizz/modulations/ModKey.h index 92f11ca43..b3047eba7 100644 --- a/src/sfizz/modulations/ModKey.h +++ b/src/sfizz/modulations/ModKey.h @@ -26,7 +26,10 @@ class ModKey { ModKey() = default; explicit ModKey(ModId id, NumericId region = {}, Parameters params = {}) - : id_(id), region_(region), params_(params), flags_(ModIds::flags(id_)) {} + : id_(id), region_(region), params_(params), flags_(ModIds::flags(id_)) + { + calculateHash(); + } static ModKey createCC(uint16_t cc, uint8_t curve, uint16_t smooth, float step); static ModKey createNXYZ(ModId id, NumericId region = {}, uint8_t N = 0, uint8_t X = 0, uint8_t Y = 0, uint8_t Z = 0); @@ -46,7 +49,7 @@ class ModKey { * @brief Obtain the modulation key of the source depth, in the connection * between source and target, if such a key exists. */ - static ModKey getSourceDepthKey(ModKey source, ModKey target); + static ModKey getSourceDepthKey(const ModKey& source, const ModKey& target); struct RawParameters { union { @@ -94,6 +97,10 @@ class ModKey { return !this->operator==(other); } +public: + size_t hash() const { return hash_; }; +private: + void calculateHash(); private: //! Identifier @@ -104,6 +111,9 @@ class ModKey { Parameters params_ {}; // Memorize the flag int flags_; + // Hash number + size_t hash_ = size_t(16806506973555142816ULL); }; + } // namespace sfz diff --git a/src/sfizz/modulations/ModKeyHash.cpp b/src/sfizz/modulations/ModKeyHash.cpp index a8b8ac6f0..f51d36d59 100644 --- a/src/sfizz/modulations/ModKeyHash.cpp +++ b/src/sfizz/modulations/ModKeyHash.cpp @@ -10,12 +10,27 @@ #include "utility/StringViewHelpers.h" #include -size_t std::hash::operator()(const sfz::ModKey &key) const +void sfz::ModKey::calculateHash() { - uint64_t k = hashNumber(static_cast(key.id())); - const sfz::ModKey::Parameters& p = key.parameters(); +#if !defined(NDEBUG) + static bool once = false; + if (!once) { + once = true; + ModKey m; + size_t h = m.hash(); + m.calculateHash(); + if (h != m.hash()) { + printf("ModKey default hash is %llu\n", (uint64_t)m.hash()); + assert(false && "Number of variables is wrong. Needs updating the default hash."); + } + } +#endif - switch (key.id()) { + uint64_t k = hashNumber(static_cast(id())); + k = hashNumber(region_.number(), k); + const sfz::ModKey::Parameters& p = parameters(); + + switch (id()) { case sfz::ModId::Controller: k = hashNumber(p.cc, k); k = hashNumber(p.curve, k); @@ -29,5 +44,10 @@ size_t std::hash::operator()(const sfz::ModKey &key) const k = hashNumber(p.Z, k); break; } - return k; + hash_ = size_t(k); +} + +size_t std::hash::operator()(const sfz::ModKey &key) const +{ + return key.hash(); } diff --git a/src/sfizz/utility/StringViewHelpers.h b/src/sfizz/utility/StringViewHelpers.h index 278833c18..9fcf355da 100644 --- a/src/sfizz/utility/StringViewHelpers.h +++ b/src/sfizz/utility/StringViewHelpers.h @@ -101,7 +101,12 @@ uint64_t hashNumber(Int i, uint64_t h = Fnv1aBasis) { static_assert(std::is_arithmetic::value, "The hashed object must be of arithmetic type"); - return hash(absl::string_view(reinterpret_cast(&i), sizeof(i)), h); + union { + uint64_t u64; + Int i; + } un {}; + un.i = i; + return (h ^ un.u64) * Fnv1aPrime; } /** From 753087fe2ea7375e650de864668d733d775298fb Mon Sep 17 00:00:00 2001 From: KIRA Ryouta Date: Fri, 9 Feb 2024 04:31:29 +0900 Subject: [PATCH 41/65] Added invoke.hpp to git submodule check. --- CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 6b45c307b..f31f64c24 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -58,6 +58,7 @@ if(SFIZZ_GIT_SUBMODULE_CHECK AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/.git" git_submodule_check(external/st_audiofile/thirdparty/dr_libs) git_submodule_check(external/st_audiofile/thirdparty/libaiff) git_submodule_check(external/st_audiofile/thirdparty/wavpack) + git_submodule_check(external/invoke.hpp) endif() include(SfizzConfig) From 0ca5a6c8cf241216e16f5a7f144fe7dbc726852f Mon Sep 17 00:00:00 2001 From: redtide Date: Thu, 8 Feb 2024 17:04:36 +0100 Subject: [PATCH 42/65] CI: added build timeout 10 minutes as default, 15 for macOS and Windows, 2 for source code package --- .github/workflows/build.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 8f5c0ebed..ca20d7acb 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -24,6 +24,7 @@ jobs: clang_tidy: name: Clang Tidy runs-on: ubuntu-20.04 + timeout-minutes: 10 steps: - name: Checkout uses: actions/checkout@v3 @@ -37,6 +38,7 @@ jobs: test_with_asan: name: ASAN runs-on: ubuntu-22.04 # abseil (libabsl) is not available in 20.04 repository + timeout-minutes: 10 env: install_name: sfizz-${{ github.ref_name }}-linux strategy: @@ -88,6 +90,7 @@ jobs: build_for_linux: name: Linux Ubuntu 22.04 runs-on: ubuntu-22.04 # abseil (libabsl) is not available in 20.04 repository + timeout-minutes: 10 env: install_name: sfizz-${{ github.ref_name }}-linux steps: @@ -171,6 +174,7 @@ jobs: build_for_macos: name: macOS 11 runs-on: macos-11 + timeout-minutes: 15 env: install_name: "sfizz-${{ github.ref_name }}-macos" steps: @@ -243,6 +247,7 @@ jobs: build_for_windows: name: Windows 2019 runs-on: windows-2019 + timeout-minutes: 15 strategy: matrix: include: @@ -306,6 +311,7 @@ jobs: if: ${{ github.ref_type == 'tag' }} name: Source code archive runs-on: ubuntu-20.04 + timeout-minutes: 2 env: install_name: sfizz-${{ github.ref_name }} steps: @@ -331,6 +337,7 @@ jobs: build_with_libsndfile: name: Linux libsndfile runs-on: ubuntu-20.04 + timeout-minutes: 10 steps: - name: Checkout uses: actions/checkout@v3 @@ -378,6 +385,7 @@ jobs: build_with_makefile: name: Linux makefile runs-on: ubuntu-20.04 + timeout-minutes: 10 steps: - name: Checkout uses: actions/checkout@v3 From f7beeffd2ad8e72044d624a416315010cd3109e2 Mon Sep 17 00:00:00 2001 From: Paul Fd Date: Sat, 10 Feb 2024 13:16:12 +0100 Subject: [PATCH 43/65] Tweak SynthMessaging --- src/sfizz/SynthMessagingHelper.hpp | 20 ++++++-------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/src/sfizz/SynthMessagingHelper.hpp b/src/sfizz/SynthMessagingHelper.hpp index 81916504c..32f1574eb 100644 --- a/src/sfizz/SynthMessagingHelper.hpp +++ b/src/sfizz/SynthMessagingHelper.hpp @@ -240,24 +240,16 @@ class MessagingHelper { template ::value>> void reply2(T value1, T value2) { - sfizz_arg_t response_args[2]; if (sizeof(value1) <= 4) { - response_args[0].i = value1; - response_args[1].i = value2; - client.receive(delay, path, "ii", response_args); + client.receive<'i', 'i'>(delay, path, value1, value2); } else { - response_args[0].h = value1; - response_args[1].h = value2; - client.receive(delay, path, "hh", response_args); + client.receive<'h', 'h'>(delay, path, value1, value2); } } void reply2(float value1, float value2) { - sfizz_arg_t response_args[2]; - response_args[0].f = value1; - response_args[1].f = value2; - client.receive(delay, path, "ff", response_args); + client.receive<'f', 'f'>(delay, path, value1, value2); } void set(std::string& target) { target = request_args[0].s; } @@ -267,11 +259,11 @@ class MessagingHelper { void set(bool& target, const OpcodeSpec& spec) { if (sig[0] == 'T') { - target = true; + target = true; } else if (sig[0] == 'F') { - target = false; + target = false; } else { - target = Opcode::read(spec, request_args[0].s); + target = Opcode::read(spec, request_args[0].s); } } From 7f501666b17cb54ac9380ccc4edcd13adda2f028 Mon Sep 17 00:00:00 2001 From: Paul Fd Date: Sat, 10 Feb 2024 13:58:56 +0100 Subject: [PATCH 44/65] Fix a used-after-free bug We took a reference to a target member, while possibly moving the vector of all targets. --- src/sfizz/modulations/ModMatrix.cpp | 11 +++++++++-- src/sfizz/modulations/ModMatrix.h | 4 ++++ 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/src/sfizz/modulations/ModMatrix.cpp b/src/sfizz/modulations/ModMatrix.cpp index 54e7a0131..31707b5cb 100644 --- a/src/sfizz/modulations/ModMatrix.cpp +++ b/src/sfizz/modulations/ModMatrix.cpp @@ -205,13 +205,20 @@ bool ModMatrix::connect(SourceId sourceId, TargetId targetId, float sourceDepth, if (sourceIndex >= impl.sources_.size() || targetIndex >= impl.targets_.size()) return false; + TargetId sourceDepthModId; + // We need to register the target _before_ taking a reference to target.connectedSources + // because if the targets_ vector is reallocated, all targets' connected sources will be moved + // and conn will point to freed memory. + if (sourceDepthMod) + sourceDepthModId = registerTarget(sourceDepthMod); + Impl::Target& target = impl.targets_[targetIndex]; Impl::ConnectionData& conn = target.connectedSources[sourceIndex]; conn.sourceDepth_ = sourceDepth; conn.sourceDepthMod_ = sourceDepthMod; - if (sourceDepthMod) - conn.sourceDepthModId_ = registerTarget(sourceDepthMod); + if (sourceDepthModId.valid()) + conn.sourceDepthModId_ = sourceDepthModId; conn.velToDepth_ = velToDepth; diff --git a/src/sfizz/modulations/ModMatrix.h b/src/sfizz/modulations/ModMatrix.h index 2aa59fe4a..2586d2571 100644 --- a/src/sfizz/modulations/ModMatrix.h +++ b/src/sfizz/modulations/ModMatrix.h @@ -57,6 +57,8 @@ class ModMatrix { * @brief Register a modulation source inside the matrix. * If it is already present, it just returns the existing id. * + * @note Might move the sources_ vector. + * * @param key source key * @param gen generator * @param flags source flags @@ -66,6 +68,8 @@ class ModMatrix { /** * @brief Register a modulation target inside the matrix. * + * @note Might move the targets_ vector. + * * @param key target key * @param region target region * @param flags target flags From 9d5f7bf4aa69bf68c2f535b83cb4a66740253fbb Mon Sep 17 00:00:00 2001 From: Paul Fd Date: Sat, 10 Feb 2024 14:04:13 +0100 Subject: [PATCH 45/65] Update abseil to the latest LTS --- external/abseil-cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/external/abseil-cpp b/external/abseil-cpp index ad73c6dc1..4a2c63365 160000 --- a/external/abseil-cpp +++ b/external/abseil-cpp @@ -1 +1 @@ -Subproject commit ad73c6dc1a253203c1c8b529cda18f2138d49df0 +Subproject commit 4a2c63365eff8823a5221db86ef490e828306f9d From cc33ed160f49ba49ada5d74c9137a634c15fbb2d Mon Sep 17 00:00:00 2001 From: KIRA Ryouta Date: Tue, 13 Feb 2024 10:36:47 +0900 Subject: [PATCH 46/65] Fixed a bug that causes invalid effect output --- src/sfizz/Synth.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sfizz/Synth.cpp b/src/sfizz/Synth.cpp index 4ad783dca..a06777a7f 100644 --- a/src/sfizz/Synth.cpp +++ b/src/sfizz/Synth.cpp @@ -1159,7 +1159,6 @@ void Synth::renderBlock(AudioSpan buffer) noexcept { // Main render block ScopedTiming logger { callbackBreakdown.renderMethod, ScopedTiming::Operation::addToDuration }; - tempMixSpan->fill(0.0f); for (auto& voice : impl.voiceManager_) { if (voice.isFree()) @@ -1197,6 +1196,7 @@ void Synth::renderBlock(AudioSpan buffer) noexcept const int numChannels = static_cast(buffer.getNumChannels()); for (int i = 0; i < impl.numOutputs_; ++i) { + tempMixSpan->fill(0.0f); const auto outputStart = numChannels == 0 ? 0 : (2 * i) % numChannels; auto outputSpan = buffer.getStereoSpan(outputStart); const auto& effectBuses = impl.getEffectBusesForOutput(i); From 698e27f6a0764eb88177c4bd2f56579688096593 Mon Sep 17 00:00:00 2001 From: KIRA Ryouta Date: Tue, 24 Oct 2023 09:59:49 +0900 Subject: [PATCH 47/65] C++20 Support --- clients/sfizz_render.cpp | 3 +- external/threadpool/ThreadPool.h | 12 ++++++++ src/sfizz/Opcode.cpp | 13 +++++++-- src/sfizz/Opcode.h | 10 +++++++ src/sfizz/Synth.cpp | 3 +- .../import/foreign_instruments/AudioFile.cpp | 6 ++-- .../foreign_instruments/DecentSampler.cpp | 3 +- src/sfizz/utility/Size.h | 2 ++ src/sfizz/utility/U8Strings.h | 28 +++++++++++++++++++ 9 files changed, 71 insertions(+), 9 deletions(-) create mode 100644 src/sfizz/utility/U8Strings.h diff --git a/clients/sfizz_render.cpp b/clients/sfizz_render.cpp index 1affaf633..1991a7c89 100644 --- a/clients/sfizz_render.cpp +++ b/clients/sfizz_render.cpp @@ -3,6 +3,7 @@ #include "sfizz/MathHelpers.h" #include "sfizz/SfzHelpers.h" #include "sfizz/SIMDHelpers.h" +#include "sfizz/utility/U8Strings.h" #include "MidiHelpers.h" #include #include @@ -165,7 +166,7 @@ int main(int argc, char** argv) ERROR_IF(!synth.loadSfzFile(sfzPath), "There was an error loading the SFZ file."); LOG_INFO(synth.getNumRegions() << " regions in the SFZ."); - fmidi_smf_u midiFile { fmidi_smf_file_read(midiPath.u8string().c_str()) }; + fmidi_smf_u midiFile { fmidi_smf_file_read(u8EncodedString(midiPath).c_str()) }; ERROR_IF(!midiFile, "Can't read " << midiPath); const auto* midiInfo = fmidi_smf_get_info(midiFile.get()); diff --git a/external/threadpool/ThreadPool.h b/external/threadpool/ThreadPool.h index 2e030687c..19b519b38 100644 --- a/external/threadpool/ThreadPool.h +++ b/external/threadpool/ThreadPool.h @@ -18,7 +18,11 @@ class ThreadPool { ThreadPool(size_t); template auto enqueue(F&& f, Args&&... args) +#if __cplusplus >= 201703L + -> std::future::type>; +#else -> std::future::type>; +#endif ~ThreadPool(); private: // need to keep track of threads so we can join them @@ -63,9 +67,17 @@ inline ThreadPool::ThreadPool(size_t threads) // add new work item to the pool template auto ThreadPool::enqueue(F&& f, Args&&... args) +#if __cplusplus >= 201703L + -> std::future::type> +#else -> std::future::type> +#endif { +#if __cplusplus >= 201703L + using return_type = typename std::invoke_result::type; +#else using return_type = typename std::result_of::type; +#endif auto task = std::make_shared< std::packaged_task >( std::bind(std::forward(f), std::forward(args)...) diff --git a/src/sfizz/Opcode.cpp b/src/sfizz/Opcode.cpp index 5468417e7..b983cba8b 100644 --- a/src/sfizz/Opcode.cpp +++ b/src/sfizz/Opcode.cpp @@ -6,6 +6,7 @@ #include "Opcode.h" #include "LFODescription.h" +#include "absl/strings/string_view.h" #include "utility/StringViewHelpers.h" #include "utility/Debug.h" #include @@ -271,11 +272,10 @@ absl::optional readNoteValue(absl::string_view value) /// std::pair flatSharpPrefixes[] = { { "#", +1 }, - { u8"♯", +1 }, + { (const char*)u8"♯", +1 }, { "b", -1 }, - { u8"â™­", -1 }, + { (const char*)u8"â™­", -1 }, }; - for (const auto& prefix : flatSharpPrefixes) { if (absl::StartsWith(value, prefix.first)) { if (prefix.second == +1) { @@ -304,6 +304,13 @@ absl::optional readNoteValue(absl::string_view value) return static_cast(noteNumber); } +#if defined(__cpp_lib_char8_t) +absl::optional readNoteValue(std::u8string_view value) +{ + return readNoteValue(absl::string_view { reinterpret_cast(value.data()), value.size() }); +} +#endif + absl::optional readBoolean(absl::string_view value) { // Cakewalk-style booleans, case-insensitive diff --git a/src/sfizz/Opcode.h b/src/sfizz/Opcode.h index 16557c3f9..d2deea670 100644 --- a/src/sfizz/Opcode.h +++ b/src/sfizz/Opcode.h @@ -143,6 +143,16 @@ struct Opcode { */ absl::optional readNoteValue(absl::string_view value); +#if defined(__cpp_lib_char8_t) +/** + * @brief Convert a note in string to its equivalent midi note number + * + * @param value + * @return absl::optional + */ +absl::optional readNoteValue(std::u8string_view value); +#endif + /** * @brief Read a boolean value from the sfz file and cast it to the destination parameter. */ diff --git a/src/sfizz/Synth.cpp b/src/sfizz/Synth.cpp index a06777a7f..c5e2bb3a6 100644 --- a/src/sfizz/Synth.cpp +++ b/src/sfizz/Synth.cpp @@ -8,6 +8,7 @@ #include "Config.h" #include "utility/Debug.h" #include "utility/Macros.h" +#include "utility/U8Strings.h" #include "modulations/ModId.h" #include "modulations/ModKey.h" #include "modulations/ModMatrix.h" @@ -702,7 +703,7 @@ void Synth::Impl::finalizeSfzLoad() filePool.setRootDirectory(rootDirectory); // a string representation used for OSC purposes - rootPath_ = rootDirectory.u8string(); + rootPath_ = u8EncodedString(rootDirectory); size_t currentRegionIndex = 0; size_t currentRegionCount = layers_.size(); diff --git a/src/sfizz/import/foreign_instruments/AudioFile.cpp b/src/sfizz/import/foreign_instruments/AudioFile.cpp index 65457cb1f..d4c4d3045 100644 --- a/src/sfizz/import/foreign_instruments/AudioFile.cpp +++ b/src/sfizz/import/foreign_instruments/AudioFile.cpp @@ -5,6 +5,7 @@ // If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz #include "AudioFile.h" +#include "utility/U8Strings.h" #include #include #include @@ -31,8 +32,7 @@ const char* AudioFileInstrumentFormat::name() const noexcept bool AudioFileInstrumentFormat::matchesFilePath(const fs::path& path) const { - const std::string ext = path.extension().u8string(); - + const std::string ext = u8EncodedString(path.extension()); for (absl::string_view knownExt : kRecognizedAudioExtensions) { if (absl::EqualsIgnoreCase(ext, knownExt)) return true; @@ -51,7 +51,7 @@ std::string AudioFileInstrumentImporter::convertToSfz(const fs::path& path) cons { std::ostringstream os; os.imbue(std::locale::classic()); - os << "sample=" << path.filename().u8string(); + os << "sample=" << u8EncodedString(path.filename()); return os.str(); } diff --git a/src/sfizz/import/foreign_instruments/DecentSampler.cpp b/src/sfizz/import/foreign_instruments/DecentSampler.cpp index 149ba86d1..492ea5e56 100644 --- a/src/sfizz/import/foreign_instruments/DecentSampler.cpp +++ b/src/sfizz/import/foreign_instruments/DecentSampler.cpp @@ -6,6 +6,7 @@ #include "DecentSampler.h" #include "sfizz/Opcode.h" +#include "utility/U8Strings.h" #include #include #include @@ -29,7 +30,7 @@ const char* DecentSamplerInstrumentFormat::name() const noexcept bool DecentSamplerInstrumentFormat::matchesFilePath(const fs::path& path) const { - const std::string ext = path.extension().u8string(); + const std::string ext = u8EncodedString(path.extension()); return absl::EqualsIgnoreCase(ext, ".dspreset"); } diff --git a/src/sfizz/utility/Size.h b/src/sfizz/utility/Size.h index f7d5277ca..86b593015 100644 --- a/src/sfizz/utility/Size.h +++ b/src/sfizz/utility/Size.h @@ -8,6 +8,7 @@ #include +#if !defined(__cpp_lib_ssize) template constexpr auto ssize(const C& c) -> std::common_type_t + +inline std::string from_u8string(const std::string &s) { + return s; +} + +inline std::string from_u8string(std::string &&s) { + return std::move(s); +} + +#if defined(__cpp_lib_char8_t) +inline std::string from_u8string(const std::u8string &s) { + return std::string(s.begin(), s.end()); +} +#endif + +inline std::string u8EncodedString(const fs::path& path) { + return from_u8string(path.u8string()); +} From 9392c2d9b2c2d140b571de08dd9929a42f142466 Mon Sep 17 00:00:00 2001 From: redtide Date: Tue, 20 Feb 2024 13:17:12 +0100 Subject: [PATCH 48/65] CI: clang-tidy on PR --- .github/workflows/build.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ca20d7acb..6b72e05d6 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -23,6 +23,7 @@ env: jobs: clang_tidy: name: Clang Tidy + if: github.event_name == 'pull_request' runs-on: ubuntu-20.04 timeout-minutes: 10 steps: From 53917ec8f204fa073eec8612bace2598a867b5a3 Mon Sep 17 00:00:00 2001 From: redtide Date: Tue, 20 Feb 2024 13:47:00 +0100 Subject: [PATCH 49/65] CI: simplified if conditional expressions, added release-test branch release-test branch is intended to be used on a user repository, not upstream, to test builds before tagging. --- .github/workflows/build.yml | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 6b72e05d6..f43975d1f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -105,7 +105,7 @@ jobs: ninja-build \ libjack-jackd2-dev - name: Install abseil - if: ${{ github.ref_type == 'branch' }} + if: github.ref_type == 'branch' run: | sudo apt-get install \ libabsl-dev \ @@ -160,13 +160,13 @@ jobs: ) cmake "${options[@]}" - name: Create tarball - if: ${{ github.ref_type == 'tag' }} + if: github.ref_type == 'tag' || github.ref_name == 'release-test' run: | DESTDIR="$(pwd)/${{ env.install_name }}" \ cmake --build build --config ${{ env.build_type }} --target install tar czvf "${{ env.install_name }}".tar.gz "${{ env.install_name }}" - name: Upload - if: ${{ github.ref_type == 'tag' }} + if: github.ref_type == 'tag' || github.ref_name == 'release-test' uses: actions/upload-artifact@v3 with: name: Linux tarball @@ -184,7 +184,7 @@ jobs: with: submodules: recursive - name: Install dependencies - if: ${{ github.ref_type == 'branch' }} + if: github.ref_type == 'branch' run: brew install abseil - name: Configure CMake run: | @@ -233,13 +233,13 @@ jobs: ) cmake "${options[@]}" - name: Create tarball - if: ${{ github.ref_type == 'tag' }} + if: github.ref_type == 'tag' || github.ref_name == 'release-test' run: | DESTDIR="$(pwd)/${{ env.install_name }}" \ cmake --build build --config ${{ env.build_type }} --target install tar czvf "${{ env.install_name }}".tar.gz "${{ env.install_name }}" - name: Upload - if: ${{ github.ref_type == 'tag' }} + if: github.ref_type == 'tag' || github.ref_name == 'release-test' uses: actions/upload-artifact@v3 with: name: macOS tarball @@ -299,17 +299,17 @@ jobs: --parallel 2 ` --verbose ` - name: Create zip package - if: ${{ github.ref_type == 'tag' }} + if: github.ref_type == 'tag' || github.ref_name == 'release-test' run: 7z a "${{ env.install_name }}.zip" ".\build\library\*" - name: Upload - if: ${{ github.ref_type == 'tag' }} + if: github.ref_type == 'tag' || github.ref_name == 'release-test' uses: actions/upload-artifact@v3 with: name: ${{ matrix.pkg_platform }} zip package path: "${{ github.workspace }}/${{ env.install_name }}.zip" archive_source_code: - if: ${{ github.ref_type == 'tag' }} + if: github.ref_type == 'tag' || github.ref_name == 'release-test' name: Source code archive runs-on: ubuntu-20.04 timeout-minutes: 2 @@ -415,12 +415,12 @@ jobs: make -C "${{ github.workspace }}" -f simple.mk deploy: - if: ${{ github.ref_type == 'tag' }} + if: github.ref_type == 'tag' runs-on: ubuntu-20.04 needs: - build_for_linux - build_for_macos - # - build_for_mod + # build_for_mod - build_for_windows - archive_source_code steps: From 85a275223a250dbe783f315dcba0096236b94924 Mon Sep 17 00:00:00 2001 From: redtide Date: Fri, 23 Feb 2024 16:14:54 +0100 Subject: [PATCH 50/65] Updated Changelog --- CHANGELOG.md | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3ea9f8c9b..30aa5f5af 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,9 +6,31 @@ This project tries to adhere to [Semantic Versioning][2]. ## [Unreleased] +### Added + +- [invoke.hpp](https://github.com/BlackMATov/invoke.hpp/) dependency/Git submodule. +- CI: build timeouts. +- C++20 Support (#1235 by @KKQ-KKQ) + +### Changed + +- Refactor the messaging system + Trading off complexity when parsing/replying to the messages for complexity + in some templated and overloaded code blocks. +- Truncate MIDI values when denormalizing (by @essej). +- Improved note-on performance (#1232 by @KKQ-KKQ) +- Updated abseil-cpp to 20240116.0. +- Usual little CI improvements. + +### Fixed + +- Tweak SynthMessaging (#1241) +- Fix a ModMatrix bug (#1242) +- Fixed a bug that causes invalid effect output (#1243 by @KKQ-KKQ) + ## [1.2.3] - 2024-01-15 -### Added +### Added - Support for curvecc opcodes on v1 EGs (@PythonBlue) - Support for `lotimer/hitimer` (@essej) @@ -813,7 +835,8 @@ becoming a library to be used in other projects, plus a LV2 plugin. [1]: https://keepachangelog.com/en/1.0.0/ [2]: https://semver.org/spec/v2.0.0.html -[Unreleased]: https://github.com/sfztools/sfizz/compare/1.2.2...HEAD +[Unreleased]: https://github.com/sfztools/sfizz/compare/1.2.3...HEAD +[1.2.3]: https://github.com/sfztools/sfizz/compare/1.2.2...1.2.3 [1.2.2]: https://github.com/sfztools/sfizz/compare/1.2.1...1.2.2 [1.2.1]: https://github.com/sfztools/sfizz/compare/1.2.0...1.2.1 [1.2.0]: https://github.com/sfztools/sfizz/compare/1.1.1...1.2.0 From 40e726c12afb1cb4e49235511bf9fcf8a5a61b54 Mon Sep 17 00:00:00 2001 From: KIRA Ryouta Date: Wed, 14 Feb 2024 08:55:37 +0900 Subject: [PATCH 51/65] Added FileData::Status::FullLoaded --- src/sfizz/FilePool.cpp | 41 ++++++++++++++++++++++++++++------------- src/sfizz/FilePool.h | 2 +- 2 files changed, 29 insertions(+), 14 deletions(-) diff --git a/src/sfizz/FilePool.cpp b/src/sfizz/FilePool.cpp index 2afcd688c..4b29acde2 100644 --- a/src/sfizz/FilePool.cpp +++ b/src/sfizz/FilePool.cpp @@ -332,11 +332,14 @@ bool sfz::FilePool::preloadFile(const FileId& fileId, uint32_t maxOffset) noexce const auto existingFile = preloadedFiles.find(fileId); if (existingFile != preloadedFiles.end()) { - if (framesToLoad > existingFile->second.preloadedData.getNumFrames()) { + auto& fileData = existingFile->second; + if (framesToLoad > fileData.preloadedData.getNumFrames()) { preloadedFiles[fileId].information.maxOffset = maxOffset; preloadedFiles[fileId].preloadedData = readFromFile(*reader, framesToLoad); + if (fileData.status == FileData::Status::Preloaded) + fileData.status = frames == framesToLoad ? FileData::Status::FullLoaded : FileData::Status::Preloaded; } - existingFile->second.preloadCallCount++; + fileData.preloadCallCount++; } else { fileInformation->sampleRate = static_cast(reader->sampleRate()); auto insertedPair = preloadedFiles.insert_or_assign(fileId, { @@ -344,7 +347,7 @@ bool sfz::FilePool::preloadFile(const FileId& fileId, uint32_t maxOffset) noexce *fileInformation }); - insertedPair.first->second.status = FileData::Status::Preloaded; + insertedPair.first->second.status = frames == framesToLoad ? FileData::Status::FullLoaded : FileData::Status::Preloaded; insertedPair.first->second.preloadCallCount++; } @@ -399,7 +402,7 @@ sfz::FileDataHolder sfz::FilePool::loadFile(const FileId& fileId) noexcept readFromFile(*reader, frames), *fileInformation }); - insertedPair.first->second.status = FileData::Status::Preloaded; + insertedPair.first->second.status = FileData::Status::FullLoaded; insertedPair.first->second.preloadCallCount++; return { &insertedPair.first->second }; } @@ -417,7 +420,7 @@ sfz::FileDataHolder sfz::FilePool::loadFromRam(const FileId& fileId, const std:: readFromFile(*reader, frames), *fileInformation }); - insertedPair.first->second.status = FileData::Status::Preloaded; + insertedPair.first->second.status = FileData::Status::FullLoaded; insertedPair.first->second.preloadCallCount++; DBG("Added a file " << fileId.filename()); return { &insertedPair.first->second }; @@ -435,8 +438,9 @@ sfz::FileDataHolder sfz::FilePool::getFilePromise(const std::shared_ptr& return {}; } - if (!loadInRam) { - QueuedFileData queuedData { fileId, &preloaded->second }; + auto& fileData = preloaded->second; + if (fileData.status != FileData::Status::FullLoaded) { + QueuedFileData queuedData { fileId, &fileData }; if (!filesToLoad->try_push(queuedData)) { DBG("[sfizz] Could not enqueue the file to load for " << fileId << " (queue capacity " << filesToLoad->capacity() << ")"); return {}; @@ -458,10 +462,19 @@ void sfz::FilePool::setPreloadSize(uint32_t preloadSize) noexcept // Update all the preloaded sizes for (auto& preloadedFile : preloadedFiles) { - const auto maxOffset = preloadedFile.second.information.maxOffset; - fs::path file { rootDirectory / preloadedFile.first.filename() }; - AudioReaderPtr reader = createAudioReader(file, preloadedFile.first.isReverse()); - preloadedFile.second.preloadedData = readFromFile(*reader, preloadSize + maxOffset); + auto& fileId = preloadedFile.first; + auto& fileData = preloadedFile.second; + const uint32_t maxOffset = fileData.information.maxOffset; + fs::path file { rootDirectory / fileId.filename() }; + AudioReaderPtr reader = createAudioReader(file, fileId.isReverse()); + const uint32_t frames = static_cast(reader->frames()); + const uint32_t framesToLoad = min(frames, maxOffset + preloadSize); + fileData.preloadedData = readFromFile(*reader, framesToLoad); + + const FileData::Status status = fileData.status; + if (status == FileData::Status::FullLoaded || status == FileData::Status::Preloaded) { + fileData.status = frames == framesToLoad ? FileData::Status::FullLoaded : FileData::Status::Preloaded; + } } } @@ -619,10 +632,12 @@ void sfz::FilePool::setRamLoading(bool loadInRam) noexcept for (auto& preloadedFile : preloadedFiles) { fs::path file { rootDirectory / preloadedFile.first.filename() }; AudioReaderPtr reader = createAudioReader(file, preloadedFile.first.isReverse()); - preloadedFile.second.preloadedData = readFromFile( + auto& fileData = preloadedFile.second; + fileData.preloadedData = readFromFile( *reader, - preloadedFile.second.information.end + fileData.information.end ); + fileData.status = FileData::Status::FullLoaded; } } else { setPreloadSize(preloadSize); diff --git a/src/sfizz/FilePool.h b/src/sfizz/FilePool.h index 6adbe2290..0c0e7249a 100644 --- a/src/sfizz/FilePool.h +++ b/src/sfizz/FilePool.h @@ -67,7 +67,7 @@ struct FileInformation { // Strict C++11 disallows member initialization if aggregate initialization is to be used... struct FileData { - enum class Status { Invalid, Preloaded, Streaming, Done }; + enum class Status { Invalid, Preloaded, Streaming, Done, FullLoaded }; FileData() = default; FileData(FileAudioBuffer preloaded, FileInformation info) : preloadedData(std::move(preloaded)), information(std::move(info)) From 4e570dd9ff6270ece9b0e30fb5c6869d0808111c Mon Sep 17 00:00:00 2001 From: KIRA Ryouta Date: Wed, 14 Feb 2024 12:50:02 +0900 Subject: [PATCH 52/65] Improved setting FilData::Status --- src/sfizz/FilePool.cpp | 41 +++++++++++++++++++++++++++++++---------- 1 file changed, 31 insertions(+), 10 deletions(-) diff --git a/src/sfizz/FilePool.cpp b/src/sfizz/FilePool.cpp index 4b29acde2..c80961781 100644 --- a/src/sfizz/FilePool.cpp +++ b/src/sfizz/FilePool.cpp @@ -336,8 +336,9 @@ bool sfz::FilePool::preloadFile(const FileId& fileId, uint32_t maxOffset) noexce if (framesToLoad > fileData.preloadedData.getNumFrames()) { preloadedFiles[fileId].information.maxOffset = maxOffset; preloadedFiles[fileId].preloadedData = readFromFile(*reader, framesToLoad); - if (fileData.status == FileData::Status::Preloaded) - fileData.status = frames == framesToLoad ? FileData::Status::FullLoaded : FileData::Status::Preloaded; + if (frames == framesToLoad && fileData.status != FileData::Status::FullLoaded) + // Forced overwrite status + fileData.status = FileData::Status::FullLoaded; } fileData.preloadCallCount++; } else { @@ -347,6 +348,7 @@ bool sfz::FilePool::preloadFile(const FileId& fileId, uint32_t maxOffset) noexce *fileInformation }); + // Initially set status insertedPair.first->second.status = frames == framesToLoad ? FileData::Status::FullLoaded : FileData::Status::Preloaded; insertedPair.first->second.preloadCallCount++; } @@ -402,6 +404,7 @@ sfz::FileDataHolder sfz::FilePool::loadFile(const FileId& fileId) noexcept readFromFile(*reader, frames), *fileInformation }); + // Initially set status insertedPair.first->second.status = FileData::Status::FullLoaded; insertedPair.first->second.preloadCallCount++; return { &insertedPair.first->second }; @@ -420,6 +423,7 @@ sfz::FileDataHolder sfz::FilePool::loadFromRam(const FileId& fileId, const std:: readFromFile(*reader, frames), *fileInformation }); + // Initially set status insertedPair.first->second.status = FileData::Status::FullLoaded; insertedPair.first->second.preloadCallCount++; DBG("Added a file " << fileId.filename()); @@ -471,9 +475,15 @@ void sfz::FilePool::setPreloadSize(uint32_t preloadSize) noexcept const uint32_t framesToLoad = min(frames, maxOffset + preloadSize); fileData.preloadedData = readFromFile(*reader, framesToLoad); - const FileData::Status status = fileData.status; - if (status == FileData::Status::FullLoaded || status == FileData::Status::Preloaded) { - fileData.status = frames == framesToLoad ? FileData::Status::FullLoaded : FileData::Status::Preloaded; + FileData::Status status = fileData.status; + bool fullLoaded = frames == framesToLoad; + if (fullLoaded && status != FileData::Status::FullLoaded) { + // Forced overwrite status + fileData.status = FileData::Status::FullLoaded; + } + else if (!fullLoaded && status == FileData::Status::FullLoaded) { + // Exchange status + fileData.status.compare_exchange_strong(status, FileData::Status::Preloaded); } } } @@ -522,7 +532,10 @@ void sfz::FilePool::loadingJob(const QueuedFileData& data) noexcept streamFromFile(*reader, data.data->fileData, &data.data->availableFrames); - data.data->status = FileData::Status::Done; + currentStatus = data.data->status.load(); + if (currentStatus == FileData::Status::Streaming) { + data.data->status.compare_exchange_strong(currentStatus, FileData::Status::Done); + } std::lock_guard guard { garbageAndLastUsedMutex }; if (absl::c_find(lastUsedFiles, *id) == lastUsedFiles.end()) @@ -637,6 +650,7 @@ void sfz::FilePool::setRamLoading(bool loadInRam) noexcept *reader, fileData.information.end ); + // Initially set status fileData.status = FileData::Status::FullLoaded; } } else { @@ -663,10 +677,13 @@ void sfz::FilePool::triggerGarbageCollection() noexcept } sfz::FileData& data = it->second; - if (data.status == FileData::Status::Preloaded) - return true; - if (data.status != FileData::Status::Done) + auto status = data.status.load(); + if (data.availableFrames == 0) { + return status == FileData::Status::Preloaded || status == FileData::Status::FullLoaded; + } + + if (status != FileData::Status::Done) return false; if (data.readerCount != 0) @@ -676,8 +693,12 @@ void sfz::FilePool::triggerGarbageCollection() noexcept if (secondsIdle < config::fileClearingPeriod) return false; + // Queue garbage collection + // At first set availableFromes to 0 + // Then forced set status data.availableFrames = 0; - data.status = FileData::Status::Preloaded; + if (status != FileData::Status::FullLoaded) + data.status = FileData::Status::Preloaded; garbageToCollect.push_back(std::move(data.fileData)); return true; }); From 3164a6c502e657bbbbb6158e7ba4c8f162cbce7a Mon Sep 17 00:00:00 2001 From: KIRA Ryouta Date: Wed, 14 Feb 2024 16:08:14 +0900 Subject: [PATCH 53/65] Improved status check in garbage collection process --- src/sfizz/FilePool.cpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/sfizz/FilePool.cpp b/src/sfizz/FilePool.cpp index c80961781..a37d22b68 100644 --- a/src/sfizz/FilePool.cpp +++ b/src/sfizz/FilePool.cpp @@ -683,8 +683,13 @@ void sfz::FilePool::triggerGarbageCollection() noexcept return status == FileData::Status::Preloaded || status == FileData::Status::FullLoaded; } - if (status != FileData::Status::Done) - return false; + switch (status) { + case FileData::Status::Invalid: + case FileData::Status::Streaming: + return false; + default: + break; + } if (data.readerCount != 0) return false; From f8f13f464ca71c4f9158da5f84a81a81d4c6e08d Mon Sep 17 00:00:00 2001 From: KIRA Ryouta Date: Wed, 14 Feb 2024 16:16:21 +0900 Subject: [PATCH 54/65] Fixed status check order in garbage collection process --- src/sfizz/FilePool.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/sfizz/FilePool.cpp b/src/sfizz/FilePool.cpp index a37d22b68..42e1f8cce 100644 --- a/src/sfizz/FilePool.cpp +++ b/src/sfizz/FilePool.cpp @@ -678,12 +678,15 @@ void sfz::FilePool::triggerGarbageCollection() noexcept sfz::FileData& data = it->second; + if (data.readerCount != 0) + return false; + auto status = data.status.load(); if (data.availableFrames == 0) { return status == FileData::Status::Preloaded || status == FileData::Status::FullLoaded; } - switch (status) { + switch(status) { case FileData::Status::Invalid: case FileData::Status::Streaming: return false; @@ -691,9 +694,6 @@ void sfz::FilePool::triggerGarbageCollection() noexcept break; } - if (data.readerCount != 0) - return false; - const auto secondsIdle = std::chrono::duration_cast(now - data.lastViewerLeftAt).count(); if (secondsIdle < config::fileClearingPeriod) return false; From c566631379a70544fb1f81455547bf35d88f2cde Mon Sep 17 00:00:00 2001 From: KIRA Ryouta Date: Fri, 16 Feb 2024 10:18:52 +0900 Subject: [PATCH 55/65] FullyLoaded status becomes independent. Added GarbageCollecting status. --- src/sfizz/FilePool.cpp | 120 +++++++++++++++++++---------------------- src/sfizz/FilePool.h | 3 +- 2 files changed, 57 insertions(+), 66 deletions(-) diff --git a/src/sfizz/FilePool.cpp b/src/sfizz/FilePool.cpp index 42e1f8cce..c5f12ab6c 100644 --- a/src/sfizz/FilePool.cpp +++ b/src/sfizz/FilePool.cpp @@ -334,11 +334,9 @@ bool sfz::FilePool::preloadFile(const FileId& fileId, uint32_t maxOffset) noexce if (existingFile != preloadedFiles.end()) { auto& fileData = existingFile->second; if (framesToLoad > fileData.preloadedData.getNumFrames()) { - preloadedFiles[fileId].information.maxOffset = maxOffset; - preloadedFiles[fileId].preloadedData = readFromFile(*reader, framesToLoad); - if (frames == framesToLoad && fileData.status != FileData::Status::FullLoaded) - // Forced overwrite status - fileData.status = FileData::Status::FullLoaded; + fileData.information.maxOffset = maxOffset; + fileData.preloadedData = readFromFile(*reader, framesToLoad); + fileData.fullyLoaded = frames == framesToLoad; } fileData.preloadCallCount++; } else { @@ -348,9 +346,9 @@ bool sfz::FilePool::preloadFile(const FileId& fileId, uint32_t maxOffset) noexce *fileInformation }); - // Initially set status - insertedPair.first->second.status = frames == framesToLoad ? FileData::Status::FullLoaded : FileData::Status::Preloaded; insertedPair.first->second.preloadCallCount++; + insertedPair.first->second.status = FileData::Status::Preloaded; + insertedPair.first->second.fullyLoaded = framesToLoad == frames; } return true; @@ -404,9 +402,9 @@ sfz::FileDataHolder sfz::FilePool::loadFile(const FileId& fileId) noexcept readFromFile(*reader, frames), *fileInformation }); - // Initially set status - insertedPair.first->second.status = FileData::Status::FullLoaded; insertedPair.first->second.preloadCallCount++; + insertedPair.first->second.status = FileData::Status::Preloaded; + insertedPair.first->second.fullyLoaded = true; return { &insertedPair.first->second }; } @@ -423,9 +421,9 @@ sfz::FileDataHolder sfz::FilePool::loadFromRam(const FileId& fileId, const std:: readFromFile(*reader, frames), *fileInformation }); - // Initially set status - insertedPair.first->second.status = FileData::Status::FullLoaded; insertedPair.first->second.preloadCallCount++; + insertedPair.first->second.status = FileData::Status::Preloaded; + insertedPair.first->second.fullyLoaded = true; DBG("Added a file " << fileId.filename()); return { &insertedPair.first->second }; } @@ -443,7 +441,7 @@ sfz::FileDataHolder sfz::FilePool::getFilePromise(const std::shared_ptr& } auto& fileData = preloaded->second; - if (fileData.status != FileData::Status::FullLoaded) { + if (!fileData.fullyLoaded) { QueuedFileData queuedData { fileId, &fileData }; if (!filesToLoad->try_push(queuedData)) { DBG("[sfizz] Could not enqueue the file to load for " << fileId << " (queue capacity " << filesToLoad->capacity() << ")"); @@ -474,17 +472,7 @@ void sfz::FilePool::setPreloadSize(uint32_t preloadSize) noexcept const uint32_t frames = static_cast(reader->frames()); const uint32_t framesToLoad = min(frames, maxOffset + preloadSize); fileData.preloadedData = readFromFile(*reader, framesToLoad); - - FileData::Status status = fileData.status; - bool fullLoaded = frames == framesToLoad; - if (fullLoaded && status != FileData::Status::FullLoaded) { - // Forced overwrite status - fileData.status = FileData::Status::FullLoaded; - } - else if (!fullLoaded && status == FileData::Status::FullLoaded) { - // Exchange status - fileData.status.compare_exchange_strong(status, FileData::Status::Preloaded); - } + fileData.fullyLoaded = frames == framesToLoad; } } @@ -507,35 +495,40 @@ void sfz::FilePool::loadingJob(const QueuedFileData& data) noexcept return; } - FileData::Status currentStatus = data.data->status.load(); + FileData::Status currentStatus; unsigned spinCounter { 0 }; - while (currentStatus == FileData::Status::Invalid) { - // Spin until the state changes - if (spinCounter > 1024) { - DBG("[sfizz] " << *id << " is stuck on Invalid? Leaving the load"); - return; - } - std::this_thread::sleep_for(std::chrono::microseconds(100)); + while (1) { currentStatus = data.data->status.load(); - spinCounter += 1; - } + while (currentStatus == FileData::Status::Invalid) { + // Spin until the state changes + if (spinCounter > 1024) { + DBG("[sfizz] " << *id << " is stuck on Invalid? Leaving the load"); + return; + } - // Already loading or loaded - if (currentStatus != FileData::Status::Preloaded) - return; + std::this_thread::sleep_for(std::chrono::microseconds(100)); + currentStatus = data.data->status.load(); + spinCounter += 1; + } + // wait for garbage collection + if (currentStatus == FileData::Status::GarbageCollecting) { + std::this_thread::sleep_for(std::chrono::microseconds(1)); + continue; + } + // Already loading or loaded + if (currentStatus != FileData::Status::Preloaded) + return; - // Someone else got the token - if (!data.data->status.compare_exchange_strong(currentStatus, FileData::Status::Streaming)) - return; + // go outside loop if this gets token + if (data.data->status.compare_exchange_strong(currentStatus, FileData::Status::Streaming)) + break; + } streamFromFile(*reader, data.data->fileData, &data.data->availableFrames); - currentStatus = data.data->status.load(); - if (currentStatus == FileData::Status::Streaming) { - data.data->status.compare_exchange_strong(currentStatus, FileData::Status::Done); - } + data.data->status = FileData::Status::Done; std::lock_guard guard { garbageAndLastUsedMutex }; if (absl::c_find(lastUsedFiles, *id) == lastUsedFiles.end()) @@ -650,8 +643,7 @@ void sfz::FilePool::setRamLoading(bool loadInRam) noexcept *reader, fileData.information.end ); - // Initially set status - fileData.status = FileData::Status::FullLoaded; + fileData.fullyLoaded = true; } } else { setPreloadSize(preloadSize); @@ -681,31 +673,29 @@ void sfz::FilePool::triggerGarbageCollection() noexcept if (data.readerCount != 0) return false; - auto status = data.status.load(); - if (data.availableFrames == 0) { - return status == FileData::Status::Preloaded || status == FileData::Status::FullLoaded; - } - - switch(status) { - case FileData::Status::Invalid: - case FileData::Status::Streaming: - return false; - default: - break; - } - const auto secondsIdle = std::chrono::duration_cast(now - data.lastViewerLeftAt).count(); if (secondsIdle < config::fileClearingPeriod) return false; - // Queue garbage collection - // At first set availableFromes to 0 - // Then forced set status - data.availableFrames = 0; - if (status != FileData::Status::FullLoaded) + auto status = data.status.load(); + if (status == FileData::Status::Preloaded) { + // do the garbage collection when availableFrames != 0 + if (data.availableFrames == 0) { + return true; + } + } + else if (status != FileData::Status::Done) { + return false; + } + + // do garbage collection when changing the status is success + if (data.status.compare_exchange_strong(status, FileData::Status::GarbageCollecting)) { + data.availableFrames = 0; + garbageToCollect.push_back(std::move(data.fileData)); data.status = FileData::Status::Preloaded; - garbageToCollect.push_back(std::move(data.fileData)); - return true; + return true; + } + return false; }); std::error_code ec; diff --git a/src/sfizz/FilePool.h b/src/sfizz/FilePool.h index 0c0e7249a..5eceda2cf 100644 --- a/src/sfizz/FilePool.h +++ b/src/sfizz/FilePool.h @@ -67,7 +67,7 @@ struct FileInformation { // Strict C++11 disallows member initialization if aggregate initialization is to be used... struct FileData { - enum class Status { Invalid, Preloaded, Streaming, Done, FullLoaded }; + enum class Status { Invalid, Preloaded, Streaming, Done, GarbageCollecting }; FileData() = default; FileData(FileAudioBuffer preloaded, FileInformation info) : preloadedData(std::move(preloaded)), information(std::move(info)) @@ -113,6 +113,7 @@ struct FileData FileAudioBuffer fileData {}; int preloadCallCount { 0 }; std::atomic status { Status::Invalid }; + bool fullyLoaded { false }; std::atomic availableFrames { 0 }; std::atomic readerCount { 0 }; std::chrono::time_point lastViewerLeftAt; From ffaed1581a8645d0346a22b15b8cca8aa6a96600 Mon Sep 17 00:00:00 2001 From: KIRA Ryouta Date: Fri, 16 Feb 2024 11:16:58 +0900 Subject: [PATCH 56/65] Improved check for garbage collection --- src/sfizz/FilePool.cpp | 6 +++++- src/sfizz/FilePool.h | 3 ++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/sfizz/FilePool.cpp b/src/sfizz/FilePool.cpp index c5f12ab6c..37dc2ff29 100644 --- a/src/sfizz/FilePool.cpp +++ b/src/sfizz/FilePool.cpp @@ -39,6 +39,7 @@ #include #include #include +#include #if defined(_WIN32) #include #else @@ -514,7 +515,10 @@ void sfz::FilePool::loadingJob(const QueuedFileData& data) noexcept } // wait for garbage collection if (currentStatus == FileData::Status::GarbageCollecting) { - std::this_thread::sleep_for(std::chrono::microseconds(1)); + atomic_queue::spin_loop_pause(); + atomic_queue::spin_loop_pause(); + atomic_queue::spin_loop_pause(); + atomic_queue::spin_loop_pause(); continue; } // Already loading or loaded diff --git a/src/sfizz/FilePool.h b/src/sfizz/FilePool.h index 5eceda2cf..02f3d3eb4 100644 --- a/src/sfizz/FilePool.h +++ b/src/sfizz/FilePool.h @@ -76,7 +76,8 @@ struct FileData } AudioSpan getData() { - if (availableFrames > preloadedData.getNumFrames()) + ASSERT(readerCount > 0); + if (status != Status::GarbageCollecting && availableFrames > preloadedData.getNumFrames()) return AudioSpan(fileData).first(availableFrames); else return AudioSpan(preloadedData); From b6ba83466911bc6bf917aec6f8ae1bec15f09c28 Mon Sep 17 00:00:00 2001 From: KIRA Ryouta Date: Fri, 16 Feb 2024 11:36:06 +0900 Subject: [PATCH 57/65] Improved check in garbage collection --- src/sfizz/FilePool.cpp | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/sfizz/FilePool.cpp b/src/sfizz/FilePool.cpp index 37dc2ff29..863109403 100644 --- a/src/sfizz/FilePool.cpp +++ b/src/sfizz/FilePool.cpp @@ -694,10 +694,14 @@ void sfz::FilePool::triggerGarbageCollection() noexcept // do garbage collection when changing the status is success if (data.status.compare_exchange_strong(status, FileData::Status::GarbageCollecting)) { - data.availableFrames = 0; - garbageToCollect.push_back(std::move(data.fileData)); - data.status = FileData::Status::Preloaded; - return true; + // recheck readerCount + auto readerCount = data.readerCount.load(); + if (readerCount == 0) { + data.availableFrames = 0; + garbageToCollect.push_back(std::move(data.fileData)); + return true; + } + data.status = status; } return false; }); From 3205fb4807a0dba71708a223bf93db5df4f64a05 Mon Sep 17 00:00:00 2001 From: KIRA Ryouta Date: Fri, 16 Feb 2024 11:37:24 +0900 Subject: [PATCH 58/65] Fixed a error in garbage collection --- src/sfizz/FilePool.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/sfizz/FilePool.cpp b/src/sfizz/FilePool.cpp index 863109403..ea99c4395 100644 --- a/src/sfizz/FilePool.cpp +++ b/src/sfizz/FilePool.cpp @@ -699,6 +699,7 @@ void sfz::FilePool::triggerGarbageCollection() noexcept if (readerCount == 0) { data.availableFrames = 0; garbageToCollect.push_back(std::move(data.fileData)); + data.status = FileData::Status::Preloaded; return true; } data.status = status; From 28e5130f446827f75f9e58d41e194023cf8139ff Mon Sep 17 00:00:00 2001 From: KIRA Ryouta Date: Fri, 16 Feb 2024 12:30:58 +0900 Subject: [PATCH 59/65] Improved process for maxOffset in FilePool::setPreloadSize() --- src/sfizz/FilePool.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/sfizz/FilePool.cpp b/src/sfizz/FilePool.cpp index ea99c4395..fa7e5afb3 100644 --- a/src/sfizz/FilePool.cpp +++ b/src/sfizz/FilePool.cpp @@ -467,12 +467,12 @@ void sfz::FilePool::setPreloadSize(uint32_t preloadSize) noexcept for (auto& preloadedFile : preloadedFiles) { auto& fileId = preloadedFile.first; auto& fileData = preloadedFile.second; - const uint32_t maxOffset = fileData.information.maxOffset; + const auto maxOffset = fileData.information.maxOffset; fs::path file { rootDirectory / fileId.filename() }; AudioReaderPtr reader = createAudioReader(file, fileId.isReverse()); - const uint32_t frames = static_cast(reader->frames()); - const uint32_t framesToLoad = min(frames, maxOffset + preloadSize); - fileData.preloadedData = readFromFile(*reader, framesToLoad); + const auto frames = reader->frames(); + const auto framesToLoad = min(frames, maxOffset + preloadSize); + fileData.preloadedData = readFromFile(*reader, static_cast(framesToLoad)); fileData.fullyLoaded = frames == framesToLoad; } } From 4b5d45f86768f9de97fae13c51c98a0d8b69863d Mon Sep 17 00:00:00 2001 From: redtide Date: Fri, 23 Feb 2024 04:41:59 +0100 Subject: [PATCH 60/65] Dependencies update: cpuid 9, cxxopts 3.2.1 and pugixml 1.14 --- external/cxxopts/cxxopts.hpp | 202 +- src/external/cpuid/src/cpuid/version.cpp | 2 +- src/external/cpuid/src/cpuid/version.hpp | 2 +- src/external/pugixml/LICENSE.md | 2 +- src/external/pugixml/src/pugiconfig.hpp | 6 +- src/external/pugixml/src/pugixml.cpp | 2201 +++++++++++----------- src/external/pugixml/src/pugixml.hpp | 58 +- 7 files changed, 1306 insertions(+), 1167 deletions(-) diff --git a/external/cxxopts/cxxopts.hpp b/external/cxxopts/cxxopts.hpp index b789a5c39..0b272acec 100644 --- a/external/cxxopts/cxxopts.hpp +++ b/external/cxxopts/cxxopts.hpp @@ -27,6 +27,7 @@ THE SOFTWARE. #ifndef CXXOPTS_HPP_INCLUDED #define CXXOPTS_HPP_INCLUDED +#include #include #include #include @@ -55,8 +56,8 @@ THE SOFTWARE. #define CXXOPTS_LINKONCE_CONST __declspec(selectany) extern #define CXXOPTS_LINKONCE __declspec(selectany) extern #else -#define CXXOPTS_LINKONCE_CONST -#define CXXOPTS_LINKONCE +#define CXXOPTS_LINKONCE_CONST +#define CXXOPTS_LINKONCE #endif #ifndef CXXOPTS_NO_REGEX @@ -73,6 +74,14 @@ THE SOFTWARE. # endif #endif +#define CXXOPTS_FALLTHROUGH +#ifdef __has_cpp_attribute + #if __has_cpp_attribute(fallthrough) + #undef CXXOPTS_FALLTHROUGH + #define CXXOPTS_FALLTHROUGH [[fallthrough]] + #endif +#endif + #if __cplusplus >= 201603L #define CXXOPTS_NODISCARD [[nodiscard]] #else @@ -84,7 +93,7 @@ THE SOFTWARE. #endif #define CXXOPTS__VERSION_MAJOR 3 -#define CXXOPTS__VERSION_MINOR 1 +#define CXXOPTS__VERSION_MINOR 2 #define CXXOPTS__VERSION_PATCH 1 #if (__GNUC__ < 10 || (__GNUC__ == 10 && __GNUC_MINOR__ < 1)) && __GNUC__ >= 6 @@ -230,10 +239,10 @@ stringAppend(String& s, Iterator begin, Iterator end) } inline -std::size_t +size_t stringLength(const String& s) { - return s.length(); + return static_cast(s.length()); } inline @@ -337,13 +346,8 @@ empty(const std::string& s) namespace cxxopts { namespace { -#ifdef _WIN32 CXXOPTS_LINKONCE_CONST std::string LQUOTE("\'"); CXXOPTS_LINKONCE_CONST std::string RQUOTE("\'"); -#else -CXXOPTS_LINKONCE_CONST std::string LQUOTE("‘"); -CXXOPTS_LINKONCE_CONST std::string RQUOTE("’"); -#endif } // namespace // GNU GCC with -Weffc++ will issue a warning regarding the upcoming class, we @@ -365,6 +369,9 @@ class Value : public std::enable_shared_from_this std::shared_ptr clone() const = 0; + virtual void + add(const std::string& text) const = 0; + virtual void parse(const std::string& text) const = 0; @@ -758,29 +765,31 @@ inline ArguDesc ParseArgument(const char *arg, bool &matched) namespace { CXXOPTS_LINKONCE -std::basic_regex integer_pattern - ("(-)?(0x)?([0-9a-zA-Z]+)|((0x)?0)"); +const char* const integer_pattern = + "(-)?(0x)?([0-9a-zA-Z]+)|((0x)?0)"; CXXOPTS_LINKONCE -std::basic_regex truthy_pattern - ("(t|T)(rue)?|1"); +const char* const truthy_pattern = + "(t|T)(rue)?|1"; CXXOPTS_LINKONCE -std::basic_regex falsy_pattern - ("(f|F)(alse)?|0"); +const char* const falsy_pattern = + "(f|F)(alse)?|0"; CXXOPTS_LINKONCE -std::basic_regex option_matcher - ("--([[:alnum:]][-_[:alnum:]\\.]+)(=(.*))?|-([[:alnum:]].*)"); +const char* const option_pattern = + "--([[:alnum:]][-_[:alnum:]\\.]+)(=(.*))?|-([[:alnum:]].*)"; CXXOPTS_LINKONCE -std::basic_regex option_specifier - ("([[:alnum:]][-_[:alnum:]\\.]*)(,[ ]*[[:alnum:]][-_[:alnum:]]*)*"); +const char* const option_specifier_pattern = + "([[:alnum:]][-_[:alnum:]\\.]*)(,[ ]*[[:alnum:]][-_[:alnum:]]*)*"; CXXOPTS_LINKONCE -std::basic_regex option_specifier_separator(", *"); +const char* const option_specifier_separator_pattern = ", *"; } // namespace inline IntegerDesc SplitInteger(const std::string &text) { + static const std::basic_regex integer_matcher(integer_pattern); + std::smatch match; - std::regex_match(text, match, integer_pattern); + std::regex_match(text, match, integer_matcher); if (match.length() == 0) { @@ -804,15 +813,17 @@ inline IntegerDesc SplitInteger(const std::string &text) inline bool IsTrueText(const std::string &text) { + static const std::basic_regex truthy_matcher(truthy_pattern); std::smatch result; - std::regex_match(text, result, truthy_pattern); + std::regex_match(text, result, truthy_matcher); return !result.empty(); } inline bool IsFalseText(const std::string &text) { + static const std::basic_regex falsy_matcher(falsy_pattern); std::smatch result; - std::regex_match(text, result, falsy_pattern); + std::regex_match(text, result, falsy_matcher); return !result.empty(); } @@ -821,22 +832,25 @@ inline bool IsFalseText(const std::string &text) // (without considering which or how many are single-character) inline OptionNames split_option_names(const std::string &text) { - if (!std::regex_match(text.c_str(), option_specifier)) + static const std::basic_regex option_specifier_matcher(option_specifier_pattern); + if (!std::regex_match(text.c_str(), option_specifier_matcher)) { throw_or_mimic(text); } OptionNames split_names; + static const std::basic_regex option_specifier_separator_matcher(option_specifier_separator_pattern); constexpr int use_non_matches { -1 }; auto token_iterator = std::sregex_token_iterator( - text.begin(), text.end(), option_specifier_separator, use_non_matches); + text.begin(), text.end(), option_specifier_separator_matcher, use_non_matches); std::copy(token_iterator, std::sregex_token_iterator(), std::back_inserter(split_names)); return split_names; } inline ArguDesc ParseArgument(const char *arg, bool &matched) { + static const std::basic_regex option_matcher(option_pattern); std::match_results result; std::regex_match(arg, result, option_matcher); matched = !result.empty(); @@ -959,13 +973,26 @@ integer_parser(const std::string& text, T& value) throw_or_mimic(text); } - const US next = static_cast(result * base + digit); - if (result > next) + US limit = 0; + if (negative) + { + limit = static_cast(std::abs(static_cast(std::numeric_limits::min()))); + } + else + { + limit = std::numeric_limits::max(); + } + + if (base != 0 && result > limit / base) + { + throw_or_mimic(text); + } + if (result * base > limit - digit) { throw_or_mimic(text); } - result = next; + result = static_cast(result * base + digit); } detail::check_signed_range(negative, result, text); @@ -1035,6 +1062,28 @@ parse_value(const std::string& text, T& value) { stringstream_parser(text, value); } +#ifdef CXXOPTS_HAS_OPTIONAL +template +void +parse_value(const std::string& text, std::optional& value) +{ + T result; + parse_value(text, result); + value = std::move(result); +} +#endif + +inline +void parse_value(const std::string& text, char& c) +{ + if (text.length() != 1) + { + throw_or_mimic(text); + } + + c = text[0]; +} + template void parse_value(const std::string& text, std::vector& value) @@ -1054,26 +1103,20 @@ parse_value(const std::string& text, std::vector& value) } } -#ifdef CXXOPTS_HAS_OPTIONAL template void -parse_value(const std::string& text, std::optional& value) +add_value(const std::string& text, T& value) { - T result; - parse_value(text, result); - value = std::move(result); + parse_value(text, value); } -#endif -inline -void parse_value(const std::string& text, char& c) +template +void +add_value(const std::string& text, std::vector& value) { - if (text.length() != 1) - { - throw_or_mimic(text); - } - - c = text[0]; + T v; + add_value(text, v); + value.emplace_back(std::move(v)); } template @@ -1127,6 +1170,12 @@ class abstract_value : public Value m_implicit_value = rhs.m_implicit_value; } + void + add(const std::string& text) const override + { + add_value(text, *m_store); + } + void parse(const std::string& text) const override { @@ -1412,6 +1461,19 @@ struct HelpGroupDetails class OptionValue { public: + void + add + ( + const std::shared_ptr& details, + const std::string& text + ) + { + ensure_value(details); + ++m_count; + m_value->add(text); + m_long_names = &details->long_names(); + } + void parse ( @@ -1498,7 +1560,7 @@ CXXOPTS_DIAGNOSTIC_POP class KeyValue { public: - KeyValue(std::string key_, std::string value_) + KeyValue(std::string key_, std::string value_) noexcept : m_key(std::move(key_)) , m_value(std::move(value_)) { @@ -1551,7 +1613,7 @@ class ParseResult Iterator(const Iterator&) = default; // GCC complains about m_iter not being initialised in the member -// initializer list +// initializer list CXXOPTS_DIAGNOSTIC_PUSH CXXOPTS_IGNORE_WARNING("-Weffc++") Iterator(const ParseResult *pr, bool end=false) @@ -1782,7 +1844,7 @@ class OptionParser ); void - add_to_option(OptionMap::const_iterator iter, const std::string& option, const std::string& arg); + add_to_option(const std::shared_ptr& value, const std::string& arg); void parse_option @@ -1985,6 +2047,7 @@ class Options std::unordered_set m_positional_set{}; //mapping from groups to help options + std::vector m_group{}; std::map m_help{}; }; @@ -2237,6 +2300,7 @@ OptionAdder::operator() case 1: short_name = *first_short_name_iter; option_names.erase(first_short_name_iter); + CXXOPTS_FALLTHROUGH; case 0: break; default: @@ -2328,9 +2392,13 @@ OptionParser::checked_parse_arg inline void -OptionParser::add_to_option(OptionMap::const_iterator iter, const std::string& option, const std::string& arg) +OptionParser::add_to_option(const std::shared_ptr& value, const std::string& arg) { - parse_option(iter->second, option, arg); + auto hash = value->hash(); + auto& result = m_parsed[hash]; + result.add(value, arg); + + m_sequential.emplace_back(value->essential_name(), arg); } inline @@ -2347,14 +2415,14 @@ OptionParser::consume_positional(const std::string& a, PositionalListIterator& n auto& result = m_parsed[iter->second->hash()]; if (result.count() == 0) { - add_to_option(iter, *next, a); + add_to_option(iter->second, a); ++next; return true; } ++next; continue; } - add_to_option(iter, *next, a); + add_to_option(iter->second, a); return true; } throw_or_mimic(*next); @@ -2618,6 +2686,12 @@ Options::add_option } //add the help details + + if (m_help.find(group) == m_help.end()) + { + m_group.push_back(group); + } + auto& options = m_help[group]; options.options.emplace_back(HelpOptionDetails{s, l, stringDesc, @@ -2749,19 +2823,7 @@ inline void Options::generate_all_groups_help(String& result) const { - std::vector all_groups; - - std::transform( - m_help.begin(), - m_help.end(), - std::back_inserter(all_groups), - [] (const std::map::value_type& group) - { - return group.first; - } - ); - - generate_group_help(result, all_groups); + generate_group_help(result, m_group); } inline @@ -2801,19 +2863,7 @@ inline std::vector Options::groups() const { - std::vector g; - - std::transform( - m_help.begin(), - m_help.end(), - std::back_inserter(g), - [] (const std::map::value_type& pair) - { - return pair.first; - } - ); - - return g; + return m_group; } inline diff --git a/src/external/cpuid/src/cpuid/version.cpp b/src/external/cpuid/src/cpuid/version.cpp index 1e0fbfd90..dd5039287 100644 --- a/src/external/cpuid/src/cpuid/version.cpp +++ b/src/external/cpuid/src/cpuid/version.cpp @@ -12,7 +12,7 @@ inline namespace STEINWURF_CPUID_VERSION { std::string version() { - return "8.0.0"; + return "9.0.0"; } } } diff --git a/src/external/cpuid/src/cpuid/version.hpp b/src/external/cpuid/src/cpuid/version.hpp index 5715dce87..62258d310 100644 --- a/src/external/cpuid/src/cpuid/version.hpp +++ b/src/external/cpuid/src/cpuid/version.hpp @@ -11,7 +11,7 @@ namespace cpuid { /// Here we define the STEINWURF_CPUID_VERSION this should be updated on each /// release -#define STEINWURF_CPUID_VERSION v8_0_0 +#define STEINWURF_CPUID_VERSION v9_0_0 inline namespace STEINWURF_CPUID_VERSION { diff --git a/src/external/pugixml/LICENSE.md b/src/external/pugixml/LICENSE.md index 63042bd91..160e06c98 100644 --- a/src/external/pugixml/LICENSE.md +++ b/src/external/pugixml/LICENSE.md @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2006-2022 Arseny Kapoulkine +Copyright (c) 2006-2024 Arseny Kapoulkine Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation diff --git a/src/external/pugixml/src/pugiconfig.hpp b/src/external/pugixml/src/pugiconfig.hpp index 88b2f2aee..a112de302 100644 --- a/src/external/pugixml/src/pugiconfig.hpp +++ b/src/external/pugixml/src/pugiconfig.hpp @@ -1,7 +1,7 @@ /** - * pugixml parser - version 1.13 + * pugixml parser - version 1.14 * -------------------------------------------------------- - * Copyright (C) 2006-2022, by Arseny Kapoulkine (arseny.kapoulkine@gmail.com) + * Copyright (C) 2006-2024, by Arseny Kapoulkine (arseny.kapoulkine@gmail.com) * Report bugs and download new versions at https://pugixml.org/ * * This library is distributed under the MIT License. See notice at the end @@ -52,7 +52,7 @@ #endif /** - * Copyright (c) 2006-2022 Arseny Kapoulkine + * Copyright (c) 2006-2024 Arseny Kapoulkine * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation diff --git a/src/external/pugixml/src/pugixml.cpp b/src/external/pugixml/src/pugixml.cpp index c63645b67..e87fa8572 100644 --- a/src/external/pugixml/src/pugixml.cpp +++ b/src/external/pugixml/src/pugixml.cpp @@ -1,7 +1,7 @@ /** - * pugixml parser - version 1.13 + * pugixml parser - version 1.14 * -------------------------------------------------------- - * Copyright (C) 2006-2022, by Arseny Kapoulkine (arseny.kapoulkine@gmail.com) + * Copyright (C) 2006-2024, by Arseny Kapoulkine (arseny.kapoulkine@gmail.com) * Report bugs and download new versions at https://pugixml.org/ * * This library is distributed under the MIT License. See notice at the end @@ -40,6 +40,11 @@ // For placement new #include +// For load_file +#if defined(__linux__) || defined(__APPLE__) +#include +#endif + #ifdef _MSC_VER # pragma warning(push) # pragma warning(disable: 4127) // conditional expression is constant @@ -48,6 +53,11 @@ # pragma warning(disable: 4996) // this function or variable may be unsafe #endif +#if defined(__clang__) +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wzero-as-null-pointer-constant" // NULL as null pointer constant +#endif + #if defined(_MSC_VER) && defined(__c2__) # pragma clang diagnostic push # pragma clang diagnostic ignored "-Wdeprecated" // this function or variable may be unsafe @@ -82,39 +92,39 @@ // Inlining controls #if defined(_MSC_VER) && _MSC_VER >= 1300 -# define PUGI__NO_INLINE __declspec(noinline) +# define PUGI_IMPL_NO_INLINE __declspec(noinline) #elif defined(__GNUC__) -# define PUGI__NO_INLINE __attribute__((noinline)) +# define PUGI_IMPL_NO_INLINE __attribute__((noinline)) #else -# define PUGI__NO_INLINE +# define PUGI_IMPL_NO_INLINE #endif // Branch weight controls #if defined(__GNUC__) && !defined(__c2__) -# define PUGI__UNLIKELY(cond) __builtin_expect(cond, 0) +# define PUGI_IMPL_UNLIKELY(cond) __builtin_expect(cond, 0) #else -# define PUGI__UNLIKELY(cond) (cond) +# define PUGI_IMPL_UNLIKELY(cond) (cond) #endif // Simple static assertion -#define PUGI__STATIC_ASSERT(cond) { static const char condition_failed[(cond) ? 1 : -1] = {0}; (void)condition_failed[0]; } +#define PUGI_IMPL_STATIC_ASSERT(cond) { static const char condition_failed[(cond) ? 1 : -1] = {0}; (void)condition_failed[0]; } // Digital Mars C++ bug workaround for passing char loaded from memory via stack #ifdef __DMC__ -# define PUGI__DMC_VOLATILE volatile +# define PUGI_IMPL_DMC_VOLATILE volatile #else -# define PUGI__DMC_VOLATILE +# define PUGI_IMPL_DMC_VOLATILE #endif // Integer sanitizer workaround; we only apply this for clang since gcc8 has no_sanitize but not unsigned-integer-overflow and produces "attribute directive ignored" warnings #if defined(__clang__) && defined(__has_attribute) # if __has_attribute(no_sanitize) -# define PUGI__UNSIGNED_OVERFLOW __attribute__((no_sanitize("unsigned-integer-overflow"))) +# define PUGI_IMPL_UNSIGNED_OVERFLOW __attribute__((no_sanitize("unsigned-integer-overflow"))) # else -# define PUGI__UNSIGNED_OVERFLOW +# define PUGI_IMPL_UNSIGNED_OVERFLOW # endif #else -# define PUGI__UNSIGNED_OVERFLOW +# define PUGI_IMPL_UNSIGNED_OVERFLOW #endif // Borland C++ bug workaround for not defining ::memcpy depending on header include order (can't always use std::memcpy because some compilers don't have it at all) @@ -124,6 +134,12 @@ using std::memmove; using std::memset; #endif +// Old versions of GCC do not define ::malloc and ::free depending on header include order +#if defined(__GNUC__) && (__GNUC__ < 3 || (__GNUC__ == 3 && __GNUC_MINOR__ < 4)) +using std::malloc; +using std::free; +#endif + // Some MinGW/GCC versions have headers that erroneously omit LLONG_MIN/LLONG_MAX/ULLONG_MAX definitions from limits.h in some configurations #if defined(PUGIXML_HAS_LONG_LONG) && defined(__GNUC__) && !defined(LLONG_MAX) && !defined(LLONG_MIN) && !defined(ULLONG_MAX) # define LLONG_MIN (-LLONG_MAX - 1LL) @@ -133,36 +149,38 @@ using std::memset; // In some environments MSVC is a compiler but the CRT lacks certain MSVC-specific features #if defined(_MSC_VER) && !defined(__S3E__) && !defined(_WIN32_WCE) -# define PUGI__MSVC_CRT_VERSION _MSC_VER +# define PUGI_IMPL_MSVC_CRT_VERSION _MSC_VER #elif defined(_WIN32_WCE) -# define PUGI__MSVC_CRT_VERSION 1310 // MSVC7.1 +# define PUGI_IMPL_MSVC_CRT_VERSION 1310 // MSVC7.1 #endif // Not all platforms have snprintf; we define a wrapper that uses snprintf if possible. This only works with buffers with a known size. #if __cplusplus >= 201103 -# define PUGI__SNPRINTF(buf, ...) snprintf(buf, sizeof(buf), __VA_ARGS__) -#elif defined(PUGI__MSVC_CRT_VERSION) && PUGI__MSVC_CRT_VERSION >= 1400 -# define PUGI__SNPRINTF(buf, ...) _snprintf_s(buf, _countof(buf), _TRUNCATE, __VA_ARGS__) +# define PUGI_IMPL_SNPRINTF(buf, ...) snprintf(buf, sizeof(buf), __VA_ARGS__) +#elif defined(PUGI_IMPL_MSVC_CRT_VERSION) && PUGI_IMPL_MSVC_CRT_VERSION >= 1400 +# define PUGI_IMPL_SNPRINTF(buf, ...) _snprintf_s(buf, _countof(buf), _TRUNCATE, __VA_ARGS__) +#elif defined(__APPLE__) && __clang_major__ >= 14 // Xcode 14 marks sprintf as deprecated while still using C++98 by default +# define PUGI_IMPL_SNPRINTF(buf, fmt, arg1, arg2) snprintf(buf, sizeof(buf), fmt, arg1, arg2) #else -# define PUGI__SNPRINTF sprintf +# define PUGI_IMPL_SNPRINTF sprintf #endif // We put implementation details into an anonymous namespace in source mode, but have to keep it in non-anonymous namespace in header-only mode to prevent binary bloat. #ifdef PUGIXML_HEADER_ONLY -# define PUGI__NS_BEGIN namespace pugi { namespace impl { -# define PUGI__NS_END } } -# define PUGI__FN inline -# define PUGI__FN_NO_INLINE inline +# define PUGI_IMPL_NS_BEGIN namespace pugi { namespace impl { +# define PUGI_IMPL_NS_END } } +# define PUGI_IMPL_FN inline +# define PUGI_IMPL_FN_NO_INLINE inline #else # if defined(_MSC_VER) && _MSC_VER < 1300 // MSVC6 seems to have an amusing bug with anonymous namespaces inside namespaces -# define PUGI__NS_BEGIN namespace pugi { namespace impl { -# define PUGI__NS_END } } +# define PUGI_IMPL_NS_BEGIN namespace pugi { namespace impl { +# define PUGI_IMPL_NS_END } } # else -# define PUGI__NS_BEGIN namespace pugi { namespace impl { namespace { -# define PUGI__NS_END } } } +# define PUGI_IMPL_NS_BEGIN namespace pugi { namespace impl { namespace { +# define PUGI_IMPL_NS_END } } } # endif -# define PUGI__FN -# define PUGI__FN_NO_INLINE PUGI__NO_INLINE +# define PUGI_IMPL_FN +# define PUGI_IMPL_FN_NO_INLINE PUGI_IMPL_NO_INLINE #endif // uintptr_t @@ -182,13 +200,13 @@ namespace pugi #endif // Memory allocation -PUGI__NS_BEGIN - PUGI__FN void* default_allocate(size_t size) +PUGI_IMPL_NS_BEGIN + PUGI_IMPL_FN void* default_allocate(size_t size) { return malloc(size); } - PUGI__FN void default_deallocate(void* ptr) + PUGI_IMPL_FN void default_deallocate(void* ptr) { free(ptr); } @@ -206,12 +224,12 @@ PUGI__NS_BEGIN template deallocation_function xml_memory_management_function_storage::deallocate = default_deallocate; typedef xml_memory_management_function_storage xml_memory; -PUGI__NS_END +PUGI_IMPL_NS_END // String utilities -PUGI__NS_BEGIN +PUGI_IMPL_NS_BEGIN // Get string length - PUGI__FN size_t strlength(const char_t* s) + PUGI_IMPL_FN size_t strlength(const char_t* s) { assert(s); @@ -223,7 +241,7 @@ PUGI__NS_BEGIN } // Compare two strings - PUGI__FN bool strequal(const char_t* src, const char_t* dst) + PUGI_IMPL_FN bool strequal(const char_t* src, const char_t* dst) { assert(src && dst); @@ -235,7 +253,7 @@ PUGI__NS_BEGIN } // Compare lhs with [rhs_begin, rhs_end) - PUGI__FN bool strequalrange(const char_t* lhs, const char_t* rhs, size_t count) + PUGI_IMPL_FN bool strequalrange(const char_t* lhs, const char_t* rhs, size_t count) { for (size_t i = 0; i < count; ++i) if (lhs[i] != rhs[i]) @@ -245,7 +263,7 @@ PUGI__NS_BEGIN } // Get length of wide string, even if CRT lacks wide character support - PUGI__FN size_t strlength_wide(const wchar_t* s) + PUGI_IMPL_FN size_t strlength_wide(const wchar_t* s) { assert(s); @@ -257,10 +275,10 @@ PUGI__NS_BEGIN return static_cast(end - s); #endif } -PUGI__NS_END +PUGI_IMPL_NS_END // auto_ptr-like object for exception recovery -PUGI__NS_BEGIN +PUGI_IMPL_NS_BEGIN template struct auto_deleter { typedef void (*D)(T*); @@ -280,18 +298,18 @@ PUGI__NS_BEGIN T* release() { T* result = data; - data = 0; + data = NULL; return result; } }; -PUGI__NS_END +PUGI_IMPL_NS_END #ifdef PUGIXML_COMPACT -PUGI__NS_BEGIN +PUGI_IMPL_NS_BEGIN class compact_hash_table { public: - compact_hash_table(): _items(0), _capacity(0), _count(0) + compact_hash_table(): _items(NULL), _capacity(0), _count(0) { } @@ -300,7 +318,7 @@ PUGI__NS_BEGIN if (_items) { xml_memory::deallocate(_items); - _items = 0; + _items = NULL; _capacity = 0; _count = 0; } @@ -308,11 +326,11 @@ PUGI__NS_BEGIN void* find(const void* key) { - if (_capacity == 0) return 0; + if (_capacity == 0) return NULL; item_t* item = get_item(key); assert(item); - assert(item->key == key || (item->key == 0 && item->value == 0)); + assert(item->key == key || (item->key == NULL && item->value == NULL)); return item->value; } @@ -324,7 +342,7 @@ PUGI__NS_BEGIN item_t* item = get_item(key); assert(item); - if (item->key == 0) + if (item->key == NULL) { _count++; item->key = key; @@ -367,7 +385,7 @@ PUGI__NS_BEGIN { item_t& probe_item = _items[bucket]; - if (probe_item.key == key || probe_item.key == 0) + if (probe_item.key == key || probe_item.key == NULL) return &probe_item; // hash collision, quadratic probing @@ -375,10 +393,10 @@ PUGI__NS_BEGIN } assert(false && "Hash table is full"); // unreachable - return 0; + return NULL; } - static PUGI__UNSIGNED_OVERFLOW unsigned int hash(const void* key) + static PUGI_IMPL_UNSIGNED_OVERFLOW unsigned int hash(const void* key) { unsigned int h = static_cast(reinterpret_cast(key) & 0xffffffff); @@ -393,7 +411,7 @@ PUGI__NS_BEGIN } }; - PUGI__FN_NO_INLINE bool compact_hash_table::rehash(size_t count) + PUGI_IMPL_FN_NO_INLINE bool compact_hash_table::rehash(size_t count) { size_t capacity = 32; while (count >= capacity - capacity / 4) @@ -423,10 +441,10 @@ PUGI__NS_BEGIN return true; } -PUGI__NS_END +PUGI_IMPL_NS_END #endif -PUGI__NS_BEGIN +PUGI_IMPL_NS_BEGIN #ifdef PUGIXML_COMPACT static const uintptr_t xml_memory_block_alignment = 4; #else @@ -444,16 +462,16 @@ PUGI__NS_BEGIN static const uintptr_t xml_memory_page_value_allocated_or_shared_mask = xml_memory_page_value_allocated_mask | xml_memory_page_contents_shared_mask; #ifdef PUGIXML_COMPACT - #define PUGI__GETHEADER_IMPL(object, page, flags) // unused - #define PUGI__GETPAGE_IMPL(header) (header).get_page() + #define PUGI_IMPL_GETHEADER_IMPL(object, page, flags) // unused + #define PUGI_IMPL_GETPAGE_IMPL(header) (header).get_page() #else - #define PUGI__GETHEADER_IMPL(object, page, flags) (((reinterpret_cast(object) - reinterpret_cast(page)) << 8) | (flags)) + #define PUGI_IMPL_GETHEADER_IMPL(object, page, flags) (((reinterpret_cast(object) - reinterpret_cast(page)) << 8) | (flags)) // this macro casts pointers through void* to avoid 'cast increases required alignment of target type' warnings - #define PUGI__GETPAGE_IMPL(header) static_cast(const_cast(static_cast(reinterpret_cast(&header) - (header >> 8)))) + #define PUGI_IMPL_GETPAGE_IMPL(header) static_cast(const_cast(static_cast(reinterpret_cast(&header) - (header >> 8)))) #endif - #define PUGI__GETPAGE(n) PUGI__GETPAGE_IMPL((n)->header) - #define PUGI__NODETYPE(n) static_cast((n)->header & impl::xml_memory_page_type_mask) + #define PUGI_IMPL_GETPAGE(n) PUGI_IMPL_GETPAGE_IMPL((n)->header) + #define PUGI_IMPL_NODETYPE(n) static_cast((n)->header & impl::xml_memory_page_type_mask) struct xml_allocator; @@ -463,16 +481,16 @@ PUGI__NS_BEGIN { xml_memory_page* result = static_cast(memory); - result->allocator = 0; - result->prev = 0; - result->next = 0; + result->allocator = NULL; + result->prev = NULL; + result->next = NULL; result->busy_size = 0; result->freed_size = 0; #ifdef PUGIXML_COMPACT - result->compact_string_base = 0; - result->compact_shared_parent = 0; - result->compact_page_marker = 0; + result->compact_string_base = NULL; + result->compact_shared_parent = NULL; + result->compact_page_marker = NULL; #endif return result; @@ -512,7 +530,7 @@ PUGI__NS_BEGIN xml_allocator(xml_memory_page* root): _root(root), _busy_size(root->busy_size) { #ifdef PUGIXML_COMPACT - _hash = 0; + _hash = NULL; #endif } @@ -522,7 +540,7 @@ PUGI__NS_BEGIN // allocate block with some alignment, leaving memory for worst-case padding void* memory = xml_memory::allocate(size); - if (!memory) return 0; + if (!memory) return NULL; // prepare page structure xml_memory_page* page = xml_memory_page::construct(memory); @@ -543,7 +561,7 @@ PUGI__NS_BEGIN void* allocate_memory(size_t size, xml_memory_page*& out_page) { - if (PUGI__UNLIKELY(_busy_size + size > xml_memory_page_size)) + if (PUGI_IMPL_UNLIKELY(_busy_size + size > xml_memory_page_size)) return allocate_memory_oob(size, out_page); void* buf = reinterpret_cast(_root) + sizeof(xml_memory_page) + _busy_size; @@ -559,12 +577,12 @@ PUGI__NS_BEGIN void* allocate_object(size_t size, xml_memory_page*& out_page) { void* result = allocate_memory(size + sizeof(uint32_t), out_page); - if (!result) return 0; + if (!result) return NULL; // adjust for marker ptrdiff_t offset = static_cast(result) - reinterpret_cast(out_page->compact_page_marker); - if (PUGI__UNLIKELY(static_cast(offset) >= 256 * xml_memory_block_alignment)) + if (PUGI_IMPL_UNLIKELY(static_cast(offset) >= 256 * xml_memory_block_alignment)) { // insert new marker uint32_t* marker = static_cast(result); @@ -605,7 +623,7 @@ PUGI__NS_BEGIN if (page->freed_size == page->busy_size) { - if (page->next == 0) + if (page->next == NULL) { assert(_root == page); @@ -615,9 +633,9 @@ PUGI__NS_BEGIN #ifdef PUGIXML_COMPACT // reset compact state to maximize efficiency - page->compact_string_base = 0; - page->compact_shared_parent = 0; - page->compact_page_marker = 0; + page->compact_string_base = NULL; + page->compact_shared_parent = NULL; + page->compact_page_marker = NULL; #endif _busy_size = 0; @@ -641,7 +659,7 @@ PUGI__NS_BEGIN { static const size_t max_encoded_offset = (1 << 16) * xml_memory_block_alignment; - PUGI__STATIC_ASSERT(xml_memory_page_size <= max_encoded_offset); + PUGI_IMPL_STATIC_ASSERT(xml_memory_page_size <= max_encoded_offset); // allocate memory for string and header block size_t size = sizeof(xml_memory_string_header) + length * sizeof(char_t); @@ -652,7 +670,7 @@ PUGI__NS_BEGIN xml_memory_page* page; xml_memory_string_header* header = static_cast(allocate_memory(full_size, page)); - if (!header) return 0; + if (!header) return NULL; // setup header ptrdiff_t page_offset = reinterpret_cast(header) - reinterpret_cast(page) - sizeof(xml_memory_page); @@ -707,14 +725,14 @@ PUGI__NS_BEGIN #endif }; - PUGI__FN_NO_INLINE void* xml_allocator::allocate_memory_oob(size_t size, xml_memory_page*& out_page) + PUGI_IMPL_FN_NO_INLINE void* xml_allocator::allocate_memory_oob(size_t size, xml_memory_page*& out_page) { const size_t large_allocation_threshold = xml_memory_page_size / 4; xml_memory_page* page = allocate_page(size <= large_allocation_threshold ? xml_memory_page_size : size); out_page = page; - if (!page) return 0; + if (!page) return NULL; if (size <= large_allocation_threshold) { @@ -744,10 +762,10 @@ PUGI__NS_BEGIN return reinterpret_cast(page) + sizeof(xml_memory_page); } -PUGI__NS_END +PUGI_IMPL_NS_END #ifdef PUGIXML_COMPACT -PUGI__NS_BEGIN +PUGI_IMPL_NS_BEGIN static const uintptr_t compact_alignment_log2 = 2; static const uintptr_t compact_alignment = 1 << compact_alignment_log2; @@ -756,7 +774,7 @@ PUGI__NS_BEGIN public: compact_header(xml_memory_page* page, unsigned int flags) { - PUGI__STATIC_ASSERT(xml_memory_block_alignment == compact_alignment); + PUGI_IMPL_STATIC_ASSERT(xml_memory_block_alignment == compact_alignment); ptrdiff_t offset = (reinterpret_cast(this) - reinterpret_cast(page->compact_page_marker)); assert(offset % compact_alignment == 0 && static_cast(offset) < 256 * compact_alignment); @@ -794,19 +812,19 @@ PUGI__NS_BEGIN unsigned char _flags; }; - PUGI__FN xml_memory_page* compact_get_page(const void* object, int header_offset) + PUGI_IMPL_FN xml_memory_page* compact_get_page(const void* object, int header_offset) { const compact_header* header = reinterpret_cast(static_cast(object) - header_offset); return header->get_page(); } - template PUGI__FN_NO_INLINE T* compact_get_value(const void* object) + template PUGI_IMPL_FN_NO_INLINE T* compact_get_value(const void* object) { return static_cast(compact_get_page(object, header_offset)->allocator->_hash->find(object)); } - template PUGI__FN_NO_INLINE void compact_set_value(const void* object, T* value) + template PUGI_IMPL_FN_NO_INLINE void compact_set_value(const void* object, T* value) { compact_get_page(object, header_offset)->allocator->_hash->insert(object, value); } @@ -861,7 +879,7 @@ PUGI__NS_BEGIN return compact_get_value(this); } else - return 0; + return NULL; } T* operator->() const @@ -904,7 +922,7 @@ PUGI__NS_BEGIN { xml_memory_page* page = compact_get_page(this, header_offset); - if (PUGI__UNLIKELY(page->compact_shared_parent == 0)) + if (PUGI_IMPL_UNLIKELY(page->compact_shared_parent == NULL)) page->compact_shared_parent = value; if (page->compact_shared_parent == value) @@ -941,7 +959,7 @@ PUGI__NS_BEGIN return compact_get_value(this); } else - return 0; + return NULL; } T* operator->() const @@ -971,7 +989,7 @@ PUGI__NS_BEGIN { xml_memory_page* page = compact_get_page(this, header_offset); - if (PUGI__UNLIKELY(page->compact_string_base == 0)) + if (PUGI_IMPL_UNLIKELY(page->compact_string_base == NULL)) page->compact_string_base = value; ptrdiff_t offset = value - page->compact_string_base; @@ -1037,13 +1055,13 @@ PUGI__NS_BEGIN } } else - return 0; + return NULL; } private: unsigned char _data; }; -PUGI__NS_END +PUGI_IMPL_NS_END #endif #ifdef PUGIXML_COMPACT @@ -1053,7 +1071,7 @@ namespace pugi { xml_attribute_struct(impl::xml_memory_page* page): header(page, 0), namevalue_base(0) { - PUGI__STATIC_ASSERT(sizeof(xml_attribute_struct) == 8); + PUGI_IMPL_STATIC_ASSERT(sizeof(xml_attribute_struct) == 8); } impl::compact_header header; @@ -1071,7 +1089,7 @@ namespace pugi { xml_node_struct(impl::xml_memory_page* page, xml_node_type type): header(page, type), namevalue_base(0) { - PUGI__STATIC_ASSERT(sizeof(xml_node_struct) == 12); + PUGI_IMPL_STATIC_ASSERT(sizeof(xml_node_struct) == 12); } impl::compact_header header; @@ -1096,9 +1114,9 @@ namespace pugi { struct xml_attribute_struct { - xml_attribute_struct(impl::xml_memory_page* page): name(0), value(0), prev_attribute_c(0), next_attribute(0) + xml_attribute_struct(impl::xml_memory_page* page): name(NULL), value(NULL), prev_attribute_c(NULL), next_attribute(NULL) { - header = PUGI__GETHEADER_IMPL(this, page, 0); + header = PUGI_IMPL_GETHEADER_IMPL(this, page, 0); } uintptr_t header; @@ -1112,9 +1130,9 @@ namespace pugi struct xml_node_struct { - xml_node_struct(impl::xml_memory_page* page, xml_node_type type): name(0), value(0), parent(0), first_child(0), prev_sibling_c(0), next_sibling(0), first_attribute(0) + xml_node_struct(impl::xml_memory_page* page, xml_node_type type): name(NULL), value(NULL), parent(NULL), first_child(NULL), prev_sibling_c(NULL), next_sibling(NULL), first_attribute(NULL) { - header = PUGI__GETHEADER_IMPL(this, page, type); + header = PUGI_IMPL_GETHEADER_IMPL(this, page, type); } uintptr_t header; @@ -1134,7 +1152,7 @@ namespace pugi } #endif -PUGI__NS_BEGIN +PUGI_IMPL_NS_BEGIN struct xml_extra_buffer { char_t* buffer; @@ -1143,7 +1161,7 @@ PUGI__NS_BEGIN struct xml_document_struct: public xml_node_struct, public xml_allocator { - xml_document_struct(xml_memory_page* page): xml_node_struct(page, node_document), xml_allocator(page), buffer(0), extra_buffers(0) + xml_document_struct(xml_memory_page* page): xml_node_struct(page, node_document), xml_allocator(page), buffer(NULL), extra_buffers(NULL) { } @@ -1160,24 +1178,24 @@ PUGI__NS_BEGIN { assert(object); - return *PUGI__GETPAGE(object)->allocator; + return *PUGI_IMPL_GETPAGE(object)->allocator; } template inline xml_document_struct& get_document(const Object* object) { assert(object); - return *static_cast(PUGI__GETPAGE(object)->allocator); + return *static_cast(PUGI_IMPL_GETPAGE(object)->allocator); } -PUGI__NS_END +PUGI_IMPL_NS_END // Low-level DOM operations -PUGI__NS_BEGIN +PUGI_IMPL_NS_BEGIN inline xml_attribute_struct* allocate_attribute(xml_allocator& alloc) { xml_memory_page* page; void* memory = alloc.allocate_object(sizeof(xml_attribute_struct), page); - if (!memory) return 0; + if (!memory) return NULL; return new (memory) xml_attribute_struct(page); } @@ -1186,7 +1204,7 @@ PUGI__NS_BEGIN { xml_memory_page* page; void* memory = alloc.allocate_object(sizeof(xml_node_struct), page); - if (!memory) return 0; + if (!memory) return NULL; return new (memory) xml_node_struct(page, type); } @@ -1199,7 +1217,7 @@ PUGI__NS_BEGIN if (a->header & impl::xml_memory_page_value_allocated_mask) alloc.deallocate_string(a->value); - alloc.deallocate_memory(a, sizeof(xml_attribute_struct), PUGI__GETPAGE(a)); + alloc.deallocate_memory(a, sizeof(xml_attribute_struct), PUGI_IMPL_GETPAGE(a)); } inline void destroy_node(xml_node_struct* n, xml_allocator& alloc) @@ -1228,7 +1246,7 @@ PUGI__NS_BEGIN child = next; } - alloc.deallocate_memory(n, sizeof(xml_node_struct), PUGI__GETPAGE(n)); + alloc.deallocate_memory(n, sizeof(xml_node_struct), PUGI_IMPL_GETPAGE(n)); } inline void append_node(xml_node_struct* child, xml_node_struct* node) @@ -1325,9 +1343,9 @@ PUGI__NS_BEGIN else parent->first_child = next; - node->parent = 0; - node->prev_sibling_c = 0; - node->next_sibling = 0; + node->parent = NULL; + node->prev_sibling_c = NULL; + node->next_sibling = NULL; } inline void append_attribute(xml_attribute_struct* attr, xml_node_struct* node) @@ -1408,37 +1426,37 @@ PUGI__NS_BEGIN else node->first_attribute = next; - attr->prev_attribute_c = 0; - attr->next_attribute = 0; + attr->prev_attribute_c = NULL; + attr->next_attribute = NULL; } - PUGI__FN_NO_INLINE xml_node_struct* append_new_node(xml_node_struct* node, xml_allocator& alloc, xml_node_type type = node_element) + PUGI_IMPL_FN_NO_INLINE xml_node_struct* append_new_node(xml_node_struct* node, xml_allocator& alloc, xml_node_type type = node_element) { - if (!alloc.reserve()) return 0; + if (!alloc.reserve()) return NULL; xml_node_struct* child = allocate_node(alloc, type); - if (!child) return 0; + if (!child) return NULL; append_node(child, node); return child; } - PUGI__FN_NO_INLINE xml_attribute_struct* append_new_attribute(xml_node_struct* node, xml_allocator& alloc) + PUGI_IMPL_FN_NO_INLINE xml_attribute_struct* append_new_attribute(xml_node_struct* node, xml_allocator& alloc) { - if (!alloc.reserve()) return 0; + if (!alloc.reserve()) return NULL; xml_attribute_struct* attr = allocate_attribute(alloc); - if (!attr) return 0; + if (!attr) return NULL; append_attribute(attr, node); return attr; } -PUGI__NS_END +PUGI_IMPL_NS_END // Helper classes for code generation -PUGI__NS_BEGIN +PUGI_IMPL_NS_BEGIN struct opt_false { enum { value = 0 }; @@ -1448,10 +1466,10 @@ PUGI__NS_BEGIN { enum { value = 1 }; }; -PUGI__NS_END +PUGI_IMPL_NS_END // Unicode utilities -PUGI__NS_BEGIN +PUGI_IMPL_NS_BEGIN inline uint16_t endian_swap(uint16_t value) { return static_cast(((value & 0xff) << 8) | (value >> 8)); @@ -1556,8 +1574,8 @@ PUGI__NS_BEGIN static value_type high(value_type result, uint32_t ch) { - uint32_t msh = static_cast(ch - 0x10000) >> 10; - uint32_t lsh = static_cast(ch - 0x10000) & 0x3ff; + uint32_t msh = (ch - 0x10000U) >> 10; + uint32_t lsh = (ch - 0x10000U) & 0x3ff; result[0] = static_cast(0xD800 + msh); result[1] = static_cast(0xDC00 + lsh); @@ -1833,15 +1851,15 @@ PUGI__NS_BEGIN }; #ifdef PUGIXML_WCHAR_MODE - PUGI__FN void convert_wchar_endian_swap(wchar_t* result, const wchar_t* data, size_t length) + PUGI_IMPL_FN void convert_wchar_endian_swap(wchar_t* result, const wchar_t* data, size_t length) { for (size_t i = 0; i < length; ++i) result[i] = static_cast(endian_swap(static_cast::type>(data[i]))); } #endif -PUGI__NS_END +PUGI_IMPL_NS_END -PUGI__NS_BEGIN +PUGI_IMPL_NS_BEGIN enum chartype_t { ct_parse_pcdata = 1, // \0, &, \r, < @@ -1907,24 +1925,24 @@ PUGI__NS_BEGIN }; #ifdef PUGIXML_WCHAR_MODE - #define PUGI__IS_CHARTYPE_IMPL(c, ct, table) ((static_cast(c) < 128 ? table[static_cast(c)] : table[128]) & (ct)) + #define PUGI_IMPL_IS_CHARTYPE_IMPL(c, ct, table) ((static_cast(c) < 128 ? table[static_cast(c)] : table[128]) & (ct)) #else - #define PUGI__IS_CHARTYPE_IMPL(c, ct, table) (table[static_cast(c)] & (ct)) + #define PUGI_IMPL_IS_CHARTYPE_IMPL(c, ct, table) (table[static_cast(c)] & (ct)) #endif - #define PUGI__IS_CHARTYPE(c, ct) PUGI__IS_CHARTYPE_IMPL(c, ct, chartype_table) - #define PUGI__IS_CHARTYPEX(c, ct) PUGI__IS_CHARTYPE_IMPL(c, ct, chartypex_table) + #define PUGI_IMPL_IS_CHARTYPE(c, ct) PUGI_IMPL_IS_CHARTYPE_IMPL(c, ct, chartype_table) + #define PUGI_IMPL_IS_CHARTYPEX(c, ct) PUGI_IMPL_IS_CHARTYPE_IMPL(c, ct, chartypex_table) - PUGI__FN bool is_little_endian() + PUGI_IMPL_FN bool is_little_endian() { unsigned int ui = 1; return *reinterpret_cast(&ui) == 1; } - PUGI__FN xml_encoding get_wchar_encoding() + PUGI_IMPL_FN xml_encoding get_wchar_encoding() { - PUGI__STATIC_ASSERT(sizeof(wchar_t) == 2 || sizeof(wchar_t) == 4); + PUGI_IMPL_STATIC_ASSERT(sizeof(wchar_t) == 2 || sizeof(wchar_t) == 4); if (sizeof(wchar_t) == 2) return is_little_endian() ? encoding_utf16_le : encoding_utf16_be; @@ -1932,13 +1950,13 @@ PUGI__NS_BEGIN return is_little_endian() ? encoding_utf32_le : encoding_utf32_be; } - PUGI__FN bool parse_declaration_encoding(const uint8_t* data, size_t size, const uint8_t*& out_encoding, size_t& out_length) + PUGI_IMPL_FN bool parse_declaration_encoding(const uint8_t* data, size_t size, const uint8_t*& out_encoding, size_t& out_length) { - #define PUGI__SCANCHAR(ch) { if (offset >= size || data[offset] != ch) return false; offset++; } - #define PUGI__SCANCHARTYPE(ct) { while (offset < size && PUGI__IS_CHARTYPE(data[offset], ct)) offset++; } + #define PUGI_IMPL_SCANCHAR(ch) { if (offset >= size || data[offset] != ch) return false; offset++; } + #define PUGI_IMPL_SCANCHARTYPE(ct) { while (offset < size && PUGI_IMPL_IS_CHARTYPE(data[offset], ct)) offset++; } // check if we have a non-empty XML declaration - if (size < 6 || !((data[0] == '<') & (data[1] == '?') & (data[2] == 'x') & (data[3] == 'm') & (data[4] == 'l') && PUGI__IS_CHARTYPE(data[5], ct_space))) + if (size < 6 || !((data[0] == '<') & (data[1] == '?') & (data[2] == 'x') & (data[3] == 'm') & (data[4] == 'l') && PUGI_IMPL_IS_CHARTYPE(data[5], ct_space))) return false; // scan XML declaration until the encoding field @@ -1953,28 +1971,28 @@ PUGI__NS_BEGIN size_t offset = i; // encoding follows the version field which can't contain 'en' so this has to be the encoding if XML is well formed - PUGI__SCANCHAR('e'); PUGI__SCANCHAR('n'); PUGI__SCANCHAR('c'); PUGI__SCANCHAR('o'); - PUGI__SCANCHAR('d'); PUGI__SCANCHAR('i'); PUGI__SCANCHAR('n'); PUGI__SCANCHAR('g'); + PUGI_IMPL_SCANCHAR('e'); PUGI_IMPL_SCANCHAR('n'); PUGI_IMPL_SCANCHAR('c'); PUGI_IMPL_SCANCHAR('o'); + PUGI_IMPL_SCANCHAR('d'); PUGI_IMPL_SCANCHAR('i'); PUGI_IMPL_SCANCHAR('n'); PUGI_IMPL_SCANCHAR('g'); // S? = S? - PUGI__SCANCHARTYPE(ct_space); - PUGI__SCANCHAR('='); - PUGI__SCANCHARTYPE(ct_space); + PUGI_IMPL_SCANCHARTYPE(ct_space); + PUGI_IMPL_SCANCHAR('='); + PUGI_IMPL_SCANCHARTYPE(ct_space); // the only two valid delimiters are ' and " uint8_t delimiter = (offset < size && data[offset] == '"') ? '"' : '\''; - PUGI__SCANCHAR(delimiter); + PUGI_IMPL_SCANCHAR(delimiter); size_t start = offset; out_encoding = data + offset; - PUGI__SCANCHARTYPE(ct_symbol); + PUGI_IMPL_SCANCHARTYPE(ct_symbol); out_length = offset - start; - PUGI__SCANCHAR(delimiter); + PUGI_IMPL_SCANCHAR(delimiter); return true; } @@ -1982,11 +2000,11 @@ PUGI__NS_BEGIN return false; - #undef PUGI__SCANCHAR - #undef PUGI__SCANCHARTYPE + #undef PUGI_IMPL_SCANCHAR + #undef PUGI_IMPL_SCANCHARTYPE } - PUGI__FN xml_encoding guess_buffer_encoding(const uint8_t* data, size_t size) + PUGI_IMPL_FN xml_encoding guess_buffer_encoding(const uint8_t* data, size_t size) { // skip encoding autodetection if input buffer is too small if (size < 4) return encoding_utf8; @@ -2011,7 +2029,7 @@ PUGI__NS_BEGIN if (d0 == 0x3c && d1 == 0) return encoding_utf16_le; // no known BOM detected; parse declaration - const uint8_t* enc = 0; + const uint8_t* enc = NULL; size_t enc_length = 0; if (d0 == 0x3c && d1 == 0x3f && d2 == 0x78 && d3 == 0x6d && parse_declaration_encoding(data, size, enc, enc_length)) @@ -2034,7 +2052,7 @@ PUGI__NS_BEGIN return encoding_utf8; } - PUGI__FN xml_encoding get_buffer_encoding(xml_encoding encoding, const void* contents, size_t size) + PUGI_IMPL_FN xml_encoding get_buffer_encoding(xml_encoding encoding, const void* contents, size_t size) { // replace wchar encoding with utf implementation if (encoding == encoding_wchar) return get_wchar_encoding(); @@ -2054,7 +2072,7 @@ PUGI__NS_BEGIN return guess_buffer_encoding(data, size); } - PUGI__FN bool get_mutable_buffer(char_t*& out_buffer, size_t& out_length, const void* contents, size_t size, bool is_mutable) + PUGI_IMPL_FN bool get_mutable_buffer(char_t*& out_buffer, size_t& out_length, const void* contents, size_t size, bool is_mutable) { size_t length = size / sizeof(char_t); @@ -2083,13 +2101,13 @@ PUGI__NS_BEGIN } #ifdef PUGIXML_WCHAR_MODE - PUGI__FN bool need_endian_swap_utf(xml_encoding le, xml_encoding re) + PUGI_IMPL_FN bool need_endian_swap_utf(xml_encoding le, xml_encoding re) { return (le == encoding_utf16_be && re == encoding_utf16_le) || (le == encoding_utf16_le && re == encoding_utf16_be) || (le == encoding_utf32_be && re == encoding_utf32_le) || (le == encoding_utf32_le && re == encoding_utf32_be); } - PUGI__FN bool convert_buffer_endian_swap(char_t*& out_buffer, size_t& out_length, const void* contents, size_t size, bool is_mutable) + PUGI_IMPL_FN bool convert_buffer_endian_swap(char_t*& out_buffer, size_t& out_length, const void* contents, size_t size, bool is_mutable) { const char_t* data = static_cast(contents); size_t length = size / sizeof(char_t); @@ -2118,7 +2136,7 @@ PUGI__NS_BEGIN return true; } - template PUGI__FN bool convert_buffer_generic(char_t*& out_buffer, size_t& out_length, const void* contents, size_t size, D) + template PUGI_IMPL_FN bool convert_buffer_generic(char_t*& out_buffer, size_t& out_length, const void* contents, size_t size, D) { const typename D::type* data = static_cast(contents); size_t data_length = size / sizeof(typename D::type); @@ -2143,7 +2161,7 @@ PUGI__NS_BEGIN return true; } - PUGI__FN bool convert_buffer(char_t*& out_buffer, size_t& out_length, xml_encoding encoding, const void* contents, size_t size, bool is_mutable) + PUGI_IMPL_FN bool convert_buffer(char_t*& out_buffer, size_t& out_length, xml_encoding encoding, const void* contents, size_t size, bool is_mutable) { // get native encoding xml_encoding wchar_encoding = get_wchar_encoding(); @@ -2188,7 +2206,7 @@ PUGI__NS_BEGIN return false; } #else - template PUGI__FN bool convert_buffer_generic(char_t*& out_buffer, size_t& out_length, const void* contents, size_t size, D) + template PUGI_IMPL_FN bool convert_buffer_generic(char_t*& out_buffer, size_t& out_length, const void* contents, size_t size, D) { const typename D::type* data = static_cast(contents); size_t data_length = size / sizeof(typename D::type); @@ -2213,7 +2231,7 @@ PUGI__NS_BEGIN return true; } - PUGI__FN size_t get_latin1_7bit_prefix_length(const uint8_t* data, size_t size) + PUGI_IMPL_FN size_t get_latin1_7bit_prefix_length(const uint8_t* data, size_t size) { for (size_t i = 0; i < size; ++i) if (data[i] > 127) @@ -2222,7 +2240,7 @@ PUGI__NS_BEGIN return size; } - PUGI__FN bool convert_buffer_latin1(char_t*& out_buffer, size_t& out_length, const void* contents, size_t size, bool is_mutable) + PUGI_IMPL_FN bool convert_buffer_latin1(char_t*& out_buffer, size_t& out_length, const void* contents, size_t size, bool is_mutable) { const uint8_t* data = static_cast(contents); size_t data_length = size; @@ -2259,7 +2277,7 @@ PUGI__NS_BEGIN return true; } - PUGI__FN bool convert_buffer(char_t*& out_buffer, size_t& out_length, xml_encoding encoding, const void* contents, size_t size, bool is_mutable) + PUGI_IMPL_FN bool convert_buffer(char_t*& out_buffer, size_t& out_length, xml_encoding encoding, const void* contents, size_t size, bool is_mutable) { // fast path: no conversion required if (encoding == encoding_utf8) @@ -2294,13 +2312,13 @@ PUGI__NS_BEGIN } #endif - PUGI__FN size_t as_utf8_begin(const wchar_t* str, size_t length) + PUGI_IMPL_FN size_t as_utf8_begin(const wchar_t* str, size_t length) { // get length in utf8 characters return wchar_decoder::process(str, length, 0, utf8_counter()); } - PUGI__FN void as_utf8_end(char* buffer, size_t size, const wchar_t* str, size_t length) + PUGI_IMPL_FN void as_utf8_end(char* buffer, size_t size, const wchar_t* str, size_t length) { // convert to utf8 uint8_t* begin = reinterpret_cast(buffer); @@ -2312,7 +2330,7 @@ PUGI__NS_BEGIN } #ifndef PUGIXML_NO_STL - PUGI__FN std::string as_utf8_impl(const wchar_t* str, size_t length) + PUGI_IMPL_FN std::string as_utf8_impl(const wchar_t* str, size_t length) { // first pass: get length in utf8 characters size_t size = as_utf8_begin(str, length); @@ -2327,7 +2345,7 @@ PUGI__NS_BEGIN return result; } - PUGI__FN std::basic_string as_wide_impl(const char* str, size_t size) + PUGI_IMPL_FN std::basic_string as_wide_impl(const char* str, size_t size) { const uint8_t* data = reinterpret_cast(str); @@ -2370,17 +2388,19 @@ PUGI__NS_BEGIN } template - PUGI__FN bool strcpy_insitu(String& dest, Header& header, uintptr_t header_mask, const char_t* source, size_t source_length) + PUGI_IMPL_FN bool strcpy_insitu(String& dest, Header& header, uintptr_t header_mask, const char_t* source, size_t source_length) { + assert((header & header_mask) == 0 || dest); // header bit indicates whether dest was previously allocated + if (source_length == 0) { // empty string and null pointer are equivalent, so just deallocate old memory - xml_allocator* alloc = PUGI__GETPAGE_IMPL(header)->allocator; + xml_allocator* alloc = PUGI_IMPL_GETPAGE_IMPL(header)->allocator; if (header & header_mask) alloc->deallocate_string(dest); // mark the string as not allocated - dest = 0; + dest = NULL; header &= ~header_mask; return true; @@ -2395,7 +2415,7 @@ PUGI__NS_BEGIN } else { - xml_allocator* alloc = PUGI__GETPAGE_IMPL(header)->allocator; + xml_allocator* alloc = PUGI_IMPL_GETPAGE_IMPL(header)->allocator; if (!alloc->reserve()) return false; @@ -2423,7 +2443,7 @@ PUGI__NS_BEGIN char_t* end; size_t size; - gap(): end(0), size(0) + gap(): end(NULL), size(0) { } @@ -2435,7 +2455,7 @@ PUGI__NS_BEGIN { // Move [old_gap_end, new_gap_start) to [old_gap_start, ...) assert(s >= end); - memmove(end - size, end, reinterpret_cast(s) - reinterpret_cast(end)); + memmove(end - size, end, (s - end) * sizeof(char_t)); } s += count; // end of current gap @@ -2452,7 +2472,7 @@ PUGI__NS_BEGIN { // Move [old_gap_end, current_pos) to [old_gap_start, ...) assert(s >= end); - memmove(end - size, end, reinterpret_cast(s) - reinterpret_cast(end)); + memmove(end - size, end, (s - end) * sizeof(char_t)); return s - size; } @@ -2460,7 +2480,7 @@ PUGI__NS_BEGIN } }; - PUGI__FN char_t* strconv_escape(char_t* s, gap& g) + PUGI_IMPL_FN char_t* strconv_escape(char_t* s, gap& g) { char_t* stre = s + 1; @@ -2601,25 +2621,25 @@ PUGI__NS_BEGIN } // Parser utilities - #define PUGI__ENDSWITH(c, e) ((c) == (e) || ((c) == 0 && endch == (e))) - #define PUGI__SKIPWS() { while (PUGI__IS_CHARTYPE(*s, ct_space)) ++s; } - #define PUGI__OPTSET(OPT) ( optmsk & (OPT) ) - #define PUGI__PUSHNODE(TYPE) { cursor = append_new_node(cursor, *alloc, TYPE); if (!cursor) PUGI__THROW_ERROR(status_out_of_memory, s); } - #define PUGI__POPNODE() { cursor = cursor->parent; } - #define PUGI__SCANFOR(X) { while (*s != 0 && !(X)) ++s; } - #define PUGI__SCANWHILE(X) { while (X) ++s; } - #define PUGI__SCANWHILE_UNROLL(X) { for (;;) { char_t ss = s[0]; if (PUGI__UNLIKELY(!(X))) { break; } ss = s[1]; if (PUGI__UNLIKELY(!(X))) { s += 1; break; } ss = s[2]; if (PUGI__UNLIKELY(!(X))) { s += 2; break; } ss = s[3]; if (PUGI__UNLIKELY(!(X))) { s += 3; break; } s += 4; } } - #define PUGI__ENDSEG() { ch = *s; *s = 0; ++s; } - #define PUGI__THROW_ERROR(err, m) return error_offset = m, error_status = err, static_cast(0) - #define PUGI__CHECK_ERROR(err, m) { if (*s == 0) PUGI__THROW_ERROR(err, m); } - - PUGI__FN char_t* strconv_comment(char_t* s, char_t endch) + #define PUGI_IMPL_ENDSWITH(c, e) ((c) == (e) || ((c) == 0 && endch == (e))) + #define PUGI_IMPL_SKIPWS() { while (PUGI_IMPL_IS_CHARTYPE(*s, ct_space)) ++s; } + #define PUGI_IMPL_OPTSET(OPT) ( optmsk & (OPT) ) + #define PUGI_IMPL_PUSHNODE(TYPE) { cursor = append_new_node(cursor, *alloc, TYPE); if (!cursor) PUGI_IMPL_THROW_ERROR(status_out_of_memory, s); } + #define PUGI_IMPL_POPNODE() { cursor = cursor->parent; } + #define PUGI_IMPL_SCANFOR(X) { while (*s != 0 && !(X)) ++s; } + #define PUGI_IMPL_SCANWHILE(X) { while (X) ++s; } + #define PUGI_IMPL_SCANWHILE_UNROLL(X) { for (;;) { char_t ss = s[0]; if (PUGI_IMPL_UNLIKELY(!(X))) { break; } ss = s[1]; if (PUGI_IMPL_UNLIKELY(!(X))) { s += 1; break; } ss = s[2]; if (PUGI_IMPL_UNLIKELY(!(X))) { s += 2; break; } ss = s[3]; if (PUGI_IMPL_UNLIKELY(!(X))) { s += 3; break; } s += 4; } } + #define PUGI_IMPL_ENDSEG() { ch = *s; *s = 0; ++s; } + #define PUGI_IMPL_THROW_ERROR(err, m) return error_offset = m, error_status = err, static_cast(NULL) + #define PUGI_IMPL_CHECK_ERROR(err, m) { if (*s == 0) PUGI_IMPL_THROW_ERROR(err, m); } + + PUGI_IMPL_FN char_t* strconv_comment(char_t* s, char_t endch) { gap g; while (true) { - PUGI__SCANWHILE_UNROLL(!PUGI__IS_CHARTYPE(ss, ct_parse_comment)); + PUGI_IMPL_SCANWHILE_UNROLL(!PUGI_IMPL_IS_CHARTYPE(ss, ct_parse_comment)); if (*s == '\r') // Either a single 0x0d or 0x0d 0x0a pair { @@ -2627,7 +2647,7 @@ PUGI__NS_BEGIN if (*s == '\n') g.push(s, 1); } - else if (s[0] == '-' && s[1] == '-' && PUGI__ENDSWITH(s[2], '>')) // comment ends here + else if (s[0] == '-' && s[1] == '-' && PUGI_IMPL_ENDSWITH(s[2], '>')) // comment ends here { *g.flush(s) = 0; @@ -2635,19 +2655,19 @@ PUGI__NS_BEGIN } else if (*s == 0) { - return 0; + return NULL; } else ++s; } } - PUGI__FN char_t* strconv_cdata(char_t* s, char_t endch) + PUGI_IMPL_FN char_t* strconv_cdata(char_t* s, char_t endch) { gap g; while (true) { - PUGI__SCANWHILE_UNROLL(!PUGI__IS_CHARTYPE(ss, ct_parse_cdata)); + PUGI_IMPL_SCANWHILE_UNROLL(!PUGI_IMPL_IS_CHARTYPE(ss, ct_parse_cdata)); if (*s == '\r') // Either a single 0x0d or 0x0d 0x0a pair { @@ -2655,7 +2675,7 @@ PUGI__NS_BEGIN if (*s == '\n') g.push(s, 1); } - else if (s[0] == ']' && s[1] == ']' && PUGI__ENDSWITH(s[2], '>')) // CDATA ends here + else if (s[0] == ']' && s[1] == ']' && PUGI_IMPL_ENDSWITH(s[2], '>')) // CDATA ends here { *g.flush(s) = 0; @@ -2663,7 +2683,7 @@ PUGI__NS_BEGIN } else if (*s == 0) { - return 0; + return NULL; } else ++s; } @@ -2681,14 +2701,14 @@ PUGI__NS_BEGIN while (true) { - PUGI__SCANWHILE_UNROLL(!PUGI__IS_CHARTYPE(ss, ct_parse_pcdata)); + PUGI_IMPL_SCANWHILE_UNROLL(!PUGI_IMPL_IS_CHARTYPE(ss, ct_parse_pcdata)); if (*s == '<') // PCDATA ends here { char_t* end = g.flush(s); if (opt_trim::value) - while (end > begin && PUGI__IS_CHARTYPE(end[-1], ct_space)) + while (end > begin && PUGI_IMPL_IS_CHARTYPE(end[-1], ct_space)) --end; *end = 0; @@ -2710,7 +2730,7 @@ PUGI__NS_BEGIN char_t* end = g.flush(s); if (opt_trim::value) - while (end > begin && PUGI__IS_CHARTYPE(end[-1], ct_space)) + while (end > begin && PUGI_IMPL_IS_CHARTYPE(end[-1], ct_space)) --end; *end = 0; @@ -2722,9 +2742,9 @@ PUGI__NS_BEGIN } }; - PUGI__FN strconv_pcdata_t get_strconv_pcdata(unsigned int optmask) + PUGI_IMPL_FN strconv_pcdata_t get_strconv_pcdata(unsigned int optmask) { - PUGI__STATIC_ASSERT(parse_escapes == 0x10 && parse_eol == 0x20 && parse_trim_pcdata == 0x0800); + PUGI_IMPL_STATIC_ASSERT(parse_escapes == 0x10 && parse_eol == 0x20 && parse_trim_pcdata == 0x0800); switch (((optmask >> 4) & 3) | ((optmask >> 9) & 4)) // get bitmask for flags (trim eol escapes); this simultaneously checks 3 options from assertion above { @@ -2736,7 +2756,7 @@ PUGI__NS_BEGIN case 5: return strconv_pcdata_impl::parse; case 6: return strconv_pcdata_impl::parse; case 7: return strconv_pcdata_impl::parse; - default: assert(false); return 0; // unreachable + default: assert(false); return NULL; // unreachable } } @@ -2749,37 +2769,37 @@ PUGI__NS_BEGIN gap g; // trim leading whitespaces - if (PUGI__IS_CHARTYPE(*s, ct_space)) + if (PUGI_IMPL_IS_CHARTYPE(*s, ct_space)) { char_t* str = s; do ++str; - while (PUGI__IS_CHARTYPE(*str, ct_space)); + while (PUGI_IMPL_IS_CHARTYPE(*str, ct_space)); g.push(s, str - s); } while (true) { - PUGI__SCANWHILE_UNROLL(!PUGI__IS_CHARTYPE(ss, ct_parse_attr_ws | ct_space)); + PUGI_IMPL_SCANWHILE_UNROLL(!PUGI_IMPL_IS_CHARTYPE(ss, ct_parse_attr_ws | ct_space)); if (*s == end_quote) { char_t* str = g.flush(s); do *str-- = 0; - while (PUGI__IS_CHARTYPE(*str, ct_space)); + while (PUGI_IMPL_IS_CHARTYPE(*str, ct_space)); return s + 1; } - else if (PUGI__IS_CHARTYPE(*s, ct_space)) + else if (PUGI_IMPL_IS_CHARTYPE(*s, ct_space)) { *s++ = ' '; - if (PUGI__IS_CHARTYPE(*s, ct_space)) + if (PUGI_IMPL_IS_CHARTYPE(*s, ct_space)) { char_t* str = s + 1; - while (PUGI__IS_CHARTYPE(*str, ct_space)) ++str; + while (PUGI_IMPL_IS_CHARTYPE(*str, ct_space)) ++str; g.push(s, str - s); } @@ -2790,7 +2810,7 @@ PUGI__NS_BEGIN } else if (!*s) { - return 0; + return NULL; } else ++s; } @@ -2802,7 +2822,7 @@ PUGI__NS_BEGIN while (true) { - PUGI__SCANWHILE_UNROLL(!PUGI__IS_CHARTYPE(ss, ct_parse_attr_ws)); + PUGI_IMPL_SCANWHILE_UNROLL(!PUGI_IMPL_IS_CHARTYPE(ss, ct_parse_attr_ws)); if (*s == end_quote) { @@ -2810,7 +2830,7 @@ PUGI__NS_BEGIN return s + 1; } - else if (PUGI__IS_CHARTYPE(*s, ct_space)) + else if (PUGI_IMPL_IS_CHARTYPE(*s, ct_space)) { if (*s == '\r') { @@ -2826,7 +2846,7 @@ PUGI__NS_BEGIN } else if (!*s) { - return 0; + return NULL; } else ++s; } @@ -2838,7 +2858,7 @@ PUGI__NS_BEGIN while (true) { - PUGI__SCANWHILE_UNROLL(!PUGI__IS_CHARTYPE(ss, ct_parse_attr)); + PUGI_IMPL_SCANWHILE_UNROLL(!PUGI_IMPL_IS_CHARTYPE(ss, ct_parse_attr)); if (*s == end_quote) { @@ -2858,7 +2878,7 @@ PUGI__NS_BEGIN } else if (!*s) { - return 0; + return NULL; } else ++s; } @@ -2870,7 +2890,7 @@ PUGI__NS_BEGIN while (true) { - PUGI__SCANWHILE_UNROLL(!PUGI__IS_CHARTYPE(ss, ct_parse_attr)); + PUGI_IMPL_SCANWHILE_UNROLL(!PUGI_IMPL_IS_CHARTYPE(ss, ct_parse_attr)); if (*s == end_quote) { @@ -2884,16 +2904,16 @@ PUGI__NS_BEGIN } else if (!*s) { - return 0; + return NULL; } else ++s; } } }; - PUGI__FN strconv_attribute_t get_strconv_attribute(unsigned int optmask) + PUGI_IMPL_FN strconv_attribute_t get_strconv_attribute(unsigned int optmask) { - PUGI__STATIC_ASSERT(parse_escapes == 0x10 && parse_eol == 0x20 && parse_wconv_attribute == 0x40 && parse_wnorm_attribute == 0x80); + PUGI_IMPL_STATIC_ASSERT(parse_escapes == 0x10 && parse_eol == 0x20 && parse_wconv_attribute == 0x40 && parse_wnorm_attribute == 0x80); switch ((optmask >> 4) & 15) // get bitmask for flags (wnorm wconv eol escapes); this simultaneously checks 4 options from assertion above { @@ -2913,7 +2933,7 @@ PUGI__NS_BEGIN case 13: return strconv_attribute_impl::parse_wnorm; case 14: return strconv_attribute_impl::parse_wnorm; case 15: return strconv_attribute_impl::parse_wnorm; - default: assert(false); return 0; // unreachable + default: assert(false); return NULL; // unreachable } } @@ -2932,7 +2952,7 @@ PUGI__NS_BEGIN char_t* error_offset; xml_parse_status error_status; - xml_parser(xml_allocator* alloc_): alloc(alloc_), error_offset(0), error_status(status_ok) + xml_parser(xml_allocator* alloc_): alloc(alloc_), error_offset(NULL), error_status(status_ok) { } @@ -2949,8 +2969,8 @@ PUGI__NS_BEGIN { // quoted string char_t ch = *s++; - PUGI__SCANFOR(*s == ch); - if (!*s) PUGI__THROW_ERROR(status_bad_doctype, s); + PUGI_IMPL_SCANFOR(*s == ch); + if (!*s) PUGI_IMPL_THROW_ERROR(status_bad_doctype, s); s++; } @@ -2958,20 +2978,20 @@ PUGI__NS_BEGIN { // s += 2; - PUGI__SCANFOR(s[0] == '?' && s[1] == '>'); // no need for ENDSWITH because ?> can't terminate proper doctype - if (!*s) PUGI__THROW_ERROR(status_bad_doctype, s); + PUGI_IMPL_SCANFOR(s[0] == '?' && s[1] == '>'); // no need for ENDSWITH because ?> can't terminate proper doctype + if (!*s) PUGI_IMPL_THROW_ERROR(status_bad_doctype, s); s += 2; } else if (s[0] == '<' && s[1] == '!' && s[2] == '-' && s[3] == '-') { s += 4; - PUGI__SCANFOR(s[0] == '-' && s[1] == '-' && s[2] == '>'); // no need for ENDSWITH because --> can't terminate proper doctype - if (!*s) PUGI__THROW_ERROR(status_bad_doctype, s); + PUGI_IMPL_SCANFOR(s[0] == '-' && s[1] == '-' && s[2] == '>'); // no need for ENDSWITH because --> can't terminate proper doctype + if (!*s) PUGI_IMPL_THROW_ERROR(status_bad_doctype, s); s += 3; } - else PUGI__THROW_ERROR(status_bad_doctype, s); + else PUGI_IMPL_THROW_ERROR(status_bad_doctype, s); return s; } @@ -3004,7 +3024,7 @@ PUGI__NS_BEGIN else s++; } - PUGI__THROW_ERROR(status_bad_doctype, s); + PUGI_IMPL_THROW_ERROR(status_bad_doctype, s); } char_t* parse_doctype_group(char_t* s, char_t endch) @@ -3048,7 +3068,7 @@ PUGI__NS_BEGIN else s++; } - if (depth != 0 || endch != '>') PUGI__THROW_ERROR(status_bad_doctype, s); + if (depth != 0 || endch != '>') PUGI_IMPL_THROW_ERROR(status_bad_doctype, s); return s; } @@ -3066,31 +3086,31 @@ PUGI__NS_BEGIN { ++s; - if (PUGI__OPTSET(parse_comments)) + if (PUGI_IMPL_OPTSET(parse_comments)) { - PUGI__PUSHNODE(node_comment); // Append a new node on the tree. + PUGI_IMPL_PUSHNODE(node_comment); // Append a new node on the tree. cursor->value = s; // Save the offset. } - if (PUGI__OPTSET(parse_eol) && PUGI__OPTSET(parse_comments)) + if (PUGI_IMPL_OPTSET(parse_eol) && PUGI_IMPL_OPTSET(parse_comments)) { s = strconv_comment(s, endch); - if (!s) PUGI__THROW_ERROR(status_bad_comment, cursor->value); + if (!s) PUGI_IMPL_THROW_ERROR(status_bad_comment, cursor->value); } else { // Scan for terminating '-->'. - PUGI__SCANFOR(s[0] == '-' && s[1] == '-' && PUGI__ENDSWITH(s[2], '>')); - PUGI__CHECK_ERROR(status_bad_comment, s); + PUGI_IMPL_SCANFOR(s[0] == '-' && s[1] == '-' && PUGI_IMPL_ENDSWITH(s[2], '>')); + PUGI_IMPL_CHECK_ERROR(status_bad_comment, s); - if (PUGI__OPTSET(parse_comments)) + if (PUGI_IMPL_OPTSET(parse_comments)) *s = 0; // Zero-terminate this segment at the first terminating '-'. s += (s[2] == '>' ? 3 : 2); // Step over the '\0->'. } } - else PUGI__THROW_ERROR(status_bad_comment, s); + else PUGI_IMPL_THROW_ERROR(status_bad_comment, s); } else if (*s == '[') { @@ -3099,22 +3119,22 @@ PUGI__NS_BEGIN { ++s; - if (PUGI__OPTSET(parse_cdata)) + if (PUGI_IMPL_OPTSET(parse_cdata)) { - PUGI__PUSHNODE(node_cdata); // Append a new node on the tree. + PUGI_IMPL_PUSHNODE(node_cdata); // Append a new node on the tree. cursor->value = s; // Save the offset. - if (PUGI__OPTSET(parse_eol)) + if (PUGI_IMPL_OPTSET(parse_eol)) { s = strconv_cdata(s, endch); - if (!s) PUGI__THROW_ERROR(status_bad_cdata, cursor->value); + if (!s) PUGI_IMPL_THROW_ERROR(status_bad_cdata, cursor->value); } else { // Scan for terminating ']]>'. - PUGI__SCANFOR(s[0] == ']' && s[1] == ']' && PUGI__ENDSWITH(s[2], '>')); - PUGI__CHECK_ERROR(status_bad_cdata, s); + PUGI_IMPL_SCANFOR(s[0] == ']' && s[1] == ']' && PUGI_IMPL_ENDSWITH(s[2], '>')); + PUGI_IMPL_CHECK_ERROR(status_bad_cdata, s); *s++ = 0; // Zero-terminate this segment. } @@ -3122,21 +3142,21 @@ PUGI__NS_BEGIN else // Flagged for discard, but we still have to scan for the terminator. { // Scan for terminating ']]>'. - PUGI__SCANFOR(s[0] == ']' && s[1] == ']' && PUGI__ENDSWITH(s[2], '>')); - PUGI__CHECK_ERROR(status_bad_cdata, s); + PUGI_IMPL_SCANFOR(s[0] == ']' && s[1] == ']' && PUGI_IMPL_ENDSWITH(s[2], '>')); + PUGI_IMPL_CHECK_ERROR(status_bad_cdata, s); ++s; } s += (s[1] == '>' ? 2 : 1); // Step over the last ']>'. } - else PUGI__THROW_ERROR(status_bad_cdata, s); + else PUGI_IMPL_THROW_ERROR(status_bad_cdata, s); } - else if (s[0] == 'D' && s[1] == 'O' && s[2] == 'C' && s[3] == 'T' && s[4] == 'Y' && s[5] == 'P' && PUGI__ENDSWITH(s[6], 'E')) + else if (s[0] == 'D' && s[1] == 'O' && s[2] == 'C' && s[3] == 'T' && s[4] == 'Y' && s[5] == 'P' && PUGI_IMPL_ENDSWITH(s[6], 'E')) { s -= 2; - if (cursor->parent) PUGI__THROW_ERROR(status_bad_doctype, s); + if (cursor->parent) PUGI_IMPL_THROW_ERROR(status_bad_doctype, s); char_t* mark = s + 9; @@ -3146,18 +3166,18 @@ PUGI__NS_BEGIN assert((*s == 0 && endch == '>') || *s == '>'); if (*s) *s++ = 0; - if (PUGI__OPTSET(parse_doctype)) + if (PUGI_IMPL_OPTSET(parse_doctype)) { - while (PUGI__IS_CHARTYPE(*mark, ct_space)) ++mark; + while (PUGI_IMPL_IS_CHARTYPE(*mark, ct_space)) ++mark; - PUGI__PUSHNODE(node_doctype); + PUGI_IMPL_PUSHNODE(node_doctype); cursor->value = mark; } } - else if (*s == 0 && endch == '-') PUGI__THROW_ERROR(status_bad_comment, s); - else if (*s == 0 && endch == '[') PUGI__THROW_ERROR(status_bad_cdata, s); - else PUGI__THROW_ERROR(status_unrecognized_tag, s); + else if (*s == 0 && endch == '-') PUGI_IMPL_THROW_ERROR(status_bad_comment, s); + else if (*s == 0 && endch == '[') PUGI_IMPL_THROW_ERROR(status_bad_cdata, s); + else PUGI_IMPL_THROW_ERROR(status_unrecognized_tag, s); return s; } @@ -3174,50 +3194,50 @@ PUGI__NS_BEGIN // read PI target char_t* target = s; - if (!PUGI__IS_CHARTYPE(*s, ct_start_symbol)) PUGI__THROW_ERROR(status_bad_pi, s); + if (!PUGI_IMPL_IS_CHARTYPE(*s, ct_start_symbol)) PUGI_IMPL_THROW_ERROR(status_bad_pi, s); - PUGI__SCANWHILE(PUGI__IS_CHARTYPE(*s, ct_symbol)); - PUGI__CHECK_ERROR(status_bad_pi, s); + PUGI_IMPL_SCANWHILE(PUGI_IMPL_IS_CHARTYPE(*s, ct_symbol)); + PUGI_IMPL_CHECK_ERROR(status_bad_pi, s); // determine node type; stricmp / strcasecmp is not portable bool declaration = (target[0] | ' ') == 'x' && (target[1] | ' ') == 'm' && (target[2] | ' ') == 'l' && target + 3 == s; - if (declaration ? PUGI__OPTSET(parse_declaration) : PUGI__OPTSET(parse_pi)) + if (declaration ? PUGI_IMPL_OPTSET(parse_declaration) : PUGI_IMPL_OPTSET(parse_pi)) { if (declaration) { // disallow non top-level declarations - if (cursor->parent) PUGI__THROW_ERROR(status_bad_pi, s); + if (cursor->parent) PUGI_IMPL_THROW_ERROR(status_bad_pi, s); - PUGI__PUSHNODE(node_declaration); + PUGI_IMPL_PUSHNODE(node_declaration); } else { - PUGI__PUSHNODE(node_pi); + PUGI_IMPL_PUSHNODE(node_pi); } cursor->name = target; - PUGI__ENDSEG(); + PUGI_IMPL_ENDSEG(); // parse value/attributes if (ch == '?') { // empty node - if (!PUGI__ENDSWITH(*s, '>')) PUGI__THROW_ERROR(status_bad_pi, s); + if (!PUGI_IMPL_ENDSWITH(*s, '>')) PUGI_IMPL_THROW_ERROR(status_bad_pi, s); s += (*s == '>'); - PUGI__POPNODE(); + PUGI_IMPL_POPNODE(); } - else if (PUGI__IS_CHARTYPE(ch, ct_space)) + else if (PUGI_IMPL_IS_CHARTYPE(ch, ct_space)) { - PUGI__SKIPWS(); + PUGI_IMPL_SKIPWS(); // scan for tag end char_t* value = s; - PUGI__SCANFOR(s[0] == '?' && PUGI__ENDSWITH(s[1], '>')); - PUGI__CHECK_ERROR(status_bad_pi, s); + PUGI_IMPL_SCANFOR(s[0] == '?' && PUGI_IMPL_ENDSWITH(s[1], '>')); + PUGI_IMPL_CHECK_ERROR(status_bad_pi, s); if (declaration) { @@ -3232,20 +3252,20 @@ PUGI__NS_BEGIN // store value and step over > cursor->value = value; - PUGI__POPNODE(); + PUGI_IMPL_POPNODE(); - PUGI__ENDSEG(); + PUGI_IMPL_ENDSEG(); s += (*s == '>'); } } - else PUGI__THROW_ERROR(status_bad_pi, s); + else PUGI_IMPL_THROW_ERROR(status_bad_pi, s); } else { // scan for tag end - PUGI__SCANFOR(s[0] == '?' && PUGI__ENDSWITH(s[1], '>')); - PUGI__CHECK_ERROR(status_bad_pi, s); + PUGI_IMPL_SCANFOR(s[0] == '?' && PUGI_IMPL_ENDSWITH(s[1], '>')); + PUGI_IMPL_CHECK_ERROR(status_bad_pi, s); s += (s[1] == '>' ? 2 : 1); } @@ -3264,6 +3284,7 @@ PUGI__NS_BEGIN char_t ch = 0; xml_node_struct* cursor = root; char_t* mark = s; + char_t* merged_pcdata = s; while (*s != 0) { @@ -3272,39 +3293,39 @@ PUGI__NS_BEGIN ++s; LOC_TAG: - if (PUGI__IS_CHARTYPE(*s, ct_start_symbol)) // '<#...' + if (PUGI_IMPL_IS_CHARTYPE(*s, ct_start_symbol)) // '<#...' { - PUGI__PUSHNODE(node_element); // Append a new node to the tree. + PUGI_IMPL_PUSHNODE(node_element); // Append a new node to the tree. cursor->name = s; - PUGI__SCANWHILE_UNROLL(PUGI__IS_CHARTYPE(ss, ct_symbol)); // Scan for a terminator. - PUGI__ENDSEG(); // Save char in 'ch', terminate & step over. + PUGI_IMPL_SCANWHILE_UNROLL(PUGI_IMPL_IS_CHARTYPE(ss, ct_symbol)); // Scan for a terminator. + PUGI_IMPL_ENDSEG(); // Save char in 'ch', terminate & step over. if (ch == '>') { // end of tag } - else if (PUGI__IS_CHARTYPE(ch, ct_space)) + else if (PUGI_IMPL_IS_CHARTYPE(ch, ct_space)) { LOC_ATTRIBUTES: while (true) { - PUGI__SKIPWS(); // Eat any whitespace. + PUGI_IMPL_SKIPWS(); // Eat any whitespace. - if (PUGI__IS_CHARTYPE(*s, ct_start_symbol)) // <... #... + if (PUGI_IMPL_IS_CHARTYPE(*s, ct_start_symbol)) // <... #... { xml_attribute_struct* a = append_new_attribute(cursor, *alloc); // Make space for this attribute. - if (!a) PUGI__THROW_ERROR(status_out_of_memory, s); + if (!a) PUGI_IMPL_THROW_ERROR(status_out_of_memory, s); a->name = s; // Save the offset. - PUGI__SCANWHILE_UNROLL(PUGI__IS_CHARTYPE(ss, ct_symbol)); // Scan for a terminator. - PUGI__ENDSEG(); // Save char in 'ch', terminate & step over. + PUGI_IMPL_SCANWHILE_UNROLL(PUGI_IMPL_IS_CHARTYPE(ss, ct_symbol)); // Scan for a terminator. + PUGI_IMPL_ENDSEG(); // Save char in 'ch', terminate & step over. - if (PUGI__IS_CHARTYPE(ch, ct_space)) + if (PUGI_IMPL_IS_CHARTYPE(ch, ct_space)) { - PUGI__SKIPWS(); // Eat any whitespace. + PUGI_IMPL_SKIPWS(); // Eat any whitespace. ch = *s; ++s; @@ -3312,7 +3333,7 @@ PUGI__NS_BEGIN if (ch == '=') // '<... #=...' { - PUGI__SKIPWS(); // Eat any whitespace. + PUGI_IMPL_SKIPWS(); // Eat any whitespace. if (*s == '"' || *s == '\'') // '<... #="...' { @@ -3322,16 +3343,16 @@ PUGI__NS_BEGIN s = strconv_attribute(s, ch); - if (!s) PUGI__THROW_ERROR(status_bad_attribute, a->value); + if (!s) PUGI_IMPL_THROW_ERROR(status_bad_attribute, a->value); // After this line the loop continues from the start; // Whitespaces, / and > are ok, symbols and EOF are wrong, // everything else will be detected - if (PUGI__IS_CHARTYPE(*s, ct_start_symbol)) PUGI__THROW_ERROR(status_bad_attribute, s); + if (PUGI_IMPL_IS_CHARTYPE(*s, ct_start_symbol)) PUGI_IMPL_THROW_ERROR(status_bad_attribute, s); } - else PUGI__THROW_ERROR(status_bad_attribute, s); + else PUGI_IMPL_THROW_ERROR(status_bad_attribute, s); } - else PUGI__THROW_ERROR(status_bad_attribute, s); + else PUGI_IMPL_THROW_ERROR(status_bad_attribute, s); } else if (*s == '/') { @@ -3339,16 +3360,16 @@ PUGI__NS_BEGIN if (*s == '>') { - PUGI__POPNODE(); + PUGI_IMPL_POPNODE(); s++; break; } else if (*s == 0 && endch == '>') { - PUGI__POPNODE(); + PUGI_IMPL_POPNODE(); break; } - else PUGI__THROW_ERROR(status_bad_start_element, s); + else PUGI_IMPL_THROW_ERROR(status_bad_start_element, s); } else if (*s == '>') { @@ -3360,16 +3381,16 @@ PUGI__NS_BEGIN { break; } - else PUGI__THROW_ERROR(status_bad_start_element, s); + else PUGI_IMPL_THROW_ERROR(status_bad_start_element, s); } // !!! } else if (ch == '/') // '<#.../' { - if (!PUGI__ENDSWITH(*s, '>')) PUGI__THROW_ERROR(status_bad_start_element, s); + if (!PUGI_IMPL_ENDSWITH(*s, '>')) PUGI_IMPL_THROW_ERROR(status_bad_start_element, s); - PUGI__POPNODE(); // Pop. + PUGI_IMPL_POPNODE(); // Pop. s += (*s == '>'); } @@ -3378,9 +3399,9 @@ PUGI__NS_BEGIN // we stepped over null terminator, backtrack & handle closing tag --s; - if (endch != '>') PUGI__THROW_ERROR(status_bad_start_element, s); + if (endch != '>') PUGI_IMPL_THROW_ERROR(status_bad_start_element, s); } - else PUGI__THROW_ERROR(status_bad_start_element, s); + else PUGI_IMPL_THROW_ERROR(status_bad_start_element, s); } else if (*s == '/') { @@ -3389,30 +3410,30 @@ PUGI__NS_BEGIN mark = s; char_t* name = cursor->name; - if (!name) PUGI__THROW_ERROR(status_end_element_mismatch, mark); + if (!name) PUGI_IMPL_THROW_ERROR(status_end_element_mismatch, mark); - while (PUGI__IS_CHARTYPE(*s, ct_symbol)) + while (PUGI_IMPL_IS_CHARTYPE(*s, ct_symbol)) { - if (*s++ != *name++) PUGI__THROW_ERROR(status_end_element_mismatch, mark); + if (*s++ != *name++) PUGI_IMPL_THROW_ERROR(status_end_element_mismatch, mark); } if (*name) { - if (*s == 0 && name[0] == endch && name[1] == 0) PUGI__THROW_ERROR(status_bad_end_element, s); - else PUGI__THROW_ERROR(status_end_element_mismatch, mark); + if (*s == 0 && name[0] == endch && name[1] == 0) PUGI_IMPL_THROW_ERROR(status_bad_end_element, s); + else PUGI_IMPL_THROW_ERROR(status_end_element_mismatch, mark); } - PUGI__POPNODE(); // Pop. + PUGI_IMPL_POPNODE(); // Pop. - PUGI__SKIPWS(); + PUGI_IMPL_SKIPWS(); if (*s == 0) { - if (endch != '>') PUGI__THROW_ERROR(status_bad_end_element, s); + if (endch != '>') PUGI_IMPL_THROW_ERROR(status_bad_end_element, s); } else { - if (*s != '>') PUGI__THROW_ERROR(status_bad_end_element, s); + if (*s != '>') PUGI_IMPL_THROW_ERROR(status_bad_end_element, s); ++s; } } @@ -3422,62 +3443,79 @@ PUGI__NS_BEGIN if (!s) return s; assert(cursor); - if (PUGI__NODETYPE(cursor) == node_declaration) goto LOC_ATTRIBUTES; + if (PUGI_IMPL_NODETYPE(cursor) == node_declaration) goto LOC_ATTRIBUTES; } else if (*s == '!') // 'first_child) continue; } } - if (!PUGI__OPTSET(parse_trim_pcdata)) + if (!PUGI_IMPL_OPTSET(parse_trim_pcdata)) s = mark; - if (cursor->parent || PUGI__OPTSET(parse_fragment)) + if (cursor->parent || PUGI_IMPL_OPTSET(parse_fragment)) { - if (PUGI__OPTSET(parse_embed_pcdata) && cursor->parent && !cursor->first_child && !cursor->value) + char_t* parsed_pcdata = s; + + s = strconv_pcdata(s); + + if (PUGI_IMPL_OPTSET(parse_embed_pcdata) && cursor->parent && !cursor->first_child && !cursor->value) { - cursor->value = s; // Save the offset. + cursor->value = parsed_pcdata; // Save the offset. } - else + else if (PUGI_IMPL_OPTSET(parse_merge_pcdata) && cursor->first_child && PUGI_IMPL_NODETYPE(cursor->first_child->prev_sibling_c) == node_pcdata) { - PUGI__PUSHNODE(node_pcdata); // Append a new node on the tree. + assert(merged_pcdata >= cursor->first_child->prev_sibling_c->value); + + // Catch up to the end of last parsed value; only needed for the first fragment. + merged_pcdata += strlength(merged_pcdata); - cursor->value = s; // Save the offset. + size_t length = strlength(parsed_pcdata); - PUGI__POPNODE(); // Pop since this is a standalone. + // Must use memmove instead of memcpy as this move may overlap + memmove(merged_pcdata, parsed_pcdata, (length + 1) * sizeof(char_t)); + merged_pcdata += length; } + else + { + xml_node_struct* prev_cursor = cursor; + PUGI_IMPL_PUSHNODE(node_pcdata); // Append a new node on the tree. - s = strconv_pcdata(s); + cursor->value = parsed_pcdata; // Save the offset. + merged_pcdata = parsed_pcdata; // Used for parse_merge_pcdata above, cheaper to save unconditionally + + cursor = prev_cursor; // Pop since this is a standalone. + } if (!*s) break; } else { - PUGI__SCANFOR(*s == '<'); // '...<' + PUGI_IMPL_SCANFOR(*s == '<'); // '...<' if (!*s) break; ++s; @@ -3489,7 +3527,7 @@ PUGI__NS_BEGIN } // check that last tag is closed - if (cursor != root) PUGI__THROW_ERROR(status_end_element_mismatch, s); + if (cursor != root) PUGI_IMPL_THROW_ERROR(status_end_element_mismatch, s); return s; } @@ -3511,7 +3549,7 @@ PUGI__NS_BEGIN { while (node) { - if (PUGI__NODETYPE(node) == node_element) return true; + if (PUGI_IMPL_NODETYPE(node) == node_element) return true; node = node->next_sibling; } @@ -3523,10 +3561,10 @@ PUGI__NS_BEGIN { // early-out for empty documents if (length == 0) - return make_parse_result(PUGI__OPTSET(parse_fragment) ? status_ok : status_no_document_element); + return make_parse_result(PUGI_IMPL_OPTSET(parse_fragment) ? status_ok : status_no_document_element); // get last child of the root before parsing - xml_node_struct* last_root_child = root->first_child ? root->first_child->prev_sibling_c + 0 : 0; + xml_node_struct* last_root_child = root->first_child ? root->first_child->prev_sibling_c + 0 : NULL; // create parser on stack xml_parser parser(static_cast(xmldoc)); @@ -3551,9 +3589,9 @@ PUGI__NS_BEGIN return make_parse_result(status_unrecognized_tag, length - 1); // check if there are any element nodes parsed - xml_node_struct* first_root_child_parsed = last_root_child ? last_root_child->next_sibling + 0 : root->first_child+ 0; + xml_node_struct* first_root_child_parsed = last_root_child ? last_root_child->next_sibling + 0 : root->first_child + 0; - if (!PUGI__OPTSET(parse_fragment) && !has_element_node_siblings(first_root_child_parsed)) + if (!PUGI_IMPL_OPTSET(parse_fragment) && !has_element_node_siblings(first_root_child_parsed)) return make_parse_result(status_no_document_element, length - 1); } else @@ -3568,7 +3606,7 @@ PUGI__NS_BEGIN }; // Output facilities - PUGI__FN xml_encoding get_write_native_encoding() + PUGI_IMPL_FN xml_encoding get_write_native_encoding() { #ifdef PUGIXML_WCHAR_MODE return get_wchar_encoding(); @@ -3577,7 +3615,7 @@ PUGI__NS_BEGIN #endif } - PUGI__FN xml_encoding get_write_encoding(xml_encoding encoding) + PUGI_IMPL_FN xml_encoding get_write_encoding(xml_encoding encoding) { // replace wchar encoding with utf implementation if (encoding == encoding_wchar) return get_wchar_encoding(); @@ -3595,18 +3633,18 @@ PUGI__NS_BEGIN return encoding_utf8; } - template PUGI__FN size_t convert_buffer_output_generic(typename T::value_type dest, const char_t* data, size_t length, D, T) + template PUGI_IMPL_FN size_t convert_buffer_output_generic(typename T::value_type dest, const char_t* data, size_t length, D, T) { - PUGI__STATIC_ASSERT(sizeof(char_t) == sizeof(typename D::type)); + PUGI_IMPL_STATIC_ASSERT(sizeof(char_t) == sizeof(typename D::type)); typename T::value_type end = D::process(reinterpret_cast(data), length, dest, T()); return static_cast(end - dest) * sizeof(*dest); } - template PUGI__FN size_t convert_buffer_output_generic(typename T::value_type dest, const char_t* data, size_t length, D, T, bool opt_swap) + template PUGI_IMPL_FN size_t convert_buffer_output_generic(typename T::value_type dest, const char_t* data, size_t length, D, T, bool opt_swap) { - PUGI__STATIC_ASSERT(sizeof(char_t) == sizeof(typename D::type)); + PUGI_IMPL_STATIC_ASSERT(sizeof(char_t) == sizeof(typename D::type)); typename T::value_type end = D::process(reinterpret_cast(data), length, dest, T()); @@ -3620,7 +3658,7 @@ PUGI__NS_BEGIN } #ifdef PUGIXML_WCHAR_MODE - PUGI__FN size_t get_valid_length(const char_t* data, size_t length) + PUGI_IMPL_FN size_t get_valid_length(const char_t* data, size_t length) { if (length < 1) return 0; @@ -3628,7 +3666,7 @@ PUGI__NS_BEGIN return (sizeof(wchar_t) == 2 && static_cast(static_cast(data[length - 1]) - 0xD800) < 0x400) ? length - 1 : length; } - PUGI__FN size_t convert_buffer_output(char_t* r_char, uint8_t* r_u8, uint16_t* r_u16, uint32_t* r_u32, const char_t* data, size_t length, xml_encoding encoding) + PUGI_IMPL_FN size_t convert_buffer_output(char_t* r_char, uint8_t* r_u8, uint16_t* r_u16, uint32_t* r_u32, const char_t* data, size_t length, xml_encoding encoding) { // only endian-swapping is required if (need_endian_swap_utf(encoding, get_wchar_encoding())) @@ -3666,7 +3704,7 @@ PUGI__NS_BEGIN return 0; } #else - PUGI__FN size_t get_valid_length(const char_t* data, size_t length) + PUGI_IMPL_FN size_t get_valid_length(const char_t* data, size_t length) { if (length < 5) return 0; @@ -3682,7 +3720,7 @@ PUGI__NS_BEGIN return length; } - PUGI__FN size_t convert_buffer_output(char_t* /* r_char */, uint8_t* r_u8, uint16_t* r_u16, uint32_t* r_u32, const char_t* data, size_t length, xml_encoding encoding) + PUGI_IMPL_FN size_t convert_buffer_output(char_t* /* r_char */, uint8_t* r_u8, uint16_t* r_u16, uint32_t* r_u32, const char_t* data, size_t length, xml_encoding encoding) { if (encoding == encoding_utf16_be || encoding == encoding_utf16_le) { @@ -3714,7 +3752,7 @@ PUGI__NS_BEGIN public: xml_buffered_writer(xml_writer& writer_, xml_encoding user_encoding): writer(writer_), bufsize(0), encoding(get_write_encoding(user_encoding)) { - PUGI__STATIC_ASSERT(bufcapacity >= 8); + PUGI_IMPL_STATIC_ASSERT(bufcapacity >= 8); } size_t flush() @@ -3920,14 +3958,14 @@ PUGI__NS_BEGIN xml_encoding encoding; }; - PUGI__FN void text_output_escaped(xml_buffered_writer& writer, const char_t* s, chartypex_t type, unsigned int flags) + PUGI_IMPL_FN void text_output_escaped(xml_buffered_writer& writer, const char_t* s, chartypex_t type, unsigned int flags) { while (*s) { const char_t* prev = s; // While *s is a usual symbol - PUGI__SCANWHILE_UNROLL(!PUGI__IS_CHARTYPEX(ss, type)); + PUGI_IMPL_SCANWHILE_UNROLL(!PUGI_IMPL_IS_CHARTYPEX(ss, type)); writer.write_buffer(prev, static_cast(s - prev)); @@ -3972,7 +4010,7 @@ PUGI__NS_BEGIN } } - PUGI__FN void text_output(xml_buffered_writer& writer, const char_t* s, chartypex_t type, unsigned int flags) + PUGI_IMPL_FN void text_output(xml_buffered_writer& writer, const char_t* s, chartypex_t type, unsigned int flags) { if (flags & format_no_escapes) writer.write_string(s); @@ -3980,7 +4018,7 @@ PUGI__NS_BEGIN text_output_escaped(writer, s, type, flags); } - PUGI__FN void text_output_cdata(xml_buffered_writer& writer, const char_t* s) + PUGI_IMPL_FN void text_output_cdata(xml_buffered_writer& writer, const char_t* s) { do { @@ -4002,7 +4040,7 @@ PUGI__NS_BEGIN while (*s); } - PUGI__FN void text_output_indent(xml_buffered_writer& writer, const char_t* indent, size_t indent_length, unsigned int depth) + PUGI_IMPL_FN void text_output_indent(xml_buffered_writer& writer, const char_t* indent, size_t indent_length, unsigned int depth) { switch (indent_length) { @@ -4042,7 +4080,7 @@ PUGI__NS_BEGIN } } - PUGI__FN void node_output_comment(xml_buffered_writer& writer, const char_t* s) + PUGI_IMPL_FN void node_output_comment(xml_buffered_writer& writer, const char_t* s) { writer.write('<', '!', '-', '-'); @@ -4067,7 +4105,7 @@ PUGI__NS_BEGIN writer.write('-', '-', '>'); } - PUGI__FN void node_output_pi_value(xml_buffered_writer& writer, const char_t* s) + PUGI_IMPL_FN void node_output_pi_value(xml_buffered_writer& writer, const char_t* s) { while (*s) { @@ -4088,7 +4126,7 @@ PUGI__NS_BEGIN } } - PUGI__FN void node_output_attributes(xml_buffered_writer& writer, xml_node_struct* node, const char_t* indent, size_t indent_length, unsigned int flags, unsigned int depth) + PUGI_IMPL_FN void node_output_attributes(xml_buffered_writer& writer, xml_node_struct* node, const char_t* indent, size_t indent_length, unsigned int flags, unsigned int depth) { const char_t* default_name = PUGIXML_TEXT(":anonymous"); const char_t enquotation_char = (flags & format_attribute_single_quote) ? '\'' : '"'; @@ -4116,7 +4154,7 @@ PUGI__NS_BEGIN } } - PUGI__FN bool node_output_start(xml_buffered_writer& writer, xml_node_struct* node, const char_t* indent, size_t indent_length, unsigned int flags, unsigned int depth) + PUGI_IMPL_FN bool node_output_start(xml_buffered_writer& writer, xml_node_struct* node, const char_t* indent, size_t indent_length, unsigned int flags, unsigned int depth) { const char_t* default_name = PUGIXML_TEXT(":anonymous"); const char_t* name = node->name ? node->name + 0 : default_name; @@ -4178,7 +4216,7 @@ PUGI__NS_BEGIN } } - PUGI__FN void node_output_end(xml_buffered_writer& writer, xml_node_struct* node) + PUGI_IMPL_FN void node_output_end(xml_buffered_writer& writer, xml_node_struct* node) { const char_t* default_name = PUGIXML_TEXT(":anonymous"); const char_t* name = node->name ? node->name + 0 : default_name; @@ -4188,11 +4226,11 @@ PUGI__NS_BEGIN writer.write('>'); } - PUGI__FN void node_output_simple(xml_buffered_writer& writer, xml_node_struct* node, unsigned int flags) + PUGI_IMPL_FN void node_output_simple(xml_buffered_writer& writer, xml_node_struct* node, unsigned int flags) { const char_t* default_name = PUGIXML_TEXT(":anonymous"); - switch (PUGI__NODETYPE(node)) + switch (PUGI_IMPL_NODETYPE(node)) { case node_pcdata: text_output(writer, node->value ? node->value + 0 : PUGIXML_TEXT(""), ctx_special_pcdata, flags); @@ -4250,7 +4288,7 @@ PUGI__NS_BEGIN indent_indent = 2 }; - PUGI__FN void node_output(xml_buffered_writer& writer, xml_node_struct* root, const char_t* indent, unsigned int flags, unsigned int depth) + PUGI_IMPL_FN void node_output(xml_buffered_writer& writer, xml_node_struct* root, const char_t* indent, unsigned int flags, unsigned int depth) { size_t indent_length = ((flags & (format_indent | format_indent_attributes)) && (flags & format_raw) == 0) ? strlength(indent) : 0; unsigned int indent_flags = indent_indent; @@ -4262,7 +4300,7 @@ PUGI__NS_BEGIN assert(node); // begin writing current node - if (PUGI__NODETYPE(node) == node_pcdata || PUGI__NODETYPE(node) == node_cdata) + if (PUGI_IMPL_NODETYPE(node) == node_pcdata || PUGI_IMPL_NODETYPE(node) == node_cdata) { node_output_simple(writer, node, flags); @@ -4276,7 +4314,7 @@ PUGI__NS_BEGIN if ((indent_flags & indent_indent) && indent_length) text_output_indent(writer, indent, indent_length, depth); - if (PUGI__NODETYPE(node) == node_element) + if (PUGI_IMPL_NODETYPE(node) == node_element) { indent_flags = indent_newline | indent_indent; @@ -4291,7 +4329,7 @@ PUGI__NS_BEGIN continue; } } - else if (PUGI__NODETYPE(node) == node_document) + else if (PUGI_IMPL_NODETYPE(node) == node_document) { indent_flags = indent_indent; @@ -4321,7 +4359,7 @@ PUGI__NS_BEGIN node = node->parent; // write closing node - if (PUGI__NODETYPE(node) == node_element) + if (PUGI_IMPL_NODETYPE(node) == node_element) { depth--; @@ -4343,11 +4381,11 @@ PUGI__NS_BEGIN writer.write('\n'); } - PUGI__FN bool has_declaration(xml_node_struct* node) + PUGI_IMPL_FN bool has_declaration(xml_node_struct* node) { for (xml_node_struct* child = node->first_child; child; child = child->next_sibling) { - xml_node_type type = PUGI__NODETYPE(child); + xml_node_type type = PUGI_IMPL_NODETYPE(child); if (type == node_declaration) return true; if (type == node_element) return false; @@ -4356,7 +4394,7 @@ PUGI__NS_BEGIN return false; } - PUGI__FN bool is_attribute_of(xml_attribute_struct* attr, xml_node_struct* node) + PUGI_IMPL_FN bool is_attribute_of(xml_attribute_struct* attr, xml_node_struct* node) { for (xml_attribute_struct* a = node->first_attribute; a; a = a->next_attribute) if (a == attr) @@ -4365,12 +4403,12 @@ PUGI__NS_BEGIN return false; } - PUGI__FN bool allow_insert_attribute(xml_node_type parent) + PUGI_IMPL_FN bool allow_insert_attribute(xml_node_type parent) { return parent == node_element || parent == node_declaration; } - PUGI__FN bool allow_insert_child(xml_node_type parent, xml_node_type child) + PUGI_IMPL_FN bool allow_insert_child(xml_node_type parent, xml_node_type child) { if (parent != node_document && parent != node_element) return false; if (child == node_document || child == node_null) return false; @@ -4379,7 +4417,7 @@ PUGI__NS_BEGIN return true; } - PUGI__FN bool allow_move(xml_node parent, xml_node child) + PUGI_IMPL_FN bool allow_move(xml_node parent, xml_node child) { // check that child can be a child of parent if (!allow_insert_child(parent.type(), child.type())) @@ -4404,9 +4442,9 @@ PUGI__NS_BEGIN } template - PUGI__FN void node_copy_string(String& dest, Header& header, uintptr_t header_mask, char_t* source, Header& source_header, xml_allocator* alloc) + PUGI_IMPL_FN void node_copy_string(String& dest, Header& header, uintptr_t header_mask, char_t* source, Header& source_header, xml_allocator* alloc) { - assert(!dest && (header & header_mask) == 0); + assert(!dest && (header & header_mask) == 0); // copies are performed into fresh nodes if (source) { @@ -4423,7 +4461,7 @@ PUGI__NS_BEGIN } } - PUGI__FN void node_copy_contents(xml_node_struct* dn, xml_node_struct* sn, xml_allocator* shared_alloc) + PUGI_IMPL_FN void node_copy_contents(xml_node_struct* dn, xml_node_struct* sn, xml_allocator* shared_alloc) { node_copy_string(dn->name, dn->header, xml_memory_page_name_allocated_mask, sn->name, sn->header, shared_alloc); node_copy_string(dn->value, dn->header, xml_memory_page_value_allocated_mask, sn->value, sn->header, shared_alloc); @@ -4440,10 +4478,10 @@ PUGI__NS_BEGIN } } - PUGI__FN void node_copy_tree(xml_node_struct* dn, xml_node_struct* sn) + PUGI_IMPL_FN void node_copy_tree(xml_node_struct* dn, xml_node_struct* sn) { xml_allocator& alloc = get_allocator(dn); - xml_allocator* shared_alloc = (&alloc == &get_allocator(sn)) ? &alloc : 0; + xml_allocator* shared_alloc = (&alloc == &get_allocator(sn)) ? &alloc : NULL; node_copy_contents(dn, sn, shared_alloc); @@ -4458,7 +4496,7 @@ PUGI__NS_BEGIN // when a tree is copied into one of the descendants, we need to skip that subtree to avoid an infinite loop if (sit != dn) { - xml_node_struct* copy = append_new_node(dit, alloc, PUGI__NODETYPE(sit)); + xml_node_struct* copy = append_new_node(dit, alloc, PUGI_IMPL_NODETYPE(sit)); if (copy) { @@ -4494,10 +4532,10 @@ PUGI__NS_BEGIN assert(!sit || dit == dn->parent); } - PUGI__FN void node_copy_attribute(xml_attribute_struct* da, xml_attribute_struct* sa) + PUGI_IMPL_FN void node_copy_attribute(xml_attribute_struct* da, xml_attribute_struct* sa) { xml_allocator& alloc = get_allocator(da); - xml_allocator* shared_alloc = (&alloc == &get_allocator(sa)) ? &alloc : 0; + xml_allocator* shared_alloc = (&alloc == &get_allocator(sa)) ? &alloc : NULL; node_copy_string(da->name, da->header, xml_memory_page_name_allocated_mask, sa->name, sa->header, shared_alloc); node_copy_string(da->value, da->header, xml_memory_page_value_allocated_mask, sa->value, sa->header, shared_alloc); @@ -4505,18 +4543,18 @@ PUGI__NS_BEGIN inline bool is_text_node(xml_node_struct* node) { - xml_node_type type = PUGI__NODETYPE(node); + xml_node_type type = PUGI_IMPL_NODETYPE(node); return type == node_pcdata || type == node_cdata; } // get value with conversion functions - template PUGI__FN PUGI__UNSIGNED_OVERFLOW U string_to_integer(const char_t* value, U minv, U maxv) + template PUGI_IMPL_FN PUGI_IMPL_UNSIGNED_OVERFLOW U string_to_integer(const char_t* value, U minv, U maxv) { U result = 0; const char_t* s = value; - while (PUGI__IS_CHARTYPE(*s, ct_space)) + while (PUGI_IMPL_IS_CHARTYPE(*s, ct_space)) s++; bool negative = (*s == '-'); @@ -4571,7 +4609,7 @@ PUGI__NS_BEGIN size_t digits = static_cast(s - start); - PUGI__STATIC_ASSERT(sizeof(U) == 8 || sizeof(U) == 4 || sizeof(U) == 2); + PUGI_IMPL_STATIC_ASSERT(sizeof(U) == 8 || sizeof(U) == 4 || sizeof(U) == 2); const size_t max_digits10 = sizeof(U) == 8 ? 20 : sizeof(U) == 4 ? 10 : 5; const char_t max_lead = sizeof(U) == 8 ? '1' : sizeof(U) == 4 ? '4' : '6'; @@ -4593,35 +4631,35 @@ PUGI__NS_BEGIN return (overflow || result > maxv) ? maxv : result; } - PUGI__FN int get_value_int(const char_t* value) + PUGI_IMPL_FN int get_value_int(const char_t* value) { return string_to_integer(value, static_cast(INT_MIN), INT_MAX); } - PUGI__FN unsigned int get_value_uint(const char_t* value) + PUGI_IMPL_FN unsigned int get_value_uint(const char_t* value) { return string_to_integer(value, 0, UINT_MAX); } - PUGI__FN double get_value_double(const char_t* value) + PUGI_IMPL_FN double get_value_double(const char_t* value) { #ifdef PUGIXML_WCHAR_MODE - return wcstod(value, 0); + return wcstod(value, NULL); #else - return strtod(value, 0); + return strtod(value, NULL); #endif } - PUGI__FN float get_value_float(const char_t* value) + PUGI_IMPL_FN float get_value_float(const char_t* value) { #ifdef PUGIXML_WCHAR_MODE - return static_cast(wcstod(value, 0)); + return static_cast(wcstod(value, NULL)); #else - return static_cast(strtod(value, 0)); + return static_cast(strtod(value, NULL)); #endif } - PUGI__FN bool get_value_bool(const char_t* value) + PUGI_IMPL_FN bool get_value_bool(const char_t* value) { // only look at first char char_t first = *value; @@ -4631,18 +4669,18 @@ PUGI__NS_BEGIN } #ifdef PUGIXML_HAS_LONG_LONG - PUGI__FN long long get_value_llong(const char_t* value) + PUGI_IMPL_FN long long get_value_llong(const char_t* value) { return string_to_integer(value, static_cast(LLONG_MIN), LLONG_MAX); } - PUGI__FN unsigned long long get_value_ullong(const char_t* value) + PUGI_IMPL_FN unsigned long long get_value_ullong(const char_t* value) { return string_to_integer(value, 0, ULLONG_MAX); } #endif - template PUGI__FN PUGI__UNSIGNED_OVERFLOW char_t* integer_to_string(char_t* begin, char_t* end, U value, bool negative) + template PUGI_IMPL_FN PUGI_IMPL_UNSIGNED_OVERFLOW char_t* integer_to_string(char_t* begin, char_t* end, U value, bool negative) { char_t* result = end - 1; U rest = negative ? 0 - value : value; @@ -4664,7 +4702,7 @@ PUGI__NS_BEGIN // set value with conversion functions template - PUGI__FN bool set_value_ascii(String& dest, Header& header, uintptr_t header_mask, char* buf) + PUGI_IMPL_FN bool set_value_ascii(String& dest, Header& header, uintptr_t header_mask, char* buf) { #ifdef PUGIXML_WCHAR_MODE char_t wbuf[128]; @@ -4680,7 +4718,7 @@ PUGI__NS_BEGIN } template - PUGI__FN bool set_value_integer(String& dest, Header& header, uintptr_t header_mask, U value, bool negative) + PUGI_IMPL_FN bool set_value_integer(String& dest, Header& header, uintptr_t header_mask, U value, bool negative) { char_t buf[64]; char_t* end = buf + sizeof(buf) / sizeof(buf[0]); @@ -4690,30 +4728,30 @@ PUGI__NS_BEGIN } template - PUGI__FN bool set_value_convert(String& dest, Header& header, uintptr_t header_mask, float value, int precision) + PUGI_IMPL_FN bool set_value_convert(String& dest, Header& header, uintptr_t header_mask, float value, int precision) { char buf[128]; - PUGI__SNPRINTF(buf, "%.*g", precision, double(value)); + PUGI_IMPL_SNPRINTF(buf, "%.*g", precision, double(value)); return set_value_ascii(dest, header, header_mask, buf); } template - PUGI__FN bool set_value_convert(String& dest, Header& header, uintptr_t header_mask, double value, int precision) + PUGI_IMPL_FN bool set_value_convert(String& dest, Header& header, uintptr_t header_mask, double value, int precision) { char buf[128]; - PUGI__SNPRINTF(buf, "%.*g", precision, value); + PUGI_IMPL_SNPRINTF(buf, "%.*g", precision, value); return set_value_ascii(dest, header, header_mask, buf); } template - PUGI__FN bool set_value_bool(String& dest, Header& header, uintptr_t header_mask, bool value) + PUGI_IMPL_FN bool set_value_bool(String& dest, Header& header, uintptr_t header_mask, bool value) { return strcpy_insitu(dest, header, header_mask, value ? PUGIXML_TEXT("true") : PUGIXML_TEXT("false"), value ? 4 : 5); } - PUGI__FN xml_parse_result load_buffer_impl(xml_document_struct* doc, xml_node_struct* root, void* contents, size_t size, unsigned int options, xml_encoding encoding, bool is_mutable, bool own, char_t** out_buffer) + PUGI_IMPL_FN xml_parse_result load_buffer_impl(xml_document_struct* doc, xml_node_struct* root, void* contents, size_t size, unsigned int options, xml_encoding encoding, bool is_mutable, bool own, char_t** out_buffer) { // check input buffer if (!contents && size) return make_parse_result(status_io_error); @@ -4722,10 +4760,10 @@ PUGI__NS_BEGIN xml_encoding buffer_encoding = impl::get_buffer_encoding(encoding, contents, size); // if convert_buffer below throws bad_alloc, we still need to deallocate contents if we own it - auto_deleter contents_guard(own ? contents : 0, xml_memory::deallocate); + auto_deleter contents_guard(own ? contents : NULL, xml_memory::deallocate); // get private buffer - char_t* buffer = 0; + char_t* buffer = NULL; size_t length = 0; // coverity[var_deref_model] @@ -4752,48 +4790,60 @@ PUGI__NS_BEGIN return res; } + template PUGI_IMPL_FN xml_parse_status convert_file_size(T length, size_t& out_result) + { + // check for I/O errors + if (length < 0) return status_io_error; + + // check for overflow + size_t result = static_cast(length); + + if (static_cast(result) != length) return status_out_of_memory; + + out_result = result; + return status_ok; + } + // we need to get length of entire file to load it in memory; the only (relatively) sane way to do it is via seek/tell trick - PUGI__FN xml_parse_status get_file_size(FILE* file, size_t& out_result) + PUGI_IMPL_FN xml_parse_status get_file_size(FILE* file, size_t& out_result) { - #if defined(PUGI__MSVC_CRT_VERSION) && PUGI__MSVC_CRT_VERSION >= 1400 - // there are 64-bit versions of fseek/ftell, let's use them - typedef __int64 length_type; + #if defined(__linux__) || defined(__APPLE__) + // this simultaneously retrieves the file size and file mode (to guard against loading non-files) + struct stat st; + if (fstat(fileno(file), &st) != 0) return status_io_error; + // anything that's not a regular file doesn't have a coherent length + if (!S_ISREG(st.st_mode)) return status_io_error; + + xml_parse_status status = convert_file_size(st.st_size, out_result); + #elif defined(PUGI_IMPL_MSVC_CRT_VERSION) && PUGI_IMPL_MSVC_CRT_VERSION >= 1400 + // there are 64-bit versions of fseek/ftell, let's use them _fseeki64(file, 0, SEEK_END); - length_type length = _ftelli64(file); + __int64 length = _ftelli64(file); _fseeki64(file, 0, SEEK_SET); + + xml_parse_status status = convert_file_size(length, out_result); #elif defined(__MINGW32__) && !defined(__NO_MINGW_LFS) && (!defined(__STRICT_ANSI__) || defined(__MINGW64_VERSION_MAJOR)) // there are 64-bit versions of fseek/ftell, let's use them - typedef off64_t length_type; - fseeko64(file, 0, SEEK_END); - length_type length = ftello64(file); + off64_t length = ftello64(file); fseeko64(file, 0, SEEK_SET); + + xml_parse_status status = convert_file_size(length, out_result); #else // if this is a 32-bit OS, long is enough; if this is a unix system, long is 64-bit, which is enough; otherwise we can't do anything anyway. - typedef long length_type; - fseek(file, 0, SEEK_END); - length_type length = ftell(file); + long length = ftell(file); fseek(file, 0, SEEK_SET); - #endif - - // check for I/O errors - if (length < 0) return status_io_error; - // check for overflow - size_t result = static_cast(length); - - if (static_cast(result) != length) return status_out_of_memory; - - // finalize - out_result = result; + xml_parse_status status = convert_file_size(length, out_result); + #endif - return status_ok; + return status; } // This function assumes that buffer has extra sizeof(char_t) writable bytes after size - PUGI__FN size_t zero_terminate_buffer(void* buffer, size_t size, xml_encoding encoding) + PUGI_IMPL_FN size_t zero_terminate_buffer(void* buffer, size_t size, xml_encoding encoding) { // We only need to zero-terminate if encoding conversion does not do it for us #ifdef PUGIXML_WCHAR_MODE @@ -4817,7 +4867,7 @@ PUGI__NS_BEGIN return size; } - PUGI__FN xml_parse_result load_file_impl(xml_document_struct* doc, FILE* file, unsigned int options, xml_encoding encoding, char_t** out_buffer) + PUGI_IMPL_FN xml_parse_result load_file_impl(xml_document_struct* doc, FILE* file, unsigned int options, xml_encoding encoding, char_t** out_buffer) { if (!file) return make_parse_result(status_file_not_found); @@ -4846,7 +4896,7 @@ PUGI__NS_BEGIN return load_buffer_impl(doc, doc, contents, zero_terminate_buffer(contents, size, real_encoding), options, real_encoding, true, true, out_buffer); } - PUGI__FN void close_file(FILE* file) + PUGI_IMPL_FN void close_file(FILE* file) { fclose(file); } @@ -4857,7 +4907,7 @@ PUGI__NS_BEGIN static xml_stream_chunk* create() { void* memory = xml_memory::allocate(sizeof(xml_stream_chunk)); - if (!memory) return 0; + if (!memory) return NULL; return new (memory) xml_stream_chunk(); } @@ -4875,7 +4925,7 @@ PUGI__NS_BEGIN } } - xml_stream_chunk(): next(0), size(0) + xml_stream_chunk(): next(NULL), size(0) { } @@ -4885,13 +4935,13 @@ PUGI__NS_BEGIN T data[xml_memory_page_size / sizeof(T)]; }; - template PUGI__FN xml_parse_status load_stream_data_noseek(std::basic_istream& stream, void** out_buffer, size_t* out_size) + template PUGI_IMPL_FN xml_parse_status load_stream_data_noseek(std::basic_istream& stream, void** out_buffer, size_t* out_size) { - auto_deleter > chunks(0, xml_stream_chunk::destroy); + auto_deleter > chunks(NULL, xml_stream_chunk::destroy); // read file to a chunk list size_t total = 0; - xml_stream_chunk* last = 0; + xml_stream_chunk* last = NULL; while (!stream.eof()) { @@ -4939,7 +4989,7 @@ PUGI__NS_BEGIN return status_ok; } - template PUGI__FN xml_parse_status load_stream_data_seek(std::basic_istream& stream, void** out_buffer, size_t* out_size) + template PUGI_IMPL_FN xml_parse_status load_stream_data_seek(std::basic_istream& stream, void** out_buffer, size_t* out_size) { // get length of remaining data in stream typename std::basic_istream::pos_type pos = stream.tellg(); @@ -4975,9 +5025,9 @@ PUGI__NS_BEGIN return status_ok; } - template PUGI__FN xml_parse_result load_stream_impl(xml_document_struct* doc, std::basic_istream& stream, unsigned int options, xml_encoding encoding, char_t** out_buffer) + template PUGI_IMPL_FN xml_parse_result load_stream_impl(xml_document_struct* doc, std::basic_istream& stream, unsigned int options, xml_encoding encoding, char_t** out_buffer) { - void* buffer = 0; + void* buffer = NULL; size_t size = 0; xml_parse_status status = status_ok; @@ -5001,18 +5051,18 @@ PUGI__NS_BEGIN } #endif -#if defined(PUGI__MSVC_CRT_VERSION) || defined(__BORLANDC__) || (defined(__MINGW32__) && (!defined(__STRICT_ANSI__) || defined(__MINGW64_VERSION_MAJOR))) - PUGI__FN FILE* open_file_wide(const wchar_t* path, const wchar_t* mode) +#if defined(PUGI_IMPL_MSVC_CRT_VERSION) || defined(__BORLANDC__) || (defined(__MINGW32__) && (!defined(__STRICT_ANSI__) || defined(__MINGW64_VERSION_MAJOR))) + PUGI_IMPL_FN FILE* open_file_wide(const wchar_t* path, const wchar_t* mode) { -#if defined(PUGI__MSVC_CRT_VERSION) && PUGI__MSVC_CRT_VERSION >= 1400 - FILE* file = 0; - return _wfopen_s(&file, path, mode) == 0 ? file : 0; +#if defined(PUGI_IMPL_MSVC_CRT_VERSION) && PUGI_IMPL_MSVC_CRT_VERSION >= 1400 + FILE* file = NULL; + return _wfopen_s(&file, path, mode) == 0 ? file : NULL; #else return _wfopen(path, mode); #endif } #else - PUGI__FN char* convert_path_heap(const wchar_t* str) + PUGI_IMPL_FN char* convert_path_heap(const wchar_t* str) { assert(str); @@ -5022,7 +5072,7 @@ PUGI__NS_BEGIN // allocate resulting string char* result = static_cast(xml_memory::allocate(size + 1)); - if (!result) return 0; + if (!result) return NULL; // second pass: convert to utf8 as_utf8_end(result, size, str, length); @@ -5033,11 +5083,11 @@ PUGI__NS_BEGIN return result; } - PUGI__FN FILE* open_file_wide(const wchar_t* path, const wchar_t* mode) + PUGI_IMPL_FN FILE* open_file_wide(const wchar_t* path, const wchar_t* mode) { // there is no standard function to open wide paths, so our best bet is to try utf8 path char* path_utf8 = convert_path_heap(path); - if (!path_utf8) return 0; + if (!path_utf8) return NULL; // convert mode to ASCII (we mirror _wfopen interface) char mode_ascii[4] = {0}; @@ -5053,17 +5103,17 @@ PUGI__NS_BEGIN } #endif - PUGI__FN FILE* open_file(const char* path, const char* mode) + PUGI_IMPL_FN FILE* open_file(const char* path, const char* mode) { -#if defined(PUGI__MSVC_CRT_VERSION) && PUGI__MSVC_CRT_VERSION >= 1400 - FILE* file = 0; - return fopen_s(&file, path, mode) == 0 ? file : 0; +#if defined(PUGI_IMPL_MSVC_CRT_VERSION) && PUGI_IMPL_MSVC_CRT_VERSION >= 1400 + FILE* file = NULL; + return fopen_s(&file, path, mode) == 0 ? file : NULL; #else return fopen(path, mode); #endif } - PUGI__FN bool save_file_impl(const xml_document& doc, FILE* file, const char_t* indent, unsigned int flags, xml_encoding encoding) + PUGI_IMPL_FN bool save_file_impl(const xml_document& doc, FILE* file, const char_t* indent, unsigned int flags, xml_encoding encoding) { if (!file) return false; @@ -5080,7 +5130,7 @@ PUGI__NS_BEGIN name_null_sentry(xml_node_struct* node_): node(node_), name(node_->name) { - node->name = 0; + node->name = NULL; } ~name_null_sentry() @@ -5088,30 +5138,34 @@ PUGI__NS_BEGIN node->name = name; } }; -PUGI__NS_END +PUGI_IMPL_NS_END namespace pugi { - PUGI__FN xml_writer_file::xml_writer_file(void* file_): file(file_) + PUGI_IMPL_FN xml_writer::~xml_writer() { } - PUGI__FN void xml_writer_file::write(const void* data, size_t size) + PUGI_IMPL_FN xml_writer_file::xml_writer_file(void* file_): file(file_) + { + } + + PUGI_IMPL_FN void xml_writer_file::write(const void* data, size_t size) { size_t result = fwrite(data, 1, size, static_cast(file)); (void)!result; // unfortunately we can't do proper error handling here } #ifndef PUGIXML_NO_STL - PUGI__FN xml_writer_stream::xml_writer_stream(std::basic_ostream >& stream): narrow_stream(&stream), wide_stream(0) + PUGI_IMPL_FN xml_writer_stream::xml_writer_stream(std::basic_ostream& stream): narrow_stream(&stream), wide_stream(NULL) { } - PUGI__FN xml_writer_stream::xml_writer_stream(std::basic_ostream >& stream): narrow_stream(0), wide_stream(&stream) + PUGI_IMPL_FN xml_writer_stream::xml_writer_stream(std::basic_ostream& stream): narrow_stream(NULL), wide_stream(&stream) { } - PUGI__FN void xml_writer_stream::write(const void* data, size_t size) + PUGI_IMPL_FN void xml_writer_stream::write(const void* data, size_t size) { if (narrow_stream) { @@ -5128,130 +5182,130 @@ namespace pugi } #endif - PUGI__FN xml_tree_walker::xml_tree_walker(): _depth(0) + PUGI_IMPL_FN xml_tree_walker::xml_tree_walker(): _depth(0) { } - PUGI__FN xml_tree_walker::~xml_tree_walker() + PUGI_IMPL_FN xml_tree_walker::~xml_tree_walker() { } - PUGI__FN int xml_tree_walker::depth() const + PUGI_IMPL_FN int xml_tree_walker::depth() const { return _depth; } - PUGI__FN bool xml_tree_walker::begin(xml_node&) + PUGI_IMPL_FN bool xml_tree_walker::begin(xml_node&) { return true; } - PUGI__FN bool xml_tree_walker::end(xml_node&) + PUGI_IMPL_FN bool xml_tree_walker::end(xml_node&) { return true; } - PUGI__FN xml_attribute::xml_attribute(): _attr(0) + PUGI_IMPL_FN xml_attribute::xml_attribute(): _attr(NULL) { } - PUGI__FN xml_attribute::xml_attribute(xml_attribute_struct* attr): _attr(attr) + PUGI_IMPL_FN xml_attribute::xml_attribute(xml_attribute_struct* attr): _attr(attr) { } - PUGI__FN static void unspecified_bool_xml_attribute(xml_attribute***) + PUGI_IMPL_FN static void unspecified_bool_xml_attribute(xml_attribute***) { } - PUGI__FN xml_attribute::operator xml_attribute::unspecified_bool_type() const + PUGI_IMPL_FN xml_attribute::operator xml_attribute::unspecified_bool_type() const { - return _attr ? unspecified_bool_xml_attribute : 0; + return _attr ? unspecified_bool_xml_attribute : NULL; } - PUGI__FN bool xml_attribute::operator!() const + PUGI_IMPL_FN bool xml_attribute::operator!() const { return !_attr; } - PUGI__FN bool xml_attribute::operator==(const xml_attribute& r) const + PUGI_IMPL_FN bool xml_attribute::operator==(const xml_attribute& r) const { return (_attr == r._attr); } - PUGI__FN bool xml_attribute::operator!=(const xml_attribute& r) const + PUGI_IMPL_FN bool xml_attribute::operator!=(const xml_attribute& r) const { return (_attr != r._attr); } - PUGI__FN bool xml_attribute::operator<(const xml_attribute& r) const + PUGI_IMPL_FN bool xml_attribute::operator<(const xml_attribute& r) const { return (_attr < r._attr); } - PUGI__FN bool xml_attribute::operator>(const xml_attribute& r) const + PUGI_IMPL_FN bool xml_attribute::operator>(const xml_attribute& r) const { return (_attr > r._attr); } - PUGI__FN bool xml_attribute::operator<=(const xml_attribute& r) const + PUGI_IMPL_FN bool xml_attribute::operator<=(const xml_attribute& r) const { return (_attr <= r._attr); } - PUGI__FN bool xml_attribute::operator>=(const xml_attribute& r) const + PUGI_IMPL_FN bool xml_attribute::operator>=(const xml_attribute& r) const { return (_attr >= r._attr); } - PUGI__FN xml_attribute xml_attribute::next_attribute() const + PUGI_IMPL_FN xml_attribute xml_attribute::next_attribute() const { if (!_attr) return xml_attribute(); return xml_attribute(_attr->next_attribute); } - PUGI__FN xml_attribute xml_attribute::previous_attribute() const + PUGI_IMPL_FN xml_attribute xml_attribute::previous_attribute() const { if (!_attr) return xml_attribute(); xml_attribute_struct* prev = _attr->prev_attribute_c; return prev->next_attribute ? xml_attribute(prev) : xml_attribute(); } - PUGI__FN const char_t* xml_attribute::as_string(const char_t* def) const + PUGI_IMPL_FN const char_t* xml_attribute::as_string(const char_t* def) const { if (!_attr) return def; const char_t* value = _attr->value; return value ? value : def; } - PUGI__FN int xml_attribute::as_int(int def) const + PUGI_IMPL_FN int xml_attribute::as_int(int def) const { if (!_attr) return def; const char_t* value = _attr->value; return value ? impl::get_value_int(value) : def; } - PUGI__FN unsigned int xml_attribute::as_uint(unsigned int def) const + PUGI_IMPL_FN unsigned int xml_attribute::as_uint(unsigned int def) const { if (!_attr) return def; const char_t* value = _attr->value; return value ? impl::get_value_uint(value) : def; } - PUGI__FN double xml_attribute::as_double(double def) const + PUGI_IMPL_FN double xml_attribute::as_double(double def) const { if (!_attr) return def; const char_t* value = _attr->value; return value ? impl::get_value_double(value) : def; } - PUGI__FN float xml_attribute::as_float(float def) const + PUGI_IMPL_FN float xml_attribute::as_float(float def) const { if (!_attr) return def; const char_t* value = _attr->value; return value ? impl::get_value_float(value) : def; } - PUGI__FN bool xml_attribute::as_bool(bool def) const + PUGI_IMPL_FN bool xml_attribute::as_bool(bool def) const { if (!_attr) return def; const char_t* value = _attr->value; @@ -5259,14 +5313,14 @@ namespace pugi } #ifdef PUGIXML_HAS_LONG_LONG - PUGI__FN long long xml_attribute::as_llong(long long def) const + PUGI_IMPL_FN long long xml_attribute::as_llong(long long def) const { if (!_attr) return def; const char_t* value = _attr->value; return value ? impl::get_value_llong(value) : def; } - PUGI__FN unsigned long long xml_attribute::as_ullong(unsigned long long def) const + PUGI_IMPL_FN unsigned long long xml_attribute::as_ullong(unsigned long long def) const { if (!_attr) return def; const char_t* value = _attr->value; @@ -5274,175 +5328,182 @@ namespace pugi } #endif - PUGI__FN bool xml_attribute::empty() const + PUGI_IMPL_FN bool xml_attribute::empty() const { return !_attr; } - PUGI__FN const char_t* xml_attribute::name() const + PUGI_IMPL_FN const char_t* xml_attribute::name() const { if (!_attr) return PUGIXML_TEXT(""); const char_t* name = _attr->name; return name ? name : PUGIXML_TEXT(""); } - PUGI__FN const char_t* xml_attribute::value() const + PUGI_IMPL_FN const char_t* xml_attribute::value() const { if (!_attr) return PUGIXML_TEXT(""); const char_t* value = _attr->value; return value ? value : PUGIXML_TEXT(""); } - PUGI__FN size_t xml_attribute::hash_value() const + PUGI_IMPL_FN size_t xml_attribute::hash_value() const { - return static_cast(reinterpret_cast(_attr) / sizeof(xml_attribute_struct)); + return reinterpret_cast(_attr) / sizeof(xml_attribute_struct); } - PUGI__FN xml_attribute_struct* xml_attribute::internal_object() const + PUGI_IMPL_FN xml_attribute_struct* xml_attribute::internal_object() const { return _attr; } - PUGI__FN xml_attribute& xml_attribute::operator=(const char_t* rhs) + PUGI_IMPL_FN xml_attribute& xml_attribute::operator=(const char_t* rhs) { set_value(rhs); return *this; } - PUGI__FN xml_attribute& xml_attribute::operator=(int rhs) + PUGI_IMPL_FN xml_attribute& xml_attribute::operator=(int rhs) { set_value(rhs); return *this; } - PUGI__FN xml_attribute& xml_attribute::operator=(unsigned int rhs) + PUGI_IMPL_FN xml_attribute& xml_attribute::operator=(unsigned int rhs) { set_value(rhs); return *this; } - PUGI__FN xml_attribute& xml_attribute::operator=(long rhs) + PUGI_IMPL_FN xml_attribute& xml_attribute::operator=(long rhs) { set_value(rhs); return *this; } - PUGI__FN xml_attribute& xml_attribute::operator=(unsigned long rhs) + PUGI_IMPL_FN xml_attribute& xml_attribute::operator=(unsigned long rhs) { set_value(rhs); return *this; } - PUGI__FN xml_attribute& xml_attribute::operator=(double rhs) + PUGI_IMPL_FN xml_attribute& xml_attribute::operator=(double rhs) { set_value(rhs); return *this; } - PUGI__FN xml_attribute& xml_attribute::operator=(float rhs) + PUGI_IMPL_FN xml_attribute& xml_attribute::operator=(float rhs) { set_value(rhs); return *this; } - PUGI__FN xml_attribute& xml_attribute::operator=(bool rhs) + PUGI_IMPL_FN xml_attribute& xml_attribute::operator=(bool rhs) { set_value(rhs); return *this; } #ifdef PUGIXML_HAS_LONG_LONG - PUGI__FN xml_attribute& xml_attribute::operator=(long long rhs) + PUGI_IMPL_FN xml_attribute& xml_attribute::operator=(long long rhs) { set_value(rhs); return *this; } - PUGI__FN xml_attribute& xml_attribute::operator=(unsigned long long rhs) + PUGI_IMPL_FN xml_attribute& xml_attribute::operator=(unsigned long long rhs) { set_value(rhs); return *this; } #endif - PUGI__FN bool xml_attribute::set_name(const char_t* rhs) + PUGI_IMPL_FN bool xml_attribute::set_name(const char_t* rhs) { if (!_attr) return false; return impl::strcpy_insitu(_attr->name, _attr->header, impl::xml_memory_page_name_allocated_mask, rhs, impl::strlength(rhs)); } - PUGI__FN bool xml_attribute::set_value(const char_t* rhs, size_t sz) + PUGI_IMPL_FN bool xml_attribute::set_name(const char_t* rhs, size_t size) { if (!_attr) return false; - return impl::strcpy_insitu(_attr->value, _attr->header, impl::xml_memory_page_value_allocated_mask, rhs, sz); + return impl::strcpy_insitu(_attr->name, _attr->header, impl::xml_memory_page_name_allocated_mask, rhs, size); } - PUGI__FN bool xml_attribute::set_value(const char_t* rhs) + PUGI_IMPL_FN bool xml_attribute::set_value(const char_t* rhs) { if (!_attr) return false; return impl::strcpy_insitu(_attr->value, _attr->header, impl::xml_memory_page_value_allocated_mask, rhs, impl::strlength(rhs)); } - PUGI__FN bool xml_attribute::set_value(int rhs) + PUGI_IMPL_FN bool xml_attribute::set_value(const char_t* rhs, size_t size) + { + if (!_attr) return false; + + return impl::strcpy_insitu(_attr->value, _attr->header, impl::xml_memory_page_value_allocated_mask, rhs, size); + } + + PUGI_IMPL_FN bool xml_attribute::set_value(int rhs) { if (!_attr) return false; return impl::set_value_integer(_attr->value, _attr->header, impl::xml_memory_page_value_allocated_mask, rhs, rhs < 0); } - PUGI__FN bool xml_attribute::set_value(unsigned int rhs) + PUGI_IMPL_FN bool xml_attribute::set_value(unsigned int rhs) { if (!_attr) return false; return impl::set_value_integer(_attr->value, _attr->header, impl::xml_memory_page_value_allocated_mask, rhs, false); } - PUGI__FN bool xml_attribute::set_value(long rhs) + PUGI_IMPL_FN bool xml_attribute::set_value(long rhs) { if (!_attr) return false; return impl::set_value_integer(_attr->value, _attr->header, impl::xml_memory_page_value_allocated_mask, rhs, rhs < 0); } - PUGI__FN bool xml_attribute::set_value(unsigned long rhs) + PUGI_IMPL_FN bool xml_attribute::set_value(unsigned long rhs) { if (!_attr) return false; return impl::set_value_integer(_attr->value, _attr->header, impl::xml_memory_page_value_allocated_mask, rhs, false); } - PUGI__FN bool xml_attribute::set_value(double rhs) + PUGI_IMPL_FN bool xml_attribute::set_value(double rhs) { if (!_attr) return false; return impl::set_value_convert(_attr->value, _attr->header, impl::xml_memory_page_value_allocated_mask, rhs, default_double_precision); } - PUGI__FN bool xml_attribute::set_value(double rhs, int precision) + PUGI_IMPL_FN bool xml_attribute::set_value(double rhs, int precision) { if (!_attr) return false; return impl::set_value_convert(_attr->value, _attr->header, impl::xml_memory_page_value_allocated_mask, rhs, precision); } - PUGI__FN bool xml_attribute::set_value(float rhs) + PUGI_IMPL_FN bool xml_attribute::set_value(float rhs) { if (!_attr) return false; return impl::set_value_convert(_attr->value, _attr->header, impl::xml_memory_page_value_allocated_mask, rhs, default_float_precision); } - PUGI__FN bool xml_attribute::set_value(float rhs, int precision) + PUGI_IMPL_FN bool xml_attribute::set_value(float rhs, int precision) { if (!_attr) return false; return impl::set_value_convert(_attr->value, _attr->header, impl::xml_memory_page_value_allocated_mask, rhs, precision); } - PUGI__FN bool xml_attribute::set_value(bool rhs) + PUGI_IMPL_FN bool xml_attribute::set_value(bool rhs) { if (!_attr) return false; @@ -5450,14 +5511,14 @@ namespace pugi } #ifdef PUGIXML_HAS_LONG_LONG - PUGI__FN bool xml_attribute::set_value(long long rhs) + PUGI_IMPL_FN bool xml_attribute::set_value(long long rhs) { if (!_attr) return false; return impl::set_value_integer(_attr->value, _attr->header, impl::xml_memory_page_value_allocated_mask, rhs, rhs < 0); } - PUGI__FN bool xml_attribute::set_value(unsigned long long rhs) + PUGI_IMPL_FN bool xml_attribute::set_value(unsigned long long rhs) { if (!_attr) return false; @@ -5466,129 +5527,129 @@ namespace pugi #endif #ifdef __BORLANDC__ - PUGI__FN bool operator&&(const xml_attribute& lhs, bool rhs) + PUGI_IMPL_FN bool operator&&(const xml_attribute& lhs, bool rhs) { return (bool)lhs && rhs; } - PUGI__FN bool operator||(const xml_attribute& lhs, bool rhs) + PUGI_IMPL_FN bool operator||(const xml_attribute& lhs, bool rhs) { return (bool)lhs || rhs; } #endif - PUGI__FN xml_node::xml_node(): _root(0) + PUGI_IMPL_FN xml_node::xml_node(): _root(NULL) { } - PUGI__FN xml_node::xml_node(xml_node_struct* p): _root(p) + PUGI_IMPL_FN xml_node::xml_node(xml_node_struct* p): _root(p) { } - PUGI__FN static void unspecified_bool_xml_node(xml_node***) + PUGI_IMPL_FN static void unspecified_bool_xml_node(xml_node***) { } - PUGI__FN xml_node::operator xml_node::unspecified_bool_type() const + PUGI_IMPL_FN xml_node::operator xml_node::unspecified_bool_type() const { - return _root ? unspecified_bool_xml_node : 0; + return _root ? unspecified_bool_xml_node : NULL; } - PUGI__FN bool xml_node::operator!() const + PUGI_IMPL_FN bool xml_node::operator!() const { return !_root; } - PUGI__FN xml_node::iterator xml_node::begin() const + PUGI_IMPL_FN xml_node::iterator xml_node::begin() const { - return iterator(_root ? _root->first_child + 0 : 0, _root); + return iterator(_root ? _root->first_child + 0 : NULL, _root); } - PUGI__FN xml_node::iterator xml_node::end() const + PUGI_IMPL_FN xml_node::iterator xml_node::end() const { - return iterator(0, _root); + return iterator(NULL, _root); } - PUGI__FN xml_node::attribute_iterator xml_node::attributes_begin() const + PUGI_IMPL_FN xml_node::attribute_iterator xml_node::attributes_begin() const { - return attribute_iterator(_root ? _root->first_attribute + 0 : 0, _root); + return attribute_iterator(_root ? _root->first_attribute + 0 : NULL, _root); } - PUGI__FN xml_node::attribute_iterator xml_node::attributes_end() const + PUGI_IMPL_FN xml_node::attribute_iterator xml_node::attributes_end() const { - return attribute_iterator(0, _root); + return attribute_iterator(NULL, _root); } - PUGI__FN xml_object_range xml_node::children() const + PUGI_IMPL_FN xml_object_range xml_node::children() const { return xml_object_range(begin(), end()); } - PUGI__FN xml_object_range xml_node::children(const char_t* name_) const + PUGI_IMPL_FN xml_object_range xml_node::children(const char_t* name_) const { - return xml_object_range(xml_named_node_iterator(child(name_)._root, _root, name_), xml_named_node_iterator(0, _root, name_)); + return xml_object_range(xml_named_node_iterator(child(name_)._root, _root, name_), xml_named_node_iterator(NULL, _root, name_)); } - PUGI__FN xml_object_range xml_node::attributes() const + PUGI_IMPL_FN xml_object_range xml_node::attributes() const { return xml_object_range(attributes_begin(), attributes_end()); } - PUGI__FN bool xml_node::operator==(const xml_node& r) const + PUGI_IMPL_FN bool xml_node::operator==(const xml_node& r) const { return (_root == r._root); } - PUGI__FN bool xml_node::operator!=(const xml_node& r) const + PUGI_IMPL_FN bool xml_node::operator!=(const xml_node& r) const { return (_root != r._root); } - PUGI__FN bool xml_node::operator<(const xml_node& r) const + PUGI_IMPL_FN bool xml_node::operator<(const xml_node& r) const { return (_root < r._root); } - PUGI__FN bool xml_node::operator>(const xml_node& r) const + PUGI_IMPL_FN bool xml_node::operator>(const xml_node& r) const { return (_root > r._root); } - PUGI__FN bool xml_node::operator<=(const xml_node& r) const + PUGI_IMPL_FN bool xml_node::operator<=(const xml_node& r) const { return (_root <= r._root); } - PUGI__FN bool xml_node::operator>=(const xml_node& r) const + PUGI_IMPL_FN bool xml_node::operator>=(const xml_node& r) const { return (_root >= r._root); } - PUGI__FN bool xml_node::empty() const + PUGI_IMPL_FN bool xml_node::empty() const { return !_root; } - PUGI__FN const char_t* xml_node::name() const + PUGI_IMPL_FN const char_t* xml_node::name() const { if (!_root) return PUGIXML_TEXT(""); const char_t* name = _root->name; return name ? name : PUGIXML_TEXT(""); } - PUGI__FN xml_node_type xml_node::type() const + PUGI_IMPL_FN xml_node_type xml_node::type() const { - return _root ? PUGI__NODETYPE(_root) : node_null; + return _root ? PUGI_IMPL_NODETYPE(_root) : node_null; } - PUGI__FN const char_t* xml_node::value() const + PUGI_IMPL_FN const char_t* xml_node::value() const { if (!_root) return PUGIXML_TEXT(""); const char_t* value = _root->value; return value ? value : PUGIXML_TEXT(""); } - PUGI__FN xml_node xml_node::child(const char_t* name_) const + PUGI_IMPL_FN xml_node xml_node::child(const char_t* name_) const { if (!_root) return xml_node(); @@ -5602,7 +5663,7 @@ namespace pugi return xml_node(); } - PUGI__FN xml_attribute xml_node::attribute(const char_t* name_) const + PUGI_IMPL_FN xml_attribute xml_node::attribute(const char_t* name_) const { if (!_root) return xml_attribute(); @@ -5616,7 +5677,7 @@ namespace pugi return xml_attribute(); } - PUGI__FN xml_node xml_node::next_sibling(const char_t* name_) const + PUGI_IMPL_FN xml_node xml_node::next_sibling(const char_t* name_) const { if (!_root) return xml_node(); @@ -5630,12 +5691,12 @@ namespace pugi return xml_node(); } - PUGI__FN xml_node xml_node::next_sibling() const + PUGI_IMPL_FN xml_node xml_node::next_sibling() const { return _root ? xml_node(_root->next_sibling) : xml_node(); } - PUGI__FN xml_node xml_node::previous_sibling(const char_t* name_) const + PUGI_IMPL_FN xml_node xml_node::previous_sibling(const char_t* name_) const { if (!_root) return xml_node(); @@ -5649,7 +5710,7 @@ namespace pugi return xml_node(); } - PUGI__FN xml_attribute xml_node::attribute(const char_t* name_, xml_attribute& hint_) const + PUGI_IMPL_FN xml_attribute xml_node::attribute(const char_t* name_, xml_attribute& hint_) const { xml_attribute_struct* hint = hint_._attr; @@ -5688,34 +5749,34 @@ namespace pugi return xml_attribute(); } - PUGI__FN xml_node xml_node::previous_sibling() const + PUGI_IMPL_FN xml_node xml_node::previous_sibling() const { if (!_root) return xml_node(); xml_node_struct* prev = _root->prev_sibling_c; return prev->next_sibling ? xml_node(prev) : xml_node(); } - PUGI__FN xml_node xml_node::parent() const + PUGI_IMPL_FN xml_node xml_node::parent() const { return _root ? xml_node(_root->parent) : xml_node(); } - PUGI__FN xml_node xml_node::root() const + PUGI_IMPL_FN xml_node xml_node::root() const { return _root ? xml_node(&impl::get_document(_root)) : xml_node(); } - PUGI__FN xml_text xml_node::text() const + PUGI_IMPL_FN xml_text xml_node::text() const { return xml_text(_root); } - PUGI__FN const char_t* xml_node::child_value() const + PUGI_IMPL_FN const char_t* xml_node::child_value() const { if (!_root) return PUGIXML_TEXT(""); // element nodes can have value if parse_embed_pcdata was used - if (PUGI__NODETYPE(_root) == node_element && _root->value) + if (PUGI_IMPL_NODETYPE(_root) == node_element && _root->value) return _root->value; for (xml_node_struct* i = _root->first_child; i; i = i->next_sibling) @@ -5728,40 +5789,40 @@ namespace pugi return PUGIXML_TEXT(""); } - PUGI__FN const char_t* xml_node::child_value(const char_t* name_) const + PUGI_IMPL_FN const char_t* xml_node::child_value(const char_t* name_) const { return child(name_).child_value(); } - PUGI__FN xml_attribute xml_node::first_attribute() const + PUGI_IMPL_FN xml_attribute xml_node::first_attribute() const { if (!_root) return xml_attribute(); return xml_attribute(_root->first_attribute); } - PUGI__FN xml_attribute xml_node::last_attribute() const + PUGI_IMPL_FN xml_attribute xml_node::last_attribute() const { if (!_root) return xml_attribute(); xml_attribute_struct* first = _root->first_attribute; return first ? xml_attribute(first->prev_attribute_c) : xml_attribute(); } - PUGI__FN xml_node xml_node::first_child() const + PUGI_IMPL_FN xml_node xml_node::first_child() const { if (!_root) return xml_node(); return xml_node(_root->first_child); } - PUGI__FN xml_node xml_node::last_child() const + PUGI_IMPL_FN xml_node xml_node::last_child() const { if (!_root) return xml_node(); xml_node_struct* first = _root->first_child; return first ? xml_node(first->prev_sibling_c) : xml_node(); } - PUGI__FN bool xml_node::set_name(const char_t* rhs) + PUGI_IMPL_FN bool xml_node::set_name(const char_t* rhs) { - xml_node_type type_ = _root ? PUGI__NODETYPE(_root) : node_null; + xml_node_type type_ = _root ? PUGI_IMPL_NODETYPE(_root) : node_null; if (type_ != node_element && type_ != node_pi && type_ != node_declaration) return false; @@ -5769,19 +5830,19 @@ namespace pugi return impl::strcpy_insitu(_root->name, _root->header, impl::xml_memory_page_name_allocated_mask, rhs, impl::strlength(rhs)); } - PUGI__FN bool xml_node::set_value(const char_t* rhs, size_t sz) + PUGI_IMPL_FN bool xml_node::set_name(const char_t* rhs, size_t size) { - xml_node_type type_ = _root ? PUGI__NODETYPE(_root) : node_null; + xml_node_type type_ = _root ? PUGI_IMPL_NODETYPE(_root) : node_null; - if (type_ != node_pcdata && type_ != node_cdata && type_ != node_comment && type_ != node_pi && type_ != node_doctype) + if (type_ != node_element && type_ != node_pi && type_ != node_declaration) return false; - return impl::strcpy_insitu(_root->value, _root->header, impl::xml_memory_page_value_allocated_mask, rhs, sz); + return impl::strcpy_insitu(_root->name, _root->header, impl::xml_memory_page_name_allocated_mask, rhs, size); } - PUGI__FN bool xml_node::set_value(const char_t* rhs) + PUGI_IMPL_FN bool xml_node::set_value(const char_t* rhs) { - xml_node_type type_ = _root ? PUGI__NODETYPE(_root) : node_null; + xml_node_type type_ = _root ? PUGI_IMPL_NODETYPE(_root) : node_null; if (type_ != node_pcdata && type_ != node_cdata && type_ != node_comment && type_ != node_pi && type_ != node_doctype) return false; @@ -5789,7 +5850,17 @@ namespace pugi return impl::strcpy_insitu(_root->value, _root->header, impl::xml_memory_page_value_allocated_mask, rhs, impl::strlength(rhs)); } - PUGI__FN xml_attribute xml_node::append_attribute(const char_t* name_) + PUGI_IMPL_FN bool xml_node::set_value(const char_t* rhs, size_t size) + { + xml_node_type type_ = _root ? PUGI_IMPL_NODETYPE(_root) : node_null; + + if (type_ != node_pcdata && type_ != node_cdata && type_ != node_comment && type_ != node_pi && type_ != node_doctype) + return false; + + return impl::strcpy_insitu(_root->value, _root->header, impl::xml_memory_page_value_allocated_mask, rhs, size); + } + + PUGI_IMPL_FN xml_attribute xml_node::append_attribute(const char_t* name_) { if (!impl::allow_insert_attribute(type())) return xml_attribute(); @@ -5806,7 +5877,7 @@ namespace pugi return a; } - PUGI__FN xml_attribute xml_node::prepend_attribute(const char_t* name_) + PUGI_IMPL_FN xml_attribute xml_node::prepend_attribute(const char_t* name_) { if (!impl::allow_insert_attribute(type())) return xml_attribute(); @@ -5823,7 +5894,7 @@ namespace pugi return a; } - PUGI__FN xml_attribute xml_node::insert_attribute_after(const char_t* name_, const xml_attribute& attr) + PUGI_IMPL_FN xml_attribute xml_node::insert_attribute_after(const char_t* name_, const xml_attribute& attr) { if (!impl::allow_insert_attribute(type())) return xml_attribute(); if (!attr || !impl::is_attribute_of(attr._attr, _root)) return xml_attribute(); @@ -5841,7 +5912,7 @@ namespace pugi return a; } - PUGI__FN xml_attribute xml_node::insert_attribute_before(const char_t* name_, const xml_attribute& attr) + PUGI_IMPL_FN xml_attribute xml_node::insert_attribute_before(const char_t* name_, const xml_attribute& attr) { if (!impl::allow_insert_attribute(type())) return xml_attribute(); if (!attr || !impl::is_attribute_of(attr._attr, _root)) return xml_attribute(); @@ -5859,7 +5930,7 @@ namespace pugi return a; } - PUGI__FN xml_attribute xml_node::append_copy(const xml_attribute& proto) + PUGI_IMPL_FN xml_attribute xml_node::append_copy(const xml_attribute& proto) { if (!proto) return xml_attribute(); if (!impl::allow_insert_attribute(type())) return xml_attribute(); @@ -5876,7 +5947,7 @@ namespace pugi return a; } - PUGI__FN xml_attribute xml_node::prepend_copy(const xml_attribute& proto) + PUGI_IMPL_FN xml_attribute xml_node::prepend_copy(const xml_attribute& proto) { if (!proto) return xml_attribute(); if (!impl::allow_insert_attribute(type())) return xml_attribute(); @@ -5893,7 +5964,7 @@ namespace pugi return a; } - PUGI__FN xml_attribute xml_node::insert_copy_after(const xml_attribute& proto, const xml_attribute& attr) + PUGI_IMPL_FN xml_attribute xml_node::insert_copy_after(const xml_attribute& proto, const xml_attribute& attr) { if (!proto) return xml_attribute(); if (!impl::allow_insert_attribute(type())) return xml_attribute(); @@ -5911,7 +5982,7 @@ namespace pugi return a; } - PUGI__FN xml_attribute xml_node::insert_copy_before(const xml_attribute& proto, const xml_attribute& attr) + PUGI_IMPL_FN xml_attribute xml_node::insert_copy_before(const xml_attribute& proto, const xml_attribute& attr) { if (!proto) return xml_attribute(); if (!impl::allow_insert_attribute(type())) return xml_attribute(); @@ -5929,7 +6000,7 @@ namespace pugi return a; } - PUGI__FN xml_node xml_node::append_child(xml_node_type type_) + PUGI_IMPL_FN xml_node xml_node::append_child(xml_node_type type_) { if (!impl::allow_insert_child(type(), type_)) return xml_node(); @@ -5946,7 +6017,7 @@ namespace pugi return n; } - PUGI__FN xml_node xml_node::prepend_child(xml_node_type type_) + PUGI_IMPL_FN xml_node xml_node::prepend_child(xml_node_type type_) { if (!impl::allow_insert_child(type(), type_)) return xml_node(); @@ -5963,7 +6034,7 @@ namespace pugi return n; } - PUGI__FN xml_node xml_node::insert_child_before(xml_node_type type_, const xml_node& node) + PUGI_IMPL_FN xml_node xml_node::insert_child_before(xml_node_type type_, const xml_node& node) { if (!impl::allow_insert_child(type(), type_)) return xml_node(); if (!node._root || node._root->parent != _root) return xml_node(); @@ -5981,7 +6052,7 @@ namespace pugi return n; } - PUGI__FN xml_node xml_node::insert_child_after(xml_node_type type_, const xml_node& node) + PUGI_IMPL_FN xml_node xml_node::insert_child_after(xml_node_type type_, const xml_node& node) { if (!impl::allow_insert_child(type(), type_)) return xml_node(); if (!node._root || node._root->parent != _root) return xml_node(); @@ -5999,7 +6070,7 @@ namespace pugi return n; } - PUGI__FN xml_node xml_node::append_child(const char_t* name_) + PUGI_IMPL_FN xml_node xml_node::append_child(const char_t* name_) { xml_node result = append_child(node_element); @@ -6008,7 +6079,7 @@ namespace pugi return result; } - PUGI__FN xml_node xml_node::prepend_child(const char_t* name_) + PUGI_IMPL_FN xml_node xml_node::prepend_child(const char_t* name_) { xml_node result = prepend_child(node_element); @@ -6017,7 +6088,7 @@ namespace pugi return result; } - PUGI__FN xml_node xml_node::insert_child_after(const char_t* name_, const xml_node& node) + PUGI_IMPL_FN xml_node xml_node::insert_child_after(const char_t* name_, const xml_node& node) { xml_node result = insert_child_after(node_element, node); @@ -6026,7 +6097,7 @@ namespace pugi return result; } - PUGI__FN xml_node xml_node::insert_child_before(const char_t* name_, const xml_node& node) + PUGI_IMPL_FN xml_node xml_node::insert_child_before(const char_t* name_, const xml_node& node) { xml_node result = insert_child_before(node_element, node); @@ -6035,7 +6106,7 @@ namespace pugi return result; } - PUGI__FN xml_node xml_node::append_copy(const xml_node& proto) + PUGI_IMPL_FN xml_node xml_node::append_copy(const xml_node& proto) { xml_node_type type_ = proto.type(); if (!impl::allow_insert_child(type(), type_)) return xml_node(); @@ -6052,7 +6123,7 @@ namespace pugi return n; } - PUGI__FN xml_node xml_node::prepend_copy(const xml_node& proto) + PUGI_IMPL_FN xml_node xml_node::prepend_copy(const xml_node& proto) { xml_node_type type_ = proto.type(); if (!impl::allow_insert_child(type(), type_)) return xml_node(); @@ -6069,7 +6140,7 @@ namespace pugi return n; } - PUGI__FN xml_node xml_node::insert_copy_after(const xml_node& proto, const xml_node& node) + PUGI_IMPL_FN xml_node xml_node::insert_copy_after(const xml_node& proto, const xml_node& node) { xml_node_type type_ = proto.type(); if (!impl::allow_insert_child(type(), type_)) return xml_node(); @@ -6087,7 +6158,7 @@ namespace pugi return n; } - PUGI__FN xml_node xml_node::insert_copy_before(const xml_node& proto, const xml_node& node) + PUGI_IMPL_FN xml_node xml_node::insert_copy_before(const xml_node& proto, const xml_node& node) { xml_node_type type_ = proto.type(); if (!impl::allow_insert_child(type(), type_)) return xml_node(); @@ -6105,7 +6176,7 @@ namespace pugi return n; } - PUGI__FN xml_node xml_node::append_move(const xml_node& moved) + PUGI_IMPL_FN xml_node xml_node::append_move(const xml_node& moved) { if (!impl::allow_move(*this, moved)) return xml_node(); @@ -6121,7 +6192,7 @@ namespace pugi return moved; } - PUGI__FN xml_node xml_node::prepend_move(const xml_node& moved) + PUGI_IMPL_FN xml_node xml_node::prepend_move(const xml_node& moved) { if (!impl::allow_move(*this, moved)) return xml_node(); @@ -6137,7 +6208,7 @@ namespace pugi return moved; } - PUGI__FN xml_node xml_node::insert_move_after(const xml_node& moved, const xml_node& node) + PUGI_IMPL_FN xml_node xml_node::insert_move_after(const xml_node& moved, const xml_node& node) { if (!impl::allow_move(*this, moved)) return xml_node(); if (!node._root || node._root->parent != _root) return xml_node(); @@ -6155,7 +6226,7 @@ namespace pugi return moved; } - PUGI__FN xml_node xml_node::insert_move_before(const xml_node& moved, const xml_node& node) + PUGI_IMPL_FN xml_node xml_node::insert_move_before(const xml_node& moved, const xml_node& node) { if (!impl::allow_move(*this, moved)) return xml_node(); if (!node._root || node._root->parent != _root) return xml_node(); @@ -6173,12 +6244,12 @@ namespace pugi return moved; } - PUGI__FN bool xml_node::remove_attribute(const char_t* name_) + PUGI_IMPL_FN bool xml_node::remove_attribute(const char_t* name_) { return remove_attribute(attribute(name_)); } - PUGI__FN bool xml_node::remove_attribute(const xml_attribute& a) + PUGI_IMPL_FN bool xml_node::remove_attribute(const xml_attribute& a) { if (!_root || !a._attr) return false; if (!impl::is_attribute_of(a._attr, _root)) return false; @@ -6192,7 +6263,7 @@ namespace pugi return true; } - PUGI__FN bool xml_node::remove_attributes() + PUGI_IMPL_FN bool xml_node::remove_attributes() { if (!_root) return false; @@ -6208,17 +6279,17 @@ namespace pugi attr = next; } - _root->first_attribute = 0; + _root->first_attribute = NULL; return true; } - PUGI__FN bool xml_node::remove_child(const char_t* name_) + PUGI_IMPL_FN bool xml_node::remove_child(const char_t* name_) { return remove_child(child(name_)); } - PUGI__FN bool xml_node::remove_child(const xml_node& n) + PUGI_IMPL_FN bool xml_node::remove_child(const xml_node& n) { if (!_root || !n._root || n._root->parent != _root) return false; @@ -6231,7 +6302,7 @@ namespace pugi return true; } - PUGI__FN bool xml_node::remove_children() + PUGI_IMPL_FN bool xml_node::remove_children() { if (!_root) return false; @@ -6247,16 +6318,19 @@ namespace pugi cur = next; } - _root->first_child = 0; + _root->first_child = NULL; return true; } - PUGI__FN xml_parse_result xml_node::append_buffer(const void* contents, size_t size, unsigned int options, xml_encoding encoding) + PUGI_IMPL_FN xml_parse_result xml_node::append_buffer(const void* contents, size_t size, unsigned int options, xml_encoding encoding) { // append_buffer is only valid for elements/documents if (!impl::allow_insert_child(type(), node_element)) return impl::make_parse_result(status_append_invalid_root); + // append buffer can not merge PCDATA into existing PCDATA nodes + if ((options & parse_merge_pcdata) != 0 && last_child().type() == node_pcdata) return impl::make_parse_result(status_append_invalid_root); + // get document node impl::xml_document_struct* doc = &impl::get_document(_root); @@ -6264,7 +6338,7 @@ namespace pugi doc->header |= impl::xml_memory_page_contents_shared_mask; // get extra buffer element (we'll store the document fragment buffer there so that we can deallocate it later) - impl::xml_memory_page* page = 0; + impl::xml_memory_page* page = NULL; impl::xml_extra_buffer* extra = static_cast(doc->allocate_memory(sizeof(impl::xml_extra_buffer) + sizeof(void*), page)); (void)page; @@ -6277,7 +6351,7 @@ namespace pugi #endif // add extra buffer to the list - extra->buffer = 0; + extra->buffer = NULL; extra->next = doc->extra_buffers; doc->extra_buffers = extra; @@ -6287,7 +6361,7 @@ namespace pugi return impl::load_buffer_impl(doc, _root, const_cast(contents), size, options, encoding, false, false, &extra->buffer); } - PUGI__FN xml_node xml_node::find_child_by_attribute(const char_t* name_, const char_t* attr_name, const char_t* attr_value) const + PUGI_IMPL_FN xml_node xml_node::find_child_by_attribute(const char_t* name_, const char_t* attr_name, const char_t* attr_value) const { if (!_root) return xml_node(); @@ -6312,7 +6386,7 @@ namespace pugi return xml_node(); } - PUGI__FN xml_node xml_node::find_child_by_attribute(const char_t* attr_name, const char_t* attr_value) const + PUGI_IMPL_FN xml_node xml_node::find_child_by_attribute(const char_t* attr_name, const char_t* attr_value) const { if (!_root) return xml_node(); @@ -6332,7 +6406,7 @@ namespace pugi } #ifndef PUGIXML_NO_STL - PUGI__FN string_t xml_node::path(char_t delimiter) const + PUGI_IMPL_FN string_t xml_node::path(char_t delimiter) const { if (!_root) return string_t(); @@ -6369,7 +6443,7 @@ namespace pugi } #endif - PUGI__FN xml_node xml_node::first_element_by_path(const char_t* path_, char_t delimiter) const + PUGI_IMPL_FN xml_node xml_node::first_element_by_path(const char_t* path_, char_t delimiter) const { xml_node context = path_[0] == delimiter ? root() : *this; @@ -6410,14 +6484,14 @@ namespace pugi } } - PUGI__FN bool xml_node::traverse(xml_tree_walker& walker) + PUGI_IMPL_FN bool xml_node::traverse(xml_tree_walker& walker) { walker._depth = -1; xml_node arg_begin(_root); if (!walker.begin(arg_begin)) return false; - xml_node_struct* cur = _root ? _root->first_child + 0 : 0; + xml_node_struct* cur = _root ? _root->first_child + 0 : NULL; if (cur) { @@ -6457,17 +6531,17 @@ namespace pugi return walker.end(arg_end); } - PUGI__FN size_t xml_node::hash_value() const + PUGI_IMPL_FN size_t xml_node::hash_value() const { - return static_cast(reinterpret_cast(_root) / sizeof(xml_node_struct)); + return reinterpret_cast(_root) / sizeof(xml_node_struct); } - PUGI__FN xml_node_struct* xml_node::internal_object() const + PUGI_IMPL_FN xml_node_struct* xml_node::internal_object() const { return _root; } - PUGI__FN void xml_node::print(xml_writer& writer, const char_t* indent, unsigned int flags, xml_encoding encoding, unsigned int depth) const + PUGI_IMPL_FN void xml_node::print(xml_writer& writer, const char_t* indent, unsigned int flags, xml_encoding encoding, unsigned int depth) const { if (!_root) return; @@ -6479,14 +6553,14 @@ namespace pugi } #ifndef PUGIXML_NO_STL - PUGI__FN void xml_node::print(std::basic_ostream >& stream, const char_t* indent, unsigned int flags, xml_encoding encoding, unsigned int depth) const + PUGI_IMPL_FN void xml_node::print(std::basic_ostream& stream, const char_t* indent, unsigned int flags, xml_encoding encoding, unsigned int depth) const { xml_writer_stream writer(stream); print(writer, indent, flags, encoding, depth); } - PUGI__FN void xml_node::print(std::basic_ostream >& stream, const char_t* indent, unsigned int flags, unsigned int depth) const + PUGI_IMPL_FN void xml_node::print(std::basic_ostream& stream, const char_t* indent, unsigned int flags, unsigned int depth) const { xml_writer_stream writer(stream); @@ -6494,7 +6568,7 @@ namespace pugi } #endif - PUGI__FN ptrdiff_t xml_node::offset_debug() const + PUGI_IMPL_FN ptrdiff_t xml_node::offset_debug() const { if (!_root) return -1; @@ -6526,37 +6600,37 @@ namespace pugi } #ifdef __BORLANDC__ - PUGI__FN bool operator&&(const xml_node& lhs, bool rhs) + PUGI_IMPL_FN bool operator&&(const xml_node& lhs, bool rhs) { return (bool)lhs && rhs; } - PUGI__FN bool operator||(const xml_node& lhs, bool rhs) + PUGI_IMPL_FN bool operator||(const xml_node& lhs, bool rhs) { return (bool)lhs || rhs; } #endif - PUGI__FN xml_text::xml_text(xml_node_struct* root): _root(root) + PUGI_IMPL_FN xml_text::xml_text(xml_node_struct* root): _root(root) { } - PUGI__FN xml_node_struct* xml_text::_data() const + PUGI_IMPL_FN xml_node_struct* xml_text::_data() const { if (!_root || impl::is_text_node(_root)) return _root; // element nodes can have value if parse_embed_pcdata was used - if (PUGI__NODETYPE(_root) == node_element && _root->value) + if (PUGI_IMPL_NODETYPE(_root) == node_element && _root->value) return _root; for (xml_node_struct* node = _root->first_child; node; node = node->next_sibling) if (impl::is_text_node(node)) return node; - return 0; + return NULL; } - PUGI__FN xml_node_struct* xml_text::_data_new() + PUGI_IMPL_FN xml_node_struct* xml_text::_data_new() { xml_node_struct* d = _data(); if (d) return d; @@ -6564,30 +6638,30 @@ namespace pugi return xml_node(_root).append_child(node_pcdata).internal_object(); } - PUGI__FN xml_text::xml_text(): _root(0) + PUGI_IMPL_FN xml_text::xml_text(): _root(NULL) { } - PUGI__FN static void unspecified_bool_xml_text(xml_text***) + PUGI_IMPL_FN static void unspecified_bool_xml_text(xml_text***) { } - PUGI__FN xml_text::operator xml_text::unspecified_bool_type() const + PUGI_IMPL_FN xml_text::operator xml_text::unspecified_bool_type() const { - return _data() ? unspecified_bool_xml_text : 0; + return _data() ? unspecified_bool_xml_text : NULL; } - PUGI__FN bool xml_text::operator!() const + PUGI_IMPL_FN bool xml_text::operator!() const { return !_data(); } - PUGI__FN bool xml_text::empty() const + PUGI_IMPL_FN bool xml_text::empty() const { - return _data() == 0; + return _data() == NULL; } - PUGI__FN const char_t* xml_text::get() const + PUGI_IMPL_FN const char_t* xml_text::get() const { xml_node_struct* d = _data(); if (!d) return PUGIXML_TEXT(""); @@ -6595,7 +6669,7 @@ namespace pugi return value ? value : PUGIXML_TEXT(""); } - PUGI__FN const char_t* xml_text::as_string(const char_t* def) const + PUGI_IMPL_FN const char_t* xml_text::as_string(const char_t* def) const { xml_node_struct* d = _data(); if (!d) return def; @@ -6603,7 +6677,7 @@ namespace pugi return value ? value : def; } - PUGI__FN int xml_text::as_int(int def) const + PUGI_IMPL_FN int xml_text::as_int(int def) const { xml_node_struct* d = _data(); if (!d) return def; @@ -6611,7 +6685,7 @@ namespace pugi return value ? impl::get_value_int(value) : def; } - PUGI__FN unsigned int xml_text::as_uint(unsigned int def) const + PUGI_IMPL_FN unsigned int xml_text::as_uint(unsigned int def) const { xml_node_struct* d = _data(); if (!d) return def; @@ -6619,7 +6693,7 @@ namespace pugi return value ? impl::get_value_uint(value) : def; } - PUGI__FN double xml_text::as_double(double def) const + PUGI_IMPL_FN double xml_text::as_double(double def) const { xml_node_struct* d = _data(); if (!d) return def; @@ -6627,7 +6701,7 @@ namespace pugi return value ? impl::get_value_double(value) : def; } - PUGI__FN float xml_text::as_float(float def) const + PUGI_IMPL_FN float xml_text::as_float(float def) const { xml_node_struct* d = _data(); if (!d) return def; @@ -6635,7 +6709,7 @@ namespace pugi return value ? impl::get_value_float(value) : def; } - PUGI__FN bool xml_text::as_bool(bool def) const + PUGI_IMPL_FN bool xml_text::as_bool(bool def) const { xml_node_struct* d = _data(); if (!d) return def; @@ -6644,7 +6718,7 @@ namespace pugi } #ifdef PUGIXML_HAS_LONG_LONG - PUGI__FN long long xml_text::as_llong(long long def) const + PUGI_IMPL_FN long long xml_text::as_llong(long long def) const { xml_node_struct* d = _data(); if (!d) return def; @@ -6652,7 +6726,7 @@ namespace pugi return value ? impl::get_value_llong(value) : def; } - PUGI__FN unsigned long long xml_text::as_ullong(unsigned long long def) const + PUGI_IMPL_FN unsigned long long xml_text::as_ullong(unsigned long long def) const { xml_node_struct* d = _data(); if (!d) return def; @@ -6661,77 +6735,77 @@ namespace pugi } #endif - PUGI__FN bool xml_text::set(const char_t* rhs, size_t sz) + PUGI_IMPL_FN bool xml_text::set(const char_t* rhs) { xml_node_struct* dn = _data_new(); - return dn ? impl::strcpy_insitu(dn->value, dn->header, impl::xml_memory_page_value_allocated_mask, rhs, sz) : false; + return dn ? impl::strcpy_insitu(dn->value, dn->header, impl::xml_memory_page_value_allocated_mask, rhs, impl::strlength(rhs)) : false; } - PUGI__FN bool xml_text::set(const char_t* rhs) + PUGI_IMPL_FN bool xml_text::set(const char_t* rhs, size_t size) { xml_node_struct* dn = _data_new(); - return dn ? impl::strcpy_insitu(dn->value, dn->header, impl::xml_memory_page_value_allocated_mask, rhs, impl::strlength(rhs)) : false; + return dn ? impl::strcpy_insitu(dn->value, dn->header, impl::xml_memory_page_value_allocated_mask, rhs, size) : false; } - PUGI__FN bool xml_text::set(int rhs) + PUGI_IMPL_FN bool xml_text::set(int rhs) { xml_node_struct* dn = _data_new(); return dn ? impl::set_value_integer(dn->value, dn->header, impl::xml_memory_page_value_allocated_mask, rhs, rhs < 0) : false; } - PUGI__FN bool xml_text::set(unsigned int rhs) + PUGI_IMPL_FN bool xml_text::set(unsigned int rhs) { xml_node_struct* dn = _data_new(); return dn ? impl::set_value_integer(dn->value, dn->header, impl::xml_memory_page_value_allocated_mask, rhs, false) : false; } - PUGI__FN bool xml_text::set(long rhs) + PUGI_IMPL_FN bool xml_text::set(long rhs) { xml_node_struct* dn = _data_new(); return dn ? impl::set_value_integer(dn->value, dn->header, impl::xml_memory_page_value_allocated_mask, rhs, rhs < 0) : false; } - PUGI__FN bool xml_text::set(unsigned long rhs) + PUGI_IMPL_FN bool xml_text::set(unsigned long rhs) { xml_node_struct* dn = _data_new(); return dn ? impl::set_value_integer(dn->value, dn->header, impl::xml_memory_page_value_allocated_mask, rhs, false) : false; } - PUGI__FN bool xml_text::set(float rhs) + PUGI_IMPL_FN bool xml_text::set(float rhs) { xml_node_struct* dn = _data_new(); return dn ? impl::set_value_convert(dn->value, dn->header, impl::xml_memory_page_value_allocated_mask, rhs, default_float_precision) : false; } - PUGI__FN bool xml_text::set(float rhs, int precision) + PUGI_IMPL_FN bool xml_text::set(float rhs, int precision) { xml_node_struct* dn = _data_new(); return dn ? impl::set_value_convert(dn->value, dn->header, impl::xml_memory_page_value_allocated_mask, rhs, precision) : false; } - PUGI__FN bool xml_text::set(double rhs) + PUGI_IMPL_FN bool xml_text::set(double rhs) { xml_node_struct* dn = _data_new(); return dn ? impl::set_value_convert(dn->value, dn->header, impl::xml_memory_page_value_allocated_mask, rhs, default_double_precision) : false; } - PUGI__FN bool xml_text::set(double rhs, int precision) + PUGI_IMPL_FN bool xml_text::set(double rhs, int precision) { xml_node_struct* dn = _data_new(); return dn ? impl::set_value_convert(dn->value, dn->header, impl::xml_memory_page_value_allocated_mask, rhs, precision) : false; } - PUGI__FN bool xml_text::set(bool rhs) + PUGI_IMPL_FN bool xml_text::set(bool rhs) { xml_node_struct* dn = _data_new(); @@ -6739,14 +6813,14 @@ namespace pugi } #ifdef PUGIXML_HAS_LONG_LONG - PUGI__FN bool xml_text::set(long long rhs) + PUGI_IMPL_FN bool xml_text::set(long long rhs) { xml_node_struct* dn = _data_new(); return dn ? impl::set_value_integer(dn->value, dn->header, impl::xml_memory_page_value_allocated_mask, rhs, rhs < 0) : false; } - PUGI__FN bool xml_text::set(unsigned long long rhs) + PUGI_IMPL_FN bool xml_text::set(unsigned long long rhs) { xml_node_struct* dn = _data_new(); @@ -6754,256 +6828,256 @@ namespace pugi } #endif - PUGI__FN xml_text& xml_text::operator=(const char_t* rhs) + PUGI_IMPL_FN xml_text& xml_text::operator=(const char_t* rhs) { set(rhs); return *this; } - PUGI__FN xml_text& xml_text::operator=(int rhs) + PUGI_IMPL_FN xml_text& xml_text::operator=(int rhs) { set(rhs); return *this; } - PUGI__FN xml_text& xml_text::operator=(unsigned int rhs) + PUGI_IMPL_FN xml_text& xml_text::operator=(unsigned int rhs) { set(rhs); return *this; } - PUGI__FN xml_text& xml_text::operator=(long rhs) + PUGI_IMPL_FN xml_text& xml_text::operator=(long rhs) { set(rhs); return *this; } - PUGI__FN xml_text& xml_text::operator=(unsigned long rhs) + PUGI_IMPL_FN xml_text& xml_text::operator=(unsigned long rhs) { set(rhs); return *this; } - PUGI__FN xml_text& xml_text::operator=(double rhs) + PUGI_IMPL_FN xml_text& xml_text::operator=(double rhs) { set(rhs); return *this; } - PUGI__FN xml_text& xml_text::operator=(float rhs) + PUGI_IMPL_FN xml_text& xml_text::operator=(float rhs) { set(rhs); return *this; } - PUGI__FN xml_text& xml_text::operator=(bool rhs) + PUGI_IMPL_FN xml_text& xml_text::operator=(bool rhs) { set(rhs); return *this; } #ifdef PUGIXML_HAS_LONG_LONG - PUGI__FN xml_text& xml_text::operator=(long long rhs) + PUGI_IMPL_FN xml_text& xml_text::operator=(long long rhs) { set(rhs); return *this; } - PUGI__FN xml_text& xml_text::operator=(unsigned long long rhs) + PUGI_IMPL_FN xml_text& xml_text::operator=(unsigned long long rhs) { set(rhs); return *this; } #endif - PUGI__FN xml_node xml_text::data() const + PUGI_IMPL_FN xml_node xml_text::data() const { return xml_node(_data()); } #ifdef __BORLANDC__ - PUGI__FN bool operator&&(const xml_text& lhs, bool rhs) + PUGI_IMPL_FN bool operator&&(const xml_text& lhs, bool rhs) { return (bool)lhs && rhs; } - PUGI__FN bool operator||(const xml_text& lhs, bool rhs) + PUGI_IMPL_FN bool operator||(const xml_text& lhs, bool rhs) { return (bool)lhs || rhs; } #endif - PUGI__FN xml_node_iterator::xml_node_iterator() + PUGI_IMPL_FN xml_node_iterator::xml_node_iterator() { } - PUGI__FN xml_node_iterator::xml_node_iterator(const xml_node& node): _wrap(node), _parent(node.parent()) + PUGI_IMPL_FN xml_node_iterator::xml_node_iterator(const xml_node& node): _wrap(node), _parent(node.parent()) { } - PUGI__FN xml_node_iterator::xml_node_iterator(xml_node_struct* ref, xml_node_struct* parent): _wrap(ref), _parent(parent) + PUGI_IMPL_FN xml_node_iterator::xml_node_iterator(xml_node_struct* ref, xml_node_struct* parent): _wrap(ref), _parent(parent) { } - PUGI__FN bool xml_node_iterator::operator==(const xml_node_iterator& rhs) const + PUGI_IMPL_FN bool xml_node_iterator::operator==(const xml_node_iterator& rhs) const { return _wrap._root == rhs._wrap._root && _parent._root == rhs._parent._root; } - PUGI__FN bool xml_node_iterator::operator!=(const xml_node_iterator& rhs) const + PUGI_IMPL_FN bool xml_node_iterator::operator!=(const xml_node_iterator& rhs) const { return _wrap._root != rhs._wrap._root || _parent._root != rhs._parent._root; } - PUGI__FN xml_node& xml_node_iterator::operator*() const + PUGI_IMPL_FN xml_node& xml_node_iterator::operator*() const { assert(_wrap._root); return _wrap; } - PUGI__FN xml_node* xml_node_iterator::operator->() const + PUGI_IMPL_FN xml_node* xml_node_iterator::operator->() const { assert(_wrap._root); - return const_cast(&_wrap); // BCC5 workaround + return &_wrap; } - PUGI__FN xml_node_iterator& xml_node_iterator::operator++() + PUGI_IMPL_FN xml_node_iterator& xml_node_iterator::operator++() { assert(_wrap._root); _wrap._root = _wrap._root->next_sibling; return *this; } - PUGI__FN xml_node_iterator xml_node_iterator::operator++(int) + PUGI_IMPL_FN xml_node_iterator xml_node_iterator::operator++(int) { xml_node_iterator temp = *this; ++*this; return temp; } - PUGI__FN xml_node_iterator& xml_node_iterator::operator--() + PUGI_IMPL_FN xml_node_iterator& xml_node_iterator::operator--() { _wrap = _wrap._root ? _wrap.previous_sibling() : _parent.last_child(); return *this; } - PUGI__FN xml_node_iterator xml_node_iterator::operator--(int) + PUGI_IMPL_FN xml_node_iterator xml_node_iterator::operator--(int) { xml_node_iterator temp = *this; --*this; return temp; } - PUGI__FN xml_attribute_iterator::xml_attribute_iterator() + PUGI_IMPL_FN xml_attribute_iterator::xml_attribute_iterator() { } - PUGI__FN xml_attribute_iterator::xml_attribute_iterator(const xml_attribute& attr, const xml_node& parent): _wrap(attr), _parent(parent) + PUGI_IMPL_FN xml_attribute_iterator::xml_attribute_iterator(const xml_attribute& attr, const xml_node& parent): _wrap(attr), _parent(parent) { } - PUGI__FN xml_attribute_iterator::xml_attribute_iterator(xml_attribute_struct* ref, xml_node_struct* parent): _wrap(ref), _parent(parent) + PUGI_IMPL_FN xml_attribute_iterator::xml_attribute_iterator(xml_attribute_struct* ref, xml_node_struct* parent): _wrap(ref), _parent(parent) { } - PUGI__FN bool xml_attribute_iterator::operator==(const xml_attribute_iterator& rhs) const + PUGI_IMPL_FN bool xml_attribute_iterator::operator==(const xml_attribute_iterator& rhs) const { return _wrap._attr == rhs._wrap._attr && _parent._root == rhs._parent._root; } - PUGI__FN bool xml_attribute_iterator::operator!=(const xml_attribute_iterator& rhs) const + PUGI_IMPL_FN bool xml_attribute_iterator::operator!=(const xml_attribute_iterator& rhs) const { return _wrap._attr != rhs._wrap._attr || _parent._root != rhs._parent._root; } - PUGI__FN xml_attribute& xml_attribute_iterator::operator*() const + PUGI_IMPL_FN xml_attribute& xml_attribute_iterator::operator*() const { assert(_wrap._attr); return _wrap; } - PUGI__FN xml_attribute* xml_attribute_iterator::operator->() const + PUGI_IMPL_FN xml_attribute* xml_attribute_iterator::operator->() const { assert(_wrap._attr); - return const_cast(&_wrap); // BCC5 workaround + return &_wrap; } - PUGI__FN xml_attribute_iterator& xml_attribute_iterator::operator++() + PUGI_IMPL_FN xml_attribute_iterator& xml_attribute_iterator::operator++() { assert(_wrap._attr); _wrap._attr = _wrap._attr->next_attribute; return *this; } - PUGI__FN xml_attribute_iterator xml_attribute_iterator::operator++(int) + PUGI_IMPL_FN xml_attribute_iterator xml_attribute_iterator::operator++(int) { xml_attribute_iterator temp = *this; ++*this; return temp; } - PUGI__FN xml_attribute_iterator& xml_attribute_iterator::operator--() + PUGI_IMPL_FN xml_attribute_iterator& xml_attribute_iterator::operator--() { _wrap = _wrap._attr ? _wrap.previous_attribute() : _parent.last_attribute(); return *this; } - PUGI__FN xml_attribute_iterator xml_attribute_iterator::operator--(int) + PUGI_IMPL_FN xml_attribute_iterator xml_attribute_iterator::operator--(int) { xml_attribute_iterator temp = *this; --*this; return temp; } - PUGI__FN xml_named_node_iterator::xml_named_node_iterator(): _name(0) + PUGI_IMPL_FN xml_named_node_iterator::xml_named_node_iterator(): _name(NULL) { } - PUGI__FN xml_named_node_iterator::xml_named_node_iterator(const xml_node& node, const char_t* name): _wrap(node), _parent(node.parent()), _name(name) + PUGI_IMPL_FN xml_named_node_iterator::xml_named_node_iterator(const xml_node& node, const char_t* name): _wrap(node), _parent(node.parent()), _name(name) { } - PUGI__FN xml_named_node_iterator::xml_named_node_iterator(xml_node_struct* ref, xml_node_struct* parent, const char_t* name): _wrap(ref), _parent(parent), _name(name) + PUGI_IMPL_FN xml_named_node_iterator::xml_named_node_iterator(xml_node_struct* ref, xml_node_struct* parent, const char_t* name): _wrap(ref), _parent(parent), _name(name) { } - PUGI__FN bool xml_named_node_iterator::operator==(const xml_named_node_iterator& rhs) const + PUGI_IMPL_FN bool xml_named_node_iterator::operator==(const xml_named_node_iterator& rhs) const { return _wrap._root == rhs._wrap._root && _parent._root == rhs._parent._root; } - PUGI__FN bool xml_named_node_iterator::operator!=(const xml_named_node_iterator& rhs) const + PUGI_IMPL_FN bool xml_named_node_iterator::operator!=(const xml_named_node_iterator& rhs) const { return _wrap._root != rhs._wrap._root || _parent._root != rhs._parent._root; } - PUGI__FN xml_node& xml_named_node_iterator::operator*() const + PUGI_IMPL_FN xml_node& xml_named_node_iterator::operator*() const { assert(_wrap._root); return _wrap; } - PUGI__FN xml_node* xml_named_node_iterator::operator->() const + PUGI_IMPL_FN xml_node* xml_named_node_iterator::operator->() const { assert(_wrap._root); - return const_cast(&_wrap); // BCC5 workaround + return &_wrap; } - PUGI__FN xml_named_node_iterator& xml_named_node_iterator::operator++() + PUGI_IMPL_FN xml_named_node_iterator& xml_named_node_iterator::operator++() { assert(_wrap._root); _wrap = _wrap.next_sibling(_name); return *this; } - PUGI__FN xml_named_node_iterator xml_named_node_iterator::operator++(int) + PUGI_IMPL_FN xml_named_node_iterator xml_named_node_iterator::operator++(int) { xml_named_node_iterator temp = *this; ++*this; return temp; } - PUGI__FN xml_named_node_iterator& xml_named_node_iterator::operator--() + PUGI_IMPL_FN xml_named_node_iterator& xml_named_node_iterator::operator--() { if (_wrap._root) _wrap = _wrap.previous_sibling(_name); @@ -7018,23 +7092,23 @@ namespace pugi return *this; } - PUGI__FN xml_named_node_iterator xml_named_node_iterator::operator--(int) + PUGI_IMPL_FN xml_named_node_iterator xml_named_node_iterator::operator--(int) { xml_named_node_iterator temp = *this; --*this; return temp; } - PUGI__FN xml_parse_result::xml_parse_result(): status(status_internal_error), offset(0), encoding(encoding_auto) + PUGI_IMPL_FN xml_parse_result::xml_parse_result(): status(status_internal_error), offset(0), encoding(encoding_auto) { } - PUGI__FN xml_parse_result::operator bool() const + PUGI_IMPL_FN xml_parse_result::operator bool() const { return status == status_ok; } - PUGI__FN const char* xml_parse_result::description() const + PUGI_IMPL_FN const char* xml_parse_result::description() const { switch (status) { @@ -7065,24 +7139,24 @@ namespace pugi } } - PUGI__FN xml_document::xml_document(): _buffer(0) + PUGI_IMPL_FN xml_document::xml_document(): _buffer(NULL) { _create(); } - PUGI__FN xml_document::~xml_document() + PUGI_IMPL_FN xml_document::~xml_document() { _destroy(); } #ifdef PUGIXML_HAS_MOVE - PUGI__FN xml_document::xml_document(xml_document&& rhs) PUGIXML_NOEXCEPT_IF_NOT_COMPACT: _buffer(0) + PUGI_IMPL_FN xml_document::xml_document(xml_document&& rhs) PUGIXML_NOEXCEPT_IF_NOT_COMPACT: _buffer(NULL) { _create(); _move(rhs); } - PUGI__FN xml_document& xml_document::operator=(xml_document&& rhs) PUGIXML_NOEXCEPT_IF_NOT_COMPACT + PUGI_IMPL_FN xml_document& xml_document::operator=(xml_document&& rhs) PUGIXML_NOEXCEPT_IF_NOT_COMPACT { if (this == &rhs) return *this; @@ -7094,20 +7168,20 @@ namespace pugi } #endif - PUGI__FN void xml_document::reset() + PUGI_IMPL_FN void xml_document::reset() { _destroy(); _create(); } - PUGI__FN void xml_document::reset(const xml_document& proto) + PUGI_IMPL_FN void xml_document::reset(const xml_document& proto) { reset(); impl::node_copy_tree(_root, proto._root); } - PUGI__FN void xml_document::_create() + PUGI_IMPL_FN void xml_document::_create() { assert(!_root); @@ -7119,7 +7193,7 @@ namespace pugi #endif // initialize sentinel page - PUGI__STATIC_ASSERT(sizeof(impl::xml_memory_page) + sizeof(impl::xml_document_struct) + page_offset <= sizeof(_memory)); + PUGI_IMPL_STATIC_ASSERT(sizeof(impl::xml_memory_page) + sizeof(impl::xml_document_struct) + page_offset <= sizeof(_memory)); // prepare page structure impl::xml_memory_page* page = impl::xml_memory_page::construct(_memory); @@ -7150,7 +7224,7 @@ namespace pugi assert(reinterpret_cast(_root) + sizeof(impl::xml_document_struct) <= _memory + sizeof(_memory)); } - PUGI__FN void xml_document::_destroy() + PUGI_IMPL_FN void xml_document::_destroy() { assert(_root); @@ -7158,7 +7232,7 @@ namespace pugi if (_buffer) { impl::xml_memory::deallocate(_buffer); - _buffer = 0; + _buffer = NULL; } // destroy extra buffers (note: no need to destroy linked list nodes, they're allocated using document allocator) @@ -7168,7 +7242,7 @@ namespace pugi } // destroy dynamic storage, leave sentinel page (it's in static memory) - impl::xml_memory_page* root_page = PUGI__GETPAGE(_root); + impl::xml_memory_page* root_page = PUGI_IMPL_GETPAGE(_root); assert(root_page && !root_page->prev); assert(reinterpret_cast(root_page) >= _memory && reinterpret_cast(root_page) < _memory + sizeof(_memory)); @@ -7186,11 +7260,11 @@ namespace pugi static_cast(_root)->hash.clear(); #endif - _root = 0; + _root = NULL; } #ifdef PUGIXML_HAS_MOVE - PUGI__FN void xml_document::_move(xml_document& rhs) PUGIXML_NOEXCEPT_IF_NOT_COMPACT + PUGI_IMPL_FN void xml_document::_move(xml_document& rhs) PUGIXML_NOEXCEPT_IF_NOT_COMPACT { impl::xml_document_struct* doc = static_cast(_root); impl::xml_document_struct* other = static_cast(rhs._root); @@ -7224,7 +7298,7 @@ namespace pugi // move allocation state // note that other->_root may point to the embedded document page, in which case we should keep original (empty) state - if (other->_root != PUGI__GETPAGE(other)) + if (other->_root != PUGI_IMPL_GETPAGE(other)) { doc->_root = other->_root; doc->_busy_size = other->_busy_size; @@ -7241,14 +7315,14 @@ namespace pugi doc->_hash = &doc->hash; // make sure we don't access other hash up until the end when we reinitialize other document - other->_hash = 0; + other->_hash = NULL; #endif // move page structure - impl::xml_memory_page* doc_page = PUGI__GETPAGE(doc); + impl::xml_memory_page* doc_page = PUGI_IMPL_GETPAGE(doc); assert(doc_page && !doc_page->prev && !doc_page->next); - impl::xml_memory_page* other_page = PUGI__GETPAGE(other); + impl::xml_memory_page* other_page = PUGI_IMPL_GETPAGE(other); assert(other_page && !other_page->prev); // relink pages since root page is embedded into xml_document @@ -7259,7 +7333,7 @@ namespace pugi page->prev = doc_page; doc_page->next = page; - other_page->next = 0; + other_page->next = NULL; } // make sure pages point to the correct document state @@ -7295,20 +7369,20 @@ namespace pugi } // reset other document - new (other) impl::xml_document_struct(PUGI__GETPAGE(other)); - rhs._buffer = 0; + new (other) impl::xml_document_struct(PUGI_IMPL_GETPAGE(other)); + rhs._buffer = NULL; } #endif #ifndef PUGIXML_NO_STL - PUGI__FN xml_parse_result xml_document::load(std::basic_istream >& stream, unsigned int options, xml_encoding encoding) + PUGI_IMPL_FN xml_parse_result xml_document::load(std::basic_istream& stream, unsigned int options, xml_encoding encoding) { reset(); return impl::load_stream_impl(static_cast(_root), stream, options, encoding, &_buffer); } - PUGI__FN xml_parse_result xml_document::load(std::basic_istream >& stream, unsigned int options) + PUGI_IMPL_FN xml_parse_result xml_document::load(std::basic_istream& stream, unsigned int options) { reset(); @@ -7316,7 +7390,7 @@ namespace pugi } #endif - PUGI__FN xml_parse_result xml_document::load_string(const char_t* contents, unsigned int options) + PUGI_IMPL_FN xml_parse_result xml_document::load_string(const char_t* contents, unsigned int options) { // Force native encoding (skip autodetection) #ifdef PUGIXML_WCHAR_MODE @@ -7328,12 +7402,12 @@ namespace pugi return load_buffer(contents, impl::strlength(contents) * sizeof(char_t), options, encoding); } - PUGI__FN xml_parse_result xml_document::load(const char_t* contents, unsigned int options) + PUGI_IMPL_FN xml_parse_result xml_document::load(const char_t* contents, unsigned int options) { return load_string(contents, options); } - PUGI__FN xml_parse_result xml_document::load_file(const char* path_, unsigned int options, xml_encoding encoding) + PUGI_IMPL_FN xml_parse_result xml_document::load_file(const char* path_, unsigned int options, xml_encoding encoding) { reset(); @@ -7343,7 +7417,7 @@ namespace pugi return impl::load_file_impl(static_cast(_root), file.data, options, encoding, &_buffer); } - PUGI__FN xml_parse_result xml_document::load_file(const wchar_t* path_, unsigned int options, xml_encoding encoding) + PUGI_IMPL_FN xml_parse_result xml_document::load_file(const wchar_t* path_, unsigned int options, xml_encoding encoding) { reset(); @@ -7353,28 +7427,28 @@ namespace pugi return impl::load_file_impl(static_cast(_root), file.data, options, encoding, &_buffer); } - PUGI__FN xml_parse_result xml_document::load_buffer(const void* contents, size_t size, unsigned int options, xml_encoding encoding) + PUGI_IMPL_FN xml_parse_result xml_document::load_buffer(const void* contents, size_t size, unsigned int options, xml_encoding encoding) { reset(); return impl::load_buffer_impl(static_cast(_root), _root, const_cast(contents), size, options, encoding, false, false, &_buffer); } - PUGI__FN xml_parse_result xml_document::load_buffer_inplace(void* contents, size_t size, unsigned int options, xml_encoding encoding) + PUGI_IMPL_FN xml_parse_result xml_document::load_buffer_inplace(void* contents, size_t size, unsigned int options, xml_encoding encoding) { reset(); return impl::load_buffer_impl(static_cast(_root), _root, contents, size, options, encoding, true, false, &_buffer); } - PUGI__FN xml_parse_result xml_document::load_buffer_inplace_own(void* contents, size_t size, unsigned int options, xml_encoding encoding) + PUGI_IMPL_FN xml_parse_result xml_document::load_buffer_inplace_own(void* contents, size_t size, unsigned int options, xml_encoding encoding) { reset(); return impl::load_buffer_impl(static_cast(_root), _root, contents, size, options, encoding, true, true, &_buffer); } - PUGI__FN void xml_document::save(xml_writer& writer, const char_t* indent, unsigned int flags, xml_encoding encoding) const + PUGI_IMPL_FN void xml_document::save(xml_writer& writer, const char_t* indent, unsigned int flags, xml_encoding encoding) const { impl::xml_buffered_writer buffered_writer(writer, encoding); @@ -7403,14 +7477,14 @@ namespace pugi } #ifndef PUGIXML_NO_STL - PUGI__FN void xml_document::save(std::basic_ostream >& stream, const char_t* indent, unsigned int flags, xml_encoding encoding) const + PUGI_IMPL_FN void xml_document::save(std::basic_ostream& stream, const char_t* indent, unsigned int flags, xml_encoding encoding) const { xml_writer_stream writer(stream); save(writer, indent, flags, encoding); } - PUGI__FN void xml_document::save(std::basic_ostream >& stream, const char_t* indent, unsigned int flags) const + PUGI_IMPL_FN void xml_document::save(std::basic_ostream& stream, const char_t* indent, unsigned int flags) const { xml_writer_stream writer(stream); @@ -7418,7 +7492,7 @@ namespace pugi } #endif - PUGI__FN bool xml_document::save_file(const char* path_, const char_t* indent, unsigned int flags, xml_encoding encoding) const + PUGI_IMPL_FN bool xml_document::save_file(const char* path_, const char_t* indent, unsigned int flags, xml_encoding encoding) const { using impl::auto_deleter; // MSVC7 workaround auto_deleter file(impl::open_file(path_, (flags & format_save_file_text) ? "w" : "wb"), impl::close_file); @@ -7426,7 +7500,7 @@ namespace pugi return impl::save_file_impl(*this, file.data, indent, flags, encoding) && fclose(file.release()) == 0; } - PUGI__FN bool xml_document::save_file(const wchar_t* path_, const char_t* indent, unsigned int flags, xml_encoding encoding) const + PUGI_IMPL_FN bool xml_document::save_file(const wchar_t* path_, const char_t* indent, unsigned int flags, xml_encoding encoding) const { using impl::auto_deleter; // MSVC7 workaround auto_deleter file(impl::open_file_wide(path_, (flags & format_save_file_text) ? L"w" : L"wb"), impl::close_file); @@ -7434,55 +7508,55 @@ namespace pugi return impl::save_file_impl(*this, file.data, indent, flags, encoding) && fclose(file.release()) == 0; } - PUGI__FN xml_node xml_document::document_element() const + PUGI_IMPL_FN xml_node xml_document::document_element() const { assert(_root); for (xml_node_struct* i = _root->first_child; i; i = i->next_sibling) - if (PUGI__NODETYPE(i) == node_element) + if (PUGI_IMPL_NODETYPE(i) == node_element) return xml_node(i); return xml_node(); } #ifndef PUGIXML_NO_STL - PUGI__FN std::string PUGIXML_FUNCTION as_utf8(const wchar_t* str) + PUGI_IMPL_FN std::string PUGIXML_FUNCTION as_utf8(const wchar_t* str) { assert(str); return impl::as_utf8_impl(str, impl::strlength_wide(str)); } - PUGI__FN std::string PUGIXML_FUNCTION as_utf8(const std::basic_string& str) + PUGI_IMPL_FN std::string PUGIXML_FUNCTION as_utf8(const std::basic_string& str) { return impl::as_utf8_impl(str.c_str(), str.size()); } - PUGI__FN std::basic_string PUGIXML_FUNCTION as_wide(const char* str) + PUGI_IMPL_FN std::basic_string PUGIXML_FUNCTION as_wide(const char* str) { assert(str); return impl::as_wide_impl(str, strlen(str)); } - PUGI__FN std::basic_string PUGIXML_FUNCTION as_wide(const std::string& str) + PUGI_IMPL_FN std::basic_string PUGIXML_FUNCTION as_wide(const std::string& str) { return impl::as_wide_impl(str.c_str(), str.size()); } #endif - PUGI__FN void PUGIXML_FUNCTION set_memory_management_functions(allocation_function allocate, deallocation_function deallocate) + PUGI_IMPL_FN void PUGIXML_FUNCTION set_memory_management_functions(allocation_function allocate, deallocation_function deallocate) { impl::xml_memory::allocate = allocate; impl::xml_memory::deallocate = deallocate; } - PUGI__FN allocation_function PUGIXML_FUNCTION get_memory_allocation_function() + PUGI_IMPL_FN allocation_function PUGIXML_FUNCTION get_memory_allocation_function() { return impl::xml_memory::allocate; } - PUGI__FN deallocation_function PUGIXML_FUNCTION get_memory_deallocation_function() + PUGI_IMPL_FN deallocation_function PUGIXML_FUNCTION get_memory_deallocation_function() { return impl::xml_memory::deallocate; } @@ -7492,17 +7566,17 @@ namespace pugi namespace std { // Workarounds for (non-standard) iterator category detection for older versions (MSVC7/IC8 and earlier) - PUGI__FN std::bidirectional_iterator_tag _Iter_cat(const pugi::xml_node_iterator&) + PUGI_IMPL_FN std::bidirectional_iterator_tag _Iter_cat(const pugi::xml_node_iterator&) { return std::bidirectional_iterator_tag(); } - PUGI__FN std::bidirectional_iterator_tag _Iter_cat(const pugi::xml_attribute_iterator&) + PUGI_IMPL_FN std::bidirectional_iterator_tag _Iter_cat(const pugi::xml_attribute_iterator&) { return std::bidirectional_iterator_tag(); } - PUGI__FN std::bidirectional_iterator_tag _Iter_cat(const pugi::xml_named_node_iterator&) + PUGI_IMPL_FN std::bidirectional_iterator_tag _Iter_cat(const pugi::xml_named_node_iterator&) { return std::bidirectional_iterator_tag(); } @@ -7513,17 +7587,17 @@ namespace std namespace std { // Workarounds for (non-standard) iterator category detection - PUGI__FN std::bidirectional_iterator_tag __iterator_category(const pugi::xml_node_iterator&) + PUGI_IMPL_FN std::bidirectional_iterator_tag __iterator_category(const pugi::xml_node_iterator&) { return std::bidirectional_iterator_tag(); } - PUGI__FN std::bidirectional_iterator_tag __iterator_category(const pugi::xml_attribute_iterator&) + PUGI_IMPL_FN std::bidirectional_iterator_tag __iterator_category(const pugi::xml_attribute_iterator&) { return std::bidirectional_iterator_tag(); } - PUGI__FN std::bidirectional_iterator_tag __iterator_category(const pugi::xml_named_node_iterator&) + PUGI_IMPL_FN std::bidirectional_iterator_tag __iterator_category(const pugi::xml_named_node_iterator&) { return std::bidirectional_iterator_tag(); } @@ -7532,7 +7606,7 @@ namespace std #ifndef PUGIXML_NO_XPATH // STL replacements -PUGI__NS_BEGIN +PUGI_IMPL_NS_BEGIN struct equal_to { template bool operator()(const T& lhs, const T& rhs) const @@ -7572,7 +7646,7 @@ PUGI__NS_BEGIN rhs = temp; } - template PUGI__FN I min_element(I begin, I end, const Pred& pred) + template PUGI_IMPL_FN I min_element(I begin, I end, const Pred& pred) { I result = begin; @@ -7583,13 +7657,13 @@ PUGI__NS_BEGIN return result; } - template PUGI__FN void reverse(I begin, I end) + template PUGI_IMPL_FN void reverse(I begin, I end) { while (end - begin > 1) swap(*begin++, *--end); } - template PUGI__FN I unique(I begin, I end) + template PUGI_IMPL_FN I unique(I begin, I end) { // fast skip head while (end - begin > 1 && *begin != *(begin + 1)) @@ -7614,7 +7688,7 @@ PUGI__NS_BEGIN return write + 1; } - template PUGI__FN void insertion_sort(T* begin, T* end, const Pred& pred) + template PUGI_IMPL_FN void insertion_sort(T* begin, T* end, const Pred& pred) { if (begin == end) return; @@ -7648,7 +7722,7 @@ PUGI__NS_BEGIN return middle; } - template PUGI__FN void partition3(T* begin, T* end, T pivot, const Pred& pred, T** out_eqbeg, T** out_eqend) + template PUGI_IMPL_FN void partition3(T* begin, T* end, T pivot, const Pred& pred, T** out_eqbeg, T** out_eqend) { // invariant: array is split into 4 groups: = < ? > (each variable denotes the boundary between the groups) T* eq = begin; @@ -7675,7 +7749,7 @@ PUGI__NS_BEGIN *out_eqend = gt; } - template PUGI__FN void sort(I begin, I end, const Pred& pred) + template PUGI_IMPL_FN void sort(I begin, I end, const Pred& pred) { // sort large chunks while (end - begin > 16) @@ -7705,7 +7779,7 @@ PUGI__NS_BEGIN insertion_sort(begin, end, pred); } - PUGI__FN bool hash_insert(const void** table, size_t size, const void* key) + PUGI_IMPL_FN bool hash_insert(const void** table, size_t size, const void* key) { assert(key); @@ -7723,7 +7797,7 @@ PUGI__NS_BEGIN for (size_t probe = 0; probe <= hashmod; ++probe) { - if (table[bucket] == 0) + if (table[bucket] == NULL) { table[bucket] = key; return true; @@ -7739,10 +7813,10 @@ PUGI__NS_BEGIN assert(false && "Hash table is full"); // unreachable return false; } -PUGI__NS_END +PUGI_IMPL_NS_END // Allocator used for AST and evaluation stacks -PUGI__NS_BEGIN +PUGI_IMPL_NS_BEGIN static const size_t xpath_memory_page_size = #ifdef PUGIXML_MEMORY_XPATH_PAGE_SIZE PUGIXML_MEMORY_XPATH_PAGE_SIZE @@ -7771,7 +7845,7 @@ PUGI__NS_BEGIN size_t _root_size; bool* _error; - xpath_allocator(xpath_memory_block* root, bool* error = 0): _root(root), _root_size(0), _error(error) + xpath_allocator(xpath_memory_block* root, bool* error = NULL): _root(root), _root_size(0), _error(error) { } @@ -7799,7 +7873,7 @@ PUGI__NS_BEGIN if (!block) { if (_error) *_error = true; - return 0; + return NULL; } block->next = _root; @@ -7819,7 +7893,7 @@ PUGI__NS_BEGIN new_size = (new_size + xpath_memory_block_alignment - 1) & ~(xpath_memory_block_alignment - 1); // we can only reallocate the last object - assert(ptr == 0 || static_cast(ptr) + old_size == &_root->data[0] + _root_size); + assert(ptr == NULL || static_cast(ptr) + old_size == &_root->data[0] + _root_size); // try to reallocate the object inplace if (ptr && _root_size - old_size + new_size <= _root->capacity) @@ -7830,7 +7904,7 @@ PUGI__NS_BEGIN // allocate a new block void* result = allocate(new_size); - if (!result) return 0; + if (!result) return NULL; // we have a new block if (ptr) @@ -7925,7 +7999,7 @@ PUGI__NS_BEGIN xpath_stack_data(): result(blocks + 0, &oom), temp(blocks + 1, &oom), oom(false) { - blocks[0].next = blocks[1].next = 0; + blocks[0].next = blocks[1].next = NULL; blocks[0].capacity = blocks[1].capacity = sizeof(blocks[0].data); stack.result = &result; @@ -7938,10 +8012,10 @@ PUGI__NS_BEGIN temp.release(); } }; -PUGI__NS_END +PUGI_IMPL_NS_END // String class -PUGI__NS_BEGIN +PUGI_IMPL_NS_BEGIN class xpath_string { const char_t* _buffer; @@ -7951,7 +8025,7 @@ PUGI__NS_BEGIN static char_t* duplicate_string(const char_t* string, size_t length, xpath_allocator* alloc) { char_t* result = static_cast(alloc->allocate((length + 1) * sizeof(char_t))); - if (!result) return 0; + if (!result) return NULL; memcpy(result, string, length * sizeof(char_t)); result[length] = 0; @@ -8011,7 +8085,7 @@ PUGI__NS_BEGIN size_t result_length = target_length + source_length; // allocate new buffer - char_t* result = static_cast(alloc->reallocate(_uses_heap ? const_cast(_buffer) : 0, (target_length + 1) * sizeof(char_t), (result_length + 1) * sizeof(char_t))); + char_t* result = static_cast(alloc->reallocate(_uses_heap ? const_cast(_buffer) : NULL, (target_length + 1) * sizeof(char_t), (result_length + 1) * sizeof(char_t))); if (!result) return; // append first string to the new buffer in case there was no reallocation @@ -8046,7 +8120,7 @@ PUGI__NS_BEGIN size_t length_ = strlength(_buffer); const char_t* data_ = duplicate_string(_buffer, length_, alloc); - if (!data_) return 0; + if (!data_) return NULL; _buffer = data_; _uses_heap = true; @@ -8076,10 +8150,10 @@ PUGI__NS_BEGIN return _uses_heap; } }; -PUGI__NS_END +PUGI_IMPL_NS_END -PUGI__NS_BEGIN - PUGI__FN bool starts_with(const char_t* string, const char_t* pattern) +PUGI_IMPL_NS_BEGIN + PUGI_IMPL_FN bool starts_with(const char_t* string, const char_t* pattern) { while (*pattern && *string == *pattern) { @@ -8090,7 +8164,7 @@ PUGI__NS_BEGIN return *pattern == 0; } - PUGI__FN const char_t* find_char(const char_t* s, char_t c) + PUGI_IMPL_FN const char_t* find_char(const char_t* s, char_t c) { #ifdef PUGIXML_WCHAR_MODE return wcschr(s, c); @@ -8099,7 +8173,7 @@ PUGI__NS_BEGIN #endif } - PUGI__FN const char_t* find_substring(const char_t* s, const char_t* p) + PUGI_IMPL_FN const char_t* find_substring(const char_t* s, const char_t* p) { #ifdef PUGIXML_WCHAR_MODE // MSVC6 wcsstr bug workaround (if s is empty it always returns 0) @@ -8110,12 +8184,12 @@ PUGI__NS_BEGIN } // Converts symbol to lower case, if it is an ASCII one - PUGI__FN char_t tolower_ascii(char_t ch) + PUGI_IMPL_FN char_t tolower_ascii(char_t ch) { return static_cast(ch - 'A') < 26 ? static_cast(ch | ' ') : ch; } - PUGI__FN xpath_string string_value(const xpath_node& na, xpath_allocator* alloc) + PUGI_IMPL_FN xpath_string string_value(const xpath_node& na, xpath_allocator* alloc) { if (na.attribute()) return xpath_string::from_const(na.attribute().value()); @@ -8169,7 +8243,7 @@ PUGI__NS_BEGIN } } - PUGI__FN bool node_is_before_sibling(xml_node_struct* ln, xml_node_struct* rn) + PUGI_IMPL_FN bool node_is_before_sibling(xml_node_struct* ln, xml_node_struct* rn) { assert(ln->parent == rn->parent); @@ -8193,7 +8267,7 @@ PUGI__NS_BEGIN return !rs; } - PUGI__FN bool node_is_before(xml_node_struct* ln, xml_node_struct* rn) + PUGI_IMPL_FN bool node_is_before(xml_node_struct* ln, xml_node_struct* rn) { // find common ancestor at the same depth, if any xml_node_struct* lp = ln; @@ -8236,14 +8310,14 @@ PUGI__NS_BEGIN return node_is_before_sibling(ln, rn); } - PUGI__FN bool node_is_ancestor(xml_node_struct* parent, xml_node_struct* node) + PUGI_IMPL_FN bool node_is_ancestor(xml_node_struct* parent, xml_node_struct* node) { while (node && node != parent) node = node->parent; return parent && node == parent; } - PUGI__FN const void* document_buffer_order(const xpath_node& xnode) + PUGI_IMPL_FN const void* document_buffer_order(const xpath_node& xnode) { xml_node_struct* node = xnode.node().internal_object(); @@ -8255,7 +8329,7 @@ PUGI__NS_BEGIN if (node->value && (node->header & impl::xml_memory_page_value_allocated_or_shared_mask) == 0) return node->value; } - return 0; + return NULL; } xml_attribute_struct* attr = xnode.attribute().internal_object(); @@ -8268,10 +8342,10 @@ PUGI__NS_BEGIN if ((attr->header & impl::xml_memory_page_value_allocated_or_shared_mask) == 0) return attr->value; } - return 0; + return NULL; } - return 0; + return NULL; } struct document_order_comparator @@ -8328,10 +8402,10 @@ PUGI__NS_BEGIN } }; - PUGI__FN double gen_nan() + PUGI_IMPL_FN double gen_nan() { #if defined(__STDC_IEC_559__) || ((FLT_RADIX - 0 == 2) && (FLT_MAX_EXP - 0 == 128) && (FLT_MANT_DIG - 0 == 24)) - PUGI__STATIC_ASSERT(sizeof(float) == sizeof(uint32_t)); + PUGI_IMPL_STATIC_ASSERT(sizeof(float) == sizeof(uint32_t)); typedef uint32_t UI; // BCC5 workaround union { float f; UI i; } u; u.i = 0x7fc00000; @@ -8343,9 +8417,9 @@ PUGI__NS_BEGIN #endif } - PUGI__FN bool is_nan(double value) + PUGI_IMPL_FN bool is_nan(double value) { - #if defined(PUGI__MSVC_CRT_VERSION) || defined(__BORLANDC__) + #if defined(PUGI_IMPL_MSVC_CRT_VERSION) || defined(__BORLANDC__) return !!_isnan(value); #elif defined(fpclassify) && defined(FP_NAN) return fpclassify(value) == FP_NAN; @@ -8356,9 +8430,9 @@ PUGI__NS_BEGIN #endif } - PUGI__FN const char_t* convert_number_to_string_special(double value) + PUGI_IMPL_FN const char_t* convert_number_to_string_special(double value) { - #if defined(PUGI__MSVC_CRT_VERSION) || defined(__BORLANDC__) + #if defined(PUGI_IMPL_MSVC_CRT_VERSION) || defined(__BORLANDC__) if (_finite(value)) return (value == 0) ? PUGIXML_TEXT("0") : 0; if (_isnan(value)) return PUGIXML_TEXT("NaN"); return value > 0 ? PUGIXML_TEXT("Infinity") : PUGIXML_TEXT("-Infinity"); @@ -8384,16 +8458,16 @@ PUGI__NS_BEGIN if (v == 0) return PUGIXML_TEXT("0"); if (v != v) return PUGIXML_TEXT("NaN"); if (v * 2 == v) return value > 0 ? PUGIXML_TEXT("Infinity") : PUGIXML_TEXT("-Infinity"); - return 0; + return NULL; #endif } - PUGI__FN bool convert_number_to_boolean(double value) + PUGI_IMPL_FN bool convert_number_to_boolean(double value) { return (value != 0 && !is_nan(value)); } - PUGI__FN void truncate_zeros(char* begin, char* end) + PUGI_IMPL_FN void truncate_zeros(char* begin, char* end) { while (begin != end && end[-1] == '0') end--; @@ -8401,8 +8475,8 @@ PUGI__NS_BEGIN } // gets mantissa digits in the form of 0.xxxxx with 0. implied and the exponent -#if defined(PUGI__MSVC_CRT_VERSION) && PUGI__MSVC_CRT_VERSION >= 1400 - PUGI__FN void convert_number_to_mantissa_exponent(double value, char (&buffer)[32], char** out_mantissa, int* out_exponent) +#if defined(PUGI_IMPL_MSVC_CRT_VERSION) && PUGI_IMPL_MSVC_CRT_VERSION >= 1400 + PUGI_IMPL_FN void convert_number_to_mantissa_exponent(double value, char (&buffer)[32], char** out_mantissa, int* out_exponent) { // get base values int sign, exponent; @@ -8416,10 +8490,10 @@ PUGI__NS_BEGIN *out_exponent = exponent; } #else - PUGI__FN void convert_number_to_mantissa_exponent(double value, char (&buffer)[32], char** out_mantissa, int* out_exponent) + PUGI_IMPL_FN void convert_number_to_mantissa_exponent(double value, char (&buffer)[32], char** out_mantissa, int* out_exponent) { // get a scientific notation value with IEEE DBL_DIG decimals - PUGI__SNPRINTF(buffer, "%.*e", DBL_DIG, value); + PUGI_IMPL_SNPRINTF(buffer, "%.*e", DBL_DIG, value); // get the exponent (possibly negative) char* exponent_string = strchr(buffer, 'e'); @@ -8429,7 +8503,7 @@ PUGI__NS_BEGIN // extract mantissa string: skip sign char* mantissa = buffer[0] == '-' ? buffer + 1 : buffer; - assert(mantissa[0] != '0' && mantissa[1] == '.'); + assert(mantissa[0] != '0' && (mantissa[1] == '.' || mantissa[1] == ',')); // divide mantissa by 10 to eliminate integer part mantissa[1] = mantissa[0]; @@ -8445,7 +8519,7 @@ PUGI__NS_BEGIN } #endif - PUGI__FN xpath_string convert_number_to_string(double value, xpath_allocator* alloc) + PUGI_IMPL_FN xpath_string convert_number_to_string(double value, xpath_allocator* alloc) { // try special number conversion const char_t* special = convert_number_to_string_special(value); @@ -8512,10 +8586,10 @@ PUGI__NS_BEGIN return xpath_string::from_heap_preallocated(result, s); } - PUGI__FN bool check_string_to_number_format(const char_t* string) + PUGI_IMPL_FN bool check_string_to_number_format(const char_t* string) { // parse leading whitespace - while (PUGI__IS_CHARTYPE(*string, ct_space)) ++string; + while (PUGI_IMPL_IS_CHARTYPE(*string, ct_space)) ++string; // parse sign if (*string == '-') ++string; @@ -8523,39 +8597,39 @@ PUGI__NS_BEGIN if (!*string) return false; // if there is no integer part, there should be a decimal part with at least one digit - if (!PUGI__IS_CHARTYPEX(string[0], ctx_digit) && (string[0] != '.' || !PUGI__IS_CHARTYPEX(string[1], ctx_digit))) return false; + if (!PUGI_IMPL_IS_CHARTYPEX(string[0], ctx_digit) && (string[0] != '.' || !PUGI_IMPL_IS_CHARTYPEX(string[1], ctx_digit))) return false; // parse integer part - while (PUGI__IS_CHARTYPEX(*string, ctx_digit)) ++string; + while (PUGI_IMPL_IS_CHARTYPEX(*string, ctx_digit)) ++string; // parse decimal part if (*string == '.') { ++string; - while (PUGI__IS_CHARTYPEX(*string, ctx_digit)) ++string; + while (PUGI_IMPL_IS_CHARTYPEX(*string, ctx_digit)) ++string; } // parse trailing whitespace - while (PUGI__IS_CHARTYPE(*string, ct_space)) ++string; + while (PUGI_IMPL_IS_CHARTYPE(*string, ct_space)) ++string; return *string == 0; } - PUGI__FN double convert_string_to_number(const char_t* string) + PUGI_IMPL_FN double convert_string_to_number(const char_t* string) { // check string format if (!check_string_to_number_format(string)) return gen_nan(); // parse string #ifdef PUGIXML_WCHAR_MODE - return wcstod(string, 0); + return wcstod(string, NULL); #else - return strtod(string, 0); + return strtod(string, NULL); #endif } - PUGI__FN bool convert_string_to_number_scratch(char_t (&buffer)[32], const char_t* begin, const char_t* end, double* out_result) + PUGI_IMPL_FN bool convert_string_to_number_scratch(char_t (&buffer)[32], const char_t* begin, const char_t* end, double* out_result) { size_t length = static_cast(end - begin); char_t* scratch = buffer; @@ -8579,24 +8653,24 @@ PUGI__NS_BEGIN return true; } - PUGI__FN double round_nearest(double value) + PUGI_IMPL_FN double round_nearest(double value) { return floor(value + 0.5); } - PUGI__FN double round_nearest_nzero(double value) + PUGI_IMPL_FN double round_nearest_nzero(double value) { // same as round_nearest, but returns -0 for [-0.5, -0] // ceil is used to differentiate between +0 and -0 (we return -0 for [-0.5, -0] and +0 for +0) return (value >= -0.5 && value <= 0) ? ceil(value) : floor(value + 0.5); } - PUGI__FN const char_t* qualified_name(const xpath_node& node) + PUGI_IMPL_FN const char_t* qualified_name(const xpath_node& node) { return node.attribute() ? node.attribute().name() : node.node().name(); } - PUGI__FN const char_t* local_name(const xpath_node& node) + PUGI_IMPL_FN const char_t* local_name(const xpath_node& node) { const char_t* name = qualified_name(node); const char_t* p = find_char(name, ':'); @@ -8613,7 +8687,7 @@ PUGI__NS_BEGIN { const char_t* pos = find_char(name, ':'); - prefix = pos ? name : 0; + prefix = pos ? name : NULL; prefix_length = pos ? static_cast(pos - name) : 0; } @@ -8627,7 +8701,7 @@ PUGI__NS_BEGIN } }; - PUGI__FN const char_t* namespace_uri(xml_node node) + PUGI_IMPL_FN const char_t* namespace_uri(xml_node node) { namespace_uri_predicate pred = node.name(); @@ -8645,7 +8719,7 @@ PUGI__NS_BEGIN return PUGIXML_TEXT(""); } - PUGI__FN const char_t* namespace_uri(xml_attribute attr, xml_node parent) + PUGI_IMPL_FN const char_t* namespace_uri(xml_attribute attr, xml_node parent) { namespace_uri_predicate pred = attr.name(); @@ -8666,12 +8740,12 @@ PUGI__NS_BEGIN return PUGIXML_TEXT(""); } - PUGI__FN const char_t* namespace_uri(const xpath_node& node) + PUGI_IMPL_FN const char_t* namespace_uri(const xpath_node& node) { return node.attribute() ? namespace_uri(node.attribute(), node.parent()) : namespace_uri(node.node()); } - PUGI__FN char_t* normalize_space(char_t* buffer) + PUGI_IMPL_FN char_t* normalize_space(char_t* buffer) { char_t* write = buffer; @@ -8679,10 +8753,10 @@ PUGI__NS_BEGIN { char_t ch = *it++; - if (PUGI__IS_CHARTYPE(ch, ct_space)) + if (PUGI_IMPL_IS_CHARTYPE(ch, ct_space)) { // replace whitespace sequence with single space - while (PUGI__IS_CHARTYPE(*it, ct_space)) it++; + while (PUGI_IMPL_IS_CHARTYPE(*it, ct_space)) it++; // avoid leading spaces if (write != buffer) *write++ = ' '; @@ -8691,7 +8765,7 @@ PUGI__NS_BEGIN } // remove trailing space - if (write != buffer && PUGI__IS_CHARTYPE(write[-1], ct_space)) write--; + if (write != buffer && PUGI_IMPL_IS_CHARTYPE(write[-1], ct_space)) write--; // zero-terminate *write = 0; @@ -8699,13 +8773,13 @@ PUGI__NS_BEGIN return write; } - PUGI__FN char_t* translate(char_t* buffer, const char_t* from, const char_t* to, size_t to_length) + PUGI_IMPL_FN char_t* translate(char_t* buffer, const char_t* from, const char_t* to, size_t to_length) { char_t* write = buffer; while (*buffer) { - PUGI__DMC_VOLATILE char_t ch = *buffer++; + PUGI_IMPL_DMC_VOLATILE char_t ch = *buffer++; const char_t* pos = find_char(from, ch); @@ -8721,7 +8795,7 @@ PUGI__NS_BEGIN return write; } - PUGI__FN unsigned char* translate_table_generate(xpath_allocator* alloc, const char_t* from, const char_t* to) + PUGI_IMPL_FN unsigned char* translate_table_generate(xpath_allocator* alloc, const char_t* from, const char_t* to) { unsigned char table[128] = {0}; @@ -8731,7 +8805,7 @@ PUGI__NS_BEGIN unsigned int tc = static_cast(*to); if (fc >= 128 || tc >= 128) - return 0; + return NULL; // code=128 means "skip character" if (!table[fc]) @@ -8746,14 +8820,14 @@ PUGI__NS_BEGIN table[i] = static_cast(i); void* result = alloc->allocate(sizeof(table)); - if (!result) return 0; + if (!result) return NULL; memcpy(result, table, sizeof(table)); return static_cast(result); } - PUGI__FN char_t* translate_table(char_t* buffer, const unsigned char* table) + PUGI_IMPL_FN char_t* translate_table(char_t* buffer, const unsigned char* table) { char_t* write = buffer; @@ -8810,7 +8884,7 @@ PUGI__NS_BEGIN struct xpath_variable_string: xpath_variable { - xpath_variable_string(): xpath_variable(xpath_type_string), value(0) + xpath_variable_string(): xpath_variable(xpath_type_string), value(NULL) { } @@ -8835,7 +8909,7 @@ PUGI__NS_BEGIN static const xpath_node_set dummy_node_set; - PUGI__FN PUGI__UNSIGNED_OVERFLOW unsigned int hash_string(const char_t* str) + PUGI_IMPL_FN PUGI_IMPL_UNSIGNED_OVERFLOW unsigned int hash_string(const char_t* str) { // Jenkins one-at-a-time hash (http://en.wikipedia.org/wiki/Jenkins_hash_function#one-at-a-time) unsigned int result = 0; @@ -8854,14 +8928,14 @@ PUGI__NS_BEGIN return result; } - template PUGI__FN T* new_xpath_variable(const char_t* name) + template PUGI_IMPL_FN T* new_xpath_variable(const char_t* name) { size_t length = strlength(name); - if (length == 0) return 0; // empty variable names are invalid + if (length == 0) return NULL; // empty variable names are invalid // $$ we can't use offsetof(T, name) because T is non-POD, so we just allocate additional length characters void* memory = xml_memory::allocate(sizeof(T) + length * sizeof(char_t)); - if (!memory) return 0; + if (!memory) return NULL; T* result = new (memory) T(); @@ -8870,7 +8944,7 @@ PUGI__NS_BEGIN return result; } - PUGI__FN xpath_variable* new_xpath_variable(xpath_value_type type, const char_t* name) + PUGI_IMPL_FN xpath_variable* new_xpath_variable(xpath_value_type type, const char_t* name) { switch (type) { @@ -8887,17 +8961,17 @@ PUGI__NS_BEGIN return new_xpath_variable(name); default: - return 0; + return NULL; } } - template PUGI__FN void delete_xpath_variable(T* var) + template PUGI_IMPL_FN void delete_xpath_variable(T* var) { var->~T(); xml_memory::deallocate(var); } - PUGI__FN void delete_xpath_variable(xpath_value_type type, xpath_variable* var) + PUGI_IMPL_FN void delete_xpath_variable(xpath_value_type type, xpath_variable* var) { switch (type) { @@ -8922,7 +8996,7 @@ PUGI__NS_BEGIN } } - PUGI__FN bool copy_xpath_variable(xpath_variable* lhs, const xpath_variable* rhs) + PUGI_IMPL_FN bool copy_xpath_variable(xpath_variable* lhs, const xpath_variable* rhs) { switch (rhs->type()) { @@ -8944,7 +9018,7 @@ PUGI__NS_BEGIN } } - PUGI__FN bool get_variable_scratch(char_t (&buffer)[32], xpath_variable_set* set, const char_t* begin, const char_t* end, xpath_variable** out_result) + PUGI_IMPL_FN bool get_variable_scratch(char_t (&buffer)[32], xpath_variable_set* set, const char_t* begin, const char_t* end, xpath_variable** out_result) { size_t length = static_cast(end - begin); char_t* scratch = buffer; @@ -8967,11 +9041,11 @@ PUGI__NS_BEGIN return true; } -PUGI__NS_END +PUGI_IMPL_NS_END // Internal node set class -PUGI__NS_BEGIN - PUGI__FN xpath_node_set::type_t xpath_get_order(const xpath_node* begin, const xpath_node* end) +PUGI_IMPL_NS_BEGIN + PUGI_IMPL_FN xpath_node_set::type_t xpath_get_order(const xpath_node* begin, const xpath_node* end) { if (end - begin < 2) return xpath_node_set::type_sorted; @@ -8987,7 +9061,7 @@ PUGI__NS_BEGIN return first ? xpath_node_set::type_sorted : xpath_node_set::type_sorted_reverse; } - PUGI__FN xpath_node_set::type_t xpath_sort(xpath_node* begin, xpath_node* end, xpath_node_set::type_t type, bool rev) + PUGI_IMPL_FN xpath_node_set::type_t xpath_sort(xpath_node* begin, xpath_node* end, xpath_node_set::type_t type, bool rev) { xpath_node_set::type_t order = rev ? xpath_node_set::type_sorted_reverse : xpath_node_set::type_sorted; @@ -9010,7 +9084,7 @@ PUGI__NS_BEGIN return order; } - PUGI__FN xpath_node xpath_first(const xpath_node* begin, const xpath_node* end, xpath_node_set::type_t type) + PUGI_IMPL_FN xpath_node xpath_first(const xpath_node* begin, const xpath_node* end, xpath_node_set::type_t type) { if (begin == end) return xpath_node(); @@ -9040,7 +9114,7 @@ PUGI__NS_BEGIN xpath_node* _eos; public: - xpath_node_set_raw(): _type(xpath_node_set::type_unsorted), _begin(0), _end(0), _eos(0) + xpath_node_set_raw(): _type(xpath_node_set::type_unsorted), _begin(NULL), _end(NULL), _eos(NULL) { } @@ -9164,7 +9238,7 @@ PUGI__NS_BEGIN } }; - PUGI__FN_NO_INLINE void xpath_node_set_raw::push_back_grow(const xpath_node& node, xpath_allocator* alloc) + PUGI_IMPL_FN_NO_INLINE void xpath_node_set_raw::push_back_grow(const xpath_node& node, xpath_allocator* alloc) { size_t capacity = static_cast(_eos - _begin); @@ -9183,9 +9257,9 @@ PUGI__NS_BEGIN // push *_end++ = node; } -PUGI__NS_END +PUGI_IMPL_NS_END -PUGI__NS_BEGIN +PUGI_IMPL_NS_BEGIN struct xpath_context { xpath_node n; @@ -9232,7 +9306,7 @@ PUGI__NS_BEGIN const char_t* begin; const char_t* end; - xpath_lexer_string(): begin(0), end(0) + xpath_lexer_string(): begin(NULL), end(NULL) { } @@ -9267,7 +9341,7 @@ PUGI__NS_BEGIN { const char_t* cur = _cur; - while (PUGI__IS_CHARTYPE(*cur, ct_space)) ++cur; + while (PUGI_IMPL_IS_CHARTYPE(*cur, ct_space)) ++cur; // save lexeme position for error reporting _cur_lexeme_pos = cur; @@ -9349,17 +9423,17 @@ PUGI__NS_BEGIN case '$': cur += 1; - if (PUGI__IS_CHARTYPEX(*cur, ctx_start_symbol)) + if (PUGI_IMPL_IS_CHARTYPEX(*cur, ctx_start_symbol)) { _cur_lexeme_contents.begin = cur; - while (PUGI__IS_CHARTYPEX(*cur, ctx_symbol)) cur++; + while (PUGI_IMPL_IS_CHARTYPEX(*cur, ctx_symbol)) cur++; - if (cur[0] == ':' && PUGI__IS_CHARTYPEX(cur[1], ctx_symbol)) // qname + if (cur[0] == ':' && PUGI_IMPL_IS_CHARTYPEX(cur[1], ctx_symbol)) // qname { cur++; // : - while (PUGI__IS_CHARTYPEX(*cur, ctx_symbol)) cur++; + while (PUGI_IMPL_IS_CHARTYPEX(*cur, ctx_symbol)) cur++; } _cur_lexeme_contents.end = cur; @@ -9422,13 +9496,13 @@ PUGI__NS_BEGIN cur += 2; _cur_lexeme = lex_double_dot; } - else if (PUGI__IS_CHARTYPEX(*(cur+1), ctx_digit)) + else if (PUGI_IMPL_IS_CHARTYPEX(*(cur+1), ctx_digit)) { _cur_lexeme_contents.begin = cur; // . ++cur; - while (PUGI__IS_CHARTYPEX(*cur, ctx_digit)) cur++; + while (PUGI_IMPL_IS_CHARTYPEX(*cur, ctx_digit)) cur++; _cur_lexeme_contents.end = cur; @@ -9482,28 +9556,28 @@ PUGI__NS_BEGIN break; default: - if (PUGI__IS_CHARTYPEX(*cur, ctx_digit)) + if (PUGI_IMPL_IS_CHARTYPEX(*cur, ctx_digit)) { _cur_lexeme_contents.begin = cur; - while (PUGI__IS_CHARTYPEX(*cur, ctx_digit)) cur++; + while (PUGI_IMPL_IS_CHARTYPEX(*cur, ctx_digit)) cur++; if (*cur == '.') { cur++; - while (PUGI__IS_CHARTYPEX(*cur, ctx_digit)) cur++; + while (PUGI_IMPL_IS_CHARTYPEX(*cur, ctx_digit)) cur++; } _cur_lexeme_contents.end = cur; _cur_lexeme = lex_number; } - else if (PUGI__IS_CHARTYPEX(*cur, ctx_start_symbol)) + else if (PUGI_IMPL_IS_CHARTYPEX(*cur, ctx_start_symbol)) { _cur_lexeme_contents.begin = cur; - while (PUGI__IS_CHARTYPEX(*cur, ctx_symbol)) cur++; + while (PUGI_IMPL_IS_CHARTYPEX(*cur, ctx_symbol)) cur++; if (cur[0] == ':') { @@ -9511,11 +9585,11 @@ PUGI__NS_BEGIN { cur += 2; // :* } - else if (PUGI__IS_CHARTYPEX(cur[1], ctx_symbol)) // namespace test qname + else if (PUGI_IMPL_IS_CHARTYPEX(cur[1], ctx_symbol)) // namespace test qname { cur++; // : - while (PUGI__IS_CHARTYPEX(*cur, ctx_symbol)) cur++; + while (PUGI_IMPL_IS_CHARTYPEX(*cur, ctx_symbol)) cur++; } } @@ -9927,7 +10001,8 @@ PUGI__NS_BEGIN xpath_node* last = ns.begin() + first; - xpath_context c(xpath_node(), 1, size); + xpath_node cn; + xpath_context c(cn, 1, size); double er = expr->eval_number(c, stack); @@ -10014,7 +10089,7 @@ PUGI__NS_BEGIN { assert(n); - xml_node_type type = PUGI__NODETYPE(n); + xml_node_type type = PUGI_IMPL_NODETYPE(n); switch (_test) { @@ -10419,40 +10494,40 @@ PUGI__NS_BEGIN public: xpath_ast_node(ast_type_t type, xpath_value_type rettype_, const char_t* value): - _type(static_cast(type)), _rettype(static_cast(rettype_)), _axis(0), _test(0), _left(0), _right(0), _next(0) + _type(static_cast(type)), _rettype(static_cast(rettype_)), _axis(0), _test(0), _left(NULL), _right(NULL), _next(NULL) { assert(type == ast_string_constant); _data.string = value; } xpath_ast_node(ast_type_t type, xpath_value_type rettype_, double value): - _type(static_cast(type)), _rettype(static_cast(rettype_)), _axis(0), _test(0), _left(0), _right(0), _next(0) + _type(static_cast(type)), _rettype(static_cast(rettype_)), _axis(0), _test(0), _left(NULL), _right(NULL), _next(NULL) { assert(type == ast_number_constant); _data.number = value; } xpath_ast_node(ast_type_t type, xpath_value_type rettype_, xpath_variable* value): - _type(static_cast(type)), _rettype(static_cast(rettype_)), _axis(0), _test(0), _left(0), _right(0), _next(0) + _type(static_cast(type)), _rettype(static_cast(rettype_)), _axis(0), _test(0), _left(NULL), _right(NULL), _next(NULL) { assert(type == ast_variable); _data.variable = value; } - xpath_ast_node(ast_type_t type, xpath_value_type rettype_, xpath_ast_node* left = 0, xpath_ast_node* right = 0): - _type(static_cast(type)), _rettype(static_cast(rettype_)), _axis(0), _test(0), _left(left), _right(right), _next(0) + xpath_ast_node(ast_type_t type, xpath_value_type rettype_, xpath_ast_node* left = NULL, xpath_ast_node* right = NULL): + _type(static_cast(type)), _rettype(static_cast(rettype_)), _axis(0), _test(0), _left(left), _right(right), _next(NULL) { } xpath_ast_node(ast_type_t type, xpath_ast_node* left, axis_t axis, nodetest_t test, const char_t* contents): - _type(static_cast(type)), _rettype(xpath_type_node_set), _axis(static_cast(axis)), _test(static_cast(test)), _left(left), _right(0), _next(0) + _type(static_cast(type)), _rettype(xpath_type_node_set), _axis(static_cast(axis)), _test(static_cast(test)), _left(left), _right(NULL), _next(NULL) { assert(type == ast_step); _data.nodetest = contents; } xpath_ast_node(ast_type_t type, xpath_ast_node* left, xpath_ast_node* right, predicate_t test): - _type(static_cast(type)), _rettype(xpath_type_node_set), _axis(0), _test(static_cast(test)), _left(left), _right(right), _next(0) + _type(static_cast(type)), _rettype(xpath_type_node_set), _axis(0), _test(static_cast(test)), _left(left), _right(right), _next(NULL) { assert(type == ast_filter || type == ast_predicate); } @@ -10512,7 +10587,7 @@ PUGI__NS_BEGIN xpath_string lr = _left->eval_string(c, stack); xpath_string rr = _right->eval_string(c, stack); - return find_substring(lr.c_str(), rr.c_str()) != 0; + return find_substring(lr.c_str(), rr.c_str()) != NULL; } case ast_func_boolean: @@ -11321,7 +11396,7 @@ PUGI__NS_BEGIN _result->error = message; _result->offset = _lexer.current_pos() - _query; - return 0; + return NULL; } xpath_ast_node* error_oom() @@ -11329,7 +11404,7 @@ PUGI__NS_BEGIN assert(_alloc->_error); *_alloc->_error = true; - return 0; + return NULL; } xpath_ast_node* error_rec() @@ -11345,37 +11420,37 @@ PUGI__NS_BEGIN xpath_ast_node* alloc_node(ast_type_t type, xpath_value_type rettype, const char_t* value) { void* memory = alloc_node(); - return memory ? new (memory) xpath_ast_node(type, rettype, value) : 0; + return memory ? new (memory) xpath_ast_node(type, rettype, value) : NULL; } xpath_ast_node* alloc_node(ast_type_t type, xpath_value_type rettype, double value) { void* memory = alloc_node(); - return memory ? new (memory) xpath_ast_node(type, rettype, value) : 0; + return memory ? new (memory) xpath_ast_node(type, rettype, value) : NULL; } xpath_ast_node* alloc_node(ast_type_t type, xpath_value_type rettype, xpath_variable* value) { void* memory = alloc_node(); - return memory ? new (memory) xpath_ast_node(type, rettype, value) : 0; + return memory ? new (memory) xpath_ast_node(type, rettype, value) : NULL; } - xpath_ast_node* alloc_node(ast_type_t type, xpath_value_type rettype, xpath_ast_node* left = 0, xpath_ast_node* right = 0) + xpath_ast_node* alloc_node(ast_type_t type, xpath_value_type rettype, xpath_ast_node* left = NULL, xpath_ast_node* right = NULL) { void* memory = alloc_node(); - return memory ? new (memory) xpath_ast_node(type, rettype, left, right) : 0; + return memory ? new (memory) xpath_ast_node(type, rettype, left, right) : NULL; } xpath_ast_node* alloc_node(ast_type_t type, xpath_ast_node* left, axis_t axis, nodetest_t test, const char_t* contents) { void* memory = alloc_node(); - return memory ? new (memory) xpath_ast_node(type, left, axis, test, contents) : 0; + return memory ? new (memory) xpath_ast_node(type, left, axis, test, contents) : NULL; } xpath_ast_node* alloc_node(ast_type_t type, xpath_ast_node* left, xpath_ast_node* right, predicate_t test) { void* memory = alloc_node(); - return memory ? new (memory) xpath_ast_node(type, left, right, test) : 0; + return memory ? new (memory) xpath_ast_node(type, left, right, test) : NULL; } const char_t* alloc_string(const xpath_lexer_string& value) @@ -11386,7 +11461,7 @@ PUGI__NS_BEGIN size_t length = static_cast(value.end - value.begin); char_t* c = static_cast(_alloc->allocate((length + 1) * sizeof(char_t))); - if (!c) return 0; + if (!c) return NULL; memcpy(c, value.begin, length * sizeof(char_t)); c[length] = 0; @@ -11629,7 +11704,7 @@ PUGI__NS_BEGIN if (!_variables) return error("Unknown variable: variable set is not provided"); - xpath_variable* var = 0; + xpath_variable* var = NULL; if (!get_variable_scratch(_scratch, _variables, name.begin, name.end, &var)) return error_oom(); @@ -11646,7 +11721,7 @@ PUGI__NS_BEGIN _lexer.next(); xpath_ast_node* n = parse_expression(); - if (!n) return 0; + if (!n) return NULL; if (_lexer.current() != lex_close_brace) return error("Expected ')' to match an opening '('"); @@ -11659,7 +11734,7 @@ PUGI__NS_BEGIN case lex_quoted_string: { const char_t* value = alloc_string(_lexer.contents()); - if (!value) return 0; + if (!value) return NULL; _lexer.next(); @@ -11680,13 +11755,13 @@ PUGI__NS_BEGIN case lex_string: { - xpath_ast_node* args[2] = {0}; + xpath_ast_node* args[2] = {NULL}; size_t argc = 0; xpath_lexer_string function = _lexer.contents(); _lexer.next(); - xpath_ast_node* last_arg = 0; + xpath_ast_node* last_arg = NULL; if (_lexer.current() != lex_open_brace) return error("Unrecognized function call"); @@ -11707,7 +11782,7 @@ PUGI__NS_BEGIN return error_rec(); xpath_ast_node* n = parse_expression(); - if (!n) return 0; + if (!n) return NULL; if (argc < 2) args[argc] = n; else last_arg->set_next(n); @@ -11734,7 +11809,7 @@ PUGI__NS_BEGIN xpath_ast_node* parse_filter_expression() { xpath_ast_node* n = parse_primary_expression(); - if (!n) return 0; + if (!n) return NULL; size_t old_depth = _depth; @@ -11749,10 +11824,10 @@ PUGI__NS_BEGIN return error("Predicate has to be applied to node set"); xpath_ast_node* expr = parse_expression(); - if (!expr) return 0; + if (!expr) return NULL; n = alloc_node(ast_filter, n, expr, predicate_default); - if (!n) return 0; + if (!n) return NULL; if (_lexer.current() != lex_close_square_brace) return error("Expected ']' to match an opening '['"); @@ -11792,7 +11867,7 @@ PUGI__NS_BEGIN if (_lexer.current() == lex_open_square_brace) return error("Predicates are not allowed after an abbreviated step"); - return alloc_node(ast_step, set, axis_self, nodetest_type_node, 0); + return alloc_node(ast_step, set, axis_self, nodetest_type_node, NULL); } else if (_lexer.current() == lex_double_dot) { @@ -11801,7 +11876,7 @@ PUGI__NS_BEGIN if (_lexer.current() == lex_open_square_brace) return error("Predicates are not allowed after an abbreviated step"); - return alloc_node(ast_step, set, axis_parent, nodetest_type_node, 0); + return alloc_node(ast_step, set, axis_parent, nodetest_type_node, NULL); } nodetest_t nt_type = nodetest_none; @@ -11908,14 +11983,14 @@ PUGI__NS_BEGIN } const char_t* nt_name_copy = alloc_string(nt_name); - if (!nt_name_copy) return 0; + if (!nt_name_copy) return NULL; xpath_ast_node* n = alloc_node(ast_step, set, axis, nt_type, nt_name_copy); - if (!n) return 0; + if (!n) return NULL; size_t old_depth = _depth; - xpath_ast_node* last = 0; + xpath_ast_node* last = NULL; while (_lexer.current() == lex_open_square_brace) { @@ -11925,10 +12000,10 @@ PUGI__NS_BEGIN return error_rec(); xpath_ast_node* expr = parse_expression(); - if (!expr) return 0; + if (!expr) return NULL; - xpath_ast_node* pred = alloc_node(ast_predicate, 0, expr, predicate_default); - if (!pred) return 0; + xpath_ast_node* pred = alloc_node(ast_predicate, NULL, expr, predicate_default); + if (!pred) return NULL; if (_lexer.current() != lex_close_square_brace) return error("Expected ']' to match an opening '['"); @@ -11949,7 +12024,7 @@ PUGI__NS_BEGIN xpath_ast_node* parse_relative_location_path(xpath_ast_node* set) { xpath_ast_node* n = parse_step(set); - if (!n) return 0; + if (!n) return NULL; size_t old_depth = _depth; @@ -11960,8 +12035,8 @@ PUGI__NS_BEGIN if (l == lex_double_slash) { - n = alloc_node(ast_step, n, axis_descendant_or_self, nodetest_type_node, 0); - if (!n) return 0; + n = alloc_node(ast_step, n, axis_descendant_or_self, nodetest_type_node, NULL); + if (!n) return NULL; ++_depth; } @@ -11970,7 +12045,7 @@ PUGI__NS_BEGIN return error_rec(); n = parse_step(n); - if (!n) return 0; + if (!n) return NULL; } _depth = old_depth; @@ -11987,7 +12062,7 @@ PUGI__NS_BEGIN _lexer.next(); xpath_ast_node* n = alloc_node(ast_step_root, xpath_type_node_set); - if (!n) return 0; + if (!n) return NULL; // relative location path can start from axis_attribute, dot, double_dot, multiply and string lexemes; any other lexeme means standalone root path lexeme_t l = _lexer.current(); @@ -12002,16 +12077,16 @@ PUGI__NS_BEGIN _lexer.next(); xpath_ast_node* n = alloc_node(ast_step_root, xpath_type_node_set); - if (!n) return 0; + if (!n) return NULL; - n = alloc_node(ast_step, n, axis_descendant_or_self, nodetest_type_node, 0); - if (!n) return 0; + n = alloc_node(ast_step, n, axis_descendant_or_self, nodetest_type_node, NULL); + if (!n) return NULL; return parse_relative_location_path(n); } // else clause moved outside of if because of bogus warning 'control may reach end of non-void function being inlined' in gcc 4.0.1 - return parse_relative_location_path(0); + return parse_relative_location_path(NULL); } // PathExpr ::= LocationPath @@ -12037,7 +12112,7 @@ PUGI__NS_BEGIN // This is either a function call, or not - if not, we shall proceed with location path const char_t* state = _lexer.state(); - while (PUGI__IS_CHARTYPE(*state, ct_space)) ++state; + while (PUGI_IMPL_IS_CHARTYPE(*state, ct_space)) ++state; if (*state != '(') return parse_location_path(); @@ -12048,7 +12123,7 @@ PUGI__NS_BEGIN } xpath_ast_node* n = parse_filter_expression(); - if (!n) return 0; + if (!n) return NULL; if (_lexer.current() == lex_slash || _lexer.current() == lex_double_slash) { @@ -12060,8 +12135,8 @@ PUGI__NS_BEGIN if (n->rettype() != xpath_type_node_set) return error("Step has to be applied to node set"); - n = alloc_node(ast_step, n, axis_descendant_or_self, nodetest_type_node, 0); - if (!n) return 0; + n = alloc_node(ast_step, n, axis_descendant_or_self, nodetest_type_node, NULL); + if (!n) return NULL; } // select from location path @@ -12076,7 +12151,7 @@ PUGI__NS_BEGIN // precedence 7+ - only parses union expressions xpath_ast_node* n = parse_expression(7); - if (!n) return 0; + if (!n) return NULL; return alloc_node(ast_op_negate, xpath_type_number, n); } @@ -12164,14 +12239,14 @@ PUGI__NS_BEGIN return error_rec(); xpath_ast_node* rhs = parse_path_or_unary_expression(); - if (!rhs) return 0; + if (!rhs) return NULL; binary_op_t nextop = binary_op_t::parse(_lexer); while (nextop.asttype != ast_unknown && nextop.precedence > op.precedence) { rhs = parse_expression_rec(rhs, nextop.precedence); - if (!rhs) return 0; + if (!rhs) return NULL; nextop = binary_op_t::parse(_lexer); } @@ -12180,7 +12255,7 @@ PUGI__NS_BEGIN return error("Union operator has to be applied to node sets"); lhs = alloc_node(op.asttype, op.rettype, lhs, rhs); - if (!lhs) return 0; + if (!lhs) return NULL; op = binary_op_t::parse(_lexer); } @@ -12214,7 +12289,7 @@ PUGI__NS_BEGIN return error_rec(); xpath_ast_node* n = parse_path_or_unary_expression(); - if (!n) return 0; + if (!n) return NULL; n = parse_expression_rec(n, limit); @@ -12230,7 +12305,7 @@ PUGI__NS_BEGIN xpath_ast_node* parse() { xpath_ast_node* n = parse_expression(); - if (!n) return 0; + if (!n) return NULL; assert(_depth == 0); @@ -12254,7 +12329,7 @@ PUGI__NS_BEGIN static xpath_query_impl* create() { void* memory = xml_memory::allocate(sizeof(xpath_query_impl)); - if (!memory) return 0; + if (!memory) return NULL; return new (memory) xpath_query_impl(); } @@ -12268,9 +12343,9 @@ PUGI__NS_BEGIN xml_memory::deallocate(impl); } - xpath_query_impl(): root(0), alloc(&block, &oom), oom(false) + xpath_query_impl(): root(NULL), alloc(&block, &oom), oom(false) { - block.next = 0; + block.next = NULL; block.capacity = sizeof(block.data); } @@ -12280,9 +12355,9 @@ PUGI__NS_BEGIN bool oom; }; - PUGI__FN impl::xpath_ast_node* evaluate_node_set_prepare(xpath_query_impl* impl) + PUGI_IMPL_FN impl::xpath_ast_node* evaluate_node_set_prepare(xpath_query_impl* impl) { - if (!impl) return 0; + if (!impl) return NULL; if (impl->root->rettype() != xpath_type_node_set) { @@ -12298,91 +12373,91 @@ PUGI__NS_BEGIN return impl->root; } -PUGI__NS_END +PUGI_IMPL_NS_END namespace pugi { #ifndef PUGIXML_NO_EXCEPTIONS - PUGI__FN xpath_exception::xpath_exception(const xpath_parse_result& result_): _result(result_) + PUGI_IMPL_FN xpath_exception::xpath_exception(const xpath_parse_result& result_): _result(result_) { assert(_result.error); } - PUGI__FN const char* xpath_exception::what() const throw() + PUGI_IMPL_FN const char* xpath_exception::what() const throw() { return _result.error; } - PUGI__FN const xpath_parse_result& xpath_exception::result() const + PUGI_IMPL_FN const xpath_parse_result& xpath_exception::result() const { return _result; } #endif - PUGI__FN xpath_node::xpath_node() + PUGI_IMPL_FN xpath_node::xpath_node() { } - PUGI__FN xpath_node::xpath_node(const xml_node& node_): _node(node_) + PUGI_IMPL_FN xpath_node::xpath_node(const xml_node& node_): _node(node_) { } - PUGI__FN xpath_node::xpath_node(const xml_attribute& attribute_, const xml_node& parent_): _node(attribute_ ? parent_ : xml_node()), _attribute(attribute_) + PUGI_IMPL_FN xpath_node::xpath_node(const xml_attribute& attribute_, const xml_node& parent_): _node(attribute_ ? parent_ : xml_node()), _attribute(attribute_) { } - PUGI__FN xml_node xpath_node::node() const + PUGI_IMPL_FN xml_node xpath_node::node() const { return _attribute ? xml_node() : _node; } - PUGI__FN xml_attribute xpath_node::attribute() const + PUGI_IMPL_FN xml_attribute xpath_node::attribute() const { return _attribute; } - PUGI__FN xml_node xpath_node::parent() const + PUGI_IMPL_FN xml_node xpath_node::parent() const { return _attribute ? _node : _node.parent(); } - PUGI__FN static void unspecified_bool_xpath_node(xpath_node***) + PUGI_IMPL_FN static void unspecified_bool_xpath_node(xpath_node***) { } - PUGI__FN xpath_node::operator xpath_node::unspecified_bool_type() const + PUGI_IMPL_FN xpath_node::operator xpath_node::unspecified_bool_type() const { - return (_node || _attribute) ? unspecified_bool_xpath_node : 0; + return (_node || _attribute) ? unspecified_bool_xpath_node : NULL; } - PUGI__FN bool xpath_node::operator!() const + PUGI_IMPL_FN bool xpath_node::operator!() const { return !(_node || _attribute); } - PUGI__FN bool xpath_node::operator==(const xpath_node& n) const + PUGI_IMPL_FN bool xpath_node::operator==(const xpath_node& n) const { return _node == n._node && _attribute == n._attribute; } - PUGI__FN bool xpath_node::operator!=(const xpath_node& n) const + PUGI_IMPL_FN bool xpath_node::operator!=(const xpath_node& n) const { return _node != n._node || _attribute != n._attribute; } #ifdef __BORLANDC__ - PUGI__FN bool operator&&(const xpath_node& lhs, bool rhs) + PUGI_IMPL_FN bool operator&&(const xpath_node& lhs, bool rhs) { return (bool)lhs && rhs; } - PUGI__FN bool operator||(const xpath_node& lhs, bool rhs) + PUGI_IMPL_FN bool operator||(const xpath_node& lhs, bool rhs) { return (bool)lhs || rhs; } #endif - PUGI__FN void xpath_node_set::_assign(const_iterator begin_, const_iterator end_, type_t type_) + PUGI_IMPL_FN void xpath_node_set::_assign(const_iterator begin_, const_iterator end_, type_t type_) { assert(begin_ <= end_); @@ -12414,7 +12489,7 @@ namespace pugi } #ifdef PUGIXML_HAS_MOVE - PUGI__FN void xpath_node_set::_move(xpath_node_set& rhs) PUGIXML_NOEXCEPT + PUGI_IMPL_FN void xpath_node_set::_move(xpath_node_set& rhs) PUGIXML_NOEXCEPT { _type = rhs._type; _storage[0] = rhs._storage[0]; @@ -12427,27 +12502,27 @@ namespace pugi } #endif - PUGI__FN xpath_node_set::xpath_node_set(): _type(type_unsorted), _begin(_storage), _end(_storage) + PUGI_IMPL_FN xpath_node_set::xpath_node_set(): _type(type_unsorted), _begin(_storage), _end(_storage) { } - PUGI__FN xpath_node_set::xpath_node_set(const_iterator begin_, const_iterator end_, type_t type_): _type(type_unsorted), _begin(_storage), _end(_storage) + PUGI_IMPL_FN xpath_node_set::xpath_node_set(const_iterator begin_, const_iterator end_, type_t type_): _type(type_unsorted), _begin(_storage), _end(_storage) { _assign(begin_, end_, type_); } - PUGI__FN xpath_node_set::~xpath_node_set() + PUGI_IMPL_FN xpath_node_set::~xpath_node_set() { if (_begin != _storage) impl::xml_memory::deallocate(_begin); } - PUGI__FN xpath_node_set::xpath_node_set(const xpath_node_set& ns): _type(type_unsorted), _begin(_storage), _end(_storage) + PUGI_IMPL_FN xpath_node_set::xpath_node_set(const xpath_node_set& ns): _type(type_unsorted), _begin(_storage), _end(_storage) { _assign(ns._begin, ns._end, ns._type); } - PUGI__FN xpath_node_set& xpath_node_set::operator=(const xpath_node_set& ns) + PUGI_IMPL_FN xpath_node_set& xpath_node_set::operator=(const xpath_node_set& ns) { if (this == &ns) return *this; @@ -12457,12 +12532,12 @@ namespace pugi } #ifdef PUGIXML_HAS_MOVE - PUGI__FN xpath_node_set::xpath_node_set(xpath_node_set&& rhs) PUGIXML_NOEXCEPT: _type(type_unsorted), _begin(_storage), _end(_storage) + PUGI_IMPL_FN xpath_node_set::xpath_node_set(xpath_node_set&& rhs) PUGIXML_NOEXCEPT: _type(type_unsorted), _begin(_storage), _end(_storage) { _move(rhs); } - PUGI__FN xpath_node_set& xpath_node_set::operator=(xpath_node_set&& rhs) PUGIXML_NOEXCEPT + PUGI_IMPL_FN xpath_node_set& xpath_node_set::operator=(xpath_node_set&& rhs) PUGIXML_NOEXCEPT { if (this == &rhs) return *this; @@ -12475,66 +12550,66 @@ namespace pugi } #endif - PUGI__FN xpath_node_set::type_t xpath_node_set::type() const + PUGI_IMPL_FN xpath_node_set::type_t xpath_node_set::type() const { return _type; } - PUGI__FN size_t xpath_node_set::size() const + PUGI_IMPL_FN size_t xpath_node_set::size() const { return _end - _begin; } - PUGI__FN bool xpath_node_set::empty() const + PUGI_IMPL_FN bool xpath_node_set::empty() const { return _begin == _end; } - PUGI__FN const xpath_node& xpath_node_set::operator[](size_t index) const + PUGI_IMPL_FN const xpath_node& xpath_node_set::operator[](size_t index) const { assert(index < size()); return _begin[index]; } - PUGI__FN xpath_node_set::const_iterator xpath_node_set::begin() const + PUGI_IMPL_FN xpath_node_set::const_iterator xpath_node_set::begin() const { return _begin; } - PUGI__FN xpath_node_set::const_iterator xpath_node_set::end() const + PUGI_IMPL_FN xpath_node_set::const_iterator xpath_node_set::end() const { return _end; } - PUGI__FN void xpath_node_set::sort(bool reverse) + PUGI_IMPL_FN void xpath_node_set::sort(bool reverse) { _type = impl::xpath_sort(_begin, _end, _type, reverse); } - PUGI__FN xpath_node xpath_node_set::first() const + PUGI_IMPL_FN xpath_node xpath_node_set::first() const { return impl::xpath_first(_begin, _end, _type); } - PUGI__FN xpath_parse_result::xpath_parse_result(): error("Internal error"), offset(0) + PUGI_IMPL_FN xpath_parse_result::xpath_parse_result(): error("Internal error"), offset(0) { } - PUGI__FN xpath_parse_result::operator bool() const + PUGI_IMPL_FN xpath_parse_result::operator bool() const { - return error == 0; + return error == NULL; } - PUGI__FN const char* xpath_parse_result::description() const + PUGI_IMPL_FN const char* xpath_parse_result::description() const { return error ? error : "No error"; } - PUGI__FN xpath_variable::xpath_variable(xpath_value_type type_): _type(type_), _next(0) + PUGI_IMPL_FN xpath_variable::xpath_variable(xpath_value_type type_): _type(type_), _next(NULL) { } - PUGI__FN const char_t* xpath_variable::name() const + PUGI_IMPL_FN const char_t* xpath_variable::name() const { switch (_type) { @@ -12552,37 +12627,37 @@ namespace pugi default: assert(false && "Invalid variable type"); // unreachable - return 0; + return NULL; } } - PUGI__FN xpath_value_type xpath_variable::type() const + PUGI_IMPL_FN xpath_value_type xpath_variable::type() const { return _type; } - PUGI__FN bool xpath_variable::get_boolean() const + PUGI_IMPL_FN bool xpath_variable::get_boolean() const { return (_type == xpath_type_boolean) ? static_cast(this)->value : false; } - PUGI__FN double xpath_variable::get_number() const + PUGI_IMPL_FN double xpath_variable::get_number() const { return (_type == xpath_type_number) ? static_cast(this)->value : impl::gen_nan(); } - PUGI__FN const char_t* xpath_variable::get_string() const + PUGI_IMPL_FN const char_t* xpath_variable::get_string() const { - const char_t* value = (_type == xpath_type_string) ? static_cast(this)->value : 0; + const char_t* value = (_type == xpath_type_string) ? static_cast(this)->value : NULL; return value ? value : PUGIXML_TEXT(""); } - PUGI__FN const xpath_node_set& xpath_variable::get_node_set() const + PUGI_IMPL_FN const xpath_node_set& xpath_variable::get_node_set() const { return (_type == xpath_type_node_set) ? static_cast(this)->value : impl::dummy_node_set; } - PUGI__FN bool xpath_variable::set(bool value) + PUGI_IMPL_FN bool xpath_variable::set(bool value) { if (_type != xpath_type_boolean) return false; @@ -12590,7 +12665,7 @@ namespace pugi return true; } - PUGI__FN bool xpath_variable::set(double value) + PUGI_IMPL_FN bool xpath_variable::set(double value) { if (_type != xpath_type_number) return false; @@ -12598,7 +12673,7 @@ namespace pugi return true; } - PUGI__FN bool xpath_variable::set(const char_t* value) + PUGI_IMPL_FN bool xpath_variable::set(const char_t* value) { if (_type != xpath_type_string) return false; @@ -12619,7 +12694,7 @@ namespace pugi return true; } - PUGI__FN bool xpath_variable::set(const xpath_node_set& value) + PUGI_IMPL_FN bool xpath_variable::set(const xpath_node_set& value) { if (_type != xpath_type_node_set) return false; @@ -12627,27 +12702,27 @@ namespace pugi return true; } - PUGI__FN xpath_variable_set::xpath_variable_set() + PUGI_IMPL_FN xpath_variable_set::xpath_variable_set() { for (size_t i = 0; i < sizeof(_data) / sizeof(_data[0]); ++i) - _data[i] = 0; + _data[i] = NULL; } - PUGI__FN xpath_variable_set::~xpath_variable_set() + PUGI_IMPL_FN xpath_variable_set::~xpath_variable_set() { for (size_t i = 0; i < sizeof(_data) / sizeof(_data[0]); ++i) _destroy(_data[i]); } - PUGI__FN xpath_variable_set::xpath_variable_set(const xpath_variable_set& rhs) + PUGI_IMPL_FN xpath_variable_set::xpath_variable_set(const xpath_variable_set& rhs) { for (size_t i = 0; i < sizeof(_data) / sizeof(_data[0]); ++i) - _data[i] = 0; + _data[i] = NULL; _assign(rhs); } - PUGI__FN xpath_variable_set& xpath_variable_set::operator=(const xpath_variable_set& rhs) + PUGI_IMPL_FN xpath_variable_set& xpath_variable_set::operator=(const xpath_variable_set& rhs) { if (this == &rhs) return *this; @@ -12657,30 +12732,30 @@ namespace pugi } #ifdef PUGIXML_HAS_MOVE - PUGI__FN xpath_variable_set::xpath_variable_set(xpath_variable_set&& rhs) PUGIXML_NOEXCEPT + PUGI_IMPL_FN xpath_variable_set::xpath_variable_set(xpath_variable_set&& rhs) PUGIXML_NOEXCEPT { for (size_t i = 0; i < sizeof(_data) / sizeof(_data[0]); ++i) { _data[i] = rhs._data[i]; - rhs._data[i] = 0; + rhs._data[i] = NULL; } } - PUGI__FN xpath_variable_set& xpath_variable_set::operator=(xpath_variable_set&& rhs) PUGIXML_NOEXCEPT + PUGI_IMPL_FN xpath_variable_set& xpath_variable_set::operator=(xpath_variable_set&& rhs) PUGIXML_NOEXCEPT { for (size_t i = 0; i < sizeof(_data) / sizeof(_data[0]); ++i) { _destroy(_data[i]); _data[i] = rhs._data[i]; - rhs._data[i] = 0; + rhs._data[i] = NULL; } return *this; } #endif - PUGI__FN void xpath_variable_set::_assign(const xpath_variable_set& rhs) + PUGI_IMPL_FN void xpath_variable_set::_assign(const xpath_variable_set& rhs) { xpath_variable_set temp; @@ -12691,7 +12766,7 @@ namespace pugi _swap(temp); } - PUGI__FN void xpath_variable_set::_swap(xpath_variable_set& rhs) + PUGI_IMPL_FN void xpath_variable_set::_swap(xpath_variable_set& rhs) { for (size_t i = 0; i < sizeof(_data) / sizeof(_data[0]); ++i) { @@ -12702,7 +12777,7 @@ namespace pugi } } - PUGI__FN xpath_variable* xpath_variable_set::_find(const char_t* name) const + PUGI_IMPL_FN xpath_variable* xpath_variable_set::_find(const char_t* name) const { const size_t hash_size = sizeof(_data) / sizeof(_data[0]); size_t hash = impl::hash_string(name) % hash_size; @@ -12712,12 +12787,12 @@ namespace pugi if (impl::strequal(var->name(), name)) return var; - return 0; + return NULL; } - PUGI__FN bool xpath_variable_set::_clone(xpath_variable* var, xpath_variable** out_result) + PUGI_IMPL_FN bool xpath_variable_set::_clone(xpath_variable* var, xpath_variable** out_result) { - xpath_variable* last = 0; + xpath_variable* last = NULL; while (var) { @@ -12742,7 +12817,7 @@ namespace pugi return true; } - PUGI__FN void xpath_variable_set::_destroy(xpath_variable* var) + PUGI_IMPL_FN void xpath_variable_set::_destroy(xpath_variable* var) { while (var) { @@ -12754,7 +12829,7 @@ namespace pugi } } - PUGI__FN xpath_variable* xpath_variable_set::add(const char_t* name, xpath_value_type type) + PUGI_IMPL_FN xpath_variable* xpath_variable_set::add(const char_t* name, xpath_value_type type) { const size_t hash_size = sizeof(_data) / sizeof(_data[0]); size_t hash = impl::hash_string(name) % hash_size; @@ -12762,7 +12837,7 @@ namespace pugi // look for existing variable for (xpath_variable* var = _data[hash]; var; var = var->_next) if (impl::strequal(var->name(), name)) - return var->type() == type ? var : 0; + return var->type() == type ? var : NULL; // add new variable xpath_variable* result = impl::new_xpath_variable(type, name); @@ -12777,41 +12852,41 @@ namespace pugi return result; } - PUGI__FN bool xpath_variable_set::set(const char_t* name, bool value) + PUGI_IMPL_FN bool xpath_variable_set::set(const char_t* name, bool value) { xpath_variable* var = add(name, xpath_type_boolean); return var ? var->set(value) : false; } - PUGI__FN bool xpath_variable_set::set(const char_t* name, double value) + PUGI_IMPL_FN bool xpath_variable_set::set(const char_t* name, double value) { xpath_variable* var = add(name, xpath_type_number); return var ? var->set(value) : false; } - PUGI__FN bool xpath_variable_set::set(const char_t* name, const char_t* value) + PUGI_IMPL_FN bool xpath_variable_set::set(const char_t* name, const char_t* value) { xpath_variable* var = add(name, xpath_type_string); return var ? var->set(value) : false; } - PUGI__FN bool xpath_variable_set::set(const char_t* name, const xpath_node_set& value) + PUGI_IMPL_FN bool xpath_variable_set::set(const char_t* name, const xpath_node_set& value) { xpath_variable* var = add(name, xpath_type_node_set); return var ? var->set(value) : false; } - PUGI__FN xpath_variable* xpath_variable_set::get(const char_t* name) + PUGI_IMPL_FN xpath_variable* xpath_variable_set::get(const char_t* name) { return _find(name); } - PUGI__FN const xpath_variable* xpath_variable_set::get(const char_t* name) const + PUGI_IMPL_FN const xpath_variable* xpath_variable_set::get(const char_t* name) const { return _find(name); } - PUGI__FN xpath_query::xpath_query(const char_t* query, xpath_variable_set* variables): _impl(0) + PUGI_IMPL_FN xpath_query::xpath_query(const char_t* query, xpath_variable_set* variables): _impl(NULL) { impl::xpath_query_impl* qimpl = impl::xpath_query_impl::create(); @@ -12835,7 +12910,7 @@ namespace pugi qimpl->root->optimize(&qimpl->alloc); _impl = impl.release(); - _result.error = 0; + _result.error = NULL; } else { @@ -12849,26 +12924,26 @@ namespace pugi } } - PUGI__FN xpath_query::xpath_query(): _impl(0) + PUGI_IMPL_FN xpath_query::xpath_query(): _impl(NULL) { } - PUGI__FN xpath_query::~xpath_query() + PUGI_IMPL_FN xpath_query::~xpath_query() { if (_impl) impl::xpath_query_impl::destroy(static_cast(_impl)); } #ifdef PUGIXML_HAS_MOVE - PUGI__FN xpath_query::xpath_query(xpath_query&& rhs) PUGIXML_NOEXCEPT + PUGI_IMPL_FN xpath_query::xpath_query(xpath_query&& rhs) PUGIXML_NOEXCEPT { _impl = rhs._impl; _result = rhs._result; - rhs._impl = 0; + rhs._impl = NULL; rhs._result = xpath_parse_result(); } - PUGI__FN xpath_query& xpath_query::operator=(xpath_query&& rhs) PUGIXML_NOEXCEPT + PUGI_IMPL_FN xpath_query& xpath_query::operator=(xpath_query&& rhs) PUGIXML_NOEXCEPT { if (this == &rhs) return *this; @@ -12877,21 +12952,21 @@ namespace pugi _impl = rhs._impl; _result = rhs._result; - rhs._impl = 0; + rhs._impl = NULL; rhs._result = xpath_parse_result(); return *this; } #endif - PUGI__FN xpath_value_type xpath_query::return_type() const + PUGI_IMPL_FN xpath_value_type xpath_query::return_type() const { if (!_impl) return xpath_type_none; return static_cast(_impl)->root->rettype(); } - PUGI__FN bool xpath_query::evaluate_boolean(const xpath_node& n) const + PUGI_IMPL_FN bool xpath_query::evaluate_boolean(const xpath_node& n) const { if (!_impl) return false; @@ -12912,7 +12987,7 @@ namespace pugi return r; } - PUGI__FN double xpath_query::evaluate_number(const xpath_node& n) const + PUGI_IMPL_FN double xpath_query::evaluate_number(const xpath_node& n) const { if (!_impl) return impl::gen_nan(); @@ -12934,7 +13009,7 @@ namespace pugi } #ifndef PUGIXML_NO_STL - PUGI__FN string_t xpath_query::evaluate_string(const xpath_node& n) const + PUGI_IMPL_FN string_t xpath_query::evaluate_string(const xpath_node& n) const { if (!_impl) return string_t(); @@ -12956,7 +13031,7 @@ namespace pugi } #endif - PUGI__FN size_t xpath_query::evaluate_string(char_t* buffer, size_t capacity, const xpath_node& n) const + PUGI_IMPL_FN size_t xpath_query::evaluate_string(char_t* buffer, size_t capacity, const xpath_node& n) const { impl::xpath_context c(n, 1, 1); impl::xpath_stack_data sd; @@ -12986,7 +13061,7 @@ namespace pugi return full_size; } - PUGI__FN xpath_node_set xpath_query::evaluate_node_set(const xpath_node& n) const + PUGI_IMPL_FN xpath_node_set xpath_query::evaluate_node_set(const xpath_node& n) const { impl::xpath_ast_node* root = impl::evaluate_node_set_prepare(static_cast(_impl)); if (!root) return xpath_node_set(); @@ -13008,7 +13083,7 @@ namespace pugi return xpath_node_set(r.begin(), r.end(), r.type()); } - PUGI__FN xpath_node xpath_query::evaluate_node(const xpath_node& n) const + PUGI_IMPL_FN xpath_node xpath_query::evaluate_node(const xpath_node& n) const { impl::xpath_ast_node* root = impl::evaluate_node_set_prepare(static_cast(_impl)); if (!root) return xpath_node(); @@ -13030,54 +13105,54 @@ namespace pugi return r.first(); } - PUGI__FN const xpath_parse_result& xpath_query::result() const + PUGI_IMPL_FN const xpath_parse_result& xpath_query::result() const { return _result; } - PUGI__FN static void unspecified_bool_xpath_query(xpath_query***) + PUGI_IMPL_FN static void unspecified_bool_xpath_query(xpath_query***) { } - PUGI__FN xpath_query::operator xpath_query::unspecified_bool_type() const + PUGI_IMPL_FN xpath_query::operator xpath_query::unspecified_bool_type() const { - return _impl ? unspecified_bool_xpath_query : 0; + return _impl ? unspecified_bool_xpath_query : NULL; } - PUGI__FN bool xpath_query::operator!() const + PUGI_IMPL_FN bool xpath_query::operator!() const { return !_impl; } - PUGI__FN xpath_node xml_node::select_node(const char_t* query, xpath_variable_set* variables) const + PUGI_IMPL_FN xpath_node xml_node::select_node(const char_t* query, xpath_variable_set* variables) const { xpath_query q(query, variables); return q.evaluate_node(*this); } - PUGI__FN xpath_node xml_node::select_node(const xpath_query& query) const + PUGI_IMPL_FN xpath_node xml_node::select_node(const xpath_query& query) const { return query.evaluate_node(*this); } - PUGI__FN xpath_node_set xml_node::select_nodes(const char_t* query, xpath_variable_set* variables) const + PUGI_IMPL_FN xpath_node_set xml_node::select_nodes(const char_t* query, xpath_variable_set* variables) const { xpath_query q(query, variables); return q.evaluate_node_set(*this); } - PUGI__FN xpath_node_set xml_node::select_nodes(const xpath_query& query) const + PUGI_IMPL_FN xpath_node_set xml_node::select_nodes(const xpath_query& query) const { return query.evaluate_node_set(*this); } - PUGI__FN xpath_node xml_node::select_single_node(const char_t* query, xpath_variable_set* variables) const + PUGI_IMPL_FN xpath_node xml_node::select_single_node(const char_t* query, xpath_variable_set* variables) const { xpath_query q(query, variables); return q.evaluate_node(*this); } - PUGI__FN xpath_node xml_node::select_single_node(const xpath_query& query) const + PUGI_IMPL_FN xpath_node xml_node::select_single_node(const xpath_query& query) const { return query.evaluate_node(*this); } @@ -13089,51 +13164,55 @@ namespace pugi # pragma option pop #endif +#if defined(_MSC_VER) && defined(__c2__) +# pragma clang diagnostic pop +#endif + +#if defined(__clang__) +# pragma clang diagnostic pop +#endif + // Intel C++ does not properly keep warning state for function templates, // so popping warning state at the end of translation unit leads to warnings in the middle. #if defined(_MSC_VER) && !defined(__INTEL_COMPILER) # pragma warning(pop) #endif -#if defined(_MSC_VER) && defined(__c2__) -# pragma clang diagnostic pop -#endif - // Undefine all local macros (makes sure we're not leaking macros in header-only mode) -#undef PUGI__NO_INLINE -#undef PUGI__UNLIKELY -#undef PUGI__STATIC_ASSERT -#undef PUGI__DMC_VOLATILE -#undef PUGI__UNSIGNED_OVERFLOW -#undef PUGI__MSVC_CRT_VERSION -#undef PUGI__SNPRINTF -#undef PUGI__NS_BEGIN -#undef PUGI__NS_END -#undef PUGI__FN -#undef PUGI__FN_NO_INLINE -#undef PUGI__GETHEADER_IMPL -#undef PUGI__GETPAGE_IMPL -#undef PUGI__GETPAGE -#undef PUGI__NODETYPE -#undef PUGI__IS_CHARTYPE_IMPL -#undef PUGI__IS_CHARTYPE -#undef PUGI__IS_CHARTYPEX -#undef PUGI__ENDSWITH -#undef PUGI__SKIPWS -#undef PUGI__OPTSET -#undef PUGI__PUSHNODE -#undef PUGI__POPNODE -#undef PUGI__SCANFOR -#undef PUGI__SCANWHILE -#undef PUGI__SCANWHILE_UNROLL -#undef PUGI__ENDSEG -#undef PUGI__THROW_ERROR -#undef PUGI__CHECK_ERROR +#undef PUGI_IMPL_NO_INLINE +#undef PUGI_IMPL_UNLIKELY +#undef PUGI_IMPL_STATIC_ASSERT +#undef PUGI_IMPL_DMC_VOLATILE +#undef PUGI_IMPL_UNSIGNED_OVERFLOW +#undef PUGI_IMPL_MSVC_CRT_VERSION +#undef PUGI_IMPL_SNPRINTF +#undef PUGI_IMPL_NS_BEGIN +#undef PUGI_IMPL_NS_END +#undef PUGI_IMPL_FN +#undef PUGI_IMPL_FN_NO_INLINE +#undef PUGI_IMPL_GETHEADER_IMPL +#undef PUGI_IMPL_GETPAGE_IMPL +#undef PUGI_IMPL_GETPAGE +#undef PUGI_IMPL_NODETYPE +#undef PUGI_IMPL_IS_CHARTYPE_IMPL +#undef PUGI_IMPL_IS_CHARTYPE +#undef PUGI_IMPL_IS_CHARTYPEX +#undef PUGI_IMPL_ENDSWITH +#undef PUGI_IMPL_SKIPWS +#undef PUGI_IMPL_OPTSET +#undef PUGI_IMPL_PUSHNODE +#undef PUGI_IMPL_POPNODE +#undef PUGI_IMPL_SCANFOR +#undef PUGI_IMPL_SCANWHILE +#undef PUGI_IMPL_SCANWHILE_UNROLL +#undef PUGI_IMPL_ENDSEG +#undef PUGI_IMPL_THROW_ERROR +#undef PUGI_IMPL_CHECK_ERROR #endif /** - * Copyright (c) 2006-2022 Arseny Kapoulkine + * Copyright (c) 2006-2024 Arseny Kapoulkine * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation diff --git a/src/external/pugixml/src/pugixml.hpp b/src/external/pugixml/src/pugixml.hpp index 050df154c..3b810099c 100644 --- a/src/external/pugixml/src/pugixml.hpp +++ b/src/external/pugixml/src/pugixml.hpp @@ -1,7 +1,7 @@ /** - * pugixml parser - version 1.13 + * pugixml parser - version 1.14 * -------------------------------------------------------- - * Copyright (C) 2006-2022, by Arseny Kapoulkine (arseny.kapoulkine@gmail.com) + * Copyright (C) 2006-2024, by Arseny Kapoulkine (arseny.kapoulkine@gmail.com) * Report bugs and download new versions at https://pugixml.org/ * * This library is distributed under the MIT License. See notice at the end @@ -14,7 +14,7 @@ // Define version macro; evaluates to major * 1000 + minor * 10 + patch so that it's safe to use in less-than comparisons // Note: pugixml used major * 100 + minor * 10 + patch format up until 1.9 (which had version identifier 190); starting from pugixml 1.10, the minor version number is two digits #ifndef PUGIXML_VERSION -# define PUGIXML_VERSION 1130 // 1.13 +# define PUGIXML_VERSION 1140 // 1.14 #endif // Include user configuration file (this can define various configuration macros) @@ -138,7 +138,7 @@ namespace pugi #ifndef PUGIXML_NO_STL // String type used for operations that work with STL string; depends on PUGIXML_WCHAR_MODE - typedef std::basic_string, std::allocator > string_t; + typedef std::basic_string string_t; #endif } @@ -212,6 +212,10 @@ namespace pugi // the document; this flag is only recommended for parsing documents with many PCDATA nodes in memory-constrained environments. // This flag is off by default. const unsigned int parse_embed_pcdata = 0x2000; + + // This flag determines whether determines whether the the two pcdata should be merged or not, if no intermediatory data are parsed in the document. + // This flag is off by default. + const unsigned int parse_merge_pcdata = 0x4000; // The default parsing mode. // Elements, PCDATA and CDATA sections are added to the DOM tree, character/reference entities are expanded, @@ -324,7 +328,7 @@ namespace pugi class PUGIXML_CLASS xml_writer { public: - virtual ~xml_writer() {} + virtual ~xml_writer(); // Write memory chunk into stream/file/whatever virtual void write(const void* data, size_t size) = 0; @@ -349,14 +353,14 @@ namespace pugi { public: // Construct writer from an output stream object - xml_writer_stream(std::basic_ostream >& stream); - xml_writer_stream(std::basic_ostream >& stream); + xml_writer_stream(std::basic_ostream& stream); + xml_writer_stream(std::basic_ostream& stream); virtual void write(const void* data, size_t size) PUGIXML_OVERRIDE; private: - std::basic_ostream >* narrow_stream; - std::basic_ostream >* wide_stream; + std::basic_ostream* narrow_stream; + std::basic_ostream* wide_stream; }; #endif @@ -418,8 +422,9 @@ namespace pugi // Set attribute name/value (returns false if attribute is empty or there is not enough memory) bool set_name(const char_t* rhs); - bool set_value(const char_t* rhs, size_t sz); + bool set_name(const char_t* rhs, size_t size); bool set_value(const char_t* rhs); + bool set_value(const char_t* rhs, size_t size); // Set attribute value with type conversion (numbers are converted to strings, boolean is converted to "true"/"false") bool set_value(int rhs); @@ -553,8 +558,9 @@ namespace pugi // Set node name/value (returns false if node is empty, there is not enough memory, or node can not have name/value) bool set_name(const char_t* rhs); - bool set_value(const char_t* rhs, size_t sz); + bool set_name(const char_t* rhs, size_t size); bool set_value(const char_t* rhs); + bool set_value(const char_t* rhs, size_t size); // Add attribute with specified name. Returns added attribute, or empty attribute on errors. xml_attribute append_attribute(const char_t* name); @@ -694,8 +700,8 @@ namespace pugi #ifndef PUGIXML_NO_STL // Print subtree to stream - void print(std::basic_ostream >& os, const char_t* indent = PUGIXML_TEXT("\t"), unsigned int flags = format_default, xml_encoding encoding = encoding_auto, unsigned int depth = 0) const; - void print(std::basic_ostream >& os, const char_t* indent = PUGIXML_TEXT("\t"), unsigned int flags = format_default, unsigned int depth = 0) const; + void print(std::basic_ostream& os, const char_t* indent = PUGIXML_TEXT("\t"), unsigned int flags = format_default, xml_encoding encoding = encoding_auto, unsigned int depth = 0) const; + void print(std::basic_ostream& os, const char_t* indent = PUGIXML_TEXT("\t"), unsigned int flags = format_default, unsigned int depth = 0) const; #endif // Child nodes iterators @@ -712,9 +718,12 @@ namespace pugi // Range-based for support xml_object_range children() const; - xml_object_range children(const char_t* name) const; xml_object_range attributes() const; + // Range-based for support for all children with the specified name + // Note: name pointer must have a longer lifetime than the returned object; be careful with passing temporaries! + xml_object_range children(const char_t* name) const; + // Get node offset in parsed file/string (in char_t units) for debugging purposes ptrdiff_t offset_debug() const; @@ -779,8 +788,8 @@ namespace pugi bool as_bool(bool def = false) const; // Set text (returns false if object is empty or there is not enough memory) - bool set(const char_t* rhs, size_t sz); bool set(const char_t* rhs); + bool set(const char_t* rhs, size_t size); // Set text with type conversion (numbers are converted to strings, boolean is converted to "true"/"false") bool set(int rhs); @@ -927,6 +936,7 @@ namespace pugi xml_named_node_iterator(); // Construct an iterator which points to the specified node + // Note: name pointer is stored in the iterator and must have a longer lifetime than iterator itself xml_named_node_iterator(const xml_node& node, const char_t* name); // Iterator operators @@ -1062,8 +1072,8 @@ namespace pugi #ifndef PUGIXML_NO_STL // Load document from stream. - xml_parse_result load(std::basic_istream >& stream, unsigned int options = parse_default, xml_encoding encoding = encoding_auto); - xml_parse_result load(std::basic_istream >& stream, unsigned int options = parse_default); + xml_parse_result load(std::basic_istream& stream, unsigned int options = parse_default, xml_encoding encoding = encoding_auto); + xml_parse_result load(std::basic_istream& stream, unsigned int options = parse_default); #endif // (deprecated: use load_string instead) Load document from zero-terminated string. No encoding conversions are applied. @@ -1092,8 +1102,8 @@ namespace pugi #ifndef PUGIXML_NO_STL // Save XML document to stream (semantics is slightly different from xml_node::print, see documentation for details). - void save(std::basic_ostream >& stream, const char_t* indent = PUGIXML_TEXT("\t"), unsigned int flags = format_default, xml_encoding encoding = encoding_auto) const; - void save(std::basic_ostream >& stream, const char_t* indent = PUGIXML_TEXT("\t"), unsigned int flags = format_default) const; + void save(std::basic_ostream& stream, const char_t* indent = PUGIXML_TEXT("\t"), unsigned int flags = format_default, xml_encoding encoding = encoding_auto) const; + void save(std::basic_ostream& stream, const char_t* indent = PUGIXML_TEXT("\t"), unsigned int flags = format_default) const; #endif // Save XML to file @@ -1429,12 +1439,12 @@ namespace pugi #ifndef PUGIXML_NO_STL // Convert wide string to UTF8 - std::basic_string, std::allocator > PUGIXML_FUNCTION as_utf8(const wchar_t* str); - std::basic_string, std::allocator > PUGIXML_FUNCTION as_utf8(const std::basic_string, std::allocator >& str); + std::basic_string PUGIXML_FUNCTION as_utf8(const wchar_t* str); + std::basic_string PUGIXML_FUNCTION as_utf8(const std::basic_string& str); // Convert UTF8 to wide string - std::basic_string, std::allocator > PUGIXML_FUNCTION as_wide(const char* str); - std::basic_string, std::allocator > PUGIXML_FUNCTION as_wide(const std::basic_string, std::allocator >& str); + std::basic_string PUGIXML_FUNCTION as_wide(const char* str); + std::basic_string PUGIXML_FUNCTION as_wide(const std::basic_string& str); #endif // Memory allocation function interface; returns pointer to allocated memory or NULL on failure @@ -1481,7 +1491,7 @@ namespace std #endif /** - * Copyright (c) 2006-2022 Arseny Kapoulkine + * Copyright (c) 2006-2024 Arseny Kapoulkine * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation From 4cafa3755d800cf744f36514eade87d9693cc5b9 Mon Sep 17 00:00:00 2001 From: redtide Date: Fri, 23 Feb 2024 04:55:38 +0100 Subject: [PATCH 61/65] dr_libs update --- external/st_audiofile/thirdparty/dr_libs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/external/st_audiofile/thirdparty/dr_libs b/external/st_audiofile/thirdparty/dr_libs index 4b3d07849..e4a7765e5 160000 --- a/external/st_audiofile/thirdparty/dr_libs +++ b/external/st_audiofile/thirdparty/dr_libs @@ -1 +1 @@ -Subproject commit 4b3d07849537ce0b71b22180c0b1335eacc6e9be +Subproject commit e4a7765e598e9e54dc0f520b7e4416359bee80cc From 4055582671d2d4071d5977fe2f8c02aac9f9114f Mon Sep 17 00:00:00 2001 From: KIRA Ryouta Date: Fri, 23 Feb 2024 21:59:53 +0900 Subject: [PATCH 62/65] Fixes an issue that caused stuck notes. Fixes an issue that caused a crash when using a loop. --- src/sfizz/ADSREnvelope.cpp | 2 +- src/sfizz/Voice.cpp | 2 +- tests/SynthT.cpp | 43 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 45 insertions(+), 2 deletions(-) diff --git a/src/sfizz/ADSREnvelope.cpp b/src/sfizz/ADSREnvelope.cpp index 479075603..fa952373c 100644 --- a/src/sfizz/ADSREnvelope.cpp +++ b/src/sfizz/ADSREnvelope.cpp @@ -101,7 +101,7 @@ void ADSREnvelope::getBlockInternal(absl::Span output) noexcept if (releaseDelay > 0) { // prevent computing the segment further than release point size = std::min(size, releaseDelay); - } else if (releaseDelay == 0 && delay < 0) { + } else if (releaseDelay == 0 && delay <= 0) { // release takes effect this frame currentState = State::Release; releaseDelay = -1; diff --git a/src/sfizz/Voice.cpp b/src/sfizz/Voice.cpp index 63db7cdbe..e5233aacd 100644 --- a/src/sfizz/Voice.cpp +++ b/src/sfizz/Voice.cpp @@ -1188,7 +1188,7 @@ void Voice::Impl::fillWithData(AudioSpan buffer) noexcept unsigned i = 0; while (i < numSamples) { int wrappedIndex = (*indices)[i] - loop.size * blockRestarts; - if (wrappedIndex > loop.end) { + while (wrappedIndex > loop.end) { wrappedIndex -= loop.size; blockRestarts += 1; loop_.restarts += 1; diff --git a/tests/SynthT.cpp b/tests/SynthT.cpp index 1b77c276f..311533ad7 100644 --- a/tests/SynthT.cpp +++ b/tests/SynthT.cpp @@ -2160,3 +2160,46 @@ TEST_CASE("[Synth] Reuse offed voices in the last case scenario for new notes") REQUIRE( notes == std::vector { i - 1, i } ); } } + +TEST_CASE("[Synth] Note on and off at the maximum delay") +{ + sfz::Synth synth; + synth.setSampleRate(12800); + synth.setSamplesPerBlock(128); + sfz::AudioBuffer buffer { 2, static_cast(synth.getSamplesPerBlock()) }; + synth.loadSfzString(fs::current_path() / "tests/TestFiles/noteonoff.sfz", R"( + loop_start=4 loop_end=124 ampeg_release=0.5 sample=looped_flute.wav + )"); + synth.setNumVoices(128); + synth.noteOn(127, 64, 64); + CHECK(synth.getNumActiveVoices() == 1); + synth.noteOff(127, 64, 0); + CHECK(synth.getNumActiveVoices() == 1); + // Render for a while + for (int i = 0; i < 100; ++i) + synth.renderBlock(buffer); + CHECK(synth.getNumActiveVoices() == 0); +} + +TEST_CASE("[Synth] Note on and off with delay") +{ + sfz::Synth synth; + synth.setSampleRate(12800); + synth.setSamplesPerBlock(128); + sfz::AudioBuffer buffer { 2, static_cast(synth.getSamplesPerBlock()) }; + synth.loadSfzString(fs::current_path() / "tests/TestFiles/noteonoff.sfz", R"( + loop_start=4 loop_end=124 ampeg_release=0.5 sample=looped_flute.wav + )"); + synth.setNumVoices(128); + + int i; + for (i = 0; i < 127; ++i) { + synth.noteOn(i, i, 64); + synth.noteOff(i+1, i, 0); + } + CHECK(synth.getNumActiveVoices() == 127); + // Render for a while + for (int i = 0; i < 100; ++i) + synth.renderBlock(buffer); + CHECK(synth.getNumActiveVoices() == 0); +} \ No newline at end of file From 342cd99dd9eec2cab8f750db23f32f77492177df Mon Sep 17 00:00:00 2001 From: KIRA Ryouta Date: Sat, 24 Feb 2024 00:09:51 +0900 Subject: [PATCH 63/65] Fix to pass a Polyphony test. --- src/sfizz/ADSREnvelope.cpp | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/sfizz/ADSREnvelope.cpp b/src/sfizz/ADSREnvelope.cpp index fa952373c..b6080fca6 100644 --- a/src/sfizz/ADSREnvelope.cpp +++ b/src/sfizz/ADSREnvelope.cpp @@ -102,9 +102,17 @@ void ADSREnvelope::getBlockInternal(absl::Span output) noexcept // prevent computing the segment further than release point size = std::min(size, releaseDelay); } else if (releaseDelay == 0 && delay <= 0) { + if (delay < 0) { + // release takes effect this frame + currentState = State::Release; + } else { + // release takes effect the next frame + size = 1; + } + releaseDelay = -1; + } else if (releaseDelay == -1 && currentState < State::Release && delay <= 0) { // release takes effect this frame currentState = State::Release; - releaseDelay = -1; } } From 806bf1dde49621d08c50a6a496b25c42b1afa8b8 Mon Sep 17 00:00:00 2001 From: redtide Date: Thu, 25 Apr 2024 20:40:07 +0200 Subject: [PATCH 64/65] Fix sfizz.pc.in The project name was changed during the repository split but the pkgconfig file template wasn't updated --- scripts/sfizz.pc.in | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/sfizz.pc.in b/scripts/sfizz.pc.in index 0d294c590..85e99abc9 100644 --- a/scripts/sfizz.pc.in +++ b/scripts/sfizz.pc.in @@ -3,10 +3,10 @@ exec_prefix=@CMAKE_INSTALL_PREFIX@ libdir=@CMAKE_INSTALL_FULL_LIBDIR@ includedir=@CMAKE_INSTALL_FULL_INCLUDEDIR@ -Name: @PROJECT_NAME@ +Name: sfizz Description: @PROJECT_DESCRIPTION@ Version: @PROJECT_VERSION@ Requires.private:@SFIZZ_PC_REQUIRES@ -Libs: -L${libdir} -l@PROJECT_NAME@ +Libs: -L${libdir} -lsfizz Cflags: -I${includedir} From a708acbe8e82805d60f084d2c3fce64abed1280d Mon Sep 17 00:00:00 2001 From: "taylor.fish" Date: Thu, 22 Feb 2024 17:55:20 -0800 Subject: [PATCH 65/65] Update atomic_queue to v1.6.1 Fixes #1023. --- .../include/atomic_queue/atomic_queue.h | 190 ++++++++++-------- .../atomic_queue/include/atomic_queue/defs.h | 37 +++- 2 files changed, 139 insertions(+), 88 deletions(-) diff --git a/external/atomic_queue/include/atomic_queue/atomic_queue.h b/external/atomic_queue/include/atomic_queue/atomic_queue.h index 21dc029bf..94d7a5681 100644 --- a/external/atomic_queue/include/atomic_queue/atomic_queue.h +++ b/external/atomic_queue/include/atomic_queue/atomic_queue.h @@ -29,12 +29,14 @@ namespace details { //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// template struct GetCacheLineIndexBits { static int constexpr value = 0; }; -template<> struct GetCacheLineIndexBits<64> { static int constexpr value = 6; }; -template<> struct GetCacheLineIndexBits<32> { static int constexpr value = 5; }; -template<> struct GetCacheLineIndexBits<16> { static int constexpr value = 4; }; -template<> struct GetCacheLineIndexBits< 8> { static int constexpr value = 3; }; -template<> struct GetCacheLineIndexBits< 4> { static int constexpr value = 2; }; -template<> struct GetCacheLineIndexBits< 2> { static int constexpr value = 1; }; +template<> struct GetCacheLineIndexBits<256> { static int constexpr value = 8; }; +template<> struct GetCacheLineIndexBits<128> { static int constexpr value = 7; }; +template<> struct GetCacheLineIndexBits< 64> { static int constexpr value = 6; }; +template<> struct GetCacheLineIndexBits< 32> { static int constexpr value = 5; }; +template<> struct GetCacheLineIndexBits< 16> { static int constexpr value = 4; }; +template<> struct GetCacheLineIndexBits< 8> { static int constexpr value = 3; }; +template<> struct GetCacheLineIndexBits< 4> { static int constexpr value = 2; }; +template<> struct GetCacheLineIndexBits< 2> { static int constexpr value = 1; }; template struct GetIndexShuffleBits { @@ -54,14 +56,11 @@ struct GetIndexShuffleBits { // minimizes contention. This is done by swapping the lowest order N bits (which are the index of // the element within the cache line) with the next N bits (which are the index of the cache line) // of the element index. -template -constexpr unsigned remap_index_with_mix(unsigned index, unsigned mix) { - return index ^ mix ^ (mix << BITS); -} - template constexpr unsigned remap_index(unsigned index) noexcept { - return remap_index_with_mix(index, (index ^ (index >> BITS)) & ((1u << BITS) - 1)); + unsigned constexpr mix_mask{(1u << BITS) - 1}; + unsigned const mix{(index ^ (index >> BITS)) & mix_mask}; + return index ^ mix ^ (mix << BITS); } template<> @@ -76,8 +75,8 @@ constexpr T& map(T* elements, unsigned index) noexcept { //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// Implement a "bit-twiddling hack" for finding the next power of 2 -// in either 32 bits or 64 bits in C++11 compatible constexpr functions +// Implement a "bit-twiddling hack" for finding the next power of 2 in either 32 bits or 64 bits +// in C++11 compatible constexpr functions. The library no longer maintains C++11 compatibility. // "Runtime" version for 32 bits // --a; @@ -89,22 +88,22 @@ constexpr T& map(T* elements, unsigned index) noexcept { // ++a; template -constexpr T decrement(T x) { +constexpr T decrement(T x) noexcept { return x - 1; } template -constexpr T increment(T x) { +constexpr T increment(T x) noexcept { return x + 1; } template -constexpr T or_equal(T x, unsigned u) { - return (x | x >> u); +constexpr T or_equal(T x, unsigned u) noexcept { + return x | x >> u; } template -constexpr T or_equal(T x, unsigned u, Args... rest) { +constexpr T or_equal(T x, unsigned u, Args... rest) noexcept { return or_equal(or_equal(x, u), rest...); } @@ -118,6 +117,16 @@ constexpr uint64_t round_up_to_power_of_2(uint64_t a) noexcept { //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +template +constexpr T nil() noexcept { +#if __cpp_lib_atomic_is_always_lock_free // Better compile-time error message requires C++17. + static_assert(std::atomic::is_always_lock_free, "Queue element type T is not atomic. Use AtomicQueue2/AtomicQueueB2 for such element types."); +#endif + return {}; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + } // namespace details //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -131,7 +140,7 @@ class AtomicQueueCommon { // The special member functions are not thread-safe. - AtomicQueueCommon() = default; + AtomicQueueCommon() noexcept = default; AtomicQueueCommon(AtomicQueueCommon const& b) noexcept : head_(b.head_.load(X)) @@ -156,9 +165,9 @@ class AtomicQueueCommon { static T do_pop_atomic(std::atomic& q_element) noexcept { if(Derived::spsc_) { for(;;) { - T element = q_element.load(X); + T element = q_element.load(A); if(ATOMIC_QUEUE_LIKELY(element != NIL)) { - q_element.store(NIL, R); + q_element.store(NIL, X); return element; } if(Derived::maximize_throughput_) @@ -167,7 +176,7 @@ class AtomicQueueCommon { } else { for(;;) { - T element = q_element.exchange(NIL, R); // (2) The store to wait for. + T element = q_element.exchange(NIL, A); // (2) The store to wait for. if(ATOMIC_QUEUE_LIKELY(element != NIL)) return element; // Do speculative loads while busy-waiting to avoid broadcasting RFO messages. @@ -188,7 +197,7 @@ class AtomicQueueCommon { q_element.store(element, R); } else { - for(T expected = NIL; ATOMIC_QUEUE_UNLIKELY(!q_element.compare_exchange_strong(expected, element, R, X)); expected = NIL) { + for(T expected = NIL; ATOMIC_QUEUE_UNLIKELY(!q_element.compare_exchange_weak(expected, element, R, X)); expected = NIL) { do spin_loop_pause(); // (1) Wait for store (2) to complete. while(Derived::maximize_throughput_ && q_element.load(X) != NIL); @@ -211,7 +220,7 @@ class AtomicQueueCommon { else { for(;;) { unsigned char expected = STORED; - if(ATOMIC_QUEUE_LIKELY(state.compare_exchange_strong(expected, LOADING, A, X))) { + if(ATOMIC_QUEUE_LIKELY(state.compare_exchange_weak(expected, LOADING, A, X))) { T element{std::move(q_element)}; state.store(EMPTY, R); return element; @@ -236,7 +245,7 @@ class AtomicQueueCommon { else { for(;;) { unsigned char expected = EMPTY; - if(ATOMIC_QUEUE_LIKELY(state.compare_exchange_strong(expected, STORING, A, X))) { + if(ATOMIC_QUEUE_LIKELY(state.compare_exchange_weak(expected, STORING, A, X))) { q_element = std::forward(element); state.store(STORED, R); return; @@ -262,7 +271,7 @@ class AtomicQueueCommon { do { if(static_cast(head - tail_.load(X)) >= static_cast(static_cast(*this).size_)) return false; - } while(ATOMIC_QUEUE_UNLIKELY(!head_.compare_exchange_strong(head, head + 1, A, X))); // This loop is not FIFO. + } while(ATOMIC_QUEUE_UNLIKELY(!head_.compare_exchange_weak(head, head + 1, X, X))); // This loop is not FIFO. } static_cast(*this).do_push(std::forward(element), head); @@ -281,7 +290,7 @@ class AtomicQueueCommon { do { if(static_cast(head_.load(X) - tail) <= 0) return false; - } while(ATOMIC_QUEUE_UNLIKELY(!tail_.compare_exchange_strong(tail, tail + 1, A, X))); // This loop is not FIFO. + } while(ATOMIC_QUEUE_UNLIKELY(!tail_.compare_exchange_weak(tail, tail + 1, X, X))); // This loop is not FIFO. } element = static_cast(*this).do_pop(tail); @@ -296,20 +305,20 @@ class AtomicQueueCommon { head_.store(head + 1, X); } else { - constexpr auto memory_order = Derived::total_order_ ? std::memory_order_seq_cst : std::memory_order_acquire; + constexpr auto memory_order = Derived::total_order_ ? std::memory_order_seq_cst : std::memory_order_relaxed; head = head_.fetch_add(1, memory_order); // FIFO and total order on Intel regardless, as of 2019. } static_cast(*this).do_push(std::forward(element), head); } - Derived pop() noexcept { + auto pop() noexcept { unsigned tail; if(Derived::spsc_) { tail = tail_.load(X); tail_.store(tail + 1, X); } else { - constexpr auto memory_order = Derived::total_order_ ? std::memory_order_seq_cst : std::memory_order_acquire; + constexpr auto memory_order = Derived::total_order_ ? std::memory_order_seq_cst : std::memory_order_relaxed; tail = tail_.fetch_add(1, memory_order); // FIFO and total order on Intel regardless, as of 2019. } return static_cast(*this).do_pop(tail); @@ -335,7 +344,7 @@ class AtomicQueueCommon { //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -template +template(), bool MINIMIZE_CONTENTION = true, bool MAXIMIZE_THROUGHPUT = true, bool TOTAL_ORDER = false, bool SPSC = false> class AtomicQueue : public AtomicQueueCommon> { using Base = AtomicQueueCommon>; friend Base; @@ -362,8 +371,8 @@ class AtomicQueue : public AtomicQueueCommon{NIL}.is_lock_free()); // This queue is for atomic elements only. AtomicQueue2 is for non-atomic ones. - if(T{} != NIL) + assert(std::atomic{NIL}.is_lock_free()); // Queue element type T is not atomic. Use AtomicQueue2/AtomicQueueB2 for such element types. + if(details::nil() != NIL) for(auto& element : elements_) element.store(NIL, X); } @@ -403,16 +412,17 @@ class AtomicQueue2 : public AtomicQueueCommon, T NIL = T{}, bool MAXIMIZE_THROUGHPUT = true, bool TOTAL_ORDER = false, bool SPSC = false> -class AtomicQueueB : public AtomicQueueCommon>, - private std::allocator_traits::template rebind_alloc> { +template, T NIL = details::nil(), bool MAXIMIZE_THROUGHPUT = true, bool TOTAL_ORDER = false, bool SPSC = false> +class AtomicQueueB : private std::allocator_traits::template rebind_alloc>, + public AtomicQueueCommon> { + using AllocatorElements = typename std::allocator_traits::template rebind_alloc>; using Base = AtomicQueueCommon>; friend Base; @@ -420,8 +430,6 @@ class AtomicQueueB : public AtomicQueueCommon::template rebind_alloc>; - static constexpr auto ELEMENTS_PER_CACHE_LINE = CACHE_LINE_SIZE / sizeof(std::atomic); static_assert(ELEMENTS_PER_CACHE_LINE, "Unexpected ELEMENTS_PER_CACHE_LINE."); @@ -445,25 +453,25 @@ class AtomicQueueB : public AtomicQueueCommon{NIL}.is_lock_free()); // This queue is for atomic elements only. AtomicQueueB2 is for non-atomic ones. + assert(std::atomic{NIL}.is_lock_free()); // Queue element type T is not atomic. Use AtomicQueue2/AtomicQueueB2 for such element types. for(auto p = elements_, q = elements_ + size_; p < q; ++p) p->store(NIL, X); } AtomicQueueB(AtomicQueueB&& b) noexcept - : Base(static_cast(b)) - , AllocatorElements(static_cast(b)) // TODO: This must be noexcept, static_assert that. - , size_(b.size_) - , elements_(b.elements_) { - b.size_ = 0; - b.elements_ = 0; - } + : AllocatorElements(static_cast(b)) // TODO: This must be noexcept, static_assert that. + , Base(static_cast(b)) + , size_(std::exchange(b.size_, 0)) + , elements_(std::exchange(b.elements_, nullptr)) + {} AtomicQueueB& operator=(AtomicQueueB&& b) noexcept { b.swap(*this); @@ -475,15 +483,19 @@ class AtomicQueueB : public AtomicQueueCommonBase::swap(b); swap(static_cast(*this), static_cast(b)); + Base::swap(b); swap(size_, b.size_); swap(elements_, b.elements_); } - friend void swap(AtomicQueueB& a, AtomicQueueB& b) { + friend void swap(AtomicQueueB& a, AtomicQueueB& b) noexcept { a.swap(b); } }; @@ -491,27 +503,25 @@ class AtomicQueueB : public AtomicQueueCommon, bool MAXIMIZE_THROUGHPUT = true, bool TOTAL_ORDER = false, bool SPSC = false> -class AtomicQueueB2 : public AtomicQueueCommon>, - private A, - private std::allocator_traits::template rebind_alloc> { +class AtomicQueueB2 : private std::allocator_traits::template rebind_alloc, + public AtomicQueueCommon> { + using StorageAllocator = typename std::allocator_traits::template rebind_alloc; using Base = AtomicQueueCommon>; using State = typename Base::State; + using AtomicState = std::atomic; friend Base; static constexpr bool total_order_ = TOTAL_ORDER; static constexpr bool spsc_ = SPSC; static constexpr bool maximize_throughput_ = MAXIMIZE_THROUGHPUT; - using AllocatorElements = A; - using AllocatorStates = typename std::allocator_traits::template rebind_alloc>; - // AtomicQueueCommon members are stored into by readers and writers. // Allocate these immutable members on another cache line which never gets invalidated by stores. alignas(CACHE_LINE_SIZE) unsigned size_; - std::atomic* states_; + AtomicState* states_; T* elements_; - static constexpr auto STATES_PER_CACHE_LINE = CACHE_LINE_SIZE / sizeof(State); + static constexpr auto STATES_PER_CACHE_LINE = CACHE_LINE_SIZE / sizeof(AtomicState); static_assert(STATES_PER_CACHE_LINE, "Unexpected STATES_PER_CACHE_LINE."); static constexpr auto SHUFFLE_BITS = details::GetCacheLineIndexBits::value; @@ -528,34 +538,43 @@ class AtomicQueueB2 : public AtomicQueueCommon(element), states_[index], elements_[index]); } + template + U* allocate_() { + U* p = reinterpret_cast(StorageAllocator::allocate(size_ * sizeof(U))); + assert(reinterpret_cast(p) % alignof(U) == 0); // Allocated storage must be suitably aligned for U. + return p; + } + + template + void deallocate_(U* p) noexcept { + StorageAllocator::deallocate(reinterpret_cast(p), size_ * sizeof(U)); // TODO: This must be noexcept, static_assert that. + } + public: using value_type = T; + using allocator_type = A; // The special member functions are not thread-safe. - AtomicQueueB2(unsigned size) - : size_(std::max(details::round_up_to_power_of_2(size), 1u << (SHUFFLE_BITS * 2))) - , states_(AllocatorStates::allocate(size_)) - , elements_(AllocatorElements::allocate(size_)) { + AtomicQueueB2(unsigned size, A const& allocator = A{}) + : StorageAllocator(allocator) + , size_(std::max(details::round_up_to_power_of_2(size), 1u << (SHUFFLE_BITS * 2))) + , states_(allocate_()) + , elements_(allocate_()) { for(auto p = states_, q = states_ + size_; p < q; ++p) p->store(Base::EMPTY, X); - - AllocatorElements& ae = *this; + A a = get_allocator(); for(auto p = elements_, q = elements_ + size_; p < q; ++p) - std::allocator_traits::construct(ae, p); + std::allocator_traits::construct(a, p); } AtomicQueueB2(AtomicQueueB2&& b) noexcept - : Base(static_cast(b)) - , AllocatorElements(static_cast(b)) // TODO: This must be noexcept, static_assert that. - , AllocatorStates(static_cast(b)) // TODO: This must be noexcept, static_assert that. - , size_(b.size_) - , states_(b.states_) - , elements_(b.elements_) { - b.size_ = 0; - b.states_ = 0; - b.elements_ = 0; - } + : StorageAllocator(static_cast(b)) // TODO: This must be noexcept, static_assert that. + , Base(static_cast(b)) + , size_(std::exchange(b.size_, 0)) + , states_(std::exchange(b.states_, nullptr)) + , elements_(std::exchange(b.elements_, nullptr)) + {} AtomicQueueB2& operator=(AtomicQueueB2&& b) noexcept { b.swap(*this); @@ -564,19 +583,22 @@ class AtomicQueueB2 : public AtomicQueueCommon::destroy(ae, p); - AllocatorElements::deallocate(elements_, size_); // TODO: This must be noexcept, static_assert that. - AllocatorStates::deallocate(states_, size_); // TODO: This must be noexcept, static_assert that. + std::allocator_traits::destroy(a, p); + deallocate_(elements_); + deallocate_(states_); } } + A get_allocator() const noexcept { + return *this; + } + void swap(AtomicQueueB2& b) noexcept { using std::swap; - this->Base::swap(b); - swap(static_cast(*this), static_cast(b)); - swap(static_cast(*this), static_cast(b)); + swap(static_cast(*this), static_cast(b)); + Base::swap(b); swap(size_, b.size_); swap(states_, b.states_); swap(elements_, b.elements_); diff --git a/external/atomic_queue/include/atomic_queue/defs.h b/external/atomic_queue/include/atomic_queue/defs.h index a279dcf8d..4601b1d46 100644 --- a/external/atomic_queue/include/atomic_queue/defs.h +++ b/external/atomic_queue/include/atomic_queue/defs.h @@ -6,8 +6,7 @@ #include -#if defined(__x86_64__) || defined(_M_X64) || \ - defined(__i386__) || defined(_M_IX86) +#if defined(__x86_64__) || defined(_M_X64) || defined(__i386__) || defined(_M_IX86) #include namespace atomic_queue { constexpr int CACHE_LINE_SIZE = 64; @@ -15,7 +14,7 @@ static inline void spin_loop_pause() noexcept { _mm_pause(); } } // namespace atomic_queue -#elif defined(__arm__) || defined(__aarch64__) +#elif defined(__arm__) || defined(__aarch64__) || defined(_M_ARM64) namespace atomic_queue { constexpr int CACHE_LINE_SIZE = 64; static inline void spin_loop_pause() noexcept { @@ -31,13 +30,42 @@ static inline void spin_loop_pause() noexcept { defined(__ARM_ARCH_8A__) || \ defined(__aarch64__)) asm volatile ("yield" ::: "memory"); +#elif defined(_M_ARM64) + __yield(); #else asm volatile ("nop" ::: "memory"); #endif } } // namespace atomic_queue +#elif defined(__ppc64__) || defined(__powerpc64__) +namespace atomic_queue { +constexpr int CACHE_LINE_SIZE = 128; // TODO: Review that this is the correct value. +static inline void spin_loop_pause() noexcept { + asm volatile("or 31,31,31 # very low priority"); // TODO: Review and benchmark that this is the right instruction. +} +} // namespace atomic_queue +#elif defined(__s390x__) +namespace atomic_queue { +constexpr int CACHE_LINE_SIZE = 256; // TODO: Review that this is the correct value. +static inline void spin_loop_pause() noexcept {} // TODO: Find the right instruction to use here, if any. +} // namespace atomic_queue +#elif defined(__riscv) +namespace atomic_queue { +constexpr int CACHE_LINE_SIZE = 64; +static inline void spin_loop_pause() noexcept { + asm volatile (".insn i 0x0F, 0, x0, x0, 0x010"); +} +} // namespace atomic_queue +#else +#ifdef _MSC_VER +#pragma message("Unknown CPU architecture. Using L1 cache line size of 64 bytes and no spinloop pause instruction.") #else -#error "Unknown CPU architecture." +#warning "Unknown CPU architecture. Using L1 cache line size of 64 bytes and no spinloop pause instruction." +#endif +namespace atomic_queue { +constexpr int CACHE_LINE_SIZE = 64; // TODO: Review that this is the correct value. +static inline void spin_loop_pause() noexcept {} +} // namespace atomic_queue #endif //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -60,6 +88,7 @@ auto constexpr A = std::memory_order_acquire; auto constexpr R = std::memory_order_release; auto constexpr X = std::memory_order_relaxed; auto constexpr C = std::memory_order_seq_cst; +auto constexpr AR = std::memory_order_acq_rel; ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////