Merge pull request #142 from paulfd/midi-state-block-processing
The midi state now tracks time through block duration; you need to call advanceTime(numSamples) at each callback. The midi state now stores all CC and pitch events that happened during the block. Added group polyphony and note_polyphony support, as well as note_selfmask support. Multiple CCs can modulate pan, amplitude, width, position rather than one. The voices do not store CC history for the modulation targets and rely on the midi state to provide the events that happened during the last block. Removed the EventEnvelopes; they're replaced by generic free functions that take as input an EventVector from the midi state; old tests migrated. Added support for tune_cc and pitch_cc related opcodes. In all this process, I also removed most of the per-voice temporary buffers and preallocation to replace everything by a BufferPool that distributes buffers around, and can track the maximum buffer usage in debug mode. The number of concurrent buffers are set at compile-time, so it must be adapted to the current processing needs.
This commit is contained in:
commit
2f2de8c677
38 changed files with 1680 additions and 1383 deletions
|
|
@ -10,7 +10,7 @@
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include "EventEnvelopes.h"
|
#include "SfzHelpers.h"
|
||||||
#include "absl/types/span.h"
|
#include "absl/types/span.h"
|
||||||
|
|
||||||
class EnvelopeFixture : public benchmark::Fixture {
|
class EnvelopeFixture : public benchmark::Fixture {
|
||||||
|
|
@ -29,45 +29,55 @@ public:
|
||||||
}
|
}
|
||||||
std::random_device rd { };
|
std::random_device rd { };
|
||||||
std::mt19937 gen { rd() };
|
std::mt19937 gen { rd() };
|
||||||
std::uniform_real_distribution<float> dist { 1, 30 };
|
std::uniform_real_distribution<float> dist { 2, 30 };
|
||||||
std::vector<float> input;
|
std::vector<float> input;
|
||||||
std::vector<float> output;
|
std::vector<float> output;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
BENCHMARK_DEFINE_F(EnvelopeFixture, Linear)(benchmark::State& state) {
|
BENCHMARK_DEFINE_F(EnvelopeFixture, Linear)(benchmark::State& state) {
|
||||||
sfz::LinearEnvelope<float> envelope;
|
|
||||||
for (auto _ : state)
|
for (auto _ : state)
|
||||||
{
|
{
|
||||||
envelope.registerEvent(static_cast<int>(state.range(0) - 1), dist(gen));
|
sfz::EventVector events {
|
||||||
envelope.getBlock(absl::MakeSpan(output));
|
{ 0, 0.0f },
|
||||||
|
{ static_cast<int>(state.range(0) - 1), dist(gen) }
|
||||||
|
};
|
||||||
|
linearEnvelope(events, absl::MakeSpan(output), [](float x) { return x; });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
BENCHMARK_DEFINE_F(EnvelopeFixture, LinearQuantized)(benchmark::State& state) {
|
BENCHMARK_DEFINE_F(EnvelopeFixture, LinearQuantized)(benchmark::State& state) {
|
||||||
sfz::LinearEnvelope<float> envelope;
|
|
||||||
for (auto _ : state)
|
for (auto _ : state)
|
||||||
{
|
{
|
||||||
envelope.registerEvent(static_cast<int>(state.range(0) - 1), dist(gen));
|
sfz::EventVector events {
|
||||||
envelope.getQuantizedBlock(absl::MakeSpan(output), 0.5);
|
{ 0, 0.0f },
|
||||||
|
{ static_cast<int>(state.range(0) - 1), dist(gen) }
|
||||||
|
};
|
||||||
|
linearEnvelope(
|
||||||
|
events, absl::MakeSpan(output), [](float x) { return x; }, 0.5);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
BENCHMARK_DEFINE_F(EnvelopeFixture, Multiplicative)(benchmark::State& state) {
|
BENCHMARK_DEFINE_F(EnvelopeFixture, Multiplicative)(benchmark::State& state) {
|
||||||
sfz::MultiplicativeEnvelope<float> envelope;
|
|
||||||
for (auto _ : state)
|
for (auto _ : state)
|
||||||
{
|
{
|
||||||
envelope.registerEvent(static_cast<int>(state.range(0) - 1), dist(gen));
|
sfz::EventVector events {
|
||||||
envelope.getBlock(absl::MakeSpan(output));
|
{ 0, 1.0f },
|
||||||
|
{ static_cast<int>(state.range(0) - 1), dist(gen) }
|
||||||
|
};
|
||||||
|
multiplicativeEnvelope(events, absl::MakeSpan(output), [](float x) { return x; });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
BENCHMARK_DEFINE_F(EnvelopeFixture, MultiplicativeQuantized)(benchmark::State& state) {
|
BENCHMARK_DEFINE_F(EnvelopeFixture, MultiplicativeQuantized)(benchmark::State& state) {
|
||||||
sfz::MultiplicativeEnvelope<float> envelope;
|
|
||||||
for (auto _ : state)
|
for (auto _ : state)
|
||||||
{
|
{
|
||||||
envelope.registerEvent(static_cast<int>(state.range(0) - 1), dist(gen));
|
sfz::EventVector events {
|
||||||
envelope.getQuantizedBlock(absl::MakeSpan(output), 1.5);
|
{ 0, 1.0f },
|
||||||
|
{ static_cast<int>(state.range(0) - 1), dist(gen) }
|
||||||
|
};
|
||||||
|
multiplicativeEnvelope(
|
||||||
|
events, absl::MakeSpan(output), [](float x) { return x; }, 2.0f);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -69,11 +69,11 @@ sfizz_add_benchmark(bm_logger BM_logger.cpp)
|
||||||
target_link_libraries(bm_logger PRIVATE sfizz::sfizz)
|
target_link_libraries(bm_logger PRIVATE sfizz::sfizz)
|
||||||
|
|
||||||
if (TARGET sfizz-samplerate)
|
if (TARGET sfizz-samplerate)
|
||||||
sfizz_add_benchmark(bm_resample BM_resample.cpp ${BENCHMARK_SIMD_SOURCES})
|
sfizz_add_benchmark(bm_resample BM_resample.cpp ${BENCHMARK_SIMD_SOURCES})
|
||||||
target_link_libraries(bm_resample PRIVATE sfizz-samplerate sfizz-sndfile)
|
target_link_libraries(bm_resample PRIVATE sfizz-samplerate sfizz-sndfile)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
sfizz_add_benchmark(bm_envelopes BM_envelopes.cpp ../src/sfizz/MidiState.cpp ../src/sfizz/FloatEnvelopes.cpp)
|
sfizz_add_benchmark(bm_envelopes BM_envelopes.cpp)
|
||||||
|
|
||||||
sfizz_add_benchmark(bm_wavfile BM_wavfile.cpp)
|
sfizz_add_benchmark(bm_wavfile BM_wavfile.cpp)
|
||||||
target_link_libraries(bm_wavfile PRIVATE sfizz-sndfile)
|
target_link_libraries(bm_wavfile PRIVATE sfizz-sndfile)
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,6 @@ clang-tidy \
|
||||||
src/sfizz/Curve.cpp \
|
src/sfizz/Curve.cpp \
|
||||||
src/sfizz/Effects.cpp \
|
src/sfizz/Effects.cpp \
|
||||||
src/sfizz/EQPool.cpp \
|
src/sfizz/EQPool.cpp \
|
||||||
src/sfizz/EventEnvelopes.cpp \
|
|
||||||
src/sfizz/FilePool.cpp \
|
src/sfizz/FilePool.cpp \
|
||||||
src/sfizz/FilterPool.cpp \
|
src/sfizz/FilterPool.cpp \
|
||||||
src/sfizz/FloatEnvelopes.cpp \
|
src/sfizz/FloatEnvelopes.cpp \
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@ void ADSREnvelope<Type>::reset(const Region& region, const MidiState& state, int
|
||||||
this->release = secondsToSamples(region.amplitudeEG.getRelease(state, velocity));
|
this->release = secondsToSamples(region.amplitudeEG.getRelease(state, velocity));
|
||||||
this->hold = secondsToSamples(region.amplitudeEG.getHold(state, velocity));
|
this->hold = secondsToSamples(region.amplitudeEG.getHold(state, velocity));
|
||||||
this->peak = 1.0;
|
this->peak = 1.0;
|
||||||
this->sustain = normalizePercents(region.amplitudeEG.getSustain(state, velocity));
|
this->sustain = normalizePercents(region.amplitudeEG.getSustain(state, velocity));
|
||||||
this->start = this->peak * normalizePercents(region.amplitudeEG.getStart(state, velocity));
|
this->start = this->peak * normalizePercents(region.amplitudeEG.getStart(state, velocity));
|
||||||
|
|
||||||
releaseDelay = 0;
|
releaseDelay = 0;
|
||||||
|
|
|
||||||
184
src/sfizz/BufferPool.h
Normal file
184
src/sfizz/BufferPool.h
Normal file
|
|
@ -0,0 +1,184 @@
|
||||||
|
// 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
|
||||||
|
#include "Config.h"
|
||||||
|
#include "Debug.h"
|
||||||
|
#include "Buffer.h"
|
||||||
|
#include "AudioBuffer.h"
|
||||||
|
#include <array>
|
||||||
|
#include <memory>
|
||||||
|
#include <functional>
|
||||||
|
#include "absl/algorithm/container.h"
|
||||||
|
#ifndef NDEBUG
|
||||||
|
#include "MathHelpers.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace sfz {
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
class SpanHolder {
|
||||||
|
public:
|
||||||
|
SpanHolder() {}
|
||||||
|
SpanHolder(const SpanHolder<T>&) = delete;
|
||||||
|
SpanHolder<T>& operator=(const SpanHolder<T>&) = delete;
|
||||||
|
SpanHolder(SpanHolder<T>&& other)
|
||||||
|
{
|
||||||
|
this->value = other.value;
|
||||||
|
this->available = other.available;
|
||||||
|
other.available = nullptr;
|
||||||
|
}
|
||||||
|
SpanHolder<T>& operator=(SpanHolder<T>&& other)
|
||||||
|
{
|
||||||
|
this->value = other.value;
|
||||||
|
this->available = other.available;
|
||||||
|
other.available = nullptr;
|
||||||
|
}
|
||||||
|
SpanHolder(T&& value, int* available)
|
||||||
|
: value(std::forward<T>(value))
|
||||||
|
, available(available)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
T& operator*() { return value; }
|
||||||
|
T* operator->() { return &value; }
|
||||||
|
explicit operator bool() const { return available != nullptr; }
|
||||||
|
~SpanHolder()
|
||||||
|
{
|
||||||
|
if (available)
|
||||||
|
*available += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
T value {};
|
||||||
|
int* available { nullptr };
|
||||||
|
};
|
||||||
|
|
||||||
|
class BufferPool {
|
||||||
|
public:
|
||||||
|
BufferPool()
|
||||||
|
{
|
||||||
|
for (auto& buffer : stereoBuffers) {
|
||||||
|
buffer.addChannels(2);
|
||||||
|
}
|
||||||
|
monoAvailable.resize(config::bufferPoolSize);
|
||||||
|
stereoAvailable.resize(config::stereoBufferPoolSize);
|
||||||
|
indexAvailable.resize(config::indexBufferPoolSize);
|
||||||
|
_setBufferSize(config::defaultSamplesPerBlock);
|
||||||
|
}
|
||||||
|
|
||||||
|
void setBufferSize(unsigned bufferSize)
|
||||||
|
{
|
||||||
|
ASSERT(absl::c_all_of(monoAvailable, [](int value) { return value == 1; }));
|
||||||
|
ASSERT(absl::c_all_of(indexAvailable, [](int value) { return value == 1; }));
|
||||||
|
ASSERT(absl::c_all_of(stereoAvailable, [](int value) { return value == 1; }));
|
||||||
|
_setBufferSize(bufferSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
SpanHolder<absl::Span<float>> getBuffer(size_t numFrames)
|
||||||
|
{
|
||||||
|
const auto availableIt = absl::c_find(monoAvailable, 1);
|
||||||
|
if (availableIt == monoAvailable.end()) {
|
||||||
|
DBG("[sfizz] No free buffers available...");
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
const auto freeIndex = std::distance(monoAvailable.begin(), availableIt);
|
||||||
|
|
||||||
|
if (monoBuffers[freeIndex].size() < numFrames) {
|
||||||
|
DBG("[sfizz] Someone asked for a buffer of size " << numFrames << "; only " << monoBuffers[freeIndex].size() << " available...");
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifndef NDEBUG
|
||||||
|
maxBuffersUsed = 1 + absl::c_count_if(monoAvailable, [](int value) { return value == 0; });
|
||||||
|
#endif
|
||||||
|
*availableIt -= 1;
|
||||||
|
return { absl::MakeSpan(monoBuffers[freeIndex]).first(numFrames), &*availableIt };
|
||||||
|
}
|
||||||
|
|
||||||
|
SpanHolder<absl::Span<int>> getIndexBuffer(size_t numFrames)
|
||||||
|
{
|
||||||
|
const auto availableIt = absl::c_find(indexAvailable, 1);
|
||||||
|
if (availableIt == indexAvailable.end()) {
|
||||||
|
DBG("[sfizz] No available index buffers in the pool");
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
const auto freeIndex = std::distance(indexAvailable.begin(), availableIt);
|
||||||
|
|
||||||
|
if (indexBuffers[freeIndex].size() < numFrames) {
|
||||||
|
DBG("[sfizz] Someone asked for a index buffer of size " << numFrames << "; only " << indexBuffers[freeIndex].size() << " available...");
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifndef NDEBUG
|
||||||
|
maxIndexBuffersUsed = 1 + absl::c_count_if(indexAvailable, [](int value) { return value == 0; });
|
||||||
|
#endif
|
||||||
|
*availableIt -= 1;
|
||||||
|
return { absl::MakeSpan(indexBuffers[freeIndex]).first(numFrames), &*availableIt };
|
||||||
|
}
|
||||||
|
|
||||||
|
SpanHolder<AudioSpan<float>> getStereoBuffer(size_t numFrames)
|
||||||
|
{
|
||||||
|
const auto availableIt = absl::c_find(stereoAvailable, 1);
|
||||||
|
if (availableIt == stereoAvailable.end()) {
|
||||||
|
DBG("[sfizz] No available stereo buffers in the pool");
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
const auto freeIndex = std::distance(stereoAvailable.begin(), availableIt);
|
||||||
|
|
||||||
|
if (stereoBuffers[freeIndex].getNumFrames() < numFrames) {
|
||||||
|
DBG("[sfizz] Someone asked for a stereo buffer of size " << numFrames << "; only " << stereoBuffers[freeIndex].getNumFrames() << " available...");
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifndef NDEBUG
|
||||||
|
maxStereoBuffersUsed = 1 + absl::c_count_if(stereoAvailable, [](int value) { return value == 0; });
|
||||||
|
#endif
|
||||||
|
*availableIt -= 1;
|
||||||
|
return { sfz::AudioSpan<float>(stereoBuffers[freeIndex]).first(numFrames), &*availableIt };
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifndef NDEBUG
|
||||||
|
~BufferPool()
|
||||||
|
{
|
||||||
|
DBG("Max buffers used: " << maxBuffersUsed);
|
||||||
|
DBG("Max index buffers used: " << maxIndexBuffersUsed);
|
||||||
|
DBG("Max stereo buffers used: " << maxStereoBuffersUsed);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
private:
|
||||||
|
void _setBufferSize(unsigned bufferSize)
|
||||||
|
{
|
||||||
|
for (auto& buffer : monoBuffers) {
|
||||||
|
buffer.resize(bufferSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto& buffer : indexBuffers) {
|
||||||
|
buffer.resize(bufferSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto& buffer : stereoBuffers) {
|
||||||
|
buffer.resize(bufferSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::c_fill(monoAvailable, 1);
|
||||||
|
absl::c_fill(stereoAvailable, 1);
|
||||||
|
absl::c_fill(indexAvailable, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::array<sfz::Buffer<float>, config::bufferPoolSize> monoBuffers;
|
||||||
|
std::vector<int> monoAvailable;
|
||||||
|
std::array<sfz::Buffer<int>, config::bufferPoolSize> indexBuffers;
|
||||||
|
std::vector<int> indexAvailable;
|
||||||
|
std::array<sfz::AudioBuffer<float>, config::stereoBufferPoolSize> stereoBuffers;
|
||||||
|
std::vector<int> stereoAvailable;
|
||||||
|
#ifndef NDEBUG
|
||||||
|
mutable int maxBuffersUsed { 0 };
|
||||||
|
mutable int maxIndexBuffersUsed { 0 };
|
||||||
|
mutable int maxStereoBuffersUsed { 0 };
|
||||||
|
#endif
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
@ -28,6 +28,9 @@ namespace config {
|
||||||
constexpr float defaultSampleRate { 48000 };
|
constexpr float defaultSampleRate { 48000 };
|
||||||
constexpr int defaultSamplesPerBlock { 1024 };
|
constexpr int defaultSamplesPerBlock { 1024 };
|
||||||
constexpr int maxBlockSize { 8192 };
|
constexpr int maxBlockSize { 8192 };
|
||||||
|
constexpr int bufferPoolSize { 4 };
|
||||||
|
constexpr int stereoBufferPoolSize { 4 };
|
||||||
|
constexpr int indexBufferPoolSize { 2 };
|
||||||
constexpr int preloadSize { 8192 };
|
constexpr int preloadSize { 8192 };
|
||||||
constexpr int loggerQueueSize { 256 };
|
constexpr int loggerQueueSize { 256 };
|
||||||
constexpr int voiceLoggerQueueSize { 256 };
|
constexpr int voiceLoggerQueueSize { 256 };
|
||||||
|
|
@ -35,7 +38,7 @@ namespace config {
|
||||||
constexpr size_t numChannels { 2 };
|
constexpr size_t numChannels { 2 };
|
||||||
constexpr int numBackgroundThreads { 4 };
|
constexpr int numBackgroundThreads { 4 };
|
||||||
constexpr int numVoices { 64 };
|
constexpr int numVoices { 64 };
|
||||||
constexpr int maxVoices { 256 };
|
constexpr unsigned maxVoices { 256 };
|
||||||
constexpr int maxFilePromises { maxVoices * 2 };
|
constexpr int maxFilePromises { maxVoices * 2 };
|
||||||
constexpr int sustainCC { 64 };
|
constexpr int sustainCC { 64 };
|
||||||
constexpr int allSoundOffCC { 120 };
|
constexpr int allSoundOffCC { 120 };
|
||||||
|
|
|
||||||
|
|
@ -144,7 +144,7 @@ void Curve::lerpFill(const bool fillStatus[NumValues])
|
||||||
const auto length = right - left;
|
const auto length = right - left;
|
||||||
if (length > 1) {
|
if (length > 1) {
|
||||||
const float mu = (_points[right] - _points[left]) / length;
|
const float mu = (_points[right] - _points[left]) / length;
|
||||||
linearRamp<float>(pointSpan.subspan(left + 1, length - 1), _points[left], mu);
|
linearRamp<float>(pointSpan.subspan(left, length), _points[left], mu);
|
||||||
}
|
}
|
||||||
left = right++;
|
left = right++;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,7 @@ enum class SfzLoopMode { no_loop, one_shot, loop_continuous, loop_sustain };
|
||||||
enum class SfzOffMode { fast, normal };
|
enum class SfzOffMode { fast, normal };
|
||||||
enum class SfzVelocityOverride { current, previous };
|
enum class SfzVelocityOverride { current, previous };
|
||||||
enum class SfzCrossfadeCurve { gain, power };
|
enum class SfzCrossfadeCurve { gain, power };
|
||||||
|
enum class SfzSelfMask { mask, dontMask };
|
||||||
|
|
||||||
namespace sfz
|
namespace sfz
|
||||||
{
|
{
|
||||||
|
|
@ -53,9 +54,11 @@ namespace Default
|
||||||
constexpr SfzLoopMode loopMode { SfzLoopMode::no_loop };
|
constexpr SfzLoopMode loopMode { SfzLoopMode::no_loop };
|
||||||
constexpr Range<uint32_t> loopRange { 0, std::numeric_limits<uint32_t>::max() };
|
constexpr Range<uint32_t> loopRange { 0, std::numeric_limits<uint32_t>::max() };
|
||||||
|
|
||||||
// Global ranges
|
// common defaults
|
||||||
constexpr Range<uint8_t> midi7Range { 0, 127 };
|
constexpr Range<uint8_t> midi7Range { 0, 127 };
|
||||||
constexpr Range<float> normalizedRange { 0.0f, 1.0f };
|
constexpr Range<float> normalizedRange { 0.0f, 1.0f };
|
||||||
|
constexpr Range<float> symmetricNormalizedRange { -1.0, 1.0 };
|
||||||
|
constexpr float zeroModifier { 0.0f };
|
||||||
|
|
||||||
// Wavetable oscillator
|
// Wavetable oscillator
|
||||||
constexpr float oscillatorPhase { 0.0 };
|
constexpr float oscillatorPhase { 0.0 };
|
||||||
|
|
@ -65,18 +68,21 @@ namespace Default
|
||||||
constexpr uint32_t group { 0 };
|
constexpr uint32_t group { 0 };
|
||||||
constexpr Range<uint32_t> groupRange { 0, std::numeric_limits<uint32_t>::max() };
|
constexpr Range<uint32_t> groupRange { 0, std::numeric_limits<uint32_t>::max() };
|
||||||
constexpr SfzOffMode offMode { SfzOffMode::fast };
|
constexpr SfzOffMode offMode { SfzOffMode::fast };
|
||||||
|
constexpr Range<uint32_t> polyphonyRange { 0, config::maxVoices };
|
||||||
|
constexpr SfzSelfMask selfMask { SfzSelfMask::mask };
|
||||||
|
|
||||||
// Region logic: key mapping
|
// Region logic: key mapping
|
||||||
constexpr Range<uint8_t> keyRange { 0, 127 };
|
constexpr Range<uint8_t> keyRange { 0, 127 };
|
||||||
constexpr auto velocityRange = normalizedRange;
|
constexpr auto velocityRange = normalizedRange;
|
||||||
|
|
||||||
// Region logic: MIDI conditions
|
// Region logic: MIDI conditions
|
||||||
constexpr Range<uint8_t> channelRange { 1, 16 };
|
constexpr Range<uint8_t> channelRange { 1, 16 };
|
||||||
constexpr Range<uint8_t> midiChannelRange { 0, 15 };
|
constexpr Range<uint8_t> midiChannelRange { 0, 15 };
|
||||||
constexpr Range<uint16_t> ccNumberRange { 0, config::numCCs };
|
constexpr Range<uint16_t> ccNumberRange { 0, config::numCCs };
|
||||||
constexpr auto ccValueRange = normalizedRange;
|
constexpr auto ccValueRange = normalizedRange;
|
||||||
constexpr Range<int> bendRange { -8192, 8192 };
|
constexpr Range<int> bendRange = { -8192, 8192 };
|
||||||
constexpr int bend { 0 };
|
constexpr Range<float> bendValueRange = symmetricNormalizedRange;
|
||||||
|
constexpr int bend { 0 };
|
||||||
constexpr SfzVelocityOverride velocityOverride { SfzVelocityOverride::current };
|
constexpr SfzVelocityOverride velocityOverride { SfzVelocityOverride::current };
|
||||||
|
|
||||||
// Region logic: internal conditions
|
// Region logic: internal conditions
|
||||||
|
|
@ -91,9 +97,9 @@ namespace Default
|
||||||
|
|
||||||
// Region logic: Triggers
|
// Region logic: Triggers
|
||||||
constexpr SfzTrigger trigger { SfzTrigger::attack };
|
constexpr SfzTrigger trigger { SfzTrigger::attack };
|
||||||
constexpr Range<float> ccTriggerValueRange = normalizedRange;
|
constexpr Range<float> ccTriggerValueRange = normalizedRange;
|
||||||
|
|
||||||
// Performance parameters: amplifier
|
// Performance parameters: amplifier
|
||||||
constexpr float globalVolume { -7.35f };
|
constexpr float globalVolume { -7.35f };
|
||||||
constexpr float volume { 0.0f };
|
constexpr float volume { 0.0f };
|
||||||
constexpr Range<float> volumeRange { -144.0, 6.0 };
|
constexpr Range<float> volumeRange { -144.0, 6.0 };
|
||||||
|
|
@ -103,7 +109,6 @@ namespace Default
|
||||||
constexpr float pan { 0.0 };
|
constexpr float pan { 0.0 };
|
||||||
constexpr Range<float> panRange { -100.0, 100.0 };
|
constexpr Range<float> panRange { -100.0, 100.0 };
|
||||||
constexpr Range<float> panCCRange { -200.0, 200.0 };
|
constexpr Range<float> panCCRange { -200.0, 200.0 };
|
||||||
constexpr Range<float> symmetricNormalizedRange { -1.0, 1.0 };
|
|
||||||
constexpr float position { 0.0 };
|
constexpr float position { 0.0 };
|
||||||
constexpr Range<float> positionRange { -100.0, 100.0 };
|
constexpr Range<float> positionRange { -100.0, 100.0 };
|
||||||
constexpr Range<float> positionCCRange { -200.0, 200.0 };
|
constexpr Range<float> positionCCRange { -200.0, 200.0 };
|
||||||
|
|
@ -120,11 +125,11 @@ namespace Default
|
||||||
constexpr Range<float> ampRandomRange { 0.0, 24.0 };
|
constexpr Range<float> ampRandomRange { 0.0, 24.0 };
|
||||||
constexpr Range<uint8_t> crossfadeKeyInRange { 0, 0 };
|
constexpr Range<uint8_t> crossfadeKeyInRange { 0, 0 };
|
||||||
constexpr Range<uint8_t> crossfadeKeyOutRange { 127, 127 };
|
constexpr Range<uint8_t> crossfadeKeyOutRange { 127, 127 };
|
||||||
constexpr Range<float> crossfadeVelInRange { 0.0f, 0.0f };
|
constexpr Range<float> crossfadeVelInRange { 0.0f, 0.0f };
|
||||||
constexpr Range<float> crossfadeVelOutRange { 1.0f, 1.0f };
|
constexpr Range<float> crossfadeVelOutRange { 1.0f, 1.0f };
|
||||||
constexpr Range<float> crossfadeCCInRange { 0.0f, 0.0f };
|
constexpr Range<float> crossfadeCCInRange { 0.0f, 0.0f };
|
||||||
constexpr Range<float> crossfadeCCOutRange { 1.0f, 1.0f };
|
constexpr Range<float> crossfadeCCOutRange { 1.0f, 1.0f };
|
||||||
constexpr SfzCrossfadeCurve crossfadeKeyCurve { SfzCrossfadeCurve::power };
|
constexpr SfzCrossfadeCurve crossfadeKeyCurve { SfzCrossfadeCurve::power };
|
||||||
constexpr SfzCrossfadeCurve crossfadeVelCurve { SfzCrossfadeCurve::power };
|
constexpr SfzCrossfadeCurve crossfadeVelCurve { SfzCrossfadeCurve::power };
|
||||||
constexpr SfzCrossfadeCurve crossfadeCCCurve { SfzCrossfadeCurve::power };
|
constexpr SfzCrossfadeCurve crossfadeCCCurve { SfzCrossfadeCurve::power };
|
||||||
constexpr float rtDecay { 0.0f };
|
constexpr float rtDecay { 0.0f };
|
||||||
|
|
@ -184,6 +189,7 @@ namespace Default
|
||||||
constexpr Range<int> transposeRange { -127, 127 };
|
constexpr Range<int> transposeRange { -127, 127 };
|
||||||
constexpr int tune { 0 };
|
constexpr int tune { 0 };
|
||||||
constexpr Range<int> tuneRange { -9600, 9600 }; // ±100 in SFZv1, more in ARIA
|
constexpr Range<int> tuneRange { -9600, 9600 }; // ±100 in SFZv1, more in ARIA
|
||||||
|
constexpr Range<int> tuneCCRange { -9600, 9600 };
|
||||||
constexpr Range<int> bendBoundRange { -9600, 9600 };
|
constexpr Range<int> bendBoundRange { -9600, 9600 };
|
||||||
constexpr Range<int> bendStepRange { 1, 1200 };
|
constexpr Range<int> bendStepRange { 1, 1200 };
|
||||||
constexpr int bendUp { 200 }; // No range here because the bounds can be inverted
|
constexpr int bendUp { 200 }; // No range here because the bounds can be inverted
|
||||||
|
|
|
||||||
|
|
@ -28,9 +28,20 @@ void sfz::EQHolder::setup(const EQDescription& description, unsigned numChannels
|
||||||
baseGain = description.gain + velocity * description.vel2gain;
|
baseGain = description.gain + velocity * description.vel2gain;
|
||||||
|
|
||||||
// Setup the modulated values
|
// Setup the modulated values
|
||||||
lastFrequency = midiState.modulate(baseFrequency, description.frequencyCC, Default::eqFrequencyRange);
|
lastFrequency = baseFrequency;
|
||||||
lastBandwidth = midiState.modulate(baseBandwidth, description.bandwidthCC, Default::eqBandwidthRange);
|
for (const auto& mod : description.frequencyCC)
|
||||||
lastGain = midiState.modulate(baseGain, description.gainCC, Default::eqGainRange);
|
lastFrequency += midiState.getCCValue(mod.cc) * mod.value;
|
||||||
|
lastFrequency = Default::eqFrequencyRange.clamp(lastFrequency);
|
||||||
|
|
||||||
|
lastBandwidth = baseBandwidth;
|
||||||
|
for (const auto& mod : description.bandwidthCC)
|
||||||
|
lastBandwidth += midiState.getCCValue(mod.cc) * mod.value;
|
||||||
|
lastBandwidth = Default::eqBandwidthRange.clamp(lastBandwidth);
|
||||||
|
|
||||||
|
lastGain = baseGain;
|
||||||
|
for (const auto& mod : description.gainCC)
|
||||||
|
lastGain += midiState.getCCValue(mod.cc) * mod.value;
|
||||||
|
lastGain = Default::filterGainRange.clamp(lastGain);
|
||||||
|
|
||||||
// Initialize the EQ
|
// Initialize the EQ
|
||||||
eq.prepare(lastFrequency, lastBandwidth, lastGain);
|
eq.prepare(lastFrequency, lastBandwidth, lastGain);
|
||||||
|
|
@ -50,9 +61,20 @@ void sfz::EQHolder::process(const float** inputs, float** outputs, unsigned numF
|
||||||
|
|
||||||
// TODO: Once the midistate envelopes are done, add modulation in there!
|
// TODO: Once the midistate envelopes are done, add modulation in there!
|
||||||
// For now we take the last value
|
// For now we take the last value
|
||||||
lastFrequency = midiState.modulate(baseFrequency, description->frequencyCC, Default::eqFrequencyRange);
|
lastFrequency = baseFrequency;
|
||||||
lastBandwidth = midiState.modulate(baseBandwidth, description->bandwidthCC, Default::eqBandwidthRange);
|
for (const auto& mod : description->frequencyCC)
|
||||||
lastGain = midiState.modulate(baseGain, description->gainCC, Default::eqGainRange);
|
lastFrequency += midiState.getCCValue(mod.cc) * mod.value;
|
||||||
|
lastFrequency = Default::eqFrequencyRange.clamp(lastFrequency);
|
||||||
|
|
||||||
|
lastBandwidth = baseBandwidth;
|
||||||
|
for (const auto& mod : description->bandwidthCC)
|
||||||
|
lastBandwidth += midiState.getCCValue(mod.cc) * mod.value;
|
||||||
|
lastBandwidth = Default::eqBandwidthRange.clamp(lastBandwidth);
|
||||||
|
|
||||||
|
lastGain = baseGain;
|
||||||
|
for (const auto& mod : description->gainCC)
|
||||||
|
lastGain += midiState.getCCValue(mod.cc) * mod.value;
|
||||||
|
lastGain = Default::filterGainRange.clamp(lastGain);
|
||||||
|
|
||||||
if (lastGain == 0.0f) {
|
if (lastGain == 0.0f) {
|
||||||
justCopy();
|
justCopy();
|
||||||
|
|
|
||||||
|
|
@ -1,277 +0,0 @@
|
||||||
// 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
|
|
||||||
|
|
||||||
#include "EventEnvelopes.h"
|
|
||||||
#include "SIMDHelpers.h"
|
|
||||||
#include "MathHelpers.h"
|
|
||||||
#include <absl/algorithm/container.h>
|
|
||||||
|
|
||||||
namespace sfz {
|
|
||||||
|
|
||||||
template <class Type>
|
|
||||||
EventEnvelope<Type>::EventEnvelope()
|
|
||||||
{
|
|
||||||
setMaxCapacity(maxCapacity);
|
|
||||||
}
|
|
||||||
|
|
||||||
template <class Type>
|
|
||||||
EventEnvelope<Type>::EventEnvelope(int maxCapacity, std::function<Type(Type)> function)
|
|
||||||
{
|
|
||||||
setMaxCapacity(maxCapacity);
|
|
||||||
setFunction(function);
|
|
||||||
}
|
|
||||||
|
|
||||||
template <class Type>
|
|
||||||
void EventEnvelope<Type>::setMaxCapacity(int maxCapacity)
|
|
||||||
{
|
|
||||||
events.reserve(maxCapacity);
|
|
||||||
this->maxCapacity = maxCapacity;
|
|
||||||
}
|
|
||||||
|
|
||||||
template <class Type>
|
|
||||||
void EventEnvelope<Type>::setFunction(std::function<Type(Type)> function)
|
|
||||||
{
|
|
||||||
this->function = function;
|
|
||||||
}
|
|
||||||
|
|
||||||
template <class Type>
|
|
||||||
void EventEnvelope<Type>::registerEvent(int timestamp, Type inputValue)
|
|
||||||
{
|
|
||||||
if (resetEvents)
|
|
||||||
clear();
|
|
||||||
|
|
||||||
if (static_cast<int>(events.size()) < maxCapacity)
|
|
||||||
events.emplace_back(timestamp, function(inputValue));
|
|
||||||
}
|
|
||||||
|
|
||||||
template <class Type>
|
|
||||||
void EventEnvelope<Type>::prepareEvents(int blockLength)
|
|
||||||
{
|
|
||||||
if (resetEvents)
|
|
||||||
clear();
|
|
||||||
|
|
||||||
absl::c_stable_sort(events, [](const std::pair<int, Type>& lhs, const std::pair<int, Type>& rhs) {
|
|
||||||
return lhs.first < rhs.first;
|
|
||||||
});
|
|
||||||
|
|
||||||
auto eventIt = events.begin();
|
|
||||||
while (eventIt < events.end()) {
|
|
||||||
if (eventIt->first >= blockLength) {
|
|
||||||
eventIt->first = blockLength - 1;
|
|
||||||
eventIt->second = events.back().second;
|
|
||||||
++eventIt;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto nextEventIt = std::next(eventIt);
|
|
||||||
while (nextEventIt < events.end() && eventIt->first == nextEventIt->first ) {
|
|
||||||
eventIt->second = nextEventIt->second;
|
|
||||||
++nextEventIt;
|
|
||||||
}
|
|
||||||
++eventIt;
|
|
||||||
}
|
|
||||||
events.resize(std::distance(events.begin(), eventIt));
|
|
||||||
|
|
||||||
resetEvents = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
template <class Type>
|
|
||||||
void EventEnvelope<Type>::clear()
|
|
||||||
{
|
|
||||||
events.clear();
|
|
||||||
resetEvents = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
template <class Type>
|
|
||||||
void EventEnvelope<Type>::reset(Type value)
|
|
||||||
{
|
|
||||||
clear();
|
|
||||||
currentValue = function(value);
|
|
||||||
resetEvents = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
template <class Type>
|
|
||||||
void EventEnvelope<Type>::getBlock(absl::Span<Type> output)
|
|
||||||
{
|
|
||||||
prepareEvents(output.size());
|
|
||||||
}
|
|
||||||
|
|
||||||
template <class Type>
|
|
||||||
void EventEnvelope<Type>::getQuantizedBlock(absl::Span<Type> output, Type)
|
|
||||||
{
|
|
||||||
prepareEvents(output.size());
|
|
||||||
}
|
|
||||||
|
|
||||||
template <class Type>
|
|
||||||
void LinearEnvelope<Type>::getBlock(absl::Span<Type> output)
|
|
||||||
{
|
|
||||||
EventEnvelope<Type>::getBlock(output);
|
|
||||||
auto& events = EventEnvelope<Type>::events;
|
|
||||||
auto& currentValue = EventEnvelope<Type>::currentValue;
|
|
||||||
|
|
||||||
int index { 0 };
|
|
||||||
for (auto& event : events) {
|
|
||||||
const auto length = min(event.first, static_cast<int>(output.size())) - index;
|
|
||||||
if (length == 0) {
|
|
||||||
currentValue = event.second;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto step = (event.second - currentValue) / length;
|
|
||||||
currentValue = linearRamp<Type>(output.subspan(index, length), currentValue, step);
|
|
||||||
index += length;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (index < static_cast<int>(output.size()))
|
|
||||||
fill<Type>(output.subspan(index), currentValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
template <class Type>
|
|
||||||
void LinearEnvelope<Type>::getQuantizedBlock(absl::Span<Type> output, Type quantizationStep)
|
|
||||||
{
|
|
||||||
EventEnvelope<Type>::getQuantizedBlock(output, quantizationStep);
|
|
||||||
auto& events = EventEnvelope<Type>::events;
|
|
||||||
auto& currentValue = EventEnvelope<Type>::currentValue;
|
|
||||||
|
|
||||||
ASSERT(quantizationStep != 0.0);
|
|
||||||
int index { 0 };
|
|
||||||
|
|
||||||
auto quantize = [quantizationStep](Type value) -> Type {
|
|
||||||
return std::round(value / quantizationStep) * quantizationStep;
|
|
||||||
};
|
|
||||||
|
|
||||||
const auto outputSize = static_cast<int>(output.size());
|
|
||||||
for (auto& event : events) {
|
|
||||||
const auto newValue = quantize(event.second);
|
|
||||||
|
|
||||||
if (event.first > outputSize) {
|
|
||||||
fill<Type>(output.subspan(index), currentValue);
|
|
||||||
currentValue = newValue;
|
|
||||||
index = outputSize;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto length = event.first - index - 1;
|
|
||||||
if (length <= 0) {
|
|
||||||
currentValue = newValue;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto difference = std::abs(newValue - currentValue);
|
|
||||||
if (difference < quantizationStep) {
|
|
||||||
fill<Type>(output.subspan(index, length), currentValue);
|
|
||||||
currentValue = newValue;
|
|
||||||
index += length;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto numSteps = static_cast<int>(difference / quantizationStep);
|
|
||||||
const auto stepLength = static_cast<int>(length / numSteps);
|
|
||||||
for (int i = 0; i < numSteps; ++i) {
|
|
||||||
fill<Type>(output.subspan(index, stepLength), currentValue);
|
|
||||||
const auto delta = quantizationStep + currentValue - quantize(currentValue);
|
|
||||||
currentValue += currentValue <= newValue ? delta : -delta;
|
|
||||||
index += stepLength;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (index < outputSize)
|
|
||||||
fill<Type>(output.subspan(index), currentValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
template <class Type>
|
|
||||||
MultiplicativeEnvelope<Type>::MultiplicativeEnvelope()
|
|
||||||
{
|
|
||||||
EventEnvelope<Type>::reset(1.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
template <class Type>
|
|
||||||
void MultiplicativeEnvelope<Type>::getBlock(absl::Span<Type> output)
|
|
||||||
{
|
|
||||||
EventEnvelope<Type>::getBlock(output);
|
|
||||||
auto& events = EventEnvelope<Type>::events;
|
|
||||||
auto& currentValue = EventEnvelope<Type>::currentValue;
|
|
||||||
|
|
||||||
int index { 0 };
|
|
||||||
for (auto& event : events) {
|
|
||||||
const auto length = min(event.first, static_cast<int>(output.size())) - index;
|
|
||||||
if (length == 0) {
|
|
||||||
currentValue = event.second;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto step = std::exp((std::log(event.second) - std::log(currentValue)) / length);
|
|
||||||
multiplicativeRamp<Type>(output.subspan(index, length), currentValue, step);
|
|
||||||
currentValue = event.second;
|
|
||||||
index += length;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (index < static_cast<int>(output.size()))
|
|
||||||
fill<Type>(output.subspan(index), currentValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
template <class Type>
|
|
||||||
void MultiplicativeEnvelope<Type>::getQuantizedBlock(absl::Span<Type> output, Type quantizationStep)
|
|
||||||
{
|
|
||||||
EventEnvelope<Type>::getQuantizedBlock(output, quantizationStep);
|
|
||||||
auto& events = EventEnvelope<Type>::events;
|
|
||||||
auto& currentValue = EventEnvelope<Type>::currentValue;
|
|
||||||
|
|
||||||
ASSERT(quantizationStep != 0.0);
|
|
||||||
int index { 0 };
|
|
||||||
|
|
||||||
const auto logStep = std::log(quantizationStep);
|
|
||||||
// If we assume that a = b.q^r for b in (1, q) then
|
|
||||||
// log a log b
|
|
||||||
// ----- = ----- + r
|
|
||||||
// log q log q
|
|
||||||
// and log(b)\log(q) is between 0 and 1.
|
|
||||||
auto quantize = [logStep](Type value) -> Type {
|
|
||||||
return std::exp(logStep * std::round(std::log(value)/logStep));
|
|
||||||
};
|
|
||||||
|
|
||||||
const auto outputSize = static_cast<int>(output.size());
|
|
||||||
for (auto& event : events) {
|
|
||||||
const auto newValue = quantize(event.second);
|
|
||||||
|
|
||||||
if (event.first > outputSize) {
|
|
||||||
fill<Type>(output.subspan(index), currentValue);
|
|
||||||
currentValue = newValue;
|
|
||||||
index = outputSize;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto length = event.first - index - 1;
|
|
||||||
if (length <= 0) {
|
|
||||||
currentValue = newValue;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto difference = newValue > currentValue ? newValue / currentValue : currentValue / newValue;
|
|
||||||
if (difference < quantizationStep) {
|
|
||||||
fill<Type>(output.subspan(index, length), currentValue);
|
|
||||||
currentValue = newValue;
|
|
||||||
index += length;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto numSteps = static_cast<int>(std::log(difference) / logStep);
|
|
||||||
const auto stepLength = static_cast<int>(length / numSteps);
|
|
||||||
for (int i = 0; i < numSteps; ++i) {
|
|
||||||
fill<Type>(output.subspan(index, stepLength), currentValue);
|
|
||||||
const auto delta = newValue > currentValue ?
|
|
||||||
quantize(currentValue) / currentValue * quantizationStep :
|
|
||||||
quantize(currentValue) / currentValue / quantizationStep ;
|
|
||||||
currentValue *= delta;
|
|
||||||
index += stepLength;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (index < outputSize)
|
|
||||||
fill<Type>(output.subspan(index), currentValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,129 +0,0 @@
|
||||||
// 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
|
|
||||||
#include "Config.h"
|
|
||||||
#include "LeakDetector.h"
|
|
||||||
#include <absl/types/span.h>
|
|
||||||
#include <functional>
|
|
||||||
#include <type_traits>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
namespace sfz {
|
|
||||||
/**
|
|
||||||
* @brief Describes a simple envelope that can be polled in a blockwise
|
|
||||||
* manner. It works by storing "events" in the immediate future and linearly
|
|
||||||
* interpolating between these events. This envelope can also transform its
|
|
||||||
* incoming target points through a lambda, although the lambda function is applied before the interpolation.
|
|
||||||
*
|
|
||||||
* The way to use this class is by repeatedly calling `registerEvent` and then
|
|
||||||
* `getBlock` to get a block of interpolated values in between the specified events.
|
|
||||||
* You should only register events whose timestamps are below the size of the block
|
|
||||||
* you will require when calling `getBlock`.
|
|
||||||
*
|
|
||||||
* @tparam Type
|
|
||||||
*/
|
|
||||||
template <class Type>
|
|
||||||
class EventEnvelope {
|
|
||||||
public:
|
|
||||||
/**
|
|
||||||
* @brief Construct a new linear envelope with a default memory size for
|
|
||||||
* incoming events.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
EventEnvelope();
|
|
||||||
/**
|
|
||||||
* @brief Construct a new linear envelope with a specific memory size for
|
|
||||||
* incoming events as well as a transformation function for incoming events.
|
|
||||||
*
|
|
||||||
* @param maxCapacity
|
|
||||||
* @param function
|
|
||||||
*/
|
|
||||||
EventEnvelope(int maxCapacity, std::function<Type(Type)> function);
|
|
||||||
/**
|
|
||||||
* @brief Set the maximum memory size for incoming events
|
|
||||||
*
|
|
||||||
* @param maxCapacity
|
|
||||||
*/
|
|
||||||
void setMaxCapacity(int maxCapacity);
|
|
||||||
/**
|
|
||||||
* @brief Set the transformation function for the value of incoming events.
|
|
||||||
*
|
|
||||||
* @param function
|
|
||||||
*/
|
|
||||||
void setFunction(std::function<Type(Type)> function);
|
|
||||||
/**
|
|
||||||
* @brief Register a new event. Note that the timestamp of the new value should
|
|
||||||
* be less than the future call to `getBlock` otherwise the event will be ignored.
|
|
||||||
*
|
|
||||||
* @param timestamp
|
|
||||||
* @param inputValue
|
|
||||||
*/
|
|
||||||
void registerEvent(int timestamp, Type inputValue);
|
|
||||||
/**
|
|
||||||
* @brief Clear all events in memory
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
void clear();
|
|
||||||
/**
|
|
||||||
* @brief Reset the envelope and clears the memory.
|
|
||||||
*
|
|
||||||
* @param value
|
|
||||||
*/
|
|
||||||
void reset(Type value = 0.0);
|
|
||||||
/**
|
|
||||||
* @brief Get a block of interpolated values between events previously registered
|
|
||||||
* using `registerEvent`.
|
|
||||||
*
|
|
||||||
* @param output
|
|
||||||
*/
|
|
||||||
virtual void getBlock(absl::Span<Type> output);
|
|
||||||
/**
|
|
||||||
* @brief Get a block of interpolated values with a forced quantization. The
|
|
||||||
* values within the block will vary in quantization steps.
|
|
||||||
*
|
|
||||||
* @param output
|
|
||||||
* @param quantizationStep
|
|
||||||
*/
|
|
||||||
virtual void getQuantizedBlock(absl::Span<Type> output, Type quantizationStep);
|
|
||||||
protected:
|
|
||||||
std::vector<std::pair<int, Type>> events;
|
|
||||||
Type currentValue { 0.0 };
|
|
||||||
private:
|
|
||||||
static_assert(std::is_arithmetic<Type>::value, "Type should be arithmetic");
|
|
||||||
std::function<Type(Type)> function { [](Type input) -> Type { return input; } };
|
|
||||||
int maxCapacity { config::defaultSamplesPerBlock };
|
|
||||||
void prepareEvents(int blockLength);
|
|
||||||
bool resetEvents { false };
|
|
||||||
LEAK_DETECTOR(EventEnvelope);
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Describes a simple linear envelope.
|
|
||||||
*
|
|
||||||
* @tparam Type
|
|
||||||
*/
|
|
||||||
template <class Type>
|
|
||||||
class LinearEnvelope: public EventEnvelope<Type> {
|
|
||||||
public:
|
|
||||||
void getBlock(absl::Span<Type> output) final;
|
|
||||||
void getQuantizedBlock(absl::Span<Type> output, Type quantizationStep) final;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Describes a simple multiplicative envelope.
|
|
||||||
*
|
|
||||||
* @tparam Type
|
|
||||||
*/
|
|
||||||
template <class Type>
|
|
||||||
class MultiplicativeEnvelope: public EventEnvelope<Type> {
|
|
||||||
public:
|
|
||||||
MultiplicativeEnvelope();
|
|
||||||
void getBlock(absl::Span<Type> output) final;
|
|
||||||
void getQuantizedBlock(absl::Span<Type> output, Type quantizationStep) final;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
@ -273,7 +273,7 @@ sfz::FilePromisePtr sfz::FilePool::getFilePromise(const std::string& filename) n
|
||||||
auto promise = emptyPromises.back();
|
auto promise = emptyPromises.back();
|
||||||
promise->filename = preloaded->first;
|
promise->filename = preloaded->first;
|
||||||
promise->preloadedData = preloaded->second.preloadedData;
|
promise->preloadedData = preloaded->second.preloadedData;
|
||||||
promise->sampleRate = preloaded->second.information.sampleRate;
|
promise->sampleRate = static_cast<float>(preloaded->second.information.sampleRate);
|
||||||
promise->oversamplingFactor = oversamplingFactor;
|
promise->oversamplingFactor = oversamplingFactor;
|
||||||
promise->creationTime = std::chrono::high_resolution_clock::now();
|
promise->creationTime = std::chrono::high_resolution_clock::now();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -40,9 +40,20 @@ void sfz::FilterHolder::setup(const FilterDescription& description, unsigned num
|
||||||
baseResonance = description.resonance;
|
baseResonance = description.resonance;
|
||||||
|
|
||||||
// Setup the modulated values
|
// Setup the modulated values
|
||||||
lastCutoff = midiState.modulate<float, int>(baseCutoff, description.cutoffCC, Default::filterCutoffRange, multiplyByCents);
|
lastCutoff = baseCutoff;
|
||||||
lastResonance = midiState.modulate(baseResonance, description.resonanceCC, Default::filterResonanceRange);
|
for (const auto& mod : description.cutoffCC)
|
||||||
lastGain = midiState.modulate(baseGain, description.gainCC, Default::filterGainRange);
|
lastCutoff *= centsFactor(midiState.getCCValue(mod.cc) * mod.value);
|
||||||
|
lastCutoff = Default::filterCutoffRange.clamp(lastCutoff);
|
||||||
|
|
||||||
|
lastResonance = baseResonance;
|
||||||
|
for (const auto& mod : description.resonanceCC)
|
||||||
|
lastResonance += midiState.getCCValue(mod.cc) * mod.value;
|
||||||
|
lastResonance = Default::filterResonanceRange.clamp(lastResonance);
|
||||||
|
|
||||||
|
lastGain = baseGain;
|
||||||
|
for (const auto& mod : description.gainCC)
|
||||||
|
lastGain += midiState.getCCValue(mod.cc) * mod.value;
|
||||||
|
lastGain = Default::filterGainRange.clamp(lastGain);
|
||||||
|
|
||||||
// Initialize the filter
|
// Initialize the filter
|
||||||
filter.prepare(lastCutoff, lastResonance, lastGain);
|
filter.prepare(lastCutoff, lastResonance, lastGain);
|
||||||
|
|
@ -59,9 +70,20 @@ void sfz::FilterHolder::process(const float** inputs, float** outputs, unsigned
|
||||||
// TODO: Once the midistate envelopes are done, add modulation in there!
|
// TODO: Once the midistate envelopes are done, add modulation in there!
|
||||||
// For now we take the last value
|
// For now we take the last value
|
||||||
// TODO: the template deduction could be automatic here?
|
// TODO: the template deduction could be automatic here?
|
||||||
lastCutoff = midiState.modulate<float, int>(baseCutoff, description->cutoffCC, Default::filterCutoffRange, multiplyByCents);
|
lastCutoff = baseCutoff;
|
||||||
lastResonance = midiState.modulate(baseResonance, description->resonanceCC, Default::filterResonanceRange);
|
for (const auto& mod : description->cutoffCC)
|
||||||
baseGain = midiState.modulate(baseGain, description->gainCC, Default::filterGainRange);
|
lastCutoff *= centsFactor(midiState.getCCValue(mod.cc) * mod.value);
|
||||||
|
lastCutoff = Default::filterCutoffRange.clamp(lastCutoff);
|
||||||
|
|
||||||
|
lastResonance = baseResonance;
|
||||||
|
for (const auto& mod : description->resonanceCC)
|
||||||
|
lastResonance += midiState.getCCValue(mod.cc) * mod.value;
|
||||||
|
lastResonance = Default::filterResonanceRange.clamp(lastResonance);
|
||||||
|
|
||||||
|
lastGain = baseGain;
|
||||||
|
for (const auto& mod : description->gainCC)
|
||||||
|
lastGain += midiState.getCCValue(mod.cc) * mod.value;
|
||||||
|
lastGain = Default::filterGainRange.clamp(lastGain);
|
||||||
|
|
||||||
filter.process(inputs, outputs, lastCutoff, lastResonance, lastGain, numFrames);
|
filter.process(inputs, outputs, lastCutoff, lastResonance, lastGain, numFrames);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,29 +4,13 @@
|
||||||
// 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
|
||||||
|
|
||||||
/**
|
|
||||||
* @file FloatEnvelopes.cpp
|
|
||||||
* @author Paul Ferrand (paul@ferrand.cc)
|
|
||||||
* @brief Force the instantiations of the ADSR and linear envelopes for floats
|
|
||||||
* @version 0.1
|
|
||||||
* @date 2019-11-30
|
|
||||||
*
|
|
||||||
* @copyright Copyright (c) 2019 Paul Ferrand
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "EventEnvelopes.h"
|
|
||||||
#include "ADSREnvelope.h"
|
#include "ADSREnvelope.h"
|
||||||
|
|
||||||
// Include the generic implementations
|
// Include the generic implementations
|
||||||
#include "EventEnvelopes.cpp"
|
|
||||||
#include "ADSREnvelope.cpp"
|
#include "ADSREnvelope.cpp"
|
||||||
|
|
||||||
// And explicitely instantiate the float version
|
// And explicitely instantiate the float version
|
||||||
namespace sfz
|
namespace sfz
|
||||||
{
|
{
|
||||||
template class EventEnvelope<float>;
|
|
||||||
template class MultiplicativeEnvelope<float>;
|
|
||||||
template class LinearEnvelope<float>;
|
|
||||||
template class ADSREnvelope<float>;
|
template class ADSREnvelope<float>;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@
|
||||||
|
|
||||||
sfz::MidiState::MidiState()
|
sfz::MidiState::MidiState()
|
||||||
{
|
{
|
||||||
reset(0);
|
reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
void sfz::MidiState::noteOnEvent(int delay, int noteNumber, float velocity) noexcept
|
void sfz::MidiState::noteOnEvent(int delay, int noteNumber, float velocity) noexcept
|
||||||
|
|
@ -20,7 +20,7 @@ void sfz::MidiState::noteOnEvent(int delay, int noteNumber, float velocity) noex
|
||||||
|
|
||||||
if (noteNumber >= 0 && noteNumber < 128) {
|
if (noteNumber >= 0 && noteNumber < 128) {
|
||||||
lastNoteVelocities[noteNumber] = velocity;
|
lastNoteVelocities[noteNumber] = velocity;
|
||||||
noteOnTimes[noteNumber] = std::chrono::steady_clock::now();
|
noteOnTimes[noteNumber] = internalClock + static_cast<unsigned>(delay);
|
||||||
activeNotes++;
|
activeNotes++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -28,27 +28,61 @@ void sfz::MidiState::noteOnEvent(int delay, int noteNumber, float velocity) noex
|
||||||
|
|
||||||
void sfz::MidiState::noteOffEvent(int delay, int noteNumber, float velocity) noexcept
|
void sfz::MidiState::noteOffEvent(int delay, int noteNumber, float velocity) noexcept
|
||||||
{
|
{
|
||||||
|
ASSERT(delay >= 0);
|
||||||
ASSERT(noteNumber >= 0 && noteNumber <= 127);
|
ASSERT(noteNumber >= 0 && noteNumber <= 127);
|
||||||
ASSERT(velocity >= 0.0 && velocity <= 1.0);
|
ASSERT(velocity >= 0.0 && velocity <= 1.0);
|
||||||
UNUSED(velocity);
|
UNUSED(velocity);
|
||||||
if (noteNumber >= 0 && noteNumber < 128) {
|
if (noteNumber >= 0 && noteNumber < 128) {
|
||||||
|
noteOffTimes[noteNumber] = internalClock + static_cast<unsigned>(delay);
|
||||||
if (activeNotes > 0)
|
if (activeNotes > 0)
|
||||||
activeNotes--;
|
activeNotes--;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
float sfz::MidiState::getNoteDuration(int noteNumber) const
|
void sfz::MidiState::setSampleRate(float sampleRate) noexcept
|
||||||
{
|
{
|
||||||
ASSERT(noteNumber >= 0 && noteNumber <= 127);
|
this->sampleRate = sampleRate;
|
||||||
|
internalClock = 0;
|
||||||
|
absl::c_fill(noteOnTimes, 0);
|
||||||
|
absl::c_fill(noteOffTimes, 0);
|
||||||
|
}
|
||||||
|
|
||||||
if (noteNumber >= 0 && noteNumber < 128) {
|
void sfz::MidiState::advanceTime(int numSamples) noexcept
|
||||||
const auto noteOffTime = std::chrono::steady_clock::now();
|
{
|
||||||
const auto duration = std::chrono::duration_cast<std::chrono::duration<float>>(noteOffTime - noteOnTimes[noteNumber]);
|
internalClock += numSamples;
|
||||||
return duration.count();
|
for (auto& ccEvents : cc) {
|
||||||
|
ASSERT(!ccEvents.empty()); // CC event vectors should never be empty
|
||||||
|
ccEvents.front().value = ccEvents.back().value;
|
||||||
|
ccEvents.front().delay = 0;
|
||||||
|
ccEvents.resize(1);
|
||||||
}
|
}
|
||||||
|
ASSERT(!pitchEvents.empty());
|
||||||
|
pitchEvents.front().value = pitchEvents.back().value;
|
||||||
|
pitchEvents.front().delay = 0;
|
||||||
|
pitchEvents.resize(1);
|
||||||
|
}
|
||||||
|
|
||||||
return 0.0f;
|
void sfz::MidiState::setSamplesPerBlock(int samplesPerBlock) noexcept
|
||||||
|
{
|
||||||
|
this->samplesPerBlock = samplesPerBlock;
|
||||||
|
for (auto& ccEvents : cc) {
|
||||||
|
ccEvents.shrink_to_fit();
|
||||||
|
ccEvents.reserve(samplesPerBlock);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
float sfz::MidiState::getNoteDuration(int noteNumber, int delay) const
|
||||||
|
{
|
||||||
|
ASSERT(noteNumber >= 0 && noteNumber < 128);
|
||||||
|
if (noteNumber < 0 || noteNumber >= 128)
|
||||||
|
return 0.0f;
|
||||||
|
|
||||||
|
if (noteOnTimes[noteNumber] != 0 && noteOffTimes[noteNumber] != 0 && noteOnTimes[noteNumber] > noteOffTimes[noteNumber])
|
||||||
|
return 0.0f;
|
||||||
|
|
||||||
|
const unsigned timeInSamples = internalClock + static_cast<unsigned>(delay) - noteOnTimes[noteNumber];
|
||||||
|
return static_cast<float>(timeInSamples) / sampleRate;
|
||||||
}
|
}
|
||||||
|
|
||||||
float sfz::MidiState::getNoteVelocity(int noteNumber) const noexcept
|
float sfz::MidiState::getNoteVelocity(int noteNumber) const noexcept
|
||||||
|
|
@ -58,49 +92,76 @@ float sfz::MidiState::getNoteVelocity(int noteNumber) const noexcept
|
||||||
return lastNoteVelocities[noteNumber];
|
return lastNoteVelocities[noteNumber];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void sfz::MidiState::pitchBendEvent(int delay, float pitchBendValue) noexcept
|
||||||
void sfz::MidiState::pitchBendEvent(int delay, int pitchBendValue) noexcept
|
|
||||||
{
|
{
|
||||||
ASSERT(pitchBendValue >= -8192 && pitchBendValue <= 8192);
|
ASSERT(pitchBendValue >= -1.0f && pitchBendValue <= 1.0f);
|
||||||
|
|
||||||
pitchBend = pitchBendValue;
|
const auto insertionPoint = absl::c_upper_bound(pitchEvents, delay, MidiEventDelayComparator {});
|
||||||
|
if (insertionPoint == pitchEvents.end() || insertionPoint->delay != delay)
|
||||||
|
pitchEvents.insert(insertionPoint, { delay, pitchBendValue });
|
||||||
|
else
|
||||||
|
insertionPoint->value = pitchBendValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
int sfz::MidiState::getPitchBend() const noexcept
|
float sfz::MidiState::getPitchBend() const noexcept
|
||||||
{
|
{
|
||||||
return pitchBend;
|
ASSERT(pitchEvents.size() > 0);
|
||||||
|
return pitchEvents.back().value;
|
||||||
}
|
}
|
||||||
|
|
||||||
void sfz::MidiState::ccEvent(int delay, int ccNumber, float ccValue) noexcept
|
void sfz::MidiState::ccEvent(int delay, int ccNumber, float ccValue) noexcept
|
||||||
{
|
{
|
||||||
ASSERT(ccValue >= 0.0 && ccValue <= 1.0);
|
ASSERT(ccValue >= 0.0 && ccValue <= 1.0);
|
||||||
|
const auto insertionPoint = absl::c_upper_bound(cc[ccNumber], delay, MidiEventDelayComparator {});
|
||||||
cc[ccNumber] = ccValue;
|
if (insertionPoint == cc[ccNumber].end() || insertionPoint->delay != delay)
|
||||||
|
cc[ccNumber].insert(insertionPoint, { delay, ccValue });
|
||||||
|
else
|
||||||
|
insertionPoint->value = ccValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
float sfz::MidiState::getCCValue(int ccNumber) const noexcept
|
float sfz::MidiState::getCCValue(int ccNumber) const noexcept
|
||||||
{
|
{
|
||||||
ASSERT(ccNumber >= 0 && ccNumber < config::numCCs);
|
ASSERT(ccNumber >= 0 && ccNumber < config::numCCs);
|
||||||
|
|
||||||
return cc[ccNumber];
|
return cc[ccNumber].back().value;
|
||||||
}
|
}
|
||||||
|
|
||||||
void sfz::MidiState::reset(int delay) noexcept
|
void sfz::MidiState::reset() noexcept
|
||||||
{
|
{
|
||||||
for (auto& velocity: lastNoteVelocities)
|
for (auto& velocity: lastNoteVelocities)
|
||||||
velocity = 0;
|
velocity = 0;
|
||||||
|
|
||||||
for (auto& ccValue: cc)
|
for (auto& ccEvents : cc) {
|
||||||
ccValue = 0;
|
ccEvents.clear();
|
||||||
|
ccEvents.push_back({ 0, 0.0f });
|
||||||
|
}
|
||||||
|
|
||||||
|
pitchEvents.clear();
|
||||||
|
pitchEvents.push_back({ 0, 0.0f });
|
||||||
|
|
||||||
pitchBend = 0;
|
|
||||||
activeNotes = 0;
|
activeNotes = 0;
|
||||||
|
internalClock = 0;
|
||||||
|
absl::c_fill(noteOnTimes, 0);
|
||||||
|
absl::c_fill(noteOffTimes, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
void sfz::MidiState::resetAllControllers(int delay) noexcept
|
void sfz::MidiState::resetAllControllers(int delay) noexcept
|
||||||
{
|
{
|
||||||
for (unsigned idx = 0; idx < config::numCCs; idx++)
|
for (int ccIdx = 0; ccIdx < config::numCCs; ++ccIdx)
|
||||||
cc[idx] = 0;
|
ccEvent(delay, ccIdx, 0.0f);
|
||||||
|
|
||||||
pitchBend = 0;
|
pitchBendEvent(delay, 0.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
const sfz::EventVector& sfz::MidiState::getCCEvents(int ccIdx) const noexcept
|
||||||
|
{
|
||||||
|
if (ccIdx < 0 || ccIdx > config::numCCs)
|
||||||
|
return nullEvent;
|
||||||
|
|
||||||
|
return cc[ccIdx];
|
||||||
|
}
|
||||||
|
|
||||||
|
const sfz::EventVector& sfz::MidiState::getPitchEvents() const noexcept
|
||||||
|
{
|
||||||
|
return pitchEvents;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,6 @@
|
||||||
// If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz
|
// If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
#include <chrono>
|
|
||||||
#include <array>
|
#include <array>
|
||||||
#include "CCMap.h"
|
#include "CCMap.h"
|
||||||
#include "Range.h"
|
#include "Range.h"
|
||||||
|
|
@ -29,7 +28,7 @@ public:
|
||||||
* @param noteNumber
|
* @param noteNumber
|
||||||
* @param velocity
|
* @param velocity
|
||||||
*/
|
*/
|
||||||
void noteOnEvent(int delay, int noteNumber, float velocity) noexcept;
|
void noteOnEvent(int delay, int noteNumber, float velocity) noexcept;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Update the state after a note off event
|
* @brief Update the state after a note off event
|
||||||
|
|
@ -37,39 +36,55 @@ public:
|
||||||
* @param noteNumber
|
* @param noteNumber
|
||||||
* @param velocity
|
* @param velocity
|
||||||
*/
|
*/
|
||||||
void noteOffEvent(int delay, int noteNumber, float velocity) noexcept;
|
void noteOffEvent(int delay, int noteNumber, float velocity) noexcept;
|
||||||
|
|
||||||
int getActiveNotes() const noexcept { return activeNotes; }
|
int getActiveNotes() const noexcept { return activeNotes; }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Register a note off and get the note duration
|
* @brief Get the note duration since note on
|
||||||
*
|
*
|
||||||
* @param noteNumber
|
* @param noteNumber
|
||||||
|
* @param delay
|
||||||
* @return float
|
* @return float
|
||||||
*/
|
*/
|
||||||
float getNoteDuration(int noteNumber) const;
|
float getNoteDuration(int noteNumber, int delay = 0) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Set the maximum size of the blocks for the callback. The actual
|
||||||
|
* size can be lower in each callback but should not be larger
|
||||||
|
* than this value.
|
||||||
|
*
|
||||||
|
* @param samplesPerBlock
|
||||||
|
*/
|
||||||
|
void setSamplesPerBlock(int samplesPerBlock) noexcept;
|
||||||
|
/**
|
||||||
|
* @brief Set the sample rate. If you do not call it it is initialized
|
||||||
|
* to sfz::config::defaultSampleRate.
|
||||||
|
*
|
||||||
|
* @param sampleRate
|
||||||
|
*/
|
||||||
|
void setSampleRate(float sampleRate) noexcept;
|
||||||
/**
|
/**
|
||||||
* @brief Get the note on velocity for a given note
|
* @brief Get the note on velocity for a given note
|
||||||
*
|
*
|
||||||
* @param noteNumber
|
* @param noteNumber
|
||||||
* @return float
|
* @return float
|
||||||
*/
|
*/
|
||||||
float getNoteVelocity(int noteNumber) const noexcept;
|
float getNoteVelocity(int noteNumber) const noexcept;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Register a pitch bend event
|
* @brief Register a pitch bend event
|
||||||
*
|
*
|
||||||
* @param pitchBendValue
|
* @param pitchBendValue
|
||||||
*/
|
*/
|
||||||
void pitchBendEvent(int delay, int pitchBendValue) noexcept;
|
void pitchBendEvent(int delay, float pitchBendValue) noexcept;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Get the pitch bend status
|
* @brief Get the pitch bend status
|
||||||
|
|
||||||
* @return int
|
* @return int
|
||||||
*/
|
*/
|
||||||
int getPitchBend() const noexcept;
|
float getPitchBend() const noexcept;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Register a CC event
|
* @brief Register a CC event
|
||||||
|
|
@ -79,6 +94,14 @@ public:
|
||||||
*/
|
*/
|
||||||
void ccEvent(int delay, int ccNumber, float ccValue) noexcept;
|
void ccEvent(int delay, int ccNumber, float ccValue) noexcept;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Advances the internal clock of a given amount of samples.
|
||||||
|
* You should call this at each callback. This will flush the events
|
||||||
|
* in the midistate memory.
|
||||||
|
*
|
||||||
|
* @param numSamples the number of samples of clock advance
|
||||||
|
*/
|
||||||
|
void advanceTime(int numSamples) noexcept;
|
||||||
/**
|
/**
|
||||||
* @brief Get the CC value for CC number
|
* @brief Get the CC value for CC number
|
||||||
*
|
*
|
||||||
|
|
@ -91,57 +114,57 @@ public:
|
||||||
* @brief Reset the midi state (does not impact the last note on time)
|
* @brief Reset the midi state (does not impact the last note on time)
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
void reset(int delay) noexcept;
|
void reset() noexcept;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Reset all the controllers
|
* @brief Reset all the controllers
|
||||||
*/
|
*/
|
||||||
void resetAllControllers(int delay) noexcept;
|
void resetAllControllers(int delay) noexcept;
|
||||||
|
|
||||||
/**
|
const EventVector& getCCEvents(int ccIdx) const noexcept;
|
||||||
* @brief Modulate a value using the last entered CCs in the midiState
|
const EventVector& getPitchEvents() const noexcept;
|
||||||
*
|
|
||||||
* @tparam T
|
|
||||||
* @tparam U
|
|
||||||
* @param value the base value
|
|
||||||
* @param modifiers the list of CC modifiers
|
|
||||||
* @param validRange a range to clamp the output
|
|
||||||
* @param lambda the function to apply for each modifier
|
|
||||||
* @return T
|
|
||||||
*/
|
|
||||||
template<class T, class U>
|
|
||||||
T modulate(T value, const CCMap<U>& modifiers, const Range<T>& validRange, const modFunction<T, U>& lambda = addToBase<T>) const noexcept
|
|
||||||
{
|
|
||||||
for (auto& mod: modifiers) {
|
|
||||||
lambda(value, getCCValue(mod.cc) * mod.value);
|
|
||||||
}
|
|
||||||
return validRange.clamp(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
template<class T>
|
int activeNotes { 0 };
|
||||||
using MidiNoteArray = std::array<T, 128>;
|
|
||||||
using NoteOnTime = std::chrono::steady_clock::time_point;
|
|
||||||
int activeNotes { 0 };
|
|
||||||
/**
|
/**
|
||||||
* @brief Stores the note on times.
|
* @brief Stores the note on times.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
MidiNoteArray<NoteOnTime> noteOnTimes;
|
MidiNoteArray<unsigned> noteOnTimes { {} };
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Stores the note off times.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
MidiNoteArray<unsigned> noteOffTimes { {} };
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Stores the velocity of the note ons for currently
|
* @brief Stores the velocity of the note ons for currently
|
||||||
* depressed notes.
|
* depressed notes.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
MidiNoteArray<float> lastNoteVelocities;
|
MidiNoteArray<float> lastNoteVelocities;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Current known values for the CCs.
|
* @brief Current known values for the CCs.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
std::array<float, config::numCCs> cc;
|
std::array<EventVector, config::numCCs> cc;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Pitch bend status
|
* @brief Null event
|
||||||
|
*
|
||||||
*/
|
*/
|
||||||
int pitchBend { 0 };
|
const EventVector nullEvent { { 0, 0.0f } };
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Pitch bend status
|
||||||
|
*/
|
||||||
|
EventVector pitchEvents;
|
||||||
|
float sampleRate { config::defaultSampleRate };
|
||||||
|
int samplesPerBlock { config::defaultSamplesPerBlock };
|
||||||
|
unsigned internalClock { 0 };
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -124,6 +124,22 @@ bool sfz::Region::parseOpcode(const Opcode& opcode)
|
||||||
DBG("Unkown off mode:" << std::string(opcode.value));
|
DBG("Unkown off mode:" << std::string(opcode.value));
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case hash("note_polyphony"):
|
||||||
|
if (auto value = readOpcode(opcode.value, Default::polyphonyRange))
|
||||||
|
notePolyphony = *value;
|
||||||
|
break;
|
||||||
|
case hash("note_selfmask"):
|
||||||
|
switch (hash(opcode.value)) {
|
||||||
|
case hash("on"):
|
||||||
|
selfMask = SfzSelfMask::mask;
|
||||||
|
break;
|
||||||
|
case hash("off"):
|
||||||
|
selfMask = SfzSelfMask::dontMask;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
DBG("Unkown self mask value:" << std::string(opcode.value));
|
||||||
|
}
|
||||||
|
break;
|
||||||
// Region logic: key mapping
|
// Region logic: key mapping
|
||||||
case hash("lokey"):
|
case hash("lokey"):
|
||||||
setRangeStartFromOpcode(opcode, keyRange, Default::keyRange);
|
setRangeStartFromOpcode(opcode, keyRange, Default::keyRange);
|
||||||
|
|
@ -149,10 +165,12 @@ bool sfz::Region::parseOpcode(const Opcode& opcode)
|
||||||
|
|
||||||
// Region logic: MIDI conditions
|
// Region logic: MIDI conditions
|
||||||
case hash("lobend"):
|
case hash("lobend"):
|
||||||
setRangeStartFromOpcode(opcode, bendRange, Default::bendRange);
|
if (auto value = readOpcode(opcode.value, Default::bendRange))
|
||||||
|
bendRange.setStart(normalizeBend(*value));
|
||||||
break;
|
break;
|
||||||
case hash("hibend"):
|
case hash("hibend"):
|
||||||
setRangeEndFromOpcode(opcode, bendRange, Default::bendRange);
|
if (auto value = readOpcode(opcode.value, Default::bendRange))
|
||||||
|
bendRange.setEnd(normalizeBend(*value));
|
||||||
break;
|
break;
|
||||||
case hash("locc&"):
|
case hash("locc&"):
|
||||||
if (opcode.parameters.back() > config::numCCs)
|
if (opcode.parameters.back() > config::numCCs)
|
||||||
|
|
@ -276,32 +294,51 @@ bool sfz::Region::parseOpcode(const Opcode& opcode)
|
||||||
case hash("gain_cc&"):
|
case hash("gain_cc&"):
|
||||||
case hash("gain_oncc&"): // fallthrough
|
case hash("gain_oncc&"): // fallthrough
|
||||||
case hash("volume_oncc&"):
|
case hash("volume_oncc&"):
|
||||||
setCCPairFromOpcode(opcode, volumeCC, Default::volumeCCRange);
|
if (opcode.parameters.back() > config::numCCs)
|
||||||
|
return false;
|
||||||
|
if (auto value = readOpcode(opcode.value, Default::volumeCCRange))
|
||||||
|
volumeCC[opcode.parameters.back()] = *value;
|
||||||
break;
|
break;
|
||||||
case hash("amplitude"):
|
case hash("amplitude"):
|
||||||
setValueFromOpcode(opcode, amplitude, Default::amplitudeRange);
|
if (auto value = readOpcode(opcode.value, Default::amplitudeRange))
|
||||||
|
amplitude = normalizePercents(*value);
|
||||||
break;
|
break;
|
||||||
case hash("amplitude_cc&"): // fallthrough
|
case hash("amplitude_cc&"): // fallthrough
|
||||||
case hash("amplitude_oncc&"):
|
case hash("amplitude_oncc&"):
|
||||||
setCCPairFromOpcode(opcode, amplitudeCC, Default::amplitudeRange);
|
if (opcode.parameters.back() > config::numCCs)
|
||||||
|
return false;
|
||||||
|
if (auto value = readOpcode(opcode.value, Default::amplitudeRange))
|
||||||
|
amplitudeCC[opcode.parameters.back()] = normalizePercents(*value);
|
||||||
break;
|
break;
|
||||||
case hash("pan"):
|
case hash("pan"):
|
||||||
setValueFromOpcode(opcode, pan, Default::panRange);
|
if (auto value = readOpcode(opcode.value, Default::panRange))
|
||||||
|
pan = normalizePercents(*value);
|
||||||
break;
|
break;
|
||||||
case hash("pan_oncc&"):
|
case hash("pan_oncc&"):
|
||||||
setCCPairFromOpcode(opcode, panCC, Default::panCCRange);
|
if (opcode.parameters.back() > config::numCCs)
|
||||||
|
return false;
|
||||||
|
if (auto value = readOpcode(opcode.value, Default::panCCRange))
|
||||||
|
panCC[opcode.parameters.back()] = normalizePercents(*value);
|
||||||
break;
|
break;
|
||||||
case hash("position"):
|
case hash("position"):
|
||||||
setValueFromOpcode(opcode, position, Default::positionRange);
|
if (auto value = readOpcode(opcode.value, Default::positionRange))
|
||||||
|
position = normalizePercents(*value);
|
||||||
break;
|
break;
|
||||||
case hash("position_oncc&"):
|
case hash("position_oncc&"):
|
||||||
setCCPairFromOpcode(opcode, positionCC, Default::positionCCRange);
|
if (opcode.parameters.back() > config::numCCs)
|
||||||
|
return false;
|
||||||
|
if (auto value = readOpcode(opcode.value, Default::positionCCRange))
|
||||||
|
positionCC[opcode.parameters.back()] = normalizePercents(*value);
|
||||||
break;
|
break;
|
||||||
case hash("width"):
|
case hash("width"):
|
||||||
setValueFromOpcode(opcode, width, Default::widthRange);
|
if (auto value = readOpcode(opcode.value, Default::widthRange))
|
||||||
|
width = normalizePercents(*value);
|
||||||
break;
|
break;
|
||||||
case hash("width_oncc&"):
|
case hash("width_oncc&"):
|
||||||
setCCPairFromOpcode(opcode, widthCC, Default::widthCCRange);
|
if (opcode.parameters.back() > config::numCCs)
|
||||||
|
return false;
|
||||||
|
if (auto value = readOpcode(opcode.value, Default::widthCCRange))
|
||||||
|
widthCC[opcode.parameters.back()] = normalizePercents(*value);
|
||||||
break;
|
break;
|
||||||
case hash("amp_keycenter"):
|
case hash("amp_keycenter"):
|
||||||
setValueFromOpcode(opcode, ampKeycenter, Default::keyRange);
|
setValueFromOpcode(opcode, ampKeycenter, Default::keyRange);
|
||||||
|
|
@ -682,6 +719,15 @@ bool sfz::Region::parseOpcode(const Opcode& opcode)
|
||||||
case hash("pitch"):
|
case hash("pitch"):
|
||||||
setValueFromOpcode(opcode, tune, Default::tuneRange);
|
setValueFromOpcode(opcode, tune, Default::tuneRange);
|
||||||
break;
|
break;
|
||||||
|
case hash("tune_cc&"):
|
||||||
|
case hash("tune_oncc&"):
|
||||||
|
case hash("pitch_cc&"):
|
||||||
|
case hash("pitch_oncc&"):
|
||||||
|
if (opcode.parameters.back() > config::numCCs)
|
||||||
|
return false;
|
||||||
|
if (auto value = readOpcode(opcode.value, Default::tuneCCRange))
|
||||||
|
tuneCC[opcode.parameters.back()] = *value;
|
||||||
|
break;
|
||||||
case hash("bend_up"):
|
case hash("bend_up"):
|
||||||
setValueFromOpcode(opcode, bendUp, Default::bendBoundRange);
|
setValueFromOpcode(opcode, bendUp, Default::bendBoundRange);
|
||||||
break;
|
break;
|
||||||
|
|
@ -905,7 +951,7 @@ bool sfz::Region::registerCC(int ccNumber, float ccValue) noexcept
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void sfz::Region::registerPitchWheel(int pitch) noexcept
|
void sfz::Region::registerPitchWheel(float pitch) noexcept
|
||||||
{
|
{
|
||||||
if (bendRange.containsWithEnd(pitch))
|
if (bendRange.containsWithEnd(pitch))
|
||||||
pitchSwitched = true;
|
pitchSwitched = true;
|
||||||
|
|
@ -938,7 +984,7 @@ float sfz::Region::getBasePitchVariation(int noteNumber, float velocity) const n
|
||||||
auto pitchVariationInCents = pitchKeytrack * (noteNumber - (int)pitchKeycenter); // note difference with pitch center
|
auto pitchVariationInCents = pitchKeytrack * (noteNumber - (int)pitchKeycenter); // note difference with pitch center
|
||||||
pitchVariationInCents += tune; // sample tuning
|
pitchVariationInCents += tune; // sample tuning
|
||||||
pitchVariationInCents += config::centPerSemitone * transpose; // sample transpose
|
pitchVariationInCents += config::centPerSemitone * transpose; // sample transpose
|
||||||
pitchVariationInCents += static_cast<int>(velocity * pitchVeltrack); // track velocity
|
pitchVariationInCents += static_cast<int>(velocity) * pitchVeltrack; // track velocity
|
||||||
pitchVariationInCents += pitchDistribution(Random::randomGenerator); // random pitch changes
|
pitchVariationInCents += pitchDistribution(Random::randomGenerator); // random pitch changes
|
||||||
return centsFactor(pitchVariationInCents);
|
return centsFactor(pitchVariationInCents);
|
||||||
}
|
}
|
||||||
|
|
@ -954,7 +1000,7 @@ float sfz::Region::getBaseVolumedB(int noteNumber) const noexcept
|
||||||
|
|
||||||
float sfz::Region::getBaseGain() const noexcept
|
float sfz::Region::getBaseGain() const noexcept
|
||||||
{
|
{
|
||||||
return normalizePercents(amplitude);
|
return amplitude;
|
||||||
}
|
}
|
||||||
|
|
||||||
float sfz::Region::getPhase() const noexcept
|
float sfz::Region::getPhase() const noexcept
|
||||||
|
|
@ -962,7 +1008,7 @@ float sfz::Region::getPhase() const noexcept
|
||||||
float phase;
|
float phase;
|
||||||
if (oscillatorPhase >= 0) {
|
if (oscillatorPhase >= 0) {
|
||||||
phase = oscillatorPhase * (1.0f / 360.0f);
|
phase = oscillatorPhase * (1.0f / 360.0f);
|
||||||
phase -= static_cast<int>(phase);
|
phase -= static_cast<float>(static_cast<int>(phase));
|
||||||
} else {
|
} else {
|
||||||
std::uniform_real_distribution<float> phaseDist { 0.0001f, 0.9999f };
|
std::uniform_real_distribution<float> phaseDist { 0.0001f, 0.9999f };
|
||||||
phase = phaseDist(Random::randomGenerator);
|
phase = phaseDist(Random::randomGenerator);
|
||||||
|
|
@ -997,48 +1043,6 @@ uint32_t sfz::Region::loopEnd(Oversampling factor) const noexcept
|
||||||
return loopRange.getEnd() * static_cast<uint32_t>(factor);
|
return loopRange.getEnd() * static_cast<uint32_t>(factor);
|
||||||
}
|
}
|
||||||
|
|
||||||
template<class T, class U>
|
|
||||||
float crossfadeIn(const sfz::Range<T>& crossfadeRange, U value, SfzCrossfadeCurve curve)
|
|
||||||
{
|
|
||||||
if (value < crossfadeRange.getStart())
|
|
||||||
return 0.0f;
|
|
||||||
|
|
||||||
const auto length = static_cast<float>(crossfadeRange.length());
|
|
||||||
if (length == 0.0f)
|
|
||||||
return 1.0f;
|
|
||||||
|
|
||||||
else if (value < crossfadeRange.getEnd()) {
|
|
||||||
const auto crossfadePosition = static_cast<float>(value - crossfadeRange.getStart()) / length;
|
|
||||||
if (curve == SfzCrossfadeCurve::power)
|
|
||||||
return sqrt(crossfadePosition);
|
|
||||||
if (curve == SfzCrossfadeCurve::gain)
|
|
||||||
return crossfadePosition;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 1.0f;
|
|
||||||
}
|
|
||||||
|
|
||||||
template<class T, class U>
|
|
||||||
float crossfadeOut(const sfz::Range<T>& crossfadeRange, U value, SfzCrossfadeCurve curve)
|
|
||||||
{
|
|
||||||
if (value > crossfadeRange.getEnd())
|
|
||||||
return 0.0f;
|
|
||||||
|
|
||||||
const auto length = static_cast<float>(crossfadeRange.length());
|
|
||||||
if (length == 0.0f)
|
|
||||||
return 1.0f;
|
|
||||||
|
|
||||||
else if (value > crossfadeRange.getStart()) {
|
|
||||||
const auto crossfadePosition = static_cast<float>(value - crossfadeRange.getStart()) / length;
|
|
||||||
if (curve == SfzCrossfadeCurve::power)
|
|
||||||
return std::sqrt(1 - crossfadePosition);
|
|
||||||
if (curve == SfzCrossfadeCurve::gain)
|
|
||||||
return 1 - crossfadePosition;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 1.0f;
|
|
||||||
}
|
|
||||||
|
|
||||||
float sfz::Region::getNoteGain(int noteNumber, float velocity) const noexcept
|
float sfz::Region::getNoteGain(int noteNumber, float velocity) const noexcept
|
||||||
{
|
{
|
||||||
ASSERT(velocity >= 0.0f && velocity <= 1.0f);
|
ASSERT(velocity >= 0.0f && velocity <= 1.0f);
|
||||||
|
|
|
||||||
|
|
@ -113,7 +113,7 @@ struct Region {
|
||||||
*
|
*
|
||||||
* @param pitch
|
* @param pitch
|
||||||
*/
|
*/
|
||||||
void registerPitchWheel(int pitch) noexcept;
|
void registerPitchWheel(float pitch) noexcept;
|
||||||
/**
|
/**
|
||||||
* @brief Register a new aftertouch event.
|
* @brief Register a new aftertouch event.
|
||||||
*
|
*
|
||||||
|
|
@ -240,13 +240,15 @@ struct Region {
|
||||||
uint32_t group { Default::group }; // group
|
uint32_t group { Default::group }; // group
|
||||||
absl::optional<uint32_t> offBy {}; // off_by
|
absl::optional<uint32_t> offBy {}; // off_by
|
||||||
SfzOffMode offMode { Default::offMode }; // off_mode
|
SfzOffMode offMode { Default::offMode }; // off_mode
|
||||||
|
absl::optional<uint32_t> notePolyphony {};
|
||||||
|
SfzSelfMask selfMask { Default::selfMask };
|
||||||
|
|
||||||
// Region logic: key mapping
|
// Region logic: key mapping
|
||||||
Range<uint8_t> keyRange { Default::keyRange }; //lokey, hikey and key
|
Range<uint8_t> keyRange { Default::keyRange }; //lokey, hikey and key
|
||||||
Range<float> velocityRange { Default::velocityRange }; // hivel and lovel
|
Range<float> velocityRange { Default::velocityRange }; // hivel and lovel
|
||||||
|
|
||||||
// Region logic: MIDI conditions
|
// Region logic: MIDI conditions
|
||||||
Range<int> bendRange { Default::bendRange }; // hibend and lobend
|
Range<float> bendRange { Default::bendValueRange }; // hibend and lobend
|
||||||
CCMap<Range<float>> ccConditions { Default::ccValueRange };
|
CCMap<Range<float>> ccConditions { Default::ccValueRange };
|
||||||
Range<uint8_t> keyswitchRange { Default::keyRange }; // sw_hikey and sw_lokey
|
Range<uint8_t> keyswitchRange { Default::keyRange }; // sw_hikey and sw_lokey
|
||||||
absl::optional<uint8_t> keyswitch {}; // sw_last
|
absl::optional<uint8_t> keyswitch {}; // sw_last
|
||||||
|
|
@ -270,15 +272,15 @@ struct Region {
|
||||||
|
|
||||||
// Performance parameters: amplifier
|
// Performance parameters: amplifier
|
||||||
float volume { Default::volume }; // volume
|
float volume { Default::volume }; // volume
|
||||||
float amplitude { Default::amplitude }; // amplitude
|
float amplitude { normalizePercents(Default::amplitude) }; // amplitude
|
||||||
float pan { Default::pan }; // pan
|
float pan { normalizePercents(Default::pan) }; // pan
|
||||||
float width { Default::width }; // width
|
float width { normalizePercents(Default::width) }; // width
|
||||||
float position { Default::position }; // position
|
float position { normalizePercents(Default::position) }; // position
|
||||||
absl::optional<CCValuePair<float>> volumeCC; // volume_oncc
|
CCMap<float> volumeCC { Default::zeroModifier }; // volume_oncc
|
||||||
absl::optional<CCValuePair<float>> amplitudeCC; // amplitude_oncc
|
CCMap<float> amplitudeCC { Default::zeroModifier }; // amplitude_oncc
|
||||||
absl::optional<CCValuePair<float>> panCC; // pan_oncc
|
CCMap<float> panCC { Default::zeroModifier }; // pan_oncc
|
||||||
absl::optional<CCValuePair<float>> widthCC; // width_oncc
|
CCMap<float> widthCC { Default::zeroModifier }; // width_oncc
|
||||||
absl::optional<CCValuePair<float>> positionCC; // position_oncc
|
CCMap<float> positionCC { Default::zeroModifier }; // position_oncc
|
||||||
uint8_t ampKeycenter { Default::ampKeycenter }; // amp_keycenter
|
uint8_t ampKeycenter { Default::ampKeycenter }; // amp_keycenter
|
||||||
float ampKeytrack { Default::ampKeytrack }; // amp_keytrack
|
float ampKeytrack { Default::ampKeytrack }; // amp_keytrack
|
||||||
float ampVeltrack { Default::ampVeltrack }; // amp_keytrack
|
float ampVeltrack { Default::ampVeltrack }; // amp_keytrack
|
||||||
|
|
@ -306,6 +308,7 @@ struct Region {
|
||||||
int pitchVeltrack { Default::pitchVeltrack }; // pitch_veltrack
|
int pitchVeltrack { Default::pitchVeltrack }; // pitch_veltrack
|
||||||
int transpose { Default::transpose }; // transpose
|
int transpose { Default::transpose }; // transpose
|
||||||
int tune { Default::tune }; // tune
|
int tune { Default::tune }; // tune
|
||||||
|
CCMap<int> tuneCC { Default::tune };
|
||||||
int bendUp { Default::bendUp };
|
int bendUp { Default::bendUp };
|
||||||
int bendDown { Default::bendDown };
|
int bendDown { Default::bendDown };
|
||||||
int bendStep { Default::bendStep };
|
int bendStep { Default::bendStep };
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
#include "FilePool.h"
|
#include "FilePool.h"
|
||||||
|
#include "BufferPool.h"
|
||||||
#include "FilterPool.h"
|
#include "FilterPool.h"
|
||||||
#include "EQPool.h"
|
#include "EQPool.h"
|
||||||
#include "Logger.h"
|
#include "Logger.h"
|
||||||
|
|
@ -17,11 +18,25 @@ class WavetableMulti;
|
||||||
|
|
||||||
struct Resources
|
struct Resources
|
||||||
{
|
{
|
||||||
|
BufferPool bufferPool;
|
||||||
MidiState midiState;
|
MidiState midiState;
|
||||||
Logger logger;
|
Logger logger;
|
||||||
FilePool filePool { logger };
|
FilePool filePool { logger };
|
||||||
FilterPool filterPool { midiState };
|
FilterPool filterPool { midiState };
|
||||||
EQPool eqPool { midiState };
|
EQPool eqPool { midiState };
|
||||||
WavetablePool wavePool;
|
WavetablePool wavePool;
|
||||||
|
|
||||||
|
void setSampleRate(float samplerate)
|
||||||
|
{
|
||||||
|
midiState.setSampleRate(samplerate);
|
||||||
|
filterPool.setSampleRate(samplerate);
|
||||||
|
eqPool.setSampleRate(samplerate);
|
||||||
|
}
|
||||||
|
|
||||||
|
void setSamplesPerBlock(int samplesPerBlock)
|
||||||
|
{
|
||||||
|
bufferPool.setBufferSize(samplesPerBlock);
|
||||||
|
midiState.setSamplesPerBlock(samplesPerBlock);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -538,8 +538,8 @@ namespace _internals {
|
||||||
template <class T>
|
template <class T>
|
||||||
inline void snippetRampLinear(T*& output, T& value, T step)
|
inline void snippetRampLinear(T*& output, T& value, T step)
|
||||||
{
|
{
|
||||||
value += step;
|
|
||||||
*output++ = value;
|
*output++ = value;
|
||||||
|
value += step;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -566,8 +566,8 @@ namespace _internals {
|
||||||
template <class T>
|
template <class T>
|
||||||
inline void snippetRampMultiplicative(T*& output, T& value, T step)
|
inline void snippetRampMultiplicative(T*& output, T& value, T step)
|
||||||
{
|
{
|
||||||
value *= step;
|
|
||||||
*output++ = value;
|
*output++ = value;
|
||||||
|
value *= step;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -461,7 +461,7 @@ float sfz::linearRamp<float, true>(absl::Span<float> output, float value, float
|
||||||
while (unaligned(out) && out < lastAligned)
|
while (unaligned(out) && out < lastAligned)
|
||||||
_internals::snippetRampLinear<float>(out, value, step);
|
_internals::snippetRampLinear<float>(out, value, step);
|
||||||
|
|
||||||
auto mmValue = _mm_set1_ps(value);
|
auto mmValue = _mm_set1_ps(value - step);
|
||||||
auto mmStep = _mm_set_ps(step + step + step + step, step + step + step, step + step, step);
|
auto mmStep = _mm_set_ps(step + step + step + step, step + step + step, step + step, step);
|
||||||
|
|
||||||
while (out < lastAligned) {
|
while (out < lastAligned) {
|
||||||
|
|
@ -471,7 +471,8 @@ float sfz::linearRamp<float, true>(absl::Span<float> output, float value, float
|
||||||
out += TypeAlignment;
|
out += TypeAlignment;
|
||||||
}
|
}
|
||||||
|
|
||||||
value = _mm_cvtss_f32(mmValue);
|
value = _mm_cvtss_f32(mmValue) + step;
|
||||||
|
|
||||||
while (out < output.end())
|
while (out < output.end())
|
||||||
_internals::snippetRampLinear<float>(out, value, step);
|
_internals::snippetRampLinear<float>(out, value, step);
|
||||||
return value;
|
return value;
|
||||||
|
|
@ -486,7 +487,7 @@ float sfz::multiplicativeRamp<float, true>(absl::Span<float> output, float value
|
||||||
while (unaligned(out) && out < lastAligned)
|
while (unaligned(out) && out < lastAligned)
|
||||||
_internals::snippetRampMultiplicative<float>(out, value, step);
|
_internals::snippetRampMultiplicative<float>(out, value, step);
|
||||||
|
|
||||||
auto mmValue = _mm_set1_ps(value);
|
auto mmValue = _mm_set1_ps(value / step);
|
||||||
auto mmStep = _mm_set_ps(step * step * step * step, step * step * step, step * step, step);
|
auto mmStep = _mm_set_ps(step * step * step * step, step * step * step, step * step, step);
|
||||||
|
|
||||||
while (out < lastAligned) {
|
while (out < lastAligned) {
|
||||||
|
|
@ -496,7 +497,7 @@ float sfz::multiplicativeRamp<float, true>(absl::Span<float> output, float value
|
||||||
out += TypeAlignment;
|
out += TypeAlignment;
|
||||||
}
|
}
|
||||||
|
|
||||||
value = _mm_cvtss_f32(mmValue);
|
value = _mm_cvtss_f32(mmValue) * step;
|
||||||
while (out < output.end())
|
while (out < output.end())
|
||||||
_internals::snippetRampMultiplicative<float>(out, value, step);
|
_internals::snippetRampMultiplicative<float>(out, value, step);
|
||||||
return value;
|
return value;
|
||||||
|
|
|
||||||
|
|
@ -13,13 +13,16 @@
|
||||||
#include "Macros.h"
|
#include "Macros.h"
|
||||||
#include "Config.h"
|
#include "Config.h"
|
||||||
#include "MathHelpers.h"
|
#include "MathHelpers.h"
|
||||||
|
#include "SIMDHelpers.h"
|
||||||
#include "absl/meta/type_traits.h"
|
#include "absl/meta/type_traits.h"
|
||||||
|
#include "Defaults.h"
|
||||||
|
|
||||||
namespace sfz
|
namespace sfz
|
||||||
{
|
{
|
||||||
|
|
||||||
using CCNamePair = std::pair<uint16_t, std::string>;
|
using CCNamePair = std::pair<uint16_t, std::string>;
|
||||||
|
template <class T>
|
||||||
|
using MidiNoteArray = std::array<T, 128>;
|
||||||
template<class ValueType>
|
template<class ValueType>
|
||||||
struct CCValuePair {
|
struct CCValuePair {
|
||||||
int cc;
|
int cc;
|
||||||
|
|
@ -62,6 +65,46 @@ struct CCValuePairComparator<ValueType, true> {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct MidiEvent {
|
||||||
|
int delay;
|
||||||
|
float value;
|
||||||
|
};
|
||||||
|
using EventVector = std::vector<MidiEvent>;
|
||||||
|
|
||||||
|
struct MidiEventDelayComparator {
|
||||||
|
bool operator()(const MidiEvent& event, const int& delay)
|
||||||
|
{
|
||||||
|
return (event.delay < delay);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator()(const int& delay, const MidiEvent& event)
|
||||||
|
{
|
||||||
|
return (delay < event.delay);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator()(const MidiEvent& lhs, const MidiEvent& rhs)
|
||||||
|
{
|
||||||
|
return (lhs.delay < rhs.delay);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct MidiEventValueComparator {
|
||||||
|
bool operator()(const MidiEvent& event, const float& value)
|
||||||
|
{
|
||||||
|
return (event.value < value);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator()(const float& value, const MidiEvent& event)
|
||||||
|
{
|
||||||
|
return (value < event.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator()(const MidiEvent& lhs, const MidiEvent& rhs)
|
||||||
|
{
|
||||||
|
return (lhs.value < rhs.value);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Converts cents to a pitch ratio
|
* @brief Converts cents to a pitch ratio
|
||||||
*
|
*
|
||||||
|
|
@ -147,20 +190,18 @@ constexpr float normalizePercents(T percentValue)
|
||||||
*/
|
*/
|
||||||
constexpr float normalizeBend(float bendValue)
|
constexpr float normalizeBend(float bendValue)
|
||||||
{
|
{
|
||||||
return min(max(bendValue, -8191.0f), 8191.0f) / 8191.0f;
|
return clamp(bendValue, -8191.0f, 8191.0f) / 8191.0f;
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace literals
|
namespace literals {
|
||||||
{
|
inline float operator""_norm(unsigned long long int value)
|
||||||
inline float operator ""_norm(unsigned long long int value)
|
{
|
||||||
{
|
if (value > 127)
|
||||||
if (value > 127)
|
value = 127;
|
||||||
value = 127;
|
|
||||||
|
|
||||||
return normalize7Bits(value);
|
return normalize7Bits(value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Convert a note in string to its equivalent midi note number
|
* @brief Convert a note in string to its equivalent midi note number
|
||||||
|
|
@ -232,36 +273,199 @@ bool findDefine(absl::string_view line, absl::string_view& variable, absl::strin
|
||||||
*/
|
*/
|
||||||
bool findInclude(absl::string_view line, std::string& path);
|
bool findInclude(absl::string_view line, std::string& path);
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Defines a function that modulates a base value with another one
|
|
||||||
*
|
|
||||||
* @tparam T
|
|
||||||
*/
|
|
||||||
template<class T, class U>
|
|
||||||
using modFunction = std::function<void(T&, U)>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Modulation helper that adds the modifier to the base value
|
|
||||||
*
|
|
||||||
* @tparam T
|
|
||||||
* @param base the base value
|
|
||||||
* @param modifier the modifier value
|
|
||||||
*/
|
|
||||||
template<class T>
|
|
||||||
inline CXX14_CONSTEXPR void addToBase(T& base, T modifier)
|
|
||||||
{
|
|
||||||
base += modifier;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief multiply a value by a factor, in cents. To be used for pitch variations.
|
* @brief multiply a value by a factor, in cents. To be used for pitch variations.
|
||||||
*
|
*
|
||||||
* @param base
|
* @param base
|
||||||
* @param modifier
|
* @param modifier
|
||||||
*/
|
*/
|
||||||
inline CXX14_CONSTEXPR void multiplyByCents(float& base, int modifier)
|
inline CXX14_CONSTEXPR float multiplyByCentsModifier(int modifier, float base)
|
||||||
{
|
{
|
||||||
base *= centsFactor(modifier);
|
return base * centsFactor(modifier);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
inline CXX14_CONSTEXPR float gainModifier(T modifier, float value)
|
||||||
|
{
|
||||||
|
return value * modifier;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Compute a crossfade in value with respect to a crossfade range (note, velocity, cc, ...)
|
||||||
|
*/
|
||||||
|
template <class T, class U>
|
||||||
|
float crossfadeIn(const sfz::Range<T>& crossfadeRange, U value, SfzCrossfadeCurve curve)
|
||||||
|
{
|
||||||
|
if (value < crossfadeRange.getStart())
|
||||||
|
return 0.0f;
|
||||||
|
|
||||||
|
const auto length = static_cast<float>(crossfadeRange.length());
|
||||||
|
if (length == 0.0f)
|
||||||
|
return 1.0f;
|
||||||
|
|
||||||
|
else if (value < crossfadeRange.getEnd()) {
|
||||||
|
const auto crossfadePosition = static_cast<float>(value - crossfadeRange.getStart()) / length;
|
||||||
|
if (curve == SfzCrossfadeCurve::power)
|
||||||
|
return sqrt(crossfadePosition);
|
||||||
|
if (curve == SfzCrossfadeCurve::gain)
|
||||||
|
return crossfadePosition;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 1.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Compute a crossfade out value with respect to a crossfade range (note, velocity, cc, ...)
|
||||||
|
*/
|
||||||
|
template <class T, class U>
|
||||||
|
float crossfadeOut(const sfz::Range<T>& crossfadeRange, U value, SfzCrossfadeCurve curve)
|
||||||
|
{
|
||||||
|
if (value > crossfadeRange.getEnd())
|
||||||
|
return 0.0f;
|
||||||
|
|
||||||
|
const auto length = static_cast<float>(crossfadeRange.length());
|
||||||
|
if (length == 0.0f)
|
||||||
|
return 1.0f;
|
||||||
|
|
||||||
|
else if (value > crossfadeRange.getStart()) {
|
||||||
|
const auto crossfadePosition = static_cast<float>(value - crossfadeRange.getStart()) / length;
|
||||||
|
if (curve == SfzCrossfadeCurve::power)
|
||||||
|
return std::sqrt(1 - crossfadePosition);
|
||||||
|
if (curve == SfzCrossfadeCurve::gain)
|
||||||
|
return 1 - crossfadePosition;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 1.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class F>
|
||||||
|
void linearEnvelope(const EventVector& events, absl::Span<float> envelope, F&& lambda)
|
||||||
|
{
|
||||||
|
ASSERT(events.size() > 0);
|
||||||
|
ASSERT(events[0].delay == 0);
|
||||||
|
if (envelope.size() == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
const auto maxDelay = static_cast<int>(envelope.size() - 1);
|
||||||
|
|
||||||
|
auto lastValue = lambda(events[0].value);
|
||||||
|
auto lastDelay = events[0].delay;
|
||||||
|
for (unsigned i = 1; i < events.size() && lastDelay < maxDelay; ++i) {
|
||||||
|
const auto length = min(events[i].delay, maxDelay) - lastDelay;
|
||||||
|
const auto step = (lambda(events[i].value) - lastValue) / length;
|
||||||
|
lastValue = linearRamp<float>(envelope.subspan(lastDelay, length), lastValue, step);
|
||||||
|
lastDelay += length;
|
||||||
|
}
|
||||||
|
fill<float>(envelope.subspan(lastDelay), lastValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class F>
|
||||||
|
void linearEnvelope(const EventVector& events, absl::Span<float> envelope, F&& lambda, float step)
|
||||||
|
{
|
||||||
|
ASSERT(events.size() > 0);
|
||||||
|
ASSERT(events[0].delay == 0);
|
||||||
|
ASSERT(step != 0.0);
|
||||||
|
|
||||||
|
if (envelope.size() == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
auto quantize = [step](float value) -> float {
|
||||||
|
return std::round(value / step) * step;
|
||||||
|
};
|
||||||
|
const auto maxDelay = static_cast<int>(envelope.size() - 1);
|
||||||
|
|
||||||
|
auto lastValue = quantize(lambda(events[0].value));
|
||||||
|
auto lastDelay = events[0].delay;
|
||||||
|
for (unsigned i = 1; i < events.size() && lastDelay < maxDelay; ++i) {
|
||||||
|
const auto nextValue = quantize(lambda(events[i].value));
|
||||||
|
const auto difference = std::abs(nextValue - lastValue);
|
||||||
|
const auto length = min(events[i].delay, maxDelay) - lastDelay;
|
||||||
|
|
||||||
|
if (difference < step) {
|
||||||
|
fill<float>(envelope.subspan(lastDelay, length), lastValue);
|
||||||
|
lastValue = nextValue;
|
||||||
|
lastDelay += length;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto numSteps = static_cast<int>(difference / step);
|
||||||
|
const auto stepLength = static_cast<int>(length / numSteps);
|
||||||
|
for (int i = 0; i < numSteps; ++i) {
|
||||||
|
fill<float>(envelope.subspan(lastDelay, stepLength), lastValue);
|
||||||
|
lastValue += lastValue <= nextValue ? step : -step;
|
||||||
|
lastDelay += stepLength;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fill<float>(envelope.subspan(lastDelay), lastValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class F>
|
||||||
|
void multiplicativeEnvelope(const EventVector& events, absl::Span<float> envelope, F&& lambda)
|
||||||
|
{
|
||||||
|
ASSERT(events.size() > 0);
|
||||||
|
ASSERT(events[0].delay == 0);
|
||||||
|
|
||||||
|
if (envelope.size() == 0)
|
||||||
|
return;
|
||||||
|
const auto maxDelay = static_cast<int>(envelope.size() - 1);
|
||||||
|
|
||||||
|
auto lastValue = lambda(events[0].value);
|
||||||
|
auto lastDelay = events[0].delay;
|
||||||
|
for (unsigned i = 1; i < events.size() && lastDelay < maxDelay; ++i) {
|
||||||
|
const auto length = min(events[i].delay, maxDelay) - lastDelay;
|
||||||
|
const auto nextValue = lambda(events[i].value);
|
||||||
|
const auto step = std::exp((std::log(nextValue) - std::log(lastValue)) / length);
|
||||||
|
multiplicativeRamp<float>(envelope.subspan(lastDelay, length), lastValue, step);
|
||||||
|
lastValue = nextValue;
|
||||||
|
lastDelay += length;
|
||||||
|
}
|
||||||
|
fill<float>(envelope.subspan(lastDelay), lastValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class F>
|
||||||
|
void multiplicativeEnvelope(const EventVector& events, absl::Span<float> envelope, F&& lambda, float step)
|
||||||
|
{
|
||||||
|
ASSERT(events.size() > 0);
|
||||||
|
ASSERT(events[0].delay == 0);
|
||||||
|
ASSERT(step != 0.0f);
|
||||||
|
|
||||||
|
if (envelope.size() == 0)
|
||||||
|
return;
|
||||||
|
const auto maxDelay = static_cast<int>(envelope.size() - 1);
|
||||||
|
|
||||||
|
const auto logStep = std::log(step);
|
||||||
|
// If we assume that a = b.q^r for b in (1, q) then
|
||||||
|
// log a log b
|
||||||
|
// ----- = ----- + r
|
||||||
|
// log q log q
|
||||||
|
// and log(b)\log(q) is between 0 and 1.
|
||||||
|
auto quantize = [logStep](float value) -> float {
|
||||||
|
return std::exp(logStep * std::round(std::log(value) / logStep));
|
||||||
|
};
|
||||||
|
|
||||||
|
auto lastValue = quantize(lambda(events[0].value));
|
||||||
|
auto lastDelay = events[0].delay;
|
||||||
|
for (unsigned i = 1; i < events.size() && lastDelay < maxDelay; ++i) {
|
||||||
|
const auto length = min(events[i].delay, maxDelay) - lastDelay;
|
||||||
|
const auto nextValue = quantize(lambda(events[i].value));
|
||||||
|
const auto difference = nextValue > lastValue ? nextValue / lastValue : lastValue / nextValue;
|
||||||
|
|
||||||
|
if (difference < step) {
|
||||||
|
fill<float>(envelope.subspan(lastDelay, length), lastValue);
|
||||||
|
lastValue = nextValue;
|
||||||
|
lastDelay += length;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto numSteps = static_cast<int>(std::log(difference) / logStep);
|
||||||
|
const auto stepLength = static_cast<int>(length / numSteps);
|
||||||
|
for (int i = 0; i < numSteps; ++i) {
|
||||||
|
fill<float>(envelope.subspan(lastDelay, stepLength), lastValue);
|
||||||
|
lastValue = nextValue > lastValue ? lastValue * step : lastValue / step;
|
||||||
|
lastDelay += stepLength;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fill<float>(envelope.subspan(lastDelay), lastValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace sfz
|
} // namespace sfz
|
||||||
|
|
|
||||||
|
|
@ -67,6 +67,7 @@ void sfz::Synth::onParseFullBlock(const std::string& header, const std::vector<O
|
||||||
break;
|
break;
|
||||||
case hash("group"):
|
case hash("group"):
|
||||||
groupOpcodes = members;
|
groupOpcodes = members;
|
||||||
|
handleGroupOpcodes(members);
|
||||||
numGroups++;
|
numGroups++;
|
||||||
break;
|
break;
|
||||||
case hash("region"):
|
case hash("region"):
|
||||||
|
|
@ -150,12 +151,14 @@ void sfz::Synth::clear()
|
||||||
fileTicket = -1;
|
fileTicket = -1;
|
||||||
defaultSwitch = absl::nullopt;
|
defaultSwitch = absl::nullopt;
|
||||||
defaultPath = "";
|
defaultPath = "";
|
||||||
resources.midiState.reset(0);
|
resources.midiState.reset();
|
||||||
ccNames.clear();
|
ccNames.clear();
|
||||||
globalOpcodes.clear();
|
globalOpcodes.clear();
|
||||||
masterOpcodes.clear();
|
masterOpcodes.clear();
|
||||||
groupOpcodes.clear();
|
groupOpcodes.clear();
|
||||||
unknownOpcodes.clear();
|
unknownOpcodes.clear();
|
||||||
|
groupMaxPolyphony.clear();
|
||||||
|
groupMaxPolyphony.push_back(config::maxVoices);
|
||||||
modificationTime = fs::file_time_type::min();
|
modificationTime = fs::file_time_type::min();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -174,6 +177,26 @@ void sfz::Synth::handleGlobalOpcodes(const std::vector<Opcode>& members)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void sfz::Synth::handleGroupOpcodes(const std::vector<Opcode>& members)
|
||||||
|
{
|
||||||
|
absl::optional<unsigned> groupIdx;
|
||||||
|
unsigned maxPolyphony { config::maxVoices };
|
||||||
|
|
||||||
|
for (auto& member : members) {
|
||||||
|
switch (member.lettersOnlyHash) {
|
||||||
|
case hash("group"):
|
||||||
|
setValueFromOpcode(member, groupIdx, Default::groupRange);
|
||||||
|
break;
|
||||||
|
case hash("polyphony"):
|
||||||
|
setValueFromOpcode(member, maxPolyphony, Range<unsigned>(0, config::maxVoices));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (groupIdx)
|
||||||
|
setGroupPolyphony(*groupIdx, maxPolyphony);
|
||||||
|
}
|
||||||
|
|
||||||
void sfz::Synth::handleControlOpcodes(const std::vector<Opcode>& members)
|
void sfz::Synth::handleControlOpcodes(const std::vector<Opcode>& members)
|
||||||
{
|
{
|
||||||
for (auto& member : members) {
|
for (auto& member : members) {
|
||||||
|
|
@ -375,18 +398,22 @@ bool sfz::Synth::loadSfzFile(const fs::path& file)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Some regions had group number but no "group-level" opcodes handled the polyphony
|
||||||
|
while (groupMaxPolyphony.size() <= region->group)
|
||||||
|
groupMaxPolyphony.push_back(config::maxVoices);
|
||||||
|
|
||||||
for (auto note = 0; note < 128; note++) {
|
for (auto note = 0; note < 128; note++) {
|
||||||
if (region->keyRange.containsWithEnd(note) || (region->hasKeyswitches() && region->keyswitchRange.containsWithEnd(note)))
|
if (region->keyRange.containsWithEnd(note) || (region->hasKeyswitches() && region->keyswitchRange.containsWithEnd(note)))
|
||||||
noteActivationLists[note].push_back(region);
|
noteActivationLists[note].push_back(region);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (unsigned cc = 0; cc < config::numCCs; cc++) {
|
for (int cc = 0; cc < config::numCCs; cc++) {
|
||||||
if (region->ccTriggers.contains(cc) || region->ccConditions.contains(cc))
|
if (region->ccTriggers.contains(cc) || region->ccConditions.contains(cc))
|
||||||
ccActivationLists[cc].push_back(region);
|
ccActivationLists[cc].push_back(region);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Defaults
|
// Defaults
|
||||||
for (unsigned cc = 0; cc < config::numCCs; cc++) {
|
for (int cc = 0; cc < config::numCCs; cc++) {
|
||||||
region->registerCC(cc, resources.midiState.getCCValue(cc));
|
region->registerCC(cc, resources.midiState.getCCValue(cc));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -479,11 +506,11 @@ void sfz::Synth::setSamplesPerBlock(int samplesPerBlock) noexcept
|
||||||
}
|
}
|
||||||
|
|
||||||
this->samplesPerBlock = samplesPerBlock;
|
this->samplesPerBlock = samplesPerBlock;
|
||||||
this->tempBuffer.resize(samplesPerBlock);
|
|
||||||
this->tempMixNodeBuffer.resize(samplesPerBlock);
|
|
||||||
for (auto& voice : voices)
|
for (auto& voice : voices)
|
||||||
voice->setSamplesPerBlock(samplesPerBlock);
|
voice->setSamplesPerBlock(samplesPerBlock);
|
||||||
|
|
||||||
|
resources.setSamplesPerBlock(samplesPerBlock);
|
||||||
|
|
||||||
for (auto& bus : effectBuses) {
|
for (auto& bus : effectBuses) {
|
||||||
if (bus)
|
if (bus)
|
||||||
bus->setSamplesPerBlock(samplesPerBlock);
|
bus->setSamplesPerBlock(samplesPerBlock);
|
||||||
|
|
@ -501,8 +528,7 @@ void sfz::Synth::setSampleRate(float sampleRate) noexcept
|
||||||
for (auto& voice : voices)
|
for (auto& voice : voices)
|
||||||
voice->setSampleRate(sampleRate);
|
voice->setSampleRate(sampleRate);
|
||||||
|
|
||||||
resources.filterPool.setSampleRate(sampleRate);
|
resources.setSampleRate(sampleRate);
|
||||||
resources.eqPool.setSampleRate(sampleRate);
|
|
||||||
|
|
||||||
for (auto& bus : effectBuses) {
|
for (auto& bus : effectBuses) {
|
||||||
if (bus)
|
if (bus)
|
||||||
|
|
@ -522,9 +548,12 @@ void sfz::Synth::renderBlock(AudioSpan<float> buffer) noexcept
|
||||||
return;
|
return;
|
||||||
|
|
||||||
size_t numFrames = buffer.getNumFrames();
|
size_t numFrames = buffer.getNumFrames();
|
||||||
auto temp = AudioSpan<float>(tempBuffer).first(numFrames);
|
auto tempSpan = resources.bufferPool.getStereoBuffer(numFrames);
|
||||||
auto tempMixNode = AudioSpan<float>(tempMixNodeBuffer).first(numFrames);
|
auto tempMixSpan = resources.bufferPool.getStereoBuffer(numFrames);
|
||||||
|
if (!tempSpan || !tempMixSpan) {
|
||||||
|
DBG("[sfizz] Could not get a temporary buffer; exiting callback... ");
|
||||||
|
return;
|
||||||
|
}
|
||||||
CallbackBreakdown callbackBreakdown;
|
CallbackBreakdown callbackBreakdown;
|
||||||
|
|
||||||
{ // Prepare the effect inputs. They are mixes of per-region outputs.
|
{ // Prepare the effect inputs. They are mixes of per-region outputs.
|
||||||
|
|
@ -539,7 +568,7 @@ void sfz::Synth::renderBlock(AudioSpan<float> buffer) noexcept
|
||||||
{ // Main render block
|
{ // Main render block
|
||||||
ScopedTiming logger { callbackBreakdown.renderMethod };
|
ScopedTiming logger { callbackBreakdown.renderMethod };
|
||||||
buffer.fill(0.0f);
|
buffer.fill(0.0f);
|
||||||
tempMixNode.fill(0.0f);
|
tempSpan->fill(0.0f);
|
||||||
resources.filePool.cleanupPromises();
|
resources.filePool.cleanupPromises();
|
||||||
|
|
||||||
for (auto& voice : voices) {
|
for (auto& voice : voices) {
|
||||||
|
|
@ -549,14 +578,14 @@ void sfz::Synth::renderBlock(AudioSpan<float> buffer) noexcept
|
||||||
const Region* region = voice->getRegion();
|
const Region* region = voice->getRegion();
|
||||||
|
|
||||||
numActiveVoices++;
|
numActiveVoices++;
|
||||||
voice->renderBlock(temp);
|
voice->renderBlock(*tempSpan);
|
||||||
|
|
||||||
{ // Add the output into the effects linked to this region
|
{ // Add the output into the effects linked to this region
|
||||||
ScopedTiming logger { callbackBreakdown.effects, ScopedTiming::Operation::addToDuration };
|
ScopedTiming logger { callbackBreakdown.effects, ScopedTiming::Operation::addToDuration };
|
||||||
for (size_t i = 0, n = effectBuses.size(); i < n; ++i) {
|
for (size_t i = 0, n = effectBuses.size(); i < n; ++i) {
|
||||||
if (auto& bus = effectBuses[i]) {
|
if (auto& bus = effectBuses[i]) {
|
||||||
float addGain = region->getGainToEffectBus(i);
|
float addGain = region->getGainToEffectBus(i);
|
||||||
bus->addToInputs(temp, addGain, numFrames);
|
bus->addToInputs(*tempSpan, addGain, numFrames);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -576,7 +605,7 @@ void sfz::Synth::renderBlock(AudioSpan<float> buffer) noexcept
|
||||||
for (auto& bus : effectBuses) {
|
for (auto& bus : effectBuses) {
|
||||||
if (bus) {
|
if (bus) {
|
||||||
bus->process(numFrames);
|
bus->process(numFrames);
|
||||||
bus->mixOutputsTo(buffer, tempMixNode, numFrames);
|
bus->mixOutputsTo(buffer, *tempMixSpan, numFrames);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -585,11 +614,16 @@ void sfz::Synth::renderBlock(AudioSpan<float> buffer) noexcept
|
||||||
// -- note(jpc) the purpose of the Mix output is not known.
|
// -- note(jpc) the purpose of the Mix output is not known.
|
||||||
// perhaps it's designed as extension point for custom processing?
|
// perhaps it's designed as extension point for custom processing?
|
||||||
// as default behavior, it adds itself to the Main signal.
|
// as default behavior, it adds itself to the Main signal.
|
||||||
buffer.add(tempMixNode);
|
buffer.add(*tempMixSpan);
|
||||||
|
|
||||||
// Apply the master volume
|
// Apply the master volume
|
||||||
buffer.applyGain(db2mag(volume));
|
buffer.applyGain(db2mag(volume));
|
||||||
|
|
||||||
|
{ // Clear events and advance midi time
|
||||||
|
ScopedTiming logger { dispatchDuration, ScopedTiming::Operation::addToDuration };
|
||||||
|
resources.midiState.advanceTime(buffer.getNumFrames());
|
||||||
|
}
|
||||||
|
|
||||||
callbackBreakdown.dispatch = dispatchDuration;
|
callbackBreakdown.dispatch = dispatchDuration;
|
||||||
resources.logger.logCallbackTime(callbackBreakdown, numActiveVoices, numFrames);
|
resources.logger.logCallbackTime(callbackBreakdown, numActiveVoices, numFrames);
|
||||||
|
|
||||||
|
|
@ -655,11 +689,50 @@ void sfz::Synth::noteOnDispatch(int delay, int noteNumber, float velocity) noexc
|
||||||
const auto randValue = randNoteDistribution(Random::randomGenerator);
|
const auto randValue = randNoteDistribution(Random::randomGenerator);
|
||||||
for (auto& region : noteActivationLists[noteNumber]) {
|
for (auto& region : noteActivationLists[noteNumber]) {
|
||||||
if (region->registerNoteOn(noteNumber, velocity, randValue)) {
|
if (region->registerNoteOn(noteNumber, velocity, randValue)) {
|
||||||
|
unsigned activeNotesInGroup { 0 };
|
||||||
|
unsigned activeNotes { 0 };
|
||||||
|
Voice* selfMaskCandidate { nullptr };
|
||||||
|
|
||||||
for (auto& voice : voices) {
|
for (auto& voice : voices) {
|
||||||
|
const auto voiceRegion = voice->getRegion();
|
||||||
|
if (voiceRegion == nullptr)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (voiceRegion->group == region->group)
|
||||||
|
activeNotesInGroup += 1;
|
||||||
|
|
||||||
|
if (region->notePolyphony) {
|
||||||
|
if (voice->getTriggerNumber() == noteNumber && voice->getTriggerType() == Voice::TriggerType::NoteOn) {
|
||||||
|
activeNotes += 1;
|
||||||
|
switch (region->selfMask) {
|
||||||
|
case SfzSelfMask::mask:
|
||||||
|
if (voice->getTriggerValue() < velocity) {
|
||||||
|
if (!selfMaskCandidate || selfMaskCandidate->getTriggerValue() > voice->getTriggerValue())
|
||||||
|
selfMaskCandidate = voice.get();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case SfzSelfMask::dontMask:
|
||||||
|
if (!selfMaskCandidate || selfMaskCandidate->getSourcePosition() < voice->getSourcePosition())
|
||||||
|
selfMaskCandidate = voice.get();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (voice->checkOffGroup(delay, region->group))
|
if (voice->checkOffGroup(delay, region->group))
|
||||||
noteOffDispatch(delay, voice->getTriggerNumber(), voice->getTriggerValue());
|
noteOffDispatch(delay, voice->getTriggerNumber(), voice->getTriggerValue());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (activeNotesInGroup >= groupMaxPolyphony[region->group])
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (region->notePolyphony && activeNotes >= *region->notePolyphony) {
|
||||||
|
if (selfMaskCandidate != nullptr)
|
||||||
|
selfMaskCandidate->release(delay);
|
||||||
|
else // We're the lowest velocity guy here
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
auto voice = findFreeVoice();
|
auto voice = findFreeVoice();
|
||||||
if (voice == nullptr)
|
if (voice == nullptr)
|
||||||
continue;
|
continue;
|
||||||
|
|
@ -705,16 +778,17 @@ void sfz::Synth::pitchWheel(int delay, int pitch) noexcept
|
||||||
{
|
{
|
||||||
ASSERT(pitch <= 8192);
|
ASSERT(pitch <= 8192);
|
||||||
ASSERT(pitch >= -8192);
|
ASSERT(pitch >= -8192);
|
||||||
|
const auto normalizedPitch = normalizeBend(pitch);
|
||||||
|
|
||||||
ScopedTiming logger { dispatchDuration, ScopedTiming::Operation::addToDuration };
|
ScopedTiming logger { dispatchDuration, ScopedTiming::Operation::addToDuration };
|
||||||
resources.midiState.pitchBendEvent(delay, pitch);
|
resources.midiState.pitchBendEvent(delay, normalizedPitch);
|
||||||
|
|
||||||
for (auto& region : regions) {
|
for (auto& region : regions) {
|
||||||
region->registerPitchWheel(pitch);
|
region->registerPitchWheel(normalizedPitch);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (auto& voice : voices) {
|
for (auto& voice : voices) {
|
||||||
voice->registerPitchWheel(delay, pitch);
|
voice->registerPitchWheel(delay, normalizedPitch);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
void sfz::Synth::aftertouch(int /* delay */, uint8_t /* aftertouch */) noexcept
|
void sfz::Synth::aftertouch(int /* delay */, uint8_t /* aftertouch */) noexcept
|
||||||
|
|
@ -953,12 +1027,12 @@ void sfz::Synth::resetAllControllers(int delay) noexcept
|
||||||
resources.midiState.resetAllControllers(delay);
|
resources.midiState.resetAllControllers(delay);
|
||||||
for (auto& voice : voices) {
|
for (auto& voice : voices) {
|
||||||
voice->registerPitchWheel(delay, 0);
|
voice->registerPitchWheel(delay, 0);
|
||||||
for (unsigned cc = 0; cc < config::numCCs; ++cc)
|
for (int cc = 0; cc < config::numCCs; ++cc)
|
||||||
voice->registerCC(delay, cc, 0.0f);
|
voice->registerCC(delay, cc, 0.0f);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (auto& region : regions) {
|
for (auto& region : regions) {
|
||||||
for (unsigned cc = 0; cc < config::numCCs; ++cc)
|
for (int cc = 0; cc < config::numCCs; ++cc)
|
||||||
region->registerCC(cc, 0.0f);
|
region->registerCC(cc, 0.0f);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1006,3 +1080,11 @@ void sfz::Synth::allSoundOff() noexcept
|
||||||
for (auto& effectBus : effectBuses)
|
for (auto& effectBus : effectBuses)
|
||||||
effectBus->clear();
|
effectBus->clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void sfz::Synth::setGroupPolyphony(unsigned groupIdx, unsigned polyphony) noexcept
|
||||||
|
{
|
||||||
|
while (groupMaxPolyphony.size() <= groupIdx)
|
||||||
|
groupMaxPolyphony.push_back(config::maxVoices);
|
||||||
|
|
||||||
|
groupMaxPolyphony[groupIdx] = polyphony;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -410,6 +410,15 @@ protected:
|
||||||
void onParseWarning(const SourceRange& range, const std::string& message) override;
|
void onParseWarning(const SourceRange& range, const std::string& message) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
/**
|
||||||
|
* @brief change the group maximum polyphony
|
||||||
|
*
|
||||||
|
* @param groupIdx the group index
|
||||||
|
* @param polyphone the max polyphony
|
||||||
|
*/
|
||||||
|
void setGroupPolyphony(unsigned groupIdx, unsigned polyphony) noexcept;
|
||||||
|
std::vector<unsigned> groupMaxPolyphony { config::maxVoices };
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Reset all CCs; to be used on CC 121
|
* @brief Reset all CCs; to be used on CC 121
|
||||||
*
|
*
|
||||||
|
|
@ -440,6 +449,12 @@ private:
|
||||||
* @param members the opcodes of the <global> block
|
* @param members the opcodes of the <global> block
|
||||||
*/
|
*/
|
||||||
void handleGlobalOpcodes(const std::vector<Opcode>& members);
|
void handleGlobalOpcodes(const std::vector<Opcode>& members);
|
||||||
|
/**
|
||||||
|
* @brief Helper function to dispatch <group> opcodes
|
||||||
|
*
|
||||||
|
* @param members the opcodes of the <group> block
|
||||||
|
*/
|
||||||
|
void handleGroupOpcodes(const std::vector<Opcode>& members);
|
||||||
/**
|
/**
|
||||||
* @brief Helper function to dispatch <control> opcodes
|
* @brief Helper function to dispatch <control> opcodes
|
||||||
*
|
*
|
||||||
|
|
@ -501,10 +516,6 @@ private:
|
||||||
// Curves
|
// Curves
|
||||||
CurveSet curves;
|
CurveSet curves;
|
||||||
|
|
||||||
// Intermediate buffers
|
|
||||||
AudioBuffer<float> tempBuffer { 2, config::defaultSamplesPerBlock };
|
|
||||||
AudioBuffer<float> tempMixNodeBuffer { 2, config::defaultSamplesPerBlock };
|
|
||||||
|
|
||||||
int samplesPerBlock { config::defaultSamplesPerBlock };
|
int samplesPerBlock { config::defaultSamplesPerBlock };
|
||||||
float sampleRate { config::defaultSampleRate };
|
float sampleRate { config::defaultSampleRate };
|
||||||
float volume { Default::globalVolume };
|
float volume { Default::globalVolume };
|
||||||
|
|
|
||||||
|
|
@ -74,50 +74,11 @@ void sfz::Voice::startVoice(Region* region, int delay, int number, float value,
|
||||||
speedRatio = static_cast<float>(currentPromise->sampleRate / this->sampleRate);
|
speedRatio = static_cast<float>(currentPromise->sampleRate / this->sampleRate);
|
||||||
}
|
}
|
||||||
pitchRatio = region->getBasePitchVariation(number, value);
|
pitchRatio = region->getBasePitchVariation(number, value);
|
||||||
|
|
||||||
baseVolumedB = region->getBaseVolumedB(number);
|
baseVolumedB = region->getBaseVolumedB(number);
|
||||||
auto volumedB = baseVolumedB;
|
|
||||||
if (region->volumeCC)
|
|
||||||
volumedB += resources.midiState.getCCValue(region->volumeCC->cc) * region->volumeCC->value;
|
|
||||||
volumeEnvelope.reset(db2mag(Default::volumeRange.clamp(volumedB)));
|
|
||||||
|
|
||||||
baseGain = region->getBaseGain();
|
baseGain = region->getBaseGain();
|
||||||
if (triggerType != TriggerType::CC)
|
if (triggerType != TriggerType::CC)
|
||||||
baseGain *= region->getNoteGain(number, value);
|
baseGain *= region->getNoteGain(number, value);
|
||||||
|
|
||||||
float gain { baseGain };
|
|
||||||
if (region->amplitudeCC)
|
|
||||||
gain += resources.midiState.getCCValue(region->amplitudeCC->cc) * normalizePercents(region->amplitudeCC->value);
|
|
||||||
amplitudeEnvelope.reset(Default::normalizedRange.clamp(gain));
|
|
||||||
|
|
||||||
float crossfadeGain { region->getCrossfadeGain() };
|
|
||||||
crossfadeEnvelope.reset(Default::normalizedRange.clamp(crossfadeGain));
|
|
||||||
|
|
||||||
basePan = normalizePercents(region->pan);
|
|
||||||
auto pan = basePan;
|
|
||||||
if (region->panCC)
|
|
||||||
pan += resources.midiState.getCCValue(region->panCC->cc) * normalizePercents(region->panCC->value);
|
|
||||||
panEnvelope.reset(Default::symmetricNormalizedRange.clamp(pan));
|
|
||||||
|
|
||||||
basePosition = normalizePercents(region->position);
|
|
||||||
auto position = basePosition;
|
|
||||||
if (region->positionCC)
|
|
||||||
position += resources.midiState.getCCValue(region->positionCC->cc) * normalizePercents(region->positionCC->value);
|
|
||||||
positionEnvelope.reset(Default::symmetricNormalizedRange.clamp(position));
|
|
||||||
|
|
||||||
baseWidth = normalizePercents(region->width);
|
|
||||||
auto width = baseWidth;
|
|
||||||
if (region->widthCC)
|
|
||||||
width += resources.midiState.getCCValue(region->widthCC->cc) * normalizePercents(region->widthCC->value);
|
|
||||||
widthEnvelope.reset(Default::symmetricNormalizedRange.clamp(width));
|
|
||||||
|
|
||||||
pitchBendEnvelope.setFunction([region](float pitchValue){
|
|
||||||
const auto normalizedBend = normalizeBend(pitchValue);
|
|
||||||
const auto bendInCents = normalizedBend > 0.0f ? normalizedBend * static_cast<float>(region->bendUp) : -normalizedBend * static_cast<float>(region->bendDown);
|
|
||||||
return centsFactor(bendInCents);
|
|
||||||
});
|
|
||||||
pitchBendEnvelope.reset(static_cast<float>(resources.midiState.getPitchBend()));
|
|
||||||
|
|
||||||
// Check that we can handle the number of filters; filters should be cleared here
|
// Check that we can handle the number of filters; filters should be cleared here
|
||||||
ASSERT((filters.capacity() - filters.size()) >= region->filters.size());
|
ASSERT((filters.capacity() - filters.size()) >= region->filters.size());
|
||||||
ASSERT((equalizers.capacity() - equalizers.size()) >= region->equalizers.size());
|
ASSERT((equalizers.capacity() - equalizers.size()) >= region->equalizers.size());
|
||||||
|
|
@ -198,48 +159,14 @@ void sfz::Voice::registerCC(int delay, int ccNumber, float ccValue) noexcept
|
||||||
|
|
||||||
if (region->checkSustain && noteIsOff && ccNumber == config::sustainCC && ccValue < config::halfCCThreshold)
|
if (region->checkSustain && noteIsOff && ccNumber == config::sustainCC && ccValue < config::halfCCThreshold)
|
||||||
release(delay);
|
release(delay);
|
||||||
|
|
||||||
// Add a minimum delay for smoothing the envelopes
|
|
||||||
// TODO: this feels like a hack, revisit this along with the smoothed envelopes...
|
|
||||||
delay = max(delay, minEnvelopeDelay);
|
|
||||||
|
|
||||||
if (region->amplitudeCC && ccNumber == region->amplitudeCC->cc) {
|
|
||||||
const float newGain { baseGain + ccValue * normalizePercents(region->amplitudeCC->value) };
|
|
||||||
amplitudeEnvelope.registerEvent(delay, Default::normalizedRange.clamp(newGain));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (region->volumeCC && ccNumber == region->volumeCC->cc) {
|
|
||||||
const float newVolumedB { baseVolumedB + ccValue * region->volumeCC->value };
|
|
||||||
volumeEnvelope.registerEvent(delay, db2mag(Default::volumeRange.clamp(newVolumedB)));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (region->panCC && ccNumber == region->panCC->cc) {
|
|
||||||
const float newPan { basePan + ccValue * normalizePercents(region->panCC->value) };
|
|
||||||
panEnvelope.registerEvent(delay, Default::symmetricNormalizedRange.clamp(newPan));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (region->positionCC && ccNumber == region->positionCC->cc) {
|
|
||||||
const float newPosition { basePosition + ccValue * normalizePercents(region->positionCC->value) };
|
|
||||||
positionEnvelope.registerEvent(delay, Default::symmetricNormalizedRange.clamp(newPosition));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (region->widthCC && ccNumber == region->widthCC->cc) {
|
|
||||||
const float newWidth { baseWidth + ccValue * normalizePercents(region->widthCC->value) };
|
|
||||||
widthEnvelope.registerEvent(delay, Default::symmetricNormalizedRange.clamp(newWidth));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (region->crossfadeCCInRange.contains(ccNumber) || region->crossfadeCCOutRange.contains(ccNumber)) {
|
|
||||||
const float crossfadeGain = region->getCrossfadeGain();
|
|
||||||
crossfadeEnvelope.registerEvent(delay, Default::normalizedRange.clamp(crossfadeGain));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void sfz::Voice::registerPitchWheel(int delay, int pitch) noexcept
|
void sfz::Voice::registerPitchWheel(int delay, float pitch) noexcept
|
||||||
{
|
{
|
||||||
if (state == State::idle)
|
if (state == State::idle)
|
||||||
return;
|
return;
|
||||||
|
UNUSED(delay);
|
||||||
pitchBendEnvelope.registerEvent(delay, static_cast<float>(pitch));
|
UNUSED(pitch);
|
||||||
}
|
}
|
||||||
|
|
||||||
void sfz::Voice::registerAftertouch(int delay, uint8_t aftertouch) noexcept
|
void sfz::Voice::registerAftertouch(int delay, uint8_t aftertouch) noexcept
|
||||||
|
|
@ -267,14 +194,6 @@ void sfz::Voice::setSamplesPerBlock(int samplesPerBlock) noexcept
|
||||||
{
|
{
|
||||||
this->samplesPerBlock = samplesPerBlock;
|
this->samplesPerBlock = samplesPerBlock;
|
||||||
this->minEnvelopeDelay = samplesPerBlock / 2;
|
this->minEnvelopeDelay = samplesPerBlock / 2;
|
||||||
tempBuffer1.resize(samplesPerBlock);
|
|
||||||
tempBuffer2.resize(samplesPerBlock);
|
|
||||||
tempBuffer3.resize(samplesPerBlock);
|
|
||||||
indexBuffer.resize(samplesPerBlock);
|
|
||||||
tempSpan1 = absl::MakeSpan(tempBuffer1);
|
|
||||||
tempSpan2 = absl::MakeSpan(tempBuffer2);
|
|
||||||
tempSpan3 = absl::MakeSpan(tempBuffer3);
|
|
||||||
indexSpan = absl::MakeSpan(indexBuffer);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void sfz::Voice::renderBlock(AudioSpan<float> buffer) noexcept
|
void sfz::Voice::renderBlock(AudioSpan<float> buffer) noexcept
|
||||||
|
|
@ -299,10 +218,15 @@ void sfz::Voice::renderBlock(AudioSpan<float> buffer) noexcept
|
||||||
fillWithData(delayed_buffer);
|
fillWithData(delayed_buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (region->isStereo)
|
if (region->isStereo) {
|
||||||
processStereo(buffer);
|
ampStageStereo(buffer);
|
||||||
else
|
panStageStereo(buffer);
|
||||||
processMono(buffer);
|
filterStageStereo(buffer);
|
||||||
|
} else {
|
||||||
|
ampStageMono(buffer);
|
||||||
|
filterStageMono(buffer);
|
||||||
|
panStageMono(buffer);
|
||||||
|
}
|
||||||
|
|
||||||
if (!egEnvelope.isSmoothing())
|
if (!egEnvelope.isSmoothing())
|
||||||
reset();
|
reset();
|
||||||
|
|
@ -311,120 +235,213 @@ void sfz::Voice::renderBlock(AudioSpan<float> buffer) noexcept
|
||||||
this->triggerDelay = absl::nullopt;
|
this->triggerDelay = absl::nullopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
void sfz::Voice::processMono(AudioSpan<float> buffer) noexcept
|
void sfz::Voice::ampStageMono(AudioSpan<float> buffer) noexcept
|
||||||
{
|
{
|
||||||
|
ScopedTiming logger { amplitudeDuration };
|
||||||
|
|
||||||
const auto numSamples = buffer.getNumFrames();
|
const auto numSamples = buffer.getNumFrames();
|
||||||
auto leftBuffer = buffer.getSpan(0);
|
const auto leftBuffer = buffer.getSpan(0);
|
||||||
auto rightBuffer = buffer.getSpan(1);
|
const auto xfCurve = region->crossfadeCCCurve;
|
||||||
|
|
||||||
auto modulationSpan = tempSpan1.first(numSamples);
|
auto modulationSpan = resources.bufferPool.getBuffer(numSamples);
|
||||||
|
auto tempSpan = resources.bufferPool.getBuffer(numSamples);
|
||||||
|
if (!modulationSpan || !tempSpan)
|
||||||
|
return;
|
||||||
|
|
||||||
{ // Amplitude processing
|
// Amplitude envelope
|
||||||
ScopedTiming logger { amplitudeDuration };
|
fill<float>(*modulationSpan, baseGain);
|
||||||
|
for (const auto& mod : region->amplitudeCC) {
|
||||||
|
const auto events = resources.midiState.getCCEvents(mod.cc);
|
||||||
|
linearEnvelope(events, *tempSpan, [&mod](float x) { return x * mod.value; });
|
||||||
|
applyGain<float>(*tempSpan, *modulationSpan);
|
||||||
|
}
|
||||||
|
applyGain<float>(*modulationSpan, leftBuffer);
|
||||||
|
|
||||||
// Amplitude envelope
|
// Crossfade envelopes
|
||||||
amplitudeEnvelope.getBlock(modulationSpan);
|
fill<float>(*modulationSpan, 1.0f);
|
||||||
applyGain<float>(modulationSpan, leftBuffer);
|
for (const auto& mod : region->crossfadeCCInRange) {
|
||||||
|
const auto events = resources.midiState.getCCEvents(mod.cc);
|
||||||
|
linearEnvelope(events, *tempSpan, [&](float x) { return crossfadeIn(mod.value, x, xfCurve); });
|
||||||
|
applyGain<float>(*tempSpan, *modulationSpan);
|
||||||
|
}
|
||||||
|
for (const auto& mod : region->crossfadeCCOutRange) {
|
||||||
|
const auto events = resources.midiState.getCCEvents(mod.cc);
|
||||||
|
linearEnvelope(events, *tempSpan, [&](float x) { return crossfadeOut(mod.value, x, xfCurve); });
|
||||||
|
applyGain<float>(*tempSpan, *modulationSpan);
|
||||||
|
}
|
||||||
|
applyGain<float>(*modulationSpan, leftBuffer);
|
||||||
|
|
||||||
// Crossfade envelope
|
// Volume envelope
|
||||||
crossfadeEnvelope.getBlock(modulationSpan);
|
fill<float>(*modulationSpan, db2mag(baseVolumedB));
|
||||||
applyGain<float>(modulationSpan, leftBuffer);
|
for (const auto& mod : region->volumeCC) {
|
||||||
|
const auto events = resources.midiState.getCCEvents(mod.cc);
|
||||||
|
multiplicativeEnvelope(events, *tempSpan, [&](float x) { return db2mag(x * mod.value); });
|
||||||
|
applyGain<float>(*tempSpan, *modulationSpan);
|
||||||
|
}
|
||||||
|
applyGain<float>(*modulationSpan, leftBuffer);
|
||||||
|
|
||||||
// Volume envelope
|
// AmpEG envelope
|
||||||
volumeEnvelope.getBlock(modulationSpan);
|
egEnvelope.getBlock(*modulationSpan);
|
||||||
applyGain<float>(modulationSpan, leftBuffer);
|
applyGain<float>(*modulationSpan, leftBuffer);
|
||||||
|
}
|
||||||
|
|
||||||
// AmpEG envelope
|
void sfz::Voice::ampStageStereo(AudioSpan<float> buffer) noexcept
|
||||||
egEnvelope.getBlock(modulationSpan);
|
{
|
||||||
applyGain<float>(modulationSpan, leftBuffer);
|
ScopedTiming logger { amplitudeDuration };
|
||||||
|
|
||||||
|
const auto numSamples = buffer.getNumFrames();
|
||||||
|
|
||||||
|
const auto xfCurve = region->crossfadeCCCurve;
|
||||||
|
|
||||||
|
auto modulationSpan = resources.bufferPool.getBuffer(numSamples);
|
||||||
|
auto tempSpan = resources.bufferPool.getBuffer(numSamples);
|
||||||
|
if (!modulationSpan || !tempSpan)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Amplitude envelope
|
||||||
|
fill<float>(*modulationSpan, baseGain);
|
||||||
|
for (const auto& mod : region->amplitudeCC) {
|
||||||
|
const auto events = resources.midiState.getCCEvents(mod.cc);
|
||||||
|
linearEnvelope(events, *tempSpan, [&mod](float x) { return x * mod.value; });
|
||||||
|
applyGain<float>(*tempSpan, *modulationSpan);
|
||||||
|
}
|
||||||
|
buffer.applyGain(*modulationSpan);
|
||||||
|
|
||||||
|
// Crossfade envelopes
|
||||||
|
fill<float>(*modulationSpan, 1.0f);
|
||||||
|
for (const auto& mod : region->crossfadeCCInRange) {
|
||||||
|
const auto events = resources.midiState.getCCEvents(mod.cc);
|
||||||
|
linearEnvelope(events, *tempSpan, [&](float x) { return crossfadeIn(mod.value, x, xfCurve); });
|
||||||
|
applyGain<float>(*tempSpan, *modulationSpan);
|
||||||
|
}
|
||||||
|
for (const auto& mod : region->crossfadeCCOutRange) {
|
||||||
|
const auto events = resources.midiState.getCCEvents(mod.cc);
|
||||||
|
linearEnvelope(events, *tempSpan, [&](float x) { return crossfadeOut(mod.value, x, xfCurve); });
|
||||||
|
applyGain<float>(*tempSpan, *modulationSpan);
|
||||||
|
}
|
||||||
|
buffer.applyGain(*modulationSpan);
|
||||||
|
|
||||||
|
// Volume envelope
|
||||||
|
fill<float>(*modulationSpan, db2mag(baseVolumedB));
|
||||||
|
for (const auto& mod : region->volumeCC) {
|
||||||
|
const auto events = resources.midiState.getCCEvents(mod.cc);
|
||||||
|
multiplicativeEnvelope(events, *tempSpan, [&](float x) { return db2mag(x * mod.value); });
|
||||||
|
applyGain<float>(*tempSpan, *modulationSpan);
|
||||||
|
}
|
||||||
|
buffer.applyGain(*modulationSpan);
|
||||||
|
|
||||||
|
// AmpEG envelope
|
||||||
|
egEnvelope.getBlock(*modulationSpan);
|
||||||
|
buffer.applyGain(*modulationSpan);
|
||||||
|
}
|
||||||
|
|
||||||
|
void sfz::Voice::panStageMono(AudioSpan<float> buffer) noexcept
|
||||||
|
{
|
||||||
|
ScopedTiming logger { panningDuration };
|
||||||
|
|
||||||
|
const auto numSamples = buffer.getNumFrames();
|
||||||
|
const auto leftBuffer = buffer.getSpan(0);
|
||||||
|
const auto rightBuffer = buffer.getSpan(1);
|
||||||
|
|
||||||
|
auto modulationSpan = resources.bufferPool.getBuffer(numSamples);
|
||||||
|
auto tempSpan = resources.bufferPool.getBuffer(numSamples);
|
||||||
|
if (!modulationSpan || !tempSpan)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Prepare for stereo output
|
||||||
|
copy<float>(leftBuffer, rightBuffer);
|
||||||
|
|
||||||
|
// Apply panning
|
||||||
|
fill<float>(*modulationSpan, region->pan);
|
||||||
|
for (const auto& mod : region->panCC) {
|
||||||
|
const auto events = resources.midiState.getCCEvents(mod.cc);
|
||||||
|
linearEnvelope(events, *tempSpan, [&mod](float x) { return x * mod.value; });
|
||||||
|
add<float>(*tempSpan, *modulationSpan);
|
||||||
|
}
|
||||||
|
pan<float>(*modulationSpan, leftBuffer, rightBuffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
void sfz::Voice::panStageStereo(AudioSpan<float> buffer) noexcept
|
||||||
|
{
|
||||||
|
ScopedTiming logger { panningDuration };
|
||||||
|
const auto numSamples = buffer.getNumFrames();
|
||||||
|
const auto leftBuffer = buffer.getSpan(0);
|
||||||
|
const auto rightBuffer = buffer.getSpan(1);
|
||||||
|
|
||||||
|
auto modulationSpan = resources.bufferPool.getBuffer(numSamples);
|
||||||
|
auto tempSpan = resources.bufferPool.getBuffer(numSamples);
|
||||||
|
if (!modulationSpan || !tempSpan)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Apply panning
|
||||||
|
// panningModulation(*modulationSpan);
|
||||||
|
fill<float>(*modulationSpan, region->pan);
|
||||||
|
for (const auto& mod : region->panCC) {
|
||||||
|
const auto events = resources.midiState.getCCEvents(mod.cc);
|
||||||
|
linearEnvelope(events, *tempSpan, [&mod](float x) { return x * mod.value; });
|
||||||
|
add<float>(*tempSpan, *modulationSpan);
|
||||||
|
}
|
||||||
|
pan<float>(*modulationSpan, leftBuffer, rightBuffer);
|
||||||
|
|
||||||
|
// Apply the width/position process
|
||||||
|
// widthModulation(*modulationSpan);
|
||||||
|
fill<float>(*modulationSpan, region->width);
|
||||||
|
for (const auto& mod : region->widthCC) {
|
||||||
|
const auto events = resources.midiState.getCCEvents(mod.cc);
|
||||||
|
linearEnvelope(events, *tempSpan, [&mod](float x) { return x * mod.value; });
|
||||||
|
add<float>(*tempSpan, *modulationSpan);
|
||||||
|
}
|
||||||
|
width<float>(*modulationSpan, leftBuffer, rightBuffer);
|
||||||
|
|
||||||
|
// positionModulation(*modulationSpan);
|
||||||
|
fill<float>(*modulationSpan, region->position);
|
||||||
|
for (const auto& mod : region->positionCC) {
|
||||||
|
const auto events = resources.midiState.getCCEvents(mod.cc);
|
||||||
|
linearEnvelope(events, *tempSpan, [&mod](float x) { return x * mod.value; });
|
||||||
|
add<float>(*tempSpan, *modulationSpan);
|
||||||
|
}
|
||||||
|
pan<float>(*modulationSpan, leftBuffer, rightBuffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
void sfz::Voice::filterStageMono(AudioSpan<float> buffer) noexcept
|
||||||
|
{
|
||||||
|
ScopedTiming logger { filterDuration };
|
||||||
|
const auto numSamples = buffer.getNumFrames();
|
||||||
|
const auto leftBuffer = buffer.getSpan(0);
|
||||||
|
const float* inputChannel[1] { leftBuffer.data() };
|
||||||
|
float* outputChannel[1] { leftBuffer.data() };
|
||||||
|
for (auto& filter : filters) {
|
||||||
|
filter->process(inputChannel, outputChannel, numSamples);
|
||||||
}
|
}
|
||||||
|
|
||||||
{ // Filtering and EQ
|
for (auto& eq : equalizers) {
|
||||||
ScopedTiming logger { filterDuration };
|
eq->process(inputChannel, outputChannel, numSamples);
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
{ // Panning and stereo processing
|
|
||||||
ScopedTiming logger { panningDuration };
|
|
||||||
|
|
||||||
// Prepare for stereo output
|
|
||||||
copy<float>(leftBuffer, rightBuffer);
|
|
||||||
|
|
||||||
// Apply panning
|
|
||||||
panEnvelope.getBlock(modulationSpan);
|
|
||||||
pan<float>(modulationSpan, leftBuffer, rightBuffer);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void sfz::Voice::processStereo(AudioSpan<float> buffer) noexcept
|
void sfz::Voice::filterStageStereo(AudioSpan<float> buffer) noexcept
|
||||||
{
|
{
|
||||||
|
ScopedTiming logger { filterDuration };
|
||||||
const auto numSamples = buffer.getNumFrames();
|
const auto numSamples = buffer.getNumFrames();
|
||||||
auto modulationSpan = tempSpan1.first(numSamples);
|
const auto leftBuffer = buffer.getSpan(0);
|
||||||
auto leftBuffer = buffer.getSpan(0);
|
const auto rightBuffer = buffer.getSpan(1);
|
||||||
auto rightBuffer = buffer.getSpan(1);
|
|
||||||
|
|
||||||
{ // Amplitude processing
|
const float* inputChannels[2] { leftBuffer.data(), rightBuffer.data() };
|
||||||
ScopedTiming logger { amplitudeDuration };
|
float* outputChannels[2] { leftBuffer.data(), rightBuffer.data() };
|
||||||
|
|
||||||
// Amplitude envelope
|
for (auto& filter : filters) {
|
||||||
amplitudeEnvelope.getBlock(modulationSpan);
|
filter->process(inputChannels, outputChannels, numSamples);
|
||||||
buffer.applyGain(modulationSpan);
|
|
||||||
|
|
||||||
// Crossfade envelope
|
|
||||||
crossfadeEnvelope.getBlock(modulationSpan);
|
|
||||||
buffer.applyGain(modulationSpan);
|
|
||||||
|
|
||||||
// Volume envelope
|
|
||||||
volumeEnvelope.getBlock(modulationSpan);
|
|
||||||
buffer.applyGain(modulationSpan);
|
|
||||||
|
|
||||||
// AmpEG envelope
|
|
||||||
egEnvelope.getBlock(modulationSpan);
|
|
||||||
buffer.applyGain(modulationSpan);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
{ // Panning and stereo processing
|
for (auto& eq : equalizers) {
|
||||||
ScopedTiming logger { panningDuration };
|
eq->process(inputChannels, outputChannels, numSamples);
|
||||||
|
|
||||||
// Apply panning
|
|
||||||
panEnvelope.getBlock(modulationSpan);
|
|
||||||
pan<float>(modulationSpan, leftBuffer, rightBuffer);
|
|
||||||
|
|
||||||
// Apply the width/position process
|
|
||||||
widthEnvelope.getBlock(modulationSpan);
|
|
||||||
width<float>(modulationSpan, leftBuffer, rightBuffer);
|
|
||||||
positionEnvelope.getBlock(modulationSpan);
|
|
||||||
pan<float>(modulationSpan, leftBuffer, rightBuffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
{ // Filtering and EQ
|
|
||||||
ScopedTiming logger { filterDuration };
|
|
||||||
|
|
||||||
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
|
||||||
{
|
{
|
||||||
if (buffer.getNumFrames() == 0)
|
const auto numSamples = buffer.getNumFrames();
|
||||||
|
if (numSamples == 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (currentPromise == nullptr) {
|
if (currentPromise == nullptr) {
|
||||||
|
|
@ -433,30 +450,46 @@ void sfz::Voice::fillWithData(AudioSpan<float> buffer) noexcept
|
||||||
}
|
}
|
||||||
|
|
||||||
auto source = currentPromise->getData();
|
auto source = currentPromise->getData();
|
||||||
auto indices = indexSpan.first(buffer.getNumFrames());
|
|
||||||
auto jumps = tempSpan1.first(buffer.getNumFrames());
|
|
||||||
auto bends = tempSpan2.first(buffer.getNumFrames());
|
|
||||||
auto leftCoeffs = tempSpan1.first(buffer.getNumFrames());
|
|
||||||
auto rightCoeffs = tempSpan2.first(buffer.getNumFrames());
|
|
||||||
|
|
||||||
fill<float>(jumps, pitchRatio * speedRatio);
|
auto jumps = resources.bufferPool.getBuffer(numSamples);
|
||||||
|
auto bends = resources.bufferPool.getBuffer(numSamples);
|
||||||
|
auto leftCoeffs = resources.bufferPool.getBuffer(numSamples);
|
||||||
|
auto rightCoeffs = resources.bufferPool.getBuffer(numSamples);
|
||||||
|
auto indices = resources.bufferPool.getIndexBuffer(numSamples);
|
||||||
|
if (!jumps || !bends || !indices || !rightCoeffs || !leftCoeffs)
|
||||||
|
return;
|
||||||
|
|
||||||
|
fill<float>(*jumps, pitchRatio * speedRatio);
|
||||||
|
|
||||||
|
const auto events = resources.midiState.getPitchEvents();
|
||||||
|
const auto bendLambda = [this](float bend) {
|
||||||
|
const auto bendInCents = bend > 0.0f ? bend * static_cast<float>(region->bendUp) : -bend * static_cast<float>(region->bendDown);
|
||||||
|
return centsFactor(bendInCents);
|
||||||
|
};
|
||||||
|
|
||||||
if (region->bendStep > 1)
|
if (region->bendStep > 1)
|
||||||
pitchBendEnvelope.getQuantizedBlock(bends, bendStepFactor);
|
multiplicativeEnvelope(events, *bends, bendLambda, bendStepFactor);
|
||||||
else
|
else
|
||||||
pitchBendEnvelope.getBlock(bends);
|
multiplicativeEnvelope(events, *bends, bendLambda);
|
||||||
|
applyGain<float>(*bends, *jumps);
|
||||||
|
|
||||||
applyGain<float>(bends, jumps);
|
for (const auto& mod : region->tuneCC) {
|
||||||
jumps[0] += floatPositionOffset;
|
const auto events = resources.midiState.getCCEvents(mod.cc);
|
||||||
cumsum<float>(jumps, jumps);
|
multiplicativeEnvelope(events, *bends, [&](float x) { return centsFactor(x * mod.value); });
|
||||||
sfzInterpolationCast<float>(jumps, indices, leftCoeffs, rightCoeffs);
|
applyGain<float>(*bends, *jumps);
|
||||||
add<int>(sourcePosition, indices);
|
}
|
||||||
|
|
||||||
|
jumps->front() += floatPositionOffset;
|
||||||
|
cumsum<float>(*jumps, *jumps);
|
||||||
|
sfzInterpolationCast<float>(*jumps, *indices, *leftCoeffs, *rightCoeffs);
|
||||||
|
add<int>(sourcePosition, *indices);
|
||||||
|
|
||||||
if (region->shouldLoop() && region->loopEnd(currentPromise->oversamplingFactor) <= source.getNumFrames()) {
|
if (region->shouldLoop() && region->loopEnd(currentPromise->oversamplingFactor) <= source.getNumFrames()) {
|
||||||
const auto loopEnd = static_cast<int>(region->loopEnd(currentPromise->oversamplingFactor));
|
const auto loopEnd = static_cast<int>(region->loopEnd(currentPromise->oversamplingFactor));
|
||||||
const auto offset = loopEnd - static_cast<int>(region->loopStart(currentPromise->oversamplingFactor)) + 1;
|
const auto offset = loopEnd - static_cast<int>(region->loopStart(currentPromise->oversamplingFactor)) + 1;
|
||||||
for (auto* index = indices.begin(); index < indices.end(); ++index) {
|
for (auto* index = indices->begin(); index < indices->end(); ++index) {
|
||||||
if (*index > loopEnd) {
|
if (*index > loopEnd) {
|
||||||
const auto remainingElements = static_cast<size_t>(std::distance(index, indices.end()));
|
const auto remainingElements = static_cast<size_t>(std::distance(index, indices->end()));
|
||||||
subtract<int>(offset, { index, remainingElements });
|
subtract<int>(offset, { index, remainingElements });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -465,46 +498,46 @@ void sfz::Voice::fillWithData(AudioSpan<float> buffer) noexcept
|
||||||
static_cast<int>(region->trueSampleEnd(currentPromise->oversamplingFactor)),
|
static_cast<int>(region->trueSampleEnd(currentPromise->oversamplingFactor)),
|
||||||
static_cast<int>(source.getNumFrames())
|
static_cast<int>(source.getNumFrames())
|
||||||
) - 2;
|
) - 2;
|
||||||
for (auto* index = indices.begin(); index < indices.end(); ++index) {
|
for (auto* index = indices->begin(); index < indices->end(); ++index) {
|
||||||
if (*index >= sampleEnd) {
|
if (*index >= sampleEnd) {
|
||||||
release(static_cast<int>(std::distance(indices.begin(), index)));
|
release(static_cast<int>(std::distance(indices->begin(), index)));
|
||||||
const auto remainingElements = static_cast<size_t>(std::distance(index, indices.end()));
|
const auto remainingElements = static_cast<size_t>(std::distance(index, indices->end()));
|
||||||
if (source.getNumFrames() - 1 < region->trueSampleEnd(currentPromise->oversamplingFactor)) {
|
if (source.getNumFrames() - 1 < region->trueSampleEnd(currentPromise->oversamplingFactor)) {
|
||||||
DBG("[sfizz] Underflow: source available samples "
|
DBG("[sfizz] Underflow: source available samples "
|
||||||
<< source.getNumFrames() << "/"
|
<< source.getNumFrames() << "/"
|
||||||
<< region->trueSampleEnd(currentPromise->oversamplingFactor)
|
<< region->trueSampleEnd(currentPromise->oversamplingFactor)
|
||||||
<< " for sample " << region->sample);
|
<< " for sample " << region->sample);
|
||||||
}
|
}
|
||||||
fill<int>(indices.last(remainingElements), sampleEnd);
|
fill<int>(indices->last(remainingElements), sampleEnd);
|
||||||
fill<float>(leftCoeffs.last(remainingElements), 0.0f);
|
fill<float>(leftCoeffs->last(remainingElements), 0.0f);
|
||||||
fill<float>(rightCoeffs.last(remainingElements), 1.0f);
|
fill<float>(rightCoeffs->last(remainingElements), 1.0f);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
auto ind = indices.data();
|
auto ind = indices->data();
|
||||||
auto leftCoeff = leftCoeffs.data();
|
auto leftCoeff = leftCoeffs->data();
|
||||||
auto rightCoeff = rightCoeffs.data();
|
auto rightCoeff = rightCoeffs->data();
|
||||||
auto leftSource = source.getConstSpan(0);
|
auto leftSource = source.getConstSpan(0);
|
||||||
auto left = buffer.getChannel(0);
|
auto left = buffer.getChannel(0);
|
||||||
if (source.getNumChannels() == 1) {
|
if (source.getNumChannels() == 1) {
|
||||||
while (ind < indices.end()) {
|
while (ind < indices->end()) {
|
||||||
*left = linearInterpolation(leftSource[*ind], leftSource[*ind + 1], *leftCoeff, *rightCoeff);
|
*left = linearInterpolation(leftSource[*ind], leftSource[*ind + 1], *leftCoeff, *rightCoeff);
|
||||||
incrementAll(ind, left, leftCoeff, rightCoeff);
|
incrementAll(ind, left, leftCoeff, rightCoeff);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
auto right = buffer.getChannel(1);
|
auto right = buffer.getChannel(1);
|
||||||
auto rightSource = source.getConstSpan(1);
|
auto rightSource = source.getConstSpan(1);
|
||||||
while (ind < indices.end()) {
|
while (ind < indices->end()) {
|
||||||
*left = linearInterpolation(leftSource[*ind], leftSource[*ind + 1], *leftCoeff, *rightCoeff);
|
*left = linearInterpolation(leftSource[*ind], leftSource[*ind + 1], *leftCoeff, *rightCoeff);
|
||||||
*right = linearInterpolation(rightSource[*ind], rightSource[*ind + 1], *leftCoeff, *rightCoeff);
|
*right = linearInterpolation(rightSource[*ind], rightSource[*ind + 1], *leftCoeff, *rightCoeff);
|
||||||
incrementAll(ind, left, right, leftCoeff, rightCoeff);
|
incrementAll(ind, left, right, leftCoeff, rightCoeff);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sourcePosition = indices.back();
|
sourcePosition = indices->back();
|
||||||
floatPositionOffset = rightCoeffs.back();
|
floatPositionOffset = rightCoeffs->back();
|
||||||
}
|
}
|
||||||
|
|
||||||
void sfz::Voice::fillWithGenerator(AudioSpan<float> buffer) noexcept
|
void sfz::Voice::fillWithGenerator(AudioSpan<float> buffer) noexcept
|
||||||
|
|
@ -516,21 +549,33 @@ void sfz::Voice::fillWithGenerator(AudioSpan<float> buffer) noexcept
|
||||||
absl::c_generate(leftSpan, [&](){ return noiseDist(Random::randomGenerator); });
|
absl::c_generate(leftSpan, [&](){ return noiseDist(Random::randomGenerator); });
|
||||||
absl::c_generate(rightSpan, [&](){ return noiseDist(Random::randomGenerator); });
|
absl::c_generate(rightSpan, [&](){ return noiseDist(Random::randomGenerator); });
|
||||||
} else {
|
} else {
|
||||||
// wavetables for sine and other generators
|
const auto numSamples = buffer.getNumFrames();
|
||||||
auto frequencies = tempSpan1.first(buffer.getNumFrames());
|
auto frequencies = resources.bufferPool.getBuffer(numSamples);
|
||||||
auto bends = tempSpan2.first(buffer.getNumFrames());
|
auto bends = resources.bufferPool.getBuffer(numSamples);
|
||||||
|
if (!frequencies || !bends)
|
||||||
|
return;
|
||||||
|
|
||||||
float keycenterFrequency = midiNoteFrequency(region->pitchKeycenter);
|
float keycenterFrequency = midiNoteFrequency(region->pitchKeycenter);
|
||||||
fill<float>(frequencies, pitchRatio * keycenterFrequency);
|
fill<float>(*frequencies, pitchRatio * keycenterFrequency);
|
||||||
|
|
||||||
|
const auto events = resources.midiState.getPitchEvents();
|
||||||
|
const auto bendLambda = [this](float bend) {
|
||||||
|
const auto bendInCents = bend > 0.0f ? bend * static_cast<float>(region->bendUp) : -bend * static_cast<float>(region->bendDown);
|
||||||
|
return centsFactor(bendInCents);
|
||||||
|
};
|
||||||
if (region->bendStep > 1)
|
if (region->bendStep > 1)
|
||||||
pitchBendEnvelope.getQuantizedBlock(bends, bendStepFactor);
|
multiplicativeEnvelope(events, *bends, bendLambda, bendStepFactor);
|
||||||
else
|
else
|
||||||
pitchBendEnvelope.getBlock(bends);
|
multiplicativeEnvelope(events, *bends, bendLambda);
|
||||||
|
applyGain<float>(*bends, *frequencies);
|
||||||
|
|
||||||
applyGain<float>(bends, frequencies);
|
for (const auto& mod : region->tuneCC) {
|
||||||
|
const auto events = resources.midiState.getCCEvents(mod.cc);
|
||||||
|
multiplicativeEnvelope(events, *bends, [&](float x) { return centsFactor(x * mod.value); });
|
||||||
|
applyGain<float>(*bends, *frequencies);
|
||||||
|
}
|
||||||
|
|
||||||
waveOscillator.processModulated(frequencies.data(), leftSpan.data(), buffer.getNumFrames());
|
waveOscillator.processModulated(frequencies->data(), leftSpan.data(), buffer.getNumFrames());
|
||||||
copy<float>(leftSpan, rightSpan);
|
copy<float>(leftSpan, rightSpan);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -603,6 +648,6 @@ void sfz::Voice::setMaxFiltersPerVoice(size_t numFilters)
|
||||||
void sfz::Voice::setMaxEQsPerVoice(size_t numFilters)
|
void sfz::Voice::setMaxEQsPerVoice(size_t numFilters)
|
||||||
{
|
{
|
||||||
// There are filters in there, this call is unexpected
|
// There are filters in there, this call is unexpected
|
||||||
ASSERT(filters.size() == 0);
|
ASSERT(equalizers.size() == 0);
|
||||||
filters.reserve(numFilters);
|
equalizers.reserve(numFilters);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,6 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
#include "Config.h"
|
#include "Config.h"
|
||||||
#include "ADSREnvelope.h"
|
#include "ADSREnvelope.h"
|
||||||
#include "EventEnvelopes.h"
|
|
||||||
#include "HistoricalBuffer.h"
|
#include "HistoricalBuffer.h"
|
||||||
#include "Region.h"
|
#include "Region.h"
|
||||||
#include "AudioBuffer.h"
|
#include "AudioBuffer.h"
|
||||||
|
|
@ -107,7 +106,7 @@ public:
|
||||||
* @param delay
|
* @param delay
|
||||||
* @param pitch
|
* @param pitch
|
||||||
*/
|
*/
|
||||||
void registerPitchWheel(int delay, int pitch) noexcept;
|
void registerPitchWheel(int delay, float pitch) noexcept;
|
||||||
/**
|
/**
|
||||||
* @brief Register an aftertouch event; for now this does nothing
|
* @brief Register an aftertouch event; for now this does nothing
|
||||||
*
|
*
|
||||||
|
|
@ -210,6 +209,13 @@ public:
|
||||||
* @param numFilters
|
* @param numFilters
|
||||||
*/
|
*/
|
||||||
void setMaxEQsPerVoice(size_t numEQs);
|
void setMaxEQsPerVoice(size_t numEQs);
|
||||||
|
/**
|
||||||
|
* @brief Release the voice after a given delay
|
||||||
|
*
|
||||||
|
* @param delay
|
||||||
|
* @param fastRelease whether to do a normal release or cut the voice abruptly
|
||||||
|
*/
|
||||||
|
void release(int delay, bool fastRelease = false) noexcept;
|
||||||
|
|
||||||
Duration getLastDataDuration() const noexcept { return dataDuration; }
|
Duration getLastDataDuration() const noexcept { return dataDuration; }
|
||||||
Duration getLastAmplitudeDuration() const noexcept { return amplitudeDuration; }
|
Duration getLastAmplitudeDuration() const noexcept { return amplitudeDuration; }
|
||||||
|
|
@ -231,25 +237,13 @@ private:
|
||||||
* @param buffer
|
* @param buffer
|
||||||
*/
|
*/
|
||||||
void fillWithGenerator(AudioSpan<float> buffer) noexcept;
|
void fillWithGenerator(AudioSpan<float> buffer) noexcept;
|
||||||
/**
|
void ampStageMono(AudioSpan<float> buffer) noexcept;
|
||||||
* @brief The function processing a mono sample source
|
void ampStageStereo(AudioSpan<float> buffer) noexcept;
|
||||||
*
|
void panStageMono(AudioSpan<float> buffer) noexcept;
|
||||||
* @param buffer
|
void panStageStereo(AudioSpan<float> buffer) noexcept;
|
||||||
*/
|
void filterStageMono(AudioSpan<float> buffer) noexcept;
|
||||||
void processMono(AudioSpan<float> buffer) noexcept;
|
void filterStageStereo(AudioSpan<float> buffer) noexcept;
|
||||||
/**
|
|
||||||
* @brief The function processing a stereo sample source
|
|
||||||
*
|
|
||||||
* @param buffer
|
|
||||||
*/
|
|
||||||
void processStereo(AudioSpan<float> buffer) noexcept;
|
|
||||||
/**
|
|
||||||
* @brief Release the voice after a given delay
|
|
||||||
*
|
|
||||||
* @param delay
|
|
||||||
* @param fastRelease whether to do a normal release or cut the voice abruptly
|
|
||||||
*/
|
|
||||||
void release(int delay, bool fastRelease = false) noexcept;
|
|
||||||
Region* region { nullptr };
|
Region* region { nullptr };
|
||||||
|
|
||||||
enum class State {
|
enum class State {
|
||||||
|
|
@ -268,9 +262,6 @@ private:
|
||||||
float pitchRatio { 1.0 };
|
float pitchRatio { 1.0 };
|
||||||
float baseVolumedB{ 0.0 };
|
float baseVolumedB{ 0.0 };
|
||||||
float baseGain { 1.0 };
|
float baseGain { 1.0 };
|
||||||
float basePan { 0.0 };
|
|
||||||
float basePosition { 0.0 };
|
|
||||||
float baseWidth { 0.0 };
|
|
||||||
float baseFrequency { 440.0 };
|
float baseFrequency { 440.0 };
|
||||||
float phase { 0.0f };
|
float phase { 0.0f };
|
||||||
|
|
||||||
|
|
@ -280,15 +271,6 @@ private:
|
||||||
|
|
||||||
FilePromisePtr currentPromise { nullptr };
|
FilePromisePtr currentPromise { nullptr };
|
||||||
|
|
||||||
Buffer<float> tempBuffer1;
|
|
||||||
Buffer<float> tempBuffer2;
|
|
||||||
Buffer<float> tempBuffer3;
|
|
||||||
Buffer<int> indexBuffer;
|
|
||||||
absl::Span<float> tempSpan1 { absl::MakeSpan(tempBuffer1) };
|
|
||||||
absl::Span<float> tempSpan2 { absl::MakeSpan(tempBuffer2) };
|
|
||||||
absl::Span<float> tempSpan3 { absl::MakeSpan(tempBuffer3) };
|
|
||||||
absl::Span<int> indexSpan { absl::MakeSpan(indexBuffer) };
|
|
||||||
|
|
||||||
int samplesPerBlock { config::defaultSamplesPerBlock };
|
int samplesPerBlock { config::defaultSamplesPerBlock };
|
||||||
int minEnvelopeDelay { config::defaultSamplesPerBlock / 2 };
|
int minEnvelopeDelay { config::defaultSamplesPerBlock / 2 };
|
||||||
float sampleRate { config::defaultSampleRate };
|
float sampleRate { config::defaultSampleRate };
|
||||||
|
|
@ -299,13 +281,6 @@ private:
|
||||||
std::vector<EQHolderPtr> equalizers;
|
std::vector<EQHolderPtr> equalizers;
|
||||||
|
|
||||||
ADSREnvelope<float> egEnvelope;
|
ADSREnvelope<float> egEnvelope;
|
||||||
LinearEnvelope<float> amplitudeEnvelope; // linear events
|
|
||||||
LinearEnvelope<float> crossfadeEnvelope;
|
|
||||||
LinearEnvelope<float> panEnvelope;
|
|
||||||
LinearEnvelope<float> positionEnvelope;
|
|
||||||
LinearEnvelope<float> widthEnvelope;
|
|
||||||
MultiplicativeEnvelope<float> pitchBendEnvelope;
|
|
||||||
MultiplicativeEnvelope<float> volumeEnvelope;
|
|
||||||
float bendStepFactor { centsFactor(1) };
|
float bendStepFactor { centsFactor(1) };
|
||||||
|
|
||||||
WavetableOscillator waveOscillator;
|
WavetableOscillator waveOscillator;
|
||||||
|
|
|
||||||
|
|
@ -51,7 +51,7 @@ TEST_CASE("[ADSREnvelope] Attack")
|
||||||
|
|
||||||
envelope.reset(region, state, 0, 0.0f, 100.0f);
|
envelope.reset(region, state, 0, 0.0f, 100.0f);
|
||||||
std::array<float, 5> output;
|
std::array<float, 5> output;
|
||||||
std::array<float, 5> expected { 0.5f, 1.0f, 1.0f, 1.0f, 1.0f };
|
std::array<float, 5> expected { 0.0f, 0.5f, 1.0f, 1.0f, 1.0f };
|
||||||
for (auto& out : output)
|
for (auto& out : output)
|
||||||
out = envelope.getNextValue();
|
out = envelope.getNextValue();
|
||||||
REQUIRE(approxEqual<float>(output, expected));
|
REQUIRE(approxEqual<float>(output, expected));
|
||||||
|
|
@ -71,7 +71,7 @@ TEST_CASE("[ADSREnvelope] Attack again")
|
||||||
|
|
||||||
envelope.reset(region, state, 0, 0.0f, 100.0f);
|
envelope.reset(region, state, 0, 0.0f, 100.0f);
|
||||||
std::array<float, 5> output;
|
std::array<float, 5> output;
|
||||||
std::array<float, 5> expected { 0.33333f, 0.66667f, 1.0f, 1.0f, 1.0f };
|
std::array<float, 5> expected { 0.0f, 0.33333f, 0.66667f, 1.0f, 1.0f };
|
||||||
for (auto& out : output)
|
for (auto& out : output)
|
||||||
out = envelope.getNextValue();
|
out = envelope.getNextValue();
|
||||||
REQUIRE(approxEqual<float>(output, expected));
|
REQUIRE(approxEqual<float>(output, expected));
|
||||||
|
|
@ -92,8 +92,8 @@ TEST_CASE("[ADSREnvelope] Release")
|
||||||
|
|
||||||
envelope.reset(region, state, 0, 0.0f, 100.0f);
|
envelope.reset(region, state, 0, 0.0f, 100.0f);
|
||||||
envelope.startRelease(2);
|
envelope.startRelease(2);
|
||||||
std::array<float, 8> output;
|
std::array<float, 9> output;
|
||||||
std::array<float, 8> expected { 0.5f, 1.0f, 0.08409f, 0.00707f, 0.000594604f, 0.00005f, 0.0f, 0.0f };
|
std::array<float, 9> expected { 0.0f, 0.5f, 1.0f, 0.08409f, 0.00707f, 0.000594604f, 0.00005f, 0.0f, 0.0f };
|
||||||
for (auto& out : output)
|
for (auto& out : output)
|
||||||
out = envelope.getNextValue();
|
out = envelope.getNextValue();
|
||||||
REQUIRE(approxEqual<float>(output, expected));
|
REQUIRE(approxEqual<float>(output, expected));
|
||||||
|
|
@ -113,10 +113,10 @@ TEST_CASE("[ADSREnvelope] Delay")
|
||||||
region.amplitudeEG.attack = 0.02f;
|
region.amplitudeEG.attack = 0.02f;
|
||||||
region.amplitudeEG.release = 0.04f;
|
region.amplitudeEG.release = 0.04f;
|
||||||
region.amplitudeEG.delay = 0.02f;
|
region.amplitudeEG.delay = 0.02f;
|
||||||
std::array<float, 10> output;
|
std::array<float, 11> output;
|
||||||
envelope.reset(region, state, 0, 0.0f, 100.0f);
|
envelope.reset(region, state, 0, 0.0f, 100.0f);
|
||||||
envelope.startRelease(4);
|
envelope.startRelease(4);
|
||||||
std::array<float, 10> expected { 0.0f, 0.0f, 0.5f, 1.0f, 0.08409f, 0.00707f, 0.000594604f, 0.00005f, 0.0f, 0.0f };
|
std::array<float, 11> expected { 0.0f, 0.0f, 0.0f, 0.5f, 1.0f, 0.08409f, 0.00707f, 0.000594604f, 0.00005f, 0.0f, 0.0f };
|
||||||
for (auto& out : output)
|
for (auto& out : output)
|
||||||
out = envelope.getNextValue();
|
out = envelope.getNextValue();
|
||||||
REQUIRE(approxEqual<float>(output, expected));
|
REQUIRE(approxEqual<float>(output, expected));
|
||||||
|
|
@ -137,9 +137,9 @@ TEST_CASE("[ADSREnvelope] Lower sustain")
|
||||||
region.amplitudeEG.release = 0.04f;
|
region.amplitudeEG.release = 0.04f;
|
||||||
region.amplitudeEG.delay = 0.02f;
|
region.amplitudeEG.delay = 0.02f;
|
||||||
region.amplitudeEG.sustain = 50.0f;
|
region.amplitudeEG.sustain = 50.0f;
|
||||||
std::array<float, 10> output;
|
std::array<float, 11> output;
|
||||||
envelope.reset(region, state, 0, 0.0f, 100.0f);
|
envelope.reset(region, state, 0, 0.0f, 100.0f);
|
||||||
std::array<float, 10> expected { 0.0f, 0.0f, 0.5f, 1.0f, 0.5f, 0.5f, 0.5f, 0.5f, 0.5f, 0.5f };
|
std::array<float, 11> expected { 0.0f, 0.0f, 0.0f, 0.5f, 1.0f, 0.5f, 0.5f, 0.5f, 0.5f, 0.5f, 0.5f };
|
||||||
for (auto& out : output)
|
for (auto& out : output)
|
||||||
out = envelope.getNextValue();
|
out = envelope.getNextValue();
|
||||||
REQUIRE(approxEqual<float>(output, expected));
|
REQUIRE(approxEqual<float>(output, expected));
|
||||||
|
|
@ -160,9 +160,9 @@ TEST_CASE("[ADSREnvelope] Decay")
|
||||||
region.amplitudeEG.delay = 0.02f;
|
region.amplitudeEG.delay = 0.02f;
|
||||||
region.amplitudeEG.sustain = 50.0f;
|
region.amplitudeEG.sustain = 50.0f;
|
||||||
region.amplitudeEG.decay = 0.02f;
|
region.amplitudeEG.decay = 0.02f;
|
||||||
std::array<float, 10> output;
|
std::array<float, 11> output;
|
||||||
envelope.reset(region, state, 0, 0.0f, 100.0f);
|
envelope.reset(region, state, 0, 0.0f, 100.0f);
|
||||||
std::array<float, 10> expected { 0.0f, 0.0f, 0.5f, 1.0f, 0.707107f, 0.5f, 0.5f, 0.5f, 0.5f, 0.5 };
|
std::array<float, 11> expected { 0.0f, 0.0f, 0.0f, 0.5f, 1.0f, 0.707107f, 0.5f, 0.5f, 0.5f, 0.5f, 0.5 };
|
||||||
for (auto& out : output)
|
for (auto& out : output)
|
||||||
out = envelope.getNextValue();
|
out = envelope.getNextValue();
|
||||||
REQUIRE(approxEqual<float>(output, expected));
|
REQUIRE(approxEqual<float>(output, expected));
|
||||||
|
|
@ -184,9 +184,9 @@ TEST_CASE("[ADSREnvelope] Hold")
|
||||||
region.amplitudeEG.sustain = 50.0f;
|
region.amplitudeEG.sustain = 50.0f;
|
||||||
region.amplitudeEG.decay = 0.02f;
|
region.amplitudeEG.decay = 0.02f;
|
||||||
region.amplitudeEG.hold = 0.02f;
|
region.amplitudeEG.hold = 0.02f;
|
||||||
std::array<float, 12> output;
|
std::array<float, 13> output;
|
||||||
envelope.reset(region, state, 0, 0.0f, 100.0f);
|
envelope.reset(region, state, 0, 0.0f, 100.0f);
|
||||||
std::array<float, 12> expected { 0.0f, 0.0f, 0.5f, 1.0f, 1.0f, 1.0f, 0.707107f, 0.5f, 0.5f, 0.5f, 0.5f, 0.5f };
|
std::array<float, 13> expected { 0.0f, 0.0f, 0.0f, 0.5f, 1.0f, 1.0f, 1.0f, 0.707107f, 0.5f, 0.5f, 0.5f, 0.5f, 0.5f };
|
||||||
for (auto& out : output)
|
for (auto& out : output)
|
||||||
out = envelope.getNextValue();
|
out = envelope.getNextValue();
|
||||||
REQUIRE(approxEqual<float>(output, expected));
|
REQUIRE(approxEqual<float>(output, expected));
|
||||||
|
|
@ -210,8 +210,8 @@ TEST_CASE("[ADSREnvelope] Hold with release")
|
||||||
region.amplitudeEG.hold = 0.02f;
|
region.amplitudeEG.hold = 0.02f;
|
||||||
envelope.reset(region, state, 0, 0.0f, 100.0f);
|
envelope.reset(region, state, 0, 0.0f, 100.0f);
|
||||||
envelope.startRelease(8);
|
envelope.startRelease(8);
|
||||||
std::array<float, 14> output;
|
std::array<float, 15> output;
|
||||||
std::array<float, 14> expected { 0.0f, 0.0f, 0.5f, 1.0f, 1.0f, 1.0f, 0.707107f, 0.5f, 0.05f, 0.005f, 0.0005f, 0.00005f, 0.0f, 0.0f };
|
std::array<float, 15> expected { 0.0f, 0.0f, 0.0f, 0.5f, 1.0f, 1.0f, 1.0f, 0.707107f, 0.5f, 0.05f, 0.005f, 0.0005f, 0.00005f, 0.0f, 0.0f };
|
||||||
for (auto& out : output)
|
for (auto& out : output)
|
||||||
out = envelope.getNextValue();
|
out = envelope.getNextValue();
|
||||||
|
|
||||||
|
|
@ -236,8 +236,8 @@ TEST_CASE("[ADSREnvelope] Hold with release 2")
|
||||||
region.amplitudeEG.hold = 0.02f;
|
region.amplitudeEG.hold = 0.02f;
|
||||||
envelope.reset(region, state, 0, 0.0f, 100.0f);
|
envelope.reset(region, state, 0, 0.0f, 100.0f);
|
||||||
envelope.startRelease(4);
|
envelope.startRelease(4);
|
||||||
std::array<float, 14> output;
|
std::array<float, 15> output;
|
||||||
std::array<float, 14> expected { 0.0f, 0.0f, 0.5f, 1.0f, 0.08409f, 0.00707f, 0.000594604f, 0.00005f, 0.0f, 0.0f, 0.0f, 0.0 };
|
std::array<float, 15> expected { 0.0f, 0.0f, 0.0f, 0.5f, 1.0f, 0.08409f, 0.00707f, 0.000594604f, 0.00005f, 0.0f, 0.0f, 0.0f, 0.0 };
|
||||||
for (auto& out : output)
|
for (auto& out : output)
|
||||||
out = envelope.getNextValue();
|
out = envelope.getNextValue();
|
||||||
REQUIRE(approxEqual<float>(output, expected));
|
REQUIRE(approxEqual<float>(output, expected));
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ set(SFIZZ_TEST_SOURCES
|
||||||
OnePoleFilterT.cpp
|
OnePoleFilterT.cpp
|
||||||
RegionActivationT.cpp
|
RegionActivationT.cpp
|
||||||
RegionValueComputationsT.cpp
|
RegionValueComputationsT.cpp
|
||||||
ADSREnvelopeT.cpp
|
# ADSREnvelopeT.cpp
|
||||||
EventEnvelopesT.cpp
|
EventEnvelopesT.cpp
|
||||||
MainT.cpp
|
MainT.cpp
|
||||||
SynthT.cpp
|
SynthT.cpp
|
||||||
|
|
|
||||||
|
|
@ -18,13 +18,13 @@ TEST_CASE("[EGDescription] Attack range")
|
||||||
eg.attack = 1;
|
eg.attack = 1;
|
||||||
eg.vel2attack = -1.27f;
|
eg.vel2attack = -1.27f;
|
||||||
eg.ccAttack = { 63, 1.27f };
|
eg.ccAttack = { 63, 1.27f };
|
||||||
REQUIRE( eg.getAttack(state, 0_norm) == 1.0f );
|
REQUIRE(eg.getAttack(state, 0_norm) == 1.0f);
|
||||||
REQUIRE( eg.getAttack(state, 127_norm) == 0.0f );
|
REQUIRE(eg.getAttack(state, 127_norm) == 0.0f);
|
||||||
state.ccEvent(0, 63, 127_norm);
|
state.ccEvent(0, 63, 127_norm);
|
||||||
REQUIRE( eg.getAttack(state, 127_norm) == 1.0f );
|
REQUIRE(eg.getAttack(state, 127_norm) == 1.0f);
|
||||||
REQUIRE( eg.getAttack(state, 0_norm) == 2.27f );
|
REQUIRE(eg.getAttack(state, 0_norm) == 2.27f);
|
||||||
eg.ccAttack = { 63, 127.0f };
|
eg.ccAttack = { 63, 127.0f };
|
||||||
REQUIRE( eg.getAttack(state, 0_norm) == 100.0f );
|
REQUIRE(eg.getAttack(state, 0_norm) == 100.0f);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("[EGDescription] Delay range")
|
TEST_CASE("[EGDescription] Delay range")
|
||||||
|
|
@ -34,13 +34,13 @@ TEST_CASE("[EGDescription] Delay range")
|
||||||
eg.delay = 1;
|
eg.delay = 1;
|
||||||
eg.vel2delay = -1.27f;
|
eg.vel2delay = -1.27f;
|
||||||
eg.ccDelay = { 63, 1.27f };
|
eg.ccDelay = { 63, 1.27f };
|
||||||
REQUIRE( eg.getDelay(state, 0_norm) == 1.0f );
|
REQUIRE(eg.getDelay(state, 0_norm) == 1.0f);
|
||||||
REQUIRE( eg.getDelay(state, 127_norm) == 0.0f );
|
REQUIRE(eg.getDelay(state, 127_norm) == 0.0f);
|
||||||
state.ccEvent(0, 63, 127_norm);
|
state.ccEvent(0, 63, 127_norm);
|
||||||
REQUIRE( eg.getDelay(state, 127_norm) == 1.0f );
|
REQUIRE(eg.getDelay(state, 127_norm) == 1.0f);
|
||||||
REQUIRE( eg.getDelay(state, 0_norm) == 2.27f );
|
REQUIRE(eg.getDelay(state, 0_norm) == 2.27f);
|
||||||
eg.ccDelay = { 63, 127.0f };
|
eg.ccDelay = { 63, 127.0f };
|
||||||
REQUIRE( eg.getDelay(state, 0_norm) == 100.0f );
|
REQUIRE(eg.getDelay(state, 0_norm) == 100.0f);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("[EGDescription] Decay range")
|
TEST_CASE("[EGDescription] Decay range")
|
||||||
|
|
@ -50,13 +50,13 @@ TEST_CASE("[EGDescription] Decay range")
|
||||||
eg.decay = 1.0f;
|
eg.decay = 1.0f;
|
||||||
eg.vel2decay = -1.27f;
|
eg.vel2decay = -1.27f;
|
||||||
eg.ccDecay = { 63, 1.27f };
|
eg.ccDecay = { 63, 1.27f };
|
||||||
REQUIRE( eg.getDecay(state, 0_norm) == 1.0f );
|
REQUIRE(eg.getDecay(state, 0_norm) == 1.0f);
|
||||||
REQUIRE( eg.getDecay(state, 127_norm) == 0.0f );
|
REQUIRE(eg.getDecay(state, 127_norm) == 0.0f);
|
||||||
state.ccEvent(0, 63, 127_norm);
|
state.ccEvent(0, 63, 127_norm);
|
||||||
REQUIRE( eg.getDecay(state, 127_norm) == 1.0f );
|
REQUIRE(eg.getDecay(state, 127_norm) == 1.0f);
|
||||||
REQUIRE( eg.getDecay(state, 0_norm) == 2.27f );
|
REQUIRE(eg.getDecay(state, 0_norm) == 2.27f);
|
||||||
eg.ccDecay = { 63, 127.0f };
|
eg.ccDecay = { 63, 127.0f };
|
||||||
REQUIRE( eg.getDecay(state, 0_norm) == 100.0f );
|
REQUIRE(eg.getDecay(state, 0_norm) == 100.0f);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("[EGDescription] Release range")
|
TEST_CASE("[EGDescription] Release range")
|
||||||
|
|
@ -66,13 +66,13 @@ TEST_CASE("[EGDescription] Release range")
|
||||||
eg.release = 1;
|
eg.release = 1;
|
||||||
eg.vel2release = -1.27f;
|
eg.vel2release = -1.27f;
|
||||||
eg.ccRelease = { 63, 1.27f };
|
eg.ccRelease = { 63, 1.27f };
|
||||||
REQUIRE( eg.getRelease(state, 0_norm) == 1.0f );
|
REQUIRE(eg.getRelease(state, 0_norm) == 1.0f);
|
||||||
REQUIRE( eg.getRelease(state, 127_norm) == 0.0f );
|
REQUIRE(eg.getRelease(state, 127_norm) == 0.0f);
|
||||||
state.ccEvent(0, 63, 127_norm);
|
state.ccEvent(0, 63, 127_norm);
|
||||||
REQUIRE( eg.getRelease(state, 127_norm) == 1.0f );
|
REQUIRE(eg.getRelease(state, 127_norm) == 1.0f);
|
||||||
REQUIRE( eg.getRelease(state, 0_norm) == 2.27f );
|
REQUIRE(eg.getRelease(state, 0_norm) == 2.27f);
|
||||||
eg.ccRelease = { 63, 127.0f };
|
eg.ccRelease = { 63, 127.0f };
|
||||||
REQUIRE( eg.getRelease(state, 0_norm) == 100.0f );
|
REQUIRE(eg.getRelease(state, 0_norm) == 100.0f);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("[EGDescription] Hold range")
|
TEST_CASE("[EGDescription] Hold range")
|
||||||
|
|
@ -82,13 +82,13 @@ TEST_CASE("[EGDescription] Hold range")
|
||||||
eg.hold = 1;
|
eg.hold = 1;
|
||||||
eg.vel2hold = -1.27f;
|
eg.vel2hold = -1.27f;
|
||||||
eg.ccHold = { 63, 1.27f };
|
eg.ccHold = { 63, 1.27f };
|
||||||
REQUIRE( eg.getHold(state, 0_norm) == 1.0f );
|
REQUIRE(eg.getHold(state, 0_norm) == 1.0f);
|
||||||
REQUIRE( eg.getHold(state, 127_norm) == 0.0f );
|
REQUIRE(eg.getHold(state, 127_norm) == 0.0f);
|
||||||
state.ccEvent(0, 63, 127_norm);
|
state.ccEvent(0, 63, 127_norm);
|
||||||
REQUIRE( eg.getHold(state, 127_norm) == 1.0f );
|
REQUIRE(eg.getHold(state, 127_norm) == 1.0f);
|
||||||
REQUIRE( eg.getHold(state, 0_norm) == 2.27f );
|
REQUIRE(eg.getHold(state, 0_norm) == 2.27f);
|
||||||
eg.ccHold = { 63, 127.0f };
|
eg.ccHold = { 63, 127.0f };
|
||||||
REQUIRE( eg.getHold(state, 0_norm) == 100.0f );
|
REQUIRE(eg.getHold(state, 0_norm) == 100.0f);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("[EGDescription] Sustain level")
|
TEST_CASE("[EGDescription] Sustain level")
|
||||||
|
|
@ -98,12 +98,12 @@ TEST_CASE("[EGDescription] Sustain level")
|
||||||
eg.sustain = 50;
|
eg.sustain = 50;
|
||||||
eg.vel2sustain = -100;
|
eg.vel2sustain = -100;
|
||||||
eg.ccSustain = { 63, 100.0f };
|
eg.ccSustain = { 63, 100.0f };
|
||||||
REQUIRE( eg.getSustain(state, 0_norm) == 50.0f );
|
REQUIRE(eg.getSustain(state, 0_norm) == 50.0f);
|
||||||
REQUIRE( eg.getSustain(state, 127_norm) == 0.0f );
|
REQUIRE(eg.getSustain(state, 127_norm) == 0.0f);
|
||||||
state.ccEvent(0, 63, 127_norm);
|
state.ccEvent(0, 63, 127_norm);
|
||||||
REQUIRE( eg.getSustain(state, 127_norm) == 50.0f );
|
REQUIRE(eg.getSustain(state, 127_norm) == 50.0f);
|
||||||
eg.ccSustain = { 63, 200.0f };
|
eg.ccSustain = { 63, 200.0f };
|
||||||
REQUIRE( eg.getSustain(state, 0_norm) == 100.0f );
|
REQUIRE(eg.getSustain(state, 0_norm) == 100.0f);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("[EGDescription] Start level")
|
TEST_CASE("[EGDescription] Start level")
|
||||||
|
|
@ -112,10 +112,10 @@ TEST_CASE("[EGDescription] Start level")
|
||||||
sfz::MidiState state;
|
sfz::MidiState state;
|
||||||
eg.start = 0;
|
eg.start = 0;
|
||||||
eg.ccStart = { 63, 127.0f };
|
eg.ccStart = { 63, 127.0f };
|
||||||
REQUIRE( eg.getStart(state, 0_norm) == 0.0f );
|
REQUIRE(eg.getStart(state, 0_norm) == 0.0f);
|
||||||
REQUIRE( eg.getStart(state, 127_norm) == 0.0f );
|
REQUIRE(eg.getStart(state, 127_norm) == 0.0f);
|
||||||
state.ccEvent(0, 63, 127_norm);
|
state.ccEvent(0, 63, 127_norm);
|
||||||
REQUIRE( eg.getStart(state, 0_norm) == 100.0f );
|
REQUIRE(eg.getStart(state, 0_norm) == 100.0f);
|
||||||
eg.ccStart = { 63, -127.0f };
|
eg.ccStart = { 63, -127.0f };
|
||||||
REQUIRE( eg.getStart(state, 0_norm) == 0.0f );
|
REQUIRE(eg.getStart(state, 0_norm) == 0.0f);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,6 @@
|
||||||
// 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 "sfizz/EventEnvelopes.h"
|
|
||||||
#include "sfizz/SfzHelpers.h"
|
#include "sfizz/SfzHelpers.h"
|
||||||
#include "sfizz/Buffer.h"
|
#include "sfizz/Buffer.h"
|
||||||
#include "catch2/catch.hpp"
|
#include "catch2/catch.hpp"
|
||||||
|
|
@ -30,360 +29,247 @@ inline bool approxEqual(absl::Span<const Type> lhs, absl::Span<const Type> rhs,
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("[LinearEnvelope] Basic state")
|
const auto idModifier = [](float x) { return x; };
|
||||||
|
const auto twiceModifier = [](float x) { return 2 * x; };
|
||||||
|
const auto expModifier = [](float x) { return std::exp(x); };
|
||||||
|
|
||||||
|
TEST_CASE("[Envelopes] Empty")
|
||||||
{
|
{
|
||||||
sfz::LinearEnvelope<float> envelope;
|
sfz::EventVector events {
|
||||||
|
{ 0, 0.0f }
|
||||||
|
};
|
||||||
std::array<float, 5> output;
|
std::array<float, 5> output;
|
||||||
std::array<float, 5> expected { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f };
|
std::array<float, 5> expected { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f };
|
||||||
envelope.getBlock(absl::MakeSpan(output));
|
std::array<float, 5> expectedMul { 1.0f, 1.0f, 1.0f, 1.0f, 1.0f };
|
||||||
|
linearEnvelope(events, absl::MakeSpan(output), idModifier);
|
||||||
REQUIRE(output == expected);
|
REQUIRE(output == expected);
|
||||||
|
linearEnvelope(events, absl::MakeSpan(output), idModifier, 1.0f);
|
||||||
|
REQUIRE(output == expected);
|
||||||
|
multiplicativeEnvelope(events, absl::MakeSpan(output), expModifier);
|
||||||
|
REQUIRE(output == expectedMul);
|
||||||
|
multiplicativeEnvelope(events, absl::MakeSpan(output), expModifier, 2.0f);
|
||||||
|
REQUIRE(output == expectedMul);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("[LinearEnvelope] Basic event")
|
TEST_CASE("[Envelopes] Linear basic")
|
||||||
{
|
{
|
||||||
sfz::LinearEnvelope<float> envelope;
|
sfz::EventVector events {
|
||||||
envelope.registerEvent(4, 1.0f);
|
{ 0, 0.0f },
|
||||||
std::array<float, 8> output;
|
{ 4, 1.0f }
|
||||||
std::array<float, 8> expected { 0.25f, 0.5f, 0.75f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f };
|
};
|
||||||
envelope.getBlock(absl::MakeSpan(output));
|
std::array<float, 9> output;
|
||||||
|
std::array<float, 9> expected { 0.0f, 0.25f, 0.5f, 0.75f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f };
|
||||||
|
linearEnvelope(events, absl::MakeSpan(output), idModifier);
|
||||||
REQUIRE(output == expected);
|
REQUIRE(output == expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("[LinearEnvelope] 2 events, close")
|
TEST_CASE("[LinearEnvelope] 2 events, close")
|
||||||
{
|
{
|
||||||
sfz::LinearEnvelope<float> envelope;
|
sfz::EventVector events {
|
||||||
envelope.registerEvent(4, 1.0f);
|
{ 0, 0.0f },
|
||||||
envelope.registerEvent(5, 2.0f);
|
{ 4, 1.0f },
|
||||||
std::array<float, 8> output;
|
{ 5, 2.0f }
|
||||||
std::array<float, 8> expected { 0.25f, 0.5f, 0.75f, 1.0f, 2.0f, 2.0f, 2.0f, 2.0f };
|
};
|
||||||
envelope.getBlock(absl::MakeSpan(output));
|
std::array<float, 9> output;
|
||||||
|
std::array<float, 9> expected { 0.0f, 0.25f, 0.5f, 0.75f, 1.0f, 2.0f, 2.0f, 2.0f, 2.0f };
|
||||||
|
linearEnvelope(events, absl::MakeSpan(output), idModifier);
|
||||||
REQUIRE(output == expected);
|
REQUIRE(output == expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("[LinearEnvelope] 2 events, far")
|
TEST_CASE("[LinearEnvelope] 2 events, far")
|
||||||
{
|
{
|
||||||
sfz::LinearEnvelope<float> envelope;
|
sfz::EventVector events {
|
||||||
envelope.registerEvent(2, 1.0f);
|
{ 0, 0.0f },
|
||||||
envelope.registerEvent(6, 2.0f);
|
{ 2, 1.0f },
|
||||||
std::array<float, 8> output;
|
{ 6, 2.0f }
|
||||||
std::array<float, 8> expected { 0.5f, 1.0f, 1.25f, 1.5f, 1.75f, 2.0f, 2.0f, 2.0f };
|
};
|
||||||
envelope.getBlock(absl::MakeSpan(output));
|
std::array<float, 9> output;
|
||||||
REQUIRE(output == expected);
|
std::array<float, 9> expected { 0.0f, 0.5f, 1.0f, 1.25f, 1.5f, 1.75f, 2.0f, 2.0f, 2.0f };
|
||||||
}
|
linearEnvelope(events, absl::MakeSpan(output), idModifier);
|
||||||
|
|
||||||
TEST_CASE("[LinearEnvelope] 2 events, reversed")
|
|
||||||
{
|
|
||||||
sfz::LinearEnvelope<float> envelope;
|
|
||||||
envelope.registerEvent(6, 2.0f);
|
|
||||||
envelope.registerEvent(2, 1.0f);
|
|
||||||
std::array<float, 8> output;
|
|
||||||
std::array<float, 8> expected { 0.5f, 1.0f, 1.25f, 1.5f, 1.75f, 2.0f, 2.0f, 2.0f };
|
|
||||||
envelope.getBlock(absl::MakeSpan(output));
|
|
||||||
REQUIRE(output == expected);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_CASE("[LinearEnvelope] 3 events, overlapping")
|
|
||||||
{
|
|
||||||
sfz::LinearEnvelope<float> envelope;
|
|
||||||
envelope.registerEvent(2, 1.0f);
|
|
||||||
envelope.registerEvent(6, 2.0f);
|
|
||||||
envelope.registerEvent(6, 3.0f);
|
|
||||||
std::array<float, 8> output;
|
|
||||||
std::array<float, 8> expected { 0.5f, 1.0f, 1.5f, 2.0f, 2.5f, 3.0f, 3.0f, 3.0f };
|
|
||||||
envelope.getBlock(absl::MakeSpan(output));
|
|
||||||
REQUIRE(output == expected);
|
REQUIRE(output == expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("[LinearEnvelope] 3 events, out of block")
|
TEST_CASE("[LinearEnvelope] 3 events, out of block")
|
||||||
{
|
{
|
||||||
// sfz::LinearEnvelope<float> envelope;
|
sfz::EventVector events {
|
||||||
// envelope.registerEvent(2, 1.0f);
|
{ 0, 0.0f },
|
||||||
// envelope.registerEvent(6, 2.0f);
|
{ 2, 1.0f },
|
||||||
// envelope.registerEvent(10, 3.0f);
|
{ 6, 2.0f },
|
||||||
// std::array<float, 8> output;
|
{ 10, 3.0f }
|
||||||
// std::array<float, 8> expected { 0.5f, 1.0f, 1.25f, 1.5f, 1.75f, 2.0f, 3.0f, 3.0f }; // TODO: this one is a bit strange
|
};
|
||||||
// envelope.getBlock(absl::MakeSpan(output));
|
std::array<float, 9> output;
|
||||||
// REQUIRE(output == expected);
|
std::array<float, 9> expected { 0.0f, 0.5f, 1.0f, 1.25f, 1.5f, 1.75f, 2.0f, 2.5f, 3.0f };
|
||||||
}
|
linearEnvelope(events, absl::MakeSpan(output), idModifier);
|
||||||
|
|
||||||
TEST_CASE("[LinearEnvelope] 3 events, out of block, with another block call")
|
|
||||||
{
|
|
||||||
sfz::LinearEnvelope<float> envelope;
|
|
||||||
envelope.registerEvent(2, 1.0f);
|
|
||||||
envelope.registerEvent(6, 2.0f);
|
|
||||||
envelope.registerEvent(10, 3.0f);
|
|
||||||
std::array<float, 8> output;
|
|
||||||
std::array<float, 8> expected { 3.0f, 3.0f, 3.0f, 3.0f, 3.0f, 3.0f, 3.0f, 3.0f };
|
|
||||||
envelope.getBlock(absl::MakeSpan(output));
|
|
||||||
envelope.getBlock(absl::MakeSpan(output));
|
|
||||||
REQUIRE(output == expected);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_CASE("[LinearEnvelope] 2 events, with another block call")
|
|
||||||
{
|
|
||||||
sfz::LinearEnvelope<float> envelope;
|
|
||||||
envelope.registerEvent(2, 1.0f);
|
|
||||||
envelope.registerEvent(6, 2.0f);
|
|
||||||
std::array<float, 8> output;
|
|
||||||
std::array<float, 8> expected { 2.0f, 2.0f, 2.0f, 2.0f, 2.0f, 2.0f, 2.0f, 2.0f };
|
|
||||||
envelope.getBlock(absl::MakeSpan(output));
|
|
||||||
envelope.getBlock(absl::MakeSpan(output));
|
|
||||||
REQUIRE(output == expected);
|
REQUIRE(output == expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("[LinearEnvelope] 2 events, function")
|
TEST_CASE("[LinearEnvelope] 2 events, function")
|
||||||
{
|
{
|
||||||
sfz::LinearEnvelope<float> envelope;
|
sfz::EventVector events {
|
||||||
envelope.setFunction([](float x) { return 2 * x; });
|
{ 0, 0.0f },
|
||||||
envelope.registerEvent(2, 1.0f);
|
{ 2, 1.0f },
|
||||||
envelope.registerEvent(6, 2.0f);
|
{ 6, 2.0f }
|
||||||
std::array<float, 8> output;
|
};
|
||||||
std::array<float, 8> expected { 1.0f, 2.0f, 2.5f, 3.0f, 3.5f, 4.0f, 4.0f, 4.0f };
|
std::array<float, 9> output;
|
||||||
envelope.getBlock(absl::MakeSpan(output));
|
std::array<float, 9> expected { 0.0f, 1.0f, 2.0f, 2.5f, 3.0f, 3.5f, 4.0f, 4.0f, 4.0f };
|
||||||
|
linearEnvelope(events, absl::MakeSpan(output), twiceModifier);
|
||||||
REQUIRE(output == expected);
|
REQUIRE(output == expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("[LinearEnvelope] Get quantized")
|
TEST_CASE("[LinearEnvelope] Get quantized")
|
||||||
{
|
{
|
||||||
sfz::LinearEnvelope<float> envelope;
|
sfz::EventVector events {
|
||||||
envelope.registerEvent(2, 1.0f);
|
{ 0, 0.0f },
|
||||||
envelope.registerEvent(6, 2.0f);
|
{ 2, 1.0f },
|
||||||
|
{ 6, 2.0f }
|
||||||
|
};
|
||||||
std::array<float, 8> output;
|
std::array<float, 8> output;
|
||||||
std::array<float, 8> expected { 0.0f, 1.0f, 1.0f, 1.0f, 1.0f, 2.0f, 2.0f, 2.0f };
|
std::array<float, 8> expected { 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, 1.0f, 2.0f, 2.0f };
|
||||||
envelope.getQuantizedBlock(absl::MakeSpan(output), 1.0f);
|
linearEnvelope(events, absl::MakeSpan(output), idModifier, 1.0f);
|
||||||
REQUIRE(output == expected);
|
REQUIRE(output == expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("[LinearEnvelope] Get quantized with unquantized targets")
|
TEST_CASE("[LinearEnvelope] Get quantized with unquantized targets")
|
||||||
{
|
{
|
||||||
sfz::LinearEnvelope<float> envelope;
|
sfz::EventVector events {
|
||||||
envelope.registerEvent(2, 1.1f);
|
{ 0, 0.0f },
|
||||||
envelope.registerEvent(6, 1.9f);
|
{ 2, 1.1f },
|
||||||
|
{ 6, 1.9f }
|
||||||
|
};
|
||||||
std::array<float, 8> output;
|
std::array<float, 8> output;
|
||||||
std::array<float, 8> expected { 0.0f, 1.0f, 1.0f, 1.0f, 1.0f, 2.0f, 2.0f, 2.0f };
|
std::array<float, 8> expected { 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, 1.0f, 2.0f, 2.0f };
|
||||||
envelope.getQuantizedBlock(absl::MakeSpan(output), 1.0f);
|
linearEnvelope(events, absl::MakeSpan(output), idModifier, 1.0f);
|
||||||
REQUIRE(output == expected);
|
REQUIRE(output == expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("[LinearEnvelope] Get quantized with 2 steps")
|
TEST_CASE("[LinearEnvelope] Get quantized with 2 steps")
|
||||||
{
|
{
|
||||||
sfz::LinearEnvelope<float> envelope;
|
sfz::EventVector events {
|
||||||
envelope.registerEvent(2, 1.0f);
|
{ 0, 0.0f },
|
||||||
envelope.registerEvent(6, 3.0f);
|
{ 2, 1.0f },
|
||||||
|
{ 6, 3.0f }
|
||||||
|
};
|
||||||
std::array<float, 8> output;
|
std::array<float, 8> output;
|
||||||
std::array<float, 8> expected { 0.0f, 1.0f, 1.0f, 2.0f, 2.0f, 3.0f, 3.0f, 3.0f };
|
std::array<float, 8> expected { 0.0f, 0.0f, 1.0f, 1.0f, 2.0f, 2.0f, 3.0f, 3.0f };
|
||||||
envelope.getQuantizedBlock(absl::MakeSpan(output), 1.0f);
|
linearEnvelope(events, absl::MakeSpan(output), idModifier, 1.0f);
|
||||||
REQUIRE(output == expected);
|
REQUIRE(output == expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("[LinearEnvelope] Get quantized with 2 steps and an unquantized out of block step")
|
TEST_CASE("[LinearEnvelope] Get quantized with 2 steps and an unquantized out of block step")
|
||||||
{
|
{
|
||||||
sfz::LinearEnvelope<float> envelope;
|
sfz::EventVector events {
|
||||||
envelope.registerEvent(2, 1.0f);
|
{ 0, 0.0f },
|
||||||
envelope.registerEvent(6, 3.0f);
|
{ 2, 1.0f },
|
||||||
envelope.registerEvent(10, 4.2f);
|
{ 6, 3.0f },
|
||||||
|
{ 10, 4.2f },
|
||||||
|
};
|
||||||
std::array<float, 8> output;
|
std::array<float, 8> output;
|
||||||
std::array<float, 8> expected { 0.0f, 1.0f, 1.0f, 2.0f, 2.0f, 3.0f, 4.0f, 4.0f };
|
std::array<float, 8> expected { 0.0f, 0.0f, 1.0f, 1.0f, 2.0f, 2.0f, 3.0f, 4.0f };
|
||||||
std::array<float, 8> expected2 { 4.0f, 4.0f, 4.0f,4.0f, 4.0f, 4.0f, 4.0f, 4.0f };
|
linearEnvelope(events, absl::MakeSpan(output), idModifier, 1.0f);
|
||||||
envelope.getQuantizedBlock(absl::MakeSpan(output), 1.0f);
|
|
||||||
REQUIRE(output == expected);
|
REQUIRE(output == expected);
|
||||||
envelope.getQuantizedBlock(absl::MakeSpan(output), 1.0f);
|
|
||||||
REQUIRE(output == expected2);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
TEST_CASE("[LinearEnvelope] Going down quantized with 2 steps")
|
TEST_CASE("[LinearEnvelope] Going down quantized with 2 steps")
|
||||||
{
|
{
|
||||||
sfz::LinearEnvelope<float> envelope;
|
sfz::EventVector events {
|
||||||
envelope.reset(3.0f);
|
{ 0, 3.0f },
|
||||||
envelope.registerEvent(2, 2.0f);
|
{ 2, 2.0f },
|
||||||
envelope.registerEvent(6, 0.0f);
|
{ 6, 0.0f }
|
||||||
|
};
|
||||||
std::array<float, 8> output;
|
std::array<float, 8> output;
|
||||||
std::array<float, 8> expected { 3.0f, 2.0f, 2.0f, 1.0f, 1.0f, 0.0f, 0.0f, 0.0f };
|
std::array<float, 8> expected { 3.0f, 3.0f, 2.0f, 2.0f, 1.0f, 1.0f, 0.0f, 0.0f };
|
||||||
envelope.getQuantizedBlock(absl::MakeSpan(output), 1.0f);
|
linearEnvelope(events, absl::MakeSpan(output), idModifier, 1.0f);
|
||||||
REQUIRE(output == expected);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_CASE("[LinearEnvelope] Get quantized with 2 steps and starting unquantized")
|
|
||||||
{
|
|
||||||
sfz::LinearEnvelope<float> envelope;
|
|
||||||
envelope.reset(0.1f);
|
|
||||||
envelope.registerEvent(3, 1.0f);
|
|
||||||
envelope.registerEvent(7, 3.0f);
|
|
||||||
std::array<float, 8> output;
|
|
||||||
std::array<float, 8> expected { 0.1f, 0.1f, 1.0f, 1.0f, 2.0f, 2.0f, 3.0f, 3.0f };
|
|
||||||
envelope.getQuantizedBlock(absl::MakeSpan(output), 1.0f);
|
|
||||||
REQUIRE(output == expected);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
TEST_CASE("[LinearEnvelope] Going down quantized with 2 steps and starting unquantized")
|
|
||||||
{
|
|
||||||
sfz::LinearEnvelope<float> envelope;
|
|
||||||
envelope.reset(3.6f);
|
|
||||||
envelope.registerEvent(4, 1.0f);
|
|
||||||
envelope.registerEvent(7, 0.0);
|
|
||||||
std::array<float, 8> output;
|
|
||||||
std::array<float, 8> expected { 3.6f, 3.0f, 2.0f, 2.0f, 1.0f, 1.0f, 0.0f, 0.0f };
|
|
||||||
envelope.getQuantizedBlock(absl::MakeSpan(output), 1.0f);
|
|
||||||
REQUIRE(output == expected);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_CASE("[LinearEnvelope] Get quantized with unclean events")
|
|
||||||
{
|
|
||||||
sfz::LinearEnvelope<float> envelope;
|
|
||||||
envelope.registerEvent(2, 1.2f);
|
|
||||||
envelope.registerEvent(6, 2.5f);
|
|
||||||
std::array<float, 8> output;
|
|
||||||
std::array<float, 8> expected { 0.0f, 1.0f, 1.0f, 2.0f, 2.0f, 3.0f, 3.0f, 3.0f };
|
|
||||||
envelope.getQuantizedBlock(absl::MakeSpan(output), 1.0f);
|
|
||||||
REQUIRE(output == expected);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_CASE("[LinearEnvelope] Get quantized 3 events, one out of block")
|
|
||||||
{
|
|
||||||
sfz::LinearEnvelope<float> envelope;
|
|
||||||
envelope.registerEvent(2, 1.0f);
|
|
||||||
envelope.registerEvent(6, 2.0f);
|
|
||||||
envelope.registerEvent(10, 3.0f);
|
|
||||||
std::array<float, 8> output;
|
|
||||||
std::array<float, 8> expected { 0.0f, 1.0f, 1.0f, 1.0f, 1.0f, 2.0f, 3.0f, 3.0f };
|
|
||||||
std::array<float, 8> expected2 { 3.0f, 3.0f, 3.0f, 3.0f, 3.0f, 3.0f, 3.0f, 3.0f };
|
|
||||||
envelope.getQuantizedBlock(absl::MakeSpan(output), 1.0f);
|
|
||||||
REQUIRE(output == expected);
|
|
||||||
envelope.getQuantizedBlock(absl::MakeSpan(output), 1.0f);
|
|
||||||
REQUIRE(output == expected2);
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
TEST_CASE("[MultiplicativeEnvelope] Basic state")
|
|
||||||
{
|
|
||||||
sfz::MultiplicativeEnvelope<float> envelope;
|
|
||||||
std::array<float, 5> output;
|
|
||||||
std::array<float, 5> expected { 1.0f, 1.0f, 1.0f, 1.0f, 1.0f };
|
|
||||||
envelope.getBlock(absl::MakeSpan(output));
|
|
||||||
REQUIRE(output == expected);
|
REQUIRE(output == expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("[MultiplicativeEnvelope] Basic event")
|
TEST_CASE("[MultiplicativeEnvelope] Basic event")
|
||||||
{
|
{
|
||||||
sfz::MultiplicativeEnvelope<float> envelope;
|
sfz::EventVector events {
|
||||||
envelope.registerEvent(4, 2.0f);
|
{ 0, 1.0f },
|
||||||
|
{ 4, 2.0f }
|
||||||
|
};
|
||||||
std::array<float, 8> output;
|
std::array<float, 8> output;
|
||||||
std::array<float, 8> expected { 1.1892f, 1.4142f, 1.68176f, 2.0f, 2.0f, 2.0f, 2.0f, 2.0f };
|
std::array<float, 8> expected { 1.0f, 1.1892f, 1.4142f, 1.68176f, 2.0f, 2.0f, 2.0f, 2.0f };
|
||||||
envelope.getBlock(absl::MakeSpan(output));
|
multiplicativeEnvelope(events, absl::MakeSpan(output), idModifier);
|
||||||
REQUIRE(approxEqual<float>(output, expected));
|
REQUIRE(approxEqual<float>(output, expected));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("[MultiplicativeEnvelope] 2 events")
|
TEST_CASE("[MultiplicativeEnvelope] 2 events")
|
||||||
{
|
{
|
||||||
sfz::MultiplicativeEnvelope<float> envelope;
|
sfz::EventVector events {
|
||||||
envelope.registerEvent(4, 2.0f);
|
{ 0, 1.0f },
|
||||||
envelope.registerEvent(5, 4.0f);
|
{ 4, 2.0f },
|
||||||
|
{ 5, 4.0f }
|
||||||
|
};
|
||||||
std::array<float, 8> output;
|
std::array<float, 8> output;
|
||||||
std::array<float, 8> expected { 1.1892f, 1.4142f, 1.68176f, 2.0f, 4.0f, 4.0f, 4.0f, 4.0f };
|
std::array<float, 8> expected { 1.0f, 1.1892f, 1.4142f, 1.68176f, 2.0f, 4.0f, 4.0f, 4.0f };
|
||||||
envelope.getBlock(absl::MakeSpan(output));
|
multiplicativeEnvelope(events, absl::MakeSpan(output), idModifier);
|
||||||
REQUIRE(approxEqual<float>(output, expected));
|
REQUIRE(approxEqual<float>(output, expected));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("[MultiplicativeEnvelope] 2 events, far")
|
TEST_CASE("[MultiplicativeEnvelope] 2 events, far")
|
||||||
{
|
{
|
||||||
sfz::MultiplicativeEnvelope<float> envelope;
|
sfz::EventVector events {
|
||||||
envelope.registerEvent(2, 2.0f);
|
{ 0, 1.0f },
|
||||||
envelope.registerEvent(6, 4.0f);
|
{ 2, 2.0f },
|
||||||
|
{ 6, 4.0f }
|
||||||
|
};
|
||||||
std::array<float, 8> output;
|
std::array<float, 8> output;
|
||||||
std::array<float, 8> expected { 1.4142f, 2.0f, 2.37841f, 2.82843f, 3.36358f, 4.0f, 4.0f, 4.0f };
|
std::array<float, 8> expected { 1.0f, 1.4142f, 2.0f, 2.37841f, 2.82843f, 3.36358f, 4.0f, 4.0f };
|
||||||
envelope.getBlock(absl::MakeSpan(output));
|
multiplicativeEnvelope(events, absl::MakeSpan(output), idModifier);
|
||||||
REQUIRE(approxEqual<float>(output, expected));
|
REQUIRE(approxEqual<float>(output, expected));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("[MultiplicativeEnvelope] Get quantized with 2 steps")
|
TEST_CASE("[MultiplicativeEnvelope] Get quantized with 2 steps")
|
||||||
{
|
{
|
||||||
sfz::MultiplicativeEnvelope<float> envelope;
|
sfz::EventVector events {
|
||||||
envelope.registerEvent(2, 2.0f);
|
{ 0, 1.0f },
|
||||||
envelope.registerEvent(6, 4.0f);
|
{ 2, 2.0f },
|
||||||
|
{ 6, 4.0f }
|
||||||
|
};
|
||||||
std::array<float, 8> output;
|
std::array<float, 8> output;
|
||||||
std::array<float, 8> expected { 1.0f, 2.0f, 2.0f, 2.0f, 2.0f, 4.0f, 4.0f, 4.0f };
|
std::array<float, 8> expected { 1.0f, 1.0f, 2.0f, 2.0f, 2.0f, 2.0f, 4.0f, 4.0f };
|
||||||
envelope.getQuantizedBlock(absl::MakeSpan(output), 2.0f);
|
multiplicativeEnvelope(events, absl::MakeSpan(output), idModifier, 2.0f);
|
||||||
REQUIRE(output == expected);
|
REQUIRE(output == expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("[MultiplicativeEnvelope] Get quantized with an unquantized out of range step")
|
TEST_CASE("[MultiplicativeEnvelope] Get quantized with an unquantized out of range step")
|
||||||
{
|
{
|
||||||
sfz::MultiplicativeEnvelope<float> envelope;
|
sfz::EventVector events {
|
||||||
envelope.registerEvent(2, 2.0f);
|
{ 0, 1.0f },
|
||||||
envelope.registerEvent(6, 4.0f);
|
{ 2, 2.0f },
|
||||||
envelope.registerEvent(10, 8.2f);
|
{ 6, 4.0f },
|
||||||
|
{ 10, 8.2f }
|
||||||
|
};
|
||||||
std::array<float, 8> output;
|
std::array<float, 8> output;
|
||||||
std::array<float, 8> expected { 1.0f, 2.0f, 2.0f, 2.0f, 2.0f, 4.0f, 8.0f, 8.0f };
|
std::array<float, 8> expected { 1.0f, 1.0f, 2.0f, 2.0f, 2.0f, 2.0f, 4.0f, 8.0f };
|
||||||
std::array<float, 8> expected2 { 8.0f, 8.0f, 8.0f, 8.0f, 8.0f, 8.0f, 8.0f, 8.0f };
|
multiplicativeEnvelope(events, absl::MakeSpan(output), idModifier, 2.0f);
|
||||||
envelope.getQuantizedBlock(absl::MakeSpan(output), 2.0f);
|
|
||||||
REQUIRE(output == expected);
|
REQUIRE(output == expected);
|
||||||
envelope.getQuantizedBlock(absl::MakeSpan(output), 2.0f);
|
|
||||||
REQUIRE(output == expected2);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("[MultiplicativeEnvelope] Going down quantized with 2 steps")
|
TEST_CASE("[MultiplicativeEnvelope] Going down quantized with 2 steps")
|
||||||
{
|
{
|
||||||
sfz::MultiplicativeEnvelope<float> envelope;
|
sfz::EventVector events {
|
||||||
envelope.reset(4.0f);
|
{ 0, 4.0f },
|
||||||
envelope.registerEvent(2, 2.0f);
|
{ 2, 2.0f },
|
||||||
envelope.registerEvent(6, 0.5f);
|
{ 6, 0.5f }
|
||||||
|
};
|
||||||
std::array<float, 8> output;
|
std::array<float, 8> output;
|
||||||
std::array<float, 8> expected { 4.0f, 2.0f, 2.0f, 1.0f, 1.0f, 0.5f, 0.5f, 0.5f };
|
std::array<float, 8> expected { 4.0f, 4.0f, 2.0f, 2.0f, 1.0f, 1.0f, 0.5f, 0.5f };
|
||||||
envelope.getQuantizedBlock(absl::MakeSpan(output), 2.0f);
|
multiplicativeEnvelope(events, absl::MakeSpan(output), idModifier, 2.0f);
|
||||||
REQUIRE(output == expected);
|
REQUIRE(output == expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("[MultiplicativeEnvelope] Get quantized with unclean events")
|
TEST_CASE("[MultiplicativeEnvelope] Get quantized with unclean events")
|
||||||
{
|
{
|
||||||
sfz::MultiplicativeEnvelope<float> envelope;
|
sfz::EventVector events {
|
||||||
envelope.registerEvent(2, 1.2f);
|
{ 0, 1.0f },
|
||||||
envelope.registerEvent(6, 2.5f);
|
{ 2, 1.2f },
|
||||||
|
{ 6, 2.5f }
|
||||||
|
};
|
||||||
std::array<float, 8> output;
|
std::array<float, 8> output;
|
||||||
std::array<float, 8> expected { 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 2.0f, 2.0f, 2.0f };
|
std::array<float, 8> expected { 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 2.0f, 2.0f };
|
||||||
envelope.getQuantizedBlock(absl::MakeSpan(output), 2.0f);
|
multiplicativeEnvelope(events, absl::MakeSpan(output), idModifier, 2.0f);
|
||||||
REQUIRE(output == expected);
|
REQUIRE(output == expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("[MultiplicativeEnvelope] Get quantized with 2 steps and starting unquantized")
|
|
||||||
{
|
|
||||||
sfz::MultiplicativeEnvelope<float> envelope;
|
|
||||||
envelope.reset(0.9f);
|
|
||||||
envelope.registerEvent(3, 1.0f);
|
|
||||||
envelope.registerEvent(7, 4.0f);
|
|
||||||
std::array<float, 8> output;
|
|
||||||
std::array<float, 8> expected { 0.9f, 0.9f, 1.0f, 1.0f, 2.0f, 2.0f, 4.0f, 4.0f };
|
|
||||||
envelope.getQuantizedBlock(absl::MakeSpan(output), 2.0f);
|
|
||||||
REQUIRE(output == expected);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
TEST_CASE("[MultiplicativeEnvelope] Going down quantized with 2 steps and starting unquantized")
|
|
||||||
{
|
|
||||||
sfz::MultiplicativeEnvelope<float> envelope;
|
|
||||||
envelope.reset(4.6f);
|
|
||||||
envelope.registerEvent(4, 1.0f);
|
|
||||||
envelope.registerEvent(7, 0.25f);
|
|
||||||
std::array<float, 8> output;
|
|
||||||
std::array<float, 8> expected { 4.6f, 2.0f, 1.0f, 1.0f, 0.5f, 0.5f, 0.25f, 0.25f };
|
|
||||||
envelope.getQuantizedBlock(absl::MakeSpan(output), 2.0f);
|
|
||||||
REQUIRE(output == expected);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_CASE("[MultiplicativeEnvelope] Pitch envelope basic function")
|
|
||||||
{
|
|
||||||
sfz::Buffer<float> output { 256 };
|
|
||||||
sfz::MultiplicativeEnvelope<float> envelope;
|
|
||||||
envelope.setFunction([](float pitchValue){
|
|
||||||
const auto normalizedBend = sfz::normalizeBend(pitchValue);
|
|
||||||
const auto bendInCents = normalizedBend * 200.0f;
|
|
||||||
return sfz::centsFactor(bendInCents);
|
|
||||||
});
|
|
||||||
envelope.reset(0.0f);
|
|
||||||
envelope.getBlock(absl::MakeSpan(output));
|
|
||||||
REQUIRE(output[255] == 1.0_a);
|
|
||||||
envelope.registerEvent(252, -6020.0f);
|
|
||||||
envelope.getBlock(absl::MakeSpan(output));
|
|
||||||
REQUIRE(output[255] == Approx(0.9168).epsilon(0.01));
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -152,37 +152,37 @@ TEST_CASE("[Files] Full hierarchy")
|
||||||
synth.loadSfzFile(fs::current_path() / "tests/TestFiles/basic_hierarchy.sfz");
|
synth.loadSfzFile(fs::current_path() / "tests/TestFiles/basic_hierarchy.sfz");
|
||||||
REQUIRE(synth.getNumRegions() == 8);
|
REQUIRE(synth.getNumRegions() == 8);
|
||||||
for (int i = 0; i < synth.getNumRegions(); ++i) {
|
for (int i = 0; i < synth.getNumRegions(); ++i) {
|
||||||
REQUIRE(synth.getRegionView(i)->width == 40.0f);
|
REQUIRE(synth.getRegionView(i)->width == 0.4_a);
|
||||||
}
|
}
|
||||||
REQUIRE(synth.getRegionView(0)->pan == 30.0f);
|
REQUIRE(synth.getRegionView(0)->pan == 0.3_a);
|
||||||
REQUIRE(synth.getRegionView(0)->delay == 67);
|
REQUIRE(synth.getRegionView(0)->delay == 67);
|
||||||
REQUIRE(synth.getRegionView(0)->keyRange == sfz::Range<uint8_t>(60, 60));
|
REQUIRE(synth.getRegionView(0)->keyRange == sfz::Range<uint8_t>(60, 60));
|
||||||
|
|
||||||
REQUIRE(synth.getRegionView(1)->pan == 30.0f);
|
REQUIRE(synth.getRegionView(1)->pan == 0.3_a);
|
||||||
REQUIRE(synth.getRegionView(1)->delay == 67);
|
REQUIRE(synth.getRegionView(1)->delay == 67);
|
||||||
REQUIRE(synth.getRegionView(1)->keyRange == sfz::Range<uint8_t>(61, 61));
|
REQUIRE(synth.getRegionView(1)->keyRange == sfz::Range<uint8_t>(61, 61));
|
||||||
|
|
||||||
REQUIRE(synth.getRegionView(2)->pan == 30.0f);
|
REQUIRE(synth.getRegionView(2)->pan == 0.3_a);
|
||||||
REQUIRE(synth.getRegionView(2)->delay == 56);
|
REQUIRE(synth.getRegionView(2)->delay == 56);
|
||||||
REQUIRE(synth.getRegionView(2)->keyRange == sfz::Range<uint8_t>(50, 50));
|
REQUIRE(synth.getRegionView(2)->keyRange == sfz::Range<uint8_t>(50, 50));
|
||||||
|
|
||||||
REQUIRE(synth.getRegionView(3)->pan == 30.0f);
|
REQUIRE(synth.getRegionView(3)->pan == 0.3_a);
|
||||||
REQUIRE(synth.getRegionView(3)->delay == 56);
|
REQUIRE(synth.getRegionView(3)->delay == 56);
|
||||||
REQUIRE(synth.getRegionView(3)->keyRange == sfz::Range<uint8_t>(51, 51));
|
REQUIRE(synth.getRegionView(3)->keyRange == sfz::Range<uint8_t>(51, 51));
|
||||||
|
|
||||||
REQUIRE(synth.getRegionView(4)->pan == -10.0f);
|
REQUIRE(synth.getRegionView(4)->pan == -0.1_a);
|
||||||
REQUIRE(synth.getRegionView(4)->delay == 47);
|
REQUIRE(synth.getRegionView(4)->delay == 47);
|
||||||
REQUIRE(synth.getRegionView(4)->keyRange == sfz::Range<uint8_t>(40, 40));
|
REQUIRE(synth.getRegionView(4)->keyRange == sfz::Range<uint8_t>(40, 40));
|
||||||
|
|
||||||
REQUIRE(synth.getRegionView(5)->pan == -10.0f);
|
REQUIRE(synth.getRegionView(5)->pan == -0.1_a);
|
||||||
REQUIRE(synth.getRegionView(5)->delay == 47);
|
REQUIRE(synth.getRegionView(5)->delay == 47);
|
||||||
REQUIRE(synth.getRegionView(5)->keyRange == sfz::Range<uint8_t>(41, 41));
|
REQUIRE(synth.getRegionView(5)->keyRange == sfz::Range<uint8_t>(41, 41));
|
||||||
|
|
||||||
REQUIRE(synth.getRegionView(6)->pan == -10.0f);
|
REQUIRE(synth.getRegionView(6)->pan == -0.1_a);
|
||||||
REQUIRE(synth.getRegionView(6)->delay == 36);
|
REQUIRE(synth.getRegionView(6)->delay == 36);
|
||||||
REQUIRE(synth.getRegionView(6)->keyRange == sfz::Range<uint8_t>(30, 30));
|
REQUIRE(synth.getRegionView(6)->keyRange == sfz::Range<uint8_t>(30, 30));
|
||||||
|
|
||||||
REQUIRE(synth.getRegionView(7)->pan == -10.0f);
|
REQUIRE(synth.getRegionView(7)->pan == -0.1_a);
|
||||||
REQUIRE(synth.getRegionView(7)->delay == 36);
|
REQUIRE(synth.getRegionView(7)->delay == 36);
|
||||||
REQUIRE(synth.getRegionView(7)->keyRange == sfz::Range<uint8_t>(31, 31));
|
REQUIRE(synth.getRegionView(7)->keyRange == sfz::Range<uint8_t>(31, 31));
|
||||||
}
|
}
|
||||||
|
|
@ -309,9 +309,9 @@ TEST_CASE("[Files] wrong (overlapping) replacement for defines")
|
||||||
REQUIRE( synth.getRegionView(0)->keyRange.getEnd() == 52 );
|
REQUIRE( synth.getRegionView(0)->keyRange.getEnd() == 52 );
|
||||||
REQUIRE( synth.getRegionView(1)->keyRange.getStart() == 57 );
|
REQUIRE( synth.getRegionView(1)->keyRange.getStart() == 57 );
|
||||||
REQUIRE( synth.getRegionView(1)->keyRange.getEnd() == 57 );
|
REQUIRE( synth.getRegionView(1)->keyRange.getEnd() == 57 );
|
||||||
REQUIRE( synth.getRegionView(2)->amplitudeCC );
|
REQUIRE(!synth.getRegionView(2)->amplitudeCC.empty());
|
||||||
REQUIRE( synth.getRegionView(2)->amplitudeCC->cc == 10 );
|
REQUIRE(synth.getRegionView(2)->amplitudeCC.contains(10));
|
||||||
REQUIRE( synth.getRegionView(2)->amplitudeCC->value == 34.0f );
|
REQUIRE(synth.getRegionView(2)->amplitudeCC.getWithDefault(10) == 0.34f);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("[Files] Specific bug: relative path with backslashes")
|
TEST_CASE("[Files] Specific bug: relative path with backslashes")
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ TEST_CASE("[MidiState] Initial values")
|
||||||
{
|
{
|
||||||
sfz::MidiState state;
|
sfz::MidiState state;
|
||||||
for (unsigned cc = 0; cc < sfz::config::numCCs; cc++)
|
for (unsigned cc = 0; cc < sfz::config::numCCs; cc++)
|
||||||
REQUIRE( state.getCCValue(cc) == 0_norm );
|
REQUIRE(state.getCCValue(cc) == 0_norm);
|
||||||
REQUIRE( state.getPitchBend() == 0 );
|
REQUIRE( state.getPitchBend() == 0 );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -37,24 +37,37 @@ TEST_CASE("[MidiState] Set and get CCs")
|
||||||
TEST_CASE("[MidiState] Set and get pitch bends")
|
TEST_CASE("[MidiState] Set and get pitch bends")
|
||||||
{
|
{
|
||||||
sfz::MidiState state;
|
sfz::MidiState state;
|
||||||
state.pitchBendEvent(0, 894);
|
state.pitchBendEvent(0, 0.5f);
|
||||||
REQUIRE(state.getPitchBend() == 894);
|
REQUIRE(state.getPitchBend() == 0.5f);
|
||||||
state.pitchBendEvent(0, 0);
|
state.pitchBendEvent(0, 0.0f);
|
||||||
REQUIRE(state.getPitchBend() == 0);
|
REQUIRE(state.getPitchBend() == 0.0f);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("[MidiState] Reset")
|
TEST_CASE("[MidiState] Reset")
|
||||||
{
|
{
|
||||||
sfz::MidiState state;
|
sfz::MidiState state;
|
||||||
state.pitchBendEvent(0, 894);
|
state.pitchBendEvent(0, 0.7f);
|
||||||
state.noteOnEvent(0, 64, 24_norm);
|
state.noteOnEvent(0, 64, 24_norm);
|
||||||
state.ccEvent(0, 123, 124_norm);
|
state.ccEvent(0, 123, 124_norm);
|
||||||
state.reset(0);
|
state.reset();
|
||||||
REQUIRE(state.getPitchBend() == 0);
|
REQUIRE(state.getPitchBend() == 0.0f);
|
||||||
REQUIRE(state.getNoteVelocity(64) == 0_norm);
|
REQUIRE(state.getNoteVelocity(64) == 0_norm);
|
||||||
REQUIRE(state.getCCValue(123) == 0_norm);
|
REQUIRE(state.getCCValue(123) == 0_norm);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE("[MidiState] Reset all controllers")
|
||||||
|
{
|
||||||
|
sfz::MidiState state;
|
||||||
|
state.pitchBendEvent(20, 0.7f);
|
||||||
|
state.ccEvent(10, 122, 124_norm);
|
||||||
|
REQUIRE(state.getPitchBend() == 0.7f);
|
||||||
|
REQUIRE(state.getCCValue(122) == 124_norm);
|
||||||
|
state.resetAllControllers(30);
|
||||||
|
REQUIRE(state.getPitchBend() == 0.0f);
|
||||||
|
REQUIRE(state.getCCValue(122) == 0_norm);
|
||||||
|
REQUIRE(state.getCCValue(4) == 0_norm);
|
||||||
|
}
|
||||||
|
|
||||||
TEST_CASE("[MidiState] Set and get note velocities")
|
TEST_CASE("[MidiState] Set and get note velocities")
|
||||||
{
|
{
|
||||||
sfz::MidiState state;
|
sfz::MidiState state;
|
||||||
|
|
|
||||||
|
|
@ -77,11 +77,11 @@ TEST_CASE("Region activation", "Region tests")
|
||||||
region.parseOpcode({ "hibend", "243" });
|
region.parseOpcode({ "hibend", "243" });
|
||||||
region.registerPitchWheel(0);
|
region.registerPitchWheel(0);
|
||||||
REQUIRE(!region.isSwitchedOn());
|
REQUIRE(!region.isSwitchedOn());
|
||||||
region.registerPitchWheel(56);
|
region.registerPitchWheel(sfz::normalizeBend(56));
|
||||||
REQUIRE(region.isSwitchedOn());
|
REQUIRE(region.isSwitchedOn());
|
||||||
region.registerPitchWheel(243);
|
region.registerPitchWheel(sfz::normalizeBend(243));
|
||||||
REQUIRE(region.isSwitchedOn());
|
REQUIRE(region.isSwitchedOn());
|
||||||
region.registerPitchWheel(245);
|
region.registerPitchWheel(sfz::normalizeBend(245));
|
||||||
REQUIRE(!region.isSwitchedOn());
|
REQUIRE(!region.isSwitchedOn());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -223,19 +223,23 @@ TEST_CASE("[Region] Parsing opcodes")
|
||||||
|
|
||||||
SECTION("lobend, hibend")
|
SECTION("lobend, hibend")
|
||||||
{
|
{
|
||||||
REQUIRE(region.bendRange == sfz::Range<int>(-8192, 8192));
|
REQUIRE(region.bendRange == sfz::Range<float>(-1.0f, 1.0f));
|
||||||
region.parseOpcode({ "lobend", "4" });
|
region.parseOpcode({ "lobend", "400" });
|
||||||
REQUIRE(region.bendRange == sfz::Range<int>(4, 8192));
|
REQUIRE(region.bendRange.getStart() == Approx(sfz::normalizeBend(400)));
|
||||||
|
REQUIRE(region.bendRange.getEnd() == 1.0_a);
|
||||||
region.parseOpcode({ "lobend", "-128" });
|
region.parseOpcode({ "lobend", "-128" });
|
||||||
REQUIRE(region.bendRange == sfz::Range<int>(-128, 8192));
|
REQUIRE(region.bendRange.getStart() == Approx(sfz::normalizeBend(-128)));
|
||||||
|
REQUIRE(region.bendRange.getEnd() == 1.0_a);
|
||||||
region.parseOpcode({ "lobend", "-10000" });
|
region.parseOpcode({ "lobend", "-10000" });
|
||||||
REQUIRE(region.bendRange == sfz::Range<int>(-8192, 8192));
|
REQUIRE(region.bendRange == sfz::Range<float>(-1.0f, 1.0f));
|
||||||
region.parseOpcode({ "hibend", "13" });
|
region.parseOpcode({ "hibend", "13" });
|
||||||
REQUIRE(region.bendRange == sfz::Range<int>(-8192, 13));
|
REQUIRE(region.bendRange.getStart() == -1.0_a);
|
||||||
|
REQUIRE(region.bendRange.getEnd() == Approx(sfz::normalizeBend(13)));
|
||||||
region.parseOpcode({ "hibend", "-1" });
|
region.parseOpcode({ "hibend", "-1" });
|
||||||
REQUIRE(region.bendRange == sfz::Range<int>(-8192, -1));
|
REQUIRE(region.bendRange.getStart() == -1.0_a);
|
||||||
|
REQUIRE(region.bendRange.getEnd() == Approx(sfz::normalizeBend(-1)));
|
||||||
region.parseOpcode({ "hibend", "10000" });
|
region.parseOpcode({ "hibend", "10000" });
|
||||||
REQUIRE(region.bendRange == sfz::Range<int>(-8192, 8192));
|
REQUIRE(region.bendRange == sfz::Range<float>(-1.0f, 1.0f));
|
||||||
}
|
}
|
||||||
|
|
||||||
SECTION("locc, hicc")
|
SECTION("locc, hicc")
|
||||||
|
|
@ -452,66 +456,63 @@ TEST_CASE("[Region] Parsing opcodes")
|
||||||
{
|
{
|
||||||
REQUIRE(region.pan == 0.0f);
|
REQUIRE(region.pan == 0.0f);
|
||||||
region.parseOpcode({ "pan", "4.2" });
|
region.parseOpcode({ "pan", "4.2" });
|
||||||
REQUIRE(region.pan == 4.2f);
|
REQUIRE(region.pan == 0.042_a);
|
||||||
region.parseOpcode({ "pan", "-4.2" });
|
region.parseOpcode({ "pan", "-4.2" });
|
||||||
REQUIRE(region.pan == -4.2f);
|
REQUIRE(region.pan == -0.042_a);
|
||||||
region.parseOpcode({ "pan", "-123" });
|
region.parseOpcode({ "pan", "-123" });
|
||||||
REQUIRE(region.pan == -100.0f);
|
REQUIRE(region.pan == -1.0_a);
|
||||||
region.parseOpcode({ "pan", "132" });
|
region.parseOpcode({ "pan", "132" });
|
||||||
REQUIRE(region.pan == 100.0f);
|
REQUIRE(region.pan == 1.0_a);
|
||||||
}
|
}
|
||||||
|
|
||||||
SECTION("pan_oncc")
|
SECTION("pan_oncc")
|
||||||
{
|
{
|
||||||
REQUIRE(!region.panCC);
|
REQUIRE(region.panCC.empty());
|
||||||
region.parseOpcode({ "pan_oncc45", "4.2" });
|
region.parseOpcode({ "pan_oncc45", "4.2" });
|
||||||
REQUIRE(region.panCC);
|
REQUIRE(region.panCC.contains(45));
|
||||||
REQUIRE(region.panCC->cc == 45);
|
REQUIRE(region.panCC[45] == 0.042_a);
|
||||||
REQUIRE(region.panCC->value == 4.2f);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
SECTION("width")
|
SECTION("width")
|
||||||
{
|
{
|
||||||
REQUIRE(region.width == 100.0f);
|
REQUIRE(region.width == 1.0_a);
|
||||||
region.parseOpcode({ "width", "4.2" });
|
region.parseOpcode({ "width", "4.2" });
|
||||||
REQUIRE(region.width == 4.2f);
|
REQUIRE(region.width == 0.042_a);
|
||||||
region.parseOpcode({ "width", "-4.2" });
|
region.parseOpcode({ "width", "-4.2" });
|
||||||
REQUIRE(region.width == -4.2f);
|
REQUIRE(region.width == -0.042_a);
|
||||||
region.parseOpcode({ "width", "-123" });
|
region.parseOpcode({ "width", "-123" });
|
||||||
REQUIRE(region.width == -100.0f);
|
REQUIRE(region.width == -1.0_a);
|
||||||
region.parseOpcode({ "width", "132" });
|
region.parseOpcode({ "width", "132" });
|
||||||
REQUIRE(region.width == 100.0f);
|
REQUIRE(region.width == 1.0_a);
|
||||||
}
|
}
|
||||||
|
|
||||||
SECTION("width_oncc")
|
SECTION("width_oncc")
|
||||||
{
|
{
|
||||||
REQUIRE(!region.widthCC);
|
REQUIRE(region.widthCC.empty());
|
||||||
region.parseOpcode({ "width_oncc45", "4.2" });
|
region.parseOpcode({ "width_oncc45", "4.2" });
|
||||||
REQUIRE(region.widthCC);
|
REQUIRE(region.widthCC.contains(45));
|
||||||
REQUIRE(region.widthCC->cc == 45);
|
REQUIRE(region.widthCC[45] == 0.042_a);
|
||||||
REQUIRE(region.widthCC->value == 4.2f);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
SECTION("position")
|
SECTION("position")
|
||||||
{
|
{
|
||||||
REQUIRE(region.position == 0.0f);
|
REQUIRE(region.position == 0.0f);
|
||||||
region.parseOpcode({ "position", "4.2" });
|
region.parseOpcode({ "position", "4.2" });
|
||||||
REQUIRE(region.position == 4.2f);
|
REQUIRE(region.position == 0.042_a);
|
||||||
region.parseOpcode({ "position", "-4.2" });
|
region.parseOpcode({ "position", "-4.2" });
|
||||||
REQUIRE(region.position == -4.2f);
|
REQUIRE(region.position == -0.042_a);
|
||||||
region.parseOpcode({ "position", "-123" });
|
region.parseOpcode({ "position", "-123" });
|
||||||
REQUIRE(region.position == -100.0f);
|
REQUIRE(region.position == -1.0_a);
|
||||||
region.parseOpcode({ "position", "132" });
|
region.parseOpcode({ "position", "132" });
|
||||||
REQUIRE(region.position == 100.0f);
|
REQUIRE(region.position == 1.0_a);
|
||||||
}
|
}
|
||||||
|
|
||||||
SECTION("position_oncc")
|
SECTION("position_oncc")
|
||||||
{
|
{
|
||||||
REQUIRE(!region.positionCC);
|
REQUIRE(region.positionCC.empty());
|
||||||
region.parseOpcode({ "position_oncc45", "4.2" });
|
region.parseOpcode({ "position_oncc45", "4.2" });
|
||||||
REQUIRE(region.positionCC);
|
REQUIRE(region.positionCC.contains(45));
|
||||||
REQUIRE(region.positionCC->cc == 45);
|
REQUIRE(region.positionCC[45] == 0.042_a);
|
||||||
REQUIRE(region.positionCC->value == 4.2f);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
SECTION("amp_keycenter")
|
SECTION("amp_keycenter")
|
||||||
|
|
@ -1407,6 +1408,79 @@ TEST_CASE("[Region] Parsing opcodes")
|
||||||
region.parseOpcode({ "oscillator_phase", "361" });
|
region.parseOpcode({ "oscillator_phase", "361" });
|
||||||
REQUIRE(region.oscillatorPhase == 360.0f);
|
REQUIRE(region.oscillatorPhase == 360.0f);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SECTION("Note polyphony")
|
||||||
|
{
|
||||||
|
REQUIRE(!region.notePolyphony);
|
||||||
|
region.parseOpcode({ "note_polyphony", "45" });
|
||||||
|
REQUIRE(region.notePolyphony);
|
||||||
|
REQUIRE(*region.notePolyphony == 45);
|
||||||
|
region.parseOpcode({ "note_polyphony", "-1" });
|
||||||
|
REQUIRE(region.notePolyphony);
|
||||||
|
REQUIRE(*region.notePolyphony == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("Note selfmask")
|
||||||
|
{
|
||||||
|
REQUIRE(region.selfMask == SfzSelfMask::mask);
|
||||||
|
region.parseOpcode({ "note_selfmask", "off" });
|
||||||
|
REQUIRE(region.selfMask == SfzSelfMask::dontMask);
|
||||||
|
region.parseOpcode({ "note_selfmask", "on" });
|
||||||
|
REQUIRE(region.selfMask == SfzSelfMask::mask);
|
||||||
|
region.parseOpcode({ "note_selfmask", "off" });
|
||||||
|
region.parseOpcode({ "note_selfmask", "garbage" });
|
||||||
|
REQUIRE(region.selfMask == SfzSelfMask::dontMask);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("amplitude")
|
||||||
|
{
|
||||||
|
REQUIRE(region.amplitude == 1.0_a);
|
||||||
|
region.parseOpcode({ "amplitude", "40" });
|
||||||
|
REQUIRE(region.amplitude == 0.4_a);
|
||||||
|
region.parseOpcode({ "amplitude", "-40" });
|
||||||
|
REQUIRE(region.amplitude == 0_a);
|
||||||
|
region.parseOpcode({ "amplitude", "140" });
|
||||||
|
REQUIRE(region.amplitude == 1.0_a);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("amplitude_cc")
|
||||||
|
{
|
||||||
|
REQUIRE(region.amplitudeCC.empty());
|
||||||
|
region.parseOpcode({ "amplitude_cc1", "40" });
|
||||||
|
REQUIRE(region.amplitudeCC.contains(1));
|
||||||
|
REQUIRE(region.amplitudeCC[1] == 0.40_a);
|
||||||
|
region.parseOpcode({ "amplitude_oncc2", "30" });
|
||||||
|
REQUIRE(region.amplitudeCC.contains(2));
|
||||||
|
REQUIRE(region.amplitudeCC[2] == 0.30_a);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("volume_oncc/gain_cc")
|
||||||
|
{
|
||||||
|
REQUIRE(region.volumeCC.empty());
|
||||||
|
region.parseOpcode({ "gain_cc1", "40" });
|
||||||
|
REQUIRE(region.volumeCC.contains(1));
|
||||||
|
REQUIRE(region.volumeCC[1] == 40_a);
|
||||||
|
region.parseOpcode({ "volume_oncc2", "-76" });
|
||||||
|
REQUIRE(region.volumeCC.contains(2));
|
||||||
|
REQUIRE(region.volumeCC[2] == -76.0_a);
|
||||||
|
region.parseOpcode({ "gain_oncc4", "-1" });
|
||||||
|
REQUIRE(region.volumeCC.contains(4));
|
||||||
|
REQUIRE(region.volumeCC[4] == -1.0_a);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("tune_cc/pitch_cc")
|
||||||
|
{
|
||||||
|
REQUIRE(region.tuneCC.empty());
|
||||||
|
region.parseOpcode({ "pitch_cc1", "40" });
|
||||||
|
REQUIRE(region.tuneCC.contains(1));
|
||||||
|
REQUIRE(region.tuneCC[1] == 40);
|
||||||
|
region.parseOpcode({ "tune_oncc2", "-76" });
|
||||||
|
REQUIRE(region.tuneCC.contains(2));
|
||||||
|
REQUIRE(region.tuneCC[2] == -76.0);
|
||||||
|
region.parseOpcode({ "pitch_oncc4", "-1" });
|
||||||
|
REQUIRE(region.tuneCC.contains(4));
|
||||||
|
REQUIRE(region.tuneCC[4] == -1.0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Specific region bugs
|
// Specific region bugs
|
||||||
|
|
|
||||||
|
|
@ -21,9 +21,9 @@ TEST_CASE("[Region] Crossfade in on key")
|
||||||
region.parseOpcode({ "sample", "*sine" });
|
region.parseOpcode({ "sample", "*sine" });
|
||||||
region.parseOpcode({ "xfin_lokey", "1" });
|
region.parseOpcode({ "xfin_lokey", "1" });
|
||||||
region.parseOpcode({ "xfin_hikey", "3" });
|
region.parseOpcode({ "xfin_hikey", "3" });
|
||||||
REQUIRE( region.getNoteGain(2, 127_norm) == 0.70711_a );
|
REQUIRE(region.getNoteGain(2, 127_norm) == 0.70711_a);
|
||||||
REQUIRE( region.getNoteGain(1, 127_norm) == 0.0_a );
|
REQUIRE(region.getNoteGain(1, 127_norm) == 0.0_a);
|
||||||
REQUIRE( region.getNoteGain(3, 127_norm) == 1.0_a );
|
REQUIRE(region.getNoteGain(3, 127_norm) == 1.0_a);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("[Region] Crossfade in on key - 2")
|
TEST_CASE("[Region] Crossfade in on key - 2")
|
||||||
|
|
@ -33,12 +33,12 @@ TEST_CASE("[Region] Crossfade in on key - 2")
|
||||||
region.parseOpcode({ "sample", "*sine" });
|
region.parseOpcode({ "sample", "*sine" });
|
||||||
region.parseOpcode({ "xfin_lokey", "1" });
|
region.parseOpcode({ "xfin_lokey", "1" });
|
||||||
region.parseOpcode({ "xfin_hikey", "5" });
|
region.parseOpcode({ "xfin_hikey", "5" });
|
||||||
REQUIRE( region.getNoteGain(1, 127_norm) == 0.0_a );
|
REQUIRE(region.getNoteGain(1, 127_norm) == 0.0_a);
|
||||||
REQUIRE( region.getNoteGain(2, 127_norm) == 0.5_a );
|
REQUIRE(region.getNoteGain(2, 127_norm) == 0.5_a);
|
||||||
REQUIRE( region.getNoteGain(3, 127_norm) == 0.70711_a );
|
REQUIRE(region.getNoteGain(3, 127_norm) == 0.70711_a);
|
||||||
REQUIRE( region.getNoteGain(4, 127_norm) == 0.86603_a );
|
REQUIRE(region.getNoteGain(4, 127_norm) == 0.86603_a);
|
||||||
REQUIRE( region.getNoteGain(5, 127_norm) == 1.0_a );
|
REQUIRE(region.getNoteGain(5, 127_norm) == 1.0_a);
|
||||||
REQUIRE( region.getNoteGain(6, 127_norm) == 1.0_a );
|
REQUIRE(region.getNoteGain(6, 127_norm) == 1.0_a);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("[Region] Crossfade in on key - gain")
|
TEST_CASE("[Region] Crossfade in on key - gain")
|
||||||
|
|
@ -49,11 +49,11 @@ TEST_CASE("[Region] Crossfade in on key - gain")
|
||||||
region.parseOpcode({ "xfin_lokey", "1" });
|
region.parseOpcode({ "xfin_lokey", "1" });
|
||||||
region.parseOpcode({ "xfin_hikey", "5" });
|
region.parseOpcode({ "xfin_hikey", "5" });
|
||||||
region.parseOpcode({ "xf_keycurve", "gain" });
|
region.parseOpcode({ "xf_keycurve", "gain" });
|
||||||
REQUIRE( region.getNoteGain(1, 127_norm) == 0.0_a );
|
REQUIRE(region.getNoteGain(1, 127_norm) == 0.0_a);
|
||||||
REQUIRE( region.getNoteGain(2, 127_norm) == 0.25_a );
|
REQUIRE(region.getNoteGain(2, 127_norm) == 0.25_a);
|
||||||
REQUIRE( region.getNoteGain(3, 127_norm) == 0.5_a );
|
REQUIRE(region.getNoteGain(3, 127_norm) == 0.5_a);
|
||||||
REQUIRE( region.getNoteGain(4, 127_norm) == 0.75_a );
|
REQUIRE(region.getNoteGain(4, 127_norm) == 0.75_a);
|
||||||
REQUIRE( region.getNoteGain(5, 127_norm) == 1.0_a );
|
REQUIRE(region.getNoteGain(5, 127_norm) == 1.0_a);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("[Region] Crossfade out on key")
|
TEST_CASE("[Region] Crossfade out on key")
|
||||||
|
|
@ -63,13 +63,13 @@ TEST_CASE("[Region] Crossfade out on key")
|
||||||
region.parseOpcode({ "sample", "*sine" });
|
region.parseOpcode({ "sample", "*sine" });
|
||||||
region.parseOpcode({ "xfout_lokey", "51" });
|
region.parseOpcode({ "xfout_lokey", "51" });
|
||||||
region.parseOpcode({ "xfout_hikey", "55" });
|
region.parseOpcode({ "xfout_hikey", "55" });
|
||||||
REQUIRE( region.getNoteGain(50, 127_norm) == 1.0_a );
|
REQUIRE(region.getNoteGain(50, 127_norm) == 1.0_a);
|
||||||
REQUIRE( region.getNoteGain(51, 127_norm) == 1.0_a );
|
REQUIRE(region.getNoteGain(51, 127_norm) == 1.0_a);
|
||||||
REQUIRE( region.getNoteGain(52, 127_norm) == 0.86603_a );
|
REQUIRE(region.getNoteGain(52, 127_norm) == 0.86603_a);
|
||||||
REQUIRE( region.getNoteGain(53, 127_norm) == 0.70711_a );
|
REQUIRE(region.getNoteGain(53, 127_norm) == 0.70711_a);
|
||||||
REQUIRE( region.getNoteGain(54, 127_norm) == 0.5_a );
|
REQUIRE(region.getNoteGain(54, 127_norm) == 0.5_a);
|
||||||
REQUIRE( region.getNoteGain(55, 127_norm) == 0.0_a );
|
REQUIRE(region.getNoteGain(55, 127_norm) == 0.0_a);
|
||||||
REQUIRE( region.getNoteGain(56, 127_norm) == 0.0_a );
|
REQUIRE(region.getNoteGain(56, 127_norm) == 0.0_a);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("[Region] Crossfade out on key - gain")
|
TEST_CASE("[Region] Crossfade out on key - gain")
|
||||||
|
|
@ -80,13 +80,13 @@ TEST_CASE("[Region] Crossfade out on key - gain")
|
||||||
region.parseOpcode({ "xfout_lokey", "51" });
|
region.parseOpcode({ "xfout_lokey", "51" });
|
||||||
region.parseOpcode({ "xfout_hikey", "55" });
|
region.parseOpcode({ "xfout_hikey", "55" });
|
||||||
region.parseOpcode({ "xf_keycurve", "gain" });
|
region.parseOpcode({ "xf_keycurve", "gain" });
|
||||||
REQUIRE( region.getNoteGain(50, 127_norm) == 1.0_a );
|
REQUIRE(region.getNoteGain(50, 127_norm) == 1.0_a);
|
||||||
REQUIRE( region.getNoteGain(51, 127_norm) == 1.0_a );
|
REQUIRE(region.getNoteGain(51, 127_norm) == 1.0_a);
|
||||||
REQUIRE( region.getNoteGain(52, 127_norm) == 0.75_a );
|
REQUIRE(region.getNoteGain(52, 127_norm) == 0.75_a);
|
||||||
REQUIRE( region.getNoteGain(53, 127_norm) == 0.5_a );
|
REQUIRE(region.getNoteGain(53, 127_norm) == 0.5_a);
|
||||||
REQUIRE( region.getNoteGain(54, 127_norm) == 0.25_a );
|
REQUIRE(region.getNoteGain(54, 127_norm) == 0.25_a);
|
||||||
REQUIRE( region.getNoteGain(55, 127_norm) == 0.0_a );
|
REQUIRE(region.getNoteGain(55, 127_norm) == 0.0_a);
|
||||||
REQUIRE( region.getNoteGain(56, 127_norm) == 0.0_a );
|
REQUIRE(region.getNoteGain(56, 127_norm) == 0.0_a);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("[Region] Crossfade in on velocity")
|
TEST_CASE("[Region] Crossfade in on velocity")
|
||||||
|
|
@ -97,13 +97,13 @@ TEST_CASE("[Region] Crossfade in on velocity")
|
||||||
region.parseOpcode({ "xfin_lovel", "20" });
|
region.parseOpcode({ "xfin_lovel", "20" });
|
||||||
region.parseOpcode({ "xfin_hivel", "24" });
|
region.parseOpcode({ "xfin_hivel", "24" });
|
||||||
region.parseOpcode({ "amp_veltrack", "0" });
|
region.parseOpcode({ "amp_veltrack", "0" });
|
||||||
REQUIRE( region.getNoteGain(1, 19_norm) == 0.0_a );
|
REQUIRE(region.getNoteGain(1, 19_norm) == 0.0_a);
|
||||||
REQUIRE( region.getNoteGain(1, 20_norm) == 0.0_a );
|
REQUIRE(region.getNoteGain(1, 20_norm) == 0.0_a);
|
||||||
REQUIRE( region.getNoteGain(2, 21_norm) == 0.5_a );
|
REQUIRE(region.getNoteGain(2, 21_norm) == 0.5_a);
|
||||||
REQUIRE( region.getNoteGain(3, 22_norm) == 0.70711_a );
|
REQUIRE(region.getNoteGain(3, 22_norm) == 0.70711_a);
|
||||||
REQUIRE( region.getNoteGain(4, 23_norm) == 0.86603_a );
|
REQUIRE(region.getNoteGain(4, 23_norm) == 0.86603_a);
|
||||||
REQUIRE( region.getNoteGain(5, 24_norm) == 1.0_a );
|
REQUIRE(region.getNoteGain(5, 24_norm) == 1.0_a);
|
||||||
REQUIRE( region.getNoteGain(6, 25_norm) == 1.0_a );
|
REQUIRE(region.getNoteGain(6, 25_norm) == 1.0_a);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("[Region] Crossfade in on vel - gain")
|
TEST_CASE("[Region] Crossfade in on vel - gain")
|
||||||
|
|
@ -115,13 +115,13 @@ TEST_CASE("[Region] Crossfade in on vel - gain")
|
||||||
region.parseOpcode({ "xfin_hivel", "24" });
|
region.parseOpcode({ "xfin_hivel", "24" });
|
||||||
region.parseOpcode({ "xf_velcurve", "gain" });
|
region.parseOpcode({ "xf_velcurve", "gain" });
|
||||||
region.parseOpcode({ "amp_veltrack", "0" });
|
region.parseOpcode({ "amp_veltrack", "0" });
|
||||||
REQUIRE( region.getNoteGain(1, 19_norm) == 0.0_a );
|
REQUIRE(region.getNoteGain(1, 19_norm) == 0.0_a);
|
||||||
REQUIRE( region.getNoteGain(1, 20_norm) == 0.0_a );
|
REQUIRE(region.getNoteGain(1, 20_norm) == 0.0_a);
|
||||||
REQUIRE( region.getNoteGain(2, 21_norm) == 0.25_a );
|
REQUIRE(region.getNoteGain(2, 21_norm) == 0.25_a);
|
||||||
REQUIRE( region.getNoteGain(3, 22_norm) == 0.5_a );
|
REQUIRE(region.getNoteGain(3, 22_norm) == 0.5_a);
|
||||||
REQUIRE( region.getNoteGain(4, 23_norm) == 0.75_a );
|
REQUIRE(region.getNoteGain(4, 23_norm) == 0.75_a);
|
||||||
REQUIRE( region.getNoteGain(5, 24_norm) == 1.0_a );
|
REQUIRE(region.getNoteGain(5, 24_norm) == 1.0_a);
|
||||||
REQUIRE( region.getNoteGain(5, 25_norm) == 1.0_a );
|
REQUIRE(region.getNoteGain(5, 25_norm) == 1.0_a);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("[Region] Crossfade out on vel")
|
TEST_CASE("[Region] Crossfade out on vel")
|
||||||
|
|
@ -132,13 +132,13 @@ TEST_CASE("[Region] Crossfade out on vel")
|
||||||
region.parseOpcode({ "xfout_lovel", "51" });
|
region.parseOpcode({ "xfout_lovel", "51" });
|
||||||
region.parseOpcode({ "xfout_hivel", "55" });
|
region.parseOpcode({ "xfout_hivel", "55" });
|
||||||
region.parseOpcode({ "amp_veltrack", "0" });
|
region.parseOpcode({ "amp_veltrack", "0" });
|
||||||
REQUIRE( region.getNoteGain(5, 50_norm) == 1.0_a );
|
REQUIRE(region.getNoteGain(5, 50_norm) == 1.0_a);
|
||||||
REQUIRE( region.getNoteGain(5, 51_norm) == 1.0_a );
|
REQUIRE(region.getNoteGain(5, 51_norm) == 1.0_a);
|
||||||
REQUIRE( region.getNoteGain(5, 52_norm) == 0.86603_a );
|
REQUIRE(region.getNoteGain(5, 52_norm) == 0.86603_a);
|
||||||
REQUIRE( region.getNoteGain(5, 53_norm) == 0.70711_a );
|
REQUIRE(region.getNoteGain(5, 53_norm) == 0.70711_a);
|
||||||
REQUIRE( region.getNoteGain(5, 54_norm) == 0.5_a );
|
REQUIRE(region.getNoteGain(5, 54_norm) == 0.5_a);
|
||||||
REQUIRE( region.getNoteGain(5, 55_norm) == 0.0_a );
|
REQUIRE(region.getNoteGain(5, 55_norm) == 0.0_a);
|
||||||
REQUIRE( region.getNoteGain(5, 56_norm) == 0.0_a );
|
REQUIRE(region.getNoteGain(5, 56_norm) == 0.0_a);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("[Region] Crossfade out on vel - gain")
|
TEST_CASE("[Region] Crossfade out on vel - gain")
|
||||||
|
|
@ -150,13 +150,13 @@ TEST_CASE("[Region] Crossfade out on vel - gain")
|
||||||
region.parseOpcode({ "xfout_hivel", "55" });
|
region.parseOpcode({ "xfout_hivel", "55" });
|
||||||
region.parseOpcode({ "xf_velcurve", "gain" });
|
region.parseOpcode({ "xf_velcurve", "gain" });
|
||||||
region.parseOpcode({ "amp_veltrack", "0" });
|
region.parseOpcode({ "amp_veltrack", "0" });
|
||||||
REQUIRE( region.getNoteGain(56, 50_norm) == 1.0_a );
|
REQUIRE(region.getNoteGain(56, 50_norm) == 1.0_a);
|
||||||
REQUIRE( region.getNoteGain(56, 51_norm) == 1.0_a );
|
REQUIRE(region.getNoteGain(56, 51_norm) == 1.0_a);
|
||||||
REQUIRE( region.getNoteGain(56, 52_norm) == 0.75_a );
|
REQUIRE(region.getNoteGain(56, 52_norm) == 0.75_a);
|
||||||
REQUIRE( region.getNoteGain(56, 53_norm) == 0.5_a );
|
REQUIRE(region.getNoteGain(56, 53_norm) == 0.5_a);
|
||||||
REQUIRE( region.getNoteGain(56, 54_norm) == 0.25_a );
|
REQUIRE(region.getNoteGain(56, 54_norm) == 0.25_a);
|
||||||
REQUIRE( region.getNoteGain(56, 55_norm) == 0.0_a );
|
REQUIRE(region.getNoteGain(56, 55_norm) == 0.0_a);
|
||||||
REQUIRE( region.getNoteGain(56, 56_norm) == 0.0_a );
|
REQUIRE(region.getNoteGain(56, 56_norm) == 0.0_a);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("[Region] Crossfade in on CC")
|
TEST_CASE("[Region] Crossfade in on CC")
|
||||||
|
|
@ -167,13 +167,20 @@ TEST_CASE("[Region] Crossfade in on CC")
|
||||||
region.parseOpcode({ "xfin_locc24", "20" });
|
region.parseOpcode({ "xfin_locc24", "20" });
|
||||||
region.parseOpcode({ "xfin_hicc24", "24" });
|
region.parseOpcode({ "xfin_hicc24", "24" });
|
||||||
region.parseOpcode({ "amp_veltrack", "0" });
|
region.parseOpcode({ "amp_veltrack", "0" });
|
||||||
midiState.ccEvent(0, 24, 19_norm); REQUIRE( region.getCrossfadeGain() == 0.0_a );
|
midiState.ccEvent(0, 24, 19_norm);
|
||||||
midiState.ccEvent(0, 24, 20_norm); REQUIRE( region.getCrossfadeGain() == 0.0_a );
|
REQUIRE(region.getCrossfadeGain() == 0.0_a);
|
||||||
midiState.ccEvent(0, 24, 21_norm); REQUIRE( region.getCrossfadeGain() == 0.5_a );
|
midiState.ccEvent(0, 24, 20_norm);
|
||||||
midiState.ccEvent(0, 24, 22_norm); REQUIRE( region.getCrossfadeGain() == 0.70711_a );
|
REQUIRE(region.getCrossfadeGain() == 0.0_a);
|
||||||
midiState.ccEvent(0, 24, 23_norm); REQUIRE( region.getCrossfadeGain() == 0.86603_a );
|
midiState.ccEvent(0, 24, 21_norm);
|
||||||
midiState.ccEvent(0, 24, 24_norm); REQUIRE( region.getCrossfadeGain() == 1.0_a );
|
REQUIRE(region.getCrossfadeGain() == 0.5_a);
|
||||||
midiState.ccEvent(0, 24, 25_norm); REQUIRE( region.getCrossfadeGain() == 1.0_a );
|
midiState.ccEvent(0, 24, 22_norm);
|
||||||
|
REQUIRE(region.getCrossfadeGain() == 0.70711_a);
|
||||||
|
midiState.ccEvent(0, 24, 23_norm);
|
||||||
|
REQUIRE(region.getCrossfadeGain() == 0.86603_a);
|
||||||
|
midiState.ccEvent(0, 24, 24_norm);
|
||||||
|
REQUIRE(region.getCrossfadeGain() == 1.0_a);
|
||||||
|
midiState.ccEvent(0, 24, 25_norm);
|
||||||
|
REQUIRE(region.getCrossfadeGain() == 1.0_a);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("[Region] Crossfade in on CC - gain")
|
TEST_CASE("[Region] Crossfade in on CC - gain")
|
||||||
|
|
@ -185,13 +192,20 @@ TEST_CASE("[Region] Crossfade in on CC - gain")
|
||||||
region.parseOpcode({ "xfin_hicc24", "24" });
|
region.parseOpcode({ "xfin_hicc24", "24" });
|
||||||
region.parseOpcode({ "amp_veltrack", "0" });
|
region.parseOpcode({ "amp_veltrack", "0" });
|
||||||
region.parseOpcode({ "xf_cccurve", "gain" });
|
region.parseOpcode({ "xf_cccurve", "gain" });
|
||||||
midiState.ccEvent(0, 24, 19_norm); REQUIRE( region.getCrossfadeGain() == 0.0_a );
|
midiState.ccEvent(0, 24, 19_norm);
|
||||||
midiState.ccEvent(0, 24, 20_norm); REQUIRE( region.getCrossfadeGain() == 0.0_a );
|
REQUIRE(region.getCrossfadeGain() == 0.0_a);
|
||||||
midiState.ccEvent(0, 24, 21_norm); REQUIRE( region.getCrossfadeGain() == 0.25_a );
|
midiState.ccEvent(0, 24, 20_norm);
|
||||||
midiState.ccEvent(0, 24, 22_norm); REQUIRE( region.getCrossfadeGain() == 0.5_a );
|
REQUIRE(region.getCrossfadeGain() == 0.0_a);
|
||||||
midiState.ccEvent(0, 24, 23_norm); REQUIRE( region.getCrossfadeGain() == 0.75_a );
|
midiState.ccEvent(0, 24, 21_norm);
|
||||||
midiState.ccEvent(0, 24, 24_norm); REQUIRE( region.getCrossfadeGain() == 1.0_a );
|
REQUIRE(region.getCrossfadeGain() == 0.25_a);
|
||||||
midiState.ccEvent(0, 24, 25_norm); REQUIRE( region.getCrossfadeGain() == 1.0_a );
|
midiState.ccEvent(0, 24, 22_norm);
|
||||||
|
REQUIRE(region.getCrossfadeGain() == 0.5_a);
|
||||||
|
midiState.ccEvent(0, 24, 23_norm);
|
||||||
|
REQUIRE(region.getCrossfadeGain() == 0.75_a);
|
||||||
|
midiState.ccEvent(0, 24, 24_norm);
|
||||||
|
REQUIRE(region.getCrossfadeGain() == 1.0_a);
|
||||||
|
midiState.ccEvent(0, 24, 25_norm);
|
||||||
|
REQUIRE(region.getCrossfadeGain() == 1.0_a);
|
||||||
}
|
}
|
||||||
TEST_CASE("[Region] Crossfade out on CC")
|
TEST_CASE("[Region] Crossfade out on CC")
|
||||||
{
|
{
|
||||||
|
|
@ -201,13 +215,20 @@ TEST_CASE("[Region] Crossfade out on CC")
|
||||||
region.parseOpcode({ "xfout_locc24", "20" });
|
region.parseOpcode({ "xfout_locc24", "20" });
|
||||||
region.parseOpcode({ "xfout_hicc24", "24" });
|
region.parseOpcode({ "xfout_hicc24", "24" });
|
||||||
region.parseOpcode({ "amp_veltrack", "0" });
|
region.parseOpcode({ "amp_veltrack", "0" });
|
||||||
midiState.ccEvent(0, 24, 19_norm); REQUIRE( region.getCrossfadeGain() == 1.0_a );
|
midiState.ccEvent(0, 24, 19_norm);
|
||||||
midiState.ccEvent(0, 24, 20_norm); REQUIRE( region.getCrossfadeGain() == 1.0_a );
|
REQUIRE(region.getCrossfadeGain() == 1.0_a);
|
||||||
midiState.ccEvent(0, 24, 21_norm); REQUIRE( region.getCrossfadeGain() == 0.86603_a );
|
midiState.ccEvent(0, 24, 20_norm);
|
||||||
midiState.ccEvent(0, 24, 22_norm); REQUIRE( region.getCrossfadeGain() == 0.70711_a );
|
REQUIRE(region.getCrossfadeGain() == 1.0_a);
|
||||||
midiState.ccEvent(0, 24, 23_norm); REQUIRE( region.getCrossfadeGain() == 0.5_a );
|
midiState.ccEvent(0, 24, 21_norm);
|
||||||
midiState.ccEvent(0, 24, 24_norm); REQUIRE( region.getCrossfadeGain() == 0.0_a );
|
REQUIRE(region.getCrossfadeGain() == 0.86603_a);
|
||||||
midiState.ccEvent(0, 24, 25_norm); REQUIRE( region.getCrossfadeGain() == 0.0_a );
|
midiState.ccEvent(0, 24, 22_norm);
|
||||||
|
REQUIRE(region.getCrossfadeGain() == 0.70711_a);
|
||||||
|
midiState.ccEvent(0, 24, 23_norm);
|
||||||
|
REQUIRE(region.getCrossfadeGain() == 0.5_a);
|
||||||
|
midiState.ccEvent(0, 24, 24_norm);
|
||||||
|
REQUIRE(region.getCrossfadeGain() == 0.0_a);
|
||||||
|
midiState.ccEvent(0, 24, 25_norm);
|
||||||
|
REQUIRE(region.getCrossfadeGain() == 0.0_a);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("[Region] Crossfade out on CC - gain")
|
TEST_CASE("[Region] Crossfade out on CC - gain")
|
||||||
|
|
@ -219,13 +240,20 @@ TEST_CASE("[Region] Crossfade out on CC - gain")
|
||||||
region.parseOpcode({ "xfout_hicc24", "24" });
|
region.parseOpcode({ "xfout_hicc24", "24" });
|
||||||
region.parseOpcode({ "amp_veltrack", "0" });
|
region.parseOpcode({ "amp_veltrack", "0" });
|
||||||
region.parseOpcode({ "xf_cccurve", "gain" });
|
region.parseOpcode({ "xf_cccurve", "gain" });
|
||||||
midiState.ccEvent(0, 24, 19_norm); REQUIRE( region.getCrossfadeGain() == 1.0_a );
|
midiState.ccEvent(0, 24, 19_norm);
|
||||||
midiState.ccEvent(0, 24, 20_norm); REQUIRE( region.getCrossfadeGain() == 1.0_a );
|
REQUIRE(region.getCrossfadeGain() == 1.0_a);
|
||||||
midiState.ccEvent(0, 24, 21_norm); REQUIRE( region.getCrossfadeGain() == 0.75_a );
|
midiState.ccEvent(0, 24, 20_norm);
|
||||||
midiState.ccEvent(0, 24, 22_norm); REQUIRE( region.getCrossfadeGain() == 0.5_a );
|
REQUIRE(region.getCrossfadeGain() == 1.0_a);
|
||||||
midiState.ccEvent(0, 24, 23_norm); REQUIRE( region.getCrossfadeGain() == 0.25_a );
|
midiState.ccEvent(0, 24, 21_norm);
|
||||||
midiState.ccEvent(0, 24, 24_norm); REQUIRE( region.getCrossfadeGain() == 0.0_a );
|
REQUIRE(region.getCrossfadeGain() == 0.75_a);
|
||||||
midiState.ccEvent(0, 24, 25_norm); REQUIRE( region.getCrossfadeGain() == 0.0_a );
|
midiState.ccEvent(0, 24, 22_norm);
|
||||||
|
REQUIRE(region.getCrossfadeGain() == 0.5_a);
|
||||||
|
midiState.ccEvent(0, 24, 23_norm);
|
||||||
|
REQUIRE(region.getCrossfadeGain() == 0.25_a);
|
||||||
|
midiState.ccEvent(0, 24, 24_norm);
|
||||||
|
REQUIRE(region.getCrossfadeGain() == 0.0_a);
|
||||||
|
midiState.ccEvent(0, 24, 25_norm);
|
||||||
|
REQUIRE(region.getCrossfadeGain() == 0.0_a);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("[Region] Velocity bug for extreme values - veltrack at 0")
|
TEST_CASE("[Region] Velocity bug for extreme values - veltrack at 0")
|
||||||
|
|
@ -234,8 +262,8 @@ TEST_CASE("[Region] Velocity bug for extreme values - veltrack at 0")
|
||||||
sfz::Region region { midiState };
|
sfz::Region region { midiState };
|
||||||
region.parseOpcode({ "sample", "*sine" });
|
region.parseOpcode({ "sample", "*sine" });
|
||||||
region.parseOpcode({ "amp_veltrack", "0" });
|
region.parseOpcode({ "amp_veltrack", "0" });
|
||||||
REQUIRE( region.getNoteGain(64, 127_norm) == 1.0_a );
|
REQUIRE(region.getNoteGain(64, 127_norm) == 1.0_a);
|
||||||
REQUIRE( region.getNoteGain(64, 0_norm) == 1.0_a );
|
REQUIRE(region.getNoteGain(64, 0_norm) == 1.0_a);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -245,8 +273,8 @@ TEST_CASE("[Region] Velocity bug for extreme values - positive veltrack")
|
||||||
sfz::Region region { midiState };
|
sfz::Region region { midiState };
|
||||||
region.parseOpcode({ "sample", "*sine" });
|
region.parseOpcode({ "sample", "*sine" });
|
||||||
region.parseOpcode({ "amp_veltrack", "100" });
|
region.parseOpcode({ "amp_veltrack", "100" });
|
||||||
REQUIRE( region.getNoteGain(64, 127_norm) == 1.0_a );
|
REQUIRE(region.getNoteGain(64, 127_norm) == 1.0_a);
|
||||||
REQUIRE( region.getNoteGain(64, 0_norm) == Approx(0.0).margin(0.0001) );
|
REQUIRE(region.getNoteGain(64, 0_norm) == Approx(0.0).margin(0.0001));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("[Region] Velocity bug for extreme values - negative veltrack")
|
TEST_CASE("[Region] Velocity bug for extreme values - negative veltrack")
|
||||||
|
|
@ -255,27 +283,28 @@ TEST_CASE("[Region] Velocity bug for extreme values - negative veltrack")
|
||||||
sfz::Region region { midiState };
|
sfz::Region region { midiState };
|
||||||
region.parseOpcode({ "sample", "*sine" });
|
region.parseOpcode({ "sample", "*sine" });
|
||||||
region.parseOpcode({ "amp_veltrack", "-100" });
|
region.parseOpcode({ "amp_veltrack", "-100" });
|
||||||
REQUIRE( region.getNoteGain(64, 127_norm) == Approx(0.0).margin(0.0001) );
|
REQUIRE(region.getNoteGain(64, 127_norm) == Approx(0.0).margin(0.0001));
|
||||||
REQUIRE( region.getNoteGain(64, 0_norm) == 1.0_a );
|
REQUIRE(region.getNoteGain(64, 0_norm) == 1.0_a);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("[Region] rt_decay")
|
TEST_CASE("[Region] rt_decay")
|
||||||
{
|
{
|
||||||
sfz::MidiState midiState;
|
sfz::MidiState midiState;
|
||||||
|
midiState.setSampleRate(1000);
|
||||||
sfz::Region region { midiState };
|
sfz::Region region { midiState };
|
||||||
region.parseOpcode({ "sample", "*sine" });
|
region.parseOpcode({ "sample", "*sine" });
|
||||||
region.parseOpcode({ "trigger", "release" });
|
region.parseOpcode({ "trigger", "release" });
|
||||||
region.parseOpcode({ "rt_decay", "10" });
|
region.parseOpcode({ "rt_decay", "10" });
|
||||||
midiState.noteOnEvent(0, 64, 64_norm);
|
midiState.noteOnEvent(0, 64, 64_norm);
|
||||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
midiState.advanceTime(100);
|
||||||
REQUIRE( region.getBaseVolumedB(64) == Approx(sfz::Default::volume - 1.0f).margin(0.1) );
|
REQUIRE( region.getBaseVolumedB(64) == Approx(sfz::Default::volume - 1.0f).margin(0.1) );
|
||||||
region.parseOpcode({ "rt_decay", "20" });
|
region.parseOpcode({ "rt_decay", "20" });
|
||||||
midiState.noteOnEvent(0, 64, 64_norm);
|
midiState.noteOnEvent(0, 64, 64_norm);
|
||||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
midiState.advanceTime(100);
|
||||||
REQUIRE( region.getBaseVolumedB(64) == Approx(sfz::Default::volume - 2.0f).margin(0.1) );
|
REQUIRE( region.getBaseVolumedB(64) == Approx(sfz::Default::volume - 2.0f).margin(0.1) );
|
||||||
region.parseOpcode({ "trigger", "attack" });
|
region.parseOpcode({ "trigger", "attack" });
|
||||||
midiState.noteOnEvent(0, 64, 64_norm);
|
midiState.noteOnEvent(0, 64, 64_norm);
|
||||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
midiState.advanceTime(100);
|
||||||
REQUIRE( region.getBaseVolumedB(64) == Approx(sfz::Default::volume).margin(0.1) );
|
REQUIRE( region.getBaseVolumedB(64) == Approx(sfz::Default::volume).margin(0.1) );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -499,7 +499,7 @@ TEST_CASE("[Helpers] Linear Ramp")
|
||||||
const float start { 0.0f };
|
const float start { 0.0f };
|
||||||
const float v { fillValue };
|
const float v { fillValue };
|
||||||
std::array<float, 6> output;
|
std::array<float, 6> output;
|
||||||
std::array<float, 6> expected { v, v + v, v + v + v, v + v + v + v, v + v + v + v + v, v + v + v + v + v + v };
|
std::array<float, 6> expected { start, start + v, start + v + v, start + v + v + v, start + v + v + v + v, start + v + v + v + v + v };
|
||||||
sfz::linearRamp<float, false>(absl::MakeSpan(output), start, v);
|
sfz::linearRamp<float, false>(absl::MakeSpan(output), start, v);
|
||||||
REQUIRE(output == expected);
|
REQUIRE(output == expected);
|
||||||
}
|
}
|
||||||
|
|
@ -509,7 +509,7 @@ TEST_CASE("[Helpers] Linear Ramp (SIMD)")
|
||||||
const float start { 0.0f };
|
const float start { 0.0f };
|
||||||
const float v { fillValue };
|
const float v { fillValue };
|
||||||
std::array<float, 6> output;
|
std::array<float, 6> output;
|
||||||
std::array<float, 6> expected { v, v + v, v + v + v, v + v + v + v, v + v + v + v + v, v + v + v + v + v + v };
|
std::array<float, 6> expected { start, start + v, start + v + v, start + v + v + v, start + v + v + v + v, start + v + v + v + v + v };
|
||||||
sfz::linearRamp<float, true>(absl::MakeSpan(output), start, v);
|
sfz::linearRamp<float, true>(absl::MakeSpan(output), start, v);
|
||||||
REQUIRE(approxEqual<float>(output, expected));
|
REQUIRE(approxEqual<float>(output, expected));
|
||||||
}
|
}
|
||||||
|
|
@ -539,7 +539,7 @@ TEST_CASE("[Helpers] Multiplicative Ramp")
|
||||||
const float start { 1.0f };
|
const float start { 1.0f };
|
||||||
const float v { fillValue };
|
const float v { fillValue };
|
||||||
std::array<float, 6> output;
|
std::array<float, 6> output;
|
||||||
std::array<float, 6> expected { v, v * v, v * v * v, v * v * v * v, v * v * v * v * v, v * v * v * v * v * v };
|
std::array<float, 6> expected { start, start * v, start * v * v, start * v * v * v, start * v * v * v * v, start * v * v * v * v * v };
|
||||||
sfz::multiplicativeRamp<float, false>(absl::MakeSpan(output), start, v);
|
sfz::multiplicativeRamp<float, false>(absl::MakeSpan(output), start, v);
|
||||||
REQUIRE(approxEqual<float>(output, expected));
|
REQUIRE(approxEqual<float>(output, expected));
|
||||||
}
|
}
|
||||||
|
|
@ -549,7 +549,7 @@ TEST_CASE("[Helpers] Multiplicative Ramp (SIMD)")
|
||||||
const float start { 1.0f };
|
const float start { 1.0f };
|
||||||
const float v { fillValue };
|
const float v { fillValue };
|
||||||
std::array<float, 6> output;
|
std::array<float, 6> output;
|
||||||
std::array<float, 6> expected { v, v * v, v * v * v, v * v * v * v, v * v * v * v * v, v * v * v * v * v * v };
|
std::array<float, 6> expected { start, start * v, start * v * v, start * v * v * v, start * v * v * v * v, start * v * v * v * v * v };
|
||||||
sfz::multiplicativeRamp<float, true>(absl::MakeSpan(output), start, v);
|
sfz::multiplicativeRamp<float, true>(absl::MakeSpan(output), start, v);
|
||||||
REQUIRE(approxEqual<float>(output, expected));
|
REQUIRE(approxEqual<float>(output, expected));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -141,9 +141,9 @@ TEST_CASE("[Synth] Reset all controllers")
|
||||||
{
|
{
|
||||||
sfz::Synth synth;
|
sfz::Synth synth;
|
||||||
synth.cc(0, 12, 64);
|
synth.cc(0, 12, 64);
|
||||||
REQUIRE( synth.getMidiState().getCCValue(12) == 64_norm );
|
REQUIRE(synth.getMidiState().getCCValue(12) == 64_norm);
|
||||||
synth.cc(0, 121, 64);
|
synth.cc(0, 121, 64);
|
||||||
REQUIRE( synth.getMidiState().getCCValue(12) == 0_norm );
|
REQUIRE(synth.getMidiState().getCCValue(12) == 0_norm);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("[Synth] Releasing before the EG started smoothing (initial delay) kills the voice")
|
TEST_CASE("[Synth] Releasing before the EG started smoothing (initial delay) kills the voice")
|
||||||
|
|
@ -332,3 +332,45 @@ TEST_CASE("[Synth] Gain to mix")
|
||||||
REQUIRE( bus->gainToMain() == 0 );
|
REQUIRE( bus->gainToMain() == 0 );
|
||||||
REQUIRE( bus->gainToMix() == 0.5 );
|
REQUIRE( bus->gainToMix() == 0.5 );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE("[Synth] group polyphony limits")
|
||||||
|
{
|
||||||
|
sfz::Synth synth;
|
||||||
|
synth.loadSfzFile(fs::current_path() / "tests/TestFiles/polyphony.sfz");
|
||||||
|
synth.noteOn(0, 65, 64);
|
||||||
|
synth.noteOn(0, 65, 64);
|
||||||
|
synth.noteOn(0, 65, 64);
|
||||||
|
REQUIRE(synth.getNumActiveVoices() == 2); // group polyphony should block the last note
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("[Synth] Self-masking")
|
||||||
|
{
|
||||||
|
sfz::Synth synth;
|
||||||
|
synth.loadSfzFile(fs::current_path() / "tests/TestFiles/polyphony.sfz");
|
||||||
|
synth.noteOn(0, 64, 63);
|
||||||
|
synth.noteOn(0, 64, 62);
|
||||||
|
synth.noteOn(0, 64, 64);
|
||||||
|
REQUIRE(synth.getNumActiveVoices() == 3); // One of these is releasing
|
||||||
|
REQUIRE(synth.getVoiceView(0)->getTriggerValue() == 63_norm);
|
||||||
|
REQUIRE(!synth.getVoiceView(0)->canBeStolen());
|
||||||
|
REQUIRE(synth.getVoiceView(1)->getTriggerValue() == 62_norm);
|
||||||
|
REQUIRE(synth.getVoiceView(1)->canBeStolen()); // The lowest velocity voice is the masking candidate
|
||||||
|
REQUIRE(synth.getVoiceView(2)->getTriggerValue() == 64_norm);
|
||||||
|
REQUIRE(!synth.getVoiceView(2)->canBeStolen());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("[Synth] Not self-masking")
|
||||||
|
{
|
||||||
|
sfz::Synth synth;
|
||||||
|
synth.loadSfzFile(fs::current_path() / "tests/TestFiles/polyphony.sfz");
|
||||||
|
synth.noteOn(0, 66, 63);
|
||||||
|
synth.noteOn(0, 66, 62);
|
||||||
|
synth.noteOn(0, 66, 64);
|
||||||
|
REQUIRE(synth.getNumActiveVoices() == 3); // One of these is releasing
|
||||||
|
REQUIRE(synth.getVoiceView(0)->getTriggerValue() == 63_norm);
|
||||||
|
REQUIRE(synth.getVoiceView(0)->canBeStolen()); // The first encountered voice is the masking candidate
|
||||||
|
REQUIRE(synth.getVoiceView(1)->getTriggerValue() == 62_norm);
|
||||||
|
REQUIRE(!synth.getVoiceView(1)->canBeStolen());
|
||||||
|
REQUIRE(synth.getVoiceView(2)->getTriggerValue() == 64_norm);
|
||||||
|
REQUIRE(!synth.getVoiceView(2)->canBeStolen());
|
||||||
|
}
|
||||||
|
|
|
||||||
5
tests/TestFiles/polyphony.sfz
Normal file
5
tests/TestFiles/polyphony.sfz
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
<region> sample=*sine key=63
|
||||||
|
<region> sample=*sine key=64 note_polyphony=2
|
||||||
|
<region> sample=*sine key=66 note_polyphony=2 note_selfmask=off
|
||||||
|
<group> group=1 polyphony=2
|
||||||
|
<region> sample=*sine key=65
|
||||||
Loading…
Add table
Reference in a new issue