Merge branch 'master' into refactor-samplebuffer

This commit is contained in:
saker
2023-09-25 21:13:26 -04:00
committed by GitHub
24 changed files with 808 additions and 73 deletions

View File

@@ -71,6 +71,7 @@ set(LMMS_SRCS
core/SamplePlayHandle.cpp
core/SampleRecordHandle.cpp
core/Scale.cpp
core/LmmsSemaphore.cpp
core/SerializingObject.cpp
core/Song.cpp
core/TempoSyncKnobModel.cpp
@@ -113,6 +114,7 @@ set(LMMS_SRCS
core/lv2/Lv2SubPluginFeatures.cpp
core/lv2/Lv2UridCache.cpp
core/lv2/Lv2UridMap.cpp
core/lv2/Lv2Worker.cpp
core/midi/MidiAlsaRaw.cpp
core/midi/MidiAlsaSeq.cpp

143
src/core/LmmsSemaphore.cpp Normal file
View File

@@ -0,0 +1,143 @@
/*
* Semaphore.cpp - Semaphore implementation
*
* Copyright (c) 2022-2022 Johannes Lorenz <jlsf2013$users.sourceforge.net, $=@>
*
* This file is part of LMMS - https://lmms.io
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this program (see COPYING); if not, write to the
* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301 USA.
*
*/
/*
* This code has been copied and adapted from https://github.com/drobilla/jalv
* File src/zix/sem.h
*/
#include "LmmsSemaphore.h"
#if defined(LMMS_BUILD_WIN32)
# include <limits.h>
#else
# include <errno.h>
#endif
#include <system_error>
namespace lmms {
#ifdef LMMS_BUILD_APPLE
Semaphore::Semaphore(unsigned val)
{
kern_return_t rval = semaphore_create(mach_task_self(), &m_sem, SYNC_POLICY_FIFO, val);
if(rval != 0) {
throw std::system_error(rval, std::system_category(), "Could not create semaphore");
}
}
Semaphore::~Semaphore()
{
semaphore_destroy(mach_task_self(), m_sem);
}
void Semaphore::post()
{
semaphore_signal(m_sem);
}
void Semaphore::wait()
{
kern_return_t rval = semaphore_wait(m_sem);
if (rval != KERN_SUCCESS) {
throw std::system_error(rval, std::system_category(), "Waiting for semaphore failed");
}
}
bool Semaphore::tryWait()
{
const mach_timespec_t zero = { 0, 0 };
return semaphore_timedwait(m_sem, zero) == KERN_SUCCESS;
}
#elif defined(LMMS_BUILD_WIN32)
Semaphore::Semaphore(unsigned initial)
{
if(CreateSemaphore(nullptr, initial, LONG_MAX, nullptr) == nullptr) {
throw std::system_error(GetLastError(), std::system_category(), "Could not create semaphore");
}
}
Semaphore::~Semaphore()
{
CloseHandle(m_sem);
}
void Semaphore::post()
{
ReleaseSemaphore(m_sem, 1, nullptr);
}
void Semaphore::wait()
{
if (WaitForSingleObject(m_sem, INFINITE) != WAIT_OBJECT_0) {
throw std::system_error(GetLastError(), std::system_category(), "Waiting for semaphore failed");
}
}
bool Semaphore::tryWait()
{
return WaitForSingleObject(m_sem, 0) == WAIT_OBJECT_0;
}
#else /* !defined(LMMS_BUILD_APPLE) && !defined(LMMS_BUILD_WIN32) */
Semaphore::Semaphore(unsigned initial)
{
if(sem_init(&m_sem, 0, initial) != 0) {
throw std::system_error(errno, std::generic_category(), "Could not create semaphore");
}
}
Semaphore::~Semaphore()
{
sem_destroy(&m_sem);
}
void Semaphore::post()
{
sem_post(&m_sem);
}
void Semaphore::wait()
{
while (sem_wait(&m_sem) != 0) {
if (errno != EINTR) {
throw std::system_error(errno, std::generic_category(), "Waiting for semaphore failed");
}
/* Otherwise, interrupted, so try again. */
}
}
bool Semaphore::tryWait()
{
return (sem_trywait(&m_sem) == 0);
}
#endif
} // namespace lmms

View File

@@ -48,7 +48,7 @@ Lv2Features::Lv2Features()
{
const Lv2Manager* man = Engine::getLv2Manager();
// create (yet empty) map feature URI -> feature
for(const char* uri : man->supportedFeatureURIs())
for(auto uri : man->supportedFeatureURIs())
{
m_featureByUri.emplace(uri, nullptr);
}
@@ -71,7 +71,7 @@ void Lv2Features::initCommon()
void Lv2Features::createFeatureVectors()
{
// create vector of features
for(std::pair<const char* const, void*>& pr : m_featureByUri)
for(const auto& [uri, feature] : m_featureByUri)
{
/*
If pr.second is nullptr here, this means that the LV2_feature
@@ -82,7 +82,7 @@ void Lv2Features::createFeatureVectors()
vector creation (This can be done in
Lv2Proc::initPluginSpecificFeatures or in Lv2Features::initCommon)
*/
m_features.push_back(LV2_Feature { pr.first, pr.second });
m_features.push_back(LV2_Feature{(const char*)uri.data(), (void*)feature});
}
// create pointer vector (for lilv_plugin_instantiate)

View File

@@ -28,13 +28,14 @@
#include <algorithm>
#include <cstdlib>
#include <cstring>
#include <lilv/lilv.h>
#include <lv2/lv2plug.in/ns/ext/buf-size/buf-size.h>
#include <lv2/lv2plug.in/ns/ext/options/options.h>
#include <lv2/lv2plug.in/ns/ext/worker/worker.h>
#include <QDebug>
#include <QElapsedTimer>
#include "AudioEngine.h"
#include "Engine.h"
#include "Plugin.h"
#include "Lv2ControlBase.h"
@@ -46,7 +47,7 @@ namespace lmms
{
const std::set<const char*, Lv2Manager::CmpStr> Lv2Manager::pluginBlacklist =
const std::set<std::string_view> Lv2Manager::pluginBlacklist =
{
// github.com/calf-studio-gear/calf, #278
"http://calf.sourceforge.net/plugins/Analyzer",
@@ -137,6 +138,26 @@ const std::set<const char*, Lv2Manager::CmpStr> Lv2Manager::pluginBlacklist =
"urn:juced:DrumSynth"
};
const std::set<std::string_view> Lv2Manager::pluginBlacklistBuffersizeLessThan32 =
{
"http://moddevices.com/plugins/mod-devel/2Voices",
"http://moddevices.com/plugins/mod-devel/Capo",
"http://moddevices.com/plugins/mod-devel/Drop",
"http://moddevices.com/plugins/mod-devel/Harmonizer",
"http://moddevices.com/plugins/mod-devel/Harmonizer2",
"http://moddevices.com/plugins/mod-devel/HarmonizerCS",
"http://moddevices.com/plugins/mod-devel/SuperCapo",
"http://moddevices.com/plugins/mod-devel/SuperWhammy",
"http://moddevices.com/plugins/mod-devel/Gx2Voices",
"http://moddevices.com/plugins/mod-devel/GxCapo",
"http://moddevices.com/plugins/mod-devel/GxDrop",
"http://moddevices.com/plugins/mod-devel/GxHarmonizer",
"http://moddevices.com/plugins/mod-devel/GxHarmonizer2",
"http://moddevices.com/plugins/mod-devel/GxHarmonizerCS",
"http://moddevices.com/plugins/mod-devel/GxSuperCapo",
"http://moddevices.com/plugins/mod-devel/GxSuperWhammy"
};
@@ -152,10 +173,15 @@ Lv2Manager::Lv2Manager() :
m_supportedFeatureURIs.insert(LV2_URID__map);
m_supportedFeatureURIs.insert(LV2_URID__unmap);
m_supportedFeatureURIs.insert(LV2_OPTIONS__options);
m_supportedFeatureURIs.insert(LV2_WORKER__schedule);
// min/max is always passed in the options
m_supportedFeatureURIs.insert(LV2_BUF_SIZE__boundedBlockLength);
// block length is only changed initially in AudioEngine CTOR
m_supportedFeatureURIs.insert(LV2_BUF_SIZE__fixedBlockLength);
if (const auto fpp = Engine::audioEngine()->framesPerPeriod(); (fpp & (fpp - 1)) == 0) // <=> ffp is power of 2 (for ffp > 0)
{
m_supportedFeatureURIs.insert(LV2_BUF_SIZE__powerOf2BlockLength);
}
auto supportOpt = [this](Lv2UridCache::Id id)
{
@@ -288,14 +314,6 @@ void Lv2Manager::initPlugins()
bool Lv2Manager::CmpStr::operator()(const char *a, const char *b) const
{
return std::strcmp(a, b) < 0;
}
bool Lv2Manager::isFeatureSupported(const char *featName) const
{
return m_supportedFeatureURIs.find(featName) != m_supportedFeatureURIs.end();

View File

@@ -1,7 +1,7 @@
/*
* Lv2Proc.cpp - Lv2 processor class
*
* Copyright (c) 2019-2020 Johannes Lorenz <jlsf2013$users.sourceforge.net, $=@>
* Copyright (c) 2019-2022 Johannes Lorenz <jlsf2013$users.sourceforge.net, $=@>
*
* This file is part of LMMS - https://lmms.io
*
@@ -30,6 +30,7 @@
#include <lv2/lv2plug.in/ns/ext/midi/midi.h>
#include <lv2/lv2plug.in/ns/ext/atom/atom.h>
#include <lv2/lv2plug.in/ns/ext/resize-port/resize-port.h>
#include <lv2/lv2plug.in/ns/ext/worker/worker.h>
#include <QDebug>
#include <QDomDocument>
#include <QtGlobal>
@@ -75,11 +76,19 @@ Plugin::Type Lv2Proc::check(const LilvPlugin *plugin,
// TODO: manage a global blacklist outside of the code
// for now, this will help
// this is only a fix for the meantime
const auto& pluginBlacklist = Lv2Manager::getPluginBlacklist();
if (!Engine::ignorePluginBlacklist() &&
pluginBlacklist.find(pluginUri) != pluginBlacklist.end())
if (!Engine::ignorePluginBlacklist())
{
issues.emplace_back(PluginIssueType::Blacklisted);
const auto& pluginBlacklist = Lv2Manager::getPluginBlacklist();
const auto& pluginBlacklist32 = Lv2Manager::getPluginBlacklistBuffersizeLessThan32();
if(pluginBlacklist.find(pluginUri) != pluginBlacklist.end())
{
issues.emplace_back(PluginIssueType::Blacklisted);
}
else if(Engine::audioEngine()->framesPerPeriod() <= 32 &&
pluginBlacklist32.find(pluginUri) != pluginBlacklist32.end())
{
issues.emplace_back(PluginIssueType::Blacklisted); // currently no special blacklist category
}
}
for (unsigned portNum = 0; portNum < maxPorts; ++portNum)
@@ -162,6 +171,7 @@ Plugin::Type Lv2Proc::check(const LilvPlugin *plugin,
Lv2Proc::Lv2Proc(const LilvPlugin *plugin, Model* parent) :
LinkedModelGroup(parent),
m_plugin(plugin),
m_workLock(1),
m_midiInputBuf(m_maxMidiInputEvents),
m_midiInputReader(m_midiInputBuf)
{
@@ -352,7 +362,19 @@ void Lv2Proc::copyBuffersToCore(sampleFrame* buf,
void Lv2Proc::run(fpp_t frames)
{
if (m_worker)
{
// Process any worker replies
m_worker->emitResponses();
}
lilv_instance_run(m_instance, static_cast<uint32_t>(frames));
if (m_worker)
{
// Notify the plugin the run() cycle is finished
m_worker->notifyPluginThatRunFinished();
}
}
@@ -420,6 +442,9 @@ void Lv2Proc::initPlugin()
if (m_instance)
{
if(m_worker) {
m_worker->setHandle(lilv_instance_get_handle(m_instance));
}
for (std::size_t portNum = 0; portNum < m_ports.size(); ++portNum)
connectPort(portNum);
lilv_instance_activate(m_instance);
@@ -496,8 +521,20 @@ void Lv2Proc::initMOptions()
void Lv2Proc::initPluginSpecificFeatures()
{
// options
initMOptions();
m_features[LV2_OPTIONS__options] = const_cast<LV2_Options_Option*>(m_options.feature());
// worker (if plugin has worker extension)
Lv2Manager* mgr = Engine::getLv2Manager();
if (lilv_plugin_has_extension_data(m_plugin, mgr->uri(LV2_WORKER__interface).get())) {
const auto iface = static_cast<const LV2_Worker_Interface*>(
lilv_instance_get_extension_data(m_instance, LV2_WORKER__interface));
bool threaded = !Engine::audioEngine()->renderOnly();
m_worker.emplace(iface, &m_workLock, threaded);
m_features[LV2_WORKER__schedule] = m_worker->feature();
// Note: m_worker::setHandle will still need to be called later
}
}
@@ -566,21 +603,35 @@ void Lv2Proc::createPort(std::size_t portNum)
break;
case Lv2Ports::Vis::Enumeration:
{
auto comboModel = new ComboBoxModel(nullptr, dispName);
LilvScalePoints* sps =
lilv_port_get_scale_points(m_plugin, lilvPort);
LILV_FOREACH(scale_points, i, sps)
ComboBoxModel* comboModel = new ComboBoxModel(nullptr, dispName);
{
const LilvScalePoint* sp = lilv_scale_points_get(sps, i);
ctrl->m_scalePointMap.push_back(lilv_node_as_float(
lilv_scale_point_get_value(sp)));
comboModel->addItem(
lilv_node_as_string(
lilv_scale_point_get_label(sp)));
AutoLilvScalePoints sps (static_cast<LilvScalePoints*>(lilv_port_get_scale_points(m_plugin, lilvPort)));
// temporary map, since lilv may return scale points in random order
std::map<float, const char*> scalePointMap;
LILV_FOREACH(scale_points, i, sps.get())
{
const LilvScalePoint* sp = lilv_scale_points_get(sps.get(), i);
const float f = lilv_node_as_float(lilv_scale_point_get_value(sp));
const char* s = lilv_node_as_string(lilv_scale_point_get_label(sp));
scalePointMap[f] = s;
}
for (const auto& [f,s] : scalePointMap)
{
ctrl->m_scalePointMap.push_back(f);
comboModel->addItem(s);
}
}
for(std::size_t i = 0; i < ctrl->m_scalePointMap.size(); ++i)
{
if(meta.def() == ctrl->m_scalePointMap[i])
{
comboModel->setValue(i);
comboModel->setInitValue(i);
break;
}
}
lilv_scale_points_free(sps);
ctrl->m_connectedModel.reset(comboModel);
// TODO: use default value on comboModel, too?
break;
}
case Lv2Ports::Vis::Toggled:

203
src/core/lv2/Lv2Worker.cpp Normal file
View File

@@ -0,0 +1,203 @@
/*
* Lv2Worker.cpp - Lv2Worker implementation
*
* Copyright (c) 2022-2022 Johannes Lorenz <jlsf2013$users.sourceforge.net, $=@>
*
* This file is part of LMMS - https://lmms.io
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this program (see COPYING); if not, write to the
* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301 USA.
*
*/
#include "Lv2Worker.h"
#include <cassert>
#include <QDebug>
#ifdef LMMS_HAVE_LV2
#include "Engine.h"
namespace lmms
{
// static wrappers
static LV2_Worker_Status
staticWorkerRespond(LV2_Worker_Respond_Handle handle,
uint32_t size, const void* data)
{
Lv2Worker* worker = static_cast<Lv2Worker*>(handle);
return worker->respond(size, data);
}
std::size_t Lv2Worker::bufferSize() const
{
// ardour uses this fixed size for ALSA:
return 8192 * 4;
// for jack, they use 4 * jack_port_type_get_buffer_size (..., JACK_DEFAULT_MIDI_TYPE)
// (possible extension for AudioDevice)
}
Lv2Worker::Lv2Worker(const LV2_Worker_Interface* iface,
Semaphore* common_work_lock,
bool threaded) :
m_iface(iface),
m_threaded(threaded),
m_response(bufferSize()),
m_requests(bufferSize()),
m_responses(bufferSize()),
m_requestsReader(m_requests),
m_responsesReader(m_responses),
m_sem(0),
m_workLock(common_work_lock)
{
assert(iface);
m_scheduleFeature.handle = static_cast<LV2_Worker_Schedule_Handle>(this);
m_scheduleFeature.schedule_work = [](LV2_Worker_Schedule_Handle handle,
uint32_t size, const void* data) -> LV2_Worker_Status
{
Lv2Worker* worker = static_cast<Lv2Worker*>(handle);
return worker->scheduleWork(size, data);
};
if (threaded) { m_thread = std::thread(&Lv2Worker::workerFunc, this); }
m_requests.mlock();
m_responses.mlock();
}
Lv2Worker::~Lv2Worker()
{
m_exit = true;
if(m_threaded) {
m_sem.post();
m_thread.join();
}
}
// Let the worker send responses to the audio thread
LV2_Worker_Status Lv2Worker::respond(uint32_t size, const void* data)
{
if(m_threaded)
{
if(m_responses.free() < sizeof(size) + size)
{
return LV2_WORKER_ERR_NO_SPACE;
}
else
{
m_responses.write((const char*)&size, sizeof(size));
if(size && data) { m_responses.write((const char*)data, size); }
}
}
else
{
m_iface->work_response(m_handle, size, data);
}
return LV2_WORKER_SUCCESS;
}
// Let the worker receive work from the audio thread and "work" on it
void Lv2Worker::workerFunc()
{
std::vector<char> buf;
uint32_t size;
while (true) {
m_sem.wait();
if (m_exit) { break; }
const std::size_t readSpace = m_requestsReader.read_space();
if (readSpace <= sizeof(size)) { continue; } // (should not happen)
m_requestsReader.read(sizeof(size)).copy((char*)&size, sizeof(size));
assert(size <= readSpace - sizeof(size));
if(size > buf.size()) { buf.resize(size); }
if(size) { m_requestsReader.read(size).copy(buf.data(), size); }
m_workLock->wait();
m_iface->work(m_handle, staticWorkerRespond, this, size, buf.data());
m_workLock->post();
}
}
// Let the audio thread schedule work for the worker
LV2_Worker_Status Lv2Worker::scheduleWork(uint32_t size, const void *data)
{
if (m_threaded)
{
if(m_requests.free() < sizeof(size) + size)
{
return LV2_WORKER_ERR_NO_SPACE;
}
else
{
// Schedule a request to be executed by the worker thread
m_requests.write((const char*)&size, sizeof(size));
if(size && data) { m_requests.write((const char*)data, size); }
m_sem.post();
}
}
else
{
// Execute work immediately in this thread
m_workLock->wait();
m_iface->work(m_handle, staticWorkerRespond, this, size, data);
m_workLock->post();
}
return LV2_WORKER_SUCCESS;
}
// Let the audio thread read incoming worker responses, and process it
void Lv2Worker::emitResponses()
{
std::size_t read_space = m_responsesReader.read_space();
uint32_t size;
while (read_space > sizeof(size)) {
m_responsesReader.read(sizeof(size)).copy((char*)&size, sizeof(size));
if(size) { m_responsesReader.read(size).copy(m_response.data(), size); }
m_iface->work_response(m_handle, size, m_response.data());
read_space -= sizeof(size) + size;
}
}
} // namespace lmms
#endif // LMMS_HAVE_LV2

View File

@@ -39,6 +39,7 @@
#include "GuiApplication.h"
#include "embed.h"
#include "gui_templates.h"
#include "lmms_math.h"
#include "Lv2ControlBase.h"
#include "Lv2Manager.h"
#include "Lv2Proc.h"
@@ -51,13 +52,13 @@ namespace lmms::gui
{
Lv2ViewProc::Lv2ViewProc(QWidget* parent, Lv2Proc* ctrlBase, int colNum) :
LinkedModelGroupView (parent, ctrlBase, colNum)
Lv2ViewProc::Lv2ViewProc(QWidget* parent, Lv2Proc* proc, int colNum) :
LinkedModelGroupView (parent, proc, colNum)
{
class SetupWidget : public Lv2Ports::ConstVisitor
class SetupTheWidget : public Lv2Ports::ConstVisitor
{
public:
QWidget* m_par; // input
QWidget* m_parent; // input
const LilvNode* m_commentUri; // input
Control* m_control = nullptr; // output
void visit(const Lv2Ports::Control& port) override
@@ -69,20 +70,22 @@ Lv2ViewProc::Lv2ViewProc(QWidget* parent, Lv2Proc* ctrlBase, int colNum) :
switch (port.m_vis)
{
case PortVis::Generic:
m_control = new KnobControl(m_par);
m_control = new KnobControl(m_parent);
break;
case PortVis::Integer:
{
sample_rate_t sr = Engine::audioEngine()->processingSampleRate();
m_control = new LcdControl((port.max(sr) <= 9.0f) ? 1 : 2,
m_par);
auto pMin = port.min(sr);
auto pMax = port.max(sr);
int numDigits = std::max(numDigitsAsInt(pMin), numDigitsAsInt(pMax));
m_control = new LcdControl(numDigits, m_parent);
break;
}
case PortVis::Enumeration:
m_control = new ComboControl(m_par);
m_control = new ComboControl(m_parent);
break;
case PortVis::Toggled:
m_control = new CheckControl(m_par);
m_control = new CheckControl(m_parent);
break;
}
m_control->setText(port.name());
@@ -100,14 +103,14 @@ Lv2ViewProc::Lv2ViewProc(QWidget* parent, Lv2Proc* ctrlBase, int colNum) :
};
AutoLilvNode commentUri = uri(LILV_NS_RDFS "comment");
ctrlBase->foreach_port(
proc->foreach_port(
[this, &commentUri](const Lv2Ports::PortBase* port)
{
if(!lilv_port_has_property(port->m_plugin, port->m_port,
uri(LV2_PORT_PROPS__notOnGUI).get()))
{
SetupWidget setup;
setup.m_par = this;
SetupTheWidget setup;
setup.m_parent = this;
setup.m_commentUri = commentUri.get();
port->accept(setup);

View File

@@ -579,32 +579,39 @@ SetupDialog::SetupDialog(ConfigTab tab_to_open) :
// Buffer size tab.
auto bufferSize_tw = new TabWidget(tr("Buffer size"), audio_w);
bufferSize_tw->setFixedHeight(76);
auto bufferSize_layout = new QVBoxLayout(bufferSize_tw);
bufferSize_layout->setSpacing(10);
bufferSize_layout->setContentsMargins(10, 18, 10, 10);
m_bufferSizeSlider = new QSlider(Qt::Horizontal, bufferSize_tw);
m_bufferSizeSlider->setRange(1, 128);
m_bufferSizeSlider->setTickInterval(8);
m_bufferSizeSlider->setPageStep(8);
m_bufferSizeSlider->setValue(m_bufferSize / BUFFERSIZE_RESOLUTION);
m_bufferSizeSlider->setGeometry(10, 18, 340, 18);
m_bufferSizeSlider->setTickPosition(QSlider::TicksBelow);
m_bufferSizeLbl = new QLabel(bufferSize_tw);
m_bufferSizeWarnLbl = new QLabel(bufferSize_tw);
m_bufferSizeWarnLbl->setWordWrap(true);
connect(m_bufferSizeSlider, SIGNAL(valueChanged(int)),
this, SLOT(setBufferSize(int)));
connect(m_bufferSizeSlider, SIGNAL(valueChanged(int)),
this, SLOT(showRestartWarning()));
m_bufferSizeLbl = new QLabel(bufferSize_tw);
m_bufferSizeLbl->setGeometry(10, 40, 200, 24);
setBufferSize(m_bufferSizeSlider->value());
auto bufferSize_reset_btn = new QPushButton(embed::getIconPixmap("reload"), "", bufferSize_tw);
bufferSize_reset_btn->setGeometry(320, 40, 28, 28);
connect(bufferSize_reset_btn, SIGNAL(clicked()),
this, SLOT(resetBufferSize()));
bufferSize_reset_btn->setToolTip(
tr("Reset to default value"));
bufferSize_layout->addWidget(m_bufferSizeSlider);
bufferSize_layout->addWidget(m_bufferSizeLbl);
bufferSize_layout->addWidget(m_bufferSizeWarnLbl);
bufferSize_layout->addWidget(bufferSize_reset_btn);
// Audio layout ordering.
audio_layout->addWidget(audioiface_tw);
@@ -1172,6 +1179,24 @@ void SetupDialog::audioInterfaceChanged(const QString & iface)
}
void SetupDialog::updateBufferSizeWarning(int value)
{
QString text = "<ul>";
if((value & (value - 1)) != 0) // <=> value is not a power of 2 (for value > 0)
{
text += "<li>" + tr("The currently selected value is not a power of 2 "
"(32, 64, 128, 256, 512, 1024, ...). Some plugins may not be available.") + "</li>";
}
if(value <= 32)
{
text += "<li>" + tr("The currently selected value is less than or equal to 32. "
"Some plugins may not be available.") + "</li>";
}
text += "</ul>";
m_bufferSizeWarnLbl->setText(text);
}
void SetupDialog::setBufferSize(int value)
{
const int step = DEFAULT_BUFFER_SIZE / BUFFERSIZE_RESOLUTION;
@@ -1197,6 +1222,7 @@ void SetupDialog::setBufferSize(int value)
m_bufferSize = value * BUFFERSIZE_RESOLUTION;
m_bufferSizeLbl->setText(tr("Frames: %1\nLatency: %2 ms").arg(m_bufferSize).arg(
1000.0f * m_bufferSize / Engine::audioEngine()->processingSampleRate(), 0, 'f', 1));
updateBufferSizeWarning(m_bufferSize);
}