From a84f7dfe1951bac95349d532d1a22da6acd2b4d2 Mon Sep 17 00:00:00 2001 From: Ross maxx Date: Sun, 2 Jul 2023 09:45:20 +0530 Subject: [PATCH 01/95] removed msvc incompaptible plugins list --- cmake/modules/PluginList.cmake | 8 -------- 1 file changed, 8 deletions(-) diff --git a/cmake/modules/PluginList.cmake b/cmake/modules/PluginList.cmake index 6b2c7519af0..cd4d3e190a9 100644 --- a/cmake/modules/PluginList.cmake +++ b/cmake/modules/PluginList.cmake @@ -98,11 +98,3 @@ IF(LIST_PLUGINS) LIST_ALL_PLUGINS() ENDIF() -IF(MSVC) - SET(MSVC_INCOMPATIBLE_PLUGINS - LadspaEffect - ) - message(WARNING "Compiling with MSVC. The following plugins are not available: ${MSVC_INCOMPATIBLE_PLUGINS}") - LIST(REMOVE_ITEM PLUGIN_LIST ${MSVC_INCOMPATIBLE_PLUGINS}) -ENDIF() - From a30943980c902d87d900643ba21a12291fa9af7d Mon Sep 17 00:00:00 2001 From: Ross maxx Date: Sun, 2 Jul 2023 09:52:39 +0530 Subject: [PATCH 02/95] added Time.h and related changes --- include/sys/Time.h | 33 ++++++++++++++++++++++++ include/sys/Times.h | 63 +++++++++++++++++++++++++++++++++++++++++++++ src/CMakeLists.txt | 2 ++ src/sys/Time.cpp | 54 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 152 insertions(+) create mode 100644 include/sys/Time.h create mode 100644 include/sys/Times.h create mode 100644 src/sys/Time.cpp diff --git a/include/sys/Time.h b/include/sys/Time.h new file mode 100644 index 00000000000..ab98db266f0 --- /dev/null +++ b/include/sys/Time.h @@ -0,0 +1,33 @@ +/* + * Time.h - Windows compatible implementation of sys/time.h + * Copied from http://www.codefull.net/2015/12/systime-h-replacement-for-windows/ + * + * Copyright (c) 2004-2008 Tobias Doerffel + * + * 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. + * + */ + +#pragma once + +#ifndef TIMES_H +#define TIMES_H + +#include "sys/Times.h" + +#endif \ No newline at end of file diff --git a/include/sys/Times.h b/include/sys/Times.h new file mode 100644 index 00000000000..80e38a85bf5 --- /dev/null +++ b/include/sys/Times.h @@ -0,0 +1,63 @@ +/* + * Times.h - Windows compatible implementation of sys/time.h + * Copied from http://www.codefull.net/2015/12/systime-h-replacement-for-windows/ + * + * Copyright (c) 2004-2008 Tobias Doerffel + * + * 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 TIMES_H_FILE +#define TIMES_H_FILE + +#ifdef _WIN32 +#include +#include +#include + +int gettimeofday(struct timeval* t, void* timezone); + +// from linux's sys/times.h + +//#include + +#define __need_clock_t +#include + + +/* Structure describing CPU time used by a process and its children. */ +struct tms +{ + clock_t tms_utime; /* User CPU time. */ + clock_t tms_stime; /* System CPU time. */ + + clock_t tms_cutime; /* User CPU time of dead children. */ + clock_t tms_cstime; /* System CPU time of dead children. */ +}; + +/* Store the CPU time used by this process and all its + dead children (and their dead children) in BUFFER. + Return the elapsed real time, or (clock_t) -1 for errors. + All times are in CLK_TCKths of a second. */ +clock_t times(struct tms* __buffer); + +typedef long long suseconds_t; + +#endif +#endif \ No newline at end of file diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index bd543779fb0..53a54d8e1c8 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -108,6 +108,7 @@ ADD_LIBRARY(lmmsobjs OBJECT ${LMMS_INCLUDES} ${LMMS_UI_OUT} ${LMMS_RCC_OUT} + "sys/Time.cpp" ) GENERATE_EXPORT_HEADER(lmmsobjs @@ -118,6 +119,7 @@ ADD_EXECUTABLE(lmms core/main.cpp $ "${WINRC}" + "sys/Time.cpp" ) TARGET_INCLUDE_DIRECTORIES(lmms PUBLIC ${CMAKE_CURRENT_BINARY_DIR} diff --git a/src/sys/Time.cpp b/src/sys/Time.cpp new file mode 100644 index 00000000000..c69935fe0d5 --- /dev/null +++ b/src/sys/Time.cpp @@ -0,0 +1,54 @@ +/* + * TimePos.cpp - Windows compatible implementation of sys/time.cpp. + * Copied from https://www.codefull.net/2015/12/systime-h-replacement-for-windows/ + * + * Copyright (c) 2004-2014 Tobias Doerffel tv_sec = timebuffer.time; +// t->tv_usec = 1000 * timebuffer.millitm; +// return 0; +//} +// +//clock_t times(struct tms* __buffer) { +// +// __buffer->tms_utime = clock(); +// __buffer->tms_stime = 0; +// __buffer->tms_cstime = 0; +// __buffer->tms_cutime = 0; +// return __buffer->tms_utime; +//} + +#endif \ No newline at end of file From a7e9798106c9170bcbaee7c42a00d51b4f3173f5 Mon Sep 17 00:00:00 2001 From: Ross maxx Date: Sun, 2 Jul 2023 09:57:16 +0530 Subject: [PATCH 03/95] tweaked some cmake files --- plugins/LadspaEffect/calf/CMakeLists.txt | 2 +- plugins/LadspaEffect/caps/CMakeLists.txt | 2 +- plugins/LadspaEffect/swh/CMakeLists.txt | 2 +- plugins/LadspaEffect/tap/CMakeLists.txt | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/plugins/LadspaEffect/calf/CMakeLists.txt b/plugins/LadspaEffect/calf/CMakeLists.txt index 90f50641738..d2d4805efe1 100644 --- a/plugins/LadspaEffect/calf/CMakeLists.txt +++ b/plugins/LadspaEffect/calf/CMakeLists.txt @@ -38,7 +38,7 @@ SET(INLINE_FLAGS "") IF("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU") SET(INLINE_FLAGS "-finline-functions-called-once -finline-limit=80") ENDIF() -SET_TARGET_PROPERTIES(veal PROPERTIES COMPILE_FLAGS "-fexceptions -O2 -finline-functions ${INLINE_FLAGS}") +SET_TARGET_PROPERTIES(veal PROPERTIES COMPILE_FLAGS "-fexceptions -finline-functions ${INLINE_FLAGS}") if(LMMS_BUILD_WIN32) add_custom_command( diff --git a/plugins/LadspaEffect/caps/CMakeLists.txt b/plugins/LadspaEffect/caps/CMakeLists.txt index bdcf3a96af4..025239789c9 100644 --- a/plugins/LadspaEffect/caps/CMakeLists.txt +++ b/plugins/LadspaEffect/caps/CMakeLists.txt @@ -9,7 +9,7 @@ ENDIF(LMMS_BUILD_WIN64) SET_TARGET_PROPERTIES(caps PROPERTIES PREFIX "") SET_TARGET_PROPERTIES(caps PROPERTIES COMPILE_FLAGS "-O2 -funroll-loops -Wno-write-strings") -IF(LMMS_BUILD_WIN32) +IF(LMMS_BUILD_WIN32 AND STRIP) add_custom_command( TARGET caps POST_BUILD diff --git a/plugins/LadspaEffect/swh/CMakeLists.txt b/plugins/LadspaEffect/swh/CMakeLists.txt index aec01c22f8e..ece3070060b 100644 --- a/plugins/LadspaEffect/swh/CMakeLists.txt +++ b/plugins/LadspaEffect/swh/CMakeLists.txt @@ -24,7 +24,7 @@ FOREACH(_item ${XML_SOURCES}) # Coerce XML source file to C ADD_CUSTOM_COMMAND( OUTPUT "${_out_file}" - COMMAND ./makestub.pl "${_item}" > "${_out_file}" + COMMAND perl ./makestub.pl "${_item}" > "${_out_file}" DEPENDS "${_item}" WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/ladspa" VERBATIM diff --git a/plugins/LadspaEffect/tap/CMakeLists.txt b/plugins/LadspaEffect/tap/CMakeLists.txt index c8d0a4eb833..1afa76153ef 100644 --- a/plugins/LadspaEffect/tap/CMakeLists.txt +++ b/plugins/LadspaEffect/tap/CMakeLists.txt @@ -1,7 +1,7 @@ INCLUDE_DIRECTORIES("${CMAKE_SOURCE_DIR}/include") FILE(GLOB PLUGIN_SOURCES tap-plugins/*.c) LIST(SORT PLUGIN_SOURCES) -SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O3 -Wno-write-strings -fomit-frame-pointer -fno-strict-aliasing -funroll-loops -ffast-math") +SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O3 -fomit-frame-pointer -fno-strict-aliasing -funroll-loops -ffast-math") FOREACH(_item ${PLUGIN_SOURCES}) GET_FILENAME_COMPONENT(_plugin "${_item}" NAME_WE) ADD_LIBRARY("${_plugin}" MODULE "${_item}") From f0662687a09e71132bcfa5284bdd49ba77ad8410 Mon Sep 17 00:00:00 2001 From: Ross maxx Date: Sun, 2 Jul 2023 09:59:37 +0530 Subject: [PATCH 04/95] removed __attribute__ --- plugins/LadspaEffect/caps/dsp/Eq.h | 6 +++--- plugins/LadspaEffect/caps/interface.cc | 2 -- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/plugins/LadspaEffect/caps/dsp/Eq.h b/plugins/LadspaEffect/caps/dsp/Eq.h index 92639e8a185..89c86dd18ff 100644 --- a/plugins/LadspaEffect/caps/dsp/Eq.h +++ b/plugins/LadspaEffect/caps/dsp/Eq.h @@ -62,11 +62,11 @@ class Eq { public: /* recursion coefficients, 3 per band */ - eq_sample __attribute__ ((aligned)) a[Bands], b[Bands], c[Bands]; + eq_sample a[Bands], b[Bands], c[Bands]; /* past outputs, 2 per band */ - eq_sample __attribute__ ((aligned)) y[2][Bands]; + eq_sample y[2][Bands]; /* current gain and recursion factor, each 1 per band = 2 */ - eq_sample __attribute__ ((aligned)) gain[Bands], gf[Bands]; + eq_sample gain[Bands], gf[Bands]; /* input history */ eq_sample x[2]; /* history index */ diff --git a/plugins/LadspaEffect/caps/interface.cc b/plugins/LadspaEffect/caps/interface.cc index 96e3d9806f6..8b5740203f3 100644 --- a/plugins/LadspaEffect/caps/interface.cc +++ b/plugins/LadspaEffect/caps/interface.cc @@ -69,7 +69,6 @@ seed() extern "C" { -__attribute__ ((constructor)) void caps_so_init() { DescriptorStub ** d = descriptors; @@ -125,7 +124,6 @@ void caps_so_init() //seed(); } -__attribute__ ((destructor)) void caps_so_fini() { for (ulong i = 0; i < N; ++i) From 844245d31d16aa48d46070b0ad46c8a98bc18afe Mon Sep 17 00:00:00 2001 From: Ross maxx Date: Sun, 2 Jul 2023 10:40:30 +0530 Subject: [PATCH 05/95] defined M_PI in caps --- plugins/LadspaEffect/caps/dsp/Eq.h | 5 +++++ plugins/LadspaEffect/caps/dsp/OnePole.h | 5 +++++ plugins/LadspaEffect/caps/dsp/SVF.h | 6 ++++++ plugins/LadspaEffect/caps/dsp/Sine.h | 4 ++++ 4 files changed, 20 insertions(+) diff --git a/plugins/LadspaEffect/caps/dsp/Eq.h b/plugins/LadspaEffect/caps/dsp/Eq.h index 89c86dd18ff..3a6c89a3042 100644 --- a/plugins/LadspaEffect/caps/dsp/Eq.h +++ b/plugins/LadspaEffect/caps/dsp/Eq.h @@ -29,6 +29,11 @@ #ifndef _DSP_EQ_H_ #define _DSP_EQ_H_ + +#ifndef M_PI +#define M_PI 3.14159265358979323846264338327 +#endif + namespace DSP { /* A single bandpass as used by the Eq, expressed as a biquad. Like all diff --git a/plugins/LadspaEffect/caps/dsp/OnePole.h b/plugins/LadspaEffect/caps/dsp/OnePole.h index 9a317805321..81abcb6ff26 100644 --- a/plugins/LadspaEffect/caps/dsp/OnePole.h +++ b/plugins/LadspaEffect/caps/dsp/OnePole.h @@ -28,6 +28,11 @@ #ifndef _ONE_POLE_H_ #define _ONE_POLE_H_ + +#ifndef M_PI +#define M_PI 3.14159265358979323846264338327 +#endif + namespace DSP { class OnePoleLP diff --git a/plugins/LadspaEffect/caps/dsp/SVF.h b/plugins/LadspaEffect/caps/dsp/SVF.h index ccd5734ab3f..6d60dc7f77a 100644 --- a/plugins/LadspaEffect/caps/dsp/SVF.h +++ b/plugins/LadspaEffect/caps/dsp/SVF.h @@ -73,6 +73,12 @@ #ifndef _DSP_SVF_H_ #define _DSP_SVF_H_ +#define min(a,b) (((a) < (b)) ? (a) : (b)) + +#ifndef M_PI +#define M_PI 3.14159265358979323846264338327 +#endif + namespace DSP { template diff --git a/plugins/LadspaEffect/caps/dsp/Sine.h b/plugins/LadspaEffect/caps/dsp/Sine.h index 43e5f06e03c..95f225b446f 100644 --- a/plugins/LadspaEffect/caps/dsp/Sine.h +++ b/plugins/LadspaEffect/caps/dsp/Sine.h @@ -28,6 +28,10 @@ #ifndef _DSP_SINE_H_ #define _DSP_SINE_H_ +#ifndef M_PI +#define M_PI 3.14159265358979323846264338327 +#endif + namespace DSP { class Sine From 3411989424d3be10955d68034fad4b8d7c608184 Mon Sep 17 00:00:00 2001 From: Ross maxx Date: Sun, 2 Jul 2023 10:55:44 +0530 Subject: [PATCH 06/95] minor fix in ladspa cmakelists --- plugins/LadspaEffect/caps/CMakeLists.txt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/plugins/LadspaEffect/caps/CMakeLists.txt b/plugins/LadspaEffect/caps/CMakeLists.txt index 025239789c9..e7ffce5faf0 100644 --- a/plugins/LadspaEffect/caps/CMakeLists.txt +++ b/plugins/LadspaEffect/caps/CMakeLists.txt @@ -7,7 +7,7 @@ IF(LMMS_BUILD_WIN64) ADD_DEFINITIONS(-DLMMS_BUILD_WIN64) ENDIF(LMMS_BUILD_WIN64) SET_TARGET_PROPERTIES(caps PROPERTIES PREFIX "") -SET_TARGET_PROPERTIES(caps PROPERTIES COMPILE_FLAGS "-O2 -funroll-loops -Wno-write-strings") +SET_TARGET_PROPERTIES(caps PROPERTIES COMPILE_FLAGS "-funroll-loops") IF(LMMS_BUILD_WIN32 AND STRIP) add_custom_command( @@ -17,7 +17,8 @@ IF(LMMS_BUILD_WIN32 AND STRIP) VERBATIM COMMAND_EXPAND_LISTS ) -ENDIF(LMMS_BUILD_WIN32) +ENDIF(LMMS_BUILD_WIN32 AND STRIP) + IF(NOT LMMS_BUILD_APPLE AND NOT LMMS_BUILD_OPENBSD) SET_TARGET_PROPERTIES(caps PROPERTIES LINK_FLAGS "${LINK_FLAGS} -shared -Wl,-no-undefined") ENDIF(NOT LMMS_BUILD_APPLE AND NOT LMMS_BUILD_OPENBSD) From 082773dea10772d97e098484f098ad9a4477b1aa Mon Sep 17 00:00:00 2001 From: Ross maxx Date: Sun, 2 Jul 2023 14:04:57 +0530 Subject: [PATCH 07/95] added namespace to Times.h --- include/sys/Times.h | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/include/sys/Times.h b/include/sys/Times.h index 80e38a85bf5..d10f669b74b 100644 --- a/include/sys/Times.h +++ b/include/sys/Times.h @@ -31,6 +31,9 @@ #include #include +namespace lmms +{ + int gettimeofday(struct timeval* t, void* timezone); // from linux's sys/times.h @@ -59,5 +62,7 @@ clock_t times(struct tms* __buffer); typedef long long suseconds_t; +} // namespace lmms + #endif #endif \ No newline at end of file From 5fb6fba80b9b13634aee79f57187d18fae3a9042 Mon Sep 17 00:00:00 2001 From: Ross maxx Date: Sun, 2 Jul 2023 14:15:53 +0530 Subject: [PATCH 08/95] relocated includes --- {include => src}/sys/Time.h | 0 {include => src}/sys/Times.h | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename {include => src}/sys/Time.h (100%) rename {include => src}/sys/Times.h (100%) diff --git a/include/sys/Time.h b/src/sys/Time.h similarity index 100% rename from include/sys/Time.h rename to src/sys/Time.h diff --git a/include/sys/Times.h b/src/sys/Times.h similarity index 100% rename from include/sys/Times.h rename to src/sys/Times.h From 8ccb161bf3442a06e7aea8d1dca8981ece33f9e1 Mon Sep 17 00:00:00 2001 From: Ross maxx Date: Sun, 2 Jul 2023 14:22:24 +0530 Subject: [PATCH 09/95] fixes to scripted-checks --- src/sys/Time.cpp | 2 ++ src/sys/Time.h | 2 ++ src/sys/Times.h | 12 +++++------- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/sys/Time.cpp b/src/sys/Time.cpp index c69935fe0d5..d0908bc0ccc 100644 --- a/src/sys/Time.cpp +++ b/src/sys/Time.cpp @@ -33,6 +33,8 @@ #ifndef TIME #define TIME +namespace lmms {} + //int gettimeofday(struct timeval* t, void* timezone) //{ // struct _timeb timebuffer; diff --git a/src/sys/Time.h b/src/sys/Time.h index ab98db266f0..073ee5e0449 100644 --- a/src/sys/Time.h +++ b/src/sys/Time.h @@ -30,4 +30,6 @@ #include "sys/Times.h" +namespace lmms{} + #endif \ No newline at end of file diff --git a/src/sys/Times.h b/src/sys/Times.h index d10f669b74b..d1186cf1b31 100644 --- a/src/sys/Times.h +++ b/src/sys/Times.h @@ -31,17 +31,15 @@ #include #include +#define __need_clock_t +#include + namespace lmms { int gettimeofday(struct timeval* t, void* timezone); -// from linux's sys/times.h -//#include - -#define __need_clock_t -#include /* Structure describing CPU time used by a process and its children. */ @@ -64,5 +62,5 @@ typedef long long suseconds_t; } // namespace lmms -#endif -#endif \ No newline at end of file +#endif // _WIN_32 +#endif // _TIMES_H_FILE \ No newline at end of file From 7613a124c4603471424a99d42447fdce7d68b4cd Mon Sep 17 00:00:00 2001 From: Ross maxx Date: Sun, 2 Jul 2023 14:22:58 +0530 Subject: [PATCH 10/95] deleted unnecessary whitespaces --- src/sys/Times.h | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/sys/Times.h b/src/sys/Times.h index d1186cf1b31..06455876d72 100644 --- a/src/sys/Times.h +++ b/src/sys/Times.h @@ -40,8 +40,6 @@ namespace lmms int gettimeofday(struct timeval* t, void* timezone); - - /* Structure describing CPU time used by a process and its children. */ struct tms { From fa02a9272cc68ae8f1b60c384980ecfd8bdb3ff8 Mon Sep 17 00:00:00 2001 From: Ross maxx Date: Sun, 2 Jul 2023 14:47:09 +0530 Subject: [PATCH 11/95] added M_PI to cmt --- plugins/LadspaEffect/cmt/cmt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/LadspaEffect/cmt/cmt b/plugins/LadspaEffect/cmt/cmt index f7c25ed4ef7..a2c9c140774 160000 --- a/plugins/LadspaEffect/cmt/cmt +++ b/plugins/LadspaEffect/cmt/cmt @@ -1 +1 @@ -Subproject commit f7c25ed4ef7f4d7efb1bcd4229d25595d4f1ce55 +Subproject commit a2c9c140774bebfbac200c91cc56df644ff24cd6 From d85c104fefa302f2961997077c39fbe10409282b Mon Sep 17 00:00:00 2001 From: Ross maxx Date: Sun, 2 Jul 2023 14:48:01 +0530 Subject: [PATCH 12/95] Revert "relocated includes" This reverts commit 5fb6fba80b9b13634aee79f57187d18fae3a9042. --- {src => include}/sys/Time.h | 0 {src => include}/sys/Times.h | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename {src => include}/sys/Time.h (100%) rename {src => include}/sys/Times.h (100%) diff --git a/src/sys/Time.h b/include/sys/Time.h similarity index 100% rename from src/sys/Time.h rename to include/sys/Time.h diff --git a/src/sys/Times.h b/include/sys/Times.h similarity index 100% rename from src/sys/Times.h rename to include/sys/Times.h From 601ee195b47eabe0b30e07ce8656224f91153440 Mon Sep 17 00:00:00 2001 From: Ross maxx Date: Sun, 2 Jul 2023 20:01:15 +0530 Subject: [PATCH 13/95] removed times.h and the other time files --- include/sys/Time.h | 35 ------------------------- include/sys/Times.h | 64 --------------------------------------------- src/CMakeLists.txt | 2 -- src/sys/Time.cpp | 56 --------------------------------------- 4 files changed, 157 deletions(-) delete mode 100644 include/sys/Time.h delete mode 100644 include/sys/Times.h delete mode 100644 src/sys/Time.cpp diff --git a/include/sys/Time.h b/include/sys/Time.h deleted file mode 100644 index 073ee5e0449..00000000000 --- a/include/sys/Time.h +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Time.h - Windows compatible implementation of sys/time.h - * Copied from http://www.codefull.net/2015/12/systime-h-replacement-for-windows/ - * - * Copyright (c) 2004-2008 Tobias Doerffel - * - * 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. - * - */ - -#pragma once - -#ifndef TIMES_H -#define TIMES_H - -#include "sys/Times.h" - -namespace lmms{} - -#endif \ No newline at end of file diff --git a/include/sys/Times.h b/include/sys/Times.h deleted file mode 100644 index 06455876d72..00000000000 --- a/include/sys/Times.h +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Times.h - Windows compatible implementation of sys/time.h - * Copied from http://www.codefull.net/2015/12/systime-h-replacement-for-windows/ - * - * Copyright (c) 2004-2008 Tobias Doerffel - * - * 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 TIMES_H_FILE -#define TIMES_H_FILE - -#ifdef _WIN32 -#include -#include -#include - -#define __need_clock_t -#include - -namespace lmms -{ - -int gettimeofday(struct timeval* t, void* timezone); - - -/* Structure describing CPU time used by a process and its children. */ -struct tms -{ - clock_t tms_utime; /* User CPU time. */ - clock_t tms_stime; /* System CPU time. */ - - clock_t tms_cutime; /* User CPU time of dead children. */ - clock_t tms_cstime; /* System CPU time of dead children. */ -}; - -/* Store the CPU time used by this process and all its - dead children (and their dead children) in BUFFER. - Return the elapsed real time, or (clock_t) -1 for errors. - All times are in CLK_TCKths of a second. */ -clock_t times(struct tms* __buffer); - -typedef long long suseconds_t; - -} // namespace lmms - -#endif // _WIN_32 -#endif // _TIMES_H_FILE \ No newline at end of file diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 53a54d8e1c8..bd543779fb0 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -108,7 +108,6 @@ ADD_LIBRARY(lmmsobjs OBJECT ${LMMS_INCLUDES} ${LMMS_UI_OUT} ${LMMS_RCC_OUT} - "sys/Time.cpp" ) GENERATE_EXPORT_HEADER(lmmsobjs @@ -119,7 +118,6 @@ ADD_EXECUTABLE(lmms core/main.cpp $ "${WINRC}" - "sys/Time.cpp" ) TARGET_INCLUDE_DIRECTORIES(lmms PUBLIC ${CMAKE_CURRENT_BINARY_DIR} diff --git a/src/sys/Time.cpp b/src/sys/Time.cpp deleted file mode 100644 index d0908bc0ccc..00000000000 --- a/src/sys/Time.cpp +++ /dev/null @@ -1,56 +0,0 @@ -/* - * TimePos.cpp - Windows compatible implementation of sys/time.cpp. - * Copied from https://www.codefull.net/2015/12/systime-h-replacement-for-windows/ - * - * Copyright (c) 2004-2014 Tobias Doerffel tv_sec = timebuffer.time; -// t->tv_usec = 1000 * timebuffer.millitm; -// return 0; -//} -// -//clock_t times(struct tms* __buffer) { -// -// __buffer->tms_utime = clock(); -// __buffer->tms_stime = 0; -// __buffer->tms_cstime = 0; -// __buffer->tms_cutime = 0; -// return __buffer->tms_utime; -//} - -#endif \ No newline at end of file From 7ad2695d4d373cc82c05cd4324b30ad28899a30e Mon Sep 17 00:00:00 2001 From: Ross maxx Date: Sun, 2 Jul 2023 20:09:36 +0530 Subject: [PATCH 14/95] addressing dom's comments 1 --- plugins/LadspaEffect/calf/CMakeLists.txt | 5 ++++- plugins/LadspaEffect/cmt/cmt | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/plugins/LadspaEffect/calf/CMakeLists.txt b/plugins/LadspaEffect/calf/CMakeLists.txt index d2d4805efe1..6ec392a81d2 100644 --- a/plugins/LadspaEffect/calf/CMakeLists.txt +++ b/plugins/LadspaEffect/calf/CMakeLists.txt @@ -38,7 +38,10 @@ SET(INLINE_FLAGS "") IF("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU") SET(INLINE_FLAGS "-finline-functions-called-once -finline-limit=80") ENDIF() -SET_TARGET_PROPERTIES(veal PROPERTIES COMPILE_FLAGS "-fexceptions -finline-functions ${INLINE_FLAGS}") + +IF(NOT MSVC) + SET_TARGET_PROPERTIES(veal PROPERTIES COMPILE_FLAGS "-fexceptions -O2 -finline-functions ${INLINE_FLAGS}") +endif() if(LMMS_BUILD_WIN32) add_custom_command( diff --git a/plugins/LadspaEffect/cmt/cmt b/plugins/LadspaEffect/cmt/cmt index a2c9c140774..5bc4d810696 160000 --- a/plugins/LadspaEffect/cmt/cmt +++ b/plugins/LadspaEffect/cmt/cmt @@ -1 +1 @@ -Subproject commit a2c9c140774bebfbac200c91cc56df644ff24cd6 +Subproject commit 5bc4d810696ea1c824651ab34c5e8d60dfd4293d From c252448c842133d8996030c25fcc0f259894fcbc Mon Sep 17 00:00:00 2001 From: Ross maxx Date: Sun, 2 Jul 2023 20:16:24 +0530 Subject: [PATCH 15/95] addressing dom's concerns 2 --- plugins/LadspaEffect/caps/CMakeLists.txt | 4 ++-- plugins/LadspaEffect/caps/dsp/SVF.h | 2 -- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/plugins/LadspaEffect/caps/CMakeLists.txt b/plugins/LadspaEffect/caps/CMakeLists.txt index e7ffce5faf0..2ee75bbc7b4 100644 --- a/plugins/LadspaEffect/caps/CMakeLists.txt +++ b/plugins/LadspaEffect/caps/CMakeLists.txt @@ -9,7 +9,7 @@ ENDIF(LMMS_BUILD_WIN64) SET_TARGET_PROPERTIES(caps PROPERTIES PREFIX "") SET_TARGET_PROPERTIES(caps PROPERTIES COMPILE_FLAGS "-funroll-loops") -IF(LMMS_BUILD_WIN32 AND STRIP) +IF(LMMS_BUILD_WIN32) add_custom_command( TARGET caps POST_BUILD @@ -17,7 +17,7 @@ IF(LMMS_BUILD_WIN32 AND STRIP) VERBATIM COMMAND_EXPAND_LISTS ) -ENDIF(LMMS_BUILD_WIN32 AND STRIP) +ENDIF(LMMS_BUILD_WIN32) IF(NOT LMMS_BUILD_APPLE AND NOT LMMS_BUILD_OPENBSD) SET_TARGET_PROPERTIES(caps PROPERTIES LINK_FLAGS "${LINK_FLAGS} -shared -Wl,-no-undefined") diff --git a/plugins/LadspaEffect/caps/dsp/SVF.h b/plugins/LadspaEffect/caps/dsp/SVF.h index 6d60dc7f77a..31528e8aadf 100644 --- a/plugins/LadspaEffect/caps/dsp/SVF.h +++ b/plugins/LadspaEffect/caps/dsp/SVF.h @@ -73,8 +73,6 @@ #ifndef _DSP_SVF_H_ #define _DSP_SVF_H_ -#define min(a,b) (((a) < (b)) ? (a) : (b)) - #ifndef M_PI #define M_PI 3.14159265358979323846264338327 #endif From 84edde371e1b0f1eee66aaf71f4505452c179553 Mon Sep 17 00:00:00 2001 From: Ross maxx Date: Sun, 2 Jul 2023 20:38:34 +0530 Subject: [PATCH 16/95] commented sys/time.h --- plugins/LadspaEffect/caps/interface.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/LadspaEffect/caps/interface.cc b/plugins/LadspaEffect/caps/interface.cc index 8b5740203f3..4f4edfbeee8 100644 --- a/plugins/LadspaEffect/caps/interface.cc +++ b/plugins/LadspaEffect/caps/interface.cc @@ -29,7 +29,7 @@ (2541 - 2580 donated to artemio@kdemail.net) */ -#include +// #include #include "basics.h" From 7785ebc18fb0eac0629f1ffb1af7bee62848f194 Mon Sep 17 00:00:00 2001 From: Ross maxx Date: Mon, 3 Jul 2023 17:33:36 +0530 Subject: [PATCH 17/95] another tweak to caps cmakelists --- plugins/LadspaEffect/caps/CMakeLists.txt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/plugins/LadspaEffect/caps/CMakeLists.txt b/plugins/LadspaEffect/caps/CMakeLists.txt index 2ee75bbc7b4..bb7f19152c8 100644 --- a/plugins/LadspaEffect/caps/CMakeLists.txt +++ b/plugins/LadspaEffect/caps/CMakeLists.txt @@ -7,7 +7,10 @@ IF(LMMS_BUILD_WIN64) ADD_DEFINITIONS(-DLMMS_BUILD_WIN64) ENDIF(LMMS_BUILD_WIN64) SET_TARGET_PROPERTIES(caps PROPERTIES PREFIX "") -SET_TARGET_PROPERTIES(caps PROPERTIES COMPILE_FLAGS "-funroll-loops") + +IF (NOT MSVC) + SET_TARGET_PROPERTIES(caps PROPERTIES COMPILE_FLAGS "-O2 -funroll-loops -Wno-write-strings") +ENDIF(NOT MSVC) IF(LMMS_BUILD_WIN32) add_custom_command( From dd89d21a6635f7ead353e766f093672fa7247a06 Mon Sep 17 00:00:00 2001 From: Ross maxx Date: Mon, 3 Jul 2023 17:41:50 +0530 Subject: [PATCH 18/95] added caps init condition --- plugins/LadspaEffect/caps/interface.cc | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/plugins/LadspaEffect/caps/interface.cc b/plugins/LadspaEffect/caps/interface.cc index 4f4edfbeee8..58a3b31e926 100644 --- a/plugins/LadspaEffect/caps/interface.cc +++ b/plugins/LadspaEffect/caps/interface.cc @@ -29,8 +29,6 @@ (2541 - 2580 donated to artemio@kdemail.net) */ -// #include - #include "basics.h" #include "Cabinet.h" @@ -58,15 +56,6 @@ #define N 39 static DescriptorStub * descriptors [N]; -/*static inline void -seed() -{ - static struct timeval tv; - gettimeofday (&tv, 0); - - srand (tv.tv_sec ^ tv.tv_usec); -}*/ - extern "C" { void caps_so_init() @@ -140,4 +129,10 @@ ladspa_descriptor (unsigned long i) return 0; } +struct CapsSoInit { + CapsSoInit() { caps_so_init(); } + ~CapsSoInit() { caps_so_fini(); } +}; +static CapsSoInit capsSoInit; + }; /* extern "C" */ From 1c045ed8aa894239c39191b1448c3dbfb8fa89cd Mon Sep 17 00:00:00 2001 From: Ross maxx Date: Mon, 3 Jul 2023 22:47:49 +0530 Subject: [PATCH 19/95] attepted fix on calf plugins --- plugins/LadspaEffect/caps/interface.cc | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/plugins/LadspaEffect/caps/interface.cc b/plugins/LadspaEffect/caps/interface.cc index 58a3b31e926..e6c76ce05ef 100644 --- a/plugins/LadspaEffect/caps/interface.cc +++ b/plugins/LadspaEffect/caps/interface.cc @@ -58,6 +58,9 @@ static DescriptorStub * descriptors [N]; extern "C" { +#ifdef __GNUC__ + __attribute__(constructor) +#endif void caps_so_init() { DescriptorStub ** d = descriptors; @@ -113,6 +116,9 @@ void caps_so_init() //seed(); } +#ifdef __GNUC__ + __attribute__(destructor) +#endif void caps_so_fini() { for (ulong i = 0; i < N; ++i) @@ -129,10 +135,10 @@ ladspa_descriptor (unsigned long i) return 0; } -struct CapsSoInit { - CapsSoInit() { caps_so_init(); } - ~CapsSoInit() { caps_so_fini(); } -}; -static CapsSoInit capsSoInit; + +#ifdef _MSC_VER + #pragma startup caps_so_init + #pragma exit caps_so_fini +#endif }; /* extern "C" */ From 961334e4daa6181e59a8db39ae18ce1cec25103a Mon Sep 17 00:00:00 2001 From: Ross maxx Date: Mon, 3 Jul 2023 23:10:20 +0530 Subject: [PATCH 20/95] fixup to mingw build --- plugins/LadspaEffect/caps/interface.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/LadspaEffect/caps/interface.cc b/plugins/LadspaEffect/caps/interface.cc index e6c76ce05ef..c7f8ff83d62 100644 --- a/plugins/LadspaEffect/caps/interface.cc +++ b/plugins/LadspaEffect/caps/interface.cc @@ -59,7 +59,7 @@ static DescriptorStub * descriptors [N]; extern "C" { #ifdef __GNUC__ - __attribute__(constructor) + __attribute__((constructor)) #endif void caps_so_init() { @@ -117,7 +117,7 @@ void caps_so_init() } #ifdef __GNUC__ - __attribute__(destructor) + __attribute__((destructor)) #endif void caps_so_fini() { From 43ca0f286a82e3a83f4db5f1e8fc937775d83849 Mon Sep 17 00:00:00 2001 From: Ross maxx Date: Tue, 4 Jul 2023 18:44:38 +0530 Subject: [PATCH 21/95] added __declspec(dllexport) --- include/ladspa.h | 3 +++ plugins/LadspaEffect/caps/interface.cc | 3 +++ 2 files changed, 6 insertions(+) diff --git a/include/ladspa.h b/include/ladspa.h index 5c30a8a4b5c..8f449562fbb 100644 --- a/include/ladspa.h +++ b/include/ladspa.h @@ -586,6 +586,9 @@ typedef struct _LADSPA_Descriptor { returning NULL, so the plugin count can be determined by checking for the least index that results in NULL being returned. */ +#ifdef _MSC_VER +__declspec(dllexport) +#endif const LADSPA_Descriptor * ladspa_descriptor(unsigned long Index); /* Datatype corresponding to the ladspa_descriptor() function. */ diff --git a/plugins/LadspaEffect/caps/interface.cc b/plugins/LadspaEffect/caps/interface.cc index c7f8ff83d62..7f5b15962b4 100644 --- a/plugins/LadspaEffect/caps/interface.cc +++ b/plugins/LadspaEffect/caps/interface.cc @@ -127,6 +127,9 @@ void caps_so_fini() /* /////////////////////////////////////////////////////////////////////// */ +#ifdef _MSC_VER +__declspec(dllexport) +#endif const LADSPA_Descriptor * ladspa_descriptor (unsigned long i) { From 4a5c64aeed9a4ad8eeb41d3350c1ae33b7e86776 Mon Sep 17 00:00:00 2001 From: Ross maxx Date: Tue, 4 Jul 2023 19:12:25 +0530 Subject: [PATCH 22/95] added init condition, Will this fix it? --- plugins/LadspaEffect/caps/interface.cc | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/plugins/LadspaEffect/caps/interface.cc b/plugins/LadspaEffect/caps/interface.cc index 7f5b15962b4..ff027bed7df 100644 --- a/plugins/LadspaEffect/caps/interface.cc +++ b/plugins/LadspaEffect/caps/interface.cc @@ -138,6 +138,11 @@ ladspa_descriptor (unsigned long i) return 0; } +struct CapsSoInit { + CapsSoInit() { caps_so_init(); } + ~CapsSoInit() { caps_so_fini(); } +}; +static CapsSoInit capsSoInit; #ifdef _MSC_VER #pragma startup caps_so_init From b2b8a6eb88d4316ba22d55d75de7307bdb19812e Mon Sep 17 00:00:00 2001 From: Ross maxx Date: Tue, 4 Jul 2023 19:18:11 +0530 Subject: [PATCH 23/95] temporary blacklist for ladspa plugins for testing --- plugins/LadspaEffect/CMakeLists.txt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/plugins/LadspaEffect/CMakeLists.txt b/plugins/LadspaEffect/CMakeLists.txt index 951615ad4d0..1c3cde36d9c 100644 --- a/plugins/LadspaEffect/CMakeLists.txt +++ b/plugins/LadspaEffect/CMakeLists.txt @@ -8,6 +8,9 @@ IF(WANT_CAPS) ADD_SUBDIRECTORY(caps) ENDIF(WANT_CAPS) +# temporary condition, will be removed +IF(NOT MSVC) + IF(WANT_TAP) ADD_SUBDIRECTORY(tap) ENDIF(WANT_TAP) @@ -23,3 +26,5 @@ ENDIF(WANT_CMT) IF(WANT_CALF) ADD_SUBDIRECTORY(calf) ENDIF(WANT_CALF) + +ENDIF(NOT MSVC) From 3c957be4e4c921b0a48349a69fce56141054e234 Mon Sep 17 00:00:00 2001 From: Ross maxx Date: Tue, 4 Jul 2023 19:54:25 +0530 Subject: [PATCH 24/95] removed cmt from blacklist --- plugins/LadspaEffect/CMakeLists.txt | 8 ++++---- plugins/LadspaEffect/caps/interface.cc | 6 ------ 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/plugins/LadspaEffect/CMakeLists.txt b/plugins/LadspaEffect/CMakeLists.txt index 1c3cde36d9c..d55993e8075 100644 --- a/plugins/LadspaEffect/CMakeLists.txt +++ b/plugins/LadspaEffect/CMakeLists.txt @@ -8,6 +8,10 @@ IF(WANT_CAPS) ADD_SUBDIRECTORY(caps) ENDIF(WANT_CAPS) +IF(WANT_CMT) +ADD_SUBDIRECTORY(cmt) +ENDIF(WANT_CMT) + # temporary condition, will be removed IF(NOT MSVC) @@ -19,10 +23,6 @@ IF(WANT_SWH) ADD_SUBDIRECTORY(swh) ENDIF(WANT_SWH) -IF(WANT_CMT) -ADD_SUBDIRECTORY(cmt) -ENDIF(WANT_CMT) - IF(WANT_CALF) ADD_SUBDIRECTORY(calf) ENDIF(WANT_CALF) diff --git a/plugins/LadspaEffect/caps/interface.cc b/plugins/LadspaEffect/caps/interface.cc index ff027bed7df..4c674923728 100644 --- a/plugins/LadspaEffect/caps/interface.cc +++ b/plugins/LadspaEffect/caps/interface.cc @@ -113,7 +113,6 @@ void caps_so_init() /* make sure N is correct */ assert (d - descriptors == N); - //seed(); } #ifdef __GNUC__ @@ -144,9 +143,4 @@ struct CapsSoInit { }; static CapsSoInit capsSoInit; -#ifdef _MSC_VER - #pragma startup caps_so_init - #pragma exit caps_so_fini -#endif - }; /* extern "C" */ From 88dfa0f6013567506c3546ae0de4591eadad1838 Mon Sep 17 00:00:00 2001 From: Ross maxx Date: Tue, 4 Jul 2023 20:16:35 +0530 Subject: [PATCH 25/95] added declspec in cmt --- plugins/LadspaEffect/cmt/cmt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/LadspaEffect/cmt/cmt b/plugins/LadspaEffect/cmt/cmt index 5bc4d810696..99b0fd3d4b3 160000 --- a/plugins/LadspaEffect/cmt/cmt +++ b/plugins/LadspaEffect/cmt/cmt @@ -1 +1 @@ -Subproject commit 5bc4d810696ea1c824651ab34c5e8d60dfd4293d +Subproject commit 99b0fd3d4b32056263a0a997dc68ca5897ad76b0 From c77b3a52e75a3775855d758b86ee2f690691385f Mon Sep 17 00:00:00 2001 From: Ross maxx Date: Wed, 5 Jul 2023 13:05:53 +0530 Subject: [PATCH 26/95] removed swh from blacklist --- plugins/LadspaEffect/CMakeLists.txt | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/plugins/LadspaEffect/CMakeLists.txt b/plugins/LadspaEffect/CMakeLists.txt index d55993e8075..5528681c806 100644 --- a/plugins/LadspaEffect/CMakeLists.txt +++ b/plugins/LadspaEffect/CMakeLists.txt @@ -12,6 +12,11 @@ IF(WANT_CMT) ADD_SUBDIRECTORY(cmt) ENDIF(WANT_CMT) + +IF(WANT_SWH) +ADD_SUBDIRECTORY(swh) +ENDIF(WANT_SWH) + # temporary condition, will be removed IF(NOT MSVC) @@ -19,10 +24,6 @@ IF(WANT_TAP) ADD_SUBDIRECTORY(tap) ENDIF(WANT_TAP) -IF(WANT_SWH) -ADD_SUBDIRECTORY(swh) -ENDIF(WANT_SWH) - IF(WANT_CALF) ADD_SUBDIRECTORY(calf) ENDIF(WANT_CALF) From dff0cd4011ca66ff91b93e05ca40238ef80f3120 Mon Sep 17 00:00:00 2001 From: Ross maxx Date: Thu, 6 Jul 2023 11:01:35 +0530 Subject: [PATCH 27/95] added _USE_MATH_DEFINES to swh --- plugins/LadspaEffect/swh/ladspa | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/LadspaEffect/swh/ladspa b/plugins/LadspaEffect/swh/ladspa index d99a0db521d..922bb429119 160000 --- a/plugins/LadspaEffect/swh/ladspa +++ b/plugins/LadspaEffect/swh/ladspa @@ -1 +1 @@ -Subproject commit d99a0db521d13a87bdaa418c674ca8858e484452 +Subproject commit 922bb429119c1268df10d5c245dfb76a5dba5f64 From 87a0103e33a18040af84baee70c50b4167e02a23 Mon Sep 17 00:00:00 2001 From: Ross maxx Date: Thu, 6 Jul 2023 11:04:57 +0530 Subject: [PATCH 28/95] codefactor fix --- plugins/LadspaEffect/caps/interface.cc | 1 - 1 file changed, 1 deletion(-) diff --git a/plugins/LadspaEffect/caps/interface.cc b/plugins/LadspaEffect/caps/interface.cc index 4c674923728..5786b27917e 100644 --- a/plugins/LadspaEffect/caps/interface.cc +++ b/plugins/LadspaEffect/caps/interface.cc @@ -112,7 +112,6 @@ void caps_so_init() /* make sure N is correct */ assert (d - descriptors == N); - } #ifdef __GNUC__ From 4570893d7102c89461bbae2e3310637d93312231 Mon Sep 17 00:00:00 2001 From: Ross maxx Date: Thu, 6 Jul 2023 11:26:23 +0530 Subject: [PATCH 29/95] added _USE_MATH_DEFINES to pitchscale.h --- plugins/LadspaEffect/swh/ladspa | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/LadspaEffect/swh/ladspa b/plugins/LadspaEffect/swh/ladspa index 922bb429119..5d05a25a158 160000 --- a/plugins/LadspaEffect/swh/ladspa +++ b/plugins/LadspaEffect/swh/ladspa @@ -1 +1 @@ -Subproject commit 922bb429119c1268df10d5c245dfb76a5dba5f64 +Subproject commit 5d05a25a158eba8583ea9c4eefd161a54ffba986 From 227414766a6f19d5f28718d8007844b297e3f502 Mon Sep 17 00:00:00 2001 From: Ross maxx Date: Thu, 6 Jul 2023 11:37:51 +0530 Subject: [PATCH 30/95] workaround to enable sinus_wavewrapper --- plugins/LadspaEffect/swh/ladspa | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/LadspaEffect/swh/ladspa b/plugins/LadspaEffect/swh/ladspa index 5d05a25a158..98908789b13 160000 --- a/plugins/LadspaEffect/swh/ladspa +++ b/plugins/LadspaEffect/swh/ladspa @@ -1 +1 @@ -Subproject commit 5d05a25a158eba8583ea9c4eefd161a54ffba986 +Subproject commit 98908789b13e99b99c342ec26d4fca1f6031cc03 From 24817311c48c3783ff7e27c1f35e397a92f6d636 Mon Sep 17 00:00:00 2001 From: Ross maxx Date: Thu, 6 Jul 2023 11:54:02 +0530 Subject: [PATCH 31/95] Revert "workaround to enable sinus_wavewrapper" This reverts commit 227414766a6f19d5f28718d8007844b297e3f502. --- plugins/LadspaEffect/swh/ladspa | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/LadspaEffect/swh/ladspa b/plugins/LadspaEffect/swh/ladspa index 98908789b13..5d05a25a158 160000 --- a/plugins/LadspaEffect/swh/ladspa +++ b/plugins/LadspaEffect/swh/ladspa @@ -1 +1 @@ -Subproject commit 98908789b13e99b99c342ec26d4fca1f6031cc03 +Subproject commit 5d05a25a158eba8583ea9c4eefd161a54ffba986 From 0b431fd24047ec4916d4354365e2b4392544b2e9 Mon Sep 17 00:00:00 2001 From: Ross maxx Date: Fri, 7 Jul 2023 09:59:19 +0530 Subject: [PATCH 32/95] fix to swh --- plugins/LadspaEffect/swh/ladspa | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/LadspaEffect/swh/ladspa b/plugins/LadspaEffect/swh/ladspa index 5d05a25a158..ec24f04536e 160000 --- a/plugins/LadspaEffect/swh/ladspa +++ b/plugins/LadspaEffect/swh/ladspa @@ -1 +1 @@ -Subproject commit 5d05a25a158eba8583ea9c4eefd161a54ffba986 +Subproject commit ec24f04536ed1ee674beb9dc266f2e74afb53b2f From 06f5ebdebc053ef35ea87fd4469dc24717d27228 Mon Sep 17 00:00:00 2001 From: Ross maxx Date: Fri, 7 Jul 2023 14:09:51 +0530 Subject: [PATCH 33/95] removed the blacklist --- plugins/LadspaEffect/CMakeLists.txt | 5 ----- 1 file changed, 5 deletions(-) diff --git a/plugins/LadspaEffect/CMakeLists.txt b/plugins/LadspaEffect/CMakeLists.txt index 5528681c806..4e1c5f8eab0 100644 --- a/plugins/LadspaEffect/CMakeLists.txt +++ b/plugins/LadspaEffect/CMakeLists.txt @@ -12,14 +12,10 @@ IF(WANT_CMT) ADD_SUBDIRECTORY(cmt) ENDIF(WANT_CMT) - IF(WANT_SWH) ADD_SUBDIRECTORY(swh) ENDIF(WANT_SWH) -# temporary condition, will be removed -IF(NOT MSVC) - IF(WANT_TAP) ADD_SUBDIRECTORY(tap) ENDIF(WANT_TAP) @@ -28,4 +24,3 @@ IF(WANT_CALF) ADD_SUBDIRECTORY(calf) ENDIF(WANT_CALF) -ENDIF(NOT MSVC) From 529a57010c5c0825d85cd6dc631b99f6e593497e Mon Sep 17 00:00:00 2001 From: Ross maxx Date: Fri, 7 Jul 2023 14:43:43 +0530 Subject: [PATCH 34/95] added math defines to tap_utils.h --- plugins/LadspaEffect/calf/veal | 2 +- plugins/LadspaEffect/tap/tap-plugins | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/LadspaEffect/calf/veal b/plugins/LadspaEffect/calf/veal index fe628885b76..045bba75782 160000 --- a/plugins/LadspaEffect/calf/veal +++ b/plugins/LadspaEffect/calf/veal @@ -1 +1 @@ -Subproject commit fe628885b761372b37136a3f2b7c3d56e179e3ba +Subproject commit 045bba757822605551613fd6d568b615547c5895 diff --git a/plugins/LadspaEffect/tap/tap-plugins b/plugins/LadspaEffect/tap/tap-plugins index 198b84e6ab3..35fd42ba813 160000 --- a/plugins/LadspaEffect/tap/tap-plugins +++ b/plugins/LadspaEffect/tap/tap-plugins @@ -1 +1 @@ -Subproject commit 198b84e6ab37a9c979435cdb8f0a27a0e9a2934f +Subproject commit 35fd42ba81377dc5bdfb8135543179a32d1909cb From 301c25f08af230a588d4878f2db8fea1e5e26087 Mon Sep 17 00:00:00 2001 From: Ross maxx Date: Fri, 7 Jul 2023 14:50:51 +0530 Subject: [PATCH 35/95] added math defines to calf primitives.h --- plugins/LadspaEffect/calf/veal | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/LadspaEffect/calf/veal b/plugins/LadspaEffect/calf/veal index 045bba75782..7334c63e9d7 160000 --- a/plugins/LadspaEffect/calf/veal +++ b/plugins/LadspaEffect/calf/veal @@ -1 +1 @@ -Subproject commit 045bba757822605551613fd6d568b615547c5895 +Subproject commit 7334c63e9d77cc9102eafd548ce47388fa15f3e2 From 4bdd52fa0c49a049a0b7ff95daa0a1a03964a519 Mon Sep 17 00:00:00 2001 From: Ross maxx Date: Fri, 7 Jul 2023 14:54:38 +0530 Subject: [PATCH 36/95] added missing endif to calf's primitives.h --- plugins/LadspaEffect/calf/veal | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/LadspaEffect/calf/veal b/plugins/LadspaEffect/calf/veal index 7334c63e9d7..d93e8999fa7 160000 --- a/plugins/LadspaEffect/calf/veal +++ b/plugins/LadspaEffect/calf/veal @@ -1 +1 @@ -Subproject commit 7334c63e9d77cc9102eafd548ce47388fa15f3e2 +Subproject commit d93e8999fa78ee344a3e653bec7a14380d86f0f4 From 7478329fc4e3070e38a8bb09cc9cf1b2f303b1ff Mon Sep 17 00:00:00 2001 From: Ross maxx Date: Fri, 7 Jul 2023 15:11:36 +0530 Subject: [PATCH 37/95] added math defines to biquad.h --- plugins/LadspaEffect/calf/veal | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/LadspaEffect/calf/veal b/plugins/LadspaEffect/calf/veal index d93e8999fa7..398d0856c43 160000 --- a/plugins/LadspaEffect/calf/veal +++ b/plugins/LadspaEffect/calf/veal @@ -1 +1 @@ -Subproject commit d93e8999fa78ee344a3e653bec7a14380d86f0f4 +Subproject commit 398d0856c4329759fa211f79cade657e32b40a58 From ffe89943a4463aa06d811287a48de36644b15c6f Mon Sep 17 00:00:00 2001 From: Ross maxx Date: Fri, 7 Jul 2023 15:16:22 +0530 Subject: [PATCH 38/95] tweak in calf to get it built --- plugins/LadspaEffect/calf/veal | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/LadspaEffect/calf/veal b/plugins/LadspaEffect/calf/veal index 398d0856c43..93a832f8f43 160000 --- a/plugins/LadspaEffect/calf/veal +++ b/plugins/LadspaEffect/calf/veal @@ -1 +1 @@ -Subproject commit 398d0856c4329759fa211f79cade657e32b40a58 +Subproject commit 93a832f8f435176704385476ce803e9037bed9aa From fad75fd2da4ad6c84b84a08fccc0509eb342d40f Mon Sep 17 00:00:00 2001 From: Ross maxx Date: Fri, 7 Jul 2023 15:29:06 +0530 Subject: [PATCH 39/95] will it get fixed? --- plugins/LadspaEffect/calf/veal | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/LadspaEffect/calf/veal b/plugins/LadspaEffect/calf/veal index 93a832f8f43..74bfae90187 160000 --- a/plugins/LadspaEffect/calf/veal +++ b/plugins/LadspaEffect/calf/veal @@ -1 +1 @@ -Subproject commit 93a832f8f435176704385476ce803e9037bed9aa +Subproject commit 74bfae9018712d74ad1c541a4e0de2402af0b167 From 03212694857d3448e344c68859003e1b992a2324 Mon Sep 17 00:00:00 2001 From: Ross maxx Date: Fri, 7 Jul 2023 15:38:34 +0530 Subject: [PATCH 40/95] final patch to fix i guess --- plugins/LadspaEffect/calf/veal | 2 +- plugins/LadspaEffect/tap/tap-plugins | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/LadspaEffect/calf/veal b/plugins/LadspaEffect/calf/veal index 74bfae90187..83f37f8acca 160000 --- a/plugins/LadspaEffect/calf/veal +++ b/plugins/LadspaEffect/calf/veal @@ -1 +1 @@ -Subproject commit 74bfae9018712d74ad1c541a4e0de2402af0b167 +Subproject commit 83f37f8accad5e8bd89be92236eed8c253d017e0 diff --git a/plugins/LadspaEffect/tap/tap-plugins b/plugins/LadspaEffect/tap/tap-plugins index 35fd42ba813..fd798c914bf 160000 --- a/plugins/LadspaEffect/tap/tap-plugins +++ b/plugins/LadspaEffect/tap/tap-plugins @@ -1 +1 @@ -Subproject commit 35fd42ba81377dc5bdfb8135543179a32d1909cb +Subproject commit fd798c914bfe9f7130f1a8aa1803b4fd3083a317 From 6242930e00f06479861fc8553274ea191ca68991 Mon Sep 17 00:00:00 2001 From: Ross maxx Date: Sat, 8 Jul 2023 14:44:37 +0530 Subject: [PATCH 41/95] added declspec to tap --- plugins/LadspaEffect/tap/tap-plugins | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/LadspaEffect/tap/tap-plugins b/plugins/LadspaEffect/tap/tap-plugins index fd798c914bf..6d5994c017e 160000 --- a/plugins/LadspaEffect/tap/tap-plugins +++ b/plugins/LadspaEffect/tap/tap-plugins @@ -1 +1 @@ -Subproject commit fd798c914bfe9f7130f1a8aa1803b4fd3083a317 +Subproject commit 6d5994c017e927be45a343e1c92c681390606e32 From c0ad959d10ffc2712bd180e7df9173145962b7da Mon Sep 17 00:00:00 2001 From: Ross maxx Date: Sat, 8 Jul 2023 15:21:30 +0530 Subject: [PATCH 42/95] added the blacklist back in for calf --- plugins/LadspaEffect/CMakeLists.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/plugins/LadspaEffect/CMakeLists.txt b/plugins/LadspaEffect/CMakeLists.txt index 4e1c5f8eab0..df000d80e16 100644 --- a/plugins/LadspaEffect/CMakeLists.txt +++ b/plugins/LadspaEffect/CMakeLists.txt @@ -20,7 +20,11 @@ IF(WANT_TAP) ADD_SUBDIRECTORY(tap) ENDIF(WANT_TAP) +IF(NOT MSVC) + IF(WANT_CALF) ADD_SUBDIRECTORY(calf) ENDIF(WANT_CALF) +ENDIF(NOT MSVC) + From 3637a1f47d8ccafe2bc6e036c7201fe8ac74b21b Mon Sep 17 00:00:00 2001 From: Ross maxx Date: Sat, 8 Jul 2023 15:23:34 +0530 Subject: [PATCH 43/95] updated swh to upstream --- plugins/LadspaEffect/swh/ladspa | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/LadspaEffect/swh/ladspa b/plugins/LadspaEffect/swh/ladspa index ec24f04536e..02bda232041 160000 --- a/plugins/LadspaEffect/swh/ladspa +++ b/plugins/LadspaEffect/swh/ladspa @@ -1 +1 @@ -Subproject commit ec24f04536ed1ee674beb9dc266f2e74afb53b2f +Subproject commit 02bda232041380c2846414945798cbbfecb2f3f2 From 7e9a73435f9f92ed5dfc303645919278ebaa6deb Mon Sep 17 00:00:00 2001 From: Ross maxx Date: Sat, 8 Jul 2023 15:29:20 +0530 Subject: [PATCH 44/95] updated cmt to master --- plugins/LadspaEffect/cmt/cmt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/LadspaEffect/cmt/cmt b/plugins/LadspaEffect/cmt/cmt index 99b0fd3d4b3..56f84d44b9b 160000 --- a/plugins/LadspaEffect/cmt/cmt +++ b/plugins/LadspaEffect/cmt/cmt @@ -1 +1 @@ -Subproject commit 99b0fd3d4b32056263a0a997dc68ca5897ad76b0 +Subproject commit 56f84d44b9bcfd8e797c9510fd19e1cfb73ec21e From 91f3b773cb7b6c5d386c94654b8db531d191a4c7 Mon Sep 17 00:00:00 2001 From: Ross maxx Date: Sat, 8 Jul 2023 15:30:01 +0530 Subject: [PATCH 45/95] pulled to cmt to master try 2 --- plugins/LadspaEffect/cmt/cmt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/LadspaEffect/cmt/cmt b/plugins/LadspaEffect/cmt/cmt index 56f84d44b9b..6e6e291fbad 160000 --- a/plugins/LadspaEffect/cmt/cmt +++ b/plugins/LadspaEffect/cmt/cmt @@ -1 +1 @@ -Subproject commit 56f84d44b9bcfd8e797c9510fd19e1cfb73ec21e +Subproject commit 6e6e291fbad1138c808860ba3f140a963b52fa58 From dd3d3b9bad04414c0ea80f76d78da03437107a28 Mon Sep 17 00:00:00 2001 From: Ross maxx Date: Mon, 10 Jul 2023 17:21:33 +0530 Subject: [PATCH 46/95] remove blacklist + experimnt removing declspec tap --- plugins/LadspaEffect/CMakeLists.txt | 3 --- plugins/LadspaEffect/tap/tap-plugins | 2 +- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/plugins/LadspaEffect/CMakeLists.txt b/plugins/LadspaEffect/CMakeLists.txt index df000d80e16..7e20ed47e6c 100644 --- a/plugins/LadspaEffect/CMakeLists.txt +++ b/plugins/LadspaEffect/CMakeLists.txt @@ -20,11 +20,8 @@ IF(WANT_TAP) ADD_SUBDIRECTORY(tap) ENDIF(WANT_TAP) -IF(NOT MSVC) - IF(WANT_CALF) ADD_SUBDIRECTORY(calf) ENDIF(WANT_CALF) -ENDIF(NOT MSVC) diff --git a/plugins/LadspaEffect/tap/tap-plugins b/plugins/LadspaEffect/tap/tap-plugins index 6d5994c017e..fed7d76b673 160000 --- a/plugins/LadspaEffect/tap/tap-plugins +++ b/plugins/LadspaEffect/tap/tap-plugins @@ -1 +1 @@ -Subproject commit 6d5994c017e927be45a343e1c92c681390606e32 +Subproject commit fed7d76b673e3943c512fc9368a31c191f02ddec From cbebc0762ef2a972b7c5ac2f502ae99d36713637 Mon Sep 17 00:00:00 2001 From: Ross maxx Date: Mon, 10 Jul 2023 17:54:58 +0530 Subject: [PATCH 47/95] experiment replacing bind2nd with lambdas veal --- plugins/LadspaEffect/calf/veal | 2 +- plugins/LadspaEffect/tap/tap-plugins | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/LadspaEffect/calf/veal b/plugins/LadspaEffect/calf/veal index 83f37f8acca..0e7b7d53d4f 160000 --- a/plugins/LadspaEffect/calf/veal +++ b/plugins/LadspaEffect/calf/veal @@ -1 +1 @@ -Subproject commit 83f37f8accad5e8bd89be92236eed8c253d017e0 +Subproject commit 0e7b7d53d4fd8229b16d227566ee9a3526116f0d diff --git a/plugins/LadspaEffect/tap/tap-plugins b/plugins/LadspaEffect/tap/tap-plugins index fed7d76b673..e53e7645b33 160000 --- a/plugins/LadspaEffect/tap/tap-plugins +++ b/plugins/LadspaEffect/tap/tap-plugins @@ -1 +1 @@ -Subproject commit fed7d76b673e3943c512fc9368a31c191f02ddec +Subproject commit e53e7645b3308867a40f3b78f64ca8df36863b8c From 8f52dba32733855d9fed064e193573fd17258bd9 Mon Sep 17 00:00:00 2001 From: Ross maxx Date: Mon, 10 Jul 2023 18:15:47 +0530 Subject: [PATCH 48/95] removed functional.h in veal --- plugins/LadspaEffect/calf/veal | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/LadspaEffect/calf/veal b/plugins/LadspaEffect/calf/veal index 0e7b7d53d4f..28e58a0542d 160000 --- a/plugins/LadspaEffect/calf/veal +++ b/plugins/LadspaEffect/calf/veal @@ -1 +1 @@ -Subproject commit 0e7b7d53d4fd8229b16d227566ee9a3526116f0d +Subproject commit 28e58a0542d886c8eed63852d875547900340cac From bd657a58c21dc7ebbc4c0e81097e7e3ea8f929af Mon Sep 17 00:00:00 2001 From: Ross maxx Date: Wed, 12 Jul 2023 18:25:31 +0530 Subject: [PATCH 49/95] checked out to ladspa branch --- plugins/LadspaEffect/calf/veal | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/LadspaEffect/calf/veal b/plugins/LadspaEffect/calf/veal index 28e58a0542d..188b75111df 160000 --- a/plugins/LadspaEffect/calf/veal +++ b/plugins/LadspaEffect/calf/veal @@ -1 +1 @@ -Subproject commit 28e58a0542d886c8eed63852d875547900340cac +Subproject commit 188b75111df513567ad1e2ff2dfd5826460e907c From 19e96aa6094a9cdbfaeef7a1bcfae842724fb701 Mon Sep 17 00:00:00 2001 From: Ross maxx Date: Wed, 12 Jul 2023 18:46:43 +0530 Subject: [PATCH 50/95] fixed veal --- plugins/LadspaEffect/calf/veal | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/LadspaEffect/calf/veal b/plugins/LadspaEffect/calf/veal index 188b75111df..aaea579b7c4 160000 --- a/plugins/LadspaEffect/calf/veal +++ b/plugins/LadspaEffect/calf/veal @@ -1 +1 @@ -Subproject commit 188b75111df513567ad1e2ff2dfd5826460e907c +Subproject commit aaea579b7c403a55f930b82c2f705e46ad989c67 From 7c6bbd25aa6b6906c579396a3611f5cf99c5be99 Mon Sep 17 00:00:00 2001 From: Ross maxx Date: Wed, 12 Jul 2023 21:54:41 +0530 Subject: [PATCH 51/95] attempt fix at mac builds --- plugins/LadspaEffect/swh/CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/LadspaEffect/swh/CMakeLists.txt b/plugins/LadspaEffect/swh/CMakeLists.txt index ece3070060b..7b5d24828dc 100644 --- a/plugins/LadspaEffect/swh/CMakeLists.txt +++ b/plugins/LadspaEffect/swh/CMakeLists.txt @@ -24,6 +24,7 @@ FOREACH(_item ${XML_SOURCES}) # Coerce XML source file to C ADD_CUSTOM_COMMAND( OUTPUT "${_out_file}" + COMMAND cpan List:MoreUtils COMMAND perl ./makestub.pl "${_item}" > "${_out_file}" DEPENDS "${_item}" WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/ladspa" From 2923971887a20732daf32b0b7c6a2146ef7d9048 Mon Sep 17 00:00:00 2001 From: Ross maxx Date: Wed, 12 Jul 2023 22:59:37 +0530 Subject: [PATCH 52/95] Revert "attempt fix at mac builds" This reverts commit 7c6bbd25aa6b6906c579396a3611f5cf99c5be99. --- plugins/LadspaEffect/swh/CMakeLists.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/plugins/LadspaEffect/swh/CMakeLists.txt b/plugins/LadspaEffect/swh/CMakeLists.txt index 7b5d24828dc..ece3070060b 100644 --- a/plugins/LadspaEffect/swh/CMakeLists.txt +++ b/plugins/LadspaEffect/swh/CMakeLists.txt @@ -24,7 +24,6 @@ FOREACH(_item ${XML_SOURCES}) # Coerce XML source file to C ADD_CUSTOM_COMMAND( OUTPUT "${_out_file}" - COMMAND cpan List:MoreUtils COMMAND perl ./makestub.pl "${_item}" > "${_out_file}" DEPENDS "${_item}" WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/ladspa" From 4cba33a9d04d4666285a0dc17e5b4957f0b252fa Mon Sep 17 00:00:00 2001 From: Rossmaxx <74815851+Rossmaxx@users.noreply.github.com> Date: Wed, 12 Jul 2023 23:20:22 +0530 Subject: [PATCH 53/95] added list-moreutils to build.yml --- .github/workflows/build.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 007842b82ad..3240aaa4b59 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -93,6 +93,7 @@ jobs: libsamplerate jack sdl libgig libsoundio lilv lv2 stk \ fluid-synth portaudio fltk qt@5 carla npm install --location=global appdmg + cpan List::MoreUtils - name: Configure run: | mkdir build From b4e77152bef0470e3ad1411a1a31ae83c6819a63 Mon Sep 17 00:00:00 2001 From: Tres Finocchiaro Date: Thu, 13 Jul 2023 00:05:10 -0400 Subject: [PATCH 54/95] Attempt to fix MacOS builds --- .github/workflows/build.yml | 1 - plugins/LadspaEffect/swh/CMakeLists.txt | 10 +++++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 3240aaa4b59..007842b82ad 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -93,7 +93,6 @@ jobs: libsamplerate jack sdl libgig libsoundio lilv lv2 stk \ fluid-synth portaudio fltk qt@5 carla npm install --location=global appdmg - cpan List::MoreUtils - name: Configure run: | mkdir build diff --git a/plugins/LadspaEffect/swh/CMakeLists.txt b/plugins/LadspaEffect/swh/CMakeLists.txt index ece3070060b..873e1b629b7 100644 --- a/plugins/LadspaEffect/swh/CMakeLists.txt +++ b/plugins/LadspaEffect/swh/CMakeLists.txt @@ -16,6 +16,14 @@ SET(COMPILE_FLAGS "${COMPILE_FLAGS} ${PIC_FLAGS}") # Loop over every XML file FILE(GLOB XML_SOURCES "${CMAKE_CURRENT_SOURCE_DIR}/ladspa/*.xml") LIST(SORT XML_SOURCES) + +IF(LMMS_BUILD_MACOS) + # Prefer system perl over Homebrew, MacPorts, etc + SET(PERL_COMMAND "/usr/bin/perl") +ELSE() + SET(PERL_COMMAND "perl") +ENDIF() + FOREACH(_item ${XML_SOURCES}) # Get library name and (soon to be) C file GET_FILENAME_COMPONENT(_plugin "${_item}" NAME_WE) @@ -24,7 +32,7 @@ FOREACH(_item ${XML_SOURCES}) # Coerce XML source file to C ADD_CUSTOM_COMMAND( OUTPUT "${_out_file}" - COMMAND perl ./makestub.pl "${_item}" > "${_out_file}" + COMMAND ${PERL_COMMAND} ./makestub.pl "${_item}" > "${_out_file}" DEPENDS "${_item}" WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/ladspa" VERBATIM From 384aa24f536b1ac2b016132d73562f1114958e61 Mon Sep 17 00:00:00 2001 From: Tres Finocchiaro Date: Thu, 13 Jul 2023 00:22:05 -0400 Subject: [PATCH 55/95] Fix typo --- plugins/LadspaEffect/swh/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/LadspaEffect/swh/CMakeLists.txt b/plugins/LadspaEffect/swh/CMakeLists.txt index 873e1b629b7..fef80debd8e 100644 --- a/plugins/LadspaEffect/swh/CMakeLists.txt +++ b/plugins/LadspaEffect/swh/CMakeLists.txt @@ -17,7 +17,7 @@ SET(COMPILE_FLAGS "${COMPILE_FLAGS} ${PIC_FLAGS}") FILE(GLOB XML_SOURCES "${CMAKE_CURRENT_SOURCE_DIR}/ladspa/*.xml") LIST(SORT XML_SOURCES) -IF(LMMS_BUILD_MACOS) +IF(LMMS_BUILD_APPLE) # Prefer system perl over Homebrew, MacPorts, etc SET(PERL_COMMAND "/usr/bin/perl") ELSE() From da019b72ca4df806672830f2cc28f84a86399c3e Mon Sep 17 00:00:00 2001 From: Tres Finocchiaro Date: Thu, 13 Jul 2023 00:40:33 -0400 Subject: [PATCH 56/95] Formatting --- plugins/LadspaEffect/CMakeLists.txt | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/plugins/LadspaEffect/CMakeLists.txt b/plugins/LadspaEffect/CMakeLists.txt index 7e20ed47e6c..ea298160517 100644 --- a/plugins/LadspaEffect/CMakeLists.txt +++ b/plugins/LadspaEffect/CMakeLists.txt @@ -8,20 +8,18 @@ IF(WANT_CAPS) ADD_SUBDIRECTORY(caps) ENDIF(WANT_CAPS) -IF(WANT_CMT) -ADD_SUBDIRECTORY(cmt) -ENDIF(WANT_CMT) +IF(WANT_TAP) +ADD_SUBDIRECTORY(tap) +ENDIF(WANT_TAP) IF(WANT_SWH) ADD_SUBDIRECTORY(swh) ENDIF(WANT_SWH) -IF(WANT_TAP) -ADD_SUBDIRECTORY(tap) -ENDIF(WANT_TAP) +IF(WANT_CMT) +ADD_SUBDIRECTORY(cmt) +ENDIF(WANT_CMT) IF(WANT_CALF) ADD_SUBDIRECTORY(calf) -ENDIF(WANT_CALF) - - +ENDIF(WANT_CALF) \ No newline at end of file From 329613b3d4a4827e3660a395b02bc491f9833a36 Mon Sep 17 00:00:00 2001 From: Tres Finocchiaro Date: Thu, 13 Jul 2023 00:41:35 -0400 Subject: [PATCH 57/95] Formatting (again) --- plugins/LadspaEffect/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/LadspaEffect/CMakeLists.txt b/plugins/LadspaEffect/CMakeLists.txt index ea298160517..951615ad4d0 100644 --- a/plugins/LadspaEffect/CMakeLists.txt +++ b/plugins/LadspaEffect/CMakeLists.txt @@ -22,4 +22,4 @@ ENDIF(WANT_CMT) IF(WANT_CALF) ADD_SUBDIRECTORY(calf) -ENDIF(WANT_CALF) \ No newline at end of file +ENDIF(WANT_CALF) From d6b4f82be5546f22f313af9f71a4ae8628c2c2e9 Mon Sep 17 00:00:00 2001 From: Ross maxx Date: Thu, 13 Jul 2023 18:59:36 +0530 Subject: [PATCH 58/95] addressed dom's pending comment --- plugins/LadspaEffect/caps/basics.h | 11 +++++++---- plugins/LadspaEffect/caps/dsp/Eq.h | 8 +++----- plugins/LadspaEffect/caps/dsp/OnePole.h | 7 ++----- plugins/LadspaEffect/caps/dsp/SVF.h | 5 ++--- plugins/LadspaEffect/caps/dsp/Sine.h | 5 ++--- 5 files changed, 16 insertions(+), 20 deletions(-) diff --git a/plugins/LadspaEffect/caps/basics.h b/plugins/LadspaEffect/caps/basics.h index df24e8c05ed..759f6469023 100644 --- a/plugins/LadspaEffect/caps/basics.h +++ b/plugins/LadspaEffect/caps/basics.h @@ -38,14 +38,17 @@ #define _ISOC99_SOURCE 1 #define _ISOC9X_SOURCE 1 +#include +#include +#include #include #include +#ifndef _USE_MATH_DEFINES +#define _USE_MATH_DEFINES +#endif #include -#include -#include -#include #include @@ -76,7 +79,7 @@ #define MIN_GAIN .000001 /* -120 dB */ -/* smallest non-denormal 32 bit IEEE float is 1.18×10-38 */ +/* smallest non-denormal 32 bit IEEE float is 1.18�10-38 */ #define NOISE_FLOOR .00000000000005 /* -266 dB */ typedef int8_t int8; diff --git a/plugins/LadspaEffect/caps/dsp/Eq.h b/plugins/LadspaEffect/caps/dsp/Eq.h index 3a6c89a3042..54fe3037b4b 100644 --- a/plugins/LadspaEffect/caps/dsp/Eq.h +++ b/plugins/LadspaEffect/caps/dsp/Eq.h @@ -26,13 +26,11 @@ 02111-1307, USA or point your web browser to http://www.gnu.org. */ -#ifndef _DSP_EQ_H_ -#define _DSP_EQ_H_ +#include "basics.h" -#ifndef M_PI -#define M_PI 3.14159265358979323846264338327 -#endif +#ifndef _DSP_EQ_H_ +#define _DSP_EQ_H_ namespace DSP { diff --git a/plugins/LadspaEffect/caps/dsp/OnePole.h b/plugins/LadspaEffect/caps/dsp/OnePole.h index 81abcb6ff26..a257b76945f 100644 --- a/plugins/LadspaEffect/caps/dsp/OnePole.h +++ b/plugins/LadspaEffect/caps/dsp/OnePole.h @@ -25,14 +25,11 @@ 02111-1307, USA or point your web browser to http://www.gnu.org. */ +#include "basics.h" + #ifndef _ONE_POLE_H_ #define _ONE_POLE_H_ - -#ifndef M_PI -#define M_PI 3.14159265358979323846264338327 -#endif - namespace DSP { class OnePoleLP diff --git a/plugins/LadspaEffect/caps/dsp/SVF.h b/plugins/LadspaEffect/caps/dsp/SVF.h index 31528e8aadf..f90740af930 100644 --- a/plugins/LadspaEffect/caps/dsp/SVF.h +++ b/plugins/LadspaEffect/caps/dsp/SVF.h @@ -70,12 +70,11 @@ } */ +#include "basics.h" + #ifndef _DSP_SVF_H_ #define _DSP_SVF_H_ -#ifndef M_PI -#define M_PI 3.14159265358979323846264338327 -#endif namespace DSP { diff --git a/plugins/LadspaEffect/caps/dsp/Sine.h b/plugins/LadspaEffect/caps/dsp/Sine.h index 95f225b446f..e8d99d404c0 100644 --- a/plugins/LadspaEffect/caps/dsp/Sine.h +++ b/plugins/LadspaEffect/caps/dsp/Sine.h @@ -25,12 +25,11 @@ 02111-1307, USA or point your web browser to http://www.gnu.org. */ +#include "basics.h" + #ifndef _DSP_SINE_H_ #define _DSP_SINE_H_ -#ifndef M_PI -#define M_PI 3.14159265358979323846264338327 -#endif namespace DSP { From 01eaa19976a5860a97ffe27dc5f4674fa3f78575 Mon Sep 17 00:00:00 2001 From: Ross maxx Date: Thu, 13 Jul 2023 19:12:31 +0530 Subject: [PATCH 59/95] fixed build issues --- plugins/LadspaEffect/caps/dsp/Eq.h | 2 +- plugins/LadspaEffect/caps/dsp/OnePole.h | 2 +- plugins/LadspaEffect/caps/dsp/SVF.h | 2 +- plugins/LadspaEffect/caps/dsp/Sine.h | 3 +-- 4 files changed, 4 insertions(+), 5 deletions(-) diff --git a/plugins/LadspaEffect/caps/dsp/Eq.h b/plugins/LadspaEffect/caps/dsp/Eq.h index 54fe3037b4b..1f3ed5eef84 100644 --- a/plugins/LadspaEffect/caps/dsp/Eq.h +++ b/plugins/LadspaEffect/caps/dsp/Eq.h @@ -27,7 +27,7 @@ */ -#include "basics.h" +#include "../basics.h" #ifndef _DSP_EQ_H_ #define _DSP_EQ_H_ diff --git a/plugins/LadspaEffect/caps/dsp/OnePole.h b/plugins/LadspaEffect/caps/dsp/OnePole.h index a257b76945f..a53ec9fc8f9 100644 --- a/plugins/LadspaEffect/caps/dsp/OnePole.h +++ b/plugins/LadspaEffect/caps/dsp/OnePole.h @@ -25,7 +25,7 @@ 02111-1307, USA or point your web browser to http://www.gnu.org. */ -#include "basics.h" +#include "../basics.h" #ifndef _ONE_POLE_H_ #define _ONE_POLE_H_ diff --git a/plugins/LadspaEffect/caps/dsp/SVF.h b/plugins/LadspaEffect/caps/dsp/SVF.h index f90740af930..fe49643a93b 100644 --- a/plugins/LadspaEffect/caps/dsp/SVF.h +++ b/plugins/LadspaEffect/caps/dsp/SVF.h @@ -70,7 +70,7 @@ } */ -#include "basics.h" +#include "../basics.h" #ifndef _DSP_SVF_H_ #define _DSP_SVF_H_ diff --git a/plugins/LadspaEffect/caps/dsp/Sine.h b/plugins/LadspaEffect/caps/dsp/Sine.h index e8d99d404c0..47477334e99 100644 --- a/plugins/LadspaEffect/caps/dsp/Sine.h +++ b/plugins/LadspaEffect/caps/dsp/Sine.h @@ -25,12 +25,11 @@ 02111-1307, USA or point your web browser to http://www.gnu.org. */ -#include "basics.h" +#include "../basics.h" #ifndef _DSP_SINE_H_ #define _DSP_SINE_H_ - namespace DSP { class Sine From 12a13007ddd83d229c33b14120befcc2b3e33d56 Mon Sep 17 00:00:00 2001 From: Tres Finocchiaro Date: Sun, 16 Jul 2023 02:09:19 -0400 Subject: [PATCH 60/95] CMake perl detection --- CMakeLists.txt | 16 +++++++++++++--- plugins/LadspaEffect/swh/CMakeLists.txt | 9 +-------- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index c5de064a54a..eb8ccb98e00 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,4 @@ -CMAKE_MINIMUM_REQUIRED(VERSION 3.9) +CMAKE_MINIMUM_REQUIRED(VERSION 3.12) PROJECT(lmms) @@ -16,6 +16,7 @@ IF(COMMAND CMAKE_POLICY) ENDIF() CMAKE_POLICY(SET CMP0020 NEW) CMAKE_POLICY(SET CMP0057 NEW) + CMAKE_POLICY(SET CMP0074 NEW) ENDIF(COMMAND CMAKE_POLICY) @@ -273,8 +274,17 @@ ELSE(WANT_CMT) ENDIF(WANT_CMT) IF(WANT_SWH) - SET(LMMS_HAVE_SWH TRUE) - SET(STATUS_SWH "OK") + 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") + ELSE() + SET(STATUS_SWH "Skipping, perl is missing") + ENDIF() ELSE(WANT_SWH) SET(STATUS_SWH "not built as requested") ENDIF(WANT_SWH) diff --git a/plugins/LadspaEffect/swh/CMakeLists.txt b/plugins/LadspaEffect/swh/CMakeLists.txt index fef80debd8e..20e4b4d70bf 100644 --- a/plugins/LadspaEffect/swh/CMakeLists.txt +++ b/plugins/LadspaEffect/swh/CMakeLists.txt @@ -17,13 +17,6 @@ SET(COMPILE_FLAGS "${COMPILE_FLAGS} ${PIC_FLAGS}") FILE(GLOB XML_SOURCES "${CMAKE_CURRENT_SOURCE_DIR}/ladspa/*.xml") LIST(SORT XML_SOURCES) -IF(LMMS_BUILD_APPLE) - # Prefer system perl over Homebrew, MacPorts, etc - SET(PERL_COMMAND "/usr/bin/perl") -ELSE() - SET(PERL_COMMAND "perl") -ENDIF() - FOREACH(_item ${XML_SOURCES}) # Get library name and (soon to be) C file GET_FILENAME_COMPONENT(_plugin "${_item}" NAME_WE) @@ -32,7 +25,7 @@ FOREACH(_item ${XML_SOURCES}) # Coerce XML source file to C ADD_CUSTOM_COMMAND( OUTPUT "${_out_file}" - COMMAND ${PERL_COMMAND} ./makestub.pl "${_item}" > "${_out_file}" + COMMAND ${PERL_EXECUTABLE} ./makestub.pl "${_item}" > "${_out_file}" DEPENDS "${_item}" WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/ladspa" VERBATIM From 91355ae89a4e8f13edc98741b6a9f11ed5b6ac22 Mon Sep 17 00:00:00 2001 From: Tres Finocchiaro Date: Sun, 16 Jul 2023 02:14:25 -0400 Subject: [PATCH 61/95] Don't mandate cmake 3.12 because mingw --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index eb8ccb98e00..2bc0223f1cb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,4 @@ -CMAKE_MINIMUM_REQUIRED(VERSION 3.12) +CMAKE_MINIMUM_REQUIRED(VERSION 3.9) PROJECT(lmms) From 2ac60365c66a1803b355de1b04b0c7d72f2ed4d7 Mon Sep 17 00:00:00 2001 From: Ross maxx Date: Sun, 16 Jul 2023 12:13:22 +0530 Subject: [PATCH 62/95] fixed broken unicode symbol in caps/basics.h --- plugins/LadspaEffect/caps/basics.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/LadspaEffect/caps/basics.h b/plugins/LadspaEffect/caps/basics.h index 759f6469023..4a82ae93550 100644 --- a/plugins/LadspaEffect/caps/basics.h +++ b/plugins/LadspaEffect/caps/basics.h @@ -79,7 +79,7 @@ #define MIN_GAIN .000001 /* -120 dB */ -/* smallest non-denormal 32 bit IEEE float is 1.18�10-38 */ +/* smallest non-denormal 32 bit IEEE float is 1.18*10-38 */ #define NOISE_FLOOR .00000000000005 /* -266 dB */ typedef int8_t int8; From 881279092e9de9cdfdc91badb00b7db4b95b7b81 Mon Sep 17 00:00:00 2001 From: Ross maxx Date: Sun, 16 Jul 2023 12:14:56 +0530 Subject: [PATCH 63/95] got rid of unnecessary __attribute__(s) --- plugins/LadspaEffect/caps/interface.cc | 6 ------ 1 file changed, 6 deletions(-) diff --git a/plugins/LadspaEffect/caps/interface.cc b/plugins/LadspaEffect/caps/interface.cc index 5786b27917e..aff95f255c0 100644 --- a/plugins/LadspaEffect/caps/interface.cc +++ b/plugins/LadspaEffect/caps/interface.cc @@ -58,9 +58,6 @@ static DescriptorStub * descriptors [N]; extern "C" { -#ifdef __GNUC__ - __attribute__((constructor)) -#endif void caps_so_init() { DescriptorStub ** d = descriptors; @@ -114,9 +111,6 @@ void caps_so_init() assert (d - descriptors == N); } -#ifdef __GNUC__ - __attribute__((destructor)) -#endif void caps_so_fini() { for (ulong i = 0; i < N; ++i) From 0e6d02e6f946faf99b678523cde037f0e7de37ef Mon Sep 17 00:00:00 2001 From: Ross maxx Date: Sun, 16 Jul 2023 12:20:01 +0530 Subject: [PATCH 64/95] updated tap plugins to master --- plugins/LadspaEffect/tap/tap-plugins | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/LadspaEffect/tap/tap-plugins b/plugins/LadspaEffect/tap/tap-plugins index e53e7645b33..85640223047 160000 --- a/plugins/LadspaEffect/tap/tap-plugins +++ b/plugins/LadspaEffect/tap/tap-plugins @@ -1 +1 @@ -Subproject commit e53e7645b3308867a40f3b78f64ca8df36863b8c +Subproject commit 85640223047d49a305e90ba1b92303eb066ba474 From 249b3325531af9d017a2d5fe7d10379b23fb6720 Mon Sep 17 00:00:00 2001 From: Tres Finocchiaro Date: Sun, 16 Jul 2023 10:25:16 -0400 Subject: [PATCH 65/95] Fix older CMake versions --- CMakeLists.txt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 2bc0223f1cb..f12cc6563c8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -16,7 +16,10 @@ IF(COMMAND CMAKE_POLICY) ENDIF() CMAKE_POLICY(SET CMP0020 NEW) CMAKE_POLICY(SET CMP0057 NEW) - CMAKE_POLICY(SET CMP0074 NEW) + # TODO: Keep CMP0074 but remove this condition when cmake 3.12+ is guaranteed + IF(${CMAKE_VERSION} VERSION_GREATER_EQUAL 3.12) + CMAKE_POLICY(SET CMP0074 NEW) + ENDIF() ENDIF(COMMAND CMAKE_POLICY) From edfb6ef3b76e695a29fe092277e863e3281e1037 Mon Sep 17 00:00:00 2001 From: dan-giddins Date: Sat, 22 Jul 2023 12:07:21 +0100 Subject: [PATCH 66/95] .gitignore --- .gitignore | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.gitignore b/.gitignore index ee289379f0e..c6ab59cff09 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,8 @@ /plugins/ZynAddSubFx/zynaddsubfx/doc/Makefile /plugins/ZynAddSubFx/zynaddsubfx/doc/gen/Makefile /data/locale/*.qm +*.vsidx +*.sqlite +*.lock +*.sqlite-journal +.vs/VSWorkspaceState.json From dae9fbd7f94034e5b20f6d7bdae62dbfbf5b5f93 Mon Sep 17 00:00:00 2001 From: dan-giddins Date: Sat, 22 Jul 2023 12:27:52 +0100 Subject: [PATCH 67/95] undo gitignore --- .gitignore | 5 ----- 1 file changed, 5 deletions(-) diff --git a/.gitignore b/.gitignore index c6ab59cff09..ee289379f0e 100644 --- a/.gitignore +++ b/.gitignore @@ -9,8 +9,3 @@ /plugins/ZynAddSubFx/zynaddsubfx/doc/Makefile /plugins/ZynAddSubFx/zynaddsubfx/doc/gen/Makefile /data/locale/*.qm -*.vsidx -*.sqlite -*.lock -*.sqlite-journal -.vs/VSWorkspaceState.json From e9bb1a2fb269fb3386d20cc0743b884e0881d234 Mon Sep 17 00:00:00 2001 From: Tres Finocchiaro Date: Sat, 22 Jul 2023 18:38:20 -0400 Subject: [PATCH 68/95] Update plugins/LadspaEffect/swh/CMakeLists.txt Co-authored-by: Dominic Clark --- plugins/LadspaEffect/swh/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/LadspaEffect/swh/CMakeLists.txt b/plugins/LadspaEffect/swh/CMakeLists.txt index 20e4b4d70bf..a8300117735 100644 --- a/plugins/LadspaEffect/swh/CMakeLists.txt +++ b/plugins/LadspaEffect/swh/CMakeLists.txt @@ -25,7 +25,7 @@ FOREACH(_item ${XML_SOURCES}) # Coerce XML source file to C ADD_CUSTOM_COMMAND( OUTPUT "${_out_file}" - COMMAND ${PERL_EXECUTABLE} ./makestub.pl "${_item}" > "${_out_file}" + COMMAND "${PERL_EXECUTABLE}" ./makestub.pl "${_item}" > "${_out_file}" DEPENDS "${_item}" WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/ladspa" VERBATIM From 5fc18bc33b2bf6a6b1fa1d3be2e78f39d6e03911 Mon Sep 17 00:00:00 2001 From: Tres Finocchiaro Date: Sat, 22 Jul 2023 18:38:43 -0400 Subject: [PATCH 69/95] Update plugins/LadspaEffect/caps/CMakeLists.txt Co-authored-by: Dominic Clark --- plugins/LadspaEffect/caps/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/LadspaEffect/caps/CMakeLists.txt b/plugins/LadspaEffect/caps/CMakeLists.txt index bb7f19152c8..456b2711a50 100644 --- a/plugins/LadspaEffect/caps/CMakeLists.txt +++ b/plugins/LadspaEffect/caps/CMakeLists.txt @@ -10,7 +10,7 @@ SET_TARGET_PROPERTIES(caps PROPERTIES PREFIX "") IF (NOT MSVC) SET_TARGET_PROPERTIES(caps PROPERTIES COMPILE_FLAGS "-O2 -funroll-loops -Wno-write-strings") -ENDIF(NOT MSVC) +ENDIF() IF(LMMS_BUILD_WIN32) add_custom_command( From c24606b399b39b7295e8a3c6aae06df00844fd00 Mon Sep 17 00:00:00 2001 From: Tres Finocchiaro Date: Sun, 23 Jul 2023 15:01:17 -0400 Subject: [PATCH 70/95] Update CMakeLists.txt Co-authored-by: Dominic Clark --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index f12cc6563c8..eeb38d66b34 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -18,7 +18,7 @@ IF(COMMAND CMAKE_POLICY) CMAKE_POLICY(SET CMP0057 NEW) # TODO: Keep CMP0074 but remove this condition when cmake 3.12+ is guaranteed IF(${CMAKE_VERSION} VERSION_GREATER_EQUAL 3.12) - CMAKE_POLICY(SET CMP0074 NEW) + CMAKE_POLICY(SET CMP0074 NEW) # find_package() uses _ROOT variables ENDIF() ENDIF(COMMAND CMAKE_POLICY) From 4840c0b294807344c2baf9b1fdb734d710775691 Mon Sep 17 00:00:00 2001 From: dan-giddins Date: Sun, 6 Aug 2023 14:47:13 +0100 Subject: [PATCH 71/95] add cmakesettings --- CMakeSettings.json | 127 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 127 insertions(+) create mode 100644 CMakeSettings.json diff --git a/CMakeSettings.json b/CMakeSettings.json new file mode 100644 index 00000000000..b9b801428a1 --- /dev/null +++ b/CMakeSettings.json @@ -0,0 +1,127 @@ +{ + "configurations": [ + { + "name": "x64-Debug", + "generator": "Ninja", + "configurationType": "Debug", + "inheritEnvironments": [ "msvc_x64_x64" ], + "buildRoot": "${projectDir}\\out\\build\\${name}", + "installRoot": "${projectDir}\\out\\install\\${name}", + "cmakeCommandArgs": "", + "buildCommandArgs": "", + "ctestCommandArgs": "", + "variables": [ + { + "name": "Qt5_DIR", + "value": "C:/Qt/5.15.2/msvc2019_64/lib/cmake", + "type": "PATH" + }, + { + "name": "SNDFILE_INCLUDE_DIR", + "value": "C:/vcpkg/packages/libsndfile_x64-windows/include", + "type": "PATH" + }, + { + "name": "SNDFILE_LIBRARY", + "value": "C:/vcpkg/packages/libsndfile_x64-windows/lib", + "type": "FILEPATH" + }, + { + "name": "SDL2_LIBRARY", + "value": "C:/vcpkg/packages/sdl2_x64-windows/lib/SDL2.lib", + "type": "FILEPATH" + }, + { + "name": "SDL2_INCLUDE_DIR", + "value": "C:/vcpkg/packages/sdl2_x64-windows/include", + "type": "PATH" + }, + { + "name": "VORBIS_INCLUDE_DIR", + "value": "C:/vcpkg/packages/libvorbis_x64-windows/include", + "type": "PATH" + }, + { + "name": "VORBIS_LIBRARY", + "value": "C:/vcpkg/packages/libvorbis_x64-windows/lib/vorbis.lib", + "type": "FILEPATH" + }, + { + "name": "SDL_INCLUDE_DIR", + "value": "C:/vcpkg/packages/sdl2_x64-windows/include", + "type": "PATH" + }, + { + "name": "FFTW_INCLUDE_DIR", + "value": "C:\\vcpkg\\packages\\fftw3_x64-windows\\include", + "type": "PATH" + }, + { + "name": "FFTW3F_LIBRARY", + "value": "C:\\vcpkg\\packages\\fftw3_x64-windows\\lib/fftw3f.lib", + "type": "FILEPATH" + }, + { + "name": "LibSndFile_DIR", + "value": "C:\\vcpkg\\packages\\libsndfile_x64-windows", + "type": "PATH" + }, + { + "name": "LILV_DIR", + "value": "C:\\vcpkg\\packages\\lilv_x64-windows", + "type": "PATH" + }, + { + "name": "LV2_DIR", + "value": "C:\\vcpkg\\packages\\lv2_x64-windows", + "type": "PATH" + }, + { + "name": "OGG_INCLUDE_DIR", + "value": "C:\\vcpkg\\packages\\libogg_x64-windows\\include", + "type": "PATH" + }, + { + "name": "OGG_LIBRARY", + "value": "C:\\vcpkg\\packages\\libogg_x64-windows\\lib/ogg.lib", + "type": "FILEPATH" + }, + { + "name": "SDL2_DIR", + "value": "C:\\vcpkg\\packages\\sdl2_x64-windows", + "type": "PATH" + }, + { + "name": "VORBISENC_LIBRARY", + "value": "C:\\vcpkg\\packages\\libvorbis_x64-windows\\lib/vorbisenc.lib", + "type": "FILEPATH" + }, + { + "name": "VORBISFILE_LIBRARY", + "value": "C:\\vcpkg\\packages\\libvorbis_x64-windows\\lib/vorbisfile.lib", + "type": "FILEPATH" + }, + { + "name": "FLTK_DIR", + "value": "C:\\vcpkg\\packages\\fltk_x64-windows", + "type": "PATH" + }, + { + "name": "FLTK_INCLUDE_DIR", + "value": "C:\\vcpkg\\packages\\fltk_x64-windows\\include", + "type": "PATH" + }, + { + "name": "FluidSynth_INCLUDE_DIR", + "value": "C:\\vcpkg\\packages\\fluidsynth_x64-windows\\include", + "type": "PATH" + }, + { + "name": "FluidSynth_LIBRARY", + "value": "C:\\vcpkg\\packages\\fluidsynth_x64-windows\\lib/fluidsynth.lib", + "type": "FILEPATH" + } + ] + } + ] +} \ No newline at end of file From f228f7eebc4d2c3d3f1dfa6cad7e01a89b3c9bf2 Mon Sep 17 00:00:00 2001 From: dan-giddins Date: Sun, 6 Aug 2023 14:48:43 +0100 Subject: [PATCH 72/95] fix slashes --- CMakeSettings.json | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/CMakeSettings.json b/CMakeSettings.json index b9b801428a1..0c38b39deb2 100644 --- a/CMakeSettings.json +++ b/CMakeSettings.json @@ -53,72 +53,72 @@ }, { "name": "FFTW_INCLUDE_DIR", - "value": "C:\\vcpkg\\packages\\fftw3_x64-windows\\include", + "value": "C:/vcpkg/packages/fftw3_x64-windows/include", "type": "PATH" }, { "name": "FFTW3F_LIBRARY", - "value": "C:\\vcpkg\\packages\\fftw3_x64-windows\\lib/fftw3f.lib", + "value": "C:/vcpkg/packages/fftw3_x64-windows/lib/fftw3f.lib", "type": "FILEPATH" }, { "name": "LibSndFile_DIR", - "value": "C:\\vcpkg\\packages\\libsndfile_x64-windows", + "value": "C:/vcpkg/packages/libsndfile_x64-windows", "type": "PATH" }, { "name": "LILV_DIR", - "value": "C:\\vcpkg\\packages\\lilv_x64-windows", + "value": "C:/vcpkg/packages/lilv_x64-windows", "type": "PATH" }, { "name": "LV2_DIR", - "value": "C:\\vcpkg\\packages\\lv2_x64-windows", + "value": "C:/vcpkg/packages/lv2_x64-windows", "type": "PATH" }, { "name": "OGG_INCLUDE_DIR", - "value": "C:\\vcpkg\\packages\\libogg_x64-windows\\include", + "value": "C:/vcpkg/packages/libogg_x64-windows/include", "type": "PATH" }, { "name": "OGG_LIBRARY", - "value": "C:\\vcpkg\\packages\\libogg_x64-windows\\lib/ogg.lib", + "value": "C:/vcpkg/packages/libogg_x64-windows/lib/ogg.lib", "type": "FILEPATH" }, { "name": "SDL2_DIR", - "value": "C:\\vcpkg\\packages\\sdl2_x64-windows", + "value": "C:/vcpkg/packages/sdl2_x64-windows", "type": "PATH" }, { "name": "VORBISENC_LIBRARY", - "value": "C:\\vcpkg\\packages\\libvorbis_x64-windows\\lib/vorbisenc.lib", + "value": "C:/vcpkg/packages/libvorbis_x64-windows/lib/vorbisenc.lib", "type": "FILEPATH" }, { "name": "VORBISFILE_LIBRARY", - "value": "C:\\vcpkg\\packages\\libvorbis_x64-windows\\lib/vorbisfile.lib", + "value": "C:/vcpkg/packages/libvorbis_x64-windows/lib/vorbisfile.lib", "type": "FILEPATH" }, { "name": "FLTK_DIR", - "value": "C:\\vcpkg\\packages\\fltk_x64-windows", + "value": "C:/vcpkg/packages/fltk_x64-windows", "type": "PATH" }, { "name": "FLTK_INCLUDE_DIR", - "value": "C:\\vcpkg\\packages\\fltk_x64-windows\\include", + "value": "C:/vcpkg/packages/fltk_x64-windows/include", "type": "PATH" }, { "name": "FluidSynth_INCLUDE_DIR", - "value": "C:\\vcpkg\\packages\\fluidsynth_x64-windows\\include", + "value": "C:/vcpkg/packages/fluidsynth_x64-windows/include", "type": "PATH" }, { "name": "FluidSynth_LIBRARY", - "value": "C:\\vcpkg\\packages\\fluidsynth_x64-windows\\lib/fluidsynth.lib", + "value": "C:/vcpkg/packages/fluidsynth_x64-windows/lib/fluidsynth.lib", "type": "FILEPATH" } ] From a1ed699659045293a7c99b68da83f9e9a683598b Mon Sep 17 00:00:00 2001 From: dan-giddins Date: Sun, 6 Aug 2023 14:51:25 +0100 Subject: [PATCH 73/95] add test --- CMakeSettings.json | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CMakeSettings.json b/CMakeSettings.json index 0c38b39deb2..6b4b5e672d6 100644 --- a/CMakeSettings.json +++ b/CMakeSettings.json @@ -120,6 +120,11 @@ "name": "FluidSynth_LIBRARY", "value": "C:/vcpkg/packages/fluidsynth_x64-windows/lib/fluidsynth.lib", "type": "FILEPATH" + }, + { + "name": "Qt5Test_DIR", + "value": "C:/Qt/5.15.2/msvc2019_64/lib/cmake/Qt5Test", + "type": "PATH" } ] } From 60c75c2252fe5e3b63cd3a4a396ac7b5f5fa17e4 Mon Sep 17 00:00:00 2001 From: dan-giddins Date: Sun, 6 Aug 2023 15:15:39 +0100 Subject: [PATCH 74/95] add more cmakesettings --- CMakeSettings.json | 55 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/CMakeSettings.json b/CMakeSettings.json index 6b4b5e672d6..013737935ee 100644 --- a/CMakeSettings.json +++ b/CMakeSettings.json @@ -125,6 +125,61 @@ "name": "Qt5Test_DIR", "value": "C:/Qt/5.15.2/msvc2019_64/lib/cmake/Qt5Test", "type": "PATH" + }, + { + "name": "FLTK_FLUID_EXECUTABLE", + "value": "C:/vcpkg/packages/fltk_x64-windows/tools/fltk/fluid.exe", + "type": "FILEPATH" + }, + { + "name": "Portaudio_LIBRARY", + "value": "C:/vcpkg/packages/portaudio_x64-windows/lib/portaudio.lib", + "type": "FILEPATH" + }, + { + "name": "Portaudio_INCLUDE_DIR", + "value": "C:/vcpkg/packages/portaudio_x64-windows/include", + "type": "PATH" + }, + { + "name": "STK_RAWWAVE_ROOT", + "value": "C:/vcpkg/packages/libstk_x64-windows/share/libstk/rawwaves", + "type": "PATH" + }, + { + "name": "STK_LIBRARY", + "value": "C:/vcpkg/packages/libstk_x64-windows/lib/libstk.lib", + "type": "FILEPATH" + }, + { + "name": "mp3lame_DIR", + "value": "C:/vcpkg/packages/mp3lame_x64-windows", + "type": "PATH" + }, + { + "name": "portaudio_DIR", + "value": "C:/vcpkg/packages/portaudio_x64-windows", + "type": "PATH" + }, + { + "name": "serd_DIR", + "value": "C:/vcpkg/packages/serd_x64-windows", + "type": "PATH" + }, + { + "name": "STK_INCLUDE_DIR", + "value": "C:/vcpkg/packages/libstk_x64-windows/include", + "type": "PATH" + }, + { + "name": "sord_DIR", + "value": "C:/vcpkg/packages/sord_x64-windows", + "type": "PATH" + }, + { + "name": "sratom_DIR", + "value": "C:/vcpkg/packages/sratom_x64-windows", + "type": "PATH" } ] } From 95e4aeab40a04a803c97b7739164705dba25cd84 Mon Sep 17 00:00:00 2001 From: dan-giddins Date: Sun, 6 Aug 2023 15:36:23 +0100 Subject: [PATCH 75/95] add samplerate --- CMakeSettings.json | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CMakeSettings.json b/CMakeSettings.json index 013737935ee..64f2843d9dd 100644 --- a/CMakeSettings.json +++ b/CMakeSettings.json @@ -180,6 +180,11 @@ "name": "sratom_DIR", "value": "C:/vcpkg/packages/sratom_x64-windows", "type": "PATH" + }, + { + "name": "SAMPLERATE_LIBRARY", + "value": "C:/vcpkg/packages/libsamplerate_x64-windows/lib/samplerate.lib", + "type": "FILEPATH" } ] } From 37b9055d27846cebfe6f26ea6ed3eb2fc785723a Mon Sep 17 00:00:00 2001 From: dan-giddins Date: Sun, 6 Aug 2023 15:39:12 +0100 Subject: [PATCH 76/95] add samplerate include dir --- CMakeSettings.json | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CMakeSettings.json b/CMakeSettings.json index 64f2843d9dd..34e654ba7c9 100644 --- a/CMakeSettings.json +++ b/CMakeSettings.json @@ -185,6 +185,11 @@ "name": "SAMPLERATE_LIBRARY", "value": "C:/vcpkg/packages/libsamplerate_x64-windows/lib/samplerate.lib", "type": "FILEPATH" + }, + { + "name": "SAMPLERATE_INCLUDE_DIR", + "value": "C:/vcpkg/packages/libsamplerate_x64-windows/include", + "type": "PATH" } ] } From 49b8d972aee0ae28437375d22ad60603092df3bf Mon Sep 17 00:00:00 2001 From: dan-giddins Date: Sun, 6 Aug 2023 16:41:55 +0100 Subject: [PATCH 77/95] Update adplug --- plugins/OpulenZ/adplug | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/OpulenZ/adplug b/plugins/OpulenZ/adplug index 3ed6617ec00..70bc483d597 160000 --- a/plugins/OpulenZ/adplug +++ b/plugins/OpulenZ/adplug @@ -1 +1 @@ -Subproject commit 3ed6617ec00022dfab574c27710d9071a6032c87 +Subproject commit 70bc483d597fa66b06778ddde1975ccae3fddd0d From 5c11dcd22f65a6d1d3019187b635aa2ded42bf18 Mon Sep 17 00:00:00 2001 From: dan-giddins Date: Sun, 6 Aug 2023 17:30:17 +0100 Subject: [PATCH 78/95] add cmakeToolchain --- CMakeSettings.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeSettings.json b/CMakeSettings.json index 34e654ba7c9..8a8f698563b 100644 --- a/CMakeSettings.json +++ b/CMakeSettings.json @@ -7,7 +7,6 @@ "inheritEnvironments": [ "msvc_x64_x64" ], "buildRoot": "${projectDir}\\out\\build\\${name}", "installRoot": "${projectDir}\\out\\install\\${name}", - "cmakeCommandArgs": "", "buildCommandArgs": "", "ctestCommandArgs": "", "variables": [ @@ -191,7 +190,8 @@ "value": "C:/vcpkg/packages/libsamplerate_x64-windows/include", "type": "PATH" } - ] + ], + "cmakeToolchain": "C:/vcpkg/scripts/buildsystems/vcpkg.cmake" } ] } \ No newline at end of file From 7677d4df48317fd13ae67bf6fd67fd6f3c34efdd Mon Sep 17 00:00:00 2001 From: dan-giddins Date: Sun, 6 Aug 2023 17:30:45 +0100 Subject: [PATCH 79/95] remove unused vars --- CMakeSettings.json | 2 -- 1 file changed, 2 deletions(-) diff --git a/CMakeSettings.json b/CMakeSettings.json index 8a8f698563b..3ed8a846a9f 100644 --- a/CMakeSettings.json +++ b/CMakeSettings.json @@ -7,8 +7,6 @@ "inheritEnvironments": [ "msvc_x64_x64" ], "buildRoot": "${projectDir}\\out\\build\\${name}", "installRoot": "${projectDir}\\out\\install\\${name}", - "buildCommandArgs": "", - "ctestCommandArgs": "", "variables": [ { "name": "Qt5_DIR", From 0c2cb2d07758b9a55ba8246127942529172b1825 Mon Sep 17 00:00:00 2001 From: dan-giddins Date: Sun, 6 Aug 2023 17:39:46 +0100 Subject: [PATCH 80/95] Update adplug --- plugins/OpulenZ/adplug | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/OpulenZ/adplug b/plugins/OpulenZ/adplug index 70bc483d597..e30f56e69f4 160000 --- a/plugins/OpulenZ/adplug +++ b/plugins/OpulenZ/adplug @@ -1 +1 @@ -Subproject commit 70bc483d597fa66b06778ddde1975ccae3fddd0d +Subproject commit e30f56e69f403c7aaf3e07252cf6cc5b2bd2f848 From c35ac75d093b81913f2cc3609d78827a4786f50c Mon Sep 17 00:00:00 2001 From: dan-giddins Date: Sun, 6 Aug 2023 19:37:07 +0100 Subject: [PATCH 81/95] remove vcpkg var --- CMakeSettings.json | 175 --------------------------------------------- 1 file changed, 175 deletions(-) diff --git a/CMakeSettings.json b/CMakeSettings.json index 3ed8a846a9f..7c355e89f7d 100644 --- a/CMakeSettings.json +++ b/CMakeSettings.json @@ -12,181 +12,6 @@ "name": "Qt5_DIR", "value": "C:/Qt/5.15.2/msvc2019_64/lib/cmake", "type": "PATH" - }, - { - "name": "SNDFILE_INCLUDE_DIR", - "value": "C:/vcpkg/packages/libsndfile_x64-windows/include", - "type": "PATH" - }, - { - "name": "SNDFILE_LIBRARY", - "value": "C:/vcpkg/packages/libsndfile_x64-windows/lib", - "type": "FILEPATH" - }, - { - "name": "SDL2_LIBRARY", - "value": "C:/vcpkg/packages/sdl2_x64-windows/lib/SDL2.lib", - "type": "FILEPATH" - }, - { - "name": "SDL2_INCLUDE_DIR", - "value": "C:/vcpkg/packages/sdl2_x64-windows/include", - "type": "PATH" - }, - { - "name": "VORBIS_INCLUDE_DIR", - "value": "C:/vcpkg/packages/libvorbis_x64-windows/include", - "type": "PATH" - }, - { - "name": "VORBIS_LIBRARY", - "value": "C:/vcpkg/packages/libvorbis_x64-windows/lib/vorbis.lib", - "type": "FILEPATH" - }, - { - "name": "SDL_INCLUDE_DIR", - "value": "C:/vcpkg/packages/sdl2_x64-windows/include", - "type": "PATH" - }, - { - "name": "FFTW_INCLUDE_DIR", - "value": "C:/vcpkg/packages/fftw3_x64-windows/include", - "type": "PATH" - }, - { - "name": "FFTW3F_LIBRARY", - "value": "C:/vcpkg/packages/fftw3_x64-windows/lib/fftw3f.lib", - "type": "FILEPATH" - }, - { - "name": "LibSndFile_DIR", - "value": "C:/vcpkg/packages/libsndfile_x64-windows", - "type": "PATH" - }, - { - "name": "LILV_DIR", - "value": "C:/vcpkg/packages/lilv_x64-windows", - "type": "PATH" - }, - { - "name": "LV2_DIR", - "value": "C:/vcpkg/packages/lv2_x64-windows", - "type": "PATH" - }, - { - "name": "OGG_INCLUDE_DIR", - "value": "C:/vcpkg/packages/libogg_x64-windows/include", - "type": "PATH" - }, - { - "name": "OGG_LIBRARY", - "value": "C:/vcpkg/packages/libogg_x64-windows/lib/ogg.lib", - "type": "FILEPATH" - }, - { - "name": "SDL2_DIR", - "value": "C:/vcpkg/packages/sdl2_x64-windows", - "type": "PATH" - }, - { - "name": "VORBISENC_LIBRARY", - "value": "C:/vcpkg/packages/libvorbis_x64-windows/lib/vorbisenc.lib", - "type": "FILEPATH" - }, - { - "name": "VORBISFILE_LIBRARY", - "value": "C:/vcpkg/packages/libvorbis_x64-windows/lib/vorbisfile.lib", - "type": "FILEPATH" - }, - { - "name": "FLTK_DIR", - "value": "C:/vcpkg/packages/fltk_x64-windows", - "type": "PATH" - }, - { - "name": "FLTK_INCLUDE_DIR", - "value": "C:/vcpkg/packages/fltk_x64-windows/include", - "type": "PATH" - }, - { - "name": "FluidSynth_INCLUDE_DIR", - "value": "C:/vcpkg/packages/fluidsynth_x64-windows/include", - "type": "PATH" - }, - { - "name": "FluidSynth_LIBRARY", - "value": "C:/vcpkg/packages/fluidsynth_x64-windows/lib/fluidsynth.lib", - "type": "FILEPATH" - }, - { - "name": "Qt5Test_DIR", - "value": "C:/Qt/5.15.2/msvc2019_64/lib/cmake/Qt5Test", - "type": "PATH" - }, - { - "name": "FLTK_FLUID_EXECUTABLE", - "value": "C:/vcpkg/packages/fltk_x64-windows/tools/fltk/fluid.exe", - "type": "FILEPATH" - }, - { - "name": "Portaudio_LIBRARY", - "value": "C:/vcpkg/packages/portaudio_x64-windows/lib/portaudio.lib", - "type": "FILEPATH" - }, - { - "name": "Portaudio_INCLUDE_DIR", - "value": "C:/vcpkg/packages/portaudio_x64-windows/include", - "type": "PATH" - }, - { - "name": "STK_RAWWAVE_ROOT", - "value": "C:/vcpkg/packages/libstk_x64-windows/share/libstk/rawwaves", - "type": "PATH" - }, - { - "name": "STK_LIBRARY", - "value": "C:/vcpkg/packages/libstk_x64-windows/lib/libstk.lib", - "type": "FILEPATH" - }, - { - "name": "mp3lame_DIR", - "value": "C:/vcpkg/packages/mp3lame_x64-windows", - "type": "PATH" - }, - { - "name": "portaudio_DIR", - "value": "C:/vcpkg/packages/portaudio_x64-windows", - "type": "PATH" - }, - { - "name": "serd_DIR", - "value": "C:/vcpkg/packages/serd_x64-windows", - "type": "PATH" - }, - { - "name": "STK_INCLUDE_DIR", - "value": "C:/vcpkg/packages/libstk_x64-windows/include", - "type": "PATH" - }, - { - "name": "sord_DIR", - "value": "C:/vcpkg/packages/sord_x64-windows", - "type": "PATH" - }, - { - "name": "sratom_DIR", - "value": "C:/vcpkg/packages/sratom_x64-windows", - "type": "PATH" - }, - { - "name": "SAMPLERATE_LIBRARY", - "value": "C:/vcpkg/packages/libsamplerate_x64-windows/lib/samplerate.lib", - "type": "FILEPATH" - }, - { - "name": "SAMPLERATE_INCLUDE_DIR", - "value": "C:/vcpkg/packages/libsamplerate_x64-windows/include", - "type": "PATH" } ], "cmakeToolchain": "C:/vcpkg/scripts/buildsystems/vcpkg.cmake" From 243e5f62951d1002c871cd2c629a9a6efcb5abba Mon Sep 17 00:00:00 2001 From: dan-giddins Date: Sun, 6 Aug 2023 20:37:44 +0100 Subject: [PATCH 82/95] VCPKG_TRACE_FIND_PACKAGE=ON --- CMakeSettings.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CMakeSettings.json b/CMakeSettings.json index 7c355e89f7d..13cb6fbfbe5 100644 --- a/CMakeSettings.json +++ b/CMakeSettings.json @@ -14,7 +14,8 @@ "type": "PATH" } ], - "cmakeToolchain": "C:/vcpkg/scripts/buildsystems/vcpkg.cmake" + "cmakeToolchain": "C:/vcpkg/scripts/buildsystems/vcpkg.cmake", + "cmakeCommandArgs": "-DVCPKG_TRACE_FIND_PACKAGE=ON" } ] } \ No newline at end of file From f6ec4de1056eae1b1737114563800a39fa8ab4b5 Mon Sep 17 00:00:00 2001 From: dan-giddins Date: Sun, 6 Aug 2023 20:50:00 +0100 Subject: [PATCH 83/95] add test var back in --- CMakeSettings.json | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CMakeSettings.json b/CMakeSettings.json index 13cb6fbfbe5..735a74f4a52 100644 --- a/CMakeSettings.json +++ b/CMakeSettings.json @@ -12,6 +12,11 @@ "name": "Qt5_DIR", "value": "C:/Qt/5.15.2/msvc2019_64/lib/cmake", "type": "PATH" + }, + { + "name": "Qt5Test_DIR", + "value": "C:/Qt/5.15.2/msvc2019_64/lib/cmake/Qt5Test", + "type": "PATH" } ], "cmakeToolchain": "C:/vcpkg/scripts/buildsystems/vcpkg.cmake", From 8b309d3f9d8daee8c5346705c7f58ba2802c4b4b Mon Sep 17 00:00:00 2001 From: dan-giddins Date: Sun, 6 Aug 2023 21:36:14 +0100 Subject: [PATCH 84/95] add strawberry perl --- CMakeSettings.json | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CMakeSettings.json b/CMakeSettings.json index 735a74f4a52..31847e9f493 100644 --- a/CMakeSettings.json +++ b/CMakeSettings.json @@ -17,6 +17,11 @@ "name": "Qt5Test_DIR", "value": "C:/Qt/5.15.2/msvc2019_64/lib/cmake/Qt5Test", "type": "PATH" + }, + { + "name": "PERL_EXECUTABLE", + "value": "C:/Strawberry/perl/bin/perl.exe", + "type": "FILEPATH" } ], "cmakeToolchain": "C:/vcpkg/scripts/buildsystems/vcpkg.cmake", From 066f25e3c4379c1cb486bde485636595af6db494 Mon Sep 17 00:00:00 2001 From: dan-giddins Date: Sun, 6 Aug 2023 22:03:03 +0100 Subject: [PATCH 85/95] add cmake prefix path --- CMakeSettings.json | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/CMakeSettings.json b/CMakeSettings.json index 31847e9f493..d3eb1d720f5 100644 --- a/CMakeSettings.json +++ b/CMakeSettings.json @@ -22,10 +22,18 @@ "name": "PERL_EXECUTABLE", "value": "C:/Strawberry/perl/bin/perl.exe", "type": "FILEPATH" + }, + { + "name": "VCPKG_TRACE_FIND_PACKAGE", + "value": "ON" + }, + { + "name": "CMAKE_PREFIX_PATH", + "value": "C:/Qt/5.15.2/msvc2019_64", + "type": "PATH" } ], - "cmakeToolchain": "C:/vcpkg/scripts/buildsystems/vcpkg.cmake", - "cmakeCommandArgs": "-DVCPKG_TRACE_FIND_PACKAGE=ON" + "cmakeToolchain": "C:/vcpkg/scripts/buildsystems/vcpkg.cmake" } ] } \ No newline at end of file From 89f3ef70713a95e3f0b3145a317ae3047c2779d9 Mon Sep 17 00:00:00 2001 From: dan-giddins Date: Sun, 6 Aug 2023 22:24:10 +0100 Subject: [PATCH 86/95] update vcpkg_trace --- CMakeSettings.json | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/CMakeSettings.json b/CMakeSettings.json index d3eb1d720f5..ed71628f872 100644 --- a/CMakeSettings.json +++ b/CMakeSettings.json @@ -25,12 +25,8 @@ }, { "name": "VCPKG_TRACE_FIND_PACKAGE", - "value": "ON" - }, - { - "name": "CMAKE_PREFIX_PATH", - "value": "C:/Qt/5.15.2/msvc2019_64", - "type": "PATH" + "value": "ON", + "type": "STRING" } ], "cmakeToolchain": "C:/vcpkg/scripts/buildsystems/vcpkg.cmake" From ba64207100dc23c05510abb1775104b45a9d19a3 Mon Sep 17 00:00:00 2001 From: dan-giddins Date: Sun, 6 Aug 2023 22:26:58 +0100 Subject: [PATCH 87/95] use msvc_x64 --- CMakeSettings.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeSettings.json b/CMakeSettings.json index ed71628f872..5444df3d17d 100644 --- a/CMakeSettings.json +++ b/CMakeSettings.json @@ -4,7 +4,7 @@ "name": "x64-Debug", "generator": "Ninja", "configurationType": "Debug", - "inheritEnvironments": [ "msvc_x64_x64" ], + "inheritEnvironments": [ "msvc_x64" ], "buildRoot": "${projectDir}\\out\\build\\${name}", "installRoot": "${projectDir}\\out\\install\\${name}", "variables": [ From b9367cd32e9e2ef1c5c16082cbb75dc26d820ee5 Mon Sep 17 00:00:00 2001 From: dan-giddins Date: Sun, 6 Aug 2023 22:59:42 +0100 Subject: [PATCH 88/95] newline --- CMakeSettings.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeSettings.json b/CMakeSettings.json index 5444df3d17d..45a5f602fe3 100644 --- a/CMakeSettings.json +++ b/CMakeSettings.json @@ -32,4 +32,4 @@ "cmakeToolchain": "C:/vcpkg/scripts/buildsystems/vcpkg.cmake" } ] -} \ No newline at end of file +} From 62605de32d2147529b9494b85a61d3adc89f0c37 Mon Sep 17 00:00:00 2001 From: dan-giddins Date: Tue, 15 Aug 2023 20:29:49 +0100 Subject: [PATCH 89/95] add release settings --- CMakeSettings.json | 33 ++++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/CMakeSettings.json b/CMakeSettings.json index 45a5f602fe3..b888151b27d 100644 --- a/CMakeSettings.json +++ b/CMakeSettings.json @@ -30,6 +30,37 @@ } ], "cmakeToolchain": "C:/vcpkg/scripts/buildsystems/vcpkg.cmake" + }, + { + "name": "x64-Release", + "generator": "Ninja", + "configurationType": "Release", + "buildRoot": "${projectDir}\\out\\build\\${name}", + "installRoot": "${projectDir}\\out\\install\\${name}", + "cmakeToolchain": "C:/vcpkg/scripts/buildsystems/vcpkg.cmake", + "inheritEnvironments": [ "msvc_x64" ], + "variables": [ + { + "name": "Qt5_DIR", + "value": "C:/Qt/5.15.2/msvc2019_64/lib/cmake", + "type": "PATH" + }, + { + "name": "Qt5Test_DIR", + "value": "C:/Qt/5.15.2/msvc2019_64/lib/cmake/Qt5Test", + "type": "PATH" + }, + { + "name": "PERL_EXECUTABLE", + "value": "C:/Strawberry/perl/bin/perl.exe", + "type": "FILEPATH" + }, + { + "name": "VCPKG_TRACE_FIND_PACKAGE", + "value": "ON", + "type": "STRING" + } + ] } ] -} +} \ No newline at end of file From f384ded8948bc3aca3fd03230dd12e0668d6c1f9 Mon Sep 17 00:00:00 2001 From: dan-giddins Date: Sat, 26 Aug 2023 11:13:58 +0100 Subject: [PATCH 90/95] Update mingw-std-threads --- src/3rdparty/mingw-std-threads | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/3rdparty/mingw-std-threads b/src/3rdparty/mingw-std-threads index 6c2061b7da4..10665829daa 160000 --- a/src/3rdparty/mingw-std-threads +++ b/src/3rdparty/mingw-std-threads @@ -1 +1 @@ -Subproject commit 6c2061b7da41d6aa1b2162ff4383ec3ece864bc6 +Subproject commit 10665829daaedc28629e5e9b014fe498c20d73f2 From 773111434a04a807101f859d6460fc9b1397a481 Mon Sep 17 00:00:00 2001 From: dan-giddins Date: Sat, 9 Sep 2023 11:58:11 +0100 Subject: [PATCH 91/95] Update adplug --- plugins/OpulenZ/adplug | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/OpulenZ/adplug b/plugins/OpulenZ/adplug index e30f56e69f4..10355fbedb3 160000 --- a/plugins/OpulenZ/adplug +++ b/plugins/OpulenZ/adplug @@ -1 +1 @@ -Subproject commit e30f56e69f403c7aaf3e07252cf6cc5b2bd2f848 +Subproject commit 10355fbedb3c4d10a67deb5f4214b3f678170ef8 From 6290c3e34314f828ba6be5e1f1f0df878b1c07f1 Mon Sep 17 00:00:00 2001 From: dan-giddins Date: Sun, 24 Sep 2023 18:39:25 +0100 Subject: [PATCH 92/95] Update resid --- plugins/Sid/resid | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/Sid/resid b/plugins/Sid/resid index 02afcc5cefa..a643428f29b 160000 --- a/plugins/Sid/resid +++ b/plugins/Sid/resid @@ -1 +1 @@ -Subproject commit 02afcc5cefac34bd0c665dc0fa6b748d238c1831 +Subproject commit a643428f29b67071a64f5ca1158f4fd902a91b57 From 5166387275d8f918bd54ed9d807a05982fcf1cad Mon Sep 17 00:00:00 2001 From: dan-giddins Date: Sun, 24 Sep 2023 18:44:17 +0100 Subject: [PATCH 93/95] update from master --- .gitmodules | 6 +- CMakeLists.txt | 78 +- data/locale/ar.ts | 2 +- data/locale/bs.ts | 2 +- data/locale/ca.ts | 2 +- data/locale/cs.ts | 2 +- data/locale/de.ts | 2 +- data/locale/el.ts | 2 +- data/locale/en.ts | 2 +- data/locale/eo.ts | 2 +- data/locale/es.ts | 2 +- data/locale/eu.ts | 2 +- data/locale/fa.ts | 2 +- data/locale/fr.ts | 2 +- data/locale/gl.ts | 2 +- data/locale/he.ts | 2 +- data/locale/hi_IN.ts | 2 +- data/locale/hu_HU.ts | 2 +- data/locale/id.ts | 2 +- data/locale/it.ts | 2 +- data/locale/ja.ts | 2 +- data/locale/ka.ts | 2 +- data/locale/ko.ts | 2 +- data/locale/ms_MY.ts | 2 +- data/locale/nb.ts | 2 +- data/locale/nl.ts | 2 +- data/locale/oc.ts | 2 +- data/locale/pl.ts | 2 +- data/locale/pt.ts | 2 +- data/locale/ro.ts | 2 +- data/locale/ru.ts | 2 +- data/locale/sl.ts | 2 +- data/locale/sr.ts | 2 +- data/locale/sv.ts | 2 +- data/locale/tr.ts | 2 +- data/locale/uk.ts | 2 +- data/locale/zh_CN.ts | 2 +- data/locale/zh_TW.ts | 2 +- data/samples/drums/kick04.ogg | Bin 7126 -> 12017 bytes data/samples/effects/scratch01.ogg | Bin 5516 -> 12370 bytes data/samples/effects/wind_chimes01.ogg | Bin 22654 -> 57873 bytes data/samples/instruments/harpsichord01.ogg | Bin 38144 -> 60978 bytes data/samples/misc/hit01.ogg | Bin 16606 -> 43603 bytes data/themes/classic/style.css | 7 +- data/themes/default/edit_draw_small.png | Bin 0 -> 5367 bytes data/themes/default/style.css | 8 +- data/themes/default/tuning_tab.png | Bin 0 -> 7016 bytes include/ArrayVector.h | 388 ++++++++ include/AudioEngine.h | 10 + include/AudioEngineProfiler.h | 48 +- include/CPULoadWidget.h | 6 + include/DataFile.h | 2 + include/FileBrowser.h | 9 +- include/InstrumentPlayHandle.h | 43 +- include/InstrumentTrack.h | 4 +- include/InstrumentTrackView.h | 3 + include/InstrumentTrackWindow.h | 4 +- include/InstrumentTuningView.h | 80 ++ include/Knob.h | 4 + include/LfoController.h | 1 + include/LmmsSemaphore.h | 93 ++ include/LocklessRingBuffer.h | 3 +- include/Lv2Basics.h | 6 + include/Lv2Features.h | 3 +- include/Lv2Manager.h | 20 +- include/Lv2Proc.h | 13 +- include/Lv2ViewBase.h | 2 +- include/Lv2Worker.h | 93 ++ include/MidiClip.h | 2 +- include/MidiController.h | 1 + include/Note.h | 2 +- include/NotePlayHandle.h | 5 +- include/PianoRoll.h | 4 +- include/SampleClip.h | 2 +- include/SampleTrackView.h | 2 + include/SetupDialog.h | 2 + include/SimpleTextFloat.h | 8 +- include/TrackView.h | 4 +- include/lmms_math.h | 26 + .../AudioFileProcessor/AudioFileProcessor.cpp | 3 - plugins/BitInvader/BitInvader.cpp | 2 - plugins/CarlaBase/Carla.cpp | 3 - plugins/Compressor/Compressor.cpp | 7 +- .../Compressor/CompressorControlDialog.cpp | 18 +- plugins/FreeBoy/FreeBoy.cpp | 1 - plugins/GigPlayer/GigPlayer.cpp | 2 - plugins/Kicker/Kicker.cpp | 2 - plugins/LadspaEffect/calf/CMakeLists.txt | 6 +- plugins/LadspaEffect/caps/Descriptor.h | 3 +- plugins/LadspaEffect/cmt/CMakeLists.txt | 6 +- plugins/Lb302/Lb302.cpp | 1 - plugins/Lv2Instrument/Lv2Instrument.cpp | 2 - plugins/Monstro/Monstro.cpp | 2 - plugins/Nes/Nes.cpp | 2 - plugins/OpulenZ/OpulenZ.cpp | 4 - plugins/Organic/Organic.cpp | 2 - plugins/Patman/Patman.cpp | 14 +- plugins/Sf2Player/Sf2Player.cpp | 101 ++- plugins/Sfxr/Sfxr.cpp | 3 - plugins/Sid/CMakeLists.txt | 47 +- plugins/Sid/SidInstrument.cpp | 26 +- plugins/Sid/resid | 2 +- plugins/Stk/Mallets/Mallets.cpp | 2 - plugins/TripleOscillator/TripleOscillator.cpp | 2 - plugins/Vestige/Vestige.cpp | 4 - plugins/Vibed/Vibed.cpp | 2 - plugins/VstEffect/VstSubPluginFeatures.cpp | 6 +- plugins/Watsyn/Watsyn.cpp | 2 - plugins/Xpressive/Xpressive.cpp | 2 - plugins/ZynAddSubFx/ZynAddSubFx.cpp | 1 - src/core/AudioEngine.cpp | 54 +- src/core/AudioEngineProfiler.cpp | 22 +- src/core/AutomationClip.cpp | 27 +- src/core/CMakeLists.txt | 2 + src/core/DataFile.cpp | 87 +- src/core/EffectChain.cpp | 3 + src/core/InstrumentPlayHandle.cpp | 49 +- src/core/LfoController.cpp | 34 +- src/core/LmmsSemaphore.cpp | 143 +++ src/core/Mixer.cpp | 4 +- src/core/NotePlayHandle.cpp | 16 +- src/core/PatternStore.cpp | 12 +- src/core/RenderManager.cpp | 4 +- src/core/SampleBuffer.cpp | 62 +- src/core/SampleClip.cpp | 27 +- src/core/Song.cpp | 2 +- src/core/UpgradeExtendedNoteRange.cpp | 1 + src/core/lv2/Lv2Features.cpp | 6 +- src/core/lv2/Lv2Manager.cpp | 38 +- src/core/lv2/Lv2Proc.cpp | 85 +- src/core/lv2/Lv2Worker.cpp | 203 +++++ src/core/midi/MidiController.cpp | 11 +- src/core/midi/MidiPort.cpp | 7 +- src/gui/CMakeLists.txt | 2 +- src/gui/FileBrowser.cpp | 124 ++- src/gui/Lv2ViewBase.cpp | 27 +- src/gui/MainWindow.cpp | 19 +- src/gui/MicrotunerConfig.cpp | 20 +- src/gui/MixerView.cpp | 4 +- src/gui/SampleTrackWindow.cpp | 1 - src/gui/SideBarWidget.cpp | 6 +- src/gui/clips/ClipView.cpp | 4 +- src/gui/clips/MidiClipView.cpp | 3 +- src/gui/editors/AutomationEditor.cpp | 2 +- src/gui/editors/PatternEditor.cpp | 4 +- src/gui/editors/PianoRoll.cpp | 64 +- src/gui/instrument/InstrumentTrackWindow.cpp | 38 +- src/gui/instrument/InstrumentTuningView.cpp | 118 +++ src/gui/instrument/PianoView.cpp | 103 ++- src/gui/menus/MidiPortMenu.cpp | 1 - src/gui/modals/ControllerConnectionDialog.cpp | 6 +- src/gui/modals/FileDialog.cpp | 58 +- src/gui/modals/SetupDialog.cpp | 38 +- src/gui/tracks/InstrumentTrackView.cpp | 36 +- src/gui/tracks/SampleTrackView.cpp | 23 +- src/gui/tracks/TrackContentWidget.cpp | 4 +- src/gui/tracks/TrackOperationsWidget.cpp | 1 - src/gui/widgets/CPULoadWidget.cpp | 27 +- src/gui/widgets/ComboBox.cpp | 1 - src/gui/widgets/Knob.cpp | 26 +- src/gui/widgets/LcdFloatSpinBox.cpp | 7 +- src/gui/widgets/SimpleTextFloat.cpp | 31 + src/tracks/InstrumentTrack.cpp | 23 +- src/tracks/MidiClip.cpp | 4 +- tests/CMakeLists.txt | 2 + tests/main.cpp | 2 +- tests/src/core/ArrayVectorTest.cpp | 831 ++++++++++++++++++ tests/src/core/MathTest.cpp | 53 ++ 168 files changed, 3293 insertions(+), 663 deletions(-) create mode 100644 data/themes/default/edit_draw_small.png create mode 100644 data/themes/default/tuning_tab.png create mode 100644 include/ArrayVector.h create mode 100644 include/InstrumentTuningView.h create mode 100644 include/LmmsSemaphore.h create mode 100644 include/Lv2Worker.h create mode 100644 src/core/LmmsSemaphore.cpp create mode 100644 src/core/lv2/Lv2Worker.cpp create mode 100644 src/gui/instrument/InstrumentTuningView.cpp create mode 100644 tests/src/core/ArrayVectorTest.cpp create mode 100644 tests/src/core/MathTest.cpp diff --git a/.gitmodules b/.gitmodules index ee6e7eac9d8..fa6980ac5f8 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 5db72ef830c..3a5754d6a56 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -8,17 +8,9 @@ SET(LMMS_SOURCE_DIR ${CMAKE_SOURCE_DIR}) # CMAKE_POLICY Section IF(COMMAND CMAKE_POLICY) - CMAKE_POLICY(SET CMP0005 NEW) - CMAKE_POLICY(SET CMP0003 NEW) - IF (CMAKE_MAJOR_VERSION GREATER 2) - CMAKE_POLICY(SET CMP0026 NEW) - CMAKE_POLICY(SET CMP0045 NEW) - CMAKE_POLICY(SET CMP0050 OLD) - ENDIF() - CMAKE_POLICY(SET CMP0020 NEW) - CMAKE_POLICY(SET CMP0057 NEW) # TODO: Keep CMP0074 but remove this condition when cmake 3.12+ is guaranteed IF(${CMAKE_VERSION} VERSION_GREATER_EQUAL 3.12) + # Needed for the SWH Ladspa plugins. See below. CMAKE_POLICY(SET CMP0074 NEW) # find_package() uses _ROOT variables ENDIF() ENDIF(COMMAND CMAKE_POLICY) @@ -85,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) @@ -93,6 +86,10 @@ OPTION(WANT_VST_32 "Include 32-bit VST support" ON) OPTION(WANT_VST_64 "Include 64-bit VST support" ON) OPTION(WANT_WINMM "Include WinMM MIDI support" OFF) OPTION(WANT_DEBUG_FPE "Debug floating point exceptions" OFF) +option(WANT_DEBUG_ASAN "Enable AddressSanitizer" OFF) +option(WANT_DEBUG_TSAN "Enable ThreadSanitizer" OFF) +option(WANT_DEBUG_MSAN "Enable MemorySanitizer" OFF) +option(WANT_DEBUG_UBSAN "Enable UndefinedBehaviorSanitizer" OFF) OPTION(BUNDLE_QT_TRANSLATIONS "Install Qt translation files for LMMS" OFF) @@ -215,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) @@ -353,6 +357,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) @@ -518,7 +532,11 @@ IF(WANT_SF2) find_package(FluidSynth 1.1.0) if(FluidSynth_FOUND) SET(LMMS_HAVE_FLUIDSYNTH TRUE) - SET(STATUS_FLUIDSYNTH "OK") + if(FluidSynth_VERSION_STRING VERSION_GREATER_EQUAL 2) + set(STATUS_FLUIDSYNTH "OK") + else() + set(STATUS_FLUIDSYNTH "OK (FluidSynth version < 2: per-note panning unsupported)") + endif() else() SET(STATUS_FLUIDSYNTH "not found, libfluidsynth-dev (or similar)" "is highly recommended") @@ -629,7 +647,9 @@ else() set(NOOP_COMMAND "${CMAKE_COMMAND}" "-E" "echo") endif() if(STRIP) - set(STRIP_COMMAND "$,${NOOP_COMMAND},${STRIP}>") + # TODO CMake 3.19: Now that CONFIG generator expressions support testing for + # multiple configurations, combine the OR into a single CONFIG expression. + set(STRIP_COMMAND "$,$>,${NOOP_COMMAND},${STRIP}>") else() set(STRIP_COMMAND "${NOOP_COMMAND}") endif() @@ -665,8 +685,36 @@ IF(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang") ELSE(WIN32) SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fPIC -DPIC") ENDIF(WIN32) +elseif(MSVC) + # Use UTF-8 as the source and execution character set + add_compile_options("/utf-8") ENDIF() +# add enabled sanitizers +function(add_sanitizer sanitizer supported_compilers want_flag status_flag) + if(${want_flag}) + if(CMAKE_CXX_COMPILER_ID MATCHES "${supported_compilers}") + set("${status_flag}" "Enabled" PARENT_SCOPE) + string(REPLACE ";" " " additional_flags "${ARGN}") + # todo CMake 3.13: use add_compile_options/add_link_options instead + set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -fsanitize=${sanitizer} ${additional_flags}" PARENT_SCOPE) + set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -fsanitize=${sanitizer} ${additional_flags}" PARENT_SCOPE) + else() + set("${status_flag}" "Wanted but disabled due to unsupported compiler" PARENT_SCOPE) + endif() + else() + set("${status_flag}" "Disabled" PARENT_SCOPE) + endif() +endfunction() + +add_sanitizer(address "GNU|Clang|MSVC" WANT_DEBUG_ASAN STATUS_DEBUG_ASAN) +add_sanitizer(thread "GNU|Clang" WANT_DEBUG_TSAN STATUS_DEBUG_TSAN) +add_sanitizer(memory "Clang" WANT_DEBUG_MSAN STATUS_DEBUG_MSAN -fno-omit-frame-pointer) +# UBSan does not link with vptr enabled due to a problem with references from PeakControllerEffect +# not being found by PeakController +add_sanitizer(undefined "GNU|Clang" WANT_DEBUG_UBSAN STATUS_DEBUG_UBSAN -fno-sanitize=vptr) + + # use ccache include(CompileCache) @@ -735,7 +783,6 @@ ADD_CUSTOM_TARGET(uninstall COMMAND ${CMAKE_COMMAND} -DCMAKE_INSTALL_PREFIX="${CMAKE_INSTALL_PREFIX}" -P "${CMAKE_CURRENT_SOURCE_DIR}/cmake/uninstall.cmake" ) - # # display configuration information # @@ -787,6 +834,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" @@ -801,7 +849,11 @@ MESSAGE( MESSAGE( "Developer options\n" "-----------------------------------------\n" -"* Debug FP exceptions : ${STATUS_DEBUG_FPE}\n" +"* Debug FP exceptions : ${STATUS_DEBUG_FPE}\n" +"* Debug using AddressSanitizer : ${STATUS_DEBUG_ASAN}\n" +"* Debug using ThreadSanitizer : ${STATUS_DEBUG_TSAN}\n" +"* Debug using MemorySanitizer : ${STATUS_DEBUG_MSAN}\n" +"* Debug using UBSanitizer : ${STATUS_DEBUG_UBSAN}\n" ) MESSAGE( diff --git a/data/locale/ar.ts b/data/locale/ar.ts index 1f159c42a2a..0d44c22bf5c 100644 --- a/data/locale/ar.ts +++ b/data/locale/ar.ts @@ -6361,7 +6361,7 @@ Please make sure you have write permission to the file and the directory contain - InstrumentMiscView + InstrumentTuningView MASTER PITCH diff --git a/data/locale/bs.ts b/data/locale/bs.ts index 506b401bd61..7abf0baf1e1 100644 --- a/data/locale/bs.ts +++ b/data/locale/bs.ts @@ -3677,7 +3677,7 @@ You can remove and move mixer channels in the context menu, which is accessed by - InstrumentMiscView + InstrumentTuningView MASTER PITCH diff --git a/data/locale/ca.ts b/data/locale/ca.ts index 765cf3b6081..0e27c39db80 100644 --- a/data/locale/ca.ts +++ b/data/locale/ca.ts @@ -6360,7 +6360,7 @@ Please make sure you have write permission to the file and the directory contain - InstrumentMiscView + InstrumentTuningView MASTER PITCH diff --git a/data/locale/cs.ts b/data/locale/cs.ts index 0ed175022be..022f554592a 100644 --- a/data/locale/cs.ts +++ b/data/locale/cs.ts @@ -6361,7 +6361,7 @@ Ověřte si prosím, zda máte povolen zápis do souboru a do složky, ve které - InstrumentMiscView + InstrumentTuningView MASTER PITCH diff --git a/data/locale/de.ts b/data/locale/de.ts index 51ca7d56204..7817857fdff 100644 --- a/data/locale/de.ts +++ b/data/locale/de.ts @@ -6361,7 +6361,7 @@ Please make sure you have write permission to the file and the directory contain - InstrumentMiscView + InstrumentTuningView MASTER PITCH diff --git a/data/locale/el.ts b/data/locale/el.ts index 320a6657f61..07e61778f1e 100644 --- a/data/locale/el.ts +++ b/data/locale/el.ts @@ -6360,7 +6360,7 @@ Please make sure you have write permission to the file and the directory contain - InstrumentMiscView + InstrumentTuningView MASTER PITCH diff --git a/data/locale/en.ts b/data/locale/en.ts index e52ae39ab7e..15c3ab1f07c 100644 --- a/data/locale/en.ts +++ b/data/locale/en.ts @@ -6362,7 +6362,7 @@ Please make sure you have write permission to the file and the directory contain - InstrumentMiscView + InstrumentTuningView MASTER PITCH diff --git a/data/locale/eo.ts b/data/locale/eo.ts index 005ee8100bf..0dd9c405f67 100644 --- a/data/locale/eo.ts +++ b/data/locale/eo.ts @@ -6360,7 +6360,7 @@ Please make sure you have write permission to the file and the directory contain - InstrumentMiscView + InstrumentTuningView MASTER PITCH diff --git a/data/locale/es.ts b/data/locale/es.ts index 4fc4951ef92..3953ddc11bf 100644 --- a/data/locale/es.ts +++ b/data/locale/es.ts @@ -6361,7 +6361,7 @@ Asegúrate de tener permisos de escritura tanto del archivo como del directorio - InstrumentMiscView + InstrumentTuningView MASTER PITCH diff --git a/data/locale/eu.ts b/data/locale/eu.ts index 25c165f81f8..fe6495c0a65 100644 --- a/data/locale/eu.ts +++ b/data/locale/eu.ts @@ -6641,7 +6641,7 @@ Please make sure you have write permission to the file and the directory contain - InstrumentMiscView + InstrumentTuningView MASTER PITCH diff --git a/data/locale/fa.ts b/data/locale/fa.ts index 181ca0ca1ff..b376a8424f8 100644 --- a/data/locale/fa.ts +++ b/data/locale/fa.ts @@ -6360,7 +6360,7 @@ Please make sure you have write permission to the file and the directory contain - InstrumentMiscView + InstrumentTuningView MASTER PITCH diff --git a/data/locale/fr.ts b/data/locale/fr.ts index 2c65444a8e0..4862f4263ce 100644 --- a/data/locale/fr.ts +++ b/data/locale/fr.ts @@ -6645,7 +6645,7 @@ Veuillez vous assurez que vous avez les droits d'écriture sur le fichier e - InstrumentMiscView + InstrumentTuningView MASTER PITCH diff --git a/data/locale/gl.ts b/data/locale/gl.ts index cf04fd5d428..a1a9e6bf1a4 100644 --- a/data/locale/gl.ts +++ b/data/locale/gl.ts @@ -6360,7 +6360,7 @@ Please make sure you have write permission to the file and the directory contain - InstrumentMiscView + InstrumentTuningView MASTER PITCH diff --git a/data/locale/he.ts b/data/locale/he.ts index ee5a23613b2..fef0caa9178 100644 --- a/data/locale/he.ts +++ b/data/locale/he.ts @@ -6361,7 +6361,7 @@ Please make sure you have write permission to the file and the directory contain - InstrumentMiscView + InstrumentTuningView MASTER PITCH diff --git a/data/locale/hi_IN.ts b/data/locale/hi_IN.ts index 15550231f85..82cf364e332 100644 --- a/data/locale/hi_IN.ts +++ b/data/locale/hi_IN.ts @@ -6362,7 +6362,7 @@ Please make sure you have write permission to the file and the directory contain - InstrumentMiscView + InstrumentTuningView MASTER PITCH diff --git a/data/locale/hu_HU.ts b/data/locale/hu_HU.ts index 83605994674..a0f1e4d4542 100644 --- a/data/locale/hu_HU.ts +++ b/data/locale/hu_HU.ts @@ -6366,7 +6366,7 @@ EllenÅ‘rizd, hogy rendelkezel-e a szükséges engedélyekkel és próbáld újra - InstrumentMiscView + InstrumentTuningView MASTER PITCH diff --git a/data/locale/id.ts b/data/locale/id.ts index e381ea726d8..c504740e936 100644 --- a/data/locale/id.ts +++ b/data/locale/id.ts @@ -6362,7 +6362,7 @@ Pastikan Anda memiliki izin menulis ke file dan direktori yang berisi berkas ter - InstrumentMiscView + InstrumentTuningView MASTER PITCH diff --git a/data/locale/it.ts b/data/locale/it.ts index ff146d47120..d5a68e6e7c3 100644 --- a/data/locale/it.ts +++ b/data/locale/it.ts @@ -6366,7 +6366,7 @@ Si prega di controllare i permessi di scrittura sul file e la cartella che lo co - InstrumentMiscView + InstrumentTuningView MASTER PITCH diff --git a/data/locale/ja.ts b/data/locale/ja.ts index e10ca511818..14b38c6984f 100644 --- a/data/locale/ja.ts +++ b/data/locale/ja.ts @@ -6362,7 +6362,7 @@ Please make sure you have write permission to the file and the directory contain - InstrumentMiscView + InstrumentTuningView MASTER PITCH diff --git a/data/locale/ka.ts b/data/locale/ka.ts index 1956d8d04e5..51eededf26b 100644 --- a/data/locale/ka.ts +++ b/data/locale/ka.ts @@ -6360,7 +6360,7 @@ Please make sure you have write permission to the file and the directory contain - InstrumentMiscView + InstrumentTuningView MASTER PITCH diff --git a/data/locale/ko.ts b/data/locale/ko.ts index 7373b5ca9a7..43b99e7f437 100644 --- a/data/locale/ko.ts +++ b/data/locale/ko.ts @@ -6364,7 +6364,7 @@ Please make sure you have write permission to the file and the directory contain - InstrumentMiscView + InstrumentTuningView MASTER PITCH diff --git a/data/locale/ms_MY.ts b/data/locale/ms_MY.ts index 209d51d108c..ff34784219a 100644 --- a/data/locale/ms_MY.ts +++ b/data/locale/ms_MY.ts @@ -6360,7 +6360,7 @@ Please make sure you have write permission to the file and the directory contain - InstrumentMiscView + InstrumentTuningView MASTER PITCH diff --git a/data/locale/nb.ts b/data/locale/nb.ts index 3675b7f589c..659344d64de 100644 --- a/data/locale/nb.ts +++ b/data/locale/nb.ts @@ -6360,7 +6360,7 @@ Please make sure you have write permission to the file and the directory contain - InstrumentMiscView + InstrumentTuningView MASTER PITCH diff --git a/data/locale/nl.ts b/data/locale/nl.ts index ad630a249c9..7ff3e8735a8 100644 --- a/data/locale/nl.ts +++ b/data/locale/nl.ts @@ -6362,7 +6362,7 @@ Zorg ervoor dat u schrijfbevoegdheid heeft voor het bestand en voor de map die h - InstrumentMiscView + InstrumentTuningView MASTER PITCH diff --git a/data/locale/oc.ts b/data/locale/oc.ts index 58c81c96453..045eaf3ad69 100644 --- a/data/locale/oc.ts +++ b/data/locale/oc.ts @@ -6360,7 +6360,7 @@ Please make sure you have write permission to the file and the directory contain - InstrumentMiscView + InstrumentTuningView MASTER PITCH diff --git a/data/locale/pl.ts b/data/locale/pl.ts index bb0c64edec0..ff36a8daca6 100644 --- a/data/locale/pl.ts +++ b/data/locale/pl.ts @@ -6646,7 +6646,7 @@ Upewnij siÄ™, że masz uprawnienia do zapisu do pliku i katalogu zawierajÄ…cego - InstrumentMiscView + InstrumentTuningView MASTER PITCH diff --git a/data/locale/pt.ts b/data/locale/pt.ts index b375e289f38..f8cfe76181c 100644 --- a/data/locale/pt.ts +++ b/data/locale/pt.ts @@ -6363,7 +6363,7 @@ Please make sure you have write permission to the file and the directory contain - InstrumentMiscView + InstrumentTuningView MASTER PITCH diff --git a/data/locale/ro.ts b/data/locale/ro.ts index eceb45a6485..58abbba9959 100644 --- a/data/locale/ro.ts +++ b/data/locale/ro.ts @@ -6361,7 +6361,7 @@ Please make sure you have write permission to the file and the directory contain - InstrumentMiscView + InstrumentTuningView MASTER PITCH diff --git a/data/locale/ru.ts b/data/locale/ru.ts index 8235f291f34..73b7e06ad2e 100644 --- a/data/locale/ru.ts +++ b/data/locale/ru.ts @@ -6375,7 +6375,7 @@ Please make sure you have write permission to the file and the directory contain - InstrumentMiscView + InstrumentTuningView MASTER PITCH diff --git a/data/locale/sl.ts b/data/locale/sl.ts index 3ad55a4c044..e7bfbc3081c 100644 --- a/data/locale/sl.ts +++ b/data/locale/sl.ts @@ -6360,7 +6360,7 @@ Please make sure you have write permission to the file and the directory contain - InstrumentMiscView + InstrumentTuningView MASTER PITCH diff --git a/data/locale/sr.ts b/data/locale/sr.ts index 9b90164ab3b..183936bc74c 100644 --- a/data/locale/sr.ts +++ b/data/locale/sr.ts @@ -2956,7 +2956,7 @@ You can remove and move mixer channels in the context menu, which is accessed by - InstrumentMiscView + InstrumentTuningView MASTER PITCH diff --git a/data/locale/sv.ts b/data/locale/sv.ts index 4963b07a9ad..f5d4e0fb496 100644 --- a/data/locale/sv.ts +++ b/data/locale/sv.ts @@ -6644,7 +6644,7 @@ Se till att du har skrivbehörighet till filen och mappen som innehÃ¥ller filen - InstrumentMiscView + InstrumentTuningView MASTER PITCH diff --git a/data/locale/tr.ts b/data/locale/tr.ts index 387be6d8b96..b899337a543 100644 --- a/data/locale/tr.ts +++ b/data/locale/tr.ts @@ -6646,7 +6646,7 @@ Lütfen dosyaya ve dosyayı içeren dizine yazma izniniz olduÄŸundan emin olun v - InstrumentMiscView + InstrumentTuningView MASTER PITCH diff --git a/data/locale/uk.ts b/data/locale/uk.ts index 50df10e4b72..9fb6389c956 100644 --- a/data/locale/uk.ts +++ b/data/locale/uk.ts @@ -6361,7 +6361,7 @@ Please make sure you have write permission to the file and the directory contain - InstrumentMiscView + InstrumentTuningView MASTER PITCH diff --git a/data/locale/zh_CN.ts b/data/locale/zh_CN.ts index 63b22df9902..9b783b963dd 100644 --- a/data/locale/zh_CN.ts +++ b/data/locale/zh_CN.ts @@ -6370,7 +6370,7 @@ Please make sure you have write permission to the file and the directory contain - InstrumentMiscView + InstrumentTuningView MASTER PITCH diff --git a/data/locale/zh_TW.ts b/data/locale/zh_TW.ts index 791a45599f9..a3a727edb0b 100644 --- a/data/locale/zh_TW.ts +++ b/data/locale/zh_TW.ts @@ -6361,7 +6361,7 @@ Please make sure you have write permission to the file and the directory contain - InstrumentMiscView + InstrumentTuningView MASTER PITCH diff --git a/data/samples/drums/kick04.ogg b/data/samples/drums/kick04.ogg index 567480abd683475c1ba4325469adc111dac42b8d..8f7dce5271252405b7adfe8bc2bb606479c342ce 100644 GIT binary patch literal 12017 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 MGw>pjd%^O*0FRxTr2qf` literal 7126 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 diff --git a/data/samples/effects/scratch01.ogg b/data/samples/effects/scratch01.ogg index 9f216038dc49095c6a07a217e28ec0ab95842f32..0b05505cd3615b2d450a59b7719793c315865a38 100644 GIT binary patch literal 12370 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 literal 5516 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 HhJyb9N{p{9 diff --git a/data/samples/effects/wind_chimes01.ogg b/data/samples/effects/wind_chimes01.ogg index 35d3374a2e2465f38380d7556b5ff4c6fecb6f12..7fb3c441a24c9184ea544604b474bcf998450dd0 100644 GIT binary patch literal 57873 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 AJ^%m! literal 22654 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�N8BI=>A&+m!_r3kJq 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�Vb`AKuTSXj;ait*miRnbc)i?K3Y`y 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 QFJbgN{C~2M8UKIz7tkX;xBvhE diff --git a/data/samples/instruments/harpsichord01.ogg b/data/samples/instruments/harpsichord01.ogg index 028bbd912596ea0492dbcbb23f0cab73e8b9f371..c84ffd7df44519fc3323c22aac403ca69a25a375 100644 GIT binary patch literal 60978 zcmb@tc|26_`!If;Si=*Pu+QCI5AF zfutd(gl*p%9S{=3|A0AM)X5j$6kL%+CTSX;?NKNJ-c)!2JE?(M@F*v@T^9!ySNV%5v9ji#A;nV|qKr8r@5_Io!!} zeelgy>|Y}A$qv9WQf!M-yo0deuGbxH<{7i_Y0R>x2o-#c{GYF+S$;taQ8^xbBowEb zqk=b7qRxh|7z5U!+JpMqC|LuRW4-3)FzmL&=Jeiqz zKp;R$<*dqKrELdA53`Lrw9Y8)Kj7$6p5o>%H?MSC^6bi)0IBp@F8=p1%gTSb_{^a! z?Yo*+?;GFKLr z%G{V^DD<#y?{n%Ja74PJ# zExn?)tV{R77t$J?L3&(7J1hYis`z zn7?8(^fdOrB!@2|s+I>voDPxxx8$%j54^NEFxUBz+S)@J2}d?%7M347@wWVw(EpH} z%~|IPvd(2>jb$9r%sdj3S$MwwRQTP?@2~$~@Bc{7l4vz_Bgt7Dt@dA%!!c5{N78gf zciGp!Yh3skA#~ef+5aX004ImGbXJe7bk}?0ZuZ1oZ?(t5|Fgs(=o8ar-KGe!`2cbN zZ`u&n>4#1q@uoL=-nG;;`?0|&SnAf`&e8(g1JS%=0WOxBHRm20m1gAntkcO=z2z@z zwR*W$neAAwGgXg*)E)^|B5||JFuKJE$hPfZI%FGB*e^Ngt~`8n>l z7^rxMnVM(kESHz#?oFpE*DNh{a|zWdWvp`1+fH{EDn)=0ts1FyE_&@* zM$}_5?#5$hQr*1~;E3hCQq+P{l6S~Gtn^GT0&MGae#^{rcMmDg>+#l9uR#Fy{9f-c zqqDsMwArpMo#_b()vE3FzBoJh8UEmWfU{oAauV=pnzWE?dpgGhN=! zr8RCYOVvwz?X)h{^ai+=*T}m#qprN2%WHbPFI}o(ETC%b<FnU8 zXz-d^{@~Rf?jigZp_kgx;N@t6VMevRq5F;66$2kwmP!SNYPI)xXY17}23|Can_amK znpF(~=(Qu1%+5*=wQ#-EChs!a6&k$Pd;g^x`Bg4xfuXMD$dul%sbR#R7W^@47^th; zj4(Z9emNL0IjP2On>$>9DTIu(fJ%F@3013&k=o8IWu(xx+6rRms-=t=QM-2Z=5mMs zrZp#3f=?H^iFk)lxNmdIUJ84bWu*|NXaO{% zwBi`LF*P@aW=BzpMvQP~qq|**N~x=hllmDMOT=gH##$w%ZtfcDXFR;M06rk}e&E8w915~tXQYr*la`0j(5`6jg0LKzjd&V;8_RP?OD>2(4@G1zkl%x_rxiRemo6)uxu3IvI_kNxPK) zE#|rZEo!Orp(=6+O^A8T@$~me8c+uSkFs^2%sv{S+i! zv%KZvti|UFKBw?mjZgf4ieYFj{32t)EHun0?Qb!V#-DhYQLpzbH0*xOKgBQ?BynMv z%*OJNX!m-@JR}3t)B^B1eU(-%&X2wPN>p?(+#0}1Yu2$u(#1zQpBn%AnMP;-^Y*{L z&AyHP1Gvv8PPXN!BEM~M%yPv3Nkm}b`&El1vq{qYHBu3&v;Ak;KgZ>C-YkOi*T|O^ zr%3QQ8Yw9CQpEXswf8NxwDQqX^tMZ&B4W~uRY-G%mDk7w?w^CSS(aYAe9T9L8nhn7 zpGXPzp(?$F_FDWZXrVr+83H`08RH`ayaelInn#XAui3al-FrQT=J z>e97#cyr_|9-ad@YM~W~{zIw!zhe5AaN0=XXIX+@+{l;TZGQoF9sdPL?c@vgEWpi9 zs}!}M%o1HC)y?H!fP{-?Y2LqGmGb5CpDGf3#&Wm%%O-z)lD}Qea-6%1;NN<)&PtvH zf0BP&AO_^uEBOm>cbVl|epmed5c8`13qS+S4n7NTx1*~62T+PSK(qP_Km+~z#_t87 zbC#as`7#0L;qi`?*NZ{L|CFv-tnN`2RTsl-;9IIBx_M zHhDQ)=_rcm&&r?-lDO7_zyr4PkQjT16(qaay*YEH%O$+Z%*B?EEO#U(y~c0K@(#JU zTxydE%s!Ul?yikADAI?$qINENH8Sh=RgHRwQA*Ar4a#S*%QsVUT*5BBW&~DRj`iDW zYSr`wWSc$p-YHsMjXJ3L?i2RFG6QLNqh9%#2WCH>FGW2UhFvzJoQ$ELE$s{4Z#GuE zlWsNHRwmrKYBK3qsq*~P1`@d|rjHa3_dW~@-hhww9xj%lRbzQ7$;H*hOYhKZmwSilZS2sTXH+X?oGMk_ z@BJ33^MDIg+p>+E%iE5)j3NIWk^zX~#*$PiR#FlTELcuC1P~IY&1qfCV(F`#=CC7O zE#@6J2e8e!PtVL~V;YsnYige*;%(%#UC`0l=X9eE*C+aPwymewQSIo-UWzPC(86Lk zZQdX+RiT)@%Hth>9DZEQMOb(Ss;7<}8+pT<1VM2*6)hu6Tj!NX9ixj0lSm1!!80}o zk6Kt+FJjp_I6AqwE_ZYH_)n$*$MM;^EcC?jzqtgK^6V$z=Mwl8pDb`=$_8x;nVC5- zGc$K)Mr>wAXl8n5a^~mE8`plT&Si&k6fVr@UGv#(VZ-xjNMsH^e|{ozYQfjfPp^ej z0&YGHiLmMYyyG|P{=khrr{`^RJkR;^IeTIz@A4;#l6+K?@QiEpTLNKd!?$fND_iUf zw#t^ARNOPMe5&T#Z=ZQIfL-6!RP2wH8-5sBx=JSE!uzMCMPIwd23mJNZ)n{8Z94So zPgeJ<70NqXMyirW#~vR4+ShpGn&g1$ih8}no<;rH$~|>Cxz|3120nRID0=1M(9l9+ z?6b2go~qt|f@kh{z&ra$mcQa%f9M41^BoIMMYR4o9j0>OQQ9xAt#ADG#)Jp&RN8Kz z`y;k&+0W>2J03N6g=#C_{j>j*Qf!f`>(SQ4-_pApw>MuK&=W3KVcc%pZ8q$5{e7?G zZkfS1J60{iA5ltH>|WpQ8oBG4k|G-Dx;ZU|adyxzT)^eiLR9ALL=SuqfF*6rw-)2Y zD7k)4FG;`uqqarr`_~BG?-%>d`L5RZ8r3p5b-2}#mS7<15U^+Ghoc>#eJ+(z2Ui{w zjq~q5IGz_>H?Q~PzR10)OZsnqrHzYzdZJ-w5YsXq8D-5g9yXh3vQ`;>eQteG{}3&#f}SGv@uoAox0~3eC`(Kq)oVDp)@0K85;JG) z8mH#v>(lz%4qthsw*Ip$u~_k^$=f>$JwKeRpGO9!kuP_*ek>mx9W*^WRdBKB==U2Q zFGIM$owCPcK1$RHU;4E3{mV(49^(}*W3S9kk{+d;!+$59TuzE={QCN90%>RK*pl~` zVlAEpUs!M6a4M*@=0(;rul2fdQ!Ab?u*@GH4Sm@8?T3%Z`fHPRYxYzO*Gi^r_S+@iW`6NbdS2P=E7zOy;MdpASv`7~e4%ty-^t^nIq!=~PSd%GX~Edvwo$ zrf*yrE~+r7AF)Dp_q!!)G3GkQATTt18`p9v?00+72D1g^)r7-`H($R#u|ov6wgL!N zSg3fU$TL=7B`&0?c~sBpPKV!?G;Zg@TR*d>>qoyy{Nk8CtAu&>l-F^mZTx1A3H-ii zG2Wl|=y1Pj(bg5e-}N^f>E4p~{EyPTde4(>j714oDzi3uG(sQdeqV8OP zw4}|D$p`hy#gi29J1JeFV>ffx&p`BBOxY)w&Ag(y6*8BZ(~-$G$zdYqH+j2bvwv=_ z$o_Eh07rlm7=JSIM~LWYT2gLVDY;>-dXj$9fX$0W1J{U&PobOjo61YyRIEvN`^~n8 zHk60?Rj)S_kq5TfKVvi<76j zMfN%St!#CG+Vzi2Qx;ci7^Zv@RK0LkUYWv9C|noj<@GbDATxrzJhdf9Um);(uj0P8 zrzhI}kopXdg%-9ZUy48N`^o9r9+m?$7kKD(9HrvP%jo65)gh9JC@q{ah4*W$Y6c#K%NTQz;j0!7Wynpw<8l=9A+f z5cqf#kx;SEUk+@T*a@4}uMf zw9c(J*`vg#P1u*Cf)h0Jg+1w|em$R>$8?iSQnDyR1hb?Ba`xSEQ(lR%A1!=~!MFPY z96Gl(A*n{%OR3#9-sB+0(mJTkc-M5AskgV=BJJ_Hvrf}8zSrLGY`bzfPRFy| z)Gub=@|Gons*6?#`sxrm9$BgiOmA@`#cQULdEEmlrkLjt*J63rF)mjahi)iuV=HJ>{tk$09)+YphNcnda&mEo{DruKBAO$F zZN-y4ly6odWpbZE@lygz+7#w6HUq|j(-`X_w05fRi zK5NnY_CtC%mU&YvCH`1uF`h^3*~I>0ApZQ9cqBPl`pW{1%5lO8WnsGQ$79<16D0?C z>^g+|Hpr$HEf&ljY%-LmY+)`uz6_{&MTJI_>gDz|l;g_;wHn=|_=F#My@ns9W-02stf< zmx~C+!d>&vy)vHMXr01PTHbXb%k(ohHMg43dgo%bnI9`AaU2e`YWU zU!CESGIxpy)`U3i7V_&U3{n$zy_pmKl=oG}m0M>#Dd0gGa_@+eoa6R%z3cqwNv||d zE;(E9$x^Y+%u8<1^x+z{?wT%=u0|915Fs+2;r~dCBYJeAFtQex z$Sg43SG!C!ibkyS}K$1CK8->8wwcRYi|K45yIU34g;Q8^4O91 z+JUJ8J^KhrhF-<5F?g-4On|Gej3|eh-=#FvhhM3sV}|>YdV9X$v&fk1dwtXQj`o$} zW^eM^4<&uoLHxqgx6?diA;8(3ce`QVgu2|A5O><_1E3+3%#@?i?hU`RD~h3f!+xEtzrjy>V_L*dl&RBX?)MSf%3?->Qc(iW1Y@ z`Yl)8@_J@UJ~VMGQ9OmN!E^@&&W>We5VWOL=$ZU{Mhw_u+;8L$cWi*;&v}};djXFE zo$7!7m^p{aN&dkgas&SRZgU*ILd;L<-Q)+|G8@n06`qG(S%2!()gQbNf57e%6geXu zT`S93jr}Qai4m2a|DC-^<+epX!2`O-cT;tj5(7Egj9x$CO|e8?|5S~yYB@p<-f_eK zE-n%3ZZz*u&;Bj9XU?7xKZFubz}|DrUzlyNJ5IQ#{>J!#pj+jLyGh?x64|XvUBItD zwUPC6P;Vw)OA>^C_3aUUaM$)XzWQqzC&AEvKC_v^bsFd(2yvTsZ??yibLSNhd4~4E zfG6;ji&TOG$|!^b_Lb%kPpi>i7dfLGQ$6IDu%OmNl<=n7*BQfy!lgC2Gwv9^fVhMI`W@@FIRQ9(p$zuzU$vw!m0C1qMzT6hmPr^74{2LVraAIeEZA$z58lV?s;JMoM-XF z^c(ROu0h*~2aE%0!rQHngd)zrC zt845Kve%RAWo{^+U1D69984acM`C_p@wOzFQ_Vh?$UZk<-GxPD-7bl`GH#ufR8b=EyJ-iYpoKEF{Z>B8S9t7GcqOb^4B}Qj}4=ro1Vb8wM9L_ zo|+wng360z83ZM-`LRh!!wem6n%jNqnj{<+7u*)PP7{_->~Xz%AWG-d-Hf58jJ3Lb zg=ROp3C|m!S?fda@!Yi{taTI10+eXgX?OQMXz717a8ehB=a%6d$d$^! zP)Z^<<8vd#JMCiugjOkZx+cf~apKkX6e2eOaj#)stGxZ&t>bM;XAq^^EWG+@yxSSP5>=7DAwyYYh3 z6LVS6+JlOttlFmk>JBB%f8p|(QTG|9Wf_;sI=|7N}t8v zcUf%#mgzq#%|!8t16R?uuTq9u^u>my4YZwyU%M>-b0p=@^dxs88lV9g5|}(RGVFcp zkD2!Hgl&+O6fyu@d7e!CT@c&K{iAbck7?Q{bUQXv0pd z=iqkiZmO6g$3S?Cu%Thed_z)1E$P8J>{B(7SqnmuU#o0;uK26X5n~=g&XoN!HqP^= zuDjAMy4w88LP21FPri6Bd=WP_S*}M36(+78-Qh|L*T@IQiYEMzjQrP7|O^uM1uboV<9H( zyW^aTy<&IvTjO>2f|S=#F;jJIYMtMd8-`UKZ(?4lqH}mC*w>w6O}%hzsPlVj74dKh za}Q**R0swZu>cnL7=8ySiiJ8%XU9J^{Zqn=scUro`T-_ZM`=aFK=5SUn5i_Vo;@)r z7MLCAy6x*W&WmZA-M=4jJ`1fF{j%>gMoTqWn{wwW&F0EZ{boPB`;*CBDTyErHKu_0 z&v7?W^}VBSCzUM54pdMY6kN_NG7CK`2LGgf?D_J9bUaG^Ae#fn?_Fq>n0YhvV&?hG zbW7Hjp=qBBebY+ckv?A-DiWsCg&P;8DBxS(wyt|bq8|uqTodN?eESCrdxlx` zCF9dP=*<#{3&$=mp4@CoDDiB2PE~yVTNNF2apjtjA@3`mYvJiz|Zza~Y-_bRMjDV%YB*h!Q=cZT7-ShR+4NWI1 ziUAbItSrhQdo(CeH(YBK!CKou72x68v@>>~t-^ z8+xQ( z*hv1qradn_sO80i;R`hDx^WpCL7 z;ZlIMm)1#^;lIjtcGC zAA6IUpMU8yzQj&b^@=CPB1|fat@u9C^!{hqZ|#yIx+5!u0&h%e=EkY38aIRmzcN6A zSs9t4FgLa=xMKJ*oUdN@@nJv@c1UY*Ca(B_wN<<1c5e&6?DA|e%40@W*Vl-|`?3Ns zGF{ouSkktsz=+5i7gyT!%KS`_S+u2HMv`x01KVNo7v#qGMYKdsa3Wj9 zeA_p3x@`E1OY>WS?KI@ov$X=I&fzHPP?kHJ|Q6aWfpSlf4F`9cuFAt;b~E4TDX~6kkq4E zlk+HquOU1Y?<{EX0=$cB3+MJAFP0MP_E@UesA}gt0-o)8MjT&Y;GQY$e?k0{kV4d^ zF(Vs^@}!#m{qL$6bw8z^W?@nm)XSX}K>s}K0NW|n0{G&J8X5MOg@R0Vd#?H?IgZf& zcPKiwo#PKiAH#KFSJrWybs*GnBYtw1>pDYxT>{!1-W5s*-cSY!zAaq~Wz1=z-BAGX zC|<8+sDj}gd!%EemjJ$Q4#h&w7@!*M#$43F`_tw@2a&0XJTTjQJd+eUA1Bwpy4ZA~ z8x#{ZIHlMhDVO#W{wd6PFU(OQ-MLJH7j9XXA#HHri(booJsknoQwMhIw4V7jyXloJo~!rvCyba{WD~c z-z2UKX!N|LK%TJAP2~(Q-v2-;yKhI_W*pg#uMt(3XHFmESx@q=N-YYGHyvcXOJ5zl zGVUqXxKo@bva3|0=j6I<1G(^~)CAJ^&l(Lx*3){zq5-_}-sNOho7UcB%Zng`**ss1 zS$ZU62kIEF8Al(zVBu8Rxhy%Z^|7j|Tbb|f$(t69`eEr3o2Rr-oGRNNT=A&$of*oB zE^=%Uz>zbAXMBke$-9SuI~+Z z)kHO!o%)`FhCLEBVEF3DK$5ogliYQO! zV3+?vE_7p?1u!#7J(-e)NYp}^{;ZsP*J$vLe>CRe7HV^mBN9Ql>_QJ7UFFMQWm=Qo z0L!l+T{`-N*qe1(t8QafbBW_hkYfiNw^7EPEAOoeJRg~9&xq!oa6nG7!%Fd?M_FUL z1#vaG`&*F~Xk9RI^62M{oHGG8eAFUu7a3FUhhE=vk%dO@p=?;9HlO|a1=;>?1IhCD zZzAxfQnTtk+bpX00Dg`8CyPh3Ih(~ZC2YOCzdkyE*f<^5sa)xMqD)M#{V4uxY2BLE zHG6JBy!^lC=7qHHBm8Entt^Mmg~norR4wXLGyaLm_mXnyy*9A{%Z4< z9PSmCEWf0j&}P%fIjcmAG)(FUv6gb&G?Rk%Pw}B8xCD65r@+eFKR&Te*4CG3yTQPY zx$IKqhk~$ljP4Gb7tOS%HwtVCl`y>}^`XJw(WiY$A79FyNI#Z&DM3b5#*e-H$a|K{ z<{)X7cw% z%dh%{iEP274SGbp_R7(TJE;~<_vh2IhI52|si6)WR;%5VJ$Sg6VfZp7=n4hfd^$5v zEZkLTKk;+G{8ht@6s>h9mN?i~$0uhti~2YoZQ%z|iBhMm*yj-?n`EY!JgJ;~Q*oK5 zym)eYgDdyZwPj`X6n+YA4tX-Kvq{xKJ?ZK_|0V%B!&}7DVvk-M4z}iD)_pj^LNS0y zl-mX+p9k3mG-$|iQ({0^Z zZ@_cG zOo$?E;ye}b{^)Tq035wlaIEu>ybO2?e0G$mez>9Vus>Eba&BQ2bY`jHAD7;~%~0#e z-J~?C{8&~95AF%a6S7(t6JSzR5M$}b(BX2&95VPE_(i3UMxfKo+`CMO{Ika&IG=wA z0ILganniY}hbk{lBm+7b9k5@;+0@(7?=n>pn7MsuD|8KJd@h_C;3|}_a?=C}SX}}X z>WJXK2Qhdh$&!5{^2;BDY-tDb<9=9e-7hR;K2RlpT{}l1C}- z8&C>_^FpESHx|AXML<42sRMZX4S+R%{zSL$Q{tacHpQBS25YZ3EV(3yRw;Z1-^u0Wm7vbZTK;g zB5WW^gKEUuhQk}2rQBFrfFC3j2h+D0lQ?EGCe7H(w-@|A2@%|$C~ou`3_)^{tLWvi zXIUR9hy+(joWmqo*Qkj3?JM4?2DDv6C>@>SO8`;jc_jF$mk32v^Q)CtTbccY07v!^xF)>-lYCA=rIV0QM4rstPo3s^3jYnT`)h+v!z04J#H&(Hfg_4#bL01B z$a{8fs{J*TO>}boVY_k(LTk8r(OMgl;bYhNCUKm!oFLBj0lh1VkE)#3h{#Y`+P5Qi z-|F)_VUIjzfNi?J{QJN7`;ui(-Q?_z9oshD2Eyy16G(IX+7KdvxD> zk;1Jlt+b~UhK+jm0>+;8UgURmb2L8@${rI}0TM-3uUsyT(cJc3%F)KQa?1k=X)kHV zt2l-Hu2I1!=m+D})vrM~6~eu2@AgZrn8RuxZ`#a)Uz6MwDjMh1&TSWK`?`*%yoe~_ zca#J>f=L2+CxH>ET2JwiGonRz1a}-wS<5b)*C|4*f;`^ z-py+To{||l>EuNPoVyZmqIV_Y zTRsIHjZ)Vq>`3|}NUT(6Y$xGlTolF`BR=}qBBF^w;{DZ+nvGc4sOd~cww<`T(;O=) z(y{dK{hNurq`29iI&3?7U%M~#HlK=q0%#J-jt1o{JyDfaM{fy)6=NqfJm#mqp4-!P zsK+9{aR$X9HRMED3m)2W7+XB^752rU9CVVUP-G48_2pb%A=fi?mCDPz z5tK}d3ete*ph}fav3n80`9CWyP+Lq5|XhbxoMN zwb~G-Br%9sC&u(q$MJ`jw_XSVdxZdN-+?TEIOl`NA&sK)$ss|?6988dWSk+peIQaU zk--|LVn-)xh3@YmYR(A~A#_H_n4IdmmvUK(uwInqElQs2mG4#99bB^tp7g)}WKxFT zd2aFK>VT}y2sfloSJV=3UYm03cI=7Oz%8gq1IEcsDIdM6;!FKVoDs_%qHsMj;id@E zHNuEQX}3IcG#Th1B79>gLZnoT?RnZX3I#aG_x&f65XL9v-%J8Jivu26V-r#{bu*PS zCufRg4$V}}oS7+~xiE8LQ2l;{RPBwlQuPF3-F`QV=f5csc}b{sp`;7SD@+|03Cy=W zK81eY5GvR)?0hq61?Ql*ypIsiK;5u$v73-soPFYw1G(xwV*8}a=HA(xepq_*P!Qr> zA3=^Tp<_mxy^erm^T!tU?4iUgz-7NdC5h)F`c6WRUN+L2wn8B9ySQdrqw{`rB5ZYY zECh5hdpkRMx8&n39=h%E+%ze!mZ6_UDd z55T7dD0t#22*Sy|{!kcZE(qdZZRxB9R|UJEQ)983zJ;l)04$>zze^Ih(?|xDTC2%v zIiR&aGK6+?`UJP6L248+<1&>QZYa7lnhf-GjJj0d7@%B%JqDv9z>zUs?4HEleur)t4i1tIlag9O&0ag;C4eyb2mK^emX$bu693Aee{vYcDHNQgxHfZWekFa%>~sO(5Y01J}_PA zN22P#^Y@>M5%%q?f1;<7WK7B?eGTIbcXSGuN#gFd7jrvga6%UPovcxMM~HysFkETFo-e0g zy%;I*c3xm1M@9CgQC7`3_>#Izy2F<|9+Im!a@ljgJrO%MBb7}sSYGF$MI$2$vJa*# zn?$-iL9`->T3n&%NZD|LW+5a)iju!j=Bq+rS*T0V!H|1O->*TWP)$q5O^Ej{2)51J zYe_6i#9r5uFA_JaCkrc~oV+kws>g73JW$KiOt~uNKb^8go)_P*?V^X*b=wpBtVgY= zti5V4#GtEqst*rqA(RfjW?n@4?#!ieIjCfdjw`_OjlmdqB7}?4GtCvisjw9|r@Q5W zwCFdb%T($^G6lxIXQMqWMKO$BVWUJ~DV)H-&Yc7-$+L5ZwCUTBA)ymGNtFcPPFnat#;c24RLFMngX(iJaSVb2#+drqJ0=he=_? z2>dI80mfx;`b40d^ql!%CSXA@oQ?uudh~%_+H{ICpJ8AT<5o$+_r;fuG#!ez&xyBr zgZ9vemsgf{8ju}z)2IG$zaW?0V3nn{S;HFl&#w(7m-EcDAIIUoMckt@jT0vy}tfs~sBXqE^}iNDyg{*eE9s91yL)REkTnKF?cp@GPJZCQJ+G!R;Wi6buK+LDE4beba?n-<)+AK(Y(oKF@b{|S?n6-xvPGw zZhZ2eY5B<=3`3WcM>3qXcIv0$k_Uc6j{AqAbb$8lcyk+AWc%-R9;2=)OCfv{h6J5v z!O+MOnAl=S5nKwft2@<@OI@@b?q4){hQf|FI<@yJG{`we^ceaAr0G4)SaobdQI%-h z3)29BtUw!=E*aL19VHKdzCK|dxb;Ns1p4Jm4un3}Q{=5M+*SV(hVm_x1YxtonK2Fzy}qeOl14Z{e;-oKiZL4@9!gMGuMUy_v?d2 zkUPC}Im!GXLK$zQj0NY7Xavq#fU@@}JG~hqOU-CZe?S{D8cI|kn9Re#MHEGBbuW>u z4MYZ6h$Hhx3dfL9(2)hhY2tZ7*gs^K>w>)q#m?J5Cr7f$SDZzG=Y&S$U(Hs)F-@wb zZjPb|w_^naCdE6{d(K_yFRc4+#0>%-l(j0s@4A+`#2(bkqh3c@%Wv^-PIEiO28mO3 zZYslM!Pqa|Gd`(0liR6@YTx4H=K;ReLVXC|dbD+eRM$bPW`U8RF_FlFg!VFGe5Kus zCHNHuY|*7owX$Z3h~(RxdyC0qD$KnUnI3bfxP_@{#cpMV#1h%I7Qpi+nMr)!Y^MtS zlr;$gnjQYdPtAqCzByNPyyO_1RB^@bUvQEpwqzxY0Bb>&l5k7ZzT5yPOpn!L=g7QH zVM3eU5&@7nEagc6-AR->EIeodtGba=*UO@E>R&$*1kR_}{FN~OE#h|vCn$d5Dh!>D z+8tG5(6fCV4G5NG6ptd3{vfb)cf$EYafHncB!qqMt$>gAyu!d(OFu`AM1)vmHqb>>j8=bmttzk{9nPmAT_HUw*R@u1J9*w_ERfBr`pk ztgB}wtOeHM&dI)e(cbff!t(;H-tb*!M%;bujj^KWkYT6bw0ou4ZeGf!c(Q@mq}6aL zI#ZT<@S5lVp>d8MG+icXPg=5fH@DKxr&jGqUg|fsk*G-W+h|GLV->LX`ay`94+efj zY1t9%-2>doNv4y^X(d6>op(~fcElAJ)ETrMfV?RR8P_oIKWAtjz0SU<U-$dV zKCerxAmNrx+sZUc6XtLOdUmd`4cY&z2b|cfcU(^#RT5RB`iGJ!&?NDR{N}% zgj2qIp>K{SVP(I@BsM}Hd!qTZS#hTJK{nB zIZ0`(Qj$kFg$~2o>C%#W@=yj9AC2ZxFDaGG|Y@rC5CEb@W+`KrJPU`gV zQ%)n|^_Tw25$|bvDWcbD*cJIJ{EW)5KwlnSY7Sn%&1QbRiTcRXFdt^1^1)x#Sy$Orq*g>nl6?z@ zC3t1J;l7>3;zrQ1J;GfYj{hcM`kq>q^}LI2%4E)hAoe&%(M?kJK`6_4ugFGkwDE{$ChTG24+3>2RMT7`Md1FpNWgFL+z5E@ z60~7M7Xt#bVZJ@U(=!tI)&rE4pxv?YIL3PPP8l7!c1_@{h!I9H3*>CdAoQ&d0an{f z6%-63l=dUTC;)v2R57tEBs)~k$MC_<5gDcVx@f~^5ejOSrDXXnmH;qaj!tcnZ&iu? z)cBzPN5N?^+3kWe8 z7^k5}h}kKP2r)ytNJbf7#2F)qJv0^?*3P|OQ7vKMO!FZi@S@iW{TY|H)I>Dod)Z~G z@A!?P;0e-%1#v;UF?VKuk?Tn^QnD0q6Y>r4tLDXO@n8cb+d#0QHz@ss2-bvSz$8()k!k~zwRH}lASUitf8+)(Q&Vv^P7GG3Y}sO^m+ zaD`Hzj7unV2Gq?}LI*9W3xs-@HZeqcgXybFXU|oC9C`3O*I>Tk-0%n8AL+vMXZ>8F zZij?dWxj$0uGmEdqn~>*$oKxSIQ5NFjEjWO+wx*Ed|+#lSO@YtfV=Xv1y<(okKtd2 z&tYV*P$I6m7|zKOHn;A?LljZ2i3NRB7^cH=i-Yvy)xiF38II!MN;x9izrYP(hYpGi zbdjAwis|q^^wg|E=OwM)&p95e?STBl5LLKHLTP?Hh{ZHt-uXp`o*RjFT71)OWqWVi zk82mYdsV&bAg@;Qy(uSpZP-sH-TvhziQ>@ zVqesmxAj>0uifVP{$48R)BNZjzEVCL-?~RG-i1N9qBvqhd^f*DxN>!}oOSD(?v-T+ zW$;Qtwj?2k4W|^jbd;6in89^v_Wb;PUo$(EV3VJXc^x4>h3!SJ#3dn42Y0RXpDUDy zNsk6Mz%2~ksBY6LSa6eyQ4@@>83}xl^x2stxNBW^oS~rirz0(LVx02{ftxz5Ua#;W zvtn!HOgTS~n`vXzOV6ZM)(sS_#tEsXo~csbECM3beKHtMX(?LzR9Ju$>4~^CNf`Qm zuB8KO#D&fp7zvSr>5tK3p!DG*I?7O;3O1kp$gD_3oU@$dW=i1|F@S^Q>Z>K;OeBDb z=wvho+)Z#xk1B%MP23WKx)!uW!y|O6Q&DIQFOf&N-Al8Z4xD0S&G=UY0hGyp22q?n ztD#c~uOu@(#(HiD&cf&o?zi$Z4oL@!u>%x7JvBfvdE=`J@!6Jap;@))9`H|o;_z&X z*^>@v8L*_X(nM;e1mz^I&g7wg#WP1x>BP)ArPl*~fr}CiMxRob9W4C#8>I+MnkEIQ`iytS#haZ5a*f_s)ao#zs7!)=|BHRLG0k*1VA zO0vr*zEZ>7K+*l6XFTRnNygXZ&);t9RhhY88ey_^_pRz{;rOPNs(nju)-Sg zG}n;;+h)8V=Z6w&ph6rxsd&E?mVtg+_}-94VCz~Vudk&f(GUF?n8rNR5vmGO!%?DA zsT~@dk<<5q^}2zwZ4O?RkAZ1ABOzT9kISbsXOW%V4v>T4(e!!3xShYe)b%}=gFNMY zS+hX8dyz-A;?bk;H3=^Mr}LAjxY6y-hw>Oaa9{rlR1@YO(SdUxcOqpCyvoJ7(vzb{ z5Q~Yzh9~#7Hf-IgbN8f_J8Plb3g=%)vCrjL5Px?Vx92@^&F`AAk&p&%*vu}P`LLDz z_z{BfO?$y;aj>snqmc(pc7kDI`qT?E+xkV35G;+-Y&>s&888k4^Q9 zQ{Z|83iac1oq&}k8wGdc)osx+k)ZQf%OhdvA~7|(0l%tlYt04vfC^O&ohA|hHf%+| zLSA78oLW=D+AtzZkqZN2qJTEla*`H^0(&uG<3d1*T9Pr;16S7Xq9eE@d1Kk{{ixw< zMKO6~VR#PPbR^ohS>2Q1JQ+Rs)Zc?>_=Ui_7oFL&67y*PMukEuzQo9(;ytgB#3q7$ zE1gfd%9t?TJ-&ScIK@?!CGs{PMOfmSe$*qIpQ`}wF67prtTCdK+L0|ayQdDjXTI)^ zv3Fl)i+uMFmc<*SvVTa@u!Go)U$U=W%gcMV(wWQNxRC$+5n4yut)G;bz=1_F%NM^n z=aK9#*fc|!SimEkUT_KaSg_O~JB3Z4|E94B@I005uoIIxDeSk=-UGM9I87VBda+JJ zHB5%ORxV_PDbXoloo`FFyC}90ue_4`#e%!EC2S2b%=7;5MSDz4is~j-5fxaUS8WJv z-Y(UD8d@(MCF3uDY`sNcv29eS+~|gNw9d`H!o-1K-2-^+Q=w$x4zj9cdkQRu~jI(mf>EtLnNV>!L6x18t^Z z1IaHf#U-76N`mv!R<1osj*o0y;XQNBcdK2BF0l8?voksbpc3!IfBr4WF;gRP4D<*# zWF>U!c31v=B^j=NNH~am+hIMnnr4EvUq8SJ^`GQE4hlE0rvz8WPf`l2l?;T9iytsl@Z{ z`~05Q<^SB&`Qh}QU&ZL3>D2>Qaw zXC%+nqc=@-^T^}U61ye!k`E9+rH91_jM`SE|DKbzNhSzS(TEX$bDJ(Yg~vRG098q7 z8%LkPlT&1CP-^Becr6@xVo($7_cane#v$F*|GxrSx&-&^%|leWKxoAvgzubYXzQ0m zhov2j zD}gn+52v?@ysR^Ub9oU73SS2YcCV`D5;22rCKb*thA6SJRbU8v44#zYw!iXn{I>++}CnZlBN`h@`w+UVg zX|Q@i9+ocD19gNPQ^LUv_aPHZm=-4gO9>Y1d$vyG`_5Z+dEeGL8hJZPq^Fl3dc~@E zJyey)j`@;#dHFkCj|^^zmLi{%hIonx0Ycx9NB0}C>1Mu zL35BKP!?zDk1E6rQQN7|RE|Gr7I7jejz`dEX>CtBLpnYv2dtmtzqG-kpl@b#g<@@b zB_-I~TRzPKdRNcx5+`15TTh1ntfeGca6U;Id)b1uV@4mOpy&(VpH@?{yokj5vxy8_ z{-aBBrfP8#(I2@RtcO(ffOUre;jh+MfPgjl`Xl_P7g&o4# zWYnbJp1T5>ZV=w*0I?Z4NbVswK+S>M!1B`ozV8 z_HH$c{J%Pco{Ej~K|H||LXq7=Zg)3Wrsg$?zAKWJ{`rEJ0vMC>*H2xQo!KV`Q9QvO6mMva;O6it= zpu2PbY(e&>YSM1nDL?Gq0vyb;0_#LCyZ1uCAm7q(SaZWd;zz1&JzT9?VweD-BYfM@}O?kEZJp3vWd zp0C_lbm?PVhyJPT?CsnlSp&6amjGJ5kn?8`>hUyDQ>?5iOPCFJ$0nPC@($+rG zV5cqyEs+&;{BJoK%5U8apmEC4iGhX$6%LOAab`EVo6DLCpi6x%z~#LxAg?gSImueK z0M98ya=`seX&rha118@8;!#?_>C8ZQpdA->q8&zs&yg{fhmzN>q;i!%N$-|!)x+91DWAaltymwdc9v(`ayT~09- z0omDYT)n9W)pfHwh8=}vkGU`u*kn(q7ir>e^jja$r#4YWnUF+MWv8z`#_r3$ig8ql z;36Jol-^C%LBdvE(O7rc8XI7Z+u2c=8^6592(QR{ViFtV28K87s+e#+V|_#IWi8_} zFoo~tk!uETXHy}ZbsQx1dpH=C@EiFIOgWrN7=G83;CQTG=q!oPNkPMchi4_2ka;YJ z;`mX2u`>T@sZtY0OI_y%u;~REK9e!3V0#9MrpVBC;Nfs+3q8YN7zJi^XVkezNKnv5 zec?+u1YE!bfodFgb%dpjXkb!|s+G|#08JK&n8zv20j|{Yf2X|ObC2bmd6nAm*3TU{ zg=My44{q$UVRDkERG}PGu0Rb*Ai6BRS^oLFDPmzWb6roJ)hhSv7yIJAJ}50O<*5YS zPAi!?!NycID)j2d6dbf3+8O8`H-Hoi1ewsSQvZ_s=5Fur&?i~TN%LK|6d<#qps%hl zO=E1I5phv)EmEXrRkJ4*2*E=lHmUNN@J?w#{2k$n4ZMRHr}9KsCBNf0kkW9OG-Zkz zkvMVCgYz>q zIOz30;>YD2$z!A49rINB9olv6=i>0e&=*+VuXNG+5+MkeW1ydj>BXkH%J(nB*t`|39>IkeZ!T2{h{JsukU`}nR5p)Sp zkr4r+Xkql8#E+tpSL~fw>Unr0$uUzWj#0SbSgr+Ggif+5p+7%*11jl8E2|#6*e5NX zAAai`q-J8Cd_z50rBBci&%t*yd!(t-5P$Fw(4Hu60+BGx5ir;?3&?U`6Rs(n_#;U&pH(gpPF z9H+XWE?+3%f#}FNHW(}00gBM=WLEtz28h(Qqi$i$3Z^GHs;@-btDsJM(KYS~Sa3o>?#G9C%PrYE-E zuI8ytH!U@$H63idB1um66RVq@pO!gm?skNqvQOyQud1iKM(!TNe~B&pgswtH9nKLq z{_OCl0w^5YVZo!HX^-dPX!zm++aGMtkwh2Oz*J3-?^aDPVgfPGL@LVwdaC)7LxYhr z#P2El09bRYbc$;l`#Q7N*H-OoasozJ9+(U!X9wRA1Nys=e3IzCtI%>y|N4?|o7`dYY@SSj;isGW%&vdBg1!uMrB zJS49J@@59aJG-zXfOsv#a+iT^%q29M2TCJi4+Tg3jM%&d{5J9kTN9bnp>nzOcFvA$M3cgtm>nI z|INn*sHQfoq4Un2+7Nd0^S+4ox_nv8{DR9`JK3@Rt^Kcz-I(E;x=kyJ9l5>Edxz#; z&Rf^8$XzP6WO&e8^twD32^}BhE+OX{M#WygLJbD9}Zr4R%o`09w!Qrb1fLP>B1CHZHGB5-5fRnKp zIPz}boQEtSdB9F+9noTwtFllB;k%OM*Bo$o1FB5O)_t1<#9CJ+%H;o!33SA;KP#XBg#Y4WrQoT%;P$=H2l~W1ilA|Fr_? zLG(O(It+}pAm}#DNdVf^Zf!s#r6xj*c2tZCJ@t(jL6lJt37MK@dN?s2ZI4_KUz0`u!dsfViSPg2)fR8LFR0uM!q^S&Wq$JnDq3lWfU*FvWW za1Y@~sOBv!3UB_HVouOeK=G5i;GTnvmStmVbqMvmIaG$I)u|F0#{DSN|V=Hw+GUbU3XKILjlX0W;Zd`KA zt_AN>H@p?>H1K9jmL$!|%DKijY`UTTdqXOZI%8MkJ4Gtbut+7z{%N%G&lY@WOEk&& zEA!)<``>y=UT}HkpkW?8I_s+emx!c{4wB0;5n_~wUWG1v{qAqsT9!hyf zy#VJ8Ayh7W>w!D{9N@!x1T_fubssVQ)0un7<#j>=VaYdWmPD6eYD10URbURQp(DSK zOqyeR5RP}D!E1M4#F&utCq^y@xdY~qq2?RX!|PPg#EK01RN%ziK_4NHkZ^8w0dP%a z|3wb*1a|)BBNDJ{P3T=Qn7@6m%*&rTX&iZ}j`pG6mrqhHzlba+&)<0QCP;LCx8(Js z16!m?qVKv}E8A$}ix&Ql?*45*;*SzbctMs7H&6|1iSx?4=UlyPLASTPz)u)NuT9K0 zcg-Vx&YD30KZcq*HzOJ)mf%9YphgrE!tPcK__?LYT*^bRqb$9mwVZfqi7Ckm-Ghl3 z2{x(8f`#3o+rhSARuhR-C8R1J4dP1pbEIJjoS-%M8#DIa|3m3q5ORklj0LWx#f8Cp zi!=A&+x}E4`@zf69AoI2b`~eshcvl`w~7hsxe!dsKowv@g0y)rI4ZDVecp;B;QS(y zINScCL9T%U1-w<6vc!h2MYymufd(}B3UO?$W;km7HKNkAiwq%jj5`9dAu<9fAo7Jl z7^`Y4Lgk;Q5jq4;(*v-57KWTC=9&m14j>G^ANi)%exhJ7g~3INYQ(=RQV|DXq8I7| zA3Xa+=U`FZOy`(Hkg`Xv?bIf=LK1_e3HARS)Epqb_71R-WJ~8BTpEJLR0fpJ<27BY zD~G&Iq1>X(g7+_mDf#GvOCbv?iuen6X;>faJmRL)wC(DA!=zur8&4k+4ztH&bbG&DW9i!1evJ zbJDKidBhr})Ra4Xa$TGTva5i_sh6CAEE6~1bKpLC%vjir*bjJhE&hJ#IS#e#6e zu6FA{R1zM1`dL_cGw1!S$P)quzC7`R9d=K2y74PEDG_5xxKM_&S}^~P)I||4pPF|C z!}-_x@~j*CP0og6=3~tT7!i2q3dXw6R-*GRM6r`0sY`?2tGLBa5>!R$0P%>G)xFh7 zhx9}85-eEeJU0zbF8gal<>bb@z~sja9rDz5KL#{yMKnbWCtKgf&?JKL0~yX$FQ{|L z#DrH6cqi(}R7ge{C)=?BxC482K_I7qwzUNz$c8keqbRZ%+QNV})S;s$`jCsPz?CTX z1>!g}4dF+CM|WFCv{`y@`GxwKvgMMtqE5tvX&IVs5Ng$SP@*z6KjeUz)B0@!rK8H# zew%mb=sH=-pS&#H*cY=j;LnYnxXDZ@f_di^b!YbP3@&uOGZWKPaHL-qDG~Fhg%qKM zXpz*As@2qJYFhT+IP8m6QS02Dty4Fbu%ne{?_hS$3!CPMVWV2u8GX2ROH8EYL+HvG z%_@Nb7@4x@?3ZFaO8%{T)C9Wh&$v7v7vQSklt5yuxDxiSFSg7%(PF{DxVi%4oyVNi z6#0?KcU6JkH%mj7mgAbRa=$GdcHN1TxtPYgIJ_TTq{tk-gkAYHwiJkpqw_)DXplhe zI;!Ajpu)c6K+9PN(1jWfqDdP?V&Kr(3}6LrRwj6_B`{ct_|cVl$e2Aie-tC5&7AIy zIP}jGTaSd&QZy5L7)pmx?*m~wcbL%y!hpP9Y&;EP>3Ji&N<0RfGzvjG)KG#L*9mdN zMdT4FW)}hUIst0F#B`3G9Su8s)eJM{4~~~~2DfavY!P)Jm5u*y|JZ@EfIyiK<<2Jm zf(Fz7b}i|6vW<_&&S%{@l`$@3W|hS0+U>mD`_TBqy){soc{jF*p8x3TY-T+iH+T=aaadHaDat1PX|kR44_z z5)(N1rQxi@vZ%@&%mHYtpn|noG7H-8%Tu|R&?S&GIR{vVh6^FVG#vz%hKr%?vN+3M zh0`+yECozp&4fvRaymlHgq+}!27F0ThXnMyD*>w;6`BWB!Nm`{PrDRBlsy5qk7Ri{tFlKLqK#{UasEW#WFzs9${hXZkn`2BcS$lQGQ_-j24?IJi~Y`>YZ<`axnjUUT}>Gf?lFhyx!YV&1v5w9kEPU71nf`*wNNH`7onK7D#)>} zIv#n8Z&LP*Ts*;=vGwez2&RG9zgUP;`w(nE4?zH#acf~#cy^VCGTyh_ixN?%N|C-( zFtjyOt@R!4o2=md5qrVWKO|)uI7PnXo_Hhwmp!}WDxo?$Hmn64rPg?Uuk@}B{fUB$ zM2Lu}{c8$STV+zb{97+;FeYYSF8W)Ql}ay4yGGlL+0$+y3dgxoa)EF zFb8-mh-pb`O~3yh__Y95e|3!Ug8psVK(fs(IN~>yw-ZB5Xbz>C(DGx^z-{$gGR(dU zg_nPpR(IeHgNY>IYaOCvX`AC-;jrvlw*)*%s8q#rw=I$+97J>HX;L#sG^qR?%UpqZ z#y|o-JXFYnCaowYSgT879MMXc`a?SD#?Ahb39JqeBreE6mjte?270b@L61?_^Ro{% zQm>-p5<^o{h}7rA6R~Gh&^TQOLR?h{hBc98mLk4`l%k5AtZ;O&oPKLye$WI*L?5rF z{5x`hsts=!8V6WFdrnF1`& zEY}N~Op!DiVEo9)>|8AO%ISx<`=iXFvWwwVLvL2~9QU4W>8DA~+nW`xq^6qz1o|n!5HHG>oxL)9ceZo3Z?+$;b$E>ae%~ol?Z(CU8*868%3i8G zT=gLEUw0P#;<%gExT{QAiDp*_&QOwC1Z8*k)d_DZ$bElL=_UVsg?9|wJ9b0kb5hSs zT2P8tYnflTULDo2X#?e*Gc;La=NM6}#bd1J=ixeTww$}?QEQD~YXheZ7nw*2bR7JD z|MmNoUH?K;-2~V9$7R8Uf=Vw8 zoxCzybLC}X7_j=LwNA>&yS4$^K60-UT~7e&3oKO09cl3l!I<}m;;k*g>{k18+h`wh zTMD9=Ka4Dr3sk)`=vLL~=q3Jo#^$(DgYGx2`A_u(s2><~H|E>})sl$}ra#-bN7_+c zTzZk^Ly-RCoSO2gHOn0OBzT+guN z)^}GiXG7p61nGz+twq$Z?k@Plmo)uR@?P;#?Jpq&viY0`KZ(78Egh^QCj>QP2l8X8 zF8ST|=$b$aRWE2xR{m{QeN@HIcYH3L{#=rJi!glpcu79cp#ANV=NreS3@l;jkDoyv z*!*1u%u3#QoRjnjd8W+1ChU_4Z5)0pMr@d1dULU5L-%tuz=IDw%8g>2TJp?84C8T0 zkQC#kNX)r@xkQ-=cQo|ClJ;y@2Ikljj1`J2!u!%wBrrf&E(sB*9A#oc9r$Z4f%xw{w%bp_jg%RjPJ!xj1ue!BlO zq9EV2=4jZw{$CgHxl-*C2q%f^=0f}Anr0Gw5B=grVgpR*iuEtnLF;*|v}aGDhYfAq zoe7c-{xF>ZedVTkrDjD#&J#K@huy>`f~r6m`eS_V4 zP5pYF5@otg$QZE4NS=THu^9+$*i4u^^q)R(dta}-k1HosQHb=~ES%H0ra>EoIXYl5 zH0@pZc9o1N1*`kiicI%Q?MVIV|2>u=6u;sabE$hd8f&QU0p?E=27I$ep6Eq}4vDyc;0b@FubZI#6r!Ij@-cY?*F8XQmkAC*m58SuxdlHWfN4x7_~PJXAV!n^ zYLA+@9q=Loz+Blg?Yfc#x$rK0V6M4z#KGuPLZP3piJ@L#^lzPFO|})MNYP8oG=rub zC)R6v>84Ha$i;bich;Pv$jsx**B{)W@Nb{9dFjA0(5jRnUcTK_iXFeW2?KA#Tu}IxkQFMJ z&s{-4-BLE}(6uDPHhBXyrsJ3mMNxA0Fy*RBBASASq02bO2LxYS5s-Y3WQp!TWl^)| zZRCK#_;%XhUG1X^$tb)6?}a4d8;vvQ0sRrmxETWwu@2M1;mkpDF6qzC541W}Bqa}X zN$(JFp(5`}ZFf6b3~(%PaFAU3C!Vcmo%=JhJ8_~iY0WS5xNC;KrhUdWy{(?6Uz0oz z7oRhXs0!wVrqtUz*>Bh>-*ZhTemsir;-V|>brmavNiePon z-MG+bg{AXSs!DMQFTU|t=@w0~)K_9&E(f?jZVhVLnF~X)lbMaJWk;duv`!9^porS9 z!k?Lou%A2HF1lV{(rQHBsMUKB6ZMie_R2@+YA2KDaz?ch-Y8k1=aisKiV?pqk!uba zq^S=`x%}$20$%uW<`zq>TbF!l@0X3$xJLz@eHR~B!OMDiUwif@ISEini&6NmcBmjt#wcVBfOuZR-7%Wze<878-U)8i_N^^$M|4u^+7eE` z$&bD^_kJYX>FS#o$X%@@SmQ8thRjJetRP9!>;$KW+$j7o+5d(0oNeNqKgI?X)9o!Y zoRIxZ!4ACGq$%-TUvg5kYh;suwHXiB2@t$(d%mlv_0wN*FAIrUm6miVUKyUnIg(~^ z2x=8tEpMw-z4=6-W$$6whjU3Yq6Fo*wD{ z_+7fXVer+Wt{1z{w_Sd2eKB^G;eEZua+{Y>0g3L1|4Qg){+f+&;ym9`H++Jo5NE{T z*I#VjXT%W%n#r$!c`h%95<~xvj4OiuyHxq0^)L&kO`fOKi4$}+$eA|OhLgJye5&hc zBgSv0h(pp4A6&nSCAec;kBt~PTu($B z0S`6H1Y||*!aN|dB;j*0si+&{WMWWf;$25L`=FBiUUHWx3gU-te?N>fk5$eP2X!HY zC>Nmd=Iq&dr^ozyecvC#v-#7Eynr~GTOB>CRi=geCVZcs{k;CvE1>jby)eH3&B z#)v`KjZ5nlz;ij73u(t=)+f1xKlY&T8s&oD)mL9WL_VD_C@dvWhcrl~ z`@hegteoi_5}$EXdoks($>tLi|ATS7KmTgvX;4nZQ%eN}l#V~)L&RPzHR#ryss9a{ zSXA?=$=BqQ!;g_c6vHg|;haJ`iF@5iUa}}YZvn&V`uwzfpC4Mws!Gu3%kLYq1VE)5+ zktu&64K6aUo`TS93s5ibk;B#pjM$k0%@nybi*RyMN)L_1w{&VkW6r*05GkXIn9JG$ z?&jjaolvPG{WI7czqZR02nvc}>pfIsT!47-_a+I77n6a4adNext=|x>K}tbH^jp7x ze5aR!7BomM;jO=O%!fLt-8tfsdyI3rSWA1wyp883Yy$s#&}rMbF-G2dkKq#^L)S2k zRb$11I{|?vUNy1DFYPlkvB2wtIhG7mKY%;eizGiW?>`KB9OW=~x!PypoI|A4r;nU| zKKfXbO$xI!{&36YdBRwF05MxG8L~aeDq(+hj?$~I21<9^Ls;SBDeh|Ohh?8GjCy03|;(UT-Q$!L(>UqD0$O$b9apcM+W5PYMRu6j4gl?b{6%IypPCmmJwcS5hVc>jy zACpy&jOjDis}ZAbb^=arIhRnAhPTqxi`RY0oUPJ)fS(2Okc-SD=vG3~t*~AOgg`s2JrO-G6xfIY_F=6&>^!~3h?;%gLBWgNb?R+z$15&yUfmCSH4^pE|=(* z7@d0GtXxQU2Pb#6hF0q^(~i_R-;8b#Iy2XacK#>lH9YC``9Cd3B0#7kUMSE_ZeD{a z4un_^FOk@uJ>Dmb6^M6?V+u|C_&?cT;6t3oTQ)6E`z`6e5fjOL#+q4U`%t3sy?<$%l^_O3qtur_H>Y`#q(yvhU4{PbVhVBF8{r#3qH<3;L(U zp4uG%VHyZtcRaXV+}|zBEE=7CIAMfGzFndWdw&&n5DEO$-+2G#vw2LTw4bsr7$G=o zA`Vk2JJRsOyW&*o>?1+G>d++6fOjfq7J{HLbqgC#YO8{OdK8J!H2hI5^TC7c4+$}3 zark7t}GO@#0eGyLT?w#5djvb zrf7=g_`J%Z*I(*s-HHPJwSxQK+;lF+WO`2FW0x)-qv!6>32Gf5`H`}wk~x3GncOeM z%$(OkE`NRYp42g9f_*Q#l_=GuvEZ3rMbCD^!oLj7*ZiNJKtkmOef={w{L`hz3S#!i z&UVb+o4q~Tj+RBfp8Y&4oE<=cSGD~yxI|@E|IrD?k6q|H39S|wpcGJ&sar)O4P@fk zUh#(XAKU3`kFL2Rf^u$W6SR9jK720s% z)6oT|>=XyG#ulmr?Ti0PS9m$=V#eY6td-|Hvb&H3?0b?#&pxei22961Aalquj^O_8 z7?rsBQZ5A;_t<2?-}?uV*`1~g=^;Ffw{|my_e1#^ zYKi-cA&o&7M$04-m}_Q0(nzEv4a8MJ5O)HArA08{pov2@L=zz^n@-!~oO z&a$)$h?|-cUc#MP%3oQ#eLekDMYALRVM=cbTeVOd@-+LZXpAy$xRP`!kR@2<&@SZ< zX5t)q;>w=ad2?N*QSK5rF$Wg!LCe%GDKL_jq)n@d+4bYga0!g-CQpRi%`(;fA<@OH z$avg&tvb(M>g2-+hr}N1PiOyoWzgeQ{pGfLKU6hmoiR>1oMjrAxXDE3RNC*42I^op zt(cG5UD!HLP&jq%=ziet+zo^~TAa>mMEfmQc4&x6%*6mW+tB>!THri6VHCiCeV&0P~V|xehc?WT=y$7mKr`%+fV;~VXnhtE!)L%!i!cLS2^kV z&0FHe)sCClWm}%WWy|N*2Me`<$p%4hAfwgna_sg-;3$TPv8K5Gi)J&agzA+iEvc^0 z!mrO*rV1#l-6e9K=@{LX7R)S;v7c_S>%n$Ma;|6%P*T`aiBfKm++qwf-%As`|9R1xgXq?R}%sUF)8f1uYDTc_&=2WBsW3-h(=y zlQ7*g3`gDll=Z`(B+9Q+`fJ{DtX^tR`oIfQ>$LVC8~L(5UY^CD<{}S@&mj<6$~oQa zoZV)5Tz;#tTTvC`_W#8pCdg_TD4*4_S$}1I{w6W*rnfH+DzgP^%RKNY)e#Af_OmUE zu$Vvf`9@GRL1o6BzeU2@3Q5dbB?$eMqt01jV~k#&;kZ$TBIFzhN7gY0Jba^W2CRDs zXo~bPxU&K=%{C2nqxelyFRq7Z3!WqIpY|eGC|Y`xhQ3!D1~gFAVj3xVC#x;UH_;Ux zn**Ppplt82?is$)B`(e6#Oy&R9GI`?}`aE0kE>!MRT`YdTWLh$wv(8-Rlh3%>73T0kC_h5fK3;XobiG#S zHNu zf2r3TM^^p)(|*{VOGHoNjgh>g!ZLzhagFxgrAC~m0Rd?61(c2pFu2Q4R}DO*G+ly4 z!3D9~*ijna)Dr*HAX6jx6zT|?U|}>gY_aZH)atw3Vc@}jtI^@6YnJze{E8o(?Gcb^ zRmaicqzA$iWhtmx=DJ>(nZ32g_j1u0idp^aZ0Al0oi?mPD>rW6tW!QW>zN2`-Ax+E zzh$RGK{BE|v^>lgl}sNU@ke8b#w7*rY==J~uOyi7 z{I+`I{IH#hz&tg_JxJmBgAC<~d7XH@5oAB{sC6lAoctbv_r*~sd1{&n*)~*#1(o<1 zEeLq{^APWRcFtW!-k|J*)U#`+UyH31E)gEl2{JT8RtnjaIBulqx@}VGup`SN?JDtm zGX1^eM#c^DXMqlbsTU@{@eJKN^(85pEbp_md$GJ#X+Enc(pJDCU5QM~|FgrOs8(L) zukowPhe$uGjs9l(^STaP|K+`Gac1T7VIQXo$Lv(L@1s}uW=%IPI(I){@Y;jR4=$&$ z%d0v&PWJggTuBiKrj>;@w{s(v_nvO(jP8~dnXg7;mHgoK!geLw@$+W&+ay!u=RH1N zffg;EzUO2F&8x}4T3%>$+);A9XfFk7_!4a4FKq^}=WCKU&vy+F;9v689b@h^vxW*G z1Lw$H@WQ%$(2%E4lLUkakruus;{(ueT z*dm=O@grx*o)gpo0<|_>M<;Po>mdNs>XO?z67b_q>h4ua5vel|iTjt9Z%WcXg35I&$P}R4=eox({4aM^e-v;}uklrq9 z>i53w#$ML>_S^{WLO$$mKw!MSfLegDa55y4ZMkGsk?WxeG%|^I_ z(1|fz$9LKb^_a*(QsfRN&q3kAy~{42w)-wo!@RUJ^OqcK#)MaMUz-pqWJP~Bw3UnU zd(QUfy1v^Bd?>#Q`lO|PzZLE}P+T4JeEQ||fw&!SM!sA$3pjGJJ!RkPx+h~v4-Mm0 z>B+9xXPeD!BY$2>DwIyfZl$@Q9^*YKXVtDOY51^W$gE$qIi-&jelFqfoF`E4zAJ2@ z4EK8tYJ+}sHwNuzR{AhnX=6ouSok+84b*cG(>#)m#zP!X?k;<>f=A}x&WQk`GY>(S zFgs;$&K<}v%0aW6=-puaKqa5uVz+m3rB=&5iLe>Hoe_+@o-e4_ z^22lcACMgTq=BY_5##M)8S!mmz)?F#VL#oFUgEW+_Q|;m_~ZHmo-Af==bWW~Xv&&e zH$3i$^XuL#L|pM&YxwEl2S(|`uIi5$*Zi{Im;B|f?c(QAZo9AdJ-*XL72CbN+57ax zo6<_B^6`6yRW*ChMAarX{n>>p{Zw-NFV-DT7iLOw9s@UmLR=kBH<~dJU!nKaGG(^* ze=Qq2;x!WU%J*ek&Yaj|TlDw_eu^&QwPZdef4SkZ>SI|=g~*L(J#5VXIzpmNU0~)} zjQEo(V<|~xc#B@1bCj}u%(W%_0y%eX175uB+&RvIdUlz1|IE-K7M)9Zp76$ zM6JnwzEWT5cuwV*m4fQJGCbg^2^xW4zOLiG`Op2FtWi0?<5U-HzW_(pD98{^Ykuh7 z*%~Ib#X|6cYPCxZXk(g%B-W)LRlpgsDg<@j=A7#FChlw1nXC2A`b%L87KWqw9%(dx z#W;&z7}q9^aPbkn(&FB~$QRmioH zCOOK6Iorwwq#~uls5DM%&S6PTubFVq6Cl2d!&+prLap=6*OcqiF2FgN7RiT#+O?el zo6cSCP;)-KQe@MYT_4G*thx1*{$%YhOh%=He{E=<2aS>r}lco{`GEIB{Y zTJOc;yC+Um9n}7>DtNx!^=^89UQzL*6H<2emBl9&jP>x>>ejBu@6^bFV+W*V00_{n zk8vd4WGIME?dBh|-{s@sATICZepmA7R^GJA`AmH7l-R9LN&WG1*SRtMV$PG2H^c^| zYJ8idZnW^_ZH3n^3CfB&Qd2)9SF>*!Ykx|^8cxX{ej-@jDl?EKT@cMi`yd-BA7&q7 zRL1(FQTOUTVZ8f(FXrT3hm z{)VphC1FDRG{>!HaNJ4_MtvWIkOh%w)`ar+sSa9OT&@+YCUGU+lDlhJw6P+Xc=aOn zNE+vB^pnvRC(S`5!LQOr*3sY|fW=xA^=R%G2)9Ko60#D|Yl}Eq^N920RjG&8#_pQ70ytLj`^^h%AbM69hzpGpM*SY4E&-2orMr7x7^`%wbdn*oQ%|g+4I7R%l&!fehT0%TDmaox+lw1fW38zn+3aZ7s zv&8vIo^a?)i7dR_l4W9~=)VBa2A_al3CEWQBP$kQE{q>^OZ#<~Y@UoWW!$UtHqZ4^ z-@PL`DT1dOd+CaF0+ zSDp~L-uF;sy>Gg7-Qz!_DdR8d#QmCwYr-IPQwnhJz9B)>m4~n7S+cpn5qt<$2g|~~ zd@^Cah;(}C?u}-!>{a+8Ovk3L9f~|NQ9PB=QuncJNi4A$d9L6^vL;kn0iT2lyEt)E z0R)e%wS{ZVK@vR8U96kiF^FR6F5i#1E2#ucm!oM-X`J}tpZ2IyY*Va+?fd);XVov~ zgTu|H&bkB{L8e~v#kb3SP5PIgI^ezc&7+cKCPl@kkV*QSyvgpsXfN`q{<}`gs&@Y^ zKYz~0gg0&Jv2*`C@OEBDE7S`$BVE!=x~*4j_aBEm^mfITvyqdf{YV~rAR}{H`&Q;@ z`VjW&yJgYe8<+oAq^bX(U}5u^z$BGSE??gF|8r zF5xicD=$s0`FP5NB-rU#!o|90=SwRNWJr(VG^3AbuDmf^(@1dez6%dB2e*Ld*bG?` zhBJsXEa^8C?Fp}zg-aT>B(PqmiRJF1W}%rTl$OVXt);Q!drl&@tBHfrF%!gD8`0FE zTm~DXVLuFP4SDv|Jzh#WhUSPuE$@-pw2X%N|89QX0U{d2>vB$~p)JhI7TNX5LBAn2 z?dk~giBZpg>-W^JXlOcbA=bah7NE9SS#W&ukpqw3d^932#ia^b1MPS0*y`%wQ0cp( zz_#;%)c&T&%k9ZOu=sUN%MHv>Ik$|zdUe~qle%*b`TpvG4eF;FA3P);fbHb-;aJpZ@-Mw5`{7f)3ug^xErP4sz~%)b($S6mvmPX zY#?>JA?iJr=I?nkPrk}YK$p$yhZakwr>P6x=CL*hn$2G03O#152BJ+9tI5v{5UzfD zefam(w6**bwVS;|A@Rb?A(l4N0rCCnZ;Q6+srelGuZVe{l8I~QF4@z1ePbk(PMD_$ zGOZDfnVt+~_e4(aUN6FQUalA9sH{gGRByyMzvp3fN4#+f?zN{(F`;1H)?YxBh(p)5 zUFtBUnRK0@2p(tA0)RchH^n(=;j*u#6V-ux3LI$!WnDLiFl-whdW(dM9`lI%^A*<< z?@&r`+!m!BC?0gX`7UAE)Oj~l)Q=APm?&L%kc>y(t4t{jA_WmW&GNXG)Xz6`Ay0$pqea}?t6T|1~AEW1Z9w|OubvH`!ly70{ zHo4WWibzKkK65W__;p0OxbMU>OPxELZ6%9Z6uQ-~E>b_N;24PIn|Ga3s< zeU;4ztoMa`z9gAs4m_hv;fO={V-zIjL_q9^7)-F_hQff*W zJ+}~v)}@=n;M}`XN4H!HxEWPZV2J&9afr5UVN%l~zIp3}h4|_YrFh-1D@69PpT+X> zwEhjrotKwfhlvl(!N&Ct>rY4H{zFOSf;Yl#ohW3WRna}m{imBBb8 z*^9iYZCXzKSGQY7bN?R!h(g~`aX2gv#6&F_Z6Iyjx1BAhdZ3|Ims65BFYVEV567=W z5g)W}k>Im@9*H>p@Tx2f@&dCYIA3_Ks2EwHs~4Ovb%jZG4F`&(L++m9Ue;c2GUwkN z^wS4j*@W)_L0xXRt5m#L#01_I{&_y2q@Gy1x%hVmHLCY$dqH|!W6xYW*_9s}ja+9J z*y#vKE5C}a&}>={S&=s`<`VBWi%)iQorU^XP2yQxeKnROAyhdme1MIj$ss>yhZ7#3jh5Edst(D%eC)BtaM|$w z{V;!gqTq{^PpIk7>kqvC3~OX1m(3q@+9N(t5Ht|BXFBmXEo$r0&Ugy3db;`mk$Yr|EBBq=3r|2{PKor#qjAJh3UWRmMjr9Bu$s}+b)F`}qp zImnnecJu}@^5^qcKzR$nTL`kv!ODQbKV?Q}7lna@?k+FLyFQlS|! zKW1Hc-xn3**@P#~VVd3bzPp_csAN3+nBh~j?qJM|A3nET-uT@7e~5bPuqfWJZFqK< z?(S|RrAt^4DJcO-rI9X?Mp!~ZDQRh>yE~T-QM$XN8)^34-}AoT_sk!2?9N|%>|Ar- z*PPcGq~q*Eq_qwy5JwK>1F}VG^8!fMlv|%?28A>nZ0V;TMFG?F=N$zuwW2haq_#Hs zl_ts37de}~Kz}t7b3?x(si)|G9;Xtve25Vm5XV7=%!|mS1_7p&vM?HG06-Np zWCmAQbDmEHOeA4FhIw?oXNFSvun}W1C=CfA5@|z^L|e1O1C|Qk2--*kHH^qdEVc

8H#vZ7^Yax`|9Q=JWLJ*QP(UD1| zJuVe47afXfg0DuLqC5L9t=asr-s()RE@OwLVI@MD4&`|%k{Mw-py?t{hSeo{Sdqk^ z-Tq<*^_I25t_g)2QYijQ0jVh-M$LfQFPR}7Gwz4Dk;7oGXAB^WTv?>6ytfY)+In;< zF3c-NpAaAjWFUeS1R=-wR3I=*#DFFMguXxn0#-vo*-ccy!X|zfYVgocYW@_g%MHKm zvH@7c1#)>@%L^?!5+u>IZUplRC(0p%u2yX&&U@!{T}KBop6}tW@{_=O&ztDG{XEVGgOTIur@5Z zLK%wfK6+Hz$}QoXsz(jfc$v-|;`_jZ-eF_QVePdVB5ZZ5cl!qO=%i)o+(%WoKQ0#> zbcgtuZULG~SnxC_vJuCati~mD7@U0j|J~GV+R#`@H~Gv~Md0l?|IW^nhyrlE>LW^R zX%O{1#n#mYP+f29Kc^yAjayrYbi8f2@gDyh>?l9>IAipR61yBBt z^nkz)42zA0Uu-k{%F4)b!o0EwlnrY24Qe&-#^8U{2MU>V-igl6X;yY~(6nO5`2-42 z4M*%tqHb#&$**;QT!z0Gn7)YxVq3v$Y43{AQWswcX7KKL;=C1b4E}o7kQ6*v+ZM4X zfkbHI4_s2$9%Q4-! zFG!muN)g})5@o^fVqY<$Phq9M*Y90sfzzsgEekBZd@c;s=^1i^038^OKuBEKDP!>` zA8}4hKSjUaNNxZ~EfDLpiU>&q;N}1^T;+jLVJ{|7VJT$lZJ*FR_<${(eUz>q0-i8o z!|WmOY0wqbgUTD}WE_jT#QsIwHKr%03RiN>$enitHU#x1m6K~iUNCBkpMrC=AQ6@H zQ4K58A1XTXU?O>~U|bsM%L!g?8701nLdTcwngJ$%=Ix0FrL?0(OXSQ+F8jB>d0;cJ z!cagAYk!2zIc4{fe*XSvov?`@C6DQl_KabGCe4`q5!c$=^*a0Mz}xw4@@8}1j_9jE zj{DoJZhQt>=VxlRTu2X1$JZ>#LV$vVXb)83AMTokLt}=%a71-%*talH`%eQH-LyV1JL$D zkRap6Z51IIJ9+X`it&nBccpIfg=1ISkFfrkP=ct6gvsKGk9q=5AOikKy%$6jB-NZG zCB89|5SPAyV_+ismkuhlDs>n|G`i35)nf9IRa)D~5Rm=X=$i%L_e75YyryS~02vj? z=%omCRk*jlv%|48WFf%=n9j0IV>jQkUE#Z7@CIeqI4%%8RTiq`BKB0~30Xs>qV{PPhg7jiF7}QB43T)-!|t(`=0O#G zKaII86UF=Y*Z|2wEeSOF$guAp1CL%AF+!mhZb%1|AMc)p3W|S*2G)c92GSkiSRC&Cd7VC`xfhMhE!XQD)BKi1_q z5y!8FikhO46i20=DiE`sN6o8PXc&YHDgbRKge`=BE%Lbb=xV2=PQ^adDzIJsyC;58 zJ5gAl5f=5H?}&C9yWfm2wPSf@KyJ@1?lDGHWdZ*=(tB0~$irp>s2W7WQK(Rgry~`P zE1H9=R(76%v8A6$0D%M-(x`cbh@(r`3?9Y+q!!emdnF&BDiije>tTjp_#k&kO(|Ls zz?U#b$+1w^$b?ph$eP}vuLMQ9QV#U-)*w}U<_Mdv)rU&pU+wmv%)36?N6`@It}_l6 zAe8IK4|8(fFe(KFyt}bxpF)yyk};qdQi3>}{wz(m7yf*CKf zr4|;Kp!+j7hxJ?~T#Bb^IKknYv^tz!`aQF1-16Eao67v*2)A0$|uQ}fhOVYp%}4Rat& z9e|-OHil~eh#g{|%t>ey*1#_j#O;YW;(>*KD%H402BcgX;T6gYaE{;S3#Tju){3{+ zk}?H6_swM%@Ok)leSl*JzCnil{r5um|7a(Q|BK>aA+O%9>KBnCu^R*)0)dM_5FrpG z2m}EV#`zD)k>3o9x_ZTm&gzo4l*Q1gcIv`D2Wp^6zh&eF8-rCjs}>D>J`>KtT{9`F$5Y2yEnu=9wJ|R}KewK*Mb5K3RP#!;KK4P83*KVF(6v zb*SrGE;yDSHBhuZM*$QxJr*?KyMTl9*}3(aHoc$9!dQ%;et9OqnA@-^Tz$CDTx7CD zKt{;O`l46$7$v*AZ-RMc*WU$Y4Cmm4h!~i7u>sa3Jhs98;%M^3Iu-GShT8gLx7%hy z;r0-)0A?DR0~`>`+bFw9!E#;AGx%!dc>JbdFn>lM5ckHVvOJ@E#g88lr2%TFZp0rnyW*uh-S)w$%NMx*G$7=^f3_=V4!| z)C!AoyU=1qk$>JmIZzf0{E+`295&m)1Rv)Ck}kc;w{FTl=l0uI=;^oHlk30o>EC>Uor3g+fAkZQ^xKTQ zv?VAM7u%?gfJGgR&{kRp{3KZ6IKcwu=aI|^nDGl3#jt4625t?Dik><%NK>pEOZ63E zV6KHbe1lj29{0Sm0$kZZiAIw5&DGo%EZc+oaSA2B3l0*i|S zu7Wy^5y{xwkK!{s71{xrL=%79Xpn4;A97FM;G*|^>u1pdtnVGZ)xI)7Q!mI3%g84t zO5u%1@3jKD^b0Yg2Z3~l|F~1g1NQlo3lze2tqcJ&EJ>c=7{)@{=sQblrxqTPk=CJ4 z0%txb>Cf2jfID9P4c6(iYpR!@XF(@?ktlv^BVSkpwY%%3)?dV~rEOp^M zIxAKF;P~OsUsb#%BwBZV!<8$(bzJ&m?%!U= zMWfVZw^Q)i9YDi!P)4JKQd9lAHymfBlRtw4yWYK%w_sGjE4zMSvCH&YCZX35r?BkG zD1yTi?^}*?RO@U8o0K_th+Au9|Ah&Rcn++GP($9cIzdrj@HX1LoIUNwX3OvOtN9Eg zH>#(OPCzV6@h{L-C&8`G=y4fk@!WJ#cv!%dJ>?1;jlo&g$%X(Ec2RT+OL-%2kZGry z{4&3UsgY$K6=oZy*hB`nfsoTzymAmJfn4C`2a)*$i3*^;g;EIgJ-^1t?Zku2e+!(B z%3|bfKLe!n-_l*y(h;k2?$2bYnk3IMr(5v4)AWa`KI&F+g3ydli!E6ndv4mfEw;@(4Fu`~Bqsho zC;1dkjili*09^$yo!P~gliA5TADpl|5=U>Th6P=aB3Pt|PpC8+Q<9ON!{!E(jmKv~k`32c+4zjd48O2aEJC!%e*Ud_jU8JM=tFM0K%R(V zW#n3gHCol|MDt65ubiPw(H8diTM#y?ix?`~7wFf}&=x)y$7io2^9SXM9Seijq{+kc zB&eEj;O8XrL-_+K?y$7@z)j~4Sj=W zz;<;XtJPwCm2Q;ya+~)QKGI z+ZR!OUz7HeKumg!^3~9`ro9m90NZgms<$JgKdJS{pMaomGY^)1#3Daq~F=S_yIm2{^Z_bk?8a{ z&&uVW0r$UhK-L4~)^)+v5HgN7 z*dBLR6c zX_~#fn`PeZqzWtT_Ck2>dI?e8iA|n4PHv8OhY0R31@w?RS&5=*69=7F6S*sbzO_@a zWTc+q0!sD8$6XjfOJbu{r^71$zC2_}ClvnZ@hB$Z(Ske=?0Y@`x!#$O9aHl%9i9)M zRH-0+XM^N_Id1O!;XyfLAbs5jR%3S}f;72k|I0@KmC+y1VxdDY|AwYE*p`NpR?6EK z?Zy$Hyvu3&14|;&?BNtjR1pNm*|0xS02HnM5U`jxT@c_(5dPqFoA{QZWl`1(pE>tO z&x=*DhZo~RnIBDB1qPD>Y}*MBtWHoG0tNAKP5Bs>g4J37-IxB<|ND)9Vu~i`8SY-@ zmHXcc*PkHMvg6rve*L63BupSxE*_K`U)EqkrXQ=HsnA278%S=0OvVMI+#gQ=u)=;v zfPpfnl{wdYfw}O^R0m-7>_rCZv-|@Xzo54*z zU)tp9Hzx^}*UZ#pLfx&!?UkbwQC*#u8#xBWZTMjYZ*cx3u@)EdaRSzeH3Q}TYc1)7 zUiPTCX{x}PEgsJuv^V*NATOF9QZ*`RFO?KLxO?gHz=b*BQe7+Uudr!2bAc$ zWE>qDUx_C1>(F%vthBF6xvgJiakwtT$eF9Xs~Q?S=WKbtK5lyQoaVT)HZ${-%b^MD z8rG$(I2yT;H@y3{GRUMk`)W@AN07P`_OF@HaLDjVc9^2*#TFM-kntyrCcsXFv>X4~ ze(<(0kUEpQ3hy<(^W$_??Y}T;c4N}nS?wp@Q2nA#XElO6vTKcyE)nuq)<5Y!Q+;A? z&S21Rq^~D2JZ?@C^OZ&tvrI*4?X5#52kD%>f%8TM0HbO2Ywy&b&LQ3HuK4%h^0m)?I4kUZzoHylm{T?KAygh#YczGaqQGJ3=h@B$8#tBe zamRB>n=y*L7kYlK$*9eLo+Q7y%{ij+O;Ihq6*`R4Eom_gwr?E&8*+E4`s4ATxD+S* z0pjv>eIc*?`IHzS!N354VR)xYDzemHBbP3*Soq(t z+IZiy$Io^}*D08b{ZKV<1x1%TtLuif7UgbxU1H35{>%~Ax5lC2$BO*ozF;z5b~1AJ z-^&FOM;F=@un>REq0vLwb|RXzG(5IRQH6ErKSe0Sx=H%hX97>_1akHM)NlQ3<&z`f ze|-gUKKmf}&wbh&z4$&aoZcI(sX-1d*Pt(=fuo7WdX(zuH-h=5>*#4JXR;(P85w_h&i{p$NIBwTgFl{cF3*b-r@x8#*D(V{SZ#@7 zFGR>BXoe3k+UaIZEfUJ(Hp#LkDQg$wBh)OGW`nAu7ww~*1o;)9!oHQ)sf&&c#XGw` zeG|)`F6S9O0hPLSe8!-zwGjQH*`TH7pje-hez7+|zZe?u)2}J|dw77(?1ciA7%9uM zFc%(fAZ^o~8DOvuK*<(zh$JSf>(T9SW-F&4=og(chWd3r8TLAw2Bv`4D5y@iBV3X_ zi_$-^ui+aGetC&Z!8{C?!PvqhLdH3cZ4+%~?K$8Y8Njku{7xQ6!|%dI3;olb*;+wW ztEs;yC6lhq7%*bs6wui(gnljR8|gW;u(&!Sil+t z7-n_P zJ`**O!a~Jz8$epus8{xH!aFInST%LH{wKr2I5=a3JcEvIVK@|Z5-Tm37-mumE&rjCrzRB5m~I-nU4q^b561&VYiw1qDy&Hd(@@wshVxlQh8e$EVEt1IiQBkT` z-zu59Eg7J)o1&^prr|dD)btJ(U8O9Em+9xnZQ_lyn9Qh~R=J`1JI%5?6|d(eH_jXD z`h$M>NoB{01oB>4_dy0{MI!;~=WOwq42F4J7EkVcC z{9^z&sVF+5IrXR9FDf~DhGHiZ!V~6(^Ka$O*?v-oZZ9Et35RP?@M6<$!l@ifOXHb? z(MXOm$I0>P6#oi~7WF0+H>BM98a2v^1ta-pYoR9D_;ca12DF6MO^j1gB~D^m zJGYJRP=~}{Gxk`zv-Q7jO5tiZ=kWK(%^LbzILjKI0@)=sdMKq^BwgV(A&*o-{Z5O= zf(6LboG#3#bqgW%EUxX?reuepArYC9H};H;9UUA!sr%d+v4B1uB_!j``4Hi{<<)7` zmo`YLeD@jA59m8O4@14eNpI`}2N539X)FzB?MFHN8&1l!hh<

f6AE{sO1O8|-| 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 WXje3ae0ayF!)1--1L*#*@BabsA+zTI literal 16606 zcmb`uby!rv+c3N+BGMorog%F+-6-A2(n~1~OGrtBfpkgdBHbXdbf~~m3rH;uN;d++ zO1}que$Vr~-}~=3*X+F$cg)-|GuN4OAj%2~=w~31{&PKfYe#n`8W0GiX@dolD+7Tz zSV15x5D5qugmvo{6POH($@-OrJ2rp;GQfbx zjkBw{o!fN>)-}f~AcZ#n`)|Oyq5R)OfHV+@2rEa9P~jT$zmn_ie_WV=P6NBww$4Cy z^>AZyurt@YVb7$@#P{?m7|h2HzGgrM{Ny!cHRa?XlK&_lzp`+|y7RBhA|tB@U}1r9 znRSddBx~n=_db!3X@`jBPy6ms%Z#WLN+N-_R?L5cHMrZL=vu}Nfcb zYw6vOSX~h483+?+`$<=&LhvFAgr6HwnF#L>KOWh(~iRw;Ts4^%N|d&hF11n6ZFRw=k@dZQt? zy;h)&Nx+f<1i}V=rTRd?*Ta+>$%hB-B$3q+T_M+r(+bXfLj_f0{z$H(#JPH-L^@nT z!#kjdAkaET^deO>R#0%+Ef62T;Wa_;4{}XXdTj{X_^lf=0ka1@%l3VNZS*6%G;6!G zg8wJu+>iq{*CChHqU*xCp`!-qsG=7+;><OE9A8>P~{pFwTUDe{QEQEg$gIz&1A3T;%HeX7RYdzzI)Avj_1&8ht11`Bg1M>OyBPs6R00`XW=j$P1l<5~2%yA&17ZTb1qKM} zcA@M21muUxaSv6idHP3q21W$b4+S(2C58?qhVM%b-F#kXH@?!jx#?2qQZ%!vLN_3{9C{p00O% zc?Ud02Lf961p#5B{PNm5fFyZ9-Y5WdO!Pf;$8}ABJf&+gk?3gTX*GlPFq!Z$`KM&S zWMa`v9C0k1ajc?o50#(U_Hcoo;aq!sc#cT)l2Y`tLEIznB%9PEyW+Hx;LWw^e-#M-DvkM!L@eDyIklLU3nDU1bX8oloRB7Yt{N|byN}mmjOT^33+XaE;os8 zHwi^|Aj>HysHp;&V`@W3I>Qtuqe8mcx{jlIj#EHRGMPzooT>Y-ObsaZ-nBozW(xY9 zcTA>&Cxc7Kc~=IPm>B?C%&V>X^S+v)K>)z+AfsMRxDEIPHb@Hu3Id06=24kPiWX+b zMslvDxCMi8=-r~9uYGohaIIyyYp8TkxkZZ>Ww=4a*i%1fsIX^)fGOPte)!oxVkxkm zWq?5MV%&V)r;bUO*zK;^RaW+1xZB%K3;FDliHQ2;W+f<)9W`eyyI0J#PY; zk#aFI781#egc?O4f+0`>&MpH;I0B&ui>N|}f;p+(6Zr0OCMKBkaCW6US1kSz2W8Jk zj6q8|5m5d$4i7Tu=I7cZXbCSs0kBGdmKP(S7Ny{Bs2dSy7Zh4jRjUUpl zxFAB$&Au3IV6j_O#~{uLPD-%nEJbYuS zRv92AH^R%&z&r#e;M$R*_1B|XHFYEUjx_!d0mc9_%8_u`(DO;U54<4I%dc1Y>8j}E?=-tc&9L`m`gg8;0?4Sg`Ri>ag2+)=Q-J%PEKw#NGDFU1j z2Z4bpL7*2uK_HTs9|}4bK_7$5ut6ihs%eqnN+iR{i9nl2MnNKcpRCSGfy*a{okyNk&L7=qTilDUkhb+1_dxQLM zCv^fN0c6C@#?TE61A)HYA~5q7WdP?J6u=PwIM4d-0vi%AjB9?4z&?fr`f`s5^!By_ z8N`NKkH%%`9u-UMf1(6iMg>^J%p<`_D(!1v5qb@PQi{bOCo;tiCE9siEugX(1h0}? zphQ=JsQAC4gdC`om=gf|fCUL35Gn*RILziKZB{-?MCHZmhSsSfH;=$czubrgOb~b8^b7h~h8z&ujD5H`UQLdHya>RzBVw77l8)0Ab?(_7y(p^5qc`hY&Up_ zNdOrEu;TTaxnHdg~Y6JUR1lsZa_ zYX&#gvWTogP#FPLN{fgala+S?g8tzlX)GLgkpS6$*^j43@*D`KOWtdzv zr)|+!JVHZKkQ`uOrGHB8N&zO454acrEW*Jz913pOBmnwGru;7l1VZu_9q{7`y0Pdp z5Gb@(kBdDYf&xF{HV+lYMln*#58Bt4b z31gnxdpnCk#tArrnXaCtVy3ncY*feF}+~R<}|K z*@Dg`8m0z|s*EFGU5sVDy?;Wv>tVkb=cdcf&f??GZhf{f)*B||s`&%?aHsjr(%AV} zXMC72R+$uNzTYVLQRjKLOAo_dQ&YE#qpU|;i==#6I?MOIJ}kPuSBRI}p?sT7l~Udm z%D>_Uh&|}h4u=jX$w9dEAQIevWF=8|{JZ6@L?8BC%mH^hB|E!kSK@iR10T+K*z=}m zf!xke%Iwmij3>v)>$%yGxKZetfs5=YLdGN|6zR-aQ6&T&7`pG&fTQ*X7Z zu~FJEWC_0JHF4(d=R7elB0)%`NcQL))%Ct7-j%MU8IrdcdbKq7_VYYUJBN->v>Pj2uwHZ)e_4;6&U_&1b^=+MnyEl_c(YMr zKs*bN9%~I0nm_4WxsVt{cP<7x_@a)T7I7P#jFZTNU*z5iNS`gh;O#dl<9yF)si&zd zBOW4}s2>_a^8}N0J-uRm_C%5I!%9(WS?S@>!v0egSAzOc69pgYcpU_%u5cho8xYmIrt+4t5v>?e^S{9BU9B4Q<&|PDp|sn8_vIP zj0_EZnPSp^nIi*gOP{rfky(jjCV!07?s(8F1}BzkT_~XJ;ABBvy|eJ!cHXy=q(14^ z$}*X49Cur5nO0GIlJ4a!EmZw@%a2yAvv|NgAEN>-jk}OVHDw+*{mPt$5U2T9lP>&T zeHa&i_Zt^$^@6kX$CMT{TM}m&&G({j$3(gts@tmau_|OQayS~E-2q9Z&w>^2!OwbS zbkts((#(|BZ(U4GNKbSqJc#9a|CD2u@e?WBpjav++^o5wMWCs{TkrVfwXQmSnIz+u zO&FARl?@t~8&_HNmCJN>BK(GWs6Lty|9kkCf<)p>b8dcYi_r$(;0;c{mYA1@~xmc+q7{tjGiX zE>B7jI4J6NYIwD>fe~9mCc6NAXWc=j# zxU9tge%SAwEvDw?Kto2$?|p==mem!{FE8Ub80KlqRk(HfPt6_2imN@v0v&WR`K-Gl zS&`K+(LJbT9Zve)S=8^!DAL2OR%uo?PiLlm>CyR2V0EVeK@jV`&iI!g4XJk$d$@M)j7}5O9p;JDX82X1EaN7QvnjRQ|73wyw0~J$POE>b zi0Idj1({JNq`7C)(q~B};^g;u8oa?|p9ixStf@5h`I7kkuymf8&yOSAO3c8Ef*Q|^ zz;d!eT=2&Lf{vbGW|_EWr0=+InU+c*V}e~ncwQ~G#O&!xP8w6DcG)FS$OQO zai^XH&Y$l?>E7glqv<{P#ZK3G_Bx0(J)Oti`16U(@emc5&sGK0FbV9ar4f;(32c4F z8s%3HyIlGBTOWqpyuamBjK@B)5us177)6JaJiQ(u zpP=|KA#GBwZ>p$wub#3`Q-;jC0QTyeR(T|XnXMRsHajm|; zfB2A87ZabkR^)11f6&Uf;81rEksXs@8vbhH5rsizYj!f~1ERO^;-fwL#G0iQ^!4w# zxS=!_pKr7GNV^^$y|x(DBKn(Vy>EItwY>yeE7yApWtG^Ootv7)s}I;nT0U!YbG)4Y zQ({eDt3r{us+8DJ#xr``SBW=zHtT>S{kP*xsbS#PgHn^`p>oNhM*2LPn!-03m%$>0X`i(IREa%~ zV@~ucc|Ky&kTTvVoDpp8EVh^gj*`myvsiw<8Do6r4!_K7eN1B&l{hHw?0n1R>Ok1e zNU>LPLbuRmW4?!QXykK+R%65SU+xT~&wi?a+R`NQgdf+t#Pf7B2fQx5aPprH!5of1 zIGUfVM%hx9k!bL@2k4}YV?2{pH^@U3tb~;$GEAG+n6q7md~~YzbSO(Al+t%XOAC+WLh~y z3K;RNv&}TwIjFSLAecOs&}_e~L(*rTe_Tnt1)2{R&(l|T-grdK2#s9+4<#W2N-Q65f#7&E!KFWmozm{S&oVRUvOKcHOqp5B z6+XtHR?d}|JG$b)shs0AdOeEY(4LShK-^pheY~KfoF(N=R$`alL8Gl@Wj)T2?~vwh z!GVX)R~N6#-^tXIu_V;6o*0lYNjUP}^;#ca?EG~iO$>LwDpzX{BsMSdVbnvRP$5i- zTON_|IXRsdG!MqaA&sO2EO<&FoJ*SKuZp=sd2caC-rm1VjQz4(0!^n3p|YiCfeuEk z+`kTJF>`C<3CW(4;{E+wH(v?Oi*Y|}mhh<^S{+_ky1_wj$5{7E+ zO4!m;unczO=d4o?J*|ln22qt{7o{=Hp79viREXqTHA&r(d| z=9io*R#BET;Dh_DPZmTE$~I-2$Cl6hQ{^5wq~xZx5pH!y7u4yxVuozAI3%Glc`BMJg={NbFQ%uDBFuAFW)V*s`7B>wilc2yo#j@Y+MJy*78)}u zQuLSQ!MaX$ClO7;I z7w=ErELCj=J5tYgR1Ll@Y>9WZx1iYDRc}g@$WOq#IgL3Ea7ql>zMC2?(>>*WH6}Rv0&Yb(}JgrCTUj#uA$o@s$zf&#*4*cHFE_lp{m zVb|Xt^rcy(D7^g9h~}&G7cd~vD6Wu}@#H~X!goZdNZuGsk^BygR}r;K zo_z4c7`EhDO6H#TUL`A~#O-Mrd?D>+*Mnm*5&kZffQ94W^Iv{$_YaSJq+RkC%7XVO zdTKhKHtv>A_*r{P?J4|icMN`Enm{^2^Ny%ym!4tQ#FRRg`-hf6v=rXVJ2(7KqTKk{ zM1EdJK1ZXnm=G3vp%o!0A?2@}zN=hsx1jXd51I@7^TukGGUu3fKE7{*>W3)2eX5U&P}dupfC-5s9lT(Y zLf=j745>8~-Lu7ibz(MB1>6faDr&fo(o|XV4rtUmPK>#P81tyhbanRi`pD9|s15JQ zf!e$!EXDP&cGR9_V5)LV(GhKoujeZ!o&{O21Y&nhrgW71ZU>&}c{aBnC#_Ur##dLR zZ4M|r1?T@D*TfaP-3kR^I>I@Iq*4Iv5t8qko80S`x9xY`jubeyYwbQE*yuaxYUPFb)mdXCML8-f)5R! zla;SULCE5;|J_J}o=yDEBZ%8T@rpMIOJHS4BpIj_u7aPpa4!x}=JGWkkXcG0vCa z9T!=LCvxpYRn?JyAW0d-QHw8};KiBIS0hj320B|#j4%4SCm&(?Z#ZF4Xff}VHfLcX z&A8Z)BlC zmV&Zys5Y1JORaZ!pff@b77f}cJ9c{_Hrswz zIMcgf7Nw+yMA>S#nu`~ylh>M4a%|lSHPg9o=Ut6qmj%+JoA&wNn_cnBO7XNU@eN(B!9AVQU2eS+X0?D)>8i1mw&tkg;a|W5+x?yziCWb9Fs?hk z$%r7idBr?cc&7@;v{+o1`LDB#gcsuw{*wgE(a)13{TB^S&!pckQJxP9UbMrdTmOzN zH4r2(DzWJIHmVmm|1erO@kX+pK_X2RzaZfw0sC(h$C?*8*Y2e!=qp5bN*I*1E$uu9 zL)kBr_)Z!c1yASc3H(d{3f^sffCoV%H}X_w+|4akvr>gmrYQ8bkB;IUB3_D=p>YfE z1cO*8$Up)2#Dg9dO9#EfxFh@JhvoM(8Qge(eJ{J>pqXsPtU#X_mpX)PnhYfEt*^8W zR}R)vi;0F-AXUe#()1h+s_z~B-t7LAw0o95J*ax+tKlp|BqyYb-SgI(eN_R@=iJ8% zJIvfwm7;&dpWLBm$?(3ka2n)<30c{j3@vyhCZFYS?kT#NiZ!Xx z5-!9@Z`Dtlz#6xO52KNnCFQ+ebLnWETVWSfTpO1K^!L-h1qjE)tQ;Q&9RBv{nxaoP z8@u3{eG}#6h}g2Gfnhc%6fpJ2VqVi!cFnYPjzk z4qhJkwn>{`>b9cDFefT}AUac8>b=;d#4(Kt6^zN;oj3LGB9Ydrt!LYsHW>)Wsd&N9 z`Dzz7{AHg8I!(cuZ{rOM(|^^BsH2{y@u@tMZK06*z?YWVvlFE%1crjyb^Bot@%7iA zn?{W&VuOG{xT8}t6#MCe$esk_bTV>j|6;Crn@xtlG(b-IxOD5~zNx9mE`Le4QINe< zHN(fO)#){TxcA?_{jC}v7K-NS)8E|-SH{WY;)Thcf8^TE^3G{;|2T zNr8fC>Bv+U8-B0(y}_olC1{VX^y1tia^_<%mFTQ|P~w$V0?IMFv2mjAk@+F(?hNMu;u)IV_kD&Z`&66bWF)Op*tww_q zE32+=!bGBr3&xI9zJ#tP#tb_gbMuu8aQsP9af@;_iD34oRH0WXG*?0!o<1btQUc9~ zwbWSK%>^41;gPN#ec*&q>>Pn%o?J!*HdYs;kY?@unI{2yxo9+zu#4rvl#3lqWV`H8q52E*plQJYyx*g_Pye+~t>p;pSQ$3!v-HUW))_VF zeW-9XD@no1;OcIp(B3ujjZnhv2<}&r#(6eX*R#ACYRxK+-#x}1YAm8<4)XXKj|)V> zJCYLO=6Y(N@0{gSQszULYe~ag>BUMena`bdVCxzC)UT~A z$!rFFw|>W#mPR-nJ=em$q8~AVJbG9qKHZaLj%eBRr4D+X4oxb>czG*f%Qfm9LB4K+F(+A@G2zFmSy;Obl zxuE*J%F2e0nN-w@&YVrT%GmR(KRm_uGd&%#{VOvs&ou_3X_GE^k1L1Na#w{M7|RbC zOlcVG1$0KMB`GeUFn#HSCHhwv#1=}w6&Jssn@!nuZ~giGOgcuh`yeh^ zhecxgm}b(Dpwa`eKSe_uxE|n>8NSB5a?%YK5%BMhn=9z!|=QfXM-m>zcc(^ zNEl+auFf8q(}mH^J5Igdz5@+EDpza6Ko<+r4#$=H>-djdST}2}bAv2Q@_%Y+CRwqu zY`%S~5rhXfXzBb^s+jH?OS0r$IaGgYSN$e_mn2*Fim|G1n}2@pNOftV+2j(Hp*e0E zWhwc0tp9cN3Je z8Uu`-jcB@$H1}7{+M?~Ry+fVB_n;{<-C6C#Xv;tPv@3zRc-Ka4ElV$R*-|E<;f=Sx;j=CBGMOmMlu2@bf=+Sn2aFq&8QE@qbYp+^i{^nSbbRSC_``Ft^U^k)Mf7^d|4 zW?$5oqGz586$@tX`HY*vrXXxTbCf^~sVGuLt2(y3`eA$JcLnzzm`Bd|aPvdubP@xx zxAr*wG6o!z&pg-0$txev{YuJ@Yp!n8G-q4uqVbRM*;G4k9I}>F6*wfGQu!8Gg3t_N zG4t))+ma1-*fP=_kQ#aXwMaZE>qCzvdUdt-jKvAjy5X;_FyARw9{9cB{`aZK0#EQI6Um(59&C(L?o z!iO@ciLW=0ORCck7T@w9hI|)S@?+Fz7g|{FXzU6X1Q)`bV=n^%wc+*`@OcVdKDyJA zD7YoL_2A8uS#v?&+S9AvzQ8Gu_PZ+MC*4b3@TijDa_TPc^?LKlonEQ*_2zdkf9rrx z%eJ=ED7Ds^U@*wS8{jGq59HJ4erVZ}+< z+UB{CY(_%g>OEHQh;wbm;}$T|zxr@7{Bn7elvH zt|N3MWu3n_a$T|N#O}Xrlg?}o#3-U{)E!O+B<9D5#k+VmE9qi-;(RQ#oSfcDOW24N zQD+qYHTRh~Bplk5KaO$W(Hv<^>>j2q{(@H1 z3@o^LlCgT42Anxu1-H-5KN8c!JxfHHmh8O~=huWXD#YARi<0<<$F%3> z8e2(Z4}y=I&@{hV)h>fdE#-$pT;N$#Chb&`e|MWx!2m;({4j;1cEOXVsELaR#O9wy z>zIp%U<-_SYE*SJl^puEmUtUAG$9p$4_#5 zq%tyqwtDnWX9A)wTEfFMPk867{cJKIe$4D7$Xyf3;4t-~GkI!Bq1Za+1bQZIG3Nec zd@J4kgTNDkAzg_Y%KmWtdpZhASRDU(bRqliy^HGy7}qan$gnxu&?cqB6H?RD;Tc)E zscAVm38|U6X}MYPDQT&xa6sKhkT>~Ecf!8y6dRkX7mP!_X;)&K@8HMb4wP*vBF~Q4 zO9nb}F!WpbGq3S=E1WhrPPgXf67JHsy*pTcEIt{3^02e#Nu{?M{`*FQLKW?b!VGz< z(Y3hjpFdoLQ3D9?%mg!-FS^4iY84t^3AMhJ`tA6=FGRrk2Tjz0d3jO7KAwehC3l0T zRhLd26BdM&6bqh$O4Va4(28Yd33x5upU+N=>@9@RLBvRtut{HCGG-^-um9Ch^COfZ z=}zf^fc#RG#xDIcwP}B&OsOj@y^Mxgj&wIR@9o1re#HD3In^NA8NJPyH!E!m?RYN! z^62iXg%{KP--JhJB}ricbO$W~sRO3Oc<{}&Qr8Q~*@Z&Mcg}-C@DJp&Q6V8%F6LuR z*?Ea%*pFp_i-i;vYkiz`Z~yzh-*(C67tL$`cqD%7ZCrN4|})x|Thaczle=+K$Cb9*hyNG_hYmK#dx z;vw|mX`_(v$%UQ>(niRacp_0q*ju>jTPM0|=DbZ{v3<^)?!-q-P%(^-W2yw|G9Dfk zAttv>DJSw6#P<*cFY4#FmSO9<{eI>J%wWck-WhD~)Aq_vi6v(+@Z}k2^p2;j+3627 zRL-%kDaLjFkNk+U_0JUw8`Apf&EET&kM2rtLJ^V&5^alvNna@lDcPgVGUw)aT3aLD zd4+#JGWez737%HkppG;6!Uoo<3c>rwR|3%aR=WUTr@RXdmls&*o@SjwAf=g|z+9;4&s!Y9tQ z$}*Au(3av{DhwU9Mg7Aa?Y2RPX;(pSqB(yFr&al=3$6@bpF(#7U#b99`9ARrk}d-d zP(TVw5&v=3QfZ=h6t|5-jUzEBs>eHR6TYy9|^BK5VFBZqE(gTB1lahc$C3# z>M|41EP8Mus$Fe0{KGro4gM4RCfWnt#~4rLi@4~;waw3?B+g=mFK@?PC|Y&V$CZ8kW})cxiVP!Im1N%EQ*&D|`Y)4L?oFg)SVA`pTE>d#3`+`WE$@P}XOXuBSaz+j_ClA3oy4Wl{ zfI0cmc##>xq9ki`6&jntx(p11^;-CAC6|nLD2I~Av<;_R+Wg0@kyXsgKC927&iUgv z+S>Jq)V{8A938Y18oO9rlr9V#CzT8P<*2Q5AH*GtpDLu4*c3Qaxo`Dua%B4n6N^u! zxneiVc8kkf$in|#wt2UnxXbn>2vjtT^vn}w6Wxt*5~GSiNTn6$#pP*HB~!DKkNLgk z#tNA1W5u}_It2Q*vuR1CFVSwfknTvEZcL`U|)S9-ij3(G6c z!@qJS?HWyGqVwB`RyqTTC&m2=OOXS+M@3f!1nMV3bqUIz8QjJJX4sk!`|I)dqvgII z`|KBRs)^BO*(eq6Rq~6DQI&13I{iK#qNhGb8~hTD{JD~2+@Wh!&h*$|K<3WaA~bw~ zsSzXv_#5Rt#PUy`#k=+T-KYM$TN!%rrA>6bJPr>zsos&ad9*`&#ct-DYK@t&M>t`3 z#BQL}idLI;MUj=w5QjPEp6(iTL+6!Cfqkwjnzo0=0oBsyZaAvepr@&1C_RuLRMfk? zTGupyl(nfF2d{72UlnB;%-T*7+rr{`R@GZ+vd?^enr=RZRT$k4+)Lr$c;4znmKH%$ zh4bv~9jQ@1?oY z>&V~buGbvZ-&<^@9rWEA5fifShYm$i9Lxr_|g8 z#p22&DyojWIB5+kI_i6DSdv)%>tbL|`y%~*GW!-W+wc;Tt8ceAPdb~kt+e}cV_!bo z#*g2Hg}h^^g+@Oy1%{lwy8Rg{!43I&hn zUAd6`B5dKOAO=8p?AS@|y>CUFO`;ZGk1Vw!JuJg;x2~8=JrzAX7c$3po){lY&+c$? z9VuzFiHflM{u%Z%6xSVk#Nc9S5l}PZYjzy)=KKgeuWUEF`s;x zKtg%_-$5W#FBJcMA}9mNT6w8F=|qQ+ai*|LWYHyKCckYba{9q|<{}i#7}&}&G^P3O z(e92g4r!Nbaa7oLd8zo!c+DDJ=BVexF}bKn7gckK2g%!eeB}H)g_Gmsj&^d(3mgI8 zvpz(jy|z-t?Sz%Qb0=qRBVWGtU8z%|mK)0rs*@r3n3Y#?si`>;+s$4e=p|cU| z1LR!6;~9>xMdY_bK~n2|l2}ANx3MdD!kLk64~ZsRjppKeT(GD}b@eWWx48c9rHw~c z59qZXh4Q_z%5R{{VjQ1@>)kJ(auNF?Z)9J~Pg}w^bN@H7<%EUU?)FkkAXu={C%{Is ziFsKtq}XBBAya6=Hw&}>NhUPEGh4W>hTl`ay@N@cc+Xm~mtJaF5XPM!JKyVCPQSvp zi2MF`-&Vd7_rrIs2F3=;cb;+FSobNG!gIVu2?`1~IkUae3U517=+TkvcDr$FU5AJ0 z*>nFBEsM~`;b)chzXhiV#CAVa1`3|}zVF`ogTA}jJtX;H{Pnr7uo+i&TG?Rq#@{Vx zQ;)5>JL6jjjiFnk1Q^XLzvYp@S&L!|{fdGS+YYYF@OF=wnUncJrkoXxOL|wm!CePj}RW-l1p3Nb*)Y>$Xx6|PygA1Ws z+@jwBr<6|_F;e361Xjl zNuDX&-@10V8Y{X;I;O|#r(@oZM>AsLcRRcy5~n~^#s)V2K^e@q7Dsr4VQ_g{GgK@3 zQ~pq8ZVt!tuOjiwW1;RHe;qbWNy>)Smgzb*j+p8Bh`Vtj+n*@Z)5$Eg>31u!c~v*i{CbB8Q#NzLGqs28DM<`%>9j< zk-}79UvCzWk?EJq51HCL8Cufk{v--EvuZbdsgV9YfpBYRKOue~z2aaz(WuzX+DUUJ zvoD!mcoHwXD;zmUIv*N!P6-LDSD4S0D##E~W~iIjhbcR;7nlf&HM^dnjUwL!j>?XS z&9b49RWl!F9MS6Eo%OW6Nh?J-T%&ytB5kr}80TsS+G(3Tdt9_FW)J_ z>AOTGtx$f%Vn{&^7-2z4NfyM3*VYgH#QJvv=pdxf%!&A(3Xi=a&c9a?Y-|^mPq|Bt z_9(2*S6wB&^Se=-4U(81)JbBdy|_mwMxyHgb2J-mxqyhNIgYtLL9{x1OE9Su-GzxR!b@sJl2*G-tMPFzC%i9IyF=A-&eid%Gie7`abE z&9IE@N5WP0PX(Haa$jCY>Ocf27ZiF3htHV{Ot9BmKt1H;GA|g*(=f81P=3jJY)hWV ziQ}rq;^||}k3039dWzuDH7a*7j_9OBhVRQwr7TKEWR5+8-sxC(I3}n>rtJIqXK&eQ zEmhrj(;t6J-J?g)zH$Bc8{jImsG)f|#`Wu?s zDBGLgrv}#VEW`3C3CC-=9$wVbd~mqybHBZfsW@(*e&_`eX`1&1sh(Ck)v>^?$=}`P zs=AV)3fFSD{nkc%-O-6%vdz3{a|EPkU+y)VIyDt^OUDojU< z$Q|)wp+aQPLS90jk;Q;P)lOf2`X9J!DEVQv?;d~2uOl~UA;S}SI&0R`Ir%CT8}1g; zxA{73VIaV${^VFmIpo*(VUC&H6M1xl5i5^aQ0Fnf8y#sA%n>D*d(`mqujbyJxhP^7b8i9n$uw}uPA>v)g3SA&BpN4wxu2Vj z%uR}Xl5wz!A^KFB`@`M0LN-D5#>xZiyIq9jh<>#1vAuBNCkx@43IT zDG;B8F6o%Ms(EZLDLX$zQ*)B@LTiVD?T+@7l%1tVL+%LcxywKioq)w}1$Kk?LJYqujjj`gevcD=LBewDIt(1~ zKje$ZbP^A`N!z=jn9Van8;x#-Qmh^+D&_ww)$?-U9jRX#`Cy6uWF&ODUhAyAF(mBb zAEIWz1CD$ZGs&f}Olr4F3fW74T}L{1xLk2aAx?~JZV787uJ zsERT1w(4iEHC2s%9seh-xYAjMHP~2bZ79fP7uPSkDth2;miz$~DHqSyt;=MTfBmfl z-~fjiJJZ@FKbxy_`am^!%9tV>5l2IiW$H^RJ*Ua)Gd;M}#Hm{xTB{DP&=aysNc_3Y z)$}*AEX*Fh^16{|*LXazre*ldMM{!OB~icqK@~C(N1MRebxWjEAmnAGDHUB1=Z&Q67$(>KMyGxNryuWh^fRb`nv61&Y? zdCsA(>ddRnhd#+~mAjN(EnfP^v3Y#^Q_TZJGf8*)cpmSHKMu3u(R-~II-meum58t3 zOhIJp^oJ>jcuRS1aw77;qo|^*`qJpReA}2dCtcC|0vd|R^XDBFo$eCmQ=04}gOUw& zC({kHr2@G%ry`7x-YDM|cn>lijRPlBcRt6)*1*1Yp5Fm;hXAY3@2EjU|4+K{GVGFy o|M4;$fCS0p(0dzjF903n=J?v-A<*`p6mI~0hx?z(|L6bz0QS!=6aWAK diff --git a/data/themes/classic/style.css b/data/themes/classic/style.css index d1f4d0588a9..c73da5a2b58 100644 --- a/data/themes/classic/style.css +++ b/data/themes/classic/style.css @@ -82,7 +82,6 @@ lmms--gui--TextFloat, lmms--gui--SimpleTextFloat { QMenu { border:1px solid #747474; background-color: #c9c9c9; - font-size:11px; } QMenu::separator { @@ -98,15 +97,12 @@ QMenu::item { QMenu::item:selected { color: white; - font-weight:bold; background-color: #747474; } QMenu::item:disabled { color: #747474; background-color: #c9c9c9; - font-size:12px; - font-weight: normal; padding: 4px 32px 4px 20px; } @@ -132,7 +128,7 @@ QMenu::indicator:selected { lmms--gui--FileBrowser QCheckBox { - font-size: 10px; + font-size: 8pt; color: white; } @@ -209,6 +205,7 @@ lmms--gui--Oscilloscope { lmms--gui--CPULoadWidget { border: none; background: url(resources:cpuload_bg.png); + qproperty-stepSize: 4; } /* scrollbar: trough */ diff --git a/data/themes/default/edit_draw_small.png b/data/themes/default/edit_draw_small.png new file mode 100644 index 0000000000000000000000000000000000000000..9979c8223457ac27e70116b300062a3237b3272a GIT binary patch literal 5367 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 +#include +#include +#include +#include +#include +#include +#include +#include + +namespace lmms { + +namespace detail { + +template +constexpr bool is_input_iterator_v = false; + +template +constexpr bool is_input_iterator_v::iterator_category>> = + std::is_convertible_v::iterator_category, std::input_iterator_tag>; + +} // namespace detail + +/** + * A container that stores up to a maximum of `N` elements of type `T` directly + * within itself, rather than separately on the heap. Useful when a dynamically + * resizeable container is needed for use in real-time code. Can be thought of + * as a hybrid between `std::array` and `std::vector`. The interface follows + * that of `std::vector` - see standard C++ documentation. + */ +template +class ArrayVector +{ +public: + using size_type = std::size_t; + using difference_type = std::ptrdiff_t; + using value_type = T; + using reference = T&; + using const_reference = const T&; + using pointer = T*; + using const_pointer = const T*; + using iterator = pointer; + using const_iterator = const_pointer; + using reverse_iterator = std::reverse_iterator; + using const_reverse_iterator = std::reverse_iterator; + + ArrayVector() = default; + + ArrayVector(const ArrayVector& other) noexcept(std::is_nothrow_copy_constructible_v) : + m_size{other.m_size} + { + std::uninitialized_copy(other.begin(), other.end(), begin()); + } + + ArrayVector(ArrayVector&& other) noexcept(std::is_nothrow_move_constructible_v) : + m_size{other.m_size} + { + std::uninitialized_move(other.begin(), other.end(), begin()); + other.clear(); + } + + ArrayVector(size_type count, const T& value) noexcept(std::is_nothrow_copy_constructible_v) : + m_size{count} + { + assert(count <= N); + std::uninitialized_fill_n(begin(), count, value); + } + + explicit ArrayVector(size_type count) noexcept(std::is_nothrow_default_constructible_v) : + m_size{count} + { + assert(count <= N); + std::uninitialized_value_construct_n(begin(), count); + } + + template, int> = 0> + ArrayVector(It first, It last) + { + // Can't check the size first as the iterator may not be multipass + const auto end = std::uninitialized_copy(first, last, begin()); + m_size = end - begin(); + assert(m_size <= N); + } + + ArrayVector(std::initializer_list il) noexcept(std::is_nothrow_copy_constructible_v) : + m_size{il.size()} + { + assert(il.size() <= N); + std::uninitialized_copy(il.begin(), il.end(), begin()); + } + + ~ArrayVector() { std::destroy(begin(), end()); } + + ArrayVector& operator=(const ArrayVector& other) + noexcept(std::is_nothrow_copy_assignable_v && std::is_nothrow_copy_constructible_v) + { + if (this != &other) { + const auto toAssign = std::min(other.size(), size()); + const auto assignedFromEnd = other.begin() + toAssign; + const auto assignedToEnd = std::copy(other.begin(), other.begin() + toAssign, begin()); + std::destroy(assignedToEnd, end()); + std::uninitialized_copy(assignedFromEnd, other.end(), end()); + m_size = other.size(); + } + return *this; + } + + ArrayVector& operator=(ArrayVector&& other) + noexcept(std::is_nothrow_move_assignable_v && std::is_nothrow_move_constructible_v) + { + if (this != &other) { + const auto toAssign = std::min(other.size(), size()); + const auto assignedFromEnd = other.begin() + toAssign; + const auto assignedToEnd = std::move(other.begin(), other.begin() + toAssign, begin()); + std::destroy(assignedToEnd, end()); + std::uninitialized_move(assignedFromEnd, other.end(), end()); + m_size = other.size(); + other.clear(); + } + return *this; + } + + ArrayVector& operator=(std::initializer_list il) + noexcept(std::is_nothrow_copy_assignable_v && std::is_nothrow_copy_constructible_v) + { + assert(il.size() <= N); + const auto toAssign = std::min(il.size(), size()); + const auto assignedFromEnd = il.begin() + toAssign; + const auto assignedToEnd = std::copy(il.begin(), assignedFromEnd, begin()); + std::destroy(assignedToEnd, end()); + std::uninitialized_copy(assignedFromEnd, il.end(), end()); + m_size = il.size(); + return *this; + } + + void assign(size_type count, const T& value) + noexcept(std::is_nothrow_copy_assignable_v && std::is_nothrow_copy_constructible_v) + { + assert(count <= N); + const auto temp = value; + const auto toAssign = std::min(count, size()); + const auto toConstruct = count - toAssign; + const auto assignedToEnd = std::fill_n(begin(), toAssign, temp); + std::destroy(assignedToEnd, end()); + std::uninitialized_fill_n(assignedToEnd, toConstruct, temp); + m_size = count; + } + + template, int> = 0> + void assign(It first, It last) + { + // Can't check the size first as the iterator may not be multipass + auto pos = begin(); + for (; first != last && pos != end(); ++pos, ++first) { + *pos = *first; + } + std::destroy(pos, end()); + pos = std::uninitialized_copy(first, last, pos); + m_size = pos - begin(); + assert(m_size <= N); + } + + reference at(size_type index) + { + if (index >= m_size) { throw std::out_of_range{"index out of range"}; } + return data()[index]; + } + + const_reference at(size_type index) const + { + if (index >= m_size) { throw std::out_of_range{"index out of range"}; } + return data()[index]; + } + + reference operator[](size_type index) noexcept + { + assert(index < m_size); + return data()[index]; + } + + const_reference operator[](size_type index) const noexcept + { + assert(index < m_size); + return data()[index]; + } + + reference front() noexcept { return operator[](0); } + const_reference front() const noexcept { return operator[](0); } + + reference back() noexcept { return operator[](m_size - 1); } + const_reference back() const noexcept { return operator[](m_size - 1); } + + pointer data() noexcept { return *std::launder(reinterpret_cast(m_data)); } + const_pointer data() const noexcept { return *std::launder(reinterpret_cast(m_data)); } + + iterator begin() noexcept { return data(); } + const_iterator begin() const noexcept { return data(); } + const_iterator cbegin() const noexcept { return data(); } + + iterator end() noexcept { return data() + m_size; } + const_iterator end() const noexcept { return data() + m_size; } + const_iterator cend() const noexcept { return data() + m_size; } + + reverse_iterator rbegin() noexcept { return std::reverse_iterator{end()}; } + const_reverse_iterator rbegin() const noexcept { return std::reverse_iterator{end()}; } + const_reverse_iterator crbegin() const noexcept { return std::reverse_iterator{cend()}; } + + reverse_iterator rend() noexcept { return std::reverse_iterator{begin()}; } + const_reverse_iterator rend() const noexcept { return std::reverse_iterator{begin()}; } + const_reverse_iterator crend() const noexcept { return std::reverse_iterator{cbegin()}; } + + bool empty() const noexcept { return m_size == 0; } + bool full() const noexcept { return m_size == N; } + size_type size() const noexcept { return m_size; } + size_type max_size() const noexcept { return N; } + size_type capacity() const noexcept { return N; } + + void clear() noexcept + { + std::destroy(begin(), end()); + m_size = 0; + } + + iterator insert(const_iterator pos, const T& value) { return emplace(pos, value); } + iterator insert(const_iterator pos, T&& value) { return emplace(pos, std::move(value)); } + + iterator insert(const_iterator pos, size_type count, const T& value) + { + assert(m_size + count <= N); + assert(cbegin() <= pos && pos <= cend()); + const auto mutPos = begin() + (pos - cbegin()); + const auto newEnd = std::uninitialized_fill_n(end(), count, value); + std::rotate(mutPos, end(), newEnd); + m_size += count; + return mutPos; + } + + template, int> = 0> + iterator insert(const_iterator pos, It first, It last) + { + // Can't check the size first as the iterator may not be multipass + assert(cbegin() <= pos && pos <= cend()); + const auto mutPos = begin() + (pos - cbegin()); + const auto newEnd = std::uninitialized_copy(first, last, end()); + std::rotate(mutPos, end(), newEnd); + m_size = newEnd - begin(); + assert(m_size <= N); + return mutPos; + } + + iterator insert(const_iterator pos, std::initializer_list il) { return insert(pos, il.begin(), il.end()); } + + template + iterator emplace(const_iterator pos, Args&&... args) + { + assert(cbegin() <= pos && pos <= cend()); + const auto mutPos = begin() + (pos - cbegin()); + emplace_back(std::forward(args)...); + std::rotate(mutPos, end() - 1, end()); + return mutPos; + } + + iterator erase(const_iterator pos) { return erase(pos, pos + 1); } + iterator erase(const_iterator first, const_iterator last) + { + assert(cbegin() <= first && first <= last && last <= cend()); + const auto mutFirst = begin() + (first - cbegin()); + const auto mutLast = begin() + (last - cbegin()); + const auto newEnd = std::move(mutLast, end(), mutFirst); + std::destroy(newEnd, end()); + m_size = newEnd - begin(); + return mutFirst; + } + + void push_back(const T& value) { emplace_back(value); } + void push_back(T&& value) { emplace_back(std::move(value)); } + + template + reference emplace_back(Args&&... args) + { + assert(!full()); + // TODO C++20: Use std::construct_at + const auto result = new(static_cast(end())) T(std::forward(args)...); + ++m_size; + return *result; + } + + void pop_back() + { + assert(!empty()); + --m_size; + std::destroy_at(end()); + } + + void resize(size_type size) + { + if (size > N) { throw std::length_error{"size exceeds maximum size"}; } + if (size < m_size) { + std::destroy(begin() + size, end()); + } else { + std::uninitialized_value_construct(end(), begin() + size); + } + m_size = size; + } + + void resize(size_type size, const value_type& value) + { + if (size > N) { throw std::length_error{"size exceeds maximum size"}; } + if (size < m_size) { + std::destroy(begin() + size, end()); + } else { + std::uninitialized_fill(end(), begin() + size, value); + } + m_size = size; + } + + void swap(ArrayVector& other) + noexcept(std::is_nothrow_swappable_v && std::is_nothrow_move_constructible_v) + { + using std::swap; + swap(*this, other); + } + + friend void swap(ArrayVector& a, ArrayVector& b) + noexcept(std::is_nothrow_swappable_v && std::is_nothrow_move_constructible_v) + { + const auto toSwap = std::min(a.size(), b.size()); + const auto aSwapEnd = a.begin() + toSwap; + const auto bSwapEnd = b.begin() + toSwap; + std::swap_ranges(a.begin(), aSwapEnd, b.begin()); + std::uninitialized_move(aSwapEnd, a.end(), bSwapEnd); + std::uninitialized_move(bSwapEnd, b.end(), aSwapEnd); + std::destroy(aSwapEnd, a.end()); + std::destroy(bSwapEnd, b.end()); + std::swap(a.m_size, b.m_size); + } + + // TODO C++20: Replace with operator<=> + friend bool operator<(const ArrayVector& l, const ArrayVector& r) + { + return std::lexicographical_compare(l.begin(), l.end(), r.begin(), r.end()); + } + friend bool operator<=(const ArrayVector& l, const ArrayVector& r) { return !(r < l); } + friend bool operator>(const ArrayVector& l, const ArrayVector& r) { return r < l; } + friend bool operator>=(const ArrayVector& l, const ArrayVector& r) { return !(l < r); } + + friend bool operator==(const ArrayVector& l, const ArrayVector& r) + { + return std::equal(l.begin(), l.end(), r.begin(), r.end()); + } + // TODO C++20: Remove + friend bool operator!=(const ArrayVector& l, const ArrayVector& r) { return !(l == r); } + +private: + alignas(T) std::byte m_data[std::max(N * sizeof(T), std::size_t{1})]; // Intentionally a raw array + size_type m_size = 0; +}; + +} // namespace lmms + +#endif // LMMS_ARRAY_VECTOR_H diff --git a/include/AudioEngine.h b/include/AudioEngine.h index 030c5bce39e..d3d0d025ffc 100644 --- a/include/AudioEngine.h +++ b/include/AudioEngine.h @@ -197,6 +197,7 @@ class LMMS_EXPORT AudioEngine : public QObject // 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 @@ -275,6 +276,11 @@ class LMMS_EXPORT AudioEngine : public QObject return m_profiler.cpuLoad(); } + int detailLoad(const AudioEngineProfiler::DetailType type) const + { + return m_profiler.detailLoad(type); + } + const qualitySettings & currentQualitySettings() const { return m_qualitySettings; @@ -401,6 +407,10 @@ class LMMS_EXPORT AudioEngine : public QObject AudioDevice * tryAudioDevices(); MidiClient * tryMidiClients(); + void renderStageNoteSetup(); + void renderStageInstruments(); + void renderStageEffects(); + void renderStageMix(); const surroundSampleFrame * renderNextBuffer(); diff --git a/include/AudioEngineProfiler.h b/include/AudioEngineProfiler.h index 7b5191e76b4..b0d62a1dc9e 100644 --- a/include/AudioEngineProfiler.h +++ b/include/AudioEngineProfiler.h @@ -25,6 +25,8 @@ #ifndef LMMS_AUDIO_ENGINE_PROFILER_H #define LMMS_AUDIO_ENGINE_PROFILER_H +#include +#include #include #include "lmms_basics.h" @@ -53,11 +55,55 @@ class AudioEngineProfiler void setOutputFile( const QString& outputFile ); + enum class DetailType { + NoteSetup, + Instruments, + Effects, + Mixing, + Count + }; + + constexpr static auto DetailCount = static_cast(DetailType::Count); + + int detailLoad(const DetailType type) const + { + return m_detailLoad[static_cast(type)].load(std::memory_order_relaxed); + } + + class Probe + { + public: + Probe(AudioEngineProfiler& profiler, AudioEngineProfiler::DetailType type) + : m_profiler(profiler) + , m_type(type) + { + profiler.startDetail(type); + } + ~Probe() { m_profiler.finishDetail(m_type); } + Probe& operator=(const Probe&) = delete; + Probe(const Probe&) = delete; + Probe(Probe&&) = delete; + + private: + AudioEngineProfiler &m_profiler; + const AudioEngineProfiler::DetailType m_type; + }; private: + void startDetail(const DetailType type) { m_detailTimer[static_cast(type)].reset(); } + void finishDetail(const DetailType type) + { + m_detailTime[static_cast(type)] = m_detailTimer[static_cast(type)].elapsed(); + } + MicroTimer m_periodTimer; - int m_cpuLoad; + std::atomic m_cpuLoad; QFile m_outputFile; + + // Use arrays to avoid dynamic allocations in realtime code + std::array m_detailTimer; + std::array m_detailTime{0}; + std::array, DetailCount> m_detailLoad{0}; }; } // namespace lmms diff --git a/include/CPULoadWidget.h b/include/CPULoadWidget.h index 904445c67f0..dfa5bac73da 100644 --- a/include/CPULoadWidget.h +++ b/include/CPULoadWidget.h @@ -26,6 +26,7 @@ #ifndef LMMS_GUI_CPU_LOAD_WIDGET_H #define LMMS_GUI_CPU_LOAD_WIDGET_H +#include #include #include #include @@ -40,6 +41,7 @@ namespace lmms::gui class CPULoadWidget : public QWidget { Q_OBJECT + Q_PROPERTY(int stepSize MEMBER m_stepSize) public: CPULoadWidget( QWidget * _parent ); ~CPULoadWidget() override = default; @@ -54,6 +56,8 @@ protected slots: private: + int stepSize() const { return std::max(1, m_stepSize); } + int m_currentLoad; QPixmap m_temp; @@ -64,6 +68,8 @@ protected slots: QTimer m_updateTimer; + int m_stepSize; + } ; diff --git a/include/DataFile.h b/include/DataFile.h index 137f0156fe2..dc82315adb7 100644 --- a/include/DataFile.h +++ b/include/DataFile.h @@ -126,6 +126,8 @@ class LMMS_EXPORT DataFile : public QDomDocument void upgrade_defaultTripleOscillatorHQ(); void upgrade_mixerRename(); void upgrade_bbTcoRename(); + void upgrade_sampleAndHold(); + void upgrade_midiCCIndexing(); // List of all upgrade methods static const std::vector UPGRADE_METHODS; diff --git a/include/FileBrowser.h b/include/FileBrowser.h index 3334a73f659..eafb827dac5 100644 --- a/include/FileBrowser.h +++ b/include/FileBrowser.h @@ -75,8 +75,7 @@ class FileBrowser : public SideBarWidget private slots: void reloadTree(); void expandItems( QTreeWidgetItem * item=nullptr, QList expandedDirs = QList() ); - // call with item=NULL to filter the entire tree - bool filterItems( const QString & filter, QTreeWidgetItem * item=nullptr ); + bool filterAndExpandItems(const QString & filter, QTreeWidgetItem * item = nullptr); void giveFocusToFilter(); private: @@ -84,6 +83,9 @@ private slots: void addItems( const QString & path ); + void saveDirectoriesStates(); + void restoreDirectoriesStates(); + FileBrowserTreeWidget * m_fileBrowserTreeWidget; QLineEdit * m_filterEdit; @@ -99,6 +101,8 @@ private slots: QCheckBox* m_showFactoryContent = nullptr; QString m_userDir; QString m_factoryDir; + QList m_savedExpandedDirs; + QString m_previousFilterValue; } ; @@ -115,7 +119,6 @@ class FileBrowserTreeWidget : public QTreeWidget //! that are expanded in the tree. QList expandedDirs( QTreeWidgetItem * item = nullptr ) const; - protected: void contextMenuEvent( QContextMenuEvent * e ) override; void mousePressEvent( QMouseEvent * me ) override; diff --git a/include/InstrumentPlayHandle.h b/include/InstrumentPlayHandle.h index bbf53d16c21..dc744b4ffdb 100644 --- a/include/InstrumentPlayHandle.h +++ b/include/InstrumentPlayHandle.h @@ -26,62 +26,33 @@ #define LMMS_INSTRUMENT_PLAY_HANDLE_H #include "PlayHandle.h" -#include "Instrument.h" -#include "NotePlayHandle.h" #include "lmms_export.h" namespace lmms { +class Instrument; +class InstrumentTrack; + class LMMS_EXPORT InstrumentPlayHandle : public PlayHandle { public: - InstrumentPlayHandle( Instrument * instrument, InstrumentTrack* instrumentTrack ); + InstrumentPlayHandle(Instrument * instrument, InstrumentTrack* instrumentTrack); ~InstrumentPlayHandle() override = default; - - void play( sampleFrame * _working_buffer ) override - { - // ensure that all our nph's have been processed first - ConstNotePlayHandleList nphv = NotePlayHandle::nphsOfInstrumentTrack( m_instrument->instrumentTrack(), true ); - - bool nphsLeft; - do - { - nphsLeft = false; - for( const NotePlayHandle * constNotePlayHandle : nphv ) - { - NotePlayHandle * notePlayHandle = const_cast( constNotePlayHandle ); - if( notePlayHandle->state() != ThreadableJob::ProcessingState::Done && - !notePlayHandle->isFinished()) - { - nphsLeft = true; - notePlayHandle->process(); - } - } - } - while( nphsLeft ); - - m_instrument->play( _working_buffer ); - } + void play(sampleFrame * working_buffer) override; bool isFinished() const override { return false; } - bool isFromTrack( const Track* _track ) const override - { - return m_instrument->isFromTrack( _track ); - } - + bool isFromTrack(const Track* track) const override; private: Instrument* m_instrument; - -} ; - +}; } // namespace lmms diff --git a/include/InstrumentTrack.h b/include/InstrumentTrack.h index f21723363e3..5efafe0c72b 100644 --- a/include/InstrumentTrack.h +++ b/include/InstrumentTrack.h @@ -51,7 +51,7 @@ namespace gui class InstrumentTrackView; class InstrumentTrackWindow; -class InstrumentMiscView; +class InstrumentTuningView; class MidiCCRackView; } // namespace gui @@ -315,7 +315,7 @@ protected slots: friend class gui::InstrumentTrackView; friend class gui::InstrumentTrackWindow; friend class NotePlayHandle; - friend class gui::InstrumentMiscView; + friend class gui::InstrumentTuningView; friend class gui::MidiCCRackView; } ; diff --git a/include/InstrumentTrackView.h b/include/InstrumentTrackView.h index 363f5b3abc0..d7d5fb83a39 100644 --- a/include/InstrumentTrackView.h +++ b/include/InstrumentTrackView.h @@ -25,6 +25,7 @@ #ifndef LMMS_GUI_INSTRUMENT_TRACK_VIEW_H #define LMMS_GUI_INSTRUMENT_TRACK_VIEW_H +#include "MixerLineLcdSpinBox.h" #include "TrackView.h" #include "InstrumentTrack.h" @@ -72,6 +73,7 @@ class InstrumentTrackView : public TrackView protected: + void modelChanged() override; void dragEnterEvent( QDragEnterEvent * _dee ) override; void dropEvent( QDropEvent * _de ) override; @@ -97,6 +99,7 @@ private slots: // widgets in track-settings-widget TrackLabelButton * m_tlb; + MixerLineLcdSpinBox* m_mixerChannelNumber; Knob * m_volumeKnob; Knob * m_panningKnob; FadeButton * m_activityIndicator; diff --git a/include/InstrumentTrackWindow.h b/include/InstrumentTrackWindow.h index d41bbdac894..971c63899c3 100644 --- a/include/InstrumentTrackWindow.h +++ b/include/InstrumentTrackWindow.h @@ -47,7 +47,7 @@ class MixerLineLcdSpinBox; class InstrumentFunctionArpeggioView; class InstrumentFunctionNoteStackingView; class InstrumentMidiIOView; -class InstrumentMiscView; +class InstrumentTuningView; class InstrumentSoundShapingView; class InstrumentTrackShapingView; class InstrumentTrackView; @@ -154,7 +154,7 @@ protected slots: InstrumentFunctionArpeggioView* m_arpeggioView; InstrumentMidiIOView * m_midiView; EffectRackView * m_effectView; - InstrumentMiscView *m_miscView; + InstrumentTuningView *m_tuningView; // test-piano at the bottom of every instrument-settings-window diff --git a/include/InstrumentTuningView.h b/include/InstrumentTuningView.h new file mode 100644 index 00000000000..4ee18dc8466 --- /dev/null +++ b/include/InstrumentTuningView.h @@ -0,0 +1,80 @@ +/* + * InstrumentTuningView.h - widget in instrument-track-window for setting up + * tuning and transposition options + * + * Copyright (c) 2005-2014 Tobias Doerffel + * Copyright (c) 2020-2022 Martin Pavelek + * + * 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 LMMS_GUI_INSTRUMENT_TUNING_VIEW_H +#define LMMS_GUI_INSTRUMENT_TUNING_VIEW_H + +#include + +class QLabel; + +namespace lmms +{ + +class InstrumentTrack; + +namespace gui +{ + +class ComboBox; +class GroupBox; +class LedCheckBox; + + +class InstrumentTuningView : public QWidget +{ + Q_OBJECT +public: + InstrumentTuningView(InstrumentTrack *it, QWidget *parent); + + GroupBox *pitchGroupBox() {return m_pitchGroupBox;} + GroupBox *microtunerGroupBox() {return m_microtunerGroupBox;} + + QLabel *microtunerNotSupportedLabel() {return m_microtunerNotSupportedLabel;} + + ComboBox *scaleCombo() {return m_scaleCombo;} + ComboBox *keymapCombo() {return m_keymapCombo;} + + LedCheckBox *rangeImportCheckbox() {return m_rangeImportCheckbox;} + +private: + GroupBox *m_pitchGroupBox; + GroupBox *m_microtunerGroupBox; + + QLabel *m_microtunerNotSupportedLabel; + + ComboBox *m_scaleCombo; + ComboBox *m_keymapCombo; + + LedCheckBox *m_rangeImportCheckbox; +}; + + +} // namespace gui + +} // namespace lmms + +#endif // LMMS_GUI_INSTRUMENT_TUNING_VIEW_H diff --git a/include/Knob.h b/include/Knob.h index 85a51e363e8..d5739bb1c3d 100644 --- a/include/Knob.h +++ b/include/Knob.h @@ -144,6 +144,9 @@ class LMMS_EXPORT Knob : public QWidget, public FloatModelView void wheelEvent( QWheelEvent * _me ) override; void changeEvent(QEvent * ev) override; + void enterEvent(QEvent *event) override; + void leaveEvent(QEvent *event) override; + virtual float getValue( const QPoint & _p ); private slots: @@ -160,6 +163,7 @@ private slots: float _innerRadius = 1) const; void drawKnob( QPainter * _p ); + void showTextFloat(int msecBeforeDisplay, int msecDisplayTime); void setPosition( const QPoint & _p ); bool updateAngle(); diff --git a/include/LfoController.h b/include/LfoController.h index 1c63ba69883..109edbd3fd8 100644 --- a/include/LfoController.h +++ b/include/LfoController.h @@ -86,6 +86,7 @@ public slots: sample_t (*m_sampleFunction)( const float ); private: + float m_heldSample; SampleBuffer * m_userDefSampleBuffer; protected slots: diff --git a/include/LmmsSemaphore.h b/include/LmmsSemaphore.h new file mode 100644 index 00000000000..4170eef6c65 --- /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 99c48cc904a..2d65badfe58 100644 --- a/include/LocklessRingBuffer.h +++ b/include/LocklessRingBuffer.h @@ -51,13 +51,14 @@ class LocklessRingBuffer 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 9a958d9736a..53489e30d10 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 b5bc284c87d..69a456bbde3 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 @@ class Lv2Features //! 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 909dba5607e..58126a0a448 100644 --- a/include/Lv2Manager.h +++ b/include/Lv2Manager.h @@ -31,6 +31,7 @@ #include #include +#include #include #include "Lv2Basics.h" @@ -120,15 +121,9 @@ class Lv2Manager 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 @@ class Lv2Manager 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 @@ class Lv2Manager 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 62070def7f6..76fa5eec25b 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 @@ class Lv2Proc : public LinkedModelGroup 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/Lv2ViewBase.h b/include/Lv2ViewBase.h index f7d0e9bcb4f..3c8f1bc3faf 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 00000000000..7931f8e7cde --- /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/MidiClip.h b/include/MidiClip.h index 43b322f8075..c2287bd009e 100644 --- a/include/MidiClip.h +++ b/include/MidiClip.h @@ -79,7 +79,7 @@ class LMMS_EXPORT MidiClip : public Clip void setStep( int step, bool enabled ); // Split the list of notes on the given position - void splitNotes(NoteVector notes, TimePos pos); + void splitNotes(const NoteVector& notes, TimePos pos); // clip-type stuff inline Type type() const diff --git a/include/MidiController.h b/include/MidiController.h index c4ef49590a6..9f49627acc6 100644 --- a/include/MidiController.h +++ b/include/MidiController.h @@ -48,6 +48,7 @@ class MidiController : public Controller, public MidiEventProcessor { Q_OBJECT public: + static constexpr int NONE = -1; MidiController( Model * _parent ); ~MidiController() override = default; diff --git a/include/Note.h b/include/Note.h index 5e3a1b8a2bd..2df196af20a 100644 --- a/include/Note.h +++ b/include/Note.h @@ -91,7 +91,7 @@ const int DefaultMiddleKey = Octave::Octave_4 + Key::C; const int DefaultBaseKey = Octave::Octave_4 + Key::A; const float DefaultBaseFreq = 440.f; -const float MaxDetuning = 4 * 12.0f; +const float MaxDetuning = 5 * 12.0f; diff --git a/include/NotePlayHandle.h b/include/NotePlayHandle.h index 46b14c4cd44..7105d6672cd 100644 --- a/include/NotePlayHandle.h +++ b/include/NotePlayHandle.h @@ -108,6 +108,9 @@ class LMMS_EXPORT NotePlayHandle : public PlayHandle, public Note return m_unpitchedFrequency; } + //! Get the current per-note detuning for this note + float currentDetuning() const { return m_baseDetuning->value(); } + /*! Renders one chunk using the attached instrument into the buffer */ void play( sampleFrame* buffer ) override; @@ -245,7 +248,7 @@ class LMMS_EXPORT NotePlayHandle : public PlayHandle, public Note } /*! Process note detuning automation */ - void processTimePos( const TimePos& time ); + void processTimePos(const TimePos& time, float pitchValue, bool isRecording); /*! Updates total length (m_frames) depending on a new tempo */ void resize( const bpm_t newTempo ); diff --git a/include/PianoRoll.h b/include/PianoRoll.h index 9f3bbcd7d20..38788180f8f 100644 --- a/include/PianoRoll.h +++ b/include/PianoRoll.h @@ -308,9 +308,9 @@ protected slots: TimePos newNoteLen() const; void shiftPos(int amount); - void shiftPos(NoteVector notes, int amount); + void shiftPos(const NoteVector& notes, int amount); void shiftSemiTone(int amount); - void shiftSemiTone(NoteVector notes, int amount); + void shiftSemiTone(const NoteVector& notes, int amount); bool isSelection() const; int selectionCount() const; void testPlayNote( Note * n ); diff --git a/include/SampleClip.h b/include/SampleClip.h index c9e247328ca..5246787bdc6 100644 --- a/include/SampleClip.h +++ b/include/SampleClip.h @@ -77,7 +77,7 @@ class SampleClip : public Clip public slots: void setSampleBuffer( lmms::SampleBuffer* sb ); - void setSampleFile( const QString & _sf ); + void setSampleFile( const QString & sf ); void updateLength(); void toggleRecord(); void playbackPositionChanged(); diff --git a/include/SampleTrackView.h b/include/SampleTrackView.h index b586df15edc..3ccb97aeaa9 100644 --- a/include/SampleTrackView.h +++ b/include/SampleTrackView.h @@ -26,6 +26,7 @@ #define LMMS_GUI_SAMPLE_TRACK_VIEW_H +#include "MixerLineLcdSpinBox.h" #include "TrackView.h" namespace lmms @@ -90,6 +91,7 @@ private slots: private: SampleTrackWindow * m_window; + MixerLineLcdSpinBox* m_mixerChannelNumber; Knob * m_volumeKnob; Knob * m_panningKnob; FadeButton * m_activityIndicator; diff --git a/include/SetupDialog.h b/include/SetupDialog.h index de4cdd9ddbd..fa41325db79 100644 --- a/include/SetupDialog.h +++ b/include/SetupDialog.h @@ -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(); @@ -179,6 +180,7 @@ private slots: int m_bufferSize; QSlider * m_bufferSizeSlider; QLabel * m_bufferSizeLbl; + QLabel * m_bufferSizeWarnLbl; // MIDI settings widgets. QComboBox * m_midiInterfaces; diff --git a/include/SimpleTextFloat.h b/include/SimpleTextFloat.h index f720d0b3ef3..bde6c84faab 100644 --- a/include/SimpleTextFloat.h +++ b/include/SimpleTextFloat.h @@ -31,6 +31,7 @@ #include "lmms_export.h" class QLabel; +class QTimer; namespace lmms::gui { @@ -44,6 +45,8 @@ class LMMS_EXPORT SimpleTextFloat : public QWidget void setText(const QString & text); + void showWithDelay(int msecBeforeDisplay, int msecDisplayTime); + void setVisibilityTimeOut(int msecs); void moveGlobal(QWidget * w, const QPoint & offset) @@ -51,11 +54,14 @@ class LMMS_EXPORT SimpleTextFloat : public QWidget move(w->mapToGlobal(QPoint(0, 0)) + offset); } + void hide(); + private: QLabel * m_textLabel; + QTimer * m_showTimer; + QTimer * m_hideTimer; }; - } // namespace lmms::gui #endif diff --git a/include/TrackView.h b/include/TrackView.h index 763705599c5..f697d9ea86a 100644 --- a/include/TrackView.h +++ b/include/TrackView.h @@ -48,11 +48,11 @@ class FadeButton; class TrackContainerView; -const int DEFAULT_SETTINGS_WIDGET_WIDTH = 224; +const int DEFAULT_SETTINGS_WIDGET_WIDTH = 256; const int TRACK_OP_WIDTH = 78; // This shaves 150-ish pixels off track buttons, // ruled from config: ui.compacttrackbuttons -const int DEFAULT_SETTINGS_WIDGET_WIDTH_COMPACT = 96; +const int DEFAULT_SETTINGS_WIDGET_WIDTH_COMPACT = 128; const int TRACK_OP_WIDTH_COMPACT = 62; diff --git a/include/lmms_math.h b/include/lmms_math.h index b62da81c249..ea0a75581e8 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/AudioFileProcessor/AudioFileProcessor.cpp b/plugins/AudioFileProcessor/AudioFileProcessor.cpp index a941e773f25..6671022070c 100644 --- a/plugins/AudioFileProcessor/AudioFileProcessor.cpp +++ b/plugins/AudioFileProcessor/AudioFileProcessor.cpp @@ -171,9 +171,6 @@ void AudioFileProcessor::playNote( NotePlayHandle * _n, static_cast( m_loopModel.value() ) ) ) { applyRelease( _working_buffer, _n ); - instrumentTrack()->processAudioBuffer( _working_buffer, - frames + offset, _n ); - emit isPlaying( ((handleState *)_n->m_pluginData)->frameIndex() ); } else diff --git a/plugins/BitInvader/BitInvader.cpp b/plugins/BitInvader/BitInvader.cpp index 98ef1e97cf1..4ea73dc7116 100644 --- a/plugins/BitInvader/BitInvader.cpp +++ b/plugins/BitInvader/BitInvader.cpp @@ -307,8 +307,6 @@ void BitInvader::playNote( NotePlayHandle * _n, } applyRelease( _working_buffer, _n ); - - instrumentTrack()->processAudioBuffer( _working_buffer, frames + offset, _n ); } diff --git a/plugins/CarlaBase/Carla.cpp b/plugins/CarlaBase/Carla.cpp index faff94b570b..819736e928b 100644 --- a/plugins/CarlaBase/Carla.cpp +++ b/plugins/CarlaBase/Carla.cpp @@ -508,7 +508,6 @@ void CarlaInstrument::play(sampleFrame* workingBuffer) if (fHandle == nullptr) { - instrumentTrack()->processAudioBuffer(workingBuffer, bufsize, nullptr); return; } @@ -556,8 +555,6 @@ void CarlaInstrument::play(sampleFrame* workingBuffer) workingBuffer[i][0] = buf1[i]; workingBuffer[i][1] = buf2[i]; } - - instrumentTrack()->processAudioBuffer(workingBuffer, bufsize, nullptr); } bool CarlaInstrument::handleMidiEvent(const MidiEvent& event, const TimePos&, f_cnt_t offset) diff --git a/plugins/Compressor/Compressor.cpp b/plugins/Compressor/Compressor.cpp index 3c5ad615714..0fe13942083 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() @@ -320,8 +321,8 @@ bool CompressorEffect::processAudioBuffer(sampleFrame* buf, const fpp_t frames) float inputValue = feedback ? m_prevOut[i] : s[i]; // Calculate the crest factor of the audio by diving the peak by the RMS - m_crestPeakVal[i] = qMax(inputValue * inputValue, m_crestTimeConst * m_crestPeakVal[i] + (1 - m_crestTimeConst) * (inputValue * inputValue)); - m_crestRmsVal[i] = m_crestTimeConst * m_crestRmsVal[i] + ((1 - m_crestTimeConst) * (inputValue * inputValue)); + m_crestPeakVal[i] = qMax(qMax(COMP_NOISE_FLOOR, inputValue * inputValue), m_crestTimeConst * m_crestPeakVal[i] + (1 - m_crestTimeConst) * (inputValue * inputValue)); + m_crestRmsVal[i] = qMax(COMP_NOISE_FLOOR, m_crestTimeConst * m_crestRmsVal[i] + ((1 - m_crestTimeConst) * (inputValue * inputValue))); m_crestFactorVal[i] = m_crestPeakVal[i] / m_crestRmsVal[i]; m_rmsVal[i] = m_rmsTimeConst * m_rmsVal[i] + ((1 - m_rmsTimeConst) * (inputValue * inputValue)); diff --git a/plugins/Compressor/CompressorControlDialog.cpp b/plugins/Compressor/CompressorControlDialog.cpp index 114980a7d96..1516456a22a 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/FreeBoy/FreeBoy.cpp b/plugins/FreeBoy/FreeBoy.cpp index 6450253ee45..f2dc95699ed 100644 --- a/plugins/FreeBoy/FreeBoy.cpp +++ b/plugins/FreeBoy/FreeBoy.cpp @@ -419,7 +419,6 @@ void FreeBoyInstrument::playNote(NotePlayHandle* nph, sampleFrame* workingBuffer } framesLeft -= count; } - instrumentTrack()->processAudioBuffer(workingBuffer, frames + offset, nph); } diff --git a/plugins/GigPlayer/GigPlayer.cpp b/plugins/GigPlayer/GigPlayer.cpp index c2e155a20c5..0713d310038 100644 --- a/plugins/GigPlayer/GigPlayer.cpp +++ b/plugins/GigPlayer/GigPlayer.cpp @@ -494,8 +494,6 @@ void GigInstrument::play( sampleFrame * _working_buffer ) _working_buffer[i][0] *= m_gain.value(); _working_buffer[i][1] *= m_gain.value(); } - - instrumentTrack()->processAudioBuffer( _working_buffer, frames, nullptr ); } diff --git a/plugins/Kicker/Kicker.cpp b/plugins/Kicker/Kicker.cpp index ef1d623c1a1..e6418e2da5b 100644 --- a/plugins/Kicker/Kicker.cpp +++ b/plugins/Kicker/Kicker.cpp @@ -197,8 +197,6 @@ void KickerInstrument::playNote( NotePlayHandle * _n, _working_buffer[f+offset][1] *= fac; } } - - instrumentTrack()->processAudioBuffer( _working_buffer, frames + offset, _n ); } diff --git a/plugins/LadspaEffect/calf/CMakeLists.txt b/plugins/LadspaEffect/calf/CMakeLists.txt index 6ec392a81d2..6f7bb8c0110 100644 --- a/plugins/LadspaEffect/calf/CMakeLists.txt +++ b/plugins/LadspaEffect/calf/CMakeLists.txt @@ -36,11 +36,11 @@ TARGET_COMPILE_DEFINITIONS(veal PRIVATE DISABLE_OSC=1) SET(INLINE_FLAGS "") IF("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU") - SET(INLINE_FLAGS "-finline-functions-called-once -finline-limit=80") + SET(INLINE_FLAGS -finline-functions-called-once -finline-limit=80) ENDIF() IF(NOT MSVC) - SET_TARGET_PROPERTIES(veal PROPERTIES COMPILE_FLAGS "-fexceptions -O2 -finline-functions ${INLINE_FLAGS}") + target_compile_options(veal PRIVATE -fexceptions -O2 -finline-functions ${INLINE_FLAGS}) endif() if(LMMS_BUILD_WIN32) @@ -53,5 +53,5 @@ if(LMMS_BUILD_WIN32) ) endif() IF(NOT LMMS_BUILD_APPLE AND NOT LMMS_BUILD_OPENBSD) - SET_TARGET_PROPERTIES(veal PROPERTIES LINK_FLAGS "${LINK_FLAGS} -shared -Wl,-no-undefined") + target_link_libraries(veal PRIVATE -shared) ENDIF() diff --git a/plugins/LadspaEffect/caps/Descriptor.h b/plugins/LadspaEffect/caps/Descriptor.h index 12c5d1c8846..c3e1c325e73 100644 --- a/plugins/LadspaEffect/caps/Descriptor.h +++ b/plugins/LadspaEffect/caps/Descriptor.h @@ -53,7 +53,7 @@ class DescriptorStub PortCount = 0; } - ~DescriptorStub() + virtual ~DescriptorStub() { if (PortCount) { @@ -87,6 +87,7 @@ class Descriptor public: Descriptor() { setup(); } + ~Descriptor() override = default; void setup(); void autogen() diff --git a/plugins/LadspaEffect/cmt/CMakeLists.txt b/plugins/LadspaEffect/cmt/CMakeLists.txt index ded7b895898..75dba319d6c 100644 --- a/plugins/LadspaEffect/cmt/CMakeLists.txt +++ b/plugins/LadspaEffect/cmt/CMakeLists.txt @@ -5,7 +5,7 @@ ADD_LIBRARY(cmt MODULE ${SOURCES}) INSTALL(TARGETS cmt LIBRARY DESTINATION "${PLUGIN_DIR}/ladspa") SET_TARGET_PROPERTIES(cmt PROPERTIES PREFIX "") -SET_TARGET_PROPERTIES(cmt PROPERTIES COMPILE_FLAGS "-Wall -O3 -fno-strict-aliasing") +target_compile_options(cmt PRIVATE -Wall -O3 -fno-strict-aliasing) if(LMMS_BUILD_WIN32) add_custom_command( @@ -18,10 +18,10 @@ if(LMMS_BUILD_WIN32) endif() if(NOT LMMS_BUILD_WIN32) - set_target_properties(cmt PROPERTIES COMPILE_FLAGS "${COMPILE_FLAGS} -fPIC") + target_compile_options(cmt PRIVATE -fPIC) endif() IF(NOT LMMS_BUILD_APPLE AND NOT LMMS_BUILD_OPENBSD) - SET_TARGET_PROPERTIES(cmt PROPERTIES LINK_FLAGS "${LINK_FLAGS} -shared -Wl,-no-undefined") + target_link_libraries(cmt PRIVATE -shared) ENDIF() diff --git a/plugins/Lb302/Lb302.cpp b/plugins/Lb302/Lb302.cpp index b8fff2c0b8a..ee49442d5d4 100644 --- a/plugins/Lb302/Lb302.cpp +++ b/plugins/Lb302/Lb302.cpp @@ -790,7 +790,6 @@ void Lb302Synth::play( sampleFrame * _working_buffer ) const fpp_t frames = Engine::audioEngine()->framesPerPeriod(); process( _working_buffer, frames ); - instrumentTrack()->processAudioBuffer( _working_buffer, frames, nullptr ); // release_frame = 0; //removed for issue # 1432 } diff --git a/plugins/Lv2Instrument/Lv2Instrument.cpp b/plugins/Lv2Instrument/Lv2Instrument.cpp index 1e45f4e919e..32f81d23c25 100644 --- a/plugins/Lv2Instrument/Lv2Instrument.cpp +++ b/plugins/Lv2Instrument/Lv2Instrument.cpp @@ -197,8 +197,6 @@ void Lv2Instrument::play(sampleFrame *buf) copyModelsToLmms(); copyBuffersToLmms(buf, fpp); - - instrumentTrack()->processAudioBuffer(buf, fpp, nullptr); } diff --git a/plugins/Monstro/Monstro.cpp b/plugins/Monstro/Monstro.cpp index f588d6b786d..2201e4ed90b 100644 --- a/plugins/Monstro/Monstro.cpp +++ b/plugins/Monstro/Monstro.cpp @@ -1040,8 +1040,6 @@ void MonstroInstrument::playNote( NotePlayHandle * _n, ms->renderOutput( frames, _working_buffer + offset ); //applyRelease( _working_buffer, _n ); // we have our own release - - instrumentTrack()->processAudioBuffer( _working_buffer, frames + offset, _n ); } void MonstroInstrument::deleteNotePluginData( NotePlayHandle * _n ) diff --git a/plugins/Nes/Nes.cpp b/plugins/Nes/Nes.cpp index a530ac19b3b..47122a0c602 100644 --- a/plugins/Nes/Nes.cpp +++ b/plugins/Nes/Nes.cpp @@ -561,8 +561,6 @@ void NesInstrument::playNote( NotePlayHandle * n, sampleFrame * workingBuffer ) nes->renderOutput( workingBuffer + offset, frames ); applyRelease( workingBuffer, n ); - - instrumentTrack()->processAudioBuffer( workingBuffer, frames + offset, n ); } diff --git a/plugins/OpulenZ/OpulenZ.cpp b/plugins/OpulenZ/OpulenZ.cpp index 64f60999576..d90d5f343a4 100644 --- a/plugins/OpulenZ/OpulenZ.cpp +++ b/plugins/OpulenZ/OpulenZ.cpp @@ -412,10 +412,6 @@ void OpulenzInstrument::play( sampleFrame * _working_buffer ) } } emulatorMutex.unlock(); - - // Throw the data to the track... - instrumentTrack()->processAudioBuffer( _working_buffer, frameCount, nullptr ); - } diff --git a/plugins/Organic/Organic.cpp b/plugins/Organic/Organic.cpp index f8a2b0d135c..a70da642156 100644 --- a/plugins/Organic/Organic.cpp +++ b/plugins/Organic/Organic.cpp @@ -312,8 +312,6 @@ void OrganicInstrument::playNote( NotePlayHandle * _n, } // -- -- - - instrumentTrack()->processAudioBuffer( _working_buffer, frames + offset, _n ); } diff --git a/plugins/Patman/Patman.cpp b/plugins/Patman/Patman.cpp index a2b829940a4..24c54d66be5 100644 --- a/plugins/Patman/Patman.cpp +++ b/plugins/Patman/Patman.cpp @@ -157,8 +157,6 @@ void PatmanInstrument::playNote( NotePlayHandle * _n, play_freq, m_loopedModel.value() ? SampleBuffer::LoopMode::On : SampleBuffer::LoopMode::Off ) ) { applyRelease( _working_buffer, _n ); - instrumentTrack()->processAudioBuffer( _working_buffer, - frames + offset, _n ); } else { @@ -446,7 +444,7 @@ namespace gui PatmanView::PatmanView( Instrument * _instrument, QWidget * _parent ) : InstrumentViewFixedSize( _instrument, _parent ), - m_pi( nullptr ) + m_pi(castModel()) { setAutoFillBackground( true ); QPalette pal; @@ -487,7 +485,15 @@ PatmanView::PatmanView( Instrument * _instrument, QWidget * _parent ) : "tune_off" ) ); m_tuneButton->setToolTip(tr("Tune mode")); - m_displayFilename = tr( "No file selected" ); + + if (m_pi->m_patchFile.isEmpty()) + { + m_displayFilename = tr("No file selected"); + } + else + { + updateFilename(); + } setAcceptDrops( true ); } diff --git a/plugins/Sf2Player/Sf2Player.cpp b/plugins/Sf2Player/Sf2Player.cpp index 1f0cb7c59e2..79bd4b97686 100644 --- a/plugins/Sf2Player/Sf2Player.cpp +++ b/plugins/Sf2Player/Sf2Player.cpp @@ -30,6 +30,7 @@ #include #include +#include "ArrayVector.h" #include "AudioEngine.h" #include "ConfigManager.h" #include "FileDialog.h" @@ -71,17 +72,47 @@ Plugin::Descriptor PLUGIN_EXPORT sf2player_plugin_descriptor = } +/** + * A non-owning reference to a single FluidSynth voice, for tracking whether the + * referenced voice is still the same voice that was passed to the constructor. + */ +class FluidVoice +{ +public: + //! Create a reference to the voice currently pointed at by `voice`. + explicit FluidVoice(fluid_voice_t* voice) : + m_voice{voice}, + m_id{fluid_voice_get_id(voice)} + { } + + //! Get a pointer to the referenced voice. + fluid_voice_t* get() const noexcept { return m_voice; } + + //! Test whether this object still refers to the original voice. + bool isValid() const + { + return fluid_voice_get_id(m_voice) == m_id && fluid_voice_is_playing(m_voice); + } + +private: + fluid_voice_t* m_voice; + unsigned int m_id; +}; struct Sf2PluginData { int midiNote; int lastPanning; float lastVelocity; - fluid_voice_t * fluidVoice; + // The soundfonts I checked used at most two voices per note, so space for + // four should be safe. This may need to be increased if a soundfont with + // more voices per note is found. + ArrayVector fluidVoices; bool isNew; f_cnt_t offset; bool noteOffSent; -} ; + panning_t panning; +}; @@ -681,10 +712,10 @@ void Sf2Instrument::playNote( NotePlayHandle * _n, sampleFrame * ) pluginData->midiNote = midiNote; pluginData->lastPanning = 0; pluginData->lastVelocity = _n->midiVelocity( baseVelocity ); - pluginData->fluidVoice = nullptr; pluginData->isNew = true; pluginData->offset = _n->offset(); pluginData->noteOffSent = false; + pluginData->panning = _n->getPanning(); _n->m_pluginData = pluginData; @@ -703,6 +734,17 @@ void Sf2Instrument::playNote( NotePlayHandle * _n, sampleFrame * ) m_playingNotes.append( _n ); m_playingNotesMutex.unlock(); } + + // Update the pitch of all the voices + if (const auto data = static_cast(_n->m_pluginData)) { + const auto detuning = _n->currentDetuning(); + for (const auto& voice : data->fluidVoices) { + if (voice.isValid()) { + fluid_voice_gen_set(voice.get(), GEN_COARSETUNE, detuning); + fluid_voice_update_param(voice.get(), GEN_COARSETUNE); + } + } + } } @@ -715,34 +757,46 @@ void Sf2Instrument::noteOn( Sf2PluginData * n ) const int poly = fluid_synth_get_polyphony( m_synth ); #ifndef _MSC_VER fluid_voice_t* voices[poly]; - unsigned int id[poly]; #else const auto voices = static_cast(_alloca(poly * sizeof(fluid_voice_t*))); - const auto id = static_cast(_alloca(poly * sizeof(unsigned int))); #endif - fluid_synth_get_voicelist( m_synth, voices, poly, -1 ); - for( int i = 0; i < poly; ++i ) - { - id[i] = 0; - } - for( int i = 0; i < poly && voices[i]; ++i ) - { - id[i] = fluid_voice_get_id( voices[i] ); - } fluid_synth_noteon( m_synth, m_channel, n->midiNote, n->lastVelocity ); - // get new voice and save it - fluid_synth_get_voicelist( m_synth, voices, poly, -1 ); - for( int i = 0; i < poly && voices[i]; ++i ) + // Get any new voices and store them in the plugin data + fluid_synth_get_voicelist(m_synth, voices, poly, -1); + for (int i = 0; i < poly && voices[i] && !n->fluidVoices.full(); ++i) { - const unsigned int newID = fluid_voice_get_id( voices[i] ); - if( id[i] != newID || newID == 0 ) - { - n->fluidVoice = voices[i]; - break; + const auto voice = voices[i]; + // FluidSynth stops voices with the same channel and pitch upon note-on, + // so voices with the current channel and pitch are playing this note. + if (fluid_voice_get_channel(voice) == m_channel + && fluid_voice_get_key(voice) == n->midiNote + && fluid_voice_is_on(voice) + ) { + n->fluidVoices.emplace_back(voices[i]); + } + } + +#if FLUIDSYNTH_VERSION_MAJOR >= 2 + // Smallest balance value that results in full attenuation of one channel. + // Corresponds to internal FluidSynth macro `FLUID_CB_AMP_SIZE`. + constexpr static auto maxBalance = 1441.f; + // Convert panning from linear to exponential for FluidSynth + const auto panning = n->panning; + const auto factor = 1.f - std::abs(panning) / static_cast(PanningRight); + const auto balance = std::copysign( + factor <= 0 ? maxBalance : std::min(-200.f * std::log10(factor), maxBalance), + panning + ); + // Set note panning on all the voices + for (const auto& voice : n->fluidVoices) { + if (voice.isValid()) { + fluid_voice_gen_set(voice.get(), GEN_CUSTOM_BALANCE, balance); + fluid_voice_update_param(voice.get(), GEN_CUSTOM_BALANCE); } } +#endif m_synthMutex.unlock(); @@ -794,7 +848,6 @@ void Sf2Instrument::play( sampleFrame * _working_buffer ) if( m_playingNotes.isEmpty() ) { renderFrames( frames, _working_buffer ); - instrumentTrack()->processAudioBuffer( _working_buffer, frames, nullptr ); return; } @@ -852,13 +905,13 @@ void Sf2Instrument::play( sampleFrame * _working_buffer ) { renderFrames( frames - currentFrame, _working_buffer + currentFrame ); } - instrumentTrack()->processAudioBuffer( _working_buffer, frames, nullptr ); } void Sf2Instrument::renderFrames( f_cnt_t frames, sampleFrame * buf ) { m_synthMutex.lock(); + fluid_synth_get_gain(m_synth); // This flushes voice updates as a side effect if( m_internalSampleRate < Engine::audioEngine()->processingSampleRate() && m_srcState != nullptr ) { diff --git a/plugins/Sfxr/Sfxr.cpp b/plugins/Sfxr/Sfxr.cpp index fc39ea0fa28..e79b8e2adbe 100644 --- a/plugins/Sfxr/Sfxr.cpp +++ b/plugins/Sfxr/Sfxr.cpp @@ -480,9 +480,6 @@ void SfxrInstrument::playNote( NotePlayHandle * _n, sampleFrame * _working_buffe delete[] pitchedBuffer; applyRelease( _working_buffer, _n ); - - instrumentTrack()->processAudioBuffer( _working_buffer, frameNum + offset, _n ); - } diff --git a/plugins/Sid/CMakeLists.txt b/plugins/Sid/CMakeLists.txt index c9fce7bb77d..c771fc66de6 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 f663c3b6977..7f9edf13f18 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 @@ -429,8 +429,6 @@ void SidInstrument::playNote( NotePlayHandle * _n, _working_buffer[frame+offset][ch] = s; } } - - instrumentTrack()->processAudioBuffer( _working_buffer, frames + offset, _n ); } @@ -438,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 index a643428f29b..40443cc903d 160000 --- a/plugins/Sid/resid +++ b/plugins/Sid/resid @@ -1 +1 @@ -Subproject commit a643428f29b67071a64f5ca1158f4fd902a91b57 +Subproject commit 40443cc903dddcb44b1de8ccf2b1e37202ba34d5 diff --git a/plugins/Stk/Mallets/Mallets.cpp b/plugins/Stk/Mallets/Mallets.cpp index 4fb077de5bc..b746e949120 100644 --- a/plugins/Stk/Mallets/Mallets.cpp +++ b/plugins/Stk/Mallets/Mallets.cpp @@ -359,8 +359,6 @@ void MalletsInstrument::playNote( NotePlayHandle * _n, _working_buffer[frame][1] = ps->nextSampleRight() * ( m_scalers[p] + add_scale ); } - - instrumentTrack()->processAudioBuffer( _working_buffer, frames + offset, _n ); } diff --git a/plugins/TripleOscillator/TripleOscillator.cpp b/plugins/TripleOscillator/TripleOscillator.cpp index f2340d3d609..5b8f6e8ade6 100644 --- a/plugins/TripleOscillator/TripleOscillator.cpp +++ b/plugins/TripleOscillator/TripleOscillator.cpp @@ -380,8 +380,6 @@ void TripleOscillator::playNote( NotePlayHandle * _n, applyFadeIn(_working_buffer, _n); applyRelease( _working_buffer, _n ); - - instrumentTrack()->processAudioBuffer( _working_buffer, frames + offset, _n ); } diff --git a/plugins/Vestige/Vestige.cpp b/plugins/Vestige/Vestige.cpp index dd8e9cbef3b..a696a4b2ded 100644 --- a/plugins/Vestige/Vestige.cpp +++ b/plugins/Vestige/Vestige.cpp @@ -399,8 +399,6 @@ void VestigeInstrument::play( sampleFrame * _buf ) { if (!m_pluginMutex.tryLock(Engine::getSong()->isExporting() ? -1 : 0)) {return;} - const fpp_t frames = Engine::audioEngine()->framesPerPeriod(); - if( m_plugin == nullptr ) { m_pluginMutex.unlock(); @@ -409,8 +407,6 @@ void VestigeInstrument::play( sampleFrame * _buf ) m_plugin->process( nullptr, _buf ); - instrumentTrack()->processAudioBuffer( _buf, frames, nullptr ); - m_pluginMutex.unlock(); } diff --git a/plugins/Vibed/Vibed.cpp b/plugins/Vibed/Vibed.cpp index 3ed51fe79d7..ad6a3942af4 100644 --- a/plugins/Vibed/Vibed.cpp +++ b/plugins/Vibed/Vibed.cpp @@ -251,8 +251,6 @@ void Vibed::playNote(NotePlayHandle* n, sampleFrame* workingBuffer) } } } - - instrumentTrack()->processAudioBuffer(workingBuffer, frames + offset, n); } void Vibed::deleteNotePluginData(NotePlayHandle* n) diff --git a/plugins/VstEffect/VstSubPluginFeatures.cpp b/plugins/VstEffect/VstSubPluginFeatures.cpp index f929b5526bb..7eab7a9bf67 100644 --- a/plugins/VstEffect/VstSubPluginFeatures.cpp +++ b/plugins/VstEffect/VstSubPluginFeatures.cpp @@ -82,7 +82,11 @@ void VstSubPluginFeatures::addPluginsFromDir( QStringList* filenames, QString pa } } QStringList dlls = QDir( ConfigManager::inst()->vstDir() + path ). - entryList( QStringList() << "*.dll", + entryList( QStringList() << "*.dll" +#ifdef LMMS_BUILD_LINUX + << "*.so" +#endif + , QDir::Files, QDir::Name ); for( int i = 0; i < dlls.size(); i++ ) { diff --git a/plugins/Watsyn/Watsyn.cpp b/plugins/Watsyn/Watsyn.cpp index 7603a9c1be6..8e49942e16a 100644 --- a/plugins/Watsyn/Watsyn.cpp +++ b/plugins/Watsyn/Watsyn.cpp @@ -445,8 +445,6 @@ void WatsynInstrument::playNote( NotePlayHandle * _n, } applyRelease( _working_buffer, _n ); - - instrumentTrack()->processAudioBuffer( _working_buffer, frames + offset, _n ); } diff --git a/plugins/Xpressive/Xpressive.cpp b/plugins/Xpressive/Xpressive.cpp index b1a17a1ce76..babc372317a 100644 --- a/plugins/Xpressive/Xpressive.cpp +++ b/plugins/Xpressive/Xpressive.cpp @@ -233,8 +233,6 @@ void Xpressive::playNote(NotePlayHandle* nph, sampleFrame* working_buffer) { const f_cnt_t offset = nph->noteOffset(); ps->renderOutput(frames, working_buffer + offset); - - instrumentTrack()->processAudioBuffer(working_buffer, frames + offset, nph); } void Xpressive::deleteNotePluginData(NotePlayHandle* nph) { diff --git a/plugins/ZynAddSubFx/ZynAddSubFx.cpp b/plugins/ZynAddSubFx/ZynAddSubFx.cpp index 2ec86459281..be38bcb7978 100644 --- a/plugins/ZynAddSubFx/ZynAddSubFx.cpp +++ b/plugins/ZynAddSubFx/ZynAddSubFx.cpp @@ -341,7 +341,6 @@ void ZynAddSubFxInstrument::play( sampleFrame * _buf ) m_plugin->processAudio( _buf ); } m_pluginMutex.unlock(); - instrumentTrack()->processAudioBuffer( _buf, Engine::audioEngine()->framesPerPeriod(), nullptr ); } diff --git a/src/core/AudioEngine.cpp b/src/core/AudioEngine.cpp index 21a9a3598bc..29c54647cf7 100644 --- a/src/core/AudioEngine.cpp +++ b/src/core/AudioEngine.cpp @@ -333,12 +333,9 @@ void AudioEngine::pushInputFrames( sampleFrame * _ab, const f_cnt_t _frames ) - -const surroundSampleFrame * AudioEngine::renderNextBuffer() +void AudioEngine::renderStageNoteSetup() { - m_profiler.startPeriod(); - - s_renderingThread = true; + AudioEngineProfiler::Probe profilerProbe(m_profiler, AudioEngineProfiler::DetailType::NoteSetup); if( m_clearSignal ) { @@ -387,9 +384,15 @@ const surroundSampleFrame * AudioEngine::renderNextBuffer() m_newPlayHandles.free( e ); e = next; } +} + + - // STAGE 1: run and render all play handles - AudioEngineWorkerThread::fillJobQueue( m_playHandles ); +void AudioEngine::renderStageInstruments() +{ + AudioEngineProfiler::Probe profilerProbe(m_profiler, AudioEngineProfiler::DetailType::Instruments); + + AudioEngineWorkerThread::fillJobQueue(m_playHandles); AudioEngineWorkerThread::startAndWaitForJobs(); // removed all play handles which are done @@ -417,15 +420,27 @@ const surroundSampleFrame * AudioEngine::renderNextBuffer() ++it; } } +} + + + +void AudioEngine::renderStageEffects() +{ + AudioEngineProfiler::Probe profilerProbe(m_profiler, AudioEngineProfiler::DetailType::Effects); // STAGE 2: process effects of all instrument- and sampletracks AudioEngineWorkerThread::fillJobQueue(m_audioPorts); AudioEngineWorkerThread::startAndWaitForJobs(); +} - // STAGE 3: do master mix in mixer - mixer->masterMix(m_outputBufferWrite); +void AudioEngine::renderStageMix() +{ + AudioEngineProfiler::Probe profilerProbe(m_profiler, AudioEngineProfiler::DetailType::Mixing); + + Mixer *mixer = Engine::mixer(); + mixer->masterMix(m_outputBufferWrite); emit nextAudioBuffer(m_outputBufferRead); @@ -435,10 +450,22 @@ const surroundSampleFrame * AudioEngine::renderNextBuffer() EnvelopeAndLfoParameters::instances()->trigger(); Controller::triggerFrameCounter(); AutomatableModel::incrementPeriodCounter(); +} - s_renderingThread = false; - m_profiler.finishPeriod( processingSampleRate(), m_framesPerPeriod ); + +const surroundSampleFrame *AudioEngine::renderNextBuffer() +{ + m_profiler.startPeriod(); + s_renderingThread = true; + + renderStageNoteSetup(); // STAGE 0: clear old play handles and buffers, setup new play handles + renderStageInstruments(); // STAGE 1: run and render all play handles + renderStageEffects(); // STAGE 2: process effects of all instrument- and sampletracks + renderStageMix(); // STAGE 3: do master mix in mixer + + s_renderingThread = false; + m_profiler.finishPeriod(processingSampleRate(), m_framesPerPeriod); return m_outputBufferRead; } @@ -674,7 +701,10 @@ void AudioEngine::removeAudioPort(AudioPort * port) bool AudioEngine::addPlayHandle( PlayHandle* handle ) { - if( criticalXRuns() == false ) + // Only add play handles if we have the CPU capacity to process them. + // Instrument play handles are not added during playback, but when the + // associated instrument is created, so add those unconditionally. + if (handle->type() == PlayHandle::Type::InstrumentPlayHandle || !criticalXRuns()) { m_newPlayHandles.push( handle ); handle->audioPort()->addPlayHandle( handle ); diff --git a/src/core/AudioEngineProfiler.cpp b/src/core/AudioEngineProfiler.cpp index 9e05ff80afa..82a412cbb98 100644 --- a/src/core/AudioEngineProfiler.cpp +++ b/src/core/AudioEngineProfiler.cpp @@ -24,6 +24,8 @@ #include "AudioEngineProfiler.h" +#include + namespace lmms { @@ -38,10 +40,24 @@ AudioEngineProfiler::AudioEngineProfiler() : void AudioEngineProfiler::finishPeriod( sample_rate_t sampleRate, fpp_t framesPerPeriod ) { - int periodElapsed = m_periodTimer.elapsed(); + // Time taken to process all data and fill the audio buffer. + const unsigned int periodElapsed = m_periodTimer.elapsed(); + // Maximum time the processing can take before causing buffer underflow. Convert to us. + const uint64_t timeLimit = static_cast(1000000) * framesPerPeriod / sampleRate; + + // Compute new overall CPU load and apply exponential averaging. + // The result is used for overload detection in AudioEngine::criticalXRuns() + // → the weight of a new sample must be high enough to allow relatively fast changes! + const auto newCpuLoad = 100.f * periodElapsed / timeLimit; + m_cpuLoad = newCpuLoad * 0.1f + m_cpuLoad * 0.9f; - const float newCpuLoad = periodElapsed / 10000.0f * sampleRate / framesPerPeriod; - m_cpuLoad = std::clamp((newCpuLoad * 0.1f + m_cpuLoad * 0.9f), 0, 100); + // Compute detailed load analysis. Can use stronger averaging to get more stable readout. + for (std::size_t i = 0; i < DetailCount; i++) + { + const auto newLoad = 100.f * m_detailTime[i] / timeLimit; + const auto oldLoad = m_detailLoad[i].load(std::memory_order_relaxed); + m_detailLoad[i].store(newLoad * 0.05f + oldLoad * 0.95f, std::memory_order_relaxed); + } if( m_outputFile.isOpen() ) { diff --git a/src/core/AutomationClip.cpp b/src/core/AutomationClip.cpp index 906cb148c82..3b36f6b49b7 100644 --- a/src/core/AutomationClip.cpp +++ b/src/core/AutomationClip.cpp @@ -1106,16 +1106,16 @@ void AutomationClip::generateTangents(timeMap::iterator it, int numToGenerate) { QMutexLocker m(&m_clipMutex); - if( m_timeMap.size() < 2 && numToGenerate > 0 ) + for (int i = 0; i < numToGenerate && it != m_timeMap.end(); ++i, ++it) { - it.value().setInTangent(0); - it.value().setOutTangent(0); - return; - } - - for( int i = 0; i < numToGenerate; i++ ) - { - if( it == m_timeMap.begin() ) + if (it + 1 == m_timeMap.end()) + { + // Previously, the last value's tangent was always set to 0. That logic was kept for both tangents + // of the last node + it.value().setInTangent(0); + it.value().setOutTangent(0); + } + else if (it == m_timeMap.begin()) { // On the first node there's no curve behind it, so we will only calculate the outTangent // and inTangent will be set to 0. @@ -1123,14 +1123,6 @@ void AutomationClip::generateTangents(timeMap::iterator it, int numToGenerate) it.value().setInTangent(0); it.value().setOutTangent(tangent); } - else if( it+1 == m_timeMap.end() ) - { - // Previously, the last value's tangent was always set to 0. That logic was kept for both tangents - // of the last node - it.value().setInTangent(0); - it.value().setOutTangent(0); - return; - } else { // When we are in a node that is in the middle of two other nodes, we need to check if we @@ -1159,7 +1151,6 @@ void AutomationClip::generateTangents(timeMap::iterator it, int numToGenerate) it.value().setOutTangent(outTangent); } } - it++; } } diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 319882af2f9..1155f5e0d22 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 6ad2f8526d8..8d0a8dca43f 100644 --- a/src/core/DataFile.cpp +++ b/src/core/DataFile.cpp @@ -79,6 +79,7 @@ const std::vector DataFile::UPGRADE_METHODS = { &DataFile::upgrade_automationNodes , &DataFile::upgrade_extendedNoteRange, &DataFile::upgrade_defaultTripleOscillatorHQ, &DataFile::upgrade_mixerRename , &DataFile::upgrade_bbTcoRename, + &DataFile::upgrade_sampleAndHold , &DataFile::upgrade_midiCCIndexing }; // Vector of all versions that have upgrade routines. @@ -1671,6 +1672,8 @@ void DataFile::upgrade_extendedNoteRange() { auto root = documentElement(); UpgradeExtendedNoteRange upgradeExtendedNoteRange(root); + + upgradeExtendedNoteRange.upgrade(); } @@ -1701,26 +1704,56 @@ void DataFile::upgrade_mixerRename() { // Change nodename to QDomNodeList fxmixer = elementsByTagName("fxmixer"); - for (int i = 0; !fxmixer.item(i).isNull(); ++i) + for (int i = 0; i < fxmixer.length(); ++i) { - fxmixer.item(i).toElement().setTagName("mixer"); + auto item = fxmixer.item(i).toElement(); + if (item.isNull()) + { + continue; + } + item.setTagName("mixer"); } // Change nodename to QDomNodeList fxchannel = elementsByTagName("fxchannel"); - for (int i = 0; !fxchannel.item(i).isNull(); ++i) + for (int i = 0; i < fxchannel.length(); ++i) { - fxchannel.item(i).toElement().setTagName("mixerchannel"); + auto item = fxchannel.item(i).toElement(); + if (item.isNull()) + { + continue; + } + item.setTagName("mixerchannel"); } // Change the attribute fxch of element to mixch QDomNodeList fxch = elementsByTagName("instrumenttrack"); - for(int i = 0; !fxch.item(i).isNull(); ++i) + for (int i = 0; i < fxch.length(); ++i) + { + auto item = fxch.item(i).toElement(); + if (item.isNull()) + { + continue; + } + if (item.hasAttribute("fxch")) + { + item.setAttribute("mixch", item.attribute("fxch")); + item.removeAttribute("fxch"); + } + } + // Change the attribute fxch of element to mixch + fxch = elementsByTagName("sampletrack"); + for (int i = 0; i < fxch.length(); ++i) { - if(fxch.item(i).toElement().hasAttribute("fxch")) + auto item = fxch.item(i).toElement(); + if (item.isNull()) + { + continue; + } + if (item.hasAttribute("fxch")) { - fxch.item(i).toElement().setAttribute("mixch", fxch.item(i).toElement().attribute("fxch")); - fxch.item(i).toElement().removeAttribute("fxch"); + item.setAttribute("mixch", item.attribute("fxch")); + item.removeAttribute("fxch"); } } } @@ -1760,6 +1793,44 @@ void DataFile::upgrade_bbTcoRename() } +// Set LFO speed to 0.01 on projects made before sample-and-hold PR +void DataFile::upgrade_sampleAndHold() +{ + QDomNodeList elements = elementsByTagName("lfocontroller"); + for (int i = 0; i < elements.length(); ++i) + { + if (elements.item(i).isNull()) { continue; } + auto e = elements.item(i).toElement(); + // Correct old random wave LFO speeds + if (e.attribute("wave").toInt() == 6) + { + e.setAttribute("speed",0.01f); + } + } +} + +//! Update MIDI CC indexes, so that they are counted from 0. Older releases of LMMS +//! count the CCs from 1. +void DataFile::upgrade_midiCCIndexing() +{ + static constexpr std::array attributesToUpdate{"inputcontroller", "outputcontroller"}; + + QDomNodeList elements = elementsByTagName("Midicontroller"); + for(int i = 0; i < elements.length(); i++) + { + if (elements.item(i).isNull()) { continue; } + auto element = elements.item(i).toElement(); + for (const char* attrName : attributesToUpdate) + { + if (element.hasAttribute(attrName)) + { + int cc = element.attribute(attrName).toInt(); + element.setAttribute(attrName, cc - 1); + } + } + } +} + void DataFile::upgrade() { // Runs all necessary upgrade methods diff --git a/src/core/EffectChain.cpp b/src/core/EffectChain.cpp index b07a7227bd9..4da5c519787 100644 --- a/src/core/EffectChain.cpp +++ b/src/core/EffectChain.cpp @@ -25,6 +25,7 @@ #include +#include #include "EffectChain.h" #include "Effect.h" @@ -162,6 +163,7 @@ void EffectChain::moveDown( Effect * _effect ) if (_effect != m_effects.back()) { auto it = std::find(m_effects.begin(), m_effects.end(), _effect); + assert(it != m_effects.end()); std::swap(*std::next(it), *it); } } @@ -174,6 +176,7 @@ void EffectChain::moveUp( Effect * _effect ) if (_effect != m_effects.front()) { auto it = std::find(m_effects.begin(), m_effects.end(), _effect); + assert(it != m_effects.end()); std::swap(*std::prev(it), *it); } } diff --git a/src/core/InstrumentPlayHandle.cpp b/src/core/InstrumentPlayHandle.cpp index e1a9d9d65fd..097719ad83d 100644 --- a/src/core/InstrumentPlayHandle.cpp +++ b/src/core/InstrumentPlayHandle.cpp @@ -24,18 +24,57 @@ #include "InstrumentPlayHandle.h" +#include "Instrument.h" #include "InstrumentTrack.h" +#include "Engine.h" +#include "AudioEngine.h" namespace lmms { -InstrumentPlayHandle::InstrumentPlayHandle( Instrument * instrument, InstrumentTrack* instrumentTrack ) : - PlayHandle( Type::InstrumentPlayHandle ), - m_instrument( instrument ) +InstrumentPlayHandle::InstrumentPlayHandle(Instrument * instrument, InstrumentTrack* instrumentTrack) : + PlayHandle(Type::InstrumentPlayHandle), + m_instrument(instrument) { - setAudioPort( instrumentTrack->audioPort() ); + setAudioPort(instrumentTrack->audioPort()); +} + +void InstrumentPlayHandle::play(sampleFrame * working_buffer) +{ + InstrumentTrack * instrumentTrack = m_instrument->instrumentTrack(); + + // ensure that all our nph's have been processed first + auto nphv = NotePlayHandle::nphsOfInstrumentTrack(instrumentTrack, true); + + bool nphsLeft; + do + { + nphsLeft = false; + for (const NotePlayHandle * constNotePlayHandle : nphv) + { + if (constNotePlayHandle->state() != ThreadableJob::ProcessingState::Done && + !constNotePlayHandle->isFinished()) + { + nphsLeft = true; + NotePlayHandle * notePlayHandle = const_cast(constNotePlayHandle); + notePlayHandle->process(); + } + } + } + while (nphsLeft); + + m_instrument->play(working_buffer); + + // Process the audio buffer that the instrument has just worked on... + const fpp_t frames = Engine::audioEngine()->framesPerPeriod(); + instrumentTrack->processAudioBuffer(working_buffer, frames, nullptr); +} + +bool InstrumentPlayHandle::isFromTrack(const Track* track) const +{ + return m_instrument->isFromTrack(track); } -} // namespace lmms \ No newline at end of file +} // namespace lmms diff --git a/src/core/LfoController.cpp b/src/core/LfoController.cpp index 23621b84727..88f64803c2b 100644 --- a/src/core/LfoController.cpp +++ b/src/core/LfoController.cpp @@ -88,6 +88,7 @@ void LfoController::updateValueBuffer() { m_phaseOffset = m_phaseModel.value() / 360.0; float phase = m_currentPhase + m_phaseOffset; + float phasePrev = 0.0f; // roll phase up until we're in sync with period counter m_bufferLastUpdated++; @@ -102,20 +103,45 @@ void LfoController::updateValueBuffer() ValueBuffer *amountBuffer = m_amountModel.valueBuffer(); int amountInc = amountBuffer ? 1 : 0; float *amountPtr = amountBuffer ? &(amountBuffer->values()[ 0 ] ) : &amount; + Oscillator::WaveShape waveshape = static_cast(m_waveModel.value()); for( float& f : m_valueBuffer ) { - const float currentSample = m_sampleFunction != nullptr - ? m_sampleFunction( phase ) - : m_userDefSampleBuffer->userWaveSample( phase ); + float currentSample = 0; + switch (waveshape) + { + case Oscillator::WaveShape::WhiteNoise: + { + if (absFraction(phase) < absFraction(phasePrev)) + { + // Resample when phase period has completed + m_heldSample = m_sampleFunction(phase); + } + currentSample = m_heldSample; + break; + } + case Oscillator::WaveShape::UserDefined: + { + currentSample = m_userDefSampleBuffer->userWaveSample(phase); + break; + } + default: + { + if (m_sampleFunction != nullptr) + { + currentSample = m_sampleFunction(phase); + } + } + } f = std::clamp(m_baseModel.value() + (*amountPtr * currentSample / 2.0f), 0.0f, 1.0f); + phasePrev = phase; phase += 1.0 / m_duration; amountPtr += amountInc; } - m_currentPhase = absFraction( phase - m_phaseOffset ); + m_currentPhase = absFraction(phase - m_phaseOffset); m_bufferLastUpdated = s_periods; } diff --git a/src/core/LmmsSemaphore.cpp b/src/core/LmmsSemaphore.cpp new file mode 100644 index 00000000000..daa70a45ba3 --- /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/Mixer.cpp b/src/core/Mixer.cpp index e14660e1fa3..59c2dd72e16 100644 --- a/src/core/Mixer.cpp +++ b/src/core/Mixer.cpp @@ -394,8 +394,8 @@ void Mixer::moveChannelLeft( int index ) else if (m_lastSoloed == b) { m_lastSoloed = a; } // go through every instrument and adjust for the channel index change - TrackContainer::TrackList songTrackList = Engine::getSong()->tracks(); - TrackContainer::TrackList patternTrackList = Engine::patternStore()->tracks(); + const TrackContainer::TrackList& songTrackList = Engine::getSong()->tracks(); + const TrackContainer::TrackList& patternTrackList = Engine::patternStore()->tracks(); for (const auto& trackList : {songTrackList, patternTrackList}) { diff --git a/src/core/NotePlayHandle.cpp b/src/core/NotePlayHandle.cpp index 70007ebf187..eb9c7ddbff4 100644 --- a/src/core/NotePlayHandle.cpp +++ b/src/core/NotePlayHandle.cpp @@ -557,14 +557,20 @@ void NotePlayHandle::updateFrequency() -void NotePlayHandle::processTimePos( const TimePos& time ) +void NotePlayHandle::processTimePos(const TimePos& time, float pitchValue, bool isRecording) { - if( detuning() && time >= songGlobalParentOffset()+pos() ) + if (!detuning() || time < songGlobalParentOffset() + pos()) { return; } + + if (isRecording && m_origin == Origin::MidiInput) + { + detuning()->automationClip()->recordValue(time - songGlobalParentOffset() - pos(), pitchValue / 100); + } + else { - const float v = detuning()->automationClip()->valueAt( time - songGlobalParentOffset() - pos() ); - if( !typeInfo::isEqual( v, m_baseDetuning->value() ) ) + const float v = detuning()->automationClip()->valueAt(time - songGlobalParentOffset() - pos()); + if (!typeInfo::isEqual(v, m_baseDetuning->value())) { - m_baseDetuning->setValue( v ); + m_baseDetuning->setValue(v); updateFrequency(); } } diff --git a/src/core/PatternStore.cpp b/src/core/PatternStore.cpp index c5a3521398a..6af434f65b3 100644 --- a/src/core/PatternStore.cpp +++ b/src/core/PatternStore.cpp @@ -61,7 +61,7 @@ bool PatternStore::play(TimePos start, fpp_t frames, f_cnt_t offset, int clipNum start = start % (lengthOfPattern(clipNum) * TimePos::ticksPerBar()); - TrackList tl = tracks(); + const TrackList& tl = tracks(); for (Track * t : tl) { if (t->play(start, frames, offset, clipNum)) @@ -117,7 +117,7 @@ int PatternStore::numOfPatterns() const void PatternStore::removePattern(int pattern) { - TrackList tl = tracks(); + const TrackList& tl = tracks(); for (Track * t : tl) { delete t->getClip(pattern); @@ -134,7 +134,7 @@ void PatternStore::removePattern(int pattern) void PatternStore::swapPattern(int pattern1, int pattern2) { - TrackList tl = tracks(); + const TrackList& tl = tracks(); for (Track * t : tl) { t->swapPositionOfClips(pattern1, pattern2); @@ -159,7 +159,7 @@ void PatternStore::updatePatternTrack(Clip* clip) void PatternStore::fixIncorrectPositions() { - TrackList tl = tracks(); + const TrackList& tl = tracks(); for (Track * t : tl) { for (int i = 0; i < numOfPatterns(); ++i) @@ -215,7 +215,7 @@ void PatternStore::updateComboBox() void PatternStore::currentPatternChanged() { // now update all track-labels (the current one has to become white, the others gray) - TrackList tl = Engine::getSong()->tracks(); + const TrackList& tl = Engine::getSong()->tracks(); for (Track * t : tl) { if (t->type() == Track::Type::Pattern) @@ -230,7 +230,7 @@ void PatternStore::currentPatternChanged() void PatternStore::createClipsForPattern(int pattern) { - TrackList tl = tracks(); + const TrackList& tl = tracks(); for (Track * t : tl) { t->createClipsForPattern(pattern); diff --git a/src/core/RenderManager.cpp b/src/core/RenderManager.cpp index 969cad15bed..9f619203903 100644 --- a/src/core/RenderManager.cpp +++ b/src/core/RenderManager.cpp @@ -97,7 +97,7 @@ void RenderManager::renderNextTrack() // Render the song into individual tracks void RenderManager::renderTracks() { - const TrackContainer::TrackList & tl = Engine::getSong()->tracks(); + const TrackContainer::TrackList& tl = Engine::getSong()->tracks(); // find all currently unnmuted tracks -- we want to render these. for (const auto& tk : tl) @@ -112,7 +112,7 @@ void RenderManager::renderTracks() } } - const TrackContainer::TrackList t2 = Engine::patternStore()->tracks(); + const TrackContainer::TrackList& t2 = Engine::patternStore()->tracks(); for (const auto& tk : t2) { Track::Type type = tk->type(); diff --git a/src/core/SampleBuffer.cpp b/src/core/SampleBuffer.cpp index 775db125b75..5e2d09c573e 100644 --- a/src/core/SampleBuffer.cpp +++ b/src/core/SampleBuffer.cpp @@ -247,7 +247,15 @@ void SampleBuffer::update(bool keepSettings) const int fileSizeMax = 300; // MB const int sampleLengthMax = 90; // Minutes - bool fileLoadError = false; + enum class FileLoadError + { + None, + ReadPermissionDenied, + TooLarge, + Invalid + }; + FileLoadError fileLoadError = FileLoadError::None; + if (m_audioFile.isEmpty() && m_origData != nullptr && m_origFrames > 0) { // TODO: reverse- and amplification-property is not covered @@ -271,31 +279,40 @@ void SampleBuffer::update(bool keepSettings) m_frames = 0; const QFileInfo fileInfo(file); - if (fileInfo.size() > fileSizeMax * 1024 * 1024) + if (!fileInfo.isReadable()) { - fileLoadError = true; + fileLoadError = FileLoadError::ReadPermissionDenied; + } + else if (fileInfo.size() > fileSizeMax * 1024 * 1024) + { + fileLoadError = FileLoadError::TooLarge; } else { // Use QFile to handle unicode file names on Windows QFile f(file); - SNDFILE * sndFile; + SNDFILE * sndFile = nullptr; SF_INFO sfInfo; sfInfo.format = 0; + if (f.open(QIODevice::ReadOnly) && (sndFile = sf_open_fd(f.handle(), SFM_READ, &sfInfo, false))) { f_cnt_t frames = sfInfo.frames; int rate = sfInfo.samplerate; if (frames / rate > sampleLengthMax * 60) { - fileLoadError = true; + fileLoadError = FileLoadError::TooLarge; } sf_close(sndFile); } + else + { + fileLoadError = FileLoadError::Invalid; + } f.close(); } - if (!fileLoadError) + if (fileLoadError == FileLoadError::None) { #ifdef LMMS_HAVE_OGGVORBIS // workaround for a bug in libsndfile or our libsndfile decoder @@ -322,7 +339,7 @@ void SampleBuffer::update(bool keepSettings) } } - if (m_frames == 0 || fileLoadError) // if still no frames, bail + if (m_frames == 0 || fileLoadError != FileLoadError::None) // if still no frames, bail { // sample couldn't be decoded, create buffer containing // one sample-frame @@ -363,16 +380,35 @@ void SampleBuffer::update(bool keepSettings) } Oscillator::generateAntiAliasUserWaveTable(this); - if (fileLoadError) + if (fileLoadError != FileLoadError::None) { QString title = tr("Fail to open file"); - QString message = tr("Audio files are limited to %1 MB " - "in size and %2 minutes of playing time" - ).arg(fileSizeMax).arg(sampleLengthMax); + QString message; + + switch (fileLoadError) + { + case FileLoadError::None: + // present just to avoid a compiler warning + break; + + case FileLoadError::ReadPermissionDenied: + message = tr("Read permission denied"); + break; + + case FileLoadError::TooLarge: + message = tr("Audio files are limited to %1 MB " + "in size and %2 minutes of playing time" + ).arg(fileSizeMax).arg(sampleLengthMax); + break; + + case FileLoadError::Invalid: + message = tr("Invalid audio file"); + break; + } + if (gui::getGUI() != nullptr) { - QMessageBox::information(nullptr, - title, message, QMessageBox::Ok); + QMessageBox::information(nullptr, title, message, QMessageBox::Ok); } else { diff --git a/src/core/SampleClip.cpp b/src/core/SampleClip.cpp index 592a6382741..b09d7b3bb2b 100644 --- a/src/core/SampleClip.cpp +++ b/src/core/SampleClip.cpp @@ -143,23 +143,26 @@ void SampleClip::setSampleBuffer( SampleBuffer* sb ) -void SampleClip::setSampleFile( const QString & _sf ) +void SampleClip::setSampleFile(const QString & sf) { - int length; - if ( _sf.isEmpty() ) - { //When creating an empty sample clip make it a bar long + int length = 0; + + if (!sf.isEmpty()) + { + m_sampleBuffer->setAudioFile(sf); + length = sampleLength(); + } + + if (length == 0) + { + //If there is no sample, make the clip a bar long float nom = Engine::getSong()->getTimeSigModel().getNumerator(); float den = Engine::getSong()->getTimeSigModel().getDenominator(); - length = DefaultTicksPerBar * ( nom / den ); + length = DefaultTicksPerBar * (nom / den); } - else - { //Otherwise set it to the sample's length - m_sampleBuffer->setAudioFile( _sf ); - length = sampleLength(); - } - changeLength(length); - setStartTimeOffset( 0 ); + changeLength(length); + setStartTimeOffset(0); emit sampleChanged(); emit playbackPositionChanged(); diff --git a/src/core/Song.cpp b/src/core/Song.cpp index e8073f225f9..3a735331c64 100644 --- a/src/core/Song.cpp +++ b/src/core/Song.cpp @@ -383,7 +383,7 @@ void Song::processAutomations(const TrackList &tracklist, TimePos timeStart, fpp } values = container->automatedValuesAt(timeStart, clipNum); - TrackList tracks = container->tracks(); + const TrackList& tracks = container->tracks(); Track::clipVector clips; for (Track* track : tracks) diff --git a/src/core/UpgradeExtendedNoteRange.cpp b/src/core/UpgradeExtendedNoteRange.cpp index e61da3723c4..6ed98e63e9e 100644 --- a/src/core/UpgradeExtendedNoteRange.cpp +++ b/src/core/UpgradeExtendedNoteRange.cpp @@ -193,6 +193,7 @@ static void fixTrack(QDomElement & track, std::set & automatedBase for (int i = 0; i < subTracks.size(); ++i) { QDomElement subTrack = subTracks.item(i).toElement(); + assert (static_cast(subTrack.attribute("type").toInt()) != Track::Type::Pattern); fixTrack(subTrack, automatedBaseNoteIds); } } diff --git a/src/core/lv2/Lv2Features.cpp b/src/core/lv2/Lv2Features.cpp index 6e74a89360c..c8fc0546517 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 9c62703e0a4..6a1b2a8af20 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 e0541b948c9..11290013e5b 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); + + { + 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) { - 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))); + 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 00000000000..5af955ff766 --- /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/core/midi/MidiController.cpp b/src/core/midi/MidiController.cpp index d7c89e940c2..0ae76d352c8 100644 --- a/src/core/midi/MidiController.cpp +++ b/src/core/midi/MidiController.cpp @@ -72,21 +72,20 @@ void MidiController::updateName() -void MidiController::processInEvent( const MidiEvent& event, const TimePos& time, f_cnt_t offset ) +void MidiController::processInEvent(const MidiEvent& event, const TimePos& time, f_cnt_t offset) { unsigned char controllerNum; - switch( event.type() ) + switch(event.type()) { case MidiControlChange: controllerNum = event.controllerNumber(); - if( m_midiPort.inputController() == controllerNum + 1 && - ( m_midiPort.inputChannel() == event.channel() + 1 || - m_midiPort.inputChannel() == 0 ) ) + if (m_midiPort.inputController() == controllerNum && + (m_midiPort.inputChannel() == event.channel() + 1 || m_midiPort.inputChannel() == 0)) { unsigned char val = event.controllerValue(); m_previousValue = m_lastValue; - m_lastValue = (float)( val ) / 127.0f; + m_lastValue = static_cast(val) / 127.0f; emit valueChanged(); } break; diff --git a/src/core/midi/MidiPort.cpp b/src/core/midi/MidiPort.cpp index c7c947e8e4f..24263f91315 100644 --- a/src/core/midi/MidiPort.cpp +++ b/src/core/midi/MidiPort.cpp @@ -31,6 +31,7 @@ #include "MidiEventProcessor.h" #include "Note.h" #include "Song.h" +#include "MidiController.h" namespace lmms @@ -54,8 +55,8 @@ MidiPort::MidiPort( const QString& name, m_mode( mode ), m_inputChannelModel( 0, 0, MidiChannelCount, this, tr( "Input channel" ) ), m_outputChannelModel( 1, 0, MidiChannelCount, this, tr( "Output channel" ) ), - m_inputControllerModel( 0, 0, MidiControllerCount, this, tr( "Input controller" ) ), - m_outputControllerModel( 0, 0, MidiControllerCount, this, tr( "Output controller" ) ), + m_inputControllerModel(MidiController::NONE, MidiController::NONE, MidiControllerCount - 1, this, tr( "Input controller" )), + m_outputControllerModel(MidiController::NONE, MidiController::NONE, MidiControllerCount - 1, this, tr( "Output controller" )), m_fixedInputVelocityModel( -1, -1, MidiMaxVelocity, this, tr( "Fixed input velocity" ) ), m_fixedOutputVelocityModel( -1, -1, MidiMaxVelocity, this, tr( "Fixed output velocity" ) ), m_fixedOutputNoteModel( -1, -1, MidiMaxKey, this, tr( "Fixed output note" ) ), @@ -436,4 +437,4 @@ void MidiPort::invalidateCilent() } -} // namespace lmms \ No newline at end of file +} // namespace lmms diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt index 9f940c0354b..afed153f928 100644 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -61,7 +61,7 @@ SET(LMMS_SRCS gui/instrument/EnvelopeAndLfoView.cpp gui/instrument/InstrumentFunctionViews.cpp gui/instrument/InstrumentMidiIOView.cpp - gui/instrument/InstrumentMiscView.cpp + gui/instrument/InstrumentTuningView.cpp gui/instrument/InstrumentSoundShapingView.cpp gui/instrument/InstrumentTrackWindow.cpp gui/instrument/InstrumentView.cpp diff --git a/src/gui/FileBrowser.cpp b/src/gui/FileBrowser.cpp index c0763d542ae..181e67cd749 100644 --- a/src/gui/FileBrowser.cpp +++ b/src/gui/FileBrowser.cpp @@ -23,7 +23,6 @@ * */ - #include #include #include @@ -126,7 +125,7 @@ FileBrowser::FileBrowser(const QString & directories, const QString & filter, m_filterEdit->setPlaceholderText( tr("Search") ); m_filterEdit->setClearButtonEnabled( true ); connect( m_filterEdit, SIGNAL( textEdited( const QString& ) ), - this, SLOT( filterItems( const QString& ) ) ); + this, SLOT( filterAndExpandItems( const QString& ) ) ); auto reload_btn = new QPushButton(embed::getIconPixmap("reload"), QString(), searchWidget); reload_btn->setToolTip( tr( "Refresh list" ) ); @@ -145,53 +144,95 @@ FileBrowser::FileBrowser(const QString & directories, const QString & filter, auto filterFocusShortcut = new QShortcut(QKeySequence(QKeySequence::Find), this, SLOT(giveFocusToFilter())); filterFocusShortcut->setContext(Qt::WidgetWithChildrenShortcut); + m_previousFilterValue = ""; + reloadTree(); show(); } -bool FileBrowser::filterItems( const QString & filter, QTreeWidgetItem * item ) +void FileBrowser::saveDirectoriesStates() +{ + m_savedExpandedDirs = m_fileBrowserTreeWidget->expandedDirs(); +} + +void FileBrowser::restoreDirectoriesStates() +{ + expandItems(nullptr, m_savedExpandedDirs); +} + +bool FileBrowser::filterAndExpandItems(const QString & filter, QTreeWidgetItem * item) { - // call with item=NULL to filter the entire tree + // Call with item = nullptr to filter the entire tree + + if (item == nullptr) + { + // First search character so need to save current expanded directories + if (m_previousFilterValue.isEmpty()) + { + saveDirectoriesStates(); + } + + m_previousFilterValue = filter; + } + + if (filter.isEmpty()) + { + // Restore previous expanded directories + if (item == nullptr) + { + restoreDirectoriesStates(); + } + + return false; + } + bool anyMatched = false; int numChildren = item ? item->childCount() : m_fileBrowserTreeWidget->topLevelItemCount(); - for( int i = 0; i < numChildren; ++i ) + + for (int i = 0; i < numChildren; ++i) { QTreeWidgetItem * it = item ? item->child( i ) : m_fileBrowserTreeWidget->topLevelItem(i); - // is directory? - if( it->childCount() ) + auto d = dynamic_cast(it); + if (d) { - // matches filter? - if( it->text( 0 ). - contains( filter, Qt::CaseInsensitive ) ) + if (it->text(0).contains(filter, Qt::CaseInsensitive)) { - // yes, then show everything below - it->setHidden( false ); - filterItems( QString(), it ); + it->setHidden(false); + it->setExpanded(true); + filterAndExpandItems(QString(), it); anyMatched = true; } else { - // only show if item below matches filter - bool didMatch = filterItems( filter, it ); - it->setHidden( !didMatch ); + // Expanding is required when recursive to load in its contents, even if it's collapsed right afterward + it->setExpanded(true); + + bool didMatch = filterAndExpandItems(filter, it); + it->setHidden(!didMatch); + it->setExpanded(didMatch); anyMatched = anyMatched || didMatch; } } - // a standard item (i.e. no file or directory item?) - else if( it->type() == QTreeWidgetItem::Type ) - { - // hide if there's any filter - it->setHidden( !filter.isEmpty() ); - } + else { - // file matches filter? - bool didMatch = it->text( 0 ). - contains( filter, Qt::CaseInsensitive ); - it->setHidden( !didMatch ); - anyMatched = anyMatched || didMatch; + auto f = dynamic_cast(it); + if (f) + { + // File + bool didMatch = it->text(0).contains(filter, Qt::CaseInsensitive); + it->setHidden(!didMatch); + anyMatched = anyMatched || didMatch; + } + + // A standard item (i.e. no file or directory item?) + else + { + // Hide if there's any filter + it->setHidden(!filter.isEmpty()); + } } } @@ -201,15 +242,20 @@ bool FileBrowser::filterItems( const QString & filter, QTreeWidgetItem * item ) void FileBrowser::reloadTree() { - QList expandedDirs = m_fileBrowserTreeWidget->expandedDirs(); - const QString text = m_filterEdit->text(); - m_filterEdit->clear(); + if (m_filterEdit->text().isEmpty()) + { + saveDirectoriesStates(); + } + m_fileBrowserTreeWidget->clear(); + QStringList paths = m_directories.split('*'); + if (m_showUserContent && !m_showUserContent->isChecked()) { paths.removeAll(m_userDir); } + if (m_showFactoryContent && !m_showFactoryContent->isChecked()) { paths.removeAll(m_factoryDir); @@ -222,9 +268,15 @@ void FileBrowser::reloadTree() addItems(path); } } - expandItems(nullptr, expandedDirs); - m_filterEdit->setText( text ); - filterItems( text ); + + if (m_filterEdit->text().isEmpty()) + { + restoreDirectoriesStates(); + } + else + { + filterAndExpandItems(m_filterEdit->text()); + } } @@ -240,12 +292,16 @@ void FileBrowser::expandItems(QTreeWidgetItem* item, QList expandedDirs { // Expanding is required when recursive to load in its contents, even if it's collapsed right afterward if (m_recurse) { d->setExpanded(true); } + d->setExpanded(expandedDirs.contains(d->fullName())); + if (m_recurse && it->childCount()) { expandItems(it, expandedDirs); } } + + it->setHidden(false); } } @@ -416,8 +472,6 @@ QList FileBrowserTreeWidget::expandedDirs( QTreeWidgetItem * item ) con } - - void FileBrowserTreeWidget::keyPressEvent(QKeyEvent * ke ) { // Shorter names for some commonly used properties of the event diff --git a/src/gui/Lv2ViewBase.cpp b/src/gui/Lv2ViewBase.cpp index 3fd1d44b109..830a994c8c6 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/MainWindow.cpp b/src/gui/MainWindow.cpp index 559756169c6..10805fe01c4 100644 --- a/src/gui/MainWindow.cpp +++ b/src/gui/MainWindow.cpp @@ -363,10 +363,12 @@ void MainWindow::finalize() } edit_menu->addSeparator(); - edit_menu->addAction( embed::getIconPixmap( "setup_general" ), - tr( "Settings" ), - this, SLOT(showSettingsDialog())); - connect( edit_menu, SIGNAL(aboutToShow()), this, SLOT(updateUndoRedoButtons())); + edit_menu->addAction(embed::getIconPixmap("microtuner"), tr("Scales and keymaps"), + this, SLOT(toggleMicrotunerWin())); + edit_menu->addAction(embed::getIconPixmap("setup_general"), tr("Settings"), + this, SLOT(showSettingsDialog())); + + connect(edit_menu, SIGNAL(aboutToShow()), this, SLOT(updateUndoRedoButtons())); m_viewMenu = new QMenu( this ); menuBar()->addMenu( m_viewMenu )->setText( tr( "&View" ) ); @@ -485,10 +487,6 @@ void MainWindow::finalize() tr("Show/hide project notes") + " (Ctrl+7)", this, SLOT(toggleProjectNotesWin()), m_toolBar); project_notes_window->setShortcut( Qt::CTRL + Qt::Key_7 ); - auto microtuner_window = new ToolButton(embed::getIconPixmap("microtuner"), - tr("Microtuner configuration") + " (Ctrl+8)", this, SLOT(toggleMicrotunerWin()), m_toolBar); - microtuner_window->setShortcut( Qt::CTRL + Qt::Key_8 ); - m_toolBarLayout->addWidget( song_editor_window, 1, 1 ); m_toolBarLayout->addWidget( pattern_editor_window, 1, 2 ); m_toolBarLayout->addWidget( piano_roll_window, 1, 3 ); @@ -496,7 +494,6 @@ void MainWindow::finalize() m_toolBarLayout->addWidget( mixer_window, 1, 5 ); m_toolBarLayout->addWidget( controllers_window, 1, 6 ); m_toolBarLayout->addWidget( project_notes_window, 1, 7 ); - m_toolBarLayout->addWidget( microtuner_window, 1, 8 ); m_toolBarLayout->setColumnStretch( 100, 1 ); // setup-dialog opened before? @@ -1100,10 +1097,6 @@ void MainWindow::updateViewMenu() tr( "Project Notes" ) + "\tCtrl+7", this, SLOT(toggleProjectNotesWin()) ); - m_viewMenu->addAction(embed::getIconPixmap( "microtuner" ), - tr( "Microtuner" ) + "\tCtrl+8", - this, SLOT(toggleMicrotunerWin()) - ); m_viewMenu->addSeparator(); diff --git a/src/gui/MicrotunerConfig.cpp b/src/gui/MicrotunerConfig.cpp index 7ab4cc0b1ac..4156b9e79df 100644 --- a/src/gui/MicrotunerConfig.cpp +++ b/src/gui/MicrotunerConfig.cpp @@ -56,8 +56,8 @@ namespace lmms::gui MicrotunerConfig::MicrotunerConfig() : QWidget(), - m_scaleComboModel(nullptr, tr("Selected scale")), - m_keymapComboModel(nullptr, tr("Selected keymap")), + m_scaleComboModel(nullptr, tr("Selected scale slot")), + m_keymapComboModel(nullptr, tr("Selected keymap slot")), m_firstKeyModel(0, 0, NumKeys - 1, nullptr, tr("First key")), m_lastKeyModel(NumKeys - 1, 0, NumKeys - 1, nullptr, tr("Last key")), m_middleKeyModel(DefaultMiddleKey, 0, NumKeys - 1, nullptr, tr("Middle key")), @@ -75,7 +75,7 @@ MicrotunerConfig::MicrotunerConfig() : #endif setWindowIcon(embed::getIconPixmap("microtuner")); - setWindowTitle(tr("Microtuner")); + setWindowTitle(tr("Microtuner Configuration")); // Organize into 2 main columns: scales and keymaps auto microtunerLayout = new QGridLayout(); @@ -84,7 +84,7 @@ MicrotunerConfig::MicrotunerConfig() : // ---------------------------------- // Scale sub-column // - auto scaleLabel = new QLabel(tr("Scale:")); + auto scaleLabel = new QLabel(tr("Scale slot to edit:")); microtunerLayout->addWidget(scaleLabel, 0, 0, 1, 2, Qt::AlignBottom); for (unsigned int i = 0; i < MaxScaleCount; i++) @@ -102,6 +102,8 @@ MicrotunerConfig::MicrotunerConfig() : auto loadScaleButton = new QPushButton(tr("Load")); auto saveScaleButton = new QPushButton(tr("Save")); + loadScaleButton->setToolTip(tr("Load scale definition from a file.")); + saveScaleButton->setToolTip(tr("Save scale definition to a file.")); microtunerLayout->addWidget(loadScaleButton, 3, 0, 1, 1); microtunerLayout->addWidget(saveScaleButton, 3, 1, 1, 1); connect(loadScaleButton, &QPushButton::clicked, [=] {loadScaleFromFile();}); @@ -112,14 +114,15 @@ MicrotunerConfig::MicrotunerConfig() : m_scaleTextEdit->setToolTip(tr("Enter intervals on separate lines. Numbers containing a decimal point are treated as cents.\nOther inputs are treated as integer ratios and must be in the form of \'a/b\' or \'a\'.\nUnity (0.0 cents or ratio 1/1) is always present as a hidden first value; do not enter it manually.")); microtunerLayout->addWidget(m_scaleTextEdit, 4, 0, 2, 2); - auto applyScaleButton = new QPushButton(tr("Apply scale")); + auto applyScaleButton = new QPushButton(tr("Apply scale changes")); + applyScaleButton->setToolTip(tr("Verify and apply changes made to the selected scale. To use the scale, select it in the settings of a supported instrument.")); microtunerLayout->addWidget(applyScaleButton, 6, 0, 1, 2); connect(applyScaleButton, &QPushButton::clicked, [=] {applyScale();}); // ---------------------------------- // Mapping sub-column // - auto keymapLabel = new QLabel(tr("Keymap:")); + auto keymapLabel = new QLabel(tr("Keymap slot to edit:")); microtunerLayout->addWidget(keymapLabel, 0, 2, 1, 2, Qt::AlignBottom); for (unsigned int i = 0; i < MaxKeymapCount; i++) @@ -137,6 +140,8 @@ MicrotunerConfig::MicrotunerConfig() : auto loadKeymapButton = new QPushButton(tr("Load")); auto saveKeymapButton = new QPushButton(tr("Save")); + loadKeymapButton->setToolTip(tr("Load key mapping definition from a file.")); + saveKeymapButton->setToolTip(tr("Save key mapping definition to a file.")); microtunerLayout->addWidget(loadKeymapButton, 3, 2, 1, 1); microtunerLayout->addWidget(saveKeymapButton, 3, 3, 1, 1); connect(loadKeymapButton, &QPushButton::clicked, [=] {loadKeymapFromFile();}); @@ -181,7 +186,8 @@ MicrotunerConfig::MicrotunerConfig() : baseFreqSpin->setToolTip(tr("Base note frequency")); keymapRangeLayout->addWidget(baseFreqSpin, 1, 1, 1, 2); - auto applyKeymapButton = new QPushButton(tr("Apply keymap")); + auto applyKeymapButton = new QPushButton(tr("Apply keymap changes")); + applyKeymapButton->setToolTip(tr("Verify and apply changes made to the selected key mapping. To use the mapping, select it in the settings of a supported instrument.")); microtunerLayout->addWidget(applyKeymapButton, 6, 2, 1, 2); connect(applyKeymapButton, &QPushButton::clicked, [=] {applyKeymap();}); diff --git a/src/gui/MixerView.cpp b/src/gui/MixerView.cpp index 0edebcb8a94..dff19ca3eb7 100644 --- a/src/gui/MixerView.cpp +++ b/src/gui/MixerView.cpp @@ -248,8 +248,8 @@ void MixerView::refreshDisplay() // update the and max. channel number for every instrument void MixerView::updateMaxChannelSelector() { - TrackContainer::TrackList songTracks = Engine::getSong()->tracks(); - TrackContainer::TrackList patternStoreTracks = Engine::patternStore()->tracks(); + const TrackContainer::TrackList& songTracks = Engine::getSong()->tracks(); + const TrackContainer::TrackList& patternStoreTracks = Engine::patternStore()->tracks(); for (const auto& trackList : {songTracks, patternStoreTracks}) { diff --git a/src/gui/SampleTrackWindow.cpp b/src/gui/SampleTrackWindow.cpp index 68b5eb8a223..f6d7f9ea1d9 100644 --- a/src/gui/SampleTrackWindow.cpp +++ b/src/gui/SampleTrackWindow.cpp @@ -84,7 +84,6 @@ SampleTrackWindow::SampleTrackWindow(SampleTrackView * tv) : // setup line edit for changing sample track name m_nameLineEdit = new QLineEdit; - m_nameLineEdit->setFont(pointSize<9>(m_nameLineEdit->font())); connect(m_nameLineEdit, SIGNAL(textChanged(const QString&)), this, SLOT(textChanged(const QString&))); diff --git a/src/gui/SideBarWidget.cpp b/src/gui/SideBarWidget.cpp index 60760ba5904..c218bedd335 100644 --- a/src/gui/SideBarWidget.cpp +++ b/src/gui/SideBarWidget.cpp @@ -62,16 +62,16 @@ void SideBarWidget::paintEvent( QPaintEvent * ) QFont f = p.font(); f.setBold( true ); - f.setUnderline( true ); + f.setUnderline(false); f.setPointSize( f.pointSize() + 2 ); p.setFont( f ); p.setPen( palette().highlightedText().color() ); - const int tx = m_icon.width()+4; + const int tx = m_icon.width() + 8; QFontMetrics metrics( f ); - const int ty = metrics.ascent(); + const int ty = (metrics.ascent() + m_icon.height()) / 2; p.drawText( tx, ty, m_title ); p.drawPixmap( 2, 2, m_icon.transformed( QTransform().rotate( -90 ) ) ); diff --git a/src/gui/clips/ClipView.cpp b/src/gui/clips/ClipView.cpp index 7a7a19c1119..de7690d26e1 100644 --- a/src/gui/clips/ClipView.cpp +++ b/src/gui/clips/ClipView.cpp @@ -25,6 +25,7 @@ #include "ClipView.h" #include +#include #include #include @@ -545,6 +546,7 @@ DataFile ClipView::createClipDataFiles( // Insert into the dom under the "clips" element Track* clipTrack = clipView->m_trackView->getTrack(); int trackIndex = std::distance(tc->tracks().begin(), std::find(tc->tracks().begin(), tc->tracks().end(), clipTrack)); + assert(trackIndex != tc->tracks().size()); QDomElement clipElement = dataFile.createElement("clip"); clipElement.setAttribute( "trackIndex", trackIndex ); clipElement.setAttribute( "trackType", static_cast(clipTrack->type()) ); @@ -1308,7 +1310,7 @@ void ClipView::mergeClips(QVector clipvs) continue; } - NoteVector currentClipNotes = mcView->getMidiClip()->notes(); + const NoteVector& currentClipNotes = mcView->getMidiClip()->notes(); TimePos mcViewPos = mcView->getMidiClip()->startPosition(); for (Note* note: currentClipNotes) diff --git a/src/gui/clips/MidiClipView.cpp b/src/gui/clips/MidiClipView.cpp index 79c4cd73d68..151df8d3c3c 100644 --- a/src/gui/clips/MidiClipView.cpp +++ b/src/gui/clips/MidiClipView.cpp @@ -523,7 +523,8 @@ void MidiClipView::paintEvent( QPaintEvent * ) p.scale(width(), height() - distanceToTop - 2 * notesBorder); // set colour based on mute status - QColor noteFillColor = muted ? getMutedNoteFillColor() : getNoteFillColor(); + QColor noteFillColor = muted ? getMutedNoteFillColor().lighter(200) + : (c.lightness() > 175 ? getNoteFillColor().darker(400) : getNoteFillColor()); QColor noteBorderColor = muted ? getMutedNoteBorderColor() : ( m_clip->hasColor() ? c.lighter( 200 ) : getNoteBorderColor() ); diff --git a/src/gui/editors/AutomationEditor.cpp b/src/gui/editors/AutomationEditor.cpp index a24165332d0..f98066bbaa2 100644 --- a/src/gui/editors/AutomationEditor.cpp +++ b/src/gui/editors/AutomationEditor.cpp @@ -531,7 +531,7 @@ void AutomationEditor::mousePressEvent( QMouseEvent* mouseEvent ) : POS(clickedNode) ), level, - true, + clickedNode == tm.end(), mouseEvent->modifiers() & Qt::ControlModifier ); diff --git a/src/gui/editors/PatternEditor.cpp b/src/gui/editors/PatternEditor.cpp index 229c90bc234..5237690a7e7 100644 --- a/src/gui/editors/PatternEditor.cpp +++ b/src/gui/editors/PatternEditor.cpp @@ -69,7 +69,7 @@ void PatternEditor::cloneSteps() void PatternEditor::removeSteps() { - TrackContainer::TrackList tl = model()->tracks(); + const TrackContainer::TrackList& tl = model()->tracks(); for (const auto& track : tl) { @@ -176,7 +176,7 @@ void PatternEditor::updatePosition() void PatternEditor::makeSteps( bool clone ) { - TrackContainer::TrackList tl = model()->tracks(); + const TrackContainer::TrackList& tl = model()->tracks(); for (const auto& track : tl) { diff --git a/src/gui/editors/PianoRoll.cpp b/src/gui/editors/PianoRoll.cpp index 0cc8257255f..cef2205d266 100644 --- a/src/gui/editors/PianoRoll.cpp +++ b/src/gui/editors/PianoRoll.cpp @@ -130,7 +130,10 @@ QPixmap* PianoRoll::s_toolKnife = nullptr; SimpleTextFloat * PianoRoll::s_textFloat = nullptr; -static std::array s_noteStrings {"C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"}; +static std::array s_noteStrings { + "C", "C\u266F / D\u266D", "D", "D\u266F / E\u266D", "E", "F", "F\u266F / G\u266D", + "G", "G\u266F / A\u266D", "A", "A\u266F / B\u266D", "B" +}; static QString getNoteString(int key) { @@ -741,10 +744,10 @@ void PianoRoll::fitNoteLengths(bool fill) { if (!hasValidMidiClip()) { return; } m_midiClip->addJournalCheckPoint(); + m_midiClip->rearrangeAllNotes(); // Reference notes - NoteVector refNotes = m_midiClip->notes(); - std::sort(refNotes.begin(), refNotes.end(), Note::lessThan); + const NoteVector& refNotes = m_midiClip->notes(); // Notes to edit NoteVector notes = getSelectedNotes(); @@ -762,7 +765,7 @@ void PianoRoll::fitNoteLengths(bool fill) } int length; - NoteVector::iterator ref = refNotes.begin(); + auto ref = refNotes.begin(); for (Note* note : notes) { // Fast forward to next reference note @@ -797,14 +800,11 @@ void PianoRoll::constrainNoteLengths(bool constrainMax) if (!hasValidMidiClip()) { return; } m_midiClip->addJournalCheckPoint(); - NoteVector notes = getSelectedNotes(); - if (notes.empty()) - { - notes = m_midiClip->notes(); - } + const NoteVector selectedNotes = getSelectedNotes(); + const auto& notes = selectedNotes.empty() ? m_midiClip->notes() : selectedNotes; - TimePos bound = m_lenOfNewNotes; // will be length of last note - for (Note* note : notes) + TimePos bound = m_lenOfNewNotes; // will be length of last note + for (auto note : notes) { if (constrainMax ? note->length() > bound : note->length() < bound) { @@ -1207,11 +1207,11 @@ void PianoRoll::shiftSemiTone(int amount) //Shift notes by amount semitones auto selectedNotes = getSelectedNotes(); //If no notes are selected, shift all of them, otherwise shift selection - if (selectedNotes.empty()) { return shiftSemiTone(m_midiClip->notes(), amount); } - else { return shiftSemiTone(selectedNotes, amount); } + if (selectedNotes.empty()) { shiftSemiTone(m_midiClip->notes(), amount); } + else { shiftSemiTone(selectedNotes, amount); } } -void PianoRoll::shiftSemiTone(NoteVector notes, int amount) +void PianoRoll::shiftSemiTone(const NoteVector& notes, int amount) { m_midiClip->addJournalCheckPoint(); for (Note *note : notes) { note->setKey( note->key() + amount ); } @@ -1232,11 +1232,11 @@ void PianoRoll::shiftPos(int amount) //Shift notes pos by amount auto selectedNotes = getSelectedNotes(); //If no notes are selected, shift all of them, otherwise shift selection - if (selectedNotes.empty()) { return shiftPos(m_midiClip->notes(), amount); } - else { return shiftPos(selectedNotes, amount); } + if (selectedNotes.empty()) { shiftPos(m_midiClip->notes(), amount); } + else { shiftPos(selectedNotes, amount); } } -void PianoRoll::shiftPos(NoteVector notes, int amount) +void PianoRoll::shiftPos(const NoteVector& notes, int amount) { m_midiClip->addJournalCheckPoint(); @@ -1722,10 +1722,10 @@ void PianoRoll::mousePressEvent(QMouseEvent * me ) const NoteVector & notes = m_midiClip->notes(); // will be our iterator in the following loop - auto it = notes.begin() + notes.size() - 1; + auto it = notes.rbegin(); // loop through whole note-vector... - for( int i = 0; i < notes.size(); ++i ) + while (it != notes.rend()) { Note *note = *it; TimePos len = note->length(); @@ -1750,7 +1750,7 @@ void PianoRoll::mousePressEvent(QMouseEvent * me ) { break; } - --it; + ++it; } // first check whether the user clicked in note-edit- @@ -1772,7 +1772,7 @@ void PianoRoll::mousePressEvent(QMouseEvent * me ) Note * created_new_note = nullptr; // did it reach end of vector because // there's no note?? - if( it == notes.begin()-1 ) + if (it == notes.rend()) { is_new_note = true; m_midiClip->addJournalCheckPoint(); @@ -1819,8 +1819,8 @@ void PianoRoll::mousePressEvent(QMouseEvent * me ) // reset it so that it can be used for // ops (move, resize) after this // code-block - it = notes.begin(); - while( it != notes.end() && *it != created_new_note ) + it = notes.rbegin(); + while (it != notes.rend() && *it != created_new_note) { ++it; } @@ -1936,7 +1936,7 @@ void PianoRoll::mousePressEvent(QMouseEvent * me ) { // erase single note m_mouseDownRight = true; - if( it != notes.begin()-1 ) + if (it != notes.rend()) { m_midiClip->addJournalCheckPoint(); m_midiClip->removeNote( *it ); @@ -2516,7 +2516,7 @@ void PianoRoll::mouseMoveEvent( QMouseEvent * me ) bool altPressed = me->modifiers() & Qt::AltModifier; // We iterate from last note in MIDI clip to the first, // chronologically - auto it = notes.begin() + notes.size() - 1; + auto it = notes.rbegin(); for( int i = 0; i < notes.size(); ++i ) { Note* n = *it; @@ -2559,7 +2559,7 @@ void PianoRoll::mouseMoveEvent( QMouseEvent * me ) } - --it; + ++it; } // Emit MIDI clip has changed @@ -2578,10 +2578,10 @@ void PianoRoll::mouseMoveEvent( QMouseEvent * me ) const NoteVector & notes = m_midiClip->notes(); // will be our iterator in the following loop - auto it = notes.begin() + notes.size() - 1; + auto it = notes.rbegin(); // loop through whole note-vector... - for( int i = 0; i < notes.size(); ++i ) + while (it != notes.rend()) { Note *note = *it; // and check whether the cursor is over an @@ -2594,12 +2594,12 @@ void PianoRoll::mouseMoveEvent( QMouseEvent * me ) { break; } - --it; + ++it; } // did it reach end of vector because there's // no note?? - if( it != notes.begin()-1 ) + if (it != notes.rend()) { Note *note = *it; // x coordinate of the right edge of the note @@ -4134,9 +4134,9 @@ void PianoRoll::finishRecordNote(const Note & n ) { if( it->key() == n.key() ) { - Note n1( n.length(), it->pos(), + Note n1(n.length(), it->pos(), it->key(), it->getVolume(), - it->getPanning() ); + it->getPanning(), n.detuning()); n1.quantizeLength( quantization() ); m_midiClip->addNote( n1 ); update(); diff --git a/src/gui/instrument/InstrumentTrackWindow.cpp b/src/gui/instrument/InstrumentTrackWindow.cpp index 43cca0dac1f..28cd8c6c8aa 100644 --- a/src/gui/instrument/InstrumentTrackWindow.cpp +++ b/src/gui/instrument/InstrumentTrackWindow.cpp @@ -49,7 +49,7 @@ #include "InstrumentFunctions.h" #include "InstrumentFunctionViews.h" #include "InstrumentMidiIOView.h" -#include "InstrumentMiscView.h" +#include "InstrumentTuningView.h" #include "InstrumentSoundShapingView.h" #include "InstrumentTrack.h" #include "InstrumentTrackView.h" @@ -255,25 +255,25 @@ InstrumentTrackWindow::InstrumentTrackWindow( InstrumentTrackView * _itv ) : instrumentFunctionsLayout->addStretch(); // MIDI tab - m_midiView = new InstrumentMidiIOView( m_tabWidget ); + m_midiView = new InstrumentMidiIOView(m_tabWidget); // FX tab - m_effectView = new EffectRackView( m_track->m_audioPort.effects(), m_tabWidget ); + m_effectView = new EffectRackView(m_track->m_audioPort.effects(), m_tabWidget); - // MISC tab - m_miscView = new InstrumentMiscView( m_track, m_tabWidget ); + // Tuning tab + m_tuningView = new InstrumentTuningView(m_track, m_tabWidget); - m_tabWidget->addTab( m_ssView, tr( "Envelope, filter & LFO" ), "env_lfo_tab", 1 ); - m_tabWidget->addTab( instrumentFunctions, tr( "Chord stacking & arpeggio" ), "func_tab", 2 ); - m_tabWidget->addTab( m_effectView, tr( "Effects" ), "fx_tab", 3 ); - m_tabWidget->addTab( m_midiView, tr( "MIDI" ), "midi_tab", 4 ); - m_tabWidget->addTab( m_miscView, tr( "Miscellaneous" ), "misc_tab", 5 ); + m_tabWidget->addTab(m_ssView, tr("Envelope, filter & LFO"), "env_lfo_tab", 1); + m_tabWidget->addTab(instrumentFunctions, tr("Chord stacking & arpeggio"), "func_tab", 2); + m_tabWidget->addTab(m_effectView, tr("Effects"), "fx_tab", 3); + m_tabWidget->addTab(m_midiView, tr("MIDI"), "midi_tab", 4); + m_tabWidget->addTab(m_tuningView, tr("Tuning and transposition"), "tuning_tab", 5); adjustTabSize(m_ssView); adjustTabSize(instrumentFunctions); m_effectView->resize(EffectRackView::DEFAULT_WIDTH, INSTRUMENT_HEIGHT - 4 - 1); adjustTabSize(m_midiView); - adjustTabSize(m_miscView); + adjustTabSize(m_tuningView); // setup piano-widget m_pianoView = new PianoView( this ); @@ -376,12 +376,14 @@ void InstrumentTrackWindow::modelChanged() if (m_track->instrument() && m_track->instrument()->flags().testFlag(Instrument::Flag::IsMidiBased)) { - m_miscView->microtunerGroupBox()->hide(); + m_tuningView->microtunerNotSupportedLabel()->show(); + m_tuningView->microtunerGroupBox()->hide(); m_track->m_microtuner.enabledModel()->setValue(false); } else { - m_miscView->microtunerGroupBox()->show(); + m_tuningView->microtunerNotSupportedLabel()->hide(); + m_tuningView->microtunerGroupBox()->show(); } m_ssView->setModel( &m_track->m_soundShaping ); @@ -389,11 +391,11 @@ void InstrumentTrackWindow::modelChanged() m_arpeggioView->setModel( &m_track->m_arpeggio ); m_midiView->setModel( &m_track->m_midiPort ); m_effectView->setModel( m_track->m_audioPort.effects() ); - m_miscView->pitchGroupBox()->setModel(&m_track->m_useMasterPitchModel); - m_miscView->microtunerGroupBox()->setModel(m_track->m_microtuner.enabledModel()); - m_miscView->scaleCombo()->setModel(m_track->m_microtuner.scaleModel()); - m_miscView->keymapCombo()->setModel(m_track->m_microtuner.keymapModel()); - m_miscView->rangeImportCheckbox()->setModel(m_track->m_microtuner.keyRangeImportModel()); + m_tuningView->pitchGroupBox()->setModel(&m_track->m_useMasterPitchModel); + m_tuningView->microtunerGroupBox()->setModel(m_track->m_microtuner.enabledModel()); + m_tuningView->scaleCombo()->setModel(m_track->m_microtuner.scaleModel()); + m_tuningView->keymapCombo()->setModel(m_track->m_microtuner.keymapModel()); + m_tuningView->rangeImportCheckbox()->setModel(m_track->m_microtuner.keyRangeImportModel()); updateName(); } diff --git a/src/gui/instrument/InstrumentTuningView.cpp b/src/gui/instrument/InstrumentTuningView.cpp new file mode 100644 index 00000000000..355d7d18c73 --- /dev/null +++ b/src/gui/instrument/InstrumentTuningView.cpp @@ -0,0 +1,118 @@ +/* + * InstrumentTuningView.cpp - Instrument settings for tuning and transpositions + * + * Copyright (c) 2005-2014 Tobias Doerffel + * Copyright (c) 2020-2022 Martin Pavelek + * + * 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 "InstrumentTuningView.h" + +#include +#include +#include +#include + +#include "ComboBox.h" +#include "GroupBox.h" +#include "GuiApplication.h" +#include "gui_templates.h" +#include "InstrumentTrack.h" +#include "LedCheckBox.h" +#include "MainWindow.h" +#include "PixmapButton.h" + + +namespace lmms::gui +{ + + +InstrumentTuningView::InstrumentTuningView(InstrumentTrack *it, QWidget *parent) : + QWidget(parent) +{ + auto layout = new QVBoxLayout(this); + layout->setContentsMargins(5, 5, 5, 5); + + // Master pitch toggle + m_pitchGroupBox = new GroupBox(tr("GLOBAL TRANSPOSITION")); + m_pitchGroupBox->setModel(&it->m_useMasterPitchModel); + layout->addWidget(m_pitchGroupBox); + + auto masterPitchLayout = new QHBoxLayout(m_pitchGroupBox); + masterPitchLayout->setContentsMargins(8, 18, 8, 8); + + auto tlabel = new QLabel(tr("Enables the use of global transposition")); + tlabel->setWordWrap(true); + tlabel->setFont(pointSize<8>(tlabel->font())); + masterPitchLayout->addWidget(tlabel); + + // Microtuner settings + m_microtunerNotSupportedLabel = new QLabel(tr("Microtuner is not available for MIDI-based instruments.")); + m_microtunerNotSupportedLabel->setWordWrap(true); + m_microtunerNotSupportedLabel->hide(); + layout->addWidget(m_microtunerNotSupportedLabel); + + m_microtunerGroupBox = new GroupBox(tr("MICROTUNER")); + m_microtunerGroupBox->setModel(it->m_microtuner.enabledModel()); + layout->addWidget(m_microtunerGroupBox); + + auto microtunerLayout = new QVBoxLayout(m_microtunerGroupBox); + microtunerLayout->setContentsMargins(8, 18, 8, 8); + + auto scaleEditLayout = new QHBoxLayout(); + scaleEditLayout->setContentsMargins(0, 0, 4, 0); + microtunerLayout->addLayout(scaleEditLayout); + + auto scaleLabel = new QLabel(tr("Active scale:")); + scaleEditLayout->addWidget(scaleLabel); + + QPixmap editPixmap(embed::getIconPixmap("edit_draw_small")); + auto editPixButton = new PixmapButton(this, tr("Edit scales and keymaps")); + editPixButton->setToolTip(tr("Edit scales and keymaps")); + editPixButton->setInactiveGraphic(editPixmap); + editPixButton->setActiveGraphic(editPixmap); + editPixButton->setFixedSize(16, 16); + connect(editPixButton, SIGNAL(clicked()), getGUI()->mainWindow(), SLOT(toggleMicrotunerWin())); + + scaleEditLayout->addWidget(editPixButton); + + m_scaleCombo = new ComboBox(); + m_scaleCombo->setModel(it->m_microtuner.scaleModel()); + microtunerLayout->addWidget(m_scaleCombo); + + auto keymapLabel = new QLabel(tr("Active keymap:")); + microtunerLayout->addWidget(keymapLabel); + + m_keymapCombo = new ComboBox(); + m_keymapCombo->setModel(it->m_microtuner.keymapModel()); + microtunerLayout->addWidget(m_keymapCombo); + + m_rangeImportCheckbox = new LedCheckBox(tr("Import note ranges from keymap"), this); + m_rangeImportCheckbox->setModel(it->m_microtuner.keyRangeImportModel()); + m_rangeImportCheckbox->setToolTip(tr("When enabled, the first, last and base notes of this instrument will be overwritten with values specified by the selected keymap.")); + m_rangeImportCheckbox->setCheckable(true); + microtunerLayout->addWidget(m_rangeImportCheckbox); + + // Fill remaining space + layout->addStretch(); +} + + +} // namespace lmms::gui diff --git a/src/gui/instrument/PianoView.cpp b/src/gui/instrument/PianoView.cpp index 20db2e8e83f..d20cbcac52e 100644 --- a/src/gui/instrument/PianoView.cpp +++ b/src/gui/instrument/PianoView.cpp @@ -322,70 +322,65 @@ void PianoView::modelChanged() -// gets the key from the given mouse-position +// Gets the key from the given mouse position /*! \brief Get the key from the mouse position in the piano display * - * First we determine it roughly by the position of the point given in - * white key widths from our start. We then add in any black keys that - * might have been skipped over (they take a key number, but no 'white - * key' space). We then add in our starting key number. - * - * We then determine whether it was a black key that was pressed by - * checking whether it was within the vertical range of black keys. - * Black keys sit exactly between white keys on this keyboard, so - * we then shift the note down or up if we were in the left or right - * half of the white note. We only do this, of course, if the white - * note has a black key on that side, so to speak. - * - * This function returns const because there is a linear mapping from - * the point given to the key returned that never changes. - * - * \param _p The point that the mouse was pressed. + * \param p The point that the mouse was pressed. */ -int PianoView::getKeyFromMouse( const QPoint & _p ) const +int PianoView::getKeyFromMouse(const QPoint& p) const { - int offset = _p.x() % PW_WHITE_KEY_WIDTH; - if( offset < 0 ) offset += PW_WHITE_KEY_WIDTH; - int key_num = ( _p.x() - offset) / PW_WHITE_KEY_WIDTH; + // The left-most key visible in the piano display is always white + const int startingWhiteKey = m_pianoScroll->value(); - for( int i = 0; i <= key_num; ++i ) - { - if ( Piano::isBlackKey( m_startKey+i ) ) - { - ++key_num; - } - } - for( int i = 0; i >= key_num; --i ) - { - if ( Piano::isBlackKey( m_startKey+i ) ) - { - --key_num; - } - } + // Adjust the mouse x position as if x == 0 was the left side of the lowest key + const int adjX = p.x() + (startingWhiteKey * PW_WHITE_KEY_WIDTH); + + // Can early return for notes too low + if (adjX <= 0) { return 0; } + + // Now we can calculate the key number (in only white keys) and the octave + const int whiteKey = adjX / PW_WHITE_KEY_WIDTH; + const int octave = whiteKey / Piano::WhiteKeysPerOctave; - key_num += m_startKey; + // Calculate for full octaves + int key = octave * KeysPerOctave; - // is it a black key? - if( _p.y() < PIANO_BASE + PW_BLACK_KEY_HEIGHT ) + // Adjust for white notes in the current octave + // (WhiteKeys maps each white key to the number of notes to their left in the octave) + key += static_cast(WhiteKeys[whiteKey % Piano::WhiteKeysPerOctave]); + + // Might be a black key, which would require further adjustment + if (p.y() < PIANO_BASE + PW_BLACK_KEY_HEIGHT) { - // then do extra checking whether the mouse-cursor is over - // a black key - if( key_num > 0 && Piano::isBlackKey( key_num-1 ) && - offset <= ( PW_WHITE_KEY_WIDTH / 2 ) - - ( PW_BLACK_KEY_WIDTH / 2 ) ) + // Maps white keys to neighboring black keys + static constexpr std::array neighboringKeyMap { + std::pair{ 0, 1 }, // C --> no B#; C# + std::pair{ 1, 1 }, // D --> C#; D# + std::pair{ 1, 0 }, // E --> D#; no E# + std::pair{ 0, 1 }, // F --> no E#; F# + std::pair{ 1, 1 }, // G --> F#; G# + std::pair{ 1, 1 }, // A --> G#; A# + std::pair{ 1, 0 }, // B --> A#; no B# + }; + + const auto neighboringBlackKeys = neighboringKeyMap[whiteKey % Piano::WhiteKeysPerOctave]; + const int offset = adjX - (whiteKey * PW_WHITE_KEY_WIDTH); // mouse X offset from white key + + if (offset < PW_BLACK_KEY_WIDTH / 2) { - --key_num; + // At the location of a (possibly non-existent) black key on the left side + key -= neighboringBlackKeys.first; } - if( key_num < NumKeys - 1 && Piano::isBlackKey( key_num+1 ) && - offset >= ( PW_WHITE_KEY_WIDTH - - PW_BLACK_KEY_WIDTH / 2 ) ) + else if (offset > PW_WHITE_KEY_WIDTH - (PW_BLACK_KEY_WIDTH / 2)) { - ++key_num; + // At the location of a (possibly non-existent) black key on the right side + key += neighboringBlackKeys.second; } + + // For white keys in between black keys, no further adjustment is needed } - // some range-checking-stuff - return qBound( 0, key_num, NumKeys - 1 ); + return std::clamp(key, 0, NumKeys - 1); } @@ -396,12 +391,12 @@ int PianoView::getKeyFromMouse( const QPoint & _p ) const * * We need to update our start key position based on the new position. * - * \param _new_pos the new key position. + * \param newPos the new key position, counting only white keys. */ -void PianoView::pianoScrolled(int new_pos) +void PianoView::pianoScrolled(int newPos) { - m_startKey = static_cast(new_pos / Piano::WhiteKeysPerOctave) - + WhiteKeys[new_pos % Piano::WhiteKeysPerOctave]; + m_startKey = static_cast(newPos / Piano::WhiteKeysPerOctave) + + WhiteKeys[newPos % Piano::WhiteKeysPerOctave]; update(); } diff --git a/src/gui/menus/MidiPortMenu.cpp b/src/gui/menus/MidiPortMenu.cpp index 296be3506f1..b99c3a0b72f 100644 --- a/src/gui/menus/MidiPortMenu.cpp +++ b/src/gui/menus/MidiPortMenu.cpp @@ -34,7 +34,6 @@ MidiPortMenu::MidiPortMenu( MidiPort::Mode _mode ) : ModelView( nullptr, this ), m_mode( _mode ) { - setFont( pointSize<9>( font() ) ); connect( this, SIGNAL(triggered(QAction*)), this, SLOT(activatedPort(QAction*))); } diff --git a/src/gui/modals/ControllerConnectionDialog.cpp b/src/gui/modals/ControllerConnectionDialog.cpp index 79daa25b565..4d1090d5c88 100644 --- a/src/gui/modals/ControllerConnectionDialog.cpp +++ b/src/gui/modals/ControllerConnectionDialog.cpp @@ -54,7 +54,7 @@ class AutoDetectMidiController : public MidiController AutoDetectMidiController( Model* parent ) : MidiController( parent ), m_detectedMidiChannel( 0 ), - m_detectedMidiController( 0 ) + m_detectedMidiController(NONE) { updateName(); } @@ -69,7 +69,7 @@ class AutoDetectMidiController : public MidiController ( m_midiPort.inputChannel() == 0 || m_midiPort.inputChannel() == event.channel() + 1 ) ) { m_detectedMidiChannel = event.channel() + 1; - m_detectedMidiController = event.controllerNumber() + 1; + m_detectedMidiController = event.controllerNumber(); m_detectedMidiPort = Engine::audioEngine()->midiClient()->sourcePortName( event ); emit valueChanged(); @@ -152,7 +152,7 @@ ControllerConnectionDialog::ControllerConnectionDialog( QWidget * _parent, m_midiControllerSpinBox = new LcdSpinBox( 3, m_midiGroupBox, tr( "Input controller" ) ); - m_midiControllerSpinBox->addTextForValue( 0, "---" ); + m_midiControllerSpinBox->addTextForValue(MidiController::NONE, "---" ); m_midiControllerSpinBox->setLabel( tr( "CONTROLLER" ) ); m_midiControllerSpinBox->move( 68, 24 ); diff --git a/src/gui/modals/FileDialog.cpp b/src/gui/modals/FileDialog.cpp index 512d7179f0d..a6cf4827a3e 100644 --- a/src/gui/modals/FileDialog.cpp +++ b/src/gui/modals/FileDialog.cpp @@ -26,11 +26,12 @@ #include #include #include +#include +#include #include "ConfigManager.h" #include "FileDialog.h" - namespace lmms::gui { @@ -45,19 +46,38 @@ FileDialog::FileDialog( QWidget *parent, const QString &caption, setOption( QFileDialog::DontUseNativeDialog ); - // Add additional locations to the sidebar +#ifdef LMMS_BUILD_LINUX + QList urls; +#else QList urls = sidebarUrls(); - urls << QUrl::fromLocalFile( QStandardPaths::writableLocation( QStandardPaths::DesktopLocation ) ); - // Find downloads directory - QDir downloadDir( QDir::homePath() + "/Downloads" ); - if ( ! downloadDir.exists() ) - downloadDir.setPath(QStandardPaths::writableLocation( QStandardPaths::DownloadLocation )); - if ( downloadDir.exists() ) - urls << QUrl::fromLocalFile( downloadDir.absolutePath() ); +#endif - urls << QUrl::fromLocalFile( QStandardPaths::writableLocation( QStandardPaths::MusicLocation ) ); - urls << QUrl::fromLocalFile( ConfigManager::inst()->workingDir() ); + QDir desktopDir; + desktopDir.setPath(QStandardPaths::writableLocation(QStandardPaths::DesktopLocation)); + if (desktopDir.exists()) + { + urls << QUrl::fromLocalFile(desktopDir.absolutePath()); + } + + QDir downloadDir(QDir::homePath() + "/Downloads"); + if (!downloadDir.exists()) + { + downloadDir.setPath(QStandardPaths::writableLocation(QStandardPaths::DownloadLocation)); + } + if (downloadDir.exists()) + { + urls << QUrl::fromLocalFile(downloadDir.absolutePath()); + } + + QDir musicDir; + musicDir.setPath(QStandardPaths::writableLocation(QStandardPaths::MusicLocation)); + if (musicDir.exists()) + { + urls << QUrl::fromLocalFile(musicDir.absolutePath()); + } + urls << QUrl::fromLocalFile(ConfigManager::inst()->workingDir()); + // Add `/Volumes` directory on OS X systems, this allows the user to browse // external disk drives. #ifdef LMMS_BUILD_APPLE @@ -66,6 +86,22 @@ FileDialog::FileDialog( QWidget *parent, const QString &caption, urls << QUrl::fromLocalFile( volumesDir.absolutePath() ); #endif +#ifdef LMMS_BUILD_LINUX + + // FileSystem types : https://www.javatpoint.com/linux-file-system + QStringList usableFileSystems = {"ext", "ext2", "ext3", "ext4", "jfs", "reiserfs", "ntfs3", "fuse.sshfs", "fuseblk"}; + + for(QStorageInfo storage : QStorageInfo::mountedVolumes()) + { + storage.refresh(); + + if (usableFileSystems.contains(QString(storage.fileSystemType()), Qt::CaseInsensitive) && storage.isValid() && storage.isReady()) + { + urls << QUrl::fromLocalFile(storage.rootPath()); + } + } +#endif + setSidebarUrls(urls); } diff --git a/src/gui/modals/SetupDialog.cpp b/src/gui/modals/SetupDialog.cpp index 63b84506e46..0266285a7a4 100644 --- a/src/gui/modals/SetupDialog.cpp +++ b/src/gui/modals/SetupDialog.cpp @@ -579,32 +579,39 @@ SetupDialog::SetupDialog(ConfigTab tab_to_open) : // Buffer size tab. auto bufferSize_tw = new TabWidget(tr("Buffer size"), audio_w); - bufferSize_tw->setFixedHeight(76); + auto bufferSize_layout = new QVBoxLayout(bufferSize_tw); + bufferSize_layout->setSpacing(10); + bufferSize_layout->setContentsMargins(10, 18, 10, 10); m_bufferSizeSlider = new QSlider(Qt::Horizontal, bufferSize_tw); m_bufferSizeSlider->setRange(1, 128); m_bufferSizeSlider->setTickInterval(8); m_bufferSizeSlider->setPageStep(8); m_bufferSizeSlider->setValue(m_bufferSize / BUFFERSIZE_RESOLUTION); - m_bufferSizeSlider->setGeometry(10, 18, 340, 18); m_bufferSizeSlider->setTickPosition(QSlider::TicksBelow); + m_bufferSizeLbl = new QLabel(bufferSize_tw); + + m_bufferSizeWarnLbl = new QLabel(bufferSize_tw); + m_bufferSizeWarnLbl->setWordWrap(true); + connect(m_bufferSizeSlider, SIGNAL(valueChanged(int)), this, SLOT(setBufferSize(int))); connect(m_bufferSizeSlider, SIGNAL(valueChanged(int)), this, SLOT(showRestartWarning())); - - m_bufferSizeLbl = new QLabel(bufferSize_tw); - m_bufferSizeLbl->setGeometry(10, 40, 200, 24); setBufferSize(m_bufferSizeSlider->value()); auto bufferSize_reset_btn = new QPushButton(embed::getIconPixmap("reload"), "", bufferSize_tw); - bufferSize_reset_btn->setGeometry(320, 40, 28, 28); connect(bufferSize_reset_btn, SIGNAL(clicked()), this, SLOT(resetBufferSize())); bufferSize_reset_btn->setToolTip( tr("Reset to default value")); + bufferSize_layout->addWidget(m_bufferSizeSlider); + bufferSize_layout->addWidget(m_bufferSizeLbl); + bufferSize_layout->addWidget(m_bufferSizeWarnLbl); + bufferSize_layout->addWidget(bufferSize_reset_btn); + // Audio layout ordering. audio_layout->addWidget(audioiface_tw); @@ -1172,6 +1179,24 @@ void SetupDialog::audioInterfaceChanged(const QString & iface) } +void SetupDialog::updateBufferSizeWarning(int value) +{ + QString text = "

    "; + 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 +1222,7 @@ void SetupDialog::setBufferSize(int value) m_bufferSize = value * BUFFERSIZE_RESOLUTION; m_bufferSizeLbl->setText(tr("Frames: %1\nLatency: %2 ms").arg(m_bufferSize).arg( 1000.0f * m_bufferSize / Engine::audioEngine()->processingSampleRate(), 0, 'f', 1)); + updateBufferSizeWarning(m_bufferSize); } diff --git a/src/gui/tracks/InstrumentTrackView.cpp b/src/gui/tracks/InstrumentTrackView.cpp index 669fdaccb8f..87c0f044944 100644 --- a/src/gui/tracks/InstrumentTrackView.cpp +++ b/src/gui/tracks/InstrumentTrackView.cpp @@ -63,7 +63,6 @@ InstrumentTrackView::InstrumentTrackView( InstrumentTrack * _it, TrackContainerV m_tlb = new TrackLabelButton( this, getTrackSettingsWidget() ); m_tlb->setCheckable( true ); m_tlb->setIcon( embed::getIconPixmap( "instrument_track" ) ); - m_tlb->move( 3, 1 ); m_tlb->show(); connect( m_tlb, SIGNAL(toggled(bool)), @@ -75,24 +74,14 @@ InstrumentTrackView::InstrumentTrackView( InstrumentTrack * _it, TrackContainerV connect(ConfigManager::inst(), SIGNAL(valueChanged(QString,QString,QString)), this, SLOT(handleConfigChange(QString,QString,QString))); - // creation of widgets for track-settings-widget - int widgetWidth; - if( ConfigManager::inst()->value( "ui", - "compacttrackbuttons" ).toInt() ) - { - widgetWidth = DEFAULT_SETTINGS_WIDGET_WIDTH_COMPACT; - } - else - { - widgetWidth = DEFAULT_SETTINGS_WIDGET_WIDTH; - } + m_mixerChannelNumber = new MixerLineLcdSpinBox(2, getTrackSettingsWidget(), tr("Mixer channel"), this); + m_mixerChannelNumber->show(); m_volumeKnob = new Knob( KnobType::Small17, getTrackSettingsWidget(), tr( "Volume" ) ); m_volumeKnob->setVolumeKnob( true ); m_volumeKnob->setModel( &_it->m_volumeModel ); m_volumeKnob->setHintText( tr( "Volume:" ), "%" ); - m_volumeKnob->move( widgetWidth-2*24, 2 ); m_volumeKnob->setLabel( tr( "VOL" ) ); m_volumeKnob->show(); @@ -100,7 +89,6 @@ InstrumentTrackView::InstrumentTrackView( InstrumentTrack * _it, TrackContainerV tr( "Panning" ) ); m_panningKnob->setModel( &_it->m_panningModel ); m_panningKnob->setHintText(tr("Panning:"), "%"); - m_panningKnob->move( widgetWidth-24, 2 ); m_panningKnob->setLabel( tr( "PAN" ) ); m_panningKnob->show(); @@ -151,9 +139,18 @@ InstrumentTrackView::InstrumentTrackView( InstrumentTrack * _it, TrackContainerV QApplication::palette().color( QPalette::Active, QPalette::BrightText).darker(), getTrackSettingsWidget() ); - m_activityIndicator->setGeometry( - widgetWidth-2*24-11, 2, 8, 28 ); + m_activityIndicator->setFixedSize(8, 28); m_activityIndicator->show(); + + auto layout = new QHBoxLayout(getTrackSettingsWidget()); + layout->setContentsMargins(0, 0, 0, 0); + layout->setSpacing(0); + layout->addWidget(m_tlb); + layout->addWidget(m_mixerChannelNumber); + layout->addWidget(m_activityIndicator); + layout->addWidget(m_volumeKnob); + layout->addWidget(m_panningKnob); + connect( m_activityIndicator, SIGNAL(pressed()), this, SLOT(activityIndicatorPressed())); connect( m_activityIndicator, SIGNAL(released()), @@ -268,6 +265,13 @@ void InstrumentTrackView::handleConfigChange(QString cls, QString attr, QString } } +void InstrumentTrackView::modelChanged() +{ + TrackView::modelChanged(); + auto st = castModel(); + m_mixerChannelNumber->setModel(&st->m_mixerChannelModel); +} + void InstrumentTrackView::dragEnterEvent( QDragEnterEvent * _dee ) { InstrumentTrackWindow::dragEnterEventGeneric( _dee ); diff --git a/src/gui/tracks/SampleTrackView.cpp b/src/gui/tracks/SampleTrackView.cpp index 6a6a2c5fd2e..8516eb5c2a9 100644 --- a/src/gui/tracks/SampleTrackView.cpp +++ b/src/gui/tracks/SampleTrackView.cpp @@ -56,20 +56,17 @@ SampleTrackView::SampleTrackView( SampleTrack * _t, TrackContainerView* tcv ) : connect(m_tlb, SIGNAL(clicked(bool)), this, SLOT(showEffects())); m_tlb->setIcon(embed::getIconPixmap("sample_track")); - m_tlb->move(3, 1); m_tlb->show(); + m_mixerChannelNumber = new MixerLineLcdSpinBox(2, getTrackSettingsWidget(), tr("Mixer channel"), this); + m_mixerChannelNumber->show(); + m_volumeKnob = new Knob( KnobType::Small17, getTrackSettingsWidget(), tr( "Track volume" ) ); m_volumeKnob->setVolumeKnob( true ); m_volumeKnob->setModel( &_t->m_volumeModel ); m_volumeKnob->setHintText( tr( "Channel volume:" ), "%" ); - int settingsWidgetWidth = ConfigManager::inst()-> - value( "ui", "compacttrackbuttons" ).toInt() - ? DEFAULT_SETTINGS_WIDGET_WIDTH_COMPACT - : DEFAULT_SETTINGS_WIDGET_WIDTH; - m_volumeKnob->move( settingsWidgetWidth - 2 * 24, 2 ); m_volumeKnob->setLabel( tr( "VOL" ) ); m_volumeKnob->show(); @@ -77,7 +74,6 @@ SampleTrackView::SampleTrackView( SampleTrack * _t, TrackContainerView* tcv ) : tr( "Panning" ) ); m_panningKnob->setModel( &_t->m_panningModel ); m_panningKnob->setHintText( tr( "Panning:" ), "%" ); - m_panningKnob->move( settingsWidgetWidth - 24, 2 ); m_panningKnob->setLabel( tr( "PAN" ) ); m_panningKnob->show(); @@ -87,8 +83,18 @@ SampleTrackView::SampleTrackView( SampleTrack * _t, TrackContainerView* tcv ) : QApplication::palette().color(QPalette::Active, QPalette::BrightText).darker(), getTrackSettingsWidget() ); - m_activityIndicator->setGeometry(settingsWidgetWidth - 2 * 24 - 11, 2, 8, 28); + m_activityIndicator->setFixedSize(8, 28); m_activityIndicator->show(); + + auto layout = new QHBoxLayout(getTrackSettingsWidget()); + layout->setContentsMargins(0, 0, 0, 0); + layout->setSpacing(0); + layout->addWidget(m_tlb); + layout->addWidget(m_mixerChannelNumber); + layout->addWidget(m_activityIndicator); + layout->addWidget(m_volumeKnob); + layout->addWidget(m_panningKnob); + connect(_t, SIGNAL(playingChanged()), this, SLOT(updateIndicator())); setModel( _t ); @@ -170,6 +176,7 @@ void SampleTrackView::modelChanged() { auto st = castModel(); m_volumeKnob->setModel(&st->m_volumeModel); + m_mixerChannelNumber->setModel(&st->m_mixerChannelModel); TrackView::modelChanged(); } diff --git a/src/gui/tracks/TrackContentWidget.cpp b/src/gui/tracks/TrackContentWidget.cpp index 442d717bfd1..619eff8317b 100644 --- a/src/gui/tracks/TrackContentWidget.cpp +++ b/src/gui/tracks/TrackContentWidget.cpp @@ -344,7 +344,7 @@ bool TrackContentWidget::canPasteSelection( TimePos clipPos, const QMimeData* md const int initialTrackIndex = tiAttr.value().toInt(); // Get the current track's index - const TrackContainer::TrackList tracks = t->trackContainer()->tracks(); + const TrackContainer::TrackList& tracks = t->trackContainer()->tracks(); const auto currentTrackIt = std::find(tracks.begin(), tracks.end(), t); const int currentTrackIndex = currentTrackIt != tracks.end() ? std::distance(tracks.begin(), currentTrackIt) : -1; @@ -443,7 +443,7 @@ bool TrackContentWidget::pasteSelection( TimePos clipPos, const QMimeData * md, TimePos grabbedClipPos = clipPosAttr.value().toInt(); // Snap the mouse position to the beginning of the dropped bar, in ticks - const TrackContainer::TrackList tracks = getTrack()->trackContainer()->tracks(); + const TrackContainer::TrackList& tracks = getTrack()->trackContainer()->tracks(); const auto currentTrackIt = std::find(tracks.begin(), tracks.end(), getTrack()); const int currentTrackIndex = currentTrackIt != tracks.end() ? std::distance(tracks.begin(), currentTrackIt) : -1; diff --git a/src/gui/tracks/TrackOperationsWidget.cpp b/src/gui/tracks/TrackOperationsWidget.cpp index ce6177d76da..fa1a651f613 100644 --- a/src/gui/tracks/TrackOperationsWidget.cpp +++ b/src/gui/tracks/TrackOperationsWidget.cpp @@ -64,7 +64,6 @@ TrackOperationsWidget::TrackOperationsWidget( TrackView * parent ) : "to begin a new drag'n'drop action." ).arg(UI_CTRL_KEY) ); auto toMenu = new QMenu(this); - toMenu->setFont( pointSize<9>( toMenu->font() ) ); connect( toMenu, SIGNAL(aboutToShow()), this, SLOT(updateMenu())); diff --git a/src/gui/widgets/CPULoadWidget.cpp b/src/gui/widgets/CPULoadWidget.cpp index 799e037ef43..db1f5cacc8f 100644 --- a/src/gui/widgets/CPULoadWidget.cpp +++ b/src/gui/widgets/CPULoadWidget.cpp @@ -24,6 +24,7 @@ */ +#include #include #include "AudioEngine.h" @@ -72,10 +73,9 @@ void CPULoadWidget::paintEvent( QPaintEvent * ) QPainter p( &m_temp ); p.drawPixmap( 0, 0, m_background ); - // as load-indicator consists of small 2-pixel wide leds with - // 1 pixel spacing, we have to make sure, only whole leds are - // shown which we achieve by the following formula - int w = ( m_leds.width() * m_currentLoad / 300 ) * 3; + // Normally the CPU load indicator moves smoothly, with 1 pixel resolution. However, some themes may want to + // draw discrete elements (like LEDs), so the stepSize property can be used to specify a larger step size. + int w = (m_leds.width() * std::min(m_currentLoad, 100) / (stepSize() * 100)) * stepSize(); if( w > 0 ) { p.drawPixmap( 23, 3, m_leds, 0, 0, w, @@ -91,10 +91,21 @@ void CPULoadWidget::paintEvent( QPaintEvent * ) void CPULoadWidget::updateCpuLoad() { - // smooth load-values a bit - int new_load = ( m_currentLoad + Engine::audioEngine()->cpuLoad() ) / 2; - if( new_load != m_currentLoad ) + // Additional display smoothing for the main load-value. Stronger averaging + // cannot be used directly in the profiler: cpuLoad() must react fast enough + // to be useful as overload indicator in AudioEngine::criticalXRuns(). + const int new_load = (m_currentLoad + Engine::audioEngine()->cpuLoad()) / 2; + + if (new_load != m_currentLoad) { + auto engine = Engine::audioEngine(); + setToolTip( + tr("DSP total: %1%").arg(new_load) + "\n" + + tr(" - Notes and setup: %1%").arg(engine->detailLoad(AudioEngineProfiler::DetailType::NoteSetup)) + "\n" + + tr(" - Instruments: %1%").arg(engine->detailLoad(AudioEngineProfiler::DetailType::Instruments)) + "\n" + + tr(" - Effects: %1%").arg(engine->detailLoad(AudioEngineProfiler::DetailType::Effects)) + "\n" + + tr(" - Mixing: %1%").arg(engine->detailLoad(AudioEngineProfiler::DetailType::Mixing)) + ); m_currentLoad = new_load; m_changed = true; update(); @@ -102,4 +113,4 @@ void CPULoadWidget::updateCpuLoad() } -} // namespace lmms::gui \ No newline at end of file +} // namespace lmms::gui diff --git a/src/gui/widgets/ComboBox.cpp b/src/gui/widgets/ComboBox.cpp index bdf78ccce36..2377a37abf8 100644 --- a/src/gui/widgets/ComboBox.cpp +++ b/src/gui/widgets/ComboBox.cpp @@ -70,7 +70,6 @@ ComboBox::ComboBox( QWidget * _parent, const QString & _name ) : } setFont( pointSize<9>( font() ) ); - m_menu.setFont( pointSize<8>( m_menu.font() ) ); connect( &m_menu, SIGNAL(triggered(QAction*)), this, SLOT(setItem(QAction*))); diff --git a/src/gui/widgets/Knob.cpp b/src/gui/widgets/Knob.cpp index c2f90fb2b71..56cf29345d0 100644 --- a/src/gui/widgets/Knob.cpp +++ b/src/gui/widgets/Knob.cpp @@ -22,6 +22,8 @@ * */ +#include "Knob.h" + #include #include #include @@ -34,7 +36,6 @@ #endif #include "lmms_math.h" -#include "Knob.h" #include "CaptionMenu.h" #include "ConfigManager.h" #include "ControllerConnection.h" @@ -48,7 +49,6 @@ #include "SimpleTextFloat.h" #include "StringPairDrag.h" - namespace lmms::gui { @@ -484,6 +484,13 @@ void Knob::drawKnob( QPainter * _p ) _p->drawImage( 0, 0, m_cache ); } +void Knob::showTextFloat(int msecBeforeDisplay, int msecDisplayTime) +{ + s_textFloat->setText(displayValue()); + s_textFloat->moveGlobal(this, QPoint(width() + 2, 0)); + s_textFloat->showWithDelay(msecBeforeDisplay, msecDisplayTime); +} + float Knob::getValue( const QPoint & _p ) { float value; @@ -580,10 +587,8 @@ void Knob::mousePressEvent( QMouseEvent * _me ) emit sliderPressed(); - s_textFloat->setText( displayValue() ); - s_textFloat->moveGlobal( this, - QPoint( width() + 2, 0 ) ); - s_textFloat->show(); + showTextFloat(0, 0); + m_buttonPressed = true; } else if( _me->button() == Qt::LeftButton && @@ -613,6 +618,7 @@ void Knob::mouseMoveEvent( QMouseEvent * _me ) m_lastMousePos = _me->pos(); } s_textFloat->setText( displayValue() ); + s_textFloat->show(); } @@ -638,7 +644,15 @@ void Knob::mouseReleaseEvent( QMouseEvent* event ) s_textFloat->hide(); } +void Knob::enterEvent(QEvent *event) +{ + showTextFloat(700, 2000); +} +void Knob::leaveEvent(QEvent *event) +{ + s_textFloat->hide(); +} void Knob::focusOutEvent( QFocusEvent * _fe ) diff --git a/src/gui/widgets/LcdFloatSpinBox.cpp b/src/gui/widgets/LcdFloatSpinBox.cpp index 6391f314ad9..96f2b27e1db 100644 --- a/src/gui/widgets/LcdFloatSpinBox.cpp +++ b/src/gui/widgets/LcdFloatSpinBox.cpp @@ -49,6 +49,7 @@ namespace lmms::gui LcdFloatSpinBox::LcdFloatSpinBox(int numWhole, int numFrac, const QString& name, QWidget* parent) : + QWidget(parent), FloatModelView(new FloatModel(0, 0, 0, 0, nullptr, name, true), this), m_wholeDisplay(numWhole, parent, name, false), m_fractionDisplay(numFrac, parent, name, true), @@ -62,6 +63,7 @@ LcdFloatSpinBox::LcdFloatSpinBox(int numWhole, int numFrac, const QString& name, LcdFloatSpinBox::LcdFloatSpinBox(int numWhole, int numFrac, const QString& style, const QString& name, QWidget* parent) : + QWidget(parent), FloatModelView(new FloatModel(0, 0, 0, 0, nullptr, name, true), this), m_wholeDisplay(numWhole, style, parent, name, false), m_fractionDisplay(numFrac, style, parent, name, true), @@ -101,6 +103,7 @@ void LcdFloatSpinBox::layoutSetup(const QString &style) outerLayout->setContentsMargins(0, 0, 0, 0); outerLayout->setSizeConstraint(QLayout::SetFixedSize); this->setLayout(outerLayout); + this->setFixedHeight(32); } @@ -240,9 +243,9 @@ void LcdFloatSpinBox::paintEvent(QPaintEvent*) { p.setFont(pointSizeF(p.font(), 6.5)); p.setPen(m_wholeDisplay.textShadowColor()); - p.drawText(width() / 2 - horizontalAdvance(p.fontMetrics(), m_label) / 2 + 1, height(), m_label); + p.drawText(width() / 2 - p.fontMetrics().boundingRect(m_label).width() / 2 + 1, height(), m_label); p.setPen(m_wholeDisplay.textColor()); - p.drawText(width() / 2 - horizontalAdvance(p.fontMetrics(), m_label) / 2, height() - 1, m_label); + p.drawText(width() / 2 - p.fontMetrics().boundingRect(m_label).width() / 2, height() - 1, m_label); } } diff --git a/src/gui/widgets/SimpleTextFloat.cpp b/src/gui/widgets/SimpleTextFloat.cpp index d1f490b5ef9..e37753229ac 100644 --- a/src/gui/widgets/SimpleTextFloat.cpp +++ b/src/gui/widgets/SimpleTextFloat.cpp @@ -45,6 +45,14 @@ SimpleTextFloat::SimpleTextFloat() : m_textLabel = new QLabel(this); layout->addWidget(m_textLabel); + + m_showTimer = new QTimer(this); + m_showTimer->setSingleShot(true); + QObject::connect(m_showTimer, &QTimer::timeout, this, &SimpleTextFloat::show); + + m_hideTimer = new QTimer(this); + m_hideTimer->setSingleShot(true); + QObject::connect(m_hideTimer, &QTimer::timeout, this, &SimpleTextFloat::hide); } void SimpleTextFloat::setText(const QString & text) @@ -52,6 +60,29 @@ void SimpleTextFloat::setText(const QString & text) m_textLabel->setText(text); } +void SimpleTextFloat::showWithDelay(int msecBeforeDisplay, int msecDisplayTime) +{ + if (msecBeforeDisplay != 0) + { + m_showTimer->start(msecBeforeDisplay); + } + else + { + show(); + } + + if (msecDisplayTime != 0) + { + m_hideTimer->start(msecBeforeDisplay + msecDisplayTime); + } +} + +void SimpleTextFloat::hide() +{ + m_showTimer->stop(); + m_hideTimer->stop(); + QWidget::hide(); +} void SimpleTextFloat::setVisibilityTimeOut(int msecs) { diff --git a/src/tracks/InstrumentTrack.cpp b/src/tracks/InstrumentTrack.cpp index 84b73614b86..29fda075e9c 100644 --- a/src/tracks/InstrumentTrack.cpp +++ b/src/tracks/InstrumentTrack.cpp @@ -28,6 +28,7 @@ #include "ConfigManager.h" #include "ControllerConnection.h" #include "DataFile.h" +#include "GuiApplication.h" #include "Mixer.h" #include "InstrumentTrackView.h" #include "Instrument.h" @@ -37,6 +38,7 @@ #include "MixHelpers.h" #include "PatternStore.h" #include "PatternTrack.h" +#include "PianoRoll.h" #include "Pitch.h" #include "Song.h" @@ -72,6 +74,7 @@ InstrumentTrack::InstrumentTrack( TrackContainer* tc ) : m_microtuner() { m_pitchModel.setCenterValue( 0 ); + m_pitchModel.setStrictStepSize(true); m_panningModel.setCenterValue( DefaultPanning ); m_baseNoteModel.setInitValue( DefaultKey ); m_firstKeyModel.setInitValue(0); @@ -341,9 +344,10 @@ void InstrumentTrack::processInEvent( const MidiEvent& event, const TimePos& tim NotePlayHandleManager::acquire( this, offset, typeInfo::max() / 2, - Note( TimePos(), TimePos(), event.key(), event.volume( midiPort()->baseVelocity() ) ), + Note(TimePos(), Engine::getSong()->getPlayPos(Engine::getSong()->playMode()), + event.key(), event.volume(midiPort()->baseVelocity())), nullptr, event.channel(), - NotePlayHandle::Origin::MidiInput ); + NotePlayHandle::Origin::MidiInput); m_notes[event.key()] = nph; if( ! Engine::audioEngine()->addPlayHandle( nph ) ) { @@ -566,6 +570,10 @@ f_cnt_t InstrumentTrack::beatLen( NotePlayHandle * _n ) const void InstrumentTrack::playNote( NotePlayHandle* n, sampleFrame* workingBuffer ) { + // Note: under certain circumstances the working buffer is a nullptr. + // These cases are triggered in PlayHandle::doProcessing when the play method is called with a nullptr. + // TODO: Find out if we can skip processing at a higher level if the buffer is nullptr. + // arpeggio- and chord-widget has to do its work -> adding sub-notes // for chords/arpeggios m_noteStacking.processNote( n ); @@ -575,6 +583,15 @@ void InstrumentTrack::playNote( NotePlayHandle* n, sampleFrame* workingBuffer ) { // all is done, so now lets play the note! m_instrument->playNote( n, workingBuffer ); + + // This is effectively the same as checking if workingBuffer is not a nullptr. + // Calling processAudioBuffer with a nullptr leads to crashes. Hence the check. + if (n->usesBuffer()) + { + const fpp_t frames = n->framesLeftForCurrentPeriod(); + const f_cnt_t offset = n->noteOffset(); + processAudioBuffer(workingBuffer, frames + offset, n); + } } } @@ -710,7 +727,7 @@ bool InstrumentTrack::play( const TimePos & _start, const fpp_t _frames, // Handle automation: detuning for (const auto& processHandle : m_processHandles) { - processHandle->processTimePos(_start); + processHandle->processTimePos(_start, m_pitchModel.value(), gui::GuiApplication::instance()->pianoRoll()->isRecording()); } if ( clips.size() == 0 ) diff --git a/src/tracks/MidiClip.cpp b/src/tracks/MidiClip.cpp index b35979f61f8..490f6e6d041 100644 --- a/src/tracks/MidiClip.cpp +++ b/src/tracks/MidiClip.cpp @@ -305,7 +305,7 @@ void MidiClip::setStep( int step, bool enabled ) -void MidiClip::splitNotes(NoteVector notes, TimePos pos) +void MidiClip::splitNotes(const NoteVector& notes, TimePos pos) { if (notes.empty()) { return; } @@ -472,7 +472,7 @@ MidiClip * MidiClip::nextMidiClip() const MidiClip * MidiClip::adjacentMidiClipByOffset(int offset) const { - std::vector clips = m_instrumentTrack->getClips(); + auto& clips = m_instrumentTrack->getClips(); int clipNum = m_instrumentTrack->getClipNum(this); if (clipNum < 0 || clipNum > clips.size() - 1) { return nullptr; } return dynamic_cast(clips[clipNum + offset]); diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 6ff9c41e967..ddf9e29621b 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -19,7 +19,9 @@ ADD_EXECUTABLE(tests QTestSuite.cpp $ + src/core/ArrayVectorTest.cpp src/core/AutomatableModelTest.cpp + src/core/MathTest.cpp src/core/ProjectVersionTest.cpp src/core/RelativePathsTest.cpp diff --git a/tests/main.cpp b/tests/main.cpp index 6d375e6c657..c1a5b5a1016 100644 --- a/tests/main.cpp +++ b/tests/main.cpp @@ -16,7 +16,7 @@ int main(int argc, char* argv[]) int failed = 0; for (QTestSuite*& suite : QTestSuite::suites()) { - failed += QTest::qExec(suite, argc, argv); + if (QTest::qExec(suite, argc, argv) != 0) { ++failed; } } qDebug() << "<<" << failed << "out of"< +#include + +#include "QTestSuite.h" + +using lmms::ArrayVector; + +struct ShouldNotConstruct +{ + ShouldNotConstruct() { QFAIL("should not construct"); } +}; + +struct ShouldNotDestruct +{ + ~ShouldNotDestruct() { QFAIL("should not destruct"); } +}; + +enum class Construction { Default, Copy, Move, CopyAssign, MoveAssign }; + +struct Constructible +{ + Constructible() : construction{Construction::Default} {} + Constructible(const Constructible&) : construction{Construction::Copy} {} + Constructible(Constructible&&) : construction{Construction::Move} {} + Constructible& operator=(const Constructible&) { construction = Construction::CopyAssign; return *this; } + Constructible& operator=(Constructible&&) { construction = Construction::MoveAssign; return *this; } + Construction construction; +}; + +struct DestructorCheck +{ + ~DestructorCheck() { *destructed = true; } + bool* destructed; +}; + +class ArrayVectorTest : QTestSuite +{ + Q_OBJECT + +private slots: + void defaultConstructorTest() + { + // Ensure no elements are constructed + const auto v = ArrayVector(); + // Ensure the container is empty + QVERIFY(v.empty()); + } + + void copyConstructorTest() + { + { + // Ensure all elements are copy constructed + const auto v = ArrayVector{{}}; + const auto copy = v; + for (const auto& element : copy) { + QCOMPARE(element.construction, Construction::Copy); + } + } + { + // Ensure corresponding elements are used + const auto v = ArrayVector{1, 2, 3}; + const auto copy = v; + const auto expected = std::array{1, 2, 3}; + QVERIFY(std::equal(copy.begin(), copy.end(), expected.begin(), expected.end())); + } + } + + void moveConstructorTest() + { + { + // Ensure all elements are move constructed + auto v = ArrayVector{{}}; + const auto moved = std::move(v); + for (const auto& element : moved) { + QCOMPARE(element.construction, Construction::Move); + } + } + { + // Ensure corresponding elements are used + auto v = ArrayVector{1, 2, 3}; + const auto moved = std::move(v); + const auto expected = std::array{1, 2, 3}; + QVERIFY(std::equal(moved.begin(), moved.end(), expected.begin(), expected.end())); + // Move construction should leave the source empty + QVERIFY(v.empty()); + } + } + + void fillValueConstructorTest() + { + // Ensure all elements are copy constructed + const auto v = ArrayVector(1, {}); + for (const auto& element : v) { + QCOMPARE(element.construction, Construction::Copy); + } + // Ensure the container has the correct size + QCOMPARE(v.size(), std::size_t{1}); + } + + void fillDefaultConstructorTest() + { + // Ensure all elements are copy constructed + const auto v = ArrayVector(1); + for (const auto& element : v) { + QCOMPARE(element.construction, Construction::Default); + } + // Ensure the container has the correct size + QCOMPARE(v.size(), std::size_t{1}); + } + + void rangeConstructorTest() + { + { + // Ensure the elements are copy constructed from normal iterators + const auto data = std::array{Constructible{}}; + const auto v = ArrayVector(data.begin(), data.end()); + for (const auto& element : v) { + QCOMPARE(element.construction, Construction::Copy); + } + } + { + // Ensure the elements are move constructed from move iterators + auto data = std::array{Constructible{}}; + const auto v = ArrayVector( + std::move_iterator{data.begin()}, std::move_iterator{data.end()}); + for (const auto& element : v) { + QCOMPARE(element.construction, Construction::Move); + } + } + { + // Ensure corresponding elements are used + const auto data = std::array{1, 2, 3}; + const auto v = ArrayVector(data.begin(), data.end()); + QVERIFY(std::equal(v.begin(), v.end(), data.begin(), data.end())); + } + } + + void initializerListConstructorTest() + { + // Ensure the container is constructed with the correct data + const auto v = ArrayVector{1, 2, 3}; + const auto expected = std::array{1, 2, 3}; + QVERIFY(std::equal(v.begin(), v.end(), expected.begin(), expected.end())); + } + + void destructorTest() + { + { + // Should not call destructors for space without elements + const auto v = ArrayVector{}; + } + { + // Should call destructors for all elements + auto destructed = false; + { + const auto v = ArrayVector{{&destructed}}; + } + QVERIFY(destructed); + } + } + + void copyAssignmentTest() + { + { + // Self-assignment should not change the contents + auto v = ArrayVector{1, 2, 3}; + const auto oldValue = v; + v = v; + QCOMPARE(v, oldValue); + } + { + // Assignment to a larger container should copy assign + const auto src = ArrayVector(3); + auto dst = ArrayVector(5); + dst = src; + QCOMPARE(dst.size(), std::size_t{3}); + for (const auto& element : dst) { + QCOMPARE(element.construction, Construction::CopyAssign); + } + } + { + // Assignment to a smaller container should copy construct + const auto src = ArrayVector(3); + auto dst = ArrayVector{}; + dst = src; + QCOMPARE(dst.size(), std::size_t{3}); + for (const auto& element : dst) { + QCOMPARE(element.construction, Construction::Copy); + } + } + { + // Ensure corresponding elements are used + const auto src = ArrayVector{1, 2, 3}; + auto dst = ArrayVector{}; + dst = src; + QCOMPARE(dst, (ArrayVector{1, 2, 3})); + } + } + + void moveAssignmentTest() + { + { + // Self-assignment should not change the contents + auto v = ArrayVector{1, 2, 3}; + const auto oldValue = v; + v = std::move(v); + QCOMPARE(v, oldValue); + } + { + // Assignment to a larger container should move assign + auto src = ArrayVector(3); + auto dst = ArrayVector(5); + dst = std::move(src); + QCOMPARE(dst.size(), std::size_t{3}); + for (const auto& element : dst) { + QCOMPARE(element.construction, Construction::MoveAssign); + } + } + { + // Assignment to a smaller container should move construct + auto src = ArrayVector(3); + auto dst = ArrayVector{}; + dst = std::move(src); + QCOMPARE(dst.size(), std::size_t{3}); + for (const auto& element : dst) { + QCOMPARE(element.construction, Construction::Move); + } + } + { + // Ensure corresponding elements are used + auto src = ArrayVector{1, 2, 3}; + auto dst = ArrayVector{}; + dst = std::move(src); + QCOMPARE(dst, (ArrayVector{1, 2, 3})); + } + } + + void initializerListAssignmentTest() + { + { + // Assignment to a larger container should copy assign + auto v = ArrayVector(2); + v = {Constructible{}}; + QCOMPARE(v.size(), std::size_t{1}); + for (const auto& element : v) { + QCOMPARE(element.construction, Construction::CopyAssign); + } + } + { + // Assignment to a smaller container should copy construct + auto v = ArrayVector{}; + v = {Constructible{}}; + QCOMPARE(v.size(), std::size_t{1}); + for (const auto& element : v) { + QCOMPARE(element.construction, Construction::Copy); + } + } + { + // Ensure corresponding elements are used + auto v = ArrayVector{}; + v = {1, 2, 3}; + QCOMPARE(v, (ArrayVector{1, 2, 3})); + } + } + + void fillValueAssignTest() + { + { + // Assignment to a larger container should copy assign + auto v = ArrayVector(5); + v.assign(3, {}); + QCOMPARE(v.size(), std::size_t{3}); + for (const auto& element : v) { + QCOMPARE(element.construction, Construction::CopyAssign); + } + } + { + // Assignment to a smaller container should copy construct + auto v = ArrayVector{}; + v.assign(3, {}); + QCOMPARE(v.size(), std::size_t{3}); + for (const auto& element : v) { + QCOMPARE(element.construction, Construction::Copy); + } + } + { + // Ensure correct value is filled + auto v = ArrayVector{}; + v.assign(3, 1); + QCOMPARE(v, (ArrayVector{1, 1, 1})); + } + } + + void rangeAssignTest() + { + { + // Assignment to a larger container should copy assign + const auto data = std::array{Constructible{}}; + auto v = ArrayVector(2); + v.assign(data.begin(), data.end()); + QCOMPARE(v.size(), std::size_t{1}); + for (const auto& element : v) { + QCOMPARE(element.construction, Construction::CopyAssign); + } + } + { + // Assignment to a smaller container should copy construct + const auto data = std::array{Constructible{}}; + auto v = ArrayVector{}; + v.assign(data.begin(), data.end()); + QCOMPARE(v.size(), std::size_t{1}); + for (const auto& element : v) { + QCOMPARE(element.construction, Construction::Copy); + } + } + { + // Ensure correct value is filled + const auto data = std::array{1, 2, 3}; + auto v = ArrayVector{}; + v.assign(data.begin(), data.end()); + QCOMPARE(v, (ArrayVector{1, 2, 3})); + } + } + + void atTest() + { + { + // Non-const version + auto v = ArrayVector{1, 2, 3}; + QCOMPARE(v.at(1), 2); + QVERIFY_EXCEPTION_THROWN(v.at(3), std::out_of_range); + } + { + // Const version + const auto v = ArrayVector{1, 2, 3}; + QCOMPARE(v.at(1), 2); + QVERIFY_EXCEPTION_THROWN(v.at(3), std::out_of_range); + } + } + + void subscriptTest() + { + { + // Non-const version + auto v = ArrayVector{1, 2, 3}; + QCOMPARE(v[1], 2); + } + { + // Const version + const auto v = ArrayVector{1, 2, 3}; + QCOMPARE(v[1], 2); + } + } + + void frontTest() + { + { + // Non-const version + auto v = ArrayVector{1, 2, 3}; + QCOMPARE(v.front(), 1); + } + { + // Const version + const auto v = ArrayVector{1, 2, 3}; + QCOMPARE(v.front(), 1); + } + } + + void backTest() + { + { + // Non-const version + auto v = ArrayVector{1, 2, 3}; + QCOMPARE(v.back(), 3); + } + { + // Const version + const auto v = ArrayVector{1, 2, 3}; + QCOMPARE(v.back(), 3); + } + } + + void dataTest() + { + { + // Non-const version + auto v = ArrayVector{1, 2, 3}; + QCOMPARE(v.data(), &v.front()); + } + { + // Const version + const auto v = ArrayVector{1, 2, 3}; + QCOMPARE(v.data(), &v.front()); + } + } + + void beginEndTest() + { + const auto expected = std::array{1, 2, 3}; + { + // Non-const version + auto v = ArrayVector{1, 2, 3}; + QVERIFY(std::equal(v.begin(), v.end(), expected.begin(), expected.end())); + QVERIFY(std::equal(v.cbegin(), v.cend(), expected.begin(), expected.end())); + } + { + // Const version + const auto v = ArrayVector{1, 2, 3}; + QVERIFY(std::equal(v.begin(), v.end(), expected.begin(), expected.end())); + } + } + + void rbeginRendTest() + { + const auto expected = std::array{3, 2, 1}; + { + // Non-const version + auto v = ArrayVector{1, 2, 3}; + QVERIFY(std::equal(v.rbegin(), v.rend(), expected.begin(), expected.end())); + QVERIFY(std::equal(v.crbegin(), v.crend(), expected.begin(), expected.end())); + } + { + // Const version + const auto v = ArrayVector{1, 2, 3}; + QVERIFY(std::equal(v.rbegin(), v.rend(), expected.begin(), expected.end())); + } + } + + void emptyFullSizeMaxCapacityTest() + { + auto v = ArrayVector{}; + QVERIFY(v.empty()); + QVERIFY(!v.full()); + QCOMPARE(v.size(), std::size_t{0}); + QCOMPARE(v.max_size(), std::size_t{2}); + QCOMPARE(v.capacity(), std::size_t{2}); + + v.push_back(1); + QVERIFY(!v.empty()); + QVERIFY(!v.full()); + QCOMPARE(v.size(), std::size_t{1}); + QCOMPARE(v.max_size(), std::size_t{2}); + QCOMPARE(v.capacity(), std::size_t{2}); + + v.push_back(2); + QVERIFY(!v.empty()); + QVERIFY(v.full()); + QCOMPARE(v.size(), std::size_t{2}); + QCOMPARE(v.max_size(), std::size_t{2}); + QCOMPARE(v.capacity(), std::size_t{2}); + + auto empty = ArrayVector{}; + QVERIFY(empty.empty()); + QVERIFY(empty.full()); + QCOMPARE(empty.size(), std::size_t{0}); + QCOMPARE(empty.max_size(), std::size_t{0}); + QCOMPARE(empty.capacity(), std::size_t{0}); + } + + void insertValueTest() + { + { + // Copy + const auto data = Constructible{}; + auto v = ArrayVector{}; + v.insert(v.cbegin(), data); + QCOMPARE(v.size(), std::size_t{1}); + QCOMPARE(v[0].construction, Construction::Copy); + } + { + // Move + auto v = ArrayVector{}; + v.insert(v.cbegin(), Constructible{}); + QCOMPARE(v.size(), std::size_t{1}); + QCOMPARE(v[0].construction, Construction::Move); + } + { + // Ensure the correct value is used (copy) + const auto data = 1; + auto v = ArrayVector{2, 3}; + v.insert(v.cbegin(), data); + QCOMPARE(v, (ArrayVector{1, 2, 3})); + } + { + // Ensure the correct value is used (move) + auto v = ArrayVector{2, 3}; + v.insert(v.cbegin(), 1); + QCOMPARE(v, (ArrayVector{1, 2, 3})); + } + } + + void insertFillValueTest() + { + { + // Insertion should copy construct + auto v = ArrayVector{}; + v.insert(v.cbegin(), 3, {}); + QCOMPARE(v.size(), std::size_t{3}); + for (const auto& element : v) { + QCOMPARE(element.construction, Construction::Copy); + } + } + { + // Ensure correct value is filled + auto v = ArrayVector{1, 3}; + v.insert(v.cbegin() + 1, 3, 2); + QCOMPARE(v, (ArrayVector{1, 2, 2, 2, 3})); + } + } + + void insertRangeTest() + { + { + // Insertion should copy construct + const auto data = std::array{Constructible{}}; + auto v = ArrayVector{}; + v.insert(v.cbegin(), data.begin(), data.end()); + QCOMPARE(v.size(), std::size_t{1}); + for (const auto& element : v) { + QCOMPARE(element.construction, Construction::Copy); + } + } + { + // Ensure correct value is filled + const auto data = std::array{2, 3}; + auto v = ArrayVector{1, 4}; + v.insert(v.cbegin() + 1, data.begin(), data.end()); + QCOMPARE(v, (ArrayVector{1, 2, 3, 4})); + } + } + + void insertInitializerListTest() + { + { + // Insertion should copy construct + auto v = ArrayVector{}; + v.insert(v.cbegin(), {Constructible{}}); + QCOMPARE(v.size(), std::size_t{1}); + for (const auto& element : v) { + QCOMPARE(element.construction, Construction::Copy); + } + } + { + // Ensure corresponding elements are used + auto v = ArrayVector{1, 4}; + v.insert(v.cbegin() + 1, {2, 3}); + QCOMPARE(v, (ArrayVector{1, 2, 3, 4})); + } + } + + void emplaceTest() + { + { + // Ensure the value is constructed in-place + auto v = ArrayVector{}; + v.emplace(v.cbegin()); + QCOMPARE(v.size(), std::size_t{1}); + QCOMPARE(v[0].construction, Construction::Default); + } + { + // Ensure the correct value is used (move) + auto v = ArrayVector{2, 3}; + v.emplace(v.cbegin(), 1); + QCOMPARE(v, (ArrayVector{1, 2, 3})); + } + } + + void eraseTest() + { + { + // Ensure destructors are run + auto destructed = false; + auto v = ArrayVector{{&destructed}}; + v.erase(v.cbegin()); + QVERIFY(destructed); + } + { + // Ensure the result is correct + auto v = ArrayVector{10, 1, 2, 3}; + v.erase(v.cbegin()); + QCOMPARE(v, (ArrayVector{1, 2, 3})); + } + } + + void eraseRangeTest() + { + { + // Ensure destructors are run + auto destructed = false; + auto v = ArrayVector{{&destructed}}; + v.erase(v.cbegin(), v.cend()); + QVERIFY(destructed); + } + { + // Ensure the result is correct + auto v = ArrayVector{1, 20, 21, 2, 3}; + v.erase(v.cbegin() + 1, v.cbegin() + 3); + QCOMPARE(v, (ArrayVector{1, 2, 3})); + } + } + + void pushBackTest() + { + { + // Copy + const auto data = Constructible{}; + auto v = ArrayVector{}; + v.push_back(data); + QCOMPARE(v.size(), std::size_t{1}); + QCOMPARE(v[0].construction, Construction::Copy); + } + { + // Move + auto v = ArrayVector{}; + v.push_back({}); + QCOMPARE(v.size(), std::size_t{1}); + QCOMPARE(v[0].construction, Construction::Move); + } + { + // Ensure the correct value is used (copy) + const auto data = 3; + auto v = ArrayVector{1, 2}; + v.push_back(data); + QCOMPARE(v, (ArrayVector{1, 2, 3})); + } + { + // Ensure the correct value is used (move) + auto v = ArrayVector{1, 2}; + v.push_back(3); + QCOMPARE(v, (ArrayVector{1, 2, 3})); + } + } + + void emplaceBackTest() + { + { + // Ensure the value is constructed in-place + auto v = ArrayVector{}; + v.emplace_back(); + QCOMPARE(v.size(), std::size_t{1}); + QCOMPARE(v[0].construction, Construction::Default); + } + { + // Ensure the correct value is used (move) + auto v = ArrayVector{1, 2}; + v.emplace_back(3); + QCOMPARE(v, (ArrayVector{1, 2, 3})); + } + } + + void popBackTest() + { + { + // Ensure destructors are run + auto destructed = false; + auto v = ArrayVector{{&destructed}}; + v.pop_back(); + QVERIFY(destructed); + } + { + // Ensure the result is correct + auto v = ArrayVector{1, 2, 3}; + v.pop_back(); + QCOMPARE(v, (ArrayVector{1, 2})); + } + } + + void resizeDefaultTest() + { + { + // Smaller + auto destructed = false; + auto v = ArrayVector{{&destructed}}; + QCOMPARE(v.size(), std::size_t{1}); + v.resize(0); + QCOMPARE(v.size(), std::size_t{0}); + QVERIFY(destructed); + } + { + // Bigger + auto v = ArrayVector{}; + QCOMPARE(v.size(), std::size_t{0}); + v.resize(1); + QCOMPARE(v.size(), std::size_t{1}); + QCOMPARE(v[0].construction, Construction::Default); + } + { + // Too big + auto v = ArrayVector{}; + QVERIFY_EXCEPTION_THROWN(v.resize(2), std::length_error); + } + } + + void resizeValueTest() + { + { + // Smaller + auto dummy = false; + auto destructed = false; + auto v = ArrayVector{{&destructed}}; + QCOMPARE(v.size(), std::size_t{1}); + v.resize(0, {&dummy}); + QCOMPARE(v.size(), std::size_t{0}); + QVERIFY(destructed); + } + { + // Bigger + auto v = ArrayVector{}; + QCOMPARE(v.size(), std::size_t{0}); + v.resize(1, {}); + QCOMPARE(v.size(), std::size_t{1}); + QCOMPARE(v[0].construction, Construction::Copy); + } + { + // Too big + auto v = ArrayVector{}; + QVERIFY_EXCEPTION_THROWN(v.resize(2), std::length_error); + } + { + // Ensure the correct value is used + auto v = ArrayVector{}; + v.resize(1, 1); + QCOMPARE(v, (ArrayVector{1})); + } + } + + void clearTest() + { + { + // Ensure destructors are run + auto destructed = false; + auto v = ArrayVector{{&destructed}}; + v.clear(); + QVERIFY(destructed); + } + { + // Ensure the result is correct + auto v = ArrayVector{1, 2, 3}; + v.clear(); + QCOMPARE(v, (ArrayVector{})); + } + } + + void memberSwapTest() + { + auto a = ArrayVector{1, 2, 3, 4}; + auto b = ArrayVector{2, 4, 6}; + + const auto aOriginal = a; + const auto bOriginal = b; + + a.swap(b); + + QCOMPARE(a, bOriginal); + QCOMPARE(b, aOriginal); + } + + void freeSwapTest() + { + auto a = ArrayVector{1, 2, 3, 4}; + auto b = ArrayVector{2, 4, 6}; + + const auto aOriginal = a; + const auto bOriginal = b; + + swap(a, b); + + QCOMPARE(a, bOriginal); + QCOMPARE(b, aOriginal); + } + + void comparisonTest() + { + const auto v = ArrayVector{1, 2, 3}; + const auto l = ArrayVector{1, 2, 2}; + const auto e = ArrayVector{1, 2, 3}; + const auto g = ArrayVector{1, 3, 3}; + + QVERIFY(l < v); + QVERIFY(!(e < v)); + QVERIFY(!(g < v)); + + QVERIFY(l <= v); + QVERIFY(e <= v); + QVERIFY(!(g <= v)); + + QVERIFY(!(l > v)); + QVERIFY(!(e > v)); + QVERIFY(g > v); + + QVERIFY(!(l >= v)); + QVERIFY(e >= v); + QVERIFY(g >= v); + + QVERIFY(!(l == v)); + QVERIFY(e == v); + QVERIFY(!(g == v)); + + QVERIFY(l != v); + QVERIFY(!(e != v)); + QVERIFY(g != v); + } +} ArrayVectorTests; + +#include "ArrayVectorTest.moc" diff --git a/tests/src/core/MathTest.cpp b/tests/src/core/MathTest.cpp new file mode 100644 index 00000000000..2b6404cfd5c --- /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" From 58206f810ddc897454720c76465df99120e89285 Mon Sep 17 00:00:00 2001 From: dan-giddins Date: Sun, 24 Sep 2023 18:51:01 +0100 Subject: [PATCH 94/95] Update resid --- plugins/Sid/resid | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/Sid/resid b/plugins/Sid/resid index 40443cc903d..02288e92da2 160000 --- a/plugins/Sid/resid +++ b/plugins/Sid/resid @@ -1 +1 @@ -Subproject commit 40443cc903dddcb44b1de8ccf2b1e37202ba34d5 +Subproject commit 02288e92da2e8563c7eb428ee6fccb4543fbf756 From 6d4f3b67488b7b3231796bc5fab640ee78963772 Mon Sep 17 00:00:00 2001 From: dan-giddins Date: Sun, 24 Sep 2023 19:03:03 +0100 Subject: [PATCH 95/95] Update resid --- plugins/Sid/resid | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/Sid/resid b/plugins/Sid/resid index 02288e92da2..02afcc5cefa 160000 --- a/plugins/Sid/resid +++ b/plugins/Sid/resid @@ -1 +1 @@ -Subproject commit 02288e92da2e8563c7eb428ee6fccb4543fbf756 +Subproject commit 02afcc5cefac34bd0c665dc0fa6b748d238c1831