diff --git a/.gitmodules b/.gitmodules index 56b7f3eabb3..8a9871b7a7b 100644 --- a/.gitmodules +++ b/.gitmodules @@ -37,3 +37,6 @@ [submodule "src/3rdparty/ringbuffer"] path = src/3rdparty/ringbuffer url = https://github.com/JohannesLorenz/ringbuffer.git +[submodule "plugins/carlabase/carla"] + path = plugins/carlabase/carla + url = https://github.com/falktx/carla diff --git a/.travis.yml b/.travis.yml index f94aa084609..c548adc26b3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,6 @@ language: cpp compiler: gcc -dist: trusty +dist: xenial sudo: required cache: directories: diff --git a/.travis/linux..before_install.sh b/.travis/linux..before_install.sh index c2e578b5422..9bf8aac01da 100755 --- a/.travis/linux..before_install.sh +++ b/.travis/linux..before_install.sh @@ -2,10 +2,7 @@ set -e -sudo add-apt-repository ppa:beineri/opt-qt592-trusty -y -sudo add-apt-repository ppa:andrewrk/libgroove -y -sudo sed -e "s/trusty/precise/" -i \ - /etc/apt/sources.list.d/andrewrk-libgroove-trusty.list +sudo add-apt-repository ppa:beineri/opt-qt592-xenial -y sudo dpkg --add-architecture i386 sudo apt-get update -qq || true diff --git a/.travis/linux..install.sh b/.travis/linux..install.sh index 2f1262d071b..fd2b79d948e 100755 --- a/.travis/linux..install.sh +++ b/.travis/linux..install.sh @@ -4,7 +4,7 @@ set -e PACKAGES="cmake libsndfile-dev fftw3-dev libvorbis-dev libogg-dev libmp3lame-dev libasound2-dev libjack-jackd2-dev libsdl-dev libsamplerate0-dev libstk0-dev stk - libfluidsynth-dev portaudio19-dev g++-multilib libfltk1.3-dev + libfluidsynth-dev portaudio19-dev g++-multilib libfltk1.3-dev fluid libgig-dev libsoundio-dev qt59base qt59translations qt59tools" # swh build dependencies @@ -18,9 +18,3 @@ PACKAGES="$PACKAGES $SWH_PACKAGES $VST_PACKAGES libjack-jackd2-0" # shellcheck disable=SC2086 sudo apt-get install -y $PACKAGES - -# kxstudio repo offers Carla; avoid package conflicts (wine, etc) by running last -sudo add-apt-repository -y ppa:kxstudio-debian/libs -sudo add-apt-repository -y ppa:kxstudio-debian/apps -sudo apt-get update -sudo apt-get install -y carla diff --git a/.travis/script.sh b/.travis/script.sh index b723f5dd0e4..a6d87b37f0d 100755 --- a/.travis/script.sh +++ b/.travis/script.sh @@ -22,7 +22,7 @@ else "$TRAVIS_BUILD_DIR/.travis/$TRAVIS_OS_NAME.$TARGET_OS.script.sh" # Package and upload non-tagged builds - if [ ! -z "$TRAVIS_TAG" ]; then + if [ -n "$TRAVIS_TAG" ]; then # Skip, handled by travis deploy instead exit 0 elif [[ $TARGET_OS == win* ]]; then diff --git a/CMakeLists.txt b/CMakeLists.txt index 2f310d27c17..fbf37852e39 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -229,7 +229,8 @@ IF(WANT_CARLA) SET(LMMS_HAVE_CARLA TRUE) SET(STATUS_CARLA "OK") ELSE(CARLA_FOUND) - SET(STATUS_CARLA "not found, please install the latest carla") + SET(LMMS_HAVE_WEAKCARLA TRUE) + SET(STATUS_CARLA "OK (weak linking enabled)") ENDIF(CARLA_FOUND) ENDIF(WANT_CARLA) @@ -499,7 +500,7 @@ IF(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang") # Due to a regression in gcc-4.8.X, we need to disable array-bounds check IF (CMAKE_COMPILER_IS_GNUCXX AND ((CMAKE_CXX_COMPILER_VERSION VERSION_EQUAL "4.8.0") OR (CMAKE_CXX_COMPILER_VERSION VERSION_GREATER "4.8.0") OR LMMS_BUILD_WIN32)) - SET(WERROR_FLAGS "${WERROR_FLAGS} -Wno-array-bounds") + SET(WERROR_FLAGS "${WERROR_FLAGS} -Wno-array-bounds -Wno-attributes") ENDIF() ELSEIF(MSVC) # Remove any existing /W flags diff --git a/cmake/install/CMakeLists.txt b/cmake/install/CMakeLists.txt index a3a81beebda..e123ae384fe 100644 --- a/cmake/install/CMakeLists.txt +++ b/cmake/install/CMakeLists.txt @@ -31,7 +31,8 @@ IF(LMMS_BUILD_WIN32 OR LMMS_INSTALL_DEPENDENCIES) NAME "plugins" TARGETS ${PLUGINS_BUILT} DESTINATION ${PLUGIN_DEP_DESTINATION} - LIB_DIRS ${LIB_DIRS} "${PLUGIN_DIR}" + LIB_DIRS ${LIB_DIRS} "${PLUGIN_DIR}" "${PLUGIN_DIR}/optional" + SEARCH_PATH "${PLUGIN_DIR}" "${PLUGIN_DIR}/optional" ) ENDIF() diff --git a/cmake/linux/launch_lmms.sh b/cmake/linux/launch_lmms.sh new file mode 100644 index 00000000000..198b5711a53 --- /dev/null +++ b/cmake/linux/launch_lmms.sh @@ -0,0 +1,24 @@ +#!/usr/bin/env bash +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +export PATH="$PATH:/sbin" +if command -v carla > /dev/null 2>&1; then + CARLAPATH="$(command -v carla)" + CARLAPREFIX="${CARLAPATH%/bin*}" + echo "Carla appears to be installed on this system at $CARLAPREFIX/lib[64]/carla so we'll use it." + export LD_LIBRARY_PATH=$CARLAPREFIX/lib/carla:$CARLAPREFIX/lib64/carla:$LD_LIBRARY_PATH +else + echo "Carla does not appear to be installed. That's OK, please ignore any related library errors." +fi +export LD_LIBRARY_PATH=$DIR/usr/lib/:$DIR/usr/lib/lmms:$LD_LIBRARY_PATH +# Prevent segfault on VirualBox +if lsmod |grep vboxguest > /dev/null 2>&1; then + echo "VirtualBox detected. Forcing libgl software rendering." + export LIBGL_ALWAYS_SOFTWARE=1; +fi +if ldconfig -p | grep libjack.so.0 > /dev/null 2>&1; then + echo "Jack appears to be installed on this system, so we'll use it." +else + echo "Jack does not appear to be installed. That's OK, we'll use a dummy version instead." + export LD_LIBRARY_PATH=$DIR/usr/lib/lmms/optional:$LD_LIBRARY_PATH +fi +QT_X11_NO_NATIVE_MENUBAR=1 "$DIR"/usr/bin/lmms.real "$@" diff --git a/cmake/linux/package_linux.sh.in b/cmake/linux/package_linux.sh.in index 9f233d4017b..a1f9ff8658f 100644 --- a/cmake/linux/package_linux.sh.in +++ b/cmake/linux/package_linux.sh.in @@ -99,33 +99,8 @@ cp -R /usr/share/stk/rawwaves/ "${APPDIR}usr/share/stk/" # Create a wrapper script which calls the lmms executable mv "${APPDIR}usr/bin/lmms" "${APPDIR}usr/bin/lmms.real" -# shellcheck disable=SC1083 -cat >"${APPDIR}usr/bin/lmms" < /dev/null 2>&1; then - CARLAPATH="\$(which carla)" - CARLAPREFIX="\${CARLAPATH%/bin*}" - echo "Carla appears to be installed on this system at \$CARLAPREFIX/lib[64]/carla so we'll use it." - export LD_LIBRARY_PATH=\$CARLAPREFIX/lib/carla:\$CARLAPREFIX/lib64/carla:\$LD_LIBRARY_PATH -else - echo "Carla does not appear to be installed. That's OK, please ignore any related library errors." -fi -export LD_LIBRARY_PATH=\$DIR/usr/lib/:\$DIR/usr/lib/lmms:\$LD_LIBRARY_PATH -# Prevent segfault on VirualBox -if lsmod |grep vboxguest > /dev/null 2>&1; then - echo "VirtualBox detected. Forcing libgl software rendering." - export LIBGL_ALWAYS_SOFTWARE=1; -fi -if ldconfig -p | grep libjack.so.0 > /dev/null 2>&1; then - echo "Jack appears to be installed on this system, so we'll use it." -else - echo "Jack does not appear to be installed. That's OK, we'll use a dummy version instead." - export LD_LIBRARY_PATH=\$DIR/usr/lib/lmms/optional:\$LD_LIBRARY_PATH -fi -QT_X11_NO_NATIVE_MENUBAR=1 \$DIR/usr/bin/lmms.real "\$@" -EOL + +cp "@CMAKE_CURRENT_SOURCE_DIR@/launch_lmms.sh" "${APPDIR}usr/bin/lmms" chmod +x "${APPDIR}usr/bin/lmms" @@ -133,15 +108,7 @@ chmod +x "${APPDIR}usr/bin/lmms" unset LD_LIBRARY_PATH # Ensure linuxdeployqt can find shared objects -export LD_LIBRARY_PATH="${APPDIR}usr/lib/lmms/":$LD_LIBRARY_PATH - -# Handle wine linking -if [ -d "@WINE_32_LIBRARY_DIR@" ]; then - export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:@WINE_32_LIBRARY_DIRS@ -fi -if [ -d "@WINE_64_LIBRARY_DIR@" ]; then - export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:@WINE_64_LIBRARY_DIRS@ -fi +export LD_LIBRARY_PATH="${APPDIR}"usr/lib/lmms/:"${APPDIR}"usr/lib/lmms/optional:"$LD_LIBRARY_PATH" # Move executables so linuxdeployqt can find them ZYNLIB="${APPDIR}usr/lib/lmms/RemoteZynAddSubFx" @@ -153,8 +120,18 @@ VSTBIN32="${APPDIR}usr/bin/RemoteVstPlugin32.exe.so" VSTBIN64="${APPDIR}usr/bin/RemoteVstPlugin64.exe.so" mv "$ZYNLIB" "$ZYNBIN" -mv "$VSTLIB32" "$VSTBIN32" -mv "$VSTLIB64" "$VSTBIN64" +mv "$VSTLIB32" "$VSTBIN32" || true +mv "$VSTLIB64" "$VSTBIN64" || true + +# Handle wine linking +if [ -d "@WINE_32_LIBRARY_DIR@" ] && \ + ldd "$VSTBIN32" | grep "libwine\.so" | grep "not found"; then + export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:"@WINE_32_LIBRARY_DIRS@" +fi +if [ -d "@WINE_64_LIBRARY_DIR@" ] && \ + ldd "$VSTBIN64" | grep "libwine\.so" | grep "not found"; then + export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:"@WINE_64_LIBRARY_DIRS@" +fi # Patch the desktop file sed -i 's/.*Exec=.*/Exec=lmms.real/' "$DESKTOPFILE" @@ -182,14 +159,15 @@ success "Bundled and relinked dependencies" # Link to original location so lmms can find them ln -sr "$ZYNBIN" "$ZYNLIB" -ln -sr "$VSTBIN32" "$VSTLIB32" -ln -sr "$VSTBIN64" "$VSTLIB64" +ln -sr "$VSTBIN32" "$VSTLIB32" || true +ln -sr "$VSTBIN64" "$VSTLIB64" || true # Remove wine library conflict rm -f "${APPDIR}/usr/lib/libwine.so.1" # Use system-provided carla rm -f "${APPDIR}usr/lib/"libcarla*.so +rm -f "${APPDIR}usr/lib/lmms/optional/"libcarla*.so # Remove bundled jack in LD_LIBRARY_PATH if exists if [ -e "${APPDIR}/usr/lib/libjack.so.0" ]; then diff --git a/cmake/modules/CheckSubmodules.cmake b/cmake/modules/CheckSubmodules.cmake index 32a2f995181..f45885cc6fb 100644 --- a/cmake/modules/CheckSubmodules.cmake +++ b/cmake/modules/CheckSubmodules.cmake @@ -100,11 +100,11 @@ ENDFOREACH() # Once called, status is stored in GIT_RESULT respectively. # Note: Git likes to write to stderr. Don't assume stderr is error; Check GIT_RESULT instead. -MACRO(GIT_SUBMODULE SUBMODULE_PATH FORCE_DEINIT FORCE_REMOTE FULL_CLONE) +MACRO(GIT_SUBMODULE SUBMODULE_PATH FORCE_DEINIT FORCE_REMOTE NO_DEPTH) FIND_PACKAGE(Git REQUIRED) # Handle missing commits SET(FORCE_REMOTE_FLAG "${FORCE_REMOTE}") - SET(FULL_CLONE_FLAG "${FULL_CLONE}") + SET(NO_DEPTH_FLAG "${NO_DEPTH}") IF(FORCE_REMOTE_FLAG) MESSAGE("-- Adding remote submodulefix to ${SUBMODULE_PATH}") EXECUTE_PROCESS( @@ -115,7 +115,7 @@ MACRO(GIT_SUBMODULE SUBMODULE_PATH FORCE_DEINIT FORCE_REMOTE FULL_CLONE) OUTPUT_QUIET ERROR_QUIET ) # Recurse - GIT_SUBMODULE(${SUBMODULE_PATH} false false ${FULL_CLONE_FLAG}) + GIT_SUBMODULE(${SUBMODULE_PATH} false false ${NO_DEPTH_FLAG}) ELSEIF(${FORCE_DEINIT}) MESSAGE("-- Resetting ${SUBMODULE_PATH}") EXECUTE_PROCESS( @@ -125,21 +125,15 @@ MACRO(GIT_SUBMODULE SUBMODULE_PATH FORCE_DEINIT FORCE_REMOTE FULL_CLONE) ) MESSAGE("-- Deleting ${CMAKE_SOURCE_DIR}/.git/${SUBMODULE_PATH}") FILE(REMOVE_RECURSE "${CMAKE_SOURCE_DIR}/.git/modules/${SUBMODULE_PATH}") - # Recurse + # Recurse without depth GIT_SUBMODULE(${SUBMODULE_PATH} false false true) ELSE() # Try to use the depth switch - IF(NO_SHALLOW_CLONE OR GIT_VERSION_STRING VERSION_LESS "1.8.4") + IF(NO_SHALLOW_CLONE OR GIT_VERSION_STRING VERSION_LESS "1.8.4" OR NO_DEPTH_FLAG) # Shallow submodules were introduced in 1.8.4 MESSAGE("-- Fetching ${SUBMODULE_PATH}") SET(DEPTH_CMD "") SET(DEPTH_VAL "") - ELSEIF(FULL_CLONE_FLAG) - # Depth doesn't revert easily... It should be "--no-recommend-shallow" - # but it's ignored by nested submodules, use the highest value instead. - MESSAGE("-- Fetching ${SUBMODULE_PATH}") - SET(DEPTH_CMD "--depth") - SET(DEPTH_VAL "2147483647") ELSE() MESSAGE("-- Fetching ${SUBMODULE_PATH} @ --depth ${DEPTH_VALUE}") SET(DEPTH_CMD "--depth") @@ -201,13 +195,7 @@ FOREACH(_submodule ${SUBMODULE_LIST}) BREAK() ELSEIF("${GIT_MESSAGE}" MATCHES "${_phrase}") MESSAGE("-- Retrying ${_submodule} using 'deinit' (attempt ${COUNTED} of ${MAX_ATTEMPTS})...") - IF(COUNTED LESS 2) - SET(FULL_CLONE false) - ELSE() - SET(FULL_CLONE true) - ENDIF() - - GIT_SUBMODULE("${_submodule}" true false ${FULL_CLONE}) + GIT_SUBMODULE("${_submodule}" true false false) BREAK() ENDIF() ENDFOREACH() diff --git a/cmake/modules/FindWine.cmake b/cmake/modules/FindWine.cmake index 50bf54edbc6..f7f3b0aa647 100644 --- a/cmake/modules/FindWine.cmake +++ b/cmake/modules/FindWine.cmake @@ -11,11 +11,19 @@ MACRO(_findwine_find_flags output expression result) STRING(REPLACE " " ";" WINEBUILD_FLAGS "${output}") FOREACH(FLAG ${WINEBUILD_FLAGS}) IF("${FLAG}" MATCHES "${expression}") - SET(${result} "${FLAG}") + LIST(APPEND ${result} "${FLAG}") ENDIF() ENDFOREACH() ENDMACRO() +MACRO(_regex_replace_foreach EXPRESSION REPLACEMENT RESULT INPUT) + SET(${RESULT} "") + FOREACH(ITEM ${INPUT}) + STRING(REGEX REPLACE "${EXPRESSION}" "${REPLACEMENT}" ITEM "${ITEM}") + LIST(APPEND ${RESULT} "${ITEM}") + ENDFOREACH() +ENDMACRO() + LIST(APPEND CMAKE_PREFIX_PATH /opt/wine-stable /opt/wine-devel /opt/wine-staging /usr/lib/wine/) FIND_PROGRAM(WINE_CXX @@ -31,10 +39,10 @@ IF(WINE_CXX) _findwine_find_flags("${WINEBUILD_OUTPUT_32}" "^-isystem" WINEGCC_INCLUDE_DIR) _findwine_find_flags("${WINEBUILD_OUTPUT_32}" "libwinecrt0\\.a.*" WINECRT_32) _findwine_find_flags("${WINEBUILD_OUTPUT_64}" "libwinecrt0\\.a.*" WINECRT_64) - STRING(REGEX REPLACE "^-isystem" "" WINE_INCLUDE_HINT "${WINEGCC_INCLUDE_DIR}") - STRING(REGEX REPLACE "/wine/windows$" "" WINE_INCLUDE_HINT "${WINE_INCLUDE_HINT}") - STRING(REGEX REPLACE "libwinecrt0\\.a.*" "" WINE_32_LIBRARY_DIR "${WINECRT_32}") - STRING(REGEX REPLACE "libwinecrt0\\.a.*" "" WINE_64_LIBRARY_DIR "${WINECRT_64}") + _regex_replace_foreach("^-isystem" "" WINE_INCLUDE_HINT "${WINEGCC_INCLUDE_DIR}") + _regex_replace_foreach("/wine/windows$" "" WINE_INCLUDE_HINT "${WINE_INCLUDE_HINT}") + STRING(REGEX REPLACE "wine/libwinecrt0\\.a.*" "" WINE_32_LIBRARY_DIR "${WINECRT_32}") + STRING(REGEX REPLACE "wine/libwinecrt0\\.a.*" "" WINE_64_LIBRARY_DIR "${WINECRT_64}") IF(BUGGED_WINEGCC) MESSAGE(WARNING "Your winegcc is unusable due to https://bugs.winehq.org/show_bug.cgi?id=46293,\n @@ -76,7 +84,7 @@ IF(WINE_CXX) ENDIF() FIND_PATH(WINE_INCLUDE_DIR wine/exception.h - HINTS "${WINE_INCLUDE_HINT}" + HINTS ${WINE_INCLUDE_HINT} ) SET(_ARCHITECTURE ${CMAKE_LIBRARY_ARCHITECTURE}) diff --git a/cmake/msys/extract_debs.sh b/cmake/msys/extract_debs.sh index cb2a868d3e9..939912bb2c3 100644 --- a/cmake/msys/extract_debs.sh +++ b/cmake/msys/extract_debs.sh @@ -1,4 +1,6 @@ #!/bin/bash +set -e + ppa_dir=./ppa/ pushd $ppa_dir diff --git a/cmake/msys/msys_helper.sh b/cmake/msys/msys_helper.sh index 294edd476da..a6a7e6aae38 100644 --- a/cmake/msys/msys_helper.sh +++ b/cmake/msys/msys_helper.sh @@ -68,7 +68,7 @@ stkver="4.5.1" mingw_root="/$(echo "$MSYSTEM"|tr '[:upper:]' '[:lower:]')" info "Downloading and building fltk $fltkver" -if ! which fluid; then +if ! command -v fluid; then wget http://fltk.org/pub/fltk/$fltkver/fltk-$fltkver-source.tar.gz -O "$HOME/fltk-source.tar.gz" tar zxf "$HOME/fltk-source.tar.gz" -C "$HOME/" pushd "$HOME/fltk-$fltkver" diff --git a/data/projects/demos/Greippi - Krem Kaakkuja (Second Flight Remix).mmpz b/data/projects/demos/Greippi - Krem Kaakkuja (Second Flight Remix).mmpz index 6c9f3436920..9ea29b34d3f 100644 Binary files a/data/projects/demos/Greippi - Krem Kaakkuja (Second Flight Remix).mmpz and b/data/projects/demos/Greippi - Krem Kaakkuja (Second Flight Remix).mmpz differ diff --git a/debian/control b/debian/control index 880e0d89ad6..63eba44d2e1 100644 --- a/debian/control +++ b/debian/control @@ -28,6 +28,7 @@ Build-Depends: libsoundio-dev, libstk0-dev, libvorbis-dev, + libx11-xcb-dev, libxcb-keysyms1-dev, libxcb-util0-dev, libxml-perl, diff --git a/include/AutomatableModel.h b/include/AutomatableModel.h index 6d8000804f6..25039eb0dc2 100644 --- a/include/AutomatableModel.h +++ b/include/AutomatableModel.h @@ -236,6 +236,7 @@ class LMMS_EXPORT AutomatableModel : public Model, public JournallingObject m_centerValue = centerVal; } + //! link @p m1 and @p m2, let @p m1 take the values of @p m2 static void linkModels( AutomatableModel* m1, AutomatableModel* m2 ); static void unlinkModels( AutomatableModel* m1, AutomatableModel* m2 ); @@ -359,7 +360,7 @@ public slots: template void roundAt( T &value, const T &where ) const; - ScaleType m_scaleType; //! scale type, linear by default + ScaleType m_scaleType; //!< scale type, linear by default float m_value; float m_initValue; float m_minValue; diff --git a/include/FileBrowser.h b/include/FileBrowser.h index 9b56a8dbd52..b84ba5e5403 100644 --- a/include/FileBrowser.h +++ b/include/FileBrowser.h @@ -48,6 +48,14 @@ class FileBrowser : public SideBarWidget { Q_OBJECT public: + /** + Create a file browser side bar widget + @param directories '*'-separated list of directories to search for. + If a directory of factory files should be in the list it + must be the last one (for the factory files delimiter to work) + @param filter Filter as used in QDir::match + @param recurse *to be documented* + */ FileBrowser( const QString & directories, const QString & filter, const QString & title, const QPixmap & pm, QWidget * parent, bool dirs_as_items = false, bool recurse = false ); @@ -69,8 +77,8 @@ private slots: QLineEdit * m_filterEdit; - QString m_directories; - QString m_filter; + QString m_directories; //!< Directories to search, split with '*' + QString m_filter; //!< Filter as used in QDir::match() bool m_dirsAsItems; bool m_recurse; @@ -163,7 +171,14 @@ class Directory : public QTreeWidgetItem static QPixmap * s_folderOpenedPixmap; static QPixmap * s_folderLockedPixmap; + //! Directories that lead here + //! Initially, this is just set to the current path of a directory + //! If, however, you have e.g. 'TripleOscillator/xyz' in two of the + //! file browser's search directories 'a' and 'b', this will have two + //! entries 'a/TripleOscillator' and 'b/TripleOscillator' + //! and 'xyz' in the tree widget QStringList m_directories; + //! Filter as used in QDir::match() QString m_filter; int m_dirCount; diff --git a/include/LcdWidget.h b/include/LcdWidget.h index f4c7d1579e1..bdad5cb302b 100644 --- a/include/LcdWidget.h +++ b/include/LcdWidget.h @@ -100,7 +100,7 @@ public slots: int m_numDigits; int m_marginWidth; - void initUi( const QString& name, const QString &style = QString("19green") ); //!< to be called by ctors + void initUi( const QString& name, const QString &style ); //!< to be called by ctors } ; diff --git a/include/MainWindow.h b/include/MainWindow.h index 5dc10232199..c894ef4f0e4 100644 --- a/include/MainWindow.h +++ b/include/MainWindow.h @@ -125,21 +125,13 @@ class MainWindow : public QMainWindow void clearKeyModifiers(); - bool isCtrlPressed() - { - return m_keyMods.m_ctrl; - } - + // TODO Remove this function, since m_shift can get stuck down. + // [[deprecated]] bool isShiftPressed() { return m_keyMods.m_shift; } - bool isAltPressed() - { - return m_keyMods.m_alt; - } - static void saveWidgetState( QWidget * _w, QDomElement & _de ); static void restoreWidgetState( QWidget * _w, const QDomElement & _de ); diff --git a/include/Track.h b/include/Track.h index 70e49a1c6c3..de622d0fb64 100644 --- a/include/Track.h +++ b/include/Track.h @@ -71,6 +71,8 @@ const int DEFAULT_TRACK_HEIGHT = 32; const int TCO_BORDER_WIDTH = 2; +char const *const FILENAME_FILTER = "[\\0000-\x1f\"*/:<>?\\\\|\x7f]"; + class LMMS_EXPORT TrackContentObject : public Model, public JournallingObject { diff --git a/include/stdshims.h b/include/stdshims.h index 5eee6543cac..c12254e1506 100644 --- a/include/stdshims.h +++ b/include/stdshims.h @@ -5,11 +5,12 @@ #define STDSHIMS_H #include +#include #include #if (__cplusplus >= 201402L || _MSC_VER) #ifndef _MSC_VER -#warning "This file should now be removed! The functions it provides are part of the C++14 standard." +#warning "This part of this file should now be removed! The functions it provides are part of the C++14 standard." #endif using std::make_unique; @@ -30,5 +31,24 @@ std::unique_ptr make_unique(Args&&... args) } #endif +#if (__cplusplus >= 201703L || _MSC_VER >= 1914) +#ifndef _MSC_VER +#warning "This part of this file should now be removed! The functions it provides are part of the C++17 standard." +#endif +using std::as_const; + +#else + +/// Shim for http://en.cppreference.com/w/cpp/utility/as_const +template +constexpr typename std::add_const::type& as_const(T& t) noexcept +{ + return t; +} + +template +void as_const(const T&&) = delete; +#endif + #endif // include guard diff --git a/plugins/HydrogenImport/HydrogenImport.cpp b/plugins/HydrogenImport/HydrogenImport.cpp index 9f9f1dd4961..4ed0a89a3f1 100644 --- a/plugins/HydrogenImport/HydrogenImport.cpp +++ b/plugins/HydrogenImport/HydrogenImport.cpp @@ -202,13 +202,19 @@ bool HydrogenImport::readSong() else { unsigned nLayer = 0; - QDomNode layerNode = instrumentNode.firstChildElement( "layer" ); + QDomNode instrumentComponentNode = instrumentNode.firstChildElement("instrumentComponent"); + if (instrumentComponentNode.isNull()) + { + instrumentComponentNode = instrumentNode; + } + + QDomNode layerNode = instrumentComponentNode.firstChildElement( "layer" ); while ( ! layerNode.isNull() ) { if ( nLayer >= MAX_LAYERS ) { - printf( "nLayer >= MAX_LAYERS" ); - continue; + printf("nLayer >= MAX_LAYERS\n"); + break; } QString sFilename = LocalFileMng::readXmlString( layerNode, "filename", "" ); QString sMode = LocalFileMng::readXmlString( layerNode, "smode", "forward" ); diff --git a/plugins/MidiImport/MidiImport.cpp b/plugins/MidiImport/MidiImport.cpp index b3d01e790dc..3646975a29a 100644 --- a/plugins/MidiImport/MidiImport.cpp +++ b/plugins/MidiImport/MidiImport.cpp @@ -217,8 +217,7 @@ class smfMidiChannel p( NULL ), it_inst( NULL ), isSF2( false ), - hasNotes( false ), - lastEnd( 0 ) + hasNotes( false ) { } InstrumentTrack * it; @@ -226,7 +225,6 @@ class smfMidiChannel Instrument * it_inst; bool isSF2; bool hasNotes; - MidiTime lastEnd; QString trackName; smfMidiChannel * create( TrackContainer* tc, QString tn ) @@ -257,9 +255,11 @@ class smfMidiChannel if( trackName != "") { it->setName( tn ); } - lastEnd = 0; // General MIDI default it->pitchRangeModel()->setInitValue( 2 ); + + // Create a default pattern + p = dynamic_cast(it->createTCO(0)); } return this; } @@ -267,16 +267,37 @@ class smfMidiChannel void addNote( Note & n ) { - if( !p || n.pos() > lastEnd + DefaultTicksPerBar ) + if (!p) { - MidiTime pPos = MidiTime( n.pos().getBar(), 0 ); - p = dynamic_cast( it->createTCO( 0 ) ); - p->movePosition( pPos ); + p = dynamic_cast(it->createTCO(0)); } + p->addNote(n, false); hasNotes = true; - lastEnd = n.pos() + n.length(); - n.setPos( n.pos( p->startPosition() ) ); - p->addNote( n, false ); + } + + void splitPatterns() + { + Pattern * newPattern = nullptr; + MidiTime lastEnd(0); + + p->rearrangeAllNotes(); + for (auto n : p->notes()) + { + if (!newPattern || n->pos() > lastEnd + DefaultTicksPerBar) + { + MidiTime pPos = MidiTime(n->pos().getBar(), 0); + newPattern = dynamic_cast(it->createTCO(0)); + newPattern->movePosition(pPos); + } + lastEnd = n->pos() + n->length(); + + Note newNote(*n); + newNote.setPos(n->pos(newPattern->startPosition())); + newPattern->addNote(newNote, false); + } + + delete p; + p = nullptr; } }; @@ -539,7 +560,11 @@ bool MidiImport::readSMF( TrackContainer* tc ) for( int c=0; c < 256; ++c ) { - if( !chs[c].hasNotes && chs[c].it ) + if (chs[c].hasNotes) + { + chs[c].splitPatterns(); + } + else if (chs[c].it) { printf(" Should remove empty track\n"); // must delete trackView first - but where is it? diff --git a/plugins/carlabase/CMakeLists.txt b/plugins/carlabase/CMakeLists.txt index 0311ca8ee2b..28a1bc88c51 100644 --- a/plugins/carlabase/CMakeLists.txt +++ b/plugins/carlabase/CMakeLists.txt @@ -6,15 +6,45 @@ IF(NOT CMAKE_VERSION VERSION_LESS 3.9) CMAKE_POLICY(SET CMP0068 OLD) ENDIF() -if(LMMS_HAVE_CARLA) +# If Carla was not provided by the system, make a dummy library instead +if(LMMS_HAVE_WEAKCARLA) + SET(CARLA_INCLUDE_DIRS + ${CMAKE_CURRENT_SOURCE_DIR}/carla/source + ${CMAKE_CURRENT_SOURCE_DIR}/carla/source/includes + ${CMAKE_CURRENT_SOURCE_DIR}/carla/source/utils + ${CMAKE_CURRENT_SOURCE_DIR}/carla/source/backend + ) + ADD_LIBRARY(carla_native-plugin SHARED DummyCarla.cpp) + TARGET_INCLUDE_DIRECTORIES(carla_native-plugin PUBLIC ${CARLA_INCLUDE_DIRS}) + INSTALL(TARGETS carla_native-plugin + LIBRARY DESTINATION "${PLUGIN_DIR}/optional" + RUNTIME DESTINATION "${PLUGIN_DIR}/optional" + ) + SET(CARLA_LIBRARIES carla_native-plugin) + # Set parent scope variables so carlarack and carlapatchbay can see them + SET(CARLA_LIBRARIES ${CARLA_LIBRARIES} PARENT_SCOPE) +endif() + +if(LMMS_HAVE_CARLA OR LMMS_HAVE_WEAKCARLA) + # Mimic the missing "config.h" + SET(CARLA_INCLUDE_DIRS ${CARLA_INCLUDE_DIRS} ${CMAKE_CURRENT_SOURCE_DIR}/CarlaConfig) + SET(CARLA_INCLUDE_DIRS ${CARLA_INCLUDE_DIRS} PARENT_SCOPE) + INCLUDE(BuildPlugin) INCLUDE_DIRECTORIES(${CARLA_INCLUDE_DIRS}) LINK_DIRECTORIES(${CARLA_LIBRARY_DIRS}) LINK_LIBRARIES(${CARLA_LIBRARIES}) - BUILD_PLUGIN(carlabase carla.cpp carla.h MOCFILES carla.h EMBEDDED_RESOURCES artwork-patchbay.png artwork-rack.png LINK SHARED) + BUILD_PLUGIN(carlabase carla.cpp carla.h + MOCFILES carla.h + EMBEDDED_RESOURCES artwork-patchbay.png artwork-rack.png + EXPORT_BASE_NAME carlabase + LINK SHARED) SET_TARGET_PROPERTIES(carlabase PROPERTIES SKIP_BUILD_RPATH TRUE BUILD_WITH_INSTALL_RPATH TRUE INSTALL_RPATH_USE_LINK_PATH TRUE INSTALL_RPATH "${CARLA_RPATH}") -endif(LMMS_HAVE_CARLA) + IF(LMMS_HAVE_WEAKCARLA) + ADD_DEPENDENCIES(carlabase carla_native-plugin) + ENDIF() +endif() diff --git a/plugins/carlabase/CarlaConfig/config.h b/plugins/carlabase/CarlaConfig/config.h new file mode 100644 index 00000000000..46b3f34649c --- /dev/null +++ b/plugins/carlabase/CarlaConfig/config.h @@ -0,0 +1,9 @@ +// config.h for Carla +#ifndef FOR_CARLA_CONFIG_H +#define FOR_CARLA_CONFIG_H + +#ifdef _MSC_VER +#define HAVE_CPP11_SUPPORT 1 +#endif + +#endif diff --git a/plugins/carlabase/DummyCarla.cpp b/plugins/carlabase/DummyCarla.cpp new file mode 100644 index 00000000000..8fd9ef6d7e6 --- /dev/null +++ b/plugins/carlabase/DummyCarla.cpp @@ -0,0 +1,13 @@ +// A dummy Carla interface +#define BUILDING_CARLA +#include "CarlaNativePlugin.h" + +CARLA_EXPORT const char* carla_get_library_filename() { return nullptr; } +CARLA_EXPORT const char* carla_get_library_folder() { return nullptr; } +CARLA_EXPORT const NativePluginDescriptor* carla_get_native_rack_plugin() { return nullptr; } +CARLA_EXPORT const NativePluginDescriptor* carla_get_native_patchbay_plugin() { return nullptr; } +CARLA_EXPORT const NativePluginDescriptor* carla_get_native_patchbay16_plugin() { return nullptr; } +CARLA_EXPORT const NativePluginDescriptor* carla_get_native_patchbay32_plugin() { return nullptr; } +CARLA_EXPORT const NativePluginDescriptor* carla_get_native_patchbay64_plugin() { return nullptr; } +CARLA_EXPORT const NativePluginDescriptor* carla_get_native_patchbay_cv_plugin() { return nullptr; } +CARLA_EXPORT CarlaBackend::CarlaEngine* carla_get_native_plugin_engine(const NativePluginDescriptor* desc, NativePluginHandle handle) { return nullptr; } diff --git a/plugins/carlabase/carla b/plugins/carlabase/carla new file mode 160000 index 00000000000..4ac8ff2ef41 --- /dev/null +++ b/plugins/carlabase/carla @@ -0,0 +1 @@ +Subproject commit 4ac8ff2ef412d4ab190d2e285e318b1f339af4ae diff --git a/plugins/carlabase/carla.cpp b/plugins/carlabase/carla.cpp index ba2dd085c49..a6faa4d275d 100644 --- a/plugins/carlabase/carla.cpp +++ b/plugins/carlabase/carla.cpp @@ -333,8 +333,14 @@ void CarlaInstrument::play(sampleFrame* workingBuffer) fTimeInfo.bbt.ticksPerBeat = ticksPerBeat; fTimeInfo.bbt.beatsPerMinute = s->getTempo(); +#ifndef _MSC_VER float buf1[bufsize]; float buf2[bufsize]; +#else + float *buf1 = static_cast(_alloca(bufsize * sizeof(float))); + float *buf2 = static_cast(_alloca(bufsize * sizeof(float))); +#endif + float* rBuf[] = { buf1, buf2 }; std::memset(buf1, 0, sizeof(float)*bufsize); std::memset(buf2, 0, sizeof(float)*bufsize); @@ -438,7 +444,8 @@ PluginView* CarlaInstrument::instantiateView(QWidget* parent) // Disable plugin focus per https://bugreports.qt.io/browse/QTBUG-30181 #ifndef CARLA_OS_MAC if (QWidget* const window = parent->window()) - fHost.uiParentId = window->winId(); + // TODO: Remove cast; Only needed for Qt4 + fHost.uiParentId = (uintptr_t)window->winId(); else #endif fHost.uiParentId = 0; diff --git a/plugins/carlabase/carla.h b/plugins/carlabase/carla.h index 393912a509f..2c683add935 100644 --- a/plugins/carlabase/carla.h +++ b/plugins/carlabase/carla.h @@ -26,10 +26,9 @@ #define CARLA_H #include -#include "plugin_export.h" +#include "carlabase_export.h" #include "CarlaNative.h" -#define REAL_BUILD // FIXME this shouldn't be needed #if CARLA_VERSION_HEX >= 0x010911 #include "CarlaNativePlugin.h" #else @@ -48,7 +47,7 @@ class QPushButton; -class PLUGIN_EXPORT CarlaInstrument : public Instrument +class CARLABASE_EXPORT CarlaInstrument : public Instrument { Q_OBJECT diff --git a/plugins/carlabase/logo.png b/plugins/carlabase/logo.png new file mode 100644 index 00000000000..353d5d718d6 Binary files /dev/null and b/plugins/carlabase/logo.png differ diff --git a/plugins/carlapatchbay/CMakeLists.txt b/plugins/carlapatchbay/CMakeLists.txt index 64a21a605c9..70d3beec780 100644 --- a/plugins/carlapatchbay/CMakeLists.txt +++ b/plugins/carlapatchbay/CMakeLists.txt @@ -1,4 +1,4 @@ -if(LMMS_HAVE_CARLA) +if(LMMS_HAVE_CARLA OR LMMS_HAVE_WEAKCARLA) ADD_DEFINITIONS(-DCARLA_PLUGIN_PATCHBAY -DCARLA_PLUGIN_SYNTH) INCLUDE(BuildPlugin) INCLUDE_DIRECTORIES(${CARLA_INCLUDE_DIRS} "${CMAKE_CURRENT_SOURCE_DIR}/../carlabase") @@ -6,4 +6,4 @@ if(LMMS_HAVE_CARLA) ${CARLA_LIBRARY_DIRS}) LINK_LIBRARIES(carlabase) BUILD_PLUGIN(carlapatchbay carlapatchbay.cpp EMBEDDED_RESOURCES logo.png) -endif(LMMS_HAVE_CARLA) +endif() diff --git a/plugins/carlapatchbay/carlapatchbay.cpp b/plugins/carlapatchbay/carlapatchbay.cpp index ac00630d44d..ad0c1f6aef5 100644 --- a/plugins/carlapatchbay/carlapatchbay.cpp +++ b/plugins/carlapatchbay/carlapatchbay.cpp @@ -25,6 +25,7 @@ #include "carla.h" #include "embed.h" +#include "plugin_export.h" #include "InstrumentTrack.h" extern "C" diff --git a/plugins/carlarack/CMakeLists.txt b/plugins/carlarack/CMakeLists.txt index 1d9cb975a12..ea0262199f2 100644 --- a/plugins/carlarack/CMakeLists.txt +++ b/plugins/carlarack/CMakeLists.txt @@ -1,4 +1,4 @@ -if(LMMS_HAVE_CARLA) +if(LMMS_HAVE_CARLA OR LMMS_HAVE_WEAKCARLA) ADD_DEFINITIONS(-DCARLA_PLUGIN_RACK -DCARLA_PLUGIN_SYNTH) INCLUDE(BuildPlugin) INCLUDE_DIRECTORIES(${CARLA_INCLUDE_DIRS} "${CMAKE_CURRENT_SOURCE_DIR}/../carlabase") @@ -6,4 +6,4 @@ if(LMMS_HAVE_CARLA) ${CARLA_LIBRARY_DIRS}) LINK_LIBRARIES(carlabase) BUILD_PLUGIN(carlarack carlarack.cpp EMBEDDED_RESOURCES logo.png) -endif(LMMS_HAVE_CARLA) +endif() diff --git a/plugins/carlarack/carlarack.cpp b/plugins/carlarack/carlarack.cpp index c0a39f9c258..ee2a788354f 100644 --- a/plugins/carlarack/carlarack.cpp +++ b/plugins/carlarack/carlarack.cpp @@ -25,6 +25,7 @@ #include "carla.h" #include "embed.h" +#include "plugin_export.h" #include "InstrumentTrack.h" extern "C" diff --git a/plugins/stk/mallets/mallets.cpp b/plugins/stk/mallets/mallets.cpp index f770d1f5827..f9e2e7ede56 100644 --- a/plugins/stk/mallets/mallets.cpp +++ b/plugins/stk/mallets/mallets.cpp @@ -338,6 +338,7 @@ void malletsInstrument::playNote( NotePlayHandle * _n, Engine::mixer()->processingSampleRate() ); } m.unlock(); + static_cast(_n->m_pluginData)->setPresetIndex(p); } const fpp_t frames = _n->framesLeftForCurrentPeriod(); @@ -345,6 +346,7 @@ void malletsInstrument::playNote( NotePlayHandle * _n, malletsSynth * ps = static_cast( _n->m_pluginData ); ps->setFrequency( freq ); + p = ps->presetIndex(); sample_t add_scale = 0.0f; if( p == 10 && m_isOldVersionModel.value() == true ) @@ -355,9 +357,9 @@ void malletsInstrument::playNote( NotePlayHandle * _n, for( fpp_t frame = offset; frame < frames + offset; ++frame ) { _working_buffer[frame][0] = ps->nextSampleLeft() * - ( m_scalers[m_presetsModel.value()] + add_scale ); + ( m_scalers[p] + add_scale ); _working_buffer[frame][1] = ps->nextSampleRight() * - ( m_scalers[m_presetsModel.value()] + add_scale ); + ( m_scalers[p] + add_scale ); } instrumentTrack()->processAudioBuffer( _working_buffer, frames + offset, _n ); @@ -579,7 +581,6 @@ void malletsInstrumentView::modelChanged() void malletsInstrumentView::changePreset() { malletsInstrument * inst = castModel(); - inst->instrumentTrack()->silenceAllNotes(); int _preset = inst->m_presetsModel.value(); if( _preset < 9 ) @@ -614,7 +615,8 @@ malletsSynth::malletsSynth( const StkFloat _pitch, const StkFloat _control11, const int _control16, const uint8_t _delay, - const sample_rate_t _sample_rate ) + const sample_rate_t _sample_rate ) : + m_presetIndex(0) { try { @@ -664,7 +666,8 @@ malletsSynth::malletsSynth( const StkFloat _pitch, const StkFloat _control11, const StkFloat _control128, const uint8_t _delay, - const sample_rate_t _sample_rate ) + const sample_rate_t _sample_rate ) : + m_presetIndex(0) { try { @@ -712,7 +715,8 @@ malletsSynth::malletsSynth( const StkFloat _pitch, const StkFloat _control64, const StkFloat _control128, const uint8_t _delay, - const sample_rate_t _sample_rate ) + const sample_rate_t _sample_rate ) : + m_presetIndex(0) { try { diff --git a/plugins/stk/mallets/mallets.h b/plugins/stk/mallets/mallets.h index 6fd28d41c67..f50d20c67bb 100644 --- a/plugins/stk/mallets/mallets.h +++ b/plugins/stk/mallets/mallets.h @@ -120,8 +120,19 @@ class malletsSynth } } + inline int presetIndex() + { + return m_presetIndex; + } + + inline void setPresetIndex(int presetIndex) + { + m_presetIndex = presetIndex; + } + protected: + int m_presetIndex; Instrmnt * m_voice; StkFloat * m_delay; diff --git a/plugins/vst_base/RemoteVstPlugin.cpp b/plugins/vst_base/RemoteVstPlugin.cpp index 5b4bbbd9b5e..a103c065282 100644 --- a/plugins/vst_base/RemoteVstPlugin.cpp +++ b/plugins/vst_base/RemoteVstPlugin.cpp @@ -1085,7 +1085,7 @@ void RemoteVstPlugin::getParameterDump() for( int i = 0; i < m_plugin->numParams; ++i ) { - char paramName[32]; + char paramName[256]; memset( paramName, 0, sizeof( paramName ) ); pluginDispatch( effGetParamName, i, 0, paramName ); paramName[sizeof(paramName)-1] = 0; @@ -1299,7 +1299,7 @@ void RemoteVstPlugin::savePreset( const std::string & _file ) if (!isPreset &&!chunky) uIntToFile = (unsigned int) m_plugin->numPrograms; pBank->numPrograms = endian_swap( uIntToFile ); - FILE * stream = F_OPEN_UTF8( _file, "w" ); + FILE * stream = F_OPEN_UTF8( _file, "wb" ); if (!stream) { fprintf( stderr, @@ -1357,7 +1357,7 @@ void RemoteVstPlugin::loadPresetFile( const std::string & _file ) unsigned int * pLen = new unsigned int[ 1 ]; unsigned int len = 0; sBank * pBank = (sBank*) new char[ sizeof( sBank ) ]; - FILE * stream = F_OPEN_UTF8( _file, "r" ); + FILE * stream = F_OPEN_UTF8( _file, "rb" ); if (!stream) { fprintf( stderr, diff --git a/src/core/AutomatableModel.cpp b/src/core/AutomatableModel.cpp index 6055598f704..8328e5cb9ea 100644 --- a/src/core/AutomatableModel.cpp +++ b/src/core/AutomatableModel.cpp @@ -499,8 +499,23 @@ void AutomatableModel::unlinkModel( AutomatableModel* model ) void AutomatableModel::linkModels( AutomatableModel* model1, AutomatableModel* model2 ) { + if (!model1->m_linkedModels.contains( model2 ) && model1 != model2) + { + // copy data + model1->m_value = model2->m_value; + if (model1->valueBuffer() && model2->valueBuffer()) + { + std::copy_n(model2->valueBuffer()->data(), + model1->valueBuffer()->length(), + model1->valueBuffer()->data()); + } + // send dataChanged() before linking (because linking will + // connect the two dataChanged() signals) + emit model1->dataChanged(); + // finally: link the models model1->linkModel( model2 ); model2->linkModel( model1 ); + } } diff --git a/src/core/NotePlayHandle.cpp b/src/core/NotePlayHandle.cpp index 9f934c61973..c101e4edf98 100644 --- a/src/core/NotePlayHandle.cpp +++ b/src/core/NotePlayHandle.cpp @@ -537,6 +537,15 @@ void NotePlayHandle::processMidiTime( const MidiTime& time ) void NotePlayHandle::resize( const bpm_t _new_tempo ) { + if (origin() == OriginMidiInput || + (origin() == OriginNoteStacking && m_parent->origin() == OriginMidiInput)) + { + // Don't resize notes from MIDI input - they should continue to play + // until the key is released, and their large duration can cause + // overflows in this method. + return; + } + double completed = m_totalFramesPlayed / (double) m_frames; double new_frames = m_origFrames * m_origTempo / (double) _new_tempo; m_frames = (f_cnt_t)new_frames; diff --git a/src/core/RemotePlugin.cpp b/src/core/RemotePlugin.cpp index 23654d782ac..45678139f9c 100644 --- a/src/core/RemotePlugin.cpp +++ b/src/core/RemotePlugin.cpp @@ -102,14 +102,14 @@ RemotePlugin::RemotePlugin() : m_socketFile = QDir::tempPath() + QDir::separator() + QUuid::createUuid().toString(); - const char * path = m_socketFile.toUtf8().constData(); - size_t length = strlen( path ); + auto path = m_socketFile.toUtf8(); + size_t length = path.length(); if ( length >= sizeof sa.sun_path ) { length = sizeof sa.sun_path - 1; qWarning( "Socket path too long." ); } - memcpy( sa.sun_path, path, length ); + memcpy(sa.sun_path, path.constData(), length ); sa.sun_path[length] = '\0'; m_server = socket( PF_LOCAL, SOCK_STREAM, 0 ); @@ -117,7 +117,7 @@ RemotePlugin::RemotePlugin() : { qWarning( "Unable to start the server." ); } - remove( path ); + remove(path.constData()); int ret = bind( m_server, (struct sockaddr *) &sa, sizeof sa ); if ( ret == -1 || listen( m_server, 1 ) == -1 ) { diff --git a/src/core/RenderManager.cpp b/src/core/RenderManager.cpp index 019e14acf42..785aa9fb16d 100644 --- a/src/core/RenderManager.cpp +++ b/src/core/RenderManager.cpp @@ -182,7 +182,7 @@ QString RenderManager::pathForTrack(const Track *track, int num) { QString extension = ProjectRenderer::getFileExtensionFromFormat( m_format ); QString name = track->name(); - name = name.remove(QRegExp("[^a-zA-Z]")); + name = name.remove(QRegExp(FILENAME_FILTER)); name = QString( "%1_%2%3" ).arg( num ).arg( name ).arg( extension ); return QDir(m_outputPath).filePath(name); } diff --git a/src/core/Track.cpp b/src/core/Track.cpp index 5c61dc5abe3..cb430da964c 100644 --- a/src/core/Track.cpp +++ b/src/core/Track.cpp @@ -1620,9 +1620,13 @@ bool TrackContentWidget::pasteSelection( MidiTime tcoPos, QDropEvent * de ) // TODO -- Need to draw the hovericon either way, or ghost the TCOs // onto their final position. + float snapSize = gui->songEditor()->m_editor->getSnapSize(); // All patterns should be offset the same amount as the grabbed pattern + MidiTime offset = MidiTime(tcoPos - grabbedTCOPos); + // Users expect clips to "fall" backwards, so bias the offset + offset = offset - MidiTime::ticksPerBar() * snapSize / 2; // The offset is quantized (rather than the positions) to preserve fine adjustments - int offset = MidiTime(tcoPos - grabbedTCOPos).quantize(gui->songEditor()->m_editor->getSnapSize()); + offset = offset.quantize(snapSize); for( int i = 0; itopLevelItem( i ) ); if( d == NULL || cur_file < d->text( 0 ) ) { + // insert before item, we're done Directory *dd = new Directory( cur_file, path, m_filter ); m_fileBrowserTreeWidget->insertTopLevelItem( i,dd ); @@ -249,6 +251,11 @@ void FileBrowser::addItems(const QString & path ) } else if( cur_file == d->text( 0 ) ) { + // imagine we have subdirs named "TripleOscillator/xyz" in + // two directories from m_directories + // then only add one tree widget for both + // so we don't add a new Directory - we just + // add the path to the current directory d->addDirectory( path ); d->update(); orphan = false; @@ -257,6 +264,8 @@ void FileBrowser::addItems(const QString & path ) } if( orphan ) { + // it has not yet been added yet, so it's (lexically) + // larger than all other dirs => append it at the bottom Directory *d = new Directory( cur_file, path, m_filter ); d->update(); @@ -768,21 +777,29 @@ void Directory::update( void ) if( !childCount() ) { m_dirCount = 0; + // for all paths leading here, add their items for( QStringList::iterator it = m_directories.begin(); it != m_directories.end(); ++it ) { - int top_index = childCount(); + int filesBeforeAdd = childCount() - m_dirCount; if( addItems( fullName( *it ) ) && ( *it ).contains( ConfigManager::inst()->dataDir() ) ) { - QTreeWidgetItem * sep = new QTreeWidgetItem; - sep->setText( 0, - FileBrowserTreeWidget::tr( - "--- Factory files ---" ) ); - sep->setIcon( 0, embed::getIconPixmap( - "factory_files" ) ); - insertChild( m_dirCount + top_index, sep ); + // factory file directory is added + // note: those are always added last + int filesNow = childCount() - m_dirCount; + if(filesNow > filesBeforeAdd) // any file appended? + { + QTreeWidgetItem * sep = new QTreeWidgetItem; + sep->setText( 0, + FileBrowserTreeWidget::tr( + "--- Factory files ---" ) ); + sep->setIcon( 0, embed::getIconPixmap( + "factory_files" ) ); + // add delimeter after last file before appending our files + insertChild( filesBeforeAdd + m_dirCount, sep ); + } } } } @@ -803,6 +820,7 @@ bool Directory::addItems(const QString & path ) bool added_something = false; + // try to add all directories from file system alphabetically into the tree QStringList files = thisDir.entryList( QDir::Dirs, QDir::Name ); for( QStringList::const_iterator it = files.constBegin(); it != files.constEnd(); ++it ) @@ -817,6 +835,7 @@ bool Directory::addItems(const QString & path ) child( i ) ); if( d == NULL || cur_file < d->text( 0 ) ) { + // insert before item, we're done insertChild( i, new Directory( cur_file, path, m_filter ) ); orphan = false; @@ -825,6 +844,12 @@ bool Directory::addItems(const QString & path ) } else if( cur_file == d->text( 0 ) ) { + // imagine we have top-level subdirs named "TripleOscillator" in + // two directories from FileBrowser::m_directories + // and imagine both have a sub folder named "xyz" + // then only add one tree widget for both + // so we don't add a new Directory - we just + // add the path to the current directory d->addDirectory( path ); orphan = false; break; @@ -832,6 +857,8 @@ bool Directory::addItems(const QString & path ) } if( orphan ) { + // it has not yet been added yet, so it's (lexically) + // larger than all other dirs => append it at the bottom addChild( new Directory( cur_file, path, m_filter ) ); m_dirCount++; diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp index acdbabb3576..3eb532981d0 100644 --- a/src/gui/MainWindow.cpp +++ b/src/gui/MainWindow.cpp @@ -1305,6 +1305,7 @@ void MainWindow::sessionCleanup() void MainWindow::focusOutEvent( QFocusEvent * _fe ) { + // TODO Remove this function, since it is apparently never actually called! // when loosing focus we do not receive key-(release!)-events anymore, // so we might miss release-events of one the modifiers we're watching! clearKeyModifiers(); diff --git a/src/gui/editors/PianoRoll.cpp b/src/gui/editors/PianoRoll.cpp index 5918c9333bb..d49c6f0b798 100644 --- a/src/gui/editors/PianoRoll.cpp +++ b/src/gui/editors/PianoRoll.cpp @@ -2382,7 +2382,7 @@ void PianoRoll::mouseMoveEvent( QMouseEvent * me ) m_pattern->instrumentTrack()->processInEvent( evt ); } } - else if( n->isPlaying() ) + else if( n->isPlaying() && !isSelection() ) { // mouse not over this note, stop playing it. m_pattern->instrumentTrack()->pianoModel()->handleKeyRelease( n->key() ); diff --git a/src/gui/editors/SongEditor.cpp b/src/gui/editors/SongEditor.cpp index 2c2485d00ed..ba7cd6f824b 100644 --- a/src/gui/editors/SongEditor.cpp +++ b/src/gui/editors/SongEditor.cpp @@ -473,12 +473,13 @@ void SongEditor::toggleProportionalSnap() void SongEditor::keyPressEvent( QKeyEvent * ke ) { - if( ke->modifiers() & Qt::ShiftModifier && + bool isShiftPressed = ke->modifiers() & Qt::ShiftModifier; + if( isShiftPressed && ( ke->key() == Qt::Key_Insert || ke->key() == Qt::Key_Enter || ke->key() == Qt::Key_Return ) ) { m_song->insertBar(); } - else if( ke->modifiers() & Qt::ShiftModifier && + else if( isShiftPressed && ( ke->key() == Qt::Key_Delete || ke->key() == Qt::Key_Backspace ) ) { m_song->removeBar(); @@ -517,7 +518,7 @@ void SongEditor::keyPressEvent( QKeyEvent * ke ) } else if( ke->key() == Qt::Key_A && ke->modifiers() & Qt::ControlModifier ) { - selectAllTcos( !(ke->modifiers() & Qt::ShiftModifier) ); + selectAllTcos( !isShiftPressed ); } else if( ke->key() == Qt::Key_Escape ) { diff --git a/src/gui/widgets/ControlLayout.cpp b/src/gui/widgets/ControlLayout.cpp index afd4e68e42e..ce2e9e4ef9b 100644 --- a/src/gui/widgets/ControlLayout.cpp +++ b/src/gui/widgets/ControlLayout.cpp @@ -73,6 +73,8 @@ #include "ControlLayout.h" +#include "stdshims.h" + #include #include #include @@ -208,7 +210,7 @@ QSize ControlLayout::minimumSize() const // get maximum height and width for all children. // as Qt will later call heightForWidth, only the width here really matters QSize size; - for (const QLayoutItem *item : qAsConst(m_itemMap)) + for (const QLayoutItem *item : as_const(m_itemMap)) { size = size.expandedTo(item->minimumSize()); } diff --git a/src/gui/widgets/Knob.cpp b/src/gui/widgets/Knob.cpp index c3e26ae117e..167c3ecf883 100644 --- a/src/gui/widgets/Knob.cpp +++ b/src/gui/widgets/Knob.cpp @@ -52,35 +52,28 @@ TextFloat * Knob::s_textFloat = NULL; -//! @todo: in C++11, we can use delegating ctors -#define DEFAULT_KNOB_INITIALIZER_LIST \ - QWidget( _parent ), \ - FloatModelView( new FloatModel( 0, 0, 0, 1, NULL, _name, true ), this ), \ - m_label( "" ), \ - m_knobPixmap( NULL ), \ - m_volumeKnob( false ), \ - m_volumeRatio( 100.0, 0.0, 1000000.0 ), \ - m_buttonPressed( false ), \ - m_angle( -10 ), \ - m_lineWidth( 0 ), \ - m_textColor( 255, 255, 255 ) Knob::Knob( knobTypes _knob_num, QWidget * _parent, const QString & _name ) : - DEFAULT_KNOB_INITIALIZER_LIST, + QWidget( _parent ), + FloatModelView( new FloatModel( 0, 0, 0, 1, NULL, _name, true ), this ), + m_label( "" ), + m_knobPixmap( NULL ), + m_volumeKnob( false ), + m_volumeRatio( 100.0, 0.0, 1000000.0 ), + m_buttonPressed( false ), + m_angle( -10 ), + m_lineWidth( 0 ), + m_textColor( 255, 255, 255 ), m_knobNum( _knob_num ) { initUi( _name ); } Knob::Knob( QWidget * _parent, const QString & _name ) : - DEFAULT_KNOB_INITIALIZER_LIST, - m_knobNum( knobBright_26 ) + Knob( knobBright_26, _parent, _name ) { - initUi( _name ); } -#undef DEFAULT_KNOB_INITIALIZER_LIST - @@ -606,7 +599,7 @@ void Knob::mousePressEvent( QMouseEvent * _me ) m_buttonPressed = true; } else if( _me->button() == Qt::LeftButton && - gui->mainWindow()->isShiftPressed() == true ) + (_me->modifiers() & Qt::ShiftModifier) ) { new StringPairDrag( "float_value", QString::number( model()->value() ), @@ -808,9 +801,9 @@ void Knob::enterValue() void Knob::friendlyUpdate() { - if( model()->controllerConnection() == NULL || + if (model() && (model()->controllerConnection() == NULL || model()->controllerConnection()->getController()->frequentUpdates() == false || - Controller::runningFrames() % (256*4) == 0 ) + Controller::runningFrames() % (256*4) == 0)) { update(); } diff --git a/src/gui/widgets/LcdSpinBox.cpp b/src/gui/widgets/LcdSpinBox.cpp index 325c0171d79..8b30a38ca14 100644 --- a/src/gui/widgets/LcdSpinBox.cpp +++ b/src/gui/widgets/LcdSpinBox.cpp @@ -114,7 +114,7 @@ void LcdSpinBox::mouseMoveEvent( QMouseEvent* event ) if( m_mouseMoving ) { int dy = event->globalY() - m_origMousePos.y(); - if( gui->mainWindow()->isShiftPressed() ) + if( event->modifiers() & Qt::ShiftModifier ) dy = qBound( -4, dy/4, 4 ); if( dy > 1 || dy < -1 ) { diff --git a/src/gui/widgets/LcdWidget.cpp b/src/gui/widgets/LcdWidget.cpp index 2b85a64ede8..e34e1eb47b3 100644 --- a/src/gui/widgets/LcdWidget.cpp +++ b/src/gui/widgets/LcdWidget.cpp @@ -39,42 +39,32 @@ -//! @todo: in C++11, we can use delegating ctors -#define DEFAULT_LCDWIDGET_INITIALIZER_LIST \ - QWidget( parent ), \ - m_label(), \ - m_textColor( 255, 255, 255 ), \ - m_textShadowColor( 64, 64, 64 ) - LcdWidget::LcdWidget( QWidget* parent, const QString& name ) : - DEFAULT_LCDWIDGET_INITIALIZER_LIST, - m_numDigits( 1 ) + LcdWidget( 1, parent, name ) { - initUi( name ); } LcdWidget::LcdWidget( int numDigits, QWidget* parent, const QString& name ) : - DEFAULT_LCDWIDGET_INITIALIZER_LIST, - m_numDigits( numDigits ) + LcdWidget( numDigits, QString("19green"), parent, name ) { - initUi( name ); } LcdWidget::LcdWidget( int numDigits, const QString& style, QWidget* parent, const QString& name ) : - DEFAULT_LCDWIDGET_INITIALIZER_LIST, + QWidget( parent ), + m_label(), + m_textColor( 255, 255, 255 ), + m_textShadowColor( 64, 64, 64 ), m_numDigits( numDigits ) { initUi( name, style ); } -#undef DEFAULT_LCDWIDGET_INITIALIZER_LIST - diff --git a/src/gui/widgets/LedCheckbox.cpp b/src/gui/widgets/LedCheckbox.cpp index f69ec6719fd..bdb537744f7 100644 --- a/src/gui/widgets/LedCheckbox.cpp +++ b/src/gui/widgets/LedCheckbox.cpp @@ -39,13 +39,9 @@ static const QString names[LedCheckBox::NumColors] = -//! @todo: in C++11, we can use delegating ctors -#define DEFAULT_LEDCHECKBOX_INITIALIZER_LIST \ - AutomatableButton( _parent, _name ) - LedCheckBox::LedCheckBox( const QString & _text, QWidget * _parent, const QString & _name, LedColors _color ) : - DEFAULT_LEDCHECKBOX_INITIALIZER_LIST, + AutomatableButton( _parent, _name ), m_text( _text ) { initUi( _color ); @@ -56,13 +52,10 @@ LedCheckBox::LedCheckBox( const QString & _text, QWidget * _parent, LedCheckBox::LedCheckBox( QWidget * _parent, const QString & _name, LedColors _color ) : - DEFAULT_LEDCHECKBOX_INITIALIZER_LIST + LedCheckBox( QString(), _parent, _name, _color ) { - initUi( _color ); } -#undef DEFAULT_LEDCHECKBOX_INITIALIZER_LIST - LedCheckBox::~LedCheckBox() diff --git a/src/tracks/InstrumentTrack.cpp b/src/tracks/InstrumentTrack.cpp index b44d1b4fa63..ac2a7f45166 100644 --- a/src/tracks/InstrumentTrack.cpp +++ b/src/tracks/InstrumentTrack.cpp @@ -447,7 +447,7 @@ void InstrumentTrack::silenceAllNotes( bool removeIPH ) } m_midiNotesMutex.unlock(); - lock(); + Engine::mixer()->requestChangeInModel(); // invalidate all NotePlayHandles and PresetPreviewHandles linked to this track m_processHandles.clear(); @@ -457,7 +457,7 @@ void InstrumentTrack::silenceAllNotes( bool removeIPH ) flags |= PlayHandle::TypeInstrumentPlayHandle; } Engine::mixer()->removePlayHandlesOfTypes( this, flags ); - unlock(); + Engine::mixer()->doneChangeInModel(); } @@ -546,11 +546,13 @@ void InstrumentTrack::setName( const QString & _new_name ) void InstrumentTrack::updateBaseNote() { + Engine::mixer()->requestChangeInModel(); for( NotePlayHandleList::Iterator it = m_processHandles.begin(); it != m_processHandles.end(); ++it ) { ( *it )->setFrequencyUpdate(); } + Engine::mixer()->doneChangeInModel(); } @@ -1599,7 +1601,7 @@ void InstrumentTrackWindow::saveSettingsBtnClicked() sfd.setDirectory( presetRoot + m_track->instrumentName() ); sfd.setFileMode( FileDialog::AnyFile ); QString fname = m_track->name(); - sfd.selectFile( fname.remove(QRegExp("[^a-zA-Z0-9_\\-\\d\\s]")) ); + sfd.selectFile(fname.remove(QRegExp(FILENAME_FILTER))); sfd.setDefaultSuffix( "xpf"); if( sfd.exec() == QDialog::Accepted && diff --git a/tests/src/core/AutomatableModelTest.cpp b/tests/src/core/AutomatableModelTest.cpp index 116f95e60a0..d84d89db4a6 100644 --- a/tests/src/core/AutomatableModelTest.cpp +++ b/tests/src/core/AutomatableModelTest.cpp @@ -1,7 +1,7 @@ /* * AutomatableModelTest.cpp * - * Copyright (c) 2019-2019 Johannes Lorenz + * Copyright (c) 2019-2020 Johannes Lorenz * * This file is part of LMMS - https://lmms.io * @@ -31,7 +31,14 @@ class AutomatableModelTest : QTestSuite { Q_OBJECT -private slots: + bool m1Changed, m2Changed; + void resetChanged() { m1Changed = m2Changed = false; } + +private slots: // helper slots + void onM1Changed() { m1Changed = true; } + void onM2Changed() { m2Changed = true; } + +private slots: // tests //! Test that upcast and exact casts work, //! but no downcast or any other casts void CastTests() @@ -50,6 +57,45 @@ private slots: QCOMPARE(&intModel, imPtr->dynamicCast()); // same class QVERIFY(nullptr == imPtr->dynamicCast()); // child class } + + void LinkTests() + { + BoolModel m1(false), m2(false); + + QObject::connect(&m1, SIGNAL(dataChanged()), + this, SLOT(onM1Changed())); + QObject::connect(&m2, SIGNAL(dataChanged()), + this, SLOT(onM2Changed())); + + resetChanged(); + AutomatableModel::linkModels(&m1, &m1); + QVERIFY(!m1Changed); // cannot link to itself + QVERIFY(!m2Changed); + + resetChanged(); + AutomatableModel::linkModels(&m1, &m2); + QVERIFY(m1Changed); // since m1 takes the value of m2 + QVERIFY(!m2Changed); // the second model is the source + + resetChanged(); + AutomatableModel::linkModels(&m1, &m2); + QVERIFY(!m1Changed); // it's already linked + QVERIFY(!m2Changed); + + resetChanged(); + BoolModel m3(false); + m1.setValue(1.f); + m2.setValue(1.f); + AutomatableModel::linkModels(&m1, &m2); + QVERIFY(m1.value()); + QVERIFY(m2.value()); + QVERIFY(!m3.value()); + AutomatableModel::linkModels(&m2, &m3); // drag m3, drop on m2 + // m2 should take m3's (0) value + // due to a bug(?), this does not happen + QVERIFY(m2.value()); + QVERIFY(!m3.value()); + } } AutomatableModelTests; #include "AutomatableModelTest.moc"