Merge branch 'develop' into width-position

This commit is contained in:
Paul Ferrand 2020-02-10 13:23:41 +01:00 committed by GitHub
commit 35d62280a7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
23 changed files with 807 additions and 109 deletions

View file

@ -5,17 +5,14 @@
// If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz // If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz
#include "Config.h" #include "Config.h"
#include "SIMDConfig.h"
#include <benchmark/benchmark.h> #include <benchmark/benchmark.h>
#include <absl/types/span.h> #include <absl/types/span.h>
#include <random> #include <random>
#include <algorithm> #include <algorithm>
#if HAVE_X86INTRIN_H #if SFIZZ_HAVE_SSE2
#include <x86intrin.h> #include <emmintrin.h>
#endif
#if HAVE_INTRIN_H
#include <intrin.h>
#endif #endif
constexpr float filterGain { 0.25f }; constexpr float filterGain { 0.25f };

View file

@ -1,7 +1,7 @@
project(sfizz) project(sfizz)
# Check SIMD # Check SIMD
include (SfizzSIMDSourceFilesCheck) include (SfizzSIMDSourceFiles)
set(BENCHMARK_SIMD_SOURCES ${SFIZZ_SIMD_SOURCES}) set(BENCHMARK_SIMD_SOURCES ${SFIZZ_SIMD_SOURCES})
list(TRANSFORM BENCHMARK_SIMD_SOURCES PREPEND "../src/") list(TRANSFORM BENCHMARK_SIMD_SOURCES PREPEND "../src/")
find_package(benchmark CONFIG REQUIRED) find_package(benchmark CONFIG REQUIRED)

View file

@ -0,0 +1,6 @@
set (SFIZZ_SIMD_SOURCES
sfizz/SIMDSSE.cpp
sfizz/SIMDNEON.cpp
sfizz/SIMDDummy.cpp)
list (APPEND SFIZZ_SOURCES ${SFIZZ_SIMD_SOURCES})

View file

@ -1,31 +0,0 @@
# Check SIMD
include (CheckIncludeFiles)
CHECK_INCLUDE_FILES(x86intrin.h HAVE_X86INTRIN_H)
CHECK_INCLUDE_FILES(intrin.h HAVE_INTRIN_H)
if (!APPLE)
CHECK_INCLUDE_FILES (arm_neon.h HAVE_ARM_NEON_H)
endif()
# SIMD checks
if (HAVE_X86INTRIN_H AND UNIX)
add_compile_options (-DHAVE_X86INTRIN_H)
set (SFIZZ_SIMD_SOURCES sfizz/SIMDSSE.cpp)
elseif (HAVE_INTRIN_H AND WIN32)
if (CMAKE_CXX_COMPILER_ID MATCHES "MSVC")
add_compile_options ("/DHAVE_INTRIN_H")
else()
add_compile_options ("-DHAVE_INTRIN_H")
endif()
set (SFIZZ_SIMD_SOURCES sfizz/SIMDSSE.cpp)
elseif (CMAKE_SYSTEM_PROCESSOR STREQUAL "armv7l")
add_compile_options (-DHAVE_ARM_NEON_H)
add_compile_options (-mfpu=neon)
add_compile_options (-march=native)
add_compile_options (-mtune=cortex-a53)
set (SFIZZ_SIMD_SOURCES sfizz/SIMDNEON.cpp)
else()
set (SFIZZ_SIMD_SOURCES sfizz/SIMDDummy.cpp)
endif()
set (SFIZZ_SOURCES ${SFIZZ_SOURCES} ${SFIZZ_SIMD_SOURCES})

View file

