diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md deleted file mode 100644 index fcc875601..000000000 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ /dev/null @@ -1,36 +0,0 @@ ---- -name: Bug report -about: Create a report to help us improve -title: '' -labels: bug -assignees: '' - ---- - -# Please search the issue tracker for existing bug reports before submitting your own. Delete this line to confirm no similar report has been posted yet. - -### Bug Summary - -#### Steps to reproduce - -#### Expected behavior - -#### Actual behavior - -#### Screenshot - -#### Affected LMMS versions - - - -#### Logs -
- Click to expand -
-
-
-
diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 000000000..4cd1464d6 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,90 @@ +# yamllint disable-file rule:line-length +name: Bug Report +description: File a bug report to help us improve +labels: + - bug +body: + - type: input + id: system-information + attributes: + label: System Information + description: | + - The operating system you use to run LMMS. + - When relevant, also include your hardware information. + placeholder: ex. Fedora Linux 39, KDE Plasma 5.27.10 - 13th Gen Intel® Core™ i9-13950HX, 32GB RAM + validations: + required: true + - type: input + id: affected-version + attributes: + label: LMMS Version(s) + description: | + - The version of LMMS affected by the bug. + - Can be an official version number, nightly release identifier, or commit hash. + - The version number can be found under the Help > About menu. + placeholder: ex. 1.2.2, 1.3.0-alpha.1.518+gdd53bec31, 2d185df + validations: + required: true + - type: input + id: working-version + attributes: + label: Most Recent Working Version + description: | + - If there is a previous version of LMMS that did not exhibit the bug, include it here. + placeholder: ex. 1.2.2, 1.3.0-alpha.1.518+gdd53bec31, 2d185df + validations: + required: false + - type: textarea + id: bug-summary + attributes: + label: Bug Summary + description: Briefly describe the bug. + validations: + required: true + - type: textarea + id: expected-behaviour + attributes: + label: Expected Behaviour + description: Describe what should have happened. + validations: + required: true + - type: textarea + id: steps-to-reproduce + attributes: + label: Steps To Reproduce + description: | + - Describe the minimum set of steps required to reproduce this bug. + - If you included a minimum reproducible project below, you can describe here how it should be used. + validations: + required: true + - type: textarea + id: logs + attributes: + label: Logs + description: | + - Copy and paste any relevant log output here. + value: | +
+ Click to expand +
+          
+        
+
+ validations: + required: false + - type: textarea + id: supporting-files + attributes: + label: Screenshots / Minimum Reproducible Project + description: | + - Upload any screenshots showing the bug in action. + - If possible, also include a .mmp/.mmpz project containing the simplest possible setup needed to reproduce the bug. + + ***Note:** To upload a project file to GitHub, it will need to be placed in a .zip archive.* + - type: checkboxes + id: search-for-existing + attributes: + label: Please search the issue tracker for existing bug reports before submitting your own. + options: + - label: I have searched all existing issues and confirmed that this is not a duplicate. + required: true diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md deleted file mode 100644 index f9a0ae192..000000000 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ /dev/null @@ -1,18 +0,0 @@ ---- -name: Feature request -about: Suggest an idea for this project -title: '' -labels: enhancement -assignees: '' - ---- - -# Please search the issue tracker for existing feature requests before submitting your own. Delete this line to confirm no similar request has been posted yet. - -### Enhancement Summary - -#### Justification - -#### Mockup - - diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml new file mode 100644 index 000000000..1f11b4eb3 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -0,0 +1,31 @@ +# yamllint disable-file rule:line-length +name: Feature Request +description: Suggest an idea for the project +labels: + - "enhancement" +body: + - type: textarea + id: enhancement-summary + attributes: + label: Enhancement Summary + description: | + - Briefly describe the enhancement. + - Explain why you believe the proposed enhancement to be a good idea, and (if applicable) how it helps overcome a limitation of LMMS you are currently facing. + validations: + required: true + - type: textarea + id: mockup + attributes: + label: Implementation Details / Mockup + description: | + - Explain how you believe this enhancement should be implemented. + - If your proposal encompasses changes to the user interface, include diagrams displaying your intent. + validations: + required: true + - type: checkboxes + id: search-for-existing + attributes: + label: Please search the issue tracker for existing feature requests before submitting your own. + options: + - label: I have searched all existing issues and confirmed that this is not a duplicate. + required: true diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 007842b82..b80d5df75 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -8,21 +8,18 @@ jobs: linux: name: linux runs-on: ubuntu-latest - container: lmmsci/linux.gcc:18.04 + container: ghcr.io/lmms/linux.gcc:20.04 env: CMAKE_OPTS: >- -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 - run: | - add-apt-repository ppa:git-core/ppa - apt-get update - apt-get --yes install git - git config --global --add safe.directory "$GITHUB_WORKSPACE" + - name: Configure git + run: git config --global --add safe.directory "$GITHUB_WORKSPACE" - name: Check out uses: actions/checkout@v3 with: @@ -38,47 +35,72 @@ 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 - name: Build run: cmake --build build - - name: Build tests - run: cmake --build build --target tests - name: Run tests - run: build/tests/tests + run: | + cd build/tests + ctest --output-on-failure -j2 - name: Package run: | cmake --build build --target install cmake --build build --target appimage - name: Upload artifacts - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 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 --show-config echo "[ccache stats]" - ccache -s + ccache --show-stats + env: + CCACHE_MAXSIZE: 500M macos: - name: macos - runs-on: macos-11 + strategy: + fail-fast: false + matrix: + arch: [ x86_64, arm64 ] + include: + - arch: x86_64 + os: macos-12 + xcode: "13.1" + - arch: arm64 + os: macos-14 + xcode: "14.3.1" + name: macos-${{ matrix.arch }} + runs-on: ${{ matrix.os }} 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_${{ matrix.xcode }}.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,40 +111,53 @@ 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 \ -DCMAKE_INSTALL_PREFIX="../target" \ - -DCMAKE_PREFIX_PATH="$(brew --prefix qt5)" \ + -DCMAKE_PREFIX_PATH="$(brew --prefix qt@5)" \ + -DCMAKE_OSX_ARCHITECTURES=${{ matrix.arch }} \ $CMAKE_OPTS \ -DUSE_WERROR=OFF - name: Build run: cmake --build build - - name: Build tests - run: cmake --build build --target tests - name: Run tests - run: build/tests/tests + run: | + cd build/tests + ctest --output-on-failure -j3 - name: Package run: | cmake --build build --target install cmake --build build --target dmg - name: Upload artifacts - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: - name: macos + name: macos-${{ matrix.arch }} 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 @@ -130,21 +165,18 @@ jobs: arch: ['32', '64'] name: mingw${{ matrix.arch }} runs-on: ubuntu-latest - container: lmmsci/linux.mingw${{ matrix.arch }}:18.04 + container: ghcr.io/lmms/linux.mingw:20.04 env: CMAKE_OPTS: >- -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 - run: | - add-apt-repository ppa:git-core/ppa - apt-get update - apt-get --yes install git - git config --global --add safe.directory "$GITHUB_WORKSPACE" + - name: Configure git + run: git config --global --add safe.directory "$GITHUB_WORKSPACE" - name: Check out uses: actions/checkout@v3 with: @@ -161,25 +193,27 @@ jobs: path: ~/.ccache - name: Configure run: | + ccache --zero-stats mkdir build && cd build ../cmake/build_win${{ matrix.arch }}.sh - name: Build run: cmake --build build - - name: Build tests - run: cmake --build build --target tests - name: Package run: cmake --build build --target package - name: Upload artifacts - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 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 --show-config echo "[ccache stats]" - ccache -s + ccache --show-stats + env: + CCACHE_MAXSIZE: 500M msvc: strategy: fail-fast: false @@ -188,7 +222,8 @@ jobs: name: msvc-${{ matrix.arch }} 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,57 +231,78 @@ 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 - - name: Install 64-bit Qt - if: matrix.arch == 'x64' - uses: jurplel/install-qt-action@64bdb64f2c14311d23733a8463e5fcbc65e8775e + path: build\vcpkg_installed + - name: Cache ccache data + uses: actions/cache@v3 with: - version: ${{ env.qt-version }} - arch: win64_msvc2019_64 + 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 Qt + uses: jurplel/install-qt-action@b3ea5275e37b734d027040e2c7fe7a10ea2ef946 + with: + version: '5.15.2' + arch: |- + ${{ + fromJSON(' + { + "x86": "win32_msvc2019", + "x64": "win64_msvc2019_64" + } + ')[matrix.arch] + }} archives: qtbase qtsvg qttools cache: true - - name: Install 32-bit Qt - uses: jurplel/install-qt-action@64bdb64f2c14311d23733a8463e5fcbc65e8775e - 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 - run: cmake --build build --target tests + - name: Run tests + run: | + cd build/tests + ctest --output-on-failure -j2 - name: Package run: cmake --build build --target package - name: Upload artifacts - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 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 ee289379f..cc2823ba0 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,5 @@ -/build -/target +/build/ +/target/ .*.sw? .DS_Store *~ @@ -9,3 +9,4 @@ /plugins/ZynAddSubFx/zynaddsubfx/doc/Makefile /plugins/ZynAddSubFx/zynaddsubfx/doc/gen/Makefile /data/locale/*.qm +Brewfile.lock.json diff --git a/.gitmodules b/.gitmodules index fa6980ac5..1c43fd1b3 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,15 +1,12 @@ [submodule "src/3rdparty/qt5-x11embed"] path = src/3rdparty/qt5-x11embed url = https://github.com/Lukas-W/qt5-x11embed.git -[submodule "src/3rdparty/rpmalloc/rpmalloc"] - path = src/3rdparty/rpmalloc/rpmalloc - url = https://github.com/mjansson/rpmalloc.git [submodule "plugins/ZynAddSubFx/zynaddsubfx"] path = plugins/ZynAddSubFx/zynaddsubfx url = https://github.com/lmms/zynaddsubfx.git [submodule "plugins/FreeBoy/game-music-emu"] path = plugins/FreeBoy/game-music-emu - url = https://bitbucket.org/mpyne/game-music-emu.git + url = https://github.com/libgme/game-music-emu.git [submodule "plugins/OpulenZ/adplug"] path = plugins/OpulenZ/adplug url = https://github.com/adplug/adplug.git @@ -24,7 +21,7 @@ url = https://github.com/swh/ladspa [submodule "plugins/LadspaEffect/tap/tap-plugins"] path = plugins/LadspaEffect/tap/tap-plugins - url = https://github.com/tomszilagyi/tap-plugins + url = https://github.com/lmms/tap-plugins [submodule "src/3rdparty/weakjack/weakjack"] path = src/3rdparty/weakjack/weakjack url = https://github.com/x42/weakjack.git diff --git a/Brewfile b/Brewfile new file mode 100644 index 000000000..1bfbd7b01 --- /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 0b6d3b4ff..1da10775b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,20 +1,31 @@ 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}) SET(LMMS_BINARY_DIR ${CMAKE_BINARY_DIR}) SET(LMMS_SOURCE_DIR ${CMAKE_SOURCE_DIR}) -# CMAKE_POLICY Section -IF(COMMAND CMAKE_POLICY) - # TODO: Keep CMP0074 but remove this condition when cmake 3.12+ is guaranteed - IF(${CMAKE_VERSION} VERSION_GREATER_EQUAL 3.12) - # Needed for the SWH Ladspa plugins. See below. - CMAKE_POLICY(SET CMP0074 NEW) # find_package() uses _ROOT variables - ENDIF() -ENDIF(COMMAND CMAKE_POLICY) - # Import of windows.h breaks min()/max() ADD_DEFINITIONS(-DNOMINMAX) @@ -35,7 +46,7 @@ INCLUDE(GenerateExportHeader) STRING(TOUPPER "${CMAKE_PROJECT_NAME}" PROJECT_NAME_UCASE) -SET(PROJECT_YEAR 2023) +SET(PROJECT_YEAR 2024) SET(PROJECT_AUTHOR "LMMS Developers") SET(PROJECT_URL "https://lmms.io") @@ -60,6 +71,7 @@ INCLUDE(VersionInfo) INCLUDE(DetectMachine) OPTION(WANT_ALSA "Include ALSA (Advanced Linux Sound Architecture) support" ON) +OPTION(WANT_OSS "Include Open Sound System support" ON) OPTION(WANT_CALF "Include CALF LADSPA plugins" ON) OPTION(WANT_CAPS "Include C* Audio Plugin Suite (LADSPA plugins)" ON) OPTION(WANT_CARLA "Include Carla plugin" ON) @@ -82,8 +94,8 @@ OPTION(WANT_STK "Include Stk (Synthesis Toolkit) support" ON) OPTION(WANT_SWH "Include Steve Harris's LADSPA plugins" ON) OPTION(WANT_TAP "Include Tom's Audio Processing LADSPA plugins" ON) OPTION(WANT_VST "Include VST support" ON) -OPTION(WANT_VST_32 "Include 32-bit VST support" ON) -OPTION(WANT_VST_64 "Include 64-bit VST support" ON) +OPTION(WANT_VST_32 "Include 32-bit Windows VST support" ON) +OPTION(WANT_VST_64 "Include 64-bit Windows VST support" ON) OPTION(WANT_WINMM "Include WinMM MIDI support" OFF) OPTION(WANT_DEBUG_FPE "Debug floating point exceptions" OFF) option(WANT_DEBUG_ASAN "Enable AddressSanitizer" OFF) @@ -98,9 +110,11 @@ IF(LMMS_BUILD_APPLE) LINK_DIRECTORIES("${APPLE_PREFIX}/lib") SET(WANT_SOUNDIO OFF) SET(WANT_ALSA OFF) + SET(WANT_OSS OFF) SET(WANT_PULSEAUDIO OFF) SET(WANT_VST OFF) SET(STATUS_ALSA "") + SET(STATUS_OSS "") SET(STATUS_PULSEAUDIO "") SET(STATUS_APPLEMIDI "OK") ELSE(LMMS_BUILD_APPLE) @@ -110,13 +124,18 @@ ENDIF(LMMS_BUILD_APPLE) IF(LMMS_BUILD_WIN32) SET(WANT_ALSA OFF) + SET(WANT_OSS OFF) SET(WANT_PULSEAUDIO OFF) SET(WANT_SNDIO OFF) SET(WANT_SOUNDIO OFF) SET(WANT_WINMM ON) SET(BUNDLE_QT_TRANSLATIONS ON) SET(LMMS_HAVE_WINMM TRUE) + if(NOT LMMS_BUILD_WIN64) + set(WANT_VST_64 OFF) + endif() SET(STATUS_ALSA "") + SET(STATUS_OSS "") SET(STATUS_PULSEAUDIO "") SET(STATUS_SOUNDIO "") SET(STATUS_SNDIO "") @@ -126,16 +145,6 @@ ELSE(LMMS_BUILD_WIN32) SET(STATUS_WINMM "") ENDIF(LMMS_BUILD_WIN32) - -# TODO: Fix linking issues with msys debug builds -IF(LMMS_BUILD_MSYS AND CMAKE_BUILD_TYPE STREQUAL "Debug") - SET(WANT_GIG OFF) - SET(WANT_STK OFF) - SET(WANT_SWH OFF) - SET(STATUS_GIG "not built as requested") - SET(STATUS_STK "not built as requested") -ENDIF() - SET(CMAKE_CXX_STANDARD_REQUIRED ON) CHECK_INCLUDE_FILES(pthread.h LMMS_HAVE_PTHREAD_H) @@ -160,7 +169,7 @@ check_library_exists(rt shm_open "" LMMS_HAVE_LIBRT) LIST(APPEND CMAKE_PREFIX_PATH "${CMAKE_INSTALL_PREFIX}") -FIND_PACKAGE(Qt5 5.6.0 COMPONENTS Core Gui Widgets Xml REQUIRED) +FIND_PACKAGE(Qt5 5.9.0 COMPONENTS Core Gui Widgets Xml REQUIRED) FIND_PACKAGE(Qt5 COMPONENTS LinguistTools QUIET) INCLUDE_DIRECTORIES( @@ -201,7 +210,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 @@ -428,8 +444,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") @@ -448,13 +462,13 @@ IF(WANT_OGGVORBIS) ENDIF(WANT_OGGVORBIS) -# check whether to enable OSS-support -IF(LMMS_HAVE_SOUNDCARD_H OR LMMS_HAVE_SYS_SOUNDCARD_H) +# check for OSS +IF(WANT_OSS AND (LMMS_HAVE_SOUNDCARD_H OR LMMS_HAVE_SYS_SOUNDCARD_H)) SET(LMMS_HAVE_OSS TRUE) SET(STATUS_OSS "OK") -ELSE(LMMS_HAVE_SOUNDCARD_H OR LMMS_HAVE_SYS_SOUNDCARD_H) +ELSEIF(WANT_OSS) SET(STATUS_OSS "") -ENDIF(LMMS_HAVE_SOUNDCARD_H OR LMMS_HAVE_SYS_SOUNDCARD_H) +ENDIF() # check for ALSA @@ -524,7 +538,7 @@ ENDIF() # check for Fluidsynth IF(WANT_SF2) - find_package(FluidSynth 1.1.0) + find_package(FluidSynth 1.1.7) if(FluidSynth_FOUND) SET(LMMS_HAVE_FLUIDSYNTH TRUE) if(FluidSynth_VERSION_STRING VERSION_GREATER_EQUAL 2) @@ -566,26 +580,42 @@ IF(WANT_SNDIO) ENDIF(WANT_SNDIO) # check for WINE -IF(WANT_VST) - FIND_PACKAGE(Wine) - IF(WINE_FOUND) - SET(LMMS_SUPPORT_VST TRUE) - IF(WINE_LIBRARY_FIX) - SET(STATUS_VST "OK, with workaround linking ${WINE_LIBRARY_FIX}") - ELSE() - SET(STATUS_VST "OK") - ENDIF() - ELSEIF(WANT_VST_NOWINE) - SET(LMMS_SUPPORT_VST TRUE) - SET(STATUS_VST "OK") - ELSE(WINE_FOUND) - SET(STATUS_VST "not found, please install (lib)wine-dev (or similar) - 64 bit systems additionally need gcc-multilib and g++-multilib") - ENDIF(WINE_FOUND) -ENDIF(WANT_VST) -IF(LMMS_BUILD_WIN32) - SET(LMMS_SUPPORT_VST TRUE) - SET(STATUS_VST "OK") -ENDIF(LMMS_BUILD_WIN32) +if(WANT_VST) + if((WANT_VST_32 OR WANT_VST_64) AND NOT LMMS_BUILD_WIN32) + find_package(Wine) + include(CheckWineGcc) + endif() + macro(check_vst bits) + if(NOT WANT_VST_${bits}) + set(STATUS_VST_${bits} "Not built, as requested") + elseif(LMMS_BUILD_WIN32) + set(STATUS_VST_${bits} "OK") + set(LMMS_HAVE_VST_${bits} TRUE) + elseif(NOT WINE_FOUND) + set(STATUS_VST_${bits} "not found, please install (lib)wine-dev (or similar) - 64 bit systems additionally need gcc-multilib and g++-multilib") + else() + CheckWineGcc("${bits}" "${WINEGCC}" WINEGCC_WORKING) + if(WINEGCC_WORKING) + set(LMMS_HAVE_VST_${bits} TRUE) + if(WINE_LIBRARY_FIX) + set(STATUS_VST_${bits} "OK, with workaround linking ${WINE_LIBRARY_FIX}") + else() + set(STATUS_VST_${bits} "OK") + endif() + else() + set(STATUS_VST_${bits} "winegcc fails to compile ${bits}-bit binaries, please make sure you have ${bits}-bit GCC libraries") + endif() + endif() + endmacro() + check_vst(32) + check_vst(64) + if(LMMS_HAVE_VST_32 OR LMMS_HAVE_VST_64 OR LMMS_BUILD_LINUX) + set(LMMS_HAVE_VST TRUE) + set(STATUS_VST "OK") + else() + set(STATUS_VST "No hosts selected and available") + endif() +endif() IF(WANT_DEBUG_FPE) IF(LMMS_BUILD_LINUX OR LMMS_BUILD_APPLE) @@ -615,6 +645,7 @@ IF(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang") ENDIF() ELSEIF(MSVC) # Remove any existing /W flags + string(REGEX REPLACE "/W[0-4]" "" CMAKE_C_FLAGS ${CMAKE_C_FLAGS}) STRING(REGEX REPLACE "/W[0-4]" "" CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS}) SET(WERROR_FLAGS "/W2") IF(${USE_WERROR}) @@ -831,8 +862,9 @@ MESSAGE( "* SoundFont2 player : ${STATUS_FLUIDSYNTH}\n" "* Sid instrument : ${STATUS_SID}\n" "* Stk Mallets : ${STATUS_STK}\n" -"* VST-instrument hoster : ${STATUS_VST}\n" -"* VST-effect hoster : ${STATUS_VST}\n" +"* VST plugin host : ${STATUS_VST}\n" +" * 32-bit Windows host : ${STATUS_VST_32}\n" +" * 64-bit Windows host : ${STATUS_VST_64}\n" "* CALF LADSPA plugins : ${STATUS_CALF}\n" "* CAPS LADSPA plugins : ${STATUS_CAPS}\n" "* CMT LADSPA plugins : ${STATUS_CMT}\n" diff --git a/cmake/apple/CMakeLists.txt b/cmake/apple/CMakeLists.txt index 0b66689e7..3fd0a4da4 100644 --- a/cmake/apple/CMakeLists.txt +++ b/cmake/apple/CMakeLists.txt @@ -19,8 +19,19 @@ CONFIGURE_FILE("lmms.plist.in" "${CMAKE_BINARY_DIR}/Info.plist") CONFIGURE_FILE("install_apple.sh.in" "${CMAKE_BINARY_DIR}/install_apple.sh" @ONLY) CONFIGURE_FILE("package_apple.json.in" "${CMAKE_BINARY_DIR}/package_apple.json" @ONLY) +IF(CMAKE_OSX_ARCHITECTURES) + SET(DMG_ARCH "${CMAKE_OSX_ARCHITECTURES}") +ELSEIF(IS_ARM64) + # Target arch is host arch + SET(DMG_ARCH "arm64") +ELSE() + # Fallback to Intel + SET(DMG_ARCH "x86_64") +ENDIF() + # DMG creation target -SET(DMG_FILE "${CMAKE_BINARY_DIR}/${CMAKE_PROJECT_NAME}-${VERSION}-mac${APPLE_OS_VER}.dmg") +SET(DMG_FILE "${CMAKE_BINARY_DIR}/${CMAKE_PROJECT_NAME}-${VERSION}-mac${APPLE_OS_VER}-${DMG_ARCH}.dmg") + FILE(REMOVE "${DMG_FILE}") ADD_CUSTOM_TARGET(removedmg COMMAND touch "\"${DMG_FILE}\"" && rm "\"${DMG_FILE}\"" diff --git a/cmake/build_win32.sh b/cmake/build_win32.sh index 33cd8ecce..bccda3a48 100755 --- a/cmake/build_win32.sh +++ b/cmake/build_win32.sh @@ -31,7 +31,7 @@ fi export PATH=$MINGW/bin:$PATH export CXXFLAGS="$CFLAGS" if [ "$ARCH" == "32" ]; then - export CFLAGS="-march=pentium3 -mtune=generic -mpreferred-stack-boundary=5 -mfpmath=sse" + export CFLAGS="-march=pentium4 -mtune=generic -mpreferred-stack-boundary=5 -mfpmath=sse" fi CMAKE_OPTS="-DCMAKE_PREFIX_PATH=$MINGW $CMAKE_OPTS" diff --git a/cmake/install/excludelist-win b/cmake/install/excludelist-win index 17793a113..ac3c47901 100644 --- a/cmake/install/excludelist-win +++ b/cmake/install/excludelist-win @@ -3,17 +3,21 @@ ADVAPI32.dll COMCTL32.dll comdlg32.dll +d3d11.dll dwmapi.dll +dxgi.dll GDI32.dll IMM32.dll KERNEL32.dll MPR.DLL msvcrt.dll +netapi32.dll ole32.dll OLEAUT32.dll OPENGL32.DLL SHELL32.dll USER32.dll +userenv.dll UxTheme.dll VERSION.dll WINMM.DLL diff --git a/cmake/modules/BuildPlugin.cmake b/cmake/modules/BuildPlugin.cmake index f8b3d3153..aaccd3c4b 100644 --- a/cmake/modules/BuildPlugin.cmake +++ b/cmake/modules/BuildPlugin.cmake @@ -1,12 +1,12 @@ # BuildPlugin.cmake - Copyright (c) 2008 Tobias Doerffel # # description: build LMMS-plugin -# usage: BUILD_PLUGIN( MOCFILES EMBEDDED_RESOURCES UICFILES LINK ) +# usage: BUILD_PLUGIN( MOCFILES EMBEDDED_RESOURCES LINK ) INCLUDE(GenQrc) MACRO(BUILD_PLUGIN PLUGIN_NAME) - CMAKE_PARSE_ARGUMENTS(PLUGIN "" "LINK;EXPORT_BASE_NAME" "MOCFILES;EMBEDDED_RESOURCES;UICFILES" ${ARGN}) + CMAKE_PARSE_ARGUMENTS(PLUGIN "" "LINK;EXPORT_BASE_NAME" "MOCFILES;EMBEDDED_RESOURCES" ${ARGN}) SET(PLUGIN_SOURCES ${PLUGIN_UNPARSED_ARGUMENTS}) INCLUDE_DIRECTORIES("${CMAKE_CURRENT_BINARY_DIR}" "${CMAKE_BINARY_DIR}" "${CMAKE_SOURCE_DIR}/include") @@ -31,10 +31,9 @@ MACRO(BUILD_PLUGIN PLUGIN_NAME) ENDIF(ER_LEN) QT5_WRAP_CPP(plugin_MOC_out ${PLUGIN_MOCFILES}) - QT5_WRAP_UI(plugin_UIC_out ${PLUGIN_UICFILES}) FOREACH(f ${PLUGIN_SOURCES}) - ADD_FILE_DEPENDENCIES(${f} ${RCC_OUT} ${plugin_UIC_out}) + ADD_FILE_DEPENDENCIES(${f} ${RCC_OUT}) ENDFOREACH(f) IF(LMMS_BUILD_APPLE) @@ -45,10 +44,6 @@ MACRO(BUILD_PLUGIN PLUGIN_NAME) LINK_DIRECTORIES("${CMAKE_BINARY_DIR}" "${CMAKE_SOURCE_DIR}") LINK_LIBRARIES(${QT_LIBRARIES}) ENDIF(LMMS_BUILD_WIN32) - IF(LMMS_BUILD_MSYS AND CMAKE_BUILD_TYPE STREQUAL "Debug") - # Override Qt debug libraries with release versions - SET(QT_LIBRARIES "${QT_OVERRIDE_LIBRARIES}") - ENDIF() IF (NOT PLUGIN_LINK) SET(PLUGIN_LINK "MODULE") @@ -56,11 +51,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 +61,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 ed4622bd9..56486e24f 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") +if(NOT USE_COMPILE_CACHE) + return() +endif() + +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() - 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") - 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/DetectMachine.cmake b/cmake/modules/DetectMachine.cmake index 388efeb82..b9aa4c8c6 100644 --- a/cmake/modules/DetectMachine.cmake +++ b/cmake/modules/DetectMachine.cmake @@ -12,11 +12,6 @@ ELSE() SET(LMMS_BUILD_LINUX 1) ENDIF(WIN32) -# LMMS_BUILD_MSYS also set in build_winXX.sh -IF(LMMS_BUILD_WIN32 AND CMAKE_COMPILER_IS_GNUCXX AND DEFINED ENV{MSYSCON}) - SET(LMMS_BUILD_MSYS TRUE) -ENDIF() - MESSAGE("PROCESSOR: ${CMAKE_SYSTEM_PROCESSOR}") SET(LMMS_HOST_X86 FALSE) SET(LMMS_HOST_X86_64 FALSE) @@ -92,7 +87,7 @@ IF(WIN32) endif() ELSE() # Detect target architecture based on compiler target triple e.g. "x86_64-pc-linux" - EXEC_PROGRAM( ${CMAKE_C_COMPILER} ARGS "-dumpmachine ${CMAKE_C_FLAGS}" OUTPUT_VARIABLE Machine ) + execute_process(COMMAND ${CMAKE_C_COMPILER} -dumpmachine ${CMAKE_C_FLAGS} OUTPUT_VARIABLE Machine) MESSAGE("Machine: ${Machine}") STRING(REGEX MATCH "i.86" IS_X86 "${Machine}") STRING(REGEX MATCH "86_64|amd64" IS_X86_64 "${Machine}") diff --git a/cmake/modules/FindFluidSynth.cmake b/cmake/modules/FindFluidSynth.cmake index fcc00cd7d..70c40b8d8 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 c3fb09c5b..3017dc5aa 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) +find_package_config_mode_with_fallback(mp3lame mp3lame::mp3lame + LIBRARY_NAMES "mp3lame" + INCLUDE_NAMES "lame/lame.h" + PREFIX Lame +) - list(APPEND Lame_DEFINITIONS HAVE_LIBMP3LAME=1) +determine_version_from_source(Lame_VERSION mp3lame::mp3lame [[ + #include + #include - mark_as_advanced(Lame_INCLUDE_DIRS Lame_LIBRARIES Lame_DEFINITIONS) - - if(Lame_LIBRARIES AND Lame_INCLUDE_DIRS) - add_library(mp3lame::mp3lame UNKNOWN IMPORTED) - - 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 79a9ab406..cfbd73256 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(ImportedTargetHelpers) -include (CheckLibraryExists) +find_package_config_mode_with_fallback(Ogg Ogg::ogg + LIBRARY_NAMES "ogg" + INCLUDE_NAMES "ogg/ogg.h" + PKG_CONFIG ogg +) -find_path(VORBIS_INCLUDE_DIR vorbis/vorbisfile.h) -find_path(OGG_INCLUDE_DIR ogg/ogg.h) +find_package_config_mode_with_fallback(Vorbis Vorbis::vorbis + LIBRARY_NAMES "vorbis" + INCLUDE_NAMES "vorbis/codec.h" + PKG_CONFIG vorbis + DEPENDS Ogg::ogg +) -find_library(OGG_LIBRARY NAMES ogg) -find_library(VORBIS_LIBRARY NAMES vorbis) -find_library(VORBISFILE_LIBRARY NAMES vorbisfile) -find_library(VORBISENC_LIBRARY NAMES vorbisenc) +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 +) -if (VORBIS_INCLUDE_DIR AND VORBIS_LIBRARY AND VORBISFILE_LIBRARY AND VORBISENC_LIBRARY) - set(OGGVORBIS_FOUND TRUE) +determine_version_from_source(Vorbis_VERSION Vorbis::vorbis [[ + #include + #include + #include - set(OGGVORBIS_LIBRARIES ${OGG_LIBRARY} ${VORBIS_LIBRARY} ${VORBISFILE_LIBRARY} ${VORBISENC_LIBRARY}) + 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); + } +]]) - 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(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 f9c7699f4..e7cfa1383 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_package_config_mode_with_fallback(portaudio portaudio + LIBRARY_NAMES "portaudio" + INCLUDE_NAMES "portaudio.h" + PKG_CONFIG portaudio-2.0 + PREFIX Portaudio +) - # Find the library and headers using the results from PkgConfig as a guide - find_library(Portaudio_LIBRARY - NAMES "portaudio" - HINTS ${PORTAUDIO_PKG_LIBRARY_DIRS} - ) +determine_version_from_source(Portaudio_VERSION portaudio [[ + #include + #include "portaudio.h" - find_path(Portaudio_INCLUDE_DIR - NAMES "portaudio.h" - HINTS ${PORTAUDIO_PKG_INCLUDE_DIRS} - ) - - # 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/FindSDL2.cmake b/cmake/modules/FindSDL2.cmake index 3bad1002e..6e07f7aff 100644 --- a/cmake/modules/FindSDL2.cmake +++ b/cmake/modules/FindSDL2.cmake @@ -33,6 +33,18 @@ find_package(SDL2 CONFIG QUIET) if(TARGET SDL2::SDL2) + # SDL2::SDL2 under MinGW is an interface target for reasons, so we can't get + # the library location from it. Print minimal information and return early. + get_target_property(sdl2_target_type SDL2::SDL2 TYPE) + if(sdl2_target_type STREQUAL "INTERFACE_LIBRARY") + unset(sdl2_target_type) + if(NOT SDL2_FIND_QUIETLY) + message(STATUS "Found SDL2 (found version \"${SDL2_VERSION}\")") + endif() + return() + endif() + unset(sdl2_target_type) + # Extract details for find_package_handle_standard_args get_target_property(SDL2_LIBRARY SDL2::SDL2 LOCATION) get_target_property(SDL2_INCLUDE_DIR SDL2::SDL2 INTERFACE_INCLUDE_DIRECTORIES) diff --git a/cmake/modules/FindSTK.cmake b/cmake/modules/FindSTK.cmake index 0718f5039..5564d24f8 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 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 ) - - find_library(STK_LIBRARY - NAMES stk - PATH /usr/lib /usr/local/lib "${CMAKE_INSTALL_PREFIX}/lib" "${CMAKE_FIND_ROOT_PATH}/lib" - ) - - 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 53b69f6c7..683748c59 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 28ebb7bb7..d69fa6331 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 + + 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); + } +]]) + +include(FindPackageHandleStandardArgs) + +find_package_handle_standard_args(SndFile + REQUIRED_VARS SndFile_LIBRARY SndFile_INCLUDE_DIRS + VERSION_VAR SndFile_VERSION ) - -find_package(PackageHandleStandardArgs) -find_package_handle_standard_args(SndFile DEFAULT_MSG SNDFILE_LIBRARY SNDFILE_INCLUDE_DIR) - -set(SNDFILE_LIBRARIES ${SNDFILE_LIBRARY}) -set(SNDFILE_INCLUDE_DIRS ${SNDFILE_INCLUDE_DIR}) - -mark_as_advanced(SNDFILE_LIBRARY SNDFILE_LIBRARIES SNDFILE_INCLUDE_DIR SNDFILE_INCLUDE_DIRS) diff --git a/cmake/modules/FindWine.cmake b/cmake/modules/FindWine.cmake index 024dac1ea..19a0cffbb 100644 --- a/cmake/modules/FindWine.cmake +++ b/cmake/modules/FindWine.cmake @@ -2,8 +2,15 @@ # Once done this will define # # WINE_FOUND - System has wine -# WINE_INCLUDE_DIRS - The wine include directories -# WINE_DEFINITIONS - Compiler switches required for using wine +# +# WINE_INCLUDE_DIR - Wine include directory +# WINE_BUILD - Path to winebuild +# WINE_CXX - Path to wineg++ +# WINE_GCC - Path to winegcc +# WINE_32_LIBRARY_DIRS - Path(s) to 32-bit wine libs +# WINE_32_FLAGS - 32-bit linker flags +# WINE_64_LIBRARY_DIRS - Path(s) to 64-bit wine libs +# WINE_64_FLAGS - 64-bit linker flags # MACRO(_findwine_find_flags output expression result) @@ -23,17 +30,49 @@ MACRO(_regex_replace_foreach EXPRESSION REPLACEMENT RESULT INPUT) ENDFOREACH() ENDMACRO() -LIST(APPEND CMAKE_PREFIX_PATH /opt/wine-stable /opt/wine-devel /opt/wine-staging /usr/lib/wine/) +# Prefer newest wine first +list(APPEND WINE_LOCATIONS + /opt/wine-staging + /opt/wine-devel + /opt/wine-stable + /usr/lib/wine) -FIND_PROGRAM(WINE_CXX - NAMES wineg++ winegcc winegcc64 winegcc32 winegcc-stable - PATHS /usr/lib/wine +# Prepare bin search +foreach(_loc ${WINE_LOCATIONS}) + if(_loc STREQUAL /usr/lib/wine) + # /usr/lib/wine doesn't have a "bin" + list(APPEND WINE_CXX_LOCATIONS "${_loc}") + else() + # expect "bin" + list(APPEND WINE_CXX_LOCATIONS "${_loc}/bin") + endif() +endforeach() +# Fallback +list(APPEND WINE_CXX_LOCATIONS "/usr/bin") + +# Prefer most-common to least common +FIND_PROGRAM(WINE_CXX NAMES + wineg++ + wineg++-stable + PATHS + ${WINE_CXX_LOCATIONS} + NO_DEFAULT_PATH ) -FIND_PROGRAM(WINE_BUILD NAMES winebuild) + +FIND_PROGRAM(WINE_GCC NAMES + winegcc + winegcc-stable + PATHS + ${WINE_CXX_LOCATIONS} + NO_DEFAULT_PATH +) + +FIND_PROGRAM(WINE_BUILD NAMES winebuild PATHS ${WINE_CXX_LOCATIONS} NO_DEFAULT_PATH) # Detect wine paths and handle linking problems IF(WINE_CXX) - EXEC_PROGRAM(${WINE_CXX} ARGS "-m32 -v /dev/zero" OUTPUT_VARIABLE WINEBUILD_OUTPUT_32) - EXEC_PROGRAM(${WINE_CXX} ARGS "-m64 -v /dev/zero" OUTPUT_VARIABLE WINEBUILD_OUTPUT_64) + # call wineg++ to obtain implied includes and libs + execute_process(COMMAND ${WINE_CXX} -m32 -v /dev/zero OUTPUT_VARIABLE WINEBUILD_OUTPUT_32 ERROR_QUIET) + execute_process(COMMAND ${WINE_CXX} -m64 -v /dev/zero OUTPUT_VARIABLE WINEBUILD_OUTPUT_64 ERROR_QUIET) _findwine_find_flags("${WINEBUILD_OUTPUT_32}" "^-isystem/usr/include$" BUGGED_WINEGCC) _findwine_find_flags("${WINEBUILD_OUTPUT_32}" "^-isystem" WINEGCC_INCLUDE_DIR) _findwine_find_flags("${WINEBUILD_OUTPUT_32}" "libwinecrt0\\.a.*" WINECRT_32) @@ -42,6 +81,9 @@ IF(WINE_CXX) _regex_replace_foreach("/wine/windows$" "" WINE_INCLUDE_HINT "${WINE_INCLUDE_HINT}") STRING(REGEX REPLACE "wine/libwinecrt0\\.a.*" "" WINE_32_LIBRARY_DIR "${WINECRT_32}") STRING(REGEX REPLACE "wine/libwinecrt0\\.a.*" "" WINE_64_LIBRARY_DIR "${WINECRT_64}") + # Handle winehq + STRING(REGEX REPLACE "/libwinecrt0\\.a.*" "/" WINE_32_LIBRARY_DIR "${WINE_32_LIBRARY_DIR}") + STRING(REGEX REPLACE "/libwinecrt0\\.a.*" "/" WINE_64_LIBRARY_DIR "${WINE_64_LIBRARY_DIR}") IF(BUGGED_WINEGCC) MESSAGE(WARNING "Your winegcc is unusable due to https://bugs.winehq.org/show_bug.cgi?id=46293,\n @@ -98,7 +140,11 @@ find_package_handle_standard_args(Wine DEFAULT_MSG WINE_CXX WINE_INCLUDE_DIRS) mark_as_advanced(WINE_INCLUDE_DIR WINE_LIBRARY WINE_CXX WINE_BUILD) IF(WINE_32_LIBRARY_DIR) - IF(WINE_32_LIBRARY_DIR MATCHES "wine*/lib") + IF(WINE_32_LIBRARY_DIR MATCHES "^/opt/wine-.*") + # winehq uses a singular lib directory + SET(WINE_32_FLAGS "-L${WINE_32_LIBRARY_DIR}") + SET(WINE_32_LIBRARY_DIRS "${WINE_32_LIBRARY_DIR}") + ELSEIF(WINE_32_LIBRARY_DIR MATCHES "wine*/lib") SET(WINE_32_FLAGS "-L${WINE_32_LIBRARY_DIR} -L${WINE_32_LIBRARY_DIR}../") SET(WINE_32_LIBRARY_DIRS "${WINE_32_LIBRARY_DIR}:${WINE_32_LIBRARY_DIR}/..") ELSE() @@ -108,7 +154,11 @@ IF(WINE_32_LIBRARY_DIR) ENDIF() IF(WINE_64_LIBRARY_DIR) - IF(WINE_64_LIBRARY_DIR MATCHES "wine*/lib") + IF(WINE_32_LIBRARY_DIR MATCHES "^/opt/wine-.*") + # winehq uses a singular lib directory + SET(WINE_64_FLAGS "-L${WINE_64_LIBRARY_DIR}") + SET(WINE_64_LIBRARY_DIRS "${WINE_64_LIBRARY_DIR}") + ELSEIF(WINE_64_LIBRARY_DIR MATCHES "wine*/lib") SET(WINE_64_FLAGS "-L${WINE_64_LIBRARY_DIR} -L${WINE_64_LIBRARY_DIR}../") SET(WINE_64_LIBRARY_DIRS "${WINE_64_LIBRARY_DIR}:${WINE_64_LIBRARY_DIR}/..") ELSE() @@ -117,6 +167,12 @@ IF(WINE_64_LIBRARY_DIR) ENDIF() ENDIF() -# Create winegcc wrapper +message(STATUS " WINE_INCLUDE_DIR: ${WINE_INCLUDE_DIR}") +message(STATUS " WINE_CXX: ${WINE_CXX}") +message(STATUS " WINE_GCC: ${WINE_GCC}") +message(STATUS " WINE_32_FLAGS: ${WINE_32_FLAGS}") +message(STATUS " WINE_64_FLAGS: ${WINE_64_FLAGS}") + +# Create winegcc (technically, wineg++) wrapper configure_file(${CMAKE_CURRENT_LIST_DIR}/winegcc_wrapper.in winegcc_wrapper @ONLY) SET(WINEGCC "${CMAKE_CURRENT_BINARY_DIR}/winegcc_wrapper") diff --git a/cmake/modules/ImportedTargetHelpers.cmake b/cmake/modules/ImportedTargetHelpers.cmake index 87b3aeedc..d3d979901 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 167a93f35..29e5b207c 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/cmake/modules/PluginList.cmake b/cmake/modules/PluginList.cmake index 0a4686fb2..8b26d4ed5 100644 --- a/cmake/modules/PluginList.cmake +++ b/cmake/modules/PluginList.cmake @@ -41,6 +41,7 @@ SET(LMMS_PLUGIN_LIST HydrogenImport LadspaBrowser LadspaEffect + LOMM Lv2Effect Lv2Instrument Lb302 @@ -59,6 +60,7 @@ SET(LMMS_PLUGIN_LIST Sf2Player Sfxr Sid + SlicerT SpectrumAnalyzer StereoEnhancer StereoMatrix @@ -98,12 +100,3 @@ IF(LIST_PLUGINS) UNSET(LIST_PLUGINS CACHE) LIST_ALL_PLUGINS() ENDIF() - -IF(MSVC) - SET(MSVC_INCOMPATIBLE_PLUGINS - LadspaEffect - ) - message(WARNING "Compiling with MSVC. The following plugins are not available: ${MSVC_INCOMPATIBLE_PLUGINS}") - LIST(REMOVE_ITEM PLUGIN_LIST ${MSVC_INCOMPATIBLE_PLUGINS}) -ENDIF() - diff --git a/cmake/msys/extract_debs.sh b/cmake/msys/extract_debs.sh deleted file mode 100644 index 939912bb2..000000000 --- a/cmake/msys/extract_debs.sh +++ /dev/null @@ -1,19 +0,0 @@ -#!/bin/bash -set -e - -ppa_dir=./ppa/ - -pushd $ppa_dir - -for f in *.deb; do - echo "Extracting $f..." - ar xv "$f" - rm debian-binary - rm control.tar.* - tar xf data.tar.* --exclude=*mingw*/bin/fluid - rm data.tar.* -done - -popd - -echo "Your extracted files should be located in $ppa_dir" diff --git a/cmake/msys/fetch_ppa.sh b/cmake/msys/fetch_ppa.sh deleted file mode 100644 index ba8697c81..000000000 --- a/cmake/msys/fetch_ppa.sh +++ /dev/null @@ -1,53 +0,0 @@ -#!/bin/bash - -# Trusty=14.04, Precise=12.04 -PPA_DISTRO=trusty - -# Architecture=i386, amd64 -PPA_ARCH=amd64 - -# These shouldn't change -PPA_HOST=http://ppa.launchpad.net -PPA_USER=tobydox -PPA_PROJECT=mingw-x-trusty -PPA_ROOT=$PPA_HOST/$PPA_USER/$PPA_PROJECT/ubuntu - -PPA_URL=$PPA_ROOT/dists/$PPA_DISTRO/main/binary-$PPA_ARCH/Packages.gz - -ppa_dir=./ppa/ - -temp_file=/tmp/ppa_listing_$$ -temp_temp_file=/tmp/ppa_listing_temp_$$ - -skip_files="binutils openssl flac libgig libogg libvorbis x-bootstrap zlib" -skip_files="$skip_files x-runtime gcc qt_4 qt5 x-stk pkgconfig" -skip_files="$skip_files glib2 libpng" - -echo "Connecting to $PPA_URL to get list of packages..." -wget -qO- $PPA_URL | gzip -d -c | grep "Filename:" > $temp_file - -for j in $skip_files ; do - grep -v "$j" $temp_file > $temp_temp_file - mv $temp_temp_file $temp_file -done - -line_count=$(wc -l $temp_file |awk '{print $1}') - -echo "Found $line_count packages for download..." - -echo "Downloading packages. They will be saved to $ppa_dir" - -mkdir $ppa_dir - -while read -r j -do - echo "Downloading $j..." - echo "$PPA_ROOT/$j" - wget -qO "$ppa_dir$(basename "$j")" "$(echo "$PPA_ROOT/$j" | sed 's/\/Filename: /\//gi')" -done < $temp_file - - -echo "Cleaning up temporary files..." -rm -rf $temp_file - -echo "Packages have been saved to $ppa_dir. Please run extract_debs.sh" diff --git a/cmake/msys/msys_helper.sh b/cmake/msys/msys_helper.sh deleted file mode 100644 index a6a7e6aae..000000000 --- a/cmake/msys/msys_helper.sh +++ /dev/null @@ -1,226 +0,0 @@ -#!/bin/bash - -set -eu - -# Git repo information -fork="lmms" # i.e. "lmms" or "tobydox" -branch="master" # i.e. "master" or "stable-1.2" - -# Console colors -red="\\x1B[1;31m" -green="\\x1B[1;32m" -yellow="\\x1B[1;33m" -plain="\\x1B[0m" - -function info() { echo -e "\n${green}$1${plain}"; } -function warn() { echo -e "\n${yellow}$1${plain}"; } -function err() { echo -e "\n${red}$1${plain}"; exit 1;} - -info "Checking for mingw environment" -if ! env | grep MINGW; then - err " - Failed. Please relaunch using MinGW shell" -fi - -info "Preparing the git directory..." -mkdir "$HOME/.git" || true -touch "$HOME/.git/config" > /dev/null 2>&1 -git config --global http.sslverify false - -info "Cloning the repository..." -if [ -d ./lmms ]; then - warn " - Skipping, ./lmms already exists" -else - git clone -b $branch https://github.com/$fork/lmms.git -fi - -info "Fetching ppa using cmake/msys/fetch_ppas.sh..." -if [ -d "$HOME/ppa" ]; then - warn " - Skipping, $HOME/ppa already exists" -else - lmms/cmake/msys/fetch_ppa.sh -fi - -info "Extracting debs to $HOME/ppa/opt/, etc..." -if [ -d "$HOME/ppa/opt" ]; then - warn " - Skipping, $HOME/ppa/opt already exists" -else - lmms/cmake/msys/extract_debs.sh -fi - -info "Preparing library merge, making all qt headers writable..." -chmod u+w /mingw64/include/qt4 -R -chmod u+w /mingw32/include/qt4 -R - -info "Merging mingw headers and libraries from ppa over existing system libraries..." -if ! find /mingw64 | grep sndfile.h; then - command cp -r "$HOME/ppa/opt/mingw"* / -else - warn " - Skipping, sndfile.h has already been merged" -fi - -fltkver="1.3.3" -oggver="1.3.2" -vorbisver="1.3.5" -flacver="1.3.2" -gigver="4.0.0" -stkver="4.5.1" - -mingw_root="/$(echo "$MSYSTEM"|tr '[:upper:]' '[:lower:]')" - -info "Downloading and building fltk $fltkver" -if ! command -v fluid; then - wget http://fltk.org/pub/fltk/$fltkver/fltk-$fltkver-source.tar.gz -O "$HOME/fltk-source.tar.gz" - tar zxf "$HOME/fltk-source.tar.gz" -C "$HOME/" - pushd "$HOME/fltk-$fltkver" - - info " - Compiling fltk $fltkver..." - ./configure --prefix="$mingw_root" --enable-shared - make - - info " - Installing fltk..." - make install - -# ln -s $mingw_root/usr/local/bin/fluid.exe $mingw_root/bin/fluid.exe - popd -else - warn " - Skipping, fluid binary already exists" -fi - -info "Downloading and building libogg $oggver" -if [ ! -e "$mingw_root/lib/libogg.dll.a" ]; then - wget http://downloads.xiph.org/releases/ogg/libogg-$oggver.tar.xz -O "$HOME/libogg-source.tar.xz" - tar xf "$HOME/libogg-source.tar.xz" -C "$HOME/" - pushd "$HOME/libogg-$oggver" - - info " - Compiling libogg $oggver..." - ./configure --prefix="$mingw_root" - make - - info " - Installing libogg..." - make install - # for some reason libgig needs this - ./configure --prefix="/opt$mingw_root" - make - - info " - Installing libogg..." - make install - - popd -else - warn " - Skipping, libogg binary already exists" -fi - -info "Downloading and building libvorbis $vorbisver" -if [ ! -e "$mingw_root/lib/libvorbis.dll.a" ]; then - wget http://downloads.xiph.org/releases/vorbis/libvorbis-$vorbisver.tar.xz -O "$HOME/libvorbis-source.tar.xz" - tar xf "$HOME/libvorbis-source.tar.xz" -C "$HOME/" - pushd "$HOME/libvorbis-$vorbisver" - - info " - Compiling libvorbis $vorbisver..." - ./configure --prefix="$mingw_root" - make - - info " - Installing libvorbis..." - make install - - # for some reason libgig needs this - ./configure --prefix="/opt$mingw_root" - make - info " - Installing libvorbis..." - make install - - popd -else - warn " - Skipping, libvorbis binary already exists" -fi - -info "Downloading and building flac $flacver" - -if [ ! -e "$mingw_root/lib/libFLAC.dll.a" ]; then - - wget http://downloads.xiph.org/releases/flac/flac-$flacver.tar.xz -O "$HOME/flac-source.tar.xz" - tar xf "$HOME/flac-source.tar.xz" -C "$HOME/" - pushd "$HOME/flac-$flacver" - - info " - Compiling flac $flacver..." - ./configure --prefix="$mingw_root" - make - - info " - Installing flac..." - make install - - # for some reason libgig needs this - ./configure --prefix="/opt$mingw_root" - make - - info " - Installing flac..." - make install - - popd -else - warn " - Skipping, libvorbis flac already exists" -fi - -info "Downloading and building libgig $gigver" - -if [ ! -e "$mingw_root/lib/libgig/libgig.dll.a" ]; then - wget http://download.linuxsampler.org/packages/libgig-$gigver.tar.bz2 -O "$HOME/gig-source.tar.xz" - tar xf "$HOME/gig-source.tar.xz" -C "$HOME/" - pushd "$HOME/libgig-$gigver" - - info " - Compiling libgig $gigver..." - ./configure --prefix="$mingw_root" - make - - info " - Installing libgig..." - make install - - mv "$mingw_root/lib/bin/libakai-0.dll" "$mingw_root/bin" - mv "$mingw_root/lib/bin/libgig-7.dll" "$mingw_root/bin" - - popd -else - warn " - Skipping, libgig binary already exists" -fi - -info "Downloading and building stk $stkver" - -if [ ! -e "$mingw_root/lib/libstk.dll" ]; then - wget http://ccrma.stanford.edu/software/stk/release/stk-$stkver.tar.gz -O "$HOME/stk-source.tar.xz" - tar xf "$HOME/stk-source.tar.xz" -C "$HOME/" - pushd "$HOME/stk-$stkver" - - info " - Compiling stk $stkver..." - ./configure --prefix="$mingw_root" - make - - info " - Installing stk..." - make install - - mv "$mingw_root/lib/libstk.so" "$mingw_root/lib/libstk.dll" - mv "$mingw_root/lib/libstk-$stkver.so" "$mingw_root/lib/libstk-$stkver.dll" - - popd -else - warn " - Skipping, stk binary already exists" -fi - -# make a symlink to make cmake happy -if [ "$mingw_root" = "/mingw64" ]; then - if [ ! -e /opt/mingw64/bin/x86_64-w64-mingw32-pkg-config ]; then - ln -s /usr/bin/pkg-config /opt/mingw64/bin/x86_64-w64-mingw32-pkg-config - fi -elif [ "$mingw_root" = "/mingw32" ]; then - if [ ! -e /opt/mingw32/bin/i686-w64-mingw32-pkg-config ]; then - ln -s /usr/bin/pkg-config /opt/mingw32/bin/i686-w64-mingw32-pkg-config - fi -fi - -info "Cleaning up..." -rm -rf "$HOME/fltk-$fltkver" -rm -rf "$HOME/libogg-$oggver" -rm -rf "$HOME/libvorbis-$vorbisver" -rm -rf "$HOME/flac-$flacver" -rm -rf "$HOME/libgig-$gigver" -rm -rf "$HOME/stk-$stkver" -info "Done." diff --git a/cmake/nsis/CMakeLists.txt b/cmake/nsis/CMakeLists.txt index ee1bd45c3..e926e074d 100644 --- a/cmake/nsis/CMakeLists.txt +++ b/cmake/nsis/CMakeLists.txt @@ -3,10 +3,9 @@ if(LMMS_MSVC_YEAR) SET(WIN_PLATFORM "msvc${LMMS_MSVC_YEAR}") endif() -SET(CPACK_PACKAGE_ICON "${CMAKE_SOURCE_DIR}/cmake/nsis/nsis_branding.bmp") -IF(MSVC) - STRING(REPLACE "/" "\\\\" CPACK_PACKAGE_ICON ${CPACK_PACKAGE_ICON}) -ENDIF(MSVC) +# the final slash needs to be flipped for CPACK_PACKAGE_ICON to work: +# https://cmake.org/pipermail/cmake/2008-June/022085.html +SET(CPACK_PACKAGE_ICON "${CMAKE_SOURCE_DIR}/cmake/nsis\\\\nsis_branding.bmp") SET(CPACK_NSIS_MUI_ICON "${CMAKE_SOURCE_DIR}/cmake/nsis/icon.ico") SET(CPACK_NSIS_INSTALLED_ICON_NAME "${CMAKE_PROJECT_NAME}.exe" PARENT_SCOPE) SET(CPACK_NSIS_DISPLAY_NAME "${PROJECT_NAME_UCASE} ${VERSION}" PARENT_SCOPE) @@ -44,25 +43,6 @@ IF(WIN64) ") ENDIF() -# Fix windows paths for msys -IF(LMMS_BUILD_MSYS) - STRING(REPLACE "/" "\\\\" CPACK_PACKAGE_ICON "${CPACK_PACKAGE_ICON}") - STRING(REPLACE "/" "\\\\" CPACK_NSIS_MUI_ICON "${CPACK_NSIS_MUI_ICON}") - STRING(REPLACE "/" "\\\\" CPACK_NSIS_DEFINES "${CPACK_NSIS_DEFINES}") - STRING(REPLACE "/" "\\\\" CMAKE_BINARY_DIR_FIX "${CMAKE_BINARY_DIR}") - - # FIXME: there's no easy way to fix $INST_DIR, so we'll redefine it manually - IF(WIN64) - SET(NSIS_ARCH "win64") - ELSE() - SET(NSIS_ARCH "win32") - ENDIF() - SET(CPACK_NSIS_DEFINES " - ${CPACK_NSIS_DEFINES} - !define /redef INST_DIR ${CMAKE_BINARY_DIR_FIX}\\\\_CPack_Packages\\\\${NSIS_ARCH}\\\\NSIS\\\\${CPACK_PACKAGE_FILE_NAME} - ") -ENDIF() - # Setup missing parent scopes SET(CPACK_PACKAGE_FILE_NAME "${CPACK_PACKAGE_FILE_NAME}" PARENT_SCOPE) SET(CPACK_NSIS_DEFINES "${CPACK_NSIS_DEFINES}" PARENT_SCOPE) diff --git a/cmake/toolchains/MSYS-32.cmake b/cmake/toolchains/MSYS-32.cmake deleted file mode 100644 index 698dd5437..000000000 --- a/cmake/toolchains/MSYS-32.cmake +++ /dev/null @@ -1,4 +0,0 @@ -INCLUDE(${CMAKE_CURRENT_LIST_DIR}/common/MSYS.cmake) -INCLUDE(${CMAKE_CURRENT_LIST_DIR}/common/Win32.cmake) - -SET(MINGW_PREFIX /mingw32) \ No newline at end of file diff --git a/cmake/toolchains/MSYS-64.cmake b/cmake/toolchains/MSYS-64.cmake deleted file mode 100644 index 8becd51b3..000000000 --- a/cmake/toolchains/MSYS-64.cmake +++ /dev/null @@ -1,5 +0,0 @@ -INCLUDE(${CMAKE_CURRENT_LIST_DIR}/common/MSYS.cmake) -INCLUDE(${CMAKE_CURRENT_LIST_DIR}/common/Win64.cmake) - -SET(MINGW_PREFIX /mingw64) -SET(MINGW_PREFIX32 /mingw32) diff --git a/cmake/toolchains/common/MSYS.cmake b/cmake/toolchains/common/MSYS.cmake deleted file mode 100644 index 0b27e8d32..000000000 --- a/cmake/toolchains/common/MSYS.cmake +++ /dev/null @@ -1,22 +0,0 @@ -# The target environment -SET(CMAKE_FIND_ROOT_PATH ${MINGW_PREFIX}) -SET(CMAKE_INSTALL_PREFIX ${MINGW_PREFIX}) - -# Windows msys mingw ships with a mostly-suitable preconfigured environment -SET(STRIP ${MINGW_PREFIX}/bin/strip) -SET(CMAKE_RC_COMPILER ${MINGW_PREFIX}/bin/windres) -SET(CMAKE_C_COMPILER ${MINGW_PREFIX}/bin/gcc) -SET(CMAKE_CXX_COMPILER ${MINGW_PREFIX}/bin/g++) - -# For 32-bit vst support -IF(WIN64) - # Specify the 32-bit cross compiler - SET(CMAKE_C_COMPILER32 ${MINGW_PREFIX32}/bin/gcc) - SET(CMAKE_CXX_COMPILER32 ${MINGW_PREFIX32}/bin/g++) -ENDIF() - -# Msys compiler does not support @CMakeFiles/Include syntax -SET(CMAKE_C_USE_RESPONSE_FILE_FOR_INCLUDES OFF) -SET(CMAKE_CXX_USE_RESPONSE_FILE_FOR_INCLUDES OFF) - -SET(LMMS_BUILD_MSYS 1) diff --git a/data/locale/ar.ts b/data/locale/ar.ts index 0d44c22bf..4aca06e51 100644 --- a/data/locale/ar.ts +++ b/data/locale/ar.ts @@ -5334,62 +5334,62 @@ Please make sure you have write permission to the file and the directory contain - MixerLine + MixerChannelView - + Channel send amount - + Move &left - + Move &right - + Rename &channel - + R&emove channel - + Remove &unused channels - + Set channel color - + Remove channel color - + Pick random channel color - MixerLineLcdSpinBox + MixerChannelLcdSpinBox - + Assign to: - + New Mixer Channel diff --git a/data/locale/bs.ts b/data/locale/bs.ts index 7abf0baf1..8050af8c2 100644 --- a/data/locale/bs.ts +++ b/data/locale/bs.ts @@ -2698,14 +2698,14 @@ Please make sure you have write-permission to the file and the directory contain - MixerLine + MixerChannelView - + Channel send amount - + The Mixer channel receives input from one or more instrument tracks. It in turn can be routed to multiple other mixer channels. LMMS automatically takes care of preventing infinite loops for you and doesn't allow making a connection that would result in an infinite loop. @@ -2716,27 +2716,27 @@ You can remove and move mixer channels in the context menu, which is accessed by - + Move &left - + Move &right - + Rename &channel - + R&emove channel - + Remove &unused channels @@ -2789,12 +2789,12 @@ You can remove and move mixer channels in the context menu, which is accessed by - + Rename mixer channel - + Enter the new name for this mixer channel @@ -9752,7 +9752,7 @@ Please make sure you have read-permission to the file and the directory containi - MixerLineLcdSpinBox + MixerChannelLcdSpinBox Assign to: diff --git a/data/locale/ca.ts b/data/locale/ca.ts index 0e27c39db..2b06d0754 100644 --- a/data/locale/ca.ts +++ b/data/locale/ca.ts @@ -5333,62 +5333,62 @@ Please make sure you have write permission to the file and the directory contain - MixerLine + MixerChannelView - + Channel send amount - + Move &left - + Move &right - + Rename &channel - + R&emove channel - + Remove &unused channels - + Set channel color - + Remove channel color - + Pick random channel color - MixerLineLcdSpinBox + MixerChannelLcdSpinBox - + Assign to: - + New mixer Channel diff --git a/data/locale/cs.ts b/data/locale/cs.ts index 022f55459..16982e4ae 100644 --- a/data/locale/cs.ts +++ b/data/locale/cs.ts @@ -5334,62 +5334,62 @@ Ověřte si prosím, zda máte povolen zápis do souboru a do složky, ve které - MixerLine + MixerChannelView - + Channel send amount Množství odeslaného kanálu - + Move &left Přesunout do&leva - + Move &right Přesun dop&rava - + Rename &channel Přejmenovat &kanál - + R&emove channel Př&esunout kanál - + Remove &unused channels Odstranit nepo&užívané kanály - + Set channel color - + Remove channel color - + Pick random channel color - MixerLineLcdSpinBox + MixerChannelLcdSpinBox - + Assign to: Přiřadit k: - + New mixer Channel Nový efektový kanál diff --git a/data/locale/de.ts b/data/locale/de.ts index 7817857fd..d5d0625c2 100644 --- a/data/locale/de.ts +++ b/data/locale/de.ts @@ -5334,62 +5334,62 @@ Please make sure you have write permission to the file and the directory contain - MixerLine + MixerChannelView - + Channel send amount Kanal Sendemenge - + Move &left Nach &links verschieben - + Move &right Nach &rechts verschieben - + Rename &channel &Kanal umbenennen - + R&emove channel Kanal &Entfernen - + Remove &unused channels Entferne &unbenutzte Kanäle - + Set channel color - + Remove channel color - + Pick random channel color - MixerLineLcdSpinBox + MixerChannelLcdSpinBox - + Assign to: Weise hinzu: - + New mixer Channel Neuer FX-Kanal diff --git a/data/locale/el.ts b/data/locale/el.ts index 07e61778f..130d5c63e 100644 --- a/data/locale/el.ts +++ b/data/locale/el.ts @@ -5333,62 +5333,62 @@ Please make sure you have write permission to the file and the directory contain - MixerLine + MixerChannelView - + Channel send amount - + Move &left - + Move &right - + Rename &channel - + R&emove channel - + Remove &unused channels - + Set channel color - + Remove channel color - + Pick random channel color - MixerLineLcdSpinBox + MixerChannelLcdSpinBox - + Assign to: - + New mixer Channel diff --git a/data/locale/en.ts b/data/locale/en.ts index 15c3ab1f0..246311921 100644 --- a/data/locale/en.ts +++ b/data/locale/en.ts @@ -5335,62 +5335,62 @@ Please make sure you have write permission to the file and the directory contain - MixerLine + MixerChannelView - + Channel send amount - + Move &left - + Move &right - + Rename &channel - + R&emove channel - + Remove &unused channels - + Set channel color - + Remove channel color - + Pick random channel color - MixerLineLcdSpinBox + MixerChannelLcdSpinBox - + Assign to: - + New mixer Channel diff --git a/data/locale/eo.ts b/data/locale/eo.ts index 0dd9c405f..88e92cda8 100644 --- a/data/locale/eo.ts +++ b/data/locale/eo.ts @@ -5333,62 +5333,62 @@ Please make sure you have write permission to the file and the directory contain - MixerLine + MixerChannelView - + Channel send amount - + Move &left - + Move &right - + Rename &channel - + R&emove channel - + Remove &unused channels - + Set channel color - + Remove channel color - + Pick random channel color - MixerLineLcdSpinBox + MixerChannelLcdSpinBox - + Assign to: - + New mixer Channel diff --git a/data/locale/es.ts b/data/locale/es.ts index 3953ddc11..26c743784 100644 --- a/data/locale/es.ts +++ b/data/locale/es.ts @@ -5334,62 +5334,62 @@ Asegúrate de tener permisos de escritura tanto del archivo como del directorio - MixerLine + MixerChannelView - + Channel send amount Cantidad de envío del canal - + Move &left Mover a la Izquierda (&L) - + Move &right Mover a la Derecha (&R) - + Rename &channel Renombrar &Canal - + R&emove channel Borrar canal (&E) - + Remove &unused channels Quitar los canales que no esten en &uso - + Set channel color - + Remove channel color - + Pick random channel color - MixerLineLcdSpinBox + MixerChannelLcdSpinBox - + Assign to: Asignar a: - + New mixer Channel Nuevo Canal FX diff --git a/data/locale/eu.ts b/data/locale/eu.ts index fe6495c0a..7e815a261 100644 --- a/data/locale/eu.ts +++ b/data/locale/eu.ts @@ -5614,62 +5614,62 @@ Please make sure you have write permission to the file and the directory contain - MixerLine + MixerChannelView - + Channel send amount - + Move &left - + Move &right - + Rename &channel - + R&emove channel - + Remove &unused channels - + Set channel color - + Remove channel color - + Pick random channel color - MixerLineLcdSpinBox + MixerChannelLcdSpinBox - + Assign to: - + New mixer Channel diff --git a/data/locale/fa.ts b/data/locale/fa.ts index b376a8424..b167716fd 100644 --- a/data/locale/fa.ts +++ b/data/locale/fa.ts @@ -5333,62 +5333,62 @@ Please make sure you have write permission to the file and the directory contain - MixerLine + MixerChannelView - + Channel send amount - + Move &left - + Move &right - + Rename &channel - + R&emove channel - + Remove &unused channels - + Set channel color - + Remove channel color - + Pick random channel color - MixerLineLcdSpinBox + MixerChannelLcdSpinBox - + Assign to: - + New mixer Channel diff --git a/data/locale/fr.ts b/data/locale/fr.ts index 4862f4263..0386fb4c6 100644 --- a/data/locale/fr.ts +++ b/data/locale/fr.ts @@ -5618,62 +5618,62 @@ Veuillez vous assurez que vous avez les droits d'écriture sur le fichier e - MixerLine + MixerChannelView - + Channel send amount Quantité de signal envoyé du canal - + Move &left Déplacer à &gauche - + Move &right Déplacer à &droite - + Rename &channel &Renommer le canal - + R&emove channel &Supprimer le canal - + Remove &unused channels Supprimer les canaux &inutilisés - + Set channel color - + Remove channel color - + Pick random channel color - MixerLineLcdSpinBox + MixerChannelLcdSpinBox - + Assign to: Assigner à : - + New mixer Channel Nouveau canal d'effet diff --git a/data/locale/gl.ts b/data/locale/gl.ts index a1a9e6bf1..01cd54322 100644 --- a/data/locale/gl.ts +++ b/data/locale/gl.ts @@ -5333,62 +5333,62 @@ Please make sure you have write permission to the file and the directory contain - MixerLine + MixerChannelView - + Channel send amount - + Move &left - + Move &right - + Rename &channel - + R&emove channel - + Remove &unused channels - + Set channel color - + Remove channel color - + Pick random channel color - MixerLineLcdSpinBox + MixerChannelLcdSpinBox - + Assign to: - + New mixer Channel diff --git a/data/locale/he.ts b/data/locale/he.ts index fef0caa91..2699b7e1a 100644 --- a/data/locale/he.ts +++ b/data/locale/he.ts @@ -5334,62 +5334,62 @@ Please make sure you have write permission to the file and the directory contain - MixerLine + MixerChannelView - + Channel send amount - + Move &left - + Move &right - + Rename &channel - + R&emove channel - + Remove &unused channels - + Set channel color - + Remove channel color - + Pick random channel color - MixerLineLcdSpinBox + MixerChannelLcdSpinBox - + Assign to: - + New mixer Channel diff --git a/data/locale/hi_IN.ts b/data/locale/hi_IN.ts index 82cf364e3..5b2eeebf5 100644 --- a/data/locale/hi_IN.ts +++ b/data/locale/hi_IN.ts @@ -5335,62 +5335,62 @@ Please make sure you have write permission to the file and the directory contain - MixerLine + MixerChannelView - + Channel send amount - + Move &left - + Move &right - + Rename &channel - + R&emove channel - + Remove &unused channels - + Set channel color - + Remove channel color - + Pick random channel color - MixerLineLcdSpinBox + MixerChannelLcdSpinBox - + Assign to: - + New mixer Channel diff --git a/data/locale/hu_HU.ts b/data/locale/hu_HU.ts index a0f1e4d45..88ef6a431 100644 --- a/data/locale/hu_HU.ts +++ b/data/locale/hu_HU.ts @@ -5339,62 +5339,62 @@ Ellenőrizd, hogy rendelkezel-e a szükséges engedélyekkel és próbáld újra - MixerLine + MixerChannelView - + Channel send amount - + Move &left Mozgatás &balra - + Move &right Mozgatás &jobbra - + Rename &channel Csatorna át&nevezése - + R&emove channel Csatorna &eltávolítása - + Remove &unused channels &Nem használt csatornák eltávolítása - + Set channel color Szín módosítása - + Remove channel color Szín eltávolítása - + Pick random channel color Véletlenszerű szín - MixerLineLcdSpinBox + MixerChannelLcdSpinBox - + Assign to: Hozzárendelés: - + New mixer Channel Új csatorna diff --git a/data/locale/id.ts b/data/locale/id.ts index c504740e9..4adeb9b22 100644 --- a/data/locale/id.ts +++ b/data/locale/id.ts @@ -5335,62 +5335,62 @@ Pastikan Anda memiliki izin menulis ke file dan direktori yang berisi berkas ter - MixerLine + MixerChannelView - + Channel send amount Jumlah kirim saluran - + Move &left Pindah ke &kiri - + Move &right Pindah ke &kanan - + Rename &channel Ganti nama &saluran - + R&emove channel H&apus saluran - + Remove &unused channels Hapus &saluran yang tak terpakai - + Set channel color - + Remove channel color - + Pick random channel color - MixerLineLcdSpinBox + MixerChannelLcdSpinBox - + Assign to: - + New mixer Channel Saluran FX Baru diff --git a/data/locale/it.ts b/data/locale/it.ts index d5a68e6e7..104a3bdbc 100644 --- a/data/locale/it.ts +++ b/data/locale/it.ts @@ -5339,62 +5339,62 @@ Si prega di controllare i permessi di scrittura sul file e la cartella che lo co - MixerLine + MixerChannelView - + Channel send amount Quantità di segnale inviata dal canale - + Move &left Sposta a &sinistra - + Move &right Sposta a $destra - + Rename &channel Rinomina &canale - + R&emove channel R&imuovi canale - + Remove &unused channels Rimuovi canali in&utilizzati - + Set channel color - + Remove channel color - + Pick random channel color - MixerLineLcdSpinBox + MixerChannelLcdSpinBox - + Assign to: Assegna a: - + New mixer Channel Nuovo canale FX diff --git a/data/locale/ja.ts b/data/locale/ja.ts index 14b38c698..84c5c8a6a 100644 --- a/data/locale/ja.ts +++ b/data/locale/ja.ts @@ -5335,62 +5335,62 @@ Please make sure you have write permission to the file and the directory contain - MixerLine + MixerChannelView - + Channel send amount - + Move &left 一つ左へ (&l) - + Move &right 一つ右へ (&r) - + Rename &channel チャンネル名を変更 (&c) - + R&emove channel チャンネルを削除 (&e) - + Remove &unused channels 使用していないチャンネルを削除 (&u) - + Set channel color - + Remove channel color - + Pick random channel color - MixerLineLcdSpinBox + MixerChannelLcdSpinBox - + Assign to: - + New mixer Channel diff --git a/data/locale/ka.ts b/data/locale/ka.ts index 51eededf2..bd7890457 100644 --- a/data/locale/ka.ts +++ b/data/locale/ka.ts @@ -5333,62 +5333,62 @@ Please make sure you have write permission to the file and the directory contain - MixerLine + MixerChannelView - + Channel send amount - + Move &left - + Move &right - + Rename &channel - + R&emove channel - + Remove &unused channels - + Set channel color - + Remove channel color - + Pick random channel color - MixerLineLcdSpinBox + MixerChannelLcdSpinBox - + Assign to: - + New mixer Channel diff --git a/data/locale/ko.ts b/data/locale/ko.ts index 43b99e7f4..036c73231 100644 --- a/data/locale/ko.ts +++ b/data/locale/ko.ts @@ -5337,62 +5337,62 @@ Please make sure you have write permission to the file and the directory contain - MixerLine + MixerChannelView - + Channel send amount - + Move &left 왼쪽으로 이동(&L) - + Move &right 오른쪽으로 이동(&R) - + Rename &channel 채널 이름 바꾸기(&C) - + R&emove channel 채널 제거(&R) - + Remove &unused channels 사용하지 않는 채널 제거(&U) - + Set channel color - + Remove channel color - + Pick random channel color - MixerLineLcdSpinBox + MixerChannelLcdSpinBox - + Assign to: 채널 할당: - + New mixer Channel 새 FX 채널 diff --git a/data/locale/ms_MY.ts b/data/locale/ms_MY.ts index ff3478421..dc3561ea2 100644 --- a/data/locale/ms_MY.ts +++ b/data/locale/ms_MY.ts @@ -5333,62 +5333,62 @@ Please make sure you have write permission to the file and the directory contain - MixerLine + MixerChannelView - + Channel send amount - + Move &left - + Move &right - + Rename &channel - + R&emove channel - + Remove &unused channels - + Set channel color - + Remove channel color - + Pick random channel color - MixerLineLcdSpinBox + MixerChannelLcdSpinBox - + Assign to: - + New mixer Channel diff --git a/data/locale/nb.ts b/data/locale/nb.ts index 659344d64..adfc6d856 100644 --- a/data/locale/nb.ts +++ b/data/locale/nb.ts @@ -5333,62 +5333,62 @@ Please make sure you have write permission to the file and the directory contain - MixerLine + MixerChannelView - + Channel send amount - + Move &left - + Move &right - + Rename &channel - + R&emove channel - + Remove &unused channels - + Set channel color - + Remove channel color - + Pick random channel color - MixerLineLcdSpinBox + MixerChannelLcdSpinBox - + Assign to: - + New mixer Channel diff --git a/data/locale/nl.ts b/data/locale/nl.ts index 7ff3e8735..8d1304135 100644 --- a/data/locale/nl.ts +++ b/data/locale/nl.ts @@ -5335,62 +5335,62 @@ Zorg ervoor dat u schrijfbevoegdheid heeft voor het bestand en voor de map die h - MixerLine + MixerChannelView - + Channel send amount Hoeveelheid kanaal-send - + Move &left &Links verplaatsen - + Move &right &Rechts verplaatsen - + Rename &channel &Kanaal hernoemen - + R&emove channel Kanaal v&erwijderen - + Remove &unused channels Ongebr&uikte kanalen verwijderen - + Set channel color - + Remove channel color - + Pick random channel color - MixerLineLcdSpinBox + MixerChannelLcdSpinBox - + Assign to: Toewijzen aan: - + New mixer Channel Nieuw FX-kanaal diff --git a/data/locale/oc.ts b/data/locale/oc.ts index 045eaf3ad..be804cfca 100644 --- a/data/locale/oc.ts +++ b/data/locale/oc.ts @@ -5333,62 +5333,62 @@ Please make sure you have write permission to the file and the directory contain - MixerLine + MixerChannelView - + Channel send amount - + Move &left - + Move &right - + Rename &channel - + R&emove channel - + Remove &unused channels - + Set channel color - + Remove channel color - + Pick random channel color - MixerLineLcdSpinBox + MixerChannelLcdSpinBox - + Assign to: - + New mixer Channel diff --git a/data/locale/pl.ts b/data/locale/pl.ts index ff36a8dac..3a12f0815 100644 --- a/data/locale/pl.ts +++ b/data/locale/pl.ts @@ -5619,62 +5619,62 @@ Upewnij się, że masz uprawnienia do zapisu do pliku i katalogu zawierającego - MixerLine + MixerChannelView - + Channel send amount Ilość wysyłania kanału - + Move &left Przesuń w &lewo - + Move &right Przesuń w p&rawo - + Rename &channel Zmień nazwę &kanału - + R&emove channel Usuń k&anał - + Remove &unused channels &Usuń nieużywane kanały - + Set channel color Ustaw kolor kanału - + Remove channel color Usuń kolor kanału - + Pick random channel color Ustaw losowy kolor kanału - MixerLineLcdSpinBox + MixerChannelLcdSpinBox - + Assign to: Przypisz do: - + New mixer Channel Nowy kanał efektów diff --git a/data/locale/pt.ts b/data/locale/pt.ts index f8cfe7618..d88d0fa24 100644 --- a/data/locale/pt.ts +++ b/data/locale/pt.ts @@ -5336,62 +5336,62 @@ Please make sure you have write permission to the file and the directory contain - MixerLine + MixerChannelView - + Channel send amount Quantidade de envio de canal - + Move &left - + Move &right - + Rename &channel Renomear canal - + R&emove channel Remover canal - + Remove &unused channels Remover canais não utilizados - + Set channel color - + Remove channel color - + Pick random channel color - MixerLineLcdSpinBox + MixerChannelLcdSpinBox - + Assign to: Atribuir a: - + New mixer Channel Novo Canal FX diff --git a/data/locale/ro.ts b/data/locale/ro.ts index 58abbba99..4823ca57e 100644 --- a/data/locale/ro.ts +++ b/data/locale/ro.ts @@ -5334,62 +5334,62 @@ Please make sure you have write permission to the file and the directory contain - MixerLine + MixerChannelView - + Channel send amount - + Move &left - + Move &right - + Rename &channel - + R&emove channel - + Remove &unused channels - + Set channel color - + Remove channel color - + Pick random channel color - MixerLineLcdSpinBox + MixerChannelLcdSpinBox - + Assign to: - + New mixer Channel diff --git a/data/locale/ru.ts b/data/locale/ru.ts index 73b7e06ad..dee2b8482 100644 --- a/data/locale/ru.ts +++ b/data/locale/ru.ts @@ -5348,62 +5348,62 @@ Please make sure you have write permission to the file and the directory contain - MixerLine + MixerChannelView - + Channel send amount Величина отправки канала - + Move &left Подвинуть в&лево - + Move &right Подвинуть в&право - + Rename &channel Пере&именовать канал - + R&emove channel &Удалить канал - + Remove &unused channels Удалить &неиспользуемые каналы - + Set channel color Установить цвет канала - + Remove channel color Удалить цвет канала - + Pick random channel color Выбрать случайный цвет канала - MixerLineLcdSpinBox + MixerChannelLcdSpinBox - + Assign to: Назначить на: - + New mixer Channel Новый канал ЭФ diff --git a/data/locale/sl.ts b/data/locale/sl.ts index e7bfbc308..1aa67d54d 100644 --- a/data/locale/sl.ts +++ b/data/locale/sl.ts @@ -5333,62 +5333,62 @@ Please make sure you have write permission to the file and the directory contain - MixerLine + MixerChannelView - + Channel send amount - + Move &left - + Move &right - + Rename &channel - + R&emove channel - + Remove &unused channels - + Set channel color - + Remove channel color - + Pick random channel color - MixerLineLcdSpinBox + MixerChannelLcdSpinBox - + Assign to: - + New mixer Channel diff --git a/data/locale/sr.ts b/data/locale/sr.ts index 183936bc7..557890f35 100644 --- a/data/locale/sr.ts +++ b/data/locale/sr.ts @@ -2178,7 +2178,7 @@ Please make sure you have write-permission to the file and the directory contain - MixerLine + MixerChannelView Channel send amount @@ -7752,7 +7752,7 @@ Please make sure you have read-permission to the file and the directory containi - MixerLineLcdSpinBox + MixerChannelLcdSpinBox Assign to: diff --git a/data/locale/sv.ts b/data/locale/sv.ts index f5d4e0fb4..b40306cac 100644 --- a/data/locale/sv.ts +++ b/data/locale/sv.ts @@ -5617,62 +5617,62 @@ Se till att du har skrivbehörighet till filen och mappen som innehåller filen - MixerLine + MixerChannelView - + Channel send amount Kanalsändningsbelopp - + Move &left Flytta &vänster - + Move &right Flytta &höger - + Rename &channel Byt namn på &kanal - + R&emove channel T&a bort kanal - + Remove &unused channels Ta bort &oanvända kanaler - + Set channel color Ställ in kanalfärg - + Remove channel color Ta bort kanalfärg - + Pick random channel color Välj slumpmässig kanalfärg - MixerLineLcdSpinBox + MixerChannelLcdSpinBox - + Assign to: Tilldela till: - + New mixer Channel Ny FX-kanal diff --git a/data/locale/tr.ts b/data/locale/tr.ts index b899337a5..bd469667f 100644 --- a/data/locale/tr.ts +++ b/data/locale/tr.ts @@ -5619,62 +5619,62 @@ Lütfen dosyaya ve dosyayı içeren dizine yazma izniniz olduğundan emin olun v - MixerLine + MixerChannelView - + Channel send amount Kanal gönderme miktarı - + Move &left Sol&a taşı - + Move &right &Sağa taşı - + Rename &channel &Kanalı yeniden adlandır - + R&emove channel Kanalı k&aldır - + Remove &unused channels &Kullanılmayan kanalları kaldırın - + Set channel color Kanal rengini ayarla - + Remove channel color Kanal rengini kaldır - + Pick random channel color Rastgele kanal rengi seçin - MixerLineLcdSpinBox + MixerChannelLcdSpinBox - + Assign to: Ata: - + New mixer Channel Yeni FX Kanalı diff --git a/data/locale/uk.ts b/data/locale/uk.ts index 9fb6389c9..245c433a1 100644 --- a/data/locale/uk.ts +++ b/data/locale/uk.ts @@ -5334,62 +5334,62 @@ Please make sure you have write permission to the file and the directory contain - MixerLine + MixerChannelView - + Channel send amount Величина відправки каналу - + Move &left Рухати вліво &L - + Move &right Рухати вправо &R - + Rename &channel Перейменувати канал &C - + R&emove channel Видалити канал &e - + Remove &unused channels Видалити канали які &не використовуються - + Set channel color - + Remove channel color - + Pick random channel color - MixerLineLcdSpinBox + MixerChannelLcdSpinBox - + Assign to: Призначити до: - + New mixer Channel Новий ефект каналу diff --git a/data/locale/zh_CN.ts b/data/locale/zh_CN.ts index 9b783b963..57c080341 100644 --- a/data/locale/zh_CN.ts +++ b/data/locale/zh_CN.ts @@ -5343,62 +5343,62 @@ Please make sure you have write permission to the file and the directory contain - MixerLine + MixerChannelView - + Channel send amount 通道发送的数量 - + Move &left 向左移(&L) - + Move &right 向右移(&R) - + Rename &channel 重命名通道(&C) - + R&emove channel 删除通道(&E) - + Remove &unused channels 移除所有未用通道(&U) - + Set channel color - + Remove channel color - + Pick random channel color - MixerLineLcdSpinBox + MixerChannelLcdSpinBox - + Assign to: 分配给: - + New mixer Channel 新的效果通道 diff --git a/data/locale/zh_TW.ts b/data/locale/zh_TW.ts index a3a727edb..9348dfb32 100644 --- a/data/locale/zh_TW.ts +++ b/data/locale/zh_TW.ts @@ -5334,62 +5334,62 @@ Please make sure you have write permission to the file and the directory contain - MixerLine + MixerChannelView - + Channel send amount 通道發送的數量 - + Move &left 向左移(&L) - + Move &right 向右移(&R) - + Rename &channel 重命名通道(&C) - + R&emove channel 刪除通道(&E) - + Remove &unused channels 移除所有未用通道(&U) - + Set channel color - + Remove channel color - + Pick random channel color - MixerLineLcdSpinBox + MixerChannelLcdSpinBox - + Assign to: 分配給: - + New mixer Channel 新的效果通道 diff --git a/data/projects/demos/unfa-Spoken.mmpz b/data/projects/demos/unfa-Spoken.mmpz index 66b7589d1..58d00d50d 100644 Binary files a/data/projects/demos/unfa-Spoken.mmpz and b/data/projects/demos/unfa-Spoken.mmpz differ diff --git a/data/projects/shorties/Greshz-CoolSnip.mmpz b/data/projects/shorties/Greshz-CoolSnip.mmpz deleted file mode 100644 index dadeef1ba..000000000 Binary files a/data/projects/shorties/Greshz-CoolSnip.mmpz and /dev/null differ diff --git a/data/samples/bassloops/briff01.ogg b/data/samples/bassloops/briff01 - 140 BPM.ogg similarity index 98% rename from data/samples/bassloops/briff01.ogg rename to data/samples/bassloops/briff01 - 140 BPM.ogg index a307df85f..0b9cc32f7 100644 Binary files a/data/samples/bassloops/briff01.ogg and b/data/samples/bassloops/briff01 - 140 BPM.ogg differ diff --git a/data/samples/bassloops/rave_bass01.ogg b/data/samples/bassloops/rave_bass01 - 180 BPM.ogg similarity index 98% rename from data/samples/bassloops/rave_bass01.ogg rename to data/samples/bassloops/rave_bass01 - 180 BPM.ogg index 920ff4a74..335195747 100644 Binary files a/data/samples/bassloops/rave_bass01.ogg and b/data/samples/bassloops/rave_bass01 - 180 BPM.ogg differ diff --git a/data/samples/bassloops/rave_bass02.ogg b/data/samples/bassloops/rave_bass02 - 180 BPM.ogg similarity index 98% rename from data/samples/bassloops/rave_bass02.ogg rename to data/samples/bassloops/rave_bass02 - 180 BPM.ogg index ff38123df..230d99d2e 100644 Binary files a/data/samples/bassloops/rave_bass02.ogg and b/data/samples/bassloops/rave_bass02 - 180 BPM.ogg differ diff --git a/data/samples/bassloops/tb303_01.ogg b/data/samples/bassloops/tb303_01 - 123 BPM.ogg similarity index 98% rename from data/samples/bassloops/tb303_01.ogg rename to data/samples/bassloops/tb303_01 - 123 BPM.ogg index 41e1b1fc4..105720175 100644 Binary files a/data/samples/bassloops/tb303_01.ogg and b/data/samples/bassloops/tb303_01 - 123 BPM.ogg differ diff --git a/data/samples/bassloops/techno_bass01.ogg b/data/samples/bassloops/techno_bass01 - 140 BPM.ogg similarity index 92% rename from data/samples/bassloops/techno_bass01.ogg rename to data/samples/bassloops/techno_bass01 - 140 BPM.ogg index 15c2c8e32..55cd204dd 100644 Binary files a/data/samples/bassloops/techno_bass01.ogg and b/data/samples/bassloops/techno_bass01 - 140 BPM.ogg differ diff --git a/data/samples/bassloops/techno_bass02.ogg b/data/samples/bassloops/techno_bass02 - 140 BPM.ogg similarity index 93% rename from data/samples/bassloops/techno_bass02.ogg rename to data/samples/bassloops/techno_bass02 - 140 BPM.ogg index 08691f435..c1f3e8637 100644 Binary files a/data/samples/bassloops/techno_bass02.ogg and b/data/samples/bassloops/techno_bass02 - 140 BPM.ogg differ diff --git a/data/samples/bassloops/techno_synth01.ogg b/data/samples/bassloops/techno_synth01 - 140 BPM.ogg similarity index 86% rename from data/samples/bassloops/techno_synth01.ogg rename to data/samples/bassloops/techno_synth01 - 140 BPM.ogg index 95c3d96ae..1086bb0c8 100644 Binary files a/data/samples/bassloops/techno_synth01.ogg and b/data/samples/bassloops/techno_synth01 - 140 BPM.ogg differ diff --git a/data/samples/bassloops/techno_synth02.ogg b/data/samples/bassloops/techno_synth02 - 140 BPM.ogg similarity index 87% rename from data/samples/bassloops/techno_synth02.ogg rename to data/samples/bassloops/techno_synth02 - 140 BPM.ogg index dfa972e1d..afb27172b 100644 Binary files a/data/samples/bassloops/techno_synth02.ogg and b/data/samples/bassloops/techno_synth02 - 140 BPM.ogg differ diff --git a/data/samples/bassloops/techno_synth03.ogg b/data/samples/bassloops/techno_synth03 - 130 BPM.ogg similarity index 92% rename from data/samples/bassloops/techno_synth03.ogg rename to data/samples/bassloops/techno_synth03 - 130 BPM.ogg index c27cda653..bffff9517 100644 Binary files a/data/samples/bassloops/techno_synth03.ogg and b/data/samples/bassloops/techno_synth03 - 130 BPM.ogg differ diff --git a/data/samples/bassloops/techno_synth04.ogg b/data/samples/bassloops/techno_synth04 - 140 BPM.ogg similarity index 99% rename from data/samples/bassloops/techno_synth04.ogg rename to data/samples/bassloops/techno_synth04 - 140 BPM.ogg index 0e9a37398..9dd34f33a 100644 Binary files a/data/samples/bassloops/techno_synth04.ogg and b/data/samples/bassloops/techno_synth04 - 140 BPM.ogg differ diff --git a/data/samples/beats/909beat01.ogg b/data/samples/beats/909beat01 - 122 BPM.ogg similarity index 99% rename from data/samples/beats/909beat01.ogg rename to data/samples/beats/909beat01 - 122 BPM.ogg index 1892eb91b..2bae51357 100644 Binary files a/data/samples/beats/909beat01.ogg and b/data/samples/beats/909beat01 - 122 BPM.ogg differ diff --git a/data/samples/beats/break01.ogg b/data/samples/beats/break01 - 168 BPM.ogg similarity index 94% rename from data/samples/beats/break01.ogg rename to data/samples/beats/break01 - 168 BPM.ogg index d1f5769bd..5d9bd2f4b 100644 Binary files a/data/samples/beats/break01.ogg and b/data/samples/beats/break01 - 168 BPM.ogg differ diff --git a/data/samples/beats/break02.ogg b/data/samples/beats/break02 - 141 BPM.ogg similarity index 89% rename from data/samples/beats/break02.ogg rename to data/samples/beats/break02 - 141 BPM.ogg index 17243cd9e..653662c75 100644 Binary files a/data/samples/beats/break02.ogg and b/data/samples/beats/break02 - 141 BPM.ogg differ diff --git a/data/samples/beats/break03.ogg b/data/samples/beats/break03 - 168 BPM.ogg similarity index 95% rename from data/samples/beats/break03.ogg rename to data/samples/beats/break03 - 168 BPM.ogg index f806be70a..3b3a4b346 100644 Binary files a/data/samples/beats/break03.ogg and b/data/samples/beats/break03 - 168 BPM.ogg differ diff --git a/data/samples/beats/electro_beat01.ogg b/data/samples/beats/electro_beat01 - 120 BPM.ogg similarity index 99% rename from data/samples/beats/electro_beat01.ogg rename to data/samples/beats/electro_beat01 - 120 BPM.ogg index 57cd690fc..29352b683 100644 Binary files a/data/samples/beats/electro_beat01.ogg and b/data/samples/beats/electro_beat01 - 120 BPM.ogg differ diff --git a/data/samples/beats/electro_beat02.ogg b/data/samples/beats/electro_beat02 - 119 BPM.ogg similarity index 98% rename from data/samples/beats/electro_beat02.ogg rename to data/samples/beats/electro_beat02 - 119 BPM.ogg index b89260bab..775b64d88 100644 Binary files a/data/samples/beats/electro_beat02.ogg and b/data/samples/beats/electro_beat02 - 119 BPM.ogg differ diff --git a/data/samples/beats/house_loop01.ogg b/data/samples/beats/house_loop01 - 142 BPM.ogg similarity index 98% rename from data/samples/beats/house_loop01.ogg rename to data/samples/beats/house_loop01 - 142 BPM.ogg index 09f3a260b..9f04d1deb 100644 Binary files a/data/samples/beats/house_loop01.ogg and b/data/samples/beats/house_loop01 - 142 BPM.ogg differ diff --git a/data/samples/beats/jungle01.ogg b/data/samples/beats/jungle01 - 168 BPM.ogg similarity index 98% rename from data/samples/beats/jungle01.ogg rename to data/samples/beats/jungle01 - 168 BPM.ogg index 9662e4514..b7196044f 100644 Binary files a/data/samples/beats/jungle01.ogg and b/data/samples/beats/jungle01 - 168 BPM.ogg differ diff --git a/data/samples/beats/rave_hihat01.ogg b/data/samples/beats/rave_hihat01 - 180 BPM.ogg similarity index 98% rename from data/samples/beats/rave_hihat01.ogg rename to data/samples/beats/rave_hihat01 - 180 BPM.ogg index 236f447a8..632bb0bec 100644 Binary files a/data/samples/beats/rave_hihat01.ogg and b/data/samples/beats/rave_hihat01 - 180 BPM.ogg differ diff --git a/data/samples/beats/rave_hihat02.ogg b/data/samples/beats/rave_hihat02 - 180 BPM.ogg similarity index 98% rename from data/samples/beats/rave_hihat02.ogg rename to data/samples/beats/rave_hihat02 - 180 BPM.ogg index 33329bd55..a6e5426f4 100644 Binary files a/data/samples/beats/rave_hihat02.ogg and b/data/samples/beats/rave_hihat02 - 180 BPM.ogg differ diff --git a/data/samples/beats/rave_kick01.ogg b/data/samples/beats/rave_kick01 - 180 BPM.ogg similarity index 98% rename from data/samples/beats/rave_kick01.ogg rename to data/samples/beats/rave_kick01 - 180 BPM.ogg index 79f99ffb8..5633f6e1c 100644 Binary files a/data/samples/beats/rave_kick01.ogg and b/data/samples/beats/rave_kick01 - 180 BPM.ogg differ diff --git a/data/samples/beats/rave_kick02.ogg b/data/samples/beats/rave_kick02 - 180 BPM.ogg similarity index 98% rename from data/samples/beats/rave_kick02.ogg rename to data/samples/beats/rave_kick02 - 180 BPM.ogg index 463112869..c57cfc0d9 100644 Binary files a/data/samples/beats/rave_kick02.ogg and b/data/samples/beats/rave_kick02 - 180 BPM.ogg differ diff --git a/data/samples/beats/rave_snare01.ogg b/data/samples/beats/rave_snare01 - 180 BPM.ogg similarity index 98% rename from data/samples/beats/rave_snare01.ogg rename to data/samples/beats/rave_snare01 - 180 BPM.ogg index ceec2d6e0..6e17a6af3 100644 Binary files a/data/samples/beats/rave_snare01.ogg and b/data/samples/beats/rave_snare01 - 180 BPM.ogg differ diff --git a/data/samples/latin/latin_brass01.ogg b/data/samples/latin/latin_brass01 - 140 BPM.ogg similarity index 95% rename from data/samples/latin/latin_brass01.ogg rename to data/samples/latin/latin_brass01 - 140 BPM.ogg index 3bf7dcd27..ac9a2c59b 100644 Binary files a/data/samples/latin/latin_brass01.ogg and b/data/samples/latin/latin_brass01 - 140 BPM.ogg differ diff --git a/data/samples/latin/latin_guitar01.ogg b/data/samples/latin/latin_guitar01 - 126 BPM.ogg similarity index 95% rename from data/samples/latin/latin_guitar01.ogg rename to data/samples/latin/latin_guitar01 - 126 BPM.ogg index 25685013d..3c316ec3b 100644 Binary files a/data/samples/latin/latin_guitar01.ogg and b/data/samples/latin/latin_guitar01 - 126 BPM.ogg differ diff --git a/data/samples/latin/latin_guitar02.ogg b/data/samples/latin/latin_guitar02 - 140 BPM.ogg similarity index 92% rename from data/samples/latin/latin_guitar02.ogg rename to data/samples/latin/latin_guitar02 - 140 BPM.ogg index 3fe4269a2..55b218d98 100644 Binary files a/data/samples/latin/latin_guitar02.ogg and b/data/samples/latin/latin_guitar02 - 140 BPM.ogg differ diff --git a/data/samples/latin/latin_guitar03.ogg b/data/samples/latin/latin_guitar03 - 120 BPM.ogg similarity index 90% rename from data/samples/latin/latin_guitar03.ogg rename to data/samples/latin/latin_guitar03 - 120 BPM.ogg index 0ae9bb3f7..1f6a9275e 100644 Binary files a/data/samples/latin/latin_guitar03.ogg and b/data/samples/latin/latin_guitar03 - 120 BPM.ogg differ diff --git a/data/themes/classic/automation_ghost_note.png b/data/themes/classic/automation_ghost_note.png new file mode 100644 index 000000000..d14c047d7 Binary files /dev/null and b/data/themes/classic/automation_ghost_note.png differ diff --git a/data/themes/classic/cursor_select_left.png b/data/themes/classic/cursor_select_left.png new file mode 100644 index 000000000..eaa80e0bb Binary files /dev/null and b/data/themes/classic/cursor_select_left.png differ diff --git a/data/themes/classic/cursor_select_right.png b/data/themes/classic/cursor_select_right.png new file mode 100644 index 000000000..abd4aecfb Binary files /dev/null and b/data/themes/classic/cursor_select_right.png differ diff --git a/data/themes/classic/fader_background.png b/data/themes/classic/fader_background.png deleted file mode 100644 index 682ff4c92..000000000 Binary files a/data/themes/classic/fader_background.png and /dev/null differ diff --git a/data/themes/classic/fader_leds.png b/data/themes/classic/fader_leds.png deleted file mode 100644 index 6c673cf36..000000000 Binary files a/data/themes/classic/fader_leds.png and /dev/null differ diff --git a/data/themes/classic/lcd_11green.png b/data/themes/classic/lcd_11green.png new file mode 100644 index 000000000..32e923fe8 Binary files /dev/null and b/data/themes/classic/lcd_11green.png differ diff --git a/data/themes/classic/lcd_11green_dot.png b/data/themes/classic/lcd_11green_dot.png new file mode 100644 index 000000000..9f5a660d3 Binary files /dev/null and b/data/themes/classic/lcd_11green_dot.png differ diff --git a/data/themes/classic/style.css b/data/themes/classic/style.css index 522d694ff..5489a7d21 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 { @@ -33,6 +33,10 @@ lmms--gui--AutomationEditor { qproperty-scaleColor: qlineargradient(spread:reflect, x1:0, y1:0.5, x2:1, y2:0.5, stop:0 #333, stop:1 #202020); + + qproperty-ghostNoteColor: rgba(248, 248, 255, 125); + qproperty-detuningNoteColor: rgba(248, 11, 11, 125); + qproperty-ghostSampleColor: rgba(125, 125, 125, 125); } /* text box */ @@ -143,6 +147,7 @@ lmms--gui--PianoRoll { qproperty-backgroundShade: rgba( 255, 255, 255, 10 ); qproperty-noteModeColor: rgb( 255, 255, 255 ); qproperty-noteColor: rgb( 119, 199, 216 ); + qproperty-stepNoteColor: #9b1313; qproperty-noteTextColor: rgb( 255, 255, 255 ); qproperty-noteOpacity: 128; qproperty-noteBorders: true; /* boolean property, set false to have borderless notes */ @@ -184,6 +189,7 @@ lmms--gui--TabWidget { qproperty-tabText: rgba(255, 255, 255, 180); qproperty-tabTitleText: #fff; qproperty-tabSelected: #61666b; + qproperty-tabTextSelected: rgba(255, 255, 255, 180); qproperty-tabBackground: #3c434b; qproperty-tabBorder: #3c434b; } @@ -197,7 +203,9 @@ lmms--gui--GroupBox { lmms--gui--Oscilloscope { background: none; border: none; - qproperty-normalColor: rgb(71, 253, 133); + qproperty-leftChannelColor: rgb(71, 253, 133); + qproperty-rightChannelColor: rgb(238, 253, 71); + qproperty-otherChannelsColor: rgb(71, 235, 253); qproperty-clippingColor: rgb(255, 64, 64); } @@ -205,7 +213,7 @@ lmms--gui--Oscilloscope { lmms--gui--CPULoadWidget { border: none; - background: url(resources:cpuload_bg.png); + background: url("resources:cpuload_bg.png"); qproperty-stepSize: 4; } @@ -322,14 +330,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 */ @@ -344,14 +352,24 @@ lmms--gui--TrackView > QWidget { /* autoscroll, loop, stop behaviour toggle buttons */ -/* track background colors */ +/* track background config */ lmms--gui--TrackContentWidget { - qproperty-darkerColor: qlineargradient(x1:0, y1:0, x2:0, y2:1, - stop:0 rgb( 50, 50, 50 ), stop:0.33 rgb( 20, 20, 20 ), stop:1 rgb( 15, 15, 15 ) ); - qproperty-lighterColor: qlineargradient(x1:0, y1:0, x2:0, y2:1, - stop:0 rgb( 50, 50, 50 ), stop:0.33 rgb( 40, 40, 40 ), stop:1 rgb( 30, 30, 30 ) ); - qproperty-gridColor: rgba( 0, 0, 0, 160 ); - qproperty-embossColor: rgba( 140, 140, 140, 64 ); + /* colors */ + qproperty-darkerColor: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 rgb(50, 50, 50), stop:0.33 rgb(20, 20, 20), stop:1 rgb(15, 15, 15)); + qproperty-lighterColor: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 rgb(50, 50, 50), stop:0.33 rgb(40, 40, 40), stop:1 rgb(30, 30, 30)); + qproperty-coarseGridColor: rgba(0, 0, 0, 160); + qproperty-fineGridColor: rgba(0, 0, 0, 80); + qproperty-horizontalColor: rgba(0, 0, 0, 160); + qproperty-embossColor: rgba(140, 140, 140, 64); + + /* line widths */ + qproperty-coarseGridWidth: 2; + qproperty-fineGridWidth: 1; + qproperty-horizontalWidth: 1; + qproperty-embossWidth: 0; + + /* positive offset shifts emboss to the right */ + qproperty-embossOffset: 0; } @@ -367,7 +385,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; @@ -375,12 +393,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; } @@ -409,7 +427,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; @@ -428,7 +446,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; @@ -627,7 +645,7 @@ lmms--gui--ControllerRackView QPushButton { font-size: 10px; } -lmms--gui--MixerLine { +lmms--gui--MixerChannelView { background: #5b6571; color: #e0e0e0; qproperty-backgroundActive: qlineargradient(spread:reflect, x1:0, y1:0, x2:1, y2:0, @@ -638,11 +656,16 @@ lmms--gui--MixerLine { qproperty-strokeInnerInactive: rgba( 255, 255, 255, 50 ); } +lmms--gui--MixerChannelView QGraphicsView { + background: transparent; + border-style: none; +} + /* persistent peak markers for fx peak meters */ lmms--gui--Fader { - qproperty-peakGreen: rgb( 74, 253, 133); - qproperty-peakYellow: rgb(224, 222, 18); - qproperty-peakRed: rgb( 255, 100, 100); + qproperty-peakOk: rgb( 74, 253, 133); + qproperty-peakWarn: rgb(224, 222, 18); + qproperty-peakClip: rgb( 255, 100, 100); } lmms--gui--TimeLineWidget { @@ -658,15 +681,25 @@ lmms--gui--TimeLineWidget { background-color: qlineargradient( x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 #8796a7, stop: 1.0 #3e454e ); - qproperty-inactiveLoopColor: rgba( 52, 63, 53, 64 ); + qproperty-inactiveLoopColor: rgba( 52, 63, 53, 64 ); qproperty-inactiveLoopBrush: rgba( 255, 255, 255, 32 ); qproperty-inactiveLoopInnerColor: rgba( 255, 255, 255, 32 ); + qproperty-inactiveLoopHandleColor: rgba( 192, 192, 192, 100 ); qproperty-activeLoopColor: rgba( 52, 63, 53, 255 ); qproperty-activeLoopBrush: qlineargradient( x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 #378d59, stop: 1.0 #297e36 ); qproperty-activeLoopInnerColor: rgba( 74, 155, 100, 255 ); + qproperty-activeLoopHandleColor: rgba( 192, 192, 192, 200 ); + + /* Width of loop marker handles (when handle mode is active) */ + qproperty-loopHandleWidth: 8; + qproperty-barLineColor: rgb( 192, 192, 192 ); qproperty-barNumberColor: rgb( 192, 192, 192 ); + + /* Cursor hotspots for loop marker adjustment */ + qproperty-mouseHotspotSelLeft: 0px 16px; + qproperty-mouseHotspotSelRight: 32px 16px; } QTreeView { @@ -899,6 +932,14 @@ lmms--gui--SidInstrumentView lmms--gui--Knob { qproperty-lineWidth: 2; } +lmms--gui--SlicerTView lmms--gui--Knob { + color: rgb(162, 128, 226); + qproperty-outerColor: rgb( 162, 128, 226 ); + qproperty-innerRadius: 1; + qproperty-outerRadius: 11; + qproperty-lineWidth: 3; +} + lmms--gui--WatsynView lmms--gui--Knob { qproperty-innerRadius: 1; qproperty-outerRadius: 7; @@ -967,6 +1008,7 @@ lmms--gui--CompressorControlDialog { qproperty-textColor: rgba(209, 216, 228, 50); qproperty-graphColor: rgba(209, 216, 228, 50); qproperty-resetColor: rgba(200, 100, 15, 200); + qproperty-backgroundColor: rgba(7, 8, 9, 255); } lmms--gui--CompressorControlDialog lmms--gui--Knob { @@ -975,6 +1017,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/automation_ghost_note.png b/data/themes/default/automation_ghost_note.png new file mode 100644 index 000000000..d14c047d7 Binary files /dev/null and b/data/themes/default/automation_ghost_note.png differ diff --git a/data/themes/default/cursor_select_left.png b/data/themes/default/cursor_select_left.png new file mode 100644 index 000000000..eaa80e0bb Binary files /dev/null and b/data/themes/default/cursor_select_left.png differ diff --git a/data/themes/default/cursor_select_right.png b/data/themes/default/cursor_select_right.png new file mode 100644 index 000000000..abd4aecfb Binary files /dev/null and b/data/themes/default/cursor_select_right.png differ diff --git a/data/themes/default/fader_background.png b/data/themes/default/fader_background.png deleted file mode 100644 index 7d6b59e35..000000000 Binary files a/data/themes/default/fader_background.png and /dev/null differ diff --git a/data/themes/default/fader_leds.png b/data/themes/default/fader_leds.png deleted file mode 100644 index 707902e04..000000000 Binary files a/data/themes/default/fader_leds.png and /dev/null differ diff --git a/data/themes/default/lcd_11green.png b/data/themes/default/lcd_11green.png new file mode 100644 index 000000000..32e923fe8 Binary files /dev/null and b/data/themes/default/lcd_11green.png differ diff --git a/data/themes/default/lcd_11green_dot.png b/data/themes/default/lcd_11green_dot.png new file mode 100644 index 000000000..9f5a660d3 Binary files /dev/null and b/data/themes/default/lcd_11green_dot.png differ diff --git a/data/themes/default/lcd_19purple.png b/data/themes/default/lcd_19purple.png new file mode 100644 index 000000000..35ecc4ace Binary files /dev/null and b/data/themes/default/lcd_19purple.png differ diff --git a/data/themes/default/lcd_19purple_dot.png b/data/themes/default/lcd_19purple_dot.png new file mode 100644 index 000000000..6582d8c20 Binary files /dev/null and b/data/themes/default/lcd_19purple_dot.png differ diff --git a/data/themes/default/lcd_21pink.png b/data/themes/default/lcd_21pink.png index c2009eeda..719730427 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/receive_bg_arrow.png b/data/themes/default/receive_bg_arrow.png index d4961540a..368a1bf15 100644 Binary files a/data/themes/default/receive_bg_arrow.png and b/data/themes/default/receive_bg_arrow.png differ diff --git a/data/themes/default/send_bg_arrow.png b/data/themes/default/send_bg_arrow.png index 05a8c7366..311505b68 100644 Binary files a/data/themes/default/send_bg_arrow.png and b/data/themes/default/send_bg_arrow.png differ diff --git a/data/themes/default/style.css b/data/themes/default/style.css index 26b2dd14b..a4f94de09 100644 --- a/data/themes/default/style.css +++ b/data/themes/default/style.css @@ -9,6 +9,7 @@ QLabel, QTreeWidget, QListWidget, QGroupBox, QMenuBar, QCheckBox { QTreeView { outline: none; + alternate-background-color: #111314; } QTreeWidget::item { @@ -65,6 +66,9 @@ lmms--gui--AutomationEditor { qproperty-graphColor: rgba(69,42,153,180); qproperty-scaleColor: #262b30; + qproperty-ghostNoteColor: rgba(248, 248, 255, 125); + qproperty-detuningNoteColor: rgba(248, 11, 11, 125); + qproperty-ghostSampleColor: rgba(125, 125, 125, 125); } /* text box */ @@ -174,6 +178,7 @@ lmms--gui--PianoRoll { qproperty-backgroundShade: rgba(255, 255, 255, 10); qproperty-noteModeColor: #0bd556; qproperty-noteColor: #0bd556; + qproperty-stepNoteColor: #9b1313; qproperty-noteTextColor: #ffffff; qproperty-noteOpacity: 165; qproperty-noteBorders: false; /* boolean property, set false to have borderless notes */ @@ -214,7 +219,8 @@ lmms--gui--TabWidget { background-color: #262b30; qproperty-tabText: rgba(255, 255, 255, 180); qproperty-tabTitleText: #fff; - qproperty-tabSelected: #323940; + qproperty-tabSelected: #1c4933; + qproperty-tabTextSelected: rgba(255, 255, 255, 255); qproperty-tabBackground: #181b1f; qproperty-tabBorder: #181b1f; } @@ -228,7 +234,9 @@ lmms--gui--GroupBox { lmms--gui--Oscilloscope { background: none; border: none; - qproperty-normalColor: rgb(71, 253, 133); + qproperty-leftChannelColor: rgb(71, 253, 133); + qproperty-rightChannelColor: rgb(238, 253, 71); + qproperty-otherChannelsColor: rgb(71, 235, 253); qproperty-clippingColor: rgb(255, 64, 64); } @@ -236,7 +244,7 @@ lmms--gui--Oscilloscope { lmms--gui--CPULoadWidget { border: none; - background: url(resources:cpuload_bg.png); + background: url("resources:cpuload_bg.png"); qproperty-stepSize: 1; } @@ -270,33 +278,33 @@ QScrollBar::add-page:vertical:pressed, QScrollBar::sub-page:vertical:pressed { /* scrollbar: handles (sliders) */ QScrollBar::handle:horizontal { - background: #3f4750; + background: #5d6b77; border: none; border-radius: 4px; min-width: 24px; } QScrollBar::handle:horizontal:hover { - background: #525e69; + background: #b8c4d1; } QScrollBar::handle:horizontal:pressed { - background: rgba(11,213,86,100); + background: #ffffff; } QScrollBar::handle:vertical { - background: #3f4750; + background: #5d6b77; border: none; border-radius: 4px; min-height: 24px; } QScrollBar::handle:vertical:hover { - background: #525e69; + background: #b8c4d1; } QScrollBar::handle:vertical:pressed { - background: rgba(11,213,86,100); + background: #ffffff; } QScrollBar::handle:horizontal:disabled, QScrollBar::handle:vertical:disabled { @@ -355,16 +363,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 */ @@ -380,12 +388,24 @@ lmms--gui--TrackView > QWidget { /* autoscroll, loop, stop behaviour toggle buttons */ -/* track background colors */ +/* track background config */ lmms--gui--TrackContentWidget { + /* colors */ qproperty-darkerColor: #0C0E0F; qproperty-lighterColor: #14151A; - qproperty-gridColor: #262B30; - qproperty-embossColor: rgba( 0, 0, 0, 0 ); + qproperty-coarseGridColor: #3C444C; + qproperty-fineGridColor: #262B30; + qproperty-horizontalColor: #3C444C; + qproperty-embossColor: rgba(0, 0, 0, 0); + + /* line widths */ + qproperty-coarseGridWidth: 2; + qproperty-fineGridWidth: 1; + qproperty-horizontalWidth: 1; + qproperty-embossWidth: 0; + + /* positive offset shifts emboss to the right */ + qproperty-embossOffset: 0; } @@ -401,7 +421,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; @@ -410,7 +430,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; } @@ -433,7 +453,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; @@ -452,7 +472,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; @@ -531,6 +551,12 @@ QToolButton:hover { background: qlineargradient(spread:reflect, x1:0, y1:0, x2:0, y2:1, stop:0 #7c8799, stop:1 #343840) } +QToolButton:hover:checked { + border: 2px solid #343840; + border-radius: 2px; + background: qlineargradient(spread:reflect, x1:0, y1:0, x2:0, y2:1, stop:0 #292d33, stop:1 #22262c) +} + QToolButton:pressed { border-top: 1px solid #778394; border-bottom: 1px solid #1e2226; @@ -542,7 +568,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 */ @@ -591,7 +617,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; @@ -601,7 +627,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; @@ -669,7 +695,7 @@ lmms--gui--ControllerRackView QPushButton { font-size: 10px; } -lmms--gui--MixerLine { +lmms--gui--MixerChannelView { background: #14161A; color: #d1d8e4; qproperty-backgroundActive: #3B424A; @@ -679,11 +705,16 @@ lmms--gui--MixerLine { qproperty-strokeInnerInactive: #0C0D0F; } +lmms--gui--MixerChannelView QGraphicsView { + background: transparent; + border-style: none; +} + /* persistent peak markers for fx peak meters */ lmms--gui--Fader { - qproperty-peakGreen: #0ad45c; - qproperty-peakYellow: #d6ec52; - qproperty-peakRed: #c12038; + qproperty-peakOk: #0ad45c; + qproperty-peakWarn: #d6ec52; + qproperty-peakClip: #c12038; } lmms--gui--TimeLineWidget { @@ -699,27 +730,32 @@ lmms--gui--TimeLineWidget { /* Properties for the loop indicator rectangle in inactive state: - LoopColor: Color of the outermost border - LoopBrush: Brush to paint the main portion of the rectangle - - LoopInnerColor: Color used to paint the inlayed border */ + - LoopInnerColor: Color used to paint the inlayed border + - LoopHandleColor: Color used to paint loop marker handles */ qproperty-inactiveLoopColor: #3B424A; qproperty-inactiveLoopBrush: #3B424A; qproperty-inactiveLoopInnerColor: #3B424A; + qproperty-inactiveLoopHandleColor: rgba( 192, 192, 192, 100 ); /* Properties for the loop indicator rectangle in active state. See above for detailed description. */ qproperty-activeLoopColor: #21A14F; qproperty-activeLoopBrush: #21A14F; qproperty-activeLoopInnerColor: #21A14F; + qproperty-activeLoopHandleColor: rgba( 192, 192, 192, 200 ); /* Vertical padding for the loop indicator rectangle. A value of zero draws the rectangle at the full height of the widget. */ qproperty-loopRectangleVerticalPadding: 1; + /* Width of loop marker handles (when handle mode is active) */ + qproperty-loopHandleWidth: 8; qproperty-barLineColor: rgb( 192, 192, 192 ); qproperty-barNumberColor: rgb( 192, 192, 192 ); -} -QTreeView { - alternate-background-color: #111314; + /* Cursor hotspots for loop marker adjustment */ + qproperty-mouseHotspotSelLeft: 0px 16px; + qproperty-mouseHotspotSelRight: 32px 16px; } lmms--gui--TrackContainerView QLabel @@ -946,6 +982,14 @@ lmms--gui--SidInstrumentView lmms--gui--Knob { qproperty-lineWidth: 2; } +lmms--gui--SlicerTView lmms--gui--Knob { + color: rgb(162, 128, 226); + qproperty-outerColor: rgb( 162, 128, 226 ); + qproperty-innerRadius: 1; + qproperty-outerRadius: 11; + qproperty-lineWidth: 3; +} + lmms--gui--WatsynView lmms--gui--Knob { qproperty-innerRadius: 1; qproperty-outerRadius: 7; @@ -1014,6 +1058,7 @@ lmms--gui--CompressorControlDialog { qproperty-textColor: rgba(209, 216, 228, 50); qproperty-graphColor: rgba(209, 216, 228, 50); qproperty-resetColor: rgba(200, 100, 15, 200); + qproperty-backgroundColor: rgba(7, 8, 9, 255); } lmms--gui--CompressorControlDialog lmms--gui--Knob { @@ -1022,6 +1067,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/AudioAlsa.h b/include/AudioAlsa.h index 975532071..92c47a6ac 100644 --- a/include/AudioAlsa.h +++ b/include/AudioAlsa.h @@ -84,7 +84,6 @@ public: private: void startProcessing() override; void stopProcessing() override; - void applyQualitySettings() override; void run() override; int setHWParams( const ch_cnt_t _channels, snd_pcm_access_t _access ); diff --git a/include/AudioDevice.h b/include/AudioDevice.h index d1a9617cd..577bb7d0e 100644 --- a/include/AudioDevice.h +++ b/include/AudioDevice.h @@ -89,18 +89,10 @@ public: virtual void stopProcessing(); - virtual void applyQualitySettings(); - - - protected: // subclasses can re-implement this for being used in conjunction with // processNextBuffer() - virtual void writeBuffer( const surroundSampleFrame * /* _buf*/, - const fpp_t /*_frames*/, - const float /*_master_gain*/ ) - { - } + virtual void writeBuffer(const surroundSampleFrame* /* _buf*/, const fpp_t /*_frames*/) {} // called by according driver for fetching new sound-data fpp_t getNextBuffer( surroundSampleFrame * _ab ); @@ -109,7 +101,6 @@ protected: // returns num of bytes in outbuf int convertToS16( const surroundSampleFrame * _ab, const fpp_t _frames, - const float _master_gain, int_sample_t * _output_buffer, const bool _convert_endian = false ); @@ -117,13 +108,6 @@ protected: void clearS16Buffer( int_sample_t * _outbuf, const fpp_t _frames ); - // resample given buffer from samplerate _src_sr to samplerate _dst_sr - fpp_t resample( const surroundSampleFrame * _src, - const fpp_t _frames, - surroundSampleFrame * _dst, - const sample_rate_t _src_sr, - const sample_rate_t _dst_sr ); - inline void setSampleRate( const sample_rate_t _new_sr ) { m_sampleRate = _new_sr; @@ -134,8 +118,6 @@ protected: return m_audioEngine; } - bool hqAudio() const; - static void stopProcessingThread( QThread * thread ); @@ -151,9 +133,6 @@ private: QMutex m_devMutex; - SRC_DATA m_srcData; - SRC_STATE * m_srcState; - surroundSampleFrame * m_buffer; }; diff --git a/include/AudioDummy.h b/include/AudioDummy.h index e34260171..6907ad167 100644 --- a/include/AudioDummy.h +++ b/include/AudioDummy.h @@ -104,7 +104,7 @@ private: delete[] b; } - const int microseconds = static_cast( audioEngine()->framesPerPeriod() * 1000000.0f / audioEngine()->processingSampleRate() - timer.elapsed() ); + const int microseconds = static_cast( audioEngine()->framesPerPeriod() * 1000000.0f / audioEngine()->outputSampleRate() - timer.elapsed() ); if( microseconds > 0 ) { usleep( microseconds ); diff --git a/include/AudioEngine.h b/include/AudioEngine.h index d3d0d025f..e434b7f15 100644 --- a/include/AudioEngine.h +++ b/include/AudioEngine.h @@ -25,14 +25,13 @@ #ifndef LMMS_AUDIO_ENGINE_H #define LMMS_AUDIO_ENGINE_H -#include - -#if (QT_VERSION >= QT_VERSION_CHECK(5,14,0)) - #include +#ifdef __MINGW32__ +#include +#else +#include #endif #include -#include #include #include @@ -109,13 +108,6 @@ public: struct qualitySettings { - enum class Mode - { - Draft, - HighQuality, - FinalMix - } ; - enum class Interpolation { Linear, @@ -124,53 +116,11 @@ public: SincBest } ; - enum class Oversampling - { - None, - X2, - X4, - X8 - } ; - Interpolation interpolation; - Oversampling oversampling; - qualitySettings(Mode m) + qualitySettings(Interpolation i) : + interpolation(i) { - switch (m) - { - case Mode::Draft: - interpolation = Interpolation::Linear; - oversampling = Oversampling::None; - break; - case Mode::HighQuality: - interpolation = - Interpolation::SincFastest; - oversampling = Oversampling::X2; - break; - case Mode::FinalMix: - interpolation = Interpolation::SincBest; - oversampling = Oversampling::X8; - break; - } - } - - qualitySettings(Interpolation i, Oversampling o) : - interpolation(i), - oversampling(o) - { - } - - int sampleRateMultiplier() const - { - switch( oversampling ) - { - case Oversampling::None: return 1; - case Oversampling::X2: return 2; - case Oversampling::X4: return 4; - case Oversampling::X8: return 8; - } - return 1; } int libsrcInterpolation() const @@ -290,8 +240,6 @@ public: sample_rate_t baseSampleRate() const; sample_rate_t outputSampleRate() const; sample_rate_t inputSampleRate() const; - sample_rate_t processingSampleRate() const; - inline float masterGain() const { @@ -420,10 +368,6 @@ private: void clearInternal(); - //! Called by the audio thread to give control to other threads, - //! such that they can do changes in the model (like e.g. removing effects) - void runChangesInModel(); - bool m_renderOnly; std::vector m_audioPorts; @@ -453,8 +397,6 @@ private: struct qualitySettings m_qualitySettings; float m_masterGain; - bool m_isProcessing; - // audio device stuff void doSetAudioDevice( AudioDevice *_dev ); AudioDevice * m_audioDev; @@ -476,19 +418,7 @@ private: bool m_clearSignal; - bool m_changesSignal; - unsigned int m_changes; - QMutex m_changesMutex; -#if (QT_VERSION >= QT_VERSION_CHECK(5,14,0)) - QRecursiveMutex m_doChangesMutex; -#else - QMutex m_doChangesMutex; -#endif - QMutex m_waitChangesMutex; - QWaitCondition m_changesAudioEngineCondition; - QWaitCondition m_changesRequestCondition; - - bool m_waitingForWrite; + std::mutex m_changeMutex; friend class Engine; friend class AudioEngineWorkerThread; diff --git a/include/AudioFileFlac.h b/include/AudioFileFlac.h index 944e30478..9432f4231 100644 --- a/include/AudioFileFlac.h +++ b/include/AudioFileFlac.h @@ -65,9 +65,7 @@ private: SF_INFO m_sfinfo; SNDFILE* m_sf; - void writeBuffer(surroundSampleFrame const* _ab, - fpp_t const frames, - float master_gain) override; + void writeBuffer(surroundSampleFrame const* _ab, fpp_t const frames) override; bool startEncoding(); void finishEncoding(); diff --git a/include/AudioFileMP3.h b/include/AudioFileMP3.h index 4289ad211..013c93a3e 100644 --- a/include/AudioFileMP3.h +++ b/include/AudioFileMP3.h @@ -58,9 +58,7 @@ public: } protected: - void writeBuffer( const surroundSampleFrame * /* _buf*/, - const fpp_t /*_frames*/, - const float /*_master_gain*/ ) override; + void writeBuffer(const surroundSampleFrame* /* _buf*/, const fpp_t /*_frames*/) override; private: void flushRemainingBuffers(); diff --git a/include/AudioFileOgg.h b/include/AudioFileOgg.h index 77be8ca1c..fc3ce25b4 100644 --- a/include/AudioFileOgg.h +++ b/include/AudioFileOgg.h @@ -58,9 +58,7 @@ public: private: - void writeBuffer( const surroundSampleFrame * _ab, - const fpp_t _frames, - const float _master_gain ) override; + void writeBuffer(const surroundSampleFrame* _ab, const fpp_t _frames) override; bool startEncoding(); void finishEncoding(); diff --git a/include/AudioFileWave.h b/include/AudioFileWave.h index c186aaaa7..22b124f93 100644 --- a/include/AudioFileWave.h +++ b/include/AudioFileWave.h @@ -56,9 +56,7 @@ public: private: - void writeBuffer( const surroundSampleFrame * _ab, - const fpp_t _frames, - float _master_gain ) override; + void writeBuffer(const surroundSampleFrame* _ab, const fpp_t _frames) override; bool startEncoding(); void finishEncoding(); diff --git a/include/AudioJack.h b/include/AudioJack.h index 164258e5f..01f41f092 100644 --- a/include/AudioJack.h +++ b/include/AudioJack.h @@ -57,88 +57,78 @@ 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(); void startProcessing() override; 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/AudioOss.h b/include/AudioOss.h index 55f64de85..91d456073 100644 --- a/include/AudioOss.h +++ b/include/AudioOss.h @@ -79,7 +79,6 @@ class setupWidget : public gui::AudioDeviceSetupWidget private: void startProcessing() override; void stopProcessing() override; - void applyQualitySettings() override; void run() override; int m_audioFD; diff --git a/include/AudioPort.h b/include/AudioPort.h index d9803d205..9e3ce2bd6 100644 --- a/include/AudioPort.h +++ b/include/AudioPort.h @@ -29,7 +29,6 @@ #include #include -#include "MemoryManager.h" #include "PlayHandle.h" namespace lmms @@ -41,7 +40,6 @@ class BoolModel; class AudioPort : public ThreadableJob { - MM_OPERATORS public: AudioPort( const QString & _name, bool _has_effect_chain = true, FloatModel * volumeModel = nullptr, FloatModel * panningModel = nullptr, diff --git a/include/AudioPortAudio.h b/include/AudioPortAudio.h index 01b8f3fd7..4465b18c1 100644 --- a/include/AudioPortAudio.h +++ b/include/AudioPortAudio.h @@ -109,7 +109,6 @@ public: private: void startProcessing() override; void stopProcessing() override; - void applyQualitySettings() override; #ifdef PORTAUDIO_V19 static int _process_callback( const void *_inputBuffer, void * _outputBuffer, diff --git a/include/AudioPulseAudio.h b/include/AudioPulseAudio.h index b6a998274..db3c566bf 100644 --- a/include/AudioPulseAudio.h +++ b/include/AudioPulseAudio.h @@ -88,7 +88,6 @@ public: private: void startProcessing() override; void stopProcessing() override; - void applyQualitySettings() override; void run() override; volatile bool m_quit; diff --git a/include/AudioResampler.h b/include/AudioResampler.h new file mode 100644 index 000000000..379146962 --- /dev/null +++ b/include/AudioResampler.h @@ -0,0 +1,64 @@ +/* + * AudioResampler.h - wrapper around libsamplerate + * + * Copyright (c) 2023 saker + * + * 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_AUDIO_RESAMPLER_H +#define LMMS_AUDIO_RESAMPLER_H + +#include + +#include "lmms_export.h" + +namespace lmms { + +class LMMS_EXPORT AudioResampler +{ +public: + struct ProcessResult + { + int error; + long inputFramesUsed; + long outputFramesGenerated; + }; + + AudioResampler(int interpolationMode, int channels); + AudioResampler(const AudioResampler&) = delete; + AudioResampler(AudioResampler&&) = delete; + ~AudioResampler(); + + AudioResampler& operator=(const AudioResampler&) = delete; + AudioResampler& operator=(AudioResampler&&) = delete; + + auto resample(const float* in, long inputFrames, float* out, long outputFrames, double ratio) -> ProcessResult; + auto interpolationMode() const -> int { return m_interpolationMode; } + auto channels() const -> int { return m_channels; } + +private: + int m_interpolationMode = -1; + int m_channels = 0; + int m_error = 0; + SRC_STATE* m_state = nullptr; +}; +} // namespace lmms + +#endif // LMMS_AUDIO_RESAMPLER_H diff --git a/include/AudioSampleRecorder.h b/include/AudioSampleRecorder.h index 8937ceb5e..a3e776881 100644 --- a/include/AudioSampleRecorder.h +++ b/include/AudioSampleRecorder.h @@ -28,6 +28,7 @@ #include #include +#include #include "AudioDevice.h" @@ -44,13 +45,10 @@ public: ~AudioSampleRecorder() override; f_cnt_t framesRecorded() const; - void createSampleBuffer( SampleBuffer** sampleBuffer ); - + std::shared_ptr createSampleBuffer(); private: - void writeBuffer( const surroundSampleFrame * _ab, - const fpp_t _frames, - const float _master_gain ) override; + void writeBuffer(const surroundSampleFrame* _ab, const fpp_t _frames) override; using BufferList = QList>; BufferList m_buffers; diff --git a/include/AudioSdl.h b/include/AudioSdl.h index 62db8b68a..5062f79ea 100644 --- a/include/AudioSdl.h +++ b/include/AudioSdl.h @@ -74,7 +74,6 @@ public: private: void startProcessing() override; void stopProcessing() override; - void applyQualitySettings() override; static void sdlAudioCallback( void * _udata, Uint8 * _buf, int _len ); void sdlAudioCallback( Uint8 * _buf, int _len ); diff --git a/include/AudioSndio.h b/include/AudioSndio.h index 594ca94e7..beb4913eb 100644 --- a/include/AudioSndio.h +++ b/include/AudioSndio.h @@ -75,7 +75,6 @@ public: private: void startProcessing() override; void stopProcessing() override; - void applyQualitySettings() override; void run() override; struct sio_hdl *m_hdl; diff --git a/include/AutomatableModel.h b/include/AutomatableModel.h index 2264a592e..15285e17a 100644 --- a/include/AutomatableModel.h +++ b/include/AutomatableModel.h @@ -25,15 +25,15 @@ #ifndef LMMS_AUTOMATABLE_MODEL_H #define LMMS_AUTOMATABLE_MODEL_H +#include #include #include -#include +#include #include "JournallingObject.h" #include "Model.h" #include "TimePos.h" #include "ValueBuffer.h" -#include "MemoryManager.h" #include "ModelVisitor.h" @@ -77,7 +77,6 @@ class ControllerConnection; class LMMS_EXPORT AutomatableModel : public Model, public JournallingObject { Q_OBJECT - MM_OPERATORS public: using AutoModelVector = std::vector; diff --git a/include/AutomationClipView.h b/include/AutomationClipView.h index a20e2ce28..bdd2f0568 100644 --- a/include/AutomationClipView.h +++ b/include/AutomationClipView.h @@ -74,9 +74,6 @@ private: QPixmap m_paintPixmap; QStaticText m_staticTextName; - - static QPixmap * s_clip_rec; - void scaleTimemapToFit( float oldMin, float oldMax ); } ; diff --git a/include/AutomationEditor.h b/include/AutomationEditor.h index dad0e4916..1110e8e4c 100644 --- a/include/AutomationEditor.h +++ b/include/AutomationEditor.h @@ -26,16 +26,18 @@ #ifndef LMMS_GUI_AUTOMATION_EDITOR_H #define LMMS_GUI_AUTOMATION_EDITOR_H +#include #include #include -#include "Editor.h" - -#include "lmms_basics.h" -#include "JournallingObject.h" -#include "TimePos.h" #include "AutomationClip.h" #include "ComboBoxModel.h" +#include "Editor.h" +#include "JournallingObject.h" +#include "MidiClip.h" +#include "SampleClip.h" +#include "TimePos.h" +#include "lmms_basics.h" class QPainter; class QPixmap; @@ -68,8 +70,13 @@ class AutomationEditor : public QWidget, public JournallingObject Q_PROPERTY(QBrush graphColor MEMBER m_graphColor) Q_PROPERTY(QColor crossColor MEMBER m_crossColor) Q_PROPERTY(QColor backgroundShade MEMBER m_backgroundShade) + Q_PROPERTY(QColor ghostNoteColor MEMBER m_ghostNoteColor) + Q_PROPERTY(QColor detuningNoteColor MEMBER m_detuningNoteColor) + Q_PROPERTY(QColor ghostSampleColor MEMBER m_ghostSampleColor) public: void setCurrentClip(AutomationClip * new_clip); + void setGhostMidiClip(MidiClip* newMidiClip); + void setGhostSample(SampleClip* newSample); inline const AutomationClip * currentClip() const { @@ -159,6 +166,13 @@ protected slots: /// Updates the clip's quantization using the current user selected value. void setQuantization(); + void resetGhostNotes() + { + m_ghostNotes = nullptr; + m_ghostSample = nullptr; + update(); + } + private: enum class Action @@ -183,17 +197,23 @@ private: static const int VALUES_WIDTH = 64; + static const int NOTE_HEIGHT = 10; // height of individual notes + static const int NOTE_MARGIN = 40; // total border margin for notes + static const int MIN_NOTE_RANGE = 20; // min number of keys for fixed size + static const int SAMPLE_MARGIN = 40; + static constexpr int MAX_SAMPLE_HEIGHT = 400; // constexpr for use in min + AutomationEditor(); AutomationEditor( const AutomationEditor & ); ~AutomationEditor() override; - 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; + QPixmap m_toolDraw = embed::getIconPixmap("edit_draw"); + QPixmap m_toolErase = embed::getIconPixmap("edit_erase"); + QPixmap m_toolDrawOut = embed::getIconPixmap("edit_draw_outvalue"); + QPixmap m_toolEditTangents = embed::getIconPixmap("edit_tangent"); + QPixmap m_toolMove = embed::getIconPixmap("edit_move"); + QPixmap m_toolYFlip = embed::getIconPixmap("flip_y"); + QPixmap m_toolXFlip = embed::getIconPixmap("flip_x"); ComboBoxModel m_zoomingXModel; ComboBoxModel m_zoomingYModel; @@ -211,6 +231,10 @@ private: float m_bottomLevel; float m_topLevel; + MidiClip* m_ghostNotes = nullptr; + QPointer m_ghostSample = nullptr; // QPointer to set to nullptr on deletion + bool m_renderSample = false; + void centerTopBottomScroll(); void updateTopBottomLevels(); @@ -261,6 +285,9 @@ private: QBrush m_scaleColor; QColor m_crossColor; QColor m_backgroundShade; + QColor m_ghostNoteColor; + QColor m_detuningNoteColor; + QColor m_ghostSampleColor; friend class AutomationEditorWindow; @@ -284,6 +311,9 @@ public: ~AutomationEditorWindow() override = default; void setCurrentClip(AutomationClip* clip); + void setGhostMidiClip(MidiClip* clip) { m_editor->setGhostMidiClip(clip); }; + void setGhostSample(SampleClip* newSample) { m_editor->setGhostSample(newSample); }; + const AutomationClip* currentClip(); void dropEvent( QDropEvent * _de ) override; @@ -337,6 +367,8 @@ private: ComboBox * m_zoomingXComboBox; ComboBox * m_zoomingYComboBox; ComboBox * m_quantizeComboBox; + + QPushButton* m_resetGhostNotes; }; } // namespace gui diff --git a/include/BandLimitedWave.h b/include/BandLimitedWave.h index 1f402aa6e..1c1a052ca 100644 --- a/include/BandLimitedWave.h +++ b/include/BandLimitedWave.h @@ -107,7 +107,7 @@ public: */ static inline float freqToLen( float f ) { - return freqToLen( f, Engine::audioEngine()->processingSampleRate() ); + return freqToLen( f, Engine::audioEngine()->outputSampleRate() ); } /*! \brief This method converts frequency to wavelength, but you can use any custom sample rate with it. diff --git a/include/BarModelEditor.h b/include/BarModelEditor.h new file mode 100644 index 000000000..79a320a7d --- /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/BasicFilters.h b/include/BasicFilters.h index 9351cbafb..25dcf834c 100644 --- a/include/BasicFilters.h +++ b/include/BasicFilters.h @@ -40,7 +40,6 @@ #include "lmms_basics.h" #include "lmms_constants.h" #include "interpolation.h" -#include "MemoryManager.h" namespace lmms { @@ -50,7 +49,6 @@ template class BasicFilters; template class LinkwitzRiley { - MM_OPERATORS public: LinkwitzRiley( float sampleRate ) { @@ -145,9 +143,13 @@ using StereoLinkwitzRiley = LinkwitzRiley<2>; template class BiQuad { - MM_OPERATORS public: - BiQuad() + BiQuad() : + m_a1(0.), + m_a2(0.), + m_b0(0.), + m_b1(0.), + m_b2(0.) { clearHistory(); } @@ -188,7 +190,6 @@ using StereoBiQuad = BiQuad<2>; template class OnePole { - MM_OPERATORS public: OnePole() { @@ -222,7 +223,6 @@ using StereoOnePole = OnePole<2>; template class BasicFilters { - MM_OPERATORS public: enum class FilterType { @@ -328,9 +328,19 @@ public: } } + inline void setSampleRate(const sample_rate_t sampleRate) + { + m_sampleRate = sampleRate; + m_sampleRatio = 1.f / m_sampleRate; + if (m_subFilter != nullptr) + { + m_subFilter->setSampleRate(m_sampleRate); + } + } + inline sample_t update( sample_t _in0, ch_cnt_t _chnl ) { - sample_t out; + sample_t out = 0.0f; switch( m_type ) { case FilterType::Moog: @@ -365,7 +375,6 @@ public: // input signal is linear-interpolated after oversampling, output signal is averaged from oversampled outputs case FilterType::Tripole: { - out = 0.0f; float ip = 0.0f; for( int i = 0; i < 4; ++i ) { @@ -421,7 +430,6 @@ public: case FilterType::Highpass_SV: { float hp; - for( int i = 0; i < 2; ++i ) // 2x oversample { m_delay2[_chnl] = m_delay2[_chnl] + m_svf1 * m_delay1[_chnl]; @@ -434,8 +442,7 @@ public: case FilterType::Notch_SV: { - float hp1, hp2; - + float hp1; for( int i = 0; i < 2; ++i ) // 2x oversample { m_delay2[_chnl] = m_delay2[_chnl] + m_svf1 * m_delay1[_chnl]; /* delay2/4 = lowpass output */ @@ -443,7 +450,7 @@ public: m_delay1[_chnl] = m_svf1 * hp1 + m_delay1[_chnl]; /* delay1/3 = bandpass output */ m_delay4[_chnl] = m_delay4[_chnl] + m_svf2 * m_delay3[_chnl]; - hp2 = m_delay2[_chnl] - m_delay4[_chnl] - m_svq * m_delay3[_chnl]; + float hp2 = m_delay2[_chnl] - m_delay4[_chnl] - m_svq * m_delay3[_chnl]; m_delay3[_chnl] = m_svf2 * hp2 + m_delay3[_chnl]; } @@ -459,19 +466,19 @@ public: case FilterType::Lowpass_RC12: { - sample_t lp, bp, hp, in; + sample_t lp = 0.0f; for( int n = 4; n != 0; --n ) { - in = _in0 + m_rcbp0[_chnl] * m_rcq; + sample_t in = _in0 + m_rcbp0[_chnl] * m_rcq; in = std::clamp(in, -1.0f, 1.0f); lp = in * m_rcb + m_rclp0[_chnl] * m_rca; lp = std::clamp(lp, -1.0f, 1.0f); - hp = m_rcc * ( m_rchp0[_chnl] + in - m_rclast0[_chnl] ); + sample_t hp = m_rcc * (m_rchp0[_chnl] + in - m_rclast0[_chnl]); hp = std::clamp(hp, -1.0f, 1.0f); - bp = hp * m_rcb + m_rcbp0[_chnl] * m_rca; + sample_t bp = hp * m_rcb + m_rcbp0[_chnl] * m_rca; bp = std::clamp(bp, -1.0f, 1.0f); m_rclast0[_chnl] = in; @@ -484,10 +491,10 @@ public: case FilterType::Highpass_RC12: case FilterType::Bandpass_RC12: { - sample_t hp, bp, in; + sample_t hp, bp; for( int n = 4; n != 0; --n ) { - in = _in0 + m_rcbp0[_chnl] * m_rcq; + sample_t in = _in0 + m_rcbp0[_chnl] * m_rcq; in = std::clamp(in, -1.0f, 1.0f); hp = m_rcc * ( m_rchp0[_chnl] + in - m_rclast0[_chnl] ); @@ -505,20 +512,20 @@ public: case FilterType::Lowpass_RC24: { - sample_t lp, bp, hp, in; + sample_t lp; for( int n = 4; n != 0; --n ) { // first stage is as for the 12dB case... - in = _in0 + m_rcbp0[_chnl] * m_rcq; + sample_t in = _in0 + m_rcbp0[_chnl] * m_rcq; in = std::clamp(in, -1.0f, 1.0f); lp = in * m_rcb + m_rclp0[_chnl] * m_rca; lp = std::clamp(lp, -1.0f, 1.0f); - hp = m_rcc * ( m_rchp0[_chnl] + in - m_rclast0[_chnl] ); + sample_t hp = m_rcc * ( m_rchp0[_chnl] + in - m_rclast0[_chnl] ); hp = std::clamp(hp, -1.0f, 1.0f); - bp = hp * m_rcb + m_rcbp0[_chnl] * m_rca; + sample_t bp = hp * m_rcb + m_rcbp0[_chnl] * m_rca; bp = std::clamp(bp, -1.0f, 1.0f); m_rclast0[_chnl] = in; @@ -549,11 +556,11 @@ public: case FilterType::Highpass_RC24: case FilterType::Bandpass_RC24: { - sample_t hp, bp, in; + sample_t hp, bp; for( int n = 4; n != 0; --n ) { // first stage is as for the 12dB case... - in = _in0 + m_rcbp0[_chnl] * m_rcq; + sample_t in = _in0 + m_rcbp0[_chnl] * m_rcq; in = std::clamp(in, -1.0f, 1.0f); hp = m_rcc * ( m_rchp0[_chnl] + in - m_rclast0[_chnl] ); @@ -590,20 +597,18 @@ public: case FilterType::FastFormant: { if (std::abs(_in0) < 1.0e-10f && std::abs(m_vflast[0][_chnl]) < 1.0e-10f) { return 0.0f; } // performance hack - skip processing when the numbers get too small - sample_t hp, bp, in; - out = 0; const int os = m_type == FilterType::FastFormant ? 1 : 4; // no oversampling for fast formant for( int o = 0; o < os; ++o ) { // first formant - in = _in0 + m_vfbp[0][_chnl] * m_vfq; + sample_t in = _in0 + m_vfbp[0][_chnl] * m_vfq; in = std::clamp(in, -1.0f, 1.0f); - hp = m_vfc[0] * ( m_vfhp[0][_chnl] + in - m_vflast[0][_chnl] ); + sample_t hp = m_vfc[0] * ( m_vfhp[0][_chnl] + in - m_vflast[0][_chnl] ); hp = std::clamp(hp, -1.0f, 1.0f); - bp = hp * m_vfb[0] + m_vfbp[0][_chnl] * m_vfa[0]; + sample_t bp = hp * m_vfb[0] + m_vfbp[0][_chnl] * m_vfa[0]; bp = std::clamp(bp, -1.0f, 1.0f); m_vflast[0][_chnl] = in; diff --git a/include/CPULoadWidget.h b/include/CPULoadWidget.h index dfa5bac73..bed10b05e 100644 --- a/include/CPULoadWidget.h +++ b/include/CPULoadWidget.h @@ -68,7 +68,7 @@ private: QTimer m_updateTimer; - int m_stepSize; + int m_stepSize = 1; } ; diff --git a/include/Clip.h b/include/Clip.h index 96394602f..a520ad4e4 100644 --- a/include/Clip.h +++ b/include/Clip.h @@ -25,6 +25,8 @@ #ifndef LMMS_CLIP_H #define LMMS_CLIP_H +#include + #include #include "AutomatableModel.h" @@ -48,7 +50,6 @@ class TrackView; class LMMS_EXPORT Clip : public Model, public JournallingObject { Q_OBJECT - MM_OPERATORS mapPropertyFromModel(bool,isMuted,setMuted,m_mutedModel); mapPropertyFromModel(bool,isSolo,setSolo,m_soloModel); public: @@ -109,24 +110,8 @@ public: return m_autoResize; } - QColor color() const - { - return m_color; - } - - void setColor( const QColor & c ) - { - m_color = c; - } - - bool hasColor(); - - void useCustomClipColor( bool b ); - - bool usesCustomClipColor() - { - return m_useCustomClipColor; - } + auto color() const -> const std::optional& { return m_color; } + void setColor(const std::optional& color); virtual void movePosition( const TimePos & pos ); virtual void changeLength( const TimePos & length ); @@ -177,8 +162,7 @@ private: bool m_selectViewOnCreate; - QColor m_color; - bool m_useCustomClipColor; + std::optional m_color; friend class ClipView; diff --git a/include/ClipView.h b/include/ClipView.h index 942258367..14898db65 100644 --- a/include/ClipView.h +++ b/include/ClipView.h @@ -25,6 +25,7 @@ #ifndef LMMS_GUI_CLIP_VIEW_H #define LMMS_GUI_CLIP_VIEW_H +#include #include @@ -184,6 +185,7 @@ protected: virtual void paintTextLabel(QString const & text, QPainter & painter); + auto hasCustomColor() const -> bool; protected slots: void updateLength(); @@ -241,7 +243,7 @@ private: bool mouseMovedDistance( QMouseEvent * me, int distance ); TimePos draggedClipPos( QMouseEvent * me ); int knifeMarkerPos( QMouseEvent * me ); - void setColor(const QColor* color); + void setColor(const std::optional& color); //! Return true iff the clip could be split. Currently only implemented for samples virtual bool splitClip( const TimePos pos ){ return false; }; void updateCursor(QMouseEvent * me); diff --git a/include/Clipboard.h b/include/Clipboard.h index c6ae66ee8..cee40b33a 100644 --- a/include/Clipboard.h +++ b/include/Clipboard.h @@ -25,8 +25,10 @@ #ifndef LMMS_CLIPBOARD_H #define LMMS_CLIPBOARD_H -#include #include +#include + +#include "lmms_export.h" class QMimeData; @@ -44,7 +46,7 @@ namespace lmms::Clipboard bool hasFormat( MimeType mT ); // Helper methods for String data - void copyString( const QString & str, MimeType mT ); + void LMMS_EXPORT copyString(const QString& str, MimeType mT); QString getString( MimeType mT ); // Helper methods for String Pair data diff --git a/include/ColorHelper.h b/include/ColorHelper.h new file mode 100644 index 000000000..78f99b9e2 --- /dev/null +++ b/include/ColorHelper.h @@ -0,0 +1,54 @@ +/* ColorHelper.h - Helper methods for color related algorithms, etc. + * + * Copyright (c) 2024- 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_COLOR_HELPER_H +#define LMMS_GUI_COLOR_HELPER_H + +#include + +namespace lmms::gui +{ + +class ColorHelper +{ +public: + static QColor interpolateInRgb(const QColor& a, const QColor& b, float t) + { + qreal ar, ag, ab, aa; + a.getRgbF(&ar, &ag, &ab, &aa); + + qreal br, bg, bb, ba; + b.getRgbF(&br, &bg, &bb, &ba); + + const float interH = lerp(ar, br, t); + const float interS = lerp(ag, bg, t); + const float interV = lerp(ab, bb, t); + const float interA = lerp(aa, ba, t); + + return QColor::fromRgbF(interH, interS, interV, interA); + } +}; + +} // namespace lmms::gui + +#endif // LMMS_GUI_COLOR_HELPER_H diff --git a/include/ComboBox.h b/include/ComboBox.h index 8153451e8..cc4ad68dd 100644 --- a/include/ComboBox.h +++ b/include/ComboBox.h @@ -66,9 +66,9 @@ protected: private: - static QPixmap* s_background; - static QPixmap* s_arrow; - static QPixmap* s_arrowSelected; + QPixmap m_background = embed::getIconPixmap("combobox_bg"); + QPixmap m_arrow = embed::getIconPixmap("combobox_arrow"); + QPixmap m_arrowSelected = embed::getIconPixmap("combobox_arrow_selected"); QMenu m_menu; diff --git a/include/ComboBoxModel.h b/include/ComboBoxModel.h index e90d804e2..7037495ea 100644 --- a/include/ComboBoxModel.h +++ b/include/ComboBoxModel.h @@ -47,11 +47,6 @@ public: { } - ~ComboBoxModel() override - { - clear(); - } - void addItem( QString item, std::unique_ptr loader = nullptr ); void replaceItem(std::size_t index, QString item, std::unique_ptr loader = nullptr); diff --git a/include/ControllerRackView.h b/include/ControllerRackView.h index 303cc2b40..93d1e8438 100644 --- a/include/ControllerRackView.h +++ b/include/ControllerRackView.h @@ -65,9 +65,11 @@ public: public slots: - void deleteController( lmms::gui::ControllerView * _view ); - void onControllerAdded( lmms::Controller * ); - void onControllerRemoved( lmms::Controller * ); + void deleteController(ControllerView* view); + void moveUp(ControllerView* view); + void moveDown(ControllerView* view); + void addController(Controller* controller); + void removeController(Controller* controller); protected: void closeEvent( QCloseEvent * _ce ) override; diff --git a/include/ControllerView.h b/include/ControllerView.h index d1ba533a1..9b442672d 100644 --- a/include/ControllerView.h +++ b/include/ControllerView.h @@ -63,12 +63,16 @@ public: public slots: void editControls(); - void deleteController(); + void removeController(); void closeControls(); void renameController(); + void moveUp(); + void moveDown(); signals: - void deleteController( lmms::gui::ControllerView * _view ); + void movedUp(ControllerView* view); + void movedDown(ControllerView* view); + void removedController(ControllerView* view); protected: diff --git a/include/DataFile.h b/include/DataFile.h index dc82315ad..452481a7f 100644 --- a/include/DataFile.h +++ b/include/DataFile.h @@ -28,9 +28,9 @@ #include #include +#include #include "lmms_export.h" -#include "MemoryManager.h" class QTextStream; @@ -42,7 +42,6 @@ class ProjectVersion; class LMMS_EXPORT DataFile : public QDomDocument { - MM_OPERATORS using UpgradeMethod = void(DataFile::*)(); @@ -128,6 +127,9 @@ private: void upgrade_bbTcoRename(); void upgrade_sampleAndHold(); void upgrade_midiCCIndexing(); + void upgrade_loopsRename(); + void upgrade_noteTypes(); + void upgrade_fixCMTDelays(); // List of all upgrade methods static const std::vector UPGRADE_METHODS; @@ -147,7 +149,6 @@ private: QDomElement m_head; Type m_type; unsigned int m_fileVersion; - } ; diff --git a/include/Delay.h b/include/Delay.h index daa871baf..71fbe1b00 100644 --- a/include/Delay.h +++ b/include/Delay.h @@ -29,7 +29,6 @@ #include "lmms_basics.h" #include "lmms_math.h" #include "interpolation.h" -#include "MemoryManager.h" namespace lmms { @@ -74,20 +73,20 @@ public: m_delay( 0 ), m_fraction( 0.0 ) { - m_buffer = MM_ALLOC(maxDelay ); + m_buffer = new frame[maxDelay]; memset( m_buffer, 0, sizeof( frame ) * maxDelay ); } virtual ~CombFeedback() { - MM_FREE( m_buffer ); + delete[] m_buffer; } inline void setMaxDelay( int maxDelay ) { if( maxDelay > m_size ) { - MM_FREE( m_buffer ); - m_buffer = MM_ALLOC( maxDelay ); + delete[] m_buffer; + m_buffer = new frame[maxDelay]; memset( m_buffer, 0, sizeof( frame ) * maxDelay ); } m_size = maxDelay; @@ -145,20 +144,20 @@ class CombFeedfwd m_delay( 0 ), m_fraction( 0.0 ) { - m_buffer = MM_ALLOC( maxDelay ); + m_buffer = new frame[maxDelay]; memset( m_buffer, 0, sizeof( frame ) * maxDelay ); } virtual ~CombFeedfwd() { - MM_FREE( m_buffer ); + delete[] m_buffer; } inline void setMaxDelay( int maxDelay ) { if( maxDelay > m_size ) { - MM_FREE( m_buffer ); - m_buffer = MM_ALLOC( maxDelay ); + delete[] m_buffer; + m_buffer = new frame[maxDelay]; memset( m_buffer, 0, sizeof( frame ) * maxDelay ); } m_size = maxDelay; @@ -216,20 +215,20 @@ class CombFeedbackDualtap m_delay( 0 ), m_fraction( 0.0 ) { - m_buffer = MM_ALLOC( maxDelay ); + m_buffer = new frame[maxDelay]; memset( m_buffer, 0, sizeof( frame ) * maxDelay ); } virtual ~CombFeedbackDualtap() { - MM_FREE( m_buffer ); + delete[] m_buffer; } inline void setMaxDelay( int maxDelay ) { if( maxDelay > m_size ) { - MM_FREE( m_buffer ); - m_buffer = MM_ALLOC( maxDelay ); + delete[] m_buffer; + m_buffer = new frame[maxDelay]; memset( m_buffer, 0, sizeof( frame ) * maxDelay ); } m_size = maxDelay; @@ -297,20 +296,20 @@ public: m_delay( 0 ), m_fraction( 0.0 ) { - m_buffer = MM_ALLOC( maxDelay ); + m_buffer = new frame[maxDelay]; memset( m_buffer, 0, sizeof( frame ) * maxDelay ); } virtual ~AllpassDelay() { - MM_FREE( m_buffer ); + delete[] m_buffer; } inline void setMaxDelay( int maxDelay ) { if( maxDelay > m_size ) { - MM_FREE( m_buffer ); - m_buffer = MM_ALLOC( maxDelay ); + delete[] m_buffer; + m_buffer = new frame[maxDelay]; memset( m_buffer, 0, sizeof( frame ) * maxDelay ); } m_size = maxDelay; diff --git a/include/DetuningHelper.h b/include/DetuningHelper.h index e5d5f5712..da8eb5983 100644 --- a/include/DetuningHelper.h +++ b/include/DetuningHelper.h @@ -27,7 +27,6 @@ #define LMMS_DETUNING_HELPER_H #include "InlineAutomation.h" -#include "MemoryManager.h" namespace lmms { @@ -35,7 +34,6 @@ namespace lmms class DetuningHelper : public InlineAutomation { Q_OBJECT - MM_OPERATORS public: DetuningHelper() : InlineAutomation() diff --git a/include/Effect.h b/include/Effect.h index 1f566e0e9..c3745a352 100644 --- a/include/Effect.h +++ b/include/Effect.h @@ -31,7 +31,6 @@ #include "AudioEngine.h" #include "AutomatableModel.h" #include "TempoSyncKnobModel.h" -#include "MemoryManager.h" namespace lmms { @@ -49,7 +48,6 @@ class EffectView; class LMMS_EXPORT Effect : public Plugin { - MM_OPERATORS Q_OBJECT public: Effect( const Plugin::Descriptor * _desc, @@ -113,7 +111,7 @@ public: inline f_cnt_t timeout() const { - const float samples = Engine::audioEngine()->processingSampleRate() * m_autoQuitModel.value() / 1000.0f; + const float samples = Engine::audioEngine()->outputSampleRate() * m_autoQuitModel.value() / 1000.0f; return 1 + ( static_cast( samples ) / Engine::audioEngine()->framesPerPeriod() ); } @@ -157,6 +155,11 @@ public: { m_noRun = _state; } + + inline TempoSyncKnobModel* autoQuitModel() + { + return &m_autoQuitModel; + } EffectChain * effectChain() const { @@ -189,7 +192,7 @@ protected: sample_rate_t _dst_sr ) { resample( 0, _src_buf, - Engine::audioEngine()->processingSampleRate(), + Engine::audioEngine()->outputSampleRate(), _dst_buf, _dst_sr, Engine::audioEngine()->framesPerPeriod() ); } @@ -199,12 +202,14 @@ protected: sample_rate_t _src_sr ) { resample( 1, _src_buf, _src_sr, _dst_buf, - Engine::audioEngine()->processingSampleRate(), + Engine::audioEngine()->outputSampleRate(), Engine::audioEngine()->framesPerPeriod() * _src_sr / - Engine::audioEngine()->processingSampleRate() ); + Engine::audioEngine()->outputSampleRate() ); } void reinitSRC(); + virtual void onEnabledChanged() {} + private: EffectChain * m_parent; diff --git a/include/EffectRackView.h b/include/EffectRackView.h index a1e21be09..4a90c6b7a 100644 --- a/include/EffectRackView.h +++ b/include/EffectRackView.h @@ -53,10 +53,9 @@ public: public slots: void clearViews(); - void moveUp( lmms::gui::EffectView* view ); - void moveDown( lmms::gui::EffectView* view ); - void deletePlugin( lmms::gui::EffectView* view ); - + void moveUp(EffectView* view); + void moveDown(EffectView* view); + void deletePlugin(EffectView* view); private slots: virtual void update(); diff --git a/include/EffectSelectDialog.h b/include/EffectSelectDialog.h index 493b07774..53e8dbe7e 100644 --- a/include/EffectSelectDialog.h +++ b/include/EffectSelectDialog.h @@ -2,6 +2,7 @@ * EffectSelectDialog.h - dialog to choose effect plugin * * Copyright (c) 2006-2009 Tobias Doerffel + * Copyright (c) 2023 Lost Robot * * This file is part of LMMS - https://lmms.io * @@ -25,49 +26,95 @@ #ifndef LMMS_GUI_EFFECT_SELECT_DIALOG_H #define LMMS_GUI_EFFECT_SELECT_DIALOG_H -#include -#include -#include - #include "Effect.h" - -namespace Ui { class EffectSelectDialog; } +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include namespace lmms::gui { +class DualColumnFilterProxyModel : public QSortFilterProxyModel +{ + Q_OBJECT +public: + DualColumnFilterProxyModel(QObject* parent = nullptr) : QSortFilterProxyModel(parent) + { + } + + void setEffectTypeFilter(const QString& filter) + { + m_effectTypeFilter = filter; + invalidateFilter(); + } + +protected: + bool filterAcceptsRow(int source_row, const QModelIndex& source_parent) const override + { + QModelIndex nameIndex = sourceModel()->index(source_row, 0, source_parent); + QModelIndex typeIndex = sourceModel()->index(source_row, 1, source_parent); + + QString name = sourceModel()->data(nameIndex, Qt::DisplayRole).toString(); + QString type = sourceModel()->data(typeIndex, Qt::DisplayRole).toString(); + + // TODO: cleanup once we drop Qt5 support +#if (QT_VERSION >= QT_VERSION_CHECK(5,12,0)) + QRegularExpression nameRegularExpression(filterRegularExpression()); + nameRegularExpression.setPatternOptions(QRegularExpression::CaseInsensitiveOption); + + bool nameFilterPassed = nameRegularExpression.match(name).capturedStart() != -1; +#else + QRegExp nameRegularExpression(filterRegExp()); + nameRegularExpression.setCaseSensitivity(Qt::CaseInsensitive); + + bool nameFilterPassed = nameRegularExpression.indexIn(name) != -1; +#endif + + bool typeFilterPassed = type.contains(m_effectTypeFilter, Qt::CaseInsensitive); + + return nameFilterPassed && typeFilterPassed; + } + +private: + QString m_effectTypeFilter; +}; + class EffectSelectDialog : public QDialog { Q_OBJECT public: - EffectSelectDialog( QWidget * _parent ); - ~EffectSelectDialog() override; - - Effect * instantiateSelectedPlugin( EffectChain * _parent ); + EffectSelectDialog(QWidget* parent); + Effect* instantiateSelectedPlugin(EffectChain* parent); protected slots: void acceptSelection(); - void rowChanged( const QModelIndex &, const QModelIndex & ); - void sortAgain(); + void rowChanged(const QModelIndex&, const QModelIndex&); void updateSelection(); - + + bool eventFilter(QObject* obj, QEvent* event) override; private: - Ui::EffectSelectDialog * ui; - EffectKeyList m_effectKeys; EffectKey m_currentSelection; QStandardItemModel m_sourceModel; - QSortFilterProxyModel m_model; - QWidget * m_descriptionWidget; - -} ; - + DualColumnFilterProxyModel m_model; + QWidget* m_descriptionWidget; + QTableView* m_pluginList; + QScrollArea* m_scrollArea; + QLineEdit* m_filterEdit; +}; } // namespace lmms::gui -#endif // LMMS_GUI_EFFECT_SELECT_DIALOG_H +#endif diff --git a/include/EffectView.h b/include/EffectView.h index e90700952..805e4a427 100644 --- a/include/EffectView.h +++ b/include/EffectView.h @@ -77,10 +77,9 @@ public slots: signals: - void moveUp( lmms::gui::EffectView * _plugin ); - void moveDown( lmms::gui::EffectView * _plugin ); - void deletePlugin( lmms::gui::EffectView * _plugin ); - + void movedUp(EffectView* view); + void movedDown(EffectView* view); + void deletedPlugin(EffectView* view); protected: void contextMenuEvent( QContextMenuEvent * _me ) override; diff --git a/include/EnvelopeAndLfoParameters.h b/include/EnvelopeAndLfoParameters.h index 7abc3910e..50bfdf787 100644 --- a/include/EnvelopeAndLfoParameters.h +++ b/include/EnvelopeAndLfoParameters.h @@ -25,6 +25,7 @@ #ifndef LMMS_ENVELOPE_AND_LFO_PARAMETERS_H #define LMMS_ENVELOPE_AND_LFO_PARAMETERS_H +#include #include #include "JournallingObject.h" @@ -70,7 +71,18 @@ public: using LfoList = QList; LfoList m_lfos; - } ; + }; + + enum class LfoShape + { + SineWave, + TriangleWave, + SawWave, + SquareWave, + UserDefinedWave, + RandomWave, + Count + }; EnvelopeAndLfoParameters( float _value_for_zero_amount, Model * _parent ); @@ -113,6 +125,28 @@ public: return m_rFrames; } + // Envelope + const FloatModel& getPredelayModel() const { return m_predelayModel; } + const FloatModel& getAttackModel() const { return m_attackModel; } + const FloatModel& getHoldModel() const { return m_holdModel; } + const FloatModel& getDecayModel() const { return m_decayModel; } + const FloatModel& getSustainModel() const { return m_sustainModel; } + const FloatModel& getReleaseModel() const { return m_releaseModel; } + const FloatModel& getAmountModel() const { return m_amountModel; } + FloatModel& getAmountModel() { return m_amountModel; } + + + // LFO + inline f_cnt_t getLfoPredelayFrames() const { return m_lfoPredelayFrames; } + inline f_cnt_t getLfoAttackFrames() const { return m_lfoAttackFrames; } + inline f_cnt_t getLfoOscillationFrames() const { return m_lfoOscillationFrames; } + + const FloatModel& getLfoAmountModel() const { return m_lfoAmountModel; } + FloatModel& getLfoAmountModel() { return m_lfoAmountModel; } + const TempoSyncKnobModel& getLfoSpeedModel() const { return m_lfoSpeedModel; } + const BoolModel& getX100Model() const { return m_x100Model; } + const IntModel& getLfoWaveModel() const { return m_lfoWaveModel; } + std::shared_ptr getLfoUserWave() const { return m_userWave; } public slots: void updateSampleVars(); @@ -167,18 +201,8 @@ private: sample_t * m_lfoShapeData; sample_t m_random; bool m_bad_lfoShapeData; - SampleBuffer m_userWave; + std::shared_ptr m_userWave = SampleBuffer::emptyBuffer(); - enum class LfoShape - { - SineWave, - TriangleWave, - SawWave, - SquareWave, - UserDefinedWave, - RandomWave, - Count - } ; constexpr static auto NumLfoShapes = static_cast(LfoShape::Count); sample_t lfoShapeSample( fpp_t _frame_offset ); diff --git a/include/EnvelopeAndLfoView.h b/include/EnvelopeAndLfoView.h index b5c7a67d4..0063dc788 100644 --- a/include/EnvelopeAndLfoView.h +++ b/include/EnvelopeAndLfoView.h @@ -30,9 +30,6 @@ #include "ModelView.h" -class QPaintEvent; -class QPixmap; - namespace lmms { @@ -46,6 +43,8 @@ class Knob; class LedCheckBox; class PixmapButton; class TempoSyncKnob; +class EnvelopeGraph; +class LfoGraph; @@ -62,8 +61,6 @@ protected: void dragEnterEvent( QDragEnterEvent * _dee ) override; void dropEvent( QDropEvent * _de ) override; - void mousePressEvent( QMouseEvent * _me ) override; - void paintEvent( QPaintEvent * _pe ) override; protected slots: @@ -71,13 +68,10 @@ protected slots: private: - static QPixmap * s_envGraph; - static QPixmap * s_lfoGraph; - EnvelopeAndLfoParameters * m_params; - // envelope stuff + EnvelopeGraph* m_envelopeGraph; Knob * m_predelayKnob; Knob * m_attackKnob; Knob * m_holdKnob; @@ -87,6 +81,7 @@ private: Knob * m_amountKnob; // LFO stuff + LfoGraph* m_lfoGraph; Knob * m_lfoPredelayKnob; Knob * m_lfoAttackKnob; TempoSyncKnob * m_lfoSpeedKnob; @@ -96,8 +91,6 @@ private: LedCheckBox * m_x100Cb; LedCheckBox * m_controlEnvAmountCb; - - float m_randomGraph; } ; } // namespace gui diff --git a/include/EnvelopeGraph.h b/include/EnvelopeGraph.h new file mode 100644 index 000000000..4f8a5c386 --- /dev/null +++ b/include/EnvelopeGraph.h @@ -0,0 +1,77 @@ +/* + * EnvelopeGraph.h - Displays envelope graphs + * + * Copyright (c) 2004-2009 Tobias Doerffel + * Copyright (c) 2024- 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_ENVELOPE_GRAPH_H +#define LMMS_GUI_ENVELOPE_GRAPH_H + +#include + +#include "ModelView.h" +#include "embed.h" + +namespace lmms +{ + +class EnvelopeAndLfoParameters; + +namespace gui +{ + +class EnvelopeGraph : public QWidget, public ModelView +{ +public: + enum class ScalingMode + { + Dynamic, + Absolute, + Relative + }; + +public: + EnvelopeGraph(QWidget* parent); + +protected: + void modelChanged() override; + + void mousePressEvent(QMouseEvent*) override; + void contextMenuEvent(QContextMenuEvent*) override; + void paintEvent(QPaintEvent*) override; + +private: + void toggleAmountModel(); + +private: + QPixmap m_envGraph = embed::getIconPixmap("envelope_graph"); + + EnvelopeAndLfoParameters* m_params = nullptr; + + ScalingMode m_scaling = ScalingMode::Dynamic; +}; + +} // namespace gui + +} // namespace lmms + +#endif // LMMS_GUI_ENVELOPE_GRAPH_H diff --git a/include/Fader.h b/include/Fader.h index b46bed11b..a3158a8b4 100644 --- a/include/Fader.h +++ b/include/Fader.h @@ -53,6 +53,8 @@ #include "AutomatableModelView.h" +#include "embed.h" +#include "lmms_math.h" namespace lmms::gui @@ -65,21 +67,21 @@ class LMMS_EXPORT Fader : public QWidget, public FloatModelView { Q_OBJECT public: - Q_PROPERTY( QColor peakGreen READ peakGreen WRITE setPeakGreen ) - Q_PROPERTY( QColor peakRed READ peakRed WRITE setPeakRed ) - Q_PROPERTY( QColor peakYellow READ peakYellow WRITE setPeakYellow ) - Q_PROPERTY( bool levelsDisplayedInDBFS READ getLevelsDisplayedInDBFS WRITE setLevelsDisplayedInDBFS ) + Q_PROPERTY(QColor peakOk MEMBER m_peakOk) + Q_PROPERTY(QColor peakClip MEMBER m_peakClip) + Q_PROPERTY(QColor peakWarn MEMBER m_peakWarn) + Q_PROPERTY(bool levelsDisplayedInDBFS MEMBER m_levelsDisplayedInDBFS) + Q_PROPERTY(bool renderUnityLine READ getRenderUnityLine WRITE setRenderUnityLine) + Q_PROPERTY(QColor unityMarker MEMBER m_unityMarker) - Fader( FloatModel * _model, const QString & _name, QWidget * _parent ); - Fader( FloatModel * _model, const QString & _name, QWidget * _parent, QPixmap * back, QPixmap * leds, QPixmap * knob ); + Fader(FloatModel* model, const QString& name, QWidget* parent); + Fader(FloatModel* model, const QString& name, QWidget* parent, const QPixmap& knob); ~Fader() override = default; - void init(FloatModel * model, QString const & name); - - void setPeak_L( float fPeak ); + void setPeak_L(float fPeak); float getPeak_L() { return m_fPeakValue_L; } - void setPeak_R( float fPeak ); + void setPeak_R(float fPeak); float getPeak_R() { return m_fPeakValue_R; } inline float getMinPeak() const { return m_fMinPeak; } @@ -88,87 +90,71 @@ public: inline float getMaxPeak() const { return m_fMaxPeak; } inline void setMaxPeak(float maxPeak) { m_fMaxPeak = maxPeak; } - QColor const & peakGreen() const; - void setPeakGreen( const QColor & c ); + inline bool getRenderUnityLine() const { return m_renderUnityLine; } + inline void setRenderUnityLine(bool value = true) { m_renderUnityLine = value; } - QColor const & peakRed() const; - void setPeakRed( const QColor & c ); - - QColor const & peakYellow() const; - void setPeakYellow( const QColor & c ); - - inline bool getLevelsDisplayedInDBFS() const { return m_levelsDisplayedInDBFS; } - inline void setLevelsDisplayedInDBFS(bool value = true) { m_levelsDisplayedInDBFS = value; } - - void setDisplayConversion( bool b ) + void setDisplayConversion(bool b) { m_conversionFactor = b ? 100.0 : 1.0; } - inline void setHintText( const QString & _txt_before, - const QString & _txt_after ) + inline void setHintText(const QString& txt_before, + const QString& txt_after) { - setDescription( _txt_before ); - setUnit( _txt_after ); + setDescription(txt_before); + setUnit(txt_after); } private: - void contextMenuEvent( QContextMenuEvent * _me ) override; - void mousePressEvent( QMouseEvent *ev ) override; - void mouseDoubleClickEvent( QMouseEvent* mouseEvent ) override; - void mouseMoveEvent( QMouseEvent *ev ) override; - void mouseReleaseEvent( QMouseEvent * _me ) override; - void wheelEvent( QWheelEvent *ev ) override; - void paintEvent( QPaintEvent *ev ) override; + void contextMenuEvent(QContextMenuEvent* me) override; + void mousePressEvent(QMouseEvent* ev) override; + void mouseDoubleClickEvent(QMouseEvent* mouseEvent) override; + void mouseMoveEvent(QMouseEvent* ev) override; + void mouseReleaseEvent(QMouseEvent* me) override; + void wheelEvent(QWheelEvent* ev) override; + void paintEvent(QPaintEvent* ev) override; - inline bool clips(float const & value) const { return value >= 1.0f; } - - void paintDBFSLevels(QPaintEvent *ev, QPainter & painter); - void paintLinearLevels(QPaintEvent *ev, QPainter & painter); + void paintLevels(QPaintEvent* ev, QPainter& painter, bool linear = false); int knobPosY() const { float fRange = model()->maxValue() - model()->minValue(); float realVal = model()->value() - model()->minValue(); - return height() - ( ( height() - m_knob->height() ) * ( realVal / fRange ) ); + return height() - ((height() - m_knob.height()) * (realVal / fRange)); } - void setPeak( float fPeak, float &targetPeak, float &persistentPeak, QElapsedTimer &lastPeakTimer ); - int calculateDisplayPeak( float fPeak ); + void setPeak(float fPeak, float& targetPeak, float& persistentPeak, QElapsedTimer& lastPeakTimer); void updateTextFloat(); // Private members private: - float m_fPeakValue_L; - float m_fPeakValue_R; - float m_persistentPeak_L; - float m_persistentPeak_R; - float m_fMinPeak; - float m_fMaxPeak; + float m_fPeakValue_L {0.}; + float m_fPeakValue_R {0.}; + float m_persistentPeak_L {0.}; + float m_persistentPeak_R {0.}; + float m_fMinPeak {dbfsToAmp(-42)}; + float m_fMaxPeak {dbfsToAmp(9)}; QElapsedTimer m_lastPeakTimer_L; QElapsedTimer m_lastPeakTimer_R; - static QPixmap * s_back; - static QPixmap * s_leds; - static QPixmap * s_knob; - - QPixmap * m_back; - QPixmap * m_leds; - QPixmap * m_knob; + QPixmap m_knob {embed::getIconPixmap("fader_knob")}; - bool m_levelsDisplayedInDBFS; + bool m_levelsDisplayedInDBFS {true}; - int m_moveStartPoint; - float m_startValue; + int m_moveStartPoint {-1}; + float m_startValue {0.}; - static SimpleTextFloat * s_textFloat; + static SimpleTextFloat* s_textFloat; - QColor m_peakGreen; - QColor m_peakRed; - QColor m_peakYellow; + QColor m_peakOk {10, 212, 92}; + QColor m_peakClip {193, 32, 56}; + QColor m_peakWarn {214, 236, 82}; + QColor m_unityMarker {63, 63, 63, 255}; + + bool m_renderUnityLine {true}; } ; diff --git a/include/FileBrowser.h b/include/FileBrowser.h index eafb827da..9185f5147 100644 --- a/include/FileBrowser.h +++ b/include/FileBrowser.h @@ -28,14 +28,19 @@ #include #include #include +#include +#include + +#include "FileSearch.h" +#include "embed.h" + #if (QT_VERSION >= QT_VERSION_CHECK(5,14,0)) #include #endif #include - #include "SideBarWidget.h" - +#include "lmmsconfig.h" class QLineEdit; @@ -66,16 +71,31 @@ public: */ FileBrowser( const QString & directories, const QString & filter, const QString & title, const QPixmap & pm, - QWidget * parent, bool dirs_as_items = false, bool recurse = false, + QWidget * parent, bool dirs_as_items = false, const QString& userDir = "", const QString& factoryDir = ""); ~FileBrowser() override = default; + static QStringList excludedPaths() + { + static auto s_excludedPaths = QStringList{ +#ifdef LMMS_BUILD_LINUX + "/bin", "/boot", "/dev", "/etc", "/proc", "/run", "/sbin", + "/sys" +#endif +#ifdef LMMS_BUILD_WIN32 + "C:\\Windows" +#endif + }; + return s_excludedPaths; + } + static QDir::Filters dirFilters() { return QDir::AllDirs | QDir::Files | QDir::NoDotAndDotDot; } + static QDir::SortFlags sortFlags() { return QDir::LocaleAware | QDir::DirsFirst | QDir::Name | QDir::IgnoreCase; } + private slots: void reloadTree(); - void expandItems( QTreeWidgetItem * item=nullptr, QList expandedDirs = QList() ); - bool filterAndExpandItems(const QString & filter, QTreeWidgetItem * item = nullptr); + void expandItems(const QList& expandedDirs, QTreeWidgetItem* item = nullptr); void giveFocusToFilter(); private: @@ -86,15 +106,23 @@ private: void saveDirectoriesStates(); void restoreDirectoriesStates(); + void foundSearchMatch(FileSearch* search, const QString& match); + void searchCompleted(FileSearch* search); + void onSearch(const QString& filter); + void displaySearch(bool on); + FileBrowserTreeWidget * m_fileBrowserTreeWidget; + FileBrowserTreeWidget * m_searchTreeWidget; QLineEdit * m_filterEdit; + std::shared_ptr m_currentSearch; + QProgressBar* m_searchIndicator = nullptr; + QString m_directories; //!< Directories to search, split with '*' QString m_filter; //!< Filter as used in QDir::match() bool m_dirsAsItems; - bool m_recurse; void addContentCheckBox(); QCheckBox* m_showUserContent = nullptr; @@ -167,12 +195,10 @@ private slots: - class Directory : public QTreeWidgetItem { public: - Directory( const QString & filename, const QString & path, - const QString & filter ); + Directory(const QString& filename, const QString& path, const QString& filter, bool disableEntryPopulation = false); void update(); @@ -197,14 +223,12 @@ public: private: - void initPixmaps(); - bool addItems( const QString & path ); - static QPixmap * s_folderPixmap; - static QPixmap * s_folderOpenedPixmap; - static QPixmap * s_folderLockedPixmap; + QPixmap m_folderPixmap = embed::getIconPixmap("folder"); + QPixmap m_folderOpenedPixmap = embed::getIconPixmap("folder_opened"); + QPixmap m_folderLockedPixmap = embed::getIconPixmap("folder_locked"); //! Directories that lead here //! Initially, this is just set to the current path of a directory @@ -217,7 +241,7 @@ private: QString m_filter; int m_dirCount; - + bool m_disableEntryPopulation = false; } ; @@ -274,20 +298,13 @@ public: QString extension(); static QString extension( const QString & file ); + static QString defaultFilters(); private: void initPixmaps(); void determineFileType(); - static QPixmap * s_projectFilePixmap; - static QPixmap * s_presetFilePixmap; - static QPixmap * s_sampleFilePixmap; - static QPixmap * s_soundfontFilePixmap; - static QPixmap * s_vstPluginFilePixmap; - static QPixmap * s_midiFilePixmap; - static QPixmap * s_unknownFilePixmap; - QString m_path; FileType m_type; FileHandling m_handling; diff --git a/include/FileSearch.h b/include/FileSearch.h new file mode 100644 index 000000000..fd311c903 --- /dev/null +++ b/include/FileSearch.h @@ -0,0 +1,73 @@ +/* + * FileSearch.h - File system search task + * + * Copyright (c) 2024 saker + * + * 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_FILE_SEARCH_H +#define LMMS_FILE_SEARCH_H + +#include +#include +#include + +namespace lmms { +//! A Qt object that encapsulates the operation of searching the file system. +class FileSearch : public QObject +{ + Q_OBJECT +public: + //! Number of milliseconds the search waits before signaling a matching result. + static constexpr int MillisecondsBetweenResults = 1; + + //! Create a `FileSearch` object that uses the specified string filter `filter` and extension filters in + //! `extensions` to search within the given `paths`. + //! `excludedPaths`, `dirFilters`, and `sortFlags` can optionally be specified to exclude certain directories, filter + //! out certain types of entries, and sort the matches. + FileSearch(const QString& filter, const QStringList& paths, const QStringList& extensions, + const QStringList& excludedPaths = {}, QDir::Filters dirFilters = QDir::Filters{}, + QDir::SortFlags sortFlags = QDir::SortFlags{}); + + //! Execute the search, emitting the `foundResult` signal when matches are found. + void operator()(); + + //! Cancel the search. + void cancel(); + +signals: + //! Emitted when a result is found when searching the file system. + void foundMatch(FileSearch* search, const QString& match); + + //! Emitted when the search completes. + void searchCompleted(FileSearch* search); + +private: + static auto isPathExcluded(const QString& path) -> bool; + QString m_filter; + QStringList m_paths; + QStringList m_extensions; + QStringList m_excludedPaths; + QDir::Filters m_dirFilters; + QDir::SortFlags m_sortFlags; + std::atomic m_cancel = false; +}; +} // namespace lmms +#endif // LMMS_FILE_SEARCH_H diff --git a/include/FloatModelEditorBase.h b/include/FloatModelEditorBase.h new file mode 100644 index 000000000..72f1450de --- /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/GroupBox.h b/include/GroupBox.h index 6a8f424f9..fdeb31c4d 100644 --- a/include/GroupBox.h +++ b/include/GroupBox.h @@ -50,6 +50,21 @@ public: return m_led; } + /** + * @brief Returns whether the LED button is shown or not + * + * @return true LED button is shown + * @return false LED button is hidden + */ + bool ledButtonShown() const; + + /** + * @brief Sets if the LED check box is shown or not + * + * @param value Set to true to show the LED check box or to false to hide it. + */ + void setLedButtonShown(bool value); + int titleBarHeight() const { return m_titleBarHeight; diff --git a/include/Instrument.h b/include/Instrument.h index f23e0b401..e2e980372 100644 --- a/include/Instrument.h +++ b/include/Instrument.h @@ -31,10 +31,12 @@ #include "Flags.h" #include "lmms_export.h" #include "lmms_basics.h" -#include "MemoryManager.h" #include "Plugin.h" #include "TimePos.h" +#include + + namespace lmms { @@ -47,7 +49,6 @@ class Track; class LMMS_EXPORT Instrument : public Plugin { - MM_OPERATORS public: enum class Flag { @@ -61,7 +62,8 @@ public: Instrument(InstrumentTrack * _instrument_track, const Descriptor * _descriptor, - const Descriptor::SubPluginFeatures::Key * key = nullptr); + const Descriptor::SubPluginFeatures::Key * key = nullptr, + Flags flags = Flag::NoFlags); ~Instrument() override = default; // -------------------------------------------------------------------- @@ -93,18 +95,39 @@ public: virtual f_cnt_t beatLen( NotePlayHandle * _n ) const; - // some instruments need a certain number of release-frames even - // if no envelope is active - such instruments can re-implement this - // method for returning how many frames they at least like to have for - // release - virtual f_cnt_t desiredReleaseFrames() const + // This method can be overridden by instruments that need a certain + // release time even if no envelope is active. It returns the time + // in milliseconds that these instruments would like to have for + // their release stage. + virtual float desiredReleaseTimeMs() const { - return 0; + return 0.f; } - virtual Flags flags() const + // Converts the desired release time in milliseconds to the corresponding + // number of frames depending on the sample rate. + f_cnt_t desiredReleaseFrames() const { - return Flag::NoFlags; + const sample_rate_t sampleRate = getSampleRate(); + + return static_cast(std::ceil(desiredReleaseTimeMs() * sampleRate / 1000.f)); + } + + sample_rate_t getSampleRate() const; + + bool isSingleStreamed() const + { + return m_flags.testFlag(Instrument::Flag::IsSingleStreamed); + } + + bool isMidiBased() const + { + return m_flags.testFlag(Instrument::Flag::IsMidiBased); + } + + bool isBendable() const + { + return !m_flags.testFlag(Instrument::Flag::IsNotBendable); } // sub-classes can re-implement this for receiving all incoming @@ -144,11 +167,13 @@ protected: // desiredReleaseFrames() frames are left void applyRelease( sampleFrame * buf, const NotePlayHandle * _n ); + float computeReleaseTimeMsByFrameCount(f_cnt_t frames) const; + private: InstrumentTrack * m_instrumentTrack; - -} ; + Flags m_flags; +}; LMMS_DECLARE_OPERATORS_FOR_FLAGS(Instrument::Flag) diff --git a/include/InstrumentSoundShapingView.h b/include/InstrumentSoundShapingView.h index 8f671514a..c9caea28c 100644 --- a/include/InstrumentSoundShapingView.h +++ b/include/InstrumentSoundShapingView.h @@ -56,7 +56,7 @@ private: void modelChanged() override; - InstrumentSoundShaping * m_ss; + InstrumentSoundShaping * m_ss = nullptr; TabWidget * m_targetsTabWidget; EnvelopeAndLfoView * m_envLfoViews[InstrumentSoundShaping::NumTargets]; diff --git a/include/InstrumentTrack.h b/include/InstrumentTrack.h index 5efafe0c7..3d84df597 100644 --- a/include/InstrumentTrack.h +++ b/include/InstrumentTrack.h @@ -60,7 +60,6 @@ class MidiCCRackView; class LMMS_EXPORT InstrumentTrack : public Track, public MidiEventProcessor { Q_OBJECT - MM_OPERATORS mapPropertyFromModel(int,getVolume,setVolume,m_volumeModel); public: InstrumentTrack( TrackContainer* tc ); diff --git a/include/InstrumentTrackView.h b/include/InstrumentTrackView.h index d7d5fb83a..c7d524b36 100644 --- a/include/InstrumentTrackView.h +++ b/include/InstrumentTrackView.h @@ -25,7 +25,7 @@ #ifndef LMMS_GUI_INSTRUMENT_TRACK_VIEW_H #define LMMS_GUI_INSTRUMENT_TRACK_VIEW_H -#include "MixerLineLcdSpinBox.h" +#include "MixerChannelLcdSpinBox.h" #include "TrackView.h" #include "InstrumentTrack.h" @@ -93,13 +93,15 @@ private slots: void handleConfigChange(QString cls, QString attr, QString value); +private: + static QPixmap determinePixmap(InstrumentTrack* instrumentTrack); private: InstrumentTrackWindow * m_window; // widgets in track-settings-widget TrackLabelButton * m_tlb; - MixerLineLcdSpinBox* m_mixerChannelNumber; + MixerChannelLcdSpinBox* m_mixerChannelNumber; Knob * m_volumeKnob; Knob * m_panningKnob; FadeButton * m_activityIndicator; diff --git a/include/InstrumentTrackWindow.h b/include/InstrumentTrackWindow.h index 971c63899..48a352cbd 100644 --- a/include/InstrumentTrackWindow.h +++ b/include/InstrumentTrackWindow.h @@ -43,7 +43,7 @@ namespace gui { class EffectRackView; -class MixerLineLcdSpinBox; +class MixerChannelLcdSpinBox; class InstrumentFunctionArpeggioView; class InstrumentFunctionNoteStackingView; class InstrumentMidiIOView; @@ -142,7 +142,7 @@ private: QLabel * m_pitchLabel; LcdSpinBox* m_pitchRangeSpinBox; QLabel * m_pitchRangeLabel; - MixerLineLcdSpinBox * m_mixerChannelNumber; + MixerChannelLcdSpinBox * m_mixerChannelNumber; diff --git a/include/IoHelper.h b/include/IoHelper.h index 40c576b83..3c453fa58 100644 --- a/include/IoHelper.h +++ b/include/IoHelper.h @@ -75,13 +75,12 @@ inline FILE* F_OPEN_UTF8(std::string const& fname, const char* mode){ inline int fileToDescriptor(FILE* f, bool closeFile = true) { - int fh; if (f == nullptr) {return -1;} #ifdef LMMS_BUILD_WIN32 - fh = _dup(_fileno(f)); + int fh = _dup(_fileno(f)); #else - fh = dup(fileno(f)); + int fh = dup(fileno(f)); #endif if (closeFile) {fclose(f);} diff --git a/include/Knob.h b/include/Knob.h index d5739bb1c..3c3339a6f 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 @@ public: 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 @@ public: 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(); - 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: 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: QColor m_textColor; KnobType m_knobNum; - -} ; - +}; } // namespace lmms::gui diff --git a/include/LadspaBase.h b/include/LadspaBase.h index 6569c5a30..0a2b067d4 100644 --- a/include/LadspaBase.h +++ b/include/LadspaBase.h @@ -26,6 +26,8 @@ #ifndef LMMS_LADSPA_BASE_H #define LMMS_LADSPA_BASE_H +#include + #include "LadspaManager.h" #include "Plugin.h" @@ -75,7 +77,7 @@ inline Plugin::Descriptor::SubPluginFeatures::Key ladspaKeyToSubPluginKey( { Plugin::Descriptor::SubPluginFeatures::Key::AttributeMap m; QString file = _key.first; - m["file"] = file.remove( QRegExp( "\\.so$" ) ).remove( QRegExp( "\\.dll$" ) ); + m["file"] = file.remove(QRegularExpression("\\.so$")).remove(QRegularExpression("\\.dll$")); m["plugin"] = _key.second; return Plugin::Descriptor::SubPluginFeatures::Key( _desc, _name, m ); } diff --git a/include/LadspaControl.h b/include/LadspaControl.h index e4f0cd745..8af8f9923 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 @@ private: friend class gui::LadspaControlView; + friend class gui::LadspaMatrixControlDialog; } ; diff --git a/include/LcdWidget.h b/include/LcdWidget.h index cef121b3f..f900fea15 100644 --- a/include/LcdWidget.h +++ b/include/LcdWidget.h @@ -47,8 +47,6 @@ public: LcdWidget(int numDigits, const QString& style, QWidget* parent, const QString& name = QString(), bool leadingZero = false); - ~LcdWidget() override; - void setValue(int value); void setValue(float value); void setLabel(const QString& label); @@ -98,7 +96,7 @@ private: QString m_display; QString m_label; - QPixmap* m_lcdPixmap; + QPixmap m_lcdPixmap; QColor m_textColor; QColor m_textShadowColor; diff --git a/include/LedCheckBox.h b/include/LedCheckBox.h index e3629e143..4f23cd74b 100644 --- a/include/LedCheckBox.h +++ b/include/LedCheckBox.h @@ -47,13 +47,12 @@ public: 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 ); - - ~LedCheckBox() override; - + LedColor _color = LedColor::Yellow, + bool legacyMode = true); inline const QString & text() { @@ -69,13 +68,18 @@ protected: private: - QPixmap * m_ledOnPixmap; - QPixmap * m_ledOffPixmap; + QPixmap m_ledOnPixmap; + QPixmap m_ledOffPixmap; 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/LfoController.h b/include/LfoController.h index 109edbd3f..01b4b1862 100644 --- a/include/LfoController.h +++ b/include/LfoController.h @@ -87,7 +87,7 @@ protected: private: float m_heldSample; - SampleBuffer * m_userDefSampleBuffer; + std::shared_ptr m_userDefSampleBuffer = SampleBuffer::emptyBuffer(); protected slots: void updatePhase(); diff --git a/include/LfoGraph.h b/include/LfoGraph.h new file mode 100644 index 000000000..9d566770f --- /dev/null +++ b/include/LfoGraph.h @@ -0,0 +1,65 @@ +/* + * LfoGraph.h - Displays LFO graphs + * + * Copyright (c) 2004-2009 Tobias Doerffel + * Copyright (c) 2024- 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_LFO_GRAPH_H +#define LMMS_GUI_LFO_GRAPH_H + +#include + +#include "ModelView.h" +#include "embed.h" + +namespace lmms +{ + +class EnvelopeAndLfoParameters; + +namespace gui +{ + +class LfoGraph : public QWidget, public ModelView +{ +public: + LfoGraph(QWidget* parent); + +protected: + void mousePressEvent(QMouseEvent* me) override; + void paintEvent(QPaintEvent* pe) override; + +private: + void drawInfoText(const EnvelopeAndLfoParameters&); + void toggleAmountModel(); + +private: + QPixmap m_lfoGraph = embed::getIconPixmap("lfo_graph"); + + float m_randomGraph {0.}; +}; + +} // namespace gui + +} // namespace lmms + +#endif // LMMS_GUI_LFO_GRAPH_H diff --git a/include/LocaleHelper.h b/include/LocaleHelper.h index 9c829fcaa..c1e1b4735 100644 --- a/include/LocaleHelper.h +++ b/include/LocaleHelper.h @@ -39,10 +39,9 @@ namespace lmms::LocaleHelper inline double toDouble(QString str, bool* ok = nullptr) { bool isOkay; - double value; QLocale c(QLocale::C); c.setNumberOptions(QLocale::RejectGroupSeparator); - value = c.toDouble(str, &isOkay); + double value = c.toDouble(str, &isOkay); if (!isOkay) { QLocale german(QLocale::German); diff --git a/include/Lv2ControlBase.h b/include/Lv2ControlBase.h index 2d44f0ecf..9bfb40f87 100644 --- a/include/Lv2ControlBase.h +++ b/include/Lv2ControlBase.h @@ -102,9 +102,6 @@ protected: Lv2ControlBase& operator=(const Lv2ControlBase&) = delete; - //! Must be checked after ctor or reload - bool isValid() const { return m_valid; } - /* overrides */ @@ -149,7 +146,6 @@ private: //! fulfill LMMS' requirement of having stereo input and output std::vector> m_procs; - bool m_valid = true; bool m_hasGUI = false; unsigned m_channelsPerProc; diff --git a/include/Lv2Options.h b/include/Lv2Options.h index ca4fe2b7f..69d294cbe 100644 --- a/include/Lv2Options.h +++ b/include/Lv2Options.h @@ -30,8 +30,8 @@ #ifdef LMMS_HAVE_LV2 #include -#include -#include +#include +#include #include #include #include @@ -84,6 +84,8 @@ public: return m_options.data(); } + void clear(); + private: //! Initialize an option internally void initOption(LV2_URID key, diff --git a/include/Lv2Proc.h b/include/Lv2Proc.h index 76fa5eec2..65bd90698 100644 --- a/include/Lv2Proc.h +++ b/include/Lv2Proc.h @@ -66,6 +66,7 @@ namespace Lv2Ports //! For Mono effects, 1 Lv2ControlBase references 2 Lv2Proc. class Lv2Proc : public LinkedModelGroup { + friend class Lv2ProcSuspender; public: static Plugin::Type check(const LilvPlugin* plugin, std::vector &issues); @@ -77,8 +78,6 @@ public: ~Lv2Proc() override; void reload(); void onSampleRateChanged(); - //! Must be checked after ctor or reload - bool isValid() const { return m_valid; } /* port access @@ -172,10 +171,8 @@ protected: void shutdownPlugin(); private: - bool m_valid = true; - const LilvPlugin* m_plugin; - LilvInstance* m_instance; + LilvInstance* m_instance = nullptr; Lv2Features m_features; // options diff --git a/include/Lv2UridMap.h b/include/Lv2UridMap.h index 6c22aca3e..23f8f758a 100644 --- a/include/Lv2UridMap.h +++ b/include/Lv2UridMap.h @@ -29,7 +29,7 @@ #ifdef LMMS_HAVE_LV2 -#include +#include #include // TODO: use semaphore, even though this is not realtime critical #include #include diff --git a/include/Lv2ViewBase.h b/include/Lv2ViewBase.h index 3c8f1bc3f..43086849c 100644 --- a/include/Lv2ViewBase.h +++ b/include/Lv2ViewBase.h @@ -37,7 +37,7 @@ class QPushButton; class QMdiSubWindow; - +class QLabel; namespace lmms { @@ -64,9 +64,25 @@ private: }; + + +class HelpWindowEventFilter : public QObject +{ + Q_OBJECT + class Lv2ViewBase* const m_viewBase; +protected: + bool eventFilter(QObject* obj, QEvent* event) override; +public: + HelpWindowEventFilter(class Lv2ViewBase* viewBase); +}; + + + + //! Base class for view for one Lv2 plugin class LMMS_EXPORT Lv2ViewBase : public LinkedModelGroupsView { + friend class HelpWindowEventFilter; protected: //! @param pluginWidget A child class which inherits QWidget Lv2ViewBase(class QWidget *pluginWidget, Lv2ControlBase *ctrlBase); @@ -79,6 +95,7 @@ protected: void toggleUI(); void toggleHelp(bool visible); + void closeHelpWindow(); // to be called by child virtuals //! Reconnect models if model changed @@ -94,12 +111,14 @@ private: static AutoLilvNode uri(const char *uriStr); LinkedModelGroupView* getGroupView() override { return m_procView; } + void onHelpWindowClosed(); Lv2ViewProc* m_procView; //! Numbers of controls per row; must be multiple of 2 for mono effects const int m_colNum = 6; QMdiSubWindow* m_helpWindow = nullptr; + HelpWindowEventFilter m_helpWindowEventFilter; }; diff --git a/include/Lv2Worker.h b/include/Lv2Worker.h index 7931f8e7c..b15bd9026 100644 --- a/include/Lv2Worker.h +++ b/include/Lv2Worker.h @@ -30,7 +30,7 @@ #ifdef LMMS_HAVE_LV2 #include -#include +#include #include #include @@ -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 @@ private: 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/MainWindow.h b/include/MainWindow.h index 30d52ec3a..4442a7ac2 100644 --- a/include/MainWindow.h +++ b/include/MainWindow.h @@ -248,7 +248,6 @@ private slots: void onExportProject(); void onExportProjectTracks(); void onImportProject(); - void onSongStopped(); void onSongModified(); void onProjectFileNameChanged(); diff --git a/include/MemoryManager.h b/include/MemoryManager.h deleted file mode 100644 index fa2fe8110..000000000 --- a/include/MemoryManager.h +++ /dev/null @@ -1,111 +0,0 @@ -/* - * MemoryManager.h - * - * Copyright (c) 2017 Lukas W - * Copyright (c) 2014 Vesa Kivimäki - * Copyright (c) 2007-2014 Tobias Doerffel - * - * This file is part of LMMS - https://lmms.io - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public - * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public - * License along with this program (see COPYING); if not, write to the - * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301 USA. - * - */ - -#ifndef LMMS_MEMORY_MANAGER_H -#define LMMS_MEMORY_MANAGER_H - -#include -#include - -#include "lmms_export.h" - -namespace lmms -{ - - -class LMMS_EXPORT MemoryManager -{ -public: - struct ThreadGuard - { - ThreadGuard(); - ~ThreadGuard(); - }; - - static void * alloc( size_t size ); - static void free( void * ptr ); -}; - -template -struct MmAllocator -{ - using value_type = T; - template struct rebind { - using other = MmAllocator; - }; - - T* allocate( std::size_t n ) - { - return reinterpret_cast( MemoryManager::alloc( sizeof(T) * n ) ); - } - - void deallocate( T* p, std::size_t ) - { - MemoryManager::free( p ); - } - - using vector = std::vector>; -}; - - -#define MM_OPERATORS \ -public: \ -static void * operator new ( size_t size ) \ -{ \ - return MemoryManager::alloc( size ); \ -} \ -static void * operator new[] ( size_t size ) \ -{ \ - return MemoryManager::alloc( size ); \ -} \ -static void operator delete ( void * ptr ) \ -{ \ - MemoryManager::free( ptr ); \ -} \ -static void operator delete[] ( void * ptr ) \ -{ \ - MemoryManager::free( ptr ); \ -} - -// for use in cases where overriding new/delete isn't a possibility -template -T* MM_ALLOC(size_t count) -{ - return reinterpret_cast( - MemoryManager::alloc(sizeof(T) * count)); -} - -// and just for symmetry... -template -void MM_FREE(T* ptr) -{ - MemoryManager::free(ptr); -} - - -} // namespace lmms - -#endif // LMMS_MEMORY_MANAGER_H diff --git a/include/MidiClip.h b/include/MidiClip.h index c2287bd00..b3ed0d84a 100644 --- a/include/MidiClip.h +++ b/include/MidiClip.h @@ -63,7 +63,8 @@ public: // note management Note * addNote( const Note & _new_note, const bool _quant_pos = true ); - void removeNote( Note * _note_to_del ); + NoteVector::const_iterator removeNote(NoteVector::const_iterator it); + NoteVector::const_iterator removeNote(Note* note); Note * noteAtStep( int _step ); diff --git a/include/MidiClipView.h b/include/MidiClipView.h index 6558688b4..4285bf9da 100644 --- a/include/MidiClipView.h +++ b/include/MidiClipView.h @@ -27,6 +27,7 @@ #include #include "ClipView.h" +#include "embed.h" namespace lmms { @@ -70,6 +71,7 @@ public slots: protected slots: void openInPianoRoll(); void setGhostInPianoRoll(); + void setGhostInAutomationEditor(); void resetName(); void changeName(); @@ -85,10 +87,10 @@ protected: private: - static QPixmap * s_stepBtnOn0; - static QPixmap * s_stepBtnOn200; - static QPixmap * s_stepBtnOff; - static QPixmap * s_stepBtnOffLight; + QPixmap m_stepBtnOn0 = embed::getIconPixmap("step_btn_on_0"); + QPixmap m_stepBtnOn200 = embed::getIconPixmap("step_btn_on_200"); + QPixmap m_stepBtnOff = embed::getIconPixmap("step_btn_off"); + QPixmap m_stepBtnOffLight = embed::getIconPixmap("step_btn_off_light"); MidiClip* m_clip; QPixmap m_paintPixmap; @@ -99,7 +101,7 @@ private: QColor m_mutedNoteBorderColor; QStaticText m_staticTextName; - + bool m_legacySEPattern; } ; diff --git a/include/MidiEventProcessor.h b/include/MidiEventProcessor.h index 1c45b3e3f..0fcb9610e 100644 --- a/include/MidiEventProcessor.h +++ b/include/MidiEventProcessor.h @@ -26,7 +26,6 @@ #define LMMS_MIDI_EVENT_PROCESSOR_H #include "MidiEvent.h" -#include "MemoryManager.h" #include "TimePos.h" namespace lmms @@ -35,7 +34,6 @@ namespace lmms // all classes being able to process MIDI-events should inherit from this class MidiEventProcessor { - MM_OPERATORS public: MidiEventProcessor() = default; diff --git a/include/MixHelpers.h b/include/MixHelpers.h index 6458c65fc..dde17dd02 100644 --- a/include/MixHelpers.h +++ b/include/MixHelpers.h @@ -45,6 +45,8 @@ bool sanitize( sampleFrame * src, int frames ); /*! \brief Add samples from src to dst */ void add( sampleFrame* dst, const sampleFrame* src, int frames ); +/*! \brief Multiply samples from `dst` by `coeff` */ +void multiply(sampleFrame* dst, float coeff, int frames); /*! \brief Add samples from src multiplied by coeffSrc to dst */ void addMultiplied( sampleFrame* dst, const sampleFrame* src, float coeffSrc, int frames ); diff --git a/include/Mixer.h b/include/Mixer.h index 35787a414..302492cab 100644 --- a/include/Mixer.h +++ b/include/Mixer.h @@ -31,7 +31,7 @@ #include "ThreadableJob.h" #include - +#include #include namespace lmms @@ -76,26 +76,18 @@ class MixerChannel : public ThreadableJob bool requiresProcessing() const override { return true; } void unmuteForSolo(); + auto color() const -> const std::optional& { return m_color; } + void setColor(const std::optional& color) { m_color = color; } - void setColor (QColor newColor) - { - m_color = newColor; - m_hasColor = true; - } - - // TODO C++17 and above: use std::optional instead - QColor m_color; - bool m_hasColor; - - std::atomic_int m_dependenciesMet; void incrementDeps(); void processed(); private: void doProcessing() override; -}; + std::optional m_color; +}; class MixerRoute : public QObject { diff --git a/include/MixerLineLcdSpinBox.h b/include/MixerChannelLcdSpinBox.h similarity index 74% rename from include/MixerLineLcdSpinBox.h rename to include/MixerChannelLcdSpinBox.h index 1ae2813f2..0abd9f100 100644 --- a/include/MixerLineLcdSpinBox.h +++ b/include/MixerChannelLcdSpinBox.h @@ -1,5 +1,5 @@ /* - * MixerLineLcdSpinBox.h - a specialization of LcdSpnBox for setting mixer channels + * MixerChannelLcdSpinBox.h - a specialization of LcdSpnBox for setting mixer channels * * Copyright (c) 2004-2014 Tobias Doerffel * @@ -22,8 +22,8 @@ * */ -#ifndef LMMS_GUI_MIXER_LINE_LCD_SPIN_BOX_H -#define LMMS_GUI_MIXER_LINE_LCD_SPIN_BOX_H +#ifndef LMMS_GUI_MIXER_CHANNEL_LCD_SPIN_BOX_H +#define LMMS_GUI_MIXER_CHANNEL_LCD_SPIN_BOX_H #include "LcdSpinBox.h" @@ -34,14 +34,14 @@ namespace lmms::gui class TrackView; -class MixerLineLcdSpinBox : public LcdSpinBox +class MixerChannelLcdSpinBox : public LcdSpinBox { Q_OBJECT public: - MixerLineLcdSpinBox(int numDigits, QWidget * parent, const QString& name, TrackView * tv = nullptr) : + MixerChannelLcdSpinBox(int numDigits, QWidget * parent, const QString& name, TrackView * tv = nullptr) : LcdSpinBox(numDigits, parent, name), m_tv(tv) {} - ~MixerLineLcdSpinBox() override = default; + ~MixerChannelLcdSpinBox() override = default; void setTrackView(TrackView * tv); @@ -56,4 +56,4 @@ private: } // namespace lmms::gui -#endif // LMMS_GUI_MIXER_LINE_LCD_SPIN_BOX_H +#endif // LMMS_GUI_MIXER_CHANNEL_LCD_SPIN_BOX_H diff --git a/include/MixerChannelView.h b/include/MixerChannelView.h new file mode 100644 index 000000000..cbaf0ccc6 --- /dev/null +++ b/include/MixerChannelView.h @@ -0,0 +1,139 @@ +/* + * MixerChannelView.h - the mixer channel view + * + * Copyright (c) 2022 saker + * + * 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 MIXER_CHANNEL_VIEW_H +#define MIXER_CHANNEL_VIEW_H + +#include "EffectRackView.h" +#include "Fader.h" +#include "Knob.h" +#include "LcdWidget.h" +#include "PixmapButton.h" +#include "SendButtonIndicator.h" + +#include +#include +#include +#include +#include + +namespace lmms +{ + class MixerChannel; +} + +namespace lmms::gui +{ + constexpr int MIXER_CHANNEL_INNER_BORDER_SIZE = 3; + constexpr int MIXER_CHANNEL_OUTER_BORDER_SIZE = 1; + + class MixerChannelView : public QWidget + { + Q_OBJECT + Q_PROPERTY(QBrush backgroundActive READ backgroundActive WRITE setBackgroundActive) + Q_PROPERTY(QColor strokeOuterActive READ strokeOuterActive WRITE setStrokeOuterActive) + Q_PROPERTY(QColor strokeOuterInactive READ strokeOuterInactive WRITE setStrokeOuterInactive) + Q_PROPERTY(QColor strokeInnerActive READ strokeInnerActive WRITE setStrokeInnerActive) + Q_PROPERTY(QColor strokeInnerInactive READ strokeInnerInactive WRITE setStrokeInnerInactive) + public: + enum class SendReceiveState + { + None, SendToThis, ReceiveFromThis + }; + + MixerChannelView(QWidget* parent, MixerView* mixerView, int channelIndex); + void paintEvent(QPaintEvent* event) override; + void contextMenuEvent(QContextMenuEvent*) override; + void mousePressEvent(QMouseEvent*) override; + void mouseDoubleClickEvent(QMouseEvent*) override; + bool eventFilter(QObject* dist, QEvent* event) override; + + int channelIndex() const; + void setChannelIndex(int index); + + SendReceiveState sendReceiveState() const; + void setSendReceiveState(const SendReceiveState& state); + + QBrush backgroundActive() const; + void setBackgroundActive(const QBrush& c); + + QColor strokeOuterActive() const; + void setStrokeOuterActive(const QColor& c); + + QColor strokeOuterInactive() const; + void setStrokeOuterInactive(const QColor& c); + + QColor strokeInnerActive() const; + void setStrokeInnerActive(const QColor& c); + + QColor strokeInnerInactive() const; + void setStrokeInnerInactive(const QColor& c); + + public slots: + void renameChannel(); + void resetColor(); + void selectColor(); + void randomizeColor(); + + private slots: + void renameFinished(); + void removeChannel(); + void removeUnusedChannels(); + void moveChannelLeft(); + void moveChannelRight(); + + private: + bool confirmRemoval(int index); + QString elideName(const QString& name); + MixerChannel* mixerChannel() const; + auto isMasterChannel() const -> bool { return m_channelIndex == 0; } + + private: + SendButtonIndicator* m_sendButton; + Knob* m_sendKnob; + LcdWidget* m_channelNumberLcd; + QLineEdit* m_renameLineEdit; + QGraphicsView* m_renameLineEditView; + QLabel* m_sendArrow; + QLabel* m_receiveArrow; + PixmapButton* m_muteButton; + PixmapButton* m_soloButton; + Fader* m_fader; + EffectRackView* m_effectRackView; + MixerView* m_mixerView; + SendReceiveState m_sendReceiveState = SendReceiveState::None; + int m_channelIndex = 0; + bool m_inRename = false; + + QBrush m_backgroundActive; + QColor m_strokeOuterActive; + QColor m_strokeOuterInactive; + QColor m_strokeInnerActive; + QColor m_strokeInnerInactive; + + friend class MixerView; + }; +} // namespace lmms::gui + +#endif \ No newline at end of file diff --git a/include/MixerLine.h b/include/MixerLine.h deleted file mode 100644 index 68a61728c..000000000 --- a/include/MixerLine.h +++ /dev/null @@ -1,120 +0,0 @@ -/* - * MixerLine.h - Mixer line widget - * - * Copyright (c) 2009 Andrew Kelley - * Copyright (c) 2014 Tobias Doerffel - * - * This file is part of LMMS - https://lmms.io - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public - * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public - * License along with this program (see COPYING); if not, write to the - * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301 USA. - * - */ - -#ifndef LMMS_GUI_MIXER_LINE_H -#define LMMS_GUI_MIXER_LINE_H - -#include - -class QGraphicsView; -class QLineEdit; - -namespace lmms::gui -{ - - -class Knob; -class LcdWidget; -class MixerView; -class SendButtonIndicator; - -class MixerLine : public QWidget -{ - Q_OBJECT -public: - Q_PROPERTY( QBrush backgroundActive READ backgroundActive WRITE setBackgroundActive ) - Q_PROPERTY( QColor strokeOuterActive READ strokeOuterActive WRITE setStrokeOuterActive ) - Q_PROPERTY( QColor strokeOuterInactive READ strokeOuterInactive WRITE setStrokeOuterInactive ) - Q_PROPERTY( QColor strokeInnerActive READ strokeInnerActive WRITE setStrokeInnerActive ) - Q_PROPERTY( QColor strokeInnerInactive READ strokeInnerInactive WRITE setStrokeInnerInactive ) - MixerLine( QWidget * _parent, MixerView * _mv, int _channelIndex); - ~MixerLine() override; - - void paintEvent( QPaintEvent * ) override; - void mousePressEvent( QMouseEvent * ) override; - void mouseDoubleClickEvent( QMouseEvent * ) override; - void contextMenuEvent( QContextMenuEvent * ) override; - - inline int channelIndex() { return m_channelIndex; } - void setChannelIndex(int index); - - Knob * m_sendKnob; - SendButtonIndicator * m_sendBtn; - - QBrush backgroundActive() const; - void setBackgroundActive( const QBrush & c ); - - QColor strokeOuterActive() const; - void setStrokeOuterActive( const QColor & c ); - - QColor strokeOuterInactive() const; - void setStrokeOuterInactive( const QColor & c ); - - QColor strokeInnerActive() const; - void setStrokeInnerActive( const QColor & c ); - - QColor strokeInnerInactive() const; - void setStrokeInnerInactive( const QColor & c ); - - static const int MixerLineHeight; - - bool eventFilter (QObject *dist, QEvent *event) override; - -private: - void drawMixerLine( QPainter* p, const MixerLine *mixerLine, bool isActive, bool sendToThis, bool receiveFromThis ); - QString elideName( const QString & name ); - - MixerView * m_mv; - LcdWidget* m_lcd; - int m_channelIndex; - QBrush m_backgroundActive; - QColor m_strokeOuterActive; - QColor m_strokeOuterInactive; - QColor m_strokeInnerActive; - QColor m_strokeInnerInactive; - static QPixmap * s_sendBgArrow; - static QPixmap * s_receiveBgArrow; - bool m_inRename; - QLineEdit * m_renameLineEdit; - QGraphicsView * m_view; - -public slots: - void renameChannel(); - void resetColor(); - void selectColor(); - void randomizeColor(); - -private slots: - void renameFinished(); - void removeChannel(); - void removeUnusedChannels(); - void moveChannelLeft(); - void moveChannelRight(); -}; - - -} // namespace lmms::gui - -#endif // LMMS_GUI_MIXER_LINE_H diff --git a/include/MixerView.h b/include/MixerView.h index 2bb5ed417..89315f93a 100644 --- a/include/MixerView.h +++ b/include/MixerView.h @@ -2,7 +2,7 @@ * MixerView.h - effect-mixer-view for LMMS * * Copyright (c) 2008-2014 Tobias Doerffel - * + * * This file is part of LMMS - https://lmms.io * * This program is free software; you can redistribute it and/or @@ -30,6 +30,7 @@ #include #include +#include "MixerChannelView.h" #include "ModelView.h" #include "Engine.h" #include "Fader.h" @@ -37,65 +38,46 @@ #include "embed.h" #include "EffectRackView.h" -class QButtonGroup; - +namespace lmms +{ + class Mixer; +} namespace lmms::gui { - -class MixerLine; - class LMMS_EXPORT MixerView : public QWidget, public ModelView, public SerializingObjectHook { Q_OBJECT public: - class MixerChannelView + MixerView(Mixer* mixer); + void keyPressEvent(QKeyEvent* e) override; + + void saveSettings(QDomDocument& doc, QDomElement& domElement) override; + void loadSettings(const QDomElement& domElement) override; + + inline MixerChannelView* currentMixerChannel() { - public: - MixerChannelView(QWidget * _parent, MixerView * _mv, int _chIndex ); - - void setChannelIndex( int index ); - - MixerLine * m_mixerLine; - PixmapButton * m_muteBtn; - PixmapButton * m_soloBtn; - Fader * m_fader; - EffectRackView * m_rackView; - }; - - - MixerView(); - ~MixerView() override; - - void keyPressEvent(QKeyEvent * e) override; - - void saveSettings( QDomDocument & _doc, QDomElement & _this ) override; - void loadSettings( const QDomElement & _this ) override; - - inline MixerLine * currentMixerLine() - { - return m_currentMixerLine; + return m_currentMixerChannel; } - inline MixerChannelView * channelView(int index) + inline MixerChannelView* channelView(int index) { return m_mixerChannelViews[index]; } - void setCurrentMixerLine( MixerLine * _line ); - void setCurrentMixerLine( int _line ); + void setCurrentMixerChannel(MixerChannelView* channel); + void setCurrentMixerChannel(int channel); void clear(); // display the send button and knob correctly - void updateMixerLine(int index); + void updateMixerChannel(int index); // notify the view that a mixer channel was deleted void deleteChannel(int index); - bool confirmRemoval(int index); // delete all unused channels void deleteUnusedChannels(); @@ -115,22 +97,33 @@ public slots: int addNewChannel(); protected: - void closeEvent( QCloseEvent * _ce ) override; - + void closeEvent(QCloseEvent* ce) override; + private slots: void updateFaders(); + // TODO This should be improved. Currently the solo and mute models are connected via + // the MixerChannelView's constructor with the MixerView. It would already be an improvement + // if the MixerView connected itself to each new MixerChannel that it creates/handles. void toggledSolo(); + void toggledMute(); private: - QVector m_mixerChannelViews; + Mixer* getMixer() const; + void updateAllMixerChannels(); + void connectToSoloAndMute(int channelIndex); + void disconnectFromSoloAndMute(int channelIndex); - MixerLine * m_currentMixerLine; +private: + QVector m_mixerChannelViews; - QScrollArea * channelArea; - QHBoxLayout * chLayout; - QWidget * m_channelAreaWidget; - QStackedLayout * m_racksLayout; - QWidget * m_racksWidget; + MixerChannelView* m_currentMixerChannel; + + QScrollArea* channelArea; + QHBoxLayout* chLayout; + QWidget* m_channelAreaWidget; + QStackedLayout* m_racksLayout; + QWidget* m_racksWidget; + Mixer* m_mixer; void updateMaxChannelSelector(); diff --git a/include/NStateButton.h b/include/NStateButton.h index a6b4c4d9a..c948fa843 100644 --- a/include/NStateButton.h +++ b/include/NStateButton.h @@ -55,25 +55,20 @@ public: public slots: - void changeState( int _n ); - + void changeState(int state); signals: - void changedState( int _n ); - + void changedState(int state); protected: - void mousePressEvent( QMouseEvent * _me ) override; - + void mousePressEvent(QMouseEvent* me) override; private: - QVector > m_states; + QVector> m_states; QString m_generalToolTip; int m_curState; - -} ; - +}; } // namespace lmms::gui diff --git a/include/NoCopyNoMove.h b/include/NoCopyNoMove.h new file mode 100644 index 000000000..d59ddee83 --- /dev/null +++ b/include/NoCopyNoMove.h @@ -0,0 +1,47 @@ +/* + * NoCopyNoMove.h - NoCopyNoMove class + * + * Copyright (c) 2023-2023 Johannes Lorenz + * + * This file is part of LMMS - https://lmms.io + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program (see COPYING); if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA. + * + */ + +#ifndef LMMS_NOCOPYNOMOVE_H +#define LMMS_NOCOPYNOMOVE_H + +namespace lmms +{ + +/** + * Inherit this class to make your class non-copyable and non-movable + */ +class NoCopyNoMove +{ +protected: + NoCopyNoMove() = default; + NoCopyNoMove(const NoCopyNoMove& other) = delete; + NoCopyNoMove& operator=(const NoCopyNoMove& other) = delete; + NoCopyNoMove(NoCopyNoMove&& other) = delete; + NoCopyNoMove& operator=(NoCopyNoMove&& other) = delete; +}; + +} // namespace lmms + +#endif // LMMS_NOCOPYNOMOVE_H + diff --git a/include/Note.h b/include/Note.h index 2df196af2..08cbce3db 100644 --- a/include/Note.h +++ b/include/Note.h @@ -107,6 +107,16 @@ public: Note( const Note & note ); ~Note() override; + // Note types + enum class Type + { + Regular = 0, + Step + }; + + Type type() const { return m_type; } + inline void setType(Type t) { m_type = t; } + // used by GUI inline void setSelected( const bool selected ) { m_selected = selected; } inline void setOldKey( const int oldKey ) { m_oldKey = oldKey; } @@ -253,6 +263,8 @@ private: TimePos m_length; TimePos m_pos; DetuningHelper * m_detuning; + + Type m_type = Type::Regular; }; using NoteVector = std::vector; diff --git a/include/NotePlayHandle.h b/include/NotePlayHandle.h index 7105d6672..f70268132 100644 --- a/include/NotePlayHandle.h +++ b/include/NotePlayHandle.h @@ -32,7 +32,6 @@ #include "Note.h" #include "PlayHandle.h" #include "Track.h" -#include "MemoryManager.h" class QReadWriteLock; @@ -47,7 +46,6 @@ using ConstNotePlayHandleList = QList; class LMMS_EXPORT NotePlayHandle : public PlayHandle, public Note { - MM_OPERATORS public: void * m_pluginData; std::unique_ptr> m_filter; @@ -273,7 +271,6 @@ public: private: class BaseDetuning { - MM_OPERATORS public: BaseDetuning( DetuningHelper* detuning ); @@ -341,7 +338,6 @@ const int NPH_CACHE_INCREMENT = 16; class NotePlayHandleManager { - MM_OPERATORS public: static void init(); static NotePlayHandle * acquire( InstrumentTrack* instrumentTrack, diff --git a/include/Oscillator.h b/include/Oscillator.h index dab0b948d..ea0227bd0 100644 --- a/include/Oscillator.h +++ b/include/Oscillator.h @@ -28,7 +28,9 @@ #include #include +#include #include +#include "interpolation.h" #include "Engine.h" #include "lmms_constants.h" @@ -46,7 +48,6 @@ class IntModel; class LMMS_EXPORT Oscillator { - MM_OPERATORS public: enum class WaveShape { @@ -91,18 +92,23 @@ public: static void waveTableInit(); static void destroyFFTPlans(); - static void generateAntiAliasUserWaveTable(SampleBuffer* sampleBuffer); + static std::unique_ptr generateAntiAliasUserWaveTable(const SampleBuffer* sampleBuffer); inline void setUseWaveTable(bool n) { m_useWaveTable = n; } - inline void setUserWave( const SampleBuffer * _wave ) + void setUserWave(std::shared_ptr _wave) { m_userWave = _wave; } + void setUserAntiAliasWaveTable(std::shared_ptr waveform) + { + m_userAntiAliasWaveTable = waveform; + } + void update(sampleFrame* ab, const fpp_t frames, const ch_cnt_t chnl, bool modulator = false); // now follow the wave-shape-routines... @@ -164,9 +170,18 @@ public: return 1.0f - fast_rand() * 2.0f / FAST_RAND_MAX; } - inline sample_t userWaveSample( const float _sample ) const + static sample_t userWaveSample(const SampleBuffer* buffer, const float sample) { - return m_userWave->userWaveSample( _sample ); + if (buffer == nullptr || buffer->size() == 0) { return 0; } + const auto frames = buffer->size(); + const auto frame = sample * frames; + auto f1 = static_cast(frame) % frames; + if (f1 < 0) + { + f1 += frames; + } + + return linearInterpolate(buffer->data()[f1][0], buffer->data()[(f1 + 1) % frames][0], fraction(frame)); } struct wtSampleControl { @@ -189,7 +204,7 @@ public: control.f1 + 1 : 0; control.band = waveTableBandFromFreq( - m_freq * m_detuning_div_samplerate * Engine::audioEngine()->processingSampleRate()); + m_freq * m_detuning_div_samplerate * Engine::audioEngine()->outputSampleRate()); return control; } @@ -203,7 +218,7 @@ public: table[control.band][control.f2], fraction(control.frame)); } - inline sample_t wtSample(const std::unique_ptr& table, const float sample) const + sample_t wtSample(const OscillatorConstants::waveform_t* table, const float sample) const { assert(table != nullptr); wtSampleControl control = getWtSampleControl(sample); @@ -247,7 +262,8 @@ private: Oscillator * m_subOsc; float m_phaseOffset; float m_phase; - const SampleBuffer * m_userWave; + std::shared_ptr m_userWave = SampleBuffer::emptyBuffer(); + std::shared_ptr m_userAntiAliasWaveTable; bool m_useWaveTable; // There are many update*() variants; the modulator flag is stored as a member variable to avoid // adding more explicit parameters to all of them. Can be converted to a parameter if needed. diff --git a/include/Oscilloscope.h b/include/Oscilloscope.h index 209370ce0..13c946aa5 100644 --- a/include/Oscilloscope.h +++ b/include/Oscilloscope.h @@ -38,7 +38,9 @@ class Oscilloscope : public QWidget { Q_OBJECT public: - Q_PROPERTY( QColor normalColor READ normalColor WRITE setNormalColor ) + Q_PROPERTY( QColor leftChannelColor READ leftChannelColor WRITE setLeftChannelColor ) + Q_PROPERTY( QColor rightChannelColor READ rightChannelColor WRITE setRightChannelColor ) + Q_PROPERTY( QColor otherChannelsColor READ otherChannelsColor WRITE setOtherChannelsColor ) Q_PROPERTY( QColor clippingColor READ clippingColor WRITE setClippingColor ) Oscilloscope( QWidget * _parent ); @@ -46,8 +48,14 @@ public: void setActive( bool _active ); - QColor const & normalColor() const; - void setNormalColor(QColor const & normalColor); + QColor const & leftChannelColor() const; + void setLeftChannelColor(QColor const & leftChannelColor); + + QColor const & rightChannelColor() const; + void setRightChannelColor(QColor const & rightChannelColor); + + QColor const & otherChannelsColor() const; + void setOtherChannelsColor(QColor const & otherChannelsColor); QColor const & clippingColor() const; void setClippingColor(QColor const & clippingColor); @@ -62,7 +70,7 @@ protected slots: void updateAudioBuffer( const lmms::surroundSampleFrame * buffer ); private: - QColor const & determineLineColor(float level) const; + bool clips(float level) const; private: QPixmap m_background; @@ -71,7 +79,9 @@ private: sampleFrame * m_buffer; bool m_active; - QColor m_normalColor; + QColor m_leftChannelColor; + QColor m_rightChannelColor; + QColor m_otherChannelsColor; QColor m_clippingColor; } ; diff --git a/include/PianoRoll.h b/include/PianoRoll.h index 38788180f..35550a5b3 100644 --- a/include/PianoRoll.h +++ b/include/PianoRoll.h @@ -73,6 +73,7 @@ class PianoRoll : public QWidget Q_PROPERTY(QColor lineColor MEMBER m_lineColor) Q_PROPERTY(QColor noteModeColor MEMBER m_noteModeColor) Q_PROPERTY(QColor noteColor MEMBER m_noteColor) + Q_PROPERTY(QColor stepNoteColor MEMBER m_stepNoteColor) Q_PROPERTY(QColor ghostNoteColor MEMBER m_ghostNoteColor) Q_PROPERTY(QColor noteTextColor MEMBER m_noteTextColor) Q_PROPERTY(QColor ghostNoteTextColor MEMBER m_ghostNoteTextColor) @@ -339,12 +340,12 @@ private: static const int cm_scrollAmtHoriz = 10; static const int cm_scrollAmtVert = 1; - static QPixmap * s_toolDraw; - static QPixmap * s_toolErase; - static QPixmap * s_toolSelect; - static QPixmap * s_toolMove; - static QPixmap * s_toolOpen; - static QPixmap* s_toolKnife; + QPixmap m_toolDraw = embed::getIconPixmap("edit_draw"); + QPixmap m_toolErase = embed::getIconPixmap("edit_erase"); + QPixmap m_toolSelect = embed::getIconPixmap("edit_select"); + QPixmap m_toolMove = embed::getIconPixmap("edit_move"); + QPixmap m_toolOpen = embed::getIconPixmap("automation"); + QPixmap m_toolKnife = embed::getIconPixmap("edit_knife"); static std::array prKeyOrder; @@ -375,6 +376,7 @@ private: TimePos m_currentPosition; bool m_recording; + bool m_doAutoQuantization{false}; QList m_recordingNotes; Note * m_currentNote; @@ -466,6 +468,7 @@ private: QColor m_lineColor; QColor m_noteModeColor; QColor m_noteColor; + QColor m_stepNoteColor; QColor m_noteTextColor; QColor m_ghostNoteColor; QColor m_ghostNoteTextColor; diff --git a/include/PianoView.h b/include/PianoView.h index 6421ff438..3f8d8026f 100644 --- a/include/PianoView.h +++ b/include/PianoView.h @@ -30,6 +30,7 @@ #include "AutomatableModel.h" #include "ModelView.h" +#include "embed.h" namespace lmms { @@ -73,12 +74,12 @@ private: int getKeyHeight(int key_num) const; IntModel *getNearestMarker(int key, QString* title = nullptr); - static QPixmap * s_whiteKeyPm; - static QPixmap * s_blackKeyPm; - static QPixmap * s_whiteKeyPressedPm; - static QPixmap * s_blackKeyPressedPm; - static QPixmap * s_whiteKeyDisabledPm; - static QPixmap * s_blackKeyDisabledPm; + QPixmap m_whiteKeyPm = embed::getIconPixmap("white_key"); + QPixmap m_blackKeyPm = embed::getIconPixmap("black_key"); + QPixmap m_whiteKeyPressedPm = embed::getIconPixmap("white_key_pressed"); + QPixmap m_blackKeyPressedPm = embed::getIconPixmap("black_key_pressed"); + QPixmap m_whiteKeyDisabledPm = embed::getIconPixmap("white_key_disabled"); + QPixmap m_blackKeyDisabledPm = embed::getIconPixmap("black_key_disabled"); Piano * m_piano; diff --git a/include/PixmapButton.h b/include/PixmapButton.h index e8f546dc0..734bd11ae 100644 --- a/include/PixmapButton.h +++ b/include/PixmapButton.h @@ -56,6 +56,8 @@ protected: void mouseReleaseEvent( QMouseEvent * _me ) override; void mouseDoubleClickEvent( QMouseEvent * _me ) override; +private: + bool isActive() const; private: QPixmap m_activePixmap; diff --git a/include/Plugin.h b/include/Plugin.h index 439dd95ad..100e9f658 100644 --- a/include/Plugin.h +++ b/include/Plugin.h @@ -30,7 +30,6 @@ #include "JournallingObject.h" #include "Model.h" -#include "MemoryManager.h" class QWidget; @@ -71,7 +70,6 @@ class PluginView; */ class LMMS_EXPORT Plugin : public Model, public JournallingObject { - MM_OPERATORS Q_OBJECT public: enum class Type diff --git a/include/RemotePluginBase.h b/include/RemotePluginBase.h index 357be1bea..5214b6f92 100644 --- a/include/RemotePluginBase.h +++ b/include/RemotePluginBase.h @@ -41,10 +41,6 @@ #ifdef LMMS_HAVE_PROCESS_H #include #endif - -#include -#include -#include #else // !(LMMS_HAVE_SYS_IPC_H && LMMS_HAVE_SEMAPHORE_H) #ifdef LMMS_HAVE_UNISTD_H #include @@ -75,6 +71,7 @@ #include #include #include +#include #ifndef SYNC_WITH_SHM_FIFO #include @@ -85,6 +82,7 @@ #ifdef SYNC_WITH_SHM_FIFO #include "SharedMemory.h" +#include "SystemSemaphore.h" #endif namespace lmms @@ -120,12 +118,11 @@ class shmFifo } ; public: +#ifndef BUILD_REMOTE_PLUGIN_CLIENT // constructor for master-side shmFifo() : m_invalid( false ), m_master( true ), - m_dataSem( QString() ), - m_messageSem( QString() ), m_lockDepth( 0 ) { m_data.create(QUuid::createUuid().toString().toStdString()); @@ -133,26 +130,21 @@ public: static int k = 0; m_data->dataSem.semKey = ( getpid()<<10 ) + ++k; m_data->messageSem.semKey = ( getpid()<<10 ) + ++k; - m_dataSem.setKey( QString::number( m_data->dataSem.semKey ), - 1, QSystemSemaphore::Create ); - m_messageSem.setKey( QString::number( - m_data->messageSem.semKey ), - 0, QSystemSemaphore::Create ); + m_dataSem = SystemSemaphore{std::to_string(m_data->dataSem.semKey), 1u}; + m_messageSem = SystemSemaphore{std::to_string(m_data->messageSem.semKey), 0u}; } +#endif // constructor for remote-/client-side - use _shm_key for making up // the connection to master shmFifo(const std::string& shmKey) : m_invalid( false ), m_master( false ), - m_dataSem( QString() ), - m_messageSem( QString() ), m_lockDepth( 0 ) { m_data.attach(shmKey); - m_dataSem.setKey( QString::number( m_data->dataSem.semKey ) ); - m_messageSem.setKey( QString::number( - m_data->messageSem.semKey ) ); + m_dataSem = SystemSemaphore{std::to_string(m_data->dataSem.semKey)}; + m_messageSem = SystemSemaphore{std::to_string(m_data->messageSem.semKey)}; } inline bool isInvalid() const @@ -336,11 +328,10 @@ private: volatile bool m_invalid; bool m_master; SharedMemory m_data; - QSystemSemaphore m_dataSem; - QSystemSemaphore m_messageSem; + SystemSemaphore m_dataSem; + SystemSemaphore m_messageSem; std::atomic_int m_lockDepth; - -} ; +}; #endif // SYNC_WITH_SHM_FIFO diff --git a/include/RingBuffer.h b/include/RingBuffer.h index 90a607a13..98f726475 100644 --- a/include/RingBuffer.h +++ b/include/RingBuffer.h @@ -29,7 +29,7 @@ #include #include #include "lmms_basics.h" -#include "MemoryManager.h" +#include "lmms_export.h" namespace lmms @@ -41,7 +41,6 @@ namespace lmms class LMMS_EXPORT RingBuffer : public QObject { Q_OBJECT - MM_OPERATORS public: /** \brief Constructs a ringbuffer of specified size, will not care about samplerate changes * \param size The size of the buffer in frames. The actual size will be size + period size diff --git a/include/Sample.h b/include/Sample.h new file mode 100644 index 000000000..92ac1a58a --- /dev/null +++ b/include/Sample.h @@ -0,0 +1,134 @@ +/* + * Sample.h - State for container-class SampleBuffer + * + * Copyright (c) 2023 saker + * + * 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_SAMPLE_H +#define LMMS_SAMPLE_H + +#include +#include + +#include "AudioResampler.h" +#include "Note.h" +#include "SampleBuffer.h" +#include "lmms_export.h" + +namespace lmms { +class LMMS_EXPORT Sample +{ +public: + // values for buffer margins, used for various libsamplerate interpolation modes + // the array positions correspond to the converter_type parameter values in libsamplerate + // if there appears problems with playback on some interpolation mode, then the value for that mode + // may need to be higher - conversely, to optimize, some may work with lower values + static constexpr auto s_interpolationMargins = std::array{64, 64, 64, 4, 4}; + + enum class Loop + { + Off, + On, + PingPong + }; + + class LMMS_EXPORT PlaybackState + { + public: + PlaybackState(bool varyingPitch = false, int interpolationMode = SRC_LINEAR) + : m_resampler(interpolationMode, DEFAULT_CHANNELS) + , m_varyingPitch(varyingPitch) + { + } + + auto resampler() -> AudioResampler& { return m_resampler; } + auto frameIndex() const -> int { return m_frameIndex; } + auto varyingPitch() const -> bool { return m_varyingPitch; } + auto backwards() const -> bool { return m_backwards; } + + void setFrameIndex(int frameIndex) { m_frameIndex = frameIndex; } + void setVaryingPitch(bool varyingPitch) { m_varyingPitch = varyingPitch; } + void setBackwards(bool backwards) { m_backwards = backwards; } + + private: + AudioResampler m_resampler; + int m_frameIndex = 0; + bool m_varyingPitch = false; + bool m_backwards = false; + friend class Sample; + }; + + Sample() = default; + Sample(const QByteArray& base64, int sampleRate = Engine::audioEngine()->outputSampleRate()); + Sample(const sampleFrame* data, size_t numFrames, int sampleRate = Engine::audioEngine()->outputSampleRate()); + Sample(const Sample& other); + Sample(Sample&& other); + explicit Sample(const QString& audioFile); + explicit Sample(std::shared_ptr buffer); + + auto operator=(const Sample&) -> Sample&; + auto operator=(Sample&&) -> Sample&; + + auto play(sampleFrame* dst, PlaybackState* state, size_t numFrames, float desiredFrequency = DefaultBaseFreq, + Loop loopMode = Loop::Off) const -> bool; + + auto sampleDuration() const -> std::chrono::milliseconds; + auto sampleFile() const -> const QString& { return m_buffer->audioFile(); } + auto sampleRate() const -> int { return m_buffer->sampleRate(); } + auto sampleSize() const -> size_t { return m_buffer->size(); } + + auto toBase64() const -> QString { return m_buffer->toBase64(); } + + auto data() const -> const sampleFrame* { return m_buffer->data(); } + auto buffer() const -> std::shared_ptr { return m_buffer; } + auto startFrame() const -> int { return m_startFrame.load(std::memory_order_relaxed); } + auto endFrame() const -> int { return m_endFrame.load(std::memory_order_relaxed); } + auto loopStartFrame() const -> int { return m_loopStartFrame.load(std::memory_order_relaxed); } + auto loopEndFrame() const -> int { return m_loopEndFrame.load(std::memory_order_relaxed); } + auto amplification() const -> float { return m_amplification.load(std::memory_order_relaxed); } + auto frequency() const -> float { return m_frequency.load(std::memory_order_relaxed); } + auto reversed() const -> bool { return m_reversed.load(std::memory_order_relaxed); } + + void setStartFrame(int startFrame) { m_startFrame.store(startFrame, std::memory_order_relaxed); } + void setEndFrame(int endFrame) { m_endFrame.store(endFrame, std::memory_order_relaxed); } + void setLoopStartFrame(int loopStartFrame) { m_loopStartFrame.store(loopStartFrame, std::memory_order_relaxed); } + void setLoopEndFrame(int loopEndFrame) { m_loopEndFrame.store(loopEndFrame, std::memory_order_relaxed); } + void setAllPointFrames(int startFrame, int endFrame, int loopStartFrame, int loopEndFrame); + void setAmplification(float amplification) { m_amplification.store(amplification, std::memory_order_relaxed); } + void setFrequency(float frequency) { m_frequency.store(frequency, std::memory_order_relaxed); } + void setReversed(bool reversed) { m_reversed.store(reversed, std::memory_order_relaxed); } + +private: + void playRaw(sampleFrame* dst, size_t numFrames, const PlaybackState* state, Loop loopMode) const; + void advance(PlaybackState* state, size_t advanceAmount, Loop loopMode) const; + +private: + std::shared_ptr m_buffer = SampleBuffer::emptyBuffer(); + std::atomic m_startFrame = 0; + std::atomic m_endFrame = 0; + std::atomic m_loopStartFrame = 0; + std::atomic m_loopEndFrame = 0; + std::atomic m_amplification = 1.0f; + std::atomic m_frequency = DefaultBaseFreq; + std::atomic m_reversed = false; +}; +} // namespace lmms +#endif diff --git a/include/SampleBuffer.h b/include/SampleBuffer.h index 3d1013baa..6f40b33d1 100644 --- a/include/SampleBuffer.h +++ b/include/SampleBuffer.h @@ -25,333 +25,74 @@ #ifndef LMMS_SAMPLE_BUFFER_H #define LMMS_SAMPLE_BUFFER_H +#include +#include #include -#include -#include - +#include #include +#include -#include "lmms_export.h" -#include "interpolation.h" +#include "AudioEngine.h" +#include "Engine.h" #include "lmms_basics.h" -#include "lmms_math.h" -#include "shared_object.h" -#include "OscillatorConstants.h" -#include "MemoryManager.h" +#include "lmms_export.h" - -class QPainter; -class QRect; - -namespace lmms +namespace lmms { +class LMMS_EXPORT SampleBuffer { - -// values for buffer margins, used for various libsamplerate interpolation modes -// the array positions correspond to the converter_type parameter values in libsamplerate -// if there appears problems with playback on some interpolation mode, then the value for that mode -// may need to be higher - conversely, to optimize, some may work with lower values -const f_cnt_t MARGIN[] = { 64, 64, 64, 4, 4 }; - -class LMMS_EXPORT SampleBuffer : public QObject, public sharedObject -{ - Q_OBJECT - MM_OPERATORS public: - enum class LoopMode { - Off = 0, - On, - PingPong - }; - class LMMS_EXPORT handleState - { - MM_OPERATORS - public: - handleState(bool varyingPitch = false, int interpolationMode = SRC_LINEAR); - virtual ~handleState(); + using value_type = sampleFrame; + using reference = sampleFrame&; + using const_reference = const sampleFrame&; + using iterator = std::vector::iterator; + using const_iterator = std::vector::const_iterator; + using difference_type = std::vector::difference_type; + using size_type = std::vector::size_type; + using reverse_iterator = std::vector::reverse_iterator; + using const_reverse_iterator = std::vector::const_reverse_iterator; - const f_cnt_t frameIndex() const - { - return m_frameIndex; - } + SampleBuffer() = default; + explicit SampleBuffer(const QString& audioFile); + SampleBuffer(const QString& base64, int sampleRate); + SampleBuffer(std::vector data, int sampleRate); + SampleBuffer( + const sampleFrame* data, size_t numFrames, int sampleRate = Engine::audioEngine()->outputSampleRate()); - void setFrameIndex(f_cnt_t index) - { - m_frameIndex = index; - } + friend void swap(SampleBuffer& first, SampleBuffer& second) noexcept; + auto toBase64() const -> QString; - bool isBackwards() const - { - return m_isBackwards; - } + auto audioFile() const -> const QString& { return m_audioFile; } + auto sampleRate() const -> sample_rate_t { return m_sampleRate; } - void setBackwards(bool backwards) - { - m_isBackwards = backwards; - } + auto begin() -> iterator { return m_data.begin(); } + auto end() -> iterator { return m_data.end(); } - int interpolationMode() const - { - return m_interpolationMode; - } + auto begin() const -> const_iterator { return m_data.begin(); } + auto end() const -> const_iterator { return m_data.end(); } + auto cbegin() const -> const_iterator { return m_data.cbegin(); } + auto cend() const -> const_iterator { return m_data.cend(); } - private: - f_cnt_t m_frameIndex; - const bool m_varyingPitch; - bool m_isBackwards; - SRC_STATE * m_resamplingData; - int m_interpolationMode; + auto rbegin() -> reverse_iterator { return m_data.rbegin(); } + auto rend() -> reverse_iterator { return m_data.rend(); } - friend class SampleBuffer; + auto rbegin() const -> const_reverse_iterator { return m_data.rbegin(); } + auto rend() const -> const_reverse_iterator { return m_data.rend(); } - } ; + auto crbegin() const -> const_reverse_iterator { return m_data.crbegin(); } + auto crend() const -> const_reverse_iterator { return m_data.crend(); } + auto data() const -> const sampleFrame* { return m_data.data(); } + auto size() const -> size_type { return m_data.size(); } + auto empty() const -> bool { return m_data.empty(); } - SampleBuffer(); - // constructor which either loads sample _audio_file or decodes - // base64-data out of string - SampleBuffer(const QString & audioFile, bool isBase64Data = false); - SampleBuffer(const sampleFrame * data, const f_cnt_t frames); - explicit SampleBuffer(const f_cnt_t frames); - SampleBuffer(const SampleBuffer & orig); - - friend void swap(SampleBuffer & first, SampleBuffer & second) noexcept; - SampleBuffer& operator= (const SampleBuffer that); - - ~SampleBuffer() override; - - bool play( - sampleFrame * ab, - handleState * state, - const fpp_t frames, - const float freq, - const LoopMode loopMode = LoopMode::Off - ); - - void visualize( - QPainter & p, - const QRect & dr, - const QRect & clip, - f_cnt_t fromFrame = 0, - f_cnt_t toFrame = 0 - ); - inline void visualize( - QPainter & p, - const QRect & dr, - f_cnt_t fromFrame = 0, - f_cnt_t toFrame = 0 - ) - { - visualize(p, dr, dr, fromFrame, toFrame); - } - - inline const QString & audioFile() const - { - return m_audioFile; - } - - inline f_cnt_t startFrame() const - { - return m_startFrame; - } - - inline f_cnt_t endFrame() const - { - return m_endFrame; - } - - inline f_cnt_t loopStartFrame() const - { - return m_loopStartFrame; - } - - inline f_cnt_t loopEndFrame() const - { - return m_loopEndFrame; - } - - void setLoopStartFrame(f_cnt_t start) - { - m_loopStartFrame = start; - } - - void setLoopEndFrame(f_cnt_t end) - { - m_loopEndFrame = end; - } - - void setAllPointFrames( - f_cnt_t start, - f_cnt_t end, - f_cnt_t loopStart, - f_cnt_t loopEnd - ) - { - m_startFrame = start; - m_endFrame = end; - m_loopStartFrame = loopStart; - m_loopEndFrame = loopEnd; - } - - inline f_cnt_t frames() const - { - return m_frames; - } - - inline float amplification() const - { - return m_amplification; - } - - inline bool reversed() const - { - return m_reversed; - } - - inline float frequency() const - { - return m_frequency; - } - - sample_rate_t sampleRate() const - { - return m_sampleRate; - } - - int sampleLength() const - { - return double(m_endFrame - m_startFrame) / m_sampleRate * 1000; - } - - inline void setFrequency(float freq) - { - m_frequency = freq; - } - - inline void setSampleRate(sample_rate_t rate) - { - m_sampleRate = rate; - } - - inline const sampleFrame * data() const - { - return m_data; - } - - QString openAudioFile() const; - QString openAndSetAudioFile(); - QString openAndSetWaveformFile(); - - QString & toBase64(QString & dst) const; - - - // protect calls from the GUI to this function with dataReadLock() and - // dataUnlock() - SampleBuffer * resample(const sample_rate_t srcSR, const sample_rate_t dstSR); - - void normalizeSampleRate(const sample_rate_t srcSR, bool keepSettings = false); - - // protect calls from the GUI to this function with dataReadLock() and - // dataUnlock(), out of loops for efficiency - inline sample_t userWaveSample(const float sample) const - { - f_cnt_t frames = m_frames; - sampleFrame * data = m_data; - const float frame = sample * frames; - f_cnt_t f1 = static_cast(frame) % frames; - if (f1 < 0) - { - f1 += frames; - } - return linearInterpolate(data[f1][0], data[(f1 + 1) % frames][0], fraction(frame)); - } - - void dataReadLock() - { - m_varLock.lockForRead(); - } - - void dataUnlock() - { - m_varLock.unlock(); - } - - - std::unique_ptr m_userAntiAliasWaveTable; - - -public slots: - void setAudioFile(const QString & audioFile); - void loadFromBase64(const QString & data); - void setStartFrame(const lmms::f_cnt_t s); - void setEndFrame(const lmms::f_cnt_t e); - void setAmplification(float a); - void setReversed(bool on); - void sampleRateChanged(); + static auto emptyBuffer() -> std::shared_ptr; private: - static sample_rate_t audioEngineSampleRate(); - - void update(bool keepSettings = false); - - void convertIntToFloat(int_sample_t * & ibuf, f_cnt_t frames, int channels); - void directFloatWrite(sample_t * & fbuf, f_cnt_t frames, int channels); - - f_cnt_t decodeSampleSF( - QString fileName, - sample_t * & buf, - ch_cnt_t & channels, - sample_rate_t & samplerate - ); -#ifdef LMMS_HAVE_OGGVORBIS - f_cnt_t decodeSampleOGGVorbis( - QString fileName, - int_sample_t * & buf, - ch_cnt_t & channels, - sample_rate_t & samplerate - ); -#endif - f_cnt_t decodeSampleDS( - QString fileName, - int_sample_t * & buf, - ch_cnt_t & channels, - sample_rate_t & samplerate - ); - + std::vector m_data; QString m_audioFile; - sampleFrame * m_origData; - f_cnt_t m_origFrames; - sampleFrame * m_data; - mutable QReadWriteLock m_varLock; - f_cnt_t m_frames; - f_cnt_t m_startFrame; - f_cnt_t m_endFrame; - f_cnt_t m_loopStartFrame; - f_cnt_t m_loopEndFrame; - float m_amplification; - bool m_reversed; - float m_frequency; - sample_rate_t m_sampleRate; - - sampleFrame * getSampleFragment( - f_cnt_t index, - f_cnt_t frames, - LoopMode loopMode, - sampleFrame * * tmp, - bool * backwards, - f_cnt_t loopStart, - f_cnt_t loopEnd, - f_cnt_t end - ) const; - - f_cnt_t getLoopedIndex(f_cnt_t index, f_cnt_t startf, f_cnt_t endf) const; - f_cnt_t getPingPongIndex(f_cnt_t index, f_cnt_t startf, f_cnt_t endf) const; - - -signals: - void sampleUpdated(); - -} ; + sample_rate_t m_sampleRate = Engine::audioEngine()->outputSampleRate(); +}; } // namespace lmms diff --git a/include/SampleClip.h b/include/SampleClip.h index 5246787bd..3beca338b 100644 --- a/include/SampleClip.h +++ b/include/SampleClip.h @@ -25,7 +25,9 @@ #ifndef LMMS_SAMPLE_CLIP_H #define LMMS_SAMPLE_CLIP_H +#include #include "Clip.h" +#include "Sample.h" namespace lmms { @@ -45,14 +47,17 @@ class SampleClip : public Clip Q_OBJECT mapPropertyFromModel(bool,isRecord,setRecord,m_recordModel); public: - SampleClip( Track * _track ); + SampleClip(Track* track, Sample sample, bool isPlaying); + SampleClip(Track* track); SampleClip( const SampleClip& orig ); ~SampleClip() override; SampleClip& operator=( const SampleClip& that ) = delete; void changeLength( const TimePos & _length ) override; - const QString & sampleFile() const; + void changeLengthToSampleLength(); + const QString& sampleFile() const; + bool hasSampleFileLoaded(const QString & filename) const; void saveSettings( QDomDocument & _doc, QDomElement & _parent ) override; void loadSettings( const QDomElement & _this ) override; @@ -61,9 +66,9 @@ public: return "sampleclip"; } - SampleBuffer* sampleBuffer() + Sample& sample() { - return m_sampleBuffer; + return m_sample; } TimePos sampleLength() const; @@ -74,10 +79,10 @@ public: bool isPlaying() const; void setIsPlaying(bool isPlaying); + void setSampleBuffer(std::shared_ptr sb); public slots: - void setSampleBuffer( lmms::SampleBuffer* sb ); - void setSampleFile( const QString & sf ); + void setSampleFile(const QString& sf); void updateLength(); void toggleRecord(); void playbackPositionChanged(); @@ -85,7 +90,7 @@ public slots: private: - SampleBuffer* m_sampleBuffer; + Sample m_sample; BoolModel m_recordModel; bool m_isPlaying; diff --git a/include/SampleClipView.h b/include/SampleClipView.h index b3f53d790..4ff218fb0 100644 --- a/include/SampleClipView.h +++ b/include/SampleClipView.h @@ -47,6 +47,7 @@ public: public slots: void updateSample(); void reverseSample(); + void setAutomationGhost(); diff --git a/include/SampleDecoder.h b/include/SampleDecoder.h new file mode 100644 index 000000000..d7ce076dd --- /dev/null +++ b/include/SampleDecoder.h @@ -0,0 +1,57 @@ +/* + * SampleDecoder.h - Decodes audio files in various formats + * + * Copyright (c) 2023 saker + * + * 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_SAMPLE_DECODER_H +#define LMMS_SAMPLE_DECODER_H + +#include +#include +#include +#include +#include + +#include "lmms_basics.h" + +namespace lmms { +class SampleDecoder +{ +public: + struct Result + { + std::vector data; + int sampleRate; + }; + + struct AudioType + { + std::string name; + std::string extension; + }; + + static auto decode(const QString& audioFile) -> std::optional; + static auto supportedAudioTypes() -> const std::vector&; +}; +} // namespace lmms + +#endif // LMMS_SAMPLE_DECODER_H diff --git a/include/SampleLoader.h b/include/SampleLoader.h new file mode 100644 index 000000000..fd8f11357 --- /dev/null +++ b/include/SampleLoader.h @@ -0,0 +1,48 @@ +/* + * SampleLoader.h - Load audio and waveform files + * + * Copyright (c) 2023 saker + * + * 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_SAMPLE_LOADER_H +#define LMMS_GUI_SAMPLE_LOADER_H + +#include +#include + +#include "SampleBuffer.h" +#include "lmms_export.h" + +namespace lmms::gui { +class LMMS_EXPORT SampleLoader +{ +public: + static QString openAudioFile(const QString& previousFile = ""); + static QString openWaveformFile(const QString& previousFile = ""); + static std::shared_ptr createBufferFromFile(const QString& filePath); + static std::shared_ptr createBufferFromBase64( + const QString& base64, int sampleRate = Engine::audioEngine()->outputSampleRate()); +private: + static void displayError(const QString& message); +}; +} // namespace lmms::gui + +#endif // LMMS_GUI_SAMPLE_LOADER_H diff --git a/include/SamplePlayHandle.h b/include/SamplePlayHandle.h index 31b4f0bd5..280010b06 100644 --- a/include/SamplePlayHandle.h +++ b/include/SamplePlayHandle.h @@ -26,6 +26,7 @@ #ifndef LMMS_SAMPLE_PLAY_HANDLE_H #define LMMS_SAMPLE_PLAY_HANDLE_H +#include "Sample.h" #include "SampleBuffer.h" #include "AutomatableModel.h" #include "PlayHandle.h" @@ -43,7 +44,7 @@ class AudioPort; class LMMS_EXPORT SamplePlayHandle : public PlayHandle { public: - SamplePlayHandle( SampleBuffer* sampleBuffer , bool ownAudioPort = true ); + SamplePlayHandle(Sample* sample, bool ownAudioPort = true); SamplePlayHandle( const QString& sampleFile ); SamplePlayHandle( SampleClip* clip ); ~SamplePlayHandle() override; @@ -81,11 +82,11 @@ public: private: - SampleBuffer * m_sampleBuffer; + Sample* m_sample; bool m_doneMayReturnTrue; f_cnt_t m_frame; - SampleBuffer::handleState m_state; + Sample::PlaybackState m_state; const bool m_ownAudioPort; diff --git a/include/SampleRecordHandle.h b/include/SampleRecordHandle.h index 0c4d0171a..41cf0c37e 100644 --- a/include/SampleRecordHandle.h +++ b/include/SampleRecordHandle.h @@ -27,6 +27,7 @@ #include #include +#include #include "PlayHandle.h" #include "TimePos.h" @@ -53,7 +54,7 @@ public: bool isFromTrack( const Track * _track ) const override; f_cnt_t framesRecorded() const; - void createSampleBuffer( SampleBuffer * * _sample_buf ); + std::shared_ptr createSampleBuffer(); private: diff --git a/include/SampleTrackView.h b/include/SampleTrackView.h index 3ccb97aea..2f94bfb56 100644 --- a/include/SampleTrackView.h +++ b/include/SampleTrackView.h @@ -26,7 +26,7 @@ #define LMMS_GUI_SAMPLE_TRACK_VIEW_H -#include "MixerLineLcdSpinBox.h" +#include "MixerChannelLcdSpinBox.h" #include "TrackView.h" namespace lmms @@ -91,7 +91,7 @@ private slots: private: SampleTrackWindow * m_window; - MixerLineLcdSpinBox* m_mixerChannelNumber; + MixerChannelLcdSpinBox* m_mixerChannelNumber; Knob * m_volumeKnob; Knob * m_panningKnob; FadeButton * m_activityIndicator; diff --git a/include/SampleTrackWindow.h b/include/SampleTrackWindow.h index c2a722d53..4d535bfe5 100644 --- a/include/SampleTrackWindow.h +++ b/include/SampleTrackWindow.h @@ -38,7 +38,7 @@ namespace lmms::gui class EffectRackView; class Knob; -class MixerLineLcdSpinBox; +class MixerChannelLcdSpinBox; class SampleTrackView; @@ -90,7 +90,7 @@ private: QLineEdit * m_nameLineEdit; Knob * m_volumeKnob; Knob * m_panningKnob; - MixerLineLcdSpinBox * m_mixerChannelNumber; + MixerChannelLcdSpinBox * m_mixerChannelNumber; EffectRackView * m_effectRack; } ; diff --git a/include/SampleWaveform.h b/include/SampleWaveform.h new file mode 100644 index 000000000..0185e0e98 --- /dev/null +++ b/include/SampleWaveform.h @@ -0,0 +1,49 @@ +/* + * SampleWaveform.h + * + * Copyright (c) 2023 saker + * + * 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_SAMPLE_WAVEFORM_H +#define LMMS_GUI_SAMPLE_WAVEFORM_H + +#include + +#include "Sample.h" +#include "lmms_export.h" + +namespace lmms::gui { +class LMMS_EXPORT SampleWaveform +{ +public: + struct Parameters + { + const sampleFrame* buffer; + size_t size; + float amplification; + bool reversed; + }; + + static void visualize(Parameters parameters, QPainter& painter, const QRect& rect); +}; +} // namespace lmms::gui + +#endif // LMMS_GUI_SAMPLE_WAVEFORM_H diff --git a/include/SendButtonIndicator.h b/include/SendButtonIndicator.h index f1ee2dbca..9e9417926 100644 --- a/include/SendButtonIndicator.h +++ b/include/SendButtonIndicator.h @@ -26,6 +26,7 @@ #define LMMS_GUI_SEND_BUTTON_INDICATOR_H #include +#include "embed.h" namespace lmms @@ -36,27 +37,25 @@ class FloatModel; namespace gui { -class MixerLine; +class MixerChannelView; class MixerView; - -class SendButtonIndicator : public QLabel +class SendButtonIndicator : public QLabel { public: - SendButtonIndicator( QWidget * _parent, MixerLine * _owner, - MixerView * _mv); + SendButtonIndicator(QWidget* parent, MixerChannelView* owner, MixerView* mv); - void mousePressEvent( QMouseEvent * e ) override; + void mousePressEvent(QMouseEvent* e) override; void updateLightStatus(); private: - MixerLine * m_parent; - MixerView * m_mv; - static QPixmap * s_qpmOn; - static QPixmap * s_qpmOff; + MixerChannelView* m_parent; + MixerView* m_mv; + QPixmap m_qpmOff = embed::getIconPixmap("mixer_send_off", 29, 20); + QPixmap m_qpmOn = embed::getIconPixmap("mixer_send_on", 29, 20); - FloatModel * getSendModel(); + FloatModel* getSendModel(); }; diff --git a/include/SetupDialog.h b/include/SetupDialog.h index 882ca2bed..871a80bcd 100644 --- a/include/SetupDialog.h +++ b/include/SetupDialog.h @@ -86,6 +86,7 @@ private slots: void toggleMMPZ(bool enabled); void toggleDisableBackup(bool enabled); void toggleOpenLastProject(bool enabled); + void loopMarkerModeChanged(); void setLanguage(int lang); // Performance settings widget. @@ -101,13 +102,13 @@ private slots: // Audio settings widget. void audioInterfaceChanged(const QString & driver); - void toggleHQAudioDev(bool enabled); void updateBufferSizeWarning(int value); void setBufferSize(int value); void resetBufferSize(); // MIDI settings widget. void midiInterfaceChanged(const QString & driver); + void toggleMidiAutoQuantization(bool enabled); // Paths settings widget. void openWorkingDir(); @@ -147,6 +148,8 @@ private: bool m_MMPZ; bool m_disableBackup; bool m_openLastProject; + QString m_loopMarkerMode; + QComboBox* m_loopMarkerComboBox; QString m_lang; QStringList m_languages; @@ -176,7 +179,6 @@ private: AswMap m_audioIfaceSetupWidgets; trMap m_audioIfaceNames; bool m_NaNHandler; - bool m_hqAudioDev; int m_bufferSize; QSlider * m_bufferSizeSlider; QLabel * m_bufferSizeLbl; @@ -187,6 +189,7 @@ private: MswMap m_midiIfaceSetupWidgets; trMap m_midiIfaceNames; QComboBox * m_assignableMidiDevices; + bool m_midiAutoQuantize; // Paths settings widgets. QString m_workingDir; diff --git a/include/Song.h b/include/Song.h index 02714d8ac..2897b2131 100644 --- a/include/Song.h +++ b/include/Song.h @@ -25,16 +25,18 @@ #ifndef LMMS_SONG_H #define LMMS_SONG_H +#include #include #include #include -#include "TrackContainer.h" #include "AudioEngine.h" #include "Controller.h" #include "lmms_constants.h" #include "MeterModel.h" +#include "Timeline.h" +#include "TrackContainer.h" #include "VstSyncController.h" namespace lmms @@ -105,7 +107,6 @@ public: public: PlayPos( const int abs = 0 ) : TimePos( abs ), - m_timeLine( nullptr ), m_currentFrame( 0.0f ) { } @@ -125,13 +126,11 @@ public: { return m_jumped; } - gui::TimeLineWidget * m_timeLine; private: float m_currentFrame; bool m_jumped; - - } ; + }; void processNextBuffer(); @@ -274,6 +273,11 @@ public: return getPlayPos(m_playMode); } + auto getTimeline(PlayMode mode) -> Timeline& { return m_timelines[static_cast(mode)]; } + auto getTimeline(PlayMode mode) const -> const Timeline& { return m_timelines[static_cast(mode)]; } + auto getTimeline() -> Timeline& { return getTimeline(m_playMode); } + auto getTimeline() const -> const Timeline& { return getTimeline(m_playMode); } + void updateLength(); bar_t length() const { @@ -402,7 +406,7 @@ private slots: void masterVolumeChanged(); - void savePos(); + void savePlayStartPosition(); void updateFramesPerTick(); @@ -481,6 +485,8 @@ private: QHash m_errors; + std::array m_timelines; + PlayMode m_playMode; PlayPos m_playPos[PlayModeCount]; bar_t m_length; diff --git a/include/SongEditor.h b/include/SongEditor.h index ee9e83f44..98a9096fb 100644 --- a/include/SongEditor.h +++ b/include/SongEditor.h @@ -97,8 +97,6 @@ protected: void mouseReleaseEvent(QMouseEvent * me) override; private slots: - void setHighQuality( bool ); - void setMasterVolume( int new_val ); void showMasterVolumeFloat(); void updateMasterVolumeFloat( int new_val ); @@ -166,6 +164,7 @@ private: signals: void pixelsPerBarChanged(float); + void proportionalSnapChanged(); } ; diff --git a/include/SubWindow.h b/include/SubWindow.h index fdda6de42..d1cc6a7af 100644 --- a/include/SubWindow.h +++ b/include/SubWindow.h @@ -68,6 +68,8 @@ public: void setActiveColor( const QBrush & b ); void setTextShadowColor( const QColor &c ); void setBorderColor( const QColor &c ); + int titleBarHeight() const; + int frameWidth() const; protected: // hook the QWidget move/resize events to update the tracked geometry diff --git a/include/SystemSemaphore.h b/include/SystemSemaphore.h new file mode 100644 index 000000000..931c472bb --- /dev/null +++ b/include/SystemSemaphore.h @@ -0,0 +1,61 @@ +/* + * SystemSemaphore.h + * + * Copyright (c) 2024 Dominic Clark + * + * 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_SYSTEM_SEMAPHORE_H +#define LMMS_SYSTEM_SEMAPHORE_H + +#include +#include + +namespace lmms { + +namespace detail { + +class SystemSemaphoreImpl; + +} // namespace detail + +class SystemSemaphore +{ +public: + SystemSemaphore() noexcept; + SystemSemaphore(std::string key, unsigned int value); + explicit SystemSemaphore(std::string key); + ~SystemSemaphore(); + + SystemSemaphore(SystemSemaphore&& other) noexcept; + auto operator=(SystemSemaphore&& other) noexcept -> SystemSemaphore&; + + auto acquire() noexcept -> bool; + auto release() noexcept -> bool; + + auto key() const noexcept -> const std::string& { return m_key; } + +private: + std::string m_key; + std::unique_ptr m_impl; +}; + +} // namespace lmms + +#endif // LMMS_SYSTEM_SEMAPHORE_H diff --git a/include/TabWidget.h b/include/TabWidget.h index d28537df8..52e59d577 100644 --- a/include/TabWidget.h +++ b/include/TabWidget.h @@ -40,53 +40,56 @@ class TabWidget : public QWidget public: //! @param resizable If true, the widget resizes to fit the size of all tabs //! If false, all child widget will be cut down to the TabWidget's size - TabWidget( const QString & _caption, QWidget * _parent, - bool usePixmap = false, bool resizable = false ); + TabWidget(const QString& caption, QWidget* parent, + bool usePixmap = false, bool resizable = false); ~TabWidget() override = default; - void addTab( QWidget * w, const QString & name, const char *pixmap = nullptr, int idx = -1 ); + void addTab(QWidget* w, const QString& name, const char* pixmap = nullptr, int idx = -1); - void setActiveTab( int idx ); + void setActiveTab(int idx); - int findTabAtPos( const QPoint *pos ); + int findTabAtPos(const QPoint* pos); inline int activeTab() const { - return( m_activeTab ); + return(m_activeTab); } // Themeability - Q_PROPERTY( QColor tabText READ tabText WRITE setTabText) - Q_PROPERTY( QColor tabTitleText READ tabTitleText WRITE setTabTitleText) - Q_PROPERTY( QColor tabSelected READ tabSelected WRITE setTabSelected) - Q_PROPERTY( QColor tabBackground READ tabBackground WRITE setTabBackground) - Q_PROPERTY( QColor tabBorder READ tabBorder WRITE setTabBorder) + Q_PROPERTY(QColor tabText READ tabText WRITE setTabText) + Q_PROPERTY(QColor tabTitleText READ tabTitleText WRITE setTabTitleText) + Q_PROPERTY(QColor tabSelected READ tabSelected WRITE setTabSelected) + Q_PROPERTY(QColor tabTextSelected READ tabTextSelected WRITE setTabTextSelected) + Q_PROPERTY(QColor tabBackground READ tabBackground WRITE setTabBackground) + Q_PROPERTY(QColor tabBorder READ tabBorder WRITE setTabBorder) QColor tabText() const; - void setTabText( const QColor & c ); + void setTabText(const QColor & c); QColor tabTitleText() const; - void setTabTitleText( const QColor & c ); + void setTabTitleText(const QColor & c); QColor tabSelected() const; - void setTabSelected( const QColor & c ); + void setTabSelected(const QColor & c); + QColor tabTextSelected() const; + void setTabTextSelected(const QColor & c); QColor tabBackground() const; - void setTabBackground( const QColor & c ); + void setTabBackground(const QColor & c); QColor tabBorder() const; - void setTabBorder( const QColor & c ); + void setTabBorder(const QColor & c); protected: - bool event( QEvent * event ) override; - void mousePressEvent( QMouseEvent * _me ) override; - void paintEvent( QPaintEvent * _pe ) override; - void resizeEvent( QResizeEvent * _re ) override; - void wheelEvent( QWheelEvent * _we ) override; + bool event(QEvent* event) override; + void mousePressEvent(QMouseEvent* me) override; + void paintEvent(QPaintEvent* pe) override; + void resizeEvent(QResizeEvent* re) override; + void wheelEvent(QWheelEvent* we) override; QSize minimumSizeHint() const override; QSize sizeHint() const override; private: struct widgetDesc { - QWidget * w; // ptr to widget - const char * pixmap; // artwork for the widget + QWidget* w; // ptr to widget + const char* pixmap; // artwork for the widget QString name; // name for widget int nwidth; // width of name when painting (only valid for text tab) } ; @@ -104,6 +107,7 @@ private: QColor m_tabText; // The color of the tabs' text. QColor m_tabTitleText; // The color of the TabWidget's title text. QColor m_tabSelected; // The highlighting color for the selected tab. + QColor m_tabTextSelected;// The text color for the selected tab. QColor m_tabBackground; // The TabWidget's background color. QColor m_tabBorder; // The TabWidget's borders color. } ; diff --git a/include/TempoSyncBarModelEditor.h b/include/TempoSyncBarModelEditor.h new file mode 100644 index 000000000..c1b0bb26f --- /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 5cd2db067..af92a58aa 100644 --- a/include/TempoSyncKnobModel.h +++ b/include/TempoSyncKnobModel.h @@ -82,6 +82,9 @@ public: 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/include/ThreadPool.h b/include/ThreadPool.h new file mode 100644 index 000000000..b1d7900a6 --- /dev/null +++ b/include/ThreadPool.h @@ -0,0 +1,100 @@ +/* + * ThreadPool.h + * + * Copyright (c) 2024 saker + * + * 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_THREAD_POOL_H +#define LMMS_THREAD_POOL_H + +#include +#include +#include +#include +#include + +#ifdef __MINGW32__ +#include +#include +#include +#include +#else +#include +#include +#include +#include +#endif + +namespace lmms { +//! A thread pool that can be used for asynchronous processing. +class ThreadPool +{ +public: + //! Destroys the `ThreadPool` object. + //! This blocks until all workers have finished executing. + ~ThreadPool(); + + //! Enqueue function `fn` with arguments `args` to be ran asynchronously. + template + auto enqueue(Fn&& fn, Args&&... args) -> std::future> + { + using ReturnType = std::invoke_result_t; + + auto promise = std::make_shared>(); + auto task = [promise, fn = std::forward(fn), args = std::make_tuple(std::forward(args)...)] + { + if constexpr (!std::is_same_v) + { + promise->set_value(std::apply(fn, args)); + return; + } + std::apply(fn, args); + promise->set_value(); + }; + + { + const auto lock = std::unique_lock{m_runMutex}; + m_queue.push(std::move(task)); + } + + m_runCond.notify_one(); + return promise->get_future(); + } + + //! Return the number of worker threads used. + auto numWorkers() const -> size_t; + + //! Return the global `ThreadPool` instance. + static auto instance() -> ThreadPool&; + +private: + ThreadPool(size_t numWorkers); + void run(); + std::vector m_workers; + std::queue> m_queue; + std::atomic m_done = false; + std::condition_variable m_runCond; + std::mutex m_runMutex; + inline static size_t s_numWorkers = std::thread::hardware_concurrency(); +}; +} // namespace lmms + +#endif // LMMS_THREAD_POOL_H diff --git a/include/TimeLineWidget.h b/include/TimeLineWidget.h index 2e4ba6a97..5c683cfd9 100644 --- a/include/TimeLineWidget.h +++ b/include/TimeLineWidget.h @@ -25,14 +25,25 @@ #ifndef LMMS_GUI_TIMELINE_WIDGET_H #define LMMS_GUI_TIMELINE_WIDGET_H +#include + +#include +#include #include #include "Song.h" +#include "embed.h" class QPixmap; class QToolBar; +namespace lmms { + +class Timeline; + +} // namespace lmms + namespace lmms::gui { @@ -41,7 +52,7 @@ class TextFloat; class SongEditor; -class TimeLineWidget : public QWidget, public JournallingObject +class TimeLineWidget : public QWidget { Q_OBJECT public: @@ -50,33 +61,24 @@ public: Q_PROPERTY( QColor inactiveLoopColor READ getInactiveLoopColor WRITE setInactiveLoopColor ) Q_PROPERTY( QBrush inactiveLoopBrush READ getInactiveLoopBrush WRITE setInactiveLoopBrush ) Q_PROPERTY( QColor inactiveLoopInnerColor READ getInactiveLoopInnerColor WRITE setInactiveLoopInnerColor ) + Q_PROPERTY(QColor inactiveLoopHandleColor MEMBER m_inactiveLoopHandleColor) Q_PROPERTY( QColor activeLoopColor READ getActiveLoopColor WRITE setActiveLoopColor ) Q_PROPERTY( QBrush activeLoopBrush READ getActiveLoopBrush WRITE setActiveLoopBrush ) Q_PROPERTY( QColor activeLoopInnerColor READ getActiveLoopInnerColor WRITE setActiveLoopInnerColor ) + Q_PROPERTY(QColor activeLoopHandleColor MEMBER m_activeLoopHandleColor) Q_PROPERTY( int loopRectangleVerticalPadding READ getLoopRectangleVerticalPadding WRITE setLoopRectangleVerticalPadding ) + Q_PROPERTY(int loopHandleWidth MEMBER m_loopHandleWidth) + Q_PROPERTY(QSize mouseHotspotSelLeft READ mouseHotspotSelLeft WRITE setMouseHotspotSelLeft) + Q_PROPERTY(QSize mouseHotspotSelRight READ mouseHotspotSelRight WRITE setMouseHotspotSelRight) enum class AutoScrollState { Enabled, Disabled - } ; + }; - enum class LoopPointState - { - Disabled, - Enabled - } ; - - enum class BehaviourAtStopState - { - BackToZero, - BackToStart, - KeepStopPosition - } ; - - - TimeLineWidget(int xoff, int yoff, float ppb, Song::PlayPos & pos, - const TimePos & begin, Song::PlayMode mode, QWidget * parent); + TimeLineWidget(int xoff, int yoff, float ppb, Song::PlayPos& pos, Timeline& timeline, + const TimePos& begin, Song::PlayMode mode, QWidget* parent); ~TimeLineWidget() override; inline QColor const & getBarLineColor() const { return m_barLineColor; } @@ -106,6 +108,28 @@ public: inline int const & getLoopRectangleVerticalPadding() const { return m_loopRectangleVerticalPadding; } inline void setLoopRectangleVerticalPadding(int const & loopRectangleVerticalPadding) { m_loopRectangleVerticalPadding = loopRectangleVerticalPadding; } + auto mouseHotspotSelLeft() const -> QSize + { + const auto point = m_cursorSelectLeft.hotSpot(); + return QSize{point.x(), point.y()}; + } + + void setMouseHotspotSelLeft(const QSize& s) + { + m_cursorSelectLeft = QCursor{m_cursorSelectLeft.pixmap(), s.width(), s.height()}; + } + + auto mouseHotspotSelRight() const -> QSize + { + const auto point = m_cursorSelectRight.hotSpot(); + return QSize{point.x(), point.y()}; + } + + void setMouseHotspotSelRight(const QSize& s) + { + m_cursorSelectRight = QCursor{m_cursorSelectRight.pixmap(), s.width(), s.height()}; + } + inline Song::PlayPos & pos() { return( m_pos ); @@ -116,42 +140,6 @@ public: return m_autoScroll; } - BehaviourAtStopState behaviourAtStop() const - { - return m_behaviourAtStop; - } - - void setBehaviourAtStop (int state) - { - emit loadBehaviourAtStop (state); - } - - bool loopPointsEnabled() const - { - return m_loopPoints == LoopPointState::Enabled; - } - - inline const TimePos & loopBegin() const - { - return ( m_loopPos[0] < m_loopPos[1] ) ? - m_loopPos[0] : m_loopPos[1]; - } - - inline const TimePos & loopEnd() const - { - return ( m_loopPos[0] > m_loopPos[1] ) ? - m_loopPos[0] : m_loopPos[1]; - } - - inline void savePos( const TimePos & pos ) - { - m_savedPos = pos; - } - inline const TimePos & savedPos() const - { - return m_savedPos; - } - inline void setPixelsPerBar( float ppb ) { m_ppb = ppb; @@ -162,14 +150,6 @@ public: void addToolButtons(QToolBar* _tool_bar ); - - void saveSettings( QDomDocument & _doc, QDomElement & _parent ) override; - void loadSettings( const QDomElement & _this ) override; - inline QString nodeName() const override - { - return "timeline"; - } - inline int markerX( const TimePos & _t ) const { return m_xOffset + static_cast( ( _t - m_begin ) * @@ -177,92 +157,82 @@ public: } signals: - + void positionChanged(const lmms::TimePos& postion); void regionSelectedFromPixels( int, int ); void selectionFinished(); - public slots: - void updatePosition( const lmms::TimePos & ); - void updatePosition() - { - updatePosition( TimePos() ); - } + void updatePosition(); void setSnapSize( const float snapSize ) { m_snapSize = snapSize; } void toggleAutoScroll( int _n ); - void toggleLoopPoints( int _n ); - void toggleBehaviourAtStop( int _n ); - protected: void paintEvent( QPaintEvent * _pe ) override; void mousePressEvent( QMouseEvent * _me ) override; void mouseMoveEvent( QMouseEvent * _me ) override; void mouseReleaseEvent( QMouseEvent * _me ) override; - + void contextMenuEvent(QContextMenuEvent* event) override; private: - static QPixmap * s_posMarkerPixmap; - - QColor m_inactiveLoopColor; - QBrush m_inactiveLoopBrush; - QColor m_inactiveLoopInnerColor; - - QColor m_activeLoopColor; - QBrush m_activeLoopBrush; - QColor m_activeLoopInnerColor; - - int m_loopRectangleVerticalPadding; - - QColor m_barLineColor; - QColor m_barNumberColor; - - AutoScrollState m_autoScroll; - LoopPointState m_loopPoints; - BehaviourAtStopState m_behaviourAtStop; - - bool m_changedPosition; - - int m_xOffset; - int m_posMarkerX; - float m_ppb; - float m_snapSize; - Song::PlayPos & m_pos; - const TimePos & m_begin; - const Song::PlayMode m_mode; - TimePos m_loopPos[2]; - - TimePos m_savedPos; - - - TextFloat * m_hint; - int m_initalXSelect; - - enum class Action { NoAction, MovePositionMarker, MoveLoopBegin, MoveLoopEnd, + MoveLoop, SelectSongClip, - } m_action; + }; - int m_moveXOff; + auto getClickedTime(int xPosition) const -> TimePos; + auto getLoopAction(QMouseEvent* event) const -> Action; + auto actionCursor(Action action) const -> QCursor; + QPixmap m_posMarkerPixmap = embed::getIconPixmap("playpos_marker"); -signals: - void positionChanged( const lmms::TimePos & _t ); - void loopPointStateLoaded( int _n ); - void positionMarkerMoved(); - void loadBehaviourAtStop( int _n ); + QColor m_inactiveLoopColor = QColor{52, 63, 53, 64}; + QBrush m_inactiveLoopBrush = QColor{255, 255, 255, 32}; + QColor m_inactiveLoopInnerColor = QColor{255, 255, 255, 32}; + QColor m_inactiveLoopHandleColor = QColor{255, 255, 255, 32}; -} ; + QColor m_activeLoopColor = QColor{52, 63, 53, 255}; + QBrush m_activeLoopBrush = QColor{55, 141, 89}; + QColor m_activeLoopInnerColor = QColor{74, 155, 100, 255}; + QColor m_activeLoopHandleColor = QColor{74, 155, 100, 255}; + int m_loopRectangleVerticalPadding = 1; + int m_loopHandleWidth = 5; + QColor m_barLineColor = QColor{192, 192, 192}; + QColor m_barNumberColor = m_barLineColor.darker(120); + + QCursor m_cursorSelectLeft = QCursor{embed::getIconPixmap("cursor_select_left"), 0, 16}; + QCursor m_cursorSelectRight = QCursor{embed::getIconPixmap("cursor_select_right"), 32, 16}; + + AutoScrollState m_autoScroll = AutoScrollState::Enabled; + + // Width of the unused region on the widget's left (above track labels or piano) + int m_xOffset; + float m_ppb; + float m_snapSize = 1.f; + Song::PlayPos & m_pos; + Timeline* m_timeline; + // Leftmost position visible in parent editor + const TimePos & m_begin; + const Song::PlayMode m_mode; + // When in MoveLoop mode we need the initial positions. Storing only the latest + // position allows for unquantized drag but fails when toggling quantization. + std::array m_oldLoopPos; + TimePos m_dragStartPos; + + TextFloat* m_hint = nullptr; + int m_initalXSelect; + + Action m_action = Action::NoAction; +}; } // namespace lmms::gui diff --git a/include/Timeline.h b/include/Timeline.h new file mode 100644 index 000000000..dc2d293c4 --- /dev/null +++ b/include/Timeline.h @@ -0,0 +1,82 @@ +/* + * Timeline.h + * + * Copyright (c) 2023 Dominic Clark + * + * 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_TIMELINE_H +#define LMMS_TIMELINE_H + +#include + +#include "JournallingObject.h" +#include "TimePos.h" + +namespace lmms { + +class Timeline : public QObject, public JournallingObject +{ + Q_OBJECT + +public: + enum class StopBehaviour + { + BackToZero, + BackToStart, + KeepPosition + }; + + auto loopBegin() const -> TimePos { return m_loopBegin; } + auto loopEnd() const -> TimePos { return m_loopEnd; } + auto loopEnabled() const -> bool { return m_loopEnabled; } + + void setLoopBegin(TimePos begin); + void setLoopEnd(TimePos end); + void setLoopPoints(TimePos begin, TimePos end); + void setLoopEnabled(bool enabled); + + auto playStartPosition() const -> TimePos { return m_playStartPosition; } + auto stopBehaviour() const -> StopBehaviour { return m_stopBehaviour; } + + void setPlayStartPosition(TimePos position) { m_playStartPosition = position; } + void setStopBehaviour(StopBehaviour behaviour); + + auto nodeName() const -> QString override { return "timeline"; } + +signals: + void loopEnabledChanged(bool enabled); + void stopBehaviourChanged(lmms::Timeline::StopBehaviour behaviour); + +protected: + void saveSettings(QDomDocument& doc, QDomElement& element) override; + void loadSettings(const QDomElement& element) override; + +private: + TimePos m_loopBegin = TimePos{0}; + TimePos m_loopEnd = TimePos{DefaultTicksPerBar}; + bool m_loopEnabled = false; + + StopBehaviour m_stopBehaviour = StopBehaviour::BackToStart; + TimePos m_playStartPosition = TimePos{-1}; +}; + +} // namespace lmms + +#endif // LMMS_TIMELINE_H diff --git a/include/Track.h b/include/Track.h index 33d1ad233..b801bb182 100644 --- a/include/Track.h +++ b/include/Track.h @@ -32,6 +32,7 @@ #include "AutomatableModel.h" #include "JournallingObject.h" #include "lmms_basics.h" +#include namespace lmms @@ -66,7 +67,6 @@ char const *const FILENAME_FILTER = "[\\0000-\x1f\"*/:<>?\\\\|\x7f]"; class LMMS_EXPORT Track : public Model, public JournallingObject { Q_OBJECT - MM_OPERATORS mapPropertyFromModel(bool,isMuted,setMuted,m_mutedModel); mapPropertyFromModel(bool,isSolo,setSolo,m_soloModel); public: @@ -188,15 +188,9 @@ public: { return m_processingLock.tryLock(); } - - QColor color() - { - return m_color; - } - bool useColor() - { - return m_hasColor; - } + + auto color() const -> const std::optional& { return m_color; } + void setColor(const std::optional& color); bool isMutedBeforeSolo() const { @@ -206,11 +200,7 @@ public: BoolModel* getMutedModel(); public slots: - virtual void setName( const QString & newName ) - { - m_name = newName; - emit nameChanged(); - } + virtual void setName(const QString& newName); void setMutedBeforeSolo(const bool muted) { @@ -219,9 +209,6 @@ public slots: void toggleSolo(); - void setColor(const QColor& c); - void resetColor(); - private: TrackContainer* m_trackContainer; Type m_type; @@ -241,8 +228,7 @@ private: QMutex m_processingLock; - QColor m_color; - bool m_hasColor; + std::optional m_color; friend class gui::TrackView; diff --git a/include/TrackContainerView.h b/include/TrackContainerView.h index 82d6f993b..9bdcdcab6 100644 --- a/include/TrackContainerView.h +++ b/include/TrackContainerView.h @@ -166,7 +166,7 @@ public slots: protected: - static const int DEFAULT_PIXELS_PER_BAR = 16; + static const int DEFAULT_PIXELS_PER_BAR = 128; void resizeEvent( QResizeEvent * ) override; diff --git a/include/TrackContentWidget.h b/include/TrackContentWidget.h index 7cf236323..f93b0a58d 100644 --- a/include/TrackContentWidget.h +++ b/include/TrackContentWidget.h @@ -49,18 +49,24 @@ class TrackContentWidget : public QWidget, public JournallingObject Q_OBJECT // qproperties for track background gradients - Q_PROPERTY( QBrush darkerColor READ darkerColor WRITE setDarkerColor ) - Q_PROPERTY( QBrush lighterColor READ lighterColor WRITE setLighterColor ) - Q_PROPERTY( QBrush gridColor READ gridColor WRITE setGridColor ) - Q_PROPERTY( QBrush embossColor READ embossColor WRITE setEmbossColor ) + Q_PROPERTY(QBrush darkerColor READ darkerColor WRITE setDarkerColor) + Q_PROPERTY(QBrush lighterColor READ lighterColor WRITE setLighterColor) + Q_PROPERTY(QBrush coarseGridColor READ coarseGridColor WRITE setCoarseGridColor) + Q_PROPERTY(QBrush fineGridColor READ fineGridColor WRITE setFineGridColor) + Q_PROPERTY(QBrush horizontalColor READ horizontalColor WRITE setHorizontalColor) + Q_PROPERTY(QBrush embossColor READ embossColor WRITE setEmbossColor) + + Q_PROPERTY(int coarseGridWidth READ coarseGridWidth WRITE setCoarseGridWidth) + Q_PROPERTY(int fineGridWidth READ fineGridWidth WRITE setFineGridWidth) + Q_PROPERTY(int horizontalWidth READ horizontalWidth WRITE setHorizontalWidth) + Q_PROPERTY(int embossWidth READ embossWidth WRITE setEmbossWidth) + + Q_PROPERTY(int embossOffset READ embossOffset WRITE setEmbossOffset) public: TrackContentWidget( TrackView * parent ); ~TrackContentWidget() override = default; - /*! \brief Updates the background tile pixmap. */ - void updateBackground(); - void addClipView( ClipView * clipv ); void removeClipView( ClipView * clipv ); void removeClipView( int clipNum ) @@ -82,17 +88,37 @@ public: QBrush darkerColor() const; QBrush lighterColor() const; - QBrush gridColor() const; + QBrush coarseGridColor() const; + QBrush fineGridColor() const; + QBrush horizontalColor() const; QBrush embossColor() const; - void setDarkerColor( const QBrush & c ); - void setLighterColor( const QBrush & c ); - void setGridColor( const QBrush & c ); - void setEmbossColor( const QBrush & c); + int coarseGridWidth() const; + int fineGridWidth() const; + int horizontalWidth() const; + int embossWidth() const; + + int embossOffset() const; + + void setDarkerColor(const QBrush & c); + void setLighterColor(const QBrush & c); + void setCoarseGridColor(const QBrush & c); + void setFineGridColor(const QBrush & c); + void setHorizontalColor(const QBrush & c); + void setEmbossColor(const QBrush & c); + + void setCoarseGridWidth(int c); + void setFineGridWidth(int c); + void setHorizontalWidth(int c); + void setEmbossWidth(int c); + + void setEmbossOffset(int c); public slots: void update(); void changePosition( const lmms::TimePos & newPos = TimePos( -1 ) ); + /*! \brief Updates the background tile pixmap. */ + void updateBackground(); protected: enum class ContextMenuAction @@ -140,8 +166,17 @@ private: // qproperty fields QBrush m_darkerColor; QBrush m_lighterColor; - QBrush m_gridColor; + QBrush m_coarseGridColor; + QBrush m_fineGridColor; + QBrush m_horizontalColor; QBrush m_embossColor; + + int m_coarseGridWidth; + int m_fineGridWidth; + int m_horizontalWidth; + int m_embossWidth; + + int m_embossOffset; } ; diff --git a/include/TrackLabelButton.h b/include/TrackLabelButton.h index 0d1c6e163..e2cba02a6 100644 --- a/include/TrackLabelButton.h +++ b/include/TrackLabelButton.h @@ -25,6 +25,8 @@ #ifndef LMMS_GUI_TRACK_LABEL_BUTTON_H #define LMMS_GUI_TRACK_LABEL_BUTTON_H +#include + #include namespace lmms::gui @@ -55,13 +57,15 @@ protected: void mousePressEvent( QMouseEvent * _me ) override; void mouseDoubleClickEvent( QMouseEvent * _me ) override; void mouseReleaseEvent( QMouseEvent * _me ) override; - void paintEvent( QPaintEvent * _pe ) override; + void paintEvent(QPaintEvent* pe) override; void resizeEvent( QResizeEvent * _re ) override; +private: + bool isInCompactMode() const; private: TrackView * m_trackView; - QString m_iconName; + std::string m_iconName; TrackRenameLineEdit * m_renameLineEdit; QRect m_buttonRect; QString elideName( const QString &name ); diff --git a/include/TrackView.h b/include/TrackView.h index f697d9ea8..b2654202b 100644 --- a/include/TrackView.h +++ b/include/TrackView.h @@ -134,9 +134,12 @@ protected: void mousePressEvent( QMouseEvent * me ) override; void mouseMoveEvent( QMouseEvent * me ) override; void mouseReleaseEvent( QMouseEvent * me ) override; + void wheelEvent(QWheelEvent* we) override; void paintEvent( QPaintEvent * pe ) override; void resizeEvent( QResizeEvent * re ) override; +private: + void resizeToHeight(int height); private: enum class Action diff --git a/include/ValueBuffer.h b/include/ValueBuffer.h index 683d17fb1..33d93cde0 100644 --- a/include/ValueBuffer.h +++ b/include/ValueBuffer.h @@ -28,7 +28,6 @@ #include -#include "MemoryManager.h" #include "lmms_export.h" namespace lmms @@ -37,7 +36,6 @@ namespace lmms class LMMS_EXPORT ValueBuffer : public std::vector { - MM_OPERATORS public: ValueBuffer() = default; ValueBuffer(int length); diff --git a/include/embed.h b/include/embed.h index 7d69f7c7d..40a3622c6 100644 --- a/include/embed.h +++ b/include/embed.h @@ -25,118 +25,82 @@ #ifndef LMMS_EMBED_H #define LMMS_EMBED_H +#include +#include + #include #include #include "lmms_export.h" #include "lmms_basics.h" +namespace lmms { -namespace lmms -{ - -namespace embed -{ +namespace embed { /** * Return an image for the icon pixmap cache. * - * @param _name Identifier for the pixmap. If it is not in the icon pixmap + * @param name Identifier for the pixmap. If it is not in the icon pixmap * cache, it will be loaded from the artwork QDir search paths (exceptions are * compiled-in XPMs, you need to provide @p xpm for loading them). * @param xpm Must be XPM data if the source should be raw XPM data instead of * a file */ -QPixmap LMMS_EXPORT getIconPixmap( const QString& _name, - int _w = -1, int _h = -1 , const char** xpm = nullptr ); -QString LMMS_EXPORT getText( const char * _name ); +auto LMMS_EXPORT getIconPixmap(std::string_view name, + int width = -1, int height = -1, const char* const* xpm = nullptr) -> QPixmap; +auto LMMS_EXPORT getText(std::string_view name) -> QString; -} +} // namespace embed +class PixmapLoader +{ +public: + PixmapLoader() = default; + + explicit PixmapLoader(std::string name, const char* const* xpm = nullptr) : + m_name{std::move(name)}, + m_xpm{xpm} + { } + + virtual ~PixmapLoader() = default; + + auto pixmap(int width = -1, int height = -1) const -> QPixmap + { + return embed::getIconPixmap(m_name, width, height, m_xpm); + } + + auto pixmapName() const -> const std::string& { return m_name; } + +private: + std::string m_name; + const char* const* m_xpm = nullptr; +}; #ifdef PLUGIN_NAME -namespace PLUGIN_NAME -{ -inline QPixmap getIconPixmap( const QString& _name, - int _w = -1, int _h = -1, const char** xpm = nullptr ) +class PluginPixmapLoader : public PixmapLoader { - return embed::getIconPixmap(QString("%1/%2").arg(LMMS_STRINGIFY(PLUGIN_NAME), _name), _w, _h, xpm); +public: + PluginPixmapLoader() = default; + + explicit PluginPixmapLoader(std::string name, const char* const* xpm = nullptr) : + PixmapLoader{LMMS_STRINGIFY(PLUGIN_NAME) "/" + name, xpm} + { } +}; + +namespace PLUGIN_NAME { + +inline auto getIconPixmap(std::string_view name, + int width = -1, int height = -1, const char* const* xpm = nullptr) -> QPixmap +{ + return PluginPixmapLoader{std::string{name}, xpm}.pixmap(width, height); } -//QString getText( const char * _name ); } // namespace PLUGIN_NAME #endif // PLUGIN_NAME - -class PixmapLoader -{ -public: - PixmapLoader( const PixmapLoader * _ref ) : - m_name( _ref != nullptr ? _ref->m_name : QString() ), - m_xpm( _ref->m_xpm ) - { - } - - PixmapLoader( const QString & _name = QString(), - const char** xpm = nullptr ) : - m_name( _name ), - m_xpm(xpm) - { - } - - virtual QPixmap pixmap() const - { - if( !m_name.isEmpty() ) - { - return( embed::getIconPixmap( - m_name.toLatin1().constData(), -1, -1, m_xpm )); - } - return( QPixmap() ); - } - - virtual ~PixmapLoader() = default; - - virtual QString pixmapName() const - { - return m_name; - } - -protected: - QString m_name; - const char** m_xpm = nullptr; -} ; - - -#ifdef PLUGIN_NAME -class PluginPixmapLoader : public PixmapLoader -{ -public: - PluginPixmapLoader( const QString & _name = QString() ) : - PixmapLoader( _name ) - { - } - - QPixmap pixmap() const override - { - if( !m_name.isEmpty() ) - { - return( PLUGIN_NAME::getIconPixmap( - m_name.toLatin1().constData() ) ); - } - return( QPixmap() ); - } - - QString pixmapName() const override - { - return QString( LMMS_STRINGIFY(PLUGIN_NAME) ) + "::" + m_name; - } - -} ; -#endif // PLUGIN_NAME - - } // namespace lmms #endif // LMMS_EMBED_H diff --git a/include/gui_templates.h b/include/gui_templates.h index c0afbdfc0..bbb5f80da 100644 --- a/include/gui_templates.h +++ b/include/gui_templates.h @@ -25,47 +25,19 @@ #ifndef LMMS_GUI_TEMPLATES_H #define LMMS_GUI_TEMPLATES_H -#include "lmmsconfig.h" - #include #include -#include -namespace lmms +namespace lmms::gui { - -// return DPI-independent font-size - font with returned font-size has always -// the same size in pixels -template -inline QFont pointSize( QFont _f ) +// Convenience method to set the font size in pixels +inline QFont adjustedToPixelSize(QFont font, int size) { - static const float DPI = 96; -#ifdef LMMS_BUILD_WIN32 - _f.setPointSizeF( ((float) SIZE+0.5f) * DPI / - QApplication::desktop()->logicalDpiY() ); -#else - _f.setPointSizeF( (float) SIZE * DPI / - QApplication::desktop()->logicalDpiY() ); -#endif - return( _f ); + font.setPixelSize(size); + return font; } - -inline QFont pointSizeF( QFont _f, float SIZE ) -{ - static const float DPI = 96; -#ifdef LMMS_BUILD_WIN32 - _f.setPointSizeF( (SIZE+0.5f) * DPI / - QApplication::desktop()->logicalDpiY() ); -#else - _f.setPointSizeF( SIZE * DPI / - QApplication::desktop()->logicalDpiY() ); -#endif - return( _f ); -} - - -} // namespace lmms +} // namespace lmms::gui #endif // LMMS_GUI_TEMPLATES_H diff --git a/include/lmms_math.h b/include/lmms_math.h index ea0a75581..bf5e53a2b 100644 --- a/include/lmms_math.h +++ b/include/lmms_math.h @@ -25,12 +25,14 @@ #ifndef LMMS_MATH_H #define LMMS_MATH_H +#include +#include +#include #include + #include "lmms_constants.h" #include "lmmsconfig.h" -#include - -#include +#include namespace lmms { @@ -268,18 +270,18 @@ static inline float safeDbfsToAmp( float dbfs ) //! @brief Converts linear amplitude (>0-1.0) to dBFS scale. //! @param amp Linear amplitude, where 1.0 = 0dBFS. ** Must be larger than zero! ** //! @return Amplitude in dBFS. -static inline float ampToDbfs( float amp ) +static inline float ampToDbfs(float amp) { - return log10f( amp ) * 20.0f; + return log10f(amp) * 20.0f; } //! @brief Converts dBFS-scale to linear amplitude with 0dBFS = 1.0 //! @param dbfs The dBFS value to convert. ** Must be a real number - not inf/nan! ** //! @return Linear amplitude -static inline float dbfsToAmp( float dbfs ) +static inline float dbfsToAmp(float dbfs) { - return std::pow(10.f, dbfs * 0.05f ); + return std::pow(10.f, dbfs * 0.05f); } @@ -325,6 +327,13 @@ static inline T absMin( T a, T b ) return std::abs(a) < std::abs(b) ? a : b; } +//! Returns the linear interpolation of the two values +template +constexpr T lerp(T a, T b, F t) +{ + return (1. - t) * a + t * b; +} + // @brief Calculate number of digits which LcdSpinBox would show for a given number // @note Once we upgrade to C++20, we could probably use std::formatted_size static inline int numDigitsAsInt(float f) @@ -351,6 +360,28 @@ static inline int numDigitsAsInt(float f) return digits; } +template +class LinearMap +{ +public: + LinearMap(T x1, T y1, T x2, T y2) + { + T const dx = x2 - x1; + assert (dx != T(0)); + + m_a = (y2 - y1) / dx; + m_b = y1 - m_a * x1; + } + + T map(T x) const + { + return m_a * x + m_b; + } + +private: + T m_a; + T m_b; +}; } // namespace lmms diff --git a/plugins/Amplifier/Amplifier.cpp b/plugins/Amplifier/Amplifier.cpp index 7de8fb180..ac5fdf23b 100644 --- a/plugins/Amplifier/Amplifier.cpp +++ b/plugins/Amplifier/Amplifier.cpp @@ -36,9 +36,9 @@ extern "C" Plugin::Descriptor PLUGIN_EXPORT amplifier_plugin_descriptor = { - LMMS_STRINGIFY( PLUGIN_NAME ), + LMMS_STRINGIFY(PLUGIN_NAME), "Amplifier", - QT_TRANSLATE_NOOP( "PluginBrowser", "A native amplifier plugin" ), + QT_TRANSLATE_NOOP("PluginBrowser", "A native amplifier plugin"), "Vesa Kivimäki ", 0x0100, Plugin::Type::Effect, @@ -50,99 +50,61 @@ Plugin::Descriptor PLUGIN_EXPORT amplifier_plugin_descriptor = } - -AmplifierEffect::AmplifierEffect( Model* parent, const Descriptor::SubPluginFeatures::Key* key ) : - Effect( &lifier_plugin_descriptor, parent, key ), - m_ampControls( this ) +AmplifierEffect::AmplifierEffect(Model* parent, const Descriptor::SubPluginFeatures::Key* key) : + Effect(&lifier_plugin_descriptor, parent, key), + m_ampControls(this) { } - - - - - - -bool AmplifierEffect::processAudioBuffer( sampleFrame* buf, const fpp_t frames ) +bool AmplifierEffect::processAudioBuffer(sampleFrame* buf, const fpp_t frames) { - if( !isEnabled() || !isRunning () ) - { - return( false ); - } + if (!isEnabled() || !isRunning()) { return false ; } double outSum = 0.0; const float d = dryLevel(); const float w = wetLevel(); - const ValueBuffer * volBuf = m_ampControls.m_volumeModel.valueBuffer(); - const ValueBuffer * panBuf = m_ampControls.m_panModel.valueBuffer(); - const ValueBuffer * leftBuf = m_ampControls.m_leftModel.valueBuffer(); - const ValueBuffer * rightBuf = m_ampControls.m_rightModel.valueBuffer(); + const ValueBuffer* volumeBuf = m_ampControls.m_volumeModel.valueBuffer(); + const ValueBuffer* panBuf = m_ampControls.m_panModel.valueBuffer(); + const ValueBuffer* leftBuf = m_ampControls.m_leftModel.valueBuffer(); + const ValueBuffer* rightBuf = m_ampControls.m_rightModel.valueBuffer(); - for( fpp_t f = 0; f < frames; ++f ) + for (fpp_t f = 0; f < frames; ++f) { -// qDebug( "offset %d, value %f", f, m_ampControls.m_volumeModel.value( f ) ); + const float volume = (volumeBuf ? volumeBuf->value(f) : m_ampControls.m_volumeModel.value()) * 0.01f; + const float pan = (panBuf ? panBuf->value(f) : m_ampControls.m_panModel.value()) * 0.01f; + const float left = (leftBuf ? leftBuf->value(f) : m_ampControls.m_leftModel.value()) * 0.01f; + const float right = (rightBuf ? rightBuf->value(f) : m_ampControls.m_rightModel.value()) * 0.01f; + + const float panLeft = std::min(1.0f, 1.0f - pan); + const float panRight = std::min(1.0f, 1.0f + pan); auto s = std::array{buf[f][0], buf[f][1]}; - // vol knob - if( volBuf ) - { - s[0] *= volBuf->value( f ) * 0.01f; - s[1] *= volBuf->value( f ) * 0.01f; - } - else - { - s[0] *= m_ampControls.m_volumeModel.value() * 0.01f; - s[1] *= m_ampControls.m_volumeModel.value() * 0.01f; - } - - // convert pan values to left/right values - const float pan = panBuf - ? panBuf->value( f ) - : m_ampControls.m_panModel.value(); - const float left1 = pan <= 0 - ? 1.0 - : 1.0 - pan * 0.01f; - const float right1 = pan >= 0 - ? 1.0 - : 1.0 + pan * 0.01f; - - // second stage amplification - const float left2 = leftBuf - ? leftBuf->value( f ) - : m_ampControls.m_leftModel.value(); - const float right2 = rightBuf - ? rightBuf->value( f ) - : m_ampControls.m_rightModel.value(); - - s[0] *= left1 * left2 * 0.01; - s[1] *= right1 * right2 * 0.01; + s[0] *= volume * left * panLeft; + s[1] *= volume * right * panRight; buf[f][0] = d * buf[f][0] + w * s[0]; buf[f][1] = d * buf[f][1] + w * s[1]; outSum += buf[f][0] * buf[f][0] + buf[f][1] * buf[f][1]; } - checkGate( outSum / frames ); + checkGate(outSum / frames); return isRunning(); } - - - extern "C" { // necessary for getting instance out of shared lib -PLUGIN_EXPORT Plugin * lmms_plugin_main( Model* parent, void* data ) +PLUGIN_EXPORT Plugin* lmms_plugin_main(Model* parent, void* data) { - return new AmplifierEffect( parent, static_cast( data ) ); + return new AmplifierEffect(parent, static_cast(data)); } } -} // namespace lmms \ No newline at end of file +} // namespace lmms diff --git a/plugins/Amplifier/Amplifier.h b/plugins/Amplifier/Amplifier.h index 38fd07c6f..8a39ffeb6 100644 --- a/plugins/Amplifier/Amplifier.h +++ b/plugins/Amplifier/Amplifier.h @@ -23,9 +23,8 @@ * */ - -#ifndef AMPLIFIER_H -#define AMPLIFIER_H +#ifndef LMMS_AMPLIFIER_H +#define LMMS_AMPLIFIER_H #include "Effect.h" #include "AmplifierControls.h" @@ -36,24 +35,21 @@ namespace lmms class AmplifierEffect : public Effect { public: - AmplifierEffect( Model* parent, const Descriptor::SubPluginFeatures::Key* key ); + AmplifierEffect(Model* parent, const Descriptor::SubPluginFeatures::Key* key); ~AmplifierEffect() override = default; - bool processAudioBuffer( sampleFrame* buf, const fpp_t frames ) override; + bool processAudioBuffer(sampleFrame* buf, const fpp_t frames) override; EffectControls* controls() override { return &m_ampControls; } - private: AmplifierControls m_ampControls; friend class AmplifierControls; - -} ; - +}; } // namespace lmms -#endif +#endif // LMMS_AMPLIFIER_H diff --git a/plugins/Amplifier/AmplifierControlDialog.cpp b/plugins/Amplifier/AmplifierControlDialog.cpp index ed9e98f29..1fbc3729a 100644 --- a/plugins/Amplifier/AmplifierControlDialog.cpp +++ b/plugins/Amplifier/AmplifierControlDialog.cpp @@ -23,53 +23,38 @@ * */ - #include "AmplifierControlDialog.h" #include "AmplifierControls.h" #include "embed.h" #include "Knob.h" - namespace lmms::gui { - -AmplifierControlDialog::AmplifierControlDialog( AmplifierControls* controls ) : - EffectControlDialog( controls ) +AmplifierControlDialog::AmplifierControlDialog(AmplifierControls* controls) : + EffectControlDialog(controls) { - setAutoFillBackground( true ); + setAutoFillBackground(true); QPalette pal; - pal.setBrush( backgroundRole(), PLUGIN_NAME::getIconPixmap( "artwork" ) ); - setPalette( pal ); - setFixedSize( 100, 110 ); + pal.setBrush(backgroundRole(), PLUGIN_NAME::getIconPixmap("artwork")); + setPalette(pal); + setFixedSize(100, 110); + + auto makeKnob = [this](int x, int y, const QString& label, const QString& hintText, const QString& unit, FloatModel* model, bool isVolume) + { + Knob* newKnob = new Knob(KnobType::Bright26, this); + newKnob->move(x, y); + newKnob->setModel(model); + newKnob->setLabel(label); + newKnob->setHintText(hintText, unit); + newKnob->setVolumeKnob(isVolume); + return newKnob; + }; - auto volumeKnob = new Knob(KnobType::Bright26, this); - volumeKnob -> move( 16, 10 ); - volumeKnob -> setVolumeKnob( true ); - volumeKnob->setModel( &controls->m_volumeModel ); - volumeKnob->setLabel( tr( "VOL" ) ); - volumeKnob->setHintText( tr( "Volume:" ) , "%" ); - - auto panKnob = new Knob(KnobType::Bright26, this); - panKnob -> move( 57, 10 ); - panKnob->setModel( &controls->m_panModel ); - panKnob->setLabel( tr( "PAN" ) ); - panKnob->setHintText( tr( "Panning:" ) , "" ); - - auto leftKnob = new Knob(KnobType::Bright26, this); - leftKnob -> move( 16, 65 ); - leftKnob -> setVolumeKnob( true ); - leftKnob->setModel( &controls->m_leftModel ); - leftKnob->setLabel( tr( "LEFT" ) ); - leftKnob->setHintText( tr( "Left gain:" ) , "%" ); - - auto rightKnob = new Knob(KnobType::Bright26, this); - rightKnob -> move( 57, 65 ); - rightKnob -> setVolumeKnob( true ); - rightKnob->setModel( &controls->m_rightModel ); - rightKnob->setLabel( tr( "RIGHT" ) ); - rightKnob->setHintText( tr( "Right gain:" ) , "%" ); + makeKnob(16, 10, tr("VOL"), tr("Volume:"), "%", &controls->m_volumeModel, true); + makeKnob(57, 10, tr("PAN"), tr("Panning:"), "%", &controls->m_panModel, false); + makeKnob(16, 65, tr("LEFT"), tr("Left gain:"), "%", &controls->m_leftModel, true); + makeKnob(57, 65, tr("RIGHT"), tr("Right gain:"), "%", &controls->m_rightModel, true); } - -} // namespace lmms::gui \ No newline at end of file +} // namespace lmms::gui diff --git a/plugins/Amplifier/AmplifierControlDialog.h b/plugins/Amplifier/AmplifierControlDialog.h index ad0ed50ca..672830117 100644 --- a/plugins/Amplifier/AmplifierControlDialog.h +++ b/plugins/Amplifier/AmplifierControlDialog.h @@ -23,8 +23,8 @@ * */ -#ifndef AMPLIFIER_CONTROL_DIALOG_H -#define AMPLIFIER_CONTROL_DIALOG_H +#ifndef LMMS_GUI_AMPLIFIER_CONTROL_DIALOG_H +#define LMMS_GUI_AMPLIFIER_CONTROL_DIALOG_H #include "EffectControlDialog.h" @@ -32,23 +32,23 @@ namespace lmms { class AmplifierControls; - +class FloatModel; namespace gui { +class Knob; + class AmplifierControlDialog : public EffectControlDialog { Q_OBJECT public: - AmplifierControlDialog( AmplifierControls* controls ); + AmplifierControlDialog(AmplifierControls* controls); ~AmplifierControlDialog() override = default; - -} ; - +}; } // namespace gui } // namespace lmms -#endif +#endif // LMMS_GUI_AMPLIFIER_CONTROL_DIALOG_H diff --git a/plugins/Amplifier/AmplifierControls.cpp b/plugins/Amplifier/AmplifierControls.cpp index 307730460..72960dd3b 100644 --- a/plugins/Amplifier/AmplifierControls.cpp +++ b/plugins/Amplifier/AmplifierControls.cpp @@ -23,7 +23,6 @@ * */ - #include #include "AmplifierControls.h" @@ -32,51 +31,33 @@ namespace lmms { -AmplifierControls::AmplifierControls( AmplifierEffect* effect ) : - EffectControls( effect ), - m_effect( effect ), - m_volumeModel( 100.0f, 0.0f, 200.0f, 0.1f, this, tr( "Volume" ) ), - m_panModel( 0.0f, -100.0f, 100.0f, 0.1f, this, tr( "Panning" ) ), - m_leftModel( 100.0f, 0.0f, 200.0f, 0.1f, this, tr( "Left gain" ) ), - m_rightModel( 100.0f, 0.0f, 200.0f, 0.1f, this, tr( "Right gain" ) ) +AmplifierControls::AmplifierControls(AmplifierEffect* effect) : + EffectControls(effect), + m_effect(effect), + m_volumeModel(100.0f, 0.0f, 200.0f, 0.1f, this, tr("Volume")), + m_panModel(0.0f, -100.0f, 100.0f, 0.1f, this, tr("Panning")), + m_leftModel(100.0f, 0.0f, 200.0f, 0.1f, this, tr("Left gain")), + m_rightModel(100.0f, 0.0f, 200.0f, 0.1f, this, tr("Right gain")) { -/* connect( &m_volumeModel, SIGNAL( dataChanged() ), this, SLOT( changeControl() ) ); - connect( &m_panModel, SIGNAL( dataChanged() ), this, SLOT( changeControl() ) ); - connect( &m_leftModel, SIGNAL( dataChanged() ), this, SLOT( changeControl() ) ); - connect( &m_rightModel, SIGNAL( dataChanged() ), this, SLOT( changeControl() ) );*/ } - - -void AmplifierControls::changeControl() +void AmplifierControls::loadSettings(const QDomElement& parent) { -// engine::getSong()->setModified(); + m_volumeModel.loadSettings(parent, "volume"); + m_panModel.loadSettings(parent, "pan"); + m_leftModel.loadSettings(parent, "left"); + m_rightModel.loadSettings(parent, "right"); } - - -void AmplifierControls::loadSettings( const QDomElement& _this ) +void AmplifierControls::saveSettings(QDomDocument& doc, QDomElement& parent) { - m_volumeModel.loadSettings( _this, "volume" ); - m_panModel.loadSettings( _this, "pan" ); - m_leftModel.loadSettings( _this, "left" ); - m_rightModel.loadSettings( _this, "right" ); -} - - - - -void AmplifierControls::saveSettings( QDomDocument& doc, QDomElement& _this ) -{ - m_volumeModel.saveSettings( doc, _this, "volume" ); - m_panModel.saveSettings( doc, _this, "pan" ); - m_leftModel.saveSettings( doc, _this, "left" ); - m_rightModel.saveSettings( doc, _this, "right" ); + m_volumeModel.saveSettings(doc, parent, "volume"); + m_panModel.saveSettings(doc, parent, "pan"); + m_leftModel.saveSettings(doc, parent, "left"); + m_rightModel.saveSettings(doc, parent, "right"); } } // namespace lmms - - diff --git a/plugins/Amplifier/AmplifierControls.h b/plugins/Amplifier/AmplifierControls.h index 573f6f896..6b5063ddd 100644 --- a/plugins/Amplifier/AmplifierControls.h +++ b/plugins/Amplifier/AmplifierControls.h @@ -23,8 +23,8 @@ * */ -#ifndef AMPLIFIER_CONTROLS_H -#define AMPLIFIER_CONTROLS_H +#ifndef LMMS_AMPLIFIER_CONTROLS_H +#define LMMS_AMPLIFIER_CONTROLS_H #include "EffectControls.h" #include "AmplifierControlDialog.h" @@ -39,34 +39,24 @@ namespace gui class AmplifierControlDialog; } - class AmplifierControls : public EffectControls { Q_OBJECT public: - AmplifierControls( AmplifierEffect* effect ); + AmplifierControls(AmplifierEffect* effect); ~AmplifierControls() override = default; - void saveSettings( QDomDocument & _doc, QDomElement & _parent ) override; - void loadSettings( const QDomElement & _this ) override; + void saveSettings(QDomDocument& doc, QDomElement& parent) override; + void loadSettings(const QDomElement& parent) override; inline QString nodeName() const override { return "AmplifierControls"; } - - int controlCount() override - { - return 4; - } - gui::EffectControlDialog* createView() override { - return new gui::AmplifierControlDialog( this ); + return new gui::AmplifierControlDialog(this); } - - -private slots: - void changeControl(); + int controlCount() override { return 4; } private: AmplifierEffect* m_effect; @@ -77,10 +67,8 @@ private: friend class gui::AmplifierControlDialog; friend class AmplifierEffect; - -} ; - +}; } // namespace lmms -#endif +#endif // LMMS_AMPLIFIER_CONTROLS_H diff --git a/plugins/AudioFileProcessor/AudioFileProcessor.cpp b/plugins/AudioFileProcessor/AudioFileProcessor.cpp index 6e9d00688..fbbcbca73 100644 --- a/plugins/AudioFileProcessor/AudioFileProcessor.cpp +++ b/plugins/AudioFileProcessor/AudioFileProcessor.cpp @@ -23,36 +23,21 @@ */ #include "AudioFileProcessor.h" +#include "AudioFileProcessorView.h" -#include - -#include -#include -#include - -#include - -#include "AudioEngine.h" -#include "ComboBox.h" -#include "ConfigManager.h" -#include "DataFile.h" -#include "Engine.h" -#include "gui_templates.h" #include "InstrumentTrack.h" -#include "NotePlayHandle.h" #include "PathUtil.h" -#include "PixmapButton.h" +#include "SampleLoader.h" #include "Song.h" -#include "StringPairDrag.h" -#include "Clipboard.h" -#include "embed.h" #include "plugin_export.h" +#include + + namespace lmms { - extern "C" { @@ -68,7 +53,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, } ; @@ -79,7 +68,6 @@ Plugin::Descriptor PLUGIN_EXPORT audiofileprocessor_plugin_descriptor = AudioFileProcessor::AudioFileProcessor( InstrumentTrack * _instrument_track ) : Instrument( _instrument_track, &audiofileprocessor_plugin_descriptor ), - m_sampleBuffer(), m_ampModel( 100, 0, 500, 1, this, tr( "Amplify" ) ), m_startPointModel( 0, 0, 1, 0.0000001f, this, tr( "Start of sample" ) ), m_endPointModel( 1, 0, 1, 0.0000001f, this, tr( "End of sample" ) ), @@ -127,18 +115,18 @@ void AudioFileProcessor::playNote( NotePlayHandle * _n, // played. if( m_stutterModel.value() == true && _n->frequency() < 20.0 ) { - m_nextPlayStartPoint = m_sampleBuffer.startFrame(); + m_nextPlayStartPoint = m_sample.startFrame(); m_nextPlayBackwards = false; return; } if( !_n->m_pluginData ) { - if( m_stutterModel.value() == true && m_nextPlayStartPoint >= m_sampleBuffer.endFrame() ) + if (m_stutterModel.value() == true && m_nextPlayStartPoint >= m_sample.endFrame()) { // Restart playing the note if in stutter mode, not in loop mode, // and we're at the end of the sample. - m_nextPlayStartPoint = m_sampleBuffer.startFrame(); + m_nextPlayStartPoint = m_sample.startFrame(); m_nextPlayBackwards = false; } // set interpolation mode for libsamplerate @@ -155,25 +143,25 @@ void AudioFileProcessor::playNote( NotePlayHandle * _n, srcmode = SRC_SINC_MEDIUM_QUALITY; break; } - _n->m_pluginData = new handleState( _n->hasDetuningInfo(), srcmode ); - ((handleState *)_n->m_pluginData)->setFrameIndex( m_nextPlayStartPoint ); - ((handleState *)_n->m_pluginData)->setBackwards( m_nextPlayBackwards ); + _n->m_pluginData = new Sample::PlaybackState(_n->hasDetuningInfo(), srcmode); + static_cast(_n->m_pluginData)->setFrameIndex(m_nextPlayStartPoint); + static_cast(_n->m_pluginData)->setBackwards(m_nextPlayBackwards); // debug code -/* qDebug( "frames %d", m_sampleBuffer.frames() ); - qDebug( "startframe %d", m_sampleBuffer.startFrame() ); +/* qDebug( "frames %d", m_sample->frames() ); + qDebug( "startframe %d", m_sample->startFrame() ); qDebug( "nextPlayStartPoint %d", m_nextPlayStartPoint );*/ } if( ! _n->isFinished() ) { - if( m_sampleBuffer.play( _working_buffer + offset, - (handleState *)_n->m_pluginData, + if (m_sample.play(_working_buffer + offset, + static_cast(_n->m_pluginData), frames, _n->frequency(), - static_cast( m_loopModel.value() ) ) ) + static_cast(m_loopModel.value()))) { applyRelease( _working_buffer, _n ); - emit isPlaying( ((handleState *)_n->m_pluginData)->frameIndex() ); + emit isPlaying(static_cast(_n->m_pluginData)->frameIndex()); } else { @@ -187,8 +175,8 @@ void AudioFileProcessor::playNote( NotePlayHandle * _n, } if( m_stutterModel.value() == true ) { - m_nextPlayStartPoint = ((handleState *)_n->m_pluginData)->frameIndex(); - m_nextPlayBackwards = ((handleState *)_n->m_pluginData)->isBackwards(); + m_nextPlayStartPoint = static_cast(_n->m_pluginData)->frameIndex(); + m_nextPlayBackwards = static_cast(_n->m_pluginData)->backwards(); } } @@ -197,7 +185,7 @@ void AudioFileProcessor::playNote( NotePlayHandle * _n, void AudioFileProcessor::deleteNotePluginData( NotePlayHandle * _n ) { - delete (handleState *)_n->m_pluginData; + delete static_cast(_n->m_pluginData); } @@ -205,11 +193,10 @@ void AudioFileProcessor::deleteNotePluginData( NotePlayHandle * _n ) void AudioFileProcessor::saveSettings(QDomDocument& doc, QDomElement& elem) { - elem.setAttribute("src", m_sampleBuffer.audioFile()); - if (m_sampleBuffer.audioFile().isEmpty()) + elem.setAttribute("src", m_sample.sampleFile()); + if (m_sample.sampleFile().isEmpty()) { - QString s; - elem.setAttribute("sampledata", m_sampleBuffer.toBase64(s)); + elem.setAttribute("sampledata", m_sample.toBase64()); } m_reverseModel.saveSettings(doc, elem, "reversed"); m_loopModel.saveSettings(doc, elem, "looped"); @@ -226,20 +213,17 @@ void AudioFileProcessor::saveSettings(QDomDocument& doc, QDomElement& elem) void AudioFileProcessor::loadSettings(const QDomElement& elem) { - if (!elem.attribute("src").isEmpty()) + if (auto srcFile = elem.attribute("src"); !srcFile.isEmpty()) { - setAudioFile(elem.attribute("src"), false); - - QString absolutePath = PathUtil::toAbsolute(m_sampleBuffer.audioFile()); - if (!QFileInfo(absolutePath).exists()) + if (QFileInfo(PathUtil::toAbsolute(srcFile)).exists()) { - QString message = tr("Sample not found: %1").arg(m_sampleBuffer.audioFile()); - Engine::getSong()->collectError(message); + setAudioFile(srcFile, false); } + else { Engine::getSong()->collectError(QString("%1: %2").arg(tr("Sample not found"), srcFile)); } } - else if (!elem.attribute("sampledata").isEmpty()) + else if (auto sampleData = elem.attribute("sampledata"); !sampleData.isEmpty()) { - m_sampleBuffer.loadFromBase64(elem.attribute("srcdata")); + m_sample = Sample(gui::SampleLoader::createBufferFromBase64(sampleData)); } m_loopModel.loadSettings(elem, "looped"); @@ -270,6 +254,7 @@ void AudioFileProcessor::loadSettings(const QDomElement& elem) } pointChanged(); + emit sampleUpdated(); } @@ -294,18 +279,18 @@ QString AudioFileProcessor::nodeName() 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; } + if (static_cast(m_loopModel.value()) != Sample::Loop::Off) { return 0; } // Otherwise, use the remaining sample duration const auto baseFreq = instrumentTrack()->baseFreq(); const auto freqFactor = baseFreq / note->frequency() - * Engine::audioEngine()->processingSampleRate() + * Engine::audioEngine()->outputSampleRate() / Engine::audioEngine()->baseSampleRate(); - const auto startFrame = m_nextPlayStartPoint >= m_sampleBuffer.endFrame() - ? m_sampleBuffer.startFrame() + const auto startFrame = m_nextPlayStartPoint >= m_sample.endFrame() + ? m_sample.startFrame() : m_nextPlayStartPoint; - const auto duration = m_sampleBuffer.endFrame() - startFrame; + const auto duration = m_sample.endFrame() - startFrame; return static_cast(std::floor(duration * freqFactor)); } @@ -318,25 +303,22 @@ gui::PluginView* AudioFileProcessor::instantiateView( QWidget * _parent ) return new gui::AudioFileProcessorView( this, _parent ); } - - - -void AudioFileProcessor::setAudioFile( const QString & _audio_file, - bool _rename ) +void AudioFileProcessor::setAudioFile(const QString& _audio_file, bool _rename) { // is current channel-name equal to previous-filename?? if( _rename && ( instrumentTrack()->name() == - QFileInfo( m_sampleBuffer.audioFile() ).fileName() || - m_sampleBuffer.audioFile().isEmpty() ) ) + QFileInfo(m_sample.sampleFile()).fileName() || + m_sample.sampleFile().isEmpty())) { // then set it to new one instrumentTrack()->setName( PathUtil::cleanName( _audio_file ) ); } // else we don't touch the track-name, because the user named it self - m_sampleBuffer.setAudioFile( _audio_file ); + m_sample = Sample(gui::SampleLoader::createBufferFromFile(_audio_file)); loopPointChanged(); + emit sampleUpdated(); } @@ -344,9 +326,10 @@ void AudioFileProcessor::setAudioFile( const QString & _audio_file, void AudioFileProcessor::reverseModelChanged() { - m_sampleBuffer.setReversed( m_reverseModel.value() ); - m_nextPlayStartPoint = m_sampleBuffer.startFrame(); + m_sample.setReversed(m_reverseModel.value()); + m_nextPlayStartPoint = m_sample.startFrame(); m_nextPlayBackwards = false; + emit sampleUpdated(); } @@ -354,13 +337,14 @@ void AudioFileProcessor::reverseModelChanged() void AudioFileProcessor::ampModelChanged() { - m_sampleBuffer.setAmplification( m_ampModel.value() / 100.0f ); + m_sample.setAmplification(m_ampModel.value() / 100.0f); + emit sampleUpdated(); } void AudioFileProcessor::stutterModelChanged() { - m_nextPlayStartPoint = m_sampleBuffer.startFrame(); + m_nextPlayStartPoint = m_sample.startFrame(); m_nextPlayBackwards = false; } @@ -429,852 +413,18 @@ void AudioFileProcessor::loopPointChanged() void AudioFileProcessor::pointChanged() { - const auto f_start = static_cast(m_startPointModel.value() * m_sampleBuffer.frames()); - const auto f_end = static_cast(m_endPointModel.value() * m_sampleBuffer.frames()); - const auto f_loop = static_cast(m_loopPointModel.value() * m_sampleBuffer.frames()); + const auto f_start = static_cast(m_startPointModel.value() * m_sample.sampleSize()); + const auto f_end = static_cast(m_endPointModel.value() * m_sample.sampleSize()); + const auto f_loop = static_cast(m_loopPointModel.value() * m_sample.sampleSize()); m_nextPlayStartPoint = f_start; m_nextPlayBackwards = false; - m_sampleBuffer.setAllPointFrames( f_start, f_end, f_loop, f_end ); + m_sample.setAllPointFrames(f_start, f_end, f_loop, f_end); emit dataChanged(); } - - -namespace gui -{ - - -QPixmap * AudioFileProcessorView::s_artwork = nullptr; - - -AudioFileProcessorView::AudioFileProcessorView( Instrument * _instrument, - QWidget * _parent ) : - InstrumentViewFixedSize( _instrument, _parent ) -{ - if( s_artwork == nullptr ) - { - s_artwork = new QPixmap( PLUGIN_NAME::getIconPixmap( - "artwork" ) ); - } - - m_openAudioFileButton = new PixmapButton( this ); - m_openAudioFileButton->setCursor( QCursor( Qt::PointingHandCursor ) ); - m_openAudioFileButton->move( 227, 72 ); - m_openAudioFileButton->setActiveGraphic( PLUGIN_NAME::getIconPixmap( - "select_file" ) ); - m_openAudioFileButton->setInactiveGraphic( PLUGIN_NAME::getIconPixmap( - "select_file" ) ); - connect( m_openAudioFileButton, SIGNAL( clicked() ), - this, SLOT( openAudioFile() ) ); - m_openAudioFileButton->setToolTip(tr("Open sample")); - - m_reverseButton = new PixmapButton( this ); - m_reverseButton->setCheckable( true ); - m_reverseButton->move( 164, 105 ); - m_reverseButton->setActiveGraphic( PLUGIN_NAME::getIconPixmap( - "reverse_on" ) ); - m_reverseButton->setInactiveGraphic( PLUGIN_NAME::getIconPixmap( - "reverse_off" ) ); - m_reverseButton->setToolTip(tr("Reverse sample")); - -// loop button group - - auto m_loopOffButton = new PixmapButton(this); - m_loopOffButton->setCheckable( true ); - m_loopOffButton->move( 190, 105 ); - m_loopOffButton->setActiveGraphic( PLUGIN_NAME::getIconPixmap( - "loop_off_on" ) ); - m_loopOffButton->setInactiveGraphic( PLUGIN_NAME::getIconPixmap( - "loop_off_off" ) ); - m_loopOffButton->setToolTip(tr("Disable loop")); - - auto m_loopOnButton = new PixmapButton(this); - m_loopOnButton->setCheckable( true ); - m_loopOnButton->move( 190, 124 ); - m_loopOnButton->setActiveGraphic( PLUGIN_NAME::getIconPixmap( - "loop_on_on" ) ); - m_loopOnButton->setInactiveGraphic( PLUGIN_NAME::getIconPixmap( - "loop_on_off" ) ); - m_loopOnButton->setToolTip(tr("Enable loop")); - - auto m_loopPingPongButton = new PixmapButton(this); - m_loopPingPongButton->setCheckable( true ); - m_loopPingPongButton->move( 216, 124 ); - m_loopPingPongButton->setActiveGraphic( PLUGIN_NAME::getIconPixmap( - "loop_pingpong_on" ) ); - m_loopPingPongButton->setInactiveGraphic( PLUGIN_NAME::getIconPixmap( - "loop_pingpong_off" ) ); - m_loopPingPongButton->setToolTip(tr("Enable ping-pong loop")); - - m_loopGroup = new automatableButtonGroup( this ); - m_loopGroup->addButton( m_loopOffButton ); - m_loopGroup->addButton( m_loopOnButton ); - m_loopGroup->addButton( m_loopPingPongButton ); - - m_stutterButton = new PixmapButton( this ); - m_stutterButton->setCheckable( true ); - m_stutterButton->move( 164, 124 ); - m_stutterButton->setActiveGraphic( PLUGIN_NAME::getIconPixmap( - "stutter_on" ) ); - m_stutterButton->setInactiveGraphic( PLUGIN_NAME::getIconPixmap( - "stutter_off" ) ); - m_stutterButton->setToolTip( - tr( "Continue sample playback across notes" ) ); - - m_ampKnob = new Knob( KnobType::Bright26, this ); - m_ampKnob->setVolumeKnob( true ); - m_ampKnob->move( 5, 108 ); - m_ampKnob->setHintText( tr( "Amplify:" ), "%" ); - - m_startKnob = new AudioFileProcessorWaveView::knob( this ); - m_startKnob->move( 45, 108 ); - m_startKnob->setHintText( tr( "Start point:" ), "" ); - - m_endKnob = new AudioFileProcessorWaveView::knob( this ); - m_endKnob->move( 125, 108 ); - m_endKnob->setHintText( tr( "End point:" ), "" ); - - m_loopKnob = new AudioFileProcessorWaveView::knob( this ); - m_loopKnob->move( 85, 108 ); - m_loopKnob->setHintText( tr( "Loopback point:" ), "" ); - -// interpolation selector - m_interpBox = new ComboBox( this ); - m_interpBox->setGeometry( 142, 62, 82, ComboBox::DEFAULT_HEIGHT ); - m_interpBox->setFont( pointSize<8>( m_interpBox->font() ) ); - -// wavegraph - m_waveView = 0; - newWaveView(); - - connect( castModel(), SIGNAL( isPlaying( lmms::f_cnt_t ) ), - m_waveView, SLOT( isPlaying( lmms::f_cnt_t ) ) ); - - qRegisterMetaType( "lmms::f_cnt_t" ); - - setAcceptDrops( true ); -} - - - - - - - - -void AudioFileProcessorView::dragEnterEvent( QDragEnterEvent * _dee ) -{ - // For mimeType() and MimeType enum class - using namespace Clipboard; - - if( _dee->mimeData()->hasFormat( mimeType( MimeType::StringPair ) ) ) - { - QString txt = _dee->mimeData()->data( - mimeType( MimeType::StringPair ) ); - if( txt.section( ':', 0, 0 ) == QString( "clip_%1" ).arg( - static_cast(Track::Type::Sample) ) ) - { - _dee->acceptProposedAction(); - } - else if( txt.section( ':', 0, 0 ) == "samplefile" ) - { - _dee->acceptProposedAction(); - } - else - { - _dee->ignore(); - } - } - else - { - _dee->ignore(); - } -} - - - - -void AudioFileProcessorView::newWaveView() -{ - if ( m_waveView ) - { - delete m_waveView; - m_waveView = 0; - } - m_waveView = new AudioFileProcessorWaveView( this, 245, 75, castModel()->m_sampleBuffer ); - m_waveView->move( 2, 172 ); - m_waveView->setKnobs( - dynamic_cast( m_startKnob ), - dynamic_cast( m_endKnob ), - dynamic_cast( m_loopKnob ) ); - m_waveView->show(); -} - - - - -void AudioFileProcessorView::dropEvent( QDropEvent * _de ) -{ - QString type = StringPairDrag::decodeKey( _de ); - QString value = StringPairDrag::decodeValue( _de ); - if( type == "samplefile" ) - { - castModel()->setAudioFile( value ); - _de->accept(); - newWaveView(); - return; - } - else if( type == QString( "clip_%1" ).arg( static_cast(Track::Type::Sample) ) ) - { - DataFile dataFile( value.toUtf8() ); - castModel()->setAudioFile( dataFile.content().firstChild().toElement().attribute( "src" ) ); - _de->accept(); - return; - } - - _de->ignore(); -} - - - - -void AudioFileProcessorView::paintEvent( QPaintEvent * ) -{ - QPainter p( this ); - - p.drawPixmap( 0, 0, *s_artwork ); - - auto a = castModel(); - - QString file_name = ""; - int idx = a->m_sampleBuffer.audioFile().length(); - - p.setFont( pointSize<8>( font() ) ); - - QFontMetrics fm( p.font() ); - - // simple algorithm for creating a text from the filename that - // matches in the white rectangle - while( idx > 0 && - fm.size( Qt::TextSingleLine, file_name + "..." ).width() < 210 ) - { - file_name = a->m_sampleBuffer.audioFile()[--idx] + file_name; - } - - if( idx > 0 ) - { - file_name = "..." + file_name; - } - - p.setPen( QColor( 255, 255, 255 ) ); - p.drawText( 8, 99, file_name ); -} - - - - -void AudioFileProcessorView::sampleUpdated() -{ - m_waveView->updateSampleRange(); - m_waveView->update(); - update(); -} - - - - - -void AudioFileProcessorView::openAudioFile() -{ - QString af = castModel()->m_sampleBuffer.openAudioFile(); - if (af.isEmpty()) { return; } - - castModel()->setAudioFile(af); - Engine::getSong()->setModified(); - m_waveView->updateSampleRange(); -} - - - - -void AudioFileProcessorView::modelChanged() -{ - auto a = castModel(); - connect( &a->m_sampleBuffer, SIGNAL( sampleUpdated() ), - this, SLOT( sampleUpdated() ) ); - m_ampKnob->setModel( &a->m_ampModel ); - m_startKnob->setModel( &a->m_startPointModel ); - m_endKnob->setModel( &a->m_endPointModel ); - m_loopKnob->setModel( &a->m_loopPointModel ); - m_reverseButton->setModel( &a->m_reverseModel ); - m_loopGroup->setModel( &a->m_loopModel ); - m_stutterButton->setModel( &a->m_stutterModel ); - m_interpBox->setModel( &a->m_interpolationModel ); - sampleUpdated(); -} - - - - -void AudioFileProcessorWaveView::updateSampleRange() -{ - if( m_sampleBuffer.frames() > 1 ) - { - const f_cnt_t marging = ( m_sampleBuffer.endFrame() - m_sampleBuffer.startFrame() ) * 0.1; - m_from = qMax( 0, m_sampleBuffer.startFrame() - marging ); - m_to = qMin( m_sampleBuffer.endFrame() + marging, m_sampleBuffer.frames() ); - } -} - -AudioFileProcessorWaveView::AudioFileProcessorWaveView( QWidget * _parent, int _w, int _h, SampleBuffer& buf ) : - QWidget( _parent ), - m_sampleBuffer( buf ), - m_graph( QPixmap( _w - 2 * s_padding, _h - 2 * s_padding ) ), - m_from( 0 ), - m_to( m_sampleBuffer.frames() ), - m_last_from( 0 ), - m_last_to( 0 ), - m_last_amp( 0 ), - m_startKnob( 0 ), - m_endKnob( 0 ), - m_loopKnob( 0 ), - m_isDragging( false ), - m_reversed( false ), - m_framesPlayed( 0 ), - m_animation(ConfigManager::inst()->value("ui", "animateafp").toInt()) -{ - setFixedSize( _w, _h ); - setMouseTracking( true ); - - updateSampleRange(); - - m_graph.fill( Qt::transparent ); - update(); - updateCursor(); -} - - - - -void AudioFileProcessorWaveView::isPlaying( f_cnt_t _current_frame ) -{ - m_framesPlayed = _current_frame; - update(); -} - - - - -void AudioFileProcessorWaveView::enterEvent( QEvent * _e ) -{ - updateCursor(); -} - - - - -void AudioFileProcessorWaveView::leaveEvent( QEvent * _e ) -{ - updateCursor(); -} - - - - -void AudioFileProcessorWaveView::mousePressEvent( QMouseEvent * _me ) -{ - m_isDragging = true; - m_draggingLastPoint = _me->pos(); - - const int x = _me->x(); - - const int start_dist = qAbs( m_startFrameX - x ); - const int end_dist = qAbs( m_endFrameX - x ); - const int loop_dist = qAbs( m_loopFrameX - x ); - - DraggingType dt = DraggingType::SampleLoop; int md = loop_dist; - if( start_dist < loop_dist ) { dt = DraggingType::SampleStart; md = start_dist; } - else if( end_dist < loop_dist ) { dt = DraggingType::SampleEnd; md = end_dist; } - - if( md < 4 ) - { - m_draggingType = dt; - } - else - { - m_draggingType = DraggingType::Wave; - updateCursor(_me); - } -} - - - - -void AudioFileProcessorWaveView::mouseReleaseEvent( QMouseEvent * _me ) -{ - m_isDragging = false; - if( m_draggingType == DraggingType::Wave ) - { - updateCursor(_me); - } -} - - - - -void AudioFileProcessorWaveView::mouseMoveEvent( QMouseEvent * _me ) -{ - if( ! m_isDragging ) - { - updateCursor(_me); - return; - } - - const int step = _me->x() - m_draggingLastPoint.x(); - switch( m_draggingType ) - { - case DraggingType::SampleStart: - slideSamplePointByPx( Point::Start, step ); - break; - case DraggingType::SampleEnd: - slideSamplePointByPx( Point::End, step ); - break; - case DraggingType::SampleLoop: - slideSamplePointByPx( Point::Loop, step ); - break; - case DraggingType::Wave: - default: - if( qAbs( _me->y() - m_draggingLastPoint.y() ) - < 2 * qAbs( _me->x() - m_draggingLastPoint.x() ) ) - { - slide( step ); - } - else - { - zoom( _me->y() < m_draggingLastPoint.y() ); - } - } - - m_draggingLastPoint = _me->pos(); - update(); -} - - - - -void AudioFileProcessorWaveView::wheelEvent( QWheelEvent * _we ) -{ - zoom( _we->angleDelta().y() > 0 ); - update(); -} - - - - -void AudioFileProcessorWaveView::paintEvent( QPaintEvent * _pe ) -{ - QPainter p( this ); - - p.drawPixmap( s_padding, s_padding, m_graph ); - - const QRect graph_rect( s_padding, s_padding, width() - 2 * s_padding, height() - 2 * s_padding ); - const f_cnt_t frames = m_to - m_from; - m_startFrameX = graph_rect.x() + ( m_sampleBuffer.startFrame() - m_from ) * - double( graph_rect.width() ) / frames; - m_endFrameX = graph_rect.x() + ( m_sampleBuffer.endFrame() - m_from ) * - double( graph_rect.width() ) / frames; - m_loopFrameX = graph_rect.x() + ( m_sampleBuffer.loopStartFrame() - m_from ) * - double( graph_rect.width() ) / frames; - const int played_width_px = ( m_framesPlayed - m_from ) * - double( graph_rect.width() ) / frames; - - // loop point line - p.setPen( QColor( 0x7F, 0xFF, 0xFF ) ); //TODO: put into a qproperty - p.drawLine( m_loopFrameX, graph_rect.y(), - m_loopFrameX, - graph_rect.height() + graph_rect.y() ); - - // start/end lines - p.setPen( QColor( 0xFF, 0xFF, 0xFF ) ); //TODO: put into a qproperty - p.drawLine( m_startFrameX, graph_rect.y(), - m_startFrameX, - graph_rect.height() + graph_rect.y() ); - p.drawLine( m_endFrameX, graph_rect.y(), - m_endFrameX, - graph_rect.height() + graph_rect.y() ); - - - if( m_endFrameX - m_startFrameX > 2 ) - { - p.fillRect( - m_startFrameX + 1, - graph_rect.y(), - m_endFrameX - m_startFrameX - 1, - graph_rect.height() + graph_rect.y(), - QColor( 95, 175, 255, 50 ) //TODO: put into a qproperty - ); - if( m_endFrameX - m_loopFrameX > 2 ) - p.fillRect( - m_loopFrameX + 1, - graph_rect.y(), - m_endFrameX - m_loopFrameX - 1, - graph_rect.height() + graph_rect.y(), - QColor( 95, 205, 255, 65 ) //TODO: put into a qproperty - ); - - if( m_framesPlayed && m_animation) - { - QLinearGradient g( m_startFrameX, 0, played_width_px, 0 ); - const QColor c( 0, 120, 255, 180 ); //TODO: put into a qproperty - g.setColorAt( 0, Qt::transparent ); - g.setColorAt( 0.8, c ); - g.setColorAt( 1, c ); - p.fillRect( - m_startFrameX + 1, - graph_rect.y(), - played_width_px - ( m_startFrameX + 1 ), - graph_rect.height() + graph_rect.y(), - g - ); - p.setPen( QColor( 255, 255, 255 ) ); //TODO: put into a qproperty - p.drawLine( - played_width_px, - graph_rect.y(), - played_width_px, - graph_rect.height() + graph_rect.y() - ); - m_framesPlayed = 0; - } - } - - QLinearGradient g( 0, 0, width() * 0.7, 0 ); - const QColor c( 16, 111, 170, 180 ); - g.setColorAt( 0, c ); - g.setColorAt( 0.4, c ); - g.setColorAt( 1, Qt::transparent ); - p.fillRect( s_padding, s_padding, m_graph.width(), 14, g ); - - p.setPen( QColor( 255, 255, 255 ) ); - p.setFont( pointSize<8>( font() ) ); - - QString length_text; - const int length = m_sampleBuffer.sampleLength(); - - if( length > 20000 ) - { - length_text = QString::number( length / 1000 ) + "s"; - } - else if( length > 2000 ) - { - length_text = QString::number( ( length / 100 ) / 10.0 ) + "s"; - } - else - { - length_text = QString::number( length ) + "ms"; - } - - p.drawText( - s_padding + 2, - s_padding + 10, - tr( "Sample length:" ) + " " + length_text - ); -} - - - - -void AudioFileProcessorWaveView::updateGraph() -{ - if( m_to == 1 ) - { - m_to = m_sampleBuffer.frames() * 0.7; - slideSamplePointToFrames( Point::End, m_to * 0.7 ); - } - - if( m_from > m_sampleBuffer.startFrame() ) - { - m_from = m_sampleBuffer.startFrame(); - } - - if( m_to < m_sampleBuffer.endFrame() ) - { - m_to = m_sampleBuffer.endFrame(); - } - - if( m_sampleBuffer.reversed() != m_reversed ) - { - reverse(); - } - else if( m_last_from == m_from && m_last_to == m_to && m_sampleBuffer.amplification() == m_last_amp ) - { - return; - } - - m_last_from = m_from; - m_last_to = m_to; - m_last_amp = m_sampleBuffer.amplification(); - - m_graph.fill( Qt::transparent ); - QPainter p( &m_graph ); - p.setPen( QColor( 255, 255, 255 ) ); - - m_sampleBuffer.visualize( - p, - QRect( 0, 0, m_graph.width(), m_graph.height() ), - m_from, m_to - ); -} - - - - -void AudioFileProcessorWaveView::zoom( const bool _out ) -{ - const f_cnt_t start = m_sampleBuffer.startFrame(); - const f_cnt_t end = m_sampleBuffer.endFrame(); - const f_cnt_t frames = m_sampleBuffer.frames(); - const f_cnt_t d_from = start - m_from; - const f_cnt_t d_to = m_to - end; - - const f_cnt_t step = qMax( 1, qMax( d_from, d_to ) / 10 ); - const f_cnt_t step_from = ( _out ? - step : step ); - const f_cnt_t step_to = ( _out ? step : - step ); - - const double comp_ratio = double( qMin( d_from, d_to ) ) - / qMax( 1, qMax( d_from, d_to ) ); - - f_cnt_t new_from; - f_cnt_t new_to; - - if( ( _out && d_from < d_to ) || ( ! _out && d_to < d_from ) ) - { - new_from = qBound( 0, m_from + step_from, start ); - new_to = qBound( - end, - m_to + f_cnt_t( step_to * ( new_from == m_from ? 1 : comp_ratio ) ), - frames - ); - } - else - { - new_to = qBound( end, m_to + step_to, frames ); - new_from = qBound( - 0, - m_from + f_cnt_t( step_from * ( new_to == m_to ? 1 : comp_ratio ) ), - start - ); - } - - if( double( new_to - new_from ) / m_sampleBuffer.sampleRate() > 0.05 ) - { - m_from = new_from; - m_to = new_to; - } -} - - - - -void AudioFileProcessorWaveView::slide( int _px ) -{ - const double fact = qAbs( double( _px ) / width() ); - f_cnt_t step = ( m_to - m_from ) * fact; - if( _px > 0 ) - { - step = -step; - } - - f_cnt_t step_from = qBound( 0, m_from + step, m_sampleBuffer.frames() ) - m_from; - f_cnt_t step_to = qBound( m_from + 1, m_to + step, m_sampleBuffer.frames() ) - m_to; - - step = qAbs( step_from ) < qAbs( step_to ) ? step_from : step_to; - - m_from += step; - m_to += step; - slideSampleByFrames( step ); -} - - - - -void AudioFileProcessorWaveView::setKnobs( knob * _start, knob * _end, knob * _loop ) -{ - m_startKnob = _start; - m_endKnob = _end; - m_loopKnob = _loop; - - m_startKnob->setWaveView( this ); - m_startKnob->setRelatedKnob( m_endKnob ); - - m_endKnob->setWaveView( this ); - m_endKnob->setRelatedKnob( m_startKnob ); - - m_loopKnob->setWaveView( this ); -} - - - - -void AudioFileProcessorWaveView::slideSamplePointByPx( Point _point, int _px ) -{ - slideSamplePointByFrames( - _point, - f_cnt_t( ( double( _px ) / width() ) * ( m_to - m_from ) ) - ); -} - - - - -void AudioFileProcessorWaveView::slideSamplePointByFrames( Point _point, f_cnt_t _frames, bool _slide_to ) -{ - knob * a_knob = m_startKnob; - switch( _point ) - { - case Point::End: - a_knob = m_endKnob; - break; - case Point::Loop: - a_knob = m_loopKnob; - break; - case Point::Start: - break; - } - if( a_knob == nullptr ) - { - return; - } - else - { - const double v = static_cast( _frames ) / m_sampleBuffer.frames(); - if( _slide_to ) - { - a_knob->slideTo( v ); - } - else - { - a_knob->slideBy( v ); - } - } -} - - - - -void AudioFileProcessorWaveView::slideSampleByFrames( f_cnt_t _frames ) -{ - if( m_sampleBuffer.frames() <= 1 ) - { - return; - } - const double v = static_cast( _frames ) / m_sampleBuffer.frames(); - if( m_startKnob ) { - m_startKnob->slideBy( v, false ); - } - if( m_endKnob ) { - m_endKnob->slideBy( v, false ); - } - if( m_loopKnob ) { - m_loopKnob->slideBy( v, false ); - } -} - - - - -void AudioFileProcessorWaveView::reverse() -{ - slideSampleByFrames( - m_sampleBuffer.frames() - - m_sampleBuffer.endFrame() - - m_sampleBuffer.startFrame() - ); - - const f_cnt_t from = m_from; - m_from = m_sampleBuffer.frames() - m_to; - m_to = m_sampleBuffer.frames() - from; - - m_reversed = ! m_reversed; -} - - - -void AudioFileProcessorWaveView::updateCursor( QMouseEvent * _me ) -{ - bool const waveIsDragged = m_isDragging && (m_draggingType == DraggingType::Wave); - bool const pointerCloseToStartEndOrLoop = (_me != nullptr ) && - ( isCloseTo( _me->x(), m_startFrameX ) || - isCloseTo( _me->x(), m_endFrameX ) || - isCloseTo( _me->x(), m_loopFrameX ) ); - - if( !m_isDragging && pointerCloseToStartEndOrLoop) - setCursor(Qt::SizeHorCursor); - else if( waveIsDragged ) - setCursor(Qt::ClosedHandCursor); - else - setCursor(Qt::OpenHandCursor); -} - - - - -void AudioFileProcessorWaveView::knob::slideTo( double _v, bool _check_bound ) -{ - if( _check_bound && ! checkBound( _v ) ) - { - return; - } - model()->setValue( _v ); - emit sliderMoved( model()->value() ); -} - - - - -float AudioFileProcessorWaveView::knob::getValue( const QPoint & _p ) -{ - const double dec_fact = ! m_waveView ? 1 : - double( m_waveView->m_to - m_waveView->m_from ) - / m_waveView->m_sampleBuffer.frames(); - const float inc = Knob::getValue( _p ) * dec_fact; - - return inc; -} - - - - -bool AudioFileProcessorWaveView::knob::checkBound( double _v ) const -{ - if( ! m_relatedKnob || ! m_waveView ) - { - return true; - } - - if( ( m_relatedKnob->model()->value() - _v > 0 ) != - ( m_relatedKnob->model()->value() - model()->value() >= 0 ) ) - return false; - - const double d1 = qAbs( m_relatedKnob->model()->value() - model()->value() ) - * ( m_waveView->m_sampleBuffer.frames() ) - / m_waveView->m_sampleBuffer.sampleRate(); - - const double d2 = qAbs( m_relatedKnob->model()->value() - _v ) - * ( m_waveView->m_sampleBuffer.frames() ) - / m_waveView->m_sampleBuffer.sampleRate(); - - return d1 < d2 || d2 > 0.005; -} - - -} // namespace gui - - - - extern "C" { diff --git a/plugins/AudioFileProcessor/AudioFileProcessor.h b/plugins/AudioFileProcessor/AudioFileProcessor.h index 39bd11c3a..00ad92129 100644 --- a/plugins/AudioFileProcessor/AudioFileProcessor.h +++ b/plugins/AudioFileProcessor/AudioFileProcessor.h @@ -26,30 +26,17 @@ #ifndef LMMS_AUDIO_FILE_PROCESSOR_H #define LMMS_AUDIO_FILE_PROCESSOR_H -#include +#include "AutomatableModel.h" #include "ComboBoxModel.h" + #include "Instrument.h" -#include "InstrumentView.h" -#include "SampleBuffer.h" -#include "Knob.h" +#include "Sample.h" namespace lmms { -namespace gui -{ -class automatableButtonGroup; -class PluginView; -class InstrumentViewFixedSize; -class Knob; -class PixmapButton; -class ComboBox; -class AudioFileProcessorView; -} - - class AudioFileProcessor : public Instrument { Q_OBJECT @@ -69,17 +56,27 @@ public: auto beatLen(NotePlayHandle* note) const -> int override; - f_cnt_t desiredReleaseFrames() const override + float desiredReleaseTimeMs() const override { - return 128; + return 3.f; } gui::PluginView* instantiateView( QWidget * _parent ) override; + Sample const & sample() const { return m_sample; } + + FloatModel & ampModel() { return m_ampModel; } + FloatModel & startPointModel() { return m_startPointModel; } + FloatModel & endPointModel() { return m_endPointModel; } + FloatModel & loopPointModel() { return m_loopPointModel; } + BoolModel & reverseModel() { return m_reverseModel; } + IntModel & loopModel() { return m_loopModel; } + BoolModel & stutterModel() { return m_stutterModel; } + ComboBoxModel & interpolationModel() { return m_interpolationModel; } + public slots: - void setAudioFile( const QString & _audio_file, bool _rename = true ); - + void setAudioFile(const QString& _audio_file, bool _rename = true); private slots: void reverseModelChanged(); @@ -93,12 +90,10 @@ private slots: signals: void isPlaying( lmms::f_cnt_t _current_frame ); - + void sampleUpdated(); private: - using handleState = SampleBuffer::handleState; - - SampleBuffer m_sampleBuffer; + Sample m_sample; FloatModel m_ampModel; FloatModel m_startPointModel; @@ -111,194 +106,8 @@ private: f_cnt_t m_nextPlayStartPoint; bool m_nextPlayBackwards; - - friend class gui::AudioFileProcessorView; - } ; - -namespace gui -{ - -class AudioFileProcessorWaveView; - - -class AudioFileProcessorView : public gui::InstrumentViewFixedSize -{ - Q_OBJECT -public: - AudioFileProcessorView( Instrument * _instrument, QWidget * _parent ); - virtual ~AudioFileProcessorView() = default; - - void newWaveView(); -protected slots: - void sampleUpdated(); - void openAudioFile(); - - -protected: - virtual void dragEnterEvent( QDragEnterEvent * _dee ); - virtual void dropEvent( QDropEvent * _de ); - virtual void paintEvent( QPaintEvent * ); - - -private: - virtual void modelChanged(); - - static QPixmap * s_artwork; - - AudioFileProcessorWaveView * m_waveView; - Knob * m_ampKnob; - Knob * m_startKnob; - Knob * m_endKnob; - Knob * m_loopKnob; - - gui::PixmapButton * m_openAudioFileButton; - PixmapButton * m_reverseButton; - automatableButtonGroup * m_loopGroup; - PixmapButton * m_stutterButton; - ComboBox * m_interpBox; - -} ; - - - -class AudioFileProcessorWaveView : public QWidget -{ - Q_OBJECT -protected: - virtual void enterEvent( QEvent * _e ); - virtual void leaveEvent( QEvent * _e ); - virtual void mousePressEvent( QMouseEvent * _me ); - virtual void mouseReleaseEvent( QMouseEvent * _me ); - virtual void mouseMoveEvent( QMouseEvent * _me ); - virtual void wheelEvent( QWheelEvent * _we ); - virtual void paintEvent( QPaintEvent * _pe ); - - -public: - enum class Point - { - Start, - End, - Loop - } ; - - class knob : public Knob - { - const AudioFileProcessorWaveView * m_waveView; - const Knob * m_relatedKnob; - - - public: - knob( QWidget * _parent ) : - Knob( KnobType::Bright26, _parent ), - m_waveView( 0 ), - m_relatedKnob( 0 ) - { - setFixedSize( 37, 47 ); - } - - void setWaveView( const AudioFileProcessorWaveView * _wv ) - { - m_waveView = _wv; - } - - void setRelatedKnob( const Knob * _knob ) - { - m_relatedKnob = _knob; - } - - void slideBy( double _v, bool _check_bound = true ) - { - slideTo( model()->value() + _v, _check_bound ); - } - - void slideTo( double _v, bool _check_bound = true ); - - - protected: - float getValue( const QPoint & _p ); - - - private: - bool checkBound( double _v ) const; - } ; - - -public slots: - void update() - { - updateGraph(); - QWidget::update(); - } - - void isPlaying( lmms::f_cnt_t _current_frame ); - - -private: - static const int s_padding = 2; - - enum class DraggingType - { - Wave, - SampleStart, - SampleEnd, - SampleLoop - } ; - - SampleBuffer& m_sampleBuffer; - QPixmap m_graph; - f_cnt_t m_from; - f_cnt_t m_to; - f_cnt_t m_last_from; - f_cnt_t m_last_to; - float m_last_amp; - knob * m_startKnob; - knob * m_endKnob; - knob * m_loopKnob; - f_cnt_t m_startFrameX; - f_cnt_t m_endFrameX; - f_cnt_t m_loopFrameX; - bool m_isDragging; - QPoint m_draggingLastPoint; - DraggingType m_draggingType; - bool m_reversed; - f_cnt_t m_framesPlayed; - bool m_animation; - -public: - AudioFileProcessorWaveView( QWidget * _parent, int _w, int _h, SampleBuffer& buf ); - void setKnobs(knob *_start, knob *_end, knob *_loop ); - - - void updateSampleRange(); -private: - void zoom( const bool _out = false ); - void slide( int _px ); - void slideSamplePointByPx( Point _point, int _px ); - void slideSamplePointByFrames( Point _point, f_cnt_t _frames, bool _slide_to = false ); - void slideSampleByFrames( f_cnt_t _frames ); - - void slideSamplePointToFrames( Point _point, f_cnt_t _frames ) - { - slideSamplePointByFrames( _point, _frames, true ); - } - - void updateGraph(); - void reverse(); - void updateCursor( QMouseEvent * _me = nullptr ); - - static bool isCloseTo( int _a, int _b ) - { - return qAbs( _a - _b ) < 4; - } - -} ; - - -} // namespace gui - } // namespace lmms #endif // LMMS_AUDIO_FILE_PROCESSOR_H diff --git a/plugins/AudioFileProcessor/AudioFileProcessorView.cpp b/plugins/AudioFileProcessor/AudioFileProcessorView.cpp new file mode 100644 index 000000000..b7d5802dc --- /dev/null +++ b/plugins/AudioFileProcessor/AudioFileProcessorView.cpp @@ -0,0 +1,285 @@ +/* + * AudioFileProcessor.cpp - instrument for using audio files + * + * Copyright (c) 2004-2014 Tobias Doerffel + * + * This file is part of LMMS - https://lmms.io + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program (see COPYING); if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA. + * + */ + +#include "AudioFileProcessorView.h" + +#include "AudioFileProcessor.h" +#include "AudioFileProcessorWaveView.h" + +#include + +#include "ComboBox.h" +#include "DataFile.h" +#include "gui_templates.h" +#include "PixmapButton.h" +#include "SampleLoader.h" +#include "Song.h" +#include "StringPairDrag.h" +#include "Track.h" +#include "Clipboard.h" + + +namespace lmms +{ + +namespace gui +{ + +AudioFileProcessorView::AudioFileProcessorView(Instrument* instrument, + QWidget* parent) : + InstrumentViewFixedSize(instrument, parent) +{ + m_openAudioFileButton = new PixmapButton(this); + m_openAudioFileButton->setCursor(QCursor(Qt::PointingHandCursor)); + m_openAudioFileButton->move(227, 72); + m_openAudioFileButton->setActiveGraphic(PLUGIN_NAME::getIconPixmap( + "select_file")); + m_openAudioFileButton->setInactiveGraphic(PLUGIN_NAME::getIconPixmap( + "select_file")); + connect(m_openAudioFileButton, SIGNAL(clicked()), + this, SLOT(openAudioFile())); + m_openAudioFileButton->setToolTip(tr("Open sample")); + + m_reverseButton = new PixmapButton(this); + m_reverseButton->setCheckable(true); + m_reverseButton->move(164, 105); + m_reverseButton->setActiveGraphic(PLUGIN_NAME::getIconPixmap( + "reverse_on")); + m_reverseButton->setInactiveGraphic(PLUGIN_NAME::getIconPixmap( + "reverse_off")); + m_reverseButton->setToolTip(tr("Reverse sample")); + +// loop button group + + auto m_loopOffButton = new PixmapButton(this); + m_loopOffButton->setCheckable(true); + m_loopOffButton->move(190, 105); + m_loopOffButton->setActiveGraphic(PLUGIN_NAME::getIconPixmap( + "loop_off_on")); + m_loopOffButton->setInactiveGraphic(PLUGIN_NAME::getIconPixmap( + "loop_off_off")); + m_loopOffButton->setToolTip(tr("Disable loop")); + + auto m_loopOnButton = new PixmapButton(this); + m_loopOnButton->setCheckable(true); + m_loopOnButton->move(190, 124); + m_loopOnButton->setActiveGraphic(PLUGIN_NAME::getIconPixmap( + "loop_on_on")); + m_loopOnButton->setInactiveGraphic(PLUGIN_NAME::getIconPixmap( + "loop_on_off")); + m_loopOnButton->setToolTip(tr("Enable loop")); + + auto m_loopPingPongButton = new PixmapButton(this); + m_loopPingPongButton->setCheckable(true); + m_loopPingPongButton->move(216, 124); + m_loopPingPongButton->setActiveGraphic(PLUGIN_NAME::getIconPixmap( + "loop_pingpong_on")); + m_loopPingPongButton->setInactiveGraphic(PLUGIN_NAME::getIconPixmap( + "loop_pingpong_off")); + m_loopPingPongButton->setToolTip(tr("Enable ping-pong loop")); + + m_loopGroup = new automatableButtonGroup(this); + m_loopGroup->addButton(m_loopOffButton); + m_loopGroup->addButton(m_loopOnButton); + m_loopGroup->addButton(m_loopPingPongButton); + + m_stutterButton = new PixmapButton(this); + m_stutterButton->setCheckable(true); + m_stutterButton->move(164, 124); + m_stutterButton->setActiveGraphic(PLUGIN_NAME::getIconPixmap( + "stutter_on")); + m_stutterButton->setInactiveGraphic(PLUGIN_NAME::getIconPixmap( + "stutter_off")); + m_stutterButton->setToolTip( + tr("Continue sample playback across notes")); + + m_ampKnob = new Knob(KnobType::Bright26, this); + m_ampKnob->setVolumeKnob(true); + m_ampKnob->move(5, 108); + m_ampKnob->setHintText(tr("Amplify:"), "%"); + + m_startKnob = new AudioFileProcessorWaveView::knob(this); + m_startKnob->move(45, 108); + m_startKnob->setHintText(tr("Start point:"), ""); + + m_endKnob = new AudioFileProcessorWaveView::knob(this); + m_endKnob->move(125, 108); + m_endKnob->setHintText(tr("End point:"), ""); + + m_loopKnob = new AudioFileProcessorWaveView::knob(this); + m_loopKnob->move(85, 108); + m_loopKnob->setHintText(tr("Loopback point:"), ""); + +// interpolation selector + m_interpBox = new ComboBox(this); + m_interpBox->setGeometry(142, 62, 82, ComboBox::DEFAULT_HEIGHT); + +// wavegraph + m_waveView = 0; + newWaveView(); + + connect(castModel(), SIGNAL(isPlaying(lmms::f_cnt_t)), + m_waveView, SLOT(isPlaying(lmms::f_cnt_t))); + + qRegisterMetaType("lmms::f_cnt_t"); + + setAcceptDrops(true); +} + +void AudioFileProcessorView::dragEnterEvent(QDragEnterEvent* dee) +{ + // For mimeType() and MimeType enum class + using namespace Clipboard; + + if (dee->mimeData()->hasFormat(mimeType(MimeType::StringPair))) + { + QString txt = dee->mimeData()->data( + mimeType(MimeType::StringPair)); + if (txt.section(':', 0, 0) == QString("clip_%1").arg( + static_cast(Track::Type::Sample))) + { + dee->acceptProposedAction(); + } + else if (txt.section(':', 0, 0) == "samplefile") + { + dee->acceptProposedAction(); + } + else + { + dee->ignore(); + } + } + else + { + dee->ignore(); + } +} + +void AudioFileProcessorView::newWaveView() +{ + if (m_waveView) + { + delete m_waveView; + m_waveView = 0; + } + m_waveView = new AudioFileProcessorWaveView(this, 245, 75, &castModel()->sample(), + dynamic_cast(m_startKnob), + dynamic_cast(m_endKnob), + dynamic_cast(m_loopKnob)); + m_waveView->move(2, 172); + + m_waveView->show(); +} + +void AudioFileProcessorView::dropEvent(QDropEvent* de) +{ + const auto type = StringPairDrag::decodeKey(de); + const auto value = StringPairDrag::decodeValue(de); + + if (type == "samplefile") { castModel()->setAudioFile(value); } + else if (type == QString("clip_%1").arg(static_cast(Track::Type::Sample))) + { + DataFile dataFile(value.toUtf8()); + castModel()->setAudioFile(dataFile.content().firstChild().toElement().attribute("src")); + } + else + { + de->ignore(); + return; + } + + m_waveView->updateSampleRange(); + Engine::getSong()->setModified(); + de->accept(); +} + +void AudioFileProcessorView::paintEvent(QPaintEvent*) +{ + QPainter p(this); + + static auto s_artwork = PLUGIN_NAME::getIconPixmap("artwork"); + p.drawPixmap(0, 0, s_artwork); + + auto a = castModel(); + + QString file_name = ""; + + int idx = a->sample().sampleFile().length(); + + p.setFont(adjustedToPixelSize(font(), 8)); + + QFontMetrics fm(p.font()); + + // simple algorithm for creating a text from the filename that + // matches in the white rectangle + while(idx > 0 && + fm.size(Qt::TextSingleLine, file_name + "...").width() < 210) + { + file_name = a->sample().sampleFile()[--idx] + file_name; + } + + if (idx > 0) + { + file_name = "..." + file_name; + } + + p.setPen(QColor(255, 255, 255)); + p.drawText(8, 99, file_name); +} + +void AudioFileProcessorView::sampleUpdated() +{ + m_waveView->updateSampleRange(); + m_waveView->update(); + update(); +} + +void AudioFileProcessorView::openAudioFile() +{ + QString af = SampleLoader::openAudioFile(); + if (af.isEmpty()) { return; } + + castModel()->setAudioFile(af); + Engine::getSong()->setModified(); + m_waveView->updateSampleRange(); +} + +void AudioFileProcessorView::modelChanged() +{ + auto a = castModel(); + connect(a, &AudioFileProcessor::sampleUpdated, this, &AudioFileProcessorView::sampleUpdated); + m_ampKnob->setModel(&a->ampModel()); + m_startKnob->setModel(&a->startPointModel()); + m_endKnob->setModel(&a->endPointModel()); + m_loopKnob->setModel(&a->loopPointModel()); + m_reverseButton->setModel(&a->reverseModel()); + m_loopGroup->setModel(&a->loopModel()); + m_stutterButton->setModel(&a->stutterModel()); + m_interpBox->setModel(&a->interpolationModel()); + sampleUpdated(); +} + +} // namespace gui + +} // namespace lmms diff --git a/plugins/AudioFileProcessor/AudioFileProcessorView.h b/plugins/AudioFileProcessor/AudioFileProcessorView.h new file mode 100644 index 000000000..039eaab2c --- /dev/null +++ b/plugins/AudioFileProcessor/AudioFileProcessorView.h @@ -0,0 +1,85 @@ +/* + * AudioFileProcessorView.h - View of the AFP + * + * Copyright (c) 2004-2014 Tobias Doerffel + * + * This file is part of LMMS - https://lmms.io + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program (see COPYING); if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA. + * + */ + +#ifndef LMMS_AUDIO_FILE_PROCESSOR_VIEW_H +#define LMMS_AUDIO_FILE_PROCESSOR_VIEW_H + +#include "InstrumentView.h" + + +namespace lmms +{ + +namespace gui +{ + +class automatableButtonGroup; +class Knob; +class PixmapButton; +class ComboBox; +class AudioFileProcessorWaveView; + + +class AudioFileProcessorView : public gui::InstrumentViewFixedSize +{ + Q_OBJECT +public: + AudioFileProcessorView(Instrument* instrument, QWidget* parent); + virtual ~AudioFileProcessorView() = default; + + void newWaveView(); + +protected slots: + void sampleUpdated(); + void openAudioFile(); + +protected: + virtual void dragEnterEvent(QDragEnterEvent* dee); + virtual void dropEvent(QDropEvent* de); + virtual void paintEvent(QPaintEvent*); + + // Private methods +private: + virtual void modelChanged(); + + // Private members +private: + AudioFileProcessorWaveView* m_waveView; + Knob* m_ampKnob; + Knob* m_startKnob; + Knob* m_endKnob; + Knob* m_loopKnob; + + gui::PixmapButton* m_openAudioFileButton; + PixmapButton* m_reverseButton; + automatableButtonGroup* m_loopGroup; + PixmapButton* m_stutterButton; + ComboBox* m_interpBox; +} ; + +} // namespace gui + +} // namespace lmms + +#endif // LMMS_AUDIO_FILE_PROCESSOR_VIEW_H diff --git a/plugins/AudioFileProcessor/AudioFileProcessorWaveView.cpp b/plugins/AudioFileProcessor/AudioFileProcessorWaveView.cpp new file mode 100644 index 000000000..1742ee3a7 --- /dev/null +++ b/plugins/AudioFileProcessor/AudioFileProcessorWaveView.cpp @@ -0,0 +1,545 @@ +/* + * AudioFileProcessorWaveView.cpp - Wave renderer of the AFP + * + * Copyright (c) 2004-2014 Tobias Doerffel + * + * This file is part of LMMS - https://lmms.io + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program (see COPYING); if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA. + * + */ + +#include "AudioFileProcessorWaveView.h" + +#include "ConfigManager.h" +#include "gui_templates.h" +#include "SampleWaveform.h" + +#include +#include + +#include + + +namespace lmms +{ + +namespace gui +{ + +void AudioFileProcessorWaveView::updateSampleRange() +{ + if (m_sample->sampleSize() > 1) + { + const f_cnt_t marging = (m_sample->endFrame() - m_sample->startFrame()) * 0.1; + setFrom(m_sample->startFrame() - marging); + setTo(m_sample->endFrame() + marging); + } +} + +void AudioFileProcessorWaveView::setTo(f_cnt_t to) +{ + m_to = std::min(to, static_cast(m_sample->sampleSize())); +} + +void AudioFileProcessorWaveView::setFrom(f_cnt_t from) +{ + m_from = std::max(from, 0); +} + +f_cnt_t AudioFileProcessorWaveView::range() const +{ + return m_to - m_from; +} + +AudioFileProcessorWaveView::AudioFileProcessorWaveView(QWidget* parent, int w, int h, Sample const* buf, + knob* start, knob* end, knob* loop) : + QWidget(parent), + m_sample(buf), + m_graph(QPixmap(w - 2 * s_padding, h - 2 * s_padding)), + m_from(0), + m_to(m_sample->sampleSize()), + m_last_from(0), + m_last_to(0), + m_last_amp(0), + m_startKnob(start), + m_endKnob(end), + m_loopKnob(loop), + m_isDragging(false), + m_reversed(false), + m_framesPlayed(0), + m_animation(ConfigManager::inst()->value("ui", "animateafp").toInt()) +{ + setFixedSize(w, h); + setMouseTracking(true); + + configureKnobRelationsAndWaveViews(); + + updateSampleRange(); + + m_graph.fill(Qt::transparent); + update(); + updateCursor(); +} + +void AudioFileProcessorWaveView::isPlaying(f_cnt_t current_frame) +{ + m_framesPlayed = current_frame; + update(); +} + +void AudioFileProcessorWaveView::enterEvent(QEvent * e) +{ + updateCursor(); +} + +void AudioFileProcessorWaveView::leaveEvent(QEvent * e) +{ + updateCursor(); +} + +void AudioFileProcessorWaveView::mousePressEvent(QMouseEvent * me) +{ + m_isDragging = true; + m_draggingLastPoint = me->pos(); + + const int x = me->x(); + + const int start_dist = qAbs(m_startFrameX - x); + const int end_dist = qAbs(m_endFrameX - x); + const int loop_dist = qAbs(m_loopFrameX - x); + + DraggingType dt = DraggingType::SampleLoop; int md = loop_dist; + if (start_dist < loop_dist) { dt = DraggingType::SampleStart; md = start_dist; } + else if (end_dist < loop_dist) { dt = DraggingType::SampleEnd; md = end_dist; } + + if (md < 4) + { + m_draggingType = dt; + } + else + { + m_draggingType = DraggingType::Wave; + updateCursor(me); + } +} + +void AudioFileProcessorWaveView::mouseReleaseEvent(QMouseEvent * me) +{ + m_isDragging = false; + if (m_draggingType == DraggingType::Wave) + { + updateCursor(me); + } +} + +void AudioFileProcessorWaveView::mouseMoveEvent(QMouseEvent * me) +{ + if (! m_isDragging) + { + updateCursor(me); + return; + } + + const int step = me->x() - m_draggingLastPoint.x(); + switch(m_draggingType) + { + case DraggingType::SampleStart: + slideSamplePointByPx(Point::Start, step); + break; + case DraggingType::SampleEnd: + slideSamplePointByPx(Point::End, step); + break; + case DraggingType::SampleLoop: + slideSamplePointByPx(Point::Loop, step); + break; + case DraggingType::Wave: + default: + if (qAbs(me->y() - m_draggingLastPoint.y()) + < 2 * qAbs(me->x() - m_draggingLastPoint.x())) + { + slide(step); + } + else + { + zoom(me->y() < m_draggingLastPoint.y()); + } + } + + m_draggingLastPoint = me->pos(); + update(); +} + +void AudioFileProcessorWaveView::wheelEvent(QWheelEvent * we) +{ + zoom(we->angleDelta().y() > 0); + update(); +} + +void AudioFileProcessorWaveView::paintEvent(QPaintEvent * pe) +{ + QPainter p(this); + + p.drawPixmap(s_padding, s_padding, m_graph); + + const QRect graph_rect(s_padding, s_padding, width() - 2 * s_padding, height() - 2 * s_padding); + const f_cnt_t frames = range(); + m_startFrameX = graph_rect.x() + (m_sample->startFrame() - m_from) * + double(graph_rect.width()) / frames; + m_endFrameX = graph_rect.x() + (m_sample->endFrame() - m_from) * + double(graph_rect.width()) / frames; + m_loopFrameX = graph_rect.x() + (m_sample->loopStartFrame() - m_from) * + double(graph_rect.width()) / frames; + const int played_width_px = (m_framesPlayed - m_from) * + double(graph_rect.width()) / frames; + + // loop point line + p.setPen(QColor(0x7F, 0xFF, 0xFF)); //TODO: put into a qproperty + p.drawLine(m_loopFrameX, graph_rect.y(), + m_loopFrameX, + graph_rect.height() + graph_rect.y()); + + // start/end lines + p.setPen(QColor(0xFF, 0xFF, 0xFF)); //TODO: put into a qproperty + p.drawLine(m_startFrameX, graph_rect.y(), + m_startFrameX, + graph_rect.height() + graph_rect.y()); + p.drawLine(m_endFrameX, graph_rect.y(), + m_endFrameX, + graph_rect.height() + graph_rect.y()); + + + if (m_endFrameX - m_startFrameX > 2) + { + p.fillRect( + m_startFrameX + 1, + graph_rect.y(), + m_endFrameX - m_startFrameX - 1, + graph_rect.height() + graph_rect.y(), + QColor(95, 175, 255, 50) //TODO: put into a qproperty + ); + if (m_endFrameX - m_loopFrameX > 2) + p.fillRect( + m_loopFrameX + 1, + graph_rect.y(), + m_endFrameX - m_loopFrameX - 1, + graph_rect.height() + graph_rect.y(), + QColor(95, 205, 255, 65) //TODO: put into a qproperty + ); + + if (m_framesPlayed && m_animation) + { + QLinearGradient g(m_startFrameX, 0, played_width_px, 0); + const QColor c(0, 120, 255, 180); //TODO: put into a qproperty + g.setColorAt(0, Qt::transparent); + g.setColorAt(0.8, c); + g.setColorAt(1, c); + p.fillRect( + m_startFrameX + 1, + graph_rect.y(), + played_width_px - (m_startFrameX + 1), + graph_rect.height() + graph_rect.y(), + g + ); + p.setPen(QColor(255, 255, 255)); //TODO: put into a qproperty + p.drawLine( + played_width_px, + graph_rect.y(), + played_width_px, + graph_rect.height() + graph_rect.y() + ); + m_framesPlayed = 0; + } + } + + QLinearGradient g(0, 0, width() * 0.7, 0); + const QColor c(16, 111, 170, 180); + g.setColorAt(0, c); + g.setColorAt(0.4, c); + g.setColorAt(1, Qt::transparent); + p.fillRect(s_padding, s_padding, m_graph.width(), 14, g); + + p.setPen(QColor(255, 255, 255)); + p.setFont(adjustedToPixelSize(font(), 8)); + + QString length_text; + const int length = m_sample->sampleDuration().count(); + + if (length > 20000) + { + length_text = QString::number(length / 1000) + "s"; + } + else if (length > 2000) + { + length_text = QString::number((length / 100) / 10.0) + "s"; + } + else + { + length_text = QString::number(length) + "ms"; + } + + p.drawText( + s_padding + 2, + s_padding + 10, + tr("Sample length:") + " " + length_text + ); +} + +void AudioFileProcessorWaveView::updateGraph() +{ + if (m_to == 1) + { + setTo(m_sample->sampleSize() * 0.7); + slideSamplePointToFrames(Point::End, m_to * 0.7); + } + + if (m_from > m_sample->startFrame()) + { + setFrom(m_sample->startFrame()); + } + + if (m_to < m_sample->endFrame()) + { + setTo(m_sample->endFrame()); + } + + if (m_sample->reversed() != m_reversed) + { + reverse(); + } + else if (m_last_from == m_from && m_last_to == m_to && m_sample->amplification() == m_last_amp) + { + return; + } + + m_last_from = m_from; + m_last_to = m_to; + m_last_amp = m_sample->amplification(); + + m_graph.fill(Qt::transparent); + QPainter p(&m_graph); + p.setPen(QColor(255, 255, 255)); + + const auto rect = QRect{0, 0, m_graph.width(), m_graph.height()}; + const auto waveform = SampleWaveform::Parameters{ + m_sample->data() + m_from, static_cast(range()), m_sample->amplification(), m_sample->reversed()}; + SampleWaveform::visualize(waveform, p, rect); +} + +void AudioFileProcessorWaveView::zoom(const bool out) +{ + const f_cnt_t start = m_sample->startFrame(); + const f_cnt_t end = m_sample->endFrame(); + const f_cnt_t frames = m_sample->sampleSize(); + const f_cnt_t d_from = start - m_from; + const f_cnt_t d_to = m_to - end; + + const f_cnt_t step = qMax(1, qMax(d_from, d_to) / 10); + const f_cnt_t step_from = (out ? - step : step); + const f_cnt_t step_to = (out ? step : - step); + + const double comp_ratio = double(qMin(d_from, d_to)) + / qMax(1, qMax(d_from, d_to)); + + const auto boundedFrom = std::clamp(m_from + step_from, 0, start); + const auto boundedTo = std::clamp(m_to + step_to, end, frames); + + const auto toStep = static_cast(step_from * (boundedTo == m_to ? 1 : comp_ratio)); + const auto newFrom + = (out && d_from < d_to) || (!out && d_to < d_from) ? boundedFrom : std::clamp(m_from + toStep, 0, start); + + const auto fromStep = static_cast(step_to * (boundedFrom == m_from ? 1 : comp_ratio)); + const auto newTo + = (out && d_from < d_to) || (!out && d_to < d_from) ? std::clamp(m_to + fromStep, end, frames) : boundedTo; + + if (static_cast(newTo - newFrom) / m_sample->sampleRate() > 0.05) + { + setFrom(newFrom); + setTo(newTo); + } +} + +void AudioFileProcessorWaveView::slide(int px) +{ + const double fact = qAbs(double(px) / width()); + f_cnt_t step = range() * fact; + if (px > 0) + { + step = -step; + } + + f_cnt_t step_from = qBound(0, m_from + step, m_sample->sampleSize()) - m_from; + f_cnt_t step_to = qBound(m_from + 1, m_to + step, m_sample->sampleSize()) - m_to; + + step = qAbs(step_from) < qAbs(step_to) ? step_from : step_to; + + setFrom(m_from + step); + setTo(m_to + step); + slideSampleByFrames(step); +} + +void AudioFileProcessorWaveView::slideSamplePointByPx(Point point, int px) +{ + slideSamplePointByFrames( + point, + f_cnt_t((double(px) / width()) * range()) + ); +} + +void AudioFileProcessorWaveView::slideSamplePointByFrames(Point point, f_cnt_t frames, bool slide_to) +{ + knob * a_knob = m_startKnob; + switch(point) + { + case Point::End: + a_knob = m_endKnob; + break; + case Point::Loop: + a_knob = m_loopKnob; + break; + case Point::Start: + break; + } + if (a_knob == nullptr) + { + return; + } + else + { + const double v = static_cast(frames) / m_sample->sampleSize(); + if (slide_to) + { + a_knob->slideTo(v); + } + else + { + a_knob->slideBy(v); + } + } +} + + + + +void AudioFileProcessorWaveView::slideSampleByFrames(f_cnt_t frames) +{ + if (m_sample->sampleSize() <= 1) + { + return; + } + const double v = static_cast(frames) / m_sample->sampleSize(); + // update knobs in the right order + // to avoid them clamping each other + if (v < 0) + { + m_startKnob->slideBy(v, false); + m_loopKnob->slideBy(v, false); + m_endKnob->slideBy(v, false); + } + else + { + m_endKnob->slideBy(v, false); + m_loopKnob->slideBy(v, false); + m_startKnob->slideBy(v, false); + } +} + +void AudioFileProcessorWaveView::reverse() +{ + slideSampleByFrames( + m_sample->sampleSize() + - m_sample->endFrame() + - m_sample->startFrame() + ); + + const f_cnt_t from = m_from; + setFrom(m_sample->sampleSize() - m_to); + setTo(m_sample->sampleSize() - from); + + m_reversed = ! m_reversed; +} + +void AudioFileProcessorWaveView::updateCursor(QMouseEvent * me) +{ + bool const waveIsDragged = m_isDragging && (m_draggingType == DraggingType::Wave); + bool const pointerCloseToStartEndOrLoop = (me != nullptr) && + (isCloseTo(me->x(), m_startFrameX) || + isCloseTo(me->x(), m_endFrameX) || + isCloseTo(me->x(), m_loopFrameX)); + + if (!m_isDragging && pointerCloseToStartEndOrLoop) + setCursor(Qt::SizeHorCursor); + else if (waveIsDragged) + setCursor(Qt::ClosedHandCursor); + else + setCursor(Qt::OpenHandCursor); +} + +void AudioFileProcessorWaveView::configureKnobRelationsAndWaveViews() +{ + m_startKnob->setWaveView(this); + m_startKnob->setRelatedKnob(m_endKnob); + + m_endKnob->setWaveView(this); + m_endKnob->setRelatedKnob(m_startKnob); + + m_loopKnob->setWaveView(this); +} + +void AudioFileProcessorWaveView::knob::slideTo(double v, bool check_bound) +{ + if (check_bound && ! checkBound(v)) + { + return; + } + model()->setValue(v); + emit sliderMoved(model()->value()); +} + +float AudioFileProcessorWaveView::knob::getValue(const QPoint & p) +{ + const double dec_fact = ! m_waveView ? 1 : + static_cast(m_waveView->m_to - m_waveView->m_from) / m_waveView->m_sample->sampleSize(); + const float inc = Knob::getValue(p) * dec_fact; + + return inc; +} + +bool AudioFileProcessorWaveView::knob::checkBound(double v) const +{ + if (! m_relatedKnob || ! m_waveView) + { + return true; + } + + if ((m_relatedKnob->model()->value() - v > 0) != + (m_relatedKnob->model()->value() - model()->value() >= 0)) + return false; + + const double d1 = qAbs(m_relatedKnob->model()->value() - model()->value()) + * (m_waveView->m_sample->sampleSize()) + / m_waveView->m_sample->sampleRate(); + + const double d2 = qAbs(m_relatedKnob->model()->value() - v) + * (m_waveView->m_sample->sampleSize()) + / m_waveView->m_sample->sampleRate(); + + return d1 < d2 || d2 > 0.005; +} + +} // namespace gui + +} // namespace lmms diff --git a/plugins/AudioFileProcessor/AudioFileProcessorWaveView.h b/plugins/AudioFileProcessor/AudioFileProcessorWaveView.h new file mode 100644 index 000000000..f40b69d12 --- /dev/null +++ b/plugins/AudioFileProcessor/AudioFileProcessorWaveView.h @@ -0,0 +1,186 @@ +/* + * AudioFileProcessorWaveView.h - Wave renderer of the AFP + * + * Copyright (c) 2004-2014 Tobias Doerffel + * + * This file is part of LMMS - https://lmms.io + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program (see COPYING); if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA. + * + */ + +#ifndef LMMS_AUDIO_FILE_PROCESSOR_WAVE_VIEW_H +#define LMMS_AUDIO_FILE_PROCESSOR_WAVE_VIEW_H + + +#include "Knob.h" + + +namespace lmms +{ + +class Sample; + +namespace gui +{ + +class AudioFileProcessorView; + +class AudioFileProcessorWaveView : public QWidget +{ + Q_OBJECT +protected: + virtual void enterEvent(QEvent* e); + virtual void leaveEvent(QEvent* e); + virtual void mousePressEvent(QMouseEvent* me); + virtual void mouseReleaseEvent(QMouseEvent* me); + virtual void mouseMoveEvent(QMouseEvent* me); + virtual void wheelEvent(QWheelEvent* we); + virtual void paintEvent(QPaintEvent* pe); + + +public: + enum class Point + { + Start, + End, + Loop + } ; + + class knob : public Knob + { + const AudioFileProcessorWaveView* m_waveView; + const Knob* m_relatedKnob; + + + public: + knob(QWidget* parent) : + Knob(KnobType::Bright26, parent), + m_waveView(0), + m_relatedKnob(0) + { + setFixedSize(37, 47); + } + + void setWaveView(const AudioFileProcessorWaveView* wv) + { + m_waveView = wv; + } + + void setRelatedKnob(const Knob* knob) + { + m_relatedKnob = knob; + } + + void slideBy(double v, bool check_bound = true) + { + slideTo(model()->value() + v, check_bound); + } + + void slideTo(double v, bool check_bound = true); + + + protected: + float getValue(const QPoint & p); + + + private: + bool checkBound(double v) const; + } ; + + +public slots: + void update() + { + updateGraph(); + QWidget::update(); + } + + void isPlaying(lmms::f_cnt_t current_frame); + + +private: + static const int s_padding = 2; + + enum class DraggingType + { + Wave, + SampleStart, + SampleEnd, + SampleLoop + } ; + + Sample const* m_sample; + QPixmap m_graph; + f_cnt_t m_from; + f_cnt_t m_to; + f_cnt_t m_last_from; + f_cnt_t m_last_to; + float m_last_amp; + knob* m_startKnob; + knob* m_endKnob; + knob* m_loopKnob; + f_cnt_t m_startFrameX; + f_cnt_t m_endFrameX; + f_cnt_t m_loopFrameX; + bool m_isDragging; + QPoint m_draggingLastPoint; + DraggingType m_draggingType; + bool m_reversed; + f_cnt_t m_framesPlayed; + bool m_animation; + + friend class AudioFileProcessorView; + +public: + AudioFileProcessorWaveView(QWidget* parent, int w, int h, Sample const* buf, + knob* start, knob* end, knob* loop); + + + void updateSampleRange(); +private: + void setTo(f_cnt_t to); + void setFrom(f_cnt_t from); + f_cnt_t range() const; + void zoom(const bool out = false); + void slide(int px); + void slideSamplePointByPx(Point point, int px); + void slideSamplePointByFrames(Point point, f_cnt_t frames, bool slide_to = false); + void slideSampleByFrames(f_cnt_t frames); + + void slideSamplePointToFrames(Point point, f_cnt_t frames) + { + slideSamplePointByFrames(point, frames, true); + } + + void updateGraph(); + void reverse(); + void updateCursor(QMouseEvent* me = nullptr); + + void configureKnobRelationsAndWaveViews(); + + static bool isCloseTo(int a, int b) + { + return qAbs(a - b) < 4; + } + +} ; + +} // namespace gui + +} // namespace lmms + +#endif // LMMS_AUDIO_FILE_PROCESSOR_WAVE_VIEW_H diff --git a/plugins/AudioFileProcessor/CMakeLists.txt b/plugins/AudioFileProcessor/CMakeLists.txt index 055fad791..a67532112 100644 --- a/plugins/AudioFileProcessor/CMakeLists.txt +++ b/plugins/AudioFileProcessor/CMakeLists.txt @@ -1,3 +1,3 @@ INCLUDE(BuildPlugin) -BUILD_PLUGIN(audiofileprocessor AudioFileProcessor.cpp AudioFileProcessor.h MOCFILES AudioFileProcessor.h EMBEDDED_RESOURCES *.png) +BUILD_PLUGIN(audiofileprocessor AudioFileProcessor.cpp AudioFileProcessor.h AudioFileProcessorView.cpp AudioFileProcessorView.h AudioFileProcessorWaveView.cpp AudioFileProcessorWaveView.h MOCFILES AudioFileProcessor.h AudioFileProcessorView.h AudioFileProcessorWaveView.h EMBEDDED_RESOURCES *.png) diff --git a/plugins/BassBooster/BassBooster.cpp b/plugins/BassBooster/BassBooster.cpp index e6b25b0d1..aa0d3ebcd 100644 --- a/plugins/BassBooster/BassBooster.cpp +++ b/plugins/BassBooster/BassBooster.cpp @@ -118,7 +118,7 @@ bool BassBoosterEffect::processAudioBuffer( sampleFrame* buf, const fpp_t frames inline void BassBoosterEffect::changeFrequency() { - const sample_t fac = Engine::audioEngine()->processingSampleRate() / 44100.0f; + const sample_t fac = Engine::audioEngine()->outputSampleRate() / 44100.0f; m_bbFX.leftFX().setFrequency( m_bbControls.m_freqModel.value() * fac ); m_bbFX.rightFX().setFrequency( m_bbControls.m_freqModel.value() * fac ); diff --git a/plugins/BitInvader/BitInvader.cpp b/plugins/BitInvader/BitInvader.cpp index 4ea73dc71..21940182b 100644 --- a/plugins/BitInvader/BitInvader.cpp +++ b/plugins/BitInvader/BitInvader.cpp @@ -110,34 +110,18 @@ sample_t BSynth::nextStringSample( float sample_length ) sample_realindex -= sample_length; } - sample_t sample; - - if (interpolation) { - - // find position in shape - int a = static_cast(sample_realindex); - int b; - if (a < (sample_length-1)) { - b = static_cast(sample_realindex+1); - } else { - b = 0; - } - - // Nachkommaanteil - const float frac = fraction( sample_realindex ); - - sample = linearInterpolate( sample_shape[a], sample_shape[b], frac ); - - } else { - // No interpolation - sample_index = static_cast(sample_realindex); - sample = sample_shape[sample_index]; - } - - // progress in shape + const auto currentRealIndex = sample_realindex; + const auto currentIndex = static_cast(sample_realindex); sample_realindex += sample_step; - return sample; + if (!interpolation) + { + sample_index = currentIndex; + return sample_shape[sample_index]; + } + + const auto nextIndex = currentIndex < sample_length - 1 ? currentIndex + 1 : 0; + return linearInterpolate(sample_shape[currentIndex], sample_shape[nextIndex], fraction(currentRealIndex)); } /*********************************************************************** @@ -153,8 +137,8 @@ BitInvader::BitInvader( InstrumentTrack * _instrument_track ) : Instrument( _instrument_track, &bitinvader_plugin_descriptor ), m_sampleLength(wavetableSize, 4, wavetableSize, 1, this, tr("Sample length")), m_graph(-1.0f, 1.0f, wavetableSize, this), - m_interpolation( false, this ), - m_normalize( false, this ) + m_interpolation(false, this, tr("Interpolation")), + m_normalize(false, this, tr("Normalize")) { m_graph.setWaveToSine(); lengthChanged(); @@ -276,21 +260,12 @@ void BitInvader::playNote( NotePlayHandle * _n, { if (!_n->m_pluginData) { - float factor; - if( !m_normalize.value() ) - { - factor = defaultNormalizationFactor; - } - else - { - factor = m_normalizeFactor; - } - + float factor = !m_normalize.value() ? defaultNormalizationFactor : m_normalizeFactor; _n->m_pluginData = new BSynth( const_cast( m_graph.samples() ), _n, m_interpolation.value(), factor, - Engine::audioEngine()->processingSampleRate() ); + Engine::audioEngine()->outputSampleRate() ); } const fpp_t frames = _n->framesLeftForCurrentPeriod(); diff --git a/plugins/BitInvader/BitInvader.h b/plugins/BitInvader/BitInvader.h index a08640e99..f4d248ec8 100644 --- a/plugins/BitInvader/BitInvader.h +++ b/plugins/BitInvader/BitInvader.h @@ -31,7 +31,6 @@ #include "Instrument.h" #include "InstrumentView.h" #include "Graph.h" -#include "MemoryManager.h" namespace lmms { @@ -48,7 +47,6 @@ class PixmapButton; class BSynth { - MM_OPERATORS public: BSynth( float * sample, NotePlayHandle * _nph, bool _interpolation, float factor, @@ -87,9 +85,9 @@ public: QString nodeName() const override; - f_cnt_t desiredReleaseFrames() const override + float desiredReleaseTimeMs() const override { - return( 64 ); + return 1.5f; } gui::PluginView * instantiateView( QWidget * _parent ) override; diff --git a/plugins/Bitcrush/Bitcrush.cpp b/plugins/Bitcrush/Bitcrush.cpp index 8d29186b5..251750a32 100644 --- a/plugins/Bitcrush/Bitcrush.cpp +++ b/plugins/Bitcrush/Bitcrush.cpp @@ -59,10 +59,10 @@ Plugin::Descriptor PLUGIN_EXPORT bitcrush_plugin_descriptor = BitcrushEffect::BitcrushEffect( Model * parent, const Descriptor::SubPluginFeatures::Key * key ) : Effect( &bitcrush_plugin_descriptor, parent, key ), m_controls( this ), - m_sampleRate( Engine::audioEngine()->processingSampleRate() ), + m_sampleRate( Engine::audioEngine()->outputSampleRate() ), m_filter( m_sampleRate ) { - m_buffer = MM_ALLOC( Engine::audioEngine()->framesPerPeriod() * OS_RATE ); + m_buffer = new sampleFrame[Engine::audioEngine()->framesPerPeriod() * OS_RATE]; m_filter.setLowpass( m_sampleRate * ( CUTOFF_RATIO * OS_RATIO ) ); m_needsUpdate = true; @@ -77,13 +77,13 @@ BitcrushEffect::BitcrushEffect( Model * parent, const Descriptor::SubPluginFeatu BitcrushEffect::~BitcrushEffect() { - MM_FREE( m_buffer ); + delete[] m_buffer; } void BitcrushEffect::sampleRateChanged() { - m_sampleRate = Engine::audioEngine()->processingSampleRate(); + m_sampleRate = Engine::audioEngine()->outputSampleRate(); m_filter.setSampleRate( m_sampleRate ); m_filter.setLowpass( m_sampleRate * ( CUTOFF_RATIO * OS_RATIO ) ); m_needsUpdate = true; diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 9a71be4b8..04862cac1 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/CarlaBase/Carla.cpp b/plugins/CarlaBase/Carla.cpp index 819736e92..197971ee9 100644 --- a/plugins/CarlaBase/Carla.cpp +++ b/plugins/CarlaBase/Carla.cpp @@ -150,7 +150,7 @@ static const char* host_ui_save_file(NativeHostHandle, bool isDir, const char* t CarlaInstrument::CarlaInstrument(InstrumentTrack* const instrumentTrack, const Descriptor* const descriptor, const bool isPatchbay) - : Instrument(instrumentTrack, descriptor), + : Instrument(instrumentTrack, descriptor, nullptr, Flag::IsSingleStreamed | Flag::IsMidiBased | Flag::IsNotBendable), kIsPatchbay(isPatchbay), fHandle(nullptr), fDescriptor(isPatchbay ? carla_get_native_patchbay_plugin() : carla_get_native_rack_plugin()), @@ -259,7 +259,7 @@ uint32_t CarlaInstrument::handleGetBufferSize() const double CarlaInstrument::handleGetSampleRate() const { - return Engine::audioEngine()->processingSampleRate(); + return Engine::audioEngine()->outputSampleRate(); } bool CarlaInstrument::handleIsOffline() const @@ -343,11 +343,6 @@ intptr_t CarlaInstrument::handleDispatcher(const NativeHostDispatcherOpcode opco // ------------------------------------------------------------------- -Instrument::Flags CarlaInstrument::flags() const -{ - return Flag::IsSingleStreamed | Flag::IsMidiBased | Flag::IsNotBendable; -} - QString CarlaInstrument::nodeName() const { return descriptor()->name; @@ -632,7 +627,7 @@ CarlaInstrumentView::CarlaInstrumentView(CarlaInstrument* const instrument, QWid m_toggleUIButton->setCheckable( true ); m_toggleUIButton->setChecked( false ); m_toggleUIButton->setIcon( embed::getIconPixmap( "zoom" ) ); - m_toggleUIButton->setFont( pointSize<8>( m_toggleUIButton->font() ) ); + m_toggleUIButton->setFont(adjustedToPixelSize(m_toggleUIButton->font(), 8)); connect( m_toggleUIButton, SIGNAL( clicked(bool) ), this, SLOT( toggleUI( bool ) ) ); m_toggleUIButton->setToolTip( @@ -642,7 +637,7 @@ CarlaInstrumentView::CarlaInstrumentView(CarlaInstrument* const instrument, QWid m_toggleParamsWindowButton = new QPushButton(tr("Params"), this); m_toggleParamsWindowButton->setIcon(embed::getIconPixmap("controller")); m_toggleParamsWindowButton->setCheckable(true); - m_toggleParamsWindowButton->setFont(pointSize<8>(m_toggleParamsWindowButton->font())); + m_toggleParamsWindowButton->setFont(adjustedToPixelSize(m_toggleParamsWindowButton->font(), 8)); #if CARLA_VERSION_HEX < CARLA_MIN_PARAM_VERSION m_toggleParamsWindowButton->setEnabled(false); m_toggleParamsWindowButton->setToolTip(tr("Available from Carla version 2.1 and up.")); @@ -1116,16 +1111,15 @@ void CarlaParamsView::clearKnobs() } // Remove spacers - QLayoutItem* item; for (int16_t i=m_inputScrollAreaLayout->count() - 1; i > 0; i--) { - item = m_inputScrollAreaLayout->takeAt(i); + auto item = m_inputScrollAreaLayout->takeAt(i); if (item->widget()) {continue;} delete item; } for (int16_t i=m_outputScrollAreaLayout->count() - 1; i > 0; i--) { - item = m_outputScrollAreaLayout->takeAt(i); + auto item = m_outputScrollAreaLayout->takeAt(i); if (item->widget()) {continue;} delete item; } diff --git a/plugins/CarlaBase/Carla.h b/plugins/CarlaBase/Carla.h index e04444f91..76a9b45b7 100644 --- a/plugins/CarlaBase/Carla.h +++ b/plugins/CarlaBase/Carla.h @@ -33,6 +33,7 @@ #include #include #include +#include // carla/source/includes #include "carlabase_export.h" @@ -89,8 +90,8 @@ public: // From AutomatableModel.h, it's private there. inline static bool mustQuoteName(const QString &name) { - QRegExp reg("^[A-Za-z0-9._-]+$"); - return !reg.exactMatch(name); + QRegularExpression reg("^[A-Za-z0-9._-]+$"); + return !reg.match(name).hasMatch(); } inline void loadSettings(const QDomElement& element, const QString& name = QString("value")) override @@ -189,7 +190,6 @@ public: intptr_t handleDispatcher(const NativeHostDispatcherOpcode opcode, const int32_t index, const intptr_t value, void* const ptr, const float opt); // LMMS functions - Flags flags() const override; QString nodeName() const override; void saveSettings(QDomDocument& doc, QDomElement& parent) override; void loadSettings(const QDomElement& elem) override; diff --git a/plugins/CarlaBase/carla b/plugins/CarlaBase/carla index 4ac8ff2ef..66afe24a0 160000 --- a/plugins/CarlaBase/carla +++ b/plugins/CarlaBase/carla @@ -1 +1 @@ -Subproject commit 4ac8ff2ef412d4ab190d2e285e318b1f339af4ae +Subproject commit 66afe24a08790732cc17d81d4b846a1e0cfa0118 diff --git a/plugins/Compressor/Compressor.cpp b/plugins/Compressor/Compressor.cpp index 0fe139420..91cc61029 100755 --- a/plugins/Compressor/Compressor.cpp +++ b/plugins/Compressor/Compressor.cpp @@ -56,13 +56,10 @@ CompressorEffect::CompressorEffect(Model* parent, const Descriptor::SubPluginFea Effect(&compressor_plugin_descriptor, parent, key), m_compressorControls(this) { - m_sampleRate = Engine::audioEngine()->processingSampleRate(); + m_sampleRate = Engine::audioEngine()->outputSampleRate(); m_yL[0] = m_yL[1] = COMP_NOISE_FLOOR; - m_maxLookaheadVal[0] = 0; - m_maxLookaheadVal[1] = 0; - // 200 ms m_crestTimeConst = exp(-1.f / (0.2f * m_sampleRate)); @@ -183,9 +180,7 @@ void CompressorEffect::resizeRMS() void CompressorEffect::calcLookaheadLength() { - m_lookaheadLength = qMax(m_compressorControls.m_lookaheadLengthModel.value() * 0.001f * m_sampleRate, 1.f); - - m_preLookaheadLength = ceil(m_lookaheadDelayLength - m_lookaheadLength); + m_lookaheadLength = std::ceil((m_compressorControls.m_lookaheadLengthModel.value() / 1000.f) * m_sampleRate); } void CompressorEffect::calcThreshold() @@ -249,17 +244,10 @@ bool CompressorEffect::processAudioBuffer(sampleFrame* buf, const fpp_t frames) m_gainResult[0] = m_gainResult[1] = 1; m_displayPeak[0] = m_displayPeak[1] = COMP_NOISE_FLOOR; m_displayGain[0] = m_displayGain[1] = COMP_NOISE_FLOOR; - std::fill(std::begin(m_lookaheadBuf[0]), std::end(m_lookaheadBuf[0]), 0); - std::fill(std::begin(m_lookaheadBuf[1]), std::end(m_lookaheadBuf[1]), 0); - m_lookaheadBufLoc[0] = 0; - m_lookaheadBufLoc[1] = 0; - std::fill(std::begin(m_preLookaheadBuf[0]), std::end(m_preLookaheadBuf[0]), 0); - std::fill(std::begin(m_preLookaheadBuf[1]), std::end(m_preLookaheadBuf[1]), 0); - m_preLookaheadBufLoc[0] = 0; - m_preLookaheadBufLoc[1] = 0; - std::fill(std::begin(m_inputBuf[0]), std::end(m_inputBuf[0]), 0); - std::fill(std::begin(m_inputBuf[1]), std::end(m_inputBuf[1]), 0); - m_inputBufLoc = 0; + std::fill(std::begin(m_scLookBuf[0]), std::end(m_scLookBuf[0]), COMP_NOISE_FLOOR); + std::fill(std::begin(m_scLookBuf[1]), std::end(m_scLookBuf[1]), COMP_NOISE_FLOOR); + std::fill(std::begin(m_inLookBuf[0]), std::end(m_inLookBuf[0]), 0); + std::fill(std::begin(m_inLookBuf[1]), std::end(m_inLookBuf[1]), 0); m_cleanedBuffers = true; } return false; @@ -318,7 +306,7 @@ bool CompressorEffect::processAudioBuffer(sampleFrame* buf, const fpp_t frames) for (int i = 0; i < 2; i++) { - float inputValue = feedback ? m_prevOut[i] : s[i]; + float inputValue = (feedback && !lookahead) ? m_prevOut[i] : s[i]; // Calculate the crest factor of the audio by diving the peak by the RMS m_crestPeakVal[i] = qMax(qMax(COMP_NOISE_FLOOR, inputValue * inputValue), m_crestTimeConst * m_crestPeakVal[i] + (1 - m_crestTimeConst) * (inputValue * inputValue)); @@ -330,53 +318,6 @@ bool CompressorEffect::processAudioBuffer(sampleFrame* buf, const fpp_t frames) // Grab the peak or RMS value inputValue = qMax(COMP_NOISE_FLOOR, peakmode ? std::abs(inputValue) : std::sqrt(m_rmsVal[i])); - // The following code uses math magic to semi-efficiently - // find the largest value in the lookahead buffer. - // This can probably be improved. - if (lookahead) - { - // Pre-lookahead delay, so the total delay always matches 20 ms - ++m_preLookaheadBufLoc[i]; - if (m_preLookaheadBufLoc[i] >= m_preLookaheadLength) - { - m_preLookaheadBufLoc[i] = 0; - } - const float tempInputValue = inputValue; - inputValue = m_preLookaheadBuf[i][m_preLookaheadBufLoc[i]]; - m_preLookaheadBuf[i][m_preLookaheadBufLoc[i]] = tempInputValue; - - - // Increment ring buffer location - ++m_lookaheadBufLoc[i]; - if (m_lookaheadBufLoc[i] >= m_lookaheadLength) - { - m_lookaheadBufLoc[i] = 0; - } - - m_lookaheadBuf[i][m_lookaheadBufLoc[i]] = inputValue; - - // If the new input value is larger than the stored maximum, - // store that as the maximum - if (inputValue >= m_maxLookaheadVal[i]) - { - m_maxLookaheadVal[i] = inputValue; - m_maxLookaheadTimer[i] = m_lookaheadLength; - } - - // Decrement timer. When the timer reaches 0, that means the - // stored maximum value has left the buffer and a new - // maximum value must be found. - if (--m_maxLookaheadTimer[i] <= 0) - { - m_maxLookaheadTimer[i] = std::distance(std::begin(m_lookaheadBuf[i]), - std::max_element(std::begin(m_lookaheadBuf[i]), std::begin(m_lookaheadBuf[i]) + m_lookaheadLength)); - m_maxLookaheadVal[i] = m_lookaheadBuf[i][m_maxLookaheadTimer[i]]; - m_maxLookaheadTimer[i] = realmod(m_maxLookaheadTimer[i] - m_lookaheadBufLoc[i], m_lookaheadLength); - } - - inputValue = m_maxLookaheadVal[i]; - } - float t = inputValue; if (t > m_yL[i])// Attack phase @@ -415,11 +356,22 @@ bool CompressorEffect::processAudioBuffer(sampleFrame* buf, const fpp_t frames) // Keep it above the noise floor m_yL[i] = qMax(COMP_NOISE_FLOOR, m_yL[i]); + + float scVal = m_yL[i]; + + if (lookahead) + { + const float temp = scVal; + // Lookahead is calculated by picking the largest value between + // the current sidechain signal and the delayed sidechain signal. + scVal = std::max(m_scLookBuf[i][m_lookWrite], m_scLookBuf[i][(m_lookWrite + m_lookBufLength - m_lookaheadLength) % m_lookBufLength]); + m_scLookBuf[i][m_lookWrite] = temp; + } // For the visualizer - m_displayPeak[i] = qMax(m_yL[i], m_displayPeak[i]); + m_displayPeak[i] = qMax(scVal, m_displayPeak[i]); - const float currentPeakDbfs = ampToDbfs(m_yL[i]); + const float currentPeakDbfs = ampToDbfs(scVal); // Now find the gain change that should be applied, // depending on the measured input value. @@ -439,7 +391,7 @@ bool CompressorEffect::processAudioBuffer(sampleFrame* buf, const fpp_t frames) : m_thresholdVal + (currentPeakDbfs - m_thresholdVal) * m_ratioVal; } - m_gainResult[i] = dbfsToAmp(m_gainResult[i]) / m_yL[i]; + m_gainResult[i] = dbfsToAmp(m_gainResult[i]) / scVal; m_gainResult[i] = qMax(m_rangeVal, m_gainResult[i]); } @@ -507,18 +459,10 @@ bool CompressorEffect::processAudioBuffer(sampleFrame* buf, const fpp_t frames) // Delay the signal by 20 ms via ring buffer if lookahead is enabled if (lookahead) { - ++m_inputBufLoc; - if (m_inputBufLoc >= m_lookaheadDelayLength) - { - m_inputBufLoc = 0; - } - - const auto temp = std::array{drySignal[0], drySignal[1]}; - s[0] = m_inputBuf[0][m_inputBufLoc]; - s[1] = m_inputBuf[1][m_inputBufLoc]; - - m_inputBuf[0][m_inputBufLoc] = temp[0]; - m_inputBuf[1][m_inputBufLoc] = temp[1]; + s[0] = m_inLookBuf[0][m_lookWrite]; + s[1] = m_inLookBuf[1][m_lookWrite]; + m_inLookBuf[0][m_lookWrite] = drySignal[0]; + m_inLookBuf[1][m_lookWrite] = drySignal[1]; } else { @@ -573,6 +517,8 @@ bool CompressorEffect::processAudioBuffer(sampleFrame* buf, const fpp_t frames) buf[f][1] = (1 - m_mixVal) * temp2 + m_mixVal * buf[f][1]; outSum += buf[f][0] * buf[f][0] + buf[f][1] * buf[f][1]; + + if (--m_lookWrite < 0) { m_lookWrite = m_lookBufLength - 1; } lInPeak = drySignal[0] > lInPeak ? drySignal[0] : lInPeak; rInPeak = drySignal[1] > rInPeak ? drySignal[1] : rInPeak; @@ -614,23 +560,20 @@ inline void CompressorEffect::calcTiltFilter(sample_t inputSample, sample_t &out void CompressorEffect::changeSampleRate() { - m_sampleRate = Engine::audioEngine()->processingSampleRate(); + m_sampleRate = Engine::audioEngine()->outputSampleRate(); m_coeffPrecalc = COMP_LOG / (m_sampleRate * 0.001f); // 200 ms m_crestTimeConst = exp(-1.f / (0.2f * m_sampleRate)); - // 20 ms - m_lookaheadDelayLength = 0.02 * m_sampleRate; - m_inputBuf[0].resize(m_lookaheadDelayLength); - m_inputBuf[1].resize(m_lookaheadDelayLength); - - m_lookaheadBuf[0].resize(m_lookaheadDelayLength); - m_lookaheadBuf[1].resize(m_lookaheadDelayLength); - - m_preLookaheadBuf[0].resize(m_lookaheadDelayLength); - m_preLookaheadBuf[1].resize(m_lookaheadDelayLength); + m_lookBufLength = std::ceil((20.f / 1000.f) * m_sampleRate) + 2; + for (int i = 0; i < 2; ++i) + { + m_inLookBuf[i].resize(m_lookBufLength); + m_scLookBuf[i].resize(m_lookBufLength, COMP_NOISE_FLOOR); + } + m_lookWrite = 0; calcThreshold(); calcKnee(); diff --git a/plugins/Compressor/Compressor.h b/plugins/Compressor/Compressor.h index da6ab52bc..3fc90b752 100755 --- a/plugins/Compressor/Compressor.h +++ b/plugins/Compressor/Compressor.h @@ -81,14 +81,10 @@ private: enum class StereoLinkMode { Unlinked, Maximum, Average, Minimum, Blend }; - std::vector m_preLookaheadBuf[2]; - int m_preLookaheadBufLoc[2] = {0}; - - std::vector m_lookaheadBuf[2]; - int m_lookaheadBufLoc[2] = {0}; - - std::vector m_inputBuf[2]; - int m_inputBufLoc = 0; + std::array, 2> m_inLookBuf; + std::array, 2> m_scLookBuf; + int m_lookWrite; + int m_lookBufLength; float m_attCoeff; float m_relCoeff; @@ -99,8 +95,6 @@ private: int m_holdTimer[2] = {0, 0}; int m_lookaheadLength; - int m_lookaheadDelayLength; - int m_preLookaheadLength; float m_thresholdAmpVal; float m_autoMakeupVal; float m_outGainVal; diff --git a/plugins/Compressor/CompressorControlDialog.cpp b/plugins/Compressor/CompressorControlDialog.cpp index 1516456a2..d7350ba59 100755 --- a/plugins/Compressor/CompressorControlDialog.cpp +++ b/plugins/Compressor/CompressorControlDialog.cpp @@ -45,23 +45,12 @@ namespace lmms::gui CompressorControlDialog::CompressorControlDialog(CompressorControls* controls) : EffectControlDialog(controls), - m_controls(controls), - m_inVolAreaColor(209, 216, 228, 17), - m_inVolColor(209, 216, 228, 100), - m_outVolAreaColor(209, 216, 228, 30), - m_outVolColor(209, 216, 228, 240), - m_gainReductionColor(180, 100, 100, 210), - m_kneeColor(39, 171, 95, 255), - m_kneeColor2(9, 171, 160, 255), - m_threshColor(39, 171, 95, 100), - m_textColor(209, 216, 228, 50), - m_graphColor(209, 216, 228, 50), - m_resetColor(200, 100, 15, 200) + m_controls(controls) { - setAutoFillBackground(true); - QPalette pal; - pal.setBrush(backgroundRole(), PLUGIN_NAME::getIconPixmap("artwork")); - setPalette(pal); + setAutoFillBackground(false); + setAttribute(Qt::WA_OpaquePaintEvent, true); + setAttribute(Qt::WA_NoSystemBackground, true); + setMinimumSize(MIN_COMP_SCREEN_X, MIN_COMP_SCREEN_Y); resize(COMP_SCREEN_X, COMP_SCREEN_Y); @@ -358,6 +347,7 @@ void CompressorControlDialog::lookaheadChanged() { m_lookaheadLengthKnob->setVisible(m_controls->m_lookaheadModel.value()); m_lookaheadEnabledLabel->setVisible(m_controls->m_lookaheadModel.value()); + feedbackButton->setVisible(!m_controls->m_lookaheadModel.value()); } @@ -611,7 +601,7 @@ void CompressorControlDialog::paintEvent(QPaintEvent *event) m_p.begin(this); m_p.setCompositionMode(QPainter::CompositionMode_Source); - m_p.fillRect(0, 0, m_windowSizeX, m_windowSizeY, QColor("transparent")); + m_p.fillRect(0, 0, m_windowSizeX, m_windowSizeY, m_backgroundColor); m_p.setCompositionMode(QPainter::CompositionMode_SourceOver); m_p.drawPixmap(0, 0, m_graphPixmap); @@ -679,7 +669,7 @@ void CompressorControlDialog::drawGraph() // Redraw graph m_p.setPen(QPen(m_graphColor, 1)); - for (int i = 1; i < m_dbRange / COMP_GRID_SPACING + 1; ++i) + for (int i = 0; i < m_dbRange / COMP_GRID_SPACING + 1; ++i) { m_p.drawLine(0, dbfsToYPoint(-COMP_GRID_SPACING * i), m_windowSizeX, dbfsToYPoint(-COMP_GRID_SPACING * i)); m_p.drawLine(dbfsToXPoint(-COMP_GRID_SPACING * i), 0, dbfsToXPoint(-COMP_GRID_SPACING * i), m_kneeWindowSizeY); @@ -690,6 +680,79 @@ void CompressorControlDialog::drawGraph() } +void CompressorControlDialog::mouseDoubleClickEvent(QMouseEvent* event) +{ + setGuiVisibility(!m_guiVisibility); +} + + +void CompressorControlDialog::setGuiVisibility(bool isVisible) +{ + if (!isVisible) + { + m_rmsKnob->setVisible(isVisible); + m_rmsEnabledLabel->setVisible(isVisible); + + m_lookaheadLengthKnob->setVisible(isVisible); + m_lookaheadEnabledLabel->setVisible(isVisible); + + m_blendKnob->setVisible(isVisible); + m_blendEnabledLabel->setVisible(isVisible); + + m_ratioKnob->setVisible(isVisible); + m_ratioEnabledLabel->setVisible(isVisible); + } + else + { + m_rmsKnob->setVisible(!m_controls->m_peakmodeModel.value()); + m_rmsEnabledLabel->setVisible(!m_controls->m_peakmodeModel.value()); + + m_blendKnob->setVisible(m_controls->m_stereoLinkModel.value() == 4); + m_blendEnabledLabel->setVisible(m_controls->m_stereoLinkModel.value() == 4); + + m_lookaheadLengthKnob->setVisible(m_controls->m_lookaheadModel.value()); + m_lookaheadEnabledLabel->setVisible(m_controls->m_lookaheadModel.value()); + + m_ratioKnob->setVisible(!m_controls->m_limiterModel.value()); + m_ratioEnabledLabel->setVisible(!m_controls->m_limiterModel.value()); + } + m_controlsBoxLabel->setVisible(isVisible); + m_thresholdKnob->setVisible(isVisible); + m_attackKnob->setVisible(isVisible); + m_releaseKnob->setVisible(isVisible); + m_kneeKnob->setVisible(isVisible); + m_rangeKnob->setVisible(isVisible); + m_holdKnob->setVisible(isVisible); + m_inBalanceKnob->setVisible(isVisible); + m_outBalanceKnob->setVisible(isVisible); + m_stereoBalanceKnob->setVisible(isVisible); + m_tiltKnob->setVisible(isVisible); + m_tiltFreqKnob->setVisible(isVisible); + m_mixKnob->setVisible(isVisible); + m_autoAttackKnob->setVisible(isVisible); + m_autoReleaseKnob->setVisible(isVisible); + m_outFader->setVisible(isVisible); + m_inFader->setVisible(isVisible); + rmsButton->setVisible(isVisible); + peakButton->setVisible(isVisible); + rmsPeakGroup->setVisible(isVisible); + leftRightButton->setVisible(isVisible); + midSideButton->setVisible(isVisible); + compressButton->setVisible(isVisible); + limitButton->setVisible(isVisible); + unlinkedButton->setVisible(isVisible); + maximumButton->setVisible(isVisible); + averageButton->setVisible(isVisible); + minimumButton->setVisible(isVisible); + blendButton->setVisible(isVisible); + autoMakeupButton->setVisible(isVisible); + auditionButton->setVisible(isVisible); + feedbackButton->setVisible(isVisible); + lookaheadButton->setVisible(isVisible); + m_guiVisibility = isVisible; +} + + void CompressorControlDialog::resetCompressorView() { m_windowSizeX = size().width(); diff --git a/plugins/Compressor/CompressorControlDialog.h b/plugins/Compressor/CompressorControlDialog.h index 1324d7e26..cedba4b04 100755 --- a/plugins/Compressor/CompressorControlDialog.h +++ b/plugins/Compressor/CompressorControlDialog.h @@ -86,6 +86,7 @@ public: Q_PROPERTY(QColor textColor MEMBER m_textColor) Q_PROPERTY(QColor graphColor MEMBER m_graphColor) Q_PROPERTY(QColor resetColor MEMBER m_resetColor) + Q_PROPERTY(QColor backgroundColor MEMBER m_backgroundColor) protected: void resizeEvent(QResizeEvent *event) override; @@ -108,6 +109,8 @@ private: void drawKneePixmap2(); void drawMiscPixmap(); void drawGraph(); + void mouseDoubleClickEvent(QMouseEvent* event) override; + void setGuiVisibility(bool isVisible); QPainter m_p; @@ -148,6 +151,7 @@ private: QColor m_textColor = QColor(209, 216, 228, 50); QColor m_graphColor = QColor(209, 216, 228, 50); QColor m_resetColor = QColor(200, 100, 15, 200); + QColor m_backgroundColor = QColor(7, 8, 9, 255); float m_peakAvg; float m_gainAvg; @@ -214,6 +218,8 @@ private: PixmapButton * feedbackButton; PixmapButton * lookaheadButton; + bool m_guiVisibility = true; + QElapsedTimer m_timeElapsed; int m_timeSinceLastUpdate = 0; diff --git a/plugins/Compressor/artwork.png b/plugins/Compressor/artwork.png deleted file mode 100755 index 8f94e3d49..000000000 Binary files a/plugins/Compressor/artwork.png and /dev/null differ diff --git a/plugins/CrossoverEQ/CrossoverEQ.cpp b/plugins/CrossoverEQ/CrossoverEQ.cpp index c4334677c..6e59627f6 100644 --- a/plugins/CrossoverEQ/CrossoverEQ.cpp +++ b/plugins/CrossoverEQ/CrossoverEQ.cpp @@ -55,7 +55,7 @@ Plugin::Descriptor PLUGIN_EXPORT crossovereq_plugin_descriptor = CrossoverEQEffect::CrossoverEQEffect( Model* parent, const Descriptor::SubPluginFeatures::Key* key ) : Effect( &crossovereq_plugin_descriptor, parent, key ), m_controls( this ), - m_sampleRate( Engine::audioEngine()->processingSampleRate() ), + m_sampleRate( Engine::audioEngine()->outputSampleRate() ), m_lp1( m_sampleRate ), m_lp2( m_sampleRate ), m_lp3( m_sampleRate ), @@ -64,21 +64,21 @@ CrossoverEQEffect::CrossoverEQEffect( Model* parent, const Descriptor::SubPlugin m_hp4( m_sampleRate ), m_needsUpdate( true ) { - m_tmp1 = MM_ALLOC( Engine::audioEngine()->framesPerPeriod() ); - m_tmp2 = MM_ALLOC( Engine::audioEngine()->framesPerPeriod() ); - m_work = MM_ALLOC( Engine::audioEngine()->framesPerPeriod() ); + m_tmp2 = new sampleFrame[Engine::audioEngine()->framesPerPeriod()]; + m_tmp1 = new sampleFrame[Engine::audioEngine()->framesPerPeriod()]; + m_work = new sampleFrame[Engine::audioEngine()->framesPerPeriod()]; } CrossoverEQEffect::~CrossoverEQEffect() { - MM_FREE( m_tmp1 ); - MM_FREE( m_tmp2 ); - MM_FREE( m_work ); + delete[] m_tmp1; + delete[] m_tmp2; + delete[] m_work; } void CrossoverEQEffect::sampleRateChanged() { - m_sampleRate = Engine::audioEngine()->processingSampleRate(); + m_sampleRate = Engine::audioEngine()->outputSampleRate(); m_lp1.setSampleRate( m_sampleRate ); m_lp2.setSampleRate( m_sampleRate ); m_lp3.setSampleRate( m_sampleRate ); diff --git a/plugins/CrossoverEQ/CrossoverEQControlDialog.cpp b/plugins/CrossoverEQ/CrossoverEQControlDialog.cpp index 12b560b23..a4f44f5d3 100644 --- a/plugins/CrossoverEQ/CrossoverEQControlDialog.cpp +++ b/plugins/CrossoverEQ/CrossoverEQControlDialog.cpp @@ -32,6 +32,9 @@ #include "Knob.h" #include "Fader.h" +#include + + namespace lmms::gui { @@ -64,30 +67,32 @@ CrossoverEQControlDialog::CrossoverEQControlDialog( CrossoverEQControls * contro xover34->setLabel( "3/4" ); xover34->setHintText( tr( "Band 3/4 crossover:" ), " Hz" ); - m_fader_bg = QPixmap( PLUGIN_NAME::getIconPixmap( "fader_bg" ) ); - m_fader_empty = QPixmap( PLUGIN_NAME::getIconPixmap( "fader_empty" ) ); - m_fader_knob = QPixmap( PLUGIN_NAME::getIconPixmap( "fader_knob2" ) ); + QPixmap const fader_knob(PLUGIN_NAME::getIconPixmap("fader_knob2")); // faders - auto gain1 = new Fader(&controls->m_gain1, tr("Band 1 gain"), this, &m_fader_bg, &m_fader_empty, &m_fader_knob); + auto gain1 = new Fader(&controls->m_gain1, tr("Band 1 gain"), this, fader_knob); gain1->move( 7, 56 ); gain1->setDisplayConversion( false ); gain1->setHintText( tr( "Band 1 gain:" ), " dBFS" ); + gain1->setRenderUnityLine(false); - auto gain2 = new Fader(&controls->m_gain2, tr("Band 2 gain"), this, &m_fader_bg, &m_fader_empty, &m_fader_knob); + auto gain2 = new Fader(&controls->m_gain2, tr("Band 2 gain"), this, fader_knob); gain2->move( 47, 56 ); gain2->setDisplayConversion( false ); gain2->setHintText( tr( "Band 2 gain:" ), " dBFS" ); + gain2->setRenderUnityLine(false); - auto gain3 = new Fader(&controls->m_gain3, tr("Band 3 gain"), this, &m_fader_bg, &m_fader_empty, &m_fader_knob); + auto gain3 = new Fader(&controls->m_gain3, tr("Band 3 gain"), this, fader_knob); gain3->move( 87, 56 ); gain3->setDisplayConversion( false ); gain3->setHintText( tr( "Band 3 gain:" ), " dBFS" ); + gain3->setRenderUnityLine(false); - auto gain4 = new Fader(&controls->m_gain4, tr("Band 4 gain"), this, &m_fader_bg, &m_fader_empty, &m_fader_knob); + auto gain4 = new Fader(&controls->m_gain4, tr("Band 4 gain"), this, fader_knob); gain4->move( 127, 56 ); gain4->setDisplayConversion( false ); gain4->setHintText( tr( "Band 4 gain:" ), " dBFS" ); + gain4->setRenderUnityLine(false); // leds auto mute1 = new LedCheckBox("", this, tr("Band 1 mute"), LedCheckBox::LedColor::Green); diff --git a/plugins/CrossoverEQ/CrossoverEQControlDialog.h b/plugins/CrossoverEQ/CrossoverEQControlDialog.h index 9ddb5d9bf..0f25600f9 100644 --- a/plugins/CrossoverEQ/CrossoverEQControlDialog.h +++ b/plugins/CrossoverEQ/CrossoverEQControlDialog.h @@ -27,7 +27,6 @@ #ifndef CROSSOVEREQ_CONTROL_DIALOG_H #define CROSSOVEREQ_CONTROL_DIALOG_H -#include #include "EffectControlDialog.h" namespace lmms @@ -46,11 +45,6 @@ class CrossoverEQControlDialog : public EffectControlDialog public: CrossoverEQControlDialog( CrossoverEQControls * controls ); ~CrossoverEQControlDialog() override = default; - -private: - QPixmap m_fader_bg; - QPixmap m_fader_empty; - QPixmap m_fader_knob; }; diff --git a/plugins/CrossoverEQ/fader_bg.png b/plugins/CrossoverEQ/fader_bg.png deleted file mode 100644 index ca4eedafd..000000000 Binary files a/plugins/CrossoverEQ/fader_bg.png and /dev/null differ diff --git a/plugins/CrossoverEQ/fader_empty.png b/plugins/CrossoverEQ/fader_empty.png deleted file mode 100644 index 797a0d3bc..000000000 Binary files a/plugins/CrossoverEQ/fader_empty.png and /dev/null differ diff --git a/plugins/Delay/DelayEffect.cpp b/plugins/Delay/DelayEffect.cpp index 05204f355..71f6fdf9a 100644 --- a/plugins/Delay/DelayEffect.cpp +++ b/plugins/Delay/DelayEffect.cpp @@ -58,8 +58,8 @@ DelayEffect::DelayEffect( Model* parent, const Plugin::Descriptor::SubPluginFeat m_delayControls( this ) { m_delay = 0; - m_delay = new StereoDelay( 20, Engine::audioEngine()->processingSampleRate() ); - m_lfo = new Lfo( Engine::audioEngine()->processingSampleRate() ); + m_delay = new StereoDelay( 20, Engine::audioEngine()->outputSampleRate() ); + m_lfo = new Lfo( Engine::audioEngine()->outputSampleRate() ); m_outGain = 1.0; } @@ -88,7 +88,7 @@ bool DelayEffect::processAudioBuffer( sampleFrame* buf, const fpp_t frames ) return( false ); } double outSum = 0.0; - const float sr = Engine::audioEngine()->processingSampleRate(); + const float sr = Engine::audioEngine()->outputSampleRate(); const float d = dryLevel(); const float w = wetLevel(); auto dryS = std::array{}; @@ -115,7 +115,7 @@ bool DelayEffect::processAudioBuffer( sampleFrame* buf, const fpp_t frames ) { m_outGain = dbfsToAmp( m_delayControls.m_outGainModel.value() ); } - int sampleLength; + for( fpp_t f = 0; f < frames; ++f ) { dryS[0] = buf[f][0]; @@ -123,8 +123,7 @@ bool DelayEffect::processAudioBuffer( sampleFrame* buf, const fpp_t frames ) m_delay->setFeedback( *feedbackPtr ); m_lfo->setFrequency( *lfoTimePtr ); - sampleLength = *lengthPtr * Engine::audioEngine()->processingSampleRate(); - m_currentLength = sampleLength; + m_currentLength = static_cast(*lengthPtr * Engine::audioEngine()->outputSampleRate()); m_delay->setLength( m_currentLength + ( *amplitudePtr * ( float )m_lfo->tick() ) ); m_delay->tick( buf[f] ); @@ -152,8 +151,8 @@ bool DelayEffect::processAudioBuffer( sampleFrame* buf, const fpp_t frames ) void DelayEffect::changeSampleRate() { - m_lfo->setSampleRate( Engine::audioEngine()->processingSampleRate() ); - m_delay->setSampleRate( Engine::audioEngine()->processingSampleRate() ); + m_lfo->setSampleRate( Engine::audioEngine()->outputSampleRate() ); + m_delay->setSampleRate( Engine::audioEngine()->outputSampleRate() ); } diff --git a/plugins/Dispersion/Dispersion.cpp b/plugins/Dispersion/Dispersion.cpp index fb28e1f47..72e4c2103 100644 --- a/plugins/Dispersion/Dispersion.cpp +++ b/plugins/Dispersion/Dispersion.cpp @@ -52,7 +52,7 @@ Plugin::Descriptor PLUGIN_EXPORT dispersion_plugin_descriptor = DispersionEffect::DispersionEffect(Model* parent, const Descriptor::SubPluginFeatures::Key* key) : Effect(&dispersion_plugin_descriptor, parent, key), m_dispersionControls(this), - m_sampleRate(Engine::audioEngine()->processingSampleRate()), + m_sampleRate(Engine::audioEngine()->outputSampleRate()), m_amountVal(0) { } diff --git a/plugins/DualFilter/DualFilter.cpp b/plugins/DualFilter/DualFilter.cpp index 4e66db988..6f91bb251 100644 --- a/plugins/DualFilter/DualFilter.cpp +++ b/plugins/DualFilter/DualFilter.cpp @@ -57,8 +57,8 @@ DualFilterEffect::DualFilterEffect( Model* parent, const Descriptor::SubPluginFe Effect( &dualfilter_plugin_descriptor, parent, key ), m_dfControls( this ) { - m_filter1 = new BasicFilters<2>( Engine::audioEngine()->processingSampleRate() ); - m_filter2 = new BasicFilters<2>( Engine::audioEngine()->processingSampleRate() ); + m_filter1 = new BasicFilters<2>( Engine::audioEngine()->outputSampleRate() ); + m_filter2 = new BasicFilters<2>( Engine::audioEngine()->outputSampleRate() ); // ensure filters get updated m_filter1changed = true; @@ -218,6 +218,11 @@ bool DualFilterEffect::processAudioBuffer( sampleFrame* buf, const fpp_t frames return isRunning(); } +void DualFilterEffect::onEnabledChanged() +{ + m_filter1->clearHistory(); + m_filter2->clearHistory(); +} diff --git a/plugins/DualFilter/DualFilter.h b/plugins/DualFilter/DualFilter.h index c179edbcc..29161039a 100644 --- a/plugins/DualFilter/DualFilter.h +++ b/plugins/DualFilter/DualFilter.h @@ -47,6 +47,8 @@ public: return &m_dfControls; } +protected: + void onEnabledChanged() override; private: DualFilterControls m_dfControls; diff --git a/plugins/DualFilter/DualFilterControlDialog.cpp b/plugins/DualFilter/DualFilterControlDialog.cpp index d316e3372..a674a4a42 100644 --- a/plugins/DualFilter/DualFilterControlDialog.cpp +++ b/plugins/DualFilter/DualFilterControlDialog.cpp @@ -29,7 +29,6 @@ #include "Knob.h" #include "LedCheckBox.h" #include "ComboBox.h" -#include "gui_templates.h" namespace lmms::gui { @@ -76,12 +75,10 @@ DualFilterControlDialog::DualFilterControlDialog( DualFilterControls* controls ) auto m_filter1ComboBox = new ComboBox(this); m_filter1ComboBox->setGeometry( 19, 70, 137, ComboBox::DEFAULT_HEIGHT ); - m_filter1ComboBox->setFont( pointSize<8>( m_filter1ComboBox->font() ) ); m_filter1ComboBox->setModel( &controls->m_filter1Model ); auto m_filter2ComboBox = new ComboBox(this); m_filter2ComboBox->setGeometry( 217, 70, 137, ComboBox::DEFAULT_HEIGHT ); - m_filter2ComboBox->setFont( pointSize<8>( m_filter2ComboBox->font() ) ); m_filter2ComboBox->setModel( &controls->m_filter2Model ); } diff --git a/plugins/DualFilter/DualFilterControls.cpp b/plugins/DualFilter/DualFilterControls.cpp index e862e6ae1..4a4091aa2 100644 --- a/plugins/DualFilter/DualFilterControls.cpp +++ b/plugins/DualFilter/DualFilterControls.cpp @@ -111,8 +111,8 @@ void DualFilterControls::updateFilters() delete m_effect->m_filter1; delete m_effect->m_filter2; - m_effect->m_filter1 = new BasicFilters<2>( Engine::audioEngine()->processingSampleRate() ); - m_effect->m_filter2 = new BasicFilters<2>( Engine::audioEngine()->processingSampleRate() ); + m_effect->m_filter1 = new BasicFilters<2>( Engine::audioEngine()->outputSampleRate() ); + m_effect->m_filter2 = new BasicFilters<2>( Engine::audioEngine()->outputSampleRate() ); // flag filters as needing recalculation diff --git a/plugins/DynamicsProcessor/DynamicsProcessor.cpp b/plugins/DynamicsProcessor/DynamicsProcessor.cpp index 6bdf41eee..a11cc28c6 100644 --- a/plugins/DynamicsProcessor/DynamicsProcessor.cpp +++ b/plugins/DynamicsProcessor/DynamicsProcessor.cpp @@ -56,7 +56,7 @@ Plugin::Descriptor PLUGIN_EXPORT dynamicsprocessor_plugin_descriptor = } const float DYN_NOISE_FLOOR = 0.00001f; // -100dBFS noise floor -const double DNF_LOG = 5.0; +const double DNF_LOG = -1.0; DynProcEffect::DynProcEffect( Model * _parent, const Descriptor::SubPluginFeatures::Key * _key ) : @@ -64,8 +64,8 @@ DynProcEffect::DynProcEffect( Model * _parent, m_dpControls( this ) { m_currentPeak[0] = m_currentPeak[1] = DYN_NOISE_FLOOR; - m_rms[0] = new RmsHelper( 64 * Engine::audioEngine()->processingSampleRate() / 44100 ); - m_rms[1] = new RmsHelper( 64 * Engine::audioEngine()->processingSampleRate() / 44100 ); + m_rms[0] = new RmsHelper( 64 * Engine::audioEngine()->outputSampleRate() / 44100 ); + m_rms[1] = new RmsHelper( 64 * Engine::audioEngine()->outputSampleRate() / 44100 ); calcAttack(); calcRelease(); } @@ -82,12 +82,12 @@ DynProcEffect::~DynProcEffect() inline void DynProcEffect::calcAttack() { - m_attCoeff = std::pow(10.f, ( DNF_LOG / ( m_dpControls.m_attackModel.value() * 0.001 ) ) / Engine::audioEngine()->processingSampleRate() ); + m_attCoeff = std::exp((DNF_LOG / (m_dpControls.m_attackModel.value() * 0.001)) / Engine::audioEngine()->outputSampleRate()); } inline void DynProcEffect::calcRelease() { - m_relCoeff = std::pow(10.f, ( -DNF_LOG / ( m_dpControls.m_releaseModel.value() * 0.001 ) ) / Engine::audioEngine()->processingSampleRate() ); + m_relCoeff = std::exp((DNF_LOG / (m_dpControls.m_releaseModel.value() * 0.001)) / Engine::audioEngine()->outputSampleRate()); } @@ -106,7 +106,6 @@ bool DynProcEffect::processAudioBuffer( sampleFrame * _buf, int i = 0; auto sm_peak = std::array{0.0f, 0.0f}; - float gain; double out_sum = 0.0; const float d = dryLevel(); @@ -123,8 +122,8 @@ bool DynProcEffect::processAudioBuffer( sampleFrame * _buf, if( m_needsUpdate ) { - m_rms[0]->setSize( 64 * Engine::audioEngine()->processingSampleRate() / 44100 ); - m_rms[1]->setSize( 64 * Engine::audioEngine()->processingSampleRate() / 44100 ); + m_rms[0]->setSize( 64 * Engine::audioEngine()->outputSampleRate() / 44100 ); + m_rms[1]->setSize( 64 * Engine::audioEngine()->outputSampleRate() / 44100 ); calcAttack(); calcRelease(); m_needsUpdate = false; @@ -155,15 +154,15 @@ bool DynProcEffect::processAudioBuffer( sampleFrame * _buf, const double t = m_rms[i]->update( s[i] ); if( t > m_currentPeak[i] ) { - m_currentPeak[i] = qMin( m_currentPeak[i] * m_attCoeff, t ); + m_currentPeak[i] = m_currentPeak[i] * m_attCoeff + (1 - m_attCoeff) * t; } else if( t < m_currentPeak[i] ) { - m_currentPeak[i] = qMax( m_currentPeak[i] * m_relCoeff, t ); + m_currentPeak[i] = m_currentPeak[i] * m_relCoeff + (1 - m_relCoeff) * t; } - m_currentPeak[i] = qBound( DYN_NOISE_FLOOR, m_currentPeak[i], 10.0f ); + m_currentPeak[i] = std::max(DYN_NOISE_FLOOR, m_currentPeak[i]); } // account for stereo mode @@ -196,20 +195,10 @@ bool DynProcEffect::processAudioBuffer( sampleFrame * _buf, if( sm_peak[i] > DYN_NOISE_FLOOR ) { - if ( lookup < 1 ) - { - gain = frac * samples[0]; - } - else - if ( lookup < 200 ) - { - gain = linearInterpolate( samples[ lookup - 1 ], - samples[ lookup ], frac ); - } - else - { - gain = samples[199]; - }; + float gain; + if (lookup < 1) { gain = frac * samples[0]; } + else if (lookup < 200) { gain = linearInterpolate(samples[lookup - 1], samples[lookup], frac); } + else { gain = samples[199]; } s[i] *= gain; s[i] /= sm_peak[i]; diff --git a/plugins/Eq/EqControls.h b/plugins/Eq/EqControls.h index 6db82f3e3..80680c7fb 100644 --- a/plugins/Eq/EqControls.h +++ b/plugins/Eq/EqControls.h @@ -66,6 +66,8 @@ public: float m_inPeakR; float m_outPeakL; float m_outPeakR; + + // The following are linear peaks float m_lowShelfPeakL, m_lowShelfPeakR; float m_para1PeakL, m_para1PeakR; float m_para2PeakL, m_para2PeakR; diff --git a/plugins/Eq/EqControlsDialog.cpp b/plugins/Eq/EqControlsDialog.cpp index a26fa0db9..8394569f6 100644 --- a/plugins/Eq/EqControlsDialog.cpp +++ b/plugins/Eq/EqControlsDialog.cpp @@ -72,18 +72,16 @@ EqControlsDialog::EqControlsDialog( EqControls *controls ) : setBand( 6, &controls->m_highShelfActiveModel, &controls->m_highShelfFreqModel, &controls->m_highShelfResModel, &controls->m_highShelfGainModel, QColor(255 ,255, 255), tr( "High-shelf" ), &controls->m_highShelfPeakL, &controls->m_highShelfPeakR,0,0,0,0,0,0 ); setBand( 7, &controls->m_lpActiveModel, &controls->m_lpFreqModel, &controls->m_lpResModel, 0, QColor(255 ,255, 255), tr( "LP" ) ,0,0,0,0,0, &controls->m_lp12Model, &controls->m_lp24Model, &controls->m_lp48Model); - auto faderBg = new QPixmap(PLUGIN_NAME::getIconPixmap("faderback")); - auto faderLeds = new QPixmap(PLUGIN_NAME::getIconPixmap("faderleds")); - auto faderKnob = new QPixmap(PLUGIN_NAME::getIconPixmap("faderknob")); + QSize const faderSize(23, 80); - auto GainFaderIn = new EqFader(&controls->m_inGainModel, tr("Input gain"), this, faderBg, faderLeds, faderKnob, - &controls->m_inPeakL, &controls->m_inPeakR); + auto GainFaderIn = new EqFader(&controls->m_inGainModel, tr("Input gain"), this, &controls->m_inPeakL, &controls->m_inPeakR); + GainFaderIn->setFixedSize(faderSize); GainFaderIn->move( 23, 295 ); GainFaderIn->setDisplayConversion( false ); GainFaderIn->setHintText( tr( "Gain" ), "dBv"); - auto GainFaderOut = new EqFader(&controls->m_outGainModel, tr("Output gain"), this, faderBg, faderLeds, faderKnob, - &controls->m_outPeakL, &controls->m_outPeakR); + auto GainFaderOut = new EqFader(&controls->m_outGainModel, tr("Output gain"), this, &controls->m_outPeakL, &controls->m_outPeakR); + GainFaderOut->setFixedSize(faderSize); GainFaderOut->move( 453, 295); GainFaderOut->setDisplayConversion( false ); GainFaderOut->setHintText( tr( "Gain" ), "dBv" ); @@ -92,8 +90,9 @@ EqControlsDialog::EqControlsDialog( EqControls *controls ) : int distance = 126; for( int i = 1; i < m_parameterWidget->bandCount() - 1; i++ ) { - auto gainFader = new EqFader(m_parameterWidget->getBandModels(i)->gain, tr(""), this, faderBg, faderLeds, - faderKnob, m_parameterWidget->getBandModels(i)->peakL, m_parameterWidget->getBandModels(i)->peakR); + auto gainFader = new EqFader(m_parameterWidget->getBandModels(i)->gain, tr(""), this, + m_parameterWidget->getBandModels(i)->peakL, m_parameterWidget->getBandModels(i)->peakR); + gainFader->setFixedSize(faderSize); gainFader->move( distance, 295 ); distance += 44; gainFader->setMinimumHeight(80); @@ -124,10 +123,10 @@ EqControlsDialog::EqControlsDialog( EqControls *controls ) : activeButton->setCheckable(true); activeButton->setModel( m_parameterWidget->getBandModels( i )->active ); - QString iconActiveFileName = "bandLabel" + QString::number(i+1); - QString iconInactiveFileName = "bandLabel" + QString::number(i+1) + "off"; - activeButton->setActiveGraphic( PLUGIN_NAME::getIconPixmap( iconActiveFileName.toLatin1() ) ); - activeButton->setInactiveGraphic( PLUGIN_NAME::getIconPixmap( iconInactiveFileName.toLatin1() ) ); + const auto iconActiveFileName = "bandLabel" + std::to_string(i + 1); + const auto iconInactiveFileName = iconActiveFileName + "off"; + activeButton->setActiveGraphic(PLUGIN_NAME::getIconPixmap(iconActiveFileName)); + activeButton->setInactiveGraphic(PLUGIN_NAME::getIconPixmap(iconInactiveFileName)); activeButton->move( distance - 2, 276 ); activeButton->setModel( m_parameterWidget->getBandModels( i )->active ); @@ -242,4 +241,4 @@ EqBand* EqControlsDialog::setBand(int index, BoolModel* active, FloatModel* freq } -} // namespace lmms::gui \ No newline at end of file +} // namespace lmms::gui diff --git a/plugins/Eq/EqCurve.cpp b/plugins/Eq/EqCurve.cpp index bb721a7e4..e1d28ca0a 100644 --- a/plugins/Eq/EqCurve.cpp +++ b/plugins/Eq/EqCurve.cpp @@ -185,9 +185,9 @@ QPainterPath EqHandle::getCurvePath() void EqHandle::loadPixmap() { - QString fileName = "handle" + QString::number(m_numb+1); - if ( !isActiveHandle() ) { fileName = fileName + "inactive"; } - m_circlePixmap = PLUGIN_NAME::getIconPixmap( fileName.toLatin1() ); + auto fileName = "handle" + std::to_string(m_numb + 1); + if (!isActiveHandle()) { fileName += "inactive"; } + m_circlePixmap = PLUGIN_NAME::getIconPixmap(fileName); } bool EqHandle::mousePressed() const @@ -201,22 +201,21 @@ bool EqHandle::mousePressed() const float EqHandle::getPeakCurve( float x ) { double freqZ = xPixelToFreq( EqHandle::x(), m_width ); - const int SR = Engine::audioEngine()->processingSampleRate(); + const int SR = Engine::audioEngine()->outputSampleRate(); double w0 = 2 * LD_PI * freqZ / SR ; double c = cosf( w0 ); double s = sinf( w0 ); double Q = getResonance(); double A = pow( 10, yPixelToGain( EqHandle::y(), m_heigth, m_pixelsPerUnitHeight ) / 40 ); double alpha = s * sinh( log( 2 ) / 2 * Q * w0 / sinf( w0 ) ); - double a0, a1, a2, b0, b1, b2; // coeffs to calculate //calc coefficents - b0 = 1 + alpha * A; - b1 = -2 * c; - b2 = 1 - alpha * A; - a0 = 1 + alpha / A; - a1 = -2 * c; - a2 = 1 - alpha / A; + double b0 = 1 + alpha * A; + double b1 = -2 * c; + double b2 = 1 - alpha * A; + double a0 = 1 + alpha / A; + double a1 = -2 * c; + double a2 = 1 - alpha / A; //normalise b0 /= a0; @@ -239,21 +238,21 @@ float EqHandle::getPeakCurve( float x ) float EqHandle::getHighShelfCurve( float x ) { double freqZ = xPixelToFreq( EqHandle::x(), m_width ); - const int SR = Engine::audioEngine()->processingSampleRate(); + const int SR = Engine::audioEngine()->outputSampleRate(); double w0 = 2 * LD_PI * freqZ / SR; double c = cosf( w0 ); double s = sinf( w0 ); double A = pow( 10, yPixelToGain( EqHandle::y(), m_heigth, m_pixelsPerUnitHeight ) * 0.025 ); double beta = sqrt( A ) / m_resonance; - double a0, a1, a2, b0, b1, b2; // coeffs to calculate //calc coefficents - b0 = A * ( ( A + 1 ) + ( A - 1 ) * c + beta * s); - b1 = -2 * A * ( ( A - 1 ) + ( A + 1 ) * c ); - b2 = A * ( ( A + 1 ) + ( A - 1 ) * c - beta * s); - a0 = ( A + 1 ) - ( A - 1 ) * c + beta * s; - a1 = 2 * ( ( A - 1 ) - ( A + 1 ) * c ); - a2 = ( A + 1 ) - ( A - 1 ) * c - beta * s; + double b0 = A * ((A + 1) + (A - 1) * c + beta * s); + double b1 = -2 * A * ((A - 1) + (A + 1) * c); + double b2 = A * ((A + 1) + (A - 1) * c - beta * s); + double a0 = (A + 1) - (A - 1) * c + beta * s; + double a1 = 2 * ((A - 1) - (A + 1) * c); + double a2 = (A + 1) - (A - 1) * c - beta * s; + //normalise b0 /= a0; b1 /= a0; @@ -275,21 +274,20 @@ float EqHandle::getHighShelfCurve( float x ) float EqHandle::getLowShelfCurve( float x ) { double freqZ = xPixelToFreq( EqHandle::x(), m_width ); - const int SR = Engine::audioEngine()->processingSampleRate(); + const int SR = Engine::audioEngine()->outputSampleRate(); double w0 = 2 * LD_PI * freqZ / SR ; double c = cosf( w0 ); double s = sinf( w0 ); double A = pow( 10, yPixelToGain( EqHandle::y(), m_heigth, m_pixelsPerUnitHeight ) / 40 ); double beta = sqrt( A ) / m_resonance; - double a0, a1, a2, b0, b1, b2; // coeffs to calculate //calc coefficents - b0 = A * ( ( A + 1 ) - ( A - 1 ) * c + beta * s ); - b1 = 2 * A * ( ( A - 1 ) - ( A + 1 ) * c ) ; - b2 = A * ( ( A + 1 ) - ( A - 1 ) * c - beta * s); - a0 = ( A + 1 ) + ( A - 1 ) * c + beta * s; - a1 = -2 * ( ( A - 1 ) + ( A + 1 ) * c ); - a2 = ( A + 1 ) + ( A - 1) * c - beta * s; + double b0 = A * ((A + 1) - (A - 1) * c + beta * s); + double b1 = 2 * A * ((A - 1) - (A + 1) * c); + double b2 = A * ((A + 1) - (A - 1) * c - beta * s); + double a0 = (A + 1) + (A - 1) * c + beta * s; + double a1 = -2 * ((A - 1) + (A + 1) * c); + double a2 = (A + 1) + (A - 1) * c - beta * s; //normalise b0 /= a0; @@ -312,20 +310,20 @@ float EqHandle::getLowShelfCurve( float x ) float EqHandle::getLowCutCurve( float x ) { double freqZ = xPixelToFreq( EqHandle::x(), m_width ); - const int SR = Engine::audioEngine()->processingSampleRate(); + const int SR = Engine::audioEngine()->outputSampleRate(); double w0 = 2 * LD_PI * freqZ / SR ; double c = cosf( w0 ); double s = sinf( w0 ); double resonance = getResonance(); double alpha = s / (2 * resonance); - double a0, a1, a2, b0, b1, b2; // coeffs to calculate - b0 = ( 1 + c ) * 0.5; - b1 = ( -( 1 + c ) ); - b2 = ( 1 + c ) * 0.5; - a0 = 1 + alpha; - a1 = ( -2 * c ); - a2 = 1 - alpha; + double b0 = (1 + c) * 0.5; + double b1 = (-(1 + c)); + double b2 = (1 + c) * 0.5; + double a0 = 1 + alpha; + double a1 = (-2 * c); + double a2 = 1 - alpha; + //normalise b0 /= a0; b1 /= a0; @@ -355,20 +353,20 @@ float EqHandle::getLowCutCurve( float x ) float EqHandle::getHighCutCurve( float x ) { double freqZ = xPixelToFreq( EqHandle::x(), m_width ); - const int SR = Engine::audioEngine()->processingSampleRate(); + const int SR = Engine::audioEngine()->outputSampleRate(); double w0 = 2 * LD_PI * freqZ / SR ; double c = cosf( w0 ); double s = sinf( w0 ); double resonance = getResonance(); double alpha = s / (2 * resonance); - double a0, a1, a2, b0, b1, b2; // coeffs to calculate - b0 = ( 1 - c ) * 0.5; - b1 = 1 - c; - b2 = ( 1 - c ) * 0.5; - a0 = 1 + alpha; - a1 = -2 * c; - a2 = 1 - alpha; + double b0 = (1 - c) * 0.5; + double b1 = 1 - c; + double b2 = (1 - c) * 0.5; + double a0 = 1 + alpha; + double a1 = -2 * c; + double a2 = 1 - alpha; + //normalise b0 /= a0; b1 /= a0; @@ -529,7 +527,7 @@ void EqHandle::setlp48() double EqHandle::calculateGain(const double freq, const double a1, const double a2, const double b0, const double b1, const double b2 ) { - const int SR = Engine::audioEngine()->processingSampleRate(); + const int SR = Engine::audioEngine()->outputSampleRate(); const double w = 2 * LD_PI * freq / SR ; const double PHI = pow( sin( w / 2 ), 2 ) * 4; @@ -569,16 +567,7 @@ void EqHandle::mouseReleaseEvent( QGraphicsSceneMouseEvent *event ) void EqHandle::wheelEvent( QGraphicsSceneWheelEvent *wevent ) { - float highestBandwich; - if( m_type != EqHandleType::Para ) - { - highestBandwich = 10; - } - else - { - highestBandwich = 4; - } - + float highestBandwich = m_type != EqHandleType::Para ? 10 : 4; int numDegrees = wevent->delta() / 120; float numSteps = 0; if( wevent->modifiers() == Qt::ControlModifier ) diff --git a/plugins/Eq/EqEffect.cpp b/plugins/Eq/EqEffect.cpp index 8a7954144..b81512c1c 100644 --- a/plugins/Eq/EqEffect.cpp +++ b/plugins/Eq/EqEffect.cpp @@ -66,7 +66,7 @@ EqEffect::EqEffect( Model *parent, const Plugin::Descriptor::SubPluginFeatures:: bool EqEffect::processAudioBuffer( sampleFrame *buf, const fpp_t frames ) { - const int sampleRate = Engine::audioEngine()->processingSampleRate(); + const int sampleRate = Engine::audioEngine()->outputSampleRate(); //wet/dry controls const float dry = dryLevel(); @@ -287,21 +287,23 @@ bool EqEffect::processAudioBuffer( sampleFrame *buf, const fpp_t frames ) -float EqEffect::peakBand( float minF, float maxF, EqAnalyser *fft, int sr ) +float EqEffect::linearPeakBand(float minF, float maxF, EqAnalyser* fft, int sr) { - float peak = -60; - float *b = fft->m_bands; - float h = 0; - for( int x = 0; x < MAX_BANDS; x++, b++ ) + auto const fftEnergy = fft->getEnergy(); + if (fftEnergy == 0.) { return 0.; } + + + float peakLinear = 0.; + + for (int i = 0; i < MAX_BANDS; ++i) { - if( bandToFreq( x ,sr) >= minF && bandToFreq( x,sr ) <= maxF ) + if (bandToFreq(i, sr) >= minF && bandToFreq(i, sr) <= maxF) { - h = 20 * ( log10( *b / fft->getEnergy() ) ); - peak = h > peak ? h : peak; + peakLinear = std::max(peakLinear, fft->m_bands[i] / fftEnergy); } } - return ( peak + 60 ) / 100; + return peakLinear; } @@ -309,45 +311,34 @@ float EqEffect::peakBand( float minF, float maxF, EqAnalyser *fft, int sr ) void EqEffect::setBandPeaks( EqAnalyser *fft, int samplerate ) { + auto computePeakBand = [&](const FloatModel& freqModel, const FloatModel& bwModel) + { + float const freq = freqModel.value(); + float const bw = bwModel.value(); + + return linearPeakBand(freq * (1 - bw * 0.5), freq * (1 + bw * 0.5), fft, samplerate); + }; + m_eqControls.m_lowShelfPeakR = m_eqControls.m_lowShelfPeakL = - peakBand( m_eqControls.m_lowShelfFreqModel.value() - * ( 1 - m_eqControls.m_lowShelfResModel.value() * 0.5 ), - m_eqControls.m_lowShelfFreqModel.value(), - fft , samplerate ); + linearPeakBand(m_eqControls.m_lowShelfFreqModel.value() * (1 - m_eqControls.m_lowShelfResModel.value() * 0.5), + m_eqControls.m_lowShelfFreqModel.value(), fft , samplerate); m_eqControls.m_para1PeakL = m_eqControls.m_para1PeakR = - peakBand( m_eqControls.m_para1FreqModel.value() - * ( 1 - m_eqControls.m_para1BwModel.value() * 0.5 ), - m_eqControls.m_para1FreqModel.value() - * ( 1 + m_eqControls.m_para1BwModel.value() * 0.5 ), - fft , samplerate ); + computePeakBand(m_eqControls.m_para1FreqModel, m_eqControls.m_para1BwModel); m_eqControls.m_para2PeakL = m_eqControls.m_para2PeakR = - peakBand( m_eqControls.m_para2FreqModel.value() - * ( 1 - m_eqControls.m_para2BwModel.value() * 0.5 ), - m_eqControls.m_para2FreqModel.value() - * ( 1 + m_eqControls.m_para2BwModel.value() * 0.5 ), - fft , samplerate ); + computePeakBand(m_eqControls.m_para2FreqModel, m_eqControls.m_para2BwModel); m_eqControls.m_para3PeakL = m_eqControls.m_para3PeakR = - peakBand( m_eqControls.m_para3FreqModel.value() - * ( 1 - m_eqControls.m_para3BwModel.value() * 0.5 ), - m_eqControls.m_para3FreqModel.value() - * ( 1 + m_eqControls.m_para3BwModel.value() * 0.5 ), - fft , samplerate ); + computePeakBand(m_eqControls.m_para3FreqModel, m_eqControls.m_para3BwModel); m_eqControls.m_para4PeakL = m_eqControls.m_para4PeakR = - peakBand( m_eqControls.m_para4FreqModel.value() - * ( 1 - m_eqControls.m_para4BwModel.value() * 0.5 ), - m_eqControls.m_para4FreqModel.value() - * ( 1 + m_eqControls.m_para4BwModel.value() * 0.5 ), - fft , samplerate ); + computePeakBand(m_eqControls.m_para4FreqModel, m_eqControls.m_para4BwModel); m_eqControls.m_highShelfPeakL = m_eqControls.m_highShelfPeakR = - peakBand( m_eqControls.m_highShelfFreqModel.value(), - m_eqControls.m_highShelfFreqModel.value() - * ( 1 + m_eqControls.m_highShelfResModel.value() * 0.5 ), - fft, samplerate ); + linearPeakBand(m_eqControls.m_highShelfFreqModel.value(), + m_eqControls.m_highShelfFreqModel.value() * (1 + m_eqControls.m_highShelfResModel.value() * 0.5), + fft, samplerate); } extern "C" diff --git a/plugins/Eq/EqEffect.h b/plugins/Eq/EqEffect.h index 2d54c42a2..7e91ee401 100644 --- a/plugins/Eq/EqEffect.h +++ b/plugins/Eq/EqEffect.h @@ -28,6 +28,9 @@ #include "EqControls.h" #include "EqFilter.h" +#include + + namespace lmms { @@ -42,23 +45,20 @@ public: { return &m_eqControls; } - inline void gain( sampleFrame * buf, const fpp_t frames, float scale, sampleFrame * peak ) + inline void gain( sampleFrame * buf, const fpp_t frames, float scale, sampleFrame * peak ) { peak[0][0] = 0.0f; peak[0][1] = 0.0f; for( fpp_t f = 0; f < frames; ++f ) { - buf[f][0] *= scale; - buf[f][1] *= scale; + auto & sf = buf[f]; - if( fabs( buf[f][0] ) > peak[0][0] ) - { - peak[0][0] = fabs( buf[f][0] ); - } - if( fabs( buf[f][1] ) > peak[0][1] ) - { - peak[0][1] = fabs( buf[f][0] ); - } + // Apply gain to sample frame + sf[0] *= scale; + sf[1] *= scale; + // Update peaks + peak[0][0] = std::max(peak[0][0], (float)fabs(sf[0])); + peak[0][1] = std::max(peak[0][1], (float)fabs(sf[1])); } } @@ -87,7 +87,7 @@ private: float m_inGain; float m_outGain; - float peakBand( float minF, float maxF, EqAnalyser *, int ); + float linearPeakBand(float minF, float maxF, EqAnalyser*, int); inline float bandToFreq ( int index , int sampleRate ) { diff --git a/plugins/Eq/EqFader.h b/plugins/Eq/EqFader.h index 9db0fbe2d..d8897af5c 100644 --- a/plugins/Eq/EqFader.h +++ b/plugins/Eq/EqFader.h @@ -42,20 +42,6 @@ class EqFader : public Fader public: Q_OBJECT public: - EqFader( FloatModel * model, const QString & name, QWidget * parent, QPixmap * backg, QPixmap * leds, QPixmap * knobpi, float* lPeak, float* rPeak ) : - Fader( model, name, parent, backg, leds, knobpi ) - { - setMinimumSize( 23, 80 ); - setMaximumSize( 23, 80 ); - resize( 23, 80 ); - m_lPeak = lPeak; - m_rPeak = rPeak; - connect( getGUI()->mainWindow(), SIGNAL( periodicUpdate() ), this, SLOT( updateVuMeters() ) ); - m_model = model; - setPeak_L( 0 ); - setPeak_R( 0 ); - } - EqFader( FloatModel * model, const QString & name, QWidget * parent, float* lPeak, float* rPeak ) : Fader( model, name, parent ) { diff --git a/plugins/Eq/EqFilter.h b/plugins/Eq/EqFilter.h index c64f6d5c3..df2b50493 100644 --- a/plugins/Eq/EqFilter.h +++ b/plugins/Eq/EqFilter.h @@ -190,15 +190,13 @@ public : float s = sinf( w0 ); float alpha = s / ( 2 * m_res ); - float a0, a1, a2, b0, b1, b2; // coeffs to calculate - //calc coefficents - b0 = ( 1 + c ) * 0.5; - b1 = ( -( 1 + c ) ); - b2 = ( 1 + c ) * 0.5; - a0 = 1 + alpha; - a1 = ( -2 * c ); - a2 = 1 - alpha; + float b0 = (1 + c) * 0.5; + float b1 = (-(1 + c)); + float b2 = (1 + c) * 0.5; + float a0 = 1 + alpha; + float a1 = (-2 * c); + float a2 = 1 - alpha; //normalise b0 /= a0; @@ -235,15 +233,13 @@ public : float s = sinf( w0 ); float alpha = s / ( 2 * m_res ); - float a0, a1, a2, b0, b1, b2; // coeffs to calculate - //calc coefficents - b0 = ( 1 - c ) * 0.5; - b1 = 1 - c; - b2 = ( 1 - c ) * 0.5; - a0 = 1 + alpha; - a1 = -2 * c; - a2 = 1 - alpha; + float b0 = (1 - c) * 0.5; + float b1 = 1 - c; + float b2 = (1 - c) * 0.5; + float a0 = 1 + alpha; + float a1 = -2 * c; + float a2 = 1 - alpha; //normalise b0 /= a0; @@ -279,15 +275,13 @@ public: float A = pow( 10, m_gain * 0.025); float alpha = s * sinh( log( 2 ) / 2 * m_bw * w0 / sinf(w0) ); - float a0, a1, a2, b0, b1, b2; // coeffs to calculate - //calc coefficents - b0 = 1 + alpha*A; - b1 = -2*c; - b2 = 1 - alpha*A; - a0 = 1 + alpha/A; - a1 = -2*c; - a2 = 1 - alpha/A; + float b0 = 1 + alpha * A; + float b1 = -2 * c; + float b2 = 1 - alpha * A; + float a0 = 1 + alpha / A; + float a1 = -2 * c; + float a2 = 1 - alpha / A; //normalise b0 /= a0; @@ -345,15 +339,13 @@ public : // float alpha = s / ( 2 * m_res ); float beta = sqrt( A ) / m_res; - float a0, a1, a2, b0, b1, b2; // coeffs to calculate - //calc coefficents - b0 = A * ( ( A+1 ) - ( A-1 ) * c + beta * s ); - b1 = 2 * A * ( ( A - 1 ) - ( A + 1 ) * c) ; - b2 = A * ( ( A + 1 ) - ( A - 1 ) * c - beta * s); - a0 = ( A + 1 ) + ( A - 1 ) * c + beta * s; - a1 = -2 * ( ( A - 1 ) + ( A + 1 ) * c ); - a2 = ( A + 1 ) + ( A - 1) * c - beta * s; + float b0 = A * ((A + 1) - (A - 1) * c + beta * s); + float b1 = 2 * A * ((A - 1) - (A + 1) * c); + float b2 = A * ((A + 1) - (A - 1) * c - beta * s); + float a0 = (A + 1) + (A - 1) * c + beta * s; + float a1 = -2 * ((A - 1) + (A + 1) * c); + float a2 = (A + 1) + (A - 1) * c - beta * s; //normalise b0 /= a0; @@ -383,15 +375,14 @@ public : float A = pow( 10, m_gain * 0.025 ); float beta = sqrt( A ) / m_res; - float a0, a1, a2, b0, b1, b2; // coeffs to calculate - //calc coefficents - b0 = A *( ( A +1 ) + ( A - 1 ) * c + beta * s); - b1 = -2 * A * ( ( A - 1 ) + ( A + 1 ) * c ); - b2 = A * ( ( A + 1 ) + ( A - 1 ) * c - beta * s); - a0 = ( A + 1 ) - ( A - 1 ) * c + beta * s; - a1 = 2 * ( ( A - 1 ) - ( A + 1 ) * c ); - a2 = ( A + 1) - ( A - 1 ) * c - beta * s; + float b0 = A * ((A + 1) + (A - 1) * c + beta * s); + float b1 = -2 * A * ((A - 1) + (A + 1) * c); + float b2 = A * ((A + 1) + (A - 1) * c - beta * s); + float a0 = (A + 1) - (A - 1) * c + beta * s; + float a1 = 2 * ((A - 1) - (A + 1) * c); + float a2 = (A + 1) - (A - 1) * c - beta * s; + //normalise b0 /= a0; b1 /= a0; diff --git a/plugins/Eq/EqParameterWidget.cpp b/plugins/Eq/EqParameterWidget.cpp index b48f0f317..ceccb669f 100644 --- a/plugins/Eq/EqParameterWidget.cpp +++ b/plugins/Eq/EqParameterWidget.cpp @@ -56,7 +56,7 @@ EqParameterWidget::EqParameterWidget( QWidget *parent, EqControls * controls ) : m_pixelsPerOctave = EqHandle::freqToXPixel( 10000, m_displayWidth ) - EqHandle::freqToXPixel( 5000, m_displayWidth ); //GraphicsScene and GraphicsView stuff - auto scene = new QGraphicsScene(); + auto scene = new QGraphicsScene(this); scene->setSceneRect( 0, 0, m_displayWidth, m_displayHeigth ); auto view = new QGraphicsView(this); view->setStyleSheet( "border-style: none; background: transparent;" ); @@ -65,22 +65,22 @@ EqParameterWidget::EqParameterWidget( QWidget *parent, EqControls * controls ) : view->setScene( scene ); //adds the handles - m_handleList = new QList; + m_handleList.reserve(bandCount()); for ( int i = 0; i < bandCount(); i++ ) { m_handle = new EqHandle ( i, m_displayWidth, m_displayHeigth ); - m_handleList->append( m_handle ); + m_handleList.append(m_handle); m_handle->setZValue( 1 ); scene->addItem( m_handle ); } //adds the curve widget - m_eqcurve = new EqCurve( m_handleList, m_displayWidth, m_displayHeigth ); + m_eqcurve = new EqCurve(&m_handleList, m_displayWidth, m_displayHeigth); scene->addItem( m_eqcurve ); for ( int i = 0; i < bandCount(); i++ ) { // if the data of handle position has changed update the models - QObject::connect( m_handleList->at( i ) ,SIGNAL( positionChanged() ), this ,SLOT( updateModels() ) ); + QObject::connect(m_handleList.at(i), SIGNAL(positionChanged()), this, SLOT(updateModels())); } } @@ -112,16 +112,13 @@ void EqParameterWidget::updateHandle() m_eqcurve->setModelChanged( true ); for( int i = 0 ; i < bandCount(); i++ ) { - if ( !m_handleList->at( i )->mousePressed() ) //prevents a short circuit between handle and data model + if (!m_handleList.at(i)->mousePressed()) // prevents a short circuit between handle and data model { //sets the band on active if a fader or a knob is moved bool hover = false; // prevents an action if handle is moved for ( int j = 0; j < bandCount(); j++ ) { - if ( m_handleList->at(j)->isMouseHover() ) - { - hover = true; - } + if (m_handleList.at(j)->isMouseHover()) { hover = true; } } if ( !hover ) { @@ -131,17 +128,14 @@ void EqParameterWidget::updateHandle() } changeHandle( i ); } - else - { - m_handleList->at( i )->setHandleActive( m_bands[i].active->value() ); - } + else { m_handleList.at(i)->setHandleActive(m_bands[i].active->value()); } } - if ( m_bands[0].hp12->value() ) m_handleList->at( 0 )->sethp12(); - if ( m_bands[0].hp24->value() ) m_handleList->at( 0 )->sethp24(); - if ( m_bands[0].hp48->value() ) m_handleList->at( 0 )->sethp48(); - if ( m_bands[7].lp12->value() ) m_handleList->at( 7 )->setlp12(); - if ( m_bands[7].lp24->value() ) m_handleList->at( 7 )->setlp24(); - if ( m_bands[7].lp48->value() ) m_handleList->at( 7 )->setlp48(); + if (m_bands[0].hp12->value()) m_handleList.at(0)->sethp12(); + if (m_bands[0].hp24->value()) m_handleList.at(0)->sethp24(); + if (m_bands[0].hp48->value()) m_handleList.at(0)->sethp48(); + if (m_bands[7].lp12->value()) m_handleList.at(7)->setlp12(); + if (m_bands[7].lp24->value()) m_handleList.at(7)->setlp24(); + if (m_bands[7].lp48->value()) m_handleList.at(7)->setlp48(); } @@ -151,7 +145,7 @@ void EqParameterWidget::changeHandle( int i ) { //fill x, y, and bw with data from model float x = EqHandle::freqToXPixel( m_bands[i].freq->value(), m_displayWidth ); - float y = m_handleList->at( i )->y(); + float y = m_handleList.at(i)->y(); //for pass filters there is no gain model if( m_bands[i].gain ) { @@ -164,48 +158,45 @@ void EqParameterWidget::changeHandle( int i ) switch ( i ) { case 0 : - m_handleList->at( i )->setType( EqHandleType::HighPass ); - m_handleList->at( i )->setPos( x, m_displayHeigth / 2 ); + m_handleList.at(i)->setType(EqHandleType::HighPass); + m_handleList.at(i)->setPos(x, m_displayHeigth / 2); break; case 1: - m_handleList->at( i )->setType( EqHandleType::LowShelf ); - m_handleList->at( i )->setPos( x, y ); + m_handleList.at(i)->setType(EqHandleType::LowShelf); + m_handleList.at(i)->setPos(x, y); break; case 2: - m_handleList->at( i )->setType( EqHandleType::Para ); - m_handleList->at( i )->setPos( x, y ); + m_handleList.at(i)->setType(EqHandleType::Para); + m_handleList.at(i)->setPos(x, y); break; case 3: - m_handleList->at( i )->setType( EqHandleType::Para ); - m_handleList->at( i )->setPos( x, y ); + m_handleList.at(i)->setType(EqHandleType::Para); + m_handleList.at(i)->setPos(x, y); break; case 4: - m_handleList->at( i )->setType( EqHandleType::Para ); - m_handleList->at( i )->setPos( x, y ); + m_handleList.at(i)->setType(EqHandleType::Para); + m_handleList.at(i)->setPos(x, y); break; case 5: - m_handleList->at( i )->setType( EqHandleType::Para ); - m_handleList->at( i )->setPos( x, y ); + m_handleList.at(i)->setType(EqHandleType::Para); + m_handleList.at(i)->setPos(x, y); break; case 6: - m_handleList->at( i )->setType( EqHandleType::HighShelf ); - m_handleList->at( i )->setPos( x, y ); + m_handleList.at(i)->setType(EqHandleType::HighShelf); + m_handleList.at(i)->setPos(x, y); break; case 7: - m_handleList->at( i )->setType( EqHandleType::LowPass ); - m_handleList->at( i )->setPos( QPointF( x, m_displayHeigth / 2 ) ); + m_handleList.at(i)->setType(EqHandleType::LowPass); + m_handleList.at(i)->setPos(QPointF(x, m_displayHeigth / 2)); break; } // set resonance/bandwidth for each handle - if ( m_handleList->at( i )->getResonance() != bw ) - { - m_handleList->at( i )->setResonance( bw ); - } + if (m_handleList.at(i)->getResonance() != bw) { m_handleList.at(i)->setResonance(bw); } // and the active status - m_handleList->at( i )->setHandleActive( m_bands[i].active->value() ); - m_handleList->at( i )->update(); + m_handleList.at(i)->setHandleActive(m_bands[i].active->value()); + m_handleList.at(i)->update(); m_eqcurve->update(); } @@ -216,19 +207,17 @@ void EqParameterWidget::updateModels() { for ( int i=0 ; i < bandCount(); i++ ) { - m_bands[i].freq->setValue( EqHandle::xPixelToFreq( m_handleList->at( i )->x(), m_displayWidth ) ); + m_bands[i].freq->setValue(EqHandle::xPixelToFreq(m_handleList.at(i)->x(), m_displayWidth)); if( m_bands[i].gain ) { - m_bands[i].gain->setValue( EqHandle::yPixelToGain( m_handleList->at(i)->y(), m_displayHeigth, m_pixelsPerUnitHeight ) ); + m_bands[i].gain->setValue( + EqHandle::yPixelToGain(m_handleList.at(i)->y(), m_displayHeigth, m_pixelsPerUnitHeight)); } - m_bands[i].res->setValue( m_handleList->at( i )->getResonance() ); + m_bands[i].res->setValue(m_handleList.at(i)->getResonance()); //identifies the handle which is moved and set the band active - if ( sender() == m_handleList->at( i ) ) - { - m_bands[i].active->setValue( true ); - } + if (sender() == m_handleList.at(i)) { m_bands[i].active->setValue(true); } } m_eqcurve->update(); } diff --git a/plugins/Eq/EqParameterWidget.h b/plugins/Eq/EqParameterWidget.h index f80499395..c3444873b 100644 --- a/plugins/Eq/EqParameterWidget.h +++ b/plugins/Eq/EqParameterWidget.h @@ -75,7 +75,7 @@ class EqParameterWidget : public QWidget public: explicit EqParameterWidget( QWidget *parent = 0, EqControls * controls = 0 ); ~EqParameterWidget() override; - QList *m_handleList; + QList m_handleList; const int bandCount() { diff --git a/plugins/Eq/EqSpectrumView.cpp b/plugins/Eq/EqSpectrumView.cpp index aa556490b..f91b3ebc3 100644 --- a/plugins/Eq/EqSpectrumView.cpp +++ b/plugins/Eq/EqSpectrumView.cpp @@ -102,7 +102,7 @@ void EqAnalyser::analyze( sampleFrame *buf, const fpp_t frames ) return; } - m_sampleRate = Engine::audioEngine()->processingSampleRate(); + m_sampleRate = Engine::audioEngine()->outputSampleRate(); const int LOWEST_FREQ = 0; const int HIGHEST_FREQ = m_sampleRate / 2; @@ -186,6 +186,7 @@ namespace gui EqSpectrumView::EqSpectrumView(EqAnalyser *b, QWidget *_parent) : QWidget( _parent ), m_analyser( b ), + m_peakSum(0.), m_periodicalUpdate( false ) { setFixedSize( 450, 200 ); @@ -207,12 +208,8 @@ EqSpectrumView::EqSpectrumView(EqAnalyser *b, QWidget *_parent) : void EqSpectrumView::paintEvent(QPaintEvent *event) { - const float energy = m_analyser->getEnergy(); - if( energy <= 0 && m_peakSum <= 0 ) - { - //dont draw anything - return; - } + const float energy = m_analyser->getEnergy(); + if (energy <= 0. && m_peakSum <= 0) { return; } const int fh = height(); const int LOWER_Y = -36; // dB @@ -231,13 +228,13 @@ void EqSpectrumView::paintEvent(QPaintEvent *event) //Now we calculate the path m_path = QPainterPath(); float *bands = m_analyser->m_bands; - float peak; m_path.moveTo( 0, height() ); m_peakSum = 0; const float fallOff = 1.07; for( int x = 0; x < MAX_BANDS; ++x, ++bands ) { - peak = ( fh * 2.0 / 3.0 * ( 20 * ( log10( *bands / energy ) ) - LOWER_Y ) / ( - LOWER_Y ) ); + float peak = *bands != 0. ? (fh * 2.0 / 3.0 * (20. * log10(*bands / energy) - LOWER_Y) / (-LOWER_Y)) : 0.; + if( peak < 0 ) { peak = 0; diff --git a/plugins/Eq/faderback.png b/plugins/Eq/faderback.png deleted file mode 100644 index 2a03c3a5c..000000000 Binary files a/plugins/Eq/faderback.png and /dev/null differ diff --git a/plugins/Eq/faderknob.png b/plugins/Eq/faderknob.png deleted file mode 100755 index 866cd8634..000000000 Binary files a/plugins/Eq/faderknob.png and /dev/null differ diff --git a/plugins/Eq/faderleds.png b/plugins/Eq/faderleds.png deleted file mode 100644 index b99fac9fd..000000000 Binary files a/plugins/Eq/faderleds.png and /dev/null differ diff --git a/plugins/Flanger/FlangerControls.cpp b/plugins/Flanger/FlangerControls.cpp index 7402216ee..5550cdfb7 100644 --- a/plugins/Flanger/FlangerControls.cpp +++ b/plugins/Flanger/FlangerControls.cpp @@ -38,9 +38,9 @@ FlangerControls::FlangerControls( FlangerEffect *effect ) : m_effect ( effect ), m_delayTimeModel(0.001, 0.0001, 0.050, 0.0001, this, tr( "Delay samples" ) ), m_lfoFrequencyModel( 0.25, 0.01, 60, 0.0001, 60000.0, this, tr( "LFO frequency" ) ), - m_lfoAmountModel( 0.0, 0.0, 0.0025, 0.0001, this, tr( "Seconds" ) ), + m_lfoAmountModel( 0.0, 0.0, 0.0025, 0.0001, this, tr( "Amount" ) ), m_lfoPhaseModel( 90.0, 0.0, 360.0, 0.0001, this, tr( "Stereo phase" ) ), - m_feedbackModel( 0.0, -1.0, 1.0, 0.0001, this, tr( "Regen" ) ), + m_feedbackModel( 0.0, -1.0, 1.0, 0.0001, this, tr( "Feedback" ) ), m_whiteNoiseAmountModel( 0.0, 0.0, 0.05, 0.0001, this, tr( "Noise" ) ), m_invertFeedbackModel ( false, this, tr( "Invert" ) ) diff --git a/plugins/Flanger/FlangerEffect.cpp b/plugins/Flanger/FlangerEffect.cpp index 60b5df67b..c06747137 100644 --- a/plugins/Flanger/FlangerEffect.cpp +++ b/plugins/Flanger/FlangerEffect.cpp @@ -58,9 +58,9 @@ FlangerEffect::FlangerEffect( Model *parent, const Plugin::Descriptor::SubPlugin Effect( &flanger_plugin_descriptor, parent, key ), m_flangerControls( this ) { - m_lfo = new QuadratureLfo( Engine::audioEngine()->processingSampleRate() ); - m_lDelay = new MonoDelay( 1, Engine::audioEngine()->processingSampleRate() ); - m_rDelay = new MonoDelay( 1, Engine::audioEngine()->processingSampleRate() ); + m_lfo = new QuadratureLfo( Engine::audioEngine()->outputSampleRate() ); + m_lDelay = new MonoDelay( 1, Engine::audioEngine()->outputSampleRate() ); + m_rDelay = new MonoDelay( 1, Engine::audioEngine()->outputSampleRate() ); m_noise = new Noise; } @@ -99,19 +99,20 @@ bool FlangerEffect::processAudioBuffer( sampleFrame *buf, const fpp_t frames ) double outSum = 0.0; const float d = dryLevel(); const float w = wetLevel(); - const float length = m_flangerControls.m_delayTimeModel.value() * Engine::audioEngine()->processingSampleRate(); + const float length = m_flangerControls.m_delayTimeModel.value() * Engine::audioEngine()->outputSampleRate(); const float noise = m_flangerControls.m_whiteNoiseAmountModel.value(); - float amplitude = m_flangerControls.m_lfoAmountModel.value() * Engine::audioEngine()->processingSampleRate(); + float amplitude = m_flangerControls.m_lfoAmountModel.value() * Engine::audioEngine()->outputSampleRate(); bool invertFeedback = m_flangerControls.m_invertFeedbackModel.value(); m_lfo->setFrequency( 1.0/m_flangerControls.m_lfoFrequencyModel.value() ); m_lfo->setOffset( m_flangerControls.m_lfoPhaseModel.value() / 180 * D_PI ); m_lDelay->setFeedback( m_flangerControls.m_feedbackModel.value() ); m_rDelay->setFeedback( m_flangerControls.m_feedbackModel.value() ); auto dryS = std::array{}; - float leftLfo; - float rightLfo; for( fpp_t f = 0; f < frames; ++f ) { + float leftLfo; + float rightLfo; + buf[f][0] += m_noise->tick() * noise; buf[f][1] += m_noise->tick() * noise; dryS[0] = buf[f][0]; @@ -142,9 +143,9 @@ bool FlangerEffect::processAudioBuffer( sampleFrame *buf, const fpp_t frames ) void FlangerEffect::changeSampleRate() { - m_lfo->setSampleRate( Engine::audioEngine()->processingSampleRate() ); - m_lDelay->setSampleRate( Engine::audioEngine()->processingSampleRate() ); - m_rDelay->setSampleRate( Engine::audioEngine()->processingSampleRate() ); + m_lfo->setSampleRate( Engine::audioEngine()->outputSampleRate() ); + m_lDelay->setSampleRate( Engine::audioEngine()->outputSampleRate() ); + m_rDelay->setSampleRate( Engine::audioEngine()->outputSampleRate() ); } diff --git a/plugins/FreeBoy/FreeBoy.cpp b/plugins/FreeBoy/FreeBoy.cpp index f2dc95699..07497f6c0 100644 --- a/plugins/FreeBoy/FreeBoy.cpp +++ b/plugins/FreeBoy/FreeBoy.cpp @@ -220,22 +220,10 @@ QString FreeBoyInstrument::nodeName() const -/*f_cnt_t FreeBoyInstrument::desiredReleaseFrames() const +float FreeBoyInstrument::desiredReleaseTimeMs() const { - const float samplerate = Engine::audioEngine()->processingSampleRate(); - int maxrel = 0; - for( int i = 0 ; i < 3 ; ++i ) - { - if( maxrel < m_voice[i]->m_releaseModel.value() ) - maxrel = m_voice[i]->m_releaseModel.value(); - } - - return f_cnt_t( float(relTime[maxrel])*samplerate/1000.0 ); -}*/ - -f_cnt_t FreeBoyInstrument::desiredReleaseFrames() const -{ - return f_cnt_t( 1000 ); + // Previous implementation was 1000 samples. At 44.1 kHz this is somewhat shy of 23. ms. + return 23.f; } @@ -243,7 +231,7 @@ f_cnt_t FreeBoyInstrument::desiredReleaseFrames() const void FreeBoyInstrument::playNote(NotePlayHandle* nph, sampleFrame* workingBuffer) { const f_cnt_t tfp = nph->totalFramesPlayed(); - const int samplerate = Engine::audioEngine()->processingSampleRate(); + const int samplerate = Engine::audioEngine()->outputSampleRate(); const fpp_t frames = nph->framesLeftForCurrentPeriod(); const f_cnt_t offset = nph->noteOffset(); diff --git a/plugins/FreeBoy/FreeBoy.h b/plugins/FreeBoy/FreeBoy.h index 747305414..501377715 100644 --- a/plugins/FreeBoy/FreeBoy.h +++ b/plugins/FreeBoy/FreeBoy.h @@ -62,7 +62,7 @@ public: QString nodeName() const override; - f_cnt_t desiredReleaseFrames() const override; + float desiredReleaseTimeMs() const override; gui::PluginView* instantiateView( QWidget * _parent ) override; diff --git a/plugins/FreeBoy/GbApuWrapper.h b/plugins/FreeBoy/GbApuWrapper.h index 493a28731..3b95869d5 100644 --- a/plugins/FreeBoy/GbApuWrapper.h +++ b/plugins/FreeBoy/GbApuWrapper.h @@ -26,7 +26,6 @@ #include "Gb_Apu.h" #include "Multi_Buffer.h" -#include "MemoryManager.h" namespace lmms { @@ -34,7 +33,6 @@ namespace lmms class GbApuWrapper : private Gb_Apu { - MM_OPERATORS public: GbApuWrapper() = default; ~GbApuWrapper() = default; diff --git a/plugins/GigPlayer/CMakeLists.txt b/plugins/GigPlayer/CMakeLists.txt index 24db813bd..6ec8fe169 100644 --- a/plugins/GigPlayer/CMakeLists.txt +++ b/plugins/GigPlayer/CMakeLists.txt @@ -1,6 +1,7 @@ if(LMMS_HAVE_GIG) INCLUDE(BuildPlugin) INCLUDE_DIRECTORIES(${GIG_INCLUDE_DIRS}) + SET(CMAKE_AUTOUIC ON) # Required for not crashing loading files with libgig SET(GCC_COVERAGE_COMPILE_FLAGS "-fexceptions") @@ -12,8 +13,12 @@ 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 + EMBEDDED_RESOURCES "${CMAKE_CURRENT_SOURCE_DIR}/*.png" + ) + target_link_libraries(gigplayer SampleRate::samplerate) endif(LMMS_HAVE_GIG) - diff --git a/plugins/GigPlayer/GigPlayer.cpp b/plugins/GigPlayer/GigPlayer.cpp index 0713d3100..7c0d0d26b 100644 --- a/plugins/GigPlayer/GigPlayer.cpp +++ b/plugins/GigPlayer/GigPlayer.cpp @@ -46,7 +46,7 @@ #include "Knob.h" #include "NotePlayHandle.h" #include "PathUtil.h" -#include "SampleBuffer.h" +#include "Sample.h" #include "Song.h" #include "PatchesDialog.h" @@ -81,7 +81,7 @@ Plugin::Descriptor PLUGIN_EXPORT gigplayer_plugin_descriptor = GigInstrument::GigInstrument( InstrumentTrack * _instrument_track ) : - Instrument( _instrument_track, &gigplayer_plugin_descriptor ), + Instrument(_instrument_track, &gigplayer_plugin_descriptor, nullptr, Flag::IsSingleStreamed | Flag::IsNotBendable), m_instance( nullptr ), m_instrument( nullptr ), m_filename( "" ), @@ -323,7 +323,7 @@ void GigInstrument::playNote( NotePlayHandle * _n, sampleFrame * ) void GigInstrument::play( sampleFrame * _working_buffer ) { const fpp_t frames = Engine::audioEngine()->framesPerPeriod(); - const int rate = Engine::audioEngine()->processingSampleRate(); + const int rate = Engine::audioEngine()->outputSampleRate(); // Initialize to zeros std::memset( &_working_buffer[0][0], 0, DEFAULT_CHANNELS * frames * sizeof( float ) ); @@ -437,7 +437,7 @@ void GigInstrument::play( sampleFrame * _working_buffer ) if (sample.region->PitchTrack == true) { freq_factor *= sample.freqFactor; } // We need a bit of margin so we don't get glitching - samples = frames / freq_factor + MARGIN[m_interpolation]; + samples = frames / freq_factor + Sample::s_interpolationMargins[m_interpolation]; } // Load this note's data @@ -746,7 +746,7 @@ void GigInstrument::addSamples( GigNote & gignote, bool wantReleaseSample ) if( gignote.midiNote >= keyLow && gignote.midiNote <= keyHigh ) { float attenuation = pDimRegion->GetVelocityAttenuation( gignote.velocity ); - float length = (float) pSample->SamplesTotal / Engine::audioEngine()->processingSampleRate(); + float length = (float) pSample->SamplesTotal / Engine::audioEngine()->outputSampleRate(); // TODO: sample panning? crossfade different layers? diff --git a/plugins/GigPlayer/GigPlayer.h b/plugins/GigPlayer/GigPlayer.h index e5039f109..50d1acd40 100644 --- a/plugins/GigPlayer/GigPlayer.h +++ b/plugins/GigPlayer/GigPlayer.h @@ -38,7 +38,6 @@ #include "Knob.h" #include "LcdSpinBox.h" #include "LedCheckBox.h" -#include "MemoryManager.h" #include "gig.h" @@ -236,7 +235,6 @@ public: class GigInstrument : public Instrument { Q_OBJECT - MM_OPERATORS mapPropertyFromModel( int, getBank, setBank, m_bankNum ); mapPropertyFromModel( int, getPatch, setPatch, m_patchNum ); @@ -261,16 +259,6 @@ public: QString nodeName() const override; - f_cnt_t desiredReleaseFrames() const override - { - return 0; - } - - Flags flags() const override - { - return Flag::IsSingleStreamed | Flag::IsNotBendable; - } - gui::PluginView* instantiateView( QWidget * _parent ) override; QString getCurrentPatchName(); diff --git a/plugins/Kicker/Kicker.cpp b/plugins/Kicker/Kicker.cpp index e6418e2da..06385608f 100644 --- a/plugins/Kicker/Kicker.cpp +++ b/plugins/Kicker/Kicker.cpp @@ -64,7 +64,7 @@ Plugin::Descriptor PLUGIN_EXPORT kicker_plugin_descriptor = KickerInstrument::KickerInstrument( InstrumentTrack * _instrument_track ) : - Instrument( _instrument_track, &kicker_plugin_descriptor ), + Instrument(_instrument_track, &kicker_plugin_descriptor, nullptr, Flag::IsNotBendable), m_startFreqModel( 150.0f, 5.0f, 1000.0f, 1.0f, this, tr( "Start frequency" ) ), m_endFreqModel( 40.0f, 5.0f, 1000.0f, 1.0f, this, tr( "End frequency" ) ), m_decayModel( 440.0f, 5.0f, 5000.0f, 1.0f, 5000.0f, this, tr( "Length" ) ), @@ -160,7 +160,7 @@ void KickerInstrument::playNote( NotePlayHandle * _n, { const fpp_t frames = _n->framesLeftForCurrentPeriod(); const f_cnt_t offset = _n->noteOffset(); - const float decfr = m_decayModel.value() * Engine::audioEngine()->processingSampleRate() / 1000.0f; + const float decfr = m_decayModel.value() * Engine::audioEngine()->outputSampleRate() / 1000.0f; const f_cnt_t tfp = _n->totalFramesPlayed(); if (!_n->m_pluginData) @@ -184,17 +184,26 @@ void KickerInstrument::playNote( NotePlayHandle * _n, } auto so = static_cast(_n->m_pluginData); - so->update( _working_buffer + offset, frames, Engine::audioEngine()->processingSampleRate() ); + so->update( _working_buffer + offset, frames, Engine::audioEngine()->outputSampleRate() ); if( _n->isReleased() ) { - const float done = _n->releaseFramesDone(); + // We need this to check if the release has ended const float desired = desiredReleaseFrames(); - for( fpp_t f = 0; f < frames; ++f ) + + // This can be considered the current release frame in the "global" context of the release. + // We need it with the desired number of release frames to compute the linear decay. + fpp_t currentReleaseFrame = _n->releaseFramesDone(); + + // Start applying the release at the correct frame + const float framesBeforeRelease = _n->framesBeforeRelease(); + for (fpp_t f = framesBeforeRelease; f < frames; ++f, ++currentReleaseFrame) { - const float fac = ( done+f < desired ) ? ( 1.0f - ( ( done+f ) / desired ) ) : 0; - _working_buffer[f+offset][0] *= fac; - _working_buffer[f+offset][1] *= fac; + const bool releaseStillActive = currentReleaseFrame < desired; + const float attenuation = releaseStillActive ? (1.0f - (currentReleaseFrame / desired)) : 0.f; + + _working_buffer[f + offset][0] *= attenuation; + _working_buffer[f + offset][1] *= attenuation; } } } diff --git a/plugins/Kicker/Kicker.h b/plugins/Kicker/Kicker.h index b5d065598..508787707 100644 --- a/plugins/Kicker/Kicker.h +++ b/plugins/Kicker/Kicker.h @@ -64,14 +64,9 @@ public: QString nodeName() const override; - Flags flags() const override + float desiredReleaseTimeMs() const override { - return Flag::IsNotBendable; - } - - f_cnt_t desiredReleaseFrames() const override - { - return( 512 ); + return 12.f; } gui::PluginView* instantiateView( QWidget * _parent ) override; diff --git a/plugins/Kicker/KickerOsc.h b/plugins/Kicker/KickerOsc.h index 1accb50a4..69436c5fc 100644 --- a/plugins/Kicker/KickerOsc.h +++ b/plugins/Kicker/KickerOsc.h @@ -31,7 +31,6 @@ #include "lmms_math.h" #include "interpolation.h" -#include "MemoryManager.h" namespace lmms { @@ -40,7 +39,6 @@ namespace lmms template class KickerOsc { - MM_OPERATORS public: KickerOsc( const FX & fx, const float start, const float end, const float noise, const float offset, const float slope, const float env, const float diststart, const float distend, const float length ) : diff --git a/plugins/LOMM/CMakeLists.txt b/plugins/LOMM/CMakeLists.txt new file mode 100644 index 000000000..dd6178f03 --- /dev/null +++ b/plugins/LOMM/CMakeLists.txt @@ -0,0 +1,3 @@ +INCLUDE(BuildPlugin) + +BUILD_PLUGIN(lomm LOMM.cpp LOMMControls.cpp LOMMControlDialog.cpp MOCFILES LOMM.h LOMMControls.h LOMMControlDialog.h EMBEDDED_RESOURCES *.png) diff --git a/plugins/LOMM/LOMM.cpp b/plugins/LOMM/LOMM.cpp new file mode 100644 index 000000000..bffcfb9cb --- /dev/null +++ b/plugins/LOMM/LOMM.cpp @@ -0,0 +1,441 @@ +/* + * LOMM.cpp + * + * Copyright (c) 2023 Lost Robot + * + * 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 "LOMM.h" + +#include "embed.h" +#include "plugin_export.h" + +namespace lmms +{ + +extern "C" +{ + Plugin::Descriptor PLUGIN_EXPORT lomm_plugin_descriptor = + { + LMMS_STRINGIFY(PLUGIN_NAME), + "LOMM", + QT_TRANSLATE_NOOP("PluginBrowser", "Upwards/downwards multiband compression plugin powered by the eldritch elder god LOMMUS."), + "Lost Robot ", + 0x0100, + Plugin::Type::Effect, + new PluginPixmapLoader("logo"), + nullptr, + nullptr + }; +} + + +LOMMEffect::LOMMEffect(Model* parent, const Descriptor::SubPluginFeatures::Key* key) : + Effect(&lomm_plugin_descriptor, parent, key), + m_lommControls(this), + m_sampleRate(Engine::audioEngine()->outputSampleRate()), + m_lp1(m_sampleRate), + m_lp2(m_sampleRate), + m_hp1(m_sampleRate), + m_hp2(m_sampleRate), + m_ap(m_sampleRate), + m_needsUpdate(true), + m_coeffPrecalc(-0.05), + m_crestTimeConst(0.999), + m_lookWrite(0), + m_lookBufLength(2) +{ + autoQuitModel()->setValue(autoQuitModel()->maxValue()); + + m_yL[0][0] = m_yL[0][1] = LOMM_MIN_FLOOR; + m_yL[1][0] = m_yL[1][1] = LOMM_MIN_FLOOR; + m_yL[2][0] = m_yL[2][1] = LOMM_MIN_FLOOR; + + m_ap.setFilterType(BasicFilters<2>::FilterType::AllPass); + + connect(Engine::audioEngine(), SIGNAL(sampleRateChanged()), this, SLOT(changeSampleRate())); + emit changeSampleRate(); +} + +void LOMMEffect::changeSampleRate() +{ + m_sampleRate = Engine::audioEngine()->outputSampleRate(); + m_lp1.setSampleRate(m_sampleRate); + m_lp2.setSampleRate(m_sampleRate); + m_hp1.setSampleRate(m_sampleRate); + m_hp2.setSampleRate(m_sampleRate); + m_ap.setSampleRate(m_sampleRate); + + m_coeffPrecalc = -2.2f / (m_sampleRate * 0.001f); + m_needsUpdate = true; + + m_crestTimeConst = exp(-1.f / (0.2f * m_sampleRate)); + + m_lookBufLength = std::ceil((LOMM_MAX_LOOKAHEAD / 1000.f) * m_sampleRate) + 2; + for (int i = 0; i < 2; ++i) + { + for (int j = 0; j < 3; ++j) + { + m_inLookBuf[j][i].resize(m_lookBufLength); + m_scLookBuf[j][i].resize(m_lookBufLength, LOMM_MIN_FLOOR); + } + } +} + + +bool LOMMEffect::processAudioBuffer(sampleFrame* buf, const fpp_t frames) +{ + if (!isEnabled() || !isRunning()) + { + return false; + } + + if (m_needsUpdate || m_lommControls.m_split1Model.isValueChanged()) + { + m_lp1.setLowpass(m_lommControls.m_split1Model.value()); + m_hp1.setHighpass(m_lommControls.m_split1Model.value()); + m_ap.calcFilterCoeffs(m_lommControls.m_split1Model.value(), 0.70710678118); + } + if (m_needsUpdate || m_lommControls.m_split2Model.isValueChanged()) + { + m_lp2.setLowpass(m_lommControls.m_split2Model.value()); + m_hp2.setHighpass(m_lommControls.m_split2Model.value()); + } + m_needsUpdate = false; + + float outSum = 0.f; + const float d = dryLevel(); + const float w = wetLevel(); + + const float depth = m_lommControls.m_depthModel.value(); + const float time = m_lommControls.m_timeModel.value(); + const float inVol = dbfsToAmp(m_lommControls.m_inVolModel.value()); + const float outVol = dbfsToAmp(m_lommControls.m_outVolModel.value()); + const float upward = m_lommControls.m_upwardModel.value(); + const float downward = m_lommControls.m_downwardModel.value(); + const bool split1Enabled = m_lommControls.m_split1EnabledModel.value(); + const bool split2Enabled = m_lommControls.m_split2EnabledModel.value(); + const bool band1Enabled = m_lommControls.m_band1EnabledModel.value(); + const bool band2Enabled = m_lommControls.m_band2EnabledModel.value(); + const bool band3Enabled = m_lommControls.m_band3EnabledModel.value(); + const float inHigh = dbfsToAmp(m_lommControls.m_inHighModel.value()); + const float inMid = dbfsToAmp(m_lommControls.m_inMidModel.value()); + const float inLow = dbfsToAmp(m_lommControls.m_inLowModel.value()); + float inBandVol[3] = {inHigh, inMid, inLow}; + const float outHigh = dbfsToAmp(m_lommControls.m_outHighModel.value()); + const float outMid = dbfsToAmp(m_lommControls.m_outMidModel.value()); + const float outLow = dbfsToAmp(m_lommControls.m_outLowModel.value()); + float outBandVol[3] = {outHigh, outMid, outLow}; + const float aThreshH = m_lommControls.m_aThreshHModel.value(); + const float aThreshM = m_lommControls.m_aThreshMModel.value(); + const float aThreshL = m_lommControls.m_aThreshLModel.value(); + float aThresh[3] = {aThreshH, aThreshM, aThreshL}; + const float aRatioH = m_lommControls.m_aRatioHModel.value(); + const float aRatioM = m_lommControls.m_aRatioMModel.value(); + const float aRatioL = m_lommControls.m_aRatioLModel.value(); + float aRatio[3] = {1.f / aRatioH, 1.f / aRatioM, 1.f / aRatioL}; + const float bThreshH = m_lommControls.m_bThreshHModel.value(); + const float bThreshM = m_lommControls.m_bThreshMModel.value(); + const float bThreshL = m_lommControls.m_bThreshLModel.value(); + float bThresh[3] = {bThreshH, bThreshM, bThreshL}; + const float bRatioH = m_lommControls.m_bRatioHModel.value(); + const float bRatioM = m_lommControls.m_bRatioMModel.value(); + const float bRatioL = m_lommControls.m_bRatioLModel.value(); + float bRatio[3] = {1.f / bRatioH, 1.f / bRatioM, 1.f / bRatioL}; + const float atkH = m_lommControls.m_atkHModel.value() * time; + const float atkM = m_lommControls.m_atkMModel.value() * time; + const float atkL = m_lommControls.m_atkLModel.value() * time; + const float atkCoefH = msToCoeff(atkH); + const float atkCoefM = msToCoeff(atkM); + const float atkCoefL = msToCoeff(atkL); + float atk[3] = {atkH, atkM, atkL}; + float atkCoef[3] = {atkCoefH, atkCoefM, atkCoefL}; + const float relH = m_lommControls.m_relHModel.value() * time; + const float relM = m_lommControls.m_relMModel.value() * time; + const float relL = m_lommControls.m_relLModel.value() * time; + const float relCoefH = msToCoeff(relH); + const float relCoefM = msToCoeff(relM); + const float relCoefL = msToCoeff(relL); + float rel[3] = {relH, relM, relL}; + float relCoef[3] = {relCoefH, relCoefM, relCoefL}; + const float rmsTime = m_lommControls.m_rmsTimeModel.value(); + const float rmsTimeConst = (rmsTime == 0) ? 0 : exp(-1.f / (rmsTime * 0.001f * m_sampleRate)); + const float knee = m_lommControls.m_kneeModel.value() * 0.5f; + const float range = m_lommControls.m_rangeModel.value(); + const float rangeAmp = dbfsToAmp(range); + const float balance = m_lommControls.m_balanceModel.value(); + const float balanceAmpTemp = dbfsToAmp(balance); + const float balanceAmp[2] = {1.f / balanceAmpTemp, balanceAmpTemp}; + const bool depthScaling = m_lommControls.m_depthScalingModel.value(); + const bool stereoLink = m_lommControls.m_stereoLinkModel.value(); + const float autoTime = m_lommControls.m_autoTimeModel.value() * m_lommControls.m_autoTimeModel.value(); + const float mix = m_lommControls.m_mixModel.value(); + const bool midside = m_lommControls.m_midsideModel.value(); + const bool lookaheadEnable = m_lommControls.m_lookaheadEnableModel.value(); + const int lookahead = std::ceil((m_lommControls.m_lookaheadModel.value() / 1000.f) * m_sampleRate); + const bool feedback = m_lommControls.m_feedbackModel.value() && !lookaheadEnable; + const bool lowSideUpwardSuppress = m_lommControls.m_lowSideUpwardSuppressModel.value() && midside; + + for (fpp_t f = 0; f < frames; ++f) + { + std::array s = {buf[f][0], buf[f][1]}; + + // Convert left/right to mid/side. Side channel is intentionally made + // to be 6 dB louder to bring it into volume ranges comparable to the mid channel. + if (midside) + { + float tempS0 = s[0]; + s[0] = (s[0] + s[1]) * 0.5f; + s[1] = tempS0 - s[1]; + } + + std::array, 3> bands = {{}}; + std::array, 3> bandsDry = {{}}; + + for (int i = 0; i < 2; ++i)// Channels + { + // These values are for the Auto time knob. Higher crest factor allows for faster attack/release. + float inSquared = s[i] * s[i]; + m_crestPeakVal[i] = std::max(std::max(LOMM_MIN_FLOOR, inSquared), m_crestTimeConst * m_crestPeakVal[i] + (1 - m_crestTimeConst) * (inSquared)); + m_crestRmsVal[i] = std::max(LOMM_MIN_FLOOR, m_crestTimeConst * m_crestRmsVal[i] + ((1 - m_crestTimeConst) * (inSquared))); + m_crestFactorVal[i] = m_crestPeakVal[i] / m_crestRmsVal[i]; + float crestFactorValTemp = ((m_crestFactorVal[i] - LOMM_AUTO_TIME_ADJUST) * autoTime) + LOMM_AUTO_TIME_ADJUST; + + // Crossover filters + bands[2][i] = m_lp2.update(s[i], i); + bands[1][i] = m_hp2.update(s[i], i); + bands[0][i] = m_hp1.update(bands[1][i], i); + bands[1][i] = m_lp1.update(bands[1][i], i); + bands[2][i] = m_ap.update(bands[2][i], i); + + if (!split1Enabled) + { + bands[1][i] += bands[0][i]; + bands[0][i] = 0; + } + if (!split2Enabled) + { + bands[1][i] += bands[2][i]; + bands[2][i] = 0; + } + + // Mute disabled bands + bands[0][i] *= band1Enabled; + bands[1][i] *= band2Enabled; + bands[2][i] *= band3Enabled; + + std::array detect = {0, 0, 0}; + for (int j = 0; j < 3; ++j)// Bands + { + bandsDry[j][i] = bands[j][i]; + + if (feedback && !lookaheadEnable) + { + bands[j][i] = m_prevOut[j][i]; + } + + bands[j][i] *= inBandVol[j] * inVol * balanceAmp[i]; + + if (rmsTime > 0)// RMS + { + m_rms[j][i] = rmsTimeConst * m_rms[j][i] + ((1 - rmsTimeConst) * (bands[j][i] * bands[j][i])); + detect[j] = std::max(LOMM_MIN_FLOOR, std::sqrt(m_rms[j][i])); + } + else// Peak + { + detect[j] = std::max(LOMM_MIN_FLOOR, std::abs(bands[j][i])); + } + + if (detect[j] > m_yL[j][i])// Attack phase + { + // Calculate attack value depending on crest factor + const float currentAttack = autoTime + ? msToCoeff(LOMM_AUTO_TIME_ADJUST * atk[j] / crestFactorValTemp) + : atkCoef[j]; + + m_yL[j][i] = m_yL[j][i] * currentAttack + (1 - currentAttack) * detect[j]; + } + else// Release phase + { + // Calculate release value depending on crest factor + const float currentRelease = autoTime + ? msToCoeff(LOMM_AUTO_TIME_ADJUST * rel[j] / crestFactorValTemp) + : relCoef[j]; + + m_yL[j][i] = m_yL[j][i] * currentRelease + (1 - currentRelease) * detect[j]; + } + + m_yL[j][i] = std::max(LOMM_MIN_FLOOR, m_yL[j][i]); + + float yAmp = m_yL[j][i]; + if (lookaheadEnable) + { + float temp = yAmp; + // Lookahead is calculated by picking the largest value between + // the current sidechain signal and the delayed sidechain signal. + yAmp = std::max(m_scLookBuf[j][i][m_lookWrite], m_scLookBuf[j][i][(m_lookWrite + m_lookBufLength - lookahead) % m_lookBufLength]); + m_scLookBuf[j][i][m_lookWrite] = temp; + } + + const float yDbfs = ampToDbfs(yAmp); + + float aboveGain = 0; + float belowGain = 0; + + // Downward compression + if (yDbfs - aThresh[j] < -knee)// Below knee + { + aboveGain = yDbfs; + } + else if (yDbfs - aThresh[j] < knee)// Within knee + { + const float temp = yDbfs - aThresh[j] + knee; + aboveGain = yDbfs + (aRatio[j] - 1) * temp * temp / (4 * knee); + } + else// Above knee + { + aboveGain = aThresh[j] + (yDbfs - aThresh[j]) * aRatio[j]; + } + if (aboveGain < yDbfs) + { + if (downward * depth <= 1) + { + aboveGain = linearInterpolate(yDbfs, aboveGain, downward * depth); + } + else + { + aboveGain = linearInterpolate(aboveGain, aThresh[j], downward * depth - 1); + } + } + + // Upward compression + if (yDbfs - bThresh[j] > knee)// Above knee + { + belowGain = yDbfs; + } + else if (bThresh[j] - yDbfs < knee)// Within knee + { + const float temp = bThresh[j] - yDbfs + knee; + belowGain = yDbfs + (1 - bRatio[j]) * temp * temp / (4 * knee); + } + else// Below knee + { + belowGain = bThresh[j] + (yDbfs - bThresh[j]) * bRatio[j]; + } + if (belowGain > yDbfs) + { + if (upward * depth <= 1) + { + belowGain = linearInterpolate(yDbfs, belowGain, upward * depth); + } + else + { + belowGain = linearInterpolate(belowGain, bThresh[j], upward * depth - 1); + } + } + + m_displayIn[j][i] = yDbfs; + m_gainResult[j][i] = (dbfsToAmp(aboveGain) / yAmp) * (dbfsToAmp(belowGain) / yAmp); + if (lowSideUpwardSuppress && m_gainResult[j][i] > 1 && j == 2 && i == 1) //undo upward compression if low side band + { + m_gainResult[j][i] = 1; + } + m_gainResult[j][i] = std::min(m_gainResult[j][i], rangeAmp); + m_displayOut[j][i] = ampToDbfs(std::max(LOMM_MIN_FLOOR, yAmp * m_gainResult[j][i])); + + // Apply the same gain reduction to both channels if stereo link is enabled. + if (stereoLink && i == 1) + { + if (m_gainResult[j][1] < m_gainResult[j][0]) + { + m_gainResult[j][0] = m_gainResult[j][1]; + m_displayOut[j][0] = m_displayIn[j][0] - (m_displayIn[j][1] - m_displayOut[j][1]); + } + else + { + m_gainResult[j][1] = m_gainResult[j][0]; + m_displayOut[j][1] = m_displayIn[j][1] - (m_displayIn[j][0] - m_displayOut[j][0]); + } + } + } + } + + for (int i = 0; i < 2; ++i)// Channels + { + for (int j = 0; j < 3; ++j)// Bands + { + if (lookaheadEnable) + { + float temp = bands[j][i]; + bands[j][i] = m_inLookBuf[j][i][m_lookWrite]; + m_inLookBuf[j][i][m_lookWrite] = temp; + bandsDry[j][i] = bands[j][i]; + } + else if (feedback) + { + bands[j][i] = bandsDry[j][i] * inBandVol[j] * inVol * balanceAmp[i]; + } + + // Apply gain reduction + bands[j][i] *= m_gainResult[j][i]; + + // Store for Feedback + m_prevOut[j][i] = bands[j][i]; + + bands[j][i] *= outBandVol[j]; + + bands[j][i] = linearInterpolate(bandsDry[j][i], bands[j][i], mix); + } + + s[i] = bands[0][i] + bands[1][i] + bands[2][i]; + + s[i] *= linearInterpolate(1.f, outVol, mix * (depthScaling ? depth : 1)); + } + + // Convert mid/side back to left/right. + // Note that the side channel was intentionally made to be 6 dB louder prior to compression. + if (midside) + { + float tempS0 = s[0]; + s[0] = s[0] + (s[1] * 0.5f); + s[1] = tempS0 - (s[1] * 0.5f); + } + + if (--m_lookWrite < 0) { m_lookWrite = m_lookBufLength - 1; } + + buf[f][0] = d * buf[f][0] + w * s[0]; + buf[f][1] = d * buf[f][1] + w * s[1]; + outSum += buf[f][0] + buf[f][1]; + } + + checkGate(outSum / frames); + return isRunning(); +} + +extern "C" +{ + // necessary for getting instance out of shared lib + PLUGIN_EXPORT Plugin * lmms_plugin_main(Model* parent, void* data) + { + return new LOMMEffect(parent, static_cast(data)); + } +} + +} // namespace lmms diff --git a/plugins/LOMM/LOMM.h b/plugins/LOMM/LOMM.h new file mode 100644 index 000000000..196d0a09d --- /dev/null +++ b/plugins/LOMM/LOMM.h @@ -0,0 +1,106 @@ +/* + * LOMM.h + * + * Copyright (c) 2023 Lost Robot + * + * 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_LOMM_H +#define LMMS_LOMM_H + +#include "LOMMControls.h" +#include "Effect.h" + +#include "BasicFilters.h" +#include "lmms_math.h" + +namespace lmms +{ + +constexpr inline float LOMM_MIN_FLOOR = 0.00012589;// -72 dBFS +constexpr inline float LOMM_MAX_LOOKAHEAD = 20.f; +constexpr inline float LOMM_AUTO_TIME_ADJUST = 5.f; + +class LOMMEffect : public Effect +{ + Q_OBJECT +public: + LOMMEffect(Model* parent, const Descriptor::SubPluginFeatures::Key* key); + ~LOMMEffect() override = default; + bool processAudioBuffer(sampleFrame* buf, const fpp_t frames) override; + + EffectControls* controls() override + { + return &m_lommControls; + } + + inline float msToCoeff(float ms) + { + return (ms == 0) ? 0 : exp(m_coeffPrecalc / ms); + } + +private slots: + void changeSampleRate(); + +private: + LOMMControls m_lommControls; + + float m_sampleRate; + + StereoLinkwitzRiley m_lp1; + StereoLinkwitzRiley m_lp2; + + StereoLinkwitzRiley m_hp1; + StereoLinkwitzRiley m_hp2; + + BasicFilters<2> m_ap; + + bool m_needsUpdate; + float m_coeffPrecalc; + + std::array, 3> m_yL; + std::array, 3> m_rms; + std::array, 3> m_gainResult; + + std::array, 3> m_displayIn; + std::array, 3> m_displayOut; + + std::array m_crestPeakVal; + std::array m_crestRmsVal; + std::array m_crestFactorVal; + float m_crestTimeConst = 0.0f; + + std::array, 3> m_prevOut; + + std::array, 2>, 3> m_inLookBuf; + std::array, 2>, 3> m_scLookBuf; + + int m_lookWrite = 0; + int m_lookBufLength = 0; + + friend class LOMMControls; + friend class gui::LOMMControlDialog; +}; + + +} // namespace lmms + +#endif // LMMS_LOMM_H diff --git a/plugins/LOMM/LOMMControlDialog.cpp b/plugins/LOMM/LOMMControlDialog.cpp new file mode 100644 index 000000000..e53987a05 --- /dev/null +++ b/plugins/LOMM/LOMMControlDialog.cpp @@ -0,0 +1,275 @@ +/* + * LOMMControlDialog.cpp + * + * Copyright (c) 2023 Lost Robot + * + * 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 "LOMM.h" +#include "LOMMControlDialog.h" +#include "LOMMControls.h" + + +namespace lmms::gui +{ + +LOMMControlDialog::LOMMControlDialog(LOMMControls* controls) : + EffectControlDialog(controls), + m_controls(controls) +{ + setAutoFillBackground(true); + QPalette pal; + pal.setBrush(backgroundRole(), PLUGIN_NAME::getIconPixmap("artwork")); + setPalette(pal); + setFixedSize(400, 256); + + createKnob(KnobType::Bright26, this, 10, 4, &controls->m_depthModel, tr("Depth:"), "", tr("Compression amount for all bands")); + createKnob(KnobType::Bright26, this, 10, 41, &controls->m_timeModel, tr("Time:"), "", tr("Attack/release scaling for all bands")); + createKnob(KnobType::Bright26, this, 10, 220, &controls->m_inVolModel, tr("Input Volume:"), " dB", tr("Input volume")); + createKnob(KnobType::Bright26, this, 363, 220, &controls->m_outVolModel, tr("Output Volume:"), " dB", tr("Output volume")); + createKnob(KnobType::Bright26, this, 10, 179, &controls->m_upwardModel, tr("Upward Depth:"), "", tr("Upward compression amount for all bands")); + createKnob(KnobType::Bright26, this, 363, 179, &controls->m_downwardModel, tr("Downward Depth:"), "", tr("Downward compression amount for all bands")); + + createLcdFloatSpinBox(5, 2, "11green", tr("High/Mid Crossover"), this, 352, 76, &controls->m_split1Model, tr("High/Mid Crossover")); + createLcdFloatSpinBox(5, 2, "11green", tr("Mid/Low Crossover"), this, 352, 156, &controls->m_split2Model, tr("Mid/Low Crossover")); + + createPixmapButton(tr("High/mid band split"), this, 369, 104, &controls->m_split1EnabledModel, "crossover_led_green", "crossover_led_off", tr("High/mid band split")); + createPixmapButton(tr("Mid/low band split"), this, 369, 126, &controls->m_split2EnabledModel, "crossover_led_green", "crossover_led_off", tr("Mid/low band split")); + + createPixmapButton(tr("Enable High Band"), this, 143, 66, &controls->m_band1EnabledModel, "high_band_active", "high_band_inactive", tr("Enable High Band")); + createPixmapButton(tr("Enable Mid Band"), this, 143, 146, &controls->m_band2EnabledModel, "mid_band_active", "mid_band_inactive", tr("Enable Mid Band")); + createPixmapButton(tr("Enable Low Band"), this, 143, 226, &controls->m_band3EnabledModel, "low_band_active", "low_band_inactive", tr("Enable Low Band")); + + createKnob(KnobType::Bright26, this, 53, 43, &controls->m_inHighModel, tr("High Input Volume:"), " dB", tr("Input volume for high band")); + createKnob(KnobType::Bright26, this, 53, 123, &controls->m_inMidModel, tr("Mid Input Volume:"), " dB", tr("Input volume for mid band")); + createKnob(KnobType::Bright26, this, 53, 203, &controls->m_inLowModel, tr("Low Input Volume:"), " dB", tr("Input volume for low band")); + createKnob(KnobType::Bright26, this, 320, 43, &controls->m_outHighModel, tr("High Output Volume:"), " dB", tr("Output volume for high band")); + createKnob(KnobType::Bright26, this, 320, 123, &controls->m_outMidModel, tr("Mid Output Volume:"), " dB", tr("Output volume for mid band")); + createKnob(KnobType::Bright26, this, 320, 203, &controls->m_outLowModel, tr("Low Output Volume:"), " dB", tr("Output volume for low band")); + + createLcdFloatSpinBox(3, 3, "11green", tr("Above Threshold High"), this, 300, 13, &controls->m_aThreshHModel, tr("Downward compression threshold for high band")); + createLcdFloatSpinBox(3, 3, "11green", tr("Above Threshold Mid"), this, 300, 93, &controls->m_aThreshMModel, tr("Downward compression threshold for mid band")); + createLcdFloatSpinBox(3, 3, "11green", tr("Above Threshold Low"), this, 300, 173, &controls->m_aThreshLModel, tr("Downward compression threshold for low band")); + createLcdFloatSpinBox(2, 2, "11green", tr("Above Ratio High"), this, 284, 44, &controls->m_aRatioHModel, tr("Downward compression ratio for high band")); + createLcdFloatSpinBox(2, 2, "11green", tr("Above Ratio Mid"), this, 284, 124, &controls->m_aRatioMModel, tr("Downward compression ratio for mid band")); + createLcdFloatSpinBox(2, 2, "11green", tr("Above Ratio Low"), this, 284, 204, &controls->m_aRatioLModel, tr("Downward compression ratio for low band")); + + createLcdFloatSpinBox(3, 3, "11green", tr("Below Threshold High"), this, 59, 13, &controls->m_bThreshHModel, tr("Upward compression threshold for high band")); + createLcdFloatSpinBox(3, 3, "11green", tr("Below Threshold Mid"), this, 59, 93, &controls->m_bThreshMModel, tr("Upward compression threshold for mid band")); + createLcdFloatSpinBox(3, 3, "11green", tr("Below Threshold Low"), this, 59, 173, &controls->m_bThreshLModel, tr("Upward compression threshold for low band")); + createLcdFloatSpinBox(2, 2, "11green", tr("Below Ratio High"), this, 87, 44, &controls->m_bRatioHModel, tr("Upward compression ratio for high band")); + createLcdFloatSpinBox(2, 2, "11green", tr("Below Ratio Mid"), this, 87, 124, &controls->m_bRatioMModel, tr("Upward compression ratio for mid band")); + createLcdFloatSpinBox(2, 2, "11green", tr("Below Ratio Low"), this, 87, 204, &controls->m_bRatioLModel, tr("Upward compression ratio for low band")); + + createKnob(KnobType::Small17, this, 120, 61, &controls->m_atkHModel, tr("Attack High:"), " ms", tr("Attack time for high band")); + createKnob(KnobType::Small17, this, 120, 141, &controls->m_atkMModel, tr("Attack Mid:"), " ms", tr("Attack time for mid band")); + createKnob(KnobType::Small17, this, 120, 221, &controls->m_atkLModel, tr("Attack Low:"), " ms", tr("Attack time for low band")); + createKnob(KnobType::Small17, this, 261, 61, &controls->m_relHModel, tr("Release High:"), " ms", tr("Release time for high band")); + createKnob(KnobType::Small17, this, 261, 141, &controls->m_relMModel, tr("Release Mid:"), " ms", tr("Release time for mid band")); + createKnob(KnobType::Small17, this, 261, 221, &controls->m_relLModel, tr("Release Low:"), " ms", tr("Release time for low band")); + + createKnob(KnobType::Small17, this, 380, 42, &controls->m_rmsTimeModel, tr("RMS Time:"), " ms", tr("RMS size for sidechain signal (set to 0 for Peak mode)")); + createKnob(KnobType::Small17, this, 356, 42, &controls->m_kneeModel, tr("Knee:"), " dB", tr("Knee size for all compressors")); + createKnob(KnobType::Small17, this, 24, 146, &controls->m_rangeModel, tr("Range:"), " dB", tr("Maximum gain increase for all bands")); + createKnob(KnobType::Small17, this, 13, 114, &controls->m_balanceModel, tr("Balance:"), " dB", tr("Bias input volume towards one channel")); + + createPixmapButton(tr("Scale output volume with Depth"), this, 51, 0, &controls->m_depthScalingModel, "depthScaling_active", "depthScaling_inactive", + tr("Scale output volume with Depth parameter")); + createPixmapButton(tr("Stereo Link"), this, 52, 237, &controls->m_stereoLinkModel, "stereoLink_active", "stereoLink_inactive", + tr("Apply same gain change to both channels")); + + createKnob(KnobType::Small17, this, 24, 80, &controls->m_autoTimeModel, tr("Auto Time:"), "", tr("Speed up attack and release times when transients occur")); + createKnob(KnobType::Bright26, this, 363, 4, &controls->m_mixModel, tr("Mix:"), "", tr("Wet/Dry of all bands")); + + m_feedbackButton = createPixmapButton(tr("Feedback"), this, 317, 238, &controls->m_feedbackModel, "feedback_active", "feedback_inactive", + tr("Use output as sidechain signal instead of input")); + createPixmapButton(tr("Mid/Side"), this, 285, 238, &controls->m_midsideModel, "midside_active", "midside_inactive", tr("Compress mid/side channels instead of left/right")); + m_lowSideUpwardSuppressButton = createPixmapButton(tr("Suppress upward compression for side band"), this, 106, 180, &controls->m_lowSideUpwardSuppressModel, + "lowSideUpwardSuppress_active", "lowSideUpwardSuppress_inactive", tr("Suppress upward compression for side band")); + createPixmapButton(tr("Lookahead"), this, 147, 0, &controls->m_lookaheadEnableModel, "lookahead_active", "lookahead_inactive", + tr(("Enable lookahead with fixed " + std::to_string(int(LOMM_MAX_LOOKAHEAD)) + " ms latency").c_str())); + createLcdFloatSpinBox(2, 2, "11green", tr("Lookahead"), this, 214, 2, &controls->m_lookaheadModel, tr("Lookahead length")); + + PixmapButton* initButton = createPixmapButton(tr("Clear all parameters"), this, 84, 237, nullptr, "init_active", "init_inactive", tr("Clear all parameters")); + + connect(initButton, SIGNAL(clicked()), m_controls, SLOT(resetAllParameters())); + connect(&controls->m_lookaheadEnableModel, SIGNAL(dataChanged()), this, SLOT(updateFeedbackVisibility())); + connect(&controls->m_midsideModel, SIGNAL(dataChanged()), this, SLOT(updateLowSideUpwardSuppressVisibility())); + connect(getGUI()->mainWindow(), SIGNAL(periodicUpdate()), this, SLOT(updateDisplay())); + + emit updateFeedbackVisibility(); + emit updateLowSideUpwardSuppressVisibility(); +} + +void LOMMControlDialog::updateFeedbackVisibility() +{ + m_feedbackButton->setVisible(!m_controls->m_lookaheadEnableModel.value()); +} + +void LOMMControlDialog::updateLowSideUpwardSuppressVisibility() +{ + m_lowSideUpwardSuppressButton->setVisible(m_controls->m_midsideModel.value()); +} + +void LOMMControlDialog::updateDisplay() +{ + update(); +} + +void LOMMControlDialog::paintEvent(QPaintEvent *event) +{ + if (!isVisible()) { return; } + + QPainter p; + p.begin(this); + + // Draw threshold lines + QColor aColor(255, 255, 0, 31); + QColor bColor(255, 0, 0, 31); + QPen aPen(QColor(255, 255, 0, 255), 1); + QPen bPen(QColor(255, 0, 0, 255), 1); + int thresholdsX[] = {dbfsToX(m_controls->m_aThreshHModel.value()), + dbfsToX(m_controls->m_aThreshMModel.value()), + dbfsToX(m_controls->m_aThreshLModel.value()), + dbfsToX(m_controls->m_bThreshHModel.value()), + dbfsToX(m_controls->m_bThreshMModel.value()), + dbfsToX(m_controls->m_bThreshLModel.value())}; + for (int i = 0; i < 3; ++i) { + p.setPen(aPen); + p.fillRect(thresholdsX[i], LOMM_DISPLAY_Y[2 * i], LOMM_DISPLAY_X + LOMM_DISPLAY_WIDTH - thresholdsX[i], LOMM_DISPLAY_Y[2 * i + 1] + LOMM_DISPLAY_HEIGHT - LOMM_DISPLAY_Y[2 * i], aColor); + p.drawLine(thresholdsX[i], LOMM_DISPLAY_Y[2 * i], thresholdsX[i], LOMM_DISPLAY_Y[2 * i + 1] + LOMM_DISPLAY_HEIGHT); + + p.setPen(bPen); + p.fillRect(LOMM_DISPLAY_X, LOMM_DISPLAY_Y[2 * i], thresholdsX[i + 3] - LOMM_DISPLAY_X, LOMM_DISPLAY_Y[2 * i + 1] + LOMM_DISPLAY_HEIGHT - LOMM_DISPLAY_Y[2 * i], bColor); + p.drawLine(thresholdsX[i + 3], LOMM_DISPLAY_Y[2 * i], thresholdsX[i + 3], LOMM_DISPLAY_Y[2 * i + 1] + LOMM_DISPLAY_HEIGHT); + } + + QPen inputPen(QColor(200, 200, 200, 80), 1); + QPen outputPen(QColor(255, 255, 255, 255), 1); + for (int i = 0; i < 3; ++i) { + // Draw input lines + p.setPen(inputPen); + int inL = dbfsToX(m_controls->m_effect->m_displayIn[i][0]); + p.drawLine(inL, LOMM_DISPLAY_Y[2 * i] + 4, inL, LOMM_DISPLAY_Y[2 * i] + LOMM_DISPLAY_HEIGHT); + int inR = dbfsToX(m_controls->m_effect->m_displayIn[i][1]); + p.drawLine(inR, LOMM_DISPLAY_Y[2 * i + 1], inR, LOMM_DISPLAY_Y[2 * i + 1] + LOMM_DISPLAY_HEIGHT - 4); + + // Draw output lines + p.setPen(outputPen); + int outL = dbfsToX(m_controls->m_effect->m_displayOut[i][0]); + p.drawLine(outL, LOMM_DISPLAY_Y[2 * i], outL, LOMM_DISPLAY_Y[2 * i] + LOMM_DISPLAY_HEIGHT); + int outR = dbfsToX(m_controls->m_effect->m_displayOut[i][1]); + p.drawLine(outR, LOMM_DISPLAY_Y[2 * i + 1], outR, LOMM_DISPLAY_Y[2 * i + 1] + LOMM_DISPLAY_HEIGHT); + } + + p.end(); +} + +int LOMMControlDialog::dbfsToX(float dbfs) +{ + float returnX = (dbfs - LOMM_DISPLAY_MIN) / (LOMM_DISPLAY_MAX - LOMM_DISPLAY_MIN); + returnX = qBound(LOMM_DISPLAY_X, LOMM_DISPLAY_X + returnX * LOMM_DISPLAY_WIDTH, LOMM_DISPLAY_X + LOMM_DISPLAY_WIDTH); + return returnX; +} + +float LOMMControlDialog::xToDbfs(int x) +{ + float xNorm = static_cast(x - LOMM_DISPLAY_X) / LOMM_DISPLAY_WIDTH; + float dbfs = xNorm * (LOMM_DISPLAY_MAX - LOMM_DISPLAY_MIN) + LOMM_DISPLAY_MIN; + return dbfs; +} + +void LOMMControlDialog::mousePressEvent(QMouseEvent* event) +{ + if ((event->button() == Qt::LeftButton || event->button() == Qt::MiddleButton) && !(event->modifiers() & (Qt::ControlModifier | Qt::ShiftModifier))) + { + const QPoint& p = event->pos(); + + if (LOMM_DISPLAY_X - 10 <= p.x() && p.x() <= LOMM_DISPLAY_X + LOMM_DISPLAY_WIDTH + 10) + { + FloatModel* aThresh[] = {&m_controls->m_aThreshHModel, &m_controls->m_aThreshMModel, &m_controls->m_aThreshLModel}; + FloatModel* bThresh[] = {&m_controls->m_bThreshHModel, &m_controls->m_bThreshMModel, &m_controls->m_bThreshLModel}; + + for (int i = 0; i < 3; ++i) + { + if (LOMM_DISPLAY_Y[i * 2] <= p.y() && p.y() <= LOMM_DISPLAY_Y[i * 2 + 1] + LOMM_DISPLAY_HEIGHT) + { + int behavior = (p.x() < dbfsToX(bThresh[i]->value())) ? 0 : (p.x() > dbfsToX(aThresh[i]->value())) ? 1 : 2; + if (event->button() == Qt::MiddleButton) + { + if (behavior == 0 || behavior == 2) {bThresh[i]->reset();} + if (behavior == 1 || behavior == 2) {aThresh[i]->reset();} + return; + } + + m_bandDrag = i; + m_lastMousePos = p; + m_buttonPressed = true; + + m_dragType = behavior; + return; + } + } + } + } +} + +void LOMMControlDialog::mouseMoveEvent(QMouseEvent * event) +{ + if (m_buttonPressed && event->pos() != m_lastMousePos) + { + const float distance = event->pos().x() - m_lastMousePos.x(); + float dbDistance = distance * LOMM_DISPLAY_DB_PER_PIXEL; + m_lastMousePos = event->pos(); + + FloatModel* aModel[] = {&m_controls->m_aThreshHModel, &m_controls->m_aThreshMModel, &m_controls->m_aThreshLModel}; + FloatModel* bModel[] = {&m_controls->m_bThreshHModel, &m_controls->m_bThreshMModel, &m_controls->m_bThreshLModel}; + + float bVal = bModel[m_bandDrag]->value(); + float aVal = aModel[m_bandDrag]->value(); + if (m_dragType == 0) + { + bModel[m_bandDrag]->setValue(bVal + dbDistance); + } + else if (m_dragType == 1) + { + aModel[m_bandDrag]->setValue(aVal + dbDistance); + } + else + { + dbDistance = qBound(bModel[m_bandDrag]->minValue(), bVal + dbDistance, bModel[m_bandDrag]->maxValue()) - bVal; + dbDistance = qBound(aModel[m_bandDrag]->minValue(), aVal + dbDistance, aModel[m_bandDrag]->maxValue()) - aVal; + bModel[m_bandDrag]->setValue(bVal + dbDistance); + aModel[m_bandDrag]->setValue(aVal + dbDistance); + } + } +} + +void LOMMControlDialog::mouseReleaseEvent(QMouseEvent* event) +{ + if (event && event->button() == Qt::LeftButton) + { + m_buttonPressed = false; + } +} + + +} // namespace lmms::gui diff --git a/plugins/LOMM/LOMMControlDialog.h b/plugins/LOMM/LOMMControlDialog.h new file mode 100644 index 000000000..3de38c984 --- /dev/null +++ b/plugins/LOMM/LOMMControlDialog.h @@ -0,0 +1,130 @@ +/* + * LOMMControlDialog.h + * + * Copyright (c) 2023 Lost Robot + * + * 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_LOMM_CONTROL_DIALOG_H +#define LMMS_GUI_LOMM_CONTROL_DIALOG_H + +#include "EffectControlDialog.h" + +#include +#include + +#include "embed.h" +#include "GuiApplication.h" +#include "Knob.h" +#include "LcdFloatSpinBox.h" +#include "LcdSpinBox.h" +#include "LedCheckBox.h" +#include "MainWindow.h" +#include "PixmapButton.h" + +namespace lmms +{ + +inline constexpr float LOMM_DISPLAY_MIN = -72; +inline constexpr float LOMM_DISPLAY_MAX = 0; +inline constexpr float LOMM_DISPLAY_X = 125; +inline constexpr float LOMM_DISPLAY_Y[6] = {24, 41, 106, 123, 186, 203}; +inline constexpr float LOMM_DISPLAY_WIDTH = 150; +inline constexpr float LOMM_DISPLAY_HEIGHT = 13; +inline constexpr float LOMM_DISPLAY_DB_PER_PIXEL = (LOMM_DISPLAY_MAX - LOMM_DISPLAY_MIN) / LOMM_DISPLAY_WIDTH; + +class LOMMControls; + + +namespace gui +{ + +class LOMMControlDialog : public EffectControlDialog +{ + Q_OBJECT +public: + LOMMControlDialog(LOMMControls* controls); + ~LOMMControlDialog() override = default; + + int dbfsToX(float dbfs); + float xToDbfs(int x); + + Knob* createKnob(KnobType knobType, QWidget* parent, int x, int y, FloatModel* model, const QString& hintText, const QString& unit, const QString& toolTip) + { + Knob* knob = new Knob(knobType, parent); + knob->move(x, y); + knob->setModel(model); + knob->setHintText(hintText, unit); + knob->setToolTip(toolTip); + return knob; + } + + LcdFloatSpinBox* createLcdFloatSpinBox(int integerDigits, int decimalDigits, const QString& color, const QString& unit, QWidget* parent, int x, int y, FloatModel* model, const QString& toolTip) + { + LcdFloatSpinBox* spinBox = new LcdFloatSpinBox(integerDigits, decimalDigits, color, unit, parent); + spinBox->move(x, y); + spinBox->setModel(model); + spinBox->setSeamless(true, true); + spinBox->setToolTip(toolTip); + return spinBox; + } + + PixmapButton* createPixmapButton(const QString& text, QWidget* parent, int x, int y, BoolModel* model, + std::string_view activeIcon, std::string_view inactiveIcon, const QString& tooltip) + { + PixmapButton* button = new PixmapButton(parent, text); + button->move(x, y); + button->setCheckable(true); + if (model) { button->setModel(model); } + button->setActiveGraphic(PLUGIN_NAME::getIconPixmap(activeIcon)); + button->setInactiveGraphic(PLUGIN_NAME::getIconPixmap(inactiveIcon)); + button->setToolTip(tooltip); + return button; + } + +protected: + void paintEvent(QPaintEvent *event) override; + void mousePressEvent(QMouseEvent* event) override; + void mouseReleaseEvent(QMouseEvent* event) override; + void mouseMoveEvent(QMouseEvent* event) override; + +private: + LOMMControls* m_controls; + + QPoint m_lastMousePos; + bool m_buttonPressed = false; + int m_bandDrag = 0; + int m_dragType = -1; + + PixmapButton* m_feedbackButton; + PixmapButton* m_lowSideUpwardSuppressButton; + +private slots: + void updateFeedbackVisibility(); + void updateLowSideUpwardSuppressVisibility(); + void updateDisplay(); +}; + + +} // namespace gui + +} // namespace lmms + +#endif // LMMS_GUI_LOMM_CONTROL_DIALOG_H diff --git a/plugins/LOMM/LOMMControls.cpp b/plugins/LOMM/LOMMControls.cpp new file mode 100644 index 000000000..d695cf483 --- /dev/null +++ b/plugins/LOMM/LOMMControls.cpp @@ -0,0 +1,277 @@ +/* + * LOMMControls.cpp + * + * Copyright (c) 2023 Lost Robot + * + * 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 "LOMMControls.h" +#include "LOMM.h" + +#include +#include + +namespace lmms +{ + +LOMMControls::LOMMControls(LOMMEffect* effect) : + EffectControls(effect), + m_effect(effect), + m_depthModel(0.4, 0, 1, 0.00001, this, tr("Depth")), + m_timeModel(1, 0, 10, 0.00001, this, tr("Time")), + m_inVolModel(0, -48, 48, 0.00001, this, tr("Input Volume")), + m_outVolModel(8, -48, 48, 0.00001, this, tr("Output Volume")), + m_upwardModel(1, 0, 2, 0.00001, this, tr("Upward Depth")), + m_downwardModel(1, 0, 2, 0.00001, this, tr("Downward Depth")), + m_split1Model(2500, 20, 20000, 0.01, this, tr("High/Mid Split")), + m_split2Model(88.3, 20, 20000, 0.01, this, tr("Mid/Low Split")), + m_split1EnabledModel(true, this, tr("Enable High/Mid Split")), + m_split2EnabledModel(true, this, tr("Enable Mid/Low Split")), + m_band1EnabledModel(true, this, tr("Enable High Band")), + m_band2EnabledModel(true, this, tr("Enable Mid Band")), + m_band3EnabledModel(true, this, tr("Enable Low Band")), + m_inHighModel(0, -48, 48, 0.00001, this, tr("High Input Volume")), + m_inMidModel(0, -48, 48, 0.00001, this, tr("Mid Input Volume")), + m_inLowModel(0, -48, 48, 0.00001, this, tr("Low Input Volume")), + m_outHighModel(4.6, -48, 48, 0.00001, this, tr("High Output Volume")), + m_outMidModel(0.0, -48, 48, 0.00001, this, tr("Mid Output Volume")), + m_outLowModel(4.6, -48, 48, 0.00001, this, tr("Low Output Volume")), + m_aThreshHModel(-30.3, LOMM_DISPLAY_MIN, LOMM_DISPLAY_MAX, 0.001, this, tr("Above Threshold High")), + m_aThreshMModel(-25.0, LOMM_DISPLAY_MIN, LOMM_DISPLAY_MAX, 0.001, this, tr("Above Threshold Mid")), + m_aThreshLModel(-28.6, LOMM_DISPLAY_MIN, LOMM_DISPLAY_MAX, 0.001, this, tr("Above Threshold Low")), + m_aRatioHModel(99.99, 1, 99.99, 0.01, this, tr("Above Ratio High")), + m_aRatioMModel(66.7, 1, 99.99, 0.01, this, tr("Above Ratio Mid")), + m_aRatioLModel(66.7, 1, 99.99, 0.01, this, tr("Above Ratio Low")), + m_bThreshHModel(-35.6, LOMM_DISPLAY_MIN, LOMM_DISPLAY_MAX, 0.001, this, tr("Below Threshold High")), + m_bThreshMModel(-36.6, LOMM_DISPLAY_MIN, LOMM_DISPLAY_MAX, 0.001, this, tr("Below Threshold Mid")), + m_bThreshLModel(-35.6, LOMM_DISPLAY_MIN, LOMM_DISPLAY_MAX, 0.001, this, tr("Below Threshold Low")), + m_bRatioHModel(4.17, 1, 99.99, 0.01, this, tr("Below Ratio High")), + m_bRatioMModel(4.17, 1, 99.99, 0.01, this, tr("Below Ratio Mid")), + m_bRatioLModel(4.17, 1, 99.99, 0.01, this, tr("Below Ratio Low")), + m_atkHModel(13.5, 0, 1000, 0.001, this, tr("Attack High")), + m_atkMModel(22.4, 0, 1000, 0.001, this, tr("Attack Mid")), + m_atkLModel(47.8, 0, 1000, 0.001, this, tr("Attack Low")), + m_relHModel(132, 0, 1000, 0.001, this, tr("Release High")), + m_relMModel(282, 0, 1000, 0.001, this, tr("Release Mid")), + m_relLModel(282, 0, 1000, 0.001, this, tr("Release Low")), + m_rmsTimeModel(10, 0, 500, 0.001, this, tr("RMS Time")), + m_kneeModel(6, 0, 36, 0.00001, this, tr("Knee")), + m_rangeModel(36, 0, 96, 0.00001, this, tr("Range")), + m_balanceModel(0, -18, 18, 0.00001, this, tr("Balance")), + m_depthScalingModel(true, this, tr("Scale output volume with Depth")), + m_stereoLinkModel(false, this, tr("Stereo Link")), + m_autoTimeModel(0, 0, 1, 0.00001, this, tr("Auto Time")), + m_mixModel(1, 0, 1, 0.00001, this, tr("Mix")), + m_feedbackModel(false, this, tr("Feedback")), + m_midsideModel(false, this, tr("Mid/Side")), + m_lookaheadEnableModel(false, this, tr("Lookahead")), + m_lookaheadModel(0.f, 0.f, LOMM_MAX_LOOKAHEAD, 0.01, this, tr("Lookahead Length")), + m_lowSideUpwardSuppressModel(false, this, tr("Suppress upward compression for side band")) +{ + auto models = {&m_timeModel, &m_inVolModel, &m_outVolModel, &m_inHighModel, &m_inMidModel, + &m_inLowModel, &m_outHighModel, &m_outMidModel, &m_outLowModel, &m_aRatioHModel, + &m_aRatioMModel, &m_aRatioLModel, &m_bRatioHModel, &m_bRatioMModel, &m_bRatioLModel, + &m_atkHModel, &m_atkMModel, &m_atkLModel, &m_relHModel, &m_relMModel, &m_relLModel, + &m_rmsTimeModel, &m_balanceModel}; + for (auto model : models) { model->setScaleLogarithmic(true); } +} + + +void LOMMControls::resetAllParameters() +{ + int choice = QMessageBox::question(m_view, "Clear Plugin Settings", "Are you sure you want to clear all parameters?\n(This wipes LOMM to a clean slate, not the default preset.)", QMessageBox::Yes | QMessageBox::No); + if (choice != QMessageBox::Yes) { return; } + + // give the user a chance to beg LMMS for forgiveness + addJournalCheckPoint(); + + // This plugin's normal default values are fairly close to what they'd want in most applications. + // The Init button is there so the user can start from a clean slate instead. + // These are those values. + setInitAndReset(m_depthModel, 1); + setInitAndReset(m_timeModel, 1); + setInitAndReset(m_inVolModel, 0); + setInitAndReset(m_outVolModel, 0); + setInitAndReset(m_upwardModel, 1); + setInitAndReset(m_downwardModel, 1); + setInitAndReset(m_split1Model, 2500); + setInitAndReset(m_split2Model, 88); + setInitAndReset(m_split1EnabledModel, true); + setInitAndReset(m_split2EnabledModel, true); + setInitAndReset(m_band1EnabledModel, true); + setInitAndReset(m_band2EnabledModel, true); + setInitAndReset(m_band3EnabledModel, true); + setInitAndReset(m_inHighModel, 0); + setInitAndReset(m_inMidModel, 0); + setInitAndReset(m_inLowModel, 0); + setInitAndReset(m_outHighModel, 0); + setInitAndReset(m_outMidModel, 0); + setInitAndReset(m_outLowModel, 0); + setInitAndReset(m_aThreshHModel, m_aThreshHModel.maxValue()); + setInitAndReset(m_aThreshMModel, m_aThreshMModel.maxValue()); + setInitAndReset(m_aThreshLModel, m_aThreshLModel.maxValue()); + setInitAndReset(m_aRatioHModel, 1); + setInitAndReset(m_aRatioMModel, 1); + setInitAndReset(m_aRatioLModel, 1); + setInitAndReset(m_bThreshHModel, m_bThreshHModel.minValue()); + setInitAndReset(m_bThreshMModel, m_bThreshMModel.minValue()); + setInitAndReset(m_bThreshLModel, m_bThreshLModel.minValue()); + setInitAndReset(m_bRatioHModel, 1); + setInitAndReset(m_bRatioMModel, 1); + setInitAndReset(m_bRatioLModel, 1); + setInitAndReset(m_atkHModel, 13.5); + setInitAndReset(m_atkMModel, 22.4); + setInitAndReset(m_atkLModel, 47.8); + setInitAndReset(m_relHModel, 132); + setInitAndReset(m_relMModel, 282); + setInitAndReset(m_relLModel, 282); + setInitAndReset(m_rmsTimeModel, 10); + setInitAndReset(m_kneeModel, 6); + setInitAndReset(m_rangeModel, 36); + setInitAndReset(m_balanceModel, 0); + setInitAndReset(m_depthScalingModel, true); + setInitAndReset(m_stereoLinkModel, false); + setInitAndReset(m_autoTimeModel, 0); + setInitAndReset(m_mixModel, 1); + setInitAndReset(m_feedbackModel, false); + setInitAndReset(m_midsideModel, false); + setInitAndReset(m_lookaheadEnableModel, false); + setInitAndReset(m_lookaheadModel, 0.f); + setInitAndReset(m_lowSideUpwardSuppressModel, false); +} + + + +void LOMMControls::loadSettings(const QDomElement& parent) +{ + m_depthModel.loadSettings(parent, "depth"); + m_timeModel.loadSettings(parent, "time"); + m_inVolModel.loadSettings(parent, "inVol"); + m_outVolModel.loadSettings(parent, "outVol"); + m_upwardModel.loadSettings(parent, "upward"); + m_downwardModel.loadSettings(parent, "downward"); + m_split1Model.loadSettings(parent, "split1"); + m_split2Model.loadSettings(parent, "split2"); + m_split1EnabledModel.loadSettings(parent, "split1Enabled"); + m_split2EnabledModel.loadSettings(parent, "split2Enabled"); + m_band1EnabledModel.loadSettings(parent, "band1Enabled"); + m_band2EnabledModel.loadSettings(parent, "band2Enabled"); + m_band3EnabledModel.loadSettings(parent, "band3Enabled"); + m_inHighModel.loadSettings(parent, "inHigh"); + m_inMidModel.loadSettings(parent, "inMid"); + m_inLowModel.loadSettings(parent, "inLow"); + m_outHighModel.loadSettings(parent, "outHigh"); + m_outMidModel.loadSettings(parent, "outMid"); + m_outLowModel.loadSettings(parent, "outLow"); + m_aThreshHModel.loadSettings(parent, "aThreshH"); + m_aThreshMModel.loadSettings(parent, "aThreshM"); + m_aThreshLModel.loadSettings(parent, "aThreshL"); + m_aRatioHModel.loadSettings(parent, "aRatioH"); + m_aRatioMModel.loadSettings(parent, "aRatioM"); + m_aRatioLModel.loadSettings(parent, "aRatioL"); + m_bThreshHModel.loadSettings(parent, "bThreshH"); + m_bThreshMModel.loadSettings(parent, "bThreshM"); + m_bThreshLModel.loadSettings(parent, "bThreshL"); + m_bRatioHModel.loadSettings(parent, "bRatioH"); + m_bRatioMModel.loadSettings(parent, "bRatioM"); + m_bRatioLModel.loadSettings(parent, "bRatioL"); + m_atkHModel.loadSettings(parent, "atkH"); + m_atkMModel.loadSettings(parent, "atkM"); + m_atkLModel.loadSettings(parent, "atkL"); + m_relHModel.loadSettings(parent, "relH"); + m_relMModel.loadSettings(parent, "relM"); + m_relLModel.loadSettings(parent, "relL"); + m_rmsTimeModel.loadSettings(parent, "rmsTime"); + m_kneeModel.loadSettings(parent, "knee"); + m_rangeModel.loadSettings(parent, "range"); + m_balanceModel.loadSettings(parent, "balance"); + m_depthScalingModel.loadSettings(parent, "depthScaling"); + m_stereoLinkModel.loadSettings(parent, "stereoLink"); + m_autoTimeModel.loadSettings(parent, "autoTime"); + m_mixModel.loadSettings(parent, "mix"); + m_feedbackModel.loadSettings(parent, "feedback"); + m_midsideModel.loadSettings(parent, "midside"); + m_lookaheadEnableModel.loadSettings(parent, "lookaheadEnable"); + m_lookaheadModel.loadSettings(parent, "lookahead"); + m_lowSideUpwardSuppressModel.loadSettings(parent, "lowSideUpwardSuppress"); +} + + + + +void LOMMControls::saveSettings(QDomDocument& doc, QDomElement& parent) +{ + m_depthModel.saveSettings(doc, parent, "depth"); + m_timeModel.saveSettings(doc, parent, "time"); + m_inVolModel.saveSettings(doc, parent, "inVol"); + m_outVolModel.saveSettings(doc, parent, "outVol"); + m_upwardModel.saveSettings(doc, parent, "upward"); + m_downwardModel.saveSettings(doc, parent, "downward"); + m_split1Model.saveSettings(doc, parent, "split1"); + m_split2Model.saveSettings(doc, parent, "split2"); + m_split1EnabledModel.saveSettings(doc, parent, "split1Enabled"); + m_split2EnabledModel.saveSettings(doc, parent, "split2Enabled"); + m_band1EnabledModel.saveSettings(doc, parent, "band1Enabled"); + m_band2EnabledModel.saveSettings(doc, parent, "band2Enabled"); + m_band3EnabledModel.saveSettings(doc, parent, "band3Enabled"); + m_inHighModel.saveSettings(doc, parent, "inHigh"); + m_inMidModel.saveSettings(doc, parent, "inMid"); + m_inLowModel.saveSettings(doc, parent, "inLow"); + m_outHighModel.saveSettings(doc, parent, "outHigh"); + m_outMidModel.saveSettings(doc, parent, "outMid"); + m_outLowModel.saveSettings(doc, parent, "outLow"); + m_aThreshHModel.saveSettings(doc, parent, "aThreshH"); + m_aThreshMModel.saveSettings(doc, parent, "aThreshM"); + m_aThreshLModel.saveSettings(doc, parent, "aThreshL"); + m_aRatioHModel.saveSettings(doc, parent, "aRatioH"); + m_aRatioMModel.saveSettings(doc, parent, "aRatioM"); + m_aRatioLModel.saveSettings(doc, parent, "aRatioL"); + m_bThreshHModel.saveSettings(doc, parent, "bThreshH"); + m_bThreshMModel.saveSettings(doc, parent, "bThreshM"); + m_bThreshLModel.saveSettings(doc, parent, "bThreshL"); + m_bRatioHModel.saveSettings(doc, parent, "bRatioH"); + m_bRatioMModel.saveSettings(doc, parent, "bRatioM"); + m_bRatioLModel.saveSettings(doc, parent, "bRatioL"); + m_atkHModel.saveSettings(doc, parent, "atkH"); + m_atkMModel.saveSettings(doc, parent, "atkM"); + m_atkLModel.saveSettings(doc, parent, "atkL"); + m_relHModel.saveSettings(doc, parent, "relH"); + m_relMModel.saveSettings(doc, parent, "relM"); + m_relLModel.saveSettings(doc, parent, "relL"); + m_rmsTimeModel.saveSettings(doc, parent, "rmsTime"); + m_kneeModel.saveSettings(doc, parent, "knee"); + m_rangeModel.saveSettings(doc, parent, "range"); + m_balanceModel.saveSettings(doc, parent, "balance"); + m_depthScalingModel.saveSettings(doc, parent, "depthScaling"); + m_stereoLinkModel.saveSettings(doc, parent, "stereoLink"); + m_autoTimeModel.saveSettings(doc, parent, "autoTime"); + m_mixModel.saveSettings(doc, parent, "mix"); + m_feedbackModel.saveSettings(doc, parent, "feedback"); + m_midsideModel.saveSettings(doc, parent, "midside"); + m_lookaheadEnableModel.saveSettings(doc, parent, "lookaheadEnable"); + m_lookaheadModel.saveSettings(doc, parent, "lookahead"); + m_lowSideUpwardSuppressModel.saveSettings(doc, parent, "lowSideUpwardSuppress"); +} + + +} // namespace lmms + + diff --git a/plugins/LOMM/LOMMControls.h b/plugins/LOMM/LOMMControls.h new file mode 100644 index 000000000..3e5325426 --- /dev/null +++ b/plugins/LOMM/LOMMControls.h @@ -0,0 +1,136 @@ +/* + * LOMMControls.h + * + * Copyright (c) 2023 Lost Robot + * + * 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_LOMM_CONTROLS_H +#define LMMS_LOMM_CONTROLS_H + +#include "LOMMControlDialog.h" +#include "EffectControls.h" + +namespace lmms +{ +class LOMMEffect; + +namespace gui +{ +class LOMMControlDialog; +} + +class LOMMControls : public EffectControls +{ + Q_OBJECT +public: + LOMMControls(LOMMEffect* effect); + ~LOMMControls() override = default; + + void saveSettings(QDomDocument & doc, QDomElement & parent) override; + void loadSettings(const QDomElement & parent) override; + inline QString nodeName() const override + { + return "LOMMControls"; + } + + int controlCount() override + { + return 49; + } + + gui::EffectControlDialog* createView() override + { + m_view = new gui::LOMMControlDialog(this); + return m_view; + } + + template + void setInitAndReset(AutomatableModel& model, T initValue) + { + model.setInitValue(initValue); + model.reset(); + } + +public slots: + void resetAllParameters(); + +private: + LOMMEffect* m_effect; + gui::LOMMControlDialog* m_view; + + FloatModel m_depthModel; + FloatModel m_timeModel; + FloatModel m_inVolModel; + FloatModel m_outVolModel; + FloatModel m_upwardModel; + FloatModel m_downwardModel; + FloatModel m_split1Model; + FloatModel m_split2Model; + BoolModel m_split1EnabledModel; + BoolModel m_split2EnabledModel; + BoolModel m_band1EnabledModel; + BoolModel m_band2EnabledModel; + BoolModel m_band3EnabledModel; + FloatModel m_inHighModel; + FloatModel m_inMidModel; + FloatModel m_inLowModel; + FloatModel m_outHighModel; + FloatModel m_outMidModel; + FloatModel m_outLowModel; + FloatModel m_aThreshHModel; + FloatModel m_aThreshMModel; + FloatModel m_aThreshLModel; + FloatModel m_aRatioHModel; + FloatModel m_aRatioMModel; + FloatModel m_aRatioLModel; + FloatModel m_bThreshHModel; + FloatModel m_bThreshMModel; + FloatModel m_bThreshLModel; + FloatModel m_bRatioHModel; + FloatModel m_bRatioMModel; + FloatModel m_bRatioLModel; + FloatModel m_atkHModel; + FloatModel m_atkMModel; + FloatModel m_atkLModel; + FloatModel m_relHModel; + FloatModel m_relMModel; + FloatModel m_relLModel; + FloatModel m_rmsTimeModel; + FloatModel m_kneeModel; + FloatModel m_rangeModel; + FloatModel m_balanceModel; + BoolModel m_depthScalingModel; + BoolModel m_stereoLinkModel; + FloatModel m_autoTimeModel; + FloatModel m_mixModel; + BoolModel m_feedbackModel; + BoolModel m_midsideModel; + BoolModel m_lookaheadEnableModel; + FloatModel m_lookaheadModel; + BoolModel m_lowSideUpwardSuppressModel; + + friend class gui::LOMMControlDialog; + friend class LOMMEffect; +}; + +} // namespace lmms + +#endif // LMMS_LOMM_CONTROLS_H diff --git a/plugins/LOMM/artwork.png b/plugins/LOMM/artwork.png new file mode 100644 index 000000000..cfc65908f Binary files /dev/null and b/plugins/LOMM/artwork.png differ diff --git a/plugins/LOMM/crossover_led_green.png b/plugins/LOMM/crossover_led_green.png new file mode 100644 index 000000000..440eb82dd Binary files /dev/null and b/plugins/LOMM/crossover_led_green.png differ diff --git a/plugins/LOMM/crossover_led_off.png b/plugins/LOMM/crossover_led_off.png new file mode 100644 index 000000000..2fd7f721c Binary files /dev/null and b/plugins/LOMM/crossover_led_off.png differ diff --git a/plugins/LOMM/depthScaling_active.png b/plugins/LOMM/depthScaling_active.png new file mode 100644 index 000000000..ff53a77e0 Binary files /dev/null and b/plugins/LOMM/depthScaling_active.png differ diff --git a/plugins/LOMM/depthScaling_inactive.png b/plugins/LOMM/depthScaling_inactive.png new file mode 100644 index 000000000..ba806b6c3 Binary files /dev/null and b/plugins/LOMM/depthScaling_inactive.png differ diff --git a/plugins/LOMM/feedback_active.png b/plugins/LOMM/feedback_active.png new file mode 100644 index 000000000..426abb506 Binary files /dev/null and b/plugins/LOMM/feedback_active.png differ diff --git a/plugins/LOMM/feedback_inactive.png b/plugins/LOMM/feedback_inactive.png new file mode 100644 index 000000000..b3e7d635a Binary files /dev/null and b/plugins/LOMM/feedback_inactive.png differ diff --git a/plugins/LOMM/high_band_active.png b/plugins/LOMM/high_band_active.png new file mode 100644 index 000000000..e3c225e2d Binary files /dev/null and b/plugins/LOMM/high_band_active.png differ diff --git a/plugins/LOMM/high_band_inactive.png b/plugins/LOMM/high_band_inactive.png new file mode 100644 index 000000000..43a24cc8f Binary files /dev/null and b/plugins/LOMM/high_band_inactive.png differ diff --git a/plugins/LOMM/init_active.png b/plugins/LOMM/init_active.png new file mode 100644 index 000000000..3401a74c1 Binary files /dev/null and b/plugins/LOMM/init_active.png differ diff --git a/plugins/LOMM/init_inactive.png b/plugins/LOMM/init_inactive.png new file mode 100644 index 000000000..dfd847c32 Binary files /dev/null and b/plugins/LOMM/init_inactive.png differ diff --git a/plugins/LOMM/logo.png b/plugins/LOMM/logo.png new file mode 100644 index 000000000..9340da708 Binary files /dev/null and b/plugins/LOMM/logo.png differ diff --git a/plugins/LOMM/lookahead_active.png b/plugins/LOMM/lookahead_active.png new file mode 100644 index 000000000..78fc1ba03 Binary files /dev/null and b/plugins/LOMM/lookahead_active.png differ diff --git a/plugins/LOMM/lookahead_inactive.png b/plugins/LOMM/lookahead_inactive.png new file mode 100644 index 000000000..8e4e2ddb3 Binary files /dev/null and b/plugins/LOMM/lookahead_inactive.png differ diff --git a/plugins/LOMM/lowSideUpwardSuppress_active.png b/plugins/LOMM/lowSideUpwardSuppress_active.png new file mode 100644 index 000000000..39e6d746c Binary files /dev/null and b/plugins/LOMM/lowSideUpwardSuppress_active.png differ diff --git a/plugins/LOMM/lowSideUpwardSuppress_inactive.png b/plugins/LOMM/lowSideUpwardSuppress_inactive.png new file mode 100644 index 000000000..8e61e129d Binary files /dev/null and b/plugins/LOMM/lowSideUpwardSuppress_inactive.png differ diff --git a/plugins/LOMM/low_band_active.png b/plugins/LOMM/low_band_active.png new file mode 100644 index 000000000..fc3ca34c1 Binary files /dev/null and b/plugins/LOMM/low_band_active.png differ diff --git a/plugins/LOMM/low_band_inactive.png b/plugins/LOMM/low_band_inactive.png new file mode 100644 index 000000000..48041495e Binary files /dev/null and b/plugins/LOMM/low_band_inactive.png differ diff --git a/plugins/LOMM/mid_band_active.png b/plugins/LOMM/mid_band_active.png new file mode 100644 index 000000000..5a714a019 Binary files /dev/null and b/plugins/LOMM/mid_band_active.png differ diff --git a/plugins/LOMM/mid_band_inactive.png b/plugins/LOMM/mid_band_inactive.png new file mode 100644 index 000000000..6ccabc0ad Binary files /dev/null and b/plugins/LOMM/mid_band_inactive.png differ diff --git a/plugins/LOMM/midside_active.png b/plugins/LOMM/midside_active.png new file mode 100644 index 000000000..9caf8b691 Binary files /dev/null and b/plugins/LOMM/midside_active.png differ diff --git a/plugins/LOMM/midside_inactive.png b/plugins/LOMM/midside_inactive.png new file mode 100644 index 000000000..7b14b75df Binary files /dev/null and b/plugins/LOMM/midside_inactive.png differ diff --git a/plugins/LOMM/split1Enabled_active.png b/plugins/LOMM/split1Enabled_active.png new file mode 100644 index 000000000..6b3f8ec62 Binary files /dev/null and b/plugins/LOMM/split1Enabled_active.png differ diff --git a/plugins/LOMM/split1Enabled_inactive.png b/plugins/LOMM/split1Enabled_inactive.png new file mode 100644 index 000000000..7577806a0 Binary files /dev/null and b/plugins/LOMM/split1Enabled_inactive.png differ diff --git a/plugins/LOMM/split2Enabled_active.png b/plugins/LOMM/split2Enabled_active.png new file mode 100644 index 000000000..b7b162ceb Binary files /dev/null and b/plugins/LOMM/split2Enabled_active.png differ diff --git a/plugins/LOMM/split2Enabled_inactive.png b/plugins/LOMM/split2Enabled_inactive.png new file mode 100644 index 000000000..678d0565b Binary files /dev/null and b/plugins/LOMM/split2Enabled_inactive.png differ diff --git a/plugins/LOMM/stereoLink_active.png b/plugins/LOMM/stereoLink_active.png new file mode 100644 index 000000000..2ba9c4b88 Binary files /dev/null and b/plugins/LOMM/stereoLink_active.png differ diff --git a/plugins/LOMM/stereoLink_inactive.png b/plugins/LOMM/stereoLink_inactive.png new file mode 100644 index 000000000..9e5d5b884 Binary files /dev/null and b/plugins/LOMM/stereoLink_inactive.png differ diff --git a/plugins/LadspaBrowser/LadspaBrowser.cpp b/plugins/LadspaBrowser/LadspaBrowser.cpp index 31be64056..e6a31e15a 100644 --- a/plugins/LadspaBrowser/LadspaBrowser.cpp +++ b/plugins/LadspaBrowser/LadspaBrowser.cpp @@ -32,7 +32,6 @@ #include -#include "gui_templates.h" #include "LadspaDescription.h" #include "LadspaPortDialog.h" #include "TabBar.h" @@ -172,7 +171,6 @@ QWidget * LadspaBrowserView::createTab( QWidget * _parent, const QString & _txt, auto title = new QLabel(type + _txt, tab); QFont f = title->font(); f.setBold( true ); - title->setFont( pointSize<12>( f ) ); layout->addSpacing( 5 ); layout->addWidget( title ); diff --git a/plugins/LadspaBrowser/LadspaDescription.cpp b/plugins/LadspaBrowser/LadspaDescription.cpp index fbcd6c25d..7b1ede1c3 100644 --- a/plugins/LadspaBrowser/LadspaDescription.cpp +++ b/plugins/LadspaBrowser/LadspaDescription.cpp @@ -125,68 +125,39 @@ void LadspaDescription::update( const ladspa_key_t & _key ) Ladspa2LMMS * manager = Engine::getLADSPAManager(); auto name = new QLabel(description); - name->setText( QWidget::tr( "Name: " ) + manager->getName( _key ) ); + name->setText(tr("Name: ") + manager->getName(_key)); layout->addWidget( name ); - auto maker = new QWidget(description); - auto makerLayout = new QHBoxLayout(maker); - makerLayout->setContentsMargins(0, 0, 0, 0); - makerLayout->setSpacing( 0 ); + auto maker = new QLabel(description); + maker->setText(tr("Maker: ") + manager->getMaker(_key)); layout->addWidget( maker ); - auto maker_label = new QLabel(maker); - maker_label->setText( QWidget::tr( "Maker: " ) ); - maker_label->setAlignment( Qt::AlignTop ); - auto maker_content = new QLabel(maker); - maker_content->setText( manager->getMaker( _key ) ); - maker_content->setWordWrap( true ); - makerLayout->addWidget( maker_label ); - makerLayout->addWidget( maker_content, 1 ); - - auto copyright = new QWidget(description); - auto copyrightLayout = new QHBoxLayout(copyright); - copyrightLayout->setContentsMargins(0, 0, 0, 0); - copyrightLayout->setSpacing( 0 ); + auto copyright = new QLabel(description); + copyright->setText(tr("Copyright: ") + manager->getCopyright(_key)); layout->addWidget( copyright ); - auto copyright_label = new QLabel(copyright); - copyright_label->setText( QWidget::tr( "Copyright: " ) ); - copyright_label->setAlignment( Qt::AlignTop ); - - auto copyright_content = new QLabel(copyright); - copyright_content->setText( manager->getCopyright( _key ) ); - copyright_content->setWordWrap( true ); - copyrightLayout->addWidget( copyright_label ); - copyrightLayout->addWidget( copyright_content, 1 ); - auto requiresRealTime = new QLabel(description); - requiresRealTime->setText( QWidget::tr( "Requires Real Time: " ) + - ( manager->hasRealTimeDependency( _key ) ? - QWidget::tr( "Yes" ) : - QWidget::tr( "No" ) ) ); + requiresRealTime->setText(tr("Requires Real Time: ") + + (manager->hasRealTimeDependency(_key) ? tr("Yes") : tr("No"))); layout->addWidget( requiresRealTime ); auto realTimeCapable = new QLabel(description); - realTimeCapable->setText( QWidget::tr( "Real Time Capable: " ) + - ( manager->isRealTimeCapable( _key ) ? - QWidget::tr( "Yes" ) : - QWidget::tr( "No" ) ) ); + realTimeCapable->setText(tr("Real Time Capable: ") + + (manager->isRealTimeCapable(_key) ? tr("Yes") : tr("No"))); layout->addWidget( realTimeCapable ); auto inplaceBroken = new QLabel(description); - inplaceBroken->setText( QWidget::tr( "In Place Broken: " ) + - ( manager->isInplaceBroken( _key ) ? - QWidget::tr( "Yes" ) : - QWidget::tr( "No" ) ) ); + inplaceBroken->setText(tr("In Place Broken: ") + + (manager->isInplaceBroken(_key) ? tr("Yes") : tr("No"))); layout->addWidget( inplaceBroken ); auto channelsIn = new QLabel(description); - channelsIn->setText( QWidget::tr( "Channels In: " ) + QString::number( + channelsIn->setText(tr("Channels In: ") + QString::number( manager->getDescription( _key )->inputChannels ) ); layout->addWidget( channelsIn ); auto channelsOut = new QLabel(description); - channelsOut->setText( QWidget::tr( "Channels Out: " ) + QString::number( + channelsOut->setText(tr("Channels Out: ") + QString::number( manager->getDescription( _key )->outputChannels ) ); layout->addWidget( channelsOut ); } diff --git a/plugins/LadspaBrowser/LadspaPortDialog.cpp b/plugins/LadspaBrowser/LadspaPortDialog.cpp index e25679511..bf4d0038a 100644 --- a/plugins/LadspaBrowser/LadspaPortDialog.cpp +++ b/plugins/LadspaBrowser/LadspaPortDialog.cpp @@ -90,11 +90,11 @@ LadspaPortDialog::LadspaPortDialog( const ladspa_key_t & _key ) { if( min != NOHINT ) { - min *= Engine::audioEngine()->processingSampleRate(); + min *= Engine::audioEngine()->outputSampleRate(); } if( max != NOHINT ) { - max *= Engine::audioEngine()->processingSampleRate(); + max *= Engine::audioEngine()->outputSampleRate(); } } diff --git a/plugins/LadspaEffect/CMakeLists.txt b/plugins/LadspaEffect/CMakeLists.txt index a01eb950f..202a8dd04 100644 --- a/plugins/LadspaEffect/CMakeLists.txt +++ b/plugins/LadspaEffect/CMakeLists.txt @@ -1,6 +1,6 @@ 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") diff --git a/plugins/LadspaEffect/LadspaControlDialog.cpp b/plugins/LadspaEffect/LadspaControlDialog.cpp index 2a5437fb1..5189b0cde 100644 --- a/plugins/LadspaEffect/LadspaControlDialog.cpp +++ b/plugins/LadspaEffect/LadspaControlDialog.cpp @@ -88,17 +88,9 @@ void LadspaControlDialog::updateEffectView( LadspaControls * _ctl ) int col = 0; BufferDataType last_port = BufferDataType::None; - QGroupBox * grouper; - if( _ctl->m_processors > 1 ) - { - grouper = new QGroupBox( tr( "Channel " ) + - QString::number( proc + 1 ), - this ); - } - else - { - grouper = new QGroupBox( this ); - } + auto grouper = _ctl->m_processors > 1 + ? new QGroupBox(tr("Channel ") + QString::number(proc + 1), this) + : new QGroupBox(this); auto gl = new QGridLayout(grouper); grouper->setLayout( gl ); diff --git a/plugins/LadspaEffect/LadspaControls.h b/plugins/LadspaEffect/LadspaControls.h index 2bef0c856..c91f3badd 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 @@ public: gui::EffectControlDialog* createView() override { - return new gui::LadspaControlDialog( this ); + return new gui::LadspaMatrixControlDialog( this ); } @@ -79,6 +80,7 @@ private: friend class gui::LadspaControlDialog; + friend class gui::LadspaMatrixControlDialog; friend class LadspaEffect; diff --git a/plugins/LadspaEffect/LadspaEffect.cpp b/plugins/LadspaEffect/LadspaEffect.cpp index cc754a829..ccf92474b 100644 --- a/plugins/LadspaEffect/LadspaEffect.cpp +++ b/plugins/LadspaEffect/LadspaEffect.cpp @@ -36,7 +36,6 @@ #include "LadspaControl.h" #include "LadspaSubPluginFeatures.h" #include "AutomationClip.h" -#include "MemoryManager.h" #include "ValueBuffer.h" #include "Song.h" @@ -144,13 +143,13 @@ bool LadspaEffect::processAudioBuffer( sampleFrame * _buf, sampleFrame * o_buf = nullptr; QVarLengthArray sBuf(_frames * DEFAULT_CHANNELS); - if( m_maxSampleRate < Engine::audioEngine()->processingSampleRate() ) + if( m_maxSampleRate < Engine::audioEngine()->outputSampleRate() ) { o_buf = _buf; _buf = reinterpret_cast(sBuf.data()); sampleDown( o_buf, _buf, m_maxSampleRate ); frames = _frames * m_maxSampleRate / - Engine::audioEngine()->processingSampleRate(); + Engine::audioEngine()->outputSampleRate(); } // Copy the LMMS audio buffer to the LADSPA input buffer and initialize @@ -326,7 +325,7 @@ void LadspaEffect::pluginInstantiation() manager->isPortInput( m_key, port ) ) { p->rate = BufferRate::ChannelIn; - p->buffer = MM_ALLOC( Engine::audioEngine()->framesPerPeriod() ); + p->buffer = new LADSPA_Data[Engine::audioEngine()->framesPerPeriod()]; inbuf[ inputch ] = p->buffer; inputch++; } @@ -341,24 +340,24 @@ void LadspaEffect::pluginInstantiation() } else { - p->buffer = MM_ALLOC( Engine::audioEngine()->framesPerPeriod() ); + p->buffer = new LADSPA_Data[Engine::audioEngine()->framesPerPeriod()]; m_inPlaceBroken = true; } } else if( manager->isPortInput( m_key, port ) ) { p->rate = BufferRate::AudioRateInput; - p->buffer = MM_ALLOC( Engine::audioEngine()->framesPerPeriod() ); + p->buffer = new LADSPA_Data[Engine::audioEngine()->framesPerPeriod()]; } else { p->rate = BufferRate::AudioRateOutput; - p->buffer = MM_ALLOC( Engine::audioEngine()->framesPerPeriod() ); + p->buffer = new LADSPA_Data[Engine::audioEngine()->framesPerPeriod()]; } } else { - p->buffer = MM_ALLOC( 1 ); + p->buffer = new LADSPA_Data[1]; if( manager->isPortInput( m_key, port ) ) { @@ -557,7 +556,7 @@ void LadspaEffect::pluginDestruction() port_desc_t * pp = m_ports.at( proc ).at( port ); if( m_inPlaceBroken || pp->rate != BufferRate::ChannelOut ) { - if( pp->buffer) MM_FREE( pp->buffer ); + if( pp->buffer) delete[] pp->buffer; } delete pp; } @@ -588,7 +587,7 @@ sample_rate_t LadspaEffect::maxSamplerate( const QString & _name ) { return( __buggy_plugins[_name] ); } - return( Engine::audioEngine()->processingSampleRate() ); + return( Engine::audioEngine()->outputSampleRate() ); } diff --git a/plugins/LadspaEffect/LadspaMatrixControlDialog.cpp b/plugins/LadspaEffect/LadspaMatrixControlDialog.cpp new file mode 100644 index 000000000..4c9cd50ac --- /dev/null +++ b/plugins/LadspaEffect/LadspaMatrixControlDialog.cpp @@ -0,0 +1,226 @@ +/* + * 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" + + +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()); + + 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 000000000..c5949fa15 --- /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/LadspaSubPluginFeatures.cpp b/plugins/LadspaEffect/LadspaSubPluginFeatures.cpp index 46a211f9f..fc4667152 100644 --- a/plugins/LadspaEffect/LadspaSubPluginFeatures.cpp +++ b/plugins/LadspaEffect/LadspaSubPluginFeatures.cpp @@ -171,8 +171,7 @@ ladspa_key_t LadspaSubPluginFeatures::subPluginKeyToLadspaKey( const Key * _key ) { QString file = _key->attributes["file"]; - return( ladspa_key_t( file.remove( QRegExp( "\\.so$" ) ). - remove( QRegExp( "\\.dll$" ) ) + + return(ladspa_key_t(file.remove(QRegularExpression("\\.so$")).remove(QRegularExpression("\\.dll$")) + #ifdef LMMS_BUILD_WIN32 ".dll" #else diff --git a/plugins/LadspaEffect/LadspaWidgetFactory.cpp b/plugins/LadspaEffect/LadspaWidgetFactory.cpp new file mode 100644 index 000000000..0491fd661 --- /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 000000000..807334d32 --- /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/LadspaEffect/calf/CMakeLists.txt b/plugins/LadspaEffect/calf/CMakeLists.txt index 0c9cd8fa9..038fa6afb 100644 --- a/plugins/LadspaEffect/calf/CMakeLists.txt +++ b/plugins/LadspaEffect/calf/CMakeLists.txt @@ -35,10 +35,21 @@ SET_TARGET_PROPERTIES(veal PROPERTIES PREFIX "") TARGET_COMPILE_DEFINITIONS(veal PRIVATE DISABLE_OSC=1) SET(INLINE_FLAGS "") +SET(OTHER_FLAGS "") IF("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU") SET(INLINE_FLAGS -finline-functions-called-once -finline-limit=80) + SET(OTHER_FLAGS -Wno-format-overflow) ENDIF() -target_compile_options(veal PRIVATE -fexceptions -O2 -finline-functions ${INLINE_FLAGS}) + +if(MSVC) + target_compile_options(veal PRIVATE /wd4099 /wd4244 /wd4305) +else() + target_compile_options(veal PRIVATE -fexceptions -O2 -finline-functions ${INLINE_FLAGS} ${OTHER_FLAGS}) +endif() + +if(MSVC) + target_link_options(veal PRIVATE "/EXPORT:ladspa_descriptor") +endif() if(LMMS_BUILD_WIN32) add_custom_command( diff --git a/plugins/LadspaEffect/calf/veal b/plugins/LadspaEffect/calf/veal index fe628885b..0162621fa 160000 --- a/plugins/LadspaEffect/calf/veal +++ b/plugins/LadspaEffect/calf/veal @@ -1 +1 @@ -Subproject commit fe628885b761372b37136a3f2b7c3d56e179e3ba +Subproject commit 0162621fa75cf90c319c704d646c734e1ed21e14 diff --git a/plugins/LadspaEffect/caps/CMakeLists.txt b/plugins/LadspaEffect/caps/CMakeLists.txt index bdcf3a96a..f82fa5ab0 100644 --- a/plugins/LadspaEffect/caps/CMakeLists.txt +++ b/plugins/LadspaEffect/caps/CMakeLists.txt @@ -7,7 +7,16 @@ IF(LMMS_BUILD_WIN64) ADD_DEFINITIONS(-DLMMS_BUILD_WIN64) ENDIF(LMMS_BUILD_WIN64) SET_TARGET_PROPERTIES(caps PROPERTIES PREFIX "") -SET_TARGET_PROPERTIES(caps PROPERTIES COMPILE_FLAGS "-O2 -funroll-loops -Wno-write-strings") + +if(MSVC) + target_compile_options(caps PRIVATE /wd4244 /wd4305) +else() + target_compile_options(caps PRIVATE -O2 -funroll-loops -Wno-write-strings) +endif() + +if(MSVC) + target_link_options(caps PRIVATE "/EXPORT:ladspa_descriptor") +endif() IF(LMMS_BUILD_WIN32) add_custom_command( @@ -18,6 +27,7 @@ IF(LMMS_BUILD_WIN32) COMMAND_EXPAND_LISTS ) ENDIF(LMMS_BUILD_WIN32) + IF(NOT LMMS_BUILD_APPLE AND NOT LMMS_BUILD_OPENBSD) SET_TARGET_PROPERTIES(caps PROPERTIES LINK_FLAGS "${LINK_FLAGS} -shared -Wl,-no-undefined") ENDIF(NOT LMMS_BUILD_APPLE AND NOT LMMS_BUILD_OPENBSD) diff --git a/plugins/LadspaEffect/caps/basics.h b/plugins/LadspaEffect/caps/basics.h index df24e8c05..62eb77887 100644 --- a/plugins/LadspaEffect/caps/basics.h +++ b/plugins/LadspaEffect/caps/basics.h @@ -41,6 +41,9 @@ #include #include +#ifndef _USE_MATH_DEFINES +#define _USE_MATH_DEFINES +#endif #include #include @@ -76,7 +79,7 @@ #define MIN_GAIN .000001 /* -120 dB */ -/* smallest non-denormal 32 bit IEEE float is 1.1810-38 */ +/* smallest non-denormal 32 bit IEEE float is 1.18×10^-38 */ #define NOISE_FLOOR .00000000000005 /* -266 dB */ typedef int8_t int8; diff --git a/plugins/LadspaEffect/caps/dsp/Eq.h b/plugins/LadspaEffect/caps/dsp/Eq.h index 92639e8a1..89c86dd18 100644 --- a/plugins/LadspaEffect/caps/dsp/Eq.h +++ b/plugins/LadspaEffect/caps/dsp/Eq.h @@ -62,11 +62,11 @@ class Eq { public: /* recursion coefficients, 3 per band */ - eq_sample __attribute__ ((aligned)) a[Bands], b[Bands], c[Bands]; + eq_sample a[Bands], b[Bands], c[Bands]; /* past outputs, 2 per band */ - eq_sample __attribute__ ((aligned)) y[2][Bands]; + eq_sample y[2][Bands]; /* current gain and recursion factor, each 1 per band = 2 */ - eq_sample __attribute__ ((aligned)) gain[Bands], gf[Bands]; + eq_sample gain[Bands], gf[Bands]; /* input history */ eq_sample x[2]; /* history index */ diff --git a/plugins/LadspaEffect/caps/interface.cc b/plugins/LadspaEffect/caps/interface.cc index 96e3d9806..4c7ca46b5 100644 --- a/plugins/LadspaEffect/caps/interface.cc +++ b/plugins/LadspaEffect/caps/interface.cc @@ -29,7 +29,7 @@ (2541 - 2580 donated to artemio@kdemail.net) */ -#include +// #include #include "basics.h" @@ -69,7 +69,6 @@ seed() extern "C" { -__attribute__ ((constructor)) void caps_so_init() { DescriptorStub ** d = descriptors; @@ -125,7 +124,6 @@ void caps_so_init() //seed(); } -__attribute__ ((destructor)) void caps_so_fini() { for (ulong i = 0; i < N; ++i) @@ -142,4 +140,11 @@ ladspa_descriptor (unsigned long i) return 0; } +struct CapsSoInit +{ + CapsSoInit() { caps_so_init(); } + ~CapsSoInit() { caps_so_fini(); } +}; +static CapsSoInit capsSoInit; + }; /* extern "C" */ diff --git a/plugins/LadspaEffect/cmt/CMakeLists.txt b/plugins/LadspaEffect/cmt/CMakeLists.txt index 75dba319d..65430d109 100644 --- a/plugins/LadspaEffect/cmt/CMakeLists.txt +++ b/plugins/LadspaEffect/cmt/CMakeLists.txt @@ -5,7 +5,12 @@ ADD_LIBRARY(cmt MODULE ${SOURCES}) INSTALL(TARGETS cmt LIBRARY DESTINATION "${PLUGIN_DIR}/ladspa") SET_TARGET_PROPERTIES(cmt PROPERTIES PREFIX "") -target_compile_options(cmt PRIVATE -Wall -O3 -fno-strict-aliasing) + +if(MSVC) + target_compile_options(cmt PRIVATE /wd4244 /wd4305) +else() + target_compile_options(cmt PRIVATE -Wall -O3 -fno-strict-aliasing) +endif() if(LMMS_BUILD_WIN32) add_custom_command( @@ -17,6 +22,10 @@ if(LMMS_BUILD_WIN32) ) endif() +if(MSVC) + target_link_options(cmt PRIVATE "/EXPORT:ladspa_descriptor") +endif() + if(NOT LMMS_BUILD_WIN32) target_compile_options(cmt PRIVATE -fPIC) endif() diff --git a/plugins/LadspaEffect/cmt/cmt b/plugins/LadspaEffect/cmt/cmt index 6e6e291fb..24599fb45 160000 --- a/plugins/LadspaEffect/cmt/cmt +++ b/plugins/LadspaEffect/cmt/cmt @@ -1 +1 @@ -Subproject commit 6e6e291fbad1138c808860ba3f140a963b52fa58 +Subproject commit 24599fb45b99fff6302136f13adb3817e5833e7d diff --git a/plugins/LadspaEffect/swh/CMakeLists.txt b/plugins/LadspaEffect/swh/CMakeLists.txt index a83001177..3847429d3 100644 --- a/plugins/LadspaEffect/swh/CMakeLists.txt +++ b/plugins/LadspaEffect/swh/CMakeLists.txt @@ -9,9 +9,13 @@ ELSE() ENDIF() # Additional compile flags -SET(COMPILE_FLAGS "${COMPILE_FLAGS} -O3 -Wall") -SET(COMPILE_FLAGS "${COMPILE_FLAGS} -fomit-frame-pointer -funroll-loops -ffast-math -c -fno-strict-aliasing") -SET(COMPILE_FLAGS "${COMPILE_FLAGS} ${PIC_FLAGS}") +if(MSVC) + set(COMPILE_FLAGS "${COMPILE_FLAGS} /wd4244 /wd4273 /wd4305") +else() + set(COMPILE_FLAGS "${COMPILE_FLAGS} -O3 -Wall") + set(COMPILE_FLAGS "${COMPILE_FLAGS} -fomit-frame-pointer -funroll-loops -ffast-math -c -fno-strict-aliasing") + set(COMPILE_FLAGS "${COMPILE_FLAGS} ${PIC_FLAGS}") +endif() # Loop over every XML file FILE(GLOB XML_SOURCES "${CMAKE_CURRENT_SOURCE_DIR}/ladspa/*.xml") @@ -34,6 +38,10 @@ FOREACH(_item ${XML_SOURCES}) # Add a library target for this C file, which depends on success of makestup.pl ADD_LIBRARY("${_plugin}" MODULE "${_out_file}") + if(MSVC) + target_link_options("${_plugin}" PRIVATE "/EXPORT:ladspa_descriptor") + endif() + # Vocoder does not use fftw IF(NOT ("${_plugin}" STREQUAL "vocoder_1337")) TARGET_LINK_LIBRARIES("${_plugin}" ${FFTW3F_LIBRARIES}) @@ -104,6 +112,6 @@ TARGET_LINK_LIBRARIES(se4_1883 rms db) ADD_LIBRARY(pitchscale STATIC ladspa/util/pitchscale.c) SET_TARGET_PROPERTIES(pitchscale PROPERTIES COMPILE_FLAGS "${PIC_FLAGS}") -TARGET_LINK_LIBRARIES(pitchscale -lfftw3f) +TARGET_LINK_LIBRARIES(pitchscale ${FFTW3F_LIBRARIES}) TARGET_LINK_LIBRARIES(pitch_scale_1193 pitchscale) TARGET_LINK_LIBRARIES(pitch_scale_1194 pitchscale) diff --git a/plugins/LadspaEffect/swh/ladspa b/plugins/LadspaEffect/swh/ladspa index d99a0db52..0f54d2430 160000 --- a/plugins/LadspaEffect/swh/ladspa +++ b/plugins/LadspaEffect/swh/ladspa @@ -1 +1 @@ -Subproject commit d99a0db521d13a87bdaa418c674ca8858e484452 +Subproject commit 0f54d2430febb4d5f02d13132dd91d7345e080b5 diff --git a/plugins/LadspaEffect/tap/CMakeLists.txt b/plugins/LadspaEffect/tap/CMakeLists.txt index c8d0a4eb8..b895c7531 100644 --- a/plugins/LadspaEffect/tap/CMakeLists.txt +++ b/plugins/LadspaEffect/tap/CMakeLists.txt @@ -1,10 +1,17 @@ INCLUDE_DIRECTORIES("${CMAKE_SOURCE_DIR}/include") FILE(GLOB PLUGIN_SOURCES tap-plugins/*.c) LIST(SORT PLUGIN_SOURCES) -SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O3 -Wno-write-strings -fomit-frame-pointer -fno-strict-aliasing -funroll-loops -ffast-math") +if(MSVC) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /wd4244 /fp:fast") +else() + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O3 -Wno-write-strings -fomit-frame-pointer -fno-strict-aliasing -funroll-loops -ffast-math") +endif() FOREACH(_item ${PLUGIN_SOURCES}) GET_FILENAME_COMPONENT(_plugin "${_item}" NAME_WE) ADD_LIBRARY("${_plugin}" MODULE "${_item}") + if(MSVC) + target_link_options("${_plugin}" PRIVATE "/EXPORT:ladspa_descriptor") + endif() # TAP pinknoise will re-init srand(); use existing seed instead IF("${_plugin}" MATCHES "tap_pinknoise") TARGET_COMPILE_DEFINITIONS("${_plugin}" PRIVATE TAP_DISABLE_SRAND=1) @@ -24,4 +31,3 @@ FOREACH(_item ${PLUGIN_SOURCES}) TARGET_LINK_LIBRARIES("${_plugin}" m) ENDIF() ENDFOREACH() - diff --git a/plugins/LadspaEffect/tap/tap-plugins b/plugins/LadspaEffect/tap/tap-plugins index 198b84e6a..856402230 160000 --- a/plugins/LadspaEffect/tap/tap-plugins +++ b/plugins/LadspaEffect/tap/tap-plugins @@ -1 +1 @@ -Subproject commit 198b84e6ab37a9c979435cdb8f0a27a0e9a2934f +Subproject commit 85640223047d49a305e90ba1b92303eb066ba474 diff --git a/plugins/Lb302/Lb302.cpp b/plugins/Lb302/Lb302.cpp index ee49442d5..5dfbd5992 100644 --- a/plugins/Lb302/Lb302.cpp +++ b/plugins/Lb302/Lb302.cpp @@ -72,7 +72,7 @@ namespace lmms { -//#define engine::audioEngine()->processingSampleRate() 44100.0f +//#define engine::audioEngine()->outputSampleRate() 44100.0f const float sampleRateCutoff = 44100.0f; extern "C" @@ -111,8 +111,8 @@ void Lb302Filter::recalc() { vcf_e1 = exp(6.109 + 1.5876*(fs->envmod) + 2.1553*(fs->cutoff) - 1.2*(1.0-(fs->reso))); vcf_e0 = exp(5.613 - 0.8*(fs->envmod) + 2.1553*(fs->cutoff) - 0.7696*(1.0-(fs->reso))); - vcf_e0*=M_PI/Engine::audioEngine()->processingSampleRate(); - vcf_e1*=M_PI/Engine::audioEngine()->processingSampleRate(); + vcf_e0*=M_PI/Engine::audioEngine()->outputSampleRate(); + vcf_e1*=M_PI/Engine::audioEngine()->outputSampleRate(); vcf_e1 -= vcf_e0; vcf_rescoeff = exp(-1.20 + 3.455*(fs->reso)); @@ -166,12 +166,10 @@ void Lb302FilterIIR2::recalc() void Lb302FilterIIR2::envRecalc() { - float k, w; - Lb302Filter::envRecalc(); - w = vcf_e0 + vcf_c0; // e0 is adjusted for Hz and doesn't need ENVINC - k = exp(-w/vcf_rescoeff); // Does this mean c0 is inheritantly? + float w = vcf_e0 + vcf_c0; // e0 is adjusted for Hz and doesn't need ENVINC + float k = exp(-w/vcf_rescoeff); // Does this mean c0 is inheritantly? vcf_a = 2.0*cos(2.0*w) * k; vcf_b = -k*k; @@ -219,18 +217,15 @@ void Lb302Filter3Pole::recalc() // TODO: Try using k instead of vcf_reso void Lb302Filter3Pole::envRecalc() { - float w,k; - float kfco; - Lb302Filter::envRecalc(); // e0 is adjusted for Hz and doesn't need ENVINC - w = vcf_e0 + vcf_c0; - k = (fs->cutoff > 0.975)?0.975:fs->cutoff; + float w = vcf_e0 + vcf_c0; + float k = (fs->cutoff > 0.975)?0.975:fs->cutoff; // sampleRateCutoff should not be changed to anything dynamic that is outside the // scope of LB302 (like e.g. the audio engine's sample rate) as this changes the filter's cutoff // behavior without any modification to its controls. - kfco = 50.f + (k)*((2300.f-1600.f*(fs->envmod))+(w) * + float kfco = 50.f + (k)*((2300.f-1600.f*(fs->envmod))+(w) * (700.f+1500.f*(k)+(1500.f+(k)*(sampleRateCutoff/2.f-6000.f)) * (fs->envmod)) ); //+iacc*(.3+.7*kfco*kenvmod)*kaccent*kaccurve*2000 @@ -238,7 +233,7 @@ void Lb302Filter3Pole::envRecalc() #ifdef LB_24_IGNORE_ENVELOPE // kfcn = fs->cutoff; - kfcn = 2.0 * kfco / Engine::audioEngine()->processingSampleRate(); + kfcn = 2.0 * kfco / Engine::audioEngine()->outputSampleRate(); #else kfcn = w; #endif @@ -273,8 +268,18 @@ float Lb302Filter3Pole::process(const float& samp) // LBSynth // +static float computeDecayFactor(float decayTimeInSeconds, float targetedAttenuation) +{ + // This is the number of samples that correspond to the decay time in seconds + auto samplesNeededForDecay = decayTimeInSeconds * Engine::audioEngine()->outputSampleRate(); + + // This computes the factor that's needed to make a signal with a value of 1 decay to the + // targeted attenuation over the time in number of samples. + return std::pow(targetedAttenuation, 1. / samplesNeededForDecay); +} + Lb302Synth::Lb302Synth( InstrumentTrack * _instrumentTrack ) : - Instrument( _instrumentTrack, &lb302_plugin_descriptor ), + Instrument(_instrumentTrack, &lb302_plugin_descriptor, nullptr, Flag::IsSingleStreamed), vcf_cut_knob( 0.75f, 0.0f, 1.5f, 0.005f, this, tr( "VCF Cutoff Frequency" ) ), vcf_res_knob( 0.75f, 0.0f, 1.25f, 0.005f, this, tr( "VCF Resonance" ) ), vcf_mod_knob( 0.1f, 0.0f, 1.0f, 0.005f, this, tr( "VCF Envelope Mod" ) ), @@ -287,7 +292,6 @@ Lb302Synth::Lb302Synth( InstrumentTrack * _instrumentTrack ) : deadToggle( false, this, tr( "Dead" ) ), db24Toggle( false, this, tr( "24dB/oct Filter" ) ), vca_attack(1.0 - 0.96406088), - vca_decay(0.99897516), vca_a0(0.5), vca_a(0.), vca_mode(VcaMode::NeverPlayed) @@ -410,7 +414,7 @@ void Lb302Synth::filterChanged() float d = 0.2 + (2.3*vcf_dec_knob.value()); - d *= Engine::audioEngine()->processingSampleRate(); // d *= smpl rate + d *= Engine::audioEngine()->outputSampleRate(); // d *= smpl rate fs.envdecay = pow(0.1, 1.0/d * ENVINC); // decay is 0.1 to the 1/d * ENVINC // vcf_envdecay is now adjusted for both // sampling rate and ENVINC @@ -444,7 +448,7 @@ void Lb302Synth::recalcFilter() // THIS IS OLD 3pole/24dB code, I may reintegrate it. Don't need it // right now. Should be toggled by LB_24_RES_TRICK at the moment. - /*kfcn = 2.0 * (((vcf_cutoff*3000))) / engine::audioEngine()->processingSampleRate(); + /*kfcn = 2.0 * (((vcf_cutoff*3000))) / engine::audioEngine()->outputSampleRate(); kp = ((-2.7528*kfcn + 3.0429)*kfcn + 1.718)*kfcn - 0.9984; kp1 = kp+1.0; kp1h = 0.5*kp1; @@ -455,14 +459,12 @@ void Lb302Synth::recalcFilter() } inline float GET_INC(float freq) { - return freq/Engine::audioEngine()->processingSampleRate(); // TODO: Use actual sampling rate. + return freq/Engine::audioEngine()->outputSampleRate(); // TODO: Use actual sampling rate. } int Lb302Synth::process(sampleFrame *outbuf, const int size) { - const float sampleRatio = 44100.f / Engine::audioEngine()->processingSampleRate(); - float w; - float samp; + const float sampleRatio = 44100.f / Engine::audioEngine()->outputSampleRate(); // Hold on to the current VCF, and use it throughout this period Lb302Filter *filter = vcf.loadAcquire(); @@ -488,6 +490,14 @@ int Lb302Synth::process(sampleFrame *outbuf, const int size) // TODO: NORMAL RELEASE // vca_mode = 1; + // Note: this has to be computed during processing and cannot be initialized + // in the constructor because it's dependent on the sample rate and that might + // change during rendering! + // + // At 44.1 kHz this will compute something very close to the previously + // hard coded value of 0.99897516. + auto decay = computeDecayFactor(0.245260770975f, 1.f / 65536.f); + for( int i=0; i0.5) { - w = 2.0*(vco_k-0.5)-1.0; + float w = 2.0 * (vco_k - 0.5) - 1.0; vco_k = 0.5 - sqrtf(1.0-(w*w)); } vco_k *= 2.0; // MOOG wave gets filtered away @@ -585,20 +595,23 @@ int Lb302Synth::process(sampleFrame *outbuf, const int size) vco_k = 0.5 * Oscillator::noiseSample( vco_c ); break; + // The next cases all use the BandLimitedWave class which uses the oscillator increment `vco_inc` to compute samples. + // If that oscillator increment is 0 we return a 0 sample because calling BandLimitedWave::pdToLen(0) leads to a + // division by 0 which in turn leads to floating point exceptions. case VcoShape::BLSawtooth: - vco_k = BandLimitedWave::oscillate( vco_c + 0.5f, BandLimitedWave::pdToLen( vco_inc ), BandLimitedWave::Waveform::BLSaw ) * 0.5f; + vco_k = vco_inc == 0. ? 0. : BandLimitedWave::oscillate(vco_c + 0.5f, BandLimitedWave::pdToLen(vco_inc), BandLimitedWave::Waveform::BLSaw) * 0.5f; break; case VcoShape::BLSquare: - vco_k = BandLimitedWave::oscillate( vco_c + 0.5f, BandLimitedWave::pdToLen( vco_inc ), BandLimitedWave::Waveform::BLSquare ) * 0.5f; + vco_k = vco_inc == 0. ? 0. : BandLimitedWave::oscillate(vco_c + 0.5f, BandLimitedWave::pdToLen(vco_inc), BandLimitedWave::Waveform::BLSquare) * 0.5f; break; case VcoShape::BLTriangle: - vco_k = BandLimitedWave::oscillate( vco_c + 0.5f, BandLimitedWave::pdToLen( vco_inc ), BandLimitedWave::Waveform::BLTriangle ) * 0.5f; + vco_k = vco_inc == 0. ? 0. : BandLimitedWave::oscillate(vco_c + 0.5f, BandLimitedWave::pdToLen(vco_inc), BandLimitedWave::Waveform::BLTriangle) * 0.5f; break; case VcoShape::BLMoog: - vco_k = BandLimitedWave::oscillate( vco_c + 0.5f, BandLimitedWave::pdToLen( vco_inc ), BandLimitedWave::Waveform::BLMoog ); + vco_k = vco_inc == 0. ? 0. : BandLimitedWave::oscillate(vco_c + 0.5f, BandLimitedWave::pdToLen(vco_inc), BandLimitedWave::Waveform::BLMoog); break; } @@ -607,7 +620,7 @@ int Lb302Synth::process(sampleFrame *outbuf, const int size) #ifdef LB_FILTERED //samp = vcf->process(vco_k)*2.0*vca_a; //samp = vcf->process(vco_k)*2.0; - samp = filter->process(vco_k) * vca_a; + float samp = filter->process(vco_k) * vca_a; //printf("%f %d\n", vco_c, sample_cnt); @@ -635,11 +648,11 @@ int Lb302Synth::process(sampleFrame *outbuf, const int size) // Handle Envelope if(vca_mode==VcaMode::Attack) { vca_a+=(vca_a0-vca_a)*vca_attack; - if(sample_cnt>=0.5*Engine::audioEngine()->processingSampleRate()) + if(sample_cnt>=0.5*Engine::audioEngine()->outputSampleRate()) vca_mode = VcaMode::Idle; } else if(vca_mode == VcaMode::Decay) { - vca_a *= vca_decay; + vca_a *= decay; // the following line actually speeds up processing if(vca_a < (1/65536.0)) { diff --git a/plugins/Lb302/Lb302.h b/plugins/Lb302/Lb302.h index 237a3f3f8..2be9e9567 100644 --- a/plugins/Lb302/Lb302.h +++ b/plugins/Lb302/Lb302.h @@ -163,16 +163,6 @@ public: QString nodeName() const override; - Flags flags() const override - { - return Flag::IsSingleStreamed; - } - - f_cnt_t desiredReleaseFrames() const override - { - return 0; //4048; - } - gui::PluginView* instantiateView( QWidget * _parent ) override; private: @@ -230,7 +220,6 @@ private: int vcf_envpos; // Update counter. Updates when >= ENVINC float vca_attack, // Amp attack - vca_decay, // Amp decay vca_a0, // Initial amplifier coefficient vca_a; // Amplifier coefficient. diff --git a/plugins/Lv2Effect/Lv2Effect.cpp b/plugins/Lv2Effect/Lv2Effect.cpp index 9c21b3f2a..eef6305cc 100644 --- a/plugins/Lv2Effect/Lv2Effect.cpp +++ b/plugins/Lv2Effect/Lv2Effect.cpp @@ -24,6 +24,7 @@ #include "Lv2Effect.h" +#include #include "Lv2SubPluginFeatures.h" @@ -109,9 +110,12 @@ extern "C" PLUGIN_EXPORT Plugin *lmms_plugin_main(Model *_parent, void *_data) { using KeyType = Plugin::Descriptor::SubPluginFeatures::Key; - auto eff = new Lv2Effect(_parent, static_cast(_data)); - if (!eff->isValid()) { delete eff; eff = nullptr; } - return eff; + try { + return new Lv2Effect(_parent, static_cast(_data)); + } catch (const std::runtime_error& e) { + qCritical() << e.what(); + return nullptr; + } } } diff --git a/plugins/Lv2Effect/Lv2Effect.h b/plugins/Lv2Effect/Lv2Effect.h index 3bcded355..a28182132 100644 --- a/plugins/Lv2Effect/Lv2Effect.h +++ b/plugins/Lv2Effect/Lv2Effect.h @@ -41,8 +41,6 @@ public: initialization */ Lv2Effect(Model* parent, const Descriptor::SubPluginFeatures::Key* _key); - //! Must be checked after ctor or reload - bool isValid() const { return m_controls.isValid(); } bool processAudioBuffer( sampleFrame* buf, const fpp_t frames ) override; EffectControls* controls() override { return &m_controls; } diff --git a/plugins/Lv2Effect/Lv2FxControlDialog.cpp b/plugins/Lv2Effect/Lv2FxControlDialog.cpp index 5265cb181..73890937c 100644 --- a/plugins/Lv2Effect/Lv2FxControlDialog.cpp +++ b/plugins/Lv2Effect/Lv2FxControlDialog.cpp @@ -72,4 +72,13 @@ void Lv2FxControlDialog::modelChanged() } + + +void Lv2FxControlDialog::hideEvent(QHideEvent *event) +{ + closeHelpWindow(); + QWidget::hideEvent(event); +} + + } // namespace lmms::gui diff --git a/plugins/Lv2Effect/Lv2FxControlDialog.h b/plugins/Lv2Effect/Lv2FxControlDialog.h index 45c14c2c0..f38c0364b 100644 --- a/plugins/Lv2Effect/Lv2FxControlDialog.h +++ b/plugins/Lv2Effect/Lv2FxControlDialog.h @@ -46,6 +46,7 @@ public: private: Lv2FxControls *lv2Controls(); void modelChanged() final; + void hideEvent(QHideEvent *event) override; }; diff --git a/plugins/Lv2Effect/Lv2FxControls.cpp b/plugins/Lv2Effect/Lv2FxControls.cpp index 3ec7dbe23..72c387ba7 100644 --- a/plugins/Lv2Effect/Lv2FxControls.cpp +++ b/plugins/Lv2Effect/Lv2FxControls.cpp @@ -38,11 +38,8 @@ Lv2FxControls::Lv2FxControls(class Lv2Effect *effect, const QString& uri) : EffectControls(effect), Lv2ControlBase(this, uri) { - if (isValid()) - { - connect(Engine::audioEngine(), &AudioEngine::sampleRateChanged, - this, [this](){onSampleRateChanged();}); - } + connect(Engine::audioEngine(), &AudioEngine::sampleRateChanged, + this, &Lv2FxControls::onSampleRateChanged); } diff --git a/plugins/Lv2Instrument/Lv2Instrument.cpp b/plugins/Lv2Instrument/Lv2Instrument.cpp index 32f81d23c..766790cc1 100644 --- a/plugins/Lv2Instrument/Lv2Instrument.cpp +++ b/plugins/Lv2Instrument/Lv2Instrument.cpp @@ -73,22 +73,25 @@ Plugin::Descriptor PLUGIN_EXPORT lv2instrument_plugin_descriptor = Lv2Instrument::Lv2Instrument(InstrumentTrack *instrumentTrackArg, Descriptor::SubPluginFeatures::Key *key) : - Instrument(instrumentTrackArg, &lv2instrument_plugin_descriptor, key), + Instrument(instrumentTrackArg, &lv2instrument_plugin_descriptor, key, +#ifdef LV2_INSTRUMENT_USE_MIDI + Flag::IsSingleStreamed | Flag::IsMidiBased +#else + Flag::IsSingleStreamed +#endif + ), Lv2ControlBase(this, key->attributes["uri"]) { - if (Lv2ControlBase::isValid()) - { - clearRunningNotes(); + clearRunningNotes(); - connect(instrumentTrack()->pitchRangeModel(), SIGNAL(dataChanged()), - this, SLOT(updatePitchRange()), Qt::DirectConnection); - connect(Engine::audioEngine(), &AudioEngine::sampleRateChanged, - this, [this](){onSampleRateChanged();}); + connect(instrumentTrack()->pitchRangeModel(), SIGNAL(dataChanged()), + this, SLOT(updatePitchRange()), Qt::DirectConnection); + connect(Engine::audioEngine(), &AudioEngine::sampleRateChanged, + this, &Lv2Instrument::onSampleRateChanged); - // now we need a play-handle which cares for calling play() - auto iph = new InstrumentPlayHandle(this, instrumentTrackArg); - Engine::audioEngine()->addPlayHandle(iph); - } + // now we need a play-handle which cares for calling play() + auto iph = new InstrumentPlayHandle(this, instrumentTrackArg); + Engine::audioEngine()->addPlayHandle(iph); } @@ -134,11 +137,6 @@ void Lv2Instrument::onSampleRateChanged() -bool Lv2Instrument::isValid() const { return Lv2ControlBase::isValid(); } - - - - void Lv2Instrument::saveSettings(QDomDocument &doc, QDomElement &that) { Lv2ControlBase::saveSettings(doc, that); @@ -295,6 +293,15 @@ void Lv2InsView::dropEvent(QDropEvent *_de) +void Lv2InsView::hideEvent(QHideEvent *event) +{ + closeHelpWindow(); + QWidget::hideEvent(event); +} + + + + void Lv2InsView::modelChanged() { Lv2ViewBase::modelChanged(castModel()); @@ -312,9 +319,12 @@ extern "C" PLUGIN_EXPORT Plugin *lmms_plugin_main(Model *_parent, void *_data) { using KeyType = Plugin::Descriptor::SubPluginFeatures::Key; - auto ins = new Lv2Instrument(static_cast(_parent), static_cast(_data)); - if (!ins->isValid()) { delete ins; ins = nullptr; } - return ins; + try { + return new Lv2Instrument(static_cast(_parent), static_cast(_data)); + } catch (const std::runtime_error& e) { + qCritical() << e.what(); + return nullptr; + } } } diff --git a/plugins/Lv2Instrument/Lv2Instrument.h b/plugins/Lv2Instrument/Lv2Instrument.h index 2cd73632d..9ae48c64c 100644 --- a/plugins/Lv2Instrument/Lv2Instrument.h +++ b/plugins/Lv2Instrument/Lv2Instrument.h @@ -61,8 +61,6 @@ public: ~Lv2Instrument() override; void reload(); void onSampleRateChanged(); - //! Must be checked after ctor or reload - bool isValid() const; /* load/save @@ -86,14 +84,6 @@ public: /* misc */ - Flags flags() const override - { -#ifdef LV2_INSTRUMENT_USE_MIDI - return Flag::IsSingleStreamed | Flag::IsMidiBased; -#else - return Flag::IsSingleStreamed; -#endif - } gui::PluginView* instantiateView(QWidget *parent) override; private slots: @@ -124,6 +114,7 @@ public: protected: void dragEnterEvent(QDragEnterEvent *_dee) override; void dropEvent(QDropEvent *_de) override; + void hideEvent(QHideEvent* event) override; private: void modelChanged() override; diff --git a/plugins/MidiExport/MidiExport.cpp b/plugins/MidiExport/MidiExport.cpp index df968e36a..b3b08a0e1 100644 --- a/plugins/MidiExport/MidiExport.cpp +++ b/plugins/MidiExport/MidiExport.cpp @@ -27,6 +27,7 @@ #include "MidiExport.h" +#include "Engine.h" #include "TrackContainer.h" #include "DataFile.h" #include "InstrumentTrack.h" @@ -75,21 +76,18 @@ bool MidiExport::tryExport(const TrackContainer::TrackList &tracks, f.open(QIODevice::WriteOnly); QDataStream midiout(&f); - InstrumentTrack* instTrack; - PatternTrack* patternTrack; QDomElement element; int nTracks = 0; auto buffer = std::array{}; - uint32_t size; for (const Track* track : tracks) if (track->type() == Track::Type::Instrument) nTracks++; for (const Track* track : patternStoreTracks) if (track->type() == Track::Type::Instrument) nTracks++; // midi header MidiFile::MIDIHeader header(nTracks); - size = header.writeToBuffer(buffer.data()); + uint32_t size = header.writeToBuffer(buffer.data()); midiout.writeRawData((char *)buffer.data(), size); std::vector>> plists; @@ -107,7 +105,7 @@ bool MidiExport::tryExport(const TrackContainer::TrackList &tracks, //mtrack.addProgramChange(0, 0); mtrack.addTempo(tempo, 0); - instTrack = dynamic_cast(track); + auto instTrack = dynamic_cast(track); element = instTrack->saveState(dataFile, dataFile.content()); int base_pitch = 0; @@ -145,7 +143,7 @@ bool MidiExport::tryExport(const TrackContainer::TrackList &tracks, if (track->type() == Track::Type::Pattern) { - patternTrack = dynamic_cast(track); + auto patternTrack = dynamic_cast(track); element = patternTrack->saveState(dataFile, dataFile.content()); std::vector> plist; @@ -183,7 +181,7 @@ bool MidiExport::tryExport(const TrackContainer::TrackList &tracks, //mtrack.addProgramChange(0, 0); mtrack.addTempo(tempo, 0); - instTrack = dynamic_cast(track); + auto instTrack = dynamic_cast(track); element = instTrack->saveState(dataFile, dataFile.content()); int base_pitch = 0; @@ -279,6 +277,7 @@ void MidiExport::writeMidiClip(MidiNoteVector &midiClip, const QDomNode& n, mnote.volume = qMin(qRound(base_volume * LocaleHelper::toDouble(note.attribute("vol", "100")) * (127.0 / 200.0)), 127); mnote.time = base_time + note.attribute("pos", "0").toInt(); mnote.duration = note.attribute("len", "0").toInt(); + mnote.type = static_cast(note.attribute("type", "0").toInt()); midiClip.push_back(mnote); } } @@ -311,6 +310,7 @@ void MidiExport::writePatternClip(MidiNoteVector& src, MidiNoteVector& dst, note.pitch = srcNote.pitch; note.time = base + time; note.volume = srcNote.volume; + note.type = srcNote.type; dst.push_back(note); } } @@ -329,9 +329,9 @@ void MidiExport::processPatternNotes(MidiNoteVector& nv, int cutPos) next = cur; cur = it->time; } - if (it->duration < 0) + if (it->type == Note::Type::Step) { - it->duration = qMin(qMin(-it->duration, next - cur), cutPos - it->time); + it->duration = qMin(qMin(DefaultBeatLength, next - cur), cutPos - it->time); } } } diff --git a/plugins/MidiExport/MidiExport.h b/plugins/MidiExport/MidiExport.h index 1e355e45a..7c77c7af2 100644 --- a/plugins/MidiExport/MidiExport.h +++ b/plugins/MidiExport/MidiExport.h @@ -30,6 +30,7 @@ #include "ExportFilter.h" #include "MidiFile.hpp" +#include "Note.h" class QDomNode; @@ -46,6 +47,7 @@ struct MidiNote uint8_t pitch; int duration; uint8_t volume; + Note::Type type; inline bool operator<(const MidiNote &b) const { @@ -63,6 +65,16 @@ public: MidiExport(); ~MidiExport() override = default; + // Default Beat Length in ticks for step notes + // TODO: The beat length actually varies per note, however the method that + // calculates it (InstrumentTrack::beatLen) requires a NotePlayHandle to do + // so. While we don't figure out a way to hold the beat length of each note + // on its member variables, we will use a default value as a beat length that + // will be used as an upper limit of the midi note length. This doesn't worsen + // the current logic used for MidiExport because right now the beat length is + // not even considered during the generation of the MIDI. + static constexpr int DefaultBeatLength = 1500; + gui::PluginView* instantiateView(QWidget *) override { return nullptr; diff --git a/plugins/MidiExport/MidiFile.hpp b/plugins/MidiExport/MidiFile.hpp index 79c8dcce2..26f203cd6 100644 --- a/plugins/MidiExport/MidiFile.hpp +++ b/plugins/MidiExport/MidiFile.hpp @@ -25,6 +25,7 @@ #include #include #include +#include using std::string; using std::vector; @@ -47,8 +48,8 @@ int writeVarLength(uint32_t val, uint8_t *buffer) byte in question is the last in the stream */ int size = 0; - uint8_t result, little_endian[4]; - result = val & 0x7F; + uint8_t little_endian[4]; + uint8_t result = val & 0x7F; little_endian[size++] = result; val = val >> 7; while (val > 0) @@ -129,31 +130,37 @@ struct Event inline int writeToBuffer(uint8_t *buffer) const { - uint8_t code, fourbytes[4]; - int size=0; - switch (type) + int size = 0; + switch (type) { case NOTE_ON: - code = 0x9 << 4 | channel; + { + uint8_t code = 0x9 << 4 | channel; size += writeVarLength(time, buffer+size); buffer[size++] = code; buffer[size++] = pitch; buffer[size++] = volume; break; + } case NOTE_OFF: - code = 0x8 << 4 | channel; + { + uint8_t code = 0x8 << 4 | channel; size += writeVarLength(time, buffer+size); buffer[size++] = code; buffer[size++] = pitch; buffer[size++] = volume; break; + } case TEMPO: - code = 0xFF; + { + uint8_t code = 0xFF; size += writeVarLength(time, buffer+size); buffer[size++] = code; buffer[size++] = 0x51; buffer[size++] = 0x03; - writeBigEndian4(int(60000000.0 / tempo), fourbytes); + + std::array fourbytes; + writeBigEndian4(int(60000000.0 / tempo), fourbytes.data()); //printf("tempo of %x translates to ", tempo); /* @@ -164,23 +171,27 @@ struct Event buffer[size++] = fourbytes[2]; buffer[size++] = fourbytes[3]; break; + } case PROG_CHANGE: - code = 0xC << 4 | channel; + { + uint8_t code = 0xC << 4 | channel; size += writeVarLength(time, buffer+size); buffer[size++] = code; buffer[size++] = programNumber; break; + } case TRACK_NAME: + { size += writeVarLength(time, buffer+size); buffer[size++] = 0xFF; buffer[size++] = 0x03; size += writeVarLength(trackName.size(), buffer+size); trackName.copy((char *)(&buffer[size]), trackName.size()); size += trackName.size(); -// buffer[size++] = '\0'; -// buffer[size++] = '\0'; - break; + // buffer[size++] = '\0'; + // buffer[size++] = '\0'; + } } return size; } // writeEventsToBuffer @@ -275,7 +286,7 @@ class MIDITrack vector _events = events; std::sort(_events.begin(), _events.end()); vector::const_iterator it; - uint32_t time_last = 0, tmp; + uint32_t time_last = 0; for (it = _events.begin(); it!=_events.end(); ++it) { Event e = *it; @@ -283,7 +294,7 @@ class MIDITrack printf("error: e.time=%d time_last=%d\n", e.time, time_last); assert(false); } - tmp = e.time; + uint32_t tmp = e.time; e.time -= time_last; time_last = tmp; start += e.writeToBuffer(buffer+start); diff --git a/plugins/MidiImport/MidiImport.h b/plugins/MidiImport/MidiImport.h index 817d06be8..0870511b5 100644 --- a/plugins/MidiImport/MidiImport.h +++ b/plugins/MidiImport/MidiImport.h @@ -61,10 +61,10 @@ private: inline int readInt( int _bytes ) { - int c, value = 0; + int value = 0; do { - c = readByte(); + int c = readByte(); if( c == -1 ) { return( -1 ); diff --git a/plugins/Monstro/Monstro.cpp b/plugins/Monstro/Monstro.cpp index 2201e4ed9..469b2da21 100644 --- a/plugins/Monstro/Monstro.cpp +++ b/plugins/Monstro/Monstro.cpp @@ -30,7 +30,6 @@ #include "ComboBox.h" #include "Engine.h" #include "InstrumentTrack.h" -#include "gui_templates.h" #include "lmms_math.h" #include "interpolation.h" @@ -320,8 +319,8 @@ void MonstroSynth::renderOutput( fpp_t _frames, sampleFrame * _buf ) float rightph; float pd_l; float pd_r; - float len_l; - float len_r; + float len_l(0.); + float len_r(0.); // osc1 vars float o1l_f; @@ -503,12 +502,27 @@ void MonstroSynth::renderOutput( fpp_t _frames, sampleFrame * _buf ) if( pd_r > 0.5 ) pd_r = 1.0 - pd_r; // multi-wave DC Oscillator - len_l = BandLimitedWave::pdToLen( pd_l ); - len_r = BandLimitedWave::pdToLen( pd_r ); - if( m_counter2l > 0 ) { len_l /= m_counter2l; m_counter2l--; } - if( m_counter2r > 0 ) { len_r /= m_counter2r; m_counter2r--; } - sample_t O2L = oscillate( o2w, leftph, len_l ); - sample_t O2R = oscillate( o2w, rightph, len_r ); + sample_t O2L = 0.; + if (pd_l != 0.) + { + len_l = BandLimitedWave::pdToLen(pd_l); + if (m_counter2l > 0) + { + len_l /= m_counter2l; m_counter2l--; + } + O2L = oscillate(o2w, leftph, len_l); + } + + sample_t O2R = 0.; + if (len_r != 0.) + { + len_r = BandLimitedWave::pdToLen(pd_r); + if (m_counter2r > 0) + { + len_r /= m_counter2r; m_counter2r--; + } + O2R = oscillate(o2w, rightph, len_r); + } // modulate volume O2L *= o2lv; @@ -568,17 +582,40 @@ void MonstroSynth::renderOutput( fpp_t _frames, sampleFrame * _buf ) if( pd_r > 0.5 ) pd_r = 1.0 - pd_r; // multi-wave DC Oscillator - len_l = BandLimitedWave::pdToLen( pd_l ); - len_r = BandLimitedWave::pdToLen( pd_r ); - if( m_counter3l > 0 ) { len_l /= m_counter3l; m_counter3l--; } - if( m_counter3r > 0 ) { len_r /= m_counter3r; m_counter3r--; } - // sub-osc 1 - sample_t O3AL = oscillate( o3w1, leftph, len_l ); - sample_t O3AR = oscillate( o3w1, rightph, len_r ); + sample_t O3AL = 0.; + sample_t O3AR = 0.; // multi-wave DC Oscillator, sub-osc 2 - sample_t O3BL = oscillate( o3w2, leftph, len_l ); - sample_t O3BR = oscillate( o3w2, rightph, len_r ); + sample_t O3BL = 0.; + sample_t O3BR = 0.; + + if (pd_l != 0.) + { + len_l = BandLimitedWave::pdToLen(pd_l); + if (m_counter3l > 0) + { + len_l /= m_counter3l; m_counter3l--; + } + // sub-osc 1 + O3AL = oscillate(o3w1, leftph, len_l); + + // multi-wave DC Oscillator, sub-osc 2 + O3BL = oscillate(o3w2, leftph, len_l); + } + + if (pd_r != 0.) + { + len_r = BandLimitedWave::pdToLen(pd_r); + if (m_counter3r > 0) + { + len_r /= m_counter3r; m_counter3r--; + } + // sub-osc 1 + O3AR = oscillate(o3w1, rightph, len_r); + + // multi-wave DC Oscillator, sub-osc 2 + O3BR = oscillate(o3w2, rightph, len_r); + } // calc and modulate sub sub = o3sub; @@ -1289,13 +1326,12 @@ QString MonstroInstrument::nodeName() const return monstro_plugin_descriptor.name; } - -f_cnt_t MonstroInstrument::desiredReleaseFrames() const +float MonstroInstrument::desiredReleaseTimeMs() const { - return qMax( 64, qMax( m_env1_relF, m_env2_relF ) ); + const auto maxEnvelope = std::max(m_env1_rel, m_env2_rel); + return std::max(1.5f, maxEnvelope); } - gui::PluginView* MonstroInstrument::instantiateView( QWidget * _parent ) { return( new gui::MonstroView( this, _parent ) ); @@ -1411,7 +1447,7 @@ void MonstroInstrument::updateLFOAtts() void MonstroInstrument::updateSamplerate() { - m_samplerate = Engine::audioEngine()->processingSampleRate(); + m_samplerate = Engine::audioEngine()->outputSampleRate(); m_integrator = 0.5f - ( 0.5f - INTEGRATOR ) * 44100.0f / m_samplerate; m_fmCorrection = 44100.f / m_samplerate * FM_AMOUNT; @@ -1656,7 +1692,6 @@ QWidget * MonstroView::setupOperatorsView( QWidget * _parent ) m_osc2WaveBox = new ComboBox( view ); m_osc2WaveBox -> setGeometry( 204, O2ROW + 7, 42, ComboBox::DEFAULT_HEIGHT ); - m_osc2WaveBox->setFont( pointSize<8>( m_osc2WaveBox->font() ) ); maketinyled( m_osc2SyncHButton, 212, O2ROW - 3, tr( "Hard sync oscillator 2" ) ) maketinyled( m_osc2SyncRButton, 191, O2ROW - 3, tr( "Reverse sync oscillator 2" ) ) @@ -1671,18 +1706,15 @@ QWidget * MonstroView::setupOperatorsView( QWidget * _parent ) m_osc3Wave1Box = new ComboBox( view ); m_osc3Wave1Box -> setGeometry( 160, O3ROW + 7, 42, ComboBox::DEFAULT_HEIGHT ); - m_osc3Wave1Box->setFont( pointSize<8>( m_osc3Wave1Box->font() ) ); m_osc3Wave2Box = new ComboBox( view ); m_osc3Wave2Box -> setGeometry( 204, O3ROW + 7, 42, ComboBox::DEFAULT_HEIGHT ); - m_osc3Wave2Box->setFont( pointSize<8>( m_osc3Wave2Box->font() ) ); maketinyled( m_osc3SyncHButton, 212, O3ROW - 3, tr( "Hard sync oscillator 3" ) ) maketinyled( m_osc3SyncRButton, 191, O3ROW - 3, tr( "Reverse sync oscillator 3" ) ) m_lfo1WaveBox = new ComboBox( view ); m_lfo1WaveBox -> setGeometry( 2, LFOROW + 7, 42, ComboBox::DEFAULT_HEIGHT ); - m_lfo1WaveBox->setFont( pointSize<8>( m_lfo1WaveBox->font() ) ); maketsknob( m_lfo1AttKnob, LFOCOL1, LFOROW, tr( "Attack" ), " ms", "lfoKnob" ) maketsknob( m_lfo1RateKnob, LFOCOL2, LFOROW, tr( "Rate" ), " ms", "lfoKnob" ) @@ -1690,7 +1722,6 @@ QWidget * MonstroView::setupOperatorsView( QWidget * _parent ) m_lfo2WaveBox = new ComboBox( view ); m_lfo2WaveBox -> setGeometry( 127, LFOROW + 7, 42, ComboBox::DEFAULT_HEIGHT ); - m_lfo2WaveBox->setFont( pointSize<8>( m_lfo2WaveBox->font() ) ); maketsknob(m_lfo2AttKnob, LFOCOL4, LFOROW, tr("Attack"), " ms", "lfoKnob") maketsknob(m_lfo2RateKnob, LFOCOL5, LFOROW, tr("Rate"), " ms", "lfoKnob") diff --git a/plugins/Monstro/Monstro.h b/plugins/Monstro/Monstro.h index 21efedaf3..0df18d5c4 100644 --- a/plugins/Monstro/Monstro.h +++ b/plugins/Monstro/Monstro.h @@ -173,7 +173,6 @@ class ComboBox; class MonstroSynth { - MM_OPERATORS public: MonstroSynth( MonstroInstrument * _i, NotePlayHandle * _nph ); virtual ~MonstroSynth() = default; @@ -367,7 +366,7 @@ public: QString nodeName() const override; - f_cnt_t desiredReleaseFrames() const override; + float desiredReleaseTimeMs() const override; gui::PluginView* instantiateView( QWidget * _parent ) override; diff --git a/plugins/MultitapEcho/MultitapEcho.cpp b/plugins/MultitapEcho/MultitapEcho.cpp index 4f5e9fdf8..c64567f9b 100644 --- a/plugins/MultitapEcho/MultitapEcho.cpp +++ b/plugins/MultitapEcho/MultitapEcho.cpp @@ -55,10 +55,10 @@ MultitapEchoEffect::MultitapEchoEffect( Model* parent, const Descriptor::SubPlug m_stages( 1 ), m_controls( this ), m_buffer( 16100.0f ), - m_sampleRate( Engine::audioEngine()->processingSampleRate() ), + m_sampleRate( Engine::audioEngine()->outputSampleRate() ), m_sampleRatio( 1.0f / m_sampleRate ) { - m_work = MM_ALLOC( Engine::audioEngine()->framesPerPeriod() ); + m_work = new sampleFrame[Engine::audioEngine()->framesPerPeriod()]; m_buffer.reset(); m_stages = static_cast( m_controls.m_stages.value() ); updateFilters( 0, 19 ); @@ -67,7 +67,7 @@ MultitapEchoEffect::MultitapEchoEffect( Model* parent, const Descriptor::SubPlug MultitapEchoEffect::~MultitapEchoEffect() { - MM_FREE( m_work ); + delete[] m_work; } diff --git a/plugins/MultitapEcho/MultitapEchoControls.cpp b/plugins/MultitapEcho/MultitapEchoControls.cpp index 19564ba8a..4df05afc6 100644 --- a/plugins/MultitapEcho/MultitapEchoControls.cpp +++ b/plugins/MultitapEcho/MultitapEchoControls.cpp @@ -172,7 +172,7 @@ void MultitapEchoControls::lengthChanged() void MultitapEchoControls::sampleRateChanged() { - m_effect->m_sampleRate = Engine::audioEngine()->processingSampleRate(); + m_effect->m_sampleRate = Engine::audioEngine()->outputSampleRate(); m_effect->m_sampleRatio = 1.0f / m_effect->m_sampleRate; m_effect->updateFilters( 0, 19 ); } diff --git a/plugins/Nes/Nes.cpp b/plugins/Nes/Nes.cpp index 47122a0c6..c5cc3b0d0 100644 --- a/plugins/Nes/Nes.cpp +++ b/plugins/Nes/Nes.cpp @@ -482,53 +482,53 @@ void NesObject::updatePitch() NesInstrument::NesInstrument( InstrumentTrack * instrumentTrack ) : Instrument( instrumentTrack, &nes_plugin_descriptor ), - m_ch1Enabled( true, this ), + m_ch1Enabled(true, this, tr("Channel 1 enable")), m_ch1Crs( 0.f, -24.f, 24.f, 1.f, this, tr( "Channel 1 coarse detune" ) ), m_ch1Volume( 15.f, 0.f, 15.f, 1.f, this, tr( "Channel 1 volume" ) ), - m_ch1EnvEnabled( false, this ), - m_ch1EnvLooped( false, this ), + m_ch1EnvEnabled(false, this, tr("Channel 1 envelope enable")), + m_ch1EnvLooped(false, this, tr("Channel 1 envelope loop")), m_ch1EnvLen( 0.f, 0.f, 15.f, 1.f, this, tr( "Channel 1 envelope length" ) ), m_ch1DutyCycle( 0, 0, 3, this, tr( "Channel 1 duty cycle" ) ), - m_ch1SweepEnabled( false, this ), + m_ch1SweepEnabled(false, this, tr("Channel 1 sweep enable")), m_ch1SweepAmt( 0.f, -7.f, 7.f, 1.f, this, tr( "Channel 1 sweep amount" ) ), m_ch1SweepRate( 0.f, 0.f, 7.f, 1.f, this, tr( "Channel 1 sweep rate" ) ), - m_ch2Enabled( true, this ), - m_ch2Crs( 0.f, -24.f, 24.f, 1.f, this, tr( "Channel 2 Coarse detune" ) ), - m_ch2Volume( 15.f, 0.f, 15.f, 1.f, this, tr( "Channel 2 Volume" ) ), + m_ch2Enabled(true, this, tr("Channel 2 enable")), + m_ch2Crs( 0.f, -24.f, 24.f, 1.f, this, tr( "Channel 2 coarse detune" ) ), + m_ch2Volume( 15.f, 0.f, 15.f, 1.f, this, tr( "Channel 2 volume" ) ), - m_ch2EnvEnabled( false, this ), - m_ch2EnvLooped( false, this ), + m_ch2EnvEnabled(false, this, tr("Channel 2 envelope enable")), + m_ch2EnvLooped(false, this, tr("Channel 2 envelope loop")), m_ch2EnvLen( 0.f, 0.f, 15.f, 1.f, this, tr( "Channel 2 envelope length" ) ), m_ch2DutyCycle( 2, 0, 3, this, tr( "Channel 2 duty cycle" ) ), - m_ch2SweepEnabled( false, this ), + m_ch2SweepEnabled(false, this, tr("Channel 2 sweep enable")), m_ch2SweepAmt( 0.f, -7.f, 7.f, 1.f, this, tr( "Channel 2 sweep amount" ) ), m_ch2SweepRate( 0.f, 0.f, 7.f, 1.f, this, tr( "Channel 2 sweep rate" ) ), //channel 3 - m_ch3Enabled( true, this ), + m_ch3Enabled(true, this, tr("Channel 3 enable")), m_ch3Crs( 0.f, -24.f, 24.f, 1.f, this, tr( "Channel 3 coarse detune" ) ), m_ch3Volume( 15.f, 0.f, 15.f, 1.f, this, tr( "Channel 3 volume" ) ), //channel 4 - m_ch4Enabled( false, this ), + m_ch4Enabled(false, this, tr("Channel 4 enable")), m_ch4Volume( 15.f, 0.f, 15.f, 1.f, this, tr( "Channel 4 volume" ) ), - m_ch4EnvEnabled( false, this ), - m_ch4EnvLooped( false, this ), + m_ch4EnvEnabled(false, this, tr("Channel 4 envelope enable")), + m_ch4EnvLooped(false, this, tr("Channel 4 envelope loop")), m_ch4EnvLen( 0.f, 0.f, 15.f, 1.f, this, tr( "Channel 4 envelope length" ) ), - m_ch4NoiseMode( false, this ), - m_ch4NoiseFreqMode( false, this ), + m_ch4NoiseMode(false, this, tr("Channel 4 noise mode")), + m_ch4NoiseFreqMode(false, this, tr("Channel 4 frequency mode")), m_ch4NoiseFreq( 0.f, 0.f, 15.f, 1.f, this, tr( "Channel 4 noise frequency" ) ), m_ch4Sweep( 0.f, -7.f, 7.f, 1.f, this, tr( "Channel 4 noise frequency sweep" ) ), - m_ch4NoiseQuantize( true, this ), + m_ch4NoiseQuantize(true, this, tr("Channel 4 quantize")), //master m_masterVol( 1.0f, 0.0f, 2.0f, 0.01f, this, tr( "Master volume" ) ), @@ -552,7 +552,7 @@ void NesInstrument::playNote( NotePlayHandle * n, sampleFrame * workingBuffer ) if (!n->m_pluginData) { - auto nes = new NesObject(this, Engine::audioEngine()->processingSampleRate(), n); + auto nes = new NesObject(this, Engine::audioEngine()->outputSampleRate(), n); n->m_pluginData = nes; } @@ -719,7 +719,6 @@ namespace gui { -QPixmap * NesInstrumentView::s_artwork = nullptr; NesInstrumentView::NesInstrumentView( Instrument * instrument, QWidget * parent ) : @@ -728,12 +727,8 @@ NesInstrumentView::NesInstrumentView( Instrument * instrument, QWidget * parent setAutoFillBackground( true ); QPalette pal; - if( s_artwork == nullptr ) - { - s_artwork = new QPixmap( PLUGIN_NAME::getIconPixmap( "artwork" ) ); - } - - pal.setBrush( backgroundRole(), *s_artwork ); + static auto s_artwork = PLUGIN_NAME::getIconPixmap("artwork"); + pal.setBrush(backgroundRole(), s_artwork); setPalette( pal ); const int KNOB_Y1 = 24; diff --git a/plugins/Nes/Nes.h b/plugins/Nes/Nes.h index 3ddf0fc9a..39e0a6719 100644 --- a/plugins/Nes/Nes.h +++ b/plugins/Nes/Nes.h @@ -31,7 +31,6 @@ #include "InstrumentView.h" #include "AutomatableModel.h" #include "PixmapButton.h" -#include "MemoryManager.h" #define makeknob( name, x, y, hint, unit, oname ) \ @@ -92,7 +91,6 @@ class NesInstrumentView; class NesObject { - MM_OPERATORS public: NesObject( NesInstrument * nes, const sample_rate_t samplerate, NotePlayHandle * nph ); virtual ~NesObject() = default; @@ -224,9 +222,9 @@ public: QString nodeName() const override; - f_cnt_t desiredReleaseFrames() const override + float desiredReleaseTimeMs() const override { - return( 8 ); + return 0.2f; } gui::PluginView* instantiateView( QWidget * parent ) override; @@ -372,7 +370,6 @@ private: Knob * m_masterVolKnob; Knob * m_vibratoKnob; - static QPixmap * s_artwork; }; diff --git a/plugins/OpulenZ/OpulenZ.cpp b/plugins/OpulenZ/OpulenZ.cpp index d90d5f343..260ba353d 100644 --- a/plugins/OpulenZ/OpulenZ.cpp +++ b/plugins/OpulenZ/OpulenZ.cpp @@ -95,7 +95,7 @@ QMutex OpulenzInstrument::emulatorMutex; const auto adlib_opadd = std::array{0x00, 0x01, 0x02, 0x08, 0x09, 0x0A, 0x10, 0x11, 0x12}; OpulenzInstrument::OpulenzInstrument( InstrumentTrack * _instrument_track ) : - Instrument( _instrument_track, &opulenz_plugin_descriptor ), + Instrument(_instrument_track, &opulenz_plugin_descriptor, nullptr, Flag::IsSingleStreamed | Flag::IsMidiBased), m_patchModel( 0, 0, 127, this, tr( "Patch" ) ), op1_a_mdl(14.0, 0.0, 15.0, 1.0, this, tr( "Op 1 attack" ) ), op1_d_mdl(14.0, 0.0, 15.0, 1.0, this, tr( "Op 1 decay" ) ), @@ -140,7 +140,7 @@ OpulenzInstrument::OpulenzInstrument( InstrumentTrack * _instrument_track ) : // Create an emulator - samplerate, 16 bit, mono emulatorMutex.lock(); - theEmulator = new CTemuopl(Engine::audioEngine()->processingSampleRate(), true, false); + theEmulator = new CTemuopl(Engine::audioEngine()->outputSampleRate(), true, false); theEmulator->init(); // Enable waveform selection theEmulator->write(0x01,0x20); @@ -231,7 +231,7 @@ OpulenzInstrument::~OpulenzInstrument() { void OpulenzInstrument::reloadEmulator() { delete theEmulator; emulatorMutex.lock(); - theEmulator = new CTemuopl(Engine::audioEngine()->processingSampleRate(), true, false); + theEmulator = new CTemuopl(Engine::audioEngine()->outputSampleRate(), true, false); theEmulator->init(); theEmulator->write(0x01,0x20); emulatorMutex.unlock(); @@ -244,14 +244,12 @@ void OpulenzInstrument::reloadEmulator() { // This shall only be called from code protected by the holy Mutex! void OpulenzInstrument::setVoiceVelocity(int voice, int vel) { - int vel_adjusted; + int vel_adjusted = !fm_mdl.value() + ? 63 - (op1_lvl_mdl.value() * vel / 127.0) + : 63 - op1_lvl_mdl.value(); + // Velocity calculation, some kind of approximation // Only calculate for operator 1 if in adding mode, don't want to change timbre - if( fm_mdl.value() == false ) { - vel_adjusted = 63 - ( op1_lvl_mdl.value() * vel/127.0) ; - } else { - vel_adjusted = 63 - op1_lvl_mdl.value(); - } theEmulator->write(0x40+adlib_opadd[voice], ( (int)op1_scale_mdl.value() & 0x03 << 6) + ( vel_adjusted & 0x3f ) ); @@ -297,66 +295,60 @@ int OpulenzInstrument::pushVoice(int v) { bool OpulenzInstrument::handleMidiEvent( const MidiEvent& event, const TimePos& time, f_cnt_t offset ) { emulatorMutex.lock(); - int key, vel, voice, tmp_pb; - switch(event.type()) { - case MidiNoteOn: - key = event.key(); - vel = event.velocity(); - - voice = popVoice(); - if( voice != OPL2_NO_VOICE ) { + int key = event.key(); + int vel = event.velocity(); + switch (event.type()) + { + case MidiNoteOn: + if (int voice = popVoice(); voice != OPL2_NO_VOICE) + { // Turn voice on, NB! the frequencies are straight by voice number, // not by the adlib_opadd table! - theEmulator->write(0xA0+voice, fnums[key] & 0xff); - theEmulator->write(0xB0+voice, 32 + ((fnums[key] & 0x1f00) >> 8) ); + theEmulator->write(0xA0 + voice, fnums[key] & 0xff); + theEmulator->write(0xB0 + voice, 32 + ((fnums[key] & 0x1f00) >> 8)); setVoiceVelocity(voice, vel); voiceNote[voice] = key; velocities[key] = vel; } - break; - case MidiNoteOff: - key = event.key(); - for(voice=0; voicewrite(0xA0+voice, fnums[key] & 0xff); - theEmulator->write(0xB0+voice, (fnums[key] & 0x1f00) >> 8 ); - voiceNote[voice] |= OPL2_VOICE_FREE; + break; + case MidiNoteOff: + for (int voice = 0; voice < OPL2_VOICES; ++voice) + { + if (voiceNote[voice] == key) + { + theEmulator->write(0xA0 + voice, fnums[key] & 0xff); + theEmulator->write(0xB0 + voice, (fnums[key] & 0x1f00) >> 8); + voiceNote[voice] |= OPL2_VOICE_FREE; pushVoice(voice); - } - } - velocities[key] = 0; - break; - case MidiKeyPressure: - key = event.key(); - vel = event.velocity(); - if( velocities[key] != 0) { - velocities[key] = vel; - } - for(voice=0; voicewrite(0xA0+v, fnums[vn] & 0xff); - theEmulator->write(0xB0+v, (playing ? 32 : 0) + ((fnums[vn] & 0x1f00) >> 8) ); - } - break; + theEmulator->write(0xA0 + v, fnums[vn] & 0xff); + theEmulator->write(0xB0 + v, (playing ? 32 : 0) + ((fnums[vn] & 0x1f00) >> 8)); + } + break; case MidiControlChange: switch (event.controllerNumber()) { case MidiControllerRegisteredParameterNumberLSB: @@ -382,7 +374,7 @@ bool OpulenzInstrument::handleMidiEvent( const MidiEvent& event, const TimePos& printf("Midi event type %d\n",event.type()); #endif break; - } + } emulatorMutex.unlock(); return true; } @@ -504,9 +496,8 @@ void OpulenzInstrument::loadPatch(const unsigned char inst[14]) { } void OpulenzInstrument::tuneEqual(int center, float Hz) { - float tmp; for(int n=0; n<128; ++n) { - tmp = Hz*pow( 2.0, ( n - center ) * ( 1.0 / 12.0 ) + pitchbend * ( 1.0 / 1200.0 ) ); + float tmp = Hz * pow(2.0, (n - center) * (1.0 / 12.0) + pitchbend * (1.0 / 1200.0)); fnums[n] = Hz2fnum( tmp ); } } diff --git a/plugins/OpulenZ/OpulenZ.h b/plugins/OpulenZ/OpulenZ.h index a3e11a6c0..a245b03ad 100644 --- a/plugins/OpulenZ/OpulenZ.h +++ b/plugins/OpulenZ/OpulenZ.h @@ -64,11 +64,6 @@ public: QString nodeName() const override; gui::PluginView* instantiateView( QWidget * _parent ) override; - Flags flags() const override - { - return Flag::IsSingleStreamed | Flag::IsMidiBased; - } - bool handleMidiEvent( const MidiEvent& event, const TimePos& time, f_cnt_t offset = 0 ) override; void play( sampleFrame * _working_buffer ) override; diff --git a/plugins/Organic/Organic.cpp b/plugins/Organic/Organic.cpp index a70da6421..e7b0cf792 100644 --- a/plugins/Organic/Organic.cpp +++ b/plugins/Organic/Organic.cpp @@ -60,7 +60,6 @@ Plugin::Descriptor PLUGIN_EXPORT organic_plugin_descriptor = } -QPixmap * gui::OrganicInstrumentView::s_artwork = nullptr; float * OrganicInstrument::s_harmonics = nullptr; /*********************************************************************** @@ -420,8 +419,8 @@ OrganicInstrumentView::OrganicInstrumentView( Instrument * _instrument, setAutoFillBackground( true ); QPalette pal; - pal.setBrush( backgroundRole(), PLUGIN_NAME::getIconPixmap( - "artwork" ) ); + static auto s_artwork = PLUGIN_NAME::getIconPixmap("artwork"); + pal.setBrush(backgroundRole(), s_artwork); setPalette( pal ); // setup knob for FX1 @@ -451,12 +450,6 @@ OrganicInstrumentView::OrganicInstrumentView( Instrument * _instrument, oi, SLOT( randomiseSettings() ) ); - if( s_artwork == nullptr ) - { - s_artwork = new QPixmap( PLUGIN_NAME::getIconPixmap( - "artwork" ) ); - } - } @@ -570,7 +563,7 @@ OscillatorObject::OscillatorObject( Model * _parent, int _index ) : m_panModel( DefaultPanning, PanningLeft, PanningRight, 1.0f, this, tr( "Osc %1 panning" ).arg( _index + 1 ) ), m_detuneModel( 0.0f, -1200.0f, 1200.0f, 1.0f, - this, tr( "Osc %1 fine detuning left" ).arg( _index + 1 ) ) + this, tr( "Osc %1 stereo detuning" ).arg( _index + 1 ) ) { } @@ -612,10 +605,10 @@ void OscillatorObject::updateDetuning() { m_detuningLeft = powf( 2.0f, OrganicInstrument::s_harmonics[ static_cast( m_harmModel.value() ) ] + (float)m_detuneModel.value() * CENT ) / - Engine::audioEngine()->processingSampleRate(); + Engine::audioEngine()->outputSampleRate(); m_detuningRight = powf( 2.0f, OrganicInstrument::s_harmonics[ static_cast( m_harmModel.value() ) ] - (float)m_detuneModel.value() * CENT ) / - Engine::audioEngine()->processingSampleRate(); + Engine::audioEngine()->outputSampleRate(); } diff --git a/plugins/Organic/Organic.h b/plugins/Organic/Organic.h index 6c53e84ec..e50550e5e 100644 --- a/plugins/Organic/Organic.h +++ b/plugins/Organic/Organic.h @@ -84,7 +84,6 @@ const float CENT = 1.0f / 1200.0f; class OscillatorObject : public Model { Q_OBJECT - MM_OPERATORS private: int m_numOscillators; IntModel m_waveShape; @@ -159,7 +158,6 @@ private: struct oscPtr { - MM_OPERATORS Oscillator * oscLeft; Oscillator * oscRight; float phaseOffsetLeft[NUM_OSCILLATORS]; @@ -196,7 +194,6 @@ private: struct OscillatorKnobs { - MM_OPERATORS OscillatorKnobs( Knob * h, Knob * v, @@ -227,7 +224,6 @@ private: int m_numOscillators; - static QPixmap * s_artwork; protected slots: void updateKnobHint(); diff --git a/plugins/Patman/Patman.cpp b/plugins/Patman/Patman.cpp index 24c54d66b..d2f4aee4e 100644 --- a/plugins/Patman/Patman.cpp +++ b/plugins/Patman/Patman.cpp @@ -153,8 +153,8 @@ void PatmanInstrument::playNote( NotePlayHandle * _n, float play_freq = hdata->tuned ? _n->frequency() : hdata->sample->frequency(); - if( hdata->sample->play( _working_buffer + offset, hdata->state, frames, - play_freq, m_loopedModel.value() ? SampleBuffer::LoopMode::On : SampleBuffer::LoopMode::Off ) ) + if (hdata->sample->play(_working_buffer + offset, hdata->state, frames, + play_freq, m_loopedModel.value() ? Sample::Loop::On : Sample::Loop::Off)) { applyRelease( _working_buffer, _n ); } @@ -170,7 +170,6 @@ void PatmanInstrument::playNote( NotePlayHandle * _n, void PatmanInstrument::deleteNotePluginData( NotePlayHandle * _n ) { auto hdata = (handle_data*)_n->m_pluginData; - sharedObject::unref( hdata->sample ); delete hdata->state; delete hdata; } @@ -299,17 +298,16 @@ PatmanInstrument::LoadError PatmanInstrument::loadPatch( SKIP_BYTES( 2 + 2 + 36 ); f_cnt_t frames; - sample_t * wave_samples; + std::unique_ptr wave_samples; if( modes & MODES_16BIT ) { frames = data_length >> 1; - wave_samples = new sample_t[frames]; + wave_samples = std::make_unique(frames); for( f_cnt_t frame = 0; frame < frames; ++frame ) { short sample; if ( fread( &sample, 2, 1, fd ) != 1 ) { - delete[] wave_samples; fclose( fd ); return( LoadError::IO ); } @@ -327,13 +325,12 @@ PatmanInstrument::LoadError PatmanInstrument::loadPatch( else { frames = data_length; - wave_samples = new sample_t[frames]; + wave_samples = std::make_unique(frames); for( f_cnt_t frame = 0; frame < frames; ++frame ) { char sample; if ( fread( &sample, 1, 1, fd ) != 1 ) { - delete[] wave_samples; fclose( fd ); return( LoadError::IO ); } @@ -356,9 +353,8 @@ PatmanInstrument::LoadError PatmanInstrument::loadPatch( } } - auto psample = new SampleBuffer(data, frames); - psample->setFrequency( root_freq / 1000.0f ); - psample->setSampleRate( sample_rate ); + auto psample = std::make_shared(data, frames, sample_rate); + psample->setFrequency(root_freq / 1000.0f); if( modes & MODES_LOOPING ) { @@ -366,9 +362,8 @@ PatmanInstrument::LoadError PatmanInstrument::loadPatch( psample->setLoopEndFrame( loop_end ); } - m_patchSamples.push_back( psample ); + m_patchSamples.push_back(psample); - delete[] wave_samples; delete[] data; } fclose( fd ); @@ -382,7 +377,6 @@ void PatmanInstrument::unloadCurrentPatch() { while( !m_patchSamples.empty() ) { - sharedObject::unref( m_patchSamples.back() ); m_patchSamples.pop_back(); } } @@ -395,7 +389,7 @@ void PatmanInstrument::selectSample( NotePlayHandle * _n ) const float freq = _n->frequency(); float min_dist = HUGE_VALF; - SampleBuffer* sample = nullptr; + std::shared_ptr sample = nullptr; for (const auto& patchSample : m_patchSamples) { @@ -412,15 +406,8 @@ void PatmanInstrument::selectSample( NotePlayHandle * _n ) auto hdata = new handle_data; hdata->tuned = m_tunedModel.value(); - if( sample ) - { - hdata->sample = sharedObject::ref( sample ); - } - else - { - hdata->sample = new SampleBuffer( nullptr, 0 ); - } - hdata->state = new SampleBuffer::handleState( _n->hasDetuningInfo() ); + hdata->sample = sample ? sample : std::make_shared(); + hdata->state = new Sample::PlaybackState(_n->hasDetuningInfo()); _n->m_pluginData = hdata; } @@ -558,7 +545,7 @@ void PatmanView::updateFilename() m_displayFilename = ""; int idx = m_pi->m_patchFile.length(); - QFontMetrics fm( pointSize<8>( font() ) ); + QFontMetrics fm(adjustedToPixelSize(font(), 8)); // simple algorithm for creating a text from the filename that // matches in the white rectangle @@ -628,7 +615,7 @@ void PatmanView::paintEvent( QPaintEvent * ) { QPainter p( this ); - p.setFont( pointSize<8>( font() ) ); + p.setFont(adjustedToPixelSize(font() ,8)); p.drawText( 8, 116, 235, 16, Qt::AlignLeft | Qt::TextSingleLine | Qt::AlignVCenter, m_displayFilename ); diff --git a/plugins/Patman/Patman.h b/plugins/Patman/Patman.h index 3a15db5f3..16b98deee 100644 --- a/plugins/Patman/Patman.h +++ b/plugins/Patman/Patman.h @@ -28,9 +28,9 @@ #include "Instrument.h" #include "InstrumentView.h" +#include "Sample.h" #include "SampleBuffer.h" #include "AutomatableModel.h" -#include "MemoryManager.h" namespace lmms { @@ -71,9 +71,9 @@ public: QString nodeName() const override; - f_cnt_t desiredReleaseFrames() const override + float desiredReleaseTimeMs() const override { - return( 128 ); + return 3.f; } gui::PluginView* instantiateView( QWidget * _parent ) override; @@ -86,14 +86,13 @@ public slots: private: struct handle_data { - MM_OPERATORS - SampleBuffer::handleState* state; + Sample::PlaybackState* state; bool tuned; - SampleBuffer* sample; + std::shared_ptr sample; }; QString m_patchFile; - QVector m_patchSamples; + QVector> m_patchSamples; BoolModel m_loopedModel; BoolModel m_tunedModel; diff --git a/plugins/ReverbSC/ReverbSC.cpp b/plugins/ReverbSC/ReverbSC.cpp index 9006f8c9f..c73e421ec 100644 --- a/plugins/ReverbSC/ReverbSC.cpp +++ b/plugins/ReverbSC/ReverbSC.cpp @@ -55,7 +55,7 @@ ReverbSCEffect::ReverbSCEffect( Model* parent, const Descriptor::SubPluginFeatur m_reverbSCControls( this ) { sp_create(&sp); - sp->sr = Engine::audioEngine()->processingSampleRate(); + sp->sr = Engine::audioEngine()->outputSampleRate(); sp_revsc_create(&revsc); sp_revsc_init(sp, revsc); @@ -63,8 +63,8 @@ ReverbSCEffect::ReverbSCEffect( Model* parent, const Descriptor::SubPluginFeatur sp_dcblock_create(&dcblk[0]); sp_dcblock_create(&dcblk[1]); - sp_dcblock_init(sp, dcblk[0], Engine::audioEngine()->currentQualitySettings().sampleRateMultiplier() ); - sp_dcblock_init(sp, dcblk[1], Engine::audioEngine()->currentQualitySettings().sampleRateMultiplier() ); + sp_dcblock_init(sp, dcblk[0], 1); + sp_dcblock_init(sp, dcblk[1], 1); } ReverbSCEffect::~ReverbSCEffect() @@ -132,7 +132,7 @@ bool ReverbSCEffect::processAudioBuffer( sampleFrame* buf, const fpp_t frames ) void ReverbSCEffect::changeSampleRate() { // Change sr variable in Soundpipe. does not need to be destroyed - sp->sr = Engine::audioEngine()->processingSampleRate(); + sp->sr = Engine::audioEngine()->outputSampleRate(); mutex.lock(); sp_revsc_destroy(&revsc); @@ -145,8 +145,8 @@ void ReverbSCEffect::changeSampleRate() sp_dcblock_create(&dcblk[0]); sp_dcblock_create(&dcblk[1]); - sp_dcblock_init(sp, dcblk[0], Engine::audioEngine()->currentQualitySettings().sampleRateMultiplier() ); - sp_dcblock_init(sp, dcblk[1], Engine::audioEngine()->currentQualitySettings().sampleRateMultiplier() ); + sp_dcblock_init(sp, dcblk[0], 1); + sp_dcblock_init(sp, dcblk[1], 1); mutex.unlock(); } diff --git a/plugins/Sf2Player/CMakeLists.txt b/plugins/Sf2Player/CMakeLists.txt index 4679a94bd..464b8bd3e 100644 --- a/plugins/Sf2Player/CMakeLists.txt +++ b/plugins/Sf2Player/CMakeLists.txt @@ -1,13 +1,10 @@ if(LMMS_HAVE_FLUIDSYNTH) + SET(CMAKE_AUTOUIC ON) 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/PatchesDialog.cpp b/plugins/Sf2Player/PatchesDialog.cpp index c3ffe2d29..8a32a10ce 100644 --- a/plugins/Sf2Player/PatchesDialog.cpp +++ b/plugins/Sf2Player/PatchesDialog.cpp @@ -328,7 +328,7 @@ void PatchesDialog::bankChanged () fluid_preset_t preset; fluid_preset_t *pCurPreset = &preset; #else - fluid_preset_t *pCurPreset; + fluid_preset_t *pCurPreset = nullptr; #endif while ((pCurPreset = fluid_sfont_iteration_next_wrapper(pSoundFont, pCurPreset))) { int iBank = fluid_preset_get_banknum(pCurPreset); diff --git a/plugins/Sf2Player/Sf2Player.cpp b/plugins/Sf2Player/Sf2Player.cpp index 79bd4b976..dd544dcf4 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 @@ public: //! Create a reference to the voice currently pointed at by `voice`. explicit FluidVoice(fluid_voice_t* voice) : m_voice{voice}, - m_id{fluid_voice_get_id(voice)} + 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 @@ public: private: fluid_voice_t* m_voice; unsigned int m_id; + float m_coarseTune; }; struct Sf2PluginData @@ -116,14 +121,8 @@ struct Sf2PluginData -// Static map of current sfonts -QMap Sf2Instrument::s_fonts; -QMutex Sf2Instrument::s_fontsMutex; - - - Sf2Instrument::Sf2Instrument( InstrumentTrack * _instrument_track ) : - Instrument( _instrument_track, &sf2player_plugin_descriptor ), + Instrument(_instrument_track, &sf2player_plugin_descriptor, nullptr, Flag::IsSingleStreamed), m_srcState( nullptr ), m_synth(nullptr), m_font( nullptr ), @@ -370,31 +369,12 @@ void Sf2Instrument::freeFont() { m_synthMutex.lock(); - if ( m_font != nullptr ) + if (m_font != nullptr) { - s_fontsMutex.lock(); - --(m_font->refCount); - - // No more references - if( m_font->refCount <= 0 ) - { - qDebug() << "Really deleting " << m_filename; - - fluid_synth_sfunload( m_synth, m_fontId, true ); - s_fonts.remove( m_filename ); - delete m_font; - } - // Just remove our reference - else - { - qDebug() << "un-referencing " << m_filename; - - fluid_synth_remove_sfont( m_synth, m_font->fluidFont ); - } - s_fontsMutex.unlock(); - + fluid_synth_sfunload(m_synth, m_fontId, true); m_font = nullptr; } + m_synthMutex.unlock(); } @@ -408,49 +388,29 @@ void Sf2Instrument::openFile( const QString & _sf2File, bool updateTrackName ) char * sf2Ascii = qstrdup( qPrintable( PathUtil::toAbsolute( _sf2File ) ) ); QString relativePath = PathUtil::toShortestRelative( _sf2File ); - // free reference to soundfont if one is selected + // free the soundfont if one is selected freeFont(); m_synthMutex.lock(); - s_fontsMutex.lock(); - // Increment Reference - if( s_fonts.contains( relativePath ) ) + bool loaded = false; + if (fluid_is_soundfont(sf2Ascii)) { - qDebug() << "Using existing reference to " << relativePath; + m_fontId = fluid_synth_sfload(m_synth, sf2Ascii, true); - m_font = s_fonts[ relativePath ]; - - m_font->refCount++; - - m_fontId = fluid_synth_add_sfont( m_synth, m_font->fluidFont ); - } - - // Add to map, if doesn't exist. - else - { - bool loaded = false; - if( fluid_is_soundfont( sf2Ascii ) ) + if (fluid_synth_sfcount(m_synth) > 0) { - m_fontId = fluid_synth_sfload( m_synth, sf2Ascii, true ); - - if( fluid_synth_sfcount( m_synth ) > 0 ) - { - // Grab this sf from the top of the stack and add to list - m_font = new Sf2Font( fluid_synth_get_sfont( m_synth, 0 ) ); - s_fonts.insert( relativePath, m_font ); - loaded = true; - } - } - - if(!loaded) - { - collectErrorForUI( Sf2Instrument::tr( "A soundfont %1 could not be loaded." ). - arg( QFileInfo( _sf2File ).baseName() ) ); + // Grab this sf from the top of the stack and add to list + m_font = fluid_synth_get_sfont(m_synth, 0); + loaded = true; } } - s_fontsMutex.unlock(); + if (!loaded) + { + collectErrorForUI(Sf2Instrument::tr("A soundfont %1 could not be loaded.").arg(QFileInfo(_sf2File).baseName())); + } + m_synthMutex.unlock(); if( m_fontId >= 0 ) @@ -614,7 +574,7 @@ void Sf2Instrument::reloadSynth() double tempRate; // Set & get, returns the true sample rate - fluid_settings_setnum( m_settings, (char *) "synth.sample-rate", Engine::audioEngine()->processingSampleRate() ); + fluid_settings_setnum( m_settings, (char *) "synth.sample-rate", Engine::audioEngine()->outputSampleRate() ); fluid_settings_getnum( m_settings, (char *) "synth.sample-rate", &tempRate ); m_internalSampleRate = static_cast( tempRate ); @@ -622,12 +582,12 @@ void Sf2Instrument::reloadSynth() { // Now, delete the old one and replace m_synthMutex.lock(); - fluid_synth_remove_sfont( m_synth, m_font->fluidFont ); + fluid_synth_remove_sfont( m_synth, m_font ); delete_fluid_synth( m_synth ); // New synth m_synth = new_fluid_synth( m_settings ); - m_fontId = fluid_synth_add_sfont( m_synth, m_font->fluidFont ); + m_fontId = fluid_synth_add_sfont( m_synth, m_font ); m_synthMutex.unlock(); // synth program change (set bank and patch) @@ -656,7 +616,7 @@ void Sf2Instrument::reloadSynth() fluid_synth_set_interp_method( m_synth, -1, FLUID_INTERP_DEFAULT ); } m_synthMutex.unlock(); - if( m_internalSampleRate < Engine::audioEngine()->processingSampleRate() ) + if( m_internalSampleRate < Engine::audioEngine()->outputSampleRate() ) { m_synthMutex.lock(); if( m_srcState != nullptr ) @@ -740,7 +700,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); } } @@ -912,10 +872,10 @@ void Sf2Instrument::renderFrames( f_cnt_t frames, sampleFrame * buf ) { m_synthMutex.lock(); fluid_synth_get_gain(m_synth); // This flushes voice updates as a side effect - if( m_internalSampleRate < Engine::audioEngine()->processingSampleRate() && + if( m_internalSampleRate < Engine::audioEngine()->outputSampleRate() && m_srcState != nullptr ) { - const fpp_t f = frames * m_internalSampleRate / Engine::audioEngine()->processingSampleRate(); + const fpp_t f = frames * m_internalSampleRate / Engine::audioEngine()->outputSampleRate(); #ifdef __GNUC__ sampleFrame tmp[f]; #else diff --git a/plugins/Sf2Player/Sf2Player.h b/plugins/Sf2Player/Sf2Player.h index bd7fa1b81..ec7ace47f 100644 --- a/plugins/Sf2Player/Sf2Player.h +++ b/plugins/Sf2Player/Sf2Player.h @@ -34,7 +34,6 @@ #include "Instrument.h" #include "InstrumentView.h" #include "LcdSpinBox.h" -#include "MemoryManager.h" class QLabel; @@ -81,16 +80,6 @@ public: QString nodeName() const override; - f_cnt_t desiredReleaseFrames() const override - { - return 0; - } - - Flags flags() const override - { - return Flag::IsSingleStreamed; - } - gui::PluginView* instantiateView( QWidget * _parent ) override; QString getCurrentPatchName(); @@ -114,16 +103,12 @@ public slots: void updateTuning(); private: - static QMutex s_fontsMutex; - static QMap s_fonts; - static int (* s_origFree)( fluid_sfont_t * ); - SRC_STATE * m_srcState; fluid_settings_t* m_settings; fluid_synth_t* m_synth; - Sf2Font* m_font; + fluid_sfont_t* m_font; int m_fontId; QString m_filename; @@ -177,22 +162,6 @@ signals: } ; - -// A soundfont in our font-map -class Sf2Font -{ - MM_OPERATORS -public: - Sf2Font( fluid_sfont_t * f ) : - fluidFont( f ), - refCount( 1 ) - {}; - - fluid_sfont_t * fluidFont; - int refCount; -}; - - namespace gui { diff --git a/plugins/Sfxr/Sfxr.cpp b/plugins/Sfxr/Sfxr.cpp index e79b8e2ad..3817706fc 100644 --- a/plugins/Sfxr/Sfxr.cpp +++ b/plugins/Sfxr/Sfxr.cpp @@ -444,7 +444,7 @@ QString SfxrInstrument::nodeName() const void SfxrInstrument::playNote( NotePlayHandle * _n, sampleFrame * _working_buffer ) { - float currentSampleRate = Engine::audioEngine()->processingSampleRate(); + float currentSampleRate = Engine::audioEngine()->outputSampleRate(); fpp_t frameNum = _n->framesLeftForCurrentPeriod(); const f_cnt_t offset = _n->noteOffset(); diff --git a/plugins/Sfxr/Sfxr.h b/plugins/Sfxr/Sfxr.h index edec0ba6f..8af8984c9 100644 --- a/plugins/Sfxr/Sfxr.h +++ b/plugins/Sfxr/Sfxr.h @@ -31,7 +31,6 @@ #include "AutomatableModel.h" #include "Instrument.h" #include "InstrumentView.h" -#include "MemoryManager.h" namespace lmms { @@ -78,7 +77,6 @@ class SfxrInstrumentView; class SfxrSynth { - MM_OPERATORS public: SfxrSynth( const SfxrInstrument * s ); virtual ~SfxrSynth() = default; diff --git a/plugins/Sid/SidInstrument.cpp b/plugins/Sid/SidInstrument.cpp index 7f9edf13f..b745075aa 100644 --- a/plugins/Sid/SidInstrument.cpp +++ b/plugins/Sid/SidInstrument.cpp @@ -221,44 +221,35 @@ QString SidInstrument::nodeName() const } - - -f_cnt_t SidInstrument::desiredReleaseFrames() const +float SidInstrument::desiredReleaseTimeMs() const { - const float samplerate = Engine::audioEngine()->processingSampleRate(); int maxrel = 0; for (const auto& voice : m_voice) { - if( maxrel < voice->m_releaseModel.value() ) - maxrel = (int)voice->m_releaseModel.value(); + maxrel = std::max(maxrel, static_cast(voice->m_releaseModel.value())); } - return f_cnt_t( float(relTime[maxrel])*samplerate/1000.0 ); + return computeReleaseTimeMsByFrameCount(relTime[maxrel]); } - - static int sid_fillbuffer(unsigned char* sidreg, reSID::SID *sid, int tdelta, short *ptr, int samples) { - int tdelta2; - int result; int total = 0; - int c; // customly added int residdelay = 0; int badline = rand() % NUMSIDREGS; - for (c = 0; c < NUMSIDREGS; c++) + for (int c = 0; c < NUMSIDREGS; c++) { unsigned char o = sidorder[c]; // Extra delay for loading the waveform (and mt_chngate,x) if ((o == 4) || (o == 11) || (o == 18)) { - tdelta2 = SIDWAVEDELAY; - result = sid->clock(tdelta2, ptr, samples); + int tdelta2 = SIDWAVEDELAY; + int result = sid->clock(tdelta2, ptr, samples); total += result; ptr += result; samples -= result; @@ -268,8 +259,8 @@ static int sid_fillbuffer(unsigned char* sidreg, reSID::SID *sid, int tdelta, sh // Possible random badline delay once per writing if ((badline == c) && (residdelay)) { - tdelta2 = residdelay; - result = sid->clock(tdelta2, ptr, samples); + int tdelta2 = residdelay; + int result = sid->clock(tdelta2, ptr, samples); total += result; ptr += result; samples -= result; @@ -278,14 +269,14 @@ static int sid_fillbuffer(unsigned char* sidreg, reSID::SID *sid, int tdelta, sh sid->write(o, sidreg[o]); - tdelta2 = SIDWRITEDELAY; - result = sid->clock(tdelta2, ptr, samples); + int tdelta2 = SIDWRITEDELAY; + int result = sid->clock(tdelta2, ptr, samples); total += result; ptr += result; samples -= result; tdelta -= SIDWRITEDELAY; } - result = sid->clock(tdelta, ptr, samples); + int result = sid->clock(tdelta, ptr, samples); total += result; return total; @@ -298,7 +289,7 @@ void SidInstrument::playNote( NotePlayHandle * _n, sampleFrame * _working_buffer ) { const int clockrate = C64_PAL_CYCLES_PER_SEC; - const int samplerate = Engine::audioEngine()->processingSampleRate(); + const int samplerate = Engine::audioEngine()->outputSampleRate(); if (!_n->m_pluginData) { diff --git a/plugins/Sid/SidInstrument.h b/plugins/Sid/SidInstrument.h index 53efa8942..8d5af8df0 100644 --- a/plugins/Sid/SidInstrument.h +++ b/plugins/Sid/SidInstrument.h @@ -48,7 +48,6 @@ class PixmapButton; class VoiceObject : public Model { Q_OBJECT - MM_OPERATORS public: enum class WaveForm { Square = 0, @@ -112,7 +111,7 @@ public: QString nodeName() const override; - f_cnt_t desiredReleaseFrames() const override; + float desiredReleaseTimeMs() const override; gui::PluginView* instantiateView( QWidget * _parent ) override; diff --git a/plugins/SlicerT/CMakeLists.txt b/plugins/SlicerT/CMakeLists.txt new file mode 100644 index 000000000..49a80ca03 --- /dev/null +++ b/plugins/SlicerT/CMakeLists.txt @@ -0,0 +1,10 @@ +INCLUDE(BuildPlugin) + +INCLUDE_DIRECTORIES(${FFTW3F_INCLUDE_DIRS}) +LINK_LIBRARIES(${FFTW3F_LIBRARIES}) + +INCLUDE_DIRECTORIES(${SAMPLERATE_INCLUDE_DIRS}) +LINK_DIRECTORIES(${SAMPLERATE_LIBRARY_DIRS}) +LINK_LIBRARIES(${SAMPLERATE_LIBRARIES}) + +BUILD_PLUGIN(slicert SlicerT.cpp SlicerT.h SlicerTView.cpp SlicerTView.h SlicerTWaveform.cpp SlicerTWaveform.h MOCFILES SlicerT.h SlicerTView.h SlicerTWaveform.h EMBEDDED_RESOURCES "${CMAKE_CURRENT_SOURCE_DIR}/*.png") \ No newline at end of file diff --git a/plugins/SlicerT/SlicerT.cpp b/plugins/SlicerT/SlicerT.cpp new file mode 100644 index 000000000..493dde6c3 --- /dev/null +++ b/plugins/SlicerT/SlicerT.cpp @@ -0,0 +1,411 @@ +/* + * SlicerT.cpp - simple slicer plugin + * + * Copyright (c) 2023 Daniel Kauss Serna + * + * 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 "SlicerT.h" + +#include +#include +#include + +#include "Engine.h" +#include "InstrumentTrack.h" +#include "PathUtil.h" +#include "SampleLoader.h" +#include "Song.h" +#include "embed.h" +#include "lmms_constants.h" +#include "plugin_export.h" + +namespace lmms { + +extern "C" { +Plugin::Descriptor PLUGIN_EXPORT slicert_plugin_descriptor = { + LMMS_STRINGIFY(PLUGIN_NAME), + "SlicerT", + QT_TRANSLATE_NOOP("PluginBrowser", "Basic Slicer"), + "Daniel Kauss Serna ", + 0x0100, + Plugin::Type::Instrument, + new PluginPixmapLoader("logo"), + nullptr, + nullptr, +}; +} // end extern + +// ################################# SlicerT #################################### + +SlicerT::SlicerT(InstrumentTrack* instrumentTrack) + : Instrument(instrumentTrack, &slicert_plugin_descriptor) + , m_noteThreshold(0.6f, 0.0f, 2.0f, 0.01f, this, tr("Note threshold")) + , m_fadeOutFrames(10.0f, 0.0f, 100.0f, 0.1f, this, tr("FadeOut")) + , m_originalBPM(1, 1, 999, this, tr("Original bpm")) + , m_sliceSnap(this, tr("Slice snap")) + , m_enableSync(false, this, tr("BPM sync")) + , m_originalSample() + , m_parentTrack(instrumentTrack) +{ + m_sliceSnap.addItem("Off"); + m_sliceSnap.addItem("1/1"); + m_sliceSnap.addItem("1/2"); + m_sliceSnap.addItem("1/4"); + m_sliceSnap.addItem("1/8"); + m_sliceSnap.addItem("1/16"); + m_sliceSnap.addItem("1/32"); + m_sliceSnap.setValue(0); +} + +void SlicerT::playNote(NotePlayHandle* handle, sampleFrame* workingBuffer) +{ + if (m_originalSample.sampleSize() <= 1) { return; } + + int noteIndex = handle->key() - m_parentTrack->baseNote(); + const fpp_t frames = handle->framesLeftForCurrentPeriod(); + const f_cnt_t offset = handle->noteOffset(); + const int bpm = Engine::getSong()->getTempo(); + const float pitchRatio = 1 / std::exp2(m_parentTrack->pitchModel()->value() / 1200); + + float speedRatio = static_cast(m_originalBPM.value()) / bpm; + if (!m_enableSync.value()) { speedRatio = 1; } + speedRatio *= pitchRatio; + speedRatio *= Engine::audioEngine()->outputSampleRate() / static_cast(m_originalSample.sampleRate()); + + float sliceStart, sliceEnd; + if (noteIndex == 0) // full sample at base note + { + sliceStart = 0; + sliceEnd = 1; + } + else if (noteIndex > 0 && noteIndex < m_slicePoints.size()) + { + noteIndex -= 1; + sliceStart = m_slicePoints[noteIndex]; + sliceEnd = m_slicePoints[noteIndex + 1]; + } + else + { + emit isPlaying(-1, 0, 0); + return; + } + + if (!handle->m_pluginData) { handle->m_pluginData = new PlaybackState(sliceStart); } + auto playbackState = static_cast(handle->m_pluginData); + + float noteDone = playbackState->noteDone(); + float noteLeft = sliceEnd - noteDone; + + if (noteLeft > 0) + { + int noteFrame = noteDone * m_originalSample.sampleSize(); + + SRC_STATE* resampleState = playbackState->resamplingState(); + SRC_DATA resampleData; + resampleData.data_in = (m_originalSample.data() + noteFrame)->data(); + resampleData.data_out = (workingBuffer + offset)->data(); + resampleData.input_frames = noteLeft * m_originalSample.sampleSize(); + resampleData.output_frames = frames; + resampleData.src_ratio = speedRatio; + + src_process(resampleState, &resampleData); + + float nextNoteDone = noteDone + frames * (1.0f / speedRatio) / m_originalSample.sampleSize(); + playbackState->setNoteDone(nextNoteDone); + + // exponential fade out, applyRelease() not used since it extends the note length + int fadeOutFrames = m_fadeOutFrames.value() / 1000.0f * Engine::audioEngine()->outputSampleRate(); + int noteFramesLeft = noteLeft * m_originalSample.sampleSize() * speedRatio; + for (int i = 0; i < frames; i++) + { + float fadeValue = static_cast(noteFramesLeft - i) / fadeOutFrames; + fadeValue = std::clamp(fadeValue, 0.0f, 1.0f); + fadeValue = cosinusInterpolate(0, 1, fadeValue); + + workingBuffer[i + offset][0] *= fadeValue; + workingBuffer[i + offset][1] *= fadeValue; + } + + emit isPlaying(noteDone, sliceStart, sliceEnd); + } + else { emit isPlaying(-1, 0, 0); } +} + +void SlicerT::deleteNotePluginData(NotePlayHandle* handle) +{ + delete static_cast(handle->m_pluginData); +} + +// uses the spectral flux to determine the change in magnitude +// resources: +// http://www.iro.umontreal.ca/~pift6080/H09/documents/papers/bello_onset_tutorial.pdf +void SlicerT::findSlices() +{ + if (m_originalSample.sampleSize() <= 1) { return; } + m_slicePoints = {}; + + const int windowSize = 512; + const float minBeatLength = 0.05f; // in seconds, ~ 1/4 length at 220 bpm + + int sampleRate = m_originalSample.sampleRate(); + int minDist = sampleRate * minBeatLength; + + float maxMag = -1; + std::vector singleChannel(m_originalSample.sampleSize(), 0); + for (int i = 0; i < m_originalSample.sampleSize(); i++) + { + singleChannel[i] = (m_originalSample.data()[i][0] + m_originalSample.data()[i][1]) / 2; + maxMag = std::max(maxMag, singleChannel[i]); + } + + // normalize and find 0 crossings + std::vector zeroCrossings; + float lastValue = 1; + for (int i = 0; i < singleChannel.size(); i++) + { + singleChannel[i] /= maxMag; + if (sign(lastValue) != sign(singleChannel[i])) + { + zeroCrossings.push_back(i); + lastValue = singleChannel[i]; + } + } + + std::vector prevMags(windowSize / 2, 0); + std::vector fftIn(windowSize, 0); + std::array fftOut; + + fftwf_plan fftPlan = fftwf_plan_dft_r2c_1d(windowSize, fftIn.data(), fftOut.data(), FFTW_MEASURE); + + int lastPoint = -minDist - 1; // to always store 0 first + float spectralFlux = 0; + float prevFlux = 1E-10; // small value, no divison by zero + + for (int i = 0; i < singleChannel.size() - windowSize; i += windowSize) + { + // fft + std::copy_n(singleChannel.data() + i, windowSize, fftIn.data()); + fftwf_execute(fftPlan); + + // calculate spectral flux in regard to last window + for (int j = 0; j < windowSize / 2; j++) // only use niquistic frequencies + { + float real = fftOut[j][0]; + float imag = fftOut[j][1]; + float magnitude = std::sqrt(real * real + imag * imag); + + // using L2-norm (euclidean distance) + float diff = std::sqrt(std::pow(magnitude - prevMags[j], 2)); + spectralFlux += diff; + + prevMags[j] = magnitude; + } + + if (spectralFlux / prevFlux > 1.0f + m_noteThreshold.value() && i - lastPoint > minDist) + { + m_slicePoints.push_back(i); + lastPoint = i; + if (m_slicePoints.size() > 128) { break; } // no more keys on the keyboard + } + + prevFlux = spectralFlux; + spectralFlux = 1E-10; // again for no divison by zero + } + + m_slicePoints.push_back(m_originalSample.sampleSize()); + + for (float& sliceValue : m_slicePoints) + { + auto closestZeroCrossing = std::lower_bound(zeroCrossings.begin(), zeroCrossings.end(), sliceValue); + if (closestZeroCrossing == zeroCrossings.end()) { continue; } + if (std::abs(sliceValue - *closestZeroCrossing) < windowSize) { sliceValue = *closestZeroCrossing; } + } + + float beatsPerMin = m_originalBPM.value() / 60.0f; + float samplesPerBeat = m_originalSample.sampleRate() / beatsPerMin * 4.0f; + int noteSnap = m_sliceSnap.value(); + int sliceLock = samplesPerBeat / std::exp2(noteSnap + 1); + if (noteSnap == 0) { sliceLock = 1; } + for (float& sliceValue : m_slicePoints) + { + sliceValue += sliceLock / 2; + sliceValue -= static_cast(sliceValue) % sliceLock; + } + + m_slicePoints.erase(std::unique(m_slicePoints.begin(), m_slicePoints.end()), m_slicePoints.end()); + + for (float& sliceIndex : m_slicePoints) + { + sliceIndex /= m_originalSample.sampleSize(); + } + + m_slicePoints[0] = 0; + m_slicePoints[m_slicePoints.size() - 1] = 1; + + emit dataChanged(); +} + +// find the bpm of the sample by assuming its in 4/4 time signature , +// and lies in the 100 - 200 bpm range +void SlicerT::findBPM() +{ + if (m_originalSample.sampleSize() <= 1) { return; } + + float sampleRate = m_originalSample.sampleRate(); + float totalFrames = m_originalSample.sampleSize(); + float sampleLength = totalFrames / sampleRate; + + float bpmEstimate = 240.0f / sampleLength; + + while (bpmEstimate < 100) + { + bpmEstimate *= 2; + } + + while (bpmEstimate > 200) + { + bpmEstimate /= 2; + } + + m_originalBPM.setValue(bpmEstimate); + m_originalBPM.setInitValue(bpmEstimate); +} + +std::vector SlicerT::getMidi() +{ + std::vector outputNotes; + + float speedRatio = static_cast(m_originalBPM.value()) / Engine::getSong()->getTempo(); + float outFrames = m_originalSample.sampleSize() * speedRatio; + + float framesPerTick = Engine::framesPerTick(); + float totalTicks = outFrames / framesPerTick; + float lastEnd = 0; + + for (int i = 0; i < m_slicePoints.size() - 1; i++) + { + float sliceStart = lastEnd; + float sliceEnd = totalTicks * m_slicePoints[i + 1]; + + Note sliceNote = Note(); + sliceNote.setKey(i + m_parentTrack->baseNote() + 1); + sliceNote.setPos(sliceStart); + sliceNote.setLength(sliceEnd - sliceStart + 1); // + 1 so that the notes allign + outputNotes.push_back(sliceNote); + + lastEnd = sliceEnd; + } + + return outputNotes; +} + +void SlicerT::updateFile(QString file) +{ + if (auto buffer = gui::SampleLoader::createBufferFromFile(file)) { m_originalSample = Sample(std::move(buffer)); } + + findBPM(); + findSlices(); + + emit dataChanged(); +} + +void SlicerT::updateSlices() +{ + findSlices(); +} + +void SlicerT::saveSettings(QDomDocument& document, QDomElement& element) +{ + element.setAttribute("version", "1"); + element.setAttribute("src", m_originalSample.sampleFile()); + if (m_originalSample.sampleFile().isEmpty()) + { + element.setAttribute("sampledata", m_originalSample.toBase64()); + } + + element.setAttribute("totalSlices", static_cast(m_slicePoints.size())); + for (int i = 0; i < m_slicePoints.size(); i++) + { + element.setAttribute(tr("slice_%1").arg(i), m_slicePoints[i]); + } + + m_fadeOutFrames.saveSettings(document, element, "fadeOut"); + m_noteThreshold.saveSettings(document, element, "threshold"); + m_originalBPM.saveSettings(document, element, "origBPM"); + m_enableSync.saveSettings(document, element, "syncEnable"); +} + +void SlicerT::loadSettings(const QDomElement& element) +{ + if (auto srcFile = element.attribute("src"); !srcFile.isEmpty()) + { + if (QFileInfo(PathUtil::toAbsolute(srcFile)).exists()) + { + auto buffer = gui::SampleLoader::createBufferFromFile(srcFile); + m_originalSample = Sample(std::move(buffer)); + } + else + { + QString message = tr("Sample not found: %1").arg(srcFile); + Engine::getSong()->collectError(message); + } + } + else if (auto sampleData = element.attribute("sampledata"); !sampleData.isEmpty()) + { + auto buffer = gui::SampleLoader::createBufferFromBase64(sampleData); + m_originalSample = Sample(std::move(buffer)); + } + + if (!element.attribute("totalSlices").isEmpty()) + { + int totalSlices = element.attribute("totalSlices").toInt(); + m_slicePoints = {}; + for (int i = 0; i < totalSlices; i++) + { + m_slicePoints.push_back(element.attribute(tr("slice_%1").arg(i)).toFloat()); + } + } + + m_fadeOutFrames.loadSettings(element, "fadeOut"); + m_noteThreshold.loadSettings(element, "threshold"); + m_originalBPM.loadSettings(element, "origBPM"); + m_enableSync.loadSettings(element, "syncEnable"); + + emit dataChanged(); +} + +QString SlicerT::nodeName() const +{ + return slicert_plugin_descriptor.name; +} + +gui::PluginView* SlicerT::instantiateView(QWidget* parent) +{ + return new gui::SlicerTView(this, parent); +} + +extern "C" { +PLUGIN_EXPORT Plugin* lmms_plugin_main(Model* m, void*) +{ + return new SlicerT(static_cast(m)); +} +} // extern +} // namespace lmms diff --git a/plugins/SlicerT/SlicerT.h b/plugins/SlicerT/SlicerT.h new file mode 100644 index 000000000..010985dfc --- /dev/null +++ b/plugins/SlicerT/SlicerT.h @@ -0,0 +1,109 @@ +/* + * SlicerT.h - declaration of class SlicerT + * + * Copyright (c) 2023 Daniel Kauss Serna + * + * 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_SLICERT_H +#define LMMS_SLICERT_H + +#include +#include +#include + +#include "AutomatableModel.h" +#include "Instrument.h" +#include "InstrumentView.h" +#include "Note.h" +#include "Sample.h" +#include "SampleBuffer.h" +#include "SlicerTView.h" +#include "lmms_basics.h" + +namespace lmms { + +class PlaybackState +{ +public: + explicit PlaybackState(float startFrame) + : m_currentNoteDone(startFrame) + , m_resamplingState(src_new(SRC_LINEAR, DEFAULT_CHANNELS, nullptr)) + { + if (!m_resamplingState) { throw std::runtime_error{"Failed to create sample rate converter object"}; } + } + ~PlaybackState() noexcept { src_delete(m_resamplingState); } + + float noteDone() const { return m_currentNoteDone; } + void setNoteDone(float newNoteDone) { m_currentNoteDone = newNoteDone; } + + SRC_STATE* resamplingState() const { return m_resamplingState; } + +private: + float m_currentNoteDone; + SRC_STATE* m_resamplingState; +}; + +class SlicerT : public Instrument +{ + Q_OBJECT + +public slots: + void updateFile(QString file); + void updateSlices(); + +signals: + void isPlaying(float current, float start, float end); + +public: + SlicerT(InstrumentTrack* instrumentTrack); + + void playNote(NotePlayHandle* handle, sampleFrame* workingBuffer) override; + void deleteNotePluginData(NotePlayHandle* handle) override; + + void saveSettings(QDomDocument& document, QDomElement& element) override; + void loadSettings(const QDomElement& element) override; + + void findSlices(); + void findBPM(); + + QString nodeName() const override; + gui::PluginView* instantiateView(QWidget* parent) override; + + std::vector getMidi(); + +private: + FloatModel m_noteThreshold; + FloatModel m_fadeOutFrames; + IntModel m_originalBPM; + ComboBoxModel m_sliceSnap; + BoolModel m_enableSync; + + Sample m_originalSample; + + std::vector m_slicePoints; + + InstrumentTrack* m_parentTrack; + + friend class gui::SlicerTView; + friend class gui::SlicerTWaveform; +}; +} // namespace lmms +#endif // LMMS_SLICERT_H diff --git a/plugins/SlicerT/SlicerTView.cpp b/plugins/SlicerT/SlicerTView.cpp new file mode 100644 index 000000000..bbdb53ccb --- /dev/null +++ b/plugins/SlicerT/SlicerTView.cpp @@ -0,0 +1,194 @@ +/* + * SlicerTView.cpp - controls the UI for slicerT + * + * Copyright (c) 2023 Daniel Kauss Serna + * + * 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 "SlicerTView.h" + +#include +#include + +#include "Clipboard.h" +#include "DataFile.h" +#include "Engine.h" +#include "InstrumentTrack.h" +#include "SampleLoader.h" +#include "SlicerT.h" +#include "Song.h" +#include "StringPairDrag.h" +#include "Track.h" +#include "embed.h" + +namespace lmms { + +namespace gui { + +SlicerTView::SlicerTView(SlicerT* instrument, QWidget* parent) + : InstrumentViewFixedSize(instrument, parent) + , m_slicerTParent(instrument) +{ + // window settings + setAcceptDrops(true); + setAutoFillBackground(true); + + // render background + QPalette pal; + pal.setBrush(backgroundRole(), PLUGIN_NAME::getIconPixmap("artwork")); + setPalette(pal); + + m_wf = new SlicerTWaveform(248, 128, instrument, this); + m_wf->move(2, 6); + + m_snapSetting = new ComboBox(this, tr("Slice snap")); + m_snapSetting->setGeometry(185, 200, 55, ComboBox::DEFAULT_HEIGHT); + m_snapSetting->setToolTip(tr("Set slice snapping for detection")); + m_snapSetting->setModel(&m_slicerTParent->m_sliceSnap); + + m_syncToggle = new LedCheckBox("Sync", this, tr("SyncToggle"), LedCheckBox::LedColor::Green); + m_syncToggle->move(135, 187); + m_syncToggle->setToolTip(tr("Enable BPM sync")); + m_syncToggle->setModel(&m_slicerTParent->m_enableSync); + + m_bpmBox = new LcdSpinBox(3, "19purple", this); + m_bpmBox->move(130, 201); + m_bpmBox->setToolTip(tr("Original sample BPM")); + m_bpmBox->setModel(&m_slicerTParent->m_originalBPM); + + m_noteThresholdKnob = createStyledKnob(); + m_noteThresholdKnob->move(10, 197); + m_noteThresholdKnob->setToolTip(tr("Threshold used for slicing")); + m_noteThresholdKnob->setModel(&m_slicerTParent->m_noteThreshold); + + m_fadeOutKnob = createStyledKnob(); + m_fadeOutKnob->move(64, 197); + m_fadeOutKnob->setToolTip(tr("Fade Out per note in milliseconds")); + m_fadeOutKnob->setModel(&m_slicerTParent->m_fadeOutFrames); + + m_midiExportButton = new QPushButton(this); + m_midiExportButton->move(199, 150); + m_midiExportButton->setIcon(PLUGIN_NAME::getIconPixmap("copy_midi")); + m_midiExportButton->setToolTip(tr("Copy midi pattern to clipboard")); + connect(m_midiExportButton, &PixmapButton::clicked, this, &SlicerTView::exportMidi); + + m_resetButton = new QPushButton(this); + m_resetButton->move(18, 150); + m_resetButton->setIcon(PLUGIN_NAME::getIconPixmap("reset_slices")); + m_resetButton->setToolTip(tr("Reset Slices")); + connect(m_resetButton, &PixmapButton::clicked, m_slicerTParent, &SlicerT::updateSlices); +} + +Knob* SlicerTView::createStyledKnob() +{ + Knob* newKnob = new Knob(KnobType::Styled, this); + newKnob->setFixedSize(50, 40); + newKnob->setCenterPointX(24.0); + newKnob->setCenterPointY(15.0); + return newKnob; +} + +// copied from piano roll +void SlicerTView::exportMidi() +{ + using namespace Clipboard; + if (m_slicerTParent->m_originalSample.sampleSize() <= 1) { return; } + + DataFile dataFile(DataFile::Type::ClipboardData); + QDomElement noteList = dataFile.createElement("note-list"); + dataFile.content().appendChild(noteList); + + auto notes = m_slicerTParent->getMidi(); + if (notes.empty()) { return; } + + TimePos startPos(notes.front().pos().getBar(), 0); + for (Note& note : notes) + { + note.setPos(note.pos(startPos)); + note.saveState(dataFile, noteList); + } + + copyString(dataFile.toString(), MimeType::Default); +} + +void SlicerTView::openFiles() +{ + const auto audioFile = SampleLoader::openAudioFile(); + if (audioFile.isEmpty()) { return; } + m_slicerTParent->updateFile(audioFile); +} + +// all the drag stuff is copied from AudioFileProcessor +void SlicerTView::dragEnterEvent(QDragEnterEvent* dee) +{ + // For mimeType() and MimeType enum class + using namespace Clipboard; + + if (dee->mimeData()->hasFormat(mimeType(MimeType::StringPair))) + { + QString txt = dee->mimeData()->data(mimeType(MimeType::StringPair)); + if (txt.section(':', 0, 0) == QString("clip_%1").arg(static_cast(Track::Type::Sample))) + { + dee->acceptProposedAction(); + } + else if (txt.section(':', 0, 0) == "samplefile") { dee->acceptProposedAction(); } + else { dee->ignore(); } + } + else { dee->ignore(); } +} + +void SlicerTView::dropEvent(QDropEvent* de) +{ + QString type = StringPairDrag::decodeKey(de); + QString value = StringPairDrag::decodeValue(de); + if (type == "samplefile") + { + // set m_wf wave file + m_slicerTParent->updateFile(value); + return; + } + else if (type == QString("clip_%1").arg(static_cast(Track::Type::Sample))) + { + DataFile dataFile(value.toUtf8()); + m_slicerTParent->updateFile(dataFile.content().firstChild().toElement().attribute("src")); + de->accept(); + return; + } + + de->ignore(); +} + +void SlicerTView::paintEvent(QPaintEvent* pe) +{ + QPainter brush(this); + brush.setPen(QColor(255, 255, 255)); + brush.setFont(QFont(brush.font().family(), 7, -1, false)); + + brush.drawText(8, s_topTextY, s_textBoxWidth, s_textBoxHeight, Qt::AlignCenter, tr("Reset")); + brush.drawText(188, s_topTextY, s_textBoxWidth, s_textBoxHeight, Qt::AlignCenter, tr("Midi")); + + brush.drawText(8, s_bottomTextY, s_textBoxWidth, s_textBoxHeight, Qt::AlignCenter, tr("Threshold")); + brush.drawText(63, s_bottomTextY, s_textBoxWidth, s_textBoxHeight, Qt::AlignCenter, tr("Fade Out")); + brush.drawText(127, s_bottomTextY, s_textBoxWidth, s_textBoxHeight, Qt::AlignCenter, tr("BPM")); + brush.drawText(188, s_bottomTextY, s_textBoxWidth, s_textBoxHeight, Qt::AlignCenter, tr("Snap")); +} + +} // namespace gui +} // namespace lmms diff --git a/plugins/SlicerT/SlicerTView.h b/plugins/SlicerT/SlicerTView.h new file mode 100644 index 000000000..ea2b979fc --- /dev/null +++ b/plugins/SlicerT/SlicerTView.h @@ -0,0 +1,85 @@ +/* + * SlicerTView.h - declaration of class SlicerTView + * + * Copyright (c) 2023 Daniel Kauss Serna + * + * 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_SLICERT_VIEW_H +#define LMMS_GUI_SLICERT_VIEW_H + +#include + +#include "ComboBox.h" +#include "Instrument.h" +#include "InstrumentView.h" +#include "Knob.h" +#include "LcdSpinBox.h" +#include "LedCheckBox.h" +#include "PixmapButton.h" +#include "SlicerTWaveform.h" + +namespace lmms { + +class SlicerT; + +namespace gui { + +class SlicerTView : public InstrumentViewFixedSize +{ + Q_OBJECT + +public slots: + void exportMidi(); + void openFiles(); + +public: + SlicerTView(SlicerT* instrument, QWidget* parent); + + static constexpr int s_textBoxHeight = 20; + static constexpr int s_textBoxWidth = 50; + static constexpr int s_topTextY = 170; + static constexpr int s_bottomTextY = 220; + +protected: + virtual void dragEnterEvent(QDragEnterEvent* dee); + virtual void dropEvent(QDropEvent* de); + + virtual void paintEvent(QPaintEvent* pe); + +private: + SlicerT* m_slicerTParent; + + Knob* m_noteThresholdKnob; + Knob* m_fadeOutKnob; + LcdSpinBox* m_bpmBox; + ComboBox* m_snapSetting; + LedCheckBox* m_syncToggle; + + QPushButton* m_resetButton; + QPushButton* m_midiExportButton; + + SlicerTWaveform* m_wf; + + Knob* createStyledKnob(); +}; +} // namespace gui +} // namespace lmms +#endif // LMMS_GUI_SLICERT_VIEW_H diff --git a/plugins/SlicerT/SlicerTWaveform.cpp b/plugins/SlicerT/SlicerTWaveform.cpp new file mode 100644 index 000000000..3793ed2f1 --- /dev/null +++ b/plugins/SlicerT/SlicerTWaveform.cpp @@ -0,0 +1,424 @@ +/* + * SlicerTWaveform.cpp - slice editor for SlicerT + * + * Copyright (c) 2023 Daniel Kauss Serna + * + * 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 "SlicerTWaveform.h" + +#include + +#include "SampleWaveform.h" +#include "SlicerT.h" +#include "SlicerTView.h" +#include "embed.h" + +namespace lmms { + +namespace gui { + +static QColor s_emptyColor = QColor(0, 0, 0, 0); +static QColor s_waveformColor = QColor(123, 49, 212); +static QColor s_waveformBgColor = QColor(255, 255, 255, 0); +static QColor s_waveformMaskColor = QColor(151, 65, 255); // update this if s_waveformColor changes +static QColor s_waveformInnerColor = QColor(183, 124, 255); + +static QColor s_playColor = QColor(255, 255, 255, 200); +static QColor s_playHighlightColor = QColor(255, 255, 255, 70); + +static QColor s_sliceColor = QColor(218, 193, 255); +static QColor s_sliceShadowColor = QColor(136, 120, 158); +static QColor s_sliceHighlightColor = QColor(255, 255, 255); + +static QColor s_seekerColor = QColor(178, 115, 255); +static QColor s_seekerHighlightColor = QColor(178, 115, 255, 100); +static QColor s_seekerShadowColor = QColor(0, 0, 0, 120); + +SlicerTWaveform::SlicerTWaveform(int totalWidth, int totalHeight, SlicerT* instrument, QWidget* parent) + : QWidget(parent) + , m_width(totalWidth) + , m_height(totalHeight) + , m_seekerWidth(totalWidth - s_seekerHorMargin * 2) + , m_editorHeight(totalHeight - s_seekerHeight - s_middleMargin) + , m_editorWidth(totalWidth) + , m_sliceArrow(PLUGIN_NAME::getIconPixmap("slice_indicator_arrow")) + , m_seeker(QPixmap(m_seekerWidth, s_seekerHeight)) + , m_seekerWaveform(QPixmap(m_seekerWidth, s_seekerHeight)) + , m_editorWaveform(QPixmap(m_editorWidth, m_editorHeight)) + , m_sliceEditor(QPixmap(totalWidth, m_editorHeight)) + , m_emptySampleIcon(embed::getIconPixmap("sample_track")) + , m_slicerTParent(instrument) +{ + setFixedSize(m_width, m_height); + setMouseTracking(true); + + m_seekerWaveform.fill(s_waveformBgColor); + m_editorWaveform.fill(s_waveformBgColor); + + connect(instrument, &SlicerT::isPlaying, this, &SlicerTWaveform::isPlaying); + connect(instrument, &SlicerT::dataChanged, this, &SlicerTWaveform::updateUI); + + m_emptySampleIcon = m_emptySampleIcon.createMaskFromColor(QColor(255, 255, 255), Qt::MaskMode::MaskOutColor); + + m_updateTimer.start(); + updateUI(); +} + +void SlicerTWaveform::drawSeekerWaveform() +{ + m_seekerWaveform.fill(s_waveformBgColor); + if (m_slicerTParent->m_originalSample.sampleSize() <= 1) { return; } + QPainter brush(&m_seekerWaveform); + brush.setPen(s_waveformColor); + + const auto& sample = m_slicerTParent->m_originalSample; + const auto waveform = SampleWaveform::Parameters{sample.data(), sample.sampleSize(), sample.amplification(), sample.reversed()}; + const auto rect = QRect(0, 0, m_seekerWaveform.width(), m_seekerWaveform.height()); + SampleWaveform::visualize(waveform, brush, rect); + + // increase brightness in inner color + QBitmap innerMask = m_seekerWaveform.createMaskFromColor(s_waveformMaskColor, Qt::MaskMode::MaskOutColor); + brush.setPen(s_waveformInnerColor); + brush.drawPixmap(0, 0, innerMask); +} + +void SlicerTWaveform::drawSeeker() +{ + m_seeker.fill(s_emptyColor); + if (m_slicerTParent->m_originalSample.sampleSize() <= 1) { return; } + QPainter brush(&m_seeker); + + brush.setPen(s_sliceColor); + for (float sliceValue : m_slicerTParent->m_slicePoints) + { + float xPos = sliceValue * m_seekerWidth; + brush.drawLine(xPos, 0, xPos, s_seekerHeight); + } + + float seekerStartPosX = m_seekerStart * m_seekerWidth; + float seekerEndPosX = m_seekerEnd * m_seekerWidth; + float seekerMiddleWidth = (m_seekerEnd - m_seekerStart) * m_seekerWidth; + + float noteCurrentPosX = m_noteCurrent * m_seekerWidth; + float noteStartPosX = m_noteStart * m_seekerWidth; + float noteEndPosX = (m_noteEnd - m_noteStart) * m_seekerWidth; + + brush.setPen(s_playColor); + brush.drawLine(noteCurrentPosX, 0, noteCurrentPosX, s_seekerHeight); + brush.fillRect(noteStartPosX, 0, noteEndPosX, s_seekerHeight, s_playHighlightColor); + + brush.fillRect(seekerStartPosX, 0, seekerMiddleWidth - 1, s_seekerHeight, s_seekerHighlightColor); + + brush.fillRect(0, 0, seekerStartPosX, s_seekerHeight, s_seekerShadowColor); + brush.fillRect(seekerEndPosX - 1, 0, m_seekerWidth, s_seekerHeight, s_seekerShadowColor); + + brush.setPen(QPen(s_seekerColor, 1)); + brush.drawRect(seekerStartPosX, 0, seekerMiddleWidth - 1, s_seekerHeight - 1); // -1 needed +} + +void SlicerTWaveform::drawEditorWaveform() +{ + m_editorWaveform.fill(s_emptyColor); + if (m_slicerTParent->m_originalSample.sampleSize() <= 1) { return; } + + QPainter brush(&m_editorWaveform); + size_t startFrame = m_seekerStart * m_slicerTParent->m_originalSample.sampleSize(); + size_t endFrame = m_seekerEnd * m_slicerTParent->m_originalSample.sampleSize(); + + brush.setPen(s_waveformColor); + float zoomOffset = (m_editorHeight - m_zoomLevel * m_editorHeight) / 2; + + const auto& sample = m_slicerTParent->m_originalSample; + const auto waveform = SampleWaveform::Parameters{sample.data() + startFrame, endFrame - startFrame, sample.amplification(), sample.reversed()}; + const auto rect = QRect(0, zoomOffset, m_editorWidth, m_zoomLevel * m_editorHeight); + SampleWaveform::visualize(waveform, brush, rect); + + // increase brightness in inner color + QBitmap innerMask = m_editorWaveform.createMaskFromColor(s_waveformMaskColor, Qt::MaskMode::MaskOutColor); + brush.setPen(s_waveformInnerColor); + brush.drawPixmap(0, 0, innerMask); +} + +void SlicerTWaveform::drawEditor() +{ + m_sliceEditor.fill(s_waveformBgColor); + QPainter brush(&m_sliceEditor); + + // No sample loaded + if (m_slicerTParent->m_originalSample.sampleSize() <= 1) + { + brush.setPen(s_playHighlightColor); + brush.setFont(QFont(brush.font().family(), 9.0f, -1, false)); + brush.drawText( + m_editorWidth / 2 - 100, m_editorHeight / 2 - 110, 200, 200, Qt::AlignCenter, tr("Click to load sample")); + int iconOffsetX = m_emptySampleIcon.width() / 2.0f; + int iconOffsetY = m_emptySampleIcon.height() / 2.0f - 13; + brush.drawPixmap(m_editorWidth / 2.0f - iconOffsetX, m_editorHeight / 2.0f - iconOffsetY, m_emptySampleIcon); + return; + } + + float startFrame = m_seekerStart; + float endFrame = m_seekerEnd; + float numFramesToDraw = endFrame - startFrame; + + // playback state + float noteCurrentPos = (m_noteCurrent - m_seekerStart) / (m_seekerEnd - m_seekerStart) * m_editorWidth; + float noteStartPos = (m_noteStart - m_seekerStart) / (m_seekerEnd - m_seekerStart) * m_editorWidth; + float noteLength = (m_noteEnd - m_noteStart) / (m_seekerEnd - m_seekerStart) * m_editorWidth; + + brush.setPen(s_playHighlightColor); + brush.drawLine(0, m_editorHeight / 2, m_editorWidth, m_editorHeight / 2); + + brush.drawPixmap(0, 0, m_editorWaveform); + + brush.setPen(s_playColor); + brush.drawLine(noteCurrentPos, 0, noteCurrentPos, m_editorHeight); + brush.fillRect(noteStartPos, 0, noteLength, m_editorHeight, s_playHighlightColor); + + brush.setPen(QPen(s_sliceColor, 2)); + + for (int i = 0; i < m_slicerTParent->m_slicePoints.size(); i++) + { + float xPos = (m_slicerTParent->m_slicePoints.at(i) - startFrame) / numFramesToDraw * m_editorWidth; + + if (i == m_closestSlice) + { + brush.setPen(QPen(s_sliceHighlightColor, 2)); + brush.drawLine(xPos, 0, xPos, m_editorHeight); + brush.drawPixmap(xPos - m_sliceArrow.width() / 2.0f, 0, m_sliceArrow); + continue; + } + else + { + brush.setPen(QPen(s_sliceShadowColor, 1)); + brush.drawLine(xPos - 1, 0, xPos - 1, m_editorHeight); + brush.setPen(QPen(s_sliceColor, 1)); + brush.drawLine(xPos, 0, xPos, m_editorHeight); + brush.drawPixmap(xPos - m_sliceArrow.width() / 2.0f, 0, m_sliceArrow); + } + } +} + +void SlicerTWaveform::isPlaying(float current, float start, float end) +{ + if (!m_updateTimer.hasExpired(s_minMilisPassed)) { return; } + m_noteCurrent = current; + m_noteStart = start; + m_noteEnd = end; + drawSeeker(); + drawEditor(); + update(); + m_updateTimer.restart(); +} + +// this should only be called if one of the waveforms has to update +void SlicerTWaveform::updateUI() +{ + drawSeekerWaveform(); + drawEditorWaveform(); + drawSeeker(); + drawEditor(); + update(); +} + +// updates the closest object and changes the cursor respectivly +void SlicerTWaveform::updateClosest(QMouseEvent* me) +{ + float normalizedClickSeeker = static_cast(me->x() - s_seekerHorMargin) / m_seekerWidth; + float normalizedClickEditor = static_cast(me->x()) / m_editorWidth; + + m_closestObject = UIObjects::Nothing; + m_closestSlice = -1; + + if (me->y() < s_seekerHeight) + { + if (std::abs(normalizedClickSeeker - m_seekerStart) < s_distanceForClick) + { + m_closestObject = UIObjects::SeekerStart; + } + else if (std::abs(normalizedClickSeeker - m_seekerEnd) < s_distanceForClick) + { + m_closestObject = UIObjects::SeekerEnd; + } + else if (normalizedClickSeeker > m_seekerStart && normalizedClickSeeker < m_seekerEnd) + { + m_closestObject = UIObjects::SeekerMiddle; + } + } + else + { + m_closestSlice = -1; + float startFrame = m_seekerStart; + float endFrame = m_seekerEnd; + for (int i = 0; i < m_slicerTParent->m_slicePoints.size(); i++) + { + float sliceIndex = m_slicerTParent->m_slicePoints.at(i); + float xPos = (sliceIndex - startFrame) / (endFrame - startFrame); + + if (std::abs(xPos - normalizedClickEditor) < s_distanceForClick) + { + m_closestObject = UIObjects::SlicePoint; + m_closestSlice = i; + } + } + } + updateCursor(); + drawSeeker(); + drawEditor(); + update(); +} + +void SlicerTWaveform::updateCursor() +{ + if (m_closestObject == UIObjects::SlicePoint || m_closestObject == UIObjects::SeekerStart + || m_closestObject == UIObjects::SeekerEnd) + { + setCursor(Qt::SizeHorCursor); + } + else if (m_closestObject == UIObjects::SeekerMiddle && m_seekerEnd - m_seekerStart != 1.0f) + { + setCursor(Qt::SizeAllCursor); + } + else { setCursor(Qt::ArrowCursor); } +} + +// handles deletion, reset and middles seeker +void SlicerTWaveform::mousePressEvent(QMouseEvent* me) +{ + switch (me->button()) + { + case Qt::MouseButton::MiddleButton: + m_seekerStart = 0; + m_seekerEnd = 1; + m_zoomLevel = 1; + drawEditorWaveform(); + break; + case Qt::MouseButton::LeftButton: + if (m_slicerTParent->m_originalSample.sampleSize() <= 1) { static_cast(parent())->openFiles(); } + // update seeker middle for correct movement + m_seekerMiddle = static_cast(me->x() - s_seekerHorMargin) / m_seekerWidth; + break; + case Qt::MouseButton::RightButton: + if (m_slicerTParent->m_slicePoints.size() > 2 && m_closestObject == UIObjects::SlicePoint) + { + m_slicerTParent->m_slicePoints.erase(m_slicerTParent->m_slicePoints.begin() + m_closestSlice); + } + break; + default:; + } + updateClosest(me); +} + +// sort slices after moving and remove draggable object +void SlicerTWaveform::mouseReleaseEvent(QMouseEvent* me) +{ + std::sort(m_slicerTParent->m_slicePoints.begin(), m_slicerTParent->m_slicePoints.end()); + updateClosest(me); +} + +// this handles dragging and mouse cursor changes +// what is being dragged is determined in mousePressEvent +void SlicerTWaveform::mouseMoveEvent(QMouseEvent* me) +{ + // if no button pressed, update closest and cursor + if (me->buttons() == Qt::MouseButton::NoButton) + { + updateClosest(me); + return; + } + + float normalizedClickSeeker = static_cast(me->x() - s_seekerHorMargin) / m_seekerWidth; + float normalizedClickEditor = static_cast(me->x()) / m_editorWidth; + + float distStart = m_seekerStart - m_seekerMiddle; + float distEnd = m_seekerEnd - m_seekerMiddle; + float startFrame = m_seekerStart; + float endFrame = m_seekerEnd; + + switch (m_closestObject) + { + case UIObjects::SeekerStart: + m_seekerStart = std::clamp(normalizedClickSeeker, 0.0f, m_seekerEnd - s_minSeekerDistance); + drawEditorWaveform(); + break; + + case UIObjects::SeekerEnd: + m_seekerEnd = std::clamp(normalizedClickSeeker, m_seekerStart + s_minSeekerDistance, 1.0f); + drawEditorWaveform(); + break; + + case UIObjects::SeekerMiddle: + m_seekerMiddle = normalizedClickSeeker; + + if (m_seekerMiddle + distStart >= 0 && m_seekerMiddle + distEnd <= 1) + { + m_seekerStart = m_seekerMiddle + distStart; + m_seekerEnd = m_seekerMiddle + distEnd; + } + drawEditorWaveform(); + break; + + case UIObjects::SlicePoint: + if (m_closestSlice == -1) { break; } + m_slicerTParent->m_slicePoints.at(m_closestSlice) + = startFrame + normalizedClickEditor * (endFrame - startFrame); + m_slicerTParent->m_slicePoints.at(m_closestSlice) + = std::clamp(m_slicerTParent->m_slicePoints.at(m_closestSlice), 0.0f, 1.0f); + break; + case UIObjects::Nothing: + break; + } + // dont update closest, and update seeker waveform + drawSeeker(); + drawEditor(); + update(); +} + +void SlicerTWaveform::mouseDoubleClickEvent(QMouseEvent* me) +{ + if (me->button() != Qt::MouseButton::LeftButton) { return; } + + float normalizedClickEditor = static_cast(me->x()) / m_editorWidth; + float startFrame = m_seekerStart; + float endFrame = m_seekerEnd; + float slicePosition = startFrame + normalizedClickEditor * (endFrame - startFrame); + + m_slicerTParent->m_slicePoints.insert(m_slicerTParent->m_slicePoints.begin(), slicePosition); + std::sort(m_slicerTParent->m_slicePoints.begin(), m_slicerTParent->m_slicePoints.end()); +} + +void SlicerTWaveform::wheelEvent(QWheelEvent* we) +{ + m_zoomLevel += we->angleDelta().y() / 360.0f * s_zoomSensitivity; + m_zoomLevel = std::max(0.0f, m_zoomLevel); + + updateUI(); +} + +void SlicerTWaveform::paintEvent(QPaintEvent* pe) +{ + QPainter p(this); + p.drawPixmap(s_seekerHorMargin, 0, m_seekerWaveform); + p.drawPixmap(s_seekerHorMargin, 0, m_seeker); + p.drawPixmap(0, s_seekerHeight + s_middleMargin, m_sliceEditor); +} +} // namespace gui +} // namespace lmms diff --git a/plugins/SlicerT/SlicerTWaveform.h b/plugins/SlicerT/SlicerTWaveform.h new file mode 100644 index 000000000..6478e7f86 --- /dev/null +++ b/plugins/SlicerT/SlicerTWaveform.h @@ -0,0 +1,125 @@ +/* + * SlicerTWaveform.h - declaration of class SlicerTWaveform + * + * Copyright (c) 2023 Daniel Kauss Serna + * + * 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_SLICERT_WAVEFORM_H +#define LMMS_GUI_SLICERT_WAVEFORM_H + +#include +#include +#include +#include +#include +#include + +#include "Instrument.h" +#include "SampleBuffer.h" + +namespace lmms { + +class SlicerT; + +namespace gui { + +class SlicerTWaveform : public QWidget +{ + Q_OBJECT + +public slots: + void updateUI(); + void isPlaying(float current, float start, float end); + +public: + SlicerTWaveform(int totalWidth, int totalHeight, SlicerT* instrument, QWidget* parent); + + // predefined sizes + static constexpr int s_seekerHorMargin = 5; + static constexpr int s_seekerHeight = 38; // used to calcualte all vertical sizes + static constexpr int s_middleMargin = 6; + + // interaction behavior values + static constexpr float s_distanceForClick = 0.02f; + static constexpr float s_minSeekerDistance = 0.13f; + static constexpr float s_zoomSensitivity = 0.5f; + static constexpr int s_minMilisPassed = 10; + + enum class UIObjects + { + Nothing, + SeekerStart, + SeekerEnd, + SeekerMiddle, + SlicePoint, + }; + +protected: + void mousePressEvent(QMouseEvent* me) override; + void mouseReleaseEvent(QMouseEvent* me) override; + void mouseMoveEvent(QMouseEvent* me) override; + void mouseDoubleClickEvent(QMouseEvent* me) override; + void wheelEvent(QWheelEvent* we) override; + + void paintEvent(QPaintEvent* pe) override; + +private: + int m_width; + int m_height; + + int m_seekerWidth; + int m_editorHeight; + int m_editorWidth; + + UIObjects m_closestObject; + int m_closestSlice = -1; + + float m_seekerStart = 0; + float m_seekerEnd = 1; + float m_seekerMiddle = 0.5f; + + float m_noteCurrent; + float m_noteStart; + float m_noteEnd; + + float m_zoomLevel = 1.0f; + + QPixmap m_sliceArrow; + QPixmap m_seeker; + QPixmap m_seekerWaveform; + QPixmap m_editorWaveform; + QPixmap m_sliceEditor; + QPixmap m_emptySampleIcon; + + SlicerT* m_slicerTParent; + + QElapsedTimer m_updateTimer; + void drawSeekerWaveform(); + void drawSeeker(); + void drawEditorWaveform(); + void drawEditor(); + + void updateClosest(QMouseEvent* me); + void updateCursor(); +}; +} // namespace gui +} // namespace lmms +#endif // LMMS_GUI_SLICERT_WAVEFORM_H diff --git a/plugins/SlicerT/artwork.png b/plugins/SlicerT/artwork.png new file mode 100644 index 000000000..e166273c7 Binary files /dev/null and b/plugins/SlicerT/artwork.png differ diff --git a/plugins/SlicerT/copy_midi.png b/plugins/SlicerT/copy_midi.png new file mode 100644 index 000000000..e2ef15199 Binary files /dev/null and b/plugins/SlicerT/copy_midi.png differ diff --git a/plugins/SlicerT/logo.png b/plugins/SlicerT/logo.png new file mode 100644 index 000000000..f2c4fabf1 Binary files /dev/null and b/plugins/SlicerT/logo.png differ diff --git a/plugins/SlicerT/reset_slices.png b/plugins/SlicerT/reset_slices.png new file mode 100644 index 000000000..15de6cdc1 Binary files /dev/null and b/plugins/SlicerT/reset_slices.png differ diff --git a/plugins/SlicerT/slice_indicator_arrow.png b/plugins/SlicerT/slice_indicator_arrow.png new file mode 100644 index 000000000..1f60fc3bf Binary files /dev/null and b/plugins/SlicerT/slice_indicator_arrow.png differ diff --git a/plugins/SpectrumAnalyzer/SaControlsDialog.cpp b/plugins/SpectrumAnalyzer/SaControlsDialog.cpp index eb09c793a..2b0ca4fec 100644 --- a/plugins/SpectrumAnalyzer/SaControlsDialog.cpp +++ b/plugins/SpectrumAnalyzer/SaControlsDialog.cpp @@ -89,28 +89,28 @@ SaControlsDialog::SaControlsDialog(SaControls *controls, SaProcessor *processor) // pause and freeze buttons auto pauseButton = new PixmapButton(this, tr("Pause")); pauseButton->setToolTip(tr("Pause data acquisition")); - auto pauseOnPixmap = new QPixmap( - PLUGIN_NAME::getIconPixmap("play").scaled(buttonSize, Qt::IgnoreAspectRatio, Qt::SmoothTransformation)); - auto pauseOffPixmap = new QPixmap( - PLUGIN_NAME::getIconPixmap("pause").scaled(buttonSize, Qt::IgnoreAspectRatio, Qt::SmoothTransformation)); - pauseOnPixmap->setDevicePixelRatio(devicePixelRatio()); - pauseOffPixmap->setDevicePixelRatio(devicePixelRatio()); - pauseButton->setActiveGraphic(*pauseOnPixmap); - pauseButton->setInactiveGraphic(*pauseOffPixmap); + static auto s_pauseOnPixmap + = PLUGIN_NAME::getIconPixmap("play").scaled(buttonSize, Qt::IgnoreAspectRatio, Qt::SmoothTransformation); + static auto s_pauseOffPixmap + = PLUGIN_NAME::getIconPixmap("pause").scaled(buttonSize, Qt::IgnoreAspectRatio, Qt::SmoothTransformation); + s_pauseOnPixmap.setDevicePixelRatio(devicePixelRatio()); + s_pauseOffPixmap.setDevicePixelRatio(devicePixelRatio()); + pauseButton->setActiveGraphic(s_pauseOnPixmap); + pauseButton->setInactiveGraphic(s_pauseOffPixmap); pauseButton->setCheckable(true); pauseButton->setModel(&controls->m_pauseModel); config_layout->addWidget(pauseButton, 0, 0, 2, 1, Qt::AlignHCenter); auto refFreezeButton = new PixmapButton(this, tr("Reference freeze")); refFreezeButton->setToolTip(tr("Freeze current input as a reference / disable falloff in peak-hold mode.")); - auto freezeOnPixmap = new QPixmap( - PLUGIN_NAME::getIconPixmap("freeze").scaled(buttonSize, Qt::IgnoreAspectRatio, Qt::SmoothTransformation)); - auto freezeOffPixmap = new QPixmap( - PLUGIN_NAME::getIconPixmap("freeze_off").scaled(buttonSize, Qt::IgnoreAspectRatio, Qt::SmoothTransformation)); - freezeOnPixmap->setDevicePixelRatio(devicePixelRatio()); - freezeOffPixmap->setDevicePixelRatio(devicePixelRatio()); - refFreezeButton->setActiveGraphic(*freezeOnPixmap); - refFreezeButton->setInactiveGraphic(*freezeOffPixmap); + static auto s_freezeOnPixmap + = PLUGIN_NAME::getIconPixmap("freeze").scaled(buttonSize, Qt::IgnoreAspectRatio, Qt::SmoothTransformation); + static auto s_freezeOffPixmap + = PLUGIN_NAME::getIconPixmap("freeze_off").scaled(buttonSize, Qt::IgnoreAspectRatio, Qt::SmoothTransformation); + s_freezeOnPixmap.setDevicePixelRatio(devicePixelRatio()); + s_freezeOffPixmap.setDevicePixelRatio(devicePixelRatio()); + refFreezeButton->setActiveGraphic(s_freezeOnPixmap); + refFreezeButton->setInactiveGraphic(s_freezeOffPixmap); refFreezeButton->setCheckable(true); refFreezeButton->setModel(&controls->m_refFreezeModel); config_layout->addWidget(refFreezeButton, 2, 0, 2, 1, Qt::AlignHCenter); @@ -119,42 +119,42 @@ SaControlsDialog::SaControlsDialog(SaControls *controls, SaProcessor *processor) auto waterfallButton = new LedCheckBox(tr("Waterfall"), this); waterfallButton->setToolTip(tr("Display real-time spectrogram")); waterfallButton->setCheckable(true); - waterfallButton->setMinimumSize(70, 12); + waterfallButton->setMinimumSize(100, 12); waterfallButton->setModel(&controls->m_waterfallModel); config_layout->addWidget(waterfallButton, 0, 1); auto smoothButton = new LedCheckBox(tr("Averaging"), this); smoothButton->setToolTip(tr("Enable exponential moving average")); smoothButton->setCheckable(true); - smoothButton->setMinimumSize(70, 12); + smoothButton->setMinimumSize(100, 12); smoothButton->setModel(&controls->m_smoothModel); config_layout->addWidget(smoothButton, 1, 1); auto stereoButton = new LedCheckBox(tr("Stereo"), this); stereoButton->setToolTip(tr("Display stereo channels separately")); stereoButton->setCheckable(true); - stereoButton->setMinimumSize(70, 12); + stereoButton->setMinimumSize(100, 12); stereoButton->setModel(&controls->m_stereoModel); config_layout->addWidget(stereoButton, 2, 1); auto peakHoldButton = new LedCheckBox(tr("Peak hold"), this); peakHoldButton->setToolTip(tr("Display envelope of peak values")); peakHoldButton->setCheckable(true); - peakHoldButton->setMinimumSize(70, 12); + peakHoldButton->setMinimumSize(100, 12); peakHoldButton->setModel(&controls->m_peakHoldModel); config_layout->addWidget(peakHoldButton, 3, 1); // frequency: linear / log. switch and range selector auto logXButton = new PixmapButton(this, tr("Logarithmic frequency")); logXButton->setToolTip(tr("Switch between logarithmic and linear frequency scale")); - auto logXOnPixmap = new QPixmap( - PLUGIN_NAME::getIconPixmap("x_log").scaled(iconSize, Qt::IgnoreAspectRatio, Qt::SmoothTransformation)); - auto logXOffPixmap = new QPixmap( - PLUGIN_NAME::getIconPixmap("x_linear").scaled(iconSize, Qt::IgnoreAspectRatio, Qt::SmoothTransformation)); - logXOnPixmap->setDevicePixelRatio(devicePixelRatio()); - logXOffPixmap->setDevicePixelRatio(devicePixelRatio()); - logXButton->setActiveGraphic(*logXOnPixmap); - logXButton->setInactiveGraphic(*logXOffPixmap); + static auto s_logXOnPixmap + = PLUGIN_NAME::getIconPixmap("x_log").scaled(iconSize, Qt::IgnoreAspectRatio, Qt::SmoothTransformation); + static auto s_logXOffPixmap + = PLUGIN_NAME::getIconPixmap("x_linear").scaled(iconSize, Qt::IgnoreAspectRatio, Qt::SmoothTransformation); + s_logXOnPixmap.setDevicePixelRatio(devicePixelRatio()); + s_logXOffPixmap.setDevicePixelRatio(devicePixelRatio()); + logXButton->setActiveGraphic(s_logXOnPixmap); + logXButton->setInactiveGraphic(s_logXOffPixmap); logXButton->setCheckable(true); logXButton->setModel(&controls->m_logXModel); config_layout->addWidget(logXButton, 0, 2, 2, 1, Qt::AlignRight); @@ -169,14 +169,14 @@ SaControlsDialog::SaControlsDialog(SaControls *controls, SaProcessor *processor) // amplitude: linear / log switch and range selector auto logYButton = new PixmapButton(this, tr("Logarithmic amplitude")); logYButton->setToolTip(tr("Switch between logarithmic and linear amplitude scale")); - auto logYOnPixmap = new QPixmap( - PLUGIN_NAME::getIconPixmap("y_log").scaled(iconSize, Qt::IgnoreAspectRatio, Qt::SmoothTransformation)); - auto logYOffPixmap = new QPixmap( - PLUGIN_NAME::getIconPixmap("y_linear").scaled(iconSize, Qt::IgnoreAspectRatio, Qt::SmoothTransformation)); - logYOnPixmap->setDevicePixelRatio(devicePixelRatio()); - logYOffPixmap->setDevicePixelRatio(devicePixelRatio()); - logYButton->setActiveGraphic(*logYOnPixmap); - logYButton->setInactiveGraphic(*logYOffPixmap); + static auto s_logYOnPixmap + = PLUGIN_NAME::getIconPixmap("y_log").scaled(iconSize, Qt::IgnoreAspectRatio, Qt::SmoothTransformation); + static auto s_logYOffPixmap + = PLUGIN_NAME::getIconPixmap("y_linear").scaled(iconSize, Qt::IgnoreAspectRatio, Qt::SmoothTransformation); + s_logYOnPixmap.setDevicePixelRatio(devicePixelRatio()); + s_logYOffPixmap.setDevicePixelRatio(devicePixelRatio()); + logYButton->setActiveGraphic(s_logYOnPixmap); + logYButton->setInactiveGraphic(s_logYOffPixmap); logYButton->setCheckable(true); logYButton->setModel(&controls->m_logYModel); config_layout->addWidget(logYButton, 2, 2, 2, 1, Qt::AlignRight); @@ -190,9 +190,9 @@ SaControlsDialog::SaControlsDialog(SaControls *controls, SaProcessor *processor) // FFT: block size: icon and selector auto blockSizeLabel = new QLabel("", this); - auto blockSizeIcon = new QPixmap(PLUGIN_NAME::getIconPixmap("block_size")); - blockSizeIcon->setDevicePixelRatio(devicePixelRatio()); - blockSizeLabel->setPixmap(blockSizeIcon->scaled(iconSize, Qt::IgnoreAspectRatio, Qt::SmoothTransformation)); + static auto s_blockSizeIcon = PLUGIN_NAME::getIconPixmap("block_size"); + s_blockSizeIcon.setDevicePixelRatio(devicePixelRatio()); + blockSizeLabel->setPixmap(s_blockSizeIcon.scaled(iconSize, Qt::IgnoreAspectRatio, Qt::SmoothTransformation)); config_layout->addWidget(blockSizeLabel, 0, 4, 2, 1, Qt::AlignRight); auto blockSizeCombo = new ComboBox(this, tr("FFT block size")); @@ -206,9 +206,9 @@ SaControlsDialog::SaControlsDialog(SaControls *controls, SaProcessor *processor) // FFT: window type: icon and selector auto windowLabel = new QLabel("", this); - auto windowIcon = new QPixmap(PLUGIN_NAME::getIconPixmap("window")); - windowIcon->setDevicePixelRatio(devicePixelRatio()); - windowLabel->setPixmap(windowIcon->scaled(iconSize, Qt::IgnoreAspectRatio, Qt::SmoothTransformation)); + static auto s_windowIcon = PLUGIN_NAME::getIconPixmap("window"); + s_windowIcon.setDevicePixelRatio(devicePixelRatio()); + windowLabel->setPixmap(s_windowIcon.scaled(iconSize, Qt::IgnoreAspectRatio, Qt::SmoothTransformation)); config_layout->addWidget(windowLabel, 2, 4, 2, 1, Qt::AlignRight); auto windowCombo = new ComboBox(this, tr("FFT window type")); @@ -307,14 +307,14 @@ SaControlsDialog::SaControlsDialog(SaControls *controls, SaProcessor *processor) // Advanced settings button auto advancedButton = new PixmapButton(this, tr("Advanced settings")); advancedButton->setToolTip(tr("Access advanced settings")); - auto advancedOnPixmap = new QPixmap(PLUGIN_NAME::getIconPixmap("advanced_on") - .scaled(advButtonSize, Qt::IgnoreAspectRatio, Qt::SmoothTransformation)); - auto advancedOffPixmap = new QPixmap(PLUGIN_NAME::getIconPixmap("advanced_off") - .scaled(advButtonSize, Qt::IgnoreAspectRatio, Qt::SmoothTransformation)); - advancedOnPixmap->setDevicePixelRatio(devicePixelRatio()); - advancedOffPixmap->setDevicePixelRatio(devicePixelRatio()); - advancedButton->setActiveGraphic(*advancedOnPixmap); - advancedButton->setInactiveGraphic(*advancedOffPixmap); + static auto s_advancedOnPixmap = PLUGIN_NAME::getIconPixmap("advanced_on") + .scaled(advButtonSize, Qt::IgnoreAspectRatio, Qt::SmoothTransformation); + static auto s_advancedOffPixmap = PLUGIN_NAME::getIconPixmap("advanced_off") + .scaled(advButtonSize, Qt::IgnoreAspectRatio, Qt::SmoothTransformation); + s_advancedOnPixmap.setDevicePixelRatio(devicePixelRatio()); + s_advancedOffPixmap.setDevicePixelRatio(devicePixelRatio()); + advancedButton->setActiveGraphic(s_advancedOnPixmap); + advancedButton->setInactiveGraphic(s_advancedOffPixmap); advancedButton->setCheckable(true); controls_layout->addStretch(0); controls_layout->addWidget(advancedButton); diff --git a/plugins/SpectrumAnalyzer/SaProcessor.cpp b/plugins/SpectrumAnalyzer/SaProcessor.cpp index a79d52bdc..b991ad3ea 100644 --- a/plugins/SpectrumAnalyzer/SaProcessor.cpp +++ b/plugins/SpectrumAnalyzer/SaProcessor.cpp @@ -41,6 +41,9 @@ #include "LocklessRingBuffer.h" #include "SaControls.h" +#include +#include + namespace lmms { @@ -50,7 +53,7 @@ SaProcessor::SaProcessor(const SaControls *controls) : m_terminate(false), m_inBlockSize(FFT_BLOCK_SIZES[0]), m_fftBlockSize(FFT_BLOCK_SIZES[0]), - m_sampleRate(Engine::audioEngine()->processingSampleRate()), + m_sampleRate(Engine::audioEngine()->outputSampleRate()), m_framesFilledUp(0), m_spectrumActive(false), m_waterfallActive(false), @@ -163,7 +166,7 @@ void SaProcessor::analyze(LocklessRingBuffer &ring_buffer) #endif // update sample rate - m_sampleRate = Engine::audioEngine()->processingSampleRate(); + m_sampleRate = Engine::audioEngine()->outputSampleRate(); // apply FFT window for (unsigned int i = 0; i < m_inBlockSize; i++) @@ -206,7 +209,6 @@ void SaProcessor::analyze(LocklessRingBuffer &ring_buffer) memset(pixel, 0, waterfallWidth() * sizeof (QRgb)); // add newest result on top - int target; // pixel being constructed float accL = 0; // accumulators for merging multiple bins float accR = 0; for (unsigned int i = 0; i < binCount(); i++) @@ -230,7 +232,8 @@ void SaProcessor::analyze(LocklessRingBuffer &ring_buffer) if (band_end - band_start > 1.0) { // band spans multiple pixels: draw all pixels it covers - for (target = std::max((int)band_start, 0); target < band_end && target < waterfallWidth(); target++) + for (int target = std::max(static_cast(band_start), 0); + target < band_end && target < waterfallWidth(); target++) { pixel[target] = makePixel(m_normSpectrumL[i], m_normSpectrumR[i]); } @@ -242,7 +245,7 @@ void SaProcessor::analyze(LocklessRingBuffer &ring_buffer) else { // sub-pixel drawing; add contribution of current band - target = (int)band_start; + int target = static_cast(band_start); if ((int)band_start == (int)band_end) { // band ends within current target pixel, accumulate @@ -267,7 +270,8 @@ void SaProcessor::analyze(LocklessRingBuffer &ring_buffer) else { // Linear: always draws one or more pixels per band - for (target = std::max((int)band_start, 0); target < band_end && target < waterfallWidth(); target++) + for (int target = std::max(static_cast(band_start), 0); + target < band_end && target < waterfallWidth(); target++) { pixel[target] = makePixel(m_normSpectrumL[i], m_normSpectrumR[i]); } @@ -358,30 +362,20 @@ void SaProcessor::setWaterfallActive(bool active) // Reallocate data buffers according to newly set block size. void SaProcessor::reallocateBuffers() { - unsigned int new_size_index = m_controls->m_blockSizeModel.value(); - unsigned int new_in_size, new_fft_size; - unsigned int new_bins; + m_zeroPadFactor = m_controls->m_zeroPaddingModel.value(); // get new block sizes and bin count based on selected index - if (new_size_index < FFT_BLOCK_SIZES.size()) - { - new_in_size = FFT_BLOCK_SIZES[new_size_index]; - } - else - { - new_in_size = FFT_BLOCK_SIZES.back(); - } - m_zeroPadFactor = m_controls->m_zeroPaddingModel.value(); - if (new_size_index + m_zeroPadFactor < FFT_BLOCK_SIZES.size()) - { - new_fft_size = FFT_BLOCK_SIZES[new_size_index + m_zeroPadFactor]; - } - else - { - new_fft_size = FFT_BLOCK_SIZES.back(); - } + const unsigned int new_size_index = m_controls->m_blockSizeModel.value(); - new_bins = new_fft_size / 2 +1; + const unsigned int new_in_size = new_size_index < FFT_BLOCK_SIZES.size() + ? FFT_BLOCK_SIZES[new_size_index] + : FFT_BLOCK_SIZES.back(); + + const unsigned int new_fft_size = (new_size_index + m_zeroPadFactor < FFT_BLOCK_SIZES.size()) + ? FFT_BLOCK_SIZES[new_size_index + m_zeroPadFactor] + : FFT_BLOCK_SIZES.back(); + + const unsigned int new_bins = new_fft_size / 2 + 1; // Use m_reallocating to tell analyze() to avoid asking for the lock. This // is needed because under heavy load the FFT thread requests data lock so @@ -650,7 +644,8 @@ float SaProcessor::ampToYPixel(float amplitude, unsigned int height) const if (m_controls->m_logYModel.value()) { // logarithmic scale: convert linear amplitude to dB (relative to 1.0) - float amplitude_dB = 10 * log10(amplitude); + assert (amplitude >= 0); + float amplitude_dB = 10 * std::log10(std::max(amplitude, std::numeric_limits::min())); if (amplitude_dB < getAmpRangeMin()) { return height; diff --git a/plugins/SpectrumAnalyzer/SaSpectrumView.cpp b/plugins/SpectrumAnalyzer/SaSpectrumView.cpp index 0d9c2af87..7f73ed7cc 100644 --- a/plugins/SpectrumAnalyzer/SaSpectrumView.cpp +++ b/plugins/SpectrumAnalyzer/SaSpectrumView.cpp @@ -647,14 +647,13 @@ float SaSpectrumView::ampToYPixel(float amplitude, unsigned int height) std::vector> SaSpectrumView::makeLogFreqTics(int low, int high) { std::vector> result; - int i, j; auto a = std::array{10, 20, 50}; // sparse series multipliers auto b = std::array{14, 30, 70}; // additional (denser) series // generate main steps (powers of 10); use the series to specify smaller steps - for (i = 1; i <= high; i *= 10) + for (int i = 1; i <= high; i *= 10) { - for (j = 0; j < 3; j++) + for (int j = 0; j < 3; j++) { // insert a label from sparse series if it falls within bounds if (i * a[j] >= low && i * a[j] <= high) @@ -691,7 +690,7 @@ std::vector> SaSpectrumView::makeLogFreqTics(int low std::vector> SaSpectrumView::makeLinearFreqTics(int low, int high) { std::vector> result; - int i, increment; + int increment; // select a suitable increment based on zoom level if (high - low < 500) {increment = 50;} @@ -700,7 +699,7 @@ std::vector> SaSpectrumView::makeLinearFreqTics(int else {increment = 2000;} // generate steps based on increment, starting at 0 - for (i = 0; i <= high; i += increment) + for (int i = 0; i <= high; i += increment) { if (i >= low) { @@ -724,7 +723,6 @@ std::vector> SaSpectrumView::makeLinearFreqTics(int std::vector> SaSpectrumView::makeLogAmpTics(int low, int high) { std::vector> result; - float i; double increment; // Base zoom level on selected range and how close is the current height @@ -744,7 +742,7 @@ std::vector> SaSpectrumView::makeLogAmpTics(int lo // Generate n dB increments, start checking at -90 dB. Limits are tweaked // just a little bit to make sure float comparisons do not miss edges. - for (i = 0.000000001; 10 * log10(i) <= (high + 0.001); i *= increment) + for (float i = 0.000000001; 10 * log10(i) <= (high + 0.001); i *= increment) { if (10 * log10(i) >= (low - 0.001)) { @@ -764,8 +762,6 @@ std::vector> SaSpectrumView::makeLogAmpTics(int lo std::vector> SaSpectrumView::makeLinearAmpTics(int low, int high) { std::vector> result; - double i, nearest; - // make about 5 labels when window is small, 10 if it is big float split = (float)height() / sizeHint().height() >= 1.5 ? 10.0 : 5.0; @@ -777,28 +773,28 @@ std::vector> SaSpectrumView::makeLinearAmpTics(int // multiples, just generate a few evenly spaced increments across the range, // paying attention only to the decimal places to keep labels short. // Limits are shifted a bit so that float comparisons do not miss edges. - for (i = 0; i <= (lin_high + 0.0001); i += (lin_high - lin_low) / split) + for (double i = 0; i <= (lin_high + 0.0001); i += (lin_high - lin_low) / split) { if (i >= (lin_low - 0.0001)) { if (i >= 9.99 && i < 99.9) { - nearest = std::round(i); + double nearest = std::round(i); result.emplace_back(nearest, std::to_string(nearest).substr(0, 2)); } else if (i >= 0.099) { // also covers numbers above 100 - nearest = std::round(i * 10) / 10; + double nearest = std::round(i * 10) / 10; result.emplace_back(nearest, std::to_string(nearest).substr(0, 3)); } else if (i >= 0.0099) { - nearest = std::round(i * 1000) / 1000; + double nearest = std::round(i * 1000) / 1000; result.emplace_back(nearest, std::to_string(nearest).substr(0, 4)); } else if (i >= 0.00099) { - nearest = std::round(i * 10000) / 10000; + double nearest = std::round(i * 10000) / 10000; result.emplace_back(nearest, std::to_string(nearest).substr(1, 4)); } else if (i > -0.01 && i < 0.01) diff --git a/plugins/SpectrumAnalyzer/SaWaterfallView.cpp b/plugins/SpectrumAnalyzer/SaWaterfallView.cpp index 598bad725..024c3aea4 100644 --- a/plugins/SpectrumAnalyzer/SaWaterfallView.cpp +++ b/plugins/SpectrumAnalyzer/SaWaterfallView.cpp @@ -213,7 +213,6 @@ float SaWaterfallView::yPixelToTime(float position, int height) std::vector> SaWaterfallView::makeTimeTics() { std::vector> result; - float i; // get time value of the last line float limit = yPixelToTime(m_displayBottom, m_displayHeight); @@ -223,7 +222,7 @@ std::vector> SaWaterfallView::makeTimeTics() if (increment < 0.1) {increment = 0.1;} // NOTE: labels positions are rounded to match the (rounded) label value - for (i = 0; i <= limit; i += increment) + for (float i = 0; i <= limit; i += increment) { if (i > 99) { diff --git a/plugins/StereoEnhancer/StereoEnhancer.cpp b/plugins/StereoEnhancer/StereoEnhancer.cpp index a7937a2ec..784003056 100644 --- a/plugins/StereoEnhancer/StereoEnhancer.cpp +++ b/plugins/StereoEnhancer/StereoEnhancer.cpp @@ -90,10 +90,6 @@ bool StereoEnhancerEffect::processAudioBuffer( sampleFrame * _buf, // audio with this effect double out_sum = 0.0; - float width; - int frameIndex = 0; - - if( !isEnabled() || !isRunning() ) { return( false ); @@ -110,10 +106,10 @@ bool StereoEnhancerEffect::processAudioBuffer( sampleFrame * _buf, m_delayBuffer[m_currFrame][1] = _buf[f][1]; // Get the width knob value from the Stereo Enhancer effect - width = m_seFX.wideCoeff(); + float width = m_seFX.wideCoeff(); // Calculate the correct sample frame for processing - frameIndex = m_currFrame - width; + int frameIndex = m_currFrame - width; if( frameIndex < 0 ) { @@ -149,8 +145,7 @@ bool StereoEnhancerEffect::processAudioBuffer( sampleFrame * _buf, void StereoEnhancerEffect::clearMyBuffer() { - int i; - for (i = 0; i < DEFAULT_BUFFER_SIZE; i++) + for (int i = 0; i < DEFAULT_BUFFER_SIZE; i++) { m_delayBuffer[i][0] = 0.0f; m_delayBuffer[i][1] = 0.0f; diff --git a/plugins/Stk/Mallets/Mallets.cpp b/plugins/Stk/Mallets/Mallets.cpp index dd3b09494..7f4548ba4 100644 --- a/plugins/Stk/Mallets/Mallets.cpp +++ b/plugins/Stk/Mallets/Mallets.cpp @@ -37,7 +37,6 @@ #include "AudioEngine.h" #include "ConfigManager.h" #include "Engine.h" -#include "gui_templates.h" #include "GuiApplication.h" #include "InstrumentTrack.h" @@ -85,7 +84,7 @@ MalletsInstrument::MalletsInstrument( InstrumentTrack * _instrument_track ): // TODO: m_vibratoModel m_velocityModel(64.0f, 0.1f, 128.0f, 0.1f, this, tr( "Speed" )), m_strikeModel( true, this, tr( "Bowed" ) ), - m_presetsModel(this), + m_presetsModel(this, tr("Instrument")), 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, "" ), @@ -343,7 +342,7 @@ void MalletsInstrument::playNote( NotePlayHandle * _n, m_vibratoFreqModel.value(), p, (uint8_t) m_spreadModel.value(), - Engine::audioEngine()->processingSampleRate() ); + Engine::audioEngine()->outputSampleRate() ); } else if( p == 9 ) { @@ -356,7 +355,7 @@ void MalletsInstrument::playNote( NotePlayHandle * _n, m_lfoSpeedModel.value(), m_adsrModel.value(), (uint8_t) m_spreadModel.value(), - Engine::audioEngine()->processingSampleRate() ); + Engine::audioEngine()->outputSampleRate() ); } else { @@ -369,7 +368,7 @@ void MalletsInstrument::playNote( NotePlayHandle * _n, m_strikeModel.value() * 128.0, speed, (uint8_t) m_spreadModel.value(), - Engine::audioEngine()->processingSampleRate() ); + Engine::audioEngine()->outputSampleRate() ); } m.unlock(); static_cast(_n->m_pluginData)->setPresetIndex(p); @@ -450,7 +449,6 @@ MalletsInstrumentView::MalletsInstrumentView( MalletsInstrument * _instrument, m_presetsCombo = new ComboBox( this, tr( "Instrument" ) ); m_presetsCombo->setGeometry( 140, 50, 99, ComboBox::DEFAULT_HEIGHT ); - m_presetsCombo->setFont( pointSize<8>( m_presetsCombo->font() ) ); connect( &_instrument->m_presetsModel, SIGNAL( dataChanged() ), this, SLOT( changePreset() ) ); diff --git a/plugins/TripleOscillator/TripleOscillator.cpp b/plugins/TripleOscillator/TripleOscillator.cpp index 5b8f6e8ad..97b773e67 100644 --- a/plugins/TripleOscillator/TripleOscillator.cpp +++ b/plugins/TripleOscillator/TripleOscillator.cpp @@ -23,8 +23,8 @@ */ - #include +#include #include "TripleOscillator.h" #include "AudioEngine.h" @@ -35,9 +35,11 @@ #include "Knob.h" #include "NotePlayHandle.h" #include "Oscillator.h" +#include "PathUtil.h" #include "PixmapButton.h" #include "SampleBuffer.h" - +#include "SampleLoader.h" +#include "Song.h" #include "embed.h" #include "plugin_export.h" @@ -133,22 +135,13 @@ OscillatorObject::OscillatorObject( Model * _parent, int _idx ) : } - - - -OscillatorObject::~OscillatorObject() -{ - sharedObject::unref( m_sampleBuffer ); -} - - - - void OscillatorObject::oscUserDefWaveDblClick() { - QString af = m_sampleBuffer->openAndSetWaveformFile(); + auto af = gui::SampleLoader::openWaveformFile(); if( af != "" ) { + m_sampleBuffer = gui::SampleLoader::createBufferFromFile(af); + m_userAntiAliasWaveTable = Oscillator::generateAntiAliasUserWaveTable(m_sampleBuffer.get()); // TODO: //m_usrWaveBtn->setToolTip(m_sampleBuffer->audioFile()); } @@ -184,7 +177,7 @@ void OscillatorObject::updateDetuningLeft() { m_detuningLeft = powf( 2.0f, ( (float)m_coarseModel.value() * 100.0f + (float)m_fineLeftModel.value() ) / 1200.0f ) - / Engine::audioEngine()->processingSampleRate(); + / Engine::audioEngine()->outputSampleRate(); } @@ -194,7 +187,7 @@ void OscillatorObject::updateDetuningRight() { m_detuningRight = powf( 2.0f, ( (float)m_coarseModel.value() * 100.0f + (float)m_fineRightModel.value() ) / 1200.0f ) - / Engine::audioEngine()->processingSampleRate(); + / Engine::audioEngine()->outputSampleRate(); } @@ -289,8 +282,16 @@ void TripleOscillator::loadSettings( const QDomElement & _this ) "modalgo" + QString::number( i+1 ) ); m_osc[i]->m_useWaveTableModel.loadSettings( _this, "useWaveTable" + QString::number (i+1 ) ); - m_osc[i]->m_sampleBuffer->setAudioFile( _this.attribute( - "userwavefile" + is ) ); + + if (auto userWaveFile = _this.attribute("userwavefile" + is); !userWaveFile.isEmpty()) + { + if (QFileInfo(PathUtil::toAbsolute(userWaveFile)).exists()) + { + m_osc[i]->m_sampleBuffer = gui::SampleLoader::createBufferFromFile(userWaveFile); + m_osc[i]->m_userAntiAliasWaveTable = Oscillator::generateAntiAliasUserWaveTable(m_osc[i]->m_sampleBuffer.get()); + } + else { Engine::getSong()->collectError(QString("%1: %2").arg(tr("Sample not found"), userWaveFile)); } + } } } @@ -360,7 +361,8 @@ void TripleOscillator::playNote( NotePlayHandle * _n, oscs_l[i]->setUserWave( m_osc[i]->m_sampleBuffer ); oscs_r[i]->setUserWave( m_osc[i]->m_sampleBuffer ); - + oscs_l[i]->setUserAntiAliasWaveTable(m_osc[i]->m_userAntiAliasWaveTable); + oscs_r[i]->setUserAntiAliasWaveTable(m_osc[i]->m_userAntiAliasWaveTable); } _n->m_pluginData = new oscPtr; diff --git a/plugins/TripleOscillator/TripleOscillator.h b/plugins/TripleOscillator/TripleOscillator.h index f3290153b..011352de4 100644 --- a/plugins/TripleOscillator/TripleOscillator.h +++ b/plugins/TripleOscillator/TripleOscillator.h @@ -26,9 +26,13 @@ #ifndef _TRIPLE_OSCILLATOR_H #define _TRIPLE_OSCILLATOR_H +#include + #include "Instrument.h" #include "InstrumentView.h" #include "AutomatableModel.h" +#include "OscillatorConstants.h" +#include "SampleBuffer.h" namespace lmms { @@ -53,13 +57,9 @@ const int NUM_OF_OSCILLATORS = 3; class OscillatorObject : public Model { - MM_OPERATORS Q_OBJECT public: OscillatorObject( Model * _parent, int _idx ); - ~OscillatorObject() override; - - private: FloatModel m_volumeModel; FloatModel m_panModel; @@ -71,7 +71,8 @@ private: IntModel m_waveShapeModel; IntModel m_modulationAlgoModel; BoolModel m_useWaveTableModel; - SampleBuffer* m_sampleBuffer; + std::shared_ptr m_sampleBuffer = SampleBuffer::emptyBuffer(); + std::shared_ptr m_userAntiAliasWaveTable; float m_volumeLeft; float m_volumeRight; @@ -120,9 +121,9 @@ public: QString nodeName() const override; - f_cnt_t desiredReleaseFrames() const override + float desiredReleaseTimeMs() const override { - return( 128 ); + return 3.f; } gui::PluginView* instantiateView( QWidget * _parent ) override; @@ -137,7 +138,6 @@ private: struct oscPtr { - MM_OPERATORS Oscillator * oscLeft; Oscillator * oscRight; } ; @@ -168,7 +168,6 @@ private: struct OscillatorKnobs { - MM_OPERATORS OscillatorKnobs( Knob * v, Knob * p, Knob * c, diff --git a/plugins/Vectorscope/VectorView.cpp b/plugins/Vectorscope/VectorView.cpp index f856f6429..c4776a688 100644 --- a/plugins/Vectorscope/VectorView.cpp +++ b/plugins/Vectorscope/VectorView.cpp @@ -138,8 +138,6 @@ void VectorView::paintEvent(QPaintEvent *event) std::size_t frameCount = inBuffer.size(); // Draw new points on top - float left, right; - int x, y; const bool logScale = m_controls->m_logarithmicModel.value(); const unsigned short activeSize = hq ? m_displaySize : m_displaySize / 2; @@ -164,6 +162,8 @@ void VectorView::paintEvent(QPaintEvent *event) // The longer the line is, the dimmer, simulating real electron trace on luminescent screen. for (std::size_t frame = 0; frame < frameCount; frame++) { + float left = 0.0f; + float right = 0.0f; float inLeft = inBuffer[frame][0] * m_zoom; float inRight = inBuffer[frame][1] * m_zoom; // Scale left and right channel from (-1.0, 1.0) to display range @@ -185,8 +185,8 @@ void VectorView::paintEvent(QPaintEvent *event) } // Rotate display coordinates 45 degrees, flip Y axis and make sure the result stays within bounds - x = saturate(right - left + activeSize / 2.f); - y = saturate(activeSize - (right + left + activeSize / 2.f)); + int x = saturate(right - left + activeSize / 2.f); + int y = saturate(activeSize - (right + left + activeSize / 2.f)); // Estimate number of points needed to fill space between the old and new pixel. Cap at 100. unsigned char points = std::min((int)sqrt((m_oldX - x) * (m_oldX - x) + (m_oldY - y) * (m_oldY - y)), 100); @@ -222,6 +222,8 @@ void VectorView::paintEvent(QPaintEvent *event) // one full-color pixel per sample. for (std::size_t frame = 0; frame < frameCount; frame++) { + float left = 0.0f; + float right = 0.0f; float inLeft = inBuffer[frame][0] * m_zoom; float inRight = inBuffer[frame][1] * m_zoom; if (logScale) { @@ -235,8 +237,8 @@ void VectorView::paintEvent(QPaintEvent *event) left = inLeft * (activeSize - 1) / 4; right = inRight * (activeSize - 1) / 4; } - x = saturate(right - left + activeSize / 2.f); - y = saturate(activeSize - (right + left + activeSize / 2.f)); + int x = saturate(right - left + activeSize / 2.f); + int y = saturate(activeSize - (right + left + activeSize / 2.f)); ((QRgb*)m_displayBuffer.data())[x + y * activeSize] = m_controls->m_colorFG.rgb(); } } diff --git a/plugins/Vestige/CMakeLists.txt b/plugins/Vestige/CMakeLists.txt index a51b051fd..0a5847889 100644 --- a/plugins/Vestige/CMakeLists.txt +++ b/plugins/Vestige/CMakeLists.txt @@ -1,14 +1,15 @@ -IF(LMMS_SUPPORT_VST) - INCLUDE(BuildPlugin) - LINK_DIRECTORIES("${CMAKE_CURRENT_BINARY_DIR}/..") - IF(LMMS_BUILD_LINUX) - LINK_LIBRARIES(-Wl,--enable-new-dtags) - SET(CMAKE_BUILD_WITH_INSTALL_RPATH TRUE) - SET(CMAKE_INSTALL_RPATH "$ORIGIN") - ELSE() - SET(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/${PLUGIN_DIR}") - ENDIF() - BUILD_PLUGIN(vestige Vestige.cpp Vestige.h MOCFILES Vestige.h EMBEDDED_RESOURCES "${CMAKE_CURRENT_SOURCE_DIR}/*.png") - TARGET_LINK_LIBRARIES(vestige vstbase) -ENDIF(LMMS_SUPPORT_VST) +if(NOT LMMS_HAVE_VST) + return() +endif() +include(BuildPlugin) +link_directories("${CMAKE_CURRENT_BINARY_DIR}/..") +if(LMMS_BUILD_LINUX) + link_libraries(-Wl,--enable-new-dtags) + set(CMAKE_BUILD_WITH_INSTALL_RPATH TRUE) + set(CMAKE_INSTALL_RPATH "$ORIGIN") +else() + set(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/${PLUGIN_DIR}") +endif() +build_plugin(vestige Vestige.cpp Vestige.h MOCFILES Vestige.h EMBEDDED_RESOURCES "${CMAKE_CURRENT_SOURCE_DIR}/*.png") +target_link_libraries(vestige vstbase) diff --git a/plugins/Vestige/Vestige.cpp b/plugins/Vestige/Vestige.cpp index a696a4b2d..1de713960 100644 --- a/plugins/Vestige/Vestige.cpp +++ b/plugins/Vestige/Vestige.cpp @@ -152,7 +152,7 @@ private: VestigeInstrument::VestigeInstrument( InstrumentTrack * _instrument_track ) : - Instrument( _instrument_track, &vestige_plugin_descriptor ), + Instrument(_instrument_track, &vestige_plugin_descriptor, nullptr, Flag::IsSingleStreamed | Flag::IsMidiBased), m_plugin( nullptr ), m_pluginMutex(), m_subWindow( nullptr ), @@ -485,21 +485,11 @@ gui::PluginView * VestigeInstrument::instantiateView( QWidget * _parent ) namespace gui { -QPixmap * VestigeInstrumentView::s_artwork = nullptr; -QPixmap * ManageVestigeInstrumentView::s_artwork = nullptr; - - VestigeInstrumentView::VestigeInstrumentView( Instrument * _instrument, QWidget * _parent ) : InstrumentViewFixedSize( _instrument, _parent ), lastPosInMenu (0) { - if( s_artwork == nullptr ) - { - s_artwork = new QPixmap( PLUGIN_NAME::getIconPixmap( - "artwork" ) ); - } - m_openPluginButton = new PixmapButton( this, "" ); m_openPluginButton->setCheckable( false ); m_openPluginButton->setCursor( Qt::PointingHandCursor ); @@ -593,11 +583,12 @@ VestigeInstrumentView::VestigeInstrumentView( Instrument * _instrument, m_selPresetButton->setMenu(menu); + constexpr int buttonFontSize = 12; m_toggleGUIButton = new QPushButton( tr( "Show/hide GUI" ), this ); m_toggleGUIButton->setGeometry( 20, 130, 200, 24 ); m_toggleGUIButton->setIcon( embed::getIconPixmap( "zoom" ) ); - m_toggleGUIButton->setFont( pointSize<8>( m_toggleGUIButton->font() ) ); + m_toggleGUIButton->setFont(adjustedToPixelSize(m_toggleGUIButton->font(), buttonFontSize)); connect( m_toggleGUIButton, SIGNAL( clicked() ), this, SLOT( toggleGUI() ) ); @@ -606,7 +597,7 @@ VestigeInstrumentView::VestigeInstrumentView( Instrument * _instrument, this); note_off_all_btn->setGeometry( 20, 160, 200, 24 ); note_off_all_btn->setIcon( embed::getIconPixmap( "stop" ) ); - note_off_all_btn->setFont( pointSize<8>( note_off_all_btn->font() ) ); + note_off_all_btn->setFont(adjustedToPixelSize(note_off_all_btn->font(), buttonFontSize)); connect( note_off_all_btn, SIGNAL( clicked() ), this, SLOT( noteOffAll() ) ); @@ -881,7 +872,8 @@ void VestigeInstrumentView::paintEvent( QPaintEvent * ) { QPainter p( this ); - p.drawPixmap( 0, 0, *s_artwork ); + static auto s_artwork = PLUGIN_NAME::getIconPixmap("artwork"); + p.drawPixmap(0, 0, s_artwork); QString plugin_name = ( m_vi->m_plugin != nullptr ) ? m_vi->m_plugin->name()/* + QString::number( @@ -890,7 +882,7 @@ void VestigeInstrumentView::paintEvent( QPaintEvent * ) tr( "No VST plugin loaded" ); QFont f = p.font(); f.setBold( true ); - p.setFont( pointSize<10>( f ) ); + p.setFont(adjustedToPixelSize(f, 10)); p.setPen( QColor( 255, 255, 255 ) ); p.drawText( 10, 100, plugin_name ); @@ -902,7 +894,7 @@ void VestigeInstrumentView::paintEvent( QPaintEvent * ) { p.setPen( QColor( 0, 0, 0 ) ); f.setBold( false ); - p.setFont( pointSize<8>( f ) ); + p.setFont(adjustedToPixelSize(f, 8)); p.drawText( 10, 114, tr( "by " ) + m_vi->m_plugin->vendorString() ); p.setPen( QColor( 255, 255, 255 ) ); @@ -1062,7 +1054,6 @@ void ManageVestigeInstrumentView::syncPlugin( void ) auto paramStr = std::array{}; QStringList s_dumpValues; const QMap & dump = m_vi->m_plugin->parameterDump(); - float f_value; for( int i = 0; i < m_vi->paramCount; i++ ) { @@ -1072,7 +1063,7 @@ void ManageVestigeInstrumentView::syncPlugin( void ) { sprintf(paramStr.data(), "param%d", i); s_dumpValues = dump[paramStr.data()].split(":"); - f_value = LocaleHelper::toFloat(s_dumpValues.at(2)); + float f_value = LocaleHelper::toFloat(s_dumpValues.at(2)); m_vi->knobFModel[ i ]->setAutomatedValue( f_value ); m_vi->knobFModel[ i ]->setInitValue( f_value ); } diff --git a/plugins/Vestige/Vestige.h b/plugins/Vestige/Vestige.h index f740913ea..529893ba0 100644 --- a/plugins/Vestige/Vestige.h +++ b/plugins/Vestige/Vestige.h @@ -70,11 +70,6 @@ public: virtual void loadFile( const QString & _file ); - virtual Flags flags() const - { - return Flag::IsSingleStreamed | Flag::IsMidiBased; - } - virtual bool handleMidiEvent( const MidiEvent& event, const TimePos& time, f_cnt_t offset = 0 ); virtual gui::PluginView* instantiateView( QWidget * _parent ); @@ -131,8 +126,6 @@ protected: private: - static QPixmap * s_artwork; - VestigeInstrument * m_vi; QWidget *widget; @@ -175,7 +168,6 @@ protected: private: virtual void modelChanged(); - static QPixmap * s_artwork; VestigeInstrument * m_vi; diff --git a/plugins/Vibed/Vibed.cpp b/plugins/Vibed/Vibed.cpp index ad6a3942a..f99c9140d 100644 --- a/plugins/Vibed/Vibed.cpp +++ b/plugins/Vibed/Vibed.cpp @@ -33,7 +33,6 @@ #include "InstrumentTrack.h" #include "NotePlayHandle.h" #include "VibratingString.h" -#include "MemoryManager.h" #include "base64.h" #include "CaptionMenu.h" #include "volume.h" @@ -67,7 +66,6 @@ Plugin::Descriptor PLUGIN_EXPORT vibedstrings_plugin_descriptor = class Vibed::StringContainer { - MM_OPERATORS public: StringContainer(float pitch, sample_rate_t sampleRate, int bufferLength) : m_pitch(pitch), m_sampleRate(sampleRate), m_bufferLength(bufferLength) {} @@ -99,7 +97,7 @@ private: }; Vibed::Vibed(InstrumentTrack* instrumentTrack) : - Instrument(instrumentTrack, &vibedstrings_plugin_descriptor) + Instrument(instrumentTrack, &vibedstrings_plugin_descriptor, nullptr, Flag::IsNotBendable) { for (int harm = 0; harm < s_stringCount; ++harm) { @@ -208,7 +206,7 @@ void Vibed::playNote(NotePlayHandle* n, sampleFrame* workingBuffer) if (!n->m_pluginData) { const auto newContainer = new StringContainer{n->frequency(), - Engine::audioEngine()->processingSampleRate(), s_sampleLength}; + Engine::audioEngine()->outputSampleRate(), s_sampleLength}; n->m_pluginData = newContainer; diff --git a/plugins/Vibed/Vibed.h b/plugins/Vibed/Vibed.h index 18d334c4d..ec8395da1 100644 --- a/plugins/Vibed/Vibed.h +++ b/plugins/Vibed/Vibed.h @@ -65,8 +65,6 @@ public: QString nodeName() const override; - Flags flags() const override { return Flag::IsNotBendable; } - gui::PluginView* instantiateView(QWidget* parent) override; private: diff --git a/plugins/Vibed/VibratingString.cpp b/plugins/Vibed/VibratingString.cpp index 44ade3c3a..216a5fbf3 100644 --- a/plugins/Vibed/VibratingString.cpp +++ b/plugins/Vibed/VibratingString.cpp @@ -75,12 +75,10 @@ std::unique_ptr VibratingString::initDelayLine(int l if (len > 0) { dl->data = std::make_unique(len); - float r; - float offset = 0.0f; for (int i = 0; i < dl->length; ++i) { - r = static_cast(std::rand()) / RAND_MAX; - offset = (m_randomize / 2.0f - m_randomize) * r; + float r = static_cast(std::rand()) / RAND_MAX; + float offset = (m_randomize / 2.0f - m_randomize) * r; dl->data[i] = offset; } } diff --git a/plugins/Vibed/VibratingString.h b/plugins/Vibed/VibratingString.h index efdaec282..0ad5a6e6e 100644 --- a/plugins/Vibed/VibratingString.h +++ b/plugins/Vibed/VibratingString.h @@ -49,8 +49,6 @@ public: sample_t nextSample() { - sample_t ym0; - sample_t ypM; for (int i = 0; i < m_oversample; ++i) { // Output at pickup position @@ -58,9 +56,9 @@ public: m_outsamp[i] += toBridgeAccess(m_toBridge.get(), m_pickupLoc); // Sample traveling into "bridge" - ym0 = toBridgeAccess(m_toBridge.get(), 1); + sample_t ym0 = toBridgeAccess(m_toBridge.get(), 1); // Sample to "nut" - ypM = fromBridgeAccess(m_fromBridge.get(), m_fromBridge->length - 2); + sample_t ypM = fromBridgeAccess(m_fromBridge.get(), m_fromBridge->length - 2); // String state update @@ -105,21 +103,18 @@ private: */ void setDelayLine(DelayLine* dl, int pick, const float* values, int len, float scale, bool state) { - float r; - float offset; - if (!state) { for (int i = 0; i < pick; ++i) { - r = static_cast(std::rand()) / RAND_MAX; - offset = (m_randomize / 2.0f - m_randomize) * r; + float r = static_cast(std::rand()) / RAND_MAX; + float offset = (m_randomize / 2.0f - m_randomize) * r; dl->data[i] = scale * values[dl->length - i - 1] + offset; } for (int i = pick; i < dl->length; ++i) { - r = static_cast(std::rand()) / RAND_MAX; - offset = (m_randomize / 2.0f - m_randomize) * r; + float r = static_cast(std::rand()) / RAND_MAX; + float offset = (m_randomize / 2.0f - m_randomize) * r; dl->data[i] = scale * values[i - pick] + offset; } } @@ -129,8 +124,8 @@ private: { for (int i = pick; i < dl->length; ++i) { - r = static_cast(std::rand()) / RAND_MAX; - offset = (m_randomize / 2.0f - m_randomize) * r; + float r = static_cast(std::rand()) / RAND_MAX; + float offset = (m_randomize / 2.0f - m_randomize) * r; dl->data[i] = scale * values[i - pick] + offset; } } @@ -138,8 +133,8 @@ private: { for (int i = 0; i < len; ++i) { - r = static_cast(std::rand()) / RAND_MAX; - offset = (m_randomize / 2.0f - m_randomize) * r; + float r = static_cast(std::rand()) / RAND_MAX; + float offset = (m_randomize / 2.0f - m_randomize) * r; dl->data[i+pick] = scale * values[i] + offset; } } diff --git a/plugins/VstBase/CMakeLists.txt b/plugins/VstBase/CMakeLists.txt index 8d3262b1a..046f515ea 100644 --- a/plugins/VstBase/CMakeLists.txt +++ b/plugins/VstBase/CMakeLists.txt @@ -1,4 +1,4 @@ -IF(NOT LMMS_SUPPORT_VST) +if(NOT LMMS_HAVE_VST) RETURN() ENDIF() @@ -47,11 +47,11 @@ foreach(var ${export_variables}) endforeach() # build 32 bit version of RemoteVstPlugin -IF(WANT_VST_32) +if(LMMS_HAVE_VST_32) INCLUDE("${CMAKE_CURRENT_LIST_DIR}/RemoteVstPlugin32.cmake") ENDIF() # build 64 bit version of RemoteVstPlugin -IF(WANT_VST_64) +if(LMMS_HAVE_VST_64) INCLUDE("${CMAKE_CURRENT_LIST_DIR}/RemoteVstPlugin64.cmake") ENDIF() diff --git a/plugins/VstBase/NativeLinuxRemoteVstPlugin64.cmake b/plugins/VstBase/NativeLinuxRemoteVstPlugin64.cmake index dc5823604..d9ef2897d 100644 --- a/plugins/VstBase/NativeLinuxRemoteVstPlugin64.cmake +++ b/plugins/VstBase/NativeLinuxRemoteVstPlugin64.cmake @@ -1,11 +1,13 @@ IF(LMMS_BUILD_LINUX) + if(LMMS_HOST_X86_64) + set(CXX_FLAGS -m64) + endif() ExternalProject_Add(NativeLinuxRemoteVstPlugin64 "${EXTERNALPROJECT_ARGS}" CMAKE_ARGS "${EXTERNALPROJECT_CMAKE_ARGS}" - "-DCMAKE_CXX_FLAGS=-m64 -DNATIVE_LINUX_VST" + "-DCMAKE_CXX_FLAGS=${CXX_FLAGS} -DNATIVE_LINUX_VST" "-DCMAKE_C_FLAGS=-DNATIVE_LINUX_VST" ) INSTALL(PROGRAMS "${CMAKE_CURRENT_BINARY_DIR}/../NativeLinuxRemoteVstPlugin64" DESTINATION "${PLUGIN_DIR}") -ENDIF() - +ENDIF() \ No newline at end of file diff --git a/plugins/VstBase/RemoteVstPlugin/CMakeLists.txt b/plugins/VstBase/RemoteVstPlugin/CMakeLists.txt index 3356ae596..c75b95ab7 100644 --- a/plugins/VstBase/RemoteVstPlugin/CMakeLists.txt +++ b/plugins/VstBase/RemoteVstPlugin/CMakeLists.txt @@ -22,6 +22,9 @@ FOREACH( OUTPUTCONFIG ${CMAKE_CONFIGURATION_TYPES} ) SET("CMAKE_RUNTIME_OUTPUT_DIRECTORY_${OUTPUTCONFIG}" "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}") ENDFOREACH() +# Import of windows.h breaks min()/max() +add_definitions(-DNOMINMAX) + ADD_SUBDIRECTORY("${LMMS_SOURCE_DIR}/src/common" common) if(NOT IS_WIN) @@ -64,12 +67,6 @@ if(MSVC) ) endif() - -if(WIN32) - find_package(Qt5Core REQUIRED) - target_link_libraries(${EXE_NAME} Qt5::Core) -endif() - if(IS_MINGW) SET(CMAKE_REQUIRED_FLAGS "-std=c++17") @@ -96,7 +93,9 @@ if(LMMS_BUILD_WIN32) set(NOOP_COMMAND "${CMAKE_COMMAND}" "-E" "echo") endif() if(STRIP) - set(STRIP_COMMAND "$,${NOOP_COMMAND},${STRIP}>") + # TODO CMake 3.19: Now that CONFIG generator expressions support testing for + # multiple configurations, combine the OR into a single CONFIG expression. + set(STRIP_COMMAND "$,$>,${NOOP_COMMAND},${STRIP}>") else() set(STRIP_COMMAND "${NOOP_COMMAND}") endif() diff --git a/plugins/VstBase/RemoteVstPlugin32.cmake b/plugins/VstBase/RemoteVstPlugin32.cmake index f39bd93d0..5afb7db81 100644 --- a/plugins/VstBase/RemoteVstPlugin32.cmake +++ b/plugins/VstBase/RemoteVstPlugin32.cmake @@ -17,23 +17,6 @@ ENDMACRO() IF(LMMS_BUILD_WIN32 AND NOT LMMS_BUILD_WIN64) ADD_SUBDIRECTORY(RemoteVstPlugin) ELSEIF(LMMS_BUILD_WIN64 AND MSVC) - IF(NOT QT_32_PREFIX) - SET(LMMS_MSVC_YEAR_FOR_QT ${LMMS_MSVC_YEAR}) - - if(LMMS_MSVC_YEAR_FOR_QT EQUAL 2019 AND Qt5_VERSION VERSION_LESS "5.15") - SET(LMMS_MSVC_YEAR_FOR_QT 2017) # Qt only provides binaries for MSVC 2017, but 2019 is binary compatible - endif() - - GET_FILENAME_COMPONENT(QT_BIN_DIR ${QT_QMAKE_EXECUTABLE} DIRECTORY) - SET(QT_32_PREFIX "${QT_BIN_DIR}/../../msvc${LMMS_MSVC_YEAR_FOR_QT}") - ENDIF() - - #TODO: qt5 installed using vcpkg: I don't know how to detect if the user built the x86 version of qt5 from here. At least not cleanly. - #So for the moment, we'll allow the built. - IF(NOT (IS_DIRECTORY ${QT_32_PREFIX} AND EXISTS ${QT_32_PREFIX}/bin/qmake.exe)) - MESSAGE(WARNING "No Qt 32 bit installation found at ${QT_32_PREFIX}. If you're using VCPKG you can ignore this message if you've built x86-windows version of qt5") - ENDIF() - ExternalProject_Add(RemoteVstPlugin32 "${EXTERNALPROJECT_ARGS}" CMAKE_GENERATOR "${LMMS_MSVC_GENERATOR}" @@ -42,18 +25,10 @@ ELSEIF(LMMS_BUILD_WIN64 AND MSVC) CMAKE_ARGS "${EXTERNALPROJECT_CMAKE_ARGS}" "-DCMAKE_TOOLCHAIN_FILE=${CMAKE_TOOLCHAIN_FILE}" - "-DCMAKE_PREFIX_PATH=${QT_32_PREFIX}" ) INSTALL_EXTERNAL_PROJECT(RemoteVstPlugin32) ELSEIF(LMMS_BUILD_LINUX) - # Use winegcc - INCLUDE(CheckWineGcc) - CheckWineGcc(32 "${WINEGCC}" WINEGCC_WORKING) - IF(NOT WINEGCC_WORKING) - MESSAGE(WARNING "winegcc fails to compile 32-bit binaries, please make sure you have 32-bit GCC libraries") - RETURN() - ENDIF() ExternalProject_Add(RemoteVstPlugin32 "${EXTERNALPROJECT_ARGS}" CMAKE_ARGS @@ -63,7 +38,6 @@ ELSEIF(LMMS_BUILD_LINUX) ) INSTALL(PROGRAMS "${CMAKE_CURRENT_BINARY_DIR}/../32/RemoteVstPlugin32" "${CMAKE_CURRENT_BINARY_DIR}/../32/RemoteVstPlugin32.exe.so" DESTINATION "${PLUGIN_DIR}/32") - ELSEIF(CMAKE_TOOLCHAIN_FILE_32) ExternalProject_Add(RemoteVstPlugin32 "${EXTERNALPROJECT_ARGS}" diff --git a/plugins/VstBase/RemoteVstPlugin64.cmake b/plugins/VstBase/RemoteVstPlugin64.cmake index 65b33a162..2f4a745ac 100644 --- a/plugins/VstBase/RemoteVstPlugin64.cmake +++ b/plugins/VstBase/RemoteVstPlugin64.cmake @@ -1,12 +1,6 @@ IF(LMMS_BUILD_WIN64) ADD_SUBDIRECTORY(RemoteVstPlugin) ELSEIF(LMMS_BUILD_LINUX) - INCLUDE(CheckWineGcc) - CheckWineGcc(64 "${WINEGCC}" WINEGCC_WORKING) - IF(NOT WINEGCC_WORKING) - MESSAGE(WARNING "winegcc fails to compile 64-bit binaries, please make sure you have 64-bit GCC libraries") - RETURN() - ENDIF() ExternalProject_Add(RemoteVstPlugin64 "${EXTERNALPROJECT_ARGS}" CMAKE_ARGS diff --git a/plugins/VstBase/VstPlugin.cpp b/plugins/VstBase/VstPlugin.cpp index b23ae39bf..0361d4c25 100644 --- a/plugins/VstBase/VstPlugin.cpp +++ b/plugins/VstBase/VstPlugin.cpp @@ -338,7 +338,7 @@ void VstPlugin::updateSampleRate() { lock(); sendMessage( message( IdSampleRateInformation ). - addInt( Engine::audioEngine()->processingSampleRate() ) ); + addInt( Engine::audioEngine()->outputSampleRate() ) ); waitForMessage( IdInformationUpdated, true ); unlock(); } @@ -735,14 +735,12 @@ void VstPlugin::createUI( QWidget * parent ) QWidget* container = nullptr; -#if QT_VERSION >= 0x050100 if (m_embedMethod == "qt" ) { QWindow* vw = QWindow::fromWinId(m_pluginWindowID); container = QWidget::createWindowContainer(vw, parent ); container->installEventFilter(this); } else -#endif #ifdef LMMS_BUILD_WIN32 if (m_embedMethod == "win32" ) @@ -801,7 +799,6 @@ void VstPlugin::createUI( QWidget * parent ) bool VstPlugin::eventFilter(QObject *obj, QEvent *event) { -#if QT_VERSION >= 0x050100 if (embedMethod() == "qt" && obj == m_pluginWidget) { if (event->type() == QEvent::Show) { @@ -809,7 +806,6 @@ bool VstPlugin::eventFilter(QObject *obj, QEvent *event) } qDebug() << obj << event; } -#endif return false; } diff --git a/plugins/VstEffect/CMakeLists.txt b/plugins/VstEffect/CMakeLists.txt index 68ef141d9..3cbe8e8cc 100644 --- a/plugins/VstEffect/CMakeLists.txt +++ b/plugins/VstEffect/CMakeLists.txt @@ -1,4 +1,7 @@ -IF(LMMS_SUPPORT_VST) +if(NOT LMMS_HAVE_VST) + return() +endif() + INCLUDE(BuildPlugin) INCLUDE_DIRECTORIES("${CMAKE_CURRENT_SOURCE_DIR}/../vst_base") LINK_DIRECTORIES("${CMAKE_CURRENT_BINARY_DIR}/..") @@ -12,6 +15,3 @@ ENDIF() BUILD_PLUGIN(vsteffect VstEffect.cpp VstEffectControls.cpp VstEffectControlDialog.cpp VstSubPluginFeatures.cpp VstEffect.h VstEffectControls.h VstEffectControlDialog.h VstSubPluginFeatures.h MOCFILES VstEffectControlDialog.h VstEffectControls.h EMBEDDED_RESOURCES *.png) TARGET_LINK_LIBRARIES(vsteffect vstbase) - -ENDIF(LMMS_SUPPORT_VST) - diff --git a/plugins/VstEffect/VstEffectControlDialog.cpp b/plugins/VstEffect/VstEffectControlDialog.cpp index 5bee94155..0fb4913a3 100644 --- a/plugins/VstEffect/VstEffectControlDialog.cpp +++ b/plugins/VstEffect/VstEffectControlDialog.cpp @@ -246,7 +246,7 @@ VstEffectControlDialog::VstEffectControlDialog( VstEffectControls * _ctl ) : tb->addWidget(space1); tbLabel = new QLabel( tr( "Effect by: " ), this ); - tbLabel->setFont( pointSize<7>( f ) ); + tbLabel->setFont(adjustedToPixelSize(f, 7)); tbLabel->setTextFormat(Qt::RichText); tbLabel->setAlignment( Qt::AlignTop | Qt::AlignLeft ); tb->addWidget( tbLabel ); diff --git a/plugins/VstEffect/VstEffectControls.cpp b/plugins/VstEffect/VstEffectControls.cpp index cf0c831a6..c9eb49234 100644 --- a/plugins/VstEffect/VstEffectControls.cpp +++ b/plugins/VstEffect/VstEffectControls.cpp @@ -50,7 +50,6 @@ VstEffectControls::VstEffectControls( VstEffect * _eff ) : EffectControls( _eff ), m_effect( _eff ), m_subWindow( nullptr ), - knobFModel( nullptr ), ctrHandle( nullptr ), lastPosInMenu (0), m_vstGuiVisible ( true ) @@ -84,7 +83,7 @@ void VstEffectControls::loadSettings( const QDomElement & _this ) const QMap & dump = m_effect->m_plugin->parameterDump(); paramCount = dump.size(); auto paramStr = std::array{}; - knobFModel = new FloatModel *[ paramCount ]; + knobFModel.resize(paramCount); QStringList s_dumpValues; for( int i = 0; i < paramCount; i++ ) { @@ -131,7 +130,7 @@ void VstEffectControls::saveSettings( QDomDocument & _doc, QDomElement & _this ) if( m_effect->m_plugin != nullptr ) { m_effect->m_plugin->saveSettings( _doc, _this ); - if (knobFModel != nullptr) { + if (!knobFModel.empty()) { const QMap & dump = m_effect->m_plugin->parameterDump(); paramCount = dump.size(); auto paramStr = std::array{}; @@ -376,8 +375,9 @@ ManageVSTEffectView::ManageVSTEffectView( VstEffect * _eff, VstEffectControls * vstKnobs = new CustomTextKnob *[ m_vi->paramCount ]; bool hasKnobModel = true; - if (m_vi->knobFModel == nullptr) { - m_vi->knobFModel = new FloatModel *[ m_vi->paramCount ]; + if (m_vi->knobFModel.empty()) + { + m_vi->knobFModel.resize(m_vi->paramCount); hasKnobModel = false; } @@ -452,7 +452,6 @@ void ManageVSTEffectView::syncPlugin() auto paramStr = std::array{}; QStringList s_dumpValues; const QMap & dump = m_effect->m_plugin->parameterDump(); - float f_value; for( int i = 0; i < m_vi2->paramCount; i++ ) { @@ -463,7 +462,7 @@ void ManageVSTEffectView::syncPlugin() { sprintf(paramStr.data(), "param%d", i); s_dumpValues = dump[paramStr.data()].split(":"); - f_value = LocaleHelper::toFloat(s_dumpValues.at(2)); + float f_value = LocaleHelper::toFloat(s_dumpValues.at(2)); m_vi2->knobFModel[ i ]->setAutomatedValue( f_value ); m_vi2->knobFModel[ i ]->setInitValue( f_value ); } @@ -543,7 +542,7 @@ void ManageVSTEffectView::syncParameterText() ManageVSTEffectView::~ManageVSTEffectView() { - if( m_vi2->knobFModel != nullptr ) + if (!m_vi2->knobFModel.empty()) { for( int i = 0; i < m_vi2->paramCount; i++ ) { @@ -558,11 +557,7 @@ ManageVSTEffectView::~ManageVSTEffectView() vstKnobs = nullptr; } - if( m_vi2->knobFModel != nullptr ) - { - delete [] m_vi2->knobFModel; - m_vi2->knobFModel = nullptr; - } + m_vi2->knobFModel.clear(); if( m_vi2->m_scrollArea != nullptr ) { diff --git a/plugins/VstEffect/VstEffectControls.h b/plugins/VstEffect/VstEffectControls.h index 42178b5b6..e2bea36e8 100644 --- a/plugins/VstEffect/VstEffectControls.h +++ b/plugins/VstEffect/VstEffectControls.h @@ -89,7 +89,7 @@ private: QMdiSubWindow * m_subWindow; QScrollArea * m_scrollArea; - FloatModel ** knobFModel; + std::vector knobFModel; int paramCount; QObject * ctrHandle; diff --git a/plugins/Watsyn/CMakeLists.txt b/plugins/Watsyn/CMakeLists.txt index 5aec12a46..b43abbb07 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/Watsyn/Watsyn.cpp b/plugins/Watsyn/Watsyn.cpp index 8e49942e1..822f9b519 100644 --- a/plugins/Watsyn/Watsyn.cpp +++ b/plugins/Watsyn/Watsyn.cpp @@ -332,7 +332,7 @@ void WatsynInstrument::playNote( NotePlayHandle * _n, if (!_n->m_pluginData) { auto w = new WatsynObject(&A1_wave[0], &A2_wave[0], &B1_wave[0], &B2_wave[0], m_amod.value(), m_bmod.value(), - Engine::audioEngine()->processingSampleRate(), _n, Engine::audioEngine()->framesPerPeriod(), this); + Engine::audioEngine()->outputSampleRate(), _n, Engine::audioEngine()->framesPerPeriod(), this); _n->m_pluginData = w; } diff --git a/plugins/Watsyn/Watsyn.h b/plugins/Watsyn/Watsyn.h index 3a736e162..b34e28f60 100644 --- a/plugins/Watsyn/Watsyn.h +++ b/plugins/Watsyn/Watsyn.h @@ -32,7 +32,6 @@ #include "AutomatableModel.h" #include "TempoSyncKnob.h" #include -#include "MemoryManager.h" namespace lmms { @@ -88,7 +87,6 @@ class WatsynView; class WatsynObject { - MM_OPERATORS public: WatsynObject( float * _A1wave, float * _A2wave, float * _B1wave, float * _B2wave, @@ -152,9 +150,9 @@ public: QString nodeName() const override; - f_cnt_t desiredReleaseFrames() const override + float desiredReleaseTimeMs() const override { - return( 64 ); + return 1.5f; } gui::PluginView* instantiateView( QWidget * _parent ) override; diff --git a/plugins/Xpressive/ExprSynth.cpp b/plugins/Xpressive/ExprSynth.cpp index 991e0d3e6..736b47598 100644 --- a/plugins/Xpressive/ExprSynth.cpp +++ b/plugins/Xpressive/ExprSynth.cpp @@ -322,14 +322,8 @@ struct RandomVectorSeedFunction : public exprtk::ifunction inline float operator()(const float& index,const float& seed) override { - int irseed; - if (seed < 0 || std::isnan(seed) || std::isinf(seed)) - { - irseed=0; - } - else - irseed=(int)seed; - return randv(index,irseed); + const int irseed = seed < 0 || std::isnan(seed) || std::isinf(seed) ? 0 : static_cast(seed); + return randv(index, irseed); } static const int data_size=sizeof(random_data)/sizeof(int); diff --git a/plugins/Xpressive/ExprSynth.h b/plugins/Xpressive/ExprSynth.h index f338b78fc..5d664c85e 100644 --- a/plugins/Xpressive/ExprSynth.h +++ b/plugins/Xpressive/ExprSynth.h @@ -30,7 +30,6 @@ #include #include "AutomatableModel.h" #include "Graph.h" -#include "MemoryManager.h" namespace lmms { @@ -102,7 +101,6 @@ public: class ExprSynth { - MM_OPERATORS public: ExprSynth(const WaveSample* gW1, const WaveSample* gW2, const WaveSample* gW3, ExprFront* exprO1, ExprFront* exprO2, NotePlayHandle* nph, const sample_rate_t sample_rate, const FloatModel* pan1, const FloatModel* pan2, float rel_trans); diff --git a/plugins/Xpressive/Xpressive.cpp b/plugins/Xpressive/Xpressive.cpp index babc37231..e90abb24c 100644 --- a/plugins/Xpressive/Xpressive.cpp +++ b/plugins/Xpressive/Xpressive.cpp @@ -204,14 +204,14 @@ void Xpressive::playNote(NotePlayHandle* nph, sampleFrame* working_buffer) { if (!nph->m_pluginData) { auto exprO1 = new ExprFront(m_outputExpression[0].constData(), - Engine::audioEngine()->processingSampleRate()); // give the "last" function a whole second - auto exprO2 = new ExprFront(m_outputExpression[1].constData(), Engine::audioEngine()->processingSampleRate()); + Engine::audioEngine()->outputSampleRate()); // give the "last" function a whole second + auto exprO2 = new ExprFront(m_outputExpression[1].constData(), Engine::audioEngine()->outputSampleRate()); auto init_expression_step1 = [this, nph](ExprFront* e) { //lambda function to init exprO1 and exprO2 //add the constants and the variables to the expression. e->add_constant("key", nph->key());//the key that was pressed. e->add_constant("bnote", nph->instrumentTrack()->baseNote()); // the base note - e->add_constant("srate", Engine::audioEngine()->processingSampleRate());// sample rate of the audio engine + e->add_constant("srate", Engine::audioEngine()->outputSampleRate());// sample rate of the audio engine e->add_constant("v", nph->getVolume() / 255.0); //volume of the note. e->add_constant("tempo", Engine::getSong()->getTempo());//tempo of the song. e->add_variable("A1", m_A1);//A1,A2,A3: general purpose input controls. @@ -225,7 +225,7 @@ void Xpressive::playNote(NotePlayHandle* nph, sampleFrame* working_buffer) { m_W2.setInterpolate(m_interpolateW2.value()); m_W3.setInterpolate(m_interpolateW3.value()); nph->m_pluginData = new ExprSynth(&m_W1, &m_W2, &m_W3, exprO1, exprO2, nph, - Engine::audioEngine()->processingSampleRate(), &m_panning1, &m_panning2, m_relTransition.value()); + Engine::audioEngine()->outputSampleRate(), &m_panning1, &m_panning2, m_relTransition.value()); } auto ps = static_cast(nph->m_pluginData); @@ -256,13 +256,12 @@ void Xpressive::smooth(float smoothness,const graphModel * in,graphModel * out) auto const guassian = new float[guass_size]; float sum = 0.0f; float temp = 0.0f; - int i; - for (i = 0; i < guass_size; i++ ) + for (int i = 0; i < guass_size; i++) { temp = (i - guass_center) / delta; sum += guassian[i] = a * powf(F_E, -0.5f * temp * temp); } - for (i = 0; i < guass_size; i++ ) + for (int i = 0; i < guass_size; i++) { guassian[i] = guassian[i] / sum; } @@ -336,13 +335,6 @@ XpressiveView::XpressiveView(Instrument * _instrument, QWidget * _parent) : pal.setBrush(backgroundRole(), PLUGIN_NAME::getIconPixmap("wavegraph")); m_graph->setPalette(pal); - PixmapButton * m_w1Btn; - PixmapButton * m_w2Btn; - PixmapButton * m_w3Btn; - PixmapButton * m_o1Btn; - PixmapButton * m_o2Btn; - PixmapButton * m_helpBtn; - m_w1Btn = new PixmapButton(this, nullptr); m_w1Btn->move(3, ROW_BTN); m_w1Btn->setActiveGraphic(PLUGIN_NAME::getIconPixmap("w1_active")); diff --git a/plugins/Xpressive/Xpressive.h b/plugins/Xpressive/Xpressive.h index b91957ac4..974b82b17 100644 --- a/plugins/Xpressive/Xpressive.h +++ b/plugins/Xpressive/Xpressive.h @@ -190,6 +190,7 @@ private: PixmapButton *m_w3Btn; PixmapButton *m_o1Btn; PixmapButton *m_o2Btn; + PixmapButton *m_helpBtn; PixmapButton *m_sinWaveBtn; PixmapButton *m_triangleWaveBtn; PixmapButton *m_sqrWaveBtn; diff --git a/plugins/ZynAddSubFx/CMakeLists.txt b/plugins/ZynAddSubFx/CMakeLists.txt index 3369a7938..35dc08c4d 100644 --- a/plugins/ZynAddSubFx/CMakeLists.txt +++ b/plugins/ZynAddSubFx/CMakeLists.txt @@ -29,7 +29,7 @@ if(NOT MSVC) endif() IF("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU" AND NOT "${CMAKE_CXX_COMPILER_VERSION}" VERSION_LESS "6.0.0") - SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-misleading-indentation") + SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-misleading-indentation -Wno-format-truncation") ENDIF() IF(MINGW_PREFIX) diff --git a/plugins/ZynAddSubFx/ZynAddSubFx.cpp b/plugins/ZynAddSubFx/ZynAddSubFx.cpp index be38bcb79..a058c5b1e 100644 --- a/plugins/ZynAddSubFx/ZynAddSubFx.cpp +++ b/plugins/ZynAddSubFx/ZynAddSubFx.cpp @@ -40,7 +40,6 @@ #include "DataFile.h" #include "InstrumentPlayHandle.h" #include "InstrumentTrack.h" -#include "gui_templates.h" #include "Song.h" #include "StringPairDrag.h" #include "RemoteZynAddSubFx.h" @@ -104,7 +103,7 @@ bool ZynAddSubFxRemotePlugin::processMessage( const message & _m ) ZynAddSubFxInstrument::ZynAddSubFxInstrument( InstrumentTrack * _instrumentTrack ) : - Instrument( _instrumentTrack, &zynaddsubfx_plugin_descriptor ), + Instrument(_instrumentTrack, &zynaddsubfx_plugin_descriptor, nullptr, Flag::IsSingleStreamed | Flag::IsMidiBased), m_hasGUI( false ), m_plugin( nullptr ), m_remotePlugin( nullptr ), @@ -311,7 +310,7 @@ void ZynAddSubFxInstrument::loadFile( const QString & _file ) m_pluginMutex.unlock(); } - instrumentTrack()->setName( QFileInfo( _file ).baseName().replace( QRegExp( "^[0-9]{4}-" ), QString() ) ); + instrumentTrack()->setName(QFileInfo(_file).baseName().replace(QRegularExpression("^[0-9]{4}-"), QString())); m_modifiedControllers.clear(); @@ -452,7 +451,7 @@ void ZynAddSubFxInstrument::initPlugin() QDir( ConfigManager::inst()->factoryPresetsDir() + "/ZynAddSubFX" ).absolutePath() ) ) ); - m_remotePlugin->updateSampleRate( Engine::audioEngine()->processingSampleRate() ); + m_remotePlugin->updateSampleRate( Engine::audioEngine()->outputSampleRate() ); // temporary workaround until the VST synchronization feature gets stripped out of the RemotePluginClient class // causing not to send buffer size information requests @@ -464,7 +463,7 @@ void ZynAddSubFxInstrument::initPlugin() else { m_plugin = new LocalZynAddSubFx; - m_plugin->setSampleRate( Engine::audioEngine()->processingSampleRate() ); + m_plugin->setSampleRate( Engine::audioEngine()->outputSampleRate() ); m_plugin->setBufferSize( Engine::audioEngine()->framesPerPeriod() ); } @@ -541,7 +540,10 @@ ZynAddSubFxView::ZynAddSubFxView( Instrument * _instrument, QWidget * _parent ) m_toggleUIButton->setCheckable( true ); m_toggleUIButton->setChecked( false ); m_toggleUIButton->setIcon( embed::getIconPixmap( "zoom" ) ); - m_toggleUIButton->setFont( pointSize<8>( m_toggleUIButton->font() ) ); + QFont f = m_toggleUIButton->font(); + f.setPointSizeF(12); + m_toggleUIButton->setFont(f); + connect( m_toggleUIButton, SIGNAL( toggled( bool ) ), this, SLOT( toggleUI() ) ); diff --git a/plugins/ZynAddSubFx/ZynAddSubFx.h b/plugins/ZynAddSubFx/ZynAddSubFx.h index a391203f3..b4f7c434c 100644 --- a/plugins/ZynAddSubFx/ZynAddSubFx.h +++ b/plugins/ZynAddSubFx/ZynAddSubFx.h @@ -86,11 +86,6 @@ public: QString nodeName() const override; - Flags flags() const override - { - return Flag::IsSingleStreamed | Flag::IsMidiBased; - } - gui::PluginView* instantiateView( QWidget * _parent ) override; diff --git a/plugins/ZynAddSubFx/zynaddsubfx b/plugins/ZynAddSubFx/zynaddsubfx index 551e816a6..aac04bc55 160000 --- a/plugins/ZynAddSubFx/zynaddsubfx +++ b/plugins/ZynAddSubFx/zynaddsubfx @@ -1 +1 @@ -Subproject commit 551e816a6334fd190c74ce971378063b2757b47b +Subproject commit aac04bc55c4114460009897686b597f98adfaa65 diff --git a/src/3rdparty/CMakeLists.txt b/src/3rdparty/CMakeLists.txt index a95332a07..3339eb926 100644 --- a/src/3rdparty/CMakeLists.txt +++ b/src/3rdparty/CMakeLists.txt @@ -1,10 +1,9 @@ -IF(LMMS_BUILD_LINUX AND WANT_VST) +if(LMMS_BUILD_LINUX AND LMMS_HAVE_VST) set(BUILD_SHARED_LIBS OFF) add_subdirectory(qt5-x11embed) ENDIF() ADD_SUBDIRECTORY(hiir) -ADD_SUBDIRECTORY(rpmalloc) ADD_SUBDIRECTORY(weakjack) if(MINGW) diff --git a/src/3rdparty/jack2 b/src/3rdparty/jack2 index db76dd6bb..ac334fabf 160000 --- a/src/3rdparty/jack2 +++ b/src/3rdparty/jack2 @@ -1 +1 @@ -Subproject commit db76dd6bb879a0a24d73ec41cc2e6a21bca8ee08 +Subproject commit ac334fabfb56989e9115ee6e2a77c1f6162d14fb diff --git a/src/3rdparty/rpmalloc/CMakeLists.txt b/src/3rdparty/rpmalloc/CMakeLists.txt deleted file mode 100644 index 047c32678..000000000 --- a/src/3rdparty/rpmalloc/CMakeLists.txt +++ /dev/null @@ -1,49 +0,0 @@ -add_library(rpmalloc STATIC - rpmalloc/rpmalloc/rpmalloc.c - rpmalloc/rpmalloc/rpmalloc.h -) - -target_include_directories(rpmalloc PUBLIC - ${CMAKE_CURRENT_SOURCE_DIR}/rpmalloc/rpmalloc -) - -set_property(TARGET rpmalloc PROPERTY C_STANDARD 11) - -IF(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang") - target_compile_options(rpmalloc - PRIVATE -Wno-unused-variable - ) -endif() - -if (NOT LMMS_BUILD_WIN32) - target_compile_definitions(rpmalloc - PRIVATE -D_GNU_SOURCE - ) -endif() - -if(MINGW) - target_compile_definitions(rpmalloc - PRIVATE -D_WIN32_WINNT=0x600 - ) -endif() - -if (CMAKE_BUILD_TYPE STREQUAL "Debug") - # rpmalloc uses GCC builtin "__builtin_umull_overflow" with ENABLE_VALIDATE_ARGS, - # which is only available starting with GCC 5 - if (CMAKE_C_COMPILER_ID STREQUAL "GNU" AND CMAKE_C_COMPILER_VERSION VERSION_LESS 5) - set(ENABLE_VALIDATE_ARGS OFF) - else () - set(ENABLE_VALIDATE_ARGS ON) - endif() - target_compile_definitions(rpmalloc - PRIVATE -DENABLE_ASSERTS=1 -DENABLE_VALIDATE_ARGS=${ENABLE_VALIDATE_ARGS} - ) -endif() - -option(LMMS_ENABLE_MALLOC_STATS "Enables statistics for rpmalloc" OFF) - -if (LMMS_ENABLE_MALLOC_STATS) - target_compile_definitions(rpmalloc - PRIVATE -DENABLE_STATISTICS=1 - ) -endif() diff --git a/src/3rdparty/rpmalloc/rpmalloc b/src/3rdparty/rpmalloc/rpmalloc deleted file mode 160000 index 80daac0d5..000000000 --- a/src/3rdparty/rpmalloc/rpmalloc +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 80daac0d539ab2a8edfd5ca24b1e0c77a4974bbb diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index f483d8b41..294ae1a07 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -4,9 +4,9 @@ CONFIGURE_FILE("lmmsconfig.h.in" "${CMAKE_BINARY_DIR}/lmmsconfig.h") CONFIGURE_FILE("lmmsversion.h.in" "${CMAKE_BINARY_DIR}/lmmsversion.h") SET(LMMS_SRCS "") -SET(LMMS_UIS "") SET(CMAKE_AUTOMOC ON) +SET(CMAKE_AUTOUIC ON) SET(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}) # Enable C++17 @@ -23,7 +23,6 @@ ADD_SUBDIRECTORY(tracks) LIST(APPEND LMMS_SRCS ${LMMS_COMMON_SRCS}) -QT5_WRAP_UI(LMMS_UI_OUT ${LMMS_UIS}) INCLUDE_DIRECTORIES( "${CMAKE_CURRENT_BINARY_DIR}" "${CMAKE_BINARY_DIR}" @@ -58,8 +57,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 +76,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() @@ -106,7 +99,6 @@ SET(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE) ADD_LIBRARY(lmmsobjs OBJECT ${LMMS_SRCS} ${LMMS_INCLUDES} - ${LMMS_UI_OUT} ${LMMS_RCC_OUT} ) @@ -140,7 +132,7 @@ IF(NOT CMAKE_VERSION VERSION_LESS 3.6) SET_PROPERTY(DIRECTORY ${CMAKE_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT lmms) ENDIF() -SET_DIRECTORY_PROPERTIES(PROPERTIES ADDITIONAL_MAKE_CLEAN_FILES "${LMMS_RCC_OUT} ${LMMS_UI_OUT} lmmsconfig.h lmms.1.gz") +SET_DIRECTORY_PROPERTIES(PROPERTIES ADDITIONAL_MAKE_CLEAN_FILES "${LMMS_RCC_OUT} lmmsconfig.h lmms.1.gz") IF(LMMS_BUILD_WIN32) SET(EXTRA_LIBRARIES "winmm") @@ -170,6 +162,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 +180,13 @@ 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} + SampleRate::samplerate + SndFile::sndfile ${EXTRA_LIBRARIES} - rpmalloc ) # Expose required libs for tests binary @@ -211,27 +205,14 @@ FOREACH(LIB ${LMMS_REQUIRED_LIBS}) ENDIF() ENDFOREACH() -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" - ) - ENDIF() - IF(LMMS_BUILD_MSYS) - # ENABLE_EXPORTS property has no effect in some MSYS2 configurations. - # Add the linker flag manually to create liblmms.dll.a import library - SET_PROPERTY(TARGET lmms - APPEND_STRING PROPERTY LINK_FLAGS " -Wl,--out-implib,liblmms.dll.a" - ) - ENDIF() -ELSE() - IF(NOT LMMS_BUILD_APPLE) - SET_TARGET_PROPERTIES(lmms PROPERTIES LINK_FLAGS "${LINK_FLAGS} -Wl,-E") - ENDIF(NOT LMMS_BUILD_APPLE) +set_target_properties(lmms PROPERTIES + ENABLE_EXPORTS ON + WIN32_EXECUTABLE $> +) +set_target_properties(lmmsobjs PROPERTIES AUTOUIC_SEARCH_PATHS "gui/modals") + +IF(NOT WIN32) if(CMAKE_INSTALL_MANDIR) SET(INSTALL_MANDIR ${CMAKE_INSTALL_MANDIR}) ELSE(CMAKE_INSTALL_MANDIR) diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index 877bea3fc..9f349f091 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -1,6 +1,7 @@ set(COMMON_SRCS RemotePluginBase.cpp SharedMemory.cpp + SystemSemaphore.cpp ) foreach(SRC ${COMMON_SRCS}) diff --git a/src/common/RemotePluginBase.cpp b/src/common/RemotePluginBase.cpp index a4739a63c..97ae8ac9e 100644 --- a/src/common/RemotePluginBase.cpp +++ b/src/common/RemotePluginBase.cpp @@ -34,23 +34,23 @@ namespace lmms #ifdef SYNC_WITH_SHM_FIFO -RemotePluginBase::RemotePluginBase( shmFifo * _in, shmFifo * _out ) : - m_in( _in ), - m_out( _out ) +RemotePluginBase::RemotePluginBase(shmFifo * _in, shmFifo * _out) : + m_in(_in), + m_out(_out) #else RemotePluginBase::RemotePluginBase() : - m_socket( -1 ), - m_invalid( false ) + m_socket(-1), + m_invalid(false) #endif { #ifdef LMMS_HAVE_LOCALE_H // make sure, we're using common ways to print/scan // floats to/from strings (',' vs. '.' for decimal point etc.) - setlocale( LC_NUMERIC, "C" ); + setlocale(LC_NUMERIC, "C"); #endif #ifndef SYNC_WITH_SHM_FIFO - pthread_mutex_init( &m_receiveMutex, nullptr ); - pthread_mutex_init( &m_sendMutex, nullptr ); + pthread_mutex_init(&m_receiveMutex, nullptr); + pthread_mutex_init(&m_sendMutex, nullptr); #endif } @@ -63,39 +63,39 @@ RemotePluginBase::~RemotePluginBase() delete m_in; delete m_out; #else - pthread_mutex_destroy( &m_receiveMutex ); - pthread_mutex_destroy( &m_sendMutex ); + pthread_mutex_destroy(&m_receiveMutex); + pthread_mutex_destroy(&m_sendMutex); #endif } -int RemotePluginBase::sendMessage( const message & _m ) +int RemotePluginBase::sendMessage(const message & _m) { #ifdef SYNC_WITH_SHM_FIFO m_out->lock(); - m_out->writeInt( _m.id ); - m_out->writeInt( _m.data.size() ); + m_out->writeInt(_m.id); + m_out->writeInt(_m.data.size()); int j = 8; - for( unsigned int i = 0; i < _m.data.size(); ++i ) + for (unsigned int i = 0; i < _m.data.size(); ++i) { - m_out->writeString( _m.data[i] ); + m_out->writeString(_m.data[i]); j += 4 + _m.data[i].size(); } m_out->unlock(); m_out->messageSent(); #else - pthread_mutex_lock( &m_sendMutex ); - writeInt( _m.id ); - writeInt( _m.data.size() ); + pthread_mutex_lock(&m_sendMutex); + writeInt(_m.id); + writeInt(_m.data.size()); int j = 8; for (const auto& str : _m.data) { writeString(str); j += 4 + str.size(); } - pthread_mutex_unlock( &m_sendMutex ); + pthread_mutex_unlock(&m_sendMutex); #endif return j; @@ -112,21 +112,21 @@ RemotePluginBase::message RemotePluginBase::receiveMessage() message m; m.id = m_in->readInt(); const int s = m_in->readInt(); - for( int i = 0; i < s; ++i ) + for (int i = 0; i < s; ++i) { - m.data.push_back( m_in->readString() ); + m.data.push_back(m_in->readString()); } m_in->unlock(); #else - pthread_mutex_lock( &m_receiveMutex ); + pthread_mutex_lock(&m_receiveMutex); message m; m.id = readInt(); const int s = readInt(); - for( int i = 0; i < s; ++i ) + for (int i = 0; i < s; ++i) { - m.data.push_back( readString() ); + m.data.push_back(readString()); } - pthread_mutex_unlock( &m_receiveMutex ); + pthread_mutex_unlock(&m_receiveMutex); #endif return m; } @@ -136,10 +136,10 @@ RemotePluginBase::message RemotePluginBase::receiveMessage() RemotePluginBase::message RemotePluginBase::waitForMessage( const message & _wm, - bool _busy_waiting ) + bool _busy_waiting) { #ifndef BUILD_REMOTE_PLUGIN_CLIENT - if( _busy_waiting ) + if (_busy_waiting) { // No point processing events outside of the main thread _busy_waiting = QThread::currentThread() == @@ -148,41 +148,41 @@ RemotePluginBase::message RemotePluginBase::waitForMessage( struct WaitDepthCounter { - WaitDepthCounter( int & depth, bool busy ) : - m_depth( depth ), - m_busy( busy ) + WaitDepthCounter(int & depth, bool busy) : + m_depth(depth), + m_busy(busy) { - if( m_busy ) { ++m_depth; } + if (m_busy) { ++m_depth; } } ~WaitDepthCounter() { - if( m_busy ) { --m_depth; } + if (m_busy) { --m_depth; } } int & m_depth; bool m_busy; }; - WaitDepthCounter wdc( waitDepthCounter(), _busy_waiting ); + WaitDepthCounter wdc(waitDepthCounter(), _busy_waiting); #endif - while( !isInvalid() ) + while (!isInvalid()) { #ifndef BUILD_REMOTE_PLUGIN_CLIENT - if( _busy_waiting && !messagesLeft() ) + if (_busy_waiting && !messagesLeft()) { QCoreApplication::processEvents( - QEventLoop::ExcludeUserInputEvents, 50 ); + QEventLoop::ExcludeUserInputEvents, 50); continue; } #endif message m = receiveMessage(); - processMessage( m ); - if( m.id == _wm.id ) + processMessage(m); + if (m.id == _wm.id) { return m; } - else if( m.id == IdUndefined ) + else if (m.id == IdUndefined) { return m; } diff --git a/src/common/SharedMemory.cpp b/src/common/SharedMemory.cpp index 005e726ed..470016530 100644 --- a/src/common/SharedMemory.cpp +++ b/src/common/SharedMemory.cpp @@ -23,45 +23,44 @@ #include "SharedMemory.h" +#include +#include + #include "lmmsconfig.h" +#include "RaiiHelpers.h" #ifdef LMMS_HAVE_UNISTD_H # include #endif -#if _POSIX_SHARED_MEMORY_OBJECTS > 0 -# include -# +#if _POSIX_SHARED_MEMORY_OBJECTS > 0 || defined(LMMS_BUILD_APPLE) # include # include # include -# -# include "RaiiHelpers.h" +#elif defined(LMMS_BUILD_WIN32) +# include #else -# include -# -# include -# include +# error "No shared memory implementation available" #endif - namespace lmms::detail { -#if _POSIX_SHARED_MEMORY_OBJECTS > 0 - +#if _POSIX_SHARED_MEMORY_OBJECTS > 0 || defined(LMMS_BUILD_APPLE) namespace { +[[noreturn]] void throwSystemError(const char* message) +{ + throw std::system_error{errno, std::generic_category(), message}; +} + template int retryWhileInterrupted(F&& function) noexcept(std::is_nothrow_invocable_v) { - int result; - do - { - result = function(); + while (true) { + const auto result = function(); + if (result != -1 || errno != EINTR) { return result; } } - while (result == -1 && errno == EINTR); - return result; } void deleteFileDescriptor(int fd) noexcept { retryWhileInterrupted([fd]() noexcept { return close(fd); }); } @@ -82,22 +81,15 @@ public: const auto fd = FileDescriptor{ retryWhileInterrupted([&]() noexcept { return shm_open(m_key.c_str(), openFlags, 0); }) }; - if (!fd) - { - throw std::system_error{errno, std::generic_category(), "SharedMemoryImpl: shm_open() failed"}; - } + if (!fd) { throwSystemError("SharedMemoryImpl: shm_open() failed"); } + auto stat = (struct stat){}; - if (fstat(fd.get(), &stat) == -1) - { - throw std::system_error{errno, std::generic_category(), "SharedMemoryImpl: fstat() failed"}; - } + if (fstat(fd.get(), &stat) == -1) { throwSystemError("SharedMemoryImpl: fstat() failed"); } m_size = stat.st_size; + const auto mappingProtection = readOnly ? PROT_READ : PROT_READ | PROT_WRITE; m_mapping = mmap(nullptr, m_size, mappingProtection, MAP_SHARED, fd.get(), 0); - if (m_mapping == MAP_FAILED) - { - throw std::system_error{errno, std::generic_category(), "SharedMemoryImpl: mmap() failed"}; - } + if (m_mapping == MAP_FAILED) { throwSystemError("SharedMemoryImpl: mmap() failed"); } } SharedMemoryImpl(const std::string& key, std::size_t size, bool readOnly) : @@ -107,21 +99,16 @@ public: const auto fd = FileDescriptor{ retryWhileInterrupted([&]() noexcept { return shm_open(m_key.c_str(), O_RDWR | O_CREAT | O_EXCL, 0600); }) }; - if (fd.get() == -1) - { - throw std::system_error{errno, std::generic_category(), "SharedMemoryImpl: shm_open() failed"}; - } + if (fd.get() == -1) { throwSystemError("SharedMemoryImpl: shm_open() failed"); } m_object.reset(m_key.c_str()); - if (retryWhileInterrupted([&]() noexcept { return ftruncate(fd.get(), m_size); }) == -1) - { - throw std::system_error{errno, std::generic_category(), "SharedMemoryImpl: ftruncate() failed"}; + + if (retryWhileInterrupted([&]() noexcept { return ftruncate(fd.get(), m_size); }) == -1) { + throwSystemError("SharedMemoryImpl: ftruncate() failed"); } + const auto mappingProtection = readOnly ? PROT_READ : PROT_READ | PROT_WRITE; m_mapping = mmap(nullptr, m_size, mappingProtection, MAP_SHARED, fd.get(), 0); - if (m_mapping == MAP_FAILED) - { - throw std::system_error{errno, std::generic_category(), "SharedMemoryImpl: mmap() failed"}; - } + if (m_mapping == MAP_FAILED) { throwSystemError("SharedMemoryImpl: mmap() failed"); } } SharedMemoryImpl(const SharedMemoryImpl&) = delete; @@ -132,7 +119,7 @@ public: munmap(m_mapping, m_size); } - void* get() { return m_mapping; } + auto get() const noexcept -> void* { return m_mapping; } private: std::string m_key; @@ -141,38 +128,67 @@ private: ShmObject m_object; }; -#else +#elif defined(LMMS_BUILD_WIN32) + +namespace { + +auto sizeToHighAndLow(std::size_t size) -> std::pair +{ + if constexpr(sizeof(std::size_t) <= sizeof(DWORD)) { + return {0, size}; + } else { + return {static_cast(size >> 32), static_cast(size)}; + } +} + +[[noreturn]] void throwLastError(const char* message) +{ + throw std::system_error{static_cast(GetLastError()), std::system_category(), message}; +} + +using UniqueHandle = UniqueNullableResource; +using FileView = UniqueNullableResource; + +} // namespace class SharedMemoryImpl { public: - SharedMemoryImpl(const std::string& key, bool readOnly) : - m_shm{QString::fromStdString(key)} + SharedMemoryImpl(const std::string& key, bool readOnly) { - const auto mode = readOnly ? QSharedMemory::ReadOnly : QSharedMemory::ReadWrite; - if (!m_shm.attach(mode)) - { - throw std::runtime_error{"SharedMemoryImpl: QSharedMemory::attach() failed"}; - } + const auto access = readOnly ? FILE_MAP_READ : FILE_MAP_WRITE; + m_mapping.reset(OpenFileMappingA(access, false, key.c_str())); + if (!m_mapping) { throwLastError("SharedMemoryImpl: OpenFileMappingA() failed"); } + + m_view.reset(MapViewOfFile(m_mapping.get(), access, 0, 0, 0)); + if (!m_view) { throwLastError("SharedMemoryImpl: MapViewOfFile() failed"); } } - SharedMemoryImpl(const std::string& key, std::size_t size, bool readOnly) : - m_shm{QString::fromStdString(key)} + SharedMemoryImpl(const std::string& key, std::size_t size, bool readOnly) { - const auto mode = readOnly ? QSharedMemory::ReadOnly : QSharedMemory::ReadWrite; - if (!m_shm.create(size, mode)) - { - throw std::runtime_error{"SharedMemoryImpl: QSharedMemory::create() failed"}; + const auto [high, low] = sizeToHighAndLow(size); + m_mapping.reset(CreateFileMappingA(INVALID_HANDLE_VALUE, nullptr, PAGE_READWRITE, high, low, key.c_str())); + // This constructor is supposed to create a new shared memory object, + // but passing the name of an existing object causes CreateFileMappingA + // to succeed and return a handle to that object. Thus we have to check + // GetLastError() too. + if (!m_mapping || GetLastError() == ERROR_ALREADY_EXISTS) { + throwLastError("SharedMemoryImpl: CreateFileMappingA() failed"); } + + const auto access = readOnly ? FILE_MAP_READ : FILE_MAP_WRITE; + m_view.reset(MapViewOfFile(m_mapping.get(), access, 0, 0, 0)); + if (!m_view) { throwLastError("SharedMemoryImpl: MapViewOfFile() failed"); } } SharedMemoryImpl(const SharedMemoryImpl&) = delete; - SharedMemoryImpl& operator=(const SharedMemoryImpl&) = delete; + auto operator=(const SharedMemoryImpl&) -> SharedMemoryImpl& = delete; - void* get() { return m_shm.data(); } + auto get() const noexcept -> void* { return m_view.get(); } private: - QSharedMemory m_shm; + UniqueHandle m_mapping; + FileView m_view; }; #endif @@ -196,7 +212,7 @@ SharedMemoryData::~SharedMemoryData() = default; SharedMemoryData::SharedMemoryData(SharedMemoryData&& other) noexcept : m_key{std::move(other.m_key)}, m_impl{std::move(other.m_impl)}, - m_ptr{other.m_ptr} + m_ptr{std::exchange(other.m_ptr, nullptr)} { } } // namespace lmms::detail diff --git a/src/common/SystemSemaphore.cpp b/src/common/SystemSemaphore.cpp new file mode 100644 index 000000000..02c9a2888 --- /dev/null +++ b/src/common/SystemSemaphore.cpp @@ -0,0 +1,181 @@ +/* + * SystemSemaphore.cpp + * + * Copyright (c) 2024 Dominic Clark + * + * 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 "SystemSemaphore.h" + +#include +#include +#include +#include + +#include "lmmsconfig.h" +#include "RaiiHelpers.h" + +#ifdef LMMS_HAVE_UNISTD_H +# include +#endif + +#if (_POSIX_SEMAPHORES > 0 && !defined(__MINGW32__)) || defined(LMMS_BUILD_APPLE) +# include +# include +#elif defined(LMMS_BUILD_WIN32) +# include +#else +# error "No system semaphore implementation available" +#endif + +namespace lmms { + +namespace detail { + +#if (_POSIX_SEMAPHORES > 0 && !defined(__MINGW32__)) || defined(LMMS_BUILD_APPLE) + +namespace { + +[[noreturn]] void throwSystemError(const char* message) +{ + throw std::system_error{errno, std::generic_category(), message}; +} + +template +auto retryWhileInterrupted(F&& function, std::invoke_result_t error = -1) + noexcept(std::is_nothrow_invocable_v) -> auto +{ + while (true) + { + const auto result = function(); + if (result != error || errno != EINTR) { return result; } + } +} + +using UniqueSemaphore = UniqueNullableResource; + +} // namespace + +class SystemSemaphoreImpl +{ +public: + SystemSemaphoreImpl(const std::string& key, unsigned int value) : + m_key{"/" + key}, + m_sem{retryWhileInterrupted([&]() noexcept { + return sem_open(m_key.c_str(), O_CREAT | O_EXCL, 0600, value); + }, SEM_FAILED)} + { + if (m_sem == SEM_FAILED) { throwSystemError("SystemSemaphoreImpl: sem_open() failed"); } + m_ownedSemaphore.reset(m_key.c_str()); + } + + explicit SystemSemaphoreImpl(const std::string& key) : + m_key{"/" + key}, + m_sem{retryWhileInterrupted([&]() noexcept { + return sem_open(m_key.c_str(), 0); + }, SEM_FAILED)} + { + if (m_sem == SEM_FAILED) { throwSystemError("SystemSemaphoreImpl: sem_open() failed"); } + } + + ~SystemSemaphoreImpl() + { + // We can't use `UniqueNullableResource` for `m_sem`, as the null value + // (`SEM_FAILED`) is not a constant expression on macOS (it's defined as + // `(sem_t*) -1`), so can't be used as a template parameter. + sem_close(m_sem); + } + + auto acquire() noexcept -> bool + { + return retryWhileInterrupted([&]() noexcept { + return sem_wait(m_sem); + }) == 0; + } + + auto release() noexcept -> bool { return sem_post(m_sem) == 0; } + +private: + std::string m_key; + sem_t* m_sem; + UniqueSemaphore m_ownedSemaphore; +}; + +#elif defined(LMMS_BUILD_WIN32) + +namespace { + +[[noreturn]] void throwSystemError(const char* message) +{ + throw std::system_error{static_cast(GetLastError()), std::system_category(), message}; +} + +using UniqueHandle = UniqueNullableResource; + +} // namespace + +class SystemSemaphoreImpl +{ +public: + SystemSemaphoreImpl(const std::string& key, unsigned int value) : + m_sem{CreateSemaphoreA(nullptr, value, std::numeric_limits::max(), key.c_str())} + { + if (!m_sem || GetLastError() == ERROR_ALREADY_EXISTS) { + throwSystemError("SystemSemaphoreImpl: CreateSemaphoreA failed"); + } + } + + explicit SystemSemaphoreImpl(const std::string& key) : + m_sem{OpenSemaphoreA(SEMAPHORE_MODIFY_STATE | SYNCHRONIZE, false, key.c_str())} + { + if (!m_sem) { throwSystemError("SystemSemaphoreImpl: OpenSemaphoreA failed"); } + } + + auto acquire() noexcept -> bool { return WaitForSingleObject(m_sem.get(), INFINITE) == WAIT_OBJECT_0; } + auto release() noexcept -> bool { return ReleaseSemaphore(m_sem.get(), 1, nullptr); } + +private: + UniqueHandle m_sem; +}; + +#endif + +} // namespace detail + +SystemSemaphore::SystemSemaphore() noexcept = default; + +SystemSemaphore::SystemSemaphore(std::string key, unsigned int value) : + m_key{std::move(key)}, + m_impl{std::make_unique(m_key, value)} +{} + +SystemSemaphore::SystemSemaphore(std::string key) : + m_key{std::move(key)}, + m_impl{std::make_unique(m_key)} +{} + +SystemSemaphore::~SystemSemaphore() = default; + +SystemSemaphore::SystemSemaphore(SystemSemaphore&& other) noexcept = default; +auto SystemSemaphore::operator=(SystemSemaphore&& other) noexcept -> SystemSemaphore& = default; + +auto SystemSemaphore::acquire() noexcept -> bool { return m_impl->acquire(); } +auto SystemSemaphore::release() noexcept -> bool { return m_impl->release(); } + +} // namespace lmms diff --git a/src/core/AudioEngine.cpp b/src/core/AudioEngine.cpp index 14831a42d..3dc0f7c15 100644 --- a/src/core/AudioEngine.cpp +++ b/src/core/AudioEngine.cpp @@ -24,6 +24,7 @@ #include "AudioEngine.h" +#include "MixHelpers.h" #include "denormals.h" #include "lmmsconfig.h" @@ -67,6 +68,7 @@ namespace lmms using LocklessListElement = LocklessList::Element; static thread_local bool s_renderingThread; +static thread_local bool s_runningChange; @@ -81,21 +83,14 @@ AudioEngine::AudioEngine( bool renderOnly ) : m_workers(), m_numWorkers( QThread::idealThreadCount()-1 ), m_newPlayHandles( PlayHandle::MaxNumber ), - m_qualitySettings( qualitySettings::Mode::Draft ), + m_qualitySettings(qualitySettings::Interpolation::Linear), m_masterGain( 1.0f ), - m_isProcessing( false ), m_audioDev( nullptr ), m_oldAudioDev( nullptr ), m_audioDevStartFailed( false ), m_profiler(), m_metronomeActive(false), - m_clearSignal( false ), - m_changesSignal( false ), - m_changes( 0 ), -#if (QT_VERSION < QT_VERSION_CHECK(5,14,0)) - m_doChangesMutex( QMutex::Recursive ), -#endif - m_waitingForWrite( false ) + m_clearSignal(false) { for( int i = 0; i < 2; ++i ) { @@ -165,8 +160,6 @@ AudioEngine::AudioEngine( bool renderOnly ) : AudioEngine::~AudioEngine() { - runChangesInModel(); - for( int w = 0; w < m_numWorkers; ++w ) { m_workers[w]->quit(); @@ -232,8 +225,6 @@ void AudioEngine::startProcessing(bool needsFifo) } m_audioDev->startProcessing(); - - m_isProcessing = true; } @@ -241,8 +232,6 @@ void AudioEngine::startProcessing(bool needsFifo) void AudioEngine::stopProcessing() { - m_isProcessing = false; - if( m_fifoWriter != nullptr ) { m_fifoWriter->finish(); @@ -288,17 +277,6 @@ sample_rate_t AudioEngine::inputSampleRate() const baseSampleRate(); } - - - -sample_rate_t AudioEngine::processingSampleRate() const -{ - return outputSampleRate() * m_qualitySettings.sampleRateMultiplier(); -} - - - - bool AudioEngine::criticalXRuns() const { return cpuLoad() >= 99 && Engine::getSong()->isExporting() == false; @@ -397,6 +375,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(); @@ -427,17 +416,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); @@ -445,9 +423,9 @@ void AudioEngine::renderStageMix() Mixer *mixer = Engine::mixer(); mixer->masterMix(m_outputBufferWrite); - emit nextAudioBuffer(m_outputBufferRead); + MixHelpers::multiply(m_outputBufferWrite, m_masterGain, m_framesPerPeriod); - runChangesInModel(); + emit nextAudioBuffer(m_outputBufferRead); // and trigger LFOs EnvelopeAndLfoParameters::instances()->trigger(); @@ -459,6 +437,8 @@ void AudioEngine::renderStageMix() const surroundSampleFrame *AudioEngine::renderNextBuffer() { + const auto lock = std::lock_guard{m_changeMutex}; + m_profiler.startPeriod(); s_renderingThread = true; @@ -468,7 +448,7 @@ const surroundSampleFrame *AudioEngine::renderNextBuffer() renderStageMix(); // STAGE 3: do master mix in mixer s_renderingThread = false; - m_profiler.finishPeriod(processingSampleRate(), m_framesPerPeriod); + m_profiler.finishPeriod(outputSampleRate(), m_framesPerPeriod); return m_outputBufferRead; } @@ -606,7 +586,6 @@ void AudioEngine::changeQuality(const struct qualitySettings & qs) stopProcessing(); m_qualitySettings = qs; - m_audioDev->applyQualitySettings(); emit sampleRateChanged(); emit qualitySettingsChanged(); @@ -811,57 +790,16 @@ void AudioEngine::removePlayHandlesOfTypes(Track * track, PlayHandle::Types type void AudioEngine::requestChangeInModel() { - if( s_renderingThread ) - return; - - m_changesMutex.lock(); - m_changes++; - m_changesMutex.unlock(); - - m_doChangesMutex.lock(); - m_waitChangesMutex.lock(); - if (m_isProcessing && !m_waitingForWrite && !m_changesSignal) - { - m_changesSignal = true; - m_changesRequestCondition.wait( &m_waitChangesMutex ); - } - m_waitChangesMutex.unlock(); + if (s_renderingThread || s_runningChange) { return; } + m_changeMutex.lock(); + s_runningChange = true; } - - - void AudioEngine::doneChangeInModel() { - if( s_renderingThread ) - return; - - m_changesMutex.lock(); - bool moreChanges = --m_changes; - m_changesMutex.unlock(); - - if( !moreChanges ) - { - m_changesSignal = false; - m_changesAudioEngineCondition.wakeOne(); - } - m_doChangesMutex.unlock(); -} - - - - -void AudioEngine::runChangesInModel() -{ - if( m_changesSignal ) - { - m_waitChangesMutex.lock(); - // allow changes in the model from other threads ... - m_changesRequestCondition.wakeOne(); - // ... and wait until they are done - m_changesAudioEngineCondition.wait( &m_waitChangesMutex ); - m_waitChangesMutex.unlock(); - } + if (s_renderingThread || !s_runningChange) { return; } + m_changeMutex.unlock(); + s_runningChange = false; } bool AudioEngine::isAudioDevNameValid(QString name) @@ -1297,29 +1235,12 @@ void AudioEngine::fifoWriter::run() auto buffer = new surroundSampleFrame[frames]; const surroundSampleFrame * b = m_audioEngine->renderNextBuffer(); memcpy( buffer, b, frames * sizeof( surroundSampleFrame ) ); - write( buffer ); + m_fifo->write(buffer); } // Let audio backend stop processing - write( nullptr ); + m_fifo->write(nullptr); m_fifo->waitUntilRead(); } - - - -void AudioEngine::fifoWriter::write( surroundSampleFrame * buffer ) -{ - m_audioEngine->m_waitChangesMutex.lock(); - m_audioEngine->m_waitingForWrite = true; - m_audioEngine->m_waitChangesMutex.unlock(); - m_audioEngine->runChangesInModel(); - - m_fifo->write( buffer ); - - m_audioEngine->m_doChangesMutex.lock(); - m_audioEngine->m_waitingForWrite = false; - m_audioEngine->m_doChangesMutex.unlock(); -} - } // namespace lmms diff --git a/src/core/AudioEngineWorkerThread.cpp b/src/core/AudioEngineWorkerThread.cpp index 528841c71..ae459c5e4 100644 --- a/src/core/AudioEngineWorkerThread.cpp +++ b/src/core/AudioEngineWorkerThread.cpp @@ -30,7 +30,6 @@ #include "denormals.h" #include "AudioEngine.h" -#include "MemoryManager.h" #include "ThreadableJob.h" #if __SSE__ @@ -167,7 +166,6 @@ void AudioEngineWorkerThread::startAndWaitForJobs() void AudioEngineWorkerThread::run() { - MemoryManager::ThreadGuard mmThreadGuard; Q_UNUSED(mmThreadGuard); disable_denormals(); QMutex m; diff --git a/src/core/AudioResampler.cpp b/src/core/AudioResampler.cpp new file mode 100644 index 000000000..5f6b6a239 --- /dev/null +++ b/src/core/AudioResampler.cpp @@ -0,0 +1,64 @@ +/* + * AudioResampler.cpp - wrapper for libsamplerate + * + * Copyright (c) 2023 saker + * + * 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 "AudioResampler.h" + +#include +#include +#include + +namespace lmms { + +AudioResampler::AudioResampler(int interpolationMode, int channels) + : m_interpolationMode(interpolationMode) + , m_channels(channels) + , m_state(src_new(interpolationMode, channels, &m_error)) +{ + if (!m_state) + { + const auto errorMessage = std::string{src_strerror(m_error)}; + const auto fullMessage = std::string{"Failed to create an AudioResampler: "} + errorMessage; + throw std::runtime_error{fullMessage}; + } +} + +AudioResampler::~AudioResampler() +{ + src_delete(m_state); +} + +auto AudioResampler::resample(const float* in, long inputFrames, float* out, long outputFrames, double ratio) + -> ProcessResult +{ + auto data = SRC_DATA{}; + data.data_in = in; + data.input_frames = inputFrames; + data.data_out = out; + data.output_frames = outputFrames; + data.src_ratio = ratio; + data.end_of_input = 0; + return {src_process(m_state, &data), data.input_frames_used, data.output_frames_gen}; +} + +} // namespace lmms diff --git a/src/core/AutomatableModel.cpp b/src/core/AutomatableModel.cpp index e46a864f8..abda0b43e 100644 --- a/src/core/AutomatableModel.cpp +++ b/src/core/AutomatableModel.cpp @@ -97,8 +97,8 @@ bool AutomatableModel::isAutomated() const bool AutomatableModel::mustQuoteName(const QString& name) { - QRegExp reg("^[A-Za-z0-9._-]+$"); - return !reg.exactMatch(name); + QRegularExpression reg("^[A-Za-z0-9._-]+$"); + return !reg.match(name).hasMatch(); } void AutomatableModel::saveSettings( QDomDocument& doc, QDomElement& element, const QString& name ) @@ -613,10 +613,9 @@ ValueBuffer * AutomatableModel::valueBuffer() float val = m_value; // make sure our m_value doesn't change midway - ValueBuffer * vb; if (m_controllerConnection && m_useControllerValue && m_controllerConnection->getController()->isSampleExact()) { - vb = m_controllerConnection->valueBuffer(); + auto vb = m_controllerConnection->valueBuffer(); if( vb ) { float * values = vb->values(); @@ -656,7 +655,7 @@ ValueBuffer * AutomatableModel::valueBuffer() if (lm && lm->controllerConnection() && lm->useControllerValue() && lm->controllerConnection()->getController()->isSampleExact()) { - vb = lm->valueBuffer(); + auto vb = lm->valueBuffer(); float * values = vb->values(); float * nvalues = m_valueBuffer.values(); for (int i = 0; i < vb->length(); i++) diff --git a/src/core/AutomationClip.cpp b/src/core/AutomationClip.cpp index 3bfc5cf8e..0e4271822 100644 --- a/src/core/AutomationClip.cpp +++ b/src/core/AutomationClip.cpp @@ -830,10 +830,10 @@ void AutomationClip::saveSettings( QDomDocument & _doc, QDomElement & _this ) _this.setAttribute( "prog", QString::number( static_cast(progressionType()) ) ); _this.setAttribute( "tens", QString::number( getTension() ) ); _this.setAttribute( "mute", QString::number( isMuted() ) ); - - if( usesCustomClipColor() ) + + if (const auto& c = color()) { - _this.setAttribute( "color", color().name() ); + _this.setAttribute("color", c->name()); } for( timeMap::const_iterator it = m_timeMap.begin(); @@ -919,10 +919,9 @@ void AutomationClip::loadSettings( const QDomElement & _this ) } } - if( _this.hasAttribute( "color" ) ) + if (_this.hasAttribute("color")) { - useCustomClipColor( true ); - setColor( _this.attribute( "color" ) ); + setColor(QColor{_this.attribute("color")}); } int len = _this.attribute( "len" ).toInt(); @@ -1223,11 +1222,9 @@ void AutomationClip::generateTangents(timeMap::iterator it, int numToGenerate) // TODO: This behavior means that a very small difference between the inValue and outValue can // result in a big change in the curve. In the future, allowing the user to manually adjust // the tangents would be better. - float inTangent; - float outTangent; if (OFFSET(it) == 0) { - inTangent = (INVAL(it + 1) - OUTVAL(it - 1)) / (POS(it + 1) - POS(it - 1)); + float inTangent = (INVAL(it + 1) - OUTVAL(it - 1)) / (POS(it + 1) - POS(it - 1)); it.value().setInTangent(inTangent); // inTangent == outTangent in this case it.value().setOutTangent(inTangent); @@ -1235,9 +1232,9 @@ void AutomationClip::generateTangents(timeMap::iterator it, int numToGenerate) else { // Calculate the left side of the curve - inTangent = (INVAL(it) - OUTVAL(it - 1)) / (POS(it) - POS(it - 1)); + float inTangent = (INVAL(it) - OUTVAL(it - 1)) / (POS(it) - POS(it - 1)); // Calculate the right side of the curve - outTangent = (INVAL(it + 1) - OUTVAL(it)) / (POS(it + 1) - POS(it)); + float outTangent = (INVAL(it + 1) - OUTVAL(it)) / (POS(it + 1) - POS(it)); it.value().setInTangent(inTangent); it.value().setOutTangent(outTangent); } diff --git a/src/core/BandLimitedWave.cpp b/src/core/BandLimitedWave.cpp index cb09dc5b8..060ff510a 100644 --- a/src/core/BandLimitedWave.cpp +++ b/src/core/BandLimitedWave.cpp @@ -49,11 +49,11 @@ QDataStream& operator<< ( QDataStream &out, WaveMipMap &waveMipMap ) QDataStream& operator>> ( QDataStream &in, WaveMipMap &waveMipMap ) { - sample_t sample; for( int tbl = 0; tbl <= MAXTBL; tbl++ ) { for( int i = 0; i < TLENS[tbl]; i++ ) { + sample_t sample; in >> sample; waveMipMap.setSampleAt( tbl, i, sample ); } @@ -67,9 +67,8 @@ void BandLimitedWave::generateWaves() // don't generate if they already exist if( s_wavesGenerated ) return; - int i; -// set wavetable directory + // set wavetable directory s_wavetableDir = "data:wavetables/"; // set wavetable files @@ -89,7 +88,7 @@ void BandLimitedWave::generateWaves() } else { - for( i = 0; i <= MAXTBL; i++ ) + for (int i = 0; i <= MAXTBL; i++) { const int len = TLENS[i]; //const double om = 1.0 / len; @@ -131,7 +130,7 @@ void BandLimitedWave::generateWaves() } else { - for( i = 0; i <= MAXTBL; i++ ) + for (int i = 0; i <= MAXTBL; i++) { const int len = TLENS[i]; //const double om = 1.0 / len; @@ -172,7 +171,7 @@ void BandLimitedWave::generateWaves() } else { - for( i = 0; i <= MAXTBL; i++ ) + for (int i = 0; i <= MAXTBL; i++) { const int len = TLENS[i]; //const double om = 1.0 / len; @@ -215,7 +214,7 @@ void BandLimitedWave::generateWaves() } else { - for( i = 0; i <= MAXTBL; i++ ) + for (int i = 0; i <= MAXTBL; i++) { const int len = TLENS[i]; diff --git a/src/core/BufferManager.cpp b/src/core/BufferManager.cpp index ff35e6a19..2362be85a 100644 --- a/src/core/BufferManager.cpp +++ b/src/core/BufferManager.cpp @@ -28,7 +28,6 @@ #include -#include "MemoryManager.h" namespace lmms { @@ -43,7 +42,7 @@ void BufferManager::init( fpp_t fpp ) sampleFrame * BufferManager::acquire() { - return MM_ALLOC( s_framesPerPeriod ); + return new sampleFrame[s_framesPerPeriod]; } void BufferManager::clear( sampleFrame *ab, const f_cnt_t frames, const f_cnt_t offset ) @@ -62,7 +61,7 @@ void BufferManager::clear( surroundSampleFrame * ab, const f_cnt_t frames, void BufferManager::release( sampleFrame * buf ) { - MM_FREE( buf ); + delete[] buf; } } // namespace lmms diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 1155f5e0d..9ebe2c355 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -4,6 +4,7 @@ set(LMMS_SRCS core/AudioEngine.cpp core/AudioEngineProfiler.cpp core/AudioEngineWorkerThread.cpp + core/AudioResampler.cpp core/AutomatableModel.cpp core/AutomationClip.cpp core/AutomationNode.cpp @@ -22,6 +23,7 @@ set(LMMS_SRCS core/Engine.cpp core/EnvelopeAndLfoParameters.cpp core/fft_helpers.cpp + core/FileSearch.cpp core/Mixer.cpp core/ImportFilter.cpp core/InlineAutomation.cpp @@ -38,7 +40,6 @@ set(LMMS_SRCS core/LinkedModelGroups.cpp core/LocklessAllocator.cpp core/MemoryHelper.cpp - core/MemoryManager.cpp core/MeterModel.cpp core/MicroTimer.cpp core/Microtuner.cpp @@ -65,8 +66,10 @@ set(LMMS_SRCS core/RemotePlugin.cpp core/RenderManager.cpp core/RingBuffer.cpp + core/Sample.cpp core/SampleBuffer.cpp core/SampleClip.cpp + core/SampleDecoder.cpp core/SamplePlayHandle.cpp core/SampleRecordHandle.cpp core/Scale.cpp @@ -74,6 +77,8 @@ set(LMMS_SRCS core/SerializingObject.cpp core/Song.cpp core/TempoSyncKnobModel.cpp + core/ThreadPool.cpp + core/Timeline.cpp core/TimePos.cpp core/ToolPlugin.cpp core/Track.cpp diff --git a/src/core/Clip.cpp b/src/core/Clip.cpp index db1200aae..b18391df1 100644 --- a/src/core/Clip.cpp +++ b/src/core/Clip.cpp @@ -48,9 +48,7 @@ Clip::Clip( Track * track ) : m_startPosition(), m_length(), m_mutedModel( false, this, tr( "Mute" ) ), - m_selectViewOnCreate( false ), - m_color( 128, 128, 128 ), - m_useCustomClipColor( false ) + m_selectViewOnCreate{false} { if( getTrack() ) { @@ -185,19 +183,10 @@ void Clip::setStartTimeOffset( const TimePos &startTimeOffset ) m_startTimeOffset = startTimeOffset; } - - -void Clip::useCustomClipColor( bool b ) +void Clip::setColor(const std::optional& color) { - if (b == m_useCustomClipColor) { return; } - m_useCustomClipColor = b; + m_color = color; emit colorChanged(); } - -bool Clip::hasColor() -{ - return usesCustomClipColor() || getTrack()->useColor(); -} - } // namespace lmms diff --git a/src/core/ConfigManager.cpp b/src/core/ConfigManager.cpp index 61d84770a..19ce80ddb 100644 --- a/src/core/ConfigManager.cpp +++ b/src/core/ConfigManager.cpp @@ -192,9 +192,7 @@ QStringList ConfigManager::availableVstEmbedMethods() { QStringList methods; methods.append("none"); -#if QT_VERSION >= 0x050100 methods.append("qt"); -#endif #ifdef LMMS_BUILD_WIN32 methods.append("win32"); #endif diff --git a/src/core/Controller.cpp b/src/core/Controller.cpp index 3e1b596b8..9218e3238 100644 --- a/src/core/Controller.cpp +++ b/src/core/Controller.cpp @@ -149,7 +149,7 @@ unsigned int Controller::runningFrames() // Get position in seconds float Controller::runningTime() { - return runningFrames() / Engine::audioEngine()->processingSampleRate(); + return runningFrames() / Engine::audioEngine()->outputSampleRate(); } @@ -220,24 +220,12 @@ Controller * Controller::create( ControllerType _ct, Model * _parent ) Controller * Controller::create( const QDomElement & _this, Model * _parent ) { - Controller * c; - if( static_cast(_this.attribute( "type" ).toInt()) == ControllerType::Peak ) - { - c = PeakController::getControllerBySetting( _this ); - } - else - { - c = create( - static_cast( _this.attribute( "type" ).toInt() ), - _parent ); - } - - if( c != nullptr ) - { - c->restoreState( _this ); - } - - return( c ); + const auto controllerType = static_cast(_this.attribute("type").toInt()); + auto controller = controllerType == ControllerType::Peak + ? PeakController::getControllerBySetting(_this) + : create(controllerType, _parent); + if (controller) { controller->restoreState(_this); } + return controller; } diff --git a/src/core/DataFile.cpp b/src/core/DataFile.cpp index 2fc96d969..e048b5e9e 100644 --- a/src/core/DataFile.cpp +++ b/src/core/DataFile.cpp @@ -35,6 +35,8 @@ #include #include #include +#include +#include #include "base64.h" #include "ConfigManager.h" @@ -42,6 +44,7 @@ #include "embed.h" #include "GuiApplication.h" #include "LocaleHelper.h" +#include "Note.h" #include "PluginFactory.h" #include "ProjectVersion.h" #include "SongEditor.h" @@ -79,7 +82,9 @@ const std::vector DataFile::UPGRADE_METHODS = { &DataFile::upgrade_automationNodes , &DataFile::upgrade_extendedNoteRange, &DataFile::upgrade_defaultTripleOscillatorHQ, &DataFile::upgrade_mixerRename , &DataFile::upgrade_bbTcoRename, - &DataFile::upgrade_sampleAndHold , &DataFile::upgrade_midiCCIndexing + &DataFile::upgrade_sampleAndHold , &DataFile::upgrade_midiCCIndexing, + &DataFile::upgrade_loopsRename , &DataFile::upgrade_noteTypes, + &DataFile::upgrade_fixCMTDelays }; // Vector of all versions that have upgrade routines. @@ -231,8 +236,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; } @@ -376,12 +384,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 +410,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 ); - - return true; + showError(SongEditor::tr("Could not write file"), + SongEditor::tr("An unknown error has occured and the file could not be saved.")); + return false; } - return false; + 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 true; } @@ -978,8 +985,7 @@ void DataFile::upgrade_0_4_0_20080622() { QDomElement el = list.item( i ).toElement(); QString s = el.attribute( "name" ); - s.replace( QRegExp( "^Beat/Baseline " ), - "Beat/Bassline " ); + s.replace(QRegularExpression("^Beat/Baseline "), "Beat/Bassline"); el.setAttribute( "name", s ); } } @@ -1114,7 +1120,7 @@ void DataFile::upgrade_1_1_91() { QDomElement el = list.item( i ).toElement(); QString s = el.attribute( "src" ); - s.replace( QRegExp("/samples/bassloopes/"), "/samples/bassloops/" ); + s.replace(QRegularExpression("/samples/bassloopes/"), "/samples/bassloops/"); el.setAttribute( "src", s ); } @@ -1199,12 +1205,11 @@ void DataFile::upgrade_1_2_0_rc3() "pattern" ); for( int j = 0; !patterns.item( j ).isNull(); ++j ) { - int patternLength, steps; QDomElement el = patterns.item( j ).toElement(); if( el.attribute( "len" ) != "" ) { - patternLength = el.attribute( "len" ).toInt(); - steps = patternLength / 12; + int patternLength = el.attribute( "len" ).toInt(); + int steps = patternLength / 12; el.setAttribute( "steps", steps ); } } @@ -1461,7 +1466,7 @@ void DataFile::upgrade_1_3_0() if(num == 4) { // don't modify port 4, but some other ones: - int zoom_port; + int zoom_port = 0; if (plugin == "Equalizer5Band") zoom_port = 36; else if (plugin == "Equalizer8Band") @@ -1672,6 +1677,62 @@ void DataFile::upgrade_automationNodes() } } +// Convert the negative length notes to StepNotes +void DataFile::upgrade_noteTypes() +{ + const auto notes = elementsByTagName("note"); + + for (int i = 0; i < notes.size(); ++i) + { + auto note = notes.item(i).toElement(); + + const auto noteSize = note.attribute("len").toInt(); + if (noteSize < 0) + { + note.setAttribute("len", DefaultTicksPerBar / 16); + note.setAttribute("type", static_cast(Note::Type::Step)); + } + } +} + +void DataFile::upgrade_fixCMTDelays() +{ + static const QMap nameMap { + { "delay_0,01s", "delay_0.01s" }, + { "delay_0,1s", "delay_0.1s" }, + { "fbdelay_0,01s", "fbdelay_0.01s" }, + { "fbdelay_0,1s", "fbdelay_0.1s" } + }; + + const auto effects = elementsByTagName("effect"); + + for (int i = 0; i < effects.size(); ++i) + { + auto effect = effects.item(i).toElement(); + + // We are only interested in LADSPA plugins + if (effect.attribute("name") != "ladspaeffect") { continue; } + + // Fetch all attributes (LMMS) beneath the LADSPA effect so that we can check the value of the plugin attribute (XML) + auto attributes = effect.elementsByTagName("attribute"); + for (int j = 0; j < attributes.size(); ++j) + { + auto attribute = attributes.item(j).toElement(); + + if (attribute.attribute("name") == "plugin") + { + const auto attributeValue = attribute.attribute("value"); + + const auto it = nameMap.constFind(attributeValue); + if (it != nameMap.constEnd()) + { + attribute.setAttribute("value", *it); + } + } + } + } +} + /** \brief Note range has been extended to match MIDI specification * @@ -1814,7 +1875,72 @@ void DataFile::upgrade_sampleAndHold() // Correct old random wave LFO speeds if (e.attribute("wave").toInt() == 6) { - e.setAttribute("speed",0.01f); + e.setAttribute("speed", 0.01f); + } + } +} + + +// Change loops' filenames in s +void DataFile::upgrade_loopsRename() +{ + auto createEntry = [](const QString& originalName, const QString& bpm, const QString& extension = "ogg") + { + const QString replacement = originalName + " - " + bpm + " BPM." + extension; + return std::pair{originalName + "." + extension, replacement}; + }; + + static const QMap namesToNamesWithBPMsMap { + { createEntry("bassloops/briff01", "140") }, + { createEntry("bassloops/rave_bass01", "180") }, + { createEntry("bassloops/rave_bass02", "180") }, + { createEntry("bassloops/tb303_01", "123") }, + { createEntry("bassloops/techno_bass01", "140") }, + { createEntry("bassloops/techno_bass02", "140") }, + { createEntry("bassloops/techno_synth01", "140") }, + { createEntry("bassloops/techno_synth02", "140") }, + { createEntry("bassloops/techno_synth03", "130") }, + { createEntry("bassloops/techno_synth04", "140") }, + { createEntry("beats/909beat01", "122") }, + { createEntry("beats/break01", "168") }, + { createEntry("beats/break02", "141") }, + { createEntry("beats/break03", "168") }, + { createEntry("beats/electro_beat01", "120") }, + { createEntry("beats/electro_beat02", "119") }, + { createEntry("beats/house_loop01", "142") }, + { createEntry("beats/jungle01", "168") }, + { createEntry("beats/rave_hihat01", "180") }, + { createEntry("beats/rave_hihat02", "180") }, + { createEntry("beats/rave_kick01", "180") }, + { createEntry("beats/rave_kick02", "180") }, + { createEntry("beats/rave_snare01", "180") }, + { createEntry("latin/latin_brass01", "140") }, + { createEntry("latin/latin_guitar01", "126") }, + { createEntry("latin/latin_guitar02", "140") }, + { createEntry("latin/latin_guitar03", "120") } + }; + + // Replace loop sample names + for (const auto& [elem, srcAttrs] : ELEMENTS_WITH_RESOURCES) + { + auto elements = elementsByTagName(elem); + + for (const auto& srcAttr : srcAttrs) + { + for (int i = 0; i < elements.length(); ++i) + { + auto item = elements.item(i).toElement(); + + if (item.isNull() || !item.hasAttribute(srcAttr)) { continue; } + + const QString srcVal = item.attribute(srcAttr); + + const auto it = namesToNamesWithBPMsMap.constFind(srcVal); + if (it != namesToNamesWithBPMsMap.constEnd()) + { + item.setAttribute(srcAttr, *it); + } + } } } } @@ -1991,5 +2117,4 @@ unsigned int DataFile::legacyFileVersion() return std::distance( UPGRADE_VERSIONS.begin(), firstRequiredUpgrade ); } - } // namespace lmms diff --git a/src/core/DrumSynth.cpp b/src/core/DrumSynth.cpp index decc6bfa2..031b19c1c 100644 --- a/src/core/DrumSynth.cpp +++ b/src/core/DrumSynth.cpp @@ -69,16 +69,16 @@ float mem_t=1.0f, mem_o=1.0f, mem_n=1.0f, mem_b=1.0f, mem_tune=1.0f, mem_time=1. int DrumSynth::LongestEnv() { - long e, eon, p; - float l=0.f; + float l = 0.f; - for(e=1; e<7; e++) //3 - { - eon = e - 1; if(eon>2) eon=eon-1; - p = 0; - while (envpts[e][0][p + 1] >= 0.f) p++; - envData[e][MAX] = envpts[e][0][p] * timestretch; - if(chkOn[eon]==1) if(envData[e][MAX]>l) l=envData[e][MAX]; + for (long e = 1; e < 7; e++) // 3 + { + long eon = e - 1; + if (eon > 2) { eon = eon - 1; } + long p = 0; + while (envpts[e][0][p + 1] >= 0.f) { p++; } + envData[e][MAX] = envpts[e][0][p] * timestretch; + if (chkOn[eon] == 1 && envData[e][MAX] > l) { l = envData[e][MAX]; } } //l *= timestretch; @@ -102,16 +102,15 @@ float DrumSynth::LoudestEnv() void DrumSynth::UpdateEnv(int e, long t) { - float endEnv, dT; - //0.2's added - envData[e][NEXTT] = envpts[e][0][(long)(envData[e][PNT] + 1.f)] * timestretch; //get next point - if(envData[e][NEXTT] < 0) envData[e][NEXTT] = 442000 * timestretch; //if end point, hold - envData[e][ENV] = envpts[e][1][(long)(envData[e][PNT] + 0.f)] * 0.01f; //this level - endEnv = envpts[e][1][(long)(envData[e][PNT] + 1.f)] * 0.01f; //next level - dT = envData[e][NEXTT] - (float)t; - if(dT < 1.0) dT = 1.0; - envData[e][dENV] = (endEnv - envData[e][ENV]) / dT; - envData[e][PNT] = envData[e][PNT] + 1.0f; + // 0.2's added + envData[e][NEXTT] = envpts[e][0][static_cast(envData[e][PNT] + 1.f)] * timestretch; // get next point + if (envData[e][NEXTT] < 0) { envData[e][NEXTT] = 442000 * timestretch; } // if end point, hold + envData[e][ENV] = envpts[e][1][static_cast(envData[e][PNT] + 0.f)] * 0.01f; // this level + float endEnv = envpts[e][1][static_cast(envData[e][PNT] + 1.f)] * 0.01f; // next level + float dT = envData[e][NEXTT] - static_cast(t); + if (dT < 1.0) { dT = 1.0; } + envData[e][dENV] = (endEnv - envData[e][ENV]) / dT; + envData[e][PNT] = envData[e][PNT] + 1.0f; } @@ -149,34 +148,41 @@ void DrumSynth::GetEnv(int env, const char *sec, const char *key, QString ini) float DrumSynth::waveform(float ph, int form) { - float w; + float w; - switch (form) - { - case 0: w = (float)sin(fmod(ph,TwoPi)); break; //sine - case 1: w = (float)fabs(2.0f*(float)sin(fmod(0.5f*ph,TwoPi)))-1.f; break; //sine^2 - case 2: while(ph1.f) w=2.f-w; - break; - case 3: w = ph - TwoPi * (float)(int)(ph / TwoPi); //saw - w = (0.3183098f * w) - 1.f; break; - default: w = (sin(fmod(ph,TwoPi))>0.0)? 1.f: -1.f; break; //square - } + switch (form) + { + case 0: + w = static_cast(sin(fmod(ph, TwoPi))); + break; // sine + case 1: + w = static_cast(fabs(2.0f * static_cast(sin(fmod(0.5f * ph, TwoPi))) - 1.f)); + break; // sine^2 + case 2: + while (ph < TwoPi) { ph += TwoPi; } + w = 0.6366197f * static_cast(fmod(ph, TwoPi) - 1.f); // tri + if (w > 1.f) { w = 2.f - w; } + break; + case 3: + w = ph - TwoPi * static_cast(static_cast(ph / TwoPi)); // saw + w = (0.3183098f * w) - 1.f; + break; + default: + w = (sin(fmod(ph, TwoPi)) > 0.0) ? 1.f : -1.f; + break; // square + } - return w; + return w; } int DrumSynth::GetPrivateProfileString(const char *sec, const char *key, const char *def, char *buffer, int size, QString file) { - stringstream is; - bool inSection = false; - char *line; - char *k, *b; - int len = 0; + stringstream is; + bool inSection = false; + int len = 0; - line = (char*)malloc(200); + char* line = static_cast(malloc(200)); // Use QFile to handle unicode file name on Windows // Previously we used ifstream directly @@ -201,8 +207,8 @@ int DrumSynth::GetPrivateProfileString(const char *sec, const char *key, const c if (line[0] == '[') break; - k = strtok(line, " \t="); - b = strtok(nullptr, "\n\r\0"); + char* k = strtok(line, " \t="); + char* b = strtok(nullptr, "\n\r\0"); if (k != 0 && strcasecmp(k, key)==0) { if (b==0) { diff --git a/src/core/Effect.cpp b/src/core/Effect.cpp index 151eaf13e..aa6e56cd2 100644 --- a/src/core/Effect.cpp +++ b/src/core/Effect.cpp @@ -52,13 +52,19 @@ Effect::Effect( const Plugin::Descriptor * _desc, m_autoQuitModel( 1.0f, 1.0f, 8000.0f, 100.0f, 1.0f, this, tr( "Decay" ) ), m_autoQuitDisabled( false ) { + m_wetDryModel.setCenterValue(0); + m_srcState[0] = m_srcState[1] = nullptr; reinitSRC(); - + if( ConfigManager::inst()->value( "ui", "disableautoquit").toInt() ) { m_autoQuitDisabled = true; } + + // Call the virtual method onEnabledChanged so that effects can react to changes, + // e.g. by resetting state. + connect(&m_enabledModel, &BoolModel::dataChanged, [this] { onEnabledChanged(); }); } @@ -208,8 +214,8 @@ void Effect::resample( int _i, const sampleFrame * _src_buf, m_srcData[_i].data_out = _dst_buf[0].data (); m_srcData[_i].src_ratio = (double) _dst_sr / _src_sr; m_srcData[_i].end_of_input = 0; - int error; - if( ( error = src_process( m_srcState[_i], &m_srcData[_i] ) ) ) + + if (int error = src_process(m_srcState[_i], &m_srcData[_i])) { qFatal( "Effect::resample(): error while resampling: %s\n", src_strerror( error ) ); diff --git a/src/core/Engine.cpp b/src/core/Engine.cpp index 6c8104721..c1f609120 100644 --- a/src/core/Engine.cpp +++ b/src/core/Engine.cpp @@ -146,7 +146,7 @@ float Engine::framesPerTick(sample_rate_t sampleRate) void Engine::updateFramesPerTick() { - s_framesPerTick = s_audioEngine->processingSampleRate() * 60.0f * 4 / DefaultTicksPerBar / s_song->getTempo(); + s_framesPerTick = s_audioEngine->outputSampleRate() * 60.0f * 4 / DefaultTicksPerBar / s_song->getTempo(); } diff --git a/src/core/EnvelopeAndLfoParameters.cpp b/src/core/EnvelopeAndLfoParameters.cpp index 0a9673c8e..861a62b51 100644 --- a/src/core/EnvelopeAndLfoParameters.cpp +++ b/src/core/EnvelopeAndLfoParameters.cpp @@ -22,13 +22,17 @@ * */ -#include - #include "EnvelopeAndLfoParameters.h" + +#include +#include + #include "AudioEngine.h" #include "Engine.h" #include "Oscillator.h" - +#include "PathUtil.h" +#include "SampleLoader.h" +#include "Song.h" namespace lmms { @@ -118,7 +122,7 @@ EnvelopeAndLfoParameters::EnvelopeAndLfoParameters( m_controlEnvAmountModel( false, this, tr( "Modulate env amount" ) ), m_lfoFrame( 0 ), m_lfoAmountIsZero( false ), - m_lfoShapeData( nullptr ) + m_lfoShapeData(nullptr) { m_amountModel.setCenterValue( 0 ); m_lfoAmountModel.setCenterValue( 0 ); @@ -221,7 +225,7 @@ inline sample_t EnvelopeAndLfoParameters::lfoShapeSample( fpp_t _frame_offset ) shape_sample = Oscillator::sawSample( phase ); break; case LfoShape::UserDefinedWave: - shape_sample = m_userWave.userWaveSample( phase ); + shape_sample = Oscillator::userWaveSample(m_userWave.get(), phase); break; case LfoShape::RandomWave: if( frame == 0 ) @@ -354,7 +358,7 @@ void EnvelopeAndLfoParameters::saveSettings( QDomDocument & _doc, m_lfoAmountModel.saveSettings( _doc, _parent, "lamt" ); m_x100Model.saveSettings( _doc, _parent, "x100" ); m_controlEnvAmountModel.saveSettings( _doc, _parent, "ctlenvamt" ); - _parent.setAttribute( "userwavefile", m_userWave.audioFile() ); + _parent.setAttribute("userwavefile", m_userWave->audioFile()); } @@ -386,7 +390,14 @@ void EnvelopeAndLfoParameters::loadSettings( const QDomElement & _this ) m_sustainModel.setValue( 1.0 - m_sustainModel.value() ); } - m_userWave.setAudioFile( _this.attribute( "userwavefile" ) ); + if (const auto userWaveFile = _this.attribute("userwavefile"); !userWaveFile.isEmpty()) + { + if (QFileInfo(PathUtil::toAbsolute(userWaveFile)).exists()) + { + m_userWave = gui::SampleLoader::createBufferFromFile(_this.attribute("userwavefile")); + } + else { Engine::getSong()->collectError(QString("%1: %2").arg(tr("Sample not found"), userWaveFile)); } + } updateSampleVars(); } @@ -399,7 +410,7 @@ void EnvelopeAndLfoParameters::updateSampleVars() QMutexLocker m(&m_paramMutex); const float frames_per_env_seg = SECS_PER_ENV_SEGMENT * - Engine::audioEngine()->processingSampleRate(); + Engine::audioEngine()->outputSampleRate(); // TODO: Remove the expKnobVals, time should be linear const auto predelay_frames = static_cast(frames_per_env_seg * expKnobVal(m_predelayModel.value())); @@ -498,7 +509,7 @@ void EnvelopeAndLfoParameters::updateSampleVars() const float frames_per_lfo_oscillation = SECS_PER_LFO_OSCILLATION * - Engine::audioEngine()->processingSampleRate(); + Engine::audioEngine()->outputSampleRate(); m_lfoPredelayFrames = static_cast( frames_per_lfo_oscillation * expKnobVal( m_lfoPredelayModel.value() ) ); m_lfoAttackFrames = static_cast( frames_per_lfo_oscillation * diff --git a/src/core/FileSearch.cpp b/src/core/FileSearch.cpp new file mode 100644 index 000000000..fe1efd97e --- /dev/null +++ b/src/core/FileSearch.cpp @@ -0,0 +1,96 @@ +/* + * FileSearch.cpp - File system search task + * + * Copyright (c) 2024 saker + * + * 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 "FileSearch.h" + +#include +#include +#include + +#ifdef __MINGW32__ +#include +#else +#include +#endif + +namespace lmms { +FileSearch::FileSearch(const QString& filter, const QStringList& paths, const QStringList& extensions, + const QStringList& excludedPaths, QDir::Filters dirFilters, QDir::SortFlags sortFlags) + : m_filter(filter) + , m_paths(paths) + , m_extensions(extensions) + , m_excludedPaths(excludedPaths) + , m_dirFilters(dirFilters) + , m_sortFlags(sortFlags) +{ +} + +void FileSearch::operator()() +{ + auto stack = QFileInfoList{}; + for (const auto& path : m_paths) + { + if (m_excludedPaths.contains(path)) { continue; } + + auto dir = QDir{path}; + stack.append(dir.entryInfoList(m_dirFilters, m_sortFlags)); + + while (!stack.empty()) + { + if (m_cancel.load(std::memory_order_relaxed)) { return; } + + const auto info = stack.takeFirst(); + const auto entryPath = info.absoluteFilePath(); + if (m_excludedPaths.contains(entryPath)) { continue; } + + const auto name = info.fileName(); + const auto validFile = info.isFile() && m_extensions.contains(info.suffix(), Qt::CaseInsensitive); + const auto passesFilter = name.contains(m_filter, Qt::CaseInsensitive); + + if ((validFile || info.isDir()) && passesFilter) + { + std::this_thread::sleep_for(std::chrono::milliseconds{MillisecondsBetweenResults}); + emit foundMatch(this, entryPath); + } + + if (info.isDir()) + { + dir.setPath(entryPath); + const auto entries = dir.entryInfoList(m_dirFilters, m_sortFlags); + + // Reverse to maintain the sorting within this directory when popped + std::for_each(entries.rbegin(), entries.rend(), [&stack](const auto& entry) { stack.push_front(entry); }); + } + } + } + + emit searchCompleted(this); +} + +void FileSearch::cancel() +{ + m_cancel.store(true, std::memory_order_relaxed); +} + +} // namespace lmms diff --git a/src/core/Instrument.cpp b/src/core/Instrument.cpp index b715bcac0..ca7ea7f25 100644 --- a/src/core/Instrument.cpp +++ b/src/core/Instrument.cpp @@ -37,9 +37,11 @@ namespace lmms Instrument::Instrument(InstrumentTrack * _instrument_track, const Descriptor * _descriptor, - const Descriptor::SubPluginFeatures::Key *key) : + const Descriptor::SubPluginFeatures::Key *key, + Flags flags) : Plugin(_descriptor, nullptr/* _instrument_track*/, key), - m_instrumentTrack( _instrument_track ) + m_instrumentTrack( _instrument_track ), + m_flags(flags) { } @@ -179,27 +181,33 @@ 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; } } } +float Instrument::computeReleaseTimeMsByFrameCount(f_cnt_t frames) const +{ + return frames / getSampleRate() * 1000.; +} +sample_rate_t Instrument::getSampleRate() const +{ + return Engine::audioEngine()->outputSampleRate(); +} + QString Instrument::fullDisplayName() const { diff --git a/src/core/InstrumentFunctions.cpp b/src/core/InstrumentFunctions.cpp index 976363d3d..549d658fc 100644 --- a/src/core/InstrumentFunctions.cpp +++ b/src/core/InstrumentFunctions.cpp @@ -369,7 +369,7 @@ void InstrumentFunctionArpeggio::processNote( NotePlayHandle * _n ) const int total_range = range * cnphv.size(); // number of frames that every note should be played - const auto arp_frames = (f_cnt_t)(m_arpTimeModel.value() / 1000.0f * Engine::audioEngine()->processingSampleRate()); + const auto arp_frames = (f_cnt_t)(m_arpTimeModel.value() / 1000.0f * Engine::audioEngine()->outputSampleRate()); const auto gated_frames = (f_cnt_t)(m_arpGateModel.value() * arp_frames / 100.0f); // used for calculating remaining frames for arp-note, we have to add @@ -433,42 +433,24 @@ void InstrumentFunctionArpeggio::processNote( NotePlayHandle * _n ) int cur_arp_idx = 0; // process according to arpeggio-direction... - if( dir == ArpDirection::Up ) + if (dir == ArpDirection::Up || dir == ArpDirection::Down) { cur_arp_idx = ( cur_frame / arp_frames ) % range; } - else if( dir == ArpDirection::Down ) - { - cur_arp_idx = range - ( cur_frame / arp_frames ) % - range - 1; - } - else if( dir == ArpDirection::UpAndDown && range > 1 ) + else if ((dir == ArpDirection::UpAndDown || dir == ArpDirection::DownAndUp) && range > 1) { // imagine, we had to play the arp once up and then // once down -> makes 2 * range possible notes... // because we don't play the lower and upper notes // twice, we have to subtract 2 - cur_arp_idx = ( cur_frame / arp_frames ) % ( range * 2 - 2 ); + cur_arp_idx = (cur_frame / arp_frames) % (range * 2 - (2 * static_cast(m_arpRepeatsModel.value()))); // if greater than range, we have to play down... // looks like the code for arp_dir==DOWN... :) - if( cur_arp_idx >= range ) + if (cur_arp_idx >= range) { - cur_arp_idx = range - cur_arp_idx % ( range - 1 ) - 1; + cur_arp_idx = range - cur_arp_idx % (range - 1) - static_cast(m_arpRepeatsModel.value()); } } - else if( dir == ArpDirection::DownAndUp && range > 1 ) - { - // copied from ArpDirection::UpAndDown above - cur_arp_idx = ( cur_frame / arp_frames ) % ( range * 2 - 2 ); - // if greater than range, we have to play down... - // looks like the code for arp_dir==DOWN... :) - if( cur_arp_idx >= range ) - { - cur_arp_idx = range - cur_arp_idx % ( range - 1 ) - 1; - } - // inverts direction - cur_arp_idx = range - cur_arp_idx - 1; - } else if( dir == ArpDirection::Random ) { // just pick a random chord-index @@ -485,6 +467,12 @@ void InstrumentFunctionArpeggio::processNote( NotePlayHandle * _n ) cur_arp_idx %= static_cast( range / m_arpRepeatsModel.value() ); } + // If ArpDirection::Down or ArpDirection::DownAndUp, invert the final range. + if (dir == ArpDirection::Down || dir == ArpDirection::DownAndUp) + { + cur_arp_idx = static_cast(range / m_arpRepeatsModel.value()) - cur_arp_idx - 1; + } + // now calculate final key for our arp-note const int sub_note_key = base_note_key + (cur_arp_idx / cur_chord_size ) * KeysPerOctave + chord_table.chords()[selected_arp][cur_arp_idx % cur_chord_size]; diff --git a/src/core/InstrumentPlayHandle.cpp b/src/core/InstrumentPlayHandle.cpp index 097719ad8..ef7d78f3f 100644 --- a/src/core/InstrumentPlayHandle.cpp +++ b/src/core/InstrumentPlayHandle.cpp @@ -46,24 +46,22 @@ void InstrumentPlayHandle::play(sampleFrame * working_buffer) // ensure that all our nph's have been processed first auto nphv = NotePlayHandle::nphsOfInstrumentTrack(instrumentTrack, true); - + bool nphsLeft; do { nphsLeft = false; - for (const NotePlayHandle * constNotePlayHandle : nphv) + for (const auto& handle : nphv) { - if (constNotePlayHandle->state() != ThreadableJob::ProcessingState::Done && - !constNotePlayHandle->isFinished()) + if (handle->state() != ThreadableJob::ProcessingState::Done && !handle->isFinished()) { nphsLeft = true; - NotePlayHandle * notePlayHandle = const_cast(constNotePlayHandle); - notePlayHandle->process(); + const_cast(handle)->process(); } } } while (nphsLeft); - + m_instrument->play(working_buffer); // Process the audio buffer that the instrument has just worked on... diff --git a/src/core/InstrumentSoundShaping.cpp b/src/core/InstrumentSoundShaping.cpp index 07c3bbf7c..9a2185da1 100644 --- a/src/core/InstrumentSoundShaping.cpp +++ b/src/core/InstrumentSoundShaping.cpp @@ -158,7 +158,7 @@ void InstrumentSoundShaping::processAudioBuffer( sampleFrame* buffer, if( n->m_filter == nullptr ) { - n->m_filter = std::make_unique>( Engine::audioEngine()->processingSampleRate() ); + n->m_filter = std::make_unique>( Engine::audioEngine()->outputSampleRate() ); } n->m_filter->setFilterType( static_cast::FilterType>(m_filterModel.value()) ); @@ -303,7 +303,7 @@ f_cnt_t InstrumentSoundShaping::releaseFrames() const f_cnt_t ret_val = m_instrumentTrack->instrument()->desiredReleaseFrames(); - if( m_instrumentTrack->instrument()->flags().testFlag( Instrument::Flag::IsSingleStreamed ) ) + if (m_instrumentTrack->instrument()->isSingleStreamed()) { return ret_val; } diff --git a/src/core/LadspaManager.cpp b/src/core/LadspaManager.cpp index 064c928ef..e4d472bd1 100644 --- a/src/core/LadspaManager.cpp +++ b/src/core/LadspaManager.cpp @@ -122,14 +122,8 @@ LadspaManager::~LadspaManager() LadspaManagerDescription * LadspaManager::getDescription( const ladspa_key_t & _plugin ) { - if( m_ladspaManagerMap.contains( _plugin ) ) - { - return( m_ladspaManagerMap[_plugin] ); - } - else - { - return( nullptr ); - } + auto const it = m_ladspaManagerMap.find(_plugin); + return it != m_ladspaManagerMap.end() ? *it : nullptr; } @@ -139,11 +133,7 @@ void LadspaManager::addPlugins( LADSPA_Descriptor_Function _descriptor_func, const QString & _file ) { - const LADSPA_Descriptor * descriptor; - - for( long pluginIndex = 0; - ( descriptor = _descriptor_func( pluginIndex ) ) != nullptr; - ++pluginIndex ) + for (long pluginIndex = 0; const auto descriptor = _descriptor_func(pluginIndex); ++pluginIndex) { ladspa_key_t key( _file, QString( descriptor->Label ) ); if( m_ladspaManagerMap.contains( key ) ) @@ -523,24 +513,16 @@ bool LadspaManager::isInteger( const ladspa_key_t & _plugin, bool LadspaManager::isEnum( const ladspa_key_t & _plugin, uint32_t _port ) { - if( m_ladspaManagerMap.contains( _plugin ) - && _port < getPortCount( _plugin ) ) + auto const * desc = getDescriptor(_plugin); + if (desc && _port < desc->PortCount) { - LADSPA_Descriptor_Function descriptorFunction = - m_ladspaManagerMap[_plugin]->descriptorFunction; - const LADSPA_Descriptor * descriptor = - descriptorFunction( - m_ladspaManagerMap[_plugin]->index ); LADSPA_PortRangeHintDescriptor hintDescriptor = - descriptor->PortRangeHints[_port].HintDescriptor; + desc->PortRangeHints[_port].HintDescriptor; // This is an LMMS extension to ladspa - return( LADSPA_IS_HINT_INTEGER( hintDescriptor ) && - LADSPA_IS_HINT_TOGGLED( hintDescriptor ) ); - } - else - { - return( false ); + return LADSPA_IS_HINT_INTEGER(hintDescriptor) && LADSPA_IS_HINT_TOGGLED(hintDescriptor); } + + return false; } @@ -566,22 +548,20 @@ const void * LadspaManager::getImplementationData( -const LADSPA_Descriptor * LadspaManager::getDescriptor( - const ladspa_key_t & _plugin ) +const LADSPA_Descriptor * LadspaManager::getDescriptor(const ladspa_key_t & _plugin) { - if( m_ladspaManagerMap.contains( _plugin ) ) + auto const it = m_ladspaManagerMap.find(_plugin); + if (it != m_ladspaManagerMap.end()) { - LADSPA_Descriptor_Function descriptorFunction = - m_ladspaManagerMap[_plugin]->descriptorFunction; - const LADSPA_Descriptor * descriptor = - descriptorFunction( - m_ladspaManagerMap[_plugin]->index ); - return( descriptor ); - } - else - { - return( nullptr ); + auto const plugin = *it; + + LADSPA_Descriptor_Function descriptorFunction = plugin->descriptorFunction; + const LADSPA_Descriptor* descriptor = descriptorFunction(plugin->index); + + return descriptor; } + + return nullptr; } diff --git a/src/core/LfoController.cpp b/src/core/LfoController.cpp index 88f64803c..0c6d3d1ae 100644 --- a/src/core/LfoController.cpp +++ b/src/core/LfoController.cpp @@ -23,13 +23,15 @@ * */ -#include - - #include "LfoController.h" -#include "AudioEngine.h" -#include "Song.h" +#include +#include + +#include "AudioEngine.h" +#include "PathUtil.h" +#include "SampleLoader.h" +#include "Song.h" namespace lmms { @@ -48,7 +50,7 @@ LfoController::LfoController( Model * _parent ) : m_phaseOffset( 0 ), m_currentPhase( 0 ), m_sampleFunction( &Oscillator::sinSample ), - m_userDefSampleBuffer( new SampleBuffer ) + m_userDefSampleBuffer(std::make_shared()) { setSampleExact( true ); connect( &m_waveModel, SIGNAL(dataChanged()), @@ -74,7 +76,6 @@ LfoController::LfoController( Model * _parent ) : LfoController::~LfoController() { - sharedObject::unref( m_userDefSampleBuffer ); m_baseModel.disconnect( this ); m_speedModel.disconnect( this ); m_amountModel.disconnect( this ); @@ -122,7 +123,7 @@ void LfoController::updateValueBuffer() } case Oscillator::WaveShape::UserDefined: { - currentSample = m_userDefSampleBuffer->userWaveSample(phase); + currentSample = Oscillator::userWaveSample(m_userDefSampleBuffer.get(), phase); break; } default: @@ -154,7 +155,7 @@ void LfoController::updatePhase() void LfoController::updateDuration() { - float newDurationF = Engine::audioEngine()->processingSampleRate() * m_speedModel.value(); + float newDurationF = Engine::audioEngine()->outputSampleRate() * m_speedModel.value(); switch(m_multiplierModel.value() ) { @@ -222,7 +223,7 @@ void LfoController::saveSettings( QDomDocument & _doc, QDomElement & _this ) m_phaseModel.saveSettings( _doc, _this, "phase" ); m_waveModel.saveSettings( _doc, _this, "wave" ); m_multiplierModel.saveSettings( _doc, _this, "multiplier" ); - _this.setAttribute( "userwavefile" , m_userDefSampleBuffer->audioFile() ); + _this.setAttribute("userwavefile", m_userDefSampleBuffer->audioFile()); } @@ -237,7 +238,15 @@ void LfoController::loadSettings( const QDomElement & _this ) m_phaseModel.loadSettings( _this, "phase" ); m_waveModel.loadSettings( _this, "wave" ); m_multiplierModel.loadSettings( _this, "multiplier" ); - m_userDefSampleBuffer->setAudioFile( _this.attribute("userwavefile" ) ); + + if (const auto userWaveFile = _this.attribute("userwavefile"); !userWaveFile.isEmpty()) + { + if (QFileInfo(PathUtil::toAbsolute(userWaveFile)).exists()) + { + m_userDefSampleBuffer = gui::SampleLoader::createBufferFromFile(_this.attribute("userwavefile")); + } + else { Engine::getSong()->collectError(QString("%1: %2").arg(tr("Sample not found"), userWaveFile)); } + } updateSampleFunction(); } diff --git a/src/core/LinkedModelGroups.cpp b/src/core/LinkedModelGroups.cpp index 83bebc2a0..c52bce433 100644 --- a/src/core/LinkedModelGroups.cpp +++ b/src/core/LinkedModelGroups.cpp @@ -143,9 +143,7 @@ bool LinkedModelGroup::containsModel(const QString &name) const void LinkedModelGroups::linkAllModels() { LinkedModelGroup* first = getGroup(0); - LinkedModelGroup* cur; - - for (std::size_t i = 1; (cur = getGroup(i)); ++i) + for (size_t i = 1; auto cur = getGroup(i); ++i) { first->linkControls(cur); } @@ -172,8 +170,7 @@ void LinkedModelGroups::saveSettings(QDomDocument& doc, QDomElement& that) void LinkedModelGroups::loadSettings(const QDomElement& that) { QDomElement models = that.firstChildElement("models"); - LinkedModelGroup* grp0; - if (!models.isNull() && (grp0 = getGroup(0))) + if (auto grp0 = getGroup(0); !models.isNull() && grp0) { // only load the first group, the others are linked to the first grp0->loadValues(models); diff --git a/src/core/MemoryHelper.cpp b/src/core/MemoryHelper.cpp index de80ef770..8f990d57e 100644 --- a/src/core/MemoryHelper.cpp +++ b/src/core/MemoryHelper.cpp @@ -36,15 +36,14 @@ namespace lmms */ void* MemoryHelper::alignedMalloc( size_t byteNum ) { - char *ptr, *ptr2, *aligned_ptr; int align_mask = LMMS_ALIGN_SIZE - 1; - ptr = static_cast( malloc( byteNum + LMMS_ALIGN_SIZE + sizeof( int ) ) ); + char* ptr = static_cast(malloc(byteNum + LMMS_ALIGN_SIZE + sizeof(int))); if( ptr == nullptr ) return nullptr; - ptr2 = ptr + sizeof( int ); - aligned_ptr = ptr2 + ( LMMS_ALIGN_SIZE - ( ( size_t ) ptr2 & align_mask ) ); + char* ptr2 = ptr + sizeof(int); + char* aligned_ptr = ptr2 + (LMMS_ALIGN_SIZE - ((size_t)ptr2 & align_mask)); ptr2 = aligned_ptr - sizeof( int ); *( ( int* ) ptr2 ) = ( int )( aligned_ptr - ptr ); diff --git a/src/core/MemoryManager.cpp b/src/core/MemoryManager.cpp deleted file mode 100644 index bd3168f14..000000000 --- a/src/core/MemoryManager.cpp +++ /dev/null @@ -1,84 +0,0 @@ -/* - * MemoryManager.cpp - * - * Copyright (c) 2017 Lukas W - * - * 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 "MemoryManager.h" - -#include -#include "rpmalloc.h" - -namespace lmms -{ - - -/// Global static object handling rpmalloc intializing and finalizing -struct MemoryManagerGlobalGuard { - MemoryManagerGlobalGuard() { - rpmalloc_initialize(); - } - ~MemoryManagerGlobalGuard() { - rpmalloc_finalize(); - } -} static mm_global_guard; - - -namespace { -static thread_local size_t thread_guard_depth; -} - -MemoryManager::ThreadGuard::ThreadGuard() -{ - if (thread_guard_depth++ == 0) { - rpmalloc_thread_initialize(); - } -} - -MemoryManager::ThreadGuard::~ThreadGuard() -{ - if (--thread_guard_depth == 0) { - rpmalloc_thread_finalize(true); - } -} - -static thread_local MemoryManager::ThreadGuard local_mm_thread_guard{}; - -void* MemoryManager::alloc(size_t size) -{ - // Reference local thread guard to ensure it is initialized. - // Compilers may optimize the instance away otherwise. - Q_UNUSED(&local_mm_thread_guard); - Q_ASSERT_X(rpmalloc_is_thread_initialized(), "MemoryManager::alloc", "Thread not initialized"); - return rpmalloc(size); -} - - -void MemoryManager::free(void * ptr) -{ - Q_UNUSED(&local_mm_thread_guard); - Q_ASSERT_X(rpmalloc_is_thread_initialized(), "MemoryManager::free", "Thread not initialized"); - return rpfree(ptr); -} - - -} // namespace lmms diff --git a/src/core/MixHelpers.cpp b/src/core/MixHelpers.cpp index bc55419e9..209640b70 100644 --- a/src/core/MixHelpers.cpp +++ b/src/core/MixHelpers.cpp @@ -178,6 +178,15 @@ struct AddSwappedMultipliedOp const float m_coeff; }; +void multiply(sampleFrame* dst, float coeff, int frames) +{ + for (int i = 0; i < frames; ++i) + { + dst[i][0] *= coeff; + dst[i][1] *= coeff; + } +} + void addSwappedMultiplied( sampleFrame* dst, const sampleFrame* src, float coeffSrc, int frames ) { run<>( dst, src, frames, AddSwappedMultipliedOp(coeffSrc) ); diff --git a/src/core/Mixer.cpp b/src/core/Mixer.cpp index 59c2dd72e..6dd2e3451 100644 --- a/src/core/Mixer.cpp +++ b/src/core/Mixer.cpp @@ -72,7 +72,6 @@ MixerChannel::MixerChannel( int idx, Model * _parent ) : m_lock(), m_channelIndex( idx ), m_queued( false ), - m_hasColor( false ), m_dependenciesMet(0) { BufferManager::clear( m_buffer, Engine::audioEngine()->framesPerPeriod() ); @@ -722,6 +721,7 @@ void Mixer::clearChannel(mix_ch_t index) ch->m_volumeModel.setDisplayName( ch->m_name + ">" + tr( "Volume" ) ); ch->m_muteModel.setDisplayName( ch->m_name + ">" + tr( "Mute" ) ); ch->m_soloModel.setDisplayName( ch->m_name + ">" + tr( "Solo" ) ); + ch->setColor(std::nullopt); // send only to master if( index > 0) @@ -759,7 +759,7 @@ void Mixer::saveSettings( QDomDocument & _doc, QDomElement & _this ) ch->m_soloModel.saveSettings( _doc, mixch, "soloed" ); mixch.setAttribute( "num", i ); mixch.setAttribute( "name", ch->m_name ); - if( ch->m_hasColor ) mixch.setAttribute( "color", ch->m_color.name() ); + if (const auto& color = ch->color()) { mixch.setAttribute("color", color->name()); } // add the channel sends for (const auto& send : ch->m_sends) @@ -805,10 +805,9 @@ void Mixer::loadSettings( const QDomElement & _this ) m_mixerChannels[num]->m_muteModel.loadSettings( mixch, "muted" ); m_mixerChannels[num]->m_soloModel.loadSettings( mixch, "soloed" ); m_mixerChannels[num]->m_name = mixch.attribute( "name" ); - if( mixch.hasAttribute( "color" ) ) + if (mixch.hasAttribute("color")) { - m_mixerChannels[num]->m_hasColor = true; - m_mixerChannels[num]->m_color.setNamedColor( mixch.attribute( "color" ) ); + m_mixerChannels[num]->setColor(QColor{mixch.attribute("color")}); } m_mixerChannels[num]->m_fxChain.restoreState( mixch.firstChildElement( diff --git a/src/core/Note.cpp b/src/core/Note.cpp index a4ad61412..ed3a00f10 100644 --- a/src/core/Note.cpp +++ b/src/core/Note.cpp @@ -74,7 +74,8 @@ Note::Note( const Note & note ) : m_panning( note.m_panning ), m_length( note.m_length ), m_pos( note.m_pos ), - m_detuning( nullptr ) + m_detuning(nullptr), + m_type(note.m_type) { if( note.m_detuning ) { @@ -179,6 +180,7 @@ void Note::saveSettings( QDomDocument & doc, QDomElement & parent ) parent.setAttribute( "pan", m_panning ); parent.setAttribute( "len", m_length ); parent.setAttribute( "pos", m_pos ); + parent.setAttribute("type", static_cast(m_type)); if( m_detuning && m_length ) { @@ -197,6 +199,9 @@ void Note::loadSettings( const QDomElement & _this ) m_panning = _this.attribute( "pan" ).toInt(); m_length = _this.attribute( "len" ).toInt(); m_pos = _this.attribute( "pos" ).toInt(); + // Default m_type value is 0, which corresponds to RegularNote + static_assert(0 == static_cast(Type::Regular)); + m_type = static_cast(_this.attribute("type", "0").toInt()); if( _this.hasChildNodes() ) { diff --git a/src/core/NotePlayHandle.cpp b/src/core/NotePlayHandle.cpp index eb9c7ddbf..d7882b525 100644 --- a/src/core/NotePlayHandle.cpp +++ b/src/core/NotePlayHandle.cpp @@ -53,7 +53,7 @@ NotePlayHandle::NotePlayHandle( InstrumentTrack* instrumentTrack, NotePlayHandle *parent, int midiEventChannel, Origin origin ) : - PlayHandle( Type::NotePlayHandle, _offset ), + PlayHandle( PlayHandle::Type::NotePlayHandle, _offset ), Note( n.length(), n.pos(), n.key(), n.getVolume(), n.getPanning(), n.detuning() ), m_pluginData( nullptr ), m_instrumentTrack( instrumentTrack ), @@ -109,7 +109,7 @@ NotePlayHandle::NotePlayHandle( InstrumentTrack* instrumentTrack, m_instrumentTrack->midiNoteOn( *this ); } - if(m_instrumentTrack->instrument() && m_instrumentTrack->instrument()->flags() & Instrument::Flag::IsSingleStreamed ) + if (m_instrumentTrack->instrument() && m_instrumentTrack->instrument()->isSingleStreamed()) { setUsesBuffer( false ); } @@ -610,9 +610,9 @@ int NotePlayHandleManager::s_size; void NotePlayHandleManager::init() { - s_available = MM_ALLOC( INITIAL_NPH_CACHE ); + s_available = new NotePlayHandle*[INITIAL_NPH_CACHE]; - auto n = MM_ALLOC(INITIAL_NPH_CACHE); + auto n = static_cast(std::malloc(sizeof(NotePlayHandle) * INITIAL_NPH_CACHE)); for( int i=0; i < INITIAL_NPH_CACHE; ++i ) { @@ -655,11 +655,11 @@ void NotePlayHandleManager::release( NotePlayHandle * nph ) void NotePlayHandleManager::extend( int c ) { s_size += c; - auto tmp = MM_ALLOC(s_size); - MM_FREE( s_available ); + auto tmp = new NotePlayHandle*[s_size]; + delete[] s_available; s_available = tmp; - auto n = MM_ALLOC(c); + auto n = static_cast(std::malloc(sizeof(NotePlayHandle) * c)); for( int i=0; i < c; ++i ) { @@ -670,7 +670,7 @@ void NotePlayHandleManager::extend( int c ) void NotePlayHandleManager::free() { - MM_FREE(s_available); + delete[] s_available; } diff --git a/src/core/Oscillator.cpp b/src/core/Oscillator.cpp index 06033b63e..e45a3aa87 100644 --- a/src/core/Oscillator.cpp +++ b/src/core/Oscillator.cpp @@ -79,7 +79,7 @@ Oscillator::Oscillator(const IntModel *wave_shape_model, void Oscillator::update(sampleFrame* ab, const fpp_t frames, const ch_cnt_t chnl, bool modulator) { - if (m_freq >= Engine::audioEngine()->processingSampleRate() / 2) + if (m_freq >= Engine::audioEngine()->outputSampleRate() / 2) { BufferManager::clear(ab, frames); return; @@ -182,19 +182,23 @@ void Oscillator::generateFromFFT(int bands, sample_t* table) normalize(s_sampleBuffer.data(), table, OscillatorConstants::WAVETABLE_LENGTH, 2*OscillatorConstants::WAVETABLE_LENGTH + 1); } -void Oscillator::generateAntiAliasUserWaveTable(SampleBuffer *sampleBuffer) +std::unique_ptr Oscillator::generateAntiAliasUserWaveTable(const SampleBuffer* sampleBuffer) { - if (sampleBuffer->m_userAntiAliasWaveTable == nullptr) {return;} - + auto userAntiAliasWaveTable = std::make_unique(); for (int i = 0; i < OscillatorConstants::WAVE_TABLES_PER_WAVEFORM_COUNT; ++i) { - for (int i = 0; i < OscillatorConstants::WAVETABLE_LENGTH; ++i) + // TODO: This loop seems to be doing the same thing for each iteration of the outer loop, + // and could probably be moved out of it + for (int j = 0; j < OscillatorConstants::WAVETABLE_LENGTH; ++j) { - s_sampleBuffer[i] = sampleBuffer->userWaveSample((float)i / (float)OscillatorConstants::WAVETABLE_LENGTH); + s_sampleBuffer[j] = Oscillator::userWaveSample( + sampleBuffer, static_cast(j) / OscillatorConstants::WAVETABLE_LENGTH); } fftwf_execute(s_fftPlan); - Oscillator::generateFromFFT(OscillatorConstants::MAX_FREQ / freqFromWaveTableBand(i), (*(sampleBuffer->m_userAntiAliasWaveTable))[i].data()); + Oscillator::generateFromFFT(OscillatorConstants::MAX_FREQ / freqFromWaveTableBand(i), (*userAntiAliasWaveTable)[i].data()); } + + return userAntiAliasWaveTable; } @@ -677,7 +681,7 @@ void Oscillator::updateFM( sampleFrame * _ab, const fpp_t _frames, m_subOsc->update( _ab, _frames, _chnl, true ); recalcPhase(); const float osc_coeff = m_freq * m_detuning_div_samplerate; - const float sampleRateCorrection = 44100.0f / Engine::audioEngine()->processingSampleRate(); + const float sampleRateCorrection = 44100.0f / Engine::audioEngine()->outputSampleRate(); for( fpp_t frame = 0; frame < _frames; ++frame ) { @@ -693,7 +697,7 @@ void Oscillator::updateFM( sampleFrame * _ab, const fpp_t _frames, template<> inline sample_t Oscillator::getSample(const float sample) { - const float current_freq = m_freq * m_detuning_div_samplerate * Engine::audioEngine()->processingSampleRate(); + const float current_freq = m_freq * m_detuning_div_samplerate * Engine::audioEngine()->outputSampleRate(); if (!m_useWaveTable || current_freq < OscillatorConstants::MAX_FREQ) { @@ -807,13 +811,13 @@ template<> inline sample_t Oscillator::getSample( const float _sample ) { - if (m_useWaveTable && !m_isModulator) + if (m_useWaveTable && m_userAntiAliasWaveTable && !m_isModulator) { - return wtSample(m_userWave->m_userAntiAliasWaveTable, _sample); + return wtSample(m_userAntiAliasWaveTable.get(), _sample); } else { - return userWaveSample(_sample); + return userWaveSample(m_userWave.get(), _sample); } } diff --git a/src/core/PatternClip.cpp b/src/core/PatternClip.cpp index 1058da6ba..15a1d1f54 100644 --- a/src/core/PatternClip.cpp +++ b/src/core/PatternClip.cpp @@ -62,9 +62,9 @@ void PatternClip::saveSettings(QDomDocument& doc, QDomElement& element) element.setAttribute( "len", length() ); element.setAttribute("off", startTimeOffset()); element.setAttribute( "muted", isMuted() ); - if( usesCustomClipColor() ) + if (const auto& c = color()) { - element.setAttribute( "color", color().name() ); + element.setAttribute("color", c->name()); } } @@ -90,20 +90,14 @@ void PatternClip::loadSettings(const QDomElement& element) if (!element.hasAttribute("usestyle")) { // for colors saved in 1.3-onwards - setColor(element.attribute("color")); - useCustomClipColor(true); + setColor(QColor{element.attribute("color")}); } - else + else if (element.attribute("usestyle").toUInt() == 0) { // for colors saved before 1.3 - setColor(QColor(element.attribute("color").toUInt())); - useCustomClipColor(element.attribute("usestyle").toUInt() == 0); + setColor(QColor{element.attribute("color").toUInt()}); } } - else - { - useCustomClipColor(false); - } } diff --git a/src/core/PeakController.cpp b/src/core/PeakController.cpp index cfcd3765c..1c38cf4cb 100644 --- a/src/core/PeakController.cpp +++ b/src/core/PeakController.cpp @@ -80,7 +80,7 @@ void PeakController::updateValueBuffer() { if( m_coeffNeedsUpdate ) { - const float ratio = 44100.0f / Engine::audioEngine()->processingSampleRate(); + const float ratio = 44100.0f / Engine::audioEngine()->outputSampleRate(); m_attackCoeff = 1.0f - powf( 2.0f, -0.3f * ( 1.0f - m_peakEffect->attackModel()->value() ) * ratio ); m_decayCoeff = 1.0f - powf( 2.0f, -0.3f * ( 1.0f - m_peakEffect->decayModel()->value() ) * ratio ); m_coeffNeedsUpdate = false; diff --git a/src/core/Plugin.cpp b/src/core/Plugin.cpp index 973914501..f165ddf75 100644 --- a/src/core/Plugin.cpp +++ b/src/core/Plugin.cpp @@ -226,8 +226,8 @@ Plugin * Plugin::instantiate(const QString& pluginName, Model * parent, } else { - InstantiationHook instantiationHook; - if ((instantiationHook = ( InstantiationHook ) pi.library->resolve( "lmms_plugin_main" ))) + auto instantiationHook = reinterpret_cast(pi.library->resolve("lmms_plugin_main")); + if (instantiationHook) { inst = instantiationHook(parent, data); if(!inst) { diff --git a/src/core/ProjectJournal.cpp b/src/core/ProjectJournal.cpp index fc77c98e6..ae17b2aa8 100644 --- a/src/core/ProjectJournal.cpp +++ b/src/core/ProjectJournal.cpp @@ -23,11 +23,13 @@ */ #include +#include #include "ProjectJournal.h" #include "Engine.h" #include "JournallingObject.h" #include "Song.h" +#include "AutomationClip.h" namespace lmms { @@ -67,6 +69,12 @@ void ProjectJournal::undo() jo->restoreState( c.data.content().firstChildElement() ); setJournalling( prev ); Engine::getSong()->setModified(); + + // loading AutomationClip connections correctly + if (!c.data.content().elementsByTagName("automationclip").isEmpty()) + { + AutomationClip::resolveAllIDs(); + } break; } } diff --git a/src/core/ProjectRenderer.cpp b/src/core/ProjectRenderer.cpp index 3f101330a..3d83515f2 100644 --- a/src/core/ProjectRenderer.cpp +++ b/src/core/ProjectRenderer.cpp @@ -159,7 +159,6 @@ void ProjectRenderer::startProcessing() void ProjectRenderer::run() { - MemoryManager::ThreadGuard mmThreadGuard; Q_UNUSED(mmThreadGuard); #if 0 #if defined(LMMS_BUILD_LINUX) || defined(LMMS_BUILD_FREEBSD) #ifdef LMMS_HAVE_SCHED_H diff --git a/src/core/RemotePlugin.cpp b/src/core/RemotePlugin.cpp index 088bc3cd8..b46c547da 100644 --- a/src/core/RemotePlugin.cpp +++ b/src/core/RemotePlugin.cpp @@ -535,7 +535,7 @@ bool RemotePlugin::processMessage( const message & _m ) case IdSampleRateInformation: reply = true; - reply_message.addInt( Engine::audioEngine()->processingSampleRate() ); + reply_message.addInt( Engine::audioEngine()->outputSampleRate() ); break; case IdBufferSizeInformation: diff --git a/src/core/RenderManager.cpp b/src/core/RenderManager.cpp index 9f6192039..d375b95ee 100644 --- a/src/core/RenderManager.cpp +++ b/src/core/RenderManager.cpp @@ -23,6 +23,7 @@ */ #include +#include #include "RenderManager.h" @@ -182,7 +183,7 @@ QString RenderManager::pathForTrack(const Track *track, int num) { QString extension = ProjectRenderer::getFileExtensionFromFormat( m_format ); QString name = track->name(); - name = name.remove(QRegExp(FILENAME_FILTER)); + name = name.remove(QRegularExpression(FILENAME_FILTER)); name = QString( "%1_%2%3" ).arg( num ).arg( name ).arg( extension ); return QDir(m_outputPath).filePath(name); } diff --git a/src/core/RingBuffer.cpp b/src/core/RingBuffer.cpp index 3f1ee7236..6cd3613ed 100644 --- a/src/core/RingBuffer.cpp +++ b/src/core/RingBuffer.cpp @@ -34,7 +34,7 @@ namespace lmms RingBuffer::RingBuffer( f_cnt_t size ) : m_fpp( Engine::audioEngine()->framesPerPeriod() ), - m_samplerate( Engine::audioEngine()->processingSampleRate() ), + m_samplerate( Engine::audioEngine()->outputSampleRate() ), m_size( size + m_fpp ) { m_buffer = new sampleFrame[ m_size ]; @@ -45,7 +45,7 @@ RingBuffer::RingBuffer( f_cnt_t size ) : RingBuffer::RingBuffer( float size ) : m_fpp( Engine::audioEngine()->framesPerPeriod() ), - m_samplerate( Engine::audioEngine()->processingSampleRate() ) + m_samplerate( Engine::audioEngine()->outputSampleRate() ) { m_size = msToFrames( size ) + m_fpp; m_buffer = new sampleFrame[ m_size ]; @@ -307,9 +307,9 @@ void RingBuffer::writeSwappedAddingMultiplied( sampleFrame * src, float offset, void RingBuffer::updateSamplerate() { - float newsize = static_cast( ( m_size - m_fpp ) * Engine::audioEngine()->processingSampleRate() ) / m_samplerate; + float newsize = static_cast( ( m_size - m_fpp ) * Engine::audioEngine()->outputSampleRate() ) / m_samplerate; m_size = static_cast( ceilf( newsize ) ) + m_fpp; - m_samplerate = Engine::audioEngine()->processingSampleRate(); + m_samplerate = Engine::audioEngine()->outputSampleRate(); delete[] m_buffer; m_buffer = new sampleFrame[ m_size ]; memset( m_buffer, 0, m_size * sizeof( sampleFrame ) ); diff --git a/src/core/Sample.cpp b/src/core/Sample.cpp new file mode 100644 index 000000000..584d1bc13 --- /dev/null +++ b/src/core/Sample.cpp @@ -0,0 +1,249 @@ +/* + * Sample.cpp - State for container-class SampleBuffer + * + * Copyright (c) 2023 saker + * + * 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 "Sample.h" + +#include + +namespace lmms { + +Sample::Sample(const QString& audioFile) + : m_buffer(std::make_shared(audioFile)) + , m_startFrame(0) + , m_endFrame(m_buffer->size()) + , m_loopStartFrame(0) + , m_loopEndFrame(m_buffer->size()) +{ +} + +Sample::Sample(const QByteArray& base64, int sampleRate) + : m_buffer(std::make_shared(base64, sampleRate)) + , m_startFrame(0) + , m_endFrame(m_buffer->size()) + , m_loopStartFrame(0) + , m_loopEndFrame(m_buffer->size()) +{ +} + +Sample::Sample(const sampleFrame* data, size_t numFrames, int sampleRate) + : m_buffer(std::make_shared(data, numFrames, sampleRate)) + , m_startFrame(0) + , m_endFrame(m_buffer->size()) + , m_loopStartFrame(0) + , m_loopEndFrame(m_buffer->size()) +{ +} + +Sample::Sample(std::shared_ptr buffer) + : m_buffer(buffer) + , m_startFrame(0) + , m_endFrame(m_buffer->size()) + , m_loopStartFrame(0) + , m_loopEndFrame(m_buffer->size()) +{ +} + +Sample::Sample(const Sample& other) + : m_buffer(other.m_buffer) + , m_startFrame(other.startFrame()) + , m_endFrame(other.endFrame()) + , m_loopStartFrame(other.loopStartFrame()) + , m_loopEndFrame(other.loopEndFrame()) + , m_amplification(other.amplification()) + , m_frequency(other.frequency()) + , m_reversed(other.reversed()) +{ +} + +Sample::Sample(Sample&& other) + : m_buffer(std::move(other.m_buffer)) + , m_startFrame(other.startFrame()) + , m_endFrame(other.endFrame()) + , m_loopStartFrame(other.loopStartFrame()) + , m_loopEndFrame(other.loopEndFrame()) + , m_amplification(other.amplification()) + , m_frequency(other.frequency()) + , m_reversed(other.reversed()) +{ +} + +auto Sample::operator=(const Sample& other) -> Sample& +{ + m_buffer = other.m_buffer; + m_startFrame = other.startFrame(); + m_endFrame = other.endFrame(); + m_loopStartFrame = other.loopStartFrame(); + m_loopEndFrame = other.loopEndFrame(); + m_amplification = other.amplification(); + m_frequency = other.frequency(); + m_reversed = other.reversed(); + + return *this; +} + +auto Sample::operator=(Sample&& other) -> Sample& +{ + m_buffer = std::move(other.m_buffer); + m_startFrame = other.startFrame(); + m_endFrame = other.endFrame(); + m_loopStartFrame = other.loopStartFrame(); + m_loopEndFrame = other.loopEndFrame(); + m_amplification = other.amplification(); + m_frequency = other.frequency(); + m_reversed = other.reversed(); + + return *this; +} + +bool Sample::play(sampleFrame* dst, PlaybackState* state, size_t numFrames, float desiredFrequency, Loop loopMode) const +{ + assert(numFrames > 0); + assert(desiredFrequency > 0); + + const auto pastBounds = state->m_frameIndex >= m_endFrame || (state->m_frameIndex < 0 && state->m_backwards); + if (loopMode == Loop::Off && pastBounds) { return false; } + + const auto outputSampleRate = Engine::audioEngine()->outputSampleRate() * m_frequency / desiredFrequency; + const auto inputSampleRate = m_buffer->sampleRate(); + const auto resampleRatio = outputSampleRate / inputSampleRate; + const auto marginSize = s_interpolationMargins[state->resampler().interpolationMode()]; + + state->m_frameIndex = std::max(m_startFrame, state->m_frameIndex); + + auto playBuffer = std::vector(numFrames / resampleRatio + marginSize); + playRaw(playBuffer.data(), playBuffer.size(), state, loopMode); + + const auto resampleResult + = state->resampler().resample(&playBuffer[0][0], playBuffer.size(), &dst[0][0], numFrames, resampleRatio); + advance(state, resampleResult.inputFramesUsed, loopMode); + + const auto outputFrames = resampleResult.outputFramesGenerated; + if (outputFrames < numFrames) { std::fill_n(dst + outputFrames, numFrames - outputFrames, sampleFrame{}); } + + if (!typeInfo::isEqual(m_amplification, 1.0f)) + { + for (int i = 0; i < numFrames; ++i) + { + dst[i][0] *= m_amplification; + dst[i][1] *= m_amplification; + } + } + + return true; +} + +auto Sample::sampleDuration() const -> std::chrono::milliseconds +{ + const auto numFrames = endFrame() - startFrame(); + const auto duration = numFrames / static_cast(m_buffer->sampleRate()) * 1000; + return std::chrono::milliseconds{static_cast(duration)}; +} + +void Sample::setAllPointFrames(int startFrame, int endFrame, int loopStartFrame, int loopEndFrame) +{ + setStartFrame(startFrame); + setEndFrame(endFrame); + setLoopStartFrame(loopStartFrame); + setLoopEndFrame(loopEndFrame); +} + +void Sample::playRaw(sampleFrame* dst, size_t numFrames, const PlaybackState* state, Loop loopMode) const +{ + if (m_buffer->size() < 1) { return; } + + auto index = state->m_frameIndex; + auto backwards = state->m_backwards; + + for (size_t i = 0; i < numFrames; ++i) + { + switch (loopMode) + { + case Loop::Off: + if (index < 0 || index >= m_endFrame) { return; } + break; + case Loop::On: + if (index < m_loopStartFrame && backwards) { index = m_loopEndFrame - 1; } + else if (index >= m_loopEndFrame) { index = m_loopStartFrame; } + break; + case Loop::PingPong: + if (index < m_loopStartFrame && backwards) + { + index = m_loopStartFrame; + backwards = false; + } + else if (index >= m_loopEndFrame) + { + index = m_loopEndFrame - 1; + backwards = true; + } + break; + default: + break; + } + + dst[i] = m_buffer->data()[m_reversed ? m_buffer->size() - index - 1 : index]; + backwards ? --index : ++index; + } +} + +void Sample::advance(PlaybackState* state, size_t advanceAmount, Loop loopMode) const +{ + state->m_frameIndex += (state->m_backwards ? -1 : 1) * advanceAmount; + if (loopMode == Loop::Off) { return; } + + const auto distanceFromLoopStart = std::abs(state->m_frameIndex - m_loopStartFrame); + const auto distanceFromLoopEnd = std::abs(state->m_frameIndex - m_loopEndFrame); + const auto loopSize = m_loopEndFrame - m_loopStartFrame; + if (loopSize == 0) { return; } + + switch (loopMode) + { + case Loop::On: + if (state->m_frameIndex < m_loopStartFrame && state->m_backwards) + { + state->m_frameIndex = m_loopEndFrame - 1 - distanceFromLoopStart % loopSize; + } + else if (state->m_frameIndex >= m_loopEndFrame) + { + state->m_frameIndex = m_loopStartFrame + distanceFromLoopEnd % loopSize; + } + break; + case Loop::PingPong: + if (state->m_frameIndex < m_loopStartFrame && state->m_backwards) + { + state->m_frameIndex = m_loopStartFrame + distanceFromLoopStart % loopSize; + state->m_backwards = false; + } + else if (state->m_frameIndex >= m_loopEndFrame) + { + state->m_frameIndex = m_loopEndFrame - 1 - distanceFromLoopEnd % loopSize; + state->m_backwards = true; + } + break; + default: + break; + } +} + +} // namespace lmms diff --git a/src/core/SampleBuffer.cpp b/src/core/SampleBuffer.cpp index f583067f8..6483dd522 100644 --- a/src/core/SampleBuffer.cpp +++ b/src/core/SampleBuffer.cpp @@ -23,1606 +23,74 @@ */ #include "SampleBuffer.h" -#include "Oscillator.h" +#include -#include - -#include -#include -#include -#include - - -#include - -#define OV_EXCLUDE_STATIC_CALLBACKS -#ifdef LMMS_HAVE_OGGVORBIS -#include -#endif - -#ifdef LMMS_HAVE_FLAC_STREAM_ENCODER_H -#include -#endif - -#ifdef LMMS_HAVE_FLAC_STREAM_DECODER_H -#include -#endif - - -#include "AudioEngine.h" -#include "base64.h" -#include "ConfigManager.h" -#include "DrumSynth.h" -#include "endian_handling.h" -#include "Engine.h" -#include "GuiApplication.h" -#include "Note.h" #include "PathUtil.h" +#include "SampleDecoder.h" +#include "lmms_basics.h" -#include "FileDialog.h" +namespace lmms { -namespace lmms +SampleBuffer::SampleBuffer(const sampleFrame* data, size_t numFrames, int sampleRate) + : m_data(data, data + numFrames) + , m_sampleRate(sampleRate) { - -SampleBuffer::SampleBuffer() : - m_userAntiAliasWaveTable(nullptr), - m_audioFile(""), - m_origData(nullptr), - m_origFrames(0), - m_data(nullptr), - m_frames(0), - m_startFrame(0), - m_endFrame(0), - m_loopStartFrame(0), - m_loopEndFrame(0), - m_amplification(1.0f), - m_reversed(false), - m_frequency(DefaultBaseFreq), - m_sampleRate(audioEngineSampleRate()) -{ - - connect(Engine::audioEngine(), SIGNAL(sampleRateChanged()), this, SLOT(sampleRateChanged())); - update(); } - - -SampleBuffer::SampleBuffer(const QString & audioFile, bool isBase64Data) - : SampleBuffer() +SampleBuffer::SampleBuffer(const QString& audioFile) { - if (isBase64Data) + if (audioFile.isEmpty()) { throw std::runtime_error{"Failure loading audio file: Audio file path is empty."}; } + const auto absolutePath = PathUtil::toAbsolute(audioFile); + + if (auto decodedResult = SampleDecoder::decode(absolutePath)) { - loadFromBase64(audioFile); - } - else - { - m_audioFile = audioFile; - update(); + auto& [data, sampleRate] = *decodedResult; + m_data = std::move(data); + m_sampleRate = sampleRate; + m_audioFile = PathUtil::toShortestRelative(audioFile); + return; } + + throw std::runtime_error{ + "Failed to decode audio file: Either the audio codec is unsupported, or the file is corrupted."}; } - - - -SampleBuffer::SampleBuffer(const sampleFrame * data, const f_cnt_t frames) - : SampleBuffer() +SampleBuffer::SampleBuffer(const QString& base64, int sampleRate) + : m_sampleRate(sampleRate) { - if (frames > 0) - { - m_origData = MM_ALLOC( frames); - memcpy(m_origData, data, frames * BYTES_PER_FRAME); - m_origFrames = frames; - update(); - } + // TODO: Replace with non-Qt equivalent + const auto bytes = QByteArray::fromBase64(base64.toUtf8()); + m_data.resize(bytes.size() / sizeof(sampleFrame)); + std::memcpy(reinterpret_cast(m_data.data()), bytes, m_data.size() * sizeof(sampleFrame)); } - - - -SampleBuffer::SampleBuffer(const f_cnt_t frames) - : SampleBuffer() +SampleBuffer::SampleBuffer(std::vector data, int sampleRate) + : m_data(std::move(data)) + , m_sampleRate(sampleRate) { - if (frames > 0) - { - m_origData = MM_ALLOC( frames); - memset(m_origData, 0, frames * BYTES_PER_FRAME); - m_origFrames = frames; - update(); - } } - - - -SampleBuffer::SampleBuffer(const SampleBuffer& orig) -{ - orig.m_varLock.lockForRead(); - - m_audioFile = orig.m_audioFile; - m_origFrames = orig.m_origFrames; - m_origData = (m_origFrames > 0) ? MM_ALLOC( m_origFrames) : nullptr; - m_frames = orig.m_frames; - m_data = (m_frames > 0) ? MM_ALLOC( m_frames) : nullptr; - m_startFrame = orig.m_startFrame; - m_endFrame = orig.m_endFrame; - m_loopStartFrame = orig.m_loopStartFrame; - m_loopEndFrame = orig.m_loopEndFrame; - m_amplification = orig.m_amplification; - m_reversed = orig.m_reversed; - m_frequency = orig.m_frequency; - m_sampleRate = orig.m_sampleRate; - - //Deep copy m_origData and m_data from original - const auto origFrameBytes = m_origFrames * BYTES_PER_FRAME; - const auto frameBytes = m_frames * BYTES_PER_FRAME; - if (orig.m_origData != nullptr && origFrameBytes > 0) - { memcpy(m_origData, orig.m_origData, origFrameBytes); } - if (orig.m_data != nullptr && frameBytes > 0) - { memcpy(m_data, orig.m_data, frameBytes); } - - orig.m_varLock.unlock(); -} - - - - void swap(SampleBuffer& first, SampleBuffer& second) noexcept { using std::swap; - - // Lock both buffers for writing, with address as lock ordering - if (&first == &second) { return; } - else if (&first > &second) - { - first.m_varLock.lockForWrite(); - second.m_varLock.lockForWrite(); - } - else - { - second.m_varLock.lockForWrite(); - first.m_varLock.lockForWrite(); - } - - first.m_audioFile.swap(second.m_audioFile); - swap(first.m_origData, second.m_origData); swap(first.m_data, second.m_data); - swap(first.m_origFrames, second.m_origFrames); - swap(first.m_frames, second.m_frames); - swap(first.m_startFrame, second.m_startFrame); - swap(first.m_endFrame, second.m_endFrame); - swap(first.m_loopStartFrame, second.m_loopStartFrame); - swap(first.m_loopEndFrame, second.m_loopEndFrame); - swap(first.m_amplification, second.m_amplification); - swap(first.m_frequency, second.m_frequency); - swap(first.m_reversed, second.m_reversed); + swap(first.m_audioFile, second.m_audioFile); swap(first.m_sampleRate, second.m_sampleRate); - - // Unlock again - first.m_varLock.unlock(); - second.m_varLock.unlock(); } - - - -SampleBuffer& SampleBuffer::operator=(SampleBuffer that) +QString SampleBuffer::toBase64() const { - swap(*this, that); - return *this; + // TODO: Replace with non-Qt equivalent + const auto data = reinterpret_cast(m_data.data()); + const auto size = static_cast(m_data.size() * sizeof(sampleFrame)); + const auto byteArray = QByteArray{data, size}; + return byteArray.toBase64(); } - - - -SampleBuffer::~SampleBuffer() +auto SampleBuffer::emptyBuffer() -> std::shared_ptr { - MM_FREE(m_origData); - MM_FREE(m_data); -} - - - -void SampleBuffer::sampleRateChanged() -{ - update(true); -} - -sample_rate_t SampleBuffer::audioEngineSampleRate() -{ - return Engine::audioEngine()->processingSampleRate(); -} - - -void SampleBuffer::update(bool keepSettings) -{ - const bool lock = (m_data != nullptr); - if (lock) - { - Engine::audioEngine()->requestChangeInModel(); - m_varLock.lockForWrite(); - MM_FREE(m_data); - } - - // File size and sample length limits - const int fileSizeMax = 300; // MB - const int sampleLengthMax = 90; // Minutes - - enum class FileLoadError - { - None, - ReadPermissionDenied, - TooLarge, - Invalid - }; - FileLoadError fileLoadError = FileLoadError::None; - - if (m_audioFile.isEmpty() && m_origData != nullptr && m_origFrames > 0) - { - // TODO: reverse- and amplification-property is not covered - // by following code... - m_data = MM_ALLOC( m_origFrames); - memcpy(m_data, m_origData, m_origFrames * BYTES_PER_FRAME); - if (keepSettings == false) - { - m_frames = m_origFrames; - m_loopStartFrame = m_startFrame = 0; - m_loopEndFrame = m_endFrame = m_frames; - } - } - else if (!m_audioFile.isEmpty()) - { - QString file = PathUtil::toAbsolute(m_audioFile); - int_sample_t * buf = nullptr; - sample_t * fbuf = nullptr; - ch_cnt_t channels = DEFAULT_CHANNELS; - sample_rate_t samplerate = audioEngineSampleRate(); - m_frames = 0; - - const QFileInfo fileInfo(file); - if (!fileInfo.isReadable()) - { - fileLoadError = FileLoadError::ReadPermissionDenied; - } - else if (fileInfo.size() > fileSizeMax * 1024 * 1024) - { - fileLoadError = FileLoadError::TooLarge; - } - else - { - // Use QFile to handle unicode file names on Windows - QFile f(file); - SNDFILE * sndFile = nullptr; - SF_INFO sfInfo; - sfInfo.format = 0; - - if (f.open(QIODevice::ReadOnly) && (sndFile = sf_open_fd(f.handle(), SFM_READ, &sfInfo, false))) - { - f_cnt_t frames = sfInfo.frames; - int rate = sfInfo.samplerate; - if (frames / rate > sampleLengthMax * 60) - { - fileLoadError = FileLoadError::TooLarge; - } - sf_close(sndFile); - } - f.close(); - } - - if (fileLoadError == FileLoadError::None) - { -#ifdef LMMS_HAVE_OGGVORBIS - // workaround for a bug in libsndfile or our libsndfile decoder - // causing some OGG files to be distorted -> try with OGG Vorbis - // decoder first if filename extension matches "ogg" - if (m_frames == 0 && fileInfo.suffix() == "ogg") - { - m_frames = decodeSampleOGGVorbis(file, buf, channels, samplerate); - } -#endif - if (m_frames == 0) - { - m_frames = decodeSampleSF(file, fbuf, channels, samplerate); - } -#ifdef LMMS_HAVE_OGGVORBIS - if (m_frames == 0) - { - m_frames = decodeSampleOGGVorbis(file, buf, channels, samplerate); - } -#endif - if (m_frames == 0) - { - 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 - { - // sample couldn't be decoded, create buffer containing - // one sample-frame - m_data = MM_ALLOC( 1); - memset(m_data, 0, sizeof(*m_data)); - m_frames = 1; - m_loopStartFrame = m_startFrame = 0; - m_loopEndFrame = m_endFrame = 1; - } - else // otherwise normalize sample rate - { - normalizeSampleRate(samplerate, keepSettings); - } - } - else - { - // neither an audio-file nor a buffer to copy from, so create - // buffer containing one sample-frame - m_data = MM_ALLOC( 1); - memset(m_data, 0, sizeof(*m_data)); - m_frames = 1; - m_loopStartFrame = m_startFrame = 0; - m_loopEndFrame = m_endFrame = 1; - } - - if (lock) - { - m_varLock.unlock(); - Engine::audioEngine()->doneChangeInModel(); - } - - emit sampleUpdated(); - - // allocate space for anti-aliased wave table - if (m_userAntiAliasWaveTable == nullptr) - { - m_userAntiAliasWaveTable = std::make_unique(); - } - Oscillator::generateAntiAliasUserWaveTable(this); - - if (fileLoadError != FileLoadError::None) - { - QString title = tr("Fail to open file"); - QString message; - - switch (fileLoadError) - { - case FileLoadError::None: - // present just to avoid a compiler warning - break; - - case FileLoadError::ReadPermissionDenied: - message = tr("Read permission denied"); - break; - - case FileLoadError::TooLarge: - message = tr("Audio files are limited to %1 MB " - "in size and %2 minutes of playing time" - ).arg(fileSizeMax).arg(sampleLengthMax); - break; - - case FileLoadError::Invalid: - message = tr("Invalid audio file"); - break; - } - - if (gui::getGUI() != nullptr) - { - QMessageBox::information(nullptr, title, message, QMessageBox::Ok); - } - else - { - fprintf(stderr, "%s\n", message.toUtf8().constData()); - } - } -} - - -void SampleBuffer::convertIntToFloat( - int_sample_t * & ibuf, - f_cnt_t frames, - int channels -) -{ - // following code transforms int-samples into float-samples and does amplifying & reversing - const float fac = 1 / OUTPUT_SAMPLE_MULTIPLIER; - m_data = MM_ALLOC( frames); - const int ch = (channels > 1) ? 1 : 0; - - // if reversing is on, we also reverse when scaling - bool isReversed = m_reversed; - int idx = isReversed ? (frames - 1) * channels : 0; - for (f_cnt_t frame = 0; frame < frames; ++frame) - { - m_data[frame][0] = ibuf[idx+0] * fac; - m_data[frame][1] = ibuf[idx+ch] * fac; - idx += isReversed ? -channels : channels; - } - - delete[] ibuf; -} - -void SampleBuffer::directFloatWrite( - sample_t * & fbuf, - f_cnt_t frames, - int channels -) -{ - - m_data = MM_ALLOC( frames); - const int ch = (channels > 1) ? 1 : 0; - - // if reversing is on, we also reverse when scaling - bool isReversed = m_reversed; - int idx = isReversed ? (frames - 1) * channels : 0; - for (f_cnt_t frame = 0; frame < frames; ++frame) - { - m_data[frame][0] = fbuf[idx+0]; - m_data[frame][1] = fbuf[idx+ch]; - idx += isReversed ? -channels : channels; - } - - delete[] fbuf; -} - - -void SampleBuffer::normalizeSampleRate(const sample_rate_t srcSR, bool keepSettings) -{ - const sample_rate_t oldRate = m_sampleRate; - // do samplerate-conversion to our default-samplerate - if (srcSR != audioEngineSampleRate()) - { - SampleBuffer * resampled = resample(srcSR, audioEngineSampleRate()); - - m_sampleRate = audioEngineSampleRate(); - MM_FREE(m_data); - m_frames = resampled->frames(); - m_data = MM_ALLOC( m_frames); - memcpy(m_data, resampled->data(), m_frames * sizeof(sampleFrame)); - delete resampled; - } - - if (keepSettings == false) - { - // update frame-variables - m_loopStartFrame = m_startFrame = 0; - m_loopEndFrame = m_endFrame = m_frames; - } - else if (oldRate != audioEngineSampleRate()) - { - auto oldRateToNewRateRatio = static_cast(audioEngineSampleRate()) / oldRate; - - m_startFrame = std::clamp(f_cnt_t(m_startFrame * oldRateToNewRateRatio), 0, m_frames); - m_endFrame = std::clamp(f_cnt_t(m_endFrame * oldRateToNewRateRatio), m_startFrame, m_frames); - m_loopStartFrame = std::clamp(f_cnt_t(m_loopStartFrame * oldRateToNewRateRatio), 0, m_frames); - m_loopEndFrame = std::clamp(f_cnt_t(m_loopEndFrame * oldRateToNewRateRatio), m_loopStartFrame, m_frames); - m_sampleRate = audioEngineSampleRate(); - } -} - - - - -f_cnt_t SampleBuffer::decodeSampleSF( - QString fileName, - sample_t * & buf, - ch_cnt_t & channels, - sample_rate_t & samplerate -) -{ - SNDFILE * sndFile; - SF_INFO sfInfo; - sfInfo.format = 0; - f_cnt_t frames = 0; - sf_count_t sfFramesRead; - - - // Use QFile to handle unicode file names on Windows - QFile f(fileName); - if (f.open(QIODevice::ReadOnly) && (sndFile = sf_open_fd(f.handle(), SFM_READ, &sfInfo, false))) - { - frames = sfInfo.frames; - - buf = new sample_t[sfInfo.channels * frames]; - sfFramesRead = sf_read_float(sndFile, buf, sfInfo.channels * frames); - - if (sfFramesRead < sfInfo.channels * frames) - { -#ifdef DEBUG_LMMS - qDebug("SampleBuffer::decodeSampleSF(): could not read" - " sample %s: %s", fileName, sf_strerror(nullptr)); -#endif - } - channels = sfInfo.channels; - samplerate = sfInfo.samplerate; - - sf_close(sndFile); - } - else - { -#ifdef DEBUG_LMMS - qDebug("SampleBuffer::decodeSampleSF(): could not load " - "sample %s: %s", fileName, sf_strerror(nullptr)); -#endif - } - f.close(); - - //write down either directly or convert i->f depending on file type - - if (frames > 0 && buf != nullptr) - { - directFloatWrite(buf, frames, channels); - } - - return frames; -} - - - - -#ifdef LMMS_HAVE_OGGVORBIS - -// callback-functions for reading ogg-file - -size_t qfileReadCallback(void * ptr, size_t size, size_t n, void * udata ) -{ - return static_cast(udata)->read((char*) ptr, size * n); -} - - - - -int qfileSeekCallback(void * udata, ogg_int64_t offset, int whence) -{ - auto f = static_cast(udata); - - if (whence == SEEK_CUR) - { - f->seek(f->pos() + offset); - } - else if (whence == SEEK_END) - { - f->seek(f->size() + offset); - } - else - { - f->seek(offset); - } - return 0; -} - - - - -int qfileCloseCallback(void * udata) -{ - delete static_cast(udata); - return 0; -} - - - - -long qfileTellCallback(void * udata) -{ - return static_cast(udata)->pos(); -} - - - - -f_cnt_t SampleBuffer::decodeSampleOGGVorbis( - QString fileName, - int_sample_t * & buf, - ch_cnt_t & channels, - sample_rate_t & samplerate -) -{ - static ov_callbacks callbacks = - { - qfileReadCallback, - qfileSeekCallback, - qfileCloseCallback, - qfileTellCallback - } ; - - OggVorbis_File vf; - - f_cnt_t frames = 0; - - auto f = new QFile(fileName); - if (f->open(QFile::ReadOnly) == false) - { - delete f; - return 0; - } - - int err = ov_open_callbacks(f, &vf, nullptr, 0, callbacks); - - if (err < 0) - { - switch (err) - { - case OV_EREAD: - printf("SampleBuffer::decodeSampleOGGVorbis():" - " media read error\n"); - break; - case OV_ENOTVORBIS: - printf("SampleBuffer::decodeSampleOGGVorbis():" - " not an Ogg Vorbis file\n"); - break; - case OV_EVERSION: - printf("SampleBuffer::decodeSampleOGGVorbis():" - " vorbis version mismatch\n"); - break; - case OV_EBADHEADER: - printf("SampleBuffer::decodeSampleOGGVorbis():" - " invalid Vorbis bitstream header\n"); - break; - case OV_EFAULT: - printf("SampleBuffer::decodeSampleOgg(): " - "internal logic fault\n"); - break; - } - delete f; - return 0; - } - - ov_pcm_seek(&vf, 0); - - channels = ov_info(&vf, -1)->channels; - samplerate = ov_info(&vf, -1)->rate; - - ogg_int64_t total = ov_pcm_total(&vf, -1); - - buf = new int_sample_t[total * channels]; - int bitstream = 0; - long bytesRead = 0; - - do - { - bytesRead = ov_read(&vf, - (char *) &buf[frames * channels], - (total - frames) * channels * BYTES_PER_INT_SAMPLE, - isLittleEndian() ? 0 : 1, - BYTES_PER_INT_SAMPLE, - 1, - &bitstream - ); - - if (bytesRead < 0) - { - break; - } - frames += bytesRead / (channels * BYTES_PER_INT_SAMPLE); - } - while (bytesRead != 0 && bitstream == 0); - - ov_clear(&vf); - - // if buffer isn't empty, convert it to float and write it down - if (frames > 0 && buf != nullptr) - { - convertIntToFloat(buf, frames, channels); - } - - return frames; -} -#endif // LMMS_HAVE_OGGVORBIS - - - - -f_cnt_t SampleBuffer::decodeSampleDS( - QString fileName, - int_sample_t * & buf, - ch_cnt_t & channels, - sample_rate_t & samplerate -) -{ - DrumSynth ds; - f_cnt_t frames = ds.GetDSFileSamples(fileName, buf, channels, samplerate); - - if (frames > 0 && buf != nullptr) - { - convertIntToFloat(buf, frames, channels); - } - - return frames; - -} - - - - -bool SampleBuffer::play( - sampleFrame * ab, - handleState * state, - const fpp_t frames, - const float freq, - const LoopMode loopMode -) -{ - f_cnt_t startFrame = m_startFrame; - f_cnt_t endFrame = m_endFrame; - f_cnt_t loopStartFrame = m_loopStartFrame; - f_cnt_t loopEndFrame = m_loopEndFrame; - - if (endFrame == 0 || frames == 0) - { - return false; - } - - // variable for determining if we should currently be playing backwards in a ping-pong loop - bool isBackwards = state->isBackwards(); - - // The SampleBuffer can play a given sample with increased or decreased pitch. However, only - // samples that contain a tone that matches the default base note frequency of 440 Hz will - // produce the exact requested pitch in [Hz]. - const double freqFactor = (double) freq / (double) m_frequency * - m_sampleRate / Engine::audioEngine()->processingSampleRate(); - - // calculate how many frames we have in requested pitch - const auto totalFramesForCurrentPitch = static_cast((endFrame - startFrame) / freqFactor); - - if (totalFramesForCurrentPitch == 0) - { - return false; - } - - - // this holds the index of the first frame to play - f_cnt_t playFrame = std::max(state->m_frameIndex, startFrame); - - if (loopMode == LoopMode::Off) - { - if (playFrame >= endFrame || (endFrame - playFrame) / freqFactor == 0) - { - // the sample is done being played - return false; - } - } - else if (loopMode == LoopMode::On) - { - playFrame = getLoopedIndex(playFrame, loopStartFrame, loopEndFrame); - } - else - { - playFrame = getPingPongIndex(playFrame, loopStartFrame, loopEndFrame); - } - - f_cnt_t fragmentSize = (f_cnt_t)(frames * freqFactor) + MARGIN[state->interpolationMode()]; - - sampleFrame * tmp = nullptr; - - // check whether we have to change pitch... - if (freqFactor != 1.0 || state->m_varyingPitch) - { - SRC_DATA srcData; - // Generate output - srcData.data_in = - getSampleFragment(playFrame, fragmentSize, loopMode, &tmp, &isBackwards, - loopStartFrame, loopEndFrame, endFrame )->data(); - srcData.data_out = ab->data(); - srcData.input_frames = fragmentSize; - srcData.output_frames = frames; - srcData.src_ratio = 1.0 / freqFactor; - srcData.end_of_input = 0; - int error = src_process(state->m_resamplingData, &srcData); - if (error) - { - printf("SampleBuffer: error while resampling: %s\n", - src_strerror(error)); - } - if (srcData.output_frames_gen > frames) - { - printf("SampleBuffer: not enough frames: %ld / %d\n", - srcData.output_frames_gen, frames); - } - // Advance - switch (loopMode) - { - case LoopMode::Off: - playFrame += srcData.input_frames_used; - break; - case LoopMode::On: - playFrame += srcData.input_frames_used; - playFrame = getLoopedIndex(playFrame, loopStartFrame, loopEndFrame); - break; - case LoopMode::PingPong: - { - f_cnt_t left = srcData.input_frames_used; - if (state->isBackwards()) - { - playFrame -= srcData.input_frames_used; - if (playFrame < loopStartFrame) - { - left -= (loopStartFrame - playFrame); - playFrame = loopStartFrame; - } - else left = 0; - } - playFrame += left; - playFrame = getPingPongIndex(playFrame, loopStartFrame, loopEndFrame); - break; - } - } - } - else - { - // we don't have to pitch, so we just copy the sample-data - // as is into pitched-copy-buffer - - // Generate output - memcpy(ab, - getSampleFragment(playFrame, frames, loopMode, &tmp, &isBackwards, - loopStartFrame, loopEndFrame, endFrame), - frames * BYTES_PER_FRAME); - // Advance - switch (loopMode) - { - case LoopMode::Off: - playFrame += frames; - break; - case LoopMode::On: - playFrame += frames; - playFrame = getLoopedIndex(playFrame, loopStartFrame, loopEndFrame); - break; - case LoopMode::PingPong: - { - f_cnt_t left = frames; - if (state->isBackwards()) - { - playFrame -= frames; - if (playFrame < loopStartFrame) - { - left -= (loopStartFrame - playFrame); - playFrame = loopStartFrame; - } - else left = 0; - } - playFrame += left; - playFrame = getPingPongIndex(playFrame, loopStartFrame, loopEndFrame); - break; - } - } - } - - if (tmp != nullptr) - { - MM_FREE(tmp); - } - - state->setBackwards(isBackwards); - state->setFrameIndex(playFrame); - - for (fpp_t i = 0; i < frames; ++i) - { - ab[i][0] *= m_amplification; - ab[i][1] *= m_amplification; - } - - return true; -} - - - - -sampleFrame * SampleBuffer::getSampleFragment( - f_cnt_t index, - f_cnt_t frames, - LoopMode loopMode, - sampleFrame * * tmp, - bool * backwards, - f_cnt_t loopStart, - f_cnt_t loopEnd, - f_cnt_t end -) const -{ - if (loopMode == LoopMode::Off) - { - if (index + frames <= end) - { - return m_data + index; - } - } - else if (loopMode == LoopMode::On) - { - if (index + frames <= loopEnd) - { - return m_data + index; - } - } - else - { - if (!*backwards && index + frames < loopEnd) - { - return m_data + index; - } - } - - *tmp = MM_ALLOC( frames); - - if (loopMode == LoopMode::Off) - { - f_cnt_t available = end - index; - memcpy(*tmp, m_data + index, available * BYTES_PER_FRAME); - memset(*tmp + available, 0, (frames - available) * BYTES_PER_FRAME); - } - else if (loopMode == LoopMode::On) - { - f_cnt_t copied = std::min(frames, loopEnd - index); - memcpy(*tmp, m_data + index, copied * BYTES_PER_FRAME); - f_cnt_t loopFrames = loopEnd - loopStart; - while (copied < frames) - { - f_cnt_t todo = std::min(frames - copied, loopFrames); - memcpy(*tmp + copied, m_data + loopStart, todo * BYTES_PER_FRAME); - copied += todo; - } - } - else - { - f_cnt_t pos = index; - bool currentBackwards = pos < loopStart - ? false - : *backwards; - f_cnt_t copied = 0; - - - if (currentBackwards) - { - copied = std::min(frames, pos - loopStart); - for (int i = 0; i < copied; i++) - { - (*tmp)[i][0] = m_data[pos - i][0]; - (*tmp)[i][1] = m_data[pos - i][1]; - } - pos -= copied; - if (pos == loopStart) { currentBackwards = false; } - } - else - { - copied = std::min(frames, loopEnd - pos); - memcpy(*tmp, m_data + pos, copied * BYTES_PER_FRAME); - pos += copied; - if (pos == loopEnd) { currentBackwards = true; } - } - - while (copied < frames) - { - if (currentBackwards) - { - f_cnt_t todo = std::min(frames - copied, pos - loopStart); - for (int i = 0; i < todo; i++) - { - (*tmp)[copied + i][0] = m_data[pos - i][0]; - (*tmp)[copied + i][1] = m_data[pos - i][1]; - } - pos -= todo; - copied += todo; - if (pos <= loopStart) { currentBackwards = false; } - } - else - { - f_cnt_t todo = std::min(frames - copied, loopEnd - pos); - memcpy(*tmp + copied, m_data + pos, todo * BYTES_PER_FRAME); - pos += todo; - copied += todo; - if (pos >= loopEnd) { currentBackwards = true; } - } - } - *backwards = currentBackwards; - } - - return *tmp; -} - - - - -f_cnt_t SampleBuffer::getLoopedIndex(f_cnt_t index, f_cnt_t startf, f_cnt_t endf) const -{ - if (index < endf) - { - return index; - } - return startf + (index - startf) % (endf - startf); -} - - -f_cnt_t SampleBuffer::getPingPongIndex(f_cnt_t index, f_cnt_t startf, f_cnt_t endf) const -{ - if (index < endf) - { - return index; - } - const f_cnt_t loopLen = endf - startf; - const f_cnt_t loopPos = (index - endf) % (loopLen * 2); - - return (loopPos < loopLen) - ? endf - loopPos - : startf + (loopPos - loopLen); -} - - -/* @brief Draws a sample buffer on the QRect given in the range [fromFrame, toFrame) - * @param QPainter p: Painter object for the painting operations - * @param QRect dr: QRect where the buffer will be drawn in - * @param QRect clip: QRect used for clipping - * @param f_cnt_t fromFrame: First frame of the range - * @param f_cnt_t toFrame: Last frame of the range non-inclusive - */ -void SampleBuffer::visualize( - QPainter & p, - const QRect & dr, - const QRect & clip, - f_cnt_t fromFrame, - f_cnt_t toFrame -) -{ - if (m_frames == 0) { return; } - - const bool focusOnRange = toFrame <= m_frames && 0 <= fromFrame && fromFrame < toFrame; - //TODO: If the clip QRect is not being used we should remove it - //p.setClipRect(clip); - const int w = dr.width(); - const int h = dr.height(); - - const int yb = h / 2 + dr.y(); - const float ySpace = h * 0.5f; - const int nbFrames = focusOnRange ? toFrame - fromFrame : m_frames; - - const double fpp = std::max(1., static_cast(nbFrames) / w); - // There are 2 possibilities: Either nbFrames is bigger than - // the width, so we will have width points, or nbFrames is - // smaller than the width (fpp = 1) and we will have nbFrames - // points - const int totalPoints = nbFrames > w - ? w - : nbFrames; - std::vector fEdgeMax(totalPoints); - std::vector fEdgeMin(totalPoints); - std::vector fRmsMax(totalPoints); - std::vector fRmsMin(totalPoints); - int curPixel = 0; - const int xb = dr.x(); - const int first = focusOnRange ? fromFrame : 0; - const int last = focusOnRange ? toFrame - 1 : m_frames - 1; - // When the number of frames isn't perfectly divisible by the - // width, the remaining frames don't fit the last pixel and are - // past the visible area. lastVisibleFrame is the index number of - // the last visible frame. - const int visibleFrames = (fpp * w); - const int lastVisibleFrame = focusOnRange - ? fromFrame + visibleFrames - 1 - : visibleFrames - 1; - - for (double frame = first; frame <= last && frame <= lastVisibleFrame; frame += fpp) - { - float maxData = -1; - float minData = 1; - - auto rmsData = std::array{}; - - // Find maximum and minimum samples within range - for (int i = 0; i < fpp && frame + i <= last; ++i) - { - for (int j = 0; j < 2; ++j) - { - auto curData = m_data[static_cast(frame) + i][j]; - - if (curData > maxData) { maxData = curData; } - if (curData < minData) { minData = curData; } - - rmsData[j] += curData * curData; - } - } - - const float trueRmsData = (rmsData[0] + rmsData[1]) / 2 / fpp; - const float sqrtRmsData = sqrt(trueRmsData); - const float maxRmsData = std::clamp(sqrtRmsData, minData, maxData); - const float minRmsData = std::clamp(-sqrtRmsData, minData, maxData); - - // If nbFrames >= w, we can use curPixel to calculate X - // but if nbFrames < w, we need to calculate it proportionally - // to the total number of points - auto x = nbFrames >= w - ? xb + curPixel - : xb + ((static_cast(curPixel) / nbFrames) * w); - // Partial Y calculation - auto py = ySpace * m_amplification; - fEdgeMax[curPixel] = QPointF(x, (yb - (maxData * py))); - fEdgeMin[curPixel] = QPointF(x, (yb - (minData * py))); - fRmsMax[curPixel] = QPointF(x, (yb - (maxRmsData * py))); - fRmsMin[curPixel] = QPointF(x, (yb - (minRmsData * py))); - ++curPixel; - } - - for (int i = 0; i < totalPoints; ++i) - { - p.drawLine(fEdgeMax[i], fEdgeMin[i]); - } - - p.setPen(p.pen().color().lighter(123)); - - for (int i = 0; i < totalPoints; ++i) - { - p.drawLine(fRmsMax[i], fRmsMin[i]); - } -} - - - - -QString SampleBuffer::openAudioFile() const -{ - gui::FileDialog ofd(nullptr, tr("Open audio file")); - - QString dir; - if (!m_audioFile.isEmpty()) - { - QString f = m_audioFile; - if (QFileInfo(f).isRelative()) - { - f = ConfigManager::inst()->userSamplesDir() + f; - if (QFileInfo(f).exists() == false) - { - f = ConfigManager::inst()->factorySamplesDir() + - m_audioFile; - } - } - dir = QFileInfo(f).absolutePath(); - } - else - { - dir = ConfigManager::inst()->userSamplesDir(); - } - // change dir to position of previously opened file - ofd.setDirectory(dir); - ofd.setFileMode(gui::FileDialog::ExistingFiles); - - // set filters - QStringList types; - types << tr("All Audio-Files (*.wav *.ogg *.ds *.flac *.spx *.voc " - "*.aif *.aiff *.au *.raw)") - << tr("Wave-Files (*.wav)") - << tr("OGG-Files (*.ogg)") - << 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)") - << tr("AU-Files (*.au)") - << tr("RAW-Files (*.raw)") - //<< tr("MOD-Files (*.mod)") - ; - ofd.setNameFilters(types); - if (!m_audioFile.isEmpty()) - { - // select previously opened file - ofd.selectFile(QFileInfo(m_audioFile).fileName()); - } - - if (ofd.exec () == QDialog::Accepted) - { - if (ofd.selectedFiles().isEmpty()) - { - return QString(); - } - return PathUtil::toShortestRelative(ofd.selectedFiles()[0]); - } - - return QString(); -} - - - - -QString SampleBuffer::openAndSetAudioFile() -{ - QString fileName = this->openAudioFile(); - - if(!fileName.isEmpty()) - { - this->setAudioFile(fileName); - } - - return fileName; -} - - -QString SampleBuffer::openAndSetWaveformFile() -{ - if (m_audioFile.isEmpty()) - { - m_audioFile = ConfigManager::inst()->factorySamplesDir() + "waveforms/10saw.flac"; - } - - QString fileName = this->openAudioFile(); - - if (!fileName.isEmpty()) - { - this->setAudioFile(fileName); - } - else - { - m_audioFile = ""; - } - - return fileName; -} - - - -#undef LMMS_HAVE_FLAC_STREAM_ENCODER_H /* not yet... */ -#undef LMMS_HAVE_FLAC_STREAM_DECODER_H - -#ifdef LMMS_HAVE_FLAC_STREAM_ENCODER_H -FLAC__StreamEncoderWriteStatus flacStreamEncoderWriteCallback( - const FLAC__StreamEncoder * /*encoder*/, - const FLAC__byte buffer[], - unsigned int /*samples*/, - unsigned int bytes, - unsigned int /*currentFrame*/, - void * clientData -) -{ -/* if (bytes == 0) - { - return FLAC__STREAM_ENCODER_WRITE_STATUS_OK; - }*/ - return (static_cast(clientData)->write( - (const char *) buffer, bytes) == (int) bytes) - ? FLAC__STREAM_ENCODER_WRITE_STATUS_OK - : FLAC__STREAM_ENCODER_WRITE_STATUS_FATAL_ERROR; -} - - -void flacStreamEncoderMetadataCallback( - const FLAC__StreamEncoder *, - const FLAC__StreamMetadata * metadata, - void * clientData -) -{ - QBuffer * b = static_cast(clientData); - b->seek(0); - b->write((const char *) metadata, sizeof(*metadata)); -} - -#endif // LMMS_HAVE_FLAC_STREAM_ENCODER_H - - - -QString & SampleBuffer::toBase64(QString & dst) const -{ -#ifdef LMMS_HAVE_FLAC_STREAM_ENCODER_H - const f_cnt_t FRAMES_PER_BUF = 1152; - - FLAC__StreamEncoder * flacEnc = FLAC__stream_encoder_new(); - FLAC__stream_encoder_set_channels(flacEnc, DEFAULT_CHANNELS); - FLAC__stream_encoder_set_blocksize(flacEnc, FRAMES_PER_BUF); -/* FLAC__stream_encoder_set_do_exhaustive_model_search(flacEnc, true); - FLAC__stream_encoder_set_do_mid_side_stereo(flacEnc, true);*/ - FLAC__stream_encoder_set_sample_rate(flacEnc, - Engine::audioEngine()->sampleRate()); - - QBuffer baWriter; - baWriter.open(QBuffer::WriteOnly); - - FLAC__stream_encoder_set_write_callback(flacEnc, - flacStreamEncoderWriteCallback); - FLAC__stream_encoder_set_metadata_callback(flacEnc, - flacStreamEncoderMetadataCallback); - FLAC__stream_encoder_set_client_data(flacEnc, &baWriter); - - if (FLAC__stream_encoder_init(flacEnc) != FLAC__STREAM_ENCODER_OK) - { - printf("Error within FLAC__stream_encoder_init()!\n"); - } - - f_cnt_t frameCnt = 0; - - while (frameCnt < m_frames) - { - f_cnt_t remaining = std::min(FRAMES_PER_BUF, m_frames - frameCnt); - FLAC__int32 buf[FRAMES_PER_BUF * DEFAULT_CHANNELS]; - for (f_cnt_t f = 0; f < remaining; ++f) - { - for (ch_cnt_t ch = 0; ch < DEFAULT_CHANNELS; ++ch) - { - buf[f*DEFAULT_CHANNELS+ch] = (FLAC__int32)( - AudioEngine::clip(m_data[f+frameCnt][ch]) * - OUTPUT_SAMPLE_MULTIPLIER); - } - } - FLAC__stream_encoder_process_interleaved(flacEnc, buf, remaining); - frameCnt += remaining; - } - FLAC__stream_encoder_finish(flacEnc); - FLAC__stream_encoder_delete(flacEnc); - printf("%d %d\n", frameCnt, (int)baWriter.size()); - baWriter.close(); - - base64::encode(baWriter.buffer().data(), baWriter.buffer().size(), dst); - -#else // LMMS_HAVE_FLAC_STREAM_ENCODER_H - - base64::encode((const char *) m_data, - m_frames * sizeof(sampleFrame), dst); - -#endif // LMMS_HAVE_FLAC_STREAM_ENCODER_H - - return dst; -} - - - - -SampleBuffer * SampleBuffer::resample(const sample_rate_t srcSR, const sample_rate_t dstSR ) -{ - sampleFrame * data = m_data; - const f_cnt_t frames = m_frames; - const auto dstFrames = static_cast((frames / (float)srcSR) * (float)dstSR); - auto dstSB = new SampleBuffer(dstFrames); - sampleFrame * dstBuf = dstSB->m_origData; - - // yeah, libsamplerate, let's rock with sinc-interpolation! - int error; - SRC_STATE * state; - if ((state = src_new(SRC_SINC_MEDIUM_QUALITY, DEFAULT_CHANNELS, &error)) != nullptr) - { - SRC_DATA srcData; - srcData.end_of_input = 1; - srcData.data_in = data->data(); - srcData.data_out = dstBuf->data(); - srcData.input_frames = frames; - srcData.output_frames = dstFrames; - srcData.src_ratio = (double) dstSR / srcSR; - if ((error = src_process(state, &srcData))) - { - printf("SampleBuffer: error while resampling: %s\n", src_strerror(error)); - } - src_delete(state); - } - else - { - printf("Error: src_new() failed in SampleBuffer.cpp!\n"); - } - dstSB->update(); - return dstSB; -} - - - - -void SampleBuffer::setAudioFile(const QString & audioFile) -{ - m_audioFile = PathUtil::toShortestRelative(audioFile); - update(); -} - - - -#ifdef LMMS_HAVE_FLAC_STREAM_DECODER_H - -struct flacStreamDecoderClientData -{ - QBuffer * readBuffer; - QBuffer * writeBuffer; -} ; - - - -FLAC__StreamDecoderReadStatus flacStreamDecoderReadCallback( - const FLAC__StreamDecoder * /*decoder*/, - FLAC__byte * buffer, - unsigned int * bytes, - void * clientData -) -{ - int res = static_cast( - clientData)->readBuffer->read((char *) buffer, *bytes); - - if (res > 0) - { - *bytes = res; - return FLAC__STREAM_DECODER_READ_STATUS_CONTINUE; - } - - *bytes = 0; - return FLAC__STREAM_DECODER_READ_STATUS_END_OF_STREAM; -} - - - - -FLAC__StreamDecoderWriteStatus flacStreamDecoderWriteCallback( - const FLAC__StreamDecoder * /*decoder*/, - const FLAC__Frame * frame, - const FLAC__int32 * const buffer[], - void * clientData -) -{ - if (frame->header.channels != 2) - { - printf("channels != 2 in flacStreamDecoderWriteCallback()\n"); - return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT; - } - - if (frame->header.bits_per_sample != 16) - { - printf("bits_per_sample != 16 in flacStreamDecoderWriteCallback()\n"); - return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT; - } - - const f_cnt_t numberOfFrames = frame->header.blocksize; - for (f_cnt_t f = 0; f < numberOfFrames; ++f) - { - sampleFrame sframe = { buffer[0][f] / OUTPUT_SAMPLE_MULTIPLIER, - buffer[1][f] / OUTPUT_SAMPLE_MULTIPLIER - } ; - static_cast( - clientData )->writeBuffer->write( - (const char *) sframe, sizeof(sframe)); - } - return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE; -} - - -void flacStreamDecoderMetadataCallback( - const FLAC__StreamDecoder *, - const FLAC__StreamMetadata *, - void * /*clientData*/ -) -{ - printf("stream decoder metadata callback\n"); -/* QBuffer * b = static_cast(clientData); - b->seek(0); - b->write((const char *) metadata, sizeof(*metadata));*/ -} - - -void flacStreamDecoderErrorCallback( - const FLAC__StreamDecoder *, - FLAC__StreamDecoderErrorStatus status, - void * /*clientData*/ -) -{ - printf("error callback! %d\n", status); - // what to do now?? -} - -#endif // LMMS_HAVE_FLAC_STREAM_DECODER_H - - -void SampleBuffer::loadFromBase64(const QString & data) -{ - char * dst = nullptr; - int dsize = 0; - base64::decode(data, &dst, &dsize); - -#ifdef LMMS_HAVE_FLAC_STREAM_DECODER_H - - QByteArray origData = QByteArray::fromRawData(dst, dsize); - QBuffer baReader(&origData); - baReader.open(QBuffer::ReadOnly); - - QBuffer baWriter; - baWriter.open(QBuffer::WriteOnly); - - flacStreamDecoderClientData cdata = { &baReader, &baWriter } ; - - FLAC__StreamDecoder * flacDec = FLAC__stream_decoder_new(); - - FLAC__stream_decoder_set_read_callback(flacDec, - flacStreamDecoderReadCallback); - FLAC__stream_decoder_set_write_callback(flacDec, - flacStreamDecoderWriteCallback); - FLAC__stream_decoder_set_error_callback(flacDec, - flacStreamDecoderErrorCallback); - FLAC__stream_decoder_set_metadata_callback(flacDec, - flacStreamDecoderMetadataCallback); - FLAC__stream_decoder_set_client_data(flacDec, &cdata); - - FLAC__stream_decoder_init(flacDec); - - FLAC__stream_decoder_process_until_end_of_stream(flacDec); - - FLAC__stream_decoder_finish(flacDec); - FLAC__stream_decoder_delete(flacDec); - - baReader.close(); - - origData = baWriter.buffer(); - printf("%d\n", (int) origData.size()); - - m_origFrames = origData.size() / sizeof(sampleFrame); - MM_FREE(m_origData); - m_origData = MM_ALLOC( m_origFrames); - memcpy(m_origData, origData.data(), origData.size()); - -#else /* LMMS_HAVE_FLAC_STREAM_DECODER_H */ - - m_origFrames = dsize / sizeof(sampleFrame); - MM_FREE(m_origData); - m_origData = MM_ALLOC( m_origFrames); - memcpy(m_origData, dst, dsize); - -#endif // LMMS_HAVE_FLAC_STREAM_DECODER_H - - delete[] dst; - - m_audioFile = QString(); - update(); -} - - - - -void SampleBuffer::setStartFrame(const f_cnt_t s) -{ - m_startFrame = s; -} - - - - -void SampleBuffer::setEndFrame(const f_cnt_t e) -{ - m_endFrame = e; -} - - - - -void SampleBuffer::setAmplification(float a) -{ - m_amplification = a; - emit sampleUpdated(); -} - - - - -void SampleBuffer::setReversed(bool on) -{ - Engine::audioEngine()->requestChangeInModel(); - m_varLock.lockForWrite(); - if (m_reversed != on) { std::reverse(m_data, m_data + m_frames); } - m_reversed = on; - m_varLock.unlock(); - Engine::audioEngine()->doneChangeInModel(); - emit sampleUpdated(); -} - - - - - -SampleBuffer::handleState::handleState(bool varyingPitch, int interpolationMode) : - m_frameIndex(0), - m_varyingPitch(varyingPitch), - m_isBackwards(false) -{ - int error; - m_interpolationMode = interpolationMode; - - if ((m_resamplingData = src_new(interpolationMode, DEFAULT_CHANNELS, &error)) == nullptr) - { - qDebug("Error: src_new() failed in SampleBuffer.cpp!\n"); - } -} - - - - -SampleBuffer::handleState::~handleState() -{ - src_delete(m_resamplingData); + static auto s_buffer = std::make_shared(); + return s_buffer; } } // namespace lmms diff --git a/src/core/SampleClip.cpp b/src/core/SampleClip.cpp index b09d7b3bb..5ef001e20 100644 --- a/src/core/SampleClip.cpp +++ b/src/core/SampleClip.cpp @@ -25,21 +25,22 @@ #include "SampleClip.h" #include +#include +#include "PathUtil.h" #include "SampleBuffer.h" #include "SampleClipView.h" +#include "SampleLoader.h" #include "SampleTrack.h" #include "TimeLineWidget.h" - namespace lmms { - -SampleClip::SampleClip( Track * _track ) : - Clip( _track ), - m_sampleBuffer( new SampleBuffer ), - m_isPlaying( false ) +SampleClip::SampleClip(Track* _track, Sample sample, bool isPlaying) + : Clip(_track) + , m_sample(std::move(sample)) + , m_isPlaying(false) { saveJournallingState( false ); setSampleFile( "" ); @@ -52,16 +53,10 @@ SampleClip::SampleClip( Track * _track ) : connect( Engine::getSong(), SIGNAL(timeSignatureChanged(int,int)), this, SLOT(updateLength())); - //care about positionmarker - gui::TimeLineWidget* timeLine = Engine::getSong()->getPlayPos( Song::PlayMode::Song ).m_timeLine; - if( timeLine ) - { - connect( timeLine, SIGNAL(positionMarkerMoved()), this, SLOT(playbackPositionChanged())); - } //playbutton clicked or space key / on Export Song set isPlaying to false connect( Engine::getSong(), SIGNAL(playbackStateChanged()), this, SLOT(playbackPositionChanged()), Qt::DirectConnection ); - //care about loops + //care about loops and jumps connect( Engine::getSong(), SIGNAL(updateSampleTracks()), this, SLOT(playbackPositionChanged()), Qt::DirectConnection ); //care about mute Clips @@ -87,14 +82,14 @@ SampleClip::SampleClip( Track * _track ) : updateTrackClips(); } -SampleClip::SampleClip(const SampleClip& orig) : - SampleClip(orig.getTrack()) +SampleClip::SampleClip(Track* track) + : SampleClip(track, Sample(), false) +{ +} + +SampleClip::SampleClip(const SampleClip& orig) : + SampleClip(orig.getTrack(), orig.m_sample, orig.m_isPlaying) { - // TODO: This creates a new SampleBuffer for the new Clip, eating up memory - // & eventually causing performance issues. Letting tracks share buffers - // when they're identical would fix this, but isn't possible right now. - *m_sampleBuffer = *orig.m_sampleBuffer; - m_isPlaying = orig.m_isPlaying; } @@ -107,9 +102,6 @@ SampleClip::~SampleClip() { sampletrack->updateClips(); } - Engine::audioEngine()->requestChangeInModel(); - sharedObject::unref( m_sampleBuffer ); - Engine::audioEngine()->doneChangeInModel(); } @@ -120,36 +112,45 @@ void SampleClip::changeLength( const TimePos & _length ) Clip::changeLength(std::max(static_cast(_length), 1)); } - - - -const QString & SampleClip::sampleFile() const +void SampleClip::changeLengthToSampleLength() { - return m_sampleBuffer->audioFile(); + int length = m_sample.sampleSize() / Engine::framesPerTick(); + changeLength(length); } -void SampleClip::setSampleBuffer( SampleBuffer* sb ) +const QString& SampleClip::sampleFile() const { - Engine::audioEngine()->requestChangeInModel(); - sharedObject::unref( m_sampleBuffer ); - Engine::audioEngine()->doneChangeInModel(); - m_sampleBuffer = sb; + return m_sample.sampleFile(); +} + +bool SampleClip::hasSampleFileLoaded(const QString & filename) const +{ + return m_sample.sampleFile() == filename; +} + +void SampleClip::setSampleBuffer(std::shared_ptr sb) +{ + { + const auto guard = Engine::audioEngine()->requestChangesGuard(); + m_sample = Sample(std::move(sb)); + } updateLength(); emit sampleChanged(); + + Engine::getSong()->setModified(); } - - -void SampleClip::setSampleFile(const QString & sf) +void SampleClip::setSampleFile(const QString& sf) { int length = 0; if (!sf.isEmpty()) { - m_sampleBuffer->setAudioFile(sf); + //Otherwise set it to the sample's length + m_sample = Sample(gui::SampleLoader::createBufferFromFile(sf)); length = sampleLength(); } @@ -221,6 +222,8 @@ void SampleClip::setIsPlaying(bool isPlaying) void SampleClip::updateLength() { emit sampleChanged(); + + Engine::getSong()->setModified(); } @@ -228,7 +231,7 @@ void SampleClip::updateLength() TimePos SampleClip::sampleLength() const { - return (int)( m_sampleBuffer->frames() / Engine::framesPerTick() ); + return static_cast(m_sample.sampleSize() / Engine::framesPerTick(m_sample.sampleRate())); } @@ -236,7 +239,7 @@ TimePos SampleClip::sampleLength() const void SampleClip::setSampleStartFrame(f_cnt_t startFrame) { - m_sampleBuffer->setStartFrame( startFrame ); + m_sample.setStartFrame(startFrame); } @@ -244,7 +247,7 @@ void SampleClip::setSampleStartFrame(f_cnt_t startFrame) void SampleClip::setSamplePlayLength(f_cnt_t length) { - m_sampleBuffer->setEndFrame( length ); + m_sample.setEndFrame(length); } @@ -267,15 +270,15 @@ void SampleClip::saveSettings( QDomDocument & _doc, QDomElement & _this ) if( sampleFile() == "" ) { QString s; - _this.setAttribute( "data", m_sampleBuffer->toBase64( s ) ); + _this.setAttribute("data", m_sample.toBase64()); } - _this.setAttribute( "sample_rate", m_sampleBuffer->sampleRate()); - if( usesCustomClipColor() ) + _this.setAttribute( "sample_rate", m_sample.sampleRate()); + if (const auto& c = color()) { - _this.setAttribute( "color", color().name() ); + _this.setAttribute("color", c->name()); } - if (m_sampleBuffer->reversed()) + if (m_sample.reversed()) { _this.setAttribute("reversed", "true"); } @@ -291,32 +294,36 @@ void SampleClip::loadSettings( const QDomElement & _this ) { movePosition( _this.attribute( "pos" ).toInt() ); } - setSampleFile( _this.attribute( "src" ) ); + + if (const auto srcFile = _this.attribute("src"); !srcFile.isEmpty()) + { + if (QFileInfo(PathUtil::toAbsolute(srcFile)).exists()) + { + setSampleFile(srcFile); + } + else { Engine::getSong()->collectError(QString("%1: %2").arg(tr("Sample not found"), srcFile)); } + } + if( sampleFile().isEmpty() && _this.hasAttribute( "data" ) ) { - m_sampleBuffer->loadFromBase64( _this.attribute( "data" ) ); - if (_this.hasAttribute("sample_rate")) - { - m_sampleBuffer->setSampleRate(_this.attribute("sample_rate").toInt()); - } + auto sampleRate = _this.hasAttribute("sample_rate") ? _this.attribute("sample_rate").toInt() : + Engine::audioEngine()->outputSampleRate(); + + auto buffer = gui::SampleLoader::createBufferFromBase64(_this.attribute("data"), sampleRate); + m_sample = Sample(std::move(buffer)); } changeLength( _this.attribute( "len" ).toInt() ); setMuted( _this.attribute( "muted" ).toInt() ); setStartTimeOffset( _this.attribute( "off" ).toInt() ); - if( _this.hasAttribute( "color" ) ) + if (_this.hasAttribute("color")) { - useCustomClipColor( true ); - setColor( _this.attribute( "color" ) ); - } - else - { - useCustomClipColor(false); + setColor(QColor{_this.attribute("color")}); } if(_this.hasAttribute("reversed")) { - m_sampleBuffer->setReversed(true); + m_sample.setReversed(true); emit wasReversed(); // tell SampleClipView to update the view } } diff --git a/src/core/SampleDecoder.cpp b/src/core/SampleDecoder.cpp new file mode 100644 index 000000000..ec0fcc39a --- /dev/null +++ b/src/core/SampleDecoder.cpp @@ -0,0 +1,236 @@ +/* + * SampleDecoder.cpp - Decodes audio files in various formats + * + * Copyright (c) 2023 saker + * + * 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 "SampleDecoder.h" + +#include +#include +#include +#include +#include + +#ifdef LMMS_HAVE_OGGVORBIS +#include +#endif + +#include "AudioEngine.h" +#include "DrumSynth.h" +#include "Engine.h" +#include "lmms_basics.h" + +namespace lmms { + +namespace { + +using Decoder = std::optional (*)(const QString&); + +auto decodeSampleSF(const QString& audioFile) -> std::optional; +auto decodeSampleDS(const QString& audioFile) -> std::optional; +#ifdef LMMS_HAVE_OGGVORBIS +auto decodeSampleOggVorbis(const QString& audioFile) -> std::optional; +#endif + +static constexpr std::array decoders = {&decodeSampleSF, +#ifdef LMMS_HAVE_OGGVORBIS + &decodeSampleOggVorbis, +#endif + &decodeSampleDS}; + +auto decodeSampleSF(const QString& audioFile) -> std::optional +{ + SNDFILE* sndFile = nullptr; + auto sfInfo = SF_INFO{}; + + // TODO: Remove use of QFile + auto file = QFile{audioFile}; + if (!file.open(QIODevice::ReadOnly)) { return std::nullopt; } + + sndFile = sf_open_fd(file.handle(), SFM_READ, &sfInfo, false); + if (sf_error(sndFile) != 0) { return std::nullopt; } + + auto buf = std::vector(sfInfo.channels * sfInfo.frames); + sf_read_float(sndFile, buf.data(), buf.size()); + + sf_close(sndFile); + file.close(); + + auto result = std::vector(sfInfo.frames); + for (int i = 0; i < static_cast(result.size()); ++i) + { + if (sfInfo.channels == 1) + { + // Upmix from mono to stereo + result[i] = {buf[i], buf[i]}; + } + else if (sfInfo.channels > 1) + { + // TODO: Add support for higher number of channels (i.e., 5.1 channel systems) + // The current behavior assumes stereo in all cases excluding mono. + // This may not be the expected behavior, given some audio files with a higher number of channels. + result[i] = {buf[i * sfInfo.channels], buf[i * sfInfo.channels + 1]}; + } + } + + return SampleDecoder::Result{std::move(result), static_cast(sfInfo.samplerate)}; +} + +auto decodeSampleDS(const QString& audioFile) -> std::optional +{ + // Populated by DrumSynth::GetDSFileSamples + int_sample_t* dataPtr = nullptr; + + auto ds = DrumSynth{}; + const auto engineRate = Engine::audioEngine()->outputSampleRate(); + const auto frames = ds.GetDSFileSamples(audioFile, dataPtr, DEFAULT_CHANNELS, engineRate); + const auto data = std::unique_ptr{dataPtr}; // NOLINT, we have to use a C-style array here + + if (frames <= 0 || !data) { return std::nullopt; } + + auto result = std::vector(frames); + src_short_to_float_array(data.get(), &result[0][0], frames * DEFAULT_CHANNELS); + + return SampleDecoder::Result{std::move(result), static_cast(engineRate)}; +} + +#ifdef LMMS_HAVE_OGGVORBIS +auto decodeSampleOggVorbis(const QString& audioFile) -> std::optional +{ + static auto s_read = [](void* buffer, size_t size, size_t count, void* stream) -> size_t { + auto file = static_cast(stream); + return file->read(static_cast(buffer), size * count); + }; + + static auto s_seek = [](void* stream, ogg_int64_t offset, int whence) -> int { + auto file = static_cast(stream); + if (whence == SEEK_SET) { file->seek(offset); } + else if (whence == SEEK_CUR) { file->seek(file->pos() + offset); } + else if (whence == SEEK_END) { file->seek(file->size() + offset); } + else { return -1; } + return 0; + }; + + static auto s_close = [](void* stream) -> int { + auto file = static_cast(stream); + file->close(); + return 0; + }; + + static auto s_tell = [](void* stream) -> long { + auto file = static_cast(stream); + return file->pos(); + }; + + static ov_callbacks s_callbacks = {s_read, s_seek, s_close, s_tell}; + + // TODO: Remove use of QFile + auto file = QFile{audioFile}; + if (!file.open(QIODevice::ReadOnly)) { return std::nullopt; } + + auto vorbisFile = OggVorbis_File{}; + if (ov_open_callbacks(&file, &vorbisFile, nullptr, 0, s_callbacks) < 0) { return std::nullopt; } + + const auto vorbisInfo = ov_info(&vorbisFile, -1); + if (vorbisInfo == nullptr) { return std::nullopt; } + + const auto numChannels = vorbisInfo->channels; + const auto sampleRate = vorbisInfo->rate; + const auto numSamples = ov_pcm_total(&vorbisFile, -1); + if (numSamples < 0) { return std::nullopt; } + + auto buffer = std::vector(numSamples); + auto output = static_cast(nullptr); + + auto totalSamplesRead = 0; + while (true) + { + auto samplesRead = ov_read_float(&vorbisFile, &output, numSamples, 0); + + if (samplesRead < 0) { return std::nullopt; } + else if (samplesRead == 0) { break; } + + std::copy_n(*output, samplesRead, buffer.begin() + totalSamplesRead); + totalSamplesRead += samplesRead; + } + + auto result = std::vector(totalSamplesRead / numChannels); + for (int i = 0; i < result.size(); ++i) + { + if (numChannels == 1) { result[i] = {buffer[i], buffer[i]}; } + else if (numChannels > 1) { result[i] = {buffer[i * numChannels], buffer[i * numChannels + 1]}; } + } + + ov_clear(&vorbisFile); + return SampleDecoder::Result{std::move(result), static_cast(sampleRate)}; +} +#endif // LMMS_HAVE_OGGVORBIS +} // namespace + +auto SampleDecoder::supportedAudioTypes() -> const std::vector& +{ + static const auto s_audioTypes = [] { + auto types = std::vector(); + + // Add DrumSynth by default since that support comes from us + types.push_back(AudioType{"DrumSynth", "ds"}); + + auto sfFormatInfo = SF_FORMAT_INFO{}; + auto simpleTypeCount = 0; + sf_command(nullptr, SFC_GET_SIMPLE_FORMAT_COUNT, &simpleTypeCount, sizeof(int)); + + // TODO: Ideally, this code should be iterating over the major formats, but some important extensions such as + // *.ogg are not included. This is planned for future versions of sndfile. + for (int simple = 0; simple < simpleTypeCount; ++simple) + { + sfFormatInfo.format = simple; + sf_command(nullptr, SFC_GET_SIMPLE_FORMAT, &sfFormatInfo, sizeof(sfFormatInfo)); + + auto it = std::find_if(types.begin(), types.end(), + [&](const AudioType& type) { return sfFormatInfo.extension == type.extension; }); + if (it != types.end()) { continue; } + + auto name = std::string{sfFormatInfo.extension}; + std::transform(name.begin(), name.end(), name.begin(), [](unsigned char ch) { return std::toupper(ch); }); + + types.push_back(AudioType{std::move(name), sfFormatInfo.extension}); + } + + std::sort(types.begin(), types.end(), [&](const AudioType& a, const AudioType& b) { return a.name < b.name; }); + return types; + }(); + return s_audioTypes; +} + +auto SampleDecoder::decode(const QString& audioFile) -> std::optional +{ + auto result = std::optional{}; + for (const auto& decoder : decoders) + { + result = decoder(audioFile); + if (result) { break; } + } + + return result; +} + +} // namespace lmms diff --git a/src/core/SamplePlayHandle.cpp b/src/core/SamplePlayHandle.cpp index ea27146cb..e23cfa473 100644 --- a/src/core/SamplePlayHandle.cpp +++ b/src/core/SamplePlayHandle.cpp @@ -35,9 +35,9 @@ namespace lmms { -SamplePlayHandle::SamplePlayHandle( SampleBuffer* sampleBuffer , bool ownAudioPort ) : +SamplePlayHandle::SamplePlayHandle(Sample* sample, bool ownAudioPort) : PlayHandle( Type::SamplePlayHandle ), - m_sampleBuffer( sharedObject::ref( sampleBuffer ) ), + m_sample(sample), m_doneMayReturnTrue( true ), m_frame( 0 ), m_ownAudioPort( ownAudioPort ), @@ -56,16 +56,15 @@ SamplePlayHandle::SamplePlayHandle( SampleBuffer* sampleBuffer , bool ownAudioPo SamplePlayHandle::SamplePlayHandle( const QString& sampleFile ) : - SamplePlayHandle( new SampleBuffer( sampleFile ) , true) + SamplePlayHandle(new Sample(sampleFile), true) { - sharedObject::unref( m_sampleBuffer ); } SamplePlayHandle::SamplePlayHandle( SampleClip* clip ) : - SamplePlayHandle( clip->sampleBuffer() , false) + SamplePlayHandle(&clip->sample(), false) { m_track = clip->getTrack(); setAudioPort( ( (SampleTrack *)clip->getTrack() )->audioPort() ); @@ -76,10 +75,10 @@ SamplePlayHandle::SamplePlayHandle( SampleClip* clip ) : SamplePlayHandle::~SamplePlayHandle() { - sharedObject::unref( m_sampleBuffer ); if( m_ownAudioPort ) { delete audioPort(); + delete m_sample; } } @@ -115,7 +114,7 @@ void SamplePlayHandle::play( sampleFrame * buffer ) m_volumeModel->value() / DefaultVolume } };*/ // SamplePlayHandle always plays the sample at its original pitch; // it is used only for previews, SampleTracks and the metronome. - if (!m_sampleBuffer->play(workingBuffer, &m_state, frames, DefaultBaseFreq)) + if (!m_sample->play(workingBuffer, &m_state, frames, DefaultBaseFreq)) { memset(workingBuffer, 0, frames * sizeof(sampleFrame)); } @@ -145,8 +144,8 @@ bool SamplePlayHandle::isFromTrack( const Track * _track ) const f_cnt_t SamplePlayHandle::totalFrames() const { - return ( m_sampleBuffer->endFrame() - m_sampleBuffer->startFrame() ) * - ( Engine::audioEngine()->processingSampleRate() / m_sampleBuffer->sampleRate() ); + return (m_sample->endFrame() - m_sample->startFrame()) * + (static_cast(Engine::audioEngine()->outputSampleRate()) / m_sample->sampleRate()); } diff --git a/src/core/SampleRecordHandle.cpp b/src/core/SampleRecordHandle.cpp index f9043570d..9f477128c 100644 --- a/src/core/SampleRecordHandle.cpp +++ b/src/core/SampleRecordHandle.cpp @@ -52,15 +52,10 @@ SampleRecordHandle::SampleRecordHandle(SampleClip* clip, TimePos startRecordTime SampleRecordHandle::~SampleRecordHandle() { - if( !m_buffers.empty() ) - { - SampleBuffer* sb; - createSampleBuffer( &sb ); - m_clip->setSampleBuffer( sb ); + if (!m_buffers.empty()) { m_clip->setSampleBuffer(createSampleBuffer()); } m_clip->setStartTimeOffset(m_startRecordTimeOffset); - } - + while( !m_buffers.empty() ) { delete[] m_buffers.front().first; @@ -114,28 +109,22 @@ f_cnt_t SampleRecordHandle::framesRecorded() const -void SampleRecordHandle::createSampleBuffer( SampleBuffer** sampleBuf ) +std::shared_ptr SampleRecordHandle::createSampleBuffer() { const f_cnt_t frames = framesRecorded(); // create buffer to store all recorded buffers in - auto data = new sampleFrame[frames]; - // make sure buffer is cleaned up properly at the end... - sampleFrame * data_ptr = data; - - - assert( data != nullptr ); + auto bigBuffer = std::vector(frames); // now copy all buffers into big buffer - for( bufferList::const_iterator it = m_buffers.begin(); it != m_buffers.end(); ++it ) + auto framesCopied = 0; + for (const auto& [buf, numFrames] : m_buffers) { - memcpy( data_ptr, ( *it ).first, ( *it ).second * - sizeof( sampleFrame ) ); - data_ptr += ( *it ).second; + std::copy_n(buf, numFrames, bigBuffer.begin() + framesCopied); + framesCopied += numFrames; } + // create according sample-buffer out of big buffer - *sampleBuf = new SampleBuffer( data, frames ); - ( *sampleBuf)->setSampleRate( Engine::audioEngine()->inputSampleRate() ); - delete[] data; + return std::make_shared(std::move(bigBuffer), Engine::audioEngine()->inputSampleRate()); } diff --git a/src/core/Song.cpp b/src/core/Song.cpp index 3a735331c..392e9e256 100644 --- a/src/core/Song.cpp +++ b/src/core/Song.cpp @@ -184,14 +184,9 @@ void Song::setTimeSignature() -void Song::savePos() +void Song::savePlayStartPosition() { - gui::TimeLineWidget* tl = getPlayPos().m_timeLine; - - if( tl != nullptr ) - { - tl->savePos( getPlayPos() ); - } + getTimeline().setPlayStartPosition(getPlayPos()); } @@ -258,16 +253,17 @@ void Song::processNextBuffer() return false; }; - const auto timeline = getPlayPos().m_timeLine; - const auto loopEnabled = !m_exporting && timeline && timeline->loopPointsEnabled(); + const auto& timeline = getTimeline(); + const auto loopEnabled = !m_exporting && timeline.loopEnabled(); // Ensure playback begins within the loop if it is enabled - if (loopEnabled) { enforceLoop(timeline->loopBegin(), timeline->loopEnd()); } + if (loopEnabled) { enforceLoop(timeline.loopBegin(), timeline.loopEnd()); } - // Inform VST plugins if the user moved the play head + // Inform VST plugins and sample tracks if the user moved the play head if (getPlayPos().jumped()) { m_vstSyncController.setPlaybackJumped(true); + emit updateSampleTracks(); getPlayPos().setJumped(false); } @@ -301,13 +297,13 @@ void Song::processNextBuffer() } // Handle loop points, and inform VST plugins of the loop status - if (loopEnabled || (m_loopRenderRemaining > 1 && getPlayPos() >= timeline->loopBegin())) + if (loopEnabled || (m_loopRenderRemaining > 1 && getPlayPos() >= timeline.loopBegin())) { m_vstSyncController.startCycle( - timeline->loopBegin().getTicks(), timeline->loopEnd().getTicks()); + timeline.loopBegin().getTicks(), timeline.loopEnd().getTicks()); // Loop if necessary, and decrement the remaining loops if we did - if (enforceLoop(timeline->loopBegin(), timeline->loopEnd()) + if (enforceLoop(timeline.loopBegin(), timeline.loopEnd()) && m_loopRenderRemaining > 1) { m_loopRenderRemaining--; @@ -492,7 +488,7 @@ void Song::playSong() m_vstSyncController.setPlaybackState( true ); - savePos(); + savePlayStartPosition(); emit playbackStateChanged(); } @@ -531,7 +527,7 @@ void Song::playPattern() m_vstSyncController.setPlaybackState( true ); - savePos(); + savePlayStartPosition(); emit playbackStateChanged(); } @@ -556,7 +552,7 @@ void Song::playMidiClip( const MidiClip* midiClipToPlay, bool loop ) m_paused = false; } - savePos(); + savePlayStartPosition(); emit playbackStateChanged(); } @@ -566,6 +562,8 @@ void Song::playMidiClip( const MidiClip* midiClipToPlay, bool loop ) void Song::updateLength() { + if (m_loadingProject) { return; } + m_length = 0; m_tracksMutex.lockForRead(); for (auto track : tracks()) @@ -644,40 +642,32 @@ void Song::stop() // To avoid race conditions with the processing threads Engine::audioEngine()->requestChangeInModel(); - TimeLineWidget * tl = getPlayPos().m_timeLine; + auto& timeline = getTimeline(); m_paused = false; m_recording = true; - - if( tl ) - { - switch( tl->behaviourAtStop() ) - { - case TimeLineWidget::BehaviourAtStopState::BackToZero: - getPlayPos().setTicks(0); - m_elapsedMilliSeconds[static_cast(m_playMode)] = 0; - break; - - case TimeLineWidget::BehaviourAtStopState::BackToStart: - if( tl->savedPos() >= 0 ) - { - getPlayPos().setTicks(tl->savedPos().getTicks()); - setToTime(tl->savedPos()); - - tl->savePos( -1 ); - } - break; - - case TimeLineWidget::BehaviourAtStopState::KeepStopPosition: - break; - } - } - else - { - getPlayPos().setTicks( 0 ); - m_elapsedMilliSeconds[static_cast(m_playMode)] = 0; - } m_playing = false; + switch (timeline.stopBehaviour()) + { + case Timeline::StopBehaviour::BackToZero: + getPlayPos().setTicks(0); + m_elapsedMilliSeconds[static_cast(m_playMode)] = 0; + break; + + case Timeline::StopBehaviour::BackToStart: + if (timeline.playStartPosition() >= 0) + { + getPlayPos().setTicks(timeline.playStartPosition().getTicks()); + setToTime(timeline.playStartPosition()); + + timeline.setPlayStartPosition(-1); + } + break; + + case Timeline::StopBehaviour::KeepPosition: + break; + } + m_elapsedMilliSeconds[static_cast(PlayMode::None)] = m_elapsedMilliSeconds[static_cast(m_playMode)]; getPlayPos(PlayMode::None).setTicks(getPlayPos().getTicks()); @@ -719,37 +709,35 @@ void Song::startExport() m_exporting = true; updateLength(); + const auto& timeline = getTimeline(PlayMode::Song); + if (m_renderBetweenMarkers) { - m_exportSongBegin = m_exportLoopBegin = getPlayPos(PlayMode::Song).m_timeLine->loopBegin(); - m_exportSongEnd = m_exportLoopEnd = getPlayPos(PlayMode::Song).m_timeLine->loopEnd(); + m_exportSongBegin = m_exportLoopBegin = timeline.loopBegin(); + m_exportSongEnd = m_exportLoopEnd = timeline.loopEnd(); - getPlayPos(PlayMode::Song).setTicks( getPlayPos(PlayMode::Song).m_timeLine->loopBegin().getTicks() ); + getPlayPos(PlayMode::Song).setTicks(timeline.loopBegin().getTicks()); } else { m_exportSongEnd = TimePos(m_length, 0); // Handle potentially ridiculous loop points gracefully. - if (m_loopRenderCount > 1 && getPlayPos(PlayMode::Song).m_timeLine->loopEnd() > m_exportSongEnd) + if (m_loopRenderCount > 1 && timeline.loopEnd() > m_exportSongEnd) { - m_exportSongEnd = getPlayPos(PlayMode::Song).m_timeLine->loopEnd(); + m_exportSongEnd = timeline.loopEnd(); } if (!m_exportLoop) m_exportSongEnd += TimePos(1,0); m_exportSongBegin = TimePos(0,0); - // FIXME: remove this check once we load timeline in headless mode - if (getPlayPos(PlayMode::Song).m_timeLine) - { - m_exportLoopBegin = getPlayPos(PlayMode::Song).m_timeLine->loopBegin() < m_exportSongEnd && - getPlayPos(PlayMode::Song).m_timeLine->loopEnd() <= m_exportSongEnd ? - getPlayPos(PlayMode::Song).m_timeLine->loopBegin() : TimePos(0,0); - m_exportLoopEnd = getPlayPos(PlayMode::Song).m_timeLine->loopBegin() < m_exportSongEnd && - getPlayPos(PlayMode::Song).m_timeLine->loopEnd() <= m_exportSongEnd ? - getPlayPos(PlayMode::Song).m_timeLine->loopEnd() : TimePos(0,0); - } + m_exportLoopBegin = timeline.loopBegin() < m_exportSongEnd && timeline.loopEnd() <= m_exportSongEnd + ? timeline.loopBegin() + : TimePos{0}; + m_exportLoopEnd = timeline.loopBegin() < m_exportSongEnd && timeline.loopEnd() <= m_exportSongEnd + ? timeline.loopEnd() + : TimePos{0}; getPlayPos(PlayMode::Song).setTicks( 0 ); } @@ -959,13 +947,12 @@ void Song::createNewProject() m_oldFileName = ""; setProjectFileName(""); - Track * t; - t = Track::create( Track::Type::Instrument, this ); - dynamic_cast( t )->loadInstrument( - "tripleoscillator" ); - t = Track::create(Track::Type::Instrument, Engine::patternStore()); - dynamic_cast( t )->loadInstrument( - "kicker" ); + auto tripleOscTrack = Track::create(Track::Type::Instrument, this); + dynamic_cast(tripleOscTrack)->loadInstrument("tripleoscillator"); + + auto kickerTrack = Track::create(Track::Type::Instrument, Engine::patternStore()); + dynamic_cast(kickerTrack)->loadInstrument("kicker"); + Track::create( Track::Type::Sample, this ); Track::create( Track::Type::Pattern, this ); Track::create( Track::Type::Automation, this ); @@ -978,7 +965,7 @@ void Song::createNewProject() QCoreApplication::instance()->processEvents(); m_loadingProject = false; - + updateLength(); Engine::patternStore()->updateAfterTrackAdd(); Engine::projectJournal()->setJournalling( true ); @@ -1080,11 +1067,7 @@ void Song::loadProject( const QString & fileName ) m_masterVolumeModel.loadSettings( dataFile.head(), "mastervol" ); m_masterPitchModel.loadSettings( dataFile.head(), "masterpitch" ); - if( getPlayPos(PlayMode::Song).m_timeLine ) - { - // reset loop-point-state - getPlayPos(PlayMode::Song).m_timeLine->toggleLoopPoints( 0 ); - } + getTimeline(PlayMode::Song).setLoopEnabled(false); if( !dataFile.content().firstChildElement( "track" ).isNull() ) { @@ -1167,9 +1150,9 @@ void Song::loadProject( const QString & fileName ) { getGUI()->getProjectNotes()->SerializingObject::restoreState( node.toElement() ); } - else if( node.nodeName() == getPlayPos(PlayMode::Song).m_timeLine->nodeName() ) + else if (node.nodeName() == getTimeline(PlayMode::Song).nodeName()) { - getPlayPos(PlayMode::Song).m_timeLine->restoreState( node.toElement() ); + getTimeline(PlayMode::Song).restoreState(node.toElement()); } } } @@ -1225,6 +1208,7 @@ void Song::loadProject( const QString & fileName ) } m_loadingProject = false; + updateLength(); setModified(false); m_loadOnLaunch = false; } @@ -1253,7 +1237,7 @@ bool Song::saveProjectFile(const QString & filename, bool withResources) getGUI()->pianoRoll()->saveState( dataFile, dataFile.content() ); getGUI()->automationEditor()->m_editor->saveState( dataFile, dataFile.content() ); getGUI()->getProjectNotes()->SerializingObject::saveState( dataFile, dataFile.content() ); - getPlayPos(PlayMode::Song).m_timeLine->saveState( dataFile, dataFile.content() ); + getTimeline(PlayMode::Song).saveState(dataFile, dataFile.content()); } saveControllerStates( dataFile, dataFile.content() ); diff --git a/src/core/ThreadPool.cpp b/src/core/ThreadPool.cpp new file mode 100644 index 000000000..2e5f00df0 --- /dev/null +++ b/src/core/ThreadPool.cpp @@ -0,0 +1,85 @@ +/* + * ThreadPool.cpp + * + * Copyright (c) 2024 saker + * + * 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 "ThreadPool.h" + +#include +#include +#include + +namespace lmms { +ThreadPool::ThreadPool(size_t numWorkers) +{ + assert(numWorkers > 0); + + m_workers.reserve(numWorkers); + for (size_t i = 0; i < numWorkers; ++i) + { + m_workers.emplace_back([this] { run(); }); + } +} + +ThreadPool::~ThreadPool() +{ + { + const auto lock = std::unique_lock{m_runMutex}; + m_done = true; + } + + m_runCond.notify_all(); + + for (auto& worker : m_workers) + { + if (worker.joinable()) { worker.join(); } + } +} + +auto ThreadPool::numWorkers() const -> size_t +{ + return m_workers.size(); +} + +void ThreadPool::run() +{ + while (!m_done) + { + std::function task; + { + auto lock = std::unique_lock{m_runMutex}; + m_runCond.wait(lock, [this] { return !m_queue.empty() || m_done; }); + + if (m_done) { break; } + task = m_queue.front(); + m_queue.pop(); + } + task(); + } +} + +auto ThreadPool::instance() -> ThreadPool& +{ + static auto s_pool = ThreadPool{s_numWorkers}; + return s_pool; +} + +} // namespace lmms diff --git a/src/core/TimePos.cpp b/src/core/TimePos.cpp index 86a65f103..09c1019bc 100644 --- a/src/core/TimePos.cpp +++ b/src/core/TimePos.cpp @@ -25,6 +25,7 @@ #include "TimePos.h" +#include #include "MeterModel.h" namespace lmms @@ -161,11 +162,11 @@ tick_t TimePos::getTickWithinBeat( const TimeSig &sig ) const f_cnt_t TimePos::frames( const float framesPerTick ) const { - if( m_ticks >= 0 ) - { - return static_cast( m_ticks * framesPerTick ); - } - return 0; + // Before, step notes used to have negative length. This + // assert is a safeguard against negative length being + // introduced again (now using Note Types instead #5902) + assert(m_ticks >= 0); + return static_cast(m_ticks * framesPerTick); } double TimePos::getTimeInMilliseconds( bpm_t beatsPerMinute ) const @@ -221,4 +222,4 @@ double TimePos::ticksToMilliseconds(double ticks, bpm_t beatsPerMinute) } -} // namespace lmms \ No newline at end of file +} // namespace lmms diff --git a/src/core/Timeline.cpp b/src/core/Timeline.cpp new file mode 100644 index 000000000..f6f30c21c --- /dev/null +++ b/src/core/Timeline.cpp @@ -0,0 +1,83 @@ +/* + * Timeline.cpp + * + * Copyright (c) 2023 Dominic Clark + * + * 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 "Timeline.h" + +#include +#include + +#include +#include + +namespace lmms { + +void Timeline::setLoopBegin(TimePos begin) +{ + std::tie(m_loopBegin, m_loopEnd) = std::minmax(begin, TimePos{m_loopEnd}); +} + +void Timeline::setLoopEnd(TimePos end) +{ + std::tie(m_loopBegin, m_loopEnd) = std::minmax(TimePos{m_loopBegin}, end); +} + +void Timeline::setLoopPoints(TimePos begin, TimePos end) +{ + std::tie(m_loopBegin, m_loopEnd) = std::minmax(begin, end); +} + +void Timeline::setLoopEnabled(bool enabled) +{ + if (enabled != m_loopEnabled) { + m_loopEnabled = enabled; + emit loopEnabledChanged(m_loopEnabled); + } +} + +void Timeline::setStopBehaviour(StopBehaviour behaviour) +{ + if (behaviour != m_stopBehaviour) { + m_stopBehaviour = behaviour; + emit stopBehaviourChanged(m_stopBehaviour); + } +} + +void Timeline::saveSettings(QDomDocument& doc, QDomElement& element) +{ + element.setAttribute("lp0pos", static_cast(loopBegin())); + element.setAttribute("lp1pos", static_cast(loopEnd())); + element.setAttribute("lpstate", static_cast(loopEnabled())); + element.setAttribute("stopbehaviour", static_cast(stopBehaviour())); +} + +void Timeline::loadSettings(const QDomElement& element) +{ + setLoopPoints( + static_cast(element.attribute("lp0pos").toInt()), + static_cast(element.attribute("lp1pos").toInt()) + ); + setLoopEnabled(static_cast(element.attribute("lpstate").toInt())); + setStopBehaviour(static_cast(element.attribute("stopbehaviour", "1").toInt())); +} + +} // namespace lmms diff --git a/src/core/Track.cpp b/src/core/Track.cpp index b034b95fb..6c4ba465e 100644 --- a/src/core/Track.cpp +++ b/src/core/Track.cpp @@ -64,10 +64,8 @@ Track::Track( Type type, TrackContainer * tc ) : m_mutedModel( false, this, tr( "Mute" ) ), /*!< For controlling track muting */ m_soloModel( false, this, tr( "Solo" ) ), /*!< For controlling track soloing */ m_simpleSerializingMode( false ), - m_clips(), /*!< The clips (segments) */ - m_color( 0, 0, 0 ), - m_hasColor( false ) -{ + m_clips() /*!< The clips (segments) */ +{ m_trackContainer->addTrack( this ); m_height = -1; } @@ -209,9 +207,9 @@ void Track::saveSettings( QDomDocument & doc, QDomElement & element ) element.setAttribute( "trackheight", m_height ); } - if( m_hasColor ) + if (m_color.has_value()) { - element.setAttribute( "color", m_color.name() ); + element.setAttribute("color", m_color->name()); } QDomElement tsDe = doc.createElement( nodeName() ); @@ -264,14 +262,9 @@ void Track::loadSettings( const QDomElement & element ) // Older project files that didn't have this attribute will set the value to false (issue 5562) m_mutedBeforeSolo = QVariant( element.attribute( "mutedBeforeSolo", "0" ) ).toBool(); - if( element.hasAttribute( "color" ) ) + if (element.hasAttribute("color")) { - QColor newColor = QColor(element.attribute("color")); - setColor(newColor); - } - else - { - resetColor(); + setColor(QColor{element.attribute("color")}); } if( m_simpleSerializingMode ) @@ -290,10 +283,9 @@ void Track::loadSettings( const QDomElement & element ) return; } - while( !m_clips.empty() ) { - delete m_clips.front(); -// m_clips.erase( m_clips.begin() ); + auto guard = Engine::audioEngine()->requestChangesGuard(); + deleteClips(); } QDomNode node = element.firstChild(); @@ -634,23 +626,30 @@ void Track::toggleSolo() } } -void Track::setColor(const QColor& c) +void Track::setColor(const std::optional& color) { - m_hasColor = true; - m_color = c; + m_color = color; emit colorChanged(); } -void Track::resetColor() -{ - m_hasColor = false; - emit colorChanged(); -} - - BoolModel *Track::getMutedModel() { return &m_mutedModel; } -} // namespace lmms \ No newline at end of file +void Track::setName(const QString& newName) +{ + if (m_name != newName) + { + m_name = newName; + + if (auto song = Engine::getSong()) + { + song->setModified(); + } + + emit nameChanged(); + } +} + +} // namespace lmms diff --git a/src/core/VstSyncController.cpp b/src/core/VstSyncController.cpp index c4b59eb6f..79344a5b5 100644 --- a/src/core/VstSyncController.cpp +++ b/src/core/VstSyncController.cpp @@ -155,7 +155,7 @@ void VstSyncController::updateSampleRate() { if (!m_syncData) { return; } - m_syncData->m_sampleRate = Engine::audioEngine()->processingSampleRate(); + m_syncData->m_sampleRate = Engine::audioEngine()->outputSampleRate(); #ifdef VST_SNC_LATENCY m_syncData->m_latency = m_syncData->m_bufferSize * m_syncData->m_bpm / ( (float) m_syncData->m_sampleRate * 60 ); diff --git a/src/core/audio/AudioAlsa.cpp b/src/core/audio/AudioAlsa.cpp index 6e17ad0fe..eda0f7a31 100644 --- a/src/core/audio/AudioAlsa.cpp +++ b/src/core/audio/AudioAlsa.cpp @@ -53,12 +53,7 @@ AudioAlsa::AudioAlsa( bool & _success_ful, AudioEngine* _audioEngine ) : "Could not avoid possible interception by PulseAudio\n" ); } - int err; - - if( ( err = snd_pcm_open( &m_handle, - probeDevice().toLatin1().constData(), - SND_PCM_STREAM_PLAYBACK, - 0 ) ) < 0 ) + if (int err = snd_pcm_open(&m_handle, probeDevice().toLatin1().constData(), SND_PCM_STREAM_PLAYBACK, 0); err < 0) { printf( "Playback open error: %s\n", snd_strerror( err ) ); return; @@ -67,14 +62,13 @@ AudioAlsa::AudioAlsa( bool & _success_ful, AudioEngine* _audioEngine ) : snd_pcm_hw_params_malloc( &m_hwParams ); snd_pcm_sw_params_malloc( &m_swParams ); - if( ( err = setHWParams( channels(), - SND_PCM_ACCESS_RW_INTERLEAVED ) ) < 0 ) + if (int err = setHWParams(channels(), SND_PCM_ACCESS_RW_INTERLEAVED); err < 0) { printf( "Setting of hwparams failed: %s\n", snd_strerror( err ) ); return; } - if( ( err = setSWParams() ) < 0 ) + if (int err = setSWParams(); err < 0) { printf( "Setting of swparams failed: %s\n", snd_strerror( err ) ); @@ -83,9 +77,8 @@ AudioAlsa::AudioAlsa( bool & _success_ful, AudioEngine* _audioEngine ) : // set FD_CLOEXEC flag for all file descriptors so forked processes // do not inherit them - struct pollfd * ufds; int count = snd_pcm_poll_descriptors_count( m_handle ); - ufds = new pollfd[count]; + auto ufds = new pollfd[count]; snd_pcm_poll_descriptors( m_handle, ufds, count ); for (int i = 0; i < std::max(3, count); ++i) { @@ -160,7 +153,7 @@ AudioAlsa::DeviceInfoCollection AudioAlsa::getAvailableDevices() { DeviceInfoCollection deviceInfos; - char **hints; + char** hints = nullptr; /* Enumerate sound devices */ int err = snd_device_name_hint(-1, "pcm", (void***)&hints); @@ -247,52 +240,6 @@ void AudioAlsa::stopProcessing() stopProcessingThread( this ); } - - - -void AudioAlsa::applyQualitySettings() -{ - if( hqAudio() ) - { - setSampleRate( Engine::audioEngine()->processingSampleRate() ); - - if( m_handle != nullptr ) - { - snd_pcm_close( m_handle ); - } - - int err; - if( ( err = snd_pcm_open( &m_handle, - probeDevice().toLatin1().constData(), - SND_PCM_STREAM_PLAYBACK, - 0 ) ) < 0 ) - { - printf( "Playback open error: %s\n", - snd_strerror( err ) ); - return; - } - - if( ( err = setHWParams( channels(), - SND_PCM_ACCESS_RW_INTERLEAVED ) ) < 0 ) - { - printf( "Setting of hwparams failed: %s\n", - snd_strerror( err ) ); - return; - } - if( ( err = setSWParams() ) < 0 ) - { - printf( "Setting of swparams failed: %s\n", - snd_strerror( err ) ); - return; - } - } - - AudioDevice::applyQualitySettings(); -} - - - - void AudioAlsa::run() { auto temp = new surroundSampleFrame[audioEngine()->framesPerPeriod()]; @@ -323,10 +270,7 @@ void AudioAlsa::run() } outbuf_size = frames * channels(); - convertToS16( temp, frames, - audioEngine()->masterGain(), - outbuf, - m_convertEndian ); + convertToS16(temp, frames, outbuf, m_convertEndian); } int min_len = std::min(len, outbuf_size - outbuf_pos); memcpy( ptr, outbuf + outbuf_pos, @@ -373,10 +317,8 @@ void AudioAlsa::run() int AudioAlsa::setHWParams( const ch_cnt_t _channels, snd_pcm_access_t _access ) { - int err, dir; - // choose all parameters - if( ( err = snd_pcm_hw_params_any( m_handle, m_hwParams ) ) < 0 ) + if (int err = snd_pcm_hw_params_any(m_handle, m_hwParams); err < 0) { printf( "Broken configuration for playback: no configurations " "available: %s\n", snd_strerror( err ) ); @@ -384,8 +326,7 @@ int AudioAlsa::setHWParams( const ch_cnt_t _channels, snd_pcm_access_t _access ) } // set the interleaved read/write format - if( ( err = snd_pcm_hw_params_set_access( m_handle, m_hwParams, - _access ) ) < 0 ) + if (int err = snd_pcm_hw_params_set_access(m_handle, m_hwParams, _access); err < 0) { printf( "Access type not available for playback: %s\n", snd_strerror( err ) ); @@ -393,11 +334,9 @@ int AudioAlsa::setHWParams( const ch_cnt_t _channels, snd_pcm_access_t _access ) } // set the sample format - if( ( snd_pcm_hw_params_set_format( m_handle, m_hwParams, - SND_PCM_FORMAT_S16_LE ) ) < 0 ) + if (int err = snd_pcm_hw_params_set_format(m_handle, m_hwParams, SND_PCM_FORMAT_S16_LE); err < 0) { - if( ( snd_pcm_hw_params_set_format( m_handle, m_hwParams, - SND_PCM_FORMAT_S16_BE ) ) < 0 ) + if (int err = snd_pcm_hw_params_set_format(m_handle, m_hwParams, SND_PCM_FORMAT_S16_BE); err < 0) { printf( "Neither little- nor big-endian available for " "playback: %s\n", snd_strerror( err ) ); @@ -411,8 +350,7 @@ int AudioAlsa::setHWParams( const ch_cnt_t _channels, snd_pcm_access_t _access ) } // set the count of channels - if( ( err = snd_pcm_hw_params_set_channels( m_handle, m_hwParams, - _channels ) ) < 0 ) + if (int err = snd_pcm_hw_params_set_channels(m_handle, m_hwParams, _channels); err < 0) { printf( "Channel count (%i) not available for playbacks: %s\n" "(Does your soundcard not support surround?)\n", @@ -421,11 +359,9 @@ int AudioAlsa::setHWParams( const ch_cnt_t _channels, snd_pcm_access_t _access ) } // set the sample rate - if( ( err = snd_pcm_hw_params_set_rate( m_handle, m_hwParams, - sampleRate(), 0 ) ) < 0 ) + if (int err = snd_pcm_hw_params_set_rate(m_handle, m_hwParams, sampleRate(), 0); err < 0) { - if( ( err = snd_pcm_hw_params_set_rate( m_handle, m_hwParams, - audioEngine()->baseSampleRate(), 0 ) ) < 0 ) + if (int err = snd_pcm_hw_params_set_rate(m_handle, m_hwParams, audioEngine()->baseSampleRate(), 0); err < 0) { printf( "Could not set sample rate: %s\n", snd_strerror( err ) ); @@ -435,36 +371,29 @@ int AudioAlsa::setHWParams( const ch_cnt_t _channels, snd_pcm_access_t _access ) m_periodSize = audioEngine()->framesPerPeriod(); m_bufferSize = m_periodSize * 8; - dir = 0; - err = snd_pcm_hw_params_set_period_size_near( m_handle, m_hwParams, - &m_periodSize, &dir ); - if( err < 0 ) + int dir; + if (int err = snd_pcm_hw_params_set_period_size_near(m_handle, m_hwParams, &m_periodSize, &dir); err < 0) { printf( "Unable to set period size %lu for playback: %s\n", m_periodSize, snd_strerror( err ) ); return err; } dir = 0; - err = snd_pcm_hw_params_get_period_size( m_hwParams, &m_periodSize, - &dir ); - if( err < 0 ) + if (int err = snd_pcm_hw_params_get_period_size(m_hwParams, &m_periodSize, &dir); err < 0) { printf( "Unable to get period size for playback: %s\n", snd_strerror( err ) ); } dir = 0; - err = snd_pcm_hw_params_set_buffer_size_near( m_handle, m_hwParams, - &m_bufferSize ); - if( err < 0 ) + if (int err = snd_pcm_hw_params_set_buffer_size_near(m_handle, m_hwParams, &m_bufferSize); err < 0) { printf( "Unable to set buffer size %lu for playback: %s\n", m_bufferSize, snd_strerror( err ) ); return ( err ); } - err = snd_pcm_hw_params_get_buffer_size( m_hwParams, &m_bufferSize ); - if( 2 * m_periodSize > m_bufferSize ) + if (int err = snd_pcm_hw_params_get_buffer_size(m_hwParams, &m_bufferSize); 2 * m_periodSize > m_bufferSize) { printf( "buffer to small, could not use\n" ); return ( err ); @@ -472,8 +401,7 @@ int AudioAlsa::setHWParams( const ch_cnt_t _channels, snd_pcm_access_t _access ) // write the parameters to device - err = snd_pcm_hw_params( m_handle, m_hwParams ); - if( err < 0 ) + if (int err = snd_pcm_hw_params(m_handle, m_hwParams); err < 0) { printf( "Unable to set hw params for playback: %s\n", snd_strerror( err ) ); @@ -488,10 +416,8 @@ int AudioAlsa::setHWParams( const ch_cnt_t _channels, snd_pcm_access_t _access ) int AudioAlsa::setSWParams() { - int err; - // get the current swparams - if( ( err = snd_pcm_sw_params_current( m_handle, m_swParams ) ) < 0 ) + if (int err = snd_pcm_sw_params_current(m_handle, m_swParams); err < 0) { printf( "Unable to determine current swparams for playback: %s" "\n", snd_strerror( err ) ); @@ -499,8 +425,7 @@ int AudioAlsa::setSWParams() } // start the transfer when a period is full - if( ( err = snd_pcm_sw_params_set_start_threshold( m_handle, - m_swParams, m_periodSize ) ) < 0 ) + if (int err = snd_pcm_sw_params_set_start_threshold(m_handle, m_swParams, m_periodSize); err < 0) { printf( "Unable to set start threshold mode for playback: %s\n", snd_strerror( err ) ); @@ -509,8 +434,7 @@ int AudioAlsa::setSWParams() // allow the transfer when at least m_periodSize samples can be // processed - if( ( err = snd_pcm_sw_params_set_avail_min( m_handle, m_swParams, - m_periodSize ) ) < 0 ) + if (int err = snd_pcm_sw_params_set_avail_min(m_handle, m_swParams, m_periodSize); err < 0) { printf( "Unable to set avail min for playback: %s\n", snd_strerror( err ) ); @@ -530,7 +454,7 @@ int AudioAlsa::setSWParams() #endif // write the parameters to the playback device - if( ( err = snd_pcm_sw_params( m_handle, m_swParams ) ) < 0 ) + if (int err = snd_pcm_sw_params(m_handle, m_swParams); err < 0) { printf( "Unable to set sw params for playback: %s\n", snd_strerror( err ) ); diff --git a/src/core/audio/AudioDevice.cpp b/src/core/audio/AudioDevice.cpp index 5fb58a1b0..988230d5b 100644 --- a/src/core/audio/AudioDevice.cpp +++ b/src/core/audio/AudioDevice.cpp @@ -34,18 +34,11 @@ namespace lmms AudioDevice::AudioDevice( const ch_cnt_t _channels, AudioEngine* _audioEngine ) : m_supportsCapture( false ), - m_sampleRate( _audioEngine->processingSampleRate() ), + m_sampleRate( _audioEngine->outputSampleRate() ), m_channels( _channels ), m_audioEngine( _audioEngine ), m_buffer( new surroundSampleFrame[audioEngine()->framesPerPeriod()] ) { - int error; - if( ( m_srcState = src_new( - audioEngine()->currentQualitySettings().libsrcInterpolation(), - SURROUND_CHANNELS, &error ) ) == nullptr ) - { - printf( "Error: src_new() failed in audio_device.cpp!\n" ); - } } @@ -53,9 +46,7 @@ AudioDevice::AudioDevice( const ch_cnt_t _channels, AudioEngine* _audioEngine ) AudioDevice::~AudioDevice() { - src_delete( m_srcState ); delete[] m_buffer; - m_devMutex.tryLock(); unlock(); } @@ -66,49 +57,23 @@ AudioDevice::~AudioDevice() void AudioDevice::processNextBuffer() { const fpp_t frames = getNextBuffer( m_buffer ); - if( frames ) - { - writeBuffer( m_buffer, frames, audioEngine()->masterGain() ); - } + if (frames) { writeBuffer(m_buffer, frames); } else { m_inProcess = false; } } - - - -fpp_t AudioDevice::getNextBuffer( surroundSampleFrame * _ab ) +fpp_t AudioDevice::getNextBuffer(surroundSampleFrame* _ab) { fpp_t frames = audioEngine()->framesPerPeriod(); - const surroundSampleFrame * b = audioEngine()->nextBuffer(); - if( !b ) - { - return 0; - } - // make sure, no other thread is accessing device - lock(); + const surroundSampleFrame* b = audioEngine()->nextBuffer(); + if (!b) { return 0; } - // resample if necessary - if( audioEngine()->processingSampleRate() != m_sampleRate ) - { - frames = resample( b, frames, _ab, audioEngine()->processingSampleRate(), m_sampleRate ); - } - else - { - memcpy( _ab, b, frames * sizeof( surroundSampleFrame ) ); - } - - // release lock - unlock(); - - if( audioEngine()->hasFifoWriter() ) - { - delete[] b; - } + memcpy(_ab, b, frames * sizeof(surroundSampleFrame)); + if (audioEngine()->hasFifoWriter()) { delete[] b; } return frames; } @@ -144,23 +109,6 @@ void AudioDevice::stopProcessingThread( QThread * thread ) - -void AudioDevice::applyQualitySettings() -{ - src_delete( m_srcState ); - - int error; - if( ( m_srcState = src_new( - audioEngine()->currentQualitySettings().libsrcInterpolation(), - SURROUND_CHANNELS, &error ) ) == nullptr ) - { - printf( "Error: src_new() failed in audio_device.cpp!\n" ); - } -} - - - - void AudioDevice::registerPort( AudioPort * ) { } @@ -179,51 +127,19 @@ void AudioDevice::renamePort( AudioPort * ) { } - - - -fpp_t AudioDevice::resample( const surroundSampleFrame * _src, - const fpp_t _frames, - surroundSampleFrame * _dst, - const sample_rate_t _src_sr, - const sample_rate_t _dst_sr ) -{ - if( m_srcState == nullptr ) - { - return _frames; - } - m_srcData.input_frames = _frames; - m_srcData.output_frames = _frames; - m_srcData.data_in = const_cast(_src[0].data()); - m_srcData.data_out = _dst[0].data (); - m_srcData.src_ratio = (double) _dst_sr / _src_sr; - m_srcData.end_of_input = 0; - int error; - if( ( error = src_process( m_srcState, &m_srcData ) ) ) - { - printf( "AudioDevice::resample(): error while resampling: %s\n", - src_strerror( error ) ); - } - return static_cast(m_srcData.output_frames_gen); -} - - - int AudioDevice::convertToS16( const surroundSampleFrame * _ab, const fpp_t _frames, - const float _master_gain, int_sample_t * _output_buffer, const bool _convert_endian ) { if( _convert_endian ) { - int_sample_t temp; for( fpp_t frame = 0; frame < _frames; ++frame ) { for( ch_cnt_t chnl = 0; chnl < channels(); ++chnl ) { - temp = static_cast( AudioEngine::clip( _ab[frame][chnl] * _master_gain ) * OUTPUT_SAMPLE_MULTIPLIER ); - + auto temp = static_cast(AudioEngine::clip(_ab[frame][chnl]) * OUTPUT_SAMPLE_MULTIPLIER); + ( _output_buffer + frame * channels() )[chnl] = ( temp & 0x00ff ) << 8 | ( temp & 0xff00 ) >> 8; @@ -236,11 +152,8 @@ int AudioDevice::convertToS16( const surroundSampleFrame * _ab, { for( ch_cnt_t chnl = 0; chnl < channels(); ++chnl ) { - ( _output_buffer + frame * channels() )[chnl] = - static_cast( - AudioEngine::clip( _ab[frame][chnl] * - _master_gain ) * - OUTPUT_SAMPLE_MULTIPLIER ); + (_output_buffer + frame * channels())[chnl] + = static_cast(AudioEngine::clip(_ab[frame][chnl]) * OUTPUT_SAMPLE_MULTIPLIER); } } } @@ -259,13 +172,4 @@ void AudioDevice::clearS16Buffer( int_sample_t * _outbuf, const fpp_t _frames ) memset( _outbuf, 0, _frames * channels() * BYTES_PER_INT_SAMPLE ); } - - - -bool AudioDevice::hqAudio() const -{ - return ConfigManager::inst()->value( "audioengine", "hqaudio" ).toInt(); -} - - } // namespace lmms \ No newline at end of file diff --git a/src/core/audio/AudioFileFlac.cpp b/src/core/audio/AudioFileFlac.cpp index af71003d1..097fbdd89 100644 --- a/src/core/audio/AudioFileFlac.cpp +++ b/src/core/audio/AudioFileFlac.cpp @@ -89,7 +89,7 @@ bool AudioFileFlac::startEncoding() return true; } -void AudioFileFlac::writeBuffer(surroundSampleFrame const* _ab, fpp_t const frames, float master_gain) +void AudioFileFlac::writeBuffer(surroundSampleFrame const* _ab, fpp_t const frames) { OutputSettings::BitDepth depth = getOutputSettings().getBitDepth(); float clipvalue = std::nextafterf( -1.0f, 0.0f ); @@ -104,7 +104,7 @@ void AudioFileFlac::writeBuffer(surroundSampleFrame const* _ab, fpp_t const fram // Clip the negative side to just above -1.0 in order to prevent it from changing sign // Upstream issue: https://github.com/erikd/libsndfile/issues/309 // When this commit is reverted libsndfile-1.0.29 must be made a requirement for FLAC - buf[frame*channels() + channel] = std::max(clipvalue, _ab[frame][channel] * master_gain); + buf[frame*channels() + channel] = std::max(clipvalue, _ab[frame][channel]); } } sf_writef_float(m_sf, static_cast(buf.data()), frames); @@ -112,7 +112,7 @@ void AudioFileFlac::writeBuffer(surroundSampleFrame const* _ab, fpp_t const fram else // integer PCM encoding { auto buf = std::vector(frames * channels()); - convertToS16(_ab, frames, master_gain, buf.data(), !isLittleEndian()); + convertToS16(_ab, frames, buf.data(), !isLittleEndian()); sf_writef_short(m_sf, static_cast(buf.data()), frames); } diff --git a/src/core/audio/AudioFileMP3.cpp b/src/core/audio/AudioFileMP3.cpp index ef0677152..2141fabfc 100644 --- a/src/core/audio/AudioFileMP3.cpp +++ b/src/core/audio/AudioFileMP3.cpp @@ -53,21 +53,18 @@ AudioFileMP3::~AudioFileMP3() tearDownEncoder(); } -void AudioFileMP3::writeBuffer( const surroundSampleFrame * _buf, - const fpp_t _frames, - const float _master_gain ) +void AudioFileMP3::writeBuffer(const surroundSampleFrame* _buf, const fpp_t _frames) { if (_frames < 1) { return; } - // TODO Why isn't the gain applied by the driver but inside the device? std::vector interleavedDataBuffer(_frames * 2); for (fpp_t i = 0; i < _frames; ++i) { - interleavedDataBuffer[2*i] = _buf[i][0] * _master_gain; - interleavedDataBuffer[2*i + 1] = _buf[i][1] * _master_gain; + interleavedDataBuffer[2*i] = _buf[i][0]; + interleavedDataBuffer[2*i + 1] = _buf[i][1]; } size_t minimumBufferSize = 1.25 * _frames + 7200; diff --git a/src/core/audio/AudioFileOgg.cpp b/src/core/audio/AudioFileOgg.cpp index d61e27da8..3818273d6 100644 --- a/src/core/audio/AudioFileOgg.cpp +++ b/src/core/audio/AudioFileOgg.cpp @@ -156,7 +156,6 @@ bool AudioFileOgg::startEncoding() ogg_packet header_main; ogg_packet header_comments; ogg_packet header_codebooks; - int result; // Build the packets vorbis_analysis_headerout( &m_vd, m_comments, &header_main, @@ -167,14 +166,9 @@ bool AudioFileOgg::startEncoding() ogg_stream_packetin( &m_os, &header_comments ); ogg_stream_packetin( &m_os, &header_codebooks ); - while( ( result = ogg_stream_flush( &m_os, &m_og ) ) ) + while (ogg_stream_flush(&m_os, &m_og)) { - if( !result ) - { - break; - } - int ret = writePage(); - if( ret != m_og.header_len + m_og.body_len ) + if (int ret = writePage(); ret != m_og.header_len + m_og.body_len) { // clean up finishEncoding(); @@ -185,12 +179,7 @@ bool AudioFileOgg::startEncoding() return true; } - - - -void AudioFileOgg::writeBuffer( const surroundSampleFrame * _ab, - const fpp_t _frames, - const float _master_gain ) +void AudioFileOgg::writeBuffer(const surroundSampleFrame* _ab, const fpp_t _frames) { int eos = 0; @@ -201,7 +190,7 @@ void AudioFileOgg::writeBuffer( const surroundSampleFrame * _ab, { for( ch_cnt_t chnl = 0; chnl < channels(); ++chnl ) { - buffer[chnl][frame] = _ab[frame][chnl] * _master_gain; + buffer[chnl][frame] = _ab[frame][chnl]; } } @@ -258,7 +247,7 @@ void AudioFileOgg::finishEncoding() if( m_ok ) { // just for flushing buffers... - writeBuffer( nullptr, 0, 0.0f ); + writeBuffer(nullptr, 0); // clean up ogg_stream_clear( &m_os ); diff --git a/src/core/audio/AudioFileWave.cpp b/src/core/audio/AudioFileWave.cpp index 9c51437ff..612b98982 100644 --- a/src/core/audio/AudioFileWave.cpp +++ b/src/core/audio/AudioFileWave.cpp @@ -93,12 +93,7 @@ bool AudioFileWave::startEncoding() return true; } - - - -void AudioFileWave::writeBuffer( const surroundSampleFrame * _ab, - const fpp_t _frames, - const float _master_gain ) +void AudioFileWave::writeBuffer(const surroundSampleFrame* _ab, const fpp_t _frames) { OutputSettings::BitDepth bitDepth = getOutputSettings().getBitDepth(); @@ -109,8 +104,7 @@ void AudioFileWave::writeBuffer( const surroundSampleFrame * _ab, { for( ch_cnt_t chnl = 0; chnl < channels(); ++chnl ) { - buf[frame*channels()+chnl] = _ab[frame][chnl] * - _master_gain; + buf[frame * channels() + chnl] = _ab[frame][chnl]; } } sf_writef_float( m_sf, buf, _frames ); @@ -119,8 +113,7 @@ void AudioFileWave::writeBuffer( const surroundSampleFrame * _ab, else { auto buf = new int_sample_t[_frames * channels()]; - convertToS16( _ab, _frames, _master_gain, buf, - !isLittleEndian() ); + convertToS16(_ab, _frames, buf, !isLittleEndian()); sf_writef_short( m_sf, buf, _frames ); delete[] buf; diff --git a/src/core/audio/AudioJack.cpp b/src/core/audio/AudioJack.cpp index 83fa3c177..4d27602ef 100644 --- a/src/core/audio/AudioJack.cpp +++ b/src/core/audio/AudioJack.cpp @@ -30,42 +30,42 @@ #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" - 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 +73,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 +97,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() ) + for (ch_cnt_t ch = 0; ch < channels(); ++ch) { - setSampleRate( jack_get_sample_rate( m_client ) ); - } - - 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 +182,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); } @@ -258,127 +229,91 @@ void AudioJack::stopProcessing() m_stopped = true; } - - - -void AudioJack::applyQualitySettings() -{ - if( hqAudio() ) - { - setSampleRate( Engine::audioEngine()->processingSampleRate() ); - - if( jack_get_sample_rate( m_client ) != sampleRate() ) - { - setSampleRate( jack_get_sample_rate( m_client ) ); - } - } - - AudioDevice::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 +322,24 @@ 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); - const float gain = audioEngine()->masterGain(); - for( int c = 0; c < channels(); ++c ) + jack_nframes_t todo = std::min(nframes - done, m_framesToDoInCurBuf - m_framesDoneInCurBuf); + 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]; } } 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 +347,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,51 +362,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) { QFormLayout * form = new QFormLayout(this); - QString cn = ConfigManager::inst()->value( "audiojack", "clientname" ); - if( cn.isEmpty() ) - { - cn = "lmms"; - } - m_clientName = new QLineEdit( cn, this ); + 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 = new gui::LcdSpinBox(1, this); + m_channels->setModel(m); form->addRow(tr("Channels"), m_channels); - } @@ -491,14 +415,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 8fedd3b2b..e18260d61 100644 --- a/src/core/audio/AudioOss.cpp +++ b/src/core/audio/AudioOss.cpp @@ -34,7 +34,6 @@ #include "LcdSpinBox.h" #include "AudioEngine.h" #include "Engine.h" -#include "gui_templates.h" #ifdef LMMS_HAVE_UNISTD_H #include @@ -255,41 +254,6 @@ void AudioOss::stopProcessing() stopProcessingThread( this ); } - - - -void AudioOss::applyQualitySettings() -{ - if( hqAudio() ) - { - setSampleRate( Engine::audioEngine()->processingSampleRate() ); - - unsigned int value = sampleRate(); - if ( ioctl( m_audioFD, SNDCTL_DSP_SPEED, &value ) < 0 ) - { - perror( "SNDCTL_DSP_SPEED" ); - printf( "Couldn't set audio frequency\n" ); - return; - } - if( value != sampleRate() ) - { - value = audioEngine()->baseSampleRate(); - if ( ioctl( m_audioFD, SNDCTL_DSP_SPEED, &value ) < 0 ) - { - perror( "SNDCTL_DSP_SPEED" ); - printf( "Couldn't set audio frequency\n" ); - return; - } - setSampleRate( value ); - } - } - - AudioDevice::applyQualitySettings(); -} - - - - void AudioOss::run() { auto temp = new surroundSampleFrame[audioEngine()->framesPerPeriod()]; @@ -303,7 +267,7 @@ void AudioOss::run() break; } - int bytes = convertToS16( temp, frames, audioEngine()->masterGain(), outbuf, m_convertEndian ); + int bytes = convertToS16(temp, frames, outbuf, m_convertEndian); if( write( m_audioFD, outbuf, bytes ) != bytes ) { break; diff --git a/src/core/audio/AudioPortAudio.cpp b/src/core/audio/AudioPortAudio.cpp index 3684a79a8..bc5d7dd9d 100644 --- a/src/core/audio/AudioPortAudio.cpp +++ b/src/core/audio/AudioPortAudio.cpp @@ -53,7 +53,6 @@ void AudioPortAudioSetupUtil::updateChannels() #include "Engine.h" #include "ConfigManager.h" -#include "gui_templates.h" #include "ComboBox.h" #include "AudioEngine.h" @@ -93,10 +92,9 @@ AudioPortAudio::AudioPortAudio( bool & _success_ful, AudioEngine * _audioEngine PaDeviceIndex inDevIdx = -1; PaDeviceIndex outDevIdx = -1; - const PaDeviceInfo * di; for( int i = 0; i < Pa_GetDeviceCount(); ++i ) { - di = Pa_GetDeviceInfo( i ); + const auto di = Pa_GetDeviceInfo(i); if( di->name == device && Pa_GetHostApiInfo( di->hostApi )->name == backend ) { @@ -231,38 +229,6 @@ void AudioPortAudio::stopProcessing() } - - -void AudioPortAudio::applyQualitySettings() -{ - if( hqAudio() ) - { - - setSampleRate( Engine::audioEngine()->processingSampleRate() ); - int samples = audioEngine()->framesPerPeriod(); - - PaError err = Pa_OpenStream( - &m_paStream, - supportsCapture() ? &m_inputParameters : nullptr, // The input parameter - &m_outputParameters, // The outputparameter - sampleRate(), - samples, - paNoFlag, // Don't use any flags - _process_callback, // our callback function - this ); - - if( err != paNoError ) - { - printf( "Couldn't open PortAudio: %s\n", Pa_GetErrorText( err ) ); - return; - } - } - - AudioDevice::applyQualitySettings(); -} - - - int AudioPortAudio::process_callback( const float *_inputBuffer, float * _outputBuffer, @@ -298,15 +264,11 @@ int AudioPortAudio::process_callback( const int min_len = std::min(static_cast(_framesPerBuffer), m_outBufSize - m_outBufPos); - float master_gain = audioEngine()->masterGain(); - for( fpp_t frame = 0; frame < min_len; ++frame ) { for( ch_cnt_t chnl = 0; chnl < channels(); ++chnl ) { - ( _outputBuffer + frame * channels() )[chnl] = - AudioEngine::clip( m_outBuf[frame][chnl] * - master_gain ); + (_outputBuffer + frame * channels())[chnl] = AudioEngine::clip(m_outBuf[frame][chnl]); } } @@ -348,10 +310,9 @@ void AudioPortAudioSetupUtil::updateBackends() return; } - const PaHostApiInfo * hi; for( int i = 0; i < Pa_GetHostApiCount(); ++i ) { - hi = Pa_GetHostApiInfo( i ); + const auto hi = Pa_GetHostApiInfo(i); m_backendModel.addItem( hi->name ); } @@ -372,10 +333,9 @@ void AudioPortAudioSetupUtil::updateDevices() // get active backend const QString& backend = m_backendModel.currentText(); int hostApi = 0; - const PaHostApiInfo * hi; for( int i = 0; i < Pa_GetHostApiCount(); ++i ) { - hi = Pa_GetHostApiInfo( i ); + const auto hi = Pa_GetHostApiInfo(i); if( backend == hi->name ) { hostApi = i; @@ -385,10 +345,9 @@ void AudioPortAudioSetupUtil::updateDevices() // get devices for selected backend m_deviceModel.clear(); - const PaDeviceInfo * di; for( int i = 0; i < Pa_GetDeviceCount(); ++i ) { - di = Pa_GetDeviceInfo( i ); + const auto di = Pa_GetDeviceInfo(i); if( di->hostApi == hostApi ) { m_deviceModel.addItem( di->name ); diff --git a/src/core/audio/AudioPulseAudio.cpp b/src/core/audio/AudioPulseAudio.cpp index 3ca8764cc..b32e6eaf2 100644 --- a/src/core/audio/AudioPulseAudio.cpp +++ b/src/core/audio/AudioPulseAudio.cpp @@ -32,7 +32,6 @@ #include "ConfigManager.h" #include "LcdSpinBox.h" #include "AudioEngine.h" -#include "gui_templates.h" #include "Engine.h" namespace lmms @@ -110,22 +109,6 @@ void AudioPulseAudio::stopProcessing() } - - -void AudioPulseAudio::applyQualitySettings() -{ - if( hqAudio() ) - { -// setSampleRate( engine::audioEngine()->processingSampleRate() ); - - } - - AudioDevice::applyQualitySettings(); -} - - - - /* This routine is called whenever the stream state changes */ static void stream_state_callback( pa_stream *s, void * userdata ) { @@ -278,10 +261,7 @@ void AudioPulseAudio::streamWriteCallback( pa_stream *s, size_t length ) m_quit = true; break; } - int bytes = convertToS16( temp, frames, - audioEngine()->masterGain(), - pcmbuf, - m_convertEndian ); + int bytes = convertToS16(temp, frames, pcmbuf, m_convertEndian); if( bytes > 0 ) { pa_stream_write( m_s, pcmbuf, bytes, nullptr, 0, diff --git a/src/core/audio/AudioSampleRecorder.cpp b/src/core/audio/AudioSampleRecorder.cpp index f60248c50..c9448b89e 100644 --- a/src/core/audio/AudioSampleRecorder.cpp +++ b/src/core/audio/AudioSampleRecorder.cpp @@ -67,39 +67,25 @@ f_cnt_t AudioSampleRecorder::framesRecorded() const return frames; } - - - -void AudioSampleRecorder::createSampleBuffer( SampleBuffer** sampleBuf ) +std::shared_ptr AudioSampleRecorder::createSampleBuffer() { const f_cnt_t frames = framesRecorded(); // create buffer to store all recorded buffers in - auto data = new sampleFrame[frames]; - // make sure buffer is cleaned up properly at the end... - sampleFrame * data_ptr = data; - - - assert( data != nullptr ); + auto bigBuffer = std::vector(frames); // now copy all buffers into big buffer - for( BufferList::ConstIterator it = m_buffers.begin(); - it != m_buffers.end(); ++it ) + auto framesCopied = 0; + for (const auto& [buf, numFrames] : m_buffers) { - memcpy( data_ptr, ( *it ).first, ( *it ).second * - sizeof( sampleFrame ) ); - data_ptr += ( *it ).second; + std::copy_n(buf, numFrames, bigBuffer.begin() + framesCopied); + framesCopied += numFrames; } + // create according sample-buffer out of big buffer - *sampleBuf = new SampleBuffer( data, frames ); - ( *sampleBuf )->setSampleRate( sampleRate() ); - delete[] data; + return std::make_shared(std::move(bigBuffer), sampleRate()); } - - - -void AudioSampleRecorder::writeBuffer( const surroundSampleFrame * _ab, - const fpp_t _frames, const float ) +void AudioSampleRecorder::writeBuffer(const surroundSampleFrame* _ab, const fpp_t _frames) { auto buf = new sampleFrame[_frames]; for( fpp_t frame = 0; frame < _frames; ++frame ) diff --git a/src/core/audio/AudioSdl.cpp b/src/core/audio/AudioSdl.cpp index 12aa97d63..0d960c107 100644 --- a/src/core/audio/AudioSdl.cpp +++ b/src/core/audio/AudioSdl.cpp @@ -32,7 +32,6 @@ #include "AudioEngine.h" #include "ConfigManager.h" -#include "gui_templates.h" namespace lmms { @@ -192,37 +191,6 @@ void AudioSdl::stopProcessing() } } - - - -void AudioSdl::applyQualitySettings() -{ - // Better than if (0) -#if 0 - if( 0 )//hqAudio() ) - { - SDL_CloseAudio(); - - setSampleRate( Engine::audioEngine()->processingSampleRate() ); - - m_audioHandle.freq = sampleRate(); - - SDL_AudioSpec actual; - - // open the audio device, forcing the desired format - if( SDL_OpenAudio( &m_audioHandle, &actual ) < 0 ) - { - qCritical( "Couldn't open SDL-audio: %s\n", SDL_GetError() ); - } - } -#endif - - AudioDevice::applyQualitySettings(); -} - - - - void AudioSdl::sdlAudioCallback( void * _udata, Uint8 * _buf, int _len ) { auto _this = static_cast(_udata); @@ -261,13 +229,6 @@ void AudioSdl::sdlAudioCallback( Uint8 * _buf, int _len ) m_currentBufferFramesCount - m_currentBufferFramePos); - const float gain = audioEngine()->masterGain(); - for (uint f = 0; f < min_frames_count; f++) - { - (m_outBuf + m_currentBufferFramePos)[f][0] *= gain; - (m_outBuf + m_currentBufferFramePos)[f][1] *= gain; - } - memcpy( _buf, m_outBuf + m_currentBufferFramePos, min_frames_count*sizeof(sampleFrame) ); _buf += min_frames_count*sizeof(sampleFrame); _len -= min_frames_count*sizeof(sampleFrame); @@ -291,10 +252,7 @@ void AudioSdl::sdlAudioCallback( Uint8 * _buf, int _len ) m_convertedBufSize = frames * channels() * sizeof( int_sample_t ); - convertToS16( m_outBuf, frames, - audioEngine()->masterGain(), - (int_sample_t *)m_convertedBuf, - m_outConvertEndian ); + convertToS16(m_outBuf, frames, reinterpret_cast(m_convertedBuf), m_outConvertEndian); } const int min_len = std::min(_len, m_convertedBufSize - m_convertedBufPos); diff --git a/src/core/audio/AudioSndio.cpp b/src/core/audio/AudioSndio.cpp index bb9b249f8..535b885da 100644 --- a/src/core/audio/AudioSndio.cpp +++ b/src/core/audio/AudioSndio.cpp @@ -35,7 +35,6 @@ #include "LcdSpinBox.h" #include "AudioEngine.h" #include "Engine.h" -#include "gui_templates.h" #include "ConfigManager.h" @@ -140,20 +139,6 @@ void AudioSndio::stopProcessing() stopProcessingThread( this ); } - -void AudioSndio::applyQualitySettings() -{ - if( hqAudio() ) - { - setSampleRate( Engine::audioEngine()->processingSampleRate() ); - - /* change sample rate to sampleRate() */ - } - - AudioDevice::applyQualitySettings(); -} - - void AudioSndio::run() { surroundSampleFrame * temp = new surroundSampleFrame[audioEngine()->framesPerPeriod()]; @@ -167,8 +152,7 @@ void AudioSndio::run() break; } - uint bytes = convertToS16( temp, frames, - audioEngine()->masterGain(), outbuf, m_convertEndian ); + uint bytes = convertToS16(temp, frames, outbuf, m_convertEndian); if( sio_write( m_hdl, outbuf, bytes ) != bytes ) { break; diff --git a/src/core/audio/AudioSoundIo.cpp b/src/core/audio/AudioSoundIo.cpp index 36a1929df..c16327a90 100644 --- a/src/core/audio/AudioSoundIo.cpp +++ b/src/core/audio/AudioSoundIo.cpp @@ -32,7 +32,6 @@ #include "Engine.h" #include "debug.h" #include "ConfigManager.h" -#include "gui_templates.h" #include "ComboBox.h" #include "AudioEngine.h" @@ -70,7 +69,6 @@ AudioSoundIo::AudioSoundIo( bool & outSuccessful, AudioEngine * _audioEngine ) : const QString& configDeviceId = ConfigManager::inst()->value( "audiosoundio", "out_device_id" ); const QString& configDeviceRaw = ConfigManager::inst()->value( "audiosoundio", "out_device_raw" ); - int err; int outDeviceCount = 0; int backendCount = soundio_backend_count(m_soundio); for (int i = 0; i < backendCount; i += 1) @@ -78,11 +76,7 @@ AudioSoundIo::AudioSoundIo( bool & outSuccessful, AudioEngine * _audioEngine ) : SoundIoBackend backend = soundio_get_backend(m_soundio, i); if (configBackend == soundio_backend_name(backend)) { - if ((err = soundio_connect_backend(m_soundio, backend))) - { - // error occurred, leave outDeviceCount 0 - } - else + if (!soundio_connect_backend(m_soundio, backend)) { soundio_flush_events(m_soundio); if (m_disconnectErr) @@ -99,7 +93,7 @@ AudioSoundIo::AudioSoundIo( bool & outSuccessful, AudioEngine * _audioEngine ) : if (outDeviceCount <= 0) { // try connecting to the default backend - if ((err = soundio_connect(m_soundio))) + if (int err = soundio_connect(m_soundio)) { fprintf(stderr, "Unable to initialize soundio: %s\n", soundio_strerror(err)); return; @@ -180,7 +174,7 @@ AudioSoundIo::AudioSoundIo( bool & outSuccessful, AudioEngine * _audioEngine ) : m_outstream->layout = *soundio_channel_layout_get_default(channels()); m_outstream->format = SoundIoFormatFloat32NE; - if ((err = soundio_outstream_open(m_outstream))) + if (int err = soundio_outstream_open(m_outstream)) { fprintf(stderr, "Unable to initialize soundio: %s\n", soundio_strerror(err)); return; @@ -215,8 +209,6 @@ AudioSoundIo::~AudioSoundIo() void AudioSoundIo::startProcessing() { - int err; - m_outBufFrameIndex = 0; m_outBufFramesTotal = 0; m_outBufSize = audioEngine()->framesPerPeriod(); @@ -225,7 +217,7 @@ void AudioSoundIo::startProcessing() if (! m_outstreamStarted) { - if ((err = soundio_outstream_start(m_outstream))) + if (int err = soundio_outstream_start(m_outstream)) { fprintf(stderr, "AudioSoundIo::startProcessing() :: soundio unable to start stream: %s\n", @@ -237,7 +229,7 @@ void AudioSoundIo::startProcessing() m_stopped = false; - if ((err = soundio_outstream_pause(m_outstream, false))) + if (int err = soundio_outstream_pause(m_outstream, false)) { m_stopped = true; fprintf(stderr, @@ -248,12 +240,10 @@ void AudioSoundIo::startProcessing() void AudioSoundIo::stopProcessing() { - int err; - m_stopped = true; if (m_outstream) { - if ((err = soundio_outstream_pause(m_outstream, true))) + if (int err = soundio_outstream_pause(m_outstream, true)) { fprintf(stderr, "AudioSoundIo::stopProcessing() :: pausing result error: %s\n", @@ -282,18 +272,14 @@ void AudioSoundIo::writeCallback(int frameCountMin, int frameCountMax) { if (m_stopped) {return;} const struct SoundIoChannelLayout *layout = &m_outstream->layout; - SoundIoChannelArea *areas; + SoundIoChannelArea* areas; int bytesPerSample = m_outstream->bytes_per_sample; - int err; - - const float gain = audioEngine()->masterGain(); - int framesLeft = frameCountMax; while (framesLeft > 0) { int frameCount = framesLeft; - if ((err = soundio_outstream_begin_write(m_outstream, &areas, &frameCount))) + if (int err = soundio_outstream_begin_write(m_outstream, &areas, &frameCount)) { errorCallback(err); return; @@ -328,14 +314,14 @@ void AudioSoundIo::writeCallback(int frameCountMin, int frameCountMax) for (int channel = 0; channel < layout->channel_count; channel += 1) { - float sample = gain * m_outBuf[m_outBufFrameIndex][channel]; + float sample = m_outBuf[m_outBufFrameIndex][channel]; memcpy(areas[channel].ptr, &sample, bytesPerSample); areas[channel].ptr += areas[channel].step; } m_outBufFrameIndex += 1; } - if ((err = soundio_outstream_end_write(m_outstream))) + if (int err = soundio_outstream_end_write(m_outstream)) { errorCallback(err); return; @@ -375,11 +361,10 @@ void AudioSoundIo::setupWidget::reconnectSoundIo() soundio_disconnect(m_soundio); - int err; int backend_index = m_backendModel.findText(configBackend); if (backend_index < 0) { - if ((err = soundio_connect(m_soundio))) + if (int err = soundio_connect(m_soundio)) { fprintf(stderr, "soundio: unable to connect backend: %s\n", soundio_strerror(err)); return; @@ -390,11 +375,11 @@ void AudioSoundIo::setupWidget::reconnectSoundIo() else { SoundIoBackend backend = soundio_get_backend(m_soundio, backend_index); - if ((err = soundio_connect_backend(m_soundio, backend))) + if (int err = soundio_connect_backend(m_soundio, backend)) { fprintf(stderr, "soundio: unable to connect %s backend: %s\n", soundio_backend_name(backend), soundio_strerror(err)); - if ((err = soundio_connect(m_soundio))) + if (int err = soundio_connect(m_soundio)) { fprintf(stderr, "soundio: unable to connect backend: %s\n", soundio_strerror(err)); return; diff --git a/src/core/lv2/Lv2ControlBase.cpp b/src/core/lv2/Lv2ControlBase.cpp index 64cdc51fd..5741866e9 100644 --- a/src/core/lv2/Lv2ControlBase.cpp +++ b/src/core/lv2/Lv2ControlBase.cpp @@ -59,7 +59,7 @@ Lv2ControlBase::Lv2ControlBase(Model* that, const QString &uri) : else { qCritical() << "No Lv2 plugin found for URI" << uri; - m_valid = false; + throw std::runtime_error("No Lv2 plugin found for given URI"); } } @@ -77,26 +77,14 @@ void Lv2ControlBase::init(Model* meAsModel) while (channelsLeft > 0) { std::unique_ptr newOne = std::make_unique(m_plugin, meAsModel); - if (newOne->isValid()) - { - channelsLeft -= std::max( - 1 + static_cast(newOne->inPorts().m_right), - 1 + static_cast(newOne->outPorts().m_right)); - Q_ASSERT(channelsLeft >= 0); - m_procs.push_back(std::move(newOne)); - } - else - { - qCritical() << "Failed instantiating LV2 processor"; - m_valid = false; - channelsLeft = 0; - } - } - if (m_valid) - { - m_channelsPerProc = DEFAULT_CHANNELS / m_procs.size(); - linkAllModels(); + channelsLeft -= std::max( + 1 + static_cast(newOne->inPorts().m_right), + 1 + static_cast(newOne->outPorts().m_right)); + Q_ASSERT(channelsLeft >= 0); + m_procs.push_back(std::move(newOne)); } + m_channelsPerProc = DEFAULT_CHANNELS / m_procs.size(); + linkAllModels(); } diff --git a/src/core/lv2/Lv2Evbuf.cpp b/src/core/lv2/Lv2Evbuf.cpp index acfb9b8aa..486910fee 100644 --- a/src/core/lv2/Lv2Evbuf.cpp +++ b/src/core/lv2/Lv2Evbuf.cpp @@ -34,7 +34,7 @@ #include #include -#include +#include namespace lmms { @@ -129,12 +129,11 @@ lv2_evbuf_next(LV2_Evbuf_Iterator iter) LV2_Evbuf* evbuf = iter.evbuf; uint32_t offset = iter.offset; - uint32_t size; - size = ((LV2_Atom_Event*) - ((char*)LV2_ATOM_CONTENTS(LV2_Atom_Sequence, &evbuf->buf.atom) - + offset))->body.size; - offset += lv2_evbuf_pad_size(sizeof(LV2_Atom_Event) + size); + const auto contents = static_cast(LV2_ATOM_CONTENTS(LV2_Atom_Sequence, &evbuf->buf.atom)) + offset; + const uint32_t size = reinterpret_cast(contents)->body.size; + + offset += lv2_evbuf_pad_size(sizeof(LV2_Atom_Event) + size); LV2_Evbuf_Iterator next = { evbuf, offset }; return next; } diff --git a/src/core/lv2/Lv2Features.cpp b/src/core/lv2/Lv2Features.cpp index c8fc05465..25ee11544 100644 --- a/src/core/lv2/Lv2Features.cpp +++ b/src/core/lv2/Lv2Features.cpp @@ -109,7 +109,12 @@ void *&Lv2Features::operator[](const char *featName) void Lv2Features::clear() { - m_featureByUri.clear(); + m_features.clear(); + for (auto& [uri, feature] : m_featureByUri) + { + (void) uri; + feature = nullptr; + } } diff --git a/src/core/lv2/Lv2Manager.cpp b/src/core/lv2/Lv2Manager.cpp index 6a1b2a8af..1633b8626 100644 --- a/src/core/lv2/Lv2Manager.cpp +++ b/src/core/lv2/Lv2Manager.cpp @@ -29,9 +29,9 @@ #include #include #include -#include -#include -#include +#include +#include +#include #include #include diff --git a/src/core/lv2/Lv2Options.cpp b/src/core/lv2/Lv2Options.cpp index 36281ee63..7b4528cb6 100644 --- a/src/core/lv2/Lv2Options.cpp +++ b/src/core/lv2/Lv2Options.cpp @@ -94,6 +94,16 @@ void Lv2Options::initOption(LV2_URID key, uint32_t size, LV2_URID type, } + + +void Lv2Options::clear() +{ + m_options.clear(); + m_optionValues.clear(); + m_optionByUrid.clear(); +} + + } // namespace lmms #endif // LMMS_HAVE_LV2 diff --git a/src/core/lv2/Lv2Ports.cpp b/src/core/lv2/Lv2Ports.cpp index a4625936e..657046817 100644 --- a/src/core/lv2/Lv2Ports.cpp +++ b/src/core/lv2/Lv2Ports.cpp @@ -27,8 +27,8 @@ #ifdef LMMS_HAVE_LV2 -#include -#include +#include +#include #include "Engine.h" #include "Lv2Basics.h" diff --git a/src/core/lv2/Lv2Proc.cpp b/src/core/lv2/Lv2Proc.cpp index 11290013e..7dfdbc767 100644 --- a/src/core/lv2/Lv2Proc.cpp +++ b/src/core/lv2/Lv2Proc.cpp @@ -27,10 +27,10 @@ #ifdef LMMS_HAVE_LV2 #include -#include -#include -#include -#include +#include +#include +#include +#include #include #include #include @@ -45,6 +45,7 @@ #include "Lv2Evbuf.h" #include "MidiEvent.h" #include "MidiEventToByteSeq.h" +#include "NoCopyNoMove.h" namespace lmms @@ -168,6 +169,27 @@ Plugin::Type Lv2Proc::check(const LilvPlugin *plugin, +class Lv2ProcSuspender : NoCopyNoMove +{ +public: + Lv2ProcSuspender(Lv2Proc* proc) + : m_proc(proc) + , m_wasActive(proc->m_instance) + { + if (m_wasActive) { m_proc->shutdownPlugin(); } + } + ~Lv2ProcSuspender() + { + if (m_wasActive) { m_proc->initPlugin(); } + } +private: + Lv2Proc* const m_proc; + const bool m_wasActive; +}; + + + + Lv2Proc::Lv2Proc(const LilvPlugin *plugin, Model* parent) : LinkedModelGroup(parent), m_plugin(plugin), @@ -175,6 +197,7 @@ Lv2Proc::Lv2Proc(const LilvPlugin *plugin, Model* parent) : m_midiInputBuf(m_maxMidiInputEvents), m_midiInputReader(m_midiInputBuf) { + createPorts(); initPlugin(); } @@ -186,22 +209,7 @@ Lv2Proc::~Lv2Proc() { shutdownPlugin(); } -void Lv2Proc::reload() -{ - // save controls, which we want to keep - QDomDocument doc; - QDomElement controls = doc.createElement("controls"); - saveValues(doc, controls); - // backup construction variables - const LilvPlugin* plugin = m_plugin; - Model* parent = Model::parentModel(); - // destroy everything using RAII ... - this->~Lv2Proc(); - // ... and reuse it ("placement new") - new (this) Lv2Proc(plugin, parent); - // reload the controls - loadValues(controls); -} +void Lv2Proc::reload() { Lv2ProcSuspender(this); } @@ -417,12 +425,8 @@ void Lv2Proc::handleMidiInputEvent(const MidiEvent &event, const TimePos &time, AutomatableModel *Lv2Proc::modelAtPort(const QString &uri) { - // unused currently - AutomatableModel *mod; - auto itr = m_connectedModels.find(uri.toUtf8().data()); - if (itr != m_connectedModels.end()) { mod = itr->second; } - else { mod = nullptr; } - return mod; + const auto itr = m_connectedModels.find(uri.toUtf8().data()); + return itr != m_connectedModels.end() ? itr->second : nullptr; } @@ -434,19 +438,23 @@ void Lv2Proc::initPlugin() initPluginSpecificFeatures(); m_features.createFeatureVectors(); - createPorts(); - m_instance = lilv_plugin_instantiate(m_plugin, - Engine::audioEngine()->processingSampleRate(), + Engine::audioEngine()->outputSampleRate(), m_features.featurePointers()); 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 @@ -456,7 +464,7 @@ void Lv2Proc::initPlugin() << "(URI:" << lilv_node_as_uri(lilv_plugin_get_uri(m_plugin)) << ")"; - m_valid = false; + throw std::runtime_error("Failed to create Lv2 processor"); } } @@ -465,15 +473,12 @@ void Lv2Proc::initPlugin() void Lv2Proc::shutdownPlugin() { - if (m_valid) - { - lilv_instance_deactivate(m_instance); - lilv_instance_free(m_instance); - m_instance = nullptr; + lilv_instance_deactivate(m_instance); + lilv_instance_free(m_instance); + m_instance = nullptr; - m_features.clear(); - } - m_valid = true; + m_features.clear(); + m_options.clear(); } @@ -503,7 +508,7 @@ void Lv2Proc::initMOptions() re-initialize, and this code section will be executed again, creating a new option vector. */ - float sampleRate = Engine::audioEngine()->processingSampleRate(); + float sampleRate = Engine::audioEngine()->outputSampleRate(); int32_t blockLength = Engine::audioEngine()->framesPerPeriod(); int32_t sequenceSize = defaultEvbufSize(); @@ -527,13 +532,12 @@ 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)); + if (lilv_plugin_has_extension_data(m_plugin, mgr->uri(LV2_WORKER__interface).get())) + { 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() } } @@ -566,7 +570,7 @@ void Lv2Proc::createPort(std::size_t portNum) { AutoLilvNode node(lilv_port_get_name(m_plugin, lilvPort)); QString dispName = lilv_node_as_string(node.get()); - sample_rate_t sr = Engine::audioEngine()->processingSampleRate(); + sample_rate_t sr = Engine::audioEngine()->outputSampleRate(); if(meta.def() < meta.min(sr) || meta.def() > meta.max(sr)) { qWarning() << "Warning: Plugin" @@ -683,7 +687,8 @@ void Lv2Proc::createPort(std::size_t portNum) AutoLilvNode rszMinimumSize = mgr->uri(LV2_RESIZE_PORT__minimumSize); AutoLilvNodes minSizeV(lilv_port_get_value(m_plugin, lilvPort, rszMinimumSize.get())); LilvNode* minSize = minSizeV ? lilv_nodes_get_first(minSizeV.get()) : nullptr; - if (minSize && lilv_node_is_int(minSize)) { + if (minSize && lilv_node_is_int(minSize)) + { minimumSize = std::max(minimumSize, lilv_node_as_int(minSize)); } } @@ -845,7 +850,8 @@ void Lv2Proc::dumpPort(std::size_t num) { struct DumpPortDetail : public Lv2Ports::ConstVisitor { - void visit(const Lv2Ports::Control& ctrl) override { + void visit(const Lv2Ports::Control& ctrl) override + { qDebug() << " control port"; // output ports may be uninitialized yet, only print inputs if (ctrl.m_flow == Lv2Ports::Flow::Input) @@ -853,7 +859,8 @@ void Lv2Proc::dumpPort(std::size_t num) qDebug() << " value:" << ctrl.m_val; } } - void visit(const Lv2Ports::Audio& audio) override { + void visit(const Lv2Ports::Audio& audio) override + { qDebug() << (audio.isSideChain() ? " audio port (sidechain)" : " audio port"); qDebug() << " buffer size:" << audio.bufferSize(); @@ -869,7 +876,7 @@ void Lv2Proc::dumpPort(std::size_t num) qDebug() << " visualization: " << Lv2Ports::toStr(port.m_vis); if (port.m_type == Lv2Ports::Type::Control || port.m_type == Lv2Ports::Type::Cv) { - sample_rate_t sr = Engine::audioEngine()->processingSampleRate(); + sample_rate_t sr = Engine::audioEngine()->outputSampleRate(); qDebug() << " default:" << port.def(); qDebug() << " min:" << port.min(sr); qDebug() << " max:" << port.max(sr); diff --git a/src/core/lv2/Lv2UridCache.cpp b/src/core/lv2/Lv2UridCache.cpp index 746878afb..7d3a14c93 100644 --- a/src/core/lv2/Lv2UridCache.cpp +++ b/src/core/lv2/Lv2UridCache.cpp @@ -26,10 +26,10 @@ #ifdef LMMS_HAVE_LV2 -#include -#include -#include -#include +#include +#include +#include +#include #include #include "Lv2UridMap.h" diff --git a/src/core/lv2/Lv2Worker.cpp b/src/core/lv2/Lv2Worker.cpp index 5af955ff7..c763bacad 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/core/main.cpp b/src/core/main.cpp index 25a6ab9c5..650ceab57 100644 --- a/src/core/main.cpp +++ b/src/core/main.cpp @@ -210,7 +210,6 @@ void printHelp() " -p, --profile Dump profiling information to file \n" " -s, --samplerate Specify output samplerate in Hz\n" " Range: 44100 (default) to 192000\n" - " -x, --oversampling Specify oversampling\n" " Possible values: 1, 2, 4, 8\n" " Default: 2\n\n", LMMS_VERSION, LMMS_PROJECT_COPYRIGHT ); @@ -356,14 +355,12 @@ int main( int argc, char * * argv ) // don't let OS steal the menu bar. FIXME: only effective on Qt4 QCoreApplication::setAttribute( Qt::AA_DontUseNativeMenuBar ); #endif -#if QT_VERSION >= QT_VERSION_CHECK(5, 6, 0) QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); -#endif QCoreApplication * app = coreOnly ? new QCoreApplication( argc, argv ) : new gui::MainApplication(argc, argv); - AudioEngine::qualitySettings qs( AudioEngine::qualitySettings::Mode::HighQuality ); + AudioEngine::qualitySettings qs(AudioEngine::qualitySettings::Interpolation::Linear); OutputSettings os( 44100, OutputSettings::BitRateSettings(160, false), OutputSettings::BitDepth::Depth16Bit, OutputSettings::StereoMode::JointStereo ); ProjectRenderer::ExportFileFormat eff = ProjectRenderer::ExportFileFormat::Wave; @@ -648,36 +645,6 @@ int main( int argc, char * * argv ) return usageError( QString( "Invalid interpolation method %1" ).arg( argv[i] ) ); } } - else if( arg == "--oversampling" || arg == "-x" ) - { - ++i; - - if( i == argc ) - { - return usageError( "No oversampling specified" ); - } - - - int o = QString( argv[i] ).toUInt(); - - switch( o ) - { - case 1: - qs.oversampling = AudioEngine::qualitySettings::Oversampling::None; - break; - case 2: - qs.oversampling = AudioEngine::qualitySettings::Oversampling::X2; - break; - case 4: - qs.oversampling = AudioEngine::qualitySettings::Oversampling::X4; - break; - case 8: - qs.oversampling = AudioEngine::qualitySettings::Oversampling::X8; - break; - default: - return usageError( QString( "Invalid oversampling %1" ).arg( argv[i] ) ); - } - } else if( arg == "--import" ) { ++i; @@ -906,19 +873,13 @@ int main( int argc, char * * argv ) mb.setWindowIcon( embed::getIconPixmap( "icon_small" ) ); mb.setWindowFlags( Qt::WindowCloseButtonHint ); - QPushButton * recover; - QPushButton * discard; - QPushButton * exit; - // setting all buttons to the same roles allows us // to have a custom layout - discard = mb.addButton( MainWindow::tr( "Discard" ), - QMessageBox::AcceptRole ); - recover = mb.addButton( MainWindow::tr( "Recover" ), - QMessageBox::AcceptRole ); + auto discard = mb.addButton(MainWindow::tr("Discard"), QMessageBox::AcceptRole); + auto recover = mb.addButton(MainWindow::tr("Recover"), QMessageBox::AcceptRole); // have a hidden exit button - exit = mb.addButton( "", QMessageBox::RejectRole); + auto exit = mb.addButton("", QMessageBox::RejectRole); exit->setVisible(false); // set icons diff --git a/src/core/midi/MidiAlsaRaw.cpp b/src/core/midi/MidiAlsaRaw.cpp index 23364fc01..f091b789f 100644 --- a/src/core/midi/MidiAlsaRaw.cpp +++ b/src/core/midi/MidiAlsaRaw.cpp @@ -39,10 +39,7 @@ MidiAlsaRaw::MidiAlsaRaw() : m_outputp( &m_output ), m_quit( false ) { - int err; - if( ( err = snd_rawmidi_open( m_inputp, m_outputp, - probeDevice().toLatin1().constData(), - 0 ) ) < 0 ) + if (int err = snd_rawmidi_open(m_inputp, m_outputp, probeDevice().toLatin1().constData(), 0); err < 0) { printf( "cannot open MIDI-device: %s\n", snd_strerror( err ) ); return; @@ -111,29 +108,27 @@ void MidiAlsaRaw::run() { msleep( 5 ); // must do that, otherwise this thread takes // too much CPU-time, even with LowPriority... - int err = poll( m_pfds, m_npfds, 10000 ); - if( err < 0 && errno == EINTR ) + if (int err = poll(m_pfds, m_npfds, 10000); err < 0 && errno == EINTR) { printf( "MidiAlsaRaw::run(): Got EINTR while " "polling. Will stop polling MIDI-events from " "MIDI-port.\n" ); break; } - if( err < 0 ) + else if (err < 0) { printf( "poll failed: %s\nWill stop polling " "MIDI-events from MIDI-port.\n", strerror( errno ) ); break; } - if( err == 0 ) + else if (err == 0) { //printf( "there seems to be no active MIDI-device %d\n", ++cnt ); continue; } - unsigned short revents; - if( ( err = snd_rawmidi_poll_descriptors_revents( - m_input, m_pfds, m_npfds, &revents ) ) < 0 ) + unsigned short revents = 0; + if (int err = snd_rawmidi_poll_descriptors_revents(m_input, m_pfds, m_npfds, &revents); err < 0) { printf( "cannot get poll events: %s\nWill stop polling " "MIDI-events from MIDI-port.\n", @@ -149,25 +144,19 @@ void MidiAlsaRaw::run() { continue; } - err = snd_rawmidi_read(m_input, buf.data(), buf.size()); - if( err == -EAGAIN ) - { - continue; - } - if( err < 0 ) + + if (int err = snd_rawmidi_read(m_input, buf.data(), buf.size()); err == -EAGAIN) { continue; } + else if (err < 0) { printf( "cannot read from port \"%s\": %s\nWill stop " "polling MIDI-events from MIDI-port.\n", /*port_name*/"default", snd_strerror( err ) ); break; } - if( err == 0 ) + else if (err == 0) { continue; } + else { - continue; - } - for( int i = 0; i < err; ++i ) - { - parseData( buf[i] ); + for (int i = 0; i < err; ++i) { parseData(buf[i]); } } } diff --git a/src/core/midi/MidiAlsaSeq.cpp b/src/core/midi/MidiAlsaSeq.cpp index 0b3bab819..e0b8b486e 100644 --- a/src/core/midi/MidiAlsaSeq.cpp +++ b/src/core/midi/MidiAlsaSeq.cpp @@ -78,10 +78,7 @@ MidiAlsaSeq::MidiAlsaSeq() : m_quit( false ), m_portListUpdateTimer( this ) { - int err; - if( ( err = snd_seq_open( &m_seqHandle, - probeDevice().toLatin1().constData(), - SND_SEQ_OPEN_DUPLEX, 0 ) ) < 0 ) + if (int err = snd_seq_open(&m_seqHandle, probeDevice().toLatin1().constData(), SND_SEQ_OPEN_DUPLEX, 0); err < 0) { fprintf( stderr, "cannot open sequencer: %s\n", snd_strerror( err ) ); diff --git a/src/core/midi/MidiController.cpp b/src/core/midi/MidiController.cpp index 0ae76d352..112d9d974 100644 --- a/src/core/midi/MidiController.cpp +++ b/src/core/midi/MidiController.cpp @@ -74,11 +74,11 @@ void MidiController::updateName() void MidiController::processInEvent(const MidiEvent& event, const TimePos& time, f_cnt_t offset) { - unsigned char controllerNum; switch(event.type()) { case MidiControlChange: - controllerNum = event.controllerNumber(); + { + unsigned char controllerNum = event.controllerNumber(); if (m_midiPort.inputController() == controllerNum && (m_midiPort.inputChannel() == event.channel() + 1 || m_midiPort.inputChannel() == 0)) @@ -89,7 +89,7 @@ void MidiController::processInEvent(const MidiEvent& event, const TimePos& time, emit valueChanged(); } break; - + } default: // Don't care - maybe add special cases for pitch and mod later break; diff --git a/src/core/midi/MidiJack.cpp b/src/core/midi/MidiJack.cpp index 145a72ecc..29e7e27ec 100644 --- a/src/core/midi/MidiJack.cpp +++ b/src/core/midi/MidiJack.cpp @@ -179,7 +179,6 @@ QString MidiJack::probeDevice() // we read data from jack void MidiJack::JackMidiRead(jack_nframes_t nframes) { - unsigned int i,b; void* port_buf = jack_port_get_buffer(m_input_port, nframes); jack_midi_event_t in_event; jack_nframes_t event_index = 0; @@ -188,13 +187,13 @@ void MidiJack::JackMidiRead(jack_nframes_t nframes) int rval = jack_midi_event_get(&in_event, port_buf, 0); if (rval == 0 /* 0 = success */) { - for(i=0; i 0; n--, p++) + for (char* p = buf; n > 0; n--, p++) { parseData( *p ); } diff --git a/src/gui/AudioAlsaSetupWidget.cpp b/src/gui/AudioAlsaSetupWidget.cpp index 7db822b4b..bc0ecde8e 100644 --- a/src/gui/AudioAlsaSetupWidget.cpp +++ b/src/gui/AudioAlsaSetupWidget.cpp @@ -31,7 +31,6 @@ #include "ConfigManager.h" #include "LcdSpinBox.h" -#include "gui_templates.h" namespace lmms::gui { diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt index afed153f9..2485b92d2 100644 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -26,14 +26,16 @@ SET(LMMS_SRCS gui/MicrotunerConfig.cpp gui/MidiCCRackView.cpp gui/MidiSetupWidget.cpp - gui/MixerLine.cpp + gui/MixerChannelView.cpp gui/MixerView.cpp gui/ModelView.cpp gui/PeakControllerDialog.cpp gui/PluginBrowser.cpp gui/ProjectNotes.cpp gui/RowTableView.cpp + gui/SampleLoader.cpp gui/SampleTrackWindow.cpp + gui/SampleWaveform.cpp gui/SendButtonIndicator.cpp gui/SideBar.cpp gui/SideBarWidget.cpp @@ -59,12 +61,14 @@ SET(LMMS_SRCS gui/editors/TrackContainerView.cpp gui/instrument/EnvelopeAndLfoView.cpp + gui/instrument/EnvelopeGraph.cpp gui/instrument/InstrumentFunctionViews.cpp gui/instrument/InstrumentMidiIOView.cpp gui/instrument/InstrumentTuningView.cpp gui/instrument/InstrumentSoundShapingView.cpp gui/instrument/InstrumentTrackWindow.cpp gui/instrument/InstrumentView.cpp + gui/instrument/LfoGraph.cpp gui/instrument/PianoView.cpp gui/menus/MidiPortMenu.cpp @@ -94,11 +98,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 @@ -108,13 +114,14 @@ SET(LMMS_SRCS gui/widgets/LedCheckBox.cpp gui/widgets/LeftRightNav.cpp gui/widgets/MeterDialog.cpp - gui/widgets/MixerLineLcdSpinBox.cpp + gui/widgets/MixerChannelLcdSpinBox.cpp gui/widgets/NStateButton.cpp gui/widgets/Oscilloscope.cpp gui/widgets/PixmapButton.cpp 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 @@ -123,11 +130,3 @@ SET(LMMS_SRCS PARENT_SCOPE ) -set(LMMS_UIS - ${LMMS_UIS} - gui/modals/about_dialog.ui - gui/modals/EffectSelectDialog.ui - gui/modals/export_project.ui - - PARENT_SCOPE -) diff --git a/src/gui/ControlLayout.cpp b/src/gui/ControlLayout.cpp index 5e9a21101..75133c8e3 100644 --- a/src/gui/ControlLayout.cpp +++ b/src/gui/ControlLayout.cpp @@ -101,8 +101,7 @@ ControlLayout::ControlLayout(QWidget *parent, int margin, int hSpacing, int vSpa ControlLayout::~ControlLayout() { - QLayoutItem *item; - while ((item = takeAt(0))) { delete item; } + while (auto item = takeAt(0)) { delete item; } } void ControlLayout::onTextChanged(const QString&) diff --git a/src/gui/ControllerRackView.cpp b/src/gui/ControllerRackView.cpp index 54c325dc6..e7d2efebd 100644 --- a/src/gui/ControllerRackView.cpp +++ b/src/gui/ControllerRackView.cpp @@ -24,6 +24,7 @@ */ #include +#include #include #include #include @@ -68,8 +69,8 @@ ControllerRackView::ControllerRackView() : this, SLOT(addController())); Song * song = Engine::getSong(); - connect( song, SIGNAL(controllerAdded(lmms::Controller*)), SLOT(onControllerAdded(lmms::Controller*))); - connect( song, SIGNAL(controllerRemoved(lmms::Controller*)), SLOT(onControllerRemoved(lmms::Controller*))); + connect(song, &Song::controllerAdded, this, qOverload(&ControllerRackView::addController)); + connect(song, &Song::controllerRemoved, this, &ControllerRackView::removeController); auto layout = new QVBoxLayout(); layout->addWidget( m_scrollArea ); @@ -132,17 +133,51 @@ void ControllerRackView::deleteController( ControllerView * _view ) song->removeController( c ); } +void ControllerRackView::moveUp(ControllerView* view) +{ + if (view == m_controllerViews.first()) { return; } + const auto storedView = std::find(m_controllerViews.begin(), m_controllerViews.end(), view); + assert(storedView != m_controllerViews.end()); + const auto index = std::distance(m_controllerViews.begin(), storedView); -void ControllerRackView::onControllerAdded( Controller * controller ) + std::swap(m_controllerViews[index - 1], m_controllerViews[index]); + m_scrollAreaLayout->removeWidget(view); + m_scrollAreaLayout->insertWidget(index - 1, view); +} + +void ControllerRackView::moveDown(ControllerView* view) +{ + if (view == m_controllerViews.last()) { return; } + + const auto storedView = std::find(m_controllerViews.begin(), m_controllerViews.end(), view); + assert(storedView != m_controllerViews.end()); + moveUp(*std::next(storedView)); +} + +void ControllerRackView::addController(Controller* controller) { QWidget * scrollAreaWidget = m_scrollArea->widget(); auto controllerView = new ControllerView(controller, scrollAreaWidget); - connect( controllerView, SIGNAL(deleteController(lmms::gui::ControllerView*)), - this, SLOT(deleteController(lmms::gui::ControllerView*)), Qt::QueuedConnection ); + connect(controllerView, &ControllerView::movedUp, this, &ControllerRackView::moveUp); + connect(controllerView, &ControllerView::movedDown, this, &ControllerRackView::moveDown); + connect(controllerView, &ControllerView::removedController, this, &ControllerRackView::deleteController, Qt::QueuedConnection); + + auto moveUpAction = new QAction(controllerView); + moveUpAction->setShortcut(Qt::Key_Up | Qt::AltModifier); + moveUpAction->setShortcutContext(Qt::WidgetShortcut); + connect(moveUpAction, &QAction::triggered, controllerView, &ControllerView::moveUp); + controllerView->addAction(moveUpAction); + + auto moveDownAction = new QAction(controllerView); + moveDownAction->setShortcut(Qt::Key_Down | Qt::AltModifier); + moveDownAction->setShortcutContext(Qt::WidgetShortcut); + connect(moveDownAction, &QAction::triggered, controllerView, &ControllerView::moveDown); + controllerView->addAction(moveDownAction); + m_controllerViews.append( controllerView ); m_scrollAreaLayout->insertWidget( m_nextIndex, controllerView ); @@ -153,7 +188,7 @@ void ControllerRackView::onControllerAdded( Controller * controller ) -void ControllerRackView::onControllerRemoved( Controller * removedController ) +void ControllerRackView::removeController(Controller* removedController) { ControllerView * viewOfRemovedController = 0; diff --git a/src/gui/ControllerView.cpp b/src/gui/ControllerView.cpp index d32e8d49c..7f7c4729c 100644 --- a/src/gui/ControllerView.cpp +++ b/src/gui/ControllerView.cpp @@ -53,6 +53,7 @@ ControllerView::ControllerView( Controller * _model, QWidget * _parent ) : { this->setFrameStyle( QFrame::StyledPanel ); this->setFrameShadow( QFrame::Raised ); + setFocusPolicy(Qt::StrongFocus); auto vBoxLayout = new QVBoxLayout(this); @@ -132,11 +133,11 @@ void ControllerView::closeControls() m_show = true; } +void ControllerView::moveUp() { emit movedUp(this); } -void ControllerView::deleteController() -{ - emit( deleteController( this ) ); -} +void ControllerView::moveDown() { emit movedDown(this); } + +void ControllerView::removeController() { emit removedController(this); } void ControllerView::renameController() { @@ -173,10 +174,13 @@ void ControllerView::modelChanged() void ControllerView::contextMenuEvent( QContextMenuEvent * ) { - QPointer contextMenu = new CaptionMenu( model()->displayName(), this ); - contextMenu->addAction( embed::getIconPixmap( "cancel" ), - tr( "&Remove this controller" ), - this, SLOT(deleteController())); + Controller* c = castModel(); + QPointer contextMenu = new CaptionMenu(c->name(), this); + contextMenu->addAction(embed::getIconPixmap("arp_up"), tr("Move &up"), this, &ControllerView::moveUp); + contextMenu->addAction(embed::getIconPixmap("arp_down"), tr("Move &down"), this, &ControllerView::moveDown); + contextMenu->addSeparator(); + contextMenu->addAction( + embed::getIconPixmap("cancel"), tr("&Remove this controller"), this, &ControllerView::removeController); contextMenu->addAction( tr("Re&name this controller"), this, SLOT(renameController())); contextMenu->addSeparator(); contextMenu->exec( QCursor::pos() ); diff --git a/src/gui/EffectRackView.cpp b/src/gui/EffectRackView.cpp index aa790d74d..478e117fe 100644 --- a/src/gui/EffectRackView.cpp +++ b/src/gui/EffectRackView.cpp @@ -24,6 +24,7 @@ */ #include +#include #include #include #include @@ -170,13 +171,22 @@ void EffectRackView::update() if( i >= m_effectViews.size() ) { auto view = new EffectView(effect, w); - connect( view, SIGNAL(moveUp(lmms::gui::EffectView*)), - this, SLOT(moveUp(lmms::gui::EffectView*))); - connect( view, SIGNAL(moveDown(lmms::gui::EffectView*)), - this, SLOT(moveDown(lmms::gui::EffectView*))); - connect( view, SIGNAL(deletePlugin(lmms::gui::EffectView*)), - this, SLOT(deletePlugin(lmms::gui::EffectView*)), - Qt::QueuedConnection ); + connect(view, &EffectView::movedUp, this, &EffectRackView::moveUp); + connect(view, &EffectView::movedDown, this, &EffectRackView::moveDown); + connect(view, &EffectView::deletedPlugin, this, &EffectRackView::deletePlugin, Qt::QueuedConnection); + + QAction* moveUpAction = new QAction(view); + moveUpAction->setShortcut(Qt::Key_Up | Qt::AltModifier); + moveUpAction->setShortcutContext(Qt::WidgetShortcut); + connect(moveUpAction, &QAction::triggered, view, &EffectView::moveUp); + view->addAction(moveUpAction); + + QAction* moveDownAction = new QAction(view); + moveDownAction->setShortcut(Qt::Key_Down | Qt::AltModifier); + moveDownAction->setShortcutContext(Qt::WidgetShortcut); + connect(moveDownAction, &QAction::triggered, view, &EffectView::moveDown); + view->addAction(moveDownAction); + view->show(); m_effectViews.append( view ); if( i < view_map.size() ) diff --git a/src/gui/EffectView.cpp b/src/gui/EffectView.cpp index 7f7f9ee9d..6f2b984c3 100644 --- a/src/gui/EffectView.cpp +++ b/src/gui/EffectView.cpp @@ -53,6 +53,7 @@ EffectView::EffectView( Effect * _model, QWidget * _parent ) : m_dragging(false) { setFixedSize(EffectView::DEFAULT_WIDTH, EffectView::DEFAULT_HEIGHT); + setFocusPolicy(Qt::StrongFocus); // Disable effects that are of type "DummyEffect" bool isEnabled = !dynamic_cast( effect() ); @@ -90,7 +91,7 @@ EffectView::EffectView( Effect * _model, QWidget * _parent ) : { auto ctls_btn = new QPushButton(tr("Controls"), this); QFont f = ctls_btn->font(); - ctls_btn->setFont( pointSize<8>( f ) ); + ctls_btn->setFont(adjustedToPixelSize(f, 10)); ctls_btn->setGeometry( 150, 14, 50, 20 ); connect( ctls_btn, SIGNAL(clicked()), this, SLOT(editControls())); @@ -162,7 +163,7 @@ void EffectView::editControls() void EffectView::moveUp() { - emit moveUp( this ); + emit movedUp(this); } @@ -170,14 +171,14 @@ void EffectView::moveUp() void EffectView::moveDown() { - emit moveDown( this ); + emit movedDown(this); } void EffectView::deletePlugin() { - emit deletePlugin( this ); + emit deletedPlugin(this); } @@ -257,7 +258,7 @@ void EffectView::paintEvent( QPaintEvent * ) QPainter p( this ); p.drawPixmap( 0, 0, m_bg ); - QFont f = pointSizeF( font(), 7.5f ); + QFont f = adjustedToPixelSize(font(), 10); f.setBold( true ); p.setFont( f ); diff --git a/src/gui/FileBrowser.cpp b/src/gui/FileBrowser.cpp index 181e67cd7..04f7ff46f 100644 --- a/src/gui/FileBrowser.cpp +++ b/src/gui/FileBrowser.cpp @@ -23,25 +23,30 @@ * */ +#include "FileBrowser.h" + #include #include +#include #include #include #include -#include -#include #include #include +#include #include +#include #include #include +#include +#include -#include "FileBrowser.h" #include "AudioEngine.h" #include "ConfigManager.h" #include "DataFile.h" -#include "embed.h" #include "Engine.h" +#include "FileBrowser.h" +#include "FileSearch.h" #include "GuiApplication.h" #include "ImportFilter.h" #include "Instrument.h" @@ -51,12 +56,16 @@ #include "PatternStore.h" #include "PluginFactory.h" #include "PresetPreviewPlayHandle.h" +#include "Sample.h" #include "SampleClip.h" +#include "SampleLoader.h" #include "SamplePlayHandle.h" #include "SampleTrack.h" #include "Song.h" #include "StringPairDrag.h" #include "TextFloat.h" +#include "ThreadPool.h" +#include "embed.h" namespace lmms::gui { @@ -96,14 +105,13 @@ void FileBrowser::addContentCheckBox() FileBrowser::FileBrowser(const QString & directories, const QString & filter, const QString & title, const QPixmap & pm, - QWidget * parent, bool dirs_as_items, bool recurse, + QWidget * parent, bool dirs_as_items, const QString& userDir, const QString& factoryDir): SideBarWidget( title, pm, parent ), m_directories( directories ), m_filter( filter ), m_dirsAsItems( dirs_as_items ), - m_recurse( recurse ), m_userDir(userDir), m_factoryDir(factoryDir) { @@ -121,11 +129,12 @@ FileBrowser::FileBrowser(const QString & directories, const QString & filter, searchWidgetLayout->setContentsMargins(0, 0, 0, 0); searchWidgetLayout->setSpacing( 0 ); - m_filterEdit = new QLineEdit( searchWidget ); - m_filterEdit->setPlaceholderText( tr("Search") ); - m_filterEdit->setClearButtonEnabled( true ); - connect( m_filterEdit, SIGNAL( textEdited( const QString& ) ), - this, SLOT( filterAndExpandItems( const QString& ) ) ); + m_filterEdit = new QLineEdit(searchWidget); + m_filterEdit->setPlaceholderText(tr("Search")); + m_filterEdit->setClearButtonEnabled(true); + m_filterEdit->addAction(embed::getIconPixmap("zoom"), QLineEdit::LeadingPosition); + + connect(m_filterEdit, &QLineEdit::textEdited, this, &FileBrowser::onSearch); auto reload_btn = new QPushButton(embed::getIconPixmap("reload"), QString(), searchWidget); reload_btn->setToolTip( tr( "Refresh list" ) ); @@ -140,6 +149,15 @@ FileBrowser::FileBrowser(const QString & directories, const QString & filter, m_fileBrowserTreeWidget = new FileBrowserTreeWidget( contentParent() ); addContentWidget( m_fileBrowserTreeWidget ); + m_searchTreeWidget = new FileBrowserTreeWidget(contentParent()); + m_searchTreeWidget->hide(); + addContentWidget(m_searchTreeWidget); + + m_searchIndicator = new QProgressBar(this); + m_searchIndicator->setMinimum(0); + m_searchIndicator->setMaximum(100); + addContentWidget(m_searchIndicator); + // Whenever the FileBrowser has focus, Ctrl+F should direct focus to its filter box. auto filterFocusShortcut = new QShortcut(QKeySequence(QKeySequence::Find), this, SLOT(giveFocusToFilter())); filterFocusShortcut->setContext(Qt::WidgetWithChildrenShortcut); @@ -157,86 +175,128 @@ void FileBrowser::saveDirectoriesStates() void FileBrowser::restoreDirectoriesStates() { - expandItems(nullptr, m_savedExpandedDirs); + expandItems(m_savedExpandedDirs); } -bool FileBrowser::filterAndExpandItems(const QString & filter, QTreeWidgetItem * item) +void FileBrowser::foundSearchMatch(FileSearch* search, const QString& match) { - // Call with item = nullptr to filter the entire tree + assert(search != nullptr); + if (m_currentSearch.get() != search) { return; } - if (item == nullptr) + auto basePath = QString{}; + for (const auto& path : m_directories.split('*')) { - // First search character so need to save current expanded directories - if (m_previousFilterValue.isEmpty()) + if (!match.startsWith(QDir{path}.absolutePath())) { continue; } + basePath = path; + break; + } + + if (basePath.isEmpty()) { return; } + + const auto baseDir = QDir{basePath}; + const auto matchInfo = QFileInfo{match}; + const auto matchRelativeToBasePath = baseDir.relativeFilePath(match); + + auto pathParts = QDir::cleanPath(matchRelativeToBasePath).split("/"); + auto currentItem = static_cast(nullptr); + auto currentDir = baseDir; + + for (const auto& pathPart : pathParts) + { + auto childCount = currentItem ? currentItem->childCount() : m_searchTreeWidget->topLevelItemCount(); + auto childItem = static_cast(nullptr); + + for (int i = 0; i < childCount; ++i) { - saveDirectoriesStates(); + auto item = currentItem ? currentItem->child(i) : m_searchTreeWidget->topLevelItem(i); + if (item->text(0) == pathPart) + { + childItem = item; + break; + } + } - m_previousFilterValue = filter; + if (!childItem) + { + auto pathPartInfo = QFileInfo(currentDir, pathPart); + if (pathPartInfo.isDir()) + { + // Only update directory (i.e., add entries) when it is the matched directory (so do not update + // parents since entries would be added to them that did not match the filter) + const auto disablePopulation = pathParts.indexOf(pathPart) < pathParts.size() - 1; + + auto item = new Directory(pathPart, currentDir.path(), m_filter, disablePopulation); + currentItem ? currentItem->addChild(item) : m_searchTreeWidget->addTopLevelItem(item); + item->update(); + if (disablePopulation) { m_searchTreeWidget->expandItem(item); } + childItem = item; + } + else + { + auto item = new FileItem(pathPart, currentDir.path()); + currentItem ? currentItem->addChild(item) : m_searchTreeWidget->addTopLevelItem(item); + childItem = item; + } + } + + currentItem = childItem; + if (!currentDir.cd(pathPart)) { break; } } +} + +void FileBrowser::searchCompleted(FileSearch* search) +{ + assert(search != nullptr); + if (m_currentSearch.get() != search) { return; } + + m_currentSearch.reset(); + m_searchIndicator->setMaximum(100); +} + +void FileBrowser::onSearch(const QString& filter) +{ + if (m_currentSearch) { m_currentSearch->cancel(); } if (filter.isEmpty()) { - // Restore previous expanded directories - if (item == nullptr) - { - restoreDirectoriesStates(); - } + displaySearch(false); + return; + } - return false; + auto directories = m_directories.split('*'); + if (m_showUserContent && !m_showUserContent->isChecked()) { directories.removeAll(m_userDir); } + if (m_showFactoryContent && !m_showFactoryContent->isChecked()) { directories.removeAll(m_factoryDir); } + if (directories.isEmpty()) { return; } + + m_searchTreeWidget->clear(); + displaySearch(true); + + auto browserExtensions = m_filter; + const auto searchExtensions = browserExtensions.remove("*.").split(' '); + + auto search = std::make_shared( + filter, directories, searchExtensions, excludedPaths(), dirFilters(), sortFlags()); + connect(search.get(), &FileSearch::foundMatch, this, &FileBrowser::foundSearchMatch, Qt::QueuedConnection); + connect(search.get(), &FileSearch::searchCompleted, this, &FileBrowser::searchCompleted, Qt::QueuedConnection); + + m_currentSearch = search; + ThreadPool::instance().enqueue([search] { (*search)(); }); +} + +void FileBrowser::displaySearch(bool on) +{ + if (on) + { + m_searchTreeWidget->show(); + m_fileBrowserTreeWidget->hide(); + m_searchIndicator->setMaximum(0); + return; } - bool anyMatched = false; - - int numChildren = item ? item->childCount() : m_fileBrowserTreeWidget->topLevelItemCount(); - - for (int i = 0; i < numChildren; ++i) - { - QTreeWidgetItem * it = item ? item->child( i ) : m_fileBrowserTreeWidget->topLevelItem(i); - - auto d = dynamic_cast(it); - if (d) - { - if (it->text(0).contains(filter, Qt::CaseInsensitive)) - { - it->setHidden(false); - it->setExpanded(true); - filterAndExpandItems(QString(), it); - anyMatched = true; - } - else - { - // Expanding is required when recursive to load in its contents, even if it's collapsed right afterward - it->setExpanded(true); - - bool didMatch = filterAndExpandItems(filter, it); - it->setHidden(!didMatch); - it->setExpanded(didMatch); - anyMatched = anyMatched || didMatch; - } - } - - else - { - auto f = dynamic_cast(it); - if (f) - { - // File - bool didMatch = it->text(0).contains(filter, Qt::CaseInsensitive); - it->setHidden(!didMatch); - anyMatched = anyMatched || didMatch; - } - - // A standard item (i.e. no file or directory item?) - else - { - // Hide if there's any filter - it->setHidden(!filter.isEmpty()); - } - } - } - - return anyMatched; + m_searchTreeWidget->hide(); + m_fileBrowserTreeWidget->show(); + m_searchIndicator->setMaximum(100); } @@ -275,14 +335,16 @@ void FileBrowser::reloadTree() } else { - filterAndExpandItems(m_filterEdit->text()); + onSearch(m_filterEdit->text()); } } -void FileBrowser::expandItems(QTreeWidgetItem* item, QList expandedDirs) +void FileBrowser::expandItems(const QList& expandedDirs, QTreeWidgetItem* item) { + if (expandedDirs.isEmpty()) { return; } + int numChildren = item ? item->childCount() : m_fileBrowserTreeWidget->topLevelItemCount(); for (int i = 0; i < numChildren; ++i) { @@ -290,14 +352,10 @@ void FileBrowser::expandItems(QTreeWidgetItem* item, QList expandedDirs auto d = dynamic_cast(it); if (d) { - // Expanding is required when recursive to load in its contents, even if it's collapsed right afterward - if (m_recurse) { d->setExpanded(true); } - d->setExpanded(expandedDirs.contains(d->fullName())); - - if (m_recurse && it->childCount()) + if (it->childCount() > 0) { - expandItems(it, expandedDirs); + expandItems(expandedDirs, it); } } @@ -321,6 +379,8 @@ void FileBrowser::giveFocusToFilter() void FileBrowser::addItems(const QString & path ) { + if (FileBrowser::excludedPaths().contains(path)) { return; } + if( m_dirsAsItems ) { m_fileBrowserTreeWidget->addTopLevelItem( new Directory( path, QString(), m_filter ) ); @@ -328,68 +388,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(' '), dirFilters(), QDir::LocaleAware | QDir::DirsFirst | QDir::Name | QDir::IgnoreCase); + for (const auto& entry : entries) { - QString cur_file = *it; - if( cur_file[0] != '.' ) + if (FileBrowser::excludedPaths().contains(entry.absoluteFilePath())) { continue; } + + 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); } } } @@ -665,9 +720,12 @@ void FileBrowserTreeWidget::previewFileItem(FileItem* file) embed::getIconPixmap("sample_file", 24, 24), 0); // TODO: this can be removed once we do this outside the event thread qApp->processEvents(QEventLoop::ExcludeUserInputEvents); - auto s = new SamplePlayHandle(fileName); - s->setDoneMayReturnTrue(false); - newPPH = s; + if (auto buffer = SampleLoader::createBufferFromFile(fileName)) + { + auto s = new SamplePlayHandle(new lmms::Sample{std::move(buffer)}); + s->setDoneMayReturnTrue(false); + newPPH = s; + } delete tf; } else if ( @@ -958,74 +1016,27 @@ void FileBrowserTreeWidget::updateDirectory(QTreeWidgetItem * item ) } } - - - - - -QPixmap * Directory::s_folderPixmap = nullptr; -QPixmap * Directory::s_folderOpenedPixmap = nullptr; -QPixmap * Directory::s_folderLockedPixmap = nullptr; - - -Directory::Directory(const QString & filename, const QString & path, - const QString & filter ) : - QTreeWidgetItem( QStringList( filename ), TypeDirectoryItem ), - m_directories( path ), - m_filter( filter ), - m_dirCount( 0 ) +Directory::Directory(const QString& filename, const QString& path, const QString& filter, bool disableEntryPopulation) + : QTreeWidgetItem(QStringList(filename), TypeDirectoryItem) + , m_directories(path) + , m_filter(filter) + , m_dirCount(0) + , m_disableEntryPopulation(disableEntryPopulation) { - initPixmaps(); - + setIcon(0, !QDir{fullName()}.isReadable() ? m_folderLockedPixmap : m_folderPixmap); setChildIndicatorPolicy( QTreeWidgetItem::ShowIndicator ); - - if( !QDir( fullName() ).isReadable() ) - { - setIcon( 0, *s_folderLockedPixmap ); - } - else - { - setIcon( 0, *s_folderPixmap ); - } } - - - -void Directory::initPixmaps() -{ - if( s_folderPixmap == nullptr ) - { - s_folderPixmap = new QPixmap( - embed::getIconPixmap( "folder" ) ); - } - - if( s_folderOpenedPixmap == nullptr ) - { - s_folderOpenedPixmap = new QPixmap( - embed::getIconPixmap( "folder_opened" ) ); - } - - if( s_folderLockedPixmap == nullptr ) - { - s_folderLockedPixmap = new QPixmap( - embed::getIconPixmap( "folder_locked" ) ); - } -} - - - - void Directory::update() { if( !isExpanded() ) { - setIcon( 0, *s_folderPixmap ); + setIcon(0, m_folderPixmap); return; } - setIcon( 0, *s_folderOpenedPixmap ); - if( !childCount() ) + setIcon(0, m_folderOpenedPixmap); + if (!m_disableEntryPopulation && !childCount()) { m_dirCount = 0; // for all paths leading here, add their items @@ -1058,14 +1069,19 @@ void Directory::update() bool Directory::addItems(const QString& path) { + if (FileBrowser::excludedPaths().contains(path)) { return false; } + QDir thisDir(path); if (!thisDir.isReadable()) { return false; } 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(' '), FileBrowser::dirFilters(), FileBrowser::sortFlags()); + for (const auto& entry : entries) { + if (FileBrowser::excludedPaths().contains(entry.absoluteFilePath())) { continue; } + QString fileName = entry.fileName(); if (entry.isDir()) { @@ -1073,7 +1089,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); @@ -1089,15 +1105,6 @@ bool Directory::addItems(const QString& path) -QPixmap * FileItem::s_projectFilePixmap = nullptr; -QPixmap * FileItem::s_presetFilePixmap = nullptr; -QPixmap * FileItem::s_sampleFilePixmap = nullptr; -QPixmap * FileItem::s_soundfontFilePixmap = nullptr; -QPixmap * FileItem::s_vstPluginFilePixmap = nullptr; -QPixmap * FileItem::s_midiFilePixmap = nullptr; -QPixmap * FileItem::s_unknownFilePixmap = nullptr; - - FileItem::FileItem(QTreeWidget * parent, const QString & name, const QString & path ) : QTreeWidgetItem( parent, QStringList( name) , TypeFileItem ), @@ -1123,72 +1130,38 @@ FileItem::FileItem(const QString & name, const QString & path ) : void FileItem::initPixmaps() { - if( s_projectFilePixmap == nullptr ) - { - s_projectFilePixmap = new QPixmap( embed::getIconPixmap( - "project_file", 16, 16 ) ); - } - - if( s_presetFilePixmap == nullptr ) - { - s_presetFilePixmap = new QPixmap( embed::getIconPixmap( - "preset_file", 16, 16 ) ); - } - - if( s_sampleFilePixmap == nullptr ) - { - s_sampleFilePixmap = new QPixmap( embed::getIconPixmap( - "sample_file", 16, 16 ) ); - } - - if ( s_soundfontFilePixmap == nullptr ) - { - s_soundfontFilePixmap = new QPixmap( embed::getIconPixmap( - "soundfont_file", 16, 16 ) ); - } - - if ( s_vstPluginFilePixmap == nullptr ) - { - s_vstPluginFilePixmap = new QPixmap( embed::getIconPixmap( - "vst_plugin_file", 16, 16 ) ); - } - - if( s_midiFilePixmap == nullptr ) - { - s_midiFilePixmap = new QPixmap( embed::getIconPixmap( - "midi_file", 16, 16 ) ); - } - - if( s_unknownFilePixmap == nullptr ) - { - s_unknownFilePixmap = new QPixmap( embed::getIconPixmap( - "unknown_file" ) ); - } + static auto s_projectFilePixmap = embed::getIconPixmap("project_file", 16, 16); + static auto s_presetFilePixmap = embed::getIconPixmap("preset_file", 16, 16); + static auto s_sampleFilePixmap = embed::getIconPixmap("sample_file", 16, 16); + static auto s_soundfontFilePixmap = embed::getIconPixmap("soundfont_file", 16, 16); + static auto s_vstPluginFilePixmap = embed::getIconPixmap("vst_plugin_file", 16, 16); + static auto s_midiFilePixmap = embed::getIconPixmap("midi_file", 16, 16); + static auto s_unknownFilePixmap = embed::getIconPixmap("unknown_file"); switch( m_type ) { case FileType::Project: - setIcon( 0, *s_projectFilePixmap ); + setIcon(0, s_projectFilePixmap); break; case FileType::Preset: - setIcon( 0, *s_presetFilePixmap ); + setIcon(0, s_presetFilePixmap); break; case FileType::SoundFont: - setIcon( 0, *s_soundfontFilePixmap ); + setIcon(0, s_soundfontFilePixmap); break; case FileType::VstPlugin: - setIcon( 0, *s_vstPluginFilePixmap ); + setIcon(0, s_vstPluginFilePixmap); break; case FileType::Sample: case FileType::Patch: // TODO - setIcon( 0, *s_sampleFilePixmap ); + setIcon(0, s_sampleFilePixmap); break; case FileType::Midi: - setIcon( 0, *s_midiFilePixmap ); + setIcon(0, s_midiFilePixmap); break; case FileType::Unknown: default: - setIcon( 0, *s_unknownFilePixmap ); + setIcon(0, s_unknownFilePixmap); break; } } @@ -1277,5 +1250,30 @@ QString FileItem::extension(const QString & file ) return QFileInfo( file ).suffix().toLower(); } +QString FileItem::defaultFilters() +{ + const auto projectFilters = QStringList{"*.mmp", "*.mpt", "*.mmpz"}; + const auto presetFilters = QStringList{"*.xpf", "*.xml", "*.xiz", "*.lv2"}; + const auto soundFontFilters = QStringList{"*.sf2", "*.sf3"}; + const auto patchFilters = QStringList{"*.pat"}; + const auto midiFilters = QStringList{"*.mid", "*.midi", "*.rmi"}; + + auto vstPluginFilters = QStringList{"*.dll"}; +#ifdef LMMS_BUILD_LINUX + vstPluginFilters.append("*.so"); +#endif + + auto audioFilters + = QStringList{"*.wav", "*.ogg", "*.ds", "*.flac", "*.spx", "*.voc", "*.aif", "*.aiff", "*.au", "*.raw"}; +#ifdef LMMS_HAVE_SNDFILE_MP3 + audioFilters.append("*.mp3"); +#endif + + const auto extensions = projectFilters + presetFilters + soundFontFilters + patchFilters + midiFilters + + vstPluginFilters + audioFilters; + + return extensions.join(" "); +} + } // namespace lmms::gui diff --git a/src/gui/GuiApplication.cpp b/src/gui/GuiApplication.cpp index 3370cbc6e..5c4bdd19a 100644 --- a/src/gui/GuiApplication.cpp +++ b/src/gui/GuiApplication.cpp @@ -148,7 +148,7 @@ GuiApplication::GuiApplication() connect(m_songEditor, SIGNAL(destroyed(QObject*)), this, SLOT(childDestroyed(QObject*))); displayInitProgress(tr("Preparing mixer")); - m_mixerView = new MixerView; + m_mixerView = new MixerView(Engine::mixer()); connect(m_mixerView, SIGNAL(destroyed(QObject*)), this, SLOT(childDestroyed(QObject*))); displayInitProgress(tr("Preparing controller rack")); diff --git a/src/gui/LfoControllerDialog.cpp b/src/gui/LfoControllerDialog.cpp index 77362b169..559ac1336 100644 --- a/src/gui/LfoControllerDialog.cpp +++ b/src/gui/LfoControllerDialog.cpp @@ -31,6 +31,7 @@ #include "Knob.h" #include "TempoSyncKnob.h" #include "PixmapButton.h" +#include "SampleLoader.h" namespace lmms::gui { @@ -210,14 +211,14 @@ LfoControllerDialog::~LfoControllerDialog() void LfoControllerDialog::askUserDefWave() { - SampleBuffer * sampleBuffer = dynamic_cast(this->model())-> - m_userDefSampleBuffer; - QString fileName = sampleBuffer->openAndSetWaveformFile(); - if( fileName.isEmpty() == false ) - { - // TODO: - m_userWaveBtn->setToolTip(sampleBuffer->audioFile()); - } + const auto fileName = SampleLoader::openWaveformFile(); + if (fileName.isEmpty()) { return; } + + auto lfoModel = dynamic_cast(model()); + auto& buffer = lfoModel->m_userDefSampleBuffer; + buffer = SampleLoader::createBufferFromFile(fileName); + + m_userWaveBtn->setToolTip(buffer->audioFile()); } diff --git a/src/gui/Lv2ViewBase.cpp b/src/gui/Lv2ViewBase.cpp index 830a994c8..fc025e268 100644 --- a/src/gui/Lv2ViewBase.cpp +++ b/src/gui/Lv2ViewBase.cpp @@ -31,7 +31,7 @@ #include #include #include -#include +#include #include "AudioEngine.h" #include "Controls.h" @@ -74,7 +74,7 @@ Lv2ViewProc::Lv2ViewProc(QWidget* parent, Lv2Proc* proc, int colNum) : break; case PortVis::Integer: { - sample_rate_t sr = Engine::audioEngine()->processingSampleRate(); + sample_rate_t sr = Engine::audioEngine()->outputSampleRate(); auto pMin = port.min(sr); auto pMax = port.max(sr); int numDigits = std::max(numDigitsAsInt(pMin), numDigitsAsInt(pMax)); @@ -137,7 +137,8 @@ AutoLilvNode Lv2ViewProc::uri(const char *uriStr) -Lv2ViewBase::Lv2ViewBase(QWidget* meAsWidget, Lv2ControlBase *ctrlBase) +Lv2ViewBase::Lv2ViewBase(QWidget* meAsWidget, Lv2ControlBase *ctrlBase) : + m_helpWindowEventFilter(this) { auto grid = new QGridLayout(meAsWidget); @@ -156,8 +157,7 @@ Lv2ViewBase::Lv2ViewBase(QWidget* meAsWidget, Lv2ControlBase *ctrlBase) m_toggleUIButton->setCheckable(true); m_toggleUIButton->setChecked(false); m_toggleUIButton->setIcon(embed::getIconPixmap("zoom")); - m_toggleUIButton->setFont( - pointSize<8>(m_toggleUIButton->font())); + m_toggleUIButton->setFont(adjustedToPixelSize(m_toggleUIButton->font(), 8)); btnBox->addWidget(m_toggleUIButton, 0); } btnBox->addStretch(1); @@ -172,7 +172,7 @@ Lv2ViewBase::Lv2ViewBase(QWidget* meAsWidget, Lv2ControlBase *ctrlBase) LILV_FOREACH(nodes, itr, props.get()) { const LilvNode* node = lilv_nodes_get(props.get(), itr); - auto infoLabel = new QLabel(lilv_node_as_string(node)); + auto infoLabel = new QLabel(QString(lilv_node_as_string(node)).trimmed() + "\n"); infoLabel->setWordWrap(true); infoLabel->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Expanding); @@ -181,8 +181,9 @@ Lv2ViewBase::Lv2ViewBase(QWidget* meAsWidget, Lv2ControlBase *ctrlBase) btnBox->addWidget(m_helpButton); m_helpWindow = getGUI()->mainWindow()->addWindowedWidget(infoLabel); - m_helpWindow->setSizePolicy(QSizePolicy::Minimum, + m_helpWindow->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + m_helpWindow->installEventFilter(&m_helpWindowEventFilter); m_helpWindow->setAttribute(Qt::WA_DeleteOnClose, false); m_helpWindow->hide(); @@ -203,6 +204,7 @@ Lv2ViewBase::Lv2ViewBase(QWidget* meAsWidget, Lv2ControlBase *ctrlBase) Lv2ViewBase::~Lv2ViewBase() { + closeHelpWindow(); // TODO: hide UI if required } @@ -228,6 +230,14 @@ void Lv2ViewBase::toggleHelp(bool visible) +void Lv2ViewBase::closeHelpWindow() +{ + if (m_helpWindow) { m_helpWindow->close(); } +} + + + + void Lv2ViewBase::modelChanged(Lv2ControlBase *ctrlBase) { // reconnect models @@ -248,6 +258,32 @@ AutoLilvNode Lv2ViewBase::uri(const char *uriStr) } + + +void Lv2ViewBase::onHelpWindowClosed() +{ + m_helpButton->setChecked(true); +} + + + + +HelpWindowEventFilter::HelpWindowEventFilter(Lv2ViewBase* viewBase) : + m_viewBase(viewBase) {} + + + + +bool HelpWindowEventFilter::eventFilter(QObject* , QEvent* event) +{ + if (event->type() == QEvent::Close) { + m_viewBase->m_helpButton->setChecked(false); + return true; + } + return false; +} + + } // namespace lmms::gui #endif // LMMS_HAVE_LV2 diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp index 10805fe01..3522d0e2d 100644 --- a/src/gui/MainWindow.cpp +++ b/src/gui/MainWindow.cpp @@ -112,34 +112,27 @@ 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, + splitter, false, confMgr->userProjectsDir(), confMgr->factoryProjectsDir())); - sideBar->appendTab( new FileBrowser( - confMgr->userSamplesDir() + "*" + - confMgr->factorySamplesDir(), - "*", tr( "My Samples" ), - embed::getIconPixmap( "sample_file" ).transformed( QTransform().rotate( 90 ) ), - splitter, false, true, - confMgr->userSamplesDir(), - confMgr->factorySamplesDir())); + sideBar->appendTab( + new FileBrowser(confMgr->userSamplesDir() + "*" + confMgr->factorySamplesDir(), FileItem::defaultFilters(), + tr("My Samples"), embed::getIconPixmap("sample_file").transformed(QTransform().rotate(90)), splitter, false, + confMgr->userSamplesDir(), confMgr->factorySamplesDir())); sideBar->appendTab( new FileBrowser( confMgr->userPresetsDir() + "*" + confMgr->factoryPresetsDir(), "*.xpf *.cs.xml *.xiz *.lv2", tr( "My Presets" ), embed::getIconPixmap( "preset_file" ).transformed( QTransform().rotate( 90 ) ), - splitter , false, true, + splitter , false, confMgr->userPresetsDir(), confMgr->factoryPresetsDir())); - sideBar->appendTab( new FileBrowser( QDir::homePath(), "*", - tr( "My Home" ), - embed::getIconPixmap( "home" ).transformed( QTransform().rotate( 90 ) ), - splitter, false, false ) ); - + sideBar->appendTab(new FileBrowser(QDir::homePath(), FileItem::defaultFilters(), tr("My Home"), + embed::getIconPixmap("home").transformed(QTransform().rotate(90)), splitter, false)); QStringList root_paths; QString title = tr( "Root directory" ); @@ -161,9 +154,8 @@ MainWindow::MainWindow() : } #endif - sideBar->appendTab( new FileBrowser( root_paths.join( "*" ), "*", title, - embed::getIconPixmap( "computer" ).transformed( QTransform().rotate( 90 ) ), - splitter, dirs_as_items) ); + sideBar->appendTab(new FileBrowser(root_paths.join("*"), FileItem::defaultFilters(), title, + embed::getIconPixmap("computer").transformed(QTransform().rotate(90)), splitter, dirs_as_items)); m_workspace = new QMdiArea(splitter); @@ -234,8 +226,6 @@ MainWindow::MainWindow() : connect( Engine::getSong(), SIGNAL(playbackStateChanged()), this, SLOT(updatePlayPauseIcons())); - connect(Engine::getSong(), SIGNAL(stopped()), SLOT(onSongStopped())); - connect(Engine::getSong(), SIGNAL(modified()), SLOT(onSongModified())); connect(Engine::getSong(), SIGNAL(projectFileNameChanged()), SLOT(onProjectFileNameChanged())); @@ -334,10 +324,7 @@ void MainWindow::finalize() SLOT(onExportProjectMidi()), Qt::CTRL + Qt::Key_M ); -// Prevent dangling separator at end of menu per https://bugreports.qt.io/browse/QTBUG-40071 -#if !(defined(LMMS_BUILD_APPLE) && (QT_VERSION < 0x050600)) project_menu->addSeparator(); -#endif project_menu->addAction( embed::getIconPixmap( "exit" ), tr( "&Quit" ), qApp, SLOT(closeAllWindows()), Qt::CTRL + Qt::Key_Q ); @@ -410,10 +397,7 @@ void MainWindow::finalize() this, SLOT(help())); } -// Prevent dangling separator at end of menu per https://bugreports.qt.io/browse/QTBUG-40071 -#if !(defined(LMMS_BUILD_APPLE) && (QT_VERSION < 0x050600)) help_menu->addSeparator(); -#endif help_menu->addAction( embed::getIconPixmap( "icon_small" ), tr( "About" ), this, SLOT(aboutLMMS())); @@ -570,13 +554,21 @@ void MainWindow::addSpacingToToolBar( int _size ) 7, _size ); } + + + SubWindow* MainWindow::addWindowedWidget(QWidget *w, Qt::WindowFlags windowFlags) { // wrap the widget in our own *custom* window that patches some errors in QMdiSubWindow auto win = new SubWindow(m_workspace->viewport(), windowFlags); win->setAttribute(Qt::WA_DeleteOnClose); win->setWidget(w); - if (w && w->sizeHint().isValid()) {win->resize(w->sizeHint());} + if (w && w->sizeHint().isValid()) { + auto titleBarHeight = win->titleBarHeight(); + auto frameWidth = win->frameWidth(); + QSize delta(2* frameWidth, titleBarHeight + frameWidth); + win->resize(delta + w->sizeHint()); + } m_workspace->addSubWindow(win); return win; } @@ -1110,8 +1102,7 @@ void MainWindow::updateViewMenu() // Here we should put all look&feel -stuff from configmanager // that is safe to change on the fly. There is probably some // more elegant way to do this. - QAction *qa; - qa = new QAction(tr( "Volume as dBFS" ), this); + auto qa = new QAction(tr("Volume as dBFS"), this); qa->setData("displaydbfs"); qa->setCheckable( true ); qa->setChecked( ConfigManager::inst()->value( "app", "displaydbfs" ).toInt() ); @@ -1606,42 +1597,6 @@ void MainWindow::onImportProject() } } -void MainWindow::onSongStopped() -{ - Song * song = Engine::getSong(); - Song::PlayPos const & playPos = song->getPlayPos(); - - TimeLineWidget * tl = playPos.m_timeLine; - - if( tl ) - { - SongEditorWindow* songEditor = getGUI()->songEditor(); - switch( tl->behaviourAtStop() ) - { - case TimeLineWidget::BehaviourAtStopState::BackToZero: - if( songEditor && ( tl->autoScroll() == TimeLineWidget::AutoScrollState::Enabled ) ) - { - songEditor->m_editor->updatePosition(0); - } - break; - - case TimeLineWidget::BehaviourAtStopState::BackToStart: - if( tl->savedPos() >= 0 ) - { - if(songEditor && ( tl->autoScroll() == TimeLineWidget::AutoScrollState::Enabled ) ) - { - songEditor->m_editor->updatePosition( TimePos(tl->savedPos().getTicks() ) ); - } - tl->savePos( -1 ); - } - break; - - case TimeLineWidget::BehaviourAtStopState::KeepStopPosition: - break; - } - } -} - void MainWindow::onSongModified() { // Only update the window title if the code is executed from the GUI main thread. diff --git a/src/gui/MicrotunerConfig.cpp b/src/gui/MicrotunerConfig.cpp index 4156b9e79..6bb8415bd 100644 --- a/src/gui/MicrotunerConfig.cpp +++ b/src/gui/MicrotunerConfig.cpp @@ -31,7 +31,7 @@ #include #include #include -#include +#include #include #include "ComboBox.h" @@ -342,7 +342,7 @@ bool MicrotunerConfig::validateScaleForm() { if (line.isEmpty()) {continue;} if (line[0] == '!') {continue;} // comment - QString firstSection = line.section(QRegExp("\\s+|/"), 0, 0, QString::SectionSkipEmpty); + QString firstSection = line.section(QRegularExpression("\\s+|/"), 0, 0, QString::SectionSkipEmpty); if (firstSection.contains('.')) // cent mode { bool ok = true; @@ -357,7 +357,7 @@ bool MicrotunerConfig::validateScaleForm() if (!ok) {fail(tr("Numerator of an interval defined as a ratio cannot be converted to a number")); return false;} if (line.contains('/')) { - den = line.split('/').at(1).section(QRegExp("\\s+"), 0, 0, QString::SectionSkipEmpty).toInt(&ok); + den = line.split('/').at(1).section(QRegularExpression("\\s+"), 0, 0, QString::SectionSkipEmpty).toInt(&ok); } if (!ok) {fail(tr("Denominator of an interval defined as a ratio cannot be converted to a number")); return false;} if (num * den < 0) {fail(tr("Interval defined as a ratio cannot be negative")); return false;} @@ -390,7 +390,7 @@ bool MicrotunerConfig::validateKeymapForm() { if (line.isEmpty()) {continue;} if (line[0] == '!') {continue;} // comment - QString firstSection = line.section(QRegExp("\\s+"), 0, 0, QString::SectionSkipEmpty); + QString firstSection = line.section(QRegularExpression("\\s+"), 0, 0, QString::SectionSkipEmpty); if (firstSection == "x") {continue;} // not mapped // otherwise must contain a number bool ok = true; @@ -424,7 +424,7 @@ bool MicrotunerConfig::applyScale() { if (line.isEmpty()) {continue;} if (line[0] == '!') {continue;} // comment - QString firstSection = line.section(QRegExp("\\s+|/"), 0, 0, QString::SectionSkipEmpty); + QString firstSection = line.section(QRegularExpression("\\s+|/"), 0, 0, QString::SectionSkipEmpty); if (firstSection.contains('.')) // cent mode { newIntervals.emplace_back(firstSection.toFloat()); @@ -435,7 +435,7 @@ bool MicrotunerConfig::applyScale() num = firstSection.toInt(); if (line.contains('/')) { - den = line.split('/').at(1).section(QRegExp("\\s+"), 0, 0, QString::SectionSkipEmpty).toInt(); + den = line.split('/').at(1).section(QRegularExpression("\\s+"), 0, 0, QString::SectionSkipEmpty).toInt(); } newIntervals.emplace_back(num, den); } @@ -470,7 +470,7 @@ bool MicrotunerConfig::applyKeymap() { if (line.isEmpty()) {continue;} if (line[0] == '!') {continue;} // comment - QString firstSection = line.section(QRegExp("\\s+"), 0, 0, QString::SectionSkipEmpty); + QString firstSection = line.section(QRegularExpression("\\s+"), 0, 0, QString::SectionSkipEmpty); if (firstSection == "x") { newMap.push_back(-1); // not mapped diff --git a/src/gui/MidiSetupWidget.cpp b/src/gui/MidiSetupWidget.cpp index 2385def02..0e6678727 100644 --- a/src/gui/MidiSetupWidget.cpp +++ b/src/gui/MidiSetupWidget.cpp @@ -28,7 +28,6 @@ #include #include "ConfigManager.h" -#include "gui_templates.h" namespace lmms::gui diff --git a/src/gui/MixerChannelView.cpp b/src/gui/MixerChannelView.cpp new file mode 100644 index 000000000..0afdb684e --- /dev/null +++ b/src/gui/MixerChannelView.cpp @@ -0,0 +1,485 @@ +/* + * MixerChannelView.h - the mixer channel view + * + * Copyright (c) 2022 saker + * + * 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 "CaptionMenu.h" +#include "ColorChooser.h" +#include "GuiApplication.h" +#include "Mixer.h" +#include "MixerChannelView.h" +#include "MixerView.h" +#include "Song.h" +#include "ConfigManager.h" + +#include "gui_templates.h" +#include "lmms_math.h" + +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace lmms::gui +{ + MixerChannelView::MixerChannelView(QWidget* parent, MixerView* mixerView, int channelIndex) : + QWidget(parent), + m_mixerView(mixerView), + m_channelIndex(channelIndex) + { + auto retainSizeWhenHidden = [](QWidget* widget) + { + auto sizePolicy = widget->sizePolicy(); + sizePolicy.setRetainSizeWhenHidden(true); + widget->setSizePolicy(sizePolicy); + }; + + m_sendButton = new SendButtonIndicator{this, this, mixerView}; + retainSizeWhenHidden(m_sendButton); + + m_sendKnob = new Knob{KnobType::Bright26, this, tr("Channel send amount")}; + retainSizeWhenHidden(m_sendKnob); + + m_channelNumberLcd = new LcdWidget{2, this}; + m_channelNumberLcd->setValue(channelIndex); + retainSizeWhenHidden(m_channelNumberLcd); + + const auto mixerChannel = Engine::mixer()->mixerChannel(channelIndex); + const auto mixerName = mixerChannel->m_name; + setToolTip(mixerName); + + m_renameLineEdit = new QLineEdit{mixerName, nullptr}; + m_renameLineEdit->setFixedWidth(65); + m_renameLineEdit->setFont(adjustedToPixelSize(font(), 12)); + m_renameLineEdit->setReadOnly(true); + m_renameLineEdit->installEventFilter(this); + + auto renameLineEditScene = new QGraphicsScene{}; + m_renameLineEditView = new QGraphicsView{}; + m_renameLineEditView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + m_renameLineEditView->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + m_renameLineEditView->setAttribute(Qt::WA_TransparentForMouseEvents, true); + m_renameLineEditView->setScene(renameLineEditScene); + + auto renameLineEditProxy = renameLineEditScene->addWidget(m_renameLineEdit); + renameLineEditProxy->setRotation(-90); + m_renameLineEditView->setFixedSize(m_renameLineEdit->height() + 5, m_renameLineEdit->width() + 5); + + m_sendArrow = new QLabel{}; + m_sendArrow->setPixmap(embed::getIconPixmap("send_bg_arrow")); + retainSizeWhenHidden(m_sendArrow); + m_sendArrow->setVisible(m_sendReceiveState == SendReceiveState::SendToThis); + + m_receiveArrow = new QLabel{}; + m_receiveArrow->setPixmap(embed::getIconPixmap("receive_bg_arrow")); + retainSizeWhenHidden(m_receiveArrow); + m_receiveArrow->setVisible(m_sendReceiveState == SendReceiveState::ReceiveFromThis); + + m_muteButton = new PixmapButton(this, tr("Mute")); + m_muteButton->setModel(&mixerChannel->m_muteModel); + m_muteButton->setActiveGraphic(embed::getIconPixmap("led_off")); + m_muteButton->setInactiveGraphic(embed::getIconPixmap("led_green")); + m_muteButton->setCheckable(true); + m_muteButton->setToolTip(tr("Mute this channel")); + + m_soloButton = new PixmapButton(this, tr("Solo")); + m_soloButton->setModel(&mixerChannel->m_soloModel); + m_soloButton->setActiveGraphic(embed::getIconPixmap("led_red")); + m_soloButton->setInactiveGraphic(embed::getIconPixmap("led_off")); + m_soloButton->setCheckable(true); + m_soloButton->setToolTip(tr("Solo this channel")); + + QVBoxLayout* soloMuteLayout = new QVBoxLayout(); + soloMuteLayout->setContentsMargins(0, 0, 0, 0); + soloMuteLayout->setSpacing(0); + soloMuteLayout->addWidget(m_soloButton, 0, Qt::AlignHCenter); + soloMuteLayout->addWidget(m_muteButton, 0, Qt::AlignHCenter); + + m_fader = new Fader{&mixerChannel->m_volumeModel, tr("Fader %1").arg(channelIndex), this}; + + m_effectRackView = new EffectRackView{&mixerChannel->m_fxChain, mixerView->m_racksWidget}; + m_effectRackView->setFixedWidth(EffectRackView::DEFAULT_WIDTH); + + auto mainLayout = new QVBoxLayout{this}; + mainLayout->setContentsMargins(4, 4, 4, 4); + mainLayout->addWidget(m_receiveArrow, 0, Qt::AlignHCenter); + mainLayout->addWidget(m_sendButton, 0, Qt::AlignHCenter); + mainLayout->addWidget(m_sendKnob, 0, Qt::AlignHCenter); + mainLayout->addWidget(m_sendArrow, 0, Qt::AlignHCenter); + mainLayout->addWidget(m_channelNumberLcd, 0, Qt::AlignHCenter); + mainLayout->addWidget(m_renameLineEditView, 0, Qt::AlignHCenter); + mainLayout->addLayout(soloMuteLayout, 0); + mainLayout->addWidget(m_fader, 1, Qt::AlignHCenter); + + connect(m_renameLineEdit, &QLineEdit::editingFinished, this, &MixerChannelView::renameFinished); + } + + void MixerChannelView::contextMenuEvent(QContextMenuEvent*) + { + auto contextMenu = new CaptionMenu(mixerChannel()->m_name, this); + + if (!isMasterChannel()) // no move-options in master + { + contextMenu->addAction(tr("Move &left"), this, &MixerChannelView::moveChannelLeft); + contextMenu->addAction(tr("Move &right"), this, &MixerChannelView::moveChannelRight); + } + + contextMenu->addAction(tr("Rename &channel"), this, &MixerChannelView::renameChannel); + contextMenu->addSeparator(); + + if (!isMasterChannel()) // no remove-option in master + { + contextMenu->addAction(embed::getIconPixmap("cancel"), tr("R&emove channel"), this, &MixerChannelView::removeChannel); + contextMenu->addSeparator(); + } + + contextMenu->addAction(embed::getIconPixmap("cancel"), tr("Remove &unused channels"), this, &MixerChannelView::removeUnusedChannels); + contextMenu->addSeparator(); + + auto colorMenu = QMenu{tr("Color"), this}; + colorMenu.setIcon(embed::getIconPixmap("colorize")); + colorMenu.addAction(tr("Change"), this, &MixerChannelView::selectColor); + colorMenu.addAction(tr("Reset"), this, &MixerChannelView::resetColor); + colorMenu.addAction(tr("Pick random"), this, &MixerChannelView::randomizeColor); + contextMenu->addMenu(&colorMenu); + + contextMenu->exec(QCursor::pos()); + delete contextMenu; + } + + void MixerChannelView::paintEvent(QPaintEvent* event) + { + auto * mixer = Engine::mixer(); + const auto channel = mixerChannel(); + const bool muted = channel->m_muteModel.value(); + const auto name = channel->m_name; + const auto elidedName = elideName(name); + const auto * mixerChannelView = m_mixerView->currentMixerChannel(); + const auto isActive = mixerChannelView == this; + + if (!m_inRename && m_renameLineEdit->text() != elidedName) + { + m_renameLineEdit->setText(elidedName); + } + + const auto width = rect().width(); + const auto height = rect().height(); + auto painter = QPainter{this}; + + if (channel->color().has_value() && !muted) + { + painter.fillRect(rect(), channel->color()->darker(isActive ? 120 : 150)); + } + else + { + painter.fillRect(rect(), isActive ? backgroundActive().color() : painter.background().color()); + } + + // inner border + painter.setPen(isActive ? strokeInnerActive() : strokeInnerInactive()); + painter.drawRect(1, 1, width - MIXER_CHANNEL_INNER_BORDER_SIZE, height - MIXER_CHANNEL_INNER_BORDER_SIZE); + + // outer border + painter.setPen(isActive ? strokeOuterActive() : strokeOuterInactive()); + painter.drawRect(0, 0, width - MIXER_CHANNEL_OUTER_BORDER_SIZE, height - MIXER_CHANNEL_OUTER_BORDER_SIZE); + + const auto & currentMixerChannelIndex = mixerChannelView->m_channelIndex; + const auto sendToThis = mixer->channelSendModel(currentMixerChannelIndex, m_channelIndex) != nullptr; + const auto receiveFromThis = mixer->channelSendModel(m_channelIndex, currentMixerChannelIndex) != nullptr; + const auto sendReceiveStateNone = !sendToThis && !receiveFromThis; + + // Only one or none of them can be on + assert(sendToThis ^ receiveFromThis || sendReceiveStateNone); + + m_sendArrow->setVisible(sendToThis); + m_receiveArrow->setVisible(receiveFromThis); + + if (sendReceiveStateNone) + { + setSendReceiveState(SendReceiveState::None); + } + else + { + setSendReceiveState(sendToThis ? SendReceiveState::SendToThis : SendReceiveState::ReceiveFromThis); + } + + QWidget::paintEvent(event); + } + + void MixerChannelView::mousePressEvent(QMouseEvent*) + { + if (m_mixerView->currentMixerChannel() != this) + { + m_mixerView->setCurrentMixerChannel(this); + } + } + + void MixerChannelView::mouseDoubleClickEvent(QMouseEvent*) + { + renameChannel(); + } + + bool MixerChannelView::eventFilter(QObject* dist, QEvent* event) + { + // If we are in a rename, capture the enter/return events and handle them + if (event->type() == QEvent::KeyPress) + { + auto keyEvent = static_cast(event); + if (keyEvent->key() == Qt::Key_Enter || keyEvent->key() == Qt::Key_Return) + { + if (m_inRename) + { + renameFinished(); + event->accept(); // Stop the event from propagating + return true; + } + } + } + return false; + } + + int MixerChannelView::channelIndex() const + { + return m_channelIndex; + } + + void MixerChannelView::setChannelIndex(int index) + { + MixerChannel* mixerChannel = Engine::mixer()->mixerChannel(index); + m_fader->setModel(&mixerChannel->m_volumeModel); + m_muteButton->setModel(&mixerChannel->m_muteModel); + m_soloButton->setModel(&mixerChannel->m_soloModel); + m_effectRackView->setModel(&mixerChannel->m_fxChain); + m_channelNumberLcd->setValue(index); + m_channelIndex = index; + } + + MixerChannelView::SendReceiveState MixerChannelView::sendReceiveState() const + { + return m_sendReceiveState; + } + + void MixerChannelView::setSendReceiveState(const SendReceiveState& state) + { + m_sendReceiveState = state; + m_sendArrow->setVisible(state == SendReceiveState::SendToThis); + m_receiveArrow->setVisible(state == SendReceiveState::ReceiveFromThis); + } + + QBrush MixerChannelView::backgroundActive() const + { + return m_backgroundActive; + } + + void MixerChannelView::setBackgroundActive(const QBrush& c) + { + m_backgroundActive = c; + } + + QColor MixerChannelView::strokeOuterActive() const + { + return m_strokeOuterActive; + } + + void MixerChannelView::setStrokeOuterActive(const QColor& c) + { + m_strokeOuterActive = c; + } + + QColor MixerChannelView::strokeOuterInactive() const + { + return m_strokeOuterInactive; + } + + void MixerChannelView::setStrokeOuterInactive(const QColor& c) + { + m_strokeOuterInactive = c; + } + + QColor MixerChannelView::strokeInnerActive() const + { + return m_strokeInnerActive; + } + + void MixerChannelView::setStrokeInnerActive(const QColor& c) + { + m_strokeInnerActive = c; + } + + QColor MixerChannelView::strokeInnerInactive() const + { + return m_strokeInnerInactive; + } + + void MixerChannelView::setStrokeInnerInactive(const QColor& c) + { + m_strokeInnerInactive = c; + } + + void MixerChannelView::renameChannel() + { + m_inRename = true; + setToolTip(""); + m_renameLineEdit->setReadOnly(false); + + m_channelNumberLcd->hide(); + m_renameLineEdit->setFixedWidth(m_renameLineEdit->width()); + m_renameLineEdit->setText(mixerChannel()->m_name); + + m_renameLineEditView->setFocus(); + m_renameLineEdit->selectAll(); + m_renameLineEdit->setFocus(); + } + + void MixerChannelView::renameFinished() + { + m_inRename = false; + + m_renameLineEdit->deselect(); + m_renameLineEdit->setReadOnly(true); + m_renameLineEdit->setFixedWidth(m_renameLineEdit->width()); + m_channelNumberLcd->show(); + + auto newName = m_renameLineEdit->text(); + setFocus(); + + const auto mc = mixerChannel(); + if (!newName.isEmpty() && mc->m_name != newName) + { + mc->m_name = newName; + m_renameLineEdit->setText(elideName(newName)); + Engine::getSong()->setModified(); + } + + setToolTip(mc->m_name); + } + + void MixerChannelView::resetColor() + { + mixerChannel()->setColor(std::nullopt); + Engine::getSong()->setModified(); + update(); + } + + void MixerChannelView::selectColor() + { + const auto channel = mixerChannel(); + + const auto initialColor = channel->color().value_or(backgroundActive().color()); + const auto * colorChooser = ColorChooser{this}.withPalette(ColorChooser::Palette::Mixer); + const auto newColor = colorChooser->getColor(initialColor); + + if (!newColor.isValid()) { return; } + + channel->setColor(newColor); + + Engine::getSong()->setModified(); + update(); + } + + void MixerChannelView::randomizeColor() + { + auto channel = mixerChannel(); + channel->setColor(ColorChooser::getPalette(ColorChooser::Palette::Mixer)[rand() % 48]); + Engine::getSong()->setModified(); + update(); + } + + bool MixerChannelView::confirmRemoval(int index) + { + // if config variable is set to false, there is no need for user confirmation + bool needConfirm = ConfigManager::inst()->value("ui", "mixerchanneldeletionwarning", "1").toInt(); + if (!needConfirm) { return true; } + + // is the channel is not in use, there is no need for user confirmation + if (!getGUI()->mixerView()->getMixer()->isChannelInUse(index)) { return true; } + + QString messageRemoveTrack = tr("This Mixer Channel is being used.\n" + "Are you sure you want to remove this channel?\n\n" + "Warning: This operation can not be undone."); + + QString messageTitleRemoveTrack = tr("Confirm removal"); + QString askAgainText = tr("Don't ask again"); + auto askAgainCheckBox = new QCheckBox(askAgainText, nullptr); + 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"); + }); + + QMessageBox mb(this); + mb.setText(messageRemoveTrack); + mb.setWindowTitle(messageTitleRemoveTrack); + mb.setIcon(QMessageBox::Warning); + mb.addButton(QMessageBox::Cancel); + mb.addButton(QMessageBox::Ok); + mb.setCheckBox(askAgainCheckBox); + mb.setDefaultButton(QMessageBox::Cancel); + + int answer = mb.exec(); + + return answer == QMessageBox::Ok; + } + + void MixerChannelView::removeChannel() + { + if (!confirmRemoval(m_channelIndex)) { return; } + auto mix = getGUI()->mixerView(); + mix->deleteChannel(m_channelIndex); + } + + void MixerChannelView::removeUnusedChannels() + { + auto mix = getGUI()->mixerView(); + mix->deleteUnusedChannels(); + } + + void MixerChannelView::moveChannelLeft() + { + auto mix = getGUI()->mixerView(); + mix->moveChannelLeft(m_channelIndex); + } + + void MixerChannelView::moveChannelRight() + { + auto mix = getGUI()->mixerView(); + mix->moveChannelRight(m_channelIndex); + } + + QString MixerChannelView::elideName(const QString& name) + { + const auto maxTextHeight = m_renameLineEdit->width(); + const auto metrics = QFontMetrics{m_renameLineEdit->font()}; + const auto elidedName = metrics.elidedText(name, Qt::ElideRight, maxTextHeight); + return elidedName; + } + + MixerChannel* MixerChannelView::mixerChannel() const + { + return Engine::mixer()->mixerChannel(m_channelIndex); + } + +} // namespace lmms::gui \ No newline at end of file diff --git a/src/gui/MixerLine.cpp b/src/gui/MixerLine.cpp deleted file mode 100644 index a90f13f83..000000000 --- a/src/gui/MixerLine.cpp +++ /dev/null @@ -1,465 +0,0 @@ -/* - * MixerLine.cpp - Mixer line widget - * - * Copyright (c) 2009 Andrew Kelley - * Copyright (c) 2014 Tobias Doerffel - * - * This file is part of LMMS - https://lmms.io - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public - * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public - * License along with this program (see COPYING); if not, write to the - * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301 USA. - * - */ - -#include "MixerLine.h" - -#include - -#include -#include -#include -#include - -#include "CaptionMenu.h" -#include "ColorChooser.h" -#include "embed.h" -#include "Knob.h" -#include "LcdWidget.h" -#include "Mixer.h" -#include "MixerView.h" -#include "gui_templates.h" -#include "GuiApplication.h" -#include "SendButtonIndicator.h" -#include "Song.h" - -namespace lmms::gui -{ - - -bool MixerLine::eventFilter( QObject *dist, QEvent *event ) -{ - // If we are in a rename, capture the enter/return events and handle them - if ( event->type() == QEvent::KeyPress ) - { - auto keyEvent = static_cast(event); - if( keyEvent->key() == Qt::Key_Enter || keyEvent->key() == Qt::Key_Return ) - { - if( m_inRename ) - { - renameFinished(); - event->accept(); // Stop the event from propagating - return true; - } - } - } - return false; -} - -const int MixerLine::MixerLineHeight = 287; -QPixmap * MixerLine::s_sendBgArrow = nullptr; -QPixmap * MixerLine::s_receiveBgArrow = nullptr; - -MixerLine::MixerLine( QWidget * _parent, MixerView * _mv, int _channelIndex ) : - QWidget( _parent ), - m_mv( _mv ), - m_channelIndex( _channelIndex ), - m_backgroundActive( Qt::SolidPattern ), - m_strokeOuterActive( 0, 0, 0 ), - m_strokeOuterInactive( 0, 0, 0 ), - m_strokeInnerActive( 0, 0, 0 ), - m_strokeInnerInactive( 0, 0, 0 ), - m_inRename( false ) -{ - if( !s_sendBgArrow ) - { - s_sendBgArrow = new QPixmap( embed::getIconPixmap( "send_bg_arrow", 29, 56 ) ); - } - if( !s_receiveBgArrow ) - { - s_receiveBgArrow = new QPixmap( embed::getIconPixmap( "receive_bg_arrow", 29, 56 ) ); - } - - setFixedSize( 33, MixerLineHeight ); - setAttribute( Qt::WA_OpaquePaintEvent, true ); - setCursor( QCursor( embed::getIconPixmap( "hand" ), 3, 3 ) ); - - // mixer sends knob - m_sendKnob = new Knob( KnobType::Bright26, this, tr( "Channel send amount" ) ); - m_sendKnob->move( 3, 22 ); - m_sendKnob->setVisible( false ); - - // send button indicator - m_sendBtn = new SendButtonIndicator( this, this, m_mv ); - m_sendBtn->move( 2, 2 ); - - // channel number - m_lcd = new LcdWidget( 2, this ); - m_lcd->setValue( m_channelIndex ); - m_lcd->move( 4, 58 ); - m_lcd->setMarginWidth( 1 ); - - QString name = Engine::mixer()->mixerChannel( m_channelIndex )->m_name; - setToolTip( name ); - - m_renameLineEdit = new QLineEdit(); - m_renameLineEdit->setText( name ); - m_renameLineEdit->setFixedWidth( 65 ); - m_renameLineEdit->setFont( pointSizeF( font(), 7.5f ) ); - m_renameLineEdit->setReadOnly( true ); - m_renameLineEdit->installEventFilter( this ); - - auto scene = new QGraphicsScene(); - scene->setSceneRect( 0, 0, 33, MixerLineHeight ); - - m_view = new QGraphicsView( this ); - m_view->setStyleSheet( "border-style: none; background: transparent;" ); - m_view->setHorizontalScrollBarPolicy( Qt::ScrollBarAlwaysOff ); - m_view->setVerticalScrollBarPolicy( Qt::ScrollBarAlwaysOff ); - m_view->setAttribute( Qt::WA_TransparentForMouseEvents, true ); - m_view->setScene( scene ); - - QGraphicsProxyWidget * proxyWidget = scene->addWidget( m_renameLineEdit ); - proxyWidget->setRotation( -90 ); - proxyWidget->setPos( 8, 145 ); - - connect( m_renameLineEdit, SIGNAL(editingFinished()), this, SLOT(renameFinished())); - connect( &Engine::mixer()->mixerChannel( m_channelIndex )->m_muteModel, SIGNAL(dataChanged()), this, SLOT(update())); -} - - - - -MixerLine::~MixerLine() -{ - delete m_sendKnob; - delete m_sendBtn; - delete m_lcd; -} - - - - -void MixerLine::setChannelIndex( int index ) -{ - m_channelIndex = index; - m_lcd->setValue( m_channelIndex ); - m_lcd->update(); -} - - - -void MixerLine::drawMixerLine( QPainter* p, const MixerLine *mixerLine, bool isActive, bool sendToThis, bool receiveFromThis ) -{ - auto channel = Engine::mixer()->mixerChannel( m_channelIndex ); - bool muted = channel->m_muteModel.value(); - QString name = channel->m_name; - QString elidedName = elideName( name ); - if( !m_inRename && m_renameLineEdit->text() != elidedName ) - { - m_renameLineEdit->setText( elidedName ); - } - - int width = mixerLine->rect().width(); - int height = mixerLine->rect().height(); - - if( channel->m_hasColor && !muted ) - { - p->fillRect( mixerLine->rect(), channel->m_color.darker( isActive ? 120 : 150 ) ); - } - else - { - p->fillRect( mixerLine->rect(), - isActive ? mixerLine->backgroundActive().color() : p->background().color() ); - } - - // inner border - p->setPen( isActive ? mixerLine->strokeInnerActive() : mixerLine->strokeInnerInactive() ); - p->drawRect( 1, 1, width-3, height-3 ); - - // outer border - p->setPen( isActive ? mixerLine->strokeOuterActive() : mixerLine->strokeOuterInactive() ); - p->drawRect( 0, 0, width-1, height-1 ); - - // draw the mixer send background - if( sendToThis ) - { - p->drawPixmap( 2, 0, 29, 56, *s_sendBgArrow ); - } - else if( receiveFromThis ) - { - p->drawPixmap( 2, 0, 29, 56, *s_receiveBgArrow ); - } -} - - - - -QString MixerLine::elideName( const QString & name ) -{ - const int maxTextHeight = 60; - QFontMetrics metrics( m_renameLineEdit->font() ); - QString elidedName = metrics.elidedText( name, Qt::ElideRight, maxTextHeight ); - return elidedName; -} - - - - -void MixerLine::paintEvent( QPaintEvent * ) -{ - bool sendToThis = Engine::mixer()->channelSendModel( m_mv->currentMixerLine()->m_channelIndex, m_channelIndex ) != nullptr; - bool receiveFromThis = Engine::mixer()->channelSendModel( m_channelIndex, m_mv->currentMixerLine()->m_channelIndex ) != nullptr; - QPainter painter; - painter.begin( this ); - drawMixerLine( &painter, this, m_mv->currentMixerLine() == this, sendToThis, receiveFromThis ); - painter.end(); -} - - - - -void MixerLine::mousePressEvent( QMouseEvent * ) -{ - m_mv->setCurrentMixerLine( this ); -} - - - - -void MixerLine::mouseDoubleClickEvent( QMouseEvent * ) -{ - renameChannel(); -} - - - - -void MixerLine::contextMenuEvent( QContextMenuEvent * ) -{ - QPointer contextMenu = new CaptionMenu( Engine::mixer()->mixerChannel( m_channelIndex )->m_name, this ); - if( m_channelIndex != 0 ) // no move-options in master - { - contextMenu->addAction( tr( "Move &left" ), this, SLOT(moveChannelLeft())); - contextMenu->addAction( tr( "Move &right" ), this, SLOT(moveChannelRight())); - } - contextMenu->addAction( tr( "Rename &channel" ), this, SLOT(renameChannel())); - contextMenu->addSeparator(); - - if( m_channelIndex != 0 ) // no remove-option in master - { - contextMenu->addAction( embed::getIconPixmap( "cancel" ), tr( "R&emove channel" ), this, SLOT(removeChannel())); - contextMenu->addSeparator(); - } - contextMenu->addAction( embed::getIconPixmap( "cancel" ), tr( "Remove &unused channels" ), this, SLOT(removeUnusedChannels())); - contextMenu->addSeparator(); - - QMenu colorMenu(tr("Color"), this); - colorMenu.setIcon(embed::getIconPixmap("colorize")); - colorMenu.addAction(tr("Change"), this, SLOT(selectColor())); - colorMenu.addAction(tr("Reset"), this, SLOT(resetColor())); - colorMenu.addAction(tr("Pick random"), this, SLOT(randomizeColor())); - contextMenu->addMenu(&colorMenu); - - contextMenu->exec( QCursor::pos() ); - delete contextMenu; -} - - - - -void MixerLine::renameChannel() -{ - m_inRename = true; - setToolTip( "" ); - m_renameLineEdit->setReadOnly( false ); - m_lcd->hide(); - m_renameLineEdit->setFixedWidth( 135 ); - m_renameLineEdit->setText( Engine::mixer()->mixerChannel( m_channelIndex )->m_name ); - m_view->setFocus(); - m_renameLineEdit->selectAll(); - m_renameLineEdit->setFocus(); -} - - - - -void MixerLine::renameFinished() -{ - m_inRename = false; - m_renameLineEdit->deselect(); - m_renameLineEdit->setReadOnly( true ); - m_renameLineEdit->setFixedWidth( 65 ); - m_lcd->show(); - QString newName = m_renameLineEdit->text(); - setFocus(); - if( !newName.isEmpty() && Engine::mixer()->mixerChannel( m_channelIndex )->m_name != newName ) - { - Engine::mixer()->mixerChannel( m_channelIndex )->m_name = newName; - m_renameLineEdit->setText( elideName( newName ) ); - Engine::getSong()->setModified(); - } - QString name = Engine::mixer()->mixerChannel( m_channelIndex )->m_name; - setToolTip( name ); -} - - - - -void MixerLine::removeChannel() -{ - MixerView * mix = getGUI()->mixerView(); - mix->deleteChannel( m_channelIndex ); -} - - - - -void MixerLine::removeUnusedChannels() -{ - MixerView * mix = getGUI()->mixerView(); - mix->deleteUnusedChannels(); -} - - - - -void MixerLine::moveChannelLeft() -{ - MixerView * mix = getGUI()->mixerView(); - mix->moveChannelLeft( m_channelIndex ); -} - - - - -void MixerLine::moveChannelRight() -{ - MixerView * mix = getGUI()->mixerView(); - mix->moveChannelRight( m_channelIndex ); -} - - - - -QBrush MixerLine::backgroundActive() const -{ - return m_backgroundActive; -} - - - - -void MixerLine::setBackgroundActive( const QBrush & c ) -{ - m_backgroundActive = c; -} - - - - -QColor MixerLine::strokeOuterActive() const -{ - return m_strokeOuterActive; -} - - - - -void MixerLine::setStrokeOuterActive( const QColor & c ) -{ - m_strokeOuterActive = c; -} - - - - -QColor MixerLine::strokeOuterInactive() const -{ - return m_strokeOuterInactive; -} - - - - -void MixerLine::setStrokeOuterInactive( const QColor & c ) -{ - m_strokeOuterInactive = c; -} - - - - -QColor MixerLine::strokeInnerActive() const -{ - return m_strokeInnerActive; -} - - - - -void MixerLine::setStrokeInnerActive( const QColor & c ) -{ - m_strokeInnerActive = c; -} - - - - -QColor MixerLine::strokeInnerInactive() const -{ - return m_strokeInnerInactive; -} - - - - -void MixerLine::setStrokeInnerInactive( const QColor & c ) -{ - m_strokeInnerInactive = c; -} - - -// Ask user for a color, and set it as the mixer line color -void MixerLine::selectColor() -{ - auto channel = Engine::mixer()->mixerChannel( m_channelIndex ); - auto new_color = ColorChooser(this).withPalette(ColorChooser::Palette::Mixer)->getColor(channel->m_color); - if(!new_color.isValid()) { return; } - channel->setColor (new_color); - Engine::getSong()->setModified(); - update(); -} - - -// Disable the usage of color on this mixer line -void MixerLine::resetColor() -{ - Engine::mixer()->mixerChannel( m_channelIndex )->m_hasColor = false; - Engine::getSong()->setModified(); - update(); -} - - -// Pick a random color from the mixer palette and set it as our color -void MixerLine::randomizeColor() -{ - auto channel = Engine::mixer()->mixerChannel( m_channelIndex ); - channel->setColor (ColorChooser::getPalette(ColorChooser::Palette::Mixer)[rand() % 48]); - Engine::getSong()->setModified(); - update(); -} - - -} // namespace lmms::gui diff --git a/src/gui/MixerView.cpp b/src/gui/MixerView.cpp index 018e72c2b..b9a698a96 100644 --- a/src/gui/MixerView.cpp +++ b/src/gui/MixerView.cpp @@ -23,9 +23,7 @@ */ -#include #include -#include #include #include #include @@ -33,9 +31,9 @@ #include "lmms_math.h" +#include "MixerChannelView.h" #include "MixerView.h" #include "Knob.h" -#include "MixerLine.h" #include "Mixer.h" #include "GuiApplication.h" #include "MainWindow.h" @@ -52,10 +50,11 @@ namespace lmms::gui { -MixerView::MixerView() : +MixerView::MixerView(Mixer* mixer) : QWidget(), - ModelView( nullptr, this ), - SerializingObjectHook() + ModelView(nullptr, this), + SerializingObjectHook(), + m_mixer(mixer) { #if QT_VERSION < 0x50C00 // Workaround for a bug in Qt versions below 5.12, @@ -67,55 +66,56 @@ MixerView::MixerView() : using ::operator|; #endif - Mixer * m = Engine::mixer(); - m->setHook( this ); + mixer->setHook(this); //QPalette pal = palette(); - //pal.setColor( QPalette::Window, QColor( 72, 76, 88 ) ); - //setPalette( pal ); + //pal.setColor(QPalette::Window, QColor(72, 76, 88)); + //setPalette(pal); - setAutoFillBackground( true ); - setSizePolicy( QSizePolicy::Preferred, QSizePolicy::Fixed ); + setAutoFillBackground(true); - setWindowTitle( tr( "Mixer" ) ); - setWindowIcon( embed::getIconPixmap( "mixer" ) ); + setWindowTitle(tr("Mixer")); + setWindowIcon(embed::getIconPixmap("mixer")); // main-layout - auto ml = new QHBoxLayout; + auto ml = new QHBoxLayout{this}; // Set margins - ml->setContentsMargins( 0, 4, 0, 0 ); + ml->setContentsMargins(0, 4, 0, 0); // Channel area m_channelAreaWidget = new QWidget; - chLayout = new QHBoxLayout( m_channelAreaWidget ); - chLayout->setSizeConstraint( QLayout::SetMinimumSize ); - chLayout->setSpacing( 0 ); + chLayout = new QHBoxLayout(m_channelAreaWidget); + chLayout->setSizeConstraint(QLayout::SetMinimumSize); + chLayout->setSpacing(0); chLayout->setContentsMargins(0, 0, 0, 0); + chLayout->setAlignment(Qt::AlignLeft); m_channelAreaWidget->setLayout(chLayout); // create rack layout before creating the first channel m_racksWidget = new QWidget; - m_racksLayout = new QStackedLayout( m_racksWidget ); - m_racksLayout->setContentsMargins( 0, 0, 0, 0 ); - m_racksWidget->setLayout( m_racksLayout ); + m_racksLayout = new QStackedLayout(m_racksWidget); + m_racksLayout->setContentsMargins(0, 0, 0, 0); + m_racksWidget->setLayout(m_racksLayout); // add master channel - m_mixerChannelViews.resize( m->numChannels() ); - m_mixerChannelViews[0] = new MixerChannelView( this, this, 0 ); + m_mixerChannelViews.resize(mixer->numChannels()); + MixerChannelView * masterView = new MixerChannelView(this, this, 0); + connectToSoloAndMute(0); + m_mixerChannelViews[0] = masterView; - m_racksLayout->addWidget( m_mixerChannelViews[0]->m_rackView ); + m_racksLayout->addWidget(m_mixerChannelViews[0]->m_effectRackView); - MixerChannelView * masterView = m_mixerChannelViews[0]; - ml->addWidget( masterView->m_mixerLine, 0, Qt::AlignTop ); + ml->addWidget(masterView, 0); - QSize mixerLineSize = masterView->m_mixerLine->size(); + auto mixerChannelSize = masterView->sizeHint(); // add mixer channels - for( int i = 1; i < m_mixerChannelViews.size(); ++i ) + for (int i = 1; i < m_mixerChannelViews.size(); ++i) { m_mixerChannelViews[i] = new MixerChannelView(m_channelAreaWidget, this, i); - chLayout->addWidget( m_mixerChannelViews[i]->m_mixerLine ); + connectToSoloAndMute(i); + chLayout->addWidget(m_mixerChannelViews[i]); } // add the scrolling section to the main layout @@ -123,83 +123,76 @@ MixerView::MixerView() : class ChannelArea : public QScrollArea { public: - ChannelArea( QWidget * parent, MixerView * mv ) : - QScrollArea( parent ), m_mv( mv ) {} + ChannelArea(QWidget* parent, MixerView* mv) : + QScrollArea(parent), m_mv(mv) {} ~ChannelArea() override = default; - void keyPressEvent( QKeyEvent * e ) override + void keyPressEvent(QKeyEvent* e) override { - m_mv->keyPressEvent( e ); + m_mv->keyPressEvent(e); } private: - MixerView * m_mv; + MixerView* m_mv; }; - channelArea = new ChannelArea( this, this ); - channelArea->setWidget( m_channelAreaWidget ); - channelArea->setVerticalScrollBarPolicy( Qt::ScrollBarAlwaysOff ); - channelArea->setFrameStyle( QFrame::NoFrame ); - channelArea->setMinimumWidth( mixerLineSize.width() * 6 ); - channelArea->setFixedHeight( mixerLineSize.height() + - style()->pixelMetric( QStyle::PM_ScrollBarExtent ) ); - ml->addWidget( channelArea, 1, Qt::AlignTop ); + channelArea = new ChannelArea(this, this); + channelArea->setWidget(m_channelAreaWidget); + channelArea->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + channelArea->setFrameStyle(QFrame::NoFrame); + channelArea->setMinimumWidth(mixerChannelSize.width() * 6); + channelArea->setWidgetResizable(true); + + int const scrollBarExtent = style()->pixelMetric(QStyle::PM_ScrollBarExtent); + channelArea->setMinimumHeight(mixerChannelSize.height() + scrollBarExtent); + + ml->addWidget(channelArea, 1); // show the add new mixer channel button auto newChannelBtn = new QPushButton(embed::getIconPixmap("new_channel"), QString(), this); - newChannelBtn->setObjectName( "newChannelBtn" ); - newChannelBtn->setFixedSize( mixerLineSize ); - connect( newChannelBtn, SIGNAL(clicked()), this, SLOT(addNewChannel())); - ml->addWidget( newChannelBtn, 0, Qt::AlignTop ); + newChannelBtn->setObjectName("newChannelBtn"); + newChannelBtn->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Expanding); + newChannelBtn->setFixedWidth(mixerChannelSize.width()); + connect(newChannelBtn, SIGNAL(clicked()), this, SLOT(addNewChannel())); + ml->addWidget(newChannelBtn, 0); // add the stacked layout for the effect racks of mixer channels - ml->addWidget( m_racksWidget, 0, Qt::AlignTop | Qt::AlignRight ); + ml->addWidget(m_racksWidget); - setCurrentMixerLine( m_mixerChannelViews[0]->m_mixerLine ); + setCurrentMixerChannel(m_mixerChannelViews[0]); - setLayout( ml ); updateGeometry(); - // timer for updating faders - connect( getGUI()->mainWindow(), SIGNAL(periodicUpdate()), - this, SLOT(updateFaders())); + auto* mainWindow = getGUI()->mainWindow(); + // timer for updating faders + connect(mainWindow, &MainWindow::periodicUpdate, this, &MixerView::updateFaders); // add ourself to workspace - QMdiSubWindow * subWin = getGUI()->mainWindow()->addWindowedWidget( this ); - Qt::WindowFlags flags = subWin->windowFlags(); - flags &= ~Qt::WindowMaximizeButtonHint; - subWin->setWindowFlags( flags ); - layout()->setSizeConstraint( QLayout::SetMinimumSize ); - subWin->layout()->setSizeConstraint( QLayout::SetMinAndMaxSize ); + QMdiSubWindow* subWin = mainWindow->addWindowedWidget(this); + layout()->setSizeConstraint(QLayout::SetMinimumSize); + subWin->layout()->setSizeConstraint(QLayout::SetMinAndMaxSize); - parentWidget()->setAttribute( Qt::WA_DeleteOnClose, false ); - parentWidget()->move( 5, 310 ); + parentWidget()->setAttribute(Qt::WA_DeleteOnClose, false); + parentWidget()->move(5, 310); // we want to receive dataChanged-signals in order to update - setModel( m ); + setModel(mixer); } -MixerView::~MixerView() -{ - for (auto mixerChannelView : m_mixerChannelViews) - { - delete mixerChannelView; - } -} int MixerView::addNewChannel() { // add new mixer channel and redraw the form. - Mixer * mix = Engine::mixer(); + Mixer * mix = getMixer(); int newChannelIndex = mix->createChannel(); - m_mixerChannelViews.push_back(new MixerChannelView(m_channelAreaWidget, this, - newChannelIndex)); - chLayout->addWidget( m_mixerChannelViews[newChannelIndex]->m_mixerLine ); - m_racksLayout->addWidget( m_mixerChannelViews[newChannelIndex]->m_rackView ); + m_mixerChannelViews.push_back(new MixerChannelView(m_channelAreaWidget, this, newChannelIndex)); + connectToSoloAndMute(newChannelIndex); + chLayout->addWidget(m_mixerChannelViews[newChannelIndex]); + m_racksLayout->addWidget(m_mixerChannelViews[newChannelIndex]->m_effectRackView); - updateMixerLine(newChannelIndex); + updateMixerChannel(newChannelIndex); updateMaxChannelSelector(); @@ -210,35 +203,37 @@ int MixerView::addNewChannel() void MixerView::refreshDisplay() { // delete all views and re-add them - for( int i = 1; iremoveWidget(m_mixerChannelViews[i]->m_mixerLine); - m_racksLayout->removeWidget( m_mixerChannelViews[i]->m_rackView ); - delete m_mixerChannelViews[i]->m_fader; - delete m_mixerChannelViews[i]->m_muteBtn; - delete m_mixerChannelViews[i]->m_soloBtn; - delete m_mixerChannelViews[i]->m_mixerLine; - delete m_mixerChannelViews[i]->m_rackView; - delete m_mixerChannelViews[i]; + // First disconnect from the solo/mute models. + disconnectFromSoloAndMute(i); + + auto * mixerChannelView = m_mixerChannelViews[i]; + chLayout->removeWidget(mixerChannelView); + m_racksLayout->removeWidget(mixerChannelView->m_effectRackView); + + delete mixerChannelView; } m_channelAreaWidget->adjustSize(); // re-add the views - m_mixerChannelViews.resize(Engine::mixer()->numChannels()); - for( int i = 1; i < m_mixerChannelViews.size(); ++i ) + m_mixerChannelViews.resize(getMixer()->numChannels()); + for (int i = 1; i < m_mixerChannelViews.size(); ++i) { m_mixerChannelViews[i] = new MixerChannelView(m_channelAreaWidget, this, i); - chLayout->addWidget(m_mixerChannelViews[i]->m_mixerLine); - m_racksLayout->addWidget( m_mixerChannelViews[i]->m_rackView ); + connectToSoloAndMute(i); + + chLayout->addWidget(m_mixerChannelViews[i]); + m_racksLayout->addWidget(m_mixerChannelViews[i]->m_effectRackView); } - // set selected mixer line to 0 - setCurrentMixerLine( 0 ); + // set selected mixer channel to 0 + setCurrentMixerChannel(0); // update all mixer lines - for( int i = 0; i < m_mixerChannelViews.size(); ++i ) + for (int i = 0; i < m_mixerChannelViews.size(); ++i) { - updateMixerLine( i ); + updateMixerChannel(i); } updateMaxChannelSelector(); @@ -272,108 +267,91 @@ void MixerView::updateMaxChannelSelector() } -void MixerView::saveSettings( QDomDocument & _doc, QDomElement & _this ) +void MixerView::saveSettings(QDomDocument& doc, QDomElement& domElement) { - MainWindow::saveWidgetState( this, _this ); + MainWindow::saveWidgetState(this, domElement); } -void MixerView::loadSettings( const QDomElement & _this ) +void MixerView::loadSettings(const QDomElement& domElement) { - MainWindow::restoreWidgetState( this, _this ); + MainWindow::restoreWidgetState(this, domElement); } -MixerView::MixerChannelView::MixerChannelView(QWidget * _parent, MixerView * _mv, - int channelIndex ) -{ - m_mixerLine = new MixerLine(_parent, _mv, channelIndex); - MixerChannel *mixerChannel = Engine::mixer()->mixerChannel(channelIndex); - - m_fader = new Fader( &mixerChannel->m_volumeModel, - tr( "Fader %1" ).arg( channelIndex ), m_mixerLine ); - m_fader->setLevelsDisplayedInDBFS(); - m_fader->setMinPeak(dbfsToAmp(-42)); - m_fader->setMaxPeak(dbfsToAmp(9)); - - m_fader->move( 16-m_fader->width()/2, - m_mixerLine->height()- - m_fader->height()-5 ); - - m_muteBtn = new PixmapButton( m_mixerLine, tr( "Mute" ) ); - m_muteBtn->setModel( &mixerChannel->m_muteModel ); - m_muteBtn->setActiveGraphic( - embed::getIconPixmap( "led_off" ) ); - m_muteBtn->setInactiveGraphic( - embed::getIconPixmap( "led_green" ) ); - m_muteBtn->setCheckable( true ); - m_muteBtn->move( 9, m_fader->y()-11); - m_muteBtn->setToolTip(tr("Mute this channel")); - - m_soloBtn = new PixmapButton( m_mixerLine, tr( "Solo" ) ); - m_soloBtn->setModel( &mixerChannel->m_soloModel ); - m_soloBtn->setActiveGraphic( - embed::getIconPixmap( "led_red" ) ); - m_soloBtn->setInactiveGraphic( - embed::getIconPixmap( "led_off" ) ); - m_soloBtn->setCheckable( true ); - m_soloBtn->move( 9, m_fader->y()-21); - connect(&mixerChannel->m_soloModel, SIGNAL(dataChanged()), - _mv, SLOT ( toggledSolo() ), Qt::DirectConnection ); - m_soloBtn->setToolTip(tr("Solo this channel")); - - // Create EffectRack for the channel - m_rackView = new EffectRackView( &mixerChannel->m_fxChain, _mv->m_racksWidget ); - m_rackView->setFixedSize( EffectRackView::DEFAULT_WIDTH, MixerLine::MixerLineHeight ); -} - - -void MixerView::MixerChannelView::setChannelIndex( int index ) -{ - MixerChannel* mixerChannel = Engine::mixer()->mixerChannel( index ); - - m_fader->setModel( &mixerChannel->m_volumeModel ); - m_muteBtn->setModel( &mixerChannel->m_muteModel ); - m_soloBtn->setModel( &mixerChannel->m_soloModel ); - m_rackView->setModel( &mixerChannel->m_fxChain ); -} void MixerView::toggledSolo() { - Engine::mixer()->toggledSolo(); + getMixer()->toggledSolo(); + + updateAllMixerChannels(); } +void MixerView::toggledMute() +{ + updateAllMixerChannels(); +} -void MixerView::setCurrentMixerLine( MixerLine * _line ) +Mixer* MixerView::getMixer() const +{ + return m_mixer; +} + +void MixerView::updateAllMixerChannels() +{ + for (int i = 0; i < m_mixerChannelViews.size(); ++i) + { + m_mixerChannelViews[i]->update(); + } +} + +void MixerView::connectToSoloAndMute(int channelIndex) +{ + auto * mixerChannel = getMixer()->mixerChannel(channelIndex); + + connect(&mixerChannel->m_muteModel, &BoolModel::dataChanged, this, &MixerView::toggledMute, Qt::DirectConnection); + connect(&mixerChannel->m_soloModel, &BoolModel::dataChanged, this, &MixerView::toggledSolo, Qt::DirectConnection); +} + +void MixerView::disconnectFromSoloAndMute(int channelIndex) +{ + auto * mixerChannel = getMixer()->mixerChannel(channelIndex); + + disconnect(&mixerChannel->m_muteModel, &BoolModel::dataChanged, this, &MixerView::toggledMute); + disconnect(&mixerChannel->m_soloModel, &BoolModel::dataChanged, this, &MixerView::toggledSolo); +} + + +void MixerView::setCurrentMixerChannel(MixerChannelView* channel) { // select - m_currentMixerLine = _line; - m_racksLayout->setCurrentWidget( m_mixerChannelViews[ _line->channelIndex() ]->m_rackView ); + m_currentMixerChannel = channel; + m_racksLayout->setCurrentWidget(m_mixerChannelViews[channel->channelIndex()]->m_effectRackView); // set up send knob - for(int i = 0; i < m_mixerChannelViews.size(); ++i) + for (int i = 0; i < m_mixerChannelViews.size(); ++i) { - updateMixerLine(i); + updateMixerChannel(i); } } -void MixerView::updateMixerLine(int index) +void MixerView::updateMixerChannel(int index) { - Mixer * mix = Engine::mixer(); + Mixer * mix = getMixer(); // does current channel send to this channel? - int selIndex = m_currentMixerLine->channelIndex(); - MixerLine * thisLine = m_mixerChannelViews[index]->m_mixerLine; - thisLine->setToolTip( Engine::mixer()->mixerChannel( index )->m_name ); + int selIndex = m_currentMixerChannel->channelIndex(); + auto thisLine = m_mixerChannelViews[index]; + thisLine->setToolTip(getMixer()->mixerChannel(index)->m_name); FloatModel * sendModel = mix->channelSendModel(selIndex, index); - if( sendModel == nullptr ) + if (sendModel == nullptr) { // does not send, hide send knob thisLine->m_sendKnob->setVisible(false); @@ -386,8 +364,8 @@ void MixerView::updateMixerLine(int index) } // disable the send button if it would cause an infinite loop - thisLine->m_sendBtn->setVisible(! mix->isInfiniteLoop(selIndex, index)); - thisLine->m_sendBtn->updateLightStatus(); + thisLine->m_sendButton->setVisible(!mix->isInfiniteLoop(selIndex, index)); + thisLine->m_sendButton->updateLightStatus(); thisLine->update(); } @@ -395,41 +373,33 @@ void MixerView::updateMixerLine(int index) void MixerView::deleteChannel(int index) { // can't delete master - if( index == 0 ) return; + if (index == 0) return; - // if there is no user confirmation, do nothing - if (!confirmRemoval(index)) - { - return; - } + // Disconnect from the solo/mute models of the channel we are about to delete + disconnectFromSoloAndMute(index); // remember selected line - int selLine = m_currentMixerLine->channelIndex(); + int selLine = m_currentMixerChannel->channelIndex(); + Mixer* mixer = getMixer(); // in case the deleted channel is soloed or the remaining // channels will be left in a muted state - Engine::mixer()->clearChannel(index); + mixer->clearChannel(index); // delete the real channel - Engine::mixer()->deleteChannel(index); + mixer->deleteChannel(index); - // delete the view - chLayout->removeWidget(m_mixerChannelViews[index]->m_mixerLine); - m_racksLayout->removeWidget(m_mixerChannelViews[index]->m_rackView); - delete m_mixerChannelViews[index]->m_fader; - delete m_mixerChannelViews[index]->m_muteBtn; - delete m_mixerChannelViews[index]->m_soloBtn; - // delete mixerLine later to prevent a crash when deleting from context menu - m_mixerChannelViews[index]->m_mixerLine->hide(); - m_mixerChannelViews[index]->m_mixerLine->deleteLater(); - delete m_mixerChannelViews[index]->m_rackView; - delete m_mixerChannelViews[index]; + chLayout->removeWidget(m_mixerChannelViews[index]); + m_racksLayout->removeWidget(m_mixerChannelViews[index]); + // delete MixerChannelView later to prevent a crash when deleting from context menu + m_mixerChannelViews[index]->hide(); + m_mixerChannelViews[index]->deleteLater(); m_channelAreaWidget->adjustSize(); // make sure every channel knows what index it is for (int i = index + 1; i < m_mixerChannelViews.size(); ++i) { - m_mixerChannelViews[i]->m_mixerLine->setChannelIndex(i - 1); + m_mixerChannelViews[i]->setChannelIndex(i - 1); } m_mixerChannelViews.remove(index); @@ -438,55 +408,14 @@ void MixerView::deleteChannel(int index) { selLine = m_mixerChannelViews.size() - 1; } - setCurrentMixerLine(selLine); + setCurrentMixerChannel(selLine); updateMaxChannelSelector(); } -bool MixerView::confirmRemoval(int index) -{ - // if config variable is set to false, there is no need for user confirmation - bool needConfirm = ConfigManager::inst()->value("ui", "mixerchanneldeletionwarning", "1").toInt(); - if (!needConfirm) { return true; } - - Mixer* mix = Engine::mixer(); - - if (!mix->isChannelInUse(index)) - { - // is the channel is not in use, there is no need for user confirmation - return true; - } - - QString messageRemoveTrack = tr("This Mixer Channel is being used.\n" - "Are you sure you want to remove this channel?\n\n" - "Warning: This operation can not be undone."); - - QString messageTitleRemoveTrack = tr("Confirm removal"); - QString askAgainText = tr("Don't ask again"); - auto askAgainCheckBox = new QCheckBox(askAgainText, nullptr); - 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"); - }); - - QMessageBox mb(this); - mb.setText(messageRemoveTrack); - mb.setWindowTitle(messageTitleRemoveTrack); - mb.setIcon(QMessageBox::Warning); - mb.addButton(QMessageBox::Cancel); - mb.addButton(QMessageBox::Ok); - mb.setCheckBox(askAgainCheckBox); - mb.setDefaultButton(QMessageBox::Cancel); - - int answer = mb.exec(); - - return answer == QMessageBox::Ok; -} - - void MixerView::deleteUnusedChannels() { - Mixer* mix = Engine::mixer(); + Mixer* mix = getMixer(); // Check all channels except master, delete those with no incoming sends for (int i = m_mixerChannelViews.size() - 1; i > 0; --i) @@ -503,39 +432,39 @@ void MixerView::deleteUnusedChannels() void MixerView::moveChannelLeft(int index, int focusIndex) { // can't move master or first channel left or last channel right - if( index <= 1 || index >= m_mixerChannelViews.size() ) return; + if (index <= 1 || index >= m_mixerChannelViews.size()) return; - Mixer *m = Engine::mixer(); + Mixer *m = getMixer(); // Move instruments channels - m->moveChannelLeft( index ); + m->moveChannelLeft(index); // Update widgets models - m_mixerChannelViews[index]->setChannelIndex( index ); - m_mixerChannelViews[index - 1]->setChannelIndex( index - 1 ); + m_mixerChannelViews[index]->setChannelIndex(index); + m_mixerChannelViews[index - 1]->setChannelIndex(index - 1); // Focus on new position - setCurrentMixerLine( focusIndex ); + setCurrentMixerChannel(focusIndex); } void MixerView::moveChannelLeft(int index) { - moveChannelLeft( index, index - 1 ); + moveChannelLeft(index, index - 1); } void MixerView::moveChannelRight(int index) { - moveChannelLeft( index + 1, index + 1 ); + moveChannelLeft(index + 1, index + 1); } void MixerView::renameChannel(int index) { - m_mixerChannelViews[index]->m_mixerLine->renameChannel(); + m_mixerChannelViews[index]->renameChannel(); } @@ -545,32 +474,32 @@ void MixerView::keyPressEvent(QKeyEvent * e) switch(e->key()) { case Qt::Key_Delete: - deleteChannel(m_currentMixerLine->channelIndex()); + deleteChannel(m_currentMixerChannel->channelIndex()); break; case Qt::Key_Left: - if( e->modifiers() & Qt::AltModifier ) + if (e->modifiers() & Qt::AltModifier) { - moveChannelLeft( m_currentMixerLine->channelIndex() ); + moveChannelLeft(m_currentMixerChannel->channelIndex()); } else { // select channel to the left - setCurrentMixerLine( m_currentMixerLine->channelIndex()-1 ); + setCurrentMixerChannel(m_currentMixerChannel->channelIndex() - 1); } break; case Qt::Key_Right: - if( e->modifiers() & Qt::AltModifier ) + if (e->modifiers() & Qt::AltModifier) { - moveChannelRight( m_currentMixerLine->channelIndex() ); + moveChannelRight(m_currentMixerChannel->channelIndex()); } else { // select channel to the right - setCurrentMixerLine( m_currentMixerLine->channelIndex()+1 ); + setCurrentMixerChannel(m_currentMixerChannel->channelIndex() + 1); } break; case Qt::Key_Insert: - if ( e->modifiers() & Qt::ShiftModifier ) + if (e->modifiers() & Qt::ShiftModifier) { addNewChannel(); } @@ -578,16 +507,16 @@ void MixerView::keyPressEvent(QKeyEvent * e) case Qt::Key_Enter: case Qt::Key_Return: case Qt::Key_F2: - renameChannel( m_currentMixerLine->channelIndex() ); + renameChannel(m_currentMixerChannel->channelIndex()); break; } } -void MixerView::closeEvent( QCloseEvent * _ce ) +void MixerView::closeEvent(QCloseEvent * ce) { - if( parentWidget() ) + if (parentWidget()) { parentWidget()->hide(); } @@ -595,16 +524,16 @@ void MixerView::closeEvent( QCloseEvent * _ce ) { hide(); } - _ce->ignore(); + ce->ignore(); } -void MixerView::setCurrentMixerLine( int _line ) +void MixerView::setCurrentMixerChannel(int channel) { - if( _line >= 0 && _line < m_mixerChannelViews.size() ) + if (channel >= 0 && channel < m_mixerChannelViews.size()) { - setCurrentMixerLine( m_mixerChannelViews[_line]->m_mixerLine ); + setCurrentMixerChannel(m_mixerChannelViews[channel]); } } @@ -612,7 +541,8 @@ void MixerView::setCurrentMixerLine( int _line ) void MixerView::clear() { - Engine::mixer()->clear(); + for (auto i = m_mixerChannelViews.size() - 1; i > 0; --i) { deleteChannel(i); } + getMixer()->clearChannel(0); refreshDisplay(); } @@ -622,37 +552,33 @@ void MixerView::clear() void MixerView::updateFaders() { - Mixer * m = Engine::mixer(); + Mixer * m = getMixer(); - // apply master gain - m->mixerChannel(0)->m_peakLeft *= Engine::audioEngine()->masterGain(); - m->mixerChannel(0)->m_peakRight *= Engine::audioEngine()->masterGain(); - - for( int i = 0; i < m_mixerChannelViews.size(); ++i ) + for (int i = 0; i < m_mixerChannelViews.size(); ++i) { const float opl = m_mixerChannelViews[i]->m_fader->getPeak_L(); const float opr = m_mixerChannelViews[i]->m_fader->getPeak_R(); const float fallOff = 1.25; - if( m->mixerChannel(i)->m_peakLeft >= opl/fallOff ) + if (m->mixerChannel(i)->m_peakLeft >= opl/fallOff) { - m_mixerChannelViews[i]->m_fader->setPeak_L( m->mixerChannel(i)->m_peakLeft ); + m_mixerChannelViews[i]->m_fader->setPeak_L(m->mixerChannel(i)->m_peakLeft); // Set to -1 so later we'll know if this value has been refreshed yet. m->mixerChannel(i)->m_peakLeft = -1; } - else if( m->mixerChannel(i)->m_peakLeft != -1 ) + else if (m->mixerChannel(i)->m_peakLeft != -1) { - m_mixerChannelViews[i]->m_fader->setPeak_L( opl/fallOff ); + m_mixerChannelViews[i]->m_fader->setPeak_L(opl/fallOff); } - if( m->mixerChannel(i)->m_peakRight >= opr/fallOff ) + if (m->mixerChannel(i)->m_peakRight >= opr/fallOff) { - m_mixerChannelViews[i]->m_fader->setPeak_R( m->mixerChannel(i)->m_peakRight ); + m_mixerChannelViews[i]->m_fader->setPeak_R(m->mixerChannel(i)->m_peakRight); // Set to -1 so later we'll know if this value has been refreshed yet. m->mixerChannel(i)->m_peakRight = -1; } - else if( m->mixerChannel(i)->m_peakRight != -1 ) + else if (m->mixerChannel(i)->m_peakRight != -1) { - m_mixerChannelViews[i]->m_fader->setPeak_R( opr/fallOff ); + m_mixerChannelViews[i]->m_fader->setPeak_R(opr/fallOff); } } } diff --git a/src/gui/PluginBrowser.cpp b/src/gui/PluginBrowser.cpp index 7ba8bcc53..963609c43 100644 --- a/src/gui/PluginBrowser.cpp +++ b/src/gui/PluginBrowser.cpp @@ -68,9 +68,10 @@ PluginBrowser::PluginBrowser( QWidget * _parent ) : hint->setWordWrap( true ); auto searchBar = new QLineEdit(m_view); - searchBar->setPlaceholderText( "Search" ); - searchBar->setMaxLength( 64 ); - searchBar->setClearButtonEnabled( true ); + searchBar->setPlaceholderText(tr("Search")); + searchBar->setMaxLength(64); + searchBar->setClearButtonEnabled(true); + searchBar->addAction(embed::getIconPixmap("zoom"), QLineEdit::LeadingPosition); m_descTree = new QTreeWidget( m_view ); m_descTree->setColumnCount( 1 ); @@ -281,9 +282,9 @@ void PluginDescWidget::leaveEvent( QEvent * _e ) void PluginDescWidget::mousePressEvent( QMouseEvent * _me ) { + Engine::setDndPluginKey(&m_pluginKey); if ( _me->button() == Qt::LeftButton ) { - Engine::setDndPluginKey(&m_pluginKey); new StringPairDrag("instrument", QString::fromUtf8(m_pluginKey.desc->name), m_logo, this); leaveEvent( _me ); diff --git a/src/gui/ProjectNotes.cpp b/src/gui/ProjectNotes.cpp index f131a017c..a71f146c6 100644 --- a/src/gui/ProjectNotes.cpp +++ b/src/gui/ProjectNotes.cpp @@ -108,10 +108,8 @@ void ProjectNotes::setText( const QString & _text ) void ProjectNotes::setupActions() { QToolBar * tb = addToolBar( tr( "Edit Actions" ) ); - QAction * a; - a = new QAction( embed::getIconPixmap( "edit_undo" ), tr( "&Undo" ), - this ); + auto a = new QAction(embed::getIconPixmap("edit_undo"), tr("&Undo"), this); a->setShortcut( tr( "%1+Z" ).arg(UI_CTRL_KEY) ); connect( a, SIGNAL(triggered()), m_edit, SLOT(undo())); tb->addAction( a ); diff --git a/src/gui/SampleLoader.cpp b/src/gui/SampleLoader.cpp new file mode 100644 index 000000000..f2340852d --- /dev/null +++ b/src/gui/SampleLoader.cpp @@ -0,0 +1,126 @@ +/* + * SampleLoader.cpp - Static functions that open audio files + * + * Copyright (c) 2023 saker + * + * 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 "SampleLoader.h" + +#include +#include +#include + +#include "ConfigManager.h" +#include "FileDialog.h" +#include "GuiApplication.h" +#include "PathUtil.h" +#include "SampleDecoder.h" +#include "Song.h" + +namespace lmms::gui { +QString SampleLoader::openAudioFile(const QString& previousFile) +{ + auto openFileDialog = FileDialog(nullptr, QObject::tr("Open audio file")); + auto dir = !previousFile.isEmpty() ? PathUtil::toAbsolute(previousFile) : ConfigManager::inst()->userSamplesDir(); + + // change dir to position of previously opened file + openFileDialog.setDirectory(dir); + openFileDialog.setFileMode(FileDialog::ExistingFiles); + + // set filters + auto fileTypes = QStringList{}; + auto allFileTypes = QStringList{}; + auto nameFilters = QStringList{}; + const auto& supportedAudioTypes = SampleDecoder::supportedAudioTypes(); + + for (const auto& audioType : supportedAudioTypes) + { + const auto name = QString::fromStdString(audioType.name); + const auto extension = QString::fromStdString(audioType.extension); + const auto displayExtension = QString{"*.%1"}.arg(extension); + fileTypes.append(QString{"%1 (%2)"}.arg(FileDialog::tr("%1 files").arg(name), displayExtension)); + allFileTypes.append(displayExtension); + } + + nameFilters.append(QString{"%1 (%2)"}.arg(FileDialog::tr("All audio files"), allFileTypes.join(" "))); + nameFilters.append(fileTypes); + nameFilters.append(QString("%1 (*)").arg(FileDialog::tr("Other files"))); + + openFileDialog.setNameFilters(nameFilters); + + if (!previousFile.isEmpty()) + { + // select previously opened file + openFileDialog.selectFile(QFileInfo{previousFile}.fileName()); + } + + if (openFileDialog.exec() == QDialog::Accepted) + { + if (openFileDialog.selectedFiles().isEmpty()) { return ""; } + + return PathUtil::toShortestRelative(openFileDialog.selectedFiles()[0]); + } + + return ""; +} + +QString SampleLoader::openWaveformFile(const QString& previousFile) +{ + return openAudioFile( + previousFile.isEmpty() ? ConfigManager::inst()->factorySamplesDir() + "waveforms/10saw.flac" : previousFile); +} + +std::shared_ptr SampleLoader::createBufferFromFile(const QString& filePath) +{ + if (filePath.isEmpty()) { return SampleBuffer::emptyBuffer(); } + + try + { + return std::make_shared(filePath); + } + catch (const std::runtime_error& error) + { + if (getGUI()) { displayError(QString::fromStdString(error.what())); } + return SampleBuffer::emptyBuffer(); + } +} + +std::shared_ptr SampleLoader::createBufferFromBase64(const QString& base64, int sampleRate) +{ + if (base64.isEmpty()) { return SampleBuffer::emptyBuffer(); } + + try + { + return std::make_shared(base64, sampleRate); + } + catch (const std::runtime_error& error) + { + if (getGUI()) { displayError(QString::fromStdString(error.what())); } + return SampleBuffer::emptyBuffer(); + } +} + +void SampleLoader::displayError(const QString& message) +{ + QMessageBox::critical(nullptr, QObject::tr("Error loading sample"), message); +} + +} // namespace lmms::gui diff --git a/src/gui/SampleTrackWindow.cpp b/src/gui/SampleTrackWindow.cpp index f6d7f9ea1..81a2ca89b 100644 --- a/src/gui/SampleTrackWindow.cpp +++ b/src/gui/SampleTrackWindow.cpp @@ -21,7 +21,7 @@ * Boston, MA 02110-1301 USA. * */ - + #include "SampleTrackWindow.h" #include @@ -33,15 +33,13 @@ #include "EffectRackView.h" #include "embed.h" -#include "gui_templates.h" #include "GuiApplication.h" #include "Knob.h" #include "MainWindow.h" -#include "MixerLineLcdSpinBox.h" +#include "MixerChannelLcdSpinBox.h" #include "SampleTrackView.h" #include "Song.h" #include "SubWindow.h" -#include "TabWidget.h" #include "TrackLabelButton.h" namespace lmms::gui @@ -70,13 +68,9 @@ SampleTrackWindow::SampleTrackWindow(SampleTrackView * tv) : vlayout->setContentsMargins(0, 0, 0, 0); vlayout->setSpacing(0); - auto generalSettingsWidget = new TabWidget(tr("GENERAL SETTINGS"), this); - + auto generalSettingsWidget = new QWidget(this); auto generalSettingsLayout = new QVBoxLayout(generalSettingsWidget); - generalSettingsLayout->setContentsMargins(8, 18, 8, 8); - generalSettingsLayout->setSpacing(6); - auto nameWidget = new QWidget(generalSettingsWidget); auto nameLayout = new QHBoxLayout(nameWidget); nameLayout->setContentsMargins(0, 0, 0, 0); @@ -133,7 +127,7 @@ SampleTrackWindow::SampleTrackWindow(SampleTrackView * tv) : // setup spinbox for selecting Mixer-channel - m_mixerChannelNumber = new MixerLineLcdSpinBox(2, nullptr, tr("Mixer channel"), m_stv); + m_mixerChannelNumber = new MixerChannelLcdSpinBox(2, nullptr, tr("Mixer channel"), m_stv); basicControlsLayout->addWidget(m_mixerChannelNumber, 0, 3); basicControlsLayout->setAlignment(m_mixerChannelNumber, widgetAlignment); diff --git a/src/gui/SampleWaveform.cpp b/src/gui/SampleWaveform.cpp new file mode 100644 index 000000000..783543ac5 --- /dev/null +++ b/src/gui/SampleWaveform.cpp @@ -0,0 +1,98 @@ +/* + * SampleWaveform.cpp + * + * Copyright (c) 2023 saker + * + * 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 "SampleWaveform.h" + +namespace lmms::gui { + +void SampleWaveform::visualize(Parameters parameters, QPainter& painter, const QRect& rect) +{ + const int x = rect.x(); + const int height = rect.height(); + const int width = rect.width(); + const int centerY = rect.center().y(); + + const int halfHeight = height / 2; + + const auto color = painter.pen().color(); + const auto rmsColor = color.lighter(123); + + const float framesPerPixel = std::max(1.0f, static_cast(parameters.size) / width); + + constexpr float maxFramesPerPixel = 512.0f; + const float resolution = std::max(1.0f, framesPerPixel / maxFramesPerPixel); + const float framesPerResolution = framesPerPixel / resolution; + + const size_t numPixels = std::min(parameters.size, width); + auto min = std::vector(numPixels, 1); + auto max = std::vector(numPixels, -1); + auto squared = std::vector(numPixels, 0); + + const size_t maxFrames = numPixels * static_cast(framesPerPixel); + + int pixelIndex = 0; + + for (int i = 0; i < maxFrames; i += static_cast(resolution)) + { + pixelIndex = i / framesPerPixel; + const int frameIndex = !parameters.reversed ? i : maxFrames - i; + + const auto& frame = parameters.buffer[frameIndex]; + const float value = std::accumulate(frame.begin(), frame.end(), 0.0f) / frame.size(); + + if (value > max[pixelIndex]) { max[pixelIndex] = value; } + if (value < min[pixelIndex]) { min[pixelIndex] = value; } + + squared[pixelIndex] += value * value; + } + + while (pixelIndex < numPixels) + { + max[pixelIndex] = 0.0; + min[pixelIndex] = 0.0; + + pixelIndex++; + } + + for (int i = 0; i < numPixels; i++) + { + const int lineY1 = centerY - max[i] * halfHeight * parameters.amplification; + const int lineY2 = centerY - min[i] * halfHeight * parameters.amplification; + const int lineX = i + x; + painter.drawLine(lineX, lineY1, lineX, lineY2); + + const float rms = std::sqrt(squared[i] / framesPerResolution); + const float maxRMS = std::clamp(rms, min[i], max[i]); + const float minRMS = std::clamp(-rms, min[i], max[i]); + + const int rmsLineY1 = centerY - maxRMS * halfHeight * parameters.amplification; + const int rmsLineY2 = centerY - minRMS * halfHeight * parameters.amplification; + + painter.setPen(rmsColor); + painter.drawLine(lineX, rmsLineY1, lineX, rmsLineY2); + painter.setPen(color); + } +} + +} // namespace lmms::gui diff --git a/src/gui/SendButtonIndicator.cpp b/src/gui/SendButtonIndicator.cpp index cd1996c45..4fb20cc31 100644 --- a/src/gui/SendButtonIndicator.cpp +++ b/src/gui/SendButtonIndicator.cpp @@ -2,69 +2,53 @@ #include "embed.h" #include "Mixer.h" -#include "MixerLine.h" +#include "MixerChannelView.h" #include "MixerView.h" namespace lmms::gui { - -QPixmap * SendButtonIndicator::s_qpmOff = nullptr; -QPixmap * SendButtonIndicator::s_qpmOn = nullptr; - -SendButtonIndicator:: SendButtonIndicator( QWidget * _parent, MixerLine * _owner, - MixerView * _mv) : - QLabel( _parent ), - m_parent( _owner ), - m_mv( _mv ) +SendButtonIndicator:: SendButtonIndicator(QWidget* parent, MixerChannelView* owner, MixerView* mv) : + QLabel(parent), + m_parent(owner), + m_mv(mv) { - if( ! s_qpmOff ) - { - s_qpmOff = new QPixmap( embed::getIconPixmap( "mixer_send_off", 29, 20 ) ); - } - - if( ! s_qpmOn ) - { - s_qpmOn = new QPixmap( embed::getIconPixmap( "mixer_send_on", 29, 20 ) ); - } - - // don't do any initializing yet, because the MixerView and MixerLine + // don't do any initializing yet, because the MixerView and MixerChannelView // that were passed to this constructor are not done with their constructors // yet. - setPixmap( *s_qpmOff ); + setPixmap(m_qpmOff); } -void SendButtonIndicator::mousePressEvent( QMouseEvent * e ) +void SendButtonIndicator::mousePressEvent(QMouseEvent* e) { - Mixer * mix = Engine::mixer(); - int from = m_mv->currentMixerLine()->channelIndex(); + Mixer* mix = Engine::mixer(); + int from = m_mv->currentMixerChannel()->channelIndex(); int to = m_parent->channelIndex(); - FloatModel * sendModel = mix->channelSendModel(from, to); - if( sendModel == nullptr ) + FloatModel* sendModel = mix->channelSendModel(from, to); + if (sendModel == nullptr) { // not sending. create a mixer send. - mix->createChannelSend( from, to ); + mix->createChannelSend(from, to); } else { // sending. delete the mixer send. - mix->deleteChannelSend( from, to ); + mix->deleteChannelSend(from, to); } - m_mv->updateMixerLine(m_parent->channelIndex()); + m_mv->updateMixerChannel(m_parent->channelIndex()); updateLightStatus(); } -FloatModel * SendButtonIndicator::getSendModel() +FloatModel* SendButtonIndicator::getSendModel() { - Mixer * mix = Engine::mixer(); - return mix->channelSendModel( - m_mv->currentMixerLine()->channelIndex(), m_parent->channelIndex()); + Mixer* mix = Engine::mixer(); + return mix->channelSendModel(m_mv->currentMixerChannel()->channelIndex(), m_parent->channelIndex()); } void SendButtonIndicator::updateLightStatus() { - setPixmap( getSendModel() == nullptr ? *s_qpmOff : *s_qpmOn ); + setPixmap(!getSendModel() ? m_qpmOff : m_qpmOn); } diff --git a/src/gui/SubWindow.cpp b/src/gui/SubWindow.cpp index 78e4f586c..dc6e49297 100644 --- a/src/gui/SubWindow.cpp +++ b/src/gui/SubWindow.cpp @@ -34,6 +34,7 @@ #include #include #include +#include #include "embed.h" @@ -41,11 +42,11 @@ namespace lmms::gui { -SubWindow::SubWindow( QWidget *parent, Qt::WindowFlags windowFlags ) : - QMdiSubWindow( parent, windowFlags ), - m_buttonSize( 17, 17 ), - m_titleBarHeight( 24 ), - m_hasFocus( false ) +SubWindow::SubWindow(QWidget *parent, Qt::WindowFlags windowFlags) : + QMdiSubWindow(parent, windowFlags), + m_buttonSize(17, 17), + m_titleBarHeight(titleBarHeight()), + m_hasFocus(false) { // initialize the tracked geometry to whatever Qt thinks the normal geometry currently is. // this should always work, since QMdiSubWindows will not start as maximized @@ -240,6 +241,27 @@ void SubWindow::setBorderColor( const QColor &c ) + +int SubWindow::titleBarHeight() const +{ + QStyleOptionTitleBar so; + so.titleBarState = Qt::WindowActive; // kThemeStateActiv + so.titleBarFlags = Qt::Window; + return style()->pixelMetric(QStyle::PM_TitleBarHeight, &so, this); +} + + + + +int SubWindow::frameWidth() const +{ + QStyleOptionFrame so; + return style()->pixelMetric(QStyle::PM_MdiSubWindowFrameWidth, &so, this); +} + + + + /** * @brief SubWindow::moveEvent * diff --git a/src/gui/clips/AutomationClipView.cpp b/src/gui/clips/AutomationClipView.cpp index 3e0e12b75..9b71bb74c 100644 --- a/src/gui/clips/AutomationClipView.cpp +++ b/src/gui/clips/AutomationClipView.cpp @@ -44,8 +44,6 @@ namespace lmms::gui { -QPixmap * AutomationClipView::s_clip_rec = nullptr; - AutomationClipView::AutomationClipView( AutomationClip * _clip, TrackView * _parent ) : ClipView( _clip, _parent ), @@ -61,10 +59,6 @@ AutomationClipView::AutomationClipView( AutomationClip * _clip, setToolTip(m_clip->name()); setStyle( QApplication::style() ); - - if( s_clip_rec == nullptr ) { s_clip_rec = new QPixmap( embed::getIconPixmap( - "clip_rec" ) ); } - update(); } @@ -320,24 +314,17 @@ void AutomationClipView::paintEvent( QPaintEvent * ) // the outValue of the current node). When we have nodes with linear or cubic progression // the value of the end of the shape between the two nodes will be the inValue of // the next node. - float nextValue; - if( m_clip->progressionType() == AutomationClip::ProgressionType::Discrete ) - { - nextValue = OUTVAL(it); - } - else - { - nextValue = INVAL(it + 1); - } + float nextValue = m_clip->progressionType() == AutomationClip::ProgressionType::Discrete + ? OUTVAL(it) + : INVAL(it + 1); QPainterPath path; QPointF origin = QPointF(POS(it) * ppTick, 0.0f); path.moveTo( origin ); path.moveTo(QPointF(POS(it) * ppTick,values[0])); - float x; for (int i = POS(it) + 1; i < POS(it + 1); i++) { - x = i * ppTick; + float x = i * ppTick; if( x > ( width() - BORDER_WIDTH ) ) break; float value = values[i - POS(it)]; path.lineTo( QPointF( x, value ) ); @@ -379,7 +366,8 @@ void AutomationClipView::paintEvent( QPaintEvent * ) // recording icon for when recording automation if( m_clip->isRecording() ) { - p.drawPixmap( 1, rect().bottom() - s_clip_rec->height(), *s_clip_rec ); + static auto s_clipRec = embed::getIconPixmap("clip_rec"); + p.drawPixmap(1, rect().bottom() - s_clipRec.height(), s_clipRec); } // clip name diff --git a/src/gui/clips/ClipView.cpp b/src/gui/clips/ClipView.cpp index de7690d26..a0f7f7c53 100644 --- a/src/gui/clips/ClipView.cpp +++ b/src/gui/clips/ClipView.cpp @@ -136,7 +136,7 @@ ClipView::ClipView( Clip * clip, connect(m_trackView->getTrack(), &Track::colorChanged, this, [this] { // redraw if clip uses track color - if (!m_clip->usesCustomClipColor()) { update(); } + if (!m_clip->color().has_value()) { update(); } }); m_trackView->getTrackContentWidget()->addClipView( this ); @@ -294,6 +294,17 @@ void ClipView::remove() // delete ourself close(); + + if (m_clip->getTrack()) + { + auto guard = Engine::audioEngine()->requestChangesGuard(); + m_clip->getTrack()->removeClip(m_clip); + } + + // TODO: Clip::~Clip should not be responsible for removing the Clip from the Track. + // One would expect that a call to Track::removeClip would already do that for you, as well + // as actually deleting the Clip with the deleteLater function. That being said, it shouldn't + // be possible to make a Clip without a Track (i.e., Clip::getTrack is never nullptr). m_clip->deleteLater(); } @@ -340,45 +351,35 @@ void ClipView::updatePosition() m_trackView->trackContainerView()->update(); } - - - void ClipView::selectColor() { // Get a color from the user - QColor new_color = ColorChooser( this ).withPalette( ColorChooser::Palette::Track )->getColor( m_clip->color() ); - if (new_color.isValid()) { setColor(&new_color); } + const auto newColor = ColorChooser{this} + .withPalette(ColorChooser::Palette::Track) + ->getColor(m_clip->color().value_or(palette().window().color())); + if (newColor.isValid()) { setColor(newColor); } } - - - void ClipView::randomizeColor() { - setColor(&ColorChooser::getPalette(ColorChooser::Palette::Mixer)[rand() % 48]); + setColor(ColorChooser::getPalette(ColorChooser::Palette::Mixer)[std::rand() % 48]); } - - - void ClipView::resetColor() { - setColor(nullptr); + setColor(std::nullopt); } - - - /*! \brief Change color of all selected clips * - * \param color The new QColor. Pass nullptr to use the Track's color. + * \param color The new color. */ -void ClipView::setColor(const QColor* color) +void ClipView::setColor(const std::optional& color) { std::set journaledTracks; auto selectedClips = getClickedClips(); - for (auto clipv: selectedClips) + for (auto clipv : selectedClips) { auto clip = clipv->getClip(); auto track = clip->getTrack(); @@ -397,25 +398,13 @@ void ClipView::setColor(const QColor* color) track->addJournalCheckPoint(); } - if (color) - { - clip->useCustomClipColor(true); - clip->setColor(*color); - } - else - { - clip->useCustomClipColor(false); - } + clip->setColor(color); clipv->update(); } Engine::getSong()->setModified(); } - - - - /*! \brief Change the ClipView's display when something * being dragged enters it. * @@ -1483,11 +1472,7 @@ TimePos ClipView::quantizeSplitPos( TimePos midiPos, bool shiftMode ) QColor ClipView::getColorForDisplay( QColor defaultColor ) { // Get the pure Clip color - auto clipColor = m_clip->hasColor() - ? m_clip->usesCustomClipColor() - ? m_clip->color() - : m_clip->getTrack()->color() - : defaultColor; + auto clipColor = m_clip->color().value_or(m_clip->getTrack()->color().value_or(defaultColor)); // Set variables QColor c, mutedCustomColor; @@ -1498,7 +1483,7 @@ QColor ClipView::getColorForDisplay( QColor defaultColor ) // Change the pure color by state: selected, muted, colored, normal if( isSelected() ) { - c = m_clip->hasColor() + c = hasCustomColor() ? ( muted ? mutedCustomColor.darker( 350 ) : clipColor.darker( 150 ) ) @@ -1508,7 +1493,7 @@ QColor ClipView::getColorForDisplay( QColor defaultColor ) { if( muted ) { - c = m_clip->hasColor() + c = hasCustomColor() ? mutedCustomColor.darker( 250 ) : mutedBackgroundColor(); } @@ -1522,5 +1507,9 @@ QColor ClipView::getColorForDisplay( QColor defaultColor ) return c; } +auto ClipView::hasCustomColor() const -> bool +{ + return m_clip->color().has_value() || m_clip->getTrack()->color().has_value(); +} } // namespace lmms::gui diff --git a/src/gui/clips/MidiClipView.cpp b/src/gui/clips/MidiClipView.cpp index 151df8d3c..b735913e4 100644 --- a/src/gui/clips/MidiClipView.cpp +++ b/src/gui/clips/MidiClipView.cpp @@ -25,12 +25,16 @@ #include "MidiClipView.h" + +#include #include #include #include #include #include +#include +#include "AutomationEditor.h" #include "ConfigManager.h" #include "DeprecationHelper.h" #include "GuiApplication.h" @@ -42,6 +46,7 @@ namespace lmms::gui { +constexpr int BeatStepButtonOffset = 4; MidiClipView::MidiClipView( MidiClip* clip, TrackView* parent ) : ClipView( clip, parent ), @@ -56,31 +61,6 @@ MidiClipView::MidiClipView( MidiClip* clip, TrackView* parent ) : { connect( getGUI()->pianoRoll(), SIGNAL(currentMidiClipChanged()), this, SLOT(update())); - - if( s_stepBtnOn0 == nullptr ) - { - s_stepBtnOn0 = new QPixmap( embed::getIconPixmap( - "step_btn_on_0" ) ); - } - - if( s_stepBtnOn200 == nullptr ) - { - s_stepBtnOn200 = new QPixmap( embed::getIconPixmap( - "step_btn_on_200" ) ); - } - - if( s_stepBtnOff == nullptr ) - { - s_stepBtnOff = new QPixmap( embed::getIconPixmap( - "step_btn_off" ) ); - } - - if( s_stepBtnOffLight == nullptr ) - { - s_stepBtnOffLight = new QPixmap( embed::getIconPixmap( - "step_btn_off_light" ) ); - } - update(); setStyle( QApplication::style() ); @@ -109,10 +89,11 @@ void MidiClipView::update() void MidiClipView::openInPianoRoll() { - getGUI()->pianoRoll()->setCurrentMidiClip( m_clip ); - getGUI()->pianoRoll()->parentWidget()->show(); - getGUI()->pianoRoll()->show(); - getGUI()->pianoRoll()->setFocus(); + auto pRoll = getGUI()->pianoRoll(); + pRoll->setCurrentMidiClip(m_clip); + pRoll->parentWidget()->show(); + pRoll->show(); + pRoll->setFocus(); } @@ -121,14 +102,21 @@ void MidiClipView::openInPianoRoll() void MidiClipView::setGhostInPianoRoll() { - getGUI()->pianoRoll()->setGhostMidiClip( m_clip ); - getGUI()->pianoRoll()->parentWidget()->show(); - getGUI()->pianoRoll()->show(); - getGUI()->pianoRoll()->setFocus(); + auto pRoll = getGUI()->pianoRoll(); + pRoll->setGhostMidiClip(m_clip); + pRoll->parentWidget()->show(); + pRoll->show(); + pRoll->setFocus(); } - - +void MidiClipView::setGhostInAutomationEditor() +{ + auto aEditor = getGUI()->automationEditor(); + aEditor->setGhostMidiClip(m_clip); + aEditor->parentWidget()->show(); + aEditor->show(); + aEditor->setFocus(); +} void MidiClipView::resetName() { m_clip->setName(""); } @@ -216,7 +204,13 @@ void MidiClipView::constructContextMenu( QMenu * _cm ) _cm->insertAction( _cm->actions()[1], b ); connect( b, SIGNAL(triggered(bool)), this, SLOT(setGhostInPianoRoll())); - _cm->insertSeparator( _cm->actions()[2] ); + + auto c = new QAction(embed::getIconPixmap("automation_ghost_note"), tr("Set as ghost in automation editor"), _cm); + if (m_clip->empty()) { c->setEnabled(false); } + _cm->insertAction(_cm->actions()[2], c); + connect(c, &QAction::triggered, this, &MidiClipView::setGhostInAutomationEditor); + + _cm->insertSeparator(_cm->actions()[3]); _cm->addSeparator(); _cm->addAction( embed::getIconPixmap( "edit_erase" ), @@ -252,9 +246,8 @@ void MidiClipView::constructContextMenu( QMenu * _cm ) void MidiClipView::mousePressEvent( QMouseEvent * _me ) { bool displayPattern = fixedClips() || (pixelsPerBar() >= 96 && m_legacySEPattern); - if( _me->button() == Qt::LeftButton && - m_clip->m_clipType == MidiClip::Type::BeatClip && - displayPattern && _me->y() > height() - s_stepBtnOff->height() ) + if (_me->button() == Qt::LeftButton && m_clip->m_clipType == MidiClip::Type::BeatClip && displayPattern + && _me->y() > BeatStepButtonOffset && _me->y() < BeatStepButtonOffset + m_stepBtnOff.height()) // when mouse button is pressed in pattern mode @@ -324,7 +317,7 @@ void MidiClipView::wheelEvent(QWheelEvent * we) { if(m_clip->m_clipType == MidiClip::Type::BeatClip && (fixedClips() || pixelsPerBar() >= 96) && - position(we).y() > height() - s_stepBtnOff->height()) + position(we).y() > height() - m_stepBtnOff.height()) { // get the step number that was wheeled on and // do calculations in floats to prevent rounding errors... @@ -339,7 +332,8 @@ void MidiClipView::wheelEvent(QWheelEvent * we) } Note * n = m_clip->noteAtStep( step ); - if(!n && we->angleDelta().y() > 0) + const int direction = (we->angleDelta().y() > 0 ? 1 : -1) * (we->inverted() ? -1 : 1); + if(!n && direction > 0) { n = m_clip->addStepNote( step ); n->setVolume( 0 ); @@ -347,8 +341,7 @@ void MidiClipView::wheelEvent(QWheelEvent * we) if( n != nullptr ) { int vol = n->getVolume(); - - if(we->angleDelta().y() > 0) + if(direction > 0) { n->setVolume( qMin( 100, vol + 5 ) ); } @@ -458,9 +451,70 @@ void MidiClipView::paintEvent( QPaintEvent * ) const int x_base = BORDER_WIDTH; bool displayPattern = fixedClips() || (pixelsPerBar >= 96 && m_legacySEPattern); - // melody clip paint event NoteVector const & noteCollection = m_clip->m_notes; - if( m_clip->m_clipType == MidiClip::Type::MelodyClip && !noteCollection.empty() ) + + // Beat clip paint event (on BB Editor) + if (beatClip && displayPattern) + { + QPixmap stepon0; + QPixmap stepon200; + QPixmap stepoff; + QPixmap stepoffl; + const int steps = std::max(1, m_clip->m_steps); + const int w = width() - 2 * BORDER_WIDTH; + + // scale step graphics to fit the beat clip length + stepon0 + = m_stepBtnOn0.scaled(w / steps, m_stepBtnOn0.height(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation); + stepon200 = m_stepBtnOn200.scaled( + w / steps, m_stepBtnOn200.height(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation); + stepoff + = m_stepBtnOff.scaled(w / steps, m_stepBtnOff.height(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation); + stepoffl = m_stepBtnOffLight.scaled( + w / steps, m_stepBtnOffLight.height(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation); + + for (int it = 0; it < steps; it++) // go through all the steps in the beat clip + { + Note* n = m_clip->noteAtStep(it); + + // figure out x and y coordinates for step graphic + const int x = BORDER_WIDTH + static_cast(it * w / steps); + const int y = BeatStepButtonOffset; + + if (n) + { + const int vol = n->getVolume(); + p.drawPixmap(x, y, stepoffl); + p.drawPixmap(x, y, stepon0); + p.setOpacity(std::sqrt(vol / 200.0)); + p.drawPixmap(x, y, stepon200); + p.setOpacity(1); + } + else if ((it / 4) % 2) + { + p.drawPixmap(x, y, stepoffl); + } + else + { + p.drawPixmap(x, y, stepoff); + } + } // end for loop + + // draw a transparent rectangle over muted clips + if (muted) + { + p.setBrush(mutedBackgroundColor()); + p.setOpacity(0.5); + p.drawRect(0, 0, width(), height()); + } + } + // Melody clip and Beat clip (on Song Editor) paint event + else if + ( + !noteCollection.empty() && + (m_clip->m_clipType == MidiClip::Type::MelodyClip || + m_clip->m_clipType == MidiClip::Type::BeatClip) + ) { // Compute the minimum and maximum key in the clip // so that we know how much there is to draw. @@ -526,7 +580,7 @@ void MidiClipView::paintEvent( QPaintEvent * ) QColor noteFillColor = muted ? getMutedNoteFillColor().lighter(200) : (c.lightness() > 175 ? getNoteFillColor().darker(400) : getNoteFillColor()); QColor noteBorderColor = muted ? getMutedNoteBorderColor() - : ( m_clip->hasColor() ? c.lighter( 200 ) : getNoteBorderColor() ); + : (hasCustomColor() ? c.lighter(200) : getNoteBorderColor()); bool const drawAsLines = height() < 64; if (drawAsLines) @@ -574,70 +628,6 @@ void MidiClipView::paintEvent( QPaintEvent * ) p.restore(); } - // beat clip paint event - else if (beatClip && displayPattern) - { - QPixmap stepon0; - QPixmap stepon200; - QPixmap stepoff; - QPixmap stepoffl; - const int steps = qMax( 1, - m_clip->m_steps ); - const int w = width() - 2 * BORDER_WIDTH; - - // scale step graphics to fit the beat clip length - stepon0 = s_stepBtnOn0->scaled( w / steps, - s_stepBtnOn0->height(), - Qt::IgnoreAspectRatio, - Qt::SmoothTransformation ); - stepon200 = s_stepBtnOn200->scaled( w / steps, - s_stepBtnOn200->height(), - Qt::IgnoreAspectRatio, - Qt::SmoothTransformation ); - stepoff = s_stepBtnOff->scaled( w / steps, - s_stepBtnOff->height(), - Qt::IgnoreAspectRatio, - Qt::SmoothTransformation ); - stepoffl = s_stepBtnOffLight->scaled( w / steps, - s_stepBtnOffLight->height(), - Qt::IgnoreAspectRatio, - Qt::SmoothTransformation ); - - for( int it = 0; it < steps; it++ ) // go through all the steps in the beat clip - { - Note * n = m_clip->noteAtStep( it ); - - // figure out x and y coordinates for step graphic - const int x = BORDER_WIDTH + static_cast( it * w / steps ); - const int y = height() - s_stepBtnOff->height() - 1; - - if( n ) - { - const int vol = n->getVolume(); - p.drawPixmap( x, y, stepoffl ); - p.drawPixmap( x, y, stepon0 ); - p.setOpacity( sqrt( vol / 200.0 ) ); - p.drawPixmap( x, y, stepon200 ); - p.setOpacity( 1 ); - } - else if( ( it / 4 ) % 2 ) - { - p.drawPixmap( x, y, stepoffl ); - } - else - { - p.drawPixmap( x, y, stepoff ); - } - } // end for loop - - // draw a transparent rectangle over muted clips - if ( muted ) - { - p.setBrush( mutedBackgroundColor() ); - p.setOpacity( 0.5 ); - p.drawRect( 0, 0, width(), height() ); - } - } // bar lines const int lineSize = 3; diff --git a/src/gui/clips/SampleClipView.cpp b/src/gui/clips/SampleClipView.cpp index ce1ab7462..2074e7944 100644 --- a/src/gui/clips/SampleClipView.cpp +++ b/src/gui/clips/SampleClipView.cpp @@ -28,11 +28,14 @@ #include #include +#include "GuiApplication.h" +#include "AutomationEditor.h" #include "embed.h" #include "gui_templates.h" #include "PathUtil.h" -#include "SampleBuffer.h" #include "SampleClip.h" +#include "SampleLoader.h" +#include "SampleWaveform.h" #include "Song.h" #include "StringPairDrag.h" @@ -61,9 +64,11 @@ void SampleClipView::updateSample() update(); // set tooltip to filename so that user can see what sample this // sample-clip contains - setToolTip(m_clip->m_sampleBuffer->audioFile() != "" ? - PathUtil::toAbsolute(m_clip->m_sampleBuffer->audioFile()) : - tr( "Double-click to open sample" ) ); + setToolTip( + !m_clip->m_sample.sampleFile().isEmpty() + ? PathUtil::toAbsolute(m_clip->m_sample.sampleFile()) + : tr("Double-click to open sample") + ); } @@ -85,6 +90,12 @@ void SampleClipView::constructContextMenu(QMenu* cm) SLOT(reverseSample()) ); + cm->addAction( + embed::getIconPixmap("automation_ghost_note"), + tr("Set as ghost in automation editor"), + this, + SLOT(setAutomationGhost()) + ); } @@ -114,12 +125,10 @@ void SampleClipView::dropEvent( QDropEvent * _de ) } else if( StringPairDrag::decodeKey( _de ) == "sampledata" ) { - m_clip->m_sampleBuffer->loadFromBase64( - StringPairDrag::decodeValue( _de ) ); + m_clip->setSampleBuffer(SampleLoader::createBufferFromBase64(StringPairDrag::decodeValue(_de))); m_clip->updateLength(); update(); _de->accept(); - Engine::getSong()->setModified(); } else { @@ -173,18 +182,21 @@ void SampleClipView::mouseReleaseEvent(QMouseEvent *_me) void SampleClipView::mouseDoubleClickEvent( QMouseEvent * ) { - QString af = m_clip->m_sampleBuffer->openAudioFile(); + const QString selectedAudioFile = SampleLoader::openAudioFile(); - if ( af.isEmpty() ) {} //Don't do anything if no file is loaded - else if ( af == m_clip->m_sampleBuffer->audioFile() ) - { //Instead of reloading the existing file, just reset the size - int length = (int) ( m_clip->m_sampleBuffer->frames() / Engine::framesPerTick() ); - m_clip->changeLength(length); + if (selectedAudioFile.isEmpty()) { return; } + + if (m_clip->hasSampleFileLoaded(selectedAudioFile)) + { + m_clip->changeLengthToSampleLength(); } else - { //Otherwise load the new file as ususal - m_clip->setSampleFile( af ); - Engine::getSong()->setModified(); + { + auto sampleBuffer = SampleLoader::createBufferFromFile(selectedAudioFile); + if (sampleBuffer != SampleBuffer::emptyBuffer()) + { + m_clip->setSampleBuffer(sampleBuffer); + } } } @@ -233,11 +245,7 @@ void SampleClipView::paintEvent( QPaintEvent * pe ) p.fillRect( rect(), c ); } - auto clipColor = m_clip->hasColor() - ? (m_clip->usesCustomClipColor() - ? m_clip->color() - : m_clip->getTrack()->color()) - : painter.pen().brush().color(); + auto clipColor = m_clip->color().value_or(m_clip->getTrack()->color().value_or(painter.pen().brush().color())); p.setPen(clipColor); @@ -265,9 +273,12 @@ void SampleClipView::paintEvent( QPaintEvent * pe ) float offset = m_clip->startTimeOffset() / ticksPerBar * pixelsPerBar(); QRect r = QRect( offset, spacing, qMax( static_cast( m_clip->sampleLength() * ppb / ticksPerBar ), 1 ), rect().bottom() - 2 * spacing ); - m_clip->m_sampleBuffer->visualize( p, r, pe->rect() ); - QString name = PathUtil::cleanName(m_clip->m_sampleBuffer->audioFile()); + const auto& sample = m_clip->m_sample; + const auto waveform = SampleWaveform::Parameters{sample.data(), sample.sampleSize(), sample.amplification(), sample.reversed()}; + SampleWaveform::visualize(waveform, p, r); + + QString name = PathUtil::cleanName(m_clip->m_sample.sampleFile()); paintTextLabel(name, p); // disable antialiasing for borders, since its not needed @@ -299,7 +310,7 @@ void SampleClipView::paintEvent( QPaintEvent * pe ) if (m_clip->isRecord()) { - p.setFont( pointSize<7>( p.font() ) ); + p.setFont(adjustedToPixelSize(p.font(), 7)); p.setPen( textShadowColor() ); p.drawText( 10, p.fontMetrics().height()+1, "Rec" ); @@ -320,13 +331,21 @@ void SampleClipView::paintEvent( QPaintEvent * pe ) void SampleClipView::reverseSample() { - m_clip->sampleBuffer()->setReversed(!m_clip->sampleBuffer()->reversed()); + m_clip->m_sample.setReversed(!m_clip->m_sample.reversed()); Engine::getSong()->setModified(); update(); } +void SampleClipView::setAutomationGhost() +{ + auto aEditor = gui::getGUI()->automationEditor(); + aEditor->setGhostSample(m_clip); + aEditor->parentWidget()->show(); + aEditor->show(); + aEditor->setFocus(); +} //! Split this Clip. /*! \param pos the position of the split, relative to the start of the clip */ diff --git a/src/gui/editors/AutomationEditor.cpp b/src/gui/editors/AutomationEditor.cpp index 282c335df..41042f524 100644 --- a/src/gui/editors/AutomationEditor.cpp +++ b/src/gui/editors/AutomationEditor.cpp @@ -27,8 +27,6 @@ #include "AutomationEditor.h" -#include - #include #include #include @@ -38,6 +36,10 @@ #include #include #include +#include + +#include "SampleClip.h" +#include "SampleWaveform.h" #ifndef __USE_XOPEN #define __USE_XOPEN @@ -46,33 +48,27 @@ #include "ActionGroup.h" #include "AutomationNode.h" #include "ComboBox.h" -#include "debug.h" #include "DeprecationHelper.h" -#include "embed.h" +#include "DetuningHelper.h" #include "Engine.h" #include "GuiApplication.h" -#include "gui_templates.h" #include "Knob.h" #include "MainWindow.h" +#include "MidiClip.h" #include "PatternStore.h" #include "PianoRoll.h" #include "ProjectJournal.h" +#include "SampleBuffer.h" #include "StringPairDrag.h" #include "TextFloat.h" #include "TimeLineWidget.h" +#include "debug.h" +#include "embed.h" +#include "gui_templates.h" 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; - const std::array AutomationEditor::m_zoomXLevels = { 0.125f, 0.25f, 0.5f, 1.0f, 2.0f, 4.0f, 8.0f }; @@ -110,7 +106,8 @@ AutomationEditor::AutomationEditor() : m_nodeTangentLineColor(0, 0, 0), m_scaleColor(Qt::SolidPattern), m_crossColor(0, 0, 0), - m_backgroundShade(0, 0, 0) + m_backgroundShade(0, 0, 0), + m_ghostNoteColor(0, 0, 0) { connect( this, SIGNAL(currentClipChanged()), this, SLOT(updateAfterClipChange()), @@ -135,25 +132,13 @@ AutomationEditor::AutomationEditor() : this, SLOT(setQuantization())); m_quantizeModel.setValue( m_quantizeModel.findText( "1/8" ) ); - if (s_toolYFlip == nullptr) - { - s_toolYFlip = new QPixmap( embed::getIconPixmap( - "flip_y" ) ); - } - if (s_toolXFlip == nullptr) - { - s_toolXFlip = new QPixmap( embed::getIconPixmap( - "flip_x" ) ); - } - // add time-line - m_timeLine = new TimeLineWidget( VALUES_WIDTH, 0, m_ppb, - Engine::getSong()->getPlayPos( - Song::PlayMode::AutomationClip ), - m_currentPosition, - Song::PlayMode::AutomationClip, this ); - connect( this, SIGNAL( positionChanged( const lmms::TimePos& ) ), - m_timeLine, SLOT( updatePosition( const lmms::TimePos& ) ) ); + m_timeLine = new TimeLineWidget(VALUES_WIDTH, 0, m_ppb, + Engine::getSong()->getPlayPos(Song::PlayMode::AutomationClip), + Engine::getSong()->getTimeline(Song::PlayMode::AutomationClip), + m_currentPosition, Song::PlayMode::AutomationClip, this + ); + connect(this, &AutomationEditor::positionChanged, m_timeLine, &TimeLineWidget::updatePosition); connect( m_timeLine, SIGNAL( positionChanged( const lmms::TimePos& ) ), this, SLOT( updatePosition( const lmms::TimePos& ) ) ); @@ -169,28 +154,6 @@ AutomationEditor::AutomationEditor() : connect( m_topBottomScroll, SIGNAL(valueChanged(int)), this, SLOT(verScrolled(int))); - // init pixmaps - if (s_toolDraw == nullptr) - { - s_toolDraw = new QPixmap(embed::getIconPixmap("edit_draw")); - } - if (s_toolErase == nullptr) - { - s_toolErase= new QPixmap(embed::getIconPixmap("edit_erase")); - } - if (s_toolDrawOut == nullptr) - { - 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")); - } - setCurrentClip(nullptr); setMouseTracking( true ); @@ -357,8 +320,6 @@ void AutomationEditor::drawLine( int x0In, float y0, int x1In, float y1 ) auto deltay = qAbs(y1 - y0); int x = x0; float y = y0; - int xstep; - int ystep; if( deltax < AutomationClip::quantization() ) { @@ -369,34 +330,14 @@ void AutomationEditor::drawLine( int x0In, float y0, int x1In, float y1 ) float yscale = deltay / ( deltax ); - if( x0 < x1 ) - { - xstep = AutomationClip::quantization(); - } - else - { - xstep = -( AutomationClip::quantization() ); - } + int xstep = (x0 < x1 ? 1 : -1) * AutomationClip::quantization(); + int ystep = y0 < y1 ? 1 : -1; + float lineAdjust = ystep * yscale; - float lineAdjust; - if( y0 < y1 ) - { - ystep = 1; - lineAdjust = yscale; - } - else - { - ystep = -1; - lineAdjust = -( yscale ); - } - - int i = 0; - while( i < deltax ) + for (int i = 0; i < deltax; ++i) { y = y0 + ( ystep * yscale * i ) + lineAdjust; - x += xstep; - i += 1; m_clip->removeNode(TimePos(x)); m_clip->putValue( TimePos( x ), y ); } @@ -1016,7 +957,6 @@ inline void AutomationEditor::drawCross( QPainter & p ) inline void AutomationEditor::drawAutomationPoint(QPainter & p, timeMap::iterator it) { int x = xCoordOfTick(POS(it)); - int y; // Below (m_ppb * AutomationClip::quantization() / 576) is used because: // 1 bar equals to 192/quantization() notes. Hence, to calculate the number of pixels // per note we would have (m_ppb * 1 bar / (192/quantization()) notes per bar), or @@ -1025,7 +965,7 @@ inline void AutomationEditor::drawAutomationPoint(QPainter & p, timeMap::iterato const int outerRadius = qBound(3, (m_ppb * AutomationClip::quantization()) / 576, 5); // Draw a circle for the outValue - y = yCoordOfLevel(OUTVAL(it)); + int y = yCoordOfLevel(OUTVAL(it)); p.setPen(QPen(m_nodeOutValueColor.lighter(200))); p.setBrush(QBrush(m_nodeOutValueColor)); p.drawEllipse(x - outerRadius, y - outerRadius, outerRadius * 2, outerRadius * 2); @@ -1043,7 +983,6 @@ 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 @@ -1057,9 +996,9 @@ inline void AutomationEditor::drawAutomationTangents(QPainter& p, timeMap::itera p.setPen(QPen(m_nodeTangentLineColor)); p.setBrush(QBrush(m_nodeTangentLineColor)); - y = yCoordOfLevel(INVAL(it)); - tx = x - 20; - ty = y + 20 * INTAN(it) * proportion; + int y = yCoordOfLevel(INVAL(it)); + int tx = x - 20; + int 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); @@ -1074,8 +1013,19 @@ inline void AutomationEditor::drawAutomationTangents(QPainter& p, timeMap::itera p.drawEllipse(tx - 3, ty - 3, 6, 6); } +void AutomationEditor::setGhostMidiClip(MidiClip* newMidiClip) +{ + // Expects a pointer to a MIDI clip or nullptr. + m_ghostNotes = newMidiClip; + m_renderSample = false; +} - +void AutomationEditor::setGhostSample(SampleClip* newGhostSample) +{ + // Expects a pointer to a Sample buffer or nullptr. + m_ghostSample = newGhostSample; + m_renderSample = true; +} void AutomationEditor::paintEvent(QPaintEvent * pe ) { @@ -1090,8 +1040,7 @@ void AutomationEditor::paintEvent(QPaintEvent * pe ) QBrush bgColor = p.background(); p.fillRect( 0, 0, width(), height(), bgColor ); - // set font-size to 8 - p.setFont( pointSize<8>( p.font() ) ); + p.setFont(adjustedToPixelSize(p.font(), 10)); int grid_height = height() - TOP_MARGIN - SCROLLBAR_SIZE; @@ -1127,7 +1076,6 @@ void AutomationEditor::paintEvent(QPaintEvent * pe ) } else { - int y; int level = (int) m_bottomLevel; int printable = qMax( 1, 5 * DEFAULT_Y_DELTA / m_y_delta ); @@ -1142,7 +1090,7 @@ void AutomationEditor::paintEvent(QPaintEvent * pe ) { const QString & label = m_clip->firstObject() ->displayValue( level ); - y = yCoordOfLevel( level ); + int y = yCoordOfLevel(level); p.setPen( QApplication::palette().color( QPalette::Active, QPalette::Shadow ) ); p.drawText( 1, y - font_height + 1, @@ -1165,7 +1113,7 @@ void AutomationEditor::paintEvent(QPaintEvent * pe ) if( m_clip ) { - int tick, x, q; + int q; int x_line_end = (int)( m_y_auto || m_topLevel < m_maxLevel ? TOP_MARGIN : grid_bottom - ( m_topLevel - m_bottomLevel ) * m_y_delta ); @@ -1189,10 +1137,8 @@ void AutomationEditor::paintEvent(QPaintEvent * pe ) // 3 independent loops, because quantization might not divide evenly into // exotic denominators (e.g. 7/11 time), which are allowed ATM. // First quantization grid... - for( tick = m_currentPosition - m_currentPosition % q, - x = xCoordOfTick( tick ); - x<=width(); - tick += q, x = xCoordOfTick( tick ) ) + for (int tick = m_currentPosition - m_currentPosition % q, x = xCoordOfTick(tick); x <= width(); + tick += q, x = xCoordOfTick(tick)) { p.setPen(m_lineColor); p.drawLine( x, grid_bottom, x, x_line_end ); @@ -1213,10 +1159,9 @@ void AutomationEditor::paintEvent(QPaintEvent * pe ) } else { - float y; for( int level = (int)m_bottomLevel; level <= m_topLevel; level++) { - y = yCoordOfLevel( (float)level ); + float y = yCoordOfLevel(static_cast(level)); p.setPen(level % 10 == 0 ? m_beatLineColor : m_lineColor); @@ -1252,20 +1197,96 @@ void AutomationEditor::paintEvent(QPaintEvent * pe ) int ticksPerBeat = DefaultTicksPerBar / Engine::getSong()->getTimeSigModel().getDenominator(); - for( tick = m_currentPosition - m_currentPosition % ticksPerBeat, - x = xCoordOfTick( tick ); - x<=width(); - tick += ticksPerBeat, x = xCoordOfTick( tick ) ) + for (int tick = m_currentPosition - m_currentPosition % ticksPerBeat, x = xCoordOfTick(tick); x <= width(); + tick += ticksPerBeat, x = xCoordOfTick(tick)) { p.setPen(m_beatLineColor); p.drawLine( x, grid_bottom, x, x_line_end ); } + // draw ghost sample + if (m_ghostSample != nullptr && m_ghostSample->sample().sampleSize() > 1 && m_renderSample) + { + int sampleFrames = m_ghostSample->sample().sampleSize(); + int length = static_cast(sampleFrames) / Engine::framesPerTick(); + int editorHeight = grid_bottom - TOP_MARGIN; + + int startPos = xCoordOfTick(0); + int sampleWidth = xCoordOfTick(length) - startPos; + int sampleHeight = std::min(editorHeight - SAMPLE_MARGIN, MAX_SAMPLE_HEIGHT); + int yOffset = (editorHeight - sampleHeight) / 2.0f + TOP_MARGIN; + + p.setPen(m_ghostSampleColor); + + const auto& sample = m_ghostSample->sample(); + const auto waveform = SampleWaveform::Parameters{ + sample.data(), sample.sampleSize(), sample.amplification(), sample.reversed()}; + const auto rect = QRect(startPos, yOffset, sampleWidth, sampleHeight); + SampleWaveform::visualize(waveform, p, rect); + } + + // draw ghost notes + if (m_ghostNotes != nullptr && !m_renderSample) + { + const NoteVector& notes = m_ghostNotes->notes(); + int minKey = 128; + int maxKey = 0; + int detuningOffset = 0; + const Note* detuningNote = nullptr; + + for (const Note* note : notes) + { + int noteKey = note->key(); + + if (note->detuning()->automationClip() == m_clip) { + detuningOffset = note->pos(); + detuningNote = note; + } + + maxKey = std::max(maxKey, noteKey); + minKey = std::min(minKey, noteKey); + } + + for (const Note* note : notes) + { + int lenTicks = note->length(); + int notePos = note->pos(); + + // offset note if detuning + if (notePos+lenTicks < detuningOffset) { continue; } + notePos -= detuningOffset; + + // remove/change after #5902 + if (lenTicks == 0) { continue; } + else if (lenTicks < 0) { lenTicks = 4; } + + int note_width = lenTicks * m_ppb / TimePos::ticksPerBar(); + int keyRange = maxKey - minKey; + + if (keyRange < MIN_NOTE_RANGE) + { + int padding = (MIN_NOTE_RANGE - keyRange) / 2.0f; + maxKey += padding; + minKey -= padding; + keyRange = MIN_NOTE_RANGE; + } + + float absNoteHeight = static_cast(note->key() - minKey) / (maxKey - minKey); + int graphHeight = grid_bottom - NOTE_HEIGHT - NOTE_MARGIN - TOP_MARGIN; + const int y = (graphHeight - graphHeight * absNoteHeight) + NOTE_HEIGHT / 2.0f + TOP_MARGIN; + const int x = xCoordOfTick(notePos); + + if (note == detuningNote) { + p.fillRect(x, y, note_width, NOTE_HEIGHT, m_detuningNoteColor); + } else { + p.fillRect(x, y, note_width, NOTE_HEIGHT, m_ghostNoteColor); + } + } + } + // and finally bars - for( tick = m_currentPosition - m_currentPosition % TimePos::ticksPerBar(), - x = xCoordOfTick( tick ); - x<=width(); - tick += TimePos::ticksPerBar(), x = xCoordOfTick( tick ) ) + for (int tick = m_currentPosition - m_currentPosition % TimePos::ticksPerBar(), x = xCoordOfTick(tick); + x <= width(); tick += TimePos::ticksPerBar(), x = xCoordOfTick(tick)) { p.setPen(m_barLineColor); p.drawLine( x, grid_bottom, x, x_line_end ); @@ -1311,15 +1332,9 @@ void AutomationEditor::paintEvent(QPaintEvent * pe ) // the outValue of the current node). When we have nodes with linear or cubic progression // the value of the end of the shape between the two nodes will be the inValue of // the next node. - float nextValue; - if( m_clip->progressionType() == AutomationClip::ProgressionType::Discrete ) - { - nextValue = OUTVAL(it); - } - else - { - nextValue = INVAL(it + 1); - } + float nextValue = m_clip->progressionType() == AutomationClip::ProgressionType::Discrete + ? OUTVAL(it) + : INVAL(it + 1); p.setRenderHints( QPainter::Antialiasing, true ); QPainterPath path; @@ -1367,16 +1382,16 @@ void AutomationEditor::paintEvent(QPaintEvent * pe ) } else { - QFont f = p.font(); + QFont f = font(); f.setBold( true ); - p.setFont( pointSize<14>( f ) ); + p.setFont(f); p.setPen( QApplication::palette().color( QPalette::Active, QPalette::BrightText ) ); p.drawText( VALUES_WIDTH + 20, TOP_MARGIN + 40, width() - VALUES_WIDTH - 20 - SCROLLBAR_SIZE, grid_height - 40, Qt::TextWordWrap, - tr( "Please open an automation clip with " - "the context menu of a control!" ) ); + tr( "Please open an automation clip by " + "double-clicking on it!" ) ); } // TODO: Get this out of paint event @@ -1400,26 +1415,26 @@ void AutomationEditor::paintEvent(QPaintEvent * pe ) { case EditMode::Draw: { - if (m_action == Action::EraseValues) { cursor = s_toolErase; } - else if (m_action == Action::MoveValue) { cursor = s_toolMove; } - else { cursor = s_toolDraw; } + if (m_action == Action::EraseValues) { cursor = &m_toolErase; } + else if (m_action == Action::MoveValue) { cursor = &m_toolMove; } + else { cursor = &m_toolDraw; } break; } case EditMode::Erase: { - cursor = s_toolErase; + cursor = &m_toolErase; break; } case EditMode::DrawOutValues: { - if (m_action == Action::ResetOutValues) { cursor = s_toolErase; } - else if (m_action == Action::MoveOutValue) { cursor = s_toolMove; } - else { cursor = s_toolDrawOut; } + if (m_action == Action::ResetOutValues) { cursor = &m_toolErase; } + else if (m_action == Action::MoveOutValue) { cursor = &m_toolMove; } + else { cursor = &m_toolDrawOut; } break; } case EditMode::EditTangents: { - cursor = m_action == Action::MoveTangent ? s_toolMove : s_toolEditTangents; + cursor = m_action == Action::MoveTangent ? &m_toolMove : &m_toolEditTangents; break; } } @@ -1469,25 +1484,11 @@ void AutomationEditor::drawLevelTick(QPainter & p, int tick, float value) || ( value > m_topLevel && m_topLevel >= 0 ) || ( value < m_bottomLevel && m_bottomLevel <= 0 ) ) { - int y_start = yCoordOfLevel( value ); - int rect_height; - - if( m_y_auto ) - { - int y_end = (int)( grid_bottom - + ( grid_bottom - TOP_MARGIN ) - * m_minLevel - / ( m_maxLevel - m_minLevel ) ); - - rect_height = y_end - y_start; - } - else - { - rect_height = (int)( value * m_y_delta ); - } + const int y_start = yCoordOfLevel(value); + const int y_end = grid_bottom + (grid_bottom - TOP_MARGIN) * m_minLevel / (m_maxLevel - m_minLevel); + const int rect_height = m_y_auto ? y_end - y_start : value * m_y_delta; QBrush currentColor = m_graphColor; - p.fillRect( x, y_start, rect_width, rect_height, currentColor ); } #ifdef LMMS_DEBUG @@ -1553,11 +1554,7 @@ void AutomationEditor::resizeEvent(QResizeEvent * re) } centerTopBottomScroll(); - if( Engine::getSong() ) - { - Engine::getSong()->getPlayPos( Song::PlayMode::AutomationClip - ).m_timeLine->setFixedWidth( width() ); - } + m_timeLine->setFixedWidth(width()); updateTopBottomLevels(); update(); @@ -1650,14 +1647,14 @@ float AutomationEditor::getLevel(int y ) { int level_line_y = height() - SCROLLBAR_SIZE - 1; // pressed level - float level = roundf( ( m_bottomLevel + ( m_y_auto ? + float level = std::roundf( ( m_bottomLevel + ( m_y_auto ? ( m_maxLevel - m_minLevel ) * ( level_line_y - y ) / (float)( level_line_y - ( TOP_MARGIN + 2 ) ) : ( level_line_y - y ) / (float)m_y_delta ) ) / m_step ) * m_step; // some range-checking-stuff - level = qBound( m_bottomLevel, level, m_topLevel ); + level = qBound(std::roundf(m_bottomLevel), level, std::roundf(m_topLevel)); - return( level ); + return level; } @@ -2159,8 +2156,18 @@ AutomationEditorWindow::AutomationEditorWindow() : quantizationActionsToolBar->addWidget( quantize_lbl ); quantizationActionsToolBar->addWidget( m_quantizeComboBox ); + m_resetGhostNotes = new QPushButton(m_toolBar); + m_resetGhostNotes->setIcon(embed::getIconPixmap("clear_ghost_note")); + m_resetGhostNotes->setToolTip(tr("Clear ghost notes")); + m_resetGhostNotes->setEnabled(true); + + connect(m_resetGhostNotes, &QPushButton::pressed, m_editor, &AutomationEditor::resetGhostNotes); + + quantizationActionsToolBar->addSeparator(); + quantizationActionsToolBar->addWidget(m_resetGhostNotes); + // Setup our actual window - setFocusPolicy( Qt::StrongFocus ); + setFocusPolicy(Qt::StrongFocus); setFocus(); setWindowIcon( embed::getIconPixmap( "automation" ) ); setAcceptDrops( true ); diff --git a/src/gui/editors/Editor.cpp b/src/gui/editors/Editor.cpp index 7091c094b..a61c2cd60 100644 --- a/src/gui/editors/Editor.cpp +++ b/src/gui/editors/Editor.cpp @@ -148,7 +148,7 @@ void Editor::closeEvent( QCloseEvent * _ce ) { hide(); } - _ce->ignore(); + _ce->accept(); } DropToolBar::DropToolBar(QWidget* parent) : QToolBar(parent) diff --git a/src/gui/editors/PianoRoll.cpp b/src/gui/editors/PianoRoll.cpp index cef2205d2..4723004e8 100644 --- a/src/gui/editors/PianoRoll.cpp +++ b/src/gui/editors/PianoRoll.cpp @@ -59,7 +59,6 @@ #include "DetuningHelper.h" #include "embed.h" #include "GuiApplication.h" -#include "gui_templates.h" #include "InstrumentTrack.h" #include "MainWindow.h" #include "MidiClip.h" @@ -119,15 +118,6 @@ const int INITIAL_START_KEY = Octave::Octave_4 + Key::C; const int NUM_EVEN_LENGTHS = 6; const int NUM_TRIPLET_LENGTHS = 5; - - -QPixmap * PianoRoll::s_toolDraw = nullptr; -QPixmap * PianoRoll::s_toolErase = nullptr; -QPixmap * PianoRoll::s_toolSelect = nullptr; -QPixmap * PianoRoll::s_toolMove = nullptr; -QPixmap * PianoRoll::s_toolOpen = nullptr; -QPixmap* PianoRoll::s_toolKnife = nullptr; - SimpleTextFloat * PianoRoll::s_textFloat = nullptr; static std::array s_noteStrings { @@ -170,6 +160,7 @@ PianoRoll::PianoRoll() : m_midiClip( nullptr ), m_currentPosition(), m_recording( false ), + m_doAutoQuantization(ConfigManager::inst()->value("midi", "autoquantize").toInt() != 0), m_currentNote( nullptr ), m_action( Action::None ), m_noteEditMode( NoteEditMode::Volume ), @@ -250,6 +241,15 @@ PianoRoll::PianoRoll() : connect( markChordAction, &QAction::triggered, [this](){ markSemiTone(SemiToneMarkerAction::MarkCurrentChord); }); connect( unmarkAllAction, &QAction::triggered, [this](){ markSemiTone(SemiToneMarkerAction::UnmarkAll); }); connect( copyAllNotesAction, &QAction::triggered, [this](){ markSemiTone(SemiToneMarkerAction::CopyAllNotesOnKey); }); + connect(ConfigManager::inst(), &ConfigManager::valueChanged, + [this](QString const& cls, QString const& attribute, QString const& value) + { + if (!(cls == "midi" && attribute == "autoquantize")) + { + return; + } + this->m_doAutoQuantization = (value.toInt() != 0); + }); markScaleAction->setEnabled( false ); markChordAction->setEnabled( false ); @@ -264,32 +264,6 @@ PianoRoll::PianoRoll() : m_semiToneMarkerMenu->addAction( unmarkAllAction ); m_semiToneMarkerMenu->addAction( copyAllNotesAction ); - // init pixmaps - if( s_toolDraw == nullptr ) - { - s_toolDraw = new QPixmap( embed::getIconPixmap( "edit_draw" ) ); - } - if( s_toolErase == nullptr ) - { - s_toolErase= new QPixmap( embed::getIconPixmap( "edit_erase" ) ); - } - if( s_toolSelect == nullptr ) - { - s_toolSelect = new QPixmap( embed::getIconPixmap( "edit_select" ) ); - } - if( s_toolMove == nullptr ) - { - s_toolMove = new QPixmap( embed::getIconPixmap( "edit_move" ) ); - } - if( s_toolOpen == nullptr ) - { - s_toolOpen = new QPixmap( embed::getIconPixmap( "automation" ) ); - } - if (s_toolKnife == nullptr) - { - s_toolKnife = new QPixmap(embed::getIconPixmap("edit_knife")); - } - // init text-float if( s_textFloat == nullptr ) { @@ -300,12 +274,11 @@ PianoRoll::PianoRoll() : // add time-line m_timeLine = new TimeLineWidget(m_whiteKeyWidth, 0, m_ppb, - Engine::getSong()->getPlayPos( - Song::PlayMode::MidiClip ), - m_currentPosition, - Song::PlayMode::MidiClip, this ); - connect( this, SIGNAL( positionChanged( const lmms::TimePos& ) ), - m_timeLine, SLOT( updatePosition( const lmms::TimePos& ) ) ); + Engine::getSong()->getPlayPos(Song::PlayMode::MidiClip), + Engine::getSong()->getTimeline(Song::PlayMode::MidiClip), + m_currentPosition, Song::PlayMode::MidiClip, this + ); + connect(this, &PianoRoll::positionChanged, m_timeLine, &TimeLineWidget::updatePosition); connect( m_timeLine, SIGNAL( positionChanged( const lmms::TimePos& ) ), this, SLOT( updatePosition( const lmms::TimePos& ) ) ); @@ -317,10 +290,7 @@ PianoRoll::PianoRoll() : this, SLOT( updatePositionStepRecording( const lmms::TimePos& ) ) ); // update timeline when in record-accompany mode - connect( Engine::getSong()->getPlayPos( Song::PlayMode::Song ).m_timeLine, - SIGNAL( positionChanged( const lmms::TimePos& ) ), - this, - SLOT( updatePositionAccompany( const lmms::TimePos& ) ) ); + connect(m_timeLine, &TimeLineWidget::positionChanged, this, &PianoRoll::updatePositionAccompany); // TODO /* connect( engine::getSong()->getPlayPos( Song::PlayMode::Pattern ).m_timeLine, SIGNAL( positionChanged( const lmms::TimePos& ) ), @@ -372,7 +342,7 @@ PianoRoll::PianoRoll() : // Set up note length model m_noteLenModel.addItem( tr( "Last note" ), std::make_unique( "edit_draw" ) ); - const auto pixmaps = std::array{"whole", "half", "quarter", "eighth", + const auto pixmaps = std::array{"whole", "half", "quarter", "eighth", "sixteenth", "thirtysecond", "triplethalf", "tripletquarter", "tripleteighth", "tripletsixteenth", "tripletthirtysecond"}; @@ -1670,6 +1640,7 @@ void PianoRoll::mousePressEvent(QMouseEvent * me ) } detuningClip = n->detuning()->automationClip(); connect(detuningClip.data(), SIGNAL(dataChanged()), this, SLOT(update())); + getGUI()->automationEditor()->setGhostMidiClip(m_midiClip); getGUI()->automationEditor()->open(detuningClip); return; } @@ -2517,7 +2488,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; @@ -2684,7 +2655,7 @@ void PianoRoll::mouseMoveEvent( QMouseEvent * me ) ) { // delete this note - m_midiClip->removeNote( note ); + it = m_midiClip->removeNote(it); Engine::getSong()->setModified(); } else @@ -3068,10 +3039,9 @@ void PianoRoll::paintEvent(QPaintEvent * pe ) if (hasValidMidiClip()) { - int pianoAreaHeight, partialKeyVisible, topKey, topNote; - pianoAreaHeight = keyAreaBottom() - keyAreaTop(); + int pianoAreaHeight = keyAreaBottom() - keyAreaTop(); m_pianoKeysVisible = pianoAreaHeight / m_keyLineHeight; - partialKeyVisible = pianoAreaHeight % m_keyLineHeight; + int partialKeyVisible = pianoAreaHeight % m_keyLineHeight; // check if we're below the minimum key area size if (m_pianoKeysVisible * m_keyLineHeight < KEY_AREA_MIN_HEIGHT) { @@ -3096,8 +3066,8 @@ void PianoRoll::paintEvent(QPaintEvent * pe ) PR_TOP_MARGIN - PR_BOTTOM_MARGIN; partialKeyVisible = 0; } - topKey = qBound(0, m_startKey + m_pianoKeysVisible - 1, NumKeys - 1); - topNote = topKey % KeysPerOctave; + int topKey = std::clamp(m_startKey + m_pianoKeysVisible - 1, 0, NumKeys - 1); + int topNote = topKey % KeysPerOctave; // if not resizing the note edit area, we can change m_notesEditHeight if (m_action != Action::ResizeNoteEditArea && partialKeyVisible != 0) { @@ -3374,9 +3344,9 @@ void PianoRoll::paintEvent(QPaintEvent * pe ) m_whiteKeyWidth, noteEditBottom() - keyAreaBottom()), bgColor); // display note editing info - //QFont f = p.font(); - f.setBold( false ); - p.setFont( pointSize<10>( f ) ); + f.setBold(false); + f.setPixelSize(10); + p.setFont(f); p.setPen(m_noteModeColor); p.drawText( QRect( 0, keyAreaBottom(), m_whiteKeyWidth, noteEditBottom() - keyAreaBottom()), @@ -3498,11 +3468,15 @@ void PianoRoll::paintEvent(QPaintEvent * pe ) // is the note in visible area? if (note->key() > bottomKey && note->key() <= topKey) { - // we've done and checked all, let's draw the note + // We've done and checked all, let's draw the note with + // the appropriate color + const auto fillColor = note->type() == Note::Type::Regular ? m_noteColor : m_stepNoteColor; + drawNoteRect( p, x + m_whiteKeyWidth, noteYPos(note->key()), note_width, - note, m_noteColor, m_noteTextColor, m_selectedNoteColor, - m_noteOpacity, m_noteBorders, drawNoteNames); + note, fillColor, m_noteTextColor, m_selectedNoteColor, + m_noteOpacity, m_noteBorders, drawNoteNames + ); } // draw note editing stuff @@ -3633,9 +3607,9 @@ void PianoRoll::paintEvent(QPaintEvent * pe ) } else { - QFont f = p.font(); - f.setBold( true ); - p.setFont( pointSize<14>( f ) ); + QFont f = font(); + f.setBold(true); + p.setFont(f); p.setPen( QApplication::palette().color( QPalette::Active, QPalette::BrightText ) ); p.drawText(m_whiteKeyWidth + 20, PR_TOP_MARGIN + 40, @@ -3699,21 +3673,29 @@ void PianoRoll::paintEvent(QPaintEvent * pe ) case EditMode::Draw: if( m_mouseDownRight ) { - cursor = s_toolErase; + cursor = &m_toolErase; } else if( m_action == Action::MoveNote ) { - cursor = s_toolMove; + cursor = &m_toolMove; } else { - cursor = s_toolDraw; + cursor = &m_toolDraw; } break; - case EditMode::Erase: cursor = s_toolErase; break; - case EditMode::Select: cursor = s_toolSelect; break; - case EditMode::Detuning: cursor = s_toolOpen; break; - case EditMode::Knife: cursor = s_toolKnife; break; + case EditMode::Erase: + cursor = &m_toolErase; + break; + case EditMode::Select: + cursor = &m_toolSelect; + break; + case EditMode::Detuning: + cursor = &m_toolOpen; + break; + case EditMode::Knife: + cursor = &m_toolKnife; + break; } QPoint mousePosition = mapFromGlobal( QCursor::pos() ); if( cursor != nullptr && mousePosition.y() > keyAreaTop() && mousePosition.x() > noteEditLeft()) @@ -3756,8 +3738,7 @@ void PianoRoll::resizeEvent(QResizeEvent* re) { updatePositionLineHeight(); updateScrollbars(); - Engine::getSong()->getPlayPos(Song::PlayMode::MidiClip) - .m_timeLine->setFixedWidth(width()); + m_timeLine->setFixedWidth(width()); update(); } @@ -3793,7 +3774,8 @@ void PianoRoll::wheelEvent(QWheelEvent * we ) } if( nv.size() > 0 ) { - const int step = we->angleDelta().y() > 0 ? 1 : -1; + const int step = (we->angleDelta().y() > 0 ? 1 : -1) * (we->inverted() ? -1 : 1); + if( m_noteEditMode == NoteEditMode::Volume ) { for ( Note * n : nv ) @@ -4137,8 +4119,13 @@ void PianoRoll::finishRecordNote(const Note & n ) Note n1(n.length(), it->pos(), it->key(), it->getVolume(), it->getPanning(), n.detuning()); - n1.quantizeLength( quantization() ); - m_midiClip->addNote( n1 ); + + if (m_doAutoQuantization) + { + n1.quantizeLength(quantization()); + n1.quantizePos(quantization()); + } + m_midiClip->addNote(n1, false); update(); m_recordingNotes.erase( it ); break; @@ -4908,7 +4895,7 @@ PianoRollWindow::PianoRollWindow() : m_quantizeComboBox = new ComboBox( m_toolBar ); m_quantizeComboBox->setModel( &m_editor->m_quantizeModel ); - m_quantizeComboBox->setFixedSize( 64, ComboBox::DEFAULT_HEIGHT ); + m_quantizeComboBox->setFixedSize(85, ComboBox::DEFAULT_HEIGHT); m_quantizeComboBox->setToolTip( tr( "Quantization") ); // setup note-len-stuff @@ -4932,7 +4919,7 @@ PianoRollWindow::PianoRollWindow() : m_scaleComboBox = new ComboBox( m_toolBar ); m_scaleComboBox->setModel( &m_editor->m_scaleModel ); - m_scaleComboBox->setFixedSize( 105, ComboBox::DEFAULT_HEIGHT ); + m_scaleComboBox->setFixedSize(155, ComboBox::DEFAULT_HEIGHT); m_scaleComboBox->setToolTip( tr( "Scale") ); // setup chord-stuff @@ -4941,7 +4928,7 @@ PianoRollWindow::PianoRollWindow() : m_chordComboBox = new ComboBox( m_toolBar ); m_chordComboBox->setModel( &m_editor->m_chordModel ); - m_chordComboBox->setFixedSize( 105, ComboBox::DEFAULT_HEIGHT ); + m_chordComboBox->setFixedSize(125, ComboBox::DEFAULT_HEIGHT); m_chordComboBox->setToolTip( tr( "Chord" ) ); // setup snap-stuff @@ -4950,7 +4937,7 @@ PianoRollWindow::PianoRollWindow() : m_snapComboBox = new ComboBox(m_toolBar); m_snapComboBox->setModel(&m_editor->m_snapModel); - m_snapComboBox->setFixedSize(105, ComboBox::DEFAULT_HEIGHT); + m_snapComboBox->setFixedSize(96, ComboBox::DEFAULT_HEIGHT); m_snapComboBox->setToolTip(tr("Snap mode")); // -- Clear ghost MIDI clip button @@ -5189,7 +5176,8 @@ void PianoRollWindow::saveSettings( QDomDocument & doc, QDomElement & de ) de.appendChild(markedSemiTonesRoot); } - de.setAttribute("stopbehaviour", static_cast(m_editor->m_timeLine->behaviourAtStop())); + de.setAttribute("stopbehaviour", static_cast( + Engine::getSong()->getTimeline(Song::PlayMode::MidiClip).stopBehaviour())); MainWindow::saveWidgetState( this, de ); } @@ -5204,7 +5192,8 @@ void PianoRollWindow::loadSettings( const QDomElement & de ) MainWindow::restoreWidgetState( this, de ); - m_editor->m_timeLine->setBehaviourAtStop(de.attribute("stopbehaviour").toInt()); + Engine::getSong()->getTimeline(Song::PlayMode::MidiClip).setStopBehaviour( + static_cast(de.attribute("stopbehaviour").toInt())); // update margins here because we're later in the startup process // We can't earlier because everything is still starting with the diff --git a/src/gui/editors/PositionLine.cpp b/src/gui/editors/PositionLine.cpp index 8b938443d..dda44e8f7 100644 --- a/src/gui/editors/PositionLine.cpp +++ b/src/gui/editors/PositionLine.cpp @@ -93,7 +93,7 @@ void PositionLine::zoomChange(float zoom) { int playHeadPos = x() + width() - 1; - resize(8.0 * zoom, height()); + resize(std::max(8.0 * zoom, 1.0), height()); move(playHeadPos - width() + 1, y()); update(); diff --git a/src/gui/editors/SongEditor.cpp b/src/gui/editors/SongEditor.cpp index fd65a2200..ded928157 100644 --- a/src/gui/editors/SongEditor.cpp +++ b/src/gui/editors/SongEditor.cpp @@ -63,7 +63,7 @@ namespace lmms::gui namespace { -constexpr int MIN_PIXELS_PER_BAR = 2; +constexpr int MIN_PIXELS_PER_BAR = 4; constexpr int MAX_PIXELS_PER_BAR = 400; constexpr int ZOOM_STEPS = 200; @@ -97,14 +97,12 @@ SongEditor::SongEditor( Song * song ) : m_zoomingModel->setParent(this); m_snappingModel->setParent(this); - m_timeLine = new TimeLineWidget( m_trackHeadWidth, 32, - pixelsPerBar(), - m_song->getPlayPos(Song::PlayMode::Song), - m_currentPosition, - Song::PlayMode::Song, this ); - connect( this, SIGNAL( positionChanged( const lmms::TimePos& ) ), - m_song->getPlayPos(Song::PlayMode::Song).m_timeLine, - SLOT( updatePosition( const lmms::TimePos& ) ) ); + m_timeLine = new TimeLineWidget(m_trackHeadWidth, 32, pixelsPerBar(), + m_song->getPlayPos(Song::PlayMode::Song), + m_song->getTimeline(Song::PlayMode::Song), + m_currentPosition, Song::PlayMode::Song, this + ); + connect(this, &TrackContainerView::positionChanged, m_timeLine, &TimeLineWidget::updatePosition); connect( m_timeLine, SIGNAL( positionChanged( const lmms::TimePos& ) ), this, SLOT( updatePosition( const lmms::TimePos& ) ) ); connect( m_timeLine, SIGNAL(regionSelectedFromPixels(int,int)), @@ -144,17 +142,6 @@ SongEditor::SongEditor( Song * song ) : int tempoSpinBoxCol = getGUI()->mainWindow()->addWidgetToToolBar( m_tempoSpinBox, 0 ); -#if 0 - toolButton * hq_btn = new toolButton( embed::getIconPixmap( "hq_mode" ), - tr( "High quality mode" ), - nullptr, nullptr, tb ); - hq_btn->setCheckable( true ); - connect( hq_btn, SIGNAL(toggled(bool)), - this, SLOT(setHighQuality(bool))); - hq_btn->setFixedWidth( 42 ); - getGUI()->mainWindow()->addWidgetToToolBar( hq_btn, 1, col ); -#endif - getGUI()->mainWindow()->addWidgetToToolBar( new TimeDisplayWidget, 1, tempoSpinBoxCol ); getGUI()->mainWindow()->addSpacingToToolBar( 10 ); @@ -276,7 +263,7 @@ SongEditor::SongEditor( Song * song ) : m_snappingModel->addItem(QString("1/%1 Bar").arg(1 / bars)); } } - m_snappingModel->setInitValue( m_snappingModel->findText( "1 Bar" ) ); + m_snappingModel->setInitValue( m_snappingModel->findText( "1/4 Bar" ) ); setFocusPolicy( Qt::StrongFocus ); setFocus(); @@ -335,19 +322,6 @@ QString SongEditor::getSnapSizeString() const } } - - - -void SongEditor::setHighQuality( bool hq ) -{ - Engine::audioEngine()->changeQuality( AudioEngine::qualitySettings( - hq ? AudioEngine::qualitySettings::Mode::HighQuality : - AudioEngine::qualitySettings::Mode::Draft ) ); -} - - - - void SongEditor::scrolled( int new_pos ) { update(); @@ -473,6 +447,8 @@ void SongEditor::toggleProportionalSnap() { m_proportionalSnap = !m_proportionalSnap; m_timeLine->setSnapSize(getSnapSize()); + + emit proportionalSnapChanged(); } @@ -560,7 +536,7 @@ void SongEditor::wheelEvent( QWheelEvent * we ) m_leftRightScroll->setValue(m_leftRightScroll->value() + bar - newBar); // update timeline - m_song->getPlayPos(Song::PlayMode::Song).m_timeLine->setPixelsPerBar(pixelsPerBar()); + m_timeLine->setPixelsPerBar(pixelsPerBar()); // and make sure, all Clip's are resized and relocated realignTracks(); } @@ -776,17 +752,9 @@ static inline void animateScroll( QScrollBar *scrollBar, int newVal, bool smooth void SongEditor::updatePosition( const TimePos & t ) { - int widgetWidth, trackOpWidth; - if( ConfigManager::inst()->value( "ui", "compacttrackbuttons" ).toInt() ) - { - widgetWidth = DEFAULT_SETTINGS_WIDGET_WIDTH_COMPACT; - trackOpWidth = TRACK_OP_WIDTH_COMPACT; - } - else - { - widgetWidth = DEFAULT_SETTINGS_WIDGET_WIDTH; - trackOpWidth = TRACK_OP_WIDTH; - } + const bool compactTrackButtons = ConfigManager::inst()->value("ui", "compacttrackbuttons").toInt(); + const auto widgetWidth = compactTrackButtons ? DEFAULT_SETTINGS_WIDGET_WIDTH_COMPACT : DEFAULT_SETTINGS_WIDGET_WIDTH; + const auto trackOpWidth = compactTrackButtons ? TRACK_OP_WIDTH_COMPACT : TRACK_OP_WIDTH; if( ( m_song->isPlaying() && m_song->m_playMode == Song::PlayMode::Song && m_timeLine->autoScroll() == TimeLineWidget::AutoScrollState::Enabled) || @@ -808,8 +776,7 @@ void SongEditor::updatePosition( const TimePos & t ) m_scrollBack = false; } - const int x = m_song->getPlayPos(Song::PlayMode::Song).m_timeLine-> - markerX( t ) + 8; + const int x = m_timeLine->markerX(t); if( x >= trackOpWidth + widgetWidth -1 ) { m_positionLine->show(); @@ -872,7 +839,7 @@ void SongEditor::zoomingChanged() int ppb = calculatePixelsPerBar(); setPixelsPerBar(ppb); - m_song->getPlayPos(Song::PlayMode::Song).m_timeLine->setPixelsPerBar(ppb); + m_timeLine->setPixelsPerBar(ppb); realignTracks(); updateRubberband(); m_timeLine->setSnapSize(getSnapSize()); diff --git a/src/gui/editors/TimeLineWidget.cpp b/src/gui/editors/TimeLineWidget.cpp index 423485a25..7657e2916 100644 --- a/src/gui/editors/TimeLineWidget.cpp +++ b/src/gui/editors/TimeLineWidget.cpp @@ -27,11 +27,14 @@ #include #include +#include +#include #include #include #include #include +#include "ConfigManager.h" #include "embed.h" #include "GuiApplication.h" #include "NStateButton.h" @@ -45,58 +48,23 @@ namespace constexpr int MIN_BAR_LABEL_DISTANCE = 35; } - -QPixmap * TimeLineWidget::s_posMarkerPixmap = nullptr; - -TimeLineWidget::TimeLineWidget( const int xoff, const int yoff, const float ppb, - Song::PlayPos & pos, const TimePos & begin, Song::PlayMode mode, - QWidget * parent ) : - QWidget( parent ), - m_inactiveLoopColor( 52, 63, 53, 64 ), - m_inactiveLoopBrush( QColor( 255, 255, 255, 32 ) ), - m_inactiveLoopInnerColor( 255, 255, 255, 32 ), - m_activeLoopColor( 52, 63, 53, 255 ), - m_activeLoopBrush( QColor( 55, 141, 89 ) ), - m_activeLoopInnerColor( 74, 155, 100, 255 ), - m_loopRectangleVerticalPadding( 1 ), - m_barLineColor( 192, 192, 192 ), - m_barNumberColor( m_barLineColor.darker( 120 ) ), - m_autoScroll( AutoScrollState::Enabled ), - m_loopPoints( LoopPointState::Disabled ), - m_behaviourAtStop( BehaviourAtStopState::BackToZero ), - m_changedPosition( true ), - m_xOffset( xoff ), - m_posMarkerX( 0 ), - m_ppb( ppb ), - m_snapSize( 1.0 ), - m_pos( pos ), - m_begin( begin ), - m_mode( mode ), - m_savedPos( -1 ), - m_hint( nullptr ), - m_action( Action::NoAction ), - m_moveXOff( 0 ) +TimeLineWidget::TimeLineWidget(const int xoff, const int yoff, const float ppb, Song::PlayPos& pos, Timeline& timeline, + const TimePos& begin, Song::PlayMode mode, QWidget* parent) : + QWidget{parent}, + m_xOffset{xoff}, + m_ppb{ppb}, + m_pos{pos}, + m_timeline{&timeline}, + m_begin{begin}, + m_mode{mode} { - m_loopPos[0] = 0; - m_loopPos[1] = DefaultTicksPerBar; - - if( s_posMarkerPixmap == nullptr ) - { - s_posMarkerPixmap = new QPixmap( embed::getIconPixmap( - "playpos_marker" ) ); - } - setAttribute( Qt::WA_OpaquePaintEvent, true ); move( 0, yoff ); - m_xOffset -= s_posMarkerPixmap->width() / 2; - setMouseTracking(true); - m_pos.m_timeLine = this; auto updateTimer = new QTimer(this); - connect( updateTimer, SIGNAL(timeout()), - this, SLOT(updatePosition())); + connect(updateTimer, &QTimer::timeout, this, &TimeLineWidget::updatePosition); updateTimer->start( 1000 / 60 ); // 60 fps connect( Engine::getSong(), SIGNAL(timeSignatureChanged(int,int)), this, SLOT(update())); @@ -107,10 +75,6 @@ TimeLineWidget::TimeLineWidget( const int xoff, const int yoff, const float ppb, TimeLineWidget::~TimeLineWidget() { - if( getGUI()->songEditor() ) - { - m_pos.m_timeLine = nullptr; - } delete m_hint; } @@ -119,12 +83,8 @@ TimeLineWidget::~TimeLineWidget() void TimeLineWidget::setXOffset(const int x) { m_xOffset = x; - if (s_posMarkerPixmap != nullptr) { m_xOffset -= s_posMarkerPixmap->width() / 2; } } - - - void TimeLineWidget::addToolButtons( QToolBar * _tool_bar ) { auto autoScroll = new NStateButton(_tool_bar); @@ -138,10 +98,10 @@ void TimeLineWidget::addToolButtons( QToolBar * _tool_bar ) loopPoints->setGeneralToolTip( tr( "Loop points" ) ); loopPoints->addState( embed::getIconPixmap( "loop_points_off" ) ); loopPoints->addState( embed::getIconPixmap( "loop_points_on" ) ); - connect( loopPoints, SIGNAL(changedState(int)), this, - SLOT(toggleLoopPoints(int))); - connect( this, SIGNAL(loopPointStateLoaded(int)), loopPoints, - SLOT(changeState(int))); + connect(loopPoints, &NStateButton::changedState, m_timeline, &Timeline::setLoopEnabled); + connect(m_timeline, &Timeline::loopEnabledChanged, loopPoints, &NStateButton::changeState); + connect(m_timeline, &Timeline::loopEnabledChanged, this, static_cast(&QWidget::update)); + loopPoints->changeState(static_cast(m_timeline->loopEnabled())); auto behaviourAtStop = new NStateButton(_tool_bar); behaviourAtStop->addState( embed::getIconPixmap( "back_to_zero" ), @@ -153,90 +113,34 @@ void TimeLineWidget::addToolButtons( QToolBar * _tool_bar ) "started" ) ); behaviourAtStop->addState( embed::getIconPixmap( "keep_stop_position" ), tr( "After stopping keep position" ) ); - connect( behaviourAtStop, SIGNAL(changedState(int)), this, - SLOT(toggleBehaviourAtStop(int))); - connect( this, SIGNAL(loadBehaviourAtStop(int)), behaviourAtStop, - SLOT(changeState(int))); - behaviourAtStop->changeState( static_cast(BehaviourAtStopState::BackToStart) ); + connect(behaviourAtStop, &NStateButton::changedState, m_timeline, + [timeline = m_timeline](int value) { + timeline->setStopBehaviour(static_cast(value)); + } + ); + connect(m_timeline, &Timeline::stopBehaviourChanged, behaviourAtStop, + [button = behaviourAtStop](Timeline::StopBehaviour value) { + button->changeState(static_cast(value)); + } + ); + behaviourAtStop->changeState(static_cast(m_timeline->stopBehaviour())); _tool_bar->addWidget( autoScroll ); _tool_bar->addWidget( loopPoints ); _tool_bar->addWidget( behaviourAtStop ); } - - - -void TimeLineWidget::saveSettings( QDomDocument & _doc, QDomElement & _this ) +void TimeLineWidget::updatePosition() { - _this.setAttribute( "lp0pos", (int) loopBegin() ); - _this.setAttribute( "lp1pos", (int) loopEnd() ); - _this.setAttribute( "lpstate", static_cast(m_loopPoints) ); - _this.setAttribute( "stopbehaviour", static_cast(m_behaviourAtStop) ); -} - - - - -void TimeLineWidget::loadSettings( const QDomElement & _this ) -{ - m_loopPos[0] = _this.attribute( "lp0pos" ).toInt(); - m_loopPos[1] = _this.attribute( "lp1pos" ).toInt(); - m_loopPoints = static_cast( - _this.attribute( "lpstate" ).toInt() ); + emit positionChanged(m_pos); update(); - emit loopPointStateLoaded( static_cast(m_loopPoints) ); - - if( _this.hasAttribute( "stopbehaviour" ) ) - { - emit loadBehaviourAtStop( _this.attribute( "stopbehaviour" ).toInt() ); - } } - - - -void TimeLineWidget::updatePosition( const TimePos & ) -{ - const int new_x = markerX( m_pos ); - - if( new_x != m_posMarkerX ) - { - m_posMarkerX = new_x; - m_changedPosition = true; - emit positionChanged( m_pos ); - update(); - } -} - - - - void TimeLineWidget::toggleAutoScroll( int _n ) { m_autoScroll = static_cast( _n ); } - - - -void TimeLineWidget::toggleLoopPoints( int _n ) -{ - m_loopPoints = static_cast( _n ); - update(); -} - - - - -void TimeLineWidget::toggleBehaviourAtStop( int _n ) -{ - m_behaviourAtStop = static_cast( _n ); -} - - - - void TimeLineWidget::paintEvent( QPaintEvent * ) { QPainter p( this ); @@ -245,19 +149,18 @@ void TimeLineWidget::paintEvent( QPaintEvent * ) p.fillRect( 0, 0, width(), height(), p.background() ); // Clip so that we only draw everything starting from the offset - const int leftMargin = m_xOffset + s_posMarkerPixmap->width() / 2; - p.setClipRect(leftMargin, 0, width() - leftMargin, height() ); + p.setClipRect(m_xOffset, 0, width() - m_xOffset, height()); - // Draw the loop rectangle - int const & loopRectMargin = getLoopRectangleVerticalPadding(); + // Variables for the loop rectangle + int const loopRectMargin = getLoopRectangleVerticalPadding(); int const loopRectHeight = this->height() - 2 * loopRectMargin; - int const loopStart = markerX( loopBegin() ) + 8; - int const loopEndR = markerX( loopEnd() ) + 9; + int const loopStart = markerX(m_timeline->loopBegin()); + int const loopEndR = markerX(m_timeline->loopEnd()); int const loopRectWidth = loopEndR - loopStart; - bool const loopPointsActive = loopPointsEnabled(); + bool const loopPointsActive = m_timeline->loopEnabled(); - // Draw the main rectangle (inner fill only) + // Draw the main loop rectangle (inner fill only) QRect outerRectangle( loopStart, loopRectMargin, loopRectWidth - 1, loopRectHeight - 1 ); p.fillRect( outerRectangle, loopPointsActive ? getActiveLoopBrush() : getInactiveLoopBrush()); @@ -273,8 +176,7 @@ void TimeLineWidget::paintEvent( QPaintEvent * ) QColor const & barNumberColor = getBarNumberColor(); bar_t barNumber = m_begin.getBar(); - int const x = m_xOffset + s_posMarkerPixmap->width() / 2 - - ( ( static_cast( m_begin * m_ppb ) / TimePos::ticksPerBar() ) % static_cast( m_ppb ) ); + int const x = m_xOffset - ((static_cast(m_begin * m_ppb) / TimePos::ticksPerBar()) % static_cast(m_ppb)); // Double the interval between bar numbers until they are far enough appart int barLabelInterval = 1; @@ -295,138 +197,214 @@ void TimeLineWidget::paintEvent( QPaintEvent * ) } } - // Draw the main rectangle (outer border) + // Draw the loop rectangle's outer outline p.setPen( loopPointsActive ? getActiveLoopColor() : getInactiveLoopColor() ); p.setBrush( Qt::NoBrush ); p.drawRect( outerRectangle ); - // Draw the inner border outline (no fill) + // Draw the loop rectangle's inner outline QRect innerRectangle = outerRectangle.adjusted( 1, 1, -1, -1 ); p.setPen( loopPointsActive ? getActiveLoopInnerColor() : getInactiveLoopInnerColor() ); p.setBrush( Qt::NoBrush ); p.drawRect( innerRectangle ); + + // Draw loop handles if necessary + const auto handleMode = ConfigManager::inst()->value("app", "loopmarkermode") == "handles"; + if (handleMode && underMouse() && QGuiApplication::keyboardModifiers().testFlag(Qt::ShiftModifier)) + { + const auto handleWidth = std::min(m_loopHandleWidth, loopRectWidth / 2 - 1); + const auto leftHandle = QRectF(loopStart - .5, loopRectMargin - .5, handleWidth, loopRectHeight); + const auto rightHandle = QRectF(loopEndR - handleWidth - .5, loopRectMargin - .5, handleWidth, loopRectHeight); + const auto color = loopPointsActive ? m_activeLoopHandleColor : m_inactiveLoopHandleColor; + + p.fillRect(leftHandle, color); + p.fillRect(rightHandle, color); + } // Only draw the position marker if the position line is in view - if (m_posMarkerX >= m_xOffset && m_posMarkerX < width() - s_posMarkerPixmap->width() / 2) + if (markerX(m_pos) >= m_xOffset && markerX(m_pos) < width() - m_posMarkerPixmap.width() / 2) { // Let the position marker extrude to the left p.setClipping(false); p.setOpacity(0.6); - p.drawPixmap(m_posMarkerX, height() - s_posMarkerPixmap->height(), *s_posMarkerPixmap); + p.drawPixmap(markerX(m_pos) - (m_posMarkerPixmap.width() / 2), + height() - m_posMarkerPixmap.height(), m_posMarkerPixmap); } } - - - -void TimeLineWidget::mousePressEvent( QMouseEvent* event ) +auto TimeLineWidget::getClickedTime(const int xPosition) const -> TimePos { - if( event->x() < m_xOffset ) + // How far into the timeline we clicked, measuring pixels from the leftmost part of the editor + const auto pixelDelta = std::max(xPosition - m_xOffset, 0); + return m_begin + static_cast(pixelDelta * TimePos::ticksPerBar() / m_ppb); +} + +auto TimeLineWidget::getLoopAction(QMouseEvent* event) const -> TimeLineWidget::Action +{ + const auto mode = ConfigManager::inst()->value("app", "loopmarkermode"); + const auto xPos = event->x(); + const auto button = event->button(); + + if (mode == "handles") { - return; + // Loop start and end pos, or closest edge of screen if loop extends off it + const auto leftMost = std::max(markerX(m_timeline->loopBegin()), m_xOffset); + const auto rightMost = std::min(markerX(m_timeline->loopEnd()), width()); + // Distance from click to handle, positive aimed towards center of loop + const auto deltaLeft = xPos - leftMost; + const auto deltaRight = rightMost - xPos; + + if (deltaLeft < 0 || deltaRight < 0) { return Action::NoAction; } // Clicked outside loop + else if (deltaLeft <= m_loopHandleWidth && deltaLeft < deltaRight) { return Action::MoveLoopBegin; } + else if (deltaRight <= m_loopHandleWidth) { return Action::MoveLoopEnd; } + else { return Action::MoveLoop; } } - if( event->button() == Qt::LeftButton && !(event->modifiers() & Qt::ShiftModifier) ) + else if (mode == "closest") { - m_action = Action::MovePositionMarker; - if( event->x() - m_xOffset < s_posMarkerPixmap->width() ) + const TimePos loopMid = (m_timeline->loopBegin() + m_timeline->loopEnd()) / 2; + return getClickedTime(xPos) < loopMid ? Action::MoveLoopBegin : Action::MoveLoopEnd; + } + else // Default to dual-button mode + { + if (button == Qt::LeftButton) { return Action::MoveLoopBegin; } + else if (button == Qt::RightButton) { return Action::MoveLoopEnd; } + return Action::NoAction; + } +} + +auto TimeLineWidget::actionCursor(Action action) const -> QCursor +{ + switch (action) { + case Action::MoveLoop: return Qt::SizeHorCursor; + case Action::MoveLoopBegin: return m_cursorSelectLeft; + case Action::MoveLoopEnd: return m_cursorSelectRight; + // Fall back to normal cursor if no action or action cursor not specified + default: return Qt::ArrowCursor; + } +} + +void TimeLineWidget::mousePressEvent(QMouseEvent* event) +{ + if (event->x() < m_xOffset) { return; } + + const auto shift = event->modifiers() & Qt::ShiftModifier; + const auto ctrl = event->modifiers() & Qt::ControlModifier; + + if (shift) // loop marker manipulation + { + m_action = getLoopAction(event); + setCursor(actionCursor(m_action)); + + if (m_action == Action::MoveLoop) { - m_moveXOff = event->x() - m_xOffset; - } - else - { - m_moveXOff = s_posMarkerPixmap->width() / 2; + m_dragStartPos = getClickedTime(event->x()); + m_oldLoopPos = {m_timeline->loopBegin(), m_timeline->loopEnd()}; } } - else if( event->button() == Qt::LeftButton && (event->modifiers() & Qt::ShiftModifier) ) + else if (event->button() == Qt::LeftButton && ctrl) // selection { m_action = Action::SelectSongClip; m_initalXSelect = event->x(); } - else if( event->button() == Qt::RightButton ) + else if (event->button() == Qt::LeftButton && !ctrl) // move playhead { - m_moveXOff = s_posMarkerPixmap->width() / 2; - const TimePos t = m_begin + static_cast( qMax( event->x() - m_xOffset - m_moveXOff, 0 ) * TimePos::ticksPerBar() / m_ppb ); - const TimePos loopMid = ( m_loopPos[0] + m_loopPos[1] ) / 2; - - m_action = t < loopMid ? Action::MoveLoopBegin : Action::MoveLoopEnd; - std::sort(std::begin(m_loopPos), std::end(m_loopPos)); - m_loopPos[( m_action == Action::MoveLoopBegin ) ? 0 : 1] = t; + m_action = Action::MovePositionMarker; } - if( m_action == Action::MoveLoopBegin || m_action == Action::MoveLoopEnd ) + if (m_action == Action::MoveLoopBegin || m_action == Action::MoveLoopEnd) { delete m_hint; - m_hint = TextFloat::displayMessage( tr( "Hint" ), - tr( "Press <%1> to disable magnetic loop points." ).arg(UI_CTRL_KEY), - embed::getIconPixmap( "hint" ), 0 ); + m_hint = TextFloat::displayMessage(tr("Hint"), + tr("Press <%1> to disable magnetic loop points.").arg(UI_CTRL_KEY), + embed::getIconPixmap("hint"), 0); } - mouseMoveEvent( event ); + + setContextMenuPolicy(m_action == Action::NoAction ? Qt::DefaultContextMenu : Qt::PreventContextMenu); + + mouseMoveEvent(event); } - - - void TimeLineWidget::mouseMoveEvent( QMouseEvent* event ) { parentWidget()->update(); // essential for widgets that this timeline had taken their mouse move event from. - const TimePos t = m_begin + static_cast( qMax( event->x() - m_xOffset - m_moveXOff, 0 ) * TimePos::ticksPerBar() / m_ppb ); + + auto timeAtCursor = getClickedTime(event->x()); + const auto control = event->modifiers() & Qt::ControlModifier; switch( m_action ) { case Action::MovePositionMarker: - m_pos.setTicks(t.getTicks()); - Engine::getSong()->setToTime(t, m_mode); + m_pos.setTicks(timeAtCursor.getTicks()); + Engine::getSong()->setToTime(timeAtCursor, m_mode); if (!( Engine::getSong()->isPlaying())) { //Song::PlayMode::None is used when nothing is being played. - Engine::getSong()->setToTime(t, Song::PlayMode::None); + Engine::getSong()->setToTime(timeAtCursor, Song::PlayMode::None); } m_pos.setCurrentFrame( 0 ); m_pos.setJumped( true ); updatePosition(); - positionMarkerMoved(); break; case Action::MoveLoopBegin: case Action::MoveLoopEnd: { - const int i = m_action == Action::MoveLoopBegin ? 0 : 1; - const bool control = event->modifiers() & Qt::ControlModifier; + const auto otherPoint = m_action == Action::MoveLoopBegin + ? m_timeline->loopEnd() + : m_timeline->loopBegin(); if (control) { // no ctrl-press-hint when having ctrl pressed delete m_hint; m_hint = nullptr; - m_loopPos[i] = t; } else { - m_loopPos[i] = t.quantize(m_snapSize); + timeAtCursor = timeAtCursor.quantize(m_snapSize); } // Catch begin == end - if (m_loopPos[0] == m_loopPos[1]) + if (timeAtCursor == otherPoint) { const int offset = control ? 1 : m_snapSize * TimePos::ticksPerBar(); - // Note, swap 1 and 0 below and the behavior "skips" the other - // marking instead of pushing it. - if (m_action == Action::MoveLoopBegin) { m_loopPos[0] -= offset; } - else { m_loopPos[1] += offset; } + if (m_action == Action::MoveLoopBegin) { timeAtCursor -= offset; } + else { timeAtCursor += offset; } } + // Update m_action so we still move the correct point even if it is + // dragged past the other. + m_action = timeAtCursor < otherPoint ? Action::MoveLoopBegin : Action::MoveLoopEnd; + m_timeline->setLoopPoints(timeAtCursor, otherPoint); update(); break; } - case Action::SelectSongClip: + case Action::MoveLoop: + { + const TimePos dragDelta = timeAtCursor - m_dragStartPos; + auto loopPos = m_oldLoopPos; + for (auto& point : loopPos) + { + point += dragDelta; + if (!control) { point = point.quantize(m_snapSize); } + } + m_timeline->setLoopPoints(loopPos[0], loopPos[1]); + break; + } + case Action::SelectSongClip: emit regionSelectedFromPixels( m_initalXSelect , event->x() ); - break; + break; default: break; } + + if (event->buttons() == Qt::NoButton) + { + setCursor(QGuiApplication::keyboardModifiers().testFlag(Qt::ShiftModifier) + ? actionCursor(getLoopAction(event)) + : Qt::ArrowCursor + ); + } } - - - void TimeLineWidget::mouseReleaseEvent( QMouseEvent* event ) { delete m_hint; @@ -435,5 +413,45 @@ void TimeLineWidget::mouseReleaseEvent( QMouseEvent* event ) m_action = Action::NoAction; } +void TimeLineWidget::contextMenuEvent(QContextMenuEvent* event) +{ + if (event->x() < m_xOffset) { return; } + + auto menu = QMenu{}; + + menu.addAction(tr("Set loop begin here"), [this, event] { + auto begin = getClickedTime(event->x()); + const auto end = m_timeline->loopEnd(); + if (!QGuiApplication::keyboardModifiers().testFlag(Qt::ControlModifier)) { begin = begin.quantize(m_snapSize); } + if (begin == end) { m_timeline->setLoopEnd(end + m_snapSize * TimePos::ticksPerBar()); } + m_timeline->setLoopBegin(begin); + update(); + }); + menu.addAction(tr("Set loop end here"), [this, event] { + const auto begin = m_timeline->loopBegin(); + auto end = getClickedTime(event->x()); + if (!QGuiApplication::keyboardModifiers().testFlag(Qt::ControlModifier)) { end = end.quantize(m_snapSize); } + if (begin == end) { m_timeline->setLoopBegin(begin - m_snapSize * TimePos::ticksPerBar()); } + m_timeline->setLoopEnd(end); + update(); + }); + + menu.addSeparator(); + + const auto loopMenu = menu.addMenu(tr("Loop edit mode (hold shift)")); + const auto loopMode = ConfigManager::inst()->value("app", "loopmarkermode", "dual"); + const auto addLoopModeAction = [loopMenu, &loopMode](QString text, QString mode) { + const auto action = loopMenu->addAction(text, [mode] { + ConfigManager::inst()->setValue("app", "loopmarkermode", mode); + }); + action->setCheckable(true); + if (loopMode == mode) { action->setChecked(true); } + }; + addLoopModeAction(tr("Dual-button"), "dual"); + addLoopModeAction(tr("Grab closest"), "closest"); + addLoopModeAction(tr("Handles"), "handles"); + + menu.exec(event->globalPos()); +} } // namespace lmms::gui diff --git a/src/gui/embed.cpp b/src/gui/embed.cpp index d934adcde..e912fd6d4 100644 --- a/src/gui/embed.cpp +++ b/src/gui/embed.cpp @@ -22,68 +22,62 @@ * */ +#include "embed.h" + #include +#include #include #include #include -#include "embed.h" -namespace lmms::embed +namespace lmms::embed { + +namespace { + +auto loadPixmap(const QString& name, int width, int height, const char* const* xpm) -> QPixmap { + if (xpm) { return QPixmap{xpm}; } -QPixmap getIconPixmap(const QString& pixmapName, - int width, int height, const char** xpm ) + const auto resourceName = QDir::isAbsolutePath(name) ? name : "artwork:" + name; + auto reader = QImageReader{resourceName}; + if (width > 0 && height > 0) { reader.setScaledSize(QSize{width, height}); } + + const auto pixmap = QPixmap::fromImageReader(&reader); + if (pixmap.isNull()) { + qWarning().nospace() << "Error loading icon pixmap " << name << ": " << reader.errorString(); + return QPixmap{1, 1}; + } + return pixmap; +} + +} // namespace + +auto getIconPixmap(std::string_view name, int width, int height, const char* const* xpm) -> QPixmap { - QString cacheName; - if (width > 0 && height > 0) - { - cacheName = QString("%1_%2_%3").arg(pixmapName, width, height); - } - else - { - cacheName = pixmapName; - } + if (name.empty()) { return QPixmap{}; } - // Return cached pixmap - QPixmap pixmap; - if( QPixmapCache::find(cacheName, &pixmap) ) - { - return pixmap; - } + const auto pixmapName = QString::fromUtf8(name.data(), name.size()); + const auto cacheName = (width > 0 && height > 0) + ? QStringLiteral("%1_%2_%3").arg(pixmapName, width, height) + : pixmapName; - if(xpm) - { - pixmap = QPixmap(xpm); - } - else - { - QImageReader reader(QString("artwork:%1").arg(pixmapName)); + // Return cached pixmap if it exists + if (auto pixmap = QPixmap{}; QPixmapCache::find(cacheName, &pixmap)) { return pixmap; } - if (width > 0 && height > 0) - { - reader.setScaledSize(QSize(width, height)); - } - - pixmap = QPixmap::fromImageReader(&reader); - - if (pixmap.isNull()) - { - qWarning().nospace() << "Error loading icon pixmap " << pixmapName << ": " << - reader.errorString().toLocal8Bit().data(); - return QPixmap(1,1); - } - } - - // Save to cache and return + // Load the pixmap and cache it before returning + const auto pixmap = loadPixmap(pixmapName, width, height, xpm); QPixmapCache::insert(cacheName, pixmap); return pixmap; } - -QString getText( const char * name ) +auto getText(std::string_view name) -> QString { - return QString::fromUtf8( (const char*) QResource(QString(":/%1").arg(name)).data()); + const auto resource = QResource{":/" + QString::fromUtf8(name.data(), name.size())}; +#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) + return QString::fromUtf8(resource.uncompressedData()); +#else + return QString::fromUtf8(reinterpret_cast(resource.data()), resource.size()); +#endif } - } // namespace lmms::embed diff --git a/src/gui/instrument/EnvelopeAndLfoView.cpp b/src/gui/instrument/EnvelopeAndLfoView.cpp index edb6c99c7..1b639e6c3 100644 --- a/src/gui/instrument/EnvelopeAndLfoView.cpp +++ b/src/gui/instrument/EnvelopeAndLfoView.cpp @@ -23,19 +23,20 @@ * */ -#include -#include - #include "EnvelopeAndLfoView.h" + +#include + +#include + +#include "EnvelopeGraph.h" +#include "LfoGraph.h" #include "EnvelopeAndLfoParameters.h" -#include "embed.h" -#include "Engine.h" +#include "SampleLoader.h" #include "gui_templates.h" #include "Knob.h" #include "LedCheckBox.h" -#include "AudioEngine.h" #include "DataFile.h" -#include "Oscillator.h" #include "PixmapButton.h" #include "StringPairDrag.h" #include "TempoSyncKnob.h" @@ -45,196 +46,148 @@ namespace lmms { -extern const float SECS_PER_ENV_SEGMENT; -extern const float SECS_PER_LFO_OSCILLATION; - - namespace gui { - -const int ENV_GRAPH_X = 6; -const int ENV_GRAPH_Y = 6; - -const int ENV_KNOBS_Y = 43; -const int ENV_KNOBS_LBL_Y = ENV_KNOBS_Y+35; -const int KNOB_X_SPACING = 32; -const int PREDELAY_KNOB_X = 6; -const int ATTACK_KNOB_X = PREDELAY_KNOB_X+KNOB_X_SPACING; -const int HOLD_KNOB_X = ATTACK_KNOB_X+KNOB_X_SPACING; -const int DECAY_KNOB_X = HOLD_KNOB_X+KNOB_X_SPACING; -const int SUSTAIN_KNOB_X = DECAY_KNOB_X+KNOB_X_SPACING; -const int RELEASE_KNOB_X = SUSTAIN_KNOB_X+KNOB_X_SPACING; -const int AMOUNT_KNOB_X = RELEASE_KNOB_X+KNOB_X_SPACING; - -const int TIME_UNIT_WIDTH = 40; - -const int LFO_GRAPH_X = 6; -const int LFO_GRAPH_Y = ENV_KNOBS_LBL_Y+14; -const int LFO_KNOB_Y = LFO_GRAPH_Y-2; -const int LFO_PREDELAY_KNOB_X = LFO_GRAPH_X + 100; -const int LFO_ATTACK_KNOB_X = LFO_PREDELAY_KNOB_X+KNOB_X_SPACING; -const int LFO_SPEED_KNOB_X = LFO_ATTACK_KNOB_X+KNOB_X_SPACING; -const int LFO_AMOUNT_KNOB_X = LFO_SPEED_KNOB_X+KNOB_X_SPACING; -const int LFO_SHAPES_X = LFO_GRAPH_X;//PREDELAY_KNOB_X; -const int LFO_SHAPES_Y = LFO_GRAPH_Y + 50; - - -QPixmap * EnvelopeAndLfoView::s_envGraph = nullptr; -QPixmap * EnvelopeAndLfoView::s_lfoGraph = nullptr; - - - -EnvelopeAndLfoView::EnvelopeAndLfoView( QWidget * _parent ) : - QWidget( _parent ), - ModelView( nullptr, this ), - m_params( nullptr ) +EnvelopeAndLfoView::EnvelopeAndLfoView(QWidget * parent) : + QWidget(parent), + ModelView(nullptr, this), + m_params(nullptr) { - if( s_envGraph == nullptr ) + // Helper lambdas for consistent repeated buiding of certain widgets + auto buildKnob = [&](const QString& label, const QString& hintText) { - s_envGraph = new QPixmap( embed::getIconPixmap( "envelope_graph" ) ); - } - if( s_lfoGraph == nullptr ) + auto knob = new Knob(KnobType::Bright26, this); + knob->setLabel(label); + knob->setHintText(hintText, ""); + + return knob; + }; + + auto buildPixmapButton = [&](std::string_view activePixmap, std::string_view inactivePixmap) { - s_lfoGraph = new QPixmap( embed::getIconPixmap( "lfo_graph" ) ); - } + auto button = new PixmapButton(this, nullptr); + button->setActiveGraphic(embed::getIconPixmap(activePixmap)); + button->setInactiveGraphic(embed::getIconPixmap(inactivePixmap)); - m_predelayKnob = new Knob( KnobType::Bright26, this ); - m_predelayKnob->setLabel( tr( "DEL" ) ); - m_predelayKnob->move( PREDELAY_KNOB_X, ENV_KNOBS_Y ); - m_predelayKnob->setHintText( tr( "Pre-delay:" ), "" ); + return button; + }; + + QVBoxLayout* mainLayout = new QVBoxLayout(this); + mainLayout->setContentsMargins(5, 5, 5, 5); + + // Envelope + + QVBoxLayout* envelopeLayout = new QVBoxLayout(); + mainLayout->addLayout(envelopeLayout); + + QHBoxLayout* graphAndAmountLayout = new QHBoxLayout(); + envelopeLayout->addLayout(graphAndAmountLayout); + + m_envelopeGraph = new EnvelopeGraph(this); + graphAndAmountLayout->addWidget(m_envelopeGraph); + + m_amountKnob = buildKnob(tr("AMT"), tr("Modulation amount:")); + graphAndAmountLayout->addWidget(m_amountKnob, 0, Qt::AlignCenter); + + QHBoxLayout* envKnobsLayout = new QHBoxLayout(); + envelopeLayout->addLayout(envKnobsLayout); + + m_predelayKnob = buildKnob(tr("DEL"), tr("Pre-delay:")); + envKnobsLayout->addWidget(m_predelayKnob); + + m_attackKnob = buildKnob(tr("ATT"), tr("Attack:")); + envKnobsLayout->addWidget(m_attackKnob); + + m_holdKnob = buildKnob(tr("HOLD"), tr("Hold:")); + envKnobsLayout->addWidget(m_holdKnob); + + m_decayKnob = buildKnob(tr("DEC"), tr("Decay:")); + envKnobsLayout->addWidget(m_decayKnob); + + m_sustainKnob = buildKnob(tr("SUST"), tr("Sustain:")); + envKnobsLayout->addWidget(m_sustainKnob); + + m_releaseKnob = buildKnob(tr("REL"), tr("Release:")); + envKnobsLayout->addWidget(m_releaseKnob); - m_attackKnob = new Knob( KnobType::Bright26, this ); - m_attackKnob->setLabel( tr( "ATT" ) ); - m_attackKnob->move( ATTACK_KNOB_X, ENV_KNOBS_Y ); - m_attackKnob->setHintText( tr( "Attack:" ), "" ); + // Add some space between the envelope and LFO section + mainLayout->addSpacing(10); - m_holdKnob = new Knob( KnobType::Bright26, this ); - m_holdKnob->setLabel( tr( "HOLD" ) ); - m_holdKnob->move( HOLD_KNOB_X, ENV_KNOBS_Y ); - m_holdKnob->setHintText( tr( "Hold:" ), "" ); + // LFO + QHBoxLayout* lfoLayout = new QHBoxLayout(); + mainLayout->addLayout(lfoLayout); - m_decayKnob = new Knob( KnobType::Bright26, this ); - m_decayKnob->setLabel( tr( "DEC" ) ); - m_decayKnob->move( DECAY_KNOB_X, ENV_KNOBS_Y ); - m_decayKnob->setHintText( tr( "Decay:" ), "" ); + QVBoxLayout* graphAndTypesLayout = new QVBoxLayout(); + lfoLayout->addLayout(graphAndTypesLayout); + m_lfoGraph = new LfoGraph(this); + graphAndTypesLayout->addWidget(m_lfoGraph); - m_sustainKnob = new Knob( KnobType::Bright26, this ); - m_sustainKnob->setLabel( tr( "SUST" ) ); - m_sustainKnob->move( SUSTAIN_KNOB_X, ENV_KNOBS_Y ); - m_sustainKnob->setHintText( tr( "Sustain:" ), "" ); + QHBoxLayout* typesLayout = new QHBoxLayout(); + graphAndTypesLayout->addLayout(typesLayout); + typesLayout->setContentsMargins(0, 0, 0, 0); + typesLayout->setSpacing(0); + auto sin_lfo_btn = buildPixmapButton("sin_wave_active", "sin_wave_inactive"); + auto triangle_lfo_btn = buildPixmapButton("triangle_wave_active", "triangle_wave_inactive"); + auto saw_lfo_btn = buildPixmapButton("saw_wave_active", "saw_wave_inactive"); + auto sqr_lfo_btn = buildPixmapButton("square_wave_active","square_wave_inactive"); + auto random_lfo_btn = buildPixmapButton("random_wave_active", "random_wave_inactive"); + m_userLfoBtn = buildPixmapButton("usr_wave_active", "usr_wave_inactive"); - m_releaseKnob = new Knob( KnobType::Bright26, this ); - m_releaseKnob->setLabel( tr( "REL" ) ); - m_releaseKnob->move( RELEASE_KNOB_X, ENV_KNOBS_Y ); - m_releaseKnob->setHintText( tr( "Release:" ), "" ); + connect(m_userLfoBtn, SIGNAL(toggled(bool)), this, SLOT(lfoUserWaveChanged())); + typesLayout->addWidget(sin_lfo_btn); + typesLayout->addWidget(triangle_lfo_btn); + typesLayout->addWidget(saw_lfo_btn); + typesLayout->addWidget(sqr_lfo_btn); + typesLayout->addWidget(random_lfo_btn); + typesLayout->addWidget(m_userLfoBtn); - m_amountKnob = new Knob( KnobType::Bright26, this ); - m_amountKnob->setLabel( tr( "AMT" ) ); - m_amountKnob->move( AMOUNT_KNOB_X, ENV_GRAPH_Y ); - m_amountKnob->setHintText( tr( "Modulation amount:" ), "" ); + m_lfoWaveBtnGrp = new automatableButtonGroup(this); + m_lfoWaveBtnGrp->addButton(sin_lfo_btn); + m_lfoWaveBtnGrp->addButton(triangle_lfo_btn); + m_lfoWaveBtnGrp->addButton(saw_lfo_btn); + m_lfoWaveBtnGrp->addButton(sqr_lfo_btn); + m_lfoWaveBtnGrp->addButton(m_userLfoBtn); + m_lfoWaveBtnGrp->addButton(random_lfo_btn); + QVBoxLayout* knobsAndCheckBoxesLayout = new QVBoxLayout(); + lfoLayout->addLayout(knobsAndCheckBoxesLayout); + QHBoxLayout* lfoKnobsLayout = new QHBoxLayout(); + knobsAndCheckBoxesLayout->addLayout(lfoKnobsLayout); + m_lfoPredelayKnob = buildKnob(tr("DEL"), tr("Pre-delay:")); + lfoKnobsLayout->addWidget(m_lfoPredelayKnob); - m_lfoPredelayKnob = new Knob( KnobType::Bright26, this ); - m_lfoPredelayKnob->setLabel( tr( "DEL" ) ); - m_lfoPredelayKnob->move( LFO_PREDELAY_KNOB_X, LFO_KNOB_Y ); - m_lfoPredelayKnob->setHintText( tr( "Pre-delay:" ), "" ); + m_lfoAttackKnob = buildKnob(tr("ATT"), tr("Attack:")); + lfoKnobsLayout->addWidget(m_lfoAttackKnob); + m_lfoSpeedKnob = new TempoSyncKnob(KnobType::Bright26, this); + m_lfoSpeedKnob->setLabel(tr("SPD")); + m_lfoSpeedKnob->setHintText(tr("Frequency:"), ""); + lfoKnobsLayout->addWidget(m_lfoSpeedKnob); - m_lfoAttackKnob = new Knob( KnobType::Bright26, this ); - m_lfoAttackKnob->setLabel( tr( "ATT" ) ); - m_lfoAttackKnob->move( LFO_ATTACK_KNOB_X, LFO_KNOB_Y ); - m_lfoAttackKnob->setHintText( tr( "Attack:" ), "" ); + m_lfoAmountKnob = buildKnob(tr("AMT"), tr("Modulation amount:")); + lfoKnobsLayout->addWidget(m_lfoAmountKnob); + QVBoxLayout* checkBoxesLayout = new QVBoxLayout(); + knobsAndCheckBoxesLayout->addLayout(checkBoxesLayout); - m_lfoSpeedKnob = new TempoSyncKnob( KnobType::Bright26, this ); - m_lfoSpeedKnob->setLabel( tr( "SPD" ) ); - m_lfoSpeedKnob->move( LFO_SPEED_KNOB_X, LFO_KNOB_Y ); - m_lfoSpeedKnob->setHintText( tr( "Frequency:" ), "" ); - - - m_lfoAmountKnob = new Knob( KnobType::Bright26, this ); - m_lfoAmountKnob->setLabel( tr( "AMT" ) ); - m_lfoAmountKnob->move( LFO_AMOUNT_KNOB_X, LFO_KNOB_Y ); - m_lfoAmountKnob->setHintText( tr( "Modulation amount:" ), "" ); - - auto sin_lfo_btn = new PixmapButton(this, nullptr); - sin_lfo_btn->move( LFO_SHAPES_X, LFO_SHAPES_Y ); - sin_lfo_btn->setActiveGraphic( embed::getIconPixmap( - "sin_wave_active" ) ); - sin_lfo_btn->setInactiveGraphic( embed::getIconPixmap( - "sin_wave_inactive" ) ); - - auto triangle_lfo_btn = new PixmapButton(this, nullptr); - triangle_lfo_btn->move( LFO_SHAPES_X+15, LFO_SHAPES_Y ); - triangle_lfo_btn->setActiveGraphic( embed::getIconPixmap( - "triangle_wave_active" ) ); - triangle_lfo_btn->setInactiveGraphic( embed::getIconPixmap( - "triangle_wave_inactive" ) ); - - auto saw_lfo_btn = new PixmapButton(this, nullptr); - saw_lfo_btn->move( LFO_SHAPES_X+30, LFO_SHAPES_Y ); - saw_lfo_btn->setActiveGraphic( embed::getIconPixmap( - "saw_wave_active" ) ); - saw_lfo_btn->setInactiveGraphic( embed::getIconPixmap( - "saw_wave_inactive" ) ); - - auto sqr_lfo_btn = new PixmapButton(this, nullptr); - sqr_lfo_btn->move( LFO_SHAPES_X+45, LFO_SHAPES_Y ); - sqr_lfo_btn->setActiveGraphic( embed::getIconPixmap( - "square_wave_active" ) ); - sqr_lfo_btn->setInactiveGraphic( embed::getIconPixmap( - "square_wave_inactive" ) ); - - m_userLfoBtn = new PixmapButton( this, nullptr ); - m_userLfoBtn->move( LFO_SHAPES_X+75, LFO_SHAPES_Y ); - m_userLfoBtn->setActiveGraphic( embed::getIconPixmap( - "usr_wave_active" ) ); - m_userLfoBtn->setInactiveGraphic( embed::getIconPixmap( - "usr_wave_inactive" ) ); - - connect( m_userLfoBtn, SIGNAL(toggled(bool)), - this, SLOT(lfoUserWaveChanged())); - - auto random_lfo_btn = new PixmapButton(this, nullptr); - random_lfo_btn->move( LFO_SHAPES_X+60, LFO_SHAPES_Y ); - random_lfo_btn->setActiveGraphic( embed::getIconPixmap( - "random_wave_active" ) ); - random_lfo_btn->setInactiveGraphic( embed::getIconPixmap( - "random_wave_inactive" ) ); - - m_lfoWaveBtnGrp = new automatableButtonGroup( this ); - m_lfoWaveBtnGrp->addButton( sin_lfo_btn ); - m_lfoWaveBtnGrp->addButton( triangle_lfo_btn ); - m_lfoWaveBtnGrp->addButton( saw_lfo_btn ); - m_lfoWaveBtnGrp->addButton( sqr_lfo_btn ); - m_lfoWaveBtnGrp->addButton( m_userLfoBtn ); - m_lfoWaveBtnGrp->addButton( random_lfo_btn ); - - m_x100Cb = new LedCheckBox( tr( "FREQ x 100" ), this ); - m_x100Cb->setFont( pointSizeF( m_x100Cb->font(), 6.5 ) ); - m_x100Cb->move( LFO_PREDELAY_KNOB_X, LFO_GRAPH_Y + 36 ); + m_x100Cb = new LedCheckBox(tr("FREQ x 100"), this); m_x100Cb->setToolTip(tr("Multiply LFO frequency by 100")); + checkBoxesLayout->addWidget(m_x100Cb); + m_controlEnvAmountCb = new LedCheckBox(tr("MOD ENV AMOUNT"), this); + m_controlEnvAmountCb->setToolTip(tr("Control envelope amount by this LFO")); + checkBoxesLayout->addWidget(m_controlEnvAmountCb); - m_controlEnvAmountCb = new LedCheckBox( tr( "MODULATE ENV AMOUNT" ), - this ); - m_controlEnvAmountCb->move( LFO_PREDELAY_KNOB_X, LFO_GRAPH_Y + 54 ); - m_controlEnvAmountCb->setFont( pointSizeF( m_controlEnvAmountCb->font(), 6.5 ) ); - m_controlEnvAmountCb->setToolTip( - tr( "Control envelope amount by this LFO" ) ); - - - setAcceptDrops( true ); - + setAcceptDrops(true); } @@ -251,6 +204,7 @@ EnvelopeAndLfoView::~EnvelopeAndLfoView() void EnvelopeAndLfoView::modelChanged() { m_params = castModel(); + m_envelopeGraph->setModel(m_params); m_predelayKnob->setModel( &m_params->m_predelayModel ); m_attackKnob->setModel( &m_params->m_attackModel ); m_holdKnob->setModel( &m_params->m_holdModel ); @@ -258,6 +212,8 @@ void EnvelopeAndLfoView::modelChanged() m_sustainKnob->setModel( &m_params->m_sustainModel ); m_releaseKnob->setModel( &m_params->m_releaseModel ); m_amountKnob->setModel( &m_params->m_amountModel ); + + m_lfoGraph->setModel(m_params); m_lfoPredelayKnob->setModel( &m_params->m_lfoPredelayModel ); m_lfoAttackKnob->setModel( &m_params->m_lfoAttackModel ); m_lfoSpeedKnob->setModel( &m_params->m_lfoSpeedModel ); @@ -270,42 +226,6 @@ void EnvelopeAndLfoView::modelChanged() -void EnvelopeAndLfoView::mousePressEvent( QMouseEvent * _me ) -{ - if( _me->button() != Qt::LeftButton ) - { - return; - } - - if( QRect( ENV_GRAPH_X, ENV_GRAPH_Y, s_envGraph->width(), - s_envGraph->height() ).contains( _me->pos() ) == true ) - { - if( m_params->m_amountModel.value() < 1.0f ) - { - m_params->m_amountModel.setValue( 1.0f ); - } - else - { - m_params->m_amountModel.setValue( 0.0f ); - } - } - else if( QRect( LFO_GRAPH_X, LFO_GRAPH_Y, s_lfoGraph->width(), - s_lfoGraph->height() ).contains( _me->pos() ) == true ) - { - if( m_params->m_lfoAmountModel.value() < 1.0f ) - { - m_params->m_lfoAmountModel.setValue( 1.0f ); - } - else - { - m_params->m_lfoAmountModel.setValue( 0.0f ); - } - } -} - - - - void EnvelopeAndLfoView::dragEnterEvent( QDragEnterEvent * _dee ) { StringPairDrag::processDragEnterEvent( _dee, @@ -322,8 +242,7 @@ void EnvelopeAndLfoView::dropEvent( QDropEvent * _de ) QString value = StringPairDrag::decodeValue( _de ); if( type == "samplefile" ) { - m_params->m_userWave.setAudioFile( - StringPairDrag::decodeValue( _de ) ); + m_params->m_userWave = SampleLoader::createBufferFromFile(value); m_userLfoBtn->model()->setValue( true ); m_params->m_lfoWaveModel.setValue(static_cast(EnvelopeAndLfoParameters::LfoShape::UserDefinedWave)); _de->accept(); @@ -332,9 +251,10 @@ void EnvelopeAndLfoView::dropEvent( QDropEvent * _de ) else if( type == QString( "clip_%1" ).arg( static_cast(Track::Type::Sample) ) ) { DataFile dataFile( value.toUtf8() ); - m_params->m_userWave.setAudioFile( dataFile.content(). + auto file = dataFile.content(). firstChildElement().firstChildElement(). - firstChildElement().attribute( "src" ) ); + firstChildElement().attribute("src"); + m_params->m_userWave = SampleLoader::createBufferFromFile(file); m_userLfoBtn->model()->setValue( true ); m_params->m_lfoWaveModel.setValue(static_cast(EnvelopeAndLfoParameters::LfoShape::UserDefinedWave)); _de->accept(); @@ -345,182 +265,12 @@ void EnvelopeAndLfoView::dropEvent( QDropEvent * _de ) -void EnvelopeAndLfoView::paintEvent( QPaintEvent * ) -{ - QPainter p( this ); - p.setRenderHint( QPainter::Antialiasing ); - - // draw envelope-graph - p.drawPixmap( ENV_GRAPH_X, ENV_GRAPH_Y, *s_envGraph ); - // draw LFO-graph - p.drawPixmap( LFO_GRAPH_X, LFO_GRAPH_Y, *s_lfoGraph ); - - - p.setFont( pointSize<8>( p.font() ) ); - - const float gray_amount = 1.0f - fabsf( m_amountKnob->value() ); - - p.setPen( QPen( QColor( static_cast( 96 * gray_amount ), - static_cast( 255 - 159 * gray_amount ), - static_cast( 128 - 32 * gray_amount ) ), - 2 ) ); - - const QColor end_points_color( 0x99, 0xAF, 0xFF ); - const QColor end_points_bg_color( 0, 0, 2 ); - - const int y_base = ENV_GRAPH_Y + s_envGraph->height() - 3; - const int avail_height = s_envGraph->height() - 6; - - int x1 = static_cast( m_predelayKnob->value() * TIME_UNIT_WIDTH ); - int x2 = x1 + static_cast( m_attackKnob->value() * TIME_UNIT_WIDTH ); - int x3 = x2 + static_cast( m_holdKnob->value() * TIME_UNIT_WIDTH ); - int x4 = x3 + static_cast( ( m_decayKnob->value() * - ( 1 - m_sustainKnob->value() ) ) * TIME_UNIT_WIDTH ); - int x5 = x4 + static_cast( m_releaseKnob->value() * TIME_UNIT_WIDTH ); - - if( x5 > 174 ) - { - x1 = ( x1 * 174 ) / x5; - x2 = ( x2 * 174 ) / x5; - x3 = ( x3 * 174 ) / x5; - x4 = ( x4 * 174 ) / x5; - x5 = ( x5 * 174 ) / x5; - } - x1 += ENV_GRAPH_X + 2; - x2 += ENV_GRAPH_X + 2; - x3 += ENV_GRAPH_X + 2; - x4 += ENV_GRAPH_X + 2; - x5 += ENV_GRAPH_X + 2; - - p.drawLine( x1, y_base, x2, y_base - avail_height ); - p.fillRect( x1 - 1, y_base - 2, 4, 4, end_points_bg_color ); - p.fillRect( x1, y_base - 1, 2, 2, end_points_color ); - - p.drawLine( x2, y_base - avail_height, x3, y_base - avail_height ); - p.fillRect( x2 - 1, y_base - 2 - avail_height, 4, 4, - end_points_bg_color ); - p.fillRect( x2, y_base - 1 - avail_height, 2, 2, end_points_color ); - - p.drawLine( x3, y_base-avail_height, x4, static_cast( y_base - - avail_height + - ( 1 - m_sustainKnob->value() ) * avail_height ) ); - p.fillRect( x3 - 1, y_base - 2 - avail_height, 4, 4, - end_points_bg_color ); - p.fillRect( x3, y_base - 1 - avail_height, 2, 2, end_points_color ); - - p.drawLine( x4, static_cast( y_base - avail_height + - ( 1 - m_sustainKnob->value() ) * - avail_height ), x5, y_base ); - p.fillRect( x4 - 1, static_cast( y_base - avail_height + - ( 1 - m_sustainKnob->value() ) * - avail_height ) - 2, 4, 4, - end_points_bg_color ); - p.fillRect( x4, static_cast( y_base - avail_height + - ( 1 - m_sustainKnob->value() ) * - avail_height ) - 1, 2, 2, - end_points_color ); - p.fillRect( x5 - 1, y_base - 2, 4, 4, end_points_bg_color ); - p.fillRect( x5, y_base - 1, 2, 2, end_points_color ); - - - int LFO_GRAPH_W = s_lfoGraph->width() - 3; // subtract border - int LFO_GRAPH_H = s_lfoGraph->height() - 6; // subtract border - int graph_x_base = LFO_GRAPH_X + 2; - int graph_y_base = LFO_GRAPH_Y + 3 + LFO_GRAPH_H / 2; - - const float frames_for_graph = SECS_PER_LFO_OSCILLATION * - Engine::audioEngine()->baseSampleRate() / 10; - - const float lfo_gray_amount = 1.0f - fabsf( m_lfoAmountKnob->value() ); - p.setPen( QPen( QColor( static_cast( 96 * lfo_gray_amount ), - static_cast( 255 - 159 * lfo_gray_amount ), - static_cast( 128 - 32 * - lfo_gray_amount ) ), - 1.5 ) ); - - - float osc_frames = m_params->m_lfoOscillationFrames; - - if( m_params->m_x100Model.value() ) - { - osc_frames *= 100.0f; - } - - // userWaveSample() may be used, called out of loop for efficiency - m_params->m_userWave.dataReadLock(); - float old_y = 0; - for( int x = 0; x <= LFO_GRAPH_W; ++x ) - { - float val = 0.0; - float cur_sample = x * frames_for_graph / LFO_GRAPH_W; - if( static_cast( cur_sample ) > - m_params->m_lfoPredelayFrames ) - { - float phase = ( cur_sample -= - m_params->m_lfoPredelayFrames ) / - osc_frames; - switch( static_cast(m_params->m_lfoWaveModel.value()) ) - { - case EnvelopeAndLfoParameters::LfoShape::SineWave: - default: - val = Oscillator::sinSample( phase ); - break; - case EnvelopeAndLfoParameters::LfoShape::TriangleWave: - val = Oscillator::triangleSample( - phase ); - break; - case EnvelopeAndLfoParameters::LfoShape::SawWave: - val = Oscillator::sawSample( phase ); - break; - case EnvelopeAndLfoParameters::LfoShape::SquareWave: - val = Oscillator::squareSample( phase ); - break; - case EnvelopeAndLfoParameters::LfoShape::RandomWave: - if( x % (int)( 900 * m_lfoSpeedKnob->value() + 1 ) == 0 ) - { - m_randomGraph = Oscillator::noiseSample( 0.0f ); - } - val = m_randomGraph; - break; - case EnvelopeAndLfoParameters::LfoShape::UserDefinedWave: - val = m_params->m_userWave. - userWaveSample( phase ); - break; - } - if( static_cast( cur_sample ) <= - m_params->m_lfoAttackFrames ) - { - val *= cur_sample / m_params->m_lfoAttackFrames; - } - } - float cur_y = -LFO_GRAPH_H / 2.0f * val; - p.drawLine( QLineF( graph_x_base + x - 1, graph_y_base + old_y, - graph_x_base + x, - graph_y_base + cur_y ) ); - old_y = cur_y; - } - m_params->m_userWave.dataUnlock(); - - p.setPen( QColor( 201, 201, 225 ) ); - int ms_per_osc = static_cast( SECS_PER_LFO_OSCILLATION * - m_lfoSpeedKnob->value() * - 1000.0f ); - p.drawText( LFO_GRAPH_X + 4, LFO_GRAPH_Y + s_lfoGraph->height() - 6, - tr( "ms/LFO:" ) ); - p.drawText( LFO_GRAPH_X + 52, LFO_GRAPH_Y + s_lfoGraph->height() - 6, - QString::number( ms_per_osc ) ); - -} - - - - void EnvelopeAndLfoView::lfoUserWaveChanged() { if( static_cast(m_params->m_lfoWaveModel.value()) == EnvelopeAndLfoParameters::LfoShape::UserDefinedWave ) { - if( m_params->m_userWave.frames() <= 1 ) + if (m_params->m_userWave->size() <= 1) { TextFloat::displayMessage( tr( "Hint" ), tr( "Drag and drop a sample into this window." ), @@ -529,11 +279,6 @@ void EnvelopeAndLfoView::lfoUserWaveChanged() } } - - - - - } // namespace gui -} // namespace lmms \ No newline at end of file +} // namespace lmms diff --git a/src/gui/instrument/EnvelopeGraph.cpp b/src/gui/instrument/EnvelopeGraph.cpp new file mode 100644 index 000000000..4cf5da74b --- /dev/null +++ b/src/gui/instrument/EnvelopeGraph.cpp @@ -0,0 +1,261 @@ +/* + * EnvelopeGraph.cpp - Displays envelope graphs + * + * Copyright (c) 2004-2014 Tobias Doerffel + * Copyright (c) 2024- 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 "EnvelopeGraph.h" + +#include +#include +#include + +#include "EnvelopeAndLfoParameters.h" +#include "lmms_math.h" +#include "ColorHelper.h" + +#include + + +namespace lmms +{ + +namespace gui +{ + +EnvelopeGraph::EnvelopeGraph(QWidget* parent) : + QWidget(parent), + ModelView(nullptr, this) +{ + setMinimumSize(m_envGraph.size()); +} + +void EnvelopeGraph::modelChanged() +{ + m_params = castModel(); +} + +void EnvelopeGraph::mousePressEvent(QMouseEvent* me) +{ + if (me->button() == Qt::LeftButton) { toggleAmountModel(); } +} + +void EnvelopeGraph::contextMenuEvent(QContextMenuEvent* event) +{ + QMenu menu(this); + QMenu* scalingMenu = menu.addMenu(tr("Scaling")); + scalingMenu->setToolTipsVisible(true); + + auto switchTo = [&](ScalingMode scaling) + { + if (m_scaling != scaling) + { + m_scaling = scaling; + update(); + } + }; + + auto addScalingEntry = [scalingMenu, &switchTo, this](const QString& text, const QString& toolTip, ScalingMode scaling) + { + QAction* action = scalingMenu->addAction(text, [&switchTo, scaling]() { switchTo(scaling); }); + action->setCheckable(true); + action->setChecked(m_scaling == scaling); + action->setToolTip(toolTip); + }; + + addScalingEntry( + tr("Dynamic"), + tr("Uses absolute spacings but switches to relative spacing if it's running out of space"), + ScalingMode::Dynamic); + addScalingEntry( + tr("Absolute"), + tr("Provides enough potential space for each segment but does not scale"), + ScalingMode::Absolute); + addScalingEntry( + tr("Relative"), + tr("Always uses all of the available space to display the envelope graph"), + ScalingMode::Relative); + + menu.exec(event->globalPos()); +} + +void EnvelopeGraph::paintEvent(QPaintEvent*) +{ + QPainter p{this}; + p.setRenderHint(QPainter::Antialiasing); + + // Draw the graph background + p.drawPixmap(rect(), m_envGraph); + + const auto* params = castModel(); + if (!params) { return; } + + // For the calculation of the percentages we will for now make use of the knowledge + // that the range goes from 0 to a positive max value, i.e. that it is in [0, max]. + const float amount = params->getAmountModel().value(); + + const float predelay = params->getPredelayModel().value(); + const float predelayPercentage = predelay / params->getPredelayModel().maxValue(); + + const float attack = params->getAttackModel().value(); + const float attackPercentage = attack / params->getAttackModel().maxValue(); + + const float hold = params->getHoldModel().value(); + const float holdPercentage = hold / params->getHoldModel().maxValue(); + + const float decay = params->getDecayModel().value(); + const float decayPercentage = decay / params->getDecayModel().maxValue(); + + const float sustain = params->getSustainModel().value(); + + const float release = params->getReleaseModel().value(); + const float releasePercentage = release / params->getReleaseModel().maxValue(); + + // The margin to the left and right so that we do not clip too much of the lines and markers + const float margin = 2.0; + const float availableWidth = width() - margin * 2; + + // Now determine the maximum width for one segment according to the scaling setting. + // The different scalings use different means to compute the maximum available width per segment. + const auto computeMaximumSegmentWidthAbsolute = [&]() -> float + { + return availableWidth / 5.; + }; + + const auto computeMaximumSegmentWidthRelative = [&]() -> float + { + const float sumOfSegments = predelayPercentage + attackPercentage + holdPercentage + decayPercentage + releasePercentage; + + return sumOfSegments != 0. + ? availableWidth / sumOfSegments + : computeMaximumSegmentWidthAbsolute(); + }; + + const auto computeMaximumSegmentWidthDynamic = [&]() -> float + { + const float sumOfSegments = predelayPercentage + attackPercentage + holdPercentage + decayPercentage + releasePercentage; + + float preliminarySegmentWidth = 80. / 182. * availableWidth; + + const float neededWidth = sumOfSegments * preliminarySegmentWidth; + + if (neededWidth > availableWidth && sumOfSegments != 0.) + { + return computeMaximumSegmentWidthRelative(); + } + + return preliminarySegmentWidth; + }; + + // This is the maximum width that each of the five segments (DAHDR) can occupy. + float maximumSegmentWidth; + + switch (m_scaling) + { + case ScalingMode::Absolute: + maximumSegmentWidth = computeMaximumSegmentWidthAbsolute(); + break; + case ScalingMode::Relative: + maximumSegmentWidth = computeMaximumSegmentWidthRelative(); + break; + case ScalingMode::Dynamic: + default: + maximumSegmentWidth = computeMaximumSegmentWidthDynamic(); + break; + } + + // Compute the actual widths that the segments occupy and add them to the + // previous x coordinates starting at the margin. + const float predelayX = margin + predelayPercentage * maximumSegmentWidth; + const float attackX = predelayX + attackPercentage * maximumSegmentWidth; + const float holdX = attackX + holdPercentage * maximumSegmentWidth; + const float decayX = holdX + (decayPercentage * (1 - sustain)) * maximumSegmentWidth; + const float releaseX = decayX + releasePercentage * maximumSegmentWidth; + + // Now compute the "full" points including y coordinates + const int yTop = 3; + const qreal yBase = height() - 3; + const int availableHeight = yBase - yTop; + + const QPointF predelayPoint{predelayX, yBase}; + const QPointF attackPoint{attackX, yTop}; + const QPointF holdPoint{holdX, yTop}; + const QPointF decayPoint{decayX, yTop + (1 - sustain) * availableHeight}; + const QPointF releasePoint{releaseX, yBase}; + + // Now that we have all points we can draw the lines + + // Compute the color of the lines based on the amount of the envelope + const float absAmount = std::abs(amount); + const QColor noAmountColor{96, 91, 96}; + const QColor fullAmountColor{0, 255, 128}; + const QColor lineColor{ColorHelper::interpolateInRgb(noAmountColor, fullAmountColor, absAmount)}; + + // Determine the line width so that it scales with the widget + // Use the minimum value of the current width and height to compute it. + const qreal lineWidth = std::min(width(), height()) / 20.; + const QPen linePen{lineColor, lineWidth}; + p.setPen(linePen); + + QPolygonF linePoly; + linePoly << predelayPoint << attackPoint << holdPoint << decayPoint << releasePoint; + p.drawPolyline(linePoly); + + // Now draw all marker on top of the lines + const QColor markerFillColor{153, 175, 255}; + const QColor markerOutlineColor{0, 0, 0}; + + QPen pen; + pen.setWidthF(lineWidth * 0.75); + pen.setBrush(markerOutlineColor); + p.setPen(pen); + p.setBrush(markerFillColor); + + // Compute the size of the circle we will draw based on the line width + const qreal baseRectSize = lineWidth * 3; + const QSizeF rectSize{baseRectSize, baseRectSize}; + + auto drawMarker = [&](const QPointF& point) + { + // Create a rectangle that has the given point at its center + QRectF bgRect{point + QPointF(-baseRectSize / 2, -baseRectSize / 2), rectSize}; + p.drawEllipse(bgRect); + }; + + drawMarker(predelayPoint); + drawMarker(attackPoint); + drawMarker(holdPoint); + drawMarker(decayPoint); + drawMarker(releasePoint); +} + +void EnvelopeGraph::toggleAmountModel() +{ + auto* params = castModel(); + auto& amountModel = params->getAmountModel(); + + amountModel.setValue(amountModel.value() < 1.0 ? 1.0 : 0.0); +} + +} // namespace gui + +} // namespace lmms diff --git a/src/gui/instrument/InstrumentFunctionViews.cpp b/src/gui/instrument/InstrumentFunctionViews.cpp index c9aa04272..ad8abe735 100644 --- a/src/gui/instrument/InstrumentFunctionViews.cpp +++ b/src/gui/instrument/InstrumentFunctionViews.cpp @@ -57,7 +57,7 @@ InstrumentFunctionNoteStackingView::InstrumentFunctionNoteStackingView( Instrume mainLayout->setVerticalSpacing( 1 ); auto chordLabel = new QLabel(tr("Chord:")); - chordLabel->setFont( pointSize<8>( chordLabel->font() ) ); + chordLabel->setFont(adjustedToPixelSize(chordLabel->font(), 10)); m_chordRangeKnob->setLabel( tr( "RANGE" ) ); m_chordRangeKnob->setHintText( tr( "Chord range:" ), " " + tr( "octave(s)" ) ); @@ -145,14 +145,15 @@ InstrumentFunctionArpeggioView::InstrumentFunctionArpeggioView( InstrumentFuncti m_arpGateKnob->setLabel( tr( "GATE" ) ); m_arpGateKnob->setHintText( tr( "Arpeggio gate:" ), tr( "%" ) ); + constexpr int labelFontSize = 10; auto arpChordLabel = new QLabel(tr("Chord:")); - arpChordLabel->setFont( pointSize<8>( arpChordLabel->font() ) ); + arpChordLabel->setFont(adjustedToPixelSize(arpChordLabel->font(), labelFontSize)); auto arpDirectionLabel = new QLabel(tr("Direction:")); - arpDirectionLabel->setFont( pointSize<8>( arpDirectionLabel->font() ) ); + arpDirectionLabel->setFont(adjustedToPixelSize(arpDirectionLabel->font(), labelFontSize)); auto arpModeLabel = new QLabel(tr("Mode:")); - arpModeLabel->setFont( pointSize<8>( arpModeLabel->font() ) ); + arpModeLabel->setFont(adjustedToPixelSize(arpModeLabel->font(), labelFontSize)); mainLayout->addWidget( arpChordLabel, 0, 0 ); mainLayout->addWidget( m_arpComboBox, 1, 0 ); diff --git a/src/gui/instrument/InstrumentMidiIOView.cpp b/src/gui/instrument/InstrumentMidiIOView.cpp index fd9d6fc54..e3f10bd1a 100644 --- a/src/gui/instrument/InstrumentMidiIOView.cpp +++ b/src/gui/instrument/InstrumentMidiIOView.cpp @@ -144,28 +144,19 @@ InstrumentMidiIOView::InstrumentMidiIOView( QWidget* parent ) : midiOutputLayout->insertWidget( 0, m_wpBtn ); } - auto baseVelocityGroupBox = new GroupBox(tr("CUSTOM BASE VELOCITY")); + auto baseVelocityGroupBox = new GroupBox(tr("VELOCITY MAPPING")); + baseVelocityGroupBox->setLedButtonShown(false); layout->addWidget( baseVelocityGroupBox ); auto baseVelocityLayout = new QVBoxLayout(baseVelocityGroupBox); baseVelocityLayout->setContentsMargins( 8, 18, 8, 8 ); baseVelocityLayout->setSpacing( 6 ); - auto baseVelocityHelp - = new QLabel(tr("Specify the velocity normalization base for MIDI-based instruments at 100% note velocity.")); - baseVelocityHelp->setWordWrap( true ); - baseVelocityHelp->setFont( pointSize<8>( baseVelocityHelp->font() ) ); - - baseVelocityLayout->addWidget( baseVelocityHelp ); - m_baseVelocitySpinBox = new LcdSpinBox( 3, baseVelocityGroupBox ); - m_baseVelocitySpinBox->setLabel( tr( "BASE VELOCITY" ) ); - m_baseVelocitySpinBox->setEnabled( false ); + m_baseVelocitySpinBox->setLabel(tr("MIDI VELOCITY")); + m_baseVelocitySpinBox->setToolTip(tr("MIDI notes at this velocity correspond to 100% note velocity.")); baseVelocityLayout->addWidget( m_baseVelocitySpinBox ); - connect( baseVelocityGroupBox->ledButton(), SIGNAL(toggled(bool)), - m_baseVelocitySpinBox, SLOT(setEnabled(bool))); - layout->addStretch(); } diff --git a/src/gui/instrument/InstrumentSoundShapingView.cpp b/src/gui/instrument/InstrumentSoundShapingView.cpp index 1bfc166b3..a3a78e256 100644 --- a/src/gui/instrument/InstrumentSoundShapingView.cpp +++ b/src/gui/instrument/InstrumentSoundShapingView.cpp @@ -22,9 +22,11 @@ * */ -#include - #include "InstrumentSoundShapingView.h" + +#include +#include + #include "EnvelopeAndLfoParameters.h" #include "EnvelopeAndLfoView.h" #include "ComboBox.h" @@ -37,69 +39,54 @@ namespace lmms::gui { -const int TARGETS_TABWIDGET_X = 4; -const int TARGETS_TABWIDGET_Y = 5; -const int TARGETS_TABWIDGET_WIDTH = 242; -const int TARGETS_TABWIDGET_HEIGTH = 175; - -const int FILTER_GROUPBOX_X = TARGETS_TABWIDGET_X; -const int FILTER_GROUPBOX_Y = TARGETS_TABWIDGET_Y+TARGETS_TABWIDGET_HEIGTH+5; -const int FILTER_GROUPBOX_WIDTH = TARGETS_TABWIDGET_WIDTH; -const int FILTER_GROUPBOX_HEIGHT = 245-FILTER_GROUPBOX_Y; - - - -InstrumentSoundShapingView::InstrumentSoundShapingView( QWidget * _parent ) : - QWidget( _parent ), - ModelView( nullptr, this ), - m_ss( nullptr ) +InstrumentSoundShapingView::InstrumentSoundShapingView(QWidget* parent) : + QWidget(parent), + ModelView(nullptr, this) { - m_targetsTabWidget = new TabWidget( tr( "TARGET" ), this ); - m_targetsTabWidget->setGeometry( TARGETS_TABWIDGET_X, - TARGETS_TABWIDGET_Y, - TARGETS_TABWIDGET_WIDTH, - TARGETS_TABWIDGET_HEIGTH ); + QVBoxLayout* mainLayout = new QVBoxLayout(this); + mainLayout->setContentsMargins(5, 5, 5, 5); - for( int i = 0; i < InstrumentSoundShaping::NumTargets; ++i ) + m_targetsTabWidget = new TabWidget(tr("TARGET"), this); + + for (int i = 0; i < InstrumentSoundShaping::NumTargets; ++i) { - m_envLfoViews[i] = new EnvelopeAndLfoView( m_targetsTabWidget ); - m_targetsTabWidget->addTab( m_envLfoViews[i], - tr( InstrumentSoundShaping::targetNames[i][0] ), - nullptr ); + m_envLfoViews[i] = new EnvelopeAndLfoView(m_targetsTabWidget); + m_targetsTabWidget->addTab(m_envLfoViews[i], + tr(InstrumentSoundShaping::targetNames[i][0]), nullptr); } - - m_filterGroupBox = new GroupBox( tr( "FILTER" ), this ); - m_filterGroupBox->setGeometry( FILTER_GROUPBOX_X, FILTER_GROUPBOX_Y, - FILTER_GROUPBOX_WIDTH, - FILTER_GROUPBOX_HEIGHT ); + mainLayout->addWidget(m_targetsTabWidget, 1); - m_filterComboBox = new ComboBox( m_filterGroupBox ); - m_filterComboBox->setGeometry( 14, 22, 120, ComboBox::DEFAULT_HEIGHT ); - m_filterComboBox->setFont( pointSize<8>( m_filterComboBox->font() ) ); + m_filterGroupBox = new GroupBox(tr("FILTER"), this); + QHBoxLayout* filterLayout = new QHBoxLayout(m_filterGroupBox); + QMargins filterMargins = filterLayout->contentsMargins(); + filterMargins.setTop(18); + filterLayout->setContentsMargins(filterMargins); + + m_filterComboBox = new ComboBox(m_filterGroupBox); + filterLayout->addWidget(m_filterComboBox); + + m_filterCutKnob = new Knob(KnobType::Bright26, m_filterGroupBox); + m_filterCutKnob->setLabel(tr("FREQ")); + m_filterCutKnob->setHintText(tr("Cutoff frequency:"), " " + tr("Hz")); + filterLayout->addWidget(m_filterCutKnob); + + m_filterResKnob = new Knob(KnobType::Bright26, m_filterGroupBox); + m_filterResKnob->setLabel(tr("Q/RESO")); + m_filterResKnob->setHintText(tr("Q/Resonance:"), ""); + filterLayout->addWidget(m_filterResKnob); + + mainLayout->addWidget(m_filterGroupBox); - m_filterCutKnob = new Knob( KnobType::Bright26, m_filterGroupBox ); - m_filterCutKnob->setLabel( tr( "FREQ" ) ); - m_filterCutKnob->move( 140, 18 ); - m_filterCutKnob->setHintText( tr( "Cutoff frequency:" ), " " + tr( "Hz" ) ); + m_singleStreamInfoLabel = new QLabel(tr("Envelopes, LFOs and filters are not supported by the current instrument."), this); + m_singleStreamInfoLabel->setWordWrap(true); + // TODO Could also be rendered in system font size... + m_singleStreamInfoLabel->setFont(adjustedToPixelSize(m_singleStreamInfoLabel->font(), 10)); + m_singleStreamInfoLabel->setFixedWidth(242); - - m_filterResKnob = new Knob( KnobType::Bright26, m_filterGroupBox ); - m_filterResKnob->setLabel( tr( "Q/RESO" ) ); - m_filterResKnob->move( 196, 18 ); - m_filterResKnob->setHintText( tr( "Q/Resonance:" ), "" ); - - - m_singleStreamInfoLabel = new QLabel( tr( "Envelopes, LFOs and filters are not supported by the current instrument." ), this ); - m_singleStreamInfoLabel->setWordWrap( true ); - m_singleStreamInfoLabel->setFont( pointSize<8>( m_singleStreamInfoLabel->font() ) ); - - m_singleStreamInfoLabel->setGeometry( TARGETS_TABWIDGET_X, - TARGETS_TABWIDGET_Y, - TARGETS_TABWIDGET_WIDTH, - TARGETS_TABWIDGET_HEIGTH ); + mainLayout->addWidget(m_singleStreamInfoLabel, 0, Qt::AlignTop); } diff --git a/src/gui/instrument/InstrumentTrackWindow.cpp b/src/gui/instrument/InstrumentTrackWindow.cpp index 28cd8c6c8..8b868bb50 100644 --- a/src/gui/instrument/InstrumentTrackWindow.cpp +++ b/src/gui/instrument/InstrumentTrackWindow.cpp @@ -42,9 +42,8 @@ #include "FileBrowser.h" #include "FileDialog.h" #include "GroupBox.h" -#include "MixerLineLcdSpinBox.h" +#include "MixerChannelLcdSpinBox.h" #include "GuiApplication.h" -#include "gui_templates.h" #include "Instrument.h" #include "InstrumentFunctions.h" #include "InstrumentFunctionViews.h" @@ -93,13 +92,9 @@ InstrumentTrackWindow::InstrumentTrackWindow( InstrumentTrackView * _itv ) : vlayout->setContentsMargins(0, 0, 0, 0); vlayout->setSpacing( 0 ); - auto generalSettingsWidget = new TabWidget(tr("GENERAL SETTINGS"), this); - + auto generalSettingsWidget = new QWidget(this); auto generalSettingsLayout = new QVBoxLayout(generalSettingsWidget); - generalSettingsLayout->setContentsMargins( 8, 18, 8, 8 ); - generalSettingsLayout->setSpacing( 6 ); - auto nameAndChangeTrackWidget = new QWidget(generalSettingsWidget); auto nameAndChangeTrackLayout = new QHBoxLayout(nameAndChangeTrackWidget); nameAndChangeTrackLayout->setContentsMargins( 0, 0, 0, 0 ); @@ -107,7 +102,6 @@ InstrumentTrackWindow::InstrumentTrackWindow( InstrumentTrackView * _itv ) : // setup line edit for changing instrument track name m_nameLineEdit = new QLineEdit; - m_nameLineEdit->setFont( pointSize<9>( m_nameLineEdit->font() ) ); connect( m_nameLineEdit, SIGNAL( textChanged( const QString& ) ), this, SLOT( textChanged( const QString& ) ) ); @@ -206,7 +200,7 @@ InstrumentTrackWindow::InstrumentTrackWindow( InstrumentTrackView * _itv ) : // setup spinbox for selecting Mixer-channel - m_mixerChannelNumber = new MixerLineLcdSpinBox( 2, nullptr, tr( "Mixer channel" ), m_itv ); + m_mixerChannelNumber = new MixerChannelLcdSpinBox(2, nullptr, tr("Mixer channel"), m_itv); basicControlsLayout->addWidget( m_mixerChannelNumber, 0, 6 ); basicControlsLayout->setAlignment( m_mixerChannelNumber, widgetAlignment ); @@ -356,7 +350,7 @@ void InstrumentTrackWindow::modelChanged() m_mixerChannelNumber->setModel( &m_track->m_mixerChannelModel ); m_pianoView->setModel( &m_track->m_piano ); - if( m_track->instrument() && m_track->instrument()->flags().testFlag( Instrument::Flag::IsNotBendable ) == false ) + if (m_track->instrument() && m_track->instrument()->isBendable()) { m_pitchKnob->setModel( &m_track->m_pitchModel ); m_pitchRangeSpinBox->setModel( &m_track->m_pitchRangeModel ); @@ -374,7 +368,7 @@ void InstrumentTrackWindow::modelChanged() m_pitchRangeLabel->hide(); } - if (m_track->instrument() && m_track->instrument()->flags().testFlag(Instrument::Flag::IsMidiBased)) + if (m_track->instrument() && m_track->instrument()->isMidiBased()) { m_tuningView->microtunerNotSupportedLabel()->show(); m_tuningView->microtunerGroupBox()->hide(); @@ -420,7 +414,7 @@ void InstrumentTrackWindow::saveSettingsBtnClicked() sfd.setDirectory(presetRoot + m_track->instrumentName()); sfd.setFileMode( FileDialog::AnyFile ); QString fname = m_track->name(); - sfd.selectFile(fname.remove(QRegExp(FILENAME_FILTER))); + sfd.selectFile(fname.remove(QRegularExpression(FILENAME_FILTER))); sfd.setDefaultSuffix( "xpf"); if( sfd.exec() == QDialog::Accepted && @@ -468,7 +462,7 @@ void InstrumentTrackWindow::updateInstrumentView() m_tabWidget->addTab( m_instrumentView, tr( "Plugin" ), "plugin_tab", 0 ); m_tabWidget->setActiveTab( 0 ); - m_ssView->setFunctionsHidden( m_track->m_instrument->flags().testFlag( Instrument::Flag::IsSingleStreamed ) ); + m_ssView->setFunctionsHidden(m_track->m_instrument->isSingleStreamed()); modelChanged(); // Get the instrument window to refresh m_track->dataChanged(); // Get the text on the trackButton to change diff --git a/src/gui/instrument/InstrumentTuningView.cpp b/src/gui/instrument/InstrumentTuningView.cpp index 355d7d18c..daa361aad 100644 --- a/src/gui/instrument/InstrumentTuningView.cpp +++ b/src/gui/instrument/InstrumentTuningView.cpp @@ -60,7 +60,7 @@ InstrumentTuningView::InstrumentTuningView(InstrumentTrack *it, QWidget *parent) auto tlabel = new QLabel(tr("Enables the use of global transposition")); tlabel->setWordWrap(true); - tlabel->setFont(pointSize<8>(tlabel->font())); + tlabel->setFont(adjustedToPixelSize(tlabel->font(), 10)); masterPitchLayout->addWidget(tlabel); // Microtuner settings diff --git a/src/gui/instrument/LfoGraph.cpp b/src/gui/instrument/LfoGraph.cpp new file mode 100644 index 000000000..7edbacb09 --- /dev/null +++ b/src/gui/instrument/LfoGraph.cpp @@ -0,0 +1,197 @@ +/* + * LfoGraph.cpp - Displays LFO graphs + * + * Copyright (c) 2004-2014 Tobias Doerffel + * Copyright (c) 2024- 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 "LfoGraph.h" + +#include +#include + +#include "EnvelopeAndLfoParameters.h" +#include "Oscillator.h" +#include "ColorHelper.h" + +#include "gui_templates.h" + +namespace lmms +{ + +extern const float SECS_PER_LFO_OSCILLATION; + +namespace gui +{ + +LfoGraph::LfoGraph(QWidget* parent) : + QWidget(parent), + ModelView(nullptr, this) +{ + setMinimumSize(m_lfoGraph.size()); +} + +void LfoGraph::mousePressEvent(QMouseEvent* me) +{ + if (me->button() == Qt::LeftButton) + { + toggleAmountModel(); + } +} + +void LfoGraph::paintEvent(QPaintEvent*) +{ + QPainter p{this}; + p.setRenderHint(QPainter::Antialiasing); + + // Draw the graph background + p.drawPixmap(rect(), m_lfoGraph); + + const auto* params = castModel(); + if (!params) { return; } + + const float amount = params->getLfoAmountModel().value(); + const float lfoSpeed = params->getLfoSpeedModel().value(); + const f_cnt_t predelayFrames = params->getLfoPredelayFrames(); + const f_cnt_t attackFrames = params->getLfoAttackFrames(); + const f_cnt_t oscillationFrames = params->getLfoOscillationFrames(); + const bool x100 = params->getX100Model().value(); + const int lfoWaveModel = params->getLfoWaveModel().value(); + const auto * userWave = params->getLfoUserWave().get(); + + const int margin = 3; + const int lfoGraphWidth = width() - margin; // subtract margin + const int lfoGraphHeight = height() - 2 * margin; // subtract margin + int graphBaseX = 2; + int graphBaseY = margin + lfoGraphHeight / 2; + + const float framesForGraph = + SECS_PER_LFO_OSCILLATION * Engine::audioEngine()->baseSampleRate() / 10; + + float oscFrames = oscillationFrames * (x100 ? 100. : 1.); + + QPolygonF polyLine; + polyLine << QPointF(graphBaseX - 1, graphBaseY); + + // Collect the points for the poly line by sampling the LFO according to its shape + for (int x = 0; x <= lfoGraphWidth; ++x) + { + float value = 0.0; + float currentSample = x * framesForGraph / lfoGraphWidth; + const auto sampleAsFrameCount = static_cast(currentSample); + if (sampleAsFrameCount > predelayFrames) + { + currentSample -= predelayFrames; + const float phase = currentSample / oscFrames; + + const auto lfoShape = static_cast(lfoWaveModel); + switch (lfoShape) + { + case EnvelopeAndLfoParameters::LfoShape::SineWave: + default: + value = Oscillator::sinSample(phase); + break; + case EnvelopeAndLfoParameters::LfoShape::TriangleWave: + value = Oscillator::triangleSample(phase); + break; + case EnvelopeAndLfoParameters::LfoShape::SawWave: + value = Oscillator::sawSample(phase); + break; + case EnvelopeAndLfoParameters::LfoShape::SquareWave: + value = Oscillator::squareSample(phase); + break; + case EnvelopeAndLfoParameters::LfoShape::RandomWave: + if (x % (int)(900 * lfoSpeed + 1) == 0) + { + m_randomGraph = Oscillator::noiseSample(0.0); + } + value = m_randomGraph; + break; + case EnvelopeAndLfoParameters::LfoShape::UserDefinedWave: + value = Oscillator::userWaveSample(userWave, phase); + break; + } + + if (sampleAsFrameCount <= attackFrames) + { + value *= currentSample / attackFrames; + } + } + + const float currentY = -lfoGraphHeight / 2.0f * value; + + polyLine << QPointF(graphBaseX + x, graphBaseY + currentY); + } + + // Compute the color of the lines based on the amount of the LFO + const float absAmount = std::abs(amount); + const QColor noAmountColor{96, 91, 96}; + const QColor fullAmountColor{0, 255, 128}; + const QColor lineColor{ColorHelper::interpolateInRgb(noAmountColor, fullAmountColor, absAmount)}; + + p.setPen(QPen(lineColor, 1.5)); + + p.drawPolyline(polyLine); + + drawInfoText(*params); +} + +void LfoGraph::drawInfoText(const EnvelopeAndLfoParameters& params) +{ + QPainter p(this); + + const float lfoSpeed = params.getLfoSpeedModel().value(); + const bool x100 = params.getX100Model().value(); + + const float hertz = 1. / (SECS_PER_LFO_OSCILLATION * lfoSpeed) * (x100 ? 100. : 1.); + const auto infoText = tr("%1 Hz").arg(hertz, 0, 'f', 3); + + // First configure the font so that we get correct results for the font metrics used below + QFont f = p.font(); + f.setPixelSize(height() * 0.2); + p.setFont(f); + + // This is the position where the text and its rectangle will be rendered + const QPoint textPosition(4, height() - 6); + + // Draw a slightly transparent black rectangle underneath the text to keep it legible + const QFontMetrics fontMetrics(f); + // This gives the bounding rectangle if the text was rendered at the origin ... + const auto boundingRect = fontMetrics.boundingRect(infoText); + // ... so we translate it to the actual position where the text will be rendered. + p.fillRect(boundingRect.translated(textPosition), QColor{0, 0, 0, 192}); + + // Now draw the actual info text + p.setPen(QColor(201, 201, 225)); + p.drawText(textPosition, infoText); +} + +void LfoGraph::toggleAmountModel() +{ + auto* params = castModel(); + auto& lfoAmountModel = params->getLfoAmountModel(); + + lfoAmountModel.setValue(lfoAmountModel.value() < 1.0 ? 1.0 : 0.0); +} + +} // namespace gui + +} // namespace lmms diff --git a/src/gui/instrument/PianoView.cpp b/src/gui/instrument/PianoView.cpp index d20cbcac5..13628d97e 100644 --- a/src/gui/instrument/PianoView.cpp +++ b/src/gui/instrument/PianoView.cpp @@ -67,15 +67,6 @@ auto WhiteKeys = std::array Key::C, Key::D, Key::E, Key::F, Key::G, Key::A, Key::H } ; - -QPixmap * PianoView::s_whiteKeyPm = nullptr; /*!< A white key released */ -QPixmap * PianoView::s_blackKeyPm = nullptr; /*!< A black key released */ -QPixmap * PianoView::s_whiteKeyPressedPm = nullptr; /*!< A white key pressed */ -QPixmap * PianoView::s_blackKeyPressedPm = nullptr; /*!< A black key pressed */ -QPixmap * PianoView::s_whiteKeyDisabledPm = nullptr; /*!< A white key disabled */ -QPixmap * PianoView::s_blackKeyDisabledPm = nullptr; /*!< A black key disabled */ - - const int PIANO_BASE = 11; /*!< The height of the root note display */ const int PW_WHITE_KEY_WIDTH = 10; /*!< The width of a white key */ const int PW_BLACK_KEY_WIDTH = 8; /*!< The width of a black key */ @@ -99,31 +90,6 @@ PianoView::PianoView(QWidget *parent) : m_lastKey(-1), /*!< The last key displayed? */ m_movedNoteModel(nullptr) /*!< Key marker which is being moved */ { - if (s_whiteKeyPm == nullptr) - { - s_whiteKeyPm = new QPixmap(embed::getIconPixmap("white_key")); - } - if (s_blackKeyPm == nullptr) - { - s_blackKeyPm = new QPixmap(embed::getIconPixmap("black_key")); - } - if (s_whiteKeyPressedPm == nullptr) - { - s_whiteKeyPressedPm = new QPixmap(embed::getIconPixmap("white_key_pressed")); - } - if (s_blackKeyPressedPm == nullptr) - { - s_blackKeyPressedPm = new QPixmap(embed::getIconPixmap("black_key_pressed")); - } - if (s_whiteKeyDisabledPm == nullptr) - { - s_whiteKeyDisabledPm = new QPixmap(embed::getIconPixmap("white_key_disabled")); - } - if (s_blackKeyDisabledPm == nullptr) - { - s_blackKeyDisabledPm = new QPixmap(embed::getIconPixmap("black_key_disabled")); - } - setAttribute(Qt::WA_OpaquePaintEvent, true); setFocusPolicy(Qt::StrongFocus); @@ -841,7 +807,7 @@ void PianoView::paintEvent( QPaintEvent * ) QPainter p( this ); // set smaller font for printing number of every octave - p.setFont( pointSize( p.font() ) ); + p.setFont(adjustedToPixelSize(p.font(), LABEL_TEXT_SIZE)); // draw bar above the keyboard (there will be the labels @@ -894,16 +860,16 @@ void PianoView::paintEvent( QPaintEvent * ) { if (m_piano && m_piano->isKeyPressed(cur_key)) { - p.drawPixmap(x, PIANO_BASE, *s_whiteKeyPressedPm); + p.drawPixmap(x, PIANO_BASE, m_whiteKeyPressedPm); } else { - p.drawPixmap(x, PIANO_BASE, *s_whiteKeyPm); + p.drawPixmap(x, PIANO_BASE, m_whiteKeyPm); } } else { - p.drawPixmap(x, PIANO_BASE, *s_whiteKeyDisabledPm); + p.drawPixmap(x, PIANO_BASE, m_whiteKeyDisabledPm); } x += PW_WHITE_KEY_WIDTH; @@ -928,16 +894,16 @@ void PianoView::paintEvent( QPaintEvent * ) { if (m_piano && m_piano->isKeyPressed(startKey)) { - p.drawPixmap(0 - PW_WHITE_KEY_WIDTH / 2, PIANO_BASE, *s_blackKeyPressedPm); + p.drawPixmap(0 - PW_WHITE_KEY_WIDTH / 2, PIANO_BASE, m_blackKeyPressedPm); } else { - p.drawPixmap(0 - PW_WHITE_KEY_WIDTH / 2, PIANO_BASE, *s_blackKeyPm); + p.drawPixmap(0 - PW_WHITE_KEY_WIDTH / 2, PIANO_BASE, m_blackKeyPm); } } else { - p.drawPixmap(0 - PW_WHITE_KEY_WIDTH / 2, PIANO_BASE, *s_blackKeyDisabledPm); + p.drawPixmap(0 - PW_WHITE_KEY_WIDTH / 2, PIANO_BASE, m_blackKeyDisabledPm); } } @@ -951,16 +917,16 @@ void PianoView::paintEvent( QPaintEvent * ) { if (m_piano && m_piano->isKeyPressed(cur_key)) { - p.drawPixmap(x + PW_WHITE_KEY_WIDTH / 2, PIANO_BASE, *s_blackKeyPressedPm); + p.drawPixmap(x + PW_WHITE_KEY_WIDTH / 2, PIANO_BASE, m_blackKeyPressedPm); } else { - p.drawPixmap(x + PW_WHITE_KEY_WIDTH / 2, PIANO_BASE, *s_blackKeyPm); + p.drawPixmap(x + PW_WHITE_KEY_WIDTH / 2, PIANO_BASE, m_blackKeyPm); } } else { - p.drawPixmap(x + PW_WHITE_KEY_WIDTH / 2, PIANO_BASE, *s_blackKeyDisabledPm); + p.drawPixmap(x + PW_WHITE_KEY_WIDTH / 2, PIANO_BASE, m_blackKeyDisabledPm); } x += PW_WHITE_KEY_WIDTH; white_cnt = 0; diff --git a/src/gui/menus/MidiPortMenu.cpp b/src/gui/menus/MidiPortMenu.cpp index b99c3a0b7..6c573fdf5 100644 --- a/src/gui/menus/MidiPortMenu.cpp +++ b/src/gui/menus/MidiPortMenu.cpp @@ -24,7 +24,6 @@ */ #include "MidiPortMenu.h" -#include "gui_templates.h" namespace lmms::gui { diff --git a/src/gui/modals/ControllerConnectionDialog.cpp b/src/gui/modals/ControllerConnectionDialog.cpp index 4d1090d5c..12e26d03c 100644 --- a/src/gui/modals/ControllerConnectionDialog.cpp +++ b/src/gui/modals/ControllerConnectionDialog.cpp @@ -299,9 +299,8 @@ void ControllerConnectionDialog::selectController() { if( m_midiControllerSpinBox->model()->value() > 0 ) { - MidiController * mc; - mc = m_midiController->copyToMidiController( Engine::getSong() ); - + auto mc = m_midiController->copyToMidiController(Engine::getSong()); + /* if( m_targetModel->getTrack() && !m_targetModel->getTrack()->displayName().isEmpty() ) diff --git a/src/gui/modals/EffectSelectDialog.cpp b/src/gui/modals/EffectSelectDialog.cpp index 31ffd7728..65976059f 100644 --- a/src/gui/modals/EffectSelectDialog.cpp +++ b/src/gui/modals/EffectSelectDialog.cpp @@ -2,6 +2,7 @@ * EffectSelectDialog.cpp - dialog to choose effect plugin * * Copyright (c) 2006-2009 Tobias Doerffel + * Copyright (c) 2023 Lost Robot * * This file is part of LMMS - https://lmms.io * @@ -23,63 +24,63 @@ */ #include "EffectSelectDialog.h" - -#include "ui_EffectSelectDialog.h" - #include "DummyEffect.h" #include "EffectChain.h" #include "embed.h" #include "PluginFactory.h" +#include +#include +#include #include +#include +#include +#include +#include namespace lmms::gui { - -EffectSelectDialog::EffectSelectDialog( QWidget * _parent ) : - QDialog( _parent ), - ui( new Ui::EffectSelectDialog ), +EffectSelectDialog::EffectSelectDialog(QWidget* parent) : + QDialog(parent), + m_effectKeys(), + m_currentSelection(), m_sourceModel(), m_model(), - m_descriptionWidget( nullptr ) + m_descriptionWidget(nullptr), + m_pluginList(new QTableView(this)), + m_scrollArea(new QScrollArea(this)) { - ui->setupUi( this ); - - setWindowIcon( embed::getIconPixmap( "setup_audio" ) ); - - // query effects + setWindowTitle(tr("Add effect")); + resize(640, 480); + + setWindowIcon(embed::getIconPixmap("setup_audio")); + // Query effects EffectKeyList subPluginEffectKeys; - - for (const Plugin::Descriptor* desc: getPluginFactory()->descriptors(Plugin::Type::Effect)) + for (const auto desc : getPluginFactory()->descriptors(Plugin::Type::Effect)) { - if( desc->subPluginFeatures ) + if (desc->subPluginFeatures) { - desc->subPluginFeatures->listSubPluginKeys( - desc, - subPluginEffectKeys ); + desc->subPluginFeatures->listSubPluginKeys(desc, subPluginEffectKeys); } else { - m_effectKeys << EffectKey( desc, desc->name ); - + m_effectKeys << EffectKey(desc, desc->name); } } - m_effectKeys += subPluginEffectKeys; - // and fill our source model - m_sourceModel.setHorizontalHeaderItem( 0, new QStandardItem( tr( "Name" ) ) ); - m_sourceModel.setHorizontalHeaderItem( 1, new QStandardItem( tr( "Type" ) ) ); + // Fill the source model + m_sourceModel.setHorizontalHeaderItem(0, new QStandardItem(tr("Name"))); + m_sourceModel.setHorizontalHeaderItem(1, new QStandardItem(tr("Type"))); int row = 0; - for( EffectKeyList::ConstIterator it = m_effectKeys.begin(); - it != m_effectKeys.end(); ++it ) + for (EffectKeyList::ConstIterator it = m_effectKeys.begin(); it != m_effectKeys.end(); ++it) { QString name; QString type; - if( it->desc->subPluginFeatures ) + if (it->desc->subPluginFeatures) { name = it->displayName(); type = it->desc->displayName; @@ -89,113 +90,153 @@ EffectSelectDialog::EffectSelectDialog( QWidget * _parent ) : name = it->desc->displayName; type = "LMMS"; } - m_sourceModel.setItem( row, 0, new QStandardItem( name ) ); - m_sourceModel.setItem( row, 1, new QStandardItem( type ) ); + m_sourceModel.setItem(row, 0, new QStandardItem(name)); + m_sourceModel.setItem(row, 1, new QStandardItem(type)); ++row; } - // setup filtering - m_model.setSourceModel( &m_sourceModel ); - m_model.setFilterCaseSensitivity( Qt::CaseInsensitive ); + // Setup filtering + m_model.setSourceModel(&m_sourceModel); + m_model.setFilterCaseSensitivity(Qt::CaseInsensitive); - connect( ui->filterEdit, SIGNAL( textChanged( const QString& ) ), - &m_model, SLOT( setFilterFixedString( const QString& ) ) ); - connect( ui->filterEdit, SIGNAL( textChanged( const QString& ) ), - this, SLOT(updateSelection())); - connect( ui->filterEdit, SIGNAL( textChanged( const QString& ) ), - SLOT(sortAgain())); + QHBoxLayout* mainLayout = new QHBoxLayout(this); - ui->pluginList->setModel( &m_model ); + QVBoxLayout* leftSectionLayout = new QVBoxLayout(); + + QStringList buttonLabels = { tr("All"), "LMMS", "LADSPA", "LV2", "VST" }; + QStringList buttonSearchString = { "", "LMMS", "LADSPA", "LV2", "VST" }; + + for (int i = 0; i < buttonLabels.size(); ++i) + { + const QString& label = buttonLabels[i]; + const QString& searchString = buttonSearchString[i]; + + QPushButton* button = new QPushButton(label, this); + button->setFixedSize(50, 50); + button->setFocusPolicy(Qt::NoFocus); + leftSectionLayout->addWidget(button); + + connect(button, &QPushButton::clicked, this, [this, searchString] { + m_model.setEffectTypeFilter(searchString); + updateSelection(); + }); + } + + leftSectionLayout->addStretch();// Add stretch to the button layout to push buttons to the top + mainLayout->addLayout(leftSectionLayout); + + m_filterEdit = new QLineEdit(this); + connect(m_filterEdit, &QLineEdit::textChanged, this, [this](const QString &text) { +// TODO: Cleanup when we don't support Qt5 anymore +#if (QT_VERSION >= QT_VERSION_CHECK(5,12,0)) + m_model.setFilterRegularExpression(QRegularExpression(text, QRegularExpression::CaseInsensitiveOption)); +#else + m_model.setFilterRegExp(QRegExp(text, Qt::CaseInsensitive)); +#endif + }); + connect(m_filterEdit, &QLineEdit::textChanged, this, &EffectSelectDialog::updateSelection); + m_filterEdit->setFocus(); + m_filterEdit->setFocusPolicy(Qt::StrongFocus); + m_filterEdit->setPlaceholderText(tr("Search")); + m_filterEdit->setClearButtonEnabled(true); + m_filterEdit->addAction(embed::getIconPixmap("zoom"), QLineEdit::LeadingPosition); + + m_pluginList->setModel(&m_model); + m_pluginList->setEditTriggers(QAbstractItemView::NoEditTriggers); + m_pluginList->setSelectionBehavior(QAbstractItemView::SelectRows); + m_pluginList->setSelectionMode(QAbstractItemView::SingleSelection); + m_pluginList->setSortingEnabled(true); + m_pluginList->sortByColumn(0, Qt::AscendingOrder); // Initial sort by column 0 (Name) + m_pluginList->verticalHeader()->setSectionResizeMode(QHeaderView::ResizeToContents); + m_pluginList->verticalHeader()->hide(); + m_pluginList->horizontalHeader()->setSectionResizeMode(0, QHeaderView::Stretch); + m_pluginList->horizontalHeader()->setSectionResizeMode(1, QHeaderView::ResizeToContents); + m_pluginList->setFocusPolicy(Qt::NoFocus); + + // Scroll Area + m_scrollArea->setWidgetResizable(true); + QWidget* scrollAreaWidgetContents = new QWidget(m_scrollArea); + scrollAreaWidgetContents->setObjectName("scrollAreaWidgetContents"); + m_scrollArea->setWidget(scrollAreaWidgetContents); + m_scrollArea->setMaximumHeight(180); + m_scrollArea->setFocusPolicy(Qt::NoFocus); + + // Button Box + QDialogButtonBox* buttonBox = new QDialogButtonBox(Qt::Horizontal, this); + buttonBox->setStandardButtons(QDialogButtonBox::Cancel | QDialogButtonBox::Ok); + buttonBox->setFocusPolicy(Qt::NoFocus); + connect(buttonBox, &QDialogButtonBox::accepted, this, &EffectSelectDialog::acceptSelection); + connect(buttonBox, &QDialogButtonBox::rejected, this, &EffectSelectDialog::reject); + + QVBoxLayout* rightSectionLayout = new QVBoxLayout(); + rightSectionLayout->addWidget(m_filterEdit); + rightSectionLayout->addWidget(m_pluginList); + rightSectionLayout->addWidget(m_scrollArea); + rightSectionLayout->addWidget(buttonBox); + mainLayout->addLayout(rightSectionLayout); + + setLayout(mainLayout); - // setup selection model auto selectionModel = new QItemSelectionModel(&m_model); - ui->pluginList->setSelectionModel( selectionModel ); - connect( selectionModel, SIGNAL( currentRowChanged( const QModelIndex&, - const QModelIndex & ) ), - SLOT( rowChanged( const QModelIndex &, const QModelIndex& ) ) ); - connect( ui->pluginList, SIGNAL( doubleClicked( const QModelIndex& ) ), - SLOT(acceptSelection())); + m_pluginList->setSelectionModel(selectionModel); + connect(selectionModel, &QItemSelectionModel::currentRowChanged, + this, &EffectSelectDialog::rowChanged); - // try to accept current selection when pressing "OK" - connect( ui->buttonBox, SIGNAL(accepted()), - this, SLOT(acceptSelection())); - - ui->filterEdit->setClearButtonEnabled( true ); - ui->pluginList->verticalHeader()->setSectionResizeMode( - QHeaderView::ResizeToContents ); - ui->pluginList->verticalHeader()->hide(); - ui->pluginList->horizontalHeader()->setSectionResizeMode( 0, - QHeaderView::Stretch ); - ui->pluginList->horizontalHeader()->setSectionResizeMode( 1, - QHeaderView::ResizeToContents ); - ui->pluginList->sortByColumn( 0, Qt::AscendingOrder ); + connect(m_pluginList, &QTableView::doubleClicked, + this, &EffectSelectDialog::acceptSelection); + + setModal(true); + installEventFilter(this); updateSelection(); show(); } - - -EffectSelectDialog::~EffectSelectDialog() -{ - delete ui; -} - - - - -Effect * EffectSelectDialog::instantiateSelectedPlugin( EffectChain * _parent ) +Effect* EffectSelectDialog::instantiateSelectedPlugin(EffectChain* parent) { Effect* result = nullptr; - if(!m_currentSelection.name.isEmpty() && m_currentSelection.desc) + if (!m_currentSelection.name.isEmpty() && m_currentSelection.desc) { - result = Effect::instantiate(m_currentSelection.desc->name, - _parent, &m_currentSelection); + result = Effect::instantiate(m_currentSelection.desc->name, parent, &m_currentSelection); } - if(!result) + if (!result) { - result = new DummyEffect(_parent, QDomElement()); + result = new DummyEffect(parent, QDomElement()); } return result; } - - - void EffectSelectDialog::acceptSelection() { - if( m_currentSelection.isValid() ) + if (m_currentSelection.isValid()) { accept(); } } - - - -void EffectSelectDialog::rowChanged( const QModelIndex & _idx, - const QModelIndex & ) +void EffectSelectDialog::rowChanged(const QModelIndex& idx, const QModelIndex&) { delete m_descriptionWidget; m_descriptionWidget = nullptr; - if( m_model.mapToSource( _idx ).row() < 0 ) + if (m_model.mapToSource(idx).row() < 0) { - // invalidate current selection + // Invalidate current selection m_currentSelection = Plugin::Descriptor::SubPluginFeatures::Key(); } else { - m_currentSelection = m_effectKeys[m_model.mapToSource( _idx ).row()]; + m_currentSelection = m_effectKeys[m_model.mapToSource(idx).row()]; } - if( m_currentSelection.desc ) + if (m_currentSelection.desc) { m_descriptionWidget = new QWidget; auto hbox = new QHBoxLayout(m_descriptionWidget); + hbox->setAlignment(Qt::AlignTop); - Plugin::Descriptor const & descriptor = *( m_currentSelection.desc ); + Plugin::Descriptor const& descriptor = *(m_currentSelection.desc); const PixmapLoader* pixLoa = m_currentSelection.logo(); if (pixLoa) @@ -203,76 +244,88 @@ void EffectSelectDialog::rowChanged( const QModelIndex & _idx, auto logoLabel = new QLabel(m_descriptionWidget); logoLabel->setPixmap(pixLoa->pixmap()); logoLabel->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); + logoLabel->setMaximumSize(64, 64); - hbox->addWidget( logoLabel ); - hbox->setAlignment( logoLabel, Qt::AlignTop); + hbox->addWidget(logoLabel); } auto textualInfoWidget = new QWidget(m_descriptionWidget); - hbox->addWidget(textualInfoWidget); auto textWidgetLayout = new QVBoxLayout(textualInfoWidget); textWidgetLayout->setContentsMargins(4, 4, 4, 4); - textWidgetLayout->setSpacing( 0 ); + textWidgetLayout->setSpacing(8); - if ( m_currentSelection.desc->subPluginFeatures ) + if (m_currentSelection.desc->subPluginFeatures) { auto subWidget = new QWidget(textualInfoWidget); auto subLayout = new QVBoxLayout(subWidget); - subLayout->setContentsMargins(4, 4, 4, 4); - subLayout->setSpacing( 0 ); + subLayout->setContentsMargins(0, 0, 0, 0); + subLayout->setSpacing(8); + m_currentSelection.desc->subPluginFeatures-> - fillDescriptionWidget( subWidget, &m_currentSelection ); - for( QWidget * w : subWidget->findChildren() ) + fillDescriptionWidget(subWidget, &m_currentSelection); + for (QWidget* w : subWidget->findChildren()) { - if( w->parent() == subWidget ) + if (w->parent() == subWidget) { - subLayout->addWidget( w ); + subLayout->addWidget(w); + subLayout->setAlignment(w, QFlags(Qt::AlignTop | Qt::AlignLeft)); } } - textWidgetLayout->addWidget(subWidget); } else { auto label = new QLabel(m_descriptionWidget); QString labelText = "

" + tr("Name") + ": " + QString::fromUtf8(descriptor.displayName) + "

"; - labelText += "

" + tr("Description") + ": " + qApp->translate( "PluginBrowser", descriptor.description ) + "

"; + labelText += "

" + tr("Description") + ": " + qApp->translate("PluginBrowser", descriptor.description) + "

"; labelText += "

" + tr("Author") + ": " + QString::fromUtf8(descriptor.author) + "

"; label->setText(labelText); + label->setWordWrap(true); + textWidgetLayout->addWidget(label); } - ui->scrollArea->setWidget( m_descriptionWidget ); + m_scrollArea->setWidget(m_descriptionWidget); m_descriptionWidget->show(); } } - - - -void EffectSelectDialog::sortAgain() -{ - ui->pluginList->setSortingEnabled( ui->pluginList->isSortingEnabled() ); -} - - - - void EffectSelectDialog::updateSelection() { - // no valid selection anymore due to changed filter? - if( ui->pluginList->selectionModel()->selection().size() <= 0 ) + // No valid selection anymore due to changed filter? + if (m_pluginList->selectionModel()->selection().size() <= 0) { - // then select our first item - ui->pluginList->selectionModel()->select( m_model.index( 0, 0 ), - QItemSelectionModel::ClearAndSelect - | QItemSelectionModel::Rows ); - rowChanged( m_model.index( 0, 0 ), QModelIndex() ); + // Then select our first item + m_pluginList->selectionModel()-> + select(m_model.index(0, 0), QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows); + rowChanged(m_model.index(0, 0), QModelIndex()); } } +bool EffectSelectDialog::eventFilter(QObject *obj, QEvent *event) +{ + if (obj == this && event->type() == QEvent::KeyPress) + { + QKeyEvent *keyEvent = static_cast(event); + if (keyEvent->key() == Qt::Key_Up || keyEvent->key() == Qt::Key_Down) + { + QItemSelectionModel* selectionModel = m_pluginList->selectionModel(); + int currentRow = selectionModel->currentIndex().row(); + int newRow = (keyEvent->key() == Qt::Key_Up) ? currentRow - 1 : currentRow + 1; + int rowCount = m_pluginList->model()->rowCount(); + newRow = qBound(0, newRow, rowCount - 1); + + selectionModel->setCurrentIndex(m_pluginList->model()->index(newRow, 0), + QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows); + m_pluginList->scrollTo(m_pluginList->model()->index(newRow, 0)); + return true; + } + } + + return QDialog::eventFilter(obj, event); +} } // namespace lmms::gui diff --git a/src/gui/modals/EffectSelectDialog.ui b/src/gui/modals/EffectSelectDialog.ui deleted file mode 100644 index b0433e66c..000000000 --- a/src/gui/modals/EffectSelectDialog.ui +++ /dev/null @@ -1,109 +0,0 @@ - - - EffectSelectDialog - - - - 0 - 0 - 585 - 550 - - - - Add effect - - - true - - - - 10 - - - - - - - - - 500 - 250 - - - - QAbstractItemView::NoEditTriggers - - - Qt::ScrollBarAlwaysOff - - - QAbstractItemView::SelectRows - - - QAbstractItemView::SingleSelection - - - false - - - true - - - - - - - QFrame::NoFrame - - - - - 0 - 0 - 497 - 109 - - - - - - - - - Qt::Horizontal - - - QDialogButtonBox::Cancel|QDialogButtonBox::Ok - - - - - - - - - buttonBox - rejected() - EffectSelectDialog - reject() - - - 316 - 260 - - - 286 - 274 - - - - - - - RowTableView - QTableView -
RowTableView.h
-
-
-
diff --git a/src/gui/modals/ExportProjectDialog.cpp b/src/gui/modals/ExportProjectDialog.cpp index fe39082e4..8dfda4981 100644 --- a/src/gui/modals/ExportProjectDialog.cpp +++ b/src/gui/modals/ExportProjectDialog.cpp @@ -154,11 +154,8 @@ OutputSettings::StereoMode mapToStereoMode(int index) void ExportProjectDialog::startExport() { - AudioEngine::qualitySettings qs = - AudioEngine::qualitySettings( - static_cast(interpolationCB->currentIndex()), - static_cast(oversamplingCB->currentIndex()) ); - + auto qs = AudioEngine::qualitySettings( + static_cast(interpolationCB->currentIndex())); const auto samplerates = std::array{44100, 48000, 88200, 96000, 192000}; const auto bitrates = std::array{64, 128, 160, 192, 256, 320}; diff --git a/src/gui/modals/SetupDialog.cpp b/src/gui/modals/SetupDialog.cpp index 209422563..6f05433b7 100644 --- a/src/gui/modals/SetupDialog.cpp +++ b/src/gui/modals/SetupDialog.cpp @@ -37,7 +37,6 @@ #include "embed.h" #include "Engine.h" #include "FileDialog.h" -#include "gui_templates.h" #include "MainWindow.h" #include "MidiSetupWidget.h" #include "ProjectJournal.h" @@ -120,6 +119,7 @@ SetupDialog::SetupDialog(ConfigTab tab_to_open) : "app", "disablebackup").toInt()), m_openLastProject(ConfigManager::inst()->value( "app", "openlastproject").toInt()), + m_loopMarkerMode{ConfigManager::inst()->value("app", "loopmarkermode", "dual")}, m_lang(ConfigManager::inst()->value( "app", "language")), m_saveInterval( ConfigManager::inst()->value( @@ -142,10 +142,10 @@ SetupDialog::SetupDialog(ConfigTab tab_to_open) : "ui", "disableautoquit", "1").toInt()), m_NaNHandler(ConfigManager::inst()->value( "app", "nanhandler", "1").toInt()), - m_hqAudioDev(ConfigManager::inst()->value( - "audioengine", "hqaudio").toInt()), m_bufferSize(ConfigManager::inst()->value( "audioengine", "framesperaudiobuffer").toInt()), + m_midiAutoQuantize(ConfigManager::inst()->value( + "midi", "autoquantize", "0").toInt() != 0), m_workingDir(QDir::toNativeSeparators(ConfigManager::inst()->workingDir())), m_vstDir(QDir::toNativeSeparators(ConfigManager::inst()->vstDir())), m_ladspaDir(QDir::toNativeSeparators(ConfigManager::inst()->ladspaDir())), @@ -159,8 +159,7 @@ SetupDialog::SetupDialog(ConfigTab tab_to_open) : { setWindowIcon(embed::getIconPixmap("setup_general")); setWindowTitle(tr("Settings")); - // TODO: Equivalent to the new setWindowFlag(Qt::WindowContextHelpButtonHint, false) - setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); + setWindowFlag(Qt::WindowContextHelpButtonHint, false); setModal(true); Engine::projectJournal()->setJournalling(false); @@ -255,6 +254,19 @@ SetupDialog::SetupDialog(ConfigTab tab_to_open) : addCheckBox(tr("Show warning when deleting a mixer channel that is in use"), guiGroupBox, guiGroupLayout, m_mixerChannelDeletionWarning, SLOT(toggleMixerChannelDeletionWarning(bool)), false); + m_loopMarkerComboBox = new QComboBox{guiGroupBox}; + + m_loopMarkerComboBox->addItem(tr("Dual-button"), "dual"); + m_loopMarkerComboBox->addItem(tr("Grab closest"), "closest"); + m_loopMarkerComboBox->addItem(tr("Handles"), "handles"); + + m_loopMarkerComboBox->setCurrentIndex(m_loopMarkerComboBox->findData(m_loopMarkerMode)); + connect(m_loopMarkerComboBox, qOverload(&QComboBox::currentIndexChanged), + this, &SetupDialog::loopMarkerModeChanged); + + guiGroupLayout->addWidget(new QLabel{tr("Loop edit mode"), guiGroupBox}); + guiGroupLayout->addWidget(m_loopMarkerComboBox); + generalControlsLayout->addWidget(guiGroupBox); generalControlsLayout->addSpacing(10); @@ -546,10 +558,6 @@ SetupDialog::SetupDialog(ConfigTab tab_to_open) : // 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 group QGroupBox * bufferSizeBox = new QGroupBox(tr("Buffer size"), audio_w); QVBoxLayout * bufferSizeLayout = new QVBoxLayout(bufferSizeBox); @@ -591,7 +599,6 @@ SetupDialog::SetupDialog(ConfigTab tab_to_open) : // Audio layout ordering. audio_layout->addWidget(audioInterfaceBox); audio_layout->addWidget(as_w); - audio_layout->addWidget(hqaudio); audio_layout->addWidget(bufferSizeBox); audio_layout->addStretch(); @@ -703,10 +710,22 @@ SetupDialog::SetupDialog(ConfigTab tab_to_open) : m_assignableMidiDevices->setCurrentIndex(current); } + // MIDI Recording tab + auto* midiRecordingTab = new QGroupBox(tr("Behavior when recording"), midi_w); + auto* midiRecordingLayout = new QVBoxLayout(midiRecordingTab); + { + auto *box = addCheckBox(tr("Auto-quantize notes in Piano Roll"), + midiRecordingTab, midiRecordingLayout, + m_midiAutoQuantize, SLOT(toggleMidiAutoQuantization(bool)), + false); + box->setToolTip(tr("If enabled, notes will be automatically quantized when recording them from a MIDI controller. If disabled, they are always recorded at the highest possible resolution.")); + } + // MIDI layout ordering. midi_layout->addWidget(midiInterfaceBox); midi_layout->addWidget(ms_w); midi_layout->addWidget(midiAutoAssignBox); + midi_layout->addWidget(midiRecordingTab); midi_layout->addStretch(); @@ -922,6 +941,7 @@ void SetupDialog::accept() QString::number(!m_disableBackup)); ConfigManager::inst()->setValue("app", "openlastproject", QString::number(m_openLastProject)); + ConfigManager::inst()->setValue("app", "loopmarkermode", m_loopMarkerMode); ConfigManager::inst()->setValue("app", "language", m_lang); ConfigManager::inst()->setValue("ui", "saveinterval", QString::number(m_saveInterval)); @@ -943,14 +963,13 @@ void SetupDialog::accept() m_audioIfaceNames[m_audioInterfaces->currentText()]); ConfigManager::inst()->setValue("app", "nanhandler", QString::number(m_NaNHandler)); - ConfigManager::inst()->setValue("audioengine", "hqaudio", - QString::number(m_hqAudioDev)); ConfigManager::inst()->setValue("audioengine", "framesperaudiobuffer", QString::number(m_bufferSize)); ConfigManager::inst()->setValue("audioengine", "mididev", m_midiIfaceNames[m_midiInterfaces->currentText()]); ConfigManager::inst()->setValue("midi", "midiautoassign", m_assignableMidiDevices->currentText()); + ConfigManager::inst()->setValue("midi", "autoquantize", QString::number(m_midiAutoQuantize)); ConfigManager::inst()->setWorkingDir(QDir::fromNativeSeparators(m_workingDir)); @@ -1061,6 +1080,12 @@ void SetupDialog::toggleOpenLastProject(bool enabled) } +void SetupDialog::loopMarkerModeChanged() +{ + m_loopMarkerMode = m_loopMarkerComboBox->currentData().toString(); +} + + void SetupDialog::setLanguage(int lang) { m_lang = m_languages[lang]; @@ -1140,17 +1165,6 @@ void SetupDialog::toggleDisableAutoQuit(bool enabled) m_disableAutoQuit = enabled; } - - - -// Audio settings slots. - -void SetupDialog::toggleHQAudioDev(bool enabled) -{ - m_hqAudioDev = enabled; -} - - void SetupDialog::audioInterfaceChanged(const QString & iface) { for(AswMap::iterator it = m_audioIfaceSetupWidgets.begin(); @@ -1209,7 +1223,7 @@ void SetupDialog::setBufferSize(int value) m_bufferSize = value * BUFFERSIZE_RESOLUTION; m_bufferSizeLbl->setText(tr("Frames: %1\nLatency: %2 ms").arg(m_bufferSize).arg( - 1000.0f * m_bufferSize / Engine::audioEngine()->processingSampleRate(), 0, 'f', 1)); + 1000.0f * m_bufferSize / Engine::audioEngine()->outputSampleRate(), 0, 'f', 1)); updateBufferSizeWarning(m_bufferSize); } @@ -1233,6 +1247,11 @@ void SetupDialog::midiInterfaceChanged(const QString & iface) m_midiIfaceSetupWidgets[m_midiIfaceNames[iface]]->show(); } +void SetupDialog::toggleMidiAutoQuantization(bool enabled) +{ + m_midiAutoQuantize = enabled; +} + // Paths settings slots. diff --git a/src/gui/modals/VersionedSaveDialog.cpp b/src/gui/modals/VersionedSaveDialog.cpp index 0c61df9f2..c8e1c6821 100644 --- a/src/gui/modals/VersionedSaveDialog.cpp +++ b/src/gui/modals/VersionedSaveDialog.cpp @@ -89,9 +89,9 @@ VersionedSaveDialog::VersionedSaveDialog( QWidget *parent, bool VersionedSaveDialog::changeFileNameVersion(QString &fileName, bool increment ) { - static QRegExp regexp( "[- ]\\d+(\\.\\w+)?$" ); + static QRegularExpression regex( "[- ]\\d+(\\.\\w+)?$" ); - int idx = regexp.indexIn( fileName ); + int idx = regex.match(fileName).capturedStart(); // For file names without extension (no ".mmpz") int insertIndex = fileName.lastIndexOf( '.' ); if ( insertIndex < idx+1 ) diff --git a/src/gui/modals/export_project.ui b/src/gui/modals/export_project.ui index 6b175de78..797ae0790 100644 --- a/src/gui/modals/export_project.ui +++ b/src/gui/modals/export_project.ui @@ -404,37 +404,6 @@ - - - - Oversampling: - - - - - - - - 1x (None) - - - - - 2x - - - - - 4x - - - - - 8x - - - - diff --git a/src/gui/tracks/InstrumentTrackView.cpp b/src/gui/tracks/InstrumentTrackView.cpp index 87c0f0449..1d9991c31 100644 --- a/src/gui/tracks/InstrumentTrackView.cpp +++ b/src/gui/tracks/InstrumentTrackView.cpp @@ -40,7 +40,7 @@ #include "Mixer.h" #include "MixerView.h" #include "GuiApplication.h" -#include "InstrumentTrack.h" +#include "Instrument.h" #include "InstrumentTrackWindow.h" #include "MainWindow.h" #include "MidiClient.h" @@ -62,7 +62,7 @@ InstrumentTrackView::InstrumentTrackView( InstrumentTrack * _it, TrackContainerV m_tlb = new TrackLabelButton( this, getTrackSettingsWidget() ); m_tlb->setCheckable( true ); - m_tlb->setIcon( embed::getIconPixmap( "instrument_track" ) ); + m_tlb->setIcon(determinePixmap(_it)); m_tlb->show(); connect( m_tlb, SIGNAL(toggled(bool)), @@ -74,7 +74,7 @@ InstrumentTrackView::InstrumentTrackView( InstrumentTrack * _it, TrackContainerV connect(ConfigManager::inst(), SIGNAL(valueChanged(QString,QString,QString)), this, SLOT(handleConfigChange(QString,QString,QString))); - m_mixerChannelNumber = new MixerLineLcdSpinBox(2, getTrackSettingsWidget(), tr("Mixer channel"), this); + m_mixerChannelNumber = new MixerChannelLcdSpinBox(2, getTrackSettingsWidget(), tr("Mixer channel"), this); m_mixerChannelNumber->show(); m_volumeKnob = new Knob( KnobType::Small17, getTrackSettingsWidget(), @@ -142,7 +142,9 @@ InstrumentTrackView::InstrumentTrackView( InstrumentTrack * _it, TrackContainerV m_activityIndicator->setFixedSize(8, 28); m_activityIndicator->show(); - auto layout = new QHBoxLayout(getTrackSettingsWidget()); + auto masterLayout = new QVBoxLayout(getTrackSettingsWidget()); + masterLayout->setContentsMargins(0, 1, 0, 0); + auto layout = new QHBoxLayout(); layout->setContentsMargins(0, 0, 0, 0); layout->setSpacing(0); layout->addWidget(m_tlb); @@ -150,6 +152,8 @@ InstrumentTrackView::InstrumentTrackView( InstrumentTrack * _it, TrackContainerV layout->addWidget(m_activityIndicator); layout->addWidget(m_volumeKnob); layout->addWidget(m_panningKnob); + masterLayout->addLayout(layout); + masterLayout->addSpacerItem(new QSpacerItem(0, 0, QSizePolicy::Minimum, QSizePolicy::Expanding)); connect( m_activityIndicator, SIGNAL(pressed()), this, SLOT(activityIndicatorPressed())); @@ -227,7 +231,7 @@ void InstrumentTrackView::createMixerLine() auto channel = Engine::mixer()->mixerChannel(channelIndex); channel->m_name = getTrack()->name(); - if (getTrack()->useColor()) { channel->setColor (getTrack()->color()); } + channel->setColor(getTrack()->color()); assignMixerLine(channelIndex); } @@ -240,7 +244,7 @@ void InstrumentTrackView::assignMixerLine(int channelIndex) { model()->mixerChannelModel()->setValue( channelIndex ); - getGUI()->mixerView()->setCurrentMixerLine( channelIndex ); + getGUI()->mixerView()->setCurrentMixerChannel(channelIndex); } @@ -392,5 +396,27 @@ QMenu * InstrumentTrackView::createMixerMenu(QString title, QString newMixerLabe return mixerMenu; } +QPixmap InstrumentTrackView::determinePixmap(InstrumentTrack* instrumentTrack) +{ + if (instrumentTrack) + { + Instrument* instrument = instrumentTrack->instrument(); + + if (instrument && instrument->descriptor()) + { + const PixmapLoader* pl = instrument->key().isValid() + ? instrument->key().logo() + : instrument->descriptor()->logo; + + if (pl) + { + return pl->pixmap(); + } + } + } + + return embed::getIconPixmap("instrument_track"); +} + } // namespace lmms::gui diff --git a/src/gui/tracks/PatternTrackView.cpp b/src/gui/tracks/PatternTrackView.cpp index be039ba79..ac0b42e2d 100644 --- a/src/gui/tracks/PatternTrackView.cpp +++ b/src/gui/tracks/PatternTrackView.cpp @@ -26,6 +26,7 @@ #include "Engine.h" #include "GuiApplication.h" +#include "MainWindow.h" #include "PatternEditor.h" #include "PatternStore.h" #include "PatternTrack.h" @@ -74,8 +75,7 @@ bool PatternTrackView::close() void PatternTrackView::clickedTrackLabel() { Engine::patternStore()->setCurrentPattern(m_patternTrack->patternIndex()); - getGUI()->patternEditor()->parentWidget()->show(); - getGUI()->patternEditor()->setFocus(Qt::ActiveWindowFocusReason); + getGUI()->mainWindow()->togglePatternEditorWin(true); } diff --git a/src/gui/tracks/SampleTrackView.cpp b/src/gui/tracks/SampleTrackView.cpp index 8516eb5c2..8475f7fa9 100644 --- a/src/gui/tracks/SampleTrackView.cpp +++ b/src/gui/tracks/SampleTrackView.cpp @@ -21,7 +21,7 @@ * Boston, MA 02110-1301 USA. * */ - + #include "SampleTrackView.h" #include @@ -58,7 +58,7 @@ SampleTrackView::SampleTrackView( SampleTrack * _t, TrackContainerView* tcv ) : m_tlb->setIcon(embed::getIconPixmap("sample_track")); m_tlb->show(); - m_mixerChannelNumber = new MixerLineLcdSpinBox(2, getTrackSettingsWidget(), tr("Mixer channel"), this); + m_mixerChannelNumber = new MixerChannelLcdSpinBox(2, getTrackSettingsWidget(), tr("Mixer channel"), this); m_mixerChannelNumber->show(); m_volumeKnob = new Knob( KnobType::Small17, getTrackSettingsWidget(), @@ -86,7 +86,9 @@ SampleTrackView::SampleTrackView( SampleTrack * _t, TrackContainerView* tcv ) : m_activityIndicator->setFixedSize(8, 28); m_activityIndicator->show(); - auto layout = new QHBoxLayout(getTrackSettingsWidget()); + auto masterLayout = new QVBoxLayout(getTrackSettingsWidget()); + masterLayout->setContentsMargins(0, 1, 0, 0); + auto layout = new QHBoxLayout(); layout->setContentsMargins(0, 0, 0, 0); layout->setSpacing(0); layout->addWidget(m_tlb); @@ -94,6 +96,8 @@ SampleTrackView::SampleTrackView( SampleTrack * _t, TrackContainerView* tcv ) : layout->addWidget(m_activityIndicator); layout->addWidget(m_volumeKnob); layout->addWidget(m_panningKnob); + masterLayout->addLayout(layout); + masterLayout->addSpacerItem(new QSpacerItem(0, 0, QSizePolicy::Minimum, QSizePolicy::Expanding)); connect(_t, SIGNAL(playingChanged()), this, SLOT(updateIndicator())); @@ -228,7 +232,7 @@ void SampleTrackView::createMixerLine() auto channel = Engine::mixer()->mixerChannel(channelIndex); channel->m_name = getTrack()->name(); - if (getTrack()->useColor()) { channel->setColor (getTrack()->color()); } + channel->setColor(getTrack()->color()); assignMixerLine(channelIndex); } @@ -241,7 +245,7 @@ void SampleTrackView::assignMixerLine(int channelIndex) { model()->mixerChannelModel()->setValue(channelIndex); - getGUI()->mixerView()->setCurrentMixerLine(channelIndex); + getGUI()->mixerView()->setCurrentMixerChannel(channelIndex); } diff --git a/src/gui/tracks/TrackContentWidget.cpp b/src/gui/tracks/TrackContentWidget.cpp index 619eff831..e205a0c00 100644 --- a/src/gui/tracks/TrackContentWidget.cpp +++ b/src/gui/tracks/TrackContentWidget.cpp @@ -43,14 +43,15 @@ #include "ClipView.h" #include "TrackView.h" - namespace lmms::gui { /*! Alternate between a darker and a lighter background color every 4 bars */ const int BARS_PER_GROUP = 4; - +/* Lines between bars will disappear if zoomed too far out (i.e + if there are less than 4 pixels between lines)*/ +const int MIN_PIXELS_BETWEEN_LINES = 4; /*! \brief Create a new trackContentWidget * @@ -65,8 +66,15 @@ TrackContentWidget::TrackContentWidget( TrackView * parent ) : m_trackView( parent ), m_darkerColor( Qt::SolidPattern ), m_lighterColor( Qt::SolidPattern ), - m_gridColor( Qt::SolidPattern ), - m_embossColor( Qt::SolidPattern ) + m_coarseGridColor( Qt::SolidPattern ), + m_fineGridColor( Qt::SolidPattern ), + m_horizontalColor( Qt::SolidPattern ), + m_embossColor( Qt::SolidPattern ), + m_coarseGridWidth(2), + m_fineGridWidth(1), + m_horizontalWidth(1), + m_embossWidth(0), + m_embossOffset(0) { setAcceptDrops( true ); @@ -74,6 +82,14 @@ TrackContentWidget::TrackContentWidget( TrackView * parent ) : SIGNAL( positionChanged( const lmms::TimePos& ) ), this, SLOT( changePosition( const lmms::TimePos& ) ) ); + // Update background if snap size changes + connect(getGUI()->songEditor()->m_editor->snappingModel(), &Model::dataChanged, + this, &TrackContentWidget::updateBackground); + + // Also update background if proportional snap is enabled/disabled + connect(getGUI()->songEditor()->m_editor, &SongEditor::proportionalSnapChanged, + this, &TrackContentWidget::updateBackground); + setStyle( QApplication::style() ); updateBackground(); @@ -82,16 +98,30 @@ TrackContentWidget::TrackContentWidget( TrackView * parent ) : - - - void TrackContentWidget::updateBackground() -{ +{ + // use snapSize to determine number of lines to draw + float snapSize = getGUI()->songEditor()->m_editor->getSnapSize(); + const TrackContainerView * tcv = m_trackView->trackContainerView(); // Assume even-pixels-per-bar. Makes sense, should be like this anyways int ppb = static_cast( tcv->pixelsPerBar() ); + // Coarse grid appears every bar (less frequently if quantization > 1 bar) + float coarseGridResolution = (snapSize >= 1) ? snapSize : 1; + // Fine grid appears within bars + float fineGridResolution = snapSize; + // Increase fine grid resolution (size between lines) if it results in less than + // 4 pixels between each line to avoid cluttering + float pixelsBetweenLines = ppb * snapSize; + if (pixelsBetweenLines < MIN_PIXELS_BETWEEN_LINES) { + // Scale fineGridResolution so that there are enough pixels between lines + // scaleFactor should be a power of 2 + int scaleFactor = 1 << static_cast( std::ceil( std::log2( MIN_PIXELS_BETWEEN_LINES / pixelsBetweenLines ) ) ); + fineGridResolution *= scaleFactor; + } + int w = ppb * BARS_PER_GROUP; int h = height(); m_background = QPixmap( w * 2, height() ); @@ -101,22 +131,29 @@ void TrackContentWidget::updateBackground() pmp.fillRect( w, 0, w , h, lighterColor() ); // draw lines - // vertical lines - pmp.setPen( QPen( gridColor(), 1 ) ); - for( float x = 0; x < w * 2; x += ppb ) + // draw fine grid + pmp.setPen( QPen( fineGridColor(), fineGridWidth() ) ); + for (float x = 0; x < w * 2; x += ppb * fineGridResolution) { pmp.drawLine( QLineF( x, 0.0, x, h ) ); } - pmp.setPen( QPen( embossColor(), 1 ) ); - for( float x = 1.0; x < w * 2; x += ppb ) + // draw coarse grid + pmp.setPen( QPen( coarseGridColor(), coarseGridWidth() ) ); + for (float x = 0; x < w * 2; x += ppb * coarseGridResolution) { pmp.drawLine( QLineF( x, 0.0, x, h ) ); } - // horizontal line - pmp.setPen( QPen( gridColor(), 1 ) ); - pmp.drawLine( 0, h-1, w*2, h-1 ); + pmp.setPen( QPen( embossColor(), embossWidth() ) ); + for (float x = (coarseGridWidth() + embossOffset()); x < w * 2; x += ppb * coarseGridResolution) + { + pmp.drawLine( QLineF( x, 0.0, x, h ) ); + } + + // draw horizontal line + pmp.setPen( QPen( horizontalColor(), horizontalWidth() ) ); + pmp.drawLine(0, h - (horizontalWidth() + 1) / 2, w * 2, h - (horizontalWidth() + 1) / 2); pmp.end(); @@ -690,13 +727,41 @@ QBrush TrackContentWidget::lighterColor() const { return m_lighterColor; } //! \brief CSS theming qproperty access method -QBrush TrackContentWidget::gridColor() const -{ return m_gridColor; } +QBrush TrackContentWidget::coarseGridColor() const +{ return m_coarseGridColor; } + +//! \brief CSS theming qproperty access method +QBrush TrackContentWidget::fineGridColor() const +{ return m_fineGridColor; } + +//! \brief CSS theming qproperty access method +QBrush TrackContentWidget::horizontalColor() const +{ return m_horizontalColor; } //! \brief CSS theming qproperty access method QBrush TrackContentWidget::embossColor() const { return m_embossColor; } +//! \brief CSS theming qproperty access method +int TrackContentWidget::coarseGridWidth() const +{ return m_coarseGridWidth; } + +//! \brief CSS theming qproperty access method +int TrackContentWidget::fineGridWidth() const +{ return m_fineGridWidth; } + +//! \brief CSS theming qproperty access method +int TrackContentWidget::horizontalWidth() const +{ return m_horizontalWidth; } + +//! \brief CSS theming qproperty access method +int TrackContentWidget::embossWidth() const +{ return m_embossWidth; } + +//! \brief CSS theming qproperty access method +int TrackContentWidget::embossOffset() const +{ return m_embossOffset; } + //! \brief CSS theming qproperty access method void TrackContentWidget::setDarkerColor( const QBrush & c ) { m_darkerColor = c; } @@ -706,12 +771,39 @@ void TrackContentWidget::setLighterColor( const QBrush & c ) { m_lighterColor = c; } //! \brief CSS theming qproperty access method -void TrackContentWidget::setGridColor( const QBrush & c ) -{ m_gridColor = c; } +void TrackContentWidget::setCoarseGridColor( const QBrush & c ) +{ m_coarseGridColor = c; } + +//! \brief CSS theming qproperty access method +void TrackContentWidget::setFineGridColor( const QBrush & c ) +{ m_fineGridColor = c; } + +//! \brief CSS theming qproperty access method +void TrackContentWidget::setHorizontalColor( const QBrush & c ) +{ m_horizontalColor = c; } //! \brief CSS theming qproperty access method void TrackContentWidget::setEmbossColor( const QBrush & c ) { m_embossColor = c; } +//! \brief CSS theming qproperty access method +void TrackContentWidget::setCoarseGridWidth(int c) +{ m_coarseGridWidth = c; } + +//! \brief CSS theming qproperty access method +void TrackContentWidget::setFineGridWidth(int c) +{ m_fineGridWidth = c; } + +//! \brief CSS theming qproperty access method +void TrackContentWidget::setHorizontalWidth(int c) +{ m_horizontalWidth = c; } + +//! \brief CSS theming qproperty access method +void TrackContentWidget::setEmbossWidth(int c) +{ m_embossWidth = c; } + +//! \brief CSS theming qproperty access method +void TrackContentWidget::setEmbossOffset(int c) +{ m_embossOffset = c; } } // namespace lmms::gui diff --git a/src/gui/tracks/TrackLabelButton.cpp b/src/gui/tracks/TrackLabelButton.cpp index 2a50a4aa2..871d42316 100644 --- a/src/gui/tracks/TrackLabelButton.cpp +++ b/src/gui/tracks/TrackLabelButton.cpp @@ -30,13 +30,13 @@ #include "ConfigManager.h" #include "embed.h" -#include "Engine.h" +#include "InstrumentTrackView.h" #include "Instrument.h" #include "InstrumentTrack.h" #include "RenameDialog.h" -#include "Song.h" #include "TrackRenameLineEdit.h" #include "TrackView.h" +#include "Track.h" namespace lmms::gui { @@ -53,7 +53,7 @@ TrackLabelButton::TrackLabelButton( TrackView * _tv, QWidget * _parent ) : m_renameLineEdit = new TrackRenameLineEdit( this ); m_renameLineEdit->hide(); - if( ConfigManager::inst()->value( "ui", "compacttrackbuttons" ).toInt() ) + if (isInCompactMode()) { setFixedSize( 32, 29 ); } @@ -77,7 +77,7 @@ TrackLabelButton::TrackLabelButton( TrackView * _tv, QWidget * _parent ) : void TrackLabelButton::rename() { - if( ConfigManager::inst()->value( "ui", "compacttrackbuttons" ).toInt() ) + if (isInCompactMode()) { QString txt = m_trackView->getTrack()->name(); RenameDialog renameDlg( txt ); @@ -85,7 +85,6 @@ void TrackLabelButton::rename() if( txt != text() ) { m_trackView->getTrack()->setName( txt ); - Engine::getSong()->setModified(); } } else @@ -103,7 +102,7 @@ void TrackLabelButton::rename() void TrackLabelButton::renameFinished() { - if( !( ConfigManager::inst()->value( "ui", "compacttrackbuttons" ).toInt() ) ) + if (!isInCompactMode()) { m_renameLineEdit->clearFocus(); m_renameLineEdit->hide(); @@ -113,7 +112,6 @@ void TrackLabelButton::renameFinished() { setText( elideName( m_renameLineEdit->text() ) ); m_trackView->getTrack()->setName( m_renameLineEdit->text() ); - Engine::getSong()->setModified(); } } } @@ -183,37 +181,33 @@ void TrackLabelButton::mouseReleaseEvent( QMouseEvent *_me ) } - - -void TrackLabelButton::paintEvent( QPaintEvent * _pe ) +void TrackLabelButton::paintEvent(QPaintEvent* pe) { - if( m_trackView->getTrack()->type() == Track::Type::Instrument ) + if (m_trackView->getTrack()->type() == Track::Type::Instrument) { auto it = dynamic_cast(m_trackView->getTrack()); - const PixmapLoader * pl; + const PixmapLoader* pl; auto get_logo = [](InstrumentTrack* it) -> const PixmapLoader* { return it->instrument()->key().isValid() ? it->instrument()->key().logo() : it->instrument()->descriptor()->logo; }; - if( it && it->instrument() && + if (it && it->instrument() && it->instrument()->descriptor() && - ( pl = get_logo(it) ) ) + (pl = get_logo(it))) { - if( pl->pixmapName() != m_iconName ) + if (pl->pixmapName() != m_iconName) { m_iconName = pl->pixmapName(); - setIcon( pl->pixmap() ); + setIcon(pl->pixmap()); } } } - QToolButton::paintEvent( _pe ); + QToolButton::paintEvent(pe); } - - void TrackLabelButton::resizeEvent(QResizeEvent *_re) { setText( elideName( m_trackView->getTrack()->displayName() ) ); @@ -237,5 +231,9 @@ QString TrackLabelButton::elideName( const QString &name ) return elidedName; } +bool TrackLabelButton::isInCompactMode() const +{ + return ConfigManager::inst()->value("ui", "compacttrackbuttons").toInt(); +} } // namespace lmms::gui diff --git a/src/gui/tracks/TrackOperationsWidget.cpp b/src/gui/tracks/TrackOperationsWidget.cpp index 31edc4949..de119c64f 100644 --- a/src/gui/tracks/TrackOperationsWidget.cpp +++ b/src/gui/tracks/TrackOperationsWidget.cpp @@ -38,7 +38,6 @@ #include "DataFile.h" #include "embed.h" #include "Engine.h" -#include "gui_templates.h" #include "InstrumentTrackView.h" #include "PixmapButton.h" #include "Song.h" @@ -172,11 +171,11 @@ void TrackOperationsWidget::paintEvent( QPaintEvent * pe ) p.fillRect(rect(), palette().brush(QPalette::Window)); - if( m_trackView->getTrack()->useColor() && ! m_trackView->getTrack()->getMutedModel()->value() ) + if (m_trackView->getTrack()->color().has_value() && !m_trackView->getTrack()->getMutedModel()->value()) { QRect coloredRect( 0, 0, 10, m_trackView->getTrack()->getHeight() ); - - p.fillRect( coloredRect, m_trackView->getTrack()->color() ); + + p.fillRect(coloredRect, m_trackView->getTrack()->color().value()); } p.drawPixmap(2, 2, embed::getIconPixmap(m_trackView->isMovingTrack() ? "track_op_grip_c" : "track_op_grip")); @@ -265,15 +264,15 @@ void TrackOperationsWidget::removeTrack() void TrackOperationsWidget::selectTrackColor() { - QColor new_color = ColorChooser( this ).withPalette( ColorChooser::Palette::Track )-> \ - getColor( m_trackView->getTrack()->color() ); + const auto newColor = ColorChooser{this} + .withPalette(ColorChooser::Palette::Track) + ->getColor(m_trackView->getTrack()->color().value_or(Qt::white)); - if( ! new_color.isValid() ) - { return; } + if (!newColor.isValid()) { return; } - auto track = m_trackView->getTrack(); + const auto track = m_trackView->getTrack(); track->addJournalCheckPoint(); - track->setColor(new_color); + track->setColor(newColor); Engine::getSong()->setModified(); } @@ -281,7 +280,7 @@ void TrackOperationsWidget::resetTrackColor() { auto track = m_trackView->getTrack(); track->addJournalCheckPoint(); - track->resetColor(); + track->setColor(std::nullopt); Engine::getSong()->setModified(); } @@ -298,16 +297,13 @@ void TrackOperationsWidget::resetClipColors() { auto track = m_trackView->getTrack(); track->addJournalCheckPoint(); - for (auto clip: track->getClips()) + for (auto clip : track->getClips()) { - clip->useCustomClipColor(false); + clip->setColor(std::nullopt); } Engine::getSong()->setModified(); } - - - /*! \brief Update the trackOperationsWidget context menu * * For all track types, we have the Clone and Remove options. diff --git a/src/gui/tracks/TrackView.cpp b/src/gui/tracks/TrackView.cpp index 426be7e36..e23236021 100644 --- a/src/gui/tracks/TrackView.cpp +++ b/src/gui/tracks/TrackView.cpp @@ -364,9 +364,7 @@ void TrackView::mouseMoveEvent( QMouseEvent * me ) } else if( m_action == Action::Resize ) { - setFixedHeight( qMax( me->y(), MINIMAL_TRACK_HEIGHT ) ); - m_trackContainerView->realignTracks(); - m_track->setHeight( height() ); + resizeToHeight(me->y()); } if( height() < DEFAULT_TRACK_HEIGHT ) @@ -393,6 +391,23 @@ void TrackView::mouseReleaseEvent( QMouseEvent * me ) QWidget::mouseReleaseEvent( me ); } +void TrackView::wheelEvent(QWheelEvent* we) +{ + // Note: we add the values because one of them will be 0. If the alt modifier + // is pressed x is non-zero and otherwise y. + const int deltaY = we->angleDelta().x() + we->angleDelta().y(); + int const direction = deltaY < 0 ? -1 : 1; + + auto const modKeys = we->modifiers(); + int stepSize = modKeys == (Qt::ControlModifier | Qt::AltModifier) ? 1 : modKeys == (Qt::ShiftModifier | Qt::AltModifier) ? 5 : 0; + + if (stepSize != 0) + { + resizeToHeight(height() + stepSize * direction); + we->accept(); + } +} + @@ -445,4 +460,12 @@ void TrackView::setIndicatorMute(FadeButton* indicator, bool muted) } +void TrackView::resizeToHeight(int h) +{ + setFixedHeight(qMax(h, MINIMAL_TRACK_HEIGHT)); + m_trackContainerView->realignTracks(); + m_track->setHeight(height()); +} + + } // namespace lmms::gui diff --git a/src/gui/widgets/BarModelEditor.cpp b/src/gui/widgets/BarModelEditor.cpp new file mode 100644 index 000000000..4b02c9634 --- /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/ComboBox.cpp b/src/gui/widgets/ComboBox.cpp index 2377a37ab..0daae1b24 100644 --- a/src/gui/widgets/ComboBox.cpp +++ b/src/gui/widgets/ComboBox.cpp @@ -26,23 +26,22 @@ #include "ComboBox.h" -#include -#include #include #include #include +#include #include "CaptionMenu.h" -#include "embed.h" #include "gui_templates.h" +#define QT_SUPPORTS_WIDGET_SCREEN (QT_VERSION >= QT_VERSION_CHECK(5,14,0)) +#if !QT_SUPPORTS_WIDGET_SCREEN +#include +#include +#endif + namespace lmms::gui { - -QPixmap * ComboBox::s_background = nullptr; -QPixmap * ComboBox::s_arrow = nullptr; -QPixmap * ComboBox::s_arrowSelected = nullptr; - const int CB_ARROW_BTN_WIDTH = 18; @@ -54,22 +53,7 @@ ComboBox::ComboBox( QWidget * _parent, const QString & _name ) : { setFixedHeight( ComboBox::DEFAULT_HEIGHT ); - if( s_background == nullptr ) - { - s_background = new QPixmap( embed::getIconPixmap( "combobox_bg" ) ); - } - - if( s_arrow == nullptr ) - { - s_arrow = new QPixmap( embed::getIconPixmap( "combobox_arrow" ) ); - } - - if( s_arrowSelected == nullptr ) - { - s_arrowSelected = new QPixmap( embed::getIconPixmap( "combobox_arrow_selected" ) ); - } - - setFont( pointSize<9>( font() ) ); + setFont(adjustedToPixelSize(font(), 10)); connect( &m_menu, SIGNAL(triggered(QAction*)), this, SLOT(setItem(QAction*))); @@ -136,15 +120,23 @@ void ComboBox::mousePressEvent( QMouseEvent* event ) a->setData( i ); } - QPoint gpos = mapToGlobal( QPoint( 0, height() ) ); - if( gpos.y() + m_menu.sizeHint().height() < qApp->desktop()->height() ) + QPoint gpos = mapToGlobal(QPoint(0, height())); + + #if (QT_SUPPORTS_WIDGET_SCREEN) + bool const menuCanBeFullyShown = screen()->geometry().contains(QRect(gpos, m_menu.sizeHint())); + #else + bool const menuCanBeFullyShown = gpos.y() + m_menu.sizeHint().height() < qApp->desktop()->height(); + #endif + + if (menuCanBeFullyShown) { - m_menu.exec( gpos ); + m_menu.exec(gpos); } else { - m_menu.exec( mapToGlobal( QPoint( width(), 0 ) ) ); + m_menu.exec(mapToGlobal(QPoint(width(), 0))); } + m_pressed = false; update(); } @@ -172,7 +164,7 @@ void ComboBox::paintEvent( QPaintEvent * _pe ) { QPainter p( this ); - p.fillRect( 2, 2, width()-2, height()-4, *s_background ); + p.fillRect(2, 2, width() - 2, height() - 4, m_background); QColor shadow = palette().shadow().color(); QColor highlight = palette().highlight().color(); @@ -194,9 +186,9 @@ void ComboBox::paintEvent( QPaintEvent * _pe ) style()->drawPrimitive( QStyle::PE_Frame, &opt, &p, this ); - QPixmap * arrow = m_pressed ? s_arrowSelected : s_arrow; + auto arrow = m_pressed ? m_arrowSelected : m_arrow; - p.drawPixmap( width() - CB_ARROW_BTN_WIDTH + 3, 4, *arrow ); + p.drawPixmap(width() - CB_ARROW_BTN_WIDTH + 3, 4, arrow); if( model() && model()->size() > 0 ) { @@ -228,7 +220,8 @@ void ComboBox::wheelEvent( QWheelEvent* event ) { if( model() ) { - model()->setInitValue(model()->value() + ((event->angleDelta().y() < 0) ? 1 : -1)); + const int direction = (event->angleDelta().y() < 0 ? 1 : -1) * (event->inverted() ? -1 : 1); + model()->setInitValue(model()->value() + direction); update(); event->accept(); } diff --git a/src/gui/widgets/Fader.cpp b/src/gui/widgets/Fader.cpp index dcf648c37..d2d7c1c2e 100644 --- a/src/gui/widgets/Fader.cpp +++ b/src/gui/widgets/Fader.cpp @@ -49,6 +49,7 @@ #include #include #include +#include #include "lmms_math.h" #include "embed.h" @@ -59,118 +60,60 @@ namespace lmms::gui { +SimpleTextFloat* Fader::s_textFloat = nullptr; -SimpleTextFloat * Fader::s_textFloat = nullptr; -QPixmap * Fader::s_back = nullptr; -QPixmap * Fader::s_leds = nullptr; -QPixmap * Fader::s_knob = nullptr; - -Fader::Fader( FloatModel * _model, const QString & _name, QWidget * _parent ) : - QWidget( _parent ), - FloatModelView( _model, this ), - m_fPeakValue_L( 0.0 ), - m_fPeakValue_R( 0.0 ), - m_persistentPeak_L( 0.0 ), - m_persistentPeak_R( 0.0 ), - m_fMinPeak( 0.01f ), - m_fMaxPeak( 1.1 ), - m_levelsDisplayedInDBFS(false), - m_moveStartPoint( -1 ), - m_startValue( 0 ), - m_peakGreen( 0, 0, 0 ), - m_peakRed( 0, 0, 0 ), - m_peakYellow( 0, 0, 0 ) +Fader::Fader(FloatModel* model, const QString& name, QWidget* parent) : + QWidget(parent), + FloatModelView(model, this) { - if( s_textFloat == nullptr ) + if (s_textFloat == nullptr) { s_textFloat = new SimpleTextFloat; } - if( ! s_back ) - { - s_back = new QPixmap( embed::getIconPixmap( "fader_background" ) ); - } - if( ! s_leds ) - { - s_leds = new QPixmap( embed::getIconPixmap( "fader_leds" ) ); - } - if( ! s_knob ) - { - s_knob = new QPixmap( embed::getIconPixmap( "fader_knob" ) ); - } - m_back = s_back; - m_leds = s_leds; - m_knob = s_knob; - - init(_model, _name); + setWindowTitle(name); + setAttribute(Qt::WA_OpaquePaintEvent, false); + // For now resize the widget to the size of the previous background image "fader_background.png" as it was found in the classic and default theme + constexpr QSize minimumSize(23, 116); + setMinimumSize(minimumSize); + resize(minimumSize); + setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding); + setModel(model); + setHintText("Volume:", "%"); m_conversionFactor = 100.0; } -Fader::Fader( FloatModel * model, const QString & name, QWidget * parent, QPixmap * back, QPixmap * leds, QPixmap * knob ) : - QWidget( parent ), - FloatModelView( model, this ), - m_fPeakValue_L( 0.0 ), - m_fPeakValue_R( 0.0 ), - m_persistentPeak_L( 0.0 ), - m_persistentPeak_R( 0.0 ), - m_fMinPeak( 0.01f ), - m_fMaxPeak( 1.1 ), - m_levelsDisplayedInDBFS(false), - m_moveStartPoint( -1 ), - m_startValue( 0 ), - m_peakGreen( 0, 0, 0 ), - m_peakRed( 0, 0, 0 ) +Fader::Fader(FloatModel* model, const QString& name, QWidget* parent, const QPixmap& knob) : + Fader(model, name, parent) { - if( s_textFloat == nullptr ) - { - s_textFloat = new SimpleTextFloat; - } - - m_back = back; - m_leds = leds; m_knob = knob; - - init(model, name); -} - -void Fader::init(FloatModel * model, QString const & name) -{ - setWindowTitle( name ); - setAttribute( Qt::WA_OpaquePaintEvent, false ); - QSize backgroundSize = m_back->size(); - setMinimumSize( backgroundSize ); - setMaximumSize( backgroundSize ); - resize( backgroundSize ); - setModel( model ); - setHintText( "Volume:","%"); } - -void Fader::contextMenuEvent( QContextMenuEvent * _ev ) +void Fader::contextMenuEvent(QContextMenuEvent* ev) { - CaptionMenu contextMenu( windowTitle() ); - addDefaultActions( &contextMenu ); - contextMenu.exec( QCursor::pos() ); - _ev->accept(); + CaptionMenu contextMenu(windowTitle()); + addDefaultActions(&contextMenu); + contextMenu.exec(QCursor::pos()); + ev->accept(); } -void Fader::mouseMoveEvent( QMouseEvent *mouseEvent ) +void Fader::mouseMoveEvent(QMouseEvent* mouseEvent) { - if( m_moveStartPoint >= 0 ) + if (m_moveStartPoint >= 0) { int dy = m_moveStartPoint - mouseEvent->globalY(); - float delta = dy * ( model()->maxValue() - model()->minValue() ) / (float) ( height() - ( *m_knob ).height() ); + float delta = dy * (model()->maxValue() - model()->minValue()) / (float)(height() - (m_knob).height()); const auto step = model()->step(); - float newValue = static_cast( static_cast( ( m_startValue + delta ) / step + 0.5 ) ) * step; - model()->setValue( newValue ); + float newValue = static_cast(static_cast((m_startValue + delta) / step + 0.5)) * step; + model()->setValue(newValue); updateTextFloat(); } @@ -179,19 +122,19 @@ void Fader::mouseMoveEvent( QMouseEvent *mouseEvent ) -void Fader::mousePressEvent( QMouseEvent* mouseEvent ) +void Fader::mousePressEvent(QMouseEvent* mouseEvent) { - if( mouseEvent->button() == Qt::LeftButton && - ! ( mouseEvent->modifiers() & Qt::ControlModifier ) ) + if (mouseEvent->button() == Qt::LeftButton && + !(mouseEvent->modifiers() & Qt::ControlModifier)) { - AutomatableModel *thisModel = model(); - if( thisModel ) + AutomatableModel* thisModel = model(); + if (thisModel) { thisModel->addJournalCheckPoint(); - thisModel->saveJournallingState( false ); + thisModel->saveJournallingState(false); } - if( mouseEvent->y() >= knobPosY() - ( *m_knob ).height() && mouseEvent->y() < knobPosY() ) + if (mouseEvent->y() >= knobPosY() - (m_knob).height() && mouseEvent->y() < knobPosY()) { updateTextFloat(); s_textFloat->show(); @@ -208,39 +151,36 @@ void Fader::mousePressEvent( QMouseEvent* mouseEvent ) } else { - AutomatableModelView::mousePressEvent( mouseEvent ); + AutomatableModelView::mousePressEvent(mouseEvent); } } -void Fader::mouseDoubleClickEvent( QMouseEvent* mouseEvent ) +void Fader::mouseDoubleClickEvent(QMouseEvent* mouseEvent) { bool ok; - float newValue; - // TODO: dbV handling - newValue = QInputDialog::getDouble( this, tr( "Set value" ), - tr( "Please enter a new value between %1 and %2:" ). - arg( model()->minValue() * m_conversionFactor ). - arg( model()->maxValue() * m_conversionFactor ), - model()->getRoundedValue() * m_conversionFactor, - model()->minValue() * m_conversionFactor, - model()->maxValue() * m_conversionFactor, model()->getDigitCount(), &ok ) / m_conversionFactor; + // TODO: dbFS handling + auto minv = model()->minValue() * m_conversionFactor; + auto maxv = model()->maxValue() * m_conversionFactor; + float enteredValue = QInputDialog::getDouble(this, tr("Set value"), + tr("Please enter a new value between %1 and %2:").arg(minv).arg(maxv), + model()->getRoundedValue() * m_conversionFactor, minv, maxv, model()->getDigitCount(), &ok); - if( ok ) + if (ok) { - model()->setValue( newValue ); + model()->setValue(enteredValue / m_conversionFactor); } } -void Fader::mouseReleaseEvent( QMouseEvent * mouseEvent ) +void Fader::mouseReleaseEvent(QMouseEvent* mouseEvent) { - if( mouseEvent && mouseEvent->button() == Qt::LeftButton ) + if (mouseEvent && mouseEvent->button() == Qt::LeftButton) { - AutomatableModel *thisModel = model(); - if( thisModel ) + AutomatableModel* thisModel = model(); + if (thisModel) { thisModel->restoreJournallingState(); } @@ -250,20 +190,14 @@ void Fader::mouseReleaseEvent( QMouseEvent * mouseEvent ) } -void Fader::wheelEvent ( QWheelEvent *ev ) +void Fader::wheelEvent (QWheelEvent* ev) { ev->accept(); + const int direction = (ev->angleDelta().y() > 0 ? 1 : -1) * (ev->inverted() ? -1 : 1); - if (ev->angleDelta().y() > 0) - { - model()->incValue( 1 ); - } - else - { - model()->incValue( -1 ); - } + model()->incValue(direction); updateTextFloat(); - s_textFloat->setVisibilityTimeOut( 1000 ); + s_textFloat->setVisibilityTimeOut(1000); } @@ -271,21 +205,14 @@ void Fader::wheelEvent ( QWheelEvent *ev ) /// /// Set peak value (0.0 .. 1.0) /// -void Fader::setPeak( float fPeak, float &targetPeak, float &persistentPeak, QElapsedTimer &lastPeakTimer ) +void Fader::setPeak(float fPeak, float& targetPeak, float& persistentPeak, QElapsedTimer& lastPeakTimer) { - if( fPeak < m_fMinPeak ) - { - fPeak = m_fMinPeak; - } - else if( fPeak > m_fMaxPeak ) - { - fPeak = m_fMaxPeak; - } + fPeak = std::clamp(fPeak, m_fMinPeak, m_fMaxPeak); - if( targetPeak != fPeak) + if (targetPeak != fPeak) { targetPeak = fPeak; - if( targetPeak >= persistentPeak ) + if (targetPeak >= persistentPeak) { persistentPeak = targetPeak; lastPeakTimer.restart(); @@ -293,25 +220,25 @@ void Fader::setPeak( float fPeak, float &targetPeak, float &persistentPeak, QEla update(); } - if( persistentPeak > 0 && lastPeakTimer.elapsed() > 1500 ) + if (persistentPeak > 0 && lastPeakTimer.elapsed() > 1500) { - persistentPeak = qMax( 0, persistentPeak-0.05 ); + persistentPeak = qMax(0, persistentPeak-0.05); update(); } } -void Fader::setPeak_L( float fPeak ) +void Fader::setPeak_L(float fPeak) { - setPeak( fPeak, m_fPeakValue_L, m_persistentPeak_L, m_lastPeakTimer_L ); + setPeak(fPeak, m_fPeakValue_L, m_persistentPeak_L, m_lastPeakTimer_L); } -void Fader::setPeak_R( float fPeak ) +void Fader::setPeak_R(float fPeak) { - setPeak( fPeak, m_fPeakValue_R, m_persistentPeak_R, m_lastPeakTimer_R ); + setPeak(fPeak, m_fPeakValue_R, m_persistentPeak_R, m_lastPeakTimer_R); } @@ -319,169 +246,194 @@ void Fader::setPeak_R( float fPeak ) // update tooltip showing value and adjust position while changing fader value void Fader::updateTextFloat() { - if( ConfigManager::inst()->value( "app", "displaydbfs" ).toInt() && m_conversionFactor == 100.0 ) + if (ConfigManager::inst()->value("app", "displaydbfs").toInt() && m_conversionFactor == 100.0) { - s_textFloat->setText( QString("Volume: %1 dBFS"). - arg( ampToDbfs( model()->value() ), 3, 'f', 2 ) ); + QString label(tr("Volume: %1 dBFS")); + + auto const modelValue = model()->value(); + if (modelValue <= 0.) + { + s_textFloat->setText(label.arg("-∞")); + } + else + { + s_textFloat->setText(label.arg(ampToDbfs(modelValue), 3, 'f', 2)); + } } else { - s_textFloat->setText( m_description + " " + QString("%1 ").arg( model()->value() * m_conversionFactor ) + " " + m_unit ); + s_textFloat->setText(m_description + " " + QString("%1 ").arg(model()->value() * m_conversionFactor) + " " + m_unit); } s_textFloat->moveGlobal(this, QPoint(width() + 2, knobPosY() - s_textFloat->height() / 2)); } -inline int Fader::calculateDisplayPeak( float fPeak ) -{ - int peak = (int)( m_back->height() - ( fPeak / ( m_fMaxPeak - m_fMinPeak ) ) * m_back->height() ); - - return qMin( peak, m_back->height() ); -} - - -void Fader::paintEvent( QPaintEvent * ev) +void Fader::paintEvent(QPaintEvent* ev) { QPainter painter(this); - // Draw the background - painter.drawPixmap( ev->rect(), *m_back, ev->rect() ); - // Draw the levels with peaks - if (getLevelsDisplayedInDBFS()) - { - paintDBFSLevels(ev, painter); - } - else - { - paintLinearLevels(ev, painter); - } + paintLevels(ev, painter, !m_levelsDisplayedInDBFS); // Draw the knob - painter.drawPixmap( 0, knobPosY() - m_knob->height(), *m_knob ); + painter.drawPixmap((width() - m_knob.width()) / 2, knobPosY() - m_knob.height(), m_knob); } -void Fader::paintDBFSLevels(QPaintEvent * ev, QPainter & painter) +void Fader::paintLevels(QPaintEvent* ev, QPainter& painter, bool linear) { - int height = m_back->height(); - int width = m_back->width() / 2; - int center = m_back->width() - width; + std::function mapper = [this](float value) { return ampToDbfs(qMax(0.0001, value)); }; - float const maxDB(ampToDbfs(m_fMaxPeak)); - float const minDB(ampToDbfs(m_fMinPeak)); + if (linear) + { + mapper = [this](float value) { return value; }; + } - // We will need to divide by the span between min and max several times. It's more - // efficient to calculate the reciprocal once and then to multiply. - float const fullSpanReciprocal = 1 / (maxDB - minDB); + const float mappedMinPeak(mapper(m_fMinPeak)); + const float mappedMaxPeak(mapper(m_fMaxPeak)); + const float mappedPeakL(mapper(m_fPeakValue_L)); + const float mappedPeakR(mapper(m_fPeakValue_R)); + const float mappedPersistentPeakL(mapper(m_persistentPeak_L)); + const float mappedPersistentPeakR(mapper(m_persistentPeak_R)); + const float mappedUnity(mapper(1.f)); + painter.save(); + + const QRect baseRect = rect(); + + const int height = baseRect.height(); + + const int margin = 1; + const int distanceBetweenMeters = 2; + + const int numberOfMeters = 2; + + // Compute the width of a single meter by removing the margins and the space between meters + const int leftAndRightMargin = 2 * margin; + const int pixelsBetweenAllMeters = distanceBetweenMeters * (numberOfMeters - 1); + const int remainingPixelsForMeters = baseRect.width() - leftAndRightMargin - pixelsBetweenAllMeters; + const int meterWidth = remainingPixelsForMeters / numberOfMeters; + + QRect leftMeterOutlineRect(margin, margin, meterWidth, height - 2 * margin); + QRect rightMeterOutlineRect(baseRect.width() - margin - meterWidth, margin, meterWidth, height - 2 * margin); + + QMargins removedMargins(1, 1, 1, 1); + QRect leftMeterRect = leftMeterOutlineRect.marginsRemoved(removedMargins); + QRect rightMeterRect = rightMeterOutlineRect.marginsRemoved(removedMargins); + + QPainterPath path; + qreal radius = 2; + path.addRoundedRect(leftMeterOutlineRect, radius, radius); + path.addRoundedRect(rightMeterOutlineRect, radius, radius); + painter.fillPath(path, Qt::black); + + // Now clip everything to the paths of the meters + painter.setClipPath(path); + + // This linear map performs the following mapping: + // Value (dbFS or linear) -> window coordinates of the widget + // It is for example used to determine the height of peaks, markers and to define the gradient for the levels + const LinearMap valuesToWindowCoordinates(mappedMaxPeak, leftMeterRect.y(), mappedMinPeak, leftMeterRect.y() + leftMeterRect.height()); + + // This lambda takes a value (in dbFS or linear) and a rectangle and computes a rectangle + // that represent the value within the rectangle. It is for example used to compute the unity indicators. + const auto computeLevelMarkerRect = [&valuesToWindowCoordinates](const QRect& rect, float peak) -> QRect + { + return QRect(rect.x(), valuesToWindowCoordinates.map(peak), rect.width(), 1); + }; + + // This lambda takes a peak value (in dbFS or linear) and a rectangle and computes a rectangle + // that represent the peak value within the rectangle. It's used to compute the peak indicators + // which "dance" on top of the level meters. + const auto computePeakRect = [&valuesToWindowCoordinates](const QRect& rect, float peak) -> QRect + { + return QRect(rect.x(), valuesToWindowCoordinates.map(peak), rect.width(), 1); + }; + + // This lambda takes a peak value (in dbFS or linear) and a rectangle and returns an adjusted copy of the + // rectangle that represents the peak value. It is used to compute the level meters themselves. + const auto computeLevelRect = [&valuesToWindowCoordinates](const QRect& rect, float peak) -> QRect + { + QRect result(rect); + result.setTop(valuesToWindowCoordinates.map(peak)); + + return result; + }; + + // Draw left and right level markers for the unity lines (0 dbFS, 1.0 amplitude) + if (getRenderUnityLine()) + { + const auto unityRectL = computeLevelMarkerRect(leftMeterRect, mappedUnity); + painter.fillRect(unityRectL, m_unityMarker); + + const auto unityRectR = computeLevelMarkerRect(rightMeterRect, mappedUnity); + painter.fillRect(unityRectR, m_unityMarker); + } + + // These values define where the gradient changes values, i.e. the ranges + // for clipping, warning and ok. + // Please ensure that "clip starts" is the maximum value and that "ok ends" + // is the minimum value and that all other values lie inbetween. Otherwise + // there will be warnings when the gradient is defined. + const float mappedClipStarts(mapper(dbfsToAmp(0.f))); + const float mappedWarnEnd(mapper(dbfsToAmp(-0.01))); + const float mappedWarnStart(mapper(dbfsToAmp(-6.f))); + const float mappedOkEnd(mapper(dbfsToAmp(-12.f))); + + // Prepare the gradient for the meters + // + // The idea is the following. We want to be able to render arbitrary ranges of min and max values. + // Therefore we first compute the start and end point of the gradient in window coordinates. + // The gradient is assumed to start with the clip color and to end with the ok color with warning values in between. + // We know the min and max peaks that map to a rectangle where we draw the levels. We can use the values of the min and max peaks + // as well as the Y-coordinates of the rectangle to compute a map which will give us the coordinates of the value where the clipping + // starts and where the ok area end. These coordinates are used to initialize the gradient. Please note that the gradient might thus + // extend the rectangle into which we paint. + float clipStartYCoord = valuesToWindowCoordinates.map(mappedClipStarts); + float okEndYCoord = valuesToWindowCoordinates.map(mappedOkEnd); + + QLinearGradient linearGrad(0, clipStartYCoord, 0, okEndYCoord); + + // We already know for the gradient that the clip color will be at 0 and that the ok color is at 1. + // What's left to do is to map the inbetween values into the interval [0,1]. + const LinearMap mapBetweenClipAndOk(mappedClipStarts, 0.f, mappedOkEnd, 1.f); + + linearGrad.setColorAt(0, m_peakClip); + linearGrad.setColorAt(mapBetweenClipAndOk.map(mappedWarnEnd), m_peakWarn); + linearGrad.setColorAt(mapBetweenClipAndOk.map(mappedWarnStart), m_peakWarn); + linearGrad.setColorAt(1, m_peakOk); // Draw left levels - float const leftSpan = ampToDbfs(qMax(0.0001, m_fPeakValue_L)) - minDB; - int peak_L = height * leftSpan * fullSpanReciprocal; - QRect drawRectL( 0, height - peak_L, width, peak_L ); // Source and target are identical - painter.drawPixmap( drawRectL, *m_leds, drawRectL ); - - float const persistentLeftPeakDBFS = ampToDbfs(qMax(0.0001, m_persistentPeak_L)); - int persistentPeak_L = height * (1 - (persistentLeftPeakDBFS - minDB) * fullSpanReciprocal); - // the LED's have a 4px padding and we don't want the peaks - // to draw on the fader background - if( persistentPeak_L <= 4 ) + if (mappedPeakL > mappedMinPeak) { - persistentPeak_L = 4; - } - if( persistentLeftPeakDBFS > minDB ) - { - QColor const & peakColor = clips(m_persistentPeak_L) ? peakRed() : - persistentLeftPeakDBFS >= -6 ? peakYellow() : peakGreen(); - painter.fillRect( QRect( 2, persistentPeak_L, 7, 1 ), peakColor ); + QPainterPath leftMeterPath; + leftMeterPath.addRoundedRect(computeLevelRect(leftMeterRect, mappedPeakL), radius, radius); + painter.fillPath(leftMeterPath, linearGrad); } + // Draw left peaks + if (mappedPersistentPeakL > mappedMinPeak) + { + const auto peakRectL = computePeakRect(leftMeterRect, mappedPersistentPeakL); + painter.fillRect(peakRectL, linearGrad); + } // Draw right levels - float const rightSpan = ampToDbfs(qMax(0.0001, m_fPeakValue_R)) - minDB; - int peak_R = height * rightSpan * fullSpanReciprocal; - QRect const drawRectR( center, height - peak_R, width, peak_R ); // Source and target are identical - painter.drawPixmap( drawRectR, *m_leds, drawRectR ); - - float const persistentRightPeakDBFS = ampToDbfs(qMax(0.0001, m_persistentPeak_R)); - int persistentPeak_R = height * (1 - (persistentRightPeakDBFS - minDB) * fullSpanReciprocal); - // the LED's have a 4px padding and we don't want the peaks - // to draw on the fader background - if( persistentPeak_R <= 4 ) + if (mappedPeakR > mappedMinPeak) { - persistentPeak_R = 4; - } - if( persistentRightPeakDBFS > minDB ) - { - QColor const & peakColor = clips(m_persistentPeak_R) ? peakRed() : - persistentRightPeakDBFS >= -6 ? peakYellow() : peakGreen(); - painter.fillRect( QRect( 14, persistentPeak_R, 7, 1 ), peakColor ); - } -} - -void Fader::paintLinearLevels(QPaintEvent * ev, QPainter & painter) -{ - // peak leds - //float fRange = abs( m_fMaxPeak ) + abs( m_fMinPeak ); - - int height = m_back->height(); - int width = m_back->width() / 2; - int center = m_back->width() - width; - - int peak_L = calculateDisplayPeak( m_fPeakValue_L - m_fMinPeak ); - int persistentPeak_L = qMax( 3, calculateDisplayPeak( m_persistentPeak_L - m_fMinPeak ) ); - painter.drawPixmap( QRect( 0, peak_L, width, height - peak_L ), *m_leds, QRect( 0, peak_L, width, height - peak_L ) ); - - if( m_persistentPeak_L > 0.05 ) - { - painter.fillRect( QRect( 2, persistentPeak_L, 7, 1 ), ( m_persistentPeak_L < 1.0 ) - ? peakGreen() - : peakRed() ); + QPainterPath rightMeterPath; + rightMeterPath.addRoundedRect(computeLevelRect(rightMeterRect, mappedPeakR), radius, radius); + painter.fillPath(rightMeterPath, linearGrad); } - int peak_R = calculateDisplayPeak( m_fPeakValue_R - m_fMinPeak ); - int persistentPeak_R = qMax( 3, calculateDisplayPeak( m_persistentPeak_R - m_fMinPeak ) ); - painter.drawPixmap( QRect( center, peak_R, width, height - peak_R ), *m_leds, QRect( center, peak_R, width, height - peak_R ) ); - - if( m_persistentPeak_R > 0.05 ) + // Draw right peaks + if (mappedPersistentPeakR > mappedMinPeak) { - painter.fillRect( QRect( 14, persistentPeak_R, 7, 1 ), ( m_persistentPeak_R < 1.0 ) - ? peakGreen() - : peakRed() ); + const auto peakRectR = computePeakRect(rightMeterRect, mappedPersistentPeakR); + painter.fillRect(peakRectR, linearGrad); } + + painter.restore(); } - -QColor const & Fader::peakGreen() const -{ - return m_peakGreen; -} - -QColor const & Fader::peakRed() const -{ - return m_peakRed; -} - -QColor const & Fader::peakYellow() const -{ - return m_peakYellow; -} - -void Fader::setPeakGreen( const QColor & c ) -{ - m_peakGreen = c; -} - -void Fader::setPeakRed( const QColor & c ) -{ - m_peakRed = c; -} - -void Fader::setPeakYellow( const QColor & c ) -{ - m_peakYellow = c; -} - - } // namespace lmms::gui diff --git a/src/gui/widgets/FloatModelEditorBase.cpp b/src/gui/widgets/FloatModelEditorBase.cpp new file mode 100644 index 000000000..ebd0d3d9d --- /dev/null +++ b/src/gui/widgets/FloatModelEditorBase.cpp @@ -0,0 +1,475 @@ +/* + * 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; + } + } + } + + // Handle "natural" scrolling, which is common on trackpads and touch devices + if (we->inverted()) { + direction = -direction; + } + + // 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()) + { + auto const initalValue = model()->getRoundedValue() / 100.0; + auto const initialDbValue = initalValue > 0. ? ampToDbfs(initalValue) : -96; + + new_val = QInputDialog::getDouble( + this, tr("Set value"), + tr("Please enter a new value between " + "-96.0 dBFS and 6.0 dBFS:"), + initialDbValue, -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()) + { + auto const valueToVolumeRatio = model()->getRoundedValue() / volumeRatio(); + return m_description.trimmed() + ( + valueToVolumeRatio == 0. + ? QString(" -∞ dBFS") + : QString(" %1 dBFS").arg(ampToDbfs(valueToVolumeRatio), 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/Graph.cpp b/src/gui/widgets/Graph.cpp index 9972209a8..0781d4f11 100644 --- a/src/gui/widgets/Graph.cpp +++ b/src/gui/widgets/Graph.cpp @@ -26,6 +26,7 @@ #include #include "Graph.h" +#include "SampleLoader.h" #include "StringPairDrag.h" #include "SampleBuffer.h" #include "Oscillator.h" @@ -588,21 +589,16 @@ void graphModel::setWaveToNoise() QString graphModel::setWaveToUser() { - auto sampleBuffer = new SampleBuffer; - QString fileName = sampleBuffer->openAndSetWaveformFile(); + QString fileName = gui::SampleLoader::openWaveformFile(); if( fileName.isEmpty() == false ) { - sampleBuffer->dataReadLock(); + auto sampleBuffer = gui::SampleLoader::createBufferFromFile(fileName); for( int i = 0; i < length(); i++ ) { - m_samples[i] = sampleBuffer->userWaveSample( - i / static_cast( length() ) ); + m_samples[i] = Oscillator::userWaveSample(sampleBuffer.get(), i / static_cast(length())); } - sampleBuffer->dataUnlock(); } - sharedObject::unref( sampleBuffer ); - emit samplesChanged( 0, length() - 1 ); return fileName; }; @@ -647,11 +643,10 @@ void graphModel::convolve(const float *convolution, // store values in temporary array QVector temp = m_samples; const int graphLength = length(); - float sum; // make a cyclic convolution for ( int i = 0; i < graphLength; i++ ) { - sum = 0; + float sum = 0.0f; for ( int j = 0; j < convolutionLength; j++ ) { sum += convolution[j] * temp[( i + j ) % graphLength]; diff --git a/src/gui/widgets/GroupBox.cpp b/src/gui/widgets/GroupBox.cpp index b5187de25..e7d78acb9 100644 --- a/src/gui/widgets/GroupBox.cpp +++ b/src/gui/widgets/GroupBox.cpp @@ -72,13 +72,23 @@ void GroupBox::modelChanged() } +bool GroupBox::ledButtonShown() const +{ + return m_led->isVisible(); +} + + +void GroupBox::setLedButtonShown(bool value) +{ + m_led->setVisible(value); +} void GroupBox::mousePressEvent( QMouseEvent * _me ) { - if( _me->y() > 1 && _me->y() < 13 && _me->button() == Qt::LeftButton ) + if (ledButtonShown() && _me->y() > 1 && _me->y() < 13 && _me->button() == Qt::LeftButton) { - model()->setValue( !model()->value() ); + model()->setValue(!model()->value()); } } @@ -101,8 +111,10 @@ void GroupBox::paintEvent( QPaintEvent * pe ) // draw text p.setPen( palette().color( QPalette::Active, QPalette::Text ) ); - p.setFont( pointSize<8>( font() ) ); - p.drawText( 22, m_titleBarHeight, m_caption ); + p.setFont(adjustedToPixelSize(font(), 10)); + + int const captionX = ledButtonShown() ? 22 : 6; + p.drawText(captionX, m_titleBarHeight, m_caption); } diff --git a/src/gui/widgets/Knob.cpp b/src/gui/widgets/Knob.cpp index 56cf29345..d282f72c2 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 ) @@ -173,7 +139,7 @@ void Knob::setLabel( const QString & txt ) if( m_knobPixmap ) { setFixedSize(qMax( m_knobPixmap->width(), - horizontalAdvance(QFontMetrics(pointSizeF(font(), 6.5)), m_label)), + horizontalAdvance(QFontMetrics(adjustedToPixelSize(font(), 10)), m_label)), m_knobPixmap->height() + 10); } @@ -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 ); @@ -682,7 +459,7 @@ void Knob::paintEvent( QPaintEvent * _me ) { if (!m_isHtmlLabel) { - p.setFont(pointSizeF(p.font(), 6.5)); + p.setFont(adjustedToPixelSize(p.font(), 10)); p.setPen(textColor()); p.drawText(width() / 2 - horizontalAdvance(p.fontMetrics(), m_label) / 2, @@ -690,208 +467,14 @@ void Knob::paintEvent( QPaintEvent * _me ) } else { - m_tdRenderer->setDefaultFont(pointSizeF(p.font(), 6.5)); + // TODO setHtmlLabel is never called so this will never be executed. Remove functionality? + m_tdRenderer->setDefaultFont(adjustedToPixelSize(p.font(), 10)); p.translate((width() - m_tdRenderer->idealWidth()) / 2, (height() - m_tdRenderer->pageSize().height()) / 2); m_tdRenderer->drawContents(&p); } } } - - - -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 c7e20467a..c71d66568 100644 --- a/src/gui/widgets/LcdFloatSpinBox.cpp +++ b/src/gui/widgets/LcdFloatSpinBox.cpp @@ -245,7 +245,7 @@ void LcdFloatSpinBox::paintEvent(QPaintEvent*) // Label if (!m_label.isEmpty()) { - p.setFont(pointSizeF(p.font(), 6.5)); + p.setFont(adjustedToPixelSize(p.font(), 10)); p.setPen(m_wholeDisplay.textShadowColor()); p.drawText(width() / 2 - p.fontMetrics().boundingRect(m_label).width() / 2 + 1, height(), m_label); p.setPen(m_wholeDisplay.textColor()); diff --git a/src/gui/widgets/LcdSpinBox.cpp b/src/gui/widgets/LcdSpinBox.cpp index b53d7ddb5..3f12360cc 100644 --- a/src/gui/widgets/LcdSpinBox.cpp +++ b/src/gui/widgets/LcdSpinBox.cpp @@ -141,7 +141,9 @@ void LcdSpinBox::mouseReleaseEvent(QMouseEvent*) void LcdSpinBox::wheelEvent(QWheelEvent * we) { we->accept(); - model()->setValue(model()->value() + ((we->angleDelta().y() > 0) ? 1 : -1) * model()->step()); + const int direction = (we->angleDelta().y() > 0 ? 1 : -1) * (we->inverted() ? -1 : 1); + + model()->setValue(model()->value() + direction * model()->step()); emit manualChange(); } diff --git a/src/gui/widgets/LcdWidget.cpp b/src/gui/widgets/LcdWidget.cpp index b8afcd46d..7370a939f 100644 --- a/src/gui/widgets/LcdWidget.cpp +++ b/src/gui/widgets/LcdWidget.cpp @@ -66,17 +66,6 @@ LcdWidget::LcdWidget(int numDigits, const QString& style, QWidget* parent, const initUi( name, style ); } - - - -LcdWidget::~LcdWidget() -{ - delete m_lcdPixmap; -} - - - - void LcdWidget::setValue(int value) { QString s = m_textForValue[value]; @@ -89,20 +78,26 @@ void LcdWidget::setValue(int value) } } - m_display = s; + if (m_display != s) + { + m_display = s; - update(); + update(); + } } void LcdWidget::setValue(float value) { - if (value < 0 && value > -1) + if (-1 < value && value < 0) { QString s = QString::number(static_cast(value)); s.prepend('-'); - m_display = s; - update(); + if (m_display != s) + { + m_display = s; + update(); + } } else { @@ -162,11 +157,8 @@ void LcdWidget::paintEvent( QPaintEvent* ) { p.translate(margin, margin); // Left Margin - p.drawPixmap( - cellRect, - *m_lcdPixmap, - QRect(QPoint(charsPerPixmap * m_cellWidth, isEnabled() ? 0 : m_cellHeight), cellSize) - ); + p.drawPixmap(cellRect, m_lcdPixmap, + QRect(QPoint(charsPerPixmap * m_cellWidth, isEnabled() ? 0 : m_cellHeight), cellSize)); p.translate(m_marginWidth, 0); } @@ -174,8 +166,7 @@ void LcdWidget::paintEvent( QPaintEvent* ) // Padding for( int i=0; i < m_numDigits - m_display.length(); i++ ) { - p.drawPixmap( cellRect, *m_lcdPixmap, - QRect( QPoint( 10 * m_cellWidth, isEnabled()?0:m_cellHeight) , cellSize ) ); + p.drawPixmap(cellRect, m_lcdPixmap, QRect(QPoint(10 * m_cellWidth, isEnabled() ? 0 : m_cellHeight), cellSize)); p.translate( m_cellWidth, 0 ); } @@ -189,19 +180,14 @@ void LcdWidget::paintEvent( QPaintEvent* ) else val = 10; } - p.drawPixmap( cellRect, *m_lcdPixmap, - QRect( QPoint( val*m_cellWidth, - isEnabled()?0:m_cellHeight ), - cellSize ) ); + p.drawPixmap(cellRect, m_lcdPixmap, QRect(QPoint(val * m_cellWidth, isEnabled() ? 0 : m_cellHeight), cellSize)); p.translate( m_cellWidth, 0 ); } // Right Margin - p.drawPixmap(QRect(0, 0, m_seamlessRight ? 0 : m_marginWidth - 1, m_cellHeight), - *m_lcdPixmap, + p.drawPixmap(QRect(0, 0, m_seamlessRight ? 0 : m_marginWidth - 1, m_cellHeight), m_lcdPixmap, QRect(charsPerPixmap * m_cellWidth, isEnabled() ? 0 : m_cellHeight, m_cellWidth / 2, m_cellHeight)); - p.restore(); // Border @@ -223,7 +209,7 @@ void LcdWidget::paintEvent( QPaintEvent* ) // Label if( !m_label.isEmpty() ) { - p.setFont( pointSizeF( p.font(), 6.5 ) ); + p.setFont(adjustedToPixelSize(p.font(), 10)); p.setPen( textShadowColor() ); p.drawText(width() / 2 - horizontalAdvance(p.fontMetrics(), m_label) / 2 + 1, @@ -275,7 +261,7 @@ void LcdWidget::updateSize() setFixedSize( qMax( m_cellWidth * m_numDigits + marginX1 + marginX2, - horizontalAdvance(QFontMetrics(pointSizeF(font(), 6.5)), m_label) + horizontalAdvance(QFontMetrics(adjustedToPixelSize(font(), 10)), m_label) ), m_cellHeight + (2 * marginY) + 9 ); @@ -294,12 +280,12 @@ void LcdWidget::initUi(const QString& name , const QString& style) setWindowTitle( name ); // We should make a factory for these or something. - //m_lcdPixmap = new QPixmap( embed::getIconPixmap( QString( "lcd_" + style ).toUtf8().constData() ) ); - //m_lcdPixmap = new QPixmap( embed::getIconPixmap( "lcd_19green" ) ); // TODO!! - m_lcdPixmap = new QPixmap( embed::getIconPixmap( QString( "lcd_" + style ).toUtf8().constData() ) ); + //m_lcdPixmap = embed::getIconPixmap(QString("lcd_" + style).toUtf8().constData()); + //m_lcdPixmap = embed::getIconPixmap("lcd_19green"); // TODO!! - m_cellWidth = m_lcdPixmap->size().width() / LcdWidget::charsPerPixmap; - m_cellHeight = m_lcdPixmap->size().height() / 2; + m_lcdPixmap = embed::getIconPixmap(QString("lcd_" + style).toUtf8().constData()); + m_cellWidth = m_lcdPixmap.size().width() / LcdWidget::charsPerPixmap; + m_cellHeight = m_lcdPixmap.size().height() / 2; m_marginWidth = m_cellWidth / 2; diff --git a/src/gui/widgets/LedCheckBox.cpp b/src/gui/widgets/LedCheckBox.cpp index 0c16bf391..42e49a8ae 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,22 +56,11 @@ 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 ) { } - - -LedCheckBox::~LedCheckBox() -{ - delete m_ledOnPixmap; - delete m_ledOffPixmap; -} - - - - void LedCheckBox::setText( const QString &s ) { m_text = s; @@ -80,24 +70,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() ); } @@ -107,11 +89,11 @@ void LedCheckBox::initUi( LedColor _color ) { setCheckable( true ); - m_ledOnPixmap = new QPixmap( embed::getIconPixmap( - names[static_cast(_color)].toUtf8().constData() ) ); - m_ledOffPixmap = new QPixmap( embed::getIconPixmap( "led_off" ) ); + m_ledOnPixmap = embed::getIconPixmap(names[static_cast(_color)].toUtf8().constData()); + m_ledOffPixmap = embed::getIconPixmap("led_off"); + + if (m_legacyMode){ setFont(adjustedToPixelSize(font(), 10)); } - setFont( pointSize<7>( font() ) ); setText( m_text ); } @@ -120,9 +102,38 @@ 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(adjustedToPixelSize(font(), 10)); + + p.drawPixmap(0, 0, model()->value() ? m_ledOnPixmap : 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); + + auto 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/MeterDialog.cpp b/src/gui/widgets/MeterDialog.cpp index ced08382e..d01dca9a8 100644 --- a/src/gui/widgets/MeterDialog.cpp +++ b/src/gui/widgets/MeterDialog.cpp @@ -30,7 +30,6 @@ #include "MeterDialog.h" #include "MeterModel.h" -#include "gui_templates.h" #include "LcdSpinBox.h" namespace lmms::gui @@ -60,7 +59,6 @@ MeterDialog::MeterDialog( QWidget * _parent, bool _simple ) : { auto num_label = new QLabel(tr("Meter Numerator"), num); QFont f = num_label->font(); - num_label->setFont( pointSize<7>( f ) ); num_layout->addSpacing( 5 ); num_layout->addWidget( num_label ); } @@ -84,7 +82,6 @@ MeterDialog::MeterDialog( QWidget * _parent, bool _simple ) : { auto den_label = new QLabel(tr("Meter Denominator"), den); QFont f = den_label->font(); - den_label->setFont( pointSize<7>( f ) ); den_layout->addSpacing( 5 ); den_layout->addWidget( den_label ); } diff --git a/src/gui/widgets/MixerLineLcdSpinBox.cpp b/src/gui/widgets/MixerChannelLcdSpinBox.cpp similarity index 82% rename from src/gui/widgets/MixerLineLcdSpinBox.cpp rename to src/gui/widgets/MixerChannelLcdSpinBox.cpp index 06eb823c0..8a67394de 100644 --- a/src/gui/widgets/MixerLineLcdSpinBox.cpp +++ b/src/gui/widgets/MixerChannelLcdSpinBox.cpp @@ -1,5 +1,5 @@ /* - * MixerLineLcdSpinBox.cpp - a specialization of LcdSpnBox for setting mixer channels + * MixerChannelLcdSpinBox.cpp - a specialization of LcdSpnBox for setting mixer channels * * Copyright (c) 2004-2014 Tobias Doerffel * @@ -22,7 +22,7 @@ * */ -#include "MixerLineLcdSpinBox.h" +#include "MixerChannelLcdSpinBox.h" #include "CaptionMenu.h" #include "MixerView.h" @@ -33,14 +33,14 @@ namespace lmms::gui { -void MixerLineLcdSpinBox::setTrackView(TrackView * tv) +void MixerChannelLcdSpinBox::setTrackView(TrackView * tv) { m_tv = tv; } -void MixerLineLcdSpinBox::mouseDoubleClickEvent(QMouseEvent* event) +void MixerChannelLcdSpinBox::mouseDoubleClickEvent(QMouseEvent* event) { - getGUI()->mixerView()->setCurrentMixerLine(model()->value()); + getGUI()->mixerView()->setCurrentMixerChannel(model()->value()); getGUI()->mixerView()->parentWidget()->show(); getGUI()->mixerView()->show();// show Mixer window @@ -48,7 +48,7 @@ void MixerLineLcdSpinBox::mouseDoubleClickEvent(QMouseEvent* event) //engine::getMixerView()->raise(); } -void MixerLineLcdSpinBox::contextMenuEvent(QContextMenuEvent* event) +void MixerChannelLcdSpinBox::contextMenuEvent(QContextMenuEvent* event) { // for the case, the user clicked right while pressing left mouse- // button, the context-menu appears while mouse-cursor is still hidden diff --git a/src/gui/widgets/NStateButton.cpp b/src/gui/widgets/NStateButton.cpp index 4fbcc0d65..4dc0252fc 100644 --- a/src/gui/widgets/NStateButton.cpp +++ b/src/gui/widgets/NStateButton.cpp @@ -67,34 +67,27 @@ void NStateButton::addState( const QPixmap & _pm, const QString & _tooltip ) -void NStateButton::changeState( int _n ) +void NStateButton::changeState(int state) { - if( _n >= 0 && _n < (int) m_states.size() ) + if (state >= 0 && state < m_states.size() && state != m_curState) { - m_curState = _n; + m_curState = state; - const QString & _tooltip = - ( m_states[m_curState].second != "" ) ? - m_states[m_curState].second : - m_generalToolTip; - setToolTip(_tooltip); + const auto& [icon, tooltip] = m_states[m_curState]; + setToolTip(tooltip.isEmpty() ? m_generalToolTip : tooltip); + setIcon(icon); - setIcon( m_states[m_curState].first ); - - emit changedState( m_curState ); + emit changedState(m_curState); } } - - -void NStateButton::mousePressEvent( QMouseEvent * _me ) +void NStateButton::mousePressEvent(QMouseEvent* me) { - if( _me->button() == Qt::LeftButton && m_states.size() ) + if (me->button() == Qt::LeftButton && !m_states.empty()) { - changeState( ( ++m_curState ) % m_states.size() ); + changeState((m_curState + 1) % m_states.size()); } - ToolButton::mousePressEvent( _me ); + ToolButton::mousePressEvent(me); } - -} // namespace lmms::gui \ No newline at end of file +} // namespace lmms::gui diff --git a/src/gui/widgets/Oscilloscope.cpp b/src/gui/widgets/Oscilloscope.cpp index bec45c162..8c342cc34 100644 --- a/src/gui/widgets/Oscilloscope.cpp +++ b/src/gui/widgets/Oscilloscope.cpp @@ -45,7 +45,9 @@ Oscilloscope::Oscilloscope( QWidget * _p ) : m_background( embed::getIconPixmap( "output_graph" ) ), m_points( new QPointF[Engine::audioEngine()->framesPerPeriod()] ), m_active( false ), - m_normalColor(71, 253, 133), + m_leftChannelColor(71, 253, 133), + m_rightChannelColor(71, 253, 133), + m_otherChannelsColor(71, 253, 133), m_clippingColor(255, 64, 64) { setFixedSize( m_background.width(), m_background.height() ); @@ -112,14 +114,34 @@ void Oscilloscope::setActive( bool _active ) } -QColor const & Oscilloscope::normalColor() const +QColor const & Oscilloscope::leftChannelColor() const { - return m_normalColor; + return m_leftChannelColor; } -void Oscilloscope::setNormalColor(QColor const & normalColor) +void Oscilloscope::setLeftChannelColor(QColor const & leftChannelColor) { - m_normalColor = normalColor; + m_leftChannelColor = leftChannelColor; +} + +QColor const & Oscilloscope::rightChannelColor() const +{ + return m_rightChannelColor; +} + +void Oscilloscope::setRightChannelColor(QColor const & rightChannelColor) +{ + m_rightChannelColor = rightChannelColor; +} + +QColor const & Oscilloscope::otherChannelsColor() const +{ + return m_otherChannelsColor; +} + +void Oscilloscope::setOtherChannelsColor(QColor const & otherChannelsColor) +{ + m_otherChannelsColor = otherChannelsColor; } QColor const & Oscilloscope::clippingColor() const @@ -143,27 +165,31 @@ void Oscilloscope::paintEvent( QPaintEvent * ) { AudioEngine const * audioEngine = Engine::audioEngine(); - float master_output = audioEngine->masterGain(); + float masterOutput = audioEngine->masterGain(); const fpp_t frames = audioEngine->framesPerPeriod(); AudioEngine::StereoSample peakValues = audioEngine->getPeakValues(m_buffer, frames); - const float max_level = qMax( peakValues.left, peakValues.right ); - // Set the color of the line according to the maximum level - float const maxLevelWithAppliedMasterGain = max_level * master_output; - p.setPen(QPen(determineLineColor(maxLevelWithAppliedMasterGain), 0.7)); + auto const leftChannelClips = clips(peakValues.left * masterOutput); + auto const rightChannelClips = clips(peakValues.right * masterOutput); p.setRenderHint( QPainter::Antialiasing ); // now draw all that stuff int w = width() - 4; const qreal xd = static_cast(w) / frames; - const qreal half_h = -( height() - 6 ) / 3.0 * static_cast(master_output) - 1; + const qreal half_h = -(height() - 6) / 3.0 * static_cast(masterOutput) - 1; int x_base = 2; const qreal y_base = height() / 2 - 0.5; + qreal const width = 0.7; for( ch_cnt_t ch = 0; ch < DEFAULT_CHANNELS; ++ch ) { + QColor color = ch == 0 ? (leftChannelClips ? clippingColor() : leftChannelColor()) : // Check left channel + ch == 1 ? (rightChannelClips ? clippingColor() : rightChannelColor()) : // Check right channel + otherChannelsColor(); // Any other channel + p.setPen(QPen(color, width)); + for( int frame = 0; frame < frames; ++frame ) { sample_t const clippedSample = AudioEngine::clip(m_buffer[frame][ch]); @@ -177,7 +203,7 @@ void Oscilloscope::paintEvent( QPaintEvent * ) else { p.setPen( QColor( 192, 192, 192 ) ); - p.setFont( pointSize<7>( p.font() ) ); + p.setFont(adjustedToPixelSize(p.font(), 10)); p.drawText( 6, height()-5, tr( "Click to enable" ) ); } } @@ -193,17 +219,9 @@ void Oscilloscope::mousePressEvent( QMouseEvent * _me ) } } - -QColor const & Oscilloscope::determineLineColor(float level) const +bool Oscilloscope::clips(float level) const { - if( level <= 1.0f ) - { - return normalColor(); - } - else - { - return clippingColor(); - } + return level > 1.0f; } diff --git a/src/gui/widgets/PixmapButton.cpp b/src/gui/widgets/PixmapButton.cpp index 13c09c52e..069acad56 100644 --- a/src/gui/widgets/PixmapButton.cpp +++ b/src/gui/widgets/PixmapButton.cpp @@ -50,20 +50,15 @@ PixmapButton::PixmapButton( QWidget * _parent, const QString & _name ) : -void PixmapButton::paintEvent( QPaintEvent * ) +void PixmapButton::paintEvent(QPaintEvent*) { - QPainter p( this ); + QPainter p(this); - if( ( model() != nullptr && model()->value() ) || m_pressed ) + QPixmap* pixmapToDraw = isActive() ? &m_activePixmap : &m_inactivePixmap; + + if (!pixmapToDraw->isNull()) { - if( !m_activePixmap.isNull() ) - { - p.drawPixmap( 0, 0, m_activePixmap ); - } - } - else if( !m_inactivePixmap.isNull() ) - { - p.drawPixmap( 0, 0, m_inactivePixmap ); + p.drawPixmap(0, 0, *pixmapToDraw); } } @@ -129,15 +124,20 @@ void PixmapButton::setInactiveGraphic( const QPixmap & _pm, bool _update ) QSize PixmapButton::sizeHint() const { - if( ( model() != nullptr && model()->value() ) || m_pressed ) + if (isActive()) { - return m_activePixmap.size() / devicePixelRatio(); + return m_activePixmap.size(); } else { - return m_inactivePixmap.size() / devicePixelRatio(); + return m_inactivePixmap.size(); } } +bool PixmapButton::isActive() const +{ + return (model() != nullptr && model()->value()) || m_pressed; +} + } // namespace lmms::gui diff --git a/src/gui/widgets/TabBar.cpp b/src/gui/widgets/TabBar.cpp index e29494551..d77573a92 100644 --- a/src/gui/widgets/TabBar.cpp +++ b/src/gui/widgets/TabBar.cpp @@ -25,7 +25,6 @@ #include "TabBar.h" #include "TabButton.h" -#include "gui_templates.h" namespace lmms::gui @@ -90,8 +89,6 @@ TabButton * TabBar::addTab( QWidget * _w, const QString & _text, int _id, _w->setFixedSize( _w->parentWidget()->size() ); } - b->setFont( pointSize<8>( b->font() ) ); - return( b ); } diff --git a/src/gui/widgets/TabWidget.cpp b/src/gui/widgets/TabWidget.cpp index 5ab56fee7..a370c1ea9 100644 --- a/src/gui/widgets/TabWidget.cpp +++ b/src/gui/widgets/TabWidget.cpp @@ -38,18 +38,19 @@ namespace lmms::gui { -TabWidget::TabWidget(const QString & caption, QWidget * parent, bool usePixmap, +TabWidget::TabWidget(const QString& caption, QWidget* parent, bool usePixmap, bool resizable) : - QWidget( parent ), - m_resizable( resizable ), - m_activeTab( 0 ), - m_caption( caption ), - m_usePixmap( usePixmap ), - m_tabText( 0, 0, 0 ), - m_tabTitleText( 0, 0, 0 ), - m_tabSelected( 0, 0, 0 ), - m_tabBackground( 0, 0, 0 ), - m_tabBorder( 0, 0, 0 ) + QWidget(parent), + m_resizable(resizable), + m_activeTab(0), + m_caption(caption), + m_usePixmap(usePixmap), + m_tabText(0, 0, 0), + m_tabTitleText(0, 0, 0), + m_tabSelected(0, 0, 0), + m_tabTextSelected(0, 0, 0), + m_tabBackground(0, 0, 0), + m_tabBorder(0, 0, 0) { // Create taller tabbar when it's to display artwork tabs @@ -57,24 +58,22 @@ TabWidget::TabWidget(const QString & caption, QWidget * parent, bool usePixmap, m_tabheight = caption.isEmpty() ? m_tabbarHeight - 3 : m_tabbarHeight - 4; - setFont( pointSize<8>( font() ) ); + setFont(adjustedToPixelSize(font(), 10)); - setAutoFillBackground( true ); - QColor bg_color = QApplication::palette().color( QPalette::Active, QPalette::Window ). darker( 132 ); + setAutoFillBackground(true); + QColor bg_color = QApplication::palette().color(QPalette::Active, QPalette::Window).darker(132); QPalette pal = palette(); - pal.setColor( QPalette::Window, bg_color ); - setPalette( pal ); + pal.setColor(QPalette::Window, bg_color); + setPalette(pal); } -void TabWidget::addTab( QWidget * w, const QString & name, const char *pixmap, int idx ) +void TabWidget::addTab(QWidget* w, const QString& name, const char* pixmap, int idx) { - setFont( pointSize<8>( font() ) ); - // Append tab when position is not given - if( idx < 0/* || m_widgets.contains( idx ) == true*/ ) + if (idx < 0/* || m_widgets.contains(idx) == true*/) { - while( m_widgets.contains( ++idx ) == true ) + while(m_widgets.contains(++idx) == true) { } } @@ -83,19 +82,19 @@ void TabWidget::addTab( QWidget * w, const QString & name, const char *pixmap, i int tab_width = horizontalAdvance(fontMetrics(), name) + 10; // Register new tab - widgetDesc d = { w, pixmap, name, tab_width }; + widgetDesc d = {w, pixmap, name, tab_width}; m_widgets[idx] = d; // Position tab's window if (!m_resizable) { - w->setFixedSize( width() - 4, height() - m_tabbarHeight ); + w->setFixedSize(width() - 4, height() - m_tabbarHeight); } - w->move( 2, m_tabbarHeight - 1 ); + w->move(2, m_tabbarHeight - 1); w->hide(); // Show tab's window if it's active - if( m_widgets.contains( m_activeTab ) ) + if (m_widgets.contains(m_activeTab)) { // make sure new tab doesn't overlap current widget m_widgets[m_activeTab].w->show(); @@ -106,15 +105,15 @@ void TabWidget::addTab( QWidget * w, const QString & name, const char *pixmap, i -void TabWidget::setActiveTab( int idx ) +void TabWidget::setActiveTab(int idx) { - if( m_widgets.contains( idx ) ) + if (m_widgets.contains(idx)) { int old_active = m_activeTab; m_activeTab = idx; m_widgets[m_activeTab].w->raise(); m_widgets[m_activeTab].w->show(); - if( old_active != idx && m_widgets.contains( old_active ) ) + if (old_active != idx && m_widgets.contains(old_active)) { m_widgets[old_active].w->hide(); } @@ -124,42 +123,44 @@ void TabWidget::setActiveTab( int idx ) // Return the index of the tab at position "pos" -int TabWidget::findTabAtPos( const QPoint *pos ) +int TabWidget::findTabAtPos(const QPoint* pos) { - if( pos->y() > 1 && pos->y() < m_tabbarHeight - 1 ) + if (pos->y() > 1 && pos->y() < m_tabbarHeight - 1) { int cx = ((m_caption == "") ? 4 : 14) + horizontalAdvance(fontMetrics(), m_caption); - for( widgetStack::iterator it = m_widgets.begin(); it != m_widgets.end(); ++it ) + for (widgetStack::iterator it = m_widgets.begin(); it != m_widgets.end(); ++it) { - if( pos->x() >= cx && pos->x() <= cx + ( *it ).nwidth ) + int const currentWidgetWidth = it->nwidth; + + if (pos->x() >= cx && pos->x() <= cx + currentWidgetWidth) { - return( it.key() ); + return(it.key()); } - cx += ( *it ).nwidth; + cx += currentWidgetWidth; } } // Haven't found any tab at position "pos" - return( -1 ); + return(-1); } // Overload the QWidget::event handler to display tooltips (from https://doc.qt.io/qt-4.8/qt-widgets-tooltips-example.html) -bool TabWidget::event(QEvent *event) +bool TabWidget::event(QEvent* event) { - if ( event->type() == QEvent::ToolTip ) + if (event->type() == QEvent::ToolTip) { auto helpEvent = static_cast(event); - int idx = findTabAtPos( & helpEvent->pos() ); + int idx = findTabAtPos(& helpEvent->pos()); - if ( idx != -1 ) + if (idx != -1) { // Display tab's tooltip - QToolTip::showText( helpEvent->globalPos(), m_widgets[idx].name ); + QToolTip::showText(helpEvent->globalPos(), m_widgets[idx].name); } else { @@ -177,17 +178,17 @@ bool TabWidget::event(QEvent *event) // Activate tab when clicked -void TabWidget::mousePressEvent( QMouseEvent * me ) +void TabWidget::mousePressEvent(QMouseEvent* me) { // Find index of tab that has been clicked QPoint pos = me->pos(); - int idx = findTabAtPos( &pos ); + int idx = findTabAtPos(&pos); // When found, activate tab that has been clicked - if ( idx != -1 ) + if (idx != -1) { - setActiveTab( idx ); + setActiveTab(idx); update(); return; } @@ -196,7 +197,7 @@ void TabWidget::mousePressEvent( QMouseEvent * me ) -void TabWidget::resizeEvent( QResizeEvent * ) +void TabWidget::resizeEvent(QResizeEvent*) { if (!m_resizable) { @@ -210,28 +211,27 @@ void TabWidget::resizeEvent( QResizeEvent * ) -void TabWidget::paintEvent( QPaintEvent * pe ) +void TabWidget::paintEvent(QPaintEvent* pe) { - QPainter p( this ); - p.setFont( pointSize<7>( font() ) ); + QPainter p(this); + p.setFont(adjustedToPixelSize(font(), 10)); // Draw background QBrush bg_color = p.background(); - p.fillRect( 0, 0, width() - 1, height() - 1, bg_color ); + p.fillRect(0, 0, width() - 1, height() - 1, bg_color); // Draw external borders - p.setPen( tabBorder() ); - p.drawRect( 0, 0, width() - 1, height() - 1 ); + p.setPen(tabBorder()); + p.drawRect(0, 0, width() - 1, height() - 1); // Draw tabs' bar background - p.fillRect( 1, 1, width() - 2, m_tabheight + 2, tabBackground() ); + p.fillRect(1, 1, width() - 2, m_tabheight + 2, tabBackground()); // Draw title, if any - if( ! m_caption.isEmpty() ) + if (!m_caption.isEmpty()) { - p.setFont( pointSize<8>( p.font() ) ); - p.setPen( tabTitleText() ); - p.drawText( 5, 11, m_caption ); + p.setPen(tabTitleText()); + p.drawText(5, 11, m_caption); } // Calculate the tabs' x (tabs are painted next to the caption) @@ -241,47 +241,54 @@ void TabWidget::paintEvent( QPaintEvent * pe ) widgetStack::iterator first = m_widgets.begin(); widgetStack::iterator last = m_widgets.end(); int tab_width = width(); - if ( first != last ) + if (first != last) { - tab_width = ( width() - tab_x_offset ) / std::distance( first, last ); + tab_width = (width() - tab_x_offset) / std::distance(first, last); } // Draw all tabs - p.setPen( tabText() ); - for( widgetStack::iterator it = first ; it != last ; ++it ) + p.setPen(tabText()); + for (widgetStack::iterator it = first ; it != last ; ++it) { + auto & currentWidgetDesc = *it; + // Draw a text tab or a artwork tab. - if( m_usePixmap ) + if (m_usePixmap) { // Fixes tab's width, because original size is only correct for text tabs - ( *it ).nwidth = tab_width; + currentWidgetDesc.nwidth = tab_width; // Get artwork - QPixmap artwork( embed::getIconPixmap( ( *it ).pixmap ) ); + QPixmap artwork(embed::getIconPixmap(currentWidgetDesc.pixmap)); // Highlight active tab - if( it.key() == m_activeTab ) + if (it.key() == m_activeTab) { - p.fillRect( tab_x_offset, 0, ( *it ).nwidth, m_tabbarHeight - 1, tabSelected() ); + p.fillRect(tab_x_offset, 0, currentWidgetDesc.nwidth, m_tabbarHeight - 1, tabSelected()); } // Draw artwork - p.drawPixmap(tab_x_offset + ( ( *it ).nwidth - artwork.width() ) / 2, 1, artwork ); + p.drawPixmap(tab_x_offset + (currentWidgetDesc.nwidth - artwork.width()) / 2, 1, artwork); } else { // Highlight tab when active - if( it.key() == m_activeTab ) + if (it.key() == m_activeTab) { - p.fillRect( tab_x_offset, 2, ( *it ).nwidth - 6, m_tabbarHeight - 4, tabSelected() ); + p.fillRect(tab_x_offset, 2, currentWidgetDesc.nwidth - 6, m_tabbarHeight - 4, tabSelected()); + p.setPen(tabTextSelected()); + p.drawText(tab_x_offset + 3, m_tabheight + 1, currentWidgetDesc.name); + } + else + { + // Draw text + p.setPen(tabText()); + p.drawText(tab_x_offset + 3, m_tabheight + 1, currentWidgetDesc.name); } - - // Draw text - p.drawText( tab_x_offset + 3, m_tabheight + 1, ( *it ).name ); } // Next tab's horizontal position - tab_x_offset += ( *it ).nwidth; + tab_x_offset += currentWidgetDesc.nwidth; } } @@ -289,9 +296,9 @@ void TabWidget::paintEvent( QPaintEvent * pe ) // Switch between tabs with mouse wheel -void TabWidget::wheelEvent( QWheelEvent * we ) +void TabWidget::wheelEvent(QWheelEvent* we) { - if(position(we).y() > m_tabheight) + if (position(we).y() > m_tabheight) { return; } @@ -299,15 +306,15 @@ void TabWidget::wheelEvent( QWheelEvent * we ) we->accept(); int dir = (we->angleDelta().y() < 0) ? 1 : -1; int tab = m_activeTab; - while( tab > -1 && static_cast( tab ) < m_widgets.count() ) + while(tab > -1 && static_cast(tab) < m_widgets.count()) { tab += dir; - if( m_widgets.contains( tab ) ) + if (m_widgets.contains(tab)) { break; } } - setActiveTab( tab ); + setActiveTab(tab); } @@ -363,7 +370,7 @@ QColor TabWidget::tabTitleText() const } // Set the color to be used to draw a TabWidget's title text (if any) -void TabWidget::setTabTitleText( const QColor & c ) +void TabWidget::setTabTitleText(const QColor& c) { m_tabTitleText = c; } @@ -375,7 +382,7 @@ QColor TabWidget::tabText() const } // Set the color to be used to draw a TabWidget's text (if any) -void TabWidget::setTabText( const QColor & c ) +void TabWidget::setTabText(const QColor& c) { m_tabText = c; } @@ -387,11 +394,23 @@ QColor TabWidget::tabSelected() const } // Set the color to be used to highlight a TabWidget'selected tab (if any) -void TabWidget::setTabSelected( const QColor & c ) +void TabWidget::setTabSelected(const QColor& c) { m_tabSelected = c; } +// Return the text color of the selected tab +QColor TabWidget::tabTextSelected() const +{ + return m_tabTextSelected; +} + +// Set the text color of the selected tab +void TabWidget::setTabTextSelected(const QColor& c) +{ + m_tabTextSelected = c; +} + // Return the color to be used for the TabWidget's background QColor TabWidget::tabBackground() const { @@ -399,7 +418,7 @@ QColor TabWidget::tabBackground() const } // Set the color to be used for the TabWidget's background -void TabWidget::setTabBackground( const QColor & c ) +void TabWidget::setTabBackground(const QColor& c) { m_tabBackground = c; } @@ -411,7 +430,7 @@ QColor TabWidget::tabBorder() const } // Set the color to be used for the TabWidget's borders -void TabWidget::setTabBorder( const QColor & c ) +void TabWidget::setTabBorder(const QColor& c) { m_tabBorder = c; } diff --git a/src/gui/widgets/TempoSyncBarModelEditor.cpp b/src/gui/widgets/TempoSyncBarModelEditor.cpp new file mode 100644 index 000000000..5ff2332e0 --- /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/gui/widgets/TimeDisplayWidget.cpp b/src/gui/widgets/TimeDisplayWidget.cpp index 3dad6b1b0..92eaf1efe 100644 --- a/src/gui/widgets/TimeDisplayWidget.cpp +++ b/src/gui/widgets/TimeDisplayWidget.cpp @@ -91,24 +91,25 @@ void TimeDisplayWidget::updateTime() switch( m_displayMode ) { case DisplayMode::MinutesSeconds: - int msec; - msec = s->getMilliseconds(); + { + int msec = s->getMilliseconds(); m_majorLCD.setValue(msec / 60000); m_minorLCD.setValue((msec / 1000) % 60); m_milliSecondsLCD.setValue(msec % 1000); break; - + } case DisplayMode::BarsTicks: - int tick; - tick = s->getPlayPos().getTicks(); + { + int tick = s->getPlayPos().getTicks(); m_majorLCD.setValue((int)(tick / s->ticksPerBar()) + 1); m_minorLCD.setValue((tick % s->ticksPerBar()) / (s->ticksPerBar() / s->getTimeSigModel().getNumerator() ) +1); m_milliSecondsLCD.setValue((tick % s->ticksPerBar()) % (s->ticksPerBar() / s->getTimeSigModel().getNumerator())); break; - - default: break; + } + default: + break; } } diff --git a/src/lmmsconfig.h.in b/src/lmmsconfig.h.in index d130d6fc2..89db21a7b 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 29fda075e..3317ea71b 100644 --- a/src/tracks/InstrumentTrack.cpp +++ b/src/tracks/InstrumentTrack.cpp @@ -233,8 +233,7 @@ void InstrumentTrack::processAudioBuffer( sampleFrame* buf, const fpp_t frames, // We could do that in all other cases as well but the overhead for silence test is bigger than // what we potentially save. While playing a note, a NotePlayHandle-driven instrument will produce sound in // 99 of 100 cases so that test would be a waste of time. - if( m_instrument->flags().testFlag( Instrument::Flag::IsSingleStreamed ) && - MixHelpers::isSilent( buf, frames ) ) + if (m_instrument->isSingleStreamed() && MixHelpers::isSilent(buf, frames)) { // at least pass one silent buffer to allow if( m_silentBuffersProcessed ) @@ -263,7 +262,7 @@ void InstrumentTrack::processAudioBuffer( sampleFrame* buf, const fpp_t frames, // instruments using instrument-play-handles will call this method // without any knowledge about notes, so they pass NULL for n, which // is no problem for us since we just bypass the envelopes+LFOs - if( m_instrument->flags().testFlag( Instrument::Flag::IsSingleStreamed ) == false && n != nullptr ) + if (!m_instrument->isSingleStreamed() && n != nullptr) { const f_cnt_t offset = n->noteOffset(); m_soundShaping.processAudioBuffer( buf + offset, frames - offset, n ); @@ -727,7 +726,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 ) @@ -771,14 +771,17 @@ bool InstrumentTrack::play( const TimePos & _start, const fpp_t _frames, } } - Note * cur_note; - while( nit != notes.end() && - ( cur_note = *nit )->pos() == cur_start ) + while (nit != notes.end() && (*nit)->pos() == cur_start) { - const f_cnt_t note_frames = - cur_note->length().frames( frames_per_tick ); + const auto currentNote = *nit; - NotePlayHandle* notePlayHandle = NotePlayHandleManager::acquire( this, _offset, note_frames, *cur_note ); + // If the note is a Step Note, frames will be 0 so the NotePlayHandle + // plays for the whole length of the sample + const auto noteFrames = currentNote->type() == Note::Type::Step + ? 0 + : currentNote->length().frames(frames_per_tick); + + NotePlayHandle* notePlayHandle = NotePlayHandleManager::acquire(this, _offset, noteFrames, *currentNote); notePlayHandle->setPatternTrack(pattern_track); // are we playing global song? if( _clip_num < 0 ) @@ -855,9 +858,11 @@ void InstrumentTrack::saveTrackSpecificSettings( QDomDocument& doc, QDomElement m_noteStacking.saveState( doc, thisElement ); m_arpeggio.saveState( doc, thisElement ); - // Don't save midi port info if the user chose to. - if (Engine::getSong()->isSavingProject() - && !Engine::getSong()->getSaveOptions().discardMIDIConnections.value()) + // Save the midi port info if we are not in song saving mode, e.g. in + // track cloning mode or if we are in song saving mode and the user + // has chosen to discard the MIDI connections. + if (!Engine::getSong()->isSavingProject() || + !Engine::getSong()->getSaveOptions().discardMIDIConnections.value()) { // Don't save auto assigned midi device connection bool hasAuto = m_hasAutoMidiDev; diff --git a/src/tracks/MidiClip.cpp b/src/tracks/MidiClip.cpp index 490f6e6d0..dfee9a5e6 100644 --- a/src/tracks/MidiClip.cpp +++ b/src/tracks/MidiClip.cpp @@ -25,6 +25,7 @@ #include "MidiClip.h" +#include #include #include "GuiApplication.h" @@ -38,13 +39,6 @@ namespace lmms { -QPixmap * gui::MidiClipView::s_stepBtnOn0 = nullptr; -QPixmap * gui::MidiClipView::s_stepBtnOn200 = nullptr; -QPixmap * gui::MidiClipView::s_stepBtnOff = nullptr; -QPixmap * gui::MidiClipView::s_stepBtnOffLight = nullptr; - - - MidiClip::MidiClip( InstrumentTrack * _instrument_track ) : Clip( _instrument_track ), m_instrumentTrack( _instrument_track ), @@ -174,19 +168,18 @@ TimePos MidiClip::beatClipLength() const for (const auto& note : m_notes) { - if (note->length() < 0) + if (note->type() == Note::Type::Step) { max_length = std::max(max_length, note->pos() + 1); } } - if( m_steps != TimePos::stepsPerBar() ) + if (m_steps != TimePos::stepsPerBar()) { - max_length = m_steps * TimePos::ticksPerBar() / - TimePos::stepsPerBar(); + max_length = m_steps * TimePos::ticksPerBar() / TimePos::stepsPerBar(); } - return TimePos( max_length ).nextFullBar() * TimePos::ticksPerBar(); + return TimePos{max_length}.nextFullBar() * TimePos::ticksPerBar(); } @@ -215,16 +208,30 @@ Note * MidiClip::addNote( const Note & _new_note, const bool _quant_pos ) -void MidiClip::removeNote( Note * _note_to_del ) +NoteVector::const_iterator MidiClip::removeNote(NoteVector::const_iterator it) +{ + instrumentTrack()->lock(); + delete *it; + auto new_it = m_notes.erase(it); + instrumentTrack()->unlock(); + + checkType(); + updateLength(); + + emit dataChanged(); + return new_it; +} + +NoteVector::const_iterator MidiClip::removeNote(Note* note) { instrumentTrack()->lock(); - m_notes.erase(std::remove_if(m_notes.begin(), m_notes.end(), [&](Note* note) + auto it = std::find(m_notes.begin(), m_notes.end(), note); + if (it != m_notes.end()) { - auto shouldRemove = note == _note_to_del; - if (shouldRemove) { delete note; } - return shouldRemove; - }), m_notes.end()); + delete *it; + it = m_notes.erase(it); + } instrumentTrack()->unlock(); @@ -232,16 +239,17 @@ void MidiClip::removeNote( Note * _note_to_del ) updateLength(); emit dataChanged(); + return it; } -// returns a pointer to the note at specified step, or NULL if note doesn't exist - -Note * MidiClip::noteAtStep( int _step ) +// Returns a pointer to the note at specified step, or nullptr if note doesn't exist +Note * MidiClip::noteAtStep(int step) { for (const auto& note : m_notes) { - if (note->pos() == TimePos::stepPosition(_step) && note->length() < 0) + if (note->pos() == TimePos::stepPosition(step) + && note->type() == Note::Type::Step) { return note; } @@ -278,8 +286,10 @@ void MidiClip::clearNotes() Note * MidiClip::addStepNote( int step ) { - return addNote( Note( TimePos( -DefaultTicksPerBar ), - TimePos::stepPosition( step ) ), false ); + Note stepNote = Note(TimePos(DefaultTicksPerBar / 16), TimePos::stepPosition(step)); + stepNote.setType(Note::Type::Step); + + return addNote(stepNote, false); } @@ -351,15 +361,10 @@ void MidiClip::setType( Type _new_clip_type ) void MidiClip::checkType() { - for (auto& note : m_notes) - { - if (note->length() > 0) - { - setType(Type::MelodyClip); - return; - } - } - setType( Type::BeatClip ); + // If all notes are StepNotes, we have a BeatClip + const auto beatClip = std::all_of(m_notes.begin(), m_notes.end(), [](auto note) { return note->type() == Note::Type::Step; }); + + setType(beatClip ? Type::BeatClip : Type::MelodyClip); } @@ -370,9 +375,9 @@ void MidiClip::saveSettings( QDomDocument & _doc, QDomElement & _this ) _this.setAttribute( "type", static_cast(m_clipType) ); _this.setAttribute( "name", name() ); - if( usesCustomClipColor() ) + if (const auto& c = color()) { - _this.setAttribute( "color", color().name() ); + _this.setAttribute("color", c->name()); } // as the target of copied/dragged MIDI clip is always an existing // MIDI clip, we must not store actual position, instead we store -1 @@ -404,17 +409,12 @@ void MidiClip::loadSettings( const QDomElement & _this ) m_clipType = static_cast( _this.attribute( "type" ).toInt() ); setName( _this.attribute( "name" ) ); - - if( _this.hasAttribute( "color" ) ) + + if (_this.hasAttribute("color")) { - useCustomClipColor( true ); - setColor( _this.attribute( "color" ) ); + setColor(QColor{_this.attribute("color")}); } - else - { - useCustomClipColor(false); - } - + if( _this.attribute( "pos" ).toInt() >= 0 ) { movePosition( _this.attribute( "pos" ).toInt() ); diff --git a/src/tracks/SampleTrack.cpp b/src/tracks/SampleTrack.cpp index a55db4249..8f69e78d0 100644 --- a/src/tracks/SampleTrack.cpp +++ b/src/tracks/SampleTrack.cpp @@ -109,10 +109,10 @@ bool SampleTrack::play( const TimePos & _start, const fpp_t _frames, if (!sClip->isPlaying() && (_start >= (sClip->startPosition() + sClip->startTimeOffset()) || sClip->isRecord())) { - auto bufferFramesPerTick = Engine::framesPerTick (sClip->sampleBuffer ()->sampleRate ()); + auto bufferFramesPerTick = Engine::framesPerTick(sClip->sample().sampleRate()); f_cnt_t sampleStart = bufferFramesPerTick * ( _start - sClip->startPosition() - sClip->startTimeOffset() ); f_cnt_t clipFrameLength = bufferFramesPerTick * ( sClip->endPosition() - sClip->startPosition() - sClip->startTimeOffset() ); - f_cnt_t sampleBufferLength = sClip->sampleBuffer()->frames(); + f_cnt_t sampleBufferLength = sClip->sample().sampleSize(); //if the Clip smaller than the sample length we play only until Clip end //else we play the sample to the end but nothing more f_cnt_t samplePlayLength = clipFrameLength > sampleBufferLength ? sampleBufferLength : clipFrameLength; diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index ddf9e2962..9a609922c 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1,34 +1,33 @@ -INCLUDE_DIRECTORIES("${CMAKE_CURRENT_SOURCE_DIR}") -INCLUDE_DIRECTORIES("${CMAKE_CURRENT_BINARY_DIR}") -INCLUDE_DIRECTORIES("${CMAKE_SOURCE_DIR}/include") -INCLUDE_DIRECTORIES("${CMAKE_BINARY_DIR}") -INCLUDE_DIRECTORIES("${CMAKE_BINARY_DIR}/src") +include(CTest) -SET(CMAKE_CXX_STANDARD 17) - -SET(CMAKE_AUTOMOC ON) - -# FIXME: remove this once we export include directories for LMMS -IF(LMMS_BUILD_APPLE) -INCLUDE_DIRECTORIES("/usr/local/include") -ENDIF() - -ADD_EXECUTABLE(tests - EXCLUDE_FROM_ALL - main.cpp - QTestSuite.cpp - $ +set(CMAKE_INCLUDE_CURRENT_DIR ON) +set(CMAKE_AUTOMOC ON) +set(LMMS_TESTS src/core/ArrayVectorTest.cpp src/core/AutomatableModelTest.cpp src/core/MathTest.cpp src/core/ProjectVersionTest.cpp src/core/RelativePathsTest.cpp - src/tracks/AutomationTrackTest.cpp ) -TARGET_COMPILE_DEFINITIONS(tests - PRIVATE $ -) -TARGET_LINK_LIBRARIES(tests ${QT_LIBRARIES} ${QT_QTTEST_LIBRARY}) -TARGET_LINK_LIBRARIES(tests ${LMMS_REQUIRED_LIBS}) + +foreach(LMMS_TEST_SRC IN LISTS LMMS_TESTS) + # TODO CMake 3.20: Use cmake_path + get_filename_component(LMMS_TEST_NAME ${LMMS_TEST_SRC} NAME_WE) + + add_executable(${LMMS_TEST_NAME} $ ${LMMS_TEST_SRC}) + add_test(NAME ${LMMS_TEST_NAME} COMMAND ${LMMS_TEST_NAME}) + + # TODO CMake 3.12: Propagate usage requirements by linking to lmmsobjs + target_include_directories(${LMMS_TEST_NAME} PRIVATE $) + + target_link_libraries(${LMMS_TEST_NAME} PRIVATE + ${LMMS_REQUIRED_LIBS} + ${QT_LIBRARIES} + ${QT_QTTEST_LIBRARY} + ) + + target_compile_features(${LMMS_TEST_NAME} PRIVATE cxx_std_17) + target_compile_definitions(${LMMS_TEST_NAME} PRIVATE $) +endforeach() diff --git a/tests/QTestSuite.cpp b/tests/QTestSuite.cpp deleted file mode 100644 index a5a49fd20..000000000 --- a/tests/QTestSuite.cpp +++ /dev/null @@ -1,19 +0,0 @@ -#include "QTestSuite.h" - -QList QTestSuite::m_suites; - -QTestSuite::QTestSuite(QObject *parent) : QObject(parent) -{ - m_suites << this; -} - -QTestSuite::~QTestSuite() -{ - m_suites.removeAll(this); -} - -QList QTestSuite::suites() -{ - return m_suites; -} - diff --git a/tests/QTestSuite.h b/tests/QTestSuite.h deleted file mode 100644 index 6cd27f5aa..000000000 --- a/tests/QTestSuite.h +++ /dev/null @@ -1,21 +0,0 @@ -#ifndef QTESTSUITE_H -#define QTESTSUITE_H - -#include -#include -#include - -class QTestSuite : public QObject -{ - Q_OBJECT -public: - explicit QTestSuite(QObject *parent = 0); - ~QTestSuite() override; - - static QList suites(); - -private: - static QList m_suites; -}; - -#endif // QTESTSUITE_H diff --git a/tests/main.cpp b/tests/main.cpp deleted file mode 100644 index c1a5b5a10..000000000 --- a/tests/main.cpp +++ /dev/null @@ -1,23 +0,0 @@ -#include "QTestSuite.h" - -#include - -#include - -#include "Engine.h" - -int main(int argc, char* argv[]) -{ - new QCoreApplication(argc, argv); - lmms::Engine::init(true); - - int numsuites = QTestSuite::suites().size(); - qDebug() << ">> Will run" << numsuites << "test suites"; - int failed = 0; - for (QTestSuite*& suite : QTestSuite::suites()) - { - if (QTest::qExec(suite, argc, argv) != 0) { ++failed; } - } - qDebug() << "<<" << failed << "out of"<8--- Script output BEGIN --->8---') print(self.result.stdout) print('--->8--- Script output END --->8---') @@ -102,22 +106,6 @@ with tempfile.TemporaryDirectory() as tmpdir: test.run(0) # exitcode 0 - no errors expected test.expect('0 errors') - with ScriptTest(check_strings) as test: - create_file('data/locale/fr.ts', - '\n' - ' \n' - ' TestClass\n' - ' \n' - ' \n' - ' About LMMS\n' - ' À propos de LMMS\n' - ' \n' - '\n' - '\n') - test.run() - test.expect('Error: data/locale: Source file does not exist: ../../src/core/non-existent.cpp') - test.expect('1 errors') - with ScriptTest(check_strings) as test: create_file('data/locale/fr.ts', '\n' diff --git a/tests/src/core/ArrayVectorTest.cpp b/tests/src/core/ArrayVectorTest.cpp index c72790d1a..9e6ed40b8 100644 --- a/tests/src/core/ArrayVectorTest.cpp +++ b/tests/src/core/ArrayVectorTest.cpp @@ -24,11 +24,11 @@ #include "ArrayVector.h" +#include +#include #include #include -#include "QTestSuite.h" - using lmms::ArrayVector; struct ShouldNotConstruct @@ -59,10 +59,9 @@ struct DestructorCheck bool* destructed; }; -class ArrayVectorTest : QTestSuite +class ArrayVectorTest : public QObject { Q_OBJECT - private slots: void defaultConstructorTest() { @@ -227,9 +226,17 @@ private slots: { { // Self-assignment should not change the contents + //// Please note the following: + //// https://www.open-std.org/jtc1/sc22/wg21/docs/lwg-defects.html#2468 + //// https://gcc.gnu.org/bugzilla/show_bug.cgi?id=81159 auto v = ArrayVector{1, 2, 3}; const auto oldValue = v; +#pragma GCC diagnostic push +#if __GNUC__ >= 13 +# pragma GCC diagnostic ignored "-Wself-move" +#endif v = std::move(v); +#pragma GCC diagnostic pop QCOMPARE(v, oldValue); } { @@ -826,6 +833,7 @@ private slots: QVERIFY(!(e != v)); QVERIFY(g != v); } -} ArrayVectorTests; +}; +QTEST_GUILESS_MAIN(ArrayVectorTest) #include "ArrayVectorTest.moc" diff --git a/tests/src/core/AutomatableModelTest.cpp b/tests/src/core/AutomatableModelTest.cpp index 78b9069b5..6e8a28116 100644 --- a/tests/src/core/AutomatableModelTest.cpp +++ b/tests/src/core/AutomatableModelTest.cpp @@ -22,15 +22,16 @@ * */ -#include "QTestSuite.h" +#include #include "AutomatableModel.h" #include "ComboBoxModel.h" +#include "Engine.h" -class AutomatableModelTest : QTestSuite +class AutomatableModelTest : public QObject { Q_OBJECT - +public: bool m1Changed, m2Changed; void resetChanged() { m1Changed = m2Changed = false; } @@ -41,6 +42,19 @@ private slots: // helper slots private slots: // tests //! Test that upcast and exact casts work, //! but no downcast or any other casts + + void initTestCase() + { + using namespace lmms; + Engine::init(true); + } + + void cleanupTestCase() + { + using namespace lmms; + Engine::destroy(); + } + void CastTests() { using namespace lmms; @@ -100,6 +114,7 @@ private slots: // tests QVERIFY(m2.value()); QVERIFY(!m3.value()); } -} AutomatableModelTests; +}; +QTEST_GUILESS_MAIN(AutomatableModelTest) #include "AutomatableModelTest.moc" diff --git a/tests/src/core/MathTest.cpp b/tests/src/core/MathTest.cpp index 2b6404cfd..00694c44f 100644 --- a/tests/src/core/MathTest.cpp +++ b/tests/src/core/MathTest.cpp @@ -22,13 +22,13 @@ * */ -#include "QTestSuite.h" +#include +#include +#include #include "lmms_math.h" -#include - -class MathTest : QTestSuite +class MathTest : public QObject { Q_OBJECT private slots: @@ -48,6 +48,7 @@ private slots: QCOMPARE(numDigitsAsInt(900000000), 9); QCOMPARE(numDigitsAsInt(-900000000), 10); } -} MathTests; +}; +QTEST_GUILESS_MAIN(MathTest) #include "MathTest.moc" diff --git a/tests/src/core/ProjectVersionTest.cpp b/tests/src/core/ProjectVersionTest.cpp index 387d90056..03b689541 100644 --- a/tests/src/core/ProjectVersionTest.cpp +++ b/tests/src/core/ProjectVersionTest.cpp @@ -22,11 +22,11 @@ * */ -#include "QTestSuite.h" - #include "ProjectVersion.h" -class ProjectVersionTest : QTestSuite +#include + +class ProjectVersionTest : public QObject { Q_OBJECT private slots: @@ -75,6 +75,7 @@ private slots: //An identifier of the form "-x" is non-numeric, not negative QVERIFY(ProjectVersion("1.0.0-alpha.-1") > "1.0.0-alpha.1"); } -} ProjectVersionTests; +}; +QTEST_GUILESS_MAIN(ProjectVersionTest) #include "ProjectVersionTest.moc" diff --git a/tests/src/core/RelativePathsTest.cpp b/tests/src/core/RelativePathsTest.cpp index 3b5d023d0..089ab2e8a 100644 --- a/tests/src/core/RelativePathsTest.cpp +++ b/tests/src/core/RelativePathsTest.cpp @@ -22,15 +22,15 @@ * */ -#include "QTestSuite.h" +#include +#include +#include #include "ConfigManager.h" -#include "SampleBuffer.h" #include "PathUtil.h" +#include "SampleBuffer.h" -#include - -class RelativePathsTest : QTestSuite +class RelativePathsTest : public QObject { Q_OBJECT private slots: @@ -66,6 +66,7 @@ private slots: QCOMPARE(PathUtil::toAbsolute(""), empty); QCOMPARE(PathUtil::toShortestRelative(""), empty); } -} RelativePathTests; +}; +QTEST_GUILESS_MAIN(RelativePathsTest) #include "RelativePathsTest.moc" diff --git a/tests/src/tracks/AutomationTrackTest.cpp b/tests/src/tracks/AutomationTrackTest.cpp index 05f345f8b..b4f6effd9 100644 --- a/tests/src/tracks/AutomationTrackTest.cpp +++ b/tests/src/tracks/AutomationTrackTest.cpp @@ -22,7 +22,7 @@ * */ -#include "QTestSuite.h" +#include #include "QCoreApplication" @@ -39,12 +39,20 @@ #include "Engine.h" #include "Song.h" -class AutomationTrackTest : QTestSuite +class AutomationTrackTest : public QObject { Q_OBJECT private slots: void initTestCase() { + using namespace lmms; + Engine::init(true); + } + + void cleanupTestCase() + { + using namespace lmms; + Engine::destroy(); } void testClipLinear() @@ -148,12 +156,11 @@ private slots: auto song = Engine::getSong(); - InstrumentTrack* instrumentTrack = - dynamic_cast(Track::create(Track::Type::Instrument, song)); + InstrumentTrack instrumentTrack(song); - MidiClip* midiClip = dynamic_cast(instrumentTrack->createClip(0)); - midiClip->changeLength(TimePos(4, 0)); - Note* note = midiClip->addNote(Note(TimePos(4, 0)), false); + MidiClip midiClip(&instrumentTrack); + midiClip.changeLength(TimePos(4, 0)); + Note* note = midiClip.addNote(Note(TimePos(4, 0)), false); note->createDetuning(); DetuningHelper* dh = note->detuning(); @@ -175,10 +182,11 @@ private slots: auto song = Engine::getSong(); auto patternStore = Engine::patternStore(); PatternTrack patternTrack(song); - Track* automationTrack = Track::create(Track::Type::Automation, patternStore); + AutomationTrack automationTrack(patternStore); + automationTrack.createClipsForPattern(patternStore->numOfPatterns() - 1); - QVERIFY(automationTrack->numOfClips()); - AutomationClip* c1 = dynamic_cast(automationTrack->getClip(0)); + QVERIFY(automationTrack.numOfClips()); + auto c1 = dynamic_cast(automationTrack.getClip(0)); QVERIFY(c1); FloatModel model; @@ -232,6 +240,7 @@ private slots: QCOMPARE(song->automatedValuesAt(0)[&model], 50.0f); } -} AutomationTrackTest; +}; +QTEST_GUILESS_MAIN(AutomationTrackTest) #include "AutomationTrackTest.moc" diff --git a/vcpkg.json b/vcpkg.json new file mode 100644 index 000000000..1f6440934 --- /dev/null +++ b/vcpkg.json @@ -0,0 +1,73 @@ +{ + "$schema": "https://raw.githubusercontent.com/microsoft/vcpkg-tool/main/docs/vcpkg.schema.json", + "name": "lmms", + "dependencies": [ + { + "name": "fftw3", + "default-features": false, + "features": [ + "sse", + "sse2" + ] + }, + { + "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 + }, + { + "name": "zlib", + "default-features": false + } + ] +}