Merge branch 'develop'

This commit is contained in:
Paul Fd 2021-10-23 23:26:41 +02:00
commit 1a3b5a9b2d
215 changed files with 9086 additions and 3317 deletions

View file

@ -42,9 +42,9 @@ for:
- ${APPVEYOR_BUILD_FOLDER}/scripts/appveyor/before_build.sh
build_script:
- cd build
- make -j2 sfizz_tests
- cmake --build . -j2 --target sfizz_tests
- tests/sfizz_tests
- make -j2
- cmake --build . -j2
after_build:
- chmod +x ${APPVEYOR_BUILD_FOLDER}/scripts/appveyor/after_build.sh
- ${APPVEYOR_BUILD_FOLDER}/scripts/appveyor/after_build.sh

View file

@ -44,6 +44,7 @@ jobs:
run: |
sudo apt-get update && \
sudo apt-get install \
ninja-build \
libjack-jackd2-dev \
libsndfile1-dev \
libcairo2-dev \
@ -64,10 +65,12 @@ jobs:
shell: bash
working-directory: ${{runner.workspace}}/build
run: |
cmake "$GITHUB_WORKSPACE" -DCMAKE_BUILD_TYPE="$BUILD_TYPE" \
cmake "$GITHUB_WORKSPACE" -G Ninja \
-DCMAKE_BUILD_TYPE="$BUILD_TYPE" \
-DSFIZZ_JACK=ON \
-DSFIZZ_VST=ON \
-DSFIZZ_LV2_UI=ON \
-DSFIZZ_PUREDATA=ON \
-DSFIZZ_TESTS=ON \
-DSFIZZ_SHARED=OFF \
-DSFIZZ_STATIC_DEPENDENCIES=OFF \
@ -137,15 +140,24 @@ jobs:
name: MOD devices tarball
path: ${{runner.workspace}}/build/${{env.install_name}}.tar.gz
build_for_win32:
build_for_windows:
runs-on: windows-2019
strategy:
matrix:
include:
- platform: x86
pkg_platform: Win32
release_arch: Win32
bits: 32
- platform: x64
pkg_platform: Win64
release_arch: x64
bits: 64
steps:
- name: Set install name
run: |
echo platform=x86 >> "${Env:GITHUB_ENV}"
echo release_arch=Win32 >> "${Env:GITHUB_ENV}"
echo "install_ref=$(${Env:GITHUB_REF}.split('/')[-1])" >> "${Env:GITHUB_ENV}"
echo "install_name=sfizz-$(${Env:GITHUB_REF}.split('/')[-1])-win32" >> "${Env:GITHUB_ENV}"
echo "install_name=sfizz-$(${Env:GITHUB_REF}.split('/')[-1])-win${{matrix.bits}}" >> "${Env:GITHUB_ENV}"
- uses: actions/checkout@v2
with:
submodules: recursive
@ -155,42 +167,7 @@ jobs:
- name: Configure CMake
working-directory: ${{runner.workspace}}/build
run: |
cmake "${Env:GITHUB_WORKSPACE}" -G"Visual Studio 16 2019" -A"${Env:release_arch}" -DCMAKE_BUILD_TYPE="${Env:BUILD_TYPE}" -DCMAKE_CXX_STANDARD=17 -DSFIZZ_TESTS=ON -DSFIZZ_VST=ON -DSFIZZ_LV2=ON
- name: Build tests
working-directory: ${{runner.workspace}}/build
run: cmake --build . --config "${Env:BUILD_TYPE}" -j 2 --target sfizz_tests
- name: Test
run: ${{runner.workspace}}/build/tests/Release/sfizz_tests
- name: Build all
working-directory: ${{runner.workspace}}/build
run: cmake --build . --config "${Env:BUILD_TYPE}" -j 2
- name: Create installer
working-directory: ${{runner.workspace}}/build
run: iscc /O"." /F"${Env:install_name}" /dARCH="${Env:platform}" innosetup.iss
- uses: actions/upload-artifact@v2
with:
name: Win32 installer
path: ${{runner.workspace}}/build/${{env.install_name}}.exe
build_for_win64:
runs-on: windows-2019
steps:
- name: Set install name
run: |
echo platform=x64 >> "${Env:GITHUB_ENV}"
echo release_arch=x64 >> "${Env:GITHUB_ENV}"
echo "install_ref=$(${Env:GITHUB_REF}.split('/')[-1])" >> "${Env:GITHUB_ENV}"
echo "install_name=sfizz-$(${Env:GITHUB_REF}.split('/')[-1])-win64" >> "${Env:GITHUB_ENV}"
- uses: actions/checkout@v2
with:
submodules: recursive
- name: Create Build Environment
working-directory: ${{runner.workspace}}
run: cmake -E make_directory build
- name: Configure CMake
working-directory: ${{runner.workspace}}/build
run: |
cmake "${Env:GITHUB_WORKSPACE}" -G"Visual Studio 16 2019" -A"${Env:release_arch}" -DCMAKE_BUILD_TYPE="${Env:BUILD_TYPE}" -DCMAKE_CXX_STANDARD=17 -DSFIZZ_TESTS=ON -DSFIZZ_VST=ON -DSFIZZ_LV2=ON
cmake "${Env:GITHUB_WORKSPACE}" -G"Visual Studio 16 2019" -A"${{matrix.release_arch}}" -DCMAKE_BUILD_TYPE="${Env:BUILD_TYPE}" -DCMAKE_CXX_STANDARD=17 -DSFIZZ_TESTS=ON -DSFIZZ_VST=ON -DSFIZZ_LV2=ON -DSFIZZ_PUREDATA=ON
- name: Build tests
working-directory: ${{runner.workspace}}/build
run: cmake --build . --config "${Env:BUILD_TYPE}" -j 2 --target sfizz_tests
@ -200,31 +177,42 @@ jobs:
working-directory: ${{runner.workspace}}/build
run: cmake --build . --config "${Env:BUILD_TYPE}" -j 2
- name: Install pluginval
if: ${{ matrix.platform == 'x64' }}
run: |
Invoke-WebRequest https://github.com/Tracktion/pluginval/releases/download/latest_release/pluginval_Windows.zip -OutFile pluginval.zip
Expand-Archive pluginval.zip -DestinationPath pluginval
echo "$(Get-Location)\pluginval" | Out-File -FilePath ${Env:GITHUB_PATH} -Encoding utf8 -Append
pluginval\pluginval --version
- name: Validate VST3
if: ${{ matrix.platform == 'x64' }}
working-directory: ${{runner.workspace}}/build
run: pluginval --validate-in-process --validate sfizz.vst3
- name: Create installer
working-directory: ${{runner.workspace}}/build
run: iscc /O"." /F"${Env:install_name}" /dARCH="${Env:platform}" innosetup.iss
run: iscc /O"." /F"${Env:install_name}" /dARCH="${{matrix.platform}}" innosetup.iss
- uses: actions/upload-artifact@v2
with:
name: Win64 installer
name: ${{matrix.pkg_platform}} installer
path: ${{runner.workspace}}/build/${{env.install_name}}.exe
build_for_mingw32:
build_for_mingw:
runs-on: ubuntu-18.04
strategy:
matrix:
include:
- platform: i686
pkg_platform: Win32
bits: 32
- platform: x86_64
pkg_platform: Win64
bits: 64
container:
image: archlinux
steps:
- name: Set install name
run: |
echo "install_ref=${GITHUB_REF##*/}" >> "$GITHUB_ENV"
echo "install_name=sfizz-${GITHUB_REF##*/}-mingw32" >> "$GITHUB_ENV"
echo "install_name=sfizz-${GITHUB_REF##*/}-mingw${{matrix.bits}}" >> "$GITHUB_ENV"
- name: Configure pacman repositories
shell: bash
run: |
@ -239,7 +227,7 @@ jobs:
shell: bash
run: |
pacman -Sqyu --noconfirm
pacman -Sq --needed --noconfirm base-devel git wget mingw-w64-cmake mingw-w64-gcc mingw-w64-pkg-config mingw-w64-libsndfile
pacman -Sq --needed --noconfirm base-devel git wget ninja mingw-w64-cmake mingw-w64-gcc mingw-w64-pkg-config mingw-w64-libsndfile
- uses: actions/checkout@v2
with:
submodules: recursive
@ -247,14 +235,7 @@ jobs:
shell: bash
run: |
cp -vf "$GITHUB_WORKSPACE"/scripts/mingw_dwrite_3.h \
/usr/i686-w64-mingw32/include/dwrite_3.h
- name: Fix VST sources
shell: bash
# need to convert some includes to lower case (as of VST 3.7.1)
run: |
find "$GITHUB_WORKSPACE"/plugins/vst/external/VST_SDK -type d -name source -exec \
find {} -type f -name '*.[hc]' -o -name '*.[hc]pp' -print0 \; | \
xargs -0 sed -i 's/<Windows.h>/<windows.h>/'
/usr/${{matrix.platform}}-w64-mingw32/include/dwrite_3.h
- name: Create Build Environment
shell: bash
working-directory: ${{runner.workspace}}
@ -263,95 +244,27 @@ jobs:
shell: bash
working-directory: ${{runner.workspace}}/build
run: |
i686-w64-mingw32-cmake "$GITHUB_WORKSPACE" \
${{matrix.platform}}-w64-mingw32-cmake "$GITHUB_WORKSPACE" -G Ninja \
-DCMAKE_BUILD_TYPE="$BUILD_TYPE" \
-DENABLE_LTO=OFF \
-DSFIZZ_JACK=OFF \
-DSFIZZ_VST=ON \
-DSFIZZ_PUREDATA=ON \
-DSFIZZ_STATIC_DEPENDENCIES=ON \
-DCMAKE_CXX_STANDARD=17
- name: Build
shell: bash
working-directory: ${{runner.workspace}}/build
run: i686-w64-mingw32-cmake --build . --config "$BUILD_TYPE" -j 2
run: ${{matrix.platform}}-w64-mingw32-cmake --build . --config "$BUILD_TYPE" -j 2
- name: Install
working-directory: ${{runner.workspace}}/build
shell: bash
run: |
DESTDIR="$(pwd)/$install_name" i686-w64-mingw32-cmake --build . --config "$BUILD_TYPE" --target install
DESTDIR="$(pwd)/$install_name" ${{matrix.platform}}-w64-mingw32-cmake --build . --config "$BUILD_TYPE" --target install
tar czvf "$install_name".tar.gz "$install_name"
- uses: actions/upload-artifact@v2
with:
name: Win32 MinGW tarball
path: ${{runner.workspace}}/build/${{env.install_name}}.tar.gz
build_for_mingw64:
runs-on: ubuntu-18.04
container:
image: archlinux
steps:
- name: Set install name
run: |
echo "install_ref=${GITHUB_REF##*/}" >> "$GITHUB_ENV"
echo "install_name=sfizz-${GITHUB_REF##*/}-mingw64" >> "$GITHUB_ENV"
- name: Configure pacman repositories
shell: bash
run: |
cat >>/etc/pacman.conf <<EOF
[multilib]
Include = /etc/pacman.d/mirrorlist
[mingw-w64]
SigLevel = Optional TrustAll
Server = https://github.com/jpcima/arch-mingw-w64/releases/download/repo.\$arch/
EOF
- name: Set up dependencies
shell: bash
run: |
pacman -Sqyu --noconfirm
pacman -Sq --needed --noconfirm base-devel git wget mingw-w64-cmake mingw-w64-gcc mingw-w64-pkg-config mingw-w64-libsndfile
- uses: actions/checkout@v2
with:
submodules: recursive
- name: Fix MinGW headers
shell: bash
run: |
cp -vf "$GITHUB_WORKSPACE"/scripts/mingw_dwrite_3.h \
/usr/x86_64-w64-mingw32/include/dwrite_3.h
- name: Fix VST sources
shell: bash
# need to convert some includes to lower case (as of VST 3.7.1)
run: |
find "$GITHUB_WORKSPACE"/plugins/vst/external/VST_SDK -type d -name source -exec \
find {} -type f -name '*.[hc]' -o -name '*.[hc]pp' -print0 \; | \
xargs -0 sed -i 's/<Windows.h>/<windows.h>/'
- name: Create Build Environment
shell: bash
working-directory: ${{runner.workspace}}
run: cmake -E make_directory build
- name: Configure CMake
shell: bash
working-directory: ${{runner.workspace}}/build
run: |
x86_64-w64-mingw32-cmake "$GITHUB_WORKSPACE" \
-DCMAKE_BUILD_TYPE="$BUILD_TYPE" \
-DENABLE_LTO=OFF \
-DSFIZZ_JACK=OFF \
-DSFIZZ_VST=ON \
-DSFIZZ_STATIC_DEPENDENCIES=ON \
-DCMAKE_CXX_STANDARD=17
- name: Build
shell: bash
working-directory: ${{runner.workspace}}/build
run: x86_64-w64-mingw32-cmake --build . --config "$BUILD_TYPE" -j 2
- name: Install
working-directory: ${{runner.workspace}}/build
shell: bash
run: |
DESTDIR="$(pwd)/$install_name" x86_64-w64-mingw32-cmake --build . --config "$BUILD_TYPE" --target install
tar czvf "$install_name".tar.gz "$install_name"
- uses: actions/upload-artifact@v2
with:
name: Win64 MinGW tarball
name: ${{matrix.pkg_platform}} MinGW tarball
path: ${{runner.workspace}}/build/${{env.install_name}}.tar.gz
build_with_makefile:
@ -417,16 +330,16 @@ jobs:
needs:
- build_for_linux
- build_for_mod
- build_for_mingw32
- build_for_mingw64
- build_for_mingw
- build_for_windows
- archive_source_code
steps:
- name: Set install name
run: |
echo "install_ref=${GITHUB_REF##*/}" >> "$GITHUB_ENV"
- uses: actions/download-artifact@v2
with:
name: Linux tarball
# - uses: actions/download-artifact@v2
# with:
# name: Linux tarball
- uses: actions/download-artifact@v2
with:
name: MOD devices tarball
@ -436,6 +349,12 @@ jobs:
- uses: actions/download-artifact@v2
with:
name: Win64 MinGW tarball
- uses: actions/download-artifact@v2
with:
name: Win32 installer
- uses: actions/download-artifact@v2
with:
name: Win64 installer
- uses: actions/download-artifact@v2
with:
name: Source code tarball

2
.gitignore vendored
View file

@ -15,6 +15,8 @@ compile_commands.json
*.autosave
/Doxyfile
.DS_Store
.cache/
.cmake/
clients/sfizz_jack
clients/sfzprint

View file

@ -28,6 +28,7 @@ option_ex (SFIZZ_LV2_UI "Enable LV2 plug-in user interface" ON)
option_ex (SFIZZ_VST "Enable VST plug-in build" ON)
option_ex (SFIZZ_AU "Enable AU plug-in build" APPLE)
option_ex (SFIZZ_VST2 "Enable VST2 plug-in build (unsupported)" OFF)
option_ex (SFIZZ_PUREDATA "Enable Puredata plug-in build" OFF)
option_ex (SFIZZ_BENCHMARKS "Enable benchmarks build" OFF)
option_ex (SFIZZ_TESTS "Enable tests build" OFF)
option_ex (SFIZZ_DEMOS "Enable feature demos build" OFF)
@ -37,8 +38,12 @@ option_ex (SFIZZ_USE_SNDFILE "Enable use of the sndfile library" OFF)
option_ex (SFIZZ_USE_VCPKG "Assume that sfizz is build using vcpkg" OFF)
option_ex (SFIZZ_USE_SYSTEM_ABSEIL "Use Abseil libraries preinstalled on system" OFF)
option_ex (SFIZZ_USE_SYSTEM_SIMDE "Use SIMDe libraries preinstalled on system" OFF)
option_ex (SFIZZ_USE_SYSTEM_KISS_FFT "Use KISS FFT libraries preinstalled on system" OFF)
option_ex (SFIZZ_USE_SYSTEM_PUGIXML "Use pugixml libraries preinstalled on system" OFF)
option_ex (SFIZZ_USE_SYSTEM_CXXOPTS "Use CXXOPTS libraries preinstalled on system" OFF)
option_ex (SFIZZ_STATIC_DEPENDENCIES "Link dependencies statically" OFF)
option_ex (SFIZZ_RELEASE_ASSERTS "Forced assertions in release builds" OFF)
option_ex (SFIZZ_PROFILE_BUILD "Profile the build time" OFF)
# The fixed number of controller parameters
set(SFIZZ_NUM_CCS 512)

View file

@ -1,10 +1,6 @@
BSD 2-Clause License
The code is copyrighted by their respective authors, as indicated by the
source control mechanism.
Please refer to AUTHORS.md for the list of contributors.
Copyright (c) 2021, sfizz contributors (detailed in AUTHORS.md)
All rights reserved.
Redistribution and use in source and binary forms, with or without

View file

@ -4,6 +4,10 @@
[![AppVeyor Build Status]](https://ci.appveyor.com/project/sfztools/sfizz)
[![Discord Badge Image]](https://discord.gg/3ArE9Mw)
[![SFZv1 Status Image]](https://sfz.tools/sfizz/development/status/opcodes/?v=1)
[![SFZv2 Status Image]](https://sfz.tools/sfizz/development/status/opcodes/?v=2)
[![ARIA Status Image]](https://sfz.tools/sfizz/development/status/opcodes/?v=aria)
[![Cakewalk Status Image]](https://sfz.tools/sfizz/development/status/opcodes/?v=cakewalk)
SFZ parser and synth c++ library, providing AU / LV2 / VST3 plugins
and JACK standalone client, please check [our website] for more details.
@ -36,9 +40,14 @@ We invite you to check out the [GOVERNANCE](GOVERNANCE.md) file to see how the o
## Dependencies and licenses
The sfizz library has the option to be compiled against either the `dr_libs`
audio libraries, which is now the default option, or `libsndfile`.
- [dr_libs] is licensed under the MIT No Attribution license
- [libsndfile] is licensed under the GNU Lesser General Public License v2.1
The sfizz library makes primary use of:
- [libsndfile], licensed under the GNU Lesser General Public License v2.1
- [Abseil], licensed under the Apache License 2.0
- [atomic_queue] by Maxim Egorushkin, licensed under the MIT license
- [filesystem] by Steffen Schümann, licensed under the BSD 3-Clause license
@ -71,6 +80,7 @@ The sfizz library also uses in some subprojects:
[pugixml]: https://pugixml.org/
[cephes]: https://www.netlib.org/cephes/
[cpuid]: https://github.com/steinwurf/cpuid
[dr_libs]: https://github.com/mackron/dr_libs
[faust-libraries]: https://github.com/grame-cncm/faustlibraries
[hiir]: http://ldesoras.free.fr/prod.html#src_hiir
[KISS FFT]: http://kissfft.sourceforge.net/
@ -92,3 +102,8 @@ The sfizz library also uses in some subprojects:
[AppVeyor Build Status]: https://img.shields.io/appveyor/ci/sfztools/sfizz.svg?label=Windows&style=popout&logo=appveyor
[Travis Build Status]: https://img.shields.io/travis/com/sfztools/sfizz.svg?label=Linux&style=popout&logo=travis
[Discord Badge Image]: https://img.shields.io/discord/587748534321807416?label=discord&logo=discord
[SFZv1 Status Image]: https://sfz.tools/assets/img/sfizz/badge_sfz1.svg
[SFZv2 Status Image]: https://sfz.tools/assets/img/sfizz/badge_sfz2.svg
[ARIA Status Image]: https://sfz.tools/assets/img/sfizz/badge_aria.svg
[Cakewalk Status Image]: https://sfz.tools/assets/img/sfizz/badge_cakewalk.svg

View file

@ -1,14 +1,22 @@
string(TIMESTAMP MAN_TODAY "%Y-%m-%d")
if(SFIZZ_JACK)
add_executable(sfizz_jack MidiHelpers.h jack_client.cpp)
target_link_libraries(sfizz_jack PRIVATE sfizz::sfizz sfizz::jack sfizz::spin_mutex absl::flags_parse)
target_link_libraries(sfizz_jack PRIVATE sfizz::import sfizz::sfizz sfizz::jack sfizz::spin_mutex absl::flags_parse)
sfizz_enable_lto_if_needed(sfizz_jack)
configure_file(sfizz_jack.man.in sfizz_jack.man @ONLY)
install(TARGETS sfizz_jack DESTINATION ${CMAKE_INSTALL_BINDIR}
COMPONENT "jack" OPTIONAL)
install(FILES ${PROJECT_BINARY_DIR}/clients/sfizz_jack.man DESTINATION ${CMAKE_INSTALL_MANDIR}/man1
RENAME sfizz_jack COMPONENT "jack" OPTIONAL)
endif()
if(SFIZZ_RENDER)
add_executable(sfizz_render MidiHelpers.h sfizz_render.cpp)
target_link_libraries(sfizz_render PRIVATE sfizz::internal sfizz::fmidi sfizz::cxxopts st_audiofile_formats)
sfizz_enable_lto_if_needed(sfizz_render)
configure_file(sfizz_render.man.in sfizz_render.man @ONLY)
install(TARGETS sfizz_render DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT "render" OPTIONAL)
install(FILES ${PROJECT_BINARY_DIR}/clients/sfizz_render.man DESTINATION ${CMAKE_INSTALL_MANDIR}/man1
RENAME sfizz_render COMPONENT "render" OPTIONAL)
endif()

View file

@ -22,6 +22,7 @@
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include "sfizz.hpp"
#include "sfizz/import/sfizz_import.h"
#include "MidiHelpers.h"
#include <absl/flags/parse.h>
#include <absl/flags/flag.h>
@ -41,6 +42,9 @@
#include <thread>
#include <mutex>
#include <algorithm>
#include <vector>
sfz::Sfizz synth;
static jack_port_t* midiInputPort;
static jack_port_t* outputPort1;
@ -148,30 +152,148 @@ static void done(int sig)
// exit(0);
}
bool loadInstrument(const char* fpath)
{
const char* importFormat = nullptr;
if (!sfizz_load_or_import_file(synth.handle(), fpath, &importFormat)) {
std::cout << "Could not load the instrument file: " << fpath << '\n';
return false;
}
std::cout << "Instrument loaded: " << fpath << '\n';
std::cout << "===========================" << '\n';
std::cout << "Total:" << '\n';
std::cout << "\tMasters: " << synth.getNumMasters() << '\n';
std::cout << "\tGroups: " << synth.getNumGroups() << '\n';
std::cout << "\tRegions: " << synth.getNumRegions() << '\n';
std::cout << "\tCurves: " << synth.getNumCurves() << '\n';
std::cout << "\tPreloadedSamples: " << synth.getNumPreloadedSamples() << '\n';
#if 0 // not currently in public API
std::cout << "===========================" << '\n';
std::cout << "Included files:" << '\n';
for (auto& file : synth.getParser().getIncludedFiles())
std::cout << '\t' << file << '\n';
std::cout << "===========================" << '\n';
std::cout << "Defines:" << '\n';
for (auto& define : synth.getParser().getDefines())
std::cout << '\t' << define.first << '=' << define.second << '\n';
#endif
std::cout << "===========================" << '\n';
std::cout << "Unknown opcodes:";
for (auto& opcode : synth.getUnknownOpcodes())
std::cout << opcode << ',';
std::cout << '\n';
if (importFormat) {
std::cout << "===========================" << '\n';
std::cout << "Import format: " << importFormat << '\n';
}
// std::cout << std::flush;
return true;
}
std::vector<std::string> stringTokenize(const std::string& str)
{
std::vector<std::string> tokens;
std::string part = "";
for (size_t i = 0; i < str.length(); i++) {
char c = str[i];
if (c == ' ' && part != "") {
tokens.push_back(part);
part = "";
} else if (c == '\"') {
i++;
while (str[i] != '\"') {
part += str[i];
i++;
}
tokens.push_back(part);
part = "";
} else {
part += c;
}
}
if (part != "") {
tokens.push_back(part);
}
return tokens;
}
void cliThreadProc()
{
while (!shouldClose) {
std::cout << "\n> ";
std::string command;
std::getline(std::cin, command);
std::size_t pos = command.find(" ");
std::string kw = command.substr(0, pos);
std::string args = command.substr(pos + 1);
std::vector<std::string> tokens = stringTokenize(args);
if (kw == "load_instrument") {
try {
std::lock_guard<SpinMutex> lock { processMutex };
loadInstrument(tokens[0].c_str());
} catch (...) {
std::cout << "ERROR: Can't load instrument!\n";
}
} else if (kw == "set_oversampling") {
try {
std::lock_guard<SpinMutex> lock { processMutex };
synth.setOversamplingFactor(stoi(args));
} catch (...) {
std::cout << "ERROR: Can't set oversampling!\n";
}
} else if (kw == "set_preload_size") {
try {
std::lock_guard<SpinMutex> lock { processMutex };
synth.setPreloadSize(stoi(args));
} catch (...) {
std::cout << "ERROR: Can't set preload size!\n";
}
} else if (kw == "set_voices") {
try {
std::lock_guard<SpinMutex> lock { processMutex };
synth.setNumVoices(stoi(args));
} catch (...) {
std::cout << "ERROR: Can't set num of voices!\n";
}
} else if (kw == "quit") {
shouldClose = true;
} else if (kw.size() > 0) {
std::cout << "ERROR: Unknown command '" << kw << "'!\n";
}
}
}
ABSL_FLAG(std::string, client_name, "sfizz", "Jack client name");
ABSL_FLAG(std::string, oversampling, "1x", "Internal oversampling factor (value values are x1, x2, x4, x8)");
ABSL_FLAG(uint32_t, preload_size, 8192, "Preloaded value");
ABSL_FLAG(uint32_t, preload_size, 8192, "Preloaded size");
ABSL_FLAG(uint32_t, num_voices, 32, "Num of voices");
ABSL_FLAG(bool, jack_autoconnect, false, "Autoconnect audio output");
ABSL_FLAG(bool, state, false, "Output the synth state in the jack loop");
int main(int argc, char** argv)
{
// std::ios::sync_with_stdio(false);
auto arguments = absl::ParseCommandLine(argc, argv);
if (arguments.size() < 2) {
std::cout << "You need to specify an SFZ file to load." << '\n';
return -1;
}
auto filesToParse = absl::MakeConstSpan(arguments).subspan(1);
const std::string clientName = absl::GetFlag(FLAGS_client_name);
const std::string oversampling = absl::GetFlag(FLAGS_oversampling);
const uint32_t preload_size = absl::GetFlag(FLAGS_preload_size);
const uint32_t num_voices = absl::GetFlag(FLAGS_num_voices);
const bool jack_autoconnect = absl::GetFlag(FLAGS_jack_autoconnect);
const bool verboseState = absl::GetFlag(FLAGS_state);
std::cout << "Flags" << '\n';
std::cout << "- Client name: " << clientName << '\n';
std::cout << "- Oversampling: " << oversampling << '\n';
std::cout << "- Preloaded Size: " << preload_size << '\n';
std::cout << "- Preloaded size: " << preload_size << '\n';
std::cout << "- Num of voices: " << num_voices << '\n';
std::cout << "- Audio Autoconnect: " << jack_autoconnect << '\n';
std::cout << "- Verbose State: " << verboseState << '\n';
const auto factor = [&]() {
if (oversampling == "x1") return 1;
if (oversampling == "x2") return 2;
@ -185,33 +307,9 @@ int main(int argc, char** argv)
std::cout << " " << file << ',';
std::cout << '\n';
sfz::Sfizz synth;
synth.setOversamplingFactor(factor);
synth.setPreloadSize(preload_size);
synth.loadSfzFile(filesToParse[0]);
std::cout << "==========" << '\n';
std::cout << "Total:" << '\n';
std::cout << "\tMasters: " << synth.getNumMasters() << '\n';
std::cout << "\tGroups: " << synth.getNumGroups() << '\n';
std::cout << "\tRegions: " << synth.getNumRegions() << '\n';
std::cout << "\tCurves: " << synth.getNumCurves() << '\n';
std::cout << "\tPreloadedSamples: " << synth.getNumPreloadedSamples() << '\n';
#if 0 // not currently in public API
std::cout << "==========" << '\n';
std::cout << "Included files:" << '\n';
for (auto& file : synth.getParser().getIncludedFiles())
std::cout << '\t' << file << '\n';
std::cout << "==========" << '\n';
std::cout << "Defines:" << '\n';
for (auto& define : synth.getParser().getDefines())
std::cout << '\t' << define.first << '=' << define.second << '\n';
#endif
std::cout << "==========" << '\n';
std::cout << "Unknown opcodes:";
for (auto& opcode : synth.getUnknownOpcodes())
std::cout << opcode << ',';
std::cout << '\n';
// std::cout << std::flush;
synth.setNumVoices(num_voices);
jack_status_t status;
client = jack_client_open(clientName.c_str(), JackNullOption, &status);
@ -254,32 +352,40 @@ int main(int argc, char** argv)
return 1;
}
auto systemPorts = jack_get_ports(client, nullptr, nullptr, JackPortIsPhysical | JackPortIsInput);
if (systemPorts == nullptr) {
std::cerr << "No physical output ports found" << '\n';
return 1;
if (jack_autoconnect) {
auto systemPorts = jack_get_ports(client, nullptr, nullptr, JackPortIsPhysical | JackPortIsInput);
if (systemPorts == nullptr) {
std::cerr << "No physical output ports found" << '\n';
return 1;
}
if (jack_connect(client, jack_port_name(outputPort1), systemPorts[0])) {
std::cerr << "Cannot connect to physical output ports (0)" << '\n';
}
if (jack_connect(client, jack_port_name(outputPort2), systemPorts[1])) {
std::cerr << "Cannot connect to physical output ports (1)" << '\n';
}
jack_free(systemPorts);
}
if (jack_connect(client, jack_port_name(outputPort1), systemPorts[0])) {
std::cerr << "Cannot connect to physical output ports (0)" << '\n';
if (!filesToParse.empty() && filesToParse[0]) {
loadInstrument(filesToParse[0]);
}
if (jack_connect(client, jack_port_name(outputPort2), systemPorts[1])) {
std::cerr << "Cannot connect to physical output ports (1)" << '\n';
}
jack_free(systemPorts);
std::thread cli_thread(cliThreadProc);
signal(SIGHUP, done);
signal(SIGINT, done);
signal(SIGTERM, done);
signal(SIGQUIT, done);
while (!shouldClose){
while (!shouldClose) {
if (verboseState) {
std::cout << "Active voices: " << synth.getNumActiveVoices() << '\n';
#ifndef NDEBUG
std::cout << "Allocated buffers: " << synth.getAllocatedBuffers() << '\n';
std::cout << "Total size: " << synth.getAllocatedBytes() << '\n';
std::cout << "Allocated buffers: " << synth.getAllocatedBuffers() << '\n';
std::cout << "Total size: " << synth.getAllocatedBytes() << '\n';
#endif
}
std::this_thread::sleep_for(std::chrono::seconds(1));

37
clients/sfizz_jack.man.in Normal file
View file

@ -0,0 +1,37 @@
.TH sfizz_jack 1 "@MAN_TODAY@" "@CMAKE_PROJECT_VERSION@" "sfizz_jack man page"
.SH NAME
sfizz_jack \- Launch a sfizz instance as a JACK client
.SH SYNOPSIS
sfizz_jack [FILE] [OPTIONS...]
.SH DESCRIPTION
sfizz_jack wraps the sfizz SFZ library in a JACK client that can be controlled by a text interface.
.SH OPTIONS
.IP FILE
An optional SFZ file name to load at firsts
.IP "--client_name"
Name for the JACK client
.IP "--num_voices NUMBER"
Change the maximum polyphony
.IP "--preload_size NUMBER"
Bytes to preload in cache for samples
.IP "--state"
Output the state in the JACK loop
.IP "--jack_autoconnect"
Autoconnect the JACK outputs
.SH TEXT INTERFACE
It is possible it interact with the JACK client through the standard input.
The possible commands are
.IP "load_instrument FILE"
Load an instrument
.IP "set_preload_size NUMBER"
Set the number of bytes to preload in cache for samples
.IP "set_voices NUMBER"
Change the maximum polyphony
.IP quit
Quit
.SH SEE ALSO
sfizz_render(1)
.SH BUGS
See the main repository at @SFIZZ_REPOSITORY@.
.SH AUTHOR
Contributors can be seen on the main repository at @SFIZZ_REPOSITORY@.

View file

@ -66,6 +66,7 @@ int main(int argc, char** argv)
bool help { false };
bool useEOT { false };
int quality { 2 };
int polyphony { 64 };
options.add_options()
("sfz", "SFZ file", cxxopts::value<std::string>())
@ -74,6 +75,7 @@ int main(int argc, char** argv)
("b,blocksize", "Block size for the sfizz callbacks", cxxopts::value(blockSize))
("s,samplerate", "Output sample rate", cxxopts::value(sampleRate))
("q,quality", "Resampling quality", cxxopts::value(quality))
("p,polyphony", "Polyphony max", cxxopts::value(polyphony))
("v,verbose", "Verbose output", cxxopts::value(verbose))
("log", "Produce logs", cxxopts::value<std::string>())
("use-eot", "End the rendering at the last End of Track Midi message", cxxopts::value(useEOT))
@ -114,11 +116,13 @@ int main(int argc, char** argv)
LOG_INFO("Output file: " << outputPath.string());
LOG_INFO("Block size: " << blockSize);
LOG_INFO("Sample rate: " << sampleRate);
LOG_INFO("Polyphony Max: " << polyphony);
sfz::Synth synth;
synth.setSamplesPerBlock(blockSize);
synth.setSampleRate(sampleRate);
synth.setSampleQuality(sfz::Synth::ProcessMode::ProcessFreewheeling, quality);
synth.setNumVoices(polyphony);
synth.enableFreeWheeling();
if (params.count("log") > 0)

View file

@ -0,0 +1,30 @@
.TH sfizz_render 1 "@MAN_TODAY@" "@CMAKE_PROJECT_VERSION@" "sfizz_render man page"
.SH NAME
sfizz_render \- Render a MIDI file as a WAV file using an SFZ instrument description.
.SH SYNOPSIS
sfizz_render --sfz FILE --wav FILE --midi FILE [OPTIONS...]
.SH DESCRIPTION
sfizz_render wraps the sfizz SFZ library and can be used to render midi file as sound files using an SFZ description file and its associated samples.
.SH OPTIONS
.IP "-b, --blocksize NUMBER"
Block size for the sfizz callbacks
.IP "-s, --samplerate NUMBER"
Output sample rate
.IP "-q, --quality NUMBER"
Resampling quality, like the SFZ sample_quality opcode. A value of 1 will use a linear interpolation of source samples, while higher value will use increasingly better algorithms.
.IP "-p, --polyphony NUMBER"
Maximum polyphony
.IP "-v, --verbose"
Verbose output
.IP "--log PREFIX"
Produce logs
.IP "--use-eot"
End the rendering at the last End of Track Midi message
.IP "-h, --help"
Show help
.SH SEE ALSO
sfizz_jack(1)
.SH BUGS
See the main repository at @SFIZZ_REPOSITORY@.
.SH AUTHOR
Contributors can be seen on the main repository at @SFIZZ_REPOSITORY@.

View file

@ -8,7 +8,7 @@ set(LV2PLUGIN_VERSION_MICRO 0)
set(LV2PLUGIN_NAME "sfizz")
set(LV2PLUGIN_COMMENT "SFZ sampler")
set(LV2PLUGIN_URI "http://sfztools.github.io/sfizz")
set(LV2PLUGIN_REPOSITORY "https://github.com/sfztools/sfizz")
set(LV2PLUGIN_REPOSITORY SFIZZ_REPOSITORY)
set(LV2PLUGIN_AUTHOR "SFZTools")
set(LV2PLUGIN_EMAIL "paul@ferrand.cc")
if(SFIZZ_USE_VCPKG)
@ -62,7 +62,9 @@ function(sfizz_lv2_generate_controllers_ttl FILE)
sfizz:cc${_i}
a lv2:Parameter ;
rdfs:label \"Controller ${_i}\" ;
rdfs:range atom:Float")
rdfs:range atom:Float ;
lv2:minimum 0.0 ;
lv2:maximum 1.0")
if(_i LESS 128 AND NOT SFIZZ_LV2_PSA)
math(EXPR _digit1 "${_i}>>4")

View file

@ -0,0 +1,70 @@
find_path(PD_INCLUDE_BASEDIR "m_pd.h" PATH_SUFFIXES "pd")
set(PD_IMP_DEF "${PROJECT_SOURCE_DIR}/plugins/puredata/external/pd/bin/pd.def")
if(PD_INCLUDE_BASEDIR)
message(STATUS "Puredata headers: ${PUREDATA_INCLUDE_DIR}")
else()
message(STATUS "Puredata headers not found, using our own")
set(PD_INCLUDE_BASEDIR "${PROJECT_SOURCE_DIR}/plugins/puredata/external/pd/include")
endif()
if(WIN32)
set(PUREDATA_SUFFIX ".dll")
elseif(APPLE)
set(PUREDATA_SUFFIX ".pd_darwin")
else()
set(PUREDATA_SUFFIX ".pd_linux")
endif()
if(APPLE)
set(PDPLUGIN_INSTALL_DIR "$ENV{HOME}/Library/Pd" CACHE STRING
"Install destination for Puredata bundle [default: $ENV{HOME}/Library/Pd]")
elseif(MSVC)
set(PDPLUGIN_INSTALL_DIR "${CMAKE_INSTALL_PREFIX}/Pd/extra" CACHE STRING
"Install destination for Puredata bundle [default: ${CMAKE_INSTALL_PREFIX}/Pd/extra]")
else()
set(PDPLUGIN_INSTALL_DIR "${CMAKE_INSTALL_PREFIX}/lib/pd/extra" CACHE STRING
"Install destination for Puredata bundle [default: ${CMAKE_INSTALL_PREFIX}/lib/pd/extra]")
endif()
if(WIN32)
add_library(pdex-implib STATIC IMPORTED)
if(MSVC)
add_custom_command(
OUTPUT "pd.lib"
COMMAND "lib" "/out:pd.lib" "/def:${PD_IMP_DEF}" "/machine:${MSVC_C_ARCHITECTURE_ID}"
DEPENDS "${PD_IMP_DEF}")
add_custom_target(pdex-implib-generated
DEPENDS "pd.lib")
set_target_properties(pdex-implib PROPERTIES
IMPORTED_LOCATION "${CMAKE_CURRENT_BINARY_DIR}/pd.lib")
else()
find_program(PD_DLLTOOL_PROGRAM "dlltool")
if(NOT PD_DLLTOOL_PROGRAM)
message(FATAL_ERROR "Cannot find dlltool")
endif()
add_custom_command(
OUTPUT "libpd.dll.a"
COMMAND "${PD_DLLTOOL_PROGRAM}" "-l" "libpd.dll.a" "-d" "${PD_IMP_DEF}"
DEPENDS "${PD_IMP_DEF}")
add_custom_target(pdex-implib-generated
DEPENDS "libpd.dll.a")
set_target_properties(pdex-implib PROPERTIES
IMPORTED_LOCATION "${CMAKE_CURRENT_BINARY_DIR}/libpd.dll.a")
endif()
add_dependencies(pdex-implib pdex-implib-generated)
endif()
function(add_pd_external TARGET)
add_library("${TARGET}" MODULE ${ARGN})
target_include_directories("${TARGET}" PRIVATE "${PD_INCLUDE_BASEDIR}")
set_target_properties("${TARGET}" PROPERTIES
PREFIX ""
SUFFIX "${PUREDATA_SUFFIX}")
if(APPLE)
set_property(TARGET "${TARGET}" APPEND_STRING
PROPERTY LINK_FLAGS " -Wl,-undefined,suppress,-flat_namespace")
elseif(WIN32)
target_link_libraries("${TARGET}" PRIVATE pdex-implib)
endif()
endfunction()

View file

@ -1,5 +1,7 @@
include(CMakeDependentOption)
include(CheckCCompilerFlag)
include(CheckCXXCompilerFlag)
include(CheckCXXSourceCompiles)
include(GNUWarnings)
set(CMAKE_CXX_STANDARD 11 CACHE STRING "C++ standard to be used")
@ -17,9 +19,21 @@ set(CMAKE_VISIBILITY_INLINES_HIDDEN ON)
# Set C++ compatibility level
if(CMAKE_CXX_COMPILER_ID MATCHES "MSVC" AND CMAKE_CXX_STANDARD LESS 17)
set(CMAKE_CXX_STANDARD 17)
elseif((SFIZZ_LV2_UI OR SFIZZ_VST OR SFIZZ_AU OR SFIZZ_VST2) AND CMAKE_CXX_STANDARD LESS 14)
# if the UI is part of the build, make it 14
set(CMAKE_CXX_STANDARD 14)
elseif((SFIZZ_LV2_UI OR SFIZZ_VST OR SFIZZ_AU OR SFIZZ_VST2) AND CMAKE_CXX_STANDARD LESS 17)
# if the UI is part of the build, make it 17
set(CMAKE_CXX_STANDARD 17)
endif()
# Set build profiling options
if(SFIZZ_PROFILE_BUILD)
check_c_compiler_flag("-ftime-trace" SFIZZ_HAVE_CFLAG_FTIME_TRACE)
check_cxx_compiler_flag("-ftime-trace" SFIZZ_HAVE_CXXFLAG_FTIME_TRACE)
if(SFIZZ_HAVE_CFLAG_FTIME_TRACE)
add_compile_options("$<$<COMPILE_LANGUAGE:C>:-ftime-trace>")
endif()
if(SFIZZ_HAVE_CXXFLAG_FTIME_TRACE)
add_compile_options("$<$<COMPILE_LANGUAGE:CXX>:-ftime-trace>")
endif()
endif()
# Process sources as UTF-8
@ -32,11 +46,39 @@ if(WIN32)
add_compile_definitions(_WIN32_WINNT=0x601)
endif()
# Define the math constants everywhere
if(WIN32)
add_compile_definitions(_USE_MATH_DEFINES)
endif()
# Set macOS compatibility level
if(APPLE)
set(CMAKE_OSX_DEPLOYMENT_TARGET "10.9")
endif()
# If using C++17, check if aligned-new has runtime support on the platform;
# on macOS, this depends on the deployment target.
if(CMAKE_CXX_STANDARD LESS 17)
# not necessary on older C++, it will call ordinary new and delete
set(SFIZZ_IMPLEMENT_CXX17_ALIGNED_NEW_SUPPORT FALSE)
else()
check_cxx_source_compiles("
struct Test { alignas(1024) int z; };
int main() { new Test; return 0; }
" SFIZZ_HAVE_CXX17_ALIGNED_NEW)
# if support is absent, sfizz will provide a substitute implementation
if(SFIZZ_HAVE_CXX17_ALIGNED_NEW)
set(SFIZZ_IMPLEMENT_CXX17_ALIGNED_NEW_SUPPORT FALSE)
else()
# on macOS, this mandatory flag tells that allocation functions are user-provided
check_cxx_compiler_flag("-faligned-allocation" SFIZZ_HAVE_CXXFLAG_FALIGNED_ALLOCATION)
if(SFIZZ_HAVE_CXXFLAG_FALIGNED_ALLOCATION)
add_compile_options("$<$<COMPILE_LANGUAGE:CXX>:-faligned-allocation>")
endif()
set(SFIZZ_IMPLEMENT_CXX17_ALIGNED_NEW_SUPPORT TRUE)
endif()
endif()
# Do not define macros `min` and `max`
if(WIN32)
add_compile_definitions(NOMINMAX)
@ -87,6 +129,9 @@ if(USE_LIBCPP)
add_link_options(-lc++abi) # New command on CMake master, not in 3.12 release
endif()
set(SFIZZ_REPOSITORY https://github.com/sfztools/sfizz)
include(GNUInstallDirs)
# Don't show build information when building a different project
function(show_build_info_if_needed)
if(CMAKE_PROJECT_NAME STREQUAL "sfizz")

View file

@ -1,3 +1,5 @@
include(CheckCXXSourceCompiles)
# Find system threads
find_package(Threads REQUIRED)
@ -65,9 +67,19 @@ add_library(sfizz::jsl ALIAS sfizz_jsl)
target_include_directories(sfizz_jsl INTERFACE "external/jsl/include")
# The cxxopts library
add_library(sfizz_cxxopts INTERFACE)
if(SFIZZ_USE_SYSTEM_CXXOPTS)
find_path(CXXOPTS_INCLUDE_DIR "cxxopts.hpp")
if(NOT CXXOPTS_INCLUDE_DIR)
message(FATAL_ERROR "Cannot find cxxopts")
endif()
add_library(sfizz_cxxopts INTERFACE)
target_include_directories(sfizz_cxxopts INTERFACE "${CXXOPTS_INCLUDE_DIR}")
else()
add_library(sfizz_cxxopts INTERFACE)
add_library(sfizz::cxxopts ALIAS sfizz_cxxopts)
target_include_directories(sfizz_cxxopts INTERFACE "external/cxxopts")
endif()
add_library(sfizz::cxxopts ALIAS sfizz_cxxopts)
target_include_directories(sfizz_cxxopts INTERFACE "external/cxxopts")
# The sndfile library
if(SFIZZ_USE_SNDFILE OR SFIZZ_DEMOS OR SFIZZ_DEVTOOLS OR SFIZZ_BENCHMARKS)
@ -105,10 +117,25 @@ add_subdirectory("external/st_audiofile" EXCLUDE_FROM_ALL)
add_library(sfizz_simde INTERFACE)
add_library(sfizz::simde ALIAS sfizz_simde)
if(SFIZZ_USE_SYSTEM_SIMDE)
find_package(PkgConfig REQUIRED)
pkg_check_modules(SIMDE "simde" REQUIRED)
target_include_directories(sfizz_simde INTERFACE "${SIMDE_INCLUDE_DIRS}")
if(NOT SIMDE_VERSION OR SIMDE_VERSION VERSION_LESS_EQUAL "0.7.2")
find_path(SIMDE_INCLUDE_DIR "simde/simde-features.h")
if(NOT SIMDE_INCLUDE_DIR)
message(FATAL_ERROR "Cannot find simde")
endif()
target_include_directories(sfizz_simde INTERFACE "${SIMDE_INCLUDE_DIR}")
function(sfizz_ensure_simde_version result major minor micro)
set(CMAKE_REQUIRED_INCLUDES "${SIMDE_INCLUDE_DIR}")
check_cxx_source_compiles(
"#include <simde/simde-common.h>
#if SIMDE_VERSION < HEDLEY_VERSION_ENCODE(${major}, ${minor}, ${micro})
# error Version check failed
#endif
int main() { return 0; }"
"${result}")
endfunction()
sfizz_ensure_simde_version(SFIZZ_SIMDE_AT_LEAST_0_7_3 0 7 3)
if(NOT SFIZZ_SIMDE_AT_LEAST_0_7_3)
message(WARNING "The version of SIMDe on this system has known issues. \
It is recommended to either update if a newer version is available, or use the \
version bundled with this package. Refer to following issues: \
@ -122,9 +149,18 @@ if(TARGET sfizz::openmp)
endif()
# The pugixml library
add_library(sfizz_pugixml STATIC "src/external/pugixml/src/pugixml.cpp")
if(SFIZZ_USE_SYSTEM_PUGIXML)
find_package(PkgConfig REQUIRED)
pkg_check_modules(PUGIXML "pugixml" REQUIRED)
add_library(sfizz_pugixml INTERFACE)
target_include_directories(sfizz_pugixml INTERFACE ${PUGIXML_INCLUDE_DIRS})
target_link_libraries(sfizz_pugixml INTERFACE ${PUGIXML_LIBRARIES})
link_directories(${PUGIXML_LIBRARY_DIRS})
else()
add_library(sfizz_pugixml STATIC "src/external/pugixml/src/pugixml.cpp")
target_include_directories(sfizz_pugixml PUBLIC "src/external/pugixml/src")
endif()
add_library(sfizz::pugixml ALIAS sfizz_pugixml)
target_include_directories(sfizz_pugixml PUBLIC "src/external/pugixml/src")
# The spline library
add_library(sfizz_spline STATIC "src/external/spline/spline/spline.cpp")
@ -148,13 +184,31 @@ add_library(sfizz::hiir_polyphase_iir2designer ALIAS sfizz_hiir_polyphase_iir2de
target_link_libraries(sfizz_hiir_polyphase_iir2designer PUBLIC sfizz::hiir)
# The kissfft library
add_library(sfizz_kissfft STATIC
"src/external/kiss_fft/kiss_fft.c"
"src/external/kiss_fft/tools/kiss_fftr.c")
if (SFIZZ_USE_SYSTEM_KISS_FFT)
find_path(KISSFFT_INCLUDE_DIR "kiss_fft.h" PATH_SUFFIXES "kissfft")
find_path(KISSFFTR_INCLUDE_DIR "kiss_fftr.h" PATH_SUFFIXES "kissfft")
find_library(KISSFFT_FFTR_LIBRARY "kiss_fftr_float" KISSFFTR_INCLUDE_DIR)
find_library(KISSFFT_FFT_LIBRARY "kiss_fft_float" KISSFFT_INCLUDE_DIR)
add_library(sfizz_kissfft INTERFACE)
if(NOT KISSFFT_FFT_LIBRARY)
message(FATAL_ERROR "Cannot find kiss fft")
endif()
if(NOT KISSFFT_FFTR_LIBRARY)
message(FATAL_ERROR "Cannot find kiss fftr")
endif()
target_include_directories(sfizz_kissfft INTERFACE "${KISSFFTR_INCLUDE_DIR}")
target_include_directories(sfizz_kissfft INTERFACE "${KISSFFT_INCLUDE_DIR}")
target_link_libraries(sfizz_kissfft INTERFACE "${KISSFFT_FFTR_LIBRARY}")
target_link_libraries(sfizz_kissfft INTERFACE "${KISSFFT_FFT_LIBRARY}")
else()
add_library(sfizz_kissfft STATIC
"src/external/kiss_fft/kiss_fft.c"
"src/external/kiss_fft/tools/kiss_fftr.c")
target_include_directories(sfizz_kissfft
PUBLIC "src/external/kiss_fft"
PUBLIC "src/external/kiss_fft/tools")
endif()
add_library(sfizz::kissfft ALIAS sfizz_kissfft)
target_include_directories(sfizz_kissfft
PUBLIC "src/external/kiss_fft"
PUBLIC "src/external/kiss_fft/tools")
# The cephes library
add_library(sfizz_cephes STATIC
@ -183,9 +237,27 @@ add_library(sfizz::atomic_queue ALIAS sfizz_atomic_queue)
target_include_directories(sfizz_atomic_queue INTERFACE "external/atomic_queue/include")
# The ghc::filesystem library
add_library(sfizz_filesystem INTERFACE)
if(FALSE)
# header-only
add_library(sfizz_filesystem INTERFACE)
target_include_directories(sfizz_filesystem INTERFACE "external/filesystem/include")
else()
# static library
file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/fs_std_impl.cpp" "#include <ghc/fs_std_impl.hpp>")
add_library(sfizz_filesystem_impl STATIC "${CMAKE_CURRENT_BINARY_DIR}/fs_std_impl.cpp")
target_include_directories(sfizz_filesystem_impl PUBLIC "external/filesystem/include")
# Add the needed linker option for GCC 8
if (CMAKE_CXX_COMPILER_ID MATCHES "GNU"
AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER 8.0
AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS 9.0)
target_link_libraries(sfizz_filesystem_impl PUBLIC stdc++fs)
endif()
#
add_library(sfizz_filesystem INTERFACE)
target_compile_definitions(sfizz_filesystem INTERFACE "GHC_FILESYSTEM_FWD")
target_link_libraries(sfizz_filesystem INTERFACE sfizz_filesystem_impl)
endif()
add_library(sfizz::filesystem ALIAS sfizz_filesystem)
target_include_directories(sfizz_filesystem INTERFACE "external/filesystem/include")
# The atomic library
add_library(sfizz_atomic INTERFACE)

View file

@ -107,6 +107,8 @@ SFIZZ_SOURCES = \
src/sfizz/PowerFollower.cpp \
src/sfizz/Region.cpp \
src/sfizz/RegionSet.cpp \
src/sfizz/RegionStateful.cpp \
src/sfizz/Resources.cpp \
src/sfizz/RTSemaphore.cpp \
src/sfizz/ScopedFTZ.cpp \
src/sfizz/sfizz.cpp \

View file

@ -1,5 +1,6 @@
#include "ui_DemoParser.h"
#include "parser/Parser.h"
#include "parser/ParserListener.h"
#include <QApplication>
#include <QTimer>
#include <QFileInfo>

View file

@ -17,6 +17,7 @@
#include "sfizz/Curve.h"
#include "sfizz/parser/Parser.h"
#include "sfizz/parser/ParserListener.h"
#include "absl/strings/numbers.h"
#include "absl/types/span.h"
#include <iostream>

View file

@ -13,6 +13,7 @@
*/
#include "parser/Parser.h"
#include "parser/ParserListener.h"
#include <pugixml.hpp>
#include <cxxopts.hpp>
#include <absl/strings/string_view.h>

2
external/filesystem vendored

@ -1 +1 @@
Subproject commit 2a8b380f8d4e77b389c42a194ab9c70d8e3a0f1e
Subproject commit 7bc5c17305fe70518ca72162e244af1d12455a91

View file

@ -0,0 +1,39 @@
// -*- C++ -*-
// SPDX-License-Identifier: BSL-1.0
//
// Copyright Jean Pierre Cimalando 2018-2020.
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE or copy at
// http://www.boost.org/LICENSE_1_0.txt)
//
#pragma once
#include "../../memory"
namespace jsl {
template <class T, std::size_t Al>
void aligned_ptr_delete<T, Al>::operator()(T *p) const noexcept
{
aligned_allocator<T, Al> allocator;
allocator.destroy(p);
allocator.deallocate(p, 0);
}
template <class T, std::size_t Al, class... Args>
aligned_unique_ptr<typename make_aligned_traits<T>::single_element, Al>
make_aligned(Args &&... args)
{
struct allocation_delete {
void operator()(T *p) const noexcept
{
aligned_allocator<T, Al> allocator;
allocator.destroy(p);
}
};
aligned_allocator<T, Al> allocator;
std::unique_ptr<T, allocation_delete> allocation(allocator.allocate(1, nullptr));
allocator.construct(allocation.get(), std::forward<Args>(args)...);
return aligned_unique_ptr<T, Al>(allocation.release());
}
} // namespace jsl

42
external/jsl/include/jsl/memory vendored Normal file
View file

@ -0,0 +1,42 @@
// -*- C++ -*-
// SPDX-License-Identifier: BSL-1.0
//
// Copyright Jean Pierre Cimalando 2018-2020.
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE or copy at
// http://www.boost.org/LICENSE_1_0.txt)
//
#pragma once
#include "./allocator"
#include <memory>
namespace jsl {
template <class T, std::size_t Al>
struct aligned_ptr_delete {
void operator()(T *p) const noexcept;
};
template <class T, std::size_t Al>
using aligned_unique_ptr = std::unique_ptr<T, aligned_ptr_delete<T, Al>>;
//------------------------------------------------------------------------------
template <class T> struct make_aligned_traits;
template <class T, size_t N> struct make_aligned_traits<T[N]> { struct invalid {}; };
template <class T> struct make_aligned_traits<T[]> { struct invalid {}; };
template <class T> struct make_aligned_traits { using single_element = T; };
//------------------------------------------------------------------------------
template <class T, std::size_t Al, class... Args>
aligned_unique_ptr<typename make_aligned_traits<T>::single_element, Al>
make_aligned(Args &&... args);
template <class T, std::size_t Al, class... Args>
typename make_aligned_traits<T>::invalid
make_aligned(Args &&... args) = delete;
} // namespace jsl
#include "bits/memory/aligned_unique_ptr.tcc"

2
external/simde vendored

@ -1 +1 @@
Subproject commit 5c2f423b41c06228e4be0cce0010a252297da4e7
Subproject commit 98075d0593f539762125dbb215d95e782a6ae344

View file

@ -1,4 +1,5 @@
add_library(plugins-common STATIC EXCLUDE_FROM_ALL
"common/plugin/RMSFollower.h"
"common/plugin/MessageUtils.h"
"common/plugin/MessageUtils.cpp"
"common/plugin/InstrumentDescription.h"
@ -28,7 +29,7 @@ endif()
target_include_directories(plugins-common PUBLIC "common")
target_link_libraries(plugins-common
PUBLIC sfizz::spin_mutex
PUBLIC sfizz::filesystem absl::strings
PUBLIC sfizz::simde sfizz::filesystem absl::strings
PRIVATE sfizz::pugixml
PRIVATE sfizz::internal sfizz::sfizz)
add_library(sfizz::plugins-common ALIAS plugins-common)
@ -40,10 +41,6 @@ elseif(APPLE)
target_link_libraries(plugins-common PRIVATE
"${APPLE_FOUNDATION_LIBRARY}")
else()
find_package(PkgConfig REQUIRED)
pkg_check_modules(GLIB REQUIRED glib-2.0)
target_include_directories(plugins-common PRIVATE ${GLIB_INCLUDE_DIRS})
target_link_libraries(plugins-common PRIVATE ${GLIB_LIBRARIES})
endif()
if((SFIZZ_LV2 AND SFIZZ_LV2_UI) OR SFIZZ_VST OR SFIZZ_AU OR SFIZZ_VST2)
@ -54,6 +51,10 @@ if(SFIZZ_LV2)
add_subdirectory(lv2)
endif()
if(SFIZZ_VST OR SFIZZ_AU OR SFIZZ_VST2)
if((SFIZZ_VST OR SFIZZ_AU OR SFIZZ_VST2) AND NOT (SFIZZ_SYSTEM_PROCESSOR MATCHES "^(powerpc|ppc).*"))
add_subdirectory(vst)
endif()
if(SFIZZ_PUREDATA)
add_subdirectory(puredata)
endif()

View file

@ -103,6 +103,7 @@ std::string getDescriptionBlob(sfizz_synth_t* handle)
synth.sendMessage(*client, 0, "/key/slots", "", nullptr);
synth.sendMessage(*client, 0, "/sw/last/slots", "", nullptr);
synth.sendMessage(*client, 0, "/cc/slots", "", nullptr);
synth.sendMessage(*client, 0, "/sustain_or_sostenuto/slots", "", nullptr);
blob.shrink_to_fit();
return blob;
@ -152,6 +153,8 @@ InstrumentDescription parseDescriptionBlob(absl::string_view blob)
copyArgToBitSpan(args[0], desc.keyswitchUsed.span());
else if (Messages::matchOSC("/cc/slots", path, indices) && !strcmp(sig, "b"))
copyArgToBitSpan(args[0], desc.ccUsed.span());
else if (Messages::matchOSC("/sustain_or_sostenuto/slots", path, indices) && !strcmp(sig, "b"))
copyArgToBitSpan(args[0], desc.sustainOrSostenuto.span());
else if (Messages::matchOSC("/key&/label", path, indices) && !strcmp(sig, "s"))
desc.keyLabel[indices[0]] = args[0].s;
else if (Messages::matchOSC("/sw/last/&/label", path, indices) && !strcmp(sig, "s"))

View file

@ -26,6 +26,7 @@ struct InstrumentDescription {
std::string image;
BitArray<128> keyUsed {};
BitArray<128> keyswitchUsed {};
BitArray<128> sustainOrSostenuto {};
BitArray<sfz::config::numCCs> ccUsed {};
std::array<std::string, 128> keyLabel {};
std::array<std::string, 128> keyswitchLabel {};

View file

@ -5,8 +5,12 @@
// If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz
#include "NativeHelpers.h"
#include <absl/strings/match.h>
#include <absl/strings/strip.h>
#include <absl/strings/string_view.h>
#include <stdexcept>
#include <cstdlib>
#include <fstream>
#if defined(_WIN32)
#include <windows.h>
@ -22,24 +26,119 @@ const fs::path& getUserDocumentsDirectory()
}();
return directory;
}
wchar_t *stringToWideChar(const char *str, int strCch)
{
unsigned strSize = MultiByteToWideChar(CP_UTF8, 0, str, strCch, nullptr, 0);
if (strSize == 0)
return {};
std::unique_ptr<wchar_t[]> strW(new wchar_t[strSize]);
if (MultiByteToWideChar(CP_UTF8, 0, str, strCch, strW.get(), strSize) == 0)
return {};
return strW.release();
}
char* stringToUTF8(const wchar_t *strW, int strWCch)
{
unsigned strSize = WideCharToMultiByte(CP_UTF8, 0, strW, strWCch, nullptr, 0, nullptr, nullptr);
if (strSize == 0)
return {};
std::unique_ptr<char[]> str(new char[strSize]);
if (WideCharToMultiByte(CP_UTF8, 0, strW, strWCch, str.get(), strSize, nullptr, nullptr) == 0)
return {};
return str.release();
}
#elif defined(__APPLE__)
// implemented in NativeHelpers.mm
#else
#include <glib.h>
const fs::path& getUserDocumentsDirectory()
{
static const fs::path directory = []() -> fs::path {
const gchar* path = g_get_user_special_dir(G_USER_DIRECTORY_DOCUMENTS);
if (path)
return fs::path(path);
else {
const char* home = getenv("HOME");
if (home && home[0] == '/')
return fs::path(home) / "Documents";
throw std::runtime_error("Cannot get the document directory.");
for (const XdgUserDirsEntry& ent :
parseXdgUserDirs(getXdgConfigHome() / "user-dirs.dirs")) {
if (ent.name == "XDG_DOCUMENTS_DIR")
return ent.value;
}
return getUserHomeDirectory() / "Documents";
}();
return directory;
}
const fs::path& getUserHomeDirectory()
{
static const fs::path directory = []() -> fs::path {
const char* home = getenv("HOME");
if (home && home[0] == '/')
return fs::u8path(home);
else
throw std::runtime_error("Cannot get the home directory.");
}();
return directory;
}
const fs::path& getXdgConfigHome()
{
static const fs::path directory = []() -> fs::path {
const char* config = getenv("XDG_CONFIG_HOME");
if (config && config[0] == '/')
return fs::u8path(config);
else
return getUserHomeDirectory() / ".config";
}();
return directory;
}
std::vector<XdgUserDirsEntry> parseXdgUserDirs(const fs::path& userDirsPath)
{
// from user-dirs.dirs(5)
// This file contains lines of the form `XDG_NAME_DIR=VALUE`
// VALUE must be of the form "$HOME/Path" or "/Path".
// Lines beginning with a # character are ignored.
std::vector<XdgUserDirsEntry> ents;
const fs::path& home = getUserHomeDirectory();
fs::ifstream in(userDirsPath);
std::string lineBuf;
lineBuf.reserve(256);
while (std::getline(in, lineBuf)) {
absl::string_view line(lineBuf);
line = absl::StripLeadingAsciiWhitespace(line);
if (line.empty() || line.front() == '#')
continue;
size_t pos = line.find('=');
if (pos == line.npos)
continue;
XdgUserDirsEntry ent;
ent.name = std::string(line.substr(0, pos));
absl::string_view rawValue = line.substr(pos + 1);
rawValue = absl::StripTrailingAsciiWhitespace(rawValue);
if (rawValue.size() < 2 || rawValue.front() != '"' || rawValue.back() != '"')
continue;
rawValue.remove_prefix(1);
rawValue.remove_suffix(1);
if (!rawValue.empty() && rawValue.front() == '/')
ent.value = fs::u8path(rawValue.begin(), rawValue.end());
else if (absl::StartsWith(rawValue, "$HOME")) {
absl::string_view part = rawValue.substr(5);
ent.value = home / fs::u8path(part.begin(), part.end()).relative_path();
}
else
continue;
ents.push_back(std::move(ent));
}
return ents;
}
#endif

View file

@ -6,5 +6,21 @@
#pragma once
#include <ghc/fs_std.hpp>
#include <vector>
#if defined(_WIN32)
#include <cwchar>
#endif
const fs::path& getUserDocumentsDirectory();
#if !defined(_WIN32) && !defined(__APPLE__)
const fs::path& getUserHomeDirectory();
const fs::path& getXdgConfigHome();
struct XdgUserDirsEntry { std::string name; fs::path value; };
std::vector<XdgUserDirsEntry> parseXdgUserDirs(const fs::path& userDirsPath);
#endif
#if defined(_WIN32)
wchar_t *stringToWideChar(const char *str, int strCch = -1);
char* stringToUTF8(const wchar_t *strW, int strWCch = -1);
#endif

View file

@ -0,0 +1,73 @@
// SPDX-License-Identifier: BSD-2-Clause
// This code is part of the sfizz library and is licensed under a BSD 2-clause
// license. You should have receive a LICENSE.md file along with the code.
// If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz
#pragma once
#include <simde/x86/sse.h>
#include <cstddef>
#include <cmath>
class RMSFollower {
public:
RMSFollower()
{
updatePole();
}
void clear()
{
mem_ = simde_mm_setzero_ps();
}
void init(float sampleRate)
{
sampleRate_ = sampleRate;
updatePole();
clear();
}
void setT60(float t60)
{
t60_ = t60;
updatePole();
}
void process(const float* leftBlock, const float* rightBlock, size_t numFrames)
{
simde__m128 mem = mem_;
const simde__m128 pole = simde_mm_load1_ps(&pole_);
for (size_t i = 0; i < numFrames; ++i) {
float left = leftBlock[i];
float right = rightBlock[i];
simde__m128 input = simde_mm_setr_ps(left, right, 0.0f, 0.0f);
input = simde_mm_mul_ps(input, input);
simde__m128 output = simde_mm_add_ps(input, simde_mm_mul_ps(pole, simde_mm_sub_ps(mem, input)));
mem = output;
}
mem_ = mem;
}
simde__m128 getMS() const
{
return mem_;
}
simde__m128 getRMS() const
{
return simde_mm_sqrt_ps(mem_);
}
private:
void updatePole()
{
pole_ = std::exp(float(-2.0 * M_PI) / (t60_ * sampleRate_));
}
private:
simde__m128 mem_ {};
float pole_ {};
float t60_ = 300e-3;
float sampleRate_ = 44100;
};

View file

@ -5,6 +5,7 @@
// If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz
#include "SfizzSettings.h"
#include "NativeHelpers.h"
#include <memory>
#include <cstdlib>
@ -36,28 +37,6 @@ static HKEY openRegistryKey()
return key;
}
static WCHAR* stringToWideChar(const char *str, int strCch = -1)
{
unsigned strSize = MultiByteToWideChar(CP_UTF8, 0, str, strCch, nullptr, 0);
if (strSize == 0)
return {};
std::unique_ptr<WCHAR[]> strW(new WCHAR[strSize]);
if (MultiByteToWideChar(CP_UTF8, 0, str, strCch, strW.get(), strSize) == 0)
return {};
return strW.release();
}
static char* stringToUTF8(const wchar_t *strW, int strWCch = -1)
{
unsigned strSize = WideCharToMultiByte(CP_UTF8, 0, strW, strWCch, nullptr, 0, nullptr, nullptr);
if (strSize == 0)
return {};
std::unique_ptr<char[]> str(new char[strSize]);
if (WideCharToMultiByte(CP_UTF8, 0, strW, strWCch, str.get(), strSize, nullptr, nullptr) == 0)
return {};
return str.release();
}
absl::optional<std::string> SfizzSettings::load(const char* name)
{
std::unique_ptr<WCHAR[]> nameW { stringToWideChar(name) };
@ -112,16 +91,7 @@ bool SfizzSettings::store(const char* name, absl::string_view value)
static const fs::path getSettingsPath()
{
fs::path dirPath;
const char* env;
if ((env = getenv("XDG_CONFIG_HOME")) && env[0] == '/')
dirPath = fs::path(env);
else if ((env = getenv("HOME")) && env[0] == '/')
dirPath = fs::path(env) / ".config";
else
return {};
dirPath /= "SFZTools";
dirPath /= "sfizz";
const fs::path dirPath = getXdgConfigHome() / "SFZTools" / "sfizz";
std::error_code ec;
fs::create_directories(dirPath, ec);
if (ec)

View file

@ -21,6 +21,7 @@ set(EDITOR_RESOURCES
Fonts/sfizz-misc-icons.ttf
Fonts/Roboto-Regular.ttf
Themes/Default/theme.xml
Themes/Dark/theme.xml
PARENT_SCOPE)
function(copy_editor_resources TARGET SOURCE_DIR DESTINATION_DIR)
@ -73,9 +74,7 @@ add_library(sfizz_editor STATIC EXCLUDE_FROM_ALL
src/editor/layout/about.hpp
src/editor/utility/vstgui_after.h
src/editor/utility/vstgui_before.h
src/editor/GitBuildId.h
${UI_FILES}
"${CMAKE_CURRENT_BINARY_DIR}/git-build-id/GitBuildId.c")
${UI_FILES})
add_library(sfizz::editor ALIAS sfizz_editor)
target_include_directories(sfizz_editor PUBLIC "src")
target_link_libraries(sfizz_editor PUBLIC sfizz::messaging sfizz::plugins-common)
@ -137,11 +136,5 @@ if(NOT CMAKE_CROSSCOMPILING)
endforeach()
endif()
# git build identifier
add_custom_target(sfizz_editor_git_build_id
COMMAND "${CMAKE_COMMAND}"
"-DSOURCE_DIR=${PROJECT_SOURCE_DIR}"
"-DOUTPUT_FILE=${CMAKE_CURRENT_BINARY_DIR}/git-build-id/GitBuildId.c"
"-P" "${CMAKE_CURRENT_SOURCE_DIR}/cmake/GitBuildID.cmake"
BYPRODUCTS "git-build-id/GitBuildId.c")
add_dependencies(sfizz_editor sfizz_editor_git_build_id)
# Git build identifier
target_link_libraries(sfizz_editor PRIVATE sfizz-git-build-id)

View file

@ -39,6 +39,7 @@ add_library(sfizz_vstgui STATIC EXCLUDE_FROM_ALL
"${VSTGUI_BASEDIR}/vstgui/lib/cfileselector.cpp"
"${VSTGUI_BASEDIR}/vstgui/lib/cfont.cpp"
"${VSTGUI_BASEDIR}/vstgui/lib/cframe.cpp"
"${VSTGUI_BASEDIR}/vstgui/lib/cgradient.cpp"
"${VSTGUI_BASEDIR}/vstgui/lib/cgradientview.cpp"
"${VSTGUI_BASEDIR}/vstgui/lib/cgraphicspath.cpp"
"${VSTGUI_BASEDIR}/vstgui/lib/clayeredviewcontainer.cpp"
@ -57,6 +58,7 @@ add_library(sfizz_vstgui STATIC EXCLUDE_FROM_ALL
"${VSTGUI_BASEDIR}/vstgui/lib/cview.cpp"
"${VSTGUI_BASEDIR}/vstgui/lib/cviewcontainer.cpp"
"${VSTGUI_BASEDIR}/vstgui/lib/cvstguitimer.cpp"
"${VSTGUI_BASEDIR}/vstgui/lib/events.cpp"
"${VSTGUI_BASEDIR}/vstgui/lib/genericstringlistdatabrowsersource.cpp"
"${VSTGUI_BASEDIR}/vstgui/lib/pixelbuffer.cpp"
"${VSTGUI_BASEDIR}/vstgui/lib/vstguidebug.cpp"
@ -69,6 +71,7 @@ if(WIN32)
"${VSTGUI_BASEDIR}/vstgui/lib/platform/win32/direct2d/d2dbitmap.cpp"
"${VSTGUI_BASEDIR}/vstgui/lib/platform/win32/direct2d/d2ddrawcontext.cpp"
"${VSTGUI_BASEDIR}/vstgui/lib/platform/win32/direct2d/d2dfont.cpp"
"${VSTGUI_BASEDIR}/vstgui/lib/platform/win32/direct2d/d2dgradient.cpp"
"${VSTGUI_BASEDIR}/vstgui/lib/platform/win32/direct2d/d2dgraphicspath.cpp"
"${VSTGUI_BASEDIR}/vstgui/lib/platform/win32/win32datapackage.cpp"
"${VSTGUI_BASEDIR}/vstgui/lib/platform/win32/win32dragging.cpp"

@ -1 +1 @@
Subproject commit 9a116d1812aa274ccf61d68f4c2bae612b3871ad
Subproject commit 0db8738ae73ff76842eb0bfe90cdf2039690a5b5

View file

@ -1,13 +1,13 @@
# data file for the Fltk User Interface Designer (fluid)
version 1.0305
version 1.0306
header_name {.h}
code_name {.cxx}
widget_class mainView {open
xywh {439 94 800 475} type Double
xywh {108 91 800 475} type Double
class LogicalGroup visible
} {
Fl_Box imageContainer_ {
image {../resources/background.png} xywh {190 110 600 280}
image {../resources/background.png} xywh {5 110 790 285}
class Background
}
Fl_Group {} {
@ -15,53 +15,58 @@ widget_class mainView {open
xywh {0 0 800 110}
class LogicalGroup
} {
Fl_Group {} {open
Fl_Group {} {
xywh {5 4 175 101} box ROUNDED_BOX align 0
class RoundedGroup
} {
Fl_Box {} {
comment {tag=kTagAbout} selected
comment {tag=kTagAbout}
image {../resources/logo_text_shaded.png} xywh {32 9 120 60}
class AboutButton
}
Fl_Button {panelButtons_[kPanelGeneral]} {
comment {tag=kTagFirstChangePanel+kPanelGeneral}
xywh {36 73 32 32} labelsize 30
class HomeButton
Fl_Button {panelButtons_[kPanelInfo]} {
comment {tag=kTagFirstChangePanel+kPanelInfo}
xywh {56 73 32 32} labelsize 30
class InfoButton
}
Fl_Button {panelButtons_[kPanelControls]} {
comment {tag=kTagFirstChangePanel+kPanelControls}
xywh {76 73 32 32} labelsize 30
xywh {97 73 32 32} labelsize 30
class CCButton
}
Fl_Button {panelButtons_[kPanelSettings]} {
comment {tag=kTagFirstChangePanel+kPanelSettings}
xywh {116 73 32 32} labelsize 30
xywh {137 73 32 32} labelsize 30
class SettingsButton
}
Fl_Button {panelButtons_[kPanelGeneral]} {
comment {tag=kTagFirstChangePanel+kPanelGeneral}
xywh {16 73 32 32} labelsize 30
class HomeButton
}
}
Fl_Group {} {open
xywh {185 5 380 100} box ROUNDED_BOX
Fl_Group {} {
xywh {185 5 418 100} box ROUNDED_BOX
class RoundedGroup
} {
Fl_Box {} {
label {Separator 1}
xywh {195 41 360 5} box BORDER_BOX labeltype NO_LABEL
xywh {195 41 395 5} box BORDER_BOX labeltype NO_LABEL
class HLine
}
Fl_Box {} {
label {Separator 2}
xywh {195 73 360 5} box BORDER_BOX labeltype NO_LABEL
xywh {195 73 395 5} box BORDER_BOX labeltype NO_LABEL
class HLine
}
Fl_Box sfzFileLabel_ {
label {DefaultInstrument.sfz}
comment {tag=kTagLoadSfzFile}
xywh {195 13 250 30} labelsize 20 align 20
xywh {195 13 320 30} labelsize 20 align 20
class ClickableLabel
}
Fl_Box keyswitchLabel_ {
xywh {265 45 290 30} labelsize 20 align 20
xywh {265 45 325 35} labelsize 20 align 20
class Label
}
Fl_Box keyswitchBadge_ {
@ -80,17 +85,17 @@ widget_class mainView {open
}
Fl_Button {} {
comment {tag=kTagPreviousSfzFile}
xywh {480 18 25 25} labelsize 24
xywh {515 18 25 25} labelsize 24
class PreviousFileButton
}
Fl_Button {} {
comment {tag=kTagNextSfzFile}
xywh {505 18 25 25} labelsize 24
xywh {540 18 25 25} labelsize 24
class NextFileButton
}
Fl_Button fileOperationsMenu_ {
comment {tag=kTagFileOperations}
xywh {530 18 25 25} labelsize 24
xywh {565 18 25 25} labelsize 24
class ChevronDropDown
}
Fl_Box infoVoicesLabel_ {
@ -99,30 +104,30 @@ widget_class mainView {open
}
Fl_Box {} {
label {Max:}
xywh {315 78 40 25} labelsize 12 align 24
xywh {332 78 40 25} labelsize 12 align 24
class Label
}
Fl_Box numVoicesLabel_ {
xywh {360 78 35 25} labelsize 12 align 16
xywh {378 78 35 25} labelsize 12 align 16
class Label
}
Fl_Box {} {
label {Memory:}
xywh {425 78 60 25} labelsize 12 align 24
xywh {459 78 60 25} labelsize 12 align 24
class Label
}
Fl_Box memoryLabel_ {
xywh {490 78 60 25} labelsize 12 align 16
xywh {525 78 60 25} labelsize 12 align 16
class Label
}
Fl_Button numVoicesSlider_ {
comment {tag=kTagSetNumVoices}
xywh {395 82 20 20} labelsize 16
xywh {420 82 20 20} labelsize 16
class ChevronValueDropDown
}
}
Fl_Group {} {open
xywh {570 5 225 100} box ROUNDED_BOX
Fl_Group {} {
xywh {608 5 187 100} box ROUNDED_BOX
class RoundedGroup
} {
Fl_Dial {} {
@ -134,30 +139,34 @@ widget_class mainView {open
xywh {610 70 60 5} labelsize 12 hide
class ValueLabel
}
Fl_Box {} {
xywh {745 20 35 55} box BORDER_BOX
class VMeter
}
Fl_Box volumeCCKnob_ {
label Volume
comment {tag=kTagSetCCVolume}
xywh {580 10 70 90} box BORDER_BOX labelsize 12 align 17
xywh {614 10 70 90} box BORDER_BOX labelsize 12 align 17
class KnobCCBox
}
Fl_Box panCCKnob_ {
label Pan
comment {tag=kTagSetCCPan}
xywh {655 10 70 90} box BORDER_BOX labelsize 12 align 17
xywh {691 10 70 90} box BORDER_BOX labelsize 12 align 17
class KnobCCBox
}
Fl_Box leftMeter_ {
xywh {767 10 9 90} box BORDER_BOX
class VMeter
}
Fl_Box rightMeter_ {
xywh {779 10 9 90} box BORDER_BOX
class VMeter
}
}
}
Fl_Group {subPanels_[kPanelGeneral]} {
xywh {5 110 791 285} hide
Fl_Group {subPanels_[kPanelInfo]} {open
xywh {5 110 790 285} hide
class LogicalGroup
} {
Fl_Group {} {open
xywh {5 110 175 280} box ROUNDED_BOX
xywh {5 110 790 285} box ROUNDED_BOX
class RoundedGroup
} {
Fl_Box {} {
@ -226,8 +235,8 @@ widget_class mainView {open
} {}
}
}
Fl_Group {subPanels_[kPanelSettings]} {open
xywh {5 109 790 316}
Fl_Group {subPanels_[kPanelSettings]} {
xywh {5 110 790 285}
class LogicalGroup
} {
Fl_Group {} {
@ -357,8 +366,8 @@ widget_class mainView {open
}
}
Fl_Group {} {
label Theme open
xywh {40 270 105 100} box ROUNDED_BOX labelsize 12 align 17 hide
label Appearance open
xywh {40 270 105 100} box ROUNDED_BOX labelsize 12 align 17
class TitleGroup
} {
Fl_Spinner themeMenu_ {
@ -366,10 +375,19 @@ widget_class mainView {open
xywh {60 330 65 25} labelsize 12 textsize 12
class OptionMenu
}
Fl_Box {} {
label Theme
xywh {50 295 80 25} labelsize 12
class ValueLabel
}
}
}
Fl_Box piano_ {
xywh {5 400 790 70} labelsize 12
class Piano
}
Fl_Group {subPanels_[kPanelGeneral]} {open selected
xywh {5 110 790 285} hide
class LogicalGroup
} {}
}

View file

@ -0,0 +1,40 @@
<?xml version="1.0"?>
<sfizz-theme author="Andrea Zanellato" version="1.0.0">
<color name="frameBackground">#121212</color>
<palette name="normal">
<color name="boxBackground">#1d1d1d</color>
<color name="text">#e3e3e3</color>
<color name="inactiveText">#a2a2a2</color>
<color name="highlightedText">#e9e9e9</color>
<color name="titleBoxText">#e3e3e3</color>
<color name="titleBoxBackground">#2d2d2d</color>
<color name="valueText">#e3e3e3</color>
<color name="valueBackground">#121212</color>
<color name="icon">#e3e3e3</color>
<color name="iconHighlight">#ff6000</color>
<color name="knobActiveTrack">#006b0b</color>
<color name="knobInactiveTrack">#6f6f6f</color>
<color name="knobLineIndicator">#ffffff</color>
<color name="knobText">#ffffff</color>
<color name="knobLabelText">#ffffff</color>
<color name="knobLabelBackground">#006b0b</color>
</palette>
<palette name="inverted">
<color name="boxBackground">#2d2d2d</color>
<color name="text">#9e9e9e</color>
<color name="inactiveText">#a2a2a2</color>
<color name="highlightedText">#e9e9e9</color>
<color name="titleBoxText">#9e9e9e</color>
<color name="titleBoxBackground">#2d2d2d</color>
<color name="valueText">#9e9e9e</color>
<color name="valueBackground">#121212</color>
<color name="icon">#9e9e9e</color>
<color name="iconHighlight">#ff6000</color>
<color name="knobActiveTrack">#006b0b</color>
<color name="knobInactiveTrack">#6f6f6f</color>
<color name="knobLineIndicator">#ffffff</color>
<color name="knobText">#ffffff</color>
<color name="knobLabelText">#ffffff</color>
<color name="knobLabelBackground">#006b0b</color>
</palette>
</sfizz-theme>

View file

@ -38,6 +38,9 @@ enum class EditId : int {
CC_RANGE(ControllerDefault),
CC_RANGE(ControllerLabel),
//
LeftLevel,
RightLevel,
//
UINumCurves,
UINumMasters,
UINumGroups,

View file

@ -66,6 +66,7 @@ struct Editor::Impl : EditorController::Receiver,
enum {
kPanelGeneral,
kPanelInfo,
kPanelControls,
kPanelSettings,
kNumPanels,
@ -153,16 +154,19 @@ struct Editor::Impl : EditorController::Receiver,
SKnobCCBox* volumeCCKnob_ = nullptr;
SKnobCCBox* panCCKnob_ = nullptr;
SLevelMeter* leftMeter_ = nullptr;
SLevelMeter* rightMeter_ = nullptr;
SAboutDialog* aboutDialog_ = nullptr;
SharedPointer<CBitmap> backgroundBitmap_;
SharedPointer<CBitmap> defaultBackgroundBitmap_;
CControl* getSecondaryCCControl(unsigned cc)
SKnobCCBox* getSecondaryCCKnob(unsigned cc)
{
switch (cc) {
case 7: return volumeCCKnob_ ? volumeCCKnob_->getControl() : nullptr;
case 10: return panCCKnob_ ? panCCKnob_->getControl() : nullptr;
case 7: return volumeCCKnob_ ? volumeCCKnob_ : nullptr;
case 10: return panCCKnob_ ? panCCKnob_ : nullptr;
default: return nullptr;
}
}
@ -504,6 +508,20 @@ void Editor::Impl::uiReceiveValue(EditId id, const EditValue& v)
updateBackgroundImage(value.c_str());
}
break;
case EditId::LeftLevel:
{
const float value = v.to_float();
if (SLevelMeter* meter = leftMeter_)
meter->setValue(value);
}
break;
case EditId::RightLevel:
{
const float value = v.to_float();
if (SLevelMeter* meter = rightMeter_)
meter->setValue(value);
}
break;
default:
if (editIdIsKey(id)) {
const int key = keyForEditId(id);
@ -703,11 +721,15 @@ void Editor::Impl::createFrameContents()
lbl->setFont(font);
return lbl;
};
auto createVMeter = [](const CRect& bounds, int, const char*, CHoriTxtAlign, int) {
// TODO the volume meter...
CViewContainer* container = new CViewContainer(bounds);
container->setBackgroundColor(CColor(0x00, 0x00, 0x00, 0x00));
return container;
auto createVMeter = [this, &palette](const CRect& bounds, int, const char*, CHoriTxtAlign, int) {
SLevelMeter* meter = new SLevelMeter(bounds);
meter->setFrameColor(CColor(0x00, 0x00, 0x00, 0x00));
meter->setNormalFillColor(CColor(0x00, 0xaa, 0x11));
meter->setDangerFillColor(CColor(0xaa, 0x00, 0x00));
OnThemeChanged.push_back([meter, palette]() {
meter->setBackColor(palette->knobInactiveTrack);
});
return meter;
};
#if 0
auto createButton = [this](const CRect& bounds, int tag, const char* label, CHoriTxtAlign align, int fontsize) {
@ -796,6 +818,9 @@ void Editor::Impl::createFrameContents()
auto createHomeButton = [&createGlyphButton](const CRect& bounds, int tag, const char*, CHoriTxtAlign, int fontsize) {
return createGlyphButton(u8"\ue1d6", bounds, tag, fontsize);
};
auto createInfoButton = [&createGlyphButton](const CRect& bounds, int tag, const char*, CHoriTxtAlign, int fontsize) {
return createGlyphButton(u8"\ue1e7", bounds, tag, fontsize);
};
auto createCCButton = [&createGlyphButton](const CRect& bounds, int tag, const char*, CHoriTxtAlign, int fontsize) {
// return createGlyphButton(u8"\ue240", bounds, tag, fontsize);
return createGlyphButton(u8"\ue253", bounds, tag, fontsize);
@ -881,6 +906,10 @@ void Editor::Impl::createFrameContents()
box->setCCLabelFont(font);
OnThemeChanged.push_back([box, palette]() {
box->setNameLabelFontColor(palette->knobText);
box->setValueEditFontColor(palette->knobText);
auto shadingColor = palette->knobText;
shadingColor.alpha = 70;
box->setShadingRectangleColor(shadingColor);
box->setCCLabelFontColor(palette->knobLabelText);
box->setCCLabelBackColor(palette->knobLabelBackground);
box->setKnobFontColor(palette->knobText);
@ -888,10 +917,6 @@ void Editor::Impl::createFrameContents()
box->setKnobActiveTrackColor(palette->knobActiveTrack);
box->setKnobInactiveTrackColor(palette->knobInactiveTrack);
});
box->setValueToStringFunction([](float value, std::string& text) -> bool {
text = std::to_string(std::lround(value * 127));
return true;
});
return box;
};
auto createBackground = [&background](const CRect& bounds, int, const char*, CHoriTxtAlign, int) {
@ -907,6 +932,10 @@ void Editor::Impl::createFrameContents()
panel->setCCLabelFont(font);
OnThemeChanged.push_back([panel, palette]() {
panel->setNameLabelFontColor(palette->knobText);
panel->setValueEditFontColor(palette->knobText);
auto shadingColor = palette->knobText;
shadingColor.alpha = 70;
panel->setShadingRectangleColor(shadingColor);
panel->setCCLabelFontColor(palette->knobLabelText);
panel->setCCLabelBackColor(palette->knobLabelBackground);
panel->setKnobFontColor(palette->knobText);
@ -1640,7 +1669,7 @@ void Editor::Impl::updateCCValue(unsigned cc, float value)
if (SControlsPanel* panel = controlsPanel_)
panel->setControlValue(cc, value);
if (CControl* other = getSecondaryCCControl(cc)) {
if (SKnobCCBox* other = getSecondaryCCKnob(cc)) {
other->setValue(value);
other->invalid();
}
@ -1651,7 +1680,7 @@ void Editor::Impl::updateCCDefaultValue(unsigned cc, float value)
if (SControlsPanel* panel = controlsPanel_)
panel->setControlDefaultValue(cc, value);
if (CControl* other = getSecondaryCCControl(cc))
if (SKnobCCBox* other = getSecondaryCCKnob(cc))
other->setDefaultValue(value);
}
@ -1977,4 +2006,6 @@ void Editor::Impl::onThemeChanged()
if (function)
function();
}
if (CFrame* frame = frame_)
frame->invalid();
}

View file

@ -15,6 +15,7 @@
#include "vstgui/lib/cvstguitimer.h"
#include "vstgui/lib/cframe.h"
#include "utility/vstgui_after.h"
#include "absl/strings/numbers.h"
///
SBoxContainer::SBoxContainer(const CRect& size)
@ -582,7 +583,7 @@ void SStyledKnob::draw(CDrawContext* dc)
dc->drawLine(p1, p2);
}
if (valueToStringFunction_ && fontColor_.alpha > 0) {
if (valueToStringFunction_ && fontColor_.alpha > 0 && !hideValue_) {
std::string text;
if (valueToStringFunction_(getValue(), text)) {
dc->setFont(font_);
@ -592,12 +593,29 @@ void SStyledKnob::draw(CDrawContext* dc)
}
}
void CFilledRect::draw(CDrawContext* dc)
{
CRect bounds = getViewSize();
dc->setFillColor(color_);
bool isRounded = radius_ > 0.0;
if (isRounded) {
auto roundRect = owned(dc->createRoundRectGraphicsPath(bounds, radius_));
dc->drawGraphicsPath(roundRect, CDrawContext::kPathFilled);
} else {
dc->drawRect(bounds, kDrawFilled);
}
}
///
SKnobCCBox::SKnobCCBox(const CRect& size, IControlListener* listener, int32_t tag)
: CViewContainer(size),
label_(makeOwned<CTextLabel>(CRect())),
valueEdit_(makeOwned<CTextEdit>(CRect(), listener, tag)),
knob_(makeOwned<SStyledKnob>(CRect(), listener, tag)),
ccLabel_(makeOwned<CTextLabel>(CRect()))
ccLabel_(makeOwned<CTextLabel>(CRect())),
shadingRectangle_(makeOwned<CFilledRect>(CRect())),
menuEntry_(makeOwned<CMenuItem>("Use HDCC", tag)),
menuListener_(owned(new MenuListener(*this)))
{
setBackgroundColor(CColor(0x00, 0x00, 0x00, 0x00));
@ -614,17 +632,129 @@ SKnobCCBox::SKnobCCBox(const CRect& size, IControlListener* listener, int32_t ta
ccLabel_->setFrameColor(CColor(0x00, 0x00, 0x00, 0x00));
ccLabel_->setFontColor(CColor(0xff, 0xff, 0xff));
valueEdit_->setBackColor(CColor(0x00, 0x00, 0x00, 0x00));
valueEdit_->setFrameColor(CColor(0x00, 0x00, 0x00, 0x00));
valueEdit_->setFontColor(CColor(0x00, 0x00, 0x00, 0xff));
valueEdit_->registerViewListener(this);
setHDMode(false);
valueEdit_->setVisible(false);
shadingRectangle_->setVisible(false);
addView(label_);
label_->remember();
addView(knob_);
knob_->remember();
addView(shadingRectangle_);
shadingRectangle_->remember();
addView(valueEdit_);
valueEdit_->remember();
addView(ccLabel_);
ccLabel_->remember();
updateViewColors();
updateViewSizes();
}
SKnobCCBox::~SKnobCCBox()
{
valueEdit_->unregisterViewListener(this);
}
void SKnobCCBox::setHDMode(bool mode)
{
if (mode) {
auto valueToString = [](float value, std::string& text, VSTGUI::CParamDisplay*) -> bool {
std::string s = std::to_string(value + 0.005f);
text = s.substr(0, 4);
return true;
};
knob_->setValueToStringFunction([valueToString](float value, std::string& text) {
return valueToString(value, text, nullptr);
});
valueEdit_->setValueToStringFunction2(valueToString);
valueEdit_->setStringToValueFunction([](UTF8StringPtr txt, float& result, CTextEdit*) -> bool {
float value;
if (absl::SimpleAtof(txt, &value)) {
result = value;
return true;
}
return false;
});
menuEntry_->setTitle("Use low-res. CC");
} else {
auto valueToString = [](float value, std::string& text, VSTGUI::CParamDisplay*) -> bool {
text = std::to_string(std::lround(value * 127));
return true;
};
knob_->setValueToStringFunction([valueToString](float value, std::string& text) {
return valueToString(value, text, nullptr);
});
valueEdit_->setValueToStringFunction2(valueToString);
valueEdit_->setStringToValueFunction([](UTF8StringPtr txt, float& result, CTextEdit*) -> bool {
float value;
if (absl::SimpleAtof(txt, &value)) {
result = value / 127.0f;
return true;
}
return false;
});
menuEntry_->setTitle("Use high-res. CC");
}
hdMode_ = mode;
valueEdit_->setValue(valueEdit_->getValue());
invalid();
}
CMouseEventResult SKnobCCBox::onMouseDown(CPoint& where, const CButtonState& buttons)
{
if (buttons.isRightButton()) {
CFrame* frame = getFrame();
CPoint frameWhere = where;
frameWhere.offset(-getViewSize().left, -getViewSize().top);
this->localToFrame(frameWhere);
auto self = shared(this);
frame->doAfterEventProcessing([self, frameWhere]() {
if (CFrame* frame = self->getFrame()) {
SharedPointer<COptionMenu> menu =
owned(new COptionMenu(CRect(), self->menuListener_, -1, nullptr, nullptr, COptionMenu::kPopupStyle));
menu->addEntry(self->menuEntry_);
self->menuEntry_->remember(); // above call does not increment refcount
menu->setFont(self->getValueEditFont());
menu->setFontColor(self->getValueEditFontColor());
menu->setBackColor(self->getValueEditBackColor());
menu->popup(frame, frameWhere);
}
});
return kMouseEventHandled;
} else if (buttons.isDoubleClick() && !valueEdit_->isVisible()) {
valueEdit_->setVisible(true);
shadingRectangle_->setVisible(true);
knob_->setHideValue(true);
valueEdit_->takeFocus();
invalid();
return kMouseEventHandled;
}
return CViewContainer::onMouseDown(where, buttons);
}
void SKnobCCBox::viewLostFocus (CView* view)
{
if (view == valueEdit_.get()) {
shadingRectangle_->setVisible(false);
valueEdit_->setVisible(false);
knob_->setHideValue(false);
invalid();
}
}
void SKnobCCBox::setHue(float hue)
{
hue_ = hue;
@ -637,12 +767,33 @@ void SKnobCCBox::setNameLabelFont(CFontRef font)
updateViewSizes();
}
void SKnobCCBox::setValueEditFont(CFontRef font)
{
label_->setFont(font);
updateViewSizes();
}
void SKnobCCBox::setCCLabelFont(CFontRef font)
{
ccLabel_->setFont(font);
updateViewSizes();
}
void SKnobCCBox::setValue(float value)
{
float oldValue = knob_->getValue();
knob_->setValue(value);
valueEdit_->setValue(value);
if (value != oldValue)
invalid();
}
void SKnobCCBox::setDefaultValue(float value)
{
knob_->setDefaultValue(value);
valueEdit_->setDefaultValue(value);
}
void SKnobCCBox::updateViewSizes()
{
const CRect size = getViewSize();
@ -650,19 +801,30 @@ void SKnobCCBox::updateViewSizes()
const CFontRef nameFont = label_->getFont();
const CFontRef ccFont = ccLabel_->getFont();
const CFontRef valueFont = valueEdit_->getFont();
nameLabelSize_ = CRect(0.0, 0.0, size.getWidth(), nameFont->getSize() + 2 * ypad);
ccLabelSize_ = CRect(0.0, size.getHeight() - ccFont->getSize() - 2 * ypad, size.getWidth(), size.getHeight());
knobSize_ = CRect(0.0, nameLabelSize_.bottom, size.getWidth(), ccLabelSize_.top);
valueEditSize_ = CRect(
size.getWidth() / 2 - valueFont->getSize(),
size.getHeight() / 2 - valueFont->getSize() / 2,
size.getWidth() / 2 + valueFont->getSize(),
size.getHeight() / 2 + valueFont->getSize() / 2
);
// remove knob side areas
CCoord side = std::max(0.0, knobSize_.getWidth() - knobSize_.getHeight());
knobSize_.extend(-0.5 * side, 0.0);
shadingRectangleSize_ = knobSize_;
shadingRectangleSize_.bottom -= ypad;
//
label_->setViewSize(nameLabelSize_);
knob_->setViewSize(knobSize_);
ccLabel_->setViewSize(ccLabelSize_);
valueEdit_->setViewSize(valueEditSize_);
shadingRectangle_->setViewSize(shadingRectangleSize_);
invalid();
}
@ -746,11 +908,6 @@ SControlsPanel::ControlSlot* SControlsPanel::getOrCreateSlot(uint32_t index)
slot->box = box;
slot->box->setCCLabelText(("CC " + std::to_string(index)).c_str());
slot->box->setValueToStringFunction([](float value, std::string& text) -> bool {
text = std::to_string(std::lround(value * 127));
return true;
});
syncSlotStyle(index);
return slot;
@ -760,10 +917,9 @@ void SControlsPanel::setControlValue(uint32_t index, float value)
{
ControlSlot* slot = getOrCreateSlot(index);
SKnobCCBox* box = slot->box;
auto* control = box->getControl();
float oldValue = control->getValue();
control->setValue(value);
if (control->getValue() != oldValue)
float oldValue = box->getValue();
box->setValue(value);
if (box->getValue() != oldValue)
box->invalid();
}
@ -771,7 +927,7 @@ void SControlsPanel::setControlDefaultValue(uint32_t index, float value)
{
ControlSlot* slot = getOrCreateSlot(index);
SKnobCCBox* box = slot->box;
box->getControl()->setDefaultValue(value);
box->setDefaultValue(value);
}
void SControlsPanel::setControlLabelText(uint32_t index, UTF8StringPtr text)
@ -788,12 +944,14 @@ void SControlsPanel::setControlLabelText(uint32_t index, UTF8StringPtr text)
void SControlsPanel::setNameLabelFont(CFontRef font)
{
slots_[0]->box->setNameLabelFont(font);
slots_[0]->box->setValueEditFont(font);
syncAllSlotStyles();
}
void SControlsPanel::setNameLabelFontColor(CColor color)
{
slots_[0]->box->setNameLabelFontColor(color);
slots_[0]->box->setValueEditFontColor(color);
syncAllSlotStyles();
}
@ -815,6 +973,24 @@ void SControlsPanel::setCCLabelFontColor(CColor color)
syncAllSlotStyles();
}
void SControlsPanel::setValueEditBackColor(CColor color)
{
slots_[0]->box->setValueEditBackColor(color);
syncAllSlotStyles();
}
void SControlsPanel::setShadingRectangleColor(CColor color)
{
slots_[0]->box->setShadingRectangleColor(color);
syncAllSlotStyles();
}
void SControlsPanel::setValueEditFontColor(CColor color)
{
slots_[0]->box->setValueEditFontColor(color);
syncAllSlotStyles();
}
void SControlsPanel::setKnobActiveTrackColor(CColor color)
{
slots_[0]->box->setKnobActiveTrackColor(color);
@ -947,6 +1123,11 @@ void SControlsPanel::syncSlotStyle(uint32_t index)
cur->setNameLabelFont(ref->getNameLabelFont());
cur->setNameLabelFontColor(ref->getNameLabelFontColor());
cur->setValueEditFont(ref->getValueEditFont());
cur->setValueEditFontColor(ref->getValueEditFontColor());
cur->setShadingRectangleColor(ref->getShadingRectangleColor());
cur->setCCLabelFont(ref->getCCLabelFont());
cur->setCCLabelFontColor(ref->getCCLabelFontColor());
cur->setCCLabelBackColor(ref->getCCLabelBackColor());
@ -979,6 +1160,113 @@ void SControlsPanel::ControlSlotListener::controlEndEdit(CControl* pControl)
panel_->EndEditFunction(pControl->getTag());
}
///
SLevelMeter::SLevelMeter(const CRect& size)
: CView(size)
{
}
void SLevelMeter::setValue(float value)
{
if (value_ == value)
return;
value_ = value;
// instantiate the timer lazily
if (!timer_) {
const uint32_t interval = 10;
timer_ = makeOwned<CVSTGUITimer>(
[this](CVSTGUITimer* timer) {
timer->stop();
timerArmed_ = false;
invalid();
}, interval, false);
}
// defer the update, but do not rearm the timer
if (!timerArmed_) {
timerArmed_ = true;
timer_->start();
}
}
void SLevelMeter::draw(CDrawContext* dc)
{
float dbValue = 20.0f * std::log10(value_);
float fill = (dbValue - dbMin_) / (dbMax_ - dbMin_);
fill = (fill < 0.0f) ? 0.0f : fill;
fill = (fill > 1.0f) ? 1.0f : fill;
CRect largeBounds = getViewSize();
CRect fillBounds = largeBounds;
fillBounds.top = largeBounds.bottom - fill * largeBounds.getHeight();
const CColor safeColor = safeFillColor_;
const CColor dangerColor = dangerFillColor_;
CColor fillColor;
if (safeColor == dangerColor) {
fillColor = safeColor;
}
else {
float thres = dangerThreshold_;
float mix = (fill - thres) / (1.0f - thres);
mix = (mix < 0.0f) ? 0.0f : mix;
CCoord safeH, safeS, safeV, safeA;
CCoord dangerH, dangerS, dangerV, dangerA;
safeColor.toHSV(safeH, safeS, safeV);
dangerColor.toHSV(dangerH, dangerS, dangerV);
safeA = safeColor.alpha / 255.0;
dangerA = dangerColor.alpha / 255.0;
CCoord H, S, V, A;
H = safeH + mix * (dangerH - safeH);
S = safeS + mix * (dangerS - safeS);
V = safeV + mix * (dangerV - safeV);
A = safeA + mix * (dangerA - safeA);
fillColor.fromHSV(H, S, V);
fillColor.alpha = static_cast<uint8_t>(A * 255.0);
}
CCoord radius = radius_;
bool isRounded = radius > 0.0;
dc->setDrawMode(isRounded ? kAntiAliasing : kAliasing);
SharedPointer<CGraphicsPath> largeRoundRect;
SharedPointer<CGraphicsPath> fillRoundRect;
if (isRounded) {
largeRoundRect = owned(dc->createRoundRectGraphicsPath(largeBounds, radius));
fillRoundRect = owned(dc->createRoundRectGraphicsPath(fillBounds, radius));
}
if (backColor_.alpha > 0) {
dc->setFillColor(backColor_);
if (!isRounded)
dc->drawRect(largeBounds, kDrawFilled);
else
dc->drawGraphicsPath(largeRoundRect, CDrawContext::kPathFilled);
}
dc->setFrameColor(frameColor_);
dc->setFillColor(fillColor);
if (!isRounded) {
if (fill > 0)
dc->drawRect(fillBounds, kDrawFilled);
dc->drawRect(largeBounds);
}
else {
if (fill > 0 && fillBounds.getHeight() >= radius)
dc->drawGraphicsPath(fillRoundRect, CDrawContext::kPathFilled);
dc->drawGraphicsPath(largeRoundRect, CDrawContext::kPathStroked);
}
}
///
SPlaceHolder::SPlaceHolder(const CRect& size, const CColor& color)
: CView(size), color_(color)

View file

@ -14,6 +14,7 @@
#include "vstgui/lib/controls/cslider.h"
#include "vstgui/lib/controls/cknob.h"
#include "vstgui/lib/controls/ctextlabel.h"
#include "vstgui/lib/controls/ctextedit.h"
#include "vstgui/lib/controls/cbuttons.h"
#include "vstgui/lib/controls/coptionmenu.h"
#include "vstgui/lib/controls/cscrollbar.h"
@ -233,6 +234,9 @@ public:
using ValueToStringFunction = std::function<bool(float value, std::string& result)>;
void setValueToStringFunction(ValueToStringFunction func);
void setHideValue(bool hide) { hideValue_ = hide; invalid(); }
bool getHideValue() const { return hideValue_; }
CLASS_METHODS(SStyledKnob, CKnobBase)
protected:
void draw(CDrawContext* dc) override;
@ -241,6 +245,7 @@ private:
CColor activeTrackColor_;
CColor inactiveTrackColor_;
CColor lineIndicatorColor_;
bool hideValue_ { false };
SharedPointer<CFontDesc> font_ = kNormalFont;
CColor fontColor_ { 0x00, 0x00, 0x00 };
@ -248,12 +253,35 @@ private:
ValueToStringFunction valueToStringFunction_;
};
class CFilledRect : public CView
{
public:
explicit CFilledRect(const CRect& size)
: CView(size) {}
void setRadius(CCoord radius) { radius_ = radius; invalid(); }
CCoord getRadius() const { return radius_; }
void setColor(CColor color){ color_ = color; invalid(); }
CColor getColor() { return color_; }
protected:
void draw(CDrawContext* dc) override;
private:
CCoord radius_ { 5.0 };
CColor color_ { 0, 0, 0, 70 };
};
///
class SKnobCCBox : public CViewContainer {
class SKnobCCBox : public CViewContainer, ViewListenerAdapter {
public:
SKnobCCBox(const CRect& size, IControlListener* listener, int32_t tag);
~SKnobCCBox();
void setHue(float hue);
SStyledKnob* getControl() const { return knob_; }
float getValue() const { return knob_->getValue(); }
float getDefaultValue() const { return knob_->getDefaultValue(); }
void setValue(float value);
void setDefaultValue(float value);
void setNameLabelText(const UTF8String& name) { label_->setText(name); label_->invalid(); }
void setCCLabelText(const UTF8String& name) { ccLabel_->setText(name); ccLabel_->invalid(); }
@ -264,6 +292,18 @@ public:
void setNameLabelFontColor(CColor color) { label_->setFontColor(color); label_->invalid(); }
CColor getNameLabelFontColor() const { return label_->getFontColor(); }
void setValueEditFont(CFontRef font);
CFontRef getValueEditFont() const { return label_->getFont(); }
void setValueEditFontColor(CColor color) { valueEdit_->setFontColor(color); valueEdit_->invalid(); }
CColor getValueEditFontColor() const { return valueEdit_->getFontColor(); }
void setValueEditBackColor(CColor color) { valueEdit_->setBackColor(color); valueEdit_->invalid(); }
CColor getValueEditBackColor() const { return valueEdit_->getBackColor(); }
void setShadingRectangleColor(CColor color) { shadingRectangle_->setColor(color); shadingRectangle_->invalid(); }
CColor getShadingRectangleColor() const { return shadingRectangle_->getColor(); }
void setCCLabelFont(CFontRef font);
CFontRef getCCLabelFont() const { return ccLabel_->getFont(); }
@ -288,21 +328,44 @@ public:
void setKnobFontColor(CColor color) { knob_->setFontColor(color); knob_->invalid(); }
CColor getKnobFontColor() const { return knob_->getFontColor(); }
using ValueToStringFunction = SStyledKnob::ValueToStringFunction;
void setValueToStringFunction(ValueToStringFunction f) { knob_->setValueToStringFunction(std::move(f)); knob_->invalid(); }
// Edit box listener
void viewLostFocus (CView* view) override;
bool isHD() const noexcept { return hdMode_; }
void setHDMode(bool mode);
protected:
CMouseEventResult onMouseDown(CPoint& where, const CButtonState& buttons) override;
private:
void updateViewSizes();
void updateViewColors();
private:
SharedPointer<CTextLabel> label_;
SharedPointer<CTextEdit> valueEdit_;
SharedPointer<SStyledKnob> knob_;
SharedPointer<CTextLabel> ccLabel_;
SharedPointer<CFilledRect> shadingRectangle_;
SharedPointer<CMenuItem> menuEntry_;
CRect nameLabelSize_;
CRect knobSize_;
CRect shadingRectangleSize_;
CRect ccLabelSize_;
CRect valueEditSize_;
CRect rectangleSize_;
float hue_ = 0.35;
class MenuListener : public IControlListener, public NonAtomicReferenceCounted {
public:
explicit MenuListener(SKnobCCBox& box) : box_(box) {}
void valueChanged(CControl*) override
{
box_.setHDMode(!box_.isHD());
}
private:
SKnobCCBox& box_;
};
SharedPointer<MenuListener> menuListener_;
bool hdMode_ { false };
};
///
@ -320,6 +383,9 @@ public:
void setCCLabelFont(CFontRef font);
void setCCLabelBackColor(CColor color);
void setCCLabelFontColor(CColor color);
void setValueEditBackColor(CColor color);
void setValueEditFontColor(CColor color);
void setShadingRectangleColor(CColor color);
void setKnobActiveTrackColor(CColor color);
void setKnobInactiveTrackColor(CColor color);
void setKnobLineIndicatorColor(CColor color);
@ -365,6 +431,47 @@ private:
SharedPointer<CVSTGUITimer> relayoutTrigger_;
};
///
class SLevelMeter : public CView {
public:
explicit SLevelMeter(const CRect& size);
float getValue() const { return value_; }
void setValue(float value);
float getDangerThreshold() const { return dangerThreshold_; }
void setDangerThreshold(float thres) { dangerThreshold_ = thres; invalid(); }
CColor getFrameColor() const { return frameColor_; }
void setFrameColor(CColor color) { frameColor_ = color; invalid(); }
CColor getBackColor() const { return backColor_; }
void setBackColor(CColor color) { backColor_ = color; invalid(); }
CColor getNormalFillColor() const { return safeFillColor_; }
void setNormalFillColor(CColor color) { safeFillColor_ = color; invalid(); }
CColor getDangerFillColor() const { return dangerFillColor_; }
void setDangerFillColor(CColor color) { dangerFillColor_ = color; invalid(); }
CCoord getRoundRectRadius() const { return radius_; }
void setRoundRectRadius(CCoord radius) { radius_ = radius; invalid(); }
protected:
void draw(CDrawContext* dc) override;
private:
float value_ = 0;
float dangerThreshold_ = 0.5;
float dbMin_ = -40;
float dbMax_ = 0;
CColor frameColor_;
CColor safeFillColor_;
CColor dangerFillColor_;
CColor backColor_;
CCoord radius_ = 5.0;
SharedPointer<CVSTGUITimer> timer_;
bool timerArmed_ = false;
};
///
class SPlaceHolder : public CView {
public:

View file

@ -5,6 +5,9 @@
// If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz
#include "GUIHelpers.h"
#include "utility/vstgui_before.h"
#include <vstgui/lib/events.h>
#include "utility/vstgui_after.h"
class SFrameDisabler::KeyAndMouseHook : public CBaseObject,
public IKeyboardHook,
@ -13,12 +16,10 @@ public:
void setEnabled(bool value) { enabled_ = value; }
protected:
int32_t onKeyDown(const VstKeyCode&, CFrame*) { return enabled_ ? -1 : 1; }
int32_t onKeyUp(const VstKeyCode&, CFrame*) { return enabled_ ? -1 : 1; }
void onMouseEntered(CView*, CFrame*) {}
void onMouseExited(CView*, CFrame*) {}
CMouseEventResult onMouseMoved(CFrame*, const CPoint&, const CButtonState&) { return enabled_ ? kMouseEventNotHandled : kMouseEventHandled; }
CMouseEventResult onMouseDown(CFrame*, const CPoint&, const CButtonState&) { return enabled_ ? kMouseEventNotHandled : kMouseEventHandled; }
void onKeyboardEvent(KeyboardEvent& event, CFrame* frame) override;
void onMouseEntered(CView* view, CFrame* frame) override {}
void onMouseExited(CView* view, CFrame* frame) override {}
void onMouseEvent(MouseEvent& event, CFrame* frame) override;
private:
bool enabled_ = true;
@ -51,3 +52,15 @@ void SFrameDisabler::disable()
hook_->setEnabled(false);
delayedEnabler_->stop();
}
void SFrameDisabler::KeyAndMouseHook::onKeyboardEvent(KeyboardEvent& event, CFrame* frame)
{
if (!enabled_)
event.consumed = true;
}
void SFrameDisabler::KeyAndMouseHook::onMouseEvent(MouseEvent& event, CFrame* frame)
{
if (!enabled_)
event.consumed = true;
}

View file

@ -164,7 +164,6 @@ void SPiano::draw(CDrawContext* dc)
const Dimensions dim = getDimensions(false);
const unsigned octs = impl.octs_;
const unsigned keyCount = octs * 12;
const bool allKeysUsed = impl.keyUsed_.all();
dc->setDrawMode(kAntiAliasing);
@ -184,14 +183,12 @@ void SPiano::draw(CDrawContext* dc)
switch (getKeyRole(key)) {
case KeyRole::Note:
if (allKeysUsed)
goto whiteKeyDefault;
hcy.h = impl.keyUsedHue_;
break;
case KeyRole::Switch:
hcy.h = impl.keySwitchHue_;
break;
default: whiteKeyDefault:
default:
hcy.y = 1.0;
if (impl.keyval_[key])
hcy.c = 0.0;
@ -224,14 +221,12 @@ void SPiano::draw(CDrawContext* dc)
switch (getKeyRole(key)) {
case KeyRole::Note:
if (allKeysUsed)
goto blackKeyDefault;
hcy.h = impl.keyUsedHue_;
break;
case KeyRole::Switch:
hcy.h = impl.keySwitchHue_;
break;
default: blackKeyDefault:
default:
hcy.c = 0.0;
break;
}

View file

@ -5,34 +5,13 @@
// If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz
#include "NativeHelpers.h"
#include "plugin/NativeHelpers.h"
#if defined(_WIN32)
#include "ghc/fs_std.hpp"
#include <windows.h>
#include <cstring>
static WCHAR *stringToWideChar(const char *str, int strCch = -1)
{
unsigned strSize = MultiByteToWideChar(CP_UTF8, 0, str, strCch, nullptr, 0);
if (strSize == 0)
return {};
std::unique_ptr<WCHAR[]> strW(new WCHAR[strSize]);
if (MultiByteToWideChar(CP_UTF8, 0, str, strCch, strW.get(), strSize) == 0)
return {};
return strW.release();
}
static char* stringToUTF8(const wchar_t *strW, int strWCch = -1)
{
unsigned strSize = WideCharToMultiByte(CP_UTF8, 0, strW, strWCch, nullptr, 0, nullptr, nullptr);
if (strSize == 0)
return {};
std::unique_ptr<char[]> str(new char[strSize]);
if (WideCharToMultiByte(CP_UTF8, 0, strW, strWCch, str.get(), strSize, nullptr, nullptr) == 0)
return {};
return str.release();
}
bool openFileInExternalEditor(const char *filename)
{
std::wstring path = stringToWideChar(filename);
@ -163,8 +142,9 @@ std::string getCurrentProcessName()
#include <sys/wait.h>
#include <sys/utsname.h>
#include <unistd.h>
#include <absl/strings/strip.h>
#include <absl/strings/string_view.h>
#include <vector>
#include <regex>
#include <fstream>
#include <cstring>
#include <cerrno>
@ -218,12 +198,21 @@ static std::vector<char *> createForkEnviron()
return newEnv;
}
static constexpr char zenityPath[] = "/usr/bin/zenity";
static const std::string zenityPath = [] {
auto glibPath = g_find_program_in_path("zenity");
if (glibPath) {
std::string s { glibPath };
g_free(glibPath);
return s;
} else {
return std::string("/usr/bin/zenity");
}
}();
bool askQuestion(const char *text)
{
char *argv[] = {
const_cast<char *>(zenityPath),
const_cast<char *>(zenityPath.c_str()),
const_cast<char *>("--question"),
const_cast<char *>("--text"),
const_cast<char *>(text),
@ -256,7 +245,7 @@ bool askQuestion(const char *text)
bool isZenityAvailable()
{
return access(zenityPath, X_OK) == 0;
return access(zenityPath.c_str(), X_OK) == 0;
}
std::string getOperatingSystemName()
@ -305,14 +294,22 @@ std::string getProcessorName()
std::string name;
std::string line;
std::ifstream in("/proc/cpuinfo", std::ios::binary);
std::regex re("^model name\\s*:\\s*(.*)");
line.reserve(256);
while (name.empty() && std::getline(in, line) && !line.empty()) {
std::smatch match;
if (std::regex_match(line, match, re))
name = match[1];
size_t pos = line.find(':');
if (pos == line.npos)
continue;
absl::string_view left = absl::string_view(line).substr(0, pos);
absl::string_view right = absl::string_view(line).substr(pos + 1);
left = absl::StripAsciiWhitespace(left);
right = absl::StripAsciiWhitespace(right);
if (left == "model name")
name = std::string(right);
}
if (name.empty())

View file

@ -24,11 +24,30 @@ static bool openFileWithApplication(const char *fileName, NSString *application)
bool openFileInExternalEditor(const char *fileName)
{
NSURL* applicationURL = (__bridge_transfer NSURL*)LSCopyDefaultApplicationURLForContentType(
kUTTypePlainText, kLSRolesEditor, nil);
if (!applicationURL || ![applicationURL isFileURL])
NSURL* appURL = nil;
NSURL* fileURL = [NSURL fileURLWithPath:[NSString stringWithUTF8String:fileName]];
const LSRolesMask roles = kLSRolesEditor;
NSArray* editorApps = (__bridge_transfer NSArray*)LSCopyApplicationURLsForURL(
(__bridge CFURLRef)fileURL, roles);
for (NSUInteger i = 0, n = [editorApps count]; i < n && !appURL; ++i) {
NSURL* url = [editorApps objectAtIndex:i];
if (url && [url isFileURL])
appURL = url;
}
if (!appURL) {
NSURL* url = (__bridge_transfer NSURL*)LSCopyDefaultApplicationURLForContentType(
kUTTypePlainText, roles, nil);
if (url && [url isFileURL])
appURL = url;
}
if (!appURL)
return false;
return openFileWithApplication(fileName, [applicationURL path]);
return openFileWithApplication(fileName, [appURL path]);
}
bool openDirectoryInExplorer(const char *fileName)

View file

@ -1,7 +1,7 @@
/* This file is generated by the layout maker tool. */
auto* const view__0 = createLogicalGroup(CRect(0, 0, 800, 475), -1, "", kCenterText, 14);
mainView = view__0;
auto* const view__1 = createBackground(CRect(190, 110, 790, 390), -1, "", kCenterText, 14);
auto* const view__1 = createBackground(CRect(5, 110, 795, 395), -1, "", kCenterText, 14);
imageContainer_ = view__1;
view__0->addView(view__1);
enterPalette(invertedPalette);
@ -11,185 +11,197 @@ auto* const view__3 = createRoundedGroup(CRect(5, 4, 180, 105), -1, "", kCenterT
view__2->addView(view__3);
auto* const view__4 = createAboutButton(CRect(27, 5, 147, 65), kTagAbout, "", kCenterText, 14);
view__3->addView(view__4);
auto* const view__5 = createHomeButton(CRect(31, 69, 63, 101), kTagFirstChangePanel+kPanelGeneral, "", kCenterText, 30);
panelButtons_[kPanelGeneral] = view__5;
auto* const view__5 = createInfoButton(CRect(51, 69, 83, 101), kTagFirstChangePanel+kPanelInfo, "", kCenterText, 30);
panelButtons_[kPanelInfo] = view__5;
view__3->addView(view__5);
auto* const view__6 = createCCButton(CRect(71, 69, 103, 101), kTagFirstChangePanel+kPanelControls, "", kCenterText, 30);
auto* const view__6 = createCCButton(CRect(92, 69, 124, 101), kTagFirstChangePanel+kPanelControls, "", kCenterText, 30);
panelButtons_[kPanelControls] = view__6;
view__3->addView(view__6);
auto* const view__7 = createSettingsButton(CRect(111, 69, 143, 101), kTagFirstChangePanel+kPanelSettings, "", kCenterText, 30);
auto* const view__7 = createSettingsButton(CRect(132, 69, 164, 101), kTagFirstChangePanel+kPanelSettings, "", kCenterText, 30);
panelButtons_[kPanelSettings] = view__7;
view__3->addView(view__7);
auto* const view__8 = createRoundedGroup(CRect(185, 5, 565, 105), -1, "", kCenterText, 14);
view__2->addView(view__8);
auto* const view__9 = createHLine(CRect(10, 36, 370, 41), -1, "", kCenterText, 14);
view__8->addView(view__9);
auto* const view__10 = createHLine(CRect(10, 68, 370, 73), -1, "", kCenterText, 14);
view__8->addView(view__10);
auto* const view__11 = createClickableLabel(CRect(10, 8, 260, 38), kTagLoadSfzFile, "DefaultInstrument.sfz", kLeftText, 20);
sfzFileLabel_ = view__11;
view__8->addView(view__11);
auto* const view__12 = createLabel(CRect(80, 40, 370, 70), -1, "", kLeftText, 20);
keyswitchLabel_ = view__12;
view__8->addView(view__12);
auto* const view__13 = createBadge(CRect(10, 42, 70, 68), -1, "", kCenterText, 20);
keyswitchBadge_ = view__13;
view__8->addView(view__13);
auto* const view__14 = createInactiveLabel(CRect(10, 40, 370, 70), -1, "No key switch", kLeftText, 20);
keyswitchInactiveLabel_ = view__14;
view__8->addView(view__14);
view__14->setVisible(false);
auto* const view__15 = createLabel(CRect(10, 73, 70, 98), -1, "Voices:", kRightText, 12);
view__8->addView(view__15);
auto* const view__16 = createPreviousFileButton(CRect(295, 13, 320, 38), kTagPreviousSfzFile, "", kCenterText, 24);
view__8->addView(view__16);
auto* const view__17 = createNextFileButton(CRect(320, 13, 345, 38), kTagNextSfzFile, "", kCenterText, 24);
view__8->addView(view__17);
auto* const view__18 = createChevronDropDown(CRect(345, 13, 370, 38), kTagFileOperations, "", kCenterText, 24);
fileOperationsMenu_ = view__18;
view__8->addView(view__18);
auto* const view__19 = createLabel(CRect(75, 73, 115, 98), -1, "", kCenterText, 12);
infoVoicesLabel_ = view__19;
view__8->addView(view__19);
auto* const view__20 = createLabel(CRect(130, 73, 170, 98), -1, "Max:", kRightText, 12);
view__8->addView(view__20);
auto* const view__21 = createLabel(CRect(175, 73, 210, 98), -1, "", kCenterText, 12);
numVoicesLabel_ = view__21;
view__8->addView(view__21);
auto* const view__22 = createLabel(CRect(240, 73, 300, 98), -1, "Memory:", kRightText, 12);
view__8->addView(view__22);
auto* const view__23 = createLabel(CRect(305, 73, 365, 98), -1, "", kCenterText, 12);
memoryLabel_ = view__23;
view__8->addView(view__23);
auto* const view__24 = createChevronValueDropDown(CRect(210, 77, 230, 97), kTagSetNumVoices, "", kCenterText, 16);
numVoicesSlider_ = view__24;
view__8->addView(view__24);
auto* const view__25 = createRoundedGroup(CRect(570, 5, 795, 105), -1, "", kCenterText, 14);
view__2->addView(view__25);
auto* const view__26 = createKnob48(CRect(45, 15, 93, 63), -1, "", kCenterText, 14);
view__25->addView(view__26);
view__26->setVisible(false);
auto* const view__27 = createValueLabel(CRect(40, 65, 100, 70), -1, "Center", kCenterText, 12);
view__25->addView(view__27);
auto* const view__8 = createHomeButton(CRect(11, 69, 43, 101), kTagFirstChangePanel+kPanelGeneral, "", kCenterText, 30);
panelButtons_[kPanelGeneral] = view__8;
view__3->addView(view__8);
auto* const view__9 = createRoundedGroup(CRect(185, 5, 603, 105), -1, "", kCenterText, 14);
view__2->addView(view__9);
auto* const view__10 = createHLine(CRect(10, 36, 405, 41), -1, "", kCenterText, 14);
view__9->addView(view__10);
auto* const view__11 = createHLine(CRect(10, 68, 405, 73), -1, "", kCenterText, 14);
view__9->addView(view__11);
auto* const view__12 = createClickableLabel(CRect(10, 8, 330, 38), kTagLoadSfzFile, "DefaultInstrument.sfz", kLeftText, 20);
sfzFileLabel_ = view__12;
view__9->addView(view__12);
auto* const view__13 = createLabel(CRect(80, 40, 405, 75), -1, "", kLeftText, 20);
keyswitchLabel_ = view__13;
view__9->addView(view__13);
auto* const view__14 = createBadge(CRect(10, 42, 70, 68), -1, "", kCenterText, 20);
keyswitchBadge_ = view__14;
view__9->addView(view__14);
auto* const view__15 = createInactiveLabel(CRect(10, 40, 370, 70), -1, "No key switch", kLeftText, 20);
keyswitchInactiveLabel_ = view__15;
view__9->addView(view__15);
view__15->setVisible(false);
auto* const view__16 = createLabel(CRect(10, 73, 70, 98), -1, "Voices:", kRightText, 12);
view__9->addView(view__16);
auto* const view__17 = createPreviousFileButton(CRect(330, 13, 355, 38), kTagPreviousSfzFile, "", kCenterText, 24);
view__9->addView(view__17);
auto* const view__18 = createNextFileButton(CRect(355, 13, 380, 38), kTagNextSfzFile, "", kCenterText, 24);
view__9->addView(view__18);
auto* const view__19 = createChevronDropDown(CRect(380, 13, 405, 38), kTagFileOperations, "", kCenterText, 24);
fileOperationsMenu_ = view__19;
view__9->addView(view__19);
auto* const view__20 = createLabel(CRect(75, 73, 115, 98), -1, "", kCenterText, 12);
infoVoicesLabel_ = view__20;
view__9->addView(view__20);
auto* const view__21 = createLabel(CRect(147, 73, 187, 98), -1, "Max:", kRightText, 12);
view__9->addView(view__21);
auto* const view__22 = createLabel(CRect(193, 73, 228, 98), -1, "", kCenterText, 12);
numVoicesLabel_ = view__22;
view__9->addView(view__22);
auto* const view__23 = createLabel(CRect(274, 73, 334, 98), -1, "Memory:", kRightText, 12);
view__9->addView(view__23);
auto* const view__24 = createLabel(CRect(340, 73, 400, 98), -1, "", kCenterText, 12);
memoryLabel_ = view__24;
view__9->addView(view__24);
auto* const view__25 = createChevronValueDropDown(CRect(235, 77, 255, 97), kTagSetNumVoices, "", kCenterText, 16);
numVoicesSlider_ = view__25;
view__9->addView(view__25);
auto* const view__26 = createRoundedGroup(CRect(608, 5, 795, 105), -1, "", kCenterText, 14);
view__2->addView(view__26);
auto* const view__27 = createKnob48(CRect(7, 15, 55, 63), -1, "", kCenterText, 14);
view__26->addView(view__27);
view__27->setVisible(false);
auto* const view__28 = createVMeter(CRect(175, 15, 210, 70), -1, "", kCenterText, 14);
view__25->addView(view__28);
auto* const view__29 = createKnobCCBox(CRect(10, 5, 80, 95), kTagSetCCVolume, "Volume", kCenterText, 12);
auto* const view__28 = createValueLabel(CRect(2, 65, 62, 70), -1, "Center", kCenterText, 12);
view__26->addView(view__28);
view__28->setVisible(false);
auto* const view__29 = createKnobCCBox(CRect(6, 5, 76, 95), kTagSetCCVolume, "Volume", kCenterText, 12);
volumeCCKnob_ = view__29;
view__25->addView(view__29);
auto* const view__30 = createKnobCCBox(CRect(85, 5, 155, 95), kTagSetCCPan, "Pan", kCenterText, 12);
view__26->addView(view__29);
auto* const view__30 = createKnobCCBox(CRect(83, 5, 153, 95), kTagSetCCPan, "Pan", kCenterText, 12);
panCCKnob_ = view__30;
view__25->addView(view__30);
view__26->addView(view__30);
auto* const view__31 = createVMeter(CRect(159, 5, 168, 95), -1, "", kCenterText, 14);
leftMeter_ = view__31;
view__26->addView(view__31);
auto* const view__32 = createVMeter(CRect(171, 5, 180, 95), -1, "", kCenterText, 14);
rightMeter_ = view__32;
view__26->addView(view__32);
enterPalette(defaultPalette);
auto* const view__31 = createLogicalGroup(CRect(5, 110, 796, 395), -1, "", kCenterText, 14);
subPanels_[kPanelGeneral] = view__31;
view__0->addView(view__31);
view__31->setVisible(false);
auto* const view__32 = createRoundedGroup(CRect(0, 0, 175, 280), -1, "", kCenterText, 14);
view__31->addView(view__32);
auto* const view__33 = createLabel(CRect(15, 10, 75, 35), -1, "Curves:", kLeftText, 14);
view__32->addView(view__33);
auto* const view__34 = createLabel(CRect(15, 35, 75, 60), -1, "Masters:", kLeftText, 14);
view__32->addView(view__34);
auto* const view__35 = createLabel(CRect(15, 60, 75, 85), -1, "Groups:", kLeftText, 14);
view__32->addView(view__35);
auto* const view__36 = createLabel(CRect(15, 85, 75, 110), -1, "Regions:", kLeftText, 14);
view__32->addView(view__36);
auto* const view__37 = createLabel(CRect(15, 110, 75, 135), -1, "Samples:", kLeftText, 14);
view__32->addView(view__37);
auto* const view__38 = createLabel(CRect(115, 10, 155, 35), -1, "0", kCenterText, 14);
infoCurvesLabel_ = view__38;
view__32->addView(view__38);
auto* const view__39 = createLabel(CRect(115, 35, 155, 60), -1, "0", kCenterText, 14);
infoMastersLabel_ = view__39;
view__32->addView(view__39);
auto* const view__40 = createLabel(CRect(115, 60, 155, 85), -1, "0", kCenterText, 14);
infoGroupsLabel_ = view__40;
view__32->addView(view__40);
auto* const view__41 = createLabel(CRect(115, 85, 155, 110), -1, "0", kCenterText, 14);
infoRegionsLabel_ = view__41;
view__32->addView(view__41);
auto* const view__42 = createLabel(CRect(115, 110, 155, 135), -1, "0", kCenterText, 14);
infoSamplesLabel_ = view__42;
view__32->addView(view__42);
auto* const view__43 = createLogicalGroup(CRect(5, 110, 795, 395), -1, "", kCenterText, 14);
subPanels_[kPanelControls] = view__43;
view__0->addView(view__43);
view__43->setVisible(false);
auto* const view__44 = createRoundedGroup(CRect(0, 0, 790, 285), -1, "", kCenterText, 14);
view__43->addView(view__44);
auto* const view__45 = createControlsPanel(CRect(0, 0, 790, 285), -1, "", kCenterText, 12);
controlsPanel_ = view__45;
view__44->addView(view__45);
auto* const view__46 = createLogicalGroup(CRect(5, 109, 795, 425), -1, "", kCenterText, 14);
subPanels_[kPanelSettings] = view__46;
view__0->addView(view__46);
auto* const view__47 = createTitleGroup(CRect(300, 26, 495, 126), -1, "Engine", kCenterText, 12);
auto* const view__33 = createLogicalGroup(CRect(5, 110, 795, 395), -1, "", kCenterText, 14);
subPanels_[kPanelInfo] = view__33;
view__0->addView(view__33);
view__33->setVisible(false);
auto* const view__34 = createRoundedGroup(CRect(0, 0, 790, 285), -1, "", kCenterText, 14);
view__33->addView(view__34);
auto* const view__35 = createLabel(CRect(15, 10, 75, 35), -1, "Curves:", kLeftText, 14);
view__34->addView(view__35);
auto* const view__36 = createLabel(CRect(15, 35, 75, 60), -1, "Masters:", kLeftText, 14);
view__34->addView(view__36);
auto* const view__37 = createLabel(CRect(15, 60, 75, 85), -1, "Groups:", kLeftText, 14);
view__34->addView(view__37);
auto* const view__38 = createLabel(CRect(15, 85, 75, 110), -1, "Regions:", kLeftText, 14);
view__34->addView(view__38);
auto* const view__39 = createLabel(CRect(15, 110, 75, 135), -1, "Samples:", kLeftText, 14);
view__34->addView(view__39);
auto* const view__40 = createLabel(CRect(115, 10, 155, 35), -1, "0", kCenterText, 14);
infoCurvesLabel_ = view__40;
view__34->addView(view__40);
auto* const view__41 = createLabel(CRect(115, 35, 155, 60), -1, "0", kCenterText, 14);
infoMastersLabel_ = view__41;
view__34->addView(view__41);
auto* const view__42 = createLabel(CRect(115, 60, 155, 85), -1, "0", kCenterText, 14);
infoGroupsLabel_ = view__42;
view__34->addView(view__42);
auto* const view__43 = createLabel(CRect(115, 85, 155, 110), -1, "0", kCenterText, 14);
infoRegionsLabel_ = view__43;
view__34->addView(view__43);
auto* const view__44 = createLabel(CRect(115, 110, 155, 135), -1, "0", kCenterText, 14);
infoSamplesLabel_ = view__44;
view__34->addView(view__44);
auto* const view__45 = createLogicalGroup(CRect(5, 110, 795, 395), -1, "", kCenterText, 14);
subPanels_[kPanelControls] = view__45;
view__0->addView(view__45);
view__45->setVisible(false);
auto* const view__46 = createRoundedGroup(CRect(0, 0, 790, 285), -1, "", kCenterText, 14);
view__45->addView(view__46);
auto* const view__47 = createControlsPanel(CRect(0, 0, 790, 285), -1, "", kCenterText, 12);
controlsPanel_ = view__47;
view__46->addView(view__47);
auto* const view__48 = createValueMenu(CRect(25, 60, 85, 85), kTagSetOversampling, "", kCenterText, 12);
oversamplingSlider_ = view__48;
view__47->addView(view__48);
auto* const view__49 = createValueLabel(CRect(15, 20, 95, 45), -1, "Oversampling", kCenterText, 12);
view__47->addView(view__49);
auto* const view__50 = createValueLabel(CRect(100, 20, 180, 45), -1, "Preload size", kCenterText, 12);
view__47->addView(view__50);
auto* const view__51 = createValueMenu(CRect(110, 60, 170, 85), kTagSetPreloadSize, "", kCenterText, 12);
preloadSizeSlider_ = view__51;
view__47->addView(view__51);
auto* const view__52 = createTitleGroup(CRect(170, 161, 585, 261), -1, "Tuning", kCenterText, 12);
view__46->addView(view__52);
auto* const view__53 = createValueLabel(CRect(155, 20, 235, 45), -1, "Root key", kCenterText, 12);
view__52->addView(view__53);
auto* const view__54 = createValueMenu(CRect(250, 60, 310, 85), kTagSetTuningFrequency, "", kCenterText, 12);
tuningFrequencySlider_ = view__54;
view__52->addView(view__54);
auto* const view__55 = createValueLabel(CRect(240, 20, 320, 45), -1, "Frequency", kCenterText, 12);
view__52->addView(view__55);
auto* const view__56 = createStyledKnob(CRect(340, 45, 388, 93), kTagSetStretchedTuning, "", kCenterText, 14);
stretchedTuningSlider_ = view__56;
view__52->addView(view__56);
auto* const view__57 = createValueLabel(CRect(325, 20, 405, 45), -1, "Stretch", kCenterText, 12);
view__52->addView(view__57);
auto* const view__58 = createValueLabel(CRect(20, 20, 120, 45), -1, "Scala file", kCenterText, 12);
view__52->addView(view__58);
auto* const view__59 = createValueButton(CRect(20, 60, 120, 85), kTagLoadScalaFile, "DefaultScale", kCenterText, 12);
scalaFileButton_ = view__59;
view__52->addView(view__59);
auto* const view__60 = createValueMenu(CRect(165, 60, 200, 85), kTagSetScalaRootKey, "", kCenterText, 12);
scalaRootKeySlider_ = view__60;
view__52->addView(view__60);
auto* const view__61 = createValueMenu(CRect(200, 60, 230, 85), kTagSetScalaRootKey, "", kCenterText, 12);
scalaRootOctaveSlider_ = view__61;
view__52->addView(view__61);
auto* const view__62 = createResetSomethingButton(CRect(120, 60, 145, 85), kTagResetScalaFile, "", kCenterText, 12);
scalaResetButton_ = view__62;
view__52->addView(view__62);
auto* const view__63 = createTitleGroup(CRect(615, 161, 754, 261), -1, "Files", kCenterText, 12);
userFilesGroup_ = view__63;
view__46->addView(view__63);
auto* const view__64 = createValueLabel(CRect(20, 20, 120, 45), -1, "User SFZ folder", kCenterText, 12);
view__63->addView(view__64);
auto* const view__65 = createValueButton(CRect(20, 60, 120, 85), kTagChooseUserFilesDir, "DefaultPath", kCenterText, 12);
userFilesDirButton_ = view__65;
view__63->addView(view__65);
auto* const view__66 = createTitleGroup(CRect(525, 26, 720, 126), -1, "Quality", kCenterText, 12);
view__46->addView(view__66);
auto* const view__67 = createValueMenu(CRect(15, 60, 95, 85), kTagSetSampleQuality, "", kCenterText, 12);
sampleQualitySlider_ = view__67;
view__66->addView(view__67);
auto* const view__68 = createValueLabel(CRect(15, 20, 95, 45), -1, "Sample", kCenterText, 12);
view__66->addView(view__68);
auto* const view__69 = createValueLabel(CRect(100, 20, 180, 45), -1, "Oscillator", kCenterText, 12);
view__66->addView(view__69);
auto* const view__70 = createValueMenu(CRect(100, 60, 180, 85), kTagSetOscillatorQuality, "", kCenterText, 12);
oscillatorQualitySlider_ = view__70;
view__66->addView(view__70);
auto* const view__71 = createTitleGroup(CRect(35, 161, 140, 261), -1, "Theme", kCenterText, 12);
view__46->addView(view__71);
view__71->setVisible(false);
auto* const view__72 = createOptionMenu(CRect(20, 60, 85, 85), kTagThemeMenu, "", kCenterText, 12);
themeMenu_ = view__72;
view__71->addView(view__72);
auto* const view__73 = createPiano(CRect(5, 400, 795, 470), -1, "", kCenterText, 12);
piano_ = view__73;
view__0->addView(view__73);
auto* const view__48 = createLogicalGroup(CRect(5, 110, 795, 395), -1, "", kCenterText, 14);
subPanels_[kPanelSettings] = view__48;
view__0->addView(view__48);
auto* const view__49 = createTitleGroup(CRect(300, 25, 495, 125), -1, "Engine", kCenterText, 12);
view__48->addView(view__49);
auto* const view__50 = createValueMenu(CRect(25, 60, 85, 85), kTagSetOversampling, "", kCenterText, 12);
oversamplingSlider_ = view__50;
view__49->addView(view__50);
auto* const view__51 = createValueLabel(CRect(15, 20, 95, 45), -1, "Oversampling", kCenterText, 12);
view__49->addView(view__51);
auto* const view__52 = createValueLabel(CRect(100, 20, 180, 45), -1, "Preload size", kCenterText, 12);
view__49->addView(view__52);
auto* const view__53 = createValueMenu(CRect(110, 60, 170, 85), kTagSetPreloadSize, "", kCenterText, 12);
preloadSizeSlider_ = view__53;
view__49->addView(view__53);
auto* const view__54 = createTitleGroup(CRect(170, 160, 585, 260), -1, "Tuning", kCenterText, 12);
view__48->addView(view__54);
auto* const view__55 = createValueLabel(CRect(155, 20, 235, 45), -1, "Root key", kCenterText, 12);
view__54->addView(view__55);
auto* const view__56 = createValueMenu(CRect(250, 60, 310, 85), kTagSetTuningFrequency, "", kCenterText, 12);
tuningFrequencySlider_ = view__56;
view__54->addView(view__56);
auto* const view__57 = createValueLabel(CRect(240, 20, 320, 45), -1, "Frequency", kCenterText, 12);
view__54->addView(view__57);
auto* const view__58 = createStyledKnob(CRect(340, 45, 388, 93), kTagSetStretchedTuning, "", kCenterText, 14);
stretchedTuningSlider_ = view__58;
view__54->addView(view__58);
auto* const view__59 = createValueLabel(CRect(325, 20, 405, 45), -1, "Stretch", kCenterText, 12);
view__54->addView(view__59);
auto* const view__60 = createValueLabel(CRect(20, 20, 120, 45), -1, "Scala file", kCenterText, 12);
view__54->addView(view__60);
auto* const view__61 = createValueButton(CRect(20, 60, 120, 85), kTagLoadScalaFile, "DefaultScale", kCenterText, 12);
scalaFileButton_ = view__61;
view__54->addView(view__61);
auto* const view__62 = createValueMenu(CRect(165, 60, 200, 85), kTagSetScalaRootKey, "", kCenterText, 12);
scalaRootKeySlider_ = view__62;
view__54->addView(view__62);
auto* const view__63 = createValueMenu(CRect(200, 60, 230, 85), kTagSetScalaRootKey, "", kCenterText, 12);
scalaRootOctaveSlider_ = view__63;
view__54->addView(view__63);
auto* const view__64 = createResetSomethingButton(CRect(120, 60, 145, 85), kTagResetScalaFile, "", kCenterText, 12);
scalaResetButton_ = view__64;
view__54->addView(view__64);
auto* const view__65 = createTitleGroup(CRect(615, 160, 754, 260), -1, "Files", kCenterText, 12);
userFilesGroup_ = view__65;
view__48->addView(view__65);
auto* const view__66 = createValueLabel(CRect(20, 20, 120, 45), -1, "User SFZ folder", kCenterText, 12);
view__65->addView(view__66);
auto* const view__67 = createValueButton(CRect(20, 60, 120, 85), kTagChooseUserFilesDir, "DefaultPath", kCenterText, 12);
userFilesDirButton_ = view__67;
view__65->addView(view__67);
auto* const view__68 = createTitleGroup(CRect(525, 25, 720, 125), -1, "Quality", kCenterText, 12);
view__48->addView(view__68);
auto* const view__69 = createValueMenu(CRect(15, 60, 95, 85), kTagSetSampleQuality, "", kCenterText, 12);
sampleQualitySlider_ = view__69;
view__68->addView(view__69);
auto* const view__70 = createValueLabel(CRect(15, 20, 95, 45), -1, "Sample", kCenterText, 12);
view__68->addView(view__70);
auto* const view__71 = createValueLabel(CRect(100, 20, 180, 45), -1, "Oscillator", kCenterText, 12);
view__68->addView(view__71);
auto* const view__72 = createValueMenu(CRect(100, 60, 180, 85), kTagSetOscillatorQuality, "", kCenterText, 12);
oscillatorQualitySlider_ = view__72;
view__68->addView(view__72);
auto* const view__73 = createTitleGroup(CRect(35, 160, 140, 260), -1, "Appearance", kCenterText, 12);
view__48->addView(view__73);
auto* const view__74 = createOptionMenu(CRect(20, 60, 85, 85), kTagThemeMenu, "", kCenterText, 12);
themeMenu_ = view__74;
view__73->addView(view__74);
auto* const view__75 = createValueLabel(CRect(10, 25, 90, 50), -1, "Theme", kCenterText, 12);
view__73->addView(view__75);
auto* const view__76 = createPiano(CRect(5, 400, 795, 470), -1, "", kCenterText, 12);
piano_ = view__76;
view__0->addView(view__76);
auto* const view__77 = createLogicalGroup(CRect(5, 110, 795, 395), -1, "", kCenterText, 14);
subPanels_[kPanelGeneral] = view__77;
view__0->addView(view__77);
view__77->setVisible(false);

View file

@ -61,6 +61,12 @@ if(SFIZZ_LV2_UI)
endif()
endif()
# Define a preprocessor variable to indicate a build with UI enabled
if(SFIZZ_LV2_UI)
target_compile_definitions(${LV2PLUGIN_PRJ_NAME} PRIVATE "SFIZZ_LV2_UI=1")
target_compile_definitions(${LV2PLUGIN_PRJ_NAME}_ui PRIVATE "SFIZZ_LV2_UI=1")
endif()
# Remove the "lib" prefix, rename the target name and build it in the .lv build dir
# <build_dir>/lv2/<plugin_name>_lv2.<ext> to
# <build_dir>/lv2/<plugin_name>.lv2/<plugin_name>.<ext>
@ -83,9 +89,6 @@ if(SFIZZ_LV2_UI)
configure_file(${PROJECT_NAME}_ui.ttl.in ${PROJECT_BINARY_DIR}/${PROJECT_NAME}_ui.ttl)
endif()
configure_file(LICENSE.md.in ${PROJECT_BINARY_DIR}/LICENSE.md)
if(SFIZZ_USE_VCPKG OR SFIZZ_STATIC_DEPENDENCIES OR CMAKE_CXX_COMPILER_ID MATCHES "MSVC")
file(COPY "lgpl-3.0.txt" DESTINATION ${PROJECT_BINARY_DIR})
endif()
# Generate controllers.ttl
sfizz_lv2_generate_controllers_ttl("${PROJECT_BINARY_DIR}/controllers.ttl")
@ -114,7 +117,8 @@ endif()
# Installation
if(NOT MSVC)
install(DIRECTORY ${PROJECT_BINARY_DIR} DESTINATION ${LV2PLUGIN_INSTALL_DIR}
COMPONENT "lv2")
COMPONENT "lv2"
USE_SOURCE_PERMISSIONS)
bundle_dylibs(lv2
"${LV2PLUGIN_INSTALL_DIR}/${PROJECT_NAME}.lv2/Contents/Binary/sfizz.so"
COMPONENT "lv2")

View file

@ -1,19 +1,12 @@
The `sfizz` LV2 plugin is able to be built statically against all its components,
which is a desirable property for audio plugin hosts. These components include
`libsndfile` from Erik de Castro Lopo (http://www.mega-nerd.com/libsndfile).
`libsndfile` is distributed under the terms of the LGPL v2 or v3. As such,
statically-linked LV2 version of `sfizz` are to be distributed under the terms
of the LGPL-v3.0 license. You should be able to rebuild from source the sfizz LV2
plugin using any `libsndfile` version you wish to use.
See the file `lgpl-3.0.txt` or (https://www.gnu.org/licenses/lgpl-3.0.txt) for more
information about the LGPL v3 license.
Most of the code in the plug comes from various examples in the LV2 distribution and
example plugins. As such, the source code of the LV2 plugin version of `sfizz` is
distributed under the same terms as the LV2 specification, which is the ISC license.
Binaries of this LV2 plugin dynamically linked against e.g. a system installation
of `libsndfile` thus have to respect the terms of the ISC license and BSD 2-clause
license. Check the LICENSE.md at (@LV2PLUGIN_REPOSITORY@) file for more
Check the LICENSE.md at (@LV2PLUGIN_REPOSITORY@) file for more
information about the license of the source code of the `sfizz` library and its
contributors.
The `sfizz` LV2 plugin can be optionally statically built against `libsndfile`
from Erik de Castro Lopo (http://www.mega-nerd.com/libsndfile). `libsndfile`
is distributed under the terms of the LGPL v2 or v3. If you distribute a
statically-linked LV2 version of `sfizz` built against `libsndfile`, you need
to respect the terms of the LGPL v2 or v3 license.

View file

@ -1,165 +0,0 @@
GNU LESSER GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
This version of the GNU Lesser General Public License incorporates
the terms and conditions of version 3 of the GNU General Public
License, supplemented by the additional permissions listed below.
0. Additional Definitions.
As used herein, "this License" refers to version 3 of the GNU Lesser
General Public License, and the "GNU GPL" refers to version 3 of the GNU
General Public License.
"The Library" refers to a covered work governed by this License,
other than an Application or a Combined Work as defined below.
An "Application" is any work that makes use of an interface provided
by the Library, but which is not otherwise based on the Library.
Defining a subclass of a class defined by the Library is deemed a mode
of using an interface provided by the Library.
A "Combined Work" is a work produced by combining or linking an
Application with the Library. The particular version of the Library
with which the Combined Work was made is also called the "Linked
Version".
The "Minimal Corresponding Source" for a Combined Work means the
Corresponding Source for the Combined Work, excluding any source code
for portions of the Combined Work that, considered in isolation, are
based on the Application, and not on the Linked Version.
The "Corresponding Application Code" for a Combined Work means the
object code and/or source code for the Application, including any data
and utility programs needed for reproducing the Combined Work from the
Application, but excluding the System Libraries of the Combined Work.
1. Exception to Section 3 of the GNU GPL.
You may convey a covered work under sections 3 and 4 of this License
without being bound by section 3 of the GNU GPL.
2. Conveying Modified Versions.
If you modify a copy of the Library, and, in your modifications, a
facility refers to a function or data to be supplied by an Application
that uses the facility (other than as an argument passed when the
facility is invoked), then you may convey a copy of the modified
version:
a) under this License, provided that you make a good faith effort to
ensure that, in the event an Application does not supply the
function or data, the facility still operates, and performs
whatever part of its purpose remains meaningful, or
b) under the GNU GPL, with none of the additional permissions of
this License applicable to that copy.
3. Object Code Incorporating Material from Library Header Files.
The object code form of an Application may incorporate material from
a header file that is part of the Library. You may convey such object
code under terms of your choice, provided that, if the incorporated
material is not limited to numerical parameters, data structure
layouts and accessors, or small macros, inline functions and templates
(ten or fewer lines in length), you do both of the following:
a) Give prominent notice with each copy of the object code that the
Library is used in it and that the Library and its use are
covered by this License.
b) Accompany the object code with a copy of the GNU GPL and this license
document.
4. Combined Works.
You may convey a Combined Work under terms of your choice that,
taken together, effectively do not restrict modification of the
portions of the Library contained in the Combined Work and reverse
engineering for debugging such modifications, if you also do each of
the following:
a) Give prominent notice with each copy of the Combined Work that
the Library is used in it and that the Library and its use are
covered by this License.
b) Accompany the Combined Work with a copy of the GNU GPL and this license
document.
c) For a Combined Work that displays copyright notices during
execution, include the copyright notice for the Library among
these notices, as well as a reference directing the user to the
copies of the GNU GPL and this license document.
d) Do one of the following:
0) Convey the Minimal Corresponding Source under the terms of this
License, and the Corresponding Application Code in a form
suitable for, and under terms that permit, the user to
recombine or relink the Application with a modified version of
the Linked Version to produce a modified Combined Work, in the
manner specified by section 6 of the GNU GPL for conveying
Corresponding Source.
1) Use a suitable shared library mechanism for linking with the
Library. A suitable mechanism is one that (a) uses at run time
a copy of the Library already present on the user's computer
system, and (b) will operate properly with a modified version
of the Library that is interface-compatible with the Linked
Version.
e) Provide Installation Information, but only if you would otherwise
be required to provide such information under section 6 of the
GNU GPL, and only to the extent that such information is
necessary to install and execute a modified version of the
Combined Work produced by recombining or relinking the
Application with a modified version of the Linked Version. (If
you use option 4d0, the Installation Information must accompany
the Minimal Corresponding Source and Corresponding Application
Code. If you use option 4d1, you must provide the Installation
Information in the manner specified by section 6 of the GNU GPL
for conveying Corresponding Source.)
5. Combined Libraries.
You may place library facilities that are a work based on the
Library side by side in a single library together with other library
facilities that are not Applications and are not covered by this
License, and convey such a combined library under terms of your
choice, if you do both of the following:
a) Accompany the combined library with a copy of the same work based
on the Library, uncombined with any other library facilities,
conveyed under the terms of this License.
b) Give prominent notice with the combined library that part of it
is a work based on the Library, and explaining where to find the
accompanying uncombined form of the same work.
6. Revised Versions of the GNU Lesser General Public License.
The Free Software Foundation may publish revised and/or new versions
of the GNU Lesser General Public License from time to time. Such new
versions will be similar in spirit to the present version, but may
differ in detail to address new problems or concerns.
Each version is given a distinguishing version number. If the
Library as you received it specifies that a certain numbered version
of the GNU Lesser General Public License "or any later version"
applies to it, you have the option of following the terms and
conditions either of that published version or of any later version
published by the Free Software Foundation. If the Library as you
received it does not specify a version number of the GNU Lesser
General Public License, you may choose any version of the GNU Lesser
General Public License ever published by the Free Software Foundation.
If the Library as you received it specifies that a proxy can decide
whether future versions of the GNU Lesser General Public License shall
apply, that proxy's public statement of acceptance of any version is
permanent authorization for you to choose that version for the
Library.

View file

@ -35,7 +35,7 @@
#include "sfizz_lv2.h"
#include "sfizz_lv2_plugin.h"
#include "sfizz/import/ForeignInstrument.h"
#include "sfizz/import/sfizz_import.h"
#include "plugin/InstrumentDescription.h"
#include <math.h>
@ -120,10 +120,12 @@ sfizz_lv2_map_required_uris(sfizz_plugin_t *self)
self->sfizz_num_voices_uri = map->map(map->handle, SFIZZ__numVoices);
self->sfizz_preload_size_uri = map->map(map->handle, SFIZZ__preloadSize);
self->sfizz_oversampling_uri = map->map(map->handle, SFIZZ__oversampling);
self->sfizz_log_status_uri = map->map(map->handle, SFIZZ__logStatus);
self->sfizz_last_keyswitch_uri = map->map(map->handle, SFIZZ__lastKeyswitch);
self->sfizz_log_status_uri = map->map(map->handle, SFIZZ__logStatus);
self->sfizz_check_modification_uri = map->map(map->handle, SFIZZ__checkModification);
self->sfizz_osc_blob_uri = map->map(map->handle, SFIZZ__OSCBlob);
self->sfizz_notify_uri = map->map(map->handle, SFIZZ__Notify);
self->sfizz_audio_level_uri = map->map(map->handle, SFIZZ__AudioLevel);
self->time_position_uri = map->map(map->handle, LV2_TIME__Position);
self->time_bar_uri = map->map(map->handle, LV2_TIME__bar);
self->time_bar_beat_uri = map->map(map->handle, LV2_TIME__barBeat);
@ -200,9 +202,6 @@ connect_port(LV2_Handle instance,
case SFIZZ_CONTROL:
self->control_port = (const LV2_Atom_Sequence *)data;
break;
case SFIZZ_NOTIFY:
self->notify_port = (LV2_Atom_Sequence *)data;
break;
case SFIZZ_AUTOMATE:
self->automate_port = (LV2_Atom_Sequence *)data;
break;
@ -325,13 +324,21 @@ sfizz_lv2_receive_message(void* data, int delay, const char* path, const char* s
sfizz_plugin_t *self = (sfizz_plugin_t *)data;
if (!strcmp(path, "/sw/last/current") && sig)
{
if (sig[0] == 'i')
self->last_keyswitch = args[0].i;
else if (sig[0] == 'N')
self->last_keyswitch = -1;
}
// transmit to UI as OSC blob
uint8_t *osc_temp = self->osc_temp;
uint32_t osc_size = sfizz_prepare_message(osc_temp, OSC_TEMP_SIZE, path, sig, args);
if (osc_size > OSC_TEMP_SIZE)
return;
LV2_Atom_Forge* forge = &self->forge_notify;
LV2_Atom_Forge* forge = &self->forge_automate;
bool write_ok =
lv2_atom_forge_frame_time(forge, 0) &&
lv2_atom_forge_atom(forge, osc_size, self->sfizz_osc_blob_uri) &&
@ -439,7 +446,6 @@ instantiate(const LV2_Descriptor *descriptor,
sfizz_lv2_map_required_uris(self);
// Initialize the forge
lv2_atom_forge_init(&self->forge_notify, self->map);
lv2_atom_forge_init(&self->forge_automate, self->map);
lv2_atom_forge_init(&self->forge_secondary, self->map);
@ -478,7 +484,7 @@ instantiate(const LV2_Descriptor *descriptor,
else
{
lv2_log_warning(&self->logger,
"No option array was given upon instantiation; will use default values\n.");
"No option array was given upon instantiation; will use default values.\n");
}
// We need _some_ information on the block size
@ -532,6 +538,9 @@ activate(LV2_Handle instance)
sfizz_set_samples_per_block(self->synth, self->max_block_size);
sfizz_set_sample_rate(self->synth, self->sample_rate);
self->must_update_midnam.store(1);
#if defined(SFIZZ_LV2_UI)
self->rms_follower.init(self->sample_rate);
#endif
}
static void
@ -542,13 +551,14 @@ deactivate(LV2_Handle instance)
}
static void
sfizz_lv2_send_file_path(sfizz_plugin_t *self, LV2_Atom_Forge* forge, LV2_URID urid, const char *path)
sfizz_lv2_send_file_path(sfizz_plugin_t *self, LV2_URID verb_uri, LV2_URID urid, const char *path)
{
LV2_Atom_Forge_Frame frame;
LV2_Atom_Forge* forge = &self->forge_automate;
bool write_ok =
lv2_atom_forge_frame_time(forge, 0) &&
lv2_atom_forge_object(forge, &frame, 0, self->patch_set_uri) &&
lv2_atom_forge_object(forge, &frame, 0, verb_uri) &&
lv2_atom_forge_key(forge, self->patch_property_uri) &&
lv2_atom_forge_urid(forge, urid) &&
lv2_atom_forge_key(forge, self->patch_value_uri) &&
@ -559,14 +569,15 @@ sfizz_lv2_send_file_path(sfizz_plugin_t *self, LV2_Atom_Forge* forge, LV2_URID u
}
static void
sfizz_lv2_send_controller(sfizz_plugin_t *self, LV2_Atom_Forge* forge, unsigned cc, float value)
sfizz_lv2_send_controller(sfizz_plugin_t *self, LV2_URID verb_uri, unsigned cc, float value)
{
LV2_URID urid = sfizz_lv2_ccmap_map(self->ccmap, int(cc));
LV2_Atom_Forge_Frame frame;
LV2_Atom_Forge* forge = &self->forge_automate;
bool write_ok =
lv2_atom_forge_frame_time(forge, 0) &&
lv2_atom_forge_object(forge, &frame, 0, self->patch_set_uri) &&
lv2_atom_forge_object(forge, &frame, 0, verb_uri) &&
lv2_atom_forge_key(forge, self->patch_property_uri) &&
lv2_atom_forge_urid(forge, urid) &&
lv2_atom_forge_key(forge, self->patch_value_uri) &&
@ -576,6 +587,31 @@ sfizz_lv2_send_controller(sfizz_plugin_t *self, LV2_Atom_Forge* forge, unsigned
lv2_atom_forge_pop(forge, &frame);
}
#if defined(SFIZZ_LV2_UI)
static void
sfizz_lv2_send_levels(sfizz_plugin_t *self, float left, float right)
{
const float levels[] = {left, right};
uint32_t num_levels = sizeof(levels) / sizeof(levels[0]);
LV2_Atom_Forge* forge = &self->forge_automate;
bool write_ok = lv2_atom_forge_frame_time(forge, 0);
LV2_Atom_Vector *vector = nullptr;
if (write_ok) {
LV2_Atom_Forge_Ref ref = lv2_atom_forge_vector(forge, sizeof(float), self->atom_float_uri, num_levels, levels);
write_ok = ref;
if (write_ok)
vector = (LV2_Atom_Vector *)lv2_atom_forge_deref(forge, ref);
}
if (write_ok)
vector->atom.type = self->sfizz_audio_level_uri;
(void)write_ok;
}
#endif
static void
sfizz_lv2_handle_atom_object(sfizz_plugin_t *self, int delay, const LV2_Atom_Object *obj)
{
@ -618,7 +654,7 @@ sfizz_lv2_handle_atom_object(sfizz_plugin_t *self, int delay, const LV2_Atom_Obj
if (cc != -1) {
if (atom->type == self->atom_float_uri && atom->size == sizeof(float)) {
float value = *(const float *)LV2_ATOM_BODY_CONST(atom);
sfizz_send_hdcc(self->synth, delay, cc, value);
sfizz_automate_hdcc(self->synth, delay, cc, value);
self->cc_current[cc] = value;
self->ccauto[cc] = absl::nullopt;
}
@ -656,6 +692,12 @@ sfizz_lv2_handle_atom_object(sfizz_plugin_t *self, int delay, const LV2_Atom_Obj
}
}
static bool
sfizz_is_sustain_or_sostenuto(sfizz_plugin_t* self, unsigned cc)
{
return (self->sustain_or_sostenuto[cc / 8] & (1 << (cc % 8)));
}
static void
sfizz_lv2_process_midi_event(sfizz_plugin_t *self, const LV2_Atom_Event *ev)
{
@ -677,23 +719,47 @@ sfizz_lv2_process_midi_event(sfizz_plugin_t *self, const LV2_Atom_Event *ev)
(int)msg[1],
msg[2]);
break;
// Note(jpc) CC must be mapped by host, not handled here.
// See LV2 midi:binding.
#if defined(SFIZZ_LV2_PSA)
case LV2_MIDI_MSG_CONTROLLER:
{
unsigned cc = msg[1];
float value = float(msg[2]) * (1.0f / 127.0f);
sfizz_send_hdcc(self->synth,
(int)ev->time.frames,
(int)cc,
value);
self->cc_current[cc] = value;
self->ccauto[cc] = value;
self->have_ccauto = true;
// Send
if (sfizz_is_sustain_or_sostenuto(self, cc)) {
sfizz_automate_hdcc(self->synth,
(int)ev->time.frames,
(int)cc,
value);
break;
}
// Note(jpc) CC must be mapped by host, not handled here.
// See LV2 midi:binding.
#if defined(SFIZZ_LV2_PSA)
switch (cc)
{
default:
{
sfizz_automate_hdcc(self->synth,
(int)ev->time.frames,
(int)cc,
value);
self->cc_current[cc] = value;
self->ccauto[cc] = value;
self->have_ccauto = true;
}
break;
case LV2_MIDI_CTL_ALL_NOTES_OFF:
// TODO implemented as all-sound-off
sfizz_all_sound_off(self->synth);
break;
case LV2_MIDI_CTL_ALL_SOUNDS_OFF:
sfizz_all_sound_off(self->synth);
break;
}
#endif
}
break;
#endif
case LV2_MIDI_MSG_CHANNEL_PRESSURE:
sfizz_send_channel_aftertouch(self->synth,
(int)ev->time.frames,
@ -829,7 +895,7 @@ static void
run(LV2_Handle instance, uint32_t sample_count)
{
sfizz_plugin_t *self = (sfizz_plugin_t *)instance;
assert(self->control_port && self->notify_port && self->automate_port);
assert(self->control_port && self->automate_port);
if (!spin_mutex_trylock(self->synth_mutex))
{
@ -839,15 +905,10 @@ run(LV2_Handle instance, uint32_t sample_count)
}
// Set up dedicated forges to write on their respective ports.
const size_t notify_capacity = self->notify_port->atom.size;
lv2_atom_forge_set_buffer(&self->forge_notify, (uint8_t *)self->notify_port, notify_capacity);
const size_t automate_capacity = self->automate_port->atom.size;
lv2_atom_forge_set_buffer(&self->forge_automate, (uint8_t *)self->automate_port, automate_capacity);
// Start sequences in the respective output ports.
LV2_Atom_Forge_Frame notify_frame;
if (!lv2_atom_forge_sequence_head(&self->forge_notify, &notify_frame, 0))
assert(false);
LV2_Atom_Forge_Frame automate_frame;
if (!lv2_atom_forge_sequence_head(&self->forge_automate, &automate_frame, 0))
assert(false);
@ -871,25 +932,25 @@ run(LV2_Handle instance, uint32_t sample_count)
lv2_atom_object_get(obj, self->patch_property_uri, &property, 0);
if (!property) // Send the full state
{
sfizz_lv2_send_file_path(self, &self->forge_notify, self->sfizz_sfz_file_uri, self->sfz_file_path);
sfizz_lv2_send_file_path(self, &self->forge_notify, self->sfizz_scala_file_uri, self->scala_file_path);
sfizz_lv2_send_file_path(self, self->sfizz_notify_uri, self->sfizz_sfz_file_uri, self->sfz_file_path);
sfizz_lv2_send_file_path(self, self->sfizz_notify_uri, self->sfizz_scala_file_uri, self->scala_file_path);
for (unsigned cc = 0; cc < sfz::config::numCCs; ++cc)
sfizz_lv2_send_controller(self, &self->forge_notify, cc, self->cc_current[cc]);
sfizz_lv2_send_controller(self, self->sfizz_notify_uri, cc, self->cc_current[cc]);
}
else if (property->body == self->sfizz_sfz_file_uri)
{
sfizz_lv2_send_file_path(self, &self->forge_notify, self->sfizz_sfz_file_uri, self->sfz_file_path);
sfizz_lv2_send_file_path(self, self->sfizz_notify_uri, self->sfizz_sfz_file_uri, self->sfz_file_path);
}
else if (property->body == self->sfizz_scala_file_uri)
{
sfizz_lv2_send_file_path(self, &self->forge_notify, self->sfizz_scala_file_uri, self->scala_file_path);
sfizz_lv2_send_file_path(self, self->sfizz_notify_uri, self->sfizz_scala_file_uri, self->scala_file_path);
}
else
{
int cc = sfizz_lv2_ccmap_unmap(self->ccmap, property->body);
if (cc != -1)
sfizz_lv2_send_controller(self, &self->forge_notify, unsigned(cc), self->cc_current[cc]);
sfizz_lv2_send_controller(self, self->sfizz_notify_uri, unsigned(cc), self->cc_current[cc]);
}
}
else if (obj->body.otype == self->time_position_uri)
@ -1037,7 +1098,7 @@ run(LV2_Handle instance, uint32_t sample_count)
for (unsigned cc = 0; cc < sfz::config::numCCs; ++cc) {
absl::optional<float> value = self->ccauto[cc];
if (value) {
sfizz_lv2_send_controller(self, &self->forge_automate, cc, *value);
sfizz_lv2_send_controller(self, self->patch_set_uri, cc, *value);
self->ccauto[cc] = absl::nullopt;
}
}
@ -1046,7 +1107,20 @@ run(LV2_Handle instance, uint32_t sample_count)
spin_mutex_unlock(self->synth_mutex);
lv2_atom_forge_pop(&self->forge_notify, &notify_frame);
#if defined(SFIZZ_LV2_UI)
if (self->ui_active)
{
self->rms_follower.process(self->output_buffers[0], self->output_buffers[1], sample_count);
const simde__m128 rms = self->rms_follower.getRMS();
const float *levels = (const float *)&rms;
sfizz_lv2_send_levels(self, levels[0], levels[1]);
}
else
{
self->rms_follower.clear();
}
#endif
lv2_atom_forge_pop(&self->forge_automate, &automate_frame);
}
@ -1168,7 +1242,7 @@ sfizz_lv2_update_sfz_info(sfizz_plugin_t *self)
//
const InstrumentDescription desc = parseDescriptionBlob(blob);
for (unsigned cc = 0; cc < sfz::config::numCCs; ++cc) {
if (desc.ccUsed.test(cc)) {
if (desc.ccUsed.test(cc) && !desc.sustainOrSostenuto.test(cc)) {
// Mark all the used CCs for automation with default values
self->ccauto[cc] = desc.ccDefault[cc];
self->have_ccauto = true;
@ -1176,13 +1250,14 @@ sfizz_lv2_update_sfz_info(sfizz_plugin_t *self)
self->cc_current[cc] = desc.ccDefault[cc];
}
}
memcpy(self->sustain_or_sostenuto, desc.sustainOrSostenuto.data(),
sizeof(self->sustain_or_sostenuto));
}
static bool
sfizz_lv2_load_file(sfizz_plugin_t *self, const char *file_path)
{
bool status;
char buf[MAX_PATH_SIZE];
if (file_path[0] == '\0')
{
@ -1191,18 +1266,7 @@ sfizz_lv2_load_file(sfizz_plugin_t *self, const char *file_path)
}
///
const sfz::InstrumentFormatRegistry& formatRegistry = sfz::InstrumentFormatRegistry::getInstance();
const sfz::InstrumentFormat* format = formatRegistry.getMatchingFormat(file_path);
if (!format)
status = sfizz_load_file(self->synth, file_path);
else {
auto importer = format->createImporter();
std::string virtual_path = std::string(file_path) + ".sfz";
std::string sfz_text = importer->convertToSfz(file_path);
status = sfizz_load_string(self->synth, virtual_path.c_str(), sfz_text.c_str());
}
bool status = sfizz_load_or_import_file(self->synth, file_path, nullptr);
sfizz_lv2_update_sfz_info(self);
sfizz_lv2_update_file_info(self, file_path);
return status;
@ -1246,11 +1310,9 @@ restore(LV2_Handle instance,
}
// Set default values
self->last_keyswitch = -1;
sfizz_lv2_get_default_sfz_path(self, self->sfz_file_path, MAX_PATH_SIZE);
sfizz_lv2_get_default_scala_path(self, self->scala_file_path, MAX_PATH_SIZE);
self->num_voices = DEFAULT_VOICES;
self->preload_size = DEFAULT_PRELOAD;
self->oversampling = DEFAULT_OVERSAMPLING;
// Fetch back the saved file path, if any
size_t size;
@ -1299,26 +1361,11 @@ restore(LV2_Handle instance,
}
}
value = retrieve(handle, self->sfizz_num_voices_uri, &size, &type, &val_flags);
value = retrieve(handle, self->sfizz_last_keyswitch_uri, &size, &type, &val_flags);
if (value)
{
int num_voices = *(const int *)value;
if (num_voices > 0 && num_voices <= MAX_VOICES)
self->num_voices = num_voices;
}
value = retrieve(handle, self->sfizz_preload_size_uri, &size, &type, &val_flags);
if (value)
{
unsigned int preload_size = *(const unsigned int *)value;
self->preload_size = preload_size;
}
value = retrieve(handle, self->sfizz_oversampling_uri, &size, &type, &val_flags);
if (value)
{
sfizz_oversampling_factor_t oversampling = *(const sfizz_oversampling_factor_t *)value;
self->oversampling = oversampling;
int last_keyswitch = *(const int*)value;
self->last_keyswitch = last_keyswitch;
}
// Collect all CC values present in the state
@ -1361,21 +1408,12 @@ restore(LV2_Handle instance,
"[sfizz] Error while restoring the scale %s\n", self->scala_file_path);
}
lv2_log_note(&self->logger, "[sfizz] Restoring the number of voices to %d\n", self->num_voices);
sfizz_set_num_voices(self->synth, self->num_voices);
lv2_log_note(&self->logger, "[sfizz] Restoring the preload size to %d\n", self->preload_size);
sfizz_set_preload_size(self->synth, self->preload_size);
lv2_log_note(&self->logger, "[sfizz] Restoring the oversampling to %d\n", self->oversampling);
sfizz_set_oversampling_factor(self->synth, self->oversampling);
// Override default automation values with these from the state file
for (unsigned cc = 0; cc < sfz::config::numCCs; ++cc) {
absl::optional<float> value = cc_values[cc];
if (value) {
// Set CC in the synth
sfizz_send_hdcc(self->synth, 0, int(cc), *value);
sfizz_automate_hdcc(self->synth, 0, int(cc), *value);
// Mark CCs for automation with state values
self->ccauto[cc] = *value;
self->have_ccauto = true;
@ -1384,6 +1422,11 @@ restore(LV2_Handle instance,
}
}
if (self->last_keyswitch >= 0 && self->last_keyswitch <= 127) {
sfizz_send_hd_note_on(self->synth, 0, self->last_keyswitch, 1.0f);
sfizz_send_hd_note_off(self->synth, 1, self->last_keyswitch, 0.0f);
}
spin_mutex_unlock(self->synth_mutex);
return status;
@ -1447,38 +1490,23 @@ save(LV2_Handle instance,
if (map_path)
free_path->free_path(free_path->handle, (char *)path);
// Save the number of voices
store(handle,
self->sfizz_num_voices_uri,
&self->num_voices,
sizeof(int),
self->atom_int_uri,
LV2_STATE_IS_POD);
// Save the preload size
store(handle,
self->sfizz_preload_size_uri,
&self->preload_size,
sizeof(unsigned int),
self->atom_int_uri,
LV2_STATE_IS_POD);
// Save the preload size
store(handle,
self->sfizz_oversampling_uri,
&self->oversampling,
sizeof(int),
self->atom_int_uri,
LV2_STATE_IS_POD);
// Save the CCs (used only)
self->sfz_blob_mutex->lock();
const InstrumentDescription desc = parseDescriptionBlob(
absl::string_view((const char*)self->sfz_blob_data, self->sfz_blob_size));
self->sfz_blob_mutex->unlock();
if (self->last_keyswitch >= 0 && self->last_keyswitch <= 127) {
store(handle,
self->sfizz_last_keyswitch_uri,
&self->last_keyswitch,
sizeof(int),
self->atom_int_uri,
LV2_STATE_IS_POD);
}
for (unsigned cc = 0; cc < sfz::config::numCCs; ++cc) {
if (desc.ccUsed.test(cc)) {
if (desc.ccUsed.test(cc) && !desc.sustainOrSostenuto.test(cc)) {
LV2_URID urid = sfizz_lv2_ccmap_map(self->ccmap, int(cc));
store(handle,
urid,

View file

@ -23,6 +23,13 @@
midnam:interface a lv2:ExtensionData .
midnam:update a lv2:Feature .
<@LV2PLUGIN_URI@#stereo_output>
a pg:StereoGroup, pg:OutputGroup ;
lv2:symbol "stereo_output" ;
lv2:name "Stereo output",
"Sortie stéréo"@fr ,
"Uscita stereo"@it .
<@LV2PLUGIN_URI@#config>
a pg:Group ;
lv2:symbol "config" ;
@ -90,10 +97,12 @@ midnam:update a lv2:Feature .
patch:writable <@LV2PLUGIN_URI@:sfzfile> ,
<@LV2PLUGIN_URI@:tuningfile> ;
pg:mainOutput <@LV2PLUGIN_URI@#stereo_output> ;
lv2:port [
a lv2:InputPort, atom:AtomPort ;
atom:bufferType atom:Sequence ;
atom:supports patch:Message, midi:MidiEvent, time:Position, <@LV2PLUGIN_URI@:OSCBlob> ;
atom:supports patch:Message, midi:MidiEvent, time:Position, <@LV2PLUGIN_URI@:OSCBlob>, <@LV2PLUGIN_URI@:Notify> ;
lv2:designation lv2:control ;
lv2:index 0 ;
lv2:symbol "control" ;
@ -104,38 +113,33 @@ midnam:update a lv2:Feature .
a lv2:OutputPort, atom:AtomPort ;
atom:bufferType atom:Sequence ;
atom:supports patch:Message, <@LV2PLUGIN_URI@:OSCBlob> ;
lv2:index 1 ;
lv2:symbol "notify" ;
lv2:name "Notify",
"Notification"@fr ;
rsz:minimumSize 524288 ;
] , [
a lv2:OutputPort, atom:AtomPort ;
atom:bufferType atom:Sequence ;
atom:supports patch:Message ;
lv2:designation lv2:control ;
lv2:index 2 ;
lv2:index 1 ;
lv2:symbol "automate" ;
lv2:name "Automate",
"Automatisation"@fr ;
rsz:minimumSize 524288 ;
] , [
a lv2:AudioPort, lv2:OutputPort ;
lv2:index 3 ;
lv2:index 2 ;
lv2:symbol "out_left" ;
lv2:name "Left Output",
"Sortie gauche"@fr ,
"Uscita Sinistra"@it
"Uscita Sinistra"@it ;
pg:group <@LV2PLUGIN_URI@#stereo_output> ;
lv2:designation pg:left
] , [
a lv2:AudioPort, lv2:OutputPort ;
lv2:index 4 ;
lv2:index 3 ;
lv2:symbol "out_right" ;
lv2:name "Right Output",
"Sortie droite"@fr ,
"Uscita Destra"@it
"Uscita Destra"@it ;
pg:group <@LV2PLUGIN_URI@#stereo_output> ;
lv2:designation pg:right
] , [
a lv2:InputPort, lv2:ControlPort ;
lv2:index 5 ;
lv2:index 4 ;
lv2:symbol "volume" ;
lv2:name "Volume" ;
lv2:default 0.0 ;
@ -144,7 +148,7 @@ midnam:update a lv2:Feature .
units:unit units:db
] , [
a lv2:InputPort, lv2:ControlPort ;
lv2:index 6 ;
lv2:index 5 ;
lv2:symbol "num_voices" ;
lv2:name "Polyphony",
"Polyphonie"@fr ,
@ -189,7 +193,7 @@ midnam:update a lv2:Feature .
] ;
] , [
a lv2:InputPort, lv2:ControlPort ;
lv2:index 7 ;
lv2:index 6 ;
lv2:symbol "oversampling" ;
lv2:name "Oversampling factor",
"Facteur de suréchantillonnage"@fr ,
@ -224,7 +228,7 @@ midnam:update a lv2:Feature .
] ;
] , [
a lv2:InputPort, lv2:ControlPort ;
lv2:index 8 ;
lv2:index 7 ;
lv2:symbol "preload_size" ;
lv2:name "Preload size",
"Taille préchargée"@fr ,
@ -267,7 +271,7 @@ midnam:update a lv2:Feature .
] ;
] , [
a lv2:InputPort, lv2:ControlPort ;
lv2:index 9 ;
lv2:index 8 ;
lv2:symbol "freewheeling" ;
lv2:name "Freewheeling",
"En roue libre (freewheeling)"@fr ,
@ -279,7 +283,7 @@ midnam:update a lv2:Feature .
lv2:maximum 1 ;
] , [
a lv2:InputPort, lv2:ControlPort ;
lv2:index 10 ;
lv2:index 9 ;
lv2:symbol "scala_root_key" ;
lv2:name "Scala root key",
"Tonalité de base Scala"@fr ,
@ -292,7 +296,7 @@ midnam:update a lv2:Feature .
units:unit units:midiNote
] , [
a lv2:InputPort, lv2:ControlPort ;
lv2:index 11 ;
lv2:index 10 ;
lv2:symbol "tuning_frequency" ;
lv2:name "Tuning frequency",
"Fréquence d'accordage"@fr ,
@ -304,7 +308,7 @@ midnam:update a lv2:Feature .
units:unit units:hz
] , [
a lv2:InputPort, lv2:ControlPort ;
lv2:index 12 ;
lv2:index 11 ;
lv2:symbol "stretched_tuning" ;
lv2:name "Stretched tuning",
"Accordage étiré"@fr ,
@ -316,7 +320,7 @@ midnam:update a lv2:Feature .
units:unit units:coef
] , [
a lv2:InputPort, lv2:ControlPort ;
lv2:index 13 ;
lv2:index 12 ;
lv2:symbol "sample_quality" ;
lv2:name "Sample quality",
"Qualité des échantillons"@fr ,
@ -327,7 +331,7 @@ midnam:update a lv2:Feature .
lv2:maximum 10.0
] , [
a lv2:InputPort, lv2:ControlPort ;
lv2:index 14 ;
lv2:index 13 ;
lv2:symbol "oscillator_quality" ;
lv2:name "Oscillator quality",
"Qualité des oscillateurs"@fr ,
@ -338,7 +342,7 @@ midnam:update a lv2:Feature .
lv2:maximum 3.0
] , [
a lv2:OutputPort, lv2:ControlPort ;
lv2:index 15 ;
lv2:index 14 ;
lv2:symbol "active_voices" ;
lv2:name "Active voices",
"Voix utilisées"@fr ;
@ -349,7 +353,7 @@ midnam:update a lv2:Feature .
lv2:maximum 256 ;
] , [
a lv2:OutputPort, lv2:ControlPort ;
lv2:index 16 ;
lv2:index 15 ;
lv2:symbol "num_curves" ;
lv2:name "Number of curves",
"Nombre de courbes"@fr ;
@ -360,7 +364,7 @@ midnam:update a lv2:Feature .
lv2:maximum 65535 ;
] , [
a lv2:OutputPort, lv2:ControlPort ;
lv2:index 17 ;
lv2:index 16 ;
lv2:symbol "num_masters" ;
lv2:name "Number of masters",
"Nombre de maîtres"@fr ;
@ -371,7 +375,7 @@ midnam:update a lv2:Feature .
lv2:maximum 65535 ;
] , [
a lv2:OutputPort, lv2:ControlPort ;
lv2:index 18 ;
lv2:index 17 ;
lv2:symbol "num_groups" ;
lv2:name "Number of groups",
"Nombre de groupes"@fr ;
@ -382,7 +386,7 @@ midnam:update a lv2:Feature .
lv2:maximum 65535 ;
] , [
a lv2:OutputPort, lv2:ControlPort ;
lv2:index 19 ;
lv2:index 18 ;
lv2:symbol "num_regions" ;
lv2:name "Number of regions",
"Nombre de régions"@fr ;
@ -393,7 +397,7 @@ midnam:update a lv2:Feature .
lv2:maximum 65535 ;
] , [
a lv2:OutputPort, lv2:ControlPort ;
lv2:index 20 ;
lv2:index 19 ;
lv2:symbol "num_samples" ;
lv2:name "Number of samples",
"Nombre d'échantillons"@fr ;

View file

@ -39,35 +39,38 @@
#define SFIZZ__numVoices SFIZZ_URI ":" "numvoices"
#define SFIZZ__preloadSize SFIZZ_URI ":" "preload_size"
#define SFIZZ__oversampling SFIZZ_URI ":" "oversampling"
#define SFIZZ__lastKeyswitch SFIZZ_URI ":" "last_keyswitch"
// These ones are just for the worker
#define SFIZZ__logStatus SFIZZ_URI ":" "log_status"
#define SFIZZ__checkModification SFIZZ_URI ":" "check_modification"
// OSC atoms
#define SFIZZ__OSCBlob SFIZZ_URI ":" "OSCBlob"
#define SFIZZ__Notify SFIZZ_URI ":" "Notify"
// Level atoms
#define SFIZZ__AudioLevel SFIZZ_URI ":" "AudioLevel"
enum
{
SFIZZ_CONTROL = 0,
SFIZZ_NOTIFY = 1,
SFIZZ_AUTOMATE = 2,
SFIZZ_LEFT = 3,
SFIZZ_RIGHT = 4,
SFIZZ_VOLUME = 5,
SFIZZ_POLYPHONY = 6,
SFIZZ_OVERSAMPLING = 7,
SFIZZ_PRELOAD = 8,
SFIZZ_FREEWHEELING = 9,
SFIZZ_SCALA_ROOT_KEY = 10,
SFIZZ_TUNING_FREQUENCY = 11,
SFIZZ_STRETCH_TUNING = 12,
SFIZZ_SAMPLE_QUALITY = 13,
SFIZZ_OSCILLATOR_QUALITY = 14,
SFIZZ_ACTIVE_VOICES = 15,
SFIZZ_NUM_CURVES = 16,
SFIZZ_NUM_MASTERS = 17,
SFIZZ_NUM_GROUPS = 18,
SFIZZ_NUM_REGIONS = 19,
SFIZZ_NUM_SAMPLES = 20,
SFIZZ_AUTOMATE = 1,
SFIZZ_LEFT = 2,
SFIZZ_RIGHT = 3,
SFIZZ_VOLUME = 4,
SFIZZ_POLYPHONY = 5,
SFIZZ_OVERSAMPLING = 6,
SFIZZ_PRELOAD = 7,
SFIZZ_FREEWHEELING = 8,
SFIZZ_SCALA_ROOT_KEY = 9,
SFIZZ_TUNING_FREQUENCY = 10,
SFIZZ_STRETCH_TUNING = 11,
SFIZZ_SAMPLE_QUALITY = 12,
SFIZZ_OSCILLATOR_QUALITY = 13,
SFIZZ_ACTIVE_VOICES = 14,
SFIZZ_NUM_CURVES = 15,
SFIZZ_NUM_MASTERS = 16,
SFIZZ_NUM_GROUPS = 17,
SFIZZ_NUM_REGIONS = 18,
SFIZZ_NUM_SAMPLES = 19,
};
// For use with instance-access
@ -89,6 +92,10 @@ bool sfizz_lv2_fetch_description(
sfizz_plugin_t *self, const int *serial,
uint8_t **descp, uint32_t *sizep, int *serialp);
#if defined(SFIZZ_LV2_UI)
void sfizz_lv2_set_ui_active(sfizz_plugin_t *self, bool ui_active);
#endif
// Mapping URID to CC and vice-versa
struct sfizz_lv2_ccmap;
sfizz_lv2_ccmap *sfizz_lv2_ccmap_create(LV2_URID_Map* map);

View file

@ -31,6 +31,13 @@ bool sfizz_lv2_fetch_description(
return true;
}
#if defined(SFIZZ_LV2_UI)
void sfizz_lv2_set_ui_active(sfizz_plugin_t *self, bool ui_active)
{
self->ui_active = ui_active;
}
#endif
struct sfizz_lv2_ccmap {
LV2_URID *cc_to_urid;
int *urid_to_cc;

View file

@ -7,6 +7,9 @@
#pragma once
#include "sfizz_lv2.h"
#if defined(SFIZZ_LV2_UI)
#include "plugin/RMSFollower.h"
#endif
#include <spin_mutex.h>
#include <sfizz.h>
#include <stdbool.h>
@ -31,7 +34,6 @@ struct sfizz_plugin_t
// Ports
const LV2_Atom_Sequence *control_port {};
LV2_Atom_Sequence *notify_port {};
LV2_Atom_Sequence *automate_port {};
float *output_buffers[2] {};
const float *volume_port {};
@ -52,7 +54,6 @@ struct sfizz_plugin_t
float *num_samples_port {};
// Atom forge
LV2_Atom_Forge forge_notify {}; ///< Forge for writing notification atoms in run thread
LV2_Atom_Forge forge_automate {}; ///< Forge for writing automation atoms in run thread
LV2_Atom_Forge forge_secondary {}; ///< Forge for writing into other buffers
@ -85,10 +86,13 @@ struct sfizz_plugin_t
LV2_URID sfizz_num_voices_uri {};
LV2_URID sfizz_preload_size_uri {};
LV2_URID sfizz_oversampling_uri {};
LV2_URID sfizz_last_keyswitch_uri {};
LV2_URID sfizz_log_status_uri {};
LV2_URID sfizz_check_modification_uri {};
LV2_URID sfizz_active_voices_uri {};
LV2_URID sfizz_osc_blob_uri {};
LV2_URID sfizz_notify_uri {};
LV2_URID sfizz_audio_level_uri {};
LV2_URID time_position_uri {};
LV2_URID time_bar_uri {};
LV2_URID time_bar_beat_uri {};
@ -117,6 +121,7 @@ struct sfizz_plugin_t
float sample_rate {};
std::atomic<int> must_update_midnam {};
volatile bool must_automate_cc {};
int last_keyswitch { -1 };
// Current instrument description
std::mutex *sfz_blob_mutex {};
@ -124,6 +129,9 @@ struct sfizz_plugin_t
const uint8_t *volatile sfz_blob_data {};
volatile uint32_t sfz_blob_size {};
// Sostenuto or sustain
char sustain_or_sostenuto[16] {};
// Current CC values in the synth (synchronized by `synth_mutex`)
// updated by hdcc or file load
float *cc_current {};
@ -145,4 +153,10 @@ struct sfizz_plugin_t
// OSC
uint8_t osc_temp[OSC_TEMP_SIZE] {};
#if defined(SFIZZ_LV2_UI)
// UI
volatile bool ui_active = false;
RMSFollower rms_follower;
#endif
};

View file

@ -101,7 +101,7 @@ struct sfizz_ui_t : EditorController, VSTGUIEditorInterface {
/// VSTGUIEditorInterface
CFrame* getFrame() const override { return uiFrame.get(); }
int32_t getKnobMode () const override { return kLinearMode; }
LV2_Atom_Forge atom_forge;
LV2_URID atom_event_transfer_uri;
LV2_URID atom_object_uri;
@ -116,6 +116,8 @@ struct sfizz_ui_t : EditorController, VSTGUIEditorInterface {
LV2_URID sfizz_sfz_file_uri;
LV2_URID sfizz_scala_file_uri;
LV2_URID sfizz_osc_blob_uri;
LV2_URID sfizz_notify_uri;
LV2_URID sfizz_audio_level_uri;
std::unique_ptr<sfizz_lv2_ccmap, sfizz_lv2_ccmap_delete> ccmap;
uint8_t osc_temp[OSC_TEMP_SIZE];
@ -217,6 +219,8 @@ instantiate(const LV2UI_Descriptor *descriptor,
self->sfizz_sfz_file_uri = map->map(map->handle, SFIZZ__sfzFile);
self->sfizz_scala_file_uri = map->map(map->handle, SFIZZ__tuningfile);
self->sfizz_osc_blob_uri = map->map(map->handle, SFIZZ__OSCBlob);
self->sfizz_notify_uri = map->map(map->handle, SFIZZ__Notify);
self->sfizz_audio_level_uri = map->map(map->handle, SFIZZ__AudioLevel);
self->ccmap.reset(sfizz_lv2_ccmap_create(map));
// set up the resource path
@ -287,6 +291,7 @@ static void
cleanup(LV2UI_Handle ui)
{
sfizz_ui_t *self = (sfizz_ui_t *)ui;
sfizz_lv2_set_ui_active(self->plugin, false);
delete self;
}
@ -391,6 +396,27 @@ port_event(LV2UI_Handle ui,
if (sfizz_extract_message(LV2_ATOM_BODY_CONST(atom), atom->size, buffer, sizeof(buffer), &path, &sig, &args) > 0)
self->uiReceiveMessage(path, sig, args);
}
else if (atom->type == self->sfizz_audio_level_uri) {
const LV2_Atom_Vector *vector = reinterpret_cast<const LV2_Atom_Vector *>(atom);
if (vector->body.child_type == self->atom_float_uri &&
vector->body.child_size == sizeof(float))
{
const uint8_t *vec_body = reinterpret_cast<const uint8_t*>(
LV2_ATOM_CONTENTS_CONST(LV2_Atom_Vector, vector));
const uint8_t *vec_end = reinterpret_cast<const uint8_t*>(
LV2_ATOM_BODY_CONST(&vector->atom)) + vector->atom.size;
const float *levels = reinterpret_cast<const float *>(vec_body);
uint32_t count = static_cast<uint32_t>((vec_end - vec_body) / sizeof(float));
float left = (count > 0) ? levels[0] : 0.0f;
float right = (count > 1) ? levels[1] : 0.0f;
self->uiReceiveValue(EditId::LeftLevel, left);
self->uiReceiveValue(EditId::RightLevel, right);
}
}
}
(void)buffer_size;
@ -421,7 +447,7 @@ sfizz_ui_update_description(sfizz_ui_t *self, const InstrumentDescription& desc)
}
for (unsigned cc = 0; cc < sfz::config::numCCs; ++cc) {
bool ccUsed = desc.ccUsed.test(cc);
bool ccUsed = desc.ccUsed.test(cc) && !desc.sustainOrSostenuto.test(cc);
self->uiReceiveValue(editIdForCCUsed(cc), float(ccUsed));
if (ccUsed) {
self->uiReceiveValue(editIdForCCDefault(cc), desc.ccDefault[cc]);
@ -466,6 +492,8 @@ idle(LV2UI_Handle ui)
(void)self;
#endif
sfizz_lv2_set_ui_active(self->plugin, self->uiFrame->isVisible());
return 0;
}

View file

@ -12,4 +12,4 @@
lv2:optionalFeature ui:touch ;
lv2:requiredFeature urid:map ;
lv2:requiredFeature urid:unmap ;
lv2:requiredFeature <http://lv2plug.in/ns/ext/instance-access> .
lv2:requiredFeature <http://lv2plug.in/ns/ext/instance-access> .

View file

@ -0,0 +1,57 @@
# Puredata plugin specific settings
include(PuredataConfig)
set(PUREDATA_BINARY_DIR "${PROJECT_BINARY_DIR}/pd/sfizz")
set(PUREDATA_RESOURCES
"sfizz~-help.pd"
"example.sfz")
function(copy_puredata_resources TARGET SOURCE_DIR DESTINATION_DIR)
set(_deps)
foreach(res ${PUREDATA_RESOURCES})
get_filename_component(_dir "${res}" DIRECTORY)
file(MAKE_DIRECTORY "${DESTINATION_DIR}/${_dir}")
add_custom_command(
OUTPUT "${DESTINATION_DIR}/${res}"
COMMAND "${CMAKE_COMMAND}" "-E" "copy"
"${SOURCE_DIR}/${res}" "${DESTINATION_DIR}/${res}"
DEPENDS "${SOURCE_DIR}/${res}")
list(APPEND _deps "${DESTINATION_DIR}/${res}")
endforeach()
add_custom_target("${TARGET}_puredata_resources" DEPENDS ${_deps})
add_dependencies("${TARGET}" "${TARGET}_puredata_resources")
endfunction()
add_pd_external(sfizz_puredata "sfizz_puredata.c")
target_compile_definitions(sfizz_puredata PRIVATE
"SFIZZ_NUM_CCS=${SFIZZ_NUM_CCS}"
"SFIZZ_VERSION=\"${CMAKE_PROJECT_VERSION}\"")
target_link_libraries(sfizz_puredata PRIVATE sfizz::import sfizz::sfizz)
set_target_properties(sfizz_puredata PROPERTIES
OUTPUT_NAME "sfizz"
LIBRARY_OUTPUT_DIRECTORY "${PUREDATA_BINARY_DIR}/$<0:>")
if(MINGW)
set_property(TARGET sfizz_puredata APPEND_STRING
PROPERTY LINK_FLAGS " -static")
endif()
# Git build identifier
target_link_libraries(sfizz_puredata PRIVATE sfizz-git-build-id)
# Copy resources
copy_puredata_resources(sfizz_puredata
"${CMAKE_CURRENT_SOURCE_DIR}"
"${PUREDATA_BINARY_DIR}")
# Installation
if(NOT MSVC)
install(DIRECTORY "${PUREDATA_BINARY_DIR}" DESTINATION "${PDPLUGIN_INSTALL_DIR}"
COMPONENT "puredata"
USE_SOURCE_PERMISSIONS)
bundle_dylibs(puredata
"${PDPLUGIN_INSTALL_DIR}/sfizz/sfizz${PUREDATA_SUFFIX}"
COMPONENT "puredata")
endif()

View file

@ -0,0 +1,22 @@
<group>
ampeg_attack=0.25
ampeg_decay=5
ampeg_sustain=50
ampeg_release=2
fil_type=lpf_4p
resonance=3
cutoff=3000
oscillator_phase=-1
cutoff_oncc300=3600
cutoff_curvecc300=1
<region>
sample=*saw
oscillator_multi=3
oscillator_detune=10
lfo1_freq=5
lfo1_pitch=30
<region>
sample=*triangle
transpose=-12

View file

@ -0,0 +1,30 @@
This software is copyrighted by Miller Puckette and others. The following
terms (the "Standard Improved BSD License") apply to all files associated with
the software unless explicitly disclaimed in individual files:
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following
disclaimer in the documentation and/or other materials provided
with the distribution.
3. The name of the author may not be used to endorse or promote
products derived from this software without specific prior
written permission.
THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR
BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
THE POSSIBILITY OF SUCH DAMAGE.

601
plugins/puredata/external/pd/bin/pd.def vendored Normal file
View file

@ -0,0 +1,601 @@
LIBRARY pd
EXPORTS
ABORT
array_free
array_getcoordinate
array_getfields
array_new
array_redraw
array_resize
array_resize_and_redraw
atom_gensym
atom_getfloat
atom_getfloatarg
atom_getint
atom_getintarg
atom_getsymbol
atom_getsymbolarg
atom_string
audio_isopen
audio_shouldkeepopen
binbuf_add
binbuf_addbinbuf
binbuf_addsemi
binbuf_addv
binbuf_clear
binbuf_duplicate
binbuf_eval
binbuf_evalfile
binbuf_free
binbuf_getnatom
binbuf_gettext
binbuf_getvec
binbuf_new
binbuf_print
binbuf_read
binbuf_read_via_canvas
binbuf_read_via_path
binbuf_realizedollsym
binbuf_resize
binbuf_restore
binbuf_text
binbuf_write
bug
canvas_addinlet
canvas_addoutlet
canvas_class DATA
canvas_closebang
canvas_connect
canvas_create_editor
canvas_dataproperties
canvas_deletelinesfor
canvas_deletelinesforio
canvas_destroy_editor
canvas_dirty
canvas_disconnect
canvas_dspstate DATA
canvas_editmode
canvas_fixlinesfor
canvas_free
canvas_getargs
canvas_getcurrent
canvas_getcurrentdir
canvas_getdir
canvas_getdollarzero
canvas_getenv
canvas_getindex
canvas_getrootfor
canvas_hitbox
canvas_initbang
canvas_isabstraction
canvas_isconnected
canvas_istable
canvas_loadbang
canvas_makebindsym
canvas_makefilename
canvas_new
canvas_noundo
canvas_open
canvas_path_iterate
canvas_readscalar
canvas_realizedollar
canvas_redraw
canvas_redrawallfortemplate
canvas_redrawallfortemplatecanvas
canvas_rename
canvas_resortinlets
canvas_resortoutlets
canvas_restoreconnections
canvas_resume_dsp
canvas_rminlet
canvas_rmoutlet
canvas_selectinrect
canvas_setargs
canvas_setcurrent
canvas_setcursor
canvas_setdeleting
canvas_setundo
canvas_showtext
canvas_stowconnections
canvas_suspend_dsp
canvas_unsetcurrent
canvas_update_dsp
canvas_updatewindowlist
canvas_vis
canvas_whichfind DATA
canvas_writescalar
class_addanything
class_addbang
class_addcreator
class_addlist
class_addmethod
class_addpointer
class_addsymbol
class_doaddfloat
class_domainsignalin
class_gethelpdir
class_gethelpname
class_getname
class_getpropertiesfn
class_getsavefn
class_isdrawcommand
class_new
class_new64
class_set_extern_dir
class_setdrawcommand
class_sethelpsymbol
class_setparentwidget
class_setpropertiesfn
class_setsavefn
class_setwidget
clock_delay
clock_free
clock_getlogicaltime
clock_getsystime
clock_getsystimeafter
clock_gettimesince
clock_gettimesincewithunits
clock_new
clock_set
clock_setunit
clock_unset
clone_class DATA
copy_perform
copybytes
cos_table DATA
dbtopow
dbtorms
dsp_add
dsp_add_copy
dsp_add_plus
dsp_add_scalarcopy
dsp_add_zero
dsp_addv
endpost
error
ex_Avg
ex_Sum
ex_avg
ex_funcs DATA
ex_getsym
ex_mkvector
ex_size
ex_store
ex_sum
ex_symname
fielddesc_cvtfromcoord
fielddesc_cvttocoord
fielddesc_getcoord
fielddesc_setcoord
floatinlet_new
freebytes
ftom
g_editor_freepdinstance
g_editor_newpdinstance
g_template_freepdinstance
g_template_newpdinstance
garray_class DATA
garray_getarray
garray_getfloatarray
garray_getfloatwords
garray_getglist
garray_npoints
garray_redraw
garray_resize
garray_resize_long
garray_setsaveit
garray_template
garray_usedindsp
garray_vec
gensym
get_sys_dacsr
get_sys_main_advance
get_sys_schedadvance
get_sys_schedblocksize
get_sys_sleepgrain
get_sys_soundin
get_sys_soundout
get_sys_time
get_sys_time_per_dsp_tick
getbytes
getfn
getzbytes
gfxstub_deleteforkey
gfxstub_new
glist_add
glist_addglist
glist_arraydialog
glist_clear
glist_delete
glist_deselect
glist_dpixtodx
glist_dpixtody
glist_drawiofor
glist_eraseiofor
glist_findgraph
glist_findrtext
glist_fontheight
glist_fontwidth
glist_getcanvas
glist_getfont
glist_getnextxy
glist_getzoom
glist_glist
glist_grab
glist_init
glist_isgraph
glist_isselected
glist_istoplevel
glist_isvisible
glist_mergefile
glist_noselect
glist_pixelstox
glist_pixelstoy
glist_read
glist_redraw
glist_retext
glist_select
glist_selectall
glist_sort
glist_valid DATA
glist_writetobinbuf
glist_xtopixels
glist_ytopixels
glob_evalfile
glob_initfromgui
glob_pdobject DATA
glob_quit
glob_setfilename
gobj_activate
gobj_click
gobj_delete
gobj_displace
gobj_getrect
gobj_save
gobj_select
gobj_shouldvis
gobj_vis
gpointer_check
gpointer_copy
gpointer_init
gpointer_setarray
gpointer_setglist
gpointer_unset
graph_array
gstub_cutoff
gstub_new
gtemplate_get
guiconnect_new
guiconnect_notarget
iem_fstyletoint
iem_inttofstyle
iem_inttosymargs
iem_symargstoint
iemgui_all_dollar2raute
iemgui_all_dollararg2sym
iemgui_all_loadcolors
iemgui_all_raute2dollar
iemgui_all_sym2dollararg
iemgui_clip_font
iemgui_clip_size
iemgui_color
iemgui_color_hex DATA
iemgui_delete
iemgui_delta
iemgui_dialog
iemgui_displace
iemgui_dollar2raute
iemgui_label
iemgui_label_font
iemgui_label_pos
iemgui_new_dogetname
iemgui_new_getnames
iemgui_newzoom
iemgui_pos
iemgui_properties
iemgui_raute2dollar
iemgui_receive
iemgui_save
iemgui_select
iemgui_send
iemgui_size
iemgui_verify_snd_ne_rcv
iemgui_vis
iemgui_vu_col DATA
iemgui_vu_db2i DATA
iemgui_vu_scale_str DATA
iemgui_zoom
ilog2
inlet_free
inlet_new
inmidi_aftertouch
inmidi_byte
inmidi_controlchange
inmidi_noteon
inmidi_pitchbend
inmidi_polyaftertouch
inmidi_programchange
inmidi_realtimein
inmidi_sysex
linetraverser_next
linetraverser_skipobject
linetraverser_start
logpost
max_ex_tab
max_ex_tab_store
max_ex_var
max_ex_var_store
mayer_fft
mayer_fht
mayer_ifft
mayer_realfft
mayer_realifft
midi_getdevs
mmio_close_audio
mmio_getdevs
mmio_open_audio
mmio_reportidle
mmio_send_dacs
mtof
namelist_append
namelist_append_files
namelist_free
namelist_get
nullfn
obj_connect
obj_disconnect
obj_issignalinlet
obj_issignaloutlet
obj_list
obj_nexttraverseoutlet
obj_ninlets
obj_noutlets
obj_nsiginlets
obj_nsigoutlets
obj_saveformat
obj_siginletindex
obj_sigoutletindex
obj_starttraverseoutlet
open_via_helppath
open_via_path
outlet_anything
outlet_bang
outlet_float
outlet_free
outlet_getsymbol
outlet_list
outlet_new
outlet_pointer
outlet_setstacklim
outlet_symbol
pa_close_audio
pa_getdevs
pa_open_audio
pa_send_dacs
pd_bang
pd_bind
pd_canvasmaker DATA
pd_checkglist
pd_checkobject
pd_compatibilitylevel DATA
pd_emptylist
pd_error
pd_fft
pd_findbyclass
pd_float
pd_forwardmess
pd_free
pd_getcanvaslist
pd_getdspstate
pd_getparentwidget
pd_globallock
pd_globalunlock
pd_list
pd_maininstance DATA
pd_new
pd_newest
pd_objectmaker DATA
pd_pointer
pd_popsym
pd_pushsym
pd_symbol
pd_typedmess
pd_unbind
pd_vmess
plus_perform
pointerinlet_new
post
postatom
postfloat
poststring
powtodb
q8_rsqrt
q8_sqrt
qrsqrt
qsqrt
resample_dsp
resample_free
resample_init
resamplefrom_dsp
resampleto_dsp
resizebytes
rmstodb
rtext_activate
rtext_displace
rtext_draw
rtext_erase
rtext_free
rtext_getseltext
rtext_gettag
rtext_gettext
rtext_height
rtext_key
rtext_mouse
rtext_new
rtext_retext
rtext_select
rtext_width
s_ DATA
s__N DATA
s__X DATA
s_anything DATA
s_bang DATA
s_float DATA
s_list DATA
s_pointer DATA
s_signal DATA
s_symbol DATA
s_x DATA
s_y DATA
scalar_class DATA
scalar_getbasexy
scalar_new
scalar_redraw
sched_geteventno
sched_set_using_audio
sched_tick
signalinlet_new
socketreceiver_new
socketreceiver_read
startpost
symbolinlet_new
sys_addhist
sys_addpollfn
sys_advance_samples DATA
sys_audioapi DATA
sys_audiodevnametonumber
sys_audiodevnumbertoname
sys_bail
sys_bashfilename
sys_clearhist
sys_close
sys_close_audio
sys_close_midi
sys_closesocket
sys_debuglevel DATA
sys_decodedialog
sys_defaultfont DATA
sys_defeatrt DATA
sys_do_open_midi
sys_externalschedlib DATA
sys_fclose
sys_flags DATA
sys_font DATA
sys_fontheight
sys_fontweight DATA
sys_fontwidth
sys_fopen
sys_get_audio_apis
sys_get_audio_devs
sys_get_audio_params
sys_get_inchannels
sys_get_midi_apis
sys_get_midi_devs
sys_get_midi_params
sys_get_outchannels
sys_getblksize
sys_getmeters
sys_getrealtime
sys_getsr
sys_getversion
sys_gui
sys_guicmd DATA
sys_havegui
sys_hipriority DATA
sys_hostfontsize
sys_idlehook DATA
sys_init_fdpoll
sys_initmidiqueue
sys_isabsolutepath
sys_libdir DATA
sys_listdevs
sys_listmididevs
sys_load_lib
sys_loadpreferences
sys_lock
sys_log_error
sys_logerror
sys_microsleep
sys_midiapi DATA
sys_midibytein
sys_mididevnametonumber
sys_mididevnumbertoname
sys_midiindevlist DATA
sys_midioutdevlist DATA
sys_nearestfontsize
sys_nmidiin DATA
sys_nmidiout DATA
sys_noloadbang DATA
sys_open
sys_open_absolute
sys_open_midi
sys_ouch
sys_poll_midi
sys_pollgui
sys_pollmidiqueue
sys_pretendguibytes
sys_printhook DATA
sys_printtostderr DATA
sys_putmidibyte
sys_putmidimess
sys_queuegui
sys_register_loader
sys_reopen_audio
sys_reopen_midi
sys_reportidle
sys_rmpollfn
sys_save_audio_params
sys_savepreferences
sys_schedadvance DATA
sys_send_dacs
sys_set_audio_api
sys_set_audio_settings
sys_set_audio_settings_reopen
sys_set_audio_state
sys_set_midi_api
sys_setchsr
sys_setextrapath
sys_setmiditimediff
sys_sleepgrain DATA
sys_sockerror
sys_trylock
sys_trytoopenone
sys_unbashfilename
sys_unixerror
sys_unlock
sys_unqueuegui
sys_usestdpath DATA
sys_verbose DATA
sys_vgui
sys_zoom_open DATA
sys_zoomfontheight
sys_zoomfontwidth
template_find_field
template_findbyname
template_findcanvas
template_free
template_getfloat
template_getsymbol
template_match
template_new
template_notify
template_setfloat
template_setsymbol
text_drawborder
text_eraseborder
text_setto
text_widgetbehavior DATA
text_xpix
text_ypix
value_get
value_getfloat
value_release
value_setfloat
verbose
vinlet_class DATA
voutlet_class DATA
word_free
word_init
word_restore
zero_perform
zgetfn

View file

@ -0,0 +1,911 @@
/* Copyright (c) 1997-1999 Miller Puckette.
* For information on usage and redistribution, and for a DISCLAIMER OF ALL
* WARRANTIES, see the file, "LICENSE.txt," in this distribution. */
#ifndef __m_pd_h_
#if defined(_LANGUAGE_C_PLUS_PLUS) || defined(__cplusplus)
extern "C" {
#endif
#define PD_MAJOR_VERSION 0
#define PD_MINOR_VERSION 51
#define PD_BUGFIX_VERSION 4
#define PD_TEST_VERSION ""
extern int pd_compatibilitylevel; /* e.g., 43 for pd 0.43 compatibility */
/* old name for "MSW" flag -- we have to take it for the sake of many old
"nmakefiles" for externs, which will define NT and not MSW */
#if defined(NT) && !defined(MSW)
#define MSW
#endif
/* These pragmas are only used for MSVC, not MinGW or Cygwin <hans@at.or.at> */
#ifdef _MSC_VER
/* #pragma warning( disable : 4091 ) */
#pragma warning( disable : 4305 ) /* uncast const double to float */
#pragma warning( disable : 4244 ) /* uncast float/int conversion etc. */
#pragma warning( disable : 4101 ) /* unused automatic variables */
#endif /* _MSC_VER */
/* the external storage class is "extern" in UNIX; in MSW it's ugly. */
#ifdef _WIN32
#ifdef PD_INTERNAL
#define EXTERN __declspec(dllexport) extern
#else
#define EXTERN __declspec(dllimport) extern
#endif /* PD_INTERNAL */
#else
#define EXTERN extern
#endif /* _WIN32 */
/* On most c compilers, you can just say "struct foo;" to declare a
structure whose elements are defined elsewhere. On MSVC, when compiling
C (but not C++) code, you have to say "extern struct foo;". So we make
a stupid macro: */
#if defined(_MSC_VER) && !defined(_LANGUAGE_C_PLUS_PLUS) \
&& !defined(__cplusplus)
#define EXTERN_STRUCT extern struct
#else
#define EXTERN_STRUCT struct
#endif
/* Define some attributes, specific to the compiler */
#if defined(__GNUC__)
#define ATTRIBUTE_FORMAT_PRINTF(a, b) __attribute__ ((format (printf, a, b)))
#else
#define ATTRIBUTE_FORMAT_PRINTF(a, b)
#endif
#if !defined(_SIZE_T) && !defined(_SIZE_T_)
#include <stddef.h> /* just for size_t -- how lame! */
#endif
/* Microsoft Visual Studio is not C99, but since VS2015 has included most C99 headers:
https://docs.microsoft.com/en-us/previous-versions/hh409293(v=vs.140)#c-runtime-library
These definitions recreate stdint.h types, but only in pre-2015 Visual Studio: */
#if defined(_MSC_VER) && _MSC_VER < 1900
typedef signed __int8 int8_t;
typedef signed __int16 int16_t;
typedef signed __int32 int32_t;
typedef signed __int64 int64_t;
typedef unsigned __int8 uint8_t;
typedef unsigned __int16 uint16_t;
typedef unsigned __int32 uint32_t;
typedef unsigned __int64 uint64_t;
#else
# include <stdint.h>
#endif
/* for FILE, needed by sys_fopen() and sys_fclose() only */
#include <stdio.h>
#define MAXPDSTRING 1000 /* use this for anything you want */
#define MAXPDARG 5 /* max number of args we can typecheck today */
/* signed and unsigned integer types the size of a pointer: */
#if !defined(PD_LONGINTTYPE)
#if defined(_WIN32) && defined(_WIN64)
#define PD_LONGINTTYPE long long
#else
#define PD_LONGINTTYPE long
#endif
#endif
#if !defined(PD_FLOATSIZE)
/* normally, our floats (t_float, t_sample,...) are 32bit */
# define PD_FLOATSIZE 32
#endif
#if PD_FLOATSIZE == 32
# define PD_FLOATTYPE float
/* an unsigned int of the same size as FLOATTYPE: */
# define PD_FLOATUINTTYPE uint32_t
#elif PD_FLOATSIZE == 64
# define PD_FLOATTYPE double
# define PD_FLOATUINTTYPE uint64_t
#else
# error invalid FLOATSIZE: must be 32 or 64
#endif
typedef PD_LONGINTTYPE t_int; /* pointer-size integer */
typedef PD_FLOATTYPE t_float; /* a float type at most the same size */
typedef PD_FLOATTYPE t_floatarg; /* float type for function calls */
typedef struct _symbol
{
const char *s_name;
struct _class **s_thing;
struct _symbol *s_next;
} t_symbol;
EXTERN_STRUCT _array;
#define t_array struct _array /* g_canvas.h */
/* pointers to glist and array elements go through a "stub" which sticks
around after the glist or array is freed. The stub itself is deleted when
both the glist/array is gone and the refcount is zero, ensuring that no
gpointers are pointing here. */
#define GP_NONE 0 /* the stub points nowhere (has been cut off) */
#define GP_GLIST 1 /* the stub points to a glist element */
#define GP_ARRAY 2 /* ... or array */
typedef struct _gstub
{
union
{
struct _glist *gs_glist; /* glist we're in */
struct _array *gs_array; /* array we're in */
} gs_un;
int gs_which; /* GP_GLIST/GP_ARRAY */
int gs_refcount; /* number of gpointers pointing here */
} t_gstub;
typedef struct _gpointer /* pointer to a gobj in a glist */
{
union
{
struct _scalar *gp_scalar; /* scalar we're in (if glist) */
union word *gp_w; /* raw data (if array) */
} gp_un;
int gp_valid; /* number which must match gpointee */
t_gstub *gp_stub; /* stub which points to glist/array */
} t_gpointer;
typedef union word
{
t_float w_float;
t_symbol *w_symbol;
t_gpointer *w_gpointer;
t_array *w_array;
struct _binbuf *w_binbuf;
int w_index;
} t_word;
typedef enum
{
A_NULL,
A_FLOAT,
A_SYMBOL,
A_POINTER,
A_SEMI,
A_COMMA,
A_DEFFLOAT,
A_DEFSYM,
A_DOLLAR,
A_DOLLSYM,
A_GIMME,
A_CANT
} t_atomtype;
#define A_DEFSYMBOL A_DEFSYM /* better name for this */
typedef struct _atom
{
t_atomtype a_type;
union word a_w;
} t_atom;
EXTERN_STRUCT _class;
#define t_class struct _class
EXTERN_STRUCT _outlet;
#define t_outlet struct _outlet
EXTERN_STRUCT _inlet;
#define t_inlet struct _inlet
EXTERN_STRUCT _binbuf;
#define t_binbuf struct _binbuf
EXTERN_STRUCT _clock;
#define t_clock struct _clock
EXTERN_STRUCT _outconnect;
#define t_outconnect struct _outconnect
EXTERN_STRUCT _glist;
#define t_glist struct _glist
#define t_canvas struct _glist /* LATER lose this */
EXTERN_STRUCT _template;
typedef t_class *t_pd; /* pure datum: nothing but a class pointer */
typedef struct _gobj /* a graphical object */
{
t_pd g_pd; /* pure datum header (class) */
struct _gobj *g_next; /* next in list */
} t_gobj;
typedef struct _scalar /* a graphical object holding data */
{
t_gobj sc_gobj; /* header for graphical object */
t_symbol *sc_template; /* template name (LATER replace with pointer) */
t_word sc_vec[1]; /* indeterminate-length array of words */
} t_scalar;
typedef struct _text /* patchable object - graphical, with text */
{
t_gobj te_g; /* header for graphical object */
t_binbuf *te_binbuf; /* holder for the text */
t_outlet *te_outlet; /* linked list of outlets */
t_inlet *te_inlet; /* linked list of inlets */
short te_xpix; /* x&y location (within the toplevel) */
short te_ypix;
short te_width; /* requested width in chars, 0 if auto */
unsigned int te_type:2; /* from defs below */
} t_text;
#define T_TEXT 0 /* just a textual comment */
#define T_OBJECT 1 /* a MAX style patchable object */
#define T_MESSAGE 2 /* a MAX type message */
#define T_ATOM 3 /* a cell to display a number or symbol */
#define te_pd te_g.g_pd
/* t_object is synonym for t_text (LATER unify them) */
typedef struct _text t_object;
#define ob_outlet te_outlet
#define ob_inlet te_inlet
#define ob_binbuf te_binbuf
#define ob_pd te_g.g_pd
#define ob_g te_g
typedef void (*t_method)(void);
typedef void *(*t_newmethod)(void);
/* in ARM 64 a varargs prototype generates a different function call sequence
from a fixed one, so in that special case we make a more restrictive
definition for t_gotfn. This will break some code in the "chaos" package
in Pd extended. (that code will run incorrectly anyhow so why not catch it
at compile time anyhow.) */
#if defined(__APPLE__) && defined(__aarch64__)
typedef void (*t_gotfn)(void *x);
#else
typedef void (*t_gotfn)(void *x, ...);
#endif
/* ---------------- pre-defined objects and symbols --------------*/
EXTERN t_pd pd_objectmaker; /* factory for creating "object" boxes */
EXTERN t_pd pd_canvasmaker; /* factory for creating canvases */
/* --------- prototypes from the central message system ----------- */
EXTERN void pd_typedmess(t_pd *x, t_symbol *s, int argc, t_atom *argv);
EXTERN void pd_forwardmess(t_pd *x, int argc, t_atom *argv);
EXTERN t_symbol *gensym(const char *s);
EXTERN t_gotfn getfn(const t_pd *x, t_symbol *s);
EXTERN t_gotfn zgetfn(const t_pd *x, t_symbol *s);
EXTERN void nullfn(void);
EXTERN void pd_vmess(t_pd *x, t_symbol *s, const char *fmt, ...);
/* the following macros are for sending non-type-checkable messages, i.e.,
using function lookup but circumventing type checking on arguments. Only
use for internal messaging protected by A_CANT so that the message can't
be generated at patch level. */
#define mess0(x, s) ((*getfn((x), (s)))((x)))
typedef void (*t_gotfn1)(void *x, void *arg1);
#define mess1(x, s, a) ((*(t_gotfn1)getfn((x), (s)))((x), (a)))
typedef void (*t_gotfn2)(void *x, void *arg1, void *arg2);
#define mess2(x, s, a,b) ((*(t_gotfn2)getfn((x), (s)))((x), (a),(b)))
typedef void (*t_gotfn3)(void *x, void *arg1, void *arg2, void *arg3);
#define mess3(x, s, a,b,c) ((*(t_gotfn3)getfn((x), (s)))((x), (a),(b),(c)))
typedef void (*t_gotfn4)(void *x,
void *arg1, void *arg2, void *arg3, void *arg4);
#define mess4(x, s, a,b,c,d) \
((*(t_gotfn4)getfn((x), (s)))((x), (a),(b),(c),(d)))
typedef void (*t_gotfn5)(void *x,
void *arg1, void *arg2, void *arg3, void *arg4, void *arg5);
#define mess5(x, s, a,b,c,d,e) \
((*(t_gotfn5)getfn((x), (s)))((x), (a),(b),(c),(d),(e)))
EXTERN void obj_list(t_object *x, t_symbol *s, int argc, t_atom *argv);
EXTERN t_pd *pd_newest(void);
/* --------------- memory management -------------------- */
EXTERN void *getbytes(size_t nbytes);
EXTERN void *getzbytes(size_t nbytes);
EXTERN void *copybytes(const void *src, size_t nbytes);
EXTERN void freebytes(void *x, size_t nbytes);
EXTERN void *resizebytes(void *x, size_t oldsize, size_t newsize);
/* -------------------- atoms ----------------------------- */
#define SETSEMI(atom) ((atom)->a_type = A_SEMI, (atom)->a_w.w_index = 0)
#define SETCOMMA(atom) ((atom)->a_type = A_COMMA, (atom)->a_w.w_index = 0)
#define SETPOINTER(atom, gp) ((atom)->a_type = A_POINTER, \
(atom)->a_w.w_gpointer = (gp))
#define SETFLOAT(atom, f) ((atom)->a_type = A_FLOAT, (atom)->a_w.w_float = (f))
#define SETSYMBOL(atom, s) ((atom)->a_type = A_SYMBOL, \
(atom)->a_w.w_symbol = (s))
#define SETDOLLAR(atom, n) ((atom)->a_type = A_DOLLAR, \
(atom)->a_w.w_index = (n))
#define SETDOLLSYM(atom, s) ((atom)->a_type = A_DOLLSYM, \
(atom)->a_w.w_symbol= (s))
EXTERN t_float atom_getfloat(const t_atom *a);
EXTERN t_int atom_getint(const t_atom *a);
EXTERN t_symbol *atom_getsymbol(const t_atom *a);
EXTERN t_symbol *atom_gensym(const t_atom *a);
EXTERN t_float atom_getfloatarg(int which, int argc, const t_atom *argv);
EXTERN t_int atom_getintarg(int which, int argc, const t_atom *argv);
EXTERN t_symbol *atom_getsymbolarg(int which, int argc, const t_atom *argv);
EXTERN void atom_string(const t_atom *a, char *buf, unsigned int bufsize);
/* ------------------ binbufs --------------- */
EXTERN t_binbuf *binbuf_new(void);
EXTERN void binbuf_free(t_binbuf *x);
EXTERN t_binbuf *binbuf_duplicate(const t_binbuf *y);
EXTERN void binbuf_text(t_binbuf *x, const char *text, size_t size);
EXTERN void binbuf_gettext(const t_binbuf *x, char **bufp, int *lengthp);
EXTERN void binbuf_clear(t_binbuf *x);
EXTERN void binbuf_add(t_binbuf *x, int argc, const t_atom *argv);
EXTERN void binbuf_addv(t_binbuf *x, const char *fmt, ...);
EXTERN void binbuf_addbinbuf(t_binbuf *x, const t_binbuf *y);
EXTERN void binbuf_addsemi(t_binbuf *x);
EXTERN void binbuf_restore(t_binbuf *x, int argc, const t_atom *argv);
EXTERN void binbuf_print(const t_binbuf *x);
EXTERN int binbuf_getnatom(const t_binbuf *x);
EXTERN t_atom *binbuf_getvec(const t_binbuf *x);
EXTERN int binbuf_resize(t_binbuf *x, int newsize);
EXTERN void binbuf_eval(const t_binbuf *x, t_pd *target, int argc, const t_atom *argv);
EXTERN int binbuf_read(t_binbuf *b, const char *filename, const char *dirname,
int crflag);
EXTERN int binbuf_read_via_canvas(t_binbuf *b, const char *filename, const t_canvas *canvas,
int crflag);
EXTERN int binbuf_read_via_path(t_binbuf *b, const char *filename, const char *dirname,
int crflag);
EXTERN int binbuf_write(const t_binbuf *x, const char *filename, const char *dir,
int crflag);
EXTERN void binbuf_evalfile(t_symbol *name, t_symbol *dir);
EXTERN t_symbol *binbuf_realizedollsym(t_symbol *s, int ac, const t_atom *av,
int tonew);
/* ------------------ clocks --------------- */
EXTERN t_clock *clock_new(void *owner, t_method fn);
EXTERN void clock_set(t_clock *x, double systime);
EXTERN void clock_delay(t_clock *x, double delaytime);
EXTERN void clock_unset(t_clock *x);
EXTERN void clock_setunit(t_clock *x, double timeunit, int sampflag);
EXTERN double clock_getlogicaltime(void);
EXTERN double clock_getsystime(void); /* OBSOLETE; use clock_getlogicaltime() */
EXTERN double clock_gettimesince(double prevsystime);
EXTERN double clock_gettimesincewithunits(double prevsystime,
double units, int sampflag);
EXTERN double clock_getsystimeafter(double delaytime);
EXTERN void clock_free(t_clock *x);
/* ----------------- pure data ---------------- */
EXTERN t_pd *pd_new(t_class *cls);
EXTERN void pd_free(t_pd *x);
EXTERN void pd_bind(t_pd *x, t_symbol *s);
EXTERN void pd_unbind(t_pd *x, t_symbol *s);
EXTERN t_pd *pd_findbyclass(t_symbol *s, const t_class *c);
EXTERN void pd_pushsym(t_pd *x);
EXTERN void pd_popsym(t_pd *x);
EXTERN void pd_bang(t_pd *x);
EXTERN void pd_pointer(t_pd *x, t_gpointer *gp);
EXTERN void pd_float(t_pd *x, t_float f);
EXTERN void pd_symbol(t_pd *x, t_symbol *s);
EXTERN void pd_list(t_pd *x, t_symbol *s, int argc, t_atom *argv);
EXTERN void pd_anything(t_pd *x, t_symbol *s, int argc, t_atom *argv);
#define pd_class(x) (*(x))
/* ----------------- pointers ---------------- */
EXTERN void gpointer_init(t_gpointer *gp);
EXTERN void gpointer_copy(const t_gpointer *gpfrom, t_gpointer *gpto);
EXTERN void gpointer_unset(t_gpointer *gp);
EXTERN int gpointer_check(const t_gpointer *gp, int headok);
/* ----------------- patchable "objects" -------------- */
EXTERN t_inlet *inlet_new(t_object *owner, t_pd *dest, t_symbol *s1,
t_symbol *s2);
EXTERN t_inlet *pointerinlet_new(t_object *owner, t_gpointer *gp);
EXTERN t_inlet *floatinlet_new(t_object *owner, t_float *fp);
EXTERN t_inlet *symbolinlet_new(t_object *owner, t_symbol **sp);
EXTERN t_inlet *signalinlet_new(t_object *owner, t_float f);
EXTERN void inlet_free(t_inlet *x);
EXTERN t_outlet *outlet_new(t_object *owner, t_symbol *s);
EXTERN void outlet_bang(t_outlet *x);
EXTERN void outlet_pointer(t_outlet *x, t_gpointer *gp);
EXTERN void outlet_float(t_outlet *x, t_float f);
EXTERN void outlet_symbol(t_outlet *x, t_symbol *s);
EXTERN void outlet_list(t_outlet *x, t_symbol *s, int argc, t_atom *argv);
EXTERN void outlet_anything(t_outlet *x, t_symbol *s, int argc, t_atom *argv);
EXTERN t_symbol *outlet_getsymbol(t_outlet *x);
EXTERN void outlet_free(t_outlet *x);
EXTERN t_object *pd_checkobject(t_pd *x);
/* -------------------- canvases -------------- */
EXTERN void glob_setfilename(void *dummy, t_symbol *name, t_symbol *dir);
EXTERN void canvas_setargs(int argc, const t_atom *argv);
EXTERN void canvas_getargs(int *argcp, t_atom **argvp);
EXTERN t_symbol *canvas_getcurrentdir(void);
EXTERN t_glist *canvas_getcurrent(void);
EXTERN void canvas_makefilename(const t_glist *c, const char *file,
char *result, int resultsize);
EXTERN t_symbol *canvas_getdir(const t_glist *x);
EXTERN char sys_font[]; /* default typeface set in s_main.c */
EXTERN char sys_fontweight[]; /* default font weight set in s_main.c */
EXTERN int sys_hostfontsize(int fontsize, int zoom);
EXTERN int sys_zoomfontwidth(int fontsize, int zoom, int worstcase);
EXTERN int sys_zoomfontheight(int fontsize, int zoom, int worstcase);
EXTERN int sys_fontwidth(int fontsize);
EXTERN int sys_fontheight(int fontsize);
EXTERN void canvas_dataproperties(t_glist *x, t_scalar *sc, t_binbuf *b);
EXTERN int canvas_open(const t_canvas *x, const char *name, const char *ext,
char *dirresult, char **nameresult, unsigned int size, int bin);
/* ---------------- widget behaviors ---------------------- */
EXTERN_STRUCT _widgetbehavior;
#define t_widgetbehavior struct _widgetbehavior
EXTERN_STRUCT _parentwidgetbehavior;
#define t_parentwidgetbehavior struct _parentwidgetbehavior
EXTERN const t_parentwidgetbehavior *pd_getparentwidget(t_pd *x);
/* -------------------- classes -------------- */
#define CLASS_DEFAULT 0 /* flags for new classes below */
#define CLASS_PD 1
#define CLASS_GOBJ 2
#define CLASS_PATCHABLE 3
#define CLASS_NOINLET 8
#define CLASS_TYPEMASK 3
EXTERN t_class *class_new(t_symbol *name, t_newmethod newmethod,
t_method freemethod, size_t size, int flags, t_atomtype arg1, ...);
EXTERN t_class *class_new64(t_symbol *name, t_newmethod newmethod,
t_method freemethod, size_t size, int flags, t_atomtype arg1, ...);
EXTERN void class_free(t_class *c);
#ifdef PDINSTANCE
EXTERN t_class *class_getfirst(void);
#endif
EXTERN void class_addcreator(t_newmethod newmethod, t_symbol *s,
t_atomtype type1, ...);
EXTERN void class_addmethod(t_class *c, t_method fn, t_symbol *sel,
t_atomtype arg1, ...);
EXTERN void class_addbang(t_class *c, t_method fn);
EXTERN void class_addpointer(t_class *c, t_method fn);
EXTERN void class_doaddfloat(t_class *c, t_method fn);
EXTERN void class_addsymbol(t_class *c, t_method fn);
EXTERN void class_addlist(t_class *c, t_method fn);
EXTERN void class_addanything(t_class *c, t_method fn);
EXTERN void class_sethelpsymbol(t_class *c, t_symbol *s);
EXTERN void class_setwidget(t_class *c, const t_widgetbehavior *w);
EXTERN void class_setparentwidget(t_class *c, const t_parentwidgetbehavior *w);
EXTERN const char *class_getname(const t_class *c);
EXTERN const char *class_gethelpname(const t_class *c);
EXTERN const char *class_gethelpdir(const t_class *c);
EXTERN void class_setdrawcommand(t_class *c);
EXTERN int class_isdrawcommand(const t_class *c);
EXTERN void class_domainsignalin(t_class *c, int onset);
EXTERN void class_set_extern_dir(t_symbol *s);
#define CLASS_MAINSIGNALIN(c, type, field) \
class_domainsignalin(c, (char *)(&((type *)0)->field) - (char *)0)
/* prototype for functions to save Pd's to a binbuf */
typedef void (*t_savefn)(t_gobj *x, t_binbuf *b);
EXTERN void class_setsavefn(t_class *c, t_savefn f);
EXTERN t_savefn class_getsavefn(const t_class *c);
EXTERN void obj_saveformat(const t_object *x, t_binbuf *bb); /* add format to bb */
/* prototype for functions to open properties dialogs */
typedef void (*t_propertiesfn)(t_gobj *x, struct _glist *glist);
EXTERN void class_setpropertiesfn(t_class *c, t_propertiesfn f);
EXTERN t_propertiesfn class_getpropertiesfn(const t_class *c);
typedef void (*t_classfreefn)(t_class *);
EXTERN void class_setfreefn(t_class *c, t_classfreefn fn);
#ifndef PD_CLASS_DEF
#define class_addbang(x, y) class_addbang((x), (t_method)(y))
#define class_addpointer(x, y) class_addpointer((x), (t_method)(y))
#define class_addfloat(x, y) class_doaddfloat((x), (t_method)(y))
#define class_addsymbol(x, y) class_addsymbol((x), (t_method)(y))
#define class_addlist(x, y) class_addlist((x), (t_method)(y))
#define class_addanything(x, y) class_addanything((x), (t_method)(y))
#endif
#if PD_FLOATSIZE == 64
# define class_new class_new64
#endif
/* ------------ printing --------------------------------- */
EXTERN void post(const char *fmt, ...);
EXTERN void startpost(const char *fmt, ...);
EXTERN void poststring(const char *s);
EXTERN void postfloat(t_floatarg f);
EXTERN void postatom(int argc, const t_atom *argv);
EXTERN void endpost(void);
EXTERN void error(const char *fmt, ...) ATTRIBUTE_FORMAT_PRINTF(1, 2);
EXTERN void verbose(int level, const char *fmt, ...) ATTRIBUTE_FORMAT_PRINTF(2, 3);
EXTERN void bug(const char *fmt, ...) ATTRIBUTE_FORMAT_PRINTF(1, 2);
EXTERN void pd_error(const void *object, const char *fmt, ...) ATTRIBUTE_FORMAT_PRINTF(2, 3);
EXTERN void logpost(const void *object, const int level, const char *fmt, ...)
ATTRIBUTE_FORMAT_PRINTF(3, 4);
/* ------------ system interface routines ------------------- */
EXTERN int sys_isabsolutepath(const char *dir);
EXTERN void sys_bashfilename(const char *from, char *to);
EXTERN void sys_unbashfilename(const char *from, char *to);
EXTERN int open_via_path(const char *dir, const char *name, const char *ext,
char *dirresult, char **nameresult, unsigned int size, int bin);
EXTERN int sched_geteventno(void);
EXTERN double sys_getrealtime(void);
EXTERN int (*sys_idlehook)(void); /* hook to add idle time computation */
/* Win32's open()/fopen() do not handle UTF-8 filenames so we need
* these internal versions that handle UTF-8 filenames the same across
* all platforms. They are recommended for use in external
* objectclasses as well so they work with Unicode filenames on Windows */
EXTERN int sys_open(const char *path, int oflag, ...);
EXTERN int sys_close(int fd);
EXTERN FILE *sys_fopen(const char *filename, const char *mode);
EXTERN int sys_fclose(FILE *stream);
/* ------------ threading ------------------- */
EXTERN void sys_lock(void);
EXTERN void sys_unlock(void);
EXTERN int sys_trylock(void);
/* --------------- signals ----------------------------------- */
typedef PD_FLOATTYPE t_sample;
typedef union _sampleint_union {
t_sample f;
PD_FLOATUINTTYPE i;
} t_sampleint_union;
#define MAXLOGSIG 32
#define MAXSIGSIZE (1 << MAXLOGSIG)
typedef struct _signal
{
int s_n; /* number of points in the array */
t_sample *s_vec; /* the array */
t_float s_sr; /* sample rate */
int s_refcount; /* number of times used */
int s_isborrowed; /* whether we're going to borrow our array */
struct _signal *s_borrowedfrom; /* signal to borrow it from */
struct _signal *s_nextfree; /* next in freelist */
struct _signal *s_nextused; /* next in used list */
int s_vecsize; /* allocated size of array in points */
} t_signal;
typedef t_int *(*t_perfroutine)(t_int *args);
EXTERN t_int *plus_perform(t_int *args);
EXTERN t_int *zero_perform(t_int *args);
EXTERN t_int *copy_perform(t_int *args);
EXTERN void dsp_add_plus(t_sample *in1, t_sample *in2, t_sample *out, int n);
EXTERN void dsp_add_copy(t_sample *in, t_sample *out, int n);
EXTERN void dsp_add_scalarcopy(t_float *in, t_sample *out, int n);
EXTERN void dsp_add_zero(t_sample *out, int n);
EXTERN int sys_getblksize(void);
EXTERN t_float sys_getsr(void);
EXTERN int sys_get_inchannels(void);
EXTERN int sys_get_outchannels(void);
EXTERN void dsp_add(t_perfroutine f, int n, ...);
EXTERN void dsp_addv(t_perfroutine f, int n, t_int *vec);
EXTERN void pd_fft(t_float *buf, int npoints, int inverse);
EXTERN int ilog2(int n);
EXTERN void mayer_fht(t_sample *fz, int n);
EXTERN void mayer_fft(int n, t_sample *real, t_sample *imag);
EXTERN void mayer_ifft(int n, t_sample *real, t_sample *imag);
EXTERN void mayer_realfft(int n, t_sample *real);
EXTERN void mayer_realifft(int n, t_sample *real);
EXTERN float *cos_table;
#define LOGCOSTABSIZE 9
#define COSTABSIZE (1<<LOGCOSTABSIZE)
EXTERN int canvas_suspend_dsp(void);
EXTERN void canvas_resume_dsp(int oldstate);
EXTERN void canvas_update_dsp(void);
EXTERN int canvas_dspstate;
/* up/downsampling */
typedef struct _resample
{
int method; /* up/downsampling method ID */
int downsample; /* downsampling factor */
int upsample; /* upsampling factor */
t_sample *s_vec; /* here we hold the resampled data */
int s_n;
t_sample *coeffs; /* coefficients for filtering... */
int coefsize;
t_sample *buffer; /* buffer for filtering */
int bufsize;
} t_resample;
EXTERN void resample_init(t_resample *x);
EXTERN void resample_free(t_resample *x);
EXTERN void resample_dsp(t_resample *x, t_sample *in, int insize, t_sample *out, int outsize, int method);
EXTERN void resamplefrom_dsp(t_resample *x, t_sample *in, int insize, int outsize, int method);
EXTERN void resampleto_dsp(t_resample *x, t_sample *out, int insize, int outsize, int method);
/* ----------------------- utility functions for signals -------------- */
EXTERN t_float mtof(t_float);
EXTERN t_float ftom(t_float);
EXTERN t_float rmstodb(t_float);
EXTERN t_float powtodb(t_float);
EXTERN t_float dbtorms(t_float);
EXTERN t_float dbtopow(t_float);
EXTERN t_float q8_sqrt(t_float);
EXTERN t_float q8_rsqrt(t_float);
#ifndef N32
EXTERN t_float qsqrt(t_float); /* old names kept for extern compatibility */
EXTERN t_float qrsqrt(t_float);
#endif
/* --------------------- data --------------------------------- */
/* graphical arrays */
EXTERN_STRUCT _garray;
#define t_garray struct _garray
EXTERN t_class *garray_class;
EXTERN int garray_getfloatarray(t_garray *x, int *size, t_float **vec);
EXTERN int garray_getfloatwords(t_garray *x, int *size, t_word **vec);
EXTERN void garray_redraw(t_garray *x);
EXTERN int garray_npoints(t_garray *x);
EXTERN char *garray_vec(t_garray *x);
EXTERN void garray_resize(t_garray *x, t_floatarg f); /* avoid; use this: */
EXTERN void garray_resize_long(t_garray *x, long n); /* better version */
EXTERN void garray_usedindsp(t_garray *x);
EXTERN void garray_setsaveit(t_garray *x, int saveit);
EXTERN t_glist *garray_getglist(t_garray *x);
EXTERN t_array *garray_getarray(t_garray *x);
EXTERN t_class *scalar_class;
EXTERN t_float *value_get(t_symbol *s);
EXTERN void value_release(t_symbol *s);
EXTERN int value_getfloat(t_symbol *s, t_float *f);
EXTERN int value_setfloat(t_symbol *s, t_float f);
/* ------- GUI interface - functions to send strings to TK --------- */
typedef void (*t_guicallbackfn)(t_gobj *client, t_glist *glist);
EXTERN void sys_vgui(const char *fmt, ...);
EXTERN void sys_gui(const char *s);
EXTERN void sys_pretendguibytes(int n);
EXTERN void sys_queuegui(void *client, t_glist *glist, t_guicallbackfn f);
EXTERN void sys_unqueuegui(void *client);
/* dialog window creation and destruction */
EXTERN void gfxstub_new(t_pd *owner, void *key, const char *cmd);
EXTERN void gfxstub_deleteforkey(void *key);
extern t_class *glob_pdobject; /* object to send "pd" messages */
/*------------- Max 0.26 compatibility --------------------*/
/* the following reflects the new way classes are laid out, with the class
pointing to the messlist and not vice versa. Externs shouldn't feel it. */
typedef t_class *t_externclass;
EXTERN void c_extern(t_externclass *cls, t_newmethod newroutine,
t_method freeroutine, t_symbol *name, size_t size, int tiny, \
t_atomtype arg1, ...);
EXTERN void c_addmess(t_method fn, t_symbol *sel, t_atomtype arg1, ...);
#define t_getbytes getbytes
#define t_freebytes freebytes
#define t_resizebytes resizebytes
#define typedmess pd_typedmess
#define vmess pd_vmess
/* A definition to help gui objects straddle 0.34-0.35 changes. If this is
defined, there is a "te_xpix" field in objects, not a "te_xpos" as before: */
#define PD_USE_TE_XPIX
#ifndef _MSC_VER /* Microoft compiler can't handle "inline" function/macros */
#if defined(__i386__) || defined(__x86_64__) || defined(__arm__)
/* a test for NANs and denormals. Should only be necessary on i386. */
#if PD_FLOATSIZE == 32
typedef union
{
t_float f;
unsigned int ui;
}t_bigorsmall32;
static inline int PD_BADFLOAT(t_float f) /* malformed float */
{
t_bigorsmall32 pun;
pun.f = f;
pun.ui &= 0x7f800000;
return((pun.ui == 0) | (pun.ui == 0x7f800000));
}
static inline int PD_BIGORSMALL(t_float f) /* exponent outside (-64,64) */
{
t_bigorsmall32 pun;
pun.f = f;
return((pun.ui & 0x20000000) == ((pun.ui >> 1) & 0x20000000));
}
#elif PD_FLOATSIZE == 64
typedef union
{
t_float f;
unsigned int ui[2];
}t_bigorsmall64;
static inline int PD_BADFLOAT(t_float f) /* malformed double */
{
t_bigorsmall64 pun;
pun.f = f;
pun.ui[1] &= 0x7ff00000;
return((pun.ui[1] == 0) | (pun.ui[1] == 0x7ff00000));
}
static inline int PD_BIGORSMALL(t_float f) /* exponent outside (-512,512) */
{
t_bigorsmall64 pun;
pun.f = f;
return((pun.ui[1] & 0x20000000) == ((pun.ui[1] >> 1) & 0x20000000));
}
#endif /* PD_FLOATSIZE */
#else /* not INTEL or ARM */
#define PD_BADFLOAT(f) 0
#define PD_BIGORSMALL(f) 0
#endif
#else /* _MSC_VER */
#if PD_FLOATSIZE == 32
#define PD_BADFLOAT(f) ((((*(unsigned int*)&(f))&0x7f800000)==0) || \
(((*(unsigned int*)&(f))&0x7f800000)==0x7f800000))
/* more stringent test: anything not between 1e-19 and 1e19 in absolute val */
#define PD_BIGORSMALL(f) ((((*(unsigned int*)&(f))&0x60000000)==0) || \
(((*(unsigned int*)&(f))&0x60000000)==0x60000000))
#else /* 64 bits... don't know what to do here */
#define PD_BADFLOAT(f) (!(((f) >= 0) || ((f) <= 0)))
#define PD_BIGORSMALL(f) ((f) > 1e150 || (f) < -1e150 \
|| (f) > -1e-150 && (f) < 1e-150 )
#endif
#endif /* _MSC_VER */
/* get version number at run time */
EXTERN void sys_getversion(int *major, int *minor, int *bugfix);
EXTERN_STRUCT _instancemidi;
#define t_instancemidi struct _instancemidi
EXTERN_STRUCT _instanceinter;
#define t_instanceinter struct _instanceinter
EXTERN_STRUCT _instancecanvas;
#define t_instancecanvas struct _instancecanvas
EXTERN_STRUCT _instanceugen;
#define t_instanceugen struct _instanceugen
EXTERN_STRUCT _instancestuff;
#define t_instancestuff struct _instancestuff
#ifndef PDTHREADS
#define PDTHREADS 1
#endif
struct _pdinstance
{
double pd_systime; /* global time in Pd ticks */
t_clock *pd_clock_setlist; /* list of set clocks */
t_canvas *pd_canvaslist; /* list of all root canvases */
struct _template *pd_templatelist; /* list of all templates */
int pd_instanceno; /* ordinal number of this instance */
t_symbol **pd_symhash; /* symbol table hash table */
t_instancemidi *pd_midi; /* private stuff for x_midi.c */
t_instanceinter *pd_inter; /* private stuff for s_inter.c */
t_instanceugen *pd_ugen; /* private stuff for d_ugen.c */
t_instancecanvas *pd_gui; /* semi-private stuff in g_canvas.h */
t_instancestuff *pd_stuff; /* semi-private stuff in s_stuff.h */
t_pd *pd_newest; /* most recently created object */
#ifdef PDINSTANCE
t_symbol pd_s_pointer;
t_symbol pd_s_float;
t_symbol pd_s_symbol;
t_symbol pd_s_bang;
t_symbol pd_s_list;
t_symbol pd_s_anything;
t_symbol pd_s_signal;
t_symbol pd_s__N;
t_symbol pd_s__X;
t_symbol pd_s_x;
t_symbol pd_s_y;
t_symbol pd_s_;
#endif
#if PDTHREADS
int pd_islocked;
#endif
};
#define t_pdinstance struct _pdinstance
EXTERN t_pdinstance pd_maininstance;
/* m_pd.c */
#ifdef PDINSTANCE
EXTERN t_pdinstance *pdinstance_new(void);
EXTERN void pd_setinstance(t_pdinstance *x);
EXTERN void pdinstance_free(t_pdinstance *x);
#endif /* PDINSTANCE */
#if defined(PDTHREADS) && defined(PDINSTANCE)
#ifdef _MSC_VER
#define PERTHREAD __declspec(thread)
#else
#define PERTHREAD __thread
#endif /* _MSC_VER */
#else
#define PERTHREAD
#endif
#ifdef PDINSTANCE
extern PERTHREAD t_pdinstance *pd_this;
EXTERN t_pdinstance **pd_instances;
EXTERN int pd_ninstances;
#else
#define pd_this (&pd_maininstance)
#endif /* PDINSTANCE */
#ifdef PDINSTANCE
#define s_pointer (pd_this->pd_s_pointer)
#define s_float (pd_this->pd_s_float)
#define s_symbol (pd_this->pd_s_symbol)
#define s_bang (pd_this->pd_s_bang)
#define s_list (pd_this->pd_s_list)
#define s_anything (pd_this->pd_s_anything)
#define s_signal (pd_this->pd_s_signal)
#define s__N (pd_this->pd_s__N)
#define s__X (pd_this->pd_s__X)
#define s_x (pd_this->pd_s_x)
#define s_y (pd_this->pd_s_y)
#define s_ (pd_this->pd_s_)
#else
EXTERN t_symbol s_pointer, s_float, s_symbol, s_bang, s_list, s_anything,
s_signal, s__N, s__X, s_x, s_y, s_;
#endif
EXTERN t_canvas *pd_getcanvaslist(void);
EXTERN int pd_getdspstate(void);
/* x_text.c */
EXTERN t_binbuf *text_getbufbyname(t_symbol *s); /* get binbuf from text obj */
EXTERN void text_notifybyname(t_symbol *s); /* notify it was modified */
#if defined(_LANGUAGE_C_PLUS_PLUS) || defined(__cplusplus)
}
#endif
#define __m_pd_h_
#endif /* __m_pd_h_ */

View file

@ -0,0 +1,336 @@
// SPDX-License-Identifier: BSD-2-Clause
// This code is part of the sfizz library and is licensed under a BSD 2-clause
// license. You should have receive a LICENSE.md file along with the code.
// If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz
#include "GitBuildId.h"
#include <m_pd.h>
#include <sfizz.h>
#include <sfizz/import/sfizz_import.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
static t_class* cls_sfizz_tilde;
typedef struct _sfizz_tilde {
t_object obj;
t_outlet* outputs[2];
t_inlet* input_cc;
t_inlet* input_bend;
t_inlet* input_touch;
t_inlet* input_polytouch;
sfizz_synth_t* synth;
int midi[3];
int midinum;
t_symbol* dir;
char* filepath;
} t_sfizz_tilde;
static t_float clamp01(t_float x)
{
x = (x > 0) ? x : 0;
x = (x < 1) ? x : 1;
return x;
}
static t_float clampB1(t_float x)
{
x = (x > -1) ? x : -1;
x = (x < 1) ? x : 1;
return x;
}
static void sfizz_tilde_set_file(t_sfizz_tilde* self, const char* file)
{
const char* dir = self->dir->s_name;
char* filepath;
if (file[0] != '\0') {
filepath = malloc(strlen(dir) + 1 + strlen(file) + 1);
sprintf(filepath, "%s/%s", dir, file);
}
else {
filepath = malloc(1);
*filepath = '\0';
}
free(self->filepath);
self->filepath = filepath;
}
static bool sfizz_tilde_do_load(t_sfizz_tilde* self)
{
bool loaded;
if (self->filepath[0] != '\0')
loaded = sfizz_load_or_import_file(self->synth, self->filepath, NULL);
else
loaded = sfizz_load_string(self->synth, "default.sfz", "<region>sample=*sine");
return loaded;
}
static void* sfizz_tilde_new(t_symbol* sym, int argc, t_atom argv[])
{
(void)sym;
t_sfizz_tilde* self = (t_sfizz_tilde*)pd_new(cls_sfizz_tilde);
const char* file;
if (argc == 0)
file = "";
else if (argc == 1 && argv[0].a_type == A_SYMBOL)
file = argv[0].a_w.w_symbol->s_name;
else {
pd_free((t_pd*)self);
return NULL;
}
self->dir = canvas_getcurrentdir();
self->outputs[0] = outlet_new(&self->obj, &s_signal);
self->outputs[1] = outlet_new(&self->obj, &s_signal);
self->input_cc = inlet_new(&self->obj, &self->obj.ob_pd, &s_float, gensym("cc"));
self->input_bend = inlet_new(&self->obj, &self->obj.ob_pd, &s_float, gensym("bend"));
self->input_touch = inlet_new(&self->obj, &self->obj.ob_pd, &s_float, gensym("touch"));
self->input_polytouch = inlet_new(&self->obj, &self->obj.ob_pd, &s_float, gensym("polytouch"));
sfizz_synth_t* synth = sfizz_create_synth();
self->synth = synth;
sfizz_set_sample_rate(synth, sys_getsr());
sfizz_set_samples_per_block(synth, sys_getblksize());
sfizz_tilde_set_file(self, file);
if (!sfizz_tilde_do_load(self)) {
pd_free((t_pd*)self);
return NULL;
}
return self;
}
static void sfizz_tilde_free(t_sfizz_tilde* self)
{
if (self->filepath)
free(self->filepath);
if (self->synth)
sfizz_free(self->synth);
if (self->outputs[0])
outlet_free(self->outputs[0]);
if (self->outputs[1])
outlet_free(self->outputs[1]);
if (self->input_cc)
inlet_free(self->input_cc);
if (self->input_bend)
inlet_free(self->input_bend);
if (self->input_touch)
inlet_free(self->input_touch);
if (self->input_polytouch)
inlet_free(self->input_polytouch);
}
static t_int* sfizz_tilde_perform(t_int* w)
{
t_sfizz_tilde* self;
t_sample* outputs[2];
t_int nframes;
w++;
self = (t_sfizz_tilde*)*w++;
outputs[0] = (t_sample*)*w++;
outputs[1] = (t_sample*)*w++;
nframes = (t_int)*w++;
sfizz_render_block(self->synth, outputs, 2, nframes);
return w;
}
static void sfizz_tilde_dsp(t_sfizz_tilde* self, t_signal** sp)
{
dsp_add(
&sfizz_tilde_perform, 4, (t_int)self,
(t_int)sp[0]->s_vec, (t_int)sp[1]->s_vec, (t_int)sp[0]->s_n);
}
static void sfizz_tilde_list(t_sfizz_tilde* self, t_symbol* sym, int argc, t_atom* argv)
{
(void)sym;
if (argc == 2 && argv[0].a_type == A_FLOAT && argv[1].a_type == A_FLOAT) {
int key = (int)argv[0].a_w.w_float;
if (key < 0 || key > 127)
return;
t_float vel = clamp01(argv[1].a_w.w_float / 127);
if (vel > 0)
sfizz_send_hd_note_on(self->synth, 0, key, vel);
else
sfizz_send_hd_note_off(self->synth, 0, key, 0);
}
}
static void sfizz_tilde_midiin(t_sfizz_tilde* self, t_float f)
{
int byte = (int)f;
bool isstatus = (byte & 0x80) != 0;
int* midi = self->midi;
int midinum = self->midinum;
//
if (isstatus) {
midi[0] = byte;
midinum = 1;
}
else if (midinum != -1 && midinum < 3)
midi[midinum++] = byte;
else
midinum = -1;
//
switch (midinum) {
case 2:
switch (midi[0] & 0xf0) {
case 0xd0: // channel aftertouch
sfizz_send_channel_aftertouch(self->synth, 0, midi[1]);
break;
}
break;
case 3:
switch (midi[0] & 0xf0) {
case 0x90: // note on
if (midi[2] == 0)
goto noteoff;
sfizz_send_note_on(self->synth, 0, midi[1], midi[2]);
break;
case 0x80: // note off
noteoff:
sfizz_send_note_off(self->synth, 0, midi[1], midi[2]);
break;
case 0xb0: // controller
sfizz_send_cc(self->synth, 0, midi[1], midi[2]);
break;
case 0xa0: // key aftertouch
sfizz_send_poly_aftertouch(self->synth, 0, midi[1], midi[2]);
break;
case 0xe0: // pitch bend
sfizz_send_pitch_wheel(self->synth, 0, (midi[1] + (midi[2] << 7)) - 8192);
break;
}
break;
}
self->midinum = midinum;
}
static void sfizz_tilde_load(t_sfizz_tilde* self, t_symbol* sym)
{
sfizz_tilde_set_file(self, sym->s_name);
sfizz_tilde_do_load(self);
}
static void sfizz_tilde_reload(t_sfizz_tilde* self, t_float value)
{
(void)value;
sfizz_tilde_do_load(self);
}
static void sfizz_tilde_hdcc(t_sfizz_tilde* self, t_float f1, t_float f2)
{
int cc = (int)f1;
if (cc < 0 || cc >= SFIZZ_NUM_CCS)
return;
sfizz_automate_hdcc(self->synth, 0, (int)cc, clamp01(f2));
}
static void sfizz_tilde_cc(t_sfizz_tilde* self, t_float f1, t_float f2)
{
sfizz_tilde_hdcc(self, f1, f2 / 127);
}
static void sfizz_tilde_hdbend(t_sfizz_tilde* self, t_float f1)
{
sfizz_send_hd_pitch_wheel(self->synth, 0, clampB1(f1));
}
static void sfizz_tilde_bend(t_sfizz_tilde* self, t_float f1)
{
return sfizz_tilde_hdbend(self, f1 / 8191);
}
static void sfizz_tilde_hdtouch(t_sfizz_tilde* self, t_float f1)
{
sfizz_send_hd_channel_aftertouch(self->synth, 0, clamp01(f1));
}
static void sfizz_tilde_touch(t_sfizz_tilde* self, t_float f1)
{
sfizz_tilde_hdtouch(self, f1 / 127);
}
static void sfizz_tilde_hdpolytouch(t_sfizz_tilde* self, t_float key, t_float f2)
{
if (key < 0 || key > 127)
return;
sfizz_send_hd_poly_aftertouch(self->synth, 0, (int)key, clamp01(f2));
}
static void sfizz_tilde_polytouch(t_sfizz_tilde* self, t_float f1, t_float f2)
{
sfizz_tilde_hdpolytouch(self, f1, f2 / 127);
}
static void sfizz_tilde_voices(t_sfizz_tilde* self, t_float f1)
{
int numvoices = (int)f1;
numvoices = (numvoices < 1) ? 1 : numvoices;
sfizz_set_num_voices(self->synth, numvoices);
}
#if defined(_WIN32)
__declspec(dllexport)
#else
__attribute__((visibility("default")))
#endif
void sfizz_setup()
{
if (GitBuildId[0])
post("sfizz external for Puredata, version '%s.%s'", SFIZZ_VERSION, GitBuildId);
else
post("sfizz external for Puredata, version '%s'", SFIZZ_VERSION);
cls_sfizz_tilde = class_new(
gensym("sfizz~"),
(t_newmethod)&sfizz_tilde_new,
(t_method)&sfizz_tilde_free,
sizeof(t_sfizz_tilde),
CLASS_DEFAULT, A_GIMME, A_NULL);
class_addmethod(
cls_sfizz_tilde, (t_method)&sfizz_tilde_dsp, gensym("dsp"), A_CANT, A_NULL);
class_addlist(
cls_sfizz_tilde, (t_method)&sfizz_tilde_list);
class_addmethod(
cls_sfizz_tilde, (t_method)&sfizz_tilde_midiin, &s_float, A_FLOAT, A_NULL);
class_addmethod(
cls_sfizz_tilde, (t_method)&sfizz_tilde_load, gensym("load"), A_DEFSYM, A_NULL);
class_addmethod(
cls_sfizz_tilde, (t_method)&sfizz_tilde_reload, gensym("reload"), A_DEFFLOAT, A_NULL);
class_addmethod(
cls_sfizz_tilde, (t_method)&sfizz_tilde_cc, gensym("cc"), A_FLOAT, A_FLOAT, A_NULL);
class_addmethod(
cls_sfizz_tilde, (t_method)&sfizz_tilde_hdcc, gensym("hdcc"), A_FLOAT, A_FLOAT, A_NULL);
class_addmethod(
cls_sfizz_tilde, (t_method)&sfizz_tilde_bend, gensym("bend"), A_FLOAT, A_NULL);
class_addmethod(
cls_sfizz_tilde, (t_method)&sfizz_tilde_hdbend, gensym("hdbend"), A_FLOAT, A_NULL);
class_addmethod(
cls_sfizz_tilde, (t_method)&sfizz_tilde_touch, gensym("touch"), A_FLOAT, A_NULL);
class_addmethod(
cls_sfizz_tilde, (t_method)&sfizz_tilde_hdtouch, gensym("hdtouch"), A_FLOAT, A_NULL);
class_addmethod(
cls_sfizz_tilde, (t_method)&sfizz_tilde_polytouch, gensym("polytouch"), A_FLOAT, A_FLOAT, A_NULL);
class_addmethod(
cls_sfizz_tilde, (t_method)&sfizz_tilde_hdpolytouch, gensym("hdpolytouch"), A_FLOAT, A_FLOAT, A_NULL);
class_addmethod(
cls_sfizz_tilde, (t_method)&sfizz_tilde_voices, gensym("voices"), A_FLOAT, A_NULL);
}

View file

@ -0,0 +1,60 @@
#N canvas 614 464 709 426 12;
#X declare -lib sfizz;
#X obj 4 3 cnv 15 700 80 empty empty sfizz 20 30 0 40 -261682 -66577
0;
#X text 45 54 A synthesizer for instruments in SFZ format;
#X text 33 167 create a SFZ instrument;
#X obj 26 323 midiin;
#X text 23 300 connect MIDI-in;
#X obj 162 223 dac~;
#X msg 177 345 voices \$1;
#X text 175 301 modify the polyphony;
#X obj 376 326 hsl 127 15 0 1 0 1 empty empty empty -2 -8 0 10 -262144
-1 -1 6300 1;
#X floatatom 466 346 5 0 0 0 - - -;
#X text 372 302 modulate a parameter;
#X msg 373 346 hdcc 300 \$1;
#X obj 27 115 declare -lib sfizz;
#X obj 163 192 sfizz~ example.sfz;
#X text 18 93 load the sfizz library;
#X msg 221 115 \; pd dsp 1;
#X text 218 94 click to turn on DSP;
#X text 153 247 output stereo audio;
#X msg 398 223 reload;
#X msg 498 223 load example.sfz;
#X text 396 200 reload the instrument or load another;
#X obj 241 165 r \$0-input;
#X obj 26 348 s \$0-input;
#X obj 177 370 s \$0-input;
#X obj 373 371 s \$0-input;
#X obj 398 248 s \$0-input;
#X obj 498 248 s \$0-input;
#X obj 177 326 nbx 5 14 1 256 0 1 empty empty empty 0 -8 0 10 -262144
-1 -1 64 256;
#X text 567 302 send a note;
#X obj 568 348 makenote 100 500;
#X obj 568 373 list;
#X obj 568 398 s \$0-input;
#X msg 568 323 60;
#X obj 386 92 cnv 15 300 100 empty empty Inlets 20 12 0 14 -233017
-66577 0;
#X text 395 140 3: pitch bend;
#X text 395 154 4: channel aftertouch;
#X text 395 168 5: polyphonic aftertouch and key;
#X text 395 126 2: controller and value;
#X text 395 112 1: note and velocity;
#X connect 3 0 22 0;
#X connect 6 0 23 0;
#X connect 8 0 11 0;
#X connect 8 0 9 0;
#X connect 11 0 24 0;
#X connect 13 0 5 0;
#X connect 13 1 5 1;
#X connect 18 0 25 0;
#X connect 19 0 26 0;
#X connect 21 0 13 0;
#X connect 27 0 6 0;
#X connect 29 0 30 0;
#X connect 29 1 30 1;
#X connect 30 0 31 0;
#X connect 32 0 29 0;

View file

@ -33,6 +33,7 @@ add_library(sfizz-vst3-core STATIC EXCLUDE_FROM_ALL
SfizzVstState.h
SfizzVstParameters.h
SfizzVstUpdates.h
SfizzVstUpdates.hpp
SfizzVstUpdates.cpp
SfizzVstIDs.h
OrderedEventProcessor.h
@ -135,7 +136,8 @@ if(SFIZZ_VST)
if(NOT MSVC)
install(DIRECTORY "${PROJECT_BINARY_DIR}/${VSTPLUGIN_BUNDLE_NAME}"
DESTINATION "${VSTPLUGIN_INSTALL_DIR}"
COMPONENT "vst")
COMPONENT "vst"
USE_SOURCE_PERMISSIONS)
bundle_dylibs(vst
"${VSTPLUGIN_INSTALL_DIR}/${VSTPLUGIN_BUNDLE_NAME}/Contents/MacOS/sfizz"
COMPONENT "vst")
@ -283,7 +285,8 @@ elseif(SFIZZ_AU)
if(AUPLUGIN_INSTALL_DIR)
install(DIRECTORY "${PROJECT_BINARY_DIR}/${AUPLUGIN_BUNDLE_NAME}"
DESTINATION "${AUPLUGIN_INSTALL_DIR}"
COMPONENT "au")
COMPONENT "au"
USE_SOURCE_PERMISSIONS)
bundle_dylibs(au
"${AUPLUGIN_INSTALL_DIR}/${AUPLUGIN_BUNDLE_NAME}/Contents/MacOS/sfizz"
COMPONENT "au")
@ -354,7 +357,8 @@ if(SFIZZ_VST2)
if(VST2PLUGIN_INSTALL_DIR)
install(DIRECTORY "${PROJECT_BINARY_DIR}/${VST2PLUGIN_BUNDLE_NAME}"
DESTINATION "${VST2PLUGIN_INSTALL_DIR}"
COMPONENT "vst2")
COMPONENT "vst2"
USE_SOURCE_PERMISSIONS)
if(APPLE)
bundle_dylibs(vst2
"${VST2PLUGIN_INSTALL_DIR}/${VST2PLUGIN_BUNDLE_NAME}/Contents/Binary/sfizz.${CMAKE_SHARED_MODULE_SUFFIX}"

View file

@ -8,26 +8,38 @@
#include "SfizzVstEditor.h"
#include "SfizzVstParameters.h"
#include "SfizzVstIDs.h"
#include "plugin/InstrumentDescription.h"
#include "base/source/fstreamer.h"
#include "base/source/updatehandler.h"
#include <absl/strings/match.h>
#include <ghc/fs_std.hpp>
#include <atomic>
#include <cassert>
enum { kProgramListID = 0 };
tresult PLUGIN_API SfizzVstControllerNoUi::initialize(FUnknown* context)
{
tresult result = EditController::initialize(context);
tresult result = EditControllerEx1::initialize(context);
if (result != kResultTrue)
return result;
// initialize the update handler
Steinberg::UpdateHandler::instance();
// initialize the thread checker
threadChecker_ = Vst::ThreadChecker::create();
// create update objects
oscUpdate_ = Steinberg::owned(new OSCUpdate);
noteUpdate_ = Steinberg::owned(new NoteUpdate);
queuedUpdates_ = Steinberg::owned(new QueuedUpdates);
sfzUpdate_ = Steinberg::owned(new SfzUpdate);
sfzDescriptionUpdate_ = Steinberg::owned(new SfzDescriptionUpdate);
scalaUpdate_ = Steinberg::owned(new ScalaUpdate);
playStateUpdate_ = Steinberg::owned(new PlayStateUpdate);
// Unit
addUnit(new Vst::Unit(Steinberg::String("Root"), Vst::kRootUnitId, Vst::kNoParentUnitId, kProgramListID));
// Parameters
Vst::ParamID pid = 0;
@ -92,6 +104,22 @@ tresult PLUGIN_API SfizzVstControllerNoUi::initialize(FUnknown* context)
Vst::kRootUnitId, shortTitle));
}
// Volume levels
parameters.addParameter(
SfizzRange::getForParameter(kPidLeftLevel).createParameter(
Steinberg::String("Left level"), pid++, nullptr,
0, Vst::ParameterInfo::kIsReadOnly|Vst::ParameterInfo::kIsHidden, Vst::kRootUnitId));
parameters.addParameter(
SfizzRange::getForParameter(kPidRightLevel).createParameter(
Steinberg::String("Right level"), pid++, nullptr,
0, Vst::ParameterInfo::kIsReadOnly|Vst::ParameterInfo::kIsHidden, Vst::kRootUnitId));
// Editor status
parameters.addParameter(
SfizzRange::getForParameter(kPidEditorOpen).createParameter(
Steinberg::String("Editor open"), pid++, nullptr,
0, Vst::ParameterInfo::kIsReadOnly|Vst::ParameterInfo::kIsHidden, Vst::kRootUnitId));
// Initial MIDI mapping
for (int32 i = 0; i < Vst::kCountCtrlNumber; ++i) {
Vst::ParamID id = Vst::kNoParamId;
@ -110,12 +138,22 @@ tresult PLUGIN_API SfizzVstControllerNoUi::initialize(FUnknown* context)
midiMapping_[i] = id;
}
// Program list
IPtr<Vst::ProgramListWithPitchNames> list = Steinberg::owned(
new Vst::ProgramListWithPitchNames(Steinberg::String("Programs"), kProgramListID, Vst::kRootUnitId));
list->addProgram(Steinberg::String("Default"));
addProgramList(list);
list->addRef();
// Use linear knobs
setKnobMode(kLinearMode);
return kResultTrue;
}
tresult PLUGIN_API SfizzVstControllerNoUi::terminate()
{
return EditController::terminate();
return EditControllerEx1::terminate();
}
tresult PLUGIN_API SfizzVstControllerNoUi::getMidiControllerAssignment(int32 busIndex, int16 channel, Vst::CtrlNumber midiControllerNumber, Vst::ParamID& id)
@ -132,6 +170,44 @@ tresult PLUGIN_API SfizzVstControllerNoUi::getMidiControllerAssignment(int32 bus
return kResultTrue;
}
int32 PLUGIN_API SfizzVstControllerNoUi::getKeyswitchCount(int32 busIndex, int16 channel)
{
(void)channel;
if (busIndex != 0)
return 0;
return keyswitches_.size();
}
tresult PLUGIN_API SfizzVstControllerNoUi::getKeyswitchInfo(int32 busIndex, int16 channel, int32 keySwitchIndex, Vst::KeyswitchInfo& info)
{
(void)channel;
if (busIndex != 0)
return kResultFalse;
if (keySwitchIndex < 0 || keySwitchIndex >= keyswitches_.size())
return kResultFalse;
info = keyswitches_[keySwitchIndex];
return kResultTrue;
}
tresult PLUGIN_API SfizzVstControllerNoUi::beginEditFromHost(Vst::ParamID paramID)
{
// Note(jpc) implementing this interface is a workaround to make
// non-automatable parameters editable in Ardour (as of 6.6)
(void)paramID;
return kResultTrue;
}
tresult PLUGIN_API SfizzVstControllerNoUi::endEditFromHost(Vst::ParamID paramID)
{
(void)paramID;
return kResultTrue;
}
tresult PLUGIN_API SfizzVstControllerNoUi::getParamStringByValue(Vst::ParamID tag, Vst::ParamValue valueNormalized, Vst::String128 string)
{
switch (tag) {
@ -146,7 +222,7 @@ tresult PLUGIN_API SfizzVstControllerNoUi::getParamStringByValue(Vst::ParamID ta
}
}
return EditController::getParamStringByValue(tag, valueNormalized, string);
return EditControllerEx1::getParamStringByValue(tag, valueNormalized, string);
}
tresult PLUGIN_API SfizzVstControllerNoUi::getParamValueByString(Vst::ParamID tag, Vst::TChar* string, Vst::ParamValue& valueNormalized)
@ -164,7 +240,7 @@ tresult PLUGIN_API SfizzVstControllerNoUi::getParamValueByString(Vst::ParamID ta
}
}
return EditController::getParamValueByString(tag, string, valueNormalized);
return EditControllerEx1::getParamValueByString(tag, string, valueNormalized);
}
tresult SfizzVstControllerNoUi::setParam(Vst::ParamID tag, float value)
@ -207,109 +283,157 @@ tresult PLUGIN_API SfizzVstControllerNoUi::setComponentState(IBStream* stream)
tresult SfizzVstControllerNoUi::notify(Vst::IMessage* message)
{
// Note: may be called from any thread (Reaper)
// Note: is expected to be called from the controller thread only
tresult result = EditController::notify(message);
tresult result = EditControllerEx1::notify(message);
if (result != kResultFalse)
return result;
if (!threadChecker_->test()) {
static std::atomic_bool warn_once_flag { false };
if (!warn_once_flag.exchange(true))
fprintf(stderr, "[sfizz] controller notification arrives from the wrong thread\n");
}
const char* id = message->getMessageID();
Vst::IAttributeList* attr = message->getAttributes();
///
auto stringFromBinaryAttribute = [attr](const char* id, absl::string_view& string) -> tresult {
const void* data = nullptr;
uint32 size = 0;
tresult result = attr->getBinary(id, data, size);
if (result == kResultTrue)
string = absl::string_view(reinterpret_cast<const char*>(data), size);
return result;
};
if (!strcmp(id, SfzUpdate::getFClassID())) {
if (!sfzUpdate_->convertFromMessage(*message)) {
assert(false);
return kResultFalse;
}
///
if (!strcmp(id, "LoadedSfz")) {
absl::string_view sfzFile;
absl::string_view sfzDescriptionBlob;
// update the program name and notify
std::string name = fs::u8path(sfzUpdate_->getPath()).filename().u8string();
if (absl::EndsWithIgnoreCase(name, ".sfz"))
name.resize(name.size() - 4);
setProgramName(kProgramListID, 0, Steinberg::String(name.c_str()));
result = stringFromBinaryAttribute("File", sfzFile);
if (result != kResultTrue)
return result;
FUnknownPtr<Vst::IUnitHandler> unitHandler(getComponentHandler());
if (unitHandler)
unitHandler->notifyProgramListChange(kProgramListID, 0);
result = stringFromBinaryAttribute("Description", sfzDescriptionBlob);
if (result != kResultTrue)
return result;
sfzUpdate_->setPath(std::string(sfzFile));
//
sfzUpdate_->deferUpdate();
sfzDescriptionUpdate_->setDescription(std::string(sfzDescriptionBlob));
}
else if (!strcmp(id, SfzDescriptionUpdate::getFClassID())) {
if (!sfzDescriptionUpdate_->convertFromMessage(*message)) {
assert(false);
return kResultFalse;
}
// parse the description blob
const InstrumentDescription desc = parseDescriptionBlob(
sfzDescriptionUpdate_->getDescription());
// update pitch names and notify
Vst::ProgramListWithPitchNames* list =
static_cast<Vst::ProgramListWithPitchNames*>(getProgramList(kProgramListID));
for (int16 pitch = 0; pitch < 128; ++pitch) {
Steinberg::String pitchName;
if (desc.keyUsed.test(pitch) && !desc.keyLabel[pitch].empty())
pitchName = Steinberg::String(desc.keyLabel[pitch].c_str());
else if (desc.keyswitchUsed.test(pitch) && !desc.keyswitchLabel[pitch].empty())
pitchName = Steinberg::String(desc.keyswitchLabel[pitch].c_str());
list->setPitchName(0, pitch, pitchName);
}
FUnknownPtr<Vst::IUnitHandler> unitHandler(getComponentHandler());
if (unitHandler)
unitHandler->notifyProgramListChange(kProgramListID, 0);
// update the key switches and notify
size_t idKeyswitch = 0;
for (int16 pitch = 0; pitch < 128; ++pitch)
idKeyswitch += desc.keyswitchUsed.test(pitch);
keyswitches_.resize(idKeyswitch);
idKeyswitch = 0;
for (int16 pitch = 0; pitch < 128; ++pitch) {
if (!desc.keyswitchUsed.test(pitch))
continue;
Vst::KeyswitchInfo info {};
info.typeId = Vst::kNoteOnKeyswitchTypeID;
Steinberg::String(desc.keyswitchLabel[pitch].c_str()).copyTo(info.title);
Steinberg::String(desc.keyswitchLabel[pitch].c_str()).copyTo(info.shortTitle);
info.keyswitchMin = pitch; // TODO reexamine this when supporting keyswitch groups
info.keyswitchMax = pitch; // TODO reexamine this when supporting keyswitch groups
info.keyRemapped = pitch;
info.unitId = Vst::kRootUnitId;
info.flags = 0;
keyswitches_[idKeyswitch++] = info;
}
if (Vst::IComponentHandler* componentHandler = getComponentHandler())
componentHandler->restartComponent(Vst::kKeyswitchChanged);
// update the parameter titles and notify
for (uint32 cc = 0; cc < sfz::config::numCCs; ++cc) {
Vst::ParamID pid = kPidCC0 + cc;
Vst::Parameter* param = getParameterObject(pid);
Vst::ParameterInfo& info = param->getInfo();
Steinberg::String title;
Steinberg::String shortTitle;
if (!desc.ccLabel[cc].empty()) {
title = desc.ccLabel[cc].c_str();
shortTitle = title;
}
else {
title.printf("Controller %u", cc);
shortTitle.printf("CC%u", cc);
}
title.copyTo(info.title);
shortTitle.copyTo(info.shortTitle);
}
if (Vst::IComponentHandler* componentHandler = getComponentHandler())
componentHandler->restartComponent(Vst::kParamTitlesChanged);
//
sfzDescriptionUpdate_->deferUpdate();
}
else if (!strcmp(id, "LoadedScala")) {
absl::string_view scalaFile;
result = stringFromBinaryAttribute("File", scalaFile);
if (result != kResultTrue)
return result;
scalaUpdate_->setPath(std::string(scalaFile));
else if (!strcmp(id, ScalaUpdate::getFClassID())) {
if (!scalaUpdate_->convertFromMessage(*message)) {
assert(false);
return kResultFalse;
}
scalaUpdate_->deferUpdate();
}
else if (!strcmp(id, "NotifiedPlayState")) {
const void* data = nullptr;
uint32 size = 0;
result = attr->getBinary("PlayState", data, size);
if (result != kResultTrue)
return result;
playStateUpdate_->setState(*static_cast<const SfizzPlayState*>(data));
else if (!strcmp(id, PlayStateUpdate::getFClassID())) {
if (!playStateUpdate_->convertFromMessage(*message)) {
assert(false);
return kResultFalse;
}
playStateUpdate_->deferUpdate();
}
else if (!strcmp(id, "ReceivedMessage")) {
const void* data = nullptr;
uint32 size = 0;
result = attr->getBinary("Message", data, size);
if (result != kResultTrue)
return result;
// this is a synchronous send, because the update object gets reused
oscUpdate_->setMessage(data, size, false);
oscUpdate_->changed();
oscUpdate_->clear();
}
else if (!strcmp(id, "NoteEvents")) {
const void* data = nullptr;
uint32 size = 0;
result = attr->getBinary("Events", data, size);
const auto* events = reinterpret_cast<
const std::pair<uint32_t, float>*>(data);
uint32 numEvents = size / sizeof(events[0]);
// this is a synchronous send, because the update object gets reused
noteUpdate_->setEvents(events, numEvents, false);
noteUpdate_->changed();
noteUpdate_->clear();
}
else if (!strcmp(id, "Automate")) {
const void* data = nullptr;
uint32 size = 0;
result = attr->getBinary("Data", data, size);
if (result != kResultTrue)
return result;
const uint8* pos = reinterpret_cast<const uint8*>(data);
const uint8* end = pos + size;
while (static_cast<size_t>(end - pos) >= sizeof(uint32) + sizeof(float)) {
Vst::ParamID pid = *reinterpret_cast<const uint32*>(pos);
pos += sizeof(uint32);
float value = *reinterpret_cast<const float*>(pos);
pos += sizeof(float);
setParam(pid, value);
else if (!strcmp(id, OSCUpdate::getFClassID())) {
IPtr<OSCUpdate> update = OSCUpdate::createFromMessage(*message);
if (!update) {
assert(false);
return kResultFalse;
}
queuedUpdates_->enqueue(update);
queuedUpdates_->deferUpdate();
}
else if (!strcmp(id, NoteUpdate::getFClassID())) {
IPtr<NoteUpdate> update = NoteUpdate::createFromMessage(*message);
if (!update) {
assert(false);
return kResultFalse;
}
queuedUpdates_->enqueue(update);
queuedUpdates_->deferUpdate();
}
else if (!strcmp(id, AutomationUpdate::getFClassID())) {
IPtr<AutomationUpdate> update = AutomationUpdate::createFromMessage(*message);
if (!update) {
assert(false);
return kResultFalse;
}
for (AutomationUpdate::Item item : update->getItems())
setParam(item.first, item.second);
}
return result;
@ -326,20 +450,17 @@ IPlugView* PLUGIN_API SfizzVstController::createView(FIDString _name)
if (name != Vst::ViewType::kEditor)
return nullptr;
std::vector<FObject*> continuousUpdates;
continuousUpdates.push_back(sfzUpdate_);
continuousUpdates.push_back(sfzDescriptionUpdate_);
continuousUpdates.push_back(scalaUpdate_);
continuousUpdates.push_back(playStateUpdate_);
std::vector<FObject*> updates;
updates.push_back(queuedUpdates_);
updates.push_back(sfzUpdate_);
updates.push_back(sfzDescriptionUpdate_);
updates.push_back(scalaUpdate_);
updates.push_back(playStateUpdate_);
for (uint32 i = 0, n = parameters.getParameterCount(); i < n; ++i)
continuousUpdates.push_back(parameters.getParameterByIndex(i));
std::vector<FObject*> triggerUpdates;
triggerUpdates.push_back(oscUpdate_);
triggerUpdates.push_back(noteUpdate_);
updates.push_back(parameters.getParameterByIndex(i));
IPtr<SfizzVstEditor> editor = Steinberg::owned(
new SfizzVstEditor(this, absl::MakeSpan(continuousUpdates), absl::MakeSpan(triggerUpdates)));
new SfizzVstEditor(this, absl::MakeSpan(updates)));
editor->remember();
return editor;

View file

@ -9,7 +9,9 @@
#include "SfizzVstUpdates.h"
#include "public.sdk/source/vst/vsteditcontroller.h"
#include "public.sdk/source/vst/vstparameters.h"
#include "public.sdk/source/common/threadchecker.h"
#include "pluginterfaces/vst/ivstmidicontrollers.h"
#include "pluginterfaces/vst/ivstnoteexpression.h"
#include "vstgui/plugin-bindings/vst3editor.h"
#include <sfizz_message.h>
#include <mutex>
@ -19,8 +21,10 @@ class SfizzVstEditor;
using namespace Steinberg;
using namespace VSTGUI;
class SfizzVstControllerNoUi : public Vst::EditController,
public Vst::IMidiMapping {
class SfizzVstControllerNoUi : public Vst::EditControllerEx1,
public Vst::IMidiMapping,
public Vst::IKeyswitchController,
public Vst::IEditControllerHostEditing {
public:
virtual ~SfizzVstControllerNoUi() {}
@ -29,6 +33,12 @@ public:
tresult PLUGIN_API getMidiControllerAssignment(int32 busIndex, int16 channel, Vst::CtrlNumber midiControllerNumber, Vst::ParamID& id) override;
int32 PLUGIN_API getKeyswitchCount (int32 busIndex, int16 channel) override;
tresult PLUGIN_API getKeyswitchInfo (int32 busIndex, int16 channel, int32 keySwitchIndex, Vst::KeyswitchInfo& info) override;
tresult PLUGIN_API beginEditFromHost(Vst::ParamID paramID) override;
tresult PLUGIN_API endEditFromHost(Vst::ParamID paramID) override;
tresult PLUGIN_API getParamStringByValue(Vst::ParamID tag, Vst::ParamValue valueNormalized, Vst::String128 string) override;
tresult PLUGIN_API getParamValueByString(Vst::ParamID tag, Vst::TChar* string, Vst::ParamValue& valueNormalized) override;
@ -37,20 +47,23 @@ public:
tresult PLUGIN_API notify(Vst::IMessage* message) override;
// interfaces
OBJ_METHODS(SfizzVstControllerNoUi, Vst::EditController)
OBJ_METHODS(SfizzVstControllerNoUi, Vst::EditControllerEx1)
DEFINE_INTERFACES
DEF_INTERFACE(Vst::IMidiMapping)
END_DEFINE_INTERFACES(Vst::EditController)
REFCOUNT_METHODS(Vst::EditController)
DEF_INTERFACE(Vst::IKeyswitchController)
DEF_INTERFACE(Vst::IEditControllerHostEditing)
END_DEFINE_INTERFACES(Vst::EditControllerEx1)
REFCOUNT_METHODS(Vst::EditControllerEx1)
protected:
Steinberg::IPtr<OSCUpdate> oscUpdate_;
Steinberg::IPtr<NoteUpdate> noteUpdate_;
std::unique_ptr<Vst::ThreadChecker> threadChecker_;
Steinberg::IPtr<QueuedUpdates> queuedUpdates_;
Steinberg::IPtr<SfzUpdate> sfzUpdate_;
Steinberg::IPtr<SfzDescriptionUpdate> sfzDescriptionUpdate_;
Steinberg::IPtr<ScalaUpdate> scalaUpdate_;
Steinberg::IPtr<PlayStateUpdate> playStateUpdate_;
Vst::ParamID midiMapping_[Vst::kCountCtrlNumber] {};
std::vector<Vst::KeyswitchInfo> keyswitches_;
};
class SfizzVstController : public SfizzVstControllerNoUi, public VSTGUI::VST3EditorDelegate {

View file

@ -18,6 +18,7 @@
#include "X11RunLoop.h"
#endif
#include <ghc/fs_std.hpp>
#include <atomic>
using namespace VSTGUI;
@ -29,14 +30,10 @@ enum {
kNoteEventQueueSize = 8192,
};
SfizzVstEditor::SfizzVstEditor(
SfizzVstController* controller,
absl::Span<FObject*> continuousUpdates,
absl::Span<FObject*> triggerUpdates)
SfizzVstEditor::SfizzVstEditor(SfizzVstController* controller, absl::Span<FObject*> updates)
: VSTGUIEditor(controller, &sfizzUiViewRect),
oscTemp_(new uint8_t[kOscTempSize]),
continuousUpdates_(continuousUpdates.begin(), continuousUpdates.end()),
triggerUpdates_(triggerUpdates.begin(), triggerUpdates.end())
updates_(updates.begin(), updates.end())
{
}
@ -62,24 +59,8 @@ bool PLUGIN_API SfizzVstEditor::open(void* parent, const VSTGUI::PlatformType& p
config = &x11config;
#endif
Editor* editor = editor_.get();
if (!editor) {
editor = new Editor(*this);
editor_.reset(editor);
}
{
std::lock_guard<std::mutex> lock(oscQueueMutex_);
OscByteVec* queue = new OscByteVec;
oscQueue_.reset(queue);
queue->reserve(kOscQueueSize);
}
{
std::lock_guard<std::mutex> lock(noteEventQueueMutex_);
NoteEventsVec* queue = new NoteEventsVec;
noteEventQueue_.reset(queue);
queue->reserve(kNoteEventQueueSize);
}
Editor* editor = new Editor(*this);
editor_.reset(editor);
if (!frame->open(parent, platformType, config)) {
fprintf(stderr, "[sfizz] error opening frame\n");
@ -88,14 +69,16 @@ bool PLUGIN_API SfizzVstEditor::open(void* parent, const VSTGUI::PlatformType& p
editor->open(*frame);
for (FObject* update : continuousUpdates_)
update->addDependent(this);
for (FObject* update : triggerUpdates_)
for (FObject* update : updates_)
update->addDependent(this);
threadChecker_ = Vst::ThreadChecker::create();
parametersToUpdate_.clear();
Steinberg::IdleUpdateHandler::start();
for (FObject* update : continuousUpdates_)
for (FObject* update : updates_)
update->deferUpdate();
// let the editor know about plugin format
@ -122,6 +105,8 @@ bool PLUGIN_API SfizzVstEditor::open(void* parent, const VSTGUI::PlatformType& p
uiReceiveValue(EditId::UserFilesDir, userFilesDir.value_or(fs::path()).u8string());
uiReceiveValue(EditId::FallbackFilesDir, SfizzPaths::getSfzFallbackDefaultPath().u8string());
updateEditorIsOpenParameter();
return true;
}
@ -131,28 +116,36 @@ void PLUGIN_API SfizzVstEditor::close()
if (frame) {
Steinberg::IdleUpdateHandler::stop();
for (FObject* update : continuousUpdates_)
update->removeDependent(this);
for (FObject* update : triggerUpdates_)
for (FObject* update : updates_)
update->removeDependent(this);
if (editor_)
if (editor_) {
editor_->close();
editor_ = nullptr;
}
if (frame->getNbReference() != 1)
frame->forget();
else
else {
frame->close();
#if !defined(__APPLE__) && !defined(_WIN32)
// if vstgui is done using the runloop, destroy it
if (!RunLoop::get())
_runLoop = nullptr;
#endif
}
this->frame = nullptr;
}
{
std::lock_guard<std::mutex> lock(oscQueueMutex_);
oscQueue_.reset();
}
{
std::lock_guard<std::mutex> lock(noteEventQueueMutex_);
noteEventQueue_.reset();
}
updateEditorIsOpenParameter();
}
void SfizzVstEditor::updateEditorIsOpenParameter()
{
SfizzVstController* ctrl = getController();
bool editorIsOpen = frame && frame->isVisible();
ctrl->setParamNormalized(kPidEditorOpen, editorIsOpen);
ctrl->performEdit(kPidEditorOpen, editorIsOpen);
}
///
@ -171,15 +164,13 @@ CMessageResult SfizzVstEditor::notify(CBaseObject* sender, const char* message)
// notifier of X11 events is working. If there is, remove this and
// avoid polluting Linux hosts which implement the loop correctly.
runLoop->processSomeEvents();
runLoop->cleanupDeadHandlers();
}
}
#endif
if (message == CVSTGUITimer::kMsgTimer) {
processOscQueue();
processNoteEventQueue();
processParameterUpdates();
updateEditorIsOpenParameter(); // Note(jpc) for Reaper, it can fail at open time
}
return result;
@ -187,34 +178,51 @@ CMessageResult SfizzVstEditor::notify(CBaseObject* sender, const char* message)
void PLUGIN_API SfizzVstEditor::update(FUnknown* changedUnknown, int32 message)
{
if (OSCUpdate* update = FCast<OSCUpdate>(changedUnknown)) {
// this update is synchronous: may happen from non-UI thread
uint32 size = update->size();
if (size > 0) {
const uint8_t* bytes = reinterpret_cast<const uint8_t*>(update->data());
std::lock_guard<std::mutex> lock(oscQueueMutex_);
if (OscByteVec* queue = oscQueue_.get())
std::copy(bytes, bytes + size, std::back_inserter(*queue));
}
if (processUpdate(changedUnknown, message))
return;
Vst::VSTGUIEditor::update(changedUnknown, message);
}
bool SfizzVstEditor::processUpdate(FUnknown* changedUnknown, int32 message)
{
if (QueuedUpdates* update = FCast<QueuedUpdates>(changedUnknown)) {
for (FObject* queuedUpdate : update->getUpdates(this))
processUpdate(queuedUpdate, message);
return true;
}
if (OSCUpdate* update = FCast<OSCUpdate>(changedUnknown)) {
const uint8* oscData = update->data();
uint32 oscSize = update->size();
const char* path;
const char* sig;
const sfizz_arg_t* args;
uint8_t buffer[1024];
uint32_t msgSize;
while ((msgSize = sfizz_extract_message(oscData, oscSize, buffer, sizeof(buffer), &path, &sig, &args)) > 0) {
uiReceiveMessage(path, sig, args);
oscData += msgSize;
oscSize -= msgSize;
}
return true;
}
if (NoteUpdate* update = FCast<NoteUpdate>(changedUnknown)) {
// this update is synchronous: may happen from non-UI thread
const NoteUpdate::Item* events = update->events();
uint32 count = update->count();
if (count > 0) {
const auto* events = update->events();
std::lock_guard<std::mutex> lock(noteEventQueueMutex_);
if (NoteEventsVec* queue = noteEventQueue_.get())
std::copy(events, events + count, std::back_inserter(*queue));
}
return;
for (uint32 i = 0; i < count; ++i)
uiReceiveValue(editIdForKey(events[i].first), events[i].second);
return true;
}
if (SfzUpdate* update = FCast<SfzUpdate>(changedUnknown)) {
const std::string path = update->getPath();
uiReceiveValue(EditId::SfzFile, path);
return;
return true;
}
if (SfzDescriptionUpdate* update = FCast<SfzDescriptionUpdate>(changedUnknown)) {
@ -242,31 +250,70 @@ void PLUGIN_API SfizzVstEditor::update(FUnknown* changedUnknown, int32 message)
}
for (unsigned cc = 0; cc < sfz::config::numCCs; ++cc) {
bool ccUsed = desc.ccUsed.test(cc);
bool ccUsed = desc.ccUsed.test(cc) && !desc.sustainOrSostenuto.test(cc);
uiReceiveValue(editIdForCCUsed(int(cc)), float(ccUsed));
if (ccUsed) {
uiReceiveValue(editIdForCCDefault(int(cc)), desc.ccDefault[cc]);
uiReceiveValue(editIdForCCLabel(int(cc)), desc.ccLabel[cc]);
}
}
return;
return true;
}
if (ScalaUpdate* update = FCast<ScalaUpdate>(changedUnknown)) {
const std::string path = update->getPath();
uiReceiveValue(EditId::ScalaFile, path);
return;
return true;
}
if (PlayStateUpdate* update = FCast<PlayStateUpdate>(changedUnknown)) {
const SfizzPlayState playState = update->getState();
uiReceiveValue(EditId::UINumActiveVoices, playState.activeVoices);
return;
return true;
}
if (Vst::RangeParameter* param = Steinberg::FCast<Vst::RangeParameter>(changedUnknown)) {
const Vst::ParamValue value = param->getNormalized();
// Note(jpc) some hosts send us the parameters in the wrong thread...
// store these parameters thread-safely and let the idle
// callback process them later
if (threadChecker_->test())
updateParameter(param);
else {
static std::atomic_bool warn_once_flag { false };
if (!warn_once_flag.exchange(true))
fprintf(stderr, "[sfizz] using a thread-safety workaround for parameter updates\n");
const Vst::ParamID id = param->getInfo().id;
std::lock_guard<std::mutex> lock(parametersToUpdateMutex_);
parametersToUpdate_.insert(id);
}
return true;
}
return false;
}
void SfizzVstEditor::processParameterUpdates()
{
auto extractNextParamID = [this]() -> Vst::ParamID {
Vst::ParamID id = Vst::kNoParamId;
std::lock_guard<std::mutex> lock(parametersToUpdateMutex_);
auto it = parametersToUpdate_.begin();
if (it != parametersToUpdate_.end()) {
id = *it;
parametersToUpdate_.erase(it);
}
return id;
};
for (Vst::ParamID id; (id = extractNextParamID()) != Vst::kNoParamId; )
updateParameter(getController()->getParameterObject(id));
}
void SfizzVstEditor::updateParameter(Vst::Parameter* parameterToUpdate)
{
if (Vst::RangeParameter* param = FCast<Vst::RangeParameter>(parameterToUpdate)) {
const Vst::ParamID id = param->getInfo().id;
const Vst::ParamValue value = param->getNormalized();
const SfizzRange range = SfizzRange::getForParameter(id);
switch (id) {
case kPidVolume:
@ -296,6 +343,12 @@ void PLUGIN_API SfizzVstEditor::update(FUnknown* changedUnknown, int32 message)
case kPidOscillatorQuality:
uiReceiveValue(EditId::OscillatorQuality, range.denormalize(value));
break;
case kPidLeftLevel:
uiReceiveValue(EditId::LeftLevel, range.denormalize(value));
break;
case kPidRightLevel:
uiReceiveValue(EditId::RightLevel, range.denormalize(value));
break;
default:
if (id >= kPidCC0 && id <= kPidCCLast) {
int cc = int(id - kPidCC0);
@ -303,50 +356,7 @@ void PLUGIN_API SfizzVstEditor::update(FUnknown* changedUnknown, int32 message)
}
break;
}
return;
}
Vst::VSTGUIEditor::update(changedUnknown, message);
}
void SfizzVstEditor::processOscQueue()
{
std::lock_guard<std::mutex> lock(oscQueueMutex_);
OscByteVec* queue = oscQueue_.get();
if (!queue)
return;
const uint8_t* oscData = queue->data();
size_t oscSize = queue->size();
const char* path;
const char* sig;
const sfizz_arg_t* args;
uint8_t buffer[1024];
uint32_t msgSize;
while ((msgSize = sfizz_extract_message(oscData, oscSize, buffer, sizeof(buffer), &path, &sig, &args)) > 0) {
uiReceiveMessage(path, sig, args);
oscData += msgSize;
oscSize -= msgSize;
}
queue->clear();
}
void SfizzVstEditor::processNoteEventQueue()
{
std::lock_guard<std::mutex> lock(noteEventQueueMutex_);
NoteEventsVec* queue = noteEventQueue_.get();
if (!queue)
return;
for (std::pair<uint32, float> event : *queue)
uiReceiveValue(editIdForKey(event.first), event.second);
queue->clear();
}
///

View file

@ -8,8 +8,10 @@
#include "SfizzVstController.h"
#include "editor/EditorController.h"
#include "public.sdk/source/vst/vstguieditor.h"
#include "public.sdk/source/common/threadchecker.h"
#include <absl/types/span.h>
#include <mutex>
#include <set>
class Editor;
#if !defined(__APPLE__) && !defined(_WIN32)
namespace VSTGUI { class RunLoop; }
@ -23,10 +25,7 @@ class SfizzVstEditor : public Vst::VSTGUIEditor,
public:
using Self = SfizzVstEditor;
SfizzVstEditor(
SfizzVstController* controller,
absl::Span<FObject*> continuousUpdates,
absl::Span<FObject*> triggerUpdates);
SfizzVstEditor(SfizzVstController* controller, absl::Span<FObject*> updates);
~SfizzVstEditor();
bool PLUGIN_API open(void* parent, const VSTGUI::PlatformType& platformType) override;
@ -37,18 +36,18 @@ public:
return static_cast<SfizzVstController*>(Vst::VSTGUIEditor::getController());
}
void updateEditorIsOpenParameter();
// VSTGUIEditor
CMessageResult notify(CBaseObject* sender, const char* message) override;
// FObject
void PLUGIN_API update(FUnknown* changedUnknown, int32 message) override;
//
void updateState(const SfizzVstState& state);
void updatePlayState(const SfizzPlayState& playState);
private:
void processOscQueue();
void processNoteEventQueue();
bool processUpdate(FUnknown* changedUnknown, int32 message);
void processParameterUpdates();
void updateParameter(Vst::Parameter* parameterToUpdate);
protected:
// EditorController
@ -73,16 +72,14 @@ private:
// messaging
std::unique_ptr<uint8[]> oscTemp_;
// editor state
// note: might be updated from a non-UI thread
typedef std::vector<uint8_t> OscByteVec;
std::unique_ptr<OscByteVec> oscQueue_;
std::mutex oscQueueMutex_;
typedef std::vector<std::pair<uint32, float>> NoteEventsVec;
std::unique_ptr<NoteEventsVec> noteEventQueue_;
std::mutex noteEventQueueMutex_;
// subscribed updates
std::vector<IPtr<FObject>> continuousUpdates_;
std::vector<IPtr<FObject>> triggerUpdates_;
std::vector<IPtr<FObject>> updates_;
// thread safety
std::unique_ptr<Vst::ThreadChecker> threadChecker_;
// parameters to process, whose values have received changes
// Note(jpc) it's because hosts send us parameter updates in the wrong thread..
std::set<Vst::ParamID> parametersToUpdate_;
std::mutex parametersToUpdateMutex_;
};

View file

@ -26,6 +26,9 @@ enum {
kPidPitchBend,
kPidCC0,
kPidCCLast = kPidCC0 + sfz::config::numCCs - 1,
kPidLeftLevel,
kPidRightLevel,
kPidEditorOpen,
/* Reserved */
kNumParameters,
};
@ -78,6 +81,12 @@ struct SfizzRange {
return {0.0, 0.0, 1.0};
case kPidPitchBend:
return {0.0, -1.0, 1.0};
case kPidLeftLevel:
return {0.0, 0.0, 1.0};
case kPidRightLevel:
return {0.0, 0.0, 1.0};
case kPidEditorOpen:
return {0.0, 0.0, 1.0};
default:
if (id >= kPidCC0 && id <= kPidCCLast)
return {0.0, 0.0, 1.0};

View file

@ -9,10 +9,11 @@
#include "SfizzVstState.h"
#include "SfizzVstParameters.h"
#include "SfizzVstIDs.h"
#include "sfizz/import/ForeignInstrument.h"
#include "sfizz/import/sfizz_import.h"
#include "plugin/SfizzFileScan.h"
#include "plugin/InstrumentDescription.h"
#include "base/source/fstreamer.h"
#include "base/source/updatehandler.h"
#include "pluginterfaces/vst/ivstevents.h"
#include "pluginterfaces/vst/ivstparameterchanges.h"
#include <ghc/fs_std.hpp>
@ -34,10 +35,10 @@ static const char* kRingIdOsc = "Osc";
static const char* kMsgIdSetNumVoices = "SetNumVoices";
static const char* kMsgIdSetOversampling = "SetOversampling";
static const char* kMsgIdSetPreloadSize = "SetPreloadSize";
static const char* kMsgIdReceiveMessage = "ReceiveMessage";
static const char* kMsgIdReceiveOSC = "ReceiveOSC";
static const char* kMsgIdNoteEvents = "NoteEvents";
static constexpr std::chrono::milliseconds kBackgroundIdleInterval { 50 };
static constexpr std::chrono::milliseconds kBackgroundIdleInterval { 20 };
SfizzVstProcessor::SfizzVstProcessor()
: _oscTemp(new uint8_t[kOscTempSize]),
@ -74,6 +75,23 @@ tresult PLUGIN_API SfizzVstProcessor::initialize(FUnknown* context)
if (result != kResultTrue)
return result;
// initialize the update handler
Steinberg::UpdateHandler::instance();
_queuedMessages = Steinberg::owned(new QueuedUpdates);
_playStateUpdate = Steinberg::owned(new PlayStateUpdate);
_sfzUpdate = Steinberg::owned(new SfzUpdate);
_sfzDescriptionUpdate = Steinberg::owned(new SfzDescriptionUpdate);
_scalaUpdate = Steinberg::owned(new ScalaUpdate);
_automationUpdate = Steinberg::owned(new AutomationUpdate);
_queuedMessages->addDependent(this);
_playStateUpdate->addDependent(this);
_sfzUpdate->addDependent(this);
_sfzDescriptionUpdate->addDependent(this);
_scalaUpdate->addDependent(this);
_automationUpdate->addDependent(this);
addAudioOutput(STR16("Audio Output"), Vst::SpeakerArr::kStereo);
addEventInput(STR16("Event Input"), 1);
@ -88,7 +106,7 @@ tresult PLUGIN_API SfizzVstProcessor::initialize(FUnknown* context)
auto onMessage = +[](void* data, int delay, const char* path, const char* sig, const sfizz_arg_t* args)
{
auto *self = reinterpret_cast<SfizzVstProcessor*>(data);
self->receiveMessage(delay, path, sig, args);
self->receiveOSC(delay, path, sig, args);
};
_client = _synth->createClient(this);
_synth->setReceiveCallback(*_client, onMessage);
@ -106,9 +124,23 @@ tresult PLUGIN_API SfizzVstProcessor::initialize(FUnknown* context)
_noteEventsCurrentCycle.fill(-1.0f);
_editorIsOpen = false;
return result;
}
tresult PLUGIN_API SfizzVstProcessor::terminate()
{
_queuedMessages->removeDependent(this);
_playStateUpdate->removeDependent(this);
_sfzUpdate->removeDependent(this);
_sfzDescriptionUpdate->removeDependent(this);
_scalaUpdate->removeDependent(this);
_automationUpdate->removeDependent(this);
return AudioEffect::terminate();
}
tresult PLUGIN_API SfizzVstProcessor::setBusArrangements(Vst::SpeakerArrangement* inputs, int32 numIns, Vst::SpeakerArrangement* outputs, int32 numOuts)
{
bool isStereo = numIns == 0 && numOuts == 1 && outputs[0] == Vst::SpeakerArr::kStereo;
@ -126,10 +158,7 @@ tresult PLUGIN_API SfizzVstProcessor::connect(IConnectionPoint* other)
return result;
// when controller connects, send these messages that we couldn't earlier
if (_loadedSfzMessage)
sendMessage(_loadedSfzMessage);
if (_automateMessage)
sendMessage(_automateMessage);
_queuedMessages->deferUpdate();
return kResultTrue;
}
@ -198,6 +227,10 @@ void SfizzVstProcessor::syncStateToSynth()
synth->setScalaRootKey(_state.scalaRootKey);
synth->setTuningFrequency(_state.tuningFrequency);
synth->loadStretchTuningByRatio(_state.stretchedTuning);
if (_state.lastKeyswitch >= 0 && _state.lastKeyswitch <= 127) {
synth->hdNoteOn(0, _state.lastKeyswitch, 1.0f);
synth->hdNoteOff(1, _state.lastKeyswitch, 0.0f);
}
}
tresult PLUGIN_API SfizzVstProcessor::canProcessSampleSize(int32 symbolicSampleSize)
@ -221,6 +254,7 @@ tresult PLUGIN_API SfizzVstProcessor::setActive(TBool state)
if (state) {
synth->setSampleRate(processSetup.sampleRate);
synth->setSamplesPerBlock(processSetup.maxSamplesPerBlock);
_rmsFollower.init(processSetup.sampleRate);
initializeEventProcessor(processSetup, kNumParameters);
startBackgroundWork();
} else {
@ -286,12 +320,30 @@ tresult PLUGIN_API SfizzVstProcessor::process(Vst::ProcessData& data)
synth.renderBlock(outputs, numFrames, numChannels);
// Update levels, if editor is open, otherwise skip
RMSFollower& rmsFollower = _rmsFollower;
if (_editorIsOpen) {
rmsFollower.process(outputs[0], outputs[1], numFrames);
simde__m128 rms = _rmsFollower.getRMS();
float left = reinterpret_cast<float*>(&rms)[0];
float right = reinterpret_cast<float*>(&rms)[1];
if (Vst::IParameterChanges* pcs = data.outputParameterChanges) {
int32 index;
if (Vst::IParamValueQueue* vq = pcs->addParameterData(kPidLeftLevel, index))
vq->addPoint(0, left, index);
if (Vst::IParamValueQueue* vq = pcs->addParameterData(kPidRightLevel, index))
vq->addPoint(0, right, index);
}
}
else
rmsFollower.clear();
// Request OSC updates
sfz::Client& client = *_client;
synth.sendMessage(client, 0, "/sw/last/current", "", nullptr);
//
std::pair<uint32, float> noteEvents[128];
NoteUpdate::Item noteEvents[128];
size_t numNoteEvents = 0;
for (uint32 key = 0; key < 128; ++key) {
float value = _noteEventsCurrentCycle[key];
@ -388,10 +440,13 @@ void SfizzVstProcessor::playOrderedParameter(int32 sampleOffset, Vst::ParamID id
case kPidPitchBend:
synth.hdPitchWheel(sampleOffset, range.denormalize(value));
break;
case kPidEditorOpen:
_editorIsOpen = value != 0;
break;
default:
if (id >= kPidCC0 && id <= kPidCCLast) {
int32 ccNumber = static_cast<int32>(id - kPidCC0);
synth.hdcc(sampleOffset, ccNumber, value);
synth.automateHdcc(sampleOffset, ccNumber, value);
_state.controllers[ccNumber] = value;
}
break;
@ -467,7 +522,7 @@ void SfizzVstProcessor::processMessagesFromUi()
synth.noteOn(0, data[1] & 0x7f, data[2] & 0x7f);
break;
case 0xb0:
synth.cc(0, data[1] & 0x7f, data[2] & 0x7f);
synth.automateHdcc(0, data[1] & 0x7f, static_cast<float>(data[2] & 0x7f) / 127.0f);
break;
case 0xe0:
synth.pitchWheel(0, (data[2] << 7) + data[1] - 8192);
@ -500,7 +555,7 @@ void SfizzVstProcessor::processMessagesFromUi()
tresult PLUGIN_API SfizzVstProcessor::notify(Vst::IMessage* message)
{
// Note(jpc) this notification is not necessarily handled by the RT thread
// Note(jpc) this notification is not handled by the RT thread
tresult result = AudioEffect::notify(message);
if (result != kResultFalse)
@ -535,10 +590,8 @@ tresult PLUGIN_API SfizzVstProcessor::notify(Vst::IMessage* message)
_synth->loadScalaFile(_state.scalaFile);
lock.unlock();
Steinberg::OPtr<Vst::IMessage> reply { allocateMessage() };
reply->setMessageID("LoadedScala");
reply->getAttributes()->setBinary("File", _state.scalaFile.data(), _state.scalaFile.size());
sendMessage(reply);
_scalaUpdate->setPath(_state.scalaFile);
_scalaUpdate->deferUpdate();
}
else if (!std::strcmp(id, "MidiMessage")) {
const void* data = nullptr;
@ -557,12 +610,81 @@ tresult PLUGIN_API SfizzVstProcessor::notify(Vst::IMessage* message)
return result;
}
void SfizzVstProcessor::receiveMessage(int delay, const char* path, const char* sig, const sfizz_arg_t* args)
void PLUGIN_API SfizzVstProcessor::update(FUnknown* changedUnknown, int32 message)
{
if (processUpdate(changedUnknown, message))
return;
AudioEffect::update(changedUnknown, message);
}
bool SfizzVstProcessor::processUpdate(FUnknown* changedUnknown, int32 message)
{
if (QueuedUpdates* update = FCast<QueuedUpdates>(changedUnknown)) {
for (FObject* queuedUpdate : update->getUpdates(this))
processUpdate(queuedUpdate, message);
return true;
}
if (OSCUpdate* update = FCast<OSCUpdate>(changedUnknown)) {
if (IPtr<Vst::IMessage> message = update->convertToMessage(this))
sendMessage(message);
return true;
}
if (PlayStateUpdate* update = FCast<PlayStateUpdate>(changedUnknown)) {
if (IPtr<Vst::IMessage> message = update->convertToMessage(this))
sendMessage(message);
return true;
}
if (NoteUpdate* update = FCast<NoteUpdate>(changedUnknown)) {
if (IPtr<Vst::IMessage> message = update->convertToMessage(this))
sendMessage(message);
return true;
}
if (SfzUpdate* update = FCast<SfzUpdate>(changedUnknown)) {
if (IPtr<Vst::IMessage> message = update->convertToMessage(this))
sendMessage(message);
return true;
}
if (SfzDescriptionUpdate* update = FCast<SfzDescriptionUpdate>(changedUnknown)) {
if (IPtr<Vst::IMessage> message = update->convertToMessage(this))
sendMessage(message);
return true;
}
if (ScalaUpdate* update = FCast<ScalaUpdate>(changedUnknown)) {
if (IPtr<Vst::IMessage> message = update->convertToMessage(this))
sendMessage(message);
return true;
}
if (AutomationUpdate* update = FCast<AutomationUpdate>(changedUnknown)) {
if (IPtr<Vst::IMessage> message = update->convertToMessage(this))
sendMessage(message);
return true;
}
return false;
}
void SfizzVstProcessor::receiveOSC(int delay, const char* path, const char* sig, const sfizz_arg_t* args)
{
if (!strcmp(path, "/sw/last/current") && sig)
{
if (sig[0] == 'i')
_state.lastKeyswitch = args[0].i;
else if (sig[0] == 'N')
_state.lastKeyswitch = -1;
}
uint8_t* oscTemp = _oscTemp.get();
uint32 oscSize = sfizz_prepare_message(oscTemp, kOscTempSize, path, sig, args);
if (oscSize <= kOscTempSize) {
if (writeWorkerMessage(kMsgIdReceiveMessage, oscTemp, oscSize))
if (writeWorkerMessage(kMsgIdReceiveOSC, oscTemp, oscSize))
_semaToWorker.post();
}
}
@ -572,16 +694,7 @@ void SfizzVstProcessor::loadSfzFileOrDefault(const std::string& filePath, bool i
sfz::Sfizz& synth = *_synth;
if (!filePath.empty()) {
const sfz::InstrumentFormatRegistry& formatRegistry = sfz::InstrumentFormatRegistry::getInstance();
const sfz::InstrumentFormat* format = formatRegistry.getMatchingFormat(filePath);
if (!format)
synth.loadSfzFile(filePath);
else {
auto importer = format->createImporter();
std::string virtualPath = filePath + ".sfz";
std::string sfzText = importer->convertToSfz(filePath);
synth.loadSfzString(virtualPath, sfzText);
}
sfizz_load_or_import_file(synth.handle(), filePath.c_str(), nullptr);
}
else {
synth.loadSfzString("default.sfz", defaultSfzText);
@ -589,12 +702,6 @@ void SfizzVstProcessor::loadSfzFileOrDefault(const std::string& filePath, bool i
const std::string descBlob = getDescriptionBlob(synth.handle());
Steinberg::OPtr<Vst::IMessage> loadMessage { allocateMessage() };
loadMessage->setMessageID("LoadedSfz");
Vst::IAttributeList* loadAttrs = loadMessage->getAttributes();
loadAttrs->setBinary("File", filePath.data(), filePath.size());
loadAttrs->setBinary("Description", descBlob.data(), descBlob.size());
{
std::vector<absl::optional<float>> newControllers(sfz::config::numCCs);
const std::vector<absl::optional<float>> oldControllers = std::move(_state.controllers);
@ -609,7 +716,7 @@ void SfizzVstProcessor::loadSfzFileOrDefault(const std::string& filePath, bool i
for (uint32 cc = 0; cc < sfz::config::numCCs; ++cc) {
if (absl::optional<float> value = oldControllers[cc]) {
newControllers[cc] = *value;
synth.hdcc(0, int(cc), *value);
synth.automateHdcc(0, int(cc), *value);
}
}
}
@ -617,26 +724,21 @@ void SfizzVstProcessor::loadSfzFileOrDefault(const std::string& filePath, bool i
}
// create a message which requests the controller to automate initial parameters
Steinberg::OPtr<Vst::IMessage> automateMessage { allocateMessage() };
automateMessage->setMessageID("Automate");
Vst::IAttributeList* automateAttrs = automateMessage->getAttributes();
std::string automateBlob;
automateBlob.reserve(sfz::config::numCCs * (sizeof(uint32) + sizeof(float)));
std::vector<AutomationUpdate::Item> automationItems;
automationItems.reserve(sfz::config::numCCs);
for (uint32 cc = 0; cc < sfz::config::numCCs; ++cc) {
uint32 pid = kPidCC0 + cc;
Vst::ParamID pid = kPidCC0 + cc;
float value = _state.controllers[cc].value_or(0.0f);
automateBlob.append(reinterpret_cast<const char*>(&pid), sizeof(uint32));
automateBlob.append(reinterpret_cast<const char*>(&value), sizeof(float));
automationItems.emplace_back(pid, value);
}
automateAttrs->setBinary("Data", automateBlob.data(), uint32(automateBlob.size()));
// sending can fail if controller is not connected yet, so keep it around
_loadedSfzMessage = loadMessage;
_automateMessage = automateMessage;
// send message
sendMessage(loadMessage);
sendMessage(automateMessage);
_sfzUpdate->setPath(filePath);
_sfzUpdate->deferUpdate();
_sfzDescriptionUpdate->setDescription(descBlob);
_sfzDescriptionUpdate->deferUpdate();
_automationUpdate->setItems(std::move(automationItems));
_automationUpdate->deferUpdate();
}
void SfizzVstProcessor::doBackgroundWork()
@ -650,8 +752,13 @@ void SfizzVstProcessor::doBackgroundWork()
for (;;) {
bool isNotified = _semaToWorker.timed_wait(kBackgroundIdleInterval.count());
if (!_workRunning)
if (!_workRunning) {
// if the quit signal is sent, the semaphore is also signaled
// make sure the count is kept consistent
if (!isNotified)
_semaToWorker.wait();
break;
}
const char* id = nullptr;
RTMessagePtr msg;
@ -680,17 +787,17 @@ void SfizzVstProcessor::doBackgroundWork()
std::lock_guard<SpinMutex> lock(_processMutex);
_synth->setPreloadSize(value);
}
else if (id == kMsgIdReceiveMessage) {
Steinberg::OPtr<Vst::IMessage> notification { allocateMessage() };
notification->setMessageID("ReceivedMessage");
notification->getAttributes()->setBinary("Message", msg->payload<uint8_t>(), msg->size);
sendMessage(notification);
else if (id == kMsgIdReceiveOSC) {
IPtr<OSCUpdate> update = Steinberg::owned(
new OSCUpdate(msg->payload<uint8>(), msg->size));
_queuedMessages->enqueue(update);
_queuedMessages->deferUpdate();
}
else if (id == kMsgIdNoteEvents) {
Steinberg::OPtr<Vst::IMessage> notification { allocateMessage() };
notification->setMessageID("NoteEvents");
notification->getAttributes()->setBinary("Events", msg->payload<uint8_t>(), msg->size);
sendMessage(notification);
IPtr<NoteUpdate> update = Steinberg::owned(
new NoteUpdate(msg->payload<NoteUpdate::Item>(), msg->size / sizeof(NoteUpdate::Item)));
_queuedMessages->enqueue(update);
_queuedMessages->deferUpdate();
}
Clock::time_point currentTime = Clock::now();
@ -707,13 +814,11 @@ void SfizzVstProcessor::doBackgroundIdle(size_t idleCounter)
{
SfizzPlayState ps;
ps.activeVoices = _synth->getNumActiveVoices();
Steinberg::OPtr<Vst::IMessage> notification { allocateMessage() };
notification->setMessageID("NotifiedPlayState");
notification->getAttributes()->setBinary("PlayState", &ps, sizeof(ps));
sendMessage(notification);
_playStateUpdate->setState(ps);
_playStateUpdate->deferUpdate();
}
if (idleCounter % 10 == 0) {
if (idleCounter % 25 == 0) {
if (_synth->shouldReloadFile()) {
fprintf(stderr, "[Sfizz] sfz file has changed, reloading\n");
std::lock_guard<SpinMutex> lock(_processMutex);

View file

@ -6,7 +6,9 @@
#pragma once
#include "SfizzVstState.h"
#include "SfizzVstUpdates.h"
#include "OrderedEventProcessor.h"
#include "plugin/RMSFollower.h"
#include "sfizz/RTSemaphore.h"
#include "ring_buffer/ring_buffer.h"
#include "public.sdk/source/vst/vstaudioeffect.h"
@ -26,6 +28,7 @@ public:
~SfizzVstProcessor();
tresult PLUGIN_API initialize(FUnknown* context) override;
tresult PLUGIN_API terminate() override;
tresult PLUGIN_API setBusArrangements(Vst::SpeakerArrangement* inputs, int32 numIns, Vst::SpeakerArrangement* outputs, int32 numOuts) override;
tresult PLUGIN_API connect(IConnectionPoint* other) override;
@ -45,6 +48,7 @@ public:
void processMessagesFromUi();
tresult PLUGIN_API notify(Vst::IMessage* message) override;
void PLUGIN_API update(FUnknown* changedUnknown, int32 message) override;
static FUnknown* createInstance(void*);
@ -54,8 +58,6 @@ public:
private:
// synth state. acquire processMutex before accessing
std::unique_ptr<sfz::Sfizz> _synth;
Steinberg::IPtr<Vst::IMessage> _loadedSfzMessage;
Steinberg::IPtr<Vst::IMessage> _automateMessage;
bool _isActive = false;
SfizzVstState _state;
float _currentStretchedTuning = 0;
@ -63,10 +65,23 @@ private:
// whether allowed to perform events (owns the processing lock)
bool _canPerformEventsAndParameters {};
// level meters
RMSFollower _rmsFollower;
bool _editorIsOpen = false;
// updates
IPtr<QueuedUpdates> _queuedMessages;
IPtr<PlayStateUpdate> _playStateUpdate;
IPtr<SfzUpdate> _sfzUpdate;
IPtr<SfzDescriptionUpdate> _sfzDescriptionUpdate;
IPtr<ScalaUpdate> _scalaUpdate;
IPtr<AutomationUpdate> _automationUpdate;
bool processUpdate(FUnknown* changedUnknown, int32 message);
// client
sfz::ClientPtr _client;
std::unique_ptr<uint8_t[]> _oscTemp;
void receiveMessage(int delay, const char* path, const char* sig, const sfizz_arg_t* args);
void receiveOSC(int delay, const char* path, const char* sig, const sfizz_arg_t* args);
// misc
void loadSfzFileOrDefault(const std::string& filePath, bool initParametersFromState);

View file

@ -73,6 +73,14 @@ tresult SfizzVstState::load(IBStream* state)
oscillatorQuality = defaults.oscillatorQuality;
}
if (version >= 4) {
if (!s.readInt32(lastKeyswitch))
return kResultFalse;
}
else {
lastKeyswitch = -1;
}
controllers.clear();
if (version >= 2) {
uint32 count;
@ -135,6 +143,9 @@ tresult SfizzVstState::store(IBStream* state) const
if (!s.writeInt32(oscillatorQuality))
return kResultFalse;
if (!s.writeInt32(lastKeyswitch))
return kResultFalse;
{
uint32 ccCount = 0;
uint32 ccLimit = uint32(std::min(controllers.size(), size_t(0x10000)));

View file

@ -27,9 +27,10 @@ public:
float stretchedTuning = 0.0;
int32 sampleQuality = 2;
int32 oscillatorQuality = 1;
int32 lastKeyswitch = -1;
std::vector<absl::optional<float>> controllers;
static constexpr uint64 currentStateVersion = 3;
static constexpr uint64 currentStateVersion = 4;
tresult load(IBStream* state);
tresult store(IBStream* state) const;

View file

@ -8,61 +8,167 @@
#include <algorithm>
#include <cstring>
OSCUpdate::~OSCUpdate()
void QueuedUpdates::enqueue(IPtr<FObject> update)
{
clear();
std::lock_guard<std::mutex> lock(mutex_);
for (std::pair<IDependent* const, List>& item : updates_)
item.second.push_back(update);
}
void OSCUpdate::clear()
auto QueuedUpdates::getUpdates(IDependent* dep) -> List
{
if (allocated_)
delete[] reinterpret_cast<const uint8_t*>(data_);
data_ = nullptr;
size_ = 0;
allocated_ = false;
std::lock_guard<std::mutex> lock(mutex_);
List list;
auto it = updates_.find(dep);
if (it != updates_.end())
std::swap(list, it->second);
return list;
}
void OSCUpdate::setMessage(const void* data, uint32_t size, bool copy)
void QueuedUpdates::addDependent(IDependent* dep)
{
clear();
std::lock_guard<std::mutex> lock(mutex_);
FObject::addDependent(dep);
updates_.emplace(dep, List());
}
if (copy) {
uint8_t *buffer = new uint8_t[size];
std::memcpy(buffer, data, size);
data = buffer;
}
data_ = data;
size_ = size;
allocated_ = copy;
void QueuedUpdates::removeDependent(IDependent* dep)
{
std::lock_guard<std::mutex> lock(mutex_);
FObject::removeDependent(dep);
updates_.erase(dep);
}
///
NoteUpdate::~NoteUpdate()
bool OSCUpdate::saveToAttributes(Vst::IAttributeList* attrs) const
{
clear();
return attrs->setBinary("Data", data(), size()) == kResultTrue;
}
void NoteUpdate::clear()
bool OSCUpdate::loadFromAttributes(Vst::IAttributeList* attrs)
{
if (allocated_)
delete[] events_;
events_ = nullptr;
count_ = 0;
allocated_ = false;
const void* data;
uint32 size;
if (attrs->getBinary("Data", data, size) != kResultTrue)
return false;
const uint8* data8 = reinterpret_cast<const uint8*>(data);
data_.assign(data8, data8 + size);
return true;
}
void NoteUpdate::setEvents(const std::pair<uint32_t, float>* events, uint32_t count, bool copy)
///
bool NoteUpdate::saveToAttributes(Vst::IAttributeList* attrs) const
{
clear();
if (copy) {
auto *buffer = new std::pair<uint32_t, float>[count];
std::copy_n(events, count, buffer);
events = buffer;
}
events_ = events;
count_ = count;
allocated_ = copy;
return attrs->setBinary("Events", events_.data(), events_.size() * sizeof(Item)) == kResultTrue;
}
bool NoteUpdate::loadFromAttributes(Vst::IAttributeList* attrs)
{
const void* binData = nullptr;
uint32 binSize = 0;
if (attrs->getBinary("Events", binData, binSize) != kResultTrue)
return false;
const Item* events = reinterpret_cast<const Item*>(binData);
uint32 numEvents = binSize / sizeof(Item);
events_.assign(events, events + numEvents);
return true;
}
///
bool FilePathUpdate::saveFilePathAttributes_(Vst::IAttributeList* attrs) const
{
std::lock_guard<std::mutex> lock(mutex_);
return attrs->setBinary("Path", path_.data(), path_.size()) == kResultTrue;
}
bool FilePathUpdate::loadFilePathAttributes_(Vst::IAttributeList* attrs)
{
const void* binData = nullptr;
uint32 binSize = 0;
if (attrs->getBinary("Path", binData, binSize) != kResultTrue)
return false;
std::lock_guard<std::mutex> lock(mutex_);
path_.assign(reinterpret_cast<const char *>(binData), binSize);
return true;
}
///
bool SfzUpdate::saveToAttributes(Vst::IAttributeList* attrs) const
{
return saveFilePathAttributes_(attrs);
}
bool SfzUpdate::loadFromAttributes(Vst::IAttributeList* attrs)
{
return loadFilePathAttributes_(attrs);
}
///
bool ScalaUpdate::saveToAttributes(Vst::IAttributeList* attrs) const
{
return saveFilePathAttributes_(attrs);
}
bool ScalaUpdate::loadFromAttributes(Vst::IAttributeList* attrs)
{
return loadFilePathAttributes_(attrs);
}
///
bool SfzDescriptionUpdate::saveToAttributes(Vst::IAttributeList* attrs) const
{
std::lock_guard<std::mutex> lock(mutex_);
return attrs->setBinary("Blob", description_.data(), description_.size()) == kResultTrue;
}
bool SfzDescriptionUpdate::loadFromAttributes(Vst::IAttributeList* attrs)
{
const void* binData = nullptr;
uint32 binSize = 0;
if (attrs->getBinary("Blob", binData, binSize) != kResultTrue)
return false;
std::lock_guard<std::mutex> lock(mutex_);
description_.assign(reinterpret_cast<const char *>(binData), binSize);
return true;
}
///
bool PlayStateUpdate::saveToAttributes(Vst::IAttributeList* attrs) const
{
std::lock_guard<std::mutex> lock(mutex_);
return attrs->setInt("ActiveVoices", state_.activeVoices) == kResultTrue;
}
bool PlayStateUpdate::loadFromAttributes(Vst::IAttributeList* attrs)
{
int64 activeVoices;
if (attrs->getInt("ActiveVoices", activeVoices) != kResultTrue)
return false;
std::lock_guard<std::mutex> lock(mutex_);
state_.activeVoices = static_cast<uint32>(activeVoices);
return true;
}
///
bool AutomationUpdate::saveToAttributes(Vst::IAttributeList* attrs) const
{
std::lock_guard<std::mutex> lock(mutex_);
return attrs->setBinary("Items", items_.data(), items_.size() * sizeof(Item)) == kResultTrue;
}
bool AutomationUpdate::loadFromAttributes(Vst::IAttributeList* attrs)
{
const void* binData = nullptr;
uint32 binSize = 0;
if (attrs->getBinary("Items", binData, binSize) != kResultTrue)
return false;
const Item* events = reinterpret_cast<const Item*>(binData);
uint32 numEvents = binSize / sizeof(Item);
std::lock_guard<std::mutex> lock(mutex_);
items_.assign(events, events + numEvents);
return true;
}

View file

@ -6,70 +6,102 @@
#pragma once
#include "SfizzVstState.h"
#include <public.sdk/source/vst/vstcomponentbase.h>
#include <pluginterfaces/vst/ivstmessage.h>
#include <base/source/fobject.h>
#include <vector>
#include <map>
#include <string>
#include <memory>
#include <mutex>
#include <cstring>
#include <cstdint>
/**
* @brief Update which notifies a single OSC message
* Is is supposed to be used synchronously.
* (ie. FObject::changed or UpdateHandler::triggerUpdates)
* @brief Update which is convertible with Vst::IMessage back and forth.
*/
class OSCUpdate : public Steinberg::FObject {
template <class T> class IConvertibleToMessage {
public:
virtual ~IConvertibleToMessage() {}
IPtr<Vst::IMessage> convertToMessage(Vst::ComponentBase* sender) const;
bool convertFromMessage(Vst::IMessage& message);
static IPtr<T> createFromMessage(Vst::IMessage& message);
protected:
virtual bool saveToAttributes(Vst::IAttributeList* attrs) const = 0;
virtual bool loadFromAttributes(Vst::IAttributeList* attrs) = 0;
};
/**
* @brief Update which notifies a FIFO queue of one-time updates
*/
class QueuedUpdates : public Steinberg::FObject {
public:
using List = std::vector<IPtr<FObject>>;
void enqueue(IPtr<FObject> update);
List getUpdates(IDependent* dep);
void addDependent(IDependent* dep) override;
void removeDependent(IDependent* dep) override;
OBJ_METHODS(QueuedUpdates, FObject)
private:
std::mutex mutex_;
std::map<IDependent*, List> updates_;
};
/**
* @brief Update which notifies a single OSC message
*/
class OSCUpdate : public Steinberg::FObject,
public IConvertibleToMessage<OSCUpdate> {
public:
OSCUpdate() = default;
~OSCUpdate();
void clear();
void setMessage(const void* data, uint32_t size, bool copy);
OSCUpdate(const uint8* data, uint32 size) : data_(data, data + size) {}
const uint8* data() const noexcept { return data_.data(); }
uint32_t size() const noexcept { return static_cast<uint32_t>(data_.size()); }
const void* data() const noexcept { return data_; }
const uint32_t size() const noexcept { return size_; }
bool saveToAttributes(Vst::IAttributeList* attrs) const override;
bool loadFromAttributes(Vst::IAttributeList* attrs) override;
OBJ_METHODS(OSCUpdate, FObject)
private:
const void* data_ = nullptr;
uint32_t size_ = 0;
bool allocated_ = false;
private:
OSCUpdate(const OSCUpdate&) = delete;
OSCUpdate& operator=(const OSCUpdate&) = delete;
std::vector<uint8> data_;
};
/**
* @brief Update which notifies one or more note on/off events
* Is is supposed to be used synchronously.
* (ie. FObject::changed or UpdateHandler::triggerUpdates)
*/
class NoteUpdate : public Steinberg::FObject {
class NoteUpdate : public Steinberg::FObject,
public IConvertibleToMessage<NoteUpdate> {
public:
NoteUpdate() = default;
~NoteUpdate();
void clear();
void setEvents(const std::pair<uint32_t, float>* events, uint32_t count, bool copy);
using Item = std::pair<uint32, float>;
const std::pair<uint32_t, float>* events() const noexcept { return events_; }
const uint32_t count() const noexcept { return count_; }
NoteUpdate() = default;
NoteUpdate(const Item* items, uint32 count) : events_(items, items + count) {}
const Item* events() const noexcept { return events_.data(); }
const uint32 count() const noexcept { return static_cast<uint32>(events_.size()); }
bool saveToAttributes(Vst::IAttributeList* attrs) const override;
bool loadFromAttributes(Vst::IAttributeList* attrs) override;
OBJ_METHODS(NoteUpdate, FObject)
private:
const std::pair<uint32_t, float>* events_ = nullptr;
uint32_t count_ = 0;
bool allocated_ = false;
private:
NoteUpdate(const NoteUpdate&) = delete;
NoteUpdate& operator=(const NoteUpdate&) = delete;
std::vector<Item> events_;
};
/**
* @brief Update which notifies a change of SFZ file.
* @brief Abstract update which notifies change of a certain file path.
*/
class SfzUpdate : public Steinberg::FObject {
class FilePathUpdate : public Steinberg::FObject {
protected:
FilePathUpdate() = default;
public:
void setPath(std::string newPath)
{
@ -83,17 +115,46 @@ public:
return path_;
}
OBJ_METHODS(SfzUpdate, FObject)
OBJ_METHODS(FilePathUpdate, FObject)
protected:
bool saveFilePathAttributes_(Vst::IAttributeList* attrs) const;
bool loadFilePathAttributes_(Vst::IAttributeList* attrs);
private:
std::string path_;
mutable std::mutex mutex_;
};
/**
* @brief Update which notifies a change of SFZ file.
*/
class SfzUpdate : public FilePathUpdate,
public IConvertibleToMessage<SfzUpdate> {
public:
OBJ_METHODS(SfzUpdate, FilePathUpdate)
bool saveToAttributes(Vst::IAttributeList* attrs) const override;
bool loadFromAttributes(Vst::IAttributeList* attrs) override;
};
/**
* @brief Update which notifies a change of scala file.
*/
class ScalaUpdate : public FilePathUpdate,
public IConvertibleToMessage<ScalaUpdate> {
public:
OBJ_METHODS(ScalaUpdate, FilePathUpdate)
bool saveToAttributes(Vst::IAttributeList* attrs) const override;
bool loadFromAttributes(Vst::IAttributeList* attrs) override;
};
/**
* @brief Update which notifies a change of SFZ description.
*/
class SfzDescriptionUpdate : public Steinberg::FObject {
class SfzDescriptionUpdate : public Steinberg::FObject,
public IConvertibleToMessage<SfzDescriptionUpdate> {
public:
void setDescription(std::string newDescription)
{
@ -107,6 +168,9 @@ public:
return description_;
}
bool saveToAttributes(Vst::IAttributeList* attrs) const override;
bool loadFromAttributes(Vst::IAttributeList* attrs) override;
OBJ_METHODS(SfzDescriptionUpdate, FObject)
private:
@ -114,34 +178,11 @@ private:
mutable std::mutex mutex_;
};
/**
* @brief Update which notifies a change of scala file.
*/
class ScalaUpdate : public Steinberg::FObject {
public:
void setPath(std::string newPath)
{
std::lock_guard<std::mutex> lock(mutex_);
path_ = std::move(newPath);
}
std::string getPath() const
{
std::lock_guard<std::mutex> lock(mutex_);
return path_;
}
OBJ_METHODS(ScalaUpdate, FObject)
private:
std::string path_;
mutable std::mutex mutex_;
};
/**
* @brief Update which indicates the playing SFZ status.
*/
class PlayStateUpdate : public Steinberg::FObject {
class PlayStateUpdate : public Steinberg::FObject,
public IConvertibleToMessage<PlayStateUpdate> {
public:
void setState(SfizzPlayState newState)
{
@ -155,9 +196,46 @@ public:
return state_;
}
bool saveToAttributes(Vst::IAttributeList* attrs) const override;
virtual bool loadFromAttributes(Vst::IAttributeList* attrs) override;
OBJ_METHODS(PlayStateUpdate, FObject)
private:
SfizzPlayState state_ {};
mutable std::mutex mutex_;
};
/**
* @brief Update which automates a pack of parameters
*/
class AutomationUpdate : public Steinberg::FObject,
public IConvertibleToMessage<AutomationUpdate> {
public:
using Item = std::pair<Vst::ParamID, float>;
AutomationUpdate() = default;
void setItems(std::vector<Item> newItems)
{
std::lock_guard<std::mutex> lock(mutex_);
items_ = std::move(newItems);
}
std::vector<Item> getItems() const
{
std::lock_guard<std::mutex> lock(mutex_);
return items_;
}
bool saveToAttributes(Vst::IAttributeList* attrs) const override;
bool loadFromAttributes(Vst::IAttributeList* attrs) override;
OBJ_METHODS(AutomationUpdate, FObject)
private:
std::vector<Item> items_;
mutable std::mutex mutex_;
};
#include "SfizzVstUpdates.hpp"

View file

@ -0,0 +1,41 @@
// SPDX-License-Identifier: BSD-2-Clause
// This code is part of the sfizz library and is licensed under a BSD 2-clause
// license. You should have receive a LICENSE.md file along with the code.
// If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz
#pragma once
#include "SfizzVstUpdates.h"
template <class T>
IPtr<Vst::IMessage> IConvertibleToMessage<T>::convertToMessage(Vst::ComponentBase* sender) const
{
IPtr<Vst::IMessage> message = Steinberg::owned(sender->allocateMessage());
if (!message)
return nullptr;
message->setMessageID(static_cast<const T*>(this)->isA());
if (!saveToAttributes(message->getAttributes()))
return nullptr;
return message;
}
template <class T>
IPtr<T> IConvertibleToMessage<T>::createFromMessage(Vst::IMessage& message)
{
IPtr<T> object;
if (!strcmp(T::getFClassID(), message.getMessageID())) {
object = Steinberg::owned(new T);
if (!object->loadFromAttributes(message.getAttributes()))
object = nullptr;
}
return object;
}
template <class T>
bool IConvertibleToMessage<T>::convertFromMessage(Vst::IMessage& message)
{
bool success = false;
if (!strcmp(T::getFClassID(), message.getMessageID()))
success = loadFromAttributes(message.getAttributes());
return success;
}

View file

@ -1,33 +1,38 @@
// SPDX-License-Identifier: GPL-3.0
// SPDX-License-Identifier: BSD-2-Clause
// This code is part of the sfizz library and is licensed under a BSD 2-clause
// license. You should have receive a LICENSE.md file along with the code.
// If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz
#if !defined(__APPLE__) && !defined(_WIN32)
#include "X11RunLoop.h"
#include "vstgui/lib/platform/linux/x11platform.h"
#include "base/source/fobject.h"
#include <vector>
#include <typeinfo>
#include <cstdio>
#include <cassert>
namespace VSTGUI {
RunLoop::RunLoop(Steinberg::FUnknown* runLoop)
: runLoop(runLoop)
{
}
struct RunLoop::Impl {
struct EventHandler;
struct TimerHandler;
RunLoop::~RunLoop() {}
using EventHandlers = std::vector<Steinberg::IPtr<EventHandler>>;
using TimerHandlers = std::vector<Steinberg::IPtr<TimerHandler>>;
SharedPointer<RunLoop> RunLoop::get()
{
return X11::RunLoop::get().cast<VSTGUI::RunLoop>();
}
EventHandlers eventHandlers;
TimerHandlers timerHandlers;
Steinberg::FUnknownPtr<Steinberg::Linux::IRunLoop> runLoop;
};
struct RunLoop::EventHandler final : Steinberg::Linux::IEventHandler, public Steinberg::FObject {
//------------------------------------------------------------------------------
struct RunLoop::Impl::EventHandler final : Steinberg::Linux::IEventHandler, public Steinberg::FObject {
X11::IEventHandler* handler { nullptr };
bool alive { false };
void PLUGIN_API onFDIsSet(Steinberg::Linux::FileDescriptor) override
{
if (alive && handler)
handler->onEvent();
}
void PLUGIN_API onFDIsSet(Steinberg::Linux::FileDescriptor) override;
DELEGATE_REFCOUNT(Steinberg::FObject)
DEFINE_INTERFACES
@ -35,15 +40,11 @@ struct RunLoop::EventHandler final : Steinberg::Linux::IEventHandler, public Ste
END_DEFINE_INTERFACES(Steinberg::FObject)
};
struct RunLoop::TimerHandler final : Steinberg::Linux::ITimerHandler, public Steinberg::FObject {
struct RunLoop::Impl::TimerHandler final : Steinberg::Linux::ITimerHandler, public Steinberg::FObject {
X11::ITimerHandler* handler { nullptr };
bool alive { false };
void PLUGIN_API onTimer() override
{
if (alive && handler)
handler->onTimer();
}
void PLUGIN_API onTimer() override;
DELEGATE_REFCOUNT(Steinberg::FObject)
DEFINE_INTERFACES
@ -51,45 +52,126 @@ struct RunLoop::TimerHandler final : Steinberg::Linux::ITimerHandler, public Ste
END_DEFINE_INTERFACES(Steinberg::FObject)
};
//------------------------------------------------------------------------------
void PLUGIN_API RunLoop::Impl::EventHandler::onFDIsSet(Steinberg::Linux::FileDescriptor)
{
SharedPointer<RunLoop> runLoop = RunLoop::get();
if (!runLoop) {
fprintf(stderr, "[x11] event has fired without active runloop\n");
return;
}
if (alive && handler)
handler->onEvent();
}
void PLUGIN_API RunLoop::Impl::TimerHandler::onTimer()
{
SharedPointer<RunLoop> runLoop = RunLoop::get();
if (!runLoop) {
fprintf(stderr, "[x11] timer has fired without active runloop\n");
return;
}
if (alive && handler)
handler->onTimer();
}
//------------------------------------------------------------------------------
RunLoop::RunLoop(Steinberg::FUnknown* runLoop)
: impl(new Impl)
{
impl->runLoop = runLoop;
}
RunLoop::~RunLoop()
{
//dumpCurrentState();
if (0) {
// remove any leftover handlers
for (size_t i = 0; i < impl->eventHandlers.size(); ++i) {
const auto& eh = impl->eventHandlers[i];
if (eh->alive && eh->handler) {
impl->runLoop->unregisterEventHandler(eh.get());
}
}
for (size_t i = 0; i < impl->timerHandlers.size(); ++i) {
const auto& th = impl->timerHandlers[i];
if (th->alive && th->handler) {
impl->runLoop->unregisterTimer(th.get());
}
}
}
}
SharedPointer<RunLoop> RunLoop::get()
{
return X11::RunLoop::get().cast<VSTGUI::RunLoop>();
}
void RunLoop::processSomeEvents()
{
for (size_t i = 0; i < eventHandlers.size(); ++i) {
const auto& eh = eventHandlers[i];
for (size_t i = 0; i < impl->eventHandlers.size(); ++i) {
const auto& eh = impl->eventHandlers[i];
if (eh->alive && eh->handler) {
eh->handler->onEvent();
}
}
}
void RunLoop::cleanupDeadHandlers()
void RunLoop::dumpCurrentState()
{
for (size_t i = 0; i < eventHandlers.size(); ++i) {
const auto& eh = eventHandlers[i];
if (!eh->alive) {
runLoop->unregisterEventHandler(eh);
eventHandlers.erase(eventHandlers.begin() + i--);
}
fprintf(stderr, "=== X11 runloop ===\n");
fprintf(stderr, "\t" "Event slots:\n");
for (size_t i = 0, n = impl->eventHandlers.size(); i < n; ++i) {
Impl::EventHandler *eh = impl->eventHandlers[i].get();
fprintf(stderr, "\t\t" "(%lu) alive=%d handler=%p type=%s\n", i, eh->alive, eh->handler, (eh->alive && eh->handler) ? typeid(*eh->handler).name() : "");
}
for (size_t i = 0; i < timerHandlers.size(); ++i) {
const auto& th = timerHandlers[i];
if (!th->alive) {
runLoop->unregisterTimer(th);
timerHandlers.erase(timerHandlers.begin() + i--);
}
fprintf(stderr, "\t" "Timer slots:\n");
for (size_t i = 0, n = impl->timerHandlers.size(); i < n; ++i) {
Impl::TimerHandler *th = impl->timerHandlers[i].get();
fprintf(stderr, "\t\t" "(%lu) alive=%d handler=%p type=%s\n", i, th->alive, th->handler, (th->alive && th->handler) ? typeid(*th->handler).name() : "");
}
fprintf(stderr, "===/X11 runloop ===\n");
}
template <class T>
static void insertHandler(std::vector<Steinberg::IPtr<T>>& list, Steinberg::IPtr<T> handler)
{
size_t i = 0;
size_t n = list.size();
while (i < n && list[i]->alive)
++i;
if (i < n)
list[i] = handler;
else
list.emplace_back(handler);
}
template <class T, class U>
static size_t findHandler(const std::vector<Steinberg::IPtr<T>>& list, U* handler)
{
for (size_t i = 0, n = list.size(); i < n; ++i) {
if (list[i]->alive && list[i]->handler == handler)
return i;
}
return ~size_t(0);
}
bool RunLoop::registerEventHandler(int fd, X11::IEventHandler* handler)
{
if (!runLoop)
if (!impl->runLoop)
return false;
auto smtgHandler = Steinberg::owned(new EventHandler());
auto smtgHandler = Steinberg::owned(new Impl::EventHandler);
smtgHandler->handler = handler;
smtgHandler->alive = true;
if (runLoop->registerEventHandler(smtgHandler, fd) == Steinberg::kResultTrue) {
eventHandlers.push_back(smtgHandler);
if (impl->runLoop->registerEventHandler(smtgHandler, fd) == Steinberg::kResultTrue) {
insertHandler(impl->eventHandlers, smtgHandler);
return true;
}
return false;
@ -97,29 +179,31 @@ bool RunLoop::registerEventHandler(int fd, X11::IEventHandler* handler)
bool RunLoop::unregisterEventHandler(X11::IEventHandler* handler)
{
if (!runLoop)
if (!impl->runLoop)
return false;
for (size_t i = 0; i < eventHandlers.size(); ++i) {
const auto& eh = eventHandlers[i];
if (eh->alive && eh->handler == handler) {
eh->alive = false;
return true;
}
}
return false;
size_t index = findHandler(impl->eventHandlers, handler);
if (index == ~size_t(0))
return false;
Impl::EventHandler *eh = impl->eventHandlers[index].get();
if (impl->runLoop->unregisterEventHandler(eh) != Steinberg::kResultTrue)
return false;
eh->alive = false;
return true;
}
bool RunLoop::registerTimer(uint64_t interval, X11::ITimerHandler* handler)
{
if (!runLoop)
if (!impl->runLoop)
return false;
auto smtgHandler = Steinberg::owned(new TimerHandler());
auto smtgHandler = Steinberg::owned(new Impl::TimerHandler);
smtgHandler->handler = handler;
smtgHandler->alive = true;
if (runLoop->registerTimer(smtgHandler, interval) == Steinberg::kResultTrue) {
timerHandlers.push_back(smtgHandler);
if (impl->runLoop->registerTimer(smtgHandler, interval) == Steinberg::kResultTrue) {
insertHandler(impl->timerHandlers, smtgHandler);
return true;
}
return false;
@ -127,17 +211,19 @@ bool RunLoop::registerTimer(uint64_t interval, X11::ITimerHandler* handler)
bool RunLoop::unregisterTimer(X11::ITimerHandler* handler)
{
if (!runLoop)
if (!impl->runLoop)
return false;
for (size_t i = 0; i < timerHandlers.size(); ++i) {
const auto& th = timerHandlers[i];
if (th->alive && th->handler == handler) {
th->alive = false;
return true;
}
}
return false;
size_t index = findHandler(impl->timerHandlers, handler);
if (index == ~size_t(0))
return false;
Impl::TimerHandler *th = impl->timerHandlers[index].get();
if (impl->runLoop->unregisterTimer(th) != Steinberg::kResultTrue)
return false;
th->alive = false;
return true;
}
} // namespace VSTGUI

View file

@ -1,16 +1,28 @@
// SPDX-License-Identifier: GPL-3.0
/*
This is a modified version the X11 run loop from vst3editor.cpp.
// SPDX-License-Identifier: BSD-2-Clause
This version is edited to add more safeguards to protect against host bugs.
It also permits to call event processing externally in case the host has a
defective X11 event loop notifier.
// This code is part of the sfizz library and is licensed under a BSD 2-clause
// license. You should have receive a LICENSE.md file along with the code.
// If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz
/*
This runloop connects to X11 VSTGUI, it connects VST3 and VSTGUI together.
The Windows and macOS runloops do not need this, the OS-provided
functionality is used instead.
Previously, this was based on VSTGUI code provided by Steinberg.
This is replaced with a rewrite, because the original code has too many
issues. For example, it has no robustness in case handlers get added or
removed within the execution of the handler.
This version allows to call event processing externally, in case the host
has a defective X11 event loop notifier. (some versions of Bitwig do)
*/
#pragma once
#if !defined(__APPLE__) && !defined(_WIN32)
#include "vstgui/lib/platform/linux/x11frame.h"
#include "pluginterfaces/gui/iplugview.h"
#include <memory>
namespace VSTGUI {
@ -22,24 +34,16 @@ public:
static SharedPointer<RunLoop> get();
void processSomeEvents();
void cleanupDeadHandlers();
void dumpCurrentState();
// X11::IRunLoop
bool registerEventHandler(int fd, X11::IEventHandler* handler);
bool unregisterEventHandler(X11::IEventHandler* handler);
bool registerTimer(uint64_t interval, X11::ITimerHandler* handler);
bool unregisterTimer(X11::ITimerHandler* handler);
bool registerEventHandler(int fd, X11::IEventHandler* handler) override;
bool unregisterEventHandler(X11::IEventHandler* handler) override;
bool registerTimer(uint64_t interval, X11::ITimerHandler* handler) override;
bool unregisterTimer(X11::ITimerHandler* handler) override;
private:
struct EventHandler;
struct TimerHandler;
private:
using EventHandlers = std::vector<Steinberg::IPtr<EventHandler>>;
using TimerHandlers = std::vector<Steinberg::IPtr<TimerHandler>>;
EventHandlers eventHandlers;
TimerHandlers timerHandlers;
Steinberg::FUnknownPtr<Steinberg::Linux::IRunLoop> runLoop;
struct Impl;
std::unique_ptr<Impl> impl;
};
} // namespace VSTGUI

View file

@ -30,11 +30,14 @@ add_library(vst3sdk STATIC EXCLUDE_FROM_ALL
"${VST3SDK_BASEDIR}/public.sdk/source/vst/vstnoteexpressiontypes.cpp"
"${VST3SDK_BASEDIR}/public.sdk/source/vst/vstparameters.cpp"
"${VST3SDK_BASEDIR}/public.sdk/source/vst/vstpresetfile.cpp"
"${VST3SDK_BASEDIR}/public.sdk/source/vst/vstrepresentation.cpp")
"${VST3SDK_BASEDIR}/public.sdk/source/vst/vstrepresentation.cpp"
"${VST3SDK_BASEDIR}/public.sdk/source/vst/utility/stringconvert.cpp")
if(WIN32)
target_sources(vst3sdk PRIVATE
"${VST3SDK_BASEDIR}/public.sdk/source/common/threadchecker_win32.cpp")
elseif(APPLE)
target_sources(vst3sdk PRIVATE
"${VST3SDK_BASEDIR}/public.sdk/source/common/threadchecker_mac.mm")
else()
target_sources(vst3sdk PRIVATE
"${VST3SDK_BASEDIR}/public.sdk/source/common/threadchecker_linux.cpp")

@ -1 +1 @@
Subproject commit b8566ef3b2a0cba60a96e3ef2001224c865c8b36
Subproject commit 93cef1afb7061e488625045ba5a82abaa83d27fe

@ -1 +1 @@
Subproject commit c3948deb407bdbff89de8fb6ab8500ea4df9d6d9
Subproject commit 9589800ed94573354bc29de45eec5744523fbfcb

View file

@ -31,6 +31,13 @@ else
codesign --sign "${CODESIGN_IDENTITY}" --keychain build.keychain --force --verbose \
"${INSTALL_DIR}"/sfizz.lv2/Contents/Frameworks/*.dylib
fi
# code-sign Puredata and dylibs
codesign --sign "${CODESIGN_IDENTITY}" --keychain build.keychain --force --verbose \
"${INSTALL_DIR}"/Puredata/*/*.pd_darwin
if ls "${INSTALL_DIR}"/Puredata/*/*.dylib &> /dev/null; then
codesign --sign "${CODESIGN_IDENTITY}" --keychain build.keychain --force --verbose \
"${INSTALL_DIR}"/Puredata/*/*.dylib
fi
fi
# Create the DMG
@ -39,12 +46,13 @@ cat > sfizz-dmg.json << EOF
"title": "sfizz",
"background": "${APPVEYOR_BUILD_FOLDER}/mac/dmg-back.png",
"window": {
"size": { "width": 500, "height": 500 }
"size": { "width": 650, "height": 500 }
},
"contents": [
{ "x": 100, "y": 50, "type": "file", "path": "${INSTALL_DIR}/sfizz.vst3" },
{ "x": 250, "y": 50, "type": "file", "path": "${INSTALL_DIR}/sfizz.component" },
{ "x": 400, "y": 50, "type": "file", "path": "${INSTALL_DIR}/sfizz.lv2" },
{ "x": 550, "y": 50, "type": "file", "path": "${INSTALL_DIR}/Puredata" },
{ "x": 100, "y": 400, "type": "link", "path": "/Library/Audio/Plug-Ins/VST3" },
{ "x": 250, "y": 400, "type": "link", "path": "/Library/Audio/Plug-Ins/Components" },
{ "x": 400, "y": 400, "type": "link", "path": "/Library/Audio/Plug-Ins/LV2" }

View file

@ -7,6 +7,7 @@ cmake -DCMAKE_BUILD_TYPE=Release \
-DCMAKE_OSX_ARCHITECTURES="arm64;x86_64" \
-DSFIZZ_VST=ON \
-DSFIZZ_AU=ON \
-DSFIZZ_PUREDATA=ON \
-DSFIZZ_RENDER=OFF \
-DSFIZZ_SHARED=OFF \
-DSFIZZ_TESTS=ON \
@ -14,4 +15,5 @@ cmake -DCMAKE_BUILD_TYPE=Release \
-DLV2PLUGIN_INSTALL_DIR=/ \
-DVSTPLUGIN_INSTALL_DIR=/ \
-DAUPLUGIN_INSTALL_DIR=/ \
-DPDPLUGIN_INSTALL_DIR=/Puredata \
..

View file

@ -259,9 +259,9 @@ TAB_SIZE = 4
# commands \{ and \} for these it is advised to use the version @{ and @} or use
# a double escape (\\{ and \\})
ALIASES = "true=<b style=\"color:blue\">true</b>" \
"false=<b style=\"color:blue\">false</b>" \
"null=<b>NULL</b>"
ALIASES = "true=\<b style='color:blue'\>true\</b\>" \
"false=\<b style='color:blue'\>false\</b\>" \
"null=\<b\>nullptr\</b\>"
# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources
# only. Doxygen will then generate output that is more tailored for C. For

View file

@ -22,7 +22,11 @@
- Tags in ALIASES list needs to be escaped (parsed by tag_list_as_string())
to avoid Doxygen convert them in its own tag structure, by loosing some
details (e.g.: <b style=\"color:blue\">true</b> becomes <bold>true</bold>).
details (e.g.: <b style=\"color:blue\">true</b> becomes <bold>true</bold>,
use \<b\> as escaping and replace double quotes with apostrophe instead).
- Some link references are lost like in
`sfizz_export_midnam` -> `sfizz_free_memory`
- Merge the work done previously to be able to fully automate the process
to be used in various CIs.

View file

@ -36,8 +36,8 @@ LicenseFile="sfizz.lv2\LICENSE.md"
OutputBaseFileName={#MyAppName}-{#MyAppVersion}-msvc-{#Arch}-setup
OutputDir=.
UninstallFilesDir={app}
WizardImageFile="C:\Program Files (x86)\Inno Setup 6\WizModernImage-IS.bmp"
WizardSmallImageFile="C:\Program Files (x86)\Inno Setup 6\WizModernSmallImage-IS.bmp"
WizardImageFile=compiler:WizClassicImage-IS.bmp
WizardSmallImageFile=compiler:WizClassicSmallImage-IS.bmp
[Languages]
Name: "english"; MessagesFile: "compiler:Default.isl"
@ -46,6 +46,7 @@ Name: "english"; MessagesFile: "compiler:Default.isl"
Name: "main"; Description: "Shared files"; Types: full custom; Flags: fixed
Name: "lv2"; Description: "LV2 plugin"; Types: full custom;
Name: "vst3"; Description: "VST3 plugin"; Types: full custom;
Name: "puredata"; Description: "Puredata external"; Types: full custom;
[Files]
Source: "sfizz.lv2\Contents\Binary\sfizz.dll"; Components: lv2; DestDir: "{commoncf}\LV2\sfizz.lv2\Contents\Binary"; Flags: ignoreversion
@ -54,13 +55,13 @@ Source: "sfizz.lv2\Contents\Resources\*"; Components: lv2; DestDir: "{commoncf}\
Source: "sfizz.lv2\manifest.ttl"; Components: lv2; DestDir: "{commoncf}\LV2\sfizz.lv2"
Source: "sfizz.lv2\sfizz.ttl"; Components: lv2; DestDir: "{commoncf}\LV2\sfizz.lv2"
Source: "sfizz.lv2\sfizz_ui.ttl"; Components: lv2; DestDir: "{commoncf}\LV2\sfizz.lv2"
Source: "sfizz.lv2\lgpl-3.0.txt"; Components: main; DestDir: "{app}"
Source: "sfizz.lv2\LICENSE.md"; Components: main; DestDir: "{app}"
Source: "sfizz.vst3\desktop.ini"; Components: vst3; DestDir: "{commoncf}\VST3\sfizz.vst3"
Source: "sfizz.vst3\Contents\@VST3_PACKAGE_ARCHITECTURE@-win\sfizz.vst3"; Components: vst3; DestDir: "{commoncf}\VST3\sfizz.vst3\Contents\@VST3_PACKAGE_ARCHITECTURE@-win"; Flags: ignoreversion
Source: "sfizz.vst3\Contents\Resources\*"; Components: vst3; DestDir: "{commoncf}\VST3\sfizz.vst3\Contents\Resources"; Flags: recursesubdirs
Source: "sfizz.vst3\Plugin.ico"; Components: vst3; DestDir: "{commoncf}\VST3\sfizz.vst3"
Source: "sfizz.vst3\gpl-3.0.txt"; Components: main; DestDir: "{app}"
Source: "pd\*"; Components: puredata; DestDir: "{commoncf}\Pd"; Flags: recursesubdirs
; Note(sfizz): OS older than Windows 10 require UI fonts to be installed system-wide
Source: "sfizz.vst3\Contents\Resources\Fonts\sfizz-fluentui-system-r20.ttf"; DestDir: "{fonts}"; FontInstall: "Sfizz Fluent System R20"; Flags: uninsneveruninstall; OnlyBelowVersion: 6.4
Source: "sfizz.vst3\Contents\Resources\Fonts\sfizz-fluentui-system-f20.ttf"; DestDir: "{fonts}"; FontInstall: "Sfizz Fluent System F20"; Flags: uninsneveruninstall; OnlyBelowVersion: 6.4

View file

@ -14,6 +14,7 @@ clang-tidy \
src/sfizz/Panning.cpp \
src/sfizz/sfizz.cpp \
src/sfizz/Region.cpp \
src/sfizz/RegionStateful.cpp \
src/sfizz/SIMDHelpers.cpp \
src/sfizz/simd/HelpersSSE.cpp \
src/sfizz/simd/HelpersAVX.cpp \

View file

@ -1,7 +1,7 @@
prefix=@CMAKE_INSTALL_PREFIX@
exec_prefix=@CMAKE_INSTALL_PREFIX@
libdir=${exec_prefix}/@CMAKE_INSTALL_LIBDIR@
includedir=${prefix}/@CMAKE_INSTALL_INCLUDEDIR@
libdir=@CMAKE_INSTALL_FULL_LIBDIR@
includedir=@CMAKE_INSTALL_FULL_INCLUDEDIR@
Name: @PROJECT_NAME@
Description: @PROJECT_DESCRIPTION@

View file

@ -1,5 +1,3 @@
include(GNUInstallDirs)
set(FAUST_FILES
sfizz/dsp/filters/filters_modulable.dsp
sfizz/dsp/filters/rbj_filters.dsp
@ -96,6 +94,7 @@ set(SFIZZ_HEADERS
sfizz/railsback/4-1.h
sfizz/railsback/4-2.h
sfizz/Region.h
sfizz/RegionStateful.h
sfizz/RegionSet.h
sfizz/Resources.h
sfizz/RTSemaphore.h
@ -131,6 +130,7 @@ set(SFIZZ_SOURCES
sfizz/AudioReader.cpp
sfizz/FilterPool.cpp
sfizz/EQPool.cpp
sfizz/RegionStateful.cpp
sfizz/Region.cpp
sfizz/Voice.cpp
sfizz/ScopedFTZ.cpp
@ -161,6 +161,7 @@ set(SFIZZ_SOURCES
sfizz/WindowedSinc.cpp
sfizz/Interpolators.cpp
sfizz/Layer.cpp
sfizz/Resources.cpp
sfizz/modulations/ModId.cpp
sfizz/modulations/ModKey.cpp
sfizz/modulations/ModKeyHash.cpp
@ -190,7 +191,8 @@ set(SFIZZ_SOURCES
sfizz/effects/impl/ResonantStringAVX.cpp
sfizz/effects/impl/ResonantArray.cpp
sfizz/effects/impl/ResonantArraySSE.cpp
sfizz/effects/impl/ResonantArrayAVX.cpp)
sfizz/effects/impl/ResonantArrayAVX.cpp
sfizz/utility/c++17/AlignedMemorySupport.cpp)
include(SfizzSIMDSourceFiles)
sfizz_add_simd_sources(SFIZZ_SOURCES ".")
@ -201,6 +203,7 @@ set(SFIZZ_PARSER_HEADERS
sfizz/Range.h
sfizz/Opcode.h
sfizz/parser/Parser.h
sfizz/parser/ParserListener.h
sfizz/parser/ParserPrivate.h
sfizz/parser/ParserPrivate.hpp
sfizz/SfzHelpers.h)
@ -249,11 +252,13 @@ endif()
# Import library
set(SFIZZ_IMPORT_HEADERS
sfizz/import/sfizz_import.h
sfizz/import/ForeignInstrument.h
sfizz/import/foreign_instruments/AudioFile.h
sfizz/import/foreign_instruments/DecentSampler.h)
set(SFIZZ_IMPORT_SOURCES
sfizz/import/sfizz_import.cpp
sfizz/import/ForeignInstrument.cpp
sfizz/import/foreign_instruments/AudioFile.cpp
sfizz/import/foreign_instruments/DecentSampler.cpp)
@ -294,12 +299,12 @@ if(SFIZZ_USE_SNDFILE)
target_compile_definitions(sfizz_internal PUBLIC "SFIZZ_USE_SNDFILE=1")
target_link_libraries(sfizz_internal PUBLIC st_audiofile)
endif()
if(WIN32)
target_compile_definitions(sfizz_internal PRIVATE _USE_MATH_DEFINES)
endif()
if(SFIZZ_RELEASE_ASSERTS)
target_compile_definitions(sfizz_internal PUBLIC "SFIZZ_ENABLE_RELEASE_ASSERT=1")
endif()
if(SFIZZ_IMPLEMENT_CXX17_ALIGNED_NEW_SUPPORT)
target_compile_definitions(sfizz_internal PRIVATE "SFIZZ_IMPLEMENT_CXX17_ALIGNED_NEW_SUPPORT=1")
endif()
sfizz_enable_fast_math(sfizz_internal)
# Check that sfizz and cmake-side definitions are matching
@ -315,7 +320,12 @@ add_library(sfizz_static STATIC sfizz/sfizz_wrapper.cpp sfizz/sfizz.cpp sfizz/sf
add_library(sfizz::static ALIAS sfizz_static)
target_include_directories(sfizz_static PUBLIC .)
target_link_libraries(sfizz_static PRIVATE sfizz::internal)
set_target_properties(sfizz_static PROPERTIES OUTPUT_NAME "sfizz" PUBLIC_HEADER "sfizz.h;sfizz.hpp;sfizz_message.h")
set_target_properties(sfizz_static PROPERTIES PUBLIC_HEADER "sfizz.h;sfizz.hpp;sfizz_message.h")
if(MSVC)
set_target_properties(sfizz_static PROPERTIES OUTPUT_NAME "sfizz_static")
else()
set_target_properties(sfizz_static PROPERTIES OUTPUT_NAME "sfizz")
endif()
# Shared library and installation target
if(SFIZZ_SHARED)
@ -324,7 +334,7 @@ if(SFIZZ_SHARED)
target_include_directories(sfizz_shared PUBLIC .)
target_link_libraries(sfizz_shared PRIVATE sfizz::internal)
target_compile_definitions(sfizz_shared PRIVATE SFIZZ_EXPORT_SYMBOLS)
set_target_properties(sfizz_shared PROPERTIES SOVERSION "${PROJECT_VERSION_MAJOR}" OUTPUT_NAME "sfizz" PUBLIC_HEADER "sfizz.h;sfizz.hpp;sfizz_message.h")
set_target_properties(sfizz_shared PROPERTIES VERSION "${PROJECT_VERSION}" SOVERSION "${PROJECT_VERSION_MAJOR}" OUTPUT_NAME "sfizz" PUBLIC_HEADER "sfizz.h;sfizz.hpp;sfizz_message.h")
sfizz_enable_lto_if_needed(sfizz_shared)
if(NOT MSVC)
@ -347,6 +357,19 @@ endif()
# Generic library alias
add_library(sfizz::sfizz ALIAS sfizz_static)
# Git build identifier
add_custom_target(sfizz-generate-git-build-id
COMMAND "${CMAKE_COMMAND}"
"-DSOURCE_DIR=${PROJECT_SOURCE_DIR}"
"-DOUTPUT_FILE=${PROJECT_BINARY_DIR}/git-build-id/GitBuildId.c"
"-P" "${PROJECT_SOURCE_DIR}/cmake/GitBuildID.cmake"
BYPRODUCTS "${PROJECT_BINARY_DIR}/git-build-id/GitBuildId.c")
add_library(sfizz-git-build-id STATIC EXCLUDE_FROM_ALL
"sfizz/git-build-id/GitBuildId.h"
"${PROJECT_BINARY_DIR}/git-build-id/GitBuildId.c")
target_include_directories(sfizz-git-build-id PUBLIC "sfizz/git-build-id")
add_dependencies(sfizz-git-build-id sfizz-generate-git-build-id)
# Preserve generated files (Faust)
set_directory_properties(PROPERTIES CLEAN_NO_CUSTOM TRUE)

View file

@ -510,7 +510,7 @@ SFIZZ_EXPORTED_API void sfizz_send_hd_pitch_wheel(sfizz_synth_t* synth, int dela
*
* @param synth The synth.
* @param delay The delay at which the event occurs; this should be lower
* than the size of the block in the next call to renderBlock().
* than the size of the block in the next call to sfizz_render_block().
* @param aftertouch The aftertouch value, in domain 0 to 127.
*
* @par Thread-safety constraints
@ -528,7 +528,7 @@ SFIZZ_EXPORTED_API SFIZZ_DEPRECATED_API void sfizz_send_aftertouch(sfizz_synth_t
*
* @param synth The synth.
* @param delay The delay at which the event occurs; this should be lower
* than the size of the block in the next call to renderBlock().
* than the size of the block in the next call to sfizz_render_block().
* @param aftertouch The aftertouch value, in domain 0 to 127.
*
* @par Thread-safety constraints
@ -546,7 +546,7 @@ SFIZZ_EXPORTED_API void sfizz_send_channel_aftertouch(sfizz_synth_t* synth, int
*
* @param synth The synth.
* @param delay The delay at which the event occurs; this should be lower
* than the size of the block in the next call to renderBlock().
* than the size of the block in the next call to sfizz_render_block().
* @param aftertouch The normalized aftertouch value, in domain 0 to 1.
*
* @par Thread-safety constraints
@ -565,7 +565,7 @@ SFIZZ_EXPORTED_API void sfizz_send_hd_channel_aftertouch(sfizz_synth_t* synth, i
*
* @param synth The synth.
* @param delay The delay at which the event occurs; this should be lower
* than the size of the block in the next call to renderBlock().
* than the size of the block in the next call to sfizz_render_block().
* @param note_number The note number, in domain 0 to 127.
* @param aftertouch The aftertouch value, in domain 0 to 127.
*
@ -585,7 +585,7 @@ SFIZZ_EXPORTED_API void sfizz_send_poly_aftertouch(sfizz_synth_t* synth, int del
*
* @param synth The synth.
* @param delay The delay at which the event occurs; this should be lower
* than the size of the block in the next call to renderBlock().
* than the size of the block in the next call to sfizz_render_block().
* @param note_number The note number, in domain 0 to 127.
* @param aftertouch The normalized aftertouch value, in domain 0 to 1.
*
@ -598,8 +598,8 @@ SFIZZ_EXPORTED_API void sfizz_send_hd_poly_aftertouch(sfizz_synth_t* synth, int
* @brief Send a tempo event.
*
* This command should be delay-ordered with all other time/signature commands, namely
* tempo(), timeSignature(), timePosition(), and playbackState(), otherwise the behavior
* of the synth is undefined.
* sfizz_send_tempo(), sfizz_send_time_signature(), sfizz_send_time_position(),
* and sfizz_send_playback_state(), otherwise the behavior of the synth is undefined.
*
* @since 0.2.0
*
@ -616,8 +616,8 @@ SFIZZ_EXPORTED_API SFIZZ_DEPRECATED_API void sfizz_send_tempo(sfizz_synth_t* syn
* @brief Send a tempo event.
*
* This command should be delay-ordered with all other time/signature commands, namely
* tempo(), timeSignature(), timePosition(), and playbackState(), otherwise the behavior
* of the synth is undefined.
* sfizz_send_tempo(), sfizz_send_time_signature(), sfizz_send_time_position(),
* and sfizz_send_playback_state(), otherwise the behavior of the synth is undefined.
*
* @since 1.0.0
*
@ -634,8 +634,8 @@ SFIZZ_EXPORTED_API void sfizz_send_bpm_tempo(sfizz_synth_t* synth, int delay, fl
* @brief Send the time signature.
*
* This command should be delay-ordered with all other time/signature commands, namely
* tempo(), timeSignature(), timePosition(), and playbackState(), otherwise the behavior
* of the synth is undefined.
* sfizz_send_tempo(), sfizz_send_time_signature(), sfizz_send_time_position(),
* and sfizz_send_playback_state(), otherwise the behavior of the synth is undefined.
*
* @since 0.5.0
*
@ -653,8 +653,8 @@ SFIZZ_EXPORTED_API void sfizz_send_time_signature(sfizz_synth_t* synth, int dela
* @brief Send the time position.
*
* This command should be delay-ordered with all other time/signature commands, namely
* tempo(), timeSignature(), timePosition(), and playbackState(), otherwise the behavior
* of the synth is undefined.
* sfizz_send_tempo(), sfizz_send_time_signature(), sfizz_send_time_position(),
* and sfizz_send_playback_state(), otherwise the behavior of the synth is undefined.
*
* @since 0.5.0
*
@ -672,8 +672,8 @@ SFIZZ_EXPORTED_API void sfizz_send_time_position(sfizz_synth_t* synth, int delay
* @brief Send the playback state.
*
* This command should be delay-ordered with all other time/signature commands, namely
* tempo(), timeSignature(), timePosition(), and playbackState(), otherwise the behavior
* of the synth is undefined.
* sfizz_send_tempo(), sfizz_send_time_signature(), sfizz_send_time_position(),
* and sfizz_send_playback_state(), otherwise the behavior of the synth is undefined.
*
* @since 0.5.0
*

View file

@ -38,30 +38,54 @@ Float ADSREnvelope::secondsToExpRate(Float timeInSeconds) const noexcept
return std::exp(Float(-9.0) / (timeInSeconds * sampleRate));
};
void ADSREnvelope::reset(const EGDescription& desc, const Region& region, const MidiState& state, int delay, float velocity, float sampleRate) noexcept
void ADSREnvelope::reset(const EGDescription& desc, const Region& region, int delay, float velocity, float sampleRate) noexcept
{
this->sampleRate = sampleRate;
this->delay = delay + secondsToSamples(desc.getDelay(state, velocity));
this->attackStep = secondsToLinRate(desc.getAttack(state, velocity));
this->decayRate = secondsToExpRate(desc.getDecay(state, velocity));
this->releaseRate = secondsToExpRate(desc.getRelease(state, velocity));
this->hold = secondsToSamples(desc.getHold(state, velocity));
this->sustain = clamp(desc.getSustain(state, velocity), 0.0f, 1.0f);
this->start = clamp(desc.getStart(state, velocity), 0.0f, 1.0f);
desc_ = &desc;
triggerVelocity_ = velocity;
currentState = State::Delay; // Has to be before the update
updateValues(delay);
releaseDelay = 0;
sustainThreshold = this->sustain + config::virtuallyZero;
shouldRelease = false;
freeRunning = (
(this->sustain <= Float(config::sustainFreeRunningThreshold))
|| (region.loopMode == LoopMode::one_shot && region.isOscillator())
);
currentValue = this->start;
currentState = State::Delay;
}
void ADSREnvelope::updateValues(int delay) noexcept
{
if (currentState == State::Delay)
this->delay = delay + secondsToSamples(desc_->getDelay(midiState_, triggerVelocity_, delay));
this->attackStep = secondsToLinRate(desc_->getAttack(midiState_, triggerVelocity_, delay));
this->decayRate = secondsToExpRate(desc_->getDecay(midiState_, triggerVelocity_, delay));
this->releaseRate = secondsToExpRate(desc_->getRelease(midiState_, triggerVelocity_, delay));
this->hold = secondsToSamples(desc_->getHold(midiState_, triggerVelocity_, delay));
this->sustain = clamp(desc_->getSustain(midiState_, triggerVelocity_, delay), 0.0f, 1.0f);
this->start = clamp(desc_->getStart(midiState_, triggerVelocity_, delay), 0.0f, 1.0f);
sustainThreshold = this->sustain + config::virtuallyZero;
}
void ADSREnvelope::getBlock(absl::Span<Float> output) noexcept
{
if (desc_ && desc_->dynamic) {
int processed = 0;
int remaining = static_cast<int>(output.size());
while(remaining > 0) {
updateValues(processed);
int chunkSize = min(config::processChunkSize, remaining);
getBlockInternal(output.subspan(processed, chunkSize));
processed += chunkSize;
remaining -= chunkSize;
}
} else {
getBlockInternal(output);
}
}
void ADSREnvelope::getBlockInternal(absl::Span<Float> output) noexcept
{
State currentState = this->currentState;
Float currentValue = this->currentValue;
@ -122,7 +146,8 @@ void ADSREnvelope::getBlock(absl::Span<Float> output) noexcept
break;
}
while (count < size) {
currentValue = std::max(sustain, currentValue + transitionDelta);
if (currentValue > sustain)
currentValue += transitionDelta;
output[count++] = currentValue;
}
break;
@ -173,6 +198,14 @@ void ADSREnvelope::startRelease(int releaseDelay) noexcept
this->releaseDelay = releaseDelay;
}
void ADSREnvelope::cancelRelease(int delay) noexcept
{
(void)delay;
currentState = State::Sustain;
shouldRelease = false;
this->releaseDelay = -1;
}
void ADSREnvelope::setReleaseTime(Float timeInSeconds) noexcept
{
releaseRate = secondsToExpRate(timeInSeconds);

View file

@ -18,7 +18,8 @@ class ADSREnvelope {
public:
using Float = float;
ADSREnvelope() = default;
ADSREnvelope(const MidiState& state)
: midiState_(state) {}
/**
* @brief Resets the ADSR envelope given a Region, the current midi state, and a delay and
* trigger velocity
@ -29,10 +30,9 @@ public:
* @param delay
* @param velocity
*/
void reset(const EGDescription& desc, const Region& region, const MidiState& state, int delay, float velocity, float sampleRate) noexcept;
void reset(const EGDescription& desc, const Region& region, int delay, float velocity, float sampleRate) noexcept;
/**
* @brief Get a block of values for the envelope. This method tries hard to be efficient
* and hopefully it is.
* @brief Get the next block of values for the envelope.
*
* @param output
*/
@ -49,6 +49,12 @@ public:
* @param releaseDelay the delay before releasing in samples
*/
void startRelease(int releaseDelay) noexcept;
/**
* @brief Cancel a release and get back into sustain.
*
* @param delay
*/
void cancelRelease(int delay) noexcept;
/**
* @brief Is the envelope smoothing?
*
@ -75,6 +81,8 @@ private:
int secondsToSamples(Float timeInSeconds) const noexcept;
Float secondsToLinRate(Float timeInSeconds) const noexcept;
Float secondsToExpRate(Float timeInSeconds) const noexcept;
void updateValues(int delay = 0) noexcept;
void getBlockInternal(absl::Span<Float> output) noexcept;
enum class State {
Delay,
@ -88,6 +96,9 @@ private:
};
State currentState { State::Done };
Float currentValue { 0.0 };
const EGDescription* desc_ { nullptr };
const MidiState& midiState_;
float triggerVelocity_ { 0.0f };
int delay { 0 };
Float attackStep { 0 };
Float decayRate { 0 };

View file

@ -26,10 +26,12 @@
#pragma once
#include "Config.h"
#include "utility/LeakDetector.h"
#include <cstdint>
#include <cstdlib>
#include <cstring>
#include <memory>
#include <type_traits>
#include <algorithm>
#include <utility>
#include <atomic>
@ -230,7 +232,7 @@ public:
*
* @param other
*/
Buffer(const Buffer<Type>& other)
Buffer(const Buffer<Type, Alignment>& other)
{
resize(other.size());
std::memcpy(this->data(), other.data(), other.size() * sizeof(value_type));
@ -241,7 +243,7 @@ public:
*
* @param other
*/
Buffer(Buffer<Type>&& other) noexcept
Buffer(Buffer<Type, Alignment>&& other) noexcept
: largerSize(other.largerSize),
alignedSize(other.alignedSize),
normalData(other.normalData),
@ -252,7 +254,7 @@ public:
other._clear();
}
Buffer<Type>& operator=(const Buffer<Type>& other)
Buffer<Type, Alignment>& operator=(const Buffer<Type, Alignment>& other)
{
if (this != &other) {
resize(other.size());
@ -261,7 +263,7 @@ public:
return *this;
}
Buffer<Type>& operator=(Buffer<Type>&& other) noexcept
Buffer<Type, Alignment>& operator=(Buffer<Type, Alignment>&& other) noexcept
{
if (this != &other) {
if (largerSize > 0)

View file

@ -70,7 +70,8 @@ namespace config {
constexpr float powerFollowerReleaseTime { 200e-3f };
constexpr uint16_t numCCs { 512 };
constexpr int maxCurves { 256 };
constexpr int chunkSize { 1024 };
constexpr int fileChunkSize { 1024 };
constexpr int processChunkSize { 16 };
constexpr unsigned int defaultAlignment { 16 };
constexpr int filtersInPool { maxVoices * 2 };
constexpr int excessFileFrames { 64 };

View file

@ -41,23 +41,27 @@ FloatSpec oscillatorDetuneMod { 0.0f, {-12000.0f, 12000.0f}, kPermissiveBounds }
FloatSpec oscillatorModDepth { 0.0f, {0.0f, 10000.0f}, kNormalizePercent|kPermissiveBounds };
FloatSpec oscillatorModDepthMod { 0.0f, {0.0f, 10000.0f}, kNormalizePercent|kPermissiveBounds };
Int32Spec oscillatorQuality { 1, {0, 3}, 0 };
UInt32Spec group { 0, {0, uint32_t_max}, 0 };
Int64Spec group { 0, {-int32_t_max, uint32_t_max}, 0 };
FloatSpec offTime { 6e-3f, {0.0f, 100.0f}, kPermissiveBounds };
UInt32Spec polyphony { config::maxVoices, {0, config::maxVoices}, kEnforceBounds };
UInt32Spec notePolyphony { config::maxVoices, {0, config::maxVoices}, kEnforceBounds };
UInt32Spec notePolyphony { config::maxVoices, {1, config::maxVoices}, kEnforceBounds };
UInt8Spec key { 60, {0, 127}, kCanBeNote };
UInt8Spec loKey { 0, {0, 127}, kCanBeNote };
UInt8Spec hiKey { 127, {0, 127}, kCanBeNote };
FloatSpec loCC { 0, {0.0f, 127.0f}, kNormalizeMidi|kPermissiveBounds };
FloatSpec hiCC { 127, {0.0f, 127.0f}, kNormalizeMidi|kPermissiveBounds };
FloatSpec hiCC { 127, {0.0f, 127.0f}, kNormalizeMidi|kFillGap|kPermissiveBounds };
FloatSpec xfoutLo { 127.0f, {0.0f, 127.0f}, kNormalizeMidi|kPermissiveBounds };
FloatSpec xfoutHi { 127.0f, {0.0f, 127.0f}, kNormalizeMidi|kFillGap|kPermissiveBounds };
FloatSpec xfinLo { 0.0f, {0.0f, 127.0f}, kNormalizeMidi|kPermissiveBounds };
FloatSpec xfinHi { 0.0f, {0.0f, 127.0f}, kNormalizeMidi|kFillGap|kPermissiveBounds };
FloatSpec loVel { 0, {0.0f, 127.0f}, kNormalizeMidi|kPermissiveBounds };
FloatSpec hiVel { 127, {0.0f, 127.0f}, kNormalizeMidi|kPermissiveBounds };
FloatSpec hiVel { 127, {0.0f, 127.0f}, kNormalizeMidi|kFillGap|kPermissiveBounds };
FloatSpec loChannelAftertouch { 0, {0, 127}, kNormalizeMidi|kPermissiveBounds };
FloatSpec hiChannelAftertouch { 127, {0, 127}, kNormalizeMidi|kPermissiveBounds };
FloatSpec hiChannelAftertouch { 127, {0, 127}, kNormalizeMidi|kFillGap|kPermissiveBounds };
FloatSpec loPolyAftertouch { 0, {0, 127}, kNormalizeMidi|kPermissiveBounds };
FloatSpec hiPolyAftertouch { 127, {0, 127}, kNormalizeMidi|kPermissiveBounds };
FloatSpec hiPolyAftertouch { 127, {0, 127}, kNormalizeMidi|kFillGap|kPermissiveBounds };
FloatSpec loBend { -8191, {-8192.0f, 8191.0f}, kNormalizeBend|kPermissiveBounds };
FloatSpec hiBend { 8191, {-8192.0f, 8191.0f}, kNormalizeBend|kPermissiveBounds };
FloatSpec hiBend { 8191, {-8192.0f, 8191.0f}, kNormalizeBend|kFillGap|kPermissiveBounds };
FloatSpec loNormalized { 0.0f, {0.0f, 1.0f}, kPermissiveBounds };
FloatSpec hiNormalized { 1.0f, {0.0f, 1.0f}, kPermissiveBounds };
FloatSpec loBipolar { -1.0f, {-1.0f, 1.0f}, kPermissiveBounds };
@ -86,6 +90,7 @@ FloatSpec width { 100.0f, {-100.0f, 100.0f}, kNormalizePercent|kPermissiveBounds
FloatSpec widthMod { 0.0f, {-200.0f, 200.0f}, kNormalizePercent|kPermissiveBounds };
FloatSpec ampKeytrack { 0.0f, {-96.0f, 12.0f}, kPermissiveBounds };
FloatSpec ampVeltrack { 100.0f, {-100.0f, 100.0f}, kNormalizePercent|kPermissiveBounds };
FloatSpec ampVeltrackMod { 0.0f, {-100.0f, 100.0f}, kNormalizePercent|kPermissiveBounds };
FloatSpec ampVelcurve { 0.0f, {0.0f, 1.0f}, kPermissiveBounds };
FloatSpec ampRandom { 0.0f, {-24.0f, 24.0f}, kPermissiveBounds };
BoolSpec rtDead { false, {false, true}, kEnforceBounds };
@ -99,6 +104,7 @@ FloatSpec filterGainMod { 0.0f, {-96.0f, 96.0f}, kPermissiveBounds };
FloatSpec filterRandom { 0.0f, {-12000.0f, 12000.0f}, kPermissiveBounds };
FloatSpec filterKeytrack { 0, {0, 1200}, kPermissiveBounds };
FloatSpec filterVeltrack { 0, {-12000, 12000}, kPermissiveBounds };
FloatSpec filterVeltrackMod { 0.0f, {-12000, 12000}, kPermissiveBounds };
FloatSpec eqBandwidth { 1.0f, {0.001f, 4.0f}, kPermissiveBounds };
FloatSpec eqBandwidthMod { 0.0f, {-4.0f, 4.0f}, kPermissiveBounds };
FloatSpec eqFrequency { 0.0f, {0.0f, 20000.0f}, kPermissiveBounds };
@ -110,6 +116,7 @@ FloatSpec eqVel2Gain { 0.0f, {-96.0f, 96.0f}, kPermissiveBounds };
FloatSpec pitchKeytrack { 100, {-1200, 1200}, kPermissiveBounds };
FloatSpec pitchRandom { 0.0f, {-12000.0f, 12000.0f}, kPermissiveBounds };
FloatSpec pitchVeltrack { 0, {-12000, 12000}, kPermissiveBounds };
FloatSpec pitchVeltrackMod { 0.0f, {-12000, 12000}, kPermissiveBounds };
FloatSpec transpose { 0, {-127, 127}, kPermissiveBounds };
FloatSpec pitch { 0.0f, {-2400.0f, 2400.0f}, kPermissiveBounds };
FloatSpec pitchMod { 0.0f, {-2400.0f, 2400.0f}, kPermissiveBounds };
@ -144,11 +151,14 @@ FloatSpec egPercent { 0.0f, {0.0f, 100.0f}, kNormalizePercent|kPermissiveBounds
FloatSpec egPercentMod { 0.0f, {-100.0f, 100.0f}, kNormalizePercent|kPermissiveBounds };
FloatSpec egDepth { 0.0f, {-12000.0f, 12000.0f}, kPermissiveBounds };
FloatSpec egVel2Depth { 0.0f, {-12000.0f, 12000.0f}, kPermissiveBounds };
BoolSpec egDynamic { 0, {0, 1}, kEnforceBounds };
BoolSpec flexEGAmpeg { false, {0, 1}, kEnforceBounds };
BoolSpec flexEGDynamic { 0, {0, 1}, kEnforceBounds };
Int32Spec flexEGSustain { 0, {0, 100}, kEnforceLowerBound|kPermissiveUpperBound };
FloatSpec flexEGPointTime { 0.0f, {0.0f, 100.0f}, kPermissiveBounds };
FloatSpec flexEGPointTimeMod { 0.0f, {-100.0f, 100.0f}, kPermissiveBounds };
FloatSpec flexEGPointLevel { 0.0f, {-1.0f, 1.0f}, kPermissiveBounds };
FloatSpec flexEGPointLevelMod { 0.0f, {-1.0f, 1.0f}, kPermissiveBounds };
FloatSpec flexEGPointShape { 0.0f, {-100.0f, 100.0f}, kPermissiveBounds };
Int32Spec sampleQuality { 2, {0, 10}, 0 };
Int32Spec octaveOffset { 0, {-10, 10}, kPermissiveBounds };
@ -183,6 +193,7 @@ FloatSpec lofiBitred { 0.0f, {0.0f, 100.0f}, 0 };
FloatSpec lofiDecim { 0.0f, {0.0f, 100.0f}, 0 };
FloatSpec rectify { 0.0f, {0.0f, 100.0f}, 0 };
UInt32Spec stringsNumber { maxStrings, {0, maxStrings}, 0 };
BoolSpec sustainCancelsRelease { false, {0, 1}, kEnforceBounds };
ESpec<Trigger> trigger { Trigger::attack, {Trigger::attack, Trigger::release_key}, 0};
ESpec<CrossfadeCurve> crossfadeCurve { CrossfadeCurve::power, {CrossfadeCurve::gain, CrossfadeCurve::power}, 0};

View file

@ -60,6 +60,7 @@ enum OpcodeFlags : int {
kNormalizeBend = 1 << 7,
kWrapPhase = 1 << 8,
kDb2Mag = 1 << 9,
kFillGap = 1 << 10, // Fill in the gap when converting from discrete midi values to float, so that 13 is actually 13.999999...
};
template<class T>
@ -97,8 +98,12 @@ struct OpcodeSpec
return input;
else if (flags & kNormalizePercent)
return static_cast<U>(input / U(100));
else if (flags & kNormalizeMidi)
return static_cast<U>(input / U(127));
else if (flags & kNormalizeMidi) {
if ((flags & kFillGap) && (input <= U(126)) && input >= 0)
return std::nextafter(static_cast<U>((input + 1.0f) / U(127)), 0.0f);
else
return static_cast<U>(input / U(127));
}
else if (flags & kNormalizeBend)
return static_cast<U>(input / U(8191));
else if (flags & kDb2Mag)
@ -148,7 +153,7 @@ namespace Default
extern const OpcodeSpec<float> oscillatorModDepth;
extern const OpcodeSpec<float> oscillatorModDepthMod;
extern const OpcodeSpec<int32_t> oscillatorQuality;
extern const OpcodeSpec<uint32_t> group;
extern const OpcodeSpec<int64_t> group;
extern const OpcodeSpec<float> offTime;
extern const OpcodeSpec<uint32_t> polyphony;
extern const OpcodeSpec<uint32_t> notePolyphony;
@ -159,6 +164,10 @@ namespace Default
extern const OpcodeSpec<float> hiVel;
extern const OpcodeSpec<float> loCC;
extern const OpcodeSpec<float> hiCC;
extern const OpcodeSpec<float> xfoutLo;
extern const OpcodeSpec<float> xfoutHi;
extern const OpcodeSpec<float> xfinHi;
extern const OpcodeSpec<float> xfinLo;
extern const OpcodeSpec<float> loBend;
extern const OpcodeSpec<float> hiBend;
extern const OpcodeSpec<float> loNormalized;
@ -193,6 +202,7 @@ namespace Default
extern const OpcodeSpec<float> widthMod;
extern const OpcodeSpec<float> ampKeytrack;
extern const OpcodeSpec<float> ampVeltrack;
extern const OpcodeSpec<float> ampVeltrackMod;
extern const OpcodeSpec<float> ampVelcurve;
extern const OpcodeSpec<float> ampRandom;
extern const OpcodeSpec<bool> rtDead;
@ -206,6 +216,7 @@ namespace Default
extern const OpcodeSpec<float> filterRandom;
extern const OpcodeSpec<float> filterKeytrack;
extern const OpcodeSpec<float> filterVeltrack;
extern const OpcodeSpec<float> filterVeltrackMod;
extern const OpcodeSpec<float> eqBandwidth;
extern const OpcodeSpec<float> eqBandwidthMod;
extern const OpcodeSpec<float> eqFrequency;
@ -217,6 +228,7 @@ namespace Default
extern const OpcodeSpec<float> pitchKeytrack;
extern const OpcodeSpec<float> pitchRandom;
extern const OpcodeSpec<float> pitchVeltrack;
extern const OpcodeSpec<float> pitchVeltrackMod;
extern const OpcodeSpec<float> transpose;
extern const OpcodeSpec<float> pitch;
extern const OpcodeSpec<float> pitchMod;
@ -251,11 +263,14 @@ namespace Default
extern const OpcodeSpec<float> egPercentMod;
extern const OpcodeSpec<float> egDepth;
extern const OpcodeSpec<float> egVel2Depth;
extern const OpcodeSpec<bool> egDynamic;
extern const OpcodeSpec<bool> flexEGAmpeg;
extern const OpcodeSpec<bool> flexEGDynamic;
extern const OpcodeSpec<int32_t> flexEGSustain;
extern const OpcodeSpec<float> flexEGPointTime;
extern const OpcodeSpec<float> flexEGPointTimeMod;
extern const OpcodeSpec<float> flexEGPointLevel;
extern const OpcodeSpec<float> flexEGPointLevelMod;
extern const OpcodeSpec<float> flexEGPointShape;
extern const OpcodeSpec<int32_t> sampleQuality;
extern const OpcodeSpec<int32_t> octaveOffset;
@ -296,6 +311,7 @@ namespace Default
extern const OpcodeSpec<SelfMask> selfMask;
extern const OpcodeSpec<FilterType> filter;
extern const OpcodeSpec<EqType> eq;
extern const OpcodeSpec<bool> sustainCancelsRelease;
// Default/max count for objects
constexpr int numEQs { 3 };

View file

@ -42,22 +42,6 @@ namespace sfz {
*
*/
/**
* @brief If a cc switch exists for the value, returns the value with the CC modifier, otherwise returns the value alone.
*
* @param ccValues
* @param ccSwitch
* @param value
* @return float
*/
inline float ccSwitchedValue(const MidiState& state, const absl::optional<CCData<float>>& ccSwitch, float value) noexcept
{
if (ccSwitch)
return value + ccSwitch->data * state.getCCValue(ccSwitch->cc);
else
return value;
}
struct EGDescription {
EGDescription() = default;
EGDescription(const EGDescription&) = default;
@ -89,6 +73,7 @@ struct EGDescription {
CCMap<float> ccRelease;
CCMap<float> ccStart;
CCMap<float> ccSustain;
bool dynamic { false };
/**
* @brief Get the attack with possibly a CC modifier and a velocity modifier
@ -97,12 +82,12 @@ struct EGDescription {
* @param velocity
* @return float
*/
float getAttack(const MidiState& state, float velocity) const noexcept
float getAttack(const MidiState& state, float velocity, int delay = 0) const noexcept
{
ASSERT(velocity >= 0.0f && velocity <= 1.0f);
float returnedValue { attack + velocity * vel2attack };
for (auto& mod: ccAttack) {
returnedValue += state.getCCValue(mod.cc) * mod.data;
returnedValue += state.getCCValueAt(mod.cc, delay) * mod.data;
}
return returnedValue;
}
@ -113,12 +98,12 @@ struct EGDescription {
* @param velocity
* @return float
*/
float getDecay(const MidiState& state, float velocity) const noexcept
float getDecay(const MidiState& state, float velocity, int delay = 0) const noexcept
{
ASSERT(velocity >= 0.0f && velocity <= 1.0f);
float returnedValue { decay + velocity * vel2decay };
for (auto& mod: ccDecay) {
returnedValue += state.getCCValue(mod.cc) * mod.data;
returnedValue += state.getCCValueAt(mod.cc, delay) * mod.data;
}
return returnedValue;
}
@ -129,12 +114,12 @@ struct EGDescription {
* @param velocity
* @return float
*/
float getDelay(const MidiState& state, float velocity) const noexcept
float getDelay(const MidiState& state, float velocity, int delay = 0) const noexcept
{
ASSERT(velocity >= 0.0f && velocity <= 1.0f);
float returnedValue { delay + velocity * vel2delay };
float returnedValue { this->delay + velocity * vel2delay };
for (auto& mod: ccDelay) {
returnedValue += state.getCCValue(mod.cc) * mod.data;
returnedValue += state.getCCValueAt(mod.cc, delay) * mod.data;
}
return returnedValue;
}
@ -145,12 +130,12 @@ struct EGDescription {
* @param velocity
* @return float
*/
float getHold(const MidiState& state, float velocity) const noexcept
float getHold(const MidiState& state, float velocity, int delay = 0) const noexcept
{
ASSERT(velocity >= 0.0f && velocity <= 1.0f);
float returnedValue { hold + velocity * vel2hold };
for (auto& mod: ccHold) {
returnedValue += state.getCCValue(mod.cc) * mod.data;
returnedValue += state.getCCValueAt(mod.cc, delay) * mod.data;
}
return returnedValue;
}
@ -161,12 +146,12 @@ struct EGDescription {
* @param velocity
* @return float
*/
float getRelease(const MidiState& state, float velocity) const noexcept
float getRelease(const MidiState& state, float velocity, int delay = 0) const noexcept
{
ASSERT(velocity >= 0.0f && velocity <= 1.0f);
float returnedValue { release + velocity * vel2release };
for (auto& mod: ccRelease) {
returnedValue += state.getCCValue(mod.cc) * mod.data;
returnedValue += state.getCCValueAt(mod.cc, delay) * mod.data;
}
return returnedValue;
}
@ -177,12 +162,12 @@ struct EGDescription {
* @param velocity
* @return float
*/
float getStart(const MidiState& state, float velocity) const noexcept
float getStart(const MidiState& state, float velocity, int delay = 0) const noexcept
{
UNUSED(velocity);
float returnedValue { start };
for (auto& mod: ccStart) {
returnedValue += state.getCCValue(mod.cc) * mod.data;
returnedValue += state.getCCValueAt(mod.cc, delay) * mod.data;
}
return returnedValue;
}
@ -193,12 +178,12 @@ struct EGDescription {
* @param velocity
* @return float
*/
float getSustain(const MidiState& state, float velocity) const noexcept
float getSustain(const MidiState& state, float velocity, int delay = 0) const noexcept
{
ASSERT(velocity >= 0.0f && velocity <= 1.0f);
float returnedValue { sustain + velocity * vel2sustain };
for (auto& mod: ccSustain) {
returnedValue += state.getCCValue(mod.cc) * mod.data;
returnedValue += state.getCCValueAt(mod.cc, delay) * mod.data;
}
return returnedValue;
}

View file

@ -1,4 +1,7 @@
#include "EQPool.h"
#include "Region.h"
#include "Resources.h"
#include "BufferPool.h"
#include "SIMDHelpers.h"
#include "utility/SwapAndPop.h"
#include <absl/algorithm/container.h>
@ -31,9 +34,10 @@ void sfz::EQHolder::setup(const Region& region, unsigned eqId, float velocity)
baseBandwidth = description->bandwidth;
baseGain = description->gain + velocity * description->vel2gain;
gainTarget = resources.modMatrix.findTarget(ModKey::createNXYZ(ModId::EqGain, region.id, eqId));
bandwidthTarget = resources.modMatrix.findTarget(ModKey::createNXYZ(ModId::EqBandwidth, region.id, eqId));
frequencyTarget = resources.modMatrix.findTarget(ModKey::createNXYZ(ModId::EqFrequency, region.id, eqId));
const ModMatrix& mm = resources.getModMatrix();
gainTarget = mm.findTarget(ModKey::createNXYZ(ModId::EqGain, region.id, eqId));
bandwidthTarget = mm.findTarget(ModKey::createNXYZ(ModId::EqBandwidth, region.id, eqId));
frequencyTarget = mm.findTarget(ModKey::createNXYZ(ModId::EqFrequency, region.id, eqId));
// Disables smoothing of the parameters on the first call
prepared = false;
@ -47,10 +51,11 @@ void sfz::EQHolder::process(const float** inputs, float** outputs, unsigned numF
return;
}
ModMatrix& mm = resources.modMatrix;
auto frequencySpan = resources.bufferPool.getBuffer(numFrames);
auto bandwidthSpan = resources.bufferPool.getBuffer(numFrames);
auto gainSpan = resources.bufferPool.getBuffer(numFrames);
ModMatrix& mm = resources.getModMatrix();
BufferPool& bufferPool = resources.getBufferPool();
auto frequencySpan = bufferPool.getBuffer(numFrames);
auto bandwidthSpan = bufferPool.getBuffer(numFrames);
auto gainSpan = bufferPool.getBuffer(numFrames);
if (!frequencySpan || !bandwidthSpan || !gainSpan)
return;

View file

@ -1,12 +1,14 @@
#pragma once
#include "SfzFilter.h"
#include "Region.h"
#include "Resources.h"
#include "Defaults.h"
#include "modulations/ModMatrix.h"
#include <vector>
#include <memory>
namespace sfz
{
namespace sfz {
struct Region;
class Resources;
struct EQDescription;
class EQHolder
{
@ -52,4 +54,4 @@ private:
ModMatrix::TargetId bandwidthTarget;
};
}
} // namespace sfz

View file

@ -13,6 +13,7 @@
#include <absl/strings/numbers.h>
#include <absl/strings/string_view.h>
#include <map>
#include <vector>
#include <string>
#include <cstdio>
#include <cstring>

View file

@ -101,7 +101,7 @@ void streamFromFile(sfz::AudioReader& reader, sfz::FileAudioBuffer& output, std:
{
const auto numFrames = static_cast<size_t>(reader.frames());
const auto numChannels = reader.channels();
const auto chunkSize = static_cast<size_t>(sfz::config::chunkSize);
const auto chunkSize = static_cast<size_t>(sfz::config::fileChunkSize);
output.reset();
output.addChannels(reader.channels());

Some files were not shown because too many files have changed in this diff Show more