From 236906fec4885865a75c037e941ac5d3721cd4a9 Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Thu, 27 Feb 2020 14:05:05 +0100 Subject: [PATCH 01/93] Add multiplyAdd with fixed gain --- benchmarks/BM_multiplyAddFixedGain.cpp | 78 ++++++++++++++++++++++++++ benchmarks/CMakeLists.txt | 1 + src/sfizz/SIMDDummy.cpp | 6 ++ src/sfizz/SIMDHelpers.h | 20 +++++++ src/sfizz/SIMDSSE.cpp | 23 ++++++++ 5 files changed, 128 insertions(+) create mode 100644 benchmarks/BM_multiplyAddFixedGain.cpp diff --git a/benchmarks/BM_multiplyAddFixedGain.cpp b/benchmarks/BM_multiplyAddFixedGain.cpp new file mode 100644 index 00000000..b484389d --- /dev/null +++ b/benchmarks/BM_multiplyAddFixedGain.cpp @@ -0,0 +1,78 @@ +// SPDX-License-Identifier: BSD-2-Clause + +// This code is part of the sfizz library and is licensed under a BSD 2-clause +// license. You should have receive a LICENSE.md file along with the code. +// If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz + +#include "SIMDHelpers.h" +#include +#include +#include +#include +#include +#include + +class MultiplyAddFixedGain : public benchmark::Fixture { +public: + void SetUp(const ::benchmark::State& state) { + std::random_device rd { }; + std::mt19937 gen { rd() }; + std::uniform_real_distribution dist { 0, 1 }; + input = std::vector(state.range(0)); + output = std::vector(state.range(0)); + gain = dist(gen); + std::fill(output.begin(), output.end(), 1.0f ); + std::generate(input.begin(), input.end(), [&]() { return dist(gen); }); + } + + void TearDown(const ::benchmark::State& state [[maybe_unused]]) { + + } + + float gain = {}; + std::vector input; + std::vector output; +}; + +BENCHMARK_DEFINE_F(MultiplyAddFixedGain, Straight)(benchmark::State& state) { + for (auto _ : state) + { + for (int i = 0; i < state.range(0); ++i) + output[i] += gain * input[i]; + } +} + +BENCHMARK_DEFINE_F(MultiplyAddFixedGain, Scalar)(benchmark::State& state) { + for (auto _ : state) + { + sfz::multiplyAdd(gain, input, absl::MakeSpan(output)); + } +} + +BENCHMARK_DEFINE_F(MultiplyAddFixedGain, SIMD)(benchmark::State& state) { + for (auto _ : state) + { + sfz::multiplyAdd(gain, input, absl::MakeSpan(output)); + } +} + +BENCHMARK_DEFINE_F(MultiplyAddFixedGain, Scalar_Unaligned)(benchmark::State& state) { + for (auto _ : state) + { + sfz::multiplyAdd(gain, absl::MakeSpan(input).subspan(1), absl::MakeSpan(output).subspan(1)); + } +} + +BENCHMARK_DEFINE_F(MultiplyAddFixedGain, SIMD_Unaligned)(benchmark::State& state) { + for (auto _ : state) + { + sfz::multiplyAdd(gain, absl::MakeSpan(input).subspan(1), absl::MakeSpan(output).subspan(1)); + } +} + +BENCHMARK_REGISTER_F(MultiplyAddFixedGain, Straight)->RangeMultiplier(4)->Range(1 << 2, 1 << 12); +BENCHMARK_REGISTER_F(MultiplyAddFixedGain, Scalar)->RangeMultiplier(4)->Range(1 << 2, 1 << 12); +BENCHMARK_REGISTER_F(MultiplyAddFixedGain, SIMD)->RangeMultiplier(4)->Range(1 << 2, 1 << 12); +BENCHMARK_REGISTER_F(MultiplyAddFixedGain, Scalar_Unaligned)->RangeMultiplier(4)->Range(1 << 2, 1 << 12); +BENCHMARK_REGISTER_F(MultiplyAddFixedGain, SIMD_Unaligned)->RangeMultiplier(4)->Range(1 << 2, 1 << 12); +BENCHMARK_MAIN(); diff --git a/benchmarks/CMakeLists.txt b/benchmarks/CMakeLists.txt index dd7bfb65..4f27ca1a 100644 --- a/benchmarks/CMakeLists.txt +++ b/benchmarks/CMakeLists.txt @@ -46,6 +46,7 @@ target_link_libraries(bm_ADSR PRIVATE sfizz::sfizz) sfizz_add_benchmark(bm_add BM_add.cpp) sfizz_add_benchmark(bm_multiplyAdd BM_multiplyAdd.cpp) +sfizz_add_benchmark(bm_multiplyAddFixedGain BM_multiplyAddFixedGain.cpp) sfizz_add_benchmark(bm_subtract BM_subtract.cpp) sfizz_add_benchmark(bm_copy BM_copy.cpp) sfizz_add_benchmark(bm_pan BM_pan.cpp) diff --git a/src/sfizz/SIMDDummy.cpp b/src/sfizz/SIMDDummy.cpp index 22a1140b..2a0ca34d 100644 --- a/src/sfizz/SIMDDummy.cpp +++ b/src/sfizz/SIMDDummy.cpp @@ -76,6 +76,12 @@ void sfz::multiplyAdd(absl::Span gain, absl::Span(gain, input, output); } +template <> +void sfz::multiplyAdd(const float gain, absl::Span input, absl::Span output) noexcept +{ + multiplyAdd(gain, input, output); +} + template <> float sfz::loopingSFZIndex(absl::Span jumps, absl::Span leftCoeff, absl::Span rightCoeff, absl::Span indices, float floatIndex, float loopEnd, float loopStart) noexcept { diff --git a/src/sfizz/SIMDHelpers.h b/src/sfizz/SIMDHelpers.h index f4c5a2d7..b3725129 100644 --- a/src/sfizz/SIMDHelpers.h +++ b/src/sfizz/SIMDHelpers.h @@ -491,6 +491,12 @@ namespace _internals { { *output++ += (*gain++) * (*input++); } + + template + inline void snippetMultiplyAdd(const T gain, const T*& input, T*& output) + { + *output++ += gain * (*input++); + } } /** @@ -520,6 +526,20 @@ void multiplyAdd(absl::Span gain, absl::Span input, absl::Span template <> void multiplyAdd(absl::Span gain, absl::Span input, absl::Span output) noexcept; +template +void multiplyAdd(const T gain, absl::Span input, absl::Span output) noexcept +{ + ASSERT(input.size() <= output.size()); + auto* in = input.begin(); + auto* out = output.begin(); + auto* sentinel = out + std::min(output.size(), input.size()); + while (out < sentinel) + _internals::snippetMultiplyAdd(gain, in, out); +} + +template <> +void multiplyAdd(const float gain, absl::Span input, absl::Span output) noexcept; + namespace _internals { template inline void snippetRampLinear(T*& output, T& value, T step) diff --git a/src/sfizz/SIMDSSE.cpp b/src/sfizz/SIMDSSE.cpp index ba98c12d..74adfb66 100644 --- a/src/sfizz/SIMDSSE.cpp +++ b/src/sfizz/SIMDSSE.cpp @@ -316,6 +316,29 @@ void sfz::multiplyAdd(absl::Span gain, absl::Span(g, in, out); } +template <> +void sfz::multiplyAdd(const float gain, absl::Span input, absl::Span output) noexcept +{ + auto* in = input.begin(); + auto* out = output.begin(); + const auto size = std::min(output.size(), input.size()); + const auto* lastAligned = prevAligned(output.begin() + size); + + while (unaligned(out, in) && out < lastAligned) + _internals::snippetMultiplyAdd(gain, in, out); + + auto mmGain = _mm_set1_ps(gain); + while (out < lastAligned) { + auto mmOut = _mm_load_ps(out); + mmOut = _mm_add_ps(_mm_mul_ps(mmGain, _mm_load_ps(in)), mmOut); + _mm_store_ps(out, mmOut); + incrementAll(in, out); + } + + while (out < output.end()) + _internals::snippetMultiplyAdd(gain, in, out); +} + template <> float sfz::loopingSFZIndex(absl::Span jumps, absl::Span leftCoeffs, From f571ac37ef8e2c78809cc16a63f344d2017f8d90 Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Wed, 26 Feb 2020 21:31:00 +0100 Subject: [PATCH 02/93] Add the HIIR downsampler --- src/external/hiir/Downsampler2xFpu.h | 115 +++++++++ src/external/hiir/Downsampler2xFpu.hpp | 303 ++++++++++++++++++++++ src/external/hiir/Downsampler2xNeon.h | 126 ++++++++++ src/external/hiir/Downsampler2xNeon.hpp | 299 ++++++++++++++++++++++ src/external/hiir/Downsampler2xSse.h | 126 ++++++++++ src/external/hiir/Downsampler2xSse.hpp | 322 ++++++++++++++++++++++++ 6 files changed, 1291 insertions(+) create mode 100644 src/external/hiir/Downsampler2xFpu.h create mode 100644 src/external/hiir/Downsampler2xFpu.hpp create mode 100644 src/external/hiir/Downsampler2xNeon.h create mode 100644 src/external/hiir/Downsampler2xNeon.hpp create mode 100644 src/external/hiir/Downsampler2xSse.h create mode 100644 src/external/hiir/Downsampler2xSse.hpp diff --git a/src/external/hiir/Downsampler2xFpu.h b/src/external/hiir/Downsampler2xFpu.h new file mode 100644 index 00000000..d69f1e44 --- /dev/null +++ b/src/external/hiir/Downsampler2xFpu.h @@ -0,0 +1,115 @@ +/***************************************************************************** + + Downsampler2xFpu.h + Author: Laurent de Soras, 2005 + +Downsamples by a factor 2 the input signal, using FPU. + +Template parameters: + - NC: number of coefficients, > 0 + +--- Legal stuff --- + +This program is free software. It comes without any warranty, to +the extent permitted by applicable law. You can redistribute it +and/or modify it under the terms of the Do What The Fuck You Want +To Public License, Version 2, as published by Sam Hocevar. See +http://sam.zoy.org/wtfpl/COPYING for more details. + +*Tab=3***********************************************************************/ + + + +#if ! defined (hiir_Downsampler2xFpu_HEADER_INCLUDED) +#define hiir_Downsampler2xFpu_HEADER_INCLUDED + +#if defined (_MSC_VER) + #pragma once + #pragma warning (4 : 4250) // "Inherits via dominance." +#endif + + + +/*\\\ INCLUDE FILES \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/ + +#include "hiir/def.h" + +#include + + + +namespace hiir +{ + + + +template +class Downsampler2xFpu +{ + + static_assert ((NC > 0), "Number of coefficient must be positive."); + +/*\\\ PUBLIC \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/ + +public: + + enum { NBR_COEFS = NC }; + + Downsampler2xFpu (); + + void set_coefs (const double coef_arr []); + + hiir_FORCEINLINE float + process_sample (const float in_ptr [2]); + void process_block (float out_ptr [], const float in_ptr [], long nbr_spl); + + hiir_FORCEINLINE void + process_sample_split (float &low, float &high, const float in_ptr [2]); + void process_block_split (float out_l_ptr [], float out_h_ptr [], const float in_ptr [], long nbr_spl); + + void clear_buffers (); + + + +/*\\\ PROTECTED \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/ + +protected: + + + +/*\\\ PRIVATE \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/ + +private: + + typedef std::array HyperGluar; + + HyperGluar _coef; + HyperGluar _x; + HyperGluar _y; + + + +/*\\\ FORBIDDEN MEMBER FUNCTIONS \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/ + +private: + + bool operator == (const Downsampler2xFpu &other); + bool operator != (const Downsampler2xFpu &other); + +}; // class Downsampler2xFpu + + + +} // namespace hiir + + + +#include "hiir/Downsampler2xFpu.hpp" + + + +#endif // hiir_Downsampler2xFpu_HEADER_INCLUDED + + + +/*\\\ EOF \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/ diff --git a/src/external/hiir/Downsampler2xFpu.hpp b/src/external/hiir/Downsampler2xFpu.hpp new file mode 100644 index 00000000..4d30b5ef --- /dev/null +++ b/src/external/hiir/Downsampler2xFpu.hpp @@ -0,0 +1,303 @@ +/***************************************************************************** + + Downsampler2xFpu.hpp + Author: Laurent de Soras, 2005 + +--- Legal stuff --- + +This program is free software. It comes without any warranty, to +the extent permitted by applicable law. You can redistribute it +and/or modify it under the terms of the Do What The Fuck You Want +To Public License, Version 2, as published by Sam Hocevar. See +http://sam.zoy.org/wtfpl/COPYING for more details. + +*Tab=3***********************************************************************/ + + + +#if defined (hiir_Downsampler2xFpu_CURRENT_CODEHEADER) + #error Recursive inclusion of Downsampler2xFpu code header. +#endif +#define hiir_Downsampler2xFpu_CURRENT_CODEHEADER + +#if ! defined (hiir_Downsampler2xFpu_CODEHEADER_INCLUDED) +#define hiir_Downsampler2xFpu_CODEHEADER_INCLUDED + + + +/*\\\ INCLUDE FILES \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/ + +#include "hiir/StageProcFpu.h" + +#include + + + +namespace hiir +{ + + + +/*\\\ PUBLIC \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/ + + + +/* +============================================================================== +Name: ctor +Throws: Nothing +============================================================================== +*/ + +template +Downsampler2xFpu ::Downsampler2xFpu () +: _coef () +, _x () +, _y () +{ + for (int i = 0; i < NBR_COEFS; ++i) + { + _coef [i] = 0; + } + clear_buffers (); +} + + + +/* +============================================================================== +Name: set_coefs +Description: + Sets filter coefficients. Generate them with the PolyphaseIir2Designer + class. + Call this function before doing any processing. +Input parameters: + - coef_arr: Array of coefficients. There should be as many coefficients as + mentioned in the class template parameter. +Throws: Nothing +============================================================================== +*/ + +template +void Downsampler2xFpu ::set_coefs (const double coef_arr []) +{ + assert (coef_arr != 0); + + for (int i = 0; i < NBR_COEFS; ++i) + { + _coef [i] = float (coef_arr [i]); + } +} + + + +/* +============================================================================== +Name: process_sample +Description: + Downsamples (x2) one pair of samples, to generate one output sample. +Input parameters: + - in_ptr: pointer on the two samples to decimate +Returns: Samplerate-reduced sample. +Throws: Nothing +============================================================================== +*/ + +template +float Downsampler2xFpu ::process_sample (const float in_ptr [2]) +{ + assert (in_ptr != 0); + + float spl_0 (in_ptr [1]); + float spl_1 (in_ptr [0]); + + #if defined (_MSC_VER) + #pragma inline_depth (255) + #endif // _MSC_VER + + StageProcFpu ::process_sample_pos ( + NBR_COEFS, + spl_0, + spl_1, + &_coef [0], + &_x [0], + &_y [0] + ); + + return 0.5f * (spl_0 + spl_1); +} + + + +/* +============================================================================== +Name: process_block +Description: + Downsamples (x2) a block of samples. + Input and output blocks may overlap, see assert() for details. +Input parameters: + - in_ptr: Input array, containing nbr_spl * 2 samples. + - nbr_spl: Number of samples to output, > 0 +Output parameters: + - out_ptr: Array for the output samples, capacity: nbr_spl samples. +Throws: Nothing +============================================================================== +*/ + +template +void Downsampler2xFpu ::process_block (float out_ptr [], const float in_ptr [], long nbr_spl) +{ + assert (in_ptr != 0); + assert (out_ptr != 0); + assert (out_ptr <= in_ptr || out_ptr >= in_ptr + nbr_spl * 2); + assert (nbr_spl > 0); + + long pos = 0; + do + { + out_ptr [pos] = process_sample (&in_ptr [pos * 2]); + ++pos; + } + while (pos < nbr_spl); +} + + + +/* +============================================================================== +Name: process_sample_split +Description: + Split (spectrum-wise) in half a pair of samples. The lower part of the + spectrum is a classic downsampling, equivalent to the output of + process_sample(). + The higher part is the complementary signal: original filter response + is flipped from left to right, becoming a high-pass filter with the same + cutoff frequency. This signal is then critically sampled (decimation by 2), + flipping the spectrum: Fs/4...Fs/2 becomes Fs/4...0. +Input parameters: + - in_ptr: pointer on the pair of input samples +Output parameters: + - low: output sample, lower part of the spectrum (downsampling) + - high: output sample, higher part of the spectrum. +Throws: Nothing +============================================================================== +*/ + +template +void Downsampler2xFpu ::process_sample_split (float &low, float &high, const float in_ptr [2]) +{ + assert (in_ptr != 0); + + float spl_0 = in_ptr [1]; + float spl_1 = in_ptr [0]; + + #if defined (_MSC_VER) + #pragma inline_depth (255) + #endif // _MSC_VER + + StageProcFpu ::process_sample_pos ( + NBR_COEFS, + spl_0, + spl_1, + &_coef [0], + &_x [0], + &_y [0] + ); + + low = (spl_0 + spl_1) * 0.5f; + high = spl_0 - low; // (spl_0 - spl_1) * 0.5f; +} + + + +/* +============================================================================== +Name: process_block_split +Description: + Split (spectrum-wise) in half a block of samples. The lower part of the + spectrum is a classic downsampling, equivalent to the output of + process_block(). + The higher part is the complementary signal: original filter response + is flipped from left to right, becoming a high-pass filter with the same + cutoff frequency. This signal is then critically sampled (decimation by 2), + flipping the spectrum: Fs/4...Fs/2 becomes Fs/4...0. + Input and output blocks may overlap, see assert() for details. +Input parameters: + - in_ptr: Input array, containing nbr_spl * 2 samples. + - nbr_spl: Number of samples for each output, > 0 +Output parameters: + - out_l_ptr: Array for the output samples, lower part of the spectrum + (downsampling). Capacity: nbr_spl samples. + - out_h_ptr: Array for the output samples, higher part of the spectrum. + Capacity: nbr_spl samples. +Throws: Nothing +============================================================================== +*/ + +template +void Downsampler2xFpu ::process_block_split (float out_l_ptr [], float out_h_ptr [], const float in_ptr [], long nbr_spl) +{ + assert (in_ptr != 0); + assert (out_l_ptr != 0); + assert (out_l_ptr <= in_ptr || out_l_ptr >= in_ptr + nbr_spl * 2); + assert (out_h_ptr != 0); + assert (out_h_ptr <= in_ptr || out_h_ptr >= in_ptr + nbr_spl * 2); + assert (out_h_ptr != out_l_ptr); + assert (nbr_spl > 0); + + long pos = 0; + do + { + process_sample_split ( + out_l_ptr [pos], + out_h_ptr [pos], + &in_ptr [pos * 2] + ); + ++pos; + } + while (pos < nbr_spl); +} + + + +/* +============================================================================== +Name: clear_buffers +Description: + Clears filter memory, as if it processed silence since an infinite amount + of time. +Throws: Nothing +============================================================================== +*/ + +template +void Downsampler2xFpu ::clear_buffers () +{ + for (int i = 0; i < NBR_COEFS; ++i) + { + _x [i] = 0; + _y [i] = 0; + } +} + + + +/*\\\ PROTECTED \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/ + + + +/*\\\ PRIVATE \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/ + + + +} // namespace hiir + + + +#endif // hiir_Downsampler2xFpu_CODEHEADER_INCLUDED + +#undef hiir_Downsampler2xFpu_CURRENT_CODEHEADER + + + +/*\\\ EOF \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/ diff --git a/src/external/hiir/Downsampler2xNeon.h b/src/external/hiir/Downsampler2xNeon.h new file mode 100644 index 00000000..28188c8e --- /dev/null +++ b/src/external/hiir/Downsampler2xNeon.h @@ -0,0 +1,126 @@ +/***************************************************************************** + + Downsampler2xNeon.h + Author: Laurent de Soras, 2016 + +Downsamples by a factor 2 the input signal, using NEON instruction set. + +This object must be aligned on a 16-byte boundary! + +If the number of coefficients is 2 or 3 modulo 4, the output is delayed from +1 sample, compared to the theoretical formula (or FPU implementation). + +Template parameters: + - NC: number of coefficients, > 0 + +--- Legal stuff --- + +This program is free software. It comes without any warranty, to +the extent permitted by applicable law. You can redistribute it +and/or modify it under the terms of the Do What The Fuck You Want +To Public License, Version 2, as published by Sam Hocevar. See +http://sam.zoy.org/wtfpl/COPYING for more details. + +*Tab=3***********************************************************************/ + + + +#pragma once +#if ! defined (hiir_Downsampler2xNeon_HEADER_INCLUDED) +#define hiir_Downsampler2xNeon_HEADER_INCLUDED + +#if defined (_MSC_VER) + #pragma warning (4 : 4250) +#endif + + + +/*\\\ INCLUDE FILES \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/ + +#include "hiir/def.h" +#include "hiir/StageDataNeon.h" + +#include + + + +namespace hiir +{ + + + +template +class Downsampler2xNeon +{ + + static_assert ((NC > 0), "Number of coefficient must be positive."); + +/*\\\ PUBLIC \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/ + +public: + + enum { NBR_COEFS = NC }; + + Downsampler2xNeon (); + Downsampler2xNeon (const Downsampler2xNeon &other) = default; + + Downsampler2xNeon & + operator = (const Downsampler2xNeon &other) = default; + + void set_coefs (const double coef_arr []); + + hiir_FORCEINLINE float + process_sample (const float in_ptr [2]); + void process_block (float out_ptr [], const float in_ptr [], long nbr_spl); + + hiir_FORCEINLINE void + process_sample_split (float &low, float &high, const float in_ptr [2]); + void process_block_split (float out_l_ptr [], float out_h_ptr [], const float in_ptr [], long nbr_spl); + + void clear_buffers (); + + + +/*\\\ PROTECTED \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/ + +protected: + + + +/*\\\ PRIVATE \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/ + +private: + + enum { STAGE_WIDTH = 4 }; + enum { NBR_STAGES = (NBR_COEFS + STAGE_WIDTH - 1) / STAGE_WIDTH }; + + typedef std::array Filter; // Stage 0 contains only input memory + + Filter _filter; // Should be the first member (thus easier to align) + + + +/*\\\ FORBIDDEN MEMBER FUNCTIONS \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/ + +private: + + bool operator == (const Downsampler2xNeon &other) const = delete; + bool operator != (const Downsampler2xNeon &other) const = delete; + +}; // class Downsampler2xNeon + + + +} // namespace hiir + + + +#include "hiir/Downsampler2xNeon.hpp" + + + +#endif // hiir_Downsampler2xNeon_HEADER_INCLUDED + + + +/*\\\ EOF \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/ diff --git a/src/external/hiir/Downsampler2xNeon.hpp b/src/external/hiir/Downsampler2xNeon.hpp new file mode 100644 index 00000000..9d0ddeaf --- /dev/null +++ b/src/external/hiir/Downsampler2xNeon.hpp @@ -0,0 +1,299 @@ +/***************************************************************************** + + Downsampler2xNeon.hpp + Author: Laurent de Soras, 2016 + +--- Legal stuff --- + +This program is free software. It comes without any warranty, to +the extent permitted by applicable law. You can redistribute it +and/or modify it under the terms of the Do What The Fuck You Want +To Public License, Version 2, as published by Sam Hocevar. See +http://sam.zoy.org/wtfpl/COPYING for more details. + +*Tab=3***********************************************************************/ + + + +#if ! defined (hiir_Downsampler2xNeon_CODEHEADER_INCLUDED) +#define hiir_Downsampler2xNeon_CODEHEADER_INCLUDED + + + +/*\\\ INCLUDE FILES \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/ + +#include "hiir/StageProcNeon.h" + +#include + +#include + + + +namespace hiir +{ + + + +/*\\\ PUBLIC \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/ + + + +/* +============================================================================== +Name: ctor +Throws: Nothing +============================================================================== +*/ + +template +Downsampler2xNeon ::Downsampler2xNeon () +: _filter () +{ + for (int i = 0; i < NBR_STAGES + 1; ++i) + { + _filter [i]._mem4 = vdupq_n_f32 (0); + } + if ((NBR_COEFS & 1) != 0) + { + const int pos = (NBR_COEFS ^ 1) & (STAGE_WIDTH - 1); + _filter [NBR_STAGES]._coef [pos] = 1; + } + + clear_buffers (); +} + + + +/* +============================================================================== +Name: set_coefs +Description: + Sets filter coefficients. Generate them with the PolyphaseIir2Designer + class. + Call this function before doing any processing. +Input parameters: + - coef_arr: Array of coefficients. There should be as many coefficients as + mentioned in the class template parameter. +Throws: Nothing +============================================================================== +*/ + +template +void Downsampler2xNeon ::set_coefs (const double coef_arr []) +{ + assert (coef_arr != 0); + + for (int i = 0; i < NBR_COEFS; ++i) + { + const int stage = (i / STAGE_WIDTH) + 1; + const int pos = (i ^ 1) & (STAGE_WIDTH - 1); + _filter [stage]._coef [pos] = float (coef_arr [i]); + } +} + + + +/* +============================================================================== +Name: process_sample +Description: + Downsamples (x2) one pair of samples, to generate one output sample. +Input parameters: + - in_ptr: pointer on the two samples to decimate +Returns: Samplerate-reduced sample. +Throws: Nothing +============================================================================== +*/ + +template +float Downsampler2xNeon ::process_sample (const float in_ptr [2]) +{ + assert (in_ptr != 0); + + // Combines two input samples and two mid-processing data + const float32x2_t spl_in = vreinterpret_f32_u8 ( + vld1_u8 (reinterpret_cast (in_ptr)) + ); + const float32x2_t spl_mid = vget_low_f32 (_filter [NBR_STAGES]._mem4); + float32x4_t y = vcombine_f32 (spl_in, spl_mid); + float32x4_t mem = _filter [0]._mem4; + + // Processes each stage + StageProcNeon ::process_sample_pos (&_filter [0], y, mem); + _filter [NBR_STAGES]._mem4 = y; + + // Averages both paths and outputs the result + const float out_0 = vgetq_lane_f32 (y, 3); + const float out_1 = vgetq_lane_f32 (y, 2); + const float out = (out_0 + out_1) * 0.5f; + + return out; +} + + + +/* +============================================================================== +Name: process_block +Description: + Downsamples (x2) a block of samples. + Input and output blocks may overlap, see assert() for details. +Input parameters: + - in_ptr: Input array, containing nbr_spl * 2 samples. + - nbr_spl: Number of samples to output, > 0 +Output parameters: + - out_ptr: Array for the output samples, capacity: nbr_spl samples. +Throws: Nothing +============================================================================== +*/ + +template +void Downsampler2xNeon ::process_block (float out_ptr [], const float in_ptr [], long nbr_spl) +{ + assert (in_ptr != 0); + assert (out_ptr != 0); + assert (out_ptr <= in_ptr || out_ptr >= in_ptr + nbr_spl * 2); + assert (nbr_spl > 0); + + long pos = 0; + do + { + out_ptr [pos] = process_sample (in_ptr + pos * 2); + ++ pos; + } + while (pos < nbr_spl); +} + + + +/* +============================================================================== +Name: process_sample_split +Description: + Split (spectrum-wise) in half a pair of samples. The lower part of the + spectrum is a classic downsampling, equivalent to the output of + process_sample(). + The higher part is the complementary signal: original filter response + is flipped from left to right, becoming a high-pass filter with the same + cutoff frequency. This signal is then critically sampled (decimation by 2), + flipping the spectrum: Fs/4...Fs/2 becomes Fs/4...0. +Input parameters: + - in_ptr: pointer on the pair of input samples +Output parameters: + - low: output sample, lower part of the spectrum (downsampling) + - high: output sample, higher part of the spectrum. +Throws: Nothing +============================================================================== +*/ + +template +void Downsampler2xNeon ::process_sample_split (float &low, float &high, const float in_ptr [2]) +{ + assert (in_ptr != 0); + + // Combines two input samples and two mid-processing data + const float32x2_t spl_in = vreinterpret_f32_u8 ( + vld1_u8 (reinterpret_cast (in_ptr)) + ); + const float32x2_t spl_mid = vget_low_f32 (_filter [NBR_STAGES]._mem4); + float32x4_t y = vcombine_f32 (spl_in, spl_mid); + float32x4_t mem = _filter [0]._mem4; + + // Processes each stage + StageProcNeon ::process_sample_pos (&_filter [0], y, mem); + _filter [NBR_STAGES]._mem4 = y; + + // Outputs the result + const float out_0 = vgetq_lane_f32 (y, 3); + const float out_1 = vgetq_lane_f32 (y, 2); + low = (out_0 + out_1) * 0.5f; + high = out_0 - low; +} + + + +/* +============================================================================== +Name: process_block_split +Description: + Split (spectrum-wise) in half a block of samples. The lower part of the + spectrum is a classic downsampling, equivalent to the output of + process_block(). + The higher part is the complementary signal: original filter response + is flipped from left to right, becoming a high-pass filter with the same + cutoff frequency. This signal is then critically sampled (decimation by 2), + flipping the spectrum: Fs/4...Fs/2 becomes Fs/4...0. + Input and output blocks may overlap, see assert() for details. +Input parameters: + - in_ptr: Input array, containing nbr_spl * 2 samples. + - nbr_spl: Number of samples for each output, > 0 +Output parameters: + - out_l_ptr: Array for the output samples, lower part of the spectrum + (downsampling). Capacity: nbr_spl samples. + - out_h_ptr: Array for the output samples, higher part of the spectrum. + Capacity: nbr_spl samples. +Throws: Nothing +============================================================================== +*/ + +template +void Downsampler2xNeon ::process_block_split (float out_l_ptr [], float out_h_ptr [], const float in_ptr [], long nbr_spl) +{ + assert (in_ptr != 0); + assert (out_l_ptr != 0); + assert (out_l_ptr <= in_ptr || out_l_ptr >= in_ptr + nbr_spl * 2); + assert (out_h_ptr != 0); + assert (out_h_ptr <= in_ptr || out_h_ptr >= in_ptr + nbr_spl * 2); + assert (out_h_ptr != out_l_ptr); + assert (nbr_spl > 0); + + long pos = 0; + do + { + process_sample_split (out_l_ptr [pos], out_h_ptr [pos], in_ptr + pos * 2); + ++ pos; + } + while (pos < nbr_spl); +} + + + +/* +============================================================================== +Name: clear_buffers +Description: + Clears filter memory, as if it processed silence since an infinite amount + of time. +Throws: Nothing +============================================================================== +*/ + +template +void Downsampler2xNeon ::clear_buffers () +{ + for (int i = 0; i < NBR_STAGES + 1; ++i) + { + _filter [i]._mem4 = vdupq_n_f32 (0); + } +} + + + +/*\\\ PROTECTED \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/ + + + +/*\\\ PRIVATE \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/ + + + +} // namespace hiir + + + +#endif // hiir_Downsampler2xNeon_CODEHEADER_INCLUDED + + + +/*\\\ EOF \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/ diff --git a/src/external/hiir/Downsampler2xSse.h b/src/external/hiir/Downsampler2xSse.h new file mode 100644 index 00000000..9aa6ff7c --- /dev/null +++ b/src/external/hiir/Downsampler2xSse.h @@ -0,0 +1,126 @@ +/***************************************************************************** + + Downsampler2xSse.h + Author: Laurent de Soras, 2005 + +Downsamples by a factor 2 the input signal, using SSE instruction set. + +This object must be aligned on a 16-byte boundary! + +If the number of coefficients is 2 or 3 modulo 4, the output is delayed from +1 sample, compared to the theoretical formula (or FPU implementation). + +Template parameters: + - NC: number of coefficients, > 0 + +--- Legal stuff --- + +This program is free software. It comes without any warranty, to +the extent permitted by applicable law. You can redistribute it +and/or modify it under the terms of the Do What The Fuck You Want +To Public License, Version 2, as published by Sam Hocevar. See +http://sam.zoy.org/wtfpl/COPYING for more details. + +*Tab=3***********************************************************************/ + + + +#if ! defined (hiir_Downsampler2xSse_HEADER_INCLUDED) +#define hiir_Downsampler2xSse_HEADER_INCLUDED + +#if defined (_MSC_VER) + #pragma once + #pragma warning (4 : 4250) // "Inherits via dominance." +#endif + + + +/*\\\ INCLUDE FILES \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/ + +#include "hiir/def.h" +#include "hiir/StageDataSse.h" + +#include + + + +namespace hiir +{ + + + +template +class Downsampler2xSse +{ + + static_assert ((NC > 0), "Number of coefficient must be positive."); + +/*\\\ PUBLIC \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/ + +public: + + enum { NBR_COEFS = NC }; + + Downsampler2xSse (); + Downsampler2xSse (const Downsampler2xSse &other) = default; + + Downsampler2xSse & + operator = (const Downsampler2xSse &other) = default; + + void set_coefs (const double coef_arr []); + + hiir_FORCEINLINE float + process_sample (const float in_ptr [2]); + void process_block (float out_ptr [], const float in_ptr [], long nbr_spl); + + hiir_FORCEINLINE void + process_sample_split (float &low, float &high, const float in_ptr [2]); + void process_block_split (float out_l_ptr [], float out_h_ptr [], const float in_ptr [], long nbr_spl); + + void clear_buffers (); + + + +/*\\\ PROTECTED \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/ + +protected: + + + +/*\\\ PRIVATE \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/ + +private: + + enum { STAGE_WIDTH = 4 }; + enum { NBR_STAGES = (NBR_COEFS + STAGE_WIDTH - 1) / STAGE_WIDTH }; + + typedef std::array Filter; // Stage 0 contains only input memory + + Filter _filter; // Should be the first member (thus easier to align) + + + +/*\\\ FORBIDDEN MEMBER FUNCTIONS \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/ + +private: + + bool operator == (const Downsampler2xSse &other) = delete; + bool operator != (const Downsampler2xSse &other) = delete; + +}; // class Downsampler2xSse + + + +} // namespace hiir + + + +#include "hiir/Downsampler2xSse.hpp" + + + +#endif // hiir_Downsampler2xSse_HEADER_INCLUDED + + + +/*\\\ EOF \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/ diff --git a/src/external/hiir/Downsampler2xSse.hpp b/src/external/hiir/Downsampler2xSse.hpp new file mode 100644 index 00000000..b05b82c0 --- /dev/null +++ b/src/external/hiir/Downsampler2xSse.hpp @@ -0,0 +1,322 @@ +/***************************************************************************** + + Downsampler2xSse.hpp + Author: Laurent de Soras, 2005 + +--- Legal stuff --- + +This program is free software. It comes without any warranty, to +the extent permitted by applicable law. You can redistribute it +and/or modify it under the terms of the Do What The Fuck You Want +To Public License, Version 2, as published by Sam Hocevar. See +http://sam.zoy.org/wtfpl/COPYING for more details. + +*Tab=3***********************************************************************/ + + + +#if defined (hiir_Downsampler2xSse_CURRENT_CODEHEADER) + #error Recursive inclusion of Downsampler2xSse code header. +#endif +#define hiir_Downsampler2xSse_CURRENT_CODEHEADER + +#if ! defined (hiir_Downsampler2xSse_CODEHEADER_INCLUDED) +#define hiir_Downsampler2xSse_CODEHEADER_INCLUDED + + + +/*\\\ INCLUDE FILES \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/ + +#include "hiir/StageProcSse.h" + +#include + +#include + + + +namespace hiir +{ + + + +/*\\\ PUBLIC \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/ + + + +/* +============================================================================== +Name: ctor +Throws: Nothing +============================================================================== +*/ + +template +Downsampler2xSse ::Downsampler2xSse () +: _filter () +{ + for (int i = 0; i < NBR_STAGES + 1; ++i) + { + _filter [i]._coef [0] = 0; + _filter [i]._coef [1] = 0; + _filter [i]._coef [2] = 0; + _filter [i]._coef [3] = 0; + } + if ((NBR_COEFS & 1) != 0) + { + const int pos = (NBR_COEFS ^ 1) & (STAGE_WIDTH - 1); + _filter [NBR_STAGES]._coef [pos] = 1; + } + + clear_buffers (); +} + + + +/* +============================================================================== +Name: set_coefs +Description: + Sets filter coefficients. Generate them with the PolyphaseIir2Designer + class. + Call this function before doing any processing. +Input parameters: + - coef_arr: Array of coefficients. There should be as many coefficients as + mentioned in the class template parameter. +Throws: Nothing +============================================================================== +*/ + +template +void Downsampler2xSse ::set_coefs (const double coef_arr []) +{ + assert (coef_arr != 0); + + for (int i = 0; i < NBR_COEFS; ++i) + { + const int stage = (i / STAGE_WIDTH) + 1; + const int pos = (i ^ 1) & (STAGE_WIDTH - 1); + _filter [stage]._coef [pos] = float (coef_arr [i]); + } +} + + + +/* +============================================================================== +Name: process_sample +Description: + Downsamples (x2) one pair of samples, to generate one output sample. +Input parameters: + - in_ptr: pointer on the two samples to decimate +Returns: Samplerate-reduced sample. +Throws: Nothing +============================================================================== +*/ + +template +float Downsampler2xSse ::process_sample (const float in_ptr [2]) +{ + assert (in_ptr != 0); + + // Combines two input samples and two mid-processing data + const __m128 spl_in = _mm_loadu_ps (in_ptr); + const __m128 spl_mid = _mm_load_ps (_filter [NBR_STAGES]._mem); + __m128 y = _mm_shuffle_ps (spl_in, spl_mid, 0x44); + + __m128 mem = _mm_load_ps (_filter [0]._mem); + + // Processes each stage + StageProcSse ::process_sample_pos (&_filter [0], y, mem); + + _mm_store_ps (_filter [NBR_STAGES]._mem, y); + + // Averages both paths and outputs the result + const __m128 dup_y = y; + y = _mm_shuffle_ps (y, y, 0x80); + y = _mm_add_ps (y, dup_y); + y = _mm_shuffle_ps (y, y, 3); + y = _mm_mul_ss (y, _mm_set_ss (0.5f)); + float result; + _mm_store_ss (&result, y); + + return (result); +} + + + +/* +============================================================================== +Name: process_block +Description: + Downsamples (x2) a block of samples. + Input and output blocks may overlap, see assert() for details. +Input parameters: + - in_ptr: Input array, containing nbr_spl * 2 samples. + - nbr_spl: Number of samples to output, > 0 +Output parameters: + - out_ptr: Array for the output samples, capacity: nbr_spl samples. +Throws: Nothing +============================================================================== +*/ + +template +void Downsampler2xSse ::process_block (float out_ptr [], const float in_ptr [], long nbr_spl) +{ + assert (in_ptr != 0); + assert (out_ptr != 0); + assert (out_ptr <= in_ptr || out_ptr >= in_ptr + nbr_spl * 2); + assert (nbr_spl > 0); + + long pos = 0; + do + { + out_ptr [pos] = process_sample (in_ptr + pos * 2); + ++ pos; + } + while (pos < nbr_spl); +} + + + +/* +============================================================================== +Name: process_sample_split +Description: + Split (spectrum-wise) in half a pair of samples. The lower part of the + spectrum is a classic downsampling, equivalent to the output of + process_sample(). + The higher part is the complementary signal: original filter response + is flipped from left to right, becoming a high-pass filter with the same + cutoff frequency. This signal is then critically sampled (decimation by 2), + flipping the spectrum: Fs/4...Fs/2 becomes Fs/4...0. +Input parameters: + - in_ptr: pointer on the pair of input samples +Output parameters: + - low: output sample, lower part of the spectrum (downsampling) + - high: output sample, higher part of the spectrum. +Throws: Nothing +============================================================================== +*/ + +template +void Downsampler2xSse ::process_sample_split (float &low, float &high, const float in_ptr [2]) +{ + assert (in_ptr != 0); + + // Combines two input samples and two mid-processing data + const __m128 spl_in = _mm_loadu_ps (in_ptr); + const __m128 spl_mid = _mm_load_ps (_filter [NBR_STAGES]._mem); + __m128 y = _mm_shuffle_ps (spl_in, spl_mid, 0x44); + + __m128 mem = _mm_load_ps (_filter [0]._mem); + + // Processes each stage + StageProcSse ::process_sample_pos (&_filter [0], y, mem); + + _mm_store_ps (_filter [NBR_STAGES]._mem, y); + + //Outputs the result + __m128 dup_y = y; + y = _mm_shuffle_ps (y, y, 0x80); + y = _mm_add_ps (y, dup_y); + y = _mm_shuffle_ps (y, y, 3); + y = _mm_mul_ss (y, _mm_set_ss (0.5f)); + _mm_store_ss (&low, y); + + dup_y = _mm_shuffle_ps (dup_y, dup_y, 3); + dup_y = _mm_sub_ps (dup_y, y); + _mm_store_ss (&high, dup_y); +} + + + +/* +============================================================================== +Name: process_block_split +Description: + Split (spectrum-wise) in half a block of samples. The lower part of the + spectrum is a classic downsampling, equivalent to the output of + process_block(). + The higher part is the complementary signal: original filter response + is flipped from left to right, becoming a high-pass filter with the same + cutoff frequency. This signal is then critically sampled (decimation by 2), + flipping the spectrum: Fs/4...Fs/2 becomes Fs/4...0. + Input and output blocks may overlap, see assert() for details. +Input parameters: + - in_ptr: Input array, containing nbr_spl * 2 samples. + - nbr_spl: Number of samples for each output, > 0 +Output parameters: + - out_l_ptr: Array for the output samples, lower part of the spectrum + (downsampling). Capacity: nbr_spl samples. + - out_h_ptr: Array for the output samples, higher part of the spectrum. + Capacity: nbr_spl samples. +Throws: Nothing +============================================================================== +*/ + +template +void Downsampler2xSse ::process_block_split (float out_l_ptr [], float out_h_ptr [], const float in_ptr [], long nbr_spl) +{ + assert (in_ptr != 0); + assert (out_l_ptr != 0); + assert (out_l_ptr <= in_ptr || out_l_ptr >= in_ptr + nbr_spl * 2); + assert (out_h_ptr != 0); + assert (out_h_ptr <= in_ptr || out_h_ptr >= in_ptr + nbr_spl * 2); + assert (out_h_ptr != out_l_ptr); + assert (nbr_spl > 0); + + long pos = 0; + do + { + process_sample_split (out_l_ptr [pos], out_h_ptr [pos], in_ptr + pos * 2); + ++ pos; + } + while (pos < nbr_spl); +} + + + +/* +============================================================================== +Name: clear_buffers +Description: + Clears filter memory, as if it processed silence since an infinite amount + of time. +Throws: Nothing +============================================================================== +*/ + +template +void Downsampler2xSse ::clear_buffers () +{ + for (int i = 0; i < NBR_STAGES + 1; ++i) + { + _filter [i]._mem [0] = 0; + _filter [i]._mem [1] = 0; + _filter [i]._mem [2] = 0; + _filter [i]._mem [3] = 0; + } +} + + + +/*\\\ PROTECTED \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/ + + + +/*\\\ PRIVATE \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/ + + + +} // namespace hiir + + + +#endif // hiir_Downsampler2xSse_CODEHEADER_INCLUDED + +#undef hiir_Downsampler2xSse_CURRENT_CODEHEADER + + + +/*\\\ EOF \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/ From a1c4dcb54804e7ae38bb68b0d3c50b8c1a126824 Mon Sep 17 00:00:00 2001 From: Paul Fd Date: Fri, 28 Feb 2020 01:18:55 +0100 Subject: [PATCH 03/93] Add a script to parse the timing logs into performance reports --- scripts/performance_report.py | 261 ++++++++++++++++++++++++++++++++++ 1 file changed, 261 insertions(+) create mode 100755 scripts/performance_report.py diff --git a/scripts/performance_report.py b/scripts/performance_report.py new file mode 100755 index 00000000..a2aa155e --- /dev/null +++ b/scripts/performance_report.py @@ -0,0 +1,261 @@ +#!/usr/bin/python3 + +import numpy as np +import pandas as pd +import os +from bokeh.io import output_file, show +from bokeh.plotting import figure +from bokeh.layouts import column, row +from bokeh.palettes import Dark2_5 as palette +from bokeh.models.widgets import Div +from bokeh.models import ColumnDataSource +import itertools +import argparse + +# Constant things +callback_log_suffix = "_callback_log.csv" +file_log_suffix = "_file_log.csv" +file_prefix_length = 14 # length of the pointer prefix + +# sfizz 0.3.0 logs +callback_log_columns = ['Dispatch', 'RenderMethod', 'Data', 'Amplitude', 'Filters', 'Panning', 'NumVoices', 'NumSamples'] +file_log_columns = ['WaitDuration', 'LoadDuration', 'FileSize', 'FileName'] + +# Helper functions +def scale_columns(dataframe, column_list, scale_factor): + """Scale the columns of a pandas Dataframe + + Arguments: + dataframe {pandas Dataframe} -- a dataframe + column_list {list of strings} -- the list of column names to scale + scale_factor {arithmetic type} -- the scaling factor + """ + for column in column_list: + dataframe[column] *= scale_factor + +def html_list(string_list): + """Returns an HTML list from a list of strings + + Arguments: + string_list {list of strings} -- the input list + + Returns: + string -- the list of string formatted as an HTML list + """ + returned_string = "
    " + for string in string_list: + returned_string += f"
  • {string}
  • " + returned_string += "
" + return returned_string + +def print_summary_to_console(title, lines): + """Prints a multi-line summary to the console + + Arguments: + title {string} -- The summary title + lines {list of strings} -- The summary lines + """ + print(title) + print('- ', end='') + print('\n- '.join(lines)) + print('\n') + +def extract_file_name_and_prefix(file_name): + """From a file name formatted as "0xAE152342334152_sfzFileName_...", extract the sfz file name and the pointer prefix + + Arguments: + file_name {string} -- The mangled sfizz log filename + + Returns: + (string, string) -- File name and file prefix + """ + file_prefix = file_name[:file_prefix_length] + + if file.endswith(file_log_suffix): + suffix_length = len(file_log_suffix) + elif file.endswith(callback_log_suffix): + suffix_length = len(callback_log_suffix) + else: + suffix_length = 0 + + sfz_file_name = file_name[file_prefix_length + 1:-suffix_length] + if sfz_file_name == '': + sfz_file_name = "Empty filename" + + return sfz_file_name, file_prefix + +def set_axis_and_legend(figure, xlabel=None, ylabel=None, hide_on_click=True): + """Generic way to set the axis labels and enable clicking on the legend to hide the plot + + Arguments: + figure {Bokeh figure} + + Keyword Arguments: + xlabel {string} -- the x label (default: {None}) + ylabel {string} -- the y label (default: {None}) + hide_on_click {bool} -- whether to hide the legend when clicking (default: {True}) + """ + if xlabel is not None: + figure.xaxis.axis_label = 'Callback index' + if ylabel is not None: + figure.yaxis.axis_label = 'Number of voices' + if hide_on_click: + figure.legend.click_policy = "hide" + +# Argument parser +parser = argparse.ArgumentParser(description="Plot performance summary and generate a detailed report on sfizz's performance") +parser.add_argument("files", nargs="+", type=str, help="The csv log files to consider") +parser.add_argument("--output", type=str, default="report.html", help="The detailed output report file name") +parser.add_argument("--title", type=str, default="sfizz's performance report", help="The report title") +parser.add_argument("-v", "--verbose", action='store_true', help="Verbose console output") +args = parser.parse_args() + +# Check that all input files are here +for file in args.files: + assert os.path.exists(file), f'Cannot find {file}' + +if args.verbose: + print(f'Input files:', args.files) + print(f'Output file:', args.output) + +output_file(args.output, args.title) + +# Dispatch files into their respective lists +file_log_list = [file for file in args.files if file.endswith(file_log_suffix)] +callback_log_list = [file for file in args.files if file.endswith(callback_log_suffix)] + +# Plot the render duration and number of voices for all callback files +fig_num_voices = figure(plot_width=600, plot_height=400, title="Number of voices") +fig_callback_duration = figure(plot_width=600, plot_height=400, title="Render method") +colors = itertools.cycle(palette) +for file_name in callback_log_list: + sfz_file_name, file_prefix = extract_file_name_and_prefix(file_name) + csv_data = pd.read_csv(file_name) + assert (csv_data.columns == callback_log_columns).all(), f"Column mismatch for {file_name}" + + color = next(colors) + fig_num_voices.line(csv_data.index, csv_data['NumVoices'], legend_label=f"{sfz_file_name} ({file_prefix[-4:]})", color=color) + fig_callback_duration.line(csv_data.index, csv_data['RenderMethod'] * 1e6, legend_label=f"{sfz_file_name} ({file_prefix[-4:]})", color=color) +set_axis_and_legend(fig_num_voices, 'Callback index', 'Number of voices') +set_axis_and_legend(fig_callback_duration, 'Callback index', 'Callback duration (µs)') + +# Callback breakdowns plots per file +callback_figures = [] +for file_name in callback_log_list: + file_prefix = file_name[:file_prefix_length] + sfz_file_name = file_name[file_prefix_length + 1:-len(callback_log_suffix)] + if sfz_file_name == '': + sfz_file_name = "Empty filename" + csv_data = pd.read_csv(file_name) + + # Scale the data and add some columns + scale_columns(csv_data, ['Dispatch', 'RenderMethod', 'Data', 'Amplitude', 'Panning', 'Filters'], 1e6) + csv_data['DataPerVoice'] = csv_data['Data'] / csv_data['NumVoices'] + csv_data['AmplitudePerVoice'] = csv_data['Amplitude'] / csv_data['NumVoices'] + csv_data['FiltersPerVoice'] = csv_data['Filters'] / csv_data['NumVoices'] + csv_data['PanningPerVoice'] = csv_data['Panning'] / csv_data['NumVoices'] + csv_data['Residual'] = (csv_data['RenderMethod'] - csv_data['Panning'] - csv_data['Filters'] - csv_data['Amplitude'] - csv_data['Data']) / csv_data['NumVoices'] + + # Prep the summary + summary_title = f"Callback statistics summary for {sfz_file_name} ({file_prefix[-4:]})" + summary_lines = [ + f"Samples per callback (avg/max): {csv_data['NumSamples'].mean():.1f}/{csv_data['NumSamples'].max()}", + f"Active voices (avg/max): {csv_data['NumVoices'].mean():.1f}/{csv_data['NumVoices'].max()}", + f"Dispatch duration (avg/max): {csv_data['Dispatch'].mean():.2f}/{csv_data['Dispatch'].max():.2f} µs", + f"Render duration (avg/max): {csv_data['RenderMethod'].mean():.2f}/{csv_data['RenderMethod'].max():.2f} µs", + f"Source data reading/generation (avg/max): {csv_data['Data'].mean():.2f}/{csv_data['Data'].max():.2f} µs", + f"Amplitude processing (avg/max): {csv_data['Amplitude'].mean():.2f}/{csv_data['Amplitude'].max():.2f} µs", + f"Panning processing (avg/max): {csv_data['Panning'].mean():.2f}/{csv_data['Panning'].max():.2f} µs", + f"Filter processing (avg/max): {csv_data['Filters'].mean():.2f}/{csv_data['Filters'].max():.2f} µs" + ] + callback_figures.append(Div(text=f"

{summary_title}

" + html_list(summary_lines), width=600)) + if args.verbose: + print_summary_to_console(summary_title, summary_lines) + + # Callback breakdown figure + stacked_column_names = ['DataPerVoice', 'AmplitudePerVoice', 'FiltersPerVoice', 'PanningPerVoice', 'Residual'] + stacked_column_legends = ['Data', 'Amplitude', 'Filters', 'Panning', 'Residual'] + source = ColumnDataSource(csv_data) + source.add(csv_data.index, 'index') + + fig_breakdown = figure(plot_width=600, plot_height=400, title=f"{sfz_file_name} - Callback breakdown") + fig_breakdown.varea_stack(stacked_column_names, x='index', source=source, legend_label=stacked_column_legends, color=palette[:5]) + set_axis_and_legend(fig_breakdown, 'Callback index', 'Aggregate duration (per voice, average, µs)') + + # Breakdown histogram figure + fig_histogram = figure(plot_width=600, plot_height=400, title=f"{sfz_file_name} - Callback breakdown histogram") + histogram_bins = np.linspace(0, csv_data['Residual'].max(), 300) + for idx, (column_name, legend_label) in enumerate(zip(stacked_column_names, stacked_column_legends)): + bins, edges = np.histogram(csv_data[column_name], bins=histogram_bins, density=True) + fig_histogram.quad(bottom=0, top=bins, left=edges[:-1], right=edges[1:], legend_label=legend_label, alpha=0.5, color=palette[idx]) + set_axis_and_legend(fig_histogram, 'Processing duration (per voice, average, µs)') + + # Add a row to the report + callback_figures.append(row(fig_breakdown, fig_histogram)) + +# File timing plots +file_figures = [] +for file_name in file_log_list: + sfz_file_name, file_prefix = extract_file_name_and_prefix(file_name) + csv_data = pd.read_csv(file_name) + assert (csv_data.columns == file_log_columns).all(), f"Column mismatch for {file_name}" + scale_columns(csv_data, ['WaitDuration', 'LoadDuration'], 1e6) + normalized_load_duration = csv_data['LoadDuration'] / csv_data['FileSize'] + + # Prep and print the summary + summary_title = f"File loading statistics summary for {sfz_file_name} ({file_prefix[-4:]})" + summary_lines = [ + f"Waiting duration (avg/max): {csv_data['WaitDuration'].mean():.2f}/{csv_data['WaitDuration'].max():.2f} µs", + f"Loading duration (avg/max): {csv_data['LoadDuration'].mean():.2f}/{csv_data['LoadDuration'].max():.2f} µs", + f"Normalized loading duration (avg/max): {normalized_load_duration.mean():.5f}/{normalized_load_duration.max():.5f} µs" + ] + file_figures.append(Div(text=f"

{summary_title}

" + html_list(summary_lines), width=600)) + if args.verbose: + print_summary_to_console(summary_title, summary_lines) + + # Split the loading duration depending on the file extension + norm_load_times = {} + load_times = {} + for idx, csv_row in csv_data.iterrows(): + file_extension = csv_row['FileName'].split('.')[-1] + if file_extension not in load_times: + load_times[file_extension] = [] + if file_extension not in norm_load_times: + norm_load_times[file_extension] = [] + norm_load_times[file_extension].append(csv_row['LoadDuration'] / csv_row['FileSize']) + load_times[file_extension].append(csv_row['LoadDuration']) + + # Waiting time histogram + fig_wait_times = figure(plot_width=400, plot_height=400, title=f"{sfz_file_name} - Wait times") + hist_wait, edges_wait = np.histogram(csv_data['WaitDuration'], bins=100, density=True) + fig_wait_times.quad(top=hist_wait, bottom=0, left=edges_wait[:-1], right=edges_wait[1:], fill_color=palette[0], alpha=0.5) + set_axis_and_legend(fig_wait_times, 'Wait time (µs)', hide_on_click=False) + + # Normalized load time histogram + colors = itertools.cycle(palette) + fig_norm_load_times = figure(plot_width=400, plot_height=400, title=f"{sfz_file_name} - Normalized load times") + for extension in load_times: + hist, edges = np.histogram(np.array(norm_load_times[extension]), bins=100, density=True) + fig_norm_load_times.quad(top=hist, bottom=0, left=edges[:-1], right=edges[1:], + fill_color=next(colors), alpha=0.5, legend_label=extension) + set_axis_and_legend(fig_norm_load_times, 'Load time per sample (µs)') + + # Load time histogram + colors = itertools.cycle(palette) + fig_load_times = figure(plot_width=400, plot_height=400, title=f"{sfz_file_name} - Load times") + for extension in load_times: + hist, edges = np.histogram(np.array(load_times[extension]), bins=100, density=True) + fig_load_times.quad(top=hist, bottom=0, left=edges[:-1], right=edges[1:], + fill_color=next(colors), alpha=0.5, legend_label=extension) + set_axis_and_legend(fig_norm_load_times, 'Load time (µs)') + + # Add a row to the report + file_figures.append(row(fig_wait_times, fig_load_times, fig_norm_load_times)) + +# Show the output +show(column( + Div(text=f"

{args.title}

Input files: {html_list(args.files)}"), + row(fig_num_voices, fig_callback_duration), + *callback_figures, + *file_figures +)) From 954997f1cdaccfa03e2c72fcdb85eb05e3bca5bb Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Fri, 14 Feb 2020 03:28:09 +0100 Subject: [PATCH 04/93] Enable Windows builds to use math.h constants --- src/CMakeLists.txt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 4bfa3400..96a47e16 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -33,6 +33,9 @@ target_include_directories (sfizz_static PUBLIC external) target_link_libraries (sfizz_static PUBLIC absl::strings absl::span) target_link_libraries (sfizz_static PRIVATE sfizz_parser absl::flat_hash_map Threads::Threads sfizz-sndfile sfizz-pugixml) set_target_properties (sfizz_static PROPERTIES OUTPUT_NAME sfizz PUBLIC_HEADER "sfizz.h;sfizz.hpp") +if (WIN32) + target_compile_definitions (sfizz_static PRIVATE _USE_MATH_DEFINES) +endif() if (NOT MSVC) install (TARGETS sfizz_static @@ -61,6 +64,9 @@ if (SFIZZ_SHARED) target_include_directories (sfizz_shared PRIVATE .) target_include_directories (sfizz_shared PRIVATE external) target_link_libraries (sfizz_shared PRIVATE absl::strings absl::span sfizz_parser absl::flat_hash_map Threads::Threads sfizz-sndfile sfizz-pugixml) + if (WIN32) + target_compile_definitions (sfizz_shared PRIVATE _USE_MATH_DEFINES) + endif() target_compile_definitions(sfizz_shared PRIVATE SFIZZ_EXPORT_SYMBOLS) set_target_properties (sfizz_shared PROPERTIES OUTPUT_NAME sfizz PUBLIC_HEADER "sfizz.h;sfizz.hpp") set_property (TARGET sfizz_shared PROPERTY SOVERSION ${PROJECT_VERSION_MAJOR}) From e47bcc909eb7b3128ab5ff89c041a624fd830b09 Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Sun, 1 Mar 2020 09:05:24 +0100 Subject: [PATCH 05/93] clang-format: do not sort includes [ci skip] --- .clang-format | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.clang-format b/.clang-format index 73897e9f..81339e1c 100644 --- a/.clang-format +++ b/.clang-format @@ -1,4 +1,5 @@ ---- +--- BasedOnStyle: WebKit +SortIncludes: false ... From d2e44ccf123665f076b983e1c09655d2867b8b74 Mon Sep 17 00:00:00 2001 From: Paul Fd Date: Sun, 1 Mar 2020 20:52:05 +0100 Subject: [PATCH 06/93] lv2 ttl patch for lv2lint --- cmake/LV2Config.cmake | 2 +- lv2/sfizz.ttl.in | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/cmake/LV2Config.cmake b/cmake/LV2Config.cmake index ad6d351d..e8662054 100644 --- a/cmake/LV2Config.cmake +++ b/cmake/LV2Config.cmake @@ -7,7 +7,7 @@ set (LV2PLUGIN_COMMENT "SFZ sampler") set (LV2PLUGIN_URI "http://sfztools.github.io/sfizz") set (LV2PLUGIN_REPOSITORY "https://github.com/sfztools/sfizz") set (LV2PLUGIN_AUTHOR "Paul Ferrand") -set (LV2PLUGIN_EMAIL "paul at ferrand dot cc") +set (LV2PLUGIN_EMAIL "paul@ferrand.cc") if (SFIZZ_USE_VCPKG) set (LV2PLUGIN_SPDX_LICENSE_ID "LGPL-3.0-only") else() diff --git a/lv2/sfizz.ttl.in b/lv2/sfizz.ttl.in index 650a4492..396ac5f1 100644 --- a/lv2/sfizz.ttl.in +++ b/lv2/sfizz.ttl.in @@ -5,6 +5,7 @@ @prefix lv2: . @prefix midi: . @prefix opts: . +@prefix param: . @prefix patch: . @prefix pg: . @prefix pprop: . @@ -57,7 +58,7 @@ midnam:update a lv2:Feature . doap:maintainer [ foaf:name "@LV2PLUGIN_AUTHOR@" ; foaf:homepage <@LV2PLUGIN_URI@> ; - foaf:email "@LV2PLUGIN_EMAIL@"; + foaf:mbox ; ] ; rdfs:comment "@LV2PLUGIN_COMMENT@", "Campionatore SFZ"@it ; @@ -72,6 +73,9 @@ midnam:update a lv2:Feature . lv2:optionalFeature midnam:update ; lv2:extensionData midnam:interface ; + opts:supportedOption param:sampleRate ; + opts:supportedOption bufsize:maxBlockLength, bufsize:nominalBlockLength ; + patch:writable <@LV2PLUGIN_URI@:sfzfile> ; lv2:port [ From 259e201fac619ea3d818543fc995ed32eab16df9 Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Mon, 2 Mar 2020 14:07:06 +0100 Subject: [PATCH 07/93] Add the multiplyAdd methods for AudioSpan --- src/sfizz/AudioSpan.h | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/src/sfizz/AudioSpan.h b/src/sfizz/AudioSpan.h index b4cb4e75..312c1e1b 100644 --- a/src/sfizz/AudioSpan.h +++ b/src/sfizz/AudioSpan.h @@ -11,6 +11,7 @@ #include "Debug.h" #include "LeakDetector.h" #include "SIMDHelpers.h" +#include "absl/types/span.h" #include #include #include @@ -306,6 +307,43 @@ public: } } + /** + * @brief Add another AudioSpan with a compatible number of channels to the current + * AudioSpan, applying an elementwise gain to the operand. + * + * @param other the other AudioSpan + * @param gain the gain to apply + */ + template > + void multiplyAdd(AudioSpan& other, absl::Span gain) + { + static_assert(!std::is_const::value, "Can't allow mutating operations on const AudioSpans"); + ASSERT(other.getNumChannels() == numChannels); + ASSERT(gain.size() == numFrames); + if (other.getNumChannels() == numChannels) { + for (size_t i = 0; i < numChannels; ++i) + sfz::multiplyAdd(gain, other.getConstSpan(i), getSpan(i)); + } + } + + /** + * @brief Add another AudioSpan with a compatible number of channels to the current + * AudioSpan, applying a fixed gain to the operand. + * + * @param other the other AudioSpan + * @param gain the gain to apply + */ + template > + void multiplyAdd(AudioSpan& other, const Type gain) + { + static_assert(!std::is_const::value, "Can't allow mutating operations on const AudioSpans"); + ASSERT(other.getNumChannels() == numChannels); + if (other.getNumChannels() == numChannels) { + for (size_t i = 0; i < numChannels; ++i) + sfz::multiplyAdd(gain, other.getConstSpan(i), getSpan(i)); + } + } + /** * @brief Copy the elements of another AudioSpan with a compatible number of channels * to the current AudioSpan. From 42343c88a1bab65b6d66e7ecdf4ba78abb15d3f7 Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Mon, 2 Mar 2020 16:29:47 +0100 Subject: [PATCH 08/93] Add tests for MultiplyAdd --- tests/SIMDHelpersT.cpp | 55 ++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 53 insertions(+), 2 deletions(-) diff --git a/tests/SIMDHelpersT.cpp b/tests/SIMDHelpersT.cpp index 51f9426c..01c087ec 100644 --- a/tests/SIMDHelpersT.cpp +++ b/tests/SIMDHelpersT.cpp @@ -606,6 +606,57 @@ TEST_CASE("[Helpers] Add (SIMD vs scalar)") REQUIRE(approxEqual(outputScalar, outputSIMD)); } +TEST_CASE("[Helpers] MultiplyAdd (SIMD)") +{ + std::array gain { 0.0f, 0.1f, 0.2f, 0.3f, 0.4f }; + std::array input { 1.0f, 2.0f, 3.0f, 4.0f, 5.0f }; + std::array output { 5.0f, 4.0f, 3.0f, 2.0f, 1.0f }; + std::array expected { 5.0f, 4.2f, 3.6f, 3.2f, 3.0f }; + sfz::multiplyAdd(gain, input, absl::MakeSpan(output)); + REQUIRE(output == expected); +} + +TEST_CASE("[Helpers] MultiplyAdd (SIMD vs scalar)") +{ + std::vector gain(bigBufferSize); + std::vector input(bigBufferSize); + std::vector outputScalar(bigBufferSize); + std::vector outputSIMD(bigBufferSize); + absl::c_iota(gain, 0.0f); + absl::c_iota(input, 0.0f); + absl::c_iota(outputScalar, 0.0f); + absl::c_iota(outputSIMD, 0.0f); + + sfz::multiplyAdd(gain, input, absl::MakeSpan(outputScalar)); + sfz::multiplyAdd(gain, input, absl::MakeSpan(outputSIMD)); + REQUIRE(approxEqual(outputScalar, outputSIMD)); +} + +TEST_CASE("[Helpers] MultiplyAdd fixed gain (SIMD)") +{ + float gain = 0.3f; + std::array input { 1.0f, 2.0f, 3.0f, 4.0f, 5.0f }; + std::array output { 5.0f, 4.0f, 3.0f, 2.0f, 1.0f }; + std::array expected { 5.3f, 4.6f, 3.9f, 3.2f, 2.5f }; + sfz::multiplyAdd(gain, input, absl::MakeSpan(output)); + REQUIRE(output == expected); +} + +TEST_CASE("[Helpers] MultiplyAdd fixed gain (SIMD vs scalar)") +{ + float gain = 0.3f; + std::vector input(bigBufferSize); + std::vector outputScalar(bigBufferSize); + std::vector outputSIMD(bigBufferSize); + absl::c_iota(input, 0.0f); + absl::c_iota(outputScalar, 0.0f); + absl::c_iota(outputSIMD, 0.0f); + + sfz::multiplyAdd(gain, input, absl::MakeSpan(outputScalar)); + sfz::multiplyAdd(gain, input, absl::MakeSpan(outputSIMD)); + REQUIRE(approxEqual(outputScalar, outputSIMD)); +} + TEST_CASE("[Helpers] Subtract") { std::array input { 1.0f, 2.0f, 3.0f, 4.0f, 5.0f }; @@ -717,7 +768,7 @@ TEST_CASE("[Helpers] Mean Squared (SIMD vs scalar)") REQUIRE(sfz::meanSquared(input) == sfz::meanSquared(input)); } -TEST_CASE("[Helpers] Cumulative sum ") +TEST_CASE("[Helpers] Cumulative sum") { std::array input { 1.1f, 1.2f, 1.3f, 1.4f, 1.5f, 1.6f }; // 1.1 2.3 3.6 5.0f 6.5 8.1 std::array output; @@ -737,7 +788,7 @@ TEST_CASE("[Helpers] Cumulative sum (SIMD vs Scalar)") REQUIRE(approxEqual(outputScalar, outputSIMD)); } -TEST_CASE("[Helpers] Diff ") +TEST_CASE("[Helpers] Diff") { std::array input { 1.1f, 2.3f, 3.6f, 5.0f, 6.5f, 8.1f }; std::array output; From c43a17c432ecbaf35501638bd268db4cd5535e2d Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Wed, 4 Mar 2020 20:02:24 +0100 Subject: [PATCH 09/93] Add the C++ wrapper to the static library --- src/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 96a47e16..57c71312 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -27,7 +27,7 @@ target_link_libraries (sfizz_parser PUBLIC absl::strings) # Sfizz static library add_library(sfizz_static STATIC) -target_sources(sfizz_static PRIVATE ${SFIZZ_SOURCES} sfizz/sfizz_wrapper.cpp) +target_sources(sfizz_static PRIVATE ${SFIZZ_SOURCES} sfizz/sfizz_wrapper.cpp sfizz/sfizz.cpp) target_include_directories (sfizz_static PUBLIC .) target_include_directories (sfizz_static PUBLIC external) target_link_libraries (sfizz_static PUBLIC absl::strings absl::span) From 507188fc88a0f090b8c82e6f1e0fb6bb072aa312 Mon Sep 17 00:00:00 2001 From: Paul Fd Date: Thu, 5 Mar 2020 12:19:37 +0100 Subject: [PATCH 10/93] Corrected a couple mistakes in legends --- scripts/performance_report.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/performance_report.py b/scripts/performance_report.py index a2aa155e..8bdef3e0 100755 --- a/scripts/performance_report.py +++ b/scripts/performance_report.py @@ -96,9 +96,9 @@ def set_axis_and_legend(figure, xlabel=None, ylabel=None, hide_on_click=True): hide_on_click {bool} -- whether to hide the legend when clicking (default: {True}) """ if xlabel is not None: - figure.xaxis.axis_label = 'Callback index' + figure.xaxis.axis_label = xlabel if ylabel is not None: - figure.yaxis.axis_label = 'Number of voices' + figure.yaxis.axis_label = ylabel if hide_on_click: figure.legend.click_policy = "hide" @@ -247,7 +247,7 @@ for file_name in file_log_list: hist, edges = np.histogram(np.array(load_times[extension]), bins=100, density=True) fig_load_times.quad(top=hist, bottom=0, left=edges[:-1], right=edges[1:], fill_color=next(colors), alpha=0.5, legend_label=extension) - set_axis_and_legend(fig_norm_load_times, 'Load time (µs)') + set_axis_and_legend(fig_load_times, 'Load time (µs)') # Add a row to the report file_figures.append(row(fig_wait_times, fig_load_times, fig_norm_load_times)) From a393ff66159e6668e5b06da43569aa50083aac84 Mon Sep 17 00:00:00 2001 From: Paul Fd Date: Thu, 5 Mar 2020 12:53:10 +0100 Subject: [PATCH 11/93] Changed the way the parser handles multiple parameters in opcodes (idea from @jpcima ;) --- src/sfizz/Opcode.cpp | 36 +---- src/sfizz/Opcode.h | 10 +- src/sfizz/Region.cpp | 336 ++++++++++++++++++++----------------------- src/sfizz/Synth.cpp | 19 ++- tests/OpcodeT.cpp | 43 +----- 5 files changed, 178 insertions(+), 266 deletions(-) diff --git a/src/sfizz/Opcode.cpp b/src/sfizz/Opcode.cpp index 1ee0f1c9..2aeba276 100644 --- a/src/sfizz/Opcode.cpp +++ b/src/sfizz/Opcode.cpp @@ -24,11 +24,11 @@ sfz::Opcode::Opcode(absl::string_view inputOpcode, absl::string_view inputValue) nextCharIndex = opcode.find_first_not_of("1234567890", nextNumIndex); uint32_t returnedValue; - hasBackParameter = (nextCharIndex == opcode.npos); - const auto numDigits = hasBackParameter ? opcode.npos : nextCharIndex - nextNumIndex; + const auto numDigits = (nextCharIndex == opcode.npos) ? opcode.npos : nextCharIndex - nextNumIndex; if (absl::SimpleAtoi(opcode.substr(nextNumIndex, numDigits), &returnedValue)) { // ASSERT(returnedValue < std::numeric_limits::max()); - parameterPositions.push_back(parameterPosition); + // parameterPositions.push_back(parameterPosition); + lettersOnlyHash = hash("&", lettersOnlyHash); parameters.push_back(returnedValue); } @@ -38,33 +38,3 @@ sfz::Opcode::Opcode(absl::string_view inputOpcode, absl::string_view inputValue) if (nextCharIndex != opcode.npos) lettersOnlyHash = hash(opcode.substr(nextCharIndex), lettersOnlyHash); } - -absl::optional sfz::Opcode::backParameter() const noexcept -{ - if (hasBackParameter && !parameters.empty()) - return parameters.back(); - - return {}; -} - -absl::optional sfz::Opcode::firstParameter() const noexcept -{ - if (!hasBackParameter && !parameters.empty()) - return parameters.front(); - - if (hasBackParameter && parameters.size() > 1) - return parameters.front(); - - return {}; -} - -absl::optional sfz::Opcode::middleParameter() const noexcept -{ - if (!hasBackParameter && parameters.size() > 1) - return parameters[1]; - - if (hasBackParameter && parameters.size() > 2) - return parameters[1]; - - return {}; -} diff --git a/src/sfizz/Opcode.h b/src/sfizz/Opcode.h index 9605a8ac..db843ff0 100644 --- a/src/sfizz/Opcode.h +++ b/src/sfizz/Opcode.h @@ -26,17 +26,12 @@ namespace sfz { */ struct Opcode { Opcode() = delete; - absl::optional backParameter() const noexcept; - absl::optional firstParameter() const noexcept; - absl::optional middleParameter() const noexcept; Opcode(absl::string_view inputOpcode, absl::string_view inputValue); absl::string_view opcode {}; absl::string_view value {}; uint64_t lettersOnlyHash { Fnv1aBasis }; // This is to handle the integer parameters of some opcodes std::vector parameters; - std::vector parameterPositions; - bool hasBackParameter { false }; LEAK_DETECTOR(Opcode); }; @@ -192,9 +187,8 @@ template inline void setCCPairFromOpcode(const Opcode& opcode, absl::optional& target, const Range& validRange) { auto value = readOpcode(opcode.value, validRange); - const auto backParameter = opcode.backParameter(); - if (value && backParameter && Default::ccNumberRange.containsWithEnd(*backParameter)) - target = std::make_pair(*backParameter, *value); + if (value && Default::ccNumberRange.containsWithEnd(opcode.parameters.back())) + target = std::make_pair(opcode.parameters.back(), *value); else target = {}; } diff --git a/src/sfizz/Region.cpp b/src/sfizz/Region.cpp index 507cd6e2..e05708a0 100644 --- a/src/sfizz/Region.cpp +++ b/src/sfizz/Region.cpp @@ -32,13 +32,6 @@ bool extendIfNecessary(std::vector& vec, unsigned size, unsigned defaultCapac bool sfz::Region::parseOpcode(const Opcode& opcode) { - const auto backParameter = opcode.backParameter(); - // Check that the parameter is well formed - if (backParameter && !sfz::Default::ccNumberRange.containsWithEnd(*backParameter)) { - DBG("Wrong parameter value (" << std::to_string(*backParameter) << ") for opcode " << opcode.opcode); - return false; - } - switch (opcode.lettersOnlyHash) { // Sound source: sample playback case hash("sample"): @@ -73,7 +66,7 @@ bool sfz::Region::parseOpcode(const Opcode& opcode) case hash("count"): setValueFromOpcode(opcode, sampleCount, Default::sampleCountRange); break; - case hash("loopmode"): + case hash("loopmode"): [[fallthrough]]; case hash("loop_mode"): switch (hash(opcode.value)) { case hash("no_loop"): @@ -92,21 +85,21 @@ bool sfz::Region::parseOpcode(const Opcode& opcode) DBG("Unkown loop mode:" << std::string(opcode.value)); } break; - case hash("loopend"): + case hash("loopend"): [[fallthrough]]; case hash("loop_end"): setRangeEndFromOpcode(opcode, loopRange, Default::loopRange); break; - case hash("loopstart"): + case hash("loopstart"): [[fallthrough]]; case hash("loop_start"): setRangeStartFromOpcode(opcode, loopRange, Default::loopRange); break; // Instrument settings: voice lifecycle - case hash("group"): + case hash("group"): [[fallthrough]]; case hash("polyphony_group"): setValueFromOpcode(opcode, group, Default::groupRange); break; - case hash("offby"): + case hash("offby"): [[fallthrough]]; case hash("off_by"): setValueFromOpcode(opcode, offBy, Default::groupRange); break; @@ -150,14 +143,11 @@ bool sfz::Region::parseOpcode(const Opcode& opcode) case hash("hibend"): setRangeEndFromOpcode(opcode, bendRange, Default::bendRange); break; - case hash("locc"): - if (backParameter) { - setRangeStartFromOpcode(opcode, ccConditions[*backParameter], Default::ccValueRange); - } + case hash("locc&"): + setRangeStartFromOpcode(opcode, ccConditions[opcode.parameters.back()], Default::ccValueRange); break; - case hash("hicc"): - if (backParameter) - setRangeEndFromOpcode(opcode, ccConditions[*backParameter], Default::ccValueRange); + case hash("hicc&"): + setRangeEndFromOpcode(opcode, ccConditions[opcode.parameters.back()], Default::ccValueRange); break; case hash("sw_lokey"): setRangeStartFromOpcode(opcode, keyswitchRange, Default::keyRange); @@ -247,49 +237,47 @@ bool sfz::Region::parseOpcode(const Opcode& opcode) DBG("Unknown trigger mode: " << std::string(opcode.value)); } break; - case hash("on_locc"): - case hash("start_locc"): - if (backParameter) - setRangeStartFromOpcode(opcode, ccTriggers[*backParameter], Default::ccTriggerValueRange); + case hash("on_locc&"): [[fallthrough]]; + case hash("start_locc&"): + setRangeStartFromOpcode(opcode, ccTriggers[opcode.parameters.back()], Default::ccTriggerValueRange); break; - case hash("on_hicc"): - case hash("start_hicc"): - if (backParameter) - setRangeEndFromOpcode(opcode, ccTriggers[*backParameter], Default::ccTriggerValueRange); + case hash("on_hicc&"): [[fallthrough]]; + case hash("start_hicc&"): + setRangeEndFromOpcode(opcode, ccTriggers[opcode.parameters.back()], Default::ccTriggerValueRange); break; // Performance parameters: amplifier case hash("volume"): setValueFromOpcode(opcode, volume, Default::volumeRange); break; - case hash("gain_cc"): - case hash("gain_oncc"): - case hash("volume_oncc"): + case hash("gain_cc&"): [[fallthrough]]; + case hash("gain_oncc&"): [[fallthrough]]; + case hash("volume_oncc&"): setCCPairFromOpcode(opcode, volumeCC, Default::volumeCCRange); break; case hash("amplitude"): setValueFromOpcode(opcode, amplitude, Default::amplitudeRange); break; - case hash("amplitude_cc"): - case hash("amplitude_oncc"): + case hash("amplitude_cc&"): [[fallthrough]]; + case hash("amplitude_oncc&"): setCCPairFromOpcode(opcode, amplitudeCC, Default::amplitudeRange); break; case hash("pan"): setValueFromOpcode(opcode, pan, Default::panRange); break; - case hash("pan_oncc"): + case hash("pan_oncc&"): setCCPairFromOpcode(opcode, panCC, Default::panCCRange); break; case hash("position"): setValueFromOpcode(opcode, position, Default::positionRange); break; - case hash("position_oncc"): + case hash("position_oncc&"): setCCPairFromOpcode(opcode, positionCC, Default::positionCCRange); break; case hash("width"): setValueFromOpcode(opcode, width, Default::widthRange); break; - case hash("width_oncc"): + case hash("width_oncc&"): setCCPairFromOpcode(opcode, widthCC, Default::widthCCRange); break; case hash("amp_keycenter"): @@ -305,11 +293,11 @@ bool sfz::Region::parseOpcode(const Opcode& opcode) setValueFromOpcode(opcode, ampRandom, Default::ampRandomRange); volumeDistribution.param(std::uniform_real_distribution::param_type(0, ampRandom)); break; - case hash("amp_velcurve_"): + case hash("amp_velcurve_&"): { auto value = readOpcode(opcode.value, Default::ampVelcurveRange); if (value) - velocityPoints.emplace_back(*backParameter, *value); + velocityPoints.emplace_back(opcode.parameters.back(), *value); } break; case hash("xfin_lokey"): @@ -360,25 +348,17 @@ bool sfz::Region::parseOpcode(const Opcode& opcode) DBG("Unknown crossfade power curve: " << std::string(opcode.value)); } break; - case hash("xfin_locc"): - if (backParameter) { - setRangeStartFromOpcode(opcode, crossfadeCCInRange[*backParameter], Default::ccValueRange); - } + case hash("xfin_locc&"): + setRangeStartFromOpcode(opcode, crossfadeCCInRange[opcode.parameters.back()], Default::ccValueRange); break; - case hash("xfin_hicc"): - if (backParameter) { - setRangeEndFromOpcode(opcode, crossfadeCCInRange[*backParameter], Default::ccValueRange); - } + case hash("xfin_hicc&"): + setRangeEndFromOpcode(opcode, crossfadeCCInRange[opcode.parameters.back()], Default::ccValueRange); break; - case hash("xfout_locc"): - if (backParameter) { - setRangeStartFromOpcode(opcode, crossfadeCCOutRange[*backParameter], Default::ccValueRange); - } + case hash("xfout_locc&"): + setRangeStartFromOpcode(opcode, crossfadeCCOutRange[opcode.parameters.back()], Default::ccValueRange); break; - case hash("xfout_hicc"): - if (backParameter) { - setRangeEndFromOpcode(opcode, crossfadeCCOutRange[*backParameter], Default::ccValueRange); - } + case hash("xfout_hicc&"): + setRangeEndFromOpcode(opcode, crossfadeCCOutRange[opcode.parameters.back()], Default::ccValueRange); break; case hash("xf_cccurve"): switch (hash(opcode.value)) { @@ -397,120 +377,124 @@ bool sfz::Region::parseOpcode(const Opcode& opcode) break; // Performance parameters: filters - case hash("cutoff"): + case hash("cutoff"): [[fallthrough]]; + case hash("cutoff&"): { - const auto filterIndex { backParameter.value_or(1) - 1 }; + const auto filterIndex = opcode.parameters.empty() ? 0 : (opcode.parameters.back() - 1); if (!extendIfNecessary(filters, filterIndex + 1, Default::numFilters)) return false; setValueFromOpcode(opcode, filters[filterIndex].cutoff, Default::filterCutoffRange); } break; - case hash("resonance"): + case hash("resonance"): [[fallthrough]]; + case hash("resonance&"): { - const auto filterIndex { backParameter.value_or(1) - 1 }; + const auto filterIndex = opcode.parameters.empty() ? 0 : (opcode.parameters.back() - 1); if (!extendIfNecessary(filters, filterIndex + 1, Default::numFilters)) return false; setValueFromOpcode(opcode, filters[filterIndex].resonance, Default::filterResonanceRange); } break; - case hash("cutoff_oncc"): - case hash("cutoff_cc"): + case hash("cutoff_oncc&"): [[fallthrough]]; + case hash("cutoff_cc&"): [[fallthrough]]; + case hash("cutoff&_oncc&"): [[fallthrough]]; + case hash("cutoff&_cc&"): { - if (!backParameter) - return false; - - const auto filterIndex { opcode.firstParameter().value_or(1) - 1 }; + const auto filterIndex = opcode.parameters.size() == 1 ? 0 : (opcode.parameters.front() - 1); if (!extendIfNecessary(filters, filterIndex + 1, Default::numFilters)) return false; setValueFromOpcode( opcode, - filters[filterIndex].cutoffCC[*backParameter], + filters[filterIndex].cutoffCC[opcode.parameters.back()], Default::filterCutoffModRange ); } break; - case hash("resonance_oncc"): - case hash("resonance_cc"): + case hash("resonance&_oncc&"): [[fallthrough]]; + case hash("resonance&_cc&"): [[fallthrough]]; + case hash("resonance_oncc&"): [[fallthrough]]; + case hash("resonance_cc&"): { - if (!backParameter) - return false; - - const auto filterIndex { opcode.firstParameter().value_or(1) - 1 }; + const auto filterIndex = opcode.parameters.size() == 1 ? 0 : (opcode.parameters.front() - 1); if (!extendIfNecessary(filters, filterIndex + 1, Default::numFilters)) return false; setValueFromOpcode( opcode, - filters[filterIndex].resonanceCC[*backParameter], + filters[filterIndex].resonanceCC[opcode.parameters.back()], Default::filterResonanceModRange ); } break; - case hash("fil_keytrack"): + case hash("fil_keytrack"): [[fallthrough]]; + case hash("fil&_keytrack"): { - const auto filterIndex { opcode.firstParameter().value_or(1) - 1 }; + const auto filterIndex = opcode.parameters.empty() ? 0 : (opcode.parameters.front() - 1); if (!extendIfNecessary(filters, filterIndex + 1, Default::numFilters)) return false; setValueFromOpcode(opcode, filters[filterIndex].keytrack, Default::filterKeytrackRange); } break; - case hash("fil_keycenter"): + case hash("fil_keycenter"): [[fallthrough]]; + case hash("fil&_keycenter"): { - const auto filterIndex { opcode.firstParameter().value_or(1) - 1 }; + const auto filterIndex = opcode.parameters.empty() ? 0 : (opcode.parameters.front() - 1); if (!extendIfNecessary(filters, filterIndex + 1, Default::numFilters)) return false; setValueFromOpcode(opcode, filters[filterIndex].keycenter, Default::keyRange); } break; - case hash("fil_veltrack"): + case hash("fil_veltrack"): [[fallthrough]]; + case hash("fil&_veltrack"): { - const auto filterIndex { opcode.firstParameter().value_or(1) - 1 }; + const auto filterIndex = opcode.parameters.empty() ? 0 : (opcode.parameters.front() - 1); if (!extendIfNecessary(filters, filterIndex + 1, Default::numFilters)) return false; setValueFromOpcode(opcode, filters[filterIndex].veltrack, Default::filterVeltrackRange); } break; - case hash("fil_random"): + case hash("fil_random"): [[fallthrough]]; + case hash("fil&_random"): { - const auto filterIndex { opcode.firstParameter().value_or(1) - 1 }; + const auto filterIndex = opcode.parameters.empty() ? 0 : (opcode.parameters.front() - 1); if (!extendIfNecessary(filters, filterIndex + 1, Default::numFilters)) return false; setValueFromOpcode(opcode, filters[filterIndex].random, Default::filterRandomRange); } break; - case hash("fil_gain"): + case hash("fil_gain"): [[fallthrough]]; + case hash("fil&_gain"): { - const auto filterIndex { opcode.firstParameter().value_or(1) - 1 }; + const auto filterIndex = opcode.parameters.empty() ? 0 : (opcode.parameters.front() - 1); if (!extendIfNecessary(filters, filterIndex + 1, Default::numFilters)) return false; setValueFromOpcode(opcode, filters[filterIndex].gain, Default::filterGainRange); } break; - case hash("fil_gaincc"): + case hash("fil_gaincc&"): [[fallthrough]]; + case hash("fil&_gaincc&"): { - if (!backParameter) - return false; - - const auto filterIndex { opcode.firstParameter().value_or(1) - 1 }; + const auto filterIndex = opcode.parameters.size() == 1 ? 0 : (opcode.parameters.front() - 1); if (!extendIfNecessary(filters, filterIndex + 1, Default::numFilters)) return false; setValueFromOpcode( opcode, - filters[filterIndex].gainCC[*backParameter], + filters[filterIndex].gainCC[opcode.parameters.back()], Default::filterGainModRange ); } break; - case hash("fil_type"): + case hash("fil_type"): [[fallthrough]]; + case hash("fil&_type"): { - const auto filterIndex { opcode.firstParameter().value_or(1) - 1 }; + const auto filterIndex = opcode.parameters.empty() ? 0 : (opcode.parameters.front() - 1); if (!extendIfNecessary(filters, filterIndex + 1, Default::numFilters)) return false; @@ -545,104 +529,96 @@ bool sfz::Region::parseOpcode(const Opcode& opcode) break; // Performance parameters: EQ - case hash("eq_bw"): + case hash("eq&_bw"): { - const auto eqNumber = opcode.firstParameter(); - if (!eqNumber || *eqNumber == 0) + const auto eqNumber = opcode.parameters.front(); + if (eqNumber == 0) return false; - if (!extendIfNecessary(equalizers, *eqNumber, Default::numEQs)) + if (!extendIfNecessary(equalizers, eqNumber, Default::numEQs)) return false; - setValueFromOpcode(opcode, equalizers[*eqNumber - 1].bandwidth, Default::eqBandwidthRange); + setValueFromOpcode(opcode, equalizers[eqNumber - 1].bandwidth, Default::eqBandwidthRange); } break; - case hash("eq_bw_oncc"): [[fallthrough]]; - case hash("eq_bwcc"): + case hash("eq&_bw_oncc&"): [[fallthrough]]; + case hash("eq&_bwcc&"): { - const auto eqNumber = opcode.firstParameter(); - if (!eqNumber || *eqNumber == 0) + const auto eqNumber = opcode.parameters.front(); + if (eqNumber == 0) return false; - if (!backParameter) - return false; - if (!extendIfNecessary(equalizers, *eqNumber, Default::numEQs)) + if (!extendIfNecessary(equalizers, eqNumber, Default::numEQs)) return false; - setValueFromOpcode(opcode, equalizers[*eqNumber - 1].bandwidthCC[*backParameter], Default::eqBandwidthModRange); + setValueFromOpcode(opcode, equalizers[eqNumber - 1].bandwidthCC[opcode.parameters.back()], Default::eqBandwidthModRange); } break; - case hash("eq_freq"): + case hash("eq&_freq"): { - const auto eqNumber = opcode.firstParameter(); - if (!eqNumber || *eqNumber == 0) + const auto eqNumber = opcode.parameters.front(); + if (eqNumber == 0) return false; - if (!extendIfNecessary(equalizers, *eqNumber, Default::numEQs)) + if (!extendIfNecessary(equalizers, eqNumber, Default::numEQs)) return false; - setValueFromOpcode(opcode, equalizers[*eqNumber - 1].frequency, Default::eqFrequencyRange); + setValueFromOpcode(opcode, equalizers[eqNumber - 1].frequency, Default::eqFrequencyRange); } break; - case hash("eq_freq_oncc"): [[fallthrough]]; - case hash("eq_freqcc"): + case hash("eq&_freq_oncc&"): [[fallthrough]]; + case hash("eq&_freqcc&"): { - const auto eqNumber = opcode.firstParameter(); - if (!eqNumber || *eqNumber == 0) + const auto eqNumber = opcode.parameters.front(); + if (eqNumber == 0) return false; - if (!backParameter) - return false; - if (!extendIfNecessary(equalizers, *eqNumber, Default::numEQs)) + if (!extendIfNecessary(equalizers, eqNumber, Default::numEQs)) return false; - setValueFromOpcode(opcode, equalizers[*eqNumber - 1].frequencyCC[*backParameter], Default::eqFrequencyModRange); + setValueFromOpcode(opcode, equalizers[eqNumber - 1].frequencyCC[opcode.parameters.back()], Default::eqFrequencyModRange); } break; - case hash("eq_velfreq"): + case hash("eq&_vel&freq"): { - const auto eqNumber = opcode.firstParameter(); - const auto check2 = opcode.middleParameter(); - if (!eqNumber || *eqNumber == 0) + const auto eqNumber = opcode.parameters.front(); + if (eqNumber == 0) return false; - if (!check2 || *check2 != 2 || opcode.parameterPositions[1] != 6) + if (opcode.parameters[1] != 2) return false; // was eqN_vel3freq or something else than eqN_vel2freq - if (!extendIfNecessary(equalizers, *eqNumber, Default::numEQs)) + if (!extendIfNecessary(equalizers, eqNumber, Default::numEQs)) return false; - setValueFromOpcode(opcode, equalizers[*eqNumber - 1].vel2frequency, Default::eqFrequencyModRange); + setValueFromOpcode(opcode, equalizers[eqNumber - 1].vel2frequency, Default::eqFrequencyModRange); } break; - case hash("eq_gain"): + case hash("eq&_gain"): { - const auto eqNumber = opcode.firstParameter(); - if (!eqNumber || *eqNumber == 0) + const auto eqNumber = opcode.parameters.front(); + if (eqNumber == 0) return false; - if (!extendIfNecessary(equalizers, *eqNumber, Default::numEQs)) + if (!extendIfNecessary(equalizers, eqNumber, Default::numEQs)) return false; - setValueFromOpcode(opcode, equalizers[*eqNumber - 1].gain, Default::eqGainRange); + setValueFromOpcode(opcode, equalizers[eqNumber - 1].gain, Default::eqGainRange); } break; - case hash("eq_gain_oncc"): [[fallthrough]]; - case hash("eq_gaincc"): + case hash("eq&_gain_oncc&"): [[fallthrough]]; + case hash("eq&_gaincc&"): { - const auto eqNumber = opcode.firstParameter(); - if (!eqNumber || *eqNumber == 0) + const auto eqNumber = opcode.parameters.front(); + if (eqNumber == 0) return false; - if (!backParameter) - return false; - if (!extendIfNecessary(equalizers, *eqNumber, Default::numEQs)) + if (!extendIfNecessary(equalizers, eqNumber, Default::numEQs)) return false; - setValueFromOpcode(opcode, equalizers[*eqNumber - 1].gainCC[*backParameter], Default::eqGainModRange); + setValueFromOpcode(opcode, equalizers[eqNumber - 1].gainCC[opcode.parameters.back()], Default::eqGainModRange); } break; - case hash("eq_velgain"): + case hash("eq&_vel&gain"): { - const auto eqNumber = opcode.firstParameter(); - const auto check2 = opcode.middleParameter(); - if (!eqNumber || *eqNumber == 0) + const auto eqNumber = opcode.parameters.front(); + if (eqNumber == 0) return false; - if (!check2 || *check2 != 2 || opcode.parameterPositions[1] != 6) - return false; // was eqN_vel3gain or something else than eqN_vel2gain - if (!extendIfNecessary(equalizers, *eqNumber, Default::numEQs)) + if (opcode.parameters[1] != 2) + return false; // was eqN_vel3gain or something else than eqN_vel2gain + if (!extendIfNecessary(equalizers, eqNumber, Default::numEQs)) return false; - setValueFromOpcode(opcode, equalizers[*eqNumber - 1].vel2gain, Default::eqGainModRange); + setValueFromOpcode(opcode, equalizers[eqNumber - 1].vel2gain, Default::eqGainModRange); } break; // Performance parameters: pitch @@ -662,7 +638,7 @@ bool sfz::Region::parseOpcode(const Opcode& opcode) case hash("transpose"): setValueFromOpcode(opcode, transpose, Default::transposeRange); break; - case hash("tune"): + case hash("tune"): [[fallthrough]]; case hash("pitch"): setValueFromOpcode(opcode, tune, Default::tuneRange); break; @@ -698,56 +674,62 @@ bool sfz::Region::parseOpcode(const Opcode& opcode) case hash("ampeg_sustain"): setValueFromOpcode(opcode, amplitudeEG.sustain, Default::egPercentRange); break; - case hash("ampeg_velattack"): - if (!opcode.parameters.empty() && opcode.parameters.front() == 2) - setValueFromOpcode(opcode, amplitudeEG.vel2attack, Default::egOnCCTimeRange); + case hash("ampeg_vel&attack"): + if (opcode.parameters.front() != 2) + return false; // Was not vel2... + setValueFromOpcode(opcode, amplitudeEG.vel2attack, Default::egOnCCTimeRange); break; - case hash("ampeg_veldecay"): - if (!opcode.parameters.empty() && opcode.parameters.front() == 2) - setValueFromOpcode(opcode, amplitudeEG.vel2decay, Default::egOnCCTimeRange); + case hash("ampeg_vel&decay"): + if (opcode.parameters.front() != 2) + return false; // Was not vel2... + setValueFromOpcode(opcode, amplitudeEG.vel2decay, Default::egOnCCTimeRange); break; - case hash("ampeg_veldelay"): - if (!opcode.parameters.empty() && opcode.parameters.front() == 2) - setValueFromOpcode(opcode, amplitudeEG.vel2delay, Default::egOnCCTimeRange); + case hash("ampeg_vel&delay"): + if (opcode.parameters.front() != 2) + return false; // Was not vel2... + setValueFromOpcode(opcode, amplitudeEG.vel2delay, Default::egOnCCTimeRange); break; - case hash("ampeg_velhold"): - if (!opcode.parameters.empty() && opcode.parameters.front() == 2) - setValueFromOpcode(opcode, amplitudeEG.vel2hold, Default::egOnCCTimeRange); + case hash("ampeg_vel&hold"): + if (opcode.parameters.front() != 2) + return false; // Was not vel2... + setValueFromOpcode(opcode, amplitudeEG.vel2hold, Default::egOnCCTimeRange); break; - case hash("ampeg_velrelease"): - if (!opcode.parameters.empty() && opcode.parameters.front() == 2) - setValueFromOpcode(opcode, amplitudeEG.vel2release, Default::egOnCCTimeRange); + case hash("ampeg_vel&release"): + if (opcode.parameters.front() != 2) + return false; // Was not vel2... + setValueFromOpcode(opcode, amplitudeEG.vel2release, Default::egOnCCTimeRange); break; - case hash("ampeg_velsustain"): - if (!opcode.parameters.empty() && opcode.parameters.front() == 2) - setValueFromOpcode(opcode, amplitudeEG.vel2sustain, Default::egOnCCPercentRange); + case hash("ampeg_vel&sustain"): + if (opcode.parameters.front() != 2) + return false; // Was not vel2... + setValueFromOpcode(opcode, amplitudeEG.vel2sustain, Default::egOnCCPercentRange); break; - case hash("ampeg_attackcc"): - case hash("ampeg_attack_oncc"): + case hash("ampeg_attackcc&"): [[fallthrough]]; + case hash("ampeg_attack_oncc&"): setCCPairFromOpcode(opcode, amplitudeEG.ccAttack, Default::egOnCCTimeRange); break; - case hash("ampeg_decaycc"): - case hash("ampeg_decay_oncc"): + case hash("ampeg_decaycc&"): [[fallthrough]]; + case hash("ampeg_decay_oncc&"): setCCPairFromOpcode(opcode, amplitudeEG.ccDecay, Default::egOnCCTimeRange); break; - case hash("ampeg_delaycc"): - case hash("ampeg_delay_oncc"): + case hash("ampeg_delaycc&"): [[fallthrough]]; + case hash("ampeg_delay_oncc&"): setCCPairFromOpcode(opcode, amplitudeEG.ccDelay, Default::egOnCCTimeRange); break; - case hash("ampeg_holdcc"): - case hash("ampeg_hold_oncc"): + case hash("ampeg_holdcc&"): [[fallthrough]]; + case hash("ampeg_hold_oncc&"): setCCPairFromOpcode(opcode, amplitudeEG.ccHold, Default::egOnCCTimeRange); break; - case hash("ampeg_releasecc"): - case hash("ampeg_release_oncc"): + case hash("ampeg_releasecc&"): [[fallthrough]]; + case hash("ampeg_release_oncc&"): setCCPairFromOpcode(opcode, amplitudeEG.ccRelease, Default::egOnCCTimeRange); break; - case hash("ampeg_startcc"): - case hash("ampeg_start_oncc"): + case hash("ampeg_startcc&"): [[fallthrough]]; + case hash("ampeg_start_oncc&"): setCCPairFromOpcode(opcode, amplitudeEG.ccStart, Default::egOnCCPercentRange); break; - case hash("ampeg_sustaincc"): - case hash("ampeg_sustain_oncc"): + case hash("ampeg_sustaincc&"): [[fallthrough]]; + case hash("ampeg_sustain_oncc&"): setCCPairFromOpcode(opcode, amplitudeEG.ccSustain, Default::egOnCCPercentRange); break; @@ -755,7 +737,7 @@ bool sfz::Region::parseOpcode(const Opcode& opcode) case hash("hichan"): case hash("lochan"): case hash("ampeg_depth"): - case hash("ampeg_vel2depth"): + case hash("ampeg_vel&depth"): break; default: return false; diff --git a/src/sfizz/Synth.cpp b/src/sfizz/Synth.cpp index 0c3b3f87..d65baa2f 100644 --- a/src/sfizz/Synth.cpp +++ b/src/sfizz/Synth.cpp @@ -153,21 +153,18 @@ void sfz::Synth::handleGlobalOpcodes(const std::vector& members) void sfz::Synth::handleControlOpcodes(const std::vector& members) { for (auto& member : members) { - const auto backParameter = member.backParameter(); switch (member.lettersOnlyHash) { - case hash("Set_cc"): - [[fallthrough]]; - case hash("set_cc"): - if (backParameter && Default::ccNumberRange.containsWithEnd(*backParameter)) { + case hash("Set_cc&"): [[fallthrough]]; + case hash("set_cc&"): + if (Default::ccNumberRange.containsWithEnd(member.parameters.back())) { const auto ccValue = readOpcode(member.value, Default::ccValueRange).value_or(0); - resources.midiState.ccEvent(*backParameter, ccValue); + resources.midiState.ccEvent(member.parameters.back(), ccValue); } break; - case hash("Label_cc"): - [[fallthrough]]; - case hash("label_cc"): - if (backParameter && Default::ccNumberRange.containsWithEnd(*backParameter)) - ccNames.emplace_back(*backParameter, std::string(member.value)); + case hash("Label_cc&"): [[fallthrough]]; + case hash("label_cc&"): + if (Default::ccNumberRange.containsWithEnd(member.parameters.back())) + ccNames.emplace_back(member.parameters.back(), std::string(member.value)); break; case hash("Default_path"): [[fallthrough]]; diff --git a/tests/OpcodeT.cpp b/tests/OpcodeT.cpp index 0b3dfb34..f939547a 100644 --- a/tests/OpcodeT.cpp +++ b/tests/OpcodeT.cpp @@ -17,9 +17,6 @@ TEST_CASE("[Opcode] Construction") REQUIRE(opcode.lettersOnlyHash == hash("sample")); REQUIRE(opcode.parameters.empty()); REQUIRE(opcode.value == "dummy"); - REQUIRE(!opcode.backParameter()); - REQUIRE(!opcode.firstParameter()); - REQUIRE(!opcode.middleParameter()); } SECTION("Normal construction with underscore") @@ -29,56 +26,41 @@ TEST_CASE("[Opcode] Construction") REQUIRE(opcode.lettersOnlyHash == hash("sample_underscore")); REQUIRE(opcode.parameters.empty()); REQUIRE(opcode.value == "dummy"); - REQUIRE(!opcode.backParameter()); - REQUIRE(!opcode.firstParameter()); - REQUIRE(!opcode.middleParameter()); } SECTION("Parameterized opcode") { sfz::Opcode opcode { "sample123", "dummy" }; REQUIRE(opcode.opcode == "sample123"); - REQUIRE(opcode.lettersOnlyHash == hash("sample")); + REQUIRE(opcode.lettersOnlyHash == hash("sample&")); REQUIRE(opcode.value == "dummy"); REQUIRE(opcode.parameters.size() == 1); REQUIRE(opcode.parameters == std::vector({ 123 })); - REQUIRE(opcode.parameterPositions == std::vector({ 6 })); - REQUIRE(opcode.backParameter()); - REQUIRE(*opcode.backParameter() == 123); - REQUIRE(!opcode.firstParameter()); - REQUIRE(!opcode.middleParameter()); } SECTION("Parameterized opcode with underscore") { sfz::Opcode opcode { "sample_underscore123", "dummy" }; REQUIRE(opcode.opcode == "sample_underscore123"); - REQUIRE(opcode.lettersOnlyHash == hash("sample_underscore")); + REQUIRE(opcode.lettersOnlyHash == hash("sample_underscore&")); REQUIRE(opcode.value == "dummy"); REQUIRE(opcode.parameters == std::vector({ 123 })); - REQUIRE(opcode.parameterPositions == std::vector({ 17 })); - REQUIRE(opcode.backParameter()); - REQUIRE(*opcode.backParameter() == 123); } SECTION("Parameterized opcode within the opcode") { sfz::Opcode opcode { "sample1_underscore", "dummy" }; REQUIRE(opcode.opcode == "sample1_underscore"); - REQUIRE(opcode.lettersOnlyHash == hash("sample_underscore")); + REQUIRE(opcode.lettersOnlyHash == hash("sample&_underscore")); REQUIRE(opcode.value == "dummy"); REQUIRE(opcode.parameters == std::vector({ 1 })); - REQUIRE(!opcode.backParameter()); - REQUIRE(opcode.firstParameter()); - REQUIRE(*opcode.firstParameter() == 1); - REQUIRE(!opcode.middleParameter()); } SECTION("Parameterized opcode within the opcode") { sfz::Opcode opcode { "sample123_underscore", "dummy" }; REQUIRE(opcode.opcode == "sample123_underscore"); - REQUIRE(opcode.lettersOnlyHash == hash("sample_underscore")); + REQUIRE(opcode.lettersOnlyHash == hash("sample&_underscore")); REQUIRE(opcode.value == "dummy"); REQUIRE(opcode.parameters.size() == 1); REQUIRE(opcode.parameters[0] == 123); @@ -88,35 +70,22 @@ TEST_CASE("[Opcode] Construction") { sfz::Opcode opcode { "sample123_double44_underscore", "dummy" }; REQUIRE(opcode.opcode == "sample123_double44_underscore"); - REQUIRE(opcode.lettersOnlyHash == hash("sample_double_underscore")); + REQUIRE(opcode.lettersOnlyHash == hash("sample&_double&_underscore")); REQUIRE(opcode.value == "dummy"); REQUIRE(opcode.parameters.size() == 2); REQUIRE(opcode.parameters[0] == 123); REQUIRE(opcode.parameters[1] == 44); REQUIRE(opcode.parameters == std::vector({ 123, 44 })); - REQUIRE(opcode.parameterPositions == std::vector({ 6, 13 })); - REQUIRE(!opcode.backParameter()); - REQUIRE(opcode.firstParameter()); - REQUIRE(*opcode.firstParameter() == 123); - REQUIRE(opcode.middleParameter()); - REQUIRE(*opcode.middleParameter() == 44); } SECTION("Parameterized opcode within the opcode twice, with a back parameter") { sfz::Opcode opcode { "sample123_double44_underscore23", "dummy" }; REQUIRE(opcode.opcode == "sample123_double44_underscore23"); - REQUIRE(opcode.lettersOnlyHash == hash("sample_double_underscore")); + REQUIRE(opcode.lettersOnlyHash == hash("sample&_double&_underscore&")); REQUIRE(opcode.value == "dummy"); REQUIRE(opcode.parameters.size() == 3); REQUIRE(opcode.parameters == std::vector({ 123, 44, 23 })); - REQUIRE(opcode.parameterPositions == std::vector({ 6, 13, 24 })); - REQUIRE(opcode.backParameter()); - REQUIRE(*opcode.backParameter() == 23); - REQUIRE(opcode.firstParameter()); - REQUIRE(*opcode.firstParameter() == 123); - REQUIRE(opcode.middleParameter()); - REQUIRE(*opcode.middleParameter() == 44); } } From a17f6b916910679e5de0ea48bf82826acf144534 Mon Sep 17 00:00:00 2001 From: Paul Fd Date: Thu, 5 Mar 2020 13:13:05 +0100 Subject: [PATCH 12/93] Changed the parameter vectors to uint16 --- src/sfizz/Opcode.cpp | 2 -- src/sfizz/Opcode.h | 2 +- tests/OpcodeT.cpp | 10 +++++----- 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/src/sfizz/Opcode.cpp b/src/sfizz/Opcode.cpp index 2aeba276..344c31e9 100644 --- a/src/sfizz/Opcode.cpp +++ b/src/sfizz/Opcode.cpp @@ -26,8 +26,6 @@ sfz::Opcode::Opcode(absl::string_view inputOpcode, absl::string_view inputValue) uint32_t returnedValue; const auto numDigits = (nextCharIndex == opcode.npos) ? opcode.npos : nextCharIndex - nextNumIndex; if (absl::SimpleAtoi(opcode.substr(nextNumIndex, numDigits), &returnedValue)) { - // ASSERT(returnedValue < std::numeric_limits::max()); - // parameterPositions.push_back(parameterPosition); lettersOnlyHash = hash("&", lettersOnlyHash); parameters.push_back(returnedValue); } diff --git a/src/sfizz/Opcode.h b/src/sfizz/Opcode.h index db843ff0..227c3d28 100644 --- a/src/sfizz/Opcode.h +++ b/src/sfizz/Opcode.h @@ -31,7 +31,7 @@ struct Opcode { absl::string_view value {}; uint64_t lettersOnlyHash { Fnv1aBasis }; // This is to handle the integer parameters of some opcodes - std::vector parameters; + std::vector parameters; LEAK_DETECTOR(Opcode); }; diff --git a/tests/OpcodeT.cpp b/tests/OpcodeT.cpp index f939547a..d55d7aff 100644 --- a/tests/OpcodeT.cpp +++ b/tests/OpcodeT.cpp @@ -35,7 +35,7 @@ TEST_CASE("[Opcode] Construction") REQUIRE(opcode.lettersOnlyHash == hash("sample&")); REQUIRE(opcode.value == "dummy"); REQUIRE(opcode.parameters.size() == 1); - REQUIRE(opcode.parameters == std::vector({ 123 })); + REQUIRE(opcode.parameters == std::vector({ 123 })); } SECTION("Parameterized opcode with underscore") @@ -44,7 +44,7 @@ TEST_CASE("[Opcode] Construction") REQUIRE(opcode.opcode == "sample_underscore123"); REQUIRE(opcode.lettersOnlyHash == hash("sample_underscore&")); REQUIRE(opcode.value == "dummy"); - REQUIRE(opcode.parameters == std::vector({ 123 })); + REQUIRE(opcode.parameters == std::vector({ 123 })); } SECTION("Parameterized opcode within the opcode") @@ -53,7 +53,7 @@ TEST_CASE("[Opcode] Construction") REQUIRE(opcode.opcode == "sample1_underscore"); REQUIRE(opcode.lettersOnlyHash == hash("sample&_underscore")); REQUIRE(opcode.value == "dummy"); - REQUIRE(opcode.parameters == std::vector({ 1 })); + REQUIRE(opcode.parameters == std::vector({ 1 })); } SECTION("Parameterized opcode within the opcode") @@ -75,7 +75,7 @@ TEST_CASE("[Opcode] Construction") REQUIRE(opcode.parameters.size() == 2); REQUIRE(opcode.parameters[0] == 123); REQUIRE(opcode.parameters[1] == 44); - REQUIRE(opcode.parameters == std::vector({ 123, 44 })); + REQUIRE(opcode.parameters == std::vector({ 123, 44 })); } SECTION("Parameterized opcode within the opcode twice, with a back parameter") @@ -85,7 +85,7 @@ TEST_CASE("[Opcode] Construction") REQUIRE(opcode.lettersOnlyHash == hash("sample&_double&_underscore&")); REQUIRE(opcode.value == "dummy"); REQUIRE(opcode.parameters.size() == 3); - REQUIRE(opcode.parameters == std::vector({ 123, 44, 23 })); + REQUIRE(opcode.parameters == std::vector({ 123, 44, 23 })); } } From 692b2cda6af27072ff7d58897e7c01fec527f250 Mon Sep 17 00:00:00 2001 From: Paul Fd Date: Thu, 5 Mar 2020 16:07:23 +0100 Subject: [PATCH 13/93] Silently ignore ampersands in opcode name --- src/sfizz/Opcode.cpp | 4 ++-- src/sfizz/StringViewHelpers.h | 21 +++++++++++++++++++++ tests/OpcodeT.cpp | 28 ++++++++++++++++++++++++++++ 3 files changed, 51 insertions(+), 2 deletions(-) diff --git a/src/sfizz/Opcode.cpp b/src/sfizz/Opcode.cpp index 344c31e9..cc022c11 100644 --- a/src/sfizz/Opcode.cpp +++ b/src/sfizz/Opcode.cpp @@ -20,7 +20,7 @@ sfz::Opcode::Opcode(absl::string_view inputOpcode, absl::string_view inputValue) while (nextNumIndex != opcode.npos) { const auto numLetters = nextNumIndex - nextCharIndex; parameterPosition += numLetters; - lettersOnlyHash = hash(opcode.substr(nextCharIndex, numLetters), lettersOnlyHash); + lettersOnlyHash = hashNoAmpersand(opcode.substr(nextCharIndex, numLetters), lettersOnlyHash); nextCharIndex = opcode.find_first_not_of("1234567890", nextNumIndex); uint32_t returnedValue; @@ -34,5 +34,5 @@ sfz::Opcode::Opcode(absl::string_view inputOpcode, absl::string_view inputValue) } if (nextCharIndex != opcode.npos) - lettersOnlyHash = hash(opcode.substr(nextCharIndex), lettersOnlyHash); + lettersOnlyHash = hashNoAmpersand(opcode.substr(nextCharIndex), lettersOnlyHash); } diff --git a/src/sfizz/StringViewHelpers.h b/src/sfizz/StringViewHelpers.h index 0cc49d38..aeb2f2bd 100644 --- a/src/sfizz/StringViewHelpers.h +++ b/src/sfizz/StringViewHelpers.h @@ -66,3 +66,24 @@ constexpr uint64_t hash(absl::string_view s, uint64_t h = Fnv1aBasis) return h; } + +/** + * @brief Same function as `hash()` but ignores ampersands (&) + * + * See e.g. the Region.cpp file + * + * @param s the input string to be hashed + * @param h the hashing seed to use + * @return uint64_t + */ +constexpr uint64_t hashNoAmpersand(absl::string_view s, uint64_t h = Fnv1aBasis) +{ + if (s.length() > 0) { + if (s.front() == '&') + return hashNoAmpersand( { s.data() + 1, s.length() - 1 }, h ); + else + return hashNoAmpersand( { s.data() + 1, s.length() - 1 }, (h ^ s.front()) * Fnv1aPrime ); + } + + return h; +} diff --git a/tests/OpcodeT.cpp b/tests/OpcodeT.cpp index d55d7aff..ffbb07ab 100644 --- a/tests/OpcodeT.cpp +++ b/tests/OpcodeT.cpp @@ -28,6 +28,24 @@ TEST_CASE("[Opcode] Construction") REQUIRE(opcode.value == "dummy"); } + SECTION("Normal construction with ampersand") + { + sfz::Opcode opcode { "sample&_ampersand", "dummy" }; + REQUIRE(opcode.opcode == "sample&_ampersand"); + REQUIRE(opcode.lettersOnlyHash == hash("sample_ampersand")); + REQUIRE(opcode.parameters.empty()); + REQUIRE(opcode.value == "dummy"); + } + + SECTION("Normal construction with multiple ampersands") + { + sfz::Opcode opcode { "&sample&_ampersand&", "dummy" }; + REQUIRE(opcode.opcode == "&sample&_ampersand&"); + REQUIRE(opcode.lettersOnlyHash == hash("sample_ampersand")); + REQUIRE(opcode.parameters.empty()); + REQUIRE(opcode.value == "dummy"); + } + SECTION("Parameterized opcode") { sfz::Opcode opcode { "sample123", "dummy" }; @@ -38,6 +56,16 @@ TEST_CASE("[Opcode] Construction") REQUIRE(opcode.parameters == std::vector({ 123 })); } + SECTION("Parameterized opcode with ampersand") + { + sfz::Opcode opcode { "sample&123", "dummy" }; + REQUIRE(opcode.opcode == "sample&123"); + REQUIRE(opcode.lettersOnlyHash == hash("sample&")); + REQUIRE(opcode.value == "dummy"); + REQUIRE(opcode.parameters.size() == 1); + REQUIRE(opcode.parameters == std::vector({ 123 })); + } + SECTION("Parameterized opcode with underscore") { sfz::Opcode opcode { "sample_underscore123", "dummy" }; From 28c12f32f3d890844a69cbb77e2e49fa35567b02 Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Wed, 26 Feb 2020 21:09:34 +0100 Subject: [PATCH 14/93] Initial effects --- src/CMakeLists.txt | 3 + src/sfizz/AudioSpan.h | 16 +++ src/sfizz/Config.h | 4 + src/sfizz/Effects.cpp | 137 ++++++++++++++++++++++ src/sfizz/Effects.h | 147 +++++++++++++++++++++++ src/sfizz/Region.cpp | 22 ++++ src/sfizz/Region.h | 13 +++ src/sfizz/Synth.cpp | 155 ++++++++++++++++++++++--- src/sfizz/Synth.h | 15 ++- src/sfizz/effects/Lofi.cpp | 213 ++++++++++++++++++++++++++++++++++ src/sfizz/effects/Lofi.h | 79 +++++++++++++ src/sfizz/effects/Nothing.cpp | 31 +++++ src/sfizz/effects/Nothing.h | 35 ++++++ 13 files changed, 852 insertions(+), 18 deletions(-) create mode 100644 src/sfizz/Effects.cpp create mode 100644 src/sfizz/Effects.h create mode 100644 src/sfizz/effects/Lofi.cpp create mode 100644 src/sfizz/effects/Lofi.h create mode 100644 src/sfizz/effects/Nothing.cpp create mode 100644 src/sfizz/effects/Nothing.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 57c71312..3580130c 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -15,6 +15,9 @@ set (SFIZZ_SOURCES sfizz/FloatEnvelopes.cpp sfizz/Logger.cpp sfizz/SfzFilter.cpp + sfizz/Effects.cpp + sfizz/effects/Nothing.cpp + sfizz/effects/Lofi.cpp ) include (SfizzSIMDSourceFiles) diff --git a/src/sfizz/AudioSpan.h b/src/sfizz/AudioSpan.h index 312c1e1b..c608848c 100644 --- a/src/sfizz/AudioSpan.h +++ b/src/sfizz/AudioSpan.h @@ -209,6 +209,22 @@ public: return {}; } + /** + * @brief Convert implicitly to a pointer of channels + */ + operator const float* const *() const noexcept + { + return spans.data(); + } + + /** + * @brief Convert implicitly to a pointer of channels + */ + operator float* const *() noexcept + { + return spans.data(); + } + /** * @brief Get a Span corresponding to a specific channel * diff --git a/src/sfizz/Config.h b/src/sfizz/Config.h index 0b629ccd..b2a6f8a2 100644 --- a/src/sfizz/Config.h +++ b/src/sfizz/Config.h @@ -69,6 +69,10 @@ namespace config { */ const absl::string_view midnamManufacturer { "The Sfizz authors" }; const absl::string_view midnamModel { "Sfizz" }; + /** + Limit of how many "fxN" buses are accepted (in SFZv2, maximum is 4) + */ + constexpr int maxEffectBuses { 256 }; } // namespace config // Enable or disable SIMD accelerators by default diff --git a/src/sfizz/Effects.cpp b/src/sfizz/Effects.cpp new file mode 100644 index 00000000..a5b3b964 --- /dev/null +++ b/src/sfizz/Effects.cpp @@ -0,0 +1,137 @@ +// SPDX-License-Identifier: BSD-2-Clause + +// This code is part of the sfizz library and is licensed under a BSD 2-clause +// license. You should have receive a LICENSE.md file along with the code. +// If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz + +#include "Effects.h" +#include "AudioSpan.h" +#include "Opcode.h" +#include "SIMDHelpers.h" +#include "Config.h" +#include "effects/Nothing.h" +#include "effects/Lofi.h" +#include + +namespace sfz { + +void EffectFactory::registerStandardEffectTypes() +{ + // TODO + registerEffectType("lofi", fx::Lofi::makeInstance); +} + +void EffectFactory::registerEffectType(absl::string_view name, Effect::MakeInstance& make) +{ + FactoryEntry ent; + ent.name = std::string(name); + ent.make = &make; + _entries.push_back(std::move(ent)); +} + +Effect* EffectFactory::makeEffect(absl::Span members) +{ + const Opcode* opcode = nullptr; + + for (auto it = members.rbegin(); it != members.rend() && !opcode; ++it) { + if (it->lettersOnlyHash == hash("type")) + opcode = &*it; + } + + if (!opcode) { + DBG("The effect does not specify a type"); + return new sfz::fx::Nothing; + } + + absl::string_view type = opcode->value; + + auto it = _entries.begin(); + auto end = _entries.end(); + for (; it != end && it->name != type; ++it) + ; + + if (it == end) { + DBG("Unsupported effect type: " << type); + return new sfz::fx::Nothing; + } + + Effect* fx = it->make(members); + if (!fx) { + DBG("Could not instantiate effect of type: " << type); + return new sfz::fx::Nothing; + } + + return fx; +} + +/// +EffectBus::EffectBus() +{ +} + +EffectBus::~EffectBus() +{ +} + +void EffectBus::addEffect(std::unique_ptr fx) +{ + _effects.emplace_back(std::move(fx)); +} + +void EffectBus::clearInputs(unsigned nframes) +{ + AudioSpan(_inputs).first(nframes).fill(0.0f); + AudioSpan(_outputs).first(nframes).fill(0.0f); +} + +void EffectBus::addToInputs(const float* const addInput[], float addGain, unsigned nframes) +{ + if (addGain == 0) + return; + + for (unsigned c = 0; c < EffectChannels; ++c) { + absl::Span addIn(addInput[c], nframes); + sfz::multiplyAdd(addGain, addIn, _inputs.getSpan(c)); + } +} + +void EffectBus::init(double sampleRate) +{ + for (const auto& effectPtr : _effects) + effectPtr->init(sampleRate); +} + +void EffectBus::clear() +{ + for (const auto& effectPtr : _effects) + effectPtr->clear(); +} + +void EffectBus::process(unsigned nframes) +{ + size_t numEffects = _effects.size(); + + if (numEffects > 0 && hasNonZeroOutput()) { + _effects[0]->process( + AudioSpan(_inputs), AudioSpan(_outputs), nframes); + for (size_t i = 1; i < numEffects; ++i) + _effects[i]->process( + AudioSpan(_outputs), AudioSpan(_outputs), nframes); + } else + fx::Nothing().process( + AudioSpan(_inputs), AudioSpan(_outputs), nframes); +} + +void EffectBus::mixOutputsTo(float* const mainOutput[], float* const mixOutput[], unsigned nframes) +{ + const float gainToMain = _gainToMain; + const float gainToMix = _gainToMix; + + for (unsigned c = 0; c < EffectChannels; ++c) { + absl::Span fxOut = _outputs.getConstSpan(c); + sfz::multiplyAdd(gainToMain, fxOut, absl::Span(mainOutput[c], nframes)); + sfz::multiplyAdd(gainToMix, fxOut, absl::Span(mixOutput[c], nframes)); + } +} + +} // namespace sfz diff --git a/src/sfizz/Effects.h b/src/sfizz/Effects.h new file mode 100644 index 00000000..80462d0c --- /dev/null +++ b/src/sfizz/Effects.h @@ -0,0 +1,147 @@ +// 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 "AudioBuffer.h" +#include "absl/strings/string_view.h" +#include "absl/types/span.h" +#include +#include +#include + +namespace sfz { +struct Opcode; + +enum { + // Number of channels processed by effects + EffectChannels = 2, +}; + +/** + @brief Abstract base of SFZ effects + */ +class Effect { +public: + virtual ~Effect() {} + + /** + @brief Initializes with the given sample rate. + */ + virtual void init(double sampleRate) = 0; + + /** + @brief Reset the state to initial. + */ + virtual void clear() = 0; + + /** + @brief Computes a cycle of the effect in stereo. + */ + virtual void process(const float* const inputs[], float* const outputs[], unsigned nframes) = 0; + + /** + @brief Type of the factory function used to instantiate an effect given + the contents of the block + */ + typedef Effect* (MakeInstance)(absl::Span members); +}; + +/** + @brief SFZ effects factory + */ +class EffectFactory { +public: + /** + @brief Registers all available standard effects into the factory. + */ + void registerStandardEffectTypes(); + + /** + @brief Registers a user-defined effect into the factory. + */ + void registerEffectType(absl::string_view name, Effect::MakeInstance& make); + + /** + @brief Instantiates an effect given the contents of the block. + */ + Effect* makeEffect(absl::Span members); + +private: + struct FactoryEntry { + std::string name; + Effect::MakeInstance* make; + }; + + std::vector _entries; +}; + +/** + @brief Sequence of effects processed in series + */ +class EffectBus { +public: + EffectBus(); + ~EffectBus(); + + /** + @brief Adds an effect at the end of the bus. + */ + void addEffect(std::unique_ptr fx); + + /** + @brief Checks whether this bus can produce output. + */ + bool hasNonZeroOutput() const { return _gainToMain != 0 || _gainToMix != 0; } + + /** + @brief Sets the amount of effect output going to the main. + */ + void setGainToMain(float gain) { _gainToMain = gain; } + + /** + @brief Sets the amount of effect output going to the mix. + */ + void setGainToMix(float gain) { _gainToMix = gain; } + + /** + @brief Resets the input buffers to zero. + */ + void clearInputs(unsigned nframes); + + /** + @brief Adds some audio into the input buffer. + */ + void addToInputs(const float* const addInput[], float addGain, unsigned nframes); + + /** + @brief Initializes all effects in the bus with the given sample rate. + */ + void init(double sampleRate); + + /** + @brief Resets the state of all effects in the bus. + */ + void clear(); + + /** + @brief Computes a cycle of the effect bus. + */ + void process(unsigned nframes); + + /** + @brief Mixes the outputs into a pair of stereo signals: Main and Mix. + */ + void mixOutputsTo(float* const mainOutput[], float* const mixOutput[], unsigned nframes); + +private: + std::vector> _effects; + AudioBuffer _inputs { EffectChannels, config::defaultSamplesPerBlock }; + AudioBuffer _outputs { EffectChannels, config::defaultSamplesPerBlock }; + float _gainToMain = 0.0; + float _gainToMix = 0.0; +}; + +} // namespace sfz diff --git a/src/sfizz/Region.cpp b/src/sfizz/Region.cpp index e05708a0..35475263 100644 --- a/src/sfizz/Region.cpp +++ b/src/sfizz/Region.cpp @@ -733,6 +733,20 @@ bool sfz::Region::parseOpcode(const Opcode& opcode) setCCPairFromOpcode(opcode, amplitudeEG.ccSustain, Default::egOnCCPercentRange); break; + case hash("effect"): // effect& + { + const auto effectNumber = opcode.backParameter(); + if (!effectNumber || *effectNumber < 1 || *effectNumber > config::maxEffectBuses) + break; + auto value = readOpcode(opcode.value, {0, 100}); + if (!value) + break; + if (static_cast(*effectNumber + 1) > gainToEffect.size()) + gainToEffect.resize(*effectNumber + 1); + gainToEffect[*effectNumber] = *value / 100; + break; + } + // Ignored opcodes case hash("hichan"): case hash("lochan"): @@ -1073,3 +1087,11 @@ void sfz::Region::offsetAllKeys(int offset) noexcept crossfadeKeyOutRange.setEnd(offsetAndClamp(end, offset, Default::keyRange)); } } + +float sfz::Region::getGainToEffectBus(unsigned number) const noexcept +{ + if (number >= gainToEffect.size()) + return 0.0; + + return gainToEffect[number]; +} diff --git a/src/sfizz/Region.h b/src/sfizz/Region.h index d398a63e..cf08657b 100644 --- a/src/sfizz/Region.h +++ b/src/sfizz/Region.h @@ -39,6 +39,9 @@ struct Region { : midiState(midiState), defaultPath(std::move(defaultPath)) { ccSwitched.set(); + + gainToEffect.reserve(5); // sufficient room for main and fx1-4 + gainToEffect.push_back(1.0); // contribute 100% into the main bus } Region(const Region&) = default; ~Region() = default; @@ -206,6 +209,12 @@ struct Region { bool hasKeyswitches() const noexcept { return keyswitchDown || keyswitchUp || keyswitch || previousNote; } + /** + * @brief Get the gain this region contributes into the input of the Nth + * effect bus + */ + float getGainToEffectBus(unsigned number) const noexcept; + // Sound source: sample playback std::string sample {}; // Sample float delay { Default::delay }; // delay @@ -297,6 +306,10 @@ struct Region { EGDescription filterEG; bool isStereo { false }; + + // Effects + std::vector gainToEffect; + private: const MidiState& midiState; bool keySwitched { true }; diff --git a/src/sfizz/Synth.cpp b/src/sfizz/Synth.cpp index d65baa2f..7ebbbb39 100644 --- a/src/sfizz/Synth.cpp +++ b/src/sfizz/Synth.cpp @@ -21,12 +21,16 @@ using namespace std::literals; sfz::Synth::Synth() + : Synth(config::numVoices) { - resetVoices(this->numVoices); } sfz::Synth::Synth(int numVoices) { + effectFactory.registerStandardEffectTypes(); + + effectBuses.reserve(5); // sufficient room for main and fx1-4 + resetVoices(numVoices); } @@ -70,7 +74,7 @@ void sfz::Synth::callback(absl::string_view header, const std::vector& m numCurves++; break; case hash("effect"): - // TODO: implement effects + handleEffectOpcodes(members); break; default: std::cerr << "Unknown header: " << header << '\n'; @@ -118,6 +122,10 @@ void sfz::Synth::clear() for (auto& list: ccActivationLists) list.clear(); regions.clear(); + effectBuses.clear(); + EffectBus* mainBus = new EffectBus; + effectBuses.emplace_back(mainBus); + mainBus->setGainToMain(1.0); resources.filePool.clear(); resources.logger.clear(); numGroups = 0; @@ -185,6 +193,75 @@ void sfz::Synth::handleControlOpcodes(const std::vector& members) } } +void sfz::Synth::handleEffectOpcodes(const std::vector& members) +{ + absl::string_view busName = "main"; + + auto getOrCreateBus = [this](unsigned index) -> EffectBus& { + if (index + 1 > effectBuses.size()) + effectBuses.resize(index + 1); + EffectBusPtr &slot = effectBuses[index]; + if (!slot) + slot.reset(new EffectBus); + return *slot; + }; + + for (const Opcode& opcode : members) { + switch (opcode.lettersOnlyHash) { + case hash("bus"): + busName = opcode.value; + break; + + // note(jpc): gain opcodes are linear volumes in % units + + case hash("directtomain"): + if (auto valueOpt = readOpcode(opcode.value, {0, 100})) + getOrCreateBus(0).setGainToMain(*valueOpt / 100); + break; + + case hash("fxtomain"): // fx&tomain + if (auto numberOpt = opcode.firstParameter()) { + unsigned number = *numberOpt; + if (number < 1 || number > config::maxEffectBuses) + break; + if (auto valueOpt = readOpcode(opcode.value, {0, 100})) + getOrCreateBus(number).setGainToMain(*valueOpt / 100); + } + break; + + case hash("fxtomix"): // fx&tomix + if (auto numberOpt = opcode.firstParameter()) { + unsigned number = *numberOpt; + if (number < 1 || number > config::maxEffectBuses) + break; + if (auto valueOpt = readOpcode(opcode.value, {0, 100})) + getOrCreateBus(number).setGainToMix(*valueOpt / 100); + } + break; + } + } + + unsigned busIndex; + if (busName.empty() || busName == "main") + busIndex = 0; + else if (busName.size() > 2 && busName.substr(0, 2) == "fx" && + absl::SimpleAtoi(busName.substr(2), &busIndex) && + busIndex >= 1 && busIndex <= config::maxEffectBuses) { + // an effect bus fxN, with N usually in [1,4] + } + else { + DBG("Unsupported effect bus: " << busName); + return; + } + + // create the effect and add it + EffectBus& bus = getOrCreateBus(busIndex); + Effect* fx = effectFactory.makeEffect(members); + bus.addEffect(std::unique_ptr(fx)); + + fx->init(sampleRate); +} + void addEndpointsToVelocityCurve(sfz::Region& region) { if (region.velocityPoints.size() > 0) { @@ -385,6 +462,7 @@ void sfz::Synth::setSamplesPerBlock(int samplesPerBlock) noexcept this->samplesPerBlock = samplesPerBlock; this->tempBuffer.resize(samplesPerBlock); + this->tempMixNodeBuffer.resize(samplesPerBlock); for (auto& voice : voices) voice->setSamplesPerBlock(samplesPerBlock); } @@ -402,13 +480,17 @@ void sfz::Synth::setSampleRate(float sampleRate) noexcept resources.filterPool.setSampleRate(sampleRate); resources.eqPool.setSampleRate(sampleRate); + + for (size_t i = 0, n = effectBuses.size(); i < n; ++i) { + if (EffectBus* bus = effectBuses[i].get()) + bus->init(sampleRate); + } } void sfz::Synth::renderBlock(AudioSpan buffer) noexcept { ScopedFTZ ftz; - if (freeWheeling) resources.filePool.waitForBackgroundLoading(); @@ -416,32 +498,71 @@ void sfz::Synth::renderBlock(AudioSpan buffer) noexcept if (!canEnterCallback) return; + size_t numFrames = buffer.getNumFrames(); + size_t numEffectBuses = effectBuses.size(); + auto temp = AudioSpan(tempBuffer).first(numFrames); + auto tempMixNode = AudioSpan(tempMixNodeBuffer).first(numFrames); + + // Prepare the effect inputs. They are mixes of per-region outputs. + for (size_t i = 0; i < numEffectBuses; ++i) { + if (EffectBus* bus = effectBuses[i].get()) + bus->clearInputs(numFrames); + } + + // CallbackBreakdown callbackBreakdown; int numActiveVoices { 0 }; { // Main render block ScopedTiming logger { callbackBreakdown.renderMethod }; buffer.fill(0.0f); + tempMixNode.fill(0.0f); resources.filePool.cleanupPromises(); - - auto tempSpan = AudioSpan(tempBuffer).first(buffer.getNumFrames()); for (auto& voice : voices) { - if (!voice->isFree()) { - numActiveVoices++; - voice->renderBlock(tempSpan); - buffer.add(tempSpan); - callbackBreakdown.data += voice->getLastDataDuration(); - callbackBreakdown.amplitude += voice->getLastAmplitudeDuration(); - callbackBreakdown.filters += voice->getLastFilterDuration(); - callbackBreakdown.panning += voice->getLastPanningDuration(); - } - } + if (voice->isFree()) + continue; - buffer.applyGain(db2mag(volume)); + const Region* region = voice->getRegion(); + + numActiveVoices++; + voice->renderBlock(temp); + + // Add the output into the effects linked to this region + for (size_t i = 0; i < numEffectBuses; ++i) { + if (EffectBus* bus = effectBuses[i].get()) { + float addGain = region->getGainToEffectBus(i); + bus->addToInputs(temp, addGain, numFrames); + } + } + + callbackBreakdown.data += voice->getLastDataDuration(); + callbackBreakdown.amplitude += voice->getLastAmplitudeDuration(); + callbackBreakdown.filters += voice->getLastFilterDuration(); + callbackBreakdown.panning += voice->getLastPanningDuration(); + } } + // Apply effect buses + // -- note(jpc) there is always a "main" bus which is initially empty. + // without any , the signal is just going to flow through it. + for (size_t i = 0; i < numEffectBuses; ++i) { + if (EffectBus* bus = effectBuses[i].get()) { + bus->process(numFrames); + bus->mixOutputsTo(buffer, tempMixNode, numFrames); + } + } + + // Add the Mix output (fxNtomix opcodes) + // -- note(jpc) the purpose of the Mix output is not known. + // perhaps it's designed as extension point for custom processing? + // as default behavior, it adds itself to the Main signal. + buffer.add(tempMixNode); + + // Apply the master volume + buffer.applyGain(db2mag(volume)); + callbackBreakdown.dispatch = dispatchDuration; - resources.logger.logCallbackTime(std::move(callbackBreakdown), numActiveVoices, buffer.getNumFrames()); + resources.logger.logCallbackTime(std::move(callbackBreakdown), numActiveVoices, numFrames); // Reset the dispatch counter dispatchDuration = Duration(0); diff --git a/src/sfizz/Synth.h b/src/sfizz/Synth.h index f2c6fd11..3f94262d 100644 --- a/src/sfizz/Synth.h +++ b/src/sfizz/Synth.h @@ -9,6 +9,7 @@ #include "Parser.h" #include "Voice.h" #include "Region.h" +#include "Effects.h" #include "LeakDetector.h" #include "MidiState.h" #include "AudioSpan.h" @@ -400,6 +401,12 @@ private: * @param members the opcodes of the block */ void handleControlOpcodes(const std::vector& members); + /** + * @brief Helper function to dispatch opcodes + * + * @param members the opcodes of the block + */ + void handleEffectOpcodes(const std::vector& members); /** * @brief Helper function to merge all the currently active opcodes * as set by the successive callbacks and create a new region to store @@ -441,8 +448,14 @@ private: std::array noteActivationLists; std::array ccActivationLists; - // Internal temporary buffer + // Effect factory and buses + EffectFactory effectFactory; + typedef std::unique_ptr EffectBusPtr; + std::vector effectBuses; // 0 is "main", 1-N are "fx1"-"fxN" + + // Intermediate buffers AudioBuffer tempBuffer { 2, config::defaultSamplesPerBlock }; + AudioBuffer tempMixNodeBuffer { 2, config::defaultSamplesPerBlock }; int samplesPerBlock { config::defaultSamplesPerBlock }; float sampleRate { config::defaultSampleRate }; diff --git a/src/sfizz/effects/Lofi.cpp b/src/sfizz/effects/Lofi.cpp new file mode 100644 index 00000000..9c4e1f7e --- /dev/null +++ b/src/sfizz/effects/Lofi.cpp @@ -0,0 +1,213 @@ +// 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 + +/** + Note(jpc): implementation status + +- [x] bitred +- [ ] bitred_oncc +- [ ] bitred_smoothcc +- [ ] bitred_stepcc +- [ ] bitred_curvecc + +- [x] decim +- [ ] decim_oncc +- [ ] decim_smoothcc +- [ ] decim_stepcc +- [ ] decim_curvecc + +- [ ] egN_bitred +- [ ] egN_bitred_oncc +- [ ] lfoN_bitred +- [ ] lfoN_bitred_oncc +- [ ] lfoN_bitred_smoothcc +- [ ] lfoN_bitred_stepcc + +- [ ] egN_decim +- [ ] egN_decim_oncc +- [ ] lfoN_decim +- [ ] lfoN_decim_oncc +- [ ] lfoN_decim_smoothcc +- [ ] lfoN_decim_stepcc + + */ + +#include "Lofi.h" +#include "Opcode.h" +#include +#include +#include +#include + +namespace sfz { +namespace fx { + + void Lofi::init(double sampleRate) + { + for (unsigned c = 0; c < EffectChannels; ++c) { + _bitred[c].init(sampleRate); + _decim[c].init(sampleRate); + } + } + + void Lofi::clear() + { + for (unsigned c = 0; c < EffectChannels; ++c) { + _bitred[c].clear(); + _decim[c].clear(); + } + } + + void Lofi::process(const float* const inputs[2], float* const outputs[2], unsigned nframes) + { + for (unsigned c = 0; c < EffectChannels; ++c) { + _bitred[c].setDepth(_bitred_depth); + _bitred[c].process(inputs[c], outputs[c], nframes); + + _decim[c].setDepth(_decim_depth); + _decim[c].process(outputs[c], outputs[c], nframes); + } + } + + Effect* Lofi::makeInstance(absl::Span members) + { + std::unique_ptr fx { new Lofi }; + + for (const Opcode& opcode : members) { + switch (opcode.lettersOnlyHash) { + case hash("bitred"): + setValueFromOpcode(opcode, fx->_bitred_depth, { 0.0, 100.0 }); + break; + case hash("decim"): + setValueFromOpcode(opcode, fx->_decim_depth, { 0.0, 100.0 }); + break; + } + } + + return fx.release(); + } + + /// + void Lofi::Bitred::init(double sampleRate) + { + (void)sampleRate; + + static constexpr double coefs2x[12] = { 0.036681502163648017, 0.13654762463195794, 0.27463175937945444, 0.42313861743656711, 0.56109869787919531, 0.67754004997416184, 0.76974183386322703, 0.83988962484963892, 0.89226081800387902, 0.9315419599631839, 0.96209454837808417, 0.98781637073289585 }; + fDownsampler2x.set_coefs(coefs2x); + } + + void Lofi::Bitred::clear() + { + fLastValue = 0.0; + fDownsampler2x.clear_buffers(); + } + + void Lofi::Bitred::setDepth(float depth) + { + fDepth = std::max(0.0f, std::min(100.0f, depth)); + } + + void Lofi::Bitred::process(const float* in, float* out, uint32_t nframes) + { + float depth = fDepth; + + if (depth == 0) { + if (in != out) + std::memcpy(out, in, nframes * sizeof(float)); + clear(); + return; + } + + float lastValue = fLastValue; + hiir::Downsampler2xFpu<12>& downsampler2x = fDownsampler2x; + + float steps = (1.0f + (100.0f - depth)) * 0.75f; + + for (uint32_t i = 0; i < nframes; ++i) { + float x = in[i]; + + float y = std::copysign((int)(0.5f + std::fabs(x * steps)), x) * (1 / steps); + + float y2x[2]; + y2x[0] = (y != lastValue) ? (0.5f * (y + lastValue)) : y; + y2x[1] = y; + + lastValue = y; + + y = downsampler2x.process_sample(y2x); + out[i] = y; + } + + fLastValue = lastValue; + } + + /// + void Lofi::Decim::init(double sampleRate) + { + fSampleTime = 1.0 / sampleRate; + + static constexpr double coefs2x[12] = { 0.036681502163648017, 0.13654762463195794, 0.27463175937945444, 0.42313861743656711, 0.56109869787919531, 0.67754004997416184, 0.76974183386322703, 0.83988962484963892, 0.89226081800387902, 0.9315419599631839, 0.96209454837808417, 0.98781637073289585 }; + fDownsampler2x.set_coefs(coefs2x); + } + + void Lofi::Decim::clear() + { + fPhase = 0.0; + fLastValue = 0.0; + fDownsampler2x.clear_buffers(); + } + + void Lofi::Decim::setDepth(float depth) + { + fDepth = std::max(0.0f, std::min(100.0f, depth)); + } + + void Lofi::Decim::process(const float* in, float* out, uint32_t nframes) + { + float depth = fDepth; + + if (depth == 0) { + if (in != out) + std::memcpy(out, in, nframes * sizeof(float)); + clear(); + return; + } + + float dt; + { + // exponential curve fit + float a = 1.289079e+00, b = 1.384141e-01, c = 1.313298e-04; + dt = std::pow(a, b * depth) * c - c; + dt = fSampleTime / dt; + } + + float phase = fPhase; + float lastValue = fLastValue; + hiir::Downsampler2xFpu<12>& downsampler2x = fDownsampler2x; + + for (uint32_t i = 0; i < nframes; ++i) { + float x = in[i]; + + phase += dt; + float y = (phase > 1.0f) ? x : lastValue; + phase -= (int)phase; + + float y2x[2]; + y2x[0] = (y != lastValue) ? (0.5f * (y + lastValue)) : y; + y2x[1] = y; + + lastValue = y; + + y = downsampler2x.process_sample(y2x); + out[i] = y; + } + + fPhase = phase; + fLastValue = lastValue; + } + +} // namespace fx +} // namespace sfz diff --git a/src/sfizz/effects/Lofi.h b/src/sfizz/effects/Lofi.h new file mode 100644 index 00000000..8bfda5d8 --- /dev/null +++ b/src/sfizz/effects/Lofi.h @@ -0,0 +1,79 @@ +// 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 "Effects.h" +#include "hiir/Downsampler2xFpu.h" + +namespace sfz { +namespace fx { + + /** + * @brief Bit crushing effect + */ + class Lofi : public Effect { + public: + /** + * @brief Initializes with the given sample rate. + */ + void init(double sampleRate) override; + + /** + * @brief Reset the state to initial. + */ + void clear() override; + + /** + * @brief Computes a cycle of the effect in stereo. + */ + void process(const float* const inputs[], float* const outputs[], unsigned nframes) override; + + /** + * @brief Instantiates given the contents of the block. + */ + static Effect* makeInstance(absl::Span members); + + private: + float _bitred_depth = 0; + float _decim_depth = 0; + + /// + class Bitred { + public: + void init(double sampleRate); + void clear(); + void setDepth(float depth); + void process(const float* in, float* out, uint32_t nframes); + + private: + float fDepth = 0.0; + float fLastValue = 0.0; + hiir::Downsampler2xFpu<12> fDownsampler2x; + }; + + /// + class Decim { + public: + void init(double sampleRate); + void clear(); + void setDepth(float depth); + void process(const float* in, float* out, uint32_t nframes); + + private: + float fSampleTime = 0.0; + float fDepth = 0.0; + float fPhase = 0.0; + float fLastValue = 0.0; + hiir::Downsampler2xFpu<12> fDownsampler2x; + }; + + /// + Bitred _bitred[EffectChannels]; + Decim _decim[EffectChannels]; + }; + +} // namespace fx +} // namespace sfz diff --git a/src/sfizz/effects/Nothing.cpp b/src/sfizz/effects/Nothing.cpp new file mode 100644 index 00000000..527ce5ae --- /dev/null +++ b/src/sfizz/effects/Nothing.cpp @@ -0,0 +1,31 @@ +// 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 "Nothing.h" +#include + +namespace sfz { +namespace fx { + + void Nothing::init(double sampleRate) + { + (void)sampleRate; + } + + void Nothing::clear() + { + } + + void Nothing::process(const float* const inputs[], float* const outputs[], unsigned nframes) + { + for (unsigned c = 0; c < EffectChannels; ++c) { + if (inputs[c] != outputs[c]) + std::memcpy(outputs[c], inputs[c], nframes * sizeof(float)); + } + } + +} // namespace fx +} // namespace sfz diff --git a/src/sfizz/effects/Nothing.h b/src/sfizz/effects/Nothing.h new file mode 100644 index 00000000..239697d8 --- /dev/null +++ b/src/sfizz/effects/Nothing.h @@ -0,0 +1,35 @@ +// 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 "Effects.h" + +namespace sfz { +namespace fx { + + /** + * @brief Effect which does nothing + */ + class Nothing : public Effect { + public: + /** + * @brief Initializes with the given sample rate. + */ + void init(double sampleRate) override; + + /** + * @brief Reset the state to initial. + */ + void clear() override; + + /** + * @brief Copy the input signal to the output + */ + void process(const float* const inputs[], float* const outputs[], unsigned nframes) override; + }; + +} // namespace fx +} // namespace sfz From 75322915450ebbf5ba2c3e0ad061bfef28f8bd26 Mon Sep 17 00:00:00 2001 From: Paul Fd Date: Thu, 5 Mar 2020 16:53:12 +0100 Subject: [PATCH 15/93] Moved the factory to unique pointers --- src/sfizz/Effects.cpp | 10 +++++----- src/sfizz/Effects.h | 4 ++-- src/sfizz/Synth.cpp | 5 ++--- src/sfizz/effects/Lofi.cpp | 6 +++--- src/sfizz/effects/Lofi.h | 2 +- 5 files changed, 13 insertions(+), 14 deletions(-) diff --git a/src/sfizz/Effects.cpp b/src/sfizz/Effects.cpp index a5b3b964..5bfddf68 100644 --- a/src/sfizz/Effects.cpp +++ b/src/sfizz/Effects.cpp @@ -29,7 +29,7 @@ void EffectFactory::registerEffectType(absl::string_view name, Effect::MakeInsta _entries.push_back(std::move(ent)); } -Effect* EffectFactory::makeEffect(absl::Span members) +std::unique_ptr EffectFactory::makeEffect(absl::Span members) { const Opcode* opcode = nullptr; @@ -40,7 +40,7 @@ Effect* EffectFactory::makeEffect(absl::Span members) if (!opcode) { DBG("The effect does not specify a type"); - return new sfz::fx::Nothing; + return std::make_unique(); } absl::string_view type = opcode->value; @@ -52,13 +52,13 @@ Effect* EffectFactory::makeEffect(absl::Span members) if (it == end) { DBG("Unsupported effect type: " << type); - return new sfz::fx::Nothing; + return std::make_unique(); } - Effect* fx = it->make(members); + auto fx = std::unique_ptr(it->make(members)); if (!fx) { DBG("Could not instantiate effect of type: " << type); - return new sfz::fx::Nothing; + return std::make_unique(); } return fx; diff --git a/src/sfizz/Effects.h b/src/sfizz/Effects.h index 80462d0c..ff159d3c 100644 --- a/src/sfizz/Effects.h +++ b/src/sfizz/Effects.h @@ -46,7 +46,7 @@ public: @brief Type of the factory function used to instantiate an effect given the contents of the block */ - typedef Effect* (MakeInstance)(absl::Span members); + typedef std::unique_ptr (MakeInstance)(absl::Span members); }; /** @@ -67,7 +67,7 @@ public: /** @brief Instantiates an effect given the contents of the block. */ - Effect* makeEffect(absl::Span members); + std::unique_ptr makeEffect(absl::Span members); private: struct FactoryEntry { diff --git a/src/sfizz/Synth.cpp b/src/sfizz/Synth.cpp index 7ebbbb39..7c2c0a02 100644 --- a/src/sfizz/Synth.cpp +++ b/src/sfizz/Synth.cpp @@ -256,10 +256,9 @@ void sfz::Synth::handleEffectOpcodes(const std::vector& members) // create the effect and add it EffectBus& bus = getOrCreateBus(busIndex); - Effect* fx = effectFactory.makeEffect(members); - bus.addEffect(std::unique_ptr(fx)); - + auto fx = effectFactory.makeEffect(members); fx->init(sampleRate); + bus.addEffect(std::move(fx)); } void addEndpointsToVelocityCurve(sfz::Region& region) diff --git a/src/sfizz/effects/Lofi.cpp b/src/sfizz/effects/Lofi.cpp index 9c4e1f7e..758df404 100644 --- a/src/sfizz/effects/Lofi.cpp +++ b/src/sfizz/effects/Lofi.cpp @@ -72,9 +72,9 @@ namespace fx { } } - Effect* Lofi::makeInstance(absl::Span members) + std::unique_ptr Lofi::makeInstance(absl::Span members) { - std::unique_ptr fx { new Lofi }; + auto fx = std::make_unique(); for (const Opcode& opcode : members) { switch (opcode.lettersOnlyHash) { @@ -87,7 +87,7 @@ namespace fx { } } - return fx.release(); + return fx; } /// diff --git a/src/sfizz/effects/Lofi.h b/src/sfizz/effects/Lofi.h index 8bfda5d8..65b5e54e 100644 --- a/src/sfizz/effects/Lofi.h +++ b/src/sfizz/effects/Lofi.h @@ -34,7 +34,7 @@ namespace fx { /** * @brief Instantiates given the contents of the block. */ - static Effect* makeInstance(absl::Span members); + static std::unique_ptr makeInstance(absl::Span members); private: float _bitred_depth = 0; From 70bc12f4849f86da25ea80727af7c5f7713da695 Mon Sep 17 00:00:00 2001 From: Paul Fd Date: Thu, 5 Mar 2020 16:59:35 +0100 Subject: [PATCH 16/93] use `find_if` --- src/sfizz/Effects.cpp | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/sfizz/Effects.cpp b/src/sfizz/Effects.cpp index 5bfddf68..eb9746f6 100644 --- a/src/sfizz/Effects.cpp +++ b/src/sfizz/Effects.cpp @@ -45,12 +45,8 @@ std::unique_ptr EffectFactory::makeEffect(absl::Span membe absl::string_view type = opcode->value; - auto it = _entries.begin(); - auto end = _entries.end(); - for (; it != end && it->name != type; ++it) - ; - - if (it == end) { + const auto it = absl::c_find_if(_entries, [&](auto&& entry) { return entry.name == type; }); + if (it == _entries.end()) { DBG("Unsupported effect type: " << type); return std::make_unique(); } From 92e58316d1eee286a52fb5c7a4b50103b5703e09 Mon Sep 17 00:00:00 2001 From: Paul Fd Date: Thu, 5 Mar 2020 17:11:48 +0100 Subject: [PATCH 17/93] Const and cosmetics --- src/sfizz/Effects.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sfizz/Effects.cpp b/src/sfizz/Effects.cpp index eb9746f6..d9396516 100644 --- a/src/sfizz/Effects.cpp +++ b/src/sfizz/Effects.cpp @@ -43,7 +43,7 @@ std::unique_ptr EffectFactory::makeEffect(absl::Span membe return std::make_unique(); } - absl::string_view type = opcode->value; + const absl::string_view type = opcode->value; const auto it = absl::c_find_if(_entries, [&](auto&& entry) { return entry.name == type; }); if (it == _entries.end()) { @@ -86,7 +86,7 @@ void EffectBus::addToInputs(const float* const addInput[], float addGain, unsign return; for (unsigned c = 0; c < EffectChannels; ++c) { - absl::Span addIn(addInput[c], nframes); + absl::Span addIn{ addInput[c], nframes }; sfz::multiplyAdd(addGain, addIn, _inputs.getSpan(c)); } } From 3ce0dedc45fb1894d7cca4a07ecf9d3eef57c160 Mon Sep 17 00:00:00 2001 From: Paul Fd Date: Thu, 5 Mar 2020 17:27:59 +0100 Subject: [PATCH 18/93] Mostly cosmetics --- src/sfizz/effects/Lofi.cpp | 30 +++++++++++++----------------- 1 file changed, 13 insertions(+), 17 deletions(-) diff --git a/src/sfizz/effects/Lofi.cpp b/src/sfizz/effects/Lofi.cpp index 758df404..3dbcec62 100644 --- a/src/sfizz/effects/Lofi.cpp +++ b/src/sfizz/effects/Lofi.cpp @@ -107,14 +107,12 @@ namespace fx { void Lofi::Bitred::setDepth(float depth) { - fDepth = std::max(0.0f, std::min(100.0f, depth)); + fDepth = clamp(depth, 0.0f, 100.0f); } void Lofi::Bitred::process(const float* in, float* out, uint32_t nframes) { - float depth = fDepth; - - if (depth == 0) { + if (fDepth == 0) { if (in != out) std::memcpy(out, in, nframes * sizeof(float)); clear(); @@ -124,12 +122,13 @@ namespace fx { float lastValue = fLastValue; hiir::Downsampler2xFpu<12>& downsampler2x = fDownsampler2x; - float steps = (1.0f + (100.0f - depth)) * 0.75f; + const float steps = (1.0f + (100.0f - fDepth)) * 0.75f; + const float invSteps = 1.0f / steps; for (uint32_t i = 0; i < nframes; ++i) { float x = in[i]; - float y = std::copysign((int)(0.5f + std::fabs(x * steps)), x) * (1 / steps); + float y = std::copysign((int)(0.5f + std::fabs(x * steps)), x) * invSteps; float y2x[2]; y2x[0] = (y != lastValue) ? (0.5f * (y + lastValue)) : y; @@ -162,27 +161,24 @@ namespace fx { void Lofi::Decim::setDepth(float depth) { - fDepth = std::max(0.0f, std::min(100.0f, depth)); + fDepth = clamp(depth, 0.0f, 100.0f); } void Lofi::Decim::process(const float* in, float* out, uint32_t nframes) { - float depth = fDepth; - - if (depth == 0) { + if (fDepth == 0) { if (in != out) std::memcpy(out, in, nframes * sizeof(float)); clear(); return; } - float dt; - { + const float dt = [this]() { // exponential curve fit - float a = 1.289079e+00, b = 1.384141e-01, c = 1.313298e-04; - dt = std::pow(a, b * depth) * c - c; - dt = fSampleTime / dt; - } + const float a = 1.289079e+00, b = 1.384141e-01, c = 1.313298e-04; + const float denom = std::pow(a, b * fDepth) * c - c; + return fSampleTime / denom; + }(); float phase = fPhase; float lastValue = fLastValue; @@ -193,7 +189,7 @@ namespace fx { phase += dt; float y = (phase > 1.0f) ? x : lastValue; - phase -= (int)phase; + phase -= static_cast(phase); float y2x[2]; y2x[0] = (y != lastValue) ? (0.5f * (y + lastValue)) : y; From 7794861ce13041745cb1e815042c604d824aa857 Mon Sep 17 00:00:00 2001 From: Paul Fd Date: Thu, 5 Mar 2020 18:08:32 +0100 Subject: [PATCH 19/93] Build the effect bus in the vector --- src/sfizz/Synth.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/sfizz/Synth.cpp b/src/sfizz/Synth.cpp index 7c2c0a02..12db2223 100644 --- a/src/sfizz/Synth.cpp +++ b/src/sfizz/Synth.cpp @@ -123,9 +123,8 @@ void sfz::Synth::clear() list.clear(); regions.clear(); effectBuses.clear(); - EffectBus* mainBus = new EffectBus; - effectBuses.emplace_back(mainBus); - mainBus->setGainToMain(1.0); + effectBuses.emplace_back(new EffectBus); + effectBuses[0]->setGainToMain(1.0); resources.filePool.clear(); resources.logger.clear(); numGroups = 0; From 49a182d90f3d97fd98a0c08373d55eeccfae1e0a Mon Sep 17 00:00:00 2001 From: Paul Fd Date: Thu, 5 Mar 2020 18:23:50 +0100 Subject: [PATCH 20/93] Add effects in the callback log --- src/sfizz/Logger.cpp | 3 ++- src/sfizz/Logger.h | 1 + src/sfizz/Synth.cpp | 43 +++++++++++++++++++++++++------------------ 3 files changed, 28 insertions(+), 19 deletions(-) diff --git a/src/sfizz/Logger.cpp b/src/sfizz/Logger.cpp index 35706167..53bfa66b 100644 --- a/src/sfizz/Logger.cpp +++ b/src/sfizz/Logger.cpp @@ -83,7 +83,7 @@ sfz::Logger::~Logger() fs::path callbackLogPath{ fs::current_path() / callbackLogFilename.str() }; std::cout << "Logging " << callbackTimes.size() << " callback times to " << callbackLogPath.filename() << '\n'; std::ofstream callbackLogFile { callbackLogPath.string() }; - callbackLogFile << "Dispatch,RenderMethod,Data,Amplitude,Filters,Panning,NumVoices,NumSamples" << '\n'; + callbackLogFile << "Dispatch,RenderMethod,Data,Amplitude,Filters,Panning,Effects,NumVoices,NumSamples" << '\n'; for (auto& time: callbackTimes) callbackLogFile << time.breakdown.dispatch.count() << ',' << time.breakdown.renderMethod.count() << ',' @@ -91,6 +91,7 @@ sfz::Logger::~Logger() << time.breakdown.amplitude.count() << ',' << time.breakdown.filters.count() << ',' << time.breakdown.panning.count() << ',' + << time.breakdown.effects.count() << ',' << time.numVoices << ',' << time.numSamples << '\n'; } diff --git a/src/sfizz/Logger.h b/src/sfizz/Logger.h index 9861ba87..449c19d9 100644 --- a/src/sfizz/Logger.h +++ b/src/sfizz/Logger.h @@ -61,6 +61,7 @@ struct CallbackBreakdown Duration amplitude { 0 }; Duration filters { 0 }; Duration panning { 0 }; + Duration effects { 0 }; }; struct CallbackTime diff --git a/src/sfizz/Synth.cpp b/src/sfizz/Synth.cpp index 12db2223..554c3894 100644 --- a/src/sfizz/Synth.cpp +++ b/src/sfizz/Synth.cpp @@ -501,14 +501,16 @@ void sfz::Synth::renderBlock(AudioSpan buffer) noexcept auto temp = AudioSpan(tempBuffer).first(numFrames); auto tempMixNode = AudioSpan(tempMixNodeBuffer).first(numFrames); - // Prepare the effect inputs. They are mixes of per-region outputs. - for (size_t i = 0; i < numEffectBuses; ++i) { - if (EffectBus* bus = effectBuses[i].get()) - bus->clearInputs(numFrames); + CallbackBreakdown callbackBreakdown; + + { // Prepare the effect inputs. They are mixes of per-region outputs. + ScopedTiming logger { callbackBreakdown.effects }; + for (size_t i = 0; i < numEffectBuses; ++i) { + if (EffectBus* bus = effectBuses[i].get()) + bus->clearInputs(numFrames); + } } - // - CallbackBreakdown callbackBreakdown; int numActiveVoices { 0 }; { // Main render block ScopedTiming logger { callbackBreakdown.renderMethod }; @@ -525,11 +527,13 @@ void sfz::Synth::renderBlock(AudioSpan buffer) noexcept numActiveVoices++; voice->renderBlock(temp); - // Add the output into the effects linked to this region - for (size_t i = 0; i < numEffectBuses; ++i) { - if (EffectBus* bus = effectBuses[i].get()) { - float addGain = region->getGainToEffectBus(i); - bus->addToInputs(temp, addGain, numFrames); + { // Add the output into the effects linked to this region + ScopedTiming logger { callbackBreakdown.renderMethod, ScopedTiming::Operation::addToDuration }; + for (size_t i = 0; i < numEffectBuses; ++i) { + if (EffectBus* bus = effectBuses[i].get()) { + float addGain = region->getGainToEffectBus(i); + bus->addToInputs(temp, addGain, numFrames); + } } } @@ -540,13 +544,16 @@ void sfz::Synth::renderBlock(AudioSpan buffer) noexcept } } - // Apply effect buses - // -- note(jpc) there is always a "main" bus which is initially empty. - // without any , the signal is just going to flow through it. - for (size_t i = 0; i < numEffectBuses; ++i) { - if (EffectBus* bus = effectBuses[i].get()) { - bus->process(numFrames); - bus->mixOutputsTo(buffer, tempMixNode, numFrames); + { // Apply effect buses + // -- note(jpc) there is always a "main" bus which is initially empty. + // without any , the signal is just going to flow through it. + ScopedTiming logger { callbackBreakdown.renderMethod, ScopedTiming::Operation::addToDuration }; + + for (size_t i = 0; i < numEffectBuses; ++i) { + if (EffectBus* bus = effectBuses[i].get()) { + bus->process(numFrames); + bus->mixOutputsTo(buffer, tempMixNode, numFrames); + } } } From 9d3364889b75374be7ff8ae0f63609e4d0ff7a7d Mon Sep 17 00:00:00 2001 From: Paul Fd Date: Thu, 5 Mar 2020 19:02:45 +0100 Subject: [PATCH 21/93] Adapt the block size of the effect bus to the one of the Synth --- src/sfizz/Effects.cpp | 8 +++++++- src/sfizz/Effects.h | 9 ++++++++- src/sfizz/Synth.cpp | 4 ++++ 3 files changed, 19 insertions(+), 2 deletions(-) diff --git a/src/sfizz/Effects.cpp b/src/sfizz/Effects.cpp index d9396516..c9fad083 100644 --- a/src/sfizz/Effects.cpp +++ b/src/sfizz/Effects.cpp @@ -124,10 +124,16 @@ void EffectBus::mixOutputsTo(float* const mainOutput[], float* const mixOutput[] const float gainToMix = _gainToMix; for (unsigned c = 0; c < EffectChannels; ++c) { - absl::Span fxOut = _outputs.getConstSpan(c); + auto fxOut = _outputs.getConstSpan(c); sfz::multiplyAdd(gainToMain, fxOut, absl::Span(mainOutput[c], nframes)); sfz::multiplyAdd(gainToMix, fxOut, absl::Span(mixOutput[c], nframes)); } } +void EffectBus::setSamplesPerBlock(int samplesPerBlock) noexcept +{ + _inputs.resize(samplesPerBlock); + _outputs.resize(samplesPerBlock); +} + } // namespace sfz diff --git a/src/sfizz/Effects.h b/src/sfizz/Effects.h index ff159d3c..0f7091fc 100644 --- a/src/sfizz/Effects.h +++ b/src/sfizz/Effects.h @@ -136,10 +136,17 @@ public: */ void mixOutputsTo(float* const mainOutput[], float* const mixOutput[], unsigned nframes); + /** + * @brief Sets the maximum number of frames to render at a time. The actual value can be lower + * but should never be higher. + * + */ + void setSamplesPerBlock(int samplesPerBlock) noexcept; + private: std::vector> _effects; AudioBuffer _inputs { EffectChannels, config::defaultSamplesPerBlock }; - AudioBuffer _outputs { EffectChannels, config::defaultSamplesPerBlock }; + AudioBuffer _outputs { EffectChannels, config::defaultSamplesPerBlock }; float _gainToMain = 0.0; float _gainToMix = 0.0; }; diff --git a/src/sfizz/Synth.cpp b/src/sfizz/Synth.cpp index 554c3894..92cfa552 100644 --- a/src/sfizz/Synth.cpp +++ b/src/sfizz/Synth.cpp @@ -125,6 +125,8 @@ void sfz::Synth::clear() effectBuses.clear(); effectBuses.emplace_back(new EffectBus); effectBuses[0]->setGainToMain(1.0); + effectBuses[0]->setSamplesPerBlock(samplesPerBlock); + effectBuses[0]->init(sampleRate); resources.filePool.clear(); resources.logger.clear(); numGroups = 0; @@ -463,6 +465,8 @@ void sfz::Synth::setSamplesPerBlock(int samplesPerBlock) noexcept this->tempMixNodeBuffer.resize(samplesPerBlock); for (auto& voice : voices) voice->setSamplesPerBlock(samplesPerBlock); + for (auto& bus: effectBuses) + bus->setSamplesPerBlock(samplesPerBlock); } void sfz::Synth::setSampleRate(float sampleRate) noexcept From dc9acbb3de572e618ad7340986d4c3b1beb98286 Mon Sep 17 00:00:00 2001 From: Paul Fd Date: Thu, 5 Mar 2020 19:03:08 +0100 Subject: [PATCH 22/93] Corrected tests --- tests/SynthT.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/SynthT.cpp b/tests/SynthT.cpp index e5bca7c9..072206c0 100644 --- a/tests/SynthT.cpp +++ b/tests/SynthT.cpp @@ -77,6 +77,7 @@ TEST_CASE("[Synth] Check that we can change the size of the preload before and a { sfz::Synth synth; synth.setPreloadSize(512); + synth.setSamplesPerBlock(blockSize); sfz::AudioBuffer buffer { 2, blockSize }; synth.loadSfzFile(fs::current_path() / "tests/TestFiles/groups_avl.sfz"); synth.setPreloadSize(1024); @@ -92,6 +93,7 @@ TEST_CASE("[Synth] Check that we can change the oversampling factor before and a { sfz::Synth synth; synth.setOversamplingFactor(sfz::Oversampling::x2); + synth.setSamplesPerBlock(blockSize); sfz::AudioBuffer buffer { 2, blockSize }; synth.loadSfzFile(fs::current_path() / "tests/TestFiles/groups_avl.sfz"); synth.setOversamplingFactor(sfz::Oversampling::x4); From dcc8a58377c97fdae8ee3c26f3d83090e30d923a Mon Sep 17 00:00:00 2001 From: Paul Fd Date: Thu, 5 Mar 2020 23:07:00 +0100 Subject: [PATCH 23/93] Removed the downsampler reference --- src/sfizz/effects/Lofi.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/sfizz/effects/Lofi.cpp b/src/sfizz/effects/Lofi.cpp index 3dbcec62..7c1c95d4 100644 --- a/src/sfizz/effects/Lofi.cpp +++ b/src/sfizz/effects/Lofi.cpp @@ -120,7 +120,6 @@ namespace fx { } float lastValue = fLastValue; - hiir::Downsampler2xFpu<12>& downsampler2x = fDownsampler2x; const float steps = (1.0f + (100.0f - fDepth)) * 0.75f; const float invSteps = 1.0f / steps; @@ -136,7 +135,7 @@ namespace fx { lastValue = y; - y = downsampler2x.process_sample(y2x); + y = fDownsampler2x.process_sample(y2x); out[i] = y; } @@ -182,7 +181,6 @@ namespace fx { float phase = fPhase; float lastValue = fLastValue; - hiir::Downsampler2xFpu<12>& downsampler2x = fDownsampler2x; for (uint32_t i = 0; i < nframes; ++i) { float x = in[i]; @@ -197,7 +195,7 @@ namespace fx { lastValue = y; - y = downsampler2x.process_sample(y2x); + y = fDownsampler2x.process_sample(y2x); out[i] = y; } From 2e6402bae0c2d7753110d51cb5492ac4703499ca Mon Sep 17 00:00:00 2001 From: Paul Fd Date: Thu, 5 Mar 2020 23:34:10 +0100 Subject: [PATCH 24/93] Corrected formatting --- benchmarks/BM_multiplyAddFixedGain.cpp | 68 ++++++++++++++------------ src/sfizz/AudioSpan.h | 4 +- src/sfizz/Effects.cpp | 2 +- src/sfizz/Effects.h | 4 +- src/sfizz/Region.cpp | 22 ++++----- src/sfizz/Synth.cpp | 19 +++---- 6 files changed, 61 insertions(+), 58 deletions(-) diff --git a/benchmarks/BM_multiplyAddFixedGain.cpp b/benchmarks/BM_multiplyAddFixedGain.cpp index b484389d..919516b2 100644 --- a/benchmarks/BM_multiplyAddFixedGain.cpp +++ b/benchmarks/BM_multiplyAddFixedGain.cpp @@ -14,58 +14,64 @@ class MultiplyAddFixedGain : public benchmark::Fixture { public: - void SetUp(const ::benchmark::State& state) { - std::random_device rd { }; - std::mt19937 gen { rd() }; - std::uniform_real_distribution dist { 0, 1 }; - input = std::vector(state.range(0)); - output = std::vector(state.range(0)); - gain = dist(gen); - std::fill(output.begin(), output.end(), 1.0f ); - std::generate(input.begin(), input.end(), [&]() { return dist(gen); }); - } + void SetUp(const ::benchmark::State& state) + { + std::random_device rd {}; + std::mt19937 gen { rd() }; + std::uniform_real_distribution dist { 0, 1 }; + input = std::vector(state.range(0)); + output = std::vector(state.range(0)); + gain = dist(gen); + std::fill(output.begin(), output.end(), 1.0f); + std::generate(input.begin(), input.end(), [&]() { return dist(gen); }); + } - void TearDown(const ::benchmark::State& state [[maybe_unused]]) { + void TearDown(const ::benchmark::State& state [[maybe_unused]]) + { + } - } - - float gain = {}; - std::vector input; - std::vector output; + float gain = {}; + std::vector input; + std::vector output; }; -BENCHMARK_DEFINE_F(MultiplyAddFixedGain, Straight)(benchmark::State& state) { - for (auto _ : state) - { +BENCHMARK_DEFINE_F(MultiplyAddFixedGain, Straight) +(benchmark::State& state) +{ + for (auto _ : state) { for (int i = 0; i < state.range(0); ++i) output[i] += gain * input[i]; } } -BENCHMARK_DEFINE_F(MultiplyAddFixedGain, Scalar)(benchmark::State& state) { - for (auto _ : state) - { +BENCHMARK_DEFINE_F(MultiplyAddFixedGain, Scalar) +(benchmark::State& state) +{ + for (auto _ : state) { sfz::multiplyAdd(gain, input, absl::MakeSpan(output)); } } -BENCHMARK_DEFINE_F(MultiplyAddFixedGain, SIMD)(benchmark::State& state) { - for (auto _ : state) - { +BENCHMARK_DEFINE_F(MultiplyAddFixedGain, SIMD) +(benchmark::State& state) +{ + for (auto _ : state) { sfz::multiplyAdd(gain, input, absl::MakeSpan(output)); } } -BENCHMARK_DEFINE_F(MultiplyAddFixedGain, Scalar_Unaligned)(benchmark::State& state) { - for (auto _ : state) - { +BENCHMARK_DEFINE_F(MultiplyAddFixedGain, Scalar_Unaligned) +(benchmark::State& state) +{ + for (auto _ : state) { sfz::multiplyAdd(gain, absl::MakeSpan(input).subspan(1), absl::MakeSpan(output).subspan(1)); } } -BENCHMARK_DEFINE_F(MultiplyAddFixedGain, SIMD_Unaligned)(benchmark::State& state) { - for (auto _ : state) - { +BENCHMARK_DEFINE_F(MultiplyAddFixedGain, SIMD_Unaligned) +(benchmark::State& state) +{ + for (auto _ : state) { sfz::multiplyAdd(gain, absl::MakeSpan(input).subspan(1), absl::MakeSpan(output).subspan(1)); } } diff --git a/src/sfizz/AudioSpan.h b/src/sfizz/AudioSpan.h index c608848c..f1bd0f91 100644 --- a/src/sfizz/AudioSpan.h +++ b/src/sfizz/AudioSpan.h @@ -212,7 +212,7 @@ public: /** * @brief Convert implicitly to a pointer of channels */ - operator const float* const *() const noexcept + operator const float* const*() const noexcept { return spans.data(); } @@ -220,7 +220,7 @@ public: /** * @brief Convert implicitly to a pointer of channels */ - operator float* const *() noexcept + operator float* const*() noexcept { return spans.data(); } diff --git a/src/sfizz/Effects.cpp b/src/sfizz/Effects.cpp index c9fad083..fb77acf2 100644 --- a/src/sfizz/Effects.cpp +++ b/src/sfizz/Effects.cpp @@ -86,7 +86,7 @@ void EffectBus::addToInputs(const float* const addInput[], float addGain, unsign return; for (unsigned c = 0; c < EffectChannels; ++c) { - absl::Span addIn{ addInput[c], nframes }; + absl::Span addIn { addInput[c], nframes }; sfz::multiplyAdd(addGain, addIn, _inputs.getSpan(c)); } } diff --git a/src/sfizz/Effects.h b/src/sfizz/Effects.h index 0f7091fc..1d0947ed 100644 --- a/src/sfizz/Effects.h +++ b/src/sfizz/Effects.h @@ -46,7 +46,7 @@ public: @brief Type of the factory function used to instantiate an effect given the contents of the block */ - typedef std::unique_ptr (MakeInstance)(absl::Span members); + typedef std::unique_ptr(MakeInstance)(absl::Span members); }; /** @@ -146,7 +146,7 @@ public: private: std::vector> _effects; AudioBuffer _inputs { EffectChannels, config::defaultSamplesPerBlock }; - AudioBuffer _outputs { EffectChannels, config::defaultSamplesPerBlock }; + AudioBuffer _outputs { EffectChannels, config::defaultSamplesPerBlock }; float _gainToMain = 0.0; float _gainToMix = 0.0; }; diff --git a/src/sfizz/Region.cpp b/src/sfizz/Region.cpp index 35475263..d0f2e706 100644 --- a/src/sfizz/Region.cpp +++ b/src/sfizz/Region.cpp @@ -734,18 +734,18 @@ bool sfz::Region::parseOpcode(const Opcode& opcode) break; case hash("effect"): // effect& - { - const auto effectNumber = opcode.backParameter(); - if (!effectNumber || *effectNumber < 1 || *effectNumber > config::maxEffectBuses) - break; - auto value = readOpcode(opcode.value, {0, 100}); - if (!value) - break; - if (static_cast(*effectNumber + 1) > gainToEffect.size()) - gainToEffect.resize(*effectNumber + 1); - gainToEffect[*effectNumber] = *value / 100; + { + const auto effectNumber = opcode.backParameter(); + if (!effectNumber || *effectNumber < 1 || *effectNumber > config::maxEffectBuses) break; - } + auto value = readOpcode(opcode.value, { 0, 100 }); + if (!value) + break; + if (static_cast(*effectNumber + 1) > gainToEffect.size()) + gainToEffect.resize(*effectNumber + 1); + gainToEffect[*effectNumber] = *value / 100; + break; + } // Ignored opcodes case hash("hichan"): diff --git a/src/sfizz/Synth.cpp b/src/sfizz/Synth.cpp index 92cfa552..a6a6482a 100644 --- a/src/sfizz/Synth.cpp +++ b/src/sfizz/Synth.cpp @@ -201,7 +201,7 @@ void sfz::Synth::handleEffectOpcodes(const std::vector& members) auto getOrCreateBus = [this](unsigned index) -> EffectBus& { if (index + 1 > effectBuses.size()) effectBuses.resize(index + 1); - EffectBusPtr &slot = effectBuses[index]; + EffectBusPtr& slot = effectBuses[index]; if (!slot) slot.reset(new EffectBus); return *slot; @@ -213,10 +213,10 @@ void sfz::Synth::handleEffectOpcodes(const std::vector& members) busName = opcode.value; break; - // note(jpc): gain opcodes are linear volumes in % units + // note(jpc): gain opcodes are linear volumes in % units case hash("directtomain"): - if (auto valueOpt = readOpcode(opcode.value, {0, 100})) + if (auto valueOpt = readOpcode(opcode.value, { 0, 100 })) getOrCreateBus(0).setGainToMain(*valueOpt / 100); break; @@ -225,7 +225,7 @@ void sfz::Synth::handleEffectOpcodes(const std::vector& members) unsigned number = *numberOpt; if (number < 1 || number > config::maxEffectBuses) break; - if (auto valueOpt = readOpcode(opcode.value, {0, 100})) + if (auto valueOpt = readOpcode(opcode.value, { 0, 100 })) getOrCreateBus(number).setGainToMain(*valueOpt / 100); } break; @@ -235,7 +235,7 @@ void sfz::Synth::handleEffectOpcodes(const std::vector& members) unsigned number = *numberOpt; if (number < 1 || number > config::maxEffectBuses) break; - if (auto valueOpt = readOpcode(opcode.value, {0, 100})) + if (auto valueOpt = readOpcode(opcode.value, { 0, 100 })) getOrCreateBus(number).setGainToMix(*valueOpt / 100); } break; @@ -245,12 +245,9 @@ void sfz::Synth::handleEffectOpcodes(const std::vector& members) unsigned busIndex; if (busName.empty() || busName == "main") busIndex = 0; - else if (busName.size() > 2 && busName.substr(0, 2) == "fx" && - absl::SimpleAtoi(busName.substr(2), &busIndex) && - busIndex >= 1 && busIndex <= config::maxEffectBuses) { + else if (busName.size() > 2 && busName.substr(0, 2) == "fx" && absl::SimpleAtoi(busName.substr(2), &busIndex) && busIndex >= 1 && busIndex <= config::maxEffectBuses) { // an effect bus fxN, with N usually in [1,4] - } - else { + } else { DBG("Unsupported effect bus: " << busName); return; } @@ -465,7 +462,7 @@ void sfz::Synth::setSamplesPerBlock(int samplesPerBlock) noexcept this->tempMixNodeBuffer.resize(samplesPerBlock); for (auto& voice : voices) voice->setSamplesPerBlock(samplesPerBlock); - for (auto& bus: effectBuses) + for (auto& bus : effectBuses) bus->setSamplesPerBlock(samplesPerBlock); } From 4c83ca3a7895b8991419e6ee020262836a8e575c Mon Sep 17 00:00:00 2001 From: Paul Fd Date: Thu, 5 Mar 2020 23:35:15 +0100 Subject: [PATCH 25/93] Put the timings in their proper field --- src/sfizz/Synth.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sfizz/Synth.cpp b/src/sfizz/Synth.cpp index a6a6482a..a10fe5f5 100644 --- a/src/sfizz/Synth.cpp +++ b/src/sfizz/Synth.cpp @@ -529,7 +529,7 @@ void sfz::Synth::renderBlock(AudioSpan buffer) noexcept voice->renderBlock(temp); { // Add the output into the effects linked to this region - ScopedTiming logger { callbackBreakdown.renderMethod, ScopedTiming::Operation::addToDuration }; + ScopedTiming logger { callbackBreakdown.effects, ScopedTiming::Operation::addToDuration }; for (size_t i = 0; i < numEffectBuses; ++i) { if (EffectBus* bus = effectBuses[i].get()) { float addGain = region->getGainToEffectBus(i); @@ -548,7 +548,7 @@ void sfz::Synth::renderBlock(AudioSpan buffer) noexcept { // Apply effect buses // -- note(jpc) there is always a "main" bus which is initially empty. // without any , the signal is just going to flow through it. - ScopedTiming logger { callbackBreakdown.renderMethod, ScopedTiming::Operation::addToDuration }; + ScopedTiming logger { callbackBreakdown.effects, ScopedTiming::Operation::addToDuration }; for (size_t i = 0; i < numEffectBuses; ++i) { if (EffectBus* bus = effectBuses[i].get()) { From 9d56e5560cdb845fce4b6c7815c0b64b4675300a Mon Sep 17 00:00:00 2001 From: Paul Fd Date: Fri, 6 Mar 2020 00:22:09 +0100 Subject: [PATCH 26/93] No need to wrap in a unique_ptr --- src/sfizz/Effects.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sfizz/Effects.cpp b/src/sfizz/Effects.cpp index fb77acf2..9ae699fb 100644 --- a/src/sfizz/Effects.cpp +++ b/src/sfizz/Effects.cpp @@ -51,7 +51,7 @@ std::unique_ptr EffectFactory::makeEffect(absl::Span membe return std::make_unique(); } - auto fx = std::unique_ptr(it->make(members)); + auto fx = it->make(members); if (!fx) { DBG("Could not instantiate effect of type: " << type); return std::make_unique(); From 090a5995db3246e0c6f7e94f339b4cbb0ca739e9 Mon Sep 17 00:00:00 2001 From: Paul Fd Date: Fri, 6 Mar 2020 11:07:09 +0100 Subject: [PATCH 27/93] Add a view on the effect bus --- src/sfizz/Synth.cpp | 5 +++++ src/sfizz/Synth.h | 8 ++++++++ 2 files changed, 13 insertions(+) diff --git a/src/sfizz/Synth.cpp b/src/sfizz/Synth.cpp index a10fe5f5..eeedafe1 100644 --- a/src/sfizz/Synth.cpp +++ b/src/sfizz/Synth.cpp @@ -810,6 +810,11 @@ const sfz::Region* sfz::Synth::getRegionView(int idx) const noexcept return (size_t)idx < regions.size() ? regions[idx].get() : nullptr; } +const sfz::EffectBus* sfz::Synth::getEffectBusView(int idx) const noexcept +{ + return (size_t)idx < effectBuses.size() ? effectBuses[idx].get() : nullptr; +} + const sfz::Voice* sfz::Synth::getVoiceView(int idx) const noexcept { return (size_t)idx < voices.size() ? voices[idx].get() : nullptr; diff --git a/src/sfizz/Synth.h b/src/sfizz/Synth.h index 3f94262d..616cf60d 100644 --- a/src/sfizz/Synth.h +++ b/src/sfizz/Synth.h @@ -132,6 +132,14 @@ public: * @return const Region* */ const Voice* getVoiceView(int idx) const noexcept; + /** + * @brief Get a raw view into a specific voice. This is mostly used + * for testing. + * + * @param idx + * @return const Region* + */ + const EffectBus* getEffectBusView(int idx) const noexcept; /** * @brief Get a list of unknown opcodes. The lifetime of the * string views in the code are linked to the currently loaded From 1ac68cc3d2d082555b26c5c5d2a229b633b779b8 Mon Sep 17 00:00:00 2001 From: Paul Fd Date: Fri, 6 Mar 2020 11:07:56 +0100 Subject: [PATCH 28/93] Add introspection in the busses --- src/sfizz/Effects.h | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/sfizz/Effects.h b/src/sfizz/Effects.h index 1d0947ed..17ded9de 100644 --- a/src/sfizz/Effects.h +++ b/src/sfizz/Effects.h @@ -106,6 +106,20 @@ public: */ void setGainToMix(float gain) { _gainToMix = gain; } + /** + * @brief Returns the gain for the main out + * + * @return float + */ + float gainToMain() const { return _gainToMain; } + + /** + * @brief Returns the gain for the mix out + * + * @return float + */ + float gainToMix() const { return _gainToMix; } + /** @brief Resets the input buffers to zero. */ @@ -143,6 +157,12 @@ public: */ void setSamplesPerBlock(int samplesPerBlock) noexcept; + /** + * @brief Return the number of effects in the bus + * + * @return size_t + */ + size_t numEffects() const noexcept; private: std::vector> _effects; AudioBuffer _inputs { EffectChannels, config::defaultSamplesPerBlock }; From 235069fd27a07076e02bc4ac2cdcb6cd5b7d397c Mon Sep 17 00:00:00 2001 From: Paul Fd Date: Fri, 6 Mar 2020 11:09:27 +0100 Subject: [PATCH 29/93] Change init to setSampleRate andset the blockSize and sampleRate of new busses --- src/sfizz/Effects.cpp | 8 ++++++-- src/sfizz/Effects.h | 4 ++-- src/sfizz/Synth.cpp | 17 ++++++++++------- src/sfizz/effects/Lofi.cpp | 2 +- src/sfizz/effects/Lofi.h | 2 +- src/sfizz/effects/Nothing.cpp | 2 +- src/sfizz/effects/Nothing.h | 2 +- 7 files changed, 22 insertions(+), 15 deletions(-) diff --git a/src/sfizz/Effects.cpp b/src/sfizz/Effects.cpp index 9ae699fb..c31f245e 100644 --- a/src/sfizz/Effects.cpp +++ b/src/sfizz/Effects.cpp @@ -91,10 +91,10 @@ void EffectBus::addToInputs(const float* const addInput[], float addGain, unsign } } -void EffectBus::init(double sampleRate) +void EffectBus::setSampleRate(double sampleRate) { for (const auto& effectPtr : _effects) - effectPtr->init(sampleRate); + effectPtr->setSampleRate(sampleRate); } void EffectBus::clear() @@ -130,6 +130,10 @@ void EffectBus::mixOutputsTo(float* const mainOutput[], float* const mixOutput[] } } +size_t EffectBus::numEffects() const noexcept +{ + return _effects.size(); +} void EffectBus::setSamplesPerBlock(int samplesPerBlock) noexcept { _inputs.resize(samplesPerBlock); diff --git a/src/sfizz/Effects.h b/src/sfizz/Effects.h index 17ded9de..0ea8d195 100644 --- a/src/sfizz/Effects.h +++ b/src/sfizz/Effects.h @@ -30,7 +30,7 @@ public: /** @brief Initializes with the given sample rate. */ - virtual void init(double sampleRate) = 0; + virtual void setSampleRate(double sampleRate) = 0; /** @brief Reset the state to initial. @@ -133,7 +133,7 @@ public: /** @brief Initializes all effects in the bus with the given sample rate. */ - void init(double sampleRate); + void setSampleRate(double sampleRate); /** @brief Resets the state of all effects in the bus. diff --git a/src/sfizz/Synth.cpp b/src/sfizz/Synth.cpp index eeedafe1..efefd1e7 100644 --- a/src/sfizz/Synth.cpp +++ b/src/sfizz/Synth.cpp @@ -126,7 +126,7 @@ void sfz::Synth::clear() effectBuses.emplace_back(new EffectBus); effectBuses[0]->setGainToMain(1.0); effectBuses[0]->setSamplesPerBlock(samplesPerBlock); - effectBuses[0]->init(sampleRate); + effectBuses[0]->setSampleRate(sampleRate); resources.filePool.clear(); resources.logger.clear(); numGroups = 0; @@ -201,10 +201,13 @@ void sfz::Synth::handleEffectOpcodes(const std::vector& members) auto getOrCreateBus = [this](unsigned index) -> EffectBus& { if (index + 1 > effectBuses.size()) effectBuses.resize(index + 1); - EffectBusPtr& slot = effectBuses[index]; - if (!slot) - slot.reset(new EffectBus); - return *slot; + EffectBusPtr& bus = effectBuses[index]; + if (!bus) { + bus.reset(new EffectBus); + bus->setSampleRate(sampleRate); + bus->setSamplesPerBlock(samplesPerBlock); + } + return *bus; }; for (const Opcode& opcode : members) { @@ -255,7 +258,7 @@ void sfz::Synth::handleEffectOpcodes(const std::vector& members) // create the effect and add it EffectBus& bus = getOrCreateBus(busIndex); auto fx = effectFactory.makeEffect(members); - fx->init(sampleRate); + fx->setSampleRate(sampleRate); bus.addEffect(std::move(fx)); } @@ -482,7 +485,7 @@ void sfz::Synth::setSampleRate(float sampleRate) noexcept for (size_t i = 0, n = effectBuses.size(); i < n; ++i) { if (EffectBus* bus = effectBuses[i].get()) - bus->init(sampleRate); + bus->setSampleRate(sampleRate); } } diff --git a/src/sfizz/effects/Lofi.cpp b/src/sfizz/effects/Lofi.cpp index 7c1c95d4..fdd511d1 100644 --- a/src/sfizz/effects/Lofi.cpp +++ b/src/sfizz/effects/Lofi.cpp @@ -45,7 +45,7 @@ namespace sfz { namespace fx { - void Lofi::init(double sampleRate) + void Lofi::setSampleRate(double sampleRate) { for (unsigned c = 0; c < EffectChannels; ++c) { _bitred[c].init(sampleRate); diff --git a/src/sfizz/effects/Lofi.h b/src/sfizz/effects/Lofi.h index 65b5e54e..1c745154 100644 --- a/src/sfizz/effects/Lofi.h +++ b/src/sfizz/effects/Lofi.h @@ -19,7 +19,7 @@ namespace fx { /** * @brief Initializes with the given sample rate. */ - void init(double sampleRate) override; + void setSampleRate(double sampleRate) override; /** * @brief Reset the state to initial. diff --git a/src/sfizz/effects/Nothing.cpp b/src/sfizz/effects/Nothing.cpp index 527ce5ae..47a3f8b0 100644 --- a/src/sfizz/effects/Nothing.cpp +++ b/src/sfizz/effects/Nothing.cpp @@ -10,7 +10,7 @@ namespace sfz { namespace fx { - void Nothing::init(double sampleRate) + void Nothing::setSampleRate(double sampleRate) { (void)sampleRate; } diff --git a/src/sfizz/effects/Nothing.h b/src/sfizz/effects/Nothing.h index 239697d8..12326897 100644 --- a/src/sfizz/effects/Nothing.h +++ b/src/sfizz/effects/Nothing.h @@ -18,7 +18,7 @@ namespace fx { /** * @brief Initializes with the given sample rate. */ - void init(double sampleRate) override; + void setSampleRate(double sampleRate) override; /** * @brief Reset the state to initial. From 9ce32c636cc54bd107bdc2244ce53ccf099dea5f Mon Sep 17 00:00:00 2001 From: Paul Fd Date: Fri, 6 Mar 2020 11:10:56 +0100 Subject: [PATCH 30/93] WIP tests --- tests/SynthT.cpp | 53 +++++++++++++++++++++++- tests/TestFiles/Effects/base.sfz | 4 ++ tests/TestFiles/Effects/bitcrusher_1.sfz | 9 ++++ tests/TestFiles/Effects/bitcrusher_2.sfz | 13 ++++++ tests/TestFiles/Effects/bitcrusher_3.sfz | 13 ++++++ 5 files changed, 90 insertions(+), 2 deletions(-) create mode 100644 tests/TestFiles/Effects/base.sfz create mode 100644 tests/TestFiles/Effects/bitcrusher_1.sfz create mode 100644 tests/TestFiles/Effects/bitcrusher_2.sfz create mode 100644 tests/TestFiles/Effects/bitcrusher_3.sfz diff --git a/tests/SynthT.cpp b/tests/SynthT.cpp index 072206c0..822f1d0a 100644 --- a/tests/SynthT.cpp +++ b/tests/SynthT.cpp @@ -13,7 +13,7 @@ constexpr int blockSize { 256 }; TEST_CASE("[Synth] Play and check active voices") { sfz::Synth synth; - synth.setSamplesPerBlock(256); + synth.setSamplesPerBlock(blockSize); sfz::AudioBuffer buffer { 2, blockSize }; synth.loadSfzFile(fs::current_path() / "tests/TestFiles/groups_avl.sfz"); @@ -29,7 +29,7 @@ TEST_CASE("[Synth] Play and check active voices") TEST_CASE("[Synth] Change the number of voice while playing") { sfz::Synth synth; - synth.setSamplesPerBlock(256); + synth.setSamplesPerBlock(blockSize); sfz::AudioBuffer buffer { 2, blockSize }; synth.loadSfzFile(fs::current_path() / "tests/TestFiles/groups_avl.sfz"); @@ -210,3 +210,52 @@ TEST_CASE("[Synth] Trigger=release_key and an envelope properly kills the voice synth.renderBlock(buffer); REQUIRE( synth.getVoiceView(0)->isFree() ); } + +TEST_CASE("[Synth] Number of effect buses and resetting behavior") +{ + sfz::Synth synth; + synth.setSamplesPerBlock(blockSize); + sfz::AudioBuffer buffer { 2, blockSize }; + + REQUIRE( synth.getEffectBusView(0) == nullptr); // No effects at first + synth.loadSfzFile(fs::current_path() / "tests/TestFiles/Effects/base.sfz"); + REQUIRE( synth.getEffectBusView(0) != nullptr); // We have a main bus + // Check that we can render blocks + for (int i = 0; i < 100; ++i) + synth.renderBlock(buffer); + + synth.loadSfzFile(fs::current_path() / "tests/TestFiles/Effects/bitcrusher_2.sfz"); + REQUIRE( synth.getEffectBusView(0) != nullptr); // We have a main bus + REQUIRE( synth.getEffectBusView(1) != nullptr); // and an FX bus + // Check that we can render blocks + for (int i = 0; i < 100; ++i) + synth.renderBlock(buffer); + + synth.loadSfzFile(fs::current_path() / "tests/TestFiles/Effects/base.sfz"); + REQUIRE( synth.getEffectBusView(0) != nullptr); // We have a main bus + REQUIRE( synth.getEffectBusView(1) == nullptr); // and no FX bus + // Check that we can render blocks + for (int i = 0; i < 100; ++i) + synth.renderBlock(buffer); + + synth.loadSfzFile(fs::current_path() / "tests/TestFiles/Effects/bitcrusher_3.sfz"); + REQUIRE( synth.getEffectBusView(0) != nullptr); // We have a main bus + REQUIRE( synth.getEffectBusView(1) == nullptr); // empty/uninitialized fx bus + REQUIRE( synth.getEffectBusView(2) == nullptr); // empty/uninitialized fx bus + REQUIRE( synth.getEffectBusView(3) != nullptr); // and an FX bus (because we built up to fx3) + REQUIRE( synth.getEffectBusView(3)->numEffects() == 1); + // Check that we can render blocks + for (int i = 0; i < 100; ++i) + synth.renderBlock(buffer); +} + +TEST_CASE("[Synth] No effect in the main bus") +{ + sfz::Synth synth; + synth.loadSfzFile(fs::current_path() / "tests/TestFiles/Effects/base.sfz"); + auto bus = synth.getEffectBusView(0); + REQUIRE( bus != nullptr); // We have a main bus + REQUIRE( bus->numEffects() == 0 ); + REQUIRE( bus->gainToMain() == 1 ); + REQUIRE( bus->gainToMix() == 0 ); +} diff --git a/tests/TestFiles/Effects/base.sfz b/tests/TestFiles/Effects/base.sfz new file mode 100644 index 00000000..07b3874d --- /dev/null +++ b/tests/TestFiles/Effects/base.sfz @@ -0,0 +1,4 @@ + +lokey=0 +hikey=127 +sample=*sine diff --git a/tests/TestFiles/Effects/bitcrusher_1.sfz b/tests/TestFiles/Effects/bitcrusher_1.sfz new file mode 100644 index 00000000..dd28c5c4 --- /dev/null +++ b/tests/TestFiles/Effects/bitcrusher_1.sfz @@ -0,0 +1,9 @@ + +lokey=0 +hikey=127 +sample=*sine + + +type=lofi +bitred=90 +decim=10 diff --git a/tests/TestFiles/Effects/bitcrusher_2.sfz b/tests/TestFiles/Effects/bitcrusher_2.sfz new file mode 100644 index 00000000..a0544f26 --- /dev/null +++ b/tests/TestFiles/Effects/bitcrusher_2.sfz @@ -0,0 +1,13 @@ + +lokey=0 +hikey=127 +sample=*sine +effect1=100 + + +directtomain=50 +fx1tomain=50 +type=lofi +bus=fx1 +bitred=90 +decim=10 diff --git a/tests/TestFiles/Effects/bitcrusher_3.sfz b/tests/TestFiles/Effects/bitcrusher_3.sfz new file mode 100644 index 00000000..51585e31 --- /dev/null +++ b/tests/TestFiles/Effects/bitcrusher_3.sfz @@ -0,0 +1,13 @@ + +lokey=0 +hikey=127 +sample=*sine +effect1=100 + + +directtomain=50 +fx3tomain=50 +type=lofi +bus=fx3 +bitred=90 +decim=10 From 09cca79ab00d9dc1a31e4bf7ef131ba6ff256ad9 Mon Sep 17 00:00:00 2001 From: Paul Fd Date: Fri, 6 Mar 2020 11:27:45 +0100 Subject: [PATCH 31/93] Rebased on the new parser --- src/sfizz/Region.cpp | 12 ++++++------ src/sfizz/Synth.cpp | 26 ++++++++++---------------- 2 files changed, 16 insertions(+), 22 deletions(-) diff --git a/src/sfizz/Region.cpp b/src/sfizz/Region.cpp index d0f2e706..94d0218e 100644 --- a/src/sfizz/Region.cpp +++ b/src/sfizz/Region.cpp @@ -733,17 +733,17 @@ bool sfz::Region::parseOpcode(const Opcode& opcode) setCCPairFromOpcode(opcode, amplitudeEG.ccSustain, Default::egOnCCPercentRange); break; - case hash("effect"): // effect& + case hash("effect&"): { - const auto effectNumber = opcode.backParameter(); - if (!effectNumber || *effectNumber < 1 || *effectNumber > config::maxEffectBuses) + const auto effectNumber = opcode.parameters.back(); + if (!effectNumber || effectNumber < 1 || effectNumber > config::maxEffectBuses) break; auto value = readOpcode(opcode.value, { 0, 100 }); if (!value) break; - if (static_cast(*effectNumber + 1) > gainToEffect.size()) - gainToEffect.resize(*effectNumber + 1); - gainToEffect[*effectNumber] = *value / 100; + if (static_cast(effectNumber + 1) > gainToEffect.size()) + gainToEffect.resize(effectNumber + 1); + gainToEffect[effectNumber] = *value / 100; break; } diff --git a/src/sfizz/Synth.cpp b/src/sfizz/Synth.cpp index efefd1e7..67dadef1 100644 --- a/src/sfizz/Synth.cpp +++ b/src/sfizz/Synth.cpp @@ -223,24 +223,18 @@ void sfz::Synth::handleEffectOpcodes(const std::vector& members) getOrCreateBus(0).setGainToMain(*valueOpt / 100); break; - case hash("fxtomain"): // fx&tomain - if (auto numberOpt = opcode.firstParameter()) { - unsigned number = *numberOpt; - if (number < 1 || number > config::maxEffectBuses) - break; - if (auto valueOpt = readOpcode(opcode.value, { 0, 100 })) - getOrCreateBus(number).setGainToMain(*valueOpt / 100); - } + case hash("fx&tomain"): // fx&tomain + if (opcode.parameters.front() < 1 || opcode.parameters.front() > config::maxEffectBuses) + break; + if (auto valueOpt = readOpcode(opcode.value, { 0, 100 })) + getOrCreateBus(opcode.parameters.front()).setGainToMain(*valueOpt / 100); break; - case hash("fxtomix"): // fx&tomix - if (auto numberOpt = opcode.firstParameter()) { - unsigned number = *numberOpt; - if (number < 1 || number > config::maxEffectBuses) - break; - if (auto valueOpt = readOpcode(opcode.value, { 0, 100 })) - getOrCreateBus(number).setGainToMix(*valueOpt / 100); - } + case hash("fx&tomix"): // fx&tomix + if (opcode.parameters.front() < 1 || opcode.parameters.front() > config::maxEffectBuses) + break; + if (auto valueOpt = readOpcode(opcode.value, { 0, 100 })) + getOrCreateBus(opcode.parameters.front()).setGainToMix(*valueOpt / 100); break; } } From 1f91bf0f66d29891bf51f756488b5aa4b08af94a Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Fri, 6 Mar 2020 23:43:30 +0100 Subject: [PATCH 32/93] Fix a potential null pointer error (effect buses can have gaps) --- src/sfizz/Synth.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/sfizz/Synth.cpp b/src/sfizz/Synth.cpp index 67dadef1..0ffcfdb8 100644 --- a/src/sfizz/Synth.cpp +++ b/src/sfizz/Synth.cpp @@ -459,8 +459,10 @@ void sfz::Synth::setSamplesPerBlock(int samplesPerBlock) noexcept this->tempMixNodeBuffer.resize(samplesPerBlock); for (auto& voice : voices) voice->setSamplesPerBlock(samplesPerBlock); - for (auto& bus : effectBuses) - bus->setSamplesPerBlock(samplesPerBlock); + for (size_t i = 0, n = effectBuses.size(); i < n; ++i) { + if (EffectBus* bus = effectBuses[i].get()) + bus->setSamplesPerBlock(samplesPerBlock); + } } void sfz::Synth::setSampleRate(float sampleRate) noexcept From 0360dfa3b682dae0ebff9e0fa67dd0e89c2dd60b Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Fri, 6 Mar 2020 23:50:11 +0100 Subject: [PATCH 33/93] Pass the buffer size to effects --- src/sfizz/Effects.cpp | 3 +++ src/sfizz/Effects.h | 6 ++++++ src/sfizz/effects/Lofi.cpp | 5 +++++ src/sfizz/effects/Lofi.h | 6 ++++++ src/sfizz/effects/Nothing.cpp | 5 +++++ src/sfizz/effects/Nothing.h | 6 ++++++ 6 files changed, 31 insertions(+) diff --git a/src/sfizz/Effects.cpp b/src/sfizz/Effects.cpp index c31f245e..512f2b5e 100644 --- a/src/sfizz/Effects.cpp +++ b/src/sfizz/Effects.cpp @@ -138,6 +138,9 @@ void EffectBus::setSamplesPerBlock(int samplesPerBlock) noexcept { _inputs.resize(samplesPerBlock); _outputs.resize(samplesPerBlock); + + for (const auto& effectPtr : _effects) + effectPtr->setSamplesPerBlock(samplesPerBlock); } } // namespace sfz diff --git a/src/sfizz/Effects.h b/src/sfizz/Effects.h index 0ea8d195..1c1edfeb 100644 --- a/src/sfizz/Effects.h +++ b/src/sfizz/Effects.h @@ -32,6 +32,12 @@ public: */ virtual void setSampleRate(double sampleRate) = 0; + /** + * @brief Sets the maximum number of frames to render at a time. The actual + * value can be lower but should never be higher. + */ + virtual void setSamplesPerBlock(int samplesPerBlock) = 0; + /** @brief Reset the state to initial. */ diff --git a/src/sfizz/effects/Lofi.cpp b/src/sfizz/effects/Lofi.cpp index fdd511d1..1fcc3304 100644 --- a/src/sfizz/effects/Lofi.cpp +++ b/src/sfizz/effects/Lofi.cpp @@ -53,6 +53,11 @@ namespace fx { } } + void Lofi::setSamplesPerBlock(int samplesPerBlock) + { + (void)samplesPerBlock; + } + void Lofi::clear() { for (unsigned c = 0; c < EffectChannels; ++c) { diff --git a/src/sfizz/effects/Lofi.h b/src/sfizz/effects/Lofi.h index 1c745154..9b97c6e3 100644 --- a/src/sfizz/effects/Lofi.h +++ b/src/sfizz/effects/Lofi.h @@ -21,6 +21,12 @@ namespace fx { */ void setSampleRate(double sampleRate) override; + /** + * @brief Sets the maximum number of frames to render at a time. The actual + * value can be lower but should never be higher. + */ + void setSamplesPerBlock(int samplesPerBlock) override; + /** * @brief Reset the state to initial. */ diff --git a/src/sfizz/effects/Nothing.cpp b/src/sfizz/effects/Nothing.cpp index 47a3f8b0..d2101de1 100644 --- a/src/sfizz/effects/Nothing.cpp +++ b/src/sfizz/effects/Nothing.cpp @@ -15,6 +15,11 @@ namespace fx { (void)sampleRate; } + void Nothing::setSamplesPerBlock(int samplesPerBlock) + { + (void)samplesPerBlock; + } + void Nothing::clear() { } diff --git a/src/sfizz/effects/Nothing.h b/src/sfizz/effects/Nothing.h index 12326897..2ac27600 100644 --- a/src/sfizz/effects/Nothing.h +++ b/src/sfizz/effects/Nothing.h @@ -20,6 +20,12 @@ namespace fx { */ void setSampleRate(double sampleRate) override; + /** + * @brief Sets the maximum number of frames to render at a time. The actual + * value can be lower but should never be higher. + */ + void setSamplesPerBlock(int samplesPerBlock) override; + /** * @brief Reset the state to initial. */ From 532cd88e96ef351dcf1665dce2a4dc560510d329 Mon Sep 17 00:00:00 2001 From: Paul Fd Date: Sat, 7 Mar 2020 21:27:19 +0100 Subject: [PATCH 34/93] Add some tests on effect gains --- tests/SynthT.cpp | 60 ++++++++++++++++++++++++++++++ tests/TestFiles/Effects/to_mix.sfz | 11 ++++++ 2 files changed, 71 insertions(+) create mode 100644 tests/TestFiles/Effects/to_mix.sfz diff --git a/tests/SynthT.cpp b/tests/SynthT.cpp index 822f1d0a..9ed0c73c 100644 --- a/tests/SynthT.cpp +++ b/tests/SynthT.cpp @@ -259,3 +259,63 @@ TEST_CASE("[Synth] No effect in the main bus") REQUIRE( bus->gainToMain() == 1 ); REQUIRE( bus->gainToMix() == 0 ); } + +TEST_CASE("[Synth] One effect") +{ + sfz::Synth synth; + synth.loadSfzFile(fs::current_path() / "tests/TestFiles/Effects/bitcrusher_1.sfz"); + auto bus = synth.getEffectBusView(0); + REQUIRE( bus != nullptr); // We have a main bus + REQUIRE( bus->numEffects() == 1 ); + REQUIRE( bus->gainToMain() == 1 ); + REQUIRE( bus->gainToMix() == 0 ); +} + +TEST_CASE("[Synth] Effect on a second bus") +{ + sfz::Synth synth; + synth.loadSfzFile(fs::current_path() / "tests/TestFiles/Effects/bitcrusher_2.sfz"); + auto bus = synth.getEffectBusView(0); + REQUIRE( bus != nullptr); // We have a main bus + REQUIRE( bus->numEffects() == 0 ); + REQUIRE( bus->gainToMain() == 0.5 ); + REQUIRE( bus->gainToMix() == 0 ); + bus = synth.getEffectBusView(1); + REQUIRE( bus != nullptr); + REQUIRE( bus->numEffects() == 1 ); + REQUIRE( bus->gainToMain() == 0.5 ); + REQUIRE( bus->gainToMix() == 0 ); +} + + +TEST_CASE("[Synth] Effect on a third bus") +{ + sfz::Synth synth; + synth.loadSfzFile(fs::current_path() / "tests/TestFiles/Effects/bitcrusher_3.sfz"); + auto bus = synth.getEffectBusView(0); + REQUIRE( bus != nullptr); // We have a main bus + REQUIRE( bus->numEffects() == 0 ); + REQUIRE( bus->gainToMain() == 0.5 ); + REQUIRE( bus->gainToMix() == 0 ); + bus = synth.getEffectBusView(3); + REQUIRE( bus != nullptr); + REQUIRE( bus->numEffects() == 1 ); + REQUIRE( bus->gainToMain() == 0.5 ); + REQUIRE( bus->gainToMix() == 0 ); +} + +TEST_CASE("[Synth] Gain to mix") +{ + sfz::Synth synth; + synth.loadSfzFile(fs::current_path() / "tests/TestFiles/Effects/to_mix.sfz"); + auto bus = synth.getEffectBusView(0); + REQUIRE( bus != nullptr); // We have a main bus + REQUIRE( bus->numEffects() == 0 ); + REQUIRE( bus->gainToMain() == 1 ); + REQUIRE( bus->gainToMix() == 0 ); + bus = synth.getEffectBusView(1); + REQUIRE( bus != nullptr); + REQUIRE( bus->numEffects() == 1 ); + REQUIRE( bus->gainToMain() == 0 ); + REQUIRE( bus->gainToMix() == 0.5 ); +} diff --git a/tests/TestFiles/Effects/to_mix.sfz b/tests/TestFiles/Effects/to_mix.sfz new file mode 100644 index 00000000..8f12e70e --- /dev/null +++ b/tests/TestFiles/Effects/to_mix.sfz @@ -0,0 +1,11 @@ + +lokey=0 +hikey=127 +sample=*sine + + +fx1tomix=50 +bus=fx1 +type=lofi +bitred=90 +decim=10 From af9231db33e2c1eb233ac340bc43957acb499c4e Mon Sep 17 00:00:00 2001 From: Paul Fd Date: Sat, 7 Mar 2020 21:32:55 +0100 Subject: [PATCH 35/93] Use foreach loops --- src/sfizz/Synth.cpp | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/sfizz/Synth.cpp b/src/sfizz/Synth.cpp index 0ffcfdb8..deb58a8c 100644 --- a/src/sfizz/Synth.cpp +++ b/src/sfizz/Synth.cpp @@ -459,8 +459,9 @@ void sfz::Synth::setSamplesPerBlock(int samplesPerBlock) noexcept this->tempMixNodeBuffer.resize(samplesPerBlock); for (auto& voice : voices) voice->setSamplesPerBlock(samplesPerBlock); - for (size_t i = 0, n = effectBuses.size(); i < n; ++i) { - if (EffectBus* bus = effectBuses[i].get()) + + for (auto& bus: effectBuses) { + if (bus) bus->setSamplesPerBlock(samplesPerBlock); } } @@ -479,8 +480,8 @@ void sfz::Synth::setSampleRate(float sampleRate) noexcept resources.filterPool.setSampleRate(sampleRate); resources.eqPool.setSampleRate(sampleRate); - for (size_t i = 0, n = effectBuses.size(); i < n; ++i) { - if (EffectBus* bus = effectBuses[i].get()) + for (auto& bus: effectBuses) { + if (bus) bus->setSampleRate(sampleRate); } } @@ -497,7 +498,6 @@ void sfz::Synth::renderBlock(AudioSpan buffer) noexcept return; size_t numFrames = buffer.getNumFrames(); - size_t numEffectBuses = effectBuses.size(); auto temp = AudioSpan(tempBuffer).first(numFrames); auto tempMixNode = AudioSpan(tempMixNodeBuffer).first(numFrames); @@ -505,8 +505,8 @@ void sfz::Synth::renderBlock(AudioSpan buffer) noexcept { // Prepare the effect inputs. They are mixes of per-region outputs. ScopedTiming logger { callbackBreakdown.effects }; - for (size_t i = 0; i < numEffectBuses; ++i) { - if (EffectBus* bus = effectBuses[i].get()) + for (auto& bus: effectBuses) { + if (bus) bus->clearInputs(numFrames); } } @@ -529,8 +529,8 @@ void sfz::Synth::renderBlock(AudioSpan buffer) noexcept { // Add the output into the effects linked to this region ScopedTiming logger { callbackBreakdown.effects, ScopedTiming::Operation::addToDuration }; - for (size_t i = 0; i < numEffectBuses; ++i) { - if (EffectBus* bus = effectBuses[i].get()) { + for (size_t i = 0, n = effectBuses.size(); i < n; ++i) { + if (auto& bus = effectBuses[i]) { float addGain = region->getGainToEffectBus(i); bus->addToInputs(temp, addGain, numFrames); } @@ -549,8 +549,8 @@ void sfz::Synth::renderBlock(AudioSpan buffer) noexcept // without any , the signal is just going to flow through it. ScopedTiming logger { callbackBreakdown.effects, ScopedTiming::Operation::addToDuration }; - for (size_t i = 0; i < numEffectBuses; ++i) { - if (EffectBus* bus = effectBuses[i].get()) { + for (auto& bus: effectBuses) { + if (bus) { bus->process(numFrames); bus->mixOutputsTo(buffer, tempMixNode, numFrames); } From e84af98d27425ce8ad7486e974ae28da8cf93f1c Mon Sep 17 00:00:00 2001 From: Paul Fd Date: Sat, 7 Mar 2020 22:09:46 +0100 Subject: [PATCH 36/93] Added tests and a send to to_mix.sfz --- tests/RegionT.cpp | 17 +++++++++++++++++ tests/TestFiles/Effects/to_mix.sfz | 1 + 2 files changed, 18 insertions(+) diff --git a/tests/RegionT.cpp b/tests/RegionT.cpp index aa86f52b..71063e74 100644 --- a/tests/RegionT.cpp +++ b/tests/RegionT.cpp @@ -1350,6 +1350,23 @@ TEST_CASE("[Region] Parsing opcodes") region.parseOpcode({ "eq1_freqcc15", "50000" }); REQUIRE(region.equalizers[0].frequencyCC[15] == 30000.0f); } + + SECTION("Effects send") + { + REQUIRE(region.gainToEffect.size() == 1); + REQUIRE(region.gainToEffect[0] == 1.0f); + region.parseOpcode({ "effect1", "50.4" }); + REQUIRE(region.gainToEffect.size() == 2); + REQUIRE(region.gainToEffect[1] == 0.504f); + region.parseOpcode({ "effect3", "100" }); + REQUIRE(region.gainToEffect.size() == 4); + REQUIRE(region.gainToEffect[2] == 0.0f); + REQUIRE(region.gainToEffect[3] == 1.0f); + region.parseOpcode({ "effect3", "150.1" }); + REQUIRE(region.gainToEffect[3] == 1.0f); + region.parseOpcode({ "effect3", "-50.65" }); + REQUIRE(region.gainToEffect[3] == 0.0f); + } } // Specific region bugs diff --git a/tests/TestFiles/Effects/to_mix.sfz b/tests/TestFiles/Effects/to_mix.sfz index 8f12e70e..65cd1837 100644 --- a/tests/TestFiles/Effects/to_mix.sfz +++ b/tests/TestFiles/Effects/to_mix.sfz @@ -2,6 +2,7 @@ lokey=0 hikey=127 sample=*sine +effect1=100 fx1tomix=50 From 2dc41f45035e5b11cf6bdbe7b119efa9c685be34 Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Thu, 5 Mar 2020 11:16:47 +0100 Subject: [PATCH 37/93] Initial VST plugin --- .gitignore | 3 + CMakeLists.txt | 5 + cmake/VSTConfig.cmake | 4 + vst/CMakeLists.txt | 77 +++++++ vst/RTSemaphore.h | 167 ++++++++++++++ vst/SfizzVstController.cpp | 121 ++++++++++ vst/SfizzVstController.h | 69 ++++++ vst/SfizzVstEditor.cpp | 180 +++++++++++++++ vst/SfizzVstEditor.h | 40 ++++ vst/SfizzVstProcessor.cpp | 284 ++++++++++++++++++++++++ vst/SfizzVstProcessor.h | 57 +++++ vst/SfizzVstState.cpp | 42 ++++ vst/SfizzVstState.h | 21 ++ vst/VstPluginDefs.h.in | 11 + vst/VstPluginFactory.cpp | 49 ++++ vst/cmake/Vst3.cmake | 244 ++++++++++++++++++++ vst/external/steinberg/LICENSE | 27 +++ vst/external/steinberg/src/x11runloop.h | 110 +++++++++ vst/vst3.version | 7 + 19 files changed, 1518 insertions(+) create mode 100644 cmake/VSTConfig.cmake create mode 100644 vst/CMakeLists.txt create mode 100644 vst/RTSemaphore.h create mode 100644 vst/SfizzVstController.cpp create mode 100644 vst/SfizzVstController.h create mode 100644 vst/SfizzVstEditor.cpp create mode 100644 vst/SfizzVstEditor.h create mode 100644 vst/SfizzVstProcessor.cpp create mode 100644 vst/SfizzVstProcessor.h create mode 100644 vst/SfizzVstState.cpp create mode 100644 vst/SfizzVstState.h create mode 100644 vst/VstPluginDefs.h.in create mode 100644 vst/VstPluginFactory.cpp create mode 100644 vst/cmake/Vst3.cmake create mode 100644 vst/external/steinberg/LICENSE create mode 100644 vst/external/steinberg/src/x11runloop.h create mode 100644 vst/vst3.version diff --git a/.gitignore b/.gitignore index 4196b1a0..d232a9b0 100644 --- a/.gitignore +++ b/.gitignore @@ -32,3 +32,6 @@ node_modules/ *.lock *.sublime-* *.code-* + +/vst/download +/vst/external/VST_SDK diff --git a/CMakeLists.txt b/CMakeLists.txt index 91a45430..00adb2e6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -27,6 +27,7 @@ endif() option (ENABLE_LTO "Enable Link Time Optimization [default: ON]" ON) option (SFIZZ_JACK "Enable JACK stand-alone build [default: ON]" ON) option (SFIZZ_LV2 "Enable LV2 plug-in build [default: ON]" ON) +option (SFIZZ_VST "Enable VST plug-in build [default: OFF]" OFF) option (SFIZZ_BENCHMARKS "Enable benchmarks build [default: OFF]" OFF) option (SFIZZ_TESTS "Enable tests build [default: OFF]" OFF) option (SFIZZ_SHARED "Enable shared library build [default: ON]" ON) @@ -51,6 +52,10 @@ if (SFIZZ_LV2) add_subdirectory (lv2) endif() +if (SFIZZ_VST) + add_subdirectory (vst) +endif() + if (SFIZZ_BENCHMARKS) add_subdirectory (benchmarks) endif() diff --git a/cmake/VSTConfig.cmake b/cmake/VSTConfig.cmake new file mode 100644 index 00000000..0d1de3e1 --- /dev/null +++ b/cmake/VSTConfig.cmake @@ -0,0 +1,4 @@ +set (VSTPLUGIN_NAME "sfizz") +set (VSTPLUGIN_VENDOR "Paul Ferrand") +set (VSTPLUGIN_URL "http://sfztools.github.io/sfizz") +set (VSTPLUGIN_EMAIL "paul@ferrand.cc") diff --git a/vst/CMakeLists.txt b/vst/CMakeLists.txt new file mode 100644 index 00000000..4cafc961 --- /dev/null +++ b/vst/CMakeLists.txt @@ -0,0 +1,77 @@ +set (VSTPLUGIN_PRJ_NAME "${PROJECT_NAME}_vst3") +set (VSTPLUGIN_BUNDLE_NAME "${PROJECT_NAME}.vst3") + +set (VST3SDK_BASEDIR "${CMAKE_CURRENT_SOURCE_DIR}/external/VST_SDK/VST3_SDK") +set (VST3SDK_ARCHIVE "vst-sdk_3.6.14_build-24_2019-11-29.zip") + +if (NOT EXISTS "${VST3SDK_BASEDIR}") + message (STATUS "VST3 SDK is not found, downloading") + + execute_process ( + COMMAND "${CMAKE_COMMAND}" -E make_directory "${CMAKE_CURRENT_SOURCE_DIR}/download") + + if (NOT EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/download/${VST3SDK_ARCHIVE}") + file (DOWNLOAD + "https://download.steinberg.net/sdk_downloads/${VST3SDK_ARCHIVE}" + "${CMAKE_CURRENT_SOURCE_DIR}/download/${VST3SDK_ARCHIVE}" + SHOW_PROGRESS) + endif() + + execute_process ( + COMMAND "${CMAKE_COMMAND}" -E make_directory "${CMAKE_CURRENT_SOURCE_DIR}/external" + COMMAND "${CMAKE_COMMAND}" -E tar xvf "../download/${VST3SDK_ARCHIVE}" + WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/external") +endif() + +# stop trying to include this atrocity.. build it ourselves +#add_subdirectory("${VST3SDK_BASEDIR}" EXCLUDE_FROM_ALL) + +# VST plugin specific settings +include (VSTConfig) + +configure_file (VstPluginDefs.h.in "${CMAKE_CURRENT_BINARY_DIR}/VstPluginDefs.h") + +# Build VST3 SDK +include("cmake/Vst3.cmake") + +# Build the plugin +add_library(${VSTPLUGIN_PRJ_NAME} MODULE + SfizzVstProcessor.cpp + SfizzVstController.cpp + SfizzVstEditor.cpp + SfizzVstState.cpp + VstPluginFactory.cpp) +target_link_libraries(${VSTPLUGIN_PRJ_NAME} + PRIVATE ${PROJECT_NAME}::${PROJECT_NAME}) +target_include_directories(${VSTPLUGIN_PRJ_NAME} + PRIVATE "${CMAKE_CURRENT_BINARY_DIR}") +set_target_properties(${VSTPLUGIN_PRJ_NAME} PROPERTIES + OUTPUT_NAME "${PROJECT_NAME}" + PREFIX "") + +plugin_add_vst3sdk(${VSTPLUGIN_PRJ_NAME}) +plugin_add_vstgui(${VSTPLUGIN_PRJ_NAME}) + +if (${CMAKE_SYSTEM_NAME} MATCHES "Linux") + target_link_libraries(${VSTPLUGIN_PRJ_NAME} PRIVATE + "-Wl,--version-script=${CMAKE_CURRENT_SOURCE_DIR}/vst3.version") +endif() + +# Create the bundle (see "VST 3 Locations / Format") +if(WIN32) + set_target_properties(${VSTPLUGIN_PRJ_NAME} PROPERTIES + LIBRARY_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/${VSTPLUGIN_BUNDLE_NAME}/Contents/${VST3_PACKAGE_ARCHITECTURE}-win") + # TODO: make desktop.ini, Plugin.ico +elseif(APPLE) + set_target_properties(${VSTPLUGIN_PRJ_NAME} PROPERTIES + LIBRARY_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/${VSTPLUGIN_BUNDLE_NAME}/Contents/MacOS") + # TODO: make Info.plist, PkgInfo +else() + set_target_properties(${VSTPLUGIN_PRJ_NAME} PROPERTIES + LIBRARY_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/${VSTPLUGIN_BUNDLE_NAME}/Contents/${VST3_PACKAGE_ARCHITECTURE}-linux") +endif() + +# To help debugging the link only +if (FALSE) + target_link_options(${VSTPLUGIN_PRJ_NAME} PRIVATE "-Wl,-no-undefined") +endif() diff --git a/vst/RTSemaphore.h b/vst/RTSemaphore.h new file mode 100644 index 00000000..0cd6135b --- /dev/null +++ b/vst/RTSemaphore.h @@ -0,0 +1,167 @@ +// 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 +#if defined(__APPLE__) +#include +#elif defined(_WIN32) +#include +#include +#else +#include +#include +#endif +#include + +class RTSemaphore { +public: + explicit RTSemaphore(unsigned value = 0); + ~RTSemaphore(); + + RTSemaphore(const RTSemaphore &) = delete; + RTSemaphore &operator=(const RTSemaphore &) = delete; + + void post(); + void wait(); + bool try_wait(); + +private: +#if defined(__APPLE__) + semaphore_t sem_; +#elif defined(_WIN32) + HANDLE sem_; +#else + sem_t sem_; +#endif +}; + +#if defined(__APPLE__) +inline RTSemaphore::RTSemaphore(unsigned value) +{ + if (semaphore_create(mach_task_self(), &sem_, SYNC_POLICY_FIFO, value) != 0) + throw std::runtime_error("RTSemaphore::RTSemaphore"); +} + +inline RTSemaphore::~RTSemaphore() +{ + semaphore_destroy(mach_task_self(), sem_); +} + +inline void RTSemaphore::post() +{ + if (semaphore_signal(sem_) != KERN_SUCCESS) + throw std::runtime_error("RTSemaphore::post"); +} + +inline void RTSemaphore::wait() +{ + do { + switch (semaphore_wait(sem_)) { + case KERN_SUCCESS: + return; + case KERN_ABORTED: + break; + default: + throw std::runtime_error("RTSemaphore::wait"); + } + } while (1); +} + +inline bool RTSemaphore::try_wait() +{ + do { + const mach_timespec_t timeout = {0, 0}; + switch (semaphore_timedwait(sem_, timeout)) { + case KERN_SUCCESS: + return true; + case KERN_OPERATION_TIMED_OUT: + return false; + case KERN_ABORTED: + break; + default: + throw std::runtime_error("RTSemaphore::try_wait"); + } + } while (1); +} +#elif defined(_WIN32) +inline RTSemaphore::RTSemaphore(unsigned value) +{ + sem_ = CreateRTSemaphore(nullptr, value, LONG_MAX, nullptr); + if (!sem_) + throw std::runtime_error("RTSemaphore::RTSemaphore"); +} + +inline RTSemaphore::~RTSemaphore() +{ + CloseHandle(sem_); +} + +inline void RTSemaphore::post() +{ + if (!ReleaseRTSemaphore(sem_, 1, nullptr)) + throw std::runtime_error("RTSemaphore::post"); +} + +inline void RTSemaphore::wait() +{ + if (WaitForSingleObject(sem_, INFINITE) != WAIT_OBJECT_0) + throw std::runtime_error("RTSemaphore::wait"); +} + +inline bool RTSemaphore::try_wait() +{ + switch (WaitForSingleObject(sem_, 0)) { + case WAIT_OBJECT_0: + return true; + case WAIT_TIMEOUT: + return false; + default: + throw std::runtime_error("RTSemaphore::try_wait"); + } +} +#else +inline RTSemaphore::RTSemaphore(unsigned value) +{ + if (sem_init(&sem_, 0, value) != 0) + throw std::runtime_error("RTSemaphore::RTSemaphore"); +} + +inline RTSemaphore::~RTSemaphore() +{ + sem_destroy(&sem_); +} + +inline void RTSemaphore::post() +{ + while (sem_post(&sem_) != 0) { + if (errno != EINTR) + throw std::runtime_error("RTSemaphore::post"); + } +} + +inline void RTSemaphore::wait() +{ + while (sem_wait(&sem_) != 0) { + if (errno != EINTR) + throw std::runtime_error("RTSemaphore::wait"); + } +} + +inline bool RTSemaphore::try_wait() +{ + do { + if (sem_trywait(&sem_) == 0) + return true; + switch (errno) { + case EINTR: + break; + case EAGAIN: + return false; + default: + throw std::runtime_error("RTSemaphore::try_wait"); + } + } while (1); +} +#endif diff --git a/vst/SfizzVstController.cpp b/vst/SfizzVstController.cpp new file mode 100644 index 00000000..88fcc6f0 --- /dev/null +++ b/vst/SfizzVstController.cpp @@ -0,0 +1,121 @@ +// 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 "SfizzVstController.h" +#include "SfizzVstEditor.h" +#include "base/source/fstreamer.h" +#include "pluginterfaces/vst/ivstmidicontrollers.h" + +tresult PLUGIN_API SfizzVstControllerNoUi::initialize(FUnknown* context) +{ + tresult result = EditController::initialize(context); + if (result != kResultTrue) + return result; + + Vst::ParamID pid = 0; + + // MIDI controllers + for (unsigned i = 0; i < numControllerParams; ++i) { + Steinberg::String title; + Steinberg::String shortTitle; + title.printf("Controller %u", i); + shortTitle.printf("CC%u", i); + + parameters.addParameter( + title, nullptr, 0, 0, Vst::ParameterInfo::kCanAutomate, + pid++, Vst::kRootUnitId, shortTitle); + } + + // MIDI extra controllers + parameters.addParameter(Steinberg::String("Aftertouch"), nullptr, 0, 0.5, 0, pid++, Vst::kRootUnitId); + parameters.addParameter(Steinberg::String("Pitch Bend"), nullptr, 0, 0.5, 0, pid++, Vst::kRootUnitId); + + return kResultTrue; +} + +tresult PLUGIN_API SfizzVstControllerNoUi::terminate() +{ + return EditController::terminate(); +} + +tresult PLUGIN_API SfizzVstControllerNoUi::getMidiControllerAssignment(int32 busIndex, int16 channel, Vst::CtrlNumber midiControllerNumber, Vst::ParamID& id) +{ + switch (midiControllerNumber) { + case Vst::kAfterTouch: + id = kPidMidiAftertouch; + return kResultTrue; + + case Vst::kPitchBend: + id = kPidMidiPitchBend; + return kResultTrue; + + default: + if (midiControllerNumber < 0 || midiControllerNumber >= numControllerParams) + return kResultFalse; + + id = kPidMidiCC0 + midiControllerNumber; + return kResultTrue; + } +} + +// --- Controller with UI --- // + +IPlugView* PLUGIN_API SfizzVstController::createView(FIDString _name) +{ + ConstString name(_name); + + if (name != Vst::ViewType::kEditor) + return nullptr; + + return new SfizzVstEditor(this); +} + +tresult PLUGIN_API SfizzVstController::setParamNormalized(Vst::ParamID tag, Vst::ParamValue value) +{ + tresult r = SfizzVstControllerNoUi::setParamNormalized(tag, value); + if (r != kResultTrue) + return r; + + return kResultTrue; +} + +tresult PLUGIN_API SfizzVstController::setComponentState(IBStream* state) +{ + SfizzVstState s; + + tresult r = s.load(state); + if (r != kResultTrue) + return r; + + for (StateListener* listener : _stateListeners) + listener->onStateChanged(); + + _state = s; + return kResultTrue; +} + +void SfizzVstController::addStateListener(StateListener* listener) +{ + _stateListeners.push_back(listener); +} + +void SfizzVstController::removeStateListener(StateListener* listener) +{ + auto it = std::find(_stateListeners.begin(), _stateListeners.end(), listener); + if (it != _stateListeners.end()) + _stateListeners.erase(it); +} + +FUnknown* SfizzVstController::createInstance(void*) +{ + return static_cast(new SfizzVstController); +} + +/* + Note(jpc) Generated at random with uuidgen. + Can't find docs on it... maybe it's to register somewhere? + */ +FUID SfizzVstController::cid(0x7129736c, 0xbc784134, 0xbb899d56, 0x2ebafe4f); diff --git a/vst/SfizzVstController.h b/vst/SfizzVstController.h new file mode 100644 index 00000000..47c3ba3c --- /dev/null +++ b/vst/SfizzVstController.h @@ -0,0 +1,69 @@ +// 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 "SfizzVstState.h" +#include "public.sdk/source/vst/vsteditcontroller.h" +#include "public.sdk/source/vst/vstparameters.h" +#include "vstgui/plugin-bindings/vst3editor.h" +class SfizzVstState; + +using namespace Steinberg; +using namespace VSTGUI; + +class SfizzVstControllerNoUi : public Vst::EditController, + public Vst::IMidiMapping { +public: + virtual ~SfizzVstControllerNoUi() {} + + tresult PLUGIN_API initialize(FUnknown* context) override; + tresult PLUGIN_API terminate() override; + + tresult PLUGIN_API getMidiControllerAssignment(int32 busIndex, int16 channel, Vst::CtrlNumber midiControllerNumber, Vst::ParamID& id) override; + + enum { numControllerParams = 128 }; + + // interfaces + OBJ_METHODS(SfizzVstControllerNoUi, Vst::EditController) + DEFINE_INTERFACES + DEF_INTERFACE(Vst::IMidiMapping) + END_DEFINE_INTERFACES(Vst::EditController) + REFCOUNT_METHODS(Vst::EditController) + + enum { + kPidMidiCC0, + kPidMidiCCLast = kPidMidiCC0 + numControllerParams - 1, + kPidMidiAftertouch, + kPidMidiPitchBend, + /* Reserved */ + }; +}; + +class SfizzVstController : public SfizzVstControllerNoUi, public VSTGUI::VST3EditorDelegate { +public: + IPlugView* PLUGIN_API createView(FIDString name) override; + + tresult PLUGIN_API setParamNormalized(Vst::ParamID tag, Vst::ParamValue value) override; + tresult PLUGIN_API setComponentState(IBStream* state) override; + + struct StateListener { + virtual void onStateChanged() = 0; + }; + + const SfizzVstState& getSfizzState() const { return _state; } + + void addStateListener(StateListener* listener); + void removeStateListener(StateListener* listener); + + /// + static FUnknown* createInstance(void*); + + static FUID cid; + +private: + SfizzVstState _state; + std::vector _stateListeners; +}; diff --git a/vst/SfizzVstEditor.cpp b/vst/SfizzVstEditor.cpp new file mode 100644 index 00000000..46bd7139 --- /dev/null +++ b/vst/SfizzVstEditor.cpp @@ -0,0 +1,180 @@ +// 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 "SfizzVstEditor.h" +#include "SfizzVstState.h" +#if !defined(__APPLE__) && !defined(_WIN32) +#include "x11runloop.h" +#endif + +using namespace VSTGUI; + +static constexpr int kEditorWidth = 800; +static constexpr int kEditorHeight = 40; + +SfizzVstEditor::SfizzVstEditor(void *controller) + : VSTGUIEditor(controller) +{ + static_cast(getController())->addStateListener(this); +} + +SfizzVstEditor::~SfizzVstEditor() +{ + static_cast(getController())->removeStateListener(this); +} + +bool PLUGIN_API SfizzVstEditor::open(void* parent, const VSTGUI::PlatformType& platformType) +{ + CRect wsize(0, 0, kEditorWidth, kEditorHeight); + CFrame *frame = new CFrame(wsize, this); + this->frame = frame; + + IPlatformFrameConfig* config = nullptr; + +#if !defined(__APPLE__) && !defined(_WIN32) + X11::FrameConfig x11config; + x11config.runLoop = VSTGUI::owned(new RunLoop(plugFrame)); + config = &x11config; +#endif + + createFrameContents(); + updateStateDisplay(); + + frame->open(parent, platformType, config); + return true; +} + +void PLUGIN_API SfizzVstEditor::close() +{ + CFrame *frame = this->frame; + if (frame) { + frame->forget(); + this->frame = nullptr; + } +} + +/// +void SfizzVstEditor::valueChanged(CControl* ctl) +{ + int32_t tag = ctl->getTag(); + float value = ctl->getValue(); + + switch (tag) { + case kTagLoadSfzFile: + if (value != 1) + break; + + chooseSfzFile(); + + break; + } +} + +void SfizzVstEditor::onStateChanged() +{ + updateStateDisplay(); +} + +/// +void SfizzVstEditor::chooseSfzFile() +{ + SharedPointer fs(CNewFileSelector::create(frame)); + + fs->setTitle("Load SFZ file"); + fs->setDefaultExtension(CFileExtension("SFZ", "sfz")); + + if (fs->runModal()) { + UTF8StringPtr file = fs->getSelectedFile(0); + if (file) + loadSfzFile(file); + } +} + +void SfizzVstEditor::loadSfzFile(const std::string& filePath) +{ + _fileLabel->setText(filePath.c_str()); + + Vst::EditController* ctl = getController(); + + Vst::IMessage *msg = ctl->allocateMessage(); + if (msg) { + msg->setMessageID("LoadSfz"); + Vst::IAttributeList* attr = msg->getAttributes(); + attr->setString("File", Steinberg::String(filePath.c_str()).text()); + ctl->sendMessage(msg); + msg->release(); + } +} + +/// +class SimpleButton : public CControl { +public: + explicit SimpleButton(const CRect& size, IControlListener* listener = nullptr, int32_t tag = -1, UTF8StringPtr title = nullptr) + : CControl(size, listener, tag), _title(title ? title : "") { + } + + void draw(CDrawContext *dc) override + { + CRect bounds = getViewSize(); + dc->setFrameColor(CColor(0xff, 0xff, 0xff)); + dc->drawRect(bounds, kDrawStroked); + dc->drawString(_title.c_str(), bounds); + } + + CMouseEventResult onMouseDown(CPoint& where, const CButtonState& buttons) override + { + if (!buttons.isLeftButton()) + return kMouseEventNotHandled; + + value = getMin(); + if (isDirty()) { + valueChanged(); + invalid(); + } + value = getMax(); + if (isDirty()) + { + valueChanged(); + invalid(); + } + + return kMouseEventHandled; + } + + CLASS_METHODS(SimpleButton, CControl) + +private: + std::string _title; +}; + +/// +void SfizzVstEditor::createFrameContents() +{ + CFrame* frame = this->frame; + CRect bounds = frame->getViewSize(); + + CTextLabel *label; + CRect rect; + CRect rect2; + + rect = CRect(10.0, 10.0, 120.0, 30.0); + frame->addView(new SimpleButton(rect, this, kTagLoadSfzFile, "Load SFZ file")); + + rect2 = CRect(150.0, 10.0, bounds.right - 10.0, 30.0); + frame->addView((label = new CTextLabel(rect2, "no file"))); + label->setHoriAlign(kLeftText); + _fileLabel = label; +} + +void SfizzVstEditor::updateStateDisplay() +{ + if (!frame) + return; + + const SfizzVstState& state = static_cast(getController())->getSfizzState(); + + _fileLabel->setText(state.sfzFile.c_str()); +} diff --git a/vst/SfizzVstEditor.h b/vst/SfizzVstEditor.h new file mode 100644 index 00000000..4c3c44c5 --- /dev/null +++ b/vst/SfizzVstEditor.h @@ -0,0 +1,40 @@ +// 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 "SfizzVstController.h" +#include "public.sdk/source/vst/vstguieditor.h" + +using namespace Steinberg; +using namespace VSTGUI; + +class SfizzVstEditor : public Vst::VSTGUIEditor, public IControlListener, public SfizzVstController::StateListener { +public: + explicit SfizzVstEditor(void *controller); + ~SfizzVstEditor(); + + bool PLUGIN_API open(void* parent, const VSTGUI::PlatformType& platformType = VSTGUI::kDefaultNative) override; + void PLUGIN_API close() override; + + // IControlListener + void valueChanged(CControl* ctl) override; + + // SfizzVstController::StateListener + void onStateChanged() override; + +private: + void chooseSfzFile(); + void loadSfzFile(const std::string& filePath); + + void createFrameContents(); + void updateStateDisplay(); + + enum { + kTagLoadSfzFile, + }; + + CTextLabel* _fileLabel = nullptr; +}; diff --git a/vst/SfizzVstProcessor.cpp b/vst/SfizzVstProcessor.cpp new file mode 100644 index 00000000..88e71d1a --- /dev/null +++ b/vst/SfizzVstProcessor.cpp @@ -0,0 +1,284 @@ +// 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 "SfizzVstProcessor.h" +#include "SfizzVstController.h" +#include "SfizzVstState.h" +#include "base/source/fstreamer.h" +#include "pluginterfaces/vst/ivstevents.h" +#include "pluginterfaces/vst/ivstparameterchanges.h" +#include + +#pragma message("TODO: send tempo") + +SfizzVstProcessor::SfizzVstProcessor() + : _fifoToWorker(1024) +{ + setControllerClass(SfizzVstController::cid); +} + +SfizzVstProcessor::~SfizzVstProcessor() +{ + setActive(false); // to be sure +} + +tresult PLUGIN_API SfizzVstProcessor::initialize(FUnknown* context) +{ + tresult result = AudioEffect::initialize(context); + if (result != kResultTrue) + return result; + + addAudioOutput(STR16("Audio Output"), Vst::SpeakerArr::kStereo); + addEventInput(STR16("Event Input"), 1); + + return result; +} + +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; + + if (!isStereo) + return kResultFalse; + + return AudioEffect::setBusArrangements(inputs, numIns, outputs, numOuts); +} + +tresult PLUGIN_API SfizzVstProcessor::setState(IBStream* state) +{ + SfizzVstState s; + + tresult r = s.load(state); + if (r != kResultTrue) + return r; + + loadSfzFile(s.sfzFile); + + return r; +} + +tresult PLUGIN_API SfizzVstProcessor::getState(IBStream* state) +{ + SfizzVstState s; + { + std::lock_guard lock(_processMutex); + s.sfzFile = _sfzFile; + } + + return s.store(state); +} + +tresult PLUGIN_API SfizzVstProcessor::canProcessSampleSize(int32 symbolicSampleSize) +{ + if (symbolicSampleSize != Vst::kSample32) + return kResultFalse; + + return kResultTrue; +} + +tresult PLUGIN_API SfizzVstProcessor::setActive(TBool state) +{ + stopBackgroundWork(); + _synth.reset(); + + if (state) { + fprintf(stderr, "[Sfizz] new synth\n"); + sfz::Sfizz* synth = new sfz::Sfizz; + _synth.reset(synth); + + synth->setSampleRate(processSetup.sampleRate); + synth->setSamplesPerBlock(processSetup.maxSamplesPerBlock); + + loadSfzFile(_sfzFile); + + _workRunning = true; + _worker = std::thread([this]() { doBackgroundWork(); }); + } + + return kResultTrue; +} + +tresult PLUGIN_API SfizzVstProcessor::process(Vst::ProcessData& data) +{ + sfz::Sfizz& synth = *_synth; + + if (data.numOutputs < 1) // flush mode + return kResultTrue; + + uint32 numFrames = data.numSamples; + constexpr uint32 numChannels = 2; + float* outputs[numChannels]; + + assert(numChannels == data.outputs[0].numChannels); + + for (unsigned c = 0; c < numChannels; ++c) + outputs[c] = data.outputs[0].channelBuffers32[c]; + + std::unique_lock lock(_processMutex, std::try_to_lock); + if (!lock.owns_lock()) { + for (unsigned c = 0; c < numChannels; ++c) + std::memset(outputs[c], 0, numFrames * sizeof(float)); + data.outputs[0].silenceFlags = 3; + return kResultTrue; + } + + if (Vst::IParameterChanges* pc = data.inputParameterChanges) { + uint32 paramCount = pc->getParameterCount(); + + for (uint32 paramIndex = 0; paramIndex < paramCount; ++paramIndex) { + Vst::IParamValueQueue* vq = pc->getParameterData(paramIndex); + + Vst::ParamID id = vq->getParameterId(); + + switch (id) { + default: + if (id >= SfizzVstController::kPidMidiCC0 && id <= SfizzVstController::kPidMidiCCLast) { + int ccNumber = id - SfizzVstController::kPidMidiCC0; + for (uint32 pointIndex = 0, pointCount = vq->getPointCount(); pointIndex < pointCount; ++pointIndex) { + int32 sampleOffset; + Vst::ParamValue value; + if (vq->getPoint(pointIndex, sampleOffset, value) == kResultTrue) + synth.cc(sampleOffset, ccNumber, (int)(0.5 + value * 127.0)); + } + } + break; + + case SfizzVstController::kPidMidiAftertouch: + for (uint32 pointIndex = 0, pointCount = vq->getPointCount(); pointIndex < pointCount; ++pointIndex) { + int32 sampleOffset; + Vst::ParamValue value; + if (vq->getPoint(pointIndex, sampleOffset, value) == kResultTrue) + synth.aftertouch(sampleOffset, (int)(0.5 + value * 127.0)); + } + break; + + case SfizzVstController::kPidMidiPitchBend: + for (uint32 pointIndex = 0, pointCount = vq->getPointCount(); pointIndex < pointCount; ++pointIndex) { + int32 sampleOffset; + Vst::ParamValue value; + if (vq->getPoint(pointIndex, sampleOffset, value) == kResultTrue) + synth.pitchWheel(sampleOffset, (int)(0.5 + value * 16383) - 8192); + } + break; + } + } + } + + if (Vst::IEventList* events = data.inputEvents) { + uint32 numEvents = events->getEventCount(); + + for (uint32 i = 0; i < numEvents; i++) { + Vst::Event e; + if (events->getEvent(i, e) != kResultTrue) + continue; + + auto convertVelocityFromFloat = [](float x) -> int { + return std::min(127, std::max(0, (int)(x * 127.0f))); + }; + + switch (e.type) { + case Vst::Event::kNoteOnEvent: + synth.noteOn(e.sampleOffset, e.noteOn.pitch, convertVelocityFromFloat(e.noteOn.velocity)); + break; + case Vst::Event::kNoteOffEvent: + synth.noteOff(e.sampleOffset, e.noteOff.pitch, convertVelocityFromFloat(e.noteOff.velocity)); + break; + // case Vst::Event::kPolyPressureEvent: + // synth.aftertouch(e.sampleOffset, convertVelocityFromFloat(e.polyPressure.pressure)); + // break; + } + } + } + + synth.renderBlock(outputs, numFrames, numChannels); + return kResultTrue; +} + +tresult PLUGIN_API SfizzVstProcessor::notify(Vst::IMessage* message) +{ + tresult result = AudioEffect::notify(message); + if (result != kResultFalse) + return result; + + if (!_fifoToWorker.push(message)) + return kOutOfMemory; + + message->addRef(); + _semaToWorker.post(); + + return kResultTrue; +} + +FUnknown* SfizzVstProcessor::createInstance(void*) +{ + return static_cast(new SfizzVstProcessor); +} + +void SfizzVstProcessor::loadSfzFile(std::string file) +{ + std::lock_guard lock(_processMutex); + + if (_synth) { + fprintf(stderr, "[Sfizz] load SFZ file: %s\n", file.c_str()); + _synth->loadSfzFile(file); + } + + _sfzFile = std::move(file); +} + +void SfizzVstProcessor::doBackgroundWork() +{ + constexpr uint32 maxPathLen = 32768; + + for (;;) { + _semaToWorker.wait(); + + if (!_workRunning) + break; + + Vst::IMessage* msg; + if (!_fifoToWorker.pop(msg)) { + fprintf(stderr, "[Sfizz] message synchronization error in worker\n"); + std::abort(); + } + + const char* id = msg->getMessageID(); + Vst::IAttributeList* attr = msg->getAttributes(); + + if (!std::strcmp(id, "LoadSfz")) { + std::vector path(maxPathLen + 1); + if (attr->getString("File", path.data(), maxPathLen) == kResultTrue) + loadSfzFile(Steinberg::String(path.data()).text8()); + } + + msg->release(); + } +} + +void SfizzVstProcessor::stopBackgroundWork() +{ + if (!_workRunning) + return; + + _workRunning = false; + _semaToWorker.post(); + _worker.join(); + + while (_semaToWorker.try_wait()) { + Vst::IMessage* msg; + if (!_fifoToWorker.pop(msg)) { + fprintf(stderr, "[Sfizz] message synchronization error in processor\n"); + std::abort(); + } + msg->release(); + } +} + +/* + Note(jpc) Generated at random with uuidgen. + Can't find docs on it... maybe it's to register somewhere? + */ +FUID SfizzVstProcessor::cid(0xe8fab718, 0x15ed46e3, 0x8b598310, 0x1e12993f); diff --git a/vst/SfizzVstProcessor.h b/vst/SfizzVstProcessor.h new file mode 100644 index 00000000..86bbab81 --- /dev/null +++ b/vst/SfizzVstProcessor.h @@ -0,0 +1,57 @@ +// SPDX-License-Identifier: BSD-2-Clause + +// This code is part of the sfizz library and is licensed under a BSD 2-clause +// license. You should have receive a LICENSE.md file along with the code. +// If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz + +#pragma once +#include "public.sdk/source/vst/vstaudioeffect.h" +#include "public.sdk/source/vst/utility/ringbuffer.h" +#include "RTSemaphore.h" +#include +#include +#include +#include + +using namespace Steinberg; + +class SfizzVstProcessor : public Vst::AudioEffect { +public: + SfizzVstProcessor(); + ~SfizzVstProcessor(); + + tresult PLUGIN_API initialize(FUnknown* context) override; + tresult PLUGIN_API setBusArrangements(Vst::SpeakerArrangement* inputs, int32 numIns, Vst::SpeakerArrangement* outputs, int32 numOuts) override; + + tresult PLUGIN_API setState(IBStream* state) override; + tresult PLUGIN_API getState(IBStream* state) override; + + tresult PLUGIN_API canProcessSampleSize(int32 symbolicSampleSize) override; + tresult PLUGIN_API setActive(TBool state) override; + tresult PLUGIN_API process(Vst::ProcessData& data) override; + + tresult PLUGIN_API notify(Vst::IMessage* message) override; + + static FUnknown* createInstance(void*); + + static FUID cid; + + // --- Sfizz stuff here below --- +private: + std::unique_ptr _synth; + std::thread _worker; + volatile bool _workRunning = false; + Steinberg::OneReaderOneWriter::RingBuffer _fifoToWorker; + RTSemaphore _semaToWorker; + std::mutex _processMutex; + + // state + std::string _sfzFile; + + // + void loadSfzFile(std::string file); + + // worker + void doBackgroundWork(); + void stopBackgroundWork(); +}; diff --git a/vst/SfizzVstState.cpp b/vst/SfizzVstState.cpp new file mode 100644 index 00000000..447f7ad0 --- /dev/null +++ b/vst/SfizzVstState.cpp @@ -0,0 +1,42 @@ +// 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 "SfizzVstState.h" +#include +#include + +tresult SfizzVstState::load(IBStream* state) +{ + IBStreamer s(state, kLittleEndian); + + uint64 version = 0; + if (!s.readInt64u(version)) + return kResultFalse; + + while (const char* key = s.readStr8()) { + if (!std::strcmp(key, "SfzFile")) { + const char* value = s.readStr8(); + if (!value) + return kResultFalse; + sfzFile = value; + } + } + + return kResultTrue; +} + +tresult SfizzVstState::store(IBStream* state) const +{ + IBStreamer s(state, kLittleEndian); + + if (!s.writeInt64u(currentStateVersion)) + return kResultFalse; + + if (!s.writeStr8("SfzFile") || !s.writeStr8(sfzFile.c_str())) + return kResultFalse; + + return kResultTrue; +} diff --git a/vst/SfizzVstState.h b/vst/SfizzVstState.h new file mode 100644 index 00000000..df0adecc --- /dev/null +++ b/vst/SfizzVstState.h @@ -0,0 +1,21 @@ +// 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 "base/source/fstreamer.h" +#include + +using namespace Steinberg; + +class SfizzVstState { +public: + std::string sfzFile; + + static constexpr uint64 currentStateVersion = 0; + + tresult load(IBStream* state); + tresult store(IBStream* state) const; +}; diff --git a/vst/VstPluginDefs.h.in b/vst/VstPluginDefs.h.in new file mode 100644 index 00000000..c67283d8 --- /dev/null +++ b/vst/VstPluginDefs.h.in @@ -0,0 +1,11 @@ +// 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 + +#define VSTPLUGIN_NAME "@VSTPLUGIN_NAME@" +#define VSTPLUGIN_VENDOR "@VSTPLUGIN_VENDOR@" +#define VSTPLUGIN_URL "@VSTPLUGIN_URL@" +#define VSTPLUGIN_EMAIL "@VSTPLUGIN_EMAIL@" +#define VSTPLUGIN_VERSION "@PROJECT_VERSION@" diff --git a/vst/VstPluginFactory.cpp b/vst/VstPluginFactory.cpp new file mode 100644 index 00000000..cda0d7f3 --- /dev/null +++ b/vst/VstPluginFactory.cpp @@ -0,0 +1,49 @@ +// 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 "SfizzVstProcessor.h" +#include "SfizzVstController.h" +#include "VstPluginDefs.h" +#include "public.sdk/source/main/pluginfactory.h" +#include "pluginterfaces/vst/ivstcomponent.h" +#include "pluginterfaces/vst/ivstaudioprocessor.h" +#include "pluginterfaces/vst/ivsteditcontroller.h" + +BEGIN_FACTORY_DEF(VSTPLUGIN_VENDOR, + VSTPLUGIN_URL, + "mailto:" VSTPLUGIN_EMAIL) + +DEF_CLASS2 (INLINE_UID_FROM_FUID(SfizzVstProcessor::cid), + PClassInfo::kManyInstances, + kVstAudioEffectClass, + VSTPLUGIN_NAME, + Vst::kDistributable, + Vst::PlugType::kInstrumentSynth, + VSTPLUGIN_VERSION, + kVstVersionString, + SfizzVstProcessor::createInstance) + +DEF_CLASS2 (INLINE_UID_FROM_FUID(SfizzVstController::cid), + PClassInfo::kManyInstances, + kVstComponentControllerClass, + VSTPLUGIN_NAME, + 0, // not used here + "", // not used here + VSTPLUGIN_VERSION, + kVstVersionString, + SfizzVstController::createInstance) + +END_FACTORY + +bool InitModule() +{ + return true; +} + +bool DeinitModule() +{ + return true; +} diff --git a/vst/cmake/Vst3.cmake b/vst/cmake/Vst3.cmake new file mode 100644 index 00000000..32b9b96a --- /dev/null +++ b/vst/cmake/Vst3.cmake @@ -0,0 +1,244 @@ +find_package(Threads REQUIRED) + +# --- VST3SDK --- +function(plugin_add_vst3sdk NAME) + target_sources("${NAME}" PRIVATE + "${VST3SDK_BASEDIR}/base/source/baseiids.cpp" + "${VST3SDK_BASEDIR}/base/source/fbuffer.cpp" + "${VST3SDK_BASEDIR}/base/source/fdebug.cpp" + "${VST3SDK_BASEDIR}/base/source/fdynlib.cpp" + "${VST3SDK_BASEDIR}/base/source/fobject.cpp" + "${VST3SDK_BASEDIR}/base/source/fstreamer.cpp" + "${VST3SDK_BASEDIR}/base/source/fstring.cpp" + # "${VST3SDK_BASEDIR}/base/source/timer.cpp" + "${VST3SDK_BASEDIR}/base/source/updatehandler.cpp" + "${VST3SDK_BASEDIR}/base/thread/source/fcondition.cpp" + "${VST3SDK_BASEDIR}/base/thread/source/flock.cpp" + "${VST3SDK_BASEDIR}/pluginterfaces/base/conststringtable.cpp" + "${VST3SDK_BASEDIR}/pluginterfaces/base/coreiids.cpp" + "${VST3SDK_BASEDIR}/pluginterfaces/base/funknown.cpp" + "${VST3SDK_BASEDIR}/pluginterfaces/base/ustring.cpp" + "${VST3SDK_BASEDIR}/public.sdk/source/common/commoniids.cpp" + "${VST3SDK_BASEDIR}/public.sdk/source/common/pluginview.cpp" + "${VST3SDK_BASEDIR}/public.sdk/source/main/pluginfactory.cpp" + "${VST3SDK_BASEDIR}/public.sdk/source/vst/vstaudioeffect.cpp" + "${VST3SDK_BASEDIR}/public.sdk/source/vst/vstbus.cpp" + "${VST3SDK_BASEDIR}/public.sdk/source/vst/vstcomponent.cpp" + "${VST3SDK_BASEDIR}/public.sdk/source/vst/vstcomponentbase.cpp" + "${VST3SDK_BASEDIR}/public.sdk/source/vst/vsteditcontroller.cpp" + "${VST3SDK_BASEDIR}/public.sdk/source/vst/vstinitiids.cpp" + "${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") + if(WIN32) + target_sources("${NAME}" PRIVATE + "${VST3SDK_BASEDIR}/public.sdk/source/common/threadchecker_win32.cpp" + "${VST3SDK_BASEDIR}/public.sdk/source/vst/vstgui_win32_bundle_support.cpp" + "${VST3SDK_BASEDIR}/public.sdk/source/main/dllmain.cpp") + elseif(APPLE) + target_sources("${NAME}" PRIVATE + "${VST3SDK_BASEDIR}/public.sdk/source/main/macmain.cpp") + else() + target_sources("${NAME}" PRIVATE + "${VST3SDK_BASEDIR}/public.sdk/source/common/threadchecker_linux.cpp" + "${VST3SDK_BASEDIR}/public.sdk/source/main/linuxmain.cpp") + endif() + target_include_directories("${NAME}" PRIVATE "${VST3SDK_BASEDIR}") + target_link_libraries("${NAME}" PRIVATE Threads::Threads) +endfunction() + +# --- VSTGUI --- +function(plugin_add_vstgui NAME) + target_sources("${NAME}" PRIVATE + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/animation/animations.cpp" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/animation/animator.cpp" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/animation/timingfunctions.cpp" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/cbitmap.cpp" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/cbitmapfilter.cpp" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/ccolor.cpp" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/cdatabrowser.cpp" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/cdrawcontext.cpp" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/cdrawmethods.cpp" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/cdropsource.cpp" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/cfileselector.cpp" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/cfont.cpp" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/cframe.cpp" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/cgradientview.cpp" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/cgraphicspath.cpp" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/clayeredviewcontainer.cpp" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/clinestyle.cpp" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/coffscreencontext.cpp" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/controls/cautoanimation.cpp" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/controls/cbuttons.cpp" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/controls/ccolorchooser.cpp" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/controls/ccontrol.cpp" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/controls/cfontchooser.cpp" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/controls/cknob.cpp" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/controls/clistcontrol.cpp" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/controls/cmoviebitmap.cpp" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/controls/cmoviebutton.cpp" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/controls/coptionmenu.cpp" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/controls/cparamdisplay.cpp" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/controls/cscrollbar.cpp" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/controls/csearchtextedit.cpp" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/controls/csegmentbutton.cpp" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/controls/cslider.cpp" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/controls/cspecialdigit.cpp" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/controls/csplashscreen.cpp" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/controls/cstringlist.cpp" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/controls/cswitch.cpp" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/controls/ctextedit.cpp" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/controls/ctextlabel.cpp" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/controls/cvumeter.cpp" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/controls/cxypad.cpp" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/copenglview.cpp" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/cpoint.cpp" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/crect.cpp" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/crowcolumnview.cpp" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/cscrollview.cpp" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/cshadowviewcontainer.cpp" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/csplitview.cpp" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/cstring.cpp" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/ctabview.cpp" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/ctooltipsupport.cpp" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/cview.cpp" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/cviewcontainer.cpp" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/cvstguitimer.cpp" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/genericstringlistdatabrowsersource.cpp" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/platform/common/genericoptionmenu.cpp" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/vstguidebug.cpp") + + if(WIN32) + target_sources("${NAME}" PRIVATE + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/platform/common/fileresourceinputstream.cpp" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/platform/win32/direct2d/d2dbitmap.cpp" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/platform/win32/direct2d/d2ddrawcontext.cpp" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/platform/win32/direct2d/d2dfont.cpp" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/platform/win32/direct2d/d2dgraphicspath.cpp" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/platform/win32/win32datapackage.cpp" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/platform/win32/win32dragging.cpp" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/platform/win32/win32frame.cpp" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/platform/win32/win32openglview.cpp" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/platform/win32/win32optionmenu.cpp" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/platform/win32/win32support.cpp" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/platform/win32/win32textedit.cpp" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/platform/win32/winfileselector.cpp" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/platform/win32/winstring.cpp" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/platform/win32/wintimer.cpp") + elseif(APPLE) + target_sources("${NAME}" PRIVATE + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/platform/common/fileresourceinputstream.cpp" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/platform/common/genericoptionmenu.cpp" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/platform/common/generictextedit.cpp" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/platform/mac/carbon/hiviewframe.cpp" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/platform/mac/carbon/hiviewoptionmenu.cpp" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/platform/mac/carbon/hiviewtextedit.cpp" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/platform/mac/caviewlayer.mm" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/platform/mac/cfontmac.mm" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/platform/mac/cgbitmap.cpp" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/platform/mac/cgdrawcontext.cpp" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/platform/mac/cocoa/autoreleasepool.mm" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/platform/mac/cocoa/cocoahelpers.mm" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/platform/mac/cocoa/cocoaopenglview.mm" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/platform/mac/cocoa/cocoatextedit.mm" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/platform/mac/cocoa/nsviewdraggingsession.mm" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/platform/mac/cocoa/nsviewframe.mm" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/platform/mac/cocoa/nsviewoptionmenu.mm" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/platform/mac/macclipboard.mm" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/platform/mac/macfileselector.mm" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/platform/mac/macglobals.cpp" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/platform/mac/macstring.mm" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/platform/mac/mactimer.cpp" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/platform/mac/quartzgraphicspath.cpp") + else() + target_sources("${NAME}" PRIVATE + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/platform/common/fileresourceinputstream.cpp" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/platform/common/generictextedit.cpp" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/platform/linux/cairobitmap.cpp" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/platform/linux/cairocontext.cpp" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/platform/linux/cairofont.cpp" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/platform/linux/cairogradient.cpp" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/platform/linux/cairopath.cpp" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/platform/linux/linuxstring.cpp" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/platform/linux/x11fileselector.cpp" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/platform/linux/x11frame.cpp" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/platform/linux/x11platform.cpp" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/platform/linux/x11timer.cpp" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/platform/linux/x11utils.cpp") + endif() + + target_include_directories("${NAME}" PRIVATE "${VST3SDK_BASEDIR}/vstgui4") + + if(WIN32) + # + elseif(APPLE) + # + else() + find_package(X11 REQUIRED) + find_package(Freetype REQUIRED) + find_package(PkgConfig REQUIRED) + pkg_check_modules(LIBXCB REQUIRED xcb) + pkg_check_modules(LIBXCB_UTIL REQUIRED xcb-util) + pkg_check_modules(LIBXCB_CURSOR REQUIRED xcb-cursor) + pkg_check_modules(LIBXCB_KEYSYMS REQUIRED xcb-keysyms) + pkg_check_modules(LIBXCB_XKB REQUIRED xcb-xkb) + pkg_check_modules(LIBXKB_COMMON REQUIRED xkbcommon) + pkg_check_modules(LIBXKB_COMMON_X11 REQUIRED xkbcommon-x11) + pkg_check_modules(CAIRO REQUIRED cairo) + pkg_check_modules(FONTCONFIG REQUIRED fontconfig) + target_include_directories("${NAME}" PRIVATE + ${X11_INCLUDE_DIRS} + ${FREETYPE_INCLUDE_DIRS} + ${LIBXCB_INCLUDE_DIRS} + ${LIBXCB_UTIL_INCLUDE_DIRS} + ${LIBXCB_CURSOR_INCLUDE_DIRS} + ${LIBXCB_KEYSYMS_INCLUDE_DIRS} + ${LIBXCB_XKB_INCLUDE_DIRS} + ${LIBXKB_COMMON_INCLUDE_DIRS} + ${LIBXKB_COMMON_X11_INCLUDE_DIRS} + ${CAIRO_INCLUDE_DIRS} + ${FONTCONFIG_INCLUDE_DIRS}) + target_link_libraries("${NAME}" PRIVATE + ${X11_LIBRARIES} + ${FREETYPE_LIBRARIES} + ${LIBXCB_LIBRARIES} + ${LIBXCB_UTIL_LIBRARIES} + ${LIBXCB_CURSOR_LIBRARIES} + ${LIBXCB_KEYSYMS_LIBRARIES} + ${LIBXCB_XKB_LIBRARIES} + ${LIBXKB_COMMON_LIBRARIES} + ${LIBXKB_COMMON_X11_LIBRARIES} + ${CAIRO_LIBRARIES} + ${FONTCONFIG_LIBRARIES}) + find_library(DL_LIBRARY "dl") + if(DL_LIBRARY) + target_link_libraries("${NAME}" PRIVATE "${DL_LIBRARY}") + endif() + endif() + + target_sources("${NAME}" PRIVATE + "${VST3SDK_BASEDIR}/public.sdk/source/vst/vstguieditor.cpp") + + target_include_directories("${NAME}" PRIVATE + external/steinberg/src) +endfunction() + +# --- VST3 Bundle architecture --- +if(NOT VST3_PACKAGE_ARCHITECTURE) + if(APPLE) + # VST3 packages are universal on Apple, architecture string not needed + else() + if(CMAKE_SYSTEM_PROCESSOR STREQUAL "x86_64") + set(VST3_PACKAGE_ARCHITECTURE "x86_64") + elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "^i.86$") + if(WIN32) + set(VST3_PACKAGE_ARCHITECTURE "x86") + else() + set(VST3_PACKAGE_ARCHITECTURE "i386") + endif() + else() + message(FATAL_ERROR "We don't know this architecture for VST3: ${CMAKE_SYSTEM_PROCESSOR}.") + endif() + endif() +endif() diff --git a/vst/external/steinberg/LICENSE b/vst/external/steinberg/LICENSE new file mode 100644 index 00000000..a75f1d5b --- /dev/null +++ b/vst/external/steinberg/LICENSE @@ -0,0 +1,27 @@ +//----------------------------------------------------------------------------- +// VSTGUI LICENSE +// (c) 2018, Steinberg Media Technologies, All Rights Reserved +//----------------------------------------------------------------------------- +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * 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. +// * Neither the name of the Steinberg Media Technologies nor the names of its +// contributors may be used to endorse or promote products derived from this +// software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, +// INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE +// OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED +// OF THE POSSIBILITY OF SUCH DAMAGE. +//----------------------------------------------------------------------------- diff --git a/vst/external/steinberg/src/x11runloop.h b/vst/external/steinberg/src/x11runloop.h new file mode 100644 index 00000000..7cece455 --- /dev/null +++ b/vst/external/steinberg/src/x11runloop.h @@ -0,0 +1,110 @@ +#include "vstgui/lib/platform/linux/x11frame.h" +#include "pluginterfaces/gui/iplugview.h" +#include "base/source/fstring.h" + +namespace VSTGUI { + +// Map Steinberg Vst Interface to VSTGUI Interface +class RunLoop : public X11::IRunLoop, public AtomicReferenceCounted +{ +public: + struct EventHandler : Steinberg::Linux::IEventHandler, public Steinberg::FObject + { + X11::IEventHandler* handler {nullptr}; + + void PLUGIN_API onFDIsSet (Steinberg::Linux::FileDescriptor) override + { + if (handler) + handler->onEvent (); + } + DELEGATE_REFCOUNT (Steinberg::FObject) + DEFINE_INTERFACES + DEF_INTERFACE (Steinberg::Linux::IEventHandler) + END_DEFINE_INTERFACES (Steinberg::FObject) + }; + struct TimerHandler : Steinberg::Linux::ITimerHandler, public Steinberg::FObject + { + X11::ITimerHandler* handler {nullptr}; + + void PLUGIN_API onTimer () final + { + if (handler) + handler->onTimer (); + } + DELEGATE_REFCOUNT (Steinberg::FObject) + DEFINE_INTERFACES + DEF_INTERFACE (Steinberg::Linux::ITimerHandler) + END_DEFINE_INTERFACES (Steinberg::FObject) + }; + + bool registerEventHandler (int fd, X11::IEventHandler* handler) final + { + if(!runLoop) + return false; + + auto smtgHandler = Steinberg::owned (new EventHandler ()); + smtgHandler->handler = handler; + if (runLoop->registerEventHandler (smtgHandler, fd) == Steinberg::kResultTrue) + { + eventHandlers.push_back (smtgHandler); + return true; + } + return false; + } + bool unregisterEventHandler (X11::IEventHandler* handler) final + { + if(!runLoop) + return false; + + for (auto it = eventHandlers.begin (), end = eventHandlers.end (); it != end; ++it) + { + if ((*it)->handler == handler) + { + runLoop->unregisterEventHandler ((*it)); + eventHandlers.erase (it); + return true; + } + } + return false; + } + bool registerTimer (uint64_t interval, X11::ITimerHandler* handler) final + { + if(!runLoop) + return false; + + auto smtgHandler = Steinberg::owned (new TimerHandler ()); + smtgHandler->handler = handler; + if (runLoop->registerTimer (smtgHandler, interval) == Steinberg::kResultTrue) + { + timerHandlers.push_back (smtgHandler); + return true; + } + return false; + } + bool unregisterTimer (X11::ITimerHandler* handler) final + { + if(!runLoop) + return false; + + for (auto it = timerHandlers.begin (), end = timerHandlers.end (); it != end; ++it) + { + if ((*it)->handler == handler) + { + runLoop->unregisterTimer ((*it)); + timerHandlers.erase (it); + return true; + } + } + return false; + } + + RunLoop (Steinberg::FUnknown* runLoop) : runLoop (runLoop) {} +private: + using EventHandlers = std::vector>; + using TimerHandlers = std::vector>; + EventHandlers eventHandlers; + TimerHandlers timerHandlers; + Steinberg::FUnknownPtr runLoop; +}; + +} // namespace diff --git a/vst/vst3.version b/vst/vst3.version new file mode 100644 index 00000000..f86d95c3 --- /dev/null +++ b/vst/vst3.version @@ -0,0 +1,7 @@ +VST3ABI_1.0 { + global: + *GetPluginFactory*; + *ModuleEntry*; + *ModuleExit*; + local: *; +}; From f315ba5e290fe283b5b9254cfe075055953debbb Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Thu, 5 Mar 2020 13:59:37 +0100 Subject: [PATCH 38/93] Add the macOS VST bundle --- vst/CMakeLists.txt | 11 ++++++++++- vst/mac/Info.plist | 24 ++++++++++++++++++++++++ vst/mac/PkgInfo | 1 + 3 files changed, 35 insertions(+), 1 deletion(-) create mode 100644 vst/mac/Info.plist create mode 100644 vst/mac/PkgInfo diff --git a/vst/CMakeLists.txt b/vst/CMakeLists.txt index 4cafc961..7eaa90f7 100644 --- a/vst/CMakeLists.txt +++ b/vst/CMakeLists.txt @@ -58,14 +58,23 @@ if (${CMAKE_SYSTEM_NAME} MATCHES "Linux") endif() # Create the bundle (see "VST 3 Locations / Format") +execute_process ( + COMMAND "${CMAKE_COMMAND}" -E make_directory "${CMAKE_CURRENT_BINARY_DIR}/${VSTPLUGIN_BUNDLE_NAME}/Contents") if(WIN32) set_target_properties(${VSTPLUGIN_PRJ_NAME} PROPERTIES LIBRARY_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/${VSTPLUGIN_BUNDLE_NAME}/Contents/${VST3_PACKAGE_ARCHITECTURE}-win") # TODO: make desktop.ini, Plugin.ico elseif(APPLE) set_target_properties(${VSTPLUGIN_PRJ_NAME} PROPERTIES + SUFFIX "" LIBRARY_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/${VSTPLUGIN_BUNDLE_NAME}/Contents/MacOS") - # TODO: make Info.plist, PkgInfo + file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/mac/PkgInfo" + DESTINATION "${CMAKE_CURRENT_BINARY_DIR}/${VSTPLUGIN_BUNDLE_NAME}/Contents") + set(SFIZZ_VST3_BUNDLE_EXECUTABLE "${PROJECT_NAME}") + set(SFIZZ_VST3_BUNDLE_VERSION "${PROJECT_VERSION}") + configure_file("${CMAKE_CURRENT_SOURCE_DIR}/mac/Info.plist" + "${CMAKE_CURRENT_BINARY_DIR}/${VSTPLUGIN_BUNDLE_NAME}/Contents/Info.plist" @ONLY) + # TODO: create icons as sfizz.icns, and fill it in as CFBundleIconFile else() set_target_properties(${VSTPLUGIN_PRJ_NAME} PROPERTIES LIBRARY_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/${VSTPLUGIN_BUNDLE_NAME}/Contents/${VST3_PACKAGE_ARCHITECTURE}-linux") diff --git a/vst/mac/Info.plist b/vst/mac/Info.plist new file mode 100644 index 00000000..00c2b750 --- /dev/null +++ b/vst/mac/Info.plist @@ -0,0 +1,24 @@ + + + + + CFBundleDevelopmentRegion + English + CFBundleExecutable + @SFIZZ_VST3_BUNDLE_EXECUTABLE@ + CFBundleIconFile + + CFBundleIdentifier + tools.sfz.sfizz + CFBundleInfoDictionaryVersion + 6.0 + CFBundlePackageType + BNDL + CFBundleSignature + ???? + CFBundleVersion + @SFIZZ_VST3_BUNDLE_VERSION@ + CSResourcesFileMapped + + + diff --git a/vst/mac/PkgInfo b/vst/mac/PkgInfo new file mode 100644 index 00000000..19a9cf67 --- /dev/null +++ b/vst/mac/PkgInfo @@ -0,0 +1 @@ +BNDL???? \ No newline at end of file From 29cf293d95494b0bd1c034278cf093fce504d364 Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Thu, 5 Mar 2020 15:05:05 +0100 Subject: [PATCH 39/93] Add bundle icons of Windows and Mac --- scripts/create_mac_icon.sh | 18 ++++++++ scripts/create_windows_icon.sh | 18 ++++++++ vst/CMakeLists.txt | 7 ++- vst/mac/Info.plist | 2 +- vst/mac/Plugin.icns | Bin 0 -> 34703 bytes vst/resources/logo.svg | 78 +++++++++++++++++++++++++++++++++ vst/win/Plugin.ico | Bin 0 -> 351974 bytes vst/win/desktop.ini | 2 + 8 files changed, 122 insertions(+), 3 deletions(-) create mode 100755 scripts/create_mac_icon.sh create mode 100755 scripts/create_windows_icon.sh create mode 100644 vst/mac/Plugin.icns create mode 100644 vst/resources/logo.svg create mode 100644 vst/win/Plugin.ico create mode 100644 vst/win/desktop.ini diff --git a/scripts/create_mac_icon.sh b/scripts/create_mac_icon.sh new file mode 100755 index 00000000..3467da93 --- /dev/null +++ b/scripts/create_mac_icon.sh @@ -0,0 +1,18 @@ +#!/bin/bash +set -e + +svg_file="$1" +test -z "$svg_file" && exit 1 + +sizes="32 48 128 256" + +rm -f "$svg_file".icon.*.png + +for size in $sizes; do + png_file="$svg_file".icon."$size".png + inkscape -e "$png_file" "$svg_file" -w "$size" -h "$size" + optipng "$png_file" +done + +png2icns "$svg_file".icns "$svg_file".icon.*.png +rm -f "$svg_file".icon.*.png diff --git a/scripts/create_windows_icon.sh b/scripts/create_windows_icon.sh new file mode 100755 index 00000000..710fbdc8 --- /dev/null +++ b/scripts/create_windows_icon.sh @@ -0,0 +1,18 @@ +#!/bin/bash +set -e + +svg_file="$1" +test -z "$svg_file" && exit 1 + +sizes="32 48 128 256" + +rm -f "$svg_file".icon.*.png + +for size in $sizes; do + png_file="$svg_file".icon."$size".png + inkscape -e "$png_file" "$svg_file" -w "$size" -h "$size" + optipng "$png_file" +done + +icotool -c -o "$svg_file".ico "$svg_file".icon.*.png +rm -f "$svg_file".icon.*.png diff --git a/vst/CMakeLists.txt b/vst/CMakeLists.txt index 7eaa90f7..808660c0 100644 --- a/vst/CMakeLists.txt +++ b/vst/CMakeLists.txt @@ -63,7 +63,9 @@ execute_process ( if(WIN32) set_target_properties(${VSTPLUGIN_PRJ_NAME} PROPERTIES LIBRARY_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/${VSTPLUGIN_BUNDLE_NAME}/Contents/${VST3_PACKAGE_ARCHITECTURE}-win") - # TODO: make desktop.ini, Plugin.ico + file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/win/Plugin.ico" + "${CMAKE_CURRENT_SOURCE_DIR}/win/desktop.ini" + DESTINATION "${CMAKE_CURRENT_BINARY_DIR}/${VSTPLUGIN_BUNDLE_NAME}") elseif(APPLE) set_target_properties(${VSTPLUGIN_PRJ_NAME} PROPERTIES SUFFIX "" @@ -74,7 +76,8 @@ elseif(APPLE) set(SFIZZ_VST3_BUNDLE_VERSION "${PROJECT_VERSION}") configure_file("${CMAKE_CURRENT_SOURCE_DIR}/mac/Info.plist" "${CMAKE_CURRENT_BINARY_DIR}/${VSTPLUGIN_BUNDLE_NAME}/Contents/Info.plist" @ONLY) - # TODO: create icons as sfizz.icns, and fill it in as CFBundleIconFile + file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/mac/Plugin.icns" + DESTINATION "${CMAKE_CURRENT_BINARY_DIR}/${VSTPLUGIN_BUNDLE_NAME}/Contents/Resources") else() set_target_properties(${VSTPLUGIN_PRJ_NAME} PROPERTIES LIBRARY_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/${VSTPLUGIN_BUNDLE_NAME}/Contents/${VST3_PACKAGE_ARCHITECTURE}-linux") diff --git a/vst/mac/Info.plist b/vst/mac/Info.plist index 00c2b750..fe09355f 100644 --- a/vst/mac/Info.plist +++ b/vst/mac/Info.plist @@ -7,7 +7,7 @@ CFBundleExecutable @SFIZZ_VST3_BUNDLE_EXECUTABLE@ CFBundleIconFile - + Plugin.icns CFBundleIdentifier tools.sfz.sfizz CFBundleInfoDictionaryVersion diff --git a/vst/mac/Plugin.icns b/vst/mac/Plugin.icns new file mode 100644 index 0000000000000000000000000000000000000000..f8ce53c4c6ff49a7b7db1d70664f955ff0e68c83 GIT binary patch literal 34703 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 literal 0 HcmV?d00001 diff --git a/vst/resources/logo.svg b/vst/resources/logo.svg new file mode 100644 index 00000000..f4a65f2c --- /dev/null +++ b/vst/resources/logo.svg @@ -0,0 +1,78 @@ + + + + +Created by potrace 1.11, written by Peter Selinger 2001-2013 + + + + + + + + + + diff --git a/vst/win/Plugin.ico b/vst/win/Plugin.ico new file mode 100644 index 0000000000000000000000000000000000000000..516565763840869a9599eb1fecf90b068b7f048c GIT binary patch literal 351974 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 literal 0 HcmV?d00001 diff --git a/vst/win/desktop.ini b/vst/win/desktop.ini new file mode 100644 index 00000000..be3001a9 --- /dev/null +++ b/vst/win/desktop.ini @@ -0,0 +1,2 @@ +[.ShellClassInfo] +IconResource=Plugin.ico,0 From 8fc43bc0be963659d4e95609cb83eab9e5a91799 Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Thu, 5 Mar 2020 15:53:01 +0100 Subject: [PATCH 40/93] Update of VST UI --- vst/CMakeLists.txt | 4 ++- vst/SfizzVstEditor.cpp | 79 ++++++++++++++++++++++++++++------------- vst/SfizzVstEditor.h | 1 + vst/resources/logo.png | Bin 0 -> 20594 bytes 4 files changed, 58 insertions(+), 26 deletions(-) create mode 100644 vst/resources/logo.png diff --git a/vst/CMakeLists.txt b/vst/CMakeLists.txt index 808660c0..b9217f22 100644 --- a/vst/CMakeLists.txt +++ b/vst/CMakeLists.txt @@ -59,7 +59,9 @@ endif() # Create the bundle (see "VST 3 Locations / Format") execute_process ( - COMMAND "${CMAKE_COMMAND}" -E make_directory "${CMAKE_CURRENT_BINARY_DIR}/${VSTPLUGIN_BUNDLE_NAME}/Contents") + COMMAND "${CMAKE_COMMAND}" -E make_directory "${CMAKE_CURRENT_BINARY_DIR}/${VSTPLUGIN_BUNDLE_NAME}/Contents/Resources") +file (COPY "${CMAKE_CURRENT_SOURCE_DIR}/resources/logo.png" + DESTINATION "${CMAKE_CURRENT_BINARY_DIR}/${VSTPLUGIN_BUNDLE_NAME}/Contents/Resources") if(WIN32) set_target_properties(${VSTPLUGIN_PRJ_NAME} PROPERTIES LIBRARY_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/${VSTPLUGIN_BUNDLE_NAME}/Contents/${VST3_PACKAGE_ARCHITECTURE}-win") diff --git a/vst/SfizzVstEditor.cpp b/vst/SfizzVstEditor.cpp index 46bd7139..adad6f6d 100644 --- a/vst/SfizzVstEditor.cpp +++ b/vst/SfizzVstEditor.cpp @@ -12,11 +12,9 @@ using namespace VSTGUI; -static constexpr int kEditorWidth = 800; -static constexpr int kEditorHeight = 40; - SfizzVstEditor::SfizzVstEditor(void *controller) - : VSTGUIEditor(controller) + : VSTGUIEditor(controller), + _logo("logo.png") { static_cast(getController())->addStateListener(this); } @@ -28,7 +26,7 @@ SfizzVstEditor::~SfizzVstEditor() bool PLUGIN_API SfizzVstEditor::open(void* parent, const VSTGUI::PlatformType& platformType) { - CRect wsize(0, 0, kEditorWidth, kEditorHeight); + CRect wsize(0, 0, _logo.getWidth(), _logo.getHeight()); CFrame *frame = new CFrame(wsize, this); this->frame = frame; @@ -67,8 +65,7 @@ void SfizzVstEditor::valueChanged(CControl* ctl) if (value != 1) break; - chooseSfzFile(); - + Call::later([this]() { chooseSfzFile(); }); break; } } @@ -95,18 +92,23 @@ void SfizzVstEditor::chooseSfzFile() void SfizzVstEditor::loadSfzFile(const std::string& filePath) { - _fileLabel->setText(filePath.c_str()); - Vst::EditController* ctl = getController(); + Vst::IMessage *msg = ctl->allocateMessage(); - if (msg) { - msg->setMessageID("LoadSfz"); - Vst::IAttributeList* attr = msg->getAttributes(); - attr->setString("File", Steinberg::String(filePath.c_str()).text()); - ctl->sendMessage(msg); - msg->release(); + if (!msg) { + fprintf(stderr, "[Sfizz] UI could not allocate message\n"); + return; } + + msg->setMessageID("LoadSfz"); + Vst::IAttributeList* attr = msg->getAttributes(); + attr->setString("File", Steinberg::String(filePath.c_str()).text()); + ctl->sendMessage(msg); + msg->release(); + + if (_fileLabel) + _fileLabel->setText(("File: " + filePath).c_str()); } /// @@ -156,17 +158,43 @@ void SfizzVstEditor::createFrameContents() CFrame* frame = this->frame; CRect bounds = frame->getViewSize(); - CTextLabel *label; - CRect rect; - CRect rect2; + frame->setBackgroundColor(CColor(0xff, 0xff, 0xff)); - rect = CRect(10.0, 10.0, 120.0, 30.0); - frame->addView(new SimpleButton(rect, this, kTagLoadSfzFile, "Load SFZ file")); + CKickButton* sfizzButton = new CKickButton(bounds, this, kTagLoadSfzFile, &_logo); + frame->addView(sfizzButton); - rect2 = CRect(150.0, 10.0, bounds.right - 10.0, 30.0); - frame->addView((label = new CTextLabel(rect2, "no file"))); - label->setHoriAlign(kLeftText); - _fileLabel = label; + CRect bottomRow = bounds; + bottomRow.top = bottomRow.bottom - 30; + + CRect topRow = bounds; + topRow.bottom = topRow.top + 30; + + CTextLabel* descLabel = new CTextLabel( + bottomRow, "Paul Ferrand and the SFZ Tools work group"); + descLabel->setFontColor(CColor(0x00, 0x00, 0x00)); + descLabel->setBackColor(CColor(0x00, 0x00, 0x00, 0x00)); + frame->addView(descLabel); + + CRect fileBox = topRow; + fileBox.right = fileBox.left + 400; + CTextLabel* fileLabel = new CTextLabel(fileBox, "No file loaded"); + fileLabel->setFontColor(CColor(0x00, 0x00, 0x00)); + fileLabel->setBackColor(CColor(0x00, 0x00, 0x00, 0x00)); + // fileLabel->setHoriAlign(kLeftText); + frame->addView(fileLabel); + _fileLabel = fileLabel; + + // CTextLabel *label; + // CRect rect; + // CRect rect2; + + // rect = CRect(10.0, 10.0, 120.0, 30.0); + // frame->addView(new SimpleButton(rect, this, kTagLoadSfzFile, "Load SFZ file")); + + // rect2 = CRect(150.0, 10.0, bounds.right - 10.0, 30.0); + // frame->addView((label = new CTextLabel(rect2, "no file"))); + // label->setHoriAlign(kLeftText); + // _fileLabel = label; } void SfizzVstEditor::updateStateDisplay() @@ -176,5 +204,6 @@ void SfizzVstEditor::updateStateDisplay() const SfizzVstState& state = static_cast(getController())->getSfizzState(); - _fileLabel->setText(state.sfzFile.c_str()); + if (_fileLabel) + _fileLabel->setText(("File: " + state.sfzFile).c_str()); } diff --git a/vst/SfizzVstEditor.h b/vst/SfizzVstEditor.h index 4c3c44c5..44839788 100644 --- a/vst/SfizzVstEditor.h +++ b/vst/SfizzVstEditor.h @@ -36,5 +36,6 @@ private: kTagLoadSfzFile, }; + CBitmap _logo; CTextLabel* _fileLabel = nullptr; }; diff --git a/vst/resources/logo.png b/vst/resources/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..e67a60a3b53ed37a95de6223ed33633d6f43107a GIT binary patch literal 20594 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 literal 0 HcmV?d00001 From 092b95fc3c6785b460c5d6c1d6ba9869563f5abe Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Thu, 5 Mar 2020 16:41:59 +0100 Subject: [PATCH 41/93] Update VST UI --- vst/SfizzVstEditor.cpp | 145 ++++++++++++++++++++++------------------- vst/SfizzVstEditor.h | 13 ++++ 2 files changed, 91 insertions(+), 67 deletions(-) diff --git a/vst/SfizzVstEditor.cpp b/vst/SfizzVstEditor.cpp index adad6f6d..4d5820d1 100644 --- a/vst/SfizzVstEditor.cpp +++ b/vst/SfizzVstEditor.cpp @@ -67,6 +67,11 @@ void SfizzVstEditor::valueChanged(CControl* ctl) Call::later([this]() { chooseSfzFile(); }); break; + + default: + if (tag >= kTagFirstChangePanel && tag <= kTagLastChangePanel) + setActivePanel(tag - kTagFirstChangePanel); + break; } } @@ -111,48 +116,6 @@ void SfizzVstEditor::loadSfzFile(const std::string& filePath) _fileLabel->setText(("File: " + filePath).c_str()); } -/// -class SimpleButton : public CControl { -public: - explicit SimpleButton(const CRect& size, IControlListener* listener = nullptr, int32_t tag = -1, UTF8StringPtr title = nullptr) - : CControl(size, listener, tag), _title(title ? title : "") { - } - - void draw(CDrawContext *dc) override - { - CRect bounds = getViewSize(); - dc->setFrameColor(CColor(0xff, 0xff, 0xff)); - dc->drawRect(bounds, kDrawStroked); - dc->drawString(_title.c_str(), bounds); - } - - CMouseEventResult onMouseDown(CPoint& where, const CButtonState& buttons) override - { - if (!buttons.isLeftButton()) - return kMouseEventNotHandled; - - value = getMin(); - if (isDirty()) { - valueChanged(); - invalid(); - } - value = getMax(); - if (isDirty()) - { - valueChanged(); - invalid(); - } - - return kMouseEventHandled; - } - - CLASS_METHODS(SimpleButton, CControl) - -private: - std::string _title; -}; - -/// void SfizzVstEditor::createFrameContents() { CFrame* frame = this->frame; @@ -160,41 +123,80 @@ void SfizzVstEditor::createFrameContents() frame->setBackgroundColor(CColor(0xff, 0xff, 0xff)); - CKickButton* sfizzButton = new CKickButton(bounds, this, kTagLoadSfzFile, &_logo); - frame->addView(sfizzButton); - CRect bottomRow = bounds; bottomRow.top = bottomRow.bottom - 30; CRect topRow = bounds; topRow.bottom = topRow.top + 30; - CTextLabel* descLabel = new CTextLabel( - bottomRow, "Paul Ferrand and the SFZ Tools work group"); - descLabel->setFontColor(CColor(0x00, 0x00, 0x00)); - descLabel->setBackColor(CColor(0x00, 0x00, 0x00, 0x00)); - frame->addView(descLabel); + CViewContainer* panel; + _activePanel = kPanelGeneral; - CRect fileBox = topRow; - fileBox.right = fileBox.left + 400; - CTextLabel* fileLabel = new CTextLabel(fileBox, "No file loaded"); - fileLabel->setFontColor(CColor(0x00, 0x00, 0x00)); - fileLabel->setBackColor(CColor(0x00, 0x00, 0x00, 0x00)); - // fileLabel->setHoriAlign(kLeftText); - frame->addView(fileLabel); - _fileLabel = fileLabel; + CRect topLeftLabelBox = topRow; + topLeftLabelBox.right -= 20 * kNumPanels; - // CTextLabel *label; - // CRect rect; - // CRect rect2; + // general panel + { + panel = new CViewContainer(bounds); + frame->addView(panel); + panel->setTransparency(true); - // rect = CRect(10.0, 10.0, 120.0, 30.0); - // frame->addView(new SimpleButton(rect, this, kTagLoadSfzFile, "Load SFZ file")); + CKickButton* sfizzButton = new CKickButton(bounds, this, kTagLoadSfzFile, &_logo); + panel->addView(sfizzButton); - // rect2 = CRect(150.0, 10.0, bounds.right - 10.0, 30.0); - // frame->addView((label = new CTextLabel(rect2, "no file"))); - // label->setHoriAlign(kLeftText); - // _fileLabel = label; + CTextLabel* topLeftLabel = new CTextLabel(topLeftLabelBox, "No file loaded"); + topLeftLabel->setFontColor(CColor(0x00, 0x00, 0x00)); + topLeftLabel->setBackColor(CColor(0x00, 0x00, 0x00, 0x00)); + panel->addView(topLeftLabel); + _fileLabel = topLeftLabel; + + _subPanels[kPanelGeneral] = panel; + } + + // settings panel + { + panel = new CViewContainer(bounds); + frame->addView(panel); + panel->setTransparency(true); + + CTextLabel* topLeftLabel = new CTextLabel(topLeftLabelBox, "Settings"); + topLeftLabel->setFontColor(CColor(0x00, 0x00, 0x00)); + topLeftLabel->setBackColor(CColor(0x00, 0x00, 0x00, 0x00)); + panel->addView(topLeftLabel); + + _subPanels[kPanelSettings] = panel; + } + + // all panels + for (unsigned currentPanel = 0; currentPanel < kNumPanels; ++currentPanel) { + panel = _subPanels[currentPanel]; + + CTextLabel* descLabel = new CTextLabel( + bottomRow, "Paul Ferrand and the SFZ Tools work group"); + descLabel->setFontColor(CColor(0x00, 0x00, 0x00)); + descLabel->setBackColor(CColor(0x00, 0x00, 0x00, 0x00)); + panel->addView(descLabel); + + for (unsigned i = 0; i < kNumPanels; ++i) { + CRect btnRect = topRow; + btnRect.left = topRow.right - (kNumPanels - i) * 20; + btnRect.right = btnRect.left + 20; + + const char *text; + switch (i) { + case kPanelGeneral: text = "G"; break; + case kPanelSettings: text = "S"; break; + default: text = "?"; break; + } + + CTextButton* changePanelButton = new CTextButton(btnRect, this, kTagFirstChangePanel + i, text); + panel->addView(changePanelButton); + + changePanelButton->setRoundRadius(0.0); + } + + panel->setVisible(currentPanel == _activePanel); + } } void SfizzVstEditor::updateStateDisplay() @@ -207,3 +209,12 @@ void SfizzVstEditor::updateStateDisplay() if (_fileLabel) _fileLabel->setText(("File: " + state.sfzFile).c_str()); } + +void SfizzVstEditor::setActivePanel(unsigned panelId) +{ + if (_activePanel != panelId) { + _subPanels[_activePanel]->setVisible(false); + _subPanels[panelId]->setVisible(true); + _activePanel = panelId; + } +} diff --git a/vst/SfizzVstEditor.h b/vst/SfizzVstEditor.h index 44839788..b293be5c 100644 --- a/vst/SfizzVstEditor.h +++ b/vst/SfizzVstEditor.h @@ -31,9 +31,22 @@ private: void createFrameContents(); void updateStateDisplay(); + void setActivePanel(unsigned panelId); + + enum { + kPanelGeneral, + // kPanelControls, + kPanelSettings, + kNumPanels, + }; + + unsigned _activePanel = 0; + CViewContainer* _subPanels[kNumPanels] = {}; enum { kTagLoadSfzFile, + kTagFirstChangePanel, + kTagLastChangePanel = kTagFirstChangePanel + kNumPanels - 1, }; CBitmap _logo; From d8b1a79fe7784f90569c9f666f1b7bbf0786a421 Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Thu, 5 Mar 2020 18:21:46 +0100 Subject: [PATCH 42/93] Add other parameter: volume + the UI --- vst/CMakeLists.txt | 1 + vst/GUIComponents.cpp | 33 ++++++ vst/GUIComponents.h | 23 ++++ vst/SfizzVstController.cpp | 61 ++++++++-- vst/SfizzVstController.h | 20 ++-- vst/SfizzVstEditor.cpp | 149 +++++++++++++++++++++++-- vst/SfizzVstEditor.h | 18 +++ vst/SfizzVstProcessor.cpp | 223 ++++++++++++++++++++++--------------- vst/SfizzVstProcessor.h | 22 ++-- vst/SfizzVstState.cpp | 47 ++++++-- vst/SfizzVstState.h | 53 +++++++++ 11 files changed, 515 insertions(+), 135 deletions(-) create mode 100644 vst/GUIComponents.cpp create mode 100644 vst/GUIComponents.h diff --git a/vst/CMakeLists.txt b/vst/CMakeLists.txt index b9217f22..0d8c505f 100644 --- a/vst/CMakeLists.txt +++ b/vst/CMakeLists.txt @@ -40,6 +40,7 @@ add_library(${VSTPLUGIN_PRJ_NAME} MODULE SfizzVstController.cpp SfizzVstEditor.cpp SfizzVstState.cpp + GUIComponents.cpp VstPluginFactory.cpp) target_link_libraries(${VSTPLUGIN_PRJ_NAME} PRIVATE ${PROJECT_NAME}::${PROJECT_NAME}) diff --git a/vst/GUIComponents.cpp b/vst/GUIComponents.cpp new file mode 100644 index 00000000..314ba642 --- /dev/null +++ b/vst/GUIComponents.cpp @@ -0,0 +1,33 @@ +// 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 "GUIComponents.h" +#include "vstgui/lib/cdrawcontext.h" + +SimpleSlider::SimpleSlider(const CRect& bounds, IControlListener* listener, int32_t tag) + : CSliderBase(bounds, listener, tag) +{ + setStyle(kHorizontal|kLeft); + + CPoint offsetHandle(2.0, 2.0); + setOffsetHandle(offsetHandle); + + CCoord handleSize = 20.0; + setHandleSizePrivate(handleSize, bounds.bottom - bounds.top - 2 * offsetHandle.y); + setHandleRangePrivate(bounds.right - bounds.left - handleSize - 2 * offsetHandle.x); +} + +void SimpleSlider::draw(CDrawContext* dc) +{ + CRect bounds = getViewSize(); + CRect handle = calculateHandleRect(getValueNormalized()); + + dc->setFrameColor(_frame); + dc->drawRect(bounds, kDrawStroked); + + dc->setFillColor(_fill); + dc->drawRect(handle, kDrawFilled); +} diff --git a/vst/GUIComponents.h b/vst/GUIComponents.h new file mode 100644 index 00000000..003d219b --- /dev/null +++ b/vst/GUIComponents.h @@ -0,0 +1,23 @@ +// 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 "vstgui/lib/controls/cslider.h" +#include "vstgui/lib/ccolor.h" + +using namespace VSTGUI; + +class SimpleSlider : public CSliderBase { +public: + SimpleSlider(const CRect& bounds, IControlListener* listener, int32_t tag); + void draw(CDrawContext* dc) override; + + CLASS_METHODS(SimpleSlider, CSliderBase) + +private: + CColor _frame = CColor(0x00, 0x00, 0x00); + CColor _fill = CColor(0x00, 0x00, 0x00); +}; diff --git a/vst/SfizzVstController.cpp b/vst/SfizzVstController.cpp index 88fcc6f0..fc7cfe5e 100644 --- a/vst/SfizzVstController.cpp +++ b/vst/SfizzVstController.cpp @@ -17,8 +17,14 @@ tresult PLUGIN_API SfizzVstControllerNoUi::initialize(FUnknown* context) Vst::ParamID pid = 0; + // Ordinary parameters + parameters.addParameter( + kParamVolumeRange.createParameter( + Steinberg::String("Volume"), pid++, Steinberg::String("dB"), + 0, Vst::ParameterInfo::kCanAutomate, Vst::kRootUnitId)); + // MIDI controllers - for (unsigned i = 0; i < numControllerParams; ++i) { + for (unsigned i = 0; i < kNumControllerParams; ++i) { Steinberg::String title; Steinberg::String shortTitle; title.printf("Controller %u", i); @@ -53,7 +59,7 @@ tresult PLUGIN_API SfizzVstControllerNoUi::getMidiControllerAssignment(int32 bus return kResultTrue; default: - if (midiControllerNumber < 0 || midiControllerNumber >= numControllerParams) + if (midiControllerNumber < 0 || midiControllerNumber >= kNumControllerParams) return kResultFalse; id = kPidMidiCC0 + midiControllerNumber; @@ -73,15 +79,53 @@ IPlugView* PLUGIN_API SfizzVstController::createView(FIDString _name) return new SfizzVstEditor(this); } -tresult PLUGIN_API SfizzVstController::setParamNormalized(Vst::ParamID tag, Vst::ParamValue value) +tresult PLUGIN_API SfizzVstController::setParamNormalized(Vst::ParamID tag, Vst::ParamValue normValue) { - tresult r = SfizzVstControllerNoUi::setParamNormalized(tag, value); + tresult r = SfizzVstControllerNoUi::setParamNormalized(tag, normValue); if (r != kResultTrue) return r; + float *slot = nullptr; + float value = 0; + + switch (tag) { + case kPidVolume: { + slot = &_state.volume; + value = kParamVolumeRange.denormalize(normValue); + break; + } + } + + if (slot && *slot != value) { + *slot = value; + for (StateListener* listener : _stateListeners) + listener->onStateChanged(); + } + return kResultTrue; } +tresult PLUGIN_API SfizzVstController::setState(IBStream* state) +{ + SfizzUiState s; + + tresult r = s.load(state); + if (r != kResultTrue) + return r; + + _uiState = s; + + for (StateListener* listener : _stateListeners) + listener->onStateChanged(); + + return kResultTrue; +} + +tresult PLUGIN_API SfizzVstController::getState(IBStream* state) +{ + return _uiState.store(state); +} + tresult PLUGIN_API SfizzVstController::setComponentState(IBStream* state) { SfizzVstState s; @@ -90,19 +134,22 @@ tresult PLUGIN_API SfizzVstController::setComponentState(IBStream* state) if (r != kResultTrue) return r; + _state = s; + + setParamNormalized(kPidVolume, kParamVolumeRange.normalize(s.volume)); + for (StateListener* listener : _stateListeners) listener->onStateChanged(); - _state = s; return kResultTrue; } -void SfizzVstController::addStateListener(StateListener* listener) +void SfizzVstController::addSfizzStateListener(StateListener* listener) { _stateListeners.push_back(listener); } -void SfizzVstController::removeStateListener(StateListener* listener) +void SfizzVstController::removeSfizzStateListener(StateListener* listener) { auto it = std::find(_stateListeners.begin(), _stateListeners.end(), listener); if (it != _stateListeners.end()) diff --git a/vst/SfizzVstController.h b/vst/SfizzVstController.h index 47c3ba3c..e8ca3037 100644 --- a/vst/SfizzVstController.h +++ b/vst/SfizzVstController.h @@ -24,22 +24,12 @@ public: tresult PLUGIN_API getMidiControllerAssignment(int32 busIndex, int16 channel, Vst::CtrlNumber midiControllerNumber, Vst::ParamID& id) override; - enum { numControllerParams = 128 }; - // interfaces OBJ_METHODS(SfizzVstControllerNoUi, Vst::EditController) DEFINE_INTERFACES DEF_INTERFACE(Vst::IMidiMapping) END_DEFINE_INTERFACES(Vst::EditController) REFCOUNT_METHODS(Vst::EditController) - - enum { - kPidMidiCC0, - kPidMidiCCLast = kPidMidiCC0 + numControllerParams - 1, - kPidMidiAftertouch, - kPidMidiPitchBend, - /* Reserved */ - }; }; class SfizzVstController : public SfizzVstControllerNoUi, public VSTGUI::VST3EditorDelegate { @@ -47,6 +37,8 @@ public: IPlugView* PLUGIN_API createView(FIDString name) override; tresult PLUGIN_API setParamNormalized(Vst::ParamID tag, Vst::ParamValue value) override; + tresult PLUGIN_API setState(IBStream* state) override; + tresult PLUGIN_API getState(IBStream* state) override; tresult PLUGIN_API setComponentState(IBStream* state) override; struct StateListener { @@ -55,8 +47,11 @@ public: const SfizzVstState& getSfizzState() const { return _state; } - void addStateListener(StateListener* listener); - void removeStateListener(StateListener* listener); + const SfizzUiState& getSfizzUiState() const { return _uiState; } + SfizzUiState& getSfizzUiState() { return _uiState; } + + void addSfizzStateListener(StateListener* listener); + void removeSfizzStateListener(StateListener* listener); /// static FUnknown* createInstance(void*); @@ -65,5 +60,6 @@ public: private: SfizzVstState _state; + SfizzUiState _uiState; std::vector _stateListeners; }; diff --git a/vst/SfizzVstEditor.cpp b/vst/SfizzVstEditor.cpp index 4d5820d1..9b197331 100644 --- a/vst/SfizzVstEditor.cpp +++ b/vst/SfizzVstEditor.cpp @@ -6,6 +6,7 @@ #include "SfizzVstEditor.h" #include "SfizzVstState.h" +#include "GUIComponents.h" #if !defined(__APPLE__) && !defined(_WIN32) #include "x11runloop.h" #endif @@ -16,12 +17,12 @@ SfizzVstEditor::SfizzVstEditor(void *controller) : VSTGUIEditor(controller), _logo("logo.png") { - static_cast(getController())->addStateListener(this); + getController()->addSfizzStateListener(this); } SfizzVstEditor::~SfizzVstEditor() { - static_cast(getController())->removeStateListener(this); + getController()->removeSfizzStateListener(this); } bool PLUGIN_API SfizzVstEditor::open(void* parent, const VSTGUI::PlatformType& platformType) @@ -59,6 +60,8 @@ void SfizzVstEditor::valueChanged(CControl* ctl) { int32_t tag = ctl->getTag(); float value = ctl->getValue(); + float valueNorm = ctl->getValueNormalized(); + SfizzVstController* controller = getController(); switch (tag) { case kTagLoadSfzFile: @@ -68,6 +71,11 @@ void SfizzVstEditor::valueChanged(CControl* ctl) Call::later([this]() { chooseSfzFile(); }); break; + case kTagSetVolume: + controller->setParamNormalized(kPidVolume, valueNorm); + controller->performEdit(kPidVolume, valueNorm); + break; + default: if (tag >= kTagFirstChangePanel && tag <= kTagLastChangePanel) setActivePanel(tag - kTagFirstChangePanel); @@ -75,6 +83,33 @@ void SfizzVstEditor::valueChanged(CControl* ctl) } } +void SfizzVstEditor::enterOrLeaveEdit(CControl* ctl, bool enter) +{ + int32_t tag = ctl->getTag(); + Vst::ParamID id; + + switch (tag) { + case kTagSetVolume: id = kPidVolume; break; + default: return; + } + + SfizzVstController* controller = getController(); + if (enter) + controller->beginEdit(id); + else + controller->endEdit(id); +} + +void SfizzVstEditor::controlBeginEdit(CControl* ctl) +{ + enterOrLeaveEdit(ctl, true); +} + +void SfizzVstEditor::controlEndEdit(CControl* ctl) +{ + enterOrLeaveEdit(ctl, false); +} + void SfizzVstEditor::onStateChanged() { updateStateDisplay(); @@ -97,8 +132,7 @@ void SfizzVstEditor::chooseSfzFile() void SfizzVstEditor::loadSfzFile(const std::string& filePath) { - Vst::EditController* ctl = getController(); - + SfizzVstController* ctl = getController(); Vst::IMessage *msg = ctl->allocateMessage(); if (!msg) { @@ -118,6 +152,9 @@ void SfizzVstEditor::loadSfzFile(const std::string& filePath) void SfizzVstEditor::createFrameContents() { + SfizzVstController* controller = getController(); + const SfizzUiState& uiState = controller->getSfizzUiState(); + CFrame* frame = this->frame; CRect bounds = frame->getViewSize(); @@ -130,7 +167,7 @@ void SfizzVstEditor::createFrameContents() topRow.bottom = topRow.top + 30; CViewContainer* panel; - _activePanel = kPanelGeneral; + _activePanel = std::max(0, std::min(kNumPanels - 1, static_cast(uiState.activePanel))); CRect topLeftLabelBox = topRow; topLeftLabelBox.right -= 20 * kNumPanels; @@ -164,6 +201,88 @@ void SfizzVstEditor::createFrameContents() topLeftLabel->setBackColor(CColor(0x00, 0x00, 0x00, 0x00)); panel->addView(topLeftLabel); + CRect row = topRow; + row.top += 200.0; + row.bottom += 200.0; + row.left += 100.0; + row.right -= 100.0; + + CCoord interRow = 35.0; + + auto leftSide = [&row]() -> CRect { + CRect div = row; + div.right = 0.5 * (div.left + div.right); + return div; + }; + + auto rightSide = [&row]() -> CRect { + CRect div = row; + div.left = 0.5 * (div.left + div.right); + return div; + }; + + CTextLabel* label; + SimpleSlider* slider; + + label = new CTextLabel(leftSide(), "Volume"); + label->setFontColor(CColor(0x00, 0x00, 0x00)); + label->setFrameColor(CColor(0x00, 0x00, 0x00, 0x00)); + label->setBackColor(CColor(0x00, 0x00, 0x00, 0x00)); + label->setHoriAlign(kLeftText); + panel->addView(label); + slider = new SimpleSlider(rightSide(), this, kTagSetVolume); + adjustMinMaxToRangeParam(slider, kPidVolume); + panel->addView(slider); + _volumeSlider = slider; + + // row.top += interRow; + // row.bottom += interRow; + + // label = new CTextLabel(leftSide(), "Polyphony"); + // label->setFontColor(CColor(0x00, 0x00, 0x00)); + // label->setFrameColor(CColor(0x00, 0x00, 0x00, 0x00)); + // label->setBackColor(CColor(0x00, 0x00, 0x00, 0x00)); + // label->setHoriAlign(kLeftText); + // panel->addView(label); + // slider = new SimpleSlider(rightSide(), this, -1); + // panel->addView(slider); + + // row.top += interRow; + // row.bottom += interRow; + + // label = new CTextLabel(leftSide(), "Oversampling"); + // label->setFontColor(CColor(0x00, 0x00, 0x00)); + // label->setFrameColor(CColor(0x00, 0x00, 0x00, 0x00)); + // label->setBackColor(CColor(0x00, 0x00, 0x00, 0x00)); + // label->setHoriAlign(kLeftText); + // panel->addView(label); + // slider = new SimpleSlider(rightSide(), this, -1); + // panel->addView(slider); + + // row.top += interRow; + // row.bottom += interRow; + + // label = new CTextLabel(leftSide(), "Preload size"); + // label->setFontColor(CColor(0x00, 0x00, 0x00)); + // label->setFrameColor(CColor(0x00, 0x00, 0x00, 0x00)); + // label->setBackColor(CColor(0x00, 0x00, 0x00, 0x00)); + // label->setHoriAlign(kLeftText); + // panel->addView(label); + // slider = new SimpleSlider(rightSide(), this, -1); + // panel->addView(slider); + + // row.top += interRow; + // row.bottom += interRow; + + // label = new CTextLabel(leftSide(), "Freewheel"); + // label->setFontColor(CColor(0x00, 0x00, 0x00)); + // label->setFrameColor(CColor(0x00, 0x00, 0x00, 0x00)); + // label->setBackColor(CColor(0x00, 0x00, 0x00, 0x00)); + // label->setHoriAlign(kLeftText); + // panel->addView(label); + // slider = new SimpleSlider(rightSide(), this, -1); + // panel->addView(slider); + _subPanels[kPanelSettings] = panel; } @@ -204,17 +323,31 @@ void SfizzVstEditor::updateStateDisplay() if (!frame) return; - const SfizzVstState& state = static_cast(getController())->getSfizzState(); + SfizzVstController* controller = getController(); + const SfizzVstState& state = controller->getSfizzState(); + const SfizzUiState& uiState = controller->getSfizzUiState(); if (_fileLabel) _fileLabel->setText(("File: " + state.sfzFile).c_str()); + if (_volumeSlider) + _volumeSlider->setValue(state.volume); + + setActivePanel(uiState.activePanel); } void SfizzVstEditor::setActivePanel(unsigned panelId) { + panelId = std::max(0, std::min(kNumPanels - 1, static_cast(panelId))); + + getController()->getSfizzUiState().activePanel = panelId; + if (_activePanel != panelId) { - _subPanels[_activePanel]->setVisible(false); - _subPanels[panelId]->setVisible(true); + if (frame) + _subPanels[_activePanel]->setVisible(false); + _activePanel = panelId; + + if (frame) + _subPanels[panelId]->setVisible(true); } } diff --git a/vst/SfizzVstEditor.h b/vst/SfizzVstEditor.h index b293be5c..1acd7492 100644 --- a/vst/SfizzVstEditor.h +++ b/vst/SfizzVstEditor.h @@ -19,8 +19,16 @@ public: bool PLUGIN_API open(void* parent, const VSTGUI::PlatformType& platformType = VSTGUI::kDefaultNative) override; void PLUGIN_API close() override; + SfizzVstController* getController() const + { + return static_cast(Vst::VSTGUIEditor::getController()); + } + // IControlListener void valueChanged(CControl* ctl) override; + void enterOrLeaveEdit(CControl* ctl, bool enter); + void controlBeginEdit(CControl* ctl) override; + void controlEndEdit(CControl* ctl) override; // SfizzVstController::StateListener void onStateChanged() override; @@ -33,6 +41,14 @@ private: void updateStateDisplay(); void setActivePanel(unsigned panelId); + template + void adjustMinMaxToRangeParam(Control* c, Vst::ParamID id) + { + auto* p = static_cast(getController()->getParameterObject(id)); + c->setMin(p->getMin()); + c->setMax(p->getMax()); + } + enum { kPanelGeneral, // kPanelControls, @@ -45,10 +61,12 @@ private: enum { kTagLoadSfzFile, + kTagSetVolume, kTagFirstChangePanel, kTagLastChangePanel = kTagFirstChangePanel + kNumPanels - 1, }; CBitmap _logo; CTextLabel* _fileLabel = nullptr; + CSliderBase *_volumeSlider = nullptr; }; diff --git a/vst/SfizzVstProcessor.cpp b/vst/SfizzVstProcessor.cpp index 88e71d1a..6a7160df 100644 --- a/vst/SfizzVstProcessor.cpp +++ b/vst/SfizzVstProcessor.cpp @@ -34,6 +34,8 @@ tresult PLUGIN_API SfizzVstProcessor::initialize(FUnknown* context) addAudioOutput(STR16("Audio Output"), Vst::SpeakerArr::kStereo); addEventInput(STR16("Event Input"), 1); + _state = SfizzVstState(); + return result; } @@ -47,28 +49,37 @@ tresult PLUGIN_API SfizzVstProcessor::setBusArrangements(Vst::SpeakerArrangement return AudioEffect::setBusArrangements(inputs, numIns, outputs, numOuts); } -tresult PLUGIN_API SfizzVstProcessor::setState(IBStream* state) +tresult PLUGIN_API SfizzVstProcessor::setState(IBStream* stream) { SfizzVstState s; - tresult r = s.load(state); + tresult r = s.load(stream); if (r != kResultTrue) return r; - loadSfzFile(s.sfzFile); + std::lock_guard lock(_processMutex); + _state = s; + + syncStateToSynth(); return r; } -tresult PLUGIN_API SfizzVstProcessor::getState(IBStream* state) +tresult PLUGIN_API SfizzVstProcessor::getState(IBStream* stream) { - SfizzVstState s; - { - std::lock_guard lock(_processMutex); - s.sfzFile = _sfzFile; - } + std::lock_guard lock(_processMutex); + return _state.store(stream); +} - return s.store(state); +void SfizzVstProcessor::syncStateToSynth() +{ + sfz::Sfizz* synth = _synth.get(); + + if (!synth) + return; + + synth->loadSfzFile(_state.sfzFile); + synth->setVolume(_state.volume); } tresult PLUGIN_API SfizzVstProcessor::canProcessSampleSize(int32 symbolicSampleSize) @@ -92,7 +103,7 @@ tresult PLUGIN_API SfizzVstProcessor::setActive(TBool state) synth->setSampleRate(processSetup.sampleRate); synth->setSamplesPerBlock(processSetup.maxSamplesPerBlock); - loadSfzFile(_sfzFile); + syncStateToSynth(); _workRunning = true; _worker = std::thread([this]() { doBackgroundWork(); }); @@ -105,6 +116,11 @@ tresult PLUGIN_API SfizzVstProcessor::process(Vst::ProcessData& data) { sfz::Sfizz& synth = *_synth; + if (Vst::IParameterChanges* pc = data.inputParameterChanges) { + std::unique_lock lock(_processMutex, std::try_to_lock); + processParameterChanges(*pc); + } + if (data.numOutputs < 1) // flush mode return kResultTrue; @@ -118,6 +134,7 @@ tresult PLUGIN_API SfizzVstProcessor::process(Vst::ProcessData& data) outputs[c] = data.outputs[0].channelBuffers32[c]; std::unique_lock lock(_processMutex, std::try_to_lock); + if (!lock.owns_lock()) { for (unsigned c = 0; c < numChannels; ++c) std::memset(outputs[c], 0, numFrames * sizeof(float)); @@ -125,78 +142,113 @@ tresult PLUGIN_API SfizzVstProcessor::process(Vst::ProcessData& data) return kResultTrue; } - if (Vst::IParameterChanges* pc = data.inputParameterChanges) { - uint32 paramCount = pc->getParameterCount(); + if (Vst::IParameterChanges* pc = data.inputParameterChanges) + processControllerChanges(*pc); - for (uint32 paramIndex = 0; paramIndex < paramCount; ++paramIndex) { - Vst::IParamValueQueue* vq = pc->getParameterData(paramIndex); + if (Vst::IEventList* events = data.inputEvents) + processEvents(*events); - Vst::ParamID id = vq->getParameterId(); - - switch (id) { - default: - if (id >= SfizzVstController::kPidMidiCC0 && id <= SfizzVstController::kPidMidiCCLast) { - int ccNumber = id - SfizzVstController::kPidMidiCC0; - for (uint32 pointIndex = 0, pointCount = vq->getPointCount(); pointIndex < pointCount; ++pointIndex) { - int32 sampleOffset; - Vst::ParamValue value; - if (vq->getPoint(pointIndex, sampleOffset, value) == kResultTrue) - synth.cc(sampleOffset, ccNumber, (int)(0.5 + value * 127.0)); - } - } - break; - - case SfizzVstController::kPidMidiAftertouch: - for (uint32 pointIndex = 0, pointCount = vq->getPointCount(); pointIndex < pointCount; ++pointIndex) { - int32 sampleOffset; - Vst::ParamValue value; - if (vq->getPoint(pointIndex, sampleOffset, value) == kResultTrue) - synth.aftertouch(sampleOffset, (int)(0.5 + value * 127.0)); - } - break; - - case SfizzVstController::kPidMidiPitchBend: - for (uint32 pointIndex = 0, pointCount = vq->getPointCount(); pointIndex < pointCount; ++pointIndex) { - int32 sampleOffset; - Vst::ParamValue value; - if (vq->getPoint(pointIndex, sampleOffset, value) == kResultTrue) - synth.pitchWheel(sampleOffset, (int)(0.5 + value * 16383) - 8192); - } - break; - } - } - } - - if (Vst::IEventList* events = data.inputEvents) { - uint32 numEvents = events->getEventCount(); - - for (uint32 i = 0; i < numEvents; i++) { - Vst::Event e; - if (events->getEvent(i, e) != kResultTrue) - continue; - - auto convertVelocityFromFloat = [](float x) -> int { - return std::min(127, std::max(0, (int)(x * 127.0f))); - }; - - switch (e.type) { - case Vst::Event::kNoteOnEvent: - synth.noteOn(e.sampleOffset, e.noteOn.pitch, convertVelocityFromFloat(e.noteOn.velocity)); - break; - case Vst::Event::kNoteOffEvent: - synth.noteOff(e.sampleOffset, e.noteOff.pitch, convertVelocityFromFloat(e.noteOff.velocity)); - break; - // case Vst::Event::kPolyPressureEvent: - // synth.aftertouch(e.sampleOffset, convertVelocityFromFloat(e.polyPressure.pressure)); - // break; - } - } - } + synth.setVolume(_state.volume); synth.renderBlock(outputs, numFrames, numChannels); return kResultTrue; } +void SfizzVstProcessor::processParameterChanges(Vst::IParameterChanges& pc) +{ + uint32 paramCount = pc.getParameterCount(); + + for (uint32 paramIndex = 0; paramIndex < paramCount; ++paramIndex) { + Vst::IParamValueQueue* vq = pc.getParameterData(paramIndex); + if (!vq) + continue; + + Vst::ParamID id = vq->getParameterId(); + uint32 pointCount = vq->getPointCount(); + int32 sampleOffset; + Vst::ParamValue value; + + switch (id) { + case kPidVolume: + if (pointCount > 0 && vq->getPoint(pointCount - 1, sampleOffset, value) == kResultTrue) + _state.volume = kParamVolumeRange.denormalize(value); + break; + } + } +} + +void SfizzVstProcessor::processControllerChanges(Vst::IParameterChanges& pc) +{ + sfz::Sfizz& synth = *_synth; + uint32 paramCount = pc.getParameterCount(); + + for (uint32 paramIndex = 0; paramIndex < paramCount; ++paramIndex) { + Vst::IParamValueQueue* vq = pc.getParameterData(paramIndex); + if (!vq) + continue; + + Vst::ParamID id = vq->getParameterId(); + uint32 pointCount = vq->getPointCount(); + int32 sampleOffset; + Vst::ParamValue value; + + switch (id) { + default: + if (id >= kPidMidiCC0 && id <= kPidMidiCCLast) { + int ccNumber = id - kPidMidiCC0; + for (uint32 pointIndex = 0; pointIndex < pointCount; ++pointIndex) { + if (vq->getPoint(pointIndex, sampleOffset, value) == kResultTrue) + synth.cc(sampleOffset, ccNumber, (int)(0.5 + value * 127.0)); + } + } + break; + + case kPidMidiAftertouch: + for (uint32 pointIndex = 0; pointIndex < pointCount; ++pointIndex) { + if (vq->getPoint(pointIndex, sampleOffset, value) == kResultTrue) + synth.aftertouch(sampleOffset, (int)(0.5 + value * 127.0)); + } + break; + + case kPidMidiPitchBend: + for (uint32 pointIndex = 0; pointIndex < pointCount; ++pointIndex) { + if (vq->getPoint(pointIndex, sampleOffset, value) == kResultTrue) + synth.pitchWheel(sampleOffset, (int)(0.5 + value * 16383) - 8192); + } + break; + } + } +} + +void SfizzVstProcessor::processEvents(Vst::IEventList& events) +{ + sfz::Sfizz& synth = *_synth; + uint32 numEvents = events.getEventCount(); + + for (uint32 i = 0; i < numEvents; i++) { + Vst::Event e; + if (events.getEvent(i, e) != kResultTrue) + continue; + + switch (e.type) { + case Vst::Event::kNoteOnEvent: + synth.noteOn(e.sampleOffset, e.noteOn.pitch, convertVelocityFromFloat(e.noteOn.velocity)); + break; + case Vst::Event::kNoteOffEvent: + synth.noteOff(e.sampleOffset, e.noteOff.pitch, convertVelocityFromFloat(e.noteOff.velocity)); + break; + // case Vst::Event::kPolyPressureEvent: + // synth.aftertouch(e.sampleOffset, convertVelocityFromFloat(e.polyPressure.pressure)); + // break; + } + } +} + +int SfizzVstProcessor::convertVelocityFromFloat(float x) +{ + return std::min(127, std::max(0, (int)(x * 127.0f))); +} + tresult PLUGIN_API SfizzVstProcessor::notify(Vst::IMessage* message) { tresult result = AudioEffect::notify(message); @@ -217,18 +269,6 @@ FUnknown* SfizzVstProcessor::createInstance(void*) return static_cast(new SfizzVstProcessor); } -void SfizzVstProcessor::loadSfzFile(std::string file) -{ - std::lock_guard lock(_processMutex); - - if (_synth) { - fprintf(stderr, "[Sfizz] load SFZ file: %s\n", file.c_str()); - _synth->loadSfzFile(file); - } - - _sfzFile = std::move(file); -} - void SfizzVstProcessor::doBackgroundWork() { constexpr uint32 maxPathLen = 32768; @@ -250,8 +290,11 @@ void SfizzVstProcessor::doBackgroundWork() if (!std::strcmp(id, "LoadSfz")) { std::vector path(maxPathLen + 1); - if (attr->getString("File", path.data(), maxPathLen) == kResultTrue) - loadSfzFile(Steinberg::String(path.data()).text8()); + if (attr->getString("File", path.data(), maxPathLen) == kResultTrue) { + std::lock_guard lock(_processMutex); + _state.sfzFile = Steinberg::String(path.data()).text8(); + _synth->loadSfzFile(_state.sfzFile); + } } msg->release(); diff --git a/vst/SfizzVstProcessor.h b/vst/SfizzVstProcessor.h index 86bbab81..86811e75 100644 --- a/vst/SfizzVstProcessor.h +++ b/vst/SfizzVstProcessor.h @@ -5,9 +5,10 @@ // If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz #pragma once +#include "SfizzVstState.h" +#include "RTSemaphore.h" #include "public.sdk/source/vst/vstaudioeffect.h" #include "public.sdk/source/vst/utility/ringbuffer.h" -#include "RTSemaphore.h" #include #include #include @@ -23,12 +24,17 @@ public: tresult PLUGIN_API initialize(FUnknown* context) override; tresult PLUGIN_API setBusArrangements(Vst::SpeakerArrangement* inputs, int32 numIns, Vst::SpeakerArrangement* outputs, int32 numOuts) override; - tresult PLUGIN_API setState(IBStream* state) override; - tresult PLUGIN_API getState(IBStream* state) override; + tresult PLUGIN_API setState(IBStream* stream) override; + tresult PLUGIN_API getState(IBStream* stream) override; + void syncStateToSynth(); tresult PLUGIN_API canProcessSampleSize(int32 symbolicSampleSize) override; tresult PLUGIN_API setActive(TBool state) override; tresult PLUGIN_API process(Vst::ProcessData& data) override; + void processParameterChanges(Vst::IParameterChanges& pc); + void processControllerChanges(Vst::IParameterChanges& pc); + void processEvents(Vst::IEventList& events); + static int convertVelocityFromFloat(float x); tresult PLUGIN_API notify(Vst::IMessage* message) override; @@ -38,19 +44,17 @@ public: // --- Sfizz stuff here below --- private: + // synth state. acquire processMutex before accessing std::unique_ptr _synth; + SfizzVstState _state; + + // worker and thread sync std::thread _worker; volatile bool _workRunning = false; Steinberg::OneReaderOneWriter::RingBuffer _fifoToWorker; RTSemaphore _semaToWorker; std::mutex _processMutex; - // state - std::string _sfzFile; - - // - void loadSfzFile(std::string file); - // worker void doBackgroundWork(); void stopBackgroundWork(); diff --git a/vst/SfizzVstState.cpp b/vst/SfizzVstState.cpp index 447f7ad0..fffeaae2 100644 --- a/vst/SfizzVstState.cpp +++ b/vst/SfizzVstState.cpp @@ -16,14 +16,13 @@ tresult SfizzVstState::load(IBStream* state) if (!s.readInt64u(version)) return kResultFalse; - while (const char* key = s.readStr8()) { - if (!std::strcmp(key, "SfzFile")) { - const char* value = s.readStr8(); - if (!value) - return kResultFalse; - sfzFile = value; - } - } + if (const char* str = s.readStr8()) + sfzFile = str; + else + return kResultFalse; + + if (!s.readFloat(volume)) + return kResultFalse; return kResultTrue; } @@ -35,7 +34,37 @@ tresult SfizzVstState::store(IBStream* state) const if (!s.writeInt64u(currentStateVersion)) return kResultFalse; - if (!s.writeStr8("SfzFile") || !s.writeStr8(sfzFile.c_str())) + if (!s.writeStr8(sfzFile.c_str())) + return kResultFalse; + + if (!s.writeFloat(volume)) + return kResultFalse; + + return kResultTrue; +} + +tresult SfizzUiState::load(IBStream* state) +{ + IBStreamer s(state, kLittleEndian); + + uint64 version = 0; + if (!s.readInt64u(version)) + return kResultFalse; + + if (!s.readInt32u(activePanel)) + return kResultFalse; + + return kResultTrue; +} + +tresult SfizzUiState::store(IBStream* state) const +{ + IBStreamer s(state, kLittleEndian); + + if (!s.writeInt64u(currentStateVersion)) + return kResultFalse; + + if (!s.writeInt32u(activePanel)) return kResultFalse; return kResultTrue; diff --git a/vst/SfizzVstState.h b/vst/SfizzVstState.h index df0adecc..f1cfc563 100644 --- a/vst/SfizzVstState.h +++ b/vst/SfizzVstState.h @@ -6,16 +6,69 @@ #pragma once #include "base/source/fstreamer.h" +#include "public.sdk/source/vst/vstparameters.h" #include using namespace Steinberg; +// number of MIDI CC +enum { + kNumControllerParams = 128, +}; + +// parameters +enum { + kPidVolume, + kPidMidiCC0, + kPidMidiCCLast = kPidMidiCC0 + kNumControllerParams - 1, + kPidMidiAftertouch, + kPidMidiPitchBend, + /* Reserved */ +}; + class SfizzVstState { public: std::string sfzFile; + float volume = 0; static constexpr uint64 currentStateVersion = 0; tresult load(IBStream* state); tresult store(IBStream* state) const; }; + +class SfizzUiState { +public: + unsigned activePanel = 0; + + static constexpr uint64 currentStateVersion = 0; + + tresult load(IBStream* state); + tresult store(IBStream* state) const; +}; + +struct SfizzParameterRange { + float def = 0.0; + float min = 0.0; + float max = 1.0; + + constexpr SfizzParameterRange() {} + constexpr SfizzParameterRange(float def, float min, float max) : def(def), min(min), max(max) {} + + constexpr float normalize(float x) const noexcept + { + return (x - min) / (max - min); + } + + constexpr float denormalize(float x) const noexcept + { + return min + x * (max - min); + } + + Vst::RangeParameter* createParameter(const Vst::TChar *title, Vst::ParamID tag, const Vst::TChar *units = nullptr, int32 stepCount = 0, int32 flags = Vst::ParameterInfo::kCanAutomate, Vst::UnitID unitID = Vst::kRootUnitId, const Vst::TChar *shortTitle = nullptr) const + { + return new Vst::RangeParameter(title, tag, units, min, max, def, stepCount, flags, unitID, shortTitle); + } +}; + +static constexpr SfizzParameterRange kParamVolumeRange(0.0, -60.0, +6.0); From aa9a5d26f4b4a86659bf947072eeba13d75d3f0f Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Fri, 6 Mar 2020 11:46:49 +0100 Subject: [PATCH 43/93] Add the polyphony parameter --- vst/SfizzVstController.cpp | 29 +++++++++++++++++++++++++---- vst/SfizzVstEditor.cpp | 32 +++++++++++++++++++++----------- vst/SfizzVstEditor.h | 2 ++ vst/SfizzVstProcessor.cpp | 23 +++++++++++++++++++++++ vst/SfizzVstState.cpp | 6 ++++++ vst/SfizzVstState.h | 3 +++ 6 files changed, 80 insertions(+), 15 deletions(-) diff --git a/vst/SfizzVstController.cpp b/vst/SfizzVstController.cpp index fc7cfe5e..eba85ca2 100644 --- a/vst/SfizzVstController.cpp +++ b/vst/SfizzVstController.cpp @@ -22,6 +22,10 @@ tresult PLUGIN_API SfizzVstControllerNoUi::initialize(FUnknown* context) kParamVolumeRange.createParameter( Steinberg::String("Volume"), pid++, Steinberg::String("dB"), 0, Vst::ParameterInfo::kCanAutomate, Vst::kRootUnitId)); + parameters.addParameter( + kParamNumVoicesRange.createParameter( + Steinberg::String("Polyphony"), pid++, nullptr, + 0, Vst::ParameterInfo::kNoFlags, Vst::kRootUnitId)); // MIDI controllers for (unsigned i = 0; i < kNumControllerParams; ++i) { @@ -85,19 +89,35 @@ tresult PLUGIN_API SfizzVstController::setParamNormalized(Vst::ParamID tag, Vst: if (r != kResultTrue) return r; - float *slot = nullptr; + float *slotF32 = nullptr; + int32 *slotI32 = nullptr; float value = 0; switch (tag) { case kPidVolume: { - slot = &_state.volume; + slotF32 = &_state.volume; value = kParamVolumeRange.denormalize(normValue); break; } + case kPidNumVoices: { + slotI32 = &_state.numVoices; + value = kParamNumVoicesRange.denormalize(normValue); + break; + } } - if (slot && *slot != value) { - *slot = value; + bool update = false; + + if (slotF32 && *slotF32 != value) { + *slotF32 = value; + update = true; + } + else if (slotI32 && *slotI32 != (int32)value) { + *slotI32 = (int32)value; + update = true; + } + + if (update) { for (StateListener* listener : _stateListeners) listener->onStateChanged(); } @@ -137,6 +157,7 @@ tresult PLUGIN_API SfizzVstController::setComponentState(IBStream* state) _state = s; setParamNormalized(kPidVolume, kParamVolumeRange.normalize(s.volume)); + setParamNormalized(kPidNumVoices, kParamNumVoicesRange.normalize(s.numVoices)); for (StateListener* listener : _stateListeners) listener->onStateChanged(); diff --git a/vst/SfizzVstEditor.cpp b/vst/SfizzVstEditor.cpp index 9b197331..d3079626 100644 --- a/vst/SfizzVstEditor.cpp +++ b/vst/SfizzVstEditor.cpp @@ -76,6 +76,11 @@ void SfizzVstEditor::valueChanged(CControl* ctl) controller->performEdit(kPidVolume, valueNorm); break; + case kTagSetNumVoices: + controller->setParamNormalized(kPidNumVoices, valueNorm); + controller->performEdit(kPidNumVoices, valueNorm); + break; + default: if (tag >= kTagFirstChangePanel && tag <= kTagLastChangePanel) setActivePanel(tag - kTagFirstChangePanel); @@ -90,6 +95,7 @@ void SfizzVstEditor::enterOrLeaveEdit(CControl* ctl, bool enter) switch (tag) { case kTagSetVolume: id = kPidVolume; break; + case kTagSetNumVoices: id = kPidNumVoices; break; default: return; } @@ -231,21 +237,23 @@ void SfizzVstEditor::createFrameContents() label->setHoriAlign(kLeftText); panel->addView(label); slider = new SimpleSlider(rightSide(), this, kTagSetVolume); - adjustMinMaxToRangeParam(slider, kPidVolume); panel->addView(slider); + adjustMinMaxToRangeParam(slider, kPidVolume); _volumeSlider = slider; - // row.top += interRow; - // row.bottom += interRow; + row.top += interRow; + row.bottom += interRow; - // label = new CTextLabel(leftSide(), "Polyphony"); - // label->setFontColor(CColor(0x00, 0x00, 0x00)); - // label->setFrameColor(CColor(0x00, 0x00, 0x00, 0x00)); - // label->setBackColor(CColor(0x00, 0x00, 0x00, 0x00)); - // label->setHoriAlign(kLeftText); - // panel->addView(label); - // slider = new SimpleSlider(rightSide(), this, -1); - // panel->addView(slider); + label = new CTextLabel(leftSide(), "Polyphony"); + label->setFontColor(CColor(0x00, 0x00, 0x00)); + label->setFrameColor(CColor(0x00, 0x00, 0x00, 0x00)); + label->setBackColor(CColor(0x00, 0x00, 0x00, 0x00)); + label->setHoriAlign(kLeftText); + panel->addView(label); + slider = new SimpleSlider(rightSide(), this, kTagSetNumVoices); + panel->addView(slider); + adjustMinMaxToRangeParam(slider, kPidNumVoices); + _numVoicesSlider = slider; // row.top += interRow; // row.bottom += interRow; @@ -331,6 +339,8 @@ void SfizzVstEditor::updateStateDisplay() _fileLabel->setText(("File: " + state.sfzFile).c_str()); if (_volumeSlider) _volumeSlider->setValue(state.volume); + if (_numVoicesSlider) + _numVoicesSlider->setValue(state.numVoices); setActivePanel(uiState.activePanel); } diff --git a/vst/SfizzVstEditor.h b/vst/SfizzVstEditor.h index 1acd7492..e71bd284 100644 --- a/vst/SfizzVstEditor.h +++ b/vst/SfizzVstEditor.h @@ -62,6 +62,7 @@ private: enum { kTagLoadSfzFile, kTagSetVolume, + kTagSetNumVoices, kTagFirstChangePanel, kTagLastChangePanel = kTagFirstChangePanel + kNumPanels - 1, }; @@ -69,4 +70,5 @@ private: CBitmap _logo; CTextLabel* _fileLabel = nullptr; CSliderBase *_volumeSlider = nullptr; + CSliderBase *_numVoicesSlider = nullptr; }; diff --git a/vst/SfizzVstProcessor.cpp b/vst/SfizzVstProcessor.cpp index 6a7160df..5cdafaf9 100644 --- a/vst/SfizzVstProcessor.cpp +++ b/vst/SfizzVstProcessor.cpp @@ -80,6 +80,7 @@ void SfizzVstProcessor::syncStateToSynth() synth->loadSfzFile(_state.sfzFile); synth->setVolume(_state.volume); + synth->setNumVoices(_state.numVoices); } tresult PLUGIN_API SfizzVstProcessor::canProcessSampleSize(int32 symbolicSampleSize) @@ -173,6 +174,21 @@ void SfizzVstProcessor::processParameterChanges(Vst::IParameterChanges& pc) if (pointCount > 0 && vq->getPoint(pointCount - 1, sampleOffset, value) == kResultTrue) _state.volume = kParamVolumeRange.denormalize(value); break; + case kPidNumVoices: + if (pointCount > 0 && vq->getPoint(pointCount - 1, sampleOffset, value) == kResultTrue) { + Vst::IMessage* msg = allocateMessage(); + if (!msg) + break; + msg->setMessageID("SetNumVoices"); + Vst::IAttributeList* attr = msg->getAttributes(); + attr->setInt("NumVoices", kParamNumVoicesRange.denormalize(value)); + if (!_fifoToWorker.push(msg)) { + msg->release(); + break; + } + _semaToWorker.post(); + } + break; } } } @@ -296,6 +312,13 @@ void SfizzVstProcessor::doBackgroundWork() _synth->loadSfzFile(_state.sfzFile); } } + else if (!std::strcmp(id, "SetNumVoices")) { + int64 value; + if (attr->getInt("NumVoices", value) == kResultTrue) { + _state.numVoices = value; + _synth->setNumVoices(value); + } + } msg->release(); } diff --git a/vst/SfizzVstState.cpp b/vst/SfizzVstState.cpp index fffeaae2..6c951314 100644 --- a/vst/SfizzVstState.cpp +++ b/vst/SfizzVstState.cpp @@ -24,6 +24,9 @@ tresult SfizzVstState::load(IBStream* state) if (!s.readFloat(volume)) return kResultFalse; + if (!s.readInt32(numVoices)) + return kResultFalse; + return kResultTrue; } @@ -40,6 +43,9 @@ tresult SfizzVstState::store(IBStream* state) const if (!s.writeFloat(volume)) return kResultFalse; + if (!s.writeInt32(numVoices)) + return kResultFalse; + return kResultTrue; } diff --git a/vst/SfizzVstState.h b/vst/SfizzVstState.h index f1cfc563..f68412f6 100644 --- a/vst/SfizzVstState.h +++ b/vst/SfizzVstState.h @@ -19,6 +19,7 @@ enum { // parameters enum { kPidVolume, + kPidNumVoices, kPidMidiCC0, kPidMidiCCLast = kPidMidiCC0 + kNumControllerParams - 1, kPidMidiAftertouch, @@ -30,6 +31,7 @@ class SfizzVstState { public: std::string sfzFile; float volume = 0; + int numVoices = 64; static constexpr uint64 currentStateVersion = 0; @@ -72,3 +74,4 @@ struct SfizzParameterRange { }; static constexpr SfizzParameterRange kParamVolumeRange(0.0, -60.0, +6.0); +static constexpr SfizzParameterRange kParamNumVoicesRange(64.0, 1.0, 256.0); From 26ba7f33b0bca9d7e98840cf90b8fdddd27438fb Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Fri, 6 Mar 2020 11:55:52 +0100 Subject: [PATCH 44/93] Add oversampling --- vst/SfizzVstController.cpp | 44 ++++++++++++++++++++++++++++++++++++++ vst/SfizzVstController.h | 4 ++++ vst/SfizzVstEditor.cpp | 38 +++++++++++++++++++++----------- vst/SfizzVstEditor.h | 2 ++ vst/SfizzVstProcessor.cpp | 25 ++++++++++++++++++++++ vst/SfizzVstState.cpp | 20 +++++++++++++++++ vst/SfizzVstState.h | 8 +++++++ 7 files changed, 129 insertions(+), 12 deletions(-) diff --git a/vst/SfizzVstController.cpp b/vst/SfizzVstController.cpp index eba85ca2..065c41fa 100644 --- a/vst/SfizzVstController.cpp +++ b/vst/SfizzVstController.cpp @@ -26,6 +26,10 @@ tresult PLUGIN_API SfizzVstControllerNoUi::initialize(FUnknown* context) kParamNumVoicesRange.createParameter( Steinberg::String("Polyphony"), pid++, nullptr, 0, Vst::ParameterInfo::kNoFlags, Vst::kRootUnitId)); + parameters.addParameter( + kParamOversamplingRange.createParameter( + Steinberg::String("Oversampling"), pid++, nullptr, + 0, Vst::ParameterInfo::kNoFlags, Vst::kRootUnitId)); // MIDI controllers for (unsigned i = 0; i < kNumControllerParams; ++i) { @@ -71,6 +75,40 @@ tresult PLUGIN_API SfizzVstControllerNoUi::getMidiControllerAssignment(int32 bus } } +tresult PLUGIN_API SfizzVstControllerNoUi::getParamStringByValue(Vst::ParamID tag, Vst::ParamValue valueNormalized, Vst::String128 string) +{ + switch (tag) { + case kPidOversampling: + { + int factor = SfizzMisc::adaptOversamplingFactor( + kParamOversamplingRange.denormalize(valueNormalized)); + Steinberg::String buf; + buf.printf("%dX", factor); + buf.copyTo(string); + return kResultTrue; + } + } + + return EditController::getParamStringByValue(tag, valueNormalized, string); +} + +tresult PLUGIN_API SfizzVstControllerNoUi::getParamValueByString(Vst::ParamID tag, Vst::TChar* string, Vst::ParamValue& valueNormalized) +{ + switch (tag) { + case kPidOversampling: + { + int factor; + if (!Steinberg::String::scanInt32(string, factor, false)) + factor = 1; + valueNormalized = kParamOversamplingRange.normalize( + SfizzMisc::adaptOversamplingFactor(factor)); + return kResultTrue; + } + } + + return EditController::getParamValueByString(tag, string, valueNormalized); +} + // --- Controller with UI --- // IPlugView* PLUGIN_API SfizzVstController::createView(FIDString _name) @@ -104,6 +142,11 @@ tresult PLUGIN_API SfizzVstController::setParamNormalized(Vst::ParamID tag, Vst: value = kParamNumVoicesRange.denormalize(normValue); break; } + case kPidOversampling: { + slotI32 = &_state.oversampling; + value = kParamOversamplingRange.denormalize(normValue); + break; + } } bool update = false; @@ -158,6 +201,7 @@ tresult PLUGIN_API SfizzVstController::setComponentState(IBStream* state) setParamNormalized(kPidVolume, kParamVolumeRange.normalize(s.volume)); setParamNormalized(kPidNumVoices, kParamNumVoicesRange.normalize(s.numVoices)); + setParamNormalized(kPidOversampling, kParamOversamplingRange.normalize(s.oversampling)); for (StateListener* listener : _stateListeners) listener->onStateChanged(); diff --git a/vst/SfizzVstController.h b/vst/SfizzVstController.h index e8ca3037..b8455ecd 100644 --- a/vst/SfizzVstController.h +++ b/vst/SfizzVstController.h @@ -24,6 +24,10 @@ public: tresult PLUGIN_API getMidiControllerAssignment(int32 busIndex, int16 channel, Vst::CtrlNumber midiControllerNumber, Vst::ParamID& id) 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; + + // interfaces OBJ_METHODS(SfizzVstControllerNoUi, Vst::EditController) DEFINE_INTERFACES diff --git a/vst/SfizzVstEditor.cpp b/vst/SfizzVstEditor.cpp index d3079626..65da051a 100644 --- a/vst/SfizzVstEditor.cpp +++ b/vst/SfizzVstEditor.cpp @@ -81,6 +81,11 @@ void SfizzVstEditor::valueChanged(CControl* ctl) controller->performEdit(kPidNumVoices, valueNorm); break; + case kTagSetOversampling: + controller->setParamNormalized(kPidOversampling, valueNorm); + controller->performEdit(kPidOversampling, valueNorm); + break; + default: if (tag >= kTagFirstChangePanel && tag <= kTagLastChangePanel) setActivePanel(tag - kTagFirstChangePanel); @@ -96,6 +101,7 @@ void SfizzVstEditor::enterOrLeaveEdit(CControl* ctl, bool enter) switch (tag) { case kTagSetVolume: id = kPidVolume; break; case kTagSetNumVoices: id = kPidNumVoices; break; + case kTagSetOversampling: id = kPidOversampling; break; default: return; } @@ -255,17 +261,19 @@ void SfizzVstEditor::createFrameContents() adjustMinMaxToRangeParam(slider, kPidNumVoices); _numVoicesSlider = slider; - // row.top += interRow; - // row.bottom += interRow; + row.top += interRow; + row.bottom += interRow; - // label = new CTextLabel(leftSide(), "Oversampling"); - // label->setFontColor(CColor(0x00, 0x00, 0x00)); - // label->setFrameColor(CColor(0x00, 0x00, 0x00, 0x00)); - // label->setBackColor(CColor(0x00, 0x00, 0x00, 0x00)); - // label->setHoriAlign(kLeftText); - // panel->addView(label); - // slider = new SimpleSlider(rightSide(), this, -1); - // panel->addView(slider); + label = new CTextLabel(leftSide(), "Oversampling"); + label->setFontColor(CColor(0x00, 0x00, 0x00)); + label->setFrameColor(CColor(0x00, 0x00, 0x00, 0x00)); + label->setBackColor(CColor(0x00, 0x00, 0x00, 0x00)); + label->setHoriAlign(kLeftText); + panel->addView(label); + slider = new SimpleSlider(rightSide(), this, kTagSetOversampling); + panel->addView(slider); + adjustMinMaxToRangeParam(slider, kPidOversampling); + _oversamplingSlider = slider; // row.top += interRow; // row.bottom += interRow; @@ -276,8 +284,10 @@ void SfizzVstEditor::createFrameContents() // label->setBackColor(CColor(0x00, 0x00, 0x00, 0x00)); // label->setHoriAlign(kLeftText); // panel->addView(label); - // slider = new SimpleSlider(rightSide(), this, -1); + // slider = new SimpleSlider(rightSide(), this, kTag); // panel->addView(slider); + // adjustMinMaxToRangeParam(slider, kPid); + // _aSlider = slider; // row.top += interRow; // row.bottom += interRow; @@ -288,8 +298,10 @@ void SfizzVstEditor::createFrameContents() // label->setBackColor(CColor(0x00, 0x00, 0x00, 0x00)); // label->setHoriAlign(kLeftText); // panel->addView(label); - // slider = new SimpleSlider(rightSide(), this, -1); + // slider = new SimpleSlider(rightSide(), this, kTag); // panel->addView(slider); + // adjustMinMaxToRangeParam(slider, kPid); + // _aSlider = slider; _subPanels[kPanelSettings] = panel; } @@ -341,6 +353,8 @@ void SfizzVstEditor::updateStateDisplay() _volumeSlider->setValue(state.volume); if (_numVoicesSlider) _numVoicesSlider->setValue(state.numVoices); + if (_oversamplingSlider) + _oversamplingSlider->setValue(state.oversampling); setActivePanel(uiState.activePanel); } diff --git a/vst/SfizzVstEditor.h b/vst/SfizzVstEditor.h index e71bd284..353db127 100644 --- a/vst/SfizzVstEditor.h +++ b/vst/SfizzVstEditor.h @@ -63,6 +63,7 @@ private: kTagLoadSfzFile, kTagSetVolume, kTagSetNumVoices, + kTagSetOversampling, kTagFirstChangePanel, kTagLastChangePanel = kTagFirstChangePanel + kNumPanels - 1, }; @@ -71,4 +72,5 @@ private: CTextLabel* _fileLabel = nullptr; CSliderBase *_volumeSlider = nullptr; CSliderBase *_numVoicesSlider = nullptr; + CSliderBase *_oversamplingSlider = nullptr; }; diff --git a/vst/SfizzVstProcessor.cpp b/vst/SfizzVstProcessor.cpp index 5cdafaf9..b7111dce 100644 --- a/vst/SfizzVstProcessor.cpp +++ b/vst/SfizzVstProcessor.cpp @@ -81,6 +81,8 @@ void SfizzVstProcessor::syncStateToSynth() synth->loadSfzFile(_state.sfzFile); synth->setVolume(_state.volume); synth->setNumVoices(_state.numVoices); + synth->setOversamplingFactor( + SfizzMisc::adaptOversamplingFactor(_state.oversampling)); } tresult PLUGIN_API SfizzVstProcessor::canProcessSampleSize(int32 symbolicSampleSize) @@ -189,6 +191,21 @@ void SfizzVstProcessor::processParameterChanges(Vst::IParameterChanges& pc) _semaToWorker.post(); } break; + case kPidOversampling: + if (pointCount > 0 && vq->getPoint(pointCount - 1, sampleOffset, value) == kResultTrue) { + Vst::IMessage* msg = allocateMessage(); + if (!msg) + break; + msg->setMessageID("SetOversampling"); + Vst::IAttributeList* attr = msg->getAttributes(); + attr->setInt("Oversampling", kParamOversamplingRange.denormalize(value)); + if (!_fifoToWorker.push(msg)) { + msg->release(); + break; + } + _semaToWorker.post(); + } + break; } } } @@ -319,6 +336,14 @@ void SfizzVstProcessor::doBackgroundWork() _synth->setNumVoices(value); } } + else if (!std::strcmp(id, "SetOversampling")) { + int64 value; + if (attr->getInt("Oversampling", value) == kResultTrue) { + _state.oversampling = value; + _synth->setOversamplingFactor( + SfizzMisc::adaptOversamplingFactor(value)); + } + } msg->release(); } diff --git a/vst/SfizzVstState.cpp b/vst/SfizzVstState.cpp index 6c951314..0b416c3d 100644 --- a/vst/SfizzVstState.cpp +++ b/vst/SfizzVstState.cpp @@ -5,6 +5,7 @@ // If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz #include "SfizzVstState.h" +#include #include #include @@ -27,6 +28,9 @@ tresult SfizzVstState::load(IBStream* state) if (!s.readInt32(numVoices)) return kResultFalse; + if (!s.readInt32(oversampling)) + return kResultFalse; + return kResultTrue; } @@ -46,6 +50,9 @@ tresult SfizzVstState::store(IBStream* state) const if (!s.writeInt32(numVoices)) return kResultFalse; + if (!s.writeInt32(oversampling)) + return kResultFalse; + return kResultTrue; } @@ -75,3 +82,16 @@ tresult SfizzUiState::store(IBStream* state) const return kResultTrue; } + +/// +int SfizzMisc::adaptOversamplingFactor(int valueDenorm) +{ + if (valueDenorm >= 8) + return SFIZZ_OVERSAMPLING_X8; + else if (valueDenorm >= 4) + return SFIZZ_OVERSAMPLING_X4; + else if (valueDenorm >= 2) + return SFIZZ_OVERSAMPLING_X2; + else + return SFIZZ_OVERSAMPLING_X1; +} diff --git a/vst/SfizzVstState.h b/vst/SfizzVstState.h index f68412f6..6bc6dbe5 100644 --- a/vst/SfizzVstState.h +++ b/vst/SfizzVstState.h @@ -20,6 +20,7 @@ enum { enum { kPidVolume, kPidNumVoices, + kPidOversampling, kPidMidiCC0, kPidMidiCCLast = kPidMidiCC0 + kNumControllerParams - 1, kPidMidiAftertouch, @@ -32,6 +33,7 @@ public: std::string sfzFile; float volume = 0; int numVoices = 64; + int oversampling = 1; static constexpr uint64 currentStateVersion = 0; @@ -75,3 +77,9 @@ struct SfizzParameterRange { static constexpr SfizzParameterRange kParamVolumeRange(0.0, -60.0, +6.0); static constexpr SfizzParameterRange kParamNumVoicesRange(64.0, 1.0, 256.0); +static constexpr SfizzParameterRange kParamOversamplingRange(1.0, 1.0, 8.0); + +class SfizzMisc { +public: + static int adaptOversamplingFactor(int factor); +}; From ca044d98c50952ad945fa29cdd2f2b029d24c0a1 Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Fri, 6 Mar 2020 13:15:32 +0100 Subject: [PATCH 45/93] Preload size and log2 oversampling --- vst/SfizzVstController.cpp | 29 +++++++++++++++++++++-------- vst/SfizzVstEditor.cpp | 34 +++++++++++++++++++++------------- vst/SfizzVstEditor.h | 2 ++ vst/SfizzVstProcessor.cpp | 31 ++++++++++++++++++++++++++----- vst/SfizzVstState.cpp | 23 ++++++++--------------- vst/SfizzVstState.h | 12 +++++------- 6 files changed, 83 insertions(+), 48 deletions(-) diff --git a/vst/SfizzVstController.cpp b/vst/SfizzVstController.cpp index 065c41fa..984a2521 100644 --- a/vst/SfizzVstController.cpp +++ b/vst/SfizzVstController.cpp @@ -30,6 +30,10 @@ tresult PLUGIN_API SfizzVstControllerNoUi::initialize(FUnknown* context) kParamOversamplingRange.createParameter( Steinberg::String("Oversampling"), pid++, nullptr, 0, Vst::ParameterInfo::kNoFlags, Vst::kRootUnitId)); + parameters.addParameter( + kParamPreloadSizeRange.createParameter( + Steinberg::String("Preload size"), pid++, nullptr, + 0, Vst::ParameterInfo::kNoFlags, Vst::kRootUnitId)); // MIDI controllers for (unsigned i = 0; i < kNumControllerParams; ++i) { @@ -80,10 +84,9 @@ tresult PLUGIN_API SfizzVstControllerNoUi::getParamStringByValue(Vst::ParamID ta switch (tag) { case kPidOversampling: { - int factor = SfizzMisc::adaptOversamplingFactor( - kParamOversamplingRange.denormalize(valueNormalized)); + int factorLog2 = kParamOversamplingRange.denormalize(valueNormalized); Steinberg::String buf; - buf.printf("%dX", factor); + buf.printf("%dX", 1 << factorLog2); buf.copyTo(string); return kResultTrue; } @@ -98,10 +101,14 @@ tresult PLUGIN_API SfizzVstControllerNoUi::getParamValueByString(Vst::ParamID ta case kPidOversampling: { int factor; - if (!Steinberg::String::scanInt32(string, factor, false)) + if (!Steinberg::String::scanInt32(string, factor, false) || factor < 1) factor = 1; - valueNormalized = kParamOversamplingRange.normalize( - SfizzMisc::adaptOversamplingFactor(factor)); + + int log2Factor = 0; + for (int f = factor; f > 1; f /= 2) + ++log2Factor; + + valueNormalized = kParamOversamplingRange.normalize(log2Factor); return kResultTrue; } } @@ -143,10 +150,15 @@ tresult PLUGIN_API SfizzVstController::setParamNormalized(Vst::ParamID tag, Vst: break; } case kPidOversampling: { - slotI32 = &_state.oversampling; + slotI32 = &_state.oversamplingLog2; value = kParamOversamplingRange.denormalize(normValue); break; } + case kPidPreloadSize: { + slotI32 = &_state.preloadSize; + value = kParamPreloadSizeRange.denormalize(normValue); + break; + } } bool update = false; @@ -201,7 +213,8 @@ tresult PLUGIN_API SfizzVstController::setComponentState(IBStream* state) setParamNormalized(kPidVolume, kParamVolumeRange.normalize(s.volume)); setParamNormalized(kPidNumVoices, kParamNumVoicesRange.normalize(s.numVoices)); - setParamNormalized(kPidOversampling, kParamOversamplingRange.normalize(s.oversampling)); + setParamNormalized(kPidOversampling, kParamOversamplingRange.normalize(s.oversamplingLog2)); + setParamNormalized(kPidPreloadSize, kParamPreloadSizeRange.normalize(s.preloadSize)); for (StateListener* listener : _stateListeners) listener->onStateChanged(); diff --git a/vst/SfizzVstEditor.cpp b/vst/SfizzVstEditor.cpp index 65da051a..422c1b01 100644 --- a/vst/SfizzVstEditor.cpp +++ b/vst/SfizzVstEditor.cpp @@ -86,6 +86,11 @@ void SfizzVstEditor::valueChanged(CControl* ctl) controller->performEdit(kPidOversampling, valueNorm); break; + case kTagSetPreloadSize: + controller->setParamNormalized(kPidPreloadSize, valueNorm); + controller->performEdit(kPidPreloadSize, valueNorm); + break; + default: if (tag >= kTagFirstChangePanel && tag <= kTagLastChangePanel) setActivePanel(tag - kTagFirstChangePanel); @@ -102,6 +107,7 @@ void SfizzVstEditor::enterOrLeaveEdit(CControl* ctl, bool enter) case kTagSetVolume: id = kPidVolume; break; case kTagSetNumVoices: id = kPidNumVoices; break; case kTagSetOversampling: id = kPidOversampling; break; + case kTagSetPreloadSize: id = kPidPreloadSize; break; default: return; } @@ -275,19 +281,19 @@ void SfizzVstEditor::createFrameContents() adjustMinMaxToRangeParam(slider, kPidOversampling); _oversamplingSlider = slider; - // row.top += interRow; - // row.bottom += interRow; + row.top += interRow; + row.bottom += interRow; - // label = new CTextLabel(leftSide(), "Preload size"); - // label->setFontColor(CColor(0x00, 0x00, 0x00)); - // label->setFrameColor(CColor(0x00, 0x00, 0x00, 0x00)); - // label->setBackColor(CColor(0x00, 0x00, 0x00, 0x00)); - // label->setHoriAlign(kLeftText); - // panel->addView(label); - // slider = new SimpleSlider(rightSide(), this, kTag); - // panel->addView(slider); - // adjustMinMaxToRangeParam(slider, kPid); - // _aSlider = slider; + label = new CTextLabel(leftSide(), "Preload size"); + label->setFontColor(CColor(0x00, 0x00, 0x00)); + label->setFrameColor(CColor(0x00, 0x00, 0x00, 0x00)); + label->setBackColor(CColor(0x00, 0x00, 0x00, 0x00)); + label->setHoriAlign(kLeftText); + panel->addView(label); + slider = new SimpleSlider(rightSide(), this, kTagSetPreloadSize); + panel->addView(slider); + adjustMinMaxToRangeParam(slider, kPidPreloadSize); + _preloadSizeSlider = slider; // row.top += interRow; // row.bottom += interRow; @@ -354,7 +360,9 @@ void SfizzVstEditor::updateStateDisplay() if (_numVoicesSlider) _numVoicesSlider->setValue(state.numVoices); if (_oversamplingSlider) - _oversamplingSlider->setValue(state.oversampling); + _oversamplingSlider->setValue(state.oversamplingLog2); + if (_preloadSizeSlider) + _preloadSizeSlider->setValue(state.preloadSize); setActivePanel(uiState.activePanel); } diff --git a/vst/SfizzVstEditor.h b/vst/SfizzVstEditor.h index 353db127..7ed9a4dd 100644 --- a/vst/SfizzVstEditor.h +++ b/vst/SfizzVstEditor.h @@ -64,6 +64,7 @@ private: kTagSetVolume, kTagSetNumVoices, kTagSetOversampling, + kTagSetPreloadSize, kTagFirstChangePanel, kTagLastChangePanel = kTagFirstChangePanel + kNumPanels - 1, }; @@ -73,4 +74,5 @@ private: CSliderBase *_volumeSlider = nullptr; CSliderBase *_numVoicesSlider = nullptr; CSliderBase *_oversamplingSlider = nullptr; + CSliderBase *_preloadSizeSlider = nullptr; }; diff --git a/vst/SfizzVstProcessor.cpp b/vst/SfizzVstProcessor.cpp index b7111dce..3380fbb3 100644 --- a/vst/SfizzVstProcessor.cpp +++ b/vst/SfizzVstProcessor.cpp @@ -81,8 +81,8 @@ void SfizzVstProcessor::syncStateToSynth() synth->loadSfzFile(_state.sfzFile); synth->setVolume(_state.volume); synth->setNumVoices(_state.numVoices); - synth->setOversamplingFactor( - SfizzMisc::adaptOversamplingFactor(_state.oversampling)); + synth->setOversamplingFactor(1 << _state.oversamplingLog2); + synth->setPreloadSize(_state.preloadSize); } tresult PLUGIN_API SfizzVstProcessor::canProcessSampleSize(int32 symbolicSampleSize) @@ -206,6 +206,21 @@ void SfizzVstProcessor::processParameterChanges(Vst::IParameterChanges& pc) _semaToWorker.post(); } break; + case kPidPreloadSize: + if (pointCount > 0 && vq->getPoint(pointCount - 1, sampleOffset, value) == kResultTrue) { + Vst::IMessage* msg = allocateMessage(); + if (!msg) + break; + msg->setMessageID("SetPreloadSize"); + Vst::IAttributeList* attr = msg->getAttributes(); + attr->setInt("PreloadSize", kParamPreloadSizeRange.denormalize(value)); + if (!_fifoToWorker.push(msg)) { + msg->release(); + break; + } + _semaToWorker.post(); + } + break; } } } @@ -339,9 +354,15 @@ void SfizzVstProcessor::doBackgroundWork() else if (!std::strcmp(id, "SetOversampling")) { int64 value; if (attr->getInt("Oversampling", value) == kResultTrue) { - _state.oversampling = value; - _synth->setOversamplingFactor( - SfizzMisc::adaptOversamplingFactor(value)); + _state.oversamplingLog2 = value; + _synth->setOversamplingFactor(1 << value); + } + } + else if (!std::strcmp(id, "SetPreloadSize")) { + int64 value; + if (attr->getInt("PreloadSize", value) == kResultTrue) { + _state.preloadSize = value; + _synth->setPreloadSize(value); } } diff --git a/vst/SfizzVstState.cpp b/vst/SfizzVstState.cpp index 0b416c3d..17240921 100644 --- a/vst/SfizzVstState.cpp +++ b/vst/SfizzVstState.cpp @@ -28,7 +28,10 @@ tresult SfizzVstState::load(IBStream* state) if (!s.readInt32(numVoices)) return kResultFalse; - if (!s.readInt32(oversampling)) + if (!s.readInt32(oversamplingLog2)) + return kResultFalse; + + if (!s.readInt32(preloadSize)) return kResultFalse; return kResultTrue; @@ -50,7 +53,10 @@ tresult SfizzVstState::store(IBStream* state) const if (!s.writeInt32(numVoices)) return kResultFalse; - if (!s.writeInt32(oversampling)) + if (!s.writeInt32(oversamplingLog2)) + return kResultFalse; + + if (!s.writeInt32(preloadSize)) return kResultFalse; return kResultTrue; @@ -82,16 +88,3 @@ tresult SfizzUiState::store(IBStream* state) const return kResultTrue; } - -/// -int SfizzMisc::adaptOversamplingFactor(int valueDenorm) -{ - if (valueDenorm >= 8) - return SFIZZ_OVERSAMPLING_X8; - else if (valueDenorm >= 4) - return SFIZZ_OVERSAMPLING_X4; - else if (valueDenorm >= 2) - return SFIZZ_OVERSAMPLING_X2; - else - return SFIZZ_OVERSAMPLING_X1; -} diff --git a/vst/SfizzVstState.h b/vst/SfizzVstState.h index 6bc6dbe5..f42817b3 100644 --- a/vst/SfizzVstState.h +++ b/vst/SfizzVstState.h @@ -21,6 +21,7 @@ enum { kPidVolume, kPidNumVoices, kPidOversampling, + kPidPreloadSize, kPidMidiCC0, kPidMidiCCLast = kPidMidiCC0 + kNumControllerParams - 1, kPidMidiAftertouch, @@ -33,7 +34,8 @@ public: std::string sfzFile; float volume = 0; int numVoices = 64; - int oversampling = 1; + int oversamplingLog2 = 0; + int preloadSize = 8192; static constexpr uint64 currentStateVersion = 0; @@ -77,9 +79,5 @@ struct SfizzParameterRange { static constexpr SfizzParameterRange kParamVolumeRange(0.0, -60.0, +6.0); static constexpr SfizzParameterRange kParamNumVoicesRange(64.0, 1.0, 256.0); -static constexpr SfizzParameterRange kParamOversamplingRange(1.0, 1.0, 8.0); - -class SfizzMisc { -public: - static int adaptOversamplingFactor(int factor); -}; +static constexpr SfizzParameterRange kParamOversamplingRange(0.0, 0.0, 3.0); +static constexpr SfizzParameterRange kParamPreloadSizeRange(8192.0, 1024.0, 65536.0); From ee530c623df19013ab044c16533c1b777bb67ada Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Fri, 6 Mar 2020 13:32:41 +0100 Subject: [PATCH 46/93] Move aftertouch and pitchbend up in the parameter list --- vst/SfizzVstController.cpp | 8 ++++---- vst/SfizzVstState.h | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/vst/SfizzVstController.cpp b/vst/SfizzVstController.cpp index 984a2521..e9869e50 100644 --- a/vst/SfizzVstController.cpp +++ b/vst/SfizzVstController.cpp @@ -35,6 +35,10 @@ tresult PLUGIN_API SfizzVstControllerNoUi::initialize(FUnknown* context) Steinberg::String("Preload size"), pid++, nullptr, 0, Vst::ParameterInfo::kNoFlags, Vst::kRootUnitId)); + // MIDI special controllers + parameters.addParameter(Steinberg::String("Aftertouch"), nullptr, 0, 0.5, 0, pid++, Vst::kRootUnitId); + parameters.addParameter(Steinberg::String("Pitch Bend"), nullptr, 0, 0.5, 0, pid++, Vst::kRootUnitId); + // MIDI controllers for (unsigned i = 0; i < kNumControllerParams; ++i) { Steinberg::String title; @@ -47,10 +51,6 @@ tresult PLUGIN_API SfizzVstControllerNoUi::initialize(FUnknown* context) pid++, Vst::kRootUnitId, shortTitle); } - // MIDI extra controllers - parameters.addParameter(Steinberg::String("Aftertouch"), nullptr, 0, 0.5, 0, pid++, Vst::kRootUnitId); - parameters.addParameter(Steinberg::String("Pitch Bend"), nullptr, 0, 0.5, 0, pid++, Vst::kRootUnitId); - return kResultTrue; } diff --git a/vst/SfizzVstState.h b/vst/SfizzVstState.h index f42817b3..3a2c712b 100644 --- a/vst/SfizzVstState.h +++ b/vst/SfizzVstState.h @@ -22,10 +22,10 @@ enum { kPidNumVoices, kPidOversampling, kPidPreloadSize, - kPidMidiCC0, - kPidMidiCCLast = kPidMidiCC0 + kNumControllerParams - 1, kPidMidiAftertouch, kPidMidiPitchBend, + kPidMidiCC0, + kPidMidiCCLast = kPidMidiCC0 + kNumControllerParams - 1, /* Reserved */ }; From b0840ef2adfda8d6e9583d938829c8f2f423bc06 Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Fri, 6 Mar 2020 13:37:12 +0100 Subject: [PATCH 47/93] Add the offline mode (freewheeling) --- vst/SfizzVstProcessor.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/vst/SfizzVstProcessor.cpp b/vst/SfizzVstProcessor.cpp index 3380fbb3..580f113e 100644 --- a/vst/SfizzVstProcessor.cpp +++ b/vst/SfizzVstProcessor.cpp @@ -145,6 +145,11 @@ tresult PLUGIN_API SfizzVstProcessor::process(Vst::ProcessData& data) return kResultTrue; } + if (data.processMode == Vst::kOffline) + synth.enableFreeWheeling(); + else + synth.disableFreeWheeling(); + if (Vst::IParameterChanges* pc = data.inputParameterChanges) processControllerChanges(*pc); From 598aeae2bafe4accd5757e9206dcbe12e25f2b59 Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Fri, 6 Mar 2020 14:37:25 +0100 Subject: [PATCH 48/93] Set the Windows compatibility version to 7 --- cmake/SfizzConfig.cmake | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmake/SfizzConfig.cmake b/cmake/SfizzConfig.cmake index e2be25e1..19215e3a 100644 --- a/cmake/SfizzConfig.cmake +++ b/cmake/SfizzConfig.cmake @@ -12,9 +12,9 @@ set (CMAKE_POSITION_INDEPENDENT_CODE ON) set (CMAKE_CXX_VISIBILITY_PRESET hidden) set (CMAKE_VISIBILITY_INLINES_HIDDEN ON) -# Set Windows compatibility level to Vista +# Set Windows compatibility level to 7 if (WIN32) - add_compile_definitions(_WIN32_WINNT=0x600) + add_compile_definitions(_WIN32_WINNT=0x601) endif() # Add required flags for the builds From 3e93b7ffac47b8fad5707df618a97877e2ebdc41 Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Fri, 6 Mar 2020 14:38:54 +0100 Subject: [PATCH 49/93] Fix a renaming problem in RTSemaphore --- vst/RTSemaphore.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vst/RTSemaphore.h b/vst/RTSemaphore.h index 0cd6135b..5ec3c765 100644 --- a/vst/RTSemaphore.h +++ b/vst/RTSemaphore.h @@ -88,7 +88,7 @@ inline bool RTSemaphore::try_wait() #elif defined(_WIN32) inline RTSemaphore::RTSemaphore(unsigned value) { - sem_ = CreateRTSemaphore(nullptr, value, LONG_MAX, nullptr); + sem_ = CreateSemaphore(nullptr, value, LONG_MAX, nullptr); if (!sem_) throw std::runtime_error("RTSemaphore::RTSemaphore"); } @@ -100,7 +100,7 @@ inline RTSemaphore::~RTSemaphore() inline void RTSemaphore::post() { - if (!ReleaseRTSemaphore(sem_, 1, nullptr)) + if (!ReleaseSemaphore(sem_, 1, nullptr)) throw std::runtime_error("RTSemaphore::post"); } From 985cabeb27c977bdea1b563714672df546f6f667 Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Fri, 6 Mar 2020 14:46:46 +0100 Subject: [PATCH 50/93] Workaround for VST wide character in MinGW --- vst/cmake/Vst3.cmake | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/vst/cmake/Vst3.cmake b/vst/cmake/Vst3.cmake index 32b9b96a..6c8f5963 100644 --- a/vst/cmake/Vst3.cmake +++ b/vst/cmake/Vst3.cmake @@ -46,6 +46,10 @@ function(plugin_add_vst3sdk NAME) endif() target_include_directories("${NAME}" PRIVATE "${VST3SDK_BASEDIR}") target_link_libraries("${NAME}" PRIVATE Threads::Threads) + if(MINGW) + target_compile_definitions("${NAME}" PRIVATE + "_NATIVE_WCHAR_T_DEFINED=1" "__wchar_t=wchar_t") + endif() endfunction() # --- VSTGUI --- From 86c4ed00aea057133545b55c41365804d4885205 Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Fri, 6 Mar 2020 14:59:19 +0100 Subject: [PATCH 51/93] Link system libs for Windows VSTGUI --- vst/cmake/Vst3.cmake | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/vst/cmake/Vst3.cmake b/vst/cmake/Vst3.cmake index 6c8f5963..d1e3fdc2 100644 --- a/vst/cmake/Vst3.cmake +++ b/vst/cmake/Vst3.cmake @@ -175,7 +175,19 @@ function(plugin_add_vstgui NAME) target_include_directories("${NAME}" PRIVATE "${VST3SDK_BASEDIR}/vstgui4") if(WIN32) - # + find_library(OPENGL32_LIBRARY "opengl32") + find_library(D2D1_LIBRARY "d2d1") + find_library(DWRITE_LIBRARY "dwrite") + find_library(DWMAPI_LIBRARY "dwmapi") + find_library(WINDOWSCODECS_LIBRARY "windowscodecs") + find_library(SHLWAPI_LIBRARY "shlwapi") + target_link_libraries("${NAME}" PRIVATE + "${OPENGL32_LIBRARY}" + "${D2D1_LIBRARY}" + "${DWRITE_LIBRARY}" + "${DWMAPI_LIBRARY}" + "${WINDOWSCODECS_LIBRARY}" + "${SHLWAPI_LIBRARY}") elseif(APPLE) # else() From 7eadbbd75306c526002c298f64d6d5123ae199ca Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Fri, 6 Mar 2020 15:02:58 +0100 Subject: [PATCH 52/93] Add the Cocoa library to the MacOS link (add others later..) --- vst/cmake/Vst3.cmake | 3 +++ 1 file changed, 3 insertions(+) diff --git a/vst/cmake/Vst3.cmake b/vst/cmake/Vst3.cmake index d1e3fdc2..bbac4600 100644 --- a/vst/cmake/Vst3.cmake +++ b/vst/cmake/Vst3.cmake @@ -189,6 +189,9 @@ function(plugin_add_vstgui NAME) "${WINDOWSCODECS_LIBRARY}" "${SHLWAPI_LIBRARY}") elseif(APPLE) + find_library(COCOA_LIBRARY "Cocoa") + target_link_libraries("${NAME}" PRIVATE + "${COCOA_LIBRARY}") # else() find_package(X11 REQUIRED) From c44e2e69855c7111a81d2d7a9a78e254ece6ba6f Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Fri, 6 Mar 2020 15:24:57 +0100 Subject: [PATCH 53/93] Add linker options for MinGW --- vst/CMakeLists.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/vst/CMakeLists.txt b/vst/CMakeLists.txt index 0d8c505f..89c9bfc4 100644 --- a/vst/CMakeLists.txt +++ b/vst/CMakeLists.txt @@ -57,6 +57,10 @@ if (${CMAKE_SYSTEM_NAME} MATCHES "Linux") target_link_libraries(${VSTPLUGIN_PRJ_NAME} PRIVATE "-Wl,--version-script=${CMAKE_CURRENT_SOURCE_DIR}/vst3.version") endif() +sfizz_enable_lto_if_needed (${VSTPLUGIN_PRJ_NAME}) +if (MINGW) + set_target_properties (${VSTPLUGIN_PRJ_NAME} PROPERTIES LINK_FLAGS "-static") +endif() # Create the bundle (see "VST 3 Locations / Format") execute_process ( From 4648e656b40152b56fa1085a6752e996f6fb5df6 Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Fri, 6 Mar 2020 15:36:16 +0100 Subject: [PATCH 54/93] Ignore lots of VST warnings to make the log lighter --- vst/CMakeLists.txt | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/vst/CMakeLists.txt b/vst/CMakeLists.txt index 89c9bfc4..61e8f6e4 100644 --- a/vst/CMakeLists.txt +++ b/vst/CMakeLists.txt @@ -90,6 +90,18 @@ else() LIBRARY_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/${VSTPLUGIN_BUNDLE_NAME}/Contents/${VST3_PACKAGE_ARCHITECTURE}-linux") endif() +if (CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang") + target_compile_options(${VSTPLUGIN_PRJ_NAME} PRIVATE + "-Wno-extra" + "-Wno-multichar" + "-Wno-reorder" + "-Wno-class-memaccess" + "-Wno-ignored-qualifiers" + "-Wno-unused-function" + "-Wno-unused-parameter" + "-Wno-unused-variable") +endif() + # To help debugging the link only if (FALSE) target_link_options(${VSTPLUGIN_PRJ_NAME} PRIVATE "-Wl,-no-undefined") From 003b2c8cf4562e706d035e074b2de7849bca3403 Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Fri, 6 Mar 2020 16:40:40 +0100 Subject: [PATCH 55/93] Silent extraction of SDK archive --- vst/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vst/CMakeLists.txt b/vst/CMakeLists.txt index 61e8f6e4..27df8777 100644 --- a/vst/CMakeLists.txt +++ b/vst/CMakeLists.txt @@ -19,7 +19,7 @@ if (NOT EXISTS "${VST3SDK_BASEDIR}") execute_process ( COMMAND "${CMAKE_COMMAND}" -E make_directory "${CMAKE_CURRENT_SOURCE_DIR}/external" - COMMAND "${CMAKE_COMMAND}" -E tar xvf "../download/${VST3SDK_ARCHIVE}" + COMMAND "${CMAKE_COMMAND}" -E tar xf "../download/${VST3SDK_ARCHIVE}" WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/external") endif() From 44271b5dfec31e37991434ea8f202c934a3344f1 Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Fri, 6 Mar 2020 16:41:38 +0100 Subject: [PATCH 56/93] Add the correct link frameworks for Mac --- vst/cmake/Vst3.cmake | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/vst/cmake/Vst3.cmake b/vst/cmake/Vst3.cmake index bbac4600..62c430a7 100644 --- a/vst/cmake/Vst3.cmake +++ b/vst/cmake/Vst3.cmake @@ -189,10 +189,19 @@ function(plugin_add_vstgui NAME) "${WINDOWSCODECS_LIBRARY}" "${SHLWAPI_LIBRARY}") elseif(APPLE) - find_library(COCOA_LIBRARY "Cocoa") + find_library(APPLE_COREFOUNDATION_LIBRARY "CoreFoundation") + find_library(APPLE_COCOA_LIBRARY "Cocoa") + find_library(APPLE_OPENGL_LIBRARY "OpenGL") + find_library(APPLE_ACCELERATE_LIBRARY "Accelerate") + find_library(APPLE_QUARTZCORE_LIBRARY "QuartzCore") + find_library(APPLE_CARBON_LIBRARY "Carbon") target_link_libraries("${NAME}" PRIVATE - "${COCOA_LIBRARY}") - # + "${APPLE_COREFOUNDATION_LIBRARY}" + "${APPLE_COCOA_LIBRARY}" + "${APPLE_OPENGL_LIBRARY}" + "${APPLE_ACCELERATE_LIBRARY}" + "${APPLE_QUARTZCORE_LIBRARY}" + "${APPLE_CARBON_LIBRARY}") else() find_package(X11 REQUIRED) find_package(Freetype REQUIRED) From 784352a444dc4002e717707efb60353df1f5bdb6 Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Fri, 6 Mar 2020 16:47:48 +0100 Subject: [PATCH 57/93] Have the vst3 bundle in the project binary dir --- vst/CMakeLists.txt | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/vst/CMakeLists.txt b/vst/CMakeLists.txt index 27df8777..b9450c3b 100644 --- a/vst/CMakeLists.txt +++ b/vst/CMakeLists.txt @@ -64,30 +64,30 @@ endif() # Create the bundle (see "VST 3 Locations / Format") execute_process ( - COMMAND "${CMAKE_COMMAND}" -E make_directory "${CMAKE_CURRENT_BINARY_DIR}/${VSTPLUGIN_BUNDLE_NAME}/Contents/Resources") + COMMAND "${CMAKE_COMMAND}" -E make_directory "${PROJECT_BINARY_DIR}/${VSTPLUGIN_BUNDLE_NAME}/Contents/Resources") file (COPY "${CMAKE_CURRENT_SOURCE_DIR}/resources/logo.png" - DESTINATION "${CMAKE_CURRENT_BINARY_DIR}/${VSTPLUGIN_BUNDLE_NAME}/Contents/Resources") + DESTINATION "${PROJECT_BINARY_DIR}/${VSTPLUGIN_BUNDLE_NAME}/Contents/Resources") if(WIN32) set_target_properties(${VSTPLUGIN_PRJ_NAME} PROPERTIES - LIBRARY_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/${VSTPLUGIN_BUNDLE_NAME}/Contents/${VST3_PACKAGE_ARCHITECTURE}-win") + LIBRARY_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/${VSTPLUGIN_BUNDLE_NAME}/Contents/${VST3_PACKAGE_ARCHITECTURE}-win") file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/win/Plugin.ico" "${CMAKE_CURRENT_SOURCE_DIR}/win/desktop.ini" - DESTINATION "${CMAKE_CURRENT_BINARY_DIR}/${VSTPLUGIN_BUNDLE_NAME}") + DESTINATION "${PROJECT_BINARY_DIR}/${VSTPLUGIN_BUNDLE_NAME}") elseif(APPLE) set_target_properties(${VSTPLUGIN_PRJ_NAME} PROPERTIES SUFFIX "" - LIBRARY_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/${VSTPLUGIN_BUNDLE_NAME}/Contents/MacOS") + LIBRARY_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/${VSTPLUGIN_BUNDLE_NAME}/Contents/MacOS") file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/mac/PkgInfo" - DESTINATION "${CMAKE_CURRENT_BINARY_DIR}/${VSTPLUGIN_BUNDLE_NAME}/Contents") + DESTINATION "${PROJECT_BINARY_DIR}/${VSTPLUGIN_BUNDLE_NAME}/Contents") set(SFIZZ_VST3_BUNDLE_EXECUTABLE "${PROJECT_NAME}") set(SFIZZ_VST3_BUNDLE_VERSION "${PROJECT_VERSION}") configure_file("${CMAKE_CURRENT_SOURCE_DIR}/mac/Info.plist" - "${CMAKE_CURRENT_BINARY_DIR}/${VSTPLUGIN_BUNDLE_NAME}/Contents/Info.plist" @ONLY) + "${PROJECT_BINARY_DIR}/${VSTPLUGIN_BUNDLE_NAME}/Contents/Info.plist" @ONLY) file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/mac/Plugin.icns" - DESTINATION "${CMAKE_CURRENT_BINARY_DIR}/${VSTPLUGIN_BUNDLE_NAME}/Contents/Resources") + DESTINATION "${PROJECT_BINARY_DIR}/${VSTPLUGIN_BUNDLE_NAME}/Contents/Resources") else() set_target_properties(${VSTPLUGIN_PRJ_NAME} PROPERTIES - LIBRARY_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/${VSTPLUGIN_BUNDLE_NAME}/Contents/${VST3_PACKAGE_ARCHITECTURE}-linux") + LIBRARY_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/${VSTPLUGIN_BUNDLE_NAME}/Contents/${VST3_PACKAGE_ARCHITECTURE}-linux") endif() if (CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang") From e2bf7f6f2110ce5c016804078334a479a25cd610 Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Fri, 6 Mar 2020 16:48:39 +0100 Subject: [PATCH 58/93] Attempt appveyor build of VST3 --- appveyor.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index 99b80d33..8bc4f3f8 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -6,6 +6,7 @@ platform: - x64 cache: - c:\tools\vcpkg\installed\ -> appveyor.yml + - vst\download install: - cmd: choco install -y innosetup @@ -20,7 +21,7 @@ before_build: - cmd: cd CMakeBuild - cmd: if %platform%==Win32 set CMAKE_GENERATOR=Visual Studio 15 2017 - cmd: if %platform%==x64 set CMAKE_GENERATOR=Visual Studio 15 2017 Win64 -- cmd: cmake .. -G"%CMAKE_GENERATOR%" -DSFIZZ_JACK=OFF -DSFIZZ_BENCHMARKS=OFF -DSFIZZ_TESTS=OFF -DSFIZZ_LV2=ON -DCMAKE_BUILD_TYPE=Release -DVCPKG_TARGET_TRIPLET=%VCPKG_TRIPLET% -DCMAKE_TOOLCHAIN_FILE=C:/Tools/vcpkg/scripts/buildsystems/vcpkg.cmake +- cmd: cmake .. -G"%CMAKE_GENERATOR%" -DSFIZZ_JACK=OFF -DSFIZZ_BENCHMARKS=OFF -DSFIZZ_TESTS=OFF -DSFIZZ_LV2=ON -DSFIZZ_VST=ON -DCMAKE_BUILD_TYPE=Release -DVCPKG_TARGET_TRIPLET=%VCPKG_TRIPLET% -DCMAKE_TOOLCHAIN_FILE=C:/Tools/vcpkg/scripts/buildsystems/vcpkg.cmake build_script: - cmd: cmake --build . --config Release -j @@ -31,6 +32,7 @@ after_build: - cmd: if %platform%==Win32 set RELEASE_ARCH=x86 - cmd: if %platform%==x64 set RELEASE_ARCH=x64 - cmd: 7z a sfizz-lv2-%APPVEYOR_REPO_TAG_NAME%-%RELEASE_ARCH%-msvc.zip sfizz.lv2 +- cmd: 7z a sfizz-vst3-%APPVEYOR_REPO_TAG_NAME%-%RELEASE_ARCH%-msvc.zip sfizz.vst3 - cmd: 7z a sfizz-lib-%APPVEYOR_REPO_TAG_NAME%-%RELEASE_ARCH%-msvc.zip src/Release/sfizz* - cmd: iscc.exe /dARCH=%RELEASE_ARCH% innosetup.iss From 52e7120dbd0d17d64a9c519bf5bb1f983c135508 Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Fri, 6 Mar 2020 17:00:08 +0100 Subject: [PATCH 59/93] Match more patterns of X86 processor --- vst/cmake/Vst3.cmake | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vst/cmake/Vst3.cmake b/vst/cmake/Vst3.cmake index 62c430a7..215ca7a0 100644 --- a/vst/cmake/Vst3.cmake +++ b/vst/cmake/Vst3.cmake @@ -257,9 +257,9 @@ if(NOT VST3_PACKAGE_ARCHITECTURE) if(APPLE) # VST3 packages are universal on Apple, architecture string not needed else() - if(CMAKE_SYSTEM_PROCESSOR STREQUAL "x86_64") + if(CMAKE_SYSTEM_PROCESSOR MATCHES "^(x86_64|amd64|AMD64)$") set(VST3_PACKAGE_ARCHITECTURE "x86_64") - elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "^i.86$") + elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "^(i.86|x86)$") if(WIN32) set(VST3_PACKAGE_ARCHITECTURE "x86") else() From 73fde1ae22a8dcb765197f1c80cc144b764cd022 Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Fri, 6 Mar 2020 17:07:37 +0100 Subject: [PATCH 60/93] Link system libs MinGW-only --- vst/cmake/Vst3.cmake | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/vst/cmake/Vst3.cmake b/vst/cmake/Vst3.cmake index 215ca7a0..1a17b6f3 100644 --- a/vst/cmake/Vst3.cmake +++ b/vst/cmake/Vst3.cmake @@ -175,19 +175,22 @@ function(plugin_add_vstgui NAME) target_include_directories("${NAME}" PRIVATE "${VST3SDK_BASEDIR}/vstgui4") if(WIN32) - find_library(OPENGL32_LIBRARY "opengl32") - find_library(D2D1_LIBRARY "d2d1") - find_library(DWRITE_LIBRARY "dwrite") - find_library(DWMAPI_LIBRARY "dwmapi") - find_library(WINDOWSCODECS_LIBRARY "windowscodecs") - find_library(SHLWAPI_LIBRARY "shlwapi") - target_link_libraries("${NAME}" PRIVATE - "${OPENGL32_LIBRARY}" - "${D2D1_LIBRARY}" - "${DWRITE_LIBRARY}" - "${DWMAPI_LIBRARY}" - "${WINDOWSCODECS_LIBRARY}" - "${SHLWAPI_LIBRARY}") + if (NOT MSVC) + # autolinked on MSVC with pragmas + find_library(OPENGL32_LIBRARY "opengl32") + find_library(D2D1_LIBRARY "d2d1") + find_library(DWRITE_LIBRARY "dwrite") + find_library(DWMAPI_LIBRARY "dwmapi") + find_library(WINDOWSCODECS_LIBRARY "windowscodecs") + find_library(SHLWAPI_LIBRARY "shlwapi") + target_link_libraries("${NAME}" PRIVATE + "${OPENGL32_LIBRARY}" + "${D2D1_LIBRARY}" + "${DWRITE_LIBRARY}" + "${DWMAPI_LIBRARY}" + "${WINDOWSCODECS_LIBRARY}" + "${SHLWAPI_LIBRARY}") + endif() elseif(APPLE) find_library(APPLE_COREFOUNDATION_LIBRARY "CoreFoundation") find_library(APPLE_COCOA_LIBRARY "Cocoa") From 836df51264cdf63ee832a4d899e864d449ee87e1 Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Fri, 6 Mar 2020 17:36:54 +0100 Subject: [PATCH 61/93] Need NOMINMAX for Windows VST --- vst/cmake/Vst3.cmake | 1 + 1 file changed, 1 insertion(+) diff --git a/vst/cmake/Vst3.cmake b/vst/cmake/Vst3.cmake index 1a17b6f3..5272d57a 100644 --- a/vst/cmake/Vst3.cmake +++ b/vst/cmake/Vst3.cmake @@ -175,6 +175,7 @@ function(plugin_add_vstgui NAME) target_include_directories("${NAME}" PRIVATE "${VST3SDK_BASEDIR}/vstgui4") if(WIN32) + target_compile_definitions("${NAME}" PRIVATE "NOMINMAX=1") if (NOT MSVC) # autolinked on MSVC with pragmas find_library(OPENGL32_LIBRARY "opengl32") From 95d6c581f74e61dc2b8a57652c06ff72af4032fb Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Fri, 6 Mar 2020 17:50:32 +0100 Subject: [PATCH 62/93] Silence VST warning: unknown pragmas --- vst/CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/vst/CMakeLists.txt b/vst/CMakeLists.txt index b9450c3b..c8b203cc 100644 --- a/vst/CMakeLists.txt +++ b/vst/CMakeLists.txt @@ -97,6 +97,7 @@ if (CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang") "-Wno-reorder" "-Wno-class-memaccess" "-Wno-ignored-qualifiers" + "-Wno-unknown-pragmas" "-Wno-unused-function" "-Wno-unused-parameter" "-Wno-unused-variable") From 14d76e6282b4426f3d101ace025ff4ade81d3f03 Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Fri, 6 Mar 2020 17:51:06 +0100 Subject: [PATCH 63/93] Use Steinberg's fixed size types, to fix MSVC build --- vst/SfizzVstController.cpp | 6 +++--- vst/SfizzVstState.h | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/vst/SfizzVstController.cpp b/vst/SfizzVstController.cpp index e9869e50..77b71279 100644 --- a/vst/SfizzVstController.cpp +++ b/vst/SfizzVstController.cpp @@ -100,12 +100,12 @@ tresult PLUGIN_API SfizzVstControllerNoUi::getParamValueByString(Vst::ParamID ta switch (tag) { case kPidOversampling: { - int factor; + int32 factor; if (!Steinberg::String::scanInt32(string, factor, false) || factor < 1) factor = 1; - int log2Factor = 0; - for (int f = factor; f > 1; f /= 2) + int32 log2Factor = 0; + for (int32 f = factor; f > 1; f /= 2) ++log2Factor; valueNormalized = kParamOversamplingRange.normalize(log2Factor); diff --git a/vst/SfizzVstState.h b/vst/SfizzVstState.h index 3a2c712b..5f3a0fb0 100644 --- a/vst/SfizzVstState.h +++ b/vst/SfizzVstState.h @@ -33,9 +33,9 @@ class SfizzVstState { public: std::string sfzFile; float volume = 0; - int numVoices = 64; - int oversamplingLog2 = 0; - int preloadSize = 8192; + int32 numVoices = 64; + int32 oversamplingLog2 = 0; + int32 preloadSize = 8192; static constexpr uint64 currentStateVersion = 0; @@ -45,7 +45,7 @@ public: class SfizzUiState { public: - unsigned activePanel = 0; + uint32 activePanel = 0; static constexpr uint64 currentStateVersion = 0; From 7ab7f9864e763272dc4c0bd8635f089dabc7919e Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Fri, 6 Mar 2020 18:16:45 +0100 Subject: [PATCH 64/93] Try fixing the library output path on MS (no Release/ subfolder) --- vst/CMakeLists.txt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/vst/CMakeLists.txt b/vst/CMakeLists.txt index c8b203cc..cf83113c 100644 --- a/vst/CMakeLists.txt +++ b/vst/CMakeLists.txt @@ -70,6 +70,11 @@ file (COPY "${CMAKE_CURRENT_SOURCE_DIR}/resources/logo.png" if(WIN32) set_target_properties(${VSTPLUGIN_PRJ_NAME} PROPERTIES LIBRARY_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/${VSTPLUGIN_BUNDLE_NAME}/Contents/${VST3_PACKAGE_ARCHITECTURE}-win") + foreach(config ${CMAKE_CONFIGURATION_TYPES}) + string(TOUPPER "${config}" config) + set_target_properties(${VSTPLUGIN_PRJ_NAME} PROPERTIES + "LIBRARY_OUTPUT_DIRECTORY_${config}" "${PROJECT_BINARY_DIR}/${VSTPLUGIN_BUNDLE_NAME}/Contents/${VST3_PACKAGE_ARCHITECTURE}-win") + endforeach() file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/win/Plugin.ico" "${CMAKE_CURRENT_SOURCE_DIR}/win/desktop.ini" DESTINATION "${PROJECT_BINARY_DIR}/${VSTPLUGIN_BUNDLE_NAME}") From 9dcefe740cf40f37dd7c62ba02f255d53c6cfc21 Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Fri, 6 Mar 2020 18:58:11 +0100 Subject: [PATCH 65/93] Add the GPL 3.0 license for VST [ci skip] --- vst/gpl-3.0.txt | 674 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 674 insertions(+) create mode 100644 vst/gpl-3.0.txt diff --git a/vst/gpl-3.0.txt b/vst/gpl-3.0.txt new file mode 100644 index 00000000..f288702d --- /dev/null +++ b/vst/gpl-3.0.txt @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU 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 +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. From 5b91f71c68d292b834f8e208bf27fa18f5d9e0bd Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Fri, 6 Mar 2020 19:00:09 +0100 Subject: [PATCH 66/93] Copy gpl-3.0.txt in the VST bundle [ci skip] --- vst/CMakeLists.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/vst/CMakeLists.txt b/vst/CMakeLists.txt index cf83113c..94d924b4 100644 --- a/vst/CMakeLists.txt +++ b/vst/CMakeLists.txt @@ -95,6 +95,9 @@ else() LIBRARY_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/${VSTPLUGIN_BUNDLE_NAME}/Contents/${VST3_PACKAGE_ARCHITECTURE}-linux") endif() +file(COPY "gpl-3.0.txt" + DESTINATION "${PROJECT_BINARY_DIR}/${VSTPLUGIN_BUNDLE_NAME}") + if (CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang") target_compile_options(${VSTPLUGIN_PRJ_NAME} PRIVATE "-Wno-extra" From 99317e764d92ae3bb002e8995e160cd5edc6ba1e Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Fri, 6 Mar 2020 19:55:36 +0100 Subject: [PATCH 67/93] Update the installer script for VST --- cmake/VSTConfig.cmake | 19 +++++++++++++++++++ scripts/innosetup.iss.in | 29 ++++++++++++++++++++--------- src/CMakeLists.txt | 4 +++- vst/cmake/Vst3.cmake | 19 ------------------- 4 files changed, 42 insertions(+), 29 deletions(-) diff --git a/cmake/VSTConfig.cmake b/cmake/VSTConfig.cmake index 0d1de3e1..e595db5c 100644 --- a/cmake/VSTConfig.cmake +++ b/cmake/VSTConfig.cmake @@ -2,3 +2,22 @@ set (VSTPLUGIN_NAME "sfizz") set (VSTPLUGIN_VENDOR "Paul Ferrand") set (VSTPLUGIN_URL "http://sfztools.github.io/sfizz") set (VSTPLUGIN_EMAIL "paul@ferrand.cc") + +# --- VST3 Bundle architecture --- +if(NOT VST3_PACKAGE_ARCHITECTURE) + if(APPLE) + # VST3 packages are universal on Apple, architecture string not needed + else() + if(CMAKE_SYSTEM_PROCESSOR MATCHES "^(x86_64|amd64|AMD64)$") + set(VST3_PACKAGE_ARCHITECTURE "x86_64") + elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "^(i.86|x86)$") + if(WIN32) + set(VST3_PACKAGE_ARCHITECTURE "x86") + else() + set(VST3_PACKAGE_ARCHITECTURE "i386") + endif() + else() + message(FATAL_ERROR "We don't know this architecture for VST3: ${CMAKE_SYSTEM_PROCESSOR}.") + endif() + endif() +endif() diff --git a/scripts/innosetup.iss.in b/scripts/innosetup.iss.in index 92de0abf..00d5642c 100644 --- a/scripts/innosetup.iss.in +++ b/scripts/innosetup.iss.in @@ -1,4 +1,5 @@ -#define MyAppName "sfizz-lv2" +; -*- mode: iss; -*- +#define MyAppName "sfizz" #define MyAppVersion "@PROJECT_VERSION@" #define MyAppPublisher "sfizz Team" #define MyAppURL "https://sfztools.github.io/sfizz/" @@ -28,25 +29,35 @@ ArchitecturesInstallIn64BitMode={#Arch} Compression=lzma SolidCompression=yes -DefaultDirName={commoncf}\LV2 +DefaultDirName={commonpf}\{#MyAppName} DefaultGroupName={#MyAppPublisher} -DisableDirPage=yes +;DisableDirPage=yes LicenseFile="sfizz.lv2\LICENSE.md" OutputBaseFileName={#MyAppName}-{#MyAppVersion}-{#Arch}-msvc-setup OutputDir=. -UninstallFilesDir={commonpf}\{#MyAppName} +UninstallFilesDir={app} WizardImageFile="C:\Program Files (x86)\Inno Setup 6\WizModernImage-IS.bmp" WizardSmallImageFile="C:\Program Files (x86)\Inno Setup 6\WizModernSmallImage-IS.bmp" [Languages] Name: "english"; MessagesFile: "compiler:Default.isl" +[Components] +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; + [Files] -Source: "sfizz.lv2\sfizz.dll"; DestDir: {commoncf}\LV2\sfizz.lv2; Flags: ignoreversion -Source: "sfizz.lv2\manifest.ttl"; DestDir: {commoncf}\LV2\sfizz.lv2 -Source: "sfizz.lv2\sfizz.ttl"; DestDir: {commoncf}\LV2\sfizz.lv2 -Source: "sfizz.lv2\lgpl-3.0.txt"; DestDir: {commonpf}\{#MyAppName} -Source: "sfizz.lv2\LICENSE.md"; DestDir: {commonpf}\{#MyAppName} +Source: "sfizz.lv2\sfizz.dll"; Components: lv2; DestDir: "{commoncf}\LV2\sfizz.lv2"; Flags: ignoreversion +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\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.dll"; Components: vst3; DestDir: "{commoncf}\VST3\sfizz.vst3\Contents\@VST3_PACKAGE_ARCHITECTURE@-win"; Flags: ignoreversion +Source: "sfizz.vst3\Contents\Resources\logo.png"; Components: vst3; DestDir: "{commoncf}\VST3\sfizz.vst3\Contents\Resources" +Source: "sfizz.vst3\Plugin.ico"; Components: vst3; DestDir: "{commoncf}\VST3\sfizz.vst3" +Source: "sfizz.vst3\gpl-3.0.txt"; Components: main; DestDir: "{app}" ;Source: "setup\vc_redist.x64.exe"; DestDir: {tmp}; Flags: deleteafterinstall ; NOTE: Don't use "Flags: ignoreversion" on any shared system files diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 3580130c..fcaeca79 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -47,7 +47,9 @@ if (NOT MSVC) PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) configure_file (${PROJECT_SOURCE_DIR}/scripts/sfizz.pc.in sfizz.pc @ONLY) -else() +endif() +if(WIN32) + include(VSTConfig) configure_file (${PROJECT_SOURCE_DIR}/scripts/innosetup.iss.in ${PROJECT_BINARY_DIR}/innosetup.iss @ONLY) endif() diff --git a/vst/cmake/Vst3.cmake b/vst/cmake/Vst3.cmake index 5272d57a..fb89987c 100644 --- a/vst/cmake/Vst3.cmake +++ b/vst/cmake/Vst3.cmake @@ -255,22 +255,3 @@ function(plugin_add_vstgui NAME) target_include_directories("${NAME}" PRIVATE external/steinberg/src) endfunction() - -# --- VST3 Bundle architecture --- -if(NOT VST3_PACKAGE_ARCHITECTURE) - if(APPLE) - # VST3 packages are universal on Apple, architecture string not needed - else() - if(CMAKE_SYSTEM_PROCESSOR MATCHES "^(x86_64|amd64|AMD64)$") - set(VST3_PACKAGE_ARCHITECTURE "x86_64") - elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "^(i.86|x86)$") - if(WIN32) - set(VST3_PACKAGE_ARCHITECTURE "x86") - else() - set(VST3_PACKAGE_ARCHITECTURE "i386") - endif() - else() - message(FATAL_ERROR "We don't know this architecture for VST3: ${CMAKE_SYSTEM_PROCESSOR}.") - endif() - endif() -endif() From a82d866866b44a26e78900b7449fcad7af484220 Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Fri, 6 Mar 2020 20:09:34 +0100 Subject: [PATCH 68/93] Print the VST architecture at CMake time --- cmake/VSTConfig.cmake | 3 +++ 1 file changed, 3 insertions(+) diff --git a/cmake/VSTConfig.cmake b/cmake/VSTConfig.cmake index e595db5c..26d642d7 100644 --- a/cmake/VSTConfig.cmake +++ b/cmake/VSTConfig.cmake @@ -21,3 +21,6 @@ if(NOT VST3_PACKAGE_ARCHITECTURE) endif() endif() endif() + +message(STATUS "The system architecture is: ${CMAKE_SYSTEM_PROCESSOR}") +message(STATUS "The VST3 architecture is deduced as: ${VST3_PACKAGE_ARCHITECTURE}") From 8fdfc98cc9dce162d454954b427a246d8171da73 Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Fri, 6 Mar 2020 20:18:35 +0100 Subject: [PATCH 69/93] Try specifying the platform as indicated in cmake-generators(7) --- appveyor.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index 8bc4f3f8..294068bb 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -19,9 +19,7 @@ before_build: - cmd: git submodule update --init - cmd: mkdir CMakeBuild - cmd: cd CMakeBuild -- cmd: if %platform%==Win32 set CMAKE_GENERATOR=Visual Studio 15 2017 -- cmd: if %platform%==x64 set CMAKE_GENERATOR=Visual Studio 15 2017 Win64 -- cmd: cmake .. -G"%CMAKE_GENERATOR%" -DSFIZZ_JACK=OFF -DSFIZZ_BENCHMARKS=OFF -DSFIZZ_TESTS=OFF -DSFIZZ_LV2=ON -DSFIZZ_VST=ON -DCMAKE_BUILD_TYPE=Release -DVCPKG_TARGET_TRIPLET=%VCPKG_TRIPLET% -DCMAKE_TOOLCHAIN_FILE=C:/Tools/vcpkg/scripts/buildsystems/vcpkg.cmake +- cmd: cmake .. -G"Visual Studio 15 2017" -A"%platform%" -DSFIZZ_JACK=OFF -DSFIZZ_BENCHMARKS=OFF -DSFIZZ_TESTS=OFF -DSFIZZ_LV2=ON -DSFIZZ_VST=ON -DCMAKE_BUILD_TYPE=Release -DVCPKG_TARGET_TRIPLET=%VCPKG_TRIPLET% -DCMAKE_TOOLCHAIN_FILE=C:/Tools/vcpkg/scripts/buildsystems/vcpkg.cmake build_script: - cmd: cmake --build . --config Release -j From 0bb1a34f552edbec104955a3996a00da98cc1f8e Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Fri, 6 Mar 2020 20:27:06 +0100 Subject: [PATCH 70/93] Try fixing processor detection with MSVC --- cmake/VSTConfig.cmake | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/cmake/VSTConfig.cmake b/cmake/VSTConfig.cmake index 26d642d7..04ded138 100644 --- a/cmake/VSTConfig.cmake +++ b/cmake/VSTConfig.cmake @@ -3,24 +3,34 @@ set (VSTPLUGIN_VENDOR "Paul Ferrand") set (VSTPLUGIN_URL "http://sfztools.github.io/sfizz") set (VSTPLUGIN_EMAIL "paul@ferrand.cc") +# The variable CMAKE_SYSTEM_PROCESSOR is incorrect on Visual studio... +# see https://gitlab.kitware.com/cmake/cmake/issues/15170 + +if(MSVC) + set(VST3_SYSTEM_PROCESSOR "${MSVC_CXX_ARCHITECTURE_ID}") +else() + set(VST3_SYSTEM_PROCESSOR "${CMAKE_SYSTEM_PROCESSOR}") +endif() + +message(STATUS "The system architecture is: ${VST3_SYSTEM_PROCESSOR}") + # --- VST3 Bundle architecture --- if(NOT VST3_PACKAGE_ARCHITECTURE) if(APPLE) # VST3 packages are universal on Apple, architecture string not needed else() - if(CMAKE_SYSTEM_PROCESSOR MATCHES "^(x86_64|amd64|AMD64)$") + if(VST3_SYSTEM_PROCESSOR MATCHES "^(x86_64|amd64|AMD64)$") set(VST3_PACKAGE_ARCHITECTURE "x86_64") - elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "^(i.86|x86)$") + elseif(VST3_SYSTEM_PROCESSOR MATCHES "^(i.86|x86)$") if(WIN32) set(VST3_PACKAGE_ARCHITECTURE "x86") else() set(VST3_PACKAGE_ARCHITECTURE "i386") endif() else() - message(FATAL_ERROR "We don't know this architecture for VST3: ${CMAKE_SYSTEM_PROCESSOR}.") + message(FATAL_ERROR "We don't know this architecture for VST3: ${VST3_SYSTEM_PROCESSOR}.") endif() endif() endif() -message(STATUS "The system architecture is: ${CMAKE_SYSTEM_PROCESSOR}") message(STATUS "The VST3 architecture is deduced as: ${VST3_PACKAGE_ARCHITECTURE}") From 094dde6dd6508c3ca875fc839516add7fdbe7476 Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Fri, 6 Mar 2020 20:33:40 +0100 Subject: [PATCH 71/93] Add another pattern to detect the CPU for VST --- cmake/VSTConfig.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake/VSTConfig.cmake b/cmake/VSTConfig.cmake index 04ded138..dece48ab 100644 --- a/cmake/VSTConfig.cmake +++ b/cmake/VSTConfig.cmake @@ -21,7 +21,7 @@ if(NOT VST3_PACKAGE_ARCHITECTURE) else() if(VST3_SYSTEM_PROCESSOR MATCHES "^(x86_64|amd64|AMD64)$") set(VST3_PACKAGE_ARCHITECTURE "x86_64") - elseif(VST3_SYSTEM_PROCESSOR MATCHES "^(i.86|x86)$") + elseif(VST3_SYSTEM_PROCESSOR MATCHES "^(i.86|x86|X86)$") if(WIN32) set(VST3_PACKAGE_ARCHITECTURE "x86") else() From d9f9d42922f344da2cb6d78111e1c453cac4df45 Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Fri, 6 Mar 2020 20:47:35 +0100 Subject: [PATCH 72/93] Add more VST matching patterns for CPU.. --- cmake/VSTConfig.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake/VSTConfig.cmake b/cmake/VSTConfig.cmake index dece48ab..aa3d52f6 100644 --- a/cmake/VSTConfig.cmake +++ b/cmake/VSTConfig.cmake @@ -19,7 +19,7 @@ if(NOT VST3_PACKAGE_ARCHITECTURE) if(APPLE) # VST3 packages are universal on Apple, architecture string not needed else() - if(VST3_SYSTEM_PROCESSOR MATCHES "^(x86_64|amd64|AMD64)$") + if(VST3_SYSTEM_PROCESSOR MATCHES "^(x86_64|amd64|AMD64|x64|X64)$") set(VST3_PACKAGE_ARCHITECTURE "x86_64") elseif(VST3_SYSTEM_PROCESSOR MATCHES "^(i.86|x86|X86)$") if(WIN32) From ac838e388b2e678dbb7dda27b746d12cae541ed3 Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Fri, 6 Mar 2020 21:35:57 +0100 Subject: [PATCH 73/93] VST suffix on Windows should be .vst3 --- scripts/innosetup.iss.in | 2 +- vst/CMakeLists.txt | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/scripts/innosetup.iss.in b/scripts/innosetup.iss.in index 00d5642c..d97d3f2b 100644 --- a/scripts/innosetup.iss.in +++ b/scripts/innosetup.iss.in @@ -54,7 +54,7 @@ Source: "sfizz.lv2\sfizz.ttl"; Components: lv2; DestDir: "{commoncf}\LV2\sfizz.l 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.dll"; Components: vst3; DestDir: "{commoncf}\VST3\sfizz.vst3\Contents\@VST3_PACKAGE_ARCHITECTURE@-win"; Flags: ignoreversion +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\logo.png"; Components: vst3; DestDir: "{commoncf}\VST3\sfizz.vst3\Contents\Resources" Source: "sfizz.vst3\Plugin.ico"; Components: vst3; DestDir: "{commoncf}\VST3\sfizz.vst3" Source: "sfizz.vst3\gpl-3.0.txt"; Components: main; DestDir: "{app}" diff --git a/vst/CMakeLists.txt b/vst/CMakeLists.txt index 94d924b4..fd98fd12 100644 --- a/vst/CMakeLists.txt +++ b/vst/CMakeLists.txt @@ -69,6 +69,7 @@ file (COPY "${CMAKE_CURRENT_SOURCE_DIR}/resources/logo.png" DESTINATION "${PROJECT_BINARY_DIR}/${VSTPLUGIN_BUNDLE_NAME}/Contents/Resources") if(WIN32) set_target_properties(${VSTPLUGIN_PRJ_NAME} PROPERTIES + SUFFIX ".vst3" LIBRARY_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/${VSTPLUGIN_BUNDLE_NAME}/Contents/${VST3_PACKAGE_ARCHITECTURE}-win") foreach(config ${CMAKE_CONFIGURATION_TYPES}) string(TOUPPER "${config}" config) From dd8b5d93a5b1e9750e887aa64a713bd835962b47 Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Fri, 6 Mar 2020 21:48:40 +0100 Subject: [PATCH 74/93] Add the exports file for Windows VST --- vst/CMakeLists.txt | 3 +++ vst/vst3.def | 4 ++++ 2 files changed, 7 insertions(+) create mode 100644 vst/vst3.def diff --git a/vst/CMakeLists.txt b/vst/CMakeLists.txt index fd98fd12..a4e214b3 100644 --- a/vst/CMakeLists.txt +++ b/vst/CMakeLists.txt @@ -42,6 +42,9 @@ add_library(${VSTPLUGIN_PRJ_NAME} MODULE SfizzVstState.cpp GUIComponents.cpp VstPluginFactory.cpp) +if(WIN32) + target_sources(${VSTPLUGIN_PRJ_NAME} PRIVATE vst3.def) +endif() target_link_libraries(${VSTPLUGIN_PRJ_NAME} PRIVATE ${PROJECT_NAME}::${PROJECT_NAME}) target_include_directories(${VSTPLUGIN_PRJ_NAME} diff --git a/vst/vst3.def b/vst/vst3.def new file mode 100644 index 00000000..279a6a5c --- /dev/null +++ b/vst/vst3.def @@ -0,0 +1,4 @@ +EXPORTS + GetPluginFactory + InitDll + ExitDll From 126dd49e2840daaa470dee7983cd5dcb05025635 Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Fri, 6 Mar 2020 23:06:18 +0100 Subject: [PATCH 75/93] Fix finding the resource images on Windows OS --- vst/SfizzVstController.cpp | 2 ++ vst/SfizzVstEditor.cpp | 8 +++++++- vst/cmake/Vst3.cmake | 2 ++ 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/vst/SfizzVstController.cpp b/vst/SfizzVstController.cpp index 77b71279..cc13b355 100644 --- a/vst/SfizzVstController.cpp +++ b/vst/SfizzVstController.cpp @@ -122,6 +122,8 @@ IPlugView* PLUGIN_API SfizzVstController::createView(FIDString _name) { ConstString name(_name); + fprintf(stderr, "[sfizz] about to create view: %s\n", _name); + if (name != Vst::ViewType::kEditor) return nullptr; diff --git a/vst/SfizzVstEditor.cpp b/vst/SfizzVstEditor.cpp index 422c1b01..826a8930 100644 --- a/vst/SfizzVstEditor.cpp +++ b/vst/SfizzVstEditor.cpp @@ -27,6 +27,8 @@ SfizzVstEditor::~SfizzVstEditor() bool PLUGIN_API SfizzVstEditor::open(void* parent, const VSTGUI::PlatformType& platformType) { + fprintf(stderr, "[sfizz] about to open view with parent %p\n", parent); + CRect wsize(0, 0, _logo.getWidth(), _logo.getHeight()); CFrame *frame = new CFrame(wsize, this); this->frame = frame; @@ -42,7 +44,11 @@ bool PLUGIN_API SfizzVstEditor::open(void* parent, const VSTGUI::PlatformType& p createFrameContents(); updateStateDisplay(); - frame->open(parent, platformType, config); + if (!frame->open(parent, platformType, config)) { + fprintf(stderr, "[sfizz] error opening frame\n"); + return false; + } + return true; } diff --git a/vst/cmake/Vst3.cmake b/vst/cmake/Vst3.cmake index fb89987c..a8aa9074 100644 --- a/vst/cmake/Vst3.cmake +++ b/vst/cmake/Vst3.cmake @@ -254,4 +254,6 @@ function(plugin_add_vstgui NAME) target_include_directories("${NAME}" PRIVATE external/steinberg/src) + + target_compile_definitions("${NAME}" PRIVATE "SMTG_MODULE_IS_BUNDLE=1") endfunction() From 41810f9994332c5cae76a19660e5e6567bc959bc Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Sat, 7 Mar 2020 13:42:11 +0100 Subject: [PATCH 76/93] Remove the unnecessary try-lock, processing the parameters --- vst/SfizzVstProcessor.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/vst/SfizzVstProcessor.cpp b/vst/SfizzVstProcessor.cpp index 580f113e..55877da3 100644 --- a/vst/SfizzVstProcessor.cpp +++ b/vst/SfizzVstProcessor.cpp @@ -119,10 +119,8 @@ tresult PLUGIN_API SfizzVstProcessor::process(Vst::ProcessData& data) { sfz::Sfizz& synth = *_synth; - if (Vst::IParameterChanges* pc = data.inputParameterChanges) { - std::unique_lock lock(_processMutex, std::try_to_lock); + if (Vst::IParameterChanges* pc = data.inputParameterChanges) processParameterChanges(*pc); - } if (data.numOutputs < 1) // flush mode return kResultTrue; From f88364b6b2bdb94d35a54bff1ae643b9621abf84 Mon Sep 17 00:00:00 2001 From: Paul Fd Date: Sat, 7 Mar 2020 22:36:47 +0100 Subject: [PATCH 77/93] The library protects itself on this call --- vst/SfizzVstProcessor.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/vst/SfizzVstProcessor.cpp b/vst/SfizzVstProcessor.cpp index 55877da3..ff2839fb 100644 --- a/vst/SfizzVstProcessor.cpp +++ b/vst/SfizzVstProcessor.cpp @@ -342,7 +342,6 @@ void SfizzVstProcessor::doBackgroundWork() if (!std::strcmp(id, "LoadSfz")) { std::vector path(maxPathLen + 1); if (attr->getString("File", path.data(), maxPathLen) == kResultTrue) { - std::lock_guard lock(_processMutex); _state.sfzFile = Steinberg::String(path.data()).text8(); _synth->loadSfzFile(_state.sfzFile); } From 65d952a1e61c76151ce44a98d1efb0005a3ee165 Mon Sep 17 00:00:00 2001 From: Paul Fd Date: Sat, 7 Mar 2020 23:05:11 +0100 Subject: [PATCH 78/93] Add the development and release flags for VST3 --- vst/CMakeLists.txt | 1 + vst/cmake/Vst3.cmake | 16 ++++++++++++++++ 2 files changed, 17 insertions(+) diff --git a/vst/CMakeLists.txt b/vst/CMakeLists.txt index a4e214b3..83c51154 100644 --- a/vst/CMakeLists.txt +++ b/vst/CMakeLists.txt @@ -42,6 +42,7 @@ add_library(${VSTPLUGIN_PRJ_NAME} MODULE SfizzVstState.cpp GUIComponents.cpp VstPluginFactory.cpp) + if(WIN32) target_sources(${VSTPLUGIN_PRJ_NAME} PRIVATE vst3.def) endif() diff --git a/vst/cmake/Vst3.cmake b/vst/cmake/Vst3.cmake index a8aa9074..544a34e6 100644 --- a/vst/cmake/Vst3.cmake +++ b/vst/cmake/Vst3.cmake @@ -50,6 +50,14 @@ function(plugin_add_vst3sdk NAME) target_compile_definitions("${NAME}" PRIVATE "_NATIVE_WCHAR_T_DEFINED=1" "__wchar_t=wchar_t") endif() + + if(${CMAKE_BUILD_TYPE} MATCHES "Debug") + target_compile_definitions("${NAME}" PRIVATE "DEVELOPMENT") + endif() + + if(${CMAKE_BUILD_TYPE} MATCHES "Release") + target_compile_definitions("${NAME}" PRIVATE "RELEASE") + endif() endfunction() # --- VSTGUI --- @@ -256,4 +264,12 @@ function(plugin_add_vstgui NAME) external/steinberg/src) target_compile_definitions("${NAME}" PRIVATE "SMTG_MODULE_IS_BUNDLE=1") + + if(${CMAKE_BUILD_TYPE} MATCHES "Debug") + target_compile_definitions("${NAME}" PRIVATE "DEVELOPMENT") + endif() + + if(${CMAKE_BUILD_TYPE} MATCHES "Release") + target_compile_definitions("${NAME}" PRIVATE "RELEASE") + endif() endfunction() From bce4fe96d047ae5f47cc6ee010805483ff2cbaf8 Mon Sep 17 00:00:00 2001 From: Paul Fd Date: Thu, 5 Mar 2020 01:38:04 +0100 Subject: [PATCH 79/93] Replace the underlying structure of CCMap with a smallish vector --- src/sfizz/CCMap.h | 43 ++++++++++++++++++++++--------------------- src/sfizz/Region.cpp | 2 +- 2 files changed, 23 insertions(+), 22 deletions(-) diff --git a/src/sfizz/CCMap.h b/src/sfizz/CCMap.h index 34bf5b7c..6da8da03 100644 --- a/src/sfizz/CCMap.h +++ b/src/sfizz/CCMap.h @@ -6,7 +6,8 @@ #pragma once #include "LeakDetector.h" -#include +#include +#include namespace sfz { /** @@ -42,7 +43,7 @@ public: */ const ValueType& getWithDefault(int index) const noexcept { - auto it = container.find(index); + auto it = absl::c_find_if(container, [&](auto&& pair){ return pair.first == index; }); if (it == container.end()) { return defaultValue; } else { @@ -51,16 +52,20 @@ public: } /** - * @brief Get the value at index key or emplace a new one if not present + * @brief Get the value at index or emplace a new one if not present * - * @param key the index of the element + * @param index the index of the element * @return ValueType& */ - ValueType& operator[](const int& key) noexcept + ValueType& operator[](const int& index) noexcept { - if (!contains(key)) - container.emplace(key, defaultValue); - return container.operator[](key); + auto it = absl::c_find_if(container, [&](auto&& pair){ return pair.first == index; }); + if (it == container.end()) { + container.emplace_back(index, defaultValue); + return container.back().second; + } else { + return it->second; + } } /** @@ -70,13 +75,6 @@ public: * @return false */ inline bool empty() const { return container.empty(); } - /** - * @brief Returns the value at index with bounds checking (and possibly exceptions) - * - * @param index - * @return const ValueType& - */ - const ValueType& at(int index) const { return container.at(index); } /** * @brief Returns true if the container containers an element at index * @@ -84,14 +82,17 @@ public: * @return true * @return false */ - bool contains(int index) const noexcept { return container.find(index) != container.end(); } - typename std::map::iterator begin() { return container.begin(); } - typename std::map::const_iterator begin() const { return container.cbegin(); } - typename std::map::iterator end() { return container.end(); } - typename std::map::const_iterator end() const { return container.cend(); } + bool contains(int index) const noexcept + { + return absl::c_find_if(container, [&](auto&& pair){ return pair.first == index; }) != container.end(); + } + typename std::vector>::iterator begin() { return container.begin(); } + typename std::vector>::const_iterator begin() const { return container.cbegin(); } + typename std::vector>::iterator end() { return container.end(); } + typename std::vector>::const_iterator end() const { return container.cend(); } private: const ValueType defaultValue; - std::map container; + std::vector> container; LEAK_DETECTOR(CCMap); }; } diff --git a/src/sfizz/Region.cpp b/src/sfizz/Region.cpp index 94d0218e..9e2c162e 100644 --- a/src/sfizz/Region.cpp +++ b/src/sfizz/Region.cpp @@ -854,7 +854,7 @@ bool sfz::Region::registerCC(int ccNumber, uint8_t ccValue) noexcept if (!triggerOnCC) return false; - if (ccTriggers.contains(ccNumber) && ccTriggers.at(ccNumber).containsWithEnd(ccValue)) + if (ccTriggers.contains(ccNumber) && ccTriggers[ccNumber].containsWithEnd(ccValue)) return true; else return false; From 8becafcae00a1eec6f5d18db2a5bdca9ab634ba5 Mon Sep 17 00:00:00 2001 From: Paul Fd Date: Thu, 5 Mar 2020 16:38:19 +0100 Subject: [PATCH 80/93] Insert sorted into the vector, and use only const iterators in CCMap --- src/sfizz/CCMap.h | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/sfizz/CCMap.h b/src/sfizz/CCMap.h index 6da8da03..c025f3e3 100644 --- a/src/sfizz/CCMap.h +++ b/src/sfizz/CCMap.h @@ -61,8 +61,9 @@ public: { auto it = absl::c_find_if(container, [&](auto&& pair){ return pair.first == index; }); if (it == container.end()) { - container.emplace_back(index, defaultValue); - return container.back().second; + auto newElement = std::make_pair(index, defaultValue); + auto inserted = container.insert(absl::c_upper_bound(container, newElement, [](auto& lhs, auto& rhs) { return lhs.first < rhs.first; }), newElement); + return inserted->second; } else { return it->second; } @@ -86,11 +87,11 @@ public: { return absl::c_find_if(container, [&](auto&& pair){ return pair.first == index; }) != container.end(); } - typename std::vector>::iterator begin() { return container.begin(); } typename std::vector>::const_iterator begin() const { return container.cbegin(); } - typename std::vector>::iterator end() { return container.end(); } typename std::vector>::const_iterator end() const { return container.cend(); } private: + // typename std::vector>::iterator begin() { return container.begin(); } + // typename std::vector>::iterator end() { return container.end(); } const ValueType defaultValue; std::vector> container; LEAK_DETECTOR(CCMap); From d10cd27ceb392b90c0c20ff61f7f4c1f6cf78c2c Mon Sep 17 00:00:00 2001 From: Paul Fd Date: Thu, 5 Mar 2020 16:38:31 +0100 Subject: [PATCH 81/93] Clean up the Range class --- src/sfizz/Range.h | 19 +------------------ 1 file changed, 1 insertion(+), 18 deletions(-) diff --git a/src/sfizz/Range.h b/src/sfizz/Range.h index acb88c66..35e8e1b4 100644 --- a/src/sfizz/Range.h +++ b/src/sfizz/Range.h @@ -22,27 +22,12 @@ class Range { public: constexpr Range() = default; - // constexpr Range(std::initializer_list list) - // { - // switch(list.size()) - // { - // case 0: - // break; - // case 1: - // _start = *list.begin(); - // _end = _start; - // break; - // default: - // _start = *list.begin(); - // _end = *(list.begin() + 1); - // } - // } constexpr Range(Type start, Type end) noexcept : _start(start) , _end(std::max(start, end)) { + } - ~Range() = default; Type getStart() const noexcept { return _start; } Type getEnd() const noexcept { return _end; } /** @@ -51,8 +36,6 @@ public: * @return std::pair */ std::pair getPair() const noexcept { return std::make_pair(_start, _end); } - Range(const Range& range) = default; - Range(Range&& range) = default; constexpr Type length() const { return _end - _start; } void setStart(Type start) noexcept { From f70b042da6c7f60101989352097a731566dc2675 Mon Sep 17 00:00:00 2001 From: Paul Fd Date: Sat, 7 Mar 2020 12:35:48 +0100 Subject: [PATCH 82/93] Change CCValuePair type alias and use it in CCMap Also use lower bound and binary search in the CCMap vector --- src/sfizz/CCMap.h | 27 ++++++++++++++------------- src/sfizz/EGDescription.h | 14 +++++++------- src/sfizz/MidiState.h | 2 +- src/sfizz/Opcode.h | 4 ++-- src/sfizz/Region.cpp | 8 ++++---- src/sfizz/Region.h | 10 +++++----- src/sfizz/SfzHelpers.h | 29 ++++++++++++++++++++++++++--- src/sfizz/Voice.cpp | 30 +++++++++++++++--------------- 8 files changed, 74 insertions(+), 50 deletions(-) diff --git a/src/sfizz/CCMap.h b/src/sfizz/CCMap.h index c025f3e3..a71ba2f4 100644 --- a/src/sfizz/CCMap.h +++ b/src/sfizz/CCMap.h @@ -6,6 +6,7 @@ #pragma once #include "LeakDetector.h" +#include "SfzHelpers.h" #include #include @@ -43,11 +44,11 @@ public: */ const ValueType& getWithDefault(int index) const noexcept { - auto it = absl::c_find_if(container, [&](auto&& pair){ return pair.first == index; }); - if (it == container.end()) { + auto it = absl::c_lower_bound(container, index, CompareCC{}); + if (it == container.end() || it->cc != index) { return defaultValue; } else { - return it->second; + return it->value; } } @@ -59,13 +60,12 @@ public: */ ValueType& operator[](const int& index) noexcept { - auto it = absl::c_find_if(container, [&](auto&& pair){ return pair.first == index; }); - if (it == container.end()) { - auto newElement = std::make_pair(index, defaultValue); - auto inserted = container.insert(absl::c_upper_bound(container, newElement, [](auto& lhs, auto& rhs) { return lhs.first < rhs.first; }), newElement); - return inserted->second; + auto it = absl::c_lower_bound(container, index, CompareCC{}); + if (it == container.end() || it->cc != index) { + auto inserted = container.insert(it, { index, defaultValue }); + return inserted->value; } else { - return it->second; + return it->value; } } @@ -85,15 +85,16 @@ public: */ bool contains(int index) const noexcept { - return absl::c_find_if(container, [&](auto&& pair){ return pair.first == index; }) != container.end(); + return absl::c_binary_search(container, index, CompareCC{}); } - typename std::vector>::const_iterator begin() const { return container.cbegin(); } - typename std::vector>::const_iterator end() const { return container.cend(); } + typename std::vector>::const_iterator begin() const { return container.cbegin(); } + typename std::vector>::const_iterator end() const { return container.cend(); } private: // typename std::vector>::iterator begin() { return container.begin(); } // typename std::vector>::iterator end() { return container.end(); } + const ValueType defaultValue; - std::vector> container; + std::vector> container; LEAK_DETECTOR(CCMap); }; } diff --git a/src/sfizz/EGDescription.h b/src/sfizz/EGDescription.h index cc655511..afaa32c8 100644 --- a/src/sfizz/EGDescription.h +++ b/src/sfizz/EGDescription.h @@ -64,13 +64,13 @@ struct EGDescription float vel2sustain { Default::vel2sustain }; int vel2depth { Default::depth }; - absl::optional ccAttack; - absl::optional ccDecay; - absl::optional ccDelay; - absl::optional ccHold; - absl::optional ccRelease; - absl::optional ccStart; - absl::optional ccSustain; + absl::optional> ccAttack; + absl::optional> ccDecay; + absl::optional> ccDelay; + absl::optional> ccHold; + absl::optional> ccRelease; + absl::optional> ccStart; + absl::optional> ccSustain; /** * @brief Get the attack with possibly a CC modifier and a velocity modifier diff --git a/src/sfizz/MidiState.h b/src/sfizz/MidiState.h index afba37c8..ad8238fd 100644 --- a/src/sfizz/MidiState.h +++ b/src/sfizz/MidiState.h @@ -120,7 +120,7 @@ public: T modulate(T value, const CCMap& modifiers, const Range& validRange, const modFunction& lambda = addToBase) const noexcept { for (auto& mod: modifiers) { - lambda(value, normalizeCC(getCCValue(mod.first)) * mod.second); + lambda(value, normalizeCC(getCCValue(mod.cc)) * mod.value); } return validRange.clamp(value); } diff --git a/src/sfizz/Opcode.h b/src/sfizz/Opcode.h index 227c3d28..2ac43209 100644 --- a/src/sfizz/Opcode.h +++ b/src/sfizz/Opcode.h @@ -184,11 +184,11 @@ inline void setRangeStartFromOpcode(const Opcode& opcode, Range& targ * @param validRange the range of admitted values used to clamp the opcode */ template -inline void setCCPairFromOpcode(const Opcode& opcode, absl::optional& target, const Range& validRange) +inline void setCCPairFromOpcode(const Opcode& opcode, absl::optional>& target, const Range& validRange) { auto value = readOpcode(opcode.value, validRange); if (value && Default::ccNumberRange.containsWithEnd(opcode.parameters.back())) - target = std::make_pair(opcode.parameters.back(), *value); + target = { opcode.parameters.back(), *value }; else target = {}; } diff --git a/src/sfizz/Region.cpp b/src/sfizz/Region.cpp index 9e2c162e..004b5fe9 100644 --- a/src/sfizz/Region.cpp +++ b/src/sfizz/Region.cpp @@ -992,14 +992,14 @@ float sfz::Region::getCrossfadeGain(const sfz::SfzCCArray& ccState) noexcept // Crossfades due to CC states for (const auto& valuePair : crossfadeCCInRange) { - const auto ccValue = ccState[valuePair.first]; - const auto crossfadeRange = valuePair.second; + const auto ccValue = ccState[valuePair.cc]; + const auto crossfadeRange = valuePair.value; gain *= crossfadeIn(crossfadeRange, ccValue, crossfadeCCCurve); } for (const auto& valuePair : crossfadeCCOutRange) { - const auto ccValue = ccState[valuePair.first]; - const auto crossfadeRange = valuePair.second; + const auto ccValue = ccState[valuePair.cc]; + const auto crossfadeRange = valuePair.value; gain *= crossfadeOut(crossfadeRange, ccValue, crossfadeCCCurve); } diff --git a/src/sfizz/Region.h b/src/sfizz/Region.h index cf08657b..fa5ea786 100644 --- a/src/sfizz/Region.h +++ b/src/sfizz/Region.h @@ -264,11 +264,11 @@ struct Region { float pan { Default::pan }; // pan float width { Default::width }; // width float position { Default::position }; // position - absl::optional volumeCC; // volume_oncc - absl::optional amplitudeCC; // amplitude_oncc - absl::optional panCC; // pan_oncc - absl::optional widthCC; // width_oncc - absl::optional positionCC; // position_oncc + absl::optional> volumeCC; // volume_oncc + absl::optional> amplitudeCC; // amplitude_oncc + absl::optional> panCC; // pan_oncc + absl::optional> widthCC; // width_oncc + absl::optional> positionCC; // position_oncc uint8_t ampKeycenter { Default::ampKeycenter }; // amp_keycenter float ampKeytrack { Default::ampKeytrack }; // amp_keytrack float ampVeltrack { Default::ampVeltrack }; // amp_keytrack diff --git a/src/sfizz/SfzHelpers.h b/src/sfizz/SfzHelpers.h index 1e590d1b..eb05e6bc 100644 --- a/src/sfizz/SfzHelpers.h +++ b/src/sfizz/SfzHelpers.h @@ -16,9 +16,32 @@ namespace sfz { using SfzCCArray = std::array; -using CCValuePair = std::pair ; using CCNamePair = std::pair; +template +struct CCValuePair { + int cc; + ValueType value; +}; + +template +struct CompareCC { + bool operator()(const CCValuePair& valuePair, const int& cc) + { + return (valuePair.cc < cc); + } + + bool operator()(const int& cc, const CCValuePair& valuePair) + { + return (cc < valuePair.cc); + } + + bool operator()(const CCValuePair& lhs, const CCValuePair& rhs) + { + return (lhs.cc < rhs.cc); + } +}; + /** * @brief Converts cents to a pitch ratio * @@ -94,10 +117,10 @@ constexpr float normalizeBend(float bendValue) * @param value * @return float */ -inline float ccSwitchedValue(const SfzCCArray& ccValues, const absl::optional& ccSwitch, float value) noexcept +inline float ccSwitchedValue(const SfzCCArray& ccValues, const absl::optional>& ccSwitch, float value) noexcept { if (ccSwitch) - return value + ccSwitch->second * normalizeCC(ccValues[ccSwitch->first]); + return value + ccSwitch->value * normalizeCC(ccValues[ccSwitch->cc]); else return value; } diff --git a/src/sfizz/Voice.cpp b/src/sfizz/Voice.cpp index 64bac0f2..df3e3c10 100644 --- a/src/sfizz/Voice.cpp +++ b/src/sfizz/Voice.cpp @@ -47,7 +47,7 @@ void sfz::Voice::startVoice(Region* region, int delay, int number, uint8_t value baseVolumedB = region->getBaseVolumedB(number); auto volumedB { baseVolumedB }; if (region->volumeCC) - volumedB += normalizeCC(resources.midiState.getCCValue(region->volumeCC->first)) * region->volumeCC->second; + volumedB += normalizeCC(resources.midiState.getCCValue(region->volumeCC->cc)) * region->volumeCC->value; volumeEnvelope.reset(db2mag(Default::volumeRange.clamp(volumedB))); baseGain = region->getBaseGain(); @@ -56,7 +56,7 @@ void sfz::Voice::startVoice(Region* region, int delay, int number, uint8_t value float gain { baseGain }; if (region->amplitudeCC) - gain += normalizeCC(resources.midiState.getCCValue(region->amplitudeCC->first)) * normalizePercents(region->amplitudeCC->second); + gain += normalizeCC(resources.midiState.getCCValue(region->amplitudeCC->cc)) * normalizePercents(region->amplitudeCC->value); amplitudeEnvelope.reset(Default::normalizedRange.clamp(gain)); float crossfadeGain { region->getCrossfadeGain(resources.midiState.getCCArray()) }; @@ -65,19 +65,19 @@ void sfz::Voice::startVoice(Region* region, int delay, int number, uint8_t value basePan = normalizePercents(region->pan); auto pan { basePan }; if (region->panCC) - pan += normalizeCC(resources.midiState.getCCValue(region->panCC->first)) * normalizePercents(region->panCC->second); + pan += normalizeCC(resources.midiState.getCCValue(region->panCC->cc)) * normalizePercents(region->panCC->value); panEnvelope.reset(Default::symmetricNormalizedRange.clamp(pan)); basePosition = normalizePercents(region->position); auto position { basePosition }; if (region->positionCC) - position += normalizeCC(resources.midiState.getCCValue(region->positionCC->first)) * normalizePercents(region->positionCC->second); + position += normalizeCC(resources.midiState.getCCValue(region->positionCC->cc)) * normalizePercents(region->positionCC->value); positionEnvelope.reset(Default::symmetricNormalizedRange.clamp(position)); baseWidth = normalizePercents(region->width); auto width { baseWidth }; if (region->widthCC) - width += normalizeCC(resources.midiState.getCCValue(region->widthCC->first)) * normalizePercents(region->widthCC->second); + width += normalizeCC(resources.midiState.getCCValue(region->widthCC->cc)) * normalizePercents(region->widthCC->value); widthEnvelope.reset(Default::symmetricNormalizedRange.clamp(width)); pitchBendEnvelope.setFunction([region](float pitchValue){ @@ -168,28 +168,28 @@ void sfz::Voice::registerCC(int delay, int ccNumber, uint8_t ccValue) noexcept // TODO: this feels like a hack, revisit this along with the smoothed envelopes... delay = max(delay, minEnvelopeDelay); - if (region->amplitudeCC && ccNumber == region->amplitudeCC->first) { - const float newGain { baseGain + normalizeCC(ccValue) * normalizePercents(region->amplitudeCC->second) }; + if (region->amplitudeCC && ccNumber == region->amplitudeCC->cc) { + const float newGain { baseGain + normalizeCC(ccValue) * normalizePercents(region->amplitudeCC->value) }; amplitudeEnvelope.registerEvent(delay, Default::normalizedRange.clamp(newGain)); } - if (region->volumeCC && ccNumber == region->volumeCC->first) { - const float newVolumedB { baseVolumedB + normalizeCC(ccValue) * region->volumeCC->second }; + if (region->volumeCC && ccNumber == region->volumeCC->cc) { + const float newVolumedB { baseVolumedB + normalizeCC(ccValue) * region->volumeCC->value }; volumeEnvelope.registerEvent(delay, db2mag(Default::volumeRange.clamp(newVolumedB))); } - if (region->panCC && ccNumber == region->panCC->first) { - const float newPan { basePan + normalizeCC(ccValue) * normalizePercents(region->panCC->second) }; + if (region->panCC && ccNumber == region->panCC->cc) { + const float newPan { basePan + normalizeCC(ccValue) * normalizePercents(region->panCC->value) }; panEnvelope.registerEvent(delay, Default::symmetricNormalizedRange.clamp(newPan)); } - if (region->positionCC && ccNumber == region->positionCC->first) { - const float newPosition { basePosition + normalizeCC(ccValue) * normalizePercents(region->positionCC->second) }; + if (region->positionCC && ccNumber == region->positionCC->cc) { + const float newPosition { basePosition + normalizeCC(ccValue) * normalizePercents(region->positionCC->value) }; positionEnvelope.registerEvent(delay, Default::symmetricNormalizedRange.clamp(newPosition)); } - if (region->widthCC && ccNumber == region->widthCC->first) { - const float newWidth { baseWidth + normalizeCC(ccValue) * normalizePercents(region->widthCC->second) }; + if (region->widthCC && ccNumber == region->widthCC->cc) { + const float newWidth { baseWidth + normalizeCC(ccValue) * normalizePercents(region->widthCC->value) }; widthEnvelope.registerEvent(delay, Default::symmetricNormalizedRange.clamp(newWidth)); } From 4398f3bab469a4e68e530b766d73e224d265d614 Mon Sep 17 00:00:00 2001 From: Paul Fd Date: Sat, 7 Mar 2020 12:36:01 +0100 Subject: [PATCH 83/93] Update the tests for the new CCValuePair alias --- tests/FilesT.cpp | 4 +-- tests/RegionT.cpp | 68 +++++++++++++++++++++++------------------------ 2 files changed, 36 insertions(+), 36 deletions(-) diff --git a/tests/FilesT.cpp b/tests/FilesT.cpp index c8cd6c03..b397d154 100644 --- a/tests/FilesT.cpp +++ b/tests/FilesT.cpp @@ -306,8 +306,8 @@ TEST_CASE("[Files] wrong (overlapping) replacement for defines") REQUIRE( synth.getRegionView(1)->keyRange.getStart() == 57 ); REQUIRE( synth.getRegionView(1)->keyRange.getEnd() == 57 ); REQUIRE( synth.getRegionView(2)->amplitudeCC ); - REQUIRE( synth.getRegionView(2)->amplitudeCC->first == 10 ); - REQUIRE( synth.getRegionView(2)->amplitudeCC->second == 34.0f ); + REQUIRE( synth.getRegionView(2)->amplitudeCC->cc == 10 ); + REQUIRE( synth.getRegionView(2)->amplitudeCC->value == 34.0f ); } TEST_CASE("[Files] Specific bug: relative path with backslashes") diff --git a/tests/RegionT.cpp b/tests/RegionT.cpp index 71063e74..65bf133f 100644 --- a/tests/RegionT.cpp +++ b/tests/RegionT.cpp @@ -464,8 +464,8 @@ TEST_CASE("[Region] Parsing opcodes") REQUIRE(!region.panCC); region.parseOpcode({ "pan_oncc45", "4.2" }); REQUIRE(region.panCC); - REQUIRE(region.panCC->first == 45); - REQUIRE(region.panCC->second == 4.2f); + REQUIRE(region.panCC->cc == 45); + REQUIRE(region.panCC->value == 4.2f); } SECTION("width") @@ -486,8 +486,8 @@ TEST_CASE("[Region] Parsing opcodes") REQUIRE(!region.widthCC); region.parseOpcode({ "width_oncc45", "4.2" }); REQUIRE(region.widthCC); - REQUIRE(region.widthCC->first == 45); - REQUIRE(region.widthCC->second == 4.2f); + REQUIRE(region.widthCC->cc == 45); + REQUIRE(region.widthCC->value == 4.2f); } SECTION("position") @@ -508,8 +508,8 @@ TEST_CASE("[Region] Parsing opcodes") REQUIRE(!region.positionCC); region.parseOpcode({ "position_oncc45", "4.2" }); REQUIRE(region.positionCC); - REQUIRE(region.positionCC->first == 45); - REQUIRE(region.positionCC->second == 4.2f); + REQUIRE(region.positionCC->cc == 45); + REQUIRE(region.positionCC->value == 4.2f); } SECTION("amp_keycenter") @@ -964,20 +964,20 @@ TEST_CASE("[Region] Parsing opcodes") REQUIRE(region.amplitudeEG.ccRelease); REQUIRE(region.amplitudeEG.ccStart); REQUIRE(region.amplitudeEG.ccSustain); - REQUIRE(region.amplitudeEG.ccAttack->first == 1); - REQUIRE(region.amplitudeEG.ccDecay->first == 2); - REQUIRE(region.amplitudeEG.ccDelay->first == 3); - REQUIRE(region.amplitudeEG.ccHold->first == 4); - REQUIRE(region.amplitudeEG.ccRelease->first == 5); - REQUIRE(region.amplitudeEG.ccStart->first == 6); - REQUIRE(region.amplitudeEG.ccSustain->first == 7); - REQUIRE(region.amplitudeEG.ccAttack->second == 1.0f); - REQUIRE(region.amplitudeEG.ccDecay->second == 2.0f); - REQUIRE(region.amplitudeEG.ccDelay->second == 3.0f); - REQUIRE(region.amplitudeEG.ccHold->second == 4.0f); - REQUIRE(region.amplitudeEG.ccRelease->second == 5.0f); - REQUIRE(region.amplitudeEG.ccStart->second == 6.0f); - REQUIRE(region.amplitudeEG.ccSustain->second == 7.0f); + REQUIRE(region.amplitudeEG.ccAttack->cc == 1); + REQUIRE(region.amplitudeEG.ccDecay->cc == 2); + REQUIRE(region.amplitudeEG.ccDelay->cc == 3); + REQUIRE(region.amplitudeEG.ccHold->cc == 4); + REQUIRE(region.amplitudeEG.ccRelease->cc == 5); + REQUIRE(region.amplitudeEG.ccStart->cc == 6); + REQUIRE(region.amplitudeEG.ccSustain->cc == 7); + REQUIRE(region.amplitudeEG.ccAttack->value == 1.0f); + REQUIRE(region.amplitudeEG.ccDecay->value == 2.0f); + REQUIRE(region.amplitudeEG.ccDelay->value == 3.0f); + REQUIRE(region.amplitudeEG.ccHold->value == 4.0f); + REQUIRE(region.amplitudeEG.ccRelease->value == 5.0f); + REQUIRE(region.amplitudeEG.ccStart->value == 6.0f); + REQUIRE(region.amplitudeEG.ccSustain->value == 7.0f); // region.parseOpcode({ "ampeg_attack_oncc1", "101" }); region.parseOpcode({ "ampeg_decay_oncc2", "101" }); @@ -986,13 +986,13 @@ TEST_CASE("[Region] Parsing opcodes") region.parseOpcode({ "ampeg_release_oncc5", "101" }); region.parseOpcode({ "ampeg_start_oncc6", "101" }); region.parseOpcode({ "ampeg_sustain_oncc7", "101" }); - REQUIRE(region.amplitudeEG.ccAttack->second == 100.0f); - REQUIRE(region.amplitudeEG.ccDecay->second == 100.0f); - REQUIRE(region.amplitudeEG.ccDelay->second == 100.0f); - REQUIRE(region.amplitudeEG.ccHold->second == 100.0f); - REQUIRE(region.amplitudeEG.ccRelease->second == 100.0f); - REQUIRE(region.amplitudeEG.ccStart->second == 100.0f); - REQUIRE(region.amplitudeEG.ccSustain->second == 100.0f); + REQUIRE(region.amplitudeEG.ccAttack->value == 100.0f); + REQUIRE(region.amplitudeEG.ccDecay->value == 100.0f); + REQUIRE(region.amplitudeEG.ccDelay->value == 100.0f); + REQUIRE(region.amplitudeEG.ccHold->value == 100.0f); + REQUIRE(region.amplitudeEG.ccRelease->value == 100.0f); + REQUIRE(region.amplitudeEG.ccStart->value == 100.0f); + REQUIRE(region.amplitudeEG.ccSustain->value == 100.0f); // region.parseOpcode({ "ampeg_attack_oncc1", "-101" }); region.parseOpcode({ "ampeg_decay_oncc2", "-101" }); @@ -1001,13 +1001,13 @@ TEST_CASE("[Region] Parsing opcodes") region.parseOpcode({ "ampeg_release_oncc5", "-101" }); region.parseOpcode({ "ampeg_start_oncc6", "-101" }); region.parseOpcode({ "ampeg_sustain_oncc7", "-101" }); - REQUIRE(region.amplitudeEG.ccAttack->second == -100.0f); - REQUIRE(region.amplitudeEG.ccDecay->second == -100.0f); - REQUIRE(region.amplitudeEG.ccDelay->second == -100.0f); - REQUIRE(region.amplitudeEG.ccHold->second == -100.0f); - REQUIRE(region.amplitudeEG.ccRelease->second == -100.0f); - REQUIRE(region.amplitudeEG.ccStart->second == -100.0f); - REQUIRE(region.amplitudeEG.ccSustain->second == -100.0f); + REQUIRE(region.amplitudeEG.ccAttack->value == -100.0f); + REQUIRE(region.amplitudeEG.ccDecay->value == -100.0f); + REQUIRE(region.amplitudeEG.ccDelay->value == -100.0f); + REQUIRE(region.amplitudeEG.ccHold->value == -100.0f); + REQUIRE(region.amplitudeEG.ccRelease->value == -100.0f); + REQUIRE(region.amplitudeEG.ccStart->value == -100.0f); + REQUIRE(region.amplitudeEG.ccSustain->value == -100.0f); } SECTION("sustain_sw and sostenuto_sw") From 3046c2393f519a698b35c3faea6eb10c23164f6b Mon Sep 17 00:00:00 2001 From: Paul Fd Date: Sat, 7 Mar 2020 12:43:20 +0100 Subject: [PATCH 84/93] Added a template specialization to compare on value and not CC --- src/sfizz/CCMap.h | 6 +++--- src/sfizz/SfzHelpers.h | 22 ++++++++++++++++++++-- 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/src/sfizz/CCMap.h b/src/sfizz/CCMap.h index a71ba2f4..09e8830c 100644 --- a/src/sfizz/CCMap.h +++ b/src/sfizz/CCMap.h @@ -44,7 +44,7 @@ public: */ const ValueType& getWithDefault(int index) const noexcept { - auto it = absl::c_lower_bound(container, index, CompareCC{}); + auto it = absl::c_lower_bound(container, index, CCValuePairComparator{}); if (it == container.end() || it->cc != index) { return defaultValue; } else { @@ -60,7 +60,7 @@ public: */ ValueType& operator[](const int& index) noexcept { - auto it = absl::c_lower_bound(container, index, CompareCC{}); + auto it = absl::c_lower_bound(container, index, CCValuePairComparator{}); if (it == container.end() || it->cc != index) { auto inserted = container.insert(it, { index, defaultValue }); return inserted->value; @@ -85,7 +85,7 @@ public: */ bool contains(int index) const noexcept { - return absl::c_binary_search(container, index, CompareCC{}); + return absl::c_binary_search(container, index, CCValuePairComparator{}); } typename std::vector>::const_iterator begin() const { return container.cbegin(); } typename std::vector>::const_iterator end() const { return container.cend(); } diff --git a/src/sfizz/SfzHelpers.h b/src/sfizz/SfzHelpers.h index eb05e6bc..4a69ba95 100644 --- a/src/sfizz/SfzHelpers.h +++ b/src/sfizz/SfzHelpers.h @@ -24,8 +24,8 @@ struct CCValuePair { ValueType value; }; -template -struct CompareCC { +template +struct CCValuePairComparator { bool operator()(const CCValuePair& valuePair, const int& cc) { return (valuePair.cc < cc); @@ -42,6 +42,24 @@ struct CompareCC { } }; +template +struct CCValuePairComparator { + bool operator()(const CCValuePair& valuePair, const ValueType& value) + { + return (valuePair.value < value); + } + + bool operator()(const ValueType& value, const CCValuePair& valuePair) + { + return (value < valuePair.value); + } + + bool operator()(const CCValuePair& lhs, const CCValuePair& rhs) + { + return (lhs.value < rhs.value); + } +}; + /** * @brief Converts cents to a pitch ratio * From 2e020cb21317fab1da06c0c333b5c7c520b018b5 Mon Sep 17 00:00:00 2001 From: Paul Fd Date: Sat, 7 Mar 2020 14:05:05 +0100 Subject: [PATCH 85/93] Added a benchmark for maps --- benchmarks/BM_maps.cpp | 350 ++++++++++++++++++++++++++++++++++++++ benchmarks/CMakeLists.txt | 2 + 2 files changed, 352 insertions(+) create mode 100644 benchmarks/BM_maps.cpp diff --git a/benchmarks/BM_maps.cpp b/benchmarks/BM_maps.cpp new file mode 100644 index 00000000..88edd380 --- /dev/null +++ b/benchmarks/BM_maps.cpp @@ -0,0 +1,350 @@ +// SPDX-License-Identifier: BSD-2-Clause + +// This code is part of the sfizz library and is licensed under a BSD 2-clause +// license. You should have receive a LICENSE.md file along with the code. +// If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz + +#include +#include +#include +#include +#include "../src/sfizz/Range.h" +#include +#include + +constexpr int maxCC { 256 }; + +class MyFixture : public benchmark::Fixture { +public: + void SetUp(const ::benchmark::State& state) + { + std::random_device rd {}; + std::mt19937 gen { rd() }; + std::uniform_real_distribution distFloat { 0.1f, 1.0f }; + std::uniform_int_distribution distInt { 1, maxCC }; + floats = std::vector(state.range(0)); + ccs = std::vector(state.range(0)); + ranges = std::vector>(state.range(0)); + absl::c_generate(floats, [&]() { + return distFloat(gen); + }); + absl::c_generate(ccs, [&]() { + return distInt(gen); + }); + absl::c_generate(ranges, [&]() { + return sfz::Range(distInt(gen), distInt(gen)); + }); + } + + void TearDown(const ::benchmark::State& state [[maybe_unused]]) + { + } + + std::vector ccs; + std::vector> ranges; + std::vector floats; +}; + +template +struct CCValuePair { + int cc; + ValueType value; +}; + +template +struct CCValuePairComparator { + bool operator()(const CCValuePair& valuePair, const int& cc) + { + return (valuePair.cc < cc); + } + + bool operator()(const int& cc, const CCValuePair& valuePair) + { + return (cc < valuePair.cc); + } + + bool operator()(const CCValuePair& lhs, const CCValuePair& rhs) + { + return (lhs.cc < rhs.cc); + } +}; + +template +struct CCValuePairComparator { + bool operator()(const CCValuePair& valuePair, const ValueType& value) + { + return (valuePair.value < value); + } + + bool operator()(const ValueType& value, const CCValuePair& valuePair) + { + return (value < valuePair.value); + } + + bool operator()(const CCValuePair& lhs, const CCValuePair& rhs) + { + return (lhs.value < rhs.value); + } +}; + +template +class CCMap { +public: + CCMap() = delete; + /** + * @brief Construct a new CCMap object with the specified default value. + * + * @param defaultValue + */ + CCMap(const ValueType& defaultValue) + : defaultValue(defaultValue) + { + } + CCMap(CCMap&&) = default; + CCMap(const CCMap&) = default; + ~CCMap() = default; + + /** + * @brief Returns the held object at the index, or a default value if not present + * + * @param index + * @return const ValueType& + */ + const ValueType& getWithDefault(int index) const noexcept + { + auto it = absl::c_lower_bound(container, index, CCValuePairComparator{}); + if (it == container.end() || it->cc != index) { + return defaultValue; + } else { + return it->value; + } + } + + /** + * @brief Get the value at index or emplace a new one if not present + * + * @param index the index of the element + * @return ValueType& + */ + ValueType& operator[](const int& index) noexcept + { + auto it = absl::c_lower_bound(container, index, CCValuePairComparator{}); + if (it == container.end() || it->cc != index) { + auto inserted = container.insert(it, { index, defaultValue }); + return inserted->value; + } else { + return it->value; + } + } + + /** + * @brief Is the container empty + * + * @return true + * @return false + */ + inline bool empty() const { return container.empty(); } + /** + * @brief Returns true if the container containers an element at index + * + * @param index + * @return true + * @return false + */ + bool contains(int index) const noexcept + { + return absl::c_binary_search(container, index, CCValuePairComparator{}); + } + typename std::vector>::const_iterator begin() const { return container.cbegin(); } + typename std::vector>::const_iterator end() const { return container.cend(); } +private: + // typename std::vector>::iterator begin() { return container.begin(); } + // typename std::vector>::iterator end() { return container.end(); } + + const ValueType defaultValue; + std::vector> container; +}; + +BENCHMARK_DEFINE_F(MyFixture, FillVector_Float) +(benchmark::State& state) +{ + for (auto _ : state) { + CCMap map { 0 }; + for (int i = 0; i < state.range(0); ++i) + map[ccs[i]] = floats[i]; + } +} + +BENCHMARK_DEFINE_F(MyFixture, FillVector_Range) +(benchmark::State& state) +{ + for (auto _ : state) { + CCMap> map { sfz::Range(0, 127) }; + for (int i = 0; i < state.range(0); ++i) + map[ccs[i]] = ranges[i]; + } +} + +BENCHMARK_DEFINE_F(MyFixture, FillAbseilFlatHM_Float) +(benchmark::State& state) +{ + for (auto _ : state) { + absl::flat_hash_map map; + for (int i = 0; i < state.range(0); ++i) + map[ccs[i]] = floats[i]; + } +} + +BENCHMARK_DEFINE_F(MyFixture, FillAbseilFlatHM_Range) +(benchmark::State& state) +{ + for (auto _ : state) { + absl::flat_hash_map> map; + for (int i = 0; i < state.range(0); ++i) + map[ccs[i]] = ranges[i]; + } +} + +BENCHMARK_DEFINE_F(MyFixture, LookupBaseline_Float) +(benchmark::State& state) +{ + std::vector output; + output.resize(state.range(0)); + + std::vector map; + output.reserve(state.range(0)); + for (int i = 0; i < state.range(0); ++i) + map.push_back(floats[i]); + + for (auto _ : state) { + for (int i = 0; i < state.range(0); ++i) + output[i] = map[i]; + } +} + +BENCHMARK_DEFINE_F(MyFixture, LookupBaseline_Range) +(benchmark::State& state) +{ + std::vector> output; + output.resize(state.range(0)); + + std::vector> map; + output.reserve(state.range(0)); + for (int i = 0; i < state.range(0); ++i) + map.push_back(ranges[i]); + + for (auto _ : state) { + for (int i = 0; i < state.range(0); ++i) + output[i] = map[i]; + } +} + +BENCHMARK_DEFINE_F(MyFixture, LookupVector_Float) +(benchmark::State& state) +{ + std::vector output; + output.resize(state.range(0)); + + CCMap map { 0 }; + for (int i = 0; i < state.range(0); ++i) + map[ccs[i]] = floats[i]; + + for (auto _ : state) { + for (int i = 0; i < state.range(0); ++i) + output[i] = map[ccs[i]]; + } +} + +BENCHMARK_DEFINE_F(MyFixture, LookupVector_Range) +(benchmark::State& state) +{ + std::vector> output; + output.resize(state.range(0)); + + CCMap> map { sfz::Range(0, 127) }; + for (int i = 0; i < state.range(0); ++i) + map[ccs[i]] = ranges[i]; + + for (auto _ : state) { + for (int i = 0; i < state.range(0); ++i) + output[i] = map[ccs[i]]; + } +} + +BENCHMARK_DEFINE_F(MyFixture, LookupAbseilFlatHM_Float) +(benchmark::State& state) +{ + std::vector output; + output.resize(state.range(0)); + + absl::flat_hash_map map; + for (int i = 0; i < state.range(0); ++i) + map[ccs[i]] = floats[i]; + for (auto _ : state) { + for (int i = 0; i < state.range(0); ++i) + output[i] = map[ccs[i]]; + } +} + +BENCHMARK_DEFINE_F(MyFixture, LookupAbseilFlatHM_Range) +(benchmark::State& state) +{ + std::vector> output; + output.resize(state.range(0)); + + absl::flat_hash_map> map; + for (int i = 0; i < state.range(0); ++i) + map[ccs[i]] = ranges[i]; + + for (auto _ : state) { + for (int i = 0; i < state.range(0); ++i) + output[i] = map[ccs[i]]; + } +} + + +BENCHMARK_DEFINE_F(MyFixture, IterateVector_Float) +(benchmark::State& state) +{ + std::vector output; + output.reserve(maxCC); + + CCMap map { 0 }; + for (int i = 0; i < state.range(0); ++i) + map[ccs[i]] = floats[i]; + + for (auto _ : state) { + for (auto& pair: map) + output.push_back(pair.value); + } +} + +BENCHMARK_DEFINE_F(MyFixture, IterateAbseilFlatHM_Float) +(benchmark::State& state) +{ + std::vector output; + output.reserve(maxCC); + + absl::flat_hash_map map; + for (int i = 0; i < state.range(0); ++i) + map[ccs[i]] = floats[i]; + for (auto _ : state) { + for (auto& pair: map) + output.push_back(pair.second); + } +} + + +BENCHMARK_REGISTER_F(MyFixture, FillVector_Float)->RangeMultiplier(2)->Range(16, 512); +// BENCHMARK_REGISTER_F(MyFixture, FillVector_Range)->RangeMultiplier(2)->Range(16, 512); +BENCHMARK_REGISTER_F(MyFixture, FillAbseilFlatHM_Float)->RangeMultiplier(2)->Range(16, 512); +// BENCHMARK_REGISTER_F(MyFixture, FillAbseilFlatHM_Range)->RangeMultiplier(2)->Range(16, 512); +BENCHMARK_REGISTER_F(MyFixture, LookupBaseline_Float)->RangeMultiplier(2)->Range(16, 512); +// BENCHMARK_REGISTER_F(MyFixture, LookupBaseline_Range)->RangeMultiplier(2)->Range(16, 512); +BENCHMARK_REGISTER_F(MyFixture, LookupVector_Float)->RangeMultiplier(2)->Range(16, 512); +// BENCHMARK_REGISTER_F(MyFixture, LookupVector_Range)->RangeMultiplier(2)->Range(16, 512); +BENCHMARK_REGISTER_F(MyFixture, LookupAbseilFlatHM_Float)->RangeMultiplier(2)->Range(16, 512); +// BENCHMARK_REGISTER_F(MyFixture, LookupAbseilFlatHM_Range)->RangeMultiplier(2)->Range(16, 512); +BENCHMARK_REGISTER_F(MyFixture, IterateVector_Float)->Range(maxCC, maxCC); +BENCHMARK_REGISTER_F(MyFixture, IterateAbseilFlatHM_Float)->Range(maxCC, maxCC); +BENCHMARK_MAIN(); diff --git a/benchmarks/CMakeLists.txt b/benchmarks/CMakeLists.txt index b143ac5a..8931eca6 100644 --- a/benchmarks/CMakeLists.txt +++ b/benchmarks/CMakeLists.txt @@ -58,6 +58,8 @@ sfizz_add_benchmark(bm_diff BM_diff.cpp) sfizz_add_benchmark(bm_widthPos BM_widthPos.cpp) sfizz_add_benchmark(bm_interpolationCast BM_interpolationCast.cpp) sfizz_add_benchmark(bm_pointerIterationOrOffsets BM_pointerIterationOrOffsets.cpp) +sfizz_add_benchmark(bm_maps BM_maps.cpp) +target_link_libraries(bm_maps PRIVATE absl::flat_hash_map) sfizz_add_benchmark(bm_logger BM_logger.cpp) target_link_libraries(bm_logger PRIVATE sfizz::sfizz) From 57eadc7a93814c0d348b60ecaf8a9a0f44a19671 Mon Sep 17 00:00:00 2001 From: Paul Ferrand Date: Sat, 7 Mar 2020 14:18:47 +0100 Subject: [PATCH 86/93] Global definition of LIBATOMIC_FOUND for arm benchmarks --- benchmarks/CMakeLists.txt | 5 ++++- cmake/SfizzConfig.cmake | 5 +++++ src/CMakeLists.txt | 8 ++------ 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/benchmarks/CMakeLists.txt b/benchmarks/CMakeLists.txt index 8931eca6..0aa090ae 100644 --- a/benchmarks/CMakeLists.txt +++ b/benchmarks/CMakeLists.txt @@ -27,7 +27,10 @@ macro(sfizz_add_benchmark TARGET) target_link_libraries("${TARGET}" PRIVATE absl::span absl::algorithm PRIVATE benchmark::benchmark benchmark::benchmark_main - PRIVATE bm_simd bm_ftz) + PRIVATE bm_simd bm_ftz) + if (LIBATOMIC_FOUND) + target_link_libraries ("${TARGET}" PRIVATE atomic) + endif() target_include_directories("${TARGET}" PRIVATE ../src/sfizz ../src/external) endmacro() diff --git a/cmake/SfizzConfig.cmake b/cmake/SfizzConfig.cmake index 19215e3a..0f5fc728 100644 --- a/cmake/SfizzConfig.cmake +++ b/cmake/SfizzConfig.cmake @@ -66,6 +66,11 @@ if (CMAKE_CXX_COMPILER_ID MATCHES "Clang" AND NOT ANDROID) endif() endif() +include (CheckLibraryExists) +if (UNIX AND NOT APPLE) + check_library_exists(atomic __atomic_load "" LIBATOMIC_FOUND) +endif() + # Don't show build information when building a different project function (show_build_info_if_needed) if (CMAKE_PROJECT_NAME STREQUAL "sfizz") diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index fcaeca79..2475fd65 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,5 +1,4 @@ include (GNUInstallDirs) -include (CheckLibraryExists) set (SFIZZ_SOURCES sfizz/Synth.cpp @@ -55,11 +54,8 @@ endif() add_library (sfizz::parser ALIAS sfizz_parser) add_library (sfizz::sfizz ALIAS sfizz_static) -if (UNIX AND NOT APPLE) - check_library_exists(atomic __atomic_load "" LIBATOMIC_FOUND) - if (LIBATOMIC_FOUND) - target_link_libraries (sfizz_static PRIVATE atomic) - endif() +if (LIBATOMIC_FOUND) + target_link_libraries (sfizz_static PRIVATE atomic) endif() # Shared library and installation target From d94cc35c5350e6733f4da6d7946a52448f53c49b Mon Sep 17 00:00:00 2001 From: Paul Fd Date: Wed, 4 Mar 2020 23:22:29 +0100 Subject: [PATCH 87/93] Trickle the delay to the MidiState --- src/sfizz/MidiState.cpp | 14 +++++++------- src/sfizz/MidiState.h | 12 ++++++------ src/sfizz/Synth.cpp | 16 +++++++--------- 3 files changed, 20 insertions(+), 22 deletions(-) diff --git a/src/sfizz/MidiState.cpp b/src/sfizz/MidiState.cpp index 4614f185..d128c6e3 100644 --- a/src/sfizz/MidiState.cpp +++ b/src/sfizz/MidiState.cpp @@ -9,10 +9,10 @@ sfz::MidiState::MidiState() { - reset(); + reset(0); } -void sfz::MidiState::noteOnEvent(int noteNumber, uint8_t velocity) noexcept +void sfz::MidiState::noteOnEvent(int delay, int noteNumber, uint8_t velocity) noexcept { ASSERT(noteNumber >= 0 && noteNumber <= 127); ASSERT(velocity >= 0 && velocity <= 127); @@ -25,7 +25,7 @@ void sfz::MidiState::noteOnEvent(int noteNumber, uint8_t velocity) noexcept } -void sfz::MidiState::noteOffEvent(int noteNumber, uint8_t velocity [[maybe_unused]]) noexcept +void sfz::MidiState::noteOffEvent(int delay, int noteNumber, uint8_t velocity [[maybe_unused]]) noexcept { ASSERT(noteNumber >= 0 && noteNumber <= 127); ASSERT(velocity >= 0 && velocity <= 127); @@ -57,7 +57,7 @@ uint8_t sfz::MidiState::getNoteVelocity(int noteNumber) const noexcept return lastNoteVelocities[noteNumber]; } -void sfz::MidiState::pitchBendEvent(int pitchBendValue) noexcept +void sfz::MidiState::pitchBendEvent(int delay, int pitchBendValue) noexcept { ASSERT(pitchBendValue >= -8192 && pitchBendValue <= 8192); @@ -69,7 +69,7 @@ int sfz::MidiState::getPitchBend() const noexcept return pitchBend; } -void sfz::MidiState::ccEvent(int ccNumber, uint8_t ccValue) noexcept +void sfz::MidiState::ccEvent(int delay, int ccNumber, uint8_t ccValue) noexcept { ASSERT(ccNumber >= 0 && ccNumber < config::numCCs); ASSERT(ccValue >= 0 && ccValue <= 127); @@ -89,7 +89,7 @@ const sfz::SfzCCArray& sfz::MidiState::getCCArray() const noexcept return cc; } -void sfz::MidiState::reset() noexcept +void sfz::MidiState::reset(int delay) noexcept { for (auto& velocity: lastNoteVelocities) velocity = 0; @@ -101,7 +101,7 @@ void sfz::MidiState::reset() noexcept activeNotes = 0; } -void sfz::MidiState::resetAllControllers() noexcept +void sfz::MidiState::resetAllControllers(int delay) noexcept { for (int idx = 0; idx < config::numCCs; idx++) cc[idx] = 0; diff --git a/src/sfizz/MidiState.h b/src/sfizz/MidiState.h index ad8238fd..b629c88a 100644 --- a/src/sfizz/MidiState.h +++ b/src/sfizz/MidiState.h @@ -29,7 +29,7 @@ public: * @param noteNumber * @param velocity */ - void noteOnEvent(int noteNumber, uint8_t velocity) noexcept; + void noteOnEvent(int delay, int noteNumber, uint8_t velocity) noexcept; /** * @brief Update the state after a note off event @@ -37,7 +37,7 @@ public: * @param noteNumber * @param velocity */ - void noteOffEvent(int noteNumber, uint8_t velocity) noexcept; + void noteOffEvent(int delay, int noteNumber, uint8_t velocity) noexcept; int getActiveNotes() const noexcept { return activeNotes; } @@ -62,7 +62,7 @@ public: * * @param pitchBendValue */ - void pitchBendEvent(int pitchBendValue) noexcept; + void pitchBendEvent(int delay, int pitchBendValue) noexcept; /** * @brief Get the pitch bend status @@ -77,7 +77,7 @@ public: * @param ccNumber * @param ccValue */ - void ccEvent(int ccNumber, uint8_t ccValue) noexcept; + void ccEvent(int delay, int ccNumber, uint8_t ccValue) noexcept; /** * @brief Get the CC value for CC number @@ -98,12 +98,12 @@ public: * @brief Reset the midi state (does not impact the last note on time) * */ - void reset() noexcept; + void reset(int delay) noexcept; /** * @brief Reset all the controllers */ - void resetAllControllers() noexcept; + void resetAllControllers(int delay) noexcept; /** * @brief Modulate a value using the last entered CCs in the midiState diff --git a/src/sfizz/Synth.cpp b/src/sfizz/Synth.cpp index deb58a8c..100aef69 100644 --- a/src/sfizz/Synth.cpp +++ b/src/sfizz/Synth.cpp @@ -135,7 +135,7 @@ void sfz::Synth::clear() fileTicket = -1; defaultSwitch = absl::nullopt; defaultPath = ""; - resources.midiState.reset(); + resources.midiState.reset(0); ccNames.clear(); globalOpcodes.clear(); masterOpcodes.clear(); @@ -167,7 +167,7 @@ void sfz::Synth::handleControlOpcodes(const std::vector& members) case hash("set_cc&"): if (Default::ccNumberRange.containsWithEnd(member.parameters.back())) { const auto ccValue = readOpcode(member.value, Default::ccValueRange).value_or(0); - resources.midiState.ccEvent(member.parameters.back(), ccValue); + resources.midiState.ccEvent(0, member.parameters.back(), ccValue); } break; case hash("Label_cc&"): [[fallthrough]]; @@ -579,7 +579,7 @@ void sfz::Synth::noteOn(int delay, int noteNumber, uint8_t velocity) noexcept ASSERT(noteNumber >= 0); ScopedTiming logger { dispatchDuration, ScopedTiming::Operation::addToDuration }; - resources.midiState.noteOnEvent(noteNumber, velocity); + resources.midiState.noteOnEvent(delay, noteNumber, velocity); AtomicGuard callbackGuard { inCallback }; if (!canEnterCallback) @@ -594,7 +594,7 @@ void sfz::Synth::noteOff(int delay, int noteNumber, uint8_t velocity [[maybe_unu ASSERT(noteNumber >= 0); ScopedTiming logger { dispatchDuration, ScopedTiming::Operation::addToDuration }; - resources.midiState.noteOffEvent(noteNumber, velocity); + resources.midiState.noteOffEvent(delay, noteNumber, velocity); AtomicGuard callbackGuard { inCallback }; if (!canEnterCallback) @@ -650,8 +650,7 @@ void sfz::Synth::cc(int delay, int ccNumber, uint8_t ccValue) noexcept ASSERT(ccNumber >= 0); ScopedTiming logger { dispatchDuration, ScopedTiming::Operation::addToDuration }; - - resources.midiState.ccEvent(ccNumber, ccValue); + resources.midiState.ccEvent(delay, ccNumber, ccValue); AtomicGuard callbackGuard { inCallback }; if (!canEnterCallback) @@ -682,8 +681,7 @@ void sfz::Synth::pitchWheel(int delay, int pitch) noexcept ASSERT(pitch >= -8192); ScopedTiming logger { dispatchDuration, ScopedTiming::Operation::addToDuration }; - - resources.midiState.pitchBendEvent(pitch); + resources.midiState.pitchBendEvent(delay, pitch); for (auto& region: regions) { region->registerPitchWheel(pitch); @@ -924,7 +922,7 @@ void sfz::Synth::resetAllControllers(int delay) noexcept if (!canEnterCallback) return; - resources.midiState.resetAllControllers(); + resources.midiState.resetAllControllers(delay); for (auto& voice: voices) { voice->registerPitchWheel(delay, 0); for (int cc = 0; cc < config::numCCs; ++cc) From 459656e3cc1fffaa331a6b5472fa84d7dcea3d3e Mon Sep 17 00:00:00 2001 From: Paul Fd Date: Wed, 4 Mar 2020 23:26:42 +0100 Subject: [PATCH 88/93] Temporary test update --- tests/MidiStateT.cpp | 22 +++++------ tests/RegionTriggersT.cpp | 20 +++++----- tests/RegionValueComputationsT.cpp | 62 +++++++++++++++--------------- 3 files changed, 52 insertions(+), 52 deletions(-) diff --git a/tests/MidiStateT.cpp b/tests/MidiStateT.cpp index 4a7a161d..d06f2215 100644 --- a/tests/MidiStateT.cpp +++ b/tests/MidiStateT.cpp @@ -27,8 +27,8 @@ TEST_CASE("[MidiState] Set and get CCs") { sfz::MidiState state; const auto& cc = state.getCCArray(); - state.ccEvent(24, 23); - state.ccEvent(123, 124); + state.ccEvent(0, 24, 23); + state.ccEvent(0, 123, 124); REQUIRE(state.getCCValue(24) == 23); REQUIRE(cc[24] == 23); REQUIRE(state.getCCValue(123) == 124); @@ -38,19 +38,19 @@ TEST_CASE("[MidiState] Set and get CCs") TEST_CASE("[MidiState] Set and get pitch bends") { sfz::MidiState state; - state.pitchBendEvent(894); + state.pitchBendEvent(0, 894); REQUIRE(state.getPitchBend() == 894); - state.pitchBendEvent(0); + state.pitchBendEvent(0, 0); REQUIRE(state.getPitchBend() == 0); } TEST_CASE("[MidiState] Reset") { sfz::MidiState state; - state.pitchBendEvent(894); - state.noteOnEvent(64, 24); - state.ccEvent(123, 124); - state.reset(); + state.pitchBendEvent(0, 894); + state.noteOnEvent(0, 64, 24); + state.ccEvent(0, 123, 124); + state.reset(0); REQUIRE(state.getPitchBend() == 0); REQUIRE(state.getNoteVelocity(64) == 0); REQUIRE(state.getCCValue(123) == 0); @@ -59,9 +59,9 @@ TEST_CASE("[MidiState] Reset") TEST_CASE("[MidiState] Set and get note velocities") { sfz::MidiState state; - state.noteOnEvent(64, 24); + state.noteOnEvent(0, 64, 24); REQUIRE(+state.getNoteVelocity(64) == 24); - state.noteOnEvent(64, 123); + state.noteOnEvent(0, 64, 123); REQUIRE(+state.getNoteVelocity(64) == 123); } @@ -69,5 +69,5 @@ TEST_CASE("[MidiState] Extended CCs") { sfz::MidiState state; REQUIRE(state.getCCArray().size() >= 142); - state.ccEvent(142, 64); // should not trap + state.ccEvent(0, 142, 64); // should not trap } diff --git a/tests/RegionTriggersT.cpp b/tests/RegionTriggersT.cpp index c2a116c8..fe7f4569 100644 --- a/tests/RegionTriggersT.cpp +++ b/tests/RegionTriggersT.cpp @@ -135,15 +135,15 @@ TEST_CASE("Legato triggers", "Region triggers") region.parseOpcode({ "lokey", "40" }); region.parseOpcode({ "hikey", "50" }); region.parseOpcode({ "trigger", "first" }); - midiState.noteOnEvent(40, 64); + midiState.noteOnEvent(0, 40, 64); REQUIRE(region.registerNoteOn(40, 64, 0.5f)); - midiState.noteOnEvent(41, 64); + midiState.noteOnEvent(0, 41, 64); REQUIRE(!region.registerNoteOn(41, 64, 0.5f)); - midiState.noteOffEvent(40, 0); + midiState.noteOffEvent(0, 40, 0); region.registerNoteOff(40, 0, 0.5f); - midiState.noteOffEvent(41, 0); + midiState.noteOffEvent(0, 41, 0); region.registerNoteOff(41, 0, 0.5f); - midiState.noteOnEvent(42, 64); + midiState.noteOnEvent(0, 42, 64); REQUIRE(region.registerNoteOn(42, 64, 0.5f)); } @@ -152,15 +152,15 @@ TEST_CASE("Legato triggers", "Region triggers") region.parseOpcode({ "lokey", "40" }); region.parseOpcode({ "hikey", "50" }); region.parseOpcode({ "trigger", "legato" }); - midiState.noteOnEvent(40, 64); + midiState.noteOnEvent(0, 40, 64); REQUIRE(!region.registerNoteOn(40, 64, 0.5f)); - midiState.noteOnEvent(41, 64); + midiState.noteOnEvent(0, 41, 64); REQUIRE(region.registerNoteOn(41, 64, 0.5f)); - midiState.noteOffEvent(40, 64); + midiState.noteOffEvent(0, 40, 64); region.registerNoteOff(40, 0, 0.5f); - midiState.noteOffEvent(41, 64); + midiState.noteOffEvent(0, 41, 64); region.registerNoteOff(41, 0, 0.5f); - midiState.noteOnEvent(42, 64); + midiState.noteOnEvent(0, 42, 64); REQUIRE(!region.registerNoteOn(42, 64, 0.5f)); } } diff --git a/tests/RegionValueComputationsT.cpp b/tests/RegionValueComputationsT.cpp index d07da9cc..eae54b0b 100644 --- a/tests/RegionValueComputationsT.cpp +++ b/tests/RegionValueComputationsT.cpp @@ -166,13 +166,13 @@ TEST_CASE("[Region] Crossfade in on CC") region.parseOpcode({ "xfin_locc24", "20" }); region.parseOpcode({ "xfin_hicc24", "24" }); region.parseOpcode({ "amp_veltrack", "0" }); - midiState.ccEvent(24, 19); REQUIRE( region.getCrossfadeGain(midiState.getCCArray()) == 0.0_a ); - midiState.ccEvent(24, 20); REQUIRE( region.getCrossfadeGain(midiState.getCCArray()) == 0.0_a ); - midiState.ccEvent(24, 21); REQUIRE( region.getCrossfadeGain(midiState.getCCArray()) == 0.5_a ); - midiState.ccEvent(24, 22); REQUIRE( region.getCrossfadeGain(midiState.getCCArray()) == 0.70711_a ); - midiState.ccEvent(24, 23); REQUIRE( region.getCrossfadeGain(midiState.getCCArray()) == 0.86603_a ); - midiState.ccEvent(24, 24); REQUIRE( region.getCrossfadeGain(midiState.getCCArray()) == 1.0_a ); - midiState.ccEvent(24, 25); REQUIRE( region.getCrossfadeGain(midiState.getCCArray()) == 1.0_a ); + midiState.ccEvent(0, 24, 19); REQUIRE( region.getCrossfadeGain(midiState.getCCArray()) == 0.0_a ); + midiState.ccEvent(0, 24, 20); REQUIRE( region.getCrossfadeGain(midiState.getCCArray()) == 0.0_a ); + midiState.ccEvent(0, 24, 21); REQUIRE( region.getCrossfadeGain(midiState.getCCArray()) == 0.5_a ); + midiState.ccEvent(0, 24, 22); REQUIRE( region.getCrossfadeGain(midiState.getCCArray()) == 0.70711_a ); + midiState.ccEvent(0, 24, 23); REQUIRE( region.getCrossfadeGain(midiState.getCCArray()) == 0.86603_a ); + midiState.ccEvent(0, 24, 24); REQUIRE( region.getCrossfadeGain(midiState.getCCArray()) == 1.0_a ); + midiState.ccEvent(0, 24, 25); REQUIRE( region.getCrossfadeGain(midiState.getCCArray()) == 1.0_a ); } TEST_CASE("[Region] Crossfade in on CC - gain") @@ -184,13 +184,13 @@ TEST_CASE("[Region] Crossfade in on CC - gain") region.parseOpcode({ "xfin_hicc24", "24" }); region.parseOpcode({ "amp_veltrack", "0" }); region.parseOpcode({ "xf_cccurve", "gain" }); - midiState.ccEvent(24, 19); REQUIRE( region.getCrossfadeGain(midiState.getCCArray()) == 0.0_a ); - midiState.ccEvent(24, 20); REQUIRE( region.getCrossfadeGain(midiState.getCCArray()) == 0.0_a ); - midiState.ccEvent(24, 21); REQUIRE( region.getCrossfadeGain(midiState.getCCArray()) == 0.25_a ); - midiState.ccEvent(24, 22); REQUIRE( region.getCrossfadeGain(midiState.getCCArray()) == 0.5_a ); - midiState.ccEvent(24, 23); REQUIRE( region.getCrossfadeGain(midiState.getCCArray()) == 0.75_a ); - midiState.ccEvent(24, 24); REQUIRE( region.getCrossfadeGain(midiState.getCCArray()) == 1.0_a ); - midiState.ccEvent(24, 25); REQUIRE( region.getCrossfadeGain(midiState.getCCArray()) == 1.0_a ); + midiState.ccEvent(0, 24, 19); REQUIRE( region.getCrossfadeGain(midiState.getCCArray()) == 0.0_a ); + midiState.ccEvent(0, 24, 20); REQUIRE( region.getCrossfadeGain(midiState.getCCArray()) == 0.0_a ); + midiState.ccEvent(0, 24, 21); REQUIRE( region.getCrossfadeGain(midiState.getCCArray()) == 0.25_a ); + midiState.ccEvent(0, 24, 22); REQUIRE( region.getCrossfadeGain(midiState.getCCArray()) == 0.5_a ); + midiState.ccEvent(0, 24, 23); REQUIRE( region.getCrossfadeGain(midiState.getCCArray()) == 0.75_a ); + midiState.ccEvent(0, 24, 24); REQUIRE( region.getCrossfadeGain(midiState.getCCArray()) == 1.0_a ); + midiState.ccEvent(0, 24, 25); REQUIRE( region.getCrossfadeGain(midiState.getCCArray()) == 1.0_a ); } TEST_CASE("[Region] Crossfade out on CC") { @@ -200,13 +200,13 @@ TEST_CASE("[Region] Crossfade out on CC") region.parseOpcode({ "xfout_locc24", "20" }); region.parseOpcode({ "xfout_hicc24", "24" }); region.parseOpcode({ "amp_veltrack", "0" }); - midiState.ccEvent(24, 19); REQUIRE( region.getCrossfadeGain(midiState.getCCArray()) == 1.0_a ); - midiState.ccEvent(24, 20); REQUIRE( region.getCrossfadeGain(midiState.getCCArray()) == 1.0_a ); - midiState.ccEvent(24, 21); REQUIRE( region.getCrossfadeGain(midiState.getCCArray()) == 0.86603_a ); - midiState.ccEvent(24, 22); REQUIRE( region.getCrossfadeGain(midiState.getCCArray()) == 0.70711_a ); - midiState.ccEvent(24, 23); REQUIRE( region.getCrossfadeGain(midiState.getCCArray()) == 0.5_a ); - midiState.ccEvent(24, 24); REQUIRE( region.getCrossfadeGain(midiState.getCCArray()) == 0.0_a ); - midiState.ccEvent(24, 25); REQUIRE( region.getCrossfadeGain(midiState.getCCArray()) == 0.0_a ); + midiState.ccEvent(0, 24, 19); REQUIRE( region.getCrossfadeGain(midiState.getCCArray()) == 1.0_a ); + midiState.ccEvent(0, 24, 20); REQUIRE( region.getCrossfadeGain(midiState.getCCArray()) == 1.0_a ); + midiState.ccEvent(0, 24, 21); REQUIRE( region.getCrossfadeGain(midiState.getCCArray()) == 0.86603_a ); + midiState.ccEvent(0, 24, 22); REQUIRE( region.getCrossfadeGain(midiState.getCCArray()) == 0.70711_a ); + midiState.ccEvent(0, 24, 23); REQUIRE( region.getCrossfadeGain(midiState.getCCArray()) == 0.5_a ); + midiState.ccEvent(0, 24, 24); REQUIRE( region.getCrossfadeGain(midiState.getCCArray()) == 0.0_a ); + midiState.ccEvent(0, 24, 25); REQUIRE( region.getCrossfadeGain(midiState.getCCArray()) == 0.0_a ); } TEST_CASE("[Region] Crossfade out on CC - gain") @@ -218,13 +218,13 @@ TEST_CASE("[Region] Crossfade out on CC - gain") region.parseOpcode({ "xfout_hicc24", "24" }); region.parseOpcode({ "amp_veltrack", "0" }); region.parseOpcode({ "xf_cccurve", "gain" }); - midiState.ccEvent(24, 19); REQUIRE( region.getCrossfadeGain(midiState.getCCArray()) == 1.0_a ); - midiState.ccEvent(24, 20); REQUIRE( region.getCrossfadeGain(midiState.getCCArray()) == 1.0_a ); - midiState.ccEvent(24, 21); REQUIRE( region.getCrossfadeGain(midiState.getCCArray()) == 0.75_a ); - midiState.ccEvent(24, 22); REQUIRE( region.getCrossfadeGain(midiState.getCCArray()) == 0.5_a ); - midiState.ccEvent(24, 23); REQUIRE( region.getCrossfadeGain(midiState.getCCArray()) == 0.25_a ); - midiState.ccEvent(24, 24); REQUIRE( region.getCrossfadeGain(midiState.getCCArray()) == 0.0_a ); - midiState.ccEvent(24, 25); REQUIRE( region.getCrossfadeGain(midiState.getCCArray()) == 0.0_a ); + midiState.ccEvent(0, 24, 19); REQUIRE( region.getCrossfadeGain(midiState.getCCArray()) == 1.0_a ); + midiState.ccEvent(0, 24, 20); REQUIRE( region.getCrossfadeGain(midiState.getCCArray()) == 1.0_a ); + midiState.ccEvent(0, 24, 21); REQUIRE( region.getCrossfadeGain(midiState.getCCArray()) == 0.75_a ); + midiState.ccEvent(0, 24, 22); REQUIRE( region.getCrossfadeGain(midiState.getCCArray()) == 0.5_a ); + midiState.ccEvent(0, 24, 23); REQUIRE( region.getCrossfadeGain(midiState.getCCArray()) == 0.25_a ); + midiState.ccEvent(0, 24, 24); REQUIRE( region.getCrossfadeGain(midiState.getCCArray()) == 0.0_a ); + midiState.ccEvent(0, 24, 25); REQUIRE( region.getCrossfadeGain(midiState.getCCArray()) == 0.0_a ); } TEST_CASE("[Region] Velocity bug for extreme values - veltrack at 0") @@ -265,15 +265,15 @@ TEST_CASE("[Region] rt_decay") region.parseOpcode({ "sample", "*sine" }); region.parseOpcode({ "trigger", "release" }); region.parseOpcode({ "rt_decay", "10" }); - midiState.noteOnEvent(64, 64); + midiState.noteOnEvent(0, 64, 64); std::this_thread::sleep_for(std::chrono::milliseconds(100)); REQUIRE( region.getBaseVolumedB(64) == Approx(sfz::Default::volume - 1.0f).margin(0.1) ); region.parseOpcode({ "rt_decay", "20" }); - midiState.noteOnEvent(64, 64); + midiState.noteOnEvent(0, 64, 64); std::this_thread::sleep_for(std::chrono::milliseconds(100)); REQUIRE( region.getBaseVolumedB(64) == Approx(sfz::Default::volume - 2.0f).margin(0.1) ); region.parseOpcode({ "trigger", "attack" }); - midiState.noteOnEvent(64, 64); + midiState.noteOnEvent(0, 64, 64); std::this_thread::sleep_for(std::chrono::milliseconds(100)); REQUIRE( region.getBaseVolumedB(64) == Approx(sfz::Default::volume).margin(0.1) ); } From 4efdcd0ca558aaf1bc9121d73d939b90477c7f6e Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Sun, 8 Mar 2020 11:22:30 +0100 Subject: [PATCH 89/93] Revert the removal of VST mutex lock, for now --- vst/SfizzVstProcessor.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/vst/SfizzVstProcessor.cpp b/vst/SfizzVstProcessor.cpp index ff2839fb..55877da3 100644 --- a/vst/SfizzVstProcessor.cpp +++ b/vst/SfizzVstProcessor.cpp @@ -342,6 +342,7 @@ void SfizzVstProcessor::doBackgroundWork() if (!std::strcmp(id, "LoadSfz")) { std::vector path(maxPathLen + 1); if (attr->getString("File", path.data(), maxPathLen) == kResultTrue) { + std::lock_guard lock(_processMutex); _state.sfzFile = Steinberg::String(path.data()).text8(); _synth->loadSfzFile(_state.sfzFile); } From d1b252f78e69d1bc13c8e7e8e104d91ff3658d77 Mon Sep 17 00:00:00 2001 From: redtide Date: Sun, 8 Mar 2020 15:29:08 +0100 Subject: [PATCH 90/93] Generate versioned Doxyfile --- .gitignore | 2 +- Doxyfile => scripts/Doxyfile.in | 4 ++-- src/CMakeLists.txt | 2 ++ 3 files changed, 5 insertions(+), 3 deletions(-) rename Doxyfile => scripts/Doxyfile.in (99%) diff --git a/.gitignore b/.gitignore index d232a9b0..cc8d2bc2 100644 --- a/.gitignore +++ b/.gitignore @@ -13,7 +13,7 @@ compile_commands.json *.a *.txt.user *.autosave - +/Doxyfile .DS_Store clients/sfizz_jack diff --git a/Doxyfile b/scripts/Doxyfile.in similarity index 99% rename from Doxyfile rename to scripts/Doxyfile.in index 1de7cecd..36cd35d5 100644 --- a/Doxyfile +++ b/scripts/Doxyfile.in @@ -38,7 +38,7 @@ PROJECT_NAME = sfizz # could be handy for archiving the generated documentation or if some version # control system is used. -PROJECT_NUMBER = 0.1.0 +PROJECT_NUMBER = @PROJECT_VERSION@ # Using the PROJECT_BRIEF tag one can provide an optional one line description # for a project that appears at the top of each page and should give viewer a @@ -1105,7 +1105,7 @@ GENERATE_HTML = YES # The default directory is: html. # This tag requires that the tag GENERATE_HTML is set to YES. -HTML_OUTPUT = _api +HTML_OUTPUT = api/@PROJECT_VERSION@ # The HTML_FILE_EXTENSION tag can be used to specify the file extension for each # generated HTML page (for example: .htm, .php, .asp). diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 2475fd65..bcc7f6e5 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -52,6 +52,8 @@ if(WIN32) configure_file (${PROJECT_SOURCE_DIR}/scripts/innosetup.iss.in ${PROJECT_BINARY_DIR}/innosetup.iss @ONLY) endif() +configure_file (${PROJECT_SOURCE_DIR}/scripts/Doxyfile.in ${PROJECT_SOURCE_DIR}/Doxyfile @ONLY) + add_library (sfizz::parser ALIAS sfizz_parser) add_library (sfizz::sfizz ALIAS sfizz_static) if (LIBATOMIC_FOUND) From 4ac7b9c2c5e2bc65852a93347201fae1402e2fe1 Mon Sep 17 00:00:00 2001 From: redtide Date: Sun, 8 Mar 2020 15:49:14 +0100 Subject: [PATCH 91/93] Restore previous Doxygen output directory --- scripts/Doxyfile.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/Doxyfile.in b/scripts/Doxyfile.in index 36cd35d5..53dc1502 100644 --- a/scripts/Doxyfile.in +++ b/scripts/Doxyfile.in @@ -1105,7 +1105,7 @@ GENERATE_HTML = YES # The default directory is: html. # This tag requires that the tag GENERATE_HTML is set to YES. -HTML_OUTPUT = api/@PROJECT_VERSION@ +HTML_OUTPUT = _api # The HTML_FILE_EXTENSION tag can be used to specify the file extension for each # generated HTML page (for example: .htm, .php, .asp). From 18529e21f854d7165c6c3a3155ff0ca4ff0b501c Mon Sep 17 00:00:00 2001 From: Paul Fd Date: Sun, 8 Mar 2020 16:30:47 +0100 Subject: [PATCH 92/93] Added stages to travis and moved the doxygen in the Deploy stage with travis conditions --- .travis.yml | 49 +++++++++++++++++++++++++++++-------------- .travis/update_dox.sh | 6 +----- 2 files changed, 34 insertions(+), 21 deletions(-) diff --git a/.travis.yml b/.travis.yml index 9f31aedc..03462119 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,20 +1,21 @@ language: cpp jobs: - include: - os: linux + stage: "Build" + name: "Windows mingw32" env: - CROSS_COMPILE=mingw32 - CONTAINER=cross - INSTALL_DIR="sfizz-${TRAVIS_BRANCH}-mingw32" - - os: linux + name: "Windows mingw64" env: - CROSS_COMPILE=mingw64 - CONTAINER=cross - INSTALL_DIR="sfizz-${TRAVIS_BRANCH}-mingw64" - - os: linux + name: "Linux amd64 library" arch: amd64 dist: bionic env: @@ -23,11 +24,8 @@ jobs: apt: sources: - sourceline: 'ppa:ubuntu-toolchain-r/test' - - packages: - - doxygen - - os: linux + name: "Linux arm64 library" arch: arm64 dist: bionic env: @@ -36,8 +34,8 @@ jobs: apt: sources: - sourceline: 'ppa:ubuntu-toolchain-r/test' - - os: linux + name: "Linux arm64 static LV2" arch: arm64 dist: bionic env: @@ -47,9 +45,9 @@ jobs: apt: sources: - sourceline: 'ppa:ubuntu-toolchain-r/test' - - os: linux - arch: amd64 + name: "Linux amd64 static LV2" + name: "sfizz-linux-amd64-lv2" dist: bionic env: - BUILD_TYPE=lv2 @@ -58,13 +56,15 @@ jobs: apt: sources: - sourceline: 'ppa:ubuntu-toolchain-r/test' - - os: osx + name: "macOS" osx_image: xcode10.1 env: - INSTALL_DIR="sfizz-${TRAVIS_BRANCH}-${TRAVIS_OS_NAME}-${TRAVIS_CPU_ARCH}" - os: linux + stage: "Deploy" + name: "Source packaging" env: - BUILD_TYPE=source - INSTALL_DIR="sfizz-${TRAVIS_BRANCH}-src" @@ -72,19 +72,37 @@ jobs: apt: packages: - python-pip - before_install: - true - install: - sudo pip install git-archive-all - script: - git-archive-all --prefix="sfizz-${TRAVIS_BRANCH}/" -9 "${INSTALL_DIR}.tar.gz" - after_failure: - true + after_success: + - true + - os: linux + name: "Generate documentation" + dist: bionic + # Change before integrating... + # if: (tag IS present) AND (branch = master) + if: (tag IS present) AND (branch = master) + addons: + apt: + packages: + - doxygen + - cmake + - libsndfile-dev + before_install: + - true + install: + - true + script: + - .travis/update_dox.sh + after_failure: + - true after_success: - true @@ -102,7 +120,6 @@ after_failure: after_success: - bash ${TRAVIS_BUILD_DIR}/.travis/discord_webhook.sh success -- bash ${TRAVIS_BUILD_DIR}/.travis/update_dox.sh - bash ${TRAVIS_BUILD_DIR}/.travis/after_success.sh deploy: diff --git a/.travis/update_dox.sh b/.travis/update_dox.sh index 8b74e4e8..e838dc05 100755 --- a/.travis/update_dox.sh +++ b/.travis/update_dox.sh @@ -3,11 +3,7 @@ set -x # No fail, we need to go back to the original branch at the end . .travis/environment.sh -# Build documentation only from Linux x86_64 builds -if [[ ${TRAVIS_CPU_ARCH} != "amd64" || ${TRAVIS_OS_NAME} != "linux" || "${CROSS_COMPILE}" != "" || ${TRAVIS_TAG} == "" ]]; then - exit 0 -fi - +mkdir build && cd build && cmake -DSFIZZ_JACK=OFF -DSFIZZ_SHARED=OFF -DSFIZZ_LV2=OFF .. && cd .. doxygen Doxyfile git fetch --depth=1 https://github.com/${TRAVIS_REPO_SLUG}.git refs/heads/gh-pages:refs/remotes/origin/gh-pages git checkout origin/gh-pages From 76ec234891ba862f42f12fe9b4cd56ecdc235157 Mon Sep 17 00:00:00 2001 From: Paul Fd Date: Sun, 8 Mar 2020 16:44:02 +0100 Subject: [PATCH 93/93] Added the webhook in the last stage and removed useless things --- .travis.yml | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index 03462119..47a733c5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -47,7 +47,6 @@ jobs: - sourceline: 'ppa:ubuntu-toolchain-r/test' - os: linux name: "Linux amd64 static LV2" - name: "sfizz-linux-amd64-lv2" dist: bionic env: - BUILD_TYPE=lv2 @@ -86,8 +85,6 @@ jobs: - os: linux name: "Generate documentation" dist: bionic - # Change before integrating... - # if: (tag IS present) AND (branch = master) if: (tag IS present) AND (branch = master) addons: apt: @@ -105,6 +102,17 @@ jobs: - true after_success: - true + - os: linux + name: "Discord Webhook" + dist: bionic + before_install: + - true + install: + - true + script: + - true + after_success: + - bash ${TRAVIS_BUILD_DIR}/.travis/discord_webhook.sh success before_install: - bash ${TRAVIS_BUILD_DIR}/.travis/before_install.sh @@ -119,7 +127,6 @@ after_failure: - bash ${TRAVIS_BUILD_DIR}/.travis/discord_webhook.sh failure after_success: -- bash ${TRAVIS_BUILD_DIR}/.travis/discord_webhook.sh success - bash ${TRAVIS_BUILD_DIR}/.travis/after_success.sh deploy: