Merge branch 'feature/oversampling' into develop

This commit is contained in:
Paul Ferrand 2019-12-02 00:11:47 +01:00
commit 8f391b9654
38 changed files with 3182 additions and 406 deletions

View file

@ -23,6 +23,7 @@
#include "sfizz.hpp"
#include <absl/flags/parse.h>
#include <absl/flags/flag.h>
#include <absl/types/span.h>
#include <atomic>
#include <cstddef>
@ -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;
}
}

View file

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

View file

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

View file

@ -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 ] ;
].

95
src/external/hiir/StageDataNeon.h vendored Normal file
View file

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

67
src/external/hiir/StageDataSse.h vendored Normal file
View file

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

100
src/external/hiir/StageProc4Neon.h vendored Normal file
View file

@ -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 <int REMAINING>
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 <REMAINING> &other) = delete;
~StageProc4Neon () = delete;
StageProc4Neon <REMAINING> &
operator = (const StageProc4Neon <REMAINING> &other) = delete;
bool operator == (const StageProc4Neon <REMAINING> &other) const = delete;
bool operator != (const StageProc4Neon <REMAINING> &other) const = delete;
}; // class StageProc4Neon
} // namespace hiir
#include "hiir/StageProc4Neon.hpp"
#endif // hiir_StageProc4Neon_HEADER_INCLUDED
/*\\\ EOF \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/

174
src/external/hiir/StageProc4Neon.hpp vendored Normal file
View file

@ -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 <int REMAINING>
void StageProc4Neon <REMAINING>::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 <REMAINING - 2>::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 <int REMAINING>
void StageProc4Neon <REMAINING>::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 <REMAINING - 2>::process_sample_neg (
nbr_coefs,
spl_0,
spl_1,
stage_arr
);
}
/*\\\ PROTECTED \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/
/*\\\ PRIVATE \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/
} // namespace hiir
#endif // hiir_StageProc4Neon_CODEHEADER_INCLUDED
/*\\\ EOF \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/

102
src/external/hiir/StageProc4Sse.h vendored Normal file
View file

@ -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 <xmmintrin.h>
namespace hiir
{
class StageDataSse;
template <int REMAINING>
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 <REMAINING> &other);
StageProc4Sse <REMAINING> &
operator = (const StageProc4Sse <REMAINING> &other);
bool operator == (const StageProc4Sse <REMAINING> &other);
bool operator != (const StageProc4Sse <REMAINING> &other);
}; // class StageProc4Sse
} // namespace hiir
#include "hiir/StageProc4Sse.hpp"
#endif // hiir_StageProc4Sse_HEADER_INCLUDED
/*\\\ EOF \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/

205
src/external/hiir/StageProc4Sse.hpp vendored Normal file
View file

@ -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 <int REMAINING>
void StageProc4Sse <REMAINING>::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 <REMAINING - 2>::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 <int REMAINING>
void StageProc4Sse <REMAINING>::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 <REMAINING - 2>::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 \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/

98
src/external/hiir/StageProcFpu.h vendored Normal file
View file

@ -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 <int REMAINING>
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 <REMAINING> &other);
StageProcFpu <REMAINING> &
operator = (const StageProcFpu <REMAINING> &other);
bool operator == (const StageProcFpu <REMAINING> &other);
bool operator != (const StageProcFpu <REMAINING> &other);
}; // class StageProcFpu
} // namespace hiir
#include "hiir/StageProcFpu.hpp"
#endif // hiir_StageProc_HEADER_INCLUDED
/*\\\ EOF \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/

165
src/external/hiir/StageProcFpu.hpp vendored Normal file
View file

@ -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 <int REMAINING>
void StageProcFpu <REMAINING>::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 <REMAINING - 2>::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 <int REMAINING>
void StageProcFpu <REMAINING>::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 <REMAINING - 2>::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 \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/

114
src/external/hiir/Upsampler2x4Neon.h vendored Normal file
View file

@ -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 <arm_neon.h>
#include <array>
namespace hiir
{
template <int NC>
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 <StageDataNeon, NBR_COEFS + 2> 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 <NC> &other) const = delete;
bool operator != (const Upsampler2x4Neon <NC> &other) const = delete;
}; // class Upsampler2x4Neon
} // namespace hiir
#include "hiir/Upsampler2x4Neon.hpp"
#endif // hiir_Upsampler2x4Neon_HEADER_INCLUDED
/*\\\ EOF \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/

205
src/external/hiir/Upsampler2x4Neon.hpp vendored Normal file
View file

@ -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 <cassert>
namespace hiir
{
/*\\\ PUBLIC \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/
/*
==============================================================================
Name: ctor
Throws: Nothing
==============================================================================
*/
template <int NC>
Upsampler2x4Neon <NC>::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 <int NC>
void Upsampler2x4Neon <NC>::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 <int NC>
void Upsampler2x4Neon <NC>::process_sample (float32x4_t &out_0, float32x4_t &out_1, float32x4_t input)
{
float32x4_t even = input;
float32x4_t odd = input;
StageProc4Neon <NBR_COEFS>::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 <int NC>
void Upsampler2x4Neon <NC>::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 <const uint8_t *> (in_ptr + pos * 4))
);
float32x4_t dst_0;
float32x4_t dst_1;
process_sample (dst_0, dst_1, src);
vst1q_u8 (
reinterpret_cast <uint8_t *> (out_ptr + pos * 8 ),
vreinterpretq_u8_f32 (dst_0)
);
vst1q_u8 (
reinterpret_cast <uint8_t *> (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 <int NC>
void Upsampler2x4Neon <NC>::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 \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/

113
src/external/hiir/Upsampler2x4Sse.h vendored Normal file
View file

@ -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 <xmmintrin.h>
#include <array>
namespace hiir
{
template <int NC>
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 <StageDataSse, NBR_COEFS + 2> 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 <NC> &other) const;
bool operator != (const Upsampler2x4Sse <NC> &other) const;
}; // class Upsampler2x4Sse
} // namespace hiir
#include "hiir/Upsampler2x4Sse.hpp"
#endif // hiir_Upsampler2x4Sse_HEADER_INCLUDED
/*\\\ EOF \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/

197
src/external/hiir/Upsampler2x4Sse.hpp vendored Normal file
View file

@ -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 <cassert>
namespace hiir
{
/*\\\ PUBLIC \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/
/*
==============================================================================
Name: ctor
Throws: Nothing
==============================================================================
*/
template <int NC>
Upsampler2x4Sse <NC>::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 <int NC>
void Upsampler2x4Sse <NC>::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 <int NC>
void Upsampler2x4Sse <NC>::process_sample (__m128 &out_0, __m128 &out_1, __m128 input)
{
__m128 even = input;
__m128 odd = input;
StageProc4Sse <NBR_COEFS>::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 <int NC>
void Upsampler2x4Sse <NC>::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 <int NC>
void Upsampler2x4Sse <NC>::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 \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/

106
src/external/hiir/Upsampler2xFpu.h vendored Normal file
View file

@ -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 <array>
namespace hiir
{
template <int NC>
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 <float, NBR_COEFS> HyperGluar;
HyperGluar _coef;
HyperGluar _x;
HyperGluar _y;
/*\\\ FORBIDDEN MEMBER FUNCTIONS \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/
private:
bool operator == (const Upsampler2xFpu <NC> &other);
bool operator != (const Upsampler2xFpu <NC> &other);
}; // class Upsampler2xFpu
} // namespace hiir
#include "hiir/Upsampler2xFpu.hpp"
#endif // hiir_Upsampler2xFpu_HEADER_INCLUDED
/*\\\ EOF \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/

205
src/external/hiir/Upsampler2xFpu.hpp vendored Normal file
View file

@ -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 <cassert>
namespace hiir
{
/*\\\ PUBLIC \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/
/*
==============================================================================
Name: ctor
Throws: Nothing
==============================================================================
*/
template <int NC>
Upsampler2xFpu <NC>::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 <int NC>
void Upsampler2xFpu <NC>::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 <int NC>
void Upsampler2xFpu <NC>::process_sample (float &out_0, float &out_1, float input)
{
float even = input;
float odd = input;
StageProcFpu <NBR_COEFS>::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 <int NC>
void Upsampler2xFpu <NC>::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 <int NC>
void Upsampler2xFpu <NC>::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 \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/

61
src/external/hiir/def.h vendored Normal file
View file

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

57
src/external/hiir/fnc.h vendored Normal file
View file

@ -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 <class T>
T ipowp (T x, long n);
} // namespace hiir
#include "hiir/fnc.hpp"
#endif // hiir_fnc_HEADER_INCLUDED
/*\\\ EOF \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/

85
src/external/hiir/fnc.hpp vendored Normal file
View file

@ -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 <cassert>
#include <cmath>
namespace hiir
{
int round_int (double x)
{
return int (floor (x + 0.5));
}
int ceil_int (double x)
{
return int (ceil (x));
}
template <class T>
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 \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/

View file

@ -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
#endif

View file

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

View file

@ -25,30 +25,80 @@
#include "AudioBuffer.h"
#include "Config.h"
#include "Debug.h"
#include "Oversampler.h"
#include "absl/types/span.h"
#include <chrono>
#include <memory>
#include <mutex>
#include <sndfile.hh>
#include <thread>
#include <mutex>
using namespace std::chrono_literals;
template <class T>
std::unique_ptr<sfz::AudioBuffer<T>> readFromFile(SndfileHandle& sndFile, int numFrames)
std::unique_ptr<sfz::AudioBuffer<T>> upsample2x(const sfz::AudioBuffer<T>& buffer)
{
auto returnedBuffer = std::make_unique<sfz::AudioBuffer<T>>(sndFile.channels(), numFrames);
if (sndFile.channels() == 1) {
sndFile.readf(returnedBuffer->channelWriter(0), numFrames);
} else if (sndFile.channels() == 2) {
auto tempReadBuffer = std::make_unique<sfz::AudioBuffer<float>>(1, 2 * numFrames);
sndFile.readf(tempReadBuffer->channelWriter(0), numFrames);
sfz::readInterleaved<float>(tempReadBuffer->getSpan(0), returnedBuffer->getSpan(0), returnedBuffer->getSpan(1));
// auto tempBuffer = std::make_unique<sfz::Buffer<T>>(buffer.getNumFrames() * 2);
auto outputBuffer = std::make_unique<sfz::AudioBuffer<T>>(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::FileInformation> sfz::FilePool::getFileInformation(const std::string& filename, uint32_t offset) noexcept
template <class T>
std::unique_ptr<sfz::AudioBuffer<T>> upsample4x(const sfz::AudioBuffer<T>& buffer)
{
auto tempBuffer = std::make_unique<sfz::Buffer<T>>(buffer.getNumFrames() * 2);
auto outputBuffer = std::make_unique<sfz::AudioBuffer<T>>(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 <class T>
std::unique_ptr<sfz::AudioBuffer<T>> upsample8x(const sfz::AudioBuffer<T>& buffer)
{
auto tempBuffer2x = std::make_unique<sfz::Buffer<T>>(buffer.getNumFrames() * 2);
auto tempBuffer4x = std::make_unique<sfz::Buffer<T>>(buffer.getNumFrames() * 4);
auto outputBuffer = std::make_unique<sfz::AudioBuffer<T>>(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 <class T>
std::unique_ptr<sfz::AudioBuffer<T>> readFromFile(SndfileHandle& sndFile, uint32_t numFrames, sfz::Oversampling factor)
{
auto baseBuffer = std::make_unique<sfz::AudioBuffer<T>>(sndFile.channels(), numFrames);
if (sndFile.channels() == 1) {
sndFile.readf(baseBuffer->channelWriter(0), numFrames);
} else if (sndFile.channels() == 2) {
auto tempReadBuffer = std::make_unique<sfz::AudioBuffer<T>>(1, 2 * numFrames);
sndFile.readf(tempReadBuffer->channelWriter(0), numFrames);
auto fileBuffer = std::make_unique<sfz::AudioBuffer<T>>(2, numFrames);
sfz::readInterleaved<T>(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::FileInformation> 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::FileInformation> sfz::FilePool::getFileInformation
FileInformation returnedValue;
returnedValue.end = static_cast<uint32_t>(sndFile.frames());
returnedValue.sampleRate = static_cast<double>(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::FileInformation> 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<uint32_t>(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<std::shared_ptr<sfz::AudioBuffer>>> is a bit ugly...
alreadyPreloaded.reset(readFromFile<float>(sndFile, preloadedSize).release());
}
returnedValue.preloadedData = alreadyPreloaded;
} else {
returnedValue.preloadedData = readFromFile<float>(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<const char*>(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<float>(sndFile, framesToLoad, oversamplingFactor);
}
} else {
preloadedFiles.insert_or_assign(filename, { readFromFile<float>(sndFile, framesToLoad, oversamplingFactor), static_cast<float>(sndFile.samplerate() * oversamplingFactor) });
}
return true;
}
sfz::FilePromisePtr sfz::FilePool::getFilePromise(const std::string& filename) noexcept
{
auto promise = std::make_shared<FilePromise>();
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<const char*>(file.c_str()));
preloadedFile.second.preloadedData = readFromFile<float>(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<const char*>(file.c_str()));
if (sndFile.error() != 0)
continue;
std::lock_guard<std::mutex> guard { fileHandleMutex };
fileHandles.emplace_back(readFromFile<float>(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<float>(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<float>(factor) / static_cast<float>(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<const char*>(file.c_str()));
preloadedFile.second.preloadedData = readFromFile<float>(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;

View file

@ -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 <absl/container/flat_hash_map.h>
#include <mutex>
#include <absl/types/optional.h>
#include <string_view>
#include "absl/strings/string_view.h"
#include "moodycamel/readerwriterqueue.h"
#include <thread>
#include <sndfile.hh>
namespace sfz {
using AudioBufferPtr = std::shared_ptr<AudioBuffer<float>>;
struct PreloadedFileHandle
{
std::shared_ptr<AudioBuffer<float>> preloadedData {};
float sampleRate { config::defaultSampleRate };
};
struct FilePromise
{
absl::string_view filename {};
AudioBufferPtr preloadedData {};
std::unique_ptr<AudioBuffer<float>> fileData {};
float sampleRate { config::defaultSampleRate };
std::atomic<bool> dataReady { false };
Oversampling oversamplingFactor { config::defaultOversamplingFactor };
};
using FilePromisePtr = std::shared_ptr<FilePromise>;
/**
* @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<AudioBuffer<float>> 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<FileInformation>
*/
absl::optional<FileInformation> 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<FileInformation>
* size will be preloadSize + offset
* @return true if the preloading went fine
* @return false if something went wrong ()
*/
absl::optional<FileInformation> 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<FileLoadingInformation> loadingQueue { config::numVoices };
void loadingThread() noexcept;
void garbageThread() noexcept;
moodycamel::ReaderWriterQueue<FilePromisePtr> promiseQueue;
uint32_t preloadSize { config::preloadSize };
Oversampling oversamplingFactor { config::defaultOversamplingFactor };
// Signals
bool quitThread { false };
bool emptyQueue { false };
std::mutex fileHandleMutex;
std::vector<std::shared_ptr<AudioBuffer<float>>> fileHandles;
absl::flat_hash_map<absl::string_view, std::shared_ptr<AudioBuffer<float>>> preloadedData;
std::vector<FilePromisePtr> temporaryFilePromises;
std::vector<FilePromisePtr> promisesToClean;
absl::flat_hash_map<absl::string_view, PreloadedFileHandle> preloadedFiles;
std::thread fileLoadingThread { &FilePool::loadingThread, this };
std::thread garbageCollectionThread { &FilePool::garbageThread, this };
LEAK_DETECTOR(FilePool);
};
}

95
src/sfizz/Oversampler.h Normal file
View file

@ -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 <array>
#include "absl/types/span.h"
#include "Debug.h"
#include "Config.h"
#include "hiir/Upsampler2xFpu.h"
namespace sfz {
constexpr std::array<double, 12> coeffsStage2x {
0.036681502163648017,
0.13654762463195771,
0.27463175937945411,
0.42313861743656667,
0.56109869787919475,
0.67754004997416162,
0.76974183386322659,
0.83988962484963803,
0.89226081800387891,
0.9315419599631839,
0.96209454837808395,
0.98781637073289708
};
constexpr std::array<double, 4> coeffsStage4x {
0.042448989488488006,
0.17072114107630679,
0.39329183835224008,
0.74569514831986694
};
constexpr std::array<double, 3> coeffsStage8x {
0.055748680811302048,
0.24305119574153092,
0.6466991311926823
};
template<bool SIMD=SIMDConfig::upsampling>
inline void upsample2xStage(absl::Span<const float> input, absl::Span<float> output)
{
ASSERT(output.size() >= 2 * input.size());
hiir::Upsampler2xFpu<coeffsStage2x.size()> upsampler;
upsampler.set_coefs(coeffsStage2x.data());
upsampler.process_block(output.data(), input.data(), input.size());
}
template<bool SIMD=SIMDConfig::upsampling>
inline void upsample4xStage(absl::Span<const float> input, absl::Span<float> output)
{
ASSERT(output.size() >= 2 * input.size());
hiir::Upsampler2xFpu<coeffsStage4x.size()> upsampler;
upsampler.set_coefs(coeffsStage4x.data());
upsampler.process_block(output.data(), input.data(), input.size());
}
template<bool SIMD=SIMDConfig::upsampling>
inline void upsample8xStage(absl::Span<const float> input, absl::Span<float> output)
{
ASSERT(output.size() >= 2 * input.size());
hiir::Upsampler2xFpu<coeffsStage8x.size()> upsampler;
upsampler.set_coefs(coeffsStage8x.data());
upsampler.process_block(output.data(), input.data(), input.size());
}
template<>
inline void upsample2xStage<true>(absl::Span<const float> input, absl::Span<float> output);
template<>
inline void upsample4xStage<true>(absl::Span<const float> input, absl::Span<float> output);
template<>
inline void upsample8xStage<true>(absl::Span<const float> input, absl::Span<float> output);
}

View file

@ -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<true>(absl::Span<const float> input, absl::Span<float> output)
{
sfz::upsample2xStage<false>(input, output);
}
template<>
inline void sfz::upsample4xStage<true>(absl::Span<const float> input, absl::Span<float> output)
{
sfz::upsample4xStage<false>(input, output);
}
template<>
inline void sfz::upsample8xStage<true>(absl::Span<const float> input, absl::Span<float> output)
{
sfz::upsample8xStage<false>(input, output);
}

View file

@ -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<true>(absl::Span<const float> input, absl::Span<float> output)
{
ASSERT(output.size() >= 2 * input.size());
hiir::Upsampler2x4Sse<sfz::coeffsStage2x.size()> upsampler;
upsampler.set_coefs(sfz::coeffsStage2x.data());
upsampler.process_block(output.data(), input.data(), input.size());
}
template<>
inline void sfz::upsample4xStage<true>(absl::Span<const float> input, absl::Span<float> output)
{
ASSERT(output.size() >= 2 * input.size());
hiir::Upsampler2x4Sse<sfz::coeffsStage4x.size()> upsampler;
upsampler.set_coefs(sfz::coeffsStage4x.data());
upsampler.process_block(output.data(), input.data(), input.size());
}
template<>
inline void sfz::upsample8xStage<true>(absl::Span<const float> input, absl::Span<float> output)
{
ASSERT(output.size() >= 2 * input.size());
hiir::Upsampler2x4Sse<sfz::coeffsStage8x.size()> upsampler;
upsampler.set_coefs(sfz::coeffsStage8x.data());
upsampler.process_block(output.data(), input.data(), input.size());
}

View file

@ -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<uint32_t>(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<class T, class U>

View file

@ -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<AudioBuffer<float>> preloadedData { nullptr };
bool isStereo { false };
private:
const MidiState& midiState;
bool keySwitched { true };

10
src/sfizz/Resources.h Normal file
View file

@ -0,0 +1,10 @@
#pragma once
#include "FilePool.h"
namespace sfz
{
struct Resources
{
FilePool filePool;
};
}

View file

@ -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<float> 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, &region->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, &region->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, &region->sample, region->trueSampleEnd(), fileTicket++);
}
}
}
}
@ -503,7 +495,7 @@ std::set<absl::string_view> 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<Voice>(midiState));
voices.push_back(std::make_unique<Voice>(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();
}

View file

@ -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<Opcode> masterOpcodes;
std::vector<Opcode> 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<float> randNoteDistribution { 0, 1 };
@ -379,6 +406,10 @@ private:
std::atomic<bool> canEnterCallback { true };
std::atomic<bool> inCallback { false };
// Singletons passed as references to the voices
Resources resources;
MidiState midiState;
LEAK_DETECTOR(Synth);
};

View file

@ -31,8 +31,8 @@
#include "absl/algorithm/container.h"
#include <memory>
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<float>(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<float>(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<AudioBuffer<float>> 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<float> 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<float> buffer) noexcept
return;
auto source { [&]() {
if (region->canUsePreloadedData())
return AudioSpan<const float>(*region->preloadedData);
else if (!dataReady)
return AudioSpan<const float>(*region->preloadedData);
// if (region->trueSampleEnd() < currentPromise->preloadedData->getNumFrames())
// return AudioSpan<const float>(*currentPromise->preloadedData);
// else
if (!currentPromise->dataReady)
return AudioSpan<const float>(*currentPromise->preloadedData);
else
return AudioSpan<const float>(*fileData);
return AudioSpan<const float>(*currentPromise->fileData);
}() };
auto indices = indexSpan.first(buffer.getNumFrames());
@ -372,9 +375,12 @@ void sfz::Voice::fillWithData(AudioSpan<float> buffer) noexcept
add<int>(sourcePosition, indices);
//FIXME : all this casting is driving me crazy
const auto sampleEnd = min(static_cast<int>(region->trueSampleEnd()), static_cast<int>(source.getNumFrames())) - 1;
if (region->shouldLoop() && region->loopRange.getEnd() <= source.getNumFrames()) {
const auto offset = sampleEnd - static_cast<int>(region->loopRange.getStart());
const auto sampleEnd = min(
static_cast<int>(region->trueSampleEnd(currentPromise->oversamplingFactor)),
static_cast<int>(source.getNumFrames())
) - 1;
if (region->shouldLoop() && region->loopEnd(currentPromise->oversamplingFactor) <= source.getNumFrames()) {
const auto offset = sampleEnd - static_cast<int>(region->loopStart(currentPromise->oversamplingFactor));
for (auto* index = indices.begin(); index < indices.end(); ++index) {
if (*index > sampleEnd) {
const auto remainingElements = static_cast<size_t>(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();

View file

@ -29,6 +29,7 @@
#include "Region.h"
#include "AudioBuffer.h"
#include "MidiState.h"
#include "Resources.h"
#include "AudioSpan.h"
#include "LeakDetector.h"
#include <absl/types/span.h>
@ -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<AudioBuffer<float>> 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<bool> dataReady { false };
std::shared_ptr<AudioBuffer<float>> fileData { nullptr };
unsigned ticket { 0 };
FilePromisePtr currentPromise { nullptr };
Buffer<float> tempBuffer1;
Buffer<float> tempBuffer2;
@ -326,6 +303,8 @@ private:
float sampleRate { config::defaultSampleRate };
const MidiState& midiState;
Resources& resources;
ADSREnvelope<float> egEnvelope;
LinearEnvelope<float> volumeEnvelope; // dB events but the envelope output is linear gain
LinearEnvelope<float> amplitudeEnvelope; // linear events

View file

@ -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<size_t>(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<sfz::Synth*>(synth);
self->garbageCollect();
return self->getPreloadSize();
}
void sfizz_set_preload_size(sfizz_synth_t* synth, unsigned int preload_size)
{
auto self = reinterpret_cast<sfz::Synth*>(synth);
self->setPreloadSize(preload_size);
}
sfizz_oversampling_factor_t sfizz_get_oversampling_factor(sfizz_synth_t* synth)
{
auto self = reinterpret_cast<sfz::Synth*>(synth);
return static_cast<sfizz_oversampling_factor_t>(self->getOversamplingFactor());
}
bool sfizz_set_oversampling_factor(sfizz_synth_t* synth, sfizz_oversampling_factor_t oversampling)
{
auto self = reinterpret_cast<sfz::Synth*>(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
#endif

View file

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

View file

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