diff --git a/benchmarks/BM_pan.cpp b/benchmarks/BM_pan.cpp index ff764cdd..b3f522e8 100644 --- a/benchmarks/BM_pan.cpp +++ b/benchmarks/BM_pan.cpp @@ -12,6 +12,7 @@ #include #include #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(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(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(span2, 1.0f); diff --git a/benchmarks/BM_widthPos.cpp b/benchmarks/BM_widthPos.cpp new file mode 100644 index 00000000..312a029a --- /dev/null +++ b/benchmarks/BM_widthPos.cpp @@ -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 +#include +#include +#include +#include +#include +#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 dist { 0.001f, 1.0f }; + width = std::vector(state.range(0)); + position = std::vector(state.range(0)); + left = std::vector(state.range(0)); + right = std::vector(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(state.range(0)); + temp2 = std::vector(state.range(0)); + temp3 = std::vector(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 width; + std::vector position; + std::vector left; + std::vector right; + std::vector temp1; + std::vector temp2; + std::vector temp3; + absl::Span span1; + absl::Span span2; + absl::Span 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(width, leftBuffer, rightBuffer); + sfz::pan(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(width, leftBuffer, rightBuffer); + sfz::pan(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(); diff --git a/benchmarks/CMakeLists.txt b/benchmarks/CMakeLists.txt index 7cff1d29..a73c12b5 100644 --- a/benchmarks/CMakeLists.txt +++ b/benchmarks/CMakeLists.txt @@ -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 diff --git a/src/sfizz/Config.h b/src/sfizz/Config.h index cbbfa41a..52029fbe 100644 --- a/src/sfizz/Config.h +++ b/src/sfizz/Config.h @@ -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 }; diff --git a/src/sfizz/SIMDHelpers.h b/src/sfizz/SIMDHelpers.h index 9ece0217..f4c5a2d7 100644 --- a/src/sfizz/SIMDHelpers.h +++ b/src/sfizz/SIMDHelpers.h @@ -743,20 +743,33 @@ namespace _internals { }(); template - 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(p, 0, 1); + // reduce range, round to nearest + int index = static_cast(T{0.5} + pan * (panSize - 1)); + return panData[index]; + } - auto lookUp = [](T pan) -> T - { - // reduce range, round to nearest - int index = static_cast(T{0.5} + pan * (panSize - 1)); - return panData[index]; - }; + template + inline void snippetPan(T pan, T& left, T& right) + { + pan = (pan + T{1.0}) * T{0.5}; + pan = clamp(pan, 0, 1); + left *= panLookup(pan); + right *= panLookup(1 - pan); + } - *left++ = lookUp(p); - *right++ = lookUp(1 - p); + template + inline void snippetWidth(T width, T& left, T& right) + { + T w = (width + T{1.0}) * T{0.5}; + w = clamp(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 panEnvelope, absl::Span leftBuffer, absl::Span void pan(absl::Span panEnvelope, absl::Span leftBuffer, absl::Span 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 +void width(absl::Span widthEnvelope, absl::Span leftBuffer, absl::Span 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(absl::Span widthEnvelope, absl::Span leftBuffer, absl::Span rightBuffer) noexcept; + /** * @brief Computes the mean of a span * diff --git a/src/sfizz/SIMDSSE.cpp b/src/sfizz/SIMDSSE.cpp index 8b541657..ba98c12d 100644 --- a/src/sfizz/SIMDSSE.cpp +++ b/src/sfizz/SIMDSSE.cpp @@ -594,8 +594,10 @@ void sfz::pan(absl::Span panEnvelope, absl::Span); @@ -607,14 +609,57 @@ void sfz::pan(absl::Span panEnvelope, absl::Span(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(absl::Span widthEnvelope, absl::Span leftBuffer, absl::Span 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); + __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(width, left, right); + } + + while (width < sentinel){ + _internals::snippetWidth(*width, *left, *right); + incrementAll(width, left, right); + } } template <> diff --git a/src/sfizz/SfzHelpers.h b/src/sfizz/SfzHelpers.h index 37cf3dda..71d3c4f1 100644 --- a/src/sfizz/SfzHelpers.h +++ b/src/sfizz/SfzHelpers.h @@ -71,7 +71,7 @@ constexpr float normalizeVelocity(T velocity) template constexpr float normalizePercents(T percentValue) { - return std::min(std::max(static_cast(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 -constexpr float normalizeNegativePercents(T percentValue) -{ - return std::min(std::max(static_cast(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. * diff --git a/src/sfizz/Voice.cpp b/src/sfizz/Voice.cpp index 2a36a27c..636fd5ba 100644 --- a/src/sfizz/Voice.cpp +++ b/src/sfizz/Voice.cpp @@ -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 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(span1, leftBuffer); + amplitudeEnvelope.getBlock(modulationSpan); + applyGain(modulationSpan, leftBuffer); // Crossfade envelope - crossfadeEnvelope.getBlock(span1); - applyGain(span1, leftBuffer); + crossfadeEnvelope.getBlock(modulationSpan); + applyGain(modulationSpan, leftBuffer); // Volume envelope - volumeEnvelope.getBlock(span1); - applyGain(span1, leftBuffer); + volumeEnvelope.getBlock(modulationSpan); + applyGain(modulationSpan, leftBuffer); // AmpEG envelope - egEnvelope.getBlock(span1); - applyGain(span1, leftBuffer); + egEnvelope.getBlock(modulationSpan); + applyGain(modulationSpan, leftBuffer); // Filtering and EQ const float* inputChannel[1] { leftBuffer.data() }; @@ -323,74 +322,39 @@ void sfz::Voice::processMono(AudioSpan buffer) noexcept // Prepare for stereo output copy(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(span2, 1.0f); - add(span1, span2); - applyGain(piFour, span2); - cos(span2, span1); - sin(span2, span2); - applyGain(span1, leftBuffer); - applyGain(span2, rightBuffer); + // Apply panning + panEnvelope.getBlock(modulationSpan); + pan(modulationSpan, leftBuffer, rightBuffer); } void sfz::Voice::processStereo(AudioSpan 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(rightBuffer, span1); - add(leftBuffer, rightBuffer); - subtract(span1, leftBuffer); - applyGain(sqrtTwoInv, leftBuffer); - applyGain(sqrtTwoInv, rightBuffer); - - // Apply the width process - widthEnvelope.getBlock(span1); - fill(span2, 1.0f); - add(span1, span2); - applyGain(piFour, span2); - cos(span2, span1); - sin(span2, span2); - applyGain(span1, leftBuffer); - applyGain(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(span2, 1.0f); - add(span1, span2); - applyGain(piFour, span2); - cos(span2, span1); - sin(span2, span2); - copy(leftBuffer, span3); - copy(rightBuffer, leftBuffer); - multiplyAdd(span1, span3, leftBuffer); - multiplyAdd(span2, span3, rightBuffer); - applyGain(sqrtTwoInv, leftBuffer); - applyGain(sqrtTwoInv, rightBuffer); + // Apply the width/position process + widthEnvelope.getBlock(modulationSpan); + width(modulationSpan, leftBuffer, rightBuffer); + positionEnvelope.getBlock(modulationSpan); + pan(modulationSpan, leftBuffer, rightBuffer); // Filtering and EQ const float* inputChannels[2] { leftBuffer.data(), rightBuffer.data() }; diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index d14ea8f1..2bb008a5 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -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) diff --git a/tests/DemoStereo.cpp b/tests/DemoStereo.cpp new file mode 100644 index 00000000..2ef1558c --- /dev/null +++ b/tests/DemoStereo.cpp @@ -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 +#include +#include +#include +#include +#include +#include + +/// +struct jack_delete { + void operator()(jack_client_t *x) const noexcept { jack_client_close(x); } +}; + +typedef std::unique_ptr 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 fTmpWidthEnvelope; + std::unique_ptr fTmpPositionEnvelope; + std::unique_ptr 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::of(&QSpinBox::valueChanged), + this, [this](int value) { valueChangedWidth(value); }); + connect( + fUi.valPan, &QSlider::valueChanged, + this, [this](int value) { valueChangedPan(value); }); + connect( + fUi.spinPan, QOverload::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(cbdata); + + absl::Span leftBuffer { + reinterpret_cast(jack_port_get_buffer(self->fPorts[2], nframes)), + nframes}; + absl::Span rightBuffer { + reinterpret_cast(jack_port_get_buffer(self->fPorts[3], nframes)), + nframes}; + + std::copy_n( + reinterpret_cast(jack_port_get_buffer(self->fPorts[0], nframes)), + nframes, leftBuffer.begin()); + std::copy_n( + reinterpret_cast(jack_port_get_buffer(self->fPorts[1], nframes)), + nframes, rightBuffer.begin()); + + absl::Span widthEnvelope{self->fTmpWidthEnvelope.get(), nframes}; + absl::Span positionEnvelope{self->fTmpPositionEnvelope.get(), nframes}; + absl::Span 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(widthEnvelope, leftBuffer, rightBuffer); + pan(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(); +} diff --git a/tests/DemoStereo.ui b/tests/DemoStereo.ui new file mode 100644 index 00000000..b69cd01d --- /dev/null +++ b/tests/DemoStereo.ui @@ -0,0 +1,98 @@ + + + DemoStereoWindow + + + + 0 + 0 + 615 + 72 + + + + + + + + Width + + + + + + + + 500 + 0 + + + + -100 + + + 100 + + + Qt::Horizontal + + + QSlider::TicksBelow + + + + + + + -100 + + + 100 + + + + + + + Pan + + + + + + + + 500 + 0 + + + + -100 + + + 100 + + + Qt::Horizontal + + + QSlider::TicksBelow + + + + + + + -100 + + + 100 + + + + + + + + + diff --git a/tests/SIMDHelpersT.cpp b/tests/SIMDHelpersT.cpp index 1fff15b5..51f9426c 100644 --- a/tests/SIMDHelpersT.cpp +++ b/tests/SIMDHelpersT.cpp @@ -756,3 +756,61 @@ TEST_CASE("[Helpers] Diff (SIMD vs Scalar)") sfz::diff(input, absl::MakeSpan(outputSIMD)); REQUIRE(approxEqual(outputScalar, outputSIMD)); } + +TEST_CASE("[Helpers] Pan Scalar") +{ + std::array leftValue { 1.0f }; + std::array rightValue { 1.0f }; + auto left = absl::MakeSpan(leftValue); + auto right = absl::MakeSpan(rightValue); + SECTION("Pan = 0") + { + std::array pan { 0.0f }; + sfz::pan(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 pan { 1.0f }; + sfz::pan(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 pan { -1.0f }; + sfz::pan(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 leftValue { 1.0f }; + std::array rightValue { 1.0f }; + auto left = absl::MakeSpan(leftValue); + auto right = absl::MakeSpan(rightValue); + SECTION("width = 1") + { + std::array width { 1.0f }; + sfz::width(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 width { 0.0f }; + sfz::width(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 width { -1.0f }; + sfz::width(width, left, right); + REQUIRE(left[0] == Approx(1.0f).margin(0.001f)); + REQUIRE(right[0] == Approx(1.0f).margin(0.001f)); + } +}