Merge pull request #56 from paulfd/width-position
Add pan width and position using the new helpers
This commit is contained in:
commit
00c3af6881
12 changed files with 596 additions and 104 deletions
|
|
@ -12,6 +12,7 @@
|
|||
#include <cmath>
|
||||
#include <iostream>
|
||||
#include "Config.h"
|
||||
#include "ScopedFTZ.h"
|
||||
#include "absl/types/span.h"
|
||||
|
||||
class PanArray : public benchmark::Fixture {
|
||||
|
|
@ -47,6 +48,7 @@ public:
|
|||
|
||||
|
||||
BENCHMARK_DEFINE_F(PanArray, Scalar)(benchmark::State& state) {
|
||||
ScopedFTZ ftz;
|
||||
for (auto _ : state)
|
||||
{
|
||||
sfz::pan<float, false>(pan, absl::MakeSpan(left), absl::MakeSpan(right));
|
||||
|
|
@ -54,6 +56,7 @@ BENCHMARK_DEFINE_F(PanArray, Scalar)(benchmark::State& state) {
|
|||
}
|
||||
|
||||
BENCHMARK_DEFINE_F(PanArray, SIMD)(benchmark::State& state) {
|
||||
ScopedFTZ ftz;
|
||||
for (auto _ : state)
|
||||
{
|
||||
sfz::pan<float, true>(pan, absl::MakeSpan(left), absl::MakeSpan(right));
|
||||
|
|
@ -61,6 +64,7 @@ BENCHMARK_DEFINE_F(PanArray, SIMD)(benchmark::State& state) {
|
|||
}
|
||||
|
||||
BENCHMARK_DEFINE_F(PanArray, BlockOps)(benchmark::State& state) {
|
||||
ScopedFTZ ftz;
|
||||
for (auto _ : state)
|
||||
{
|
||||
sfz::fill<float>(span2, 1.0f);
|
||||
|
|
|
|||
80
benchmarks/BM_widthPos.cpp
Normal file
80
benchmarks/BM_widthPos.cpp
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
// 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 "SIMDHelpers.h"
|
||||
#include <benchmark/benchmark.h>
|
||||
#include <random>
|
||||
#include <numeric>
|
||||
#include <vector>
|
||||
#include <cmath>
|
||||
#include <iostream>
|
||||
#include "Config.h"
|
||||
#include "ScopedFTZ.h"
|
||||
#include "absl/types/span.h"
|
||||
|
||||
class WidthPosArray : public benchmark::Fixture {
|
||||
public:
|
||||
void SetUp(const ::benchmark::State& state) {
|
||||
std::random_device rd { };
|
||||
std::mt19937 gen { rd() };
|
||||
std::uniform_real_distribution<float> dist { 0.001f, 1.0f };
|
||||
width = std::vector<float>(state.range(0));
|
||||
position = std::vector<float>(state.range(0));
|
||||
left = std::vector<float>(state.range(0));
|
||||
right = std::vector<float>(state.range(0));
|
||||
std::generate(width.begin(), width.end(), [&]() { return dist(gen); });
|
||||
std::generate(position.begin(), position.end(), [&]() { return dist(gen); });
|
||||
std::generate(right.begin(), right.end(), [&]() { return dist(gen); });
|
||||
std::generate(left.begin(), left.end(), [&]() { return dist(gen); });
|
||||
temp1 = std::vector<float>(state.range(0));
|
||||
temp2 = std::vector<float>(state.range(0));
|
||||
temp3 = std::vector<float>(state.range(0));
|
||||
span1 = absl::MakeSpan(temp1);
|
||||
span2 = absl::MakeSpan(temp2);
|
||||
span3 = absl::MakeSpan(temp3);
|
||||
}
|
||||
|
||||
void TearDown(const ::benchmark::State& state [[maybe_unused]]) {
|
||||
|
||||
}
|
||||
|
||||
std::vector<float> width;
|
||||
std::vector<float> position;
|
||||
std::vector<float> left;
|
||||
std::vector<float> right;
|
||||
std::vector<float> temp1;
|
||||
std::vector<float> temp2;
|
||||
std::vector<float> temp3;
|
||||
absl::Span<float> span1;
|
||||
absl::Span<float> span2;
|
||||
absl::Span<float> span3;
|
||||
};
|
||||
|
||||
BENCHMARK_DEFINE_F(WidthPosArray, Scalar)(benchmark::State& state) {
|
||||
ScopedFTZ ftz;
|
||||
const auto leftBuffer = absl::MakeSpan(left);
|
||||
const auto rightBuffer = absl::MakeSpan(right);
|
||||
for (auto _ : state)
|
||||
{
|
||||
sfz::width<float, false>(width, leftBuffer, rightBuffer);
|
||||
sfz::pan<float, false>(position, leftBuffer, rightBuffer);
|
||||
}
|
||||
}
|
||||
|
||||
BENCHMARK_DEFINE_F(WidthPosArray, SIMD)(benchmark::State& state) {
|
||||
ScopedFTZ ftz;
|
||||
const auto leftBuffer = absl::MakeSpan(left);
|
||||
const auto rightBuffer = absl::MakeSpan(right);
|
||||
for (auto _ : state)
|
||||
{
|
||||
sfz::width<float, true>(width, leftBuffer, rightBuffer);
|
||||
sfz::pan<float, true>(position, leftBuffer, rightBuffer);
|
||||
}
|
||||
}
|
||||
|
||||
BENCHMARK_REGISTER_F(WidthPosArray, Scalar)->RangeMultiplier(4)->Range(1 << 2, 1 << 12);
|
||||
BENCHMARK_REGISTER_F(WidthPosArray, SIMD)->RangeMultiplier(4)->Range(1 << 2, 1 << 12);
|
||||
BENCHMARK_MAIN();
|
||||
|
|
@ -51,6 +51,7 @@ sfizz_add_benchmark(bm_mean BM_mean.cpp)
|
|||
sfizz_add_benchmark(bm_meanSquared BM_meanSquared.cpp)
|
||||
sfizz_add_benchmark(bm_cumsum BM_cumsum.cpp)
|
||||
sfizz_add_benchmark(bm_diff BM_diff.cpp)
|
||||
sfizz_add_benchmark(bm_widthPos BM_widthPos.cpp)
|
||||
sfizz_add_benchmark(bm_interpolationCast BM_interpolationCast.cpp)
|
||||
sfizz_add_benchmark(bm_pointerIterationOrOffsets BM_pointerIterationOrOffsets.cpp)
|
||||
|
||||
|
|
@ -106,6 +107,7 @@ add_dependencies(sfizz_benchmarks
|
|||
bm_resampleChunk
|
||||
bm_envelopes
|
||||
bm_wavfile
|
||||
bm_widthPos
|
||||
bm_flacfile
|
||||
bm_filterModulation
|
||||
bm_filterStereoMono
|
||||
|
|
|
|||
|
|
@ -81,7 +81,7 @@ namespace SIMDConfig {
|
|||
constexpr bool subtract { false };
|
||||
constexpr bool multiplyAdd { false };
|
||||
constexpr bool copy { false };
|
||||
constexpr bool pan { true };
|
||||
constexpr bool pan { false };
|
||||
constexpr bool cumsum { true };
|
||||
constexpr bool diff { false };
|
||||
constexpr bool sfzInterpolationCast { true };
|
||||
|
|
|
|||
|
|
@ -743,20 +743,33 @@ namespace _internals {
|
|||
}();
|
||||
|
||||
template <class T>
|
||||
inline void snippetPan(const T*& pan, T*& left, T*& right)
|
||||
inline T panLookup(T pan)
|
||||
{
|
||||
T p = ((*pan++) + T{1.0}) * T{0.5};
|
||||
p = clamp<T>(p, 0, 1);
|
||||
// reduce range, round to nearest
|
||||
int index = static_cast<int>(T{0.5} + pan * (panSize - 1));
|
||||
return panData[index];
|
||||
}
|
||||
|
||||
auto lookUp = [](T pan) -> T
|
||||
{
|
||||
// reduce range, round to nearest
|
||||
int index = static_cast<int>(T{0.5} + pan * (panSize - 1));
|
||||
return panData[index];
|
||||
};
|
||||
template <class T>
|
||||
inline void snippetPan(T pan, T& left, T& right)
|
||||
{
|
||||
pan = (pan + T{1.0}) * T{0.5};
|
||||
pan = clamp<T>(pan, 0, 1);
|
||||
left *= panLookup(pan);
|
||||
right *= panLookup(1 - pan);
|
||||
}
|
||||
|
||||
*left++ = lookUp(p);
|
||||
*right++ = lookUp(1 - p);
|
||||
template <class T>
|
||||
inline void snippetWidth(T width, T& left, T& right)
|
||||
{
|
||||
T w = (width + T{1.0}) * T{0.5};
|
||||
w = clamp<T>(w, 0, 1);
|
||||
const auto coeff1 = panLookup(w);
|
||||
const auto coeff2 = panLookup(1 - w);
|
||||
const auto l = left;
|
||||
const auto r = right;
|
||||
left = l * coeff2 + r * coeff1;
|
||||
right = l * coeff1 + r * coeff2;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -780,13 +793,45 @@ void pan(absl::Span<const T> panEnvelope, absl::Span<T> leftBuffer, absl::Span<T
|
|||
auto* left = leftBuffer.begin();
|
||||
auto* right = rightBuffer.begin();
|
||||
auto* sentinel = pan + min(panEnvelope.size(), leftBuffer.size(), rightBuffer.size());
|
||||
while (pan < sentinel)
|
||||
_internals::snippetPan(pan, left, right);
|
||||
while (pan < sentinel) {
|
||||
_internals::snippetPan(*pan, *left, *right);
|
||||
incrementAll(pan, left, right);
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void pan<float, true>(absl::Span<const float> panEnvelope, absl::Span<float> leftBuffer, absl::Span<float> rightBuffer) noexcept;
|
||||
|
||||
/**
|
||||
* @brief Controls the width of a stereo signal, setting it to mono when width = 0 and inverting the channels
|
||||
* when width = -1. Width = 1 has no effect.
|
||||
*
|
||||
* The output size will be the minimum of the width envelope span and left and right buffer span sizes.
|
||||
*
|
||||
* @tparam T the underlying type
|
||||
* @tparam SIMD use the SIMD version or the scalar version
|
||||
* @param panEnvelope
|
||||
* @param leftBuffer
|
||||
* @param rightBuffer
|
||||
*/
|
||||
template <class T, bool SIMD = SIMDConfig::pan>
|
||||
void width(absl::Span<const T> widthEnvelope, absl::Span<T> leftBuffer, absl::Span<T> rightBuffer) noexcept
|
||||
{
|
||||
ASSERT(leftBuffer.size() >= widthEnvelope.size());
|
||||
ASSERT(rightBuffer.size() >= widthEnvelope.size());
|
||||
auto* width = widthEnvelope.begin();
|
||||
auto* left = leftBuffer.begin();
|
||||
auto* right = rightBuffer.begin();
|
||||
auto* sentinel = width + min(widthEnvelope.size(), leftBuffer.size(), rightBuffer.size());
|
||||
while (width < sentinel) {
|
||||
_internals::snippetWidth(*width, *left, *right);
|
||||
incrementAll(width, left, right);
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void width<float, true>(absl::Span<const float> widthEnvelope, absl::Span<float> leftBuffer, absl::Span<float> rightBuffer) noexcept;
|
||||
|
||||
/**
|
||||
* @brief Computes the mean of a span
|
||||
*
|
||||
|
|
|
|||
|
|
@ -594,8 +594,10 @@ void sfz::pan<float, true>(absl::Span<const float> panEnvelope, absl::Span<float
|
|||
auto* sentinel = pan + min(panEnvelope.size(), leftBuffer.size(), rightBuffer.size());
|
||||
const auto* lastAligned = prevAligned(sentinel);
|
||||
|
||||
while (unaligned(pan, left, right) && pan < lastAligned)
|
||||
_internals::snippetPan(pan, left, right);
|
||||
while (unaligned(pan, left, right) && pan < lastAligned) {
|
||||
_internals::snippetPan(*pan, *left, *right);
|
||||
incrementAll(pan, left, right);
|
||||
}
|
||||
|
||||
const auto mmOne = _mm_set_ps1(1.0f);
|
||||
const auto mmPiFour = _mm_set_ps1(piFour<float>);
|
||||
|
|
@ -607,14 +609,57 @@ void sfz::pan<float, true>(absl::Span<const float> panEnvelope, absl::Span<float
|
|||
mmPan = _mm_mul_ps(mmPan, mmPiFour);
|
||||
sincos_ps(mmPan, &mmSin, &mmCos);
|
||||
auto mmLeft = _mm_mul_ps(mmCos, _mm_load_ps(left));
|
||||
auto mmRight = _mm_mul_ps(mmCos, _mm_load_ps(left));
|
||||
auto mmRight = _mm_mul_ps(mmSin, _mm_load_ps(right));
|
||||
_mm_store_ps(left, mmLeft);
|
||||
_mm_store_ps(right, mmRight);
|
||||
incrementAll<TypeAlignment>(pan, left, right);
|
||||
}
|
||||
|
||||
while (pan < sentinel)
|
||||
_internals::snippetPan(pan, left, right);
|
||||
while (pan < sentinel){
|
||||
_internals::snippetPan(*pan, *left, *right);
|
||||
incrementAll(pan, left, right);
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void sfz::width<float, true>(absl::Span<const float> widthEnvelope, absl::Span<float> leftBuffer, absl::Span<float> rightBuffer) noexcept
|
||||
{
|
||||
ASSERT(leftBuffer.size() >= widthEnvelope.size());
|
||||
ASSERT(rightBuffer.size() >= widthEnvelope.size());
|
||||
auto* width = widthEnvelope.begin();
|
||||
auto* left = leftBuffer.begin();
|
||||
auto* right = rightBuffer.begin();
|
||||
auto* sentinel = width + min(widthEnvelope.size(), leftBuffer.size(), rightBuffer.size());
|
||||
const auto* lastAligned = prevAligned(sentinel);
|
||||
|
||||
while (unaligned(width, left, right) && width < lastAligned) {
|
||||
_internals::snippetWidth(*width, *left, *right);
|
||||
incrementAll(width, left, right);
|
||||
}
|
||||
|
||||
const auto mmPiFour = _mm_set_ps1(piFour<float>);
|
||||
__m128 mmCos;
|
||||
__m128 mmSin;
|
||||
while (width < lastAligned) {
|
||||
auto mmWidth = _mm_load_ps(width);
|
||||
mmWidth = _mm_mul_ps(mmWidth, mmPiFour);
|
||||
sincos_ps(mmWidth, &mmSin, &mmCos);
|
||||
auto mmCosPlusSine = _mm_add_ps(mmCos, mmSin);
|
||||
auto mmCosMinusSine = _mm_sub_ps(mmCos, mmSin);
|
||||
auto mmLeft = _mm_load_ps(left);
|
||||
auto mmRight = _mm_load_ps(right);
|
||||
auto mmTemp = _mm_mul_ps(mmCosMinusSine, mmRight);
|
||||
mmRight = _mm_add_ps(_mm_mul_ps(mmCosMinusSine, mmLeft), _mm_mul_ps(mmCosPlusSine, mmRight));
|
||||
mmLeft = _mm_add_ps(_mm_mul_ps(mmCosPlusSine, mmLeft), mmTemp);
|
||||
_mm_store_ps(left, mmLeft);
|
||||
_mm_store_ps(right, mmRight);
|
||||
incrementAll<TypeAlignment>(width, left, right);
|
||||
}
|
||||
|
||||
while (width < sentinel){
|
||||
_internals::snippetWidth(*width, *left, *right);
|
||||
incrementAll(width, left, right);
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
|
|
|
|||
|
|
@ -71,7 +71,7 @@ constexpr float normalizeVelocity(T velocity)
|
|||
template<class T>
|
||||
constexpr float normalizePercents(T percentValue)
|
||||
{
|
||||
return std::min(std::max(static_cast<float>(percentValue), 0.0f), 100.0f) / 100.0f;
|
||||
return percentValue * 0.01f;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -86,19 +86,6 @@ constexpr float normalizeBend(float bendValue)
|
|||
return std::min(std::max(bendValue, -8191.0f), 8191.0f) / 8191.0f;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Normalize a possibly negative percentage between -1 and 1
|
||||
*
|
||||
* @tparam T
|
||||
* @param percentValue
|
||||
* @return constexpr float
|
||||
*/
|
||||
template<class T>
|
||||
constexpr float normalizeNegativePercents(T percentValue)
|
||||
{
|
||||
return std::min(std::max(static_cast<float>(percentValue), -100.0f), 100.0f) / 100.0f;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief If a cc switch exists for the value, returns the value with the CC modifier, otherwise returns the value alone.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -62,22 +62,22 @@ void sfz::Voice::startVoice(Region* region, int delay, int number, uint8_t value
|
|||
float crossfadeGain { region->getCrossfadeGain(resources.midiState.getCCArray()) };
|
||||
crossfadeEnvelope.reset(Default::normalizedRange.clamp(crossfadeGain));
|
||||
|
||||
basePan = normalizeNegativePercents(region->pan);
|
||||
basePan = normalizePercents(region->pan);
|
||||
auto pan { basePan };
|
||||
if (region->panCC)
|
||||
pan += normalizeCC(resources.midiState.getCCValue(region->panCC->first)) * normalizeNegativePercents(region->panCC->second);
|
||||
pan += normalizeCC(resources.midiState.getCCValue(region->panCC->first)) * normalizePercents(region->panCC->second);
|
||||
panEnvelope.reset(Default::symmetricNormalizedRange.clamp(pan));
|
||||
|
||||
basePosition = normalizeNegativePercents(region->position);
|
||||
basePosition = normalizePercents(region->position);
|
||||
auto position { basePosition };
|
||||
if (region->positionCC)
|
||||
position += normalizeCC(resources.midiState.getCCValue(region->positionCC->first)) * normalizeNegativePercents(region->positionCC->second);
|
||||
position += normalizeCC(resources.midiState.getCCValue(region->positionCC->first)) * normalizePercents(region->positionCC->second);
|
||||
positionEnvelope.reset(Default::symmetricNormalizedRange.clamp(position));
|
||||
|
||||
baseWidth = normalizeNegativePercents(region->width);
|
||||
baseWidth = normalizePercents(region->width);
|
||||
auto width { baseWidth };
|
||||
if (region->widthCC)
|
||||
width += normalizeCC(resources.midiState.getCCValue(region->widthCC->first)) * normalizeNegativePercents(region->widthCC->second);
|
||||
width += normalizeCC(resources.midiState.getCCValue(region->widthCC->first)) * normalizePercents(region->widthCC->second);
|
||||
widthEnvelope.reset(Default::symmetricNormalizedRange.clamp(width));
|
||||
|
||||
pitchBendEnvelope.setFunction([region](float pitchValue){
|
||||
|
|
@ -196,17 +196,17 @@ void sfz::Voice::registerCC(int delay, int ccNumber, uint8_t ccValue) noexcept
|
|||
}
|
||||
|
||||
if (region->panCC && ccNumber == region->panCC->first) {
|
||||
const float newPan { basePan + normalizeCC(ccValue) * normalizeNegativePercents(region->panCC->second) };
|
||||
const float newPan { basePan + normalizeCC(ccValue) * normalizePercents(region->panCC->second) };
|
||||
panEnvelope.registerEvent(delay, Default::symmetricNormalizedRange.clamp(newPan));
|
||||
}
|
||||
|
||||
if (region->positionCC && ccNumber == region->positionCC->first) {
|
||||
const float newPosition { basePosition + normalizeCC(ccValue) * normalizeNegativePercents(region->positionCC->second) };
|
||||
const float newPosition { basePosition + normalizeCC(ccValue) * normalizePercents(region->positionCC->second) };
|
||||
positionEnvelope.registerEvent(delay, Default::symmetricNormalizedRange.clamp(newPosition));
|
||||
}
|
||||
|
||||
if (region->widthCC && ccNumber == region->widthCC->first) {
|
||||
const float newWidth { baseWidth + normalizeCC(ccValue) * normalizeNegativePercents(region->widthCC->second) };
|
||||
const float newWidth { baseWidth + normalizeCC(ccValue) * normalizePercents(region->widthCC->second) };
|
||||
widthEnvelope.registerEvent(delay, Default::symmetricNormalizedRange.clamp(newWidth));
|
||||
}
|
||||
|
||||
|
|
@ -290,24 +290,23 @@ void sfz::Voice::processMono(AudioSpan<float> buffer) noexcept
|
|||
auto leftBuffer = buffer.getSpan(0);
|
||||
auto rightBuffer = buffer.getSpan(1);
|
||||
|
||||
auto span1 = tempSpan1.first(numSamples);
|
||||
auto span2 = tempSpan2.first(numSamples);
|
||||
auto modulationSpan = tempSpan1.first(numSamples);
|
||||
|
||||
// Amplitude envelope
|
||||
amplitudeEnvelope.getBlock(span1);
|
||||
applyGain<float>(span1, leftBuffer);
|
||||
amplitudeEnvelope.getBlock(modulationSpan);
|
||||
applyGain<float>(modulationSpan, leftBuffer);
|
||||
|
||||
// Crossfade envelope
|
||||
crossfadeEnvelope.getBlock(span1);
|
||||
applyGain<float>(span1, leftBuffer);
|
||||
crossfadeEnvelope.getBlock(modulationSpan);
|
||||
applyGain<float>(modulationSpan, leftBuffer);
|
||||
|
||||
// Volume envelope
|
||||
volumeEnvelope.getBlock(span1);
|
||||
applyGain<float>(span1, leftBuffer);
|
||||
volumeEnvelope.getBlock(modulationSpan);
|
||||
applyGain<float>(modulationSpan, leftBuffer);
|
||||
|
||||
// AmpEG envelope
|
||||
egEnvelope.getBlock(span1);
|
||||
applyGain<float>(span1, leftBuffer);
|
||||
egEnvelope.getBlock(modulationSpan);
|
||||
applyGain<float>(modulationSpan, leftBuffer);
|
||||
|
||||
// Filtering and EQ
|
||||
const float* inputChannel[1] { leftBuffer.data() };
|
||||
|
|
@ -323,74 +322,39 @@ void sfz::Voice::processMono(AudioSpan<float> buffer) noexcept
|
|||
// Prepare for stereo output
|
||||
copy<float>(leftBuffer, rightBuffer);
|
||||
|
||||
panEnvelope.getBlock(span1);
|
||||
// We assume that the pan envelope is already normalized between -1 and 1
|
||||
// Check bm_pan for your architecture to check if it's interesting to use the pan helper instead
|
||||
fill<float>(span2, 1.0f);
|
||||
add<float>(span1, span2);
|
||||
applyGain<float>(piFour<float>, span2);
|
||||
cos<float>(span2, span1);
|
||||
sin<float>(span2, span2);
|
||||
applyGain<float>(span1, leftBuffer);
|
||||
applyGain<float>(span2, rightBuffer);
|
||||
// Apply panning
|
||||
panEnvelope.getBlock(modulationSpan);
|
||||
pan<float>(modulationSpan, leftBuffer, rightBuffer);
|
||||
}
|
||||
|
||||
void sfz::Voice::processStereo(AudioSpan<float> buffer) noexcept
|
||||
{
|
||||
const auto numSamples = buffer.getNumFrames();
|
||||
auto span1 = tempSpan1.first(numSamples);
|
||||
auto span2 = tempSpan2.first(numSamples);
|
||||
auto span3 = tempSpan3.first(numSamples);
|
||||
auto modulationSpan = tempSpan1.first(numSamples);
|
||||
auto leftBuffer = buffer.getSpan(0);
|
||||
auto rightBuffer = buffer.getSpan(1);
|
||||
|
||||
// Amplitude envelope
|
||||
amplitudeEnvelope.getBlock(span1);
|
||||
buffer.applyGain(span1);
|
||||
amplitudeEnvelope.getBlock(modulationSpan);
|
||||
buffer.applyGain(modulationSpan);
|
||||
|
||||
// Crossfade envelope
|
||||
crossfadeEnvelope.getBlock(span1);
|
||||
buffer.applyGain(span1);
|
||||
crossfadeEnvelope.getBlock(modulationSpan);
|
||||
buffer.applyGain(modulationSpan);
|
||||
|
||||
// Volume envelope
|
||||
volumeEnvelope.getBlock(span1);
|
||||
buffer.applyGain(span1);
|
||||
volumeEnvelope.getBlock(modulationSpan);
|
||||
buffer.applyGain(modulationSpan);
|
||||
|
||||
// AmpEG envelope
|
||||
egEnvelope.getBlock(span1);
|
||||
buffer.applyGain(span1);
|
||||
egEnvelope.getBlock(modulationSpan);
|
||||
buffer.applyGain(modulationSpan);
|
||||
|
||||
// Create mid/side from left/right in the output buffer
|
||||
copy<float>(rightBuffer, span1);
|
||||
add<float>(leftBuffer, rightBuffer);
|
||||
subtract<float>(span1, leftBuffer);
|
||||
applyGain<float>(sqrtTwoInv<float>, leftBuffer);
|
||||
applyGain<float>(sqrtTwoInv<float>, rightBuffer);
|
||||
|
||||
// Apply the width process
|
||||
widthEnvelope.getBlock(span1);
|
||||
fill<float>(span2, 1.0f);
|
||||
add<float>(span1, span2);
|
||||
applyGain<float>(piFour<float>, span2);
|
||||
cos<float>(span2, span1);
|
||||
sin<float>(span2, span2);
|
||||
applyGain<float>(span1, leftBuffer);
|
||||
applyGain<float>(span2, rightBuffer);
|
||||
|
||||
// Apply a position to the "left" channel which is supposed to be our mid channel
|
||||
// TODO: add panning here too?
|
||||
positionEnvelope.getBlock(span1);
|
||||
fill<float>(span2, 1.0f);
|
||||
add<float>(span1, span2);
|
||||
applyGain<float>(piFour<float>, span2);
|
||||
cos<float>(span2, span1);
|
||||
sin<float>(span2, span2);
|
||||
copy<float>(leftBuffer, span3);
|
||||
copy<float>(rightBuffer, leftBuffer);
|
||||
multiplyAdd<float>(span1, span3, leftBuffer);
|
||||
multiplyAdd<float>(span2, span3, rightBuffer);
|
||||
applyGain<float>(sqrtTwoInv<float>, leftBuffer);
|
||||
applyGain<float>(sqrtTwoInv<float>, 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
|
||||
const float* inputChannels[2] { leftBuffer.data(), rightBuffer.data() };
|
||||
|
|
|
|||
|
|
@ -42,6 +42,11 @@ if(JACK_FOUND AND TARGET Qt5::Widgets)
|
|||
target_include_directories(sfizz_demo_filters PRIVATE ${JACK_INCLUDE_DIRS})
|
||||
target_link_libraries(sfizz_demo_filters PRIVATE sfizz::sfizz Qt5::Widgets ${JACK_LIBRARIES})
|
||||
set_target_properties(sfizz_demo_filters PROPERTIES AUTOUIC ON)
|
||||
|
||||
add_executable(sfizz_demo_stereo DemoStereo.cpp)
|
||||
target_include_directories(sfizz_demo_stereo PRIVATE ${JACK_INCLUDE_DIRS})
|
||||
target_link_libraries(sfizz_demo_stereo PRIVATE sfizz::sfizz Qt5::Widgets ${JACK_LIBRARIES})
|
||||
set_target_properties(sfizz_demo_stereo PROPERTIES AUTOUIC ON)
|
||||
endif()
|
||||
|
||||
file(COPY "." DESTINATION ${CMAKE_BINARY_DIR}/tests)
|
||||
|
|
|
|||
204
tests/DemoStereo.cpp
Normal file
204
tests/DemoStereo.cpp
Normal file
|
|
@ -0,0 +1,204 @@
|
|||
// 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 "sfizz/SIMDHelpers.h"
|
||||
#include "ui_DemoStereo.h"
|
||||
#include <QApplication>
|
||||
#include <QMainWindow>
|
||||
#include <QMessageBox>
|
||||
#include <QButtonGroup>
|
||||
#include <QDebug>
|
||||
#include <jack/jack.h>
|
||||
#include <memory>
|
||||
|
||||
///
|
||||
struct jack_delete {
|
||||
void operator()(jack_client_t *x) const noexcept { jack_client_close(x); }
|
||||
};
|
||||
|
||||
typedef std::unique_ptr<jack_client_t, jack_delete> jack_client_u;
|
||||
|
||||
///
|
||||
class DemoApp : public QApplication {
|
||||
public:
|
||||
DemoApp(int &argc, char **argv);
|
||||
bool initSound();
|
||||
void initWindow();
|
||||
|
||||
private:
|
||||
static int processAudio(jack_nframes_t nframes, void *cbdata);
|
||||
|
||||
private:
|
||||
void valueChangedWidth(int value);
|
||||
void valueChangedPan(int value);
|
||||
|
||||
private:
|
||||
QMainWindow *fWindow = nullptr;
|
||||
Ui::DemoStereoWindow fUi;
|
||||
|
||||
static constexpr int widthMin = -100;
|
||||
static constexpr int widthMax = +100;
|
||||
|
||||
static constexpr int panMin = -100;
|
||||
static constexpr int panMax = +100;
|
||||
|
||||
int fWidth = 100;
|
||||
int fPan = 0;
|
||||
|
||||
std::unique_ptr<float[]> fTmpWidthEnvelope;
|
||||
std::unique_ptr<float[]> fTmpPositionEnvelope;
|
||||
std::unique_ptr<float[]> fTmpBuffer1;
|
||||
|
||||
jack_client_u fClient;
|
||||
jack_port_t *fPorts[4] = {};
|
||||
};
|
||||
|
||||
DemoApp::DemoApp(int &argc, char **argv)
|
||||
: QApplication(argc, argv)
|
||||
{
|
||||
setApplicationName(tr("Sfizz Stereo"));
|
||||
}
|
||||
|
||||
bool DemoApp::initSound()
|
||||
{
|
||||
jack_client_t *client = jack_client_open(
|
||||
applicationName().toUtf8().data(), JackNoStartServer, nullptr);
|
||||
if (!client) {
|
||||
QMessageBox::critical(nullptr, tr("Error"), tr("Cannot open JACK audio."));
|
||||
return false;
|
||||
}
|
||||
|
||||
fClient.reset(client);
|
||||
|
||||
uint32_t bufsize = jack_get_buffer_size(client);
|
||||
fTmpWidthEnvelope.reset(new float[bufsize]);
|
||||
fTmpPositionEnvelope.reset(new float[bufsize]);
|
||||
fTmpBuffer1.reset(new float[bufsize]);
|
||||
|
||||
fPorts[0] = jack_port_register(client, "in_left", JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput, 0);
|
||||
fPorts[1] = jack_port_register(client, "in_right", JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput, 0);
|
||||
fPorts[2] = jack_port_register(client, "out_left", JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0);
|
||||
fPorts[3] = jack_port_register(client, "out_right", JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0);
|
||||
|
||||
if (!(fPorts[0] && fPorts[1] && fPorts[2] && fPorts[3])) {
|
||||
QMessageBox::critical(nullptr, tr("Error"), tr("Cannot register JACK ports."));
|
||||
return false;
|
||||
}
|
||||
|
||||
jack_set_process_callback(client, &processAudio, this);
|
||||
|
||||
if (jack_activate(client) != 0) {
|
||||
QMessageBox::critical(nullptr, tr("Error"), tr("Cannot activate JACK client."));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void DemoApp::initWindow()
|
||||
{
|
||||
QMainWindow *window = new QMainWindow;
|
||||
fWindow = window;
|
||||
fUi.setupUi(window);
|
||||
window->setWindowTitle(applicationDisplayName());
|
||||
|
||||
fUi.valWidth->setRange(widthMin, widthMax);
|
||||
fUi.valPan->setRange(panMin, panMax);
|
||||
fUi.spinWidth->setRange(widthMin, widthMax);
|
||||
fUi.spinPan->setRange(panMin, panMax);
|
||||
|
||||
fUi.valWidth->setValue(fWidth);
|
||||
fUi.valPan->setValue(fPan);
|
||||
fUi.spinWidth->setValue(fWidth);
|
||||
fUi.spinPan->setValue(fPan);
|
||||
|
||||
connect(
|
||||
fUi.valWidth, &QSlider::valueChanged,
|
||||
this, [this](int value) { valueChangedWidth(value); });
|
||||
connect(
|
||||
fUi.spinWidth, QOverload<int>::of(&QSpinBox::valueChanged),
|
||||
this, [this](int value) { valueChangedWidth(value); });
|
||||
connect(
|
||||
fUi.valPan, &QSlider::valueChanged,
|
||||
this, [this](int value) { valueChangedPan(value); });
|
||||
connect(
|
||||
fUi.spinPan, QOverload<int>::of(&QSpinBox::valueChanged),
|
||||
this, [this](int value) { valueChangedPan(value); });
|
||||
|
||||
window->adjustSize();
|
||||
window->setFixedSize(window->size());
|
||||
|
||||
window->show();
|
||||
}
|
||||
|
||||
int DemoApp::processAudio(jack_nframes_t nframes, void *cbdata)
|
||||
{
|
||||
DemoApp *self = reinterpret_cast<DemoApp *>(cbdata);
|
||||
|
||||
absl::Span<float> leftBuffer {
|
||||
reinterpret_cast<float *>(jack_port_get_buffer(self->fPorts[2], nframes)),
|
||||
nframes};
|
||||
absl::Span<float> rightBuffer {
|
||||
reinterpret_cast<float *>(jack_port_get_buffer(self->fPorts[3], nframes)),
|
||||
nframes};
|
||||
|
||||
std::copy_n(
|
||||
reinterpret_cast<float *>(jack_port_get_buffer(self->fPorts[0], nframes)),
|
||||
nframes, leftBuffer.begin());
|
||||
std::copy_n(
|
||||
reinterpret_cast<float *>(jack_port_get_buffer(self->fPorts[1], nframes)),
|
||||
nframes, rightBuffer.begin());
|
||||
|
||||
absl::Span<float> widthEnvelope{self->fTmpWidthEnvelope.get(), nframes};
|
||||
absl::Span<float> positionEnvelope{self->fTmpPositionEnvelope.get(), nframes};
|
||||
absl::Span<float> tempSpan1{self->fTmpBuffer1.get(), nframes};
|
||||
|
||||
std::fill(widthEnvelope.begin(), widthEnvelope.end(), self->fWidth * 0.01f);
|
||||
std::fill(positionEnvelope.begin(), positionEnvelope.end(), self->fPan * 0.01f);
|
||||
|
||||
using namespace sfz;
|
||||
width<float>(widthEnvelope, leftBuffer, rightBuffer);
|
||||
pan<float>(positionEnvelope, leftBuffer, rightBuffer);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void DemoApp::valueChangedWidth(int value)
|
||||
{
|
||||
fUi.valWidth->blockSignals(true);
|
||||
fUi.valWidth->setValue(value);
|
||||
fUi.valWidth->blockSignals(false);
|
||||
|
||||
fUi.spinWidth->blockSignals(true);
|
||||
fUi.spinWidth->setValue(value);
|
||||
fUi.spinWidth->blockSignals(false);
|
||||
|
||||
fWidth = value;
|
||||
}
|
||||
|
||||
void DemoApp::valueChangedPan(int value)
|
||||
{
|
||||
fUi.valPan->blockSignals(true);
|
||||
fUi.valPan->setValue(value);
|
||||
fUi.valPan->blockSignals(false);
|
||||
|
||||
fUi.spinPan->blockSignals(true);
|
||||
fUi.spinPan->setValue(value);
|
||||
fUi.spinPan->blockSignals(false);
|
||||
|
||||
fPan = value;
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
DemoApp app(argc, argv);
|
||||
|
||||
if (!app.initSound())
|
||||
return 1;
|
||||
|
||||
app.initWindow();
|
||||
|
||||
return app.exec();
|
||||
}
|
||||
98
tests/DemoStereo.ui
Normal file
98
tests/DemoStereo.ui
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>DemoStereoWindow</class>
|
||||
<widget class="QMainWindow" name="DemoStereoWindow">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>615</width>
|
||||
<height>72</height>
|
||||
</rect>
|
||||
</property>
|
||||
<widget class="QWidget" name="centralwidget">
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Width</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QSlider" name="valWidth">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>500</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<number>-100</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>100</number>
|
||||
</property>
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="tickPosition">
|
||||
<enum>QSlider::TicksBelow</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="2">
|
||||
<widget class="QSpinBox" name="spinWidth">
|
||||
<property name="minimum">
|
||||
<number>-100</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>100</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>Pan</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QSlider" name="valPan">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>500</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<number>-100</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>100</number>
|
||||
</property>
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="tickPosition">
|
||||
<enum>QSlider::TicksBelow</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="2">
|
||||
<widget class="QSpinBox" name="spinPan">
|
||||
<property name="minimum">
|
||||
<number>-100</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>100</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
||||
|
|
@ -756,3 +756,61 @@ TEST_CASE("[Helpers] Diff (SIMD vs Scalar)")
|
|||
sfz::diff<float, true>(input, absl::MakeSpan(outputSIMD));
|
||||
REQUIRE(approxEqual<float>(outputScalar, outputSIMD));
|
||||
}
|
||||
|
||||
TEST_CASE("[Helpers] Pan Scalar")
|
||||
{
|
||||
std::array<float, 1> leftValue { 1.0f };
|
||||
std::array<float, 1> rightValue { 1.0f };
|
||||
auto left = absl::MakeSpan(leftValue);
|
||||
auto right = absl::MakeSpan(rightValue);
|
||||
SECTION("Pan = 0")
|
||||
{
|
||||
std::array<float, 1> pan { 0.0f };
|
||||
sfz::pan<float, false>(pan, left, right);
|
||||
REQUIRE(left[0] == Approx(0.70711f).margin(0.001f));
|
||||
REQUIRE(right[0] == Approx(0.70711f).margin(0.001f));
|
||||
}
|
||||
SECTION("Pan = 1")
|
||||
{
|
||||
std::array<float, 1> pan { 1.0f };
|
||||
sfz::pan<float, false>(pan, left, right);
|
||||
REQUIRE(left[0] == Approx(0.0f).margin(0.001f));
|
||||
REQUIRE(right[0] == Approx(1.0f).margin(0.001f));
|
||||
}
|
||||
SECTION("Pan = -1")
|
||||
{
|
||||
std::array<float, 1> pan { -1.0f };
|
||||
sfz::pan<float, false>(pan, left, right);
|
||||
REQUIRE(left[0] == Approx(1.0f).margin(0.001f));
|
||||
REQUIRE(right[0] == Approx(0.0f).margin(0.001f));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("[Helpers] Width Scalar")
|
||||
{
|
||||
std::array<float, 1> leftValue { 1.0f };
|
||||
std::array<float, 1> rightValue { 1.0f };
|
||||
auto left = absl::MakeSpan(leftValue);
|
||||
auto right = absl::MakeSpan(rightValue);
|
||||
SECTION("width = 1")
|
||||
{
|
||||
std::array<float, 1> width { 1.0f };
|
||||
sfz::width<float, false>(width, left, right);
|
||||
REQUIRE(left[0] == Approx(1.0f).margin(0.001f));
|
||||
REQUIRE(right[0] == Approx(1.0f).margin(0.001f));
|
||||
}
|
||||
SECTION("width = 0")
|
||||
{
|
||||
std::array<float, 1> width { 0.0f };
|
||||
sfz::width<float, false>(width, left, right);
|
||||
REQUIRE(left[0] == Approx(1.414f).margin(0.001f));
|
||||
REQUIRE(right[0] == Approx(1.414f).margin(0.001f));
|
||||
}
|
||||
SECTION("width = -1")
|
||||
{
|
||||
std::array<float, 1> width { -1.0f };
|
||||
sfz::width<float, false>(width, left, right);
|
||||
REQUIRE(left[0] == Approx(1.0f).margin(0.001f));
|
||||
REQUIRE(right[0] == Approx(1.0f).margin(0.001f));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue