diff --git a/clients/jack_client.cpp b/clients/jack_client.cpp index f2e8c11b..1092a487 100644 --- a/clients/jack_client.cpp +++ b/clients/jack_client.cpp @@ -23,6 +23,7 @@ #include "sfizz.hpp" #include +#include #include #include #include @@ -156,17 +157,36 @@ static void done(int sig [[maybe_unused]]) // exit(0); } +ABSL_FLAG(std::string, oversampling, "1x", "Internal oversampling factor (value values are 1x, 2x, 4x, 8x)"); +ABSL_FLAG(uint32_t, preload_size, 8192, "Preloaded value"); + int main(int argc, char** argv) { // std::ios::sync_with_stdio(false); auto arguments = absl::ParseCommandLine(argc, argv); auto filesToParse = absl::MakeConstSpan(arguments).subspan(1); + const std::string oversampling = absl::GetFlag(FLAGS_oversampling); + const uint32_t preload_size = absl::GetFlag(FLAGS_preload_size); + + std::cout << "Flags" << '\n'; + std::cout << "- Oversampling: " << oversampling << '\n'; + std::cout << "- Preloaded Size: " << preload_size << '\n'; + const auto factor = [&]() { + if (oversampling == "x1") return sfz::Oversampling::x1; + if (oversampling == "x2") return sfz::Oversampling::x2; + if (oversampling == "x4") return sfz::Oversampling::x4; + if (oversampling == "x8") return sfz::Oversampling::x8; + return sfz::Oversampling::x1; + }(); + std::cout << "Positional arguments:"; for (auto& file : filesToParse) std::cout << " " << file << ','; std::cout << '\n'; sfz::Synth synth; + synth.setOversamplingFactor(factor); + synth.setPreloadSize(preload_size); synth.loadSfzFile(filesToParse[0]); std::cout << "==========" << '\n'; std::cout << "Total:" << '\n'; @@ -260,4 +280,4 @@ int main(int argc, char** argv) std::cout << "Closing..." << '\n'; jack_client_close(client); return 0; -} \ No newline at end of file +} diff --git a/cmake/SfizzSIMDSourceFilesCheck.cmake b/cmake/SfizzSIMDSourceFilesCheck.cmake index 1237b3bc..3c88f5b2 100644 --- a/cmake/SfizzSIMDSourceFilesCheck.cmake +++ b/cmake/SfizzSIMDSourceFilesCheck.cmake @@ -10,7 +10,7 @@ endif() # SIMD checks if (HAVE_X86INTRIN_H AND UNIX) add_compile_options (-DHAVE_X86INTRIN_H) - set (SFIZZ_SIMD_SOURCES sfizz/SIMDSSE.cpp) + set (SFIZZ_SIMD_SOURCES sfizz/SIMDSSE.cpp sfizz/OversamplerSSE.cpp) elseif (HAVE_INTRIN_H AND WIN32) add_compile_options (/DHAVE_INTRIN_H) set (SFIZZ_SIMD_SOURCES sfizz/SIMDSSE.cpp) @@ -20,9 +20,9 @@ elseif (HAVE_ARM_NEON_H AND UNIX) add_compile_options (-march=native) add_compile_options (-mtune=cortex-a53) add_compile_options (-funsafe-math-optimizations) - set (SFIZZ_SIMD_SOURCES sfizz/SIMDDummy.cpp) + set (SFIZZ_SIMD_SOURCES sfizz/SIMDDummy.cpp sfizz/OversamplerDummy.cpp) else() - set (SFIZZ_SIMD_SOURCES sfizz/SIMDDummy.cpp) + set (SFIZZ_SIMD_SOURCES sfizz/SIMDDummy.cpp sfizz/OversamplerDummy.cpp) endif() set (SFIZZ_SOURCES ${SFIZZ_SOURCES} ${SFIZZ_SIMD_SOURCES}) diff --git a/lv2/sfizz.c b/lv2/sfizz.c index 5856be0f..f78794ae 100644 --- a/lv2/sfizz.c +++ b/lv2/sfizz.c @@ -51,6 +51,8 @@ #define SFIZZ_PREFIX SFIZZ_URI "#" #define SFIZZ__sfzFile "http://sfztools.github.io/sfizz:sfzfile" #define SFIZZ__numVoices "http://sfztools.github.io/sfizz:numvoices" +#define SFIZZ__preloadSize "http://sfztools.github.io/sfizz:preload_size" +#define SFIZZ__oversampling "http://sfztools.github.io/sfizz:oversampling" #define CHANNEL_MASK 0x0F #define MIDI_CHANNEL(byte) (byte & CHANNEL_MASK) @@ -59,6 +61,8 @@ #define MAX_PATH_SIZE 1024 #define MAX_VOICES 256 #define DEFAULT_VOICES 64 +#define DEFAULT_OVERSAMPLING SFIZZ_OVERSAMPLING_X1 +#define DEFAULT_PRELOAD 8192 #define UNUSED(x) (void)(x) typedef struct @@ -75,6 +79,8 @@ typedef struct float *output_buffers[2]; const float *volume_port; const float *polyphony_port; + const float *oversampling_port; + const float *preload_port; // Atom forge LV2_Atom_Forge forge; ///< Forge for writing atoms in run thread @@ -103,13 +109,17 @@ typedef struct LV2_URID state_changed_uri; LV2_URID sfizz_sfz_file_uri; LV2_URID sfizz_num_voices_uri; + LV2_URID sfizz_preload_size_uri; + LV2_URID sfizz_oversampling_uri; // Sfizz related data sfizz_synth_t *synth; bool expect_nominal_block_length; char sfz_file_path[MAX_PATH_SIZE]; int num_voices; - bool changing_voices; + unsigned int preload_size; + sfizz_oversampling_factor_t oversampling; + bool changing_state; int max_block_size; float sample_rate; } sfizz_plugin_t; @@ -121,7 +131,9 @@ enum SFIZZ_LEFT = 2, SFIZZ_RIGHT = 3, SFIZZ_VOLUME = 4, - SFIZZ_POLYPHONY = 5 + SFIZZ_POLYPHONY = 5, + SFIZZ_OVERSAMPLING = 6, + SFIZZ_PRELOAD = 7 }; static void @@ -146,6 +158,8 @@ sfizz_lv2_map_required_uris(sfizz_plugin_t *self) self->state_changed_uri = map->map(map->handle, LV2_STATE__StateChanged); self->sfizz_sfz_file_uri = map->map(map->handle, SFIZZ__sfzFile); self->sfizz_num_voices_uri = map->map(map->handle, SFIZZ__numVoices); + self->sfizz_preload_size_uri = map->map(map->handle, SFIZZ__preloadSize); + self->sfizz_oversampling_uri = map->map(map->handle, SFIZZ__oversampling); } static void @@ -174,6 +188,12 @@ connect_port(LV2_Handle instance, case SFIZZ_POLYPHONY: self->polyphony_port = (const float *)data; break; + case SFIZZ_OVERSAMPLING: + self->oversampling_port = (const float *)data; + break; + case SFIZZ_PRELOAD: + self->preload_port = (const float *)data; + break; default: break; } @@ -203,7 +223,9 @@ instantiate(const LV2_Descriptor *descriptor, self->expect_nominal_block_length = false; self->sfz_file_path[0] = '\0'; self->num_voices = DEFAULT_VOICES; - self->changing_voices = false; + self->oversampling = DEFAULT_OVERSAMPLING; + self->preload_size = DEFAULT_PRELOAD; + self->changing_state = false; // Get the features from the host and populate the structure for (const LV2_Feature *const *f = features; *f; f++) @@ -509,8 +531,7 @@ run(LV2_Handle instance, uint32_t sample_count) sfizz_set_volume(self->synth, volume); int num_voices = (int)*self->polyphony_port; - if (num_voices != self->num_voices && !self->changing_voices) - { + if (num_voices != self->num_voices && !self->changing_state) { lv2_log_note(&self->logger, "[run] Number of voices changed to %d\n", num_voices); LV2_Atom_Int num_voices_atom; num_voices_atom.atom.type = self->sfizz_num_voices_uri; @@ -520,7 +541,37 @@ run(LV2_Handle instance, uint32_t sample_count) lv2_atom_total_size((LV2_Atom*)&num_voices_atom), &num_voices_atom) == LV2_WORKER_SUCCESS) { - self->changing_voices = true; + self->changing_state = true; + } + } + + unsigned int preload_size = (int)*self->preload_port; + if (preload_size != self->preload_size && !self->changing_state) { + lv2_log_note(&self->logger, "[run] Preload size changed to %d\n", preload_size); + LV2_Atom_Int atom; + atom.atom.type = self->sfizz_preload_size_uri; + atom.atom.size = sizeof(int); + atom.body = preload_size; + if (self->worker->schedule_work(self->worker->handle, + lv2_atom_total_size((LV2_Atom*)&atom), + &atom) == LV2_WORKER_SUCCESS) + { + self->changing_state = true; + } + } + + sfizz_oversampling_factor_t oversampling = (sfizz_oversampling_factor_t)*self->oversampling_port; + if (oversampling != self->oversampling && !self->changing_state) { + lv2_log_note(&self->logger, "[run] Oversampling size changed to %d\n", oversampling); + LV2_Atom_Int atom; + atom.atom.type = self->sfizz_oversampling_uri; + atom.atom.size = sizeof(int); + atom.body = oversampling; + if (self->worker->schedule_work(self->worker->handle, + lv2_atom_total_size((LV2_Atom*)&atom), + &atom) == LV2_WORKER_SUCCESS) + { + self->changing_state = true; } } @@ -614,6 +665,30 @@ restore(LV2_Handle instance, self->num_voices = num_voices; } } + + value = retrieve(handle, self->sfizz_preload_size_uri, &size, &type, &val_flags); + if (value) + { + unsigned int preload_size = *(const unsigned int *)value; + if (preload_size != self->preload_size) + { + lv2_log_note(&self->logger, "Restoring the preload size to %d\n", preload_size); + sfizz_set_preload_size(self->synth, preload_size); + self->preload_size = preload_size; + } + } + + value = retrieve(handle, self->sfizz_oversampling_uri, &size, &type, &val_flags); + if (value) + { + sfizz_oversampling_factor_t oversampling = *(const sfizz_oversampling_factor_t *)value; + if (oversampling != self->oversampling) + { + lv2_log_note(&self->logger, "Restoring the oversampling to %d\n", oversampling); + sfizz_set_oversampling_factor(self->synth, oversampling); + self->oversampling = oversampling; + } + } return LV2_STATE_SUCCESS; } @@ -643,6 +718,22 @@ save(LV2_Handle instance, self->atom_int_uri, LV2_STATE_IS_POD | LV2_STATE_IS_PORTABLE); + // Save the preload size + store(handle, + self->sfizz_preload_size_uri, + &self->preload_size, + sizeof(unsigned int), + self->atom_int_uri, + LV2_STATE_IS_POD | LV2_STATE_IS_PORTABLE); + + // Save the preload size + store(handle, + self->sfizz_oversampling_uri, + &self->oversampling, + sizeof(int), + self->atom_int_uri, + LV2_STATE_IS_POD | LV2_STATE_IS_PORTABLE); + return LV2_STATE_SUCCESS; } @@ -674,6 +765,19 @@ work(LV2_Handle instance, lv2_log_note(&self->logger, "[work] Changing number of voices to: %d\n", num_voices); sfizz_set_num_voices(self->synth, num_voices); } + else if (atom->type == self->sfizz_preload_size_uri) + { + const unsigned int preload_size = *(const unsigned int *)LV2_ATOM_BODY_CONST(atom); + lv2_log_note(&self->logger, "[work] Changing preload size to: %d\n", preload_size); + sfizz_set_preload_size(self->synth, preload_size); + } + else if (atom->type == self->sfizz_oversampling_uri) + { + const sfizz_oversampling_factor_t oversampling = + *(const sfizz_oversampling_factor_t *)LV2_ATOM_BODY_CONST(atom); + lv2_log_note(&self->logger, "[work] Changing oversampling to: %d\n", oversampling); + sfizz_set_oversampling_factor(self->synth, oversampling); + } else { lv2_log_error(&self->logger, "[worker] Got an unknown atom.\n"); @@ -702,18 +806,33 @@ work_response(LV2_Handle instance, const LV2_Atom *atom = (const LV2_Atom *)data; if (atom->type == self->sfizz_sfz_file_uri) - { + { // TODO: could check that everything is indeed changed here const char *sfz_file_path = LV2_ATOM_BODY_CONST(atom); strcpy(self->sfz_file_path, sfz_file_path); lv2_log_note(&self->logger, "[work_response] File changed to: %s\n", self->sfz_file_path); } else if (atom->type == self->sfizz_num_voices_uri) - { + { // TODO: could check that everything is indeed changed here const int num_voices = *(const int *)LV2_ATOM_BODY_CONST(atom); self->num_voices = num_voices; - self->changing_voices = false; + self->changing_state = false; lv2_log_note(&self->logger, "[work_response] Number of voices changed to: %d\n", self->num_voices); } + else if (atom->type == self->sfizz_preload_size_uri) + { // TODO: could check that everything is indeed changed here + const unsigned int preload_size = *(const unsigned int *)LV2_ATOM_BODY_CONST(atom); + self->preload_size = preload_size; + self->changing_state = false; + lv2_log_note(&self->logger, "[work_response] Preload size changed to: %d\n", self->preload_size); + } + else if (atom->type == self->sfizz_oversampling_uri) + { // TODO: could check that everything is indeed changed here + const sfizz_oversampling_factor_t oversampling = + *(const sfizz_oversampling_factor_t *)LV2_ATOM_BODY_CONST(atom); + self->oversampling = oversampling; + self->changing_state = false; + lv2_log_note(&self->logger, "[work_response] Oversampling changed to: %d\n", self->oversampling); + } else { lv2_log_error(&self->logger, "[work_response] Got an unknown atom.\n"); diff --git a/lv2/sfizz.ttl.in b/lv2/sfizz.ttl.in index 032d51ff..fb3a07bc 100644 --- a/lv2/sfizz.ttl.in +++ b/lv2/sfizz.ttl.in @@ -30,6 +30,16 @@ rdfs:label "Polyphony" ; rdfs:range atom:Int . +<@LV2PLUGIN_URI@:preload_size> + a lv2:Parameter ; + rdfs:label "Preload size" ; + rdfs:range atom:Int . + +<@LV2PLUGIN_URI@:oversampling> + a lv2:Parameter ; + rdfs:label "Oversampling" ; + rdfs:range atom:Int . + <@LV2PLUGIN_URI@> a doap:Project, lv2:Plugin, lv2:InstrumentPlugin ; @@ -105,4 +115,39 @@ lv2:scalePoint [ rdfs:label "64 voices"; rdf:value 64 ] ; lv2:scalePoint [ rdfs:label "128 voices"; rdf:value 128 ] ; lv2:scalePoint [ rdfs:label "256 voices"; rdf:value 256 ] ; - ] . + ] , [ + a lv2:InputPort, lv2:ControlPort ; + lv2:index 6 ; + lv2:symbol "oversampling" ; + lv2:name "Internal oversampling factor" ; + pg:group <#config> ; + lv2:portProperty pprop:notAutomatic ; + lv2:portProperty pprop:expensive ; + lv2:portProperty lv2:integer ; + lv2:portProperty lv2:enumeration ; + lv2:default 1 ; + lv2:minimum 1 ; + lv2:maximum 8 ; + lv2:scalePoint [ rdfs:label "x1 oversampling"; rdf:value 1 ] ; + lv2:scalePoint [ rdfs:label "x2 oversampling"; rdf:value 2 ] ; + lv2:scalePoint [ rdfs:label "x4 oversampling"; rdf:value 4 ] ; + lv2:scalePoint [ rdfs:label "x8 oversampling"; rdf:value 8 ] ; + ], [ + a lv2:InputPort, lv2:ControlPort ; + lv2:index 7 ; + lv2:symbol "preload_size" ; + lv2:name "Preload size" ; + pg:group <#config> ; + lv2:portProperty pprop:notAutomatic ; + lv2:portProperty pprop:expensive ; + lv2:portProperty lv2:integer ; + lv2:portProperty lv2:enumeration ; + lv2:default 8192 ; + lv2:minimum 1024 ; + lv2:maximum 16384 ; + lv2:scalePoint [ rdfs:label "4 Ko"; rdf:value 1024 ] ; + lv2:scalePoint [ rdfs:label "8 Ko"; rdf:value 2048 ] ; + lv2:scalePoint [ rdfs:label "16 Ko"; rdf:value 4096 ] ; + lv2:scalePoint [ rdfs:label "32 Ko"; rdf:value 8192 ] ; + lv2:scalePoint [ rdfs:label "64 Ko"; rdf:value 16384 ] ; + ]. diff --git a/src/external/hiir/StageDataNeon.h b/src/external/hiir/StageDataNeon.h new file mode 100644 index 00000000..7ec6fded --- /dev/null +++ b/src/external/hiir/StageDataNeon.h @@ -0,0 +1,95 @@ +/***************************************************************************** + + StageDataNeon.h + Author: Laurent de Soras, 2016 + +--- Legal stuff --- + +This program is free software. It comes without any warranty, to +the extent permitted by applicable law. You can redistribute it +and/or modify it under the terms of the Do What The Fuck You Want +To Public License, Version 2, as published by Sam Hocevar. See +http://sam.zoy.org/wtfpl/COPYING for more details. + +*Tab=3***********************************************************************/ + + + +#pragma once +#if ! defined (hiir_StageDataNeon_HEADER_INCLUDED) +#define hiir_StageDataNeon_HEADER_INCLUDED + +#if defined (_MSC_VER) + #pragma warning (4 : 4250) +#endif + + + +/*\\\ INCLUDE FILES \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/ + +#include + + + +namespace hiir +{ + + + +class StageDataNeon +{ + +/*\\\ PUBLIC \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/ + +public: + + union + { + __attribute__ ((aligned (16))) float32x4_t + _coef4; + __attribute__ ((aligned (16))) float + _coef [4]; // a_{4n+1}, a_{4n}, a_{4n+3}, a_{4n+2} + }; + union + { + __attribute__ ((aligned (16))) float32x4_t + _mem4; + __attribute__ ((aligned (16))) float + _mem [4]; // y of the stage + }; + + + +/*\\\ PROTECTED \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/ + +protected: + + + +/*\\\ PRIVATE \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/ + +private: + + + +/*\\\ FORBIDDEN MEMBER FUNCTIONS \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/ + +private: + +}; // class StageDataNeon + + + +} // namespace hiir + + + +//#include "hiir/StageDataNeon.hpp" + + + +#endif // hiir_StageDataNeon_HEADER_INCLUDED + + + +/*\\\ EOF \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/ diff --git a/src/external/hiir/StageDataSse.h b/src/external/hiir/StageDataSse.h new file mode 100644 index 00000000..a1dfe929 --- /dev/null +++ b/src/external/hiir/StageDataSse.h @@ -0,0 +1,67 @@ +/***************************************************************************** + + StageDataSse.h + Author: Laurent de Soras, 2005 + +--- Legal stuff --- + +This program is free software. It comes without any warranty, to +the extent permitted by applicable law. You can redistribute it +and/or modify it under the terms of the Do What The Fuck You Want +To Public License, Version 2, as published by Sam Hocevar. See +http://sam.zoy.org/wtfpl/COPYING for more details. + +*Tab=3***********************************************************************/ + + + +#if ! defined (hiir_StageDataSse_HEADER_INCLUDED) +#define hiir_StageDataSse_HEADER_INCLUDED + +#if defined (_MSC_VER) + #pragma once + #pragma warning (4 : 4250) // "Inherits via dominance." +#endif + + + +/*\\\ INCLUDE FILES \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/ + +#include + + + +namespace hiir +{ + + + +class StageDataSse +{ + +public: + + union + { + __m128 _coef4; // Just to ensure alignement + float _coef [4]; // a_{4n+1}, a_{4n}, a_{4n+3}, a_{4n+2} + }; + union + { + __m128 _mem4; + float _mem [4]; // y of the stage + }; + +}; // class StageDataSse + + + +} // namespace hiir + + + +#endif // hiir_StageDataSse_HEADER_INCLUDED + + + +/*\\\ EOF \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/ diff --git a/src/external/hiir/StageProc4Neon.h b/src/external/hiir/StageProc4Neon.h new file mode 100644 index 00000000..534cb9c5 --- /dev/null +++ b/src/external/hiir/StageProc4Neon.h @@ -0,0 +1,100 @@ +/***************************************************************************** + + StageProc4Neon.h + Author: Laurent de Soras, 2016 + +Template parameters: + - REMAINING: Number of remaining coefficients to process, >= 0 + +--- Legal stuff --- + +This program is free software. It comes without any warranty, to +the extent permitted by applicable law. You can redistribute it +and/or modify it under the terms of the Do What The Fuck You Want +To Public License, Version 2, as published by Sam Hocevar. See +http://sam.zoy.org/wtfpl/COPYING for more details. + +*Tab=3***********************************************************************/ + + + +#pragma once +#if ! defined (hiir_StageProc4Neon_HEADER_INCLUDED) +#define hiir_StageProc4Neon_HEADER_INCLUDED + +#if defined (_MSC_VER) + #pragma warning (4 : 4250) +#endif + + + +/*\\\ INCLUDE FILES \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/ + +#include "hiir/def.h" + + + +namespace hiir +{ + + + +class StageDataNeon; + +template +class StageProc4Neon +{ + static_assert (REMAINING >= 0, "REMAINING must be >= 0."); + +/*\\\ PUBLIC \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/ + +public: + + static hiir_FORCEINLINE void + process_sample_pos (const int nbr_coefs, float32x4_t &spl_0, float32x4_t &spl_1, StageDataNeon *stage_arr); + static hiir_FORCEINLINE void + process_sample_neg (const int nbr_coefs, float32x4_t &spl_0, float32x4_t &spl_1, StageDataNeon *stage_arr); + + + +/*\\\ PROTECTED \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/ + +protected: + + + +/*\\\ PRIVATE \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/ + +private: + + + +/*\\\ FORBIDDEN MEMBER FUNCTIONS \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/ + +private: + + StageProc4Neon () = delete; + StageProc4Neon (const StageProc4Neon &other) = delete; + ~StageProc4Neon () = delete; + StageProc4Neon & + operator = (const StageProc4Neon &other) = delete; + bool operator == (const StageProc4Neon &other) const = delete; + bool operator != (const StageProc4Neon &other) const = delete; + +}; // class StageProc4Neon + + + +} // namespace hiir + + + +#include "hiir/StageProc4Neon.hpp" + + + +#endif // hiir_StageProc4Neon_HEADER_INCLUDED + + + +/*\\\ EOF \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/ diff --git a/src/external/hiir/StageProc4Neon.hpp b/src/external/hiir/StageProc4Neon.hpp new file mode 100644 index 00000000..72ce50ea --- /dev/null +++ b/src/external/hiir/StageProc4Neon.hpp @@ -0,0 +1,174 @@ +/***************************************************************************** + + StageProc4Neon.hpp + Author: Laurent de Soras, 2016 + +--- Legal stuff --- + +This program is free software. It comes without any warranty, to +the extent permitted by applicable law. You can redistribute it +and/or modify it under the terms of the Do What The Fuck You Want +To Public License, Version 2, as published by Sam Hocevar. See +http://sam.zoy.org/wtfpl/COPYING for more details. + +*Tab=3***********************************************************************/ + + + +#if ! defined (hiir_StageProc4Neon_CODEHEADER_INCLUDED) +#define hiir_StageProc4Neon_CODEHEADER_INCLUDED + + + +/*\\\ INCLUDE FILES \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/ + +#include "hiir/StageDataNeon.h" + + + +namespace hiir +{ + + + +/*\\\ PUBLIC \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/ + + + +template <> +inline void StageProc4Neon <1>::process_sample_pos (const int nbr_coefs, float32x4_t &spl_0, float32x4_t &spl_1, StageDataNeon *stage_arr) +{ + const int cnt = nbr_coefs + 2 - 1; + + const float32x4_t tmp_0 = vmlaq_f32 ( + stage_arr [cnt - 2]._mem4, + spl_0 - stage_arr [cnt ]._mem4, + stage_arr [cnt ]._coef4 + ); + + stage_arr [cnt - 2]._mem4 = spl_0; + stage_arr [cnt - 1]._mem4 = spl_1; + stage_arr [cnt ]._mem4 = tmp_0; + + spl_0 = tmp_0; +} + + + +template <> +inline void StageProc4Neon <0>::process_sample_pos (const int nbr_coefs, float32x4_t &spl_0, float32x4_t &spl_1, StageDataNeon *stage_arr) +{ + const int cnt = nbr_coefs + 2; + + stage_arr [cnt - 2]._mem4 = spl_0; + stage_arr [cnt - 1]._mem4 = spl_1; +} + + + +template +void StageProc4Neon ::process_sample_pos (const int nbr_coefs, float32x4_t &spl_0, float32x4_t &spl_1, StageDataNeon *stage_arr) +{ + const int cnt = nbr_coefs + 2 - REMAINING; + + const float32x4_t tmp_0 = vmlaq_f32 ( + stage_arr [cnt - 2]._mem4, + spl_0 - stage_arr [cnt ]._mem4, + stage_arr [cnt ]._coef4 + ); + const float32x4_t tmp_1 = vmlaq_f32 ( + stage_arr [cnt - 1]._mem4, + spl_1 - stage_arr [cnt + 1]._mem4, + stage_arr [cnt + 1]._coef4 + ); + + stage_arr [cnt - 2]._mem4 = spl_0; + stage_arr [cnt - 1]._mem4 = spl_1; + + spl_0 = tmp_0; + spl_1 = tmp_1; + + StageProc4Neon ::process_sample_pos ( + nbr_coefs, + spl_0, + spl_1, + stage_arr + ); +} + + + +template <> +inline void StageProc4Neon <1>::process_sample_neg (const int nbr_coefs, float32x4_t &spl_0, float32x4_t &spl_1, StageDataNeon *stage_arr) +{ + const int cnt = nbr_coefs + 2 - 1; + + float32x4_t tmp_0 = spl_0; + tmp_0 += stage_arr [cnt ]._mem4; + tmp_0 *= stage_arr [cnt ]._coef4; + tmp_0 -= stage_arr [cnt - 2]._mem4; + + stage_arr [cnt - 2]._mem4 = spl_0; + stage_arr [cnt - 1]._mem4 = spl_1; + stage_arr [cnt ]._mem4 = tmp_0; + + spl_0 = tmp_0; +} + +template <> +inline void StageProc4Neon <0>::process_sample_neg (const int nbr_coefs, float32x4_t &spl_0, float32x4_t &spl_1, StageDataNeon *stage_arr) +{ + const int cnt = nbr_coefs + 2; + + stage_arr [cnt - 2]._mem4 = spl_0; + stage_arr [cnt - 1]._mem4 = spl_1; +} + +template +void StageProc4Neon ::process_sample_neg (const int nbr_coefs, float32x4_t &spl_0, float32x4_t &spl_1, StageDataNeon *stage_arr) +{ + const int cnt = nbr_coefs + 2 - REMAINING; + + float32x4_t tmp_0 = spl_0; + tmp_0 += stage_arr [cnt ]._mem4; + tmp_0 *= stage_arr [cnt ]._coef4; + tmp_0 -= stage_arr [cnt - 2]._mem4; + + float32x4_t tmp_1 = spl_1; + tmp_1 += stage_arr [cnt + 1]._mem4; + tmp_1 *= stage_arr [cnt + 1]._coef4; + tmp_1 -= stage_arr [cnt - 1]._mem4; + + stage_arr [cnt - 2]._mem4 = spl_0; + stage_arr [cnt - 1]._mem4 = spl_1; + + spl_0 = tmp_0; + spl_1 = tmp_1; + + StageProc4Neon ::process_sample_neg ( + nbr_coefs, + spl_0, + spl_1, + stage_arr + ); +} + + + +/*\\\ PROTECTED \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/ + + + +/*\\\ PRIVATE \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/ + + + +} // namespace hiir + + + +#endif // hiir_StageProc4Neon_CODEHEADER_INCLUDED + + + +/*\\\ EOF \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/ diff --git a/src/external/hiir/StageProc4Sse.h b/src/external/hiir/StageProc4Sse.h new file mode 100644 index 00000000..4b697aa7 --- /dev/null +++ b/src/external/hiir/StageProc4Sse.h @@ -0,0 +1,102 @@ +/***************************************************************************** + + StageProc4Sse.h + Author: Laurent de Soras, 2015 + +Template parameters: + - REMAINING: Number of remaining coefficients to process, >= 0 + +--- Legal stuff --- + +This program is free software. It comes without any warranty, to +the extent permitted by applicable law. You can redistribute it +and/or modify it under the terms of the Do What The Fuck You Want +To Public License, Version 2, as published by Sam Hocevar. See +http://sam.zoy.org/wtfpl/COPYING for more details. + +*Tab=3***********************************************************************/ + + + +#if ! defined (hiir_StageProc4Sse_HEADER_INCLUDED) +#define hiir_StageProc4Sse_HEADER_INCLUDED + +#if defined (_MSC_VER) + #pragma once + #pragma warning (4 : 4250) // "Inherits via dominance." +#endif + + + +/*\\\ INCLUDE FILES \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/ + +#include "hiir/def.h" + +#include + + + +namespace hiir +{ + + + +class StageDataSse; + +template +class StageProc4Sse +{ + + static_assert ((REMAINING >= 0), "REMAINING must be >= 0"); + +/*\\\ PUBLIC \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/ + +public: + + static hiir_FORCEINLINE void + process_sample_pos (const int nbr_coefs, __m128 &spl_0, __m128 &spl_1, StageDataSse *stage_arr); + static hiir_FORCEINLINE void + process_sample_neg (const int nbr_coefs, __m128 &spl_0, __m128 &spl_1, StageDataSse *stage_arr); + + + +/*\\\ PROTECTED \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/ + +protected: + + + +/*\\\ PRIVATE \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/ + +private: + + + +/*\\\ FORBIDDEN MEMBER FUNCTIONS \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/ + +private: + + StageProc4Sse (); + StageProc4Sse (const StageProc4Sse &other); + StageProc4Sse & + operator = (const StageProc4Sse &other); + bool operator == (const StageProc4Sse &other); + bool operator != (const StageProc4Sse &other); + +}; // class StageProc4Sse + + + +} // namespace hiir + + + +#include "hiir/StageProc4Sse.hpp" + + + +#endif // hiir_StageProc4Sse_HEADER_INCLUDED + + + +/*\\\ EOF \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/ diff --git a/src/external/hiir/StageProc4Sse.hpp b/src/external/hiir/StageProc4Sse.hpp new file mode 100644 index 00000000..1419e826 --- /dev/null +++ b/src/external/hiir/StageProc4Sse.hpp @@ -0,0 +1,205 @@ +/***************************************************************************** + + StageProc4Sse.hpp + Author: Laurent de Soras, 2015 + +--- 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_StageProc4Sse_CURRENT_CODEHEADER) + #error Recursive inclusion of StageProc4Sse code header. +#endif +#define hiir_StageProc4Sse_CURRENT_CODEHEADER + +#if ! defined (hiir_StageProc4Sse_CODEHEADER_INCLUDED) +#define hiir_StageProc4Sse_CODEHEADER_INCLUDED + + + +/*\\\ INCLUDE FILES \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/ + +#include "hiir/StageDataSse.h" + + + +#if defined (_MSC_VER) + #pragma inline_depth (255) +#endif + + + +namespace hiir +{ + + + +/*\\\ PUBLIC \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/ + + + +template <> +hiir_FORCEINLINE void StageProc4Sse <1>::process_sample_pos (const int nbr_coefs, __m128 &spl_0, __m128 &spl_1, StageDataSse *stage_arr) +{ + const int cnt = nbr_coefs + 2 - 1; + + const __m128 tmp_0 = _mm_add_ps ( + _mm_mul_ps ( + _mm_sub_ps (spl_0, _mm_load_ps (stage_arr [cnt ]._mem)), + _mm_load_ps (stage_arr [cnt ]._coef) + ), + _mm_load_ps (stage_arr [cnt - 2]._mem) + ); + + _mm_store_ps (stage_arr [cnt - 2]._mem, spl_0); + _mm_store_ps (stage_arr [cnt - 1]._mem, spl_1); + _mm_store_ps (stage_arr [cnt ]._mem, tmp_0); + + spl_0 = tmp_0; +} + + + +template <> +hiir_FORCEINLINE void StageProc4Sse <0>::process_sample_pos (const int nbr_coefs, __m128 &spl_0, __m128 &spl_1, StageDataSse *stage_arr) +{ + const int cnt = nbr_coefs + 2; + + _mm_store_ps (stage_arr [cnt - 2]._mem, spl_0); + _mm_store_ps (stage_arr [cnt - 1]._mem, spl_1); +} + + + +template +void StageProc4Sse ::process_sample_pos (const int nbr_coefs, __m128 &spl_0, __m128 &spl_1, StageDataSse *stage_arr) +{ + const int cnt = nbr_coefs + 2 - REMAINING; + + const __m128 tmp_0 = _mm_add_ps ( + _mm_mul_ps ( + _mm_sub_ps (spl_0, _mm_load_ps (stage_arr [cnt ]._mem)), + _mm_load_ps (stage_arr [cnt ]._coef) + ), + _mm_load_ps (stage_arr [cnt - 2]._mem) + ); + const __m128 tmp_1 = _mm_add_ps ( + _mm_mul_ps ( + _mm_sub_ps (spl_1, _mm_load_ps (stage_arr [cnt + 1]._mem)), + _mm_load_ps (stage_arr [cnt + 1]._coef) + ), + _mm_load_ps (stage_arr [cnt - 1]._mem) + ); + + _mm_store_ps (stage_arr [cnt - 2]._mem, spl_0); + _mm_store_ps (stage_arr [cnt - 1]._mem, spl_1); + + spl_0 = tmp_0; + spl_1 = tmp_1; + + StageProc4Sse ::process_sample_pos ( + nbr_coefs, + spl_0, + spl_1, + stage_arr + ); +} + + + +template <> +hiir_FORCEINLINE void StageProc4Sse <1>::process_sample_neg (const int nbr_coefs, __m128 &spl_0, __m128 &spl_1, StageDataSse *stage_arr) +{ + const int cnt = nbr_coefs + 2 - 1; + + const __m128 tmp_0 = _mm_sub_ps ( + _mm_mul_ps ( + _mm_add_ps (spl_0, _mm_load_ps (stage_arr [cnt ]._mem)), + _mm_load_ps (stage_arr [cnt ]._coef) + ), + _mm_load_ps (stage_arr [cnt - 2]._mem) + ); + + _mm_store_ps (stage_arr [cnt - 2]._mem, spl_0); + _mm_store_ps (stage_arr [cnt - 1]._mem, spl_1); + _mm_store_ps (stage_arr [cnt ]._mem, tmp_0); + + spl_0 = tmp_0; +} + + + +template <> +hiir_FORCEINLINE void StageProc4Sse <0>::process_sample_neg (const int nbr_coefs, __m128 &spl_0, __m128 &spl_1, StageDataSse *stage_arr) +{ + const int cnt = nbr_coefs + 2; + + _mm_store_ps (stage_arr [cnt - 2]._mem, spl_0); + _mm_store_ps (stage_arr [cnt - 1]._mem, spl_1); +} + + + +template +void StageProc4Sse ::process_sample_neg (const int nbr_coefs, __m128 &spl_0, __m128 &spl_1, StageDataSse *stage_arr) +{ + const int cnt = nbr_coefs + 2 - REMAINING; + + const __m128 tmp_0 = _mm_sub_ps ( + _mm_mul_ps ( + _mm_add_ps (spl_0, _mm_load_ps (stage_arr [cnt ]._mem)), + _mm_load_ps (stage_arr [cnt ]._coef) + ), + _mm_load_ps (stage_arr [cnt - 2]._mem) + ); + const __m128 tmp_1 = _mm_sub_ps ( + _mm_mul_ps ( + _mm_add_ps (spl_1, _mm_load_ps (stage_arr [cnt + 1]._mem)), + _mm_load_ps (stage_arr [cnt + 1]._coef) + ), + _mm_load_ps (stage_arr [cnt - 1]._mem) + ); + + _mm_store_ps (stage_arr [cnt - 2]._mem, spl_0); + _mm_store_ps (stage_arr [cnt - 1]._mem, spl_1); + + spl_0 = tmp_0; + spl_1 = tmp_1; + + StageProc4Sse ::process_sample_neg ( + nbr_coefs, + spl_0, + spl_1, + stage_arr + ); +} + + + +/*\\\ PROTECTED \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/ + + + +/*\\\ PRIVATE \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/ + + + +} // namespace hiir + + + +#endif // hiir_StageProc4Sse_CODEHEADER_INCLUDED + +#undef hiir_StageProc4Sse_CURRENT_CODEHEADER + + + +/*\\\ EOF \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/ diff --git a/src/external/hiir/StageProcFpu.h b/src/external/hiir/StageProcFpu.h new file mode 100644 index 00000000..8cdeab71 --- /dev/null +++ b/src/external/hiir/StageProcFpu.h @@ -0,0 +1,98 @@ +/***************************************************************************** + + StageProcFpu.h + Author: Laurent de Soras, 2005 + +Template parameters: + - REMAINING: Number of remaining coefficients to process, >= 0 + +--- Legal stuff --- + +This program is free software. It comes without any warranty, to +the extent permitted by applicable law. You can redistribute it +and/or modify it under the terms of the Do What The Fuck You Want +To Public License, Version 2, as published by Sam Hocevar. See +http://sam.zoy.org/wtfpl/COPYING for more details. + +*Tab=3***********************************************************************/ + + + +#if ! defined (hiir_StageProc_HEADER_INCLUDED) +#define hiir_StageProc_HEADER_INCLUDED + +#if defined (_MSC_VER) + #pragma once + #pragma warning (4 : 4250) // "Inherits via dominance." +#endif + + + +/*\\\ INCLUDE FILES \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/ + +#include "hiir/def.h" + + + +namespace hiir +{ + + + +template +class StageProcFpu +{ + + static_assert ((REMAINING >= 0), "REMAINING must be >= 0"); + +/*\\\ PUBLIC \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/ + +public: + + static hiir_FORCEINLINE void + process_sample_pos (const int nbr_coefs, float &spl_0, float &spl_1, const float coef [], float x [], float y []); + static hiir_FORCEINLINE void + process_sample_neg (const int nbr_coefs, float &spl_0, float &spl_1, const float coef [], float x [], float y []); + + + +/*\\\ PROTECTED \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/ + +protected: + + + +/*\\\ PRIVATE \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/ + +private: + + + +/*\\\ FORBIDDEN MEMBER FUNCTIONS \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/ + +private: + + StageProcFpu (); + StageProcFpu (const StageProcFpu &other); + StageProcFpu & + operator = (const StageProcFpu &other); + bool operator == (const StageProcFpu &other); + bool operator != (const StageProcFpu &other); + +}; // class StageProcFpu + + + +} // namespace hiir + + + +#include "hiir/StageProcFpu.hpp" + + + +#endif // hiir_StageProc_HEADER_INCLUDED + + + +/*\\\ EOF \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/ diff --git a/src/external/hiir/StageProcFpu.hpp b/src/external/hiir/StageProcFpu.hpp new file mode 100644 index 00000000..9fc1c018 --- /dev/null +++ b/src/external/hiir/StageProcFpu.hpp @@ -0,0 +1,165 @@ +/***************************************************************************** + + StageProcFpu.hpp + Author: Laurent de Soras, 2005 + +--- Legal stuff --- + +This program is free software. It comes without any warranty, to +the extent permitted by applicable law. You can redistribute it +and/or modify it under the terms of the Do What The Fuck You Want +To Public License, Version 2, as published by Sam Hocevar. See +http://sam.zoy.org/wtfpl/COPYING for more details. + +*Tab=3***********************************************************************/ + + + +#if defined (hiir_StageProc_CURRENT_CODEHEADER) + #error Recursive inclusion of StageProcFpu code header. +#endif +#define hiir_StageProc_CURRENT_CODEHEADER + +#if ! defined (hiir_StageProc_CODEHEADER_INCLUDED) +#define hiir_StageProc_CODEHEADER_INCLUDED + + + +/*\\\ INCLUDE FILES \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/ + +#if defined (_MSC_VER) + #pragma inline_depth (255) +#endif + + + +namespace hiir +{ + + + +/*\\\ PUBLIC \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/ + + + +template <> +hiir_FORCEINLINE void StageProcFpu <1>::process_sample_pos (const int nbr_coefs, float &spl_0, float &/*spl_1*/, const float coef [], float x [], float y []) +{ + const int last = nbr_coefs - 1; + const float temp = (spl_0 - y [last]) * coef [last] + x [last]; + x [last] = spl_0; + y [last] = temp; + spl_0 = temp; +} + + + +template <> +hiir_FORCEINLINE void StageProcFpu <0>::process_sample_pos (const int /*nbr_coefs*/, float &/*spl_0*/, float &/*spl_1*/, const float /*coef*/ [], float /*x*/ [], float /*y*/ []) +{ + // Nothing (stops recursion) +} + + + +template +void StageProcFpu ::process_sample_pos (const int nbr_coefs, float &spl_0, float &spl_1, const float coef [], float x [], float y []) +{ + const int cnt = nbr_coefs - REMAINING; + + const float temp_0 = + (spl_0 - y [cnt + 0]) * coef [cnt + 0] + x [cnt + 0]; + const float temp_1 = + (spl_1 - y [cnt + 1]) * coef [cnt + 1] + x [cnt + 1]; + + x [cnt + 0] = spl_0; + x [cnt + 1] = spl_1; + + y [cnt + 0] = temp_0; + y [cnt + 1] = temp_1; + + spl_0 = temp_0; + spl_1 = temp_1; + + StageProcFpu ::process_sample_pos ( + nbr_coefs, + spl_0, + spl_1, + &coef [0], + &x [0], + &y [0] + ); +} + + + +template <> +hiir_FORCEINLINE void StageProcFpu <1>::process_sample_neg (const int nbr_coefs, float &spl_0, float &/*spl_1*/, const float coef [], float x [], float y []) +{ + const int last = nbr_coefs - 1; + const float temp = (spl_0 + y [last]) * coef [last] - x [last]; + x [last] = spl_0; + y [last] = temp; + spl_0 = temp; +} + + + +template <> +hiir_FORCEINLINE void StageProcFpu <0>::process_sample_neg (const int /*nbr_coefs*/, float &/*spl_0*/, float &/*spl_1*/, const float /*coef*/ [], float /*x*/ [], float /*y*/ []) +{ + // Nothing (stops recursion) +} + + + +template +void StageProcFpu ::process_sample_neg (const int nbr_coefs, float &spl_0, float &spl_1, const float coef [], float x [], float y []) +{ + const int cnt = nbr_coefs - REMAINING; + + const float temp_0 = + (spl_0 + y [cnt + 0]) * coef [cnt + 0] - x [cnt + 0]; + const float temp_1 = + (spl_1 + y [cnt + 1]) * coef [cnt + 1] - x [cnt + 1]; + + x [cnt + 0] = spl_0; + x [cnt + 1] = spl_1; + + y [cnt + 0] = temp_0; + y [cnt + 1] = temp_1; + + spl_0 = temp_0; + spl_1 = temp_1; + + StageProcFpu ::process_sample_neg ( + nbr_coefs, + spl_0, + spl_1, + &coef [0], + &x [0], + &y [0] + ); +} + + + +/*\\\ PROTECTED \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/ + + + +/*\\\ PRIVATE \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/ + + + +} // namespace hiir + + + +#endif // hiir_StageProc_CODEHEADER_INCLUDED + +#undef hiir_StageProc_CURRENT_CODEHEADER + + + +/*\\\ EOF \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/ diff --git a/src/external/hiir/Upsampler2x4Neon.h b/src/external/hiir/Upsampler2x4Neon.h new file mode 100644 index 00000000..aeb56751 --- /dev/null +++ b/src/external/hiir/Upsampler2x4Neon.h @@ -0,0 +1,114 @@ +/***************************************************************************** + + Upsampler2x4Neon.h + Author: Laurent de Soras, 2016 + +Upsamples vectors of 4 float by a factor 2 the input signal, using the NEON +instruction set. + +This object must be aligned on a 16-byte boundary! + +Template parameters: + - NC: number of coefficients, > 0 + +--- Legal stuff --- + +This program is free software. It comes without any warranty, to +the extent permitted by applicable law. You can redistribute it +and/or modify it under the terms of the Do What The Fuck You Want +To Public License, Version 2, as published by Sam Hocevar. See +http://sam.zoy.org/wtfpl/COPYING for more details. + +*Tab=3***********************************************************************/ + + + +#pragma once +#if ! defined (hiir_Upsampler2x4Neon_HEADER_INCLUDED) +#define hiir_Upsampler2x4Neon_HEADER_INCLUDED + +#if defined (_MSC_VER) + #pragma warning (4 : 4250) +#endif + + + +/*\\\ INCLUDE FILES \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/ + +#include "hiir/def.h" +#include "hiir/StageDataNeon.h" + +#include + +#include + + + +namespace hiir +{ + + + +template +class Upsampler2x4Neon +{ + + static_assert ((NC > 0), "Number of coefficient must be positive."); + +/*\\\ PUBLIC \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/ + +public: + + enum { NBR_COEFS = NC }; + + Upsampler2x4Neon (); + + void set_coefs (const double coef_arr [NBR_COEFS]); + hiir_FORCEINLINE void + process_sample (float32x4_t &out_0, float32x4_t &out_1, float32x4_t input); + void process_block (float out_ptr [], const float in_ptr [], long nbr_spl); + void clear_buffers (); + + + + +/*\\\ PROTECTED \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/ + +protected: + + + +/*\\\ PRIVATE \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/ + +private: + + typedef std::array Filter; // Stages 0 and 1 contain only input memories + + Filter _filter; // Should be the first member (thus easier to align) + + + +/*\\\ FORBIDDEN MEMBER FUNCTIONS \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/ + +private: + + bool operator == (const Upsampler2x4Neon &other) const = delete; + bool operator != (const Upsampler2x4Neon &other) const = delete; + +}; // class Upsampler2x4Neon + + + +} // namespace hiir + + + +#include "hiir/Upsampler2x4Neon.hpp" + + + +#endif // hiir_Upsampler2x4Neon_HEADER_INCLUDED + + + +/*\\\ EOF \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/ diff --git a/src/external/hiir/Upsampler2x4Neon.hpp b/src/external/hiir/Upsampler2x4Neon.hpp new file mode 100644 index 00000000..6304ed15 --- /dev/null +++ b/src/external/hiir/Upsampler2x4Neon.hpp @@ -0,0 +1,205 @@ +/***************************************************************************** + + Upsampler2x4Neon.hpp + Author: Laurent de Soras, 2016 + +--- Legal stuff --- + +This program is free software. It comes without any warranty, to +the extent permitted by applicable law. You can redistribute it +and/or modify it under the terms of the Do What The Fuck You Want +To Public License, Version 2, as published by Sam Hocevar. See +http://sam.zoy.org/wtfpl/COPYING for more details. + +*Tab=3***********************************************************************/ + + + +#if ! defined (hiir_Upsampler2x4Neon_CODEHEADER_INCLUDED) +#define hiir_Upsampler2x4Neon_CODEHEADER_INCLUDED + + + +/*\\\ INCLUDE FILES \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/ + +#include "hiir/StageProc4Neon.h" + +#include + + + +namespace hiir +{ + + + +/*\\\ PUBLIC \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/ + + + +/* +============================================================================== +Name: ctor +Throws: Nothing +============================================================================== +*/ + +template +Upsampler2x4Neon ::Upsampler2x4Neon () +: _filter () +{ + for (int i = 0; i < NBR_COEFS + 2; ++i) + { + _filter [i]._coef4 = vdupq_n_f32 (0); + } + + clear_buffers (); +} + + + +/* +============================================================================== +Name: set_coefs +Description: + Sets filter coefficients. Generate them with the PolyphaseIir2Designer + class. + Call this function before doing any processing. +Input parameters: + - coef_arr: Array of coefficients. There should be as many coefficients as + mentioned in the class template parameter. +Throws: Nothing +============================================================================== +*/ + +template +void Upsampler2x4Neon ::set_coefs (const double coef_arr [NBR_COEFS]) +{ + assert (coef_arr != 0); + + for (int i = 0; i < NBR_COEFS; ++i) + { + _filter [i + 2]._coef4 = vdupq_n_f32 (float (coef_arr [i])); + } +} + + + +/* +============================================================================== +Name: process_sample +Description: + Upsamples (x2) the input vector, generating two output vectors. +Input parameters: + - input: The input vector. +Output parameters: + - out_0: First output vector. + - out_1: Second output vector. +Throws: Nothing +============================================================================== +*/ + +template +void Upsampler2x4Neon ::process_sample (float32x4_t &out_0, float32x4_t &out_1, float32x4_t input) +{ + float32x4_t even = input; + float32x4_t odd = input; + StageProc4Neon ::process_sample_pos ( + NBR_COEFS, + even, + odd, + &_filter [0] + ); + out_0 = even; + out_1 = odd; +} + + + +/* +============================================================================== +Name: process_block +Description: + Upsamples (x2) the input vector block. + Input and output blocks may overlap, see assert() for details. +Input parameters: + - in_ptr: Input array, containing nbr_spl vector. + No alignment constraint. + - nbr_spl: Number of input vectors to process, > 0 +Output parameters: + - out_0_ptr: Output vector array, capacity: nbr_spl * 2 vectors. + No alignment constraint. +Throws: Nothing +============================================================================== +*/ + +template +void Upsampler2x4Neon ::process_block (float out_ptr [], const float in_ptr [], long nbr_spl) +{ + assert (out_ptr != 0); + assert (in_ptr != 0); + assert (out_ptr >= in_ptr + nbr_spl * 4 || in_ptr >= out_ptr + nbr_spl * 4); + assert (nbr_spl > 0); + + long pos = 0; + do + { + const float32x4_t src = vreinterpretq_f32_u8 ( + vld1q_u8 (reinterpret_cast (in_ptr + pos * 4)) + ); + float32x4_t dst_0; + float32x4_t dst_1; + process_sample (dst_0, dst_1, src); + vst1q_u8 ( + reinterpret_cast (out_ptr + pos * 8 ), + vreinterpretq_u8_f32 (dst_0) + ); + vst1q_u8 ( + reinterpret_cast (out_ptr + pos * 8 + 4), + vreinterpretq_u8_f32 (dst_1) + ); + ++ pos; + } + while (pos < nbr_spl); +} + + + +/* +============================================================================== +Name: clear_buffers +Description: + Clears filter memory, as if it processed silence since an infinite amount + of time. +Throws: Nothing +============================================================================== +*/ + +template +void Upsampler2x4Neon ::clear_buffers () +{ + for (int i = 0; i < NBR_COEFS + 2; ++i) + { + _filter [i]._mem4 = vdupq_n_f32 (0); + } +} + + + +/*\\\ PROTECTED \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/ + + + +/*\\\ PRIVATE \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/ + + + +} // namespace hiir + + + +#endif // hiir_Upsampler2x4Neon_CODEHEADER_INCLUDED + + + +/*\\\ EOF \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/ diff --git a/src/external/hiir/Upsampler2x4Sse.h b/src/external/hiir/Upsampler2x4Sse.h new file mode 100644 index 00000000..629abc07 --- /dev/null +++ b/src/external/hiir/Upsampler2x4Sse.h @@ -0,0 +1,113 @@ +/***************************************************************************** + + Upsampler2x4Sse.h + Author: Laurent de Soras, 2015 + +Upsamples vectors of 4 float by a factor 2 the input signal, using the SSE +instruction set. + +This object must be aligned on a 16-byte boundary! + +Template parameters: + - NC: number of coefficients, > 0 + +--- Legal stuff --- + +This program is free software. It comes without any warranty, to +the extent permitted by applicable law. You can redistribute it +and/or modify it under the terms of the Do What The Fuck You Want +To Public License, Version 2, as published by Sam Hocevar. See +http://sam.zoy.org/wtfpl/COPYING for more details. + +*Tab=3***********************************************************************/ + + + +#pragma once +#if ! defined (hiir_Upsampler2x4Sse_HEADER_INCLUDED) +#define hiir_Upsampler2x4Sse_HEADER_INCLUDED + +#if defined (_MSC_VER) + #pragma warning (4 : 4250) +#endif + + + +/*\\\ INCLUDE FILES \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/ + +#include "hiir/def.h" +#include "hiir/StageDataSse.h" + +#include + +#include + + + +namespace hiir +{ + + + +template +class Upsampler2x4Sse +{ + + static_assert ((NC > 0), "Number of coefficient must be positive."); + +/*\\\ PUBLIC \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/ + +public: + + enum { NBR_COEFS = NC }; + + Upsampler2x4Sse (); + + void set_coefs (const double coef_arr [NBR_COEFS]); + hiir_FORCEINLINE void + process_sample (__m128 &out_0, __m128 &out_1, __m128 input); + void process_block (float out_ptr [], const float in_ptr [], long nbr_spl); + void clear_buffers (); + + + +/*\\\ PROTECTED \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/ + +protected: + + + +/*\\\ PRIVATE \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/ + +private: + + typedef std::array Filter; // Stages 0 and 1 contain only input memories + + Filter _filter; // Should be the first member (thus easier to align) + + + +/*\\\ FORBIDDEN MEMBER FUNCTIONS \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/ + +private: + + bool operator == (const Upsampler2x4Sse &other) const; + bool operator != (const Upsampler2x4Sse &other) const; + +}; // class Upsampler2x4Sse + + + +} // namespace hiir + + + +#include "hiir/Upsampler2x4Sse.hpp" + + + +#endif // hiir_Upsampler2x4Sse_HEADER_INCLUDED + + + +/*\\\ EOF \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/ diff --git a/src/external/hiir/Upsampler2x4Sse.hpp b/src/external/hiir/Upsampler2x4Sse.hpp new file mode 100644 index 00000000..9228894a --- /dev/null +++ b/src/external/hiir/Upsampler2x4Sse.hpp @@ -0,0 +1,197 @@ +/***************************************************************************** + + Upsampler2x4Sse.hpp + Author: Laurent de Soras, 2015 + +--- 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_Upsampler2x4Sse_CODEHEADER_INCLUDED) +#define hiir_Upsampler2x4Sse_CODEHEADER_INCLUDED + + + +/*\\\ INCLUDE FILES \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/ + +#include "hiir/StageProc4Sse.h" + +#include + + + +namespace hiir +{ + + + +/*\\\ PUBLIC \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/ + + + +/* +============================================================================== +Name: ctor +Throws: Nothing +============================================================================== +*/ + +template +Upsampler2x4Sse ::Upsampler2x4Sse () +: _filter () +{ + for (int i = 0; i < NBR_COEFS + 2; ++i) + { + _mm_store_ps (_filter [i]._coef, _mm_setzero_ps ()); + } + + clear_buffers (); +} + + + +/* +============================================================================== +Name: set_coefs +Description: + Sets filter coefficients. Generate them with the PolyphaseIir2Designer + class. + Call this function before doing any processing. +Input parameters: + - coef_arr: Array of coefficients. There should be as many coefficients as + mentioned in the class template parameter. +Throws: Nothing +============================================================================== +*/ + +template +void Upsampler2x4Sse ::set_coefs (const double coef_arr [NBR_COEFS]) +{ + assert (coef_arr != 0); + + for (int i = 0; i < NBR_COEFS; ++i) + { + _mm_store_ps (_filter [i + 2]._coef, _mm_set1_ps (float (coef_arr [i]))); + } +} + + + +/* +============================================================================== +Name: process_sample +Description: + Upsamples (x2) the input vector, generating two output vectors. +Input parameters: + - input: The input vector. +Output parameters: + - out_0: First output vector. + - out_1: Second output vector. +Throws: Nothing +============================================================================== +*/ + +template +void Upsampler2x4Sse ::process_sample (__m128 &out_0, __m128 &out_1, __m128 input) +{ + __m128 even = input; + __m128 odd = input; + StageProc4Sse ::process_sample_pos ( + NBR_COEFS, + even, + odd, + &_filter [0] + ); + out_0 = even; + out_1 = odd; +} + + + +/* +============================================================================== +Name: process_block +Description: + Upsamples (x2) the input vector block. + Input and output blocks may overlap, see assert() for details. +Input parameters: + - in_ptr: Input array, containing nbr_spl vector. + No alignment constraint. + - nbr_spl: Number of input vectors to process, > 0 +Output parameters: + - out_0_ptr: Output vector array, capacity: nbr_spl * 2 vectors. + No alignment constraint. +Throws: Nothing +============================================================================== +*/ + +template +void Upsampler2x4Sse ::process_block (float out_ptr [], const float in_ptr [], long nbr_spl) +{ + assert (out_ptr != 0); + assert (in_ptr != 0); + assert (out_ptr >= in_ptr + nbr_spl * 4 || in_ptr >= out_ptr + nbr_spl * 4); + assert (nbr_spl > 0); + + long pos = 0; + do + { + __m128 dst_0; + __m128 dst_1; + const __m128 src = _mm_loadu_ps (in_ptr + pos * 4); + process_sample (dst_0, dst_1, src); + _mm_storeu_ps (out_ptr + pos * 8 , dst_0); + _mm_storeu_ps (out_ptr + pos * 8 + 4, dst_1); + ++ pos; + } + while (pos < nbr_spl); +} + + + +/* +============================================================================== +Name: clear_buffers +Description: + Clears filter memory, as if it processed silence since an infinite amount + of time. +Throws: Nothing +============================================================================== +*/ + +template +void Upsampler2x4Sse ::clear_buffers () +{ + for (int i = 0; i < NBR_COEFS + 2; ++i) + { + _mm_store_ps (_filter [i]._mem, _mm_setzero_ps ()); + } +} + + + +/*\\\ PROTECTED \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/ + + + +/*\\\ PRIVATE \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/ + + + +} // namespace hiir + + + +#endif // hiir_Upsampler2x4Sse_CODEHEADER_INCLUDED + + + +/*\\\ EOF \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/ diff --git a/src/external/hiir/Upsampler2xFpu.h b/src/external/hiir/Upsampler2xFpu.h new file mode 100644 index 00000000..78de9cd8 --- /dev/null +++ b/src/external/hiir/Upsampler2xFpu.h @@ -0,0 +1,106 @@ +/***************************************************************************** + + Upsampler2xFpu.h + Author: Laurent de Soras, 2005 + +Upsamples by a factor 2 the input signal, using FPU. + +Template parameters: + - NC: number of coefficients, > 0 + +--- Legal stuff --- + +This program is free software. It comes without any warranty, to +the extent permitted by applicable law. You can redistribute it +and/or modify it under the terms of the Do What The Fuck You Want +To Public License, Version 2, as published by Sam Hocevar. See +http://sam.zoy.org/wtfpl/COPYING for more details. + +*Tab=3***********************************************************************/ + + + +#if ! defined (hiir_Upsampler2xFpu_HEADER_INCLUDED) +#define hiir_Upsampler2xFpu_HEADER_INCLUDED + +#if defined (_MSC_VER) + #pragma once + #pragma warning (4 : 4250) // "Inherits via dominance." +#endif + + + +/*\\\ INCLUDE FILES \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/ + +#include + + + +namespace hiir +{ + + + +template +class Upsampler2xFpu +{ + + static_assert ((NC > 0), "Number of coefficient must be positive."); + +/*\\\ PUBLIC \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/ + +public: + + enum { NBR_COEFS = NC }; + + Upsampler2xFpu (); + + void set_coefs (const double coef_arr [NBR_COEFS]); + inline void process_sample (float &out_0, float &out_1, float input); + void process_block (float out_ptr [], const float in_ptr [], long nbr_spl); + void clear_buffers (); + + + +/*\\\ PROTECTED \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/ + +protected: + + + +/*\\\ PRIVATE \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/ + +private: + + typedef std::array HyperGluar; + + HyperGluar _coef; + HyperGluar _x; + HyperGluar _y; + + + +/*\\\ FORBIDDEN MEMBER FUNCTIONS \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/ + +private: + + bool operator == (const Upsampler2xFpu &other); + bool operator != (const Upsampler2xFpu &other); + +}; // class Upsampler2xFpu + + + +} // namespace hiir + + + +#include "hiir/Upsampler2xFpu.hpp" + + + +#endif // hiir_Upsampler2xFpu_HEADER_INCLUDED + + + +/*\\\ EOF \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/ diff --git a/src/external/hiir/Upsampler2xFpu.hpp b/src/external/hiir/Upsampler2xFpu.hpp new file mode 100644 index 00000000..79fb05ef --- /dev/null +++ b/src/external/hiir/Upsampler2xFpu.hpp @@ -0,0 +1,205 @@ +/***************************************************************************** + + Upsampler2xFpu.hpp + Author: Laurent de Soras, 2005 + +--- Legal stuff --- + +This program is free software. It comes without any warranty, to +the extent permitted by applicable law. You can redistribute it +and/or modify it under the terms of the Do What The Fuck You Want +To Public License, Version 2, as published by Sam Hocevar. See +http://sam.zoy.org/wtfpl/COPYING for more details. + +*Tab=3***********************************************************************/ + + + +#if defined (hiir_Upsampler2xFpu_CURRENT_CODEHEADER) + #error Recursive inclusion of Upsampler2xFpu code header. +#endif +#define hiir_Upsampler2xFpu_CURRENT_CODEHEADER + +#if ! defined (hiir_Upsampler2xFpu_CODEHEADER_INCLUDED) +#define hiir_Upsampler2xFpu_CODEHEADER_INCLUDED + + + +/*\\\ INCLUDE FILES \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/ + +#include "hiir/StageProcFpu.h" + +#include + + + +namespace hiir +{ + + + +/*\\\ PUBLIC \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/ + + + +/* +============================================================================== +Name: ctor +Throws: Nothing +============================================================================== +*/ + +template +Upsampler2xFpu ::Upsampler2xFpu () +: _coef () +, _x () +, _y () +{ + for (int i = 0; i < NBR_COEFS; ++i) + { + _coef [i] = 0; + } + clear_buffers (); +} + + + +/* +============================================================================== +Name: set_coefs +Description: + Sets filter coefficients. Generate them with the PolyphaseIir2Designer + class. + Call this function before doing any processing. +Input parameters: + - coef_arr: Array of coefficients. There should be as many coefficients as + mentioned in the class template parameter. +Throws: Nothing +============================================================================== +*/ + +template +void Upsampler2xFpu ::set_coefs (const double coef_arr [NBR_COEFS]) +{ + assert (coef_arr != 0); + + for (int i = 0; i < NBR_COEFS; ++i) + { + _coef [i] = float (coef_arr [i]); + } +} + + + +/* +============================================================================== +Name: process_sample +Description: + Upsamples (x2) the input sample, generating two output samples. +Input parameters: + - input: The input sample. +Output parameters: + - out_0: First output sample. + - out_1: Second output sample. +Throws: Nothing +============================================================================== +*/ + +template +void Upsampler2xFpu ::process_sample (float &out_0, float &out_1, float input) +{ + float even = input; + float odd = input; + StageProcFpu ::process_sample_pos ( + NBR_COEFS, + even, + odd, + &_coef [0], + &_x [0], + &_y [0] + ); + out_0 = even; + out_1 = odd; +} + + + +/* +============================================================================== +Name: process_block +Description: + Upsamples (x2) the input sample block. + Input and output blocks may overlap, see assert() for details. +Input parameters: + - in_ptr: Input array, containing nbr_spl samples. + - nbr_spl: Number of input samples to process, > 0 +Output parameters: + - out_0_ptr: Output sample array, capacity: nbr_spl * 2 samples. +Throws: Nothing +============================================================================== +*/ + +template +void Upsampler2xFpu ::process_block (float out_ptr [], const float in_ptr [], long nbr_spl) +{ + assert (out_ptr != 0); + assert (in_ptr != 0); + assert (out_ptr >= in_ptr + nbr_spl || in_ptr >= out_ptr + nbr_spl); + assert (nbr_spl > 0); + + long pos = 0; + do + { + process_sample ( + out_ptr [pos * 2 ], + out_ptr [pos * 2 + 1], + in_ptr [pos] + ); + ++ pos; + } + while (pos < nbr_spl); +} + + + +/* +============================================================================== +Name: clear_buffers +Description: + Clears filter memory, as if it processed silence since an infinite amount + of time. +Throws: Nothing +============================================================================== +*/ + +template +void Upsampler2xFpu ::clear_buffers () +{ + for (int i = 0; i < NBR_COEFS; ++i) + { + _x [i] = 0; + _y [i] = 0; + } +} + + + +/*\\\ PROTECTED \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/ + + + +/*\\\ PRIVATE \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/ + + + +} // namespace hiir + + + +#endif // hiir_Upsampler2xFpu_CODEHEADER_INCLUDED + +#undef hiir_Upsampler2xFpu_CURRENT_CODEHEADER + + + +/*\\\ EOF \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/ diff --git a/src/external/hiir/def.h b/src/external/hiir/def.h new file mode 100644 index 00000000..f91e2bc0 --- /dev/null +++ b/src/external/hiir/def.h @@ -0,0 +1,61 @@ +/***************************************************************************** + + def.h + Author: Laurent de Soras, 2005 + +--- Legal stuff --- + +This program is free software. It comes without any warranty, to +the extent permitted by applicable law. You can redistribute it +and/or modify it under the terms of the Do What The Fuck You Want +To Public License, Version 2, as published by Sam Hocevar. See +http://sam.zoy.org/wtfpl/COPYING for more details. + +*Tab=3***********************************************************************/ + + + +#if ! defined (hiir_def_HEADER_INCLUDED) +#define hiir_def_HEADER_INCLUDED + +#if defined (_MSC_VER) + #pragma once + #pragma warning (4 : 4250) // "Inherits via dominance." +#endif + + + +/*\\\ INCLUDE FILES \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/ + + + +namespace hiir +{ + + + +const double PI = 3.1415926535897932384626433832795; + + + +#if defined (_MSC_VER) + + #define hiir_FORCEINLINE __forceinline + +#else + + #define hiir_FORCEINLINE inline + +#endif + + + +} // namespace hiir + + + +#endif // hiir_def_HEADER_INCLUDED + + + +/*\\\ EOF \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/ diff --git a/src/external/hiir/fnc.h b/src/external/hiir/fnc.h new file mode 100644 index 00000000..f4825e74 --- /dev/null +++ b/src/external/hiir/fnc.h @@ -0,0 +1,57 @@ +/***************************************************************************** + + fnc.h + Author: Laurent de Soras, 2005 + +--- Legal stuff --- + +This program is free software. It comes without any warranty, to +the extent permitted by applicable law. You can redistribute it +and/or modify it under the terms of the Do What The Fuck You Want +To Public License, Version 2, as published by Sam Hocevar. See +http://sam.zoy.org/wtfpl/COPYING for more details. + +*Tab=3***********************************************************************/ + + + +#if ! defined (hiir_fnc_HEADER_INCLUDED) +#define hiir_fnc_HEADER_INCLUDED + +#if defined (_MSC_VER) + #pragma once + #pragma warning (4 : 4250) // "Inherits via dominance." +#endif + + + +/*\\\ INCLUDE FILES \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/ + + + +namespace hiir +{ + + + +inline int round_int (double x); +inline int ceil_int (double x); + +template +T ipowp (T x, long n); + + + +} // namespace hiir + + + +#include "hiir/fnc.hpp" + + + +#endif // hiir_fnc_HEADER_INCLUDED + + + +/*\\\ EOF \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/ diff --git a/src/external/hiir/fnc.hpp b/src/external/hiir/fnc.hpp new file mode 100644 index 00000000..76db475e --- /dev/null +++ b/src/external/hiir/fnc.hpp @@ -0,0 +1,85 @@ +/***************************************************************************** + + fnc.hpp + Author: Laurent de Soras, 2005 + +--- Legal stuff --- + +This program is free software. It comes without any warranty, to +the extent permitted by applicable law. You can redistribute it +and/or modify it under the terms of the Do What The Fuck You Want +To Public License, Version 2, as published by Sam Hocevar. See +http://sam.zoy.org/wtfpl/COPYING for more details. + +*Tab=3***********************************************************************/ + + + +#if defined (hiir_fnc_CURRENT_CODEHEADER) + #error Recursive inclusion of fnc code header. +#endif +#define hiir_fnc_CURRENT_CODEHEADER + +#if ! defined (hiir_fnc_CODEHEADER_INCLUDED) +#define hiir_fnc_CODEHEADER_INCLUDED + + + +/*\\\ INCLUDE FILES \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/ + +#include +#include + + + +namespace hiir +{ + + + +int round_int (double x) +{ + return int (floor (x + 0.5)); +} + + + +int ceil_int (double x) +{ + return int (ceil (x)); +} + + + +template +T ipowp (T x, long n) +{ + assert (n >= 0); + + T z (1); + while (n != 0) + { + if ((n & 1) != 0) + { + z *= x; + } + n >>= 1; + x *= x; + } + + return z; +} + + + +} // namespace hiir + + + +#endif // hiir_fnc_CODEHEADER_INCLUDED + +#undef hiir_fnc_CURRENT_CODEHEADER + + + +/*\\\ EOF \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/ diff --git a/src/sfizz.h b/src/sfizz.h index 34f75085..6df7d7b8 100644 --- a/src/sfizz.h +++ b/src/sfizz.h @@ -33,220 +33,278 @@ extern "C" { #endif typedef struct sfizz_synth_t sfizz_synth_t; +typedef enum { + SFIZZ_OVERSAMPLING_X1 = 1, + SFIZZ_OVERSAMPLING_X2 = 2, + SFIZZ_OVERSAMPLING_X4 = 4, + SFIZZ_OVERSAMPLING_X8 = 8 +} sfizz_oversampling_factor_t; /** - * @brief Creates a sfizz synth. - * This object has to be freed by the caller using sfizz_free(). - * - * @return sfizz_synth_t* + * @brief Creates a sfizz synth. This object has to be freed by the caller + * using sfizz_free(). + * + * @return sfizz_synth_t* */ sfizz_synth_t* sfizz_create_synth(); /** - * @brief Frees an existing sfizz synth. - * - * @param synth The synth to destroy + * @brief Frees an existing sfizz synth. + * + * @param synth The synth to destroy */ void sfizz_free(sfizz_synth_t* synth); /** - * @brief Loads an SFZ file. - * The file path can be absolute or relative. All file operations for this SFZ file will be relative to the parent directory of the SFZ file. - * - * @param synth The sfizz synth. - * @param path A null-terminated string representing a path to an SFZ file. - * @return true when file loading went OK. - * @return false if some error occured while loading. + * @brief Loads an SFZ file. The file path can be absolute or relative. All + * file operations for this SFZ file will be relative to the parent + * directory of the SFZ file. + * + * @param synth The sfizz synth. + * @param path A null-terminated string representing a path to an SFZ + * file. + * + * @return true when file loading went OK. + * @return false if some error occured while loading. */ bool sfizz_load_file(sfizz_synth_t* synth, const char* path); /** - * @brief Returns the number of regions in the currently loaded SFZ file. - * - * @param synth - * @return int the number of regions + * @brief Returns the number of regions in the currently loaded SFZ file. + * + * @param synth The synth + * + * @return int the number of regions */ int sfizz_get_num_regions(sfizz_synth_t* synth); /** - * @brief Returns the number of groups in the currently loaded SFZ file. - * - * @param synth - * @return int the number of groups + * @brief Returns the number of groups in the currently loaded SFZ file. + * + * @param synth The synth + * + * @return int the number of groups */ int sfizz_get_num_groups(sfizz_synth_t* synth); /** - * @brief Returns the number of masters in the currently loaded SFZ file. - * - * @param synth - * @return int the number of masters + * @brief Returns the number of masters in the currently loaded SFZ file. + * + * @param synth The synth + * + * @return int the number of masters */ int sfizz_get_num_masters(sfizz_synth_t* synth); /** - * @brief Returns the number of curves in the currently loaded SFZ file. - * - * @param synth - * @return int the number of curves + * @brief Returns the number of curves in the currently loaded SFZ file. + * + * @param synth The synth + * + * @return int the number of curves */ int sfizz_get_num_curves(sfizz_synth_t* synth); /** - * @brief Returns the number of preloaded samples for the current SFZ file. - * - * @param synth - * @return int the number of preloaded samples + * @brief Returns the number of preloaded samples for the current SFZ file. + * + * @param synth The synth + * + * @return int the number of preloaded samples */ int sfizz_get_num_preloaded_samples(sfizz_synth_t* synth); /** - * @brief Returns the number of active voices. - * Note that this function is a basic indicator and does not aim to be perfect. - * In particular, it runs on the calling thread so voices may well start or stop - * while the function is checking which voice is active. - * - * @param synth - * @return int the number of playing voices + * @brief Returns the number of active voices. Note that this function is a + * basic indicator and does not aim to be perfect. In particular, it + * runs on the calling thread so voices may well start or stop while + * the function is checking which voice is active. + * + * @param synth The synth + * + * @return int the number of playing voices */ int sfizz_get_num_active_voices(sfizz_synth_t* synth); /** - * @brief Sets the expected number of samples per block. - * If unsure, give an upper bound since right now ugly things may happen if you - * go over this number. - * - * @param synth - * @param samples_per_block the number of samples per block + * @brief Sets the expected number of samples per block. If unsure, give an + * upper bound since right now ugly things may happen if you go over + * this number. + * + * @param synth The synth + * @param samples_per_block the number of samples per block */ void sfizz_set_samples_per_block(sfizz_synth_t* synth, int samples_per_block); /** - * @brief Sets the sample rate for the synth. - * This is the output sample rate. This setting does not affect the internal - * processing. - * - * @param synth - * @param sample_rate the sample rate + * @brief Sets the sample rate for the synth. This is the output sample + * rate. This setting does not affect the internal processing. + * + * @param synth The synth + * @param sample_rate the sample rate */ void sfizz_set_sample_rate(sfizz_synth_t* synth, float sample_rate); /** - * @brief Send a note on event to the synth. - * As with all MIDI events, this needs to happen before the call to sfizz_render_block - * in each block. - * - * @param synth - * @param delay the delay of the event in the block, in samples. - * @param channel the MIDI channel; currently starts at 1 (FIXME) - * @param note_number the MIDI note number - * @param velocity the MIDI velocity + * @brief Send a note on event to the synth. As with all MIDI events, this + * needs to happen before the call to sfizz_render_block in each + * block. + * + * @param synth The synth + * @param delay the delay of the event in the block, in samples. + * @param channel the MIDI channel; currently starts at 1 (FIXME) + * @param note_number the MIDI note number + * @param velocity the MIDI velocity */ void sfizz_send_note_on(sfizz_synth_t* synth, int delay, int channel, int note_number, char velocity); /** - * @brief Send a note off event to the synth. - * As with all MIDI events, this needs to happen before the call to sfizz_render_block - * in each block. As per the SFZ spec the velocity of note-off events is usually replaced - * by the note-on velocity. - * - * @param synth - * @param delay the delay of the event in the block, in samples. - * @param channel the MIDI channel; currently starts at 1 (FIXME) - * @param note_number the MIDI note number - * @param velocity the MIDI velocity + * @brief Send a note off event to the synth. As with all MIDI events, this + * needs to happen before the call to sfizz_render_block in each + * block. As per the SFZ spec the velocity of note-off events is + * usually replaced by the note-on velocity. + * + * @param synth The synth + * @param delay the delay of the event in the block, in samples. + * @param channel the MIDI channel; currently starts at 1 (FIXME) + * @param note_number the MIDI note number + * @param velocity the MIDI velocity */ void sfizz_send_note_off(sfizz_synth_t* synth, int delay, int channel, int note_number, char velocity); /** - * @brief Send a CC event to the synth. - * As with all MIDI events, this needs to happen before the call to sfizz_render_block - * in each block. - * - * @param synth - * @param delay the delay of the event in the block, in samples. - * @param channel the MIDI channel; currently starts at 1 (FIXME) - * @param cc_number the MIDI CC number - * @param cc_value the MIDI CC value + * @brief Send a CC event to the synth. As with all MIDI events, this needs + * to happen before the call to sfizz_render_block in each block. + * + * @param synth The synth + * @param delay the delay of the event in the block, in samples. + * @param channel the MIDI channel; currently starts at 1 (FIXME) + * @param cc_number the MIDI CC number + * @param cc_value the MIDI CC value */ void sfizz_send_cc(sfizz_synth_t* synth, int delay, int channel, int cc_number, char cc_value); /** - * @brief Send a pitch wheel event. (CURRENTLY UNIMPLEMENTED) - * - * @param synth - * @param delay - * @param channel - * @param pitch + * @brief Send a pitch wheel event. (CURRENTLY UNIMPLEMENTED) + * + * @param synth The synth + * @param delay The delay + * @param channel The channel + * @param pitch The pitch */ void sfizz_send_pitch_wheel(sfizz_synth_t* synth, int delay, int channel, int pitch); /** * @brief Send an aftertouch event. (CURRENTLY UNIMPLEMENTED) - * - * @param synth - * @param delay - * @param channel - * @param aftertouch + * + * @param synth + * @param delay + * @param channel + * @param aftertouch */ void sfizz_send_aftertouch(sfizz_synth_t* synth, int delay, int channel, char aftertouch); /** - * @brief Send a tempo event. (CURRENTLY UNIMPLEMENTED) - * - * @param synth - * @param delay - * @param seconds_per_quarter + * @brief Send a tempo event. (CURRENTLY UNIMPLEMENTED) + * + * @param synth The synth + * @param delay The delay + * @param seconds_per_quarter The seconds per quarter */ void sfizz_send_tempo(sfizz_synth_t* synth, int delay, float seconds_per_quarter); /** - * @brief Render a block audio data into a stereo channel. - * No other channel configuration is supported. The synth will gracefully ignore your request if you - * provide a value. You should pass all the relevant events for the block (midi notes, CCs, ...) before - * rendering each block. The synth will memorize the inputs and render sample accurates envelopes - * depending on the input events passed to it. - * - * @param synth - * @param channels pointers to the left and right channel of the output - * @param num_channels should be equal to 2 for the time being. - * @param num_frames number of frames to fill. This should be less than or equal to the expected - * samples_per_block. + * @brief Render a block audio data into a stereo channel. No other channel + * configuration is supported. The synth will gracefully ignore your + * request if you provide a value. You should pass all the relevant + * events for the block (midi notes, CCs, ...) before rendering each + * block. The synth will memorize the inputs and render sample + * accurates envelopes depending on the input events passed to it. + * + * @param synth The synth + * @param channels pointers to the left and right channel of the + * output + * @param num_channels should be equal to 2 for the time being. + * @param num_frames number of frames to fill. This should be less than + * or equal to the expected samples_per_block. */ void sfizz_render_block(sfizz_synth_t* synth, float** channels, int num_channels, int num_frames); /** - * @brief Force a memory cleanup of the samples loaded in the background. - * This should happen automatically but if you want it done more frequently - * this is the way to go. - * - * @param synth + * @brief Get the size of the preloaded data. This returns the number of + * floats used in the preloading buffers. + * + * @param synth The synth + * + * @return the preloaded data size in sizeof(floats) */ -void sfizz_force_garbage_collection(sfizz_synth_t* synth); +unsigned int sfizz_get_preload_size(sfizz_synth_t* synth); +/** + * @brief Sets the size of the preloaded data in number of floats (not + * bytes). This will disable the callbacks for the duration of the + * load. + * + * @param synth The synth + * @param[in] preload_size The preload size + */ +void sfizz_set_preload_size(sfizz_synth_t* synth, unsigned int preload_size); /** - * @brief Set the global instrument volume. - * - * @param synth - * @param volume the new volume + * @brief Get the internal oversampling rate. This is the sampling rate of + * the engine, not the output or expected rate of the calling + * function. For the latter use the `get_sample_rate()` functions. + * + * @param synth The synth + * + * @return The internal sample rate of the engine + */ +sfizz_oversampling_factor_t sfizz_get_oversampling_factor(sfizz_synth_t* synth); +/** + * @brief Set the internal oversampling rate. This is the sampling rate of + * the engine, not the output or expected rate of the calling + * function. For the latter use the `set_sample_rate()` functions. + * + * Increasing this value (up to x8 oversampling) improves the + * quality of the output at the expense of memory consumption and + * background loading speed. The main render path still uses the + * same linear interpolation algorithm and should not see its + * performance decrease, but the files are oversampled upon loading + * which increases the stress on the background loader and reduce + * the loading speed. You can tweak the size of the preloaded data + * to compensate for the memory increase, but the full loading will + * need to take place anyway. + * + * @param synth The synth + * @param[in] preload_size The preload size + * + * @return True if the oversampling factor was correct + */ +bool sfizz_set_oversampling_factor(sfizz_synth_t* synth, sfizz_oversampling_factor_t oversampling); + +/** + * @brief Set the global instrument volume. + * + * @param synth The synth + * @param volume the new volume */ void sfizz_set_volume(sfizz_synth_t* synth, float volume); /** - * @brief Get the global instrument volume. - * - * @param synth - * @return float the instrument volume + * @brief Get the global instrument volume. + * + * @param synth The synth + * + * @return float the instrument volume */ float sfizz_get_volume(sfizz_synth_t* synth); /** - * @brief Sets the number of voices used by the synth - * - * @param synth - * @param num_voices + * @brief Sets the number of voices used by the synth + * + * @param synth The synth + * @param num_voices The number voices */ void sfizz_set_num_voices(sfizz_synth_t* synth, int num_voices); /** * @brief Returns the number of voices - * - * @param synth - * @return num_voices + * + * @param synth + * @return num_voices */ int sfizz_get_num_voices(sfizz_synth_t* synth); #ifdef __cplusplus } -#endif \ No newline at end of file +#endif diff --git a/src/sfizz/Config.h b/src/sfizz/Config.h index 9176f6e0..52d26a31 100644 --- a/src/sfizz/Config.h +++ b/src/sfizz/Config.h @@ -24,6 +24,12 @@ #pragma once namespace sfz { +enum Oversampling { + x1 = 1, + x2 = 2, + x4 = 4, + x8 = 8 +}; namespace config { constexpr float defaultSampleRate { 48000 }; @@ -37,7 +43,7 @@ namespace config { constexpr float virtuallyZero { 0.00005f }; constexpr float fastReleaseDuration { 0.01 }; constexpr char defineCharacter { '$' }; - constexpr int oversamplingFactor { 2 }; + constexpr Oversampling defaultOversamplingFactor { Oversampling::x1 }; constexpr float A440 { 440.0 }; constexpr unsigned powerHistoryLength { 16 }; constexpr float voiceStealingThreshold { 0.00001 }; @@ -65,5 +71,6 @@ namespace SIMDConfig { constexpr bool sfzInterpolationCast { true }; constexpr bool mean { false }; constexpr bool meanSquared { false }; + constexpr bool upsampling { false }; } } // namespace sfz diff --git a/src/sfizz/FilePool.cpp b/src/sfizz/FilePool.cpp index 9ff3dfcf..7e9addce 100644 --- a/src/sfizz/FilePool.cpp +++ b/src/sfizz/FilePool.cpp @@ -25,30 +25,80 @@ #include "AudioBuffer.h" #include "Config.h" #include "Debug.h" +#include "Oversampler.h" #include "absl/types/span.h" #include #include #include #include #include -#include using namespace std::chrono_literals; template -std::unique_ptr> readFromFile(SndfileHandle& sndFile, int numFrames) +std::unique_ptr> upsample2x(const sfz::AudioBuffer& buffer) { - auto returnedBuffer = std::make_unique>(sndFile.channels(), numFrames); - if (sndFile.channels() == 1) { - sndFile.readf(returnedBuffer->channelWriter(0), numFrames); - } else if (sndFile.channels() == 2) { - auto tempReadBuffer = std::make_unique>(1, 2 * numFrames); - sndFile.readf(tempReadBuffer->channelWriter(0), numFrames); - sfz::readInterleaved(tempReadBuffer->getSpan(0), returnedBuffer->getSpan(0), returnedBuffer->getSpan(1)); + // auto tempBuffer = std::make_unique>(buffer.getNumFrames() * 2); + auto outputBuffer = std::make_unique>(buffer.getNumChannels(), buffer.getNumFrames() * 2); + for (int channelIdx = 0; channelIdx < buffer.getNumChannels(); channelIdx++) { + sfz::upsample2xStage(buffer.getConstSpan(channelIdx), outputBuffer->getSpan(channelIdx)); } - return returnedBuffer; + return outputBuffer; } -absl::optional sfz::FilePool::getFileInformation(const std::string& filename, uint32_t offset) noexcept +template +std::unique_ptr> upsample4x(const sfz::AudioBuffer& buffer) +{ + auto tempBuffer = std::make_unique>(buffer.getNumFrames() * 2); + auto outputBuffer = std::make_unique>(buffer.getNumChannels(), buffer.getNumFrames() * 4); + for (int channelIdx = 0; channelIdx < buffer.getNumChannels(); channelIdx++) { + sfz::upsample2xStage(buffer.getConstSpan(channelIdx), absl::MakeSpan(*tempBuffer)); + sfz::upsample4xStage(absl::MakeConstSpan(*tempBuffer), outputBuffer->getSpan(channelIdx)); + } + return outputBuffer; +} + +template +std::unique_ptr> upsample8x(const sfz::AudioBuffer& buffer) +{ + auto tempBuffer2x = std::make_unique>(buffer.getNumFrames() * 2); + auto tempBuffer4x = std::make_unique>(buffer.getNumFrames() * 4); + auto outputBuffer = std::make_unique>(buffer.getNumChannels(), buffer.getNumFrames() * 8); + for (int channelIdx = 0; channelIdx < buffer.getNumChannels(); channelIdx++) { + sfz::upsample2xStage(buffer.getConstSpan(channelIdx), absl::MakeSpan(*tempBuffer2x)); + sfz::upsample4xStage(absl::MakeConstSpan(*tempBuffer2x), absl::MakeSpan(*tempBuffer4x)); + sfz::upsample8xStage(absl::MakeConstSpan(*tempBuffer4x), outputBuffer->getSpan(channelIdx)); + } + return outputBuffer; +} + +template +std::unique_ptr> readFromFile(SndfileHandle& sndFile, uint32_t numFrames, sfz::Oversampling factor) +{ + auto baseBuffer = std::make_unique>(sndFile.channels(), numFrames); + if (sndFile.channels() == 1) { + sndFile.readf(baseBuffer->channelWriter(0), numFrames); + } else if (sndFile.channels() == 2) { + auto tempReadBuffer = std::make_unique>(1, 2 * numFrames); + sndFile.readf(tempReadBuffer->channelWriter(0), numFrames); + auto fileBuffer = std::make_unique>(2, numFrames); + sfz::readInterleaved(tempReadBuffer->getSpan(0), baseBuffer->getSpan(0), baseBuffer->getSpan(1)); + } + + switch (factor) { + case sfz::Oversampling::x1: + return baseBuffer; + case sfz::Oversampling::x2: + return upsample2x(*baseBuffer); + case sfz::Oversampling::x4: + return upsample4x(*baseBuffer); + case sfz::Oversampling::x8: + return upsample8x(*baseBuffer); + default: + return {}; + } +} + +absl::optional sfz::FilePool::getFileInformation(const std::string& filename) noexcept { fs::path file { rootDirectory / filename }; if (!fs::exists(file)) @@ -63,6 +113,7 @@ absl::optional sfz::FilePool::getFileInformation FileInformation returnedValue; returnedValue.end = static_cast(sndFile.frames()); returnedValue.sampleRate = static_cast(sndFile.samplerate()); + returnedValue.numChannels = sndFile.channels(); SF_INSTRUMENT instrumentInfo; sndFile.command(SFC_GET_INSTRUMENT, &instrumentInfo, sizeof(instrumentInfo)); @@ -71,110 +122,151 @@ absl::optional sfz::FilePool::getFileInformation returnedValue.loopEnd = instrumentInfo.loops[0].end; } - // FIXME: Large offsets will require large preloading; is this OK in practice? - const auto preloadedSize = [&]() { - if (config::preloadSize == 0) - return returnedValue.end; - else - return std::min(returnedValue.end, offset + static_cast(config::preloadSize)); - }(); - - if (preloadedData.contains(filename)) { - auto alreadyPreloaded = preloadedData[filename]; - if (preloadedSize > alreadyPreloaded->getNumFrames()) { - // FIXME: Okay, ideally here you would have a double indirection so that we can update _all_ the preloaded - // files in previous regions to account for the new offset - // - // Before this next command, some old regions and the file pool hold a shared pointer to the same audio buffer. - // This audio buffer is OK for the old regions, but too small for the new one. - // By resetting the filepool data to a new, longer audiobuffer, we are creating 2 copies of the same audio data. - // The filepool and the new regions have the longer copy, and the older regions have the shorter copy. - // This is not entirely optimal, but is it better to write a double shared pointer ? - // std::shared_ptr>> is a bit ugly... - alreadyPreloaded.reset(readFromFile(sndFile, preloadedSize).release()); - } - returnedValue.preloadedData = alreadyPreloaded; - } else { - returnedValue.preloadedData = readFromFile(sndFile, preloadedSize); - preloadedData[filename] = returnedValue.preloadedData; - } - return returnedValue; } -void sfz::FilePool::enqueueLoading(Voice* voice, const std::string* sample, int numFrames, unsigned ticket) noexcept +bool sfz::FilePool::preloadFile(const std::string& filename, uint32_t maxOffset) noexcept { - if (!loadingQueue.try_enqueue({ voice, sample, numFrames, ticket })) { - DBG("Problem enqueuing a file read for file " << sample); + fs::path file { rootDirectory / filename }; + if (!fs::exists(file)) + return false; + + SndfileHandle sndFile(reinterpret_cast(file.c_str())); + if (sndFile.channels() != 1 && sndFile.channels() != 2) + return false; + + // FIXME: Large offsets will require large preloading; is this OK in practice? Apparently sforzando does the same + const uint32_t frames = sndFile.frames(); + const auto framesToLoad = [&]() { + if (preloadSize == 0) + return frames; + else + return min(frames, maxOffset + preloadSize); + }(); + + if (preloadedFiles.contains(filename)) { + if (framesToLoad > preloadedFiles[filename].preloadedData->getNumFrames()) { + preloadedFiles[filename].preloadedData = readFromFile(sndFile, framesToLoad, oversamplingFactor); + } + } else { + preloadedFiles.insert_or_assign(filename, { readFromFile(sndFile, framesToLoad, oversamplingFactor), static_cast(sndFile.samplerate() * oversamplingFactor) }); } + + return true; +} + +sfz::FilePromisePtr sfz::FilePool::getFilePromise(const std::string& filename) noexcept +{ + auto promise = std::make_shared(); + const auto preloaded = preloadedFiles.find(filename); + if (preloaded != preloadedFiles.end()) { + promise->filename = preloaded->first; + promise->preloadedData = preloaded->second.preloadedData; + promise->sampleRate = preloaded->second.sampleRate; + promise->oversamplingFactor = oversamplingFactor; + promiseQueue.enqueue(promise); + } + return promise; +} + +void sfz::FilePool::setPreloadSize(uint32_t preloadSize) noexcept +{ + // Update all the preloaded sizes + for (auto& preloadedFile : preloadedFiles) { + const auto numFrames = preloadedFile.second.preloadedData->getNumFrames(); + const uint32_t maxOffset = numFrames > this->preloadSize ? numFrames - this->preloadSize : 0; + fs::path file { rootDirectory / std::string(preloadedFile.first) }; + SndfileHandle sndFile(reinterpret_cast(file.c_str())); + preloadedFile.second.preloadedData = readFromFile(sndFile, preloadSize + maxOffset, oversamplingFactor); + } + this->preloadSize = preloadSize; } void sfz::FilePool::loadingThread() noexcept { + FilePromisePtr promise; while (!quitThread) { + promisesToClean.clear(); + if (emptyQueue) { - while(loadingQueue.pop()) {} + while(promiseQueue.pop()) { + + } emptyQueue = false; continue; } - FileLoadingInformation fileToLoad {}; - if (!loadingQueue.wait_dequeue_timed(fileToLoad, 200ms)) { - continue; - } - - if (fileToLoad.voice == nullptr) { - DBG("Background thread error: voice is null."); - continue; - } - - if (fileToLoad.sample == nullptr) { - DBG("Background thread error: sample is null."); - continue; - } - - DBG("Background loading of: " << *fileToLoad.sample); - fs::path file { rootDirectory / *fileToLoad.sample }; - if (!fs::exists(file)) { - DBG("Background thread: no file " << *fileToLoad.sample << " exists."); + if (!promiseQueue.try_dequeue(promise)) { + std::this_thread::sleep_for(1ms); continue; } + fs::path file { rootDirectory / std::string(promise->filename) }; SndfileHandle sndFile(reinterpret_cast(file.c_str())); + if (sndFile.error() != 0) + continue; - std::lock_guard guard { fileHandleMutex }; - fileHandles.emplace_back(readFromFile(sndFile, fileToLoad.numFrames)); - fileToLoad.voice->setFileData(fileHandles.back(), fileToLoad.ticket); - } -} - -void sfz::FilePool::garbageThread() noexcept -{ - while (!quitThread) { - fileHandleMutex.lock(); - for (auto handle = fileHandles.begin(); handle < fileHandles.end();) { - if (handle->use_count() == 1) { - handle->reset(); - std::iter_swap(handle, fileHandles.end() - 1); - fileHandles.pop_back(); - } else { - handle++; - } - } - fileHandleMutex.unlock(); - std::this_thread::sleep_for(200ms); + DBG("Loading file for " << promise->filename << " in the background"); + const uint32_t frames = sndFile.frames(); + promise->fileData = readFromFile(sndFile, frames, oversamplingFactor); + promise->dataReady = true; + temporaryFilePromises.push_back(promise); + promise.reset(); } } void sfz::FilePool::clear() { - preloadedData.clear(); - fileHandles.clear(); - while (loadingQueue.pop()) { - // Pop the queue + emptyFileLoadingQueue(); + preloadedFiles.clear(); + temporaryFilePromises.clear(); + promisesToClean.clear(); +} + +void sfz::FilePool::cleanupPromises() noexcept +{ + if (temporaryFilePromises.empty()) + return; + + auto promise = temporaryFilePromises.begin(); + auto sentinel = temporaryFilePromises.end() - 1; + while (promise != temporaryFilePromises.end()) { + if (promise->use_count() == 1) { + promisesToClean.push_back(*promise); + std::iter_swap(promise, sentinel); + sentinel--; + temporaryFilePromises.pop_back(); + } else { + promise++; + } } } +void sfz::FilePool::setOversamplingFactor(sfz::Oversampling factor) noexcept +{ + float samplerateChange { static_cast(factor) / static_cast(this->oversamplingFactor) }; + for (auto& preloadedFile : preloadedFiles) { + const auto numFrames = preloadedFile.second.preloadedData->getNumFrames(); + const uint32_t maxOffset = numFrames > this->preloadSize ? numFrames - this->preloadSize : 0; + fs::path file { rootDirectory / std::string(preloadedFile.first) }; + SndfileHandle sndFile(reinterpret_cast(file.c_str())); + preloadedFile.second.preloadedData = readFromFile(sndFile, preloadSize + maxOffset, factor); + preloadedFile.second.sampleRate *= samplerateChange; + } + + this->oversamplingFactor = factor; +} + +sfz::Oversampling sfz::FilePool::getOversamplingFactor() const noexcept +{ + return oversamplingFactor; +} + +uint32_t sfz::FilePool::getPreloadSize() const noexcept +{ + return preloadSize; +} + void sfz::FilePool::emptyFileLoadingQueue() noexcept { emptyQueue = true; diff --git a/src/sfizz/FilePool.h b/src/sfizz/FilePool.h index e9dee38c..ec1a05b0 100644 --- a/src/sfizz/FilePool.h +++ b/src/sfizz/FilePool.h @@ -26,16 +26,36 @@ #include "Defaults.h" #include "LeakDetector.h" #include "AudioBuffer.h" -#include "Voice.h" +#include "SIMDHelpers.h" #include "ghc/fs_std.hpp" -#include "moodycamel/readerwriterqueue.h" #include -#include #include -#include +#include "absl/strings/string_view.h" +#include "moodycamel/readerwriterqueue.h" #include +#include namespace sfz { +using AudioBufferPtr = std::shared_ptr>; + + +struct PreloadedFileHandle +{ + std::shared_ptr> preloadedData {}; + float sampleRate { config::defaultSampleRate }; +}; + +struct FilePromise +{ + absl::string_view filename {}; + AudioBufferPtr preloadedData {}; + std::unique_ptr> fileData {}; + float sampleRate { config::defaultSampleRate }; + std::atomic dataReady { false }; + Oversampling oversamplingFactor { config::defaultOversamplingFactor }; +}; + +using FilePromisePtr = std::shared_ptr; /** * @brief This is a singleton-designed class that holds all the preloaded * data as well as functions to request new file data and collect the file @@ -53,6 +73,8 @@ namespace sfz { * to 1. A garbage collection thread then runs regularly to clear the memory of all file * handles with a reference count of 1. */ + + class FilePool { public: FilePool() { } @@ -61,7 +83,6 @@ public: { quitThread = true; fileLoadingThread.join(); - garbageCollectionThread.join(); } /** * @brief Set the root directory from which to search for files to load @@ -74,71 +95,63 @@ public: * * @return size_t */ - size_t getNumPreloadedSamples() const noexcept { return preloadedData.size(); } + size_t getNumPreloadedSamples() const noexcept { return preloadedFiles.size(); } struct FileInformation { uint32_t end { Default::sampleEndRange.getEnd() }; uint32_t loopBegin { Default::loopRange.getStart() }; uint32_t loopEnd { Default::loopRange.getEnd() }; double sampleRate { config::defaultSampleRate }; - std::shared_ptr> preloadedData; + int numChannels { 0 }; }; + /** - * @brief Get metadata information about a file as well as the first chunk of data + * @brief Get metadata information about a file. * - * If the same file was already preloaded and with a compatible offset, the handle - * is shared between the regions. Otherwise, a new handle is created (the others keep - * the old preloaded file). + * @param filename + * @return absl::optional + */ + absl::optional getFileInformation(const std::string& filename) noexcept; + + /** + * @brief Check that a file is preloaded with the proper offset bounds * * @param filename * @param offset the maximum offset to consider for preloading. The total preloaded - * size will be preloadedSize + offset - * @return absl::optional + * size will be preloadSize + offset + * @return true if the preloading went fine + * @return false if something went wrong () */ - absl::optional getFileInformation(const std::string& filename, uint32_t offset) noexcept; - /** - * @brief Queue a full loading operation for a given voice. - * - * The goal of the ticket is to avoid file loading operations that for some reason - * finish too late a "replace" a proper sample with an obsolete one for a voice. - * - * @param voice the voice to give the full file data to - * @param sample the sample file - * @param numFrames the number of frames to load from the file - * @param ticket an ideally unique ticket number for this file. - */ - void enqueueLoading(Voice* voice, const std::string* sample, int numFrames, unsigned ticket) noexcept; + bool preloadFile(const std::string& filename, uint32_t maxOffset) noexcept; + /** * @brief Clear all preloaded files. * */ void clear(); - /** - * @brief Empty the file loading queue. This function will lock and wait - * for the background thread to finish its business, so don't call it from - * the audio thread. - * - */ + + void cleanupPromises() noexcept; + FilePromisePtr getFilePromise(const std::string& filename) noexcept; + void setPreloadSize(uint32_t preloadSize) noexcept; + uint32_t getPreloadSize() const noexcept; + void setOversamplingFactor(Oversampling factor) noexcept; + Oversampling getOversamplingFactor() const noexcept; void emptyFileLoadingQueue() noexcept; private: fs::path rootDirectory; - struct FileLoadingInformation { - Voice* voice; - const std::string* sample; - int numFrames; - unsigned ticket; - }; - - moodycamel::BlockingReaderWriterQueue loadingQueue { config::numVoices }; void loadingThread() noexcept; - void garbageThread() noexcept; + + moodycamel::ReaderWriterQueue promiseQueue; + uint32_t preloadSize { config::preloadSize }; + Oversampling oversamplingFactor { config::defaultOversamplingFactor }; + // Signals bool quitThread { false }; bool emptyQueue { false }; - std::mutex fileHandleMutex; - std::vector>> fileHandles; - absl::flat_hash_map>> preloadedData; + + std::vector temporaryFilePromises; + std::vector promisesToClean; + absl::flat_hash_map preloadedFiles; std::thread fileLoadingThread { &FilePool::loadingThread, this }; - std::thread garbageCollectionThread { &FilePool::garbageThread, this }; LEAK_DETECTOR(FilePool); }; } diff --git a/src/sfizz/Oversampler.h b/src/sfizz/Oversampler.h new file mode 100644 index 00000000..68953be6 --- /dev/null +++ b/src/sfizz/Oversampler.h @@ -0,0 +1,95 @@ +// Copyright (c) 2019, Paul Ferrand +// All rights reserved. + +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: + +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. + +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#pragma once +#include +#include "absl/types/span.h" +#include "Debug.h" +#include "Config.h" +#include "hiir/Upsampler2xFpu.h" + +namespace sfz { + +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 +}; + +template +inline void upsample2xStage(absl::Span input, absl::Span output) +{ + ASSERT(output.size() >= 2 * input.size()); + hiir::Upsampler2xFpu upsampler; + upsampler.set_coefs(coeffsStage2x.data()); + upsampler.process_block(output.data(), input.data(), input.size()); +} + + +template +inline void upsample4xStage(absl::Span input, absl::Span output) +{ + ASSERT(output.size() >= 2 * input.size()); + hiir::Upsampler2xFpu upsampler; + upsampler.set_coefs(coeffsStage4x.data()); + upsampler.process_block(output.data(), input.data(), input.size()); +} + +template +inline void upsample8xStage(absl::Span input, absl::Span output) +{ + ASSERT(output.size() >= 2 * input.size()); + hiir::Upsampler2xFpu upsampler; + upsampler.set_coefs(coeffsStage8x.data()); + upsampler.process_block(output.data(), input.data(), input.size()); +} + +template<> +inline void upsample2xStage(absl::Span input, absl::Span output); +template<> +inline void upsample4xStage(absl::Span input, absl::Span output); +template<> +inline void upsample8xStage(absl::Span input, absl::Span output); +} diff --git a/src/sfizz/OversamplerDummy.cpp b/src/sfizz/OversamplerDummy.cpp new file mode 100644 index 00000000..b92477b5 --- /dev/null +++ b/src/sfizz/OversamplerDummy.cpp @@ -0,0 +1,40 @@ +// Copyright (c) 2019, Paul Ferrand +// All rights reserved. + +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: + +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. + +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "Oversampler.h" + +template<> +inline void sfz::upsample2xStage(absl::Span input, absl::Span output) +{ + sfz::upsample2xStage(input, output); +} +template<> +inline void sfz::upsample4xStage(absl::Span input, absl::Span output) +{ + sfz::upsample4xStage(input, output); +} +template<> +inline void sfz::upsample8xStage(absl::Span input, absl::Span output) +{ + sfz::upsample8xStage(input, output); +} diff --git a/src/sfizz/OversamplerSSE.cpp b/src/sfizz/OversamplerSSE.cpp new file mode 100644 index 00000000..f3617a02 --- /dev/null +++ b/src/sfizz/OversamplerSSE.cpp @@ -0,0 +1,50 @@ +// Copyright (c) 2019, Paul Ferrand +// All rights reserved. + +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: + +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. + +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "Oversampler.h" +#include "hiir/Upsampler2x4Sse.h" + +template<> +inline void sfz::upsample2xStage(absl::Span input, absl::Span output) +{ + ASSERT(output.size() >= 2 * input.size()); + hiir::Upsampler2x4Sse upsampler; + upsampler.set_coefs(sfz::coeffsStage2x.data()); + upsampler.process_block(output.data(), input.data(), input.size()); +} +template<> +inline void sfz::upsample4xStage(absl::Span input, absl::Span output) +{ + ASSERT(output.size() >= 2 * input.size()); + hiir::Upsampler2x4Sse upsampler; + upsampler.set_coefs(sfz::coeffsStage4x.data()); + upsampler.process_block(output.data(), input.data(), input.size()); +} +template<> +inline void sfz::upsample8xStage(absl::Span input, absl::Span output) +{ + ASSERT(output.size() >= 2 * input.size()); + hiir::Upsampler2x4Sse upsampler; + upsampler.set_coefs(sfz::coeffsStage8x.data()); + upsampler.process_block(output.data(), input.data(), input.size()); +} diff --git a/src/sfizz/Region.cpp b/src/sfizz/Region.cpp index 26a8ba41..2531ba81 100644 --- a/src/sfizz/Region.cpp +++ b/src/sfizz/Region.cpp @@ -644,9 +644,9 @@ float sfz::Region::getBaseGain() noexcept return normalizePercents(amplitude); } -uint32_t sfz::Region::getOffset() noexcept +uint32_t sfz::Region::getOffset(Oversampling factor) noexcept { - return offset + offsetDistribution(Random::randomGenerator); + return (offset + offsetDistribution(Random::randomGenerator)) * factor; } float sfz::Region::getDelay() noexcept @@ -654,25 +654,19 @@ float sfz::Region::getDelay() noexcept return delay + delayDistribution(Random::randomGenerator); } -uint32_t sfz::Region::trueSampleEnd() const noexcept +uint32_t sfz::Region::trueSampleEnd(Oversampling factor) const noexcept { - return min(sampleEnd, loopRange.getEnd()); + return min(sampleEnd, loopRange.getEnd()) * factor; } -bool sfz::Region::canUsePreloadedData() const noexcept +uint32_t sfz::Region::loopStart(Oversampling factor) const noexcept { - if (preloadedData == nullptr) - return false; - - return trueSampleEnd() < static_cast(preloadedData->getNumFrames()); + return loopRange.getStart() * factor; } -bool sfz::Region::isStereo() const noexcept +uint32_t sfz::Region::loopEnd(Oversampling factor) const noexcept { - if (isGenerator()) - return 1; - - return (this->preloadedData->getNumChannels() == 2); + return loopRange.getEnd() * factor; } template diff --git a/src/sfizz/Region.h b/src/sfizz/Region.h index 6943c1e1..9a697e15 100644 --- a/src/sfizz/Region.h +++ b/src/sfizz/Region.h @@ -149,13 +149,7 @@ struct Region { * @return false */ void registerTempo(float secondsPerQuarter) noexcept; - /** - * @brief Is the underlying region sample a stereo one? - * - * @return true - * @return false - */ - bool isStereo() const noexcept; + /** * @brief Get the base pitch of the region depending on which note has been * pressed and at which velocity. @@ -207,7 +201,7 @@ struct Region { * * @return uint32_t */ - uint32_t getOffset() noexcept; + uint32_t getOffset(Oversampling factor = x1) noexcept; /** * @brief Get the region delay in seconds * @@ -220,14 +214,7 @@ struct Region { * * @return uint32_t */ - uint32_t trueSampleEnd() const noexcept; - /** - * @brief Can the region use the preloaded data only to play its full range? - * - * @return true - * @return false - */ - bool canUsePreloadedData() const noexcept; + uint32_t trueSampleEnd(Oversampling factor = x1) const noexcept; /** * @brief Parse a new opcode into the region to fill in the proper parameters. * This must be called multiple times for each opcode applying to this region. @@ -238,6 +225,9 @@ struct Region { */ bool parseOpcode(const Opcode& opcode); + uint32_t loopStart(Oversampling factor = x1) const noexcept; + uint32_t loopEnd(Oversampling factor = x1) const noexcept; + // Sound source: sample playback std::string sample {}; // Sample float delay { Default::delay }; // delay @@ -323,8 +313,7 @@ struct Region { EGDescription pitchEG; EGDescription filterEG; - double sampleRate { config::defaultSampleRate }; - std::shared_ptr> preloadedData { nullptr }; + bool isStereo { false }; private: const MidiState& midiState; bool keySwitched { true }; diff --git a/src/sfizz/Resources.h b/src/sfizz/Resources.h new file mode 100644 index 00000000..3fb24a5f --- /dev/null +++ b/src/sfizz/Resources.h @@ -0,0 +1,10 @@ +#pragma once +#include "FilePool.h" + +namespace sfz +{ +struct Resources +{ + FilePool filePool; +}; +} diff --git a/src/sfizz/Synth.cpp b/src/sfizz/Synth.cpp index 8a57bf96..dea38bc3 100644 --- a/src/sfizz/Synth.cpp +++ b/src/sfizz/Synth.cpp @@ -123,7 +123,7 @@ void sfz::Synth::clear() for (auto& list: ccActivationLists) list.clear(); regions.clear(); - filePool.clear(); + resources.filePool.clear(); hasGlobal = false; hasControl = false; numGroups = 0; @@ -219,7 +219,7 @@ bool sfz::Synth::loadSfzFile(const fs::path& filename) if (regions.empty()) return false; - filePool.setRootDirectory(this->rootDirectory); + resources.filePool.setRootDirectory(this->rootDirectory); auto lastRegion = regions.end() - 1; auto currentRegion = regions.begin(); @@ -227,7 +227,7 @@ bool sfz::Synth::loadSfzFile(const fs::path& filename) auto region = currentRegion->get(); if (!region->isGenerator()) { - auto fileInformation = filePool.getFileInformation(region->sample, region->offset + region->offsetRandom); + auto fileInformation = resources.filePool.getFileInformation(region->sample); if (!fileInformation) { DBG("Removing the region with sample " << region->sample); std::iter_swap(currentRegion, lastRegion); @@ -236,8 +236,12 @@ bool sfz::Synth::loadSfzFile(const fs::path& filename) } region->sampleEnd = std::min(region->sampleEnd, fileInformation->end); region->loopRange.shrinkIfSmaller(fileInformation->loopBegin, fileInformation->loopEnd); - region->preloadedData = fileInformation->preloadedData; - region->sampleRate = fileInformation->sampleRate; + if (fileInformation->numChannels == 2) + region->isStereo = true; + + // TODO: adjust with LFO targets + const auto maxOffset { region->offset + region->offsetRandom }; + resources.filePool.preloadFile(region->sample, maxOffset); } for (auto note = 0; note < 128; note++) { @@ -312,9 +316,7 @@ int sfz::Synth::getNumActiveVoices() const noexcept void sfz::Synth::garbageCollect() noexcept { - for (auto& voice : voices) { - voice->garbageCollect(); - } + } void sfz::Synth::setSamplesPerBlock(int samplesPerBlock) noexcept @@ -348,6 +350,8 @@ void sfz::Synth::renderBlock(AudioSpan buffer) noexcept ScopedFTZ ftz; buffer.fill(0.0f); + resources.filePool.cleanupPromises(); + AtomicGuard callbackGuard { inCallback }; if (!canEnterCallback) return; @@ -387,10 +391,6 @@ void sfz::Synth::noteOn(int delay, int channel, int noteNumber, uint8_t velocity continue; voice->startVoice(region, delay, channel, noteNumber, velocity, Voice::TriggerType::NoteOn); - if (!region->isGenerator()) { - voice->expectFileData(fileTicket); - filePool.enqueueLoading(voice, ®ion->sample, region->trueSampleEnd(), fileTicket++); - } } } } @@ -420,10 +420,6 @@ void sfz::Synth::noteOff(int delay, int channel, int noteNumber, uint8_t velocit continue; voice->startVoice(region, delay, channel, noteNumber, replacedVelocity, Voice::TriggerType::NoteOff); - if (!region->isGenerator()) { - voice->expectFileData(fileTicket); - filePool.enqueueLoading(voice, ®ion->sample, region->trueSampleEnd(), fileTicket++); - } } } } @@ -449,10 +445,6 @@ void sfz::Synth::cc(int delay, int channel, int ccNumber, uint8_t ccValue) noexc continue; voice->startVoice(region, delay, channel, ccNumber, ccValue, Voice::TriggerType::CC); - if (!region->isGenerator()) { - voice->expectFileData(fileTicket); - filePool.enqueueLoading(voice, ®ion->sample, region->trueSampleEnd(), fileTicket++); - } } } } @@ -503,7 +495,7 @@ std::set sfz::Synth::getUnknownOpcodes() const noexcept } size_t sfz::Synth::getNumPreloadedSamples() const noexcept { - return filePool.getNumPreloadedSamples(); + return resources.filePool.getNumPreloadedSamples(); } float sfz::Synth::getVolume() const noexcept @@ -533,10 +525,9 @@ void sfz::Synth::resetVoices(int numVoices) std::this_thread::sleep_for(1ms); } - filePool.emptyFileLoadingQueue(); voices.clear(); for (int i = 0; i < numVoices; ++i) - voices.push_back(std::make_unique(midiState)); + voices.push_back(std::make_unique(midiState, resources)); for (auto& voice: voices) { voice->setSampleRate(this->sampleRate); @@ -546,3 +537,38 @@ void sfz::Synth::resetVoices(int numVoices) voiceViewArray.reserve(numVoices); this->numVoices = numVoices; } + +void sfz::Synth::setOversamplingFactor(sfz::Oversampling factor) noexcept +{ + AtomicDisabler callbackDisabler{ canEnterCallback }; + while (inCallback) { + std::this_thread::sleep_for(1ms); + } + + for (auto& voice: voices) + voice->reset(); + + resources.filePool.emptyFileLoadingQueue(); + resources.filePool.setOversamplingFactor(factor); + oversamplingFactor = factor; +} + +sfz::Oversampling sfz::Synth::getOversamplingFactor() const noexcept +{ + return oversamplingFactor; +} + +void sfz::Synth::setPreloadSize(uint32_t preloadSize) noexcept +{ + AtomicDisabler callbackDisabler{ canEnterCallback }; + while (inCallback) { + std::this_thread::sleep_for(1ms); + } + + resources.filePool.setPreloadSize(preloadSize); +} + +uint32_t sfz::Synth::getPreloadSize() const noexcept +{ + return resources.filePool.getPreloadSize(); +} diff --git a/src/sfizz/Synth.h b/src/sfizz/Synth.h index 35280db0..410c14f4 100644 --- a/src/sfizz/Synth.h +++ b/src/sfizz/Synth.h @@ -22,8 +22,9 @@ // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #pragma once -#include "FilePool.h" +#include "Resources.h" #include "Parser.h" +#include "Voice.h" #include "Region.h" #include "LeakDetector.h" #include "MidiState.h" @@ -279,6 +280,36 @@ public: * */ void garbageCollect() noexcept; + + /** + * @brief Set the oversampling factor to a new value. This will disable all callbacks + * kill all the voices, and trigger a reloading of every file in the FilePool under + * the new oversampling. + * + * @param factor + */ + void setOversamplingFactor(Oversampling factor) noexcept; + + /** + * @brief get the current oversampling factor + * + * @return Oversampling + */ + Oversampling getOversamplingFactor() const noexcept; + + /** + * @brief Set the preloaded file size. This will disable the callback. + * + * @param factor + */ + void setPreloadSize(uint32_t preloadSize) noexcept; + + /** + * @brief get the current preloaded file size + * + * @return Oversampling + */ + uint32_t getPreloadSize() const noexcept; protected: /** * @brief The parser callback; this is called by the parent object each time @@ -337,11 +368,6 @@ private: std::vector masterOpcodes; std::vector groupOpcodes; - // Singletons passed as references to the voices - // TODO: these should probably go in a global singleton holder along with a buffer distribution and LFO/EG stuff... - FilePool filePool; - MidiState midiState; - /** * @brief Find a voice that is not currently playing * @@ -370,6 +396,7 @@ private: float sampleRate { config::defaultSampleRate }; float volume { Default::volume }; int numVoices { config::numVoices }; + Oversampling oversamplingFactor { config::defaultOversamplingFactor }; // Distribution used to generate random value for the *rand opcodes std::uniform_real_distribution randNoteDistribution { 0, 1 }; @@ -379,6 +406,10 @@ private: std::atomic canEnterCallback { true }; std::atomic inCallback { false }; + // Singletons passed as references to the voices + Resources resources; + MidiState midiState; + LEAK_DETECTOR(Synth); }; diff --git a/src/sfizz/Voice.cpp b/src/sfizz/Voice.cpp index cf351ca3..62c79437 100644 --- a/src/sfizz/Voice.cpp +++ b/src/sfizz/Voice.cpp @@ -31,8 +31,8 @@ #include "absl/algorithm/container.h" #include -sfz::Voice::Voice(const MidiState& midiState) - : midiState(midiState) +sfz::Voice::Voice(const sfz::MidiState& midiState, sfz::Resources& resources) + : midiState(midiState), resources(resources) { } @@ -44,6 +44,7 @@ void sfz::Voice::startVoice(Region* region, int delay, int channel, int number, triggerValue = value; this->region = region; + state = State::playing; ASSERT(delay >= 0); if (delay < 0) @@ -51,9 +52,19 @@ void sfz::Voice::startVoice(Region* region, int delay, int channel, int number, // DBG("Starting voice with " << region->sample); - state = State::playing; - speedRatio = static_cast(region->sampleRate / this->sampleRate); + if (!region->isGenerator()) { + currentPromise = resources.filePool.getFilePromise(region->sample); + if (currentPromise == nullptr) { + DBG("[Voice] Could not fetch the file promise for sample " << region->sample); + reset(); + return; + } + speedRatio = static_cast(currentPromise->sampleRate / this->sampleRate); + DBG("[Voice] Sample rate for " << region->sample << " is " << currentPromise->sampleRate); + DBG("[Voice] Speed ratio set to " << speedRatio); + } pitchRatio = region->getBasePitchVariation(number, value); + DBG("[Voice] Pitch ratio set to " << pitchRatio); baseVolumedB = region->getBaseVolumedB(number); @@ -118,15 +129,6 @@ void sfz::Voice::prepareEGEnvelope(int delay, uint8_t velocity) noexcept normalizePercents(region->amplitudeEG.getStart(midiState.cc, velocity))); } -void sfz::Voice::setFileData(std::shared_ptr> file, unsigned ticket) noexcept -{ - if (ticket != this->ticket) - return; - - fileData = std::move(file); - dataReady.store(true); -} - bool sfz::Voice::isFree() const noexcept { return (region == nullptr); @@ -245,7 +247,7 @@ void sfz::Voice::renderBlock(AudioSpan buffer) noexcept else fillWithData(delayed_buffer); - if (region->isStereo()) + if (region->isStereo) processStereo(buffer); else processMono(buffer); @@ -352,12 +354,13 @@ void sfz::Voice::fillWithData(AudioSpan buffer) noexcept return; auto source { [&]() { - if (region->canUsePreloadedData()) - return AudioSpan(*region->preloadedData); - else if (!dataReady) - return AudioSpan(*region->preloadedData); + // if (region->trueSampleEnd() < currentPromise->preloadedData->getNumFrames()) + // return AudioSpan(*currentPromise->preloadedData); + // else + if (!currentPromise->dataReady) + return AudioSpan(*currentPromise->preloadedData); else - return AudioSpan(*fileData); + return AudioSpan(*currentPromise->fileData); }() }; auto indices = indexSpan.first(buffer.getNumFrames()); @@ -372,9 +375,12 @@ void sfz::Voice::fillWithData(AudioSpan buffer) noexcept add(sourcePosition, indices); //FIXME : all this casting is driving me crazy - const auto sampleEnd = min(static_cast(region->trueSampleEnd()), static_cast(source.getNumFrames())) - 1; - if (region->shouldLoop() && region->loopRange.getEnd() <= source.getNumFrames()) { - const auto offset = sampleEnd - static_cast(region->loopRange.getStart()); + const auto sampleEnd = min( + static_cast(region->trueSampleEnd(currentPromise->oversamplingFactor)), + static_cast(source.getNumFrames()) + ) - 1; + if (region->shouldLoop() && region->loopEnd(currentPromise->oversamplingFactor) <= source.getNumFrames()) { + const auto offset = sampleEnd - static_cast(region->loopStart(currentPromise->oversamplingFactor)); for (auto* index = indices.begin(); index < indices.end(); ++index) { if (*index > sampleEnd) { const auto remainingElements = static_cast(std::distance(index, indices.end())); @@ -483,30 +489,17 @@ sfz::Voice::TriggerType sfz::Voice::getTriggerType() const noexcept void sfz::Voice::reset() noexcept { - dataReady.store(false); - fileData.reset(); state = State::idle; if (region != nullptr) { DBG("Reset voice with sample " << region->sample); } region = nullptr; + currentPromise.reset(); sourcePosition = 0; floatPositionOffset = 0.0f; noteIsOff = false; } -void sfz::Voice::garbageCollect() noexcept -{ - if (state == State::idle && region == nullptr) { - fileData.reset(); - } -} - -void sfz::Voice::expectFileData(unsigned ticket) -{ - this->ticket = ticket; -} - float sfz::Voice::getMeanSquaredAverage() const noexcept { return powerHistory.getAverage(); diff --git a/src/sfizz/Voice.h b/src/sfizz/Voice.h index 350265c2..d90e0f7e 100644 --- a/src/sfizz/Voice.h +++ b/src/sfizz/Voice.h @@ -29,6 +29,7 @@ #include "Region.h" #include "AudioBuffer.h" #include "MidiState.h" +#include "Resources.h" #include "AudioSpan.h" #include "LeakDetector.h" #include @@ -50,7 +51,7 @@ public: * * @param midiState */ - Voice(const MidiState& midiState); + Voice(const MidiState& midiState, Resources& resources); enum class TriggerType { NoteOn, NoteOff, @@ -98,23 +99,6 @@ public: */ void startVoice(Region* region, int delay, int channel, int number, uint8_t value, TriggerType triggerType) noexcept; - /** - * @brief Tells the voice that it should expect to receive a file at some point using the - * setFileData() function. The ticket is a unique identifier that will prevent the file data - * to be set "too late"; if the voice receives the file for an older ticket, it will discard - * it. - * - * @param ticket - */ - void expectFileData(unsigned ticket); - /** - * @brief Sets the file data for a given ticket. The voice can freely release and destroy the - * shared pointer, as it will be garbage collected by the file pool afterwards. - * - * @param file - * @param ticket - */ - void setFileData(std::shared_ptr> file, unsigned ticket) noexcept; /** * @brief Register a note-off event; this may trigger a release. * @@ -221,11 +205,6 @@ public: * */ void reset() noexcept; - /** - * @brief Clear the loaded file data if it's not useful anymore - * - */ - void garbageCollect() noexcept; /** * @brief Get the mean squared power of the last rendered block. This is used @@ -309,9 +288,7 @@ private: int sourcePosition { 0 }; int initialDelay { 0 }; - std::atomic dataReady { false }; - std::shared_ptr> fileData { nullptr }; - unsigned ticket { 0 }; + FilePromisePtr currentPromise { nullptr }; Buffer tempBuffer1; Buffer tempBuffer2; @@ -326,6 +303,8 @@ private: float sampleRate { config::defaultSampleRate }; const MidiState& midiState; + Resources& resources; + ADSREnvelope egEnvelope; LinearEnvelope volumeEnvelope; // dB events but the envelope output is linear gain LinearEnvelope amplitudeEnvelope; // linear events diff --git a/src/sfizz/sfizz_wrapper.cpp b/src/sfizz/sfizz_wrapper.cpp index 7b3a4a01..736241d8 100644 --- a/src/sfizz/sfizz_wrapper.cpp +++ b/src/sfizz/sfizz_wrapper.cpp @@ -21,6 +21,7 @@ // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +#include "Config.h" #include "Synth.h" #include "sfizz.h" @@ -127,10 +128,44 @@ void sfizz_render_block(sfizz_synth_t* synth, float** channels, int num_channels self->renderBlock({{channels[0], channels[1]}, static_cast(num_frames)}); } -void sfizz_force_garbage_collection(sfizz_synth_t* synth) +unsigned int sfizz_get_preload_size(sfizz_synth_t* synth) { auto self = reinterpret_cast(synth); - self->garbageCollect(); + return self->getPreloadSize(); +} +void sfizz_set_preload_size(sfizz_synth_t* synth, unsigned int preload_size) +{ + auto self = reinterpret_cast(synth); + self->setPreloadSize(preload_size); +} + +sfizz_oversampling_factor_t sfizz_get_oversampling_factor(sfizz_synth_t* synth) +{ + auto self = reinterpret_cast(synth); + return static_cast(self->getOversamplingFactor()); +} + +bool sfizz_set_oversampling_factor(sfizz_synth_t* synth, sfizz_oversampling_factor_t oversampling) +{ + auto self = reinterpret_cast(synth); + using sfz::Oversampling; + switch(oversampling) + { + case SFIZZ_OVERSAMPLING_X1: + self->setOversamplingFactor(sfz::Oversampling::x1); + return true; + case SFIZZ_OVERSAMPLING_X2: + self->setOversamplingFactor(sfz::Oversampling::x2); + return true; + case SFIZZ_OVERSAMPLING_X4: + self->setOversamplingFactor(sfz::Oversampling::x4); + return true; + case SFIZZ_OVERSAMPLING_X8: + self->setOversamplingFactor(sfz::Oversampling::x8); + return true; + default: + return false; + } } void sfizz_set_volume(sfizz_synth_t* synth, float volume) @@ -159,4 +194,4 @@ int sfizz_get_num_voices(sfizz_synth_t* synth) #ifdef __cplusplus } -#endif \ No newline at end of file +#endif diff --git a/tests/FilesT.cpp b/tests/FilesT.cpp index 3dd5a1cd..20e5ab4b 100644 --- a/tests/FilesT.cpp +++ b/tests/FilesT.cpp @@ -257,9 +257,9 @@ TEST_CASE("[Files] Channels (channels.sfz)") synth.loadSfzFile(fs::current_path() / "tests/TestFiles/channels.sfz"); REQUIRE(synth.getNumRegions() == 2); REQUIRE(synth.getRegionView(0)->sample == "mono_sample.wav"); - REQUIRE(!synth.getRegionView(0)->isStereo()); + REQUIRE(!synth.getRegionView(0)->isStereo); REQUIRE(synth.getRegionView(1)->sample == "stereo_sample.wav"); - REQUIRE(synth.getRegionView(1)->isStereo()); + REQUIRE(synth.getRegionView(1)->isStereo); } TEST_CASE("[Files] sw_default") @@ -315,4 +315,4 @@ TEST_CASE("[Files] wrong (overlapping) replacement for defines") REQUIRE( synth.getRegionView(2)->amplitudeCC ); REQUIRE( synth.getRegionView(2)->amplitudeCC->first == 10 ); REQUIRE( synth.getRegionView(2)->amplitudeCC->second == 34.0f ); -} \ No newline at end of file +} diff --git a/tests/SynthT.cpp b/tests/SynthT.cpp index 7a6d2082..f5dadec0 100644 --- a/tests/SynthT.cpp +++ b/tests/SynthT.cpp @@ -21,6 +21,7 @@ // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +#include "Config.h" #include "sfizz.hpp" #include "catch2/catch.hpp" using namespace Catch::literals; @@ -89,3 +90,33 @@ TEST_CASE("[Synth] Check that the sample per block and sample rate are actually REQUIRE( synth.getVoiceView(i)->getSampleRate() == 48000.0f ); } } + +TEST_CASE("[Synth] Check that we can change the size of the preload before and after loading") +{ + sfz::Synth synth; + synth.setPreloadSize(512); + sfz::AudioBuffer buffer { 2, blockSize }; + synth.loadSfzFile(fs::current_path() / "tests/TestFiles/groups_avl.sfz"); + synth.setPreloadSize(1024); + + synth.noteOn(0, 1, 36, 24); + synth.noteOn(0, 1, 36, 89); + synth.renderBlock(buffer); + synth.setPreloadSize(2048); + synth.renderBlock(buffer); +} + +TEST_CASE("[Synth] Check that we can change the oversampling factor before and after loading") +{ + sfz::Synth synth; + synth.setOversamplingFactor(sfz::x2); + sfz::AudioBuffer buffer { 2, blockSize }; + synth.loadSfzFile(fs::current_path() / "tests/TestFiles/groups_avl.sfz"); + synth.setOversamplingFactor(sfz::x4); + + synth.noteOn(0, 1, 36, 24); + synth.noteOn(0, 1, 36, 89); + synth.renderBlock(buffer); + synth.setOversamplingFactor(sfz::x2); + synth.renderBlock(buffer); +}