Add the test program for stereo
This commit is contained in:
parent
33cc4a6518
commit
ef5a2fa546
3 changed files with 341 additions and 0 deletions
|
|
@ -42,6 +42,11 @@ if(JACK_FOUND AND TARGET Qt5::Widgets)
|
||||||
target_include_directories(sfizz_demo_filters PRIVATE ${JACK_INCLUDE_DIRS})
|
target_include_directories(sfizz_demo_filters PRIVATE ${JACK_INCLUDE_DIRS})
|
||||||
target_link_libraries(sfizz_demo_filters PRIVATE sfizz::sfizz Qt5::Widgets ${JACK_LIBRARIES})
|
target_link_libraries(sfizz_demo_filters PRIVATE sfizz::sfizz Qt5::Widgets ${JACK_LIBRARIES})
|
||||||
set_target_properties(sfizz_demo_filters PROPERTIES AUTOUIC ON)
|
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()
|
endif()
|
||||||
|
|
||||||
file(COPY "." DESTINATION ${CMAKE_BINARY_DIR}/tests)
|
file(COPY "." DESTINATION ${CMAKE_BINARY_DIR}/tests)
|
||||||
|
|
|
||||||
238
tests/DemoStereo.cpp
Normal file
238
tests/DemoStereo.cpp
Normal file
|
|
@ -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 <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;
|
||||||
|
|
||||||
|
/* 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<float>(leftBuffer, leftBufferCopy);
|
||||||
|
|
||||||
|
const auto midBuffer = leftBuffer;
|
||||||
|
add<float>(rightBuffer, midBuffer);
|
||||||
|
|
||||||
|
const auto sideBuffer = rightBuffer;
|
||||||
|
applyGain<float>(-1.0f, sideBuffer);
|
||||||
|
add<float>(leftBufferCopy, sideBuffer);
|
||||||
|
|
||||||
|
applyGain<float>(sqrtTwoInv<float>, midBuffer);
|
||||||
|
applyGain<float>(sqrtTwoInv<float>, sideBuffer);
|
||||||
|
|
||||||
|
// Apply the width process
|
||||||
|
width<float>(widthEnvelope, midBuffer, sideBuffer);
|
||||||
|
|
||||||
|
// Copy the mid channel into another span
|
||||||
|
const auto midBufferCopy = tempSpan1;
|
||||||
|
copy<float>(midBuffer, midBufferCopy);
|
||||||
|
pan<float>(positionEnvelope, midBuffer, midBufferCopy);
|
||||||
|
|
||||||
|
// Rebuild left/right
|
||||||
|
// Recall that midBuffer and leftBuffer point to the same buffer
|
||||||
|
add<float>(sideBuffer, leftBuffer);
|
||||||
|
applyGain(sqrtTwoInv<float>, leftBuffer);
|
||||||
|
|
||||||
|
// Recall that sideBuffer and rightBuffer point to the same buffer
|
||||||
|
applyGain<float>(-1.0f, sideBuffer);
|
||||||
|
add<float>(midBufferCopy, sideBuffer);
|
||||||
|
applyGain(sqrtTwoInv<float>, 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>
|
||||||
Loading…
Add table
Reference in a new issue