This commit is contained in:
paul 2019-08-09 18:24:27 +02:00
parent 4f998cad8f
commit 8af7b7bfa7
23 changed files with 611 additions and 18 deletions

4
.gitignore vendored
View file

@ -1,4 +1,6 @@
build
build-arm
build-subl
.vscode
.vscode
perf.data
perf.data.old

6
.gitmodules vendored
View file

@ -16,3 +16,9 @@
[submodule "external/readerwriterqueue"]
path = external/readerwriterqueue
url = https://github.com/cameron314/readerwriterqueue.git
[submodule "external/gsl-lite"]
path = external/gsl-lite
url = https://github.com/martinmoene/gsl-lite.git
[submodule "external/cnpy"]
path = external/cnpy
url = https://github.com/rogersce/cnpy.git

View file

@ -17,7 +17,6 @@ if (UNIX)
add_compile_options(-Wextra)
# add_compile_options(-fno-exceptions)
add_compile_options(-ffast-math)
add_compile_options(-fno-rtti)
# add_compile_options(-fno-omit-frame-pointer)
endif()
@ -47,7 +46,9 @@ add_subdirectory(external/abseil-cpp)
add_subdirectory(external/spdlog)
add_subdirectory(external/Catch2)
add_subdirectory(external/cxxopts)
add_subdirectory(external/gsl-lite)
add_subdirectory(external/benchmark)
add_subdirectory(external/cnpy)
# Download libsndfile
if (WIN32)
@ -106,6 +107,7 @@ set(TEST_SOURCES
tests/BufferT.cpp
tests/StereoBufferT.cpp
tests/FilesT.cpp
tests/OnePoleFilterT.cpp
tests/MainT.cpp
)
@ -121,8 +123,12 @@ target_link_libraries(sfzprint absl::strings cxxopts)
###############################
# Basic command line program
add_executable(sfizz sources/Main.cpp ${COMMON_SOURCES})
if (HAVE_INTRIN_H OR HAVE_X86INTRIN_H)
target_sources(sfizz PRIVATE ${SSE_SOURCES})
endif()
# Per OS properties
if(UNIX)
target_compile_options(sfizz PRIVATE -fno-rtti)
target_link_libraries(sfizz stdc++fs)
endif(UNIX)
target_link_libraries(sfizz absl::strings cxxopts sndfile absl::flat_hash_map readerwriterqueue)
@ -131,33 +137,37 @@ target_link_libraries(sfizz absl::strings cxxopts sndfile absl::flat_hash_map re
# Test application
add_executable(sfizz_tests ${TEST_SOURCES} ${COMMON_SOURCES})
# Per OS properties
if (HAVE_INTRIN_H OR HAVE_X86INTRIN_H)
target_sources(sfizz_tests PRIVATE ${SSE_SOURCES})
endif()
if(UNIX)
target_link_libraries(sfizz_tests stdc++fs)
target_link_libraries(sfizz_tests stdc++fs)
endif(UNIX)
target_link_libraries(sfizz_tests Catch2::Catch2 absl::strings absl::flat_hash_map sndfile readerwriterqueue)
target_link_libraries(sfizz_tests Catch2::Catch2 absl::strings absl::flat_hash_map sndfile readerwriterqueue cnpy-static gsl::gsl-lite)
target_include_directories(sfizz_tests SYSTEM PRIVATE sources)
file(COPY "tests" DESTINATION ${CMAKE_BINARY_DIR})
###############################
add_executable(bench_fill benchmarks/Stereo_Fill.cpp ${COMMON_SOURCES})
add_executable(bench_fill benchmarks/Stereo_Fill.cpp sources/StereoBufferSSE.cpp)
# Per OS properties
if(UNIX)
target_link_libraries(bench_fill stdc++fs)
endif(UNIX)
target_link_libraries(bench_fill absl::strings benchmark)
target_link_libraries(bench_fill benchmark)
###############################
add_executable(bench_read benchmarks/Stereo_Read_Interleaved.cpp sources/StereoBufferSSE.cpp)
# Per OS properties
if(UNIX)
target_link_libraries(bench_read stdc++fs)
endif(UNIX)
target_link_libraries(bench_read absl::strings benchmark)
target_link_libraries(bench_read benchmark)
###############################
add_executable(bench_write benchmarks/Stereo_Write_Interleaved.cpp sources/StereoBufferSSE.cpp)
# Per OS properties
if(UNIX)
target_link_libraries(bench_write stdc++fs)
endif(UNIX)
target_link_libraries(bench_write absl::strings benchmark)
target_link_libraries(bench_write benchmark)
###############################
add_executable(bench_span benchmarks/Spans.cpp)
# Per OS properties
target_link_libraries(bench_span benchmark gsl::gsl-lite absl::span)
###############################
add_executable(bench_opf_high_vs_low benchmarks/OPF_high_vs_low.cpp)
# Per OS properties
target_link_libraries(bench_opf_high_vs_low benchmark gsl::gsl-lite)

View file

@ -0,0 +1,168 @@
#include <benchmark/benchmark.h>
#include "gsl/gsl-lite.hpp"
#include <random>
#include <algorithm>
#include "../sources/Intrinsics.h"
constexpr float filterGain { 0.25f };
void lowpass(gsl::span<const float> input, gsl::span<float> lowpass, float gain)
{
float state = 0.0f;
float intermediate;
const auto G = gain / (1 - gain);
for (auto [in, out] = std::make_pair(input.begin(), lowpass.begin());
in < input.end() && out < lowpass.end();
in++, out++)
{
intermediate = G * (*in - state);
*out = intermediate + state;
state = *out + intermediate;
}
}
void highpass(gsl::span<const float> input, gsl::span<float> highpass, float gain)
{
float state = 0.0f;
float intermediate;
const auto G = gain / (1 - gain);
for (auto [in, out] = std::make_pair(input.begin(), highpass.begin());
in < input.end() && out < highpass.end();
in++, out++)
{
intermediate = G * (*in - state);
*out = *in - intermediate - state;
state += 2*intermediate;
}
}
void highpass_foreach(gsl::span<const float> input, gsl::span<float> highpass, float gain)
{
lowpass(input, highpass, gain);
for (auto [in, out] = std::make_pair(input.begin(), highpass.begin());
in < input.end() && out < highpass.end();
in++, out++)
*out = *in - *out;
}
void highpass_raw(gsl::span<const float> input, gsl::span<float> highpass, float gain)
{
lowpass(input, highpass, gain);
auto in = input.data();
auto out = highpass.data();
while (in < input.end() && out < highpass.end())
{
*out = *in - *out;
out++;
in++;
}
}
void highpass_sse(gsl::span<const float> input, gsl::span<float> highpass, float gain)
{
lowpass(input, highpass, gain);
auto in = input.data();
auto out = highpass.data();
constexpr int FloatAlignment { 4 };
const auto inputAlignedEnd = input.data() + (input.size() - (input.size() & (FloatAlignment - 1)));
const auto outputAlignedEnd = highpass.data() + (highpass.size() - (highpass.size() & (FloatAlignment - 1)));
while (in < inputAlignedEnd && out < outputAlignedEnd)
{
const auto inputRegister = _mm_loadu_ps(in);
auto outputRegister = _mm_loadu_ps(out);
outputRegister = _mm_sub_ps(inputRegister, outputRegister);
_mm_storeu_ps(out, outputRegister);
in += 4;
out += 4;
}
while (in < input.end() && out < highpass.end())
{
*out = *in - *out;
out++;
in++;
}
}
static void Low(benchmark::State& state) {
std::vector<float> input(state.range(0));
std::vector<float> output(state.range(0));
std::random_device rd { };
std::mt19937 gen { rd() };
std::normal_distribution<float> dist { };
std::generate(input.begin(), input.end(), [&]() {
return dist(gen);
});
for (auto _ : state)
lowpass(input, output, filterGain);
}
static void High(benchmark::State& state) {
std::vector<float> input(state.range(0));
std::vector<float> output(state.range(0));
std::random_device rd { };
std::mt19937 gen { rd() };
std::normal_distribution<float> dist { };
std::generate(input.begin(), input.end(), [&]() {
return dist(gen);
});
for (auto _ : state)
highpass(input, output, filterGain);
}
static void High_ForEach(benchmark::State& state) {
std::vector<float> input(state.range(0));
std::vector<float> output(state.range(0));
std::random_device rd { };
std::mt19937 gen { rd() };
std::normal_distribution<float> dist { };
std::generate(input.begin(), input.end(), [&]() {
return dist(gen);
});
for (auto _ : state)
highpass_foreach(input, output, filterGain);
}
static void High_Raw(benchmark::State& state) {
std::vector<float> input(state.range(0));
std::vector<float> output(state.range(0));
std::random_device rd { };
std::mt19937 gen { rd() };
std::normal_distribution<float> dist { };
std::generate(input.begin(), input.end(), [&]() {
return dist(gen);
});
for (auto _ : state)
highpass_raw(input, output, filterGain);
}
static void High_SSE(benchmark::State& state) {
std::vector<float> input(state.range(0));
std::vector<float> output(state.range(0));
std::random_device rd { };
std::mt19937 gen { rd() };
std::normal_distribution<float> dist { };
std::generate(input.begin(), input.end(), [&]() {
return dist(gen);
});
for (auto _ : state)
highpass_sse(input, output, filterGain);
}
BENCHMARK(Low)->RangeMultiplier(2)->Range((2<<5), (2<<10));
BENCHMARK(High)->RangeMultiplier(2)->Range((2<<5), (2<<10));
BENCHMARK(High_ForEach)->RangeMultiplier(2)->Range((2<<5), (2<<10));
BENCHMARK(High_Raw)->RangeMultiplier(2)->Range((2<<5), (2<<10));
BENCHMARK(High_SSE)->RangeMultiplier(2)->Range((2<<5), (2<<10));
BENCHMARK_MAIN();

111
benchmarks/Spans.cpp Normal file
View file

@ -0,0 +1,111 @@
#include <benchmark/benchmark.h>
#include "gsl/gsl-lite.hpp"
#include "absl/types/span.h"
#include <random>
#include <algorithm>
constexpr float filterGain { 0.25f };
void processRaw(float* input, float* lowpass, float gain, int numSamples)
{
const auto end = input + numSamples;
float state = 0.0f;
float intermediate;
const auto G = gain / (1 - gain);
while (input < end)
{
intermediate = G * (*input++ - state);
*lowpass = intermediate + state;
state = *lowpass++ + intermediate;
}
}
void processGSLSpan(gsl::span<const float> input, gsl::span<float> lowpass, float gain)
{
float state = 0.0f;
float intermediate;
const auto G = gain / (1 - gain);
for (auto [in, out] = std::make_pair(input.begin(), lowpass.begin());
in < input.end() && out < lowpass.end();
in++, out++)
{
intermediate = G * (*in - state);
*out = intermediate + state;
state = *out + intermediate;
}
}
void processABSLSpan(absl::Span<const float> input, absl::Span<float> lowpass, float gain)
{
float state = 0.0f;
float intermediate;
const auto G = gain / (1 - gain);
for (auto [in, out] = std::make_pair(input.begin(), lowpass.begin());
in < input.end() && out < lowpass.end();
in++, out++)
{
intermediate = G * (*in - state);
*out = intermediate + state;
state = *out + intermediate;
}
}
static void Raw(benchmark::State& state) {
std::vector<float> input(state.range(0));
std::vector<float> output(state.range(0));
std::random_device rd { };
std::mt19937 gen { rd() };
std::normal_distribution<float> dist { };
std::generate(input.begin(), input.end(), [&]() {
return dist(gen);
});
for (auto _ : state)
{
processRaw(input.data(), output.data(), filterGain, state.range(0));
}
}
static void GSLSpan(benchmark::State& state) {
std::vector<float> input(state.range(0));
std::vector<float> output(state.range(0));
std::random_device rd { };
std::mt19937 gen { rd() };
std::normal_distribution<float> dist { };
std::generate(input.begin(), input.end(), [&]() {
return dist(gen);
});
for (auto _ : state)
{
processGSLSpan(input, output, filterGain);
}
}
static void ABSLSpan(benchmark::State& state) {
std::vector<float> input(state.range(0));
std::vector<float> output(state.range(0));
std::random_device rd { };
std::mt19937 gen { rd() };
std::normal_distribution<float> dist { };
std::generate(input.begin(), input.end(), [&]() {
return dist(gen);
});
for (auto _ : state)
{
processABSLSpan(input, absl::MakeSpan(output), filterGain);
}
}
// Register the function as a benchmark
BENCHMARK(Raw)->RangeMultiplier(2)->Range((2<<6), (2<<12));
BENCHMARK(GSLSpan)->RangeMultiplier(2)->Range((2<<6), (2<<12));
BENCHMARK(ABSLSpan)->RangeMultiplier(2)->Range((2<<6), (2<<12));
BENCHMARK_MAIN();

1
external/cnpy vendored Submodule

@ -0,0 +1 @@
Subproject commit 4e8810b1a8637695171ed346ce68f6984e585ef4

1
external/gsl-lite vendored Submodule

@ -0,0 +1 @@
Subproject commit df36f6378336280043d7e06d8013e6fa4af6abd7

View file

@ -0,0 +1,35 @@
#include "StateVariableFilter.h"
#include <tuple>
// template<>
// template<>
// auto StateVariableFilter<SVFReturn::BP>::process<float>(const float& input [[maybe_unused]])
// {
// return 0.0;
// }
// template<>
// template<>
// auto StateVariableFilter<SVFReturn::BP_LP>::process<float>(const float& input [[maybe_unused]])
// {
// return std::make_pair<float, float>(0.0, 0.0);
// }
// template<>
// template<>
// auto StateVariableFilter<SVFReturn::BP_LP_HP>::process<float>(const float& input [[maybe_unused]])
// {
// return std::make_tuple<float, float, float>(0.0, 0.0, 0.0);
// }
template<>
auto StateVariableFilter::process(const float& input [[maybe_unused]], float& bandpass [[maybe_unused]])
{
}
template<>
auto StateVariableFilter::process(const float& input [[maybe_unused]], float& bandpass [[maybe_unused]], float& lowpass [[maybe_unused]])
{
}

27
old/StateVariableFilter.h Normal file
View file

@ -0,0 +1,27 @@
#pragma once
#include "Globals.h"
#include <tuple>
#include <array>
// enum class SVFReturn { BP_LP_HP, BP_LP, BP};
// template<SVFReturn ReturnType = SVFReturn::BP>
class StateVariableFilter
{
public:
StateVariableFilter() = default;
StateVariableFilter(float R, float gain)
: R(R), gain(gain)
{
}
// Only use specializations
template<class InputType, class... OutputTypes>
auto process(const InputType& input, OutputTypes... outputs) = delete;
private:
std::array<float, 2> state;
float R { 0.0 };
float gain { 1.0 };
};

View file

@ -0,0 +1,36 @@
#include "StateVariableFilter.h"
#include <tuple>
#include "Intrinsics.h"
// template<>
// template<>
// auto StateVariableFilter<SVFReturn::BP>::process<__m128>(const __m128& input [[maybe_unused]])
// {
// return 0.0;
// }
// template<>
// template<>
// auto StateVariableFilter<SVFReturn::BP_LP>::process<__m128>(const __m128& input [[maybe_unused]])
// {
// return std::make_pair<float, float>(0.0, 0.0);
// }
// template<>
// template<>
// auto StateVariableFilter<SVFReturn::BP_LP_HP>::process<__m128>(const __m128& input [[maybe_unused]])
// {
// return std::make_tuple<float, float, float>(0.0, 0.0, 0.0);
// }
template<>
auto StateVariableFilter::process(const __m128& input [[maybe_unused]], __m128& bandpass [[maybe_unused]])
{
}
template<>
auto StateVariableFilter::process(const __m128& input [[maybe_unused]], __m128& bandpass [[maybe_unused]], __m128& lowpass [[maybe_unused]])
{
}

View file

@ -8,7 +8,7 @@
#include <string_view>
#include <absl/container/flat_hash_map.h>
#include <map>
#include <readerwriterqueue.h>
#include "readerwriterqueue.h"
#include <thread>
namespace sfz

94
sources/OnePoleFilter.h Normal file
View file

@ -0,0 +1,94 @@
#include "Globals.h"
#include <cmath>
#include "gsl/gsl-lite.hpp"
template<class Type=float>
class OnePoleFilter
{
public:
OnePoleFilter() = default;
// Normalized cutoff with respect to the sampling rate
template<class C>
static Type normalizedGain(Type cutoff, C sampleRate)
{
return std::tan( cutoff / static_cast<Type>(sampleRate) * M_PIf32 );
}
OnePoleFilter(Type gain)
{
setGain(gain);
}
void setGain(Type gain)
{
this->gain = gain;
G = gain / ( 1 + gain);
}
Type getGain() const { return gain; }
int processLowpass(gsl::span<const Type> input, gsl::span<Type> lowpass)
{
for (auto [in, out] = std::pair(input.begin(), lowpass.begin());
in < input.end() && out < lowpass.end(); in++, out++)
{
oneLowpass(in, out);
}
return std::min(input.size(), lowpass.size());
}
int processHighpass(gsl::span<const Type> input, gsl::span<Type> highpass)
{
for (auto [in, out] = std::pair(input.begin(), highpass.begin());
in < input.end() && out < highpass.end(); in++, out++)
{
oneHighpass(in, out);
}
return std::min(input.size(), highpass.size());
}
int processLowpassVariableGain(gsl::span<const Type> input, gsl::span<Type> lowpass, gsl::span<const Type> gain)
{
for (auto [in, out, g] = std::tuple(input.begin(), lowpass.begin(), gain.begin());
in < input.end() && out < lowpass.end() && g < gain.end(); in++, out++, g++)
{
setGain(*g);
oneLowpass(in, out);
}
return std::min({ input.size(), lowpass.size(), gain.size() });
}
int processHighpassVariableGain(gsl::span<const Type> input, gsl::span<Type> highpass, gsl::span<const Type> gain)
{
for (auto [in, out, g] = std::tuple(input.begin(), highpass.begin(), gain.begin());
in < input.end() && out < highpass.end() && g < gain.end(); in++, out++, g++)
{
setGain(*g);
oneHighpass(in, out);
}
return std::min({ input.size(), highpass.size(), gain.size() });
}
void reset() { state = 0.0; }
private:
Type state { 0.0 };
Type gain { 0.25 };
Type intermediate { 0.0 };
Type G { gain / (1 + gain) };
inline void oneLowpass(const Type* in, Type* out)
{
intermediate = G * (*in - state);
*out = intermediate + state;
state = *out + intermediate;
}
inline void oneHighpass(const Type* in, Type* out)
{
intermediate = G * (*in - state);
*out = *in - intermediate - state;
state += 2*intermediate;
}
};

102
tests/OnePoleFilterT.cpp Normal file
View file

@ -0,0 +1,102 @@
#include "../sources/OnePoleFilter.h"
#include "catch2/catch.hpp"
#include "cnpy.h"
#include "gsl/gsl-lite.hpp"
#include <string>
#include <filesystem>
using namespace Catch::literals;
template<class Type>
void testInputOutput(const std::filesystem::path& inputNumpyFile, const std::filesystem::path& outputNumpyFile, Type gain)
{
const auto input = cnpy::npy_load(inputNumpyFile.string());
REQUIRE( input.word_size == 8 );
const auto inputSpan = gsl::make_span(input.data<double>(), input.shape[0]);
const auto output = cnpy::npy_load(outputNumpyFile.string());
REQUIRE( output.word_size == 8 );
const auto outputSpan = gsl::make_span(output.data<double>(), output.shape[0]);
auto size = std::min(outputSpan.size(), inputSpan.size());
REQUIRE( size > 0 );
std::vector<Type> inputData;
std::vector<Type> expectedData;
inputData.reserve(size);
expectedData.reserve(size);
for (auto& data: inputSpan)
inputData.push_back(static_cast<Type>(data));
for (auto& data: outputSpan)
expectedData.push_back(static_cast<Type>(data));
OnePoleFilter filter { gain };
std::vector<Type> outputData (size);
filter.processLowpass(inputData, outputData);
for (size_t i = 0; i < size; ++i)
REQUIRE( outputData[i] == Approx(expectedData[i]) );
filter.reset();
std::fill(outputData.begin(), outputData.end(), 0.0);
std::vector<Type> gains(size);
std::fill(gains.begin(), gains.end(), gain);
filter.processLowpassVariableGain(inputData, outputData, gains);
for (size_t i = 0; i < size; ++i)
REQUIRE( outputData[i] == Approx(expectedData[i]) );
}
TEST_CASE("[OnePoleFilter] Float")
{
testInputOutput<float>(
std::filesystem::current_path() / "tests/TestFiles/OnePoleFilter/OPF_input_gain_0.1.npy",
std::filesystem::current_path() / "tests/TestFiles/OnePoleFilter/OPF_output_gain_0.1.npy",
0.1f
);
testInputOutput<float>(
std::filesystem::current_path() / "tests/TestFiles/OnePoleFilter/OPF_input_gain_0.3.npy",
std::filesystem::current_path() / "tests/TestFiles/OnePoleFilter/OPF_output_gain_0.3.npy",
0.3f
);
testInputOutput<float>(
std::filesystem::current_path() / "tests/TestFiles/OnePoleFilter/OPF_input_gain_0.5.npy",
std::filesystem::current_path() / "tests/TestFiles/OnePoleFilter/OPF_output_gain_0.5.npy",
0.5f
);
testInputOutput<float>(
std::filesystem::current_path() / "tests/TestFiles/OnePoleFilter/OPF_input_gain_0.7.npy",
std::filesystem::current_path() / "tests/TestFiles/OnePoleFilter/OPF_output_gain_0.7.npy",
0.7f
);
testInputOutput<float>(
std::filesystem::current_path() / "tests/TestFiles/OnePoleFilter/OPF_input_gain_0.9.npy",
std::filesystem::current_path() / "tests/TestFiles/OnePoleFilter/OPF_output_gain_0.9.npy",
0.9f
);
}
TEST_CASE("[OnePoleFilter] Double")
{
testInputOutput<double>(
std::filesystem::current_path() / "tests/TestFiles/OnePoleFilter/OPF_input_gain_0.1.npy",
std::filesystem::current_path() / "tests/TestFiles/OnePoleFilter/OPF_output_gain_0.1.npy",
0.1f
);
testInputOutput<double>(
std::filesystem::current_path() / "tests/TestFiles/OnePoleFilter/OPF_input_gain_0.3.npy",
std::filesystem::current_path() / "tests/TestFiles/OnePoleFilter/OPF_output_gain_0.3.npy",
0.3f
);
testInputOutput<double>(
std::filesystem::current_path() / "tests/TestFiles/OnePoleFilter/OPF_input_gain_0.5.npy",
std::filesystem::current_path() / "tests/TestFiles/OnePoleFilter/OPF_output_gain_0.5.npy",
0.5f
);
testInputOutput<double>(
std::filesystem::current_path() / "tests/TestFiles/OnePoleFilter/OPF_input_gain_0.7.npy",
std::filesystem::current_path() / "tests/TestFiles/OnePoleFilter/OPF_output_gain_0.7.npy",
0.7f
);
testInputOutput<double>(
std::filesystem::current_path() / "tests/TestFiles/OnePoleFilter/OPF_input_gain_0.9.npy",
std::filesystem::current_path() / "tests/TestFiles/OnePoleFilter/OPF_output_gain_0.9.npy",
0.9f
);
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.