diff --git a/.gitmodules b/.gitmodules index ee6e7eac9..fa6980ac5 100644 --- a/.gitmodules +++ b/.gitmodules @@ -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 diff --git a/CMakeLists.txt b/CMakeLists.txt index 7c3770f31..0b6d3b4ff 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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" diff --git a/data/themes/default/style.css b/data/themes/default/style.css index 6c7fe8470..8ead9062d 100644 --- a/data/themes/default/style.css +++ b/data/themes/default/style.css @@ -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 { diff --git a/include/AudioDeviceSetupWidget.h b/include/AudioDeviceSetupWidget.h index f56fa07a6..acc99602d 100644 --- a/include/AudioDeviceSetupWidget.h +++ b/include/AudioDeviceSetupWidget.h @@ -2,6 +2,7 @@ * AudioDeviceSetupWidget.h - Base class for audio device setup widgets * * Copyright (c) 2004-2015 Tobias Doerffel + * 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 namespace lmms::gui { -class AudioDeviceSetupWidget : public TabWidget +class AudioDeviceSetupWidget : public QGroupBox { Q_OBJECT public: diff --git a/include/AudioEngine.h b/include/AudioEngine.h index f056c22e1..d3d0d025f 100644 --- a/include/AudioEngine.h +++ b/include/AudioEngine.h @@ -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 diff --git a/include/Flags.h b/include/Flags.h index 76106dde6..62a5f8af8 100644 --- a/include/Flags.h +++ b/include/Flags.h @@ -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(*this & flag); } constexpr auto operator~() const -> Flags { return Flags{~m_value}; } diff --git a/include/LmmsSemaphore.h b/include/LmmsSemaphore.h new file mode 100644 index 000000000..4170eef6c --- /dev/null +++ b/include/LmmsSemaphore.h @@ -0,0 +1,93 @@ +/* + * Semaphore.h - Semaphore declaration + * + * Copyright (c) 2022-2022 Johannes Lorenz + * + * 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 +#elif defined(LMMS_BUILD_WIN32) +# include +#else +# include +#endif + +#include + +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 diff --git a/include/LocklessRingBuffer.h b/include/LocklessRingBuffer.h index 99c48cc90..2d65badfe 100644 --- a/include/LocklessRingBuffer.h +++ b/include/LocklessRingBuffer.h @@ -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::m_buffer.write(src, cnt); // Let all waiting readers know new data are available. if (notify) {LocklessRingBuffer::m_notifier.wakeAll();} return written; } + void mlock() { m_buffer.mlock(); } protected: ringbuffer_t m_buffer; diff --git a/include/Lv2Basics.h b/include/Lv2Basics.h index 9a958d973..53489e30d 100644 --- a/include/Lv2Basics.h +++ b/include/Lv2Basics.h @@ -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; using AutoLilvNodes = std::unique_ptr; +using AutoLilvScalePoints = std::unique_ptr; /** Return QString from a plugin's node, everything will be freed automatically diff --git a/include/Lv2Features.h b/include/Lv2Features.h index b5bc284c8..69a456bbd 100644 --- a/include/Lv2Features.h +++ b/include/Lv2Features.h @@ -30,6 +30,7 @@ #ifdef LMMS_HAVE_LV2 #include +#include #include #include "Lv2Manager.h" @@ -78,7 +79,7 @@ private: //! pointers to m_features, required for lilv_plugin_instantiate std::vector m_featurePointers; //! features + data, ordered by URI - std::map m_featureByUri; + std::map m_featureByUri; }; diff --git a/include/Lv2Manager.h b/include/Lv2Manager.h index 909dba560..58126a0a4 100644 --- a/include/Lv2Manager.h +++ b/include/Lv2Manager.h @@ -31,6 +31,7 @@ #include #include +#include #include #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& supportedFeatureURIs() const + const std::set& 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& getPluginBlacklist() + static const std::set& getPluginBlacklist() { return pluginBlacklist; } + static const std::set& getPluginBlacklistBuffersizeLessThan32() + { + return pluginBlacklistBuffersizeLessThan32; + } private: // general data bool m_debug; //!< if set, debug output will be printed LilvWorld* m_world; Lv2InfoMap m_lv2InfoMap; - std::set m_supportedFeatureURIs; + std::set 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 pluginBlacklist; + static const std::set + pluginBlacklist, pluginBlacklistBuffersizeLessThan32; // functions bool isSubclassOf(const LilvPluginClass *clvss, const char *uriStr); diff --git a/include/Lv2Proc.h b/include/Lv2Proc.h index 62070def7..76fa5eec2 100644 --- a/include/Lv2Proc.h +++ b/include/Lv2Proc.h @@ -1,7 +1,7 @@ /* * Lv2Proc.h - Lv2 processor class * - * Copyright (c) 2019-2020 Johannes Lorenz + * Copyright (c) 2019-2022 Johannes Lorenz * * This file is part of LMMS - https://lmms.io * @@ -31,11 +31,14 @@ #include #include +#include +#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 m_worker; + Semaphore m_workLock; // this must be shared by different workers + // full list of ports std::vector> m_ports; // quick reference to specific, unique ports diff --git a/include/Lv2UridMap.h b/include/Lv2UridMap.h index b8733023e..6c22aca3e 100644 --- a/include/Lv2UridMap.h +++ b/include/Lv2UridMap.h @@ -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(); diff --git a/include/Lv2ViewBase.h b/include/Lv2ViewBase.h index f7d0e9bcb..3c8f1bc3f 100644 --- a/include/Lv2ViewBase.h +++ b/include/Lv2ViewBase.h @@ -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: diff --git a/include/Lv2Worker.h b/include/Lv2Worker.h new file mode 100644 index 000000000..7931f8e7c --- /dev/null +++ b/include/Lv2Worker.h @@ -0,0 +1,93 @@ +/* + * Lv2Worker.h - Lv2Worker class + * + * Copyright (c) 2022-2022 Johannes Lorenz + * + * 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 +#include +#include +#include + +#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 m_response; //!< buffer where single requests from m_requests are unpacked + LocklessRingBuffer m_requests, m_responses; //!< ringbuffer to queue multiple requests + LocklessRingBufferReader m_requestsReader, m_responsesReader; + std::atomic 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 + diff --git a/include/MidiEvent.h b/include/MidiEvent.h index 956c33fb3..9a14e427c 100644 --- a/include/MidiEvent.h +++ b/include/MidiEvent.h @@ -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). diff --git a/include/MidiSetupWidget.h b/include/MidiSetupWidget.h index a61b606ac..7b6606017 100644 --- a/include/MidiSetupWidget.h +++ b/include/MidiSetupWidget.h @@ -25,7 +25,7 @@ #ifndef LMMS_GUI_MIDI_SETUP_WIDGET_H #define LMMS_GUI_MIDI_SETUP_WIDGET_H -#include "TabWidget.h" +#include 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, diff --git a/include/SetupDialog.h b/include/SetupDialog.h index de4cdd9dd..882ca2bed 100644 --- a/include/SetupDialog.h +++ b/include/SetupDialog.h @@ -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; diff --git a/include/TabBar.h b/include/TabBar.h index fa2703287..29c100e0c 100644 --- a/include/TabBar.h +++ b/include/TabBar.h @@ -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 ) diff --git a/include/lmms_math.h b/include/lmms_math.h index b62da81c2..ea0a75581 100644 --- a/include/lmms_math.h +++ b/include/lmms_math.h @@ -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(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(asInt) >= power) { ++digits; } // 2 digits for >=10, 3 for >=100 + else { break; } + } + return digits; +} + } // namespace lmms diff --git a/plugins/Compressor/Compressor.cpp b/plugins/Compressor/Compressor.cpp index 33571606e..0fe139420 100755 --- a/plugins/Compressor/Compressor.cpp +++ b/plugins/Compressor/Compressor.cpp @@ -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() diff --git a/plugins/Compressor/CompressorControlDialog.cpp b/plugins/Compressor/CompressorControlDialog.cpp index 114980a7d..1516456a2 100755 --- a/plugins/Compressor/CompressorControlDialog.cpp +++ b/plugins/Compressor/CompressorControlDialog.cpp @@ -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 \ No newline at end of file +} // namespace lmms::gui diff --git a/plugins/LadspaEffect/CMakeLists.txt b/plugins/LadspaEffect/CMakeLists.txt index 025d874da..202a8dd04 100644 --- a/plugins/LadspaEffect/CMakeLists.txt +++ b/plugins/LadspaEffect/CMakeLists.txt @@ -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() diff --git a/plugins/Sid/CMakeLists.txt b/plugins/Sid/CMakeLists.txt index c9fce7bb7..c771fc66d 100644 --- a/plugins/Sid/CMakeLists.txt +++ b/plugins/Sid/CMakeLists.txt @@ -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) diff --git a/plugins/Sid/SidInstrument.cpp b/plugins/Sid/SidInstrument.cpp index 143003f98..7f9edf13f 100644 --- a/plugins/Sid/SidInstrument.cpp +++ b/plugins/Sid/SidInstrument.cpp @@ -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( _n->m_pluginData ); + auto sid = static_cast(_n->m_pluginData); int delta_t = clockrate * frames / samplerate + 4; // avoid variable length array for msvc compat auto buf = reinterpret_cast(_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( _n->m_pluginData ); + delete static_cast(_n->m_pluginData); } diff --git a/plugins/Sid/resid b/plugins/Sid/resid deleted file mode 160000 index 02afcc5ce..000000000 --- a/plugins/Sid/resid +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 02afcc5cefac34bd0c665dc0fa6b748d238c1831 diff --git a/plugins/Sid/resid/CMakeLists.txt b/plugins/Sid/resid/CMakeLists.txt new file mode 100644 index 000000000..bb39e3d16 --- /dev/null +++ b/plugins/Sid/resid/CMakeLists.txt @@ -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 $) + +get_target_property(resid_includes resid_objects INCLUDE_DIRECTORIES) +target_include_directories(resid INTERFACE ${resid_includes}) diff --git a/plugins/Sid/resid/resid b/plugins/Sid/resid/resid new file mode 160000 index 000000000..ef72462f5 --- /dev/null +++ b/plugins/Sid/resid/resid @@ -0,0 +1 @@ +Subproject commit ef72462f5fa0682d099413512b764ae479e77f9b diff --git a/plugins/Stk/Mallets/Mallets.cpp b/plugins/Stk/Mallets/Mallets.cpp index b746e9491..1bca0d40f 100644 --- a/plugins/Stk/Mallets/Mallets.cpp +++ b/plugins/Stk/Mallets/Mallets.cpp @@ -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(fast_rand() % 128) - 64.0; + hardness = std::clamp(hardness, 0.0f, 128.0f); + + position += random * static_cast(fast_rand() % 64) - 32.0; + position = std::clamp(position, 0.0f, 64.0f); + } + else if (p == 9) + { + modulator += random * static_cast(fast_rand() % 128) - 64.0; + modulator = std::clamp(modulator, 0.0f, 128.0f); + + crossfade += random * static_cast(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); } diff --git a/plugins/Stk/Mallets/Mallets.h b/plugins/Stk/Mallets/Mallets.h index f66ac25d0..657d1538a 100644 --- a/plugins/Stk/Mallets/Mallets.h +++ b/plugins/Stk/Mallets/Mallets.h @@ -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; }; diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 319882af2..1155f5e0d 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -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 diff --git a/src/core/DataFile.cpp b/src/core/DataFile.cpp index 8d0a8dca4..90e819077 100644 --- a/src/core/DataFile.cpp +++ b/src/core/DataFile.cpp @@ -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; diff --git a/src/core/DrumSynth.cpp b/src/core/DrumSynth.cpp index bc5455c96..decc6bfa2 100644 --- a/src/core/DrumSynth.cpp +++ b/src/core/DrumSynth.cpp @@ -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(RAND_MAX); + constexpr float randmax2 = 2.f / static_cast(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 \ No newline at end of file +} // namespace lmms diff --git a/src/core/InstrumentFunctions.cpp b/src/core/InstrumentFunctions.cpp index 431afd2fe..976363d3d 100644 --- a/src/core/InstrumentFunctions.cpp +++ b/src/core/InstrumentFunctions.cpp @@ -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(rand()) / (static_cast(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(rand()) / (static_cast(RAND_MAX) + 1.0f) < m_arpMissModel.value()) { dir = ArpDirection::Random; } diff --git a/src/core/LmmsSemaphore.cpp b/src/core/LmmsSemaphore.cpp new file mode 100644 index 000000000..daa70a45b --- /dev/null +++ b/src/core/LmmsSemaphore.cpp @@ -0,0 +1,143 @@ +/* + * Semaphore.cpp - Semaphore implementation + * + * Copyright (c) 2022-2022 Johannes Lorenz + * + * 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 +#else +# include +#endif + +#include + +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 + diff --git a/src/core/SampleBuffer.cpp b/src/core/SampleBuffer.cpp index 5e2d09c57..c96111bba 100644 --- a/src/core/SampleBuffer.cpp +++ b/src/core/SampleBuffer.cpp @@ -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"); } } diff --git a/src/core/audio/AudioJack.cpp b/src/core/audio/AudioJack.cpp index 7371c7bfb..83fa3c177 100644 --- a/src/core/audio/AudioJack.cpp +++ b/src/core/audio/AudioJack.cpp @@ -26,8 +26,8 @@ #ifdef LMMS_HAVE_JACK +#include #include -#include #include #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); } diff --git a/src/core/audio/AudioOss.cpp b/src/core/audio/AudioOss.cpp index 73969533f..8fedd3b2b 100644 --- a/src/core/audio/AudioOss.cpp +++ b/src/core/audio/AudioOss.cpp @@ -27,7 +27,7 @@ #ifdef LMMS_HAVE_OSS #include -#include +#include #include #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); } diff --git a/src/core/audio/AudioPortAudio.cpp b/src/core/audio/AudioPortAudio.cpp index c06eee3d4..3684a79a8 100644 --- a/src/core/audio/AudioPortAudio.cpp +++ b/src/core/audio/AudioPortAudio.cpp @@ -49,7 +49,7 @@ void AudioPortAudioSetupUtil::updateChannels() #ifdef LMMS_HAVE_PORTAUDIO -#include +#include #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 ); diff --git a/src/core/audio/AudioPulseAudio.cpp b/src/core/audio/AudioPulseAudio.cpp index 26a5a02e2..3ca8764cc 100644 --- a/src/core/audio/AudioPulseAudio.cpp +++ b/src/core/audio/AudioPulseAudio.cpp @@ -22,8 +22,8 @@ * */ +#include #include -#include #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); } diff --git a/src/core/audio/AudioSdl.cpp b/src/core/audio/AudioSdl.cpp index c5ffa64a9..12aa97d63 100644 --- a/src/core/audio/AudioSdl.cpp +++ b/src/core/audio/AudioSdl.cpp @@ -26,7 +26,7 @@ #ifdef LMMS_HAVE_SDL -#include +#include #include #include @@ -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); } diff --git a/src/core/audio/AudioSndio.cpp b/src/core/audio/AudioSndio.cpp index 0e46d08f6..bb9b249f8 100644 --- a/src/core/audio/AudioSndio.cpp +++ b/src/core/audio/AudioSndio.cpp @@ -28,7 +28,7 @@ #ifdef LMMS_HAVE_SNDIO #include -#include +#include #include #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); } diff --git a/src/core/audio/AudioSoundIo.cpp b/src/core/audio/AudioSoundIo.cpp index 556909a84..36a1929df 100644 --- a/src/core/audio/AudioSoundIo.cpp +++ b/src/core/audio/AudioSoundIo.cpp @@ -26,7 +26,7 @@ #ifdef LMMS_HAVE_SOUNDIO -#include +#include #include #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(); diff --git a/src/core/lv2/Lv2Features.cpp b/src/core/lv2/Lv2Features.cpp index 6e74a8936..c8fc05465 100644 --- a/src/core/lv2/Lv2Features.cpp +++ b/src/core/lv2/Lv2Features.cpp @@ -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& 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) diff --git a/src/core/lv2/Lv2Manager.cpp b/src/core/lv2/Lv2Manager.cpp index 9c62703e0..6a1b2a8af 100644 --- a/src/core/lv2/Lv2Manager.cpp +++ b/src/core/lv2/Lv2Manager.cpp @@ -28,13 +28,14 @@ #include #include -#include #include #include #include +#include #include #include +#include "AudioEngine.h" #include "Engine.h" #include "Plugin.h" #include "Lv2ControlBase.h" @@ -46,7 +47,7 @@ namespace lmms { -const std::set Lv2Manager::pluginBlacklist = +const std::set Lv2Manager::pluginBlacklist = { // github.com/calf-studio-gear/calf, #278 "http://calf.sourceforge.net/plugins/Analyzer", @@ -137,6 +138,26 @@ const std::set Lv2Manager::pluginBlacklist = "urn:juced:DrumSynth" }; +const std::set 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(); diff --git a/src/core/lv2/Lv2Proc.cpp b/src/core/lv2/Lv2Proc.cpp index e0541b948..11290013e 100644 --- a/src/core/lv2/Lv2Proc.cpp +++ b/src/core/lv2/Lv2Proc.cpp @@ -1,7 +1,7 @@ /* * Lv2Proc.cpp - Lv2 processor class * - * Copyright (c) 2019-2020 Johannes Lorenz + * Copyright (c) 2019-2022 Johannes Lorenz * * This file is part of LMMS - https://lmms.io * @@ -30,6 +30,7 @@ #include #include #include +#include #include #include #include @@ -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(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(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( + 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(lilv_port_get_scale_points(m_plugin, lilvPort))); + // temporary map, since lilv may return scale points in random order + std::map 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: diff --git a/src/core/lv2/Lv2Worker.cpp b/src/core/lv2/Lv2Worker.cpp new file mode 100644 index 000000000..5af955ff7 --- /dev/null +++ b/src/core/lv2/Lv2Worker.cpp @@ -0,0 +1,203 @@ +/* + * Lv2Worker.cpp - Lv2Worker implementation + * + * Copyright (c) 2022-2022 Johannes Lorenz + * + * 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 +#include + +#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(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(this); + m_scheduleFeature.schedule_work = [](LV2_Worker_Schedule_Handle handle, + uint32_t size, const void* data) -> LV2_Worker_Status + { + Lv2Worker* worker = static_cast(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 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 diff --git a/src/gui/AudioAlsaSetupWidget.cpp b/src/gui/AudioAlsaSetupWidget.cpp index 4ea6d4c58..7db822b4b 100644 --- a/src/gui/AudioAlsaSetupWidget.cpp +++ b/src/gui/AudioAlsaSetupWidget.cpp @@ -23,7 +23,7 @@ */ #include -#include +#include #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); } diff --git a/src/gui/AudioDeviceSetupWidget.cpp b/src/gui/AudioDeviceSetupWidget.cpp index b78800cec..98d03638f 100644 --- a/src/gui/AudioDeviceSetupWidget.cpp +++ b/src/gui/AudioDeviceSetupWidget.cpp @@ -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 \ No newline at end of file +} // namespace lmms::gui diff --git a/src/gui/Lv2ViewBase.cpp b/src/gui/Lv2ViewBase.cpp index 3fd1d44b1..830a994c8 100644 --- a/src/gui/Lv2ViewBase.cpp +++ b/src/gui/Lv2ViewBase.cpp @@ -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); diff --git a/src/gui/MidiSetupWidget.cpp b/src/gui/MidiSetupWidget.cpp index 4f620fb0e..2385def02 100644 --- a/src/gui/MidiSetupWidget.cpp +++ b/src/gui/MidiSetupWidget.cpp @@ -24,7 +24,7 @@ #include "MidiSetupWidget.h" -#include +#include #include #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); } } diff --git a/src/gui/MixerView.cpp b/src/gui/MixerView.cpp index dff19ca3e..018e72c2b 100644 --- a/src/gui/MixerView.cpp +++ b/src/gui/MixerView.cpp @@ -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"); }); diff --git a/src/gui/modals/SetupDialog.cpp b/src/gui/modals/SetupDialog.cpp index 63b84506e..e64d1c572 100644 --- a/src/gui/modals/SetupDialog.cpp +++ b/src/gui/modals/SetupDialog.cpp @@ -23,14 +23,15 @@ */ +#include #include +#include #include #include #include #include #include -#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(w->layout()); + assert(boxLayout); - assert(dynamic_cast(w->layout()) != nullptr); - - dynamic_cast(w->layout())->addSpacing(5); - dynamic_cast(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(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 = "
    "; + if((value & (value - 1)) != 0) // <=> value is not a power of 2 (for value > 0) + { + text += "
  • " + tr("The currently selected value is not a power of 2 " + "(32, 64, 128, 256, 512, 1024, ...). Some plugins may not be available.") + "
  • "; + } + if(value <= 32) + { + text += "
  • " + tr("The currently selected value is less than or equal to 32. " + "Some plugins may not be available.") + "
  • "; + } + text += "
"; + 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); } diff --git a/src/gui/tracks/TrackOperationsWidget.cpp b/src/gui/tracks/TrackOperationsWidget.cpp index fa1a651f6..31edc4949 100644 --- a/src/gui/tracks/TrackOperationsWidget.cpp +++ b/src/gui/tracks/TrackOperationsWidget.cpp @@ -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"); }); diff --git a/src/gui/widgets/TabBar.cpp b/src/gui/widgets/TabBar.cpp index 806a93252..e29494551 100644 --- a/src/gui/widgets/TabBar.cpp +++ b/src/gui/widgets/TabBar.cpp @@ -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() ) ); diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 514bf7815..ddf9e2962 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -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 diff --git a/tests/src/core/MathTest.cpp b/tests/src/core/MathTest.cpp new file mode 100644 index 000000000..2b6404cfd --- /dev/null +++ b/tests/src/core/MathTest.cpp @@ -0,0 +1,53 @@ +/* + * MathTest.cpp + * + * Copyright (c) 2023 Johannes Lorenz + * + * 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 + +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"