Merge branch 'develop' into width-position
This commit is contained in:
commit
35d62280a7
23 changed files with 807 additions and 109 deletions
|
|
@ -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 };
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
6
cmake/SfizzSIMDSourceFiles.cmake
Normal file
6
cmake/SfizzSIMDSourceFiles.cmake
Normal 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})
|
||||||
|
|
@ -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})
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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.
|
||||||
|
|
|
||||||
|
|
@ -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
138
src/sfizz/EQPool.cpp
Normal 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
123
src/sfizz/EQPool.h
Normal 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
152
src/sfizz/FilterPool.cpp
Normal 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
127
src/sfizz/FilterPool.h
Normal 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;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
@ -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
69
src/sfizz/SIMDConfig.h
Normal 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
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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 { "" };
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue