From ef5a2fa546a3f257ca38301e761a8ec0b47ee01e Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Mon, 10 Feb 2020 16:16:15 +0100 Subject: [PATCH] Add the test program for stereo --- tests/CMakeLists.txt | 5 + tests/DemoStereo.cpp | 238 +++++++++++++++++++++++++++++++++++++++++++ tests/DemoStereo.ui | 98 ++++++++++++++++++ 3 files changed, 341 insertions(+) create mode 100644 tests/DemoStereo.cpp create mode 100644 tests/DemoStereo.ui 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..0c6f47ee --- /dev/null +++ b/tests/DemoStereo.cpp @@ -0,0 +1,238 @@ +// 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; + + /* TODO(jpc) have this code in common instead of copy-paste */ + + // Create mid/side from left/right in the output buffer + // Add const aliases to be slightly more readable + const auto leftBufferCopy = tempSpan1; + copy(leftBuffer, leftBufferCopy); + + const auto midBuffer = leftBuffer; + add(rightBuffer, midBuffer); + + const auto sideBuffer = rightBuffer; + applyGain(-1.0f, sideBuffer); + add(leftBufferCopy, sideBuffer); + + applyGain(sqrtTwoInv, midBuffer); + applyGain(sqrtTwoInv, sideBuffer); + + // Apply the width process + width(widthEnvelope, midBuffer, sideBuffer); + + // Copy the mid channel into another span + const auto midBufferCopy = tempSpan1; + copy(midBuffer, midBufferCopy); + pan(positionEnvelope, midBuffer, midBufferCopy); + + // Rebuild left/right + // Recall that midBuffer and leftBuffer point to the same buffer + add(sideBuffer, leftBuffer); + applyGain(sqrtTwoInv, leftBuffer); + + // Recall that sideBuffer and rightBuffer point to the same buffer + applyGain(-1.0f, sideBuffer); + add(midBufferCopy, sideBuffer); + applyGain(sqrtTwoInv, 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 + + + + + + + + +