Merge pull request #640 from jpcima/oversample

Oversampler update
This commit is contained in:
JP Cimalando 2021-02-22 03:12:09 +01:00 committed by GitHub
commit 21294e7d4a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
19 changed files with 1575 additions and 227 deletions

View file

@ -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"

View file

@ -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)

403
devtools/HIIRDesigner.cpp Normal file
View file

@ -0,0 +1,403 @@
// 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 <hiir/PolyphaseIir2Designer.h>
#include <vector>
#include <memory>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
using FD = hiir::PolyphaseIir2Designer;
struct Stage {
int factor;
double tbw;
int nbr_coefs;
std::unique_ptr<double[]> coefs;
};
static std::vector<Stage> 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<Stage> 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<Stage> calculate_stages(int oversampling, double attenuation, double transition)
{
std::vector<Stage> 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("//------------------------------------------------------------------------------\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");
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" "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");
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) {
// 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");
// 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");
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) {
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" "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");
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) {
// 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");
// 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");
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) {
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");
}

View file

@ -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 <cassert>
#include <cmath>
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 \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/

View file

@ -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 \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/

View file

@ -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 <class T, std::size_t A = sfz::config::defaultAlignment>
using aligned_vector = std::vector<T, jsl::aligned_allocator<T, A>>;
constexpr std::array<double, 12> 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<double, 4> coeffsStage4x {
0.042448989488488006,
0.17072114107630679,
0.39329183835224008,
0.74569514831986694
};
constexpr std::array<double, 3> coeffsStage8x {
0.055748680811302048,
0.24305119574153092,
0.6466991311926823
};
#if SFIZZ_HAVE_SSE
#include "hiir/Upsampler2xSse.h"
using Upsampler2x = hiir::Upsampler2xSse<coeffsStage2x.size()>;
using Upsampler4x = hiir::Upsampler2xSse<coeffsStage4x.size()>;
using Upsampler8x = hiir::Upsampler2xSse<coeffsStage8x.size()>;
#elif SFIZZ_HAVE_NEON
#include "hiir/Upsampler2xNeon.h"
using Upsampler2x = hiir::Upsampler2xNeon<coeffsStage2x.size()>;
using Upsampler4x = hiir::Upsampler2xNeon<coeffsStage4x.size()>;
using Upsampler8x = hiir::Upsampler2xNeon<coeffsStage8x.size()>;
#else
#include "hiir/Upsampler2xFpu.h"
using Upsampler2x = hiir::Upsampler2xFpu<coeffsStage2x.size()>;
using Upsampler4x = hiir::Upsampler2xFpu<coeffsStage4x.size()>;
using Upsampler8x = hiir::Upsampler2xFpu<coeffsStage8x.size()>;
#endif
sfz::Oversampler::Oversampler(sfz::Oversampling factor, size_t chunkSize)
: factor(factor), chunkSize(chunkSize)
{
@ -74,36 +29,10 @@ void sfz::Oversampler::stream(AudioSpan<float> input, AudioSpan<float> output, s
const auto numFrames = input.getNumFrames();
const auto numChannels = input.getNumChannels();
aligned_vector<Upsampler2x> upsampler2x;
aligned_vector<Upsampler4x> upsampler4x;
aligned_vector<Upsampler8x> upsampler8x;
aligned_vector<Upsampler> 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<float> buffer1 { chunkSize * 2 };
sfz::Buffer<float> buffer2 { chunkSize * 4 };
auto span1 = absl::MakeSpan(buffer1);
auto span2 = absl::MakeSpan(buffer2);
// Intermediate buffer
sfz::Buffer<float> temp { std::max<size_t>(128, Upsampler::recommendedBuffer(16, chunkSize)) };
size_t inputFrameCounter { 0 };
size_t outputFrameCounter { 0 };
@ -115,23 +44,10 @@ void sfz::Oversampler::stream(AudioSpan<float> input, AudioSpan<float> 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<float>(inputChunk, outputChunk);
break;
case Oversampling::x2:
upsampler2x[chanIdx].process_block(outputChunk.data(), inputChunk.data(), static_cast<long>(thisChunkSize));
break;
case Oversampling::x4:
upsampler2x[chanIdx].process_block(span1.data(), inputChunk.data(), static_cast<long>(thisChunkSize));
upsampler4x[chanIdx].process_block(outputChunk.data(), span1.data(), static_cast<long>(thisChunkSize * 2));
break;
case Oversampling::x8:
upsampler2x[chanIdx].process_block(span1.data(), inputChunk.data(), static_cast<long>(thisChunkSize));
upsampler4x[chanIdx].process_block(span2.data(), span1.data(), static_cast<long>(thisChunkSize * 2));
upsampler8x[chanIdx].process_block(outputChunk.data(), span2.data(), static_cast<long>(thisChunkSize * 4));
break;
}
upsampler[chanIdx].process(
static_cast<int>(factor),
inputChunk.data(), outputChunk.data(), static_cast<int>(inputChunk.size()),
temp.data(), static_cast<int>(temp.size()));
}
inputFrameCounter += thisChunkSize;
outputFrameCounter += outputChunkSize;
@ -149,47 +65,18 @@ void sfz::Oversampler::stream(AudioReader& input, AudioSpan<float> output, std::
const auto numFrames = static_cast<size_t>(input.frames());
const auto numChannels = input.channels();
aligned_vector<Upsampler2x> upsampler2x;
aligned_vector<Upsampler4x> upsampler4x;
aligned_vector<Upsampler8x> 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> upsampler(numChannels);
// Intermediate buffers
sfz::Buffer<float> fileBlock { chunkSize * numChannels };
sfz::Buffer<float> buffer1 { chunkSize * 2 };
sfz::Buffer<float> buffer2 { chunkSize * 4 };
auto span1 = absl::MakeSpan(buffer1);
auto span2 = absl::MakeSpan(buffer2);
sfz::Buffer<float> channelBlock { chunkSize };
sfz::Buffer<float> temp { std::max<size_t>(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<float> 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<long>(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<long>(thisChunkSize * 2));
upsampler8x[chanIdx].process_block(outputChunk.data(), span2.data(), static_cast<long>(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<int>(factor),
channelBlock.data(), outputChunk.data(), static_cast<int>(thisChunkSize),
temp.data(), static_cast<int>(temp.size()));
}
}
inputFrameCounter += thisChunkSize;

View file

@ -0,0 +1,50 @@
#pragma once
#include "SIMDConfig.h"
#include "Config.h"
#include "Debug.h"
#include <type_traits>
#include <cstring>
namespace sfz {
class Upsampler;
class Downsampler;
} // namespace sfz
#include <hiir/Upsampler2xFpu.h>
#include <hiir/Downsampler2xFpu.h>
// Note: according to HIIR documentation, FPU versions are
// more efficient than SIMD below 12 coefficients.
#if SFIZZ_HAVE_SSE
#include <hiir/Upsampler2xSse.h>
#include <hiir/Downsampler2xSse.h>
namespace hiir {
template <int NC> using Upsampler2x = typename std::conditional<NC >= 12,
hiir::Upsampler2xSse<NC>, hiir::Upsampler2xFpu<NC>>::type;
template <int NC> using Downsampler2x = typename std::conditional<NC >= 12,
hiir::Downsampler2xSse<NC>, hiir::Downsampler2xFpu<NC>>::type;
} // namespace hiir
#elif SFIZZ_HAVE_NEON
#include <hiir/Upsampler2xNeon.h>
#include <hiir/Downsampler2xNeon.h>
namespace hiir {
template <int NC> using Upsampler2x = typename std::conditional<NC >= 12,
hiir::Upsampler2xNeon<NC>, hiir::Upsampler2xFpu<NC>>::type;
template <int NC> using Downsampler2x = typename std::conditional<NC >= 12,
hiir::Downsampler2xNeon<NC>, hiir::Downsampler2xFpu<NC>>::type;
} // namespace hiir
#else
namespace hiir {
template <int NC> using Upsampler2x = hiir::Upsampler2xFpu<NC>;
template <int NC> using Downsampler2x = hiir::Downsampler2xFpu<NC>;
} // namespace hiir
#endif
#include "OversamplerHelpers.hxx"

View file

@ -0,0 +1,462 @@
// 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)
{
switch (factor) {
case 2:
return 0;
case 4:
return 2 * spl;
default:
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 * = nullptr, int = 0)
{
up2_.process_block(out, in, spl);
}
void process4x(const float *in, float *out, int spl, float *temp, int ntemp)
{
int maxspl = ntemp / 2;
ASSERT(maxspl > 0);
float *t1 = temp;
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;
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;
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;
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;
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;
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)
{
switch (factor) {
case 2:
return 0;
case 4:
return 2 * spl;
default:
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 * = nullptr, int = 0)
{
down2_.process_block(out, in, spl);
}
void process4x(const float *in, float *out, int spl, float *temp, int ntemp)
{
int maxspl = ntemp / 2;
ASSERT(maxspl > 0);
float *t1 = temp;
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;
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;
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;
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;
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;
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

View file

@ -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<float, 2> _tempBuffer2x { 2, _oversampling * config::defaultSamplesPerBlock };
AudioBuffer<float, 2> _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();

View file

@ -6,8 +6,6 @@
#pragma once
#include "Effects.h"
#include "hiir/Downsampler2xFpu.h"
#include "hiir/Upsampler2xFpu.h"
#include <memory>
namespace sfz {

View file

@ -22,8 +22,7 @@
#include "Opcode.h"
#include "Config.h"
#include "MathHelpers.h"
#include <hiir/Upsampler2xFpu.h>
#include <hiir/Downsampler2xFpu.h>
#include "OversamplerHelpers.h"
#include <absl/types/span.h>
#include <cmath>
@ -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<float[]> _temp8x[2];
sfz::Upsampler _upsampler[EffectChannels];
sfz::Downsampler _downsampler[EffectChannels];
std::unique_ptr<float[]> _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<float[]>& temp : impl._temp8x)
temp.reset(new float[8 * samplesPerBlock]);
for (std::unique_ptr<float[]>& 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<float> temp[2] = {
absl::Span<float>(impl._temp8x[0].get(), 8 * nframes),
absl::Span<float>(impl._temp8x[1].get(), 8 * nframes),
absl::Span<float>(impl._temp[0].get(), _oversampling * nframes),
absl::Span<float>(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<int>(temp[1].size()));
absl::Span<float> 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<int>(temp[1].size()));
// dry/wet mix
absl::Span<float> mixOut(outputs[c], nframes);

View file

@ -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<float, 2> _tempBuffer2x { 2, _oversampling * config::defaultSamplesPerBlock };
AudioBuffer<float, 2> _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();

View file

@ -6,8 +6,6 @@
#pragma once
#include "Effects.h"
#include "hiir/Downsampler2xFpu.h"
#include "hiir/Upsampler2xFpu.h"
#include <memory>
namespace sfz {

View file

@ -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();

View file

@ -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<faustLimiter> _limiter;
AudioBuffer<float, 2> _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

View file

@ -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<float>(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()

View file

@ -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;
};
///

View file

@ -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);
}
}

View file

@ -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<float, 1> _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;