Merge pull request #56 from paulfd/width-position

Add pan width and position using the new helpers
This commit is contained in:
Paul Ferrand 2020-02-11 08:14:56 +01:00 committed by GitHub
commit 00c3af6881
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 596 additions and 104 deletions

View file

@ -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);

View 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();

View file

@ -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

View file

@ -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 };

View file

@ -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
*

View file

@ -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 <>

View file

@ -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.
*

View file

@ -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() };

View file

@ -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
View 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
View 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>

View file

@ -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));
}
}