@ -3,6 +3,8 @@ include(CheckLibraryExists)
set (SFIZZ_SOURCES set (SFIZZ_SOURCES
sfizz/Synth.cpp sfizz/Synth.cpp
sfizz/FilePool.cpp sfizz/FilePool.cpp
sfizz/FilterPool.cpp
sfizz/EQPool.cpp
sfizz/Region.cpp sfizz/Region.cpp
sfizz/Voice.cpp sfizz/Voice.cpp
sfizz/ScopedFTZ.cpp sfizz/ScopedFTZ.cpp
@ -13,7 +15,7 @@ set (SFIZZ_SOURCES
sfizz/Logger.cpp sfizz/Logger.cpp
sfizz/SfzFilter.cpp sfizz/SfzFilter.cpp
) )
include (SfizzSIMDSourceFilesCheck) include (SfizzSIMDSourceFiles)
# Parser core library # Parser core library
add_library (sfizz_parser STATIC) add_library (sfizz_parser STATIC)

View file

@ -86,7 +86,9 @@ public:
*/ */
bool contains(int index) const noexcept { return container.find(index) != container.end(); } bool contains(int index) const noexcept { return container.find(index) != container.end(); }
typename std::map<int, ValueType>::iterator begin() { return container.begin(); } typename std::map<int, ValueType>::iterator begin() { return container.begin(); }
typename std::map<int, ValueType>::const_iterator begin() const { return container.cbegin(); }
typename std::map<int, ValueType>::iterator end() { return container.end(); } typename std::map<int, ValueType>::iterator end() { return container.end(); }
typename std::map<int, ValueType>::const_iterator end() const { return container.cend(); }
private: private:
const ValueType defaultValue; const ValueType defaultValue;
std::map<int, ValueType> container; std::map<int, ValueType> container;

View file

@ -53,6 +53,10 @@ namespace config {
constexpr uint8_t numCCs { 143 }; constexpr uint8_t numCCs { 143 };
constexpr int chunkSize { 1024 }; constexpr int chunkSize { 1024 };
constexpr float defaultAmpEGRelease { 0.02f }; constexpr float defaultAmpEGRelease { 0.02f };
constexpr int filtersInPool { maxVoices * 2 };
constexpr int filtersPerVoice { 2 };
constexpr int eqsPerVoice { 3 };
constexpr float noiseVariance { 0.25f };
/** /**
Minimum interval in frames between recomputations of coefficients of the Minimum interval in frames between recomputations of coefficients of the
modulated filter. The lower, the more CPU resources are consumed. modulated filter. The lower, the more CPU resources are consumed.

View file

@ -131,7 +131,7 @@ namespace Default
constexpr float filterGain { 0 }; constexpr float filterGain { 0 };
constexpr int filterKeytrack { 0 }; constexpr int filterKeytrack { 0 };
constexpr uint8_t filterKeycenter { 60 }; constexpr uint8_t filterKeycenter { 60 };
constexpr int filterRandom { 60 }; constexpr int filterRandom { 0 };
constexpr int filterVeltrack { 0 }; constexpr int filterVeltrack { 0 };
constexpr int filterCutoffCC { 0 }; constexpr int filterCutoffCC { 0 };
constexpr float filterResonanceCC { 0 }; constexpr float filterResonanceCC { 0 };

138
src/sfizz/EQPool.cpp Normal file
View file

@ -0,0 +1,138 @@
#include "EQPool.h"
#include "AtomicGuard.h"
#include <thread>
#include "absl/algorithm/container.h"
#include "SIMDHelpers.h"
using namespace std::chrono_literals;
sfz::EQHolder::EQHolder(const MidiState& state)
:midiState(state)
{
}
void sfz::EQHolder::reset()
{
eq.clear();
}
void sfz::EQHolder::setup(const EQDescription& description, unsigned numChannels, uint8_t velocity)
{
reset();
eq.setChannels(numChannels);
this->description = &description;
const auto normalizedVelocity = normalizeVelocity(velocity);
baseBandwidth = description.bandwidth;
baseGain = Default::eqGainRange.clamp(description.gain + normalizedVelocity * description.vel2gain);
baseFrequency = Default::eqFrequencyRange.clamp(description.frequency + normalizedVelocity * description.vel2frequency);
}
void sfz::EQHolder::process(const float** inputs, float** outputs, unsigned numFrames)
{
if (description == nullptr) {
for (unsigned channelIdx = 0; channelIdx < eq.channels(); channelIdx++)
copy<float>({ inputs[channelIdx], numFrames }, { outputs[channelIdx], numFrames });
return;
}
// TODO: Once the midistate envelopes are done, add modulation in there!
// For now we take the last value
lastFrequency = baseFrequency;
for (auto& mod: description->frequencyCC) {
lastFrequency += midiState.getCCValue(mod.first) * mod.second;
}
lastFrequency = Default::eqFrequencyRange.clamp(lastFrequency);
lastBandwidth = baseBandwidth;
for (auto& mod: description->bandwidthCC) {
lastBandwidth += midiState.getCCValue(mod.first) * mod.second;
}
lastBandwidth = Default::eqBandwidthRange.clamp(lastBandwidth);
lastGain = baseGain;
for (auto& mod: description->gainCC) {
lastGain += midiState.getCCValue(mod.first) * mod.second;
}
lastGain = Default::eqGainRange.clamp(lastGain);
eq.process(inputs, outputs, lastFrequency, lastBandwidth, lastGain, numFrames);
}
float sfz::EQHolder::getLastFrequency() const
{
return lastFrequency;
}
float sfz::EQHolder::getLastBandwidth() const
{
return lastBandwidth;
}
float sfz::EQHolder::getLastGain() const
{
return lastGain;
}
void sfz::EQHolder::setSampleRate(float sampleRate)
{
eq.init(static_cast<double>(sampleRate));
}
sfz::EQPool::EQPool(const MidiState& state, int numEQs)
: midiState(state)
{
setnumEQs(numEQs);
}
sfz::EQHolderPtr sfz::EQPool::getEQ(const EQDescription& description, unsigned numChannels, uint8_t velocity)
{
AtomicGuard guard { givingOutEQs };
if (!canGiveOutEQs)
return {};
auto eq = absl::c_find_if(eqs, [](const EQHolderPtr& holder) {
return holder.use_count() == 1;
});
if (eq == eqs.end())
return {};
(**eq).setup(description, numChannels, velocity);
return *eq;
}
size_t sfz::EQPool::getActiveEQs() const
{
return absl::c_count_if(eqs, [](const EQHolderPtr& holder) {
return holder.use_count() > 1;
});
}
size_t sfz::EQPool::setnumEQs(size_t numEQs)
{
AtomicDisabler disabler { canGiveOutEQs };
while(givingOutEQs)
std::this_thread::sleep_for(1ms);
auto eqIterator = eqs.begin();
auto eqSentinel = eqs.rbegin();
while (eqIterator < eqSentinel.base()) {
if (eqIterator->use_count() == 1) {
std::iter_swap(eqIterator, eqSentinel);
++eqSentinel;
} else {
++eqIterator;
}
}
eqs.resize(std::distance(eqs.begin(), eqSentinel.base()));
for (size_t i = eqs.size(); i < numEQs; ++i) {
eqs.emplace_back(std::make_shared<EQHolder>(midiState));
eqs.back()->setSampleRate(sampleRate);
}
return eqs.size();
}
void sfz::EQPool::setSampleRate(float sampleRate)
{
for (auto& eq: eqs)
eq->setSampleRate(sampleRate);
}

123
src/sfizz/EQPool.h Normal file
View file

@ -0,0 +1,123 @@
#pragma once
#include "SfzFilter.h"
#include "EQDescription.h"
#include "MidiState.h"
#include <vector>
#include <memory>
namespace sfz
{
class EQHolder
{
public:
EQHolder() = delete;
EQHolder(const MidiState& state);
/**
* @brief Setup a new EQ based on an EQ description.
*
* @param description the EQ description
* @param numChannels the number of channels for the EQ
* @param description the triggering velocity/value
*/
void setup(const EQDescription& description, unsigned numChannels, uint8_t velocity);
/**
* @brief Process a block of stereo inputs
*
* @param inputs
* @param outputs
* @param numFrames
*/
void process(const float** inputs, float** outputs, unsigned numFrames);
/**
* @brief Returns the last value of the frequency for the EQ
*
* @return float
*/
float getLastFrequency() const;
/**
* @brief Returns the last value of the bandwitdh for the EQ
*
* @return float
*/
float getLastBandwidth() const;
/**
* @brief Returns the last value of the gain for the EQ
*
* @return float
*/
float getLastGain() const;
/**
* @brief Set the sample rate for the EQ
*
* @param sampleRate
*/
void setSampleRate(float sampleRate);
private:
/**
* Reset the filter. Is called internally when using setup().
*/
void reset();
const MidiState& midiState;
const EQDescription* description;
FilterEq eq;
float baseBandwidth { Default::eqBandwidth };
float baseFrequency { Default::eqFrequency1 };
float baseGain { Default::eqGain };
float lastBandwidth { Default::eqBandwidth };
float lastFrequency { Default::eqFrequency1 };
float lastGain { Default::eqGain };
};
using EQHolderPtr = std::shared_ptr<EQHolder>;
class EQPool
{
public:
EQPool() = delete;
/**
* @brief Construct a new EQPool object
*
* @param state the associated midi state
* @param numEQs the number of inactive EQs to hold in the pool
*/
EQPool(const MidiState& state, int numEQs = config::filtersInPool);
/**
* @brief Get an EQ object to use in Voices
*
* @param description the filter description to bind to the EQ
* @param numChannels the number of channels for the EQ
* @param velocity the triggering note velocity/value
* @return EQHolderPtr release this when done with the filter; no deallocation will be done
*/
EQHolderPtr getEQ(const EQDescription& description, unsigned numChannels, uint8_t velocity);
/**
* @brief Get the number of active EQs
*
* @return size_t
*/
size_t getActiveEQs() const;
/**
* @brief Set the number of EQs in the pool. This function may sleep and should be called from a background thread.
* No EQs will be distributed during the reallocation of EQs. Existing running EQs are kept. If the target
* number of EQs is less that the number of active EQs, the function will not remove them and you may need
* to call it again after existing EQs have run out.
*
* @param numEQs
* @return size_t the actual number of EQs in the pool
*/
size_t setnumEQs(size_t numEQs);
/**
* @brief Set the sample rate for all EQs
*
* @param sampleRate
*/
void setSampleRate(float sampleRate);
private:
std::atomic<bool> givingOutEQs { false };
std::atomic<bool> canGiveOutEQs { true };
float sampleRate { config::defaultSampleRate };
const MidiState& midiState;
std::vector<EQHolderPtr> eqs;
};
}

152
src/sfizz/FilterPool.cpp Normal file
View file

@ -0,0 +1,152 @@
#include "FilterPool.h"
#include "SIMDHelpers.h"
#include "absl/algorithm/container.h"
#include "AtomicGuard.h"
#include <thread>
#include <chrono>
using namespace std::chrono_literals;
sfz::FilterHolder::FilterHolder(const MidiState& midiState)
: midiState(midiState)
{
}
void sfz::FilterHolder::reset()
{
filter.clear();
}
void sfz::FilterHolder::setup(const FilterDescription& description, unsigned numChannels, int noteNumber, uint8_t velocity)
{
reset();
this->description = &description;
filter.setType(description.type);
filter.setChannels(numChannels);
baseCutoff = description.cutoff;
if (description.random != 0) {
dist.param(filterRandomDist::param_type(0, description.random));
baseCutoff *= centsFactor(dist(Random::randomGenerator));
}
const auto keytrack = description.keytrack * (noteNumber - description.keycenter);
baseCutoff *= centsFactor(keytrack);
const auto veltrack = description.veltrack * normalizeVelocity(velocity);
baseCutoff *= centsFactor(veltrack);
baseCutoff = Default::filterCutoffRange.clamp(baseCutoff);
baseGain = description.gain;
baseResonance = description.resonance;
}
void sfz::FilterHolder::process(const float** inputs, float** outputs, unsigned numFrames)
{
if (description == nullptr) {
for (unsigned channelIdx = 0; channelIdx < filter.channels(); channelIdx++)
copy<float>({ inputs[channelIdx], numFrames }, { outputs[channelIdx], numFrames });
return;
}
// TODO: Once the midistate envelopes are done, add modulation in there!
// For now we take the last value
lastCutoff = baseCutoff;
for (auto& mod: description->cutoffCC) {
lastCutoff *= centsFactor(midiState.getCCValue(mod.first) * mod.second);
}
lastCutoff = Default::filterCutoffRange.clamp(lastCutoff);
lastResonance = baseResonance;
for (auto& mod: description->resonanceCC) {
lastResonance += midiState.getCCValue(mod.first) * mod.second;
}
lastResonance = Default::filterResonanceRange.clamp(lastResonance);
lastGain = baseGain;
for (auto& mod: description->gainCC) {
lastGain += midiState.getCCValue(mod.first) * mod.second;
}
lastGain = Default::filterGainRange.clamp(lastGain);
filter.process(inputs, outputs, lastCutoff, lastResonance, lastGain, numFrames);
}
float sfz::FilterHolder::getLastCutoff() const
{
return lastCutoff;
}
float sfz::FilterHolder::getLastResonance() const
{
return lastResonance;
}
float sfz::FilterHolder::getLastGain() const
{
return lastGain;
}
sfz::FilterPool::FilterPool(const MidiState& state, int numFilters)
: midiState(state)
{
setNumFilters(numFilters);
}
sfz::FilterHolderPtr sfz::FilterPool::getFilter(const FilterDescription& description, unsigned numChannels, int noteNumber, uint8_t velocity)
{
AtomicGuard guard { givingOutFilters };
if (!canGiveOutFilters)
return {};
auto filter = absl::c_find_if(filters, [](const FilterHolderPtr& holder) {
return holder.use_count() == 1;
});
if (filter == filters.end())
return {};
(**filter).setup(description, numChannels, noteNumber, velocity);
return *filter;
}
size_t sfz::FilterPool::getActiveFilters() const
{
return absl::c_count_if(filters, [](const FilterHolderPtr& holder) {
return holder.use_count() > 1;
});
}
size_t sfz::FilterPool::setNumFilters(size_t numFilters)
{
AtomicDisabler disabler { canGiveOutFilters };
while(givingOutFilters)
std::this_thread::sleep_for(1ms);
auto filterIterator = filters.begin();
auto filterSentinel = filters.rbegin();
while (filterIterator < filterSentinel.base()) {
if (filterIterator->use_count() == 1) {
std::iter_swap(filterIterator, filterSentinel);
++filterSentinel;
} else {
++filterIterator;
}
}
filters.resize(std::distance(filters.begin(), filterSentinel.base()));
for (size_t i = filters.size(); i < numFilters; ++i) {
filters.emplace_back(std::make_shared<FilterHolder>(midiState));
filters.back()->setSampleRate(sampleRate);
}
return filters.size();
}
void sfz::FilterPool::setSampleRate(float sampleRate)
{
for (auto& filter: filters)
filter->setSampleRate(sampleRate);
}
void sfz::FilterHolder::setSampleRate(float sampleRate)
{
filter.init(static_cast<double>(sampleRate));
}

127
src/sfizz/FilterPool.h Normal file
View file

@ -0,0 +1,127 @@
#pragma once
#include "SfzFilter.h"
#include "FilterDescription.h"
#include "MidiState.h"
#include <vector>
#include <memory>
namespace sfz
{
class FilterHolder
{
public:
FilterHolder() = delete;
FilterHolder(const MidiState& state);
/**
* @brief Setup a new filter based on a filter description, and a triggering note parameters.
*
* @param description the filter description
* @param numChannels the number of channels
* @param noteNumber the triggering note number
* @param velocity the triggering note velocity/value
*/
void setup(const FilterDescription& description, unsigned numChannels, int noteNumber = static_cast<int>(Default::filterKeycenter), uint8_t velocity = 0);
/**
* @brief Process a block of stereo inputs
*
* @param inputs
* @param outputs
* @param numFrames
*/
void process(const float** inputs, float** outputs, unsigned numFrames);
/**
* @brief Returns the last value of the cutoff for the filter
*
* @return float
*/
float getLastCutoff() const;
/**
* @brief Returns the last value of the resonance for the filter
*
* @return float
*/
float getLastResonance() const;
/**
* @brief Returns the last value of the gain for the filter
*
* @return float
*/
float getLastGain() const;
/**
* @brief Set the sample rate for a filter
*
* @param sampleRate
*/
void setSampleRate(float sampleRate);
private:
/**
* Reset the filter. Is called internally when using setup().
*/
void reset();
const MidiState& midiState;
const FilterDescription* description;
Filter filter;
float baseCutoff { Default::filterCutoff };
float baseResonance { Default::filterResonance };
float baseGain { Default::filterGain };
float lastCutoff { Default::filterCutoff };
float lastResonance { Default::filterResonance };
float lastGain { Default::filterGain };
using filterRandomDist = std::uniform_int_distribution<int>;
filterRandomDist dist { 0, sfz::Default::filterRandom };
};
using FilterHolderPtr = std::shared_ptr<FilterHolder>;
class FilterPool
{
public:
FilterPool() = delete;
/**
* @brief Construct a new Filter Pool object
*
* @param state the associated midi state
* @param numFilters the number of inactive filters to hold in the pool
*/
FilterPool(const MidiState& state, int numFilters = config::filtersInPool);
/**
* @brief Get a filter object to use in Voices
*
* @param description the filter description to bind to the filter
* @param numChannels the number of channels in the underlying filter
* @param noteNumber the triggering note number
* @param velocity the triggering note velocity
* @return FilterHolderPtr release this when done with the filter; no deallocation will be done
*/
FilterHolderPtr getFilter(const FilterDescription& description, unsigned numChannels, int noteNumber = static_cast<int>(Default::filterKeycenter), uint8_t velocity = 0);
/**
* @brief Get the number of active filters
*
* @return size_t
*/
size_t getActiveFilters() const;
/**
* @brief Set the number of filters in the pool. This function may sleep and should be called from a background thread.
* No filters will be distributed during the reallocation of filters. Existing running filters are kept. If the target
* number of filters is less that the number of active filters, the function will not remove them and you may need
* to call it again after existing filters have run out.
*
* @param numFilters
* @return size_t the actual number of filters in the pool
*/
size_t setNumFilters(size_t numFilters);
/**
* @brief Set the sample rate for all filters
*
* @param sampleRate
*/
void setSampleRate(float sampleRate);
private:
std::atomic<bool> givingOutFilters { false };
std::atomic<bool> canGiveOutFilters { true };
float sampleRate { config::defaultSampleRate };
const MidiState& midiState;
std::vector<FilterHolderPtr> filters;
};
}

View file

@ -6,13 +6,18 @@
#pragma once #pragma once
#include "FilePool.h" #include "FilePool.h"
#include "FilterPool.h"
#include "EQPool.h"
#include "Logger.h" #include "Logger.h"
namespace sfz namespace sfz
{ {
struct Resources struct Resources
{ {
MidiState midiState;
Logger logger; Logger logger;
FilePool filePool { logger }; FilePool filePool { logger };
FilterPool filterPool { midiState };
EQPool eqPool { midiState };
}; };
} }

69
src/sfizz/SIMDConfig.h Normal file
View file

@ -0,0 +1,69 @@
// SPDX-License-Identifier: BSD-2-Clause
// This code is part of the sfizz library and is licensed under a BSD 2-clause
// license. You should have receive a LICENSE.md file along with the code.
// If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz
#pragma once
/**
Define the following macros, 1 if available, otherwise 0.
These are allowed to be defined externally, in case automatic detection would
fail based on the compiler predefinitions.
- SFIZZ_HAVE_SSE
- SFIZZ_HAVE_SSE2
- SFIZZ_HAVE_NEON
*/
#if defined(__GNUC__)
# if defined(__SSE2__)
# define SFIZZ_DETECT_SSE 1
# define SFIZZ_DETECT_SSE2 1
# elif defined(__SSE__)
# define SFIZZ_DETECT_SSE 1
# define SFIZZ_DETECT_SSE2 0
# else
# define SFIZZ_DETECT_SSE 0
# define SFIZZ_DETECT_SSE2 0
# endif
# if defined(__ARM_NEON__)
# define SFIZZ_DETECT_NEON 1
# else
# define SFIZZ_DETECT_NEON 0
# endif
#elif defined(_MSC_VER)
# if defined(_M_AMD64) || defined(_M_X64)
# define SFIZZ_DETECT_SSE 1
# define SFIZZ_DETECT_SSE2 1
# elif _M_IX86_FP == 2
# define SFIZZ_DETECT_SSE 1
# define SFIZZ_DETECT_SSE2 1
# elif _M_IX86_FP == 1
# define SFIZZ_DETECT_SSE 1
# define SFIZZ_DETECT_SSE2 0
# endif
// TODO: how to check for NEON on MSVC ARM?
#endif
#ifndef SFIZZ_HAVE_SSE
# ifdef SFIZZ_DETECT_SSE
# define SFIZZ_HAVE_SSE SFIZZ_DETECT_SSE
# else
# define SFIZZ_HAVE_SSE 0
# endif
#endif
#ifndef SFIZZ_HAVE_SSE2
# ifdef SFIZZ_DETECT_SSE2
# define SFIZZ_HAVE_SSE2 SFIZZ_DETECT_SSE2
# else
# define SFIZZ_HAVE_SSE2 0
# endif
#endif
#ifndef SFIZZ_HAVE_NEON
# ifdef SFIZZ_DETECT_NEON
# define SFIZZ_HAVE_NEON SFIZZ_DETECT_NEON
# else
# define SFIZZ_HAVE_NEON 0
# endif
#endif

View file

@ -4,6 +4,10 @@
// license. You should have receive a LICENSE.md file along with the code. // license. You should have receive a LICENSE.md file along with the code.
// If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz // If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz
#include "SIMDConfig.h"
#if !(SFIZZ_HAVE_SSE2 || SFIZZ_HAVE_NEON)
#include "SIMDHelpers.h" #include "SIMDHelpers.h"
template <> template <>
@ -163,3 +167,5 @@ void sfz::diff<float, true>(absl::Span<const float> input, absl::Span<float> out
{ {
diff<float, false>(input, output); diff<float, false>(input, output);
} }
#endif // !(SFIZZ_HAVE_SSE2 || SFIZZ_HAVE_NEON)

View file

@ -21,8 +21,12 @@
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include <arm_neon.h> #include "SIMDConfig.h"
#if SFIZZ_HAVE_NEON
#include "SIMDHelpers.h" #include "SIMDHelpers.h"
#include <arm_neon.h>
using Type = float; using Type = float;
[[maybe_unused]] constexpr uintptr_t TypeAlignment { 4 }; [[maybe_unused]] constexpr uintptr_t TypeAlignment { 4 };
@ -234,3 +238,5 @@ void sfz::diff<float, true>(absl::Span<const float> input, absl::Span<float> out
{ {
diff<float, false>(input, output); diff<float, false>(input, output);
} }
#endif // SFIZZ_HAVE_NEON

View file

@ -4,16 +4,14 @@
// license. You should have receive a LICENSE.md file along with the code. // license. You should have receive a LICENSE.md file along with the code.
// If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz // If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz
#include "SIMDConfig.h"
#if SFIZZ_HAVE_SSE2
#include "SIMDHelpers.h" #include "SIMDHelpers.h"
#include <array> #include <array>
#include <xmmintrin.h> #include <xmmintrin.h>
#if HAVE_X86INTRIN_H #include <emmintrin.h>
#include <x86intrin.h>
#endif
#if HAVE_INTRIN_H
#include <intrin.h>
#endif
#include "mathfuns/sse_mathfun.h" #include "mathfuns/sse_mathfun.h"
@ -783,3 +781,5 @@ void sfz::diff<float, true>(absl::Span<const float> input, absl::Span<float> out
while (in < sentinel) while (in < sentinel)
_internals::snippetDiff(in, out); _internals::snippetDiff(in, out);
} }
#endif // SFIZZ_HAVE_SSE2

View file

@ -5,22 +5,21 @@
// If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz // If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz
#include "ScopedFTZ.h" #include "ScopedFTZ.h"
#if (HAVE_X86INTRIN_H) #include "SIMDConfig.h"
#include <x86intrin.h> #if SFIZZ_HAVE_SSE
#elif (HAVE_INTRIN_H) #include <pmmintrin.h> // x86 CSR is SSE, but FTZ bits SSE3 and up
#include <intrin.h> #elif SFIZZ_HAVE_NEON
#elif (HAVE_ARM_NEON_H)
#include "arm_neon.h" #include "arm_neon.h"
#endif #endif
ScopedFTZ::ScopedFTZ() ScopedFTZ::ScopedFTZ()
{ {
#if (HAVE_X86INTRIN_H || HAVE_INTRIN_H) #if SFIZZ_HAVE_SSE
unsigned mask = _MM_DENORMALS_ZERO_MASK | _MM_FLUSH_ZERO_MASK; unsigned mask = _MM_DENORMALS_ZERO_MASK | _MM_FLUSH_ZERO_MASK;
registerState = _mm_getcsr(); registerState = _mm_getcsr();
_mm_setcsr((registerState & (~mask)) | mask); _mm_setcsr((registerState & (~mask)) | mask);
#elif HAVE_ARM_NEON_H #elif SFIZZ_HAVE_NEON
intptr_t mask = (1 << 24); intptr_t mask = (1 << 24);
asm volatile("vmrs %0, fpscr" : "=r"(registerState)); asm volatile("vmrs %0, fpscr" : "=r"(registerState));
asm volatile("vmsr fpscr, %0" : : "ri"((registerState & (~mask)) | mask)); asm volatile("vmsr fpscr, %0" : : "ri"((registerState & (~mask)) | mask));
@ -29,9 +28,9 @@ ScopedFTZ::ScopedFTZ()
ScopedFTZ::~ScopedFTZ() ScopedFTZ::~ScopedFTZ()
{ {
#if (HAVE_X86INTRIN_H || HAVE_INTRIN_H) #if SFIZZ_HAVE_SSE
_mm_setcsr(registerState); _mm_setcsr(registerState);
#elif HAVE_ARM_NEON_H #elif SFIZZ_HAVE_NEON
asm volatile("vmrs %0, fpscr" : : "ri"(registerState)); asm volatile("vmrs %0, fpscr" : : "ri"(registerState));
#endif #endif
} }

View file

@ -160,7 +160,7 @@ unsigned Filter::channels() const
void Filter::setChannels(unsigned channels) void Filter::setChannels(unsigned channels)
{ {
ASSERT(channels < Impl::maxChannels); ASSERT(channels <= Impl::maxChannels);
if (P->fChannels != channels) { if (P->fChannels != channels) {
P->fChannels = channels; P->fChannels = channels;
clear(); clear();
@ -330,7 +330,7 @@ unsigned FilterEq::channels() const
void FilterEq::setChannels(unsigned channels) void FilterEq::setChannels(unsigned channels)
{ {
ASSERT(channels < Impl::maxChannels); ASSERT(channels <= Impl::maxChannels);
if (P->fChannels != channels) { if (P->fChannels != channels) {
P->fChannels = channels; P->fChannels = channels;
clear(); clear();

View file

@ -78,7 +78,7 @@ void sfz::Synth::callback(absl::string_view header, const std::vector<Opcode>& m
void sfz::Synth::buildRegion(const std::vector<Opcode>& regionOpcodes) void sfz::Synth::buildRegion(const std::vector<Opcode>& regionOpcodes)
{ {
auto lastRegion = std::make_unique<Region>(midiState, defaultPath); auto lastRegion = std::make_unique<Region>(resources.midiState, defaultPath);
auto parseOpcodes = [&](const auto& opcodes) { auto parseOpcodes = [&](const auto& opcodes) {
for (auto& opcode : opcodes) { for (auto& opcode : opcodes) {
@ -125,7 +125,7 @@ void sfz::Synth::clear()
fileTicket = -1; fileTicket = -1;
defaultSwitch = absl::nullopt; defaultSwitch = absl::nullopt;
defaultPath = ""; defaultPath = "";
midiState.reset(); resources.midiState.reset();
ccNames.clear(); ccNames.clear();
globalOpcodes.clear(); globalOpcodes.clear();
masterOpcodes.clear(); masterOpcodes.clear();
@ -159,7 +159,7 @@ void sfz::Synth::handleControlOpcodes(const std::vector<Opcode>& members)
case hash("set_cc"): case hash("set_cc"):
if (backParameter && Default::ccNumberRange.containsWithEnd(*backParameter)) { if (backParameter && Default::ccNumberRange.containsWithEnd(*backParameter)) {
const auto ccValue = readOpcode(member.value, Default::ccValueRange).value_or(0); const auto ccValue = readOpcode(member.value, Default::ccValueRange).value_or(0);
midiState.ccEvent(*backParameter, ccValue); resources.midiState.ccEvent(*backParameter, ccValue);
} }
break; break;
case hash("Label_cc"): case hash("Label_cc"):
@ -234,6 +234,9 @@ bool sfz::Synth::loadSfzFile(const fs::path& file)
++lastRegion; ++lastRegion;
}; };
size_t maxFilters { 0 };
size_t maxEQs { 0 };
while (currentRegion < lastRegion.base()) { while (currentRegion < lastRegion.base()) {
auto region = currentRegion->get(); auto region = currentRegion->get();
@ -287,7 +290,7 @@ bool sfz::Synth::loadSfzFile(const fs::path& file)
// Defaults // Defaults
for (int ccIndex = 0; ccIndex < config::numCCs; ccIndex++) { for (int ccIndex = 0; ccIndex < config::numCCs; ccIndex++) {
region->registerCC(ccIndex, midiState.getCCValue(ccIndex)); region->registerCC(ccIndex, resources.midiState.getCCValue(ccIndex));
} }
if (defaultSwitch) { if (defaultSwitch) {
@ -316,6 +319,8 @@ bool sfz::Synth::loadSfzFile(const fs::path& file)
region->registerPitchWheel(0); region->registerPitchWheel(0);
region->registerAftertouch(0); region->registerAftertouch(0);
region->registerTempo(2.0f); region->registerTempo(2.0f);
maxFilters = max(maxFilters, region->filters.size());
maxEQs = max(maxEQs, region->equalizers.size());
++currentRegion; ++currentRegion;
} }
@ -324,6 +329,11 @@ bool sfz::Synth::loadSfzFile(const fs::path& file)
regions.resize(remainingRegions); regions.resize(remainingRegions);
modificationTime = checkModificationTime(); modificationTime = checkModificationTime();
for (auto& voice: voices) {
voice->setMaxFiltersPerVoice(maxFilters);
voice->setMaxEQsPerVoice(maxEQs);
}
return parserReturned; return parserReturned;
} }
@ -391,6 +401,9 @@ void sfz::Synth::setSampleRate(float sampleRate) noexcept
this->sampleRate = sampleRate; this->sampleRate = sampleRate;
for (auto& voice : voices) for (auto& voice : voices)
voice->setSampleRate(sampleRate); voice->setSampleRate(sampleRate);
resources.filterPool.setSampleRate(sampleRate);
resources.eqPool.setSampleRate(sampleRate);
} }
void sfz::Synth::renderBlock(AudioSpan<float> buffer) noexcept void sfz::Synth::renderBlock(AudioSpan<float> buffer) noexcept
@ -430,7 +443,7 @@ void sfz::Synth::noteOn(int delay, int noteNumber, uint8_t velocity) noexcept
ASSERT(noteNumber < 128); ASSERT(noteNumber < 128);
ASSERT(noteNumber >= 0); ASSERT(noteNumber >= 0);
midiState.noteOnEvent(noteNumber, velocity); resources.midiState.noteOnEvent(noteNumber, velocity);
AtomicGuard callbackGuard { inCallback }; AtomicGuard callbackGuard { inCallback };
if (!canEnterCallback) if (!canEnterCallback)
@ -444,7 +457,7 @@ void sfz::Synth::noteOff(int delay, int noteNumber, uint8_t velocity [[maybe_unu
ASSERT(noteNumber < 128); ASSERT(noteNumber < 128);
ASSERT(noteNumber >= 0); ASSERT(noteNumber >= 0);
midiState.noteOffEvent(noteNumber, velocity); resources.midiState.noteOffEvent(noteNumber, velocity);
AtomicGuard callbackGuard { inCallback }; AtomicGuard callbackGuard { inCallback };
if (!canEnterCallback) if (!canEnterCallback)
@ -453,7 +466,7 @@ void sfz::Synth::noteOff(int delay, int noteNumber, uint8_t velocity [[maybe_unu
// FIXME: Some keyboards (e.g. Casio PX5S) can send a real note-off velocity. In this case, do we have a // FIXME: Some keyboards (e.g. Casio PX5S) can send a real note-off velocity. In this case, do we have a
// way in sfz to specify that a release trigger should NOT use the note-on velocity? // way in sfz to specify that a release trigger should NOT use the note-on velocity?
// auto replacedVelocity = (velocity == 0 ? sfz::getNoteVelocity(noteNumber) : velocity); // auto replacedVelocity = (velocity == 0 ? sfz::getNoteVelocity(noteNumber) : velocity);
const auto replacedVelocity = midiState.getNoteVelocity(noteNumber); const auto replacedVelocity = resources.midiState.getNoteVelocity(noteNumber);
for (auto& voice : voices) for (auto& voice : voices)
voice->registerNoteOff(delay, noteNumber, replacedVelocity); voice->registerNoteOff(delay, noteNumber, replacedVelocity);
@ -508,7 +521,7 @@ void sfz::Synth::cc(int delay, int ccNumber, uint8_t ccValue) noexcept
return; return;
} }
midiState.ccEvent(ccNumber, ccValue); resources.midiState.ccEvent(ccNumber, ccValue);
for (auto& voice : voices) for (auto& voice : voices)
voice->registerCC(delay, ccNumber, ccValue); voice->registerCC(delay, ccNumber, ccValue);
@ -528,7 +541,7 @@ void sfz::Synth::pitchWheel(int delay, int pitch) noexcept
ASSERT(pitch <= 8192); ASSERT(pitch <= 8192);
ASSERT(pitch >= -8192); ASSERT(pitch >= -8192);
midiState.pitchBendEvent(pitch); resources.midiState.pitchBendEvent(pitch);
for (auto& region: regions) { for (auto& region: regions) {
region->registerPitchWheel(pitch); region->registerPitchWheel(pitch);
@ -611,7 +624,7 @@ void sfz::Synth::resetVoices(int numVoices)
voices.clear(); voices.clear();
for (int i = 0; i < numVoices; ++i) for (int i = 0; i < numVoices; ++i)
voices.push_back(std::make_unique<Voice>(midiState, resources)); voices.push_back(std::make_unique<Voice>(resources));
for (auto& voice: voices) { for (auto& voice: voices) {
voice->setSampleRate(this->sampleRate); voice->setSampleRate(this->sampleRate);
@ -678,7 +691,7 @@ void sfz::Synth::resetAllControllers(int delay) noexcept
if (!canEnterCallback) if (!canEnterCallback)
return; return;
midiState.resetAllControllers(); resources.midiState.resetAllControllers();
for (auto& voice: voices) { for (auto& voice: voices) {
voice->registerPitchWheel(delay, 0); voice->registerPitchWheel(delay, 0);
for (int cc = 0; cc < config::numCCs; ++cc) for (int cc = 0; cc < config::numCCs; ++cc)

View file

@ -324,7 +324,7 @@ public:
*/ */
void disableFreeWheeling() noexcept; void disableFreeWheeling() noexcept;
const MidiState& getMidiState() const noexcept { return midiState; } const MidiState& getMidiState() const noexcept { return resources.midiState; }
/** /**
* @brief Check if the SFZ should be reloaded. * @brief Check if the SFZ should be reloaded.
@ -446,7 +446,6 @@ private:
// Singletons passed as references to the voices // Singletons passed as references to the voices
Resources resources; Resources resources;
MidiState midiState;
// Control opcodes // Control opcodes
std::string defaultPath { "" }; std::string defaultPath { "" };

View file

@ -14,9 +14,11 @@
#include "absl/algorithm/container.h" #include "absl/algorithm/container.h"
#include <memory> #include <memory>
sfz::Voice::Voice(const sfz::MidiState& midiState, sfz::Resources& resources) sfz::Voice::Voice(sfz::Resources& resources)
: midiState(midiState), resources(resources) : resources(resources)
{ {
filters.reserve(config::filtersPerVoice);
equalizers.reserve(config::eqsPerVoice);
} }
void sfz::Voice::startVoice(Region* region, int delay, int number, uint8_t value, sfz::Voice::TriggerType triggerType) noexcept void sfz::Voice::startVoice(Region* region, int delay, int number, uint8_t value, sfz::Voice::TriggerType triggerType) noexcept
@ -45,7 +47,7 @@ void sfz::Voice::startVoice(Region* region, int delay, int number, uint8_t value
baseVolumedB = region->getBaseVolumedB(number); baseVolumedB = region->getBaseVolumedB(number);
auto volumedB { baseVolumedB }; auto volumedB { baseVolumedB };
if (region->volumeCC) if (region->volumeCC)
volumedB += normalizeCC(midiState.getCCValue(region->volumeCC->first)) * region->volumeCC->second; volumedB += normalizeCC(resources.midiState.getCCValue(region->volumeCC->first)) * region->volumeCC->second;
volumeEnvelope.reset(db2mag(Default::volumeRange.clamp(volumedB))); volumeEnvelope.reset(db2mag(Default::volumeRange.clamp(volumedB)));
baseGain = region->getBaseGain(); baseGain = region->getBaseGain();
@ -54,28 +56,28 @@ void sfz::Voice::startVoice(Region* region, int delay, int number, uint8_t value
float gain { baseGain }; float gain { baseGain };
if (region->amplitudeCC) if (region->amplitudeCC)
gain *= normalizeCC(midiState.getCCValue(region->amplitudeCC->first)) * normalizePercents(region->amplitudeCC->second); gain *= normalizeCC(resources.midiState.getCCValue(region->amplitudeCC->first)) * normalizePercents(region->amplitudeCC->second);
amplitudeEnvelope.reset(Default::normalizedRange.clamp(gain)); amplitudeEnvelope.reset(Default::normalizedRange.clamp(gain));
float crossfadeGain { region->getCrossfadeGain(midiState.getCCArray()) }; float crossfadeGain { region->getCrossfadeGain(resources.midiState.getCCArray()) };
crossfadeEnvelope.reset(Default::normalizedRange.clamp(crossfadeGain)); crossfadeEnvelope.reset(Default::normalizedRange.clamp(crossfadeGain));
basePan = normalizePercents(region->pan); basePan = normalizePercents(region->pan);
auto pan { basePan }; auto pan { basePan };
if (region->panCC) if (region->panCC)
pan += normalizeCC(midiState.getCCValue(region->panCC->first)) * normalizePercents(region->panCC->second); pan += normalizeCC(resources.midiState.getCCValue(region->panCC->first)) * normalizePercents(region->panCC->second);
panEnvelope.reset(Default::symmetricNormalizedRange.clamp(pan)); panEnvelope.reset(Default::symmetricNormalizedRange.clamp(pan));
basePosition = normalizePercents(region->position); basePosition = normalizePercents(region->position);
auto position { basePosition }; auto position { basePosition };
if (region->positionCC) if (region->positionCC)
position += normalizeCC(midiState.getCCValue(region->positionCC->first)) * normalizePercents(region->positionCC->second); position += normalizeCC(resources.midiState.getCCValue(region->positionCC->first)) * normalizePercents(region->positionCC->second);
positionEnvelope.reset(Default::symmetricNormalizedRange.clamp(position)); positionEnvelope.reset(Default::symmetricNormalizedRange.clamp(position));
baseWidth = normalizePercents(region->width); baseWidth = normalizePercents(region->width);
auto width { baseWidth }; auto width { baseWidth };
if (region->widthCC) if (region->widthCC)
width += normalizeCC(midiState.getCCValue(region->widthCC->first)) * normalizePercents(region->widthCC->second); width += normalizeCC(resources.midiState.getCCValue(region->widthCC->first)) * normalizePercents(region->widthCC->second);
widthEnvelope.reset(Default::symmetricNormalizedRange.clamp(width)); widthEnvelope.reset(Default::symmetricNormalizedRange.clamp(width));
pitchBendEnvelope.setFunction([region](float pitchValue){ pitchBendEnvelope.setFunction([region](float pitchValue){
@ -83,7 +85,24 @@ void sfz::Voice::startVoice(Region* region, int delay, int number, uint8_t value
const auto bendInCents = normalizedBend > 0.0f ? normalizedBend * region->bendUp : -normalizedBend * region->bendDown; const auto bendInCents = normalizedBend > 0.0f ? normalizedBend * region->bendUp : -normalizedBend * region->bendDown;
return centsFactor(bendInCents); return centsFactor(bendInCents);
}); });
pitchBendEnvelope.reset(static_cast<float>(midiState.getPitchBend())); pitchBendEnvelope.reset(static_cast<float>(resources.midiState.getPitchBend()));
// Check that we can handle the number of filters; filters should be cleared here
ASSERT((filters.capacity() - filters.size()) >= region->filters.size());
ASSERT((equalizers.capacity() - equalizers.size()) >= region->equalizers.size());
const unsigned numChannels = region->isStereo ? 2 : 1;
for (auto& filter: region->filters) {
auto newFilter = resources.filterPool.getFilter(filter, numChannels, number, value);
if (newFilter)
filters.push_back(newFilter);
}
for (auto& eq: region->equalizers) {
auto newEQ = resources.eqPool.getEQ(eq, numChannels, value);
if (newEQ)
equalizers.push_back(newEQ);
}
sourcePosition = region->getOffset(); sourcePosition = region->getOffset();
triggerDelay = delay; triggerDelay = delay;
@ -98,7 +117,7 @@ void sfz::Voice::prepareEGEnvelope(int delay, uint8_t velocity) noexcept
auto secondsToSamples = [this](auto timeInSeconds) { auto secondsToSamples = [this](auto timeInSeconds) {
return static_cast<int>(timeInSeconds * sampleRate); return static_cast<int>(timeInSeconds * sampleRate);
}; };
const auto& ccArray = midiState.getCCArray(); const auto& ccArray = resources.midiState.getCCArray();
egEnvelope.reset( egEnvelope.reset(
secondsToSamples(region->amplitudeEG.getAttack(ccArray, velocity)), secondsToSamples(region->amplitudeEG.getAttack(ccArray, velocity)),
secondsToSamples(region->amplitudeEG.getRelease(ccArray, velocity)), secondsToSamples(region->amplitudeEG.getRelease(ccArray, velocity)),
@ -141,7 +160,7 @@ void sfz::Voice::registerNoteOff(int delay, int noteNumber, uint8_t velocity [[m
if (region->loopMode == SfzLoopMode::one_shot) if (region->loopMode == SfzLoopMode::one_shot)
return; return;
if (!region->checkSustain || midiState.getCCValue(config::sustainCC) < config::halfCCThreshold) if (!region->checkSustain || resources.midiState.getCCValue(config::sustainCC) < config::halfCCThreshold)
release(delay); release(delay);
} }
} }
@ -192,7 +211,7 @@ void sfz::Voice::registerCC(int delay, int ccNumber, uint8_t ccValue) noexcept
} }
if (region->crossfadeCCInRange.contains(ccNumber) || region->crossfadeCCOutRange.contains(ccNumber)) { if (region->crossfadeCCInRange.contains(ccNumber) || region->crossfadeCCOutRange.contains(ccNumber)) {
const float crossfadeGain = region->getCrossfadeGain(midiState.getCCArray()); const float crossfadeGain = region->getCrossfadeGain(resources.midiState.getCCArray());
crossfadeEnvelope.registerEvent(delay, Default::normalizedRange.clamp(crossfadeGain)); crossfadeEnvelope.registerEvent(delay, Default::normalizedRange.clamp(crossfadeGain));
} }
} }
@ -289,6 +308,17 @@ void sfz::Voice::processMono(AudioSpan<float> buffer) noexcept
egEnvelope.getBlock(span1); egEnvelope.getBlock(span1);
applyGain<float>(span1, leftBuffer); applyGain<float>(span1, leftBuffer);
// Filtering and EQ
const float* inputChannel[1] { leftBuffer.data() };
float* outputChannel[1] { leftBuffer.data() };
for (auto& filter: filters) {
filter->process(inputChannel, outputChannel, numSamples);
}
for (auto& eq: equalizers) {
eq->process(inputChannel, outputChannel, numSamples);
}
// Prepare for stereo output // Prepare for stereo output
copy<float>(leftBuffer, rightBuffer); copy<float>(leftBuffer, rightBuffer);
@ -346,6 +376,18 @@ void sfz::Voice::processStereo(AudioSpan<float> buffer) noexcept
applyGain(sqrtTwoInv<float>, leftBuffer); applyGain(sqrtTwoInv<float>, leftBuffer);
add<float>(midBufferRight, sideBuffer); add<float>(midBufferRight, sideBuffer);
applyGain(sqrtTwoInv<float>, rightBuffer); applyGain(sqrtTwoInv<float>, rightBuffer);
// Filtering and EQ
const float* inputChannels[2] { leftBuffer.data(), rightBuffer.data() };
float* outputChannels[2] { leftBuffer.data(), rightBuffer.data() };
for (auto& filter: filters) {
filter->process(inputChannels, outputChannels, numSamples);
}
for (auto& eq: equalizers) {
eq->process(inputChannels, outputChannels, numSamples);
}
} }
void sfz::Voice::fillWithData(AudioSpan<float> buffer) noexcept void sfz::Voice::fillWithData(AudioSpan<float> buffer) noexcept
@ -443,35 +485,41 @@ void sfz::Voice::fillWithData(AudioSpan<float> buffer) noexcept
void sfz::Voice::fillWithGenerator(AudioSpan<float> buffer) noexcept void sfz::Voice::fillWithGenerator(AudioSpan<float> buffer) noexcept
{ {
if (region->sample != "*sine") const auto leftSpan = buffer.getSpan(0);
return; const auto rightSpan = buffer.getSpan(1);
if (region->sample == "*noise") {
absl::c_generate(leftSpan, [&](){ return noiseDist(Random::randomGenerator); });
absl::c_generate(rightSpan, [&](){ return noiseDist(Random::randomGenerator); });
}
else if (region->sample == "*sine") {
// TODO: wavetables for sine and other generators
if (buffer.getNumFrames() == 0)
return;
if (buffer.getNumFrames() == 0) auto jumps = tempSpan1.first(buffer.getNumFrames());
return; auto bends = tempSpan2.first(buffer.getNumFrames());
auto phases = tempSpan2.first(buffer.getNumFrames());
auto jumps = tempSpan1.first(buffer.getNumFrames()); const float step = baseFrequency * twoPi<float> / sampleRate;
auto bends = tempSpan2.first(buffer.getNumFrames()); fill<float>(jumps, step);
auto phases = tempSpan2.first(buffer.getNumFrames());
const float step = baseFrequency * twoPi<float> / sampleRate; if (region->bendStep > 1)
fill<float>(jumps, step); pitchBendEnvelope.getQuantizedBlock(bends, bendStepFactor);
else
pitchBendEnvelope.getBlock(bends);
if (region->bendStep > 1) applyGain<float>(bends, jumps);
pitchBendEnvelope.getQuantizedBlock(bends, bendStepFactor); jumps[0] += phase;
else cumsum<float>(jumps, phases);
pitchBendEnvelope.getBlock(bends); phase = phases.back();
applyGain<float>(bends, jumps); sin<float>(phases, leftSpan);
jumps[0] += phase; copy<float>(leftSpan, rightSpan);
cumsum<float>(jumps, phases);
phase = phases.back();
sin<float>(phases, buffer.getSpan(0)); // Wrap the phase so we don't loose too much precision on longer notes
copy<float>(buffer.getSpan(0), buffer.getSpan(1)); const auto numTwoPiWraps = static_cast<int>(phase / twoPi<float>);
phase -= twoPi<float> * static_cast<float>(numTwoPiWraps);
// Wrap the phase so we don't loose too much precision on longer notes }
const auto numTwoPiWraps = static_cast<int>(phase / twoPi<float>);
phase -= twoPi<float> * static_cast<float>(numTwoPiWraps);
} }
bool sfz::Voice::checkOffGroup(int delay, uint32_t group) noexcept bool sfz::Voice::checkOffGroup(int delay, uint32_t group) noexcept
@ -513,6 +561,8 @@ void sfz::Voice::reset() noexcept
sourcePosition = 0; sourcePosition = 0;
floatPositionOffset = 0.0f; floatPositionOffset = 0.0f;
noteIsOff = false; noteIsOff = false;
filters.clear();
equalizers.clear();
} }
float sfz::Voice::getMeanSquaredAverage() const noexcept float sfz::Voice::getMeanSquaredAverage() const noexcept
@ -529,3 +579,17 @@ uint32_t sfz::Voice::getSourcePosition() const noexcept
{ {
return sourcePosition; return sourcePosition;
} }
void sfz::Voice::setMaxFiltersPerVoice(size_t numFilters)
{
// There are filters in there, this call is unexpected
ASSERT(filters.size() == 0);
filters.reserve(numFilters);
}
void sfz::Voice::setMaxEQsPerVoice(size_t numFilters)
{
// There are filters in there, this call is unexpected
ASSERT(filters.size() == 0);
filters.reserve(numFilters);
}

View file

@ -18,6 +18,7 @@
#include <absl/types/span.h> #include <absl/types/span.h>
#include <atomic> #include <atomic>
#include <memory> #include <memory>
#include <random>
namespace sfz { namespace sfz {
/** /**
@ -34,7 +35,7 @@ public:
* *
* @param midiState * @param midiState
*/ */
Voice(const MidiState& midiState, Resources& resources); Voice(Resources& resources);
enum class TriggerType { enum class TriggerType {
NoteOn, NoteOn,
NoteOff, NoteOff,
@ -196,6 +197,18 @@ public:
* @return * @return
*/ */
const Region* getRegion() const noexcept { return region; } const Region* getRegion() const noexcept { return region; }
/**
* @brief Set the max number of filters per voice
*
* @param numFilters
*/
void setMaxFiltersPerVoice(size_t numFilters);
/**
* @brief Set the max number of EQs per voice
*
* @param numFilters
*/
void setMaxEQsPerVoice(size_t numEQs);
private: private:
/** /**
* @brief Fill a span with data from a file source. This is the first step * @brief Fill a span with data from a file source. This is the first step
@ -281,9 +294,11 @@ private:
int minEnvelopeDelay { config::defaultSamplesPerBlock / 2 }; int minEnvelopeDelay { config::defaultSamplesPerBlock / 2 };
float sampleRate { config::defaultSampleRate }; float sampleRate { config::defaultSampleRate };
const MidiState& midiState;
Resources& resources; Resources& resources;
std::vector<FilterHolderPtr> filters;
std::vector<EQHolderPtr> equalizers;
ADSREnvelope<float> egEnvelope; ADSREnvelope<float> egEnvelope;
LinearEnvelope<float> amplitudeEnvelope; // linear events LinearEnvelope<float> amplitudeEnvelope; // linear events
LinearEnvelope<float> crossfadeEnvelope; LinearEnvelope<float> crossfadeEnvelope;
@ -294,6 +309,8 @@ private:
MultiplicativeEnvelope<float> volumeEnvelope; MultiplicativeEnvelope<float> volumeEnvelope;
float bendStepFactor { centsFactor(1) }; float bendStepFactor { centsFactor(1) };
std::normal_distribution<float> noiseDist { 0, config::noiseVariance };
HistoricalBuffer<float> powerHistory { config::powerHistoryLength }; HistoricalBuffer<float> powerHistory { config::powerHistoryLength };
LEAK_DETECTOR(Voice); LEAK_DETECTOR(Voice);
}; };