diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 007842b82ad..5fe1ec7bf72 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -14,7 +14,8 @@ jobs: -DUSE_WERROR=ON -DCMAKE_BUILD_TYPE=RelWithDebInfo -DUSE_COMPILE_CACHE=ON - CCACHE_MAXSIZE: 500M + CCACHE_MAXSIZE: 0 + CCACHE_NOCOMPRESS: 1 MAKEFLAGS: -j2 steps: - name: Update and configure Git @@ -38,6 +39,7 @@ jobs: path: ~/.ccache - name: Configure run: | + ccache --zero-stats source /opt/qt5*/bin/qt5*-env.sh || true mkdir build && cd build cmake .. $CMAKE_OPTS -DCMAKE_INSTALL_PREFIX=./install @@ -56,29 +58,42 @@ jobs: with: name: linux path: build/lmms-*.AppImage - - name: Print ccache statistics + - name: Trim ccache and print statistics run: | + ccache --cleanup echo "[ccache config]" - ccache -p + ccache --print-config echo "[ccache stats]" - ccache -s + ccache --show-stats + env: + CCACHE_MAXSIZE: 500M macos: name: macos - runs-on: macos-11 + runs-on: macos-12 env: CMAKE_OPTS: >- -DUSE_WERROR=ON -DCMAKE_BUILD_TYPE=RelWithDebInfo -DUSE_COMPILE_CACHE=ON - CCACHE_MAXSIZE: 500M + CCACHE_MAXSIZE: 0 + CCACHE_NOCOMPRESS: 1 MAKEFLAGS: -j3 - DEVELOPER_DIR: /Applications/Xcode_11.7.app/Contents/Developer + DEVELOPER_DIR: /Applications/Xcode_13.1.app/Contents/Developer steps: - name: Check out uses: actions/checkout@v3 with: fetch-depth: 0 submodules: recursive + - name: Clean up Homebrew download cache + run: rm -rf ~/Library/Caches/Homebrew/downloads + - name: Restore Homebrew download cache + uses: actions/cache/restore@v3 + with: + key: n/a - only restore from restore-keys + restore-keys: | + homebrew- + path: ~/Library/Caches/Homebrew/downloads - name: Cache ccache data uses: actions/cache@v3 with: @@ -89,12 +104,16 @@ jobs: path: ~/Library/Caches/ccache - name: Install dependencies run: | - brew install ccache fftw pkg-config libogg libvorbis lame libsndfile \ - libsamplerate jack sdl libgig libsoundio lilv lv2 stk \ - fluid-synth portaudio fltk qt@5 carla + brew bundle install --verbose + npm update -g npm npm install --location=global appdmg + env: + HOMEBREW_NO_AUTO_UPDATE: 1 + HOMEBREW_NO_INSTALL_UPGRADE: 1 + HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK: 1 - name: Configure run: | + ccache --zero-stats mkdir build cmake -S . \ -B build \ @@ -117,12 +136,20 @@ jobs: with: name: macos path: build/lmms-*.dmg - - name: Print ccache statistics + - name: Trim ccache and print statistics run: | + ccache --cleanup echo "[ccache config]" - ccache -p + ccache --show-config echo "[ccache stats]" - ccache -s + ccache --show-stats --verbose + env: + CCACHE_MAXSIZE: 500MB + - name: Save Homebrew download cache + uses: actions/cache/save@v3 + with: + key: homebrew-${{ hashFiles('Brewfile.lock.json') }} + path: ~/Library/Caches/Homebrew/downloads mingw: strategy: fail-fast: false @@ -136,7 +163,8 @@ jobs: -DUSE_WERROR=ON -DCMAKE_BUILD_TYPE=RelWithDebInfo -DUSE_COMPILE_CACHE=ON - CCACHE_MAXSIZE: 500M + CCACHE_MAXSIZE: 0 + CCACHE_NOCOMPRESS: 1 MAKEFLAGS: -j2 steps: - name: Update and configure Git @@ -161,6 +189,7 @@ jobs: path: ~/.ccache - name: Configure run: | + ccache --zero-stats mkdir build && cd build ../cmake/build_win${{ matrix.arch }}.sh - name: Build @@ -174,12 +203,15 @@ jobs: with: name: mingw${{ matrix.arch }} path: build/lmms-*.exe - - name: Print ccache statistics + - name: Trim ccache and print statistics run: | + ccache --cleanup echo "[ccache config]" - ccache -p + ccache --print-config echo "[ccache stats]" - ccache -s + ccache --show-stats + env: + CCACHE_MAXSIZE: 500M msvc: strategy: fail-fast: false @@ -189,6 +221,8 @@ jobs: runs-on: windows-2019 env: qt-version: '5.15.2' + CCACHE_MAXSIZE: 0 + CCACHE_NOCOMPRESS: 1 steps: - name: Check out uses: actions/checkout@v3 @@ -196,49 +230,60 @@ jobs: fetch-depth: 0 submodules: recursive - name: Cache vcpkg dependencies + id: cache-deps uses: actions/cache@v3 with: - key: vcpkg-${{ matrix.arch }}-${{ github.ref }}-${{ github.run_id }} + key: vcpkg-${{ matrix.arch }}-${{ hashFiles('vcpkg.json') }} restore-keys: | - vcpkg-${{ matrix.arch }}-${{ github.ref }}- vcpkg-${{ matrix.arch }}- - path: C:\vcpkg\installed + path: build\vcpkg_installed + - name: Cache ccache data + uses: actions/cache@v3 + with: + key: "ccache-${{ github.job }}-${{ matrix.arch }}-${{ github.ref }}\ + -${{ github.run_id }}" + restore-keys: | + ccache-${{ github.job }}-${{ matrix.arch }}-${{ github.ref }}- + ccache-${{ github.job }}-${{ matrix.arch }}- + path: ~\AppData\Local\ccache + - name: Install tools + run: choco install ccache - name: Install 64-bit Qt if: matrix.arch == 'x64' - uses: jurplel/install-qt-action@64bdb64f2c14311d23733a8463e5fcbc65e8775e + uses: jurplel/install-qt-action@b3ea5275e37b734d027040e2c7fe7a10ea2ef946 with: version: ${{ env.qt-version }} arch: win64_msvc2019_64 archives: qtbase qtsvg qttools cache: true - name: Install 32-bit Qt - uses: jurplel/install-qt-action@64bdb64f2c14311d23733a8463e5fcbc65e8775e + uses: jurplel/install-qt-action@b3ea5275e37b734d027040e2c7fe7a10ea2ef946 with: version: ${{ env.qt-version }} arch: win32_msvc2019 archives: qtbase qtsvg qttools cache: true set-env: ${{ matrix.arch == 'x86' }} - - name: Install dependencies - run: | - vcpkg install ` - --triplet=${{ matrix.arch }}-windows ` - --host-triplet=${{ matrix.arch }}-windows ` - --recurse ` - fftw3 fltk fluidsynth[sndfile] libsamplerate libsndfile libstk ` - lilv lv2 portaudio sdl2 - name: Set up build environment - uses: ilammy/msvc-dev-cmd@d8610e2b41c6d0f0c3b4c46dad8df0fd826c68e1 + uses: ilammy/msvc-dev-cmd@cec98b9d092141f74527d0afa6feb2af698cfe89 with: arch: ${{ matrix.arch }} - name: Configure run: | - mkdir build + ccache --zero-stats + mkdir build -Force cmake -S . ` -B build ` -G Ninja ` --toolchain C:/vcpkg/scripts/buildsystems/vcpkg.cmake ` - -DCMAKE_BUILD_TYPE=RelWithDebInfo + -DCMAKE_BUILD_TYPE=RelWithDebInfo ` + -DUSE_COMPILE_CACHE=ON ` + -DVCPKG_TARGET_TRIPLET="${{ matrix.arch }}-windows" ` + -DVCPKG_HOST_TRIPLET="${{ matrix.arch }}-windows" ` + -DVCPKG_MANIFEST_INSTALL="${{ env.should_install_manifest }}" + env: + should_install_manifest: + ${{ steps.cache-deps.outputs.cache-hit == 'true' && 'NO' || 'YES' }} - name: Build run: cmake --build build - name: Build tests @@ -250,3 +295,12 @@ jobs: with: name: msvc-${{ matrix.arch }} path: build\lmms-*.exe + - name: Trim ccache and print statistics + run: | + ccache --cleanup + echo "[ccache config]" + ccache --show-config + echo "[ccache stats]" + ccache --show-stats --verbose + env: + CCACHE_MAXSIZE: 500MB diff --git a/.gitignore b/.gitignore index ee289379f0e..1b855f204cb 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,4 @@ /plugins/ZynAddSubFx/zynaddsubfx/doc/Makefile /plugins/ZynAddSubFx/zynaddsubfx/doc/gen/Makefile /data/locale/*.qm +Brewfile.lock.json diff --git a/Brewfile b/Brewfile new file mode 100644 index 00000000000..1bfbd7b01c1 --- /dev/null +++ b/Brewfile @@ -0,0 +1,20 @@ +brew "carla" +brew "ccache" +brew "fftw" +brew "fltk" +brew "fluid-synth" +brew "jack" +brew "lame" +brew "libgig" +brew "libogg" +brew "libsamplerate" +brew "libsndfile" +brew "libsoundio" +brew "libvorbis" +brew "lilv" +brew "lv2" +brew "pkg-config" +brew "portaudio" +brew "qt@5" +brew "sdl2" +brew "stk" diff --git a/CMakeLists.txt b/CMakeLists.txt index 3a5754d6a56..b59d0c59315 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,25 @@ CMAKE_MINIMUM_REQUIRED(VERSION 3.9) +# Set the given policy to NEW. If it does not exist, it will not be set. If it +# is already set to NEW (most likely due to predating the minimum required CMake +# version), a developer warning is emitted indicating that the policy need no +# longer be explicitly set. +function(enable_policy_if_exists id) + if(POLICY "${id}") + cmake_policy(GET "${id}" current_value) + if(current_value STREQUAL "NEW") + message(AUTHOR_WARNING "${id} is now set to NEW by default, and no longer needs to be explicitly set.") + else() + cmake_policy(SET "${id}" NEW) + endif() + endif() +endfunction() + +# Needed for the SWH Ladspa plugins. See below. +enable_policy_if_exists(CMP0074) # find_package() uses _ROOT variables. +# Needed for ccache support with MSVC +enable_policy_if_exists(CMP0141) # MSVC debug information format flags are selected by an abstraction. + PROJECT(lmms) SET(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake/modules" ${CMAKE_MODULE_PATH}) @@ -8,6 +28,15 @@ 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. @@ -35,7 +64,7 @@ INCLUDE(GenerateExportHeader) STRING(TOUPPER "${CMAKE_PROJECT_NAME}" PROJECT_NAME_UCASE) -SET(PROJECT_YEAR 2020) +SET(PROJECT_YEAR 2023) SET(PROJECT_AUTHOR "LMMS Developers") SET(PROJECT_URL "https://lmms.io") @@ -201,7 +230,14 @@ SET(QT_QTTEST_LIBRARY Qt5::Test) # check for libsndfile FIND_PACKAGE(SndFile REQUIRED) -IF(NOT SNDFILE_FOUND) +IF(SNDFILE_FOUND) + IF(SndFile_VERSION VERSION_GREATER_EQUAL "1.1.0") + SET(LMMS_HAVE_SNDFILE_MP3 TRUE) + ELSE() + MESSAGE("libsndfile version is < 1.1.0; MP3 import disabled") + SET(LMMS_HAVE_SNDFILE_MP3 FALSE) + ENDIF() +ELSE() MESSAGE(FATAL_ERROR "LMMS requires libsndfile1 and libsndfile1-dev >= 1.0.18 - please install, remove CMakeCache.txt and try again!") ENDIF() # check if we can use SFC_SET_COMPRESSION_LEVEL @@ -433,8 +469,6 @@ IF(WANT_MP3LAME) SET(STATUS_MP3LAME "OK") ELSE(LAME_FOUND) SET(STATUS_MP3LAME "not found, please install libmp3lame-dev (or similar)") - SET(LAME_LIBRARIES "") - SET(LAME_INCLUDE_DIRS "") ENDIF(LAME_FOUND) ELSE(WANT_MP3LAME) SET(STATUS_MP3LAME "Disabled for build") diff --git a/cmake/modules/BuildPlugin.cmake b/cmake/modules/BuildPlugin.cmake index f8b3d315392..70e518c93ce 100644 --- a/cmake/modules/BuildPlugin.cmake +++ b/cmake/modules/BuildPlugin.cmake @@ -56,11 +56,7 @@ MACRO(BUILD_PLUGIN PLUGIN_NAME) ADD_LIBRARY(${PLUGIN_NAME} ${PLUGIN_LINK} ${PLUGIN_SOURCES} ${plugin_MOC_out} ${RCC_OUT}) - TARGET_LINK_LIBRARIES(${PLUGIN_NAME} Qt5::Widgets Qt5::Xml) - - IF(LMMS_BUILD_WIN32) - TARGET_LINK_LIBRARIES(${PLUGIN_NAME} lmms) - ENDIF(LMMS_BUILD_WIN32) + target_link_libraries("${PLUGIN_NAME}" lmms Qt5::Widgets Qt5::Xml) INSTALL(TARGETS ${PLUGIN_NAME} LIBRARY DESTINATION "${PLUGIN_DIR}" @@ -70,10 +66,7 @@ MACRO(BUILD_PLUGIN PLUGIN_NAME) IF(LMMS_BUILD_APPLE) IF ("${PLUGIN_LINK}" STREQUAL "SHARED") SET_TARGET_PROPERTIES(${PLUGIN_NAME} PROPERTIES LINK_FLAGS "-undefined dynamic_lookup") - ELSE() - SET_TARGET_PROPERTIES(${PLUGIN_NAME} PROPERTIES LINK_FLAGS "-bundle_loader \"${CMAKE_BINARY_DIR}/lmms\"") ENDIF() - ADD_DEPENDENCIES(${PLUGIN_NAME} lmms) ENDIF(LMMS_BUILD_APPLE) IF(LMMS_BUILD_WIN32) add_custom_command( diff --git a/cmake/modules/CompileCache.cmake b/cmake/modules/CompileCache.cmake index ed4622bd921..56486e24ffb 100644 --- a/cmake/modules/CompileCache.cmake +++ b/cmake/modules/CompileCache.cmake @@ -1,25 +1,40 @@ -option(USE_COMPILE_CACHE "Use ccache or clcache for compilation" OFF) +option(USE_COMPILE_CACHE "Use a compiler cache for compilation" OFF) # Compatibility for old option name if(USE_CCACHE) set(USE_COMPILE_CACHE ON) endif() -if(USE_COMPILE_CACHE) - if(MSVC) - set(CACHE_TOOL_NAME clcache) - elseif(CMAKE_CXX_COMPILER_ID MATCHES "(GNU|AppleClang|Clang)") - set(CACHE_TOOL_NAME ccache) - else() - message(WARNING "Compile cache only available with MSVC or GNU") - endif() +if(NOT USE_COMPILE_CACHE) + return() +endif() - find_program(CACHE_TOOL ${CACHE_TOOL_NAME}) - if (CACHE_TOOL) - message(STATUS "Using ${CACHE_TOOL} found for caching") - set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE ${CACHE_TOOL}) - set_property(GLOBAL PROPERTY RULE_LAUNCH_LINK ${CACHE_TOOL}) - else() - message(WARNING "USE_COMPILE_CACHE enabled, but no ${CACHE_TOOL_NAME} found") +if(NOT CMAKE_CXX_COMPILER_ID MATCHES "(GNU|AppleClang|Clang|MSVC)") + message(WARNING "Compiler cache only available with MSVC or GNU") + return() +endif() + +set(CACHE_TOOL_NAME ccache) +find_program(CACHE_TOOL "${CACHE_TOOL_NAME}") +if(NOT CACHE_TOOL) + message(WARNING "USE_COMPILE_CACHE enabled, but no ${CACHE_TOOL_NAME} found") + return() +endif() + +if(MSVC) + # ccache doesn't support debug information in the PDB format. Setting the + # debug information format requires CMP0141, introduced with CMake 3.25, to + # be set to NEW prior to the initial `project` command. + if(CMAKE_VERSION VERSION_LESS "3.25") + message(WARNING "Use of compiler cache with MSVC requires at least CMake 3.25") + return() endif() + + set(CMAKE_MSVC_DEBUG_INFORMATION_FORMAT "$<$:Embedded>") endif() + +message(STATUS "Using ${CACHE_TOOL} for compiler caching") + +# TODO CMake 3.21: Use CMAKE___LAUNCHER variables instead +set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE "${CACHE_TOOL}") +set_property(GLOBAL PROPERTY RULE_LAUNCH_LINK "${CACHE_TOOL}") diff --git a/cmake/modules/FindFluidSynth.cmake b/cmake/modules/FindFluidSynth.cmake index fcc00cd7d0e..70c40b8d8fa 100644 --- a/cmake/modules/FindFluidSynth.cmake +++ b/cmake/modules/FindFluidSynth.cmake @@ -34,7 +34,7 @@ if(FluidSynth_INCLUDE_DIR AND FluidSynth_LIBRARY) if(VCPKG_INSTALLED_DIR) include(ImportedTargetHelpers) - _get_vcpkg_library_configs(FluidSynth_IMPLIB_RELEASE FluidSynth_IMPLIB_DEBUG "${FluidSynth_LIBRARY}") + get_vcpkg_library_configs(FluidSynth_IMPLIB_RELEASE FluidSynth_IMPLIB_DEBUG "${FluidSynth_LIBRARY}") else() set(FluidSynth_IMPLIB_RELEASE "${FluidSynth_LIBRARY}") endif() diff --git a/cmake/modules/FindLame.cmake b/cmake/modules/FindLame.cmake index c3fb09c5bf1..3017dc5aa7a 100644 --- a/cmake/modules/FindLame.cmake +++ b/cmake/modules/FindLame.cmake @@ -1,37 +1,31 @@ -# - Try to find LAME -# Once done this will define +# Copyright (c) 2023 Dominic Clark # -# Lame_FOUND - system has liblame -# Lame_INCLUDE_DIRS - the liblame include directory -# Lame_LIBRARIES - The liblame libraries -# mp3lame::mp3lame - an imported target providing lame +# Redistribution and use is allowed according to the terms of the New BSD license. +# For details see the accompanying COPYING-CMAKE-SCRIPTS file. -find_package(mp3lame CONFIG QUIET) +include(ImportedTargetHelpers) -if(TARGET mp3lame::mp3lame) - # Extract details for find_package_handle_standard_args - get_target_property(Lame_LIBRARIES mp3lame::mp3lame LOCATION) - get_target_property(Lame_INCLUDE_DIRS mp3lame::mp3lame INTERFACE_INCLUDE_DIRECTORIES) -else() - find_path(Lame_INCLUDE_DIRS lame/lame.h) - find_library(Lame_LIBRARIES mp3lame) - - list(APPEND Lame_DEFINITIONS HAVE_LIBMP3LAME=1) - - mark_as_advanced(Lame_INCLUDE_DIRS Lame_LIBRARIES Lame_DEFINITIONS) +find_package_config_mode_with_fallback(mp3lame mp3lame::mp3lame + LIBRARY_NAMES "mp3lame" + INCLUDE_NAMES "lame/lame.h" + PREFIX Lame +) - if(Lame_LIBRARIES AND Lame_INCLUDE_DIRS) - add_library(mp3lame::mp3lame UNKNOWN IMPORTED) +determine_version_from_source(Lame_VERSION mp3lame::mp3lame [[ + #include + #include - set_target_properties(mp3lame::mp3lame PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES "${Lame_INCLUDE_DIRS}" - INTERFACE_COMPILE_DEFINITIONS "${Lame_DEFINITIONS}" - IMPORTED_LOCATION "${Lame_LIBRARIES}" - ) - endif() -endif() + auto main() -> int + { + auto version = lame_version_t{}; + get_lame_version_numerical(&version); + std::cout << version.major << "." << version.minor; + } +]]) include(FindPackageHandleStandardArgs) + find_package_handle_standard_args(Lame - REQUIRED_VARS Lame_LIBRARIES Lame_INCLUDE_DIRS + REQUIRED_VARS Lame_LIBRARY Lame_INCLUDE_DIRS + VERSION_VAR Lame_VERSION ) diff --git a/cmake/modules/FindOggVorbis.cmake b/cmake/modules/FindOggVorbis.cmake index 79a9ab40657..cfbd73256c3 100644 --- a/cmake/modules/FindOggVorbis.cmake +++ b/cmake/modules/FindOggVorbis.cmake @@ -1,86 +1,68 @@ -# - Try to find the OggVorbis libraries -# Once done this will define +# Copyright (c) 2023 Dominic Clark # -# OGGVORBIS_FOUND - system has OggVorbis -# OGGVORBIS_VERSION - set either to 1 or 2 -# OGGVORBIS_INCLUDE_DIR - the OggVorbis include directory -# OGGVORBIS_LIBRARIES - The libraries needed to use OggVorbis -# OGG_LIBRARY - The Ogg library -# VORBIS_LIBRARY - The Vorbis library -# VORBISFILE_LIBRARY - The VorbisFile library -# VORBISENC_LIBRARY - The VorbisEnc library - -# Copyright (c) 2006, Richard Laerkaeng, -# -# Redistribution and use is allowed according to the terms of the BSD license. +# Redistribution and use is allowed according to the terms of the New BSD license. # For details see the accompanying COPYING-CMAKE-SCRIPTS file. - -include (CheckLibraryExists) - -find_path(VORBIS_INCLUDE_DIR vorbis/vorbisfile.h) -find_path(OGG_INCLUDE_DIR ogg/ogg.h) - -find_library(OGG_LIBRARY NAMES ogg) -find_library(VORBIS_LIBRARY NAMES vorbis) -find_library(VORBISFILE_LIBRARY NAMES vorbisfile) -find_library(VORBISENC_LIBRARY NAMES vorbisenc) - - -if (VORBIS_INCLUDE_DIR AND VORBIS_LIBRARY AND VORBISFILE_LIBRARY AND VORBISENC_LIBRARY) - set(OGGVORBIS_FOUND TRUE) - - set(OGGVORBIS_LIBRARIES ${OGG_LIBRARY} ${VORBIS_LIBRARY} ${VORBISFILE_LIBRARY} ${VORBISENC_LIBRARY}) - - set(_CMAKE_REQUIRED_LIBRARIES_TMP ${CMAKE_REQUIRED_LIBRARIES}) - set(CMAKE_REQUIRED_LIBRARIES ${CMAKE_REQUIRED_LIBRARIES} ${OGGVORBIS_LIBRARIES}) - check_library_exists(vorbis vorbis_bitrate_addblock "" HAVE_LIBVORBISENC2) - set(CMAKE_REQUIRED_LIBRARIES ${_CMAKE_REQUIRED_LIBRARIES_TMP}) - - if (HAVE_LIBVORBISENC2) - set (OGGVORBIS_VERSION 2) - else (HAVE_LIBVORBISENC2) - set (OGGVORBIS_VERSION 1) - endif (HAVE_LIBVORBISENC2) - -else (VORBIS_INCLUDE_DIR AND VORBIS_LIBRARY AND VORBISFILE_LIBRARY AND VORBISENC_LIBRARY) - set (OGGVORBIS_VERSION) - set(OGGVORBIS_FOUND FALSE) -endif (VORBIS_INCLUDE_DIR AND VORBIS_LIBRARY AND VORBISFILE_LIBRARY AND VORBISENC_LIBRARY) - - -if (OGGVORBIS_FOUND) - if (NOT OggVorbis_FIND_QUIETLY) - message(STATUS "Found OggVorbis: ${OGGVORBIS_LIBRARIES}") - endif (NOT OggVorbis_FIND_QUIETLY) -else (OGGVORBIS_FOUND) - if (OggVorbis_FIND_REQUIRED) - message(FATAL_ERROR "Could NOT find OggVorbis libraries") - endif (OggVorbis_FIND_REQUIRED) - if (NOT OggVorbis_FIND_QUITELY) - message(STATUS "Could NOT find OggVorbis libraries") - endif (NOT OggVorbis_FIND_QUITELY) -endif (OGGVORBIS_FOUND) - -#check_include_files(vorbis/vorbisfile.h HAVE_VORBISFILE_H) -#check_library_exists(ogg ogg_page_version "" HAVE_LIBOGG) -#check_library_exists(vorbis vorbis_info_init "" HAVE_LIBVORBIS) -#check_library_exists(vorbisfile ov_open "" HAVE_LIBVORBISFILE) -#check_library_exists(vorbisenc vorbis_info_clear "" HAVE_LIBVORBISENC) -#check_library_exists(vorbis vorbis_bitrate_addblock "" HAVE_LIBVORBISENC2) - -#if (HAVE_LIBOGG AND HAVE_VORBISFILE_H AND HAVE_LIBVORBIS AND HAVE_LIBVORBISFILE AND HAVE_LIBVORBISENC) -# message(STATUS "Ogg/Vorbis found") -# set (VORBIS_LIBS "-lvorbis -logg") -# set (VORBISFILE_LIBS "-lvorbisfile") -# set (VORBISENC_LIBS "-lvorbisenc") -# set (OGGVORBIS_FOUND TRUE) -# if (HAVE_LIBVORBISENC2) -# set (HAVE_VORBIS 2) -# else (HAVE_LIBVORBISENC2) -# set (HAVE_VORBIS 1) -# endif (HAVE_LIBVORBISENC2) -#else (HAVE_LIBOGG AND HAVE_VORBISFILE_H AND HAVE_LIBVORBIS AND HAVE_LIBVORBISFILE AND HAVE_LIBVORBISENC) -# message(STATUS "Ogg/Vorbis not found") -#endif (HAVE_LIBOGG AND HAVE_VORBISFILE_H AND HAVE_LIBVORBIS AND HAVE_LIBVORBISFILE AND HAVE_LIBVORBISENC) - +include(ImportedTargetHelpers) + +find_package_config_mode_with_fallback(Ogg Ogg::ogg + LIBRARY_NAMES "ogg" + INCLUDE_NAMES "ogg/ogg.h" + PKG_CONFIG ogg +) + +find_package_config_mode_with_fallback(Vorbis Vorbis::vorbis + LIBRARY_NAMES "vorbis" + INCLUDE_NAMES "vorbis/codec.h" + PKG_CONFIG vorbis + DEPENDS Ogg::ogg +) + +find_package_config_mode_with_fallback(Vorbis Vorbis::vorbisfile + LIBRARY_NAMES "vorbisfile" + INCLUDE_NAMES "vorbis/vorbisfile.h" + PKG_CONFIG vorbisfile + DEPENDS Vorbis::vorbis + PREFIX VorbisFile +) + +find_package_config_mode_with_fallback(Vorbis Vorbis::vorbisenc + LIBRARY_NAMES "vorbisenc" + INCLUDE_NAMES "vorbis/vorbisenc.h" + PKG_CONFIG vorbisenc + DEPENDS Vorbis::vorbis + PREFIX VorbisEnc +) + +determine_version_from_source(Vorbis_VERSION Vorbis::vorbis [[ + #include + #include + #include + + auto main() -> int + { + // Version string has the format "org name version" + const auto version = std::string_view{vorbis_version_string()}; + const auto nameBegin = version.find(' ') + 1; + const auto versionBegin = version.find(' ', nameBegin) + 1; + std::cout << version.substr(versionBegin); + } +]]) + +include(FindPackageHandleStandardArgs) + +find_package_handle_standard_args(OggVorbis + REQUIRED_VARS + Ogg_LIBRARY + Ogg_INCLUDE_DIRS + Vorbis_LIBRARY + Vorbis_INCLUDE_DIRS + VorbisFile_LIBRARY + VorbisFile_INCLUDE_DIRS + VorbisEnc_LIBRARY + VorbisEnc_INCLUDE_DIRS + # This only reports the Vorbis version - Ogg can have a different version, + # so if we ever care about that, it should be split off into a different + # find module. + VERSION_VAR Vorbis_VERSION +) diff --git a/cmake/modules/FindPortaudio.cmake b/cmake/modules/FindPortaudio.cmake index f9c7699f451..e7cfa1383fc 100644 --- a/cmake/modules/FindPortaudio.cmake +++ b/cmake/modules/FindPortaudio.cmake @@ -1,44 +1,34 @@ -# Copyright (c) 2022 Dominic Clark +# Copyright (c) 2023 Dominic Clark # # Redistribution and use is allowed according to the terms of the New BSD license. # For details see the accompanying COPYING-CMAKE-SCRIPTS file. -# Try config mode if possible -find_package(portaudio CONFIG QUIET) +include(ImportedTargetHelpers) -if(TARGET portaudio) - # Extract details for find_package_handle_standard_args - get_target_property(Portaudio_LIBRARY portaudio LOCATION) - get_target_property(Portaudio_INCLUDE_DIR portaudio INTERFACE_INCLUDE_DIRECTORIES) -else() - # Attempt to find PortAudio using PkgConfig, if we have it - find_package(PkgConfig QUIET) - if(PKG_CONFIG_FOUND) - pkg_check_modules(PORTAUDIO_PKG portaudio-2.0) - endif() - - # Find the library and headers using the results from PkgConfig as a guide - find_library(Portaudio_LIBRARY - NAMES "portaudio" - HINTS ${PORTAUDIO_PKG_LIBRARY_DIRS} - ) +find_package_config_mode_with_fallback(portaudio portaudio + LIBRARY_NAMES "portaudio" + INCLUDE_NAMES "portaudio.h" + PKG_CONFIG portaudio-2.0 + PREFIX Portaudio +) - find_path(Portaudio_INCLUDE_DIR - NAMES "portaudio.h" - HINTS ${PORTAUDIO_PKG_INCLUDE_DIRS} - ) +determine_version_from_source(Portaudio_VERSION portaudio [[ + #include + #include "portaudio.h" - # Create an imported target for PortAudio if we succeeded in finding it. - if(Portaudio_LIBRARY AND Portaudio_INCLUDE_DIR) - add_library(portaudio UNKNOWN IMPORTED) - set_target_properties(portaudio PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES "${Portaudio_INCLUDE_DIR}" - IMPORTED_LOCATION "${Portaudio_LIBRARY}" - ) - endif() -endif() + auto main() -> int + { + // Version number has the format 0xMMmmpp + const auto version = Pa_GetVersion(); + std::cout << ((version >> 16) & 0xff) + << "." << ((version >> 8) & 0xff) + << "." << ((version >> 0) & 0xff); + } +]]) include(FindPackageHandleStandardArgs) + find_package_handle_standard_args(Portaudio - REQUIRED_VARS Portaudio_LIBRARY Portaudio_INCLUDE_DIR + REQUIRED_VARS Portaudio_LIBRARY Portaudio_INCLUDE_DIRS + VERSION_VAR Portaudio_VERSION ) diff --git a/cmake/modules/FindSTK.cmake b/cmake/modules/FindSTK.cmake index 0718f5039a3..5564d24f8fd 100644 --- a/cmake/modules/FindSTK.cmake +++ b/cmake/modules/FindSTK.cmake @@ -1,39 +1,27 @@ -# Try config mode first -find_package(unofficial-libstk CONFIG QUIET) +include(ImportedTargetHelpers) -if(TARGET unofficial::libstk::libstk) - # Extract details for find_package_handle_standard_args - get_target_property(STK_LIBRARY unofficial::libstk::libstk LOCATION) - get_target_property(STK_INCLUDE_DIR unofficial::libstk::libstk INTERFACE_INCLUDE_DIRECTORIES) -else() - find_path(STK_INCLUDE_DIR - NAMES stk/Stk.h - PATH /usr/include /usr/local/include "${CMAKE_INSTALL_PREFIX}/include" "${CMAKE_FIND_ROOT_PATH}/include" - ) +# TODO CMake 3.18: Alias this target to something less hideous +find_package_config_mode_with_fallback(unofficial-libstk unofficial::libstk::libstk + LIBRARY_NAMES "stk" + INCLUDE_NAMES "stk/Stk.h" + LIBRARY_HINTS "/usr/lib" "/usr/local/lib" "${CMAKE_INSTALL_PREFIX}/lib" "${CMAKE_FIND_ROOT_PATH}/lib" + INCLUDE_HINTS "/usr/include" "/usr/local/include" "${CMAKE_INSTALL_PREFIX}/include" "${CMAKE_FIND_ROOT_PATH}/include" + PREFIX STK +) - find_library(STK_LIBRARY - NAMES stk - PATH /usr/lib /usr/local/lib "${CMAKE_INSTALL_PREFIX}/lib" "${CMAKE_FIND_ROOT_PATH}/lib" +# Find STK rawwave path +if(STK_INCLUDE_DIRS) + list(GET STK_INCLUDE_DIRS 0 STK_INCLUDE_DIR) + find_path(STK_RAWWAVE_ROOT + NAMES silence.raw sinewave.raw + HINTS "${STK_INCLUDE_DIR}/.." + PATH_SUFFIXES share/stk/rawwaves share/libstk/rawwaves ) - - if(STK_INCLUDE_DIR AND STK_LIBRARY) - # Yes, this target name is hideous, but it matches that provided by vcpkg - add_library(unofficial::libstk::libstk UNKNOWN IMPORTED) - set_target_properties(unofficial::libstk::libstk PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES "${STK_INCLUDE_DIR}" - IMPORTED_LOCATION "${STK_LIBRARY}" - ) - endif() endif() -# find STK rawwave path -find_path(STK_RAWWAVE_ROOT - NAMES silence.raw sinewave.raw - HINTS "${STK_INCLUDE_DIR}/.." - PATH_SUFFIXES share/stk/rawwaves share/libstk/rawwaves -) - include(FindPackageHandleStandardArgs) + find_package_handle_standard_args(STK REQUIRED_VARS STK_LIBRARY STK_INCLUDE_DIR + # STK doesn't appear to expose its version, so we can't pass it here ) diff --git a/cmake/modules/FindSamplerate.cmake b/cmake/modules/FindSamplerate.cmake index 53b69f6c722..683748c59f0 100644 --- a/cmake/modules/FindSamplerate.cmake +++ b/cmake/modules/FindSamplerate.cmake @@ -1,34 +1,35 @@ -# FindFFTW.cmake - Try to find FFTW3 -# Copyright (c) 2018 Lukas W -# This file is MIT licensed. -# See http://opensource.org/licenses/MIT +# Copyright (c) 2023 Dominic Clark +# +# Redistribution and use is allowed according to the terms of the New BSD license. +# For details see the accompanying COPYING-CMAKE-SCRIPTS file. -find_package(PkgConfig QUIET) -if(PKG_CONFIG_FOUND) - pkg_check_modules(SAMPLERATE_PKG samplerate) -endif() +include(ImportedTargetHelpers) -find_path(SAMPLERATE_INCLUDE_DIR - NAMES samplerate.h - PATHS ${SAMPLERATE_PKG_INCLUDE_DIRS} +find_package_config_mode_with_fallback(SampleRate SampleRate::samplerate + LIBRARY_NAMES "samplerate" "libsamplerate" "libsamplerate-0" + INCLUDE_NAMES "samplerate.h" + PKG_CONFIG samplerate + PREFIX Samplerate ) -set(SAMPLERATE_NAMES samplerate libsamplerate) -if(Samplerate_FIND_VERSION_MAJOR) - list(APPEND SAMPLERATE_NAMES libsamplerate-${Samplerate_FIND_VERSION_MAJOR}) -else() - list(APPEND SAMPLERATE_NAMES libsamplerate-0) -endif() +determine_version_from_source(Samplerate_VERSION SampleRate::samplerate [[ + #include + #include + #include -find_library(SAMPLERATE_LIBRARY - NAMES ${SAMPLERATE_NAMES} - PATHS ${SAMPLERATE_PKG_LIBRARY_DIRS} -) + auto main() -> int + { + // Version string has the format "name-version copyright" + const auto version = std::string_view{src_get_version()}; + const auto begin = version.find('-') + 1; + const auto end = version.find(' ', begin); + std::cout << version.substr(begin, end - begin); + } +]]) include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(SAMPLERATE DEFAULT_MSG SAMPLERATE_LIBRARY SAMPLERATE_INCLUDE_DIR) - -mark_as_advanced(SAMPLERATE_INCLUDE_DIR SAMPLERATE_LIBRARY ) -set(SAMPLERATE_LIBRARIES ${SAMPLERATE_LIBRARY} ) -set(SAMPLERATE_INCLUDE_DIRS ${SAMPLERATE_INCLUDE_DIR}) +find_package_handle_standard_args(Samplerate + REQUIRED_VARS Samplerate_LIBRARY Samplerate_INCLUDE_DIRS + VERSION_VAR Samplerate_VERSION +) diff --git a/cmake/modules/FindSndFile.cmake b/cmake/modules/FindSndFile.cmake index 28ebb7bb73f..d69fa6331ce 100644 --- a/cmake/modules/FindSndFile.cmake +++ b/cmake/modules/FindSndFile.cmake @@ -1,39 +1,34 @@ -# FindSndFile.cmake - Try to find libsndfile -# Copyright (c) 2018 Lukas W -# This file is MIT licensed. -# See http://opensource.org/licenses/MIT +# Copyright (c) 2023 Dominic Clark +# +# Redistribution and use is allowed according to the terms of the New BSD license. +# For details see the accompanying COPYING-CMAKE-SCRIPTS file. -# Try pkgconfig for hints -find_package(PkgConfig QUIET) -if(PKG_CONFIG_FOUND) - pkg_check_modules(SNDFILE_PKG sndfile) -endif(PKG_CONFIG_FOUND) -set(SndFile_DEFINITIONS ${SNDFILE_PKG_CFLAGS_OTHER}) +include(ImportedTargetHelpers) -if(WIN32) - # Try Vcpkg - find_package(LibSndFile ${SndFile_FIND_VERSION} CONFIG QUIET) - if(LibSndFile_FOUND) - get_target_property(LibSndFile_Location sndfile-shared LOCATION) - get_target_property(LibSndFile_Include_Path sndfile-shared INTERFACE_INCLUDE_DIRECTORIES) - get_filename_component(LibSndFile_Path LibSndFile_Location PATH) - endif() -endif() - -find_path(SNDFILE_INCLUDE_DIR - NAMES sndfile.h - PATHS ${SNDFILE_PKG_INCLUDE_DIRS} ${LibSndFile_Include_Path} +find_package_config_mode_with_fallback(SndFile SndFile::sndfile + LIBRARY_NAMES "sndfile" "libsndfile" "libsndfile-1" + INCLUDE_NAMES "sndfile.h" + PKG_CONFIG sndfile ) -find_library(SNDFILE_LIBRARY - NAMES sndfile libsndfile libsndfile-1 - PATHS ${SNDFILE_PKG_LIBRARY_DIRS} ${LibSndFile_Path} -) +determine_version_from_source(SndFile_VERSION SndFile::sndfile [[ + #include + #include + #include -find_package(PackageHandleStandardArgs) -find_package_handle_standard_args(SndFile DEFAULT_MSG SNDFILE_LIBRARY SNDFILE_INCLUDE_DIR) + auto main() -> int + { + // Version string has the format "name-version", optionally followed by "-exp" + const auto version = std::string_view{sf_version_string()}; + const auto begin = version.find('-') + 1; + const auto end = version.find('-', begin); + std::cout << version.substr(begin, end - begin); + } +]]) -set(SNDFILE_LIBRARIES ${SNDFILE_LIBRARY}) -set(SNDFILE_INCLUDE_DIRS ${SNDFILE_INCLUDE_DIR}) +include(FindPackageHandleStandardArgs) -mark_as_advanced(SNDFILE_LIBRARY SNDFILE_LIBRARIES SNDFILE_INCLUDE_DIR SNDFILE_INCLUDE_DIRS) +find_package_handle_standard_args(SndFile + REQUIRED_VARS SndFile_LIBRARY SndFile_INCLUDE_DIRS + VERSION_VAR SndFile_VERSION +) diff --git a/cmake/modules/ImportedTargetHelpers.cmake b/cmake/modules/ImportedTargetHelpers.cmake index 87b3aeedc33..d3d979901e9 100644 --- a/cmake/modules/ImportedTargetHelpers.cmake +++ b/cmake/modules/ImportedTargetHelpers.cmake @@ -1,7 +1,178 @@ +# ImportedTargetHelpers.cmake - various helper functions for use in find modules. +# +# Copyright (c) 2022-2023 Dominic Clark +# +# Redistribution and use is allowed according to the terms of the New BSD license. +# For details see the accompanying COPYING-CMAKE-SCRIPTS file. + +# If the version variable is not yet set, build the source linked to the target, +# run it, and set the version variable to the output. Useful for libraries which +# do not expose the version information in a header where it can be extracted +# with regular expressions, but do provide a function to get the version. +# +# Usage: +# determine_version_from_source( +# # The cache variable in which to store the computed version +# # The target which the source will link to +# # The source code to determine the version +# ) +function(determine_version_from_source _version_out _target _source) + # Return if we already know the version, or the target was not found + if(NOT "${${_version_out}}" STREQUAL "" OR NOT TARGET "${_target}") + return() + endif() + + # Return with a notice if cross-compiling, since we are unlikely to be able + # to run the compiled source + if(CMAKE_CROSSCOMPILING) + message( + "${_target} was found but the version could not be determined automatically.\n" + "Set the cache variable `${_version_out}` to the version you have installed." + ) + return() + endif() + + # Write the source code to a temporary file + string(SHA1 _source_hash "${_source}") + set(_source_file "${CMAKE_CURRENT_BINARY_DIR}/${_source_hash}.cpp") + file(WRITE "${_source_file}" "${_source}") + + # Build and run the temporary file to get the version + # TODO CMake 3.25: Use the new signature for try_run which has a NO_CACHE + # option and doesn't require separate file management. + try_run( + _dvfs_run_result _dvfs_compile_result "${CMAKE_CURRENT_BINARY_DIR}" + SOURCES "${_source_file}" + LINK_LIBRARIES "${_target}" + CXX_STANDARD 17 + RUN_OUTPUT_VARIABLE _run_output + COMPILE_OUTPUT_VARIABLE _compile_output + ) + + # Clean up the temporary file + file(REMOVE "${_source_file}") + + # Set the version if the run was successful, using a cache variable since + # this version check may be relatively expensive. Otherwise, log the error + # and inform the user. + if(_dvfs_run_result EQUAL "0") + set("${_version_out}" "${_run_output}" CACHE INTERNAL "Version of ${_target}") + else() + message(DEBUG "${_compile_output}") + message( + "${_target} was found but the version could not be determined automatically.\n" + "Set the cache variable `${_version_out}` to the version you have installed." + ) + endif() +endfunction() + +# Search for a package using config mode. If this fails to find the desired +# target, use the specified fallbacks and add the target if they succeed. Set +# the variables `prefix_LIBRARY`, `prefix_INCLUDE_DIRS`, and `prefix_VERSION` +# if found for the caller to pass to `find_package_handle_standard_args`. +# +# Usage: +# find_package_config_mode_with_fallback( +# # The package to search for with config mode +# # The target to expect from config mode, or define if not found +# LIBRARY_NAMES names... # Possible library names to search for as a fallback +# INCLUDE_NAMES names... # Possible header names to search for as a fallback +# [PKG_CONFIG ] # The pkg-config name to search for as a fallback +# [LIBRARY_HINTS hints...] # Locations to look for libraries +# [INCLUDE_HINTS hints...] # Locations to look for headers +# [DEPENDS dependencies...] # Dependencies of the target - added to INTERFACE_LINK_LIBRARIES, and will fail if not found +# [PREFIX ] # The prefix for result variables - defaults to the package name +# ) +function(find_package_config_mode_with_fallback _fpcmwf_PACKAGE_NAME _fpcmwf_TARGET_NAME) + # Parse remaining arguments + set(_options "") + set(_one_value_args "PKG_CONFIG" "PREFIX") + set(_multi_value_args "LIBRARY_NAMES" "LIBRARY_HINTS" "INCLUDE_NAMES" "INCLUDE_HINTS" "DEPENDS") + cmake_parse_arguments(PARSE_ARGV 2 _fpcmwf "${_options}" "${_one_value_args}" "${_multi_value_args}") + + # Compute result variable names + if(NOT DEFINED _fpcmwf_PREFIX) + set(_fpcmwf_PREFIX "${_fpcmwf_PACKAGE_NAME}") + endif() + set(_version_var "${_fpcmwf_PREFIX}_VERSION") + set(_library_var "${_fpcmwf_PREFIX}_LIBRARY") + set(_include_var "${_fpcmwf_PREFIX}_INCLUDE_DIRS") + + # Try config mode if possible + find_package("${_fpcmwf_PACKAGE_NAME}" CONFIG QUIET) + + if(TARGET "${_fpcmwf_TARGET_NAME}") + # Extract package details from existing target + get_target_property("${_library_var}" "${_fpcmwf_TARGET_NAME}" LOCATION) + get_target_property("${_include_var}" "${_fpcmwf_TARGET_NAME}" INTERFACE_INCLUDE_DIRECTORIES) + if(DEFINED "${_fpcmwf_PACKAGE_NAME}_VERSION") + set("${_version_var}" "${${_fpcmwf_PACKAGE_NAME}_VERSION}") + endif() + else() + # Check whether the dependencies exist + foreach(_dependency IN LISTS _fpcmwf_DEPENDS) + if(NOT TARGET "${_dependency}") + return() + endif() + endforeach() + + # Attempt to find the package using pkg-config, if we have it and it was requested + set(_pkg_config_prefix "${_fpcmwf_PKG_CONFIG}_PKG") + if(DEFINED _fpcmwf_PKG_CONFIG) + find_package(PkgConfig QUIET) + if(PKG_CONFIG_FOUND) + pkg_check_modules("${_pkg_config_prefix}" QUIET "${_fpcmwf_PKG_CONFIG}") + if("${${_pkg_config_prefix}_FOUND}") + set("${_version_var}" "${${_pkg_config_prefix}_VERSION}") + endif() + endif() + endif() + + # Find the library and headers using the results from pkg-config as a guide + find_library("${_library_var}" + NAMES ${_fpcmwf_LIBRARY_NAMES} + HINTS ${${_pkg_config_prefix}_LIBRARY_DIRS} ${_fpcmwf_LIBRARY_HINTS} + ) + + find_path("${_include_var}" + NAMES ${_fpcmwf_INCLUDE_NAMES} + HINTS ${${_pkg_config_prefix}_INCLUDE_DIRS} ${_fpcmwf_INCLUDE_HINTS} + ) + + # Create an imported target if we succeeded in finding the package + if(${_library_var} AND ${_include_var}) + add_library("${_fpcmwf_TARGET_NAME}" UNKNOWN IMPORTED) + set_target_properties("${_fpcmwf_TARGET_NAME}" PROPERTIES + IMPORTED_LOCATION "${${_library_var}}" + INTERFACE_INCLUDE_DIRECTORIES "${${_include_var}}" + INTERFACE_LINK_LIBRARIES "${_fpcmwf_DEPENDS}" + ) + endif() + + mark_as_advanced("${_library_var}" "${_include_var}") + endif() + + # Return results to caller + if(DEFINED "${_version_var}") + set("${_version_var}" "${${_version_var}}" PARENT_SCOPE) + else() + unset("${_version_var}" PARENT_SCOPE) + endif() + set("${_library_var}" "${${_library_var}}" PARENT_SCOPE) + set("${_include_var}" "${${_include_var}}" PARENT_SCOPE) +endfunction() + # Given a library in vcpkg, find appropriate debug and release versions. If only -# one version exists, it is used as the release version, and the debug version -# is not set. -function(_get_vcpkg_library_configs _release_out _debug_out _library) +# one version exists, use it as the release version, and do not set the debug +# version. +# +# Usage: +# get_vcpkg_library_configs( +# # Variable in which to store the path to the release version of the library +# # Variable in which to store the path to the debug version of the library +# # Known path to some version of the library +# ) +function(get_vcpkg_library_configs _release_out _debug_out _library) # We want to do all operations within the vcpkg directory file(RELATIVE_PATH _lib_relative "${VCPKG_INSTALLED_DIR}" "${_library}") diff --git a/cmake/modules/InstallDependencies.cmake b/cmake/modules/InstallDependencies.cmake index 167a93f351f..29e5b207c18 100644 --- a/cmake/modules/InstallDependencies.cmake +++ b/cmake/modules/InstallDependencies.cmake @@ -1,7 +1,8 @@ include(GetPrerequisites) include(CMakeParseArguments) -# Project's cmake_minimum_required doesn't always propagate +# Project's cmake_minimum_required doesn't propagate to install scripts +cmake_policy(PUSH) cmake_policy(SET CMP0057 NEW) # Support new if() IN_LIST operator. function(make_absolute var) @@ -182,3 +183,5 @@ function(FIND_PREREQUISITES target RESULT_VAR exclude_system recurse set(${RESULT_VAR} ${RESULTS} PARENT_SCOPE) endfunction() + +cmake_policy(POP) diff --git a/data/themes/classic/edit_tangent.png b/data/themes/classic/edit_tangent.png new file mode 100644 index 00000000000..438673b33a9 Binary files /dev/null and b/data/themes/classic/edit_tangent.png differ diff --git a/data/themes/classic/style.css b/data/themes/classic/style.css index c73da5a2b58..c4fe0e153ca 100644 --- a/data/themes/classic/style.css +++ b/data/themes/classic/style.css @@ -8,7 +8,7 @@ QLabel, QTreeWidget, QListWidget, QGroupBox, QMenuBar { } QMdiArea { - background-image: url(resources:background_artwork.png); + background-image: url("resources:background_artwork.png"); } lmms--gui--Knob { @@ -22,6 +22,7 @@ lmms--gui--AutomationEditor { qproperty-backgroundShade: rgba(255, 255, 255, 15); qproperty-nodeInValueColor: rgba(255, 119, 175, 150); qproperty-nodeOutValueColor: rgba(129, 231, 181, 150); + qproperty-nodeTangentLineColor: rgba(200, 200, 200, 255); qproperty-crossColor: rgb( 255, 51, 51 ); /* Grid colors */ qproperty-lineColor: rgba(128, 128, 128, 80); @@ -204,7 +205,7 @@ lmms--gui--Oscilloscope { lmms--gui--CPULoadWidget { border: none; - background: url(resources:cpuload_bg.png); + background: url("resources:cpuload_bg.png"); qproperty-stepSize: 4; } @@ -321,14 +322,14 @@ QScrollBar::up-arrow:vertical, QScrollBar::down-arrow:vertical { height: 5px; } -QScrollBar::left-arrow:horizontal { background-image: url(resources:sbarrow_left.png);} -QScrollBar::right-arrow:horizontal { background-image: url(resources:sbarrow_right.png);} -QScrollBar::up-arrow:vertical { background-image: url(resources:sbarrow_up.png);} -QScrollBar::down-arrow:vertical { background-image: url(resources:sbarrow_down.png);} -QScrollBar::left-arrow:horizontal:disabled { background-image: url(resources:sbarrow_left_d.png);} -QScrollBar::right-arrow:horizontal:disabled { background-image: url(resources:sbarrow_right_d.png);} -QScrollBar::up-arrow:vertical:disabled { background-image: url(resources:sbarrow_up_d.png);} -QScrollBar::down-arrow:vertical:disabled { background-image: url(resources:sbarrow_down_d.png);} +QScrollBar::left-arrow:horizontal { background-image: url("resources:sbarrow_left.png");} +QScrollBar::right-arrow:horizontal { background-image: url("resources:sbarrow_right.png");} +QScrollBar::up-arrow:vertical { background-image: url("resources:sbarrow_up.png");} +QScrollBar::down-arrow:vertical { background-image: url("resources:sbarrow_down.png");} +QScrollBar::left-arrow:horizontal:disabled { background-image: url("resources:sbarrow_left_d.png");} +QScrollBar::right-arrow:horizontal:disabled { background-image: url("resources:sbarrow_right_d.png");} +QScrollBar::up-arrow:vertical:disabled { background-image: url("resources:sbarrow_up_d.png");} +QScrollBar::down-arrow:vertical:disabled { background-image: url("resources:sbarrow_down_d.png");} /* background for song editor and pattern editor */ @@ -366,7 +367,7 @@ lmms--gui--TrackOperationsWidget > QPushButton { } lmms--gui--TrackOperationsWidget > QPushButton::menu-indicator { - image: url(resources:trackop.png); + image: url("resources:trackop.png"); subcontrol-origin: padding; subcontrol-position: center; position: relative; @@ -374,12 +375,12 @@ lmms--gui--TrackOperationsWidget > QPushButton::menu-indicator { } lmms--gui--TrackOperationsWidget > QPushButton::menu-indicator:hover { - image: url(resources:trackop_h.png); + image: url("resources:trackop_h.png"); } lmms--gui--TrackOperationsWidget > QPushButton::menu-indicator:pressed, lmms--gui--TrackOperationsWidget > QPushButton::menu-indicator:checked { - image: url(resources:trackop_c.png); + image: url("resources:trackop_c.png"); position: relative; top: 2px; } @@ -408,7 +409,7 @@ lmms--gui--AutomatableSlider::groove:vertical { lmms--gui--AutomatableSlider::handle:vertical { background: none; - border-image: url(resources:main_slider.png); + border-image: url("resources:main_slider.png"); width: 26px; height: 10px; border-radius: 2px; @@ -427,7 +428,7 @@ lmms--gui--AutomatableSlider::groove:horizontal { lmms--gui--AutomatableSlider::handle:horizontal { background: none; - border-image: url(resources:horizontal_slider.png); + border-image: url("resources:horizontal_slider.png"); width: 10px; height: 26px; border-radius: 2px; @@ -974,6 +975,11 @@ lmms--gui--CompressorControlDialog lmms--gui--Knob { qproperty-lineWidth: 2; } +lmms--gui--BarModelEditor { + qproperty-backgroundBrush: rgba(28, 73, 51, 255); + qproperty-barBrush: rgba(17, 136, 71, 255); +} + /* palette information */ lmms--gui--LmmsPalette { diff --git a/data/themes/default/edit_tangent.png b/data/themes/default/edit_tangent.png new file mode 100644 index 00000000000..7bc4000947d Binary files /dev/null and b/data/themes/default/edit_tangent.png differ diff --git a/data/themes/default/lcd_21pink.png b/data/themes/default/lcd_21pink.png index c2009eedac6..719730427bc 100644 Binary files a/data/themes/default/lcd_21pink.png and b/data/themes/default/lcd_21pink.png differ diff --git a/data/themes/default/style.css b/data/themes/default/style.css index 854d4a4c39e..80d56a4bbe9 100644 --- a/data/themes/default/style.css +++ b/data/themes/default/style.css @@ -3,12 +3,13 @@ ********************/ /* most foreground text items */ -QLabel, QTreeWidget, QListWidget, QGroupBox, QMenuBar { +QLabel, QTreeWidget, QListWidget, QGroupBox, QMenuBar, QCheckBox { color: #d1d8e4; } QTreeView { outline: none; + alternate-background-color: #111314; } QTreeWidget::item { @@ -56,6 +57,7 @@ lmms--gui--AutomationEditor { qproperty-backgroundShade: rgba(255, 255, 255, 15); qproperty-nodeInValueColor: rgba(103, 73, 194, 150); qproperty-nodeOutValueColor: rgba(125, 40, 40, 150); + qproperty-nodeTangentLineColor: rgba(200, 200, 200, 255); qproperty-crossColor: rgba(215, 210, 254, 150); /* Grid colors */ qproperty-lineColor: #292929; @@ -235,7 +237,7 @@ lmms--gui--Oscilloscope { lmms--gui--CPULoadWidget { border: none; - background: url(resources:cpuload_bg.png); + background: url("resources:cpuload_bg.png"); qproperty-stepSize: 1; } @@ -354,16 +356,16 @@ QScrollBar::up-arrow:vertical, QScrollBar::down-arrow:vertical { margin-left: 3px; } -QScrollBar::left-arrow:horizontal { background-image: url(resources:sbarrow_left.png);} -QScrollBar::right-arrow:horizontal { background-image: url(resources:sbarrow_right.png);} -QScrollBar::up-arrow:vertical { background-image: url(resources:sbarrow_up.png);} -QScrollBar::down-arrow:vertical { background-image: url(resources:sbarrow_down.png);} -QScrollBar::left-arrow:horizontal:disabled { background-image: url(resources:sbarrow_left_d.png);} -QScrollBar::right-arrow:horizontal:disabled { background-image: url(resources:sbarrow_right_d.png);} -QScrollBar::up-arrow:vertical:disabled { background-image: url(resources:sbarrow_up_d.png);} -QScrollBar::down-arrow:vertical:disabled { background-image: url(resources:sbarrow_down_d.png);} -lmms--gui--EffectRackView QScrollBar::up-arrow:vertical:disabled { background-image: url(resources:sbarrow_up.png);} -lmms--gui--EffectRackView QScrollBar::down-arrow:vertical:disabled { background-image: url(resources:sbarrow_down.png);} +QScrollBar::left-arrow:horizontal { background-image: url("resources:sbarrow_left.png");} +QScrollBar::right-arrow:horizontal { background-image: url("resources:sbarrow_right.png");} +QScrollBar::up-arrow:vertical { background-image: url("resources:sbarrow_up.png");} +QScrollBar::down-arrow:vertical { background-image: url("resources:sbarrow_down.png");} +QScrollBar::left-arrow:horizontal:disabled { background-image: url("resources:sbarrow_left_d.png");} +QScrollBar::right-arrow:horizontal:disabled { background-image: url("resources:sbarrow_right_d.png");} +QScrollBar::up-arrow:vertical:disabled { background-image: url("resources:sbarrow_up_d.png");} +QScrollBar::down-arrow:vertical:disabled { background-image: url("resources:sbarrow_down_d.png");} +lmms--gui--EffectRackView QScrollBar::up-arrow:vertical:disabled { background-image: url("resources:sbarrow_up.png");} +lmms--gui--EffectRackView QScrollBar::down-arrow:vertical:disabled { background-image: url("resources:sbarrow_down.png");} /* background for song editor and pattern editor */ @@ -400,7 +402,7 @@ lmms--gui--TrackOperationsWidget > QPushButton { } lmms--gui--TrackOperationsWidget > QPushButton::menu-indicator { - image: url(resources:trackop.png); + image: url("resources:trackop.png"); subcontrol-origin: padding; subcontrol-position: center; position: relative; @@ -409,7 +411,7 @@ lmms--gui--TrackOperationsWidget > QPushButton::menu-indicator { lmms--gui--TrackOperationsWidget > QPushButton::menu-indicator:pressed, lmms--gui--TrackOperationsWidget > QPushButton::menu-indicator:checked { - image: url(resources:trackop.png); + image: url("resources:trackop.png"); position: relative; top: 2px; } @@ -432,7 +434,7 @@ lmms--gui--AutomatableSlider::groove:vertical { lmms--gui--AutomatableSlider::handle:vertical { background: none; - border-image: url(resources:main_slider.png); + border-image: url("resources:main_slider.png"); width: 26px; height: 10px; border-radius: 2px; @@ -451,7 +453,7 @@ lmms--gui--AutomatableSlider::groove:horizontal { lmms--gui--AutomatableSlider::handle:horizontal { background: none; - border-image: url(resources:horizontal_slider.png); + border-image: url("resources:horizontal_slider.png"); width: 10px; height: 26px; border-radius: 2px; @@ -464,6 +466,10 @@ lmms--gui--EffectSelectDialog QScrollArea { background: #262b30; } +lmms--gui--SetupDialog QScrollArea { + border: 0px; +} + /* the inner boxes in LADSPA effect windows */ lmms--gui--EffectControlDialog QGroupBox { @@ -537,7 +543,7 @@ QToolButton:checked { border-top: 1px solid #1b1f22; border-bottom: 1px solid #4a515e; background: qlineargradient(spread:reflect, x1:0, y1:0, x2:0, y2:1, stop:0 #1b1f22, stop:1 #13161a); - background-image: url(resources:shadow_p.png); + background-image: url("resources:shadow_p.png"); } /* buttons with combined menu */ @@ -586,7 +592,7 @@ lmms--gui--TrackLabelButton:pressed { lmms--gui--TrackLabelButton:checked { border: 1px solid #485059; background: #1C1F24; - background-image: url(resources:track_shadow_p.png); + background-image: url("resources:track_shadow_p.png"); border-radius: none; font-size: 11px; font-weight: normal; @@ -596,7 +602,7 @@ lmms--gui--TrackLabelButton:checked { lmms--gui--TrackLabelButton:checked:pressed { border: 1px solid #2f353b; background: #0e1012; - background-image: url(resources:track_shadow_p.png); + background-image: url("resources:track_shadow_p.png"); font-size: 11px; padding: 2px 1px; font-weight: solid; @@ -713,10 +719,6 @@ lmms--gui--TimeLineWidget { qproperty-barNumberColor: rgb( 192, 192, 192 ); } -QTreeView { - alternate-background-color: #111314; -} - lmms--gui--TrackContainerView QLabel { background: none; @@ -1017,6 +1019,11 @@ lmms--gui--CompressorControlDialog lmms--gui--Knob { qproperty-lineWidth: 2; } +lmms--gui--BarModelEditor { + qproperty-backgroundBrush: rgba(28, 73, 51, 255); + qproperty-barBrush: rgba(17, 136, 71, 255); +} + /* palette information */ lmms--gui--LmmsPalette { diff --git a/include/AudioDeviceSetupWidget.h b/include/AudioDeviceSetupWidget.h index f56fa07a6ee..acc99602dba 100644 --- a/include/AudioDeviceSetupWidget.h +++ b/include/AudioDeviceSetupWidget.h @@ -2,6 +2,7 @@ * AudioDeviceSetupWidget.h - Base class for audio device setup widgets * * Copyright (c) 2004-2015 Tobias Doerffel + * Copyright (c) 2023- Michael Gregorius * * This file is part of LMMS - https://lmms.io * @@ -25,12 +26,12 @@ #ifndef LMMS_GUI_AUDIO_DEVICE_SETUP_WIDGET_H #define LMMS_GUI_AUDIO_DEVICE_SETUP_WIDGET_H -#include "TabWidget.h" +#include namespace lmms::gui { -class AudioDeviceSetupWidget : public TabWidget +class AudioDeviceSetupWidget : public QGroupBox { Q_OBJECT public: diff --git a/include/AudioJack.h b/include/AudioJack.h index 164258e5fbd..6efb262ed40 100644 --- a/include/AudioJack.h +++ b/include/AudioJack.h @@ -57,42 +57,37 @@ class AudioJack : public QObject, public AudioDevice { Q_OBJECT public: - AudioJack( bool & _success_ful, AudioEngine* audioEngine ); + AudioJack(bool& successful, AudioEngine* audioEngine); ~AudioJack() override; // this is to allow the jack midi connection to use the same jack client connection // the jack callback is handled here, we call the midi client so that it can read // it's midi data during the callback - AudioJack * addMidiClient(MidiJack *midiClient); + AudioJack* addMidiClient(MidiJack* midiClient); void removeMidiClient() { m_midiClient = nullptr; } - jack_client_t * jackClient() {return m_client;}; + jack_client_t* jackClient() { return m_client; }; inline static QString name() { - return QT_TRANSLATE_NOOP( "AudioDeviceSetupWidget", - "JACK (JACK Audio Connection Kit)" ); + return QT_TRANSLATE_NOOP("AudioDeviceSetupWidget", "JACK (JACK Audio Connection Kit)"); } - -class setupWidget : public gui::AudioDeviceSetupWidget + class setupWidget : public gui::AudioDeviceSetupWidget { public: - setupWidget( QWidget * _parent ); + setupWidget(QWidget* parent); ~setupWidget() override; void saveSettings() override; private: - QLineEdit * m_clientName; - gui::LcdSpinBox * m_channels; - - } ; - + QLineEdit* m_clientName; + gui::LcdSpinBox* m_channels; + }; private slots: void restartAfterZombified(); - private: bool initJackClient(); @@ -100,45 +95,41 @@ private slots: void stopProcessing() override; void applyQualitySettings() override; - void registerPort( AudioPort * _port ) override; - void unregisterPort( AudioPort * _port ) override; - void renamePort( AudioPort * _port ) override; + void registerPort(AudioPort* port) override; + void unregisterPort(AudioPort* port) override; + void renamePort(AudioPort* port) override; - int processCallback( jack_nframes_t _nframes, void * _udata ); + int processCallback(jack_nframes_t nframes); - static int staticProcessCallback( jack_nframes_t _nframes, - void * _udata ); - static void shutdownCallback( void * _udata ); + static int staticProcessCallback(jack_nframes_t nframes, void* udata); + static void shutdownCallback(void* _udata); - - jack_client_t * m_client; + jack_client_t* m_client; bool m_active; std::atomic m_stopped; - std::atomic m_midiClient; - std::vector m_outputPorts; - jack_default_audio_sample_t * * m_tempOutBufs; - surroundSampleFrame * m_outBuf; + std::atomic m_midiClient; + std::vector m_outputPorts; + jack_default_audio_sample_t** m_tempOutBufs; + surroundSampleFrame* m_outBuf; f_cnt_t m_framesDoneInCurBuf; f_cnt_t m_framesToDoInCurBuf; - #ifdef AUDIO_PORT_SUPPORT struct StereoPort { - jack_port_t * ports[2]; - } ; + jack_port_t* ports[2]; + }; - using JackPortMap = QMap; + using JackPortMap = QMap; JackPortMap m_portMap; #endif signals: void zombified(); - -} ; +}; } // namespace lmms diff --git a/include/AutomationClip.h b/include/AutomationClip.h index ceb5611c958..0b49978c7f7 100644 --- a/include/AutomationClip.h +++ b/include/AutomationClip.h @@ -46,6 +46,7 @@ class TimePos; namespace gui { class AutomationClipView; +class AutomationEditor; } // namespace gui @@ -111,6 +112,13 @@ class LMMS_EXPORT AutomationClip : public Clip void resetNodes(const int tick0, const int tick1); + /** + * @brief Resets the tangents from the nodes between the given ticks + * @param Int first tick of the range + * @param Int second tick of the range + */ + void resetTangents(const int tick0, const int tick1); + void recordValue(TimePos time, float value); TimePos setDragValue( const TimePos & time, @@ -151,6 +159,17 @@ class LMMS_EXPORT AutomationClip : public Clip return m_timeMap.isEmpty() == false; } + static bool supportsTangentEditing(ProgressionType pType) + { + // Update function if we have new progression types that support tangent editing + return pType == ProgressionType::CubicHermite; + } + + inline bool canEditTangents() const + { + return supportsTangentEditing(m_progressionType); + } + float valueAt( const TimePos & _time ) const; float *valuesAfter( const TimePos & _time ) const; @@ -219,6 +238,9 @@ public slots: bool m_dragging; bool m_dragKeepOutValue; // Should we keep the current dragged node's outValue? float m_dragOutValue; // The outValue of the dragged node's + bool m_dragLockedTan; // If the dragged node has it's tangents locked + float m_dragInTan; // The dragged node's inTangent + float m_dragOutTan; // The dragged node's outTangent bool m_isRecording; float m_lastRecordedValue; @@ -230,6 +252,7 @@ public slots: friend class gui::AutomationClipView; friend class AutomationNode; + friend class gui::AutomationEditor; } ; @@ -261,6 +284,11 @@ inline float OUTTAN(AutomationClip::TimemapIterator it) return it->getOutTangent(); } +inline float LOCKEDTAN(AutomationClip::TimemapIterator it) +{ + return it->lockedTangents(); +} + inline int POS(AutomationClip::TimemapIterator it) { return it.key(); diff --git a/include/AutomationEditor.h b/include/AutomationEditor.h index ecefa8b26f1..dad0e4916f4 100644 --- a/include/AutomationEditor.h +++ b/include/AutomationEditor.h @@ -63,6 +63,7 @@ class AutomationEditor : public QWidget, public JournallingObject Q_PROPERTY(QColor lineColor MEMBER m_lineColor) Q_PROPERTY(QColor nodeInValueColor MEMBER m_nodeInValueColor) Q_PROPERTY(QColor nodeOutValueColor MEMBER m_nodeOutValueColor) + Q_PROPERTY(QColor nodeTangentLineColor MEMBER m_nodeTangentLineColor) Q_PROPERTY(QBrush scaleColor MEMBER m_scaleColor) Q_PROPERTY(QBrush graphColor MEMBER m_graphColor) Q_PROPERTY(QColor crossColor MEMBER m_crossColor) @@ -91,7 +92,8 @@ class AutomationEditor : public QWidget, public JournallingObject { Draw, Erase, - DrawOutValues + DrawOutValues, + EditTangents }; public slots: @@ -118,6 +120,13 @@ public slots: inline void drawLevelTick(QPainter & p, int tick, float value); timeMap::iterator getNodeAt(int x, int y, bool outValue = false, int r = 5); + /** + * @brief Given a mouse X coordinate, returns a timeMap::iterator that points to + * the closest node. + * @param Int X coordinate + * @return timeMap::iterator with the closest node or timeMap.end() if there are no nodes. + */ + timeMap::iterator getClosestNode(int x); void drawLine( int x0, float y0, int x1, float y1 ); bool fineTuneValue(timeMap::iterator node, bool editingOutValue); @@ -133,6 +142,12 @@ protected slots: void setEditMode(int mode); void setProgressionType(AutomationClip::ProgressionType type); + /** + * @brief This method handles the AutomationEditorWindow event of changing + * progression types. After that, it calls updateEditTanButton so the edit + * tangents button is updated accordingly + * @param Int New progression type + */ void setProgressionType(int type); void setTension(); @@ -153,7 +168,9 @@ protected slots: EraseValues, MoveOutValue, ResetOutValues, - DrawLine + DrawLine, + MoveTangent, + ResetTangents } ; // some constants... @@ -173,6 +190,7 @@ protected slots: static QPixmap * s_toolDraw; static QPixmap * s_toolErase; static QPixmap * s_toolDrawOut; + static QPixmap * s_toolEditTangents; static QPixmap * s_toolMove; static QPixmap * s_toolYFlip; static QPixmap * s_toolXFlip; @@ -215,6 +233,11 @@ protected slots: // Time position (key) of automation node whose outValue is being dragged int m_draggedOutValueKey; + // The tick from the node whose tangent is being dragged + int m_draggedTangentTick; + // Whether the tangent being dragged is the InTangent or OutTangent + bool m_draggedOutTangent; + EditMode m_editMode; bool m_mouseDownLeft; @@ -225,6 +248,7 @@ protected slots: void drawCross(QPainter & p ); void drawAutomationPoint( QPainter & p, timeMap::iterator it ); + void drawAutomationTangents(QPainter& p, timeMap::iterator it); bool inPatternEditor(); QColor m_barLineColor; @@ -233,6 +257,7 @@ protected slots: QBrush m_graphColor; QColor m_nodeInValueColor; QColor m_nodeOutValueColor; + QColor m_nodeTangentLineColor; QBrush m_scaleColor; QColor m_crossColor; QColor m_backgroundShade; @@ -285,8 +310,21 @@ protected slots: private slots: void updateWindowTitle(); + void setProgressionType(int progType); + /** + * @brief The Edit Tangent edit mode should only be available for + * Cubic Hermite progressions, so this method is responsable for disabling it + * for other edit modes and reenabling it when it changes back to the Edit Tangent + * mode. + */ + void updateEditTanButton(); private: + QAction* m_drawAction; + QAction* m_eraseAction; + QAction* m_drawOutAction; + QAction* m_editTanAction; + QAction* m_discreteAction; QAction* m_linearAction; QAction* m_cubicHermiteAction; diff --git a/include/AutomationNode.h b/include/AutomationNode.h index a922109e604..60154332f2c 100644 --- a/include/AutomationNode.h +++ b/include/AutomationNode.h @@ -125,6 +125,22 @@ class AutomationNode m_outTangent = tangent; } + /** + * @brief Checks if the tangents from the node are locked + */ + inline const bool lockedTangents() const + { + return m_lockedTangents; + } + + /** + * @brief Locks or Unlocks the tangents from this node + */ + inline void setLockedTangents(bool b) + { + m_lockedTangents = b; + } + /** * @brief Sets the clip this node belongs to * @param AutomationClip* clip that m_clip will be @@ -152,6 +168,11 @@ class AutomationNode // outValue are equal, inTangent and outTangent are equal too. float m_inTangent; float m_outTangent; + + // If the tangents were edited manually, this will be true. That way + // the tangents from this node will not be recalculated. It's set back + // to false if the tangents are reset. + bool m_lockedTangents; }; } // namespace lmms diff --git a/include/BarModelEditor.h b/include/BarModelEditor.h new file mode 100644 index 00000000000..79a320a7d05 --- /dev/null +++ b/include/BarModelEditor.h @@ -0,0 +1,76 @@ +/* + * BarModelEditor.h - edit model values using a bar display + * + * Copyright (c) 2023-now Michael Gregorius + * + * 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 LMMS_GUI_BAR_MODEL_EDITOR_H +#define LMMS_GUI_BAR_MODEL_EDITOR_H + +#include "FloatModelEditorBase.h" + + +namespace lmms::gui +{ + +class LMMS_EXPORT BarModelEditor : public FloatModelEditorBase +{ + Q_OBJECT + +public: + Q_PROPERTY(QBrush backgroundBrush READ getBackgroundBrush WRITE setBackgroundBrush) + Q_PROPERTY(QBrush barBrush READ getBarBrush WRITE setBarBrush) + Q_PROPERTY(QColor textColor READ getTextColor WRITE setTextColor) + + BarModelEditor(QString text, FloatModel * floatModel, QWidget * parent = nullptr); + + // Define how the widget will behave in a layout + QSizePolicy sizePolicy() const; + + QSize minimumSizeHint() const override; + + QSize sizeHint() const override; + + QBrush const & getBackgroundBrush() const; + void setBackgroundBrush(QBrush const & backgroundBrush); + + QBrush const & getBarBrush() const; + void setBarBrush(QBrush const & barBrush); + + QColor const & getTextColor() const; + void setTextColor(QColor const & textColor); + +protected: + void paintEvent(QPaintEvent *event) override; + +private: + QString const m_text; + + QBrush m_backgroundBrush; + QBrush m_barBrush; + QColor m_textColor; +}; + +} // namespace lmms::gui + +#endif // LMMS_GUI_BAR_MODEL_EDITOR_H diff --git a/include/CPULoadWidget.h b/include/CPULoadWidget.h index dfa5bac73da..bed10b05eb7 100644 --- a/include/CPULoadWidget.h +++ b/include/CPULoadWidget.h @@ -68,7 +68,7 @@ protected slots: QTimer m_updateTimer; - int m_stepSize; + int m_stepSize = 1; } ; diff --git a/include/Flags.h b/include/Flags.h index 76106dde660..62a5f8af8ba 100644 --- a/include/Flags.h +++ b/include/Flags.h @@ -48,8 +48,8 @@ class Flags m_value{value} {} - constexpr auto testAll(Flags flags) const -> bool { return *this & flags == flags; } - constexpr auto testAny(Flags flags) const -> bool { return *this & flags != Flags{}; } + constexpr auto testAll(Flags flags) const -> bool { return (*this & flags) == flags; } + constexpr auto testAny(Flags flags) const -> bool { return (*this & flags) != Flags{}; } constexpr auto testFlag(EnumType flag) const -> bool { return static_cast(*this & flag); } constexpr auto operator~() const -> Flags { return Flags{~m_value}; } diff --git a/include/FloatModelEditorBase.h b/include/FloatModelEditorBase.h new file mode 100644 index 00000000000..72f1450de5a --- /dev/null +++ b/include/FloatModelEditorBase.h @@ -0,0 +1,121 @@ +/* + * FloatModelEditorBase.h - Base editor for float models + * + * Copyright (c) 2004-2008 Tobias Doerffel + * Copyright (c) 2023 Michael Gregorius + * + * 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_FLOAT_MODEL_EDITOR_BASE_H +#define LMMS_GUI_FLOAT_MODEL_EDITOR_BASE_H + +#include +#include + +#include "AutomatableModelView.h" + + +namespace lmms::gui +{ + +class SimpleTextFloat; + +class LMMS_EXPORT FloatModelEditorBase : public QWidget, public FloatModelView +{ + Q_OBJECT + + mapPropertyFromModel(bool, isVolumeKnob, setVolumeKnob, m_volumeKnob); + mapPropertyFromModel(float, volumeRatio, setVolumeRatio, m_volumeRatio); + + void initUi(const QString & name); //!< to be called by ctors + +public: + enum class DirectionOfManipulation + { + Vertical, + Horizontal + }; + + FloatModelEditorBase(DirectionOfManipulation directionOfManipulation = DirectionOfManipulation::Vertical, QWidget * _parent = nullptr, const QString & _name = QString()); //!< default ctor + FloatModelEditorBase(const FloatModelEditorBase& other) = delete; + + // TODO: remove + inline void setHintText(const QString & txt_before, const QString & txt_after) + { + setDescription(txt_before); + setUnit(txt_after); + } + +signals: + void sliderPressed(); + void sliderReleased(); + void sliderMoved(float value); + + +protected: + void contextMenuEvent(QContextMenuEvent * me) override; + void dragEnterEvent(QDragEnterEvent * dee) override; + void dropEvent(QDropEvent * de) override; + void focusOutEvent(QFocusEvent * fe) override; + void mousePressEvent(QMouseEvent * me) override; + void mouseReleaseEvent(QMouseEvent * me) override; + void mouseMoveEvent(QMouseEvent * me) override; + void mouseDoubleClickEvent(QMouseEvent * me) override; + void paintEvent(QPaintEvent * me) override; + void wheelEvent(QWheelEvent * me) override; + + void enterEvent(QEvent *event) override; + void leaveEvent(QEvent *event) override; + + virtual float getValue(const QPoint & p); + +private slots: + virtual void enterValue(); + void friendlyUpdate(); + void toggleScale(); + +private: + virtual QString displayValue() const; + + void doConnections() override; + + void showTextFloat(int msecBeforeDisplay, int msecDisplayTime); + void setPosition(const QPoint & p); + + inline float pageSize() const + { + return (model()->maxValue() - model()->minValue()) / 100.0f; + } + + static SimpleTextFloat * s_textFloat; + + BoolModel m_volumeKnob; + FloatModel m_volumeRatio; + + QPoint m_lastMousePos; //!< mouse position in last mouseMoveEvent + float m_leftOver; + bool m_buttonPressed; + + DirectionOfManipulation m_directionOfManipulation; +}; + +} // namespace lmms::gui + +#endif // LMMS_GUI_FLOAT_MODEL_EDITOR_BASE_H diff --git a/include/Knob.h b/include/Knob.h index d5739bb1c3d..3c3339a6fe7 100644 --- a/include/Knob.h +++ b/include/Knob.h @@ -26,12 +26,9 @@ #define LMMS_GUI_KNOB_H #include -#include -#include -#include #include -#include "AutomatableModelView.h" +#include "FloatModelEditorBase.h" class QPixmap; @@ -50,7 +47,7 @@ enum class KnobType void convertPixmapToGrayScale(QPixmap &pixMap); -class LMMS_EXPORT Knob : public QWidget, public FloatModelView +class LMMS_EXPORT Knob : public FloatModelEditorBase { Q_OBJECT Q_ENUMS( KnobType ) @@ -72,9 +69,6 @@ class LMMS_EXPORT Knob : public QWidget, public FloatModelView Q_PROPERTY(QColor arcActiveColor MEMBER m_arcActiveColor) Q_PROPERTY(QColor arcInactiveColor MEMBER m_arcInactiveColor) - mapPropertyFromModel(bool,isVolumeKnob,setVolumeKnob,m_volumeKnob); - mapPropertyFromModel(float,volumeRatio,setVolumeRatio,m_volumeRatio); - Q_PROPERTY(KnobType knobNum READ knobNum WRITE setknobNum) Q_PROPERTY(QColor textColor READ textColor WRITE setTextColor) @@ -87,13 +81,6 @@ class LMMS_EXPORT Knob : public QWidget, public FloatModelView Knob( QWidget * _parent = nullptr, const QString & _name = QString() ); //!< default ctor Knob( const Knob& other ) = delete; - // TODO: remove - inline void setHintText( const QString & _txt_before, - const QString & _txt_after ) - { - setDescription( _txt_before ); - setUnit( _txt_after ); - } void setLabel( const QString & txt ); void setHtmlLabel( const QString &htmltxt ); @@ -125,46 +112,16 @@ class LMMS_EXPORT Knob : public QWidget, public FloatModelView void setTextColor( const QColor & c ); -signals: - void sliderPressed(); - void sliderReleased(); - void sliderMoved( float value ); - - protected: - void contextMenuEvent( QContextMenuEvent * _me ) override; - void dragEnterEvent( QDragEnterEvent * _dee ) override; - void dropEvent( QDropEvent * _de ) override; - void focusOutEvent( QFocusEvent * _fe ) override; - void mousePressEvent( QMouseEvent * _me ) override; - void mouseReleaseEvent( QMouseEvent * _me ) override; - void mouseMoveEvent( QMouseEvent * _me ) override; - void mouseDoubleClickEvent( QMouseEvent * _me ) override; void paintEvent( QPaintEvent * _me ) override; - 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: - virtual void enterValue(); - void friendlyUpdate(); - void toggleScale(); + void changeEvent(QEvent * ev) override; private: - virtual QString displayValue() const; - - void doConnections() override; - QLineF calculateLine( const QPointF & _mid, float _radius, float _innerRadius = 1) const; void drawKnob( QPainter * _p ); - void showTextFloat(int msecBeforeDisplay, int msecDisplayTime); - void setPosition( const QPoint & _p ); bool updateAngle(); int angleFromValue( float value, float minValue, float maxValue, float totalAngle ) const @@ -172,25 +129,11 @@ private slots: return static_cast( ( value - 0.5 * ( minValue + maxValue ) ) / ( maxValue - minValue ) * m_totalAngle ) % 360; } - inline float pageSize() const - { - return ( model()->maxValue() - model()->minValue() ) / 100.0f; - } - - - static SimpleTextFloat * s_textFloat; - QString m_label; bool m_isHtmlLabel; QTextDocument* m_tdRenderer; std::unique_ptr m_knobPixmap; - BoolModel m_volumeKnob; - FloatModel m_volumeRatio; - - QPoint m_lastMousePos; //!< mouse position in last mouseMoveEvent - float m_leftOver; - bool m_buttonPressed; float m_totalAngle; int m_angle; @@ -211,9 +154,7 @@ private slots: QColor m_textColor; KnobType m_knobNum; - -} ; - +}; } // namespace lmms::gui diff --git a/include/LadspaControl.h b/include/LadspaControl.h index e4f0cd745ce..8af8f99231f 100644 --- a/include/LadspaControl.h +++ b/include/LadspaControl.h @@ -41,6 +41,7 @@ namespace gui { class LadspaControlView; +class LadspaMatrixControlDialog; } // namespace gui @@ -125,6 +126,7 @@ protected slots: friend class gui::LadspaControlView; + friend class gui::LadspaMatrixControlDialog; } ; diff --git a/include/LcdFloatSpinBox.h b/include/LcdFloatSpinBox.h index 74a870114a6..a87588b6230 100644 --- a/include/LcdFloatSpinBox.h +++ b/include/LcdFloatSpinBox.h @@ -49,6 +49,12 @@ class LMMS_EXPORT LcdFloatSpinBox : public QWidget, public FloatModelView } void setLabel(const QString &label) { m_label = label; } + + void setSeamless(bool left, bool right) + { + m_wholeDisplay.setSeamless(left, true); + m_fractionDisplay.setSeamless(true, right); + } public slots: virtual void update(); diff --git a/include/LcdWidget.h b/include/LcdWidget.h index f19c2c5838d..cef121b3fce 100644 --- a/include/LcdWidget.h +++ b/include/LcdWidget.h @@ -49,8 +49,9 @@ class LMMS_EXPORT LcdWidget : public QWidget ~LcdWidget() override; - void setValue( int value ); - void setLabel( const QString& label ); + void setValue(int value); + void setValue(float value); + void setLabel(const QString& label); void addTextForValue( int value, const QString& text ) { diff --git a/include/LedCheckBox.h b/include/LedCheckBox.h index e3629e143e7..aaafffaa14e 100644 --- a/include/LedCheckBox.h +++ b/include/LedCheckBox.h @@ -47,10 +47,12 @@ class LMMS_EXPORT LedCheckBox : public AutomatableButton LedCheckBox( const QString & _txt, QWidget * _parent, const QString & _name = QString(), - LedColor _color = LedColor::Yellow ); + LedColor _color = LedColor::Yellow, + bool legacyMode = true); LedCheckBox( QWidget * _parent, const QString & _name = QString(), - LedColor _color = LedColor::Yellow ); + LedColor _color = LedColor::Yellow, + bool legacyMode = true); ~LedCheckBox() override; @@ -74,8 +76,13 @@ class LMMS_EXPORT LedCheckBox : public AutomatableButton QString m_text; + bool m_legacyMode; + void initUi( LedColor _color ); //!< to be called by ctors + void onTextUpdated(); //!< to be called when you updated @a m_text + void paintLegacy(QPaintEvent * p); + void paintNonLegacy(QPaintEvent * p); } ; diff --git a/include/Lv2UridMap.h b/include/Lv2UridMap.h index b8733023e5f..6c22aca3e40 100644 --- a/include/Lv2UridMap.h +++ b/include/Lv2UridMap.h @@ -55,8 +55,6 @@ class UridMap LV2_URID_Map m_mapFeature; LV2_URID_Unmap m_unmapFeature; - LV2_URID m_lastUrid = 0; - public: //! constructor; will set up the features UridMap(); diff --git a/include/Lv2Worker.h b/include/Lv2Worker.h index 7931f8e7cde..90a3d9d4f8d 100644 --- a/include/Lv2Worker.h +++ b/include/Lv2Worker.h @@ -47,16 +47,17 @@ class Lv2Worker { public: // CTOR/DTOR/feature access - Lv2Worker(const LV2_Worker_Interface* iface, Semaphore* common_work_lock, bool threaded); + Lv2Worker(Semaphore* commonWorkLock, bool threaded); ~Lv2Worker(); - void setHandle(LV2_Handle handle) { m_handle = handle; } + void setHandle(LV2_Handle handle); + void setInterface(const LV2_Worker_Interface* newInterface); 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); } + if(m_interface->end_run) { m_interface->end_run(m_scheduleFeature.handle); } } // to be called only by static functions @@ -69,9 +70,9 @@ class Lv2Worker std::size_t bufferSize() const; //!< size of internal buffers // parameters - const LV2_Worker_Interface* m_iface; - bool m_threaded; - LV2_Handle m_handle; + const bool m_threaded; + const LV2_Worker_Interface* m_interface = nullptr; + LV2_Handle m_handle = nullptr; LV2_Worker_Schedule m_scheduleFeature; // threading/synchronization diff --git a/include/MidiEvent.h b/include/MidiEvent.h index 956c33fb389..9a14e427c44 100644 --- a/include/MidiEvent.h +++ b/include/MidiEvent.h @@ -212,7 +212,7 @@ class MidiEvent int32_t m_sysExDataLen; // len of m_sysExData } m_data; - const char* m_sysExData; + [[maybe_unused]] const char* m_sysExData; const void* m_sourcePort; // Stores the source of the MidiEvent: Internal or External (hardware controllers). diff --git a/include/MidiSetupWidget.h b/include/MidiSetupWidget.h index a61b606ac21..7b660601771 100644 --- a/include/MidiSetupWidget.h +++ b/include/MidiSetupWidget.h @@ -25,7 +25,7 @@ #ifndef LMMS_GUI_MIDI_SETUP_WIDGET_H #define LMMS_GUI_MIDI_SETUP_WIDGET_H -#include "TabWidget.h" +#include class QLineEdit; @@ -33,7 +33,7 @@ namespace lmms::gui { -class MidiSetupWidget : public TabWidget +class MidiSetupWidget : public QGroupBox { Q_OBJECT MidiSetupWidget( const QString & caption, const QString & configSection, diff --git a/include/SetupDialog.h b/include/SetupDialog.h index fa41325db79..882ca2bedce 100644 --- a/include/SetupDialog.h +++ b/include/SetupDialog.h @@ -30,12 +30,12 @@ #include "AudioDevice.h" #include "AudioDeviceSetupWidget.h" -#include "LedCheckBox.h" #include "lmmsconfig.h" #include "MidiClient.h" #include "MidiSetupWidget.h" +class QCheckBox; class QComboBox; class QLabel; class QLineEdit; @@ -156,14 +156,14 @@ private slots: bool m_enableRunningAutoSave; QSlider * m_saveIntervalSlider; QLabel * m_saveIntervalLbl; - LedCheckBox * m_autoSave; - LedCheckBox * m_runningAutoSave; + QCheckBox * m_autoSave; + QCheckBox * m_runningAutoSave; bool m_smoothScroll; bool m_animateAFP; QLabel * m_vstEmbedLbl; QComboBox* m_vstEmbedComboBox; QString m_vstEmbedMethod; - LedCheckBox * m_vstAlwaysOnTopCheckBox; + QCheckBox * m_vstAlwaysOnTopCheckBox; bool m_vstAlwaysOnTop; bool m_disableAutoQuit; diff --git a/include/TabBar.h b/include/TabBar.h index fa27032873c..29c100e0c7a 100644 --- a/include/TabBar.h +++ b/include/TabBar.h @@ -49,7 +49,12 @@ class LMMS_EXPORT TabBar : public QWidget TabButton * addTab( QWidget * _w, const QString & _text, int _id, bool _add_stretch = false, - bool _text_is_tooltip = false ); + bool _text_is_tooltip = false, + // TODO Remove fixWidgetToParentSize once it is used + // with false everywhere. + // At the time of writing it is only used in + // LadspaBrowser with default parameters. + bool fixWidgetToParentSize = true ); void removeTab( int _id ); inline void setExclusive( bool _on ) diff --git a/include/TempoSyncBarModelEditor.h b/include/TempoSyncBarModelEditor.h new file mode 100644 index 00000000000..c1b0bb26f7a --- /dev/null +++ b/include/TempoSyncBarModelEditor.h @@ -0,0 +1,90 @@ +/* + * TempoSyncBarModelEditor.h - adds bpm to ms conversion for the bar editor class + * + * Copyright (c) 2005-2008 Danny McRae + * Copyright (c) 2009-2014 Tobias Doerffel + * Copyright (c) 2023 Michael Gregorius + * + * 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_TEMPO_SYNC_BAR_MODEL_EDITOR_H +#define LMMS_GUI_TEMPO_SYNC_BAR_MODEL_EDITOR_H + +#include +#include + +#include "BarModelEditor.h" +#include "TempoSyncKnobModel.h" + +namespace lmms::gui +{ + +class MeterDialog; + +class LMMS_EXPORT TempoSyncBarModelEditor : public BarModelEditor +{ + Q_OBJECT +public: + TempoSyncBarModelEditor(QString text, FloatModel * floatModel, QWidget * parent = nullptr); + ~TempoSyncBarModelEditor() override; + + const QString & syncDescription(); + void setSyncDescription(const QString & new_description); + + const QPixmap & syncIcon(); + void setSyncIcon(const QPixmap & new_pix); + + TempoSyncKnobModel * model() + { + return castModel(); + } + + void modelChanged() override; + + +signals: + void syncDescriptionChanged(const QString & new_description); + void syncIconChanged(); + + +protected: + void contextMenuEvent(QContextMenuEvent * me) override; + + +protected slots: + void updateDescAndIcon(); + void showCustom(); + + +private: + void updateTextDescription(); + void updateIcon(); + + +private: + QPixmap m_tempoSyncIcon; + QString m_tempoSyncDescription; + + QPointer m_custom; +}; + +} // namespace lmms::gui + +#endif // LMMS_GUI_TEMPO_SYNC_BAR_MODEL_EDITOR_H diff --git a/include/TempoSyncKnobModel.h b/include/TempoSyncKnobModel.h index 5cd2db067fa..af92a58aab0 100644 --- a/include/TempoSyncKnobModel.h +++ b/include/TempoSyncKnobModel.h @@ -82,6 +82,9 @@ class LMMS_EXPORT TempoSyncKnobModel : public FloatModel void setScale( float _new_scale ); + MeterModel & getCustomMeterModel() { return m_custom; } + MeterModel const & getCustomMeterModel() const { return m_custom; } + signals: void syncModeChanged( lmms::TempoSyncKnobModel::SyncMode _new_mode ); void scaleChanged( float _new_scale ); diff --git a/plugins/AudioFileProcessor/AudioFileProcessor.cpp b/plugins/AudioFileProcessor/AudioFileProcessor.cpp index 6671022070c..864bda5b659 100644 --- a/plugins/AudioFileProcessor/AudioFileProcessor.cpp +++ b/plugins/AudioFileProcessor/AudioFileProcessor.cpp @@ -24,6 +24,8 @@ #include "AudioFileProcessor.h" +#include + #include #include #include @@ -66,7 +68,11 @@ Plugin::Descriptor PLUGIN_EXPORT audiofileprocessor_plugin_descriptor = 0x0100, Plugin::Type::Instrument, new PluginPixmapLoader( "logo" ), - "wav,ogg,ds,spx,au,voc,aif,aiff,flac,raw", + "wav,ogg,ds,spx,au,voc,aif,aiff,flac,raw" +#ifdef LMMS_HAVE_SNDFILE_MP3 + ",mp3" +#endif + , nullptr, } ; @@ -289,15 +295,24 @@ QString AudioFileProcessor::nodeName() const -int AudioFileProcessor::getBeatLen( NotePlayHandle * _n ) const +auto AudioFileProcessor::beatLen(NotePlayHandle* note) const -> int { + // If we can play indefinitely, use the default beat note duration + if (static_cast(m_loopModel.value()) != SampleBuffer::LoopMode::Off) { return 0; } + + // Otherwise, use the remaining sample duration const auto baseFreq = instrumentTrack()->baseFreq(); - const float freq_factor = baseFreq / _n->frequency() * - Engine::audioEngine()->processingSampleRate() / Engine::audioEngine()->baseSampleRate(); + const auto freqFactor = baseFreq / note->frequency() + * Engine::audioEngine()->processingSampleRate() + / Engine::audioEngine()->baseSampleRate(); - return static_cast( floorf( ( m_sampleBuffer.endFrame() - m_sampleBuffer.startFrame() ) * freq_factor ) ); -} + const auto startFrame = m_nextPlayStartPoint >= m_sampleBuffer.endFrame() + ? m_sampleBuffer.startFrame() + : m_nextPlayStartPoint; + const auto duration = m_sampleBuffer.endFrame() - startFrame; + return static_cast(std::floor(duration * freqFactor)); +} diff --git a/plugins/AudioFileProcessor/AudioFileProcessor.h b/plugins/AudioFileProcessor/AudioFileProcessor.h index 5fed10862ff..39bd11c3af5 100644 --- a/plugins/AudioFileProcessor/AudioFileProcessor.h +++ b/plugins/AudioFileProcessor/AudioFileProcessor.h @@ -67,7 +67,7 @@ class AudioFileProcessor : public Instrument QString nodeName() const override; - virtual int getBeatLen( NotePlayHandle * _n ) const; + auto beatLen(NotePlayHandle* note) const -> int override; f_cnt_t desiredReleaseFrames() const override { diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 9a71be4b823..04862cac1ba 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -9,10 +9,7 @@ IF(LMMS_BUILD_APPLE AND CMAKE_CXX_COMPILER_ID MATCHES "Clang") SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++") ENDIF() -INCLUDE_DIRECTORIES( - ${SAMPLERATE_INCLUDE_DIRS} - "${CMAKE_BINARY_DIR}/src" -) +include_directories("${CMAKE_BINARY_DIR}/src") # See cmake/modules/PluginList.cmake FOREACH(PLUGIN ${PLUGIN_LIST}) diff --git a/plugins/Eq/EqCurve.cpp b/plugins/Eq/EqCurve.cpp index 10213bfa92f..bb721a7e462 100644 --- a/plugins/Eq/EqCurve.cpp +++ b/plugins/Eq/EqCurve.cpp @@ -65,6 +65,7 @@ QRectF EqHandle::boundingRect() const float EqHandle::freqToXPixel( float freq , int w ) { + if (typeInfo::isEqual(freq, 0.0f)) { return 0.0f; } float min = log10f( 20 ); float max = log10f( 20000 ); float range = max - min; @@ -739,14 +740,18 @@ void EqCurve::paint( QPainter *painter, const QStyleOptionGraphicsItem *option, } //compute a QPainterPath m_curve = QPainterPath(); - for ( int x = 0; x < m_width ; x++ ) + //only draw the EQ curve if there are any activeHandles + if (activeHandles != 0) { - mainCurve[x] = ( ( mainCurve[x] / activeHandles ) ) - ( m_heigth/2 ); - if ( x==0 ) + for (int x = 0; x < m_width; x++) { - m_curve.moveTo( x, mainCurve[x] ); + mainCurve[x] = ((mainCurve[x] / activeHandles)) - (m_heigth / 2); + if (x == 0) + { + m_curve.moveTo(x, mainCurve[x]); + } + m_curve.lineTo(x, mainCurve[x]); } - m_curve.lineTo( x, mainCurve[x] ); } //we cache the curve painting in a pixmap for saving cpu QPixmap cacheMap( boundingRect().size().toSize() ); diff --git a/plugins/GigPlayer/CMakeLists.txt b/plugins/GigPlayer/CMakeLists.txt index 24db813bdee..7b634b605ce 100644 --- a/plugins/GigPlayer/CMakeLists.txt +++ b/plugins/GigPlayer/CMakeLists.txt @@ -12,8 +12,13 @@ if(LMMS_HAVE_GIG) add_definitions(${GCC_GIG_COMPILE_FLAGS}) endif(LMMS_BUILD_WIN32) - LINK_DIRECTORIES(${GIG_LIBRARY_DIRS} ${SAMPLERATE_LIBRARY_DIRS}) - LINK_LIBRARIES(${GIG_LIBRARIES} ${SAMPLERATE_LIBRARIES}) - BUILD_PLUGIN(gigplayer GigPlayer.cpp GigPlayer.h PatchesDialog.cpp PatchesDialog.h PatchesDialog.ui MOCFILES GigPlayer.h PatchesDialog.h UICFILES PatchesDialog.ui EMBEDDED_RESOURCES "${CMAKE_CURRENT_SOURCE_DIR}/*.png") + link_directories(${GIG_LIBRARY_DIRS}) + link_libraries(${GIG_LIBRARIES}) + build_plugin(gigplayer + GigPlayer.cpp GigPlayer.h PatchesDialog.cpp PatchesDialog.h PatchesDialog.ui + MOCFILES GigPlayer.h PatchesDialog.h + UICFILES PatchesDialog.ui + EMBEDDED_RESOURCES "${CMAKE_CURRENT_SOURCE_DIR}/*.png" + ) + target_link_libraries(gigplayer SampleRate::samplerate) endif(LMMS_HAVE_GIG) - diff --git a/plugins/LadspaEffect/CMakeLists.txt b/plugins/LadspaEffect/CMakeLists.txt index 951615ad4d0..202a8dd0473 100644 --- a/plugins/LadspaEffect/CMakeLists.txt +++ b/plugins/LadspaEffect/CMakeLists.txt @@ -1,25 +1,25 @@ INCLUDE(BuildPlugin) -BUILD_PLUGIN(ladspaeffect LadspaEffect.cpp LadspaControls.cpp LadspaControlDialog.cpp LadspaSubPluginFeatures.cpp LadspaEffect.h LadspaControls.h LadspaControlDialog.h LadspaSubPluginFeatures.h MOCFILES LadspaEffect.h LadspaControls.h LadspaControlDialog.h EMBEDDED_RESOURCES logo.png) +BUILD_PLUGIN(ladspaeffect LadspaEffect.cpp LadspaControls.cpp LadspaControlDialog.cpp LadspaMatrixControlDialog.cpp LadspaSubPluginFeatures.cpp LadspaWidgetFactory.cpp LadspaEffect.h LadspaControls.h LadspaControlDialog.h LadspaMatrixControlDialog.h LadspaSubPluginFeatures.h LadspaWidgetFactory.h MOCFILES LadspaEffect.h LadspaControls.h LadspaControlDialog.h LadspaMatrixControlDialog.h EMBEDDED_RESOURCES logo.png) SET(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/ladspa") -IF(WANT_CAPS) +IF(LMMS_HAVE_CAPS) ADD_SUBDIRECTORY(caps) -ENDIF(WANT_CAPS) +ENDIF() -IF(WANT_TAP) +IF(LMMS_HAVE_TAP) ADD_SUBDIRECTORY(tap) -ENDIF(WANT_TAP) +ENDIF() -IF(WANT_SWH) +IF(LMMS_HAVE_SWH) ADD_SUBDIRECTORY(swh) -ENDIF(WANT_SWH) +ENDIF() -IF(WANT_CMT) +IF(LMMS_HAVE_CMT) ADD_SUBDIRECTORY(cmt) -ENDIF(WANT_CMT) +ENDIF() -IF(WANT_CALF) +IF(LMMS_HAVE_CALF) ADD_SUBDIRECTORY(calf) -ENDIF(WANT_CALF) +ENDIF() diff --git a/plugins/LadspaEffect/LadspaControls.h b/plugins/LadspaEffect/LadspaControls.h index 2bef0c85629..c91f3badd10 100644 --- a/plugins/LadspaEffect/LadspaControls.h +++ b/plugins/LadspaEffect/LadspaControls.h @@ -27,6 +27,7 @@ #include "EffectControls.h" #include "LadspaControlDialog.h" +#include "LadspaMatrixControlDialog.h" namespace lmms { @@ -59,7 +60,7 @@ class LadspaControls : public EffectControls gui::EffectControlDialog* createView() override { - return new gui::LadspaControlDialog( this ); + return new gui::LadspaMatrixControlDialog( this ); } @@ -79,6 +80,7 @@ protected slots: friend class gui::LadspaControlDialog; + friend class gui::LadspaMatrixControlDialog; friend class LadspaEffect; diff --git a/plugins/LadspaEffect/LadspaMatrixControlDialog.cpp b/plugins/LadspaEffect/LadspaMatrixControlDialog.cpp new file mode 100644 index 00000000000..88810cee639 --- /dev/null +++ b/plugins/LadspaEffect/LadspaMatrixControlDialog.cpp @@ -0,0 +1,243 @@ +/* + * LadspaMatrixControlDialog.h - Dialog for displaying and editing control port + * values for LADSPA plugins in a matrix display + * + * Copyright (c) 2015 Michael Gregorius + * + * This file is part of LMMS - http://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 +#include +#include +#include +#include +#include +#include + + +#include "LadspaBase.h" +#include "LadspaControl.h" +#include "LadspaEffect.h" +#include "LadspaMatrixControlDialog.h" +#include "LadspaWidgetFactory.h" +#include "LadspaControlView.h" +#include "LedCheckBox.h" + +#include "GuiApplication.h" +#include "MainWindow.h" + + +namespace lmms::gui +{ + +LadspaMatrixControlDialog::LadspaMatrixControlDialog(LadspaControls * ladspaControls) : + EffectControlDialog(ladspaControls), + m_scrollArea(nullptr), + m_stereoLink(nullptr) +{ + QVBoxLayout * mainLayout = new QVBoxLayout(this); + + m_scrollArea = new QScrollArea(this); + m_scrollArea->setWidgetResizable(true); + m_scrollArea->setFrameShape(QFrame::NoFrame); + // Set to always on so that the elements do not move around when the + // scroll bar is hidden or shown. + m_scrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn); + + // Add a scroll area that grows + mainLayout->addWidget(m_scrollArea, 1); + + // Populate the parameter matrix and put it into the scroll area + updateEffectView(ladspaControls); + + // Add button to link all channels if there's more than one channel + if (getChannelCount() > 1) + { + mainLayout->addSpacing(3); + + m_stereoLink = new LedCheckBox(tr("Link Channels"), this, QString(), LedCheckBox::LedColor::Green, false); + m_stereoLink->setModel(&ladspaControls->m_stereoLinkModel); + mainLayout->addWidget(m_stereoLink, 0, Qt::AlignCenter); + } +} + +bool LadspaMatrixControlDialog::isResizable() const +{ + return true; +} + +bool LadspaMatrixControlDialog::needsLinkColumn() const +{ + LadspaControls * ladspaControls = getLadspaControls(); + + ch_cnt_t const channelCount = getChannelCount(); + for (ch_cnt_t i = 0; i < channelCount; ++i) + { + // Create a const reference so that the C++11 based for loop does not detach the Qt container + auto const & currentControls = ladspaControls->m_controls[i]; + for (auto ladspaControl : currentControls) + { + if (ladspaControl->m_link) + { + return true; + } + } + } + + return false; +} + +void LadspaMatrixControlDialog::arrangeControls(QWidget * parent, QGridLayout* gridLayout) +{ + LadspaControls * ladspaControls = getLadspaControls(); + + int const headerRow = 0; + int const linkColumn = 0; + + bool const linkColumnNeeded = needsLinkColumn(); + if (linkColumnNeeded) + { + gridLayout->addWidget(new QLabel("" + tr("Link") + "", parent), headerRow, linkColumn, Qt::AlignHCenter); + + // If there's a link column then it should not stretch + gridLayout->setColumnStretch(linkColumn, 0); + } + + int const channelStartColumn = linkColumnNeeded ? 1 : 0; + + // The header row should not grow vertically + gridLayout->setRowStretch(0, 0); + + // Records the maximum row with parameters so that we can add a vertical spacer after that row + int maxRow = 0; + + // Iterate the channels and add widgets for each control + // Note: the code assumes that all channels have the same structure, i.e. that all channels + // have the same number of parameters which are in the same order. + ch_cnt_t const numberOfChannels = getChannelCount(); + for (ch_cnt_t i = 0; i < numberOfChannels; ++i) + { + int currentChannelColumn = channelStartColumn + i; + gridLayout->setColumnStretch(currentChannelColumn, 1); + + // First add the channel header with the channel number + gridLayout->addWidget(new QLabel("" + tr("Channel %1").arg(QString::number(i + 1)) + "", parent), headerRow, currentChannelColumn, Qt::AlignHCenter); + + int currentRow = 1; + + if (i == 0) + { + // Configure the current parameter row to not stretch. + // Only do this once, i.e. when working with the first channel. + gridLayout->setRowStretch(currentRow, 0); + } + + // Create a const reference so that the C++11 based for loop does not detach the Qt container + auto const & currentControls = ladspaControls->m_controls[i]; + for (auto ladspaControl : currentControls) + { + // Only use the first channel to determine if we need to add link controls + if (i == 0 && ladspaControl->m_link) + { + LedCheckBox * linkCheckBox = new LedCheckBox("", parent, "", LedCheckBox::LedColor::Green); + linkCheckBox->setModel(&ladspaControl->m_linkEnabledModel); + linkCheckBox->setToolTip(tr("Link channels")); + gridLayout->addWidget(linkCheckBox, currentRow, linkColumn, Qt::AlignHCenter); + } + + QWidget * controlWidget = LadspaWidgetFactory::createWidget(ladspaControl, this); + if (controlWidget) + { + gridLayout->addWidget(controlWidget, currentRow, currentChannelColumn); + } + + // Record the maximum row so that we add a vertical spacer after that row + maxRow = std::max(maxRow, currentRow); + + ++currentRow; + } + } + + // Add a spacer item after the maximum row + QSpacerItem * spacer = new QSpacerItem(0, 0, QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding); + gridLayout->addItem(spacer, maxRow + 1, 0); +} + +QWidget * LadspaMatrixControlDialog::createMatrixWidget() +{ + QWidget *widget = new QWidget(this); + QGridLayout *gridLayout = new QGridLayout(widget); + gridLayout->setMargin(0); + widget->setLayout(gridLayout); + + arrangeControls(widget, gridLayout); + + return widget; +} + +void LadspaMatrixControlDialog::updateEffectView(LadspaControls * ladspaControls) +{ + m_effectControls = ladspaControls; + + // No need to delete the existing widget as it's deleted + // by the scroll view when we replace it. + QWidget * matrixWidget = createMatrixWidget(); + m_scrollArea->setWidget(matrixWidget); + + // Make sure that the horizontal scroll bar does not show + // From: https://forum.qt.io/topic/13374/solved-qscrollarea-vertical-scroll-only/4 + m_scrollArea->setMinimumWidth(matrixWidget->minimumSizeHint().width() + m_scrollArea->verticalScrollBar()->width()); + + + // Make sure that the widget is shown without a scrollbar whenever possible + // If the widget fits on the workspace we use the height of the widget as the minimum size of the scroll area. + // This will ensure that the scrollbar is not shown initially (and never will be). + // If the widget is larger than the workspace then we want it to mostly cover the workspace. + // + // This is somewhat ugly but I have no idea how to control the initial size of the scroll area otherwise + auto const workspaceSize = getGUI()->mainWindow()->workspace()->viewport()->size(); + // Make sure that we always account a minumum height for the workspace, i.e. that we never compute + // something close to 0 if the LMMS window is very small + int workspaceHeight = qMax(200, static_cast(workspaceSize.height() * 0.9)); + int minOfWidgetAndWorkspace = qMin(matrixWidget->minimumSizeHint().height(), workspaceHeight); + m_scrollArea->setMinimumHeight(minOfWidgetAndWorkspace); + + if (getChannelCount() > 1 && m_stereoLink != nullptr) + { + m_stereoLink->setModel(&ladspaControls->m_stereoLinkModel); + } + + connect(ladspaControls, &LadspaControls::effectModelChanged, + this, &LadspaMatrixControlDialog::updateEffectView, + Qt::DirectConnection); +} + +LadspaControls * LadspaMatrixControlDialog::getLadspaControls() const +{ + return dynamic_cast(m_effectControls); +} + +ch_cnt_t LadspaMatrixControlDialog::getChannelCount() const +{ + return getLadspaControls()->m_processors; +} + +} // namespace lmms::gui diff --git a/plugins/LadspaEffect/LadspaMatrixControlDialog.h b/plugins/LadspaEffect/LadspaMatrixControlDialog.h new file mode 100644 index 00000000000..c5949fa15d2 --- /dev/null +++ b/plugins/LadspaEffect/LadspaMatrixControlDialog.h @@ -0,0 +1,91 @@ +/* + * LadspaMatrixControlDialog.h - Dialog for displaying and editing control port + * values for LADSPA plugins in a matrix display + * + * Copyright (c) 2015 Michael Gregorius + * + * This file is part of LMMS - http://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 LADSPA_MATRIX_CONTROL_DIALOG_H +#define LADSPA_MATRIX_CONTROL_DIALOG_H + +#include "EffectControlDialog.h" + +#include "lmms_basics.h" + + +class QGridLayout; +class QScrollArea; + +namespace lmms +{ + +class LadspaControls; + +namespace gui +{ + +class LedCheckBox; + + +class LadspaMatrixControlDialog : public EffectControlDialog +{ + Q_OBJECT +public: + LadspaMatrixControlDialog(LadspaControls* ctl); + bool isResizable() const override; + +private slots: + void updateEffectView(LadspaControls* ctl); + +private: + /** + * @brief Checks if a link column is needed for the current effect controls. + * @return true if a link column is needed. + */ + bool needsLinkColumn() const; + + /** + * @brief Arranges widgets for the current controls in a grid/matrix layout. + * @param parent The parent of all created widgets + * @param gridLayout The layout into which the controls are organized + */ + void arrangeControls(QWidget * parent, QGridLayout* gridLayout); + + /** + * @brief Creates a widget that holds the widgets of the current controls in a matrix arrangement. + * @param ladspaControls + * @return + */ + QWidget * createMatrixWidget(); + + LadspaControls * getLadspaControls() const; + ch_cnt_t getChannelCount() const; + +private: + QScrollArea* m_scrollArea; + LedCheckBox* m_stereoLink; +}; + +} // namespace gui + +} // namespace lmms + +#endif diff --git a/plugins/LadspaEffect/LadspaWidgetFactory.cpp b/plugins/LadspaEffect/LadspaWidgetFactory.cpp new file mode 100644 index 00000000000..0491fd66187 --- /dev/null +++ b/plugins/LadspaEffect/LadspaWidgetFactory.cpp @@ -0,0 +1,83 @@ +/* + * LadspaWidgetFactory.cpp - Factory that creates widgets for LADSPA ports + * + * Copyright (c) 2006-2008 Danny McRae + * Copyright (c) 2009 Tobias Doerffel + * Copyright (c) 2015-2023 Michael Gregorius + * + * 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 "LadspaWidgetFactory.h" + +#include "LadspaControl.h" +#include "LadspaBase.h" + +#include "BarModelEditor.h" +#include "LedCheckBox.h" +#include "TempoSyncBarModelEditor.h" + +#include +#include + + +namespace lmms::gui +{ + +QWidget * LadspaWidgetFactory::createWidget(LadspaControl * ladspaControl, QWidget * parent) +{ + auto const * port = ladspaControl->port(); + + QString const name = port->name; + + switch (port->data_type) + { + case BufferDataType::Toggled: + { + // The actual check box is put into a widget because LedCheckBox does not play nice with layouts. + // Putting it directly into a grid layout disables the resizing behavior of all columns where it + // appears. Hence we put it into the layout of a widget that knows how to play nice with layouts. + QWidget * widgetWithLayout = new QWidget(parent); + QHBoxLayout * layout = new QHBoxLayout(widgetWithLayout); + layout->setContentsMargins(0, 0, 0, 0); + LedCheckBox * toggle = new LedCheckBox( + name, parent, QString(), LedCheckBox::LedColor::Green, false); + toggle->setModel(ladspaControl->toggledModel()); + layout->addWidget(toggle, 0, Qt::AlignLeft); + + return widgetWithLayout; + } + + case BufferDataType::Integer: + case BufferDataType::Enum: + case BufferDataType::Floating: + return new BarModelEditor(name, ladspaControl->knobModel(), parent); + + case BufferDataType::Time: + return new TempoSyncBarModelEditor(name, ladspaControl->tempoSyncKnobModel(), parent); + + default: + return new QLabel(QObject::tr("%1 (unsupported)").arg(name), parent); + } + + return nullptr; +} + +} // namespace lmms::gui diff --git a/plugins/LadspaEffect/LadspaWidgetFactory.h b/plugins/LadspaEffect/LadspaWidgetFactory.h new file mode 100644 index 00000000000..807334d327f --- /dev/null +++ b/plugins/LadspaEffect/LadspaWidgetFactory.h @@ -0,0 +1,49 @@ +/* + * LadspaWidgetFactory.h - Factory that creates widgets for LADSPA ports + * + * Copyright (c) 2023 Michael Gregorius + * + * 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_LADSPA_WIDGET_FACTORY_H +#define LMMS_GUI_LADSPA_WIDGET_FACTORY_H + + +class QWidget; + +namespace lmms +{ + +class LadspaControl; + +namespace gui +{ + +class LadspaWidgetFactory +{ +public: + static QWidget * createWidget(LadspaControl * ladspaControl, QWidget * parent); +}; + +} // namespace gui + +} // namespace lmms + +#endif // LMMS_GUI_LADSPA_WIDGET_FACTORY_H diff --git a/plugins/Sf2Player/CMakeLists.txt b/plugins/Sf2Player/CMakeLists.txt index 4679a94bda3..1d004a6c595 100644 --- a/plugins/Sf2Player/CMakeLists.txt +++ b/plugins/Sf2Player/CMakeLists.txt @@ -1,13 +1,10 @@ if(LMMS_HAVE_FLUIDSYNTH) include(BuildPlugin) - include_directories(${SAMPLERATE_INCLUDE_DIRS}) - link_directories(${SAMPLERATE_LIBRARY_DIRS}) - link_libraries(${SAMPLERATE_LIBRARIES}) build_plugin(sf2player Sf2Player.cpp Sf2Player.h PatchesDialog.cpp PatchesDialog.h PatchesDialog.ui MOCFILES Sf2Player.h PatchesDialog.h UICFILES PatchesDialog.ui EMBEDDED_RESOURCES *.png ) - target_link_libraries(sf2player fluidsynth) + target_link_libraries(sf2player fluidsynth SampleRate::samplerate) endif() diff --git a/plugins/Sf2Player/Sf2Player.cpp b/plugins/Sf2Player/Sf2Player.cpp index 79bd4b97686..ee97e98ef4c 100644 --- a/plugins/Sf2Player/Sf2Player.cpp +++ b/plugins/Sf2Player/Sf2Player.cpp @@ -73,8 +73,8 @@ 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. + * A non-owning reference to a single FluidSynth voice. Captures some initial + * properties of the referenced voice to help manage changes to it over time. */ class FluidVoice { @@ -82,12 +82,16 @@ class FluidVoice //! 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)} + m_id{fluid_voice_get_id(voice)}, + m_coarseTune{fluid_voice_gen_get(voice, GEN_COARSETUNE)} { } //! Get a pointer to the referenced voice. fluid_voice_t* get() const noexcept { return m_voice; } + //! Get the original coarse tuning of the referenced voice. + float coarseTune() const noexcept { return m_coarseTune; } + //! Test whether this object still refers to the original voice. bool isValid() const { @@ -97,6 +101,7 @@ class FluidVoice private: fluid_voice_t* m_voice; unsigned int m_id; + float m_coarseTune; }; struct Sf2PluginData @@ -740,7 +745,7 @@ void Sf2Instrument::playNote( NotePlayHandle * _n, sampleFrame * ) 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_gen_set(voice.get(), GEN_COARSETUNE, voice.coarseTune() + detuning); fluid_voice_update_param(voice.get(), GEN_COARSETUNE); } } diff --git a/plugins/Stk/Mallets/Mallets.cpp b/plugins/Stk/Mallets/Mallets.cpp index b746e949120..dd3b094940f 100644 --- a/plugins/Stk/Mallets/Mallets.cpp +++ b/plugins/Stk/Mallets/Mallets.cpp @@ -87,6 +87,7 @@ MalletsInstrument::MalletsInstrument( InstrumentTrack * _instrument_track ): m_strikeModel( true, this, tr( "Bowed" ) ), m_presetsModel(this), m_spreadModel(0, 0, 255, 1, this, tr( "Spread" )), + m_randomModel(0.0f, 0.0f, 1.0f, 0.01f, this, tr("Randomness")), m_versionModel( MALLETS_PRESET_VERSION, 0, MALLETS_PRESET_VERSION, this, "" ), m_isOldVersionModel( false, this, "" ), m_filesMissing( !QDir( ConfigManager::inst()->stkDir() ).exists() || @@ -155,6 +156,7 @@ void MalletsInstrument::saveSettings( QDomDocument & _doc, QDomElement & _this ) m_presetsModel.saveSettings( _doc, _this, "preset" ); m_spreadModel.saveSettings( _doc, _this, "spread" ); + m_randomModel.saveSettings(_doc, _this, "randomness"); m_versionModel.saveSettings( _doc, _this, "version" ); m_isOldVersionModel.saveSettings( _doc, _this, "oldversion" ); } @@ -189,6 +191,7 @@ void MalletsInstrument::loadSettings( const QDomElement & _this ) m_presetsModel.loadSettings( _this, "preset" ); m_spreadModel.loadSettings( _this, "spread" ); + m_randomModel.loadSettings(_this, "randomness"); m_isOldVersionModel.loadSettings( _this, "oldversion" ); // To maintain backward compatibility @@ -284,7 +287,7 @@ void MalletsInstrument::playNote( NotePlayHandle * _n, } int p = m_presetsModel.value(); - + const float freq = _n->frequency(); if (!_n->m_pluginData) { @@ -293,6 +296,39 @@ void MalletsInstrument::playNote( NotePlayHandle * _n, m_isOldVersionModel.value() ? 100.0 : 200.0; const float vel = _n->getVolume() / velocityAdjust; + const float random = m_randomModel.value(); + float hardness = m_hardnessModel.value(); + float position = m_positionModel.value(); + float modulator = m_modulatorModel.value(); + float crossfade = m_crossfadeModel.value(); + float pressure = m_pressureModel.value(); + float speed = m_velocityModel.value(); + + if (p < 9) + { + hardness += random * (static_cast(fast_rand() % 128) - 64.0); + hardness = std::clamp(hardness, 0.0f, 128.0f); + + position += random * (static_cast(fast_rand() % 64) - 32.0); + position = std::clamp(position, 0.0f, 64.0f); + } + else if (p == 9) + { + modulator += random * (static_cast(fast_rand() % 128) - 64.0); + modulator = std::clamp(modulator, 0.0f, 128.0f); + + crossfade += random * (static_cast(fast_rand() % 128) - 64.0); + crossfade = std::clamp(crossfade, 0.0f, 128.0f); + } + else + { + pressure += random * (static_cast(fast_rand() % 128) - 64.0); + pressure = std::clamp(pressure, 0.0f, 128.0f); + + speed += random * (static_cast(fast_rand() % 128) - 64.0); + speed = std::clamp(speed, 0.0f, 128.0f); + } + // critical section as STK is not thread-safe static QMutex m; m.lock(); @@ -301,8 +337,8 @@ void MalletsInstrument::playNote( NotePlayHandle * _n, _n->m_pluginData = new MalletsSynth( freq, vel, m_stickModel.value(), - m_hardnessModel.value(), - m_positionModel.value(), + hardness, + position, m_vibratoGainModel.value(), m_vibratoFreqModel.value(), p, @@ -315,8 +351,8 @@ void MalletsInstrument::playNote( NotePlayHandle * _n, vel, p, m_lfoDepthModel.value(), - m_modulatorModel.value(), - m_crossfadeModel.value(), + modulator, + crossfade, m_lfoSpeedModel.value(), m_adsrModel.value(), (uint8_t) m_spreadModel.value(), @@ -326,12 +362,12 @@ void MalletsInstrument::playNote( NotePlayHandle * _n, { _n->m_pluginData = new MalletsSynth( freq, vel, - m_pressureModel.value(), + pressure, m_motionModel.value(), m_vibratoModel.value(), p - 10, m_strikeModel.value() * 128.0, - m_velocityModel.value(), + speed, (uint8_t) m_spreadModel.value(), Engine::audioEngine()->processingSampleRate() ); } @@ -343,8 +379,20 @@ void MalletsInstrument::playNote( NotePlayHandle * _n, const f_cnt_t offset = _n->noteOffset(); auto ps = static_cast(_n->m_pluginData); - ps->setFrequency( freq ); + ps->setFrequency(freq); + p = ps->presetIndex(); + if (p < 9) // ModalBar updates + { + ps->setVibratoGain(m_vibratoGainModel.value()); + ps->setVibratoFreq(m_vibratoFreqModel.value()); + } + else if (p == 9) // Tubular Bells updates + { + ps->setADSR(m_adsrModel.value()); + ps->setLFODepth(m_lfoDepthModel.value()); + ps->setLFOSpeed(m_lfoSpeedModel.value()); + } sample_t add_scale = 0.0f; if( p == 10 && m_isOldVersionModel.value() == true ) @@ -412,6 +460,11 @@ MalletsInstrumentView::MalletsInstrumentView( MalletsInstrument * _instrument, m_spreadKnob->move( 190, 140 ); m_spreadKnob->setHintText( tr( "Spread:" ), "" ); + m_randomKnob = new Knob(KnobType::Vintage32, this); + m_randomKnob->setLabel(tr("Random")); + m_randomKnob->move(190, 190); + m_randomKnob->setHintText(tr("Random:"), ""); + // try to inform user about missing Stk-installation if( _instrument->m_filesMissing && getGUI() != nullptr ) { @@ -467,7 +520,7 @@ QWidget * MalletsInstrumentView::setupModalBarControls( QWidget * _parent ) m_stickKnob->setLabel( tr( "Stick mix" ) ); m_stickKnob->move( 190, 90 ); m_stickKnob->setHintText( tr( "Stick mix:" ), "" ); - + return( widget ); } @@ -565,6 +618,7 @@ void MalletsInstrumentView::modelChanged() // m_strikeLED->setModel( &inst->m_strikeModel ); m_presetsCombo->setModel( &inst->m_presetsModel ); m_spreadKnob->setModel( &inst->m_spreadModel ); + m_randomKnob->setModel(&inst->m_randomModel); } diff --git a/plugins/Stk/Mallets/Mallets.h b/plugins/Stk/Mallets/Mallets.h index f66ac25d011..91e2dfce140 100644 --- a/plugins/Stk/Mallets/Mallets.h +++ b/plugins/Stk/Mallets/Mallets.h @@ -124,12 +124,38 @@ class MalletsSynth return( s ); } - inline void setFrequency( const StkFloat _pitch ) + inline void setFrequency(const StkFloat _pitch) { - if( m_voice ) - { - m_voice->setFrequency( _pitch ); - } + if (m_voice) { m_voice->setFrequency(_pitch); } + } + + // ModalBar updates + inline void setVibratoGain(const StkFloat _control8) + { + // bug in stk, Control Number 8 and 1 swapped in ModalBar + // we send the control number for stick direct mix instead + if (m_voice) { m_voice->controlChange(8, _control8); } + } + + inline void setVibratoFreq(const StkFloat _control11) + { + if (m_voice) { m_voice->controlChange(11, _control11); } + } + + // Tubular Bells updates + inline void setADSR(const StkFloat _control128) + { + if (m_voice) { m_voice->controlChange(128, _control128); } + } + + inline void setLFODepth(const StkFloat _control1) + { + if (m_voice) { m_voice->controlChange(1, _control1); } + } + + inline void setLFOSpeed(const StkFloat _control11) + { + if (m_voice) { m_voice->controlChange(11, _control11); } } inline int presetIndex() @@ -197,6 +223,7 @@ class MalletsInstrument : public Instrument ComboBoxModel m_presetsModel; FloatModel m_spreadModel; + FloatModel m_randomModel; IntModel m_versionModel; BoolModel m_isOldVersionModel; @@ -255,6 +282,7 @@ public slots: ComboBox * m_presetsCombo; Knob * m_spreadKnob; + Knob * m_randomKnob; }; diff --git a/plugins/Watsyn/CMakeLists.txt b/plugins/Watsyn/CMakeLists.txt index 5aec12a46d2..b43abbb07b7 100644 --- a/plugins/Watsyn/CMakeLists.txt +++ b/plugins/Watsyn/CMakeLists.txt @@ -1,5 +1,8 @@ -INCLUDE(BuildPlugin) +include(BuildPlugin) -LINK_DIRECTORIES(${SAMPLERATE_LIBRARY_DIRS}) -LINK_LIBRARIES(${SAMPLERATE_LIBRARIES}) -BUILD_PLUGIN(watsyn Watsyn.cpp Watsyn.h MOCFILES Watsyn.h EMBEDDED_RESOURCES *.png) +build_plugin(watsyn + Watsyn.cpp Watsyn.h + MOCFILES Watsyn.h + EMBEDDED_RESOURCES *.png +) +target_link_libraries(watsyn SampleRate::samplerate) diff --git a/plugins/ZynAddSubFx/zynaddsubfx b/plugins/ZynAddSubFx/zynaddsubfx index 551e816a633..7ad5663cbee 160000 --- a/plugins/ZynAddSubFx/zynaddsubfx +++ b/plugins/ZynAddSubFx/zynaddsubfx @@ -1 +1 @@ -Subproject commit 551e816a6334fd190c74ce971378063b2757b47b +Subproject commit 7ad5663cbeebc02d73fd3ad666e428c1287f2cda diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index f483d8b4137..c074ea2ef5e 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -58,8 +58,6 @@ FILE(RELATIVE_PATH PLUGIN_DIR_RELATIVE "/${BIN_DIR}" "/${PLUGIN_DIR}") ADD_DEFINITIONS(-DLIB_DIR="${LIB_DIR_RELATIVE}" -DPLUGIN_DIR="${PLUGIN_DIR_RELATIVE}" ${PULSEAUDIO_DEFINITIONS}) INCLUDE_DIRECTORIES( ${JACK_INCLUDE_DIRS} - ${SAMPLERATE_INCLUDE_DIRS} - ${SNDFILE_INCLUDE_DIRS} ${SNDIO_INCLUDE_DIRS} ${FFTW3F_INCLUDE_DIRS} ) @@ -79,10 +77,6 @@ IF(NOT ("${PULSEAUDIO_INCLUDE_DIR}" STREQUAL "")) INCLUDE_DIRECTORIES("${PULSEAUDIO_INCLUDE_DIR}") ENDIF() -IF(NOT ("${OGGVORBIS_INCLUDE_DIR}" STREQUAL "")) - INCLUDE_DIRECTORIES("${OGGVORBIS_INCLUDE_DIR}") -ENDIF() - IF(NOT ("${LV2_INCLUDE_DIRS}" STREQUAL "")) INCLUDE_DIRECTORIES(${LV2_INCLUDE_DIRS}) ENDIF() @@ -170,6 +164,10 @@ if(LMMS_HAVE_MP3LAME) list(APPEND EXTRA_LIBRARIES mp3lame::mp3lame) endif() +if(LMMS_HAVE_OGGVORBIS) + list(APPEND EXTRA_LIBRARIES Vorbis::vorbisenc Vorbis::vorbisfile) +endif() + if(LMMS_USE_MINGW_STD_THREADS) list(APPEND EXTRA_LIBRARIES mingw_stdthreads) endif() @@ -184,15 +182,14 @@ SET(LMMS_REQUIRED_LIBS ${LMMS_REQUIRED_LIBS} ${SNDIO_LIBRARIES} ${PULSEAUDIO_LIBRARIES} ${JACK_LIBRARIES} - ${OGGVORBIS_LIBRARIES} ${LV2_LIBRARIES} ${SUIL_LIBRARIES} ${LILV_LIBRARIES} - ${SAMPLERATE_LIBRARIES} - ${SNDFILE_LIBRARIES} ${FFTW3F_LIBRARIES} - ${EXTRA_LIBRARIES} rpmalloc + SampleRate::samplerate + SndFile::sndfile + ${EXTRA_LIBRARIES} ) # Expose required libs for tests binary @@ -211,10 +208,11 @@ FOREACH(LIB ${LMMS_REQUIRED_LIBS}) ENDIF() ENDFOREACH() +set_target_properties(lmms PROPERTIES + ENABLE_EXPORTS ON +) + IF(LMMS_BUILD_WIN32) - SET_TARGET_PROPERTIES(lmms PROPERTIES - ENABLE_EXPORTS ON - ) IF(NOT MSVC) SET_PROPERTY(TARGET lmms APPEND_STRING PROPERTY LINK_FLAGS " -mwindows" @@ -228,10 +226,6 @@ IF(LMMS_BUILD_WIN32) ) ENDIF() ELSE() - IF(NOT LMMS_BUILD_APPLE) - SET_TARGET_PROPERTIES(lmms PROPERTIES LINK_FLAGS "${LINK_FLAGS} -Wl,-E") - ENDIF(NOT LMMS_BUILD_APPLE) - if(CMAKE_INSTALL_MANDIR) SET(INSTALL_MANDIR ${CMAKE_INSTALL_MANDIR}) ELSE(CMAKE_INSTALL_MANDIR) diff --git a/src/core/AudioEngine.cpp b/src/core/AudioEngine.cpp index 29c54647cf7..59bb87a307f 100644 --- a/src/core/AudioEngine.cpp +++ b/src/core/AudioEngine.cpp @@ -126,6 +126,9 @@ AudioEngine::AudioEngine( bool renderOnly ) : m_framesPerPeriod = DEFAULT_BUFFER_SIZE; } + // lmms works with chunks of size DEFAULT_BUFFER_SIZE (256) and only the final mix will use the actual + // buffer size. Plugins don't see a larger buffer size than 256. If m_framesPerPeriod is larger than + // DEFAULT_BUFFER_SIZE, it's set to DEFAULT_BUFFER_SIZE and the rest is handled by an increased fifoSize. else if( m_framesPerPeriod > DEFAULT_BUFFER_SIZE ) { fifoSize = m_framesPerPeriod / DEFAULT_BUFFER_SIZE; @@ -394,6 +397,17 @@ void AudioEngine::renderStageInstruments() AudioEngineWorkerThread::fillJobQueue(m_playHandles); AudioEngineWorkerThread::startAndWaitForJobs(); +} + + + +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(); // removed all play handles which are done for( PlayHandleList::Iterator it = m_playHandles.begin(); @@ -424,17 +438,6 @@ void AudioEngine::renderStageInstruments() -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(); -} - - - void AudioEngine::renderStageMix() { AudioEngineProfiler::Probe profilerProbe(m_profiler, AudioEngineProfiler::DetailType::Mixing); diff --git a/src/core/AutomationClip.cpp b/src/core/AutomationClip.cpp index 3b36f6b49b7..3bfc5cf8edd 100644 --- a/src/core/AutomationClip.cpp +++ b/src/core/AutomationClip.cpp @@ -422,6 +422,32 @@ void AutomationClip::resetNodes(const int tick0, const int tick1) +void AutomationClip::resetTangents(const int tick0, const int tick1) +{ + if (tick0 == tick1) + { + auto it = m_timeMap.find(TimePos(tick0)); + if (it != m_timeMap.end()) + { + it.value().setLockedTangents(false); + generateTangents(it, 1); + } + return; + } + + TimePos start = TimePos(std::min(tick0, tick1)); + TimePos end = TimePos(std::max(tick0, tick1)); + + for (auto it = m_timeMap.lowerBound(start), endIt = m_timeMap.upperBound(end); it != endIt; ++it) + { + it.value().setLockedTangents(false); + generateTangents(it, 1); + } +} + + + + void AutomationClip::recordValue(TimePos time, float value) { QMutexLocker m(&m_clipMutex); @@ -467,16 +493,31 @@ TimePos AutomationClip::setDragValue( // inValue m_dragKeepOutValue = false; + // We will set the tangents back to what they were if the node had + // its tangents locked + m_dragLockedTan = false; + // Check if we already have a node on the position we are dragging // and if we do, store the outValue so the discrete jump can be kept + // and information about the tangents timeMap::iterator it = m_timeMap.find(newTime); if (it != m_timeMap.end()) { + // If we don't have a discrete jump, the outValue will be the + // same as the inValue if (OFFSET(it) != 0) { m_dragKeepOutValue = true; m_dragOutValue = OUTVAL(it); } + // For the tangents, we will only keep them if the tangents were + // locked + if (LOCKEDTAN(it)) + { + m_dragLockedTan = true; + m_dragInTan = INTAN(it); + m_dragOutTan = OUTTAN(it); + } } this->removeNode(newTime); @@ -489,12 +530,31 @@ TimePos AutomationClip::setDragValue( generateTangents(); + TimePos returnedPos; + if (m_dragKeepOutValue) { - return this->putValues(time, value, m_dragOutValue, quantPos, controlKey); + returnedPos = this->putValues(time, value, m_dragOutValue, quantPos, controlKey); + } + else + { + returnedPos = this->putValue(time, value, quantPos, controlKey); + } + + // Set the tangents on the newly created node if they were locked + // before dragging + if (m_dragLockedTan) + { + timeMap::iterator it = m_timeMap.find(returnedPos); + if (it != m_timeMap.end()) + { + it.value().setInTangent(m_dragInTan); + it.value().setOutTangent(m_dragOutTan); + it.value().setLockedTangents(true); + } } - return this->putValue(time, value, quantPos, controlKey); + return returnedPos; } @@ -783,6 +843,9 @@ void AutomationClip::saveSettings( QDomDocument & _doc, QDomElement & _this ) element.setAttribute("pos", POS(it)); element.setAttribute("value", INVAL(it)); element.setAttribute("outValue", OUTVAL(it)); + element.setAttribute("inTan", INTAN(it)); + element.setAttribute("outTan", OUTTAN(it)); + element.setAttribute("lockedTan", static_cast(LOCKEDTAN(it))); _this.appendChild( element ); } @@ -804,6 +867,11 @@ void AutomationClip::loadSettings( const QDomElement & _this ) { QMutexLocker m(&m_clipMutex); + // Legacy compatibility: Previously tangents were not stored in + // the project file. So if any node doesn't have tangent information + // we will generate the tangents + bool shouldGenerateTangents = false; + clear(); movePosition( _this.attribute( "pos" ).toInt() ); @@ -828,6 +896,22 @@ void AutomationClip::loadSettings( const QDomElement & _this ) float timeMapOutValue = LocaleHelper::toFloat(element.attribute("outValue")); m_timeMap[timeMapPos] = AutomationNode(this, timeMapInValue, timeMapOutValue, timeMapPos); + + // Load tangents if there is information about it (it's enough to check for either inTan or outTan) + if (element.hasAttribute("inTan")) + { + float inTan = LocaleHelper::toFloat(element.attribute("inTan")); + float outTan = LocaleHelper::toFloat(element.attribute("outTan")); + bool lockedTan = static_cast(element.attribute("lockedTan", "0").toInt()); + + m_timeMap[timeMapPos].setInTangent(inTan); + m_timeMap[timeMapPos].setOutTangent(outTan); + m_timeMap[timeMapPos].setLockedTangents(lockedTan); + } + else + { + shouldGenerateTangents = true; + } } else if( element.tagName() == "object" ) { @@ -851,7 +935,8 @@ void AutomationClip::loadSettings( const QDomElement & _this ) { changeLength( len ); } - generateTangents(); + + if (shouldGenerateTangents) { generateTangents(); } } @@ -1108,6 +1193,12 @@ void AutomationClip::generateTangents(timeMap::iterator it, int numToGenerate) for (int i = 0; i < numToGenerate && it != m_timeMap.end(); ++i, ++it) { + // Skip the node if it has locked tangents (were manually edited) + if (LOCKEDTAN(it)) + { + continue; + } + if (it + 1 == m_timeMap.end()) { // Previously, the last value's tangent was always set to 0. That logic was kept for both tangents diff --git a/src/core/AutomationNode.cpp b/src/core/AutomationNode.cpp index eee4df8d21e..f15c28f8028 100644 --- a/src/core/AutomationNode.cpp +++ b/src/core/AutomationNode.cpp @@ -37,7 +37,8 @@ AutomationNode::AutomationNode() : m_inValue(0), m_outValue(0), m_inTangent(0), - m_outTangent(0) + m_outTangent(0), + m_lockedTangents(false) { } @@ -47,7 +48,8 @@ AutomationNode::AutomationNode(AutomationClip* clip, float value, int pos) : m_inValue(value), m_outValue(value), m_inTangent(0), - m_outTangent(0) + m_outTangent(0), + m_lockedTangents(false) { } @@ -57,7 +59,8 @@ AutomationNode::AutomationNode(AutomationClip* clip, float inValue, float outVal m_inValue(inValue), m_outValue(outValue), m_inTangent(0), - m_outTangent(0) + m_outTangent(0), + m_lockedTangents(false) { } diff --git a/src/core/DataFile.cpp b/src/core/DataFile.cpp index 8d0a8dca43f..b87d069319e 100644 --- a/src/core/DataFile.cpp +++ b/src/core/DataFile.cpp @@ -35,6 +35,7 @@ #include #include #include +#include #include "base64.h" #include "ConfigManager.h" @@ -231,8 +232,11 @@ bool DataFile::validate( QString extension ) { return true; } - if( extension == "wav" || extension == "ogg" || - extension == "ds" ) + if( extension == "wav" || extension == "ogg" || extension == "ds" +#ifdef LMMS_HAVE_SNDFILE_MP3 + || extension == "mp3" +#endif + ) { return true; } @@ -302,7 +306,7 @@ void DataFile::write( QTextStream & _strm ) bool DataFile::writeFile(const QString& filename, bool withResources) { // Small lambda function for displaying errors - auto showError = [this](QString title, QString body){ + auto showError = [](QString title, QString body){ if (gui::getGUI() != nullptr) { QMessageBox mb; @@ -376,12 +380,12 @@ bool DataFile::writeFile(const QString& filename, bool withResources) } } - QFile outfile (fullNameTemp); + QSaveFile outfile(fullNameTemp); if (!outfile.open(QIODevice::WriteOnly | QIODevice::Truncate)) { showError(SongEditor::tr("Could not write file"), - SongEditor::tr("Could not open %1 for writing. You probably are not permitted to" + SongEditor::tr("Could not open %1 for writing. You probably are not permitted to " "write to this file. Please make sure you have write-access to " "the file and try again.").arg(fullName)); @@ -402,30 +406,29 @@ bool DataFile::writeFile(const QString& filename, bool withResources) write( ts ); } - outfile.close(); - - // make sure the file has been written correctly - if( QFileInfo( outfile.fileName() ).size() > 0 ) + if (!outfile.commit()) { - if( ConfigManager::inst()->value( "app", "disablebackup" ).toInt() ) - { - // remove current file - QFile::remove( fullName ); - } - else - { - // remove old backup file - QFile::remove( fullNameBak ); - // move current file to backup file - QFile::rename( fullName, fullNameBak ); - } - // move temporary file to current file - QFile::rename( fullNameTemp, fullName ); + showError(SongEditor::tr("Could not write file"), + SongEditor::tr("An unknown error has occured and the file could not be saved.")); + return false; + } - return true; + if (ConfigManager::inst()->value("app", "disablebackup").toInt()) + { + // remove current file + QFile::remove(fullName); + } + else + { + // remove old backup file + QFile::remove(fullNameBak); + // move current file to backup file + QFile::rename(fullName, fullNameBak); } + // move temporary file to current file + QFile::rename(fullNameTemp, fullName); - return false; + return true; } diff --git a/src/core/DrumSynth.cpp b/src/core/DrumSynth.cpp index bc5455c96fc..decc6bfa26d 100644 --- a/src/core/DrumSynth.cpp +++ b/src/core/DrumSynth.cpp @@ -273,7 +273,9 @@ int DrumSynth::GetDSFileSamples(QString dsfile, int16_t *&wave, int channels, sa //generation long Length, tpos=0, tplus, totmp, t, i, j; float x[3] = {0.f, 0.f, 0.f}; - float MasterTune, randmax, randmax2; + float MasterTune; + constexpr float randmax = 1.f / static_cast(RAND_MAX); + constexpr float randmax2 = 2.f / static_cast(RAND_MAX); int MainFilter, HighPass; long NON, NT, TON, DiON, TDroop=0, DStep; @@ -454,7 +456,6 @@ int DrumSynth::GetDSFileSamples(QString dsfile, int16_t *&wave, int channels, sa } //prepare envelopes - randmax = 1.f / RAND_MAX; randmax2 = 2.f * randmax; for (i=1;i<8;i++) { envData[i][NEXTT]=0; envData[i][PNT]=0; } Length = LongestEnv(); @@ -745,4 +746,4 @@ int DrumSynth::GetDSFileSamples(QString dsfile, int16_t *&wave, int channels, sa } -} // namespace lmms \ No newline at end of file +} // namespace lmms diff --git a/src/core/Instrument.cpp b/src/core/Instrument.cpp index b715bcac02c..a7cfc467ba4 100644 --- a/src/core/Instrument.cpp +++ b/src/core/Instrument.cpp @@ -179,21 +179,18 @@ void Instrument::applyFadeIn(sampleFrame * buf, NotePlayHandle * n) void Instrument::applyRelease( sampleFrame * buf, const NotePlayHandle * _n ) { - const fpp_t frames = _n->framesLeftForCurrentPeriod(); - const fpp_t fpp = Engine::audioEngine()->framesPerPeriod(); - const f_cnt_t fl = _n->framesLeft(); - if( fl <= desiredReleaseFrames()+fpp ) + const auto fpp = Engine::audioEngine()->framesPerPeriod(); + const auto releaseFrames = desiredReleaseFrames(); + + const auto endFrame = _n->framesLeft(); + const auto startFrame = std::max(0, endFrame - releaseFrames); + + for (auto f = startFrame; f < endFrame && f < fpp; f++) { - for( fpp_t f = (fpp_t)( ( fl > desiredReleaseFrames() ) ? - (std::max(fpp - desiredReleaseFrames(), 0) + - fl % fpp) : 0); f < frames; ++f) + const float fac = (float)(endFrame - f) / (float)releaseFrames; + for (ch_cnt_t ch = 0; ch < DEFAULT_CHANNELS; ch++) { - const float fac = (float)( fl-f-1 ) / - desiredReleaseFrames(); - for( ch_cnt_t ch = 0; ch < DEFAULT_CHANNELS; ++ch ) - { - buf[f][ch] *= fac; - } + buf[f][ch] *= fac; } } } diff --git a/src/core/InstrumentFunctions.cpp b/src/core/InstrumentFunctions.cpp index 431afd2fe5c..976363d3d08 100644 --- a/src/core/InstrumentFunctions.cpp +++ b/src/core/InstrumentFunctions.cpp @@ -409,7 +409,7 @@ void InstrumentFunctionArpeggio::processNote( NotePlayHandle * _n ) // Skip notes randomly if( m_arpSkipModel.value() ) { - if( 100 * ( (float) rand() / (float)( RAND_MAX + 1.0f ) ) < m_arpSkipModel.value() ) + if (100 * static_cast(rand()) / (static_cast(RAND_MAX) + 1.0f) < m_arpSkipModel.value()) { // update counters frames_processed += arp_frames; @@ -425,7 +425,7 @@ void InstrumentFunctionArpeggio::processNote( NotePlayHandle * _n ) if( m_arpMissModel.value() ) { - if( 100 * ( (float) rand() / (float)( RAND_MAX + 1.0f ) ) < m_arpMissModel.value() ) + if (100 * static_cast(rand()) / (static_cast(RAND_MAX) + 1.0f) < m_arpMissModel.value()) { dir = ArpDirection::Random; } diff --git a/src/core/SampleBuffer.cpp b/src/core/SampleBuffer.cpp index 5e2d09c573e..2a0076a283c 100644 --- a/src/core/SampleBuffer.cpp +++ b/src/core/SampleBuffer.cpp @@ -305,10 +305,6 @@ void SampleBuffer::update(bool keepSettings) } sf_close(sndFile); } - else - { - fileLoadError = FileLoadError::Invalid; - } f.close(); } @@ -337,6 +333,11 @@ void SampleBuffer::update(bool keepSettings) { m_frames = decodeSampleDS(file, buf, channels, samplerate); } + + if (m_frames == 0) + { + fileLoadError = FileLoadError::Invalid; + } } if (m_frames == 0 || fileLoadError != FileLoadError::None) // if still no frames, bail @@ -1184,14 +1185,20 @@ QString SampleBuffer::openAudioFile() const // set filters QStringList types; - types << tr("All Audio-Files (*.wav *.ogg *.ds *.flac *.spx *.voc " + types << tr("All Audio-Files (*.wav *.ogg " +#ifdef LMMS_HAVE_SNDFILE_MP3 + "*.mp3 " +#endif + "*.ds *.flac *.spx *.voc " "*.aif *.aiff *.au *.raw)") << tr("Wave-Files (*.wav)") << tr("OGG-Files (*.ogg)") +#ifdef LMMS_HAVE_SNDFILE_MP3 + << tr("MP3-Files (*.mp3)") +#endif << tr("DrumSynth-Files (*.ds)") << tr("FLAC-Files (*.flac)") << tr("SPEEX-Files (*.spx)") - //<< tr("MP3-Files (*.mp3)") //<< tr("MIDI-Files (*.mid)") << tr("VOC-Files (*.voc)") << tr("AIFF-Files (*.aif *.aiff)") @@ -1389,7 +1396,7 @@ SampleBuffer * SampleBuffer::resample(const sample_rate_t srcSR, const sample_ra } else { - printf("Error: src_new() failed in sample_buffer.cpp!\n"); + printf("Error: src_new() failed in SampleBuffer.cpp!\n"); } dstSB->update(); return dstSB; @@ -1612,7 +1619,7 @@ SampleBuffer::handleState::handleState(bool varyingPitch, int interpolationMode) if ((m_resamplingData = src_new(interpolationMode, DEFAULT_CHANNELS, &error)) == nullptr) { - qDebug("Error: src_new() failed in sample_buffer.cpp!\n"); + qDebug("Error: src_new() failed in SampleBuffer.cpp!\n"); } } diff --git a/src/core/audio/AudioJack.cpp b/src/core/audio/AudioJack.cpp index 7371c7bfb93..a4fd2c095de 100644 --- a/src/core/audio/AudioJack.cpp +++ b/src/core/audio/AudioJack.cpp @@ -26,46 +26,47 @@ #ifdef LMMS_HAVE_JACK +#include #include -#include #include +#include "AudioEngine.h" +#include "ConfigManager.h" #include "Engine.h" #include "GuiApplication.h" -#include "gui_templates.h" -#include "ConfigManager.h" #include "LcdSpinBox.h" #include "MainWindow.h" -#include "AudioEngine.h" #include "MidiJack.h" - +#include "gui_templates.h" namespace lmms { -AudioJack::AudioJack( bool & _success_ful, AudioEngine* _audioEngine ) : - AudioDevice(std::clamp( - ConfigManager::inst()->value("audiojack", "channels").toInt(), - DEFAULT_CHANNELS, - SURROUND_CHANNELS), _audioEngine), - m_client( nullptr ), - m_active( false ), - m_midiClient( nullptr ), - m_tempOutBufs( new jack_default_audio_sample_t *[channels()] ), - m_outBuf( new surroundSampleFrame[audioEngine()->framesPerPeriod()] ), - m_framesDoneInCurBuf( 0 ), - m_framesToDoInCurBuf( 0 ) + +AudioJack::AudioJack(bool& successful, AudioEngine* audioEngineParam) + : AudioDevice( + // clang-format off + std::clamp( + ConfigManager::inst()->value("audiojack", "channels").toInt(), + DEFAULT_CHANNELS, + SURROUND_CHANNELS + ), + // clang-format on + audioEngineParam) + , m_client(nullptr) + , m_active(false) + , m_midiClient(nullptr) + , m_tempOutBufs(new jack_default_audio_sample_t*[channels()]) + , m_outBuf(new surroundSampleFrame[audioEngine()->framesPerPeriod()]) + , m_framesDoneInCurBuf(0) + , m_framesToDoInCurBuf(0) { m_stopped = true; - _success_ful = initJackClient(); - if( _success_ful ) - { - connect( this, SIGNAL(zombified()), - this, SLOT(restartAfterZombified()), - Qt::QueuedConnection ); + successful = initJackClient(); + if (successful) { + connect(this, SIGNAL(zombified()), this, SLOT(restartAfterZombified()), Qt::QueuedConnection); } - } @@ -73,21 +74,18 @@ AudioJack::AudioJack( bool & _success_ful, AudioEngine* _audioEngine ) : AudioJack::~AudioJack() { - stopProcessing(); + AudioJack::stopProcessing(); #ifdef AUDIO_PORT_SUPPORT - while( m_portMap.size() ) + while (m_portMap.size()) { - unregisterPort( m_portMap.begin().key() ); + unregisterPort(m_portMap.begin().key()); } #endif - if( m_client != nullptr ) + if (m_client != nullptr) { - if( m_active ) - { - jack_deactivate( m_client ); - } - jack_client_close( m_client ); + if (m_active) { jack_deactivate(m_client); } + jack_client_close(m_client); } delete[] m_tempOutBufs; @@ -100,97 +98,79 @@ AudioJack::~AudioJack() void AudioJack::restartAfterZombified() { - if( initJackClient() ) + if (initJackClient()) { m_active = false; startProcessing(); - QMessageBox::information(gui::getGUI()->mainWindow(), - tr( "JACK client restarted" ), - tr( "LMMS was kicked by JACK for some reason. " + QMessageBox::information(gui::getGUI()->mainWindow(), tr("JACK client restarted"), + tr( "LMMS was kicked by JACK for some reason. " "Therefore the JACK backend of LMMS has been " "restarted. You will have to make manual " - "connections again." ) ); + "connections again.")); } else { - QMessageBox::information(gui::getGUI()->mainWindow(), - tr( "JACK server down" ), - tr( "The JACK server seems to have been shutdown " + QMessageBox::information(gui::getGUI()->mainWindow(), tr("JACK server down"), + tr( "The JACK server seems to have been shutdown " "and starting a new instance failed. " "Therefore LMMS is unable to proceed. " "You should save your project and restart " - "JACK and LMMS." ) ); + "JACK and LMMS.")); } } -AudioJack* AudioJack::addMidiClient(MidiJack *midiClient) + +AudioJack* AudioJack::addMidiClient(MidiJack* midiClient) { - if( m_client == nullptr ) - return nullptr; + if (m_client == nullptr) { return nullptr; } m_midiClient = midiClient; return this; } + + + bool AudioJack::initJackClient() { - QString clientName = ConfigManager::inst()->value( "audiojack", - "clientname" ); - if( clientName.isEmpty() ) - { - clientName = "lmms"; - } + QString clientName = ConfigManager::inst()->value("audiojack", "clientname"); + if (clientName.isEmpty()) { clientName = "lmms"; } - const char * serverName = nullptr; + const char* serverName = nullptr; jack_status_t status; - m_client = jack_client_open( clientName.toLatin1().constData(), - JackNullOption, &status, - serverName ); - if( m_client == nullptr ) + m_client = jack_client_open(clientName.toLatin1().constData(), JackNullOption, &status, serverName); + if (m_client == nullptr) { - printf( "jack_client_open() failed, status 0x%2.0x\n", status ); - if( status & JackServerFailed ) - { - printf( "Could not connect to JACK server.\n" ); - } + printf("jack_client_open() failed, status 0x%2.0x\n", status); + if (status & JackServerFailed) { printf("Could not connect to JACK server.\n"); } return false; } - if( status & JackNameNotUnique ) + if (status & JackNameNotUnique) { - printf( "there's already a client with name '%s', so unique " - "name '%s' was assigned\n", clientName. - toLatin1().constData(), - jack_get_client_name( m_client ) ); + printf( "there's already a client with name '%s', so unique " + "name '%s' was assigned\n", + clientName.toLatin1().constData(), jack_get_client_name(m_client)); } // set process-callback - jack_set_process_callback( m_client, staticProcessCallback, this ); + jack_set_process_callback(m_client, staticProcessCallback, this); // set shutdown-callback - jack_on_shutdown( m_client, shutdownCallback, this ); - - + jack_on_shutdown(m_client, shutdownCallback, this); - if( jack_get_sample_rate( m_client ) != sampleRate() ) - { - setSampleRate( jack_get_sample_rate( m_client ) ); - } + if (jack_get_sample_rate(m_client) != sampleRate()) { setSampleRate(jack_get_sample_rate(m_client)); } - for( ch_cnt_t ch = 0; ch < channels(); ++ch ) + for (ch_cnt_t ch = 0; ch < channels(); ++ch) { - QString name = QString( "master out " ) + - ( ( ch % 2 ) ? "R" : "L" ) + - QString::number( ch / 2 + 1 ); - m_outputPorts.push_back( jack_port_register( m_client, - name.toLatin1().constData(), - JACK_DEFAULT_AUDIO_TYPE, - JackPortIsOutput, 0 ) ); - if( m_outputPorts.back() == nullptr ) + QString name = QString("master out ") + ((ch % 2) ? "R" : "L") + QString::number(ch / 2 + 1); + m_outputPorts.push_back( + jack_port_register(m_client, name.toLatin1().constData(), JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0)); + if (m_outputPorts.back() == nullptr) { - printf( "no more JACK-ports available!\n" ); + printf("no more JACK-ports available!\n"); return false; } } @@ -203,51 +183,43 @@ bool AudioJack::initJackClient() void AudioJack::startProcessing() { - if( m_active || m_client == nullptr ) + if (m_active || m_client == nullptr) { m_stopped = false; return; } - if( jack_activate( m_client ) ) + if (jack_activate(m_client)) { - printf( "cannot activate client\n" ); + printf("cannot activate client\n"); return; } m_active = true; - // try to sync JACK's and LMMS's buffer-size -// jack_set_buffer_size( m_client, audioEngine()->framesPerPeriod() ); + // jack_set_buffer_size( m_client, audioEngine()->framesPerPeriod() ); - - - const char * * ports = jack_get_ports( m_client, nullptr, nullptr, - JackPortIsPhysical | - JackPortIsInput ); - if( ports == nullptr ) + const char** ports = jack_get_ports(m_client, nullptr, nullptr, JackPortIsPhysical | JackPortIsInput); + if (ports == nullptr) { - printf( "no physical playback ports. you'll have to do " - "connections at your own!\n" ); + printf("no physical playback ports. you'll have to do " + "connections at your own!\n"); } else { - for( ch_cnt_t ch = 0; ch < channels(); ++ch ) + for (ch_cnt_t ch = 0; ch < channels(); ++ch) { - if( jack_connect( m_client, jack_port_name( - m_outputPorts[ch] ), - ports[ch] ) ) + if (jack_connect(m_client, jack_port_name(m_outputPorts[ch]), ports[ch])) { - printf( "cannot connect output ports. you'll " - "have to do connections at your own!\n" - ); + printf("cannot connect output ports. you'll " + "have to do connections at your own!\n"); } } } m_stopped = false; - free( ports ); + jack_free(ports); } @@ -263,14 +235,11 @@ void AudioJack::stopProcessing() void AudioJack::applyQualitySettings() { - if( hqAudio() ) + if (hqAudio()) { - setSampleRate( Engine::audioEngine()->processingSampleRate() ); + setSampleRate(Engine::audioEngine()->processingSampleRate()); - if( jack_get_sample_rate( m_client ) != sampleRate() ) - { - setSampleRate( jack_get_sample_rate( m_client ) ); - } + if (jack_get_sample_rate(m_client) != sampleRate()) { setSampleRate(jack_get_sample_rate(m_client)); } } AudioDevice::applyQualitySettings(); @@ -279,106 +248,91 @@ void AudioJack::applyQualitySettings() -void AudioJack::registerPort( AudioPort * _port ) +void AudioJack::registerPort(AudioPort* port) { #ifdef AUDIO_PORT_SUPPORT // make sure, port is not already registered - unregisterPort( _port ); - const QString name[2] = { _port->name() + " L", - _port->name() + " R" } ; + unregisterPort(port); + const QString name[2] = {port->name() + " L", port->name() + " R"}; - for( ch_cnt_t ch = 0; ch < DEFAULT_CHANNELS; ++ch ) + for (ch_cnt_t ch = 0; ch < DEFAULT_CHANNELS; ++ch) { - m_portMap[_port].ports[ch] = jack_port_register( m_client, - name[ch].toLatin1().constData(), - JACK_DEFAULT_AUDIO_TYPE, - JackPortIsOutput, 0 ); + m_portMap[port].ports[ch] = jack_port_register( + m_client, name[ch].toLatin1().constData(), JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0); } +#else + (void)port; #endif } -void AudioJack::unregisterPort( AudioPort * _port ) +void AudioJack::unregisterPort(AudioPort* port) { #ifdef AUDIO_PORT_SUPPORT - if( m_portMap.contains( _port ) ) + if (m_portMap.contains(port)) { - for( ch_cnt_t ch = 0; ch < DEFAULT_CHANNELS; ++ch ) + for (ch_cnt_t ch = 0; ch < DEFAULT_CHANNELS; ++ch) { - if( m_portMap[_port].ports[ch] != nullptr ) - { - jack_port_unregister( m_client, - m_portMap[_port].ports[ch] ); - } + if (m_portMap[port].ports[ch] != nullptr) { jack_port_unregister(m_client, m_portMap[port].ports[ch]); } } - m_portMap.erase( m_portMap.find( _port ) ); + m_portMap.erase(m_portMap.find(port)); } +#else + (void)port; #endif } - - - -void AudioJack::renamePort( AudioPort * _port ) +void AudioJack::renamePort(AudioPort* port) { #ifdef AUDIO_PORT_SUPPORT - if( m_portMap.contains( _port ) ) + if (m_portMap.contains(port)) { - const QString name[2] = { _port->name() + " L", - _port->name() + " R" }; - for( ch_cnt_t ch = 0; ch < DEFAULT_CHANNELS; ++ch ) + const QString name[2] = {port->name() + " L", port->name() + " R"}; + for (ch_cnt_t ch = 0; ch < DEFAULT_CHANNELS; ++ch) { #ifdef LMMS_HAVE_JACK_PRENAME - jack_port_rename( m_client, m_portMap[_port].ports[ch], - name[ch].toLatin1().constData() ); + jack_port_rename(m_client, m_portMap[port].ports[ch], name[ch].toLatin1().constData()); #else - jack_port_set_name( m_portMap[_port].ports[ch], - name[ch].toLatin1().constData() ); + jack_port_set_name(m_portMap[port].ports[ch], name[ch].toLatin1().constData()); #endif } } +#else + (void)port; #endif // AUDIO_PORT_SUPPORT } -int AudioJack::processCallback( jack_nframes_t _nframes, void * _udata ) +int AudioJack::processCallback(jack_nframes_t nframes) { // do midi processing first so that midi input can // add to the following sound processing - if( m_midiClient && _nframes > 0 ) + if (m_midiClient && nframes > 0) { - m_midiClient.load()->JackMidiRead(_nframes); - m_midiClient.load()->JackMidiWrite(_nframes); + m_midiClient.load()->JackMidiRead(nframes); + m_midiClient.load()->JackMidiWrite(nframes); } - for( int c = 0; c < channels(); ++c ) + for (int c = 0; c < channels(); ++c) { - m_tempOutBufs[c] = - (jack_default_audio_sample_t *) jack_port_get_buffer( - m_outputPorts[c], _nframes ); + m_tempOutBufs[c] = (jack_default_audio_sample_t*)jack_port_get_buffer(m_outputPorts[c], nframes); } #ifdef AUDIO_PORT_SUPPORT - const int frames = std::min(_nframes, audioEngine()->framesPerPeriod()); - for( JackPortMap::iterator it = m_portMap.begin(); - it != m_portMap.end(); ++it ) + const int frames = std::min(nframes, audioEngine()->framesPerPeriod()); + for (JackPortMap::iterator it = m_portMap.begin(); it != m_portMap.end(); ++it) { - for( ch_cnt_t ch = 0; ch < channels(); ++ch ) + for (ch_cnt_t ch = 0; ch < channels(); ++ch) { - if( it.value().ports[ch] == nullptr ) - { - continue; - } - jack_default_audio_sample_t * buf = - (jack_default_audio_sample_t *) jack_port_get_buffer( - it.value().ports[ch], - _nframes ); - for( int frame = 0; frame < frames; ++frame ) + if (it.value().ports[ch] == nullptr) { continue; } + jack_default_audio_sample_t* buf + = (jack_default_audio_sample_t*)jack_port_get_buffer(it.value().ports[ch], nframes); + for (int frame = 0; frame < frames; ++frame) { buf[frame] = it.key()->buffer()[frame][ch]; } @@ -387,28 +341,25 @@ int AudioJack::processCallback( jack_nframes_t _nframes, void * _udata ) #endif jack_nframes_t done = 0; - while( done < _nframes && m_stopped == false ) + while (done < nframes && !m_stopped) { - jack_nframes_t todo = std::min( - _nframes, - m_framesToDoInCurBuf - - m_framesDoneInCurBuf); + jack_nframes_t todo = std::min(nframes - done, m_framesToDoInCurBuf - m_framesDoneInCurBuf); const float gain = audioEngine()->masterGain(); - for( int c = 0; c < channels(); ++c ) + for (int c = 0; c < channels(); ++c) { - jack_default_audio_sample_t * o = m_tempOutBufs[c]; - for( jack_nframes_t frame = 0; frame < todo; ++frame ) + jack_default_audio_sample_t* o = m_tempOutBufs[c]; + for (jack_nframes_t frame = 0; frame < todo; ++frame) { - o[done+frame] = m_outBuf[m_framesDoneInCurBuf+frame][c] * gain; + o[done + frame] = m_outBuf[m_framesDoneInCurBuf + frame][c] * gain; } } done += todo; m_framesDoneInCurBuf += todo; - if( m_framesDoneInCurBuf == m_framesToDoInCurBuf ) + if (m_framesDoneInCurBuf == m_framesToDoInCurBuf) { - m_framesToDoInCurBuf = getNextBuffer( m_outBuf ); + m_framesToDoInCurBuf = getNextBuffer(m_outBuf); m_framesDoneInCurBuf = 0; - if( !m_framesToDoInCurBuf ) + if (!m_framesToDoInCurBuf) { m_stopped = true; break; @@ -416,12 +367,12 @@ int AudioJack::processCallback( jack_nframes_t _nframes, void * _udata ) } } - if( _nframes != done ) + if (nframes != done) { - for( int c = 0; c < channels(); ++c ) + for (int c = 0; c < channels(); ++c) { - jack_default_audio_sample_t * b = m_tempOutBufs[c] + done; - memset( b, 0, sizeof( *b ) * ( _nframes - done ) ); + jack_default_audio_sample_t* b = m_tempOutBufs[c] + done; + memset(b, 0, sizeof(*b) * (nframes - done)); } } @@ -431,52 +382,44 @@ int AudioJack::processCallback( jack_nframes_t _nframes, void * _udata ) -int AudioJack::staticProcessCallback( jack_nframes_t _nframes, void * _udata ) +int AudioJack::staticProcessCallback(jack_nframes_t nframes, void* udata) { - return static_cast( _udata )-> - processCallback( _nframes, _udata ); + return static_cast(udata)->processCallback(nframes); } -void AudioJack::shutdownCallback( void * _udata ) +void AudioJack::shutdownCallback(void* udata) { - auto _this = static_cast(_udata); - _this->m_client = nullptr; - _this->zombified(); + auto thisClass = static_cast(udata); + thisClass->m_client = nullptr; + emit thisClass->zombified(); } - -AudioJack::setupWidget::setupWidget( QWidget * _parent ) : - AudioDeviceSetupWidget( AudioJack::name(), _parent ) +AudioJack::setupWidget::setupWidget(QWidget* parent) + : AudioDeviceSetupWidget(AudioJack::name(), parent) { - QString cn = ConfigManager::inst()->value( "audiojack", "clientname" ); - if( cn.isEmpty() ) - { - cn = "lmms"; - } - m_clientName = new QLineEdit( cn, this ); - m_clientName->setGeometry( 10, 20, 160, 20 ); + QFormLayout * form = new QFormLayout(this); - auto cn_lbl = new QLabel(tr("Client name"), this); - cn_lbl->setFont( pointSize<7>( cn_lbl->font() ) ); - cn_lbl->setGeometry( 10, 40, 160, 10 ); + QString cn = ConfigManager::inst()->value("audiojack", "clientname"); + if (cn.isEmpty()) { cn = "lmms"; } + m_clientName = new QLineEdit(cn, this); + + form->addRow(tr("Client name"), m_clientName); auto m = new gui::LcdSpinBoxModel(/* this */); - m->setRange( DEFAULT_CHANNELS, SURROUND_CHANNELS ); - m->setStep( 2 ); - m->setValue( ConfigManager::inst()->value( "audiojack", - "channels" ).toInt() ); + m->setRange(DEFAULT_CHANNELS, SURROUND_CHANNELS); + m->setStep(2); + m->setValue(ConfigManager::inst()->value("audiojack", "channels").toInt()); - m_channels = new gui::LcdSpinBox( 1, this ); - m_channels->setModel( m ); - m_channels->setLabel( tr( "Channels" ) ); - m_channels->move( 180, 20 ); + m_channels = new gui::LcdSpinBox(1, this); + m_channels->setModel(m); + form->addRow(tr("Channels"), m_channels); } @@ -492,14 +435,11 @@ AudioJack::setupWidget::~setupWidget() void AudioJack::setupWidget::saveSettings() { - ConfigManager::inst()->setValue( "audiojack", "clientname", - m_clientName->text() ); - ConfigManager::inst()->setValue( "audiojack", "channels", - QString::number( m_channels->value() ) ); + ConfigManager::inst()->setValue("audiojack", "clientname", m_clientName->text()); + ConfigManager::inst()->setValue("audiojack", "channels", QString::number(m_channels->value())); } - } // namespace lmms #endif // LMMS_HAVE_JACK diff --git a/src/core/audio/AudioOss.cpp b/src/core/audio/AudioOss.cpp index 73969533fba..8fedd3b2b9a 100644 --- a/src/core/audio/AudioOss.cpp +++ b/src/core/audio/AudioOss.cpp @@ -27,7 +27,7 @@ #ifdef LMMS_HAVE_OSS #include -#include +#include #include #include "endian_handling.h" @@ -320,12 +320,11 @@ void AudioOss::run() AudioOss::setupWidget::setupWidget( QWidget * _parent ) : AudioDeviceSetupWidget( AudioOss::name(), _parent ) { + QFormLayout * form = new QFormLayout(this); + m_device = new QLineEdit( probeDevice(), this ); - m_device->setGeometry( 10, 20, 160, 20 ); - auto dev_lbl = new QLabel(tr("Device"), this); - dev_lbl->setFont( pointSize<7>( dev_lbl->font() ) ); - dev_lbl->setGeometry( 10, 40, 160, 10 ); + form->addRow(tr("Device"), m_device); auto m = new gui::LcdSpinBoxModel(/* this */); m->setRange( DEFAULT_CHANNELS, SURROUND_CHANNELS ); @@ -335,9 +334,8 @@ AudioOss::setupWidget::setupWidget( QWidget * _parent ) : m_channels = new gui::LcdSpinBox( 1, this ); m_channels->setModel( m ); - m_channels->setLabel( tr( "Channels" ) ); - m_channels->move( 180, 20 ); + form->addRow(tr("Channels"), m_channels); } diff --git a/src/core/audio/AudioPortAudio.cpp b/src/core/audio/AudioPortAudio.cpp index c06eee3d4ad..3684a79a8d7 100644 --- a/src/core/audio/AudioPortAudio.cpp +++ b/src/core/audio/AudioPortAudio.cpp @@ -49,7 +49,7 @@ void AudioPortAudioSetupUtil::updateChannels() #ifdef LMMS_HAVE_PORTAUDIO -#include +#include #include "Engine.h" #include "ConfigManager.h" @@ -419,19 +419,13 @@ AudioPortAudio::setupWidget::setupWidget( QWidget * _parent ) : { using gui::ComboBox; - m_backend = new ComboBox( this, "BACKEND" ); - m_backend->setGeometry( 64, 15, 260, ComboBox::DEFAULT_HEIGHT ); + QFormLayout * form = new QFormLayout(this); - auto backend_lbl = new QLabel(tr("Backend"), this); - backend_lbl->setFont( pointSize<7>( backend_lbl->font() ) ); - backend_lbl->move( 8, 18 ); + m_backend = new ComboBox( this, "BACKEND" ); + form->addRow(tr("Backend"), m_backend); m_device = new ComboBox( this, "DEVICE" ); - m_device->setGeometry( 64, 35, 260, ComboBox::DEFAULT_HEIGHT ); - - auto dev_lbl = new QLabel(tr("Device"), this); - dev_lbl->setFont( pointSize<7>( dev_lbl->font() ) ); - dev_lbl->move( 8, 38 ); + form->addRow(tr("Device"), m_device); /* LcdSpinBoxModel * m = new LcdSpinBoxModel( ); m->setRange( DEFAULT_CHANNELS, SURROUND_CHANNELS ); diff --git a/src/core/audio/AudioPulseAudio.cpp b/src/core/audio/AudioPulseAudio.cpp index 26a5a02e295..3ca8764cc47 100644 --- a/src/core/audio/AudioPulseAudio.cpp +++ b/src/core/audio/AudioPulseAudio.cpp @@ -22,8 +22,8 @@ * */ +#include #include -#include #include "AudioPulseAudio.h" @@ -312,24 +312,21 @@ void AudioPulseAudio::signalConnected( bool connected ) AudioPulseAudio::setupWidget::setupWidget( QWidget * _parent ) : AudioDeviceSetupWidget( AudioPulseAudio::name(), _parent ) { - m_device = new QLineEdit( AudioPulseAudio::probeDevice(), this ); - m_device->setGeometry( 10, 20, 160, 20 ); + QFormLayout * form = new QFormLayout(this); - auto dev_lbl = new QLabel(tr("Device"), this); - dev_lbl->setFont( pointSize<7>( dev_lbl->font() ) ); - dev_lbl->setGeometry( 10, 40, 160, 10 ); + m_device = new QLineEdit( AudioPulseAudio::probeDevice(), this ); + form->addRow(tr("Device"), m_device); - auto m = new gui::LcdSpinBoxModel(/* this */); + auto m = new gui::LcdSpinBoxModel(); m->setRange( DEFAULT_CHANNELS, SURROUND_CHANNELS ); m->setStep( 2 ); m->setValue( ConfigManager::inst()->value( "audiopa", - "channels" ).toInt() ); + "channels" ).toInt() ); m_channels = new gui::LcdSpinBox( 1, this ); m_channels->setModel( m ); - m_channels->setLabel( tr( "Channels" ) ); - m_channels->move( 180, 20 ); + form->addRow(tr("Channels"), m_channels); } diff --git a/src/core/audio/AudioSdl.cpp b/src/core/audio/AudioSdl.cpp index c5ffa64a942..12aa97d6385 100644 --- a/src/core/audio/AudioSdl.cpp +++ b/src/core/audio/AudioSdl.cpp @@ -26,7 +26,7 @@ #ifdef LMMS_HAVE_SDL -#include +#include #include #include @@ -327,14 +327,12 @@ void AudioSdl::sdlInputAudioCallback(Uint8 *_buf, int _len) { AudioSdl::setupWidget::setupWidget( QWidget * _parent ) : AudioDeviceSetupWidget( AudioSdl::name(), _parent ) { + QFormLayout * form = new QFormLayout(this); + QString dev = ConfigManager::inst()->value( "audiosdl", "device" ); m_device = new QLineEdit( dev, this ); - m_device->setGeometry( 10, 20, 160, 20 ); - - auto dev_lbl = new QLabel(tr("Device"), this); - dev_lbl->setFont( pointSize<7>( dev_lbl->font() ) ); - dev_lbl->setGeometry( 10, 40, 160, 10 ); + form->addRow(tr("Device"), m_device); } diff --git a/src/core/audio/AudioSndio.cpp b/src/core/audio/AudioSndio.cpp index 0e46d08f6d1..bb9b249f87b 100644 --- a/src/core/audio/AudioSndio.cpp +++ b/src/core/audio/AudioSndio.cpp @@ -28,7 +28,7 @@ #ifdef LMMS_HAVE_SNDIO #include -#include +#include #include #include "endian_handling.h" @@ -183,12 +183,10 @@ void AudioSndio::run() AudioSndio::setupWidget::setupWidget( QWidget * _parent ) : AudioDeviceSetupWidget( AudioSndio::name(), _parent ) { - m_device = new QLineEdit( "", this ); - m_device->setGeometry( 10, 20, 160, 20 ); + QFormLayout * form = new QFormLayout(this); - QLabel * dev_lbl = new QLabel( tr( "Device" ), this ); - dev_lbl->setFont( pointSize<6>( dev_lbl->font() ) ); - dev_lbl->setGeometry( 10, 40, 160, 10 ); + m_device = new QLineEdit( "", this ); + form->addRow(tr("Device"), m_device); gui::LcdSpinBoxModel * m = new gui::LcdSpinBoxModel( /* this */ ); m->setRange( DEFAULT_CHANNELS, SURROUND_CHANNELS ); @@ -198,9 +196,8 @@ AudioSndio::setupWidget::setupWidget( QWidget * _parent ) : m_channels = new gui::LcdSpinBox( 1, this ); m_channels->setModel( m ); - m_channels->setLabel( tr( "Channels" ) ); - m_channels->move( 180, 20 ); + form->addRow(tr("Channels"), m_channels); } diff --git a/src/core/audio/AudioSoundIo.cpp b/src/core/audio/AudioSoundIo.cpp index 556909a843d..36a1929dfdf 100644 --- a/src/core/audio/AudioSoundIo.cpp +++ b/src/core/audio/AudioSoundIo.cpp @@ -26,7 +26,7 @@ #ifdef LMMS_HAVE_SOUNDIO -#include +#include #include #include "Engine.h" @@ -451,19 +451,13 @@ AudioSoundIo::setupWidget::setupWidget( QWidget * _parent ) : { m_setupUtil.m_setupWidget = this; - m_backend = new gui::ComboBox( this, "BACKEND" ); - m_backend->setGeometry( 64, 15, 260, 20 ); + QFormLayout * form = new QFormLayout(this); - QLabel * backend_lbl = new QLabel( tr( "Backend" ), this ); - backend_lbl->setFont( pointSize<7>( backend_lbl->font() ) ); - backend_lbl->move( 8, 18 ); + m_backend = new gui::ComboBox( this, "BACKEND" ); + form->addRow(tr("Backend"), m_backend); m_device = new gui::ComboBox( this, "DEVICE" ); - m_device->setGeometry( 64, 35, 260, 20 ); - - QLabel * dev_lbl = new QLabel( tr( "Device" ), this ); - dev_lbl->setFont( pointSize<7>( dev_lbl->font() ) ); - dev_lbl->move( 8, 38 ); + form->addRow(tr("Device"), m_device); // Setup models m_soundio = soundio_create(); diff --git a/src/core/lv2/Lv2Proc.cpp b/src/core/lv2/Lv2Proc.cpp index 11290013e5b..bd89cea0a8d 100644 --- a/src/core/lv2/Lv2Proc.cpp +++ b/src/core/lv2/Lv2Proc.cpp @@ -442,11 +442,16 @@ void Lv2Proc::initPlugin() if (m_instance) { - if(m_worker) { + const auto iface = static_cast( + lilv_instance_get_extension_data(m_instance, LV2_WORKER__interface)); + if (iface) { m_worker->setHandle(lilv_instance_get_handle(m_instance)); + m_worker->setInterface(iface); } for (std::size_t portNum = 0; portNum < m_ports.size(); ++portNum) + { connectPort(portNum); + } lilv_instance_activate(m_instance); } else @@ -528,12 +533,10 @@ void Lv2Proc::initPluginSpecificFeatures() // 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_worker.emplace(&m_workLock, threaded); m_features[LV2_WORKER__schedule] = m_worker->feature(); - // Note: m_worker::setHandle will still need to be called later + // note: the worker interface can not be instantiated yet - it requires m_instance. see initPlugin() } } diff --git a/src/core/lv2/Lv2Worker.cpp b/src/core/lv2/Lv2Worker.cpp index 5af955ff766..c763bacad97 100644 --- a/src/core/lv2/Lv2Worker.cpp +++ b/src/core/lv2/Lv2Worker.cpp @@ -60,10 +60,7 @@ std::size_t Lv2Worker::bufferSize() const -Lv2Worker::Lv2Worker(const LV2_Worker_Interface* iface, - Semaphore* common_work_lock, - bool threaded) : - m_iface(iface), +Lv2Worker::Lv2Worker(Semaphore* commonWorkLock, bool threaded) : m_threaded(threaded), m_response(bufferSize()), m_requests(bufferSize()), @@ -71,9 +68,8 @@ Lv2Worker::Lv2Worker(const LV2_Worker_Interface* iface, m_requestsReader(m_requests), m_responsesReader(m_responses), m_sem(0), - m_workLock(common_work_lock) + m_workLock(commonWorkLock) { - 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 @@ -91,6 +87,24 @@ Lv2Worker::Lv2Worker(const LV2_Worker_Interface* iface, +void Lv2Worker::setHandle(LV2_Handle handle) +{ + assert(handle); + m_handle = handle; +} + + + + +void Lv2Worker::setInterface(const LV2_Worker_Interface* newInterface) +{ + assert(newInterface); + m_interface = newInterface; +} + + + + Lv2Worker::~Lv2Worker() { m_exit = true; @@ -120,7 +134,9 @@ LV2_Worker_Status Lv2Worker::respond(uint32_t size, const void* data) } else { - m_iface->work_response(m_handle, size, data); + assert(m_handle); + assert(m_interface); + m_interface->work_response(m_handle, size, data); } return LV2_WORKER_SUCCESS; } @@ -136,6 +152,7 @@ void Lv2Worker::workerFunc() 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) @@ -144,8 +161,10 @@ void Lv2Worker::workerFunc() if(size > buf.size()) { buf.resize(size); } if(size) { m_requestsReader.read(size).copy(buf.data(), size); } + assert(m_handle); + assert(m_interface); m_workLock->wait(); - m_iface->work(m_handle, staticWorkerRespond, this, size, buf.data()); + m_interface->work(m_handle, staticWorkerRespond, this, size, buf.data()); m_workLock->post(); } } @@ -172,9 +191,11 @@ LV2_Worker_Status Lv2Worker::scheduleWork(uint32_t size, const void *data) } else { + assert(m_handle); + assert(m_interface); // Execute work immediately in this thread m_workLock->wait(); - m_iface->work(m_handle, staticWorkerRespond, this, size, data); + m_interface->work(m_handle, staticWorkerRespond, this, size, data); m_workLock->post(); } @@ -189,10 +210,13 @@ void Lv2Worker::emitResponses() { std::size_t read_space = m_responsesReader.read_space(); uint32_t size; - while (read_space > sizeof(size)) { + while (read_space > sizeof(size)) + { + assert(m_handle); + assert(m_interface); 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()); + m_interface->work_response(m_handle, size, m_response.data()); read_space -= sizeof(size) + size; } } diff --git a/src/gui/AudioAlsaSetupWidget.cpp b/src/gui/AudioAlsaSetupWidget.cpp index 4ea6d4c5855..7db822b4be8 100644 --- a/src/gui/AudioAlsaSetupWidget.cpp +++ b/src/gui/AudioAlsaSetupWidget.cpp @@ -23,7 +23,7 @@ */ #include -#include +#include #include "AudioAlsaSetupWidget.h" @@ -40,6 +40,8 @@ AudioAlsaSetupWidget::AudioAlsaSetupWidget( QWidget * _parent ) : AudioDeviceSetupWidget( AudioAlsa::name(), _parent ), m_selectedDevice(-1) { + QFormLayout * form = new QFormLayout(this); + m_deviceInfos = AudioAlsa::getAvailableDevices(); QString deviceText = ConfigManager::inst()->value( "audioalsa", "device" ); @@ -62,14 +64,11 @@ AudioAlsaSetupWidget::AudioAlsaSetupWidget( QWidget * _parent ) : m_selectedDevice = m_deviceComboBox->currentIndex(); - m_deviceComboBox->setGeometry( 10, 20, 160, 20 ); connect(m_deviceComboBox, SIGNAL(currentIndexChanged(int)), SLOT(onCurrentIndexChanged(int))); - auto dev_lbl = new QLabel(tr("DEVICE"), this); - dev_lbl->setFont( pointSize<7>( dev_lbl->font() ) ); - dev_lbl->setGeometry( 10, 40, 160, 10 ); + form->addRow(tr("Device"), m_deviceComboBox); auto m = new LcdSpinBoxModel(/* this */); m->setRange( DEFAULT_CHANNELS, SURROUND_CHANNELS ); @@ -79,9 +78,8 @@ AudioAlsaSetupWidget::AudioAlsaSetupWidget( QWidget * _parent ) : m_channels = new LcdSpinBox( 1, this ); m_channels->setModel( m ); - m_channels->setLabel( tr( "CHANNELS" ) ); - m_channels->move( 180, 20 ); + form->addRow(tr("Channels"), m_channels); } diff --git a/src/gui/AudioDeviceSetupWidget.cpp b/src/gui/AudioDeviceSetupWidget.cpp index b78800cecb7..98d03638f3c 100644 --- a/src/gui/AudioDeviceSetupWidget.cpp +++ b/src/gui/AudioDeviceSetupWidget.cpp @@ -28,7 +28,7 @@ namespace lmms::gui { AudioDeviceSetupWidget::AudioDeviceSetupWidget(const QString & caption, QWidget * parent) : - TabWidget(TabWidget::tr("Settings for %1").arg(tr(caption.toUtf8())), parent) + QGroupBox(QGroupBox::tr("Settings for %1").arg(tr(caption.toUtf8())), parent) { } @@ -38,4 +38,4 @@ void AudioDeviceSetupWidget::show() QWidget::show(); } -} // namespace lmms::gui \ No newline at end of file +} // namespace lmms::gui diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt index afed153f928..1e809e9d710 100644 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -94,11 +94,13 @@ SET(LMMS_SRCS gui/widgets/AutomatableButton.cpp gui/widgets/AutomatableSlider.cpp + gui/widgets/BarModelEditor.cpp gui/widgets/CPULoadWidget.cpp gui/widgets/CaptionMenu.cpp gui/widgets/ComboBox.cpp gui/widgets/CustomTextKnob.cpp gui/widgets/Fader.cpp + gui/widgets/FloatModelEditorBase.cpp gui/widgets/Graph.cpp gui/widgets/GroupBox.cpp gui/widgets/Knob.cpp @@ -115,6 +117,7 @@ SET(LMMS_SRCS gui/widgets/SimpleTextFloat.cpp gui/widgets/TabBar.cpp gui/widgets/TabWidget.cpp + gui/widgets/TempoSyncBarModelEditor.cpp gui/widgets/TempoSyncKnob.cpp gui/widgets/TextFloat.cpp gui/widgets/TimeDisplayWidget.cpp diff --git a/src/gui/FileBrowser.cpp b/src/gui/FileBrowser.cpp index 181e67cd749..74d8f755a4a 100644 --- a/src/gui/FileBrowser.cpp +++ b/src/gui/FileBrowser.cpp @@ -328,68 +328,63 @@ void FileBrowser::addItems(const QString & path ) } // try to add all directories from file system alphabetically into the tree - QDir cdir( path ); - QStringList files = cdir.entryList( QDir::Dirs, QDir::Name ); - files.sort(Qt::CaseInsensitive); - for( QStringList::const_iterator it = files.constBegin(); - it != files.constEnd(); ++it ) + QDir cdir(path); + if (!cdir.isReadable()) { return; } + QFileInfoList entries = cdir.entryInfoList( + m_filter.split(' '), + QDir::AllDirs | QDir::Files | QDir::NoDotAndDotDot, + QDir::LocaleAware | QDir::DirsFirst | QDir::Name | QDir::IgnoreCase); + for (const auto& entry : entries) { - QString cur_file = *it; - if( cur_file[0] != '.' ) + QString fileName = entry.fileName(); + if (entry.isDir()) { + // Merge dir's together bool orphan = true; - for( int i = 0; i < m_fileBrowserTreeWidget->topLevelItemCount(); ++i ) + for (int i = 0; i < m_fileBrowserTreeWidget->topLevelItemCount(); ++i) { auto d = dynamic_cast(m_fileBrowserTreeWidget->topLevelItem(i)); - if( d == nullptr || cur_file < d->text( 0 ) ) + if (d == nullptr || fileName < d->text(0)) { // insert before item, we're done - auto dd = new Directory(cur_file, path, m_filter); - m_fileBrowserTreeWidget->insertTopLevelItem( i,dd ); + auto dd = new Directory(fileName, path, m_filter); + m_fileBrowserTreeWidget->insertTopLevelItem(i,dd); dd->update(); // add files to the directory orphan = false; break; } - else if( cur_file == d->text( 0 ) ) + else if (fileName == 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->addDirectory(path); d->update(); orphan = false; break; } } - if( orphan ) + if (orphan) { // it has not yet been added yet, so it's (lexically) // larger than all other dirs => append it at the bottom - auto d = new Directory(cur_file, path, m_filter); + auto d = new Directory(fileName, path, m_filter); d->update(); - m_fileBrowserTreeWidget->addTopLevelItem( d ); + m_fileBrowserTreeWidget->addTopLevelItem(d); } } - } - - files = cdir.entryList( QDir::Files, QDir::Name ); - for( QStringList::const_iterator it = files.constBegin(); - it != files.constEnd(); ++it ) - { - QString cur_file = *it; - if( cur_file[0] != '.' ) + else if (entry.isFile()) { // TODO: don't insert instead of removing, order changed // remove existing file-items - QList existing = m_fileBrowserTreeWidget->findItems( - cur_file, Qt::MatchFixedString ); - if( !existing.empty() ) + QList existing = m_fileBrowserTreeWidget->findItems(fileName, Qt::MatchFixedString); + if (!existing.empty()) { delete existing.front(); } - (void) new FileItem( m_fileBrowserTreeWidget, cur_file, path ); + (void) new FileItem(m_fileBrowserTreeWidget, fileName, path); } } } @@ -1063,8 +1058,11 @@ bool Directory::addItems(const QString& path) treeWidget()->setUpdatesEnabled(false); - QFileInfoList entries = thisDir.entryInfoList(QDir::Dirs | QDir::Files | QDir::NoDotAndDotDot, QDir::LocaleAware | QDir::DirsFirst | QDir::Name); - for (auto& entry : entries) + QFileInfoList entries = thisDir.entryInfoList( + m_filter.split(' '), + QDir::AllDirs | QDir::Files | QDir::NoDotAndDotDot, + QDir::LocaleAware | QDir::DirsFirst | QDir::Name | QDir::IgnoreCase); + for (const auto& entry : entries) { QString fileName = entry.fileName(); if (entry.isDir()) @@ -1073,7 +1071,7 @@ bool Directory::addItems(const QString& path) addChild(dir); m_dirCount++; } - else if (entry.isFile() && thisDir.match(m_filter, fileName.toLower())) + else if (entry.isFile()) { auto fileItem = new FileItem(fileName, path); addChild(fileItem); diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp index 10805fe01c4..f867d86d925 100644 --- a/src/gui/MainWindow.cpp +++ b/src/gui/MainWindow.cpp @@ -112,7 +112,7 @@ MainWindow::MainWindow() : sideBar->appendTab( new FileBrowser( confMgr->userProjectsDir() + "*" + confMgr->factoryProjectsDir(), - "*.mmp *.mmpz *.xml *.mid", + "*.mmp *.mmpz *.xml *.mid *.mpt", tr( "My Projects" ), embed::getIconPixmap( "project_file" ).transformed( QTransform().rotate( 90 ) ), splitter, false, true, diff --git a/src/gui/MidiSetupWidget.cpp b/src/gui/MidiSetupWidget.cpp index 4f620fb0ed1..2385def02dd 100644 --- a/src/gui/MidiSetupWidget.cpp +++ b/src/gui/MidiSetupWidget.cpp @@ -24,7 +24,7 @@ #include "MidiSetupWidget.h" -#include +#include #include #include "ConfigManager.h" @@ -37,7 +37,7 @@ namespace lmms::gui MidiSetupWidget::MidiSetupWidget(const QString & caption, const QString & configSection, const QString & devName, QWidget * parent) : - TabWidget(TabWidget::tr("Settings for %1").arg(tr(caption.toUtf8())), parent), + QGroupBox(QGroupBox::tr("Settings for %1").arg(tr(caption.toUtf8())), parent), m_configSection(configSection), m_device(nullptr) { @@ -45,12 +45,11 @@ MidiSetupWidget::MidiSetupWidget(const QString & caption, const QString & config // to indicate that there is no editable device field if (!devName.isNull()) { + QFormLayout * form = new QFormLayout(this); + m_device = new QLineEdit(devName, this); - m_device->setGeometry(10, 20, 160, 20); - auto dev_lbl = new QLabel(tr("Device"), this); - dev_lbl->setFont(pointSize<7>(dev_lbl->font())); - dev_lbl->setGeometry(10, 40, 160, 10); + form->addRow(tr("Device"), m_device); } } diff --git a/src/gui/MixerView.cpp b/src/gui/MixerView.cpp index dff19ca3eb7..018e72c2b20 100644 --- a/src/gui/MixerView.cpp +++ b/src/gui/MixerView.cpp @@ -464,7 +464,7 @@ bool MixerView::confirmRemoval(int index) QString messageTitleRemoveTrack = tr("Confirm removal"); QString askAgainText = tr("Don't ask again"); auto askAgainCheckBox = new QCheckBox(askAgainText, nullptr); - connect(askAgainCheckBox, &QCheckBox::stateChanged, [this](int state) { + connect(askAgainCheckBox, &QCheckBox::stateChanged, [](int state) { // Invert button state, if it's checked we *shouldn't* ask again ConfigManager::inst()->setValue("ui", "mixerchanneldeletionwarning", state ? "0" : "1"); }); diff --git a/src/gui/editors/AutomationEditor.cpp b/src/gui/editors/AutomationEditor.cpp index f98066bbaa2..282c335dfe1 100644 --- a/src/gui/editors/AutomationEditor.cpp +++ b/src/gui/editors/AutomationEditor.cpp @@ -68,6 +68,7 @@ namespace lmms::gui QPixmap * AutomationEditor::s_toolDraw = nullptr; QPixmap * AutomationEditor::s_toolErase = nullptr; QPixmap * AutomationEditor::s_toolDrawOut = nullptr; +QPixmap * AutomationEditor::s_toolEditTangents = nullptr; QPixmap * AutomationEditor::s_toolMove = nullptr; QPixmap * AutomationEditor::s_toolYFlip = nullptr; QPixmap * AutomationEditor::s_toolXFlip = nullptr; @@ -106,6 +107,7 @@ AutomationEditor::AutomationEditor() : m_graphColor(Qt::SolidPattern), m_nodeInValueColor(0, 0, 0), m_nodeOutValueColor(0, 0, 0), + m_nodeTangentLineColor(0, 0, 0), m_scaleColor(Qt::SolidPattern), m_crossColor(0, 0, 0), m_backgroundShade(0, 0, 0) @@ -180,6 +182,10 @@ AutomationEditor::AutomationEditor() : { s_toolDrawOut = new QPixmap(embed::getIconPixmap("edit_draw_outvalue")); } + if (s_toolEditTangents == nullptr) + { + s_toolEditTangents = new QPixmap(embed::getIconPixmap("edit_tangent")); + } if (s_toolMove == nullptr) { s_toolMove = new QPixmap(embed::getIconPixmap("edit_move")); @@ -470,6 +476,17 @@ void AutomationEditor::mousePressEvent( QMouseEvent* mouseEvent ) Engine::getSong()->setModified(); } }; + auto resetTangent = [this](timeMap::iterator node) + { + if (node != m_clip->getTimeMap().end()) + { + // Unlock the tangents from that node + node.value().setLockedTangents(false); + // Recalculate the tangents + m_clip->generateTangents(node, 1); + Engine::getSong()->setModified(); + } + }; // If we clicked inside the AutomationEditor viewport (where the nodes are represented) if (mouseEvent->y() > TOP_MARGIN && mouseEvent->x() >= VALUES_WIDTH) @@ -654,6 +671,47 @@ void AutomationEditor::mousePressEvent( QMouseEvent* mouseEvent ) } break; } + case EditMode::EditTangents: + { + if (!m_clip->canEditTangents()) + { + update(); + return; + } + + m_clip->addJournalCheckPoint(); + + // Gets the closest node to the mouse click + timeMap::iterator node = getClosestNode(mouseEvent->x()); + + // Starts dragging a tangent + if (m_mouseDownLeft && node != tm.end()) + { + // Lock the tangents from that node, so it can only be + // manually edited + node.value().setLockedTangents(true); + + m_draggedTangentTick = POS(node); + + // Are we dragging the out or in tangent? + m_draggedOutTangent = posTicks >= m_draggedTangentTick; + + m_action = Action::MoveTangent; + } + // Resets node's tangent + else if (m_mouseDownRight) + { + // Resets tangent from node + resetTangent(node); + + // Update the last clicked position so we reset all tangents from + // that point up to the point we release the mouse button + m_drawLastTick = posTicks; + + m_action = Action::ResetTangents; + } + break; + } } update(); @@ -862,6 +920,51 @@ void AutomationEditor::mouseMoveEvent(QMouseEvent * mouseEvent ) } break; } + case EditMode::EditTangents: + { + // If we moved the mouse past the beginning correct the position in ticks + posTicks = std::max(posTicks, 0); + + if (m_mouseDownLeft && m_action == Action::MoveTangent) + { + timeMap& tm = m_clip->getTimeMap(); + auto it = tm.find(m_draggedTangentTick); + + // Safety check + if (it == tm.end()) + { + update(); + return; + } + + // Calculate new tangent + float y = m_draggedOutTangent + ? yCoordOfLevel(OUTVAL(it)) + : yCoordOfLevel(INVAL(it)); + float dy = m_draggedOutTangent + ? y - mouseEvent->y() + : mouseEvent->y() - y; + float dx = std::abs(posTicks - POS(it)); + float newTangent = dy / std::max(dx, 1.0f); + + if (m_draggedOutTangent) + { + it.value().setOutTangent(newTangent); + } + else + { + it.value().setInTangent(newTangent); + } + } + else if (m_mouseDownRight && m_action == Action::ResetTangents) + { + // Resets all tangents from the last clicked tick up to the current position tick + m_clip->resetTangents(m_drawLastTick, posTicks); + + Engine::getSong()->setModified(); + } + break; + } } } else // If the mouse Y position is above the AutomationEditor viewport @@ -937,6 +1040,43 @@ inline void AutomationEditor::drawAutomationPoint(QPainter & p, timeMap::iterato +inline void AutomationEditor::drawAutomationTangents(QPainter& p, timeMap::iterator it) +{ + int x = xCoordOfTick(POS(it)); + int y, tx, ty; + + // The tangent value correlates the variation in the node value related to the increase + // in ticks. So to have a proportionate drawing of the tangent line, we need to find the + // relation between the number of pixels per tick and the number of pixels per value level. + float viewportHeight = (height() - SCROLLBAR_SIZE - 1) - TOP_MARGIN; + float pixelsPerTick = m_ppb / TimePos::ticksPerBar(); + // std::abs just in case the topLevel is smaller than the bottomLevel for some reason + float pixelsPerLevel = std::abs(viewportHeight / (m_topLevel - m_bottomLevel)); + float proportion = pixelsPerLevel / pixelsPerTick; + + p.setPen(QPen(m_nodeTangentLineColor)); + p.setBrush(QBrush(m_nodeTangentLineColor)); + + y = yCoordOfLevel(INVAL(it)); + tx = x - 20; + ty = y + 20 * INTAN(it) * proportion; + p.drawLine(x, y, tx, ty); + p.setBrush(QBrush(m_nodeTangentLineColor.darker(200))); + p.drawEllipse(tx - 3, ty - 3, 6, 6); + + p.setBrush(QBrush(m_nodeTangentLineColor)); + + y = yCoordOfLevel(OUTVAL(it)); + tx = x + 20; + ty = y - 20 * OUTTAN(it) * proportion; + p.drawLine(x, y, tx, ty); + p.setBrush(QBrush(m_nodeTangentLineColor.darker(200))); + p.drawEllipse(tx - 3, ty - 3, 6, 6); +} + + + + void AutomationEditor::paintEvent(QPaintEvent * pe ) { QStyleOption opt; @@ -1197,6 +1337,11 @@ void AutomationEditor::paintEvent(QPaintEvent * pe ) // Draw circle drawAutomationPoint(p, it); + // Draw tangents if necessary (only for manually edited tangents) + if (m_clip->canEditTangents() && LOCKEDTAN(it)) + { + drawAutomationTangents(p, it); + } ++it; } @@ -1213,6 +1358,11 @@ void AutomationEditor::paintEvent(QPaintEvent * pe ) } // Draw circle(the last one) drawAutomationPoint(p, it); + // Draw tangents if necessary (only for manually edited tangents) + if (m_clip->canEditTangents() && LOCKEDTAN(it)) + { + drawAutomationTangents(p, it); + } } } else @@ -1267,6 +1417,11 @@ void AutomationEditor::paintEvent(QPaintEvent * pe ) else { cursor = s_toolDrawOut; } break; } + case EditMode::EditTangents: + { + cursor = m_action == Action::MoveTangent ? s_toolMove : s_toolEditTangents; + break; + } } QPoint mousePosition = mapFromGlobal( QCursor::pos() ); if (cursor != nullptr && mousePosition.y() > TOP_MARGIN + SCROLLBAR_SIZE) @@ -1819,6 +1974,49 @@ AutomationEditor::timeMap::iterator AutomationEditor::getNodeAt(int x, int y, bo return tm.end(); } +AutomationEditor::timeMap::iterator AutomationEditor::getClosestNode(int x) +{ + // Remove the VALUES_WIDTH from the x position, so we have the actual viewport x + x -= VALUES_WIDTH; + // Convert the x position to the position in ticks + int posTicks = (x * TimePos::ticksPerBar() / m_ppb) + m_currentPosition; + + // Get our pattern timeMap and create a iterator so we can check the nodes + timeMap& tm = m_clip->getTimeMap(); + + if (tm.isEmpty()) { return tm.end(); } + + // Get the node with an equal or higher position + auto it = tm.lowerBound(posTicks); + + // If there are no nodes equal or higher than the position return + // the one before it + if (it == tm.end()) + { + --it; + return it; + } + // If the node returned is the first, return it + else if (it == tm.begin()) + { + return it; + } + // Else return the closest node + else + { + // Distance from node to the right + int distanceRight = std::abs(POS(it) - posTicks); + // Distance from node to the left + int distanceLeft = std::abs(POS(--it) - posTicks); + + if (distanceLeft >= distanceRight) + { + ++it; + } + return it; + } +} + @@ -1839,24 +2037,29 @@ AutomationEditorWindow::AutomationEditorWindow() : DropToolBar *editActionsToolBar = addDropToolBarToTop(tr("Edit actions")); auto editModeGroup = new ActionGroup(this); - QAction* drawAction = editModeGroup->addAction(embed::getIconPixmap("edit_draw"), tr("Draw mode (Shift+D)")); - drawAction->setShortcut(Qt::SHIFT | Qt::Key_D); - drawAction->setChecked(true); + m_drawAction = editModeGroup->addAction(embed::getIconPixmap("edit_draw"), tr("Draw mode (Shift+D)")); + m_drawAction->setShortcut(Qt::SHIFT | Qt::Key_D); + m_drawAction->setChecked(true); + + m_eraseAction = editModeGroup->addAction(embed::getIconPixmap("edit_erase"), tr("Erase mode (Shift+E)")); + m_eraseAction->setShortcut(Qt::SHIFT | Qt::Key_E); - QAction* eraseAction = editModeGroup->addAction(embed::getIconPixmap("edit_erase"), tr("Erase mode (Shift+E)")); - eraseAction->setShortcut(Qt::SHIFT | Qt::Key_E); + m_drawOutAction = editModeGroup->addAction(embed::getIconPixmap("edit_draw_outvalue"), tr("Draw outValues mode (Shift+C)")); + m_drawOutAction->setShortcut(Qt::SHIFT | Qt::Key_C); - QAction* drawOutAction = editModeGroup->addAction(embed::getIconPixmap("edit_draw_outvalue"), tr("Draw outValues mode (Shift+C)")); - drawOutAction->setShortcut(Qt::SHIFT | Qt::Key_C); + m_editTanAction = editModeGroup->addAction(embed::getIconPixmap("edit_tangent"), tr("Edit tangents mode (Shift+T)")); + m_editTanAction->setShortcut(Qt::SHIFT | Qt::Key_T); + m_editTanAction->setEnabled(false); m_flipYAction = new QAction(embed::getIconPixmap("flip_y"), tr("Flip vertically"), this); m_flipXAction = new QAction(embed::getIconPixmap("flip_x"), tr("Flip horizontally"), this); connect(editModeGroup, SIGNAL(triggered(int)), m_editor, SLOT(setEditMode(int))); - editActionsToolBar->addAction(drawAction); - editActionsToolBar->addAction(eraseAction); - editActionsToolBar->addAction(drawOutAction); + editActionsToolBar->addAction(m_drawAction); + editActionsToolBar->addAction(m_eraseAction); + editActionsToolBar->addAction(m_drawOutAction); + editActionsToolBar->addAction(m_editTanAction); editActionsToolBar->addAction(m_flipXAction); editActionsToolBar->addAction(m_flipYAction); @@ -1874,7 +2077,7 @@ AutomationEditorWindow::AutomationEditorWindow() : m_cubicHermiteAction = progression_type_group->addAction( embed::getIconPixmap("progression_cubic_hermite"), tr( "Cubic Hermite progression")); - connect(progression_type_group, SIGNAL(triggered(int)), m_editor, SLOT(setProgressionType(int))); + connect(progression_type_group, SIGNAL(triggered(int)), this, SLOT(setProgressionType(int))); // setup tension-stuff m_tensionKnob = new Knob( KnobType::Small17, this, "Tension" ); @@ -2014,6 +2217,7 @@ void AutomationEditorWindow::setCurrentClip(AutomationClip* clip) connect(m_flipYAction, SIGNAL(triggered()), clip, SLOT(flipY())); } + updateEditTanButton(); emit currentClipChanged(); } @@ -2102,5 +2306,17 @@ void AutomationEditorWindow::updateWindowTitle() setWindowTitle( tr( "Automation Editor - %1" ).arg( m_editor->m_clip->name() ) ); } +void AutomationEditorWindow::setProgressionType(int progType) +{ + m_editor->setProgressionType(progType); + updateEditTanButton(); +} + +void AutomationEditorWindow::updateEditTanButton() +{ + auto progType = currentClip()->progressionType(); + m_editTanAction->setEnabled(AutomationClip::supportsTangentEditing(progType)); + if (!m_editTanAction->isEnabled() && m_editTanAction->isChecked()) { m_drawAction->trigger(); } +} } // namespace lmms::gui diff --git a/src/gui/editors/PianoRoll.cpp b/src/gui/editors/PianoRoll.cpp index cef2205d266..6bf1b5daf8f 100644 --- a/src/gui/editors/PianoRoll.cpp +++ b/src/gui/editors/PianoRoll.cpp @@ -2517,7 +2517,7 @@ void PianoRoll::mouseMoveEvent( QMouseEvent * me ) // We iterate from last note in MIDI clip to the first, // chronologically auto it = notes.rbegin(); - for( int i = 0; i < notes.size(); ++i ) + while (it != notes.rend()) { Note* n = *it; diff --git a/src/gui/modals/SetupDialog.cpp b/src/gui/modals/SetupDialog.cpp index 0266285a7a4..209422563bc 100644 --- a/src/gui/modals/SetupDialog.cpp +++ b/src/gui/modals/SetupDialog.cpp @@ -23,14 +23,15 @@ */ +#include #include +#include #include #include #include #include #include -#include "AudioDeviceSetupWidget.h" #include "AudioEngine.h" #include "debug.h" #include "embed.h" @@ -79,13 +80,12 @@ inline void labelWidget(QWidget * w, const QString & txt) auto title = new QLabel(txt, w); QFont f = title->font(); f.setBold(true); - title->setFont(pointSize<12>(f)); + title->setFont(f); + QBoxLayout * boxLayout = dynamic_cast(w->layout()); + assert(boxLayout); - assert(dynamic_cast(w->layout()) != nullptr); - - dynamic_cast(w->layout())->addSpacing(5); - dynamic_cast(w->layout())->addWidget(title); + boxLayout->addWidget(title); } @@ -162,15 +162,10 @@ SetupDialog::SetupDialog(ConfigTab tab_to_open) : // TODO: Equivalent to the new setWindowFlag(Qt::WindowContextHelpButtonHint, false) setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); setModal(true); - setFixedSize(454, 400); Engine::projectJournal()->setJournalling(false); - // Constants for positioning LED check boxes. - const int XDelta = 10; - const int YDelta = 18; - // Main widget. auto main_w = new QWidget(this); @@ -191,7 +186,8 @@ SetupDialog::SetupDialog(ConfigTab tab_to_open) : // Settings widget. auto settings_w = new QWidget(main_w); - settings_w->setFixedSize(360, 360); + + QVBoxLayout * settingsLayout = new QVBoxLayout(settings_w); // General widget. auto general_w = new QWidget(settings_w); @@ -211,77 +207,79 @@ SetupDialog::SetupDialog(ConfigTab tab_to_open) : // Path selectors layout. auto generalControlsLayout = new QVBoxLayout; generalControlsLayout->setSpacing(10); + generalControlsLayout->setContentsMargins(0, 0, 0, 0); - auto addLedCheckBox = [&XDelta, &YDelta, this](const QString& ledText, TabWidget* tw, int& counter, - bool initialState, const char* toggledSlot, bool showRestartWarning) { - auto checkBox = new LedCheckBox(ledText, tw); - counter++; - checkBox->move(XDelta, YDelta * counter); + auto addCheckBox = [&](const QString& ledText, QWidget* parent, QBoxLayout * layout, + bool initialState, const char* toggledSlot, bool showRestartWarning) -> QCheckBox * { + auto checkBox = new QCheckBox(ledText, parent); checkBox->setChecked(initialState); connect(checkBox, SIGNAL(toggled(bool)), this, toggledSlot); + if (showRestartWarning) { connect(checkBox, SIGNAL(toggled(bool)), this, SLOT(showRestartWarning())); } - }; - int counter = 0; + if (layout) + { + layout->addWidget(checkBox); + } + + return checkBox; + }; // GUI tab. - auto gui_tw = new TabWidget(tr("Graphical user interface (GUI)"), generalControls); + QGroupBox * guiGroupBox = new QGroupBox(tr("Graphical user interface (GUI)"), generalControls); + QVBoxLayout * guiGroupLayout = new QVBoxLayout(guiGroupBox); - addLedCheckBox(tr("Display volume as dBFS "), gui_tw, counter, + addCheckBox(tr("Display volume as dBFS "), guiGroupBox, guiGroupLayout, m_displaydBFS, SLOT(toggleDisplaydBFS(bool)), true); - addLedCheckBox(tr("Enable tooltips"), gui_tw, counter, + addCheckBox(tr("Enable tooltips"), guiGroupBox, guiGroupLayout, m_tooltips, SLOT(toggleTooltips(bool)), true); - addLedCheckBox(tr("Enable master oscilloscope by default"), gui_tw, counter, + addCheckBox(tr("Enable master oscilloscope by default"), guiGroupBox, guiGroupLayout, m_displayWaveform, SLOT(toggleDisplayWaveform(bool)), true); - addLedCheckBox(tr("Enable all note labels in piano roll"), gui_tw, counter, + addCheckBox(tr("Enable all note labels in piano roll"), guiGroupBox, guiGroupLayout, m_printNoteLabels, SLOT(toggleNoteLabels(bool)), false); - addLedCheckBox(tr("Enable compact track buttons"), gui_tw, counter, + addCheckBox(tr("Enable compact track buttons"), guiGroupBox, guiGroupLayout, m_compactTrackButtons, SLOT(toggleCompactTrackButtons(bool)), true); - addLedCheckBox(tr("Enable one instrument-track-window mode"), gui_tw, counter, + addCheckBox(tr("Enable one instrument-track-window mode"), guiGroupBox, guiGroupLayout, m_oneInstrumentTrackWindow, SLOT(toggleOneInstrumentTrackWindow(bool)), true); - addLedCheckBox(tr("Show sidebar on the right-hand side"), gui_tw, counter, + addCheckBox(tr("Show sidebar on the right-hand side"), guiGroupBox, guiGroupLayout, m_sideBarOnRight, SLOT(toggleSideBarOnRight(bool)), true); - addLedCheckBox(tr("Let sample previews continue when mouse is released"), gui_tw, counter, + addCheckBox(tr("Let sample previews continue when mouse is released"), guiGroupBox, guiGroupLayout, m_letPreviewsFinish, SLOT(toggleLetPreviewsFinish(bool)), false); - addLedCheckBox(tr("Mute automation tracks during solo"), gui_tw, counter, + addCheckBox(tr("Mute automation tracks during solo"), guiGroupBox, guiGroupLayout, m_soloLegacyBehavior, SLOT(toggleSoloLegacyBehavior(bool)), false); - addLedCheckBox(tr("Show warning when deleting tracks"), gui_tw, counter, + addCheckBox(tr("Show warning when deleting tracks"), guiGroupBox, guiGroupLayout, m_trackDeletionWarning, SLOT(toggleTrackDeletionWarning(bool)), false); - addLedCheckBox(tr("Show warning when deleting a mixer channel that is in use"), gui_tw, counter, + addCheckBox(tr("Show warning when deleting a mixer channel that is in use"), guiGroupBox, guiGroupLayout, m_mixerChannelDeletionWarning, SLOT(toggleMixerChannelDeletionWarning(bool)), false); - gui_tw->setFixedHeight(YDelta + YDelta * counter); + generalControlsLayout->addWidget(guiGroupBox); - generalControlsLayout->addWidget(gui_tw); generalControlsLayout->addSpacing(10); - - counter = 0; - // Projects tab. - auto projects_tw = new TabWidget(tr("Projects"), generalControls); + QGroupBox * projectsGroupBox = new QGroupBox(tr("Projects"), generalControls); + QVBoxLayout * projectsGroupLayout = new QVBoxLayout(projectsGroupBox); - addLedCheckBox(tr("Compress project files by default"), projects_tw, counter, + addCheckBox(tr("Compress project files by default"), projectsGroupBox, projectsGroupLayout, m_MMPZ, SLOT(toggleMMPZ(bool)), true); - addLedCheckBox(tr("Create a backup file when saving a project"), projects_tw, counter, + addCheckBox(tr("Create a backup file when saving a project"), projectsGroupBox, projectsGroupLayout, m_disableBackup, SLOT(toggleDisableBackup(bool)), false); - addLedCheckBox(tr("Reopen last project on startup"), projects_tw, counter, + addCheckBox(tr("Reopen last project on startup"), projectsGroupBox, projectsGroupLayout, m_openLastProject, SLOT(toggleOpenLastProject(bool)), false); - projects_tw->setFixedHeight(YDelta + YDelta * counter); + generalControlsLayout->addWidget(projectsGroupBox); - generalControlsLayout->addWidget(projects_tw); generalControlsLayout->addSpacing(10); - // Language tab. - auto lang_tw = new TabWidget(tr("Language"), generalControls); - lang_tw->setFixedHeight(48); - auto changeLang = new QComboBox(lang_tw); - changeLang->move(XDelta, 20); + QGroupBox * languageGroupBox = new QGroupBox(tr("Language"), generalControls); + QVBoxLayout * languageGroupLayout = new QVBoxLayout(languageGroupBox); + + auto changeLang = new QComboBox(languageGroupBox); + languageGroupLayout->addWidget(changeLang); QDir dir(ConfigManager::inst()->localeDir()); QStringList fileNames = dir.entryList(QStringList("*.qm")); @@ -333,7 +331,7 @@ SetupDialog::SetupDialog(ConfigTab tab_to_open) : connect(changeLang, SIGNAL(currentIndexChanged(int)), this, SLOT(showRestartWarning())); - generalControlsLayout->addWidget(lang_tw); + generalControlsLayout->addWidget(languageGroupBox); generalControlsLayout->addSpacing(10); // General layout ordering. @@ -341,9 +339,7 @@ SetupDialog::SetupDialog(ConfigTab tab_to_open) : generalControls->setLayout(generalControlsLayout); generalScroll->setWidget(generalControls); generalScroll->setWidgetResizable(true); - general_layout->addWidget(generalScroll); - general_layout->addStretch(); - + general_layout->addWidget(generalScroll, 1); @@ -357,71 +353,63 @@ SetupDialog::SetupDialog(ConfigTab tab_to_open) : // Autosave tab. - auto auto_save_tw = new TabWidget(tr("Autosave"), performance_w); - auto_save_tw->setFixedHeight(106); + QGroupBox * autoSaveBox = new QGroupBox(tr("Autosave"), performance_w); + QVBoxLayout * autoSaveLayout = new QVBoxLayout(autoSaveBox); + QHBoxLayout * autoSaveSubLayout = new QHBoxLayout(); - m_saveIntervalSlider = new QSlider(Qt::Horizontal, auto_save_tw); + m_saveIntervalSlider = new QSlider(Qt::Horizontal, autoSaveBox); m_saveIntervalSlider->setValue(m_saveInterval); m_saveIntervalSlider->setRange(1, 20); m_saveIntervalSlider->setTickInterval(1); m_saveIntervalSlider->setPageStep(1); - m_saveIntervalSlider->setGeometry(10, 18, 340, 18); m_saveIntervalSlider->setTickPosition(QSlider::TicksBelow); connect(m_saveIntervalSlider, SIGNAL(valueChanged(int)), this, SLOT(setAutoSaveInterval(int))); - m_saveIntervalLbl = new QLabel(auto_save_tw); - m_saveIntervalLbl->setGeometry(10, 40, 200, 24); + auto autoSaveResetBtn = new QPushButton(embed::getIconPixmap("reload"), "", autoSaveBox); + autoSaveResetBtn->setFixedSize(32, 32); + connect(autoSaveResetBtn, SIGNAL(clicked()), + this, SLOT(resetAutoSave())); + + autoSaveSubLayout->addWidget(m_saveIntervalSlider); + autoSaveSubLayout->addWidget(autoSaveResetBtn); + + autoSaveLayout->addLayout(autoSaveSubLayout); + + m_saveIntervalLbl = new QLabel(autoSaveBox); setAutoSaveInterval(m_saveIntervalSlider->value()); + autoSaveLayout->addWidget(m_saveIntervalLbl); - m_autoSave = new LedCheckBox( - tr("Enable autosave"), auto_save_tw); - m_autoSave->move(10, 70); - m_autoSave->setChecked(m_enableAutoSave); - connect(m_autoSave, SIGNAL(toggled(bool)), - this, SLOT(toggleAutoSave(bool))); - - m_runningAutoSave = new LedCheckBox( - tr("Allow autosave while playing"), auto_save_tw); - m_runningAutoSave->move(20, 88); - m_runningAutoSave->setChecked(m_enableRunningAutoSave); - connect(m_runningAutoSave, SIGNAL(toggled(bool)), - this, SLOT(toggleRunningAutoSave(bool))); - - auto autoSaveResetBtn = new QPushButton(embed::getIconPixmap("reload"), "", auto_save_tw); - autoSaveResetBtn->setGeometry(320, 70, 28, 28); - connect(autoSaveResetBtn, SIGNAL(clicked()), - this, SLOT(resetAutoSave())); + m_autoSave = addCheckBox(tr("Enable autosave"), autoSaveBox, autoSaveLayout, + m_enableAutoSave, SLOT(toggleAutoSave(bool)), false); + + m_runningAutoSave = addCheckBox(tr("Allow autosave while playing"), autoSaveBox, autoSaveLayout, + m_enableRunningAutoSave, SLOT(toggleRunningAutoSave(bool)), false); m_saveIntervalSlider->setEnabled(m_enableAutoSave); m_runningAutoSave->setVisible(m_enableAutoSave); - counter = 0; - // UI effect vs. performance tab. - auto ui_fx_tw = new TabWidget(tr("User interface (UI) effects vs. performance"), performance_w); + QGroupBox * uiFxBox = new QGroupBox(tr("User interface (UI) effects vs. performance"), performance_w); + QVBoxLayout * uiFxLayout = new QVBoxLayout(uiFxBox); - addLedCheckBox(tr("Smooth scroll in song editor"), ui_fx_tw, counter, + addCheckBox(tr("Smooth scroll in song editor"), uiFxBox, uiFxLayout, m_smoothScroll, SLOT(toggleSmoothScroll(bool)), false); - addLedCheckBox(tr("Display playback cursor in AudioFileProcessor"), ui_fx_tw, counter, + addCheckBox(tr("Display playback cursor in AudioFileProcessor"), uiFxBox, uiFxLayout, m_animateAFP, SLOT(toggleAnimateAFP(bool)), false); - ui_fx_tw->setFixedHeight(YDelta + YDelta * counter); - - counter = 0; + // Plugins group + QGroupBox * pluginsBox = new QGroupBox(tr("Plugins"), performance_w); + QVBoxLayout * pluginsLayout = new QVBoxLayout(pluginsBox); - // Plugins tab. - auto plugins_tw = new TabWidget(tr("Plugins"), performance_w); - - m_vstEmbedLbl = new QLabel(plugins_tw); - m_vstEmbedLbl->move(XDelta, YDelta * ++counter); + m_vstEmbedLbl = new QLabel(pluginsBox); m_vstEmbedLbl->setText(tr("VST plugins embedding:")); + pluginsLayout->addWidget(m_vstEmbedLbl); - m_vstEmbedComboBox = new QComboBox(plugins_tw); - m_vstEmbedComboBox->move(XDelta, YDelta * ++counter); + m_vstEmbedComboBox = new QComboBox(pluginsBox); QStringList embedMethods = ConfigManager::availableVstEmbedMethods(); m_vstEmbedComboBox->addItem(tr("No embedding"), "none"); @@ -440,27 +428,19 @@ SetupDialog::SetupDialog(ConfigTab tab_to_open) : m_vstEmbedComboBox->setCurrentIndex(m_vstEmbedComboBox->findData(m_vstEmbedMethod)); connect(m_vstEmbedComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(vstEmbedMethodChanged())); + pluginsLayout->addWidget(m_vstEmbedComboBox); - counter += 2; - - m_vstAlwaysOnTopCheckBox = new LedCheckBox( - tr("Keep plugin windows on top when not embedded"), plugins_tw); - m_vstAlwaysOnTopCheckBox->move(20, 66); - m_vstAlwaysOnTopCheckBox->setChecked(m_vstAlwaysOnTop); - m_vstAlwaysOnTopCheckBox->setVisible(m_vstEmbedMethod == "none"); - connect(m_vstAlwaysOnTopCheckBox, SIGNAL(toggled(bool)), - this, SLOT(toggleVSTAlwaysOnTop(bool))); + m_vstAlwaysOnTopCheckBox = addCheckBox(tr("Keep plugin windows on top when not embedded"), pluginsBox, pluginsLayout, + m_vstAlwaysOnTop, SLOT(toggleVSTAlwaysOnTop(bool)), false); - addLedCheckBox(tr("Keep effects running even without input"), plugins_tw, counter, + addCheckBox(tr("Keep effects running even without input"), pluginsBox, pluginsLayout, m_disableAutoQuit, SLOT(toggleDisableAutoQuit(bool)), false); - plugins_tw->setFixedHeight(YDelta + YDelta * counter); - // Performance layout ordering. - performance_layout->addWidget(auto_save_tw); - performance_layout->addWidget(ui_fx_tw); - performance_layout->addWidget(plugins_tw); + performance_layout->addWidget(autoSaveBox); + performance_layout->addWidget(uiFxBox); + performance_layout->addWidget(pluginsBox); performance_layout->addStretch(); @@ -473,17 +453,15 @@ SetupDialog::SetupDialog(ConfigTab tab_to_open) : labelWidget(audio_w, tr("Audio")); - // Audio interface tab. - auto audioiface_tw = new TabWidget(tr("Audio interface"), audio_w); - audioiface_tw->setFixedHeight(56); - - m_audioInterfaces = new QComboBox(audioiface_tw); - m_audioInterfaces->setGeometry(10, 20, 240, 28); + // Audio interface group + QGroupBox * audioInterfaceBox = new QGroupBox(tr("Audio interface"), audio_w); + QVBoxLayout * audioInterfaceLayout = new QVBoxLayout(audioInterfaceBox); + m_audioInterfaces = new QComboBox(audioInterfaceBox); + audioInterfaceLayout->addWidget(m_audioInterfaces); // Ifaces-settings-widget. auto as_w = new QWidget(audio_w); - as_w->setFixedHeight(60); auto as_w_layout = new QHBoxLayout(as_w); as_w_layout->setSpacing(0); @@ -563,61 +541,58 @@ SetupDialog::SetupDialog(ConfigTab tab_to_open) : this, SLOT(audioInterfaceChanged(const QString&))); // Advanced setting, hidden for now - if(false) - { - auto useNaNHandler = new LedCheckBox(tr("Use built-in NaN handler"), audio_w); - useNaNHandler->setChecked(m_NaNHandler); - } - - // HQ mode LED. - auto hqaudio = new LedCheckBox(tr("HQ mode for output audio device"), audio_w); - hqaudio->move(10, 0); - hqaudio->setChecked(m_hqAudioDev); - connect(hqaudio, SIGNAL(toggled(bool)), - this, SLOT(toggleHQAudioDev(bool))); + // // TODO Handle or remove. + // auto useNaNHandler = new LedCheckBox(tr("Use built-in NaN handler"), audio_w); + // audio_layout->addWidget(useNaNHandler); + // useNaNHandler->setChecked(m_NaNHandler); + // HQ mode checkbox + auto hqaudio = addCheckBox(tr("HQ mode for output audio device"), audioInterfaceBox, nullptr, + m_hqAudioDev, SLOT(toggleHQAudioDev(bool)), false); - // Buffer size tab. - auto bufferSize_tw = new TabWidget(tr("Buffer size"), audio_w); - auto bufferSize_layout = new QVBoxLayout(bufferSize_tw); - bufferSize_layout->setSpacing(10); - bufferSize_layout->setContentsMargins(10, 18, 10, 10); + // Buffer size group + QGroupBox * bufferSizeBox = new QGroupBox(tr("Buffer size"), audio_w); + QVBoxLayout * bufferSizeLayout = new QVBoxLayout(bufferSizeBox); + QHBoxLayout * bufferSizeSubLayout = new QHBoxLayout(); - m_bufferSizeSlider = new QSlider(Qt::Horizontal, bufferSize_tw); + m_bufferSizeSlider = new QSlider(Qt::Horizontal, bufferSizeBox); m_bufferSizeSlider->setRange(1, 128); m_bufferSizeSlider->setTickInterval(8); m_bufferSizeSlider->setPageStep(8); m_bufferSizeSlider->setValue(m_bufferSize / BUFFERSIZE_RESOLUTION); m_bufferSizeSlider->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())); - setBufferSize(m_bufferSizeSlider->value()); + bufferSizeSubLayout->addWidget(m_bufferSizeSlider, 1); - auto bufferSize_reset_btn = new QPushButton(embed::getIconPixmap("reload"), "", bufferSize_tw); + auto bufferSize_reset_btn = new QPushButton(embed::getIconPixmap("reload"), "", bufferSizeBox); + bufferSize_reset_btn->setFixedSize(32, 32); connect(bufferSize_reset_btn, SIGNAL(clicked()), - this, SLOT(resetBufferSize())); + this, SLOT(resetBufferSize())); bufferSize_reset_btn->setToolTip( - tr("Reset to default value")); + tr("Reset to default value")); + + bufferSizeSubLayout->addWidget(bufferSize_reset_btn); + bufferSizeLayout->addLayout(bufferSizeSubLayout); + + m_bufferSizeLbl = new QLabel(bufferSizeBox); + bufferSizeLayout->addWidget(m_bufferSizeLbl); - bufferSize_layout->addWidget(m_bufferSizeSlider); - bufferSize_layout->addWidget(m_bufferSizeLbl); - bufferSize_layout->addWidget(m_bufferSizeWarnLbl); - bufferSize_layout->addWidget(bufferSize_reset_btn); + m_bufferSizeWarnLbl = new QLabel(bufferSizeBox); + m_bufferSizeWarnLbl->setWordWrap(true); + bufferSizeLayout->addWidget(m_bufferSizeWarnLbl); + + setBufferSize(m_bufferSizeSlider->value()); // Audio layout ordering. - audio_layout->addWidget(audioiface_tw); + audio_layout->addWidget(audioInterfaceBox); audio_layout->addWidget(as_w); audio_layout->addWidget(hqaudio); - audio_layout->addWidget(bufferSize_tw); + audio_layout->addWidget(bufferSizeBox); audio_layout->addStretch(); @@ -627,19 +602,17 @@ SetupDialog::SetupDialog(ConfigTab tab_to_open) : auto midi_layout = new QVBoxLayout(midi_w); midi_layout->setSpacing(10); midi_layout->setContentsMargins(0, 0, 0, 0); - labelWidget(midi_w, - tr("MIDI")); + labelWidget(midi_w, tr("MIDI")); - // MIDI interface tab. - auto midiiface_tw = new TabWidget(tr("MIDI interface"), midi_w); - midiiface_tw->setFixedHeight(56); + // MIDI interface group + QGroupBox * midiInterfaceBox = new QGroupBox(tr("MIDI interface"), midi_w); + QVBoxLayout * midiInterfaceLayout = new QVBoxLayout(midiInterfaceBox); - m_midiInterfaces = new QComboBox(midiiface_tw); - m_midiInterfaces->setGeometry(10, 20, 240, 28); + m_midiInterfaces = new QComboBox(midiInterfaceBox); + midiInterfaceLayout->addWidget(m_midiInterfaces); // Ifaces-settings-widget. auto ms_w = new QWidget(midi_w); - ms_w->setFixedHeight(60); auto ms_w_layout = new QHBoxLayout(ms_w); ms_w_layout->setSpacing(0); @@ -709,12 +682,12 @@ SetupDialog::SetupDialog(ConfigTab tab_to_open) : this, SLOT(midiInterfaceChanged(const QString&))); - // MIDI autoassign tab. - auto midiAutoAssign_tw = new TabWidget(tr("Automatically assign MIDI controller to selected track"), midi_w); - midiAutoAssign_tw->setFixedHeight(56); + // MIDI autoassign group + QGroupBox * midiAutoAssignBox = new QGroupBox(tr("Automatically assign MIDI controller to selected track"), midi_w); + QVBoxLayout * midiAutoAssignLayout = new QVBoxLayout(midiAutoAssignBox); - m_assignableMidiDevices = new QComboBox(midiAutoAssign_tw); - m_assignableMidiDevices->setGeometry(10, 20, 240, 28); + m_assignableMidiDevices = new QComboBox(midiAutoAssignBox); + midiAutoAssignLayout->addWidget(m_assignableMidiDevices); m_assignableMidiDevices->addItem("none"); if ( !Engine::audioEngine()->midiClient()->isRaw() ) { @@ -731,9 +704,9 @@ SetupDialog::SetupDialog(ConfigTab tab_to_open) : } // MIDI layout ordering. - midi_layout->addWidget(midiiface_tw); + midi_layout->addWidget(midiInterfaceBox); midi_layout->addWidget(ms_w); - midi_layout->addWidget(midiAutoAssign_tw); + midi_layout->addWidget(midiAutoAssignBox); midi_layout->addStretch(); @@ -756,29 +729,29 @@ SetupDialog::SetupDialog(ConfigTab tab_to_open) : // Path selectors widget. auto pathSelectors = new QWidget(paths_w); - const int txtLength = 284; - const int btnStart = 300; - // Path selectors layout. auto pathSelectorsLayout = new QVBoxLayout; pathSelectorsLayout->setSpacing(10); + pathSelectorsLayout->setContentsMargins(0, 0, 0, 0); auto addPathEntry = [&](const QString& caption, const QString& content, const char* setSlot, const char* openSlot, QLineEdit*& lineEdit, const char* pixmap = "project_open") { - auto newTw = new TabWidget(caption, pathSelectors); - newTw->setFixedHeight(48); + auto pathEntryGroupBox = new QGroupBox(caption, pathSelectors); + QHBoxLayout * pathEntryLayout = new QHBoxLayout(pathEntryGroupBox); - lineEdit = new QLineEdit(content, newTw); - lineEdit->setGeometry(10, 20, txtLength, 16); + lineEdit = new QLineEdit(content, pathEntryGroupBox); connect(lineEdit, SIGNAL(textChanged(const QString&)), this, setSlot); - auto selectBtn = new QPushButton(embed::getIconPixmap(pixmap, 16, 16), "", newTw); + pathEntryLayout->addWidget(lineEdit, 1); + + auto selectBtn = new QPushButton(embed::getIconPixmap(pixmap, 16, 16), "", pathEntryGroupBox); selectBtn->setFixedSize(24, 24); - selectBtn->move(btnStart, 16); connect(selectBtn, SIGNAL(clicked()), this, openSlot); - pathSelectorsLayout->addWidget(newTw); + pathEntryLayout->addWidget(selectBtn, 0); + + pathSelectorsLayout->addWidget(pathEntryGroupBox); pathSelectorsLayout->addSpacing(10); }; @@ -824,24 +797,32 @@ SetupDialog::SetupDialog(ConfigTab tab_to_open) : pathsScroll->setWidget(pathSelectors); pathsScroll->setWidgetResizable(true); - paths_layout->addWidget(pathsScroll); + paths_layout->addWidget(pathsScroll, 1); paths_layout->addStretch(); + // Add all main widgets to the layout of the settings widget + // This is needed so that we automatically get the correct sizes. + settingsLayout->addWidget(general_w); + settingsLayout->addWidget(performance_w); + settingsLayout->addWidget(audio_w); + settingsLayout->addWidget(midi_w); + settingsLayout->addWidget(paths_w); + // Major tabs ordering. m_tabBar->addTab(general_w, - tr("General"), 0, false, true)->setIcon( + tr("General"), 0, false, true, false)->setIcon( embed::getIconPixmap("setup_general")); m_tabBar->addTab(performance_w, - tr("Performance"), 1, false, true)->setIcon( + tr("Performance"), 1, false, true, false)->setIcon( embed::getIconPixmap("setup_performance")); m_tabBar->addTab(audio_w, - tr("Audio"), 2, false, true)->setIcon( + tr("Audio"), 2, false, true, false)->setIcon( embed::getIconPixmap("setup_audio")); m_tabBar->addTab(midi_w, - tr("MIDI"), 3, false, true)->setIcon( + tr("MIDI"), 3, false, true, false)->setIcon( embed::getIconPixmap("setup_midi")); m_tabBar->addTab(paths_w, - tr("Paths"), 4, true, true)->setIcon( + tr("Paths"), 4, true, true, false)->setIcon( embed::getIconPixmap("setup_directories")); m_tabBar->setActiveTab(static_cast(tab_to_open)); @@ -884,11 +865,14 @@ SetupDialog::SetupDialog(ConfigTab tab_to_open) : extras_layout->addSpacing(10); // Vertical layout ordering. - vlayout->addWidget(main_w); + vlayout->addWidget(main_w, 1); vlayout->addSpacing(10); vlayout->addWidget(extras_w); vlayout->addSpacing(10); + // Ensure that we cannot make the dialog smaller than it wants to be + setMinimumWidth(width()); + show(); } @@ -1182,10 +1166,14 @@ 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) + // 'value' is not a power of 2 (for value > 0) and under 256. On buffer sizes larger than 256 + // lmms works with chunks of size 256 and only the final mix will use the actual buffer size. + // Plugins don't see a larger buffer size than 256 so anything larger than this is functionally + // a 'power of 2' value. + if(((value & (value - 1)) != 0) && value < 256) { text += "
  • " + tr("The currently selected value is not a power of 2 " - "(32, 64, 128, 256, 512, 1024, ...). Some plugins may not be available.") + "
  • "; + "(32, 64, 128, 256). Some plugins may not be available.") + ""; } if(value <= 32) { diff --git a/src/gui/tracks/TrackOperationsWidget.cpp b/src/gui/tracks/TrackOperationsWidget.cpp index fa1a651f613..31edc4949a0 100644 --- a/src/gui/tracks/TrackOperationsWidget.cpp +++ b/src/gui/tracks/TrackOperationsWidget.cpp @@ -195,7 +195,7 @@ bool TrackOperationsWidget::confirmRemoval() QString messageTitleRemoveTrack = tr("Confirm removal"); QString askAgainText = tr("Don't ask again"); auto askAgainCheckBox = new QCheckBox(askAgainText, nullptr); - connect(askAgainCheckBox, &QCheckBox::stateChanged, [this](int state){ + connect(askAgainCheckBox, &QCheckBox::stateChanged, [](int state){ // Invert button state, if it's checked we *shouldn't* ask again ConfigManager::inst()->setValue("ui", "trackdeletionwarning", state ? "0" : "1"); }); diff --git a/src/gui/widgets/BarModelEditor.cpp b/src/gui/widgets/BarModelEditor.cpp new file mode 100644 index 00000000000..4b02c963461 --- /dev/null +++ b/src/gui/widgets/BarModelEditor.cpp @@ -0,0 +1,117 @@ +#include + +#include +#include + + +namespace lmms::gui +{ + +BarModelEditor::BarModelEditor(QString text, FloatModel * floatModel, QWidget * parent) : + FloatModelEditorBase(DirectionOfManipulation::Horizontal, parent), + m_text(text), + m_backgroundBrush(palette().base()), + m_barBrush(palette().button()), + m_textColor(palette().text().color()) +{ + setModel(floatModel); +} + +QSizePolicy BarModelEditor::sizePolicy() const +{ + return QSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); +} + +QSize BarModelEditor::minimumSizeHint() const +{ + auto const fm = fontMetrics(); + return QSize(50, fm.height() + 6); +} + +QSize BarModelEditor::sizeHint() const +{ + return minimumSizeHint(); +} + +QBrush const & BarModelEditor::getBackgroundBrush() const +{ + return m_backgroundBrush; +} + +void BarModelEditor::setBackgroundBrush(QBrush const & backgroundBrush) +{ + m_backgroundBrush = backgroundBrush; +} + +QBrush const & BarModelEditor::getBarBrush() const +{ + return m_barBrush; +} + +void BarModelEditor::setBarBrush(QBrush const & barBrush) +{ + m_barBrush = barBrush; +} + +QColor const & BarModelEditor::getTextColor() const +{ + return m_textColor; +} + +void BarModelEditor::setTextColor(QColor const & textColor) +{ + m_textColor = textColor; +} + +void BarModelEditor::paintEvent(QPaintEvent *event) +{ + QWidget::paintEvent(event); + + auto const * mod = model(); + auto const minValue = mod->minValue(); + auto const maxValue = mod->maxValue(); + auto const range = maxValue - minValue; + + QRect const r = rect(); + + QPainter painter(this); + + // Paint the base rectangle into which the bar and the text go + QBrush const & backgroundBrush = getBackgroundBrush(); + painter.setPen(backgroundBrush.color()); + painter.setBrush(backgroundBrush); + painter.drawRect(r); + + + // Paint the bar + // Compute the percentage as: + // min + x * (max - min) = v <=> x = (v - min) / (max - min) + auto const percentage = range == 0 ? 1. : (mod->value() - minValue) / range; + + int const margin = 3; + QMargins const margins(margin, margin, margin, margin); + QRect const valueRect = r.marginsRemoved(margins); + + QBrush const & barBrush = getBarBrush(); + painter.setPen(barBrush.color()); + painter.setBrush(barBrush); + QPoint const startPoint = valueRect.topLeft(); + QPoint endPoint = valueRect.bottomRight(); + endPoint.setX(startPoint.x() + percentage * (endPoint.x() - startPoint.x())); + + painter.drawRect(QRect(startPoint, endPoint)); + + + // Draw the text into the value rectangle but move it slightly to the right + QRect const textRect = valueRect.marginsRemoved(QMargins(3, 0, 0, 0)); + + // Elide the text if needed + auto const fm = fontMetrics(); + QString const elidedText = fm.elidedText(m_text, Qt::ElideRight, textRect.width()); + + // Now draw the text + painter.setPen(getTextColor()); + painter.drawText(textRect, elidedText); +} + +} // namespace lmms::gui diff --git a/src/gui/widgets/FloatModelEditorBase.cpp b/src/gui/widgets/FloatModelEditorBase.cpp new file mode 100644 index 00000000000..7421908e2d2 --- /dev/null +++ b/src/gui/widgets/FloatModelEditorBase.cpp @@ -0,0 +1,464 @@ +/* + * FloatModelEditorBase.cpp - Base editor for float models + * + * Copyright (c) 2004-2014 Tobias Doerffel + * Copyright (c) 2023 Michael Gregorius + * + * 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 "FloatModelEditorBase.h" + +#include +#include +#include + +#ifndef __USE_XOPEN +#define __USE_XOPEN +#endif + +#include "lmms_math.h" +#include "CaptionMenu.h" +#include "ControllerConnection.h" +#include "GuiApplication.h" +#include "LocaleHelper.h" +#include "MainWindow.h" +#include "ProjectJournal.h" +#include "SimpleTextFloat.h" +#include "StringPairDrag.h" + + +namespace lmms::gui +{ + +SimpleTextFloat * FloatModelEditorBase::s_textFloat = nullptr; + +FloatModelEditorBase::FloatModelEditorBase(DirectionOfManipulation directionOfManipulation, QWidget * parent, const QString & name) : + QWidget(parent), + FloatModelView(new FloatModel(0, 0, 0, 1, nullptr, name, true), this), + m_volumeKnob(false), + m_volumeRatio(100.0, 0.0, 1000000.0), + m_buttonPressed(false), + m_directionOfManipulation(directionOfManipulation) +{ + initUi(name); +} + + +void FloatModelEditorBase::initUi(const QString & name) +{ + if (s_textFloat == nullptr) + { + s_textFloat = new SimpleTextFloat; + } + + setWindowTitle(name); + + setFocusPolicy(Qt::ClickFocus); + + doConnections(); +} + + +void FloatModelEditorBase::showTextFloat(int msecBeforeDisplay, int msecDisplayTime) +{ + s_textFloat->setText(displayValue()); + s_textFloat->moveGlobal(this, QPoint(width() + 2, 0)); + s_textFloat->showWithDelay(msecBeforeDisplay, msecDisplayTime); +} + + +float FloatModelEditorBase::getValue(const QPoint & p) +{ + // Find out which direction/coordinate is relevant for this control + int const coordinate = m_directionOfManipulation == DirectionOfManipulation::Vertical ? p.y() : -p.x(); + + // knob value increase is linear to mouse movement + float value = .4f * coordinate; + + // if shift pressed we want slower movement + if (getGUI()->mainWindow()->isShiftPressed()) + { + value /= 4.0f; + value = qBound(-4.0f, value, 4.0f); + } + + return value * pageSize(); +} + + +void FloatModelEditorBase::contextMenuEvent(QContextMenuEvent *) +{ + // for the case, the user clicked right while pressing left mouse- + // button, the context-menu appears while mouse-cursor is still hidden + // and it isn't shown again until user does something which causes + // an QApplication::restoreOverrideCursor()-call... + mouseReleaseEvent(nullptr); + + CaptionMenu contextMenu(model()->displayName(), this); + addDefaultActions(&contextMenu); + contextMenu.addAction(QPixmap(), + model()->isScaleLogarithmic() ? tr("Set linear") : tr("Set logarithmic"), + this, SLOT(toggleScale())); + contextMenu.addSeparator(); + contextMenu.exec(QCursor::pos()); +} + + +void FloatModelEditorBase::toggleScale() +{ + model()->setScaleLogarithmic(! model()->isScaleLogarithmic()); + update(); +} + + +void FloatModelEditorBase::dragEnterEvent(QDragEnterEvent * dee) +{ + StringPairDrag::processDragEnterEvent(dee, "float_value," + "automatable_model"); +} + + +void FloatModelEditorBase::dropEvent(QDropEvent * de) +{ + QString type = StringPairDrag::decodeKey(de); + QString val = StringPairDrag::decodeValue(de); + if (type == "float_value") + { + model()->setValue(LocaleHelper::toFloat(val)); + de->accept(); + } + else if (type == "automatable_model") + { + auto mod = dynamic_cast(Engine::projectJournal()->journallingObject(val.toInt())); + if (mod != nullptr) + { + AutomatableModel::linkModels(model(), mod); + mod->setValue(model()->value()); + } + } +} + + +void FloatModelEditorBase::mousePressEvent(QMouseEvent * me) +{ + if (me->button() == Qt::LeftButton && + ! (me->modifiers() & Qt::ControlModifier) && + ! (me->modifiers() & Qt::ShiftModifier)) + { + AutomatableModel *thisModel = model(); + if (thisModel) + { + thisModel->addJournalCheckPoint(); + thisModel->saveJournallingState(false); + } + + const QPoint & p = me->pos(); + m_lastMousePos = p; + m_leftOver = 0.0f; + + emit sliderPressed(); + + showTextFloat(0, 0); + + s_textFloat->setText(displayValue()); + s_textFloat->moveGlobal(this, + QPoint(width() + 2, 0)); + s_textFloat->show(); + m_buttonPressed = true; + } + else if (me->button() == Qt::LeftButton && + (me->modifiers() & Qt::ShiftModifier)) + { + new StringPairDrag("float_value", + QString::number(model()->value()), + QPixmap(), this); + } + else + { + FloatModelView::mousePressEvent(me); + } +} + + +void FloatModelEditorBase::mouseMoveEvent(QMouseEvent * me) +{ + if (m_buttonPressed && me->pos() != m_lastMousePos) + { + // knob position is changed depending on last mouse position + setPosition(me->pos() - m_lastMousePos); + emit sliderMoved(model()->value()); + // original position for next time is current position + m_lastMousePos = me->pos(); + } + s_textFloat->setText(displayValue()); + s_textFloat->show(); +} + + +void FloatModelEditorBase::mouseReleaseEvent(QMouseEvent* event) +{ + if (event && event->button() == Qt::LeftButton) + { + AutomatableModel *thisModel = model(); + if (thisModel) + { + thisModel->restoreJournallingState(); + } + } + + m_buttonPressed = false; + + emit sliderReleased(); + + QApplication::restoreOverrideCursor(); + + s_textFloat->hide(); +} + + +void FloatModelEditorBase::enterEvent(QEvent *event) +{ + showTextFloat(700, 2000); +} + + +void FloatModelEditorBase::leaveEvent(QEvent *event) +{ + s_textFloat->hide(); +} + + +void FloatModelEditorBase::focusOutEvent(QFocusEvent * fe) +{ + // make sure we don't loose mouse release event + mouseReleaseEvent(nullptr); + QWidget::focusOutEvent(fe); +} + + +void FloatModelEditorBase::mouseDoubleClickEvent(QMouseEvent *) +{ + enterValue(); +} + + +void FloatModelEditorBase::paintEvent(QPaintEvent *) +{ + QPainter p(this); + + QColor const foreground(3, 94, 97); + + auto const * mod = model(); + auto const minValue = mod->minValue(); + auto const maxValue = mod->maxValue(); + auto const range = maxValue - minValue; + + // Compute the percentage + // min + x * (max - min) = v <=> x = (v - min) / (max - min) + auto const percentage = range == 0 ? 1. : (mod->value() - minValue) / range; + + QRect r = rect(); + p.setPen(foreground); + p.setBrush(foreground); + p.drawRect(QRect(r.topLeft(), QPoint(r.width() * percentage, r.height()))); +} + + +void FloatModelEditorBase::wheelEvent(QWheelEvent * we) +{ + we->accept(); + const int deltaY = we->angleDelta().y(); + float direction = deltaY > 0 ? 1 : -1; + + auto * m = model(); + float const step = m->step(); + float const range = m->range(); + + // This is the default number of steps or mouse wheel events that it takes to sweep + // from the lowest value to the highest value. + // It might be modified if the user presses modifier keys. See below. + float numberOfStepsForFullSweep = 100.; + + auto const modKeys = we->modifiers(); + if (modKeys == Qt::ShiftModifier) + { + // The shift is intended to go through the values in very coarse steps as in: + // "Shift into overdrive" + numberOfStepsForFullSweep = 10; + } + else if (modKeys == Qt::ControlModifier) + { + // The control key gives more control, i.e. it enables more fine-grained adjustments + numberOfStepsForFullSweep = 1000; + } + else if (modKeys == Qt::AltModifier) + { + // The alt key enables even finer adjustments + numberOfStepsForFullSweep = 2000; + + // It seems that on some systems pressing Alt with mess with the directions, + // i.e. scrolling the mouse wheel is interpreted as pressing the mouse wheel + // left and right. Account for this quirk. + if (deltaY == 0) + { + int const deltaX = we->angleDelta().x(); + if (deltaX != 0) + { + direction = deltaX > 0 ? 1 : -1; + } + } + } + + // Compute the number of steps but make sure that we always do at least one step + const float stepMult = std::max(range / numberOfStepsForFullSweep / step, 1.f); + const int inc = direction * stepMult; + model()->incValue(inc); + + s_textFloat->setText(displayValue()); + s_textFloat->moveGlobal(this, QPoint(width() + 2, 0)); + s_textFloat->setVisibilityTimeOut(1000); + + emit sliderMoved(model()->value()); +} + + +void FloatModelEditorBase::setPosition(const QPoint & p) +{ + const float value = getValue(p) + m_leftOver; + const auto step = model()->step(); + const float oldValue = model()->value(); + + if (model()->isScaleLogarithmic()) // logarithmic code + { + const float pos = model()->minValue() < 0 + ? oldValue / qMax(qAbs(model()->maxValue()), qAbs(model()->minValue())) + : (oldValue - model()->minValue()) / model()->range(); + const float ratio = 0.1f + qAbs(pos) * 15.f; + float newValue = value * ratio; + if (qAbs(newValue) >= step) + { + float roundedValue = qRound((oldValue - value) / step) * step; + model()->setValue(roundedValue); + m_leftOver = 0.0f; + } + else + { + m_leftOver = value; + } + } + + else // linear code + { + if (qAbs(value) >= step) + { + float roundedValue = qRound((oldValue - value) / step) * step; + model()->setValue(roundedValue); + m_leftOver = 0.0f; + } + else + { + m_leftOver = value; + } + } +} + + +void FloatModelEditorBase::enterValue() +{ + bool ok; + float new_val; + + if (isVolumeKnob() && + ConfigManager::inst()->value("app", "displaydbfs").toInt()) + { + new_val = QInputDialog::getDouble( + this, tr("Set value"), + tr("Please enter a new value between " + "-96.0 dBFS and 6.0 dBFS:"), + ampToDbfs(model()->getRoundedValue() / 100.0), + -96.0, 6.0, model()->getDigitCount(), &ok); + if (new_val <= -96.0) + { + new_val = 0.0f; + } + else + { + new_val = dbfsToAmp(new_val) * 100.0; + } + } + else + { + new_val = QInputDialog::getDouble( + this, tr("Set value"), + tr("Please enter a new value between " + "%1 and %2:"). + arg(model()->minValue()). + arg(model()->maxValue()), + model()->getRoundedValue(), + model()->minValue(), + model()->maxValue(), model()->getDigitCount(), &ok); + } + + if (ok) + { + model()->setValue(new_val); + } +} + + +void FloatModelEditorBase::friendlyUpdate() +{ + if (model() && (model()->controllerConnection() == nullptr || + model()->controllerConnection()->getController()->frequentUpdates() == false || + Controller::runningFrames() % (256*4) == 0)) + { + update(); + } +} + + +QString FloatModelEditorBase::displayValue() const +{ + if (isVolumeKnob() && + ConfigManager::inst()->value("app", "displaydbfs").toInt()) + { + return m_description.trimmed() + QString(" %1 dBFS"). + arg(ampToDbfs(model()->getRoundedValue() / volumeRatio()), + 3, 'f', 2); + } + + return m_description.trimmed() + QString(" %1"). + arg(model()->getRoundedValue()) + m_unit; +} + + +void FloatModelEditorBase::doConnections() +{ + if (model() != nullptr) + { + QObject::connect(model(), SIGNAL(dataChanged()), + this, SLOT(friendlyUpdate())); + + QObject::connect(model(), SIGNAL(propertiesChanged()), + this, SLOT(update())); + } +} + +} // namespace lmms::gui diff --git a/src/gui/widgets/Knob.cpp b/src/gui/widgets/Knob.cpp index 56cf29345d0..00a9363c87f 100644 --- a/src/gui/widgets/Knob.cpp +++ b/src/gui/widgets/Knob.cpp @@ -24,11 +24,6 @@ #include "Knob.h" -#include -#include -#include -#include -#include #include #ifndef __USE_XOPEN @@ -36,36 +31,19 @@ #endif #include "lmms_math.h" -#include "CaptionMenu.h" -#include "ConfigManager.h" -#include "ControllerConnection.h" #include "DeprecationHelper.h" #include "embed.h" #include "gui_templates.h" -#include "GuiApplication.h" -#include "LocaleHelper.h" -#include "MainWindow.h" -#include "ProjectJournal.h" -#include "SimpleTextFloat.h" -#include "StringPairDrag.h" + namespace lmms::gui { -SimpleTextFloat * Knob::s_textFloat = nullptr; - - - - Knob::Knob( KnobType _knob_num, QWidget * _parent, const QString & _name ) : - QWidget( _parent ), - FloatModelView( new FloatModel( 0, 0, 0, 1, nullptr, _name, true ), this ), + FloatModelEditorBase(DirectionOfManipulation::Vertical, _parent, _name), m_label( "" ), m_isHtmlLabel(false), m_tdRenderer(nullptr), - 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 ), @@ -84,18 +62,10 @@ Knob::Knob( QWidget * _parent, const QString & _name ) : void Knob::initUi( const QString & _name ) { - if( s_textFloat == nullptr ) - { - s_textFloat = new SimpleTextFloat; - } - - setWindowTitle( _name ); - onKnobNumUpdated(); setTotalAngle( 270.0f ); setInnerRadius( 1.0f ); setOuterRadius( 10.0f ); - setFocusPolicy( Qt::ClickFocus ); // This is a workaround to enable style sheets for knobs which are not styled knobs. // @@ -123,13 +93,9 @@ void Knob::initUi( const QString & _name ) default: break; } - - doConnections(); } - - void Knob::onKnobNumUpdated() { if( m_knobNum != KnobType::Styled ) @@ -484,195 +450,6 @@ 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; - - // knob value increase is linear to mouse movement - value = .4f * _p.y(); - - // if shift pressed we want slower movement - if( getGUI()->mainWindow()->isShiftPressed() ) - { - value /= 4.0f; - value = qBound( -4.0f, value, 4.0f ); - } - return value * pageSize(); -} - - - - -void Knob::contextMenuEvent( QContextMenuEvent * ) -{ - // for the case, the user clicked right while pressing left mouse- - // button, the context-menu appears while mouse-cursor is still hidden - // and it isn't shown again until user does something which causes - // an QApplication::restoreOverrideCursor()-call... - mouseReleaseEvent( nullptr ); - - CaptionMenu contextMenu( model()->displayName(), this ); - addDefaultActions( &contextMenu ); - contextMenu.addAction( QPixmap(), - model()->isScaleLogarithmic() ? tr( "Set linear" ) : tr( "Set logarithmic" ), - this, SLOT(toggleScale())); - contextMenu.addSeparator(); - contextMenu.exec( QCursor::pos() ); -} - - -void Knob::toggleScale() -{ - model()->setScaleLogarithmic( ! model()->isScaleLogarithmic() ); - update(); -} - - - -void Knob::dragEnterEvent( QDragEnterEvent * _dee ) -{ - StringPairDrag::processDragEnterEvent( _dee, "float_value," - "automatable_model" ); -} - - - - -void Knob::dropEvent( QDropEvent * _de ) -{ - QString type = StringPairDrag::decodeKey( _de ); - QString val = StringPairDrag::decodeValue( _de ); - if( type == "float_value" ) - { - model()->setValue( LocaleHelper::toFloat(val) ); - _de->accept(); - } - else if( type == "automatable_model" ) - { - auto mod = dynamic_cast(Engine::projectJournal()->journallingObject(val.toInt())); - if( mod != nullptr ) - { - AutomatableModel::linkModels( model(), mod ); - mod->setValue( model()->value() ); - } - } -} - - - - -void Knob::mousePressEvent( QMouseEvent * _me ) -{ - if( _me->button() == Qt::LeftButton && - ! ( _me->modifiers() & Qt::ControlModifier ) && - ! ( _me->modifiers() & Qt::ShiftModifier ) ) - { - AutomatableModel *thisModel = model(); - if( thisModel ) - { - thisModel->addJournalCheckPoint(); - thisModel->saveJournallingState( false ); - } - - const QPoint & p = _me->pos(); - m_lastMousePos = p; - m_leftOver = 0.0f; - - emit sliderPressed(); - - showTextFloat(0, 0); - - m_buttonPressed = true; - } - else if( _me->button() == Qt::LeftButton && - (_me->modifiers() & Qt::ShiftModifier) ) - { - new StringPairDrag( "float_value", - QString::number( model()->value() ), - QPixmap(), this ); - } - else - { - FloatModelView::mousePressEvent( _me ); - } -} - - - - -void Knob::mouseMoveEvent( QMouseEvent * _me ) -{ - if( m_buttonPressed && _me->pos() != m_lastMousePos ) - { - // knob position is changed depending on last mouse position - setPosition( _me->pos() - m_lastMousePos ); - emit sliderMoved( model()->value() ); - // original position for next time is current position - m_lastMousePos = _me->pos(); - } - s_textFloat->setText( displayValue() ); - s_textFloat->show(); -} - - - - -void Knob::mouseReleaseEvent( QMouseEvent* event ) -{ - if( event && event->button() == Qt::LeftButton ) - { - AutomatableModel *thisModel = model(); - if( thisModel ) - { - thisModel->restoreJournallingState(); - } - } - - m_buttonPressed = false; - - emit sliderReleased(); - - QApplication::restoreOverrideCursor(); - - s_textFloat->hide(); -} - -void Knob::enterEvent(QEvent *event) -{ - showTextFloat(700, 2000); -} - -void Knob::leaveEvent(QEvent *event) -{ - s_textFloat->hide(); -} - - -void Knob::focusOutEvent( QFocusEvent * _fe ) -{ - // make sure we don't loose mouse release event - mouseReleaseEvent( nullptr ); - QWidget::focusOutEvent( _fe ); -} - - - - -void Knob::mouseDoubleClickEvent( QMouseEvent * ) -{ - enterValue(); -} - - - - void Knob::paintEvent( QPaintEvent * _me ) { QPainter p( this ); @@ -697,201 +474,6 @@ void Knob::paintEvent( QPaintEvent * _me ) } } - - - -void Knob::wheelEvent(QWheelEvent * we) -{ - we->accept(); - const int deltaY = we->angleDelta().y(); - float direction = deltaY > 0 ? 1 : -1; - - auto * m = model(); - float const step = m->step(); - float const range = m->range(); - - // This is the default number of steps or mouse wheel events that it takes to sweep - // from the lowest value to the highest value. - // It might be modified if the user presses modifier keys. See below. - float numberOfStepsForFullSweep = 100.; - - auto const modKeys = we->modifiers(); - if (modKeys == Qt::ShiftModifier) - { - // The shift is intended to go through the values in very coarse steps as in: - // "Shift into overdrive" - numberOfStepsForFullSweep = 10; - } - else if (modKeys == Qt::ControlModifier) - { - // The control key gives more control, i.e. it enables more fine-grained adjustments - numberOfStepsForFullSweep = 1000; - } - else if (modKeys == Qt::AltModifier) - { - // The alt key enables even finer adjustments - numberOfStepsForFullSweep = 2000; - - // It seems that on some systems pressing Alt with mess with the directions, - // i.e. scrolling the mouse wheel is interpreted as pressing the mouse wheel - // left and right. Account for this quirk. - if (deltaY == 0) - { - int const deltaX = we->angleDelta().x(); - if (deltaX != 0) - { - direction = deltaX > 0 ? 1 : -1; - } - } - } - - // Compute the number of steps but make sure that we always do at least one step - const float stepMult = std::max(range / numberOfStepsForFullSweep / step, 1.f); - const int inc = direction * stepMult; - model()->incValue(inc); - - s_textFloat->setText( displayValue() ); - s_textFloat->moveGlobal( this, QPoint( width() + 2, 0 ) ); - s_textFloat->setVisibilityTimeOut( 1000 ); - - emit sliderMoved( model()->value() ); -} - - - - -void Knob::setPosition( const QPoint & _p ) -{ - const float value = getValue( _p ) + m_leftOver; - const auto step = model()->step(); - const float oldValue = model()->value(); - - - - if( model()->isScaleLogarithmic() ) // logarithmic code - { - const float pos = model()->minValue() < 0 - ? oldValue / qMax( qAbs( model()->maxValue() ), qAbs( model()->minValue() ) ) - : ( oldValue - model()->minValue() ) / model()->range(); - const float ratio = 0.1f + qAbs( pos ) * 15.f; - float newValue = value * ratio; - if( qAbs( newValue ) >= step ) - { - float roundedValue = qRound( ( oldValue - value ) / step ) * step; - model()->setValue( roundedValue ); - m_leftOver = 0.0f; - } - else - { - m_leftOver = value; - } - } - - else // linear code - { - if( qAbs( value ) >= step ) - { - float roundedValue = qRound( ( oldValue - value ) / step ) * step; - model()->setValue( roundedValue ); - m_leftOver = 0.0f; - } - else - { - m_leftOver = value; - } - } -} - - - - -void Knob::enterValue() -{ - bool ok; - float new_val; - - if( isVolumeKnob() && - ConfigManager::inst()->value( "app", "displaydbfs" ).toInt() ) - { - new_val = QInputDialog::getDouble( - this, tr( "Set value" ), - tr( "Please enter a new value between " - "-96.0 dBFS and 6.0 dBFS:" ), - ampToDbfs( model()->getRoundedValue() / 100.0 ), - -96.0, 6.0, model()->getDigitCount(), &ok ); - if( new_val <= -96.0 ) - { - new_val = 0.0f; - } - else - { - new_val = dbfsToAmp( new_val ) * 100.0; - } - } - else - { - new_val = QInputDialog::getDouble( - this, tr( "Set value" ), - tr( "Please enter a new value between " - "%1 and %2:" ). - arg( model()->minValue() ). - arg( model()->maxValue() ), - model()->getRoundedValue(), - model()->minValue(), - model()->maxValue(), model()->getDigitCount(), &ok ); - } - - if( ok ) - { - model()->setValue( new_val ); - } -} - - - - -void Knob::friendlyUpdate() -{ - if (model() && (model()->controllerConnection() == nullptr || - model()->controllerConnection()->getController()->frequentUpdates() == false || - Controller::runningFrames() % (256*4) == 0)) - { - update(); - } -} - - - - -QString Knob::displayValue() const -{ - if( isVolumeKnob() && - ConfigManager::inst()->value( "app", "displaydbfs" ).toInt() ) - { - return m_description.trimmed() + QString( " %1 dBFS" ). - arg( ampToDbfs( model()->getRoundedValue() / volumeRatio() ), - 3, 'f', 2 ); - } - return m_description.trimmed() + QString( " %1" ). - arg( model()->getRoundedValue() ) + m_unit; -} - - - - -void Knob::doConnections() -{ - if( model() != nullptr ) - { - QObject::connect( model(), SIGNAL(dataChanged()), - this, SLOT(friendlyUpdate())); - - QObject::connect( model(), SIGNAL(propertiesChanged()), - this, SLOT(update())); - } -} - - void Knob::changeEvent(QEvent * ev) { if (ev->type() == QEvent::EnabledChange) diff --git a/src/gui/widgets/LcdFloatSpinBox.cpp b/src/gui/widgets/LcdFloatSpinBox.cpp index 96f2b27e1db..c7e20467a5b 100644 --- a/src/gui/widgets/LcdFloatSpinBox.cpp +++ b/src/gui/widgets/LcdFloatSpinBox.cpp @@ -109,11 +109,16 @@ void LcdFloatSpinBox::layoutSetup(const QString &style) void LcdFloatSpinBox::update() { - const int whole = static_cast(model()->value()); - const float fraction = model()->value() - whole; - const int intFraction = fraction * std::pow(10.f, m_fractionDisplay.numDigits()); - m_wholeDisplay.setValue(whole); - m_fractionDisplay.setValue(intFraction); + const int digitValue = std::pow(10.f, m_fractionDisplay.numDigits()); + float value = model()->value(); + int fraction = std::abs(std::round((value - static_cast(value)) * digitValue)); + if (fraction == digitValue) + { + value += std::copysign(1, value); + fraction = 0; + } + m_wholeDisplay.setValue(value); + m_fractionDisplay.setValue(fraction); QWidget::update(); } @@ -129,6 +134,9 @@ void LcdFloatSpinBox::contextMenuEvent(QContextMenuEvent* event) void LcdFloatSpinBox::mousePressEvent(QMouseEvent* event) { + // switch between integer and fractional step based on cursor position + m_intStep = event->x() < m_wholeDisplay.width(); + if (event->button() == Qt::LeftButton && !(event->modifiers() & Qt::ControlModifier) && event->y() < m_wholeDisplay.cellHeight() + 2) @@ -152,10 +160,6 @@ void LcdFloatSpinBox::mousePressEvent(QMouseEvent* event) void LcdFloatSpinBox::mouseMoveEvent(QMouseEvent* event) { - // switch between integer and fractional step based on cursor position - if (event->x() < m_wholeDisplay.width()) { m_intStep = true; } - else { m_intStep = false; } - if (m_mouseMoving) { int dy = event->globalY() - m_origMousePos.y(); diff --git a/src/gui/widgets/LcdWidget.cpp b/src/gui/widgets/LcdWidget.cpp index 0f5e1346658..b8afcd46d19 100644 --- a/src/gui/widgets/LcdWidget.cpp +++ b/src/gui/widgets/LcdWidget.cpp @@ -94,6 +94,22 @@ void LcdWidget::setValue(int value) update(); } +void LcdWidget::setValue(float value) +{ + if (value < 0 && value > -1) + { + QString s = QString::number(static_cast(value)); + s.prepend('-'); + + m_display = s; + update(); + } + else + { + setValue(static_cast(value)); + } +} + diff --git a/src/gui/widgets/LedCheckBox.cpp b/src/gui/widgets/LedCheckBox.cpp index 0c16bf391ae..75e73328fce 100644 --- a/src/gui/widgets/LedCheckBox.cpp +++ b/src/gui/widgets/LedCheckBox.cpp @@ -44,9 +44,10 @@ static const auto names = std::array LedCheckBox::LedCheckBox( const QString & _text, QWidget * _parent, - const QString & _name, LedColor _color ) : + const QString & _name, LedColor _color, bool legacyMode ) : AutomatableButton( _parent, _name ), - m_text( _text ) + m_text( _text ), + m_legacyMode(legacyMode) { initUi( _color ); } @@ -55,8 +56,8 @@ LedCheckBox::LedCheckBox( const QString & _text, QWidget * _parent, LedCheckBox::LedCheckBox( QWidget * _parent, - const QString & _name, LedColor _color ) : - LedCheckBox( QString(), _parent, _name, _color ) + const QString & _name, LedColor _color, bool legacyMode ) : + LedCheckBox( QString(), _parent, _name, _color, legacyMode ) { } @@ -80,24 +81,16 @@ void LedCheckBox::setText( const QString &s ) -void LedCheckBox::paintEvent( QPaintEvent * ) +void LedCheckBox::paintEvent( QPaintEvent * pe ) { - QPainter p( this ); - p.setFont( pointSize<7>( font() ) ); - - if( model()->value() == true ) - { - p.drawPixmap( 0, 0, *m_ledOnPixmap ); + if (!m_legacyMode) + { + paintNonLegacy(pe); } else { - p.drawPixmap( 0, 0, *m_ledOffPixmap ); + paintLegacy(pe); } - - p.setPen( QColor( 64, 64, 64 ) ); - p.drawText( m_ledOffPixmap->width() + 4, 11, text() ); - p.setPen( QColor( 255, 255, 255 ) ); - p.drawText( m_ledOffPixmap->width() + 3, 10, text() ); } @@ -111,7 +104,11 @@ void LedCheckBox::initUi( LedColor _color ) names[static_cast(_color)].toUtf8().constData() ) ); m_ledOffPixmap = new QPixmap( embed::getIconPixmap( "led_off" ) ); - setFont( pointSize<7>( font() ) ); + if (m_legacyMode) + { + setFont( pointSize<7>( font() ) ); + } + setText( m_text ); } @@ -120,9 +117,45 @@ void LedCheckBox::initUi( LedColor _color ) void LedCheckBox::onTextUpdated() { - setFixedSize(m_ledOffPixmap->width() + 5 + horizontalAdvance(QFontMetrics(font()), - text()), - m_ledOffPixmap->height()); + QFontMetrics const fm = fontMetrics(); + + int const width = m_ledOffPixmap->width() + 5 + horizontalAdvance(fm, text()); + int const height = m_legacyMode ? m_ledOffPixmap->height() : qMax(m_ledOffPixmap->height(), fm.height()); + + setFixedSize(width, height); +} + +void LedCheckBox::paintLegacy(QPaintEvent * pe) +{ + QPainter p( this ); + p.setFont( pointSize<7>( font() ) ); + + if( model()->value() == true ) + { + p.drawPixmap( 0, 0, *m_ledOnPixmap ); + } + else + { + p.drawPixmap( 0, 0, *m_ledOffPixmap ); + } + + p.setPen( QColor( 64, 64, 64 ) ); + p.drawText( m_ledOffPixmap->width() + 4, 11, text() ); + p.setPen( QColor( 255, 255, 255 ) ); + p.drawText( m_ledOffPixmap->width() + 3, 10, text() ); +} + +void LedCheckBox::paintNonLegacy(QPaintEvent * pe) +{ + QPainter p(this); + + QPixmap * drawnPixmap = model()->value() ? m_ledOnPixmap : m_ledOffPixmap; + + p.drawPixmap( 0, rect().height() / 2 - drawnPixmap->height() / 2, *drawnPixmap); + + QRect r = rect(); + r -= QMargins(m_ledOffPixmap->width() + 5, 0, 0, 0); + p.drawText(r, text()); } diff --git a/src/gui/widgets/TabBar.cpp b/src/gui/widgets/TabBar.cpp index 806a932528c..e2949455138 100644 --- a/src/gui/widgets/TabBar.cpp +++ b/src/gui/widgets/TabBar.cpp @@ -44,7 +44,7 @@ TabBar::TabBar( QWidget * _parent, QBoxLayout::Direction _dir ) : } TabButton * TabBar::addTab( QWidget * _w, const QString & _text, int _id, - bool _add_stretch, bool _text_is_tooltip ) + bool _add_stretch, bool _text_is_tooltip, bool fixWidgetToParentSize ) { // already tab with id? if( m_tabs.contains( _id ) ) @@ -83,10 +83,12 @@ TabButton * TabBar::addTab( QWidget * _w, const QString & _text, int _id, m_layout->addStretch(); } - - // we assume, parent-widget is a widget acting as widget-stack so all - // widgets have the same size and only the one on the top is visible - _w->setFixedSize( _w->parentWidget()->size() ); + if (fixWidgetToParentSize) + { + // we assume, parent-widget is a widget acting as widget-stack so all + // widgets have the same size and only the one on the top is visible + _w->setFixedSize( _w->parentWidget()->size() ); + } b->setFont( pointSize<8>( b->font() ) ); diff --git a/src/gui/widgets/TempoSyncBarModelEditor.cpp b/src/gui/widgets/TempoSyncBarModelEditor.cpp new file mode 100644 index 00000000000..5ff2332e051 --- /dev/null +++ b/src/gui/widgets/TempoSyncBarModelEditor.cpp @@ -0,0 +1,302 @@ +/* + * TempoSyncBarModelEditor.cpp - adds bpm to ms conversion for the bar editor class + * + * Copyright (c) 2005-2007 Danny McRae + * Copyright (c) 2005-2009 Tobias Doerffel + * Copyright (c) 2023 Michael Gregorius + * + * 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 + +#include "TempoSyncBarModelEditor.h" +#include "Engine.h" +#include "CaptionMenu.h" +#include "embed.h" +#include "GuiApplication.h" +#include "MainWindow.h" +#include "MeterDialog.h" +#include "Song.h" +#include "SubWindow.h" + + +namespace lmms::gui +{ + +TempoSyncBarModelEditor::TempoSyncBarModelEditor(QString text, FloatModel * floatModel, QWidget * parent) : + BarModelEditor(text, floatModel, parent), + m_tempoSyncIcon(embed::getIconPixmap("tempo_sync")), + m_tempoSyncDescription(tr("Tempo Sync")), + m_custom(nullptr) +{ + modelChanged(); +} + + +TempoSyncBarModelEditor::~TempoSyncBarModelEditor() +{ + if(m_custom) + { + delete m_custom->parentWidget(); + } +} + + +void TempoSyncBarModelEditor::modelChanged() +{ + TempoSyncKnobModel * tempoSyncModel = model(); + + if(tempoSyncModel == nullptr) + { + qWarning("no TempoSyncKnobModel has been set!"); + } + + if(m_custom != nullptr) + { + m_custom->setModel(&tempoSyncModel->getCustomMeterModel()); + } + + connect(tempoSyncModel, &TempoSyncKnobModel::syncModeChanged, this, &TempoSyncBarModelEditor::updateDescAndIcon); + connect(this, SIGNAL(sliderMoved(float)), tempoSyncModel, SLOT(disableSync())); + + updateDescAndIcon(); +} + + +void TempoSyncBarModelEditor::contextMenuEvent(QContextMenuEvent *) +{ + mouseReleaseEvent(nullptr); + + TempoSyncKnobModel * tempoSyncModel = model(); + + CaptionMenu contextMenu(tempoSyncModel->displayName(), this); + addDefaultActions(&contextMenu); + + contextMenu.addSeparator(); + + float limit = 60000.0f / (Engine::getSong()->getTempo() * tempoSyncModel->scale()); + + QMenu * syncMenu = contextMenu.addMenu(m_tempoSyncIcon, m_tempoSyncDescription); + + float const maxValue = tempoSyncModel->maxValue(); + + if(limit / 8.0f <= maxValue) + { + connect(syncMenu, SIGNAL(triggered(QAction*)), tempoSyncModel, SLOT(setTempoSync(QAction*))); + + syncMenu->addAction(embed::getIconPixmap("note_none"), + tr("No Sync"))->setData((int) TempoSyncKnobModel::SyncMode::None); + + if(limit / 0.125f <= maxValue) + { + syncMenu->addAction(embed::getIconPixmap("note_double_whole"), + tr("Eight beats"))->setData((int) TempoSyncKnobModel::SyncMode::DoubleWholeNote); + } + + if(limit / 0.25f <= maxValue) + { + syncMenu->addAction(embed::getIconPixmap("note_whole"), + tr("Whole note"))->setData((int) TempoSyncKnobModel::SyncMode::WholeNote); + } + + if(limit / 0.5f <= maxValue) + { + syncMenu->addAction(embed::getIconPixmap("note_half"), + tr("Half note"))->setData((int) TempoSyncKnobModel::SyncMode::HalfNote); + } + + if(limit <= maxValue) + { + syncMenu->addAction(embed::getIconPixmap("note_quarter"), + tr("Quarter note"))->setData((int) TempoSyncKnobModel::SyncMode::QuarterNote); + } + + if(limit / 2.0f <= maxValue) + { + syncMenu->addAction(embed::getIconPixmap("note_eighth"), + tr("8th note"))->setData((int) TempoSyncKnobModel::SyncMode::EighthNote); + } + + if(limit / 4.0f <= maxValue) + { + syncMenu->addAction(embed::getIconPixmap("note_sixteenth"), + tr("16th note"))->setData((int) TempoSyncKnobModel::SyncMode::SixteenthNote); + } + + syncMenu->addAction(embed::getIconPixmap("note_thirtysecond"), + tr("32nd note"))->setData((int) TempoSyncKnobModel::SyncMode::ThirtysecondNote); + + syncMenu->addAction(embed::getIconPixmap("dont_know"), + tr("Custom..."), this, SLOT(showCustom()))->setData((int) TempoSyncKnobModel::SyncMode::Custom); + + contextMenu.addSeparator(); + } + + contextMenu.exec(QCursor::pos()); + + delete syncMenu; +} + +void TempoSyncBarModelEditor::updateDescAndIcon() +{ + updateTextDescription(); + + if(m_custom != nullptr && model()->syncMode() != TempoSyncKnobModel::SyncMode::Custom) + { + m_custom->parentWidget()->hide(); + } + + updateIcon(); + + emit syncDescriptionChanged(m_tempoSyncDescription); + emit syncIconChanged(); +} + + +const QString & TempoSyncBarModelEditor::syncDescription() +{ + return m_tempoSyncDescription; +} + + +void TempoSyncBarModelEditor::setSyncDescription(const QString & new_description) +{ + m_tempoSyncDescription = new_description; + emit syncDescriptionChanged(new_description); +} + + +const QPixmap & TempoSyncBarModelEditor::syncIcon() +{ + return m_tempoSyncIcon; +} + + +void TempoSyncBarModelEditor::setSyncIcon(const QPixmap & new_icon) +{ + m_tempoSyncIcon = new_icon; + emit syncIconChanged(); +} + + +void TempoSyncBarModelEditor::showCustom() +{ + if(m_custom == nullptr) + { + m_custom = new MeterDialog(getGUI()->mainWindow()->workspace()); + QMdiSubWindow * subWindow = getGUI()->mainWindow()->addWindowedWidget(m_custom); + Qt::WindowFlags flags = subWindow->windowFlags(); + flags &= ~Qt::WindowMaximizeButtonHint; + subWindow->setWindowFlags(flags); + subWindow->setFixedSize(subWindow->size()); + m_custom->setWindowTitle("Meter"); + m_custom->setModel(&model()->getCustomMeterModel()); + } + + m_custom->parentWidget()->show(); + model()->setTempoSync(TempoSyncKnobModel::SyncMode::Custom); +} + + +void TempoSyncBarModelEditor::updateTextDescription() +{ + TempoSyncKnobModel * tempoSyncModel = model(); + + auto const syncMode = tempoSyncModel->syncMode(); + + switch(syncMode) + { + case TempoSyncKnobModel::SyncMode::None: + m_tempoSyncDescription = tr("Tempo Sync"); + break; + case TempoSyncKnobModel::SyncMode::Custom: + m_tempoSyncDescription = tr("Custom ") + + "(" + + QString::number(tempoSyncModel->getCustomMeterModel().numeratorModel().value()) + + "/" + + QString::number(tempoSyncModel->getCustomMeterModel().denominatorModel().value()) + + ")"; + break; + case TempoSyncKnobModel::SyncMode::DoubleWholeNote: + m_tempoSyncDescription = tr("Synced to Eight Beats"); + break; + case TempoSyncKnobModel::SyncMode::WholeNote: + m_tempoSyncDescription = tr("Synced to Whole Note"); + break; + case TempoSyncKnobModel::SyncMode::HalfNote: + m_tempoSyncDescription = tr("Synced to Half Note"); + break; + case TempoSyncKnobModel::SyncMode::QuarterNote: + m_tempoSyncDescription = tr("Synced to Quarter Note"); + break; + case TempoSyncKnobModel::SyncMode::EighthNote: + m_tempoSyncDescription = tr("Synced to 8th Note"); + break; + case TempoSyncKnobModel::SyncMode::SixteenthNote: + m_tempoSyncDescription = tr("Synced to 16th Note"); + break; + case TempoSyncKnobModel::SyncMode::ThirtysecondNote: + m_tempoSyncDescription = tr("Synced to 32nd Note"); + break; + default: ; + } +} + +void TempoSyncBarModelEditor::updateIcon() +{ + switch(model()->syncMode()) + { + case TempoSyncKnobModel::SyncMode::None: + m_tempoSyncIcon = embed::getIconPixmap("tempo_sync"); + break; + case TempoSyncKnobModel::SyncMode::Custom: + m_tempoSyncIcon = embed::getIconPixmap("dont_know"); + break; + case TempoSyncKnobModel::SyncMode::DoubleWholeNote: + m_tempoSyncIcon = embed::getIconPixmap("note_double_whole"); + break; + case TempoSyncKnobModel::SyncMode::WholeNote: + m_tempoSyncIcon = embed::getIconPixmap("note_whole"); + break; + case TempoSyncKnobModel::SyncMode::HalfNote: + m_tempoSyncIcon = embed::getIconPixmap("note_half"); + break; + case TempoSyncKnobModel::SyncMode::QuarterNote: + m_tempoSyncIcon = embed::getIconPixmap("note_quarter"); + break; + case TempoSyncKnobModel::SyncMode::EighthNote: + m_tempoSyncIcon = embed::getIconPixmap("note_eighth"); + break; + case TempoSyncKnobModel::SyncMode::SixteenthNote: + m_tempoSyncIcon = embed::getIconPixmap("note_sixteenth"); + break; + case TempoSyncKnobModel::SyncMode::ThirtysecondNote: + m_tempoSyncIcon = embed::getIconPixmap("note_thirtysecond"); + break; + default: + qWarning("TempoSyncKnob::calculateTempoSyncTime:" + "invalid TempoSyncMode"); + break; + } +} + + +} // namespace lmms::gui diff --git a/src/lmmsconfig.h.in b/src/lmmsconfig.h.in index d130d6fc255..89db21a7bfb 100644 --- a/src/lmmsconfig.h.in +++ b/src/lmmsconfig.h.in @@ -23,6 +23,7 @@ #cmakedefine LMMS_HAVE_LV2 #cmakedefine LMMS_HAVE_SUIL #cmakedefine LMMS_HAVE_MP3LAME +#cmakedefine LMMS_HAVE_SNDFILE_MP3 #cmakedefine LMMS_HAVE_OGGVORBIS #cmakedefine LMMS_HAVE_OSS #cmakedefine LMMS_HAVE_SNDIO diff --git a/src/tracks/InstrumentTrack.cpp b/src/tracks/InstrumentTrack.cpp index 29fda075e9c..8804833eedf 100644 --- a/src/tracks/InstrumentTrack.cpp +++ b/src/tracks/InstrumentTrack.cpp @@ -727,7 +727,8 @@ bool InstrumentTrack::play( const TimePos & _start, const fpp_t _frames, // Handle automation: detuning for (const auto& processHandle : m_processHandles) { - processHandle->processTimePos(_start, m_pitchModel.value(), gui::GuiApplication::instance()->pianoRoll()->isRecording()); + processHandle->processTimePos( + _start, m_pitchModel.value(), gui::getGUI() && gui::getGUI()->pianoRoll()->isRecording()); } if ( clips.size() == 0 ) diff --git a/vcpkg.json b/vcpkg.json new file mode 100644 index 00000000000..48a3e3c28c3 --- /dev/null +++ b/vcpkg.json @@ -0,0 +1,77 @@ +{ + "$schema": "https://raw.githubusercontent.com/microsoft/vcpkg-tool/main/docs/vcpkg.schema.json", + "dependencies": [ + { + "name": "fftw3", + "default-features": false, + "features": [ + "sse", + "sse2", + "avx", + "avx2" + ] + }, + { + "name": "fltk", + "default-features": false + }, + { + "name": "fluidsynth", + "default-features": false, + "features": [ + "sndfile" + ] + }, + { + "name": "libogg", + "default-features": false + }, + { + "name": "libsamplerate", + "default-features": false + }, + { + "name": "libsndfile", + "default-features": false, + "features": [ + "external-libs", + "mpeg" + ] + }, + { + "name": "libstk", + "default-features": false + }, + { + "name": "libvorbis", + "default-features": false + }, + { + "name": "lilv", + "default-features": false + }, + { + "name": "lv2", + "default-features": false + }, + { + "name": "mp3lame", + "default-features": false + }, + { + "name": "portaudio", + "default-features": false + }, + { + "name": "sdl2", + "default-features": false, + "features": [ + "base" + ] + }, + { + "name": "zlib", + "default-features": false + } + ] +}