Merge remote-tracking branch 'upstream/master' into dynamic-effect-dialog

Merge master from upstream to get past the problems related to the
submodule for resid once and for all. See #6916.
This commit is contained in:
Michael Gregorius
2023-10-03 09:06:43 +02:00
57 changed files with 1208 additions and 425 deletions

6
.gitmodules vendored
View File

@@ -40,9 +40,9 @@
[submodule "plugins/CarlaBase/carla"]
path = plugins/CarlaBase/carla
url = https://github.com/falktx/carla
[submodule "plugins/Sid/resid"]
path = plugins/Sid/resid
url = https://github.com/simonowen/resid
[submodule "plugins/Sid/resid/resid"]
path = plugins/Sid/resid/resid
url = https://github.com/libsidplayfp/resid
[submodule "src/3rdparty/jack2"]
path = src/3rdparty/jack2
url = https://github.com/jackaudio/jack2

View File

@@ -35,7 +35,7 @@ INCLUDE(GenerateExportHeader)
STRING(TOUPPER "${CMAKE_PROJECT_NAME}" PROJECT_NAME_UCASE)
SET(PROJECT_YEAR 2020)
SET(PROJECT_YEAR 2023)
SET(PROJECT_AUTHOR "LMMS Developers")
SET(PROJECT_URL "https://lmms.io")
@@ -77,6 +77,7 @@ OPTION(WANT_SOUNDIO "Include libsoundio support" ON)
OPTION(WANT_SDL "Include SDL (Simple DirectMedia Layer) support" ON)
OPTION(WANT_SF2 "Include SoundFont2 player plugin" ON)
OPTION(WANT_GIG "Include GIG player plugin" ON)
option(WANT_SID "Include Sid instrument" ON)
OPTION(WANT_STK "Include Stk (Synthesis Toolkit) support" ON)
OPTION(WANT_SWH "Include Steve Harris's LADSPA plugins" ON)
OPTION(WANT_TAP "Include Tom's Audio Processing LADSPA plugins" ON)
@@ -211,6 +212,13 @@ CHECK_CXX_SOURCE_COMPILES(
LMMS_HAVE_SF_COMPLEVEL
)
# check for perl
if(LMMS_BUILD_APPLE)
# Prefer system perl over Homebrew, MacPorts, etc
set(Perl_ROOT "/usr/bin")
endif()
find_package(Perl)
IF(WANT_LV2)
IF(PKG_CONFIG_FOUND)
PKG_CHECK_MODULES(LV2 lv2)
@@ -273,11 +281,6 @@ ELSE(WANT_CMT)
ENDIF(WANT_CMT)
IF(WANT_SWH)
IF(LMMS_BUILD_APPLE)
# Prefer system perl over Homebrew, MacPorts, etc
SET(Perl_ROOT "/usr/bin")
ENDIF()
FIND_PACKAGE(Perl)
IF(PERL_FOUND)
SET(LMMS_HAVE_SWH TRUE)
SET(STATUS_SWH "OK")
@@ -349,6 +352,16 @@ IF(WANT_SDL AND NOT LMMS_HAVE_SDL2)
ENDIF()
ENDIF()
# check for Sid
if(WANT_SID)
if(PERL_FOUND)
set(LMMS_HAVE_SID TRUE)
set(STATUS_SID "OK")
else()
set(STATUS_SID "not found, please install perl if you require the Sid instrument")
endif()
endif()
# check for Stk
IF(WANT_STK)
FIND_PACKAGE(STK)
@@ -816,6 +829,7 @@ MESSAGE(
"* ZynAddSubFX instrument : ${STATUS_ZYN}\n"
"* Carla Patchbay & Rack : ${STATUS_CARLA}\n"
"* SoundFont2 player : ${STATUS_FLUIDSYNTH}\n"
"* Sid instrument : ${STATUS_SID}\n"
"* Stk Mallets : ${STATUS_STK}\n"
"* VST-instrument hoster : ${STATUS_VST}\n"
"* VST-effect hoster : ${STATUS_VST}\n"

View File

@@ -3,7 +3,7 @@
********************/
/* most foreground text items */
QLabel, QTreeWidget, QListWidget, QGroupBox, QMenuBar {
QLabel, QTreeWidget, QListWidget, QGroupBox, QMenuBar, QCheckBox {
color: #d1d8e4;
}
@@ -464,6 +464,10 @@ lmms--gui--EffectSelectDialog QScrollArea {
background: #262b30;
}
lmms--gui--SetupDialog QScrollArea {
border: 0px;
}
/* the inner boxes in LADSPA effect windows */
lmms--gui--EffectControlDialog QGroupBox {

View File

@@ -2,6 +2,7 @@
* AudioDeviceSetupWidget.h - Base class for audio device setup widgets
*
* Copyright (c) 2004-2015 Tobias Doerffel <tobydox/at/users.sourceforge.net>
* Copyright (c) 2023- Michael Gregorius
*
* This file is part of LMMS - https://lmms.io
*
@@ -25,12 +26,12 @@
#ifndef LMMS_GUI_AUDIO_DEVICE_SETUP_WIDGET_H
#define LMMS_GUI_AUDIO_DEVICE_SETUP_WIDGET_H
#include "TabWidget.h"
#include <QGroupBox>
namespace lmms::gui
{
class AudioDeviceSetupWidget : public TabWidget
class AudioDeviceSetupWidget : public QGroupBox
{
Q_OBJECT
public:

View File

@@ -197,6 +197,7 @@ public:
// audio-device-stuff
bool renderOnly() const { return m_renderOnly; }
// Returns the current audio device's name. This is not necessarily
// the user's preferred audio device, in case you were thinking that.
inline const QString & audioDevName() const

View File

@@ -48,8 +48,8 @@ public:
m_value{value}
{}
constexpr auto testAll(Flags flags) const -> bool { return *this & flags == flags; }
constexpr auto testAny(Flags flags) const -> bool { return *this & flags != Flags{}; }
constexpr auto testAll(Flags flags) const -> bool { return (*this & flags) == flags; }
constexpr auto testAny(Flags flags) const -> bool { return (*this & flags) != Flags{}; }
constexpr auto testFlag(EnumType flag) const -> bool { return static_cast<bool>(*this & flag); }
constexpr auto operator~() const -> Flags { return Flags{~m_value}; }

93
include/LmmsSemaphore.h Normal file
View File

@@ -0,0 +1,93 @@
/*
* Semaphore.h - Semaphore declaration
*
* 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
*/
#ifndef LMMS_SEMAPHORE_H
#define LMMS_SEMAPHORE_H
#include "lmmsconfig.h"
#ifdef LMMS_BUILD_APPLE
# include <mach/mach.h>
#elif defined(LMMS_BUILD_WIN32)
# include <windows.h>
#else
# include <semaphore.h>
#endif
#include <system_error>
namespace lmms {
/**
A counting semaphore.
This is an integer that is always positive, and has two main operations:
increment (post) and decrement (wait). If a decrement can not be performed
(i.e. the value is 0) the caller will be blocked until another thread posts
and the operation can succeed.
Semaphores can be created with any starting value, but typically this will
be 0 so the semaphore can be used as a simple signal where each post
corresponds to one wait.
Semaphores are very efficient (much moreso than a mutex/cond pair). In
particular, at least on Linux, post is async-signal-safe, which means it
does not block and will not be interrupted. If you need to signal from
a realtime thread, this is the most appropriate primitive to use.
@note Likely outdated with C++20's std::counting_semaphore
(though we have to check that this will be RT conforming on all platforms)
*/
class Semaphore
{
public:
Semaphore(unsigned initial);
Semaphore(const Semaphore&) = delete;
Semaphore& operator=(const Semaphore&) = delete;
Semaphore(Semaphore&&) = delete;
Semaphore& operator=(Semaphore&&) = delete;
~Semaphore();
void post();
void wait();
bool tryWait();
private:
#ifdef LMMS_BUILD_APPLE
semaphore_t m_sem;
#elif defined(LMMS_BUILD_WIN32)
HANDLE m_sem;
#else
sem_t m_sem;
#endif
};
} // namespace lmms
#endif // LMMS_SEMAPHORE_H

View File

@@ -51,13 +51,14 @@ public:
std::size_t capacity() const {return m_buffer.maximum_eventual_write_space();}
std::size_t free() const {return m_buffer.write_space();}
void wakeAll() {m_notifier.wakeAll();}
std::size_t write(const sampleFrame *src, std::size_t cnt, bool notify = false)
std::size_t write(const T *src, std::size_t cnt, bool notify = false)
{
std::size_t written = LocklessRingBuffer<T>::m_buffer.write(src, cnt);
// Let all waiting readers know new data are available.
if (notify) {LocklessRingBuffer<T>::m_notifier.wakeAll();}
return written;
}
void mlock() { m_buffer.mlock(); }
protected:
ringbuffer_t<T> m_buffer;

View File

@@ -47,8 +47,14 @@ struct LilvNodesDeleter
void operator()(LilvNodes* n) { lilv_nodes_free(n); }
};
struct LilvScalePointsDeleter
{
void operator()(LilvScalePoints* s) { lilv_scale_points_free(s); }
};
using AutoLilvNode = std::unique_ptr<LilvNode, LilvNodeDeleter>;
using AutoLilvNodes = std::unique_ptr<LilvNodes, LilvNodesDeleter>;
using AutoLilvScalePoints = std::unique_ptr<LilvScalePoints, LilvScalePointsDeleter>;
/**
Return QString from a plugin's node, everything will be freed automatically

View File

@@ -30,6 +30,7 @@
#ifdef LMMS_HAVE_LV2
#include <map>
#include <string_view>
#include <vector>
#include "Lv2Manager.h"
@@ -78,7 +79,7 @@ private:
//! pointers to m_features, required for lilv_plugin_instantiate
std::vector<const LV2_Feature*> m_featurePointers;
//! features + data, ordered by URI
std::map<const char*, void*, Lv2Manager::CmpStr> m_featureByUri;
std::map<std::string_view, void*> m_featureByUri;
};

View File

@@ -31,6 +31,7 @@
#include <map>
#include <set>
#include <string_view>
#include <lilv/lilv.h>
#include "Lv2Basics.h"
@@ -120,15 +121,9 @@ public:
Iterator begin() { return m_lv2InfoMap.begin(); }
Iterator end() { return m_lv2InfoMap.end(); }
//! strcmp based key comparator for std::set and std::map
struct CmpStr
{
bool operator()(char const *a, char const *b) const;
};
UridMap& uridMap() { return m_uridMap; }
const Lv2UridCache& uridCache() const { return m_uridCache; }
const std::set<const char*, CmpStr>& supportedFeatureURIs() const
const std::set<std::string_view>& supportedFeatureURIs() const
{
return m_supportedFeatureURIs;
}
@@ -136,17 +131,21 @@ public:
AutoLilvNodes findNodes(const LilvNode *subject,
const LilvNode *predicate, const LilvNode *object);
static const std::set<const char*, Lv2Manager::CmpStr>& getPluginBlacklist()
static const std::set<std::string_view>& getPluginBlacklist()
{
return pluginBlacklist;
}
static const std::set<std::string_view>& getPluginBlacklistBuffersizeLessThan32()
{
return pluginBlacklistBuffersizeLessThan32;
}
private:
// general data
bool m_debug; //!< if set, debug output will be printed
LilvWorld* m_world;
Lv2InfoMap m_lv2InfoMap;
std::set<const char*, CmpStr> m_supportedFeatureURIs;
std::set<std::string_view> m_supportedFeatureURIs;
// feature data that are common for all Lv2Proc
UridMap m_uridMap;
@@ -155,7 +154,8 @@ private:
Lv2UridCache m_uridCache;
// static
static const std::set<const char*, Lv2Manager::CmpStr> pluginBlacklist;
static const std::set<std::string_view>
pluginBlacklist, pluginBlacklistBuffersizeLessThan32;
// functions
bool isSubclassOf(const LilvPluginClass *clvss, const char *uriStr);

View File

@@ -1,7 +1,7 @@
/*
* Lv2Proc.h - 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
*
@@ -31,11 +31,14 @@
#include <lilv/lilv.h>
#include <memory>
#include <optional>
#include "LinkedModelGroups.h"
#include "LmmsSemaphore.h"
#include "Lv2Basics.h"
#include "Lv2Features.h"
#include "Lv2Options.h"
#include "LinkedModelGroups.h"
#include "Lv2Worker.h"
#include "Plugin.h"
#include "../src/3rdparty/ringbuffer/include/ringbuffer/ringbuffer.h"
#include "TimePos.h"
@@ -174,8 +177,14 @@ private:
const LilvPlugin* m_plugin;
LilvInstance* m_instance;
Lv2Features m_features;
// options
Lv2Options m_options;
// worker
std::optional<Lv2Worker> m_worker;
Semaphore m_workLock; // this must be shared by different workers
// full list of ports
std::vector<std::unique_ptr<Lv2Ports::PortBase>> m_ports;
// quick reference to specific, unique ports

View File

@@ -55,8 +55,6 @@ class UridMap
LV2_URID_Map m_mapFeature;
LV2_URID_Unmap m_unmapFeature;
LV2_URID m_lastUrid = 0;
public:
//! constructor; will set up the features
UridMap();

View File

@@ -56,7 +56,7 @@ class Lv2ViewProc : public LinkedModelGroupView
{
public:
//! @param colNum numbers of columns for the controls
Lv2ViewProc(QWidget *parent, Lv2Proc *ctrlBase, int colNum);
Lv2ViewProc(QWidget *parent, Lv2Proc *proc, int colNum);
~Lv2ViewProc() override = default;
private:

93
include/Lv2Worker.h Normal file
View File

@@ -0,0 +1,93 @@
/*
* Lv2Worker.h - Lv2Worker class
*
* 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.
*
*/
#ifndef LV2WORKER_H
#define LV2WORKER_H
#include "lmmsconfig.h"
#ifdef LMMS_HAVE_LV2
#include <lilv/lilv.h>
#include <lv2/lv2plug.in/ns/ext/worker/worker.h>
#include <thread>
#include <vector>
#include "LocklessRingBuffer.h"
#include "LmmsSemaphore.h"
namespace lmms
{
/**
Worker container
*/
class Lv2Worker
{
public:
// CTOR/DTOR/feature access
Lv2Worker(const LV2_Worker_Interface* iface, Semaphore* common_work_lock, bool threaded);
~Lv2Worker();
void setHandle(LV2_Handle handle) { m_handle = handle; }
LV2_Worker_Schedule* feature() { return &m_scheduleFeature; }
// public API
void emitResponses();
void notifyPluginThatRunFinished()
{
if(m_iface->end_run) { m_iface->end_run(m_scheduleFeature.handle); }
}
// to be called only by static functions
LV2_Worker_Status scheduleWork(uint32_t size, const void* data);
LV2_Worker_Status respond(uint32_t size, const void* data);
private:
// functions
void workerFunc();
std::size_t bufferSize() const; //!< size of internal buffers
// parameters
const LV2_Worker_Interface* m_iface;
bool m_threaded;
LV2_Handle m_handle;
LV2_Worker_Schedule m_scheduleFeature;
// threading/synchronization
std::thread m_thread;
std::vector<char> m_response; //!< buffer where single requests from m_requests are unpacked
LocklessRingBuffer<char> m_requests, m_responses; //!< ringbuffer to queue multiple requests
LocklessRingBufferReader<char> m_requestsReader, m_responsesReader;
std::atomic<bool> m_exit = false; //!< Whether the worker function should keep looping
Semaphore m_sem;
Semaphore* m_workLock;
};
} // namespace lmms
#endif // LMMS_HAVE_LV2
#endif // LV2WORKER_H

View File

@@ -212,7 +212,7 @@ private:
int32_t m_sysExDataLen; // len of m_sysExData
} m_data;
const char* m_sysExData;
[[maybe_unused]] const char* m_sysExData;
const void* m_sourcePort;
// Stores the source of the MidiEvent: Internal or External (hardware controllers).

View File

@@ -25,7 +25,7 @@
#ifndef LMMS_GUI_MIDI_SETUP_WIDGET_H
#define LMMS_GUI_MIDI_SETUP_WIDGET_H
#include "TabWidget.h"
#include <QGroupBox>
class QLineEdit;
@@ -33,7 +33,7 @@ namespace lmms::gui
{
class MidiSetupWidget : public TabWidget
class MidiSetupWidget : public QGroupBox
{
Q_OBJECT
MidiSetupWidget( const QString & caption, const QString & configSection,

View File

@@ -30,12 +30,12 @@
#include "AudioDevice.h"
#include "AudioDeviceSetupWidget.h"
#include "LedCheckBox.h"
#include "lmmsconfig.h"
#include "MidiClient.h"
#include "MidiSetupWidget.h"
class QCheckBox;
class QComboBox;
class QLabel;
class QLineEdit;
@@ -102,6 +102,7 @@ private slots:
// Audio settings widget.
void audioInterfaceChanged(const QString & driver);
void toggleHQAudioDev(bool enabled);
void updateBufferSizeWarning(int value);
void setBufferSize(int value);
void resetBufferSize();
@@ -155,14 +156,14 @@ private:
bool m_enableRunningAutoSave;
QSlider * m_saveIntervalSlider;
QLabel * m_saveIntervalLbl;
LedCheckBox * m_autoSave;
LedCheckBox * m_runningAutoSave;
QCheckBox * m_autoSave;
QCheckBox * m_runningAutoSave;
bool m_smoothScroll;
bool m_animateAFP;
QLabel * m_vstEmbedLbl;
QComboBox* m_vstEmbedComboBox;
QString m_vstEmbedMethod;
LedCheckBox * m_vstAlwaysOnTopCheckBox;
QCheckBox * m_vstAlwaysOnTopCheckBox;
bool m_vstAlwaysOnTop;
bool m_disableAutoQuit;
@@ -179,6 +180,7 @@ private:
int m_bufferSize;
QSlider * m_bufferSizeSlider;
QLabel * m_bufferSizeLbl;
QLabel * m_bufferSizeWarnLbl;
// MIDI settings widgets.
QComboBox * m_midiInterfaces;

View File

@@ -49,7 +49,12 @@ public:
TabButton * addTab( QWidget * _w, const QString & _text,
int _id, bool _add_stretch = false,
bool _text_is_tooltip = false );
bool _text_is_tooltip = false,
// TODO Remove fixWidgetToParentSize once it is used
// with false everywhere.
// At the time of writing it is only used in
// LadspaBrowser with default parameters.
bool fixWidgetToParentSize = true );
void removeTab( int _id );
inline void setExclusive( bool _on )

View File

@@ -325,6 +325,32 @@ static inline T absMin( T a, T b )
return std::abs(a) < std::abs(b) ? a : b;
}
// @brief Calculate number of digits which LcdSpinBox would show for a given number
// @note Once we upgrade to C++20, we could probably use std::formatted_size
static inline int numDigitsAsInt(float f)
{
// use rounding:
// LcdSpinBox sometimes uses roundf(), sometimes cast rounding
// we use rounding to be on the "safe side"
const float rounded = roundf(f);
int asInt = static_cast<int>(rounded);
int digits = 1; // always at least 1
if(asInt < 0)
{
++digits;
asInt = -asInt;
}
// "asInt" is positive from now
int32_t power = 1;
for(int32_t i = 1; i<10; ++i)
{
power *= 10;
if(static_cast<int32_t>(asInt) >= power) { ++digits; } // 2 digits for >=10, 3 for >=100
else { break; }
}
return digits;
}
} // namespace lmms

View File

@@ -177,7 +177,8 @@ void CompressorEffect::calcRange()
void CompressorEffect::resizeRMS()
{
m_rmsTimeConst = exp(-1.f / (m_compressorControls.m_rmsModel.value() * 0.001f * m_sampleRate));
const float rmsValue = m_compressorControls.m_rmsModel.value();
m_rmsTimeConst = (rmsValue > 0) ? exp(-1.f / (rmsValue * 0.001f * m_sampleRate)) : 0;
}
void CompressorEffect::calcLookaheadLength()

View File

@@ -497,10 +497,12 @@ void CompressorControlDialog::redrawKnee()
float actualRatio = m_controls->m_limiterModel.value() ? 0 : m_controls->m_effect->m_ratioVal;
// Calculate endpoints for the two straight lines
float kneePoint1 = m_controls->m_effect->m_thresholdVal - m_controls->m_effect->m_kneeVal;
float kneePoint2X = m_controls->m_effect->m_thresholdVal + m_controls->m_effect->m_kneeVal;
float kneePoint2Y = (m_controls->m_effect->m_thresholdVal + (-m_controls->m_effect->m_thresholdVal * (actualRatio * (m_controls->m_effect->m_kneeVal / -m_controls->m_effect->m_thresholdVal))));
float ratioPoint = m_controls->m_effect->m_thresholdVal + (-m_controls->m_effect->m_thresholdVal * actualRatio);
const float thresholdVal = m_controls->m_effect->m_thresholdVal;
const float kneeVal = m_controls->m_effect->m_kneeVal;
float kneePoint1 = thresholdVal - kneeVal;
float kneePoint2X = thresholdVal + kneeVal;
float kneePoint2Y = thresholdVal + kneeVal * actualRatio;
float ratioPoint = thresholdVal + (-thresholdVal * actualRatio);
// Draw two straight lines
m_p.drawLine(0, m_kneeWindowSizeY, dbfsToXPoint(kneePoint1), dbfsToYPoint(kneePoint1));
@@ -510,7 +512,7 @@ void CompressorControlDialog::redrawKnee()
}
// Draw knee section
if (m_controls->m_effect->m_kneeVal)
if (kneeVal)
{
m_p.setPen(QPen(m_kneeColor2, 3));
@@ -522,8 +524,8 @@ void CompressorControlDialog::redrawKnee()
{
newPoint[0] = linearInterpolate(kneePoint1, kneePoint2X, (i + 1) / (float)COMP_KNEE_LINES);
const float temp = newPoint[0] - m_controls->m_effect->m_thresholdVal + m_controls->m_effect->m_kneeVal;
newPoint[1] = (newPoint[0] + (actualRatio - 1) * temp * temp / (4 * m_controls->m_effect->m_kneeVal));
const float temp = newPoint[0] - thresholdVal + kneeVal;
newPoint[1] = (newPoint[0] + (actualRatio - 1) * temp * temp / (4 * kneeVal));
m_p.drawLine(dbfsToXPoint(prevPoint[0]), dbfsToYPoint(prevPoint[1]), dbfsToXPoint(newPoint[0]), dbfsToYPoint(newPoint[1]));
@@ -768,4 +770,4 @@ void CompressorControlDialog::resetCompressorView()
}
} // namespace lmms::gui
} // namespace lmms::gui

View File

@@ -4,22 +4,22 @@ BUILD_PLUGIN(ladspaeffect LadspaEffect.cpp LadspaControls.cpp LadspaControlDialo
SET(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/ladspa")
IF(WANT_CAPS)
IF(LMMS_HAVE_CAPS)
ADD_SUBDIRECTORY(caps)
ENDIF(WANT_CAPS)
ENDIF()
IF(WANT_TAP)
IF(LMMS_HAVE_TAP)
ADD_SUBDIRECTORY(tap)
ENDIF(WANT_TAP)
ENDIF()
IF(WANT_SWH)
IF(LMMS_HAVE_SWH)
ADD_SUBDIRECTORY(swh)
ENDIF(WANT_SWH)
ENDIF()
IF(WANT_CMT)
IF(LMMS_HAVE_CMT)
ADD_SUBDIRECTORY(cmt)
ENDIF(WANT_CMT)
ENDIF()
IF(WANT_CALF)
IF(LMMS_HAVE_CALF)
ADD_SUBDIRECTORY(calf)
ENDIF(WANT_CALF)
ENDIF()

View File

@@ -1,51 +1,14 @@
INCLUDE(BuildPlugin)
INCLUDE_DIRECTORIES(resid)
if(NOT LMMS_HAVE_SID)
return()
endif()
BUILD_PLUGIN(sid
SidInstrument.cpp
SidInstrument.h
resid/envelope.h
resid/extfilt.h
resid/filter.h
resid/pot.h
resid/siddefs.h
resid/sid.h
resid/spline.h
resid/voice.h
resid/wave.h
resid/envelope.cc
resid/extfilt.cc
resid/filter.cc
resid/pot.cc
resid/sid.cc
resid/version.cc
resid/voice.cc
resid/wave6581_PS_.cc
resid/wave6581_PST.cc
resid/wave6581_P_T.cc
resid/wave6581__ST.cc
resid/wave8580_PS_.cc
resid/wave8580_PST.cc
resid/wave8580_P_T.cc
resid/wave8580__ST.cc
resid/wave.cc
MOCFILES SidInstrument.h
EMBEDDED_RESOURCES *.png)
# Parse VERSION
FILE(READ "resid/CMakeLists.txt" lines)
STRING(REGEX MATCH "set\\(MAJOR_VER [A-Za-z0-9_]*\\)" MAJOR_RAW ${lines})
STRING(REGEX MATCH "set\\(MINOR_VER [A-Za-z0-9_]*\\)" MINOR_RAW ${lines})
STRING(REGEX MATCH "set\\(PATCH_VER [A-Za-z0-9_]*\\)" PATCH_RAW ${lines})
SEPARATE_ARGUMENTS(MAJOR_RAW)
SEPARATE_ARGUMENTS(MINOR_RAW)
SEPARATE_ARGUMENTS(PATCH_RAW)
LIST(GET MAJOR_RAW 1 MAJOR_RAW)
LIST(GET MINOR_RAW 1 MINOR_RAW)
LIST(GET PATCH_RAW 1 PATCH_RAW)
STRING(REPLACE ")" "" MAJOR_VER "${MAJOR_RAW}")
STRING(REPLACE ")" "" MINOR_VER "${MINOR_RAW}")
STRING(REPLACE ")" "" PATCH_VER "${PATCH_RAW}")
TARGET_COMPILE_DEFINITIONS(sid PRIVATE VERSION="${MAJOR_VER}.${MINOR_VER}.${PATCH_VER}")
add_subdirectory(resid)
target_link_libraries(sid resid)

View File

@@ -239,7 +239,7 @@ f_cnt_t SidInstrument::desiredReleaseFrames() const
static int sid_fillbuffer(unsigned char* sidreg, SID *sid, int tdelta, short *ptr, int samples)
static int sid_fillbuffer(unsigned char* sidreg, reSID::SID *sid, int tdelta, short *ptr, int samples)
{
int tdelta2;
int result;
@@ -302,9 +302,9 @@ void SidInstrument::playNote( NotePlayHandle * _n,
if (!_n->m_pluginData)
{
SID *sid = new SID();
sid->set_sampling_parameters( clockrate, SAMPLE_FAST, samplerate );
sid->set_chip_model( MOS8580 );
auto sid = new reSID::SID();
sid->set_sampling_parameters(clockrate, reSID::SAMPLE_FAST, samplerate);
sid->set_chip_model(reSID::MOS8580);
sid->enable_filter( true );
sid->reset();
_n->m_pluginData = sid;
@@ -312,7 +312,7 @@ void SidInstrument::playNote( NotePlayHandle * _n,
const fpp_t frames = _n->framesLeftForCurrentPeriod();
const f_cnt_t offset = _n->noteOffset();
SID *sid = static_cast<SID *>( _n->m_pluginData );
auto sid = static_cast<reSID::SID*>(_n->m_pluginData);
int delta_t = clockrate * frames / samplerate + 4;
// avoid variable length array for msvc compat
auto buf = reinterpret_cast<short*>(_working_buffer + offset);
@@ -325,20 +325,20 @@ void SidInstrument::playNote( NotePlayHandle * _n,
if( (ChipModel)m_chipModel.value() == ChipModel::MOS6581 )
{
sid->set_chip_model( MOS6581 );
sid->set_chip_model(reSID::MOS6581);
}
else
{
sid->set_chip_model( MOS8580 );
sid->set_chip_model(reSID::MOS8580);
}
// voices
reg8 data8 = 0;
reg8 data16 = 0;
reg8 base = 0;
reSID::reg8 data8 = 0;
reSID::reg16 data16 = 0;
size_t base = 0;
float freq = 0.0;
float note = 0.0;
for( reg8 i = 0 ; i < 3 ; ++i )
for (size_t i = 0; i < 3; ++i)
{
base = i*7;
// freq ( Fn = Fout / Fclk * 16777216 ) + coarse detuning
@@ -436,7 +436,7 @@ void SidInstrument::playNote( NotePlayHandle * _n,
void SidInstrument::deleteNotePluginData( NotePlayHandle * _n )
{
delete static_cast<SID *>( _n->m_pluginData );
delete static_cast<reSID::SID*>(_n->m_pluginData);
}

View File

@@ -0,0 +1,68 @@
# These are the defaults
set(RESID_INLINING 1)
set(RESID_INLINE inline)
set(RESID_BRANCH_HINTS 1)
set(NEW_8580_FILTER 0)
set(HAVE_BOOL 1)
set(HAVE_LOG1P 1)
if(CMAKE_CXX_COMPILER_ID MATCHES "GCC|Clang")
set(HAVE_BUILTIN_EXPECT 1)
else()
set(HAVE_BUILTIN_EXPECT 0)
endif()
configure_file(resid/siddefs.h.in resid/siddefs.h @ONLY)
add_library(resid_objects OBJECT
resid/sid.cc
resid/voice.cc
resid/wave.cc
resid/envelope.cc
resid/filter.cc
resid/dac.cc
resid/extfilt.cc
resid/pot.cc
resid/version.cc
)
target_include_directories(resid_objects PUBLIC
"${CMAKE_CURRENT_SOURCE_DIR}/resid"
"${CMAKE_CURRENT_BINARY_DIR}/resid"
)
target_compile_definitions(resid_objects PUBLIC VERSION="1.0")
set(RESID_WAVES
wave6581_PST
wave6581_PS_
wave6581_P_T
wave6581__ST
wave8580_PST
wave8580_PS_
wave8580_P_T
wave8580__ST
)
set(RESID_SAMP2SRC_SCRIPT ${CMAKE_CURRENT_SOURCE_DIR}/resid/samp2src.pl)
foreach(WAVE_DATA IN LISTS RESID_WAVES)
set(WAVE_DATA_IN ${CMAKE_CURRENT_SOURCE_DIR}/resid/${WAVE_DATA}.dat)
set(WAVE_SRC_OUT ${CMAKE_CURRENT_BINARY_DIR}/resid/${WAVE_DATA}.h)
set(WAVE_COMMAND
"${PERL_EXECUTABLE}"
"${RESID_SAMP2SRC_SCRIPT}"
"${WAVE_DATA}"
"${WAVE_DATA_IN}"
"${WAVE_SRC_OUT}"
)
add_custom_command(OUTPUT ${WAVE_SRC_OUT} COMMAND ${WAVE_COMMAND} VERBATIM)
target_sources(resid_objects PUBLIC ${WAVE_SRC_OUT})
endforeach()
# TODO CMake 3.12: Use target_link_libraries() to propagate usage requirements directly to sid plugin
add_library(resid INTERFACE)
target_sources(resid INTERFACE $<TARGET_OBJECTS:resid_objects>)
get_target_property(resid_includes resid_objects INCLUDE_DIRECTORIES)
target_include_directories(resid INTERFACE ${resid_includes})

View File

@@ -87,6 +87,7 @@ MalletsInstrument::MalletsInstrument( InstrumentTrack * _instrument_track ):
m_strikeModel( true, this, tr( "Bowed" ) ),
m_presetsModel(this),
m_spreadModel(0, 0, 255, 1, this, tr( "Spread" )),
m_randomModel(0.0f, 0.0f, 1.0f, 0.01f, this, tr("Randomness")),
m_versionModel( MALLETS_PRESET_VERSION, 0, MALLETS_PRESET_VERSION, this, "" ),
m_isOldVersionModel( false, this, "" ),
m_filesMissing( !QDir( ConfigManager::inst()->stkDir() ).exists() ||
@@ -155,6 +156,7 @@ void MalletsInstrument::saveSettings( QDomDocument & _doc, QDomElement & _this )
m_presetsModel.saveSettings( _doc, _this, "preset" );
m_spreadModel.saveSettings( _doc, _this, "spread" );
m_randomModel.saveSettings(_doc, _this, "randomness");
m_versionModel.saveSettings( _doc, _this, "version" );
m_isOldVersionModel.saveSettings( _doc, _this, "oldversion" );
}
@@ -189,6 +191,7 @@ void MalletsInstrument::loadSettings( const QDomElement & _this )
m_presetsModel.loadSettings( _this, "preset" );
m_spreadModel.loadSettings( _this, "spread" );
m_randomModel.loadSettings(_this, "randomness");
m_isOldVersionModel.loadSettings( _this, "oldversion" );
// To maintain backward compatibility
@@ -284,7 +287,7 @@ void MalletsInstrument::playNote( NotePlayHandle * _n,
}
int p = m_presetsModel.value();
const float freq = _n->frequency();
if (!_n->m_pluginData)
{
@@ -293,6 +296,29 @@ void MalletsInstrument::playNote( NotePlayHandle * _n,
m_isOldVersionModel.value() ? 100.0 : 200.0;
const float vel = _n->getVolume() / velocityAdjust;
const float random = m_randomModel.value();
float hardness = m_hardnessModel.value();
float position = m_positionModel.value();
float modulator = m_modulatorModel.value();
float crossfade = m_crossfadeModel.value();
if (p < 9)
{
hardness += random * static_cast<float>(fast_rand() % 128) - 64.0;
hardness = std::clamp(hardness, 0.0f, 128.0f);
position += random * static_cast<float>(fast_rand() % 64) - 32.0;
position = std::clamp(position, 0.0f, 64.0f);
}
else if (p == 9)
{
modulator += random * static_cast<float>(fast_rand() % 128) - 64.0;
modulator = std::clamp(modulator, 0.0f, 128.0f);
crossfade += random * static_cast<float>(fast_rand() % 128) - 64.0;
crossfade = std::clamp(crossfade, 0.0f, 128.0f);
}
// critical section as STK is not thread-safe
static QMutex m;
m.lock();
@@ -301,8 +327,8 @@ void MalletsInstrument::playNote( NotePlayHandle * _n,
_n->m_pluginData = new MalletsSynth( freq,
vel,
m_stickModel.value(),
m_hardnessModel.value(),
m_positionModel.value(),
hardness,
position,
m_vibratoGainModel.value(),
m_vibratoFreqModel.value(),
p,
@@ -315,8 +341,8 @@ void MalletsInstrument::playNote( NotePlayHandle * _n,
vel,
p,
m_lfoDepthModel.value(),
m_modulatorModel.value(),
m_crossfadeModel.value(),
modulator,
crossfade,
m_lfoSpeedModel.value(),
m_adsrModel.value(),
(uint8_t) m_spreadModel.value(),
@@ -412,6 +438,11 @@ MalletsInstrumentView::MalletsInstrumentView( MalletsInstrument * _instrument,
m_spreadKnob->move( 190, 140 );
m_spreadKnob->setHintText( tr( "Spread:" ), "" );
m_randomKnob = new Knob(KnobType::Vintage32, this);
m_randomKnob->setLabel(tr("Random"));
m_randomKnob->move(190, 190);
m_randomKnob->setHintText(tr("Random:"), "");
// try to inform user about missing Stk-installation
if( _instrument->m_filesMissing && getGUI() != nullptr )
{
@@ -467,7 +498,7 @@ QWidget * MalletsInstrumentView::setupModalBarControls( QWidget * _parent )
m_stickKnob->setLabel( tr( "Stick mix" ) );
m_stickKnob->move( 190, 90 );
m_stickKnob->setHintText( tr( "Stick mix:" ), "" );
return( widget );
}
@@ -565,6 +596,7 @@ void MalletsInstrumentView::modelChanged()
// m_strikeLED->setModel( &inst->m_strikeModel );
m_presetsCombo->setModel( &inst->m_presetsModel );
m_spreadKnob->setModel( &inst->m_spreadModel );
m_randomKnob->setModel(&inst->m_randomModel);
}

View File

@@ -197,6 +197,7 @@ private:
ComboBoxModel m_presetsModel;
FloatModel m_spreadModel;
FloatModel m_randomModel;
IntModel m_versionModel;
BoolModel m_isOldVersionModel;
@@ -255,6 +256,7 @@ private:
ComboBox * m_presetsCombo;
Knob * m_spreadKnob;
Knob * m_randomKnob;
};

View File

@@ -70,6 +70,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
@@ -112,6 +113,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

View File

@@ -302,7 +302,7 @@ void DataFile::write( QTextStream & _strm )
bool DataFile::writeFile(const QString& filename, bool withResources)
{
// Small lambda function for displaying errors
auto showError = [this](QString title, QString body){
auto showError = [](QString title, QString body){
if (gui::getGUI() != nullptr)
{
QMessageBox mb;

View File

@@ -273,7 +273,9 @@ int DrumSynth::GetDSFileSamples(QString dsfile, int16_t *&wave, int channels, sa
//generation
long Length, tpos=0, tplus, totmp, t, i, j;
float x[3] = {0.f, 0.f, 0.f};
float MasterTune, randmax, randmax2;
float MasterTune;
constexpr float randmax = 1.f / static_cast<float>(RAND_MAX);
constexpr float randmax2 = 2.f / static_cast<float>(RAND_MAX);
int MainFilter, HighPass;
long NON, NT, TON, DiON, TDroop=0, DStep;
@@ -454,7 +456,6 @@ int DrumSynth::GetDSFileSamples(QString dsfile, int16_t *&wave, int channels, sa
}
//prepare envelopes
randmax = 1.f / RAND_MAX; randmax2 = 2.f * randmax;
for (i=1;i<8;i++) { envData[i][NEXTT]=0; envData[i][PNT]=0; }
Length = LongestEnv();
@@ -745,4 +746,4 @@ int DrumSynth::GetDSFileSamples(QString dsfile, int16_t *&wave, int channels, sa
}
} // namespace lmms
} // namespace lmms

View File

@@ -409,7 +409,7 @@ void InstrumentFunctionArpeggio::processNote( NotePlayHandle * _n )
// Skip notes randomly
if( m_arpSkipModel.value() )
{
if( 100 * ( (float) rand() / (float)( RAND_MAX + 1.0f ) ) < m_arpSkipModel.value() )
if (100 * static_cast<float>(rand()) / (static_cast<float>(RAND_MAX) + 1.0f) < m_arpSkipModel.value())
{
// update counters
frames_processed += arp_frames;
@@ -425,7 +425,7 @@ void InstrumentFunctionArpeggio::processNote( NotePlayHandle * _n )
if( m_arpMissModel.value() )
{
if( 100 * ( (float) rand() / (float)( RAND_MAX + 1.0f ) ) < m_arpMissModel.value() )
if (100 * static_cast<float>(rand()) / (static_cast<float>(RAND_MAX) + 1.0f) < m_arpMissModel.value())
{
dir = ArpDirection::Random;
}

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

@@ -1389,7 +1389,7 @@ SampleBuffer * SampleBuffer::resample(const sample_rate_t srcSR, const sample_ra
}
else
{
printf("Error: src_new() failed in sample_buffer.cpp!\n");
printf("Error: src_new() failed in SampleBuffer.cpp!\n");
}
dstSB->update();
return dstSB;
@@ -1612,7 +1612,7 @@ SampleBuffer::handleState::handleState(bool varyingPitch, int interpolationMode)
if ((m_resamplingData = src_new(interpolationMode, DEFAULT_CHANNELS, &error)) == nullptr)
{
qDebug("Error: src_new() failed in sample_buffer.cpp!\n");
qDebug("Error: src_new() failed in SampleBuffer.cpp!\n");
}
}

View File

@@ -26,8 +26,8 @@
#ifdef LMMS_HAVE_JACK
#include <QFormLayout>
#include <QLineEdit>
#include <QLabel>
#include <QMessageBox>
#include "Engine.h"
@@ -454,17 +454,16 @@ void AudioJack::shutdownCallback( void * _udata )
AudioJack::setupWidget::setupWidget( QWidget * _parent ) :
AudioDeviceSetupWidget( AudioJack::name(), _parent )
{
QFormLayout * form = new QFormLayout(this);
QString cn = ConfigManager::inst()->value( "audiojack", "clientname" );
if( cn.isEmpty() )
{
cn = "lmms";
}
m_clientName = new QLineEdit( cn, this );
m_clientName->setGeometry( 10, 20, 160, 20 );
auto cn_lbl = new QLabel(tr("Client name"), this);
cn_lbl->setFont( pointSize<7>( cn_lbl->font() ) );
cn_lbl->setGeometry( 10, 40, 160, 10 );
form->addRow(tr("Client name"), m_clientName);
auto m = new gui::LcdSpinBoxModel(/* this */);
m->setRange( DEFAULT_CHANNELS, SURROUND_CHANNELS );
@@ -474,8 +473,8 @@ AudioJack::setupWidget::setupWidget( QWidget * _parent ) :
m_channels = new gui::LcdSpinBox( 1, this );
m_channels->setModel( m );
m_channels->setLabel( tr( "Channels" ) );
m_channels->move( 180, 20 );
form->addRow(tr("Channels"), m_channels);
}

View File

@@ -27,7 +27,7 @@
#ifdef LMMS_HAVE_OSS
#include <QFileInfo>
#include <QLabel>
#include <QFormLayout>
#include <QLineEdit>
#include "endian_handling.h"
@@ -320,12 +320,11 @@ void AudioOss::run()
AudioOss::setupWidget::setupWidget( QWidget * _parent ) :
AudioDeviceSetupWidget( AudioOss::name(), _parent )
{
m_device = new QLineEdit( probeDevice(), this );
m_device->setGeometry( 10, 20, 160, 20 );
QFormLayout * form = new QFormLayout(this);
auto dev_lbl = new QLabel(tr("Device"), this);
dev_lbl->setFont( pointSize<7>( dev_lbl->font() ) );
dev_lbl->setGeometry( 10, 40, 160, 10 );
m_device = new QLineEdit( probeDevice(), this );
form->addRow(tr("Device"), m_device);
auto m = new gui::LcdSpinBoxModel(/* this */);
m->setRange( DEFAULT_CHANNELS, SURROUND_CHANNELS );
@@ -335,9 +334,8 @@ AudioOss::setupWidget::setupWidget( QWidget * _parent ) :
m_channels = new gui::LcdSpinBox( 1, this );
m_channels->setModel( m );
m_channels->setLabel( tr( "Channels" ) );
m_channels->move( 180, 20 );
form->addRow(tr("Channels"), m_channels);
}

View File

@@ -49,7 +49,7 @@ void AudioPortAudioSetupUtil::updateChannels()
#ifdef LMMS_HAVE_PORTAUDIO
#include <QLabel>
#include <QFormLayout>
#include "Engine.h"
#include "ConfigManager.h"
@@ -419,19 +419,13 @@ AudioPortAudio::setupWidget::setupWidget( QWidget * _parent ) :
{
using gui::ComboBox;
m_backend = new ComboBox( this, "BACKEND" );
m_backend->setGeometry( 64, 15, 260, ComboBox::DEFAULT_HEIGHT );
QFormLayout * form = new QFormLayout(this);
auto backend_lbl = new QLabel(tr("Backend"), this);
backend_lbl->setFont( pointSize<7>( backend_lbl->font() ) );
backend_lbl->move( 8, 18 );
m_backend = new ComboBox( this, "BACKEND" );
form->addRow(tr("Backend"), m_backend);
m_device = new ComboBox( this, "DEVICE" );
m_device->setGeometry( 64, 35, 260, ComboBox::DEFAULT_HEIGHT );
auto dev_lbl = new QLabel(tr("Device"), this);
dev_lbl->setFont( pointSize<7>( dev_lbl->font() ) );
dev_lbl->move( 8, 38 );
form->addRow(tr("Device"), m_device);
/* LcdSpinBoxModel * m = new LcdSpinBoxModel( );
m->setRange( DEFAULT_CHANNELS, SURROUND_CHANNELS );

View File

@@ -22,8 +22,8 @@
*
*/
#include <QFormLayout>
#include <QLineEdit>
#include <QLabel>
#include "AudioPulseAudio.h"
@@ -312,24 +312,21 @@ void AudioPulseAudio::signalConnected( bool connected )
AudioPulseAudio::setupWidget::setupWidget( QWidget * _parent ) :
AudioDeviceSetupWidget( AudioPulseAudio::name(), _parent )
{
QFormLayout * form = new QFormLayout(this);
m_device = new QLineEdit( AudioPulseAudio::probeDevice(), this );
m_device->setGeometry( 10, 20, 160, 20 );
form->addRow(tr("Device"), m_device);
auto dev_lbl = new QLabel(tr("Device"), this);
dev_lbl->setFont( pointSize<7>( dev_lbl->font() ) );
dev_lbl->setGeometry( 10, 40, 160, 10 );
auto m = new gui::LcdSpinBoxModel(/* this */);
auto m = new gui::LcdSpinBoxModel();
m->setRange( DEFAULT_CHANNELS, SURROUND_CHANNELS );
m->setStep( 2 );
m->setValue( ConfigManager::inst()->value( "audiopa",
"channels" ).toInt() );
"channels" ).toInt() );
m_channels = new gui::LcdSpinBox( 1, this );
m_channels->setModel( m );
m_channels->setLabel( tr( "Channels" ) );
m_channels->move( 180, 20 );
form->addRow(tr("Channels"), m_channels);
}

View File

@@ -26,7 +26,7 @@
#ifdef LMMS_HAVE_SDL
#include <QLabel>
#include <QFormLayout>
#include <QLineEdit>
#include <SDL.h>
@@ -327,14 +327,12 @@ void AudioSdl::sdlInputAudioCallback(Uint8 *_buf, int _len) {
AudioSdl::setupWidget::setupWidget( QWidget * _parent ) :
AudioDeviceSetupWidget( AudioSdl::name(), _parent )
{
QFormLayout * form = new QFormLayout(this);
QString dev = ConfigManager::inst()->value( "audiosdl", "device" );
m_device = new QLineEdit( dev, this );
m_device->setGeometry( 10, 20, 160, 20 );
auto dev_lbl = new QLabel(tr("Device"), this);
dev_lbl->setFont( pointSize<7>( dev_lbl->font() ) );
dev_lbl->setGeometry( 10, 40, 160, 10 );
form->addRow(tr("Device"), m_device);
}

View File

@@ -28,7 +28,7 @@
#ifdef LMMS_HAVE_SNDIO
#include <cstdlib>
#include <QLabel>
#include <QFormLayout>
#include <QLineEdit>
#include "endian_handling.h"
@@ -183,12 +183,10 @@ void AudioSndio::run()
AudioSndio::setupWidget::setupWidget( QWidget * _parent ) :
AudioDeviceSetupWidget( AudioSndio::name(), _parent )
{
m_device = new QLineEdit( "", this );
m_device->setGeometry( 10, 20, 160, 20 );
QFormLayout * form = new QFormLayout(this);
QLabel * dev_lbl = new QLabel( tr( "Device" ), this );
dev_lbl->setFont( pointSize<6>( dev_lbl->font() ) );
dev_lbl->setGeometry( 10, 40, 160, 10 );
m_device = new QLineEdit( "", this );
form->addRow(tr("Device"), m_device);
gui::LcdSpinBoxModel * m = new gui::LcdSpinBoxModel( /* this */ );
m->setRange( DEFAULT_CHANNELS, SURROUND_CHANNELS );
@@ -198,9 +196,8 @@ AudioSndio::setupWidget::setupWidget( QWidget * _parent ) :
m_channels = new gui::LcdSpinBox( 1, this );
m_channels->setModel( m );
m_channels->setLabel( tr( "Channels" ) );
m_channels->move( 180, 20 );
form->addRow(tr("Channels"), m_channels);
}

View File

@@ -26,7 +26,7 @@
#ifdef LMMS_HAVE_SOUNDIO
#include <QLabel>
#include <QFormLayout>
#include <QLineEdit>
#include "Engine.h"
@@ -451,19 +451,13 @@ AudioSoundIo::setupWidget::setupWidget( QWidget * _parent ) :
{
m_setupUtil.m_setupWidget = this;
m_backend = new gui::ComboBox( this, "BACKEND" );
m_backend->setGeometry( 64, 15, 260, 20 );
QFormLayout * form = new QFormLayout(this);
QLabel * backend_lbl = new QLabel( tr( "Backend" ), this );
backend_lbl->setFont( pointSize<7>( backend_lbl->font() ) );
backend_lbl->move( 8, 18 );
m_backend = new gui::ComboBox( this, "BACKEND" );
form->addRow(tr("Backend"), m_backend);
m_device = new gui::ComboBox( this, "DEVICE" );
m_device->setGeometry( 64, 35, 260, 20 );
QLabel * dev_lbl = new QLabel( tr( "Device" ), this );
dev_lbl->setFont( pointSize<7>( dev_lbl->font() ) );
dev_lbl->move( 8, 38 );
form->addRow(tr("Device"), m_device);
// Setup models
m_soundio = soundio_create();

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

@@ -23,7 +23,7 @@
*/
#include <QComboBox>
#include <QLabel>
#include <QFormLayout>
#include "AudioAlsaSetupWidget.h"
@@ -40,6 +40,8 @@ AudioAlsaSetupWidget::AudioAlsaSetupWidget( QWidget * _parent ) :
AudioDeviceSetupWidget( AudioAlsa::name(), _parent ),
m_selectedDevice(-1)
{
QFormLayout * form = new QFormLayout(this);
m_deviceInfos = AudioAlsa::getAvailableDevices();
QString deviceText = ConfigManager::inst()->value( "audioalsa", "device" );
@@ -62,14 +64,11 @@ AudioAlsaSetupWidget::AudioAlsaSetupWidget( QWidget * _parent ) :
m_selectedDevice = m_deviceComboBox->currentIndex();
m_deviceComboBox->setGeometry( 10, 20, 160, 20 );
connect(m_deviceComboBox,
SIGNAL(currentIndexChanged(int)),
SLOT(onCurrentIndexChanged(int)));
auto dev_lbl = new QLabel(tr("DEVICE"), this);
dev_lbl->setFont( pointSize<7>( dev_lbl->font() ) );
dev_lbl->setGeometry( 10, 40, 160, 10 );
form->addRow(tr("Device"), m_deviceComboBox);
auto m = new LcdSpinBoxModel(/* this */);
m->setRange( DEFAULT_CHANNELS, SURROUND_CHANNELS );
@@ -79,9 +78,8 @@ AudioAlsaSetupWidget::AudioAlsaSetupWidget( QWidget * _parent ) :
m_channels = new LcdSpinBox( 1, this );
m_channels->setModel( m );
m_channels->setLabel( tr( "CHANNELS" ) );
m_channels->move( 180, 20 );
form->addRow(tr("Channels"), m_channels);
}

View File

@@ -28,7 +28,7 @@ namespace lmms::gui
{
AudioDeviceSetupWidget::AudioDeviceSetupWidget(const QString & caption, QWidget * parent) :
TabWidget(TabWidget::tr("Settings for %1").arg(tr(caption.toUtf8())), parent)
QGroupBox(QGroupBox::tr("Settings for %1").arg(tr(caption.toUtf8())), parent)
{
}
@@ -38,4 +38,4 @@ void AudioDeviceSetupWidget::show()
QWidget::show();
}
} // namespace lmms::gui
} // namespace lmms::gui

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

@@ -24,7 +24,7 @@
#include "MidiSetupWidget.h"
#include <QLabel>
#include <QFormLayout>
#include <QLineEdit>
#include "ConfigManager.h"
@@ -37,7 +37,7 @@ namespace lmms::gui
MidiSetupWidget::MidiSetupWidget(const QString & caption, const QString & configSection,
const QString & devName, QWidget * parent) :
TabWidget(TabWidget::tr("Settings for %1").arg(tr(caption.toUtf8())), parent),
QGroupBox(QGroupBox::tr("Settings for %1").arg(tr(caption.toUtf8())), parent),
m_configSection(configSection),
m_device(nullptr)
{
@@ -45,12 +45,11 @@ MidiSetupWidget::MidiSetupWidget(const QString & caption, const QString & config
// to indicate that there is no editable device field
if (!devName.isNull())
{
m_device = new QLineEdit(devName, this);
m_device->setGeometry(10, 20, 160, 20);
QFormLayout * form = new QFormLayout(this);
auto dev_lbl = new QLabel(tr("Device"), this);
dev_lbl->setFont(pointSize<7>(dev_lbl->font()));
dev_lbl->setGeometry(10, 40, 160, 10);
m_device = new QLineEdit(devName, this);
form->addRow(tr("Device"), m_device);
}
}

View File

@@ -464,7 +464,7 @@ bool MixerView::confirmRemoval(int index)
QString messageTitleRemoveTrack = tr("Confirm removal");
QString askAgainText = tr("Don't ask again");
auto askAgainCheckBox = new QCheckBox(askAgainText, nullptr);
connect(askAgainCheckBox, &QCheckBox::stateChanged, [this](int state) {
connect(askAgainCheckBox, &QCheckBox::stateChanged, [](int state) {
// Invert button state, if it's checked we *shouldn't* ask again
ConfigManager::inst()->setValue("ui", "mixerchanneldeletionwarning", state ? "0" : "1");
});

View File

@@ -23,14 +23,15 @@
*/
#include <QCheckBox>
#include <QComboBox>
#include <QGroupBox>
#include <QImageReader>
#include <QLabel>
#include <QLayout>
#include <QLineEdit>
#include <QScrollArea>
#include "AudioDeviceSetupWidget.h"
#include "AudioEngine.h"
#include "debug.h"
#include "embed.h"
@@ -79,13 +80,12 @@ inline void labelWidget(QWidget * w, const QString & txt)
auto title = new QLabel(txt, w);
QFont f = title->font();
f.setBold(true);
title->setFont(pointSize<12>(f));
title->setFont(f);
QBoxLayout * boxLayout = dynamic_cast<QBoxLayout *>(w->layout());
assert(boxLayout);
assert(dynamic_cast<QBoxLayout *>(w->layout()) != nullptr);
dynamic_cast<QBoxLayout *>(w->layout())->addSpacing(5);
dynamic_cast<QBoxLayout *>(w->layout())->addWidget(title);
boxLayout->addWidget(title);
}
@@ -162,15 +162,10 @@ SetupDialog::SetupDialog(ConfigTab tab_to_open) :
// TODO: Equivalent to the new setWindowFlag(Qt::WindowContextHelpButtonHint, false)
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
setModal(true);
setFixedSize(454, 400);
Engine::projectJournal()->setJournalling(false);
// Constants for positioning LED check boxes.
const int XDelta = 10;
const int YDelta = 18;
// Main widget.
auto main_w = new QWidget(this);
@@ -191,7 +186,8 @@ SetupDialog::SetupDialog(ConfigTab tab_to_open) :
// Settings widget.
auto settings_w = new QWidget(main_w);
settings_w->setFixedSize(360, 360);
QVBoxLayout * settingsLayout = new QVBoxLayout(settings_w);
// General widget.
auto general_w = new QWidget(settings_w);
@@ -211,77 +207,79 @@ SetupDialog::SetupDialog(ConfigTab tab_to_open) :
// Path selectors layout.
auto generalControlsLayout = new QVBoxLayout;
generalControlsLayout->setSpacing(10);
generalControlsLayout->setContentsMargins(0, 0, 0, 0);
auto addLedCheckBox = [&XDelta, &YDelta, this](const QString& ledText, TabWidget* tw, int& counter,
bool initialState, const char* toggledSlot, bool showRestartWarning) {
auto checkBox = new LedCheckBox(ledText, tw);
counter++;
checkBox->move(XDelta, YDelta * counter);
auto addCheckBox = [&](const QString& ledText, QWidget* parent, QBoxLayout * layout,
bool initialState, const char* toggledSlot, bool showRestartWarning) -> QCheckBox * {
auto checkBox = new QCheckBox(ledText, parent);
checkBox->setChecked(initialState);
connect(checkBox, SIGNAL(toggled(bool)), this, toggledSlot);
if (showRestartWarning)
{
connect(checkBox, SIGNAL(toggled(bool)), this, SLOT(showRestartWarning()));
}
if (layout)
{
layout->addWidget(checkBox);
}
return checkBox;
};
int counter = 0;
// GUI tab.
auto gui_tw = new TabWidget(tr("Graphical user interface (GUI)"), generalControls);
QGroupBox * guiGroupBox = new QGroupBox(tr("Graphical user interface (GUI)"), generalControls);
QVBoxLayout * guiGroupLayout = new QVBoxLayout(guiGroupBox);
addLedCheckBox(tr("Display volume as dBFS "), gui_tw, counter,
addCheckBox(tr("Display volume as dBFS "), guiGroupBox, guiGroupLayout,
m_displaydBFS, SLOT(toggleDisplaydBFS(bool)), true);
addLedCheckBox(tr("Enable tooltips"), gui_tw, counter,
addCheckBox(tr("Enable tooltips"), guiGroupBox, guiGroupLayout,
m_tooltips, SLOT(toggleTooltips(bool)), true);
addLedCheckBox(tr("Enable master oscilloscope by default"), gui_tw, counter,
addCheckBox(tr("Enable master oscilloscope by default"), guiGroupBox, guiGroupLayout,
m_displayWaveform, SLOT(toggleDisplayWaveform(bool)), true);
addLedCheckBox(tr("Enable all note labels in piano roll"), gui_tw, counter,
addCheckBox(tr("Enable all note labels in piano roll"), guiGroupBox, guiGroupLayout,
m_printNoteLabels, SLOT(toggleNoteLabels(bool)), false);
addLedCheckBox(tr("Enable compact track buttons"), gui_tw, counter,
addCheckBox(tr("Enable compact track buttons"), guiGroupBox, guiGroupLayout,
m_compactTrackButtons, SLOT(toggleCompactTrackButtons(bool)), true);
addLedCheckBox(tr("Enable one instrument-track-window mode"), gui_tw, counter,
addCheckBox(tr("Enable one instrument-track-window mode"), guiGroupBox, guiGroupLayout,
m_oneInstrumentTrackWindow, SLOT(toggleOneInstrumentTrackWindow(bool)), true);
addLedCheckBox(tr("Show sidebar on the right-hand side"), gui_tw, counter,
addCheckBox(tr("Show sidebar on the right-hand side"), guiGroupBox, guiGroupLayout,
m_sideBarOnRight, SLOT(toggleSideBarOnRight(bool)), true);
addLedCheckBox(tr("Let sample previews continue when mouse is released"), gui_tw, counter,
addCheckBox(tr("Let sample previews continue when mouse is released"), guiGroupBox, guiGroupLayout,
m_letPreviewsFinish, SLOT(toggleLetPreviewsFinish(bool)), false);
addLedCheckBox(tr("Mute automation tracks during solo"), gui_tw, counter,
addCheckBox(tr("Mute automation tracks during solo"), guiGroupBox, guiGroupLayout,
m_soloLegacyBehavior, SLOT(toggleSoloLegacyBehavior(bool)), false);
addLedCheckBox(tr("Show warning when deleting tracks"), gui_tw, counter,
addCheckBox(tr("Show warning when deleting tracks"), guiGroupBox, guiGroupLayout,
m_trackDeletionWarning, SLOT(toggleTrackDeletionWarning(bool)), false);
addLedCheckBox(tr("Show warning when deleting a mixer channel that is in use"), gui_tw, counter,
addCheckBox(tr("Show warning when deleting a mixer channel that is in use"), guiGroupBox, guiGroupLayout,
m_mixerChannelDeletionWarning, SLOT(toggleMixerChannelDeletionWarning(bool)), false);
gui_tw->setFixedHeight(YDelta + YDelta * counter);
generalControlsLayout->addWidget(guiGroupBox);
generalControlsLayout->addWidget(gui_tw);
generalControlsLayout->addSpacing(10);
counter = 0;
// Projects tab.
auto projects_tw = new TabWidget(tr("Projects"), generalControls);
QGroupBox * projectsGroupBox = new QGroupBox(tr("Projects"), generalControls);
QVBoxLayout * projectsGroupLayout = new QVBoxLayout(projectsGroupBox);
addLedCheckBox(tr("Compress project files by default"), projects_tw, counter,
addCheckBox(tr("Compress project files by default"), projectsGroupBox, projectsGroupLayout,
m_MMPZ, SLOT(toggleMMPZ(bool)), true);
addLedCheckBox(tr("Create a backup file when saving a project"), projects_tw, counter,
addCheckBox(tr("Create a backup file when saving a project"), projectsGroupBox, projectsGroupLayout,
m_disableBackup, SLOT(toggleDisableBackup(bool)), false);
addLedCheckBox(tr("Reopen last project on startup"), projects_tw, counter,
addCheckBox(tr("Reopen last project on startup"), projectsGroupBox, projectsGroupLayout,
m_openLastProject, SLOT(toggleOpenLastProject(bool)), false);
projects_tw->setFixedHeight(YDelta + YDelta * counter);
generalControlsLayout->addWidget(projectsGroupBox);
generalControlsLayout->addWidget(projects_tw);
generalControlsLayout->addSpacing(10);
// Language tab.
auto lang_tw = new TabWidget(tr("Language"), generalControls);
lang_tw->setFixedHeight(48);
auto changeLang = new QComboBox(lang_tw);
changeLang->move(XDelta, 20);
QGroupBox * languageGroupBox = new QGroupBox(tr("Language"), generalControls);
QVBoxLayout * languageGroupLayout = new QVBoxLayout(languageGroupBox);
auto changeLang = new QComboBox(languageGroupBox);
languageGroupLayout->addWidget(changeLang);
QDir dir(ConfigManager::inst()->localeDir());
QStringList fileNames = dir.entryList(QStringList("*.qm"));
@@ -333,7 +331,7 @@ SetupDialog::SetupDialog(ConfigTab tab_to_open) :
connect(changeLang, SIGNAL(currentIndexChanged(int)),
this, SLOT(showRestartWarning()));
generalControlsLayout->addWidget(lang_tw);
generalControlsLayout->addWidget(languageGroupBox);
generalControlsLayout->addSpacing(10);
// General layout ordering.
@@ -341,9 +339,7 @@ SetupDialog::SetupDialog(ConfigTab tab_to_open) :
generalControls->setLayout(generalControlsLayout);
generalScroll->setWidget(generalControls);
generalScroll->setWidgetResizable(true);
general_layout->addWidget(generalScroll);
general_layout->addStretch();
general_layout->addWidget(generalScroll, 1);
@@ -357,71 +353,63 @@ SetupDialog::SetupDialog(ConfigTab tab_to_open) :
// Autosave tab.
auto auto_save_tw = new TabWidget(tr("Autosave"), performance_w);
auto_save_tw->setFixedHeight(106);
QGroupBox * autoSaveBox = new QGroupBox(tr("Autosave"), performance_w);
QVBoxLayout * autoSaveLayout = new QVBoxLayout(autoSaveBox);
QHBoxLayout * autoSaveSubLayout = new QHBoxLayout();
m_saveIntervalSlider = new QSlider(Qt::Horizontal, auto_save_tw);
m_saveIntervalSlider = new QSlider(Qt::Horizontal, autoSaveBox);
m_saveIntervalSlider->setValue(m_saveInterval);
m_saveIntervalSlider->setRange(1, 20);
m_saveIntervalSlider->setTickInterval(1);
m_saveIntervalSlider->setPageStep(1);
m_saveIntervalSlider->setGeometry(10, 18, 340, 18);
m_saveIntervalSlider->setTickPosition(QSlider::TicksBelow);
connect(m_saveIntervalSlider, SIGNAL(valueChanged(int)),
this, SLOT(setAutoSaveInterval(int)));
m_saveIntervalLbl = new QLabel(auto_save_tw);
m_saveIntervalLbl->setGeometry(10, 40, 200, 24);
setAutoSaveInterval(m_saveIntervalSlider->value());
m_autoSave = new LedCheckBox(
tr("Enable autosave"), auto_save_tw);
m_autoSave->move(10, 70);
m_autoSave->setChecked(m_enableAutoSave);
connect(m_autoSave, SIGNAL(toggled(bool)),
this, SLOT(toggleAutoSave(bool)));
m_runningAutoSave = new LedCheckBox(
tr("Allow autosave while playing"), auto_save_tw);
m_runningAutoSave->move(20, 88);
m_runningAutoSave->setChecked(m_enableRunningAutoSave);
connect(m_runningAutoSave, SIGNAL(toggled(bool)),
this, SLOT(toggleRunningAutoSave(bool)));
auto autoSaveResetBtn = new QPushButton(embed::getIconPixmap("reload"), "", auto_save_tw);
autoSaveResetBtn->setGeometry(320, 70, 28, 28);
auto autoSaveResetBtn = new QPushButton(embed::getIconPixmap("reload"), "", autoSaveBox);
autoSaveResetBtn->setFixedSize(32, 32);
connect(autoSaveResetBtn, SIGNAL(clicked()),
this, SLOT(resetAutoSave()));
this, SLOT(resetAutoSave()));
autoSaveSubLayout->addWidget(m_saveIntervalSlider);
autoSaveSubLayout->addWidget(autoSaveResetBtn);
autoSaveLayout->addLayout(autoSaveSubLayout);
m_saveIntervalLbl = new QLabel(autoSaveBox);
setAutoSaveInterval(m_saveIntervalSlider->value());
autoSaveLayout->addWidget(m_saveIntervalLbl);
m_autoSave = addCheckBox(tr("Enable autosave"), autoSaveBox, autoSaveLayout,
m_enableAutoSave, SLOT(toggleAutoSave(bool)), false);
m_runningAutoSave = addCheckBox(tr("Allow autosave while playing"), autoSaveBox, autoSaveLayout,
m_enableRunningAutoSave, SLOT(toggleRunningAutoSave(bool)), false);
m_saveIntervalSlider->setEnabled(m_enableAutoSave);
m_runningAutoSave->setVisible(m_enableAutoSave);
counter = 0;
// UI effect vs. performance tab.
auto ui_fx_tw = new TabWidget(tr("User interface (UI) effects vs. performance"), performance_w);
QGroupBox * uiFxBox = new QGroupBox(tr("User interface (UI) effects vs. performance"), performance_w);
QVBoxLayout * uiFxLayout = new QVBoxLayout(uiFxBox);
addLedCheckBox(tr("Smooth scroll in song editor"), ui_fx_tw, counter,
addCheckBox(tr("Smooth scroll in song editor"), uiFxBox, uiFxLayout,
m_smoothScroll, SLOT(toggleSmoothScroll(bool)), false);
addLedCheckBox(tr("Display playback cursor in AudioFileProcessor"), ui_fx_tw, counter,
addCheckBox(tr("Display playback cursor in AudioFileProcessor"), uiFxBox, uiFxLayout,
m_animateAFP, SLOT(toggleAnimateAFP(bool)), false);
ui_fx_tw->setFixedHeight(YDelta + YDelta * counter);
// Plugins group
QGroupBox * pluginsBox = new QGroupBox(tr("Plugins"), performance_w);
QVBoxLayout * pluginsLayout = new QVBoxLayout(pluginsBox);
counter = 0;
// Plugins tab.
auto plugins_tw = new TabWidget(tr("Plugins"), performance_w);
m_vstEmbedLbl = new QLabel(plugins_tw);
m_vstEmbedLbl->move(XDelta, YDelta * ++counter);
m_vstEmbedLbl = new QLabel(pluginsBox);
m_vstEmbedLbl->setText(tr("VST plugins embedding:"));
pluginsLayout->addWidget(m_vstEmbedLbl);
m_vstEmbedComboBox = new QComboBox(plugins_tw);
m_vstEmbedComboBox->move(XDelta, YDelta * ++counter);
m_vstEmbedComboBox = new QComboBox(pluginsBox);
QStringList embedMethods = ConfigManager::availableVstEmbedMethods();
m_vstEmbedComboBox->addItem(tr("No embedding"), "none");
@@ -440,27 +428,19 @@ SetupDialog::SetupDialog(ConfigTab tab_to_open) :
m_vstEmbedComboBox->setCurrentIndex(m_vstEmbedComboBox->findData(m_vstEmbedMethod));
connect(m_vstEmbedComboBox, SIGNAL(currentIndexChanged(int)),
this, SLOT(vstEmbedMethodChanged()));
pluginsLayout->addWidget(m_vstEmbedComboBox);
counter += 2;
m_vstAlwaysOnTopCheckBox = addCheckBox(tr("Keep plugin windows on top when not embedded"), pluginsBox, pluginsLayout,
m_vstAlwaysOnTop, SLOT(toggleVSTAlwaysOnTop(bool)), false);
m_vstAlwaysOnTopCheckBox = new LedCheckBox(
tr("Keep plugin windows on top when not embedded"), plugins_tw);
m_vstAlwaysOnTopCheckBox->move(20, 66);
m_vstAlwaysOnTopCheckBox->setChecked(m_vstAlwaysOnTop);
m_vstAlwaysOnTopCheckBox->setVisible(m_vstEmbedMethod == "none");
connect(m_vstAlwaysOnTopCheckBox, SIGNAL(toggled(bool)),
this, SLOT(toggleVSTAlwaysOnTop(bool)));
addLedCheckBox(tr("Keep effects running even without input"), plugins_tw, counter,
addCheckBox(tr("Keep effects running even without input"), pluginsBox, pluginsLayout,
m_disableAutoQuit, SLOT(toggleDisableAutoQuit(bool)), false);
plugins_tw->setFixedHeight(YDelta + YDelta * counter);
// Performance layout ordering.
performance_layout->addWidget(auto_save_tw);
performance_layout->addWidget(ui_fx_tw);
performance_layout->addWidget(plugins_tw);
performance_layout->addWidget(autoSaveBox);
performance_layout->addWidget(uiFxBox);
performance_layout->addWidget(pluginsBox);
performance_layout->addStretch();
@@ -473,17 +453,15 @@ SetupDialog::SetupDialog(ConfigTab tab_to_open) :
labelWidget(audio_w,
tr("Audio"));
// Audio interface tab.
auto audioiface_tw = new TabWidget(tr("Audio interface"), audio_w);
audioiface_tw->setFixedHeight(56);
m_audioInterfaces = new QComboBox(audioiface_tw);
m_audioInterfaces->setGeometry(10, 20, 240, 28);
// Audio interface group
QGroupBox * audioInterfaceBox = new QGroupBox(tr("Audio interface"), audio_w);
QVBoxLayout * audioInterfaceLayout = new QVBoxLayout(audioInterfaceBox);
m_audioInterfaces = new QComboBox(audioInterfaceBox);
audioInterfaceLayout->addWidget(m_audioInterfaces);
// Ifaces-settings-widget.
auto as_w = new QWidget(audio_w);
as_w->setFixedHeight(60);
auto as_w_layout = new QHBoxLayout(as_w);
as_w_layout->setSpacing(0);
@@ -563,54 +541,58 @@ SetupDialog::SetupDialog(ConfigTab tab_to_open) :
this, SLOT(audioInterfaceChanged(const QString&)));
// Advanced setting, hidden for now
if(false)
{
auto useNaNHandler = new LedCheckBox(tr("Use built-in NaN handler"), audio_w);
useNaNHandler->setChecked(m_NaNHandler);
}
// // TODO Handle or remove.
// auto useNaNHandler = new LedCheckBox(tr("Use built-in NaN handler"), audio_w);
// audio_layout->addWidget(useNaNHandler);
// useNaNHandler->setChecked(m_NaNHandler);
// HQ mode LED.
auto hqaudio = new LedCheckBox(tr("HQ mode for output audio device"), audio_w);
hqaudio->move(10, 0);
hqaudio->setChecked(m_hqAudioDev);
connect(hqaudio, SIGNAL(toggled(bool)),
this, SLOT(toggleHQAudioDev(bool)));
// HQ mode checkbox
auto hqaudio = addCheckBox(tr("HQ mode for output audio device"), audioInterfaceBox, nullptr,
m_hqAudioDev, SLOT(toggleHQAudioDev(bool)), false);
// Buffer size group
QGroupBox * bufferSizeBox = new QGroupBox(tr("Buffer size"), audio_w);
QVBoxLayout * bufferSizeLayout = new QVBoxLayout(bufferSizeBox);
QHBoxLayout * bufferSizeSubLayout = new QHBoxLayout();
// Buffer size tab.
auto bufferSize_tw = new TabWidget(tr("Buffer size"), audio_w);
bufferSize_tw->setFixedHeight(76);
m_bufferSizeSlider = new QSlider(Qt::Horizontal, bufferSize_tw);
m_bufferSizeSlider = new QSlider(Qt::Horizontal, bufferSizeBox);
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);
connect(m_bufferSizeSlider, SIGNAL(valueChanged(int)),
this, SLOT(setBufferSize(int)));
connect(m_bufferSizeSlider, SIGNAL(valueChanged(int)),
this, SLOT(showRestartWarning()));
bufferSizeSubLayout->addWidget(m_bufferSizeSlider, 1);
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);
auto bufferSize_reset_btn = new QPushButton(embed::getIconPixmap("reload"), "", bufferSizeBox);
bufferSize_reset_btn->setFixedSize(32, 32);
connect(bufferSize_reset_btn, SIGNAL(clicked()),
this, SLOT(resetBufferSize()));
this, SLOT(resetBufferSize()));
bufferSize_reset_btn->setToolTip(
tr("Reset to default value"));
tr("Reset to default value"));
bufferSizeSubLayout->addWidget(bufferSize_reset_btn);
bufferSizeLayout->addLayout(bufferSizeSubLayout);
m_bufferSizeLbl = new QLabel(bufferSizeBox);
bufferSizeLayout->addWidget(m_bufferSizeLbl);
m_bufferSizeWarnLbl = new QLabel(bufferSizeBox);
m_bufferSizeWarnLbl->setWordWrap(true);
bufferSizeLayout->addWidget(m_bufferSizeWarnLbl);
setBufferSize(m_bufferSizeSlider->value());
// Audio layout ordering.
audio_layout->addWidget(audioiface_tw);
audio_layout->addWidget(audioInterfaceBox);
audio_layout->addWidget(as_w);
audio_layout->addWidget(hqaudio);
audio_layout->addWidget(bufferSize_tw);
audio_layout->addWidget(bufferSizeBox);
audio_layout->addStretch();
@@ -620,19 +602,17 @@ SetupDialog::SetupDialog(ConfigTab tab_to_open) :
auto midi_layout = new QVBoxLayout(midi_w);
midi_layout->setSpacing(10);
midi_layout->setContentsMargins(0, 0, 0, 0);
labelWidget(midi_w,
tr("MIDI"));
labelWidget(midi_w, tr("MIDI"));
// MIDI interface tab.
auto midiiface_tw = new TabWidget(tr("MIDI interface"), midi_w);
midiiface_tw->setFixedHeight(56);
// MIDI interface group
QGroupBox * midiInterfaceBox = new QGroupBox(tr("MIDI interface"), midi_w);
QVBoxLayout * midiInterfaceLayout = new QVBoxLayout(midiInterfaceBox);
m_midiInterfaces = new QComboBox(midiiface_tw);
m_midiInterfaces->setGeometry(10, 20, 240, 28);
m_midiInterfaces = new QComboBox(midiInterfaceBox);
midiInterfaceLayout->addWidget(m_midiInterfaces);
// Ifaces-settings-widget.
auto ms_w = new QWidget(midi_w);
ms_w->setFixedHeight(60);
auto ms_w_layout = new QHBoxLayout(ms_w);
ms_w_layout->setSpacing(0);
@@ -702,12 +682,12 @@ SetupDialog::SetupDialog(ConfigTab tab_to_open) :
this, SLOT(midiInterfaceChanged(const QString&)));
// MIDI autoassign tab.
auto midiAutoAssign_tw = new TabWidget(tr("Automatically assign MIDI controller to selected track"), midi_w);
midiAutoAssign_tw->setFixedHeight(56);
// MIDI autoassign group
QGroupBox * midiAutoAssignBox = new QGroupBox(tr("Automatically assign MIDI controller to selected track"), midi_w);
QVBoxLayout * midiAutoAssignLayout = new QVBoxLayout(midiAutoAssignBox);
m_assignableMidiDevices = new QComboBox(midiAutoAssign_tw);
m_assignableMidiDevices->setGeometry(10, 20, 240, 28);
m_assignableMidiDevices = new QComboBox(midiAutoAssignBox);
midiAutoAssignLayout->addWidget(m_assignableMidiDevices);
m_assignableMidiDevices->addItem("none");
if ( !Engine::audioEngine()->midiClient()->isRaw() )
{
@@ -724,9 +704,9 @@ SetupDialog::SetupDialog(ConfigTab tab_to_open) :
}
// MIDI layout ordering.
midi_layout->addWidget(midiiface_tw);
midi_layout->addWidget(midiInterfaceBox);
midi_layout->addWidget(ms_w);
midi_layout->addWidget(midiAutoAssign_tw);
midi_layout->addWidget(midiAutoAssignBox);
midi_layout->addStretch();
@@ -749,29 +729,29 @@ SetupDialog::SetupDialog(ConfigTab tab_to_open) :
// Path selectors widget.
auto pathSelectors = new QWidget(paths_w);
const int txtLength = 284;
const int btnStart = 300;
// Path selectors layout.
auto pathSelectorsLayout = new QVBoxLayout;
pathSelectorsLayout->setSpacing(10);
pathSelectorsLayout->setContentsMargins(0, 0, 0, 0);
auto addPathEntry = [&](const QString& caption, const QString& content, const char* setSlot, const char* openSlot,
QLineEdit*& lineEdit, const char* pixmap = "project_open") {
auto newTw = new TabWidget(caption, pathSelectors);
newTw->setFixedHeight(48);
auto pathEntryGroupBox = new QGroupBox(caption, pathSelectors);
QHBoxLayout * pathEntryLayout = new QHBoxLayout(pathEntryGroupBox);
lineEdit = new QLineEdit(content, newTw);
lineEdit->setGeometry(10, 20, txtLength, 16);
lineEdit = new QLineEdit(content, pathEntryGroupBox);
connect(lineEdit, SIGNAL(textChanged(const QString&)),
this, setSlot);
auto selectBtn = new QPushButton(embed::getIconPixmap(pixmap, 16, 16), "", newTw);
pathEntryLayout->addWidget(lineEdit, 1);
auto selectBtn = new QPushButton(embed::getIconPixmap(pixmap, 16, 16), "", pathEntryGroupBox);
selectBtn->setFixedSize(24, 24);
selectBtn->move(btnStart, 16);
connect(selectBtn, SIGNAL(clicked()), this, openSlot);
pathSelectorsLayout->addWidget(newTw);
pathEntryLayout->addWidget(selectBtn, 0);
pathSelectorsLayout->addWidget(pathEntryGroupBox);
pathSelectorsLayout->addSpacing(10);
};
@@ -817,24 +797,32 @@ SetupDialog::SetupDialog(ConfigTab tab_to_open) :
pathsScroll->setWidget(pathSelectors);
pathsScroll->setWidgetResizable(true);
paths_layout->addWidget(pathsScroll);
paths_layout->addWidget(pathsScroll, 1);
paths_layout->addStretch();
// Add all main widgets to the layout of the settings widget
// This is needed so that we automatically get the correct sizes.
settingsLayout->addWidget(general_w);
settingsLayout->addWidget(performance_w);
settingsLayout->addWidget(audio_w);
settingsLayout->addWidget(midi_w);
settingsLayout->addWidget(paths_w);
// Major tabs ordering.
m_tabBar->addTab(general_w,
tr("General"), 0, false, true)->setIcon(
tr("General"), 0, false, true, false)->setIcon(
embed::getIconPixmap("setup_general"));
m_tabBar->addTab(performance_w,
tr("Performance"), 1, false, true)->setIcon(
tr("Performance"), 1, false, true, false)->setIcon(
embed::getIconPixmap("setup_performance"));
m_tabBar->addTab(audio_w,
tr("Audio"), 2, false, true)->setIcon(
tr("Audio"), 2, false, true, false)->setIcon(
embed::getIconPixmap("setup_audio"));
m_tabBar->addTab(midi_w,
tr("MIDI"), 3, false, true)->setIcon(
tr("MIDI"), 3, false, true, false)->setIcon(
embed::getIconPixmap("setup_midi"));
m_tabBar->addTab(paths_w,
tr("Paths"), 4, true, true)->setIcon(
tr("Paths"), 4, true, true, false)->setIcon(
embed::getIconPixmap("setup_directories"));
m_tabBar->setActiveTab(static_cast<int>(tab_to_open));
@@ -877,11 +865,14 @@ SetupDialog::SetupDialog(ConfigTab tab_to_open) :
extras_layout->addSpacing(10);
// Vertical layout ordering.
vlayout->addWidget(main_w);
vlayout->addWidget(main_w, 1);
vlayout->addSpacing(10);
vlayout->addWidget(extras_w);
vlayout->addSpacing(10);
// Ensure that we cannot make the dialog smaller than it wants to be
setMinimumWidth(width());
show();
}
@@ -1172,6 +1163,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 +1206,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);
}

View File

@@ -195,7 +195,7 @@ bool TrackOperationsWidget::confirmRemoval()
QString messageTitleRemoveTrack = tr("Confirm removal");
QString askAgainText = tr("Don't ask again");
auto askAgainCheckBox = new QCheckBox(askAgainText, nullptr);
connect(askAgainCheckBox, &QCheckBox::stateChanged, [this](int state){
connect(askAgainCheckBox, &QCheckBox::stateChanged, [](int state){
// Invert button state, if it's checked we *shouldn't* ask again
ConfigManager::inst()->setValue("ui", "trackdeletionwarning", state ? "0" : "1");
});

View File

@@ -44,7 +44,7 @@ TabBar::TabBar( QWidget * _parent, QBoxLayout::Direction _dir ) :
}
TabButton * TabBar::addTab( QWidget * _w, const QString & _text, int _id,
bool _add_stretch, bool _text_is_tooltip )
bool _add_stretch, bool _text_is_tooltip, bool fixWidgetToParentSize )
{
// already tab with id?
if( m_tabs.contains( _id ) )
@@ -83,10 +83,12 @@ TabButton * TabBar::addTab( QWidget * _w, const QString & _text, int _id,
m_layout->addStretch();
}
// we assume, parent-widget is a widget acting as widget-stack so all
// widgets have the same size and only the one on the top is visible
_w->setFixedSize( _w->parentWidget()->size() );
if (fixWidgetToParentSize)
{
// we assume, parent-widget is a widget acting as widget-stack so all
// widgets have the same size and only the one on the top is visible
_w->setFixedSize( _w->parentWidget()->size() );
}
b->setFont( pointSize<8>( b->font() ) );

View File

@@ -21,6 +21,7 @@ ADD_EXECUTABLE(tests
src/core/ArrayVectorTest.cpp
src/core/AutomatableModelTest.cpp
src/core/MathTest.cpp
src/core/ProjectVersionTest.cpp
src/core/RelativePathsTest.cpp

View File

@@ -0,0 +1,53 @@
/*
* MathTest.cpp
*
* Copyright (c) 2023 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 "QTestSuite.h"
#include "lmms_math.h"
#include <QDir>
class MathTest : QTestSuite
{
Q_OBJECT
private slots:
void NumDigitsTest()
{
using namespace lmms;
QCOMPARE(numDigitsAsInt(1.f), 1);
QCOMPARE(numDigitsAsInt(9.9f), 2);
QCOMPARE(numDigitsAsInt(10.f), 2);
QCOMPARE(numDigitsAsInt(0.f), 1);
QCOMPARE(numDigitsAsInt(-100.f), 4);
QCOMPARE(numDigitsAsInt(-99.f), 3);
QCOMPARE(numDigitsAsInt(-0.4f), 1); // there is no "-0" for LED spinbox
QCOMPARE(numDigitsAsInt(-0.99f), 2);
QCOMPARE(numDigitsAsInt(1000000000), 10);
QCOMPARE(numDigitsAsInt(-1000000000), 11);
QCOMPARE(numDigitsAsInt(900000000), 9);
QCOMPARE(numDigitsAsInt(-900000000), 10);
}
} MathTests;
#include "MathTest.moc"