Merge branch 'master' into refactor-samplebuffer
This commit is contained in:
@@ -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
143
src/core/LmmsSemaphore.cpp
Normal 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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
203
src/core/lv2/Lv2Worker.cpp
Normal 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
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user