From e154c135fc52966e70f205958b8d91300e855b61 Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Mon, 15 Feb 2021 04:56:10 +0100 Subject: [PATCH 1/9] Add the filter designer from hiir --- cmake/SfizzDeps.cmake | 6 + .../hiir/hiir/PolyphaseIir2Designer.cpp | 444 ++++++++++++++++++ .../hiir/hiir/PolyphaseIir2Designer.h | 143 ++++++ 3 files changed, 593 insertions(+) create mode 100644 src/external/hiir/hiir/PolyphaseIir2Designer.cpp create mode 100644 src/external/hiir/hiir/PolyphaseIir2Designer.h diff --git a/cmake/SfizzDeps.cmake b/cmake/SfizzDeps.cmake index 864f1225..464d6186 100644 --- a/cmake/SfizzDeps.cmake +++ b/cmake/SfizzDeps.cmake @@ -105,6 +105,12 @@ add_library(sfizz_hiir INTERFACE) add_library(sfizz::hiir ALIAS sfizz_hiir) target_include_directories(sfizz_hiir INTERFACE "src/external/hiir") +# The hiir filter designer +add_library(sfizz_hiir_polyphase_iir2designer STATIC + "src/external/hiir/hiir/PolyphaseIir2Designer.cpp") +add_library(sfizz::hiir_polyphase_iir2designer ALIAS sfizz_hiir_polyphase_iir2designer) +target_link_libraries(sfizz_hiir_polyphase_iir2designer PUBLIC sfizz::hiir) + # The kissfft library add_library(sfizz_kissfft STATIC "src/external/kiss_fft/kiss_fft.c" diff --git a/src/external/hiir/hiir/PolyphaseIir2Designer.cpp b/src/external/hiir/hiir/PolyphaseIir2Designer.cpp new file mode 100644 index 00000000..d36883d6 --- /dev/null +++ b/src/external/hiir/hiir/PolyphaseIir2Designer.cpp @@ -0,0 +1,444 @@ +/***************************************************************************** + + PolyphaseIir2Designer.cpp + 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 (_MSC_VER) + #pragma warning (1 : 4130) // "'operator' : logical operation on address of string constant" + #pragma warning (1 : 4223) // "nonstandard extension used : non-lvalue array converted to pointer" + #pragma warning (1 : 4705) // "statement has no effect" + #pragma warning (1 : 4706) // "assignment within conditional expression" + #pragma warning (4 : 4786) // "identifier was truncated to '255' characters in the debug information" + #pragma warning (4 : 4800) // "forcing value to bool 'true' or 'false' (performance warning)" + #pragma warning (4 : 4355) // "'this' : used in base member initializer list" +#endif + + + +/*\\\ INCLUDE FILES \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/ + +#include "hiir/def.h" +#include "hiir/fnc.h" +#include "hiir/PolyphaseIir2Designer.h" + +#include +#include + + + +namespace hiir +{ + + + +/*\\\ PUBLIC \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/ + + + +/* +============================================================================== +Name: compute_nbr_coefs_from_proto +Description: + Finds the minimum number of coefficients for a given filter specification +Input parameters: + - attenuation: stopband attenuation, dB. > 0. + - transition: normalized transition bandwith. Range ]0 ; 1/2[ +Returns: Number of coefficients, > 0 +Throws: Nothing +============================================================================== +*/ + +int PolyphaseIir2Designer::compute_nbr_coefs_from_proto (double attenuation, double transition) +{ + assert (attenuation > 0); + assert (transition > 0); + assert (transition < 0.5); + + double k; + double q; + compute_transition_param (k, q, transition); + const int order = compute_order (attenuation, q); + const int nbr_coefs = (order - 1) / 2; + + return nbr_coefs; +} + + + +/* +============================================================================== +Name: compute_atten_from_order_tbw +Description: + Compute the attenuation correspounding to a given number of coefficients + and the transition bandwith. +Input parameters: + - nbr_coefs: Number of desired coefficients. > 0. + - transition: normalized transition bandwith. Range ]0 ; 1/2[ +Returns: stopband attenuation, dB. > 0. +Throws: Nothing +============================================================================== +*/ + +double PolyphaseIir2Designer::compute_atten_from_order_tbw (int nbr_coefs, double transition) +{ + assert (nbr_coefs > 0); + assert (transition > 0); + assert (transition < 0.5); + + double k; + double q; + compute_transition_param (k, q, transition); + const int order = nbr_coefs * 2 + 1; + const double attenuation = compute_atten (q, order); + + return attenuation; +} + + + +/* +============================================================================== +Name: compute_coefs +Description: + Computes coefficients for a half-band polyphase IIR filter, function of a + given stopband gain / transition bandwidth specification. + Order is automatically calculated. +Input parameters: + - attenuation: stopband attenuation, dB. > 0. + - transition: normalized transition bandwith. Range ]0 ; 1/2[ +Output parameters: + - coef_arr: Coefficient list, must be large enough to store all the + coefficients. Filter order = nbr_coefs * 2 + 1 +Returns: number of coefficients +Throws: Nothing +============================================================================== +*/ + +int PolyphaseIir2Designer::compute_coefs (double coef_arr [], double attenuation, double transition) +{ + assert (attenuation > 0); + assert (transition > 0); + assert (transition < 0.5); + + double k; + double q; + compute_transition_param (k, q, transition); + + // Computes number of required coefficients + const int order = compute_order (attenuation, q); + const int nbr_coefs = (order - 1) / 2; + + // Coefficient calculation + for (int index = 0; index < nbr_coefs; ++index) + { + coef_arr [index] = compute_coef (index, k, q, order); + } + + return nbr_coefs; +} + + + +/* +============================================================================== +Name: compute_coefs_spec_order_tbw +Description: + Computes coefficients for a half-band polyphase IIR filter, function of a + given transition bandwidth and desired filter order. Bandstop attenuation + is set to the maximum value for these constraints. +Input parameters: + - nbr_coefs: Number of desired coefficients. > 0. + - transition: normalized transition bandwith. Range ]0 ; 1/2[ +Output parameters: + - coef_arr: Coefficient list, must be large enough to store all the + coefficients. +Throws: Nothing +============================================================================== +*/ + +void PolyphaseIir2Designer::compute_coefs_spec_order_tbw (double coef_arr [], int nbr_coefs, double transition) +{ + assert (nbr_coefs > 0); + assert (transition > 0); + assert (transition < 0.5); + + double k; + double q; + compute_transition_param (k, q, transition); + const int order = nbr_coefs * 2 + 1; + + // Coefficient calculation + for (int index = 0; index < nbr_coefs; ++index) + { + coef_arr [index] = compute_coef (index, k, q, order); + } +} + + + +/* +============================================================================== +Name: compute_phase_delay +Description: + Computes the phase delay introduced by a single filtering unit at a + specified frequency. + The delay is given for a constant sampling rate between input and output. +Input parameters: + - a: coefficient for the cell, [0 ; 1] + - f_fs: frequency relative to the sampling rate, [0 ; 0.5]. +Returns: + The phase delay in samples, >= 0. +Throws: Nothing +============================================================================== +*/ + +double PolyphaseIir2Designer::compute_phase_delay (double a, double f_fs) +{ + assert (a >= 0); + assert (a <= 1); + assert (f_fs >= 0); + assert (f_fs < 0.5); + + const double w = 2 * hiir::PI * f_fs; + const double c = cos (w); + const double s = sin (w); + const double x = a + c + a * (c * (a + c) + s * s); + const double y = a * a * s - s; + double ph = atan2 (y, x); + if (ph < 0) + { + ph += 2 * hiir::PI; + } + const double dly = ph / w; + + return dly; +} + + + +/* +============================================================================== +Name: compute_group_delay +Description: + Computes the group delay introduced by a single filtering unit at a + specified frequency. + The delay is given for a constant sampling rate between input and output. + To compute the group delay of a complete filter, add the group delays + of all the units in A0 (z). +Input parameters: + - a: coefficient for the cell, [0 ; 1] + - f_fs: frequency relative to the sampling rate, [0 ; 0.5]. + - ph_flag: set if filtering unit is used in pi/2-phaser mode, in the form + (a - z^-2) / (1 - az^-2) +Returns: + The group delay in samples, >= 0. +Throws: Nothing +============================================================================== +*/ + +double PolyphaseIir2Designer::compute_group_delay (double a, double f_fs, bool ph_flag) +{ + assert (a >= 0); + assert (a <= 1); + assert (f_fs >= 0); + assert (f_fs < 0.5); + + const double w = 2 * hiir::PI * f_fs; + const double a2 = a * a; + const double sig = (ph_flag) ? -2 : 2; + const double dly = 2 * (1 - a2) / (a2 + sig * a * cos (2 * w) + 1); + + return dly; +} + + + +/* +============================================================================== +Name: compute_group_delay +Description: + Computes the group delay introduced by a complete filter at a specified + frequency. + The delay is given for a constant sampling rate between input and output. +Input parameters: + - coef_arr: filter coefficient, as given by the designing functions + - nbr_coefs: Number of filter coefficients. > 0. + - f_fs: frequency relative to the sampling rate, [0 ; 0.5]. + - ph_flag: set if filter is used in pi/2-phaser mode, in the form + (a - z^-2) / (1 - az^-2) +Returns: + The group delay in samples, >= 0. +Throws: Nothing +============================================================================== +*/ + +double PolyphaseIir2Designer::compute_group_delay (const double coef_arr [], int nbr_coefs, double f_fs, bool ph_flag) +{ + assert (nbr_coefs > 0); + assert (f_fs >= 0); + assert (f_fs < 0.5); + + double dly_total = 0; + for (int k = 0; k < nbr_coefs; ++k) + { + const double dly = compute_group_delay (coef_arr [k], f_fs, ph_flag); + dly_total += dly; + } + + return dly_total; +} + + + +/*\\\ PROTECTED \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/ + + + +/*\\\ PRIVATE \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/ + + + +void PolyphaseIir2Designer::compute_transition_param (double &k, double &q, double transition) +{ + assert (transition > 0); + assert (transition < 0.5); + + k = tan ((1 - transition * 2) * hiir::PI / 4); + k *= k; + assert (k < 1); + assert (k > 0); + double kksqrt = pow (1 - k * k, 0.25); + const double e = 0.5 * (1 - kksqrt) / (1 + kksqrt); + const double e2 = e * e; + const double e4 = e2 * e2; + q = e * (1 + e4 * (2 + e4 * (15 + 150 * e4))); + assert (q > 0); +} + + + +int PolyphaseIir2Designer::compute_order (double attenuation, double q) +{ + assert (attenuation > 0); + assert (q > 0); + + const double attn_p2 = pow (10.0, -attenuation / 10); + const double a = attn_p2 / (1 - attn_p2); + int order = hiir::ceil_int (log (a * a / 16) / log (q)); + if ((order & 1) == 0) + { + ++ order; + } + if (order == 1) + { + order = 3; + } + + return order; +} + + + +double PolyphaseIir2Designer::compute_atten (double q, int order) +{ + assert (q > 0); + assert (order > 0); + assert ((order & 1) == 1); + + const double a = 4 * exp (order * 0.5 * log (q)); + assert (a != -1.0); + const double attn_p2 = a / (1 + a); + const double attenuation = -10 * log10 (attn_p2); + assert (attenuation > 0); + + return attenuation; +} + + + +double PolyphaseIir2Designer::compute_coef (int index, double k, double q, int order) +{ + assert (index >= 0); + assert (index * 2 < order); + + const int c = index + 1; + const double num = compute_acc_num (q, order, c) * pow (q, 0.25); + const double den = compute_acc_den (q, order, c) + 0.5; + const double ww = num / den; + const double wwsq = ww * ww; + + const double x = sqrt ((1 - wwsq * k) * (1 - wwsq / k)) / (1 + wwsq); + const double coef = (1 - x) / (1 + x); + + return coef; +} + + + +double PolyphaseIir2Designer::compute_acc_num (double q, int order, int c) +{ + assert (c >= 1); + assert (c < order * 2); + + int i = 0; + int j = 1; + double acc = 0; + double q_ii1; + do + { + q_ii1 = hiir::ipowp (q, i * (i + 1)); + q_ii1 *= sin ((i * 2 + 1) * c * hiir::PI / order) * j; + acc += q_ii1; + + j = -j; + ++i; + } + while (fabs (q_ii1) > 1e-100); + + return acc; +} + + + +double PolyphaseIir2Designer::compute_acc_den (double q, int order, int c) +{ + assert (c >= 1); + assert (c < order * 2); + + int i = 1; + int j = -1; + double acc = 0; + double q_i2; + do + { + q_i2 = hiir::ipowp (q, i * i); + q_i2 *= cos (i * 2 * c * hiir::PI / order) * j; + acc += q_i2; + + j = -j; + ++i; + } + while (fabs (q_i2) > 1e-100); + + return acc; +} + + + +} // namespace hiir + + + +/*\\\ EOF \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/ diff --git a/src/external/hiir/hiir/PolyphaseIir2Designer.h b/src/external/hiir/hiir/PolyphaseIir2Designer.h new file mode 100644 index 00000000..db3b4d09 --- /dev/null +++ b/src/external/hiir/hiir/PolyphaseIir2Designer.h @@ -0,0 +1,143 @@ +/***************************************************************************** + + PolyphaseIir2Designer.h + Author: Laurent de Soras, 2005 + +Compute coefficients for 2-path polyphase IIR filter, half-band filter or +Pi/2 phaser. + + -2 + a + z + N/2-1 2k +A0 (z) = Prod ---------- + k = 0 -2 + 1 + a z + 2k + + -2 + a + z + -1 (N-1)/2 2k+1 +A1 (z) = z . Prod ------------ + k = 0 -2 + 1 + a z + 2k+1 + + 1 +H (z) = - (A0 (z) + A1 (z)) + 2 + +Sum of A0 and A1 gives a low-pass filter. +Difference of A0 and A1 gives the complementary high-pass filter. + +For the Pi/2 phaser, product form is (a - z^-2) / (1 - az^-2) +Sum and difference of A0 and A1 have a Pi/2 phase difference. + +References: + +* Artur Krukowski + Polyphase Two-Path Filter Designer in Java + http://www.cmsa.wmin.ac.uk/~artur/Poly.html + +* R.A. Valenzuela, A.G. Constantinides + Digital Signal Processing Schemes for Efficient Interpolation and Decimation + IEE Proceedings, Dec 1983 + +* Scott Wardle + A Hilbert-Transformer Frequency Shifter for Audio + International Conference on Digital Audio Effects (DAFx) 1998 + http://www.iua.upf.es/dafx98/papers/WAR19.PS + +--- 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_PolyphaseIir2Designer_HEADER_INCLUDED) +#define hiir_PolyphaseIir2Designer_HEADER_INCLUDED + +#if defined (_MSC_VER) + #pragma once + #pragma warning (4 : 4250) // "Inherits via dominance." +#endif + + + +/*\\\ INCLUDE FILES \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/ + + + +namespace hiir +{ + + + +class PolyphaseIir2Designer +{ + +/*\\\ PUBLIC \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/ + +public: + + static int compute_nbr_coefs_from_proto (double attenuation, double transition); + static double compute_atten_from_order_tbw (int nbr_coefs, double transition); + + static int compute_coefs (double coef_arr [], double attenuation, double transition); + static void compute_coefs_spec_order_tbw (double coef_arr [], int nbr_coefs, double transition); + + static double compute_phase_delay (double a, double f_fs); + static double compute_group_delay (double a, double f_fs, bool ph_flag); + static double compute_group_delay (const double coef_arr [], int nbr_coefs, double f_fs, bool ph_flag); + + + +/*\\\ PROTECTED \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/ + +protected: + + + +/*\\\ PRIVATE \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/ + +private: + + static void compute_transition_param (double &k, double &q, double transition); + static int compute_order (double attenuation, double q); + static double compute_atten (double q, int order); + static double compute_coef (int index, double k, double q, int order); + static double compute_acc_num (double q, int order, int c); + static double compute_acc_den (double q, int order, int c); + + + +/*\\\ FORBIDDEN MEMBER FUNCTIONS \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/ + +private: + + PolyphaseIir2Designer (); + ~PolyphaseIir2Designer (); + PolyphaseIir2Designer (const PolyphaseIir2Designer &other); + PolyphaseIir2Designer & + operator = (const PolyphaseIir2Designer &other); + bool operator == (const PolyphaseIir2Designer &other); + bool operator != (const PolyphaseIir2Designer &other); + +}; // class PolyphaseIir2Designer + + + +} // namespace hiir + + + +#endif // hiir_PolyphaseIir2Designer_HEADER_INCLUDED + + + +/*\\\ EOF \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/ From 0034fe59034791600b04ce0c4cf0a75c05a4a6e3 Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Mon, 15 Feb 2021 06:39:35 +0100 Subject: [PATCH 2/9] Add tool to generate oversampler coeffs and code --- devtools/CMakeLists.txt | 3 + devtools/HIIRDesigner.cpp | 365 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 368 insertions(+) create mode 100644 devtools/HIIRDesigner.cpp diff --git a/devtools/CMakeLists.txt b/devtools/CMakeLists.txt index 352c5cd1..e1bc85f6 100644 --- a/devtools/CMakeLists.txt +++ b/devtools/CMakeLists.txt @@ -10,3 +10,6 @@ endif() add_executable(sfizz_preprocessor Preprocessor.cpp) target_link_libraries(sfizz_preprocessor PRIVATE sfizz::parser sfizz::pugixml sfizz::cxxopts) + +add_executable(sfizz_hiir_designer HIIRDesigner.cpp) +target_link_libraries(sfizz_hiir_designer PRIVATE sfizz::hiir_polyphase_iir2designer) diff --git a/devtools/HIIRDesigner.cpp b/devtools/HIIRDesigner.cpp new file mode 100644 index 00000000..d857a449 --- /dev/null +++ b/devtools/HIIRDesigner.cpp @@ -0,0 +1,365 @@ +// 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 +#include +#include + +using FD = hiir::PolyphaseIir2Designer; + +struct Stage { + int factor; + double tbw; + int nbr_coefs; + std::unique_ptr coefs; +}; + +static std::vector calculate_stages(int oversampling, double attenuation, double transition); +static void generate_cpp_prologue(int argc, char *argv[]); +static void generate_cpp_epilogue(); +static void generate_cpp_coefs(const Stage *stages, int num_stages); +static void generate_cpp_upsampler(const Stage *stages, int num_stages); +static void generate_cpp_downsampler(const Stage *stages, int num_stages); + +int main(int argc, char *argv[]) +{ + double attenuation = 0.0; + double transition = 0.0; + int oversampling = 16; + bool have_a = false; + bool have_t = false; + + for (int argi = 1; argi < argc; ++argi) { + const char *arg = argv[argi]; + if (!strcmp(arg, "-a")) { + if (++argi >= argc) { + fprintf(stderr, "The option %s expects a value.\n", arg); + return 1; + } + arg = argv[argi]; + attenuation = atof(arg); + have_a = true; + } + else if (!strcmp(arg, "-t")) { + if (++argi >= argc) { + fprintf(stderr, "The option %s expects a value.\n", arg); + return 1; + } + arg = argv[argi]; + transition = atof(arg); + have_t = true; + } + else if (!strcmp(arg, "-o")) { + if (++argi >= argc) { + fprintf(stderr, "The option %s expects a value.\n", arg); + return 1; + } + arg = argv[argi]; + oversampling = atoi(arg); + } + else { + fprintf(stderr, "Unrecognized argument: %s\n", arg); + return 1; + } + } + + if (!have_a) { + fprintf(stderr, "No attenuation given (-a)\n"); + return 1; + } + if (!have_t) { + fprintf(stderr, "No transition bandwidth given (-t)\n"); + return 1; + } + else if (attenuation < 0.0) { + fprintf(stderr, "Invalid attenuation\n"); + return 1; + } + else if (transition <= 0.0 || transition >= 0.5) { + fprintf(stderr, "Invalid transition bandwidth\n"); + return 1; + } + else if (oversampling < 2) { + fprintf(stderr, "Invalid oversampling\n"); + return 1; + } + + std::vector stages = calculate_stages(oversampling, attenuation, transition); + int num_stages = (int)stages.size(); + + // generate the coeffs + generate_cpp_prologue(argc, argv); + printf("\n"); + generate_cpp_coefs(stages.data(), num_stages); + printf("\n"); + generate_cpp_upsampler(stages.data(), num_stages); + printf("\n"); + generate_cpp_downsampler(stages.data(), num_stages); + printf("\n"); + generate_cpp_epilogue(); + + return 0; +} + +static std::vector calculate_stages(int oversampling, double attenuation, double transition) +{ + std::vector stages; + stages.reserve(8); + + bool done = false; + for (int num_stage = 0; !done; ++num_stage) { + if (num_stage > 0) + printf("\n"); + + Stage stage; + stage.factor = 2 << num_stage; + stage.tbw = transition * + std::pow(0.5, num_stage) + 0.5 * (1 - std::pow(0.5, num_stage)); + + stage.nbr_coefs = FD::compute_nbr_coefs_from_proto(attenuation, stage.tbw); + double *coefs = new double[stage.nbr_coefs]{}; + stage.coefs.reset(coefs); + + FD::compute_coefs(coefs, attenuation, stage.tbw); + + done = stage.factor >= oversampling; + + stages.push_back(std::move(stage)); + } + + return stages; +} + +static void generate_cpp_prologue(int argc, char *argv[]) +{ + printf("// This is generated by the Sfizz HIIR designer\n"); + printf("// Using options:"); + for (int i = 1; i < argc; ++i) + printf(" %s", argv[i]); + printf("\n"); + + printf("\n"); + + printf( + "#pragma once\n" + "#include \"OversamplerHelpers.h\"\n" + "\n" + "namespace sfz {\n" + ); +} + +static void generate_cpp_epilogue() +{ + printf("} // namespace sfz\n"); +} + +static void generate_cpp_coefs(const Stage *stages, int num_stages) +{ + for (int i = 0; i < num_stages; ++i) { + const Stage &stage = stages[i]; + const double *coefs = stage.coefs.get(); + printf("// %dx <-> %dx: TBW = %g\n", stage.factor, stage.factor / 2, stage.tbw); + printf("static constexpr double OSCoeffs%dx[%d] = {\n", stage.factor, stage.nbr_coefs); + for (int i = 0; i < stage.nbr_coefs; ++i) { + printf("\t" "%.18f,\n", coefs[i]); + } + printf("};\n"); + } +} + +static void generate_cpp_upsampler(const Stage *stages, int num_stages) +{ + printf("class Upsampler {\n"); + printf("public:\n"); + + printf("\t" "Upsampler()\n"); + printf("\t" "{\n"); + for (int i = 0; i < num_stages; ++i) { + const Stage &stage = stages[i]; + printf("\t\t" "up%d_.set_coefs(OSCoeffs%dx);\n", stage.factor, stage.factor); + } + printf("\t" "}\n"); + + printf("\t" "void clear()\n"); + printf("\t" "{\n"); + for (int i = 0; i < num_stages; ++i) { + const Stage &stage = stages[i]; + printf("\t\t" "up%d_.clear_buffers();\n", stage.factor); + } + printf("\t" "}\n"); + + printf("\t" "static int recommendedBuffer(int factor, int spl)\n"); + printf("\t" "{\n"); + printf("\t\t" "return factor * spl;\n"); + printf("\t" "}\n"); + + printf("\t" "static bool canProcess(int factor)\n"); + printf("\t" "{\n"); + printf("\t\t" "switch (factor) {\n"); + printf("\t\t" "case 1:\n"); + for (int i = 0; i < num_stages; ++i) { + const Stage &stage = stages[i]; + printf("\t\t" "case %d:\n", stage.factor); + } + printf("\t\t\t" "return true;\n"); + printf("\t\t" "default:\n"); + printf("\t\t\t" "return false;\n"); + printf("\t\t" "}\n"); + printf("\t" "}\n"); + + printf("\t" "void process(int factor, const float *in, float *out, int spl, float *temp, int ntemp)\n"); + printf("\t" "{\n"); + printf("\t\t" "switch (factor) {\n"); + printf("\t\t" "case 1:\n"); + printf("\t\t\t" "if (in != out) std::memcpy(out, in, spl * sizeof(float));\n"); + printf("\t\t\t" "break;\n"); + for (int i = 0; i < num_stages; ++i) { + const Stage &stage = stages[i]; + printf("\t\t" "case %d:\n", stage.factor); + printf("\t\t\t" "process%dx(in, out, spl, temp, ntemp);\n", stage.factor); + printf("\t\t\t" "break;\n"); + } + printf("\t\t" "default:\n"); + printf("\t\t\t" "ASSERTFALSE;\n"); + printf("\t\t\t" "break;\n"); + printf("\t\t" "}\n"); + printf("\t" "}\n"); + + for (int n = 1; n <= num_stages; ++n) { + printf("\t" "void process%dx(const float *in, float *out, int spl, float *temp, int ntemp)\n", stages[n - 1].factor); + printf("\t" "{\n"); + printf("\t\t" "int maxspl = ntemp / %d;\n", stages[n - 1].factor); + printf("\t\t" "ASSERT(maxspl >= 0);\n"); + printf("\t\t" "float *t1 = temp;\n"); + printf("\t\t" "float *t2 = temp + %d * maxspl;\n", stages[n - 1].factor / 2); + printf("\t\t" "(void)t1;\n"); + printf("\t\t" "(void)t2;\n"); + printf("\t\t" "while (spl > 0) {\n"); + printf("\t\t\t" "int curspl = (spl < maxspl) ? spl : maxspl;\n"); + for (int i = 0; i < n; ++i) { + const Stage &stage = stages[i]; + const char *tempnames[] = {"t1", "t2"}; + const char *outname = tempnames[i & 1]; + const char *inname = tempnames[1 - (i & 1)]; + if (i == 0) + inname = "in"; + if (i + 1 == n) + outname = "out"; + printf("\t\t\t" "up%d_.process_block(%s, %s, %d * curspl);\n", stage.factor, outname, inname, stage.factor / 2); + } + printf("\t\t\t" "in += curspl;\n"); + printf("\t\t\t" "out += curspl;\n"); + printf("\t\t\t" "spl -= curspl;\n"); + printf("\t\t" "}\n"); + printf("\t" "}\n"); + } + + printf("private:\n"); + for (int i = 0; i < num_stages; ++i) { + const Stage &stage = stages[i]; + printf("\t" "hiir::Upsampler2x<%d> up%d_;\n", stage.nbr_coefs, stage.factor); + } + printf("};\n"); +} + +static void generate_cpp_downsampler(const Stage *stages, int num_stages) +{ + printf("class Downsampler {\n"); + printf("public:\n"); + + printf("\t" "Downsampler()\n"); + printf("\t" "{\n"); + for (int i = 0; i < num_stages; ++i) { + const Stage &stage = stages[num_stages - 1 - i]; + printf("\t\t" "down%d_.set_coefs(OSCoeffs%dx);\n", stage.factor, stage.factor); + } + printf("\t" "}\n"); + + printf("\t" "void clear()\n"); + printf("\t" "{\n"); + for (int i = 0; i < num_stages; ++i) { + const Stage &stage = stages[num_stages - 1 - i]; + printf("\t\t" "down%d_.clear_buffers();\n", stage.factor); + } + printf("\t" "}\n"); + + printf("\t" "static int recommendedBuffer(int factor, int spl)\n"); + printf("\t" "{\n"); + printf("\t\t" "return factor * spl;\n"); + printf("\t" "}\n"); + + printf("\t" "static bool canProcess(int factor)\n"); + printf("\t" "{\n"); + printf("\t\t" "switch (factor) {\n"); + printf("\t\t" "case 1:\n"); + for (int i = 0; i < num_stages; ++i) { + const Stage &stage = stages[i]; + printf("\t\t" "case %d:\n", stage.factor); + } + printf("\t\t\t" "return true;\n"); + printf("\t\t" "default:\n"); + printf("\t\t\t" "return false;\n"); + printf("\t\t" "}\n"); + printf("\t" "}\n"); + + printf("\t" "void process(int factor, const float *in, float *out, int spl, float *temp, int ntemp)\n"); + printf("\t" "{\n"); + printf("\t\t" "switch (factor) {\n"); + for (int i = 0; i < num_stages; ++i) { + const Stage &stage = stages[num_stages - 1 - i]; + printf("\t\t" "case %d:\n", stage.factor); + printf("\t\t\t" "process%dx(in, out, spl, temp, ntemp);\n", stage.factor); + printf("\t\t\t" "break;\n"); + } + printf("\t\t" "case 1:\n"); + printf("\t\t\t" "if (in != out) std::memcpy(out, in, spl * sizeof(float));\n"); + printf("\t\t\t" "break;\n"); + printf("\t\t" "default:\n"); + printf("\t\t\t" "ASSERTFALSE;\n"); + printf("\t\t\t" "break;\n"); + printf("\t\t" "}\n"); + printf("\t" "}\n"); + + for (int n = 1; n <= num_stages; ++n) { + printf("\t" "void process%dx(const float *in, float *out, int spl, float *temp, int ntemp)\n", stages[n - 1].factor); + printf("\t" "{\n"); + printf("\t\t" "int maxspl = ntemp / %d;\n", stages[n - 1].factor); + printf("\t\t" "ASSERT(maxspl >= 0);\n"); + printf("\t\t" "float *t1 = temp;\n"); + printf("\t\t" "float *t2 = temp + %d * maxspl;\n", stages[n - 1].factor / 2); + printf("\t\t" "(void)t1;\n"); + printf("\t\t" "(void)t2;\n"); + printf("\t\t" "while (spl > 0) {\n"); + printf("\t\t\t" "int curspl = (spl < maxspl) ? spl : maxspl;\n"); + for (int i = 0; i < n; ++i) { + const Stage &stage = stages[n - 1 - i]; + const char *tempnames[] = {"t1", "t2"}; + const char *outname = tempnames[i & 1]; + const char *inname = tempnames[1 - (i & 1)]; + if (i == 0) + inname = "in"; + if (i + 1 == n) + outname = "out"; + printf("\t\t\t" "down%d_.process_block(%s, %s, %d * curspl);\n", stage.factor, outname, inname, stage.factor / 2); + } + printf("\t\t\t" "in += curspl;\n"); + printf("\t\t\t" "out += curspl;\n"); + printf("\t\t\t" "spl -= curspl;\n"); + printf("\t\t" "}\n"); + printf("\t" "}\n"); + } + + printf("private:\n"); + for (int i = 0; i < num_stages; ++i) { + const Stage &stage = stages[num_stages - 1 - i]; + printf("\t" "hiir::Downsampler2x<%d> down%d_;\n", stage.nbr_coefs, stage.factor); + } + printf("};\n"); +} From 090619cfa4bc33dda4b18a090d4ec0bcab62aff5 Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Mon, 15 Feb 2021 14:06:42 +0100 Subject: [PATCH 3/9] Add the generated oversampling code --- src/sfizz/OversamplerHelpers.h | 50 ++++ src/sfizz/OversamplerHelpers.hxx | 496 +++++++++++++++++++++++++++++++ 2 files changed, 546 insertions(+) create mode 100644 src/sfizz/OversamplerHelpers.h create mode 100644 src/sfizz/OversamplerHelpers.hxx diff --git a/src/sfizz/OversamplerHelpers.h b/src/sfizz/OversamplerHelpers.h new file mode 100644 index 00000000..ae7e6771 --- /dev/null +++ b/src/sfizz/OversamplerHelpers.h @@ -0,0 +1,50 @@ +#pragma once +#include "SIMDConfig.h" +#include "Config.h" +#include "Debug.h" +#include +#include + +namespace sfz { +class Upsampler; +class Downsampler; +} // namespace sfz + +#include +#include + +// Note: according to HIIR documentation, FPU versions are +// more efficient than SIMD below 12 coefficients. + +#if SFIZZ_HAVE_SSE +#include +#include + +namespace hiir { +template using Upsampler2x = typename std::conditional= 12, + hiir::Upsampler2xSse, hiir::Upsampler2xFpu>::type; +template using Downsampler2x = typename std::conditional= 12, + hiir::Downsampler2xSse, hiir::Downsampler2xFpu>::type; +} // namespace hiir + +#elif SFIZZ_HAVE_NEON +#include +#include + +namespace hiir { +template using Upsampler2x = typename std::conditional= 12, + hiir::Upsampler2xNeon, hiir::Upsampler2xFpu>::type; +template using Downsampler2x = typename std::conditional= 12, + hiir::Downsampler2xNeon, hiir::Downsampler2xFpu>::type; +} // namespace hiir + +#else + +namespace hiir { +template using Upsampler2x = hiir::Upsampler2xFpu; +template using Downsampler2x = hiir::Downsampler2xFpu; +} // namespace hiir + +#endif + +#include "OversamplerHelpers.hxx" diff --git a/src/sfizz/OversamplerHelpers.hxx b/src/sfizz/OversamplerHelpers.hxx new file mode 100644 index 00000000..bca26547 --- /dev/null +++ b/src/sfizz/OversamplerHelpers.hxx @@ -0,0 +1,496 @@ +// SPDX-License-Identifier: BSD-2-Clause + +// This code is part of the sfizz library and is licensed under a BSD 2-clause +// license. You should have receive a LICENSE.md file along with the code. +// If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz + +// This is generated by the Sfizz HIIR designer +// Using options: -a 96 -t 0.01 -o 128 + +#pragma once +#include "OversamplerHelpers.h" + +namespace sfz { + +// 2x <-> 1x: TBW = 0.01 +static constexpr double OSCoeffs2x[12] = { + 0.036681502163648017, + 0.136547624631957715, + 0.274631759379454110, + 0.423138617436566666, + 0.561098697879194752, + 0.677540049974161618, + 0.769741833863226588, + 0.839889624849638028, + 0.892260818003878908, + 0.931541959963183896, + 0.962094548378083947, + 0.987816370732897076, +}; +// 4x <-> 2x: TBW = 0.255 +static constexpr double OSCoeffs4x[4] = { + 0.041893991997656171, + 0.168903482439952013, + 0.390560772921165922, + 0.743895748268478152, +}; +// 8x <-> 4x: TBW = 0.3775 +static constexpr double OSCoeffs8x[3] = { + 0.055748680811302048, + 0.243051195741530918, + 0.646699131192682297, +}; +// 16x <-> 8x: TBW = 0.43875 +static constexpr double OSCoeffs16x[2] = { + 0.107172166664564611, + 0.530904350331903085, +}; +// 32x <-> 16x: TBW = 0.469375 +static constexpr double OSCoeffs32x[2] = { + 0.105969237763476387, + 0.528620279623742473, +}; +// 64x <-> 32x: TBW = 0.484687 +static constexpr double OSCoeffs64x[1] = { + 0.333526281707771211, +}; +// 128x <-> 64x: TBW = 0.492344 +static constexpr double OSCoeffs128x[1] = { + 0.333381553051105561, +}; + +class Upsampler { +public: + Upsampler() + { + up2_.set_coefs(OSCoeffs2x); + up4_.set_coefs(OSCoeffs4x); + up8_.set_coefs(OSCoeffs8x); + up16_.set_coefs(OSCoeffs16x); + up32_.set_coefs(OSCoeffs32x); + up64_.set_coefs(OSCoeffs64x); + up128_.set_coefs(OSCoeffs128x); + } + void clear() + { + up2_.clear_buffers(); + up4_.clear_buffers(); + up8_.clear_buffers(); + up16_.clear_buffers(); + up32_.clear_buffers(); + up64_.clear_buffers(); + up128_.clear_buffers(); + } + static int recommendedBuffer(int factor, int spl) + { + return factor * spl; + } + static bool canProcess(int factor) + { + switch (factor) { + case 1: + case 2: + case 4: + case 8: + case 16: + case 32: + case 64: + case 128: + return true; + default: + return false; + } + } + void process(int factor, const float *in, float *out, int spl, float *temp, int ntemp) + { + switch (factor) { + case 1: + if (in != out) std::memcpy(out, in, spl * sizeof(float)); + break; + case 2: + process2x(in, out, spl, temp, ntemp); + break; + case 4: + process4x(in, out, spl, temp, ntemp); + break; + case 8: + process8x(in, out, spl, temp, ntemp); + break; + case 16: + process16x(in, out, spl, temp, ntemp); + break; + case 32: + process32x(in, out, spl, temp, ntemp); + break; + case 64: + process64x(in, out, spl, temp, ntemp); + break; + case 128: + process128x(in, out, spl, temp, ntemp); + break; + default: + ASSERTFALSE; + break; + } + } + void process2x(const float *in, float *out, int spl, float *temp, int ntemp) + { + int maxspl = ntemp / 2; + ASSERT(maxspl >= 0); + float *t1 = temp; + float *t2 = temp + 1 * maxspl; + (void)t1; + (void)t2; + while (spl > 0) { + int curspl = (spl < maxspl) ? spl : maxspl; + up2_.process_block(out, in, 1 * curspl); + in += curspl; + out += curspl; + spl -= curspl; + } + } + void process4x(const float *in, float *out, int spl, float *temp, int ntemp) + { + int maxspl = ntemp / 4; + ASSERT(maxspl >= 0); + float *t1 = temp; + float *t2 = temp + 2 * maxspl; + (void)t1; + (void)t2; + while (spl > 0) { + int curspl = (spl < maxspl) ? spl : maxspl; + up2_.process_block(t1, in, 1 * curspl); + up4_.process_block(out, t1, 2 * curspl); + in += curspl; + out += curspl; + spl -= curspl; + } + } + void process8x(const float *in, float *out, int spl, float *temp, int ntemp) + { + int maxspl = ntemp / 8; + ASSERT(maxspl >= 0); + float *t1 = temp; + float *t2 = temp + 4 * maxspl; + (void)t1; + (void)t2; + while (spl > 0) { + int curspl = (spl < maxspl) ? spl : maxspl; + up2_.process_block(t1, in, 1 * curspl); + up4_.process_block(t2, t1, 2 * curspl); + up8_.process_block(out, t2, 4 * curspl); + in += curspl; + out += curspl; + spl -= curspl; + } + } + void process16x(const float *in, float *out, int spl, float *temp, int ntemp) + { + int maxspl = ntemp / 16; + ASSERT(maxspl >= 0); + float *t1 = temp; + float *t2 = temp + 8 * maxspl; + (void)t1; + (void)t2; + while (spl > 0) { + int curspl = (spl < maxspl) ? spl : maxspl; + up2_.process_block(t1, in, 1 * curspl); + up4_.process_block(t2, t1, 2 * curspl); + up8_.process_block(t1, t2, 4 * curspl); + up16_.process_block(out, t1, 8 * curspl); + in += curspl; + out += curspl; + spl -= curspl; + } + } + void process32x(const float *in, float *out, int spl, float *temp, int ntemp) + { + int maxspl = ntemp / 32; + ASSERT(maxspl >= 0); + float *t1 = temp; + float *t2 = temp + 16 * maxspl; + (void)t1; + (void)t2; + while (spl > 0) { + int curspl = (spl < maxspl) ? spl : maxspl; + up2_.process_block(t1, in, 1 * curspl); + up4_.process_block(t2, t1, 2 * curspl); + up8_.process_block(t1, t2, 4 * curspl); + up16_.process_block(t2, t1, 8 * curspl); + up32_.process_block(out, t2, 16 * curspl); + in += curspl; + out += curspl; + spl -= curspl; + } + } + void process64x(const float *in, float *out, int spl, float *temp, int ntemp) + { + int maxspl = ntemp / 64; + ASSERT(maxspl >= 0); + float *t1 = temp; + float *t2 = temp + 32 * maxspl; + (void)t1; + (void)t2; + while (spl > 0) { + int curspl = (spl < maxspl) ? spl : maxspl; + up2_.process_block(t1, in, 1 * curspl); + up4_.process_block(t2, t1, 2 * curspl); + up8_.process_block(t1, t2, 4 * curspl); + up16_.process_block(t2, t1, 8 * curspl); + up32_.process_block(t1, t2, 16 * curspl); + up64_.process_block(out, t1, 32 * curspl); + in += curspl; + out += curspl; + spl -= curspl; + } + } + void process128x(const float *in, float *out, int spl, float *temp, int ntemp) + { + int maxspl = ntemp / 128; + ASSERT(maxspl >= 0); + float *t1 = temp; + float *t2 = temp + 64 * maxspl; + (void)t1; + (void)t2; + while (spl > 0) { + int curspl = (spl < maxspl) ? spl : maxspl; + up2_.process_block(t1, in, 1 * curspl); + up4_.process_block(t2, t1, 2 * curspl); + up8_.process_block(t1, t2, 4 * curspl); + up16_.process_block(t2, t1, 8 * curspl); + up32_.process_block(t1, t2, 16 * curspl); + up64_.process_block(t2, t1, 32 * curspl); + up128_.process_block(out, t2, 64 * curspl); + in += curspl; + out += curspl; + spl -= curspl; + } + } +private: + hiir::Upsampler2x<12> up2_; + hiir::Upsampler2x<4> up4_; + hiir::Upsampler2x<3> up8_; + hiir::Upsampler2x<2> up16_; + hiir::Upsampler2x<2> up32_; + hiir::Upsampler2x<1> up64_; + hiir::Upsampler2x<1> up128_; +}; + +class Downsampler { +public: + Downsampler() + { + down128_.set_coefs(OSCoeffs128x); + down64_.set_coefs(OSCoeffs64x); + down32_.set_coefs(OSCoeffs32x); + down16_.set_coefs(OSCoeffs16x); + down8_.set_coefs(OSCoeffs8x); + down4_.set_coefs(OSCoeffs4x); + down2_.set_coefs(OSCoeffs2x); + } + void clear() + { + down128_.clear_buffers(); + down64_.clear_buffers(); + down32_.clear_buffers(); + down16_.clear_buffers(); + down8_.clear_buffers(); + down4_.clear_buffers(); + down2_.clear_buffers(); + } + static int recommendedBuffer(int factor, int spl) + { + return factor * spl; + } + static bool canProcess(int factor) + { + switch (factor) { + case 1: + case 2: + case 4: + case 8: + case 16: + case 32: + case 64: + case 128: + return true; + default: + return false; + } + } + void process(int factor, const float *in, float *out, int spl, float *temp, int ntemp) + { + switch (factor) { + case 128: + process128x(in, out, spl, temp, ntemp); + break; + case 64: + process64x(in, out, spl, temp, ntemp); + break; + case 32: + process32x(in, out, spl, temp, ntemp); + break; + case 16: + process16x(in, out, spl, temp, ntemp); + break; + case 8: + process8x(in, out, spl, temp, ntemp); + break; + case 4: + process4x(in, out, spl, temp, ntemp); + break; + case 2: + process2x(in, out, spl, temp, ntemp); + break; + case 1: + if (in != out) std::memcpy(out, in, spl * sizeof(float)); + break; + default: + ASSERTFALSE; + break; + } + } + void process2x(const float *in, float *out, int spl, float *temp, int ntemp) + { + int maxspl = ntemp / 2; + ASSERT(maxspl >= 0); + float *t1 = temp; + float *t2 = temp + 1 * maxspl; + (void)t1; + (void)t2; + while (spl > 0) { + int curspl = (spl < maxspl) ? spl : maxspl; + down2_.process_block(out, in, 1 * curspl); + in += curspl; + out += curspl; + spl -= curspl; + } + } + void process4x(const float *in, float *out, int spl, float *temp, int ntemp) + { + int maxspl = ntemp / 4; + ASSERT(maxspl >= 0); + float *t1 = temp; + float *t2 = temp + 2 * maxspl; + (void)t1; + (void)t2; + while (spl > 0) { + int curspl = (spl < maxspl) ? spl : maxspl; + down4_.process_block(t1, in, 2 * curspl); + down2_.process_block(out, t1, 1 * curspl); + in += curspl; + out += curspl; + spl -= curspl; + } + } + void process8x(const float *in, float *out, int spl, float *temp, int ntemp) + { + int maxspl = ntemp / 8; + ASSERT(maxspl >= 0); + float *t1 = temp; + float *t2 = temp + 4 * maxspl; + (void)t1; + (void)t2; + while (spl > 0) { + int curspl = (spl < maxspl) ? spl : maxspl; + down8_.process_block(t1, in, 4 * curspl); + down4_.process_block(t2, t1, 2 * curspl); + down2_.process_block(out, t2, 1 * curspl); + in += curspl; + out += curspl; + spl -= curspl; + } + } + void process16x(const float *in, float *out, int spl, float *temp, int ntemp) + { + int maxspl = ntemp / 16; + ASSERT(maxspl >= 0); + float *t1 = temp; + float *t2 = temp + 8 * maxspl; + (void)t1; + (void)t2; + while (spl > 0) { + int curspl = (spl < maxspl) ? spl : maxspl; + down16_.process_block(t1, in, 8 * curspl); + down8_.process_block(t2, t1, 4 * curspl); + down4_.process_block(t1, t2, 2 * curspl); + down2_.process_block(out, t1, 1 * curspl); + in += curspl; + out += curspl; + spl -= curspl; + } + } + void process32x(const float *in, float *out, int spl, float *temp, int ntemp) + { + int maxspl = ntemp / 32; + ASSERT(maxspl >= 0); + float *t1 = temp; + float *t2 = temp + 16 * maxspl; + (void)t1; + (void)t2; + while (spl > 0) { + int curspl = (spl < maxspl) ? spl : maxspl; + down32_.process_block(t1, in, 16 * curspl); + down16_.process_block(t2, t1, 8 * curspl); + down8_.process_block(t1, t2, 4 * curspl); + down4_.process_block(t2, t1, 2 * curspl); + down2_.process_block(out, t2, 1 * curspl); + in += curspl; + out += curspl; + spl -= curspl; + } + } + void process64x(const float *in, float *out, int spl, float *temp, int ntemp) + { + int maxspl = ntemp / 64; + ASSERT(maxspl >= 0); + float *t1 = temp; + float *t2 = temp + 32 * maxspl; + (void)t1; + (void)t2; + while (spl > 0) { + int curspl = (spl < maxspl) ? spl : maxspl; + down64_.process_block(t1, in, 32 * curspl); + down32_.process_block(t2, t1, 16 * curspl); + down16_.process_block(t1, t2, 8 * curspl); + down8_.process_block(t2, t1, 4 * curspl); + down4_.process_block(t1, t2, 2 * curspl); + down2_.process_block(out, t1, 1 * curspl); + in += curspl; + out += curspl; + spl -= curspl; + } + } + void process128x(const float *in, float *out, int spl, float *temp, int ntemp) + { + int maxspl = ntemp / 128; + ASSERT(maxspl >= 0); + float *t1 = temp; + float *t2 = temp + 64 * maxspl; + (void)t1; + (void)t2; + while (spl > 0) { + int curspl = (spl < maxspl) ? spl : maxspl; + down128_.process_block(t1, in, 64 * curspl); + down64_.process_block(t2, t1, 32 * curspl); + down32_.process_block(t1, t2, 16 * curspl); + down16_.process_block(t2, t1, 8 * curspl); + down8_.process_block(t1, t2, 4 * curspl); + down4_.process_block(t2, t1, 2 * curspl); + down2_.process_block(out, t2, 1 * curspl); + in += curspl; + out += curspl; + spl -= curspl; + } + } +private: + hiir::Downsampler2x<1> down128_; + hiir::Downsampler2x<1> down64_; + hiir::Downsampler2x<2> down32_; + hiir::Downsampler2x<2> down16_; + hiir::Downsampler2x<3> down8_; + hiir::Downsampler2x<4> down4_; + hiir::Downsampler2x<12> down2_; +}; + +} // namespace sfz From 4b6497272d54c02f24e03b97b5100e8c5ad29685 Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Mon, 15 Feb 2021 15:25:50 +0100 Subject: [PATCH 4/9] Implement sfizz oversampler using new code --- src/sfizz/Oversampler.cpp | 169 ++++++-------------------------------- 1 file changed, 24 insertions(+), 145 deletions(-) diff --git a/src/sfizz/Oversampler.cpp b/src/sfizz/Oversampler.cpp index 954a6119..b0d1ed5f 100644 --- a/src/sfizz/Oversampler.cpp +++ b/src/sfizz/Oversampler.cpp @@ -5,6 +5,7 @@ // If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz #include "Oversampler.h" +#include "OversamplerHelpers.h" #include "Buffer.h" #include "AudioSpan.h" #include "AudioReader.h" @@ -14,52 +15,6 @@ template using aligned_vector = std::vector>; -constexpr std::array coeffsStage2x { - 0.036681502163648017, - 0.13654762463195771, - 0.27463175937945411, - 0.42313861743656667, - 0.56109869787919475, - 0.67754004997416162, - 0.76974183386322659, - 0.83988962484963803, - 0.89226081800387891, - 0.9315419599631839, - 0.96209454837808395, - 0.98781637073289708 -}; - -constexpr std::array coeffsStage4x { - 0.042448989488488006, - 0.17072114107630679, - 0.39329183835224008, - 0.74569514831986694 -}; - -constexpr std::array coeffsStage8x { - 0.055748680811302048, - 0.24305119574153092, - 0.6466991311926823 -}; - - -#if SFIZZ_HAVE_SSE -#include "hiir/Upsampler2xSse.h" -using Upsampler2x = hiir::Upsampler2xSse; -using Upsampler4x = hiir::Upsampler2xSse; -using Upsampler8x = hiir::Upsampler2xSse; -#elif SFIZZ_HAVE_NEON -#include "hiir/Upsampler2xNeon.h" -using Upsampler2x = hiir::Upsampler2xNeon; -using Upsampler4x = hiir::Upsampler2xNeon; -using Upsampler8x = hiir::Upsampler2xNeon; -#else -#include "hiir/Upsampler2xFpu.h" -using Upsampler2x = hiir::Upsampler2xFpu; -using Upsampler4x = hiir::Upsampler2xFpu; -using Upsampler8x = hiir::Upsampler2xFpu; -#endif - sfz::Oversampler::Oversampler(sfz::Oversampling factor, size_t chunkSize) : factor(factor), chunkSize(chunkSize) { @@ -74,36 +29,10 @@ void sfz::Oversampler::stream(AudioSpan input, AudioSpan output, s const auto numFrames = input.getNumFrames(); const auto numChannels = input.getNumChannels(); - aligned_vector upsampler2x; - aligned_vector upsampler4x; - aligned_vector upsampler8x; + aligned_vector upsampler(numChannels); - switch(factor) - { - case Oversampling::x8: - upsampler8x.resize(numChannels); - for (auto& upsampler: upsampler8x) - upsampler.set_coefs(coeffsStage8x.data()); - // fallthrough - case Oversampling::x4: - upsampler4x.resize(numChannels); - for (auto& upsampler: upsampler4x) - upsampler.set_coefs(coeffsStage4x.data()); - // fallthrough - case Oversampling::x2: - upsampler2x.resize(numChannels); - for (auto& upsampler: upsampler2x) - upsampler.set_coefs(coeffsStage2x.data()); - break; - case Oversampling::x1: - break; - } - - // Intermediate buffers - sfz::Buffer buffer1 { chunkSize * 2 }; - sfz::Buffer buffer2 { chunkSize * 4 }; - auto span1 = absl::MakeSpan(buffer1); - auto span2 = absl::MakeSpan(buffer2); + // Intermediate buffer + sfz::Buffer temp { std::max(128, Upsampler::recommendedBuffer(16, chunkSize)) }; size_t inputFrameCounter { 0 }; size_t outputFrameCounter { 0 }; @@ -115,23 +44,10 @@ void sfz::Oversampler::stream(AudioSpan input, AudioSpan output, s for (size_t chanIdx = 0; chanIdx < numChannels; chanIdx++) { const auto inputChunk = input.getSpan(chanIdx).subspan(inputFrameCounter, thisChunkSize); const auto outputChunk = output.getSpan(chanIdx).subspan(outputFrameCounter, outputChunkSize); - switch (factor) { - case Oversampling::x1: - copy(inputChunk, outputChunk); - break; - case Oversampling::x2: - upsampler2x[chanIdx].process_block(outputChunk.data(), inputChunk.data(), static_cast(thisChunkSize)); - break; - case Oversampling::x4: - upsampler2x[chanIdx].process_block(span1.data(), inputChunk.data(), static_cast(thisChunkSize)); - upsampler4x[chanIdx].process_block(outputChunk.data(), span1.data(), static_cast(thisChunkSize * 2)); - break; - case Oversampling::x8: - upsampler2x[chanIdx].process_block(span1.data(), inputChunk.data(), static_cast(thisChunkSize)); - upsampler4x[chanIdx].process_block(span2.data(), span1.data(), static_cast(thisChunkSize * 2)); - upsampler8x[chanIdx].process_block(outputChunk.data(), span2.data(), static_cast(thisChunkSize * 4)); - break; - } + upsampler[chanIdx].process( + static_cast(factor), + inputChunk.data(), outputChunk.data(), static_cast(inputChunk.size()), + temp.data(), static_cast(temp.size())); } inputFrameCounter += thisChunkSize; outputFrameCounter += outputChunkSize; @@ -149,47 +65,18 @@ void sfz::Oversampler::stream(AudioReader& input, AudioSpan output, std:: const auto numFrames = static_cast(input.frames()); const auto numChannels = input.channels(); - aligned_vector upsampler2x; - aligned_vector upsampler4x; - aligned_vector upsampler8x; - - switch(factor) - { - case Oversampling::x8: - upsampler8x.resize(numChannels); - for (auto& upsampler: upsampler8x) - upsampler.set_coefs(coeffsStage8x.data()); - // fallthrough - case Oversampling::x4: - upsampler4x.resize(numChannels); - for (auto& upsampler: upsampler4x) - upsampler.set_coefs(coeffsStage4x.data()); - // fallthrough - case Oversampling::x2: - upsampler2x.resize(numChannels); - for (auto& upsampler: upsampler2x) - upsampler.set_coefs(coeffsStage2x.data()); - break; - case Oversampling::x1: - break; - } + aligned_vector upsampler(numChannels); // Intermediate buffers sfz::Buffer fileBlock { chunkSize * numChannels }; - sfz::Buffer buffer1 { chunkSize * 2 }; - sfz::Buffer buffer2 { chunkSize * 4 }; - auto span1 = absl::MakeSpan(buffer1); - auto span2 = absl::MakeSpan(buffer2); + sfz::Buffer channelBlock { chunkSize }; + sfz::Buffer temp { std::max(128, Upsampler::recommendedBuffer(16, chunkSize)) }; - auto upsample2xFromInterleaved = [numChannels]( - Upsampler2x& upsampler, float* output, const float* input, - size_t numInputFrames, unsigned chanIdx) + auto deinterleave = [numChannels]( + float* output, const float* input, size_t numFrames, unsigned chanIdx) { - for (size_t i = 0; i < numInputFrames; ++i) { - float* outp = &output[2 * i]; - const float* inp = &input[i * numChannels + chanIdx]; - upsampler.process_sample(outp[0], outp[1], inp[0]); - } + for (size_t i = 0; i < numFrames; ++i) + output[i] = input[i * numChannels + chanIdx]; }; size_t inputFrameCounter { 0 }; @@ -211,23 +98,15 @@ void sfz::Oversampler::stream(AudioReader& input, AudioSpan output, std:: for (size_t chanIdx = 0; chanIdx < numChannels; chanIdx++) { const auto outputChunk = output.getSpan(chanIdx).subspan(outputFrameCounter, outputChunkSize); - switch (factor) { - case Oversampling::x1: - for (size_t i = 0; i < thisChunkSize; ++i) - outputChunk[i] = fileBlock[i * numChannels + chanIdx]; - break; - case Oversampling::x2: - upsample2xFromInterleaved(upsampler2x[chanIdx], outputChunk.data(), fileBlock.data(), thisChunkSize, chanIdx); - break; - case Oversampling::x4: - upsample2xFromInterleaved(upsampler2x[chanIdx], span1.data(), fileBlock.data(), thisChunkSize, chanIdx); - upsampler4x[chanIdx].process_block(outputChunk.data(), span1.data(), static_cast(thisChunkSize * 2)); - break; - case Oversampling::x8: - upsample2xFromInterleaved(upsampler2x[chanIdx], span1.data(), fileBlock.data(), thisChunkSize, chanIdx); - upsampler4x[chanIdx].process_block(span2.data(), span1.data(), static_cast(thisChunkSize * 2)); - upsampler8x[chanIdx].process_block(outputChunk.data(), span2.data(), static_cast(thisChunkSize * 4)); - break; + + if (factor == Oversampling::x1) + deinterleave(outputChunk.data(), fileBlock.data(), thisChunkSize, chanIdx); + else { + deinterleave(channelBlock.data(), fileBlock.data(), thisChunkSize, chanIdx); + upsampler[chanIdx].process( + static_cast(factor), + channelBlock.data(), outputChunk.data(), static_cast(thisChunkSize), + temp.data(), static_cast(temp.size())); } } inputFrameCounter += thisChunkSize; From 69729b386bb4fed4ac35548633a39fb9c6f02d8f Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Mon, 22 Feb 2021 00:55:33 +0100 Subject: [PATCH 5/9] Comment emphasis about the generated file --- devtools/HIIRDesigner.cpp | 2 ++ src/sfizz/OversamplerHelpers.hxx | 2 ++ 2 files changed, 4 insertions(+) diff --git a/devtools/HIIRDesigner.cpp b/devtools/HIIRDesigner.cpp index d857a449..bbb640fd 100644 --- a/devtools/HIIRDesigner.cpp +++ b/devtools/HIIRDesigner.cpp @@ -139,11 +139,13 @@ static std::vector calculate_stages(int oversampling, double attenuation, static void generate_cpp_prologue(int argc, char *argv[]) { + printf("//------------------------------------------------------------------------------\n"); printf("// This is generated by the Sfizz HIIR designer\n"); printf("// Using options:"); for (int i = 1; i < argc; ++i) printf(" %s", argv[i]); printf("\n"); + printf("//------------------------------------------------------------------------------\n"); printf("\n"); diff --git a/src/sfizz/OversamplerHelpers.hxx b/src/sfizz/OversamplerHelpers.hxx index bca26547..49cf3f60 100644 --- a/src/sfizz/OversamplerHelpers.hxx +++ b/src/sfizz/OversamplerHelpers.hxx @@ -4,8 +4,10 @@ // license. You should have receive a LICENSE.md file along with the code. // If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz +//------------------------------------------------------------------------------ // This is generated by the Sfizz HIIR designer // Using options: -a 96 -t 0.01 -o 128 +//------------------------------------------------------------------------------ #pragma once #include "OversamplerHelpers.h" From 781f2167adb6bfd73e0a76653ffd5928c1504d78 Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Mon, 22 Feb 2021 01:20:57 +0100 Subject: [PATCH 6/9] Update disto fx with new oversampler --- src/sfizz/effects/Disto.cpp | 58 ++++++++++--------------------------- 1 file changed, 15 insertions(+), 43 deletions(-) diff --git a/src/sfizz/effects/Disto.cpp b/src/sfizz/effects/Disto.cpp index 946fdae6..0ee5534e 100644 --- a/src/sfizz/effects/Disto.cpp +++ b/src/sfizz/effects/Disto.cpp @@ -22,8 +22,7 @@ #include "Opcode.h" #include "Config.h" #include "MathHelpers.h" -#include -#include +#include "OversamplerHelpers.h" #include #include @@ -47,15 +46,9 @@ struct Disto::Impl { float _toneLpfMem[EffectChannels] = {}; faustDisto _stages[EffectChannels][Default::maxDistoStages]; - hiir::Upsampler2xFpu<12> _up2x[EffectChannels]; - hiir::Upsampler2xFpu<4> _up4x[EffectChannels]; - hiir::Upsampler2xFpu<3> _up8x[EffectChannels]; - - hiir::Downsampler2xFpu<12> _down2x[EffectChannels]; - hiir::Downsampler2xFpu<4> _down4x[EffectChannels]; - hiir::Downsampler2xFpu<3> _down8x[EffectChannels]; - - std::unique_ptr _temp8x[2]; + sfz::Upsampler _upsampler[EffectChannels]; + sfz::Downsampler _downsampler[EffectChannels]; + std::unique_ptr _temp[2]; // use the same formula as reverb float toneCutoff() const noexcept @@ -97,27 +90,14 @@ void Disto::setSampleRate(double sampleRate) stage.instanceConstants(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 }; - static constexpr double coefs4x[4] = { 0.042448989488488006, 0.17072114107630679, 0.39329183835224008, 0.74569514831986694 }; - static constexpr double coefs8x[3] = { 0.055748680811302048, 0.24305119574153092, 0.6466991311926823 }; - - for (unsigned c = 0; c < EffectChannels; ++c) { - impl._down2x[c].set_coefs(coefs2x); - impl._down4x[c].set_coefs(coefs4x); - impl._down8x[c].set_coefs(coefs8x); - impl._up2x[c].set_coefs(coefs2x); - impl._up4x[c].set_coefs(coefs4x); - impl._up8x[c].set_coefs(coefs8x); - } } void Disto::setSamplesPerBlock(int samplesPerBlock) { Impl& impl = *_impl; - for (std::unique_ptr& temp : impl._temp8x) - temp.reset(new float[8 * samplesPerBlock]); + for (std::unique_ptr& temp : impl._temp) + temp.reset(new float[_oversampling * samplesPerBlock]); } void Disto::clear() @@ -130,12 +110,8 @@ void Disto::clear() for (unsigned c = 0; c < EffectChannels; ++c) { impl._toneLpfMem[c] = 0.0f; - impl._up2x[c].clear_buffers(); - impl._up4x[c].clear_buffers(); - impl._up8x[c].clear_buffers(); - impl._down2x[c].clear_buffers(); - impl._down4x[c].clear_buffers(); - impl._down8x[c].clear_buffers(); + impl._downsampler[c].clear(); + impl._upsampler[c].clear(); } } @@ -162,14 +138,12 @@ void Disto::process(const float* const inputs[], float* const outputs[], unsigne } impl._toneLpfMem[c] = lpfMem; - // upsample to 8x + // upsample absl::Span temp[2] = { - absl::Span(impl._temp8x[0].get(), 8 * nframes), - absl::Span(impl._temp8x[1].get(), 8 * nframes), + absl::Span(impl._temp[0].get(), _oversampling * nframes), + absl::Span(impl._temp[1].get(), _oversampling * nframes), }; - impl._up2x[c].process_block(temp[0].data(), lpfOut.data(), nframes); - impl._up4x[c].process_block(temp[1].data(), temp[0].data(), 2 * nframes); - impl._up8x[c].process_block(temp[0].data(), temp[1].data(), 4 * nframes); + impl._upsampler[c].process(_oversampling, lpfOut.data(), temp[0].data(), nframes, temp[1].data(), static_cast(temp[1].size())); absl::Span upsamplerOut = temp[0]; // run disto stages @@ -180,13 +154,11 @@ void Disto::process(const float* const inputs[], float* const outputs[], unsigne // float *faustIn[] = { stageInOut.data() }; float *faustOut[] = { stageInOut.data() }; - impl._stages[c][s].compute(8 * nframes, faustIn, faustOut); + impl._stages[c][s].compute(_oversampling * nframes, faustIn, faustOut); } - // downsample to 1x - impl._down8x[c].process_block(temp[1].data(), stageInOut.data(), 4 * nframes); - impl._down4x[c].process_block(temp[0].data(), temp[1].data(), 2 * nframes); - impl._down2x[c].process_block(outputs[c], temp[0].data(), nframes); + // downsample + impl._downsampler[c].process(_oversampling, stageInOut.data(), outputs[c], nframes, temp[1].data(), static_cast(temp[1].size())); // dry/wet mix absl::Span mixOut(outputs[c], nframes); From ff359df5cd599feb79d276cdce260fa60af8ee5a Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Mon, 22 Feb 2021 02:03:21 +0100 Subject: [PATCH 7/9] Oversampler special cases 2x 4x --- devtools/HIIRDesigner.cpp | 60 +++++++++++++---- src/sfizz/OversamplerHelpers.hxx | 112 +++++++++++-------------------- 2 files changed, 86 insertions(+), 86 deletions(-) diff --git a/devtools/HIIRDesigner.cpp b/devtools/HIIRDesigner.cpp index bbb640fd..d021764a 100644 --- a/devtools/HIIRDesigner.cpp +++ b/devtools/HIIRDesigner.cpp @@ -199,7 +199,14 @@ static void generate_cpp_upsampler(const Stage *stages, int num_stages) printf("\t" "static int recommendedBuffer(int factor, int spl)\n"); printf("\t" "{\n"); - printf("\t\t" "return factor * spl;\n"); + printf("\t\t" "switch (factor) {\n"); + printf("\t\t" "case 2:\n"); + printf("\t\t\t" "return 0;\n"); + printf("\t\t" "case 4:\n"); + printf("\t\t\t" "return 2 * spl;\n"); + printf("\t\t" "default:\n"); + printf("\t\t\t" "return factor * spl;\n"); + printf("\t\t" "}\n"); printf("\t" "}\n"); printf("\t" "static bool canProcess(int factor)\n"); @@ -235,14 +242,25 @@ static void generate_cpp_upsampler(const Stage *stages, int num_stages) printf("\t" "}\n"); for (int n = 1; n <= num_stages; ++n) { + // special case factor=2, buffer not required + if (stages[n - 1].factor == 2) { + printf("\t" "void process2x(const float *in, float *out, int spl, float * = nullptr, int = 0)\n"); + printf("\t" "{\n"); + printf("\t\t" "up2_.process_block(out, in, spl);\n"); + printf("\t" "}\n"); + continue; + } printf("\t" "void process%dx(const float *in, float *out, int spl, float *temp, int ntemp)\n", stages[n - 1].factor); printf("\t" "{\n"); - printf("\t\t" "int maxspl = ntemp / %d;\n", stages[n - 1].factor); - printf("\t\t" "ASSERT(maxspl >= 0);\n"); + // special case factor=4, only 1 buffer required + if (stages[n - 1].factor > 4) + printf("\t\t" "int maxspl = ntemp / %d;\n", stages[n - 1].factor); + else + printf("\t\t" "int maxspl = ntemp / %d;\n", stages[n - 1].factor / 2); + printf("\t\t" "ASSERT(maxspl > 0);\n"); printf("\t\t" "float *t1 = temp;\n"); - printf("\t\t" "float *t2 = temp + %d * maxspl;\n", stages[n - 1].factor / 2); - printf("\t\t" "(void)t1;\n"); - printf("\t\t" "(void)t2;\n"); + if (stages[n - 1].factor > 4) + printf("\t\t" "float *t2 = temp + %d * maxspl;\n", stages[n - 1].factor / 2); printf("\t\t" "while (spl > 0) {\n"); printf("\t\t\t" "int curspl = (spl < maxspl) ? spl : maxspl;\n"); for (int i = 0; i < n; ++i) { @@ -294,7 +312,14 @@ static void generate_cpp_downsampler(const Stage *stages, int num_stages) printf("\t" "static int recommendedBuffer(int factor, int spl)\n"); printf("\t" "{\n"); - printf("\t\t" "return factor * spl;\n"); + printf("\t\t" "switch (factor) {\n"); + printf("\t\t" "case 2:\n"); + printf("\t\t\t" "return 0;\n"); + printf("\t\t" "case 4:\n"); + printf("\t\t\t" "return 2 * spl;\n"); + printf("\t\t" "default:\n"); + printf("\t\t\t" "return factor * spl;\n"); + printf("\t\t" "}\n"); printf("\t" "}\n"); printf("\t" "static bool canProcess(int factor)\n"); @@ -330,14 +355,25 @@ static void generate_cpp_downsampler(const Stage *stages, int num_stages) printf("\t" "}\n"); for (int n = 1; n <= num_stages; ++n) { + // special case factor=2, buffer not required + if (stages[n - 1].factor == 2) { + printf("\t" "void process2x(const float *in, float *out, int spl, float * = nullptr, int = 0)\n"); + printf("\t" "{\n"); + printf("\t\t" "down2_.process_block(out, in, spl);\n"); + printf("\t" "}\n"); + continue; + } printf("\t" "void process%dx(const float *in, float *out, int spl, float *temp, int ntemp)\n", stages[n - 1].factor); printf("\t" "{\n"); - printf("\t\t" "int maxspl = ntemp / %d;\n", stages[n - 1].factor); - printf("\t\t" "ASSERT(maxspl >= 0);\n"); + // special case factor=4, only 1 buffer required + if (stages[n - 1].factor > 4) + printf("\t\t" "int maxspl = ntemp / %d;\n", stages[n - 1].factor); + else + printf("\t\t" "int maxspl = ntemp / %d;\n", stages[n - 1].factor / 2); + printf("\t\t" "ASSERT(maxspl > 0);\n"); printf("\t\t" "float *t1 = temp;\n"); - printf("\t\t" "float *t2 = temp + %d * maxspl;\n", stages[n - 1].factor / 2); - printf("\t\t" "(void)t1;\n"); - printf("\t\t" "(void)t2;\n"); + if (stages[n - 1].factor > 4) + printf("\t\t" "float *t2 = temp + %d * maxspl;\n", stages[n - 1].factor / 2); printf("\t\t" "while (spl > 0) {\n"); printf("\t\t\t" "int curspl = (spl < maxspl) ? spl : maxspl;\n"); for (int i = 0; i < n; ++i) { diff --git a/src/sfizz/OversamplerHelpers.hxx b/src/sfizz/OversamplerHelpers.hxx index 49cf3f60..8e1bef52 100644 --- a/src/sfizz/OversamplerHelpers.hxx +++ b/src/sfizz/OversamplerHelpers.hxx @@ -1,8 +1,8 @@ -// SPDX-License-Identifier: BSD-2-Clause -// This code is part of the sfizz library and is licensed under a BSD 2-clause -// license. You should have receive a LICENSE.md file along with the code. -// If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz + + + + //------------------------------------------------------------------------------ // This is generated by the Sfizz HIIR designer @@ -85,7 +85,14 @@ public: } static int recommendedBuffer(int factor, int spl) { - return factor * spl; + switch (factor) { + case 2: + return 0; + case 4: + return 2 * spl; + default: + return factor * spl; + } } static bool canProcess(int factor) { @@ -135,30 +142,15 @@ public: break; } } - void process2x(const float *in, float *out, int spl, float *temp, int ntemp) + void process2x(const float *in, float *out, int spl, float * = nullptr, int = 0) { - int maxspl = ntemp / 2; - ASSERT(maxspl >= 0); - float *t1 = temp; - float *t2 = temp + 1 * maxspl; - (void)t1; - (void)t2; - while (spl > 0) { - int curspl = (spl < maxspl) ? spl : maxspl; - up2_.process_block(out, in, 1 * curspl); - in += curspl; - out += curspl; - spl -= curspl; - } + up2_.process_block(out, in, spl); } void process4x(const float *in, float *out, int spl, float *temp, int ntemp) { - int maxspl = ntemp / 4; - ASSERT(maxspl >= 0); + int maxspl = ntemp / 2; + ASSERT(maxspl > 0); float *t1 = temp; - float *t2 = temp + 2 * maxspl; - (void)t1; - (void)t2; while (spl > 0) { int curspl = (spl < maxspl) ? spl : maxspl; up2_.process_block(t1, in, 1 * curspl); @@ -171,11 +163,9 @@ public: void process8x(const float *in, float *out, int spl, float *temp, int ntemp) { int maxspl = ntemp / 8; - ASSERT(maxspl >= 0); + ASSERT(maxspl > 0); float *t1 = temp; float *t2 = temp + 4 * maxspl; - (void)t1; - (void)t2; while (spl > 0) { int curspl = (spl < maxspl) ? spl : maxspl; up2_.process_block(t1, in, 1 * curspl); @@ -189,11 +179,9 @@ public: void process16x(const float *in, float *out, int spl, float *temp, int ntemp) { int maxspl = ntemp / 16; - ASSERT(maxspl >= 0); + ASSERT(maxspl > 0); float *t1 = temp; float *t2 = temp + 8 * maxspl; - (void)t1; - (void)t2; while (spl > 0) { int curspl = (spl < maxspl) ? spl : maxspl; up2_.process_block(t1, in, 1 * curspl); @@ -208,11 +196,9 @@ public: void process32x(const float *in, float *out, int spl, float *temp, int ntemp) { int maxspl = ntemp / 32; - ASSERT(maxspl >= 0); + ASSERT(maxspl > 0); float *t1 = temp; float *t2 = temp + 16 * maxspl; - (void)t1; - (void)t2; while (spl > 0) { int curspl = (spl < maxspl) ? spl : maxspl; up2_.process_block(t1, in, 1 * curspl); @@ -228,11 +214,9 @@ public: void process64x(const float *in, float *out, int spl, float *temp, int ntemp) { int maxspl = ntemp / 64; - ASSERT(maxspl >= 0); + ASSERT(maxspl > 0); float *t1 = temp; float *t2 = temp + 32 * maxspl; - (void)t1; - (void)t2; while (spl > 0) { int curspl = (spl < maxspl) ? spl : maxspl; up2_.process_block(t1, in, 1 * curspl); @@ -249,11 +233,9 @@ public: void process128x(const float *in, float *out, int spl, float *temp, int ntemp) { int maxspl = ntemp / 128; - ASSERT(maxspl >= 0); + ASSERT(maxspl > 0); float *t1 = temp; float *t2 = temp + 64 * maxspl; - (void)t1; - (void)t2; while (spl > 0) { int curspl = (spl < maxspl) ? spl : maxspl; up2_.process_block(t1, in, 1 * curspl); @@ -302,7 +284,14 @@ public: } static int recommendedBuffer(int factor, int spl) { - return factor * spl; + switch (factor) { + case 2: + return 0; + case 4: + return 2 * spl; + default: + return factor * spl; + } } static bool canProcess(int factor) { @@ -352,30 +341,15 @@ public: break; } } - void process2x(const float *in, float *out, int spl, float *temp, int ntemp) + void process2x(const float *in, float *out, int spl, float * = nullptr, int = 0) { - int maxspl = ntemp / 2; - ASSERT(maxspl >= 0); - float *t1 = temp; - float *t2 = temp + 1 * maxspl; - (void)t1; - (void)t2; - while (spl > 0) { - int curspl = (spl < maxspl) ? spl : maxspl; - down2_.process_block(out, in, 1 * curspl); - in += curspl; - out += curspl; - spl -= curspl; - } + down2_.process_block(out, in, spl); } void process4x(const float *in, float *out, int spl, float *temp, int ntemp) { - int maxspl = ntemp / 4; - ASSERT(maxspl >= 0); + int maxspl = ntemp / 2; + ASSERT(maxspl > 0); float *t1 = temp; - float *t2 = temp + 2 * maxspl; - (void)t1; - (void)t2; while (spl > 0) { int curspl = (spl < maxspl) ? spl : maxspl; down4_.process_block(t1, in, 2 * curspl); @@ -388,11 +362,9 @@ public: void process8x(const float *in, float *out, int spl, float *temp, int ntemp) { int maxspl = ntemp / 8; - ASSERT(maxspl >= 0); + ASSERT(maxspl > 0); float *t1 = temp; float *t2 = temp + 4 * maxspl; - (void)t1; - (void)t2; while (spl > 0) { int curspl = (spl < maxspl) ? spl : maxspl; down8_.process_block(t1, in, 4 * curspl); @@ -406,11 +378,9 @@ public: void process16x(const float *in, float *out, int spl, float *temp, int ntemp) { int maxspl = ntemp / 16; - ASSERT(maxspl >= 0); + ASSERT(maxspl > 0); float *t1 = temp; float *t2 = temp + 8 * maxspl; - (void)t1; - (void)t2; while (spl > 0) { int curspl = (spl < maxspl) ? spl : maxspl; down16_.process_block(t1, in, 8 * curspl); @@ -425,11 +395,9 @@ public: void process32x(const float *in, float *out, int spl, float *temp, int ntemp) { int maxspl = ntemp / 32; - ASSERT(maxspl >= 0); + ASSERT(maxspl > 0); float *t1 = temp; float *t2 = temp + 16 * maxspl; - (void)t1; - (void)t2; while (spl > 0) { int curspl = (spl < maxspl) ? spl : maxspl; down32_.process_block(t1, in, 16 * curspl); @@ -445,11 +413,9 @@ public: void process64x(const float *in, float *out, int spl, float *temp, int ntemp) { int maxspl = ntemp / 64; - ASSERT(maxspl >= 0); + ASSERT(maxspl > 0); float *t1 = temp; float *t2 = temp + 32 * maxspl; - (void)t1; - (void)t2; while (spl > 0) { int curspl = (spl < maxspl) ? spl : maxspl; down64_.process_block(t1, in, 32 * curspl); @@ -466,11 +432,9 @@ public: void process128x(const float *in, float *out, int spl, float *temp, int ntemp) { int maxspl = ntemp / 128; - ASSERT(maxspl >= 0); + ASSERT(maxspl > 0); float *t1 = temp; float *t2 = temp + 64 * maxspl; - (void)t1; - (void)t2; while (spl > 0) { int curspl = (spl < maxspl) ? spl : maxspl; down128_.process_block(t1, in, 64 * curspl); From 25dd348191372f91079834c91a8ab3c82c5276ed Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Mon, 22 Feb 2021 02:27:46 +0100 Subject: [PATCH 8/9] Deduplication of hiir coefs in effects --- src/sfizz/effects/Compressor.cpp | 11 +++++------ src/sfizz/effects/Compressor.h | 2 -- src/sfizz/effects/Gate.cpp | 11 +++++------ src/sfizz/effects/Gate.h | 2 -- src/sfizz/effects/Limiter.cpp | 6 ++---- src/sfizz/effects/Limiter.h | 7 +++---- src/sfizz/effects/Lofi.cpp | 6 ++---- src/sfizz/effects/Lofi.h | 6 +++--- src/sfizz/effects/Rectify.cpp | 6 ++---- src/sfizz/effects/Rectify.h | 7 +++---- 10 files changed, 25 insertions(+), 39 deletions(-) diff --git a/src/sfizz/effects/Compressor.cpp b/src/sfizz/effects/Compressor.cpp index 8fffd680..e5a0c2e5 100644 --- a/src/sfizz/effects/Compressor.cpp +++ b/src/sfizz/effects/Compressor.cpp @@ -20,6 +20,7 @@ #include "Opcode.h" #include "AudioSpan.h" #include "MathHelpers.h" +#include "OversamplerHelpers.h" #include "absl/memory/memory.h" static constexpr int _oversampling = 2; @@ -35,8 +36,8 @@ namespace fx { float _inputGain { Default::compGain }; AudioBuffer _tempBuffer2x { 2, _oversampling * config::defaultSamplesPerBlock }; AudioBuffer _gain2x { 2, _oversampling * config::defaultSamplesPerBlock }; - hiir::Downsampler2xFpu<12> _downsampler2x[EffectChannels]; - hiir::Upsampler2xFpu<12> _upsampler2x[EffectChannels]; + hiir::Downsampler2x<12> _downsampler2x[EffectChannels]; + hiir::Upsampler2x<12> _upsampler2x[EffectChannels]; #define DEFINE_SET_GET(type, ident, name, var, def, min, max, step) \ float get_##ident(size_t i) const noexcept { return _compressor[i].var; } \ @@ -65,11 +66,9 @@ namespace fx { comp.instanceConstants(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 }; - for (unsigned c = 0; c < EffectChannels; ++c) { - impl._downsampler2x[c].set_coefs(coefs2x); - impl._upsampler2x[c].set_coefs(coefs2x); + impl._downsampler2x[c].set_coefs(OSCoeffs2x); + impl._upsampler2x[c].set_coefs(OSCoeffs2x); } clear(); diff --git a/src/sfizz/effects/Compressor.h b/src/sfizz/effects/Compressor.h index ed2b0e5d..531803a8 100644 --- a/src/sfizz/effects/Compressor.h +++ b/src/sfizz/effects/Compressor.h @@ -6,8 +6,6 @@ #pragma once #include "Effects.h" -#include "hiir/Downsampler2xFpu.h" -#include "hiir/Upsampler2xFpu.h" #include namespace sfz { diff --git a/src/sfizz/effects/Gate.cpp b/src/sfizz/effects/Gate.cpp index 7bbc158a..b97ffd4a 100644 --- a/src/sfizz/effects/Gate.cpp +++ b/src/sfizz/effects/Gate.cpp @@ -23,6 +23,7 @@ #include "Opcode.h" #include "AudioSpan.h" #include "MathHelpers.h" +#include "OversamplerHelpers.h" #include "absl/memory/memory.h" static constexpr int _oversampling = 2; @@ -38,8 +39,8 @@ namespace fx { float _inputGain = 1.0; AudioBuffer _tempBuffer2x { 2, _oversampling * config::defaultSamplesPerBlock }; AudioBuffer _gain2x { 2, _oversampling * config::defaultSamplesPerBlock }; - hiir::Downsampler2xFpu<12> _downsampler2x[EffectChannels]; - hiir::Upsampler2xFpu<12> _upsampler2x[EffectChannels]; + hiir::Downsampler2x<12> _downsampler2x[EffectChannels]; + hiir::Upsampler2x<12> _upsampler2x[EffectChannels]; #define DEFINE_SET_GET(type, ident, name, var, def, min, max, step) \ float get_##ident(size_t i) const noexcept { return _gate[i].var; } \ @@ -68,11 +69,9 @@ namespace fx { gate.instanceConstants(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 }; - for (unsigned c = 0; c < EffectChannels; ++c) { - impl._downsampler2x[c].set_coefs(coefs2x); - impl._upsampler2x[c].set_coefs(coefs2x); + impl._downsampler2x[c].set_coefs(OSCoeffs2x); + impl._upsampler2x[c].set_coefs(OSCoeffs2x); } clear(); diff --git a/src/sfizz/effects/Gate.h b/src/sfizz/effects/Gate.h index bfe3ccfd..c461b9e7 100644 --- a/src/sfizz/effects/Gate.h +++ b/src/sfizz/effects/Gate.h @@ -6,8 +6,6 @@ #pragma once #include "Effects.h" -#include "hiir/Downsampler2xFpu.h" -#include "hiir/Upsampler2xFpu.h" #include namespace sfz { diff --git a/src/sfizz/effects/Limiter.cpp b/src/sfizz/effects/Limiter.cpp index 9c28ce73..4560ebbd 100644 --- a/src/sfizz/effects/Limiter.cpp +++ b/src/sfizz/effects/Limiter.cpp @@ -36,11 +36,9 @@ namespace fx { _limiter->classInit(sampleRate); _limiter->instanceConstants(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 }; - for (unsigned c = 0; c < EffectChannels; ++c) { - _downsampler2x[c].set_coefs(coefs2x); - _upsampler2x[c].set_coefs(coefs2x); + _downsampler2x[c].set_coefs(OSCoeffs2x); + _upsampler2x[c].set_coefs(OSCoeffs2x); } clear(); diff --git a/src/sfizz/effects/Limiter.h b/src/sfizz/effects/Limiter.h index 30737377..e32cc9a9 100644 --- a/src/sfizz/effects/Limiter.h +++ b/src/sfizz/effects/Limiter.h @@ -6,8 +6,7 @@ #pragma once #include "Effects.h" -#include "hiir/Downsampler2xFpu.h" -#include "hiir/Upsampler2xFpu.h" +#include "OversamplerHelpers.h" class faustLimiter; namespace sfz { @@ -50,8 +49,8 @@ namespace fx { private: std::unique_ptr _limiter; AudioBuffer _tempBuffer2x { 2, 2 * config::defaultSamplesPerBlock }; - hiir::Downsampler2xFpu<12> _downsampler2x[EffectChannels]; - hiir::Upsampler2xFpu<12> _upsampler2x[EffectChannels]; + hiir::Downsampler2x<12> _downsampler2x[EffectChannels]; + hiir::Upsampler2x<12> _upsampler2x[EffectChannels]; }; } // namespace fx diff --git a/src/sfizz/effects/Lofi.cpp b/src/sfizz/effects/Lofi.cpp index 24aaf772..591be3a9 100644 --- a/src/sfizz/effects/Lofi.cpp +++ b/src/sfizz/effects/Lofi.cpp @@ -101,8 +101,7 @@ namespace fx { { (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); + fDownsampler2x.set_coefs(OSCoeffs2x); } void Lofi::Bitred::clear() @@ -153,8 +152,7 @@ namespace fx { { fSampleTime = 1.0f / static_cast(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); + fDownsampler2x.set_coefs(OSCoeffs2x); } void Lofi::Decim::clear() diff --git a/src/sfizz/effects/Lofi.h b/src/sfizz/effects/Lofi.h index 9b97c6e3..54cfa12e 100644 --- a/src/sfizz/effects/Lofi.h +++ b/src/sfizz/effects/Lofi.h @@ -6,7 +6,7 @@ #pragma once #include "Effects.h" -#include "hiir/Downsampler2xFpu.h" +#include "OversamplerHelpers.h" namespace sfz { namespace fx { @@ -57,7 +57,7 @@ namespace fx { private: float fDepth = 0.0; float fLastValue = 0.0; - hiir::Downsampler2xFpu<12> fDownsampler2x; + hiir::Downsampler2x<12> fDownsampler2x; }; /// @@ -73,7 +73,7 @@ namespace fx { float fDepth = 0.0; float fPhase = 0.0; float fLastValue = 0.0; - hiir::Downsampler2xFpu<12> fDownsampler2x; + hiir::Downsampler2x<12> fDownsampler2x; }; /// diff --git a/src/sfizz/effects/Rectify.cpp b/src/sfizz/effects/Rectify.cpp index 5d25be46..dbc92997 100644 --- a/src/sfizz/effects/Rectify.cpp +++ b/src/sfizz/effects/Rectify.cpp @@ -28,11 +28,9 @@ namespace fx { { (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 }; - for (unsigned c = 0; c < EffectChannels; ++c) { - _downsampler2x[c].set_coefs(coefs2x); - _upsampler2x[c].set_coefs(coefs2x); + _downsampler2x[c].set_coefs(OSCoeffs2x); + _upsampler2x[c].set_coefs(OSCoeffs2x); } } diff --git a/src/sfizz/effects/Rectify.h b/src/sfizz/effects/Rectify.h index b6c27a84..7497a7c7 100644 --- a/src/sfizz/effects/Rectify.h +++ b/src/sfizz/effects/Rectify.h @@ -6,8 +6,7 @@ #pragma once #include "Effects.h" -#include "hiir/Downsampler2xFpu.h" -#include "hiir/Upsampler2xFpu.h" +#include "OversamplerHelpers.h" namespace sfz { namespace fx { @@ -47,8 +46,8 @@ namespace fx { private: AudioBuffer _tempBuffer { 1, config::defaultSamplesPerBlock }; - hiir::Downsampler2xFpu<12> _downsampler2x[2]; - hiir::Upsampler2xFpu<12> _upsampler2x[2]; + hiir::Downsampler2x<12> _downsampler2x[2]; + hiir::Upsampler2x<12> _upsampler2x[2]; float _amount = 0; bool _full = false; From 5f93729b538ae44cfbd872f5560870d6eee13fa3 Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Mon, 22 Feb 2021 02:33:48 +0100 Subject: [PATCH 9/9] Restore the license header --- src/sfizz/OversamplerHelpers.hxx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/sfizz/OversamplerHelpers.hxx b/src/sfizz/OversamplerHelpers.hxx index 8e1bef52..458cdd87 100644 --- a/src/sfizz/OversamplerHelpers.hxx +++ b/src/sfizz/OversamplerHelpers.hxx @@ -1,8 +1,8 @@ +// SPDX-License-Identifier: BSD-2-Clause - - - - +// This code is part of the sfizz library and is licensed under a BSD 2-clause +// license. You should have receive a LICENSE.md file along with the code. +// If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz //------------------------------------------------------------------------------ // This is generated by the Sfizz HIIR designer