diff --git a/.clang-tidy b/.clang-tidy index aff46bba6..5de9376e5 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -2,17 +2,22 @@ Checks: > bugprone-macro-parentheses, bugprone-macro-repeated-side-effects, + modernize-avoid-c-arrays, + modernize-loop-convert, modernize-redundant-void-arg, + modernize-use-auto, modernize-use-bool-literals, modernize-use-emplace, modernize-use-equals-default, modernize-use-equals-delete, modernize-use-override, + modernize-use-using, performance-trivially-destructible, + readability-braces-around-statements, + readability-const-return-type, readability-identifier-naming, readability-misleading-indentation, - readability-simplify-boolean-expr, - readability-braces-around-statements + readability-simplify-boolean-expr WarningsAsErrors: '' HeaderFilterRegex: '' # don't show errors from headers AnalyzeTemporaryDtors: false 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..b08c3ba20 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -14,7 +14,8 @@ jobs: -DUSE_WERROR=ON -DCMAKE_BUILD_TYPE=RelWithDebInfo -DUSE_COMPILE_CACHE=ON - CCACHE_MAXSIZE: 500M + CCACHE_MAXSIZE: 0 + CCACHE_NOCOMPRESS: 1 MAKEFLAGS: -j2 steps: - name: Update and configure Git @@ -38,15 +39,16 @@ 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 @@ -56,29 +58,42 @@ jobs: with: name: linux path: build/lmms-*.AppImage - - name: Print ccache statistics + - name: Trim ccache and print statistics run: | + ccache --cleanup echo "[ccache config]" - ccache -p + ccache --print-config echo "[ccache stats]" - ccache -s + ccache --show-stats + env: + CCACHE_MAXSIZE: 500M macos: name: macos - runs-on: macos-11 + runs-on: macos-12 env: CMAKE_OPTS: >- -DUSE_WERROR=ON -DCMAKE_BUILD_TYPE=RelWithDebInfo -DUSE_COMPILE_CACHE=ON - CCACHE_MAXSIZE: 500M + CCACHE_MAXSIZE: 0 + CCACHE_NOCOMPRESS: 1 MAKEFLAGS: -j3 - DEVELOPER_DIR: /Applications/Xcode_11.7.app/Contents/Developer + DEVELOPER_DIR: /Applications/Xcode_13.1.app/Contents/Developer steps: - name: Check out uses: actions/checkout@v3 with: fetch-depth: 0 submodules: recursive + - name: Clean up Homebrew download cache + run: rm -rf ~/Library/Caches/Homebrew/downloads + - name: Restore Homebrew download cache + uses: actions/cache/restore@v3 + with: + key: n/a - only restore from restore-keys + restore-keys: | + homebrew- + path: ~/Library/Caches/Homebrew/downloads - name: Cache ccache data uses: actions/cache@v3 with: @@ -89,12 +104,16 @@ jobs: path: ~/Library/Caches/ccache - name: Install dependencies run: | - brew install ccache fftw pkg-config libogg libvorbis lame libsndfile \ - libsamplerate jack sdl libgig libsoundio lilv lv2 stk \ - fluid-synth portaudio fltk qt@5 carla + brew bundle install --verbose + npm update -g npm npm install --location=global appdmg + env: + HOMEBREW_NO_AUTO_UPDATE: 1 + HOMEBREW_NO_INSTALL_UPGRADE: 1 + HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK: 1 - name: Configure run: | + ccache --zero-stats mkdir build cmake -S . \ -B build \ @@ -104,10 +123,10 @@ jobs: -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 @@ -117,12 +136,20 @@ jobs: with: name: macos path: build/lmms-*.dmg - - name: Print ccache statistics + - name: Trim ccache and print statistics run: | + ccache --cleanup echo "[ccache config]" - ccache -p + ccache --show-config echo "[ccache stats]" - ccache -s + ccache --show-stats --verbose + env: + CCACHE_MAXSIZE: 500MB + - name: Save Homebrew download cache + uses: actions/cache/save@v3 + with: + key: homebrew-${{ hashFiles('Brewfile.lock.json') }} + path: ~/Library/Caches/Homebrew/downloads mingw: strategy: fail-fast: false @@ -136,7 +163,8 @@ jobs: -DUSE_WERROR=ON -DCMAKE_BUILD_TYPE=RelWithDebInfo -DUSE_COMPILE_CACHE=ON - CCACHE_MAXSIZE: 500M + CCACHE_MAXSIZE: 0 + CCACHE_NOCOMPRESS: 1 MAKEFLAGS: -j2 steps: - name: Update and configure Git @@ -161,12 +189,11 @@ 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 @@ -174,12 +201,15 @@ jobs: with: name: mingw${{ matrix.arch }} path: build/lmms-*.exe - - name: Print ccache statistics + - name: Trim ccache and print statistics run: | + ccache --cleanup echo "[ccache config]" - ccache -p + ccache --print-config echo "[ccache stats]" - ccache -s + ccache --show-stats + env: + CCACHE_MAXSIZE: 500M msvc: strategy: fail-fast: false @@ -189,6 +219,8 @@ jobs: runs-on: windows-2019 env: qt-version: '5.15.2' + CCACHE_MAXSIZE: 0 + CCACHE_NOCOMPRESS: 1 steps: - name: Check out uses: actions/checkout@v3 @@ -196,53 +228,66 @@ jobs: fetch-depth: 0 submodules: recursive - name: Cache vcpkg dependencies + id: cache-deps uses: actions/cache@v3 with: - key: vcpkg-${{ matrix.arch }}-${{ github.ref }}-${{ github.run_id }} + key: vcpkg-${{ matrix.arch }}-${{ hashFiles('vcpkg.json') }} restore-keys: | - vcpkg-${{ matrix.arch }}-${{ github.ref }}- vcpkg-${{ matrix.arch }}- - path: C:\vcpkg\installed + path: build\vcpkg_installed + - name: Cache ccache data + uses: actions/cache@v3 + with: + key: "ccache-${{ github.job }}-${{ matrix.arch }}-${{ github.ref }}\ + -${{ github.run_id }}" + restore-keys: | + ccache-${{ github.job }}-${{ matrix.arch }}-${{ github.ref }}- + ccache-${{ github.job }}-${{ matrix.arch }}- + path: ~\AppData\Local\ccache + - name: Install tools + run: choco install ccache - name: Install 64-bit Qt if: matrix.arch == 'x64' - uses: jurplel/install-qt-action@64bdb64f2c14311d23733a8463e5fcbc65e8775e + uses: jurplel/install-qt-action@b3ea5275e37b734d027040e2c7fe7a10ea2ef946 with: version: ${{ env.qt-version }} arch: win64_msvc2019_64 archives: qtbase qtsvg qttools cache: true - name: Install 32-bit Qt - uses: jurplel/install-qt-action@64bdb64f2c14311d23733a8463e5fcbc65e8775e + uses: jurplel/install-qt-action@b3ea5275e37b734d027040e2c7fe7a10ea2ef946 with: version: ${{ env.qt-version }} arch: win32_msvc2019 archives: qtbase qtsvg qttools cache: true set-env: ${{ matrix.arch == 'x86' }} - - name: Install dependencies - run: | - vcpkg install ` - --triplet=${{ matrix.arch }}-windows ` - --host-triplet=${{ matrix.arch }}-windows ` - --recurse ` - fftw3 fltk fluidsynth[sndfile] libsamplerate libsndfile libstk ` - lilv lv2 portaudio sdl2 - name: Set up build environment - uses: ilammy/msvc-dev-cmd@d8610e2b41c6d0f0c3b4c46dad8df0fd826c68e1 + uses: ilammy/msvc-dev-cmd@cec98b9d092141f74527d0afa6feb2af698cfe89 with: arch: ${{ matrix.arch }} - name: Configure run: | - mkdir build + ccache --zero-stats + mkdir build -Force cmake -S . ` -B build ` -G Ninja ` --toolchain C:/vcpkg/scripts/buildsystems/vcpkg.cmake ` - -DCMAKE_BUILD_TYPE=RelWithDebInfo + -DCMAKE_BUILD_TYPE=RelWithDebInfo ` + -DUSE_COMPILE_CACHE=ON ` + -DVCPKG_TARGET_TRIPLET="${{ matrix.arch }}-windows" ` + -DVCPKG_HOST_TRIPLET="${{ matrix.arch }}-windows" ` + -DVCPKG_MANIFEST_INSTALL="${{ env.should_install_manifest }}" + env: + should_install_manifest: + ${{ steps.cache-deps.outputs.cache-hit == 'true' && 'NO' || 'YES' }} - name: Build run: cmake --build build - - name: Build tests - 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 @@ -250,3 +295,12 @@ jobs: with: name: msvc-${{ matrix.arch }} path: build\lmms-*.exe + - name: Trim ccache and print statistics + run: | + ccache --cleanup + echo "[ccache config]" + ccache --show-config + echo "[ccache stats]" + ccache --show-stats --verbose + env: + CCACHE_MAXSIZE: 500MB diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index 1d893989e..3f7700674 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -21,7 +21,7 @@ jobs: run: tests/scripted/check-namespace shellcheck: runs-on: ubuntu-latest - container: koalaman/shellcheck-alpine:v0.4.6 + container: koalaman/shellcheck-alpine:v0.9.0 steps: - name: Check out uses: actions/checkout@v3 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 2ccfcbcdf..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 @@ -40,12 +37,15 @@ [submodule "plugins/CarlaBase/carla"] path = plugins/CarlaBase/carla url = https://github.com/falktx/carla -[submodule "plugins/Sid/resid"] - path = plugins/Sid/resid - url = https://github.com/simonowen/resid +[submodule "plugins/Sid/resid/resid"] + path = plugins/Sid/resid/resid + url = https://github.com/libsidplayfp/resid [submodule "src/3rdparty/jack2"] path = src/3rdparty/jack2 url = https://github.com/jackaudio/jack2 [submodule "plugins/LadspaEffect/cmt/cmt"] path = plugins/LadspaEffect/cmt/cmt url = https://github.com/lmms/cmt +[submodule "src/3rdparty/hiir/hiir"] + path = src/3rdparty/hiir/hiir + url = https://github.com/LostRobotMusic/hiir 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 c5de064a5..8401c3c41 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,24 +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}) -IF(COMMAND CMAKE_POLICY) - CMAKE_POLICY(SET CMP0005 NEW) - CMAKE_POLICY(SET CMP0003 NEW) - IF (CMAKE_MAJOR_VERSION GREATER 2) - CMAKE_POLICY(SET CMP0026 NEW) - CMAKE_POLICY(SET CMP0045 NEW) - CMAKE_POLICY(SET CMP0050 OLD) - ENDIF() - CMAKE_POLICY(SET CMP0020 NEW) - CMAKE_POLICY(SET CMP0057 NEW) -ENDIF(COMMAND CMAKE_POLICY) - - # Import of windows.h breaks min()/max() ADD_DEFINITIONS(-DNOMINMAX) @@ -39,7 +46,7 @@ INCLUDE(GenerateExportHeader) STRING(TOUPPER "${CMAKE_PROJECT_NAME}" PROJECT_NAME_UCASE) -SET(PROJECT_YEAR 2020) +SET(PROJECT_YEAR 2024) SET(PROJECT_AUTHOR "LMMS Developers") SET(PROJECT_URL "https://lmms.io") @@ -64,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) @@ -81,14 +89,19 @@ OPTION(WANT_SOUNDIO "Include libsoundio support" ON) OPTION(WANT_SDL "Include SDL (Simple DirectMedia Layer) support" ON) OPTION(WANT_SF2 "Include SoundFont2 player plugin" ON) OPTION(WANT_GIG "Include GIG player plugin" ON) +option(WANT_SID "Include Sid instrument" ON) OPTION(WANT_STK "Include Stk (Synthesis Toolkit) support" ON) OPTION(WANT_SWH "Include Steve Harris's LADSPA plugins" ON) OPTION(WANT_TAP "Include Tom's Audio Processing LADSPA plugins" ON) 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) +option(WANT_DEBUG_TSAN "Enable ThreadSanitizer" OFF) +option(WANT_DEBUG_MSAN "Enable MemorySanitizer" OFF) +option(WANT_DEBUG_UBSAN "Enable UndefinedBehaviorSanitizer" OFF) OPTION(BUNDLE_QT_TRANSLATIONS "Install Qt translation files for LMMS" OFF) @@ -97,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) @@ -109,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 "") @@ -200,7 +220,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 @@ -211,6 +238,13 @@ CHECK_CXX_SOURCE_COMPILES( LMMS_HAVE_SF_COMPLEVEL ) +# check for perl +if(LMMS_BUILD_APPLE) + # Prefer system perl over Homebrew, MacPorts, etc + set(Perl_ROOT "/usr/bin") +endif() +find_package(Perl) + IF(WANT_LV2) IF(PKG_CONFIG_FOUND) PKG_CHECK_MODULES(LV2 lv2) @@ -273,8 +307,12 @@ ELSE(WANT_CMT) ENDIF(WANT_CMT) IF(WANT_SWH) - SET(LMMS_HAVE_SWH TRUE) - SET(STATUS_SWH "OK") + IF(PERL_FOUND) + SET(LMMS_HAVE_SWH TRUE) + SET(STATUS_SWH "OK") + ELSE() + SET(STATUS_SWH "Skipping, perl is missing") + ENDIF() ELSE(WANT_SWH) SET(STATUS_SWH "not built as requested") ENDIF(WANT_SWH) @@ -340,6 +378,16 @@ IF(WANT_SDL AND NOT LMMS_HAVE_SDL2) ENDIF() ENDIF() +# check for Sid +if(WANT_SID) + if(PERL_FOUND) + set(LMMS_HAVE_SID TRUE) + set(STATUS_SID "OK") + else() + set(STATUS_SID "not found, please install perl if you require the Sid instrument") + endif() +endif() + # check for Stk IF(WANT_STK) FIND_PACKAGE(STK) @@ -406,8 +454,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") @@ -426,13 +472,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 @@ -502,10 +548,14 @@ 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) - SET(STATUS_FLUIDSYNTH "OK") + if(FluidSynth_VERSION_STRING VERSION_GREATER_EQUAL 2) + set(STATUS_FLUIDSYNTH "OK") + else() + set(STATUS_FLUIDSYNTH "OK (FluidSynth version < 2: per-note panning unsupported)") + endif() else() SET(STATUS_FLUIDSYNTH "not found, libfluidsynth-dev (or similar)" "is highly recommended") @@ -540,26 +590,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) @@ -616,7 +682,9 @@ else() set(NOOP_COMMAND "${CMAKE_COMMAND}" "-E" "echo") endif() if(STRIP) - set(STRIP_COMMAND "$,${NOOP_COMMAND},${STRIP}>") + # TODO CMake 3.19: Now that CONFIG generator expressions support testing for + # multiple configurations, combine the OR into a single CONFIG expression. + set(STRIP_COMMAND "$,$>,${NOOP_COMMAND},${STRIP}>") else() set(STRIP_COMMAND "${NOOP_COMMAND}") endif() @@ -652,8 +720,36 @@ IF(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang") ELSE(WIN32) SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fPIC -DPIC") ENDIF(WIN32) +elseif(MSVC) + # Use UTF-8 as the source and execution character set + add_compile_options("/utf-8") ENDIF() +# add enabled sanitizers +function(add_sanitizer sanitizer supported_compilers want_flag status_flag) + if(${want_flag}) + if(CMAKE_CXX_COMPILER_ID MATCHES "${supported_compilers}") + set("${status_flag}" "Enabled" PARENT_SCOPE) + string(REPLACE ";" " " additional_flags "${ARGN}") + # todo CMake 3.13: use add_compile_options/add_link_options instead + set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -fsanitize=${sanitizer} ${additional_flags}" PARENT_SCOPE) + set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -fsanitize=${sanitizer} ${additional_flags}" PARENT_SCOPE) + else() + set("${status_flag}" "Wanted but disabled due to unsupported compiler" PARENT_SCOPE) + endif() + else() + set("${status_flag}" "Disabled" PARENT_SCOPE) + endif() +endfunction() + +add_sanitizer(address "GNU|Clang|MSVC" WANT_DEBUG_ASAN STATUS_DEBUG_ASAN) +add_sanitizer(thread "GNU|Clang" WANT_DEBUG_TSAN STATUS_DEBUG_TSAN) +add_sanitizer(memory "Clang" WANT_DEBUG_MSAN STATUS_DEBUG_MSAN -fno-omit-frame-pointer) +# UBSan does not link with vptr enabled due to a problem with references from PeakControllerEffect +# not being found by PeakController +add_sanitizer(undefined "GNU|Clang" WANT_DEBUG_UBSAN STATUS_DEBUG_UBSAN -fno-sanitize=vptr) + + # use ccache include(CompileCache) @@ -722,7 +818,6 @@ ADD_CUSTOM_TARGET(uninstall COMMAND ${CMAKE_COMMAND} -DCMAKE_INSTALL_PREFIX="${CMAKE_INSTALL_PREFIX}" -P "${CMAKE_CURRENT_SOURCE_DIR}/cmake/uninstall.cmake" ) - # # display configuration information # @@ -774,9 +869,11 @@ MESSAGE( "* ZynAddSubFX instrument : ${STATUS_ZYN}\n" "* Carla Patchbay & Rack : ${STATUS_CARLA}\n" "* SoundFont2 player : ${STATUS_FLUIDSYNTH}\n" +"* Sid instrument : ${STATUS_SID}\n" "* Stk Mallets : ${STATUS_STK}\n" -"* VST-instrument hoster : ${STATUS_VST}\n" -"* VST-effect hoster : ${STATUS_VST}\n" +"* 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" @@ -788,7 +885,11 @@ MESSAGE( MESSAGE( "Developer options\n" "-----------------------------------------\n" -"* Debug FP exceptions : ${STATUS_DEBUG_FPE}\n" +"* Debug FP exceptions : ${STATUS_DEBUG_FPE}\n" +"* Debug using AddressSanitizer : ${STATUS_DEBUG_ASAN}\n" +"* Debug using ThreadSanitizer : ${STATUS_DEBUG_TSAN}\n" +"* Debug using MemorySanitizer : ${STATUS_DEBUG_MSAN}\n" +"* Debug using UBSanitizer : ${STATUS_DEBUG_UBSAN}\n" ) MESSAGE( diff --git a/README.md b/README.md index 7090a6a15..c8324226e 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,11 @@ # ![LMMS Logo](https://raw.githubusercontent.com/LMMS/artwork/master/Icon%20%26%20Mimetypes/lmms-64x64.svg) LMMS -[![Build status](https://circleci.com/gh/LMMS/lmms.svg?style=shield)](https://circleci.com/gh/LMMS/lmms) +[![Build status](https://github.com/LMMS/lmms/actions/workflows/build.yml/badge.svg)](https://github.com/LMMS/lmms/actions/workflows/build.yml) [![Latest stable release](https://img.shields.io/github/release/LMMS/lmms.svg?maxAge=3600)](https://lmms.io/download) [![Overall downloads on Github](https://img.shields.io/github/downloads/LMMS/lmms/total.svg?maxAge=3600)](https://github.com/LMMS/lmms/releases) [![Join the chat at Discord](https://img.shields.io/badge/chat-on%20discord-7289DA.svg)](https://discord.gg/3sc5su7) [![Localise on transifex](https://img.shields.io/badge/localise-on_transifex-green.svg)](https://www.transifex.com/lmms/lmms/) -**New PRs may be affected by ongoing reorganization ([#5592](https://github.com/LMMS/lmms/issues/5592)). Please be prepared to rebase your PR as necessary.** - What is LMMS? -------------- diff --git a/cmake/apple/install_apple.sh.in b/cmake/apple/install_apple.sh.in index e1921b1a5..fc27d78b3 100644 --- a/cmake/apple/install_apple.sh.in +++ b/cmake/apple/install_apple.sh.in @@ -6,9 +6,6 @@ set -e -# STK rawwaves directory -STK_RAWWAVE=$(brew --prefix stk)/share/stk/rawwaves - # Place to create ".app" bundle APP="@CMAKE_BINARY_DIR@/@PROJECT_NAME_UCASE@.app" @@ -29,19 +26,15 @@ command cp "@CMAKE_BINARY_DIR@/Info.plist" "@CMAKE_INSTALL_PREFIX@/" mkdir -p "$APP/Contents/MacOS" mkdir -p "$APP/Contents/Frameworks" mkdir -p "$APP/Contents/Resources" -mkdir -p "$APP/Contents/share/stk/rawwaves" cd "@CMAKE_INSTALL_PREFIX@" cp -R ./* "$APP/Contents" cp "@CMAKE_SOURCE_DIR@/cmake/apple/"*.icns "$APP/Contents/Resources/" -cp "$STK_RAWWAVE"/*.raw "$APP/Contents/share/stk/rawwaves" > /dev/null 2>&1 # Make all libraries writable for macdeployqt cd "$APP" find . -type f -print0 | xargs -0 chmod u+w lmmsbin="MacOS/@CMAKE_PROJECT_NAME@" -zynlib="lib/lmms/libzynaddsubfx.so" -zynfmk="Frameworks/libZynAddSubFxCore.dylib" zynbin="MacOS/RemoteZynAddSubFx" # Move lmms binary @@ -49,15 +42,6 @@ mv "$APP/Contents/bin/@CMAKE_PROJECT_NAME@" "$APP/Contents/$lmmsbin" # Fix zyn linking mv "$APP/Contents/lib/lmms/RemoteZynAddSubFx" "$APP/Contents/$zynbin" -mv "$APP/Contents/lib/lmms/libZynAddSubFxCore.dylib" "$APP/Contents/$zynfmk" - -install_name_tool -change @rpath/libZynAddSubFxCore.dylib \ - @loader_path/../$zynfmk \ - "$APP/Contents/$zynbin" - -install_name_tool -change @rpath/libZynAddSubFxCore.dylib \ - @loader_path/../../$zynfmk \ - "$APP/Contents/$zynlib" # Replace @rpath with @loader_path for Carla # See also plugins/CarlaBase/CMakeLists.txt @@ -72,7 +56,6 @@ install_name_tool -change @rpath/libcarlabase.dylib \ # Link lmms binary _executables="${_executables} -executable=$APP/Contents/$zynbin" -_executables="${_executables} -executable=$APP/Contents/$zynfmk" # Build a list of shared objects in target/lib/lmms for file in "$APP/Contents/lib/lmms/"*.so; do @@ -114,4 +97,8 @@ done # Cleanup rm -rf "$APP/Contents/bin" + +# Codesign +codesign --force --deep --sign - "$APP" + echo -e "\nFinished.\n\n" diff --git a/cmake/install/CMakeLists.txt b/cmake/install/CMakeLists.txt index cd4100c9b..3f6e3624e 100644 --- a/cmake/install/CMakeLists.txt +++ b/cmake/install/CMakeLists.txt @@ -36,7 +36,21 @@ IF(LMMS_BUILD_WIN32 OR LMMS_INSTALL_DEPENDENCIES) ) ENDIF() +# Install STK rawwaves +if(LMMS_HAVE_STK AND (LMMS_BUILD_WIN32 OR LMMS_BUILD_APPLE)) + if(STK_RAWWAVE_ROOT) + file(GLOB RAWWAVES "${STK_RAWWAVE_ROOT}/*.raw") + install(FILES ${RAWWAVES} DESTINATION "${DATA_DIR}/stk/rawwaves") + else() + message(WARNING "Can't find STK rawwave root!") + endif() +endif() + IF(LMMS_BUILD_APPLE) INSTALL(CODE "EXECUTE_PROCESS(COMMAND chmod u+x ${CMAKE_BINARY_DIR}/install_apple.sh)") - INSTALL(CODE "EXECUTE_PROCESS(COMMAND ${CMAKE_BINARY_DIR}/install_apple.sh)") + INSTALL(CODE "EXECUTE_PROCESS(COMMAND ${CMAKE_BINARY_DIR}/install_apple.sh RESULT_VARIABLE EXIT_CODE) + IF(NOT EXIT_CODE EQUAL 0) + MESSAGE(FATAL_ERROR \"Execution of install_apple.sh failed\") + ENDIF() + ") ENDIF() diff --git a/cmake/linux/package_linux.sh.in b/cmake/linux/package_linux.sh.in index 89e500060..16cd5719b 100644 --- a/cmake/linux/package_linux.sh.in +++ b/cmake/linux/package_linux.sh.in @@ -149,6 +149,7 @@ fi # Patch the desktop file sed -i 's/.*Exec=.*/Exec=lmms.real/' "$DESKTOPFILE" +echo "X-AppImage-Version=@VERSION@" >> "$DESKTOPFILE" # Fix linking for soft-linked plugins for file in "${APPDIR}usr/lib/lmms/"*.so; do @@ -201,6 +202,9 @@ fi rm -f "${APPDIR}/AppRun" ln -sr "${APPDIR}/usr/bin/lmms" "${APPDIR}/AppRun" +# Add icon +ln -srf "${APPDIR}/lmms.png" "${APPDIR}/.DirIcon" + # Create AppImage echo -e "\nFinishing the AppImage..." run_and_log "$APPIMAGETOOL" "${APPDIR}" "@APPIMAGE_FILE@" diff --git a/cmake/modules/BashCompletion.cmake b/cmake/modules/BashCompletion.cmake index c3916f201..7301e82aa 100644 --- a/cmake/modules/BashCompletion.cmake +++ b/cmake/modules/BashCompletion.cmake @@ -24,7 +24,7 @@ # - Windows does not support bash completion # - macOS support should eventually be added for Homebrew (TODO) IF(WIN32) - MESSAGE(STATUS "Bash competion is not supported on this platform.") + MESSAGE(STATUS "Bash completion is not supported on this platform.") ELSEIF(APPLE) MESSAGE(STATUS "Bash completion is not yet implemented for this platform.") ELSE() diff --git a/cmake/modules/BuildPlugin.cmake b/cmake/modules/BuildPlugin.cmake index f8b3d3153..70e518c93 100644 --- a/cmake/modules/BuildPlugin.cmake +++ b/cmake/modules/BuildPlugin.cmake @@ -56,11 +56,7 @@ MACRO(BUILD_PLUGIN PLUGIN_NAME) ADD_LIBRARY(${PLUGIN_NAME} ${PLUGIN_LINK} ${PLUGIN_SOURCES} ${plugin_MOC_out} ${RCC_OUT}) - TARGET_LINK_LIBRARIES(${PLUGIN_NAME} Qt5::Widgets Qt5::Xml) - - IF(LMMS_BUILD_WIN32) - TARGET_LINK_LIBRARIES(${PLUGIN_NAME} lmms) - ENDIF(LMMS_BUILD_WIN32) + target_link_libraries("${PLUGIN_NAME}" lmms Qt5::Widgets Qt5::Xml) INSTALL(TARGETS ${PLUGIN_NAME} LIBRARY DESTINATION "${PLUGIN_DIR}" @@ -70,10 +66,7 @@ MACRO(BUILD_PLUGIN PLUGIN_NAME) IF(LMMS_BUILD_APPLE) IF ("${PLUGIN_LINK}" STREQUAL "SHARED") SET_TARGET_PROPERTIES(${PLUGIN_NAME} PROPERTIES LINK_FLAGS "-undefined dynamic_lookup") - ELSE() - SET_TARGET_PROPERTIES(${PLUGIN_NAME} PROPERTIES LINK_FLAGS "-bundle_loader \"${CMAKE_BINARY_DIR}/lmms\"") ENDIF() - ADD_DEPENDENCIES(${PLUGIN_NAME} lmms) ENDIF(LMMS_BUILD_APPLE) IF(LMMS_BUILD_WIN32) add_custom_command( diff --git a/cmake/modules/CheckSubmodules.cmake b/cmake/modules/CheckSubmodules.cmake index f36189c38..b9ea20778 100644 --- a/cmake/modules/CheckSubmodules.cmake +++ b/cmake/modules/CheckSubmodules.cmake @@ -18,7 +18,7 @@ # For details see the accompanying COPYING-CMAKE-SCRIPTS file. # Files which confirm a successful clone -SET(VALID_CRUMBS "CMakeLists.txt;Makefile;Makefile.in;Makefile.am;configure.ac;configure.py;autogen.sh;.gitignore;LICENSE;Home.md") +SET(VALID_CRUMBS "CMakeLists.txt;Makefile;Makefile.in;Makefile.am;configure.ac;configure.py;autogen.sh;.gitignore;LICENSE;Home.md;license.txt") OPTION(NO_SHALLOW_CLONE "Disable shallow cloning of submodules" OFF) 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/DefineInstallVar.cmake b/cmake/modules/DefineInstallVar.cmake index b13cb1d52..0ca8fa429 100644 --- a/cmake/modules/DefineInstallVar.cmake +++ b/cmake/modules/DefineInstallVar.cmake @@ -24,7 +24,7 @@ function(DEFINE_INSTALL_VAR) endif() else() if(VAR_GENERATOR_EXPRESSION) - cmake_policy(SET CMP0087 NEW) + cmake_policy(SET CMP0087 NEW) # install(CODE) and install(SCRIPT) support generator expressions. endif() install(CODE "set(\"${VAR_NAME}\" \"${VAR_CONTENT}\")") endif() diff --git a/cmake/modules/DetectMachine.cmake b/cmake/modules/DetectMachine.cmake index 388efeb82..65bc6d2b7 100644 --- a/cmake/modules/DetectMachine.cmake +++ b/cmake/modules/DetectMachine.cmake @@ -92,7 +92,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/FindSTK.cmake b/cmake/modules/FindSTK.cmake index 80ed0da42..5564d24f8 100644 --- a/cmake/modules/FindSTK.cmake +++ b/cmake/modules/FindSTK.cmake @@ -1,32 +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() 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..aabb5ef78 100644 --- a/cmake/modules/FindWine.cmake +++ b/cmake/modules/FindWine.cmake @@ -32,8 +32,8 @@ FIND_PROGRAM(WINE_CXX FIND_PROGRAM(WINE_BUILD NAMES winebuild) # 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) + execute_process(COMMAND ${WINE_CXX} -m32 -v /dev/zero OUTPUT_VARIABLE WINEBUILD_OUTPUT_32) + execute_process(COMMAND ${WINE_CXX} -m64 -v /dev/zero OUTPUT_VARIABLE WINEBUILD_OUTPUT_64) _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) 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 791041bb2..29e5b207c 100644 --- a/cmake/modules/InstallDependencies.cmake +++ b/cmake/modules/InstallDependencies.cmake @@ -1,8 +1,9 @@ include(GetPrerequisites) include(CMakeParseArguments) -CMAKE_POLICY(SET CMP0011 NEW) -CMAKE_POLICY(SET CMP0057 NEW) +# 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) get_filename_component(abs "${${var}}" ABSOLUTE BASE_DIR "${CMAKE_INSTALL_PREFIX}") @@ -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 fe98a64b4..8c444aca2 100644 --- a/cmake/modules/PluginList.cmake +++ b/cmake/modules/PluginList.cmake @@ -33,6 +33,7 @@ SET(LMMS_PLUGIN_LIST Compressor CrossoverEQ Delay + Dispersion DualFilter DynamicsProcessor Eq @@ -40,6 +41,7 @@ SET(LMMS_PLUGIN_LIST HydrogenImport LadspaBrowser LadspaEffect + LOMM Lv2Effect Lv2Instrument Lb302 @@ -58,10 +60,12 @@ SET(LMMS_PLUGIN_LIST Sf2Player Sfxr Sid + SlicerT SpectrumAnalyzer StereoEnhancer StereoMatrix Stk + TapTempo VstBase Vestige VstEffect diff --git a/cmake/nsis/CMakeLists.txt b/cmake/nsis/CMakeLists.txt index 9dca3495d..ee1bd45c3 100644 --- a/cmake/nsis/CMakeLists.txt +++ b/cmake/nsis/CMakeLists.txt @@ -73,12 +73,6 @@ SET(CPACK_NSIS_MUI_ICON "${CPACK_NSIS_MUI_ICON}" PARENT_SCOPE) CONFIGURE_FILE("lmms.rc.in" "${CMAKE_BINARY_DIR}/lmms.rc") CONFIGURE_FILE("zynaddsubfx.rc.in" "${CMAKE_BINARY_DIR}/plugins/ZynAddSubFx/zynaddsubfx.rc") -IF(LMMS_HAVE_STK) - FILE(GLOB RAWWAVES "${MINGW_PREFIX}/share/stk/rawwaves/*.raw") - LIST(SORT RAWWAVES) - INSTALL(FILES ${RAWWAVES} DESTINATION "${DATA_DIR}/stk/rawwaves") -ENDIF() - INSTALL(FILES "lmms.exe.manifest" DESTINATION .) INSTALL(FILES "lmms.VisualElementsManifest.xml" DESTINATION .) INSTALL(DIRECTORY "assets" DESTINATION .) diff --git a/data/locale/ar.ts b/data/locale/ar.ts index 1f159c42a..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 @@ -6361,7 +6361,7 @@ Please make sure you have write permission to the file and the directory contain - InstrumentMiscView + InstrumentTuningView MASTER PITCH diff --git a/data/locale/bs.ts b/data/locale/bs.ts index 506b401bd..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 @@ -3677,7 +3677,7 @@ You can remove and move mixer channels in the context menu, which is accessed by - InstrumentMiscView + InstrumentTuningView MASTER PITCH @@ -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 765cf3b60..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 @@ -6360,7 +6360,7 @@ Please make sure you have write permission to the file and the directory contain - InstrumentMiscView + InstrumentTuningView MASTER PITCH diff --git a/data/locale/cs.ts b/data/locale/cs.ts index 0ed175022..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 @@ -6361,7 +6361,7 @@ Ověřte si prosím, zda máte povolen zápis do souboru a do složky, ve které - InstrumentMiscView + InstrumentTuningView MASTER PITCH diff --git a/data/locale/de.ts b/data/locale/de.ts index 51ca7d562..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 @@ -6361,7 +6361,7 @@ Please make sure you have write permission to the file and the directory contain - InstrumentMiscView + InstrumentTuningView MASTER PITCH diff --git a/data/locale/el.ts b/data/locale/el.ts index 320a6657f..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 @@ -6360,7 +6360,7 @@ Please make sure you have write permission to the file and the directory contain - InstrumentMiscView + InstrumentTuningView MASTER PITCH diff --git a/data/locale/en.ts b/data/locale/en.ts index e52ae39ab..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 @@ -6362,7 +6362,7 @@ Please make sure you have write permission to the file and the directory contain - InstrumentMiscView + InstrumentTuningView MASTER PITCH diff --git a/data/locale/eo.ts b/data/locale/eo.ts index 005ee8100..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 @@ -6360,7 +6360,7 @@ Please make sure you have write permission to the file and the directory contain - InstrumentMiscView + InstrumentTuningView MASTER PITCH diff --git a/data/locale/es.ts b/data/locale/es.ts index 4fc4951ef..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 @@ -6361,7 +6361,7 @@ Asegúrate de tener permisos de escritura tanto del archivo como del directorio - InstrumentMiscView + InstrumentTuningView MASTER PITCH diff --git a/data/locale/eu.ts b/data/locale/eu.ts index 25c165f81..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 @@ -6641,7 +6641,7 @@ Please make sure you have write permission to the file and the directory contain - InstrumentMiscView + InstrumentTuningView MASTER PITCH diff --git a/data/locale/fa.ts b/data/locale/fa.ts index 181ca0ca1..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 @@ -6360,7 +6360,7 @@ Please make sure you have write permission to the file and the directory contain - InstrumentMiscView + InstrumentTuningView MASTER PITCH diff --git a/data/locale/fr.ts b/data/locale/fr.ts index 2c65444a8..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 @@ -6645,7 +6645,7 @@ Veuillez vous assurez que vous avez les droits d'écriture sur le fichier e - InstrumentMiscView + InstrumentTuningView MASTER PITCH diff --git a/data/locale/gl.ts b/data/locale/gl.ts index cf04fd5d4..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 @@ -6360,7 +6360,7 @@ Please make sure you have write permission to the file and the directory contain - InstrumentMiscView + InstrumentTuningView MASTER PITCH diff --git a/data/locale/he.ts b/data/locale/he.ts index ee5a23613..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 @@ -6361,7 +6361,7 @@ Please make sure you have write permission to the file and the directory contain - InstrumentMiscView + InstrumentTuningView MASTER PITCH diff --git a/data/locale/hi_IN.ts b/data/locale/hi_IN.ts index 15550231f..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 @@ -6362,7 +6362,7 @@ Please make sure you have write permission to the file and the directory contain - InstrumentMiscView + InstrumentTuningView MASTER PITCH diff --git a/data/locale/hu_HU.ts b/data/locale/hu_HU.ts index 836059946..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 @@ -6366,7 +6366,7 @@ Ellenőrizd, hogy rendelkezel-e a szükséges engedélyekkel és próbáld újra - InstrumentMiscView + InstrumentTuningView MASTER PITCH diff --git a/data/locale/id.ts b/data/locale/id.ts index e381ea726..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 @@ -6362,7 +6362,7 @@ Pastikan Anda memiliki izin menulis ke file dan direktori yang berisi berkas ter - InstrumentMiscView + InstrumentTuningView MASTER PITCH diff --git a/data/locale/it.ts b/data/locale/it.ts index ff146d471..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 @@ -6366,7 +6366,7 @@ Si prega di controllare i permessi di scrittura sul file e la cartella che lo co - InstrumentMiscView + InstrumentTuningView MASTER PITCH diff --git a/data/locale/ja.ts b/data/locale/ja.ts index e10ca5118..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 @@ -6362,7 +6362,7 @@ Please make sure you have write permission to the file and the directory contain - InstrumentMiscView + InstrumentTuningView MASTER PITCH diff --git a/data/locale/ka.ts b/data/locale/ka.ts index 1956d8d04..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 @@ -6360,7 +6360,7 @@ Please make sure you have write permission to the file and the directory contain - InstrumentMiscView + InstrumentTuningView MASTER PITCH diff --git a/data/locale/ko.ts b/data/locale/ko.ts index 7373b5ca9..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 채널 @@ -6364,7 +6364,7 @@ Please make sure you have write permission to the file and the directory contain - InstrumentMiscView + InstrumentTuningView MASTER PITCH diff --git a/data/locale/ms_MY.ts b/data/locale/ms_MY.ts index 209d51d10..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 @@ -6360,7 +6360,7 @@ Please make sure you have write permission to the file and the directory contain - InstrumentMiscView + InstrumentTuningView MASTER PITCH diff --git a/data/locale/nb.ts b/data/locale/nb.ts index 3675b7f58..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 @@ -6360,7 +6360,7 @@ Please make sure you have write permission to the file and the directory contain - InstrumentMiscView + InstrumentTuningView MASTER PITCH diff --git a/data/locale/nl.ts b/data/locale/nl.ts index ad630a249..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 @@ -6362,7 +6362,7 @@ Zorg ervoor dat u schrijfbevoegdheid heeft voor het bestand en voor de map die h - InstrumentMiscView + InstrumentTuningView MASTER PITCH diff --git a/data/locale/oc.ts b/data/locale/oc.ts index 58c81c964..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 @@ -6360,7 +6360,7 @@ Please make sure you have write permission to the file and the directory contain - InstrumentMiscView + InstrumentTuningView MASTER PITCH diff --git a/data/locale/pl.ts b/data/locale/pl.ts index bb0c64ede..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 @@ -6646,7 +6646,7 @@ Upewnij się, że masz uprawnienia do zapisu do pliku i katalogu zawierającego - InstrumentMiscView + InstrumentTuningView MASTER PITCH diff --git a/data/locale/pt.ts b/data/locale/pt.ts index b375e289f..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 @@ -6363,7 +6363,7 @@ Please make sure you have write permission to the file and the directory contain - InstrumentMiscView + InstrumentTuningView MASTER PITCH diff --git a/data/locale/ro.ts b/data/locale/ro.ts index eceb45a64..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 @@ -6361,7 +6361,7 @@ Please make sure you have write permission to the file and the directory contain - InstrumentMiscView + InstrumentTuningView MASTER PITCH diff --git a/data/locale/ru.ts b/data/locale/ru.ts index 8235f291f..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 Новый канал ЭФ @@ -6375,7 +6375,7 @@ Please make sure you have write permission to the file and the directory contain - InstrumentMiscView + InstrumentTuningView MASTER PITCH diff --git a/data/locale/sl.ts b/data/locale/sl.ts index 3ad55a4c0..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 @@ -6360,7 +6360,7 @@ Please make sure you have write permission to the file and the directory contain - InstrumentMiscView + InstrumentTuningView MASTER PITCH diff --git a/data/locale/sr.ts b/data/locale/sr.ts index 9b90164ab..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 @@ -2956,7 +2956,7 @@ You can remove and move mixer channels in the context menu, which is accessed by - InstrumentMiscView + InstrumentTuningView MASTER PITCH @@ -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 4963b07a9..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 @@ -6644,7 +6644,7 @@ Se till att du har skrivbehörighet till filen och mappen som innehåller filen - InstrumentMiscView + InstrumentTuningView MASTER PITCH diff --git a/data/locale/tr.ts b/data/locale/tr.ts index 387be6d8b..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ı @@ -6646,7 +6646,7 @@ Lütfen dosyaya ve dosyayı içeren dizine yazma izniniz olduğundan emin olun v - InstrumentMiscView + InstrumentTuningView MASTER PITCH diff --git a/data/locale/uk.ts b/data/locale/uk.ts index 50df10e4b..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 Новий ефект каналу @@ -6361,7 +6361,7 @@ Please make sure you have write permission to the file and the directory contain - InstrumentMiscView + InstrumentTuningView MASTER PITCH diff --git a/data/locale/zh_CN.ts b/data/locale/zh_CN.ts index 63b22df99..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 新的效果通道 @@ -6370,7 +6370,7 @@ Please make sure you have write permission to the file and the directory contain - InstrumentMiscView + InstrumentTuningView MASTER PITCH diff --git a/data/locale/zh_TW.ts b/data/locale/zh_TW.ts index 791a45599..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 新的效果通道 @@ -6361,7 +6361,7 @@ Please make sure you have write permission to the file and the directory contain - InstrumentMiscView + InstrumentTuningView MASTER PITCH diff --git a/data/samples/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/drums/kick04.ogg b/data/samples/drums/kick04.ogg index 567480abd..8f7dce527 100644 Binary files a/data/samples/drums/kick04.ogg and b/data/samples/drums/kick04.ogg differ diff --git a/data/samples/effects/scratch01.ogg b/data/samples/effects/scratch01.ogg index 9f216038d..0b05505cd 100644 Binary files a/data/samples/effects/scratch01.ogg and b/data/samples/effects/scratch01.ogg differ diff --git a/data/samples/effects/wind_chimes01.ogg b/data/samples/effects/wind_chimes01.ogg index 35d3374a2..7fb3c441a 100644 Binary files a/data/samples/effects/wind_chimes01.ogg and b/data/samples/effects/wind_chimes01.ogg differ diff --git a/data/samples/instruments/harpsichord01.ogg b/data/samples/instruments/harpsichord01.ogg index 028bbd912..c84ffd7df 100644 Binary files a/data/samples/instruments/harpsichord01.ogg and b/data/samples/instruments/harpsichord01.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/samples/misc/hit01.ogg b/data/samples/misc/hit01.ogg index d5e93633e..30dd86135 100644 Binary files a/data/samples/misc/hit01.ogg and b/data/samples/misc/hit01.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/edit_tangent.png b/data/themes/classic/edit_tangent.png new file mode 100644 index 000000000..438673b33 Binary files /dev/null and b/data/themes/classic/edit_tangent.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/horizontal_slider.png b/data/themes/classic/horizontal_slider.png new file mode 100644 index 000000000..49d16b9d8 Binary files /dev/null and b/data/themes/classic/horizontal_slider.png 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 58ec5dc09..dfaee134d 100644 --- a/data/themes/classic/style.css +++ b/data/themes/classic/style.css @@ -8,7 +8,7 @@ QLabel, QTreeWidget, QListWidget, QGroupBox, QMenuBar { } QMdiArea { - background-image: url(resources:background_artwork.png); + background-image: url("resources:background_artwork.png"); } lmms--gui--Knob { @@ -22,6 +22,7 @@ lmms--gui--AutomationEditor { qproperty-backgroundShade: rgba(255, 255, 255, 15); qproperty-nodeInValueColor: rgba(255, 119, 175, 150); qproperty-nodeOutValueColor: rgba(129, 231, 181, 150); + qproperty-nodeTangentLineColor: rgba(200, 200, 200, 255); qproperty-crossColor: rgb( 255, 51, 51 ); /* Grid colors */ qproperty-lineColor: rgba(128, 128, 128, 80); @@ -32,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 */ @@ -70,7 +75,7 @@ QToolTip { color: #4afd85; } -lmms--gui--TextFloat { +lmms--gui--TextFloat, lmms--gui--SimpleTextFloat { border-radius: 4px; background: qlineargradient(spread:reflect, x1:0.5, y1:0.5, x2:0.5, y2:0, stop:0 rgba(0, 0, 0, 255), stop:1 rgba(50, 50, 50, 220)); opacity: 175; @@ -82,7 +87,6 @@ lmms--gui--TextFloat { QMenu { border:1px solid #747474; background-color: #c9c9c9; - font-size:11px; } QMenu::separator { @@ -98,15 +102,12 @@ QMenu::item { QMenu::item:selected { color: white; - font-weight:bold; background-color: #747474; } QMenu::item:disabled { color: #747474; background-color: #c9c9c9; - font-size:12px; - font-weight: normal; padding: 4px 32px 4px 20px; } @@ -132,7 +133,7 @@ QMenu::indicator:selected { lmms--gui--FileBrowser QCheckBox { - font-size: 10px; + font-size: 8pt; color: white; } @@ -146,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 */ @@ -187,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; } @@ -200,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); } @@ -208,7 +213,8 @@ lmms--gui--Oscilloscope { lmms--gui--CPULoadWidget { border: none; - background: url(resources:cpuload_bg.png); + background: url("resources:cpuload_bg.png"); + qproperty-stepSize: 4; } /* scrollbar: trough */ @@ -324,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 */ @@ -369,7 +375,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; @@ -377,12 +383,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; } @@ -411,13 +417,32 @@ 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; margin: -4px -12px -2px; } +/* main horizontal sliders (zoom) */ + +lmms--gui--AutomatableSlider::groove:horizontal { + background: rgba(0,0,0, 128); + border: 1px inset rgba(100,100,100, 64); + border-radius: 2px; + height: 2px; + margin: 2px; +} + +lmms--gui--AutomatableSlider::handle:horizontal { + background: none; + border-image: url("resources:horizontal_slider.png"); + width: 10px; + height: 26px; + border-radius: 2px; + margin: -12px -2px; +} + /* about dialog */ QTabWidget, QTabWidget QWidget { background: #5b6571; @@ -610,7 +635,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, @@ -621,11 +646,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 { @@ -641,15 +671,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 { @@ -882,6 +922,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; @@ -950,6 +998,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 { @@ -958,6 +1007,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/edit_draw_small.png b/data/themes/default/edit_draw_small.png new file mode 100644 index 000000000..9979c8223 Binary files /dev/null and b/data/themes/default/edit_draw_small.png differ diff --git a/data/themes/default/edit_tangent.png b/data/themes/default/edit_tangent.png new file mode 100644 index 000000000..7bc400094 Binary files /dev/null and b/data/themes/default/edit_tangent.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/horizontal_slider.png b/data/themes/default/horizontal_slider.png new file mode 100644 index 000000000..24f6098e5 Binary files /dev/null and b/data/themes/default/horizontal_slider.png 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 f4c651c9e..f13ec09d8 100644 --- a/data/themes/default/style.css +++ b/data/themes/default/style.css @@ -3,13 +3,13 @@ ********************/ /* most foreground text items */ -QLabel, QTreeWidget, QListWidget, QGroupBox, QMenuBar { +QLabel, QTreeWidget, QListWidget, QGroupBox, QMenuBar, QCheckBox { color: #d1d8e4; } QTreeView { outline: none; - font-size: 12px; + alternate-background-color: #111314; } QTreeWidget::item { @@ -42,7 +42,7 @@ QMdiArea { lmms--gui--FileBrowser QCheckBox { - font-size: 10px; + font-size: 8pt; color: white; } @@ -57,6 +57,7 @@ lmms--gui--AutomationEditor { qproperty-backgroundShade: rgba(255, 255, 255, 15); qproperty-nodeInValueColor: rgba(103, 73, 194, 150); qproperty-nodeOutValueColor: rgba(125, 40, 40, 150); + qproperty-nodeTangentLineColor: rgba(200, 200, 200, 255); qproperty-crossColor: rgba(215, 210, 254, 150); /* Grid colors */ qproperty-lineColor: #292929; @@ -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 */ @@ -100,7 +104,7 @@ QToolTip { color: #d1d8e4; } -lmms--gui--TextFloat { +lmms--gui--TextFloat, lmms--gui--SimpleTextFloat { background: #040506; color: #d1d8e4; } @@ -115,7 +119,6 @@ QSplashScreen QLabel { QMenu { border-top: 2px solid #08993E; background-color: #15191c; - font-size: 11px; } QMenu::separator { @@ -133,15 +136,12 @@ QMenu::item { QMenu::item:selected { color: #d1d8e4; - font-weight: normal; background-color: #21272b; } QMenu::item:disabled { color: #515459; background-color: #262b30; - font-size: 12px; - font-weight: normal; padding: 4px 32px 4px 20px; } @@ -178,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 */ @@ -218,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; } @@ -232,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); } @@ -240,7 +244,8 @@ lmms--gui--Oscilloscope { lmms--gui--CPULoadWidget { border: none; - background: url(resources:cpuload_bg.png); + background: url("resources:cpuload_bg.png"); + qproperty-stepSize: 1; } /* scrollbar: trough */ @@ -358,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 */ @@ -404,7 +409,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; @@ -413,7 +418,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; } @@ -436,19 +441,42 @@ 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; margin: -4px -12px -2px; } +/* main horizontal sliders (zoom) */ + +lmms--gui--AutomatableSlider::groove:horizontal { + background: #040506; + border: none; + border-radius: 2px; + height: 2px; + margin: 2px; +} + +lmms--gui--AutomatableSlider::handle:horizontal { + background: none; + border-image: url("resources:horizontal_slider.png"); + width: 10px; + height: 26px; + border-radius: 2px; + margin: -12px -2px; +} + /* window that shows up when you add effects */ lmms--gui--EffectSelectDialog QScrollArea { background: #262b30; } +lmms--gui--SetupDialog QScrollArea { + border: 0px; +} + /* the inner boxes in LADSPA effect windows */ lmms--gui--EffectControlDialog QGroupBox { @@ -522,7 +550,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 */ @@ -571,7 +599,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; @@ -581,7 +609,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; @@ -649,7 +677,7 @@ lmms--gui--ControllerRackView QPushButton { font-size: 10px; } -lmms--gui--MixerLine { +lmms--gui--MixerChannelView { background: #14161A; color: #d1d8e4; qproperty-backgroundActive: #3B424A; @@ -659,11 +687,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 { @@ -679,27 +712,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 @@ -926,6 +964,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; @@ -994,6 +1040,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 { @@ -1002,6 +1049,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/tuning_tab.png b/data/themes/default/tuning_tab.png new file mode 100644 index 000000000..41c4f2d9f Binary files /dev/null and b/data/themes/default/tuning_tab.png differ diff --git a/include/AboutDialog.h b/include/AboutDialog.h index c7d65023f..2fd01faf5 100644 --- a/include/AboutDialog.h +++ b/include/AboutDialog.h @@ -22,9 +22,8 @@ * */ - -#ifndef ABOUT_DIALOG_H -#define ABOUT_DIALOG_H +#ifndef LMMS_GUI_ABOUT_DIALOG_H +#define LMMS_GUI_ABOUT_DIALOG_H #include @@ -42,5 +41,4 @@ public: } // namespace lmms::gui -#endif - +#endif // LMMS_GUI_ABOUT_DIALOG_H diff --git a/include/ActionGroup.h b/include/ActionGroup.h index 0fe9d78ed..e18f6fb23 100644 --- a/include/ActionGroup.h +++ b/include/ActionGroup.h @@ -22,9 +22,8 @@ * */ - -#ifndef ACTION_GROUP_H -#define ACTION_GROUP_H +#ifndef LMMS_GUI_ACTION_GROUP_H +#define LMMS_GUI_ACTION_GROUP_H #include @@ -59,4 +58,4 @@ private: } // namespace lmms::gui -#endif +#endif // LMMS_GUI_ACTION_GROUP_H diff --git a/include/ArrayVector.h b/include/ArrayVector.h new file mode 100644 index 000000000..06e09226c --- /dev/null +++ b/include/ArrayVector.h @@ -0,0 +1,388 @@ +/* + * ArrayVector.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_ARRAY_VECTOR_H +#define LMMS_ARRAY_VECTOR_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace lmms { + +namespace detail { + +template +constexpr bool is_input_iterator_v = false; + +template +constexpr bool is_input_iterator_v::iterator_category>> = + std::is_convertible_v::iterator_category, std::input_iterator_tag>; + +} // namespace detail + +/** + * A container that stores up to a maximum of `N` elements of type `T` directly + * within itself, rather than separately on the heap. Useful when a dynamically + * resizeable container is needed for use in real-time code. Can be thought of + * as a hybrid between `std::array` and `std::vector`. The interface follows + * that of `std::vector` - see standard C++ documentation. + */ +template +class ArrayVector +{ +public: + using size_type = std::size_t; + using difference_type = std::ptrdiff_t; + using value_type = T; + using reference = T&; + using const_reference = const T&; + using pointer = T*; + using const_pointer = const T*; + using iterator = pointer; + using const_iterator = const_pointer; + using reverse_iterator = std::reverse_iterator; + using const_reverse_iterator = std::reverse_iterator; + + ArrayVector() = default; + + ArrayVector(const ArrayVector& other) noexcept(std::is_nothrow_copy_constructible_v) : + m_size{other.m_size} + { + std::uninitialized_copy(other.begin(), other.end(), begin()); + } + + ArrayVector(ArrayVector&& other) noexcept(std::is_nothrow_move_constructible_v) : + m_size{other.m_size} + { + std::uninitialized_move(other.begin(), other.end(), begin()); + other.clear(); + } + + ArrayVector(size_type count, const T& value) noexcept(std::is_nothrow_copy_constructible_v) : + m_size{count} + { + assert(count <= N); + std::uninitialized_fill_n(begin(), count, value); + } + + explicit ArrayVector(size_type count) noexcept(std::is_nothrow_default_constructible_v) : + m_size{count} + { + assert(count <= N); + std::uninitialized_value_construct_n(begin(), count); + } + + template, int> = 0> + ArrayVector(It first, It last) + { + // Can't check the size first as the iterator may not be multipass + const auto end = std::uninitialized_copy(first, last, begin()); + m_size = end - begin(); + assert(m_size <= N); + } + + ArrayVector(std::initializer_list il) noexcept(std::is_nothrow_copy_constructible_v) : + m_size{il.size()} + { + assert(il.size() <= N); + std::uninitialized_copy(il.begin(), il.end(), begin()); + } + + ~ArrayVector() { std::destroy(begin(), end()); } + + ArrayVector& operator=(const ArrayVector& other) + noexcept(std::is_nothrow_copy_assignable_v && std::is_nothrow_copy_constructible_v) + { + if (this != &other) { + const auto toAssign = std::min(other.size(), size()); + const auto assignedFromEnd = other.begin() + toAssign; + const auto assignedToEnd = std::copy(other.begin(), other.begin() + toAssign, begin()); + std::destroy(assignedToEnd, end()); + std::uninitialized_copy(assignedFromEnd, other.end(), end()); + m_size = other.size(); + } + return *this; + } + + ArrayVector& operator=(ArrayVector&& other) + noexcept(std::is_nothrow_move_assignable_v && std::is_nothrow_move_constructible_v) + { + if (this != &other) { + const auto toAssign = std::min(other.size(), size()); + const auto assignedFromEnd = other.begin() + toAssign; + const auto assignedToEnd = std::move(other.begin(), other.begin() + toAssign, begin()); + std::destroy(assignedToEnd, end()); + std::uninitialized_move(assignedFromEnd, other.end(), end()); + m_size = other.size(); + other.clear(); + } + return *this; + } + + ArrayVector& operator=(std::initializer_list il) + noexcept(std::is_nothrow_copy_assignable_v && std::is_nothrow_copy_constructible_v) + { + assert(il.size() <= N); + const auto toAssign = std::min(il.size(), size()); + const auto assignedFromEnd = il.begin() + toAssign; + const auto assignedToEnd = std::copy(il.begin(), assignedFromEnd, begin()); + std::destroy(assignedToEnd, end()); + std::uninitialized_copy(assignedFromEnd, il.end(), end()); + m_size = il.size(); + return *this; + } + + void assign(size_type count, const T& value) + noexcept(std::is_nothrow_copy_assignable_v && std::is_nothrow_copy_constructible_v) + { + assert(count <= N); + const auto temp = value; + const auto toAssign = std::min(count, size()); + const auto toConstruct = count - toAssign; + const auto assignedToEnd = std::fill_n(begin(), toAssign, temp); + std::destroy(assignedToEnd, end()); + std::uninitialized_fill_n(assignedToEnd, toConstruct, temp); + m_size = count; + } + + template, int> = 0> + void assign(It first, It last) + { + // Can't check the size first as the iterator may not be multipass + auto pos = begin(); + for (; first != last && pos != end(); ++pos, ++first) { + *pos = *first; + } + std::destroy(pos, end()); + pos = std::uninitialized_copy(first, last, pos); + m_size = pos - begin(); + assert(m_size <= N); + } + + reference at(size_type index) + { + if (index >= m_size) { throw std::out_of_range{"index out of range"}; } + return data()[index]; + } + + const_reference at(size_type index) const + { + if (index >= m_size) { throw std::out_of_range{"index out of range"}; } + return data()[index]; + } + + reference operator[](size_type index) noexcept + { + assert(index < m_size); + return data()[index]; + } + + const_reference operator[](size_type index) const noexcept + { + assert(index < m_size); + return data()[index]; + } + + reference front() noexcept { return operator[](0); } + const_reference front() const noexcept { return operator[](0); } + + reference back() noexcept { return operator[](m_size - 1); } + const_reference back() const noexcept { return operator[](m_size - 1); } + + pointer data() noexcept { return *std::launder(reinterpret_cast(m_data)); } + const_pointer data() const noexcept { return *std::launder(reinterpret_cast(m_data)); } + + iterator begin() noexcept { return data(); } + const_iterator begin() const noexcept { return data(); } + const_iterator cbegin() const noexcept { return data(); } + + iterator end() noexcept { return data() + m_size; } + const_iterator end() const noexcept { return data() + m_size; } + const_iterator cend() const noexcept { return data() + m_size; } + + reverse_iterator rbegin() noexcept { return std::reverse_iterator{end()}; } + const_reverse_iterator rbegin() const noexcept { return std::reverse_iterator{end()}; } + const_reverse_iterator crbegin() const noexcept { return std::reverse_iterator{cend()}; } + + reverse_iterator rend() noexcept { return std::reverse_iterator{begin()}; } + const_reverse_iterator rend() const noexcept { return std::reverse_iterator{begin()}; } + const_reverse_iterator crend() const noexcept { return std::reverse_iterator{cbegin()}; } + + bool empty() const noexcept { return m_size == 0; } + bool full() const noexcept { return m_size == N; } + size_type size() const noexcept { return m_size; } + size_type max_size() const noexcept { return N; } + size_type capacity() const noexcept { return N; } + + void clear() noexcept + { + std::destroy(begin(), end()); + m_size = 0; + } + + iterator insert(const_iterator pos, const T& value) { return emplace(pos, value); } + iterator insert(const_iterator pos, T&& value) { return emplace(pos, std::move(value)); } + + iterator insert(const_iterator pos, size_type count, const T& value) + { + assert(m_size + count <= N); + assert(cbegin() <= pos && pos <= cend()); + const auto mutPos = begin() + (pos - cbegin()); + const auto newEnd = std::uninitialized_fill_n(end(), count, value); + std::rotate(mutPos, end(), newEnd); + m_size += count; + return mutPos; + } + + template, int> = 0> + iterator insert(const_iterator pos, It first, It last) + { + // Can't check the size first as the iterator may not be multipass + assert(cbegin() <= pos && pos <= cend()); + const auto mutPos = begin() + (pos - cbegin()); + const auto newEnd = std::uninitialized_copy(first, last, end()); + std::rotate(mutPos, end(), newEnd); + m_size = newEnd - begin(); + assert(m_size <= N); + return mutPos; + } + + iterator insert(const_iterator pos, std::initializer_list il) { return insert(pos, il.begin(), il.end()); } + + template + iterator emplace(const_iterator pos, Args&&... args) + { + assert(cbegin() <= pos && pos <= cend()); + const auto mutPos = begin() + (pos - cbegin()); + emplace_back(std::forward(args)...); + std::rotate(mutPos, end() - 1, end()); + return mutPos; + } + + iterator erase(const_iterator pos) { return erase(pos, pos + 1); } + iterator erase(const_iterator first, const_iterator last) + { + assert(cbegin() <= first && first <= last && last <= cend()); + const auto mutFirst = begin() + (first - cbegin()); + const auto mutLast = begin() + (last - cbegin()); + const auto newEnd = std::move(mutLast, end(), mutFirst); + std::destroy(newEnd, end()); + m_size = newEnd - begin(); + return mutFirst; + } + + void push_back(const T& value) { emplace_back(value); } + void push_back(T&& value) { emplace_back(std::move(value)); } + + template + reference emplace_back(Args&&... args) + { + assert(!full()); + // TODO C++20: Use std::construct_at + const auto result = new(static_cast(end())) T(std::forward(args)...); + ++m_size; + return *result; + } + + void pop_back() + { + assert(!empty()); + --m_size; + std::destroy_at(end()); + } + + void resize(size_type size) + { + if (size > N) { throw std::length_error{"size exceeds maximum size"}; } + if (size < m_size) { + std::destroy(begin() + size, end()); + } else { + std::uninitialized_value_construct(end(), begin() + size); + } + m_size = size; + } + + void resize(size_type size, const value_type& value) + { + if (size > N) { throw std::length_error{"size exceeds maximum size"}; } + if (size < m_size) { + std::destroy(begin() + size, end()); + } else { + std::uninitialized_fill(end(), begin() + size, value); + } + m_size = size; + } + + void swap(ArrayVector& other) + noexcept(std::is_nothrow_swappable_v && std::is_nothrow_move_constructible_v) + { + using std::swap; + swap(*this, other); + } + + friend void swap(ArrayVector& a, ArrayVector& b) + noexcept(std::is_nothrow_swappable_v && std::is_nothrow_move_constructible_v) + { + const auto toSwap = std::min(a.size(), b.size()); + const auto aSwapEnd = a.begin() + toSwap; + const auto bSwapEnd = b.begin() + toSwap; + std::swap_ranges(a.begin(), aSwapEnd, b.begin()); + std::uninitialized_move(aSwapEnd, a.end(), bSwapEnd); + std::uninitialized_move(bSwapEnd, b.end(), aSwapEnd); + std::destroy(aSwapEnd, a.end()); + std::destroy(bSwapEnd, b.end()); + std::swap(a.m_size, b.m_size); + } + + // TODO C++20: Replace with operator<=> + friend bool operator<(const ArrayVector& l, const ArrayVector& r) + { + return std::lexicographical_compare(l.begin(), l.end(), r.begin(), r.end()); + } + friend bool operator<=(const ArrayVector& l, const ArrayVector& r) { return !(r < l); } + friend bool operator>(const ArrayVector& l, const ArrayVector& r) { return r < l; } + friend bool operator>=(const ArrayVector& l, const ArrayVector& r) { return !(l < r); } + + friend bool operator==(const ArrayVector& l, const ArrayVector& r) + { + return std::equal(l.begin(), l.end(), r.begin(), r.end()); + } + // TODO C++20: Remove + friend bool operator!=(const ArrayVector& l, const ArrayVector& r) { return !(l == r); } + +private: + alignas(T) std::byte m_data[std::max(N * sizeof(T), std::size_t{1})]; // Intentionally a raw array + size_type m_size = 0; +}; + +} // namespace lmms + +#endif // LMMS_ARRAY_VECTOR_H diff --git a/include/AudioAlsa.h b/include/AudioAlsa.h index f96dd01c9..975532071 100644 --- a/include/AudioAlsa.h +++ b/include/AudioAlsa.h @@ -22,8 +22,8 @@ * */ -#ifndef AUDIO_ALSA_H -#define AUDIO_ALSA_H +#ifndef LMMS_AUDIO_ALSA_H +#define LMMS_AUDIO_ALSA_H #include "lmmsconfig.h" @@ -108,4 +108,4 @@ private: #endif // LMMS_HAVE_ALSA -#endif +#endif // LMMS_AUDIO_ALSA_H diff --git a/include/AudioAlsaSetupWidget.h b/include/AudioAlsaSetupWidget.h index f68d71e8a..cbe99ba01 100644 --- a/include/AudioAlsaSetupWidget.h +++ b/include/AudioAlsaSetupWidget.h @@ -22,8 +22,8 @@ * */ -#ifndef AUDIO_ALSA_SETUP_WIDGET_H -#define AUDIO_ALSA_SETUP_WIDGET_H +#ifndef LMMS_GUI_AUDIO_ALSA_SETUP_WIDGET_H +#define LMMS_GUI_AUDIO_ALSA_SETUP_WIDGET_H #include "lmmsconfig.h" @@ -66,4 +66,4 @@ private: #endif // LMMS_HAVE_ALSA -#endif +#endif // LMMS_GUI_AUDIO_ALSA_SETUP_WIDGET_H diff --git a/include/AudioDevice.h b/include/AudioDevice.h index 6b4e9939a..c6ee46efc 100644 --- a/include/AudioDevice.h +++ b/include/AudioDevice.h @@ -22,8 +22,8 @@ * */ -#ifndef AUDIO_DEVICE_H -#define AUDIO_DEVICE_H +#ifndef LMMS_AUDIO_DEVICE_H +#define LMMS_AUDIO_DEVICE_H #include #include @@ -96,11 +96,7 @@ public: 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 +105,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 ); @@ -160,4 +155,4 @@ private: } // namespace lmms -#endif +#endif // LMMS_AUDIO_DEVICE_H diff --git a/include/AudioDeviceSetupWidget.h b/include/AudioDeviceSetupWidget.h index e984651e4..acc99602d 100644 --- a/include/AudioDeviceSetupWidget.h +++ b/include/AudioDeviceSetupWidget.h @@ -2,6 +2,7 @@ * AudioDeviceSetupWidget.h - Base class for audio device setup widgets * * Copyright (c) 2004-2015 Tobias Doerffel + * Copyright (c) 2023- Michael Gregorius * * This file is part of LMMS - https://lmms.io * @@ -22,15 +23,15 @@ * */ -#ifndef AUDIO_DEVICE_SETUP_WIDGET_H -#define AUDIO_DEVICE_SETUP_WIDGET_H +#ifndef LMMS_GUI_AUDIO_DEVICE_SETUP_WIDGET_H +#define LMMS_GUI_AUDIO_DEVICE_SETUP_WIDGET_H -#include "TabWidget.h" +#include namespace lmms::gui { -class AudioDeviceSetupWidget : public TabWidget +class AudioDeviceSetupWidget : public QGroupBox { Q_OBJECT public: @@ -45,4 +46,4 @@ public: } // namespace lmms::gui -#endif +#endif // LMMS_GUI_AUDIO_DEVICE_SETUP_WIDGET_H diff --git a/include/AudioDummy.h b/include/AudioDummy.h index 30b125b3a..e34260171 100644 --- a/include/AudioDummy.h +++ b/include/AudioDummy.h @@ -22,8 +22,8 @@ * */ -#ifndef AUDIO_DUMMY_H -#define AUDIO_DUMMY_H +#ifndef LMMS_AUDIO_DUMMY_H +#define LMMS_AUDIO_DUMMY_H #include "AudioDevice.h" #include "AudioDeviceSetupWidget.h" @@ -116,4 +116,4 @@ private: } // namespace lmms -#endif +#endif // LMMS_AUDIO_DUMMY_H diff --git a/include/AudioEngine.h b/include/AudioEngine.h index 71751acb8..67c2edd86 100644 --- a/include/AudioEngine.h +++ b/include/AudioEngine.h @@ -22,20 +22,19 @@ * */ -#ifndef AUDIO_ENGINE_H -#define AUDIO_ENGINE_H +#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 +#include #include "lmms_basics.h" #include "LocklessList.h" @@ -109,27 +108,27 @@ public: struct qualitySettings { - enum Mode + enum class Mode { - Mode_Draft, - Mode_HighQuality, - Mode_FinalMix + Draft, + HighQuality, + FinalMix } ; - enum Interpolation + enum class Interpolation { - Interpolation_Linear, - Interpolation_SincFastest, - Interpolation_SincMedium, - Interpolation_SincBest + Linear, + SincFastest, + SincMedium, + SincBest } ; - enum Oversampling + enum class Oversampling { - Oversampling_None, - Oversampling_2x, - Oversampling_4x, - Oversampling_8x + None, + X2, + X4, + X8 } ; Interpolation interpolation; @@ -139,18 +138,18 @@ public: { switch (m) { - case Mode_Draft: - interpolation = Interpolation_Linear; - oversampling = Oversampling_None; + case Mode::Draft: + interpolation = Interpolation::Linear; + oversampling = Oversampling::None; break; - case Mode_HighQuality: + case Mode::HighQuality: interpolation = - Interpolation_SincFastest; - oversampling = Oversampling_2x; + Interpolation::SincFastest; + oversampling = Oversampling::X2; break; - case Mode_FinalMix: - interpolation = Interpolation_SincBest; - oversampling = Oversampling_8x; + case Mode::FinalMix: + interpolation = Interpolation::SincBest; + oversampling = Oversampling::X8; break; } } @@ -165,10 +164,10 @@ public: { switch( oversampling ) { - case Oversampling_None: return 1; - case Oversampling_2x: return 2; - case Oversampling_4x: return 4; - case Oversampling_8x: return 8; + case Oversampling::None: return 1; + case Oversampling::X2: return 2; + case Oversampling::X4: return 4; + case Oversampling::X8: return 8; } return 1; } @@ -177,13 +176,13 @@ public: { switch( interpolation ) { - case Interpolation_Linear: + case Interpolation::Linear: return SRC_ZERO_ORDER_HOLD; - case Interpolation_SincFastest: + case Interpolation::SincFastest: return SRC_SINC_FASTEST; - case Interpolation_SincMedium: + case Interpolation::SincMedium: return SRC_SINC_MEDIUM_QUALITY; - case Interpolation_SincBest: + case Interpolation::SincBest: return SRC_SINC_BEST_QUALITY; } return SRC_LINEAR; @@ -197,6 +196,7 @@ public: // audio-device-stuff + bool renderOnly() const { return m_renderOnly; } // Returns the current audio device's name. This is not necessarily // the user's preferred audio device, in case you were thinking that. inline const QString & audioDevName() const @@ -255,7 +255,7 @@ public: return m_playHandles; } - void removePlayHandlesOfTypes(Track * track, const quint8 types); + void removePlayHandlesOfTypes(Track * track, PlayHandle::Types types); // methods providing information for other classes @@ -275,6 +275,11 @@ public: return m_profiler.cpuLoad(); } + int detailLoad(const AudioEngineProfiler::DetailType type) const + { + return m_profiler.detailLoad(type); + } + const qualitySettings & currentQualitySettings() const { return m_qualitySettings; @@ -401,6 +406,10 @@ private: AudioDevice * tryAudioDevices(); MidiClient * tryMidiClients(); + void renderStageNoteSetup(); + void renderStageInstruments(); + void renderStageEffects(); + void renderStageMix(); const surroundSampleFrame * renderNextBuffer(); @@ -410,13 +419,9 @@ 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; - QVector m_audioPorts; + std::vector m_audioPorts; fpp_t m_framesPerPeriod; @@ -430,7 +435,7 @@ private: surroundSampleFrame * m_outputBufferWrite; // worker thread stuff - QVector m_workers; + std::vector m_workers; int m_numWorkers; // playhandle stuff @@ -443,8 +448,6 @@ private: struct qualitySettings m_qualitySettings; float m_masterGain; - bool m_isProcessing; - // audio device stuff void doSetAudioDevice( AudioDevice *_dev ); AudioDevice * m_audioDev; @@ -466,19 +469,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; @@ -487,4 +478,4 @@ private: } // namespace lmms -#endif +#endif // LMMS_AUDIO_ENGINE_H diff --git a/include/AudioEngineProfiler.h b/include/AudioEngineProfiler.h index 38631c6a9..b0d62a1dc 100644 --- a/include/AudioEngineProfiler.h +++ b/include/AudioEngineProfiler.h @@ -22,9 +22,11 @@ * */ -#ifndef AUDIO_ENGINE_PROFILER_H -#define AUDIO_ENGINE_PROFILER_H +#ifndef LMMS_AUDIO_ENGINE_PROFILER_H +#define LMMS_AUDIO_ENGINE_PROFILER_H +#include +#include #include #include "lmms_basics.h" @@ -53,13 +55,57 @@ public: void setOutputFile( const QString& outputFile ); + enum class DetailType { + NoteSetup, + Instruments, + Effects, + Mixing, + Count + }; + + constexpr static auto DetailCount = static_cast(DetailType::Count); + + int detailLoad(const DetailType type) const + { + return m_detailLoad[static_cast(type)].load(std::memory_order_relaxed); + } + + class Probe + { + public: + Probe(AudioEngineProfiler& profiler, AudioEngineProfiler::DetailType type) + : m_profiler(profiler) + , m_type(type) + { + profiler.startDetail(type); + } + ~Probe() { m_profiler.finishDetail(m_type); } + Probe& operator=(const Probe&) = delete; + Probe(const Probe&) = delete; + Probe(Probe&&) = delete; + + private: + AudioEngineProfiler &m_profiler; + const AudioEngineProfiler::DetailType m_type; + }; private: + void startDetail(const DetailType type) { m_detailTimer[static_cast(type)].reset(); } + void finishDetail(const DetailType type) + { + m_detailTime[static_cast(type)] = m_detailTimer[static_cast(type)].elapsed(); + } + MicroTimer m_periodTimer; - int m_cpuLoad; + std::atomic m_cpuLoad; QFile m_outputFile; + + // Use arrays to avoid dynamic allocations in realtime code + std::array m_detailTimer; + std::array m_detailTime{0}; + std::array, DetailCount> m_detailLoad{0}; }; } // namespace lmms -#endif +#endif // LMMS_AUDIO_ENGINE_PROFILER_H diff --git a/include/AudioEngineWorkerThread.h b/include/AudioEngineWorkerThread.h index 87e2791b1..b76235aa1 100644 --- a/include/AudioEngineWorkerThread.h +++ b/include/AudioEngineWorkerThread.h @@ -22,8 +22,8 @@ * */ -#ifndef AUDIO_ENGINE_WORKER_THREAD_H -#define AUDIO_ENGINE_WORKER_THREAD_H +#ifndef LMMS_AUDIO_ENGINE_WORKER_THREAD_H +#define LMMS_AUDIO_ENGINE_WORKER_THREAD_H #include @@ -45,7 +45,7 @@ public: class JobQueue { public: - enum OperationMode + enum class OperationMode { Static, // no jobs added while processing queue Dynamic // jobs can be added while processing queue @@ -57,7 +57,7 @@ public: m_items(), m_writeIndex( 0 ), m_itemsDone( 0 ), - m_opMode( Static ) + m_opMode( OperationMode::Static ) { std::fill(m_items, m_items + JOB_QUEUE_SIZE, nullptr); } @@ -83,7 +83,7 @@ public: virtual void quit(); static void resetJobQueue( JobQueue::OperationMode _opMode = - JobQueue::Static ) + JobQueue::OperationMode::Static ) { globalJobQueue.reset( _opMode ); } @@ -97,12 +97,12 @@ public: // to ThreadableJob objects template static void fillJobQueue( const T & _vec, - JobQueue::OperationMode _opMode = JobQueue::Static ) + JobQueue::OperationMode _opMode = JobQueue::OperationMode::Static ) { resetJobQueue( _opMode ); - for( typename T::ConstIterator it = _vec.begin(); it != _vec.end(); ++it ) + for (const auto& job : _vec) { - addJob( *it ); + addJob(job); } } @@ -121,4 +121,4 @@ private: } // namespace lmms -#endif +#endif // LMMS_AUDIO_ENGINE_WORKER_THREAD_H diff --git a/include/AudioFileDevice.h b/include/AudioFileDevice.h index beee087e7..dc9a786a4 100644 --- a/include/AudioFileDevice.h +++ b/include/AudioFileDevice.h @@ -23,8 +23,8 @@ * */ -#ifndef AUDIO_FILE_DEVICE_H -#define AUDIO_FILE_DEVICE_H +#ifndef LMMS_AUDIO_FILE_DEVICE_H +#define LMMS_AUDIO_FILE_DEVICE_H #include @@ -73,4 +73,4 @@ using AudioFileDeviceInstantiaton } // namespace lmms -#endif +#endif // LMMS_AUDIO_FILE_DEVICE_H diff --git a/include/AudioFileFlac.h b/include/AudioFileFlac.h index 542a2e717..9432f4231 100644 --- a/include/AudioFileFlac.h +++ b/include/AudioFileFlac.h @@ -22,8 +22,8 @@ * */ -#ifndef AUDIO_FILE_FLAC_H -#define AUDIO_FILE_FLAC_H +#ifndef LMMS_AUDIO_FILE_FLAC_H +#define LMMS_AUDIO_FILE_FLAC_H #include "lmmsconfig.h" @@ -33,7 +33,7 @@ namespace lmms { -class AudioFileFlac: public AudioFileDevice +class AudioFileFlac : public AudioFileDevice { public: AudioFileFlac(OutputSettings const& outputSettings, @@ -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(); @@ -77,4 +75,4 @@ private: } // namespace lmms -#endif //AUDIO_FILE_FLAC_H +#endif // LMMS_AUDIO_FILE_FLAC_H diff --git a/include/AudioFileMP3.h b/include/AudioFileMP3.h index 290c9ccbf..013c93a3e 100644 --- a/include/AudioFileMP3.h +++ b/include/AudioFileMP3.h @@ -23,8 +23,8 @@ * */ -#ifndef AUDIO_FILE_MP3_H -#define AUDIO_FILE_MP3_H +#ifndef LMMS_AUDIO_FILE_MP3_H +#define LMMS_AUDIO_FILE_MP3_H #include "lmmsconfig.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(); @@ -75,4 +73,4 @@ private: #endif // LMMS_HAVE_MP3LAME -#endif +#endif // LMMS_AUDIO_FILE_MP3_H diff --git a/include/AudioFileOgg.h b/include/AudioFileOgg.h index 18617fa5f..fc3ce25b4 100644 --- a/include/AudioFileOgg.h +++ b/include/AudioFileOgg.h @@ -23,8 +23,8 @@ * */ -#ifndef AUDIO_FILE_OGG_H -#define AUDIO_FILE_OGG_H +#ifndef LMMS_AUDIO_FILE_OGG_H +#define LMMS_AUDIO_FILE_OGG_H #include "lmmsconfig.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(); @@ -113,4 +111,4 @@ private: #endif // LMMS_HAVE_OGGVORBIS -#endif +#endif // LMMS_AUDIO_FILE_OGG_H diff --git a/include/AudioFileWave.h b/include/AudioFileWave.h index 8dd3566f4..22b124f93 100644 --- a/include/AudioFileWave.h +++ b/include/AudioFileWave.h @@ -23,8 +23,8 @@ * */ -#ifndef AUDIO_FILE_WAVE_H -#define AUDIO_FILE_WAVE_H +#ifndef LMMS_AUDIO_FILE_WAVE_H +#define LMMS_AUDIO_FILE_WAVE_H #include "lmmsconfig.h" #include "AudioFileDevice.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(); @@ -71,4 +69,4 @@ private: } // namespace lmms -#endif +#endif // LMMS_AUDIO_FILE_WAVE_H diff --git a/include/AudioJack.h b/include/AudioJack.h index 263399487..6efb262ed 100644 --- a/include/AudioJack.h +++ b/include/AudioJack.h @@ -22,8 +22,8 @@ * */ -#ifndef AUDIO_JACK_H -#define AUDIO_JACK_H +#ifndef LMMS_AUDIO_JACK_H +#define LMMS_AUDIO_JACK_H #include "lmmsconfig.h" @@ -35,7 +35,7 @@ #endif #include -#include +#include #include "AudioDevice.h" #include "AudioDeviceSetupWidget.h" @@ -57,42 +57,37 @@ class AudioJack : public QObject, public AudioDevice { Q_OBJECT public: - AudioJack( bool & _success_ful, AudioEngine* audioEngine ); + AudioJack(bool& successful, AudioEngine* audioEngine); ~AudioJack() override; // this is to allow the jack midi connection to use the same jack client connection // the jack callback is handled here, we call the midi client so that it can read // it's midi data during the callback - AudioJack * addMidiClient(MidiJack *midiClient); + AudioJack* addMidiClient(MidiJack* midiClient); void removeMidiClient() { m_midiClient = nullptr; } - jack_client_t * jackClient() {return m_client;}; + jack_client_t* jackClient() { return m_client; }; inline static QString name() { - return QT_TRANSLATE_NOOP( "AudioDeviceSetupWidget", - "JACK (JACK Audio Connection Kit)" ); + return QT_TRANSLATE_NOOP("AudioDeviceSetupWidget", "JACK (JACK Audio Connection Kit)"); } - -class setupWidget : public gui::AudioDeviceSetupWidget + class setupWidget : public gui::AudioDeviceSetupWidget { public: - setupWidget( QWidget * _parent ); + setupWidget(QWidget* parent); ~setupWidget() override; void saveSettings() override; private: - QLineEdit * m_clientName; - gui::LcdSpinBox * m_channels; - - } ; - + QLineEdit* m_clientName; + gui::LcdSpinBox* m_channels; + }; private slots: void restartAfterZombified(); - private: bool initJackClient(); @@ -100,48 +95,44 @@ private: 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; - QVector 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 #endif // LMMS_HAVE_JACK -#endif +#endif // LMMS_AUDIO_JACK_H diff --git a/include/AudioOss.h b/include/AudioOss.h index 71103586a..55f64de85 100644 --- a/include/AudioOss.h +++ b/include/AudioOss.h @@ -22,8 +22,8 @@ * */ -#ifndef AUDIO_OSS_H -#define AUDIO_OSS_H +#ifndef LMMS_AUDIO_OSS_H +#define LMMS_AUDIO_OSS_H #include "lmmsconfig.h" @@ -92,4 +92,4 @@ private: #endif // LMMS_HAVE_OSS -#endif +#endif // LMMS_AUDIO_OSS_H diff --git a/include/AudioPort.h b/include/AudioPort.h index 5a2645784..9e3ce2bd6 100644 --- a/include/AudioPort.h +++ b/include/AudioPort.h @@ -22,14 +22,13 @@ * */ -#ifndef AUDIO_PORT_H -#define AUDIO_PORT_H +#ifndef LMMS_AUDIO_PORT_H +#define LMMS_AUDIO_PORT_H #include #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, @@ -138,4 +136,4 @@ private: } // namespace lmms -#endif +#endif // LMMS_AUDIO_PORT_H diff --git a/include/AudioPortAudio.h b/include/AudioPortAudio.h index 3fd32a7ac..01b8f3fd7 100644 --- a/include/AudioPortAudio.h +++ b/include/AudioPortAudio.h @@ -22,8 +22,8 @@ * */ -#ifndef AUDIO_PORTAUDIO_H -#define AUDIO_PORTAUDIO_H +#ifndef LMMS_AUDIO_PORTAUDIO_H +#define LMMS_AUDIO_PORTAUDIO_H #include @@ -163,4 +163,4 @@ private: } // namespace lmms -#endif +#endif // LMMS_AUDIO_PORTAUDIO_H diff --git a/include/AudioPulseAudio.h b/include/AudioPulseAudio.h index 789296e27..b6a998274 100644 --- a/include/AudioPulseAudio.h +++ b/include/AudioPulseAudio.h @@ -22,8 +22,8 @@ * */ -#ifndef AUDIO_PULSEAUDIO_H -#define AUDIO_PULSEAUDIO_H +#ifndef LMMS_AUDIO_PULSEAUDIO_H +#define LMMS_AUDIO_PULSEAUDIO_H #include "lmmsconfig.h" @@ -104,4 +104,4 @@ private: #endif // LMMS_HAVE_PULSEAUDIO -#endif +#endif // LMMS_AUDIO_PULSEAUDIO_H 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 7ad551916..a3e776881 100644 --- a/include/AudioSampleRecorder.h +++ b/include/AudioSampleRecorder.h @@ -23,11 +23,12 @@ * */ -#ifndef AUDIO_SAMPLE_RECORDER_H -#define AUDIO_SAMPLE_RECORDER_H +#ifndef LMMS_AUDIO_SAMPLE_RECORDER_H +#define LMMS_AUDIO_SAMPLE_RECORDER_H #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; @@ -59,4 +57,4 @@ private: } // namespace lmms -#endif +#endif // LMMS_AUDIO_SAMPLE_RECORDER_H diff --git a/include/AudioSdl.h b/include/AudioSdl.h index 7e7710a2f..62db8b68a 100644 --- a/include/AudioSdl.h +++ b/include/AudioSdl.h @@ -22,8 +22,8 @@ * */ -#ifndef AUDIO_SDL_H -#define AUDIO_SDL_H +#ifndef LMMS_AUDIO_SDL_H +#define LMMS_AUDIO_SDL_H #include "lmmsconfig.h" @@ -115,4 +115,4 @@ private: #endif // LMMS_HAVE_SDL -#endif +#endif // LMMS_AUDIO_SDL_H diff --git a/include/AudioSndio.h b/include/AudioSndio.h index 606850105..594ca94e7 100644 --- a/include/AudioSndio.h +++ b/include/AudioSndio.h @@ -23,8 +23,8 @@ * */ -#ifndef _AUDIO_SNDIO_H -#define _AUDIO_SNDIO_H +#ifndef LMMS_AUDIO_SNDIO_H +#define LMMS_AUDIO_SNDIO_H #include "lmmsconfig.h" @@ -87,6 +87,6 @@ private: } // namespace lmms -#endif // LMMS_HAVE_SNDIO +#endif // LMMS_HAVE_SNDIO -#endif // _AUDIO_SNDIO_H +#endif // LMMS_AUDIO_SNDIO_H diff --git a/include/AudioSoundIo.h b/include/AudioSoundIo.h index dc9afe7e4..b327f7d84 100644 --- a/include/AudioSoundIo.h +++ b/include/AudioSoundIo.h @@ -22,8 +22,8 @@ * */ -#ifndef AUDIO_SOUNDIO_H -#define AUDIO_SOUNDIO_H +#ifndef LMMS_AUDIO_SOUNDIO_H +#define LMMS_AUDIO_SOUNDIO_H #include @@ -145,4 +145,4 @@ private: #endif // LMMS_HAVE_SOUNDIO -#endif +#endif // LMMS_AUDIO_SOUNDIO_H diff --git a/include/AutomatableButton.h b/include/AutomatableButton.h index d8c58523c..3d78b8e9c 100644 --- a/include/AutomatableButton.h +++ b/include/AutomatableButton.h @@ -22,9 +22,8 @@ * */ - -#ifndef AUTOMATABLE_BUTTON_H -#define AUTOMATABLE_BUTTON_H +#ifndef LMMS_GUI_AUTOMATABLE_BUTTON_H +#define LMMS_GUI_AUTOMATABLE_BUTTON_H #include @@ -109,4 +108,4 @@ private: } // namespace lmms::gui -#endif +#endif // LMMS_GUI_AUTOMATABLE_BUTTON_H diff --git a/include/AutomatableModel.h b/include/AutomatableModel.h index c8f2ab548..15285e17a 100644 --- a/include/AutomatableModel.h +++ b/include/AutomatableModel.h @@ -22,17 +22,18 @@ * */ -#ifndef AUTOMATABLE_MODEL_H -#define AUTOMATABLE_MODEL_H +#ifndef LMMS_AUTOMATABLE_MODEL_H +#define LMMS_AUTOMATABLE_MODEL_H +#include #include #include +#include #include "JournallingObject.h" #include "Model.h" #include "TimePos.h" #include "ValueBuffer.h" -#include "MemoryManager.h" #include "ModelVisitor.h" @@ -76,11 +77,10 @@ class ControllerConnection; class LMMS_EXPORT AutomatableModel : public Model, public JournallingObject { Q_OBJECT - MM_OPERATORS public: - using AutoModelVector = QVector; + using AutoModelVector = std::vector; - enum ScaleType + enum class ScaleType { Linear, Logarithmic, @@ -144,7 +144,7 @@ public: template static bool castValue( const float v ) { - return ( qRound( v ) != 0 ); + return (std::round(v) != 0); } @@ -231,11 +231,11 @@ public: } void setScaleLogarithmic( bool setToTrue = true ) { - setScaleType( setToTrue ? Logarithmic : Linear ); + setScaleType( setToTrue ? ScaleType::Logarithmic : ScaleType::Linear ); } bool isScaleLogarithmic() const { - return m_scaleType == Logarithmic; + return m_scaleType == ScaleType::Logarithmic; } void setStep( const float step ); @@ -507,5 +507,4 @@ using AutomatedValueMap = QMap; } // namespace lmms -#endif - +#endif // LMMS_AUTOMATABLE_MODEL_H diff --git a/include/AutomatableModelView.h b/include/AutomatableModelView.h index 1e8ef7398..12b2e4d49 100644 --- a/include/AutomatableModelView.h +++ b/include/AutomatableModelView.h @@ -23,8 +23,8 @@ * */ -#ifndef AUTOMATABLE_MODEL_VIEW_H -#define AUTOMATABLE_MODEL_VIEW_H +#ifndef LMMS_GUI_AUTOMATABLE_MODEL_VIEW_H +#define LMMS_GUI_AUTOMATABLE_MODEL_VIEW_H #include "ModelView.h" #include "AutomatableModel.h" @@ -137,5 +137,4 @@ using BoolModelView = TypedModelView; } // namespace lmms::gui -#endif - +#endif // LMMS_GUI_AUTOMATABLE_MODEL_VIEW_H diff --git a/include/AutomatableSlider.h b/include/AutomatableSlider.h index ba11741b5..cb1a6965f 100644 --- a/include/AutomatableSlider.h +++ b/include/AutomatableSlider.h @@ -22,9 +22,8 @@ * */ - -#ifndef AUTOMATABLE_SLIDER_H -#define AUTOMATABLE_SLIDER_H +#ifndef LMMS_GUI_AUTOMATABLE_SLIDER_H +#define LMMS_GUI_AUTOMATABLE_SLIDER_H #include @@ -77,4 +76,4 @@ using sliderModel = IntModel; } // namespace lmms::gui -#endif +#endif // LMMS_GUI_AUTOMATABLE_SLIDER_H diff --git a/include/AutomationClip.h b/include/AutomationClip.h index 3e253d85f..0b49978c7 100644 --- a/include/AutomationClip.h +++ b/include/AutomationClip.h @@ -24,8 +24,8 @@ * */ -#ifndef AUTOMATION_CLIP_H -#define AUTOMATION_CLIP_H +#ifndef LMMS_AUTOMATION_CLIP_H +#define LMMS_AUTOMATION_CLIP_H #include #include @@ -46,6 +46,7 @@ class TimePos; namespace gui { class AutomationClipView; +class AutomationEditor; } // namespace gui @@ -54,15 +55,15 @@ class LMMS_EXPORT AutomationClip : public Clip { Q_OBJECT public: - enum ProgressionTypes + enum class ProgressionType { - DiscreteProgression, - LinearProgression, - CubicHermiteProgression + Discrete, + Linear, + CubicHermite } ; using timeMap = QMap; - using objectVector = QVector>; + using objectVector = std::vector>; using TimemapIterator = timeMap::const_iterator; @@ -76,11 +77,11 @@ public: const objectVector& objects() const; // progression-type stuff - inline ProgressionTypes progressionType() const + inline ProgressionType progressionType() const { return m_progressionType; } - void setProgressionType( ProgressionTypes _new_progression_type ); + void setProgressionType( ProgressionType _new_progression_type ); inline float getTension() const { @@ -111,6 +112,13 @@ public: void resetNodes(const int tick0, const int tick1); + /** + * @brief Resets the tangents from the nodes between the given ticks + * @param Int first tick of the range + * @param Int second tick of the range + */ + void resetTangents(const int tick0, const int tick1); + void recordValue(TimePos time, float value); TimePos setDragValue( const TimePos & time, @@ -151,10 +159,21 @@ public: return m_timeMap.isEmpty() == false; } + static bool supportsTangentEditing(ProgressionType pType) + { + // Update function if we have new progression types that support tangent editing + return pType == ProgressionType::CubicHermite; + } + + inline bool canEditTangents() const + { + return supportsTangentEditing(m_progressionType); + } + float valueAt( const TimePos & _time ) const; float *valuesAfter( const TimePos & _time ) const; - const QString name() const; + QString name() const; // settings-management void saveSettings( QDomDocument & _doc, QDomElement & _parent ) override; @@ -167,7 +186,7 @@ public: static bool isAutomated( const AutomatableModel * _m ); - static QVector clipsForModel( const AutomatableModel * _m ); + static std::vector clipsForModel(const AutomatableModel* _m); static AutomationClip * globalAutomationClip( AutomatableModel * _m ); static void resolveAllIDs(); @@ -190,6 +209,15 @@ private: void generateTangents(timeMap::iterator it, int numToGenerate); float valueAt( timeMap::const_iterator v, int offset ) const; + /** + * @brief + * This function combines the song tracks, pattern store tracks, + * and the global automation track all in one vector. + * + * @return std::vector + */ + static std::vector combineAllTracks(); + // Mutex to make methods involving automation clips thread safe // Mutable so we can lock it from const objects #if (QT_VERSION >= QT_VERSION_CHECK(5,14,0)) @@ -199,17 +227,20 @@ private: #endif AutomationTrack * m_autoTrack; - QVector m_idsToResolve; + std::vector m_idsToResolve; objectVector m_objects; timeMap m_timeMap; // actual values timeMap m_oldTimeMap; // old values for storing the values before setDragValue() is called. float m_tension; bool m_hasAutomation; - ProgressionTypes m_progressionType; + ProgressionType m_progressionType; bool m_dragging; bool m_dragKeepOutValue; // Should we keep the current dragged node's outValue? float m_dragOutValue; // The outValue of the dragged node's + bool m_dragLockedTan; // If the dragged node has it's tangents locked + float m_dragInTan; // The dragged node's inTangent + float m_dragOutTan; // The dragged node's outTangent bool m_isRecording; float m_lastRecordedValue; @@ -221,6 +252,7 @@ private: friend class gui::AutomationClipView; friend class AutomationNode; + friend class gui::AutomationEditor; } ; @@ -252,6 +284,11 @@ inline float OUTTAN(AutomationClip::TimemapIterator it) return it->getOutTangent(); } +inline float LOCKEDTAN(AutomationClip::TimemapIterator it) +{ + return it->lockedTangents(); +} + inline int POS(AutomationClip::TimemapIterator it) { return it.key(); @@ -260,4 +297,4 @@ inline int POS(AutomationClip::TimemapIterator it) } // namespace lmms -#endif +#endif // LMMS_AUTOMATION_CLIP_H diff --git a/include/AutomationClipView.h b/include/AutomationClipView.h index b4de7839c..bdd2f0568 100644 --- a/include/AutomationClipView.h +++ b/include/AutomationClipView.h @@ -22,8 +22,8 @@ * */ -#ifndef AUTOMATION_CLIP_VIEW_H -#define AUTOMATION_CLIP_VIEW_H +#ifndef LMMS_GUI_AUTOMATION_CLIP_VIEW_H +#define LMMS_GUI_AUTOMATION_CLIP_VIEW_H #include @@ -74,9 +74,6 @@ private: QPixmap m_paintPixmap; QStaticText m_staticTextName; - - static QPixmap * s_clip_rec; - void scaleTimemapToFit( float oldMin, float oldMax ); } ; @@ -85,4 +82,4 @@ private: } // namespace lmms -#endif +#endif // LMMS_GUI_AUTOMATION_CLIP_VIEW_H diff --git a/include/AutomationEditor.h b/include/AutomationEditor.h index 52609416f..1110e8e4c 100644 --- a/include/AutomationEditor.h +++ b/include/AutomationEditor.h @@ -23,18 +23,21 @@ * */ -#ifndef AUTOMATION_EDITOR_H -#define AUTOMATION_EDITOR_H +#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; @@ -62,12 +65,18 @@ class AutomationEditor : public QWidget, public JournallingObject Q_PROPERTY(QColor lineColor MEMBER m_lineColor) Q_PROPERTY(QColor nodeInValueColor MEMBER m_nodeInValueColor) Q_PROPERTY(QColor nodeOutValueColor MEMBER m_nodeOutValueColor) + Q_PROPERTY(QColor nodeTangentLineColor MEMBER m_nodeTangentLineColor) Q_PROPERTY(QBrush scaleColor MEMBER m_scaleColor) Q_PROPERTY(QBrush graphColor MEMBER m_graphColor) Q_PROPERTY(QColor crossColor MEMBER m_crossColor) 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 { @@ -86,11 +95,12 @@ public: return "automationeditor"; } - enum EditModes + enum class EditMode { - DRAW, - ERASE, - DRAW_OUTVALUES + Draw, + Erase, + DrawOutValues, + EditTangents }; public slots: @@ -117,6 +127,13 @@ protected: inline void drawLevelTick(QPainter & p, int tick, float value); timeMap::iterator getNodeAt(int x, int y, bool outValue = false, int r = 5); + /** + * @brief Given a mouse X coordinate, returns a timeMap::iterator that points to + * the closest node. + * @param Int X coordinate + * @return timeMap::iterator with the closest node or timeMap.end() if there are no nodes. + */ + timeMap::iterator getClosestNode(int x); void drawLine( int x0, float y0, int x1, float y1 ); bool fineTuneValue(timeMap::iterator node, bool editingOutValue); @@ -128,10 +145,16 @@ protected slots: void horScrolled( int new_pos ); void verScrolled( int new_pos ); - void setEditMode(AutomationEditor::EditModes mode); + void setEditMode(AutomationEditor::EditMode mode); void setEditMode(int mode); - void setProgressionType(AutomationClip::ProgressionTypes type); + void setProgressionType(AutomationClip::ProgressionType type); + /** + * @brief This method handles the AutomationEditorWindow event of changing + * progression types. After that, it calls updateEditTanButton so the edit + * tangents button is updated accordingly + * @param Int New progression type + */ void setProgressionType(int type); void setTension(); @@ -143,16 +166,25 @@ 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 Actions + enum class Action { - NONE, - MOVE_VALUE, - ERASE_VALUES, - MOVE_OUTVALUE, - RESET_OUTVALUES, - DRAW_LINE + None, + MoveValue, + EraseValues, + MoveOutValue, + ResetOutValues, + DrawLine, + MoveTangent, + ResetTangents } ; // some constants... @@ -165,22 +197,29 @@ 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_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; ComboBoxModel m_quantizeModel; - static const QVector m_zoomXLevels; + static const std::array m_zoomXLevels; FloatModel * m_tensionModel; @@ -192,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(); @@ -200,7 +243,7 @@ private: TimePos m_currentPosition; - Actions m_action; + Action m_action; int m_moveXOffset; @@ -214,7 +257,12 @@ private: // Time position (key) of automation node whose outValue is being dragged int m_draggedOutValueKey; - EditModes m_editMode; + // The tick from the node whose tangent is being dragged + int m_draggedTangentTick; + // Whether the tangent being dragged is the InTangent or OutTangent + bool m_draggedOutTangent; + + EditMode m_editMode; bool m_mouseDownLeft; bool m_mouseDownRight; //true if right click is being held down @@ -224,6 +272,7 @@ private: void drawCross(QPainter & p ); void drawAutomationPoint( QPainter & p, timeMap::iterator it ); + void drawAutomationTangents(QPainter& p, timeMap::iterator it); bool inPatternEditor(); QColor m_barLineColor; @@ -232,9 +281,13 @@ private: QBrush m_graphColor; QColor m_nodeInValueColor; QColor m_nodeOutValueColor; + QColor m_nodeTangentLineColor; QBrush m_scaleColor; QColor m_crossColor; QColor m_backgroundShade; + QColor m_ghostNoteColor; + QColor m_detuningNoteColor; + QColor m_ghostSampleColor; friend class AutomationEditorWindow; @@ -258,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; @@ -284,8 +340,21 @@ protected slots: private slots: void updateWindowTitle(); + void setProgressionType(int progType); + /** + * @brief The Edit Tangent edit mode should only be available for + * Cubic Hermite progressions, so this method is responsable for disabling it + * for other edit modes and reenabling it when it changes back to the Edit Tangent + * mode. + */ + void updateEditTanButton(); private: + QAction* m_drawAction; + QAction* m_eraseAction; + QAction* m_drawOutAction; + QAction* m_editTanAction; + QAction* m_discreteAction; QAction* m_linearAction; QAction* m_cubicHermiteAction; @@ -298,10 +367,12 @@ private: ComboBox * m_zoomingXComboBox; ComboBox * m_zoomingYComboBox; ComboBox * m_quantizeComboBox; + + QPushButton* m_resetGhostNotes; }; } // namespace gui } // namespace lmms -#endif +#endif // LMMS_GUI_AUTOMATION_EDITOR_H diff --git a/include/AutomationNode.h b/include/AutomationNode.h index 11bd6d57d..60154332f 100644 --- a/include/AutomationNode.h +++ b/include/AutomationNode.h @@ -23,8 +23,8 @@ * */ -#ifndef AUTOMATION_NODE_H -#define AUTOMATION_NODE_H +#ifndef LMMS_AUTOMATION_NODE_H +#define LMMS_AUTOMATION_NODE_H namespace lmms { @@ -125,6 +125,22 @@ public: m_outTangent = tangent; } + /** + * @brief Checks if the tangents from the node are locked + */ + inline const bool lockedTangents() const + { + return m_lockedTangents; + } + + /** + * @brief Locks or Unlocks the tangents from this node + */ + inline void setLockedTangents(bool b) + { + m_lockedTangents = b; + } + /** * @brief Sets the clip this node belongs to * @param AutomationClip* clip that m_clip will be @@ -152,8 +168,13 @@ private: // outValue are equal, inTangent and outTangent are equal too. float m_inTangent; float m_outTangent; + + // If the tangents were edited manually, this will be true. That way + // the tangents from this node will not be recalculated. It's set back + // to false if the tangents are reset. + bool m_lockedTangents; }; } // namespace lmms -#endif +#endif // LMMS_AUTOMATION_NODE_H diff --git a/include/AutomationTrack.h b/include/AutomationTrack.h index 1779bf102..64c2cc43a 100644 --- a/include/AutomationTrack.h +++ b/include/AutomationTrack.h @@ -24,8 +24,8 @@ * */ -#ifndef AUTOMATION_TRACK_H -#define AUTOMATION_TRACK_H +#ifndef LMMS_AUTOMATION_TRACK_H +#define LMMS_AUTOMATION_TRACK_H #include "Track.h" @@ -62,4 +62,4 @@ private: } // namespace lmms -#endif +#endif // LMMS_AUTOMATION_TRACK_H diff --git a/include/AutomationTrackView.h b/include/AutomationTrackView.h index 18454bec2..449e9bef8 100644 --- a/include/AutomationTrackView.h +++ b/include/AutomationTrackView.h @@ -23,8 +23,8 @@ * */ -#ifndef AUTOMATION_TRACK_VIEW_H -#define AUTOMATION_TRACK_VIEW_H +#ifndef LMMS_GUI_AUTOMATION_TRACK_VIEW_H +#define LMMS_GUI_AUTOMATION_TRACK_VIEW_H #include "TrackView.h" @@ -52,4 +52,4 @@ public: } // namespace lmms -#endif +#endif // LMMS_GUI_AUTOMATION_TRACK_VIEW_H diff --git a/include/BandLimitedWave.h b/include/BandLimitedWave.h index 588fc4eea..1f402aa6e 100644 --- a/include/BandLimitedWave.h +++ b/include/BandLimitedWave.h @@ -23,8 +23,8 @@ * */ -#ifndef BANDLIMITEDWAVE_H -#define BANDLIMITEDWAVE_H +#ifndef LMMS_BANDLIMITEDWAVE_H +#define LMMS_BANDLIMITEDWAVE_H class QDataStream; class QString; @@ -89,14 +89,15 @@ QDataStream& operator>> ( QDataStream &in, WaveMipMap &waveMipMap ); class LMMS_EXPORT BandLimitedWave { public: - enum Waveforms + enum class Waveform { BLSaw, BLSquare, BLTriangle, BLMoog, - NumBLWaveforms + Count }; + constexpr static auto NumWaveforms = static_cast(Waveform::Count); BandLimitedWave() = default; virtual ~BandLimitedWave() = default; @@ -127,7 +128,7 @@ public: * \param _wavelen The wavelength (length of one cycle, ie. the inverse of frequency) of the wanted oscillation, measured in sample frames * \param _wave The wanted waveform. Options currently are saw, triangle, square and moog saw. */ - static inline sample_t oscillate( float _ph, float _wavelen, Waveforms _wave ) + static inline sample_t oscillate( float _ph, float _wavelen, Waveform _wave ) { // get the next higher tlen int t = 0; @@ -139,27 +140,28 @@ public: int lookup = static_cast( lookupf ); const float ip = fraction( lookupf ); - const sample_t s1 = s_waveforms[ _wave ].sampleAt( t, lookup ); - const sample_t s2 = s_waveforms[ _wave ].sampleAt( t, ( lookup + 1 ) % tlen ); + const sample_t s1 = s_waveforms[ static_cast(_wave) ].sampleAt( t, lookup ); + const sample_t s2 = s_waveforms[ static_cast(_wave) ].sampleAt( t, ( lookup + 1 ) % tlen ); const int lm = lookup == 0 ? tlen - 1 : lookup - 1; - const sample_t s0 = s_waveforms[ _wave ].sampleAt( t, lm ); - const sample_t s3 = s_waveforms[ _wave ].sampleAt( t, ( lookup + 2 ) % tlen ); + const sample_t s0 = s_waveforms[ static_cast(_wave) ].sampleAt( t, lm ); + const sample_t s3 = s_waveforms[ static_cast(_wave) ].sampleAt( t, ( lookup + 2 ) % tlen ); const sample_t sr = optimal4pInterpolate( s0, s1, s2, s3, ip ); return sr; -/* lookup = lookup << 1; + /* + lookup = lookup << 1; tlen = tlen << 1; t += 1; - const sample_t s3 = s_waveforms[ _wave ].sampleAt( t, lookup ); - const sample_t s4 = s_waveforms[ _wave ].sampleAt( t, ( lookup + 1 ) % tlen ); + const sample_t s3 = s_waveforms[ static_cast(_wave) ].sampleAt( t, lookup ); + const sample_t s4 = s_waveforms[ static_cast(_wave) ].sampleAt( t, ( lookup + 1 ) % tlen ); const sample_t s34 = linearInterpolate( s3, s4, ip ); const float ip2 = ( ( tlen - _wavelen ) / tlen - 0.5 ) * 2.0; return linearInterpolate( s12, s34, ip2 ); - */ + */ }; @@ -167,11 +169,11 @@ public: static bool s_wavesGenerated; - static std::array s_waveforms; + static std::array s_waveforms; static QString s_wavetableDir; }; } // namespace lmms -#endif +#endif // LMMS_BANDLIMITEDWAVE_H 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 836d758b6..25dcf834c 100644 --- a/include/BasicFilters.h +++ b/include/BasicFilters.h @@ -28,9 +28,8 @@ * */ - -#ifndef BASIC_FILTERS_H -#define BASIC_FILTERS_H +#ifndef LMMS_BASIC_FILTERS_H +#define LMMS_BASIC_FILTERS_H #ifndef __USE_XOPEN #define __USE_XOPEN @@ -41,7 +40,6 @@ #include "lmms_basics.h" #include "lmms_constants.h" #include "interpolation.h" -#include "MemoryManager.h" namespace lmms { @@ -51,7 +49,6 @@ template class BasicFilters; template class LinkwitzRiley { - MM_OPERATORS public: LinkwitzRiley( float sampleRate ) { @@ -146,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(); } @@ -189,7 +190,6 @@ using StereoBiQuad = BiQuad<2>; template class OnePole { - MM_OPERATORS public: OnePole() { @@ -210,7 +210,7 @@ public: inline float update( float s, ch_cnt_t ch ) { - if( qAbs( s ) < 1.0e-10f && qAbs( m_z1[ch] ) < 1.0e-10f ) return 0.0f; + if (std::abs(s) < 1.0e-10f && std::abs(m_z1[ch]) < 1.0e-10f) return 0.0f; return m_z1[ch] = s * m_a0 + m_z1[ch] * m_b1; } @@ -223,9 +223,8 @@ using StereoOnePole = OnePole<2>; template class BasicFilters { - MM_OPERATORS public: - enum FilterTypes + enum class FilterType { LowPass, HiPass, @@ -248,8 +247,7 @@ public: Highpass_SV, Notch_SV, FastFormant, - Tripole, - NumFilters + Tripole }; static inline float minFreq() @@ -262,20 +260,20 @@ public: return( 0.01f ); } - inline void setFilterType( const int _idx ) + inline void setFilterType( const FilterType _idx ) { - m_doubleFilter = _idx == DoubleLowPass || _idx == DoubleMoog; + m_doubleFilter = _idx == FilterType::DoubleLowPass || _idx == FilterType::DoubleMoog; if( !m_doubleFilter ) { - m_type = static_cast( _idx ); + m_type = _idx; return; } // Double lowpass mode, backwards-compat for the goofy // Add-NumFilters to signify doubleFilter stuff - m_type = _idx == DoubleLowPass - ? LowPass - : Moog; + m_type = _idx == FilterType::DoubleLowPass + ? FilterType::LowPass + : FilterType::Moog; if( m_subFilter == nullptr ) { m_subFilter = new BasicFilters( @@ -330,33 +328,39 @@ 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 Moog: + case FilterType::Moog: { sample_t x = _in0 - m_r*m_y4[_chnl]; // four cascaded onepole filters // (bilinear transform) - m_y1[_chnl] = qBound( -10.0f, - ( x + m_oldx[_chnl] ) * m_p - - m_k * m_y1[_chnl], - 10.0f ); - m_y2[_chnl] = qBound( -10.0f, - ( m_y1[_chnl] + m_oldy1[_chnl] ) * m_p - - m_k * m_y2[_chnl], - 10.0f ); - m_y3[_chnl] = qBound( -10.0f, - ( m_y2[_chnl] + m_oldy2[_chnl] ) * m_p - - m_k * m_y3[_chnl], - 10.0f ); - m_y4[_chnl] = qBound( -10.0f, - ( m_y3[_chnl] + m_oldy3[_chnl] ) * m_p - - m_k * m_y4[_chnl], + m_y1[_chnl] = std::clamp((x + m_oldx[_chnl]) * m_p + - m_k * m_y1[_chnl], -10.0f, + 10.0f); + m_y2[_chnl] = std::clamp((m_y1[_chnl] + m_oldy1[_chnl]) * m_p + - m_k * m_y2[_chnl], -10.0f, + 10.0f); + m_y3[_chnl] = std::clamp((m_y2[_chnl] + m_oldy2[_chnl]) * m_p + - m_k * m_y3[_chnl], -10.0f, 10.0f ); + m_y4[_chnl] = std::clamp((m_y3[_chnl] + m_oldy3[_chnl]) * m_p + - m_k * m_y4[_chnl], -10.0f, + 10.0f); m_oldx[_chnl] = x; m_oldy1[_chnl] = m_y1[_chnl]; @@ -369,27 +373,23 @@ public: // 3x onepole filters with 4x oversampling and interpolation of oversampled signal: // input signal is linear-interpolated after oversampling, output signal is averaged from oversampled outputs - case Tripole: + case FilterType::Tripole: { - out = 0.0f; float ip = 0.0f; for( int i = 0; i < 4; ++i ) { ip += 0.25f; sample_t x = linearInterpolate( m_last[_chnl], _in0, ip ) - m_r * m_y3[_chnl]; - m_y1[_chnl] = qBound( -10.0f, - ( x + m_oldx[_chnl] ) * m_p - - m_k * m_y1[_chnl], - 10.0f ); - m_y2[_chnl] = qBound( -10.0f, - ( m_y1[_chnl] + m_oldy1[_chnl] ) * m_p - - m_k * m_y2[_chnl], - 10.0f ); - m_y3[_chnl] = qBound( -10.0f, - ( m_y2[_chnl] + m_oldy2[_chnl] ) * m_p - - m_k * m_y3[_chnl], - 10.0f ); + m_y1[_chnl] = std::clamp((x + m_oldx[_chnl]) * m_p + - m_k * m_y1[_chnl], -10.0f, + 10.0f); + m_y2[_chnl] = std::clamp((m_y1[_chnl] + m_oldy1[_chnl]) * m_p + - m_k * m_y2[_chnl], -10.0f, + 10.0f); + m_y3[_chnl] = std::clamp((m_y2[_chnl] + m_oldy2[_chnl]) * m_p + - m_k * m_y3[_chnl], -10.0f, + 10.0f); m_oldx[_chnl] = x; m_oldy1[_chnl] = m_y1[_chnl]; m_oldy2[_chnl] = m_y2[_chnl]; @@ -405,8 +405,8 @@ public: // and extended to other SV filter types // /* Hal Chamberlin's state variable filter */ - case Lowpass_SV: - case Bandpass_SV: + case FilterType::Lowpass_SV: + case FilterType::Bandpass_SV: { float highpass; @@ -422,15 +422,14 @@ public: } /* mix filter output into output buffer */ - return m_type == Lowpass_SV + return m_type == FilterType::Lowpass_SV ? m_delay4[_chnl] : m_delay3[_chnl]; } - case Highpass_SV: + 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]; @@ -441,10 +440,9 @@ public: return hp; } - case Notch_SV: + 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 */ @@ -452,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]; } @@ -466,22 +464,22 @@ public: // can be driven up to self-oscillation (BTW: do not remove the limits!!!). // (C) 1998 ... 2009 S.Fendt. Released under the GPL v2.0 or any later version. - case Lowpass_RC12: + 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; - in = qBound( -1.0f, in, 1.0f ); + 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 = qBound( -1.0f, lp, 1.0f ); + lp = std::clamp(lp, -1.0f, 1.0f); - hp = m_rcc * ( m_rchp0[_chnl] + in - m_rclast0[_chnl] ); - hp = qBound( -1.0f, hp, 1.0f ); + 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; - bp = qBound( -1.0f, bp, 1.0f ); + sample_t bp = hp * m_rcb + m_rcbp0[_chnl] * m_rca; + bp = std::clamp(bp, -1.0f, 1.0f); m_rclast0[_chnl] = in; m_rclp0[_chnl] = lp; @@ -490,45 +488,45 @@ public: } return lp; } - case Highpass_RC12: - case Bandpass_RC12: + 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; - in = qBound( -1.0f, in, 1.0f ); + 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] ); - hp = qBound( -1.0f, hp, 1.0f ); + hp = std::clamp(hp, -1.0f, 1.0f); bp = hp * m_rcb + m_rcbp0[_chnl] * m_rca; - bp = qBound( -1.0f, bp, 1.0f ); + bp = std::clamp(bp, -1.0f, 1.0f); m_rclast0[_chnl] = in; m_rchp0[_chnl] = hp; m_rcbp0[_chnl] = bp; } - return m_type == Highpass_RC12 ? hp : bp; + return m_type == FilterType::Highpass_RC12 ? hp : bp; } - case Lowpass_RC24: + 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; - in = qBound( -1.0f, in, 1.0f ); + 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 = qBound( -1.0f, lp, 1.0f ); + lp = std::clamp(lp, -1.0f, 1.0f); - hp = m_rcc * ( m_rchp0[_chnl] + in - m_rclast0[_chnl] ); - hp = qBound( -1.0f, hp, 1.0f ); + 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; - bp = qBound( -1.0f, bp, 1.0f ); + sample_t bp = hp * m_rcb + m_rcbp0[_chnl] * m_rca; + bp = std::clamp(bp, -1.0f, 1.0f); m_rclast0[_chnl] = in; m_rclp0[_chnl] = lp; @@ -537,16 +535,16 @@ public: // second stage gets the output of the first stage as input... in = lp + m_rcbp1[_chnl] * m_rcq; - in = qBound( -1.0f, in, 1.0f ); + in = std::clamp(in, -1.0f, 1.0f ); lp = in * m_rcb + m_rclp1[_chnl] * m_rca; - lp = qBound( -1.0f, lp, 1.0f ); + lp = std::clamp(lp, -1.0f, 1.0f); hp = m_rcc * ( m_rchp1[_chnl] + in - m_rclast1[_chnl] ); - hp = qBound( -1.0f, hp, 1.0f ); + hp = std::clamp(hp, -1.0f, 1.0f); bp = hp * m_rcb + m_rcbp1[_chnl] * m_rca; - bp = qBound( -1.0f, bp, 1.0f ); + bp = std::clamp(bp, -1.0f, 1.0f); m_rclast1[_chnl] = in; m_rclp1[_chnl] = lp; @@ -555,91 +553,89 @@ public: } return lp; } - case Highpass_RC24: - case Bandpass_RC24: + 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; - in = qBound( -1.0f, in, 1.0f ); + 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] ); - hp = qBound( -1.0f, hp, 1.0f ); + hp = std::clamp(hp, -1.0f, 1.0f); bp = hp * m_rcb + m_rcbp0[_chnl] * m_rca; - bp = qBound( -1.0f, bp, 1.0f ); + bp = std::clamp(bp, -1.0f, 1.0f); m_rclast0[_chnl] = in; m_rchp0[_chnl] = hp; m_rcbp0[_chnl] = bp; // second stage gets the output of the first stage as input... - in = m_type == Highpass_RC24 + in = m_type == FilterType::Highpass_RC24 ? hp + m_rcbp1[_chnl] * m_rcq : bp + m_rcbp1[_chnl] * m_rcq; - in = qBound( -1.0f, in, 1.0f ); + in = std::clamp(in, -1.0f, 1.0f); hp = m_rcc * ( m_rchp1[_chnl] + in - m_rclast1[_chnl] ); - hp = qBound( -1.0f, hp, 1.0f ); + hp = std::clamp(hp, -1.0f, 1.0f); bp = hp * m_rcb + m_rcbp1[_chnl] * m_rca; - bp = qBound( -1.0f, bp, 1.0f ); + bp = std::clamp(bp, -1.0f, 1.0f); m_rclast1[_chnl] = in; m_rchp1[_chnl] = hp; m_rcbp1[_chnl] = bp; } - return m_type == Highpass_RC24 ? hp : bp; + return m_type == FilterType::Highpass_RC24 ? hp : bp; } - case Formantfilter: - case FastFormant: + case FilterType::Formantfilter: + case FilterType::FastFormant: { - if( qAbs( _in0 ) < 1.0e-10f && qAbs( 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; + 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 - out = 0; - const int os = m_type == FastFormant ? 1 : 4; // no oversampling for fast formant + 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; - in = qBound( -1.0f, in, 1.0f ); + 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] ); - hp = qBound( -1.0f, hp, 1.0f ); + 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]; - bp = qBound( -1.0f, bp, 1.0f ); + 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; m_vfhp[0][_chnl] = hp; m_vfbp[0][_chnl] = bp; in = bp + m_vfbp[2][_chnl] * m_vfq; - in = qBound( -1.0f, in, 1.0f ); + in = std::clamp(in, -1.0f, 1.0f); hp = m_vfc[0] * ( m_vfhp[2][_chnl] + in - m_vflast[2][_chnl] ); - hp = qBound( -1.0f, hp, 1.0f ); + hp = std::clamp(hp, -1.0f, 1.0f); bp = hp * m_vfb[0] + m_vfbp[2][_chnl] * m_vfa[0]; - bp = qBound( -1.0f, bp, 1.0f ); + bp = std::clamp(bp, -1.0f, 1.0f); m_vflast[2][_chnl] = in; m_vfhp[2][_chnl] = hp; m_vfbp[2][_chnl] = bp; in = bp + m_vfbp[4][_chnl] * m_vfq; - in = qBound( -1.0f, in, 1.0f ); + in = std::clamp(in, -1.0f, 1.0f); hp = m_vfc[0] * ( m_vfhp[4][_chnl] + in - m_vflast[4][_chnl] ); - hp = qBound( -1.0f, hp, 1.0f ); + hp = std::clamp(hp, -1.0f, 1.0f); bp = hp * m_vfb[0] + m_vfbp[4][_chnl] * m_vfa[0]; - bp = qBound( -1.0f, bp, 1.0f ); + bp = std::clamp(bp, -1.0f, 1.0f); m_vflast[4][_chnl] = in; m_vfhp[4][_chnl] = hp; @@ -649,39 +645,39 @@ public: // second formant in = _in0 + m_vfbp[0][_chnl] * m_vfq; - in = qBound( -1.0f, in, 1.0f ); + in = std::clamp(in, -1.0f, 1.0f); hp = m_vfc[1] * ( m_vfhp[1][_chnl] + in - m_vflast[1][_chnl] ); - hp = qBound( -1.0f, hp, 1.0f ); + hp = std::clamp(hp, -1.0f, 1.0f); bp = hp * m_vfb[1] + m_vfbp[1][_chnl] * m_vfa[1]; - bp = qBound( -1.0f, bp, 1.0f ); + bp = std::clamp(bp, -1.0f, 1.0f); m_vflast[1][_chnl] = in; m_vfhp[1][_chnl] = hp; m_vfbp[1][_chnl] = bp; in = bp + m_vfbp[3][_chnl] * m_vfq; - in = qBound( -1.0f, in, 1.0f ); + in = std::clamp(in, -1.0f, 1.0f); hp = m_vfc[1] * ( m_vfhp[3][_chnl] + in - m_vflast[3][_chnl] ); - hp = qBound( -1.0f, hp, 1.0f ); + hp = std::clamp(hp, -1.0f, 1.0f); bp = hp * m_vfb[1] + m_vfbp[3][_chnl] * m_vfa[1]; - bp = qBound( -1.0f, bp, 1.0f ); + bp = std::clamp(bp, -1.0f, 1.0f); m_vflast[3][_chnl] = in; m_vfhp[3][_chnl] = hp; m_vfbp[3][_chnl] = bp; in = bp + m_vfbp[5][_chnl] * m_vfq; - in = qBound( -1.0f, in, 1.0f ); + in = std::clamp(in, -1.0f, 1.0f); hp = m_vfc[1] * ( m_vfhp[5][_chnl] + in - m_vflast[5][_chnl] ); - hp = qBound( -1.0f, hp, 1.0f ); + hp = std::clamp(hp, -1.0f, 1.0f); bp = hp * m_vfb[1] + m_vfbp[5][_chnl] * m_vfa[1]; - bp = qBound( -1.0f, bp, 1.0f ); + bp = std::clamp(bp, -1.0f, 1.0f); m_vflast[5][_chnl] = in; m_vfhp[5][_chnl] = hp; @@ -689,7 +685,7 @@ public: out += bp; } - return m_type == FastFormant ? out * 2.0f : out * 0.5f; + return m_type == FilterType::FastFormant ? out * 2.0f : out * 0.5f; } default: @@ -710,16 +706,16 @@ public: inline void calcFilterCoeffs( float _freq, float _q ) { // temp coef vars - _q = qMax( _q, minQ() ); + _q = std::max(_q, minQ()); - if( m_type == Lowpass_RC12 || - m_type == Bandpass_RC12 || - m_type == Highpass_RC12 || - m_type == Lowpass_RC24 || - m_type == Bandpass_RC24 || - m_type == Highpass_RC24 ) + if( m_type == FilterType::Lowpass_RC12 || + m_type == FilterType::Bandpass_RC12 || + m_type == FilterType::Highpass_RC12 || + m_type == FilterType::Lowpass_RC24 || + m_type == FilterType::Bandpass_RC24 || + m_type == FilterType::Highpass_RC24 ) { - _freq = qBound( 50.0f, _freq, 20000.0f ); + _freq = std::clamp(_freq, 50.0f, 20000.0f); const float sr = m_sampleRatio * 0.25f; const float f = 1.0f / ( _freq * F_2PI ); @@ -732,10 +728,10 @@ public: return; } - if( m_type == Formantfilter || - m_type == FastFormant ) + if( m_type == FilterType::Formantfilter || + m_type == FilterType::FastFormant ) { - _freq = qBound( minFreq(), _freq, 20000.0f ); // limit freq and q for not getting bad noise out of the filter... + _freq = std::clamp(_freq, minFreq(), 20000.0f); // limit freq and q for not getting bad noise out of the filter... // formats for a, e, i, o, u, a static const float _f[6][2] = { { 1000, 1400 }, { 500, 2300 }, @@ -758,7 +754,7 @@ public: const float f1 = 1.0f / ( linearInterpolate( _f[vowel+0][1], _f[vowel+1][1], fract ) * F_2PI ); // samplerate coeff: depends on oversampling - const float sr = m_type == FastFormant ? m_sampleRatio : m_sampleRatio * 0.25f; + const float sr = m_type == FilterType::FastFormant ? m_sampleRatio : m_sampleRatio * 0.25f; m_vfa[0] = 1.0f - sr / ( f0 + sr ); m_vfb[0] = 1.0f - m_vfa[0]; @@ -769,11 +765,11 @@ public: return; } - if( m_type == Moog || - m_type == DoubleMoog ) + if( m_type == FilterType::Moog || + m_type == FilterType::DoubleMoog ) { // [ 0 - 0.5 ] - const float f = qBound( minFreq(), _freq, 20000.0f ) * m_sampleRatio; + const float f = std::clamp(_freq, minFreq(), 20000.0f) * m_sampleRatio; // (Empirical tunning) m_p = ( 3.6f - 3.2f * f ) * f; m_k = 2.0f * m_p - 1; @@ -788,9 +784,9 @@ public: return; } - if( m_type == Tripole ) + if( m_type == FilterType::Tripole ) { - const float f = qBound( 20.0f, _freq, 20000.0f ) * m_sampleRatio * 0.25f; + const float f = std::clamp(_freq, 20.0f, 20000.0f) * m_sampleRatio * 0.25f; m_p = ( 3.6f - 3.2f * f ) * f; m_k = 2.0f * m_p - 1.0f; @@ -799,20 +795,20 @@ public: return; } - if( m_type == Lowpass_SV || - m_type == Bandpass_SV || - m_type == Highpass_SV || - m_type == Notch_SV ) + if( m_type == FilterType::Lowpass_SV || + m_type == FilterType::Bandpass_SV || + m_type == FilterType::Highpass_SV || + m_type == FilterType::Notch_SV ) { - const float f = sinf( qMax( minFreq(), _freq ) * m_sampleRatio * F_PI ); - m_svf1 = qMin( f, 0.825f ); - m_svf2 = qMin( f * 2.0f, 0.825f ); - m_svq = qMax( 0.0001f, 2.0f - ( _q * 0.1995f ) ); + const float f = sinf(std::max(minFreq(), _freq) * m_sampleRatio * F_PI); + m_svf1 = std::min(f, 0.825f); + m_svf2 = std::min(f * 2.0f, 0.825f); + m_svq = std::max(0.0001f, 2.0f - (_q * 0.1995f)); return; } // other filters - _freq = qBound( minFreq(), _freq, 20000.0f ); + _freq = std::clamp(_freq, minFreq(), 20000.0f); const float omega = F_2PI * _freq * m_sampleRatio; const float tsin = sinf( omega ) * 0.5f; const float tcos = cosf( omega ); @@ -826,38 +822,38 @@ public: switch( m_type ) { - case LowPass: + case FilterType::LowPass: { const float b1 = ( 1.0f - tcos ) * a0; const float b0 = b1 * 0.5f; m_biQuad.setCoeffs( a1, a2, b0, b1, b0 ); break; } - case HiPass: + case FilterType::HiPass: { const float b1 = ( -1.0f - tcos ) * a0; const float b0 = b1 * -0.5f; m_biQuad.setCoeffs( a1, a2, b0, b1, b0 ); break; } - case BandPass_CSG: + case FilterType::BandPass_CSG: { const float b0 = tsin * a0; m_biQuad.setCoeffs( a1, a2, b0, 0.0f, -b0 ); break; } - case BandPass_CZPG: + case FilterType::BandPass_CZPG: { const float b0 = alpha * a0; m_biQuad.setCoeffs( a1, a2, b0, 0.0f, -b0 ); break; } - case Notch: + case FilterType::Notch: { m_biQuad.setCoeffs( a1, a2, a0, a1, a0 ); break; } - case AllPass: + case FilterType::AllPass: { m_biQuad.setCoeffs( a1, a2, a2, a1, 1.0f ); break; @@ -906,7 +902,7 @@ private: // in/out history for Lowpass_SV (state-variant lowpass) frame m_delay1, m_delay2, m_delay3, m_delay4; - FilterTypes m_type; + FilterType m_type; bool m_doubleFilter; float m_sampleRate; @@ -918,4 +914,4 @@ private: } // namespace lmms -#endif +#endif // LMMS_BASIC_FILTERS_H diff --git a/include/BufferManager.h b/include/BufferManager.h index 57729918e..712e420ff 100644 --- a/include/BufferManager.h +++ b/include/BufferManager.h @@ -23,8 +23,8 @@ * */ -#ifndef BUFFER_MANAGER_H -#define BUFFER_MANAGER_H +#ifndef LMMS_BUFFER_MANAGER_H +#define LMMS_BUFFER_MANAGER_H #include "lmms_export.h" #include "lmms_basics.h" @@ -54,4 +54,4 @@ private: } // namespace lmms -#endif +#endif // LMMS_BUFFER_MANAGER_H diff --git a/include/CPULoadWidget.h b/include/CPULoadWidget.h index dd2747ef2..bed10b05e 100644 --- a/include/CPULoadWidget.h +++ b/include/CPULoadWidget.h @@ -23,10 +23,10 @@ * */ +#ifndef LMMS_GUI_CPU_LOAD_WIDGET_H +#define LMMS_GUI_CPU_LOAD_WIDGET_H -#ifndef CPULOAD_WIDGET_H -#define CPULOAD_WIDGET_H - +#include #include #include #include @@ -41,6 +41,7 @@ namespace lmms::gui class CPULoadWidget : public QWidget { Q_OBJECT + Q_PROPERTY(int stepSize MEMBER m_stepSize) public: CPULoadWidget( QWidget * _parent ); ~CPULoadWidget() override = default; @@ -55,6 +56,8 @@ protected slots: private: + int stepSize() const { return std::max(1, m_stepSize); } + int m_currentLoad; QPixmap m_temp; @@ -65,9 +68,11 @@ private: QTimer m_updateTimer; + int m_stepSize = 1; + } ; } // namespace lmms::gui -#endif +#endif // LMMS_GUI_CPU_LOAD_WIDGET_H diff --git a/include/CaptionMenu.h b/include/CaptionMenu.h index f6a2fff25..4a8889a83 100644 --- a/include/CaptionMenu.h +++ b/include/CaptionMenu.h @@ -22,9 +22,8 @@ * */ - -#ifndef CAPTION_MENU_H -#define CAPTION_MENU_H +#ifndef LMMS_GUI_CAPTION_MENU_H +#define LMMS_GUI_CAPTION_MENU_H #include @@ -47,4 +46,4 @@ public: } // namespace lmms::gui -#endif +#endif // LMMS_GUI_CAPTION_MENU_H diff --git a/include/Clip.h b/include/Clip.h index 204a071a7..a520ad4e4 100644 --- a/include/Clip.h +++ b/include/Clip.h @@ -22,8 +22,10 @@ * */ -#ifndef TRACK_CONTENT_OBJECT_H -#define TRACK_CONTENT_OBJECT_H +#ifndef LMMS_CLIP_H +#define LMMS_CLIP_H + +#include #include @@ -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 ); @@ -164,13 +149,6 @@ signals: private: - enum Actions - { - NoAction, - Move, - Resize - } ; - Track * m_track; QString m_name; @@ -184,8 +162,7 @@ private: bool m_selectViewOnCreate; - QColor m_color; - bool m_useCustomClipColor; + std::optional m_color; friend class ClipView; @@ -194,4 +171,4 @@ private: } // namespace lmms -#endif +#endif // LMMS_CLIP_H diff --git a/include/ClipView.h b/include/ClipView.h index 8cfb9e7a4..14898db65 100644 --- a/include/ClipView.h +++ b/include/ClipView.h @@ -22,9 +22,10 @@ * */ -#ifndef TRACK_CONTENT_OBJECT_VIEW_H -#define TRACK_CONTENT_OBJECT_VIEW_H +#ifndef LMMS_GUI_CLIP_VIEW_H +#define LMMS_GUI_CLIP_VIEW_H +#include #include @@ -140,7 +141,7 @@ public slots: void resetColor(); protected: - enum ContextMenuAction + enum class ContextMenuAction { Remove, Cut, @@ -184,6 +185,7 @@ protected: virtual void paintTextLabel(QString const & text, QPainter & painter); + auto hasCustomColor() const -> bool; protected slots: void updateLength(); @@ -191,9 +193,9 @@ protected slots: private: - enum Actions + enum class Action { - NoAction, + None, Move, MoveSelection, Resize, @@ -206,7 +208,7 @@ private: static TextFloat * s_textFloat; Clip * m_clip; - Actions m_action; + Action m_action; QPoint m_initialMousePos; QPoint m_initialMouseGlobalPos; QVector m_initialOffsets; @@ -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); @@ -252,4 +254,4 @@ private: } // namespace lmms -#endif +#endif // LMMS_GUI_CLIP_VIEW_H diff --git a/include/Clipboard.h b/include/Clipboard.h index 1c2dcb647..cee40b33a 100644 --- a/include/Clipboard.h +++ b/include/Clipboard.h @@ -22,11 +22,13 @@ * */ -#ifndef CLIPBOARD_H -#define CLIPBOARD_H +#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 @@ -68,4 +70,4 @@ namespace lmms::Clipboard } // namespace lmms::Clipboard -#endif +#endif // LMMS_CLIPBOARD_H diff --git a/include/ColorChooser.h b/include/ColorChooser.h index 5482a9a03..0e85ea9bb 100644 --- a/include/ColorChooser.h +++ b/include/ColorChooser.h @@ -21,8 +21,8 @@ * */ -#ifndef COLOR_CHOOSER_H -#define COLOR_CHOOSER_H +#ifndef LMMS_GUI_COLOR_CHOOSER_H +#define LMMS_GUI_COLOR_CHOOSER_H #include #include @@ -34,7 +34,7 @@ namespace lmms::gui { -class ColorChooser: public QColorDialog +class ColorChooser : public QColorDialog { public: ColorChooser(const QColor &initial, QWidget *parent): QColorDialog(initial, parent) {}; @@ -68,5 +68,4 @@ private: } // namespace lmms::gui -#endif - +#endif // LMMS_GUI_COLOR_CHOOSER_H 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 9a0e5a517..cc4ad68dd 100644 --- a/include/ComboBox.h +++ b/include/ComboBox.h @@ -22,9 +22,8 @@ * */ - -#ifndef COMBOBOX_H -#define COMBOBOX_H +#ifndef LMMS_GUI_COMBOBOX_H +#define LMMS_GUI_COMBOBOX_H #include #include @@ -67,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; @@ -83,4 +82,4 @@ private slots: } // namespace lmms::gui -#endif +#endif // LMMS_GUI_COMBOBOX_H diff --git a/include/ComboBoxModel.h b/include/ComboBoxModel.h index 0c76620d8..e90d804e2 100644 --- a/include/ComboBoxModel.h +++ b/include/ComboBoxModel.h @@ -22,8 +22,8 @@ * */ -#ifndef COMBOBOX_MODEL_H -#define COMBOBOX_MODEL_H +#ifndef LMMS_COMBOBOX_MODEL_H +#define LMMS_COMBOBOX_MODEL_H #include #include @@ -72,12 +72,12 @@ public: const QString & itemText( int i ) const { - return m_items[qBound( minValue(), i, maxValue() )].first; + return m_items[std::clamp(i, minValue(), maxValue())].first; } const PixmapLoader* itemPixmap( int i ) const { - return m_items[qBound( minValue(), i, maxValue() )].second.get(); + return m_items[std::clamp(i, minValue(), maxValue())].second.get(); } int size() const @@ -95,4 +95,4 @@ private: } // namespace lmms -#endif +#endif // LMMS_COMBOBOX_MODEL_H diff --git a/include/ConfigManager.h b/include/ConfigManager.h index fd2967b6a..f6239c297 100644 --- a/include/ConfigManager.h +++ b/include/ConfigManager.h @@ -22,18 +22,17 @@ * */ - -#ifndef CONFIG_MGR_H -#define CONFIG_MGR_H +#ifndef LMMS_CONFIG_MANAGER_H +#define LMMS_CONFIG_MANAGER_H #include "lmmsconfig.h" #include #include #include -#include #include +#include #include "lmms_export.h" @@ -240,11 +239,8 @@ public: void addRecentlyOpenedProject(const QString & _file); - const QString & value(const QString & cls, - const QString & attribute) const; - const QString & value(const QString & cls, - const QString & attribute, - const QString & defaultVal) const; + QString value(const QString& cls, const QString& attribute, const QString& defaultVal = "") const; + void setValue(const QString & cls, const QString & attribute, const QString & value); void deleteValue(const QString & cls, const QString & attribute); @@ -303,7 +299,7 @@ private: unsigned int m_configVersion; QStringList m_recentlyOpenedProjects; - using stringPairVector = QVector>; + using stringPairVector = std::vector>; using settingsMap = QMap; settingsMap m_settings; @@ -314,4 +310,4 @@ private: } // namespace lmms -#endif +#endif // LMMS_CONFIG_MANAGER_H diff --git a/include/ControlLayout.h b/include/ControlLayout.h index 8c629f8e1..568ce1a85 100644 --- a/include/ControlLayout.h +++ b/include/ControlLayout.h @@ -70,8 +70,8 @@ ** ****************************************************************************/ -#ifndef CONTROLLAYOUT_H -#define CONTROLLAYOUT_H +#ifndef LMMS_GUI_CONTROL_LAYOUT_H +#define LMMS_GUI_CONTROL_LAYOUT_H #include #include @@ -141,4 +141,4 @@ private: } // namespace lmms::gui -#endif // CONTROLLAYOUT_H +#endif // LMMS_GUI_CONTROL_LAYOUT_H diff --git a/include/Controller.h b/include/Controller.h index d949cf90c..fe78c55cc 100644 --- a/include/Controller.h +++ b/include/Controller.h @@ -23,9 +23,8 @@ * */ - -#ifndef CONTROLLER_H -#define CONTROLLER_H +#ifndef LMMS_CONTROLLER_H +#define LMMS_CONTROLLER_H #include "lmms_export.h" #include "Engine.h" @@ -46,26 +45,25 @@ class ControllerDialog; } // namespace gui -using ControllerVector = QVector; +using ControllerVector = std::vector; class LMMS_EXPORT Controller : public Model, public JournallingObject { Q_OBJECT public: - enum ControllerTypes + enum class ControllerType { - DummyController, - LfoController, - MidiController, - PeakController, + Dummy, + Lfo, + Midi, + Peak, /* - XYController, - EquationController + XY, + Equation */ - NumControllerTypes } ; - Controller( ControllerTypes _type, Model * _parent, + Controller( ControllerType _type, Model * _parent, const QString & _display_name ); ~Controller() override; @@ -84,7 +82,7 @@ public: m_sampleExact = _exact; } - inline ControllerTypes type() const + inline ControllerType type() const { return( m_type ); } @@ -95,8 +93,8 @@ public: { switch( m_type ) { - case LfoController: return( true ); - case PeakController: return( true ); + case ControllerType::Lfo: return( true ); + case ControllerType::Peak: return( true ); default: break; } @@ -113,13 +111,13 @@ public: void loadSettings( const QDomElement & _this ) override; QString nodeName() const override; - static Controller * create( ControllerTypes _tt, Model * _parent ); + static Controller * create( ControllerType _tt, Model * _parent ); static Controller * create( const QDomElement & _this, Model * _parent ); inline static float fittedValue( float _val ) { - return qBound( 0.0f, _val, 1.0f ); + return std::clamp(_val, 0.0f, 1.0f); } static long runningPeriods() @@ -166,7 +164,7 @@ protected: int m_connectionCount; QString m_name; - ControllerTypes m_type; + ControllerType m_type; static ControllerVector s_controllers; @@ -184,5 +182,4 @@ signals: } // namespace lmms -#endif - +#endif // LMMS_CONTROLLER_H diff --git a/include/ControllerConnection.h b/include/ControllerConnection.h index 8230971cc..b60687123 100644 --- a/include/ControllerConnection.h +++ b/include/ControllerConnection.h @@ -26,17 +26,17 @@ * */ - -#ifndef CONTROLLER_CONNECTION_H -#define CONTROLLER_CONNECTION_H +#ifndef LMMS_CONTROLLER_CONNECTION_H +#define LMMS_CONTROLLER_CONNECTION_H #include -#include #include "Controller.h" #include "JournallingObject.h" #include "ValueBuffer.h" +#include + namespace lmms { @@ -47,7 +47,7 @@ namespace gui class ControllerConnectionDialog; } -using ControllerConnectionVector = QVector; +using ControllerConnectionVector = std::vector; class LMMS_EXPORT ControllerConnection : public QObject, public JournallingObject { @@ -128,5 +128,4 @@ signals: } // namespace lmms -#endif - +#endif // LMMS_CONTROLLER_CONNECTION_H diff --git a/include/ControllerConnectionDialog.h b/include/ControllerConnectionDialog.h index 5565c8a67..6fb9d45d0 100644 --- a/include/ControllerConnectionDialog.h +++ b/include/ControllerConnectionDialog.h @@ -23,9 +23,8 @@ * */ - -#ifndef CONTROLLER_CONNECTION_DIALOG_H -#define CONTROLLER_CONNECTION_DIALOG_H +#ifndef LMMS_GUI_CONTROLLER_CONNECTION_DIALOG_H +#define LMMS_GUI_CONTROLLER_CONNECTION_DIALOG_H #include #include @@ -111,4 +110,4 @@ private: } // namespace lmms -#endif +#endif // LMMS_GUI_CONTROLLER_CONNECTION_DIALOG_H diff --git a/include/ControllerDialog.h b/include/ControllerDialog.h index 4f496ad77..0c53a8c84 100644 --- a/include/ControllerDialog.h +++ b/include/ControllerDialog.h @@ -23,8 +23,8 @@ * */ -#ifndef CONTROLLER_DIALOG_H -#define CONTROLLER_DIALOG_H +#ifndef LMMS_GUI_CONTROLLER_DIALOG_H +#define LMMS_GUI_CONTROLLER_DIALOG_H #include @@ -61,4 +61,4 @@ protected: } // namespace lmms -#endif +#endif // LMMS_GUI_CONTROLLER_DIALOG_H diff --git a/include/ControllerRackView.h b/include/ControllerRackView.h index 904e523dd..303cc2b40 100644 --- a/include/ControllerRackView.h +++ b/include/ControllerRackView.h @@ -22,8 +22,8 @@ * */ -#ifndef CONTROLLER_RACK_VIEW_H -#define CONTROLLER_RACK_VIEW_H +#ifndef LMMS_GUI_CONTROLLER_RACK_VIEW_H +#define LMMS_GUI_CONTROLLER_RACK_VIEW_H #include #include @@ -92,4 +92,4 @@ private: } // namespace lmms -#endif +#endif // LMMS_GUI_CONTROLLER_RACK_VIEW_H diff --git a/include/ControllerView.h b/include/ControllerView.h index 8b8db0674..d1ba533a1 100644 --- a/include/ControllerView.h +++ b/include/ControllerView.h @@ -22,8 +22,8 @@ * */ -#ifndef CONTROLLER_VIEW_H -#define CONTROLLER_VIEW_H +#ifndef LMMS_GUI_CONTROLLER_VIEW_H +#define LMMS_GUI_CONTROLLER_VIEW_H #include @@ -88,4 +88,4 @@ private: } // namespace lmms::gui -#endif +#endif // LMMS_GUI_CONTROLLER_VIEW_H diff --git a/include/Controls.h b/include/Controls.h index 9ffed465e..5ed19027e 100644 --- a/include/Controls.h +++ b/include/Controls.h @@ -22,10 +22,8 @@ * */ -#ifndef CONTROLS_H -#define CONTROLS_H - - +#ifndef LMMS_GUI_CONTROLS_H +#define LMMS_GUI_CONTROLS_H // headers only required for covariance #include "AutomatableModel.h" @@ -146,4 +144,4 @@ public: } // namespace lmms -#endif // CONTROLS_H +#endif // LMMS_GUI_CONTROLS_H diff --git a/include/CustomTextKnob.h b/include/CustomTextKnob.h index cde718810..31a58415e 100644 --- a/include/CustomTextKnob.h +++ b/include/CustomTextKnob.h @@ -1,6 +1,29 @@ -/* Text customizable knob */ -#ifndef CUSTOM_TEXT_KNOB_H -#define CUSTOM_TEXT_KNOB_H +/* + * CustomTextKnob.h + * + * Copyright (c) 2020 Ibuki Sugiyama
+ * + * 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_CUSTOM_TEXT_KNOB_H +#define LMMS_GUI_CUSTOM_TEXT_KNOB_H #include "Knob.h" @@ -13,7 +36,7 @@ class LMMS_EXPORT CustomTextKnob : public Knob protected: inline void setHintText( const QString & _txt_before, const QString & _txt_after ) {} // inaccessible public: - CustomTextKnob( knobTypes _knob_num, QWidget * _parent = nullptr, const QString & _name = QString(), const QString & _value_text = QString() ); + CustomTextKnob( KnobType _knob_num, QWidget * _parent = nullptr, const QString & _name = QString(), const QString & _value_text = QString() ); CustomTextKnob( QWidget * _parent = nullptr, const QString & _name = QString(), const QString & _value_text = QString() ); //!< default ctor @@ -34,4 +57,4 @@ protected: } // namespace lmms::gui -#endif +#endif // LMMS_GUI_CUSTOM_TEXT_KNOB_H diff --git a/include/DataFile.h b/include/DataFile.h index fb8b2ae3e..452481a7f 100644 --- a/include/DataFile.h +++ b/include/DataFile.h @@ -23,15 +23,14 @@ * */ - -#ifndef DATA_FILE_H -#define DATA_FILE_H +#ifndef LMMS_DATA_FILE_H +#define LMMS_DATA_FILE_H #include #include +#include #include "lmms_export.h" -#include "MemoryManager.h" class QTextStream; @@ -43,14 +42,13 @@ class ProjectVersion; class LMMS_EXPORT DataFile : public QDomDocument { - MM_OPERATORS using UpgradeMethod = void(DataFile::*)(); public: - enum Types + enum class Type { - UnknownType, + Unknown, SongProject, SongProjectTemplate, InstrumentTrackSettings, @@ -58,10 +56,8 @@ public: ClipboardData, JournalData, EffectSettings, - MidiClip, - TypeCount + MidiClip } ; - using Type = Types; DataFile( const QString& fileName ); DataFile( const QByteArray& data ); @@ -129,6 +125,11 @@ private: void upgrade_defaultTripleOscillatorHQ(); void upgrade_mixerRename(); 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; @@ -148,10 +149,9 @@ private: QDomElement m_head; Type m_type; unsigned int m_fileVersion; - } ; } // namespace lmms -#endif +#endif // LMMS_DATA_FILE_H diff --git a/include/Delay.h b/include/Delay.h index 529577d58..71fbe1b00 100644 --- a/include/Delay.h +++ b/include/Delay.h @@ -22,15 +22,13 @@ * Boston, MA 02110-1301 USA. * */ - - -#ifndef DELAY_H -#define DELAY_H + +#ifndef LMMS_DELAY_H +#define LMMS_DELAY_H #include "lmms_basics.h" #include "lmms_math.h" #include "interpolation.h" -#include "MemoryManager.h" namespace lmms { @@ -75,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; @@ -146,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; @@ -217,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; @@ -298,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; @@ -365,4 +363,4 @@ using StereoAllpassDelay = AllpassDelay<2>; } // namespace lmms -#endif +#endif // LMMS_DELAY_H diff --git a/include/DeprecationHelper.h b/include/DeprecationHelper.h index 7786665a3..b9be6d42f 100644 --- a/include/DeprecationHelper.h +++ b/include/DeprecationHelper.h @@ -24,8 +24,8 @@ * */ -#ifndef DEPRECATIONHELPER_H -#define DEPRECATIONHELPER_H +#ifndef LMMS_DEPRECATIONHELPER_H +#define LMMS_DEPRECATIONHELPER_H #include #include @@ -66,4 +66,4 @@ inline QPoint position(QWheelEvent *wheelEvent) } // namespace lmms -#endif // DEPRECATIONHELPER_H +#endif // LMMS_DEPRECATIONHELPER_H diff --git a/include/DetuningHelper.h b/include/DetuningHelper.h index f90af3697..da8eb5983 100644 --- a/include/DetuningHelper.h +++ b/include/DetuningHelper.h @@ -23,11 +23,10 @@ * */ -#ifndef DETUNING_HELPER_H -#define DETUNING_HELPER_H +#ifndef LMMS_DETUNING_HELPER_H +#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() @@ -64,4 +62,4 @@ public: } // namespace lmms -#endif +#endif // LMMS_DETUNING_HELPER_H diff --git a/include/DrumSynth.h b/include/DrumSynth.h index 98574f2d2..750055ddb 100644 --- a/include/DrumSynth.h +++ b/include/DrumSynth.h @@ -23,9 +23,8 @@ * */ - -#ifndef DRUMSYNTH_H -#define DRUMSYNTH_H +#ifndef LMMS_DRUM_SYNTH_H +#define LMMS_DRUM_SYNTH_H #include #include "lmms_basics.h" @@ -57,4 +56,4 @@ class DrumSynth { } // namespace lmms -#endif // DRUMSYNTH_H +#endif // LMMS_DRUM_SYNTH_H diff --git a/include/DspEffectLibrary.h b/include/DspEffectLibrary.h index eb03e1cf0..14dbd7ede 100644 --- a/include/DspEffectLibrary.h +++ b/include/DspEffectLibrary.h @@ -22,9 +22,8 @@ * */ - -#ifndef DSP_EFFECT_LIBRARY_H -#define DSP_EFFECT_LIBRARY_H +#ifndef LMMS_DSPEFFECTLIBRARY_H +#define LMMS_DSPEFFECTLIBRARY_H #include "lmms_math.h" #include "lmms_constants.h" @@ -188,7 +187,7 @@ namespace lmms::DspEffectLibrary template inline sample_t saturate( sample_t x ) { - return qMin( qMax( -1.0f, x ), 1.0f ); + return std::min(std::max(-1.0f, x), 1.0f); } @@ -199,7 +198,7 @@ namespace lmms::DspEffectLibrary const sample_t _gain, const sample_t _ratio, const FastBassBoost & _orig = FastBassBoost() ) : - m_frequency( qMax( _frequency, 10.0 ) ), + m_frequency(std::max(_frequency, 10.0)), m_gain1( 1.0 / ( m_frequency + 1.0 ) ), m_gain2( _gain ), m_ratio( _ratio ), @@ -331,5 +330,4 @@ namespace lmms::DspEffectLibrary } // namespace lmms::DspEffectLibrary - -#endif +#endif // LMMS_DSPEFFECTLIBRARY_H diff --git a/include/DummyEffect.h b/include/DummyEffect.h index 9db45a3e1..0a80b90a9 100644 --- a/include/DummyEffect.h +++ b/include/DummyEffect.h @@ -22,8 +22,8 @@ * */ -#ifndef DUMMY_EFFECT_H -#define DUMMY_EFFECT_H +#ifndef LMMS_DUMMY_EFFECT_H +#define LMMS_DUMMY_EFFECT_H #include @@ -149,4 +149,4 @@ private: } // namespace lmms -#endif +#endif // LMMS_DUMMY_EFFECT_H diff --git a/include/DummyInstrument.h b/include/DummyInstrument.h index a40d8338f..aefa035db 100644 --- a/include/DummyInstrument.h +++ b/include/DummyInstrument.h @@ -23,8 +23,8 @@ * */ -#ifndef DUMMY_INSTRUMENT_H -#define DUMMY_INSTRUMENT_H +#ifndef LMMS_DUMMY_INSTRUMENT_H +#define LMMS_DUMMY_INSTRUMENT_H #include "Instrument.h" #include "InstrumentView.h" @@ -77,4 +77,4 @@ public: } // namespace lmms -#endif +#endif // LMMS_DUMMY_INSTRUMENT_H diff --git a/include/DummyPlugin.h b/include/DummyPlugin.h index 1c763ec3e..3f6746b74 100644 --- a/include/DummyPlugin.h +++ b/include/DummyPlugin.h @@ -23,8 +23,8 @@ * */ -#ifndef DUMMY_PLUGIN_H -#define DUMMY_PLUGIN_H +#ifndef LMMS_DUMMY_PLUGIN_H +#define LMMS_DUMMY_PLUGIN_H #include "Plugin.h" #include "PluginView.h" @@ -68,4 +68,4 @@ protected: } // namespace lmms -#endif +#endif // LMMS_DUMMY_PLUGIN_H diff --git a/include/Editor.h b/include/Editor.h index 1a39ea72f..681bce46c 100644 --- a/include/Editor.h +++ b/include/Editor.h @@ -22,8 +22,8 @@ * */ -#ifndef EDITOR_COMMON_H -#define EDITOR_COMMON_H +#ifndef LMMS_GUI_EDITOR_H +#define LMMS_GUI_EDITOR_H #include #include @@ -113,4 +113,4 @@ protected: } // namespace lmms::gui -#endif +#endif // LMMS_GUI_EDITOR_H diff --git a/include/Effect.h b/include/Effect.h index add1f7158..8b2ff81f0 100644 --- a/include/Effect.h +++ b/include/Effect.h @@ -23,15 +23,14 @@ * */ -#ifndef EFFECT_H -#define EFFECT_H +#ifndef LMMS_EFFECT_H +#define LMMS_EFFECT_H #include "Plugin.h" #include "Engine.h" #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, @@ -157,6 +155,11 @@ public: { m_noRun = _state; } + + inline TempoSyncKnobModel* autoQuitModel() + { + return &m_autoQuitModel; + } EffectChain * effectChain() const { @@ -241,4 +244,4 @@ using EffectKeyList = Effect::Descriptor::SubPluginFeatures::KeyList; } // namespace lmms -#endif +#endif // LMMS_EFFECT_H diff --git a/include/EffectChain.h b/include/EffectChain.h index a9da8e530..f9482174e 100644 --- a/include/EffectChain.h +++ b/include/EffectChain.h @@ -23,8 +23,8 @@ * */ -#ifndef EFFECT_CHAIN_H -#define EFFECT_CHAIN_H +#ifndef LMMS_EFFECT_CHAIN_H +#define LMMS_EFFECT_CHAIN_H #include "Model.h" #include "SerializingObject.h" @@ -69,7 +69,7 @@ public: private: - using EffectList = QVector; + using EffectList = std::vector; EffectList m_effects; BoolModel m_enabledModel; @@ -85,5 +85,4 @@ signals: } // namespace lmms -#endif - +#endif // LMMS_EFFECT_CHAIN_H diff --git a/include/EffectControlDialog.h b/include/EffectControlDialog.h index 6389db4a3..4bef5ee79 100644 --- a/include/EffectControlDialog.h +++ b/include/EffectControlDialog.h @@ -23,8 +23,8 @@ * */ -#ifndef EFFECT_CONTROL_DIALOG_H -#define EFFECT_CONTROL_DIALOG_H +#ifndef LMMS_GUI_EFFECT_CONTROL_DIALOG_H +#define LMMS_GUI_EFFECT_CONTROL_DIALOG_H #include @@ -64,4 +64,4 @@ protected: } // namespace lmms -#endif +#endif // LMMS_GUI_EFFECT_CONTROL_DIALOG_H diff --git a/include/EffectControls.h b/include/EffectControls.h index 994145a40..294f61027 100644 --- a/include/EffectControls.h +++ b/include/EffectControls.h @@ -22,8 +22,8 @@ * */ -#ifndef EFFECT_CONTROLS_H -#define EFFECT_CONTROLS_H +#ifndef LMMS_EFFECT_CONTROLS_H +#define LMMS_EFFECT_CONTROLS_H #include "Model.h" #include "JournallingObject.h" @@ -82,4 +82,4 @@ private: } // namespace lmms -#endif +#endif // LMMS_EFFECT_CONTROLS_H diff --git a/include/EffectRackView.h b/include/EffectRackView.h index fb68fda68..a1e21be09 100644 --- a/include/EffectRackView.h +++ b/include/EffectRackView.h @@ -23,8 +23,8 @@ * */ -#ifndef EFFECT_RACK_VIEW_H -#define EFFECT_RACK_VIEW_H +#ifndef LMMS_GUI_EFFECT_RACK_VIEW_H +#define LMMS_GUI_EFFECT_RACK_VIEW_H #include @@ -88,4 +88,4 @@ private: } // namespace lmms::gui -#endif +#endif // LMMS_GUI_EFFECT_RACK_VIEW_H diff --git a/include/EffectSelectDialog.h b/include/EffectSelectDialog.h index b0acfb734..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 * @@ -22,53 +23,98 @@ * */ -#ifndef EFFECT_SELECT_DIALOG_H -#define EFFECT_SELECT_DIALOG_H - -#include -#include -#include +#ifndef LMMS_GUI_EFFECT_SELECT_DIALOG_H +#define LMMS_GUI_EFFECT_SELECT_DIALOG_H #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 diff --git a/include/EffectView.h b/include/EffectView.h index 90bb54896..e90700952 100644 --- a/include/EffectView.h +++ b/include/EffectView.h @@ -23,13 +23,14 @@ * */ -#ifndef EFFECT_VIEW_H -#define EFFECT_VIEW_H +#ifndef LMMS_GUI_EFFECT_VIEW_H +#define LMMS_GUI_EFFECT_VIEW_H #include "AutomatableModel.h" #include "PluginView.h" #include "Effect.h" +class QGraphicsOpacityEffect; class QGroupBox; class QLabel; class QPushButton; @@ -61,6 +62,11 @@ public: } static constexpr int DEFAULT_WIDTH = 215; + static constexpr int DEFAULT_HEIGHT = 60; + + void mouseMoveEvent(QMouseEvent* event) override; + void mousePressEvent(QMouseEvent* event) override; + void mouseReleaseEvent(QMouseEvent* event) override; public slots: void editControls(); @@ -90,10 +96,13 @@ private: Knob * m_gate; QMdiSubWindow * m_subWindow; EffectControlDialog * m_controlView; + + bool m_dragging; + QGraphicsOpacityEffect* m_opacityEffect; } ; } // namespace lmms::gui -#endif +#endif // LMMS_GUI_EFFECT_VIEW_H diff --git a/include/Engine.h b/include/Engine.h index b63308cde..ed4cbd93c 100644 --- a/include/Engine.h +++ b/include/Engine.h @@ -22,14 +22,12 @@ * */ - -#ifndef ENGINE_H -#define ENGINE_H +#ifndef LMMS_ENGINE_H +#define LMMS_ENGINE_H #include #include - #include "lmmsconfig.h" #include "lmms_export.h" #include "lmms_basics.h" @@ -157,5 +155,4 @@ private: } // namespace lmms -#endif - +#endif // LMMS_ENGINE_H diff --git a/include/EnvelopeAndLfoParameters.h b/include/EnvelopeAndLfoParameters.h index 534adcbe5..50bfdf787 100644 --- a/include/EnvelopeAndLfoParameters.h +++ b/include/EnvelopeAndLfoParameters.h @@ -22,10 +22,11 @@ * */ -#ifndef ENVELOPE_AND_LFO_PARAMETERS_H -#define ENVELOPE_AND_LFO_PARAMETERS_H +#ifndef LMMS_ENVELOPE_AND_LFO_PARAMETERS_H +#define LMMS_ENVELOPE_AND_LFO_PARAMETERS_H -#include +#include +#include #include "JournallingObject.h" #include "AutomatableModel.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,9 @@ private: sample_t * m_lfoShapeData; sample_t m_random; bool m_bad_lfoShapeData; - SampleBuffer m_userWave; + std::shared_ptr m_userWave = SampleBuffer::emptyBuffer(); - enum LfoShapes - { - SineWave, - TriangleWave, - SawWave, - SquareWave, - UserDefinedWave, - RandomWave, - NumLfoShapes - } ; + constexpr static auto NumLfoShapes = static_cast(LfoShape::Count); sample_t lfoShapeSample( fpp_t _frame_offset ); void updateLfoShapeData(); @@ -190,4 +215,4 @@ private: } // namespace lmms -#endif +#endif // LMMS_ENVELOPE_AND_LFO_PARAMETERS_H diff --git a/include/EnvelopeAndLfoView.h b/include/EnvelopeAndLfoView.h index a82ccf606..0063dc788 100644 --- a/include/EnvelopeAndLfoView.h +++ b/include/EnvelopeAndLfoView.h @@ -23,16 +23,13 @@ * */ -#ifndef ENVELOPE_AND_LFO_VIEW_H -#define ENVELOPE_AND_LFO_VIEW_H +#ifndef LMMS_GUI_ENVELOPE_AND_LFO_VIEW_H +#define LMMS_GUI_ENVELOPE_AND_LFO_VIEW_H #include #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,12 +91,10 @@ private: LedCheckBox * m_x100Cb; LedCheckBox * m_controlEnvAmountCb; - - float m_randomGraph; } ; } // namespace gui } // namespace lmms -#endif +#endif // LMMS_GUI_ENVELOPE_AND_LFO_VIEW_H 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/ExportFilter.h b/include/ExportFilter.h index 29efef539..acbdbb65e 100644 --- a/include/ExportFilter.h +++ b/include/ExportFilter.h @@ -23,8 +23,8 @@ * */ -#ifndef EXPORT_FILTER_H -#define EXPORT_FILTER_H +#ifndef LMMS_EXPORT_FILTER_H +#define LMMS_EXPORT_FILTER_H #include @@ -69,4 +69,4 @@ private: } // namespace lmms -#endif +#endif // LMMS_EXPORT_FILTER_H diff --git a/include/ExportProjectDialog.h b/include/ExportProjectDialog.h index 56d039afe..37f6e0399 100644 --- a/include/ExportProjectDialog.h +++ b/include/ExportProjectDialog.h @@ -23,9 +23,8 @@ * */ - -#ifndef EXPORT_PROJECT_DIALOG_H -#define EXPORT_PROJECT_DIALOG_H +#ifndef LMMS_GUI_EXPORT_PROJECT_DIALOG_H +#define LMMS_GUI_EXPORT_PROJECT_DIALOG_H #include #include @@ -63,12 +62,11 @@ private: QString m_fileExtension; bool m_multiExport; - ProjectRenderer::ExportFileFormats m_ft; + ProjectRenderer::ExportFileFormat m_ft; std::unique_ptr m_renderManager; } ; } // namespace lmms::gui - -#endif +#endif // LMMS_GUI_EXPORT_PROJECT_DIALOG_H diff --git a/include/FadeButton.h b/include/FadeButton.h index d33900913..166ef5f21 100644 --- a/include/FadeButton.h +++ b/include/FadeButton.h @@ -22,9 +22,8 @@ * */ - -#ifndef FADE_BUTTON_H -#define FADE_BUTTON_H +#ifndef LMMS_GUI_FADE_BUTTON_H +#define LMMS_GUI_FADE_BUTTON_H #include #include @@ -77,4 +76,4 @@ private: } // namespace lmms::gui -#endif +#endif // LMMS_GUI_FADE_BUTTON_H diff --git a/include/Fader.h b/include/Fader.h index 54acfc57d..a3158a8b4 100644 --- a/include/Fader.h +++ b/include/Fader.h @@ -44,9 +44,8 @@ * */ - -#ifndef FADER_H -#define FADER_H +#ifndef LMMS_GUI_FADER_H +#define LMMS_GUI_FADER_H #include #include @@ -54,32 +53,35 @@ #include "AutomatableModelView.h" +#include "embed.h" +#include "lmms_math.h" + namespace lmms::gui { -class TextFloat; +class SimpleTextFloat; 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,91 +90,74 @@ 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 TextFloat * 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}; } ; } // namespace lmms::gui - -#endif +#endif // LMMS_GUI_FADER_H diff --git a/include/FifoBuffer.h b/include/FifoBuffer.h index d49e367c0..cdb1df41f 100644 --- a/include/FifoBuffer.h +++ b/include/FifoBuffer.h @@ -22,8 +22,8 @@ * */ -#ifndef FIFO_BUFFER_H -#define FIFO_BUFFER_H +#ifndef LMMS_FIFO_BUFFER_H +#define LMMS_FIFO_BUFFER_H #include @@ -94,4 +94,4 @@ private: } // namespace lmms -#endif +#endif // LMMS_FIFO_BUFFER_H diff --git a/include/FileBrowser.h b/include/FileBrowser.h index 43a0b3331..b0c8a5199 100644 --- a/include/FileBrowser.h +++ b/include/FileBrowser.h @@ -22,21 +22,24 @@ * */ - -#ifndef FILE_BROWSER_H -#define FILE_BROWSER_H +#ifndef LMMS_GUI_FILE_BROWSER_H +#define LMMS_GUI_FILE_BROWSER_H #include #include #include +#include "embed.h" + +#include "FileBrowserSearcher.h" +#include + #if (QT_VERSION >= QT_VERSION_CHECK(5,14,0)) #include #endif #include - #include "SideBarWidget.h" - +#include "lmmsconfig.h" class QLineEdit; @@ -67,17 +70,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 directoryBlacklist() + { + static auto s_blacklist = QStringList{ +#ifdef LMMS_BUILD_LINUX + "/bin", "/boot", "/dev", "/etc", "/proc", "/run", "/sbin", + "/sys" +#endif +#ifdef LMMS_BUILD_WIN32 + "C:\\Windows" +#endif + }; + return s_blacklist; + } + 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() ); - // call with item=NULL to filter the entire tree - bool filterItems( const QString & filter, QTreeWidgetItem * item=nullptr ); + void expandItems(const QList& expandedDirs, QTreeWidgetItem* item = nullptr); void giveFocusToFilter(); private: @@ -85,21 +102,33 @@ private: void addItems( const QString & path ); + void saveDirectoriesStates(); + void restoreDirectoriesStates(); + + void buildSearchTree(); + void onSearch(const QString& filter); + void toggleSearch(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; QCheckBox* m_showFactoryContent = nullptr; QString m_userDir; QString m_factoryDir; + QList m_savedExpandedDirs; + QString m_previousFilterValue; } ; @@ -116,7 +145,6 @@ public: //! that are expanded in the tree. QList expandedDirs( QTreeWidgetItem * item = nullptr ) const; - protected: void contextMenuEvent( QContextMenuEvent * e ) override; void mousePressEvent( QMouseEvent * me ) override; @@ -165,12 +193,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(); @@ -195,14 +221,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 @@ -215,7 +239,7 @@ private: QString m_filter; int m_dirCount; - + bool m_disableEntryPopulation = false; } ; @@ -224,20 +248,19 @@ private: class FileItem : public QTreeWidgetItem { public: - enum FileTypes + enum class FileType { - ProjectFile, - PresetFile, - SampleFile, - SoundFontFile, - PatchFile, - MidiFile, - VstPluginFile, - UnknownFile, - NumFileTypes + Project, + Preset, + Sample, + SoundFont, + Patch, + Midi, + VstPlugin, + Unknown } ; - enum FileHandling + enum class FileHandling { NotSupported, LoadAsProject, @@ -256,7 +279,7 @@ public: return QFileInfo(m_path, text(0)).absoluteFilePath(); } - inline FileTypes type() const + inline FileType type() const { return( m_type ); } @@ -268,27 +291,20 @@ public: inline bool isTrack() const { - return m_handling == LoadAsPreset || m_handling == LoadByPlugin; + return m_handling == FileHandling::LoadAsPreset || m_handling == FileHandling::LoadByPlugin; } 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; - FileTypes m_type; + FileType m_type; FileHandling m_handling; } ; @@ -298,4 +314,4 @@ private: } // namespace lmms -#endif +#endif // LMMS_GUI_FILE_BROWSER_H diff --git a/include/FileBrowserSearcher.h b/include/FileBrowserSearcher.h new file mode 100644 index 000000000..4f4d3ff1c --- /dev/null +++ b/include/FileBrowserSearcher.h @@ -0,0 +1,148 @@ +/* + * FileBrowserSearcher.h - Batch processor for searching the filesystem + * + * 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_FILE_BROWSER_SEARCHER_H +#define LMMS_FILE_BROWSER_SEARCHER_H + +#include +#include +#include +#include +#include + +#ifdef __MINGW32__ +#include +#include +#include +#else +#include +#include +#include +#endif + +namespace lmms::gui { + +//! An active object that handles searching for files that match a certain filter across the file system. +class FileBrowserSearcher +{ +public: + //! Number of milliseconds to wait for before a match should be processed by the user. + static constexpr int MillisecondsPerMatch = 1; + + //! The future object for FileBrowserSearcher. It is used to track the current state of search operations, as + // well as retrieve matches. + class SearchFuture + { + public: + //! Possible state values of the future object. + enum class State + { + Idle, + Running, + Cancelled, + Completed + }; + + //! Constructs a future object using the specified filter, paths, and valid file extensions in the Idle state. + SearchFuture(const QString& filter, const QStringList& paths, const QStringList& extensions) + : m_filter(filter) + , m_paths(paths) + , m_extensions(extensions) + { + } + + //! Retrieves a match from the match list. + auto match() -> QString + { + const auto lock = std::lock_guard{m_matchesMutex}; + return m_matches.empty() ? QString{} : m_matches.takeFirst(); + } + + //! Returns the current state of this future object. + auto state() -> State { return m_state; } + + //! Returns the filter used. + auto filter() -> const QString& { return m_filter; } + + //! Returns the paths to filter. + auto paths() -> const QStringList& { return m_paths; } + + //! Returns the valid file extensions. + auto extensions() -> const QStringList& { return m_extensions; } + + private: + //! Adds a match to the match list. + auto addMatch(const QString& match) -> void + { + const auto lock = std::lock_guard{m_matchesMutex}; + m_matches.append(match); + } + + QString m_filter; + QStringList m_paths; + QStringList m_extensions; + + QStringList m_matches; + std::mutex m_matchesMutex; + + std::atomic m_state = State::Idle; + + friend FileBrowserSearcher; + }; + + ~FileBrowserSearcher(); + + //! Enqueues a search to be ran by the worker thread. + //! Returns a future that the caller can use to track state and results of the operation. + auto search(const QString& filter, const QStringList& paths, const QStringList& extensions) + -> std::shared_ptr; + + //! Sends a signal to cancel a running search. + auto cancel() -> void { m_cancelRunningSearch = true; } + + //! Returns the global instance of the searcher object. + static auto instance() -> FileBrowserSearcher* + { + static auto s_instance = FileBrowserSearcher{}; + return &s_instance; + } + +private: + //! Event loop for the worker thread. + auto run() -> void; + + //! Using Depth-first search (DFS), filters the specified path and adds any matches to the future list. + auto process(SearchFuture* searchFuture, const QString& path) -> bool; + + std::queue> m_searchQueue; + std::atomic m_cancelRunningSearch = false; + + bool m_workerStopped = false; + std::mutex m_workerMutex; + std::condition_variable m_workerCond; + std::thread m_worker{[this] { run(); }}; +}; +} // namespace lmms::gui + +#endif // LMMS_FILE_BROWSER_SEARCHER_H diff --git a/include/FileDialog.h b/include/FileDialog.h index ed5d2f231..67ef1d8eb 100644 --- a/include/FileDialog.h +++ b/include/FileDialog.h @@ -22,9 +22,8 @@ * */ - -#ifndef FILEDIALOG_H -#define FILEDIALOG_H +#ifndef LMMS_GUI_FILE_DIALOG_H +#define LMMS_GUI_FILE_DIALOG_H #include @@ -57,4 +56,4 @@ public: } // namespace lmms::gui -#endif // FILEDIALOG_H +#endif // LMMS_GUI_FILE_DIALOG_H diff --git a/include/Flags.h b/include/Flags.h new file mode 100644 index 000000000..62a5f8af8 --- /dev/null +++ b/include/Flags.h @@ -0,0 +1,83 @@ +/* + * Flags.h - class to make flags from enums + * + * 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_FLAGS_H +#define LMMS_FLAGS_H + +#include + +namespace lmms { + +template +class Flags +{ + static_assert(std::is_enum_v, "lmms::Flags can only be used with enum types"); + +public: + using EnumType = T; + using UnderlyingType = std::underlying_type_t; + + constexpr Flags() = default; + + constexpr Flags(T value) : // Intentionally not explicit + m_value{static_cast(value)} + {} + + constexpr explicit Flags(UnderlyingType value) : + m_value{value} + {} + + constexpr auto testAll(Flags flags) const -> bool { return (*this & flags) == flags; } + constexpr auto testAny(Flags flags) const -> bool { return (*this & flags) != Flags{}; } + constexpr auto testFlag(EnumType flag) const -> bool { return static_cast(*this & flag); } + + constexpr auto operator~() const -> Flags { return Flags{~m_value}; } + friend constexpr auto operator&(Flags l, Flags r) -> Flags { return Flags{l.m_value & r.m_value}; } + friend constexpr auto operator|(Flags l, Flags r) -> Flags { return Flags{l.m_value | r.m_value}; } + friend constexpr auto operator^(Flags l, Flags r) -> Flags { return Flags{l.m_value ^ r.m_value}; } + friend constexpr auto operator+(Flags l, Flags r) -> Flags { return Flags{l.m_value | r.m_value}; } + friend constexpr auto operator-(Flags l, Flags r) -> Flags { return Flags{l.m_value & ~r.m_value}; } + + constexpr auto operator&=(Flags f) -> Flags& { m_value &= f.m_value; return *this; } + constexpr auto operator|=(Flags f) -> Flags& { m_value |= f.m_value; return *this; } + constexpr auto operator^=(Flags f) -> Flags& { m_value ^= f.m_value; return *this; } + constexpr auto operator+=(Flags f) -> Flags& { m_value |= f.m_value; return *this; } + constexpr auto operator-=(Flags f) -> Flags& { m_value &= ~f.m_value; return *this; } + + constexpr explicit operator UnderlyingType() const { return m_value; } // TODO C++23: explicit(std::is_scoped_enum) + constexpr explicit operator bool() const { return m_value != 0; } + + friend constexpr auto operator==(Flags l, Flags r) -> bool { return l.m_value == r.m_value; } // TODO C++20: = default + friend constexpr auto operator!=(Flags l, Flags r) -> bool { return l.m_value != r.m_value; } // TODO C++20: Remove + +private: + UnderlyingType m_value = 0; +}; + +#define LMMS_DECLARE_OPERATORS_FOR_FLAGS(type) \ +constexpr inline auto operator|(type l, type r) -> ::lmms::Flags { return ::lmms::Flags{l} | ::lmms::Flags{r}; } + +} // namespace lmms + +#endif // LMMS_FLAGS_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/Graph.h b/include/Graph.h index 178d1b567..0f5f24524 100644 --- a/include/Graph.h +++ b/include/Graph.h @@ -23,9 +23,8 @@ * */ - -#ifndef GRAPH_H -#define GRAPH_H +#ifndef LMMS_GUI_GRAPH_H +#define LMMS_GUI_GRAPH_H #include #include @@ -49,13 +48,12 @@ class LMMS_EXPORT Graph : public QWidget, public ModelView { Q_OBJECT public: - enum graphStyle + enum class Style { - NearestStyle, //!< draw as stairs - LinearStyle, //!< connect each 2 samples with a line, with wrapping - LinearNonCyclicStyle, //!< LinearStyle without wrapping - BarStyle, //!< draw thick bars - NumGraphStyles + Nearest, //!< draw as stairs + Linear, //!< connect each 2 samples with a line, with wrapping + LinearNonCyclic, //!< Linear without wrapping + Bar, //!< draw thick bars }; /** @@ -63,7 +61,7 @@ public: * @param _width Pixel width of widget * @param _height Pixel height of widget */ - Graph( QWidget * _parent, graphStyle _style = Graph::LinearStyle, + Graph( QWidget * _parent, Style _style = Style::Linear, int _width = 132, int _height = 104 ); @@ -79,13 +77,13 @@ public: return castModel(); } - inline graphStyle getGraphStyle() + inline Style getGraphStyle() { return m_graphStyle; } - inline void setGraphStyle( graphStyle _s ) + inline void setGraphStyle( Style _s ) { m_graphStyle = _s; update(); @@ -115,7 +113,7 @@ private: QPixmap m_foreground; QColor m_graphColor; - graphStyle m_graphStyle; + Style m_graphStyle; bool m_mouseDown; int m_lastCursorX; @@ -228,4 +226,4 @@ private: } // namespace lmms -#endif +#endif // LMMS_GUI_GRAPH_H diff --git a/include/GroupBox.h b/include/GroupBox.h index e20db80a6..fdeb31c4d 100644 --- a/include/GroupBox.h +++ b/include/GroupBox.h @@ -22,9 +22,8 @@ * */ - -#ifndef GROUP_BOX_H -#define GROUP_BOX_H +#ifndef LMMS_GUI_GROUP_BOX_H +#define LMMS_GUI_GROUP_BOX_H #include @@ -51,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; @@ -74,5 +88,4 @@ private: } // namespace lmms::gui - -#endif +#endif // LMMS_GUI_GROUP_BOX_H diff --git a/include/GuiApplication.h b/include/GuiApplication.h index e0b535240..686c5bdd2 100644 --- a/include/GuiApplication.h +++ b/include/GuiApplication.h @@ -22,8 +22,8 @@ * */ -#ifndef GUIAPPLICATION_H -#define GUIAPPLICATION_H +#ifndef LMMS_GUI_GUI_APPLICATION_H +#define LMMS_GUI_GUI_APPLICATION_H #include @@ -96,4 +96,4 @@ LMMS_EXPORT GuiApplication* getGUI(); } // namespace lmms::gui -#endif // GUIAPPLICATION_H +#endif // LMMS_GUI_GUI_APPLICATION_H diff --git a/include/ImportFilter.h b/include/ImportFilter.h index b7a910e5d..9c73b3861 100644 --- a/include/ImportFilter.h +++ b/include/ImportFilter.h @@ -23,8 +23,8 @@ * */ -#ifndef IMPORT_FILTER_H -#define IMPORT_FILTER_H +#ifndef LMMS_IMPORT_FILTER_H +#define LMMS_IMPORT_FILTER_H #include @@ -114,4 +114,4 @@ private: } // namespace lmms -#endif +#endif // LMMS_IMPORT_FILTER_H diff --git a/include/InlineAutomation.h b/include/InlineAutomation.h index 435604506..3e27e137b 100644 --- a/include/InlineAutomation.h +++ b/include/InlineAutomation.h @@ -22,8 +22,8 @@ * */ -#ifndef INLINE_AUTOMATION_H -#define INLINE_AUTOMATION_H +#ifndef LMMS_INLINE_AUTOMATION_H +#define LMMS_INLINE_AUTOMATION_H #include "AutomationNode.h" #include "AutomationClip.h" @@ -99,4 +99,4 @@ private: } // namespace lmms -#endif +#endif // LMMS_INLINE_AUTOMATION_H diff --git a/include/Instrument.h b/include/Instrument.h index 4e48e2fd6..243bdba61 100644 --- a/include/Instrument.h +++ b/include/Instrument.h @@ -23,13 +23,14 @@ * */ -#ifndef INSTRUMENT_H -#define INSTRUMENT_H +#ifndef LMMS_INSTRUMENT_H +#define LMMS_INSTRUMENT_H #include + +#include "Flags.h" #include "lmms_export.h" #include "lmms_basics.h" -#include "MemoryManager.h" #include "Plugin.h" #include "TimePos.h" @@ -45,9 +46,8 @@ class Track; class LMMS_EXPORT Instrument : public Plugin { - MM_OPERATORS public: - enum Flag + enum class Flag { NoFlags = 0x00, IsSingleStreamed = 0x01, /*! Instrument provides a single audio stream for all notes */ @@ -55,7 +55,7 @@ public: IsNotBendable = 0x04, /*! Instrument can't react to pitch bend changes */ }; - Q_DECLARE_FLAGS(Flags, Flag); + using Flags = lmms::Flags; Instrument(InstrumentTrack * _instrument_track, const Descriptor * _descriptor, @@ -102,7 +102,7 @@ public: virtual Flags flags() const { - return NoFlags; + return Flag::NoFlags; } // sub-classes can re-implement this for receiving all incoming @@ -149,9 +149,9 @@ private: } ; -Q_DECLARE_OPERATORS_FOR_FLAGS(Instrument::Flags) +LMMS_DECLARE_OPERATORS_FOR_FLAGS(Instrument::Flag) } // namespace lmms -#endif +#endif // LMMS_INSTRUMENT_H diff --git a/include/InstrumentFunctionViews.h b/include/InstrumentFunctionViews.h index 99ba92031..71a6e8aaa 100644 --- a/include/InstrumentFunctionViews.h +++ b/include/InstrumentFunctionViews.h @@ -22,8 +22,8 @@ * */ -#ifndef INSTRUMENT_FUNCTION_VIEWS_H -#define INSTRUMENT_FUNCTION_VIEWS_H +#ifndef LMMS_INSTRUMENT_FUNCTION_VIEWS_H +#define LMMS_INSTRUMENT_FUNCTION_VIEWS_H #include "ModelView.h" @@ -99,4 +99,4 @@ private: } // namespace lmms -#endif +#endif // LMMS_INSTRUMENT_FUNCTION_VIEWS_H diff --git a/include/InstrumentFunctions.h b/include/InstrumentFunctions.h index 5dcb1114b..59c651a68 100644 --- a/include/InstrumentFunctions.h +++ b/include/InstrumentFunctions.h @@ -22,8 +22,8 @@ * */ -#ifndef INSTRUMENT_FUNCTIONS_H -#define INSTRUMENT_FUNCTIONS_H +#ifndef LMMS_INSTRUMENT_FUNCTIONS_H +#define LMMS_INSTRUMENT_FUNCTIONS_H #include "JournallingObject.h" #include "lmms_basics.h" @@ -119,7 +119,7 @@ public: }; - struct ChordTable : public QVector + struct ChordTable { private: ChordTable(); @@ -131,6 +131,7 @@ public: }; static std::array s_initTable; + std::vector m_chords; public: static const ChordTable & getInstance() @@ -150,6 +151,11 @@ public: { return getByName( name, false ); } + + const std::vector& chords() const + { + return m_chords; + } }; @@ -170,14 +176,13 @@ class InstrumentFunctionArpeggio : public Model, public JournallingObject { Q_OBJECT public: - enum ArpDirections + enum class ArpDirection { - ArpDirUp, - ArpDirDown, - ArpDirUpAndDown, - ArpDirDownAndUp, - ArpDirRandom, - NumArpDirections + Up, + Down, + UpAndDown, + DownAndUp, + Random } ; InstrumentFunctionArpeggio( Model * _parent ); @@ -196,11 +201,11 @@ public: private: - enum ArpModes + enum class ArpMode { - FreeMode, - SortMode, - SyncMode + Free, + Sort, + Sync } ; BoolModel m_arpEnabledModel; @@ -224,4 +229,4 @@ private: } // namespace lmms -#endif +#endif // LMMS_INSTRUMENT_FUNCTIONS_H diff --git a/include/InstrumentMidiIOView.h b/include/InstrumentMidiIOView.h index 1a5cd3d19..f5498ee6e 100644 --- a/include/InstrumentMidiIOView.h +++ b/include/InstrumentMidiIOView.h @@ -23,8 +23,8 @@ * */ -#ifndef INSTRUMENT_MIDI_IO_VIEW_H -#define INSTRUMENT_MIDI_IO_VIEW_H +#ifndef LMMS_GUI_INSTRUMENT_MIDI_IO_VIEW_H +#define LMMS_GUI_INSTRUMENT_MIDI_IO_VIEW_H #include @@ -76,4 +76,4 @@ private: } // namespace lmms -#endif +#endif // LMMS_GUI_INSTRUMENT_MIDI_IO_VIEW_H diff --git a/include/InstrumentPlayHandle.h b/include/InstrumentPlayHandle.h index e9516ab19..dc744b4ff 100644 --- a/include/InstrumentPlayHandle.h +++ b/include/InstrumentPlayHandle.h @@ -22,67 +22,38 @@ * */ -#ifndef INSTRUMENT_PLAY_HANDLE_H -#define INSTRUMENT_PLAY_HANDLE_H +#ifndef LMMS_INSTRUMENT_PLAY_HANDLE_H +#define LMMS_INSTRUMENT_PLAY_HANDLE_H #include "PlayHandle.h" -#include "Instrument.h" -#include "NotePlayHandle.h" #include "lmms_export.h" namespace lmms { +class Instrument; +class InstrumentTrack; + class LMMS_EXPORT InstrumentPlayHandle : public PlayHandle { public: - InstrumentPlayHandle( Instrument * instrument, InstrumentTrack* instrumentTrack ); + InstrumentPlayHandle(Instrument * instrument, InstrumentTrack* instrumentTrack); ~InstrumentPlayHandle() override = default; - - void play( sampleFrame * _working_buffer ) override - { - // ensure that all our nph's have been processed first - ConstNotePlayHandleList nphv = NotePlayHandle::nphsOfInstrumentTrack( m_instrument->instrumentTrack(), true ); - - bool nphsLeft; - do - { - nphsLeft = false; - for( const NotePlayHandle * constNotePlayHandle : nphv ) - { - NotePlayHandle * notePlayHandle = const_cast( constNotePlayHandle ); - if( notePlayHandle->state() != ThreadableJob::ProcessingState::Done && - !notePlayHandle->isFinished()) - { - nphsLeft = true; - notePlayHandle->process(); - } - } - } - while( nphsLeft ); - - m_instrument->play( _working_buffer ); - } + void play(sampleFrame * working_buffer) override; bool isFinished() const override { return false; } - bool isFromTrack( const Track* _track ) const override - { - return m_instrument->isFromTrack( _track ); - } - + bool isFromTrack(const Track* track) const override; private: Instrument* m_instrument; - -} ; - +}; } // namespace lmms -#endif +#endif // LMMS_INSTRUMENT_PLAY_HANDLE_H diff --git a/include/InstrumentSoundShaping.h b/include/InstrumentSoundShaping.h index 11ed4f1c4..fb5f1e8bd 100644 --- a/include/InstrumentSoundShaping.h +++ b/include/InstrumentSoundShaping.h @@ -22,8 +22,8 @@ * */ -#ifndef INSTRUMENT_SOUND_SHAPING_H -#define INSTRUMENT_SOUND_SHAPING_H +#ifndef LMMS_INSTRUMENT_SOUND_SHAPING_H +#define LMMS_INSTRUMENT_SOUND_SHAPING_H #include "ComboBoxModel.h" @@ -51,13 +51,14 @@ public: void processAudioBuffer( sampleFrame * _ab, const fpp_t _frames, NotePlayHandle * _n ); - enum Targets + enum class Target { Volume, Cut, Resonance, - NumTargets + Count } ; + constexpr static auto NumTargets = static_cast(Target::Count); f_cnt_t envFrames( const bool _only_vol = false ) const; f_cnt_t releaseFrames() const; @@ -82,7 +83,7 @@ private: FloatModel m_filterCutModel; FloatModel m_filterResModel; - static const char *const targetNames[InstrumentSoundShaping::NumTargets][3]; + static const char *const targetNames[NumTargets][3]; friend class gui::InstrumentSoundShapingView; @@ -92,4 +93,4 @@ private: } // namespace lmms -#endif +#endif // LMMS_INSTRUMENT_SOUND_SHAPING_H diff --git a/include/InstrumentSoundShapingView.h b/include/InstrumentSoundShapingView.h index aa8aff19d..c9caea28c 100644 --- a/include/InstrumentSoundShapingView.h +++ b/include/InstrumentSoundShapingView.h @@ -22,8 +22,8 @@ * */ -#ifndef INSTRUMENT_SOUND_SHAPING_VIEW_H -#define INSTRUMENT_SOUND_SHAPING_VIEW_H +#ifndef LMMS_GUI_INSTRUMENT_SOUND_SHAPING_VIEW_H +#define LMMS_GUI_INSTRUMENT_SOUND_SHAPING_VIEW_H #include @@ -56,7 +56,7 @@ private: void modelChanged() override; - InstrumentSoundShaping * m_ss; + InstrumentSoundShaping * m_ss = nullptr; TabWidget * m_targetsTabWidget; EnvelopeAndLfoView * m_envLfoViews[InstrumentSoundShaping::NumTargets]; @@ -73,4 +73,4 @@ private: } // namespace lmms::gui -#endif +#endif // LMMS_GUI_INSTRUMENT_SOUND_SHAPING_VIEW_H diff --git a/include/InstrumentTrack.h b/include/InstrumentTrack.h index 02fa8c297..a77112c62 100644 --- a/include/InstrumentTrack.h +++ b/include/InstrumentTrack.h @@ -23,8 +23,8 @@ * */ -#ifndef INSTRUMENT_TRACK_H -#define INSTRUMENT_TRACK_H +#ifndef LMMS_INSTRUMENT_TRACK_H +#define LMMS_INSTRUMENT_TRACK_H #include "AudioPort.h" #include "Groove.h" @@ -52,7 +52,7 @@ namespace gui class InstrumentTrackView; class InstrumentTrackWindow; -class InstrumentMiscView; +class InstrumentTuningView; class MidiCCRackView; } // namespace gui @@ -61,7 +61,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 ); @@ -249,7 +248,6 @@ signals: void instrumentChanged(); void midiNoteOn( const lmms::Note& ); void midiNoteOff( const lmms::Note& ); - void nameChanged(); void newNote(); void endNote(); @@ -327,7 +325,7 @@ private: friend class gui::InstrumentTrackView; friend class gui::InstrumentTrackWindow; friend class NotePlayHandle; - friend class gui::InstrumentMiscView; + friend class gui::InstrumentTuningView; friend class gui::MidiCCRackView; private slots: @@ -338,4 +336,4 @@ private slots: } // namespace lmms -#endif +#endif // LMMS_INSTRUMENT_TRACK_H diff --git a/include/InstrumentTrackView.h b/include/InstrumentTrackView.h index 3201f81d6..cfde89bde 100644 --- a/include/InstrumentTrackView.h +++ b/include/InstrumentTrackView.h @@ -22,9 +22,10 @@ * */ -#ifndef INSTRUMENT_TRACK_VIEW_H -#define INSTRUMENT_TRACK_VIEW_H +#ifndef LMMS_GUI_INSTRUMENT_TRACK_VIEW_H +#define LMMS_GUI_INSTRUMENT_TRACK_VIEW_H +#include "MixerChannelLcdSpinBox.h" #include "TrackView.h" #include "InstrumentTrack.h" @@ -70,8 +71,11 @@ public: // Create a menu for assigning/creating channels for this track QMenu * createMixerMenu( QString title, QString newMixerLabel ) override; + QPixmap determinePixmap(); + protected: + void modelChanged() override; void dragEnterEvent( QDragEnterEvent * _dee ) override; void dropEvent( QDropEvent * _de ) override; @@ -91,12 +95,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; + MixerChannelLcdSpinBox* m_mixerChannelNumber; Knob * m_volumeKnob; Knob * m_panningKnob; FadeButton * m_activityIndicator; @@ -121,4 +128,4 @@ private: } // namespace lmms::gui -#endif +#endif // LMMS_GUI_INSTRUMENT_TRACK_VIEW_H diff --git a/include/InstrumentTrackWindow.h b/include/InstrumentTrackWindow.h index 68750c0b8..48a352cbd 100644 --- a/include/InstrumentTrackWindow.h +++ b/include/InstrumentTrackWindow.h @@ -22,8 +22,8 @@ * */ -#ifndef INSTRUMENT_TRACK_WINDOW_H -#define INSTRUMENT_TRACK_WINDOW_H +#ifndef LMMS_GUI_INSTRUMENT_TRACK_WINDOW_H +#define LMMS_GUI_INSTRUMENT_TRACK_WINDOW_H #include @@ -43,11 +43,11 @@ namespace gui { class EffectRackView; -class MixerLineLcdSpinBox; +class MixerChannelLcdSpinBox; class InstrumentFunctionArpeggioView; class InstrumentFunctionNoteStackingView; class InstrumentMidiIOView; -class InstrumentMiscView; +class InstrumentTuningView; class InstrumentSoundShapingView; class InstrumentTrackShapingView; class InstrumentTrackView; @@ -142,7 +142,7 @@ private: QLabel * m_pitchLabel; LcdSpinBox* m_pitchRangeSpinBox; QLabel * m_pitchRangeLabel; - MixerLineLcdSpinBox * m_mixerChannelNumber; + MixerChannelLcdSpinBox * m_mixerChannelNumber; @@ -154,7 +154,7 @@ private: InstrumentFunctionArpeggioView* m_arpeggioView; InstrumentMidiIOView * m_midiView; EffectRackView * m_effectView; - InstrumentMiscView *m_miscView; + InstrumentTuningView *m_tuningView; // test-piano at the bottom of every instrument-settings-window @@ -169,4 +169,4 @@ private: } // namespace lmms -#endif +#endif // LMMS_GUI_INSTRUMENT_TRACK_WINDOW_H diff --git a/include/InstrumentMiscView.h b/include/InstrumentTuningView.h similarity index 73% rename from include/InstrumentMiscView.h rename to include/InstrumentTuningView.h index 758220ac4..58e147cf0 100644 --- a/include/InstrumentMiscView.h +++ b/include/InstrumentTuningView.h @@ -1,9 +1,9 @@ /* - * InstrumentMiscView.h - widget in instrument-track-window for setting up - * miscellaneous options not covered by other tabs + * InstrumentTuningView.h - widget in instrument-track-window for setting up + * tuning and transposition options * * Copyright (c) 2005-2014 Tobias Doerffel - * Copyright (c) 2020 Martin Pavelek + * Copyright (c) 2020-2022 Martin Pavelek * * This file is part of LMMS - https://lmms.io * @@ -24,11 +24,13 @@ * */ -#ifndef INSTRUMENT_MISC_VIEW_H -#define INSTRUMENT_MISC_VIEW_H +#ifndef LMMS_GUI_INSTRUMENT_TUNING_VIEW_H +#define LMMS_GUI_INSTRUMENT_TUNING_VIEW_H #include +class QLabel; + namespace lmms { @@ -42,15 +44,16 @@ class GroupBox; class LedCheckBox; -class InstrumentMiscView : public QWidget +class InstrumentTuningView : public QWidget { Q_OBJECT public: - InstrumentMiscView(InstrumentTrack *it, QWidget *parent); + InstrumentTuningView(InstrumentTrack *it, QWidget *parent); GroupBox *pitchGroupBox() {return m_pitchGroupBox;} GroupBox *microtunerGroupBox() {return m_microtunerGroupBox;} + QLabel *microtunerNotSupportedLabel() {return m_microtunerNotSupportedLabel;} ComboBox *scaleCombo() {return m_scaleCombo;} ComboBox *keymapCombo() {return m_keymapCombo;} @@ -63,6 +66,8 @@ private: GroupBox *m_pitchGroupBox; GroupBox *m_microtunerGroupBox; + QLabel *m_microtunerNotSupportedLabel; + ComboBox *m_scaleCombo; ComboBox *m_keymapCombo; @@ -76,4 +81,4 @@ private: } // namespace lmms -#endif +#endif // LMMS_GUI_INSTRUMENT_TUNING_VIEW_H diff --git a/include/InstrumentView.h b/include/InstrumentView.h index ab18465eb..40014a11f 100644 --- a/include/InstrumentView.h +++ b/include/InstrumentView.h @@ -22,9 +22,8 @@ * */ - -#ifndef INSTRUMENT_VIEW_H -#define INSTRUMENT_VIEW_H +#ifndef LMMS_GUI_INSTRUMENT_VIEW_H +#define LMMS_GUI_INSTRUMENT_VIEW_H #include "Instrument.h" #include "PluginView.h" @@ -76,4 +75,4 @@ public: } // namespace lmms::gui -#endif +#endif // LMMS_GUI_INSTRUMENT_VIEW_H diff --git a/include/IoHelper.h b/include/IoHelper.h index 96d6aa144..3c453fa58 100644 --- a/include/IoHelper.h +++ b/include/IoHelper.h @@ -22,8 +22,8 @@ * */ -#ifndef IO_HELPER_H -#define IO_HELPER_H +#ifndef LMMS_IO_HELPER_H +#define LMMS_IO_HELPER_H #include "lmmsconfig.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);} @@ -91,4 +90,4 @@ inline int fileToDescriptor(FILE* f, bool closeFile = true) } // namespace lmms -#endif +#endif // LMMS_IO_HELPER_H diff --git a/include/JournallingObject.h b/include/JournallingObject.h index 41ae65295..f3e134f34 100644 --- a/include/JournallingObject.h +++ b/include/JournallingObject.h @@ -22,8 +22,8 @@ * */ -#ifndef JOURNALLING_OBJECT_H -#define JOURNALLING_OBJECT_H +#ifndef LMMS_JOURNALLING_OBJECT_H +#define LMMS_JOURNALLING_OBJECT_H #include @@ -103,5 +103,4 @@ private: } // namespace lmms -#endif - +#endif // LMMS_JOURNALLING_OBJECT_H diff --git a/include/Keymap.h b/include/Keymap.h index 6286f8362..967d80789 100644 --- a/include/Keymap.h +++ b/include/Keymap.h @@ -22,8 +22,8 @@ * */ -#ifndef KEYMAP_H -#define KEYMAP_H +#ifndef LMMS_KEYMAP_H +#define LMMS_KEYMAP_H #include #include @@ -80,4 +80,4 @@ private: } // namespace lmms -#endif +#endif // LMMS_KEYMAP_H diff --git a/include/Knob.h b/include/Knob.h index 16ac7ed01..3c3339a6f 100644 --- a/include/Knob.h +++ b/include/Knob.h @@ -22,17 +22,13 @@ * */ - -#ifndef KNOB_H -#define KNOB_H +#ifndef LMMS_GUI_KNOB_H +#define LMMS_GUI_KNOB_H #include -#include -#include -#include #include -#include "AutomatableModelView.h" +#include "FloatModelEditorBase.h" class QPixmap; @@ -41,20 +37,20 @@ namespace lmms::gui { -class TextFloat; +class SimpleTextFloat; -enum knobTypes +enum class KnobType { - knobDark_28, knobBright_26, knobSmall_17, knobVintage_32, knobStyled + Dark28, Bright26, Small17, Vintage32, Styled } ; void convertPixmapToGrayScale(QPixmap &pixMap); -class LMMS_EXPORT Knob : public QWidget, public FloatModelView +class LMMS_EXPORT Knob : public FloatModelEditorBase { Q_OBJECT - Q_ENUMS( knobTypes ) + Q_ENUMS( KnobType ) Q_PROPERTY(float innerRadius READ innerRadius WRITE setInnerRadius) Q_PROPERTY(float outerRadius READ outerRadius WRITE setOuterRadius) @@ -73,10 +69,7 @@ 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(knobTypes knobNum READ knobNum WRITE setknobNum) + Q_PROPERTY(KnobType knobNum READ knobNum WRITE setknobNum) Q_PROPERTY(QColor textColor READ textColor WRITE setTextColor) @@ -84,17 +77,10 @@ class LMMS_EXPORT Knob : public QWidget, public FloatModelView void onKnobNumUpdated(); //!< to be called when you updated @a m_knobNum public: - Knob( knobTypes _knob_num, QWidget * _parent = nullptr, const QString & _name = QString() ); + Knob( KnobType _knob_num, QWidget * _parent = nullptr, const QString & _name = QString() ); 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 ); @@ -107,8 +93,8 @@ public: float outerRadius() const; void setOuterRadius( float r ); - knobTypes knobNum() const; - void setknobNum( knobTypes k ); + KnobType knobNum() const; + void setknobNum( KnobType k ); QPointF centerPoint() const; float centerPointX() const; @@ -126,42 +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; - 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 setPosition( const QPoint & _p ); bool updateAngle(); int angleFromValue( float value, float minValue, float maxValue, float totalAngle ) const @@ -169,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 TextFloat * 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; @@ -207,11 +153,9 @@ private: QColor m_textColor; - knobTypes m_knobNum; - -} ; - + KnobType m_knobNum; +}; } // namespace lmms::gui -#endif +#endif // LMMS_GUI_KNOB_H diff --git a/include/Ladspa2LMMS.h b/include/Ladspa2LMMS.h index 4b7eb2482..abb969124 100644 --- a/include/Ladspa2LMMS.h +++ b/include/Ladspa2LMMS.h @@ -23,13 +23,11 @@ * */ -#ifndef LADSPA_2_LMMS_H -#define LADSPA_2_LMMS_H - +#ifndef LMMS_LADSPA_2_LMMS_H +#define LMMS_LADSPA_2_LMMS_H #include "LadspaManager.h" - namespace lmms { @@ -83,4 +81,4 @@ private: } // namespace lmms -#endif +#endif // LMMS_LADSPA_2_LMMS_H diff --git a/include/LadspaBase.h b/include/LadspaBase.h index 3feb7d204..0a2b067d4 100644 --- a/include/LadspaBase.h +++ b/include/LadspaBase.h @@ -23,8 +23,10 @@ * */ -#ifndef LADSPA_BASE_H -#define LADSPA_BASE_H +#ifndef LMMS_LADSPA_BASE_H +#define LMMS_LADSPA_BASE_H + +#include #include "LadspaManager.h" #include "Plugin.h" @@ -35,16 +37,16 @@ namespace lmms class LadspaControl; -enum buffer_rate_t { - CHANNEL_IN, - CHANNEL_OUT, - AUDIO_RATE_INPUT, - AUDIO_RATE_OUTPUT, - CONTROL_RATE_INPUT, - CONTROL_RATE_OUTPUT +enum class BufferRate { + ChannelIn, + ChannelOut, + AudioRateInput, + AudioRateOutput, + ControlRateInput, + ControlRateOutput }; -enum buffer_data_t { TOGGLED, ENUM, INTEGER, FLOATING, TIME, NONE }; +enum class BufferDataType { Toggled, Enum, Integer, Floating, Time, None }; //! This struct is used to hold port descriptions internally //! which where received from the ladspa plugin @@ -54,8 +56,8 @@ struct port_desc_t ch_cnt_t proc; uint16_t port_id; uint16_t control_id; - buffer_rate_t rate; - buffer_data_t data_type; + BufferRate rate; + BufferDataType data_type; float scale; LADSPA_Data max; LADSPA_Data min; @@ -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 ); } @@ -83,4 +85,4 @@ inline Plugin::Descriptor::SubPluginFeatures::Key ladspaKeyToSubPluginKey( } // namespace lmms -#endif +#endif // LMMS_LADSPA_BASE_H diff --git a/include/LadspaControl.h b/include/LadspaControl.h index 258a5d8fc..8af8f9923 100644 --- a/include/LadspaControl.h +++ b/include/LadspaControl.h @@ -23,8 +23,8 @@ * */ -#ifndef LADSPA_CONTROL_H -#define LADSPA_CONTROL_H +#ifndef LMMS_LADSPA_CONTROL_H +#define LMMS_LADSPA_CONTROL_H #include @@ -41,6 +41,7 @@ namespace gui { class LadspaControlView; +class LadspaMatrixControlDialog; } // namespace gui @@ -125,10 +126,11 @@ private: friend class gui::LadspaControlView; + friend class gui::LadspaMatrixControlDialog; } ; } // namespace lmms -#endif +#endif // LMMS_LADSPA_CONTROL_H diff --git a/include/LadspaControlView.h b/include/LadspaControlView.h index 5dafa4e5c..4f12c29e2 100644 --- a/include/LadspaControlView.h +++ b/include/LadspaControlView.h @@ -23,8 +23,8 @@ * */ -#ifndef LADSPA_CONTROL_VIEW_H -#define LADSPA_CONTROL_VIEW_H +#ifndef LMMS_GUI_LADSPA_CONTROL_VIEW_H +#define LMMS_GUI_LADSPA_CONTROL_VIEW_H #include @@ -55,4 +55,4 @@ private: } // namespace lmms -#endif +#endif // LMMS_GUI_LADSPA_CONTROL_VIEW_H diff --git a/include/LadspaManager.h b/include/LadspaManager.h index 176526193..1a3360231 100644 --- a/include/LadspaManager.h +++ b/include/LadspaManager.h @@ -24,9 +24,8 @@ * */ - -#ifndef LADSPA_MANAGER_H -#define LADSPA_MANAGER_H +#ifndef LMMS_LADSPA_MANAGER_H +#define LMMS_LADSPA_MANAGER_H #include @@ -63,14 +62,14 @@ calls using: as the plug-in key. */ -enum LadspaPluginType +enum class LadspaPluginType { - SOURCE, - TRANSFER, - VALID, - INVALID, - SINK, - OTHER + Source, + Transfer, + Valid, + Invalid, + Sink, + Other }; struct LadspaManagerDescription @@ -350,4 +349,4 @@ private: } // namespace lmms -#endif +#endif // LMMS_LADSPA_MANAGER_H diff --git a/include/LcdFloatSpinBox.h b/include/LcdFloatSpinBox.h index ab0de304e..a87588b62 100644 --- a/include/LcdFloatSpinBox.h +++ b/include/LcdFloatSpinBox.h @@ -23,9 +23,8 @@ * */ - -#ifndef LCD_FLOATSPINBOX_H -#define LCD_FLOATSPINBOX_H +#ifndef LMMS_GUI_LCD_FLOATSPINBOX_H +#define LMMS_GUI_LCD_FLOATSPINBOX_H #include @@ -50,6 +49,12 @@ public: } void setLabel(const QString &label) { m_label = label; } + + void setSeamless(bool left, bool right) + { + m_wholeDisplay.setSeamless(left, true); + m_fractionDisplay.setSeamless(true, right); + } public slots: virtual void update(); @@ -85,4 +90,4 @@ using LcdFloatSpinBoxModel = FloatModel; } // namespace lmms::gui -#endif +#endif // LMMS_GUI_LCD_FLOATSPINBOX_H diff --git a/include/LcdSpinBox.h b/include/LcdSpinBox.h index 9c8923763..aeccd2635 100644 --- a/include/LcdSpinBox.h +++ b/include/LcdSpinBox.h @@ -22,9 +22,8 @@ * */ - -#ifndef LCD_SPINBOX_H -#define LCD_SPINBOX_H +#ifndef LMMS_GUI_LCD_SPINBOX_H +#define LMMS_GUI_LCD_SPINBOX_H #include "LcdWidget.h" #include "AutomatableModelView.h" @@ -90,4 +89,4 @@ using LcdSpinBoxModel = IntModel; } // namespace lmms::gui -#endif +#endif // LMMS_GUI_LCD_SPINBOX_H diff --git a/include/LcdWidget.h b/include/LcdWidget.h index d55cfc017..f900fea15 100644 --- a/include/LcdWidget.h +++ b/include/LcdWidget.h @@ -22,9 +22,8 @@ * */ - -#ifndef LCD_WIDGET_H -#define LCD_WIDGET_H +#ifndef LMMS_GUI_LCD_WIDGET_H +#define LMMS_GUI_LCD_WIDGET_H #include #include @@ -48,10 +47,9 @@ public: LcdWidget(int numDigits, const QString& style, QWidget* parent, const QString& name = QString(), bool leadingZero = false); - ~LcdWidget() override; - - void setValue( int value ); - void setLabel( const QString& label ); + void setValue(int value); + void setValue(float value); + void setLabel(const QString& label); void addTextForValue( int value, const QString& text ) { @@ -98,7 +96,7 @@ private: QString m_display; QString m_label; - QPixmap* m_lcdPixmap; + QPixmap m_lcdPixmap; QColor m_textColor; QColor m_textShadowColor; @@ -117,4 +115,4 @@ private: } // namespace lmms::gui -#endif +#endif // LMMS_GUI_LCD_WIDGET_H diff --git a/include/LedCheckBox.h b/include/LedCheckBox.h index a215592e6..4f23cd74b 100644 --- a/include/LedCheckBox.h +++ b/include/LedCheckBox.h @@ -22,9 +22,8 @@ * */ - -#ifndef LED_CHECKBOX_H -#define LED_CHECKBOX_H +#ifndef LMMS_GUI_LED_CHECKBOX_H +#define LMMS_GUI_LED_CHECKBOX_H #include "AutomatableButton.h" @@ -39,23 +38,21 @@ class LMMS_EXPORT LedCheckBox : public AutomatableButton { Q_OBJECT public: - enum LedColors + enum class LedColor { Yellow, Green, - Red, - NumColors + Red } ; LedCheckBox( const QString & _txt, QWidget * _parent, const QString & _name = QString(), - LedColors _color = Yellow ); + LedColor _color = LedColor::Yellow, + bool legacyMode = true); LedCheckBox( QWidget * _parent, const QString & _name = QString(), - LedColors _color = Yellow ); - - ~LedCheckBox() override; - + LedColor _color = LedColor::Yellow, + bool legacyMode = true); inline const QString & text() { @@ -71,17 +68,22 @@ protected: private: - QPixmap * m_ledOnPixmap; - QPixmap * m_ledOffPixmap; + QPixmap m_ledOnPixmap; + QPixmap m_ledOffPixmap; QString m_text; - void initUi( LedColors _color ); //!< to be called by ctors + 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); } ; } // namespace lmms::gui -#endif +#endif // LMMS_GUI_LED_CHECKBOX_H diff --git a/include/LeftRightNav.h b/include/LeftRightNav.h index df72193dc..49ad25ecf 100644 --- a/include/LeftRightNav.h +++ b/include/LeftRightNav.h @@ -22,8 +22,8 @@ * */ -#ifndef LEFT_RIGHT_NAV_H -#define LEFT_RIGHT_NAV_H +#ifndef LMMS_GUI_LEFT_RIGHT_NAV_H +#define LMMS_GUI_LEFT_RIGHT_NAV_H #include "PixmapButton.h" @@ -51,4 +51,4 @@ private: } // namespace lmms::gui -#endif \ No newline at end of file +#endif // LMMS_GUI_LEFT_RIGHT_NAV_H diff --git a/include/LfoController.h b/include/LfoController.h index 48aee3d30..01b4b1862 100644 --- a/include/LfoController.h +++ b/include/LfoController.h @@ -22,8 +22,8 @@ * */ -#ifndef LFO_CONTROLLER_H -#define LFO_CONTROLLER_H +#ifndef LMMS_LFO_CONTROLLER_H +#define LMMS_LFO_CONTROLLER_H #include @@ -51,7 +51,7 @@ class LfoControllerDialog; } -class LfoController : public Controller +class LfoController : public Controller { Q_OBJECT public: @@ -86,7 +86,8 @@ protected: sample_t (*m_sampleFunction)( const float ); private: - SampleBuffer * m_userDefSampleBuffer; + float m_heldSample; + std::shared_ptr m_userDefSampleBuffer = SampleBuffer::emptyBuffer(); protected slots: void updatePhase(); @@ -136,4 +137,4 @@ private slots: } // namespace lmms -#endif +#endif // LMMS_LFO_CONTROLLER_H 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/LinkedModelGroupViews.h b/include/LinkedModelGroupViews.h index 72598281b..cf5aabd0d 100644 --- a/include/LinkedModelGroupViews.h +++ b/include/LinkedModelGroupViews.h @@ -22,15 +22,13 @@ * */ -#ifndef LINKEDMODELGROUPVIEWS_H -#define LINKEDMODELGROUPVIEWS_H - +#ifndef LMMS_GUI_LINKED_MODEL_GROUP_VIEWS_H +#define LMMS_GUI_LINKED_MODEL_GROUP_VIEWS_H #include #include #include - namespace lmms { @@ -120,4 +118,4 @@ private: } // namespace lmms -#endif // LINKEDMODELGROUPVIEWS_H +#endif // LMMS_GUI_LINKED_MODEL_GROUP_VIEWS_H diff --git a/include/LinkedModelGroups.h b/include/LinkedModelGroups.h index 84a216f30..c537d7fca 100644 --- a/include/LinkedModelGroups.h +++ b/include/LinkedModelGroups.h @@ -22,15 +22,13 @@ * */ -#ifndef LINKEDMODELGROUPS_H -#define LINKEDMODELGROUPS_H - +#ifndef LMMS_LINKED_MODEL_GROUPS_H +#define LMMS_LINKED_MODEL_GROUPS_H #include #include "Model.h" - class QDomDocument; class QDomElement; @@ -133,6 +131,8 @@ public: private: //! models for the controls + //! @note The AutomatableModels behind the ModelInfo are not owned, + //! but referenced after `addModel` is being called. std::map m_models; }; @@ -178,4 +178,4 @@ public: } // namespace lmms -#endif // LINKEDMODELGROUPS_H +#endif // LMMS_LINKED_MODEL_GROUPS_H diff --git a/include/LmmsPalette.h b/include/LmmsPalette.h index 023872254..91a469de0 100644 --- a/include/LmmsPalette.h +++ b/include/LmmsPalette.h @@ -23,13 +23,12 @@ * */ -#ifndef LMMSPALETTE_H -#define LMMSPALETTE_H +#ifndef LMMS_GUI_LMMS_PALETTE_H +#define LMMS_GUI_LMMS_PALETTE_H #include #include "lmms_export.h" - namespace lmms::gui { @@ -86,7 +85,6 @@ private: }; - } // namespace lmms::gui -#endif +#endif // LMMS_GUI_LMMS_PALETTE_H diff --git a/include/LmmsSemaphore.h b/include/LmmsSemaphore.h new file mode 100644 index 000000000..4170eef6c --- /dev/null +++ b/include/LmmsSemaphore.h @@ -0,0 +1,93 @@ +/* + * Semaphore.h - Semaphore declaration + * + * Copyright (c) 2022-2022 Johannes Lorenz + * + * This file is part of LMMS - https://lmms.io + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program (see COPYING); if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA. + * + */ + +/* + * This code has been copied and adapted from https://github.com/drobilla/jalv + * File src/zix/sem.h + */ + +#ifndef LMMS_SEMAPHORE_H +#define LMMS_SEMAPHORE_H + +#include "lmmsconfig.h" + +#ifdef LMMS_BUILD_APPLE +# include +#elif defined(LMMS_BUILD_WIN32) +# include +#else +# include +#endif + +#include + +namespace lmms { + +/** + A counting semaphore. + + This is an integer that is always positive, and has two main operations: + increment (post) and decrement (wait). If a decrement can not be performed + (i.e. the value is 0) the caller will be blocked until another thread posts + and the operation can succeed. + + Semaphores can be created with any starting value, but typically this will + be 0 so the semaphore can be used as a simple signal where each post + corresponds to one wait. + + Semaphores are very efficient (much moreso than a mutex/cond pair). In + particular, at least on Linux, post is async-signal-safe, which means it + does not block and will not be interrupted. If you need to signal from + a realtime thread, this is the most appropriate primitive to use. + + @note Likely outdated with C++20's std::counting_semaphore + (though we have to check that this will be RT conforming on all platforms) +*/ +class Semaphore +{ +public: + Semaphore(unsigned initial); + Semaphore(const Semaphore&) = delete; + Semaphore& operator=(const Semaphore&) = delete; + Semaphore(Semaphore&&) = delete; + Semaphore& operator=(Semaphore&&) = delete; + ~Semaphore(); + + void post(); + void wait(); + bool tryWait(); + +private: +#ifdef LMMS_BUILD_APPLE + semaphore_t m_sem; +#elif defined(LMMS_BUILD_WIN32) + HANDLE m_sem; +#else + sem_t m_sem; +#endif +}; + +} // namespace lmms + +#endif // LMMS_SEMAPHORE_H diff --git a/include/LmmsStyle.h b/include/LmmsStyle.h index ae5b65f99..d17bbed98 100644 --- a/include/LmmsStyle.h +++ b/include/LmmsStyle.h @@ -23,9 +23,8 @@ * */ - -#ifndef LMMS_STYLE_H -#define LMMS_STYLE_H +#ifndef LMMS_GUI_LMMS_STYLE_H +#define LMMS_GUI_LMMS_STYLE_H #include @@ -37,34 +36,6 @@ namespace lmms::gui class LmmsStyle : public QProxyStyle { public: - enum ColorRole - { - AutomationBarFill, - AutomationBarValue, - AutomationSelectedBarFill, - AutomationCrosshair, - PianoRollStepNote, - PianoRollSelectedNote, - PianoRollDefaultNote, - PianoRollFrozenNote, - PianoRollMutedNote, - PianoRollEditHandle, - PianoRollVolumeLevel, - PianoRollPanningLevel, - PianoRollSelectedLevel, - TimelineForecolor, - StandardGraphLine, - StandardGraphHandle, - StandardGraphHandleBorder, - StandardGraphCrosshair, - TextFloatForecolor, - TextFloatFill, - VisualizationLevelLow, - VisualizationLevelMid, - VisualizationLevelPeak, - NumColorRoles - }; - LmmsStyle(); ~LmmsStyle() override = default; @@ -89,11 +60,9 @@ public: private: QImage colorizeXpm( const char * const * xpm, const QBrush& fill ) const; void hoverColors( bool sunken, bool hover, bool active, QColor& color, QColor& blend ) const; - QColor m_colors[ LmmsStyle::NumColorRoles ]; - }; } // namespace lmms::gui -#endif +#endif // LMMS_GUI_LMMS_STYLE_H diff --git a/include/LocaleHelper.h b/include/LocaleHelper.h index b071a1596..c1e1b4735 100644 --- a/include/LocaleHelper.h +++ b/include/LocaleHelper.h @@ -25,8 +25,8 @@ * */ -#ifndef LOCALEHELPER_H -#define LOCALEHELPER_H +#ifndef LMMS_LOCALEHELPER_H +#define LMMS_LOCALEHELPER_H #include @@ -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); @@ -67,4 +66,4 @@ inline float toFloat(QString str, bool* ok = nullptr) } // namespace lmms::LocaleHelper -#endif // LOCALEHELPER_H +#endif // LMMS_LOCALEHELPER_H diff --git a/include/LocklessAllocator.h b/include/LocklessAllocator.h index adde5fe6f..d44b99543 100644 --- a/include/LocklessAllocator.h +++ b/include/LocklessAllocator.h @@ -22,8 +22,8 @@ * */ -#ifndef LOCKLESS_ALLOCATOR_H -#define LOCKLESS_ALLOCATOR_H +#ifndef LMMS_LOCKLESS_ALLOCATOR_H +#define LMMS_LOCKLESS_ALLOCATOR_H #include #include @@ -84,4 +84,4 @@ public: } // namespace lmms -#endif +#endif // LMMS_LOCKLESS_ALLOCATOR_H diff --git a/include/LocklessList.h b/include/LocklessList.h index 44b718059..4a0c27de0 100644 --- a/include/LocklessList.h +++ b/include/LocklessList.h @@ -22,8 +22,8 @@ * */ -#ifndef LOCKLESS_LIST_H -#define LOCKLESS_LIST_H +#ifndef LMMS_LOCKLESS_LIST_H +#define LMMS_LOCKLESS_LIST_H #include "LocklessAllocator.h" @@ -97,4 +97,4 @@ private: } // namespace lmms -#endif +#endif // LMMS_LOCKLESS_LIST_H diff --git a/include/LocklessRingBuffer.h b/include/LocklessRingBuffer.h index e77c62ccb..2d65badfe 100644 --- a/include/LocklessRingBuffer.h +++ b/include/LocklessRingBuffer.h @@ -22,8 +22,8 @@ * */ -#ifndef LOCKLESSRINGBUFFER_H -#define LOCKLESSRINGBUFFER_H +#ifndef LMMS_LOCKLESS_RING_BUFFER_H +#define LMMS_LOCKLESS_RING_BUFFER_H #include #include @@ -51,13 +51,14 @@ public: std::size_t capacity() const {return m_buffer.maximum_eventual_write_space();} std::size_t free() const {return m_buffer.write_space();} void wakeAll() {m_notifier.wakeAll();} - std::size_t write(const sampleFrame *src, std::size_t cnt, bool notify = false) + std::size_t write(const T *src, std::size_t cnt, bool notify = false) { std::size_t written = LocklessRingBuffer::m_buffer.write(src, cnt); // Let all waiting readers know new data are available. if (notify) {LocklessRingBuffer::m_notifier.wakeAll();} return written; } + void mlock() { m_buffer.mlock(); } protected: ringbuffer_t m_buffer; @@ -89,4 +90,4 @@ private: } // namespace lmms -#endif //LOCKLESSRINGBUFFER_H +#endif // LMMS_LOCKLESS_RING_BUFFER_H diff --git a/include/Lv2Basics.h b/include/Lv2Basics.h index b935e6a02..53489e30d 100644 --- a/include/Lv2Basics.h +++ b/include/Lv2Basics.h @@ -1,7 +1,7 @@ /* * Lv2Basics.h - basic Lv2 utils * - * Copyright (c) 2018-2020 Johannes Lorenz + * Copyright (c) 2018-2023 Johannes Lorenz * * This file is part of LMMS - https://lmms.io * @@ -22,10 +22,8 @@ * */ - -#ifndef LV2BASICS_H -#define LV2BASICS_H - +#ifndef LMMS_LV2BASICS_H +#define LMMS_LV2BASICS_H #include "lmmsconfig.h" @@ -49,8 +47,14 @@ struct LilvNodesDeleter void operator()(LilvNodes* n) { lilv_nodes_free(n); } }; +struct LilvScalePointsDeleter +{ + void operator()(LilvScalePoints* s) { lilv_scale_points_free(s); } +}; + using AutoLilvNode = std::unique_ptr; using AutoLilvNodes = std::unique_ptr; +using AutoLilvScalePoints = std::unique_ptr; /** Return QString from a plugin's node, everything will be freed automatically @@ -69,4 +73,5 @@ std::string stdStringFromPortName(const LilvPlugin* plug, const LilvPort* port); } // namespace lmms #endif // LMMS_HAVE_LV2 -#endif // LV2BASICS_H + +#endif // LMMS_LV2BASICS_H diff --git a/include/Lv2ControlBase.h b/include/Lv2ControlBase.h index c6b08db8c..9bfb40f87 100644 --- a/include/Lv2ControlBase.h +++ b/include/Lv2ControlBase.h @@ -1,7 +1,7 @@ /* * Lv2ControlBase.h - Lv2 control base class * - * Copyright (c) 2018-2020 Johannes Lorenz + * Copyright (c) 2018-2023 Johannes Lorenz * * This file is part of LMMS - https://lmms.io * @@ -22,8 +22,8 @@ * */ -#ifndef LV2_CONTROL_BASE_H -#define LV2_CONTROL_BASE_H +#ifndef LMMS_LV2_CONTROL_BASE_H +#define LMMS_LV2_CONTROL_BASE_H #include "lmmsconfig.h" @@ -74,9 +74,12 @@ class PluginIssue; class LMMS_EXPORT Lv2ControlBase : public LinkedModelGroups { public: - static Plugin::PluginTypes check(const LilvPlugin* m_plugin, + static Plugin::Type check(const LilvPlugin* m_plugin, std::vector &issues); + void shutdown(); + void init(Model* meAsModel); + const LilvPlugin* getPlugin() const { return m_plugin; } Lv2Proc *control(std::size_t idx) { return m_procs[idx].get(); } @@ -95,12 +98,10 @@ protected: Lv2ControlBase(class Model *that, const QString& uri); Lv2ControlBase(const Lv2ControlBase&) = delete; ~Lv2ControlBase() override; + void reload(); Lv2ControlBase& operator=(const Lv2ControlBase&) = delete; - //! Must be checked after ctor or reload - bool isValid() const { return m_valid; } - /* overrides */ @@ -129,8 +130,6 @@ protected: void saveSettings(QDomDocument &doc, QDomElement &that); void loadSettings(const QDomElement &that); void loadFile(const QString &file); - //! TODO: not implemented - void reloadPlugin(); /* more functions that must be called from virtuals @@ -142,17 +141,11 @@ protected: const class TimePos &time, f_cnt_t offset); private: - //! Return the DataFile settings type - virtual DataFile::Types settingsType() = 0; - //! Inform the plugin about a file name change - virtual void setNameFromFile(const QString &fname) = 0; - //! Independent processors //! If this is a mono effect, the vector will have size 2 in order to //! fulfill LMMS' requirement of having stereo input and output std::vector> m_procs; - bool m_valid = true; bool m_hasGUI = false; unsigned m_channelsPerProc; @@ -163,4 +156,5 @@ private: } // namespace lmms #endif // LMMS_HAVE_LV2 -#endif // LV2_CONTROL_BASE_H + +#endif // LMMS_LV2_CONTROL_BASE_H diff --git a/include/Lv2Evbuf.h b/include/Lv2Evbuf.h index 9732fc5e1..319bdf9ce 100644 --- a/include/Lv2Evbuf.h +++ b/include/Lv2Evbuf.h @@ -1,5 +1,5 @@ /* - * lv2_evbuf.h - Lv2 event buffer definitions + * Lv2Evbuf.h - Lv2 event buffer definitions * * Copyright (c) 2019-2020 Johannes Lorenz * @@ -30,8 +30,8 @@ * match the LMMS coding conventions. */ -#ifndef LV2_EVBUF_H -#define LV2_EVBUF_H +#ifndef LMMS_LV2_EVBUF_H +#define LMMS_LV2_EVBUF_H #include "lmmsconfig.h" @@ -155,4 +155,4 @@ lv2_evbuf_write( LV2_Evbuf_Iterator* iter, #endif // LMMS_HAVE_LV2 -#endif // LV2_EVBUF_H +#endif // LMMS_LV2_EVBUF_H diff --git a/include/Lv2Features.h b/include/Lv2Features.h index 033273f31..69a456bbd 100644 --- a/include/Lv2Features.h +++ b/include/Lv2Features.h @@ -22,14 +22,15 @@ * */ -#ifndef LV2FEATURES_H -#define LV2FEATURES_H +#ifndef LMMS_LV2_FEATURES_H +#define LMMS_LV2_FEATURES_H #include "lmmsconfig.h" #ifdef LMMS_HAVE_LV2 #include +#include #include #include "Lv2Manager.h" @@ -69,6 +70,8 @@ public: { return m_featurePointers.data(); } + //! Clear everything + void clear(); private: //! feature storage @@ -76,7 +79,7 @@ private: //! pointers to m_features, required for lilv_plugin_instantiate std::vector m_featurePointers; //! features + data, ordered by URI - std::map m_featureByUri; + std::map m_featureByUri; }; @@ -84,4 +87,4 @@ private: #endif // LMMS_HAVE_LV2 -#endif // LV2FEATURES_H +#endif // LMMS_LV2_FEATURES_H diff --git a/include/Lv2Manager.h b/include/Lv2Manager.h index 7c4a4bcb7..58126a0a4 100644 --- a/include/Lv2Manager.h +++ b/include/Lv2Manager.h @@ -1,7 +1,7 @@ /* * Lv2Manager.h - Implementation of Lv2Manager class * - * Copyright (c) 2018-2020 Johannes Lorenz + * Copyright (c) 2018-2023 Johannes Lorenz * * This file is part of LMMS - https://lmms.io * @@ -22,8 +22,8 @@ * */ -#ifndef LV2MANAGER_H -#define LV2MANAGER_H +#ifndef LMMS_LV2_MANAGER_H +#define LMMS_LV2_MANAGER_H #include "lmmsconfig.h" @@ -31,6 +31,7 @@ #include #include +#include #include #include "Lv2Basics.h" @@ -95,18 +96,18 @@ public: //! use only for std::map internals Lv2Info() : m_plugin(nullptr) {} //! ctor used inside Lv2Manager - Lv2Info(const LilvPlugin* plug, Plugin::PluginTypes type, bool valid) : + Lv2Info(const LilvPlugin* plug, Plugin::Type type, bool valid) : m_plugin(plug), m_type(type), m_valid(valid) {} Lv2Info(Lv2Info&& other) = default; Lv2Info& operator=(Lv2Info&& other) = default; const LilvPlugin* plugin() const { return m_plugin; } - Plugin::PluginTypes type() const { return m_type; } + Plugin::Type type() const { return m_type; } bool isValid() const { return m_valid; } private: const LilvPlugin* m_plugin; - Plugin::PluginTypes m_type; + Plugin::Type m_type; bool m_valid = false; }; @@ -120,15 +121,9 @@ public: Iterator begin() { return m_lv2InfoMap.begin(); } Iterator end() { return m_lv2InfoMap.end(); } - //! strcmp based key comparator for std::set and std::map - struct CmpStr - { - bool operator()(char const *a, char const *b) const; - }; - UridMap& uridMap() { return m_uridMap; } const Lv2UridCache& uridCache() const { return m_uridCache; } - const std::set& supportedFeatureURIs() const + const std::set& supportedFeatureURIs() const { return m_supportedFeatureURIs; } @@ -136,17 +131,21 @@ public: AutoLilvNodes findNodes(const LilvNode *subject, const LilvNode *predicate, const LilvNode *object); - static const std::set& getPluginBlacklist() + static const std::set& getPluginBlacklist() { return pluginBlacklist; } + static const std::set& getPluginBlacklistBuffersizeLessThan32() + { + return pluginBlacklistBuffersizeLessThan32; + } private: // general data bool m_debug; //!< if set, debug output will be printed LilvWorld* m_world; Lv2InfoMap m_lv2InfoMap; - std::set m_supportedFeatureURIs; + std::set m_supportedFeatureURIs; // feature data that are common for all Lv2Proc UridMap m_uridMap; @@ -155,7 +154,8 @@ private: Lv2UridCache m_uridCache; // static - static const std::set pluginBlacklist; + static const std::set + pluginBlacklist, pluginBlacklistBuffersizeLessThan32; // functions bool isSubclassOf(const LilvPluginClass *clvss, const char *uriStr); @@ -166,4 +166,4 @@ private: #endif // LMMS_HAVE_LV2 -#endif // LV2MANAGER_H +#endif // LMMS_LV2_MANAGER_H diff --git a/include/Lv2Options.h b/include/Lv2Options.h index 32225ea89..603cdda43 100644 --- a/include/Lv2Options.h +++ b/include/Lv2Options.h @@ -22,8 +22,8 @@ * */ -#ifndef LV2OPTIONS_H -#define LV2OPTIONS_H +#ifndef LMMS_LV2_OPTIONS_H +#define LMMS_LV2_OPTIONS_H #include "lmmsconfig.h" @@ -84,6 +84,8 @@ public: return m_options.data(); } + void clear(); + private: //! Initialize an option internally void initOption(LV2_URID key, @@ -107,4 +109,4 @@ private: #endif // LMMS_HAVE_LV2 -#endif // LV2OPTIONS_H +#endif // LMMS_LV2_OPTIONS_H diff --git a/include/Lv2Ports.h b/include/Lv2Ports.h index 07676fdd4..e4c896ff3 100644 --- a/include/Lv2Ports.h +++ b/include/Lv2Ports.h @@ -22,8 +22,8 @@ * */ -#ifndef LV2PORTS_H -#define LV2PORTS_H +#ifndef LMMS_LV2PORTS_H +#define LMMS_LV2PORTS_H #include "lmmsconfig.h" @@ -33,6 +33,7 @@ #include #include +#include "Flags.h" #include "lmms_basics.h" #include "PluginIssue.h" @@ -210,12 +211,12 @@ private: struct AtomSeq : public VisitablePort { - enum FlagType + enum class FlagType { None = 0, Midi = 1 }; - unsigned flags = FlagType::None; + Flags flags = FlagType::None; struct Lv2EvbufDeleter { @@ -270,4 +271,5 @@ const Target* dcast(const PortBase* base) } // namespace lmms #endif // LMMS_HAVE_LV2 -#endif // LV2PORTS_H + +#endif // LMMS_LV2PORTS_H diff --git a/include/Lv2Proc.h b/include/Lv2Proc.h index 31a694044..65bd90698 100644 --- a/include/Lv2Proc.h +++ b/include/Lv2Proc.h @@ -1,7 +1,7 @@ /* * Lv2Proc.h - Lv2 processor class * - * Copyright (c) 2019-2020 Johannes Lorenz + * Copyright (c) 2019-2022 Johannes Lorenz * * This file is part of LMMS - https://lmms.io * @@ -22,8 +22,8 @@ * */ -#ifndef LV2PROC_H -#define LV2PROC_H +#ifndef LMMS_LV2_PROC_H +#define LMMS_LV2_PROC_H #include "lmmsconfig.h" @@ -31,11 +31,14 @@ #include #include +#include +#include "LinkedModelGroups.h" +#include "LmmsSemaphore.h" #include "Lv2Basics.h" #include "Lv2Features.h" #include "Lv2Options.h" -#include "LinkedModelGroups.h" +#include "Lv2Worker.h" #include "Plugin.h" #include "../src/3rdparty/ringbuffer/include/ringbuffer/ringbuffer.h" #include "TimePos.h" @@ -59,21 +62,22 @@ namespace Lv2Ports } -//! Class representing one Lv2 processor, i.e. one Lv2 handle -//! For Mono effects, 1 Lv2ControlBase references 2 Lv2Proc +//! Class representing one Lv2 processor, i.e. one Lv2 handle. +//! For Mono effects, 1 Lv2ControlBase references 2 Lv2Proc. class Lv2Proc : public LinkedModelGroup { + friend class Lv2ProcSuspender; public: - static Plugin::PluginTypes check(const LilvPlugin* plugin, + static Plugin::Type check(const LilvPlugin* plugin, std::vector &issues); /* - ctor/dtor + ctor/dtor/reload */ Lv2Proc(const LilvPlugin* plugin, Model *parent); ~Lv2Proc() override; - //! Must be checked after ctor or reload - bool isValid() const { return m_valid; } + void reload(); + void onSampleRateChanged(); /* port access @@ -167,13 +171,17 @@ protected: void shutdownPlugin(); private: - bool m_valid = true; - const LilvPlugin* m_plugin; - LilvInstance* m_instance; + LilvInstance* m_instance = nullptr; Lv2Features m_features; + + // options Lv2Options m_options; + // worker + std::optional m_worker; + Semaphore m_workLock; // this must be shared by different workers + // full list of ports std::vector> m_ports; // quick reference to specific, unique ports @@ -195,6 +203,8 @@ private: static int32_t defaultEvbufSize() { return 1 << 15; /* ardour uses this*/ } //! models for the controls, sorted by port symbols + //! @note These are not owned, but rather link to the models in + //! ControlPorts in `m_ports` std::map m_connectedModels; void initMOptions(); //!< initialize m_options @@ -220,4 +230,5 @@ private: } // namespace lmms #endif // LMMS_HAVE_LV2 -#endif // LV2PROC_H + +#endif // LMMS_LV2_PROC_H diff --git a/include/Lv2SubPluginFeatures.h b/include/Lv2SubPluginFeatures.h index 9ba2c76cb..eb0bd9900 100644 --- a/include/Lv2SubPluginFeatures.h +++ b/include/Lv2SubPluginFeatures.h @@ -3,7 +3,7 @@ * Plugin::Descriptor::SubPluginFeatures for * hosting LV2 plugins * - * Copyright (c) 2018-2020 Johannes Lorenz + * Copyright (c) 2018-2023 Johannes Lorenz * * This file is part of LMMS - https://lmms.io * @@ -24,8 +24,8 @@ * */ -#ifndef LV2_SUBPLUGIN_FEATURES_H -#define LV2_SUBPLUGIN_FEATURES_H +#ifndef LMMS_LV2_SUBPLUGIN_FEATURES_H +#define LMMS_LV2_SUBPLUGIN_FEATURES_H #include "lmmsconfig.h" @@ -47,7 +47,7 @@ private: static QString pluginName(const LilvPlugin *plug); public: - Lv2SubPluginFeatures(Plugin::PluginTypes type); + Lv2SubPluginFeatures(Plugin::Type type); void fillDescriptionWidget( QWidget *parent, const Key *k) const override; @@ -66,4 +66,4 @@ public: #endif // LMMS_HAVE_LV2 -#endif +#endif // LMMS_LV2_SUBPLUGIN_FEATURES_H diff --git a/include/Lv2UridCache.h b/include/Lv2UridCache.h index bae42d318..b64003a2e 100644 --- a/include/Lv2UridCache.h +++ b/include/Lv2UridCache.h @@ -22,8 +22,8 @@ * */ -#ifndef LV2URIDCACHE_H -#define LV2URIDCACHE_H +#ifndef LMMS_LV2_URID_CACHE_H +#define LMMS_LV2_URID_CACHE_H #include "lmmsconfig.h" @@ -74,4 +74,5 @@ template<> struct Lv2UridCache::IdForType { static constexpr auto } // namespace lmms #endif // LMMS_HAVE_LV2 -#endif // LV2URIDCACHE_H + +#endif // LMMS_LV2_URID_CACHE_H diff --git a/include/Lv2UridMap.h b/include/Lv2UridMap.h index 9db9cb0c2..6c22aca3e 100644 --- a/include/Lv2UridMap.h +++ b/include/Lv2UridMap.h @@ -22,8 +22,8 @@ * */ -#ifndef LV2URIDMAP_H -#define LV2URIDMAP_H +#ifndef LMMS_LV2_URID_MAP_H +#define LMMS_LV2_URID_MAP_H #include "lmmsconfig.h" @@ -55,8 +55,6 @@ class UridMap LV2_URID_Map m_mapFeature; LV2_URID_Unmap m_unmapFeature; - LV2_URID m_lastUrid = 0; - public: //! constructor; will set up the features UridMap(); @@ -75,4 +73,5 @@ public: } // namespace lmms #endif // LMMS_HAVE_LV2 -#endif // LV2URIDMAP_H + +#endif // LMMS_LV2_URID_MAP_H diff --git a/include/Lv2ViewBase.h b/include/Lv2ViewBase.h index 605440814..43086849c 100644 --- a/include/Lv2ViewBase.h +++ b/include/Lv2ViewBase.h @@ -1,7 +1,7 @@ /* * Lv2ViewBase.h - base class for Lv2 plugin views * - * Copyright (c) 2018-2020 Johannes Lorenz + * Copyright (c) 2018-2023 Johannes Lorenz * * This file is part of LMMS - https://lmms.io * @@ -22,15 +22,14 @@ * */ -#ifndef LV2VIEWBASE_H -#define LV2VIEWBASE_H +#ifndef LMMS_GUI_LV2_VIEW_BASE_H +#define LMMS_GUI_LV2_VIEW_BASE_H #include "lmmsconfig.h" #ifdef LMMS_HAVE_LV2 - #include "LinkedModelGroupViews.h" #include "lmms_export.h" #include "Lv2Basics.h" @@ -38,7 +37,7 @@ class QPushButton; class QMdiSubWindow; - +class QLabel; namespace lmms { @@ -57,7 +56,7 @@ class Lv2ViewProc : public LinkedModelGroupView { public: //! @param colNum numbers of columns for the controls - Lv2ViewProc(QWidget *parent, Lv2Proc *ctrlBase, int colNum); + Lv2ViewProc(QWidget *parent, Lv2Proc *proc, int colNum); ~Lv2ViewProc() override = default; private: @@ -65,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); @@ -80,6 +95,7 @@ protected: void toggleUI(); void toggleHelp(bool visible); + void closeHelpWindow(); // to be called by child virtuals //! Reconnect models if model changed @@ -95,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; }; @@ -109,4 +127,5 @@ private: } // namespace lmms #endif // LMMS_HAVE_LV2 -#endif // LV2VIEWBASE_H + +#endif // LMMS_GUI_LV2_VIEW_BASE_H diff --git a/include/Lv2Worker.h b/include/Lv2Worker.h new file mode 100644 index 000000000..90a3d9d4f --- /dev/null +++ b/include/Lv2Worker.h @@ -0,0 +1,94 @@ +/* + * Lv2Worker.h - Lv2Worker class + * + * Copyright (c) 2022-2022 Johannes Lorenz + * + * This file is part of LMMS - https://lmms.io + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program (see COPYING); if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA. + * + */ + +#ifndef LV2WORKER_H +#define LV2WORKER_H + +#include "lmmsconfig.h" + +#ifdef LMMS_HAVE_LV2 + +#include +#include +#include +#include + +#include "LocklessRingBuffer.h" +#include "LmmsSemaphore.h" + +namespace lmms +{ + +/** + Worker container +*/ +class Lv2Worker +{ +public: + // CTOR/DTOR/feature access + Lv2Worker(Semaphore* commonWorkLock, bool threaded); + ~Lv2Worker(); + 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_interface->end_run) { m_interface->end_run(m_scheduleFeature.handle); } + } + + // to be called only by static functions + LV2_Worker_Status scheduleWork(uint32_t size, const void* data); + LV2_Worker_Status respond(uint32_t size, const void* data); + +private: + // functions + void workerFunc(); + std::size_t bufferSize() const; //!< size of internal buffers + + // parameters + const bool m_threaded; + const LV2_Worker_Interface* m_interface = nullptr; + LV2_Handle m_handle = nullptr; + LV2_Worker_Schedule m_scheduleFeature; + + // threading/synchronization + std::thread m_thread; + std::vector m_response; //!< buffer where single requests from m_requests are unpacked + LocklessRingBuffer m_requests, m_responses; //!< ringbuffer to queue multiple requests + LocklessRingBufferReader m_requestsReader, m_responsesReader; + std::atomic m_exit = false; //!< Whether the worker function should keep looping + Semaphore m_sem; + Semaphore* m_workLock; +}; + + +} // namespace lmms + +#endif // LMMS_HAVE_LV2 + +#endif // LV2WORKER_H + diff --git a/include/MainApplication.h b/include/MainApplication.h index 04bc82226..98fcdef20 100644 --- a/include/MainApplication.h +++ b/include/MainApplication.h @@ -22,8 +22,8 @@ * */ -#ifndef MAINAPPLICATION_H -#define MAINAPPLICATION_H +#ifndef LMMS_GUI_MAIN_APPLICATION_H +#define LMMS_GUI_MAIN_APPLICATION_H #include "lmmsconfig.h" @@ -64,4 +64,4 @@ private: } // namespace lmms::gui -#endif // MAINAPPLICATION_H +#endif // LMMS_GUI_MAIN_APPLICATION_H diff --git a/include/MainWindow.h b/include/MainWindow.h index e093d1082..4442a7ac2 100644 --- a/include/MainWindow.h +++ b/include/MainWindow.h @@ -22,8 +22,8 @@ * */ -#ifndef MAIN_WINDOW_H -#define MAIN_WINDOW_H +#ifndef LMMS_GUI_MAIN_WINDOW_H +#define LMMS_GUI_MAIN_WINDOW_H #include #include @@ -113,7 +113,7 @@ public: return m_autoSaveTimer.interval(); } - enum SessionState + enum class SessionState { Normal, Recover @@ -248,7 +248,6 @@ private slots: void onExportProject(); void onExportProjectTracks(); void onImportProject(); - void onSongStopped(); void onSongModified(); void onProjectFileNameChanged(); @@ -263,4 +262,4 @@ signals: } // namespace lmms -#endif +#endif // LMMS_GUI_MAIN_WINDOW_H diff --git a/include/MemoryHelper.h b/include/MemoryHelper.h index 1def9d9c4..e709ffc8a 100644 --- a/include/MemoryHelper.h +++ b/include/MemoryHelper.h @@ -21,9 +21,8 @@ * */ - -#ifndef MEMORY_HELPER_H -#define MEMORY_HELPER_H +#ifndef LMMS_MEMORY_HELPER_H +#define LMMS_MEMORY_HELPER_H #include @@ -47,5 +46,4 @@ private: } // namespace lmms -#endif - +#endif // LMMS_MEMORY_HELPER_H diff --git a/include/MemoryManager.h b/include/MemoryManager.h deleted file mode 100644 index d50ad173d..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 MEMORY_MANAGER_H -#define 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 diff --git a/include/MeterDialog.h b/include/MeterDialog.h index 67dca2568..dca7e5de6 100644 --- a/include/MeterDialog.h +++ b/include/MeterDialog.h @@ -23,8 +23,8 @@ * */ -#ifndef METER_DIALOG_H -#define METER_DIALOG_H +#ifndef LMMS_GUI_METER_DIALOG_H +#define LMMS_GUI_METER_DIALOG_H #include @@ -54,4 +54,4 @@ private: } // namespace lmms::gui -#endif +#endif // LMMS_GUI_METER_DIALOG_H diff --git a/include/MeterModel.h b/include/MeterModel.h index c2e715b79..6c0304e37 100644 --- a/include/MeterModel.h +++ b/include/MeterModel.h @@ -22,8 +22,8 @@ * */ -#ifndef METER_MODEL_H -#define METER_MODEL_H +#ifndef LMMS_METER_MODEL_H +#define LMMS_METER_MODEL_H #include "AutomatableModel.h" @@ -68,4 +68,4 @@ private: } // namespace lmms -#endif +#endif // LMMS_METER_MODEL_H diff --git a/include/MicroTimer.h b/include/MicroTimer.h index ea74e964d..cd03403dc 100644 --- a/include/MicroTimer.h +++ b/include/MicroTimer.h @@ -22,8 +22,8 @@ * */ -#ifndef MICRO_TIMER_H -#define MICRO_TIMER_H +#ifndef LMMS_MICRO_TIMER_H +#define LMMS_MICRO_TIMER_H #include @@ -48,4 +48,4 @@ private: } // namespace lmms -#endif // MICRO_TIMER_H +#endif // LMMS_MICRO_TIMER_H diff --git a/include/Microtuner.h b/include/Microtuner.h index dff8f5773..c92549581 100644 --- a/include/Microtuner.h +++ b/include/Microtuner.h @@ -22,8 +22,8 @@ * */ -#ifndef MICROTUNER_H -#define MICROTUNER_H +#ifndef LMMS_MICROTUNER_H +#define LMMS_MICROTUNER_H #include "AutomatableModel.h" #include "ComboBoxModel.h" @@ -74,4 +74,4 @@ private: } // namespace lmms -#endif +#endif // LMMS_MICROTUNER_H diff --git a/include/MicrotunerConfig.h b/include/MicrotunerConfig.h index 690587dd5..0706a530f 100644 --- a/include/MicrotunerConfig.h +++ b/include/MicrotunerConfig.h @@ -22,9 +22,8 @@ * */ -#ifndef MICROTUNER_CONFIG_H -#define MICROTUNER_CONFIG_H - +#ifndef LMMS_GUI_MICROTUNER_CONFIG_H +#define LMMS_GUI_MICROTUNER_CONFIG_H #include @@ -96,4 +95,4 @@ private: } // namespace lmms::gui -#endif +#endif // LMMS_GUI_MICROTUNER_CONFIG_H diff --git a/include/Midi.h b/include/Midi.h index f75254d84..21db021b3 100644 --- a/include/Midi.h +++ b/include/Midi.h @@ -22,8 +22,8 @@ * */ -#ifndef MIDI_H -#define MIDI_H +#ifndef LMMS_MIDI_H +#define LMMS_MIDI_H #include "lmms_basics.h" @@ -143,4 +143,4 @@ const int MidiMaxPitchBend = 16383; } // namespace lmms -#endif +#endif // LMMS_MIDI_H diff --git a/include/MidiAlsaRaw.h b/include/MidiAlsaRaw.h index 57a477264..345cdbd54 100644 --- a/include/MidiAlsaRaw.h +++ b/include/MidiAlsaRaw.h @@ -22,8 +22,8 @@ * */ -#ifndef MIDI_ALSA_RAW_H -#define MIDI_ALSA_RAW_H +#ifndef LMMS_MIDI_ALSA_RAW_H +#define LMMS_MIDI_ALSA_RAW_H #include "lmmsconfig.h" @@ -85,4 +85,4 @@ private: #endif // LMMS_HAVE_ALSA -#endif +#endif // LMMS_MIDI_ALSA_RAW_H diff --git a/include/MidiAlsaSeq.h b/include/MidiAlsaSeq.h index fa287a409..006cdefc8 100644 --- a/include/MidiAlsaSeq.h +++ b/include/MidiAlsaSeq.h @@ -22,8 +22,8 @@ * */ -#ifndef MIDI_ALSA_SEQ_H -#define MIDI_ALSA_SEQ_H +#ifndef LMMS_MIDI_ALSA_SEQ_H +#define LMMS_MIDI_ALSA_SEQ_H #include "lmmsconfig.h" @@ -158,5 +158,4 @@ signals: #endif // LMMS_HAVE_ALSA -#endif - +#endif // LMMS_MIDI_ALSA_SEQ_H diff --git a/include/MidiApple.h b/include/MidiApple.h index 794641e64..37d408d4d 100644 --- a/include/MidiApple.h +++ b/include/MidiApple.h @@ -23,8 +23,8 @@ * */ -#ifndef MIDI_APPLE_H -#define MIDI_APPLE_H +#ifndef LMMS_MIDI_APPLE_H +#define LMMS_MIDI_APPLE_H #include "lmmsconfig.h" @@ -155,5 +155,4 @@ signals: #endif // LMMS_BUILD_APPLE - -#endif +#endif // LMMS_MIDI_APPLE_H diff --git a/include/MidiCCRackView.h b/include/MidiCCRackView.h index 59f1de020..4cb82b3d8 100644 --- a/include/MidiCCRackView.h +++ b/include/MidiCCRackView.h @@ -1,5 +1,29 @@ -#ifndef MIDI_CC_RACK_VIEW_H -#define MIDI_CC_RACK_VIEW_H +/* + * MidiCCRackView.h - declaration of the MIDI CC rack widget + * + * Copyright (c) 2020 Ian Caio + * + * 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_MIDI_CC_RACK_VIEW_H +#define LMMS_GUI_MIDI_CC_RACK_VIEW_H #include @@ -49,4 +73,4 @@ private: } // namespace lmms -#endif +#endif // LMMS_GUI_MIDI_CC_RACK_VIEW_H diff --git a/include/MidiClient.h b/include/MidiClient.h index 954cd5ef5..1eb04402b 100644 --- a/include/MidiClient.h +++ b/include/MidiClient.h @@ -22,11 +22,11 @@ * */ -#ifndef MIDI_CLIENT_H -#define MIDI_CLIENT_H +#ifndef LMMS_MIDI_CLIENT_H +#define LMMS_MIDI_CLIENT_H #include -#include +#include #include "MidiEvent.h" @@ -111,7 +111,7 @@ public: static MidiClient * openMidiClient(); protected: - QVector m_midiPorts; + std::vector m_midiPorts; } ; @@ -173,5 +173,4 @@ private: } // namespace lmms -#endif - +#endif // LMMS_MIDI_CLIENT_H diff --git a/include/MidiClip.h b/include/MidiClip.h index 6433ba507..b3ed0d84a 100644 --- a/include/MidiClip.h +++ b/include/MidiClip.h @@ -23,9 +23,8 @@ * */ -#ifndef MIDI_CLIP_H -#define MIDI_CLIP_H - +#ifndef LMMS_MIDI_CLIP_H +#define LMMS_MIDI_CLIP_H #include "Clip.h" #include "Note.h" @@ -47,7 +46,7 @@ class LMMS_EXPORT MidiClip : public Clip { Q_OBJECT public: - enum MidiClipTypes + enum class Type { BeatClip, MelodyClip @@ -64,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 ); @@ -80,10 +80,10 @@ public: void setStep( int step, bool enabled ); // Split the list of notes on the given position - void splitNotes(NoteVector notes, TimePos pos); + void splitNotes(const NoteVector& notes, TimePos pos); // clip-type stuff - inline MidiClipTypes type() const + inline Type type() const { return m_clipType; } @@ -130,14 +130,14 @@ protected slots: private: TimePos beatClipLength() const; - void setType( MidiClipTypes _new_clip_type ); + void setType( Type _new_clip_type ); void checkType(); void resizeToFirstTrack(); InstrumentTrack * m_instrumentTrack; - MidiClipTypes m_clipType; + Type m_clipType; // data-stuff NoteVector m_notes; @@ -155,4 +155,4 @@ signals: } // namespace lmms -#endif +#endif // LMMS_MIDI_CLIP_H diff --git a/include/MidiClipView.h b/include/MidiClipView.h index 1a9809733..4285bf9da 100644 --- a/include/MidiClipView.h +++ b/include/MidiClipView.h @@ -21,12 +21,13 @@ * Boston, MA 02110-1301 USA. * */ - -#ifndef MIDI_CLIP_VIEW_H -#define MIDI_CLIP_VIEW_H - + +#ifndef LMMS_GUI_MIDI_CLIP_VIEW_H +#define LMMS_GUI_MIDI_CLIP_VIEW_H + #include #include "ClipView.h" +#include "embed.h" namespace lmms { @@ -43,7 +44,7 @@ class MidiClipView : public ClipView public: MidiClipView( MidiClip* clip, TrackView* parent ); - ~MidiClipView() override = default; + ~MidiClipView() override = default; Q_PROPERTY(QColor noteFillColor READ getNoteFillColor WRITE setNoteFillColor) Q_PROPERTY(QColor noteBorderColor READ getNoteBorderColor WRITE setNoteBorderColor) @@ -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; } ; @@ -107,5 +109,5 @@ private: } // namespace gui } // namespace lmms - - #endif + +#endif // LMMS_GUI_MIDI_CLIP_VIEW_H diff --git a/include/MidiController.h b/include/MidiController.h index 500acc5f8..9f49627ac 100644 --- a/include/MidiController.h +++ b/include/MidiController.h @@ -22,8 +22,8 @@ * */ -#ifndef MIDI_CONTROLLER_H -#define MIDI_CONTROLLER_H +#ifndef LMMS_MIDI_CONTROLLER_H +#define LMMS_MIDI_CONTROLLER_H #include @@ -48,6 +48,7 @@ class MidiController : public Controller, public MidiEventProcessor { Q_OBJECT public: + static constexpr int NONE = -1; MidiController( Model * _parent ); ~MidiController() override = default; @@ -92,4 +93,4 @@ protected: } // namespace lmms -#endif +#endif // LMMS_MIDI_CONTROLLER_H diff --git a/include/MidiDummy.h b/include/MidiDummy.h index 67a91e326..b117bd136 100644 --- a/include/MidiDummy.h +++ b/include/MidiDummy.h @@ -22,8 +22,8 @@ * */ -#ifndef MIDI_DUMMY_H -#define MIDI_DUMMY_H +#ifndef LMMS_MIDI_DUMMY_H +#define LMMS_MIDI_DUMMY_H #include "MidiClient.h" @@ -65,4 +65,4 @@ protected: } // namespace lmms -#endif +#endif // LMMS_MIDI_DUMMY_H diff --git a/include/MidiEvent.h b/include/MidiEvent.h index 6c2bfdd55..9a14e427c 100644 --- a/include/MidiEvent.h +++ b/include/MidiEvent.h @@ -22,8 +22,8 @@ * */ -#ifndef MIDI_EVENT_H -#define MIDI_EVENT_H +#ifndef LMMS_MIDI_EVENT_H +#define LMMS_MIDI_EVENT_H #include #include "Midi.h" @@ -212,7 +212,7 @@ private: int32_t m_sysExDataLen; // len of m_sysExData } m_data; - const char* m_sysExData; + [[maybe_unused]] const char* m_sysExData; const void* m_sourcePort; // Stores the source of the MidiEvent: Internal or External (hardware controllers). @@ -221,4 +221,4 @@ private: } // namespace lmms -#endif +#endif // LMMS_MIDI_EVENT_H diff --git a/include/MidiEventProcessor.h b/include/MidiEventProcessor.h index 132a5fc42..0fcb9610e 100644 --- a/include/MidiEventProcessor.h +++ b/include/MidiEventProcessor.h @@ -22,11 +22,10 @@ * */ -#ifndef MIDI_EVENT_PROCESSOR_H -#define MIDI_EVENT_PROCESSOR_H +#ifndef LMMS_MIDI_EVENT_PROCESSOR_H +#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; @@ -49,4 +47,4 @@ public: } // namespace lmms -#endif +#endif // LMMS_MIDI_EVENT_PROCESSOR_H diff --git a/include/MidiEventToByteSeq.h b/include/MidiEventToByteSeq.h index f2e36f50a..6490ae385 100644 --- a/include/MidiEventToByteSeq.h +++ b/include/MidiEventToByteSeq.h @@ -22,8 +22,8 @@ * */ -#ifndef MIDIEVENTTOBYTESEQ_H -#define MIDIEVENTTOBYTESEQ_H +#ifndef LMMS_MIDIEVENTTOBYTESEQ_H +#define LMMS_MIDIEVENTTOBYTESEQ_H #include #include @@ -51,4 +51,4 @@ std::size_t LMMS_EXPORT writeToByteSeq( const class MidiEvent& ev, } // namespace lmms -#endif // MIDIEVENTTOBYTESEQ_H +#endif // LMMS_MIDIEVENTTOBYTESEQ_H diff --git a/include/MidiJack.h b/include/MidiJack.h index a4a944b81..e2b38e290 100644 --- a/include/MidiJack.h +++ b/include/MidiJack.h @@ -22,8 +22,8 @@ * */ -#ifndef MIDIJACK_H -#define MIDIJACK_H +#ifndef LMMS_MIDI_JACK_H +#define LMMS_MIDI_JACK_H #include "lmmsconfig.h" @@ -102,4 +102,4 @@ private: #endif // LMMS_HAVE_JACK -#endif // MIDIJACK_H +#endif // LMMS_MIDI_JACK_H diff --git a/include/MidiOss.h b/include/MidiOss.h index 944359055..9acc0e8e6 100644 --- a/include/MidiOss.h +++ b/include/MidiOss.h @@ -22,8 +22,8 @@ * */ -#ifndef MIDI_OSS_H -#define MIDI_OSS_H +#ifndef LMMS_MIDI_OSS_H +#define LMMS_MIDI_OSS_H #include "lmmsconfig.h" @@ -78,5 +78,4 @@ private: #endif // LMMS_HAVE_OSS - -#endif +#endif // LMMS_MIDI_OSS_H diff --git a/include/MidiPort.h b/include/MidiPort.h index 7ef9adf90..6078f7a9b 100644 --- a/include/MidiPort.h +++ b/include/MidiPort.h @@ -23,8 +23,8 @@ * */ -#ifndef MIDI_PORT_H -#define MIDI_PORT_H +#ifndef LMMS_MIDI_PORT_H +#define LMMS_MIDI_PORT_H #include #include @@ -69,20 +69,19 @@ class MidiPort : public Model, public SerializingObject public: using Map = QMap; - enum Modes + enum class Mode { Disabled, // don't route any MIDI-events (default) Input, // from MIDI-client to MIDI-event-processor Output, // from MIDI-event-processor to MIDI-client Duplex // both directions } ; - using Mode = Modes; MidiPort( const QString& name, MidiClient* client, MidiEventProcessor* eventProcessor, Model* parent = nullptr, - Mode mode = Disabled ); + Mode mode = Mode::Disabled ); ~MidiPort() override; void setName( const QString& name ); @@ -96,12 +95,12 @@ public: bool isInputEnabled() const { - return mode() == Input || mode() == Duplex; + return mode() == Mode::Input || mode() == Mode::Duplex; } bool isOutputEnabled() const { - return mode() == Output || mode() == Duplex; + return mode() == Mode::Output || mode() == Mode::Duplex; } int realOutputChannel() const @@ -190,4 +189,4 @@ using MidiPortList = QList; } // namespace lmms -#endif +#endif // LMMS_MIDI_PORT_H diff --git a/include/MidiPortMenu.h b/include/MidiPortMenu.h index 215a73d06..59604969b 100644 --- a/include/MidiPortMenu.h +++ b/include/MidiPortMenu.h @@ -23,8 +23,8 @@ * */ -#ifndef MIDI_PORT_MENU_H -#define MIDI_PORT_MENU_H +#ifndef LMMS_GUI_MIDI_PORT_MENU_H +#define LMMS_GUI_MIDI_PORT_MENU_H #include @@ -40,7 +40,7 @@ class MidiPortMenu : public QMenu, public ModelView { Q_OBJECT public: - MidiPortMenu( MidiPort::Modes _mode ); + MidiPortMenu( MidiPort::Mode _mode ); ~MidiPortMenu() override = default; @@ -55,10 +55,10 @@ protected slots: private: void modelChanged() override; - MidiPort::Modes m_mode; + MidiPort::Mode m_mode; } ; } // namespace lmms::gui -#endif +#endif // LMMS_GUI_MIDI_PORT_MENU_H diff --git a/include/MidiSetupWidget.h b/include/MidiSetupWidget.h index 4021db142..7b6606017 100644 --- a/include/MidiSetupWidget.h +++ b/include/MidiSetupWidget.h @@ -22,11 +22,10 @@ * */ -#ifndef MIDISETUPWIDGET_H -#define MIDISETUPWIDGET_H +#ifndef LMMS_GUI_MIDI_SETUP_WIDGET_H +#define LMMS_GUI_MIDI_SETUP_WIDGET_H - -#include "TabWidget.h" +#include class QLineEdit; @@ -34,7 +33,7 @@ namespace lmms::gui { -class MidiSetupWidget : public TabWidget +class MidiSetupWidget : public QGroupBox { Q_OBJECT MidiSetupWidget( const QString & caption, const QString & configSection, @@ -60,4 +59,4 @@ private: } // namespace lmms::gui -#endif +#endif // LMMS_GUI_MIDI_SETUP_WIDGET_H diff --git a/include/MidiSndio.h b/include/MidiSndio.h index 3ebd14d05..5734745b9 100644 --- a/include/MidiSndio.h +++ b/include/MidiSndio.h @@ -23,8 +23,8 @@ * */ -#ifndef _MIDI_SNDIO_H -#define _MIDI_SNDIO_H +#ifndef LMMS_MIDI_SNDIO_H +#define LMMS_MIDI_SNDIO_H #include "lmmsconfig.h" @@ -75,4 +75,4 @@ private: #endif // LMMS_HAVE_SNDIO -#endif // _MIDI_SNDIO_H +#endif // LMMS_MIDI_SNDIO_H diff --git a/include/MidiWinMM.h b/include/MidiWinMM.h index 0f2bd0d5f..3a8edfbcd 100644 --- a/include/MidiWinMM.h +++ b/include/MidiWinMM.h @@ -22,8 +22,8 @@ * */ -#ifndef MIDI_WINMM_H -#define MIDI_WINMM_H +#ifndef LMMS_MIDI_WINMM_H +#define LMMS_MIDI_WINMM_H #include "lmmsconfig.h" @@ -150,5 +150,4 @@ signals: #endif // LMMS_BUILD_WIN32 -#endif - +#endif // LMMS_MIDI_WINMM_H diff --git a/include/MixHelpers.h b/include/MixHelpers.h index 6599b40ec..dde17dd02 100644 --- a/include/MixHelpers.h +++ b/include/MixHelpers.h @@ -22,8 +22,8 @@ * */ -#ifndef MIX_HELPERS_H -#define MIX_HELPERS_H +#ifndef LMMS_MIX_HELPERS_H +#define LMMS_MIX_HELPERS_H #include "lmms_basics.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 ); @@ -81,5 +83,4 @@ void multiplyAndAddMultipliedJoined( sampleFrame* dst, const sample_t* srcLeft, } // namespace lmms -#endif - +#endif // LMMS_MIX_HELPERS_H diff --git a/include/Mixer.h b/include/Mixer.h index ecd864ae7..302492cab 100644 --- a/include/Mixer.h +++ b/include/Mixer.h @@ -22,8 +22,8 @@ * */ -#ifndef MIXER_H -#define MIXER_H +#ifndef LMMS_MIXER_H +#define LMMS_MIXER_H #include "Model.h" #include "EffectChain.h" @@ -31,7 +31,7 @@ #include "ThreadableJob.h" #include - +#include #include namespace lmms @@ -39,7 +39,7 @@ namespace lmms class MixerRoute; -using MixerRouteVector = QVector; +using MixerRouteVector = std::vector; class MixerChannel : public ThreadableJob { @@ -76,61 +76,53 @@ 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 { Q_OBJECT - public: - MixerRoute( MixerChannel * from, MixerChannel * to, float amount ); - ~MixerRoute() override = default; - +public: + MixerRoute( MixerChannel * from, MixerChannel * to, float amount ); + ~MixerRoute() override = default; + mix_ch_t senderIndex() const { return m_from->m_channelIndex; } - + mix_ch_t receiverIndex() const { return m_to->m_channelIndex; } - + FloatModel * amount() { return &m_amount; } - + MixerChannel * sender() const { return m_from; } - + MixerChannel * receiver() const { return m_to; } - + void updateName(); - + private: MixerChannel * m_from; MixerChannel * m_to; @@ -202,6 +194,10 @@ public: // rename channels when moving etc. if they still have their original name void validateChannelName( int index, int oldIndex ); + // check if the index channel receives audio from any other channel + // or from any instrument or sample track + bool isChannelInUse(int index); + void toggledSolo(); void activateSolo(); void deactivateSolo(); @@ -215,7 +211,7 @@ public: private: // the mixer channels in the mixer. index 0 is always master. - QVector m_mixerChannels; + std::vector m_mixerChannels; // make sure we have at least num channels void allocateChannelsTo(int num); @@ -226,4 +222,4 @@ private: } // namespace lmms -#endif +#endif // LMMS_MIXER_H diff --git a/include/MixerLineLcdSpinBox.h b/include/MixerChannelLcdSpinBox.h similarity index 74% rename from include/MixerLineLcdSpinBox.h rename to include/MixerChannelLcdSpinBox.h index bfd6cd5c9..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 MIXER_LINE_LCD_SPIN_BOX_H -#define 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 +#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 decd5f3aa..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 MIXERLINE_H -#define MIXERLINE_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 // MIXERLINE_H diff --git a/include/MixerView.h b/include/MixerView.h index a7b1b5191..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 @@ -22,14 +22,15 @@ * */ -#ifndef MIXER_VIEW_H -#define MIXER_VIEW_H +#ifndef LMMS_GUI_MIXER_VIEW_H +#define LMMS_GUI_MIXER_VIEW_H #include #include #include #include +#include "MixerChannelView.h" #include "ModelView.h" #include "Engine.h" #include "Fader.h" @@ -37,61 +38,43 @@ #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); @@ -114,29 +97,40 @@ 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(); - + friend class MixerChannelView; } ; } // namespace lmms::gui -#endif +#endif // LMMS_GUI_MIXER_VIEW_H diff --git a/include/Model.h b/include/Model.h index 567d4ffd7..ce7b751e7 100644 --- a/include/Model.h +++ b/include/Model.h @@ -22,8 +22,8 @@ * */ -#ifndef MODEL_H -#define MODEL_H +#ifndef LMMS_MODEL_H +#define LMMS_MODEL_H #include #include @@ -37,35 +37,18 @@ class LMMS_EXPORT Model : public QObject { Q_OBJECT public: - Model( Model * _parent, QString _display_name = QString(), - bool _default_constructed = false ) : - QObject( _parent ), - m_displayName( _display_name ), - m_defaultConstructed( _default_constructed ) - { - } + Model(Model* parent, QString displayName = QString(), + bool defaultConstructed = false); ~Model() override = default; - bool isDefaultConstructed() - { - return m_defaultConstructed; - } + bool isDefaultConstructed() const; - Model* parentModel() const - { - return static_cast( parent() ); - } + Model* parentModel() const; - virtual QString displayName() const - { - return m_displayName; - } + virtual QString displayName() const; - virtual void setDisplayName( const QString& displayName ) - { - m_displayName = displayName; - } + virtual void setDisplayName(const QString& displayName); virtual QString fullDisplayName() const; @@ -90,5 +73,4 @@ signals: } // namespace lmms -#endif - +#endif // LMMS_MODEL_H diff --git a/include/ModelView.h b/include/ModelView.h index a858dc7ec..786a6812c 100644 --- a/include/ModelView.h +++ b/include/ModelView.h @@ -22,8 +22,8 @@ * */ -#ifndef MODEL_VIEW_H -#define MODEL_VIEW_H +#ifndef LMMS_GUI_MODEL_VIEW_H +#define LMMS_GUI_MODEL_VIEW_H #include #include "Model.h" @@ -85,5 +85,4 @@ private: } // namespace lmms::gui -#endif - +#endif // LMMS_GUI_MODEL_VIEW_H diff --git a/include/ModelVisitor.h b/include/ModelVisitor.h index 8805e30e2..a4654e529 100644 --- a/include/ModelVisitor.h +++ b/include/ModelVisitor.h @@ -22,8 +22,8 @@ * */ -#ifndef MODELVISITOR_H -#define MODELVISITOR_H +#ifndef LMMS_MODEL_VISITOR_H +#define LMMS_MODEL_VISITOR_H namespace lmms { @@ -68,4 +68,4 @@ public: } // namespace lmms -#endif // MODELVISITOR_H +#endif // LMMS_MODEL_VISITOR_H diff --git a/include/NStateButton.h b/include/NStateButton.h index 4dac1a436..c948fa843 100644 --- a/include/NStateButton.h +++ b/include/NStateButton.h @@ -22,9 +22,8 @@ * */ - -#ifndef NSTATE_BUTTON_H -#define NSTATE_BUTTON_H +#ifndef LMMS_GUI_NSTATE_BUTTON_H +#define LMMS_GUI_NSTATE_BUTTON_H #include #include @@ -56,26 +55,21 @@ 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 -#endif +#endif // LMMS_GUI_NSTATE_BUTTON_H 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 464f3906f..08cbce3db 100644 --- a/include/Note.h +++ b/include/Note.h @@ -23,11 +23,11 @@ * */ -#ifndef NOTE_H -#define NOTE_H +#ifndef LMMS_NOTE_H +#define LMMS_NOTE_H #include -#include +#include #include "volume.h" #include "panning.h" @@ -42,50 +42,56 @@ namespace lmms class DetuningHelper; -enum Keys +enum class Key : int { - Key_C = 0, - Key_CIS = 1, Key_DES = 1, - Key_D = 2, - Key_DIS = 3, Key_ES = 3, - Key_E = 4, Key_FES = 4, - Key_F = 5, - Key_FIS = 6, Key_GES = 6, - Key_G = 7, - Key_GIS = 8, Key_AS = 8, - Key_A = 9, - Key_AIS = 10, Key_B = 10, - Key_H = 11 + C = 0, + Cis = 1, Des = 1, + D = 2, + Dis = 3, Es = 3, + E = 4, Fes = 4, + F = 5, + Fis = 6, Ges = 6, + G = 7, + Gis = 8, As = 8, + A = 9, + Ais = 10, B = 10, + H = 11 } ; -enum Octaves +enum class Octave : int { Octave_m1, // MIDI standard starts at C-1 Octave_0, Octave_1, Octave_2, Octave_3, - Octave_4, DefaultOctave = Octave_4, + Octave_4, Octave_5, Octave_6, Octave_7, Octave_8, Octave_9, // incomplete octave, MIDI only goes up to G9 - NumOctaves }; const int FirstOctave = -1; const int KeysPerOctave = 12; -const int DefaultKey = DefaultOctave * KeysPerOctave + Key_A; + +constexpr inline auto operator+(Octave octave, Key key) -> int +{ + return static_cast(octave) * KeysPerOctave + static_cast(key); +} + +constexpr auto DefaultOctave = Octave::Octave_4; +const int DefaultKey = DefaultOctave + Key::A; //! Number of physical keys, limited to MIDI range (valid for both MIDI 1.0 and 2.0) const int NumKeys = 128; -const int DefaultMiddleKey = Octave_4 * KeysPerOctave + Key_C; -const int DefaultBaseKey = Octave_4 * KeysPerOctave + Key_A; +const int DefaultMiddleKey = Octave::Octave_4 + Key::C; +const int DefaultBaseKey = Octave::Octave_4 + Key::A; const float DefaultBaseFreq = 440.f; -const float MaxDetuning = 4 * 12.0f; +const float MaxDetuning = 5 * 12.0f; @@ -101,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; } @@ -198,7 +214,7 @@ public: int midiVelocity( int midiBaseVelocity ) const { - return qMin( MidiMaxVelocity, getVolume() * midiBaseVelocity / DefaultVolume ); + return std::min(MidiMaxVelocity, getVolume() * midiBaseVelocity / DefaultVolume); } inline panning_t getPanning() const @@ -247,9 +263,11 @@ private: TimePos m_length; TimePos m_pos; DetuningHelper * m_detuning; + + Type m_type = Type::Regular; }; -using NoteVector = QVector; +using NoteVector = std::vector; struct NoteBounds { @@ -265,4 +283,4 @@ std::optional boundsForNotes(const NoteVector& notes); } // namespace lmms -#endif +#endif // LMMS_NOTE_H diff --git a/include/NotePlayHandle.h b/include/NotePlayHandle.h index 4969fd84a..f70268132 100644 --- a/include/NotePlayHandle.h +++ b/include/NotePlayHandle.h @@ -23,8 +23,8 @@ * */ -#ifndef NOTE_PLAY_HANDLE_H -#define NOTE_PLAY_HANDLE_H +#ifndef LMMS_NOTE_PLAY_HANDLE_H +#define LMMS_NOTE_PLAY_HANDLE_H #include @@ -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; @@ -56,15 +54,13 @@ public: fpp_t m_fadeInLength; // specifies origin of NotePlayHandle - enum Origins + enum class Origin { - OriginMidiClip, /*! playback of a note from a MIDI clip */ - OriginMidiInput, /*! playback of a MIDI note input event */ - OriginNoteStacking, /*! created by note stacking instrument function */ - OriginArpeggio, /*! created by arpeggio instrument function */ - OriginCount + MidiClip, /*! playback of a note from a MIDI clip */ + MidiInput, /*! playback of a MIDI note input event */ + NoteStacking, /*! created by note stacking instrument function */ + Arpeggio, /*! created by arpeggio instrument function */ }; - using Origin = Origins; NotePlayHandle( InstrumentTrack* instrumentTrack, const f_cnt_t offset, @@ -72,7 +68,7 @@ public: const Note& noteToPlay, NotePlayHandle* parent = nullptr, int midiEventChannel = -1, - Origin origin = OriginMidiClip ); + Origin origin = Origin::MidiClip ); ~NotePlayHandle() override; void * operator new ( size_t size, void * p ) @@ -110,6 +106,9 @@ public: return m_unpitchedFrequency; } + //! Get the current per-note detuning for this note + float currentDetuning() const { return m_baseDetuning->value(); } + /*! Renders one chunk using the attached instrument into the buffer */ void play( sampleFrame* buffer ) override; @@ -247,7 +246,7 @@ public: } /*! Process note detuning automation */ - void processTimePos( const TimePos& time ); + void processTimePos(const TimePos& time, float pitchValue, bool isRecording); /*! Updates total length (m_frames) depending on a new tempo */ void resize( const bpm_t newTempo ); @@ -272,7 +271,6 @@ public: private: class BaseDetuning { - MM_OPERATORS public: BaseDetuning( DetuningHelper* detuning ); @@ -340,7 +338,6 @@ const int NPH_CACHE_INCREMENT = 16; class NotePlayHandleManager { - MM_OPERATORS public: static void init(); static NotePlayHandle * acquire( InstrumentTrack* instrumentTrack, @@ -349,7 +346,7 @@ public: const Note& noteToPlay, NotePlayHandle* parent = nullptr, int midiEventChannel = -1, - NotePlayHandle::Origin origin = NotePlayHandle::OriginMidiClip ); + NotePlayHandle::Origin origin = NotePlayHandle::Origin::MidiClip ); static void release( NotePlayHandle * nph ); static void extend( int i ); static void free(); @@ -364,4 +361,4 @@ private: } // namespace lmms -#endif +#endif // LMMS_NOTE_PLAY_HANDLE_H diff --git a/include/Oscillator.h b/include/Oscillator.h index 8727ee39c..a480bf524 100644 --- a/include/Oscillator.h +++ b/include/Oscillator.h @@ -23,12 +23,14 @@ * */ -#ifndef OSCILLATOR_H -#define OSCILLATOR_H +#ifndef LMMS_OSCILLATOR_H +#define LMMS_OSCILLATOR_H #include #include +#include #include +#include "interpolation.h" #include "Engine.h" #include "lmms_constants.h" @@ -46,33 +48,35 @@ class IntModel; class LMMS_EXPORT Oscillator { - MM_OPERATORS public: - enum WaveShapes + enum class WaveShape { - SineWave, - TriangleWave, - SawWave, - SquareWave, - MoogSawWave, - ExponentialWave, + Sine, + Triangle, + Saw, + Square, + MoogSaw, + Exponential, WhiteNoise, - UserDefinedWave, - NumWaveShapes, //!< Number of all available wave shapes - FirstWaveShapeTable = TriangleWave, //!< First wave shape that has a pre-generated table - NumWaveShapeTables = WhiteNoise - FirstWaveShapeTable, //!< Number of band-limited wave shapes to be generated + UserDefined, + Count //!< Number of all available wave shapes }; + constexpr static auto NumWaveShapes = static_cast(WaveShape::Count); + //! First wave shape that has a pre-generated table + constexpr static auto FirstWaveShapeTable = static_cast(WaveShape::Triangle); + //! Number of band-limited wave shapes to be generated + constexpr static auto NumWaveShapeTables = static_cast(WaveShape::WhiteNoise) - FirstWaveShapeTable; - enum ModulationAlgos + enum class ModulationAlgo { PhaseModulation, AmplitudeModulation, SignalMix, SynchronizedBySubOsc, FrequencyModulation, - NumModulationAlgos + Count } ; - + constexpr static auto NumModulationAlgos = static_cast(ModulationAlgo::Count); Oscillator( const IntModel *wave_shape_model, const IntModel *mod_algo_model, @@ -88,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... @@ -161,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 { @@ -200,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); @@ -244,14 +262,15 @@ 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. bool m_isModulator; /* Multiband WaveTable */ - static sample_t s_waveTables[WaveShapes::NumWaveShapeTables][OscillatorConstants::WAVE_TABLES_PER_WAVEFORM_COUNT][OscillatorConstants::WAVETABLE_LENGTH]; + static sample_t s_waveTables[NumWaveShapeTables][OscillatorConstants::WAVE_TABLES_PER_WAVEFORM_COUNT][OscillatorConstants::WAVETABLE_LENGTH]; static fftwf_plan s_fftPlan; static fftwf_plan s_ifftPlan; static fftwf_complex * s_specBuf; @@ -284,26 +303,26 @@ private: const ch_cnt_t _chnl ); inline bool syncOk( float _osc_coeff ); - template + template void updateNoSub( sampleFrame * _ab, const fpp_t _frames, const ch_cnt_t _chnl ); - template + template void updatePM( sampleFrame * _ab, const fpp_t _frames, const ch_cnt_t _chnl ); - template + template void updateAM( sampleFrame * _ab, const fpp_t _frames, const ch_cnt_t _chnl ); - template + template void updateMix( sampleFrame * _ab, const fpp_t _frames, const ch_cnt_t _chnl ); - template + template void updateSync( sampleFrame * _ab, const fpp_t _frames, const ch_cnt_t _chnl ); - template + template void updateFM( sampleFrame * _ab, const fpp_t _frames, const ch_cnt_t _chnl ); - template + template inline sample_t getSample( const float _sample ); inline void recalcPhase(); @@ -313,4 +332,4 @@ private: } // namespace lmms -#endif +#endif // LMMS_OSCILLATOR_H diff --git a/include/OscillatorConstants.h b/include/OscillatorConstants.h index 0450c2f17..85ae38fe8 100644 --- a/include/OscillatorConstants.h +++ b/include/OscillatorConstants.h @@ -1,5 +1,5 @@ /* - * OscillatorConstants.h - declaration of constants used in Oscillator and SampleBuffer + * OscillatorConstants.h - declaration of constants used in Oscillator and SampleBuffer * for band limited wave tables * * Copyright (c) 2018 Dave French @@ -23,8 +23,8 @@ * */ -#ifndef OSCILLATORCONSTANTS_H -#define OSCILLATORCONSTANTS_H +#ifndef LMMS_OSCILLATORCONSTANTS_H +#define LMMS_OSCILLATORCONSTANTS_H #include @@ -55,4 +55,4 @@ namespace lmms::OscillatorConstants } // namespace lmms::OscillatorConstants -#endif // OSCILLATORCONSTANTS_H +#endif // LMMS_OSCILLATORCONSTANTS_H diff --git a/include/Oscilloscope.h b/include/Oscilloscope.h index db4cf2b4a..13c946aa5 100644 --- a/include/Oscilloscope.h +++ b/include/Oscilloscope.h @@ -22,9 +22,8 @@ * */ - -#ifndef OSCILLOSCOPE_H -#define OSCILLOSCOPE_H +#ifndef LMMS_GUI_OSCILLOSCOPE_H +#define LMMS_GUI_OSCILLOSCOPE_H #include #include @@ -39,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 ); @@ -47,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); @@ -63,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; @@ -72,11 +79,13 @@ private: sampleFrame * m_buffer; bool m_active; - QColor m_normalColor; + QColor m_leftChannelColor; + QColor m_rightChannelColor; + QColor m_otherChannelsColor; QColor m_clippingColor; } ; } // namespace lmms::gui -#endif // OSCILLOSCOPE_H +#endif // LMMS_GUI_OSCILLOSCOPE_H diff --git a/include/OutputSettings.h b/include/OutputSettings.h index 03e3333ac..94de0612c 100644 --- a/include/OutputSettings.h +++ b/include/OutputSettings.h @@ -23,12 +23,11 @@ * */ -#ifndef OUTPUT_SETTINGS_H -#define OUTPUT_SETTINGS_H +#ifndef LMMS_OUTPUT_SETTINGS_H +#define LMMS_OUTPUT_SETTINGS_H #include "lmms_basics.h" - namespace lmms { @@ -36,19 +35,18 @@ namespace lmms class OutputSettings { public: - enum BitDepth + enum class BitDepth { - Depth_16Bit, - Depth_24Bit, - Depth_32Bit, - NumDepths + Depth16Bit, + Depth24Bit, + Depth32Bit }; - enum StereoMode + enum class StereoMode { - StereoMode_Stereo, - StereoMode_JointStereo, - StereoMode_Mono + Stereo, + JointStereo, + Mono }; class BitRateSettings @@ -86,7 +84,7 @@ public: OutputSettings( sample_rate_t sampleRate, BitRateSettings const & bitRateSettings, BitDepth bitDepth ) : - OutputSettings(sampleRate, bitRateSettings, bitDepth, StereoMode_Stereo ) + OutputSettings(sampleRate, bitRateSettings, bitDepth, StereoMode::Stereo ) { } @@ -120,4 +118,4 @@ private: } // namespace lmms -#endif +#endif // LMMS_OUTPUT_SETTINGS_H diff --git a/include/PathUtil.h b/include/PathUtil.h index 8b1ef1097..9b410d014 100644 --- a/include/PathUtil.h +++ b/include/PathUtil.h @@ -22,8 +22,8 @@ * */ -#ifndef PATHUTIL_H -#define PATHUTIL_H +#ifndef LMMS_PATHUTIL_H +#define LMMS_PATHUTIL_H #include "lmms_export.h" @@ -70,4 +70,4 @@ namespace lmms::PathUtil } // namespace lmms::PathUtil -#endif +#endif // LMMS_PATHUTIL_H diff --git a/include/PatternClip.h b/include/PatternClip.h index f1d0e2557..968a0b198 100644 --- a/include/PatternClip.h +++ b/include/PatternClip.h @@ -21,10 +21,9 @@ * Boston, MA 02110-1301 USA. * */ - -#ifndef PATTERN_CLIP_H -#define PATTERN_CLIP_H +#ifndef LMMS_PATTERN_CLIP_H +#define LMMS_PATTERN_CLIP_H #include "Clip.h" @@ -59,4 +58,4 @@ private: } // namespace lmms -#endif +#endif // LMMS_PATTERN_CLIP_H diff --git a/include/PatternClipView.h b/include/PatternClipView.h index d170e4bef..ee313f32a 100644 --- a/include/PatternClipView.h +++ b/include/PatternClipView.h @@ -21,11 +21,9 @@ * Boston, MA 02110-1301 USA. * */ - - -#ifndef PATTERN_CLIP_VIEW_H -#define PATTERN_CLIP_VIEW_H +#ifndef LMMS_GUI_PATTERN_CLIP_VIEW_H +#define LMMS_GUI_PATTERN_CLIP_VIEW_H #include @@ -74,4 +72,4 @@ private: } // namespace lmms -#endif +#endif // LMMS_GUI_PATTERN_CLIP_VIEW_H diff --git a/include/PatternEditor.h b/include/PatternEditor.h index ffe08e335..5787f27ec 100644 --- a/include/PatternEditor.h +++ b/include/PatternEditor.h @@ -22,10 +22,8 @@ * */ - -#ifndef PATTERN_EDITOR_H -#define PATTERN_EDITOR_H - +#ifndef LMMS_GUI_PATTERN_EDITOR_H +#define LMMS_GUI_PATTERN_EDITOR_H #include "Editor.h" #include "TrackContainerView.h" @@ -99,4 +97,4 @@ private: } // namespace lmms -#endif +#endif // LMMS_GUI_PATTERN_EDITOR_H diff --git a/include/PatternStore.h b/include/PatternStore.h index 7bd377941..fcb55e411 100644 --- a/include/PatternStore.h +++ b/include/PatternStore.h @@ -22,9 +22,8 @@ * */ - -#ifndef PATTERN_STORE_H -#define PATTERN_STORE_H +#ifndef LMMS_PATTERN_STORE_H +#define LMMS_PATTERN_STORE_H #include "TrackContainer.h" #include "ComboBoxModel.h" @@ -114,4 +113,4 @@ private: } // namespace lmms -#endif +#endif // LMMS_PATTERN_STORE_H diff --git a/include/PatternTrack.h b/include/PatternTrack.h index 73ddd4766..1c0c610d2 100644 --- a/include/PatternTrack.h +++ b/include/PatternTrack.h @@ -22,10 +22,8 @@ * */ - -#ifndef PATTERN_TRACK_H -#define PATTERN_TRACK_H - +#ifndef LMMS_PATTERN_TRACK_H +#define LMMS_PATTERN_TRACK_H #include @@ -104,4 +102,4 @@ private: } // namespace lmms -#endif +#endif // LMMS_PATTERN_TRACK_H diff --git a/include/PatternTrackView.h b/include/PatternTrackView.h index b6d33e444..09a1ba37b 100644 --- a/include/PatternTrackView.h +++ b/include/PatternTrackView.h @@ -22,10 +22,8 @@ * */ - -#ifndef PATTERN_TRACK_VIEW_H -#define PATTERN_TRACK_VIEW_H - +#ifndef LMMS_GUI_PATTERN_TRACK_VIEW_H +#define LMMS_GUI_PATTERN_TRACK_VIEW_H #include "TrackView.h" @@ -70,4 +68,4 @@ private: } // namespace lmms -#endif +#endif // LMMS_GUI_PATTERN_TRACK_VIEW_H diff --git a/include/PeakController.h b/include/PeakController.h index 58d666dd0..de9da3b1c 100644 --- a/include/PeakController.h +++ b/include/PeakController.h @@ -22,8 +22,8 @@ * */ -#ifndef PEAK_CONTROLLER_H -#define PEAK_CONTROLLER_H +#ifndef LMMS_PEAK_CONTROLLER_H +#define LMMS_PEAK_CONTROLLER_H #include "Controller.h" #include "ControllerDialog.h" @@ -36,7 +36,7 @@ namespace lmms class PeakControllerEffect; -using PeakControllerEffectVector = QVector; +using PeakControllerEffectVector = std::vector; class LMMS_EXPORT PeakController : public Controller { @@ -107,4 +107,4 @@ protected: } // namespace lmms -#endif +#endif // LMMS_PEAK_CONTROLLER_H diff --git a/include/PerfLog.h b/include/PerfLog.h index b8cc35f45..7b0b587f6 100644 --- a/include/PerfLog.h +++ b/include/PerfLog.h @@ -22,8 +22,8 @@ * */ -#ifndef PERFLOG_H -#define PERFLOG_H +#ifndef LMMS_PERFLOG_H +#define LMMS_PERFLOG_H #include #include @@ -77,4 +77,4 @@ class PerfLogTimer } // namespace lmms -#endif +#endif // LMMS_PERFLOG_H diff --git a/include/Piano.h b/include/Piano.h index 531bb65a6..698d9c8fe 100644 --- a/include/Piano.h +++ b/include/Piano.h @@ -22,8 +22,8 @@ * */ -#ifndef PIANO_H -#define PIANO_H +#ifndef LMMS_PIANO_H +#define LMMS_PIANO_H #include "Note.h" #include "Model.h" @@ -38,10 +38,10 @@ class MidiEventProcessor; class Piano final : public Model { public: - enum KeyTypes + enum class KeyType { - WhiteKey, - BlackKey + White, + Black } ; Piano(InstrumentTrack* track); @@ -89,5 +89,4 @@ private: } // namespace lmms -#endif - +#endif // LMMS_PIANO_H diff --git a/include/PianoRoll.h b/include/PianoRoll.h index c1973f407..35550a5b3 100644 --- a/include/PianoRoll.h +++ b/include/PianoRoll.h @@ -24,12 +24,13 @@ * */ -#ifndef PIANO_ROLL_H -#define PIANO_ROLL_H +#ifndef LMMS_GUI_PIANO_ROLL_H +#define LMMS_GUI_PIANO_ROLL_H -#include #include +#include + #include "Editor.h" #include "ComboBoxModel.h" #include "SerializingObject.h" @@ -60,7 +61,7 @@ namespace gui class ComboBox; class PositionLine; -class TextFloat; +class SimpleTextFloat; class TimeLineWidget; @@ -72,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) @@ -103,13 +105,13 @@ class PianoRoll : public QWidget Q_PROPERTY(QBrush blackKeyActiveBackground MEMBER m_blackKeyActiveBackground) Q_PROPERTY(QBrush blackKeyDisabledBackground MEMBER m_blackKeyDisabledBackground) public: - enum EditModes + enum class EditMode { - ModeDraw, - ModeErase, - ModeSelect, - ModeEditDetuning, - ModeEditKnife + Draw, + Erase, + Select, + Detuning, + Knife }; /*! \brief Resets settings to default when e.g. creating a new project */ @@ -149,17 +151,29 @@ public: { return m_midiClip != nullptr; } + + int trackOctaveSize() const; - Song::PlayModes desiredPlayModeForAccompany() const; + Song::PlayMode desiredPlayModeForAccompany() const; int quantization() const; protected: - enum QuantizeActions + enum class QuantizeAction { - QuantizeBoth, - QuantizePos, - QuantizeLength + Both, + Pos, + Length + }; + + enum class SemiToneMarkerAction + { + UnmarkAll, + MarkCurrentSemiTone, + MarkAllOctaveSemiTones, + MarkCurrentScale, + MarkCurrentChord, + CopyAllNotesOnKey }; void keyPressEvent( QKeyEvent * ke ) override; @@ -218,12 +232,12 @@ protected slots: void quantizeChanged(); void noteLengthChanged(); void keyChanged(); - void quantizeNotes(lmms::gui::PianoRoll::QuantizeActions mode = QuantizeBoth); + void quantizeNotes(QuantizeAction mode = QuantizeAction::Both); void updateSemiToneMarkerMenu(); void changeNoteEditMode( int i ); - void markSemiTone(int i, bool fromMenu = true); + void markSemiTone(SemiToneMarkerAction i, bool fromMenu = true); void hideMidiClip( lmms::MidiClip* clip ); @@ -245,51 +259,41 @@ signals: private: - enum Actions + enum class Action { - ActionNone, - ActionMoveNote, - ActionResizeNote, - ActionSelectNotes, - ActionChangeNoteProperty, - ActionResizeNoteEditArea, - ActionKnife + None, + MoveNote, + ResizeNote, + SelectNotes, + ChangeNoteProperty, + ResizeNoteEditArea, + Knife }; - enum NoteEditMode + enum class NoteEditMode { - NoteEditVolume, - NoteEditPanning, - NoteEditCount // make sure this one is always last + Volume, + Panning, + Count // make sure this one is always last }; - enum SemiToneMarkerAction + enum class KeyType { - stmaUnmarkAll, - stmaMarkCurrentSemiTone, - stmaMarkAllOctaveSemiTones, - stmaMarkCurrentScale, - stmaMarkCurrentChord, - stmaCopyAllNotesOnKey + WhiteSmall, + WhiteBig, + Black }; - enum PianoRollKeyTypes + enum class GridMode { - PR_WHITE_KEY_SMALL, - PR_WHITE_KEY_BIG, - PR_BLACK_KEY - }; - - enum GridMode - { - gridNudge, - gridSnap - // gridFree + Nudge, + Snap + // Free }; PositionLine * m_positionLine; - QVector m_nemStr; // gui names of each edit mode + std::vector m_nemStr; // gui names of each edit mode QMenu * m_noteEditMenu; // when you right click below the key area QList m_markedSemiTones; @@ -305,9 +309,9 @@ private: TimePos newNoteLen() const; void shiftPos(int amount); - void shiftPos(NoteVector notes, int amount); + void shiftPos(const NoteVector& notes, int amount); void shiftSemiTone(int amount); - void shiftSemiTone(NoteVector notes, int amount); + void shiftSemiTone(const NoteVector& notes, int amount); bool isSelection() const; int selectionCount() const; void testPlayNote( Note * n ); @@ -336,16 +340,16 @@ 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; + static std::array prKeyOrder; - static TextFloat * s_textFloat; + static SimpleTextFloat * s_textFloat; ComboBoxModel m_zoomingModel; ComboBoxModel m_zoomingYModel; @@ -356,8 +360,8 @@ private: ComboBoxModel m_chordModel; ComboBoxModel m_snapModel; - static const QVector m_zoomLevels; - static const QVector m_zoomYLevels; + static const std::vector m_zoomLevels; + static const std::vector m_zoomYLevels; MidiClip* m_midiClip; NoteVector m_ghostNotes; @@ -372,10 +376,11 @@ private: TimePos m_currentPosition; bool m_recording; + bool m_doAutoQuantization{false}; QList m_recordingNotes; Note * m_currentNote; - Actions m_action; + Action m_action; NoteEditMode m_noteEditMode; GridMode m_gridMode; @@ -426,9 +431,9 @@ private: int m_startKey; // first key when drawing int m_lastKey; - EditModes m_editMode; - EditModes m_ctrlMode; // mode they were in before they hit ctrl - EditModes m_knifeMode; // mode they where in before entering knife mode + EditMode m_editMode; + EditMode m_ctrlMode; // mode they were in before they hit ctrl + EditMode m_knifeMode; // mode they where in before entering knife mode bool m_mouseDownRight; //true if right click is being held down @@ -463,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; @@ -573,4 +579,4 @@ private: } // namespace lmms -#endif +#endif // LMMS_GUI_PIANO_ROLL_H diff --git a/include/PianoView.h b/include/PianoView.h index 7f586809d..3f8d8026f 100644 --- a/include/PianoView.h +++ b/include/PianoView.h @@ -22,14 +22,15 @@ * */ -#ifndef PIANO_VIEW_H -#define PIANO_VIEW_H +#ifndef LMMS_GUI_PIANO_VIEW_H +#define LMMS_GUI_PIANO_VIEW_H #include #include #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; @@ -103,4 +104,4 @@ signals: } // namespace lmms -#endif +#endif // LMMS_GUI_PIANO_VIEW_H diff --git a/include/Pitch.h b/include/Pitch.h index 6213ef374..1f721b5f1 100644 --- a/include/Pitch.h +++ b/include/Pitch.h @@ -22,8 +22,8 @@ * */ -#ifndef PITCH_H -#define PITCH_H +#ifndef LMMS_PITCH_H +#define LMMS_PITCH_H #include @@ -40,4 +40,4 @@ constexpr pitch_t DefaultPitch = 0; } // namespace lmms -#endif +#endif // LMMS_PITCH_H diff --git a/include/PixmapButton.h b/include/PixmapButton.h index 6c37f9112..734bd11ae 100644 --- a/include/PixmapButton.h +++ b/include/PixmapButton.h @@ -22,9 +22,8 @@ * */ - -#ifndef PIXMAP_BUTTON_H -#define PIXMAP_BUTTON_H +#ifndef LMMS_GUI_PIXMAP_BUTTON_H +#define LMMS_GUI_PIXMAP_BUTTON_H #include @@ -57,6 +56,8 @@ protected: void mouseReleaseEvent( QMouseEvent * _me ) override; void mouseDoubleClickEvent( QMouseEvent * _me ) override; +private: + bool isActive() const; private: QPixmap m_activePixmap; @@ -68,4 +69,4 @@ private: } // namespace lmms::gui -#endif +#endif // LMMS_GUI_PIXMAP_BUTTON_H diff --git a/include/PlayHandle.h b/include/PlayHandle.h index 542d45871..8f5d771ed 100644 --- a/include/PlayHandle.h +++ b/include/PlayHandle.h @@ -22,15 +22,15 @@ * */ -#ifndef PLAY_HANDLE_H -#define PLAY_HANDLE_H +#ifndef LMMS_PLAY_HANDLE_H +#define LMMS_PLAY_HANDLE_H #include #include #include "lmms_export.h" - +#include "Flags.h" #include "ThreadableJob.h" #include "lmms_basics.h" @@ -45,19 +45,16 @@ class AudioPort; class LMMS_EXPORT PlayHandle : public ThreadableJob { public: - enum Types + enum class Type { - TypeNotePlayHandle = 0x01, - TypeInstrumentPlayHandle = 0x02, - TypeSamplePlayHandle = 0x04, - TypePresetPreviewHandle = 0x08 + NotePlayHandle = 0x01, + InstrumentPlayHandle = 0x02, + SamplePlayHandle = 0x04, + PresetPreviewHandle = 0x08 } ; - using Type = Types; + using Types = Flags; - enum - { - MaxNumber = 1024 - } ; + constexpr static std::size_t MaxNumber = 1024; PlayHandle( const Type type, f_cnt_t offset = 0 ); @@ -164,6 +161,8 @@ private: using PlayHandleList = QList; using ConstPlayHandleList = QList; +LMMS_DECLARE_OPERATORS_FOR_FLAGS(PlayHandle::Type) + } // namespace lmms -#endif +#endif // LMMS_PLAY_HANDLE_H diff --git a/include/Plugin.h b/include/Plugin.h index 6a4d95bb4..100e9f658 100644 --- a/include/Plugin.h +++ b/include/Plugin.h @@ -22,15 +22,14 @@ * */ -#ifndef PLUGIN_H -#define PLUGIN_H +#ifndef LMMS_PLUGIN_H +#define LMMS_PLUGIN_H #include #include #include "JournallingObject.h" #include "Model.h" -#include "MemoryManager.h" class QWidget; @@ -71,10 +70,9 @@ class PluginView; */ class LMMS_EXPORT Plugin : public Model, public JournallingObject { - MM_OPERATORS Q_OBJECT public: - enum PluginTypes + enum class Type { Instrument, // instrument being used in channel-track Effect, // effect-plugin for effect-board @@ -97,7 +95,7 @@ public: const char * description; const char * author; int version; - PluginTypes type; + Type type; const PixmapLoader * logo; const char * supportedFileTypes; //!< csv list of extensions @@ -181,7 +179,7 @@ public: using KeyList = QList; - SubPluginFeatures( Plugin::PluginTypes type ) : + SubPluginFeatures( Plugin::Type type ) : m_type( type ) { } @@ -227,7 +225,7 @@ public: } protected: - const Plugin::PluginTypes m_type; + const Plugin::Type m_type; } ; SubPluginFeatures * subPluginFeatures; @@ -250,7 +248,7 @@ public: const PixmapLoader *logo() const; //! Return plugin type - inline PluginTypes type() const + inline Type type() const { return m_descriptor->type; } @@ -312,4 +310,4 @@ private: } // namespace lmms -#endif +#endif // LMMS_PLUGIN_H diff --git a/include/PluginBrowser.h b/include/PluginBrowser.h index 1f2041cf4..2bb1b6d5c 100644 --- a/include/PluginBrowser.h +++ b/include/PluginBrowser.h @@ -22,8 +22,8 @@ * */ -#ifndef PLUGIN_BROWSER_H -#define PLUGIN_BROWSER_H +#ifndef LMMS_GUI_PLUGIN_BROWSER_H +#define LMMS_GUI_PLUGIN_BROWSER_H #include @@ -56,8 +56,6 @@ private: }; - - class PluginDescWidget : public QWidget { Q_OBJECT @@ -65,13 +63,14 @@ public: using PluginKey = Plugin::Descriptor::SubPluginFeatures::Key; PluginDescWidget( const PluginKey & _pk, QWidget * _parent ); QString name() const; - + void openInNewInstrumentTrack(QString value); protected: void enterEvent( QEvent * _e ) override; void leaveEvent( QEvent * _e ) override; void mousePressEvent( QMouseEvent * _me ) override; void paintEvent( QPaintEvent * _pe ) override; + void contextMenuEvent(QContextMenuEvent* e) override; private: constexpr static int DEFAULT_HEIGHT{24}; @@ -85,4 +84,4 @@ private: } // namespace lmms::gui -#endif +#endif // LMMS_GUI_PLUGIN_BROWSER_H diff --git a/include/PluginFactory.h b/include/PluginFactory.h index 8b8b88b6e..7221f2b09 100644 --- a/include/PluginFactory.h +++ b/include/PluginFactory.h @@ -22,16 +22,17 @@ * */ -#ifndef PLUGINFACTORY_H -#define PLUGINFACTORY_H +#ifndef LMMS_PLUGIN_FACTORY_H +#define LMMS_PLUGIN_FACTORY_H #include #include +#include #include +#include #include #include -#include #include "lmms_export.h" #include "Plugin.h" @@ -46,7 +47,7 @@ class LMMS_EXPORT PluginFactory public: struct PluginInfo { - const QString name() const; + QString name() const; QFileInfo file; std::shared_ptr library = nullptr; Plugin::Descriptor* descriptor = nullptr; @@ -54,7 +55,7 @@ public: bool isNull() const {return ! library;} }; using PluginInfoList = QList; - using DescriptorMap = QMultiMap; + using DescriptorMap = QMultiMap; PluginFactory(); ~PluginFactory() = default; @@ -66,8 +67,8 @@ public: static PluginFactory* instance(); /// Returns a list of all found plugins' descriptors. - const Plugin::DescriptorList descriptors() const; - const Plugin::DescriptorList descriptors(Plugin::PluginTypes type) const; + Plugin::DescriptorList descriptors() const; + Plugin::DescriptorList descriptors(Plugin::Type type) const; struct PluginInfoAndKey { @@ -79,12 +80,12 @@ public: /// Returns a list of all found plugins' PluginFactory::PluginInfo objects. const PluginInfoList& pluginInfos() const; /// Returns a plugin that support the given file extension - const PluginInfoAndKey pluginSupportingExtension(const QString& ext); + PluginInfoAndKey pluginSupportingExtension(const QString& ext); /// Returns the PluginInfo object of the plugin with the given name. /// If the plugin is not found, an empty PluginInfo is returned (use /// PluginInfo::isNull() to check this). - const PluginInfo pluginInfo(const char* name) const; + PluginInfo pluginInfo(const char* name) const; /// When loading a library fails during discovery, the error string is saved. /// It can be retrieved by calling this function. @@ -98,7 +99,7 @@ private: PluginInfoList m_pluginInfos; QMap m_pluginByExt; - QVector m_garbage; //!< cleaned up at destruction + std::vector m_garbage; //!< cleaned up at destruction QHash m_errors; @@ -111,4 +112,4 @@ LMMS_EXPORT PluginFactory* getPluginFactory(); } // namespace lmms -#endif // PLUGINFACTORY_H +#endif // LMMS_PLUGIN_FACTORY_H diff --git a/include/PluginIssue.h b/include/PluginIssue.h index f0fa275f3..01a4268ec 100644 --- a/include/PluginIssue.h +++ b/include/PluginIssue.h @@ -22,44 +22,43 @@ * */ -#ifndef PLUGINISSUE_H -#define PLUGINISSUE_H +#ifndef LMMS_PLUGIN_ISSUE_H +#define LMMS_PLUGIN_ISSUE_H #include #include - namespace lmms { //! Types of issues that can cause LMMS to not load a plugin //! LMMS Plugins should use this to indicate errors -enum PluginIssueType +enum class PluginIssueType { // port flow & type - unknownPortFlow, - unknownPortType, + UnknownPortFlow, + UnknownPortType, // channel count - tooManyInputChannels, - tooManyOutputChannels, - tooManyMidiInputChannels, - tooManyMidiOutputChannels, - noOutputChannel, + TooManyInputChannels, + TooManyOutputChannels, + TooManyMidiInputChannels, + TooManyMidiOutputChannels, + NoOutputChannel, // port metadata - portHasNoDef, - portHasNoMin, - portHasNoMax, - minGreaterMax, - defaultValueNotInRange, - logScaleMinMissing, - logScaleMaxMissing, - logScaleMinMaxDifferentSigns, + PortHasNoDef, + PortHasNoMin, + PortHasNoMax, + MinGreaterMax, + DefaultValueNotInRange, + LogScaleMinMissing, + LogScaleMaxMissing, + LogScaleMinMaxDifferentSigns, // features - featureNotSupported, //!< plugin requires functionality LMMS can't offer + FeatureNotSupported, //!< plugin requires functionality LMMS can't offer // misc - badPortType, //!< port type not supported - blacklisted, - noIssue + BadPortType, //!< port type not supported + Blacklisted, + NoIssue }; //! Issue type bundled with informational string @@ -85,5 +84,4 @@ QDebug operator<<(QDebug stream, const PluginIssue& iss); } // namespace lmms - -#endif // PLUGINISSUE_H +#endif // LMMS_PLUGIN_ISSUE_H diff --git a/include/PluginView.h b/include/PluginView.h index 2c06b9ba2..3c78cb00a 100644 --- a/include/PluginView.h +++ b/include/PluginView.h @@ -22,8 +22,8 @@ * */ -#ifndef PLUGIN_VIEW_H -#define PLUGIN_VIEW_H +#ifndef LMMS_GUI_PLUGIN_VIEW_H +#define LMMS_GUI_PLUGIN_VIEW_H #include @@ -47,4 +47,4 @@ public: } // namespace lmms::gui -#endif +#endif // LMMS_GUI_PLUGIN_VIEW_H diff --git a/include/PositionLine.h b/include/PositionLine.h index cc1313e57..3e798948f 100644 --- a/include/PositionLine.h +++ b/include/PositionLine.h @@ -23,8 +23,8 @@ * */ -#ifndef POSITION_LINE_H -#define POSITION_LINE_H +#ifndef LMMS_GUI_POSITION_LINE_H +#define LMMS_GUI_POSITION_LINE_H #include @@ -51,4 +51,4 @@ private: } // namespace lmms::gui -#endif +#endif // LMMS_GUI_POSITION_LINE_H diff --git a/include/PresetPreviewPlayHandle.h b/include/PresetPreviewPlayHandle.h index dfff0fc21..b8199a71b 100644 --- a/include/PresetPreviewPlayHandle.h +++ b/include/PresetPreviewPlayHandle.h @@ -23,8 +23,8 @@ * */ -#ifndef PRESET_PREVIEW_PLAY_HANDLE_H -#define PRESET_PREVIEW_PLAY_HANDLE_H +#ifndef LMMS_PRESET_PREVIEW_PLAY_HANDLE_H +#define LMMS_PRESET_PREVIEW_PLAY_HANDLE_H #include "NotePlayHandle.h" @@ -69,4 +69,4 @@ private: } // namespace lmms -#endif +#endif // LMMS_PRESET_PREVIEW_PLAY_HANDLE_H diff --git a/include/ProjectJournal.h b/include/ProjectJournal.h index 31a61755d..841bbf094 100644 --- a/include/ProjectJournal.h +++ b/include/ProjectJournal.h @@ -22,8 +22,8 @@ * */ -#ifndef PROJECT_JOURNAL_H -#define PROJECT_JOURNAL_H +#ifndef LMMS_PROJECT_JOURNAL_H +#define LMMS_PROJECT_JOURNAL_H #include #include @@ -104,7 +104,7 @@ private: struct CheckPoint { - CheckPoint( jo_id_t initID = 0, const DataFile& initData = DataFile( DataFile::JournalData ) ) : + CheckPoint( jo_id_t initID = 0, const DataFile& initData = DataFile( DataFile::Type::JournalData ) ) : joID( initID ), data( initData ) { @@ -126,5 +126,4 @@ private: } // namespace lmms -#endif - +#endif // LMMS_PROJECT_JOURNAL_H diff --git a/include/ProjectNotes.h b/include/ProjectNotes.h index 4b9476616..861dcb4a8 100644 --- a/include/ProjectNotes.h +++ b/include/ProjectNotes.h @@ -22,9 +22,8 @@ * */ - -#ifndef PROJECT_NOTES_H -#define PROJECT_NOTES_H +#ifndef LMMS_GUI_PROJECT_NOTES_H +#define LMMS_GUI_PROJECT_NOTES_H #include @@ -94,4 +93,4 @@ private: } // namespace lmms::gui -#endif +#endif // LMMS_GUI_PROJECT_NOTES_H diff --git a/include/ProjectRenderer.h b/include/ProjectRenderer.h index 2ca20569b..14c584a2e 100644 --- a/include/ProjectRenderer.h +++ b/include/ProjectRenderer.h @@ -22,8 +22,8 @@ * */ -#ifndef PROJECT_RENDERER_H -#define PROJECT_RENDERER_H +#ifndef LMMS_PROJECT_RENDERER_H +#define LMMS_PROJECT_RENDERER_H #include "AudioFileDevice.h" #include "lmmsconfig.h" @@ -40,20 +40,21 @@ class LMMS_EXPORT ProjectRenderer : public QThread { Q_OBJECT public: - enum ExportFileFormats: int + enum class ExportFileFormat : int { - WaveFile, - FlacFile, - OggFile, - MP3File, - NumFileFormats + Wave, + Flac, + Ogg, + MP3, + Count } ; + constexpr static auto NumFileFormats = static_cast(ExportFileFormat::Count); struct FileEncodeDevice { bool isAvailable() const { return m_getDevInst != nullptr; } - ExportFileFormats m_fileFormat; + ExportFileFormat m_fileFormat; const char * m_description; const char * m_extension; AudioFileDeviceInstantiaton m_getDevInst; @@ -62,7 +63,7 @@ public: ProjectRenderer( const AudioEngine::qualitySettings & _qs, const OutputSettings & _os, - ExportFileFormats _file_format, + ExportFileFormat _file_format, const QString & _out_file ); ~ProjectRenderer() override = default; @@ -71,10 +72,10 @@ public: return m_fileDev != nullptr; } - static ExportFileFormats getFileFormatFromExtension( + static ExportFileFormat getFileFormatFromExtension( const QString & _ext ); - static QString getFileExtensionFromFormat( ExportFileFormats fmt ); + static QString getFileExtensionFromFormat( ExportFileFormat fmt ); static const std::array fileEncodeDevices; @@ -103,4 +104,4 @@ private: } // namespace lmms -#endif +#endif // LMMS_PROJECT_RENDERER_H diff --git a/include/ProjectVersion.h b/include/ProjectVersion.h index 5be259c97..20b32d1c9 100644 --- a/include/ProjectVersion.h +++ b/include/ProjectVersion.h @@ -23,9 +23,8 @@ * */ - -#ifndef PROJECT_VERSION_H -#define PROJECT_VERSION_H +#ifndef LMMS_PROJECT_VERSION_H +#define LMMS_PROJECT_VERSION_H #include #include @@ -43,11 +42,11 @@ namespace lmms class ProjectVersion { public: - enum CompareType : int { None = 0, Major=1, Minor=2, Release=3, Stage=4, Build=5, All = std::numeric_limits::max() }; + enum class CompareType : int { None = 0, Major=1, Minor=2, Release=3, Stage=4, Build=5, All = std::numeric_limits::max() }; - ProjectVersion(QString version, CompareType c = All); - ProjectVersion(const char * version, CompareType c = All); + ProjectVersion(QString version, CompareType c = CompareType::All); + ProjectVersion(const char * version, CompareType c = CompareType::All); const QString& getVersion() const { return m_version; } int getMajor() const { return m_major; } @@ -82,4 +81,4 @@ inline bool operator!=(const ProjectVersion & v1, const ProjectVersion & v2) { r } // namespace lmms -#endif +#endif // LMMS_PROJECT_VERSION_H diff --git a/include/QuadratureLfo.h b/include/QuadratureLfo.h index 42d4d32c0..6f007e072 100644 --- a/include/QuadratureLfo.h +++ b/include/QuadratureLfo.h @@ -22,8 +22,8 @@ * */ -#ifndef QUADRATURELFO_H -#define QUADRATURELFO_H +#ifndef LMMS_QUADRATURE_LFO_H +#define LMMS_QUADRATURE_LFO_H #include "lmms_math.h" @@ -100,4 +100,4 @@ private: } // namespace lmms -#endif // QUADRATURELFO_H +#endif // LMMS_QUADRATURE_LFO_H diff --git a/include/RaiiHelpers.h b/include/RaiiHelpers.h index 0cfa2c4ac..3108dc080 100644 --- a/include/RaiiHelpers.h +++ b/include/RaiiHelpers.h @@ -19,10 +19,11 @@ * 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 RAII_HELPERS_H -#define RAII_HELPERS_H +#ifndef LMMS_RAII_HELPERS_H +#define LMMS_RAII_HELPERS_H #include #include @@ -63,4 +64,4 @@ using UniqueNullableResource = std::unique_ptr @@ -45,4 +45,4 @@ private slots: } // namespace lmms::gui -#endif // RECENTPROJECTSMENU_H +#endif // LMMS_GUI_RECENT_PROJECTS_MENU_H diff --git a/include/RemotePlugin.h b/include/RemotePlugin.h index 96c910d5d..c5fcd7dd2 100644 --- a/include/RemotePlugin.h +++ b/include/RemotePlugin.h @@ -22,8 +22,8 @@ * */ -#ifndef REMOTE_PLUGIN_H -#define REMOTE_PLUGIN_H +#ifndef LMMS_REMOTE_PLUGIN_H +#define LMMS_REMOTE_PLUGIN_H #include "RemotePluginBase.h" #include "SharedMemory.h" @@ -197,4 +197,4 @@ LMMS_EXPORT inline std::string QSTR_TO_STDSTR(QString const& qstr) } // namespace lmms -#endif // REMOTE_PLUGIN_H +#endif // LMMS_REMOTE_PLUGIN_H diff --git a/include/RemotePluginBase.h b/include/RemotePluginBase.h index e309496bc..357be1bea 100644 --- a/include/RemotePluginBase.h +++ b/include/RemotePluginBase.h @@ -22,8 +22,8 @@ * */ -#ifndef REMOTE_PLUGIN_BASE_H -#define REMOTE_PLUGIN_BASE_H +#ifndef LMMS_REMOTE_PLUGIN_BASE_H +#define LMMS_REMOTE_PLUGIN_BASE_H #include "MidiEvent.h" @@ -673,4 +673,4 @@ private: } // namespace lmms -#endif // REMOTE_PLUGIN_BASE_H +#endif // LMMS_REMOTE_PLUGIN_BASE_H diff --git a/include/RemotePluginClient.h b/include/RemotePluginClient.h index 4c5fcc060..77eef68f0 100644 --- a/include/RemotePluginClient.h +++ b/include/RemotePluginClient.h @@ -22,8 +22,8 @@ * */ -#ifndef REMOTE_PLUGIN_CLIENT_H -#define REMOTE_PLUGIN_CLIENT_H +#ifndef LMMS_REMOTE_PLUGIN_CLIENT_H +#define LMMS_REMOTE_PLUGIN_CLIENT_H #include "RemotePluginBase.h" @@ -355,4 +355,4 @@ void RemotePluginClient::doProcessing() } // namespace lmms -#endif // REMOTE_PLUGIN_CLIENT_H +#endif // LMMS_REMOTE_PLUGIN_CLIENT_H diff --git a/include/RenameDialog.h b/include/RenameDialog.h index b543963d7..1129f2bb5 100644 --- a/include/RenameDialog.h +++ b/include/RenameDialog.h @@ -23,13 +23,11 @@ * */ - -#ifndef RENAME_DIALOG_H -#define RENAME_DIALOG_H +#ifndef LMMS_GUI_RENAME_DIALOG_H +#define LMMS_GUI_RENAME_DIALOG_H #include - class QLineEdit; namespace lmms::gui @@ -62,4 +60,4 @@ private: } // namespace lmms::gui -#endif +#endif // LMMS_GUI_RENAME_DIALOG_H diff --git a/include/RenderManager.h b/include/RenderManager.h index 9ec4c511e..686522778 100644 --- a/include/RenderManager.h +++ b/include/RenderManager.h @@ -23,8 +23,8 @@ * */ -#ifndef RENDER_MANAGER_H -#define RENDER_MANAGER_H +#ifndef LMMS_RENDER_MANAGER_H +#define LMMS_RENDER_MANAGER_H #include @@ -43,7 +43,7 @@ public: RenderManager( const AudioEngine::qualitySettings & qualitySettings, const OutputSettings & outputSettings, - ProjectRenderer::ExportFileFormats fmt, + ProjectRenderer::ExportFileFormat fmt, QString outputPath); ~RenderManager() override; @@ -73,16 +73,16 @@ private: const AudioEngine::qualitySettings m_qualitySettings; const AudioEngine::qualitySettings m_oldQualitySettings; const OutputSettings m_outputSettings; - ProjectRenderer::ExportFileFormats m_format; + ProjectRenderer::ExportFileFormat m_format; QString m_outputPath; std::unique_ptr m_activeRenderer; - QVector m_tracksToRender; - QVector m_unmuted; + std::vector m_tracksToRender; + std::vector m_unmuted; } ; } // namespace lmms -#endif +#endif // LMMS_RENDER_MANAGER_H diff --git a/include/RingBuffer.h b/include/RingBuffer.h index c7db6b4e5..98f726475 100644 --- a/include/RingBuffer.h +++ b/include/RingBuffer.h @@ -23,14 +23,13 @@ * */ - -#ifndef RINGBUFFER_H -#define RINGBUFFER_H +#ifndef LMMS_RING_BUFFER_H +#define LMMS_RING_BUFFER_H #include #include #include "lmms_basics.h" -#include "MemoryManager.h" +#include "lmms_export.h" namespace lmms @@ -42,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 @@ -225,4 +223,4 @@ private: } // namespace lmms -#endif +#endif // LMMS_RING_BUFFER_H diff --git a/include/RmsHelper.h b/include/RmsHelper.h index 19b7da89c..fd2c0f9bb 100644 --- a/include/RmsHelper.h +++ b/include/RmsHelper.h @@ -23,8 +23,8 @@ * */ -#ifndef RMS_HELPER_H -#define RMS_HELPER_H +#ifndef LMMS_RMS_HELPER_H +#define LMMS_RMS_HELPER_H #include "lmms_math.h" @@ -98,4 +98,4 @@ private: } // namespace lmms -#endif +#endif // LMMS_RMS_HELPER_H diff --git a/include/RowTableView.h b/include/RowTableView.h index 9934dbade..3e1c1988d 100644 --- a/include/RowTableView.h +++ b/include/RowTableView.h @@ -22,8 +22,8 @@ * */ -#ifndef ROW_TABLE_VIEW_H -#define ROW_TABLE_VIEW_H +#ifndef LMMS_GUI_ROW_TABLE_VIEW_H +#define LMMS_GUI_ROW_TABLE_VIEW_H #include @@ -56,4 +56,4 @@ private: } // namespace lmms::gui -#endif +#endif // LMMS_GUI_ROW_TABLE_VIEW_H diff --git a/include/Rubberband.h b/include/Rubberband.h index d35233b4f..cfe36bb70 100644 --- a/include/Rubberband.h +++ b/include/Rubberband.h @@ -23,9 +23,8 @@ * */ - -#ifndef RUBBERBAND_H -#define RUBBERBAND_H +#ifndef LMMS_GUI_RUBBERBAND_H +#define LMMS_GUI_RUBBERBAND_H #include #include @@ -94,5 +93,4 @@ private: } // namespace lmms::gui -#endif - +#endif // LMMS_GUI_RUBBERBAND_H diff --git a/include/Sample.h b/include/Sample.h new file mode 100644 index 000000000..86fba1ddc --- /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()->processingSampleRate()); + Sample(const sampleFrame* data, size_t numFrames, int sampleRate = Engine::audioEngine()->processingSampleRate()); + 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 f4d67859e..4089eb446 100644 --- a/include/SampleBuffer.h +++ b/include/SampleBuffer.h @@ -22,338 +22,78 @@ * */ +#ifndef LMMS_SAMPLE_BUFFER_H +#define LMMS_SAMPLE_BUFFER_H -#ifndef SAMPLE_BUFFER_H -#define 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 LoopMode { - LoopOff = 0, - LoopOn, - LoopPingPong - }; - 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()->processingSampleRate()); - 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 = LoopOff - ); - - 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()->processingSampleRate(); +}; } // namespace lmms -#endif +#endif // LMMS_SAMPLE_BUFFER_H diff --git a/include/SampleClip.h b/include/SampleClip.h index 51225ded8..da11996b1 100644 --- a/include/SampleClip.h +++ b/include/SampleClip.h @@ -21,11 +21,13 @@ * Boston, MA 02110-1301 USA. * */ - -#ifndef SAMPLE_CLIP_H -#define SAMPLE_CLIP_H +#ifndef LMMS_SAMPLE_CLIP_H +#define LMMS_SAMPLE_CLIP_H + +#include #include "Clip.h" +#include "Sample.h" namespace lmms { @@ -45,14 +47,15 @@ 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; + const QString& sampleFile() const; void saveSettings( QDomDocument & _doc, QDomElement & _parent ) override; void loadSettings( const QDomElement & _this ) override; @@ -61,9 +64,9 @@ public: return "sampleclip"; } - SampleBuffer* sampleBuffer() + Sample& sample() { - return m_sampleBuffer; + return m_sample; } TimePos sampleLength() const; @@ -74,10 +77,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 +88,7 @@ public slots: private: - SampleBuffer* m_sampleBuffer; + Sample m_sample; BoolModel m_recordModel; bool m_isPlaying; @@ -100,4 +103,4 @@ signals: } // namespace lmms -#endif +#endif // LMMS_SAMPLE_CLIP_H diff --git a/include/SampleClipView.h b/include/SampleClipView.h index 74873dca7..4ff218fb0 100644 --- a/include/SampleClipView.h +++ b/include/SampleClipView.h @@ -22,13 +22,11 @@ * */ -#ifndef SAMPLE_CLIP_VIEW_H -#define SAMPLE_CLIP_VIEW_H +#ifndef LMMS_GUI_SAMPLE_CLIP_VIEW_H +#define LMMS_GUI_SAMPLE_CLIP_VIEW_H #include "ClipView.h" - - namespace lmms { @@ -49,6 +47,7 @@ public: public slots: void updateSample(); void reverseSample(); + void setAutomationGhost(); @@ -73,4 +72,4 @@ private: } // namespace lmms -#endif +#endif // LMMS_GUI_SAMPLE_CLIP_VIEW_H 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..7dbdbdc33 --- /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()->processingSampleRate()); +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 69ee3b38d..280010b06 100644 --- a/include/SamplePlayHandle.h +++ b/include/SamplePlayHandle.h @@ -23,9 +23,10 @@ * */ -#ifndef SAMPLE_PLAY_HANDLE_H -#define SAMPLE_PLAY_HANDLE_H +#ifndef LMMS_SAMPLE_PLAY_HANDLE_H +#define LMMS_SAMPLE_PLAY_HANDLE_H +#include "Sample.h" #include "SampleBuffer.h" #include "AutomatableModel.h" #include "PlayHandle.h" @@ -40,10 +41,10 @@ class Track; class AudioPort; -class SamplePlayHandle : public PlayHandle +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; @@ -100,4 +101,4 @@ private: } // namespace lmms -#endif +#endif // LMMS_SAMPLE_PLAY_HANDLE_H diff --git a/include/SampleRecordHandle.h b/include/SampleRecordHandle.h index 5b23f5345..df2d7c772 100644 --- a/include/SampleRecordHandle.h +++ b/include/SampleRecordHandle.h @@ -22,12 +22,12 @@ * */ - -#ifndef SAMPLE_RECORD_HANDLE_H -#define SAMPLE_RECORD_HANDLE_H +#ifndef LMMS_SAMPLE_RECORD_HANDLE_H +#define LMMS_SAMPLE_RECORD_HANDLE_H #include #include +#include #include "PlayHandle.h" #include "TimePos.h" @@ -54,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: @@ -75,4 +75,4 @@ private: } // namespace lmms -#endif +#endif // LMMS_SAMPLE_RECORD_HANDLE_H diff --git a/include/SampleTrack.h b/include/SampleTrack.h index 8f424178a..f71f01cd1 100644 --- a/include/SampleTrack.h +++ b/include/SampleTrack.h @@ -22,9 +22,8 @@ * */ -#ifndef SAMPLE_TRACK_H -#define SAMPLE_TRACK_H - +#ifndef LMMS_SAMPLE_TRACK_H +#define LMMS_SAMPLE_TRACK_H #include "AudioPort.h" #include "Track.h" @@ -110,4 +109,4 @@ private: } // namespace lmms -#endif \ No newline at end of file +#endif // LMMS_SAMPLE_TRACK_H diff --git a/include/SampleTrackView.h b/include/SampleTrackView.h index 9921aa735..2f94bfb56 100644 --- a/include/SampleTrackView.h +++ b/include/SampleTrackView.h @@ -22,10 +22,11 @@ * */ -#ifndef SAMPLE_TRACK_VIEW_H -#define SAMPLE_TRACK_VIEW_H +#ifndef LMMS_GUI_SAMPLE_TRACK_VIEW_H +#define LMMS_GUI_SAMPLE_TRACK_VIEW_H +#include "MixerChannelLcdSpinBox.h" #include "TrackView.h" namespace lmms @@ -90,6 +91,7 @@ private slots: private: SampleTrackWindow * m_window; + MixerChannelLcdSpinBox* m_mixerChannelNumber; Knob * m_volumeKnob; Knob * m_panningKnob; FadeButton * m_activityIndicator; @@ -109,4 +111,4 @@ private: } // namespace lmms -#endif \ No newline at end of file +#endif // LMMS_GUI_SAMPLE_TRACK_VIEW_H diff --git a/include/SampleTrackWindow.h b/include/SampleTrackWindow.h index d302b2c44..4d535bfe5 100644 --- a/include/SampleTrackWindow.h +++ b/include/SampleTrackWindow.h @@ -21,12 +21,9 @@ * Boston, MA 02110-1301 USA. * */ - - -#ifndef SAMPLE_TRACK_WINDOW_H -#define SAMPLE_TRACK_WINDOW_H - +#ifndef LMMS_GUI_SAMPLE_TRACK_WINDOW_H +#define LMMS_GUI_SAMPLE_TRACK_WINDOW_H #include @@ -41,9 +38,9 @@ namespace lmms::gui class EffectRackView; class Knob; -class MixerLineLcdSpinBox; +class MixerChannelLcdSpinBox; class SampleTrackView; - + class SampleTrackWindow : public QWidget, public ModelView, public SerializingObjectHook { @@ -93,7 +90,7 @@ private: QLineEdit * m_nameLineEdit; Knob * m_volumeKnob; Knob * m_panningKnob; - MixerLineLcdSpinBox * m_mixerChannelNumber; + MixerChannelLcdSpinBox * m_mixerChannelNumber; EffectRackView * m_effectRack; } ; @@ -102,5 +99,4 @@ private: } // namespace lmms::gui - -#endif +#endif // LMMS_GUI_SAMPLE_TRACK_WINDOW_H 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/Scale.h b/include/Scale.h index 96159a53c..eeae5eb92 100644 --- a/include/Scale.h +++ b/include/Scale.h @@ -22,8 +22,8 @@ * */ -#ifndef SCALE_H -#define SCALE_H +#ifndef LMMS_SCALE_H +#define LMMS_SCALE_H #include #include @@ -89,4 +89,4 @@ private: } // namespace lmms -#endif +#endif // LMMS_SCALE_H diff --git a/include/SendButtonIndicator.h b/include/SendButtonIndicator.h index ec4a90db3..9e9417926 100644 --- a/include/SendButtonIndicator.h +++ b/include/SendButtonIndicator.h @@ -21,10 +21,12 @@ * Boston, MA 02110-1301 USA. * */ -#ifndef SENDBUTTONINDICATOR_H -#define SENDBUTTONINDICATOR_H + +#ifndef LMMS_GUI_SEND_BUTTON_INDICATOR_H +#define LMMS_GUI_SEND_BUTTON_INDICATOR_H #include +#include "embed.h" namespace lmms @@ -35,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(); }; @@ -63,4 +63,4 @@ private: } // namespace lmms -#endif // SENDBUTTONINDICATOR_H +#endif // LMMS_GUI_SEND_BUTTON_INDICATOR_H diff --git a/include/SerializingObject.h b/include/SerializingObject.h index e32d5d046..8f75d069e 100644 --- a/include/SerializingObject.h +++ b/include/SerializingObject.h @@ -22,8 +22,8 @@ * */ -#ifndef SERIALIZING_OBJECT_H -#define SERIALIZING_OBJECT_H +#ifndef LMMS_SERIALIZING_OBJECT_H +#define LMMS_SERIALIZING_OBJECT_H #include @@ -101,5 +101,4 @@ private: } // namespace lmms -#endif - +#endif // LMMS_SERIALIZING_OBJECT_H diff --git a/include/SetupDialog.h b/include/SetupDialog.h index 8973938bf..7a1304d9a 100644 --- a/include/SetupDialog.h +++ b/include/SetupDialog.h @@ -22,21 +22,20 @@ * */ - -#ifndef SETUP_DIALOG_H -#define SETUP_DIALOG_H +#ifndef LMMS_GUI_SETUP_DIALOG_H +#define LMMS_GUI_SETUP_DIALOG_H #include #include #include "AudioDevice.h" #include "AudioDeviceSetupWidget.h" -#include "LedCheckBox.h" #include "lmmsconfig.h" #include "MidiClient.h" #include "MidiSetupWidget.h" +class QCheckBox; class QComboBox; class QLabel; class QLineEdit; @@ -54,7 +53,7 @@ class SetupDialog : public QDialog Q_OBJECT public: - enum ConfigTabs + enum class ConfigTab { GeneralSettings, PerformanceSettings, @@ -63,7 +62,7 @@ public: PathsSettings }; - SetupDialog(ConfigTabs tab_to_open = GeneralSettings); + SetupDialog(ConfigTab tab_to_open = ConfigTab::GeneralSettings); ~SetupDialog() override; @@ -83,9 +82,11 @@ private slots: void toggleLetPreviewsFinish(bool enabled); void toggleSoloLegacyBehavior(bool enabled); void toggleTrackDeletionWarning(bool enabled); + void toggleMixerChannelDeletionWarning(bool enabled); void toggleMMPZ(bool enabled); void toggleDisableBackup(bool enabled); void toggleOpenLastProject(bool enabled); + void loopMarkerModeChanged(); void setLanguage(int lang); // Performance settings widget. @@ -102,11 +103,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(); @@ -142,9 +145,12 @@ private: bool m_letPreviewsFinish; bool m_soloLegacyBehavior; bool m_trackDeletionWarning; + bool m_mixerChannelDeletionWarning; bool m_MMPZ; bool m_disableBackup; bool m_openLastProject; + QString m_loopMarkerMode; + QComboBox* m_loopMarkerComboBox; QString m_lang; QStringList m_languages; @@ -154,14 +160,14 @@ private: bool m_enableRunningAutoSave; QSlider * m_saveIntervalSlider; QLabel * m_saveIntervalLbl; - LedCheckBox * m_autoSave; - LedCheckBox * m_runningAutoSave; + QCheckBox * m_autoSave; + QCheckBox * m_runningAutoSave; bool m_smoothScroll; bool m_animateAFP; QLabel * m_vstEmbedLbl; QComboBox* m_vstEmbedComboBox; QString m_vstEmbedMethod; - LedCheckBox * m_vstAlwaysOnTopCheckBox; + QCheckBox * m_vstAlwaysOnTopCheckBox; bool m_vstAlwaysOnTop; bool m_disableAutoQuit; @@ -178,12 +184,14 @@ private: int m_bufferSize; QSlider * m_bufferSizeSlider; QLabel * m_bufferSizeLbl; + QLabel * m_bufferSizeWarnLbl; // MIDI settings widgets. QComboBox * m_midiInterfaces; MswMap m_midiIfaceSetupWidgets; trMap m_midiIfaceNames; QComboBox * m_assignableMidiDevices; + bool m_midiAutoQuantize; // Paths settings widgets. QString m_workingDir; @@ -214,4 +222,4 @@ private: } // namespace lmms::gui -#endif +#endif // LMMS_GUI_SETUP_DIALOG_H diff --git a/include/SharedMemory.h b/include/SharedMemory.h index 32a40ff25..c2790c78b 100644 --- a/include/SharedMemory.h +++ b/include/SharedMemory.h @@ -19,10 +19,11 @@ * 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 SHARED_MEMORY_H -#define SHARED_MEMORY_H +#ifndef LMMS_SHARED_MEMORY_H +#define LMMS_SHARED_MEMORY_H #include #include @@ -148,4 +149,4 @@ private: } // namespace lmms -#endif // SHARED_MEMORY_H +#endif // LMMS_SHARED_MEMORY_H diff --git a/include/SideBar.h b/include/SideBar.h index 8755c660a..0dab7abd7 100644 --- a/include/SideBar.h +++ b/include/SideBar.h @@ -22,8 +22,8 @@ * */ -#ifndef SIDE_BAR_H -#define SIDE_BAR_H +#ifndef LMMS_GUI_SIDE_BAR_H +#define LMMS_GUI_SIDE_BAR_H #include #include @@ -60,4 +60,4 @@ private: } // namespace lmms::gui -#endif +#endif // LMMS_GUI_SIDE_BAR_H diff --git a/include/SideBarWidget.h b/include/SideBarWidget.h index 5df3ccb7a..caf955327 100644 --- a/include/SideBarWidget.h +++ b/include/SideBarWidget.h @@ -22,8 +22,8 @@ * */ -#ifndef SIDE_BAR_WIDGET_H -#define SIDE_BAR_WIDGET_H +#ifndef LMMS_GUI_SIDE_BAR_WIDGET_H +#define LMMS_GUI_SIDE_BAR_WIDGET_H #include #include @@ -90,4 +90,4 @@ private: } // namespace lmms::gui -#endif +#endif // LMMS_GUI_SIDE_BAR_WIDGET_H diff --git a/include/SimpleTextFloat.h b/include/SimpleTextFloat.h new file mode 100644 index 000000000..bde6c84fa --- /dev/null +++ b/include/SimpleTextFloat.h @@ -0,0 +1,67 @@ +/* + * TextFloat.h - class textFloat, a floating text-label + * + * Copyright (c) 2023 LMMS team +* +* 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 SIMPLE_TEXT_FLOAT_H +#define SIMPLE_TEXT_FLOAT_H + +#include + +#include "lmms_export.h" + +class QLabel; +class QTimer; + +namespace lmms::gui +{ + +class LMMS_EXPORT SimpleTextFloat : public QWidget +{ + Q_OBJECT +public: + SimpleTextFloat(); + ~SimpleTextFloat() override = default; + + void setText(const QString & text); + + void showWithDelay(int msecBeforeDisplay, int msecDisplayTime); + + void setVisibilityTimeOut(int msecs); + + void moveGlobal(QWidget * w, const QPoint & offset) + { + move(w->mapToGlobal(QPoint(0, 0)) + offset); + } + + void hide(); + +private: + QLabel * m_textLabel; + QTimer * m_showTimer; + QTimer * m_hideTimer; +}; + +} // namespace lmms::gui + +#endif diff --git a/include/Song.h b/include/Song.h index b6f631f38..9f237a413 100644 --- a/include/Song.h +++ b/include/Song.h @@ -22,19 +22,21 @@ * */ -#ifndef SONG_H -#define SONG_H +#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" #include "Groove.h" @@ -70,15 +72,16 @@ class LMMS_EXPORT Song : public TrackContainer mapPropertyFromModel( int,masterPitch,setMasterPitch,m_masterPitchModel ); mapPropertyFromModel( int,masterVolume,setMasterVolume, m_masterVolumeModel ); public: - enum PlayModes + enum class PlayMode { - Mode_None, - Mode_PlaySong, - Mode_PlayPattern, - Mode_PlayMidiClip, - Mode_PlayAutomationClip, - Mode_Count + None, + Song, + Pattern, + MidiClip, + AutomationClip, + Count } ; + constexpr static auto PlayModeCount = static_cast(PlayMode::Count); struct SaveOptions { /** @@ -106,7 +109,6 @@ public: public: PlayPos( const int abs = 0 ) : TimePos( abs ), - m_timeLine( nullptr ), m_currentFrame( 0.0f ) { } @@ -126,13 +128,11 @@ public: { return m_jumped; } - gui::TimeLineWidget * m_timeLine; private: float m_currentFrame; bool m_jumped; - - } ; + }; void processNextBuffer(); @@ -143,36 +143,34 @@ public: inline int getMilliseconds() const { - return m_elapsedMilliSeconds[m_playMode]; + return getMilliseconds(m_playMode); } - inline int getMilliseconds(PlayModes playMode) const + inline int getMilliseconds(PlayMode playMode) const { - return m_elapsedMilliSeconds[playMode]; + return m_elapsedMilliSeconds[static_cast(playMode)]; } inline void setToTime(TimePos const & pos) { - m_elapsedMilliSeconds[m_playMode] = pos.getTimeInMilliseconds(getTempo()); - m_playPos[m_playMode].setTicks(pos.getTicks()); + setToTime(pos, m_playMode); } - inline void setToTime(TimePos const & pos, PlayModes playMode) + inline void setToTime(TimePos const & pos, PlayMode playMode) { - m_elapsedMilliSeconds[playMode] = pos.getTimeInMilliseconds(getTempo()); - m_playPos[playMode].setTicks(pos.getTicks()); + m_elapsedMilliSeconds[static_cast(playMode)] = pos.getTimeInMilliseconds(getTempo()); + getPlayPos(playMode).setTicks(pos.getTicks()); } inline void setToTimeByTicks(tick_t ticks) { - m_elapsedMilliSeconds[m_playMode] = TimePos::ticksToMilliseconds(ticks, getTempo()); - m_playPos[m_playMode].setTicks(ticks); + setToTimeByTicks(ticks, m_playMode); } - inline void setToTimeByTicks(tick_t ticks, PlayModes playMode) + inline void setToTimeByTicks(tick_t ticks, PlayMode playMode) { - m_elapsedMilliSeconds[playMode] = TimePos::ticksToMilliseconds(ticks, getTempo()); - m_playPos[playMode].setTicks(ticks); + m_elapsedMilliSeconds[static_cast(playMode)] = TimePos::ticksToMilliseconds(ticks, getTempo()); + getPlayPos(playMode).setTicks(ticks); } inline int getBars() const @@ -255,18 +253,18 @@ public: m_renderBetweenMarkers = renderBetweenMarkers; } - inline PlayModes playMode() const + inline PlayMode playMode() const { return m_playMode; } - inline PlayPos & getPlayPos( PlayModes pm ) + inline PlayPos & getPlayPos( PlayMode pm ) { - return m_playPos[pm]; + return m_playPos[static_cast(pm)]; } - inline const PlayPos & getPlayPos( PlayModes pm ) const + inline const PlayPos & getPlayPos( PlayMode pm ) const { - return m_playPos[pm]; + return m_playPos[static_cast(pm)]; } inline PlayPos & getPlayPos() { @@ -277,6 +275,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 { @@ -285,7 +288,6 @@ public: bpm_t getTempo(); - AutomationClip * tempoAutomationClip() override; AutomationTrack * globalAutomationTrack() { @@ -360,6 +362,11 @@ public: return m_timeSigModel; } + IntModel& tempoModel() + { + return m_tempoModel; + } + void exportProjectMidi(QString const & exportFileName) const; inline void setLoadOnLaunch(bool value) { m_loadOnLaunch = value; } @@ -384,7 +391,7 @@ public slots: void playMidiClip( const lmms::MidiClip * midiClipToPlay, bool loop = true ); void togglePause(); void stop(); - void setPlayPos( tick_t ticks, PlayModes playMode ); + void setPlayPos( tick_t ticks, PlayMode playMode ); void startExport(); void stopExport(); @@ -408,7 +415,7 @@ private slots: void masterVolumeChanged(); - void savePos(); + void savePlayStartPosition(); void updateFramesPerTick(); @@ -422,18 +429,18 @@ private: inline bar_t currentBar() const { - return m_playPos[m_playMode].getBar(); + return getPlayPos(m_playMode).getBar(); } inline tick_t currentTick() const { - return m_playPos[m_playMode].getTicks(); + return getPlayPos(m_playMode).getTicks(); } inline f_cnt_t currentFrame() const { - return m_playPos[m_playMode].getTicks() * Engine::framesPerTick() + - m_playPos[m_playMode].currentFrame(); + return getPlayPos(m_playMode).getTicks() * Engine::framesPerTick() + + getPlayPos(m_playMode).currentFrame(); } void saveControllerStates( QDomDocument & doc, QDomElement & element ); @@ -486,14 +493,16 @@ private: QHash m_errors; - PlayModes m_playMode; - PlayPos m_playPos[Mode_Count]; + std::array m_timelines; + + PlayMode m_playMode; + PlayPos m_playPos[PlayModeCount]; bar_t m_length; const MidiClip* m_midiClipToPlay; bool m_loopMidiClip; - double m_elapsedMilliSeconds[Mode_Count]; + double m_elapsedMilliSeconds[PlayModeCount]; tick_t m_elapsedTicks; bar_t m_elapsedBars; @@ -536,4 +545,4 @@ signals: } // namespace lmms -#endif +#endif // LMMS_SONG_H diff --git a/include/SongEditor.h b/include/SongEditor.h index ea57cc125..ee9e83f44 100644 --- a/include/SongEditor.h +++ b/include/SongEditor.h @@ -23,11 +23,10 @@ * */ +#ifndef LMMS_GUI_SONG_EDITOR_H +#define LMMS_GUI_SONG_EDITOR_H -#ifndef SONG_EDITOR_H -#define SONG_EDITOR_H - - +#include "AutomatableModel.h" #include "Editor.h" #include "TrackContainerView.h" @@ -58,11 +57,11 @@ class SongEditor : public TrackContainerView { Q_OBJECT public: - enum EditMode + enum class EditMode { - DrawMode, - KnifeMode, - SelectMode + Draw, + Knife, + Select }; SongEditor( Song * song ); @@ -71,7 +70,6 @@ public: void saveSettings( QDomDocument& doc, QDomElement& element ) override; void loadSettings( const QDomElement& element ) override; - ComboBoxModel *zoomingModel() const; ComboBoxModel *snappingModel() const; float getSnapSize() const; QString getSnapSizeString() const; @@ -122,10 +120,12 @@ private: bool allowRubberband() const override; bool knifeMode() const override; + int calculatePixelsPerBar() const; + int calculateZoomSliderValue(int pixelsPerBar) const; + int trackIndexFromSelectionPoint(int yPos); int indexOfTrackView(const TrackView* tv); - Song * m_song; QScrollBar * m_leftRightScroll; @@ -143,12 +143,10 @@ private: PositionLine * m_positionLine; - ComboBoxModel* m_zoomingModel; + IntModel* m_zoomingModel; ComboBoxModel* m_snappingModel; bool m_proportionalSnap; - static const QVector m_zoomLevels; - bool m_scrollBack; bool m_smoothScroll; @@ -160,14 +158,14 @@ private: QPoint m_mousePos; int m_rubberBandStartTrackview; TimePos m_rubberbandStartTimePos; - int m_currentZoomingValue; + int m_rubberbandPixelsPerBar; //!< pixels per bar when selection starts int m_trackHeadWidth; bool m_selectRegion; friend class SongEditorWindow; signals: - void zoomingValueChanged( float ); + void pixelsPerBarChanged(float); } ; @@ -215,7 +213,7 @@ private: QAction* m_selectModeAction; QAction* m_crtlAction; - ComboBox * m_zoomingComboBox; + AutomatableSlider * m_zoomingSlider; ComboBox * m_snappingComboBox; QLabel* m_snapSizeLabel; @@ -223,9 +221,8 @@ private: QAction* m_removeBarAction; }; - } // namespace gui } // namespace lmms -#endif +#endif // LMMS_GUI_SONG_EDITOR_H diff --git a/include/StepRecorder.h b/include/StepRecorder.h index b89fadb15..55435617c 100644 --- a/include/StepRecorder.h +++ b/include/StepRecorder.h @@ -18,8 +18,8 @@ * */ -#ifndef STEP_RECORDER_H -#define STEP_RECORDER_H +#ifndef LMMS_STEP_RECORDER_H +#define LMMS_STEP_RECORDER_H #include #include @@ -59,7 +59,7 @@ class StepRecorder : public QObject void setCurrentMidiClip(MidiClip* newMidiClip); void setStepsLength(const TimePos& newLength); - QVector getCurStepNotes(); + std::vector getCurStepNotes(); bool isRecording() const { @@ -142,7 +142,7 @@ class StepRecorder : public QObject QElapsedTimer releasedTimer; } ; - QVector m_curStepNotes; // contains the current recorded step notes (i.e. while user still press the notes; before they are applied to the clip) + std::vector m_curStepNotes; // contains the current recorded step notes (i.e. while user still press the notes; before they are applied to the clip) StepNote* findCurStepNote(const int key); @@ -152,4 +152,4 @@ class StepRecorder : public QObject } // namespace lmms -#endif //STEP_RECORDER_H +#endif // LMMS_STEP_RECORDER_H diff --git a/include/StepRecorderWidget.h b/include/StepRecorderWidget.h index 4067b0001..48f1e0fb0 100644 --- a/include/StepRecorderWidget.h +++ b/include/StepRecorderWidget.h @@ -19,9 +19,9 @@ * Boston, MA 02110-1301 USA. * */ -#ifndef STEP_RECOREDER_WIDGET_H -#define STEP_RECOREDER_WIDGET_H +#ifndef LMMS_GUI_STEP_RECOREDER_WIDGET_H +#define LMMS_GUI_STEP_RECOREDER_WIDGET_H #include #include @@ -95,4 +95,4 @@ signals: } // namespace lmms::gui -#endif //STEP_RECOREDER_WIDGET_H +#endif // LMMS_GUI_STEP_RECOREDER_WIDGET_H diff --git a/include/StringPairDrag.h b/include/StringPairDrag.h index e58b88f90..c43996d76 100644 --- a/include/StringPairDrag.h +++ b/include/StringPairDrag.h @@ -23,8 +23,8 @@ * */ -#ifndef STRING_PAIR_DRAG_H -#define STRING_PAIR_DRAG_H +#ifndef LMMS_GUI_STRING_PAIR_DRAG_H +#define LMMS_GUI_STRING_PAIR_DRAG_H #include #include @@ -55,4 +55,4 @@ public: } // namespace lmms::gui -#endif +#endif // LMMS_GUI_STRING_PAIR_DRAG_H diff --git a/include/SubWindow.h b/include/SubWindow.h index 3c73a9dde..d1cc6a7af 100644 --- a/include/SubWindow.h +++ b/include/SubWindow.h @@ -23,8 +23,9 @@ * Boston, MA 02110-1301 USA. * */ -#ifndef SUBWINDOW_H -#define SUBWINDOW_H + +#ifndef LMMS_GUI_SUBWINDOW_H +#define LMMS_GUI_SUBWINDOW_H #include #include @@ -67,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 @@ -104,4 +107,4 @@ private slots: } // namespace lmms::gui -#endif +#endif // LMMS_GUI_SUBWINDOW_H diff --git a/include/SweepOscillator.h b/include/SweepOscillator.h index f113ea0fa..b5521da3b 100644 --- a/include/SweepOscillator.h +++ b/include/SweepOscillator.h @@ -22,8 +22,8 @@ * */ -#ifndef SWEEP_OSCILLATOR_H -#define SWEEP_OSCILLATOR_H +#ifndef LMMS_SWEEP_OSCILLATOR_H +#define LMMS_SWEEP_OSCILLATOR_H #include "Oscillator.h" #include "DspEffectLibrary.h" @@ -69,4 +69,4 @@ private: } // namespace lmms -#endif +#endif // LMMS_SWEEP_OSCILLATOR_H diff --git a/include/TabBar.h b/include/TabBar.h index 2395e15f5..29c100e0c 100644 --- a/include/TabBar.h +++ b/include/TabBar.h @@ -22,9 +22,8 @@ * */ - -#ifndef TAB_BAR_H -#define TAB_BAR_H +#ifndef LMMS_GUI_TAB_BAR_H +#define LMMS_GUI_TAB_BAR_H #include #include @@ -50,7 +49,12 @@ public: TabButton * addTab( QWidget * _w, const QString & _text, int _id, bool _add_stretch = false, - bool _text_is_tooltip = false ); + bool _text_is_tooltip = false, + // TODO Remove fixWidgetToParentSize once it is used + // with false everywhere. + // At the time of writing it is only used in + // LadspaBrowser with default parameters. + bool fixWidgetToParentSize = true ); void removeTab( int _id ); inline void setExclusive( bool _on ) @@ -91,4 +95,4 @@ signals: } // namespace lmms::gui -#endif +#endif // LMMS_GUI_TAB_BAR_H diff --git a/include/TabButton.h b/include/TabButton.h index 938230a91..c12a2037f 100644 --- a/include/TabButton.h +++ b/include/TabButton.h @@ -22,9 +22,8 @@ * */ - -#ifndef TAB_BUTTON_H -#define TAB_BUTTON_H +#ifndef LMMS_GUI_TAB_BUTTON_H +#define LMMS_GUI_TAB_BUTTON_H #include @@ -67,4 +66,4 @@ private: } // namespace lmms::gui -#endif +#endif // LMMS_GUI_TAB_BUTTON_H diff --git a/include/TabWidget.h b/include/TabWidget.h index 27a35d2a8..52e59d577 100644 --- a/include/TabWidget.h +++ b/include/TabWidget.h @@ -22,9 +22,8 @@ * */ - -#ifndef TAB_WIDGET_H -#define TAB_WIDGET_H +#ifndef LMMS_GUI_TAB_WIDGET_H +#define LMMS_GUI_TAB_WIDGET_H #include #include @@ -41,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) } ; @@ -105,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. } ; @@ -112,4 +115,4 @@ private: } // namespace lmms::gui -#endif +#endif // LMMS_GUI_TAB_WIDGET_H diff --git a/include/TemplatesMenu.h b/include/TemplatesMenu.h index 7f425bd2f..6e527c571 100644 --- a/include/TemplatesMenu.h +++ b/include/TemplatesMenu.h @@ -21,8 +21,9 @@ * Boston, MA 02110-1301 USA. * */ -#ifndef TEMPLATESMENU_H -#define TEMPLATESMENU_H + +#ifndef LMMS_GUI_TEMPLATES_MENU_H +#define LMMS_GUI_TEMPLATES_MENU_H #include #include @@ -49,4 +50,4 @@ private slots: } // namespace lmms::gui -#endif // TEMPLATESMENU_H +#endif // LMMS_GUI_TEMPLATES_MENU_H 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/TempoSyncKnob.h b/include/TempoSyncKnob.h index 25a3b7e3b..b86320d13 100644 --- a/include/TempoSyncKnob.h +++ b/include/TempoSyncKnob.h @@ -23,8 +23,8 @@ * */ -#ifndef TEMPO_SYNC_KNOB_H -#define TEMPO_SYNC_KNOB_H +#ifndef LMMS_GUI_TEMPO_SYNC_KNOB_H +#define LMMS_GUI_TEMPO_SYNC_KNOB_H #include #include @@ -41,7 +41,7 @@ class LMMS_EXPORT TempoSyncKnob : public Knob { Q_OBJECT public: - TempoSyncKnob( knobTypes knobNum, QWidget* parent = nullptr, const QString& name = QString() ); + TempoSyncKnob( KnobType knobNum, QWidget* parent = nullptr, const QString& name = QString() ); ~TempoSyncKnob() override; const QString & syncDescription(); @@ -84,4 +84,4 @@ private: } // namespace lmms::gui -#endif +#endif // LMMS_GUI_TEMPO_SYNC_KNOB_H diff --git a/include/TempoSyncKnobModel.h b/include/TempoSyncKnobModel.h index 1a18486ff..af92a58aa 100644 --- a/include/TempoSyncKnobModel.h +++ b/include/TempoSyncKnobModel.h @@ -23,8 +23,8 @@ * */ -#ifndef TEMPO_SYNC_KNOB_MODEL_H -#define TEMPO_SYNC_KNOB_MODEL_H +#ifndef LMMS_TEMPO_SYNC_KNOB_MODEL_H +#define LMMS_TEMPO_SYNC_KNOB_MODEL_H #include "MeterModel.h" @@ -46,17 +46,17 @@ class LMMS_EXPORT TempoSyncKnobModel : public FloatModel Q_OBJECT MODEL_IS_VISITABLE public: - enum TempoSyncMode + enum class SyncMode { - SyncNone, - SyncDoubleWholeNote, - SyncWholeNote, - SyncHalfNote, - SyncQuarterNote, - SyncEighthNote, - SyncSixteenthNote, - SyncThirtysecondNote, - SyncCustom + None, + DoubleWholeNote, + WholeNote, + HalfNote, + QuarterNote, + EighthNote, + SixteenthNote, + ThirtysecondNote, + Custom } ; TempoSyncKnobModel( const float _val, const float _min, @@ -68,12 +68,12 @@ public: void saveSettings( QDomDocument & _doc, QDomElement & _this, const QString& name ) override; void loadSettings( const QDomElement & _this, const QString& name ) override; - TempoSyncMode syncMode() const + SyncMode syncMode() const { return m_tempoSyncMode; } - void setSyncMode( TempoSyncMode _new_mode ); + void setSyncMode( SyncMode _new_mode ); float scale() const { @@ -82,17 +82,20 @@ public: void setScale( float _new_scale ); + MeterModel & getCustomMeterModel() { return m_custom; } + MeterModel const & getCustomMeterModel() const { return m_custom; } + signals: - void syncModeChanged( lmms::TempoSyncKnobModel::TempoSyncMode _new_mode ); + void syncModeChanged( lmms::TempoSyncKnobModel::SyncMode _new_mode ); void scaleChanged( float _new_scale ); public slots: inline void disableSync() { - setTempoSync( SyncNone ); + setTempoSync( SyncMode::None ); } - void setTempoSync( int _note_type ); + void setTempoSync( SyncMode _note_type ); void setTempoSync( QAction * _item ); @@ -102,8 +105,8 @@ protected slots: private: - TempoSyncMode m_tempoSyncMode; - TempoSyncMode m_tempoLastSyncMode; + SyncMode m_tempoSyncMode; + SyncMode m_tempoLastSyncMode; float m_scale; MeterModel m_custom; @@ -115,4 +118,4 @@ private: } // namespace lmms -#endif +#endif // LMMS_TEMPO_SYNC_KNOB_MODEL_H diff --git a/include/TextFloat.h b/include/TextFloat.h index 9fdabc06b..f37544997 100644 --- a/include/TextFloat.h +++ b/include/TextFloat.h @@ -22,15 +22,15 @@ * */ - -#ifndef TEXT_FLOAT_H -#define TEXT_FLOAT_H +#ifndef LMMS_GUI_TEXT_FLOAT_H +#define LMMS_GUI_TEXT_FLOAT_H #include -#include #include "lmms_export.h" +class QLabel; + namespace lmms::gui { @@ -41,45 +41,38 @@ public: TextFloat(); ~TextFloat() override = default; - void setTitle( const QString & _title ); - void setText( const QString & _text ); - void setPixmap( const QPixmap & _pixmap ); + void setTitle(const QString & title); + void setText(const QString & text); + void setPixmap(const QPixmap & pixmap); - void setVisibilityTimeOut( int _msecs ); + void setVisibilityTimeOut(int msecs); + static TextFloat * displayMessage(const QString & title, + const QString & msg, + const QPixmap & pixmap = QPixmap(), + int timeout = 2000, + QWidget * parent = nullptr); - static TextFloat * displayMessage( const QString & _msg, - int _timeout = 2000, - QWidget * _parent = nullptr, - int _add_y_margin = 0 ); - static TextFloat * displayMessage( const QString & _title, - const QString & _msg, - const QPixmap & _pixmap = - QPixmap(), - int _timeout = 2000, - QWidget * _parent = nullptr ); - - void moveGlobal( QWidget * _w, const QPoint & _offset ) + void moveGlobal(QWidget * w, const QPoint & offset) { - move( _w->mapToGlobal( QPoint( 0, 0 ) )+_offset ); + move(w->mapToGlobal(QPoint(0, 0)) + offset); } protected: - void paintEvent( QPaintEvent * _me ) override; - void mousePressEvent( QMouseEvent * _me ) override; + void mousePressEvent(QMouseEvent * me) override; private: - void updateSize(); + TextFloat(const QString & title, const QString & text, const QPixmap & pixmap); - QString m_title; - QString m_text; - QPixmap m_pixmap; + QLabel * m_pixmapLabel; + QLabel * m_titleLabel; + QLabel * m_textLabel; }; } // namespace lmms::gui -#endif +#endif // LMMS_GUI_TEXT_FLOAT_H diff --git a/include/ThreadableJob.h b/include/ThreadableJob.h index 8e714ae19..9d5a0beee 100644 --- a/include/ThreadableJob.h +++ b/include/ThreadableJob.h @@ -22,8 +22,8 @@ * */ -#ifndef THREADABLE_JOB_H -#define THREADABLE_JOB_H +#ifndef LMMS_THREADABLE_JOB_H +#define LMMS_THREADABLE_JOB_H #include "lmms_basics.h" @@ -90,4 +90,4 @@ protected: } // namespace lmms -#endif +#endif // LMMS_THREADABLE_JOB_H diff --git a/include/TimeDisplayWidget.h b/include/TimeDisplayWidget.h index 894666970..287b4ee7e 100644 --- a/include/TimeDisplayWidget.h +++ b/include/TimeDisplayWidget.h @@ -23,8 +23,8 @@ * */ -#ifndef TIME_DISPLAY_WIDGET_H -#define TIME_DISPLAY_WIDGET_H +#ifndef LMMS_GUI_TIME_DISPLAY_WIDGET_H +#define LMMS_GUI_TIME_DISPLAY_WIDGET_H #include #include @@ -51,13 +51,11 @@ private slots: private: - enum DisplayModes + enum class DisplayMode { MinutesSeconds, - BarsTicks, - DisplayModeCount + BarsTicks }; - using DisplayMode = DisplayModes; void setDisplayMode( DisplayMode displayMode ); @@ -71,4 +69,4 @@ private: } // namespace lmms::gui -#endif // TIME_DISPLAY_WIDGET_H +#endif // LMMS_GUI_TIME_DISPLAY_WIDGET_H diff --git a/include/TimeLineWidget.h b/include/TimeLineWidget.h index bada25640..5c683cfd9 100644 --- a/include/TimeLineWidget.h +++ b/include/TimeLineWidget.h @@ -22,18 +22,28 @@ * */ +#ifndef LMMS_GUI_TIMELINE_WIDGET_H +#define LMMS_GUI_TIMELINE_WIDGET_H -#ifndef TIMELINE_H -#define TIMELINE_H +#include +#include +#include #include #include "Song.h" +#include "embed.h" class QPixmap; class QToolBar; +namespace lmms { + +class Timeline; + +} // namespace lmms + namespace lmms::gui { @@ -42,7 +52,7 @@ class TextFloat; class SongEditor; -class TimeLineWidget : public QWidget, public JournallingObject +class TimeLineWidget : public QWidget { Q_OBJECT public: @@ -51,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 AutoScrollStates + enum class AutoScrollState { - AutoScrollEnabled, - AutoScrollDisabled - } ; + Enabled, + Disabled + }; - enum LoopPointStates - { - LoopPointsDisabled, - LoopPointsEnabled - } ; - - enum BehaviourAtStopStates - { - BackToZero, - BackToStart, - KeepStopPosition - } ; - - - TimeLineWidget(int xoff, int yoff, float ppb, Song::PlayPos & pos, - const TimePos & begin, Song::PlayModes 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; } @@ -107,47 +108,38 @@ 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 ); } - AutoScrollStates autoScroll() const + AutoScrollState autoScroll() const { return m_autoScroll; } - BehaviourAtStopStates behaviourAtStop() const - { - return m_behaviourAtStop; - } - - bool loopPointsEnabled() const - { - return m_loopPoints == LoopPointsEnabled; - } - - 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; @@ -158,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 ) * @@ -173,93 +157,83 @@ 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; - - AutoScrollStates m_autoScroll; - LoopPointStates m_loopPoints; - BehaviourAtStopStates 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::PlayModes m_mode; - TimePos m_loopPos[2]; - - TimePos m_savedPos; - - - TextFloat * m_hint; - int m_initalXSelect; - - - enum actions + 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 -#endif +#endif // LMMS_GUI_TIMELINE_WIDGET_H diff --git a/include/TimePos.h b/include/TimePos.h index 808eede25..b86d8eb0f 100644 --- a/include/TimePos.h +++ b/include/TimePos.h @@ -23,10 +23,8 @@ * */ - -#ifndef TIME_POS_H -#define TIME_POS_H - +#ifndef LMMS_TIME_POS_H +#define LMMS_TIME_POS_H #include "lmms_export.h" #include "lmms_basics.h" @@ -118,4 +116,4 @@ private: } // namespace lmms -#endif +#endif // LMMS_TIME_POS_H 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/ToolButton.h b/include/ToolButton.h index c173a9052..6d947f7ad 100644 --- a/include/ToolButton.h +++ b/include/ToolButton.h @@ -22,9 +22,8 @@ * */ - -#ifndef TOOL_BUTTON_H -#define TOOL_BUTTON_H +#ifndef LMMS_GUI_TOOL_BUTTON_H +#define LMMS_GUI_TOOL_BUTTON_H #include @@ -49,5 +48,4 @@ public: } // namespace lmms::gui -#endif - +#endif // LMMS_GUI_TOOL_BUTTON_H diff --git a/include/ToolPlugin.h b/include/ToolPlugin.h index 38f4d87b1..e64afd055 100644 --- a/include/ToolPlugin.h +++ b/include/ToolPlugin.h @@ -24,8 +24,8 @@ * */ -#ifndef TOOL_PLUGIN_H -#define TOOL_PLUGIN_H +#ifndef LMMS_TOOL_PLUGIN_H +#define LMMS_TOOL_PLUGIN_H #include "Plugin.h" @@ -50,4 +50,4 @@ public: } // namespace lmms -#endif +#endif // LMMS_TOOL_PLUGIN_H diff --git a/include/ToolPluginView.h b/include/ToolPluginView.h index 0b702fd1b..b0654f125 100644 --- a/include/ToolPluginView.h +++ b/include/ToolPluginView.h @@ -23,8 +23,8 @@ * */ -#ifndef TOOL_PLUGIN_VIEW_H -#define TOOL_PLUGIN_VIEW_H +#ifndef LMMS_GUI_TOOL_PLUGIN_VIEW_H +#define LMMS_GUI_TOOL_PLUGIN_VIEW_H #include "PluginView.h" @@ -49,4 +49,4 @@ public: } // namespace lmms -#endif +#endif // LMMS_GUI_TOOL_PLUGIN_VIEW_H diff --git a/include/Track.h b/include/Track.h index 8d756c914..b801bb182 100644 --- a/include/Track.h +++ b/include/Track.h @@ -22,16 +22,17 @@ * */ -#ifndef TRACK_H -#define TRACK_H +#ifndef LMMS_TRACK_H +#define LMMS_TRACK_H +#include -#include #include #include "AutomatableModel.h" #include "JournallingObject.h" #include "lmms_basics.h" +#include namespace lmms @@ -66,35 +67,34 @@ 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: - using clipVector = QVector; + using clipVector = std::vector; - enum TrackTypes + enum class Type { - InstrumentTrack, - PatternTrack, - SampleTrack, - EventTrack, - VideoTrack, - AutomationTrack, - HiddenAutomationTrack, - NumTrackTypes + Instrument, + Pattern, + Sample, + Event, + Video, + Automation, + HiddenAutomation, + Count } ; - Track( TrackTypes type, TrackContainer * tc ); + Track( Type type, TrackContainer * tc ); ~Track() override; - static Track * create( TrackTypes tt, TrackContainer * tc ); + static Track * create( Type tt, TrackContainer * tc ); static Track * create( const QDomElement & element, TrackContainer * tc ); Track * clone(); // pure virtual functions - TrackTypes type() const + Type type() const { return m_type; } @@ -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,12 +209,9 @@ public slots: void toggleSolo(); - void setColor(const QColor& c); - void resetColor(); - private: TrackContainer* m_trackContainer; - TrackTypes m_type; + Type m_type; QString m_name; int m_height; @@ -241,8 +228,7 @@ private: QMutex m_processingLock; - QColor m_color; - bool m_hasColor; + std::optional m_color; friend class gui::TrackView; @@ -257,4 +243,4 @@ signals: } // namespace lmms -#endif +#endif // LMMS_TRACK_H diff --git a/include/TrackContainer.h b/include/TrackContainer.h index d947094f7..01e94df54 100644 --- a/include/TrackContainer.h +++ b/include/TrackContainer.h @@ -23,8 +23,8 @@ * */ -#ifndef TRACK_CONTAINER_H -#define TRACK_CONTAINER_H +#ifndef LMMS_TRACK_CONTAINER_H +#define LMMS_TRACK_CONTAINER_H #include @@ -49,11 +49,11 @@ class LMMS_EXPORT TrackContainer : public Model, public JournallingObject { Q_OBJECT public: - using TrackList = QVector; - enum TrackContainerTypes + using TrackList = std::vector; + enum class Type { - PatternContainer, - SongContainer + Pattern, + Song } ; TrackContainer(); @@ -63,13 +63,7 @@ public: void loadSettings( const QDomElement & _this ) override; - - virtual AutomationClip * tempoAutomationClip() - { - return nullptr; - } - - int countTracks( Track::TrackTypes _tt = Track::NumTrackTypes ) const; + int countTracks( Track::Type _tt = Track::Type::Count ) const; void addTrack( Track * _track ); @@ -91,12 +85,12 @@ public: return "trackcontainer"; } - inline void setType( TrackContainerTypes newType ) + inline void setType( Type newType ) { m_TrackContainerType = newType; } - inline TrackContainerTypes type() const + inline Type type() const { return m_TrackContainerType; } @@ -114,7 +108,7 @@ protected: private: TrackList m_tracks; - TrackContainerTypes m_TrackContainerType; + Type m_TrackContainerType; friend class gui::TrackContainerView; @@ -124,4 +118,4 @@ private: } // namespace lmms -#endif +#endif // LMMS_TRACK_CONTAINER_H diff --git a/include/TrackContainerView.h b/include/TrackContainerView.h index 444420d98..82d6f993b 100644 --- a/include/TrackContainerView.h +++ b/include/TrackContainerView.h @@ -22,9 +22,8 @@ * */ - -#ifndef TRACK_CONTAINER_VIEW_H -#define TRACK_CONTAINER_VIEW_H +#ifndef LMMS_GUI_TRACK_CONTAINER_VIEW_H +#define LMMS_GUI_TRACK_CONTAINER_VIEW_H #include #include @@ -175,12 +174,6 @@ protected: private: - enum Actions - { - AddTrack, - RemoveTrack - } ; - class scrollArea : public QScrollArea { public: @@ -219,4 +212,4 @@ signals: } // namespace lmms -#endif +#endif // LMMS_GUI_TRACK_CONTAINER_VIEW_H diff --git a/include/TrackContentWidget.h b/include/TrackContentWidget.h index f4a44d2a6..7cf236323 100644 --- a/include/TrackContentWidget.h +++ b/include/TrackContentWidget.h @@ -22,8 +22,8 @@ * */ -#ifndef TRACK_CONTENT_WIDGET_H -#define TRACK_CONTENT_WIDGET_H +#ifndef LMMS_GUI_TRACK_CONTENT_WIDGET_H +#define LMMS_GUI_TRACK_CONTENT_WIDGET_H #include @@ -95,7 +95,7 @@ public slots: void changePosition( const lmms::TimePos & newPos = TimePos( -1 ) ); protected: - enum ContextMenuAction + enum class ContextMenuAction { Paste }; @@ -149,4 +149,4 @@ private: } // namespace lmms -#endif +#endif // LMMS_GUI_TRACK_CONTENT_WIDGET_H diff --git a/include/TrackLabelButton.h b/include/TrackLabelButton.h index 1a8d05e71..1d3620d12 100644 --- a/include/TrackLabelButton.h +++ b/include/TrackLabelButton.h @@ -22,9 +22,8 @@ * */ - -#ifndef TRACK_LABEL_BUTTON_H -#define TRACK_LABEL_BUTTON_H +#ifndef LMMS_GUI_TRACK_LABEL_BUTTON_H +#define LMMS_GUI_TRACK_LABEL_BUTTON_H #include @@ -56,9 +55,11 @@ 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; @@ -72,4 +73,4 @@ private: } // namespace lmms::gui -#endif +#endif // LMMS_GUI_TRACK_LABEL_BUTTON_H diff --git a/include/TrackOperationsWidget.h b/include/TrackOperationsWidget.h index edd9796ac..4dbb5353c 100644 --- a/include/TrackOperationsWidget.h +++ b/include/TrackOperationsWidget.h @@ -22,8 +22,8 @@ * */ -#ifndef TRACK_OPERATIONS_WIDGET_H -#define TRACK_OPERATIONS_WIDGET_H +#ifndef LMMS_GUI_TRACK_OPERATIONS_WIDGET_H +#define LMMS_GUI_TRACK_OPERATIONS_WIDGET_H #include @@ -80,4 +80,4 @@ signals: } // namespace lmms::gui -#endif +#endif // LMMS_GUI_TRACK_OPERATIONS_WIDGET_H diff --git a/include/TrackRenameLineEdit.h b/include/TrackRenameLineEdit.h index 4b13ff9b7..2f6a1f4fb 100644 --- a/include/TrackRenameLineEdit.h +++ b/include/TrackRenameLineEdit.h @@ -23,9 +23,8 @@ * */ - -#ifndef TRACK_RENAME_LINE_EDIT_H -#define TRACK_RENAME_LINE_EDIT_H +#ifndef LMMS_GUI_TRACK_RENAME_LINE_EDIT_H +#define LMMS_GUI_TRACK_RENAME_LINE_EDIT_H #include @@ -48,4 +47,4 @@ private: } // namespace lmms::gui -#endif +#endif // LMMS_GUI_TRACK_RENAME_LINE_EDIT_H diff --git a/include/TrackView.h b/include/TrackView.h index 4b02c8015..b2654202b 100644 --- a/include/TrackView.h +++ b/include/TrackView.h @@ -22,10 +22,8 @@ * */ - - -#ifndef TRACK_VIEW_H -#define TRACK_VIEW_H +#ifndef LMMS_GUI_TRACK_VIEW_H +#define LMMS_GUI_TRACK_VIEW_H #include @@ -34,7 +32,6 @@ #include "TrackContentWidget.h" #include "TrackOperationsWidget.h" - class QMenu; namespace lmms @@ -51,11 +48,11 @@ class FadeButton; class TrackContainerView; -const int DEFAULT_SETTINGS_WIDGET_WIDTH = 224; +const int DEFAULT_SETTINGS_WIDGET_WIDTH = 256; const int TRACK_OP_WIDTH = 78; // This shaves 150-ish pixels off track buttons, // ruled from config: ui.compacttrackbuttons -const int DEFAULT_SETTINGS_WIDGET_WIDTH_COMPACT = 96; +const int DEFAULT_SETTINGS_WIDGET_WIDTH_COMPACT = 128; const int TRACK_OP_WIDTH_COMPACT = 62; @@ -98,7 +95,7 @@ public: bool isMovingTrack() const { - return m_action == MoveTrack; + return m_action == Action::Move; } virtual void update(); @@ -137,16 +134,19 @@ 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 Actions + enum class Action { - NoAction, - MoveTrack, - ResizeTrack + None, + Move, + Resize } ; Track * m_track; @@ -156,7 +156,7 @@ private: QWidget m_trackSettingsWidget; TrackContentWidget m_trackContentWidget; - Actions m_action; + Action m_action; virtual FadeButton * getActivityIndicator() { @@ -179,4 +179,4 @@ private slots: } // namespace lmms -#endif +#endif // LMMS_GUI_TRACK_VIEW_H diff --git a/include/ValueBuffer.h b/include/ValueBuffer.h index 950ad06f0..33d93cde0 100644 --- a/include/ValueBuffer.h +++ b/include/ValueBuffer.h @@ -23,12 +23,11 @@ * */ -#ifndef VALUE_BUFFER_H -#define VALUE_BUFFER_H +#ifndef LMMS_VALUE_BUFFER_H +#define LMMS_VALUE_BUFFER_H #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); @@ -57,4 +55,4 @@ public: } // namespace lmms -#endif +#endif // LMMS_VALUE_BUFFER_H diff --git a/include/VersionedSaveDialog.h b/include/VersionedSaveDialog.h index 79ab516b0..6d707cd51 100644 --- a/include/VersionedSaveDialog.h +++ b/include/VersionedSaveDialog.h @@ -24,9 +24,8 @@ * */ - -#ifndef VERSIONEDSAVEDIALOG_H -#define VERSIONEDSAVEDIALOG_H +#ifndef LMMS_GUI_VERSIONED_SAVE_DIALOG_H +#define LMMS_GUI_VERSIONED_SAVE_DIALOG_H #include "FileDialog.h" #include "Song.h" @@ -69,4 +68,4 @@ public slots: } // namespace lmms::gui -#endif // VERSIONEDSAVEDIALOG_H +#endif // LMMS_GUI_VERSIONED_SAVE_DIALOG_H diff --git a/include/VstSyncController.h b/include/VstSyncController.h index c70bc85c8..d3c6af051 100644 --- a/include/VstSyncController.h +++ b/include/VstSyncController.h @@ -23,8 +23,8 @@ * */ -#ifndef VST_SYNC_CONTROLLER_H -#define VST_SYNC_CONTROLLER_H +#ifndef LMMS_VST_SYNC_CONTROLLER_H +#define LMMS_VST_SYNC_CONTROLLER_H #include @@ -62,4 +62,4 @@ private: } // namespace lmms -#endif +#endif // LMMS_VST_SYNC_CONTROLLER_H diff --git a/include/VstSyncData.h b/include/VstSyncData.h index baf3fa7a8..480bc7fb4 100644 --- a/include/VstSyncData.h +++ b/include/VstSyncData.h @@ -23,8 +23,8 @@ * */ -#ifndef VST_SYNC_DATA_H -#define VST_SYNC_DATA_H +#ifndef LMMS_VST_SYNC_DATA_H +#define LMMS_VST_SYNC_DATA_H namespace lmms { @@ -61,4 +61,4 @@ struct VstSyncData } // namespace lmms -#endif +#endif // LMMS_VST_SYNC_DATA_H diff --git a/include/aeffectx.h b/include/aeffectx.h index a06567ba7..2c69464cc 100644 --- a/include/aeffectx.h +++ b/include/aeffectx.h @@ -22,7 +22,6 @@ * */ - #ifndef AEFFECTX_H #define AEFFECTX_H @@ -298,4 +297,4 @@ public: using audioMasterCallback = intptr_t (VST_CALL_CONV*)(AEffect*, int32_t, int32_t, intptr_t, void*, float); -#endif +#endif // AEFFECTX_H diff --git a/include/base64.h b/include/base64.h index f52d90d13..42066d564 100644 --- a/include/base64.h +++ b/include/base64.h @@ -23,8 +23,8 @@ * */ -#ifndef _BASE64_H -#define _BASE64_H +#ifndef LMMS_BASE64_H +#define LMMS_BASE64_H #include #include @@ -53,4 +53,4 @@ namespace lmms::base64 } // namespace lmms::base64 -#endif +#endif // LMMS_BASE64_H diff --git a/include/debug.h b/include/debug.h index f2ea29cc1..7cf51acaa 100644 --- a/include/debug.h +++ b/include/debug.h @@ -22,9 +22,8 @@ * */ - -#ifndef DEBUG_H -#define DEBUG_H +#ifndef LMMS_DEBUG_H +#define LMMS_DEBUG_H #include "lmmsconfig.h" @@ -39,4 +38,4 @@ #include -#endif +#endif // LMMS_DEBUG_H diff --git a/include/denormals.h b/include/denormals.h index e99e767a3..92b84c7dd 100644 --- a/include/denormals.h +++ b/include/denormals.h @@ -1,8 +1,8 @@ -// Denormals stripping. +// Denormals stripping. // These snippets should be common enough to be considered public domain. -#ifndef DENORMALS_H -#define DENORMALS_H +#ifndef LMMS_DENORMALS_H +#define LMMS_DENORMALS_H #ifdef __SSE__ #include @@ -35,7 +35,7 @@ int inline can_we_daz() #endif // __SSE__ -// Set denormal protection for this thread. +// Set denormal protection for this thread. void inline disable_denormals() { #ifdef __SSE__ @@ -50,5 +50,4 @@ void inline disable_denormals() } // namespace lmms -#endif // DENORMALS_H - +#endif // LMMS_DENORMALS_H diff --git a/include/embed.h b/include/embed.h index 03d767b43..7d69f7c7d 100644 --- a/include/embed.h +++ b/include/embed.h @@ -22,8 +22,8 @@ * */ -#ifndef EMBED_H -#define EMBED_H +#ifndef LMMS_EMBED_H +#define LMMS_EMBED_H #include #include @@ -139,4 +139,4 @@ public: } // namespace lmms -#endif +#endif // LMMS_EMBED_H diff --git a/include/endian_handling.h b/include/endian_handling.h index b3d122fa3..7ddb22f22 100644 --- a/include/endian_handling.h +++ b/include/endian_handling.h @@ -22,8 +22,8 @@ * */ -#ifndef ENDIAN_HANDLING_H -#define ENDIAN_HANDLING_H +#ifndef LMMS_ENDIAN_HANDLING_H +#define LMMS_ENDIAN_HANDLING_H #include @@ -57,4 +57,4 @@ inline int32_t swap32IfBE( int32_t i ) } // namespace lmms -#endif +#endif // LMMS_ENDIAN_HANDLING_H diff --git a/include/fenv.h b/include/fenv.h index c9cc508f5..665d88155 100644 --- a/include/fenv.h +++ b/include/fenv.h @@ -45,4 +45,4 @@ inline int fedisableexcept(unsigned int excepts) return fesetenv(&fenv) ? -1 : old_excepts; } -#endif +#endif // defined(__APPLE__) && defined(__MACH__) diff --git a/include/fft_helpers.h b/include/fft_helpers.h index 8ba0fc7a0..cd4e5f88d 100644 --- a/include/fft_helpers.h +++ b/include/fft_helpers.h @@ -23,9 +23,8 @@ * */ - -#ifndef FFT_HELPERS_H -#define FFT_HELPERS_H +#ifndef LMMS_FFT_HELPERS_H +#define LMMS_FFT_HELPERS_H #include "lmms_export.h" @@ -45,12 +44,12 @@ const unsigned int FFT_BUFFER_SIZE = 2048; const std::vector FFT_BLOCK_SIZES = {256, 512, 1024, 2048, 4096, 8192, 16384}; // List of FFT window functions supported by precomputeWindow() -enum FFT_WINDOWS +enum class FFTWindow { - RECTANGULAR = 0, - BLACKMAN_HARRIS, - HAMMING, - HANNING + Rectangular = 0, + BlackmanHarris, + Hamming, + Hanning }; @@ -84,7 +83,7 @@ int LMMS_EXPORT notEmpty(const std::vector &spectrum); * * @return -1 on error */ -int LMMS_EXPORT precomputeWindow(float *window, unsigned int length, FFT_WINDOWS type, bool normalized = true); +int LMMS_EXPORT precomputeWindow(float *window, unsigned int length, FFTWindow type, bool normalized = true); /** Compute absolute values of complex_buffer, save to absspec_buffer. @@ -109,4 +108,4 @@ int LMMS_EXPORT compressbands(const float * _absspec_buffer, float * _compressed } // namespace lmms -#endif +#endif // LMMS_FFT_HELPERS_H diff --git a/include/gui_templates.h b/include/gui_templates.h index ee0ccdbf6..bbb5f80da 100644 --- a/include/gui_templates.h +++ b/include/gui_templates.h @@ -22,51 +22,22 @@ * */ - -#ifndef GUI_TEMPLATES_H -#define GUI_TEMPLATES_H - -#include "lmmsconfig.h" +#ifndef LMMS_GUI_TEMPLATES_H +#define LMMS_GUI_TEMPLATES_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; } +} // namespace lmms::gui -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 - -#endif +#endif // LMMS_GUI_TEMPLATES_H diff --git a/include/interpolation.h b/include/interpolation.h index 3a74334e4..fd2c29a04 100644 --- a/include/interpolation.h +++ b/include/interpolation.h @@ -22,9 +22,8 @@ * */ - -#ifndef INTERPOLATION_H -#define INTERPOLATION_H +#ifndef LMMS_INTERPOLATION_H +#define LMMS_INTERPOLATION_H #ifndef __USE_XOPEN #define __USE_XOPEN @@ -140,4 +139,4 @@ inline float lagrangeInterpolate( float v0, float v1, float v2, float v3, float } // namespace lmms -#endif +#endif // LMMS_INTERPOLATION_H diff --git a/include/lmms_basics.h b/include/lmms_basics.h index 59a6aa359..b566fa781 100644 --- a/include/lmms_basics.h +++ b/include/lmms_basics.h @@ -22,9 +22,8 @@ * */ - -#ifndef TYPES_H -#define TYPES_H +#ifndef LMMS_TYPES_H +#define LMMS_TYPES_H #include #include @@ -146,4 +145,4 @@ constexpr const char* UI_CTRL_KEY = } // namespace lmms -#endif +#endif // LMMS_TYPES_H diff --git a/include/lmms_constants.h b/include/lmms_constants.h index 7796f6bdf..c6452d6c6 100644 --- a/include/lmms_constants.h +++ b/include/lmms_constants.h @@ -62,13 +62,13 @@ constexpr unsigned int MaxKeymapCount = 10; //!< number of keyboard mappings per constexpr int LOWEST_LOG_FREQ = 5; // Full range is defined by LOWEST_LOG_FREQ and current sample rate. -enum FREQUENCY_RANGES +enum class FrequencyRange { - FRANGE_FULL = 0, - FRANGE_AUDIBLE, - FRANGE_BASS, - FRANGE_MIDS, - FRANGE_HIGH + Full = 0, + Audible, + Bass, + Mids, + High }; constexpr int FRANGE_AUDIBLE_START = 20; @@ -83,12 +83,12 @@ constexpr int FRANGE_HIGH_END = 20000; // Amplitude ranges (in dBFS). // Reference: full scale sine wave (-1.0 to 1.0) is 0 dB. // Doubling or halving the amplitude produces 3 dB difference. -enum AMPLITUDE_RANGES +enum class AmplitudeRange { - ARANGE_EXTENDED = 0, - ARANGE_AUDIBLE, - ARANGE_LOUD, - ARANGE_SILENT + Extended = 0, + Audible, + Loud, + Silent }; constexpr int ARANGE_EXTENDED_START = -80; @@ -103,4 +103,4 @@ constexpr int ARANGE_SILENT_END = -10; } // namespace lmms -#endif +#endif // LMMS_CONSTANTS_H diff --git a/include/lmms_math.h b/include/lmms_math.h index db4502f1c..bf5e53a2b 100644 --- a/include/lmms_math.h +++ b/include/lmms_math.h @@ -22,16 +22,17 @@ * */ - #ifndef LMMS_MATH_H #define LMMS_MATH_H +#include +#include +#include #include + #include "lmms_constants.h" #include "lmmsconfig.h" -#include - -#include +#include namespace lmms { @@ -215,7 +216,7 @@ static inline float logToLinearScale( float min, float max, float value ) { if( min < 0 ) { - const float mmax = qMax( qAbs( min ), qAbs( max ) ); + const float mmax = std::max(std::abs(min), std::abs(max)); const float val = value * ( max - min ) + min; float result = signedPowf( val / mmax, F_E ) * mmax; return std::isnan( result ) ? 0 : result; @@ -229,11 +230,11 @@ static inline float logToLinearScale( float min, float max, float value ) static inline float linearToLogScale( float min, float max, float value ) { static const float EXP = 1.0f / F_E; - const float valueLimited = qBound( min, value, max); + const float valueLimited = std::clamp(value, min, max); const float val = ( valueLimited - min ) / ( max - min ); if( min < 0 ) { - const float mmax = qMax( qAbs( min ), qAbs( max ) ); + const float mmax = std::max(std::abs(min), std::abs(max)); float result = signedPowf( valueLimited / mmax, EXP ) * mmax; return std::isnan( result ) ? 0 : result; } @@ -269,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); } @@ -316,17 +317,72 @@ static inline float fastSqrt( float n ) template static inline T absMax( T a, T b ) { - return qAbs(a) > qAbs(b) ? a : b; + return std::abs(a) > std::abs(b) ? a : b; } //! returns value nearest to zero template static inline T absMin( T a, T b ) { - return qAbs(a) < qAbs(b) ? a : 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) +{ + // use rounding: + // LcdSpinBox sometimes uses roundf(), sometimes cast rounding + // we use rounding to be on the "safe side" + const float rounded = roundf(f); + int asInt = static_cast(rounded); + int digits = 1; // always at least 1 + if(asInt < 0) + { + ++digits; + asInt = -asInt; + } + // "asInt" is positive from now + int32_t power = 1; + for(int32_t i = 1; i<10; ++i) + { + power *= 10; + if(static_cast(asInt) >= power) { ++digits; } // 2 digits for >=10, 3 for >=100 + else { break; } + } + return digits; +} + +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 -#endif +#endif // LMMS_MATH_H diff --git a/include/panning.h b/include/panning.h index 1521ec984..0fd74d1cc 100644 --- a/include/panning.h +++ b/include/panning.h @@ -23,14 +23,16 @@ * */ -#ifndef PANNING_H -#define PANNING_H +#ifndef LMMS_PANNING_H +#define LMMS_PANNING_H #include "lmms_basics.h" #include "panning_constants.h" #include "Midi.h" #include "volume.h" +#include + namespace lmms { @@ -40,7 +42,7 @@ inline StereoVolumeVector panningToVolumeVector( panning_t _p, { StereoVolumeVector v = { { _scale, _scale } }; const float pf = _p / 100.0f; - v.vol[_p >= PanningCenter ? 0 : 1] *= 1.0f - qAbs( pf ); + v.vol[_p >= PanningCenter ? 0 : 1] *= 1.0f - std::abs(pf); return v; } @@ -56,4 +58,4 @@ inline int panningToMidi( panning_t _p ) } // namespace lmms -#endif +#endif // LMMS_PANNING_H diff --git a/include/panning_constants.h b/include/panning_constants.h index 00a5c3291..8f40219f8 100644 --- a/include/panning_constants.h +++ b/include/panning_constants.h @@ -23,8 +23,8 @@ * */ -#ifndef PANNING_CONSTANTS_H -#define PANNING_CONSTANTS_H +#ifndef LMMS_PANNING_CONSTANTS_H +#define LMMS_PANNING_CONSTANTS_H namespace lmms { @@ -38,4 +38,4 @@ constexpr panning_t DefaultPanning = PanningCenter; } // namespace lmms -#endif +#endif // LMMS_PANNING_CONSTANTS_H diff --git a/include/shared_object.h b/include/shared_object.h index ac45d6cd7..e9fdb57a3 100644 --- a/include/shared_object.h +++ b/include/shared_object.h @@ -23,8 +23,8 @@ * */ -#ifndef SHARED_OBJECT_H -#define SHARED_OBJECT_H +#ifndef LMMS_SHARED_OBJECT_H +#define LMMS_SHARED_OBJECT_H #include @@ -86,4 +86,4 @@ private: } // namespace lmms -#endif +#endif // LMMS_SHARED_OBJECT_H diff --git a/include/versioninfo.h b/include/versioninfo.h index 29bf3bea9..7495299c2 100644 --- a/include/versioninfo.h +++ b/include/versioninfo.h @@ -1,17 +1,19 @@ -#ifndef VERSION_INFO_H -#define VERSION_INFO_H +#ifndef LMMS_VERSION_INFO_H +#define LMMS_VERSION_INFO_H #include "lmms_basics.h" -#ifdef __GNUC__ +#if defined(__GNUC__) constexpr const char* LMMS_BUILDCONF_COMPILER_VERSION = "GCC " __VERSION__; +#elif defined(__clang__) +constexpr const char* LMMS_BUILDCONF_COMPILER_VERSION = "Clang " __clang_version__; #elif defined(_MSC_VER) constexpr const char* LMMS_BUILDCONF_COMPILER_VERSION = "MSVC " LMMS_STRINGIFY(_MSC_FULL_VER); #else constexpr const char* LMMS_BUILDCONF_COMPILER_VERSION = "unknown compiler"; #endif -#ifdef LMMS_HOST_X86 +#if defined(LMMS_HOST_X86) constexpr const char* LMMS_BUILDCONF_MACHINE = "i386"; #elif defined(LMMS_HOST_X86_64) constexpr const char* LMMS_BUILDCONF_MACHINE = "x86_64"; @@ -23,32 +25,28 @@ constexpr const char* LMMS_BUILDCONF_MACHINE = "arm64"; constexpr const char* LMMS_BUILDCONF_MACHINE = "riscv32"; #elif defined(LMMS_HOST_RISCV64) constexpr const char* LMMS_BUILDCONF_MACHINE = "riscv64"; +#elif defined(LMMS_HOST_PPC32) +constexpr const char* LMMS_BUILDCONF_MACHINE = "ppc"; +#elif defined(LMMS_HOST_PPC64) +constexpr const char* LMMS_BUILDCONF_MACHINE = "ppc64"; #else constexpr const char* LMMS_BUILDCONF_MACHINE = "unknown processor"; #endif -#ifdef LMMS_BUILD_LINUX +#if defined(LMMS_BUILD_LINUX) constexpr const char* LMMS_BUILDCONF_PLATFORM = "Linux"; -#endif - -#ifdef LMMS_BUILD_APPLE +#elif defined(LMMS_BUILD_APPLE) constexpr const char* LMMS_BUILDCONF_PLATFORM = "OS X"; -#endif - -#ifdef LMMS_BUILD_OPENBSD +#elif defined(LMMS_BUILD_OPENBSD) constexpr const char* LMMS_BUILDCONF_PLATFORM = "OpenBSD"; -#endif - -#ifdef LMMS_BUILD_FREEBSD +#elif defined(LMMS_BUILD_FREEBSD) constexpr const char* LMMS_BUILDCONF_PLATFORM = "FreeBSD"; -#endif - -#ifdef LMMS_BUILD_WIN32 +#elif defined(LMMS_BUILD_WIN32) constexpr const char* LMMS_BUILDCONF_PLATFORM = "win32"; -#endif - -#ifdef LMMS_BUILD_HAIKU +#elif defined(LMMS_BUILD_HAIKU) constexpr const char* LMMS_BUILDCONF_PLATFORM = "Haiku"; +#else +constexpr const char* LMMS_BUILDCONF_PLATFORM = "unknown platform"; #endif -#endif +#endif // LMMS_VERSION_INFO_H diff --git a/include/volume.h b/include/volume.h index 5223bfa09..382f76780 100644 --- a/include/volume.h +++ b/include/volume.h @@ -23,8 +23,8 @@ * */ -#ifndef VOLUME_H -#define VOLUME_H +#ifndef LMMS_VOLUME_H +#define LMMS_VOLUME_H #include "lmms_basics.h" @@ -43,4 +43,4 @@ struct StereoVolumeVector } // namespace lmms -#endif +#endif // LMMS_VOLUME_H diff --git a/plugins/Amplifier/Amplifier.cpp b/plugins/Amplifier/Amplifier.cpp index 9344807c4..ac5fdf23b 100644 --- a/plugins/Amplifier/Amplifier.cpp +++ b/plugins/Amplifier/Amplifier.cpp @@ -36,12 +36,12 @@ 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::Effect, + Plugin::Type::Effect, new PluginPixmapLoader("logo"), nullptr, nullptr, @@ -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 226130fcd..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(knobBright_26, 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(knobBright_26, this); - panKnob -> move( 57, 10 ); - panKnob->setModel( &controls->m_panModel ); - panKnob->setLabel( tr( "PAN" ) ); - panKnob->setHintText( tr( "Panning:" ) , "" ); - - auto leftKnob = new Knob(knobBright_26, 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(knobBright_26, 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 2e2d7163b..7cc5ee3fa 100644 --- a/plugins/AudioFileProcessor/AudioFileProcessor.cpp +++ b/plugins/AudioFileProcessor/AudioFileProcessor.cpp @@ -1,5 +1,5 @@ /* - * AudioFileProcessor.cpp - instrument for using audio-files + * AudioFileProcessor.cpp - instrument for using audio files * * Copyright (c) 2004-2014 Tobias Doerffel * @@ -23,35 +23,21 @@ */ #include "AudioFileProcessor.h" +#include "AudioFileProcessorView.h" -#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" { @@ -65,9 +51,13 @@ Plugin::Descriptor PLUGIN_EXPORT audiofileprocessor_plugin_descriptor = "instrument-track" ), "Tobias Doerffel ", 0x0100, - Plugin::Instrument, + 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, } ; @@ -78,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" ) ), @@ -126,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 @@ -154,28 +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 ); - instrumentTrack()->processAudioBuffer( _working_buffer, - frames + offset, _n ); - - emit isPlaying( ((handleState *)_n->m_pluginData)->frameIndex() ); + emit isPlaying(static_cast(_n->m_pluginData)->frameIndex()); } else { @@ -189,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(); } } @@ -199,83 +185,76 @@ void AudioFileProcessor::playNote( NotePlayHandle * _n, void AudioFileProcessor::deleteNotePluginData( NotePlayHandle * _n ) { - delete (handleState *)_n->m_pluginData; + delete static_cast(_n->m_pluginData); } -void AudioFileProcessor::saveSettings( QDomDocument & _doc, - QDomElement & _this ) +void AudioFileProcessor::saveSettings(QDomDocument& doc, QDomElement& elem) { - _this.setAttribute( "src", m_sampleBuffer.audioFile() ); - if( m_sampleBuffer.audioFile() == "" ) + elem.setAttribute("src", m_sample.sampleFile()); + if (m_sample.sampleFile().isEmpty()) { - QString s; - _this.setAttribute( "sampledata", - m_sampleBuffer.toBase64( s ) ); + elem.setAttribute("sampledata", m_sample.toBase64()); } - m_reverseModel.saveSettings( _doc, _this, "reversed" ); - m_loopModel.saveSettings( _doc, _this, "looped" ); - m_ampModel.saveSettings( _doc, _this, "amp" ); - m_startPointModel.saveSettings( _doc, _this, "sframe" ); - m_endPointModel.saveSettings( _doc, _this, "eframe" ); - m_loopPointModel.saveSettings( _doc, _this, "lframe" ); - m_stutterModel.saveSettings( _doc, _this, "stutter" ); - m_interpolationModel.saveSettings( _doc, _this, "interp" ); - + m_reverseModel.saveSettings(doc, elem, "reversed"); + m_loopModel.saveSettings(doc, elem, "looped"); + m_ampModel.saveSettings(doc, elem, "amp"); + m_startPointModel.saveSettings(doc, elem, "sframe"); + m_endPointModel.saveSettings(doc, elem, "eframe"); + m_loopPointModel.saveSettings(doc, elem, "lframe"); + m_stutterModel.saveSettings(doc, elem, "stutter"); + m_interpolationModel.saveSettings(doc, elem, "interp"); } -void AudioFileProcessor::loadSettings( const QDomElement & _this ) +void AudioFileProcessor::loadSettings(const QDomElement& elem) { - if( _this.attribute( "src" ) != "" ) + if (auto srcFile = elem.attribute("src"); !srcFile.isEmpty()) { - setAudioFile( _this.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( _this.attribute( "sampledata" ) != "" ) + else if (auto sampleData = elem.attribute("sampledata"); !sampleData.isEmpty()) { - m_sampleBuffer.loadFromBase64( _this.attribute( "srcdata" ) ); + m_sample = Sample(gui::SampleLoader::createBufferFromBase64(sampleData)); } - m_loopModel.loadSettings( _this, "looped" ); - m_ampModel.loadSettings( _this, "amp" ); - m_endPointModel.loadSettings( _this, "eframe" ); - m_startPointModel.loadSettings( _this, "sframe" ); + m_loopModel.loadSettings(elem, "looped"); + m_ampModel.loadSettings(elem, "amp"); + m_endPointModel.loadSettings(elem, "eframe"); + m_startPointModel.loadSettings(elem, "sframe"); // compat code for not having a separate loopback point - if (_this.hasAttribute("lframe") || !(_this.firstChildElement("lframe").isNull())) + if (elem.hasAttribute("lframe") || !elem.firstChildElement("lframe").isNull()) { - m_loopPointModel.loadSettings( _this, "lframe" ); + m_loopPointModel.loadSettings(elem, "lframe"); } else { - m_loopPointModel.loadSettings( _this, "sframe" ); + m_loopPointModel.loadSettings(elem, "sframe"); } - m_reverseModel.loadSettings( _this, "reversed" ); + m_reverseModel.loadSettings(elem, "reversed"); - m_stutterModel.loadSettings( _this, "stutter" ); - if( _this.hasAttribute( "interp" ) ) + m_stutterModel.loadSettings(elem, "stutter"); + if (elem.hasAttribute("interp") || !elem.firstChildElement("interp").isNull()) { - m_interpolationModel.loadSettings( _this, "interp" ); + m_interpolationModel.loadSettings(elem, "interp"); } else { - m_interpolationModel.setValue( 1 ); //linear by default + m_interpolationModel.setValue(1.0f); // linear by default } pointChanged(); + emit sampleUpdated(); } @@ -297,43 +276,49 @@ QString AudioFileProcessor::nodeName() const -int AudioFileProcessor::getBeatLen( NotePlayHandle * _n ) const +auto AudioFileProcessor::beatLen(NotePlayHandle* note) const -> int { + // If we can play indefinitely, use the default beat note duration + if (static_cast(m_loopModel.value()) != Sample::Loop::Off) { return 0; } + + // Otherwise, use the remaining sample duration const auto baseFreq = instrumentTrack()->baseFreq(); - const float freq_factor = baseFreq / _n->frequency() * - Engine::audioEngine()->processingSampleRate() / Engine::audioEngine()->baseSampleRate(); + const auto freqFactor = baseFreq / note->frequency() + * Engine::audioEngine()->processingSampleRate() + / Engine::audioEngine()->baseSampleRate(); - return static_cast( floorf( ( m_sampleBuffer.endFrame() - m_sampleBuffer.startFrame() ) * freq_factor ) ); + const auto startFrame = m_nextPlayStartPoint >= m_sample.endFrame() + ? m_sample.startFrame() + : m_nextPlayStartPoint; + const auto duration = m_sample.endFrame() - startFrame; + + return static_cast(std::floor(duration * freqFactor)); } - 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(); } @@ -341,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(); } @@ -351,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; } @@ -426,854 +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( knobBright_26, 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( - Track::SampleTrack ) ) - { - _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( Track::SampleTrack ) ) - { - 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 != "" ) - { - 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 = sample_loop; int md = loop_dist; - if( start_dist < loop_dist ) { dt = sample_start; md = start_dist; } - else if( end_dist < loop_dist ) { dt = sample_end; md = end_dist; } - - if( md < 4 ) - { - m_draggingType = dt; - } - else - { - m_draggingType = wave; - updateCursor(_me); - } -} - - - - -void AudioFileProcessorWaveView::mouseReleaseEvent( QMouseEvent * _me ) -{ - m_isDragging = false; - if( m_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 sample_start: - slideSamplePointByPx( start, step ); - break; - case sample_end: - slideSamplePointByPx( end, step ); - break; - case sample_loop: - slideSamplePointByPx( loop, step ); - break; - case 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( 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( knobType _point, int _px ) -{ - slideSamplePointByFrames( - _point, - f_cnt_t( ( double( _px ) / width() ) * ( m_to - m_from ) ) - ); -} - - - - -void AudioFileProcessorWaveView::slideSamplePointByFrames( knobType _point, f_cnt_t _frames, bool _slide_to ) -{ - knob * a_knob = m_startKnob; - switch( _point ) - { - case end: - a_knob = m_endKnob; - break; - case loop: - a_knob = m_loopKnob; - break; - case 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 == 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 b80954577..7ade1ec4f 100644 --- a/plugins/AudioFileProcessor/AudioFileProcessor.h +++ b/plugins/AudioFileProcessor/AudioFileProcessor.h @@ -23,34 +23,20 @@ * */ +#ifndef LMMS_AUDIO_FILE_PROCESSOR_H +#define LMMS_AUDIO_FILE_PROCESSOR_H -#ifndef AUDIO_FILE_PROCESSOR_H -#define 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 @@ -61,15 +47,14 @@ public: sampleFrame * _working_buffer ) override; void deleteNotePluginData( NotePlayHandle * _n ) override; - void saveSettings( QDomDocument & _doc, - QDomElement & _parent ) override; - void loadSettings( const QDomElement & _this ) override; + void saveSettings(QDomDocument& doc, QDomElement& elem) override; + void loadSettings(const QDomElement& elem) override; void loadFile( const QString & _file ) override; QString nodeName() const override; - virtual int getBeatLen( NotePlayHandle * _n ) const; + auto beatLen(NotePlayHandle* note) const -> int override; f_cnt_t desiredReleaseFrames() const override { @@ -78,10 +63,20 @@ public: 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(); @@ -95,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; @@ -113,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 knobType - { - start, - end, - loop - } ; - - class knob : public Knob - { - const AudioFileProcessorWaveView * m_waveView; - const Knob * m_relatedKnob; - - - public: - knob( QWidget * _parent ) : - Knob( knobBright_26, _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 draggingType - { - wave, - sample_start, - sample_end, - sample_loop - } ; - - 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( knobType _point, int _px ); - void slideSamplePointByFrames( knobType _point, f_cnt_t _frames, bool _slide_to = false ); - void slideSampleByFrames( f_cnt_t _frames ); - - void slideSamplePointToFrames( knobType _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 +#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 48e265911..e6b25b0d1 100644 --- a/plugins/BassBooster/BassBooster.cpp +++ b/plugins/BassBooster/BassBooster.cpp @@ -41,7 +41,7 @@ Plugin::Descriptor PLUGIN_EXPORT bassbooster_plugin_descriptor = QT_TRANSLATE_NOOP( "PluginBrowser", "Boost your bass the fast and simple way" ), "Tobias Doerffel ", 0x0100, - Plugin::Effect, + Plugin::Type::Effect, new PluginPixmapLoader("logo"), nullptr, nullptr, diff --git a/plugins/BassBooster/BassBoosterControlDialog.cpp b/plugins/BassBooster/BassBoosterControlDialog.cpp index 21aacb0f2..9efa07c0d 100644 --- a/plugins/BassBooster/BassBoosterControlDialog.cpp +++ b/plugins/BassBooster/BassBoosterControlDialog.cpp @@ -50,17 +50,17 @@ BassBoosterControlDialog::BassBoosterControlDialog( BassBoosterControls* control auto l = new QHBoxLayout; - auto freqKnob = new Knob(knobBright_26, this); + auto freqKnob = new Knob(KnobType::Bright26, this); freqKnob->setModel( &controls->m_freqModel ); freqKnob->setLabel( tr( "FREQ" ) ); freqKnob->setHintText( tr( "Frequency:" ) , "Hz" ); - auto gainKnob = new Knob(knobBright_26, this); + auto gainKnob = new Knob(KnobType::Bright26, this); gainKnob->setModel( &controls->m_gainModel ); gainKnob->setLabel( tr( "GAIN" ) ); gainKnob->setHintText( tr( "Gain:" ) , "" ); - auto ratioKnob = new Knob(knobBright_26, this); + auto ratioKnob = new Knob(KnobType::Bright26, this); ratioKnob->setModel( &controls->m_ratioModel ); ratioKnob->setLabel( tr( "RATIO" ) ); ratioKnob->setHintText( tr( "Ratio:" ) , "" ); diff --git a/plugins/BitInvader/BitInvader.cpp b/plugins/BitInvader/BitInvader.cpp index 947c6f5d6..4743e262d 100644 --- a/plugins/BitInvader/BitInvader.cpp +++ b/plugins/BitInvader/BitInvader.cpp @@ -60,7 +60,7 @@ Plugin::Descriptor PLUGIN_EXPORT bitinvader_plugin_descriptor = "Customizable wavetable synthesizer" ), "Andreas Brandmaier ", 0x0100, - Plugin::Instrument, + Plugin::Type::Instrument, new PluginPixmapLoader( "logo" ), nullptr, nullptr, @@ -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(); @@ -274,19 +258,9 @@ QString BitInvader::nodeName() const void BitInvader::playNote( NotePlayHandle * _n, sampleFrame * _working_buffer ) { - if ( _n->totalFramesPlayed() == 0 || _n->m_pluginData == nullptr ) + 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, @@ -308,8 +282,6 @@ void BitInvader::playNote( NotePlayHandle * _n, } applyRelease( _working_buffer, _n ); - - instrumentTrack()->processAudioBuffer( _working_buffer, frames + offset, _n ); } @@ -346,11 +318,11 @@ BitInvaderView::BitInvaderView( Instrument * _instrument, "artwork" ) ); setPalette( pal ); - m_sampleLengthKnob = new Knob( knobDark_28, this ); + m_sampleLengthKnob = new Knob( KnobType::Dark28, this ); m_sampleLengthKnob->move( 6, 201 ); m_sampleLengthKnob->setHintText( tr( "Sample length" ), "" ); - m_graph = new Graph( this, Graph::NearestStyle, 204, 134 ); + m_graph = new Graph( this, Graph::Style::Nearest, 204, 134 ); m_graph->move(23,59); // 55,120 - 2px border m_graph->setAutoFillBackground( true ); m_graph->setGraphColor( QColor( 255, 255, 255 ) ); @@ -432,12 +404,12 @@ BitInvaderView::BitInvaderView( Instrument * _instrument, m_interpolationToggle = new LedCheckBox( "Interpolation", this, - tr( "Interpolation" ), LedCheckBox::Yellow ); + tr( "Interpolation" ), LedCheckBox::LedColor::Yellow ); m_interpolationToggle->move( 131, 221 ); m_normalizeToggle = new LedCheckBox( "Normalize", this, - tr( "Normalize" ), LedCheckBox::Green ); + tr( "Normalize" ), LedCheckBox::LedColor::Green ); m_normalizeToggle->move( 131, 236 ); @@ -557,7 +529,7 @@ void BitInvaderView::smoothClicked() void BitInvaderView::interpolationToggled( bool value ) { - m_graph->setGraphStyle( value ? Graph::LinearStyle : Graph::NearestStyle); + m_graph->setGraphStyle( value ? Graph::Style::Linear : Graph::Style::Nearest); Engine::getSong()->setModified(); } diff --git a/plugins/BitInvader/BitInvader.h b/plugins/BitInvader/BitInvader.h index a08640e99..6dce9db83 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, diff --git a/plugins/Bitcrush/Bitcrush.cpp b/plugins/Bitcrush/Bitcrush.cpp index 963e970db..df4a8605d 100644 --- a/plugins/Bitcrush/Bitcrush.cpp +++ b/plugins/Bitcrush/Bitcrush.cpp @@ -48,7 +48,7 @@ Plugin::Descriptor PLUGIN_EXPORT bitcrush_plugin_descriptor = QT_TRANSLATE_NOOP( "PluginBrowser", "An oversampling bitcrusher" ), "Vesa Kivimäki ", 0x0100, - Plugin::Effect, + Plugin::Type::Effect, new PluginPixmapLoader( "logo" ), nullptr, nullptr, @@ -62,7 +62,7 @@ BitcrushEffect::BitcrushEffect( Model * parent, const Descriptor::SubPluginFeatu m_sampleRate( Engine::audioEngine()->processingSampleRate() ), 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,7 +77,7 @@ BitcrushEffect::BitcrushEffect( Model * parent, const Descriptor::SubPluginFeatu BitcrushEffect::~BitcrushEffect() { - MM_FREE( m_buffer ); + delete[] m_buffer; } diff --git a/plugins/Bitcrush/BitcrushControlDialog.cpp b/plugins/Bitcrush/BitcrushControlDialog.cpp index f3dc85470..64c9b6361 100644 --- a/plugins/Bitcrush/BitcrushControlDialog.cpp +++ b/plugins/Bitcrush/BitcrushControlDialog.cpp @@ -53,13 +53,13 @@ BitcrushControlDialog::BitcrushControlDialog( BitcrushControls * controls ) : outLabel->move( 139, 15 ); // input knobs - auto inGain = new Knob(knobBright_26, this); + auto inGain = new Knob(KnobType::Bright26, this); inGain->move( 16, 32 ); inGain->setModel( & controls->m_inGain ); inGain->setLabel( tr( "GAIN" ) ); inGain->setHintText( tr( "Input gain:" ) , " dBFS" ); - auto inNoise = new Knob(knobBright_26, this); + auto inNoise = new Knob(KnobType::Bright26, this); inNoise->move( 14, 76 ); inNoise->setModel( & controls->m_inNoise ); inNoise->setLabel( tr( "NOISE" ) ); @@ -67,13 +67,13 @@ BitcrushControlDialog::BitcrushControlDialog( BitcrushControls * controls ) : // output knobs - auto outGain = new Knob(knobBright_26, this); + auto outGain = new Knob(KnobType::Bright26, this); outGain->move( 138, 32 ); outGain->setModel( & controls->m_outGain ); outGain->setLabel( tr( "GAIN" ) ); outGain->setHintText( tr( "Output gain:" ) , " dBFS" ); - auto outClip = new Knob(knobBright_26, this); + auto outClip = new Knob(KnobType::Bright26, this); outClip->move( 138, 76 ); outClip->setModel( & controls->m_outClip ); outClip->setLabel( tr( "CLIP" ) ); @@ -82,25 +82,25 @@ BitcrushControlDialog::BitcrushControlDialog( BitcrushControls * controls ) : // leds - auto rateEnabled = new LedCheckBox("", this, tr("Rate enabled"), LedCheckBox::Green); + auto rateEnabled = new LedCheckBox("", this, tr("Rate enabled"), LedCheckBox::LedColor::Green); rateEnabled->move( 64, 14 ); rateEnabled->setModel( & controls->m_rateEnabled ); rateEnabled->setToolTip(tr("Enable sample-rate crushing")); - auto depthEnabled = new LedCheckBox("", this, tr("Depth enabled"), LedCheckBox::Green); + auto depthEnabled = new LedCheckBox("", this, tr("Depth enabled"), LedCheckBox::LedColor::Green); depthEnabled->move( 101, 14 ); depthEnabled->setModel( & controls->m_depthEnabled ); depthEnabled->setToolTip(tr("Enable bit-depth crushing")); // rate crushing knobs - auto rate = new Knob(knobBright_26, this); + auto rate = new Knob(KnobType::Bright26, this); rate->move( 59, 32 ); rate->setModel( & controls->m_rate ); rate->setLabel( tr( "FREQ" ) ); rate->setHintText( tr( "Sample rate:" ) , " Hz" ); - auto stereoDiff = new Knob(knobBright_26, this); + auto stereoDiff = new Knob(KnobType::Bright26, this); stereoDiff->move( 72, 76 ); stereoDiff->setModel( & controls->m_stereoDiff ); stereoDiff->setLabel( tr( "STEREO" ) ); @@ -108,7 +108,7 @@ BitcrushControlDialog::BitcrushControlDialog( BitcrushControls * controls ) : // depth crushing knob - auto levels = new Knob(knobBright_26, this); + auto levels = new Knob(KnobType::Bright26, this); levels->move( 92, 32 ); levels->setModel( & controls->m_levels ); levels->setLabel( tr( "QUANT" ) ); diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index cad7c00b4..04862cac1 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -5,14 +5,11 @@ SET(CMAKE_DEBUG_POSTFIX "") # Enable C++17 SET(CMAKE_CXX_STANDARD 17) -IF(LMMS_BUILD_APPLE) +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/CMakeLists.txt b/plugins/CarlaBase/CMakeLists.txt index f60804719..8f9e14dd1 100644 --- a/plugins/CarlaBase/CMakeLists.txt +++ b/plugins/CarlaBase/CMakeLists.txt @@ -1,11 +1,3 @@ -# For MacOS, use "OLD" RPATH install_name behavior -# This can be changed to "NEW" safely if install_apple.sh.in -# is updated to relink libcarlabase.dylib. MacOS 10.8 uses -# cmake 3.9.6, so this can be done at any time. -IF(NOT CMAKE_VERSION VERSION_LESS 3.9) - CMAKE_POLICY(SET CMP0068 OLD) -ENDIF() - # If Carla was not provided by the system, make a dummy library instead if(LMMS_HAVE_WEAKCARLA) SET(CARLA_INCLUDE_DIRS @@ -15,14 +7,13 @@ if(LMMS_HAVE_WEAKCARLA) ${CMAKE_CURRENT_SOURCE_DIR}/carla/source/backend ) + # force "lib" prefix IF(LMMS_BUILD_WIN32) - # use carla.dll - SET(CMAKE_SHARED_LIBRARY_PREFIX "") - SET(CARLA_NATIVE_LIB carla) - ELSE() - # use libcarla_native-plugin - SET(CARLA_NATIVE_LIB carla_native-plugin) + SET(CMAKE_SHARED_LIBRARY_PREFIX "lib") ENDIF() + + SET(CARLA_NATIVE_LIB carla_native-plugin) + ADD_LIBRARY(${CARLA_NATIVE_LIB} SHARED DummyCarla.cpp) TARGET_INCLUDE_DIRECTORIES(${CARLA_NATIVE_LIB} PUBLIC ${CARLA_INCLUDE_DIRS}) INSTALL(TARGETS ${CARLA_NATIVE_LIB} diff --git a/plugins/CarlaBase/Carla.cpp b/plugins/CarlaBase/Carla.cpp index ef14b7aa4..c95a965c9 100644 --- a/plugins/CarlaBase/Carla.cpp +++ b/plugins/CarlaBase/Carla.cpp @@ -220,7 +220,7 @@ CarlaInstrument::CarlaInstrument(InstrumentTrack* const instrumentTrack, const D CarlaInstrument::~CarlaInstrument() { - Engine::audioEngine()->removePlayHandlesOfTypes(instrumentTrack(), PlayHandle::TypeNotePlayHandle | PlayHandle::TypeInstrumentPlayHandle); + Engine::audioEngine()->removePlayHandlesOfTypes(instrumentTrack(), PlayHandle::Type::NotePlayHandle | PlayHandle::Type::InstrumentPlayHandle); if (fHost.resourceDir != nullptr) { @@ -345,7 +345,7 @@ intptr_t CarlaInstrument::handleDispatcher(const NativeHostDispatcherOpcode opco Instrument::Flags CarlaInstrument::flags() const { - return IsSingleStreamed|IsMidiBased|IsNotBendable; + return Flag::IsSingleStreamed | Flag::IsMidiBased | Flag::IsNotBendable; } QString CarlaInstrument::nodeName() const @@ -508,7 +508,6 @@ void CarlaInstrument::play(sampleFrame* workingBuffer) if (fHandle == nullptr) { - instrumentTrack()->processAudioBuffer(workingBuffer, bufsize, nullptr); return; } @@ -556,8 +555,6 @@ void CarlaInstrument::play(sampleFrame* workingBuffer) workingBuffer[i][0] = buf1[i]; workingBuffer[i][1] = buf2[i]; } - - instrumentTrack()->processAudioBuffer(workingBuffer, bufsize, nullptr); } bool CarlaInstrument::handleMidiEvent(const MidiEvent& event, const TimePos&, f_cnt_t offset) @@ -635,7 +632,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( @@ -645,7 +642,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.")); @@ -1015,7 +1012,7 @@ void CarlaParamsView::refreshKnobs() for (uint32_t i=0; i < m_carlaInstrument->m_paramModels.count(); ++i) { bool enabled = m_carlaInstrument->m_paramModels[i]->enabled(); - m_knobs.push_back(new Knob(knobDark_28, m_inputScrollAreaWidgetContent)); + m_knobs.push_back(new Knob(KnobType::Dark28, m_inputScrollAreaWidgetContent)); QString name = (*m_carlaInstrument->m_paramModels[i]).displayName(); m_knobs[i]->setHintText(name, ""); m_knobs[i]->setLabel(name); @@ -1119,16 +1116,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..3d0e424a2 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 diff --git a/plugins/CarlaBase/DummyCarla.cpp b/plugins/CarlaBase/DummyCarla.cpp index 8fd9ef6d7..572bdf4bc 100644 --- a/plugins/CarlaBase/DummyCarla.cpp +++ b/plugins/CarlaBase/DummyCarla.cpp @@ -2,12 +2,16 @@ #define BUILDING_CARLA #include "CarlaNativePlugin.h" -CARLA_EXPORT const char* carla_get_library_filename() { return nullptr; } -CARLA_EXPORT const char* carla_get_library_folder() { return nullptr; } -CARLA_EXPORT const NativePluginDescriptor* carla_get_native_rack_plugin() { return nullptr; } -CARLA_EXPORT const NativePluginDescriptor* carla_get_native_patchbay_plugin() { return nullptr; } -CARLA_EXPORT const NativePluginDescriptor* carla_get_native_patchbay16_plugin() { return nullptr; } -CARLA_EXPORT const NativePluginDescriptor* carla_get_native_patchbay32_plugin() { return nullptr; } -CARLA_EXPORT const NativePluginDescriptor* carla_get_native_patchbay64_plugin() { return nullptr; } -CARLA_EXPORT const NativePluginDescriptor* carla_get_native_patchbay_cv_plugin() { return nullptr; } -CARLA_EXPORT CarlaBackend::CarlaEngine* carla_get_native_plugin_engine(const NativePluginDescriptor* desc, NativePluginHandle handle) { return nullptr; } +#ifndef CARLA_PLUGIN_EXPORT +#define CARLA_PLUGIN_EXPORT CARLA_EXPORT +#endif + +CARLA_PLUGIN_EXPORT const char* carla_get_library_filename() { return nullptr; } +CARLA_PLUGIN_EXPORT const char* carla_get_library_folder() { return nullptr; } +CARLA_PLUGIN_EXPORT const NativePluginDescriptor* carla_get_native_rack_plugin() { return nullptr; } +CARLA_PLUGIN_EXPORT const NativePluginDescriptor* carla_get_native_patchbay_plugin() { return nullptr; } +CARLA_PLUGIN_EXPORT const NativePluginDescriptor* carla_get_native_patchbay16_plugin() { return nullptr; } +CARLA_PLUGIN_EXPORT const NativePluginDescriptor* carla_get_native_patchbay32_plugin() { return nullptr; } +CARLA_PLUGIN_EXPORT const NativePluginDescriptor* carla_get_native_patchbay64_plugin() { return nullptr; } +CARLA_PLUGIN_EXPORT const NativePluginDescriptor* carla_get_native_patchbay_cv_plugin() { return nullptr; } +CARLA_PLUGIN_EXPORT CarlaBackend::CarlaEngine* carla_get_native_plugin_engine(const NativePluginDescriptor* desc, NativePluginHandle handle) { return nullptr; } diff --git a/plugins/CarlaBase/carla b/plugins/CarlaBase/carla index 4ac8ff2ef..66afe24a0 160000 --- a/plugins/CarlaBase/carla +++ b/plugins/CarlaBase/carla @@ -1 +1 @@ -Subproject commit 4ac8ff2ef412d4ab190d2e285e318b1f339af4ae +Subproject commit 66afe24a08790732cc17d81d4b846a1e0cfa0118 diff --git a/plugins/CarlaPatchbay/CarlaPatchbay.cpp b/plugins/CarlaPatchbay/CarlaPatchbay.cpp index e440d4e3f..ae22e30d1 100644 --- a/plugins/CarlaPatchbay/CarlaPatchbay.cpp +++ b/plugins/CarlaPatchbay/CarlaPatchbay.cpp @@ -43,7 +43,7 @@ Plugin::Descriptor PLUGIN_EXPORT carlapatchbay_plugin_descriptor = "Carla Patchbay Instrument" ), "falkTX ", CARLA_VERSION_HEX, - Plugin::Instrument, + Plugin::Type::Instrument, new PluginPixmapLoader( "logo" ), nullptr, nullptr, diff --git a/plugins/CarlaRack/CarlaRack.cpp b/plugins/CarlaRack/CarlaRack.cpp index 5360baf96..8be5e40b3 100644 --- a/plugins/CarlaRack/CarlaRack.cpp +++ b/plugins/CarlaRack/CarlaRack.cpp @@ -43,7 +43,7 @@ Plugin::Descriptor PLUGIN_EXPORT carlarack_plugin_descriptor = "Carla Rack Instrument" ), "falkTX ", CARLA_VERSION_HEX, - Plugin::Instrument, + Plugin::Type::Instrument, new PluginPixmapLoader( "logo" ), nullptr, nullptr, diff --git a/plugins/Compressor/Compressor.cpp b/plugins/Compressor/Compressor.cpp index 92123ffd9..e59562b02 100755 --- a/plugins/Compressor/Compressor.cpp +++ b/plugins/Compressor/Compressor.cpp @@ -43,7 +43,7 @@ Plugin::Descriptor PLUGIN_EXPORT compressor_plugin_descriptor = QT_TRANSLATE_NOOP("PluginBrowser", "A dynamic range compressor."), "Lost Robot ", 0x0100, - Plugin::Effect, + Plugin::Type::Effect, new PluginPixmapLoader("logo"), nullptr, nullptr, @@ -60,9 +60,6 @@ CompressorEffect::CompressorEffect(Model* parent, const Descriptor::SubPluginFea 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)); @@ -177,14 +174,13 @@ void CompressorEffect::calcRange() void CompressorEffect::resizeRMS() { - m_rmsTimeConst = exp(-1.f / (m_compressorControls.m_rmsModel.value() * 0.001f * m_sampleRate)); + const float rmsValue = m_compressorControls.m_rmsModel.value(); + m_rmsTimeConst = (rmsValue > 0) ? exp(-1.f / (rmsValue * 0.001f * m_sampleRate)) : 0; } void CompressorEffect::calcLookaheadLength() { - 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() @@ -248,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; @@ -317,11 +306,11 @@ 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(inputValue * inputValue, m_crestTimeConst * m_crestPeakVal[i] + (1 - m_crestTimeConst) * (inputValue * inputValue)); - m_crestRmsVal[i] = m_crestTimeConst * m_crestRmsVal[i] + ((1 - m_crestTimeConst) * (inputValue * inputValue)); + m_crestPeakVal[i] = qMax(qMax(COMP_NOISE_FLOOR, inputValue * inputValue), m_crestTimeConst * m_crestPeakVal[i] + (1 - m_crestTimeConst) * (inputValue * inputValue)); + m_crestRmsVal[i] = qMax(COMP_NOISE_FLOOR, m_crestTimeConst * m_crestRmsVal[i] + ((1 - m_crestTimeConst) * (inputValue * inputValue))); m_crestFactorVal[i] = m_crestPeakVal[i] / m_crestRmsVal[i]; m_rmsVal[i] = m_rmsTimeConst * m_rmsVal[i] + ((1 - m_rmsTimeConst) * (inputValue * inputValue)); @@ -329,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 @@ -414,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. @@ -438,32 +391,32 @@ 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]); } - switch (stereoLink) + switch (static_cast(stereoLink)) { - case Unlinked: + case StereoLinkMode::Unlinked: { break; } - case Maximum: + case StereoLinkMode::Maximum: { m_gainResult[0] = m_gainResult[1] = qMin(m_gainResult[0], m_gainResult[1]); break; } - case Average: + case StereoLinkMode::Average: { m_gainResult[0] = m_gainResult[1] = (m_gainResult[0] + m_gainResult[1]) * 0.5f; break; } - case Minimum: + case StereoLinkMode::Minimum: { m_gainResult[0] = m_gainResult[1] = qMax(m_gainResult[0], m_gainResult[1]); break; } - case Blend: + case StereoLinkMode::Blend: { if (blend > 0)// 0 is unlinked { @@ -506,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 { @@ -572,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; @@ -620,16 +567,13 @@ void CompressorEffect::changeSampleRate() // 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 121e0b88f..3fc90b752 100755 --- a/plugins/Compressor/Compressor.h +++ b/plugins/Compressor/Compressor.h @@ -79,16 +79,12 @@ private: inline int realmod(int k, int n); inline float realfmod(float k, float n); - enum StereoLinkModes { Unlinked, Maximum, Average, Minimum, Blend }; + 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 8c6f61bec..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); @@ -95,92 +84,92 @@ CompressorControlDialog::CompressorControlDialog(CompressorControls* controls) : m_ratioEnabledLabel->setPixmap(PLUGIN_NAME::getIconPixmap("knob_enabled_large")); m_ratioEnabledLabel->setAttribute(Qt::WA_TransparentForMouseEvents); - m_thresholdKnob = new Knob(knobStyled, this); + m_thresholdKnob = new Knob(KnobType::Styled, this); makeLargeKnob(m_thresholdKnob, tr("Threshold:") , " dBFS"); m_thresholdKnob->setModel(&controls->m_thresholdModel); m_thresholdKnob->setToolTip(tr("Volume at which the compression begins to take place")); - m_ratioKnob = new Knob(knobStyled, this); + m_ratioKnob = new Knob(KnobType::Styled, this); makeLargeKnob(m_ratioKnob, tr("Ratio:") , ":1"); m_ratioKnob->setModel(&controls->m_ratioModel); m_ratioKnob->setToolTip(tr("How far the compressor must turn the volume down after crossing the threshold")); - m_attackKnob = new Knob(knobStyled, this); + m_attackKnob = new Knob(KnobType::Styled, this); makeLargeKnob(m_attackKnob, tr("Attack:") , " ms"); m_attackKnob->setModel(&controls->m_attackModel); m_attackKnob->setToolTip(tr("Speed at which the compressor starts to compress the audio")); - m_releaseKnob = new Knob(knobStyled, this); + m_releaseKnob = new Knob(KnobType::Styled, this); makeLargeKnob(m_releaseKnob, tr("Release:") , " ms"); m_releaseKnob->setModel(&controls->m_releaseModel); m_releaseKnob->setToolTip(tr("Speed at which the compressor ceases to compress the audio")); - m_kneeKnob = new Knob(knobStyled, this); + m_kneeKnob = new Knob(KnobType::Styled, this); makeSmallKnob(m_kneeKnob, tr("Knee:") , " dB"); m_kneeKnob->setModel(&controls->m_kneeModel); m_kneeKnob->setToolTip(tr("Smooth out the gain reduction curve around the threshold")); - m_rangeKnob = new Knob(knobStyled, this); + m_rangeKnob = new Knob(KnobType::Styled, this); makeSmallKnob(m_rangeKnob, tr("Range:") , " dBFS"); m_rangeKnob->setModel(&controls->m_rangeModel); m_rangeKnob->setToolTip(tr("Maximum gain reduction")); - m_lookaheadLengthKnob = new Knob(knobStyled, this); + m_lookaheadLengthKnob = new Knob(KnobType::Styled, this); makeSmallKnob(m_lookaheadLengthKnob, tr("Lookahead Length:") , " ms"); m_lookaheadLengthKnob->setModel(&controls->m_lookaheadLengthModel); m_lookaheadLengthKnob->setToolTip(tr("How long the compressor has to react to the sidechain signal ahead of time")); - m_holdKnob = new Knob(knobStyled, this); + m_holdKnob = new Knob(KnobType::Styled, this); makeSmallKnob(m_holdKnob, tr("Hold:") , " ms"); m_holdKnob->setModel(&controls->m_holdModel); m_holdKnob->setToolTip(tr("Delay between attack and release stages")); - m_rmsKnob = new Knob(knobStyled, this); + m_rmsKnob = new Knob(KnobType::Styled, this); makeSmallKnob(m_rmsKnob, tr("RMS Size:") , ""); m_rmsKnob->setModel(&controls->m_rmsModel); m_rmsKnob->setToolTip(tr("Size of the RMS buffer")); - m_inBalanceKnob = new Knob(knobStyled, this); + m_inBalanceKnob = new Knob(KnobType::Styled, this); makeSmallKnob(m_inBalanceKnob, tr("Input Balance:") , ""); m_inBalanceKnob->setModel(&controls->m_inBalanceModel); m_inBalanceKnob->setToolTip(tr("Bias the input audio to the left/right or mid/side")); - m_outBalanceKnob = new Knob(knobStyled, this); + m_outBalanceKnob = new Knob(KnobType::Styled, this); makeSmallKnob(m_outBalanceKnob, tr("Output Balance:") , ""); m_outBalanceKnob->setModel(&controls->m_outBalanceModel); m_outBalanceKnob->setToolTip(tr("Bias the output audio to the left/right or mid/side")); - m_stereoBalanceKnob = new Knob(knobStyled, this); + m_stereoBalanceKnob = new Knob(KnobType::Styled, this); makeSmallKnob(m_stereoBalanceKnob, tr("Stereo Balance:") , ""); m_stereoBalanceKnob->setModel(&controls->m_stereoBalanceModel); m_stereoBalanceKnob->setToolTip(tr("Bias the sidechain signal to the left/right or mid/side")); - m_blendKnob = new Knob(knobStyled, this); + m_blendKnob = new Knob(KnobType::Styled, this); makeSmallKnob(m_blendKnob, tr("Stereo Link Blend:") , ""); m_blendKnob->setModel(&controls->m_blendModel); m_blendKnob->setToolTip(tr("Blend between unlinked/maximum/average/minimum stereo linking modes")); - m_tiltKnob = new Knob(knobStyled, this); + m_tiltKnob = new Knob(KnobType::Styled, this); makeSmallKnob(m_tiltKnob, tr("Tilt Gain:") , " dB"); m_tiltKnob->setModel(&controls->m_tiltModel); m_tiltKnob->setToolTip(tr("Bias the sidechain signal to the low or high frequencies. -6 db is lowpass, 6 db is highpass.")); - m_tiltFreqKnob = new Knob(knobStyled, this); + m_tiltFreqKnob = new Knob(KnobType::Styled, this); makeSmallKnob(m_tiltFreqKnob, tr("Tilt Frequency:") , " Hz"); m_tiltFreqKnob->setModel(&controls->m_tiltFreqModel); m_tiltFreqKnob->setToolTip(tr("Center frequency of sidechain tilt filter")); - m_mixKnob = new Knob(knobStyled, this); + m_mixKnob = new Knob(KnobType::Styled, this); makeSmallKnob(m_mixKnob, tr("Mix:") , "%"); m_mixKnob->setModel(&controls->m_mixModel); m_mixKnob->setToolTip(tr("Balance between wet and dry signals")); - m_autoAttackKnob = new Knob(knobStyled, this); + m_autoAttackKnob = new Knob(KnobType::Styled, this); makeSmallKnob(m_autoAttackKnob, tr("Auto Attack:") , "%"); m_autoAttackKnob->setModel(&controls->m_autoAttackModel); m_autoAttackKnob->setToolTip(tr("Automatically control attack value depending on crest factor")); - m_autoReleaseKnob = new Knob(knobStyled, this); + m_autoReleaseKnob = new Knob(KnobType::Styled, this); makeSmallKnob(m_autoReleaseKnob, tr("Auto Release:") , "%"); m_autoReleaseKnob->setModel(&controls->m_autoReleaseModel); m_autoReleaseKnob->setToolTip(tr("Automatically control release value depending on crest factor")); @@ -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()); } @@ -497,10 +487,12 @@ void CompressorControlDialog::redrawKnee() float actualRatio = m_controls->m_limiterModel.value() ? 0 : m_controls->m_effect->m_ratioVal; // Calculate endpoints for the two straight lines - float kneePoint1 = m_controls->m_effect->m_thresholdVal - m_controls->m_effect->m_kneeVal; - float kneePoint2X = m_controls->m_effect->m_thresholdVal + m_controls->m_effect->m_kneeVal; - float kneePoint2Y = (m_controls->m_effect->m_thresholdVal + (-m_controls->m_effect->m_thresholdVal * (actualRatio * (m_controls->m_effect->m_kneeVal / -m_controls->m_effect->m_thresholdVal)))); - float ratioPoint = m_controls->m_effect->m_thresholdVal + (-m_controls->m_effect->m_thresholdVal * actualRatio); + const float thresholdVal = m_controls->m_effect->m_thresholdVal; + const float kneeVal = m_controls->m_effect->m_kneeVal; + float kneePoint1 = thresholdVal - kneeVal; + float kneePoint2X = thresholdVal + kneeVal; + float kneePoint2Y = thresholdVal + kneeVal * actualRatio; + float ratioPoint = thresholdVal + (-thresholdVal * actualRatio); // Draw two straight lines m_p.drawLine(0, m_kneeWindowSizeY, dbfsToXPoint(kneePoint1), dbfsToYPoint(kneePoint1)); @@ -510,7 +502,7 @@ void CompressorControlDialog::redrawKnee() } // Draw knee section - if (m_controls->m_effect->m_kneeVal) + if (kneeVal) { m_p.setPen(QPen(m_kneeColor2, 3)); @@ -522,8 +514,8 @@ void CompressorControlDialog::redrawKnee() { newPoint[0] = linearInterpolate(kneePoint1, kneePoint2X, (i + 1) / (float)COMP_KNEE_LINES); - const float temp = newPoint[0] - m_controls->m_effect->m_thresholdVal + m_controls->m_effect->m_kneeVal; - newPoint[1] = (newPoint[0] + (actualRatio - 1) * temp * temp / (4 * m_controls->m_effect->m_kneeVal)); + const float temp = newPoint[0] - thresholdVal + kneeVal; + newPoint[1] = (newPoint[0] + (actualRatio - 1) * temp * temp / (4 * kneeVal)); m_p.drawLine(dbfsToXPoint(prevPoint[0]), dbfsToYPoint(prevPoint[1]), dbfsToXPoint(newPoint[0]), dbfsToYPoint(newPoint[1])); @@ -609,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); @@ -677,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); @@ -688,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(); @@ -768,4 +833,4 @@ void CompressorControlDialog::resetCompressorView() } -} // namespace lmms::gui \ No newline at end of file +} // namespace lmms::gui 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 641e06b4e..4dca94a4c 100644 --- a/plugins/CrossoverEQ/CrossoverEQ.cpp +++ b/plugins/CrossoverEQ/CrossoverEQ.cpp @@ -43,7 +43,7 @@ Plugin::Descriptor PLUGIN_EXPORT crossovereq_plugin_descriptor = QT_TRANSLATE_NOOP( "PluginBrowser", "A 4-band Crossover Equalizer" ), "Vesa Kivimäki ", 0x0100, - Plugin::Effect, + Plugin::Type::Effect, new PluginPixmapLoader( "logo" ), nullptr, nullptr, @@ -64,16 +64,16 @@ 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() diff --git a/plugins/CrossoverEQ/CrossoverEQControlDialog.cpp b/plugins/CrossoverEQ/CrossoverEQControlDialog.cpp index ba32e9dfc..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 { @@ -46,66 +49,68 @@ CrossoverEQControlDialog::CrossoverEQControlDialog( CrossoverEQControls * contro setFixedSize( 167, 178 ); // knobs - auto xover12 = new Knob(knobBright_26, this); + auto xover12 = new Knob(KnobType::Bright26, this); xover12->move( 29, 11 ); xover12->setModel( & controls->m_xover12 ); xover12->setLabel( "1/2" ); xover12->setHintText( tr( "Band 1/2 crossover:" ), " Hz" ); - auto xover23 = new Knob(knobBright_26, this); + auto xover23 = new Knob(KnobType::Bright26, this); xover23->move( 69, 11 ); xover23->setModel( & controls->m_xover23 ); xover23->setLabel( "2/3" ); xover23->setHintText( tr( "Band 2/3 crossover:" ), " Hz" ); - auto xover34 = new Knob(knobBright_26, this); + auto xover34 = new Knob(KnobType::Bright26, this); xover34->move( 109, 11 ); xover34->setModel( & controls->m_xover34 ); 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::Green); + auto mute1 = new LedCheckBox("", this, tr("Band 1 mute"), LedCheckBox::LedColor::Green); mute1->move( 15, 154 ); mute1->setModel( & controls->m_mute1 ); mute1->setToolTip(tr("Mute band 1")); - auto mute2 = new LedCheckBox("", this, tr("Band 2 mute"), LedCheckBox::Green); + auto mute2 = new LedCheckBox("", this, tr("Band 2 mute"), LedCheckBox::LedColor::Green); mute2->move( 55, 154 ); mute2->setModel( & controls->m_mute2 ); mute2->setToolTip(tr("Mute band 2")); - auto mute3 = new LedCheckBox("", this, tr("Band 3 mute"), LedCheckBox::Green); + auto mute3 = new LedCheckBox("", this, tr("Band 3 mute"), LedCheckBox::LedColor::Green); mute3->move( 95, 154 ); mute3->setModel( & controls->m_mute3 ); mute3->setToolTip(tr("Mute band 3")); - auto mute4 = new LedCheckBox("", this, tr("Band 4 mute"), LedCheckBox::Green); + auto mute4 = new LedCheckBox("", this, tr("Band 4 mute"), LedCheckBox::LedColor::Green); mute4->move( 135, 154 ); mute4->setModel( & controls->m_mute4 ); mute4->setToolTip(tr("Mute band 4")); 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/DelayControlsDialog.cpp b/plugins/Delay/DelayControlsDialog.cpp index 9a9dea0d4..065b3d1e4 100644 --- a/plugins/Delay/DelayControlsDialog.cpp +++ b/plugins/Delay/DelayControlsDialog.cpp @@ -44,28 +44,28 @@ DelayControlsDialog::DelayControlsDialog( DelayControls *controls ) : setPalette( pal ); setFixedSize( 300, 208 ); - auto sampleDelayKnob = new TempoSyncKnob(knobBright_26, this); + auto sampleDelayKnob = new TempoSyncKnob(KnobType::Bright26, this); sampleDelayKnob->move( 10,14 ); sampleDelayKnob->setVolumeKnob( false ); sampleDelayKnob->setModel( &controls->m_delayTimeModel ); sampleDelayKnob->setLabel( tr( "DELAY" ) ); sampleDelayKnob->setHintText( tr( "Delay time" ) + " ", " s" ); - auto feedbackKnob = new Knob(knobBright_26, this); + auto feedbackKnob = new Knob(KnobType::Bright26, this); feedbackKnob->move( 11, 58 ); feedbackKnob->setVolumeKnob( true) ; feedbackKnob->setModel( &controls->m_feedbackModel); feedbackKnob->setLabel( tr( "FDBK" ) ); feedbackKnob->setHintText( tr ( "Feedback amount" ) + " " , "" ); - auto lfoFreqKnob = new TempoSyncKnob(knobBright_26, this); + auto lfoFreqKnob = new TempoSyncKnob(KnobType::Bright26, this); lfoFreqKnob->move( 11, 119 ); lfoFreqKnob->setVolumeKnob( false ); lfoFreqKnob->setModel( &controls->m_lfoTimeModel ); lfoFreqKnob->setLabel( tr( "RATE" ) ); lfoFreqKnob->setHintText( tr ( "LFO frequency") + " ", " s" ); - auto lfoAmtKnob = new TempoSyncKnob(knobBright_26, this); + auto lfoAmtKnob = new TempoSyncKnob(KnobType::Bright26, this); lfoAmtKnob->move( 11, 159 ); lfoAmtKnob->setVolumeKnob( false ); lfoAmtKnob->setModel( &controls->m_lfoAmountModel ); diff --git a/plugins/Delay/DelayEffect.cpp b/plugins/Delay/DelayEffect.cpp index 6db2f38e3..0050cd6fa 100644 --- a/plugins/Delay/DelayEffect.cpp +++ b/plugins/Delay/DelayEffect.cpp @@ -44,7 +44,7 @@ Plugin::Descriptor PLUGIN_EXPORT delay_plugin_descriptor = QT_TRANSLATE_NOOP( "PluginBrowser", "A native delay plugin" ), "Dave French ", 0x0100, - Plugin::Effect, + Plugin::Type::Effect, new PluginPixmapLoader("logo"), nullptr, nullptr, @@ -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()->processingSampleRate()); m_delay->setLength( m_currentLength + ( *amplitudePtr * ( float )m_lfo->tick() ) ); m_delay->tick( buf[f] ); diff --git a/plugins/Dispersion/CMakeLists.txt b/plugins/Dispersion/CMakeLists.txt new file mode 100644 index 000000000..a40e04b80 --- /dev/null +++ b/plugins/Dispersion/CMakeLists.txt @@ -0,0 +1,3 @@ +INCLUDE(BuildPlugin) + +BUILD_PLUGIN(dispersion Dispersion.cpp DispersionControls.cpp DispersionControlDialog.cpp MOCFILES DispersionControls.h DispersionControlDialog.h EMBEDDED_RESOURCES *.png) diff --git a/plugins/Dispersion/Dispersion.cpp b/plugins/Dispersion/Dispersion.cpp new file mode 100644 index 000000000..fb28e1f47 --- /dev/null +++ b/plugins/Dispersion/Dispersion.cpp @@ -0,0 +1,162 @@ +/* + * Dispersion.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 "Dispersion.h" + +#include "embed.h" +#include "plugin_export.h" + +namespace lmms +{ + +extern "C" +{ + +Plugin::Descriptor PLUGIN_EXPORT dispersion_plugin_descriptor = +{ + LMMS_STRINGIFY(PLUGIN_NAME), + "Dispersion", + QT_TRANSLATE_NOOP("PluginBrowser", "An all-pass filter allowing for extremely high orders."), + "Lost Robot ", + 0x0100, + Plugin::Type::Effect, + new PluginPixmapLoader("logo"), + nullptr, + nullptr +}; + +} + + +DispersionEffect::DispersionEffect(Model* parent, const Descriptor::SubPluginFeatures::Key* key) : + Effect(&dispersion_plugin_descriptor, parent, key), + m_dispersionControls(this), + m_sampleRate(Engine::audioEngine()->processingSampleRate()), + m_amountVal(0) +{ +} + + +bool DispersionEffect::processAudioBuffer(sampleFrame* buf, const fpp_t frames) +{ + if (!isEnabled() || !isRunning()) + { + return false; + } + + double outSum = 0.0; + const float d = dryLevel(); + const float w = wetLevel(); + + const int amount = m_dispersionControls.m_amountModel.value(); + const float freq = m_dispersionControls.m_freqModel.value(); + const float reso = m_dispersionControls.m_resoModel.value(); + float feedback = m_dispersionControls.m_feedbackModel.value(); + const bool dc = m_dispersionControls.m_dcModel.value(); + + // All-pass coefficient calculation + const float w0 = (F_2PI / m_sampleRate) * freq; + const float a0 = 1 + (std::sin(w0) / (reso * 2.f)); + float apCoeff1 = (1 - (a0 - 1)) / a0; + float apCoeff2 = (-2 * std::cos(w0)) / a0; + + float dcCoeff = 0.001 * (44100.f / m_sampleRate); + + if (amount != m_amountVal) + { + if (amount < m_amountVal) + { + // Flush filter buffers when they're no longer in use + for (int i = amount * 2; i < m_amountVal * 2; ++i) + { + m_state.x0[i] = m_state.x1[i] = m_state.y0[i] = m_state.y1[i] = 0; + } + } + m_amountVal = amount; + } + + if (amount == 0) + { + feedback = 0; + m_feedbackVal[0] = m_feedbackVal[1] = 0; + } + + for (fpp_t f = 0; f < frames; ++f) + { + std::array s = { buf[f][0] + m_feedbackVal[0], buf[f][1] + m_feedbackVal[1] }; + + runDispersionAP(m_amountVal, apCoeff1, apCoeff2, s); + m_feedbackVal[0] = s[0] * feedback; + m_feedbackVal[1] = s[1] * feedback; + + if (dc) + { + // DC offset removal + for (int i = 0; i < 2; ++i) + { + m_integrator[i] = m_integrator[i] * (1.f - dcCoeff) + s[i] * dcCoeff; + s[i] -= m_integrator[i]; + } + } + + 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); + return isRunning(); +} + + +void DispersionEffect::runDispersionAP(const int filtNum, const float apCoeff1, const float apCoeff2, std::array &put) +{ + for (int i = 0; i < filtNum * 2; ++i) + { + const int channel = i % 2; + const sample_t currentInput = put[channel]; + const sample_t filterOutput = apCoeff1 * (currentInput - m_state.y1[i]) + + apCoeff2 * (m_state.x0[i] - m_state.y0[i]) + m_state.x1[i]; + m_state.x1[i] = m_state.x0[i]; + m_state.x0[i] = currentInput; + m_state.y1[i] = m_state.y0[i]; + m_state.y0[i] = filterOutput; + + put[channel] = filterOutput; + } +} + + +extern "C" +{ + +// necessary for getting instance out of shared lib +PLUGIN_EXPORT Plugin * lmms_plugin_main(Model* parent, void* data) +{ + return new DispersionEffect(parent, static_cast(data)); +} + +} + +} // namespace lmms diff --git a/plugins/Dispersion/Dispersion.h b/plugins/Dispersion/Dispersion.h new file mode 100644 index 000000000..9e2014baf --- /dev/null +++ b/plugins/Dispersion/Dispersion.h @@ -0,0 +1,78 @@ +/* + * Dispersion.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_DISPERSION_H +#define LMMS_DISPERSION_H + +#include "DispersionControls.h" +#include "Effect.h" + +#include "lmms_math.h" + +namespace lmms +{ + +constexpr inline int MAX_DISPERSION_FILTERS = 999; + +class DispersionEffect : public Effect +{ +public: + DispersionEffect(Model* parent, const Descriptor::SubPluginFeatures::Key* key); + ~DispersionEffect() override = default; + bool processAudioBuffer(sampleFrame* buf, const fpp_t frames) override; + + EffectControls* controls() override + { + return &m_dispersionControls; + } + + void runDispersionAP(const int filtNum, const float apCoeff1, const float apCoeff2, std::array &put); + +private: + DispersionControls m_dispersionControls; + + float m_sampleRate; + + int m_amountVal; + + using Filter = std::array; + struct FilterState { + Filter x0{}; + Filter x1{}; + Filter y0{}; + Filter y1{}; + }; + FilterState m_state = {}; + + std::array m_feedbackVal{}; + std::array m_integrator{}; + + friend class DispersionControls; +}; + + +} // namespace lmms + +#endif // LMMS_DISPERSION_H diff --git a/plugins/Dispersion/DispersionControlDialog.cpp b/plugins/Dispersion/DispersionControlDialog.cpp new file mode 100644 index 000000000..2879e7613 --- /dev/null +++ b/plugins/Dispersion/DispersionControlDialog.cpp @@ -0,0 +1,82 @@ +/* + * DispersionControlDialog.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 "DispersionControlDialog.h" +#include "DispersionControls.h" + +#include "embed.h" +#include "Knob.h" +#include "LcdSpinBox.h" +#include "PixmapButton.h" + + +namespace lmms::gui +{ + + +DispersionControlDialog::DispersionControlDialog(DispersionControls* controls) : + EffectControlDialog(controls) +{ + setAutoFillBackground(true); + QPalette pal; + pal.setBrush(backgroundRole(), PLUGIN_NAME::getIconPixmap("artwork")); + setPalette(pal); + setFixedSize(207, 50); + + LcdSpinBox * m_amountBox = new LcdSpinBox(3, this, "Amount"); + m_amountBox->setModel(&controls->m_amountModel); + m_amountBox->move(5, 10); + m_amountBox->setLabel(tr("AMOUNT")); + m_amountBox->setToolTip(tr("Number of all-pass filters")); + + Knob * freqKnob = new Knob(KnobType::Bright26, this); + freqKnob->move(59, 8); + freqKnob->setModel(&controls->m_freqModel); + freqKnob->setLabel(tr("FREQ")); + freqKnob->setHintText(tr("Frequency:") , " Hz"); + + Knob * resoKnob = new Knob(KnobType::Bright26, this); + resoKnob->move(99, 8); + resoKnob->setModel(&controls->m_resoModel); + resoKnob->setLabel(tr("RESO")); + resoKnob->setHintText(tr("Resonance:") , " octaves"); + + Knob * feedbackKnob = new Knob(KnobType::Bright26, this); + feedbackKnob->move(139, 8); + feedbackKnob->setModel(&controls->m_feedbackModel); + feedbackKnob->setLabel(tr("FEED")); + feedbackKnob->setHintText(tr("Feedback:") , ""); + + PixmapButton * dcButton = new PixmapButton(this, tr("DC Offset Removal")); + dcButton->move(176, 16); + dcButton->setActiveGraphic(PLUGIN_NAME::getIconPixmap("dc_active")); + dcButton->setInactiveGraphic(PLUGIN_NAME::getIconPixmap("dc_inactive")); + dcButton->setCheckable(true); + dcButton->setModel(&controls->m_dcModel); + dcButton->setToolTip(tr("Remove DC Offset")); +} + + +} // namespace lmms::gui diff --git a/plugins/Dispersion/DispersionControlDialog.h b/plugins/Dispersion/DispersionControlDialog.h new file mode 100644 index 000000000..0d55678bd --- /dev/null +++ b/plugins/Dispersion/DispersionControlDialog.h @@ -0,0 +1,52 @@ +/* + * DispersionControlDialog.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_DISPERSION_CONTROL_DIALOG_H +#define LMMS_GUI_DISPERSION_CONTROL_DIALOG_H + +#include "EffectControlDialog.h" + +namespace lmms +{ + +class DispersionControls; + + +namespace gui +{ + +class DispersionControlDialog : public EffectControlDialog +{ + Q_OBJECT +public: + DispersionControlDialog(DispersionControls* controls); + ~DispersionControlDialog() override = default; +}; + + +} // namespace gui + +} // namespace lmms + +#endif // LMMS_GUI_DISPERSION_CONTROL_DIALOG_H diff --git a/plugins/Dispersion/DispersionControls.cpp b/plugins/Dispersion/DispersionControls.cpp new file mode 100644 index 000000000..771ffb89d --- /dev/null +++ b/plugins/Dispersion/DispersionControls.cpp @@ -0,0 +1,73 @@ +/* + * DispersionControls.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 "DispersionControls.h" +#include "Dispersion.h" + +#include + +namespace lmms +{ + +DispersionControls::DispersionControls(DispersionEffect* effect) : + EffectControls(effect), + m_effect(effect), + m_amountModel(0, 0, MAX_DISPERSION_FILTERS, this, tr("Amount")), + m_freqModel(200, 20, 20000, 0.001, this, tr("Frequency")), + m_resoModel(0.707, 0.01, 8, 0.0001, this, tr("Resonance")), + m_feedbackModel(0.f, -1.f, 1.f, 0.0001, this, tr("Feedback")), + m_dcModel(false, this, tr("DC Offset Removal")) +{ + m_freqModel.setScaleLogarithmic(true); + m_resoModel.setScaleLogarithmic(true); +} + + + +void DispersionControls::loadSettings(const QDomElement& parent) +{ + m_amountModel.loadSettings(parent, "amount"); + m_freqModel.loadSettings(parent, "freq"); + m_resoModel.loadSettings(parent, "reso"); + m_feedbackModel.loadSettings(parent, "feedback"); + m_dcModel.loadSettings(parent, "dc"); +} + + + + +void DispersionControls::saveSettings(QDomDocument& doc, QDomElement& parent) +{ + m_amountModel.saveSettings(doc, parent, "amount"); + m_freqModel.saveSettings(doc, parent, "freq"); + m_resoModel.saveSettings(doc, parent, "reso"); + m_feedbackModel.saveSettings(doc, parent, "feedback"); + m_dcModel.saveSettings(doc, parent, "dc"); +} + + +} // namespace lmms + + diff --git a/plugins/Dispersion/DispersionControls.h b/plugins/Dispersion/DispersionControls.h new file mode 100644 index 000000000..e815e1115 --- /dev/null +++ b/plugins/Dispersion/DispersionControls.h @@ -0,0 +1,81 @@ +/* + * DispersionControls.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_DISPERSION_CONTROLS_H +#define LMMS_DISPERSION_CONTROLS_H + +#include "DispersionControlDialog.h" +#include "EffectControls.h" + +namespace lmms +{ + +class DispersionEffect; + +namespace gui +{ +class DispersionControlDialog; +} + + +class DispersionControls : public EffectControls +{ + Q_OBJECT +public: + DispersionControls(DispersionEffect* effect); + ~DispersionControls() override = default; + + void saveSettings(QDomDocument & doc, QDomElement & parent) override; + void loadSettings(const QDomElement & parent) override; + inline QString nodeName() const override + { + return "DispersionControls"; + } + + int controlCount() override + { + return 5; + } + + gui::EffectControlDialog* createView() override + { + return new gui::DispersionControlDialog(this); + } + +private: + DispersionEffect* m_effect; + IntModel m_amountModel; + FloatModel m_freqModel; + FloatModel m_resoModel; + FloatModel m_feedbackModel; + BoolModel m_dcModel; + + friend class gui::DispersionControlDialog; + friend class DispersionEffect; +}; + + +} // namespace lmms + +#endif // LMMS_DISPERSION_CONTROLS_H diff --git a/plugins/Dispersion/artwork.png b/plugins/Dispersion/artwork.png new file mode 100644 index 000000000..17e3b9a11 Binary files /dev/null and b/plugins/Dispersion/artwork.png differ diff --git a/plugins/Dispersion/dc_active.png b/plugins/Dispersion/dc_active.png new file mode 100644 index 000000000..d9c8c9378 Binary files /dev/null and b/plugins/Dispersion/dc_active.png differ diff --git a/plugins/Dispersion/dc_inactive.png b/plugins/Dispersion/dc_inactive.png new file mode 100644 index 000000000..9a0ee0693 Binary files /dev/null and b/plugins/Dispersion/dc_inactive.png differ diff --git a/plugins/Dispersion/logo.png b/plugins/Dispersion/logo.png new file mode 100644 index 000000000..9340da708 Binary files /dev/null and b/plugins/Dispersion/logo.png differ diff --git a/plugins/DualFilter/DualFilter.cpp b/plugins/DualFilter/DualFilter.cpp index e510109e9..4e66db988 100644 --- a/plugins/DualFilter/DualFilter.cpp +++ b/plugins/DualFilter/DualFilter.cpp @@ -43,7 +43,7 @@ Plugin::Descriptor PLUGIN_EXPORT dualfilter_plugin_descriptor = QT_TRANSLATE_NOOP( "PluginBrowser", "A Dual filter plugin" ), "Vesa Kivimäki ", 0x0100, - Plugin::Effect, + Plugin::Type::Effect, new PluginPixmapLoader( "logo" ), nullptr, nullptr, @@ -90,12 +90,12 @@ bool DualFilterEffect::processAudioBuffer( sampleFrame* buf, const fpp_t frames if( m_dfControls.m_filter1Model.isValueChanged() || m_filter1changed ) { - m_filter1->setFilterType( m_dfControls.m_filter1Model.value() ); + m_filter1->setFilterType( static_cast::FilterType>(m_dfControls.m_filter1Model.value()) ); m_filter1changed = true; } if( m_dfControls.m_filter2Model.isValueChanged() || m_filter2changed ) { - m_filter2->setFilterType( m_dfControls.m_filter2Model.value() ); + m_filter2->setFilterType( static_cast::FilterType>(m_dfControls.m_filter2Model.value()) ); m_filter2changed = true; } diff --git a/plugins/DualFilter/DualFilterControlDialog.cpp b/plugins/DualFilter/DualFilterControlDialog.cpp index 81a835901..a674a4a42 100644 --- a/plugins/DualFilter/DualFilterControlDialog.cpp +++ b/plugins/DualFilter/DualFilterControlDialog.cpp @@ -29,14 +29,13 @@ #include "Knob.h" #include "LedCheckBox.h" #include "ComboBox.h" -#include "gui_templates.h" namespace lmms::gui { #define makeknob( name, x, y, model, label, hint, unit ) \ - Knob * name = new Knob( knobBright_26, this); \ + Knob * name = new Knob( KnobType::Bright26, this); \ (name) -> move( x, y ); \ (name) ->setModel( &controls-> model ); \ (name) ->setLabel( label ); \ @@ -64,8 +63,8 @@ DualFilterControlDialog::DualFilterControlDialog( DualFilterControls* controls ) gain1Knob-> setVolumeKnob( true ); gain2Knob-> setVolumeKnob( true ); - auto enabled1Toggle = new LedCheckBox("", this, tr("Filter 1 enabled"), LedCheckBox::Green); - auto enabled2Toggle = new LedCheckBox("", this, tr("Filter 2 enabled"), LedCheckBox::Green); + auto enabled1Toggle = new LedCheckBox("", this, tr("Filter 1 enabled"), LedCheckBox::LedColor::Green); + auto enabled2Toggle = new LedCheckBox("", this, tr("Filter 2 enabled"), LedCheckBox::LedColor::Green); enabled1Toggle -> move( 12, 11 ); enabled1Toggle -> setModel( &controls -> m_enabled1Model ); @@ -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/DynamicsProcessor/DynamicsProcessor.cpp b/plugins/DynamicsProcessor/DynamicsProcessor.cpp index 54f1f0c50..583128bfb 100644 --- a/plugins/DynamicsProcessor/DynamicsProcessor.cpp +++ b/plugins/DynamicsProcessor/DynamicsProcessor.cpp @@ -47,7 +47,7 @@ Plugin::Descriptor PLUGIN_EXPORT dynamicsprocessor_plugin_descriptor = "plugin for processing dynamics in a flexible way" ), "Vesa Kivimäki ", 0x0100, - Plugin::Effect, + Plugin::Type::Effect, new PluginPixmapLoader("logo"), nullptr, nullptr, @@ -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 ) : @@ -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()->processingSampleRate()); } 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()->processingSampleRate()); } @@ -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(); @@ -155,31 +154,31 @@ 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 - switch( stereoMode ) + switch( static_cast(stereoMode) ) { - case DynProcControls::SM_Maximum: + case DynProcControls::StereoMode::Maximum: { sm_peak[0] = sm_peak[1] = qMax( m_currentPeak[0], m_currentPeak[1] ); break; } - case DynProcControls::SM_Average: + case DynProcControls::StereoMode::Average: { sm_peak[0] = sm_peak[1] = ( m_currentPeak[0] + m_currentPeak[1] ) * 0.5; break; } - case DynProcControls::SM_Unlinked: + case DynProcControls::StereoMode::Unlinked: { sm_peak[0] = m_currentPeak[0]; sm_peak[1] = m_currentPeak[1]; @@ -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/DynamicsProcessor/DynamicsProcessorControlDialog.cpp b/plugins/DynamicsProcessor/DynamicsProcessorControlDialog.cpp index 4d8758e03..bd076b946 100644 --- a/plugins/DynamicsProcessor/DynamicsProcessorControlDialog.cpp +++ b/plugins/DynamicsProcessor/DynamicsProcessorControlDialog.cpp @@ -47,7 +47,7 @@ DynProcControlDialog::DynProcControlDialog( setPalette( pal ); setFixedSize( 224, 319 ); - auto waveGraph = new Graph(this, Graph::LinearNonCyclicStyle, 204, 205); + auto waveGraph = new Graph(this, Graph::Style::LinearNonCyclic, 204, 205); waveGraph -> move( 10, 6 ); waveGraph -> setModel( &_controls -> m_wavegraphModel ); waveGraph -> setAutoFillBackground( true ); @@ -58,7 +58,7 @@ DynProcControlDialog::DynProcControlDialog( waveGraph->setGraphColor( QColor( 85, 204, 145 ) ); waveGraph -> setMaximumSize( 204, 205 ); - auto inputKnob = new Knob(knobBright_26, this); + auto inputKnob = new Knob(KnobType::Bright26, this); inputKnob -> setVolumeKnob( true ); inputKnob -> setVolumeRatio( 1.0 ); inputKnob -> move( 26, 223 ); @@ -66,7 +66,7 @@ DynProcControlDialog::DynProcControlDialog( inputKnob->setLabel( tr( "INPUT" ) ); inputKnob->setHintText( tr( "Input gain:" ) , "" ); - auto outputKnob = new Knob(knobBright_26, this); + auto outputKnob = new Knob(KnobType::Bright26, this); outputKnob -> setVolumeKnob( true ); outputKnob -> setVolumeRatio( 1.0 ); outputKnob -> move( 76, 223 ); @@ -74,13 +74,13 @@ DynProcControlDialog::DynProcControlDialog( outputKnob->setLabel( tr( "OUTPUT" ) ); outputKnob->setHintText( tr( "Output gain:" ) , "" ); - auto attackKnob = new Knob(knobBright_26, this); + auto attackKnob = new Knob(KnobType::Bright26, this); attackKnob -> move( 24, 268 ); attackKnob->setModel( &_controls->m_attackModel ); attackKnob->setLabel( tr( "ATTACK" ) ); attackKnob->setHintText( tr( "Peak attack time:" ) , "ms" ); - auto releaseKnob = new Knob(knobBright_26, this); + auto releaseKnob = new Knob(KnobType::Bright26, this); releaseKnob -> move( 74, 268 ); releaseKnob->setModel( &_controls->m_releaseModel ); releaseKnob->setLabel( tr( "RELEASE" ) ); diff --git a/plugins/DynamicsProcessor/DynamicsProcessorControls.h b/plugins/DynamicsProcessor/DynamicsProcessorControls.h index cbe109eaf..8fb66fee3 100644 --- a/plugins/DynamicsProcessor/DynamicsProcessorControls.h +++ b/plugins/DynamicsProcessor/DynamicsProcessorControls.h @@ -41,12 +41,11 @@ class DynProcControls : public EffectControls { Q_OBJECT public: - enum StereoModes + enum class StereoMode { - SM_Maximum, - SM_Average, - SM_Unlinked, - NumStereoModes + Maximum, + Average, + Unlinked }; DynProcControls( DynProcEffect * _eff ); ~DynProcControls() override = default; diff --git a/plugins/Eq/EqControls.cpp b/plugins/Eq/EqControls.cpp index ad1ffa2ac..04fb9bd3a 100644 --- a/plugins/Eq/EqControls.cpp +++ b/plugins/Eq/EqControls.cpp @@ -46,12 +46,12 @@ EqControls::EqControls( EqEffect *effect ) : m_para4GainModel( 0.0 , -18, 18, 0.001, this, tr( "Peak 4 gain" ) ), m_highShelfGainModel( 0.0 , -18, 18, 0.001, this, tr( "High-shelf gain" ) ), m_hpResModel( 0.707,0.003, 10.0 , 0.001, this, tr( "HP res" ) ), - m_lowShelfResModel( 1.4,0.55, 10.0 , 0.001, this , tr( "Low-shelf res" ) ), + m_lowShelfResModel( 0.707, 0.55, 10.0 , 0.001, this , tr( "Low-shelf res" ) ), m_para1BwModel( 0.3, 0.1, 4 , 0.001, this , tr( "Peak 1 BW" ) ), m_para2BwModel( 0.3, 0.1, 4 , 0.001, this , tr( "Peak 2 BW" ) ), m_para3BwModel( 0.3, 0.1, 4 , 0.001, this , tr( "Peak 3 BW" ) ), m_para4BwModel( 0.3, 0.1, 4 , 0.001, this , tr( "Peak 4 BW" ) ), - m_highShelfResModel( 1.4, 0.55, 10.0 , 0.001, this , tr( "High-shelf res" ) ), + m_highShelfResModel( 0.707, 0.55, 10.0 , 0.001, this , tr( "High-shelf res" ) ), m_lpResModel( 0.707,0.003, 10.0 , 0.001, this , tr( "LP res" ) ), m_hpFeqModel( 31.0, 20.0, 20000, 0.001, this , tr( "HP freq" ) ), m_lowShelfFreqModel( 80.0, 20.0, 20000, 0.001, this , tr( "Low-shelf freq" ) ), 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 6535b61f3..17de9ce98 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); @@ -106,14 +105,14 @@ EqControlsDialog::EqControlsDialog( EqControls *controls ) : distance = 81; for( int i = 0; i < m_parameterWidget->bandCount() ; i++ ) { - auto resKnob = new Knob(knobBright_26, this); + auto resKnob = new Knob(KnobType::Bright26, this); resKnob->move( distance, 440 ); resKnob->setVolumeKnob(false); resKnob->setModel( m_parameterWidget->getBandModels( i )->res ); if(i > 1 && i < 6) { resKnob->setHintText( tr( "Bandwidth: " ) , tr( " Octave" ) ); } else { resKnob->setHintText( tr( "Resonance : " ) , "" ); } - auto freqKnob = new Knob(knobBright_26, this); + auto freqKnob = new Knob(KnobType::Bright26, this); freqKnob->move( distance, 396 ); freqKnob->setVolumeKnob( false ); freqKnob->setModel( m_parameterWidget->getBandModels( i )->freq ); @@ -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 704231284..0493963b1 100644 --- a/plugins/Eq/EqCurve.cpp +++ b/plugins/Eq/EqCurve.cpp @@ -65,6 +65,7 @@ QRectF EqHandle::boundingRect() const float EqHandle::freqToXPixel( float freq , int w ) { + if (typeInfo::isEqual(freq, 0.0f)) { return 0.0f; } float min = log10f( 20 ); float max = log10f( 20000 ); float range = max - min; @@ -137,7 +138,7 @@ void EqHandle::paint( QPainter *painter, const QStyleOptionGraphicsItem *option, QRectF textRect2 = QRectF ( rectX+1, rectY+1, 80, 30 ); QString freq = QString::number( xPixelToFreq( EqHandle::x(), m_width ) ); QString res; - if ( getType() != para ) + if ( getType() != EqHandleType::Para ) { res = tr( "Reso: ") + QString::number( getResonance() ); } @@ -171,11 +172,11 @@ QPainterPath EqHandle::getCurvePath() float y = m_heigth * 0.5; for ( float x = 0 ; x < m_width; x++ ) { - if ( m_type == highpass ) y = getLowCutCurve( x ); - if ( m_type == lowshelf ) y = getLowShelfCurve( x ); - if ( m_type == para ) y = getPeakCurve( x ); - if ( m_type == highshelf ) y = getHighShelfCurve( x ); - if ( m_type == lowpass ) y = getHighCutCurve( x ); + if ( m_type == EqHandleType::HighPass ) y = getLowCutCurve( x ); + if ( m_type == EqHandleType::LowShelf ) y = getLowShelfCurve( x ); + if ( m_type == EqHandleType::Para ) y = getPeakCurve( x ); + if ( m_type == EqHandleType::HighShelf ) y = getHighShelfCurve( x ); + if ( m_type == EqHandleType::LowPass ) y = getHighCutCurve( x ); if ( x == 0 ) path.moveTo( x, y ); // sets the begin of Path path.lineTo( x, y ); } @@ -207,15 +208,14 @@ float EqHandle::getPeakCurve( float x ) 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; @@ -244,15 +244,15 @@ float EqHandle::getHighShelfCurve( float x ) 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; @@ -280,15 +280,14 @@ float EqHandle::getLowShelfCurve( float x ) 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; @@ -316,16 +315,15 @@ float EqHandle::getLowCutCurve( float x ) double c = cosf( w0 ); double s = sinf( w0 ); double resonance = getResonance(); - double A = pow( 10, yPixelToGain( EqHandle::y(), m_heigth, m_pixelsPerUnitHeight ) / 20); - double alpha = s / 2 * sqrt ( ( A +1/A ) * ( 1 / resonance -1 ) +2 ); - double a0, a1, a2, b0, b1, b2; // coeffs to calculate + double alpha = s / (2 * resonance); + + 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; - b0 = ( 1 + c ) * 0.5; - b1 = ( -( 1 + c ) ); - b2 = ( 1 + c ) * 0.5; - a0 = 1 + alpha; - a1 = ( -2 * c ); - a2 = 1 - alpha; //normalise b0 /= a0; b1 /= a0; @@ -360,16 +358,15 @@ float EqHandle::getHighCutCurve( float x ) double c = cosf( w0 ); double s = sinf( w0 ); double resonance = getResonance(); - double A = pow( 10, yPixelToGain( EqHandle::y(), m_heigth, m_pixelsPerUnitHeight ) / 20 ); - double alpha = s / 2 * sqrt ( ( A + 1 / A ) * ( 1 / resonance -1 ) +2 ); - double a0, a1, a2, b0, b1, b2; // coeffs to calculate + double alpha = s / (2 * resonance); + + 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; - b0 = ( 1 - c ) * 0.5; - b1 = 1 - c; - b2 = ( 1 - c ) * 0.5; - a0 = 1 + alpha; - a1 = -2 * c; - a2 = 1 - alpha; //normalise b0 /= a0; b1 /= a0; @@ -412,7 +409,7 @@ int EqHandle::getNum() -void EqHandle::setType( int t ) +void EqHandle::setType( EqHandleType t ) { EqHandle::m_type = t; } @@ -444,7 +441,7 @@ void EqHandle::setMouseHover( bool d ) -int EqHandle::getType() +EqHandleType EqHandle::getType() { return m_type; } @@ -570,16 +567,7 @@ void EqHandle::mouseReleaseEvent( QGraphicsSceneMouseEvent *event ) void EqHandle::wheelEvent( QGraphicsSceneWheelEvent *wevent ) { - float highestBandwich; - if( m_type != 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 ) @@ -633,7 +621,7 @@ QVariant EqHandle::itemChange( QGraphicsItem::GraphicsItemChange change, const Q if( change == ItemPositionChange ) { // pass filter don't move in y direction - if ( m_type == highpass || m_type == lowpass ) + if ( m_type == EqHandleType::HighPass || m_type == EqHandleType::LowPass ) { float newX = value.toPointF().x(); if( newX < 0 ) @@ -716,23 +704,23 @@ void EqCurve::paint( QPainter *painter, const QStyleOptionGraphicsItem *option, { for ( int x = 0; x < m_width ; x=x+1 ) { - if ( m_handle->at( thatHandle )->getType() == highpass ) + if ( m_handle->at( thatHandle )->getType() == EqHandleType::HighPass ) { mainCurve[x]= ( mainCurve[x] + ( m_handle->at( thatHandle )->getLowCutCurve( x ) * ( activeHandles ) ) - ( ( activeHandles * ( m_heigth/2 ) ) - m_heigth ) ); } - if ( m_handle->at(thatHandle)->getType() == lowshelf ) + if ( m_handle->at(thatHandle)->getType() == EqHandleType::LowShelf ) { mainCurve[x]= ( mainCurve[x] + ( m_handle->at( thatHandle )->getLowShelfCurve( x ) * ( activeHandles ) ) - ( ( activeHandles * ( m_heigth/2 ) ) - m_heigth ) ); } - if ( m_handle->at( thatHandle )->getType() == para ) + if ( m_handle->at( thatHandle )->getType() == EqHandleType::Para ) { mainCurve[x]= ( mainCurve[x] + ( m_handle->at( thatHandle )->getPeakCurve( x ) * ( activeHandles ) ) - ( ( activeHandles * ( m_heigth/2 ) ) - m_heigth ) ); } - if ( m_handle->at( thatHandle )->getType() == highshelf ) + if ( m_handle->at( thatHandle )->getType() == EqHandleType::HighShelf ) { mainCurve[x]= ( mainCurve[x] + ( m_handle->at( thatHandle )->getHighShelfCurve( x ) * ( activeHandles ) ) - ( ( activeHandles * ( m_heigth/2 ) ) - m_heigth ) ); } - if ( m_handle->at(thatHandle)->getType() == lowpass ) + if ( m_handle->at(thatHandle)->getType() == EqHandleType::LowPass ) { mainCurve[x]= ( mainCurve[x] + ( m_handle->at( thatHandle )->getHighCutCurve( x ) * ( activeHandles ) ) - ( ( activeHandles * ( m_heigth/2 ) ) - m_heigth ) ); } @@ -741,14 +729,18 @@ void EqCurve::paint( QPainter *painter, const QStyleOptionGraphicsItem *option, } //compute a QPainterPath m_curve = QPainterPath(); - for ( int x = 0; x < m_width ; x++ ) + //only draw the EQ curve if there are any activeHandles + if (activeHandles != 0) { - mainCurve[x] = ( ( mainCurve[x] / activeHandles ) ) - ( m_heigth/2 ); - if ( x==0 ) + for (int x = 0; x < m_width; x++) { - m_curve.moveTo( x, mainCurve[x] ); + mainCurve[x] = ((mainCurve[x] / activeHandles)) - (m_heigth / 2); + if (x == 0) + { + m_curve.moveTo(x, mainCurve[x]); + } + m_curve.lineTo(x, mainCurve[x]); } - m_curve.lineTo( x, mainCurve[x] ); } //we cache the curve painting in a pixmap for saving cpu QPixmap cacheMap( boundingRect().size().toSize() ); diff --git a/plugins/Eq/EqCurve.h b/plugins/Eq/EqCurve.h index e01e30841..27236aabb 100644 --- a/plugins/Eq/EqCurve.h +++ b/plugins/Eq/EqCurve.h @@ -32,12 +32,12 @@ namespace lmms::gui { -enum{ - highpass=1, - lowshelf, - para, - highshelf, - lowpass +enum class EqHandleType { + HighPass=1, + LowShelf, + Para, + HighShelf, + LowPass }; @@ -64,8 +64,8 @@ public: float getHighCutCurve( float x ); float getResonance(); int getNum(); - int getType(); - void setType( int t ); + EqHandleType getType(); + void setType( EqHandleType t ); void setResonance( float r ); bool isMouseHover(); void setMouseHover( bool d ); @@ -104,7 +104,8 @@ private: bool m_lp24; bool m_lp48; bool m_mouseHover; - int m_type, m_numb; + EqHandleType m_type; + int m_numb; float m_width, m_heigth; float m_resonance; bool m_mousePressed; diff --git a/plugins/Eq/EqEffect.cpp b/plugins/Eq/EqEffect.cpp index eb168a9f9..d8d2b2b29 100644 --- a/plugins/Eq/EqEffect.cpp +++ b/plugins/Eq/EqEffect.cpp @@ -44,7 +44,7 @@ Plugin::Descriptor PLUGIN_EXPORT eq_plugin_descriptor = QT_TRANSLATE_NOOP( "PluginBrowser", "A native eq plugin" ), "Dave French ", 0x0100, - Plugin::Effect, + Plugin::Type::Effect, new PluginPixmapLoader("logo"), nullptr, nullptr, @@ -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 fb60322a1..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( 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( 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( 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( 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( 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( 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( 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( 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..a90682b36 100644 --- a/plugins/Eq/EqSpectrumView.cpp +++ b/plugins/Eq/EqSpectrumView.cpp @@ -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/FlangerControlsDialog.cpp b/plugins/Flanger/FlangerControlsDialog.cpp index f35aabfd4..3ac5dc9c6 100644 --- a/plugins/Flanger/FlangerControlsDialog.cpp +++ b/plugins/Flanger/FlangerControlsDialog.cpp @@ -42,42 +42,42 @@ FlangerControlsDialog::FlangerControlsDialog( FlangerControls *controls ) : setPalette( pal ); setFixedSize( 233, 75 ); - auto delayKnob = new Knob(knobBright_26, this); + auto delayKnob = new Knob(KnobType::Bright26, this); delayKnob->move( 10,10 ); delayKnob->setVolumeKnob( false ); delayKnob->setModel( &controls->m_delayTimeModel ); delayKnob->setLabel( tr( "DELAY" ) ); delayKnob->setHintText( tr( "Delay time:" ) + " ", "s" ); - auto lfoFreqKnob = new TempoSyncKnob(knobBright_26, this); + auto lfoFreqKnob = new TempoSyncKnob(KnobType::Bright26, this); lfoFreqKnob->move( 48,10 ); lfoFreqKnob->setVolumeKnob( false ); lfoFreqKnob->setModel( &controls->m_lfoFrequencyModel ); lfoFreqKnob->setLabel( tr( "RATE" ) ); lfoFreqKnob->setHintText( tr( "Period:" ) , " Sec" ); - auto lfoAmtKnob = new Knob(knobBright_26, this); + auto lfoAmtKnob = new Knob(KnobType::Bright26, this); lfoAmtKnob->move( 85,10 ); lfoAmtKnob->setVolumeKnob( false ); lfoAmtKnob->setModel( &controls->m_lfoAmountModel ); lfoAmtKnob->setLabel( tr( "AMNT" ) ); lfoAmtKnob->setHintText( tr( "Amount:" ) , "" ); - auto lfoPhaseKnob = new Knob(knobBright_26, this); + auto lfoPhaseKnob = new Knob(KnobType::Bright26, this); lfoPhaseKnob->move( 123,10 ); lfoPhaseKnob->setVolumeKnob( false ); lfoPhaseKnob->setModel( &controls->m_lfoPhaseModel ); lfoPhaseKnob->setLabel( tr( "PHASE" ) ); lfoPhaseKnob->setHintText( tr( "Phase:" ) , " degrees" ); - auto feedbackKnob = new Knob(knobBright_26, this); + auto feedbackKnob = new Knob(KnobType::Bright26, this); feedbackKnob->move( 160,10 ); feedbackKnob->setVolumeKnob( true) ; feedbackKnob->setModel( &controls->m_feedbackModel ); feedbackKnob->setLabel( tr( "FDBK" ) ); feedbackKnob->setHintText( tr( "Feedback amount:" ) , "" ); - auto whiteNoiseKnob = new Knob(knobBright_26, this); + auto whiteNoiseKnob = new Knob(KnobType::Bright26, this); whiteNoiseKnob->move( 196,10 ); whiteNoiseKnob->setVolumeKnob( true) ; whiteNoiseKnob->setModel( &controls->m_whiteNoiseAmountModel ); diff --git a/plugins/Flanger/FlangerEffect.cpp b/plugins/Flanger/FlangerEffect.cpp index 9f7200f0e..ddba0cb77 100644 --- a/plugins/Flanger/FlangerEffect.cpp +++ b/plugins/Flanger/FlangerEffect.cpp @@ -45,7 +45,7 @@ Plugin::Descriptor PLUGIN_EXPORT flanger_plugin_descriptor = QT_TRANSLATE_NOOP( "PluginBrowser", "A native flanger plugin" ), "Dave French ", 0x0100, - Plugin::Effect, + Plugin::Type::Effect, new PluginPixmapLoader("logo"), nullptr, nullptr, @@ -108,10 +108,11 @@ bool FlangerEffect::processAudioBuffer( sampleFrame *buf, const fpp_t frames ) 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]; diff --git a/plugins/FreeBoy/CMakeLists.txt b/plugins/FreeBoy/CMakeLists.txt index fb5093ee0..485ed3cc2 100644 --- a/plugins/FreeBoy/CMakeLists.txt +++ b/plugins/FreeBoy/CMakeLists.txt @@ -4,8 +4,8 @@ INCLUDE_DIRECTORIES(game-music-emu/gme) BUILD_PLUGIN(freeboy FreeBoy.cpp FreeBoy.h - Gb_Apu_Buffer.cpp - Gb_Apu_Buffer.h + GbApuWrapper.cpp + GbApuWrapper.h game-music-emu/gme/Gb_Apu.cpp game-music-emu/gme/Gb_Apu.h game-music-emu/gme/Gb_Oscs.cpp diff --git a/plugins/FreeBoy/FreeBoy.cpp b/plugins/FreeBoy/FreeBoy.cpp index 0d639d3a6..f2dc95699 100644 --- a/plugins/FreeBoy/FreeBoy.cpp +++ b/plugins/FreeBoy/FreeBoy.cpp @@ -2,7 +2,7 @@ * FreeBoy.cpp - GameBoy papu based instrument * * Copyright (c) 2008 Attila Herman - * Csaba Hruska + * Csaba Hruska * * This file is part of LMMS - https://lmms.io * @@ -23,11 +23,12 @@ * */ -#include - -#include #include "FreeBoy.h" -#include "Gb_Apu_Buffer.h" + +#include +#include + +#include "GbApuWrapper.h" #include "base64.h" #include "InstrumentTrack.h" #include "Knob.h" @@ -45,8 +46,11 @@ namespace lmms { -const blip_time_t FRAME_LENGTH = 70224; -const long CLOCK_RATE = 4194304; +namespace +{ +constexpr blip_time_t FRAME_LENGTH = 70224; +constexpr long CLOCK_RATE = 4194304; +} extern "C" { @@ -59,7 +63,7 @@ Plugin::Descriptor PLUGIN_EXPORT freeboy_plugin_descriptor = "Attila Herman " "Csaba Hruska ", 0x0100, - Plugin::Instrument, + Plugin::Type::Instrument, new PluginPixmapLoader( "logo" ), nullptr, } ; @@ -118,9 +122,7 @@ FreeBoyInstrument::FreeBoyInstrument( InstrumentTrack * _instrument_track ) : m_trebleModel( -20.0f, -100.0f, 200.0f, 1.0f, this, tr( "Treble" ) ), m_bassModel( 461.0f, -1.0f, 600.0f, 1.0f, this, tr( "Bass" ) ), - m_graphModel( 0, 15, 32, this, false, 1 ), - - m_time(0) + m_graphModel( 0, 15, 32, this, false, 1 ) { } @@ -238,189 +240,192 @@ f_cnt_t FreeBoyInstrument::desiredReleaseFrames() const -void FreeBoyInstrument::playNote( NotePlayHandle * _n, - sampleFrame * _working_buffer ) +void FreeBoyInstrument::playNote(NotePlayHandle* nph, sampleFrame* workingBuffer) { - const f_cnt_t tfp = _n->totalFramesPlayed(); + const f_cnt_t tfp = nph->totalFramesPlayed(); const int samplerate = Engine::audioEngine()->processingSampleRate(); - const fpp_t frames = _n->framesLeftForCurrentPeriod(); - const f_cnt_t offset = _n->noteOffset(); + const fpp_t frames = nph->framesLeftForCurrentPeriod(); + const f_cnt_t offset = nph->noteOffset(); int data = 0; - int freq = _n->frequency(); + int freq = nph->frequency(); - if ( tfp == 0 ) + if (!nph->m_pluginData) { - auto papu = new Gb_Apu_Buffer(); - papu->set_sample_rate( samplerate, CLOCK_RATE ); + auto papu = new GbApuWrapper{}; + papu->setSampleRate(samplerate, CLOCK_RATE); // Master sound circuitry power control - papu->write_register( fakeClock(), 0xff26, 0x80 ); + papu->writeRegister(0xff26, 0x80); data = m_ch1VolumeModel.value(); - data = data<<1; + data = data << 1; data += m_ch1VolSweepDirModel.value(); - data = data<<3; + data = data << 3; data += m_ch1SweepStepLengthModel.value(); - papu->write_register( fakeClock(), 0xff12, data ); + papu->writeRegister(0xff12, data); data = m_ch2VolumeModel.value(); - data = data<<1; + data = data << 1; data += m_ch2VolSweepDirModel.value(); - data = data<<3; + data = data << 3; data += m_ch2SweepStepLengthModel.value(); - papu->write_register( fakeClock(), 0xff17, data ); + papu->writeRegister(0xff17, data); //channel 4 - noise data = m_ch4VolumeModel.value(); - data = data<<1; + data = data << 1; data += m_ch4VolSweepDirModel.value(); - data = data<<3; + data = data << 3; data += m_ch4SweepStepLengthModel.value(); - papu->write_register( fakeClock(), 0xff21, data ); + papu->writeRegister(0xff21, data); - _n->m_pluginData = papu; + nph->m_pluginData = papu; } - auto papu = static_cast(_n->m_pluginData); + auto papu = static_cast(nph->m_pluginData); - papu->treble_eq( m_trebleModel.value() ); - papu->bass_freq( m_bassModel.value() ); + papu->trebleEq(m_trebleModel.value()); + papu->bassFreq(m_bassModel.value()); //channel 1 - square data = m_ch1SweepTimeModel.value(); - data = data<<1; + data = data << 1; data += m_ch1SweepDirModel.value(); data = data << 3; data += m_ch1SweepRtShiftModel.value(); - papu->write_register( fakeClock(), 0xff10, data ); + papu->writeRegister(0xff10, data); data = m_ch1WavePatternDutyModel.value(); - data = data<<6; - papu->write_register( fakeClock(), 0xff11, data ); - + data = data << 6; + papu->writeRegister(0xff11, data); //channel 2 - square data = m_ch2WavePatternDutyModel.value(); - data = data<<6; - papu->write_register( fakeClock(), 0xff16, data ); - + data = data << 6; + papu->writeRegister(0xff16, data); //channel 3 - wave - //data = m_ch3OnModel.value()?128:0; + //data = m_ch3OnModel.value() ? 128 : 0; data = 128; - papu->write_register( fakeClock(), 0xff1a, data ); + papu->writeRegister(0xff1a, data); auto ch3voldata = std::array{0, 3, 2, 1}; data = ch3voldata[(int)m_ch3VolumeModel.value()]; - data = data<<5; - papu->write_register( fakeClock(), 0xff1c, data ); - + data = data << 5; + papu->writeRegister(0xff1c, data); //controls data = m_so1VolumeModel.value(); - data = data<<4; + data = data << 4; data += m_so2VolumeModel.value(); - papu->write_register( fakeClock(), 0xff24, data ); + papu->writeRegister(0xff24, data); - data = m_ch4So2Model.value()?128:0; - data += m_ch3So2Model.value()?64:0; - data += m_ch2So2Model.value()?32:0; - data += m_ch1So2Model.value()?16:0; - data += m_ch4So1Model.value()?8:0; - data += m_ch3So1Model.value()?4:0; - data += m_ch2So1Model.value()?2:0; - data += m_ch1So1Model.value()?1:0; - papu->write_register( fakeClock(), 0xff25, data ); + data = m_ch4So2Model.value() ? 128 : 0; + data += m_ch3So2Model.value() ? 64 : 0; + data += m_ch2So2Model.value() ? 32 : 0; + data += m_ch1So2Model.value() ? 16 : 0; + data += m_ch4So1Model.value() ? 8 : 0; + data += m_ch3So1Model.value() ? 4 : 0; + data += m_ch2So1Model.value() ? 2 : 0; + data += m_ch1So1Model.value() ? 1 : 0; + papu->writeRegister(0xff25, data); - const float * wpm = m_graphModel.samples(); + const float* wpm = m_graphModel.samples(); - for( char i=0; i<16; i++ ) + for (char i = 0; i < 16; ++i) { - data = (int)floor(wpm[i*2]) << 4; - data += (int)floor(wpm[i*2+1]); - papu->write_register( fakeClock(), 0xff30 + i, data ); + data = static_cast(std::floor(wpm[i * 2])) << 4; + data += static_cast(std::floor(wpm[(i * 2) + 1])); + papu->writeRegister(0xff30 + i, data); } - if( ( freq >= 65 ) && ( freq <=4000 ) ) + if ((freq >= 65) && (freq <= 4000)) { - int initflag = (tfp==0)?128:0; - // Hz = 4194304 / ( ( 2048 - ( 11-bit-freq ) ) << 5 ) - data = 2048 - ( ( 4194304 / freq )>>5 ); - if( tfp==0 ) + int initFlag = (tfp == 0) ? 128 : 0; + // Hz = 4194304 / ((2048 - (11-bit-freq)) << 5) + data = 2048 - ((4194304 / freq) >> 5); + if (tfp == 0) { - papu->write_register( fakeClock(), 0xff13, data & 0xff ); - papu->write_register( fakeClock(), 0xff14, (data>>8) | initflag ); + papu->writeRegister(0xff13, data & 0xff); + papu->writeRegister(0xff14, (data >> 8) | initFlag); } - papu->write_register( fakeClock(), 0xff18, data & 0xff ); - papu->write_register( fakeClock(), 0xff19, (data>>8) | initflag ); - papu->write_register( fakeClock(), 0xff1d, data & 0xff ); - papu->write_register( fakeClock(), 0xff1e, (data>>8) | initflag ); + papu->writeRegister(0xff18, data & 0xff); + papu->writeRegister(0xff19, (data >> 8) | initFlag); + papu->writeRegister(0xff1d, data & 0xff); + papu->writeRegister(0xff1e, (data >> 8) | initFlag); } - if( tfp == 0 ) + if (tfp == 0) { - //PRNG Frequency = (1048576 Hz / (ratio + 1)) / 2 ^ (shiftclockfreq + 1) - char sopt=0; - char ropt=1; - float fopt = 524288.0 / ( ropt * pow( 2.0, sopt + 1.0 ) ); - float f; - for ( char s=0; s<16; s++ ) - for ( char r=0; r<8; r++ ) { - f = 524288.0 / ( r * pow( 2.0, s + 1.0 ) ); - if( fabs( freq-fopt ) > fabs( freq-f ) ) { - fopt = f; - ropt = r; - sopt = s; + // Initialize noise channel... + // PRNG Frequency = (1048576 Hz / (ratio + 1)) / 2 ^ (shiftclockfreq + 1) + // When div_ratio = 0 the ratio should be 0.5. Since s = 0 is the only case where r = 0 gives + // a unique frequency, we can start by guessing s = r = 0 here and then skip r = 0 in the loop. + char clock_freq = 0; + char div_ratio = 0; + float closest_freq = 524288.0 / (0.5 * std::pow(2.0, clock_freq + 1.0)); + // This nested for loop iterates over all possible combinations of clock frequency and dividing + // ratio and chooses the combination whose resulting frequency is closest to the note frequency + for (char s = 0; s < 16; ++s) + { + for (char r = 1; r < 8; ++r) + { + float f = 524288.0 / (r * std::pow(2.0, s + 1.0)); + if (std::fabs(freq - closest_freq) > std::fabs(freq - f)) + { + closest_freq = f; + div_ratio = r; + clock_freq = s; + } } } - data = sopt; + + data = clock_freq; data = data << 1; data += m_ch4ShiftRegWidthModel.value(); data = data << 3; - data += ropt; - papu->write_register( fakeClock(), 0xff22, data ); + data += div_ratio; + papu->writeRegister(0xff22, data); //channel 4 init - papu->write_register( fakeClock(), 0xff23, 128 ); + papu->writeRegister(0xff23, 128); } - int const buf_size = 2048; - int framesleft = frames; - int datalen = 0; - auto buf = std::array{}; - while( framesleft > 0 ) + constexpr int bufSize = 2048; + int framesLeft = frames; + int dataLen = 0; + auto buf = std::array{}; + while (framesLeft > 0) { - int avail = papu->samples_avail(); - if( avail <= 0 ) + int avail = papu->samplesAvail(); + if (avail <= 0) { - m_time = 0; - papu->end_frame(FRAME_LENGTH); - avail = papu->samples_avail(); + papu->endFrame(FRAME_LENGTH); + avail = papu->samplesAvail(); } - datalen = framesleft>avail?avail:framesleft; - datalen = datalen>buf_size?buf_size:datalen; + dataLen = framesLeft > avail ? avail : framesLeft; + dataLen = dataLen > bufSize ? bufSize : dataLen; - long count = papu->read_samples(buf.data(), datalen * 2) / 2; + long count = papu->readSamples(buf.data(), dataLen * 2) / 2; - for( fpp_t frame = 0; frame < count; ++frame ) + for (fpp_t frame = 0; frame < count; ++frame) { - for( ch_cnt_t ch = 0; ch < DEFAULT_CHANNELS; ++ch ) + for (ch_cnt_t ch = 0; ch < DEFAULT_CHANNELS; ++ch) { - sample_t s = float(buf[frame*2+ch])/32768.0; - _working_buffer[frames-framesleft+frame+offset][ch] = s; + sample_t s = static_cast(buf[(frame * 2) + ch]) / 32768.0f; + workingBuffer[frames - framesLeft + frame + offset][ch] = s; } } - framesleft -= count; + framesLeft -= count; } - instrumentTrack()->processAudioBuffer( _working_buffer, frames + offset, _n ); } -void FreeBoyInstrument::deleteNotePluginData( NotePlayHandle * _n ) +void FreeBoyInstrument::deleteNotePluginData(NotePlayHandle* nph) { - delete static_cast( _n->m_pluginData ); + delete static_cast(nph->m_pluginData); } @@ -440,7 +445,7 @@ class FreeBoyKnob : public Knob { public: FreeBoyKnob( QWidget * _parent ) : - Knob( knobStyled, _parent ) + Knob( KnobType::Styled, _parent ) { setFixedSize( 30, 30 ); setCenterPointX( 15.0 ); @@ -671,7 +676,7 @@ FreeBoyInstrumentView::FreeBoyInstrumentView( Instrument * _instrument, m_graph = new Graph( this ); - m_graph->setGraphStyle( Graph::NearestStyle ); + m_graph->setGraphStyle( Graph::Style::Nearest ); m_graph->setGraphColor( QColor(0x4E, 0x83, 0x2B) ); m_graph->move( 37, 199 ); m_graph->resize(208, 47); @@ -736,4 +741,4 @@ PLUGIN_EXPORT Plugin * lmms_plugin_main( Model *m, void * ) } -} // namespace lmms \ No newline at end of file +} // namespace lmms diff --git a/plugins/FreeBoy/FreeBoy.h b/plugins/FreeBoy/FreeBoy.h index aecd2b188..747305414 100644 --- a/plugins/FreeBoy/FreeBoy.h +++ b/plugins/FreeBoy/FreeBoy.h @@ -1,8 +1,8 @@ /* - * FreeBoyInstrument.h - GameBoy papu based instrument + * FreeBoy.h - GameBoy papu based instrument * - * Copyright (c) 2008 - * Csaba Hruska + * Copyright (c) 2008 Attila Herman + * Csaba Hruska * * This file is part of LMMS - https://lmms.io * @@ -23,8 +23,8 @@ * */ -#ifndef FREEBOY_H -#define FREEBOY_H +#ifndef LMMS_FREEBOY_H +#define LMMS_FREEBOY_H #include "AutomatableModel.h" #include "Blip_Buffer.h" @@ -54,10 +54,8 @@ public: FreeBoyInstrument( InstrumentTrack * _instrument_track ); ~FreeBoyInstrument() override = default; - void playNote( NotePlayHandle * _n, - sampleFrame * _working_buffer ) override; - void deleteNotePluginData( NotePlayHandle * _n ) override; - + void playNote(NotePlayHandle* nph, sampleFrame* workingBuffer) override; + void deleteNotePluginData(NotePlayHandle* nph) override; void saveSettings( QDomDocument & _doc, QDomElement & _parent ) override; void loadSettings( const QDomElement & _this ) override; @@ -112,12 +110,8 @@ private: graphModel m_graphModel; - // Fake CPU timing - blip_time_t m_time; - blip_time_t fakeClock() { return m_time += 4; } - friend class gui::FreeBoyInstrumentView; -} ; +}; namespace gui @@ -172,11 +166,11 @@ private: /*protected slots: void updateKnobHint(); void updateKnobToolTip();*/ -} ; +}; } // namespace gui } // namespace lmms -#endif +#endif // LMMS_FREEBOY_H diff --git a/plugins/FreeBoy/Gb_Apu_Buffer.cpp b/plugins/FreeBoy/GbApuWrapper.cpp similarity index 57% rename from plugins/FreeBoy/Gb_Apu_Buffer.cpp rename to plugins/FreeBoy/GbApuWrapper.cpp index ec1a36479..790cf96e2 100644 --- a/plugins/FreeBoy/Gb_Apu_Buffer.cpp +++ b/plugins/FreeBoy/GbApuWrapper.cpp @@ -1,7 +1,7 @@ /* - * Gb_Apu_Buffer.cpp - Gb_Apu subclass which allows direct buffer access + * GbApuWrapper.cpp - Gb_Apu subclass which allows direct buffer access * Copyright (c) 2017 Tres Finocchiaro - * + * * This file is part of LMMS - https://lmms.io * * This program is free software; you can redistribute it and/or @@ -20,37 +20,45 @@ * Boston, MA 02110-1301 USA. * */ -#include "Gb_Apu_Buffer.h" + +#include "GbApuWrapper.h" namespace lmms { -void Gb_Apu_Buffer::end_frame(blip_time_t end_time) { - Gb_Apu::end_frame(end_time); - m_buf.end_frame(end_time); +// Sets specified sample rate and clock rate in Stereo_Buffer +blargg_err_t GbApuWrapper::setSampleRate(long sampleRate, long clockRate) +{ + Gb_Apu::output(m_buf.center(), m_buf.left(), m_buf.right()); + m_buf.clock_rate(clockRate); + return m_buf.set_sample_rate(sampleRate); } -// Sets specified sample rate and clock rate in Multi_Buffer -blargg_err_t Gb_Apu_Buffer::set_sample_rate(long sample_rate, long clock_rate) { - Gb_Apu_Buffer::output(m_buf.center(), m_buf.left(), m_buf.right()); - m_buf.clock_rate(clock_rate); - return m_buf.set_sample_rate(sample_rate); -} - -// Wrap Multi_Buffer::samples_avail() -long Gb_Apu_Buffer::samples_avail() const { +// Wrap Stereo_Buffer::samples_avail() +long GbApuWrapper::samplesAvail() const +{ return m_buf.samples_avail(); } -// Wrap Multi_Buffer::read_samples(...) -long Gb_Apu_Buffer::read_samples(sample_t* out, long count) { +// Wrap Stereo_Buffer::read_samples(...) +long GbApuWrapper::readSamples(blip_sample_t* out, long count) +{ return m_buf.read_samples(out, count); } -void Gb_Apu_Buffer::bass_freq(int freq) { +// Wrap Stereo_Buffer::bass_freq(...) +void GbApuWrapper::bassFreq(int freq) +{ m_buf.bass_freq(freq); } +void GbApuWrapper::endFrame(blip_time_t endTime) +{ + m_time = 0; + Gb_Apu::end_frame(endTime); + m_buf.end_frame(endTime); +} + } // namespace lmms diff --git a/plugins/FreeBoy/Gb_Apu_Buffer.h b/plugins/FreeBoy/GbApuWrapper.h similarity index 58% rename from plugins/FreeBoy/Gb_Apu_Buffer.h rename to plugins/FreeBoy/GbApuWrapper.h index 760e0920d..3b95869d5 100644 --- a/plugins/FreeBoy/Gb_Apu_Buffer.h +++ b/plugins/FreeBoy/GbApuWrapper.h @@ -1,7 +1,7 @@ /* - * Gb_Apu_Buffer.cpp - Gb_Apu subclass which allows direct buffer access + * GbApuWrapper.h - Gb_Apu subclass which allows direct buffer access * Copyright (c) 2017 Tres Finocchiaro - * + * * This file is part of LMMS - https://lmms.io * * This program is free software; you can redistribute it and/or @@ -20,36 +20,40 @@ * Boston, MA 02110-1301 USA. * */ -#ifndef GB_APU_BUFFER_H -#define GB_APU_BUFFER_H + +#ifndef LMMS_GB_APU_WRAPPER_H +#define LMMS_GB_APU_WRAPPER_H #include "Gb_Apu.h" #include "Multi_Buffer.h" -#include "MemoryManager.h" namespace lmms { -class Gb_Apu_Buffer : public Gb_Apu { - MM_OPERATORS +class GbApuWrapper : private Gb_Apu +{ public: - Gb_Apu_Buffer() = default; - ~Gb_Apu_Buffer() = default; + GbApuWrapper() = default; + ~GbApuWrapper() = default; - void end_frame(blip_time_t); + blargg_err_t setSampleRate(long sampleRate, long clockRate); + void writeRegister(unsigned addr, int data) { Gb_Apu::write_register(fakeClock(), addr, data); } + long samplesAvail() const; + long readSamples(blip_sample_t* out, long count); + void trebleEq(const blip_eq_t& eq) { Gb_Apu::treble_eq(eq); } + void bassFreq(int freq); + void endFrame(blip_time_t endTime); - blargg_err_t set_sample_rate(long sample_rate, long clock_rate); - long samples_avail() const; - using sample_t = blip_sample_t; - long read_samples(sample_t* out, long count); - void bass_freq(int freq); private: Stereo_Buffer m_buf; + + // Fake CPU timing + blip_time_t fakeClock() { return m_time += 4; } + blip_time_t m_time = 0; }; } // namespace lmms -#endif - +#endif // LMMS_GB_APU_WRAPPER_H diff --git a/plugins/GigPlayer/CMakeLists.txt b/plugins/GigPlayer/CMakeLists.txt index 24db813bd..7b634b605 100644 --- a/plugins/GigPlayer/CMakeLists.txt +++ b/plugins/GigPlayer/CMakeLists.txt @@ -12,8 +12,13 @@ if(LMMS_HAVE_GIG) add_definitions(${GCC_GIG_COMPILE_FLAGS}) endif(LMMS_BUILD_WIN32) - LINK_DIRECTORIES(${GIG_LIBRARY_DIRS} ${SAMPLERATE_LIBRARY_DIRS}) - LINK_LIBRARIES(${GIG_LIBRARIES} ${SAMPLERATE_LIBRARIES}) - BUILD_PLUGIN(gigplayer GigPlayer.cpp GigPlayer.h PatchesDialog.cpp PatchesDialog.h PatchesDialog.ui MOCFILES GigPlayer.h PatchesDialog.h UICFILES PatchesDialog.ui EMBEDDED_RESOURCES "${CMAKE_CURRENT_SOURCE_DIR}/*.png") + link_directories(${GIG_LIBRARY_DIRS}) + link_libraries(${GIG_LIBRARIES}) + build_plugin(gigplayer + GigPlayer.cpp GigPlayer.h PatchesDialog.cpp PatchesDialog.h PatchesDialog.ui + MOCFILES GigPlayer.h PatchesDialog.h + UICFILES PatchesDialog.ui + EMBEDDED_RESOURCES "${CMAKE_CURRENT_SOURCE_DIR}/*.png" + ) + target_link_libraries(gigplayer SampleRate::samplerate) endif(LMMS_HAVE_GIG) - diff --git a/plugins/GigPlayer/GigPlayer.cpp b/plugins/GigPlayer/GigPlayer.cpp index 24073ceea..2d67f0ddf 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" @@ -69,7 +69,7 @@ Plugin::Descriptor PLUGIN_EXPORT gigplayer_plugin_descriptor = QT_TRANSLATE_NOOP( "PluginBrowser", "Player for GIG files" ), "Garrett Wilson ", 0x0100, - Plugin::Instrument, + Plugin::Type::Instrument, new PluginPixmapLoader( "logo" ), "gig", nullptr, @@ -108,8 +108,8 @@ GigInstrument::GigInstrument( InstrumentTrack * _instrument_track ) : GigInstrument::~GigInstrument() { Engine::audioEngine()->removePlayHandlesOfTypes( instrumentTrack(), - PlayHandle::TypeNotePlayHandle - | PlayHandle::TypeInstrumentPlayHandle ); + PlayHandle::Type::NotePlayHandle + | PlayHandle::Type::InstrumentPlayHandle ); freeInstance(); } @@ -293,8 +293,6 @@ void GigInstrument::playNote( NotePlayHandle * _n, sampleFrame * ) { const float LOG440 = 2.643452676f; - const f_cnt_t tfp = _n->totalFramesPlayed(); - int midiNote = (int) floor( 12.0 * ( log2( _n->unpitchedFrequency() ) - LOG440 ) - 4.0 ); // out of range? @@ -303,7 +301,7 @@ void GigInstrument::playNote( NotePlayHandle * _n, sampleFrame * ) return; } - if( tfp == 0 ) + if (!_n->m_pluginData) { auto pluginData = new GIGPluginData; pluginData->midiNote = midiNote; @@ -343,16 +341,16 @@ void GigInstrument::play( sampleFrame * _working_buffer ) for( QList::iterator it = m_notes.begin(); it != m_notes.end(); ++it ) { // Process notes in the KeyUp state, adding release samples if desired - if( it->state == KeyUp ) + if( it->state == GigState::KeyUp ) { // If there are no samples, we're done if( it->samples.empty() ) { - it->state = Completed; + it->state = GigState::Completed; } else { - it->state = PlayingKeyUp; + it->state = GigState::PlayingKeyUp; // Notify each sample that the key has been released for (auto& sample : it->samples) @@ -368,9 +366,9 @@ void GigInstrument::play( sampleFrame * _working_buffer ) } } // Process notes in the KeyDown state, adding samples for the notes - else if( it->state == KeyDown ) + else if( it->state == GigState::KeyDown ) { - it->state = PlayingKeyDown; + it->state = GigState::PlayingKeyDown; addSamples( *it, false ); } @@ -395,7 +393,7 @@ void GigInstrument::play( sampleFrame * _working_buffer ) } // Delete ended notes (either in the completed state or all the samples ended) - if( it->state == Completed || it->samples.empty() ) + if( it->state == GigState::Completed || it->samples.empty() ) { it = m_notes.erase( it ); @@ -410,7 +408,7 @@ void GigInstrument::play( sampleFrame * _working_buffer ) for (auto& note : m_notes) { // Only process the notes if we're in a playing state - if (!(note.state == PlayingKeyDown || note.state == PlayingKeyUp )) + if (!(note.state == GigState::PlayingKeyDown || note.state == GigState::PlayingKeyUp )) { continue; } @@ -439,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 @@ -496,8 +494,6 @@ void GigInstrument::play( sampleFrame * _working_buffer ) _working_buffer[i][0] *= m_gain.value(); _working_buffer[i][1] *= m_gain.value(); } - - instrumentTrack()->processAudioBuffer( _working_buffer, frames, nullptr ); } @@ -682,9 +678,9 @@ void GigInstrument::deleteNotePluginData( NotePlayHandle * _n ) for (auto& note : m_notes) { // Find the note by matching pointers to the plugin data - if (note.handle == pluginData && (note.state == KeyDown || note.state == PlayingKeyDown)) + if (note.handle == pluginData && (note.state == GigState::KeyDown || note.state == GigState::PlayingKeyDown)) { - note.state = KeyUp; + note.state = GigState::KeyUp; } } @@ -908,7 +904,7 @@ class gigKnob : public Knob { public: gigKnob( QWidget * _parent ) : - Knob( knobBright_26, _parent ) + Knob( KnobType::Bright26, _parent ) { setFixedSize( 31, 38 ); } diff --git a/plugins/GigPlayer/GigPlayer.h b/plugins/GigPlayer/GigPlayer.h index 20058424b..986018654 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" @@ -187,7 +186,7 @@ public: // What portion of a note are we in? -enum GigState +enum class GigState { // We just pressed the key KeyDown, @@ -224,7 +223,7 @@ public: GigNote( int midiNote, int velocity, float frequency, GIGPluginData * handle ) : midiNote( midiNote ), velocity( velocity ), - release( false ), isRelease( false ), state( KeyDown ), + release( false ), isRelease( false ), state( GigState::KeyDown ), frequency( frequency ), handle( handle ) { } @@ -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 ); @@ -268,7 +266,7 @@ public: Flags flags() const override { - return IsSingleStreamed|IsNotBendable; + return Flag::IsSingleStreamed | Flag::IsNotBendable; } gui::PluginView* instantiateView( QWidget * _parent ) override; diff --git a/plugins/HydrogenImport/HydrogenImport.cpp b/plugins/HydrogenImport/HydrogenImport.cpp index d8f76071a..144a2f5e7 100644 --- a/plugins/HydrogenImport/HydrogenImport.cpp +++ b/plugins/HydrogenImport/HydrogenImport.cpp @@ -30,7 +30,7 @@ Plugin::Descriptor PLUGIN_EXPORT hydrogenimport_plugin_descriptor = "Filter for importing Hydrogen files into LMMS" ), "frank mather", 0x0100, - Plugin::ImportFilter, + Plugin::Type::ImportFilter, nullptr, nullptr, nullptr, @@ -42,7 +42,7 @@ QString filename; class NoteKey { public: - enum Key { + enum class Key { C = 0, Cs, D, @@ -59,7 +59,7 @@ public: static int stringToNoteKey( const QString& str ) { - int m_key = NoteKey::C; + auto m_key = Key::C; QString sKey = str.left( str.length() - 1 ); @@ -74,54 +74,54 @@ public: if ( sKey == "C" ) { - m_key = NoteKey::C; + m_key = Key::C; } else if ( sKey == "Cs" ) { - m_key = NoteKey::Cs; + m_key = Key::Cs; } else if ( sKey == "D" ) { - m_key = NoteKey::D; + m_key = Key::D; } else if ( sKey == "Ef" ) { - m_key = NoteKey::Ef; + m_key = Key::Ef; } else if ( sKey == "E" ) { - m_key = NoteKey::E; + m_key = Key::E; } else if ( sKey == "F" ) { - m_key = NoteKey::F; + m_key = Key::F; } else if ( sKey == "Fs" ) { - m_key = NoteKey::Fs; + m_key = Key::Fs; } else if ( sKey == "G" ) { - m_key = NoteKey::G; + m_key = Key::G; } else if ( sKey == "Af" ) { - m_key = NoteKey::Af; + m_key = Key::Af; } else if ( sKey == "A" ) { - m_key = NoteKey::A; + m_key = Key::A; } else if ( sKey == "Bf" ) { - m_key = NoteKey::Bf; + m_key = Key::Bf; } else if ( sKey == "B" ) { - m_key = NoteKey::B; + m_key = Key::B; } // Hydrogen records MIDI notes from C-1 to B5, and exports them as a number ranging from -3 to 3 - return m_key + ((nOctave + 3) * 12); + return static_cast(m_key) + ((nOctave + 3) * 12); } }; @@ -218,7 +218,7 @@ bool HydrogenImport::readSong() if ( nLayer == 0 ) { drum_track[sId] = static_cast( - Track::create(Track::InstrumentTrack, Engine::patternStore()) + Track::create(Track::Type::Instrument, Engine::patternStore()) ); drum_track[sId]->volumeModel()->setValue( fVolume * 100 ); drum_track[sId]->panningModel()->setValue( ( fPan_R - fPan_L ) * 100 ); diff --git a/plugins/Kicker/Kicker.cpp b/plugins/Kicker/Kicker.cpp index 7d4c6fbb0..e6418e2da 100644 --- a/plugins/Kicker/Kicker.cpp +++ b/plugins/Kicker/Kicker.cpp @@ -23,11 +23,10 @@ * */ - +#include "Kicker.h" #include -#include "Kicker.h" #include "AudioEngine.h" #include "Engine.h" #include "InstrumentTrack.h" @@ -55,7 +54,7 @@ Plugin::Descriptor PLUGIN_EXPORT kicker_plugin_descriptor = "Versatile drum synthesizer" ), "Tobias Doerffel ", 0x0100, - Plugin::Instrument, + Plugin::Type::Instrument, new PluginPixmapLoader( "logo" ), nullptr, nullptr, @@ -85,65 +84,64 @@ KickerInstrument::KickerInstrument( InstrumentTrack * _instrument_track ) : -void KickerInstrument::saveSettings( QDomDocument & _doc, - QDomElement & _this ) +void KickerInstrument::saveSettings(QDomDocument& doc, QDomElement& elem) { - m_startFreqModel.saveSettings( _doc, _this, "startfreq" ); - m_endFreqModel.saveSettings( _doc, _this, "endfreq" ); - m_decayModel.saveSettings( _doc, _this, "decay" ); - m_distModel.saveSettings( _doc, _this, "dist" ); - m_distEndModel.saveSettings( _doc, _this, "distend" ); - m_gainModel.saveSettings( _doc, _this, "gain" ); - m_envModel.saveSettings( _doc, _this, "env" ); - m_noiseModel.saveSettings( _doc, _this, "noise" ); - m_clickModel.saveSettings( _doc, _this, "click" ); - m_slopeModel.saveSettings( _doc, _this, "slope" ); - m_startNoteModel.saveSettings( _doc, _this, "startnote" ); - m_endNoteModel.saveSettings( _doc, _this, "endnote" ); - m_versionModel.saveSettings( _doc, _this, "version" ); + m_startFreqModel.saveSettings(doc, elem, "startfreq"); + m_endFreqModel.saveSettings(doc, elem, "endfreq"); + m_decayModel.saveSettings(doc, elem, "decay"); + m_distModel.saveSettings(doc, elem, "dist"); + m_distEndModel.saveSettings(doc, elem, "distend"); + m_gainModel.saveSettings(doc, elem, "gain"); + m_envModel.saveSettings(doc, elem, "env"); + m_noiseModel.saveSettings(doc, elem, "noise"); + m_clickModel.saveSettings(doc, elem, "click"); + m_slopeModel.saveSettings(doc, elem, "slope"); + m_startNoteModel.saveSettings(doc, elem, "startnote"); + m_endNoteModel.saveSettings(doc, elem, "endnote"); + m_versionModel.saveSettings(doc, elem, "version"); } -void KickerInstrument::loadSettings( const QDomElement & _this ) +void KickerInstrument::loadSettings(const QDomElement& elem) { - m_versionModel.loadSettings( _this, "version" ); + m_versionModel.loadSettings(elem, "version"); - m_startFreqModel.loadSettings( _this, "startfreq" ); - m_endFreqModel.loadSettings( _this, "endfreq" ); - m_decayModel.loadSettings( _this, "decay" ); - m_distModel.loadSettings( _this, "dist" ); - if( _this.hasAttribute( "distend" ) ) + m_startFreqModel.loadSettings(elem, "startfreq"); + m_endFreqModel.loadSettings(elem, "endfreq"); + m_decayModel.loadSettings(elem, "decay"); + m_distModel.loadSettings(elem, "dist"); + if (elem.hasAttribute("distend") || !elem.firstChildElement("distend").isNull()) { - m_distEndModel.loadSettings( _this, "distend" ); + m_distEndModel.loadSettings(elem, "distend"); } else { - m_distEndModel.setValue( m_distModel.value() ); + m_distEndModel.setValue(m_distModel.value()); } - m_gainModel.loadSettings( _this, "gain" ); - m_envModel.loadSettings( _this, "env" ); - m_noiseModel.loadSettings( _this, "noise" ); - m_clickModel.loadSettings( _this, "click" ); - m_slopeModel.loadSettings( _this, "slope" ); - m_startNoteModel.loadSettings( _this, "startnote" ); - if( m_versionModel.value() < 1 ) + m_gainModel.loadSettings(elem, "gain"); + m_envModel.loadSettings(elem, "env"); + m_noiseModel.loadSettings(elem, "noise"); + m_clickModel.loadSettings(elem, "click"); + m_slopeModel.loadSettings(elem, "slope"); + m_startNoteModel.loadSettings(elem, "startnote"); + if (m_versionModel.value() < 1) { - m_startNoteModel.setValue( false ); + m_startNoteModel.setValue(false); } - m_endNoteModel.loadSettings( _this, "endnote" ); + m_endNoteModel.loadSettings(elem, "endnote"); // Try to maintain backwards compatibility - if( !_this.hasAttribute( "version" ) ) + if (!elem.hasAttribute("version")) { - m_startNoteModel.setValue( false ); - m_decayModel.setValue( m_decayModel.value() * 1.33f ); - m_envModel.setValue( 1.0f ); - m_slopeModel.setValue( 1.0f ); - m_clickModel.setValue( 0.0f ); + m_startNoteModel.setValue(false); + m_decayModel.setValue(m_decayModel.value() * 1.33f); + m_envModel.setValue(1.0f); + m_slopeModel.setValue(1.0f); + m_clickModel.setValue(0.0f); } - m_versionModel.setValue( KICKER_PRESET_VERSION ); + m_versionModel.setValue(KICKER_PRESET_VERSION); } @@ -165,7 +163,7 @@ void KickerInstrument::playNote( NotePlayHandle * _n, const float decfr = m_decayModel.value() * Engine::audioEngine()->processingSampleRate() / 1000.0f; const f_cnt_t tfp = _n->totalFramesPlayed(); - if ( tfp == 0 ) + if (!_n->m_pluginData) { _n->m_pluginData = new SweepOsc( DistFX( m_distModel.value(), @@ -199,8 +197,6 @@ void KickerInstrument::playNote( NotePlayHandle * _n, _working_buffer[f+offset][1] *= fac; } } - - instrumentTrack()->processAudioBuffer( _working_buffer, frames + offset, _n ); } @@ -228,7 +224,7 @@ class KickerKnob : public Knob { public: KickerKnob( QWidget * _parent ) : - Knob( knobStyled, _parent ) + Knob( KnobType::Styled, _parent ) { setFixedSize( 29, 29 ); setObjectName( "smallKnob" ); @@ -240,7 +236,7 @@ class KickerEnvKnob : public TempoSyncKnob { public: KickerEnvKnob( QWidget * _parent ) : - TempoSyncKnob( knobStyled, _parent ) + TempoSyncKnob( KnobType::Styled, _parent ) { setFixedSize( 29, 29 ); setObjectName( "smallKnob" ); @@ -252,7 +248,7 @@ class KickerLargeKnob : public Knob { public: KickerLargeKnob( QWidget * _parent ) : - Knob( knobStyled, _parent ) + Knob( KnobType::Styled, _parent ) { setFixedSize( 34, 34 ); setObjectName( "largeKnob" ); @@ -317,10 +313,10 @@ KickerInstrumentView::KickerInstrumentView( Instrument * _instrument, m_distEndKnob->setHintText( tr( "End distortion:" ), "" ); m_distEndKnob->move( COL5, ROW2 ); - m_startNoteToggle = new LedCheckBox( "", this, "", LedCheckBox::Green ); + m_startNoteToggle = new LedCheckBox( "", this, "", LedCheckBox::LedColor::Green ); m_startNoteToggle->move( COL1 + 8, LED_ROW ); - m_endNoteToggle = new LedCheckBox( "", this, "", LedCheckBox::Green ); + m_endNoteToggle = new LedCheckBox( "", this, "", LedCheckBox::LedColor::Green ); m_endNoteToggle->move( END_COL + 8, LED_ROW ); setAutoFillBackground( true ); diff --git a/plugins/Kicker/Kicker.h b/plugins/Kicker/Kicker.h index 22413c4db..b5d065598 100644 --- a/plugins/Kicker/Kicker.h +++ b/plugins/Kicker/Kicker.h @@ -23,9 +23,8 @@ * */ - -#ifndef KICKER_H -#define KICKER_H +#ifndef LMMS_KICKER_H +#define LMMS_KICKER_H #include "AutomatableModel.h" #include "Instrument.h" @@ -60,14 +59,14 @@ public: sampleFrame * _working_buffer ) override; void deleteNotePluginData( NotePlayHandle * _n ) override; - void saveSettings( QDomDocument & _doc, QDomElement & _parent ) override; - void loadSettings( const QDomElement & _this ) override; + void saveSettings(QDomDocument& doc, QDomElement& elem) override; + void loadSettings(const QDomElement& elem) override; QString nodeName() const override; Flags flags() const override { - return IsNotBendable; + return Flag::IsNotBendable; } f_cnt_t desiredReleaseFrames() const override @@ -135,4 +134,4 @@ private: } // namespace lmms -#endif +#endif // LMMS_KICKER_H 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..a0bd556ef --- /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()->processingSampleRate()), + 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()->processingSampleRate(); + 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..bf7e67c4c --- /dev/null +++ b/plugins/LOMM/LOMMControlDialog.h @@ -0,0 +1,129 @@ +/* + * 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, const QString& activeIcon, const QString& 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 714b3887c..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" @@ -56,7 +55,7 @@ Plugin::Descriptor PLUGIN_EXPORT ladspabrowser_plugin_descriptor = "List installed LADSPA plugins" ), "Danny McRae ", 0x0100, - Plugin::Tool, + Plugin::Type::Tool, new PluginPixmapLoader("logo"), nullptr, nullptr, @@ -98,7 +97,7 @@ LadspaBrowserView::LadspaBrowserView( ToolPlugin * _tool ) : { auto hlayout = new QHBoxLayout(this); hlayout->setSpacing( 0 ); - hlayout->setMargin( 0 ); + hlayout->setContentsMargins(0, 0, 0, 0); m_tabBar = new TabBar( this, QBoxLayout::TopToBottom ); m_tabBar->setExclusive( true ); @@ -107,12 +106,12 @@ LadspaBrowserView::LadspaBrowserView( ToolPlugin * _tool ) : auto ws = new QWidget(this); ws->setFixedSize( 500, 480 ); - QWidget * available = createTab( ws, tr( "Available Effects" ), VALID ); + QWidget * available = createTab( ws, tr( "Available Effects" ), LadspaPluginType::Valid ); QWidget * unavailable = createTab( ws, tr( "Unavailable Effects" ), - INVALID ); - QWidget * instruments = createTab( ws, tr( "Instruments" ), SOURCE ); - QWidget * analysis = createTab( ws, tr( "Analysis Tools" ), SINK ); - QWidget * other = createTab( ws, tr( "Don't know" ), OTHER ); + LadspaPluginType::Invalid ); + QWidget * instruments = createTab( ws, tr( "Instruments" ), LadspaPluginType::Source ); + QWidget * analysis = createTab( ws, tr( "Analysis Tools" ), LadspaPluginType::Sink ); + QWidget * other = createTab( ws, tr( "Don't know" ), LadspaPluginType::Other ); m_tabBar->addTab( available, tr( "Available Effects" ), @@ -166,13 +165,12 @@ QWidget * LadspaBrowserView::createTab( QWidget * _parent, const QString & _txt, tab->setFixedSize( 500, 400 ); auto layout = new QVBoxLayout(tab); layout->setSpacing( 0 ); - layout->setMargin( 0 ); + layout->setContentsMargins(0, 0, 0, 0); const QString type = "" + tr( "Type:" ) + " "; 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 ff4d74867..7b1ede1c3 100644 --- a/plugins/LadspaBrowser/LadspaDescription.cpp +++ b/plugins/LadspaBrowser/LadspaDescription.cpp @@ -49,22 +49,22 @@ LadspaDescription::LadspaDescription( QWidget * _parent, l_sortable_plugin_t plugins; switch( _type ) { - case SOURCE: + case LadspaPluginType::Source: plugins = manager->getInstruments(); break; - case TRANSFER: + case LadspaPluginType::Transfer: plugins = manager->getValidEffects(); break; - case VALID: + case LadspaPluginType::Valid: plugins = manager->getValidEffects(); break; - case INVALID: + case LadspaPluginType::Invalid: plugins = manager->getInvalidEffects(); break; - case SINK: + case LadspaPluginType::Sink: plugins = manager->getAnalysisTools(); break; - case OTHER: + case LadspaPluginType::Other: plugins = manager->getOthers(); break; default: @@ -75,7 +75,7 @@ LadspaDescription::LadspaDescription( QWidget * _parent, for (const auto& plugin : plugins) { ch_cnt_t audioDeviceChannels = Engine::audioEngine()->audioDev()->channels(); - if (_type != VALID || manager->getDescription(plugin.second)->inputChannels <= audioDeviceChannels) + if (_type != LadspaPluginType::Valid || manager->getDescription(plugin.second)->inputChannels <= audioDeviceChannels) { pluginNames.push_back(plugin.first); m_pluginKeys.push_back(plugin.second); @@ -94,7 +94,7 @@ LadspaDescription::LadspaDescription( QWidget * _parent, auto descriptionBox = new QGroupBox(tr("Description"), this); auto descriptionLayout = new QVBoxLayout(descriptionBox); descriptionLayout->setSpacing( 0 ); - descriptionLayout->setMargin( 0 ); + descriptionLayout->setContentsMargins(0, 0, 0, 0); m_scrollArea = new QScrollArea( descriptionBox ); descriptionLayout->addWidget( m_scrollArea ); @@ -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->setMargin( 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->setMargin( 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 3c0dbabd5..e25679511 100644 --- a/plugins/LadspaBrowser/LadspaPortDialog.cpp +++ b/plugins/LadspaBrowser/LadspaPortDialog.cpp @@ -47,7 +47,7 @@ LadspaPortDialog::LadspaPortDialog( const ladspa_key_t & _key ) auto vlayout = new QVBoxLayout(this); vlayout->setSpacing( 0 ); - vlayout->setMargin( 0 ); + vlayout->setContentsMargins(0, 0, 0, 0); int pc = manager->getPortCount( _key ); diff --git a/plugins/LadspaEffect/CMakeLists.txt b/plugins/LadspaEffect/CMakeLists.txt index 951615ad4..202a8dd04 100644 --- a/plugins/LadspaEffect/CMakeLists.txt +++ b/plugins/LadspaEffect/CMakeLists.txt @@ -1,25 +1,25 @@ INCLUDE(BuildPlugin) -BUILD_PLUGIN(ladspaeffect LadspaEffect.cpp LadspaControls.cpp LadspaControlDialog.cpp LadspaSubPluginFeatures.cpp LadspaEffect.h LadspaControls.h LadspaControlDialog.h LadspaSubPluginFeatures.h MOCFILES LadspaEffect.h LadspaControls.h LadspaControlDialog.h EMBEDDED_RESOURCES logo.png) +BUILD_PLUGIN(ladspaeffect LadspaEffect.cpp LadspaControls.cpp LadspaControlDialog.cpp LadspaMatrixControlDialog.cpp LadspaSubPluginFeatures.cpp LadspaWidgetFactory.cpp LadspaEffect.h LadspaControls.h LadspaControlDialog.h LadspaMatrixControlDialog.h LadspaSubPluginFeatures.h LadspaWidgetFactory.h MOCFILES LadspaEffect.h LadspaControls.h LadspaControlDialog.h LadspaMatrixControlDialog.h EMBEDDED_RESOURCES logo.png) SET(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/ladspa") -IF(WANT_CAPS) +IF(LMMS_HAVE_CAPS) ADD_SUBDIRECTORY(caps) -ENDIF(WANT_CAPS) +ENDIF() -IF(WANT_TAP) +IF(LMMS_HAVE_TAP) ADD_SUBDIRECTORY(tap) -ENDIF(WANT_TAP) +ENDIF() -IF(WANT_SWH) +IF(LMMS_HAVE_SWH) ADD_SUBDIRECTORY(swh) -ENDIF(WANT_SWH) +ENDIF() -IF(WANT_CMT) +IF(LMMS_HAVE_CMT) ADD_SUBDIRECTORY(cmt) -ENDIF(WANT_CMT) +ENDIF() -IF(WANT_CALF) +IF(LMMS_HAVE_CALF) ADD_SUBDIRECTORY(calf) -ENDIF(WANT_CALF) +ENDIF() diff --git a/plugins/LadspaEffect/LadspaControlDialog.cpp b/plugins/LadspaEffect/LadspaControlDialog.cpp index 571dbd454..5189b0cde 100644 --- a/plugins/LadspaEffect/LadspaControlDialog.cpp +++ b/plugins/LadspaEffect/LadspaControlDialog.cpp @@ -86,19 +86,11 @@ void LadspaControlDialog::updateEffectView( LadspaControls * _ctl ) control_list_t & controls = _ctl->m_controls[proc]; int row = 0; int col = 0; - buffer_data_t last_port = NONE; + 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 ); @@ -108,10 +100,10 @@ void LadspaControlDialog::updateEffectView( LadspaControls * _ctl ) { if (control->port()->proc == proc) { - buffer_data_t this_port = control->port()->data_type; - if( last_port != NONE && - ( this_port == TOGGLED || this_port == ENUM ) && - ( last_port != TOGGLED && last_port != ENUM ) ) + BufferDataType this_port = control->port()->data_type; + if( last_port != BufferDataType::None && + ( this_port == BufferDataType::Toggled || this_port == BufferDataType::Enum ) && + ( last_port != BufferDataType::Toggled && last_port != BufferDataType::Enum ) ) { ++row; col = 0; 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 e5d1c5d69..837bd554c 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" @@ -60,10 +59,10 @@ Plugin::Descriptor PLUGIN_EXPORT ladspaeffect_plugin_descriptor = "inside LMMS." ), "Danny McRae ", 0x0100, - Plugin::Effect, + Plugin::Type::Effect, new PluginPixmapLoader("logo"), nullptr, - new LadspaSubPluginFeatures( Plugin::Effect ) + new LadspaSubPluginFeatures( Plugin::Type::Effect ) } ; } @@ -106,7 +105,7 @@ LadspaEffect::~LadspaEffect() void LadspaEffect::changeSampleRate() { - DataFile dataFile( DataFile::EffectSettings ); + DataFile dataFile( DataFile::Type::EffectSettings ); m_controls->saveState( dataFile, dataFile.content() ); LadspaControls * controls = m_controls; @@ -163,7 +162,7 @@ bool LadspaEffect::processAudioBuffer( sampleFrame * _buf, port_desc_t * pp = m_ports.at( proc ).at( port ); switch( pp->rate ) { - case CHANNEL_IN: + case BufferRate::ChannelIn: for( fpp_t frame = 0; frame < frames; ++frame ) { @@ -172,7 +171,7 @@ bool LadspaEffect::processAudioBuffer( sampleFrame * _buf, } ++channel; break; - case AUDIO_RATE_INPUT: + case BufferRate::AudioRateInput: { ValueBuffer * vb = pp->control->valueBuffer(); if( vb ) @@ -195,7 +194,7 @@ bool LadspaEffect::processAudioBuffer( sampleFrame * _buf, } break; } - case CONTROL_RATE_INPUT: + case BufferRate::ControlRateInput: if( pp->control == nullptr ) { break; @@ -205,9 +204,9 @@ bool LadspaEffect::processAudioBuffer( sampleFrame * _buf, pp->buffer[0] = pp->value; break; - case CHANNEL_OUT: - case AUDIO_RATE_OUTPUT: - case CONTROL_RATE_OUTPUT: + case BufferRate::ChannelOut: + case BufferRate::AudioRateOutput: + case BufferRate::ControlRateOutput: break; default: break; @@ -234,11 +233,11 @@ bool LadspaEffect::processAudioBuffer( sampleFrame * _buf, port_desc_t * pp = m_ports.at( proc ).at( port ); switch( pp->rate ) { - case CHANNEL_IN: - case AUDIO_RATE_INPUT: - case CONTROL_RATE_INPUT: + case BufferRate::ChannelIn: + case BufferRate::AudioRateInput: + case BufferRate::ControlRateInput: break; - case CHANNEL_OUT: + case BufferRate::ChannelOut: for( fpp_t frame = 0; frame < frames; ++frame ) { @@ -247,8 +246,8 @@ bool LadspaEffect::processAudioBuffer( sampleFrame * _buf, } ++channel; break; - case AUDIO_RATE_OUTPUT: - case CONTROL_RATE_OUTPUT: + case BufferRate::AudioRateOutput: + case BufferRate::ControlRateOutput: break; default: break; @@ -325,15 +324,15 @@ void LadspaEffect::pluginInstantiation() if( p->name.toUpper().contains( "IN" ) && manager->isPortInput( m_key, port ) ) { - p->rate = CHANNEL_IN; - p->buffer = MM_ALLOC( Engine::audioEngine()->framesPerPeriod() ); + p->rate = BufferRate::ChannelIn; + p->buffer = new LADSPA_Data[Engine::audioEngine()->framesPerPeriod()]; inbuf[ inputch ] = p->buffer; inputch++; } else if( p->name.toUpper().contains( "OUT" ) && manager->isPortOutput( m_key, port ) ) { - p->rate = CHANNEL_OUT; + p->rate = BufferRate::ChannelOut; if( ! m_inPlaceBroken && inbuf[ outputch ] ) { p->buffer = inbuf[ outputch ]; @@ -341,51 +340,51 @@ 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 = AUDIO_RATE_INPUT; - p->buffer = MM_ALLOC( Engine::audioEngine()->framesPerPeriod() ); + p->rate = BufferRate::AudioRateInput; + p->buffer = new LADSPA_Data[Engine::audioEngine()->framesPerPeriod()]; } else { - p->rate = AUDIO_RATE_OUTPUT; - p->buffer = MM_ALLOC( Engine::audioEngine()->framesPerPeriod() ); + p->rate = BufferRate::AudioRateOutput; + 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 ) ) { - p->rate = CONTROL_RATE_INPUT; + p->rate = BufferRate::ControlRateInput; } else { - p->rate = CONTROL_RATE_OUTPUT; + p->rate = BufferRate::ControlRateOutput; } } p->scale = 1.0f; if( manager->isEnum( m_key, port ) ) { - p->data_type = ENUM; + p->data_type = BufferDataType::Enum; } else if( manager->isPortToggled( m_key, port ) ) { - p->data_type = TOGGLED; + p->data_type = BufferDataType::Toggled; } else if( manager->isInteger( m_key, port ) ) { - p->data_type = INTEGER; + p->data_type = BufferDataType::Integer; } else if( p->name.toUpper().contains( "(SECONDS)" ) ) { - p->data_type = TIME; + p->data_type = BufferDataType::Time; p->scale = 1000.0f; int loc = p->name.toUpper().indexOf( "(SECONDS)" ); @@ -393,20 +392,20 @@ void LadspaEffect::pluginInstantiation() } else if( p->name.toUpper().contains( "(S)" ) ) { - p->data_type = TIME; + p->data_type = BufferDataType::Time; p->scale = 1000.0f; int loc = p->name.toUpper().indexOf( "(S)" ); p->name.replace( loc, 3, "(ms)" ); } else if( p->name.toUpper().contains( "(MS)" ) ) { - p->data_type = TIME; + p->data_type = BufferDataType::Time; int loc = p->name.toUpper().indexOf( "(MS)" ); p->name.replace( loc, 4, "(ms)" ); } else { - p->data_type = FLOATING; + p->data_type = BufferDataType::Floating; } // Get the range and default values. @@ -438,7 +437,7 @@ void LadspaEffect::pluginInstantiation() p->def = manager->getDefaultSetting( m_key, port ); if( p->def == NOHINT ) { - if( p->data_type != TOGGLED ) + if( p->data_type != BufferDataType::Toggled ) { p->def = ( p->min + p->max ) / 2.0f; } @@ -465,8 +464,8 @@ void LadspaEffect::pluginInstantiation() // For convenience, keep a separate list of the ports that are used // to control the processors. - if( p->rate == AUDIO_RATE_INPUT || - p->rate == CONTROL_RATE_INPUT ) + if( p->rate == BufferRate::AudioRateInput || + p->rate == BufferRate::ControlRateInput ) { p->control_id = m_portControls.count(); m_portControls.append( p ); @@ -555,9 +554,9 @@ void LadspaEffect::pluginDestruction() for( int port = 0; port < m_portCount; port++ ) { port_desc_t * pp = m_ports.at( proc ).at( port ); - if( m_inPlaceBroken || pp->rate != CHANNEL_OUT ) + if( m_inPlaceBroken || pp->rate != BufferRate::ChannelOut ) { - if( pp->buffer) MM_FREE( pp->buffer ); + if( pp->buffer) delete[] pp->buffer; } delete pp; } 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 8ab50858a..fc4667152 100644 --- a/plugins/LadspaEffect/LadspaSubPluginFeatures.cpp +++ b/plugins/LadspaEffect/LadspaSubPluginFeatures.cpp @@ -39,7 +39,7 @@ namespace lmms { -LadspaSubPluginFeatures::LadspaSubPluginFeatures( Plugin::PluginTypes _type ) : +LadspaSubPluginFeatures::LadspaSubPluginFeatures( Plugin::Type _type ) : SubPluginFeatures( _type ) { } @@ -71,7 +71,7 @@ void LadspaSubPluginFeatures::fillDescriptionWidget( QWidget * _parent, auto maker = new QWidget(_parent); auto l = new QHBoxLayout(maker); - l->setMargin( 0 ); + l->setContentsMargins(0, 0, 0, 0); l->setSpacing( 0 ); auto maker_label = new QLabel(maker); @@ -85,7 +85,7 @@ void LadspaSubPluginFeatures::fillDescriptionWidget( QWidget * _parent, auto copyright = new QWidget(_parent); l = new QHBoxLayout( copyright ); - l->setMargin( 0 ); + l->setContentsMargins(0, 0, 0, 0); l->setSpacing( 0 ); copyright->setMinimumWidth( _parent->minimumWidth() ); @@ -137,17 +137,17 @@ void LadspaSubPluginFeatures::listSubPluginKeys( l_sortable_plugin_t plugins; switch( m_type ) { - case Plugin::Instrument: + case Plugin::Type::Instrument: plugins = lm->getInstruments(); break; - case Plugin::Effect: + case Plugin::Type::Effect: plugins = lm->getValidEffects(); //plugins += lm->getInvalidEffects(); break; - case Plugin::Tool: + case Plugin::Type::Tool: plugins = lm->getAnalysisTools(); break; - case Plugin::Other: + case Plugin::Type::Other: plugins = lm->getOthers(); break; default: @@ -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/LadspaSubPluginFeatures.h b/plugins/LadspaEffect/LadspaSubPluginFeatures.h index c65dce9fe..7747127b8 100644 --- a/plugins/LadspaEffect/LadspaSubPluginFeatures.h +++ b/plugins/LadspaEffect/LadspaSubPluginFeatures.h @@ -38,7 +38,7 @@ namespace lmms class LadspaSubPluginFeatures : public Plugin::Descriptor::SubPluginFeatures { public: - LadspaSubPluginFeatures( Plugin::PluginTypes _type ); + LadspaSubPluginFeatures( Plugin::Type _type ); QString displayName(const Key& k) const override; void fillDescriptionWidget( QWidget * _parent, 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 90f506417..0c9cd8fa9 100644 --- a/plugins/LadspaEffect/calf/CMakeLists.txt +++ b/plugins/LadspaEffect/calf/CMakeLists.txt @@ -36,9 +36,9 @@ TARGET_COMPILE_DEFINITIONS(veal PRIVATE DISABLE_OSC=1) SET(INLINE_FLAGS "") IF("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU") - SET(INLINE_FLAGS "-finline-functions-called-once -finline-limit=80") + SET(INLINE_FLAGS -finline-functions-called-once -finline-limit=80) ENDIF() -SET_TARGET_PROPERTIES(veal PROPERTIES COMPILE_FLAGS "-fexceptions -O2 -finline-functions ${INLINE_FLAGS}") +target_compile_options(veal PRIVATE -fexceptions -O2 -finline-functions ${INLINE_FLAGS}) if(LMMS_BUILD_WIN32) add_custom_command( @@ -50,5 +50,5 @@ if(LMMS_BUILD_WIN32) ) endif() IF(NOT LMMS_BUILD_APPLE AND NOT LMMS_BUILD_OPENBSD) - SET_TARGET_PROPERTIES(veal PROPERTIES LINK_FLAGS "${LINK_FLAGS} -shared -Wl,-no-undefined") + target_link_libraries(veal PRIVATE -shared) ENDIF() diff --git a/plugins/LadspaEffect/caps/Descriptor.h b/plugins/LadspaEffect/caps/Descriptor.h index 12c5d1c88..c3e1c325e 100644 --- a/plugins/LadspaEffect/caps/Descriptor.h +++ b/plugins/LadspaEffect/caps/Descriptor.h @@ -53,7 +53,7 @@ class DescriptorStub PortCount = 0; } - ~DescriptorStub() + virtual ~DescriptorStub() { if (PortCount) { @@ -87,6 +87,7 @@ class Descriptor public: Descriptor() { setup(); } + ~Descriptor() override = default; void setup(); void autogen() diff --git a/plugins/LadspaEffect/cmt/CMakeLists.txt b/plugins/LadspaEffect/cmt/CMakeLists.txt index ded7b8958..75dba319d 100644 --- a/plugins/LadspaEffect/cmt/CMakeLists.txt +++ b/plugins/LadspaEffect/cmt/CMakeLists.txt @@ -5,7 +5,7 @@ ADD_LIBRARY(cmt MODULE ${SOURCES}) INSTALL(TARGETS cmt LIBRARY DESTINATION "${PLUGIN_DIR}/ladspa") SET_TARGET_PROPERTIES(cmt PROPERTIES PREFIX "") -SET_TARGET_PROPERTIES(cmt PROPERTIES COMPILE_FLAGS "-Wall -O3 -fno-strict-aliasing") +target_compile_options(cmt PRIVATE -Wall -O3 -fno-strict-aliasing) if(LMMS_BUILD_WIN32) add_custom_command( @@ -18,10 +18,10 @@ if(LMMS_BUILD_WIN32) endif() if(NOT LMMS_BUILD_WIN32) - set_target_properties(cmt PROPERTIES COMPILE_FLAGS "${COMPILE_FLAGS} -fPIC") + target_compile_options(cmt PRIVATE -fPIC) endif() IF(NOT LMMS_BUILD_APPLE AND NOT LMMS_BUILD_OPENBSD) - SET_TARGET_PROPERTIES(cmt PROPERTIES LINK_FLAGS "${LINK_FLAGS} -shared -Wl,-no-undefined") + target_link_libraries(cmt PRIVATE -shared) ENDIF() diff --git a/plugins/LadspaEffect/cmt/cmt b/plugins/LadspaEffect/cmt/cmt index f7c25ed4e..d8bf8084a 160000 --- a/plugins/LadspaEffect/cmt/cmt +++ b/plugins/LadspaEffect/cmt/cmt @@ -1 +1 @@ -Subproject commit f7c25ed4ef7f4d7efb1bcd4229d25595d4f1ce55 +Subproject commit d8bf8084aa3a47497092f5ab99c843a55090d151 diff --git a/plugins/LadspaEffect/swh/CMakeLists.txt b/plugins/LadspaEffect/swh/CMakeLists.txt index aec01c22f..a83001177 100644 --- a/plugins/LadspaEffect/swh/CMakeLists.txt +++ b/plugins/LadspaEffect/swh/CMakeLists.txt @@ -16,6 +16,7 @@ SET(COMPILE_FLAGS "${COMPILE_FLAGS} ${PIC_FLAGS}") # Loop over every XML file FILE(GLOB XML_SOURCES "${CMAKE_CURRENT_SOURCE_DIR}/ladspa/*.xml") LIST(SORT XML_SOURCES) + FOREACH(_item ${XML_SOURCES}) # Get library name and (soon to be) C file GET_FILENAME_COMPONENT(_plugin "${_item}" NAME_WE) @@ -24,7 +25,7 @@ FOREACH(_item ${XML_SOURCES}) # Coerce XML source file to C ADD_CUSTOM_COMMAND( OUTPUT "${_out_file}" - COMMAND ./makestub.pl "${_item}" > "${_out_file}" + COMMAND "${PERL_EXECUTABLE}" ./makestub.pl "${_item}" > "${_out_file}" DEPENDS "${_item}" WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/ladspa" VERBATIM diff --git a/plugins/LadspaEffect/swh/ladspa b/plugins/LadspaEffect/swh/ladspa index d99a0db52..02bda2320 160000 --- a/plugins/LadspaEffect/swh/ladspa +++ b/plugins/LadspaEffect/swh/ladspa @@ -1 +1 @@ -Subproject commit d99a0db521d13a87bdaa418c674ca8858e484452 +Subproject commit 02bda232041380c2846414945798cbbfecb2f3f2 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 140b84189..e0b31360f 100644 --- a/plugins/Lb302/Lb302.cpp +++ b/plugins/Lb302/Lb302.cpp @@ -86,7 +86,7 @@ Plugin::Descriptor PLUGIN_EXPORT lb302_plugin_descriptor = "Incomplete monophonic imitation TB-303" ), "Paul Giblock ", 0x0100, - Plugin::Instrument, + Plugin::Type::Instrument, new PluginPixmapLoader( "logo" ), nullptr, nullptr, @@ -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 @@ -290,7 +285,7 @@ Lb302Synth::Lb302Synth( InstrumentTrack * _instrumentTrack ) : vca_decay(0.99897516), vca_a0(0.5), vca_a(0.), - vca_mode(never_played) + vca_mode(VcaMode::NeverPlayed) { connect( Engine::audioEngine(), SIGNAL( sampleRateChanged() ), @@ -332,7 +327,7 @@ Lb302Synth::Lb302Synth( InstrumentTrack * _instrumentTrack ) : vcf_envpos = ENVINC; - vco_shape = BL_SAWTOOTH; + vco_shape = VcoShape::BLSawtooth; vcfs[0] = new Lb302FilterIIR2(&fs); vcfs[1] = new Lb302Filter3Pole(&fs); @@ -461,15 +456,13 @@ inline float GET_INC(float freq) { int Lb302Synth::process(sampleFrame *outbuf, const int size) { const float sampleRatio = 44100.f / Engine::audioEngine()->processingSampleRate(); - float w; - float samp; // Hold on to the current VCF, and use it throughout this period Lb302Filter *filter = vcf.loadAcquire(); if( release_frame == 0 || ! m_playingNote ) { - vca_mode = decay; + vca_mode = VcaMode::Decay; } if( new_freq ) @@ -493,7 +486,7 @@ int Lb302Synth::process(sampleFrame *outbuf, const int size) // start decay if we're past release if( i >= release_frame ) { - vca_mode = decay; + vca_mode = VcaMode::Decay; } // update vcf @@ -523,82 +516,85 @@ int Lb302Synth::process(sampleFrame *outbuf, const int size) vco_c -= 1.0; switch(int(rint(wave_shape.value()))) { - case 0: vco_shape = SAWTOOTH; break; - case 1: vco_shape = TRIANGLE; break; - case 2: vco_shape = SQUARE; break; - case 3: vco_shape = ROUND_SQUARE; break; - case 4: vco_shape = MOOG; break; - case 5: vco_shape = SINE; break; - case 6: vco_shape = EXPONENTIAL; break; - case 7: vco_shape = WHITE_NOISE; break; - case 8: vco_shape = BL_SAWTOOTH; break; - case 9: vco_shape = BL_SQUARE; break; - case 10: vco_shape = BL_TRIANGLE; break; - case 11: vco_shape = BL_MOOG; break; - default: vco_shape = SAWTOOTH; break; + case 0: vco_shape = VcoShape::Sawtooth; break; + case 1: vco_shape = VcoShape::Triangle; break; + case 2: vco_shape = VcoShape::Square; break; + case 3: vco_shape = VcoShape::RoundSquare; break; + case 4: vco_shape = VcoShape::Moog; break; + case 5: vco_shape = VcoShape::Sine; break; + case 6: vco_shape = VcoShape::Exponential; break; + case 7: vco_shape = VcoShape::WhiteNoise; break; + case 8: vco_shape = VcoShape::BLSawtooth; break; + case 9: vco_shape = VcoShape::BLSquare; break; + case 10: vco_shape = VcoShape::BLTriangle; break; + case 11: vco_shape = VcoShape::BLMoog; break; + default: vco_shape = VcoShape::Sawtooth; break; } // add vco_shape_param the changes the shape of each curve. // merge sawtooths with triangle and square with round square? switch (vco_shape) { - case SAWTOOTH: // p0: curviness of line + case VcoShape::Sawtooth: // p0: curviness of line vco_k = vco_c; // Is this sawtooth backwards? break; - case TRIANGLE: // p0: duty rev.saw<->triangle<->saw p1: curviness + case VcoShape::Triangle: // p0: duty rev.saw<->triangle<->saw p1: curviness vco_k = (vco_c*2.0)+0.5; if (vco_k>0.5) vco_k = 1.0- vco_k; break; - case SQUARE: // p0: slope of top + case VcoShape::Square: // p0: slope of top vco_k = (vco_c<0)?0.5:-0.5; break; - case ROUND_SQUARE: // p0: width of round + case VcoShape::RoundSquare: // p0: width of round vco_k = (vco_c<0)?(sqrtf(1-(vco_c*vco_c*4))-0.5):-0.5; break; - case MOOG: // Maybe the fall should be exponential/sinsoidal instead of quadric. + case VcoShape::Moog: // Maybe the fall should be exponential/sinsoidal instead of quadric. // [-0.5, 0]: Rise, [0,0.25]: Slope down, [0.25,0.5]: Low vco_k = (vco_c*2.0)+0.5; if (vco_k>1.0) { vco_k = -0.5 ; } else if (vco_k>0.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 break; - case SINE: + case VcoShape::Sine: // [-0.5, 0.5] : [-pi, pi] vco_k = 0.5f * Oscillator::sinSample( vco_c ); break; - case EXPONENTIAL: + case VcoShape::Exponential: vco_k = 0.5 * Oscillator::expSample( vco_c ); break; - case WHITE_NOISE: + case VcoShape::WhiteNoise: vco_k = 0.5 * Oscillator::noiseSample( vco_c ); break; - case BL_SAWTOOTH: - vco_k = BandLimitedWave::oscillate( vco_c + 0.5f, BandLimitedWave::pdToLen( vco_inc ), BandLimitedWave::BLSaw ) * 0.5f; + // 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 = vco_inc == 0. ? 0. : BandLimitedWave::oscillate(vco_c + 0.5f, BandLimitedWave::pdToLen(vco_inc), BandLimitedWave::Waveform::BLSaw) * 0.5f; break; - case BL_SQUARE: - vco_k = BandLimitedWave::oscillate( vco_c + 0.5f, BandLimitedWave::pdToLen( vco_inc ), BandLimitedWave::BLSquare ) * 0.5f; + case VcoShape::BLSquare: + vco_k = vco_inc == 0. ? 0. : BandLimitedWave::oscillate(vco_c + 0.5f, BandLimitedWave::pdToLen(vco_inc), BandLimitedWave::Waveform::BLSquare) * 0.5f; break; - case BL_TRIANGLE: - vco_k = BandLimitedWave::oscillate( vco_c + 0.5f, BandLimitedWave::pdToLen( vco_inc ), BandLimitedWave::BLTriangle ) * 0.5f; + case VcoShape::BLTriangle: + vco_k = vco_inc == 0. ? 0. : BandLimitedWave::oscillate(vco_c + 0.5f, BandLimitedWave::pdToLen(vco_inc), BandLimitedWave::Waveform::BLTriangle) * 0.5f; break; - case BL_MOOG: - vco_k = BandLimitedWave::oscillate( vco_c + 0.5f, BandLimitedWave::pdToLen( vco_inc ), BandLimitedWave::BLMoog ); + case VcoShape::BLMoog: + vco_k = vco_inc == 0. ? 0. : BandLimitedWave::oscillate(vco_c + 0.5f, BandLimitedWave::pdToLen(vco_inc), BandLimitedWave::Waveform::BLMoog); break; } @@ -607,7 +603,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); @@ -633,18 +629,18 @@ int Lb302Synth::process(sampleFrame *outbuf, const int size) } // Handle Envelope - if(vca_mode==attack) { + if(vca_mode==VcaMode::Attack) { vca_a+=(vca_a0-vca_a)*vca_attack; if(sample_cnt>=0.5*Engine::audioEngine()->processingSampleRate()) - vca_mode = idle; + vca_mode = VcaMode::Idle; } - else if(vca_mode == decay) { + else if(vca_mode == VcaMode::Decay) { vca_a *= vca_decay; // the following line actually speeds up processing if(vca_a < (1/65536.0)) { vca_a = 0; - vca_mode = never_played; + vca_mode = VcaMode::NeverPlayed; } } @@ -666,15 +662,15 @@ void Lb302Synth::initNote( Lb302Note *n) // Always reset vca on non-dead notes, and // Only reset vca on decaying(decayed) and never-played - if(n->dead == 0 || (vca_mode == decay || vca_mode == never_played)) { + if(n->dead == 0 || (vca_mode == VcaMode::Decay || vca_mode == VcaMode::NeverPlayed)) { //printf(" good\n"); sample_cnt = 0; - vca_mode = attack; + vca_mode = VcaMode::Attack; // LB303: //vca_a = 0; } else { - vca_mode = idle; + vca_mode = VcaMode::Idle; } initSlide(); @@ -746,7 +742,7 @@ void Lb302Synth::playNote( NotePlayHandle * _n, sampleFrame * _working_buffer ) void Lb302Synth::processNote( NotePlayHandle * _n ) { /// Start a new note. - if( _n->m_pluginData != this ) + if (_n->m_pluginData != this) { m_playingNote = _n; new_freq = true; @@ -790,7 +786,6 @@ void Lb302Synth::play( sampleFrame * _working_buffer ) const fpp_t frames = Engine::audioEngine()->framesPerPeriod(); process( _working_buffer, frames ); - instrumentTrack()->processAudioBuffer( _working_buffer, frames, nullptr ); // release_frame = 0; //removed for issue # 1432 } @@ -819,22 +814,22 @@ Lb302SynthView::Lb302SynthView( Instrument * _instrument, QWidget * _parent ) : InstrumentViewFixedSize( _instrument, _parent ) { // GUI - m_vcfCutKnob = new Knob( knobBright_26, this ); + m_vcfCutKnob = new Knob( KnobType::Bright26, this ); m_vcfCutKnob->move( 75, 130 ); m_vcfCutKnob->setHintText( tr( "Cutoff Freq:" ), "" ); m_vcfCutKnob->setLabel( "" ); - m_vcfResKnob = new Knob( knobBright_26, this ); + m_vcfResKnob = new Knob( KnobType::Bright26, this ); m_vcfResKnob->move( 120, 130 ); m_vcfResKnob->setHintText( tr( "Resonance:" ), "" ); m_vcfResKnob->setLabel( "" ); - m_vcfModKnob = new Knob( knobBright_26, this ); + m_vcfModKnob = new Knob( KnobType::Bright26, this ); m_vcfModKnob->move( 165, 130 ); m_vcfModKnob->setHintText( tr( "Env Mod:" ), "" ); m_vcfModKnob->setLabel( "" ); - m_vcfDecKnob = new Knob( knobBright_26, this ); + m_vcfDecKnob = new Knob( KnobType::Bright26, this ); m_vcfDecKnob->move( 210, 130 ); m_vcfDecKnob->setHintText( tr( "Decay:" ), "" ); m_vcfDecKnob->setLabel( "" ); @@ -855,12 +850,12 @@ Lb302SynthView::Lb302SynthView( Instrument * _instrument, QWidget * _parent ) : tr( "303-es-que, 24dB/octave, 3 pole filter" ) ); - m_slideDecKnob = new Knob( knobBright_26, this ); + m_slideDecKnob = new Knob( KnobType::Bright26, this ); m_slideDecKnob->move( 210, 75 ); m_slideDecKnob->setHintText( tr( "Slide Decay:" ), "" ); m_slideDecKnob->setLabel( ""); - m_distKnob = new Knob( knobBright_26, this ); + m_distKnob = new Knob( KnobType::Bright26, this ); m_distKnob->move( 210, 190 ); m_distKnob->setHintText( tr( "DIST:" ), "" ); m_distKnob->setLabel( tr( "")); diff --git a/plugins/Lb302/Lb302.h b/plugins/Lb302/Lb302.h index 3abece98f..237a3f3f8 100644 --- a/plugins/Lb302/Lb302.h +++ b/plugins/Lb302/Lb302.h @@ -165,7 +165,7 @@ public: Flags flags() const override { - return IsSingleStreamed; + return Flag::IsSingleStreamed; } f_cnt_t desiredReleaseFrames() const override @@ -213,9 +213,9 @@ private: vco_slideinc, //* Slide base to use in next node. Nonzero=slide next note vco_slidebase; //* The base vco_inc while sliding. - enum vco_shape_t { SAWTOOTH, SQUARE, TRIANGLE, MOOG, ROUND_SQUARE, SINE, EXPONENTIAL, WHITE_NOISE, - BL_SAWTOOTH, BL_SQUARE, BL_TRIANGLE, BL_MOOG }; - vco_shape_t vco_shape; + enum class VcoShape { Sawtooth, Square, Triangle, Moog, RoundSquare, Sine, Exponential, WhiteNoise, + BLSawtooth, BLSquare, BLTriangle, BLMoog }; + VcoShape vco_shape; // Filters (just keep both loaded and switch) Lb302Filter* vcfs[NUM_FILTERS]; @@ -235,14 +235,14 @@ private: vca_a; // Amplifier coefficient. // Envelope State - enum VCA_Mode + enum class VcaMode { - attack = 0, - decay = 1, - idle = 2, - never_played = 3 + Attack = 0, + Decay = 1, + Idle = 2, + NeverPlayed = 3 }; - VCA_Mode vca_mode; + VcaMode vca_mode; // My hacks int sample_cnt; diff --git a/plugins/Lv2Effect/Lv2Effect.cpp b/plugins/Lv2Effect/Lv2Effect.cpp index 1a25c718a..eef6305cc 100644 --- a/plugins/Lv2Effect/Lv2Effect.cpp +++ b/plugins/Lv2Effect/Lv2Effect.cpp @@ -1,7 +1,7 @@ /* * Lv2Effect.cpp - implementation of LV2 effect * - * Copyright (c) 2018-2020 Johannes Lorenz + * Copyright (c) 2018-2023 Johannes Lorenz * * This file is part of LMMS - https://lmms.io * @@ -24,6 +24,7 @@ #include "Lv2Effect.h" +#include #include "Lv2SubPluginFeatures.h" @@ -46,10 +47,10 @@ Plugin::Descriptor PLUGIN_EXPORT lv2effect_plugin_descriptor = "plugin for using arbitrary LV2-effects inside LMMS."), "Johannes Lorenz ", 0x0100, - Plugin::Effect, + Plugin::Type::Effect, new PluginPixmapLoader("logo"), nullptr, - new Lv2SubPluginFeatures(Plugin::Effect) + new Lv2SubPluginFeatures(Plugin::Type::Effect) }; } @@ -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 521a7df74..a28182132 100644 --- a/plugins/Lv2Effect/Lv2Effect.h +++ b/plugins/Lv2Effect/Lv2Effect.h @@ -1,7 +1,7 @@ /* * Lv2Effect.h - implementation of LV2 effect * - * Copyright (c) 2018-2020 Johannes Lorenz + * Copyright (c) 2018-2023 Johannes Lorenz * * This file is part of LMMS - https://lmms.io * @@ -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 d10c01cd4..73890937c 100644 --- a/plugins/Lv2Effect/Lv2FxControlDialog.cpp +++ b/plugins/Lv2Effect/Lv2FxControlDialog.cpp @@ -1,7 +1,7 @@ /* * Lv2FxControlDialog.cpp - Lv2FxControlDialog implementation * - * Copyright (c) 2018-2020 Johannes Lorenz + * Copyright (c) 2018-2023 Johannes Lorenz * * This file is part of LMMS - https://lmms.io * @@ -38,7 +38,7 @@ Lv2FxControlDialog::Lv2FxControlDialog(Lv2FxControls *controls) : { if (m_reloadPluginButton) { connect(m_reloadPluginButton, &QPushButton::clicked, - this, [this](){ lv2Controls()->reloadPlugin(); }); + this, [this](){ lv2Controls()->reload(); }); } if (m_toggleUIButton) { connect(m_toggleUIButton, &QPushButton::toggled, @@ -67,7 +67,18 @@ Lv2FxControls *Lv2FxControlDialog::lv2Controls() void Lv2FxControlDialog::modelChanged() { Lv2ViewBase::modelChanged(lv2Controls()); + connect(lv2Controls(), &Lv2FxControls::modelChanged, + this, [this](){ this->modelChanged();} ); } -} // namespace lmms::gui \ No newline at end of file + + +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 c749dc124..f38c0364b 100644 --- a/plugins/Lv2Effect/Lv2FxControlDialog.h +++ b/plugins/Lv2Effect/Lv2FxControlDialog.h @@ -1,7 +1,7 @@ /* * Lv2FxControlDialog.h - Lv2FxControlDialog implementation * - * Copyright (c) 2018-2020 Johannes Lorenz + * Copyright (c) 2018-2023 Johannes Lorenz * * This file is part of LMMS - https://lmms.io * @@ -45,7 +45,8 @@ public: private: Lv2FxControls *lv2Controls(); - void modelChanged() override; + void modelChanged() final; + void hideEvent(QHideEvent *event) override; }; diff --git a/plugins/Lv2Effect/Lv2FxControls.cpp b/plugins/Lv2Effect/Lv2FxControls.cpp index a865151dd..72c387ba7 100644 --- a/plugins/Lv2Effect/Lv2FxControls.cpp +++ b/plugins/Lv2Effect/Lv2FxControls.cpp @@ -1,7 +1,7 @@ /* * Lv2FxControls.cpp - Lv2FxControls implementation * - * Copyright (c) 2018-2020 Johannes Lorenz + * Copyright (c) 2018-2023 Johannes Lorenz * * This file is part of LMMS - https://lmms.io * @@ -38,11 +38,28 @@ Lv2FxControls::Lv2FxControls(class Lv2Effect *effect, const QString& uri) : EffectControls(effect), Lv2ControlBase(this, uri) { - if (isValid()) - { - connect(Engine::audioEngine(), &AudioEngine::sampleRateChanged, - this, [this](){Lv2ControlBase::reloadPlugin();}); - } + connect(Engine::audioEngine(), &AudioEngine::sampleRateChanged, + this, &Lv2FxControls::onSampleRateChanged); +} + + + + +void Lv2FxControls::reload() +{ + Lv2ControlBase::reload(); + emit modelChanged(); +} + + + + +void Lv2FxControls::onSampleRateChanged() +{ + // TODO: once lv2 options are implemented, + // plugins that support it might allow changing their samplerate + // through it instead of reloading + reload(); } @@ -86,20 +103,4 @@ void Lv2FxControls::changeControl() // TODO: what is that? } - - -DataFile::Types Lv2FxControls::settingsType() -{ - return DataFile::EffectSettings; -} - - - - -void Lv2FxControls::setNameFromFile(const QString &name) -{ - effect()->setDisplayName(name); -} - - } // namespace lmms diff --git a/plugins/Lv2Effect/Lv2FxControls.h b/plugins/Lv2Effect/Lv2FxControls.h index 952e4db6c..9f3736292 100644 --- a/plugins/Lv2Effect/Lv2FxControls.h +++ b/plugins/Lv2Effect/Lv2FxControls.h @@ -1,7 +1,7 @@ /* * Lv2FxControls.h - Lv2FxControls implementation * - * Copyright (c) 2018-2020 Johannes Lorenz + * Copyright (c) 2018-2023 Johannes Lorenz * * This file is part of LMMS - https://lmms.io * @@ -43,8 +43,11 @@ class Lv2FxControlDialog; class Lv2FxControls : public EffectControls, public Lv2ControlBase { Q_OBJECT +signals: + void modelChanged(); public: Lv2FxControls(Lv2Effect *effect, const QString &uri); + void reload(); void saveSettings(QDomDocument &_doc, QDomElement &_parent) override; void loadSettings(const QDomElement &that) override; @@ -60,8 +63,7 @@ private slots: void changeControl(); private: - DataFile::Types settingsType() override; - void setNameFromFile(const QString &name) override; + void onSampleRateChanged(); friend class gui::Lv2FxControlDialog; friend class Lv2Effect; diff --git a/plugins/Lv2Instrument/Lv2Instrument.cpp b/plugins/Lv2Instrument/Lv2Instrument.cpp index ae7e37119..316829327 100644 --- a/plugins/Lv2Instrument/Lv2Instrument.cpp +++ b/plugins/Lv2Instrument/Lv2Instrument.cpp @@ -1,7 +1,7 @@ /* * Lv2Instrument.cpp - implementation of LV2 instrument * - * Copyright (c) 2018-2020 Johannes Lorenz + * Copyright (c) 2018-2023 Johannes Lorenz * * This file is part of LMMS - https://lmms.io * @@ -55,10 +55,10 @@ Plugin::Descriptor PLUGIN_EXPORT lv2instrument_plugin_descriptor = "plugin for using arbitrary LV2 instruments inside LMMS."), "Johannes Lorenz ", 0x0100, - Plugin::Instrument, + Plugin::Type::Instrument, new PluginPixmapLoader("logo"), nullptr, - new Lv2SubPluginFeatures(Plugin::Instrument) + new Lv2SubPluginFeatures(Plugin::Type::Instrument) }; } @@ -76,17 +76,16 @@ Lv2Instrument::Lv2Instrument(InstrumentTrack *instrumentTrackArg, Instrument(instrumentTrackArg, &lv2instrument_plugin_descriptor, key), Lv2ControlBase(this, key->attributes["uri"]) { - if (Lv2ControlBase::isValid()) - { - connect(instrumentTrack()->pitchRangeModel(), SIGNAL(dataChanged()), - this, SLOT(updatePitchRange()), Qt::DirectConnection); - connect(Engine::audioEngine(), &AudioEngine::sampleRateChanged, - this, [this](){Lv2ControlBase::reloadPlugin();}); + clearRunningNotes(); - // now we need a play-handle which cares for calling play() - auto iph = new InstrumentPlayHandle(this, instrumentTrackArg); - Engine::audioEngine()->addPlayHandle(iph); - } + 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); } @@ -95,13 +94,39 @@ Lv2Instrument::Lv2Instrument(InstrumentTrack *instrumentTrackArg, Lv2Instrument::~Lv2Instrument() { Engine::audioEngine()->removePlayHandlesOfTypes(instrumentTrack(), - PlayHandle::TypeNotePlayHandle | PlayHandle::TypeInstrumentPlayHandle); + PlayHandle::Type::NotePlayHandle | PlayHandle::Type::InstrumentPlayHandle); } -bool Lv2Instrument::isValid() const { return Lv2ControlBase::isValid(); } +void Lv2Instrument::reload() +{ + Lv2ControlBase::reload(); + clearRunningNotes(); + emit modelChanged(); +} + + + + +void Lv2Instrument::clearRunningNotes() +{ +#ifdef LV2_INSTRUMENT_USE_MIDI + for (int i = 0; i < NumKeys; ++i) { m_runningNotes[i] = 0; } +#endif +} + + + + +void Lv2Instrument::onSampleRateChanged() +{ + // TODO: once lv2 options are implemented, + // plugins that support it might allow changing their samplerate + // through it instead of reloading + reload(); +} @@ -164,8 +189,6 @@ void Lv2Instrument::play(sampleFrame *buf) copyModelsToLmms(); copyBuffersToLmms(buf, fpp); - - instrumentTrack()->processAudioBuffer(buf, fpp, nullptr); } @@ -196,25 +219,9 @@ QString Lv2Instrument::nodeName() const -DataFile::Types Lv2Instrument::settingsType() -{ - return DataFile::InstrumentTrackSettings; -} - - - - -void Lv2Instrument::setNameFromFile(const QString &name) -{ - instrumentTrack()->setName(name); -} - - - namespace gui { - /* Lv2InsView */ @@ -227,7 +234,7 @@ Lv2InsView::Lv2InsView(Lv2Instrument *_instrument, QWidget *_parent) : setAutoFillBackground(true); if (m_reloadPluginButton) { connect(m_reloadPluginButton, &QPushButton::clicked, - this, [this](){ this->castModel()->reloadPlugin();} ); + this, [this](){ this->castModel()->reload();} ); } if (m_toggleUIButton) { connect(m_toggleUIButton, &QPushButton::toggled, @@ -280,9 +287,20 @@ void Lv2InsView::dropEvent(QDropEvent *_de) +void Lv2InsView::hideEvent(QHideEvent *event) +{ + closeHelpWindow(); + QWidget::hideEvent(event); +} + + + + void Lv2InsView::modelChanged() { Lv2ViewBase::modelChanged(castModel()); + connect(castModel(), &Lv2Instrument::modelChanged, + this, [this](){ this->modelChanged();} ); } @@ -295,9 +313,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 e9a6cc2fd..de41dc958 100644 --- a/plugins/Lv2Instrument/Lv2Instrument.h +++ b/plugins/Lv2Instrument/Lv2Instrument.h @@ -1,7 +1,7 @@ /* * Lv2Instrument.h - implementation of LV2 instrument * - * Copyright (c) 2018-2020 Johannes Lorenz + * Copyright (c) 2018-2023 Johannes Lorenz * * This file is part of LMMS - https://lmms.io * @@ -50,6 +50,8 @@ class Lv2InsView; class Lv2Instrument : public Instrument, public Lv2ControlBase { Q_OBJECT +signals: + void modelChanged(); public: /* initialization @@ -57,8 +59,8 @@ public: Lv2Instrument(InstrumentTrack *instrumentTrackArg, Descriptor::SubPluginFeatures::Key* key); ~Lv2Instrument() override; - //! Must be checked after ctor or reload - bool isValid() const; + void reload(); + void onSampleRateChanged(); /* load/save @@ -85,9 +87,9 @@ public: Flags flags() const override { #ifdef LV2_INSTRUMENT_USE_MIDI - return IsSingleStreamed | IsMidiBased; + return Flag::IsSingleStreamed | Flag::IsMidiBased; #else - return IsSingleStreamed; + return Flag::IsSingleStreamed; #endif } gui::PluginView* instantiateView(QWidget *parent) override; @@ -97,12 +99,11 @@ private slots: private: QString nodeName() const override; - DataFile::Types settingsType() override; - void setNameFromFile(const QString &name) override; #ifdef LV2_INSTRUMENT_USE_MIDI std::array m_runningNotes = {}; #endif + void clearRunningNotes(); friend class gui::Lv2InsView; }; @@ -121,6 +122,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 0d18d8ae1..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" @@ -51,7 +52,7 @@ Plugin::Descriptor PLUGIN_EXPORT midiexport_plugin_descriptor = "Mohamed Abdel Maksoud and " "Hyunjin Song ", 0x0100, - Plugin::ExportFilter, + Plugin::Type::ExportFilter, nullptr, nullptr, nullptr, @@ -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::InstrumentTrack) nTracks++; - for (const Track* track : patternStoreTracks) if (track->type() == Track::InstrumentTrack) nTracks++; + 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; @@ -97,17 +95,17 @@ bool MidiExport::tryExport(const TrackContainer::TrackList &tracks, // midi tracks for (Track* track : tracks) { - DataFile dataFile(DataFile::SongProject); + DataFile dataFile(DataFile::Type::SongProject); MTrack mtrack; - if (track->type() == Track::InstrumentTrack) + if (track->type() == Track::Type::Instrument) { mtrack.addName(track->name().toStdString(), 0); //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; @@ -143,9 +141,9 @@ bool MidiExport::tryExport(const TrackContainer::TrackList &tracks, midiout.writeRawData((char *)buffer.data(), size); } - if (track->type() == Track::PatternTrack) + if (track->type() == Track::Type::Pattern) { - patternTrack = dynamic_cast(track); + auto patternTrack = dynamic_cast(track); element = patternTrack->saveState(dataFile, dataFile.content()); std::vector> plist; @@ -169,7 +167,7 @@ bool MidiExport::tryExport(const TrackContainer::TrackList &tracks, // for each instrument in the pattern editor for (Track* track : patternStoreTracks) { - DataFile dataFile(DataFile::SongProject); + DataFile dataFile(DataFile::Type::SongProject); MTrack mtrack; // begin at the first pattern track (first pattern) @@ -177,13 +175,13 @@ bool MidiExport::tryExport(const TrackContainer::TrackList &tracks, std::vector> st; - if (track->type() != Track::InstrumentTrack) continue; + if (track->type() != Track::Type::Instrument) continue; mtrack.addName(track->name().toStdString(), 0); //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.cpp b/plugins/MidiImport/MidiImport.cpp index 6a0314966..785cd079a 100644 --- a/plugins/MidiImport/MidiImport.cpp +++ b/plugins/MidiImport/MidiImport.cpp @@ -71,7 +71,7 @@ Plugin::Descriptor PLUGIN_EXPORT midiimport_plugin_descriptor = "Filter for importing MIDI-files into LMMS" ), "Tobias Doerffel ", 0x0100, - Plugin::ImportFilter, + Plugin::Type::ImportFilter, nullptr, nullptr, nullptr, @@ -165,7 +165,7 @@ public: // in the main thread. This should probably be // removed if that ever changes. qApp->processEvents(); - at = dynamic_cast( Track::create( Track::AutomationTrack, tc ) ); + at = dynamic_cast( Track::create( Track::Type::Automation, tc ) ); } if( tn != "") { at->setName( tn ); @@ -227,7 +227,7 @@ public: if( !it ) { // Keep LMMS responsive qApp->processEvents(); - it = dynamic_cast( Track::create( Track::InstrumentTrack, tc ) ); + it = dynamic_cast( Track::create( Track::Type::Instrument, tc ) ); #ifdef LMMS_HAVE_FLUIDSYNTH it_inst = it->loadInstrument( "sf2player" ); @@ -328,9 +328,9 @@ bool MidiImport::readSMF( TrackContainer* tc ) // NOTE: unordered_map::operator[] creates a new element if none exists MeterModel & timeSigMM = Engine::getSong()->getTimeSigModel(); - auto nt = dynamic_cast(Track::create(Track::AutomationTrack, Engine::getSong())); + auto nt = dynamic_cast(Track::create(Track::Type::Automation, Engine::getSong())); nt->setName(tr("MIDI Time Signature Numerator")); - auto dt = dynamic_cast(Track::create(Track::AutomationTrack, Engine::getSong())); + auto dt = dynamic_cast(Track::create(Track::Type::Automation, Engine::getSong())); dt->setName(tr("MIDI Time Signature Denominator")); auto timeSigNumeratorPat = new AutomationClip(nt); timeSigNumeratorPat->setDisplayName(tr("Numerator")); @@ -358,7 +358,11 @@ bool MidiImport::readSMF( TrackContainer* tc ) pd.setValue( 2 ); // Tempo stuff - AutomationClip * tap = tc->tempoAutomationClip(); + auto tt = dynamic_cast(Track::create(Track::Type::Automation, Engine::getSong())); + tt->setName(tr("Tempo")); + auto tap = new AutomationClip(tt); + tap->setDisplayName(tr("Tempo")); + tap->addObject(&Engine::getSong()->tempoModel()); if( tap ) { tap->clear(); 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/MidiImport/portsmf/allegro.h b/plugins/MidiImport/portsmf/allegro.h index ca5859aef..6210652e2 100644 --- a/plugins/MidiImport/portsmf/allegro.h +++ b/plugins/MidiImport/portsmf/allegro.h @@ -49,6 +49,7 @@ #ifndef ALLEGRO_H #define ALLEGRO_H #include +#include #include #include #include diff --git a/plugins/Monstro/Monstro.cpp b/plugins/Monstro/Monstro.cpp index 3a9737fdb..3de9fbce6 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" @@ -53,7 +52,7 @@ Plugin::Descriptor PLUGIN_EXPORT monstro_plugin_descriptor = "Monstrous 3-oscillator synth with modulation matrix" ), "Vesa Kivimäki ", 0x0100, - Plugin::Instrument, + Plugin::Type::Instrument, new PluginPixmapLoader( "logo" ), nullptr, nullptr, @@ -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; @@ -1030,7 +1067,7 @@ void MonstroInstrument::playNote( NotePlayHandle * _n, const fpp_t frames = _n->framesLeftForCurrentPeriod(); const f_cnt_t offset = _n->noteOffset(); - if ( _n->totalFramesPlayed() == 0 || _n->m_pluginData == nullptr ) + if (!_n->m_pluginData) { _n->m_pluginData = new MonstroSynth( this, _n ); } @@ -1040,8 +1077,6 @@ void MonstroInstrument::playNote( NotePlayHandle * _n, ms->renderOutput( frames, _working_buffer + offset ); //applyRelease( _working_buffer, _n ); // we have our own release - - instrumentTrack()->processAudioBuffer( _working_buffer, frames + offset, _n ); } void MonstroInstrument::deleteNotePluginData( NotePlayHandle * _n ) @@ -1658,7 +1693,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" ) ) @@ -1673,18 +1707,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" ) @@ -1692,7 +1723,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 319b7e000..919409b2d 100644 --- a/plugins/Monstro/Monstro.h +++ b/plugins/Monstro/Monstro.h @@ -43,14 +43,14 @@ // #define makeknob( name, x, y, hint, unit, oname ) \ - name = new Knob( knobStyled, view ); \ + name = new Knob( KnobType::Styled, view ); \ name ->move( x, y ); \ name ->setHintText( hint, unit ); \ name ->setObjectName( oname ); \ name ->setFixedSize( 20, 20 ); #define maketsknob( name, x, y, hint, unit, oname ) \ - name = new TempoSyncKnob( knobStyled, view ); \ + name = new TempoSyncKnob( KnobType::Styled, view ); \ name ->move( x, y ); \ name ->setHintText( hint, unit ); \ name ->setObjectName( oname ); \ @@ -173,7 +173,6 @@ class ComboBox; class MonstroSynth { - MM_OPERATORS public: MonstroSynth( MonstroInstrument * _i, NotePlayHandle * _nph ); virtual ~MonstroSynth() = default; @@ -211,19 +210,19 @@ private: break; case WAVE_TRI: //return Oscillator::triangleSample( _ph ); - return BandLimitedWave::oscillate( _ph, _wavelen, BandLimitedWave::BLTriangle ); + return BandLimitedWave::oscillate( _ph, _wavelen, BandLimitedWave::Waveform::BLTriangle ); break; case WAVE_SAW: //return Oscillator::sawSample( _ph ); - return BandLimitedWave::oscillate( _ph, _wavelen, BandLimitedWave::BLSaw ); + return BandLimitedWave::oscillate( _ph, _wavelen, BandLimitedWave::Waveform::BLSaw ); break; case WAVE_RAMP: //return Oscillator::sawSample( _ph ) * -1.0; - return BandLimitedWave::oscillate( _ph, _wavelen, BandLimitedWave::BLSaw ) * -1.0; + return BandLimitedWave::oscillate( _ph, _wavelen, BandLimitedWave::Waveform::BLSaw ) * -1.0; break; case WAVE_SQR: //return Oscillator::squareSample( _ph ); - return BandLimitedWave::oscillate( _ph, _wavelen, BandLimitedWave::BLSquare ); + return BandLimitedWave::oscillate( _ph, _wavelen, BandLimitedWave::Waveform::BLSquare ); break; case WAVE_SQRSOFT: { @@ -236,7 +235,7 @@ private: } case WAVE_MOOG: //return Oscillator::moogSawSample( _ph ); - return BandLimitedWave::oscillate( _ph, _wavelen, BandLimitedWave::BLMoog ); + return BandLimitedWave::oscillate( _ph, _wavelen, BandLimitedWave::Waveform::BLMoog ); break; case WAVE_SINABS: return qAbs( Oscillator::sinSample( _ph ) ); diff --git a/plugins/MultitapEcho/MultitapEcho.cpp b/plugins/MultitapEcho/MultitapEcho.cpp index 05c3f30fe..ff3ca828a 100644 --- a/plugins/MultitapEcho/MultitapEcho.cpp +++ b/plugins/MultitapEcho/MultitapEcho.cpp @@ -41,7 +41,7 @@ Plugin::Descriptor PLUGIN_EXPORT multitapecho_plugin_descriptor = QT_TRANSLATE_NOOP( "PluginBrowser", "A multitap echo delay plugin" ), "Vesa Kivimäki ", 0x0100, - Plugin::Effect, + Plugin::Type::Effect, new PluginPixmapLoader( "logo" ), nullptr, nullptr, @@ -58,7 +58,7 @@ MultitapEchoEffect::MultitapEchoEffect( Model* parent, const Descriptor::SubPlug m_sampleRate( Engine::audioEngine()->processingSampleRate() ), 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/MultitapEchoControlDialog.cpp b/plugins/MultitapEcho/MultitapEchoControlDialog.cpp index dbf428e5f..e89137bf0 100644 --- a/plugins/MultitapEcho/MultitapEchoControlDialog.cpp +++ b/plugins/MultitapEcho/MultitapEchoControlDialog.cpp @@ -48,8 +48,8 @@ MultitapEchoControlDialog::MultitapEchoControlDialog( MultitapEchoControls * con // graph widgets - auto ampGraph = new Graph(this, Graph::BarStyle, 204, 105); - auto lpGraph = new Graph(this, Graph::BarStyle, 204, 105); + auto ampGraph = new Graph(this, Graph::Style::Bar, 204, 105); + auto lpGraph = new Graph(this, Graph::Style::Bar, 204, 105); ampGraph->move( 30, 10 ); lpGraph->move( 30, 125 ); @@ -78,26 +78,26 @@ MultitapEchoControlDialog::MultitapEchoControlDialog( MultitapEchoControls * con // knobs - auto stepLength = new TempoSyncKnob(knobBright_26, this); + auto stepLength = new TempoSyncKnob(KnobType::Bright26, this); stepLength->move( 100, 245 ); stepLength->setModel( & controls->m_stepLength ); stepLength->setLabel( tr( "Length" ) ); stepLength->setHintText( tr( "Step length:" ) , " ms" ); - auto dryGain = new Knob(knobBright_26, this); + auto dryGain = new Knob(KnobType::Bright26, this); dryGain->move( 150, 245 ); dryGain->setModel( & controls->m_dryGain ); dryGain->setLabel( tr( "Dry" ) ); dryGain->setHintText( tr( "Dry gain:" ) , " dBFS" ); - auto stages = new Knob(knobBright_26, this); + auto stages = new Knob(KnobType::Bright26, this); stages->move( 200, 245 ); stages->setModel( & controls->m_stages ); stages->setLabel( tr( "Stages" ) ); stages->setHintText( tr( "Low-pass stages:" ) , "x" ); // switch led - auto swapInputs = new LedCheckBox("Swap inputs", this, tr("Swap inputs"), LedCheckBox::Green); + auto swapInputs = new LedCheckBox("Swap inputs", this, tr("Swap inputs"), LedCheckBox::LedColor::Green); swapInputs->move( 20, 275 ); swapInputs->setModel( & controls->m_swapInputs ); swapInputs->setToolTip(tr("Swap left and right input channels for reflections")); diff --git a/plugins/Nes/Nes.cpp b/plugins/Nes/Nes.cpp index d47ebd728..df88c4942 100644 --- a/plugins/Nes/Nes.cpp +++ b/plugins/Nes/Nes.cpp @@ -51,7 +51,7 @@ Plugin::Descriptor PLUGIN_EXPORT nes_plugin_descriptor = "A NES-like synthesizer" ), "Vesa Kivimäki ", 0x0100, - Plugin::Instrument, + Plugin::Type::Instrument, new PluginPixmapLoader( "logo" ), nullptr, nullptr, @@ -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" ) ), @@ -550,7 +550,7 @@ void NesInstrument::playNote( NotePlayHandle * n, sampleFrame * workingBuffer ) const fpp_t frames = n->framesLeftForCurrentPeriod(); const f_cnt_t offset = n->noteOffset(); - if ( n->totalFramesPlayed() == 0 || n->m_pluginData == nullptr ) + if (!n->m_pluginData) { auto nes = new NesObject(this, Engine::audioEngine()->processingSampleRate(), n); n->m_pluginData = nes; @@ -561,8 +561,6 @@ void NesInstrument::playNote( NotePlayHandle * n, sampleFrame * workingBuffer ) nes->renderOutput( workingBuffer + offset, frames ); applyRelease( workingBuffer, n ); - - instrumentTrack()->processAudioBuffer( workingBuffer, frames + offset, n ); } @@ -721,7 +719,6 @@ namespace gui { -QPixmap * NesInstrumentView::s_artwork = nullptr; NesInstrumentView::NesInstrumentView( Instrument * instrument, QWidget * parent ) : @@ -730,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 91e5e556c..a05b3a2f8 100644 --- a/plugins/Nes/Nes.h +++ b/plugins/Nes/Nes.h @@ -31,11 +31,10 @@ #include "InstrumentView.h" #include "AutomatableModel.h" #include "PixmapButton.h" -#include "MemoryManager.h" #define makeknob( name, x, y, hint, unit, oname ) \ - name = new Knob( knobStyled, this ); \ + name = new Knob( KnobType::Styled, this ); \ name ->move( x, y ); \ name ->setHintText( hint, unit ); \ name ->setObjectName( 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; @@ -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 3a18c1e3f..f0571ba5c 100644 --- a/plugins/OpulenZ/OpulenZ.cpp +++ b/plugins/OpulenZ/OpulenZ.cpp @@ -73,7 +73,7 @@ Plugin::Descriptor PLUGIN_EXPORT opulenz_plugin_descriptor = "2-operator FM Synth" ), "Raine M. Ekman ", 0x0100, - Plugin::Instrument, + Plugin::Type::Instrument, new PluginPixmapLoader( "logo" ), "sbi", nullptr, @@ -222,8 +222,8 @@ OpulenzInstrument::OpulenzInstrument( InstrumentTrack * _instrument_track ) : OpulenzInstrument::~OpulenzInstrument() { delete theEmulator; Engine::audioEngine()->removePlayHandlesOfTypes( instrumentTrack(), - PlayHandle::TypeNotePlayHandle - | PlayHandle::TypeInstrumentPlayHandle ); + PlayHandle::Type::NotePlayHandle + | PlayHandle::Type::InstrumentPlayHandle ); delete [] renderbuffer; } @@ -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; } @@ -412,10 +404,6 @@ void OpulenzInstrument::play( sampleFrame * _working_buffer ) } } emulatorMutex.unlock(); - - // Throw the data to the track... - instrumentTrack()->processAudioBuffer( _working_buffer, frameCount, nullptr ); - } @@ -508,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 ); } } @@ -686,7 +673,7 @@ OpulenzInstrumentView::OpulenzInstrumentView( Instrument * _instrument, { #define KNOB_GEN(knobname, hinttext, hintunit,xpos,ypos) \ - knobname = new Knob( knobStyled, this );\ + knobname = new Knob( KnobType::Styled, this );\ knobname->setHintText( tr(hinttext), hintunit );\ knobname->setFixedSize(22,22);\ knobname->setCenterPointX(11.0);\ diff --git a/plugins/OpulenZ/OpulenZ.h b/plugins/OpulenZ/OpulenZ.h index 1f999252f..a3e11a6c0 100644 --- a/plugins/OpulenZ/OpulenZ.h +++ b/plugins/OpulenZ/OpulenZ.h @@ -66,7 +66,7 @@ public: Flags flags() const override { - return IsSingleStreamed | IsMidiBased; + return Flag::IsSingleStreamed | Flag::IsMidiBased; } bool handleMidiEvent( const MidiEvent& event, const TimePos& time, f_cnt_t offset = 0 ) override; diff --git a/plugins/Organic/Organic.cpp b/plugins/Organic/Organic.cpp index 6c2cde773..2dba63629 100644 --- a/plugins/Organic/Organic.cpp +++ b/plugins/Organic/Organic.cpp @@ -22,13 +22,10 @@ * */ - #include "Organic.h" - #include - #include "Engine.h" #include "AudioEngine.h" #include "InstrumentTrack.h" @@ -38,7 +35,6 @@ #include "PixmapButton.h" #include "embed.h" - #include "plugin_export.h" namespace lmms @@ -56,7 +52,7 @@ Plugin::Descriptor PLUGIN_EXPORT organic_plugin_descriptor = "Additive Synthesizer for organ-like sounds" ), "Andreas Brandmaier ", 0x0100, - Plugin::Instrument, + Plugin::Type::Instrument, new PluginPixmapLoader( "logo" ), nullptr, nullptr, @@ -64,7 +60,6 @@ Plugin::Descriptor PLUGIN_EXPORT organic_plugin_descriptor = } -QPixmap * gui::OrganicInstrumentView::s_artwork = nullptr; float * OrganicInstrument::s_harmonics = nullptr; /*********************************************************************** @@ -78,7 +73,9 @@ float * OrganicInstrument::s_harmonics = nullptr; OrganicInstrument::OrganicInstrument( InstrumentTrack * _instrument_track ) : Instrument( _instrument_track, &organic_plugin_descriptor ), - m_modulationAlgo( Oscillator::SignalMix, Oscillator::SignalMix, Oscillator::SignalMix), + m_modulationAlgo(static_cast(Oscillator::ModulationAlgo::SignalMix), + static_cast(Oscillator::ModulationAlgo::SignalMix), + static_cast(Oscillator::ModulationAlgo::SignalMix)), m_fx1Model( 0.0f, 0.0f, 0.99f, 0.01f , this, tr( "Distortion" ) ), m_volModel( 100.0f, 0.0f, 200.0f, 1.0f, this, tr( "Volume" ) ) { @@ -159,61 +156,59 @@ OrganicInstrument::~OrganicInstrument() -void OrganicInstrument::saveSettings( QDomDocument & _doc, QDomElement & _this ) +void OrganicInstrument::saveSettings(QDomDocument& doc, QDomElement& elem) { - _this.setAttribute( "num_osc", QString::number( m_numOscillators ) ); - m_fx1Model.saveSettings( _doc, _this, "foldback" ); - m_volModel.saveSettings( _doc, _this, "vol" ); + elem.setAttribute("num_osc", QString::number(m_numOscillators)); + m_fx1Model.saveSettings(doc, elem, "foldback"); + m_volModel.saveSettings(doc, elem, "vol"); - for( int i = 0; i < m_numOscillators; ++i ) + for (int i = 0; i < m_numOscillators; ++i) { - QString is = QString::number( i ); - m_osc[i]->m_volModel.saveSettings( _doc, _this, "vol" + is ); - m_osc[i]->m_panModel.saveSettings( _doc, _this, "pan" + is ); - m_osc[i]->m_harmModel.saveSettings( _doc, _this, "newharmonic" + is ); - - m_osc[i]->m_detuneModel.saveSettings( _doc, _this, "newdetune" - + is ); - m_osc[i]->m_oscModel.saveSettings( _doc, _this, "wavetype" - + is ); + const auto is = QString::number(i); + m_osc[i]->m_volModel.saveSettings(doc, elem, "vol" + is); + m_osc[i]->m_panModel.saveSettings(doc, elem, "pan" + is); + m_osc[i]->m_harmModel.saveSettings(doc, elem, "newharmonic" + is); + m_osc[i]->m_detuneModel.saveSettings(doc, elem, "newdetune" + is); + m_osc[i]->m_oscModel.saveSettings(doc, elem, "wavetype" + is); } } -void OrganicInstrument::loadSettings( const QDomElement & _this ) +void OrganicInstrument::loadSettings(const QDomElement& elem) { -// m_numOscillators = _this.attribute( "num_osc" ). - // toInt(); - - for( int i = 0; i < m_numOscillators; ++i ) + for (int i = 0; i < m_numOscillators; ++i) { - QString is = QString::number( i ); - m_osc[i]->m_volModel.loadSettings( _this, "vol" + is ); - if( _this.hasAttribute( "detune" + is ) ) - { - m_osc[i]->m_detuneModel.setValue( _this.attribute( "detune" ).toInt() * 12 ); - } - else - { - m_osc[i]->m_detuneModel.loadSettings( _this, "newdetune" + is ); - } - m_osc[i]->m_panModel.loadSettings( _this, "pan" + is ); - m_osc[i]->m_oscModel.loadSettings( _this, "wavetype" + is ); + const auto is = QString::number(i); - if( _this.hasAttribute( "newharmonic" + is ) ) + m_osc[i]->m_volModel.loadSettings(elem, "vol" + is); + + if (elem.hasAttribute("detune" + is) || !elem.firstChildElement("detune" + is).isNull()) { - m_osc[i]->m_harmModel.loadSettings( _this, "newharmonic" + is ); + m_osc[i]->m_detuneModel.loadSettings(elem, "detune" + is); + m_osc[i]->m_detuneModel.setValue(m_osc[i]->m_detuneModel.value() * 12); // compat } else { - m_osc[i]->m_harmModel.setValue( static_cast( i ) ); + m_osc[i]->m_detuneModel.loadSettings(elem, "newdetune" + is); + } + + m_osc[i]->m_panModel.loadSettings(elem, "pan" + is); + m_osc[i]->m_oscModel.loadSettings(elem, "wavetype" + is); + + if (elem.hasAttribute("newharmonic" + is) || !elem.firstChildElement("newharmonic" + is).isNull()) + { + m_osc[i]->m_harmModel.loadSettings(elem, "newharmonic" + is); + } + else + { + m_osc[i]->m_harmModel.setValue(static_cast(i)); } } - m_volModel.loadSettings( _this, "vol" ); - m_fx1Model.loadSettings( _this, "foldback" ); + m_volModel.loadSettings(elem, "vol"); + m_fx1Model.loadSettings(elem, "foldback"); } @@ -231,7 +226,7 @@ void OrganicInstrument::playNote( NotePlayHandle * _n, const fpp_t frames = _n->framesLeftForCurrentPeriod(); const f_cnt_t offset = _n->noteOffset(); - if( _n->totalFramesPlayed() == 0 || _n->m_pluginData == nullptr ) + if (!_n->m_pluginData) { auto oscs_l = std::array{}; auto oscs_r = std::array{}; @@ -316,8 +311,6 @@ void OrganicInstrument::playNote( NotePlayHandle * _n, } // -- -- - - instrumentTrack()->processAudioBuffer( _working_buffer, frames + offset, _n ); } @@ -409,7 +402,7 @@ class OrganicKnob : public Knob { public: OrganicKnob( QWidget * _parent ) : - Knob( knobStyled, _parent ) + Knob( KnobType::Styled, _parent ) { setFixedSize( 21, 21 ); } @@ -426,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 @@ -457,12 +450,6 @@ OrganicInstrumentView::OrganicInstrumentView( Instrument * _instrument, oi, SLOT( randomiseSettings() ) ); - if( s_artwork == nullptr ) - { - s_artwork = new QPixmap( PLUGIN_NAME::getIconPixmap( - "artwork" ) ); - } - } @@ -512,7 +499,7 @@ void OrganicInstrumentView::modelChanged() oscKnob->setHintText( tr( "Osc %1 waveform:" ).arg( i + 1 ), QString() ); // setup volume-knob - auto volKnob = new Knob(knobStyled, this); + auto volKnob = new Knob(KnobType::Styled, this); volKnob->setVolumeKnob( true ); volKnob->move( x + i * colWidth, y + rowHeight*1 ); volKnob->setFixedSize( 21, 21 ); @@ -566,7 +553,7 @@ void OrganicInstrumentView::updateKnobHint() OscillatorObject::OscillatorObject( Model * _parent, int _index ) : Model( _parent ), - m_waveShape( Oscillator::SineWave, 0, Oscillator::NumWaveShapes-1, this ), + m_waveShape( static_cast(Oscillator::WaveShape::Sine), 0, Oscillator::NumWaveShapes-1, this ), m_oscModel( 0.0f, 0.0f, 5.0f, 1.0f, this, tr( "Osc %1 waveform" ).arg( _index + 1 ) ), m_harmModel( static_cast( _index ), 0.0f, 17.0f, 1.0f, @@ -576,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 ) ) { } @@ -588,15 +575,15 @@ void OscillatorObject::oscButtonChanged() static auto shapes = std::array { - Oscillator::SineWave, - Oscillator::SawWave, - Oscillator::SquareWave, - Oscillator::TriangleWave, - Oscillator::MoogSawWave, - Oscillator::ExponentialWave + Oscillator::WaveShape::Sine, + Oscillator::WaveShape::Saw, + Oscillator::WaveShape::Square, + Oscillator::WaveShape::Triangle, + Oscillator::WaveShape::MoogSaw, + Oscillator::WaveShape::Exponential } ; - m_waveShape.setValue( shapes[(int)roundf( m_oscModel.value() )] ); + m_waveShape.setValue( static_cast(shapes[(int)roundf( m_oscModel.value() )]) ); } diff --git a/plugins/Organic/Organic.h b/plugins/Organic/Organic.h index 057a4a5b4..e50550e5e 100644 --- a/plugins/Organic/Organic.h +++ b/plugins/Organic/Organic.h @@ -22,9 +22,8 @@ * */ - -#ifndef ORGANIC_H -#define ORGANIC_H +#ifndef LMMS_ORGANIC_H +#define LMMS_ORGANIC_H #include @@ -85,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; @@ -131,8 +129,8 @@ public: void deleteNotePluginData( NotePlayHandle * _n ) override; - void saveSettings( QDomDocument & _doc, QDomElement & _parent ) override; - void loadSettings( const QDomElement & _this ) override; + void saveSettings(QDomDocument& doc, QDomElement& elem) override; + void loadSettings(const QDomElement& elem) override; QString nodeName() const override; @@ -160,7 +158,6 @@ private: struct oscPtr { - MM_OPERATORS Oscillator * oscLeft; Oscillator * oscRight; float phaseOffsetLeft[NUM_OSCILLATORS]; @@ -197,7 +194,6 @@ private: struct OscillatorKnobs { - MM_OPERATORS OscillatorKnobs( Knob * h, Knob * v, @@ -228,7 +224,6 @@ private: int m_numOscillators; - static QPixmap * s_artwork; protected slots: void updateKnobHint(); @@ -239,4 +234,4 @@ protected slots: } // namespace lmms -#endif +#endif // LMMS_ORGANIC_H diff --git a/plugins/Patman/Patman.cpp b/plugins/Patman/Patman.cpp index a8f75db60..d2f4aee4e 100644 --- a/plugins/Patman/Patman.cpp +++ b/plugins/Patman/Patman.cpp @@ -61,7 +61,7 @@ Plugin::Descriptor PLUGIN_EXPORT patman_plugin_descriptor = "GUS-compatible patch instrument" ), "Javier Serrano Polo ", 0x0100, - Plugin::Instrument, + Plugin::Type::Instrument, new PluginPixmapLoader( "logo" ), "pat", nullptr, @@ -144,7 +144,7 @@ void PatmanInstrument::playNote( NotePlayHandle * _n, const fpp_t frames = _n->framesLeftForCurrentPeriod(); const f_cnt_t offset = _n->noteOffset(); - if( !_n->m_pluginData ) + if (!_n->m_pluginData) { selectSample( _n ); } @@ -153,12 +153,10 @@ 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::LoopOn : SampleBuffer::LoopOff ) ) + 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 ); - instrumentTrack()->processAudioBuffer( _working_buffer, - frames + offset, _n ); } else { @@ -172,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; } @@ -201,8 +198,8 @@ void PatmanInstrument::setFile( const QString & _patch_file, bool _rename ) // named it self m_patchFile = PathUtil::toShortestRelative( _patch_file ); - LoadErrors error = loadPatch( PathUtil::toAbsolute( _patch_file ) ); - if( error ) + LoadError error = loadPatch( PathUtil::toAbsolute( _patch_file ) ); + if( error != LoadError::OK ) { printf("Load error\n"); } @@ -213,7 +210,7 @@ void PatmanInstrument::setFile( const QString & _patch_file, bool _rename ) -PatmanInstrument::LoadErrors PatmanInstrument::loadPatch( +PatmanInstrument::LoadError PatmanInstrument::loadPatch( const QString & _filename ) { unloadCurrentPatch(); @@ -222,7 +219,7 @@ PatmanInstrument::LoadErrors PatmanInstrument::loadPatch( if( !fd ) { perror( "fopen" ); - return( LoadOpen ); + return( LoadError::Open ); } auto header = std::array{}; @@ -232,19 +229,19 @@ PatmanInstrument::LoadErrors PatmanInstrument::loadPatch( && memcmp(header.data(), "GF1PATCH100\0ID#000002", 22))) { fclose( fd ); - return( LoadNotGUS ); + return( LoadError::NotGUS ); } if( header[82] != 1 && header[82] != 0 ) { fclose( fd ); - return( LoadInstruments ); + return( LoadError::Instruments ); } if( header[151] != 1 && header[151] != 0 ) { fclose( fd ); - return( LoadLayers ); + return( LoadError::Layers ); } int sample_count = header[198]; @@ -256,14 +253,14 @@ PatmanInstrument::LoadErrors PatmanInstrument::loadPatch( if ( fseek( fd, x, SEEK_CUR ) == -1 ) \ { \ fclose( fd ); \ - return( LoadIO ); \ + return( LoadError::IO ); \ } #define READ_SHORT( x ) \ if ( fread( &tmpshort, 2, 1, fd ) != 1 ) \ { \ fclose( fd ); \ - return( LoadIO ); \ + return( LoadError::IO ); \ } \ x = (unsigned short)swap16IfBE( tmpshort ); @@ -271,7 +268,7 @@ PatmanInstrument::LoadErrors PatmanInstrument::loadPatch( if ( fread( &x, 4, 1, fd ) != 1 ) \ { \ fclose( fd ); \ - return( LoadIO ); \ + return( LoadError::IO ); \ } \ x = (unsigned)swap32IfBE( x ); @@ -295,25 +292,24 @@ PatmanInstrument::LoadErrors PatmanInstrument::loadPatch( if ( fread( &modes, 1, 1, fd ) != 1 ) { fclose( fd ); - return( LoadIO ); + return( LoadError::IO ); } // skip scale frequency, scale factor, reserved space 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( LoadIO ); + return( LoadError::IO ); } sample = swap16IfBE( sample ); if( modes & MODES_UNSIGNED ) @@ -329,15 +325,14 @@ PatmanInstrument::LoadErrors 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( LoadIO ); + return( LoadError::IO ); } if( modes & MODES_UNSIGNED ) { @@ -358,9 +353,8 @@ PatmanInstrument::LoadErrors 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 ) { @@ -368,13 +362,12 @@ PatmanInstrument::LoadErrors PatmanInstrument::loadPatch( psample->setLoopEndFrame( loop_end ); } - m_patchSamples.push_back( psample ); + m_patchSamples.push_back(psample); - delete[] wave_samples; delete[] data; } fclose( fd ); - return( LoadOK ); + return( LoadError::OK ); } @@ -384,7 +377,6 @@ void PatmanInstrument::unloadCurrentPatch() { while( !m_patchSamples.empty() ) { - sharedObject::unref( m_patchSamples.back() ); m_patchSamples.pop_back(); } } @@ -397,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) { @@ -414,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; } @@ -446,7 +431,7 @@ namespace gui PatmanView::PatmanView( Instrument * _instrument, QWidget * _parent ) : InstrumentViewFixedSize( _instrument, _parent ), - m_pi( nullptr ) + m_pi(castModel()) { setAutoFillBackground( true ); QPalette pal; @@ -487,7 +472,15 @@ PatmanView::PatmanView( Instrument * _instrument, QWidget * _parent ) : "tune_off" ) ); m_tuneButton->setToolTip(tr("Tune mode")); - m_displayFilename = tr( "No file selected" ); + + if (m_pi->m_patchFile.isEmpty()) + { + m_displayFilename = tr("No file selected"); + } + else + { + updateFilename(); + } setAcceptDrops( true ); } @@ -552,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 @@ -622,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 acdf4f7ef..486524522 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 { @@ -86,29 +86,28 @@ 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; - enum LoadErrors + enum class LoadError { - LoadOK, - LoadOpen, - LoadNotGUS, - LoadInstruments, - LoadLayers, - LoadIO + OK, + Open, + NotGUS, + Instruments, + Layers, + IO } ; - LoadErrors loadPatch( const QString & _filename ); + LoadError loadPatch( const QString & _filename ); void unloadCurrentPatch(); void selectSample( NotePlayHandle * _n ); diff --git a/plugins/PeakControllerEffect/PeakControllerEffect.cpp b/plugins/PeakControllerEffect/PeakControllerEffect.cpp index d32a22320..7aff6f803 100644 --- a/plugins/PeakControllerEffect/PeakControllerEffect.cpp +++ b/plugins/PeakControllerEffect/PeakControllerEffect.cpp @@ -48,7 +48,7 @@ Plugin::Descriptor PLUGIN_EXPORT peakcontrollereffect_plugin_descriptor = "Plugin for controlling knobs with sound peaks" ), "Paul Giblock ", 0x0100, - Plugin::Effect, + Plugin::Type::Effect, new PluginPixmapLoader("logo"), nullptr, nullptr, @@ -76,7 +76,7 @@ PeakControllerEffect::PeakControllerEffect( { Engine::getSong()->addController( m_autoController ); } - PeakController::s_effects.append( this ); + PeakController::s_effects.push_back(this); } @@ -84,11 +84,11 @@ PeakControllerEffect::PeakControllerEffect( PeakControllerEffect::~PeakControllerEffect() { - int idx = PeakController::s_effects.indexOf( this ); - if( idx >= 0 ) + auto it = std::find(PeakController::s_effects.begin(), PeakController::s_effects.end(), this); + if (it != PeakController::s_effects.end()) { - PeakController::s_effects.remove( idx ); - Engine::getSong()->removeController( m_autoController ); + PeakController::s_effects.erase(it); + Engine::getSong()->removeController(m_autoController); } } diff --git a/plugins/PeakControllerEffect/PeakControllerEffectControlDialog.cpp b/plugins/PeakControllerEffect/PeakControllerEffectControlDialog.cpp index 69566a86c..e44d5bcc2 100644 --- a/plugins/PeakControllerEffect/PeakControllerEffectControlDialog.cpp +++ b/plugins/PeakControllerEffect/PeakControllerEffectControlDialog.cpp @@ -49,32 +49,32 @@ PeakControllerEffectControlDialog::PeakControllerEffectControlDialog( setPalette( pal ); setFixedSize( 240, 80 ); - m_baseKnob = new Knob( knobBright_26, this ); + m_baseKnob = new Knob( KnobType::Bright26, this ); m_baseKnob->setLabel( tr( "BASE" ) ); m_baseKnob->setModel( &_controls->m_baseModel ); m_baseKnob->setHintText( tr( "Base:" ) , "" ); - m_amountKnob = new Knob( knobBright_26, this ); + m_amountKnob = new Knob( KnobType::Bright26, this ); m_amountKnob->setLabel( tr( "AMNT" ) ); m_amountKnob->setModel( &_controls->m_amountModel ); m_amountKnob->setHintText( tr( "Modulation amount:" ) , "" ); - m_amountMultKnob = new Knob( knobBright_26, this ); + m_amountMultKnob = new Knob( KnobType::Bright26, this ); m_amountMultKnob->setLabel( tr( "MULT" ) ); m_amountMultKnob->setModel( &_controls->m_amountMultModel ); m_amountMultKnob->setHintText( tr( "Amount multiplicator:" ) , "" ); - m_attackKnob = new Knob( knobBright_26, this ); + m_attackKnob = new Knob( KnobType::Bright26, this ); m_attackKnob->setLabel( tr( "ATCK" ) ); m_attackKnob->setModel( &_controls->m_attackModel ); m_attackKnob->setHintText( tr( "Attack:" ) , "" ); - m_decayKnob = new Knob( knobBright_26, this ); + m_decayKnob = new Knob( KnobType::Bright26, this ); m_decayKnob->setLabel( tr( "DCAY" ) ); m_decayKnob->setModel( &_controls->m_decayModel ); m_decayKnob->setHintText( tr( "Release:" ) , "" ); - m_tresholdKnob = new Knob( knobBright_26, this ); + m_tresholdKnob = new Knob( KnobType::Bright26, this ); m_tresholdKnob->setLabel( tr( "TRSH" ) ); m_tresholdKnob->setModel( &_controls->m_tresholdModel ); m_tresholdKnob->setHintText( tr( "Treshold:" ) , "" ); diff --git a/plugins/ReverbSC/ReverbSC.cpp b/plugins/ReverbSC/ReverbSC.cpp index e8dec8fd7..9006f8c9f 100644 --- a/plugins/ReverbSC/ReverbSC.cpp +++ b/plugins/ReverbSC/ReverbSC.cpp @@ -42,7 +42,7 @@ Plugin::Descriptor PLUGIN_EXPORT reverbsc_plugin_descriptor = QT_TRANSLATE_NOOP( "PluginBrowser", "Reverb algorithm by Sean Costello" ), "Paul Batchelor", 0x0123, - Plugin::Effect, + Plugin::Type::Effect, new PluginPixmapLoader( "logo" ), nullptr, nullptr, diff --git a/plugins/ReverbSC/ReverbSCControlDialog.cpp b/plugins/ReverbSC/ReverbSCControlDialog.cpp index c9ddef384..615d3823e 100644 --- a/plugins/ReverbSC/ReverbSCControlDialog.cpp +++ b/plugins/ReverbSC/ReverbSCControlDialog.cpp @@ -42,25 +42,25 @@ ReverbSCControlDialog::ReverbSCControlDialog( ReverbSCControls* controls ) : setPalette( pal ); setFixedSize( 185, 55 ); - auto inputGainKnob = new Knob(knobBright_26, this); + auto inputGainKnob = new Knob(KnobType::Bright26, this); inputGainKnob -> move( 16, 10 ); inputGainKnob->setModel( &controls->m_inputGainModel ); inputGainKnob->setLabel( tr( "Input" ) ); inputGainKnob->setHintText( tr( "Input gain:" ) , "dB" ); - auto sizeKnob = new Knob(knobBright_26, this); + auto sizeKnob = new Knob(KnobType::Bright26, this); sizeKnob -> move( 57, 10 ); sizeKnob->setModel( &controls->m_sizeModel ); sizeKnob->setLabel( tr( "Size" ) ); sizeKnob->setHintText( tr( "Size:" ) , "" ); - auto colorKnob = new Knob(knobBright_26, this); + auto colorKnob = new Knob(KnobType::Bright26, this); colorKnob -> move( 98, 10 ); colorKnob->setModel( &controls->m_colorModel ); colorKnob->setLabel( tr( "Color" ) ); colorKnob->setHintText( tr( "Color:" ) , "" ); - auto outputGainKnob = new Knob(knobBright_26, this); + auto outputGainKnob = new Knob(KnobType::Bright26, this); outputGainKnob -> move( 139, 10 ); outputGainKnob->setModel( &controls->m_outputGainModel ); outputGainKnob->setLabel( tr( "Output" ) ); diff --git a/plugins/Sf2Player/CMakeLists.txt b/plugins/Sf2Player/CMakeLists.txt index 4679a94bd..1d004a6c5 100644 --- a/plugins/Sf2Player/CMakeLists.txt +++ b/plugins/Sf2Player/CMakeLists.txt @@ -1,13 +1,10 @@ if(LMMS_HAVE_FLUIDSYNTH) include(BuildPlugin) - include_directories(${SAMPLERATE_INCLUDE_DIRS}) - link_directories(${SAMPLERATE_LIBRARY_DIRS}) - link_libraries(${SAMPLERATE_LIBRARIES}) build_plugin(sf2player Sf2Player.cpp Sf2Player.h PatchesDialog.cpp PatchesDialog.h PatchesDialog.ui MOCFILES Sf2Player.h PatchesDialog.h UICFILES PatchesDialog.ui EMBEDDED_RESOURCES *.png ) - target_link_libraries(sf2player fluidsynth) + target_link_libraries(sf2player fluidsynth SampleRate::samplerate) endif() diff --git a/plugins/Sf2Player/PatchesDialog.cpp b/plugins/Sf2Player/PatchesDialog.cpp index 9c9a689a0..8a32a10ce 100644 --- a/plugins/Sf2Player/PatchesDialog.cpp +++ b/plugins/Sf2Player/PatchesDialog.cpp @@ -154,7 +154,7 @@ void PatchesDialog::setup ( fluid_synth_t * pSynth, int iChan, 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); @@ -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 ab3729f7d..7795671c5 100644 --- a/plugins/Sf2Player/Sf2Player.cpp +++ b/plugins/Sf2Player/Sf2Player.cpp @@ -30,6 +30,7 @@ #include #include +#include "ArrayVector.h" #include "AudioEngine.h" #include "ConfigManager.h" #include "FileDialog.h" @@ -63,7 +64,7 @@ Plugin::Descriptor PLUGIN_EXPORT sf2player_plugin_descriptor = QT_TRANSLATE_NOOP( "PluginBrowser", "Player for SoundFont files" ), "Paul Giblock ", 0x0100, - Plugin::Instrument, + Plugin::Type::Instrument, new PluginPixmapLoader( "logo" ), "sf2,sf3", nullptr, @@ -71,23 +72,52 @@ Plugin::Descriptor PLUGIN_EXPORT sf2player_plugin_descriptor = } +/** + * 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 +{ +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_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 + { + return fluid_voice_get_id(m_voice) == m_id && fluid_voice_is_playing(m_voice); + } + +private: + fluid_voice_t* m_voice; + unsigned int m_id; + float m_coarseTune; +}; struct Sf2PluginData { int midiNote; int lastPanning; float lastVelocity; - fluid_voice_t * fluidVoice; + // The soundfonts I checked used at most two voices per note, so space for + // four should be safe. This may need to be increased if a soundfont with + // more voices per note is found. + ArrayVector fluidVoices; bool isNew; f_cnt_t offset; bool noteOffSent; -} ; - - - -// Static map of current sfonts -QMap Sf2Instrument::s_fonts; -QMutex Sf2Instrument::s_fontsMutex; + panning_t panning; +}; @@ -200,8 +230,8 @@ Sf2Instrument::Sf2Instrument( InstrumentTrack * _instrument_track ) : Sf2Instrument::~Sf2Instrument() { Engine::audioEngine()->removePlayHandlesOfTypes( instrumentTrack(), - PlayHandle::TypeNotePlayHandle - | PlayHandle::TypeInstrumentPlayHandle ); + PlayHandle::Type::NotePlayHandle + | PlayHandle::Type::InstrumentPlayHandle ); freeFont(); delete_fluid_synth( m_synth ); delete_fluid_settings( m_settings ); @@ -339,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(); } @@ -377,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 ) @@ -591,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) @@ -616,7 +607,7 @@ void Sf2Instrument::reloadSynth() m_synthMutex.lock(); if( Engine::audioEngine()->currentQualitySettings().interpolation >= - AudioEngine::qualitySettings::Interpolation_SincFastest ) + AudioEngine::qualitySettings::Interpolation::SincFastest ) { fluid_synth_set_interp_method( m_synth, -1, FLUID_INTERP_7THORDER ); } @@ -663,8 +654,6 @@ void Sf2Instrument::playNote( NotePlayHandle * _n, sampleFrame * ) return; } - const f_cnt_t tfp = _n->totalFramesPlayed(); - int masterPitch = instrumentTrack()->useMasterPitchModel()->value() ? Engine::getSong()->masterPitch() : 0; int baseNote = instrumentTrack()->baseNoteModel()->value(); int midiNote = _n->midiKey() - baseNote + DefaultBaseKey + masterPitch; @@ -675,7 +664,7 @@ void Sf2Instrument::playNote( NotePlayHandle * _n, sampleFrame * ) return; } - if (tfp == 0) + if (!_n->m_pluginData) { const int baseVelocity = instrumentTrack()->midiPort()->baseVelocity(); @@ -683,10 +672,10 @@ void Sf2Instrument::playNote( NotePlayHandle * _n, sampleFrame * ) pluginData->midiNote = midiNote; pluginData->lastPanning = 0; pluginData->lastVelocity = _n->midiVelocity( baseVelocity ); - pluginData->fluidVoice = nullptr; pluginData->isNew = true; pluginData->offset = _n->offset(); pluginData->noteOffSent = false; + pluginData->panning = _n->getPanning(); _n->m_pluginData = pluginData; @@ -705,6 +694,17 @@ void Sf2Instrument::playNote( NotePlayHandle * _n, sampleFrame * ) m_playingNotes.append( _n ); m_playingNotesMutex.unlock(); } + + // Update the pitch of all the voices + if (const auto data = static_cast(_n->m_pluginData)) { + const auto detuning = _n->currentDetuning(); + for (const auto& voice : data->fluidVoices) { + if (voice.isValid()) { + fluid_voice_gen_set(voice.get(), GEN_COARSETUNE, voice.coarseTune() + detuning); + fluid_voice_update_param(voice.get(), GEN_COARSETUNE); + } + } + } } @@ -717,35 +717,47 @@ void Sf2Instrument::noteOn( Sf2PluginData * n ) const int poly = fluid_synth_get_polyphony( m_synth ); #ifndef _MSC_VER fluid_voice_t* voices[poly]; - unsigned int id[poly]; #else const auto voices = static_cast(_alloca(poly * sizeof(fluid_voice_t*))); - const auto id = static_cast(_alloca(poly * sizeof(unsigned int))); #endif - fluid_synth_get_voicelist( m_synth, voices, poly, -1 ); - for( int i = 0; i < poly; ++i ) - { - id[i] = 0; - } - for( int i = 0; i < poly && voices[i]; ++i ) - { - id[i] = fluid_voice_get_id( voices[i] ); - } fluid_synth_noteon( m_synth, m_channel, n->midiNote, n->lastVelocity ); - // get new voice and save it - fluid_synth_get_voicelist( m_synth, voices, poly, -1 ); - for( int i = 0; i < poly && voices[i]; ++i ) + // Get any new voices and store them in the plugin data + fluid_synth_get_voicelist(m_synth, voices, poly, -1); + for (int i = 0; i < poly && voices[i] && !n->fluidVoices.full(); ++i) { - const unsigned int newID = fluid_voice_get_id( voices[i] ); - if( id[i] != newID || newID == 0 ) - { - n->fluidVoice = voices[i]; - break; + const auto voice = voices[i]; + // FluidSynth stops voices with the same channel and pitch upon note-on, + // so voices with the current channel and pitch are playing this note. + if (fluid_voice_get_channel(voice) == m_channel + && fluid_voice_get_key(voice) == n->midiNote + && fluid_voice_is_on(voice) + ) { + n->fluidVoices.emplace_back(voices[i]); } } +#if FLUIDSYNTH_VERSION_MAJOR >= 2 + // Smallest balance value that results in full attenuation of one channel. + // Corresponds to internal FluidSynth macro `FLUID_CB_AMP_SIZE`. + constexpr static auto maxBalance = 1441.f; + // Convert panning from linear to exponential for FluidSynth + const auto panning = n->panning; + const auto factor = 1.f - std::abs(panning) / static_cast(PanningRight); + const auto balance = std::copysign( + factor <= 0 ? maxBalance : std::min(-200.f * std::log10(factor), maxBalance), + panning + ); + // Set note panning on all the voices + for (const auto& voice : n->fluidVoices) { + if (voice.isValid()) { + fluid_voice_gen_set(voice.get(), GEN_CUSTOM_BALANCE, balance); + fluid_voice_update_param(voice.get(), GEN_CUSTOM_BALANCE); + } + } +#endif + m_synthMutex.unlock(); m_notesRunningMutex.lock(); @@ -796,7 +808,6 @@ void Sf2Instrument::play( sampleFrame * _working_buffer ) if( m_playingNotes.isEmpty() ) { renderFrames( frames, _working_buffer ); - instrumentTrack()->processAudioBuffer( _working_buffer, frames, nullptr ); return; } @@ -854,13 +865,13 @@ void Sf2Instrument::play( sampleFrame * _working_buffer ) { renderFrames( frames - currentFrame, _working_buffer + currentFrame ); } - instrumentTrack()->processAudioBuffer( _working_buffer, frames, nullptr ); } void Sf2Instrument::renderFrames( f_cnt_t frames, sampleFrame * buf ) { m_synthMutex.lock(); + fluid_synth_get_gain(m_synth); // This flushes voice updates as a side effect if( m_internalSampleRate < Engine::audioEngine()->processingSampleRate() && m_srcState != nullptr ) { @@ -938,7 +949,7 @@ class Sf2Knob : public Knob { public: Sf2Knob( QWidget * _parent ) : - Knob( knobStyled, _parent ) + Knob( KnobType::Styled, _parent ) { setFixedSize( 31, 38 ); } diff --git a/plugins/Sf2Player/Sf2Player.h b/plugins/Sf2Player/Sf2Player.h index 5a88d0f95..1af370e05 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; @@ -88,7 +87,7 @@ public: Flags flags() const override { - return IsSingleStreamed; + return Flag::IsSingleStreamed; } gui::PluginView* instantiateView( QWidget * _parent ) override; @@ -114,16 +113,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 +172,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 d97217446..e79b8e2ad 100644 --- a/plugins/Sfxr/Sfxr.cpp +++ b/plugins/Sfxr/Sfxr.cpp @@ -67,7 +67,7 @@ Plugin::Descriptor PLUGIN_EXPORT sfxr_plugin_descriptor = "LMMS port of sfxr" ), "Wong Cho Ching", 0x0100, - Plugin::Instrument, + Plugin::Type::Instrument, new PluginPixmapLoader( "logo" ), nullptr, nullptr, @@ -350,7 +350,7 @@ SfxrInstrument::SfxrInstrument( InstrumentTrack * _instrument_track ) : m_lpFilResoModel(0.0f, this, "LP Filter Resonance"), m_hpFilCutModel(0.0f, this, "HP Filter Cutoff"), m_hpFilCutSweepModel(0.0f, this, "HP Filter Cutoff Sweep"), - m_waveFormModel( SQR_WAVE, 0, WAVES_NUM-1, this, tr( "Wave" ) ) + m_waveFormModel( static_cast(SfxrWave::Square), 0, NumSfxrWaves-1, this, tr( "Wave" ) ) { } @@ -446,9 +446,9 @@ void SfxrInstrument::playNote( NotePlayHandle * _n, sampleFrame * _working_buffe { float currentSampleRate = Engine::audioEngine()->processingSampleRate(); - fpp_t frameNum = _n->framesLeftForCurrentPeriod(); - const f_cnt_t offset = _n->noteOffset(); - if ( _n->totalFramesPlayed() == 0 || _n->m_pluginData == nullptr ) + fpp_t frameNum = _n->framesLeftForCurrentPeriod(); + const f_cnt_t offset = _n->noteOffset(); + if (!_n->m_pluginData) { _n->m_pluginData = new SfxrSynth( this ); } @@ -480,9 +480,6 @@ void SfxrInstrument::playNote( NotePlayHandle * _n, sampleFrame * _working_buffe delete[] pitchedBuffer; applyRelease( _working_buffer, _n ); - - instrumentTrack()->processAudioBuffer( _working_buffer, frameNum + offset, _n ); - } @@ -549,7 +546,7 @@ class SfxrKnob : public Knob { public: SfxrKnob( QWidget * _parent ) : - Knob( knobStyled, _parent ) + Knob( KnobType::Styled, _parent ) { setFixedSize( 20, 20 ); setCenterPointX( 10.0 ); diff --git a/plugins/Sfxr/Sfxr.h b/plugins/Sfxr/Sfxr.h index 632ccfebd..8af8984c9 100644 --- a/plugins/Sfxr/Sfxr.h +++ b/plugins/Sfxr/Sfxr.h @@ -31,16 +31,16 @@ #include "AutomatableModel.h" #include "Instrument.h" #include "InstrumentView.h" -#include "MemoryManager.h" namespace lmms { -enum SfxrWaves +enum class SfxrWave { - SQR_WAVE, SAW_WAVE, SINE_WAVE, NOISE_WAVE, WAVES_NUM + Square, Saw, Sine, Noise, Count }; +constexpr auto NumSfxrWaves = static_cast(SfxrWave::Count); const int WAVEFORM_BASE_X = 20; const int WAVEFORM_BASE_Y = 15; @@ -77,7 +77,6 @@ class SfxrInstrumentView; class SfxrSynth { - MM_OPERATORS public: SfxrSynth( const SfxrInstrument * s ); virtual ~SfxrSynth() = default; diff --git a/plugins/Sid/CMakeLists.txt b/plugins/Sid/CMakeLists.txt index c9fce7bb7..c771fc66d 100644 --- a/plugins/Sid/CMakeLists.txt +++ b/plugins/Sid/CMakeLists.txt @@ -1,51 +1,14 @@ INCLUDE(BuildPlugin) -INCLUDE_DIRECTORIES(resid) +if(NOT LMMS_HAVE_SID) + return() +endif() BUILD_PLUGIN(sid SidInstrument.cpp SidInstrument.h - resid/envelope.h - resid/extfilt.h - resid/filter.h - resid/pot.h - resid/siddefs.h - resid/sid.h - resid/spline.h - resid/voice.h - resid/wave.h - resid/envelope.cc - resid/extfilt.cc - resid/filter.cc - resid/pot.cc - resid/sid.cc - resid/version.cc - resid/voice.cc - resid/wave6581_PS_.cc - resid/wave6581_PST.cc - resid/wave6581_P_T.cc - resid/wave6581__ST.cc - resid/wave8580_PS_.cc - resid/wave8580_PST.cc - resid/wave8580_P_T.cc - resid/wave8580__ST.cc - resid/wave.cc MOCFILES SidInstrument.h EMBEDDED_RESOURCES *.png) -# Parse VERSION -FILE(READ "resid/CMakeLists.txt" lines) -STRING(REGEX MATCH "set\\(MAJOR_VER [A-Za-z0-9_]*\\)" MAJOR_RAW ${lines}) -STRING(REGEX MATCH "set\\(MINOR_VER [A-Za-z0-9_]*\\)" MINOR_RAW ${lines}) -STRING(REGEX MATCH "set\\(PATCH_VER [A-Za-z0-9_]*\\)" PATCH_RAW ${lines}) -SEPARATE_ARGUMENTS(MAJOR_RAW) -SEPARATE_ARGUMENTS(MINOR_RAW) -SEPARATE_ARGUMENTS(PATCH_RAW) -LIST(GET MAJOR_RAW 1 MAJOR_RAW) -LIST(GET MINOR_RAW 1 MINOR_RAW) -LIST(GET PATCH_RAW 1 PATCH_RAW) -STRING(REPLACE ")" "" MAJOR_VER "${MAJOR_RAW}") -STRING(REPLACE ")" "" MINOR_VER "${MINOR_RAW}") -STRING(REPLACE ")" "" PATCH_VER "${PATCH_RAW}") - -TARGET_COMPILE_DEFINITIONS(sid PRIVATE VERSION="${MAJOR_VER}.${MINOR_VER}.${PATCH_VER}") +add_subdirectory(resid) +target_link_libraries(sid resid) diff --git a/plugins/Sid/SidInstrument.cpp b/plugins/Sid/SidInstrument.cpp index 6e7473492..d85939eb8 100644 --- a/plugins/Sid/SidInstrument.cpp +++ b/plugins/Sid/SidInstrument.cpp @@ -83,7 +83,7 @@ Plugin::Descriptor PLUGIN_EXPORT sid_plugin_descriptor = "Csaba Hruska " "Attila Herman ", 0x0100, - Plugin::Instrument, + Plugin::Type::Instrument, new PluginPixmapLoader( "logo" ), nullptr, nullptr, @@ -105,7 +105,7 @@ VoiceObject::VoiceObject( Model * _parent, int _idx ) : tr( "Voice %1 release" ).arg( _idx+1 ) ), m_coarseModel( 0.0f, -24.0, 24.0, 1.0f, this, tr( "Voice %1 coarse detuning" ).arg( _idx+1 ) ), - m_waveFormModel( TriangleWave, 0, NumWaveShapes-1, this, + m_waveFormModel( static_cast(WaveForm::Triangle), 0, NumWaveShapes-1, this, tr( "Voice %1 wave shape" ).arg( _idx+1 ) ), m_syncModel( false, this, tr( "Voice %1 sync" ).arg( _idx+1 ) ), @@ -121,12 +121,12 @@ SidInstrument::SidInstrument( InstrumentTrack * _instrument_track ) : // filter m_filterFCModel( 1024.0f, 0.0f, 2047.0f, 1.0f, this, tr( "Cutoff frequency" ) ), m_filterResonanceModel( 8.0f, 0.0f, 15.0f, 1.0f, this, tr( "Resonance" ) ), - m_filterModeModel( LowPass, 0, NumFilterTypes-1, this, tr( "Filter type" )), + m_filterModeModel( static_cast(FilterType::LowPass), 0, NumFilterTypes-1, this, tr( "Filter type" )), // misc m_voice3OffModel( false, this, tr( "Voice 3 off" ) ), m_volumeModel( 15.0f, 0.0f, 15.0f, 1.0f, this, tr( "Volume" ) ), - m_chipModel( sidMOS8580, 0, NumChipModels-1, this, tr( "Chip model" ) ) + m_chipModel( static_cast(ChipModel::MOS8580), 0, NumChipModels-1, this, tr( "Chip model" ) ) { for( int i = 0; i < 3; ++i ) { @@ -239,26 +239,23 @@ f_cnt_t SidInstrument::desiredReleaseFrames() const -static int sid_fillbuffer(unsigned char* sidreg, SID *sid, int tdelta, short *ptr, int samples) +static int sid_fillbuffer(unsigned char* sidreg, reSID::SID *sid, int tdelta, short *ptr, int samples) { - int tdelta2; - int result; 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 +265,8 @@ static int sid_fillbuffer(unsigned char* sidreg, SID *sid, int tdelta, short *pt // 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 +275,14 @@ static int sid_fillbuffer(unsigned char* sidreg, SID *sid, int tdelta, short *pt 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; @@ -297,16 +294,14 @@ static int sid_fillbuffer(unsigned char* sidreg, SID *sid, int tdelta, short *pt void SidInstrument::playNote( NotePlayHandle * _n, sampleFrame * _working_buffer ) { - const f_cnt_t tfp = _n->totalFramesPlayed(); - const int clockrate = C64_PAL_CYCLES_PER_SEC; const int samplerate = Engine::audioEngine()->processingSampleRate(); - if ( tfp == 0 ) + if (!_n->m_pluginData) { - SID *sid = new SID(); - sid->set_sampling_parameters( clockrate, SAMPLE_FAST, samplerate ); - sid->set_chip_model( MOS8580 ); + auto sid = new reSID::SID(); + sid->set_sampling_parameters(clockrate, reSID::SAMPLE_FAST, samplerate); + sid->set_chip_model(reSID::MOS8580); sid->enable_filter( true ); sid->reset(); _n->m_pluginData = sid; @@ -314,7 +309,7 @@ void SidInstrument::playNote( NotePlayHandle * _n, const fpp_t frames = _n->framesLeftForCurrentPeriod(); const f_cnt_t offset = _n->noteOffset(); - SID *sid = static_cast( _n->m_pluginData ); + auto sid = static_cast(_n->m_pluginData); int delta_t = clockrate * frames / samplerate + 4; // avoid variable length array for msvc compat auto buf = reinterpret_cast(_working_buffer + offset); @@ -325,22 +320,22 @@ void SidInstrument::playNote( NotePlayHandle * _n, reg = 0x00; } - if( (ChipModel)m_chipModel.value() == sidMOS6581 ) + if( (ChipModel)m_chipModel.value() == ChipModel::MOS6581 ) { - sid->set_chip_model( MOS6581 ); + sid->set_chip_model(reSID::MOS6581); } else { - sid->set_chip_model( MOS8580 ); + sid->set_chip_model(reSID::MOS8580); } // voices - reg8 data8 = 0; - reg8 data16 = 0; - reg8 base = 0; + reSID::reg8 data8 = 0; + reSID::reg16 data16 = 0; + size_t base = 0; float freq = 0.0; float note = 0.0; - for( reg8 i = 0 ; i < 3 ; ++i ) + for (size_t i = 0; i < 3; ++i) { base = i*7; // freq ( Fn = Fout / Fclk * 16777216 ) + coarse detuning @@ -362,13 +357,13 @@ void SidInstrument::playNote( NotePlayHandle * _n, data8 += m_voice[i]->m_syncModel.value()?2:0; data8 += m_voice[i]->m_ringModModel.value()?4:0; data8 += m_voice[i]->m_testModel.value()?8:0; - switch( m_voice[i]->m_waveFormModel.value() ) + switch( static_cast(m_voice[i]->m_waveFormModel.value()) ) { default: break; - case VoiceObject::NoiseWave: data8 += 128; break; - case VoiceObject::SquareWave: data8 += 64; break; - case VoiceObject::SawWave: data8 += 32; break; - case VoiceObject::TriangleWave: data8 += 16; break; + case VoiceObject::WaveForm::Noise: data8 += 128; break; + case VoiceObject::WaveForm::Square: data8 += 64; break; + case VoiceObject::WaveForm::Saw: data8 += 32; break; + case VoiceObject::WaveForm::Triangle: data8 += 16; break; } sidreg[base+4] = data8&0x00FF; // ad @@ -408,12 +403,12 @@ void SidInstrument::playNote( NotePlayHandle * _n, data8 = data16&0x000F; data8 += m_voice3OffModel.value()?128:0; - switch( m_filterModeModel.value() ) + switch( static_cast(m_filterModeModel.value()) ) { default: break; - case LowPass: data8 += 16; break; - case BandPass: data8 += 32; break; - case HighPass: data8 += 64; break; + case FilterType::LowPass: data8 += 16; break; + case FilterType::BandPass: data8 += 32; break; + case FilterType::HighPass: data8 += 64; break; } sidreg[24] = data8&0x00FF; @@ -431,8 +426,6 @@ void SidInstrument::playNote( NotePlayHandle * _n, _working_buffer[frame+offset][ch] = s; } } - - instrumentTrack()->processAudioBuffer( _working_buffer, frames + offset, _n ); } @@ -440,7 +433,7 @@ void SidInstrument::playNote( NotePlayHandle * _n, void SidInstrument::deleteNotePluginData( NotePlayHandle * _n ) { - delete static_cast( _n->m_pluginData ); + delete static_cast(_n->m_pluginData); } @@ -461,7 +454,7 @@ class sidKnob : public Knob { public: sidKnob( QWidget * _parent ) : - Knob( knobStyled, _parent ) + Knob( KnobType::Styled, _parent ) { setFixedSize( 16, 16 ); setCenterPointX( 7.5 ); diff --git a/plugins/Sid/SidInstrument.h b/plugins/Sid/SidInstrument.h index 203e52e7b..1a133b58b 100644 --- a/plugins/Sid/SidInstrument.h +++ b/plugins/Sid/SidInstrument.h @@ -48,15 +48,16 @@ class PixmapButton; class VoiceObject : public Model { Q_OBJECT - MM_OPERATORS public: - enum WaveForm { - SquareWave = 0, - TriangleWave, - SawWave, - NoiseWave, - NumWaveShapes + enum class WaveForm { + Square = 0, + Triangle, + Saw, + Noise, + Count }; + constexpr static auto NumWaveShapes = static_cast(WaveForm::Count); + VoiceObject( Model * _parent, int _idx ); ~VoiceObject() override = default; @@ -82,19 +83,20 @@ class SidInstrument : public Instrument { Q_OBJECT public: - enum FilerType { + enum class FilterType { HighPass = 0, BandPass, LowPass, - NumFilterTypes + Count }; + constexpr static auto NumFilterTypes = static_cast(FilterType::Count); - enum ChipModel { - sidMOS6581 = 0, - sidMOS8580, - NumChipModels + enum class ChipModel { + MOS6581 = 0, + MOS8580, + Count }; - + constexpr static auto NumChipModels = static_cast(ChipModel::Count); SidInstrument( InstrumentTrack * _instrument_track ); ~SidInstrument() override = default; diff --git a/plugins/Sid/filter.png b/plugins/Sid/filter.png new file mode 100644 index 000000000..ff5c760db Binary files /dev/null and b/plugins/Sid/filter.png differ diff --git a/plugins/Sid/resid b/plugins/Sid/resid deleted file mode 160000 index 02afcc5ce..000000000 --- a/plugins/Sid/resid +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 02afcc5cefac34bd0c665dc0fa6b748d238c1831 diff --git a/plugins/Sid/resid/CMakeLists.txt b/plugins/Sid/resid/CMakeLists.txt new file mode 100644 index 000000000..bb39e3d16 --- /dev/null +++ b/plugins/Sid/resid/CMakeLists.txt @@ -0,0 +1,68 @@ +# These are the defaults +set(RESID_INLINING 1) +set(RESID_INLINE inline) +set(RESID_BRANCH_HINTS 1) +set(NEW_8580_FILTER 0) + +set(HAVE_BOOL 1) +set(HAVE_LOG1P 1) + +if(CMAKE_CXX_COMPILER_ID MATCHES "GCC|Clang") + set(HAVE_BUILTIN_EXPECT 1) +else() + set(HAVE_BUILTIN_EXPECT 0) +endif() + +configure_file(resid/siddefs.h.in resid/siddefs.h @ONLY) + +add_library(resid_objects OBJECT + resid/sid.cc + resid/voice.cc + resid/wave.cc + resid/envelope.cc + resid/filter.cc + resid/dac.cc + resid/extfilt.cc + resid/pot.cc + resid/version.cc +) + +target_include_directories(resid_objects PUBLIC + "${CMAKE_CURRENT_SOURCE_DIR}/resid" + "${CMAKE_CURRENT_BINARY_DIR}/resid" +) +target_compile_definitions(resid_objects PUBLIC VERSION="1.0") + +set(RESID_WAVES + wave6581_PST + wave6581_PS_ + wave6581_P_T + wave6581__ST + wave8580_PST + wave8580_PS_ + wave8580_P_T + wave8580__ST +) + +set(RESID_SAMP2SRC_SCRIPT ${CMAKE_CURRENT_SOURCE_DIR}/resid/samp2src.pl) +foreach(WAVE_DATA IN LISTS RESID_WAVES) + set(WAVE_DATA_IN ${CMAKE_CURRENT_SOURCE_DIR}/resid/${WAVE_DATA}.dat) + set(WAVE_SRC_OUT ${CMAKE_CURRENT_BINARY_DIR}/resid/${WAVE_DATA}.h) + set(WAVE_COMMAND + "${PERL_EXECUTABLE}" + "${RESID_SAMP2SRC_SCRIPT}" + "${WAVE_DATA}" + "${WAVE_DATA_IN}" + "${WAVE_SRC_OUT}" + ) + add_custom_command(OUTPUT ${WAVE_SRC_OUT} COMMAND ${WAVE_COMMAND} VERBATIM) + target_sources(resid_objects PUBLIC ${WAVE_SRC_OUT}) +endforeach() + +# TODO CMake 3.12: Use target_link_libraries() to propagate usage requirements directly to sid plugin +add_library(resid INTERFACE) + +target_sources(resid INTERFACE $) + +get_target_property(resid_includes resid_objects INCLUDE_DIRECTORIES) +target_include_directories(resid INTERFACE ${resid_includes}) diff --git a/plugins/Sid/resid/resid b/plugins/Sid/resid/resid new file mode 160000 index 000000000..ef72462f5 --- /dev/null +++ b/plugins/Sid/resid/resid @@ -0,0 +1 @@ +Subproject commit ef72462f5fa0682d099413512b764ae479e77f9b diff --git a/plugins/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..732ebfb44 --- /dev/null +++ b/plugins/SlicerT/SlicerT.cpp @@ -0,0 +1,410 @@ +/* + * 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()->processingSampleRate() / 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()->processingSampleRate(); + 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) + { + int closestZeroCrossing = *std::lower_bound(zeroCrossings.begin(), zeroCrossings.end(), sliceValue); + 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/Analyzer.cpp b/plugins/SpectrumAnalyzer/Analyzer.cpp index de4ac8c68..0bbada7db 100644 --- a/plugins/SpectrumAnalyzer/Analyzer.cpp +++ b/plugins/SpectrumAnalyzer/Analyzer.cpp @@ -48,7 +48,7 @@ extern "C" { QT_TRANSLATE_NOOP("PluginBrowser", "A graphical spectrum analyzer."), "Martin Pavelek ", 0x0112, - Plugin::Effect, + Plugin::Type::Effect, new PluginPixmapLoader("logo"), nullptr, nullptr, diff --git a/plugins/SpectrumAnalyzer/SaControls.cpp b/plugins/SpectrumAnalyzer/SaControls.cpp index 1ebfcdca3..d14176ed3 100644 --- a/plugins/SpectrumAnalyzer/SaControls.cpp +++ b/plugins/SpectrumAnalyzer/SaControls.cpp @@ -66,7 +66,7 @@ SaControls::SaControls(Analyzer *effect) : m_zeroPaddingModel(2.0f, 0.0f, 4.0f, 1.0f, this, tr("FFT zero padding")) { // Frequency and amplitude ranges; order must match - // FREQUENCY_RANGES and AMPLITUDE_RANGES defined in SaControls.h + // FrequencyRange and AmplitudeRange defined in SaControls.h m_freqRangeModel.addItem(tr("Full (auto)")); m_freqRangeModel.addItem(tr("Audible")); m_freqRangeModel.addItem(tr("Bass")); @@ -99,7 +99,7 @@ SaControls::SaControls(Analyzer *effect) : } m_blockSizeModel.setValue(m_blockSizeModel.findText("2048")); - // Window type order must match FFT_WINDOWS defined in fft_helpers.h + // Window type order must match FFTWindow defined in fft_helpers.h m_windowModel.addItem(tr("Rectangular (Off)")); m_windowModel.addItem(tr("Blackman-Harris (Default)")); m_windowModel.addItem(tr("Hamming")); diff --git a/plugins/SpectrumAnalyzer/SaControlsDialog.cpp b/plugins/SpectrumAnalyzer/SaControlsDialog.cpp index c4fc431b1..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")); @@ -236,7 +236,7 @@ SaControlsDialog::SaControlsDialog(SaControls *controls, SaProcessor *processor) controls_layout->setStretchFactor(advanced_widget, 10); // Peak envelope resolution - auto envelopeResolutionKnob = new Knob(knobSmall_17, this); + auto envelopeResolutionKnob = new Knob(KnobType::Small17, this); envelopeResolutionKnob->setModel(&controls->m_envelopeResolutionModel); envelopeResolutionKnob->setLabel(tr("Envelope res.")); envelopeResolutionKnob->setToolTip(tr("Increase envelope resolution for better details, decrease for better GUI performance.")); @@ -244,7 +244,7 @@ SaControlsDialog::SaControlsDialog(SaControls *controls, SaProcessor *processor) advanced_layout->addWidget(envelopeResolutionKnob, 0, 0, 1, 1, Qt::AlignCenter); // Spectrum graph resolution - auto spectrumResolutionKnob = new Knob(knobSmall_17, this); + auto spectrumResolutionKnob = new Knob(KnobType::Small17, this); spectrumResolutionKnob->setModel(&controls->m_spectrumResolutionModel); spectrumResolutionKnob->setLabel(tr("Spectrum res.")); spectrumResolutionKnob->setToolTip(tr("Increase spectrum resolution for better details, decrease for better GUI performance.")); @@ -252,7 +252,7 @@ SaControlsDialog::SaControlsDialog(SaControls *controls, SaProcessor *processor) advanced_layout->addWidget(spectrumResolutionKnob, 1, 0, 1, 1, Qt::AlignCenter); // Peak falloff speed - auto peakDecayFactorKnob = new Knob(knobSmall_17, this); + auto peakDecayFactorKnob = new Knob(KnobType::Small17, this); peakDecayFactorKnob->setModel(&controls->m_peakDecayFactorModel); peakDecayFactorKnob->setLabel(tr("Falloff factor")); peakDecayFactorKnob->setToolTip(tr("Decrease to make peaks fall faster.")); @@ -260,7 +260,7 @@ SaControlsDialog::SaControlsDialog(SaControls *controls, SaProcessor *processor) advanced_layout->addWidget(peakDecayFactorKnob, 0, 1, 1, 1, Qt::AlignCenter); // Averaging weight - auto averagingWeightKnob = new Knob(knobSmall_17, this); + auto averagingWeightKnob = new Knob(KnobType::Small17, this); averagingWeightKnob->setModel(&controls->m_averagingWeightModel); averagingWeightKnob->setLabel(tr("Averaging weight")); averagingWeightKnob->setToolTip(tr("Decrease to make averaging slower and smoother.")); @@ -268,7 +268,7 @@ SaControlsDialog::SaControlsDialog(SaControls *controls, SaProcessor *processor) advanced_layout->addWidget(averagingWeightKnob, 1, 1, 1, 1, Qt::AlignCenter); // Waterfall history size - auto waterfallHeightKnob = new Knob(knobSmall_17, this); + auto waterfallHeightKnob = new Knob(KnobType::Small17, this); waterfallHeightKnob->setModel(&controls->m_waterfallHeightModel); waterfallHeightKnob->setLabel(tr("Waterfall height")); waterfallHeightKnob->setToolTip(tr("Increase to get slower scrolling, decrease to see fast transitions better. Warning: medium CPU usage.")); @@ -278,7 +278,7 @@ SaControlsDialog::SaControlsDialog(SaControls *controls, SaProcessor *processor) connect(&controls->m_waterfallHeightModel, &FloatModel::dataChanged, [=] {processor->reallocateBuffers();}); // Waterfall gamma correction - auto waterfallGammaKnob = new Knob(knobSmall_17, this); + auto waterfallGammaKnob = new Knob(KnobType::Small17, this); waterfallGammaKnob->setModel(&controls->m_waterfallGammaModel); waterfallGammaKnob->setLabel(tr("Waterfall gamma")); waterfallGammaKnob->setToolTip(tr("Decrease to see very weak signals, increase to get better contrast.")); @@ -286,7 +286,7 @@ SaControlsDialog::SaControlsDialog(SaControls *controls, SaProcessor *processor) advanced_layout->addWidget(waterfallGammaKnob, 1, 2, 1, 1, Qt::AlignCenter); // FFT window overlap - auto windowOverlapKnob = new Knob(knobSmall_17, this); + auto windowOverlapKnob = new Knob(KnobType::Small17, this); windowOverlapKnob->setModel(&controls->m_windowOverlapModel); windowOverlapKnob->setLabel(tr("Window overlap")); windowOverlapKnob->setToolTip(tr("Increase to prevent missing fast transitions arriving near FFT window edges. Warning: high CPU usage.")); @@ -294,7 +294,7 @@ SaControlsDialog::SaControlsDialog(SaControls *controls, SaProcessor *processor) advanced_layout->addWidget(windowOverlapKnob, 0, 3, 1, 1, Qt::AlignCenter); // FFT zero padding - auto zeroPaddingKnob = new Knob(knobSmall_17, this); + auto zeroPaddingKnob = new Knob(KnobType::Small17, this); zeroPaddingKnob->setModel(&controls->m_zeroPaddingModel); zeroPaddingKnob->setLabel(tr("Zero padding")); zeroPaddingKnob->setToolTip(tr("Increase to get smoother-looking spectrum. Warning: high CPU usage.")); @@ -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 d7a1dd5fb..c55acbdf6 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 { @@ -58,7 +61,7 @@ SaProcessor::SaProcessor(const SaControls *controls) : m_reallocating(false) { m_fftWindow.resize(m_inBlockSize, 1.0); - precomputeWindow(m_fftWindow.data(), m_inBlockSize, BLACKMAN_HARRIS); + precomputeWindow(m_fftWindow.data(), m_inBlockSize, FFTWindow::BlackmanHarris); m_bufferL.resize(m_inBlockSize, 0); m_bufferR.resize(m_inBlockSize, 0); @@ -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 @@ -402,7 +396,7 @@ void SaProcessor::reallocateBuffers() // allocate new space, create new plan and resize containers m_fftWindow.resize(new_in_size, 1.0); - precomputeWindow(m_fftWindow.data(), new_in_size, (FFT_WINDOWS) m_controls->m_windowModel.value()); + precomputeWindow(m_fftWindow.data(), new_in_size, (FFTWindow) m_controls->m_windowModel.value()); m_bufferL.resize(new_in_size, 0); m_bufferR.resize(new_in_size, 0); m_filteredBufferL.resize(new_fft_size, 0); @@ -448,7 +442,7 @@ void SaProcessor::rebuildWindow() { // computation is done in fft_helpers QMutexLocker lock(&m_dataAccess); - precomputeWindow(m_fftWindow.data(), m_inBlockSize, (FFT_WINDOWS) m_controls->m_windowModel.value()); + precomputeWindow(m_fftWindow.data(), m_inBlockSize, (FFTWindow) m_controls->m_windowModel.value()); } @@ -545,28 +539,28 @@ float SaProcessor::binBandwidth() const float SaProcessor::getFreqRangeMin(bool linear) const { - switch (m_controls->m_freqRangeModel.value()) + switch (static_cast(m_controls->m_freqRangeModel.value())) { - case FRANGE_AUDIBLE: return FRANGE_AUDIBLE_START; - case FRANGE_BASS: return FRANGE_BASS_START; - case FRANGE_MIDS: return FRANGE_MIDS_START; - case FRANGE_HIGH: return FRANGE_HIGH_START; + case FrequencyRange::Audible: return FRANGE_AUDIBLE_START; + case FrequencyRange::Bass: return FRANGE_BASS_START; + case FrequencyRange::Mids: return FRANGE_MIDS_START; + case FrequencyRange::High: return FRANGE_HIGH_START; default: - case FRANGE_FULL: return linear ? 0 : LOWEST_LOG_FREQ; + case FrequencyRange::Full: return linear ? 0 : LOWEST_LOG_FREQ; } } float SaProcessor::getFreqRangeMax() const { - switch (m_controls->m_freqRangeModel.value()) + switch (static_cast(m_controls->m_freqRangeModel.value())) { - case FRANGE_AUDIBLE: return FRANGE_AUDIBLE_END; - case FRANGE_BASS: return FRANGE_BASS_END; - case FRANGE_MIDS: return FRANGE_MIDS_END; - case FRANGE_HIGH: return FRANGE_HIGH_END; + case FrequencyRange::Audible: return FRANGE_AUDIBLE_END; + case FrequencyRange::Bass: return FRANGE_BASS_END; + case FrequencyRange::Mids: return FRANGE_MIDS_END; + case FrequencyRange::High: return FRANGE_HIGH_END; default: - case FRANGE_FULL: return getNyquistFreq(); + case FrequencyRange::Full: return getNyquistFreq(); } } @@ -619,26 +613,26 @@ float SaProcessor::getAmpRangeMin(bool linear) const { // return very low limit to make sure zero gets included at linear grid if (linear) {return -900;} - switch (m_controls->m_ampRangeModel.value()) + switch (static_cast(m_controls->m_ampRangeModel.value())) { - case ARANGE_EXTENDED: return ARANGE_EXTENDED_START; - case ARANGE_SILENT: return ARANGE_SILENT_START; - case ARANGE_LOUD: return ARANGE_LOUD_START; + case AmplitudeRange::Extended: return ARANGE_EXTENDED_START; + case AmplitudeRange::Silent: return ARANGE_SILENT_START; + case AmplitudeRange::Loud: return ARANGE_LOUD_START; default: - case ARANGE_AUDIBLE: return ARANGE_AUDIBLE_START; + case AmplitudeRange::Audible: return ARANGE_AUDIBLE_START; } } float SaProcessor::getAmpRangeMax() const { - switch (m_controls->m_ampRangeModel.value()) + switch (static_cast(m_controls->m_ampRangeModel.value())) { - case ARANGE_EXTENDED: return ARANGE_EXTENDED_END; - case ARANGE_SILENT: return ARANGE_SILENT_END; - case ARANGE_LOUD: return ARANGE_LOUD_END; + case AmplitudeRange::Extended: return ARANGE_EXTENDED_END; + case AmplitudeRange::Silent: return ARANGE_SILENT_END; + case AmplitudeRange::Loud: return ARANGE_LOUD_END; default: - case ARANGE_AUDIBLE: return ARANGE_AUDIBLE_END; + case AmplitudeRange::Audible: return ARANGE_AUDIBLE_END; } } @@ -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 01e55f2de..784003056 100644 --- a/plugins/StereoEnhancer/StereoEnhancer.cpp +++ b/plugins/StereoEnhancer/StereoEnhancer.cpp @@ -43,7 +43,7 @@ Plugin::Descriptor PLUGIN_EXPORT stereoenhancer_plugin_descriptor = "Plugin for enhancing stereo separation of a stereo input file" ), "Lou Herard ", 0x0100, - Plugin::Effect, + Plugin::Type::Effect, new PluginPixmapLoader("logo"), nullptr, nullptr, @@ -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/StereoEnhancer/StereoEnhancerControlDialog.cpp b/plugins/StereoEnhancer/StereoEnhancerControlDialog.cpp index 3f7841706..05c78616e 100644 --- a/plugins/StereoEnhancer/StereoEnhancerControlDialog.cpp +++ b/plugins/StereoEnhancer/StereoEnhancerControlDialog.cpp @@ -40,7 +40,7 @@ StereoEnhancerControlDialog::StereoEnhancerControlDialog( { auto l = new QHBoxLayout(this); - auto widthKnob = new Knob(knobBright_26, this); + auto widthKnob = new Knob(KnobType::Bright26, this); widthKnob->setModel( &_controls->m_widthModel ); widthKnob->setLabel( tr( "WIDTH" ) ); widthKnob->setHintText( tr( "Width:" ) , " samples" ); diff --git a/plugins/StereoMatrix/StereoMatrix.cpp b/plugins/StereoMatrix/StereoMatrix.cpp index 2dff69b77..b96d2e107 100644 --- a/plugins/StereoMatrix/StereoMatrix.cpp +++ b/plugins/StereoMatrix/StereoMatrix.cpp @@ -43,7 +43,7 @@ Plugin::Descriptor PLUGIN_EXPORT stereomatrix_plugin_descriptor = "Plugin for freely manipulating stereo output" ), "Paul Giblock ", 0x0100, - Plugin::Effect, + Plugin::Type::Effect, new PluginPixmapLoader("logo"), nullptr, nullptr, diff --git a/plugins/StereoMatrix/StereoMatrixControlDialog.cpp b/plugins/StereoMatrix/StereoMatrixControlDialog.cpp index 06ff2a03b..da9a3aa9e 100644 --- a/plugins/StereoMatrix/StereoMatrixControlDialog.cpp +++ b/plugins/StereoMatrix/StereoMatrixControlDialog.cpp @@ -48,22 +48,22 @@ StereoMatrixControlDialog::StereoMatrixControlDialog( PLUGIN_NAME::getIconPixmap( "artwork" ) ); setPalette( pal ); - auto llKnob = new Knob(knobBright_26, this); + auto llKnob = new Knob(KnobType::Bright26, this); llKnob->setModel( &_controls->m_llModel ); llKnob->setHintText( tr( "Left to Left Vol:" ) , "" ); llKnob->move( 10, 79 ); - auto lrKnob = new Knob(knobBright_26, this); + auto lrKnob = new Knob(KnobType::Bright26, this); lrKnob->setModel( &_controls->m_lrModel ); lrKnob->setHintText( tr( "Left to Right Vol:" ) , "" ); lrKnob->move( 48, 79 ); - auto rlKnob = new Knob(knobBright_26, this); + auto rlKnob = new Knob(KnobType::Bright26, this); rlKnob->setModel( &_controls->m_rlModel ); rlKnob->setHintText( tr( "Right to Left Vol:" ) , "" ); rlKnob->move( 85, 79 ); - auto rrKnob = new Knob(knobBright_26, this); + auto rrKnob = new Knob(KnobType::Bright26, this); rrKnob->setModel( &_controls->m_rrModel ); rrKnob->setHintText( tr( "Right to Right Vol:" ) , "" ); rrKnob->move( 123, 79 ); diff --git a/plugins/Stk/Mallets/Mallets.cpp b/plugins/Stk/Mallets/Mallets.cpp index 8aa71d62e..3fb7fc0ff 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" @@ -59,7 +58,7 @@ Plugin::Descriptor PLUGIN_EXPORT malletsstk_plugin_descriptor = "Tuneful things to bang on" ), "Danny McRae ", 0x0100, - Plugin::Instrument, + Plugin::Type::Instrument, new PluginPixmapLoader( "logo" ), nullptr, nullptr, @@ -85,8 +84,9 @@ 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, "" ), m_isOldVersionModel( false, this, "" ), m_filesMissing( !QDir( ConfigManager::inst()->stkDir() ).exists() || @@ -155,6 +155,7 @@ void MalletsInstrument::saveSettings( QDomDocument & _doc, QDomElement & _this ) m_presetsModel.saveSettings( _doc, _this, "preset" ); m_spreadModel.saveSettings( _doc, _this, "spread" ); + m_randomModel.saveSettings(_doc, _this, "randomness"); m_versionModel.saveSettings( _doc, _this, "version" ); m_isOldVersionModel.saveSettings( _doc, _this, "oldversion" ); } @@ -189,6 +190,7 @@ void MalletsInstrument::loadSettings( const QDomElement & _this ) m_presetsModel.loadSettings( _this, "preset" ); m_spreadModel.loadSettings( _this, "spread" ); + m_randomModel.loadSettings(_this, "randomness"); m_isOldVersionModel.loadSettings( _this, "oldversion" ); // To maintain backward compatibility @@ -284,15 +286,48 @@ void MalletsInstrument::playNote( NotePlayHandle * _n, } int p = m_presetsModel.value(); - + const float freq = _n->frequency(); - if ( _n->totalFramesPlayed() == 0 || _n->m_pluginData == nullptr ) + if (!_n->m_pluginData) { // If newer projects, adjust velocity to within stk's limits float velocityAdjust = m_isOldVersionModel.value() ? 100.0 : 200.0; const float vel = _n->getVolume() / velocityAdjust; + const float random = m_randomModel.value(); + float hardness = m_hardnessModel.value(); + float position = m_positionModel.value(); + float modulator = m_modulatorModel.value(); + float crossfade = m_crossfadeModel.value(); + float pressure = m_pressureModel.value(); + float speed = m_velocityModel.value(); + + if (p < 9) + { + hardness += random * (static_cast(fast_rand() % 128) - 64.0); + hardness = std::clamp(hardness, 0.0f, 128.0f); + + position += random * (static_cast(fast_rand() % 64) - 32.0); + position = std::clamp(position, 0.0f, 64.0f); + } + else if (p == 9) + { + modulator += random * (static_cast(fast_rand() % 128) - 64.0); + modulator = std::clamp(modulator, 0.0f, 128.0f); + + crossfade += random * (static_cast(fast_rand() % 128) - 64.0); + crossfade = std::clamp(crossfade, 0.0f, 128.0f); + } + else + { + pressure += random * (static_cast(fast_rand() % 128) - 64.0); + pressure = std::clamp(pressure, 0.0f, 128.0f); + + speed += random * (static_cast(fast_rand() % 128) - 64.0); + speed = std::clamp(speed, 0.0f, 128.0f); + } + // critical section as STK is not thread-safe static QMutex m; m.lock(); @@ -301,8 +336,8 @@ void MalletsInstrument::playNote( NotePlayHandle * _n, _n->m_pluginData = new MalletsSynth( freq, vel, m_stickModel.value(), - m_hardnessModel.value(), - m_positionModel.value(), + hardness, + position, m_vibratoGainModel.value(), m_vibratoFreqModel.value(), p, @@ -315,8 +350,8 @@ void MalletsInstrument::playNote( NotePlayHandle * _n, vel, p, m_lfoDepthModel.value(), - m_modulatorModel.value(), - m_crossfadeModel.value(), + modulator, + crossfade, m_lfoSpeedModel.value(), m_adsrModel.value(), (uint8_t) m_spreadModel.value(), @@ -326,12 +361,12 @@ void MalletsInstrument::playNote( NotePlayHandle * _n, { _n->m_pluginData = new MalletsSynth( freq, vel, - m_pressureModel.value(), + pressure, m_motionModel.value(), m_vibratoModel.value(), p - 10, m_strikeModel.value() * 128.0, - m_velocityModel.value(), + speed, (uint8_t) m_spreadModel.value(), Engine::audioEngine()->processingSampleRate() ); } @@ -343,8 +378,20 @@ void MalletsInstrument::playNote( NotePlayHandle * _n, const f_cnt_t offset = _n->noteOffset(); auto ps = static_cast(_n->m_pluginData); - ps->setFrequency( freq ); + ps->setFrequency(freq); + p = ps->presetIndex(); + if (p < 9) // ModalBar updates + { + ps->setVibratoGain(m_vibratoGainModel.value()); + ps->setVibratoFreq(m_vibratoFreqModel.value()); + } + else if (p == 9) // Tubular Bells updates + { + ps->setADSR(m_adsrModel.value()); + ps->setLFODepth(m_lfoDepthModel.value()); + ps->setLFOSpeed(m_lfoSpeedModel.value()); + } sample_t add_scale = 0.0f; if( p == 10 && m_isOldVersionModel.value() == true ) @@ -359,8 +406,6 @@ void MalletsInstrument::playNote( NotePlayHandle * _n, _working_buffer[frame][1] = ps->nextSampleRight() * ( m_scalers[p] + add_scale ); } - - instrumentTrack()->processAudioBuffer( _working_buffer, frames + offset, _n ); } @@ -404,16 +449,20 @@ 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() ) ); - m_spreadKnob = new Knob( knobVintage_32, this ); + m_spreadKnob = new Knob( KnobType::Vintage32, this ); m_spreadKnob->setLabel( tr( "Spread" ) ); m_spreadKnob->move( 190, 140 ); m_spreadKnob->setHintText( tr( "Spread:" ), "" ); + m_randomKnob = new Knob(KnobType::Vintage32, this); + m_randomKnob->setLabel(tr("Random")); + m_randomKnob->move(190, 190); + m_randomKnob->setHintText(tr("Random:"), ""); + // try to inform user about missing Stk-installation if( _instrument->m_filesMissing && getGUI() != nullptr ) { @@ -445,31 +494,31 @@ QWidget * MalletsInstrumentView::setupModalBarControls( QWidget * _parent ) auto widget = new QWidget(_parent); widget->setFixedSize( 250, 250 ); - m_hardnessKnob = new Knob( knobVintage_32, widget ); + m_hardnessKnob = new Knob( KnobType::Vintage32, widget ); m_hardnessKnob->setLabel( tr( "Hardness" ) ); m_hardnessKnob->move( 30, 90 ); m_hardnessKnob->setHintText( tr( "Hardness:" ), "" ); - m_positionKnob = new Knob( knobVintage_32, widget ); + m_positionKnob = new Knob( KnobType::Vintage32, widget ); m_positionKnob->setLabel( tr( "Position" ) ); m_positionKnob->move( 110, 90 ); m_positionKnob->setHintText( tr( "Position:" ), "" ); - m_vibratoGainKnob = new Knob( knobVintage_32, widget ); + m_vibratoGainKnob = new Knob( KnobType::Vintage32, widget ); m_vibratoGainKnob->setLabel( tr( "Vibrato gain" ) ); m_vibratoGainKnob->move( 30, 140 ); m_vibratoGainKnob->setHintText( tr( "Vibrato gain:" ), "" ); - m_vibratoFreqKnob = new Knob( knobVintage_32, widget ); + m_vibratoFreqKnob = new Knob( KnobType::Vintage32, widget ); m_vibratoFreqKnob->setLabel( tr( "Vibrato frequency" ) ); m_vibratoFreqKnob->move( 110, 140 ); m_vibratoFreqKnob->setHintText( tr( "Vibrato frequency:" ), "" ); - m_stickKnob = new Knob( knobVintage_32, widget ); + m_stickKnob = new Knob( KnobType::Vintage32, widget ); m_stickKnob->setLabel( tr( "Stick mix" ) ); m_stickKnob->move( 190, 90 ); m_stickKnob->setHintText( tr( "Stick mix:" ), "" ); - + return( widget ); } @@ -481,27 +530,27 @@ QWidget * MalletsInstrumentView::setupTubeBellControls( QWidget * _parent ) auto widget = new QWidget(_parent); widget->setFixedSize( 250, 250 ); - m_modulatorKnob = new Knob( knobVintage_32, widget ); + m_modulatorKnob = new Knob( KnobType::Vintage32, widget ); m_modulatorKnob->setLabel( tr( "Modulator" ) ); m_modulatorKnob->move( 30, 90 ); m_modulatorKnob->setHintText( tr( "Modulator:" ), "" ); - m_crossfadeKnob = new Knob( knobVintage_32, widget ); + m_crossfadeKnob = new Knob( KnobType::Vintage32, widget ); m_crossfadeKnob->setLabel( tr( "Crossfade" ) ); m_crossfadeKnob->move( 110, 90 ); m_crossfadeKnob->setHintText( tr( "Crossfade:" ), "" ); - m_lfoSpeedKnob = new Knob( knobVintage_32, widget ); + m_lfoSpeedKnob = new Knob( KnobType::Vintage32, widget ); m_lfoSpeedKnob->setLabel( tr( "LFO speed" ) ); m_lfoSpeedKnob->move( 30, 140 ); m_lfoSpeedKnob->setHintText( tr( "LFO speed:" ), "" ); - m_lfoDepthKnob = new Knob( knobVintage_32, widget ); + m_lfoDepthKnob = new Knob( KnobType::Vintage32, widget ); m_lfoDepthKnob->setLabel( tr( "LFO depth" ) ); m_lfoDepthKnob->move( 110, 140 ); m_lfoDepthKnob->setHintText( tr( "LFO depth:" ), "" ); - m_adsrKnob = new Knob( knobVintage_32, widget ); + m_adsrKnob = new Knob( KnobType::Vintage32, widget ); m_adsrKnob->setLabel( tr( "ADSR" ) ); m_adsrKnob->move( 190, 90 ); m_adsrKnob->setHintText( tr( "ADSR:" ), "" ); @@ -521,22 +570,22 @@ QWidget * MalletsInstrumentView::setupBandedWGControls( QWidget * _parent ) /* m_strikeLED = new LedCheckBox( tr( "Bowed" ), widget ); m_strikeLED->move( 138, 25 );*/ - m_pressureKnob = new Knob( knobVintage_32, widget ); + m_pressureKnob = new Knob( KnobType::Vintage32, widget ); m_pressureKnob->setLabel( tr( "Pressure" ) ); m_pressureKnob->move( 30, 90 ); m_pressureKnob->setHintText( tr( "Pressure:" ), "" ); -/* m_motionKnob = new Knob( knobVintage_32, widget ); +/* m_motionKnob = new Knob( KnobType::Vintage32, widget ); m_motionKnob->setLabel( tr( "Motion" ) ); m_motionKnob->move( 110, 90 ); m_motionKnob->setHintText( tr( "Motion:" ), "" );*/ - m_velocityKnob = new Knob( knobVintage_32, widget ); + m_velocityKnob = new Knob( KnobType::Vintage32, widget ); m_velocityKnob->setLabel( tr( "Speed" ) ); m_velocityKnob->move( 30, 140 ); m_velocityKnob->setHintText( tr( "Speed:" ), "" ); -/* m_vibratoKnob = new Knob( knobVintage_32, widget, tr( "Vibrato" ) ); +/* m_vibratoKnob = new Knob( KnobType::Vintage32, widget, tr( "Vibrato" ) ); m_vibratoKnob->setLabel( tr( "Vibrato" ) ); m_vibratoKnob->move( 110, 140 ); m_vibratoKnob->setHintText( tr( "Vibrato:" ), "" );*/ @@ -567,6 +616,7 @@ void MalletsInstrumentView::modelChanged() // m_strikeLED->setModel( &inst->m_strikeModel ); m_presetsCombo->setModel( &inst->m_presetsModel ); m_spreadKnob->setModel( &inst->m_spreadModel ); + m_randomKnob->setModel(&inst->m_randomModel); } diff --git a/plugins/Stk/Mallets/Mallets.h b/plugins/Stk/Mallets/Mallets.h index f66ac25d0..91e2dfce1 100644 --- a/plugins/Stk/Mallets/Mallets.h +++ b/plugins/Stk/Mallets/Mallets.h @@ -124,12 +124,38 @@ public: return( s ); } - inline void setFrequency( const StkFloat _pitch ) + inline void setFrequency(const StkFloat _pitch) { - if( m_voice ) - { - m_voice->setFrequency( _pitch ); - } + if (m_voice) { m_voice->setFrequency(_pitch); } + } + + // ModalBar updates + inline void setVibratoGain(const StkFloat _control8) + { + // bug in stk, Control Number 8 and 1 swapped in ModalBar + // we send the control number for stick direct mix instead + if (m_voice) { m_voice->controlChange(8, _control8); } + } + + inline void setVibratoFreq(const StkFloat _control11) + { + if (m_voice) { m_voice->controlChange(11, _control11); } + } + + // Tubular Bells updates + inline void setADSR(const StkFloat _control128) + { + if (m_voice) { m_voice->controlChange(128, _control128); } + } + + inline void setLFODepth(const StkFloat _control1) + { + if (m_voice) { m_voice->controlChange(1, _control1); } + } + + inline void setLFOSpeed(const StkFloat _control11) + { + if (m_voice) { m_voice->controlChange(11, _control11); } } inline int presetIndex() @@ -197,6 +223,7 @@ private: ComboBoxModel m_presetsModel; FloatModel m_spreadModel; + FloatModel m_randomModel; IntModel m_versionModel; BoolModel m_isOldVersionModel; @@ -255,6 +282,7 @@ private: ComboBox * m_presetsCombo; Knob * m_spreadKnob; + Knob * m_randomKnob; }; diff --git a/plugins/TapTempo/CMakeLists.txt b/plugins/TapTempo/CMakeLists.txt new file mode 100644 index 000000000..1fb6ba8dc --- /dev/null +++ b/plugins/TapTempo/CMakeLists.txt @@ -0,0 +1,3 @@ +INCLUDE(BuildPlugin) + +BUILD_PLUGIN(taptempo TapTempo.cpp TapTempoView.cpp MOCFILES TapTempo.h TapTempoView.h EMBEDDED_RESOURCES logo.png) \ No newline at end of file diff --git a/plugins/TapTempo/TapTempo.cpp b/plugins/TapTempo/TapTempo.cpp new file mode 100644 index 000000000..aad8d99b9 --- /dev/null +++ b/plugins/TapTempo/TapTempo.cpp @@ -0,0 +1,85 @@ +/* + * TapTempo.cpp - Plugin to count beats per minute + * + * + * 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 "TapTempo.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "AudioEngine.h" +#include "Engine.h" +#include "LedCheckBox.h" +#include "SamplePlayHandle.h" +#include "Song.h" +#include "embed.h" +#include "plugin_export.h" + +namespace lmms { + +extern "C" { +Plugin::Descriptor PLUGIN_EXPORT taptempo_plugin_descriptor + = {LMMS_STRINGIFY(PLUGIN_NAME), "Tap Tempo", QT_TRANSLATE_NOOP("PluginBrowser", "Tap to the beat"), + "saker ", 0x0100, Plugin::Type::Tool, new PluginPixmapLoader("logo"), nullptr, nullptr}; + +PLUGIN_EXPORT Plugin* lmms_plugin_main(Model*, void*) +{ + return new TapTempo; +} +} + +TapTempo::TapTempo() + : ToolPlugin(&taptempo_plugin_descriptor, nullptr) +{ +} + +void TapTempo::onBpmClick() +{ + const auto currentTime = clock::now(); + if (m_numTaps == 0) + { + m_startTime = currentTime; + } + else + { + using namespace std::chrono_literals; + const auto secondsElapsed = (currentTime - m_startTime) / 1.0s; + if (m_numTaps >= m_tapsNeededToDisplay) { m_bpm = m_numTaps / secondsElapsed * 60; } + } + + ++m_numTaps; +} + +QString TapTempo::nodeName() const +{ + return taptempo_plugin_descriptor.name; +} +} // namespace lmms \ No newline at end of file diff --git a/plugins/TapTempo/TapTempo.h b/plugins/TapTempo/TapTempo.h new file mode 100644 index 000000000..be540b8da --- /dev/null +++ b/plugins/TapTempo/TapTempo.h @@ -0,0 +1,61 @@ +/* + * TapTempo.h - Plugin to count beats per minute + * + * 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 LMMS_TAP_TEMPO_H +#define LMMS_TAP_TEMPO_H + +#include + +#include "TapTempoView.h" +#include "ToolPlugin.h" + +namespace lmms { + +class TapTempo : public ToolPlugin +{ + Q_OBJECT +public: + using clock = std::chrono::steady_clock; + + TapTempo(); + void onBpmClick(); + + QString nodeName() const override; + void saveSettings(QDomDocument&, QDomElement&) override {} + void loadSettings(const QDomElement&) override {} + + gui::PluginView* instantiateView(QWidget*) override { return new gui::TapTempoView(this); } + +private: + std::chrono::time_point m_startTime; + int m_numTaps = 0; + int m_tapsNeededToDisplay = 2; + double m_bpm = 0.0; + bool m_showDecimal = false; + + friend class gui::TapTempoView; +}; +} // namespace lmms + +#endif // LMMS_TAP_TEMPO_H diff --git a/plugins/TapTempo/TapTempoView.cpp b/plugins/TapTempo/TapTempoView.cpp new file mode 100644 index 000000000..d6c24fcf5 --- /dev/null +++ b/plugins/TapTempo/TapTempoView.cpp @@ -0,0 +1,168 @@ +/* + * TapTempoView.cpp - Plugin to count beats per minute + * + * + * 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 "TapTempoView.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "Engine.h" +#include "SamplePlayHandle.h" +#include "Song.h" +#include "TapTempo.h" + +namespace lmms::gui { +TapTempoView::TapTempoView(TapTempo* plugin) + : ToolPluginView(plugin) + , m_plugin(plugin) +{ + setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); + + auto font = QFont(); + font.setPointSize(24); + + m_tapButton = new QPushButton(); + m_tapButton->setFixedSize(200, 200); + m_tapButton->setFont(font); + m_tapButton->setText(tr("0")); + + auto precisionCheckBox = new QCheckBox(tr("Precision")); + precisionCheckBox->setFocusPolicy(Qt::NoFocus); + precisionCheckBox->setToolTip(tr("Display in high precision")); + precisionCheckBox->setText(tr("Precision")); + + auto muteCheckBox = new QCheckBox(tr("0.0 ms")); + muteCheckBox->setFocusPolicy(Qt::NoFocus); + muteCheckBox->setToolTip(tr("Mute metronome")); + muteCheckBox->setText(tr("Mute")); + + m_msLabel = new QLabel(); + m_msLabel->setFocusPolicy(Qt::NoFocus); + m_msLabel->setToolTip(tr("BPM in milliseconds")); + m_msLabel->setText(tr("0 ms")); + + m_hzLabel = new QLabel(); + m_hzLabel->setFocusPolicy(Qt::NoFocus); + m_hzLabel->setToolTip(tr("Frequency of BPM")); + m_hzLabel->setText(tr("0.0000 hz")); + + auto resetButton = new QPushButton(tr("Reset")); + resetButton->setFocusPolicy(Qt::NoFocus); + resetButton->setToolTip(tr("Reset counter and sidebar information")); + + auto syncButton = new QPushButton(tr("Sync")); + syncButton->setFocusPolicy(Qt::NoFocus); + syncButton->setToolTip(tr("Sync with project tempo")); + + auto optionLayout = new QVBoxLayout(); + optionLayout->addWidget(precisionCheckBox); + optionLayout->addWidget(muteCheckBox); + + auto bpmInfoLayout = new QVBoxLayout(); + bpmInfoLayout->addWidget(m_msLabel, 0, Qt::AlignHCenter); + bpmInfoLayout->addWidget(m_hzLabel, 0, Qt::AlignHCenter); + + auto sidebarLayout = new QHBoxLayout(); + sidebarLayout->addLayout(optionLayout); + sidebarLayout->addLayout(bpmInfoLayout); + + auto buttonsLayout = new QHBoxLayout(); + buttonsLayout->addWidget(resetButton, 0, Qt::AlignCenter); + buttonsLayout->addWidget(syncButton, 0, Qt::AlignCenter); + + auto mainLayout = new QVBoxLayout(this); + mainLayout->addWidget(m_tapButton, 0, Qt::AlignCenter); + mainLayout->addLayout(buttonsLayout); + mainLayout->addLayout(sidebarLayout); + + connect(m_tapButton, &QPushButton::pressed, this, [this, muteCheckBox]() { + if (!muteCheckBox->isChecked()) + { + const auto timeSigNumerator = Engine::getSong()->getTimeSigModel().getNumerator(); + Engine::audioEngine()->addPlayHandle(new SamplePlayHandle( + m_plugin->m_numTaps % timeSigNumerator == 0 ? "misc/metronome02.ogg" : "misc/metronome01.ogg")); + } + + m_plugin->onBpmClick(); + updateLabels(); + }); + + connect(resetButton, &QPushButton::pressed, this, [this]() { closeEvent(nullptr); }); + + connect(precisionCheckBox, &QCheckBox::toggled, [this](bool checked) { + m_plugin->m_showDecimal = checked; + updateLabels(); + }); + + connect(syncButton, &QPushButton::clicked, this, [this]() { + const auto& tempoModel = Engine::getSong()->tempoModel(); + if (m_plugin->m_bpm < tempoModel.minValue() || m_plugin->m_bpm > tempoModel.maxValue()) { return; } + Engine::getSong()->setTempo(std::round(m_plugin->m_bpm)); + }); + + hide(); + if (parentWidget()) + { + parentWidget()->hide(); + parentWidget()->layout()->setSizeConstraint(QLayout::SetFixedSize); + + Qt::WindowFlags flags = parentWidget()->windowFlags(); + flags |= Qt::MSWindowsFixedSizeDialogHint; + flags &= ~Qt::WindowMaximizeButtonHint; + parentWidget()->setWindowFlags(flags); + } +} + +void TapTempoView::updateLabels() +{ + const double bpm = m_plugin->m_showDecimal ? m_plugin->m_bpm : std::round(m_plugin->m_bpm); + const double hz = bpm / 60; + const double ms = bpm > 0 ? 1 / hz * 1000 : 0; + + m_tapButton->setText(QString::number(bpm, 'f', m_plugin->m_showDecimal ? 1 : 0)); + m_msLabel->setText(tr("%1 ms").arg(ms, 0, 'f', m_plugin->m_showDecimal ? 1 : 0)); + m_hzLabel->setText(tr("%1 hz").arg(hz, 0, 'f', 4)); +} + +void TapTempoView::keyPressEvent(QKeyEvent* event) +{ + QWidget::keyPressEvent(event); + if (!event->isAutoRepeat()) { m_plugin->onBpmClick(); } +} + +void TapTempoView::closeEvent(QCloseEvent*) +{ + m_plugin->m_numTaps = 0; + m_plugin->m_bpm = 0; + updateLabels(); +} + +} // namespace lmms::gui diff --git a/plugins/TapTempo/TapTempoView.h b/plugins/TapTempo/TapTempoView.h new file mode 100644 index 000000000..a2a522d3f --- /dev/null +++ b/plugins/TapTempo/TapTempoView.h @@ -0,0 +1,64 @@ +/* + * TapTempoView.h - Plugin to count beats per minute + * + * + * 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 LMMS_GUI_TAP_TEMPO_VIEW_H +#define LMMS_GUI_TAP_TEMPO_VIEW_H + +#include "ToolPluginView.h" + +class QVBoxLayout; +class QHBoxLayout; +class QPushButton; +class QLabel; +class QCloseEvent; +class QKeyEvent; +class QCheckBox; + +namespace lmms { +class TapTempo; +} + +namespace lmms::gui { + +class TapTempoView : public ToolPluginView +{ + Q_OBJECT +public: + TapTempoView(TapTempo* plugin); + void updateLabels(); + void keyPressEvent(QKeyEvent* event) override; + void closeEvent(QCloseEvent*) override; + +private: + QPushButton* m_tapButton; + QLabel* m_msLabel; + QLabel* m_hzLabel; + TapTempo* m_plugin; + friend class TapTempo; +}; +} // namespace lmms::gui + +#endif // LMMS_GUI_TAP_TEMPO_VIEW_H diff --git a/plugins/TapTempo/logo.png b/plugins/TapTempo/logo.png new file mode 100644 index 000000000..67e6a3503 Binary files /dev/null and b/plugins/TapTempo/logo.png differ diff --git a/plugins/TripleOscillator/TripleOscillator.cpp b/plugins/TripleOscillator/TripleOscillator.cpp index 25baea208..d5f2e905f 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" @@ -57,7 +59,7 @@ Plugin::Descriptor PLUGIN_EXPORT tripleoscillator_plugin_descriptor = "in several ways" ), "Tobias Doerffel ", 0x0110, - Plugin::Instrument, + Plugin::Type::Instrument, new PluginPixmapLoader( "logo" ), nullptr, nullptr, @@ -84,10 +86,10 @@ OscillatorObject::OscillatorObject( Model * _parent, int _idx ) : tr( "Osc %1 phase-offset" ).arg( _idx+1 ) ), m_stereoPhaseDetuningModel( 0.0f, 0.0f, 360.0f, 1.0f, this, tr( "Osc %1 stereo phase-detuning" ).arg( _idx+1 ) ), - m_waveShapeModel( Oscillator::SineWave, 0, + m_waveShapeModel( static_cast(Oscillator::WaveShape::Sine), 0, Oscillator::NumWaveShapes-1, this, tr( "Osc %1 wave shape" ).arg( _idx+1 ) ), - m_modulationAlgoModel( Oscillator::SignalMix, 0, + m_modulationAlgoModel( static_cast(Oscillator::ModulationAlgo::SignalMix), 0, Oscillator::NumModulationAlgos-1, this, tr( "Modulation type %1" ).arg( _idx+1 ) ), m_useWaveTableModel(true), @@ -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()); } @@ -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)); } + } } } @@ -308,7 +309,7 @@ QString TripleOscillator::nodeName() const void TripleOscillator::playNote( NotePlayHandle * _n, sampleFrame * _working_buffer ) { - if( _n->totalFramesPlayed() == 0 || _n->m_pluginData == nullptr ) + if (!_n->m_pluginData) { auto oscs_l = std::array{}; auto oscs_r = std::array{}; @@ -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; @@ -380,8 +382,6 @@ void TripleOscillator::playNote( NotePlayHandle * _n, applyFadeIn(_working_buffer, _n); applyRelease( _working_buffer, _n ); - - instrumentTrack()->processAudioBuffer( _working_buffer, frames + offset, _n ); } @@ -426,7 +426,7 @@ class TripleOscKnob : public Knob { public: TripleOscKnob( QWidget * _parent ) : - Knob( knobStyled, _parent ) + Knob( KnobType::Styled, _parent ) { setFixedSize( 28, 35 ); } @@ -554,7 +554,7 @@ TripleOscillatorView::TripleOscillatorView( Instrument * _instrument, int knob_y = osc_y + i * osc_h; // setup volume-knob - auto vk = new Knob(knobStyled, this); + auto vk = new Knob(KnobType::Styled, this); vk->setVolumeKnob( true ); vk->setFixedSize( 28, 35 ); vk->move( 6, knob_y ); diff --git a/plugins/TripleOscillator/TripleOscillator.h b/plugins/TripleOscillator/TripleOscillator.h index f3290153b..4b6d97835 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; @@ -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/VecControlsDialog.cpp b/plugins/Vectorscope/VecControlsDialog.cpp index 97898fe70..9aa2cfd8d 100644 --- a/plugins/Vectorscope/VecControlsDialog.cpp +++ b/plugins/Vectorscope/VecControlsDialog.cpp @@ -79,7 +79,7 @@ VecControlsDialog::VecControlsDialog(VecControls *controls) : config_layout->addStretch(); // Persistence knob - auto persistenceKnob = new Knob(knobSmall_17, this); + auto persistenceKnob = new Knob(KnobType::Small17, this); persistenceKnob->setModel(&controls->m_persistenceModel); persistenceKnob->setLabel(tr("Persist.")); persistenceKnob->setToolTip(tr("Trace persistence: higher amount means the trace will stay bright for longer time.")); 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/Vectorscope/Vectorscope.cpp b/plugins/Vectorscope/Vectorscope.cpp index b9880691c..f843fc86d 100644 --- a/plugins/Vectorscope/Vectorscope.cpp +++ b/plugins/Vectorscope/Vectorscope.cpp @@ -39,7 +39,7 @@ extern "C" { QT_TRANSLATE_NOOP("PluginBrowser", "A stereo field visualizer."), "Martin Pavelek ", 0x0100, - Plugin::Effect, + Plugin::Type::Effect, new PluginPixmapLoader("logo"), nullptr, nullptr, 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 eab3693b8..834b583ed 100644 --- a/plugins/Vestige/Vestige.cpp +++ b/plugins/Vestige/Vestige.cpp @@ -77,7 +77,7 @@ Plugin::Descriptor Q_DECL_EXPORT vestige_plugin_descriptor = "VST-host for using VST(i)-plugins within LMMS" ), "Tobias Doerffel ", 0x0100, - Plugin::Instrument, + Plugin::Type::Instrument, new PluginPixmapLoader( "logo" ), #ifdef LMMS_BUILD_LINUX "dll,so", @@ -185,8 +185,8 @@ VestigeInstrument::~VestigeInstrument() } Engine::audioEngine()->removePlayHandlesOfTypes( instrumentTrack(), - PlayHandle::TypeNotePlayHandle - | PlayHandle::TypeInstrumentPlayHandle ); + PlayHandle::Type::NotePlayHandle + | PlayHandle::Type::InstrumentPlayHandle ); closePlugin(); } @@ -399,8 +399,6 @@ void VestigeInstrument::play( sampleFrame * _buf ) { if (!m_pluginMutex.tryLock(Engine::getSong()->isExporting() ? -1 : 0)) {return;} - const fpp_t frames = Engine::audioEngine()->framesPerPeriod(); - if( m_plugin == nullptr ) { m_pluginMutex.unlock(); @@ -409,8 +407,6 @@ void VestigeInstrument::play( sampleFrame * _buf ) m_plugin->process( nullptr, _buf ); - instrumentTrack()->processAudioBuffer( _buf, frames, nullptr ); - m_pluginMutex.unlock(); } @@ -489,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 ); @@ -597,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() ) ); @@ -610,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() ) ); @@ -885,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( @@ -894,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 ); @@ -906,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 ) ); @@ -1004,7 +992,7 @@ ManageVestigeInstrumentView::ManageVestigeInstrumentView( Instrument * _instrume sprintf(paramStr.data(), "param%d", i); s_dumpValues = dump[paramStr.data()].split(":"); - vstKnobs[ i ] = new CustomTextKnob( knobBright_26, this, s_dumpValues.at( 1 ) ); + vstKnobs[ i ] = new CustomTextKnob( KnobType::Bright26, this, s_dumpValues.at( 1 ) ); vstKnobs[ i ]->setDescription( s_dumpValues.at( 1 ) + ":" ); vstKnobs[ i ]->setLabel( s_dumpValues.at( 1 ).left( 15 ) ); @@ -1066,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++ ) { @@ -1076,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 0a36c4924..9ac66f74d 100644 --- a/plugins/Vestige/Vestige.h +++ b/plugins/Vestige/Vestige.h @@ -72,7 +72,7 @@ public: virtual Flags flags() const { - return IsSingleStreamed | IsMidiBased; + return Flag::IsSingleStreamed | Flag::IsMidiBased; } virtual bool handleMidiEvent( const MidiEvent& event, const TimePos& time, f_cnt_t offset = 0 ); @@ -131,8 +131,6 @@ protected: private: - static QPixmap * s_artwork; - VestigeInstrument * m_vi; QWidget *widget; @@ -175,7 +173,6 @@ protected: private: virtual void modelChanged(); - static QPixmap * s_artwork; VestigeInstrument * m_vi; diff --git a/plugins/Vibed/CMakeLists.txt b/plugins/Vibed/CMakeLists.txt index 39e2ce57c..1cffeccfc 100644 --- a/plugins/Vibed/CMakeLists.txt +++ b/plugins/Vibed/CMakeLists.txt @@ -1,3 +1,6 @@ INCLUDE(BuildPlugin) -BUILD_PLUGIN(vibedstrings Vibed.cpp NineButtonSelector.cpp StringContainer.cpp VibratingString.cpp Vibed.h NineButtonSelector.h StringContainer.h VibratingString.h MOCFILES Vibed.h NineButtonSelector.h EMBEDDED_RESOURCES *.png) +BUILD_PLUGIN(vibedstrings Vibed.cpp NineButtonSelector.cpp VibratingString.cpp + Vibed.h NineButtonSelector.h VibratingString.h + MOCFILES Vibed.h NineButtonSelector.h + EMBEDDED_RESOURCES *.png) diff --git a/plugins/Vibed/NineButtonSelector.cpp b/plugins/Vibed/NineButtonSelector.cpp index 431dbec8b..3a68e5ee6 100644 --- a/plugins/Vibed/NineButtonSelector.cpp +++ b/plugins/Vibed/NineButtonSelector.cpp @@ -3,7 +3,7 @@ * * Copyright (c) 2006-2007 Danny McRae * Copyright (c) 2009 Tobias Doerffel - * + * * This file is part of LMMS - https://lmms.io * * This program is free software; you can redistribute it and/or @@ -23,237 +23,69 @@ * */ - #include "NineButtonSelector.h" #include "CaptionMenu.h" -#include "PixmapButton.h" namespace lmms::gui { -NineButtonSelector::NineButtonSelector( QPixmap _button0_on, - QPixmap _button0_off, - QPixmap _button1_on, - QPixmap _button1_off, - QPixmap _button2_on, - QPixmap _button2_off, - QPixmap _button3_on, - QPixmap _button3_off, - QPixmap _button4_on, - QPixmap _button4_off, - QPixmap _button5_on, - QPixmap _button5_off, - QPixmap _button6_on, - QPixmap _button6_off, - QPixmap _button7_on, - QPixmap _button7_off, - QPixmap _button8_on, - QPixmap _button8_off, - int _default, - int _x, int _y, - QWidget * _parent ): - QWidget( _parent ), - IntModelView( new NineButtonSelectorModel(0, 8, _default, nullptr, - QString(), true ), this ) +NineButtonSelector::NineButtonSelector(std::array onOffIcons, int defaultButton, int x, int y, QWidget* parent) : + QWidget{parent}, + IntModelView{new NineButtonSelectorModel{defaultButton, 0, 8, nullptr, QString{}, true}, this} { - setFixedSize( 50, 50 ); - move( _x, _y ); + setFixedSize(50, 50); + move(x, y); - m_button = new PixmapButton( this, nullptr ); - m_button->move( 1, 1 ); - m_button->setActiveGraphic( _button0_on ); - m_button->setInactiveGraphic( _button0_off ); - m_button->setChecked( false ); - connect( m_button, SIGNAL ( clicked () ), - this, SLOT ( button0Clicked() ) ); - m_buttons.append( m_button ); - - m_button = new PixmapButton( this, nullptr ); - m_button->move( 18, 1 ); - m_button->setActiveGraphic( _button1_on ); - m_button->setInactiveGraphic( _button1_off ); - m_button->setChecked( false ); - connect( m_button, SIGNAL ( clicked () ), - this, SLOT ( button1Clicked() ) ); - m_buttons.append( m_button ); - - m_button = new PixmapButton( this, nullptr ); - m_button->move( 35, 1 ); - m_button->setActiveGraphic( _button2_on ); - m_button->setInactiveGraphic( _button2_off ); - m_button->setChecked( false ); - connect( m_button, SIGNAL ( clicked () ), - this, SLOT ( button2Clicked() ) ); - m_buttons.append( m_button ); - - m_button = new PixmapButton( this, nullptr ); - m_button->move( 1, 18 ); - m_button->setActiveGraphic( _button3_on ); - m_button->setInactiveGraphic( _button3_off ); - m_button->setChecked( false ); - connect( m_button, SIGNAL ( clicked () ), - this, SLOT ( button3Clicked() ) ); - m_buttons.append( m_button ); - - m_button = new PixmapButton( this, nullptr ); - m_button->move( 18, 18 ); - m_button->setActiveGraphic( _button4_on ); - m_button->setInactiveGraphic( _button4_off ); - m_button->setChecked( false ); - connect( m_button, SIGNAL ( clicked () ), - this, SLOT ( button4Clicked() ) ); - m_buttons.append( m_button ); - - m_button = new PixmapButton( this, nullptr ); - m_button->move( 35, 18 ); - m_button->setActiveGraphic( _button5_on ); - m_button->setInactiveGraphic( _button5_off ); - m_button->setChecked( false ); - connect( m_button, SIGNAL ( clicked () ), - this, SLOT ( button5Clicked() ) ); - m_buttons.append( m_button ); - - m_button = new PixmapButton( this, nullptr ); - m_button->move( 1, 35 ); - m_button->setActiveGraphic( _button6_on ); - m_button->setInactiveGraphic( _button6_off ); - m_button->setChecked( false ); - connect( m_button, SIGNAL ( clicked () ), - this, SLOT ( button6Clicked() ) ); - m_buttons.append( m_button ); - - m_button = new PixmapButton( this, nullptr ); - m_button->move( 18, 35 ); - m_button->setActiveGraphic( _button7_on ); - m_button->setInactiveGraphic( _button7_off ); - m_button->setChecked( false ); - connect( m_button, SIGNAL ( clicked () ), - this, SLOT ( button7Clicked() ) ); - m_buttons.append( m_button ); - - m_button = new PixmapButton( this, nullptr ); - m_button->move( 35, 35 ); - m_button->setActiveGraphic( _button8_on ); - m_button->setInactiveGraphic( _button8_off ); - m_button->setChecked( false ); - connect( m_button, SIGNAL ( clicked () ), - this, SLOT ( button8Clicked() ) ); - m_buttons.append( m_button ); - - m_lastBtn = m_buttons[_default]; - m_lastBtn->setChecked( true ); -} - - -NineButtonSelector::~ NineButtonSelector() -{ - for( int i = 0; i < 9; i++ ) + for (int i = 0; i < 9; ++i) { - delete m_buttons[i]; + m_buttons[i] = std::make_unique(this, nullptr); + const int buttonX = 1 + (i % 3) * 17; + const int buttonY = 1 + (i / 3) * 17; + m_buttons[i]->move(buttonX, buttonY); + m_buttons[i]->setActiveGraphic(onOffIcons[i * 2]); + m_buttons[i]->setInactiveGraphic(onOffIcons[(i * 2) + 1]); + m_buttons[i]->setChecked(false); + connect(m_buttons[i].get(), &PixmapButton::clicked, this, [=](){ this->buttonClicked(i); }); } + + m_lastBtn = m_buttons[defaultButton].get(); + m_lastBtn->setChecked(true); } - - - -void NineButtonSelector::button0Clicked() +void NineButtonSelector::buttonClicked(int id) { - setSelected( 0 ); -} - - - - -void NineButtonSelector::button1Clicked() -{ - setSelected( 1 ); -} - - - - -void NineButtonSelector::button2Clicked() -{ - setSelected( 2 ); -} - - - - -void NineButtonSelector::button3Clicked() -{ - setSelected( 3 ); -} - - - - -void NineButtonSelector::button4Clicked() -{ - setSelected( 4 ); -} - - - - -void NineButtonSelector::button5Clicked() -{ - setSelected( 5 ); -} - - - - -void NineButtonSelector::button6Clicked() -{ - setSelected( 6 ); -} - - - - -void NineButtonSelector::button7Clicked() -{ - setSelected( 7 ); -} - - - - -void NineButtonSelector::button8Clicked() -{ - setSelected( 8 ); + setSelected(id); } void NineButtonSelector::modelChanged() { - updateButton( model()->value() ); + updateButton(model()->value()); } -void NineButtonSelector::setSelected( int _new_button ) +void NineButtonSelector::setSelected(int newButton) { - model()->setValue(_new_button); - updateButton( _new_button ); + model()->setValue(newButton); + updateButton(newButton); } -void NineButtonSelector::updateButton( int _new_button ) +void NineButtonSelector::updateButton(int newButton) { - m_lastBtn->setChecked( false ); + m_lastBtn->setChecked(false); m_lastBtn->update(); - m_lastBtn = m_buttons[_new_button]; - m_lastBtn->setChecked( true ); + m_lastBtn = m_buttons[newButton].get(); + m_lastBtn->setChecked(true); m_lastBtn->update(); - - emit NineButtonSelection( _new_button ); + + emit NineButtonSelection(newButton); } -void NineButtonSelector::contextMenuEvent( QContextMenuEvent * ) +void NineButtonSelector::contextMenuEvent(QContextMenuEvent*) { - CaptionMenu contextMenu( windowTitle(), this ); - contextMenu.exec( QCursor::pos() ); + CaptionMenu contextMenu{windowTitle(), this}; + contextMenu.exec(QCursor::pos()); } diff --git a/plugins/Vibed/NineButtonSelector.h b/plugins/Vibed/NineButtonSelector.h index 9c768e996..78ae7c3d1 100644 --- a/plugins/Vibed/NineButtonSelector.h +++ b/plugins/Vibed/NineButtonSelector.h @@ -3,7 +3,7 @@ * * Copyright (c) 2006-2007 Danny McRae * Copyright (c) 2009 Tobias Doerffel - * + * * This file is part of LMMS - https://lmms.io * * This program is free software; you can redistribute it and/or @@ -22,87 +22,56 @@ * Boston, MA 02110-1301 USA. * */ -#ifndef _NINE_BUTTON_SELECTOR_H -#define _NINE_BUTTON_SELECTOR_H +#ifndef LMMS_GUI_NINE_BUTTON_SELECTOR_H +#define LMMS_GUI_NINE_BUTTON_SELECTOR_H + +#include +#include #include + #include "AutomatableModelView.h" +#include "PixmapButton.h" namespace lmms { -class graphModel; -} -namespace lmms::gui + +namespace gui { -class Knob; -class PixmapButton; - - -class NineButtonSelector: public QWidget , public IntModelView +class NineButtonSelector : public QWidget, public IntModelView { Q_OBJECT - public: - NineButtonSelector( QPixmap _button0_on, - QPixmap _button0_off, - QPixmap _button1_on, - QPixmap _button1_off, - QPixmap _button2_on, - QPixmap _button2_off, - QPixmap _button3_on, - QPixmap _button3_off, - QPixmap _button4_on, - QPixmap _button4_off, - QPixmap _button5_on, - QPixmap _button5_off, - QPixmap _button6_on, - QPixmap _button6_off, - QPixmap _button7_on, - QPixmap _button7_off, - QPixmap _button8_on, - QPixmap _button8_off, - int _default, - int _x, int _y, - QWidget * _parent); - ~NineButtonSelector() override; - -// inline int getSelected() { -// return( castModel()->value() ); -// }; + NineButtonSelector(std::array onOffIcons, int defaultButton, int x, int y, QWidget* parent); + ~NineButtonSelector() override = default; protected: - void setSelected( int _new_button ); - + void setSelected(int newButton); + public slots: - void button0Clicked(); - void button1Clicked(); - void button2Clicked(); - void button3Clicked(); - void button4Clicked(); - void button5Clicked(); - void button6Clicked(); - void button7Clicked(); - void button8Clicked(); - void contextMenuEvent( QContextMenuEvent * ) override; - + void buttonClicked(int id); + void contextMenuEvent(QContextMenuEvent*) override; + signals: - void NineButtonSelection( int ); - + void NineButtonSelection(int); + private: void modelChanged() override; - void updateButton( int ); + void updateButton(int); - QList m_buttons; - PixmapButton * m_button; - PixmapButton * m_lastBtn; + std::array, 9> m_buttons; + PixmapButton* m_lastBtn; +}; -} ; + +} // namespace gui using NineButtonSelectorModel = IntModel; -} // namespace lmms::gui -#endif +} // namespace lmms + +#endif // LMMS_GUI_NINE_BUTTON_SELECTOR_H diff --git a/plugins/Vibed/StringContainer.cpp b/plugins/Vibed/StringContainer.cpp deleted file mode 100644 index 36dee6da0..000000000 --- a/plugins/Vibed/StringContainer.cpp +++ /dev/null @@ -1,108 +0,0 @@ -/* - * StringContainer.cpp - contains a collection of strings - * - * Copyright (c) 2006 Danny McRae - * - * 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 "StringContainer.h" - -namespace lmms -{ - - -StringContainer::StringContainer(const float _pitch, - const sample_rate_t _sample_rate, - const int _buffer_length, - const int _strings ) : - m_pitch( _pitch ), - m_sampleRate( _sample_rate ), - m_bufferLength( _buffer_length ) -{ - for( int i = 0; i < _strings; i++ ) - { - m_exists.append( false ); - } -} - - - - -void StringContainer::addString(int _harm, - const float _pick, - const float _pickup, - const float * _impulse, - const float _randomize, - const float _string_loss, - const float _detune, - const int _oversample, - const bool _state, - const int _id ) -{ - float harm; - switch( _harm ) - { - case 0: - harm = 0.25f; - break; - case 1: - harm = 0.5f; - break; - case 2: - harm = 1.0f; - break; - case 3: - harm = 2.0f; - break; - case 4: - harm = 3.0f; - break; - case 5: - harm = 4.0f; - break; - case 6: - harm = 5.0f; - break; - case 7: - harm = 6.0f; - break; - case 8: - harm = 7.0f; - break; - default: - harm = 1.0f; - } - - m_strings.append( new VibratingString( m_pitch * harm, - _pick, - _pickup, - const_cast(_impulse), - m_bufferLength, - m_sampleRate, - _oversample, - _randomize, - _string_loss, - _detune, - _state ) ); - m_exists[_id] = true; -} - - -} // namespace lmms \ No newline at end of file diff --git a/plugins/Vibed/StringContainer.h b/plugins/Vibed/StringContainer.h deleted file mode 100644 index 96408c56a..000000000 --- a/plugins/Vibed/StringContainer.h +++ /dev/null @@ -1,86 +0,0 @@ -/* StringContainer.h - contains a collection of strings - * - * Copyright (c) 2006 Danny McRae - * - * 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 _STRING_CONTAINER_H -#define _STRING_CONTAINER_H - -#include - -#include "VibratingString.h" -#include "MemoryManager.h" - -namespace lmms -{ - - -class StringContainer -{ - MM_OPERATORS -public: - StringContainer(const float _pitch, - const sample_rate_t _sample_rate, - const int _buffer_length, - const int _strings = 9 ); - - void addString( int _harm, - const float _pick, - const float _pickup, - const float * _impluse, - const float _randomize, - const float _string_loss, - const float _detune, - const int _oversample, - const bool _state, - const int _id ); - - bool exists( int _id ) const - { - return m_exists[_id]; - } - - ~StringContainer() - { - int strings = m_strings.count(); - for( int i = 0; i < strings; i++ ) - { - delete m_strings[i]; - } - } - - float getStringSample( int _string ) - { - return m_strings[_string]->nextSample(); - } - -private: - QVector m_strings; - const float m_pitch; - const sample_rate_t m_sampleRate; - const int m_bufferLength; - QVector m_exists; -} ; - - -} // namespace lmms - -#endif diff --git a/plugins/Vibed/Vibed.cpp b/plugins/Vibed/Vibed.cpp index 9f119aa6e..ddf9097a5 100644 --- a/plugins/Vibed/Vibed.cpp +++ b/plugins/Vibed/Vibed.cpp @@ -22,21 +22,19 @@ * */ +#include "Vibed.h" +#include +#include #include -#include "Vibed.h" #include "AudioEngine.h" #include "Engine.h" -#include "Graph.h" #include "InstrumentTrack.h" -#include "Knob.h" -#include "LedCheckBox.h" #include "NotePlayHandle.h" -#include "PixmapButton.h" +#include "VibratingString.h" #include "base64.h" #include "CaptionMenu.h" -#include "StringContainer.h" #include "volume.h" #include "Song.h" @@ -52,628 +50,469 @@ extern "C" Plugin::Descriptor PLUGIN_EXPORT vibedstrings_plugin_descriptor = { - LMMS_STRINGIFY( PLUGIN_NAME ), + LMMS_STRINGIFY(PLUGIN_NAME), "Vibed", - QT_TRANSLATE_NOOP( "PluginBrowser", - "Vibrating string modeler" ), + QT_TRANSLATE_NOOP("PluginBrowser", "Vibrating string modeler"), "Danny McRae ", 0x0100, - Plugin::Instrument, - new PluginPixmapLoader( "logo" ), - nullptr, + Plugin::Type::Instrument, + new PluginPixmapLoader("logo"), nullptr, + nullptr }; } -Vibed::Vibed( InstrumentTrack * _instrumentTrack ) : - Instrument( _instrumentTrack, &vibedstrings_plugin_descriptor ) +class Vibed::StringContainer { +public: + StringContainer(float pitch, sample_rate_t sampleRate, int bufferLength) : + m_pitch(pitch), m_sampleRate(sampleRate), m_bufferLength(bufferLength) {} - FloatModel * knob; - BoolModel * led; - gui::NineButtonSelectorModel * harmonic; - graphModel * graphTmp; + ~StringContainer() = default; - for( int harm = 0; harm < 9; harm++ ) + void addString(int harm, float pick, float pickup, const float* impulse, float randomize, + float stringLoss, float detune, int oversample, bool state, int id) { - knob = new FloatModel( DefaultVolume, MinVolume, MaxVolume, - 1.0f, this, tr( "String %1 volume" ).arg( harm+1 ) ); - m_volumeKnobs.append( knob ); + constexpr auto octave = std::array{0.25f, 0.5f, 1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f}; + assert(harm >= 0 && harm < octave.size()); - knob = new FloatModel( 0.0f, 0.0f, 0.05f, 0.001f, this, - tr( "String %1 stiffness" ).arg( harm+1 ) ); - m_stiffnessKnobs.append( knob ); + m_strings[id] = VibratingString{m_pitch * octave[harm], pick, pickup, impulse, m_bufferLength, + m_sampleRate, oversample, randomize, stringLoss, detune, state}; - knob = new FloatModel( 0.0f, 0.0f, 0.05f, 0.005f, this, - tr( "Pick %1 position" ).arg( harm+1 ) ); - m_pickKnobs.append( knob ); + m_exists[id] = true; + } - knob = new FloatModel( 0.05f, 0.0f, 0.05f, 0.005f, this, - tr( "Pickup %1 position" ).arg( harm+1 ) ); - m_pickupKnobs.append( knob ); + bool exists(int id) const { return m_exists[id]; } - knob = new FloatModel( 0.0f, -1.0f, 1.0f, 0.01f, this, - tr( "String %1 panning" ).arg( harm+1 ) ); - m_panKnobs.append( knob ); + sample_t getStringSample(int id) { return m_strings[id].nextSample(); } - knob = new FloatModel( 0.0f, -0.1f, 0.1f, 0.001f, this, - tr( "String %1 detune" ).arg( harm+1 ) ); - m_detuneKnobs.append( knob ); +private: + const float m_pitch; + const sample_rate_t m_sampleRate; + const int m_bufferLength; + std::array m_strings{}; + std::array m_exists{}; +}; - knob = new FloatModel( 0.0f, 0.0f, 0.75f, 0.01f, this, - tr( "String %1 fuzziness" ).arg( harm+1 ) ); - m_randomKnobs.append( knob ); - - knob = new FloatModel( 1, 1, 16, 1, this, - tr( "String %1 length" ).arg( harm+1 ) ); - m_lengthKnobs.append( knob ); - - led = new BoolModel( false, this, - tr( "Impulse %1" ).arg( harm+1 ) ); - m_impulses.append( led ); - - led = new BoolModel( harm==0, this, - tr( "String %1" ).arg( harm+1 ) ); - m_powerButtons.append( led ); - - harmonic = new gui::NineButtonSelectorModel( 2, 0, 8, this ); - m_harmonics.append( harmonic ); - - graphTmp = new graphModel( -1.0, 1.0, __sampleLength, this ); - graphTmp->setWaveToSine(); - - m_graphs.append( graphTmp ); +Vibed::Vibed(InstrumentTrack* instrumentTrack) : + Instrument(instrumentTrack, &vibedstrings_plugin_descriptor) +{ + for (int harm = 0; harm < s_stringCount; ++harm) + { + m_volumeModels[harm] = std::make_unique( + DefaultVolume, MinVolume, MaxVolume, 1.0f, this, tr("String %1 volume").arg(harm + 1)); + m_stiffnessModels[harm] = std::make_unique( + 0.0f, 0.0f, 0.05f, 0.001f, this, tr("String %1 stiffness").arg(harm + 1)); + m_pickModels[harm] = std::make_unique( + 0.0f, 0.0f, 0.05f, 0.005f, this, tr("Pick %1 position").arg(harm + 1)); + m_pickupModels[harm] = std::make_unique( + 0.05f, 0.0f, 0.05f, 0.005f, this, tr("Pickup %1 position").arg( harm + 1)); + m_panModels[harm] = std::make_unique( + 0.0f, -1.0f, 1.0f, 0.01f, this, tr("String %1 panning").arg(harm + 1)); + m_detuneModels[harm] = std::make_unique( + 0.0f, -0.1f, 0.1f, 0.001f, this, tr("String %1 detune").arg(harm + 1)); + m_randomModels[harm] = std::make_unique( + 0.0f, 0.0f, 0.75f, 0.01f, this, tr("String %1 fuzziness").arg(harm + 1)); + m_lengthModels[harm] = std::make_unique( + 1, 1, 16, 1, this, tr("String %1 length").arg(harm + 1)); + m_impulseModels[harm] = std::make_unique(false, this, tr("Impulse %1").arg(harm + 1)); + m_powerModels[harm] = std::make_unique(harm == 0, this, tr("String %1").arg(harm + 1)); + m_harmonicModels[harm] = std::make_unique(2, 0, 8, this); + m_graphModels[harm] = std::make_unique(-1.0, 1.0, s_sampleLength, this); + m_graphModels[harm]->setWaveToSine(); } } - - - -void Vibed::saveSettings( QDomDocument & _doc, QDomElement & _this ) +void Vibed::saveSettings(QDomDocument& doc, QDomElement& elem) { - - QString name; - // Save plugin version - _this.setAttribute( "version", "0.1" ); - - for( int i = 0; i < 9; i++ ) + elem.setAttribute("version", "0.2"); + + for (int i = 0; i < s_stringCount; ++i) { - name = "active" + QString::number( i ); - _this.setAttribute( name, QString::number( - m_powerButtons[i]->value() ) ); + const auto is = QString::number(i); - if( m_powerButtons[i]->value() ) - { - name = "volume" + QString::number( i ); - m_volumeKnobs[i]->saveSettings( _doc, _this, name ); - - name = "stiffness" + QString::number( i ); - m_stiffnessKnobs[i]->saveSettings( _doc, _this, name ); + elem.setAttribute("active" + is, QString::number(m_powerModels[i]->value())); - name = "pick" + QString::number( i ); - m_pickKnobs[i]->saveSettings( _doc, _this, name ); + m_volumeModels[i]->saveSettings(doc, elem, "volume" + is); + m_stiffnessModels[i]->saveSettings(doc, elem, "stiffness" + is); + m_pickModels[i]->saveSettings(doc, elem, "pick" + is); + m_pickupModels[i]->saveSettings(doc, elem, "pickup" + is); + m_harmonicModels[i]->saveSettings(doc, elem, "octave" + is); + m_lengthModels[i]->saveSettings(doc, elem, "length" + is); + m_panModels[i]->saveSettings(doc, elem, "pan" + is); + m_detuneModels[i]->saveSettings(doc, elem, "detune" + is); + m_randomModels[i]->saveSettings(doc, elem, "slap" + is); + m_impulseModels[i]->saveSettings(doc, elem, "impulse" + is); - name = "pickup" + QString::number( i ); - m_pickupKnobs[i]->saveSettings( _doc, _this, name ); + QString sampleString; + base64::encode((const char*)m_graphModels[i]->samples(), s_sampleLength * sizeof(float), sampleString); - name = "octave" + QString::number( i ); - m_harmonics[i]->saveSettings( _doc, _this, name ); - - name = "length" + QString::number( i ); - m_lengthKnobs[i]->saveSettings( _doc, _this, name ); - - name = "pan" + QString::number( i ); - m_panKnobs[i]->saveSettings( _doc, _this, name ); - - name = "detune" + QString::number( i ); - m_detuneKnobs[i]->saveSettings( _doc, _this, name ); - - name = "slap" + QString::number( i ); - m_randomKnobs[i]->saveSettings( _doc, _this, name ); - - name = "impulse" + QString::number( i ); - m_impulses[i]->saveSettings( _doc, _this, name ); - - QString sampleString; - base64::encode( - (const char *)m_graphs[i]->samples(), - __sampleLength * sizeof(float), - sampleString ); - name = "graph" + QString::number( i ); - _this.setAttribute( name, sampleString ); - } + elem.setAttribute("graph" + is, sampleString); } - } - - -void Vibed::loadSettings( const QDomElement & _this ) +void Vibed::loadSettings(const QDomElement& elem) { - - QString name; - - for( int i = 0; i < 9; i++ ) + // Load plugin version + bool newVersion = false; + if (elem.hasAttribute("version")) { - name = "active" + QString::number( i ); - m_powerButtons[i]->setValue( _this.attribute( name ).toInt() ); - - if( m_powerButtons[i]->value() && - _this.hasAttribute( "volume" + QString::number( i ) ) ) + newVersion = elem.attribute("version").toFloat() >= 0.2f; + } + + for (int i = 0; i < s_stringCount; ++i) + { + const auto is = QString::number(i); + + m_powerModels[i]->setValue(elem.attribute("active" + is).toInt()); + + // Version 0.2 saves/loads all instrument data unconditionally + const bool hasVolumeAttr = elem.hasAttribute("volume" + is) || !elem.firstChildElement("volume" + is).isNull(); + if (newVersion || (m_powerModels[i]->value() && hasVolumeAttr)) { - name = "volume" + QString::number( i ); - m_volumeKnobs[i]->loadSettings( _this, name ); - - name = "stiffness" + QString::number( i ); - m_stiffnessKnobs[i]->loadSettings( _this, name ); - - name = "pick" + QString::number( i ); - m_pickKnobs[i]->loadSettings( _this, name ); - - name = "pickup" + QString::number( i ); - m_pickupKnobs[i]->loadSettings( _this, name ); - - name = "octave" + QString::number( i ); - m_harmonics[i]->loadSettings( _this, name ); - - name = "length" + QString::number( i ); - m_lengthKnobs[i]->loadSettings( _this, name ); - - name = "pan" + QString::number( i ); - m_panKnobs[i]->loadSettings( _this, name ); - - name = "detune" + QString::number( i ); - m_detuneKnobs[i]->loadSettings( _this, name ); - - name = "slap" + QString::number( i ); - m_randomKnobs[i]->loadSettings( _this, name ); - - name = "impulse" + QString::number( i ); - m_impulses[i]->loadSettings( _this, name ); + m_volumeModels[i]->loadSettings(elem, "volume" + is); + m_stiffnessModels[i]->loadSettings(elem, "stiffness" + is); + m_pickModels[i]->loadSettings(elem, "pick" + is); + m_pickupModels[i]->loadSettings(elem, "pickup" + is); + m_harmonicModels[i]->loadSettings(elem, "octave" + is); + m_lengthModels[i]->loadSettings(elem, "length" + is); + m_panModels[i]->loadSettings(elem, "pan" + is); + m_detuneModels[i]->loadSettings(elem, "detune" + is); + m_randomModels[i]->loadSettings(elem, "slap" + is); + m_impulseModels[i]->loadSettings(elem, "impulse" + is); int size = 0; - float * shp = 0; - base64::decode( _this.attribute( "graph" + - QString::number( i ) ), - &shp, - &size ); - // TODO: check whether size == 128 * sizeof( float ), - // otherwise me might and up in a segfault - m_graphs[i]->setSamples( shp ); - delete[] shp; - + float* shp = nullptr; + base64::decode(elem.attribute("graph" + is), &shp, &size); - // TODO: do one of the following to avoid - // "uninitialized" wave-shape-buttongroup - // - activate random-wave-shape-button here - // - make wave-shape-buttons simple toggle-buttons - // instead of checkable buttons - // - save and restore selected wave-shape-button + assert(size == 128 * sizeof(float)); + m_graphModels[i]->setSamples(shp); + delete[] shp; } } - -// update(); } - - - QString Vibed::nodeName() const { - return( vibedstrings_plugin_descriptor.name ); + return vibedstrings_plugin_descriptor.name; } - - - -void Vibed::playNote( NotePlayHandle * _n, sampleFrame * _working_buffer ) +void Vibed::playNote(NotePlayHandle* n, sampleFrame* workingBuffer) { - if ( _n->totalFramesPlayed() == 0 || _n->m_pluginData == nullptr ) + if (!n->m_pluginData) { - _n->m_pluginData = new StringContainer( _n->frequency(), - Engine::audioEngine()->processingSampleRate(), - __sampleLength ); - - for( int i = 0; i < 9; ++i ) + const auto newContainer = new StringContainer{n->frequency(), + Engine::audioEngine()->processingSampleRate(), s_sampleLength}; + + n->m_pluginData = newContainer; + + for (int i = 0; i < s_stringCount; ++i) { - if( m_powerButtons[i]->value() ) + if (m_powerModels[i]->value()) { - static_cast( - _n->m_pluginData )->addString( - m_harmonics[i]->value(), - m_pickKnobs[i]->value(), - m_pickupKnobs[i]->value(), - m_graphs[i]->samples(), - m_randomKnobs[i]->value(), - m_stiffnessKnobs[i]->value(), - m_detuneKnobs[i]->value(), - static_cast( - m_lengthKnobs[i]->value() ), - m_impulses[i]->value(), - i ); + newContainer->addString( + m_harmonicModels[i]->value(), + m_pickModels[i]->value(), + m_pickupModels[i]->value(), + m_graphModels[i]->samples(), + m_randomModels[i]->value(), + m_stiffnessModels[i]->value(), + m_detuneModels[i]->value(), + static_cast(m_lengthModels[i]->value()), + m_impulseModels[i]->value(), + i); } } } - const fpp_t frames = _n->framesLeftForCurrentPeriod(); - const f_cnt_t offset = _n->noteOffset(); - auto ps = static_cast(_n->m_pluginData); + const fpp_t frames = n->framesLeftForCurrentPeriod(); + const f_cnt_t offset = n->noteOffset(); + auto ps = static_cast(n->m_pluginData); - for( fpp_t i = offset; i < frames + offset; ++i ) + for (fpp_t i = offset; i < frames + offset; ++i) { - _working_buffer[i][0] = 0.0f; - _working_buffer[i][1] = 0.0f; - int s = 0; - for( int string = 0; string < 9; ++string ) + workingBuffer[i][0] = 0.0f; + workingBuffer[i][1] = 0.0f; + for (int str = 0; str < s_stringCount; ++str) { - if( ps->exists( string ) ) + if (ps->exists(str)) { // pan: 0 -> left, 1 -> right - const float pan = ( m_panKnobs[string]->value() + 1 ) / 2.0f; - const sample_t sample = ps->getStringSample( s ) * m_volumeKnobs[string]->value() / 100.0f; - _working_buffer[i][0] += ( 1.0f - pan ) * sample; - _working_buffer[i][1] += pan * sample; - s++; + const float pan = (m_panModels[str]->value() + 1) / 2.0f; + const sample_t sample = ps->getStringSample(str) * m_volumeModels[str]->value() / 100.0f; + workingBuffer[i][0] += (1.0f - pan) * sample; + workingBuffer[i][1] += pan * sample; } } } - - instrumentTrack()->processAudioBuffer( _working_buffer, frames + offset, _n ); } - - - -void Vibed::deleteNotePluginData( NotePlayHandle * _n ) +void Vibed::deleteNotePluginData(NotePlayHandle* n) { - delete static_cast( _n->m_pluginData ); + delete static_cast(n->m_pluginData); } - - - -gui::PluginView * Vibed::instantiateView( QWidget * _parent ) +gui::PluginView* Vibed::instantiateView(QWidget* parent) { - return( new gui::VibedView( this, _parent ) ); + return new gui::VibedView(this, parent); } - namespace gui { -VibedView::VibedView( Instrument * _instrument, - QWidget * _parent ) : - InstrumentViewFixedSize( _instrument, _parent ) +VibedView::VibedView(Instrument* instrument, QWidget* parent) : + InstrumentViewFixedSize(instrument, parent), + m_volumeKnob(KnobType::Bright26, this), + m_stiffnessKnob(KnobType::Bright26, this), + m_pickKnob(KnobType::Bright26, this), + m_pickupKnob(KnobType::Bright26, this), + m_panKnob(KnobType::Bright26, this), + m_detuneKnob(KnobType::Bright26, this), + m_randomKnob(KnobType::Bright26, this), + m_lengthKnob(KnobType::Bright26, this), + m_graph(this), + m_impulse("", this), + m_power("", this, tr("Enable waveform")), + m_smoothBtn(this, tr("Smooth waveform")), + m_normalizeBtn(this, tr("Normalize waveform")), + m_sinWaveBtn(this, tr("Sine wave")), + m_triangleWaveBtn(this, tr("Triangle wave")), + m_sawWaveBtn(this, tr("Saw wave")), + m_sqrWaveBtn(this, tr("Square wave")), + m_whiteNoiseWaveBtn(this, tr("White noise")), + m_usrWaveBtn(this, tr("User-defined wave")) { - setAutoFillBackground( true ); + setAutoFillBackground(true); QPalette pal; - pal.setBrush( backgroundRole(), PLUGIN_NAME::getIconPixmap( - "artwork" ) ); - setPalette( pal ); - - m_volumeKnob = new Knob( knobBright_26, this ); - m_volumeKnob->setVolumeKnob( true ); - m_volumeKnob->move( 103, 142 ); - m_volumeKnob->setHintText( tr( "String volume:" ), "" ); + pal.setBrush(backgroundRole(), PLUGIN_NAME::getIconPixmap("artwork")); + setPalette(pal); - m_stiffnessKnob = new Knob( knobBright_26, this ); - m_stiffnessKnob->move( 129, 142 ); - m_stiffnessKnob->setHintText( tr( "String stiffness:" ) - , "" ); - - - m_pickKnob = new Knob( knobBright_26, this ); - m_pickKnob->move( 153, 142 ); - m_pickKnob->setHintText( tr( "Pick position:" ), "" ); + m_volumeKnob.setVolumeKnob(true); + m_volumeKnob.move(103, 142); + m_volumeKnob.setHintText(tr("String volume:"), ""); - m_pickupKnob = new Knob( knobBright_26, this ); - m_pickupKnob->move( 177, 142 ); - m_pickupKnob->setHintText( tr( "Pickup position:" ) - , "" ); + m_stiffnessKnob.move(129, 142); + m_stiffnessKnob.setHintText(tr("String stiffness:"), ""); - m_panKnob = new Knob( knobBright_26, this ); - m_panKnob->move( 105, 187 ); - m_panKnob->setHintText( tr( "String panning:" ), "" ); - - m_detuneKnob = new Knob( knobBright_26, this ); - m_detuneKnob->move( 150, 187 ); - m_detuneKnob->setHintText( tr( "String detune:" ), "" ); + m_pickKnob.move(153, 142); + m_pickKnob.setHintText(tr("Pick position:"), ""); - m_randomKnob = new Knob( knobBright_26, this ); - m_randomKnob->move( 194, 187 ); - m_randomKnob->setHintText( tr( "String fuzziness:" ) - , "" ); + m_pickupKnob.move(177, 142); + m_pickupKnob.setHintText(tr("Pickup position:"), ""); - m_lengthKnob = new Knob( knobBright_26, this ); - m_lengthKnob->move( 23, 193 ); - m_lengthKnob->setHintText( tr( "String length:" ) - , "" ); + m_panKnob.move(105, 187); + m_panKnob.setHintText(tr("String panning:"), ""); - m_impulse = new LedCheckBox( "", this ); - m_impulse->move( 23, 94 ); - m_impulse->setToolTip( - tr( "Impulse" ) ); + m_detuneKnob.move(150, 187); + m_detuneKnob.setHintText(tr("String detune:"), ""); - m_harmonic = new NineButtonSelector( - PLUGIN_NAME::getIconPixmap( "button_-2_on" ), - PLUGIN_NAME::getIconPixmap( "button_-2_off" ), - PLUGIN_NAME::getIconPixmap( "button_-1_on" ), - PLUGIN_NAME::getIconPixmap( "button_-1_off" ), - PLUGIN_NAME::getIconPixmap( "button_f_on" ), - PLUGIN_NAME::getIconPixmap( "button_f_off" ), - PLUGIN_NAME::getIconPixmap( "button_2_on" ), - PLUGIN_NAME::getIconPixmap( "button_2_off" ), - PLUGIN_NAME::getIconPixmap( "button_3_on" ), - PLUGIN_NAME::getIconPixmap( "button_3_off" ), - PLUGIN_NAME::getIconPixmap( "button_4_on" ), - PLUGIN_NAME::getIconPixmap( "button_4_off" ), - PLUGIN_NAME::getIconPixmap( "button_5_on" ), - PLUGIN_NAME::getIconPixmap( "button_5_off" ), - PLUGIN_NAME::getIconPixmap( "button_6_on" ), - PLUGIN_NAME::getIconPixmap( "button_6_off" ), - PLUGIN_NAME::getIconPixmap( "button_7_on" ), - PLUGIN_NAME::getIconPixmap( "button_7_off" ), + m_randomKnob.move(194, 187); + m_randomKnob.setHintText(tr("String fuzziness:"), ""); + + m_lengthKnob.move(23, 193); + m_lengthKnob.setHintText(tr("String length:"), ""); + + m_graph.setWindowTitle(tr("Impulse Editor")); + m_graph.setForeground(PLUGIN_NAME::getIconPixmap("wavegraph4")); + m_graph.move(76, 21); + m_graph.resize(132, 104); + + m_impulse.move(23, 94); + m_impulse.setToolTip(tr("Impulse")); + + m_power.move(212, 130); + m_power.setToolTip(tr("Enable/disable string")); + + m_harmonic = std::make_unique( + std::array{ + PLUGIN_NAME::getIconPixmap("button_-2_on"), + PLUGIN_NAME::getIconPixmap("button_-2_off"), + PLUGIN_NAME::getIconPixmap("button_-1_on"), + PLUGIN_NAME::getIconPixmap("button_-1_off"), + PLUGIN_NAME::getIconPixmap("button_f_on"), + PLUGIN_NAME::getIconPixmap("button_f_off"), + PLUGIN_NAME::getIconPixmap("button_2_on"), + PLUGIN_NAME::getIconPixmap("button_2_off"), + PLUGIN_NAME::getIconPixmap("button_3_on"), + PLUGIN_NAME::getIconPixmap("button_3_off"), + PLUGIN_NAME::getIconPixmap("button_4_on"), + PLUGIN_NAME::getIconPixmap("button_4_off"), + PLUGIN_NAME::getIconPixmap("button_5_on"), + PLUGIN_NAME::getIconPixmap("button_5_off"), + PLUGIN_NAME::getIconPixmap("button_6_on"), + PLUGIN_NAME::getIconPixmap("button_6_off"), + PLUGIN_NAME::getIconPixmap("button_7_on"), + PLUGIN_NAME::getIconPixmap("button_7_off")}, 2, 21, 127, - this ); + this); - m_harmonic->setWindowTitle( tr( "Octave" ) ); - + m_harmonic->setWindowTitle(tr("Octave")); - m_stringSelector = new NineButtonSelector( - PLUGIN_NAME::getIconPixmap( "button_1_on" ), - PLUGIN_NAME::getIconPixmap( "button_1_off" ), - PLUGIN_NAME::getIconPixmap( "button_2_on" ), - PLUGIN_NAME::getIconPixmap( "button_2_off" ), - PLUGIN_NAME::getIconPixmap( "button_3_on" ), - PLUGIN_NAME::getIconPixmap( "button_3_off" ), - PLUGIN_NAME::getIconPixmap( "button_4_on" ), - PLUGIN_NAME::getIconPixmap( "button_4_off" ), - PLUGIN_NAME::getIconPixmap( "button_5_on" ), - PLUGIN_NAME::getIconPixmap( "button_5_off" ), - PLUGIN_NAME::getIconPixmap( "button_6_on" ), - PLUGIN_NAME::getIconPixmap( "button_6_off" ), - PLUGIN_NAME::getIconPixmap( "button_7_on" ), - PLUGIN_NAME::getIconPixmap( "button_7_off" ), - PLUGIN_NAME::getIconPixmap( "button_8_on" ), - PLUGIN_NAME::getIconPixmap( "button_8_off" ), - PLUGIN_NAME::getIconPixmap( "button_9_on" ), - PLUGIN_NAME::getIconPixmap( "button_9_off" ), - 0, - 21, 39, - this); + m_stringSelector = std::make_unique( + std::array{ + PLUGIN_NAME::getIconPixmap("button_1_on"), + PLUGIN_NAME::getIconPixmap("button_1_off"), + PLUGIN_NAME::getIconPixmap("button_2_on"), + PLUGIN_NAME::getIconPixmap("button_2_off"), + PLUGIN_NAME::getIconPixmap("button_3_on"), + PLUGIN_NAME::getIconPixmap("button_3_off"), + PLUGIN_NAME::getIconPixmap("button_4_on"), + PLUGIN_NAME::getIconPixmap("button_4_off"), + PLUGIN_NAME::getIconPixmap("button_5_on"), + PLUGIN_NAME::getIconPixmap("button_5_off"), + PLUGIN_NAME::getIconPixmap("button_6_on"), + PLUGIN_NAME::getIconPixmap("button_6_off"), + PLUGIN_NAME::getIconPixmap("button_7_on"), + PLUGIN_NAME::getIconPixmap("button_7_off"), + PLUGIN_NAME::getIconPixmap("button_8_on"), + PLUGIN_NAME::getIconPixmap("button_8_off"), + PLUGIN_NAME::getIconPixmap("button_9_on"), + PLUGIN_NAME::getIconPixmap("button_9_off")}, + 0, + 21, 39, + this); - m_graph = new Graph( this ); - m_graph->setWindowTitle( tr( "Impulse Editor" ) ); - m_graph->setForeground( PLUGIN_NAME::getIconPixmap( "wavegraph4" ) ); - m_graph->move( 76, 21 ); - m_graph->resize(132, 104); - - - m_power = new LedCheckBox( "", this, tr( "Enable waveform" ) ); - m_power->move( 212, 130 ); - m_power->setToolTip( - tr( "Enable/disable string" ) ); - - // String selector is not a part of the model - m_stringSelector->setWindowTitle( tr( "String" ) ); + m_stringSelector->setWindowTitle(tr("String")); - connect( m_stringSelector, SIGNAL( NineButtonSelection( int ) ), - this, SLOT( showString( int ) ) ); + connect(m_stringSelector.get(), SIGNAL(NineButtonSelection(int)), this, SLOT(showString(int))); - showString( 0 ); + showString(0); - m_sinWaveBtn = new PixmapButton( this, tr( "Sine wave" ) ); - m_sinWaveBtn->move( 212, 24 ); - m_sinWaveBtn->setActiveGraphic( embed::getIconPixmap( - "sin_wave_active" ) ); - m_sinWaveBtn->setInactiveGraphic( embed::getIconPixmap( - "sin_wave_inactive" ) ); - m_sinWaveBtn->setToolTip( - tr( "Sine wave" ) ); - connect( m_sinWaveBtn, SIGNAL (clicked () ), - this, SLOT ( sinWaveClicked() ) ); + m_smoothBtn.move(79, 129); + m_smoothBtn.setActiveGraphic(PLUGIN_NAME::getIconPixmap("smooth_active")); + m_smoothBtn.setInactiveGraphic(PLUGIN_NAME::getIconPixmap("smooth_inactive")); + m_smoothBtn.setChecked(false); + m_smoothBtn.setToolTip(tr("Smooth waveform")); + connect(&m_smoothBtn, SIGNAL(clicked()), this, SLOT(smoothClicked())); - - m_triangleWaveBtn = new PixmapButton( this, tr( "Triangle wave" ) ); - m_triangleWaveBtn->move( 212, 41 ); - m_triangleWaveBtn->setActiveGraphic( - embed::getIconPixmap( "triangle_wave_active" ) ); - m_triangleWaveBtn->setInactiveGraphic( - embed::getIconPixmap( "triangle_wave_inactive" ) ); - m_triangleWaveBtn->setToolTip( - tr( "Triangle wave" ) ); - connect( m_triangleWaveBtn, SIGNAL ( clicked () ), - this, SLOT ( triangleWaveClicked() ) ); + m_normalizeBtn.move(96, 129); + m_normalizeBtn.setActiveGraphic(PLUGIN_NAME::getIconPixmap("normalize_active")); + m_normalizeBtn.setInactiveGraphic(PLUGIN_NAME::getIconPixmap("normalize_inactive")); + m_normalizeBtn.setChecked(false); + m_normalizeBtn.setToolTip(tr("Normalize waveform")); + connect(&m_normalizeBtn, SIGNAL(clicked()), this, SLOT(normalizeClicked())); - - m_sawWaveBtn = new PixmapButton( this, tr( "Saw wave" ) ); - m_sawWaveBtn->move( 212, 58 ); - m_sawWaveBtn->setActiveGraphic( embed::getIconPixmap( - "saw_wave_active" ) ); - m_sawWaveBtn->setInactiveGraphic( embed::getIconPixmap( - "saw_wave_inactive" ) ); - m_sawWaveBtn->setToolTip( - tr( "Saw wave" ) ); - connect( m_sawWaveBtn, SIGNAL (clicked () ), - this, SLOT ( sawWaveClicked() ) ); + m_sinWaveBtn.move(212, 24); + m_sinWaveBtn.setActiveGraphic(embed::getIconPixmap("sin_wave_active")); + m_sinWaveBtn.setInactiveGraphic(embed::getIconPixmap("sin_wave_inactive")); + m_sinWaveBtn.setToolTip(tr("Sine wave")); + connect(&m_sinWaveBtn, SIGNAL(clicked()), this, SLOT(sinWaveClicked())); - - m_sqrWaveBtn = new PixmapButton( this, tr( "Square wave" ) ); - m_sqrWaveBtn->move( 212, 75 ); - m_sqrWaveBtn->setActiveGraphic( embed::getIconPixmap( - "square_wave_active" ) ); - m_sqrWaveBtn->setInactiveGraphic( embed::getIconPixmap( - "square_wave_inactive" ) ); - m_sqrWaveBtn->setToolTip( - tr( "Square wave" ) ); - connect( m_sqrWaveBtn, SIGNAL ( clicked () ), - this, SLOT ( sqrWaveClicked() ) ); + m_triangleWaveBtn.move(212, 41); + m_triangleWaveBtn.setActiveGraphic(embed::getIconPixmap("triangle_wave_active")); + m_triangleWaveBtn.setInactiveGraphic(embed::getIconPixmap("triangle_wave_inactive")); + m_triangleWaveBtn.setToolTip(tr("Triangle wave")); + connect(&m_triangleWaveBtn, SIGNAL(clicked()), this, SLOT(triangleWaveClicked())); - - m_whiteNoiseWaveBtn = new PixmapButton( this, tr( "White noise" ) ); - m_whiteNoiseWaveBtn->move( 212, 92 ); - m_whiteNoiseWaveBtn->setActiveGraphic( - embed::getIconPixmap( "white_noise_wave_active" ) ); - m_whiteNoiseWaveBtn->setInactiveGraphic( - embed::getIconPixmap( "white_noise_wave_inactive" ) ); - m_whiteNoiseWaveBtn->setToolTip( - tr( "White noise" ) ); - connect( m_whiteNoiseWaveBtn, SIGNAL ( clicked () ), - this, SLOT ( noiseWaveClicked() ) ); + m_sawWaveBtn.move(212, 58); + m_sawWaveBtn.setActiveGraphic(embed::getIconPixmap("saw_wave_active")); + m_sawWaveBtn.setInactiveGraphic(embed::getIconPixmap("saw_wave_inactive")); + m_sawWaveBtn.setToolTip(tr("Saw wave")); + connect(&m_sawWaveBtn, SIGNAL(clicked()), this, SLOT(sawWaveClicked())); - - m_usrWaveBtn = new PixmapButton( this, tr( "User-defined wave" ) ); - m_usrWaveBtn->move( 212, 109 ); - m_usrWaveBtn->setActiveGraphic( embed::getIconPixmap( - "usr_wave_active" ) ); - m_usrWaveBtn->setInactiveGraphic( embed::getIconPixmap( - "usr_wave_inactive" ) ); - m_usrWaveBtn->setToolTip( - tr( "User-defined wave" ) ); - connect( m_usrWaveBtn, SIGNAL ( clicked () ), - this, SLOT ( usrWaveClicked() ) ); + m_sqrWaveBtn.move(212, 75); + m_sqrWaveBtn.setActiveGraphic(embed::getIconPixmap("square_wave_active")); + m_sqrWaveBtn.setInactiveGraphic(embed::getIconPixmap("square_wave_inactive")); + m_sqrWaveBtn.setToolTip(tr("Square wave")); + connect(&m_sqrWaveBtn, SIGNAL(clicked()), this, SLOT(sqrWaveClicked())); + m_whiteNoiseWaveBtn.move(212, 92); + m_whiteNoiseWaveBtn.setActiveGraphic(embed::getIconPixmap("white_noise_wave_active")); + m_whiteNoiseWaveBtn.setInactiveGraphic(embed::getIconPixmap("white_noise_wave_inactive")); + m_whiteNoiseWaveBtn.setToolTip(tr("White noise")); + connect(&m_whiteNoiseWaveBtn, SIGNAL(clicked()), this, SLOT(noiseWaveClicked())); - m_smoothBtn = new PixmapButton( this, tr( "Smooth waveform" ) ); - m_smoothBtn->move( 79, 129 ); - m_smoothBtn->setActiveGraphic( PLUGIN_NAME::getIconPixmap( - "smooth_active" ) ); - m_smoothBtn->setInactiveGraphic( PLUGIN_NAME::getIconPixmap( - "smooth_inactive" ) ); - m_smoothBtn->setChecked( false ); - m_smoothBtn->setToolTip( - tr( "Smooth waveform" ) ); - connect( m_smoothBtn, SIGNAL ( clicked () ), - this, SLOT ( smoothClicked() ) ); - - m_normalizeBtn = new PixmapButton( this, tr( "Normalize waveform" ) ); - m_normalizeBtn->move( 96, 129 ); - m_normalizeBtn->setActiveGraphic( PLUGIN_NAME::getIconPixmap( - "normalize_active" ) ); - m_normalizeBtn->setInactiveGraphic( PLUGIN_NAME::getIconPixmap( - "normalize_inactive" ) ); - m_normalizeBtn->setChecked( false ); - m_normalizeBtn->setToolTip( - tr( "Normalize waveform" ) ); - - connect( m_normalizeBtn, SIGNAL ( clicked () ), - this, SLOT ( normalizeClicked() ) ); - + m_usrWaveBtn.move(212, 109); + m_usrWaveBtn.setActiveGraphic(embed::getIconPixmap("usr_wave_active")); + m_usrWaveBtn.setInactiveGraphic(embed::getIconPixmap("usr_wave_inactive")); + m_usrWaveBtn.setToolTip(tr("User-defined wave")); + connect(&m_usrWaveBtn, SIGNAL(clicked()),this, SLOT(usrWaveClicked())); } - - - void VibedView::modelChanged() { - showString( 0 ); + showString(0); } - - - -void VibedView::showString( int _string ) +void VibedView::showString(int str) { auto v = castModel(); - m_pickKnob->setModel( v->m_pickKnobs[_string] ); - m_pickupKnob->setModel( v->m_pickupKnobs[_string] ); - m_stiffnessKnob->setModel( v->m_stiffnessKnobs[_string] ); - m_volumeKnob->setModel( v->m_volumeKnobs[_string] ); - m_panKnob->setModel( v->m_panKnobs[_string] ); - m_detuneKnob->setModel( v->m_detuneKnobs[_string] ); - m_randomKnob->setModel( v->m_randomKnobs[_string] ); - m_lengthKnob->setModel( v->m_lengthKnobs[_string] ); - m_graph->setModel( v->m_graphs[_string] ); - m_impulse->setModel( v->m_impulses[_string] ); - m_harmonic->setModel( v->m_harmonics[_string] ); - m_power->setModel( v->m_powerButtons[_string] ); - + m_pickKnob.setModel(v->m_pickModels[str].get()); + m_pickupKnob.setModel(v->m_pickupModels[str].get()); + m_stiffnessKnob.setModel(v->m_stiffnessModels[str].get()); + m_volumeKnob.setModel(v->m_volumeModels[str].get()); + m_panKnob.setModel(v->m_panModels[str].get()); + m_detuneKnob.setModel(v->m_detuneModels[str].get()); + m_randomKnob.setModel(v->m_randomModels[str].get()); + m_lengthKnob.setModel(v->m_lengthModels[str].get()); + m_graph.setModel(v->m_graphModels[str].get()); + m_impulse.setModel(v->m_impulseModels[str].get()); + m_harmonic->setModel(v->m_harmonicModels[str].get()); + m_power.setModel(v->m_powerModels[str].get()); } - - - void VibedView::sinWaveClicked() { - m_graph->model()->setWaveToSine(); + m_graph.model()->setWaveToSine(); Engine::getSong()->setModified(); } - - void VibedView::triangleWaveClicked() { - m_graph->model()->setWaveToTriangle(); + m_graph.model()->setWaveToTriangle(); Engine::getSong()->setModified(); } - - void VibedView::sawWaveClicked() { - m_graph->model()->setWaveToSaw(); + m_graph.model()->setWaveToSaw(); Engine::getSong()->setModified(); } - - void VibedView::sqrWaveClicked() { - m_graph->model()->setWaveToSquare(); + m_graph.model()->setWaveToSquare(); Engine::getSong()->setModified(); } - - void VibedView::noiseWaveClicked() { - m_graph->model()->setWaveToNoise(); + m_graph.model()->setWaveToNoise(); Engine::getSong()->setModified(); } - - void VibedView::usrWaveClicked() { - QString fileName = m_graph->model()->setWaveToUser(); - m_usrWaveBtn->setToolTip(fileName); + QString fileName = m_graph.model()->setWaveToUser(); + m_usrWaveBtn.setToolTip(fileName); Engine::getSong()->setModified(); } - - void VibedView::smoothClicked() { - m_graph->model()->smooth(); + m_graph.model()->smooth(); Engine::getSong()->setModified(); } - - void VibedView::normalizeClicked() { - m_graph->model()->normalize(); + m_graph.model()->normalize(); Engine::getSong()->setModified(); } - - - -void VibedView::contextMenuEvent( QContextMenuEvent * ) +void VibedView::contextMenuEvent(QContextMenuEvent*) { - - CaptionMenu contextMenu( model()->displayName(), this ); - contextMenu.exec( QCursor::pos() ); - + CaptionMenu contextMenu(model()->displayName(), this); + contextMenu.exec(QCursor::pos()); } @@ -683,12 +522,11 @@ extern "C" { // necessary for getting instance out of shared lib -PLUGIN_EXPORT Plugin * lmms_plugin_main( Model *m, void * ) +PLUGIN_EXPORT Plugin* lmms_plugin_main(Model* m, void*) { - return( new Vibed( static_cast( m ) ) ); + return new Vibed(static_cast(m)); } - } diff --git a/plugins/Vibed/Vibed.h b/plugins/Vibed/Vibed.h index 75f92157c..18d334c4d 100644 --- a/plugins/Vibed/Vibed.h +++ b/plugins/Vibed/Vibed.h @@ -2,7 +2,7 @@ * Vibed.h - combination of PluckedStringSynth and BitInvader * * Copyright (c) 2006-2008 Danny McRae - * + * * This file is part of LMMS - https://lmms.io * * This program is free software; you can redistribute it and/or @@ -21,12 +21,20 @@ * Boston, MA 02110-1301 USA. * */ -#ifndef _VIBED_H -#define _VIBED_H + +#ifndef LMMS_VIBED_H +#define LMMS_VIBED_H #include "Instrument.h" #include "InstrumentView.h" #include "NineButtonSelector.h" +#include "Knob.h" +#include "LedCheckBox.h" +#include "Graph.h" +#include "PixmapButton.h" + +#include +#include namespace lmms { @@ -46,45 +54,42 @@ class Vibed : public Instrument { Q_OBJECT public: - Vibed( InstrumentTrack * _instrument_track ); + Vibed(InstrumentTrack* instrumentTrack); + ~Vibed() override = default; - void playNote( NotePlayHandle * _n, - sampleFrame * _working_buffer ) override; - void deleteNotePluginData( NotePlayHandle * _n ) override; + void playNote(NotePlayHandle* n, sampleFrame* workingBuffer) override; + void deleteNotePluginData(NotePlayHandle* n) override; - - void saveSettings( QDomDocument & _doc, QDomElement & _parent ) override; - void loadSettings( const QDomElement & _this ) override; + void saveSettings(QDomDocument& doc, QDomElement& elem) override; + void loadSettings(const QDomElement& elem) override; QString nodeName() const override; - Flags flags() const override - { - return IsNotBendable; - } - - - gui::PluginView* instantiateView( QWidget * _parent ) override; + Flags flags() const override { return Flag::IsNotBendable; } + gui::PluginView* instantiateView(QWidget* parent) override; private: - QList m_pickKnobs; - QList m_pickupKnobs; - QList m_stiffnessKnobs; - QList m_volumeKnobs; - QList m_panKnobs; - QList m_detuneKnobs; - QList m_randomKnobs; - QList m_lengthKnobs; - QList m_powerButtons; - QList m_graphs; - QList m_impulses; - QList m_harmonics; + class StringContainer; - static const int __sampleLength = 128; + static constexpr int s_sampleLength = 128; + static constexpr int s_stringCount = 9; + + std::array, s_stringCount> m_pickModels; + std::array, s_stringCount> m_pickupModels; + std::array, s_stringCount> m_stiffnessModels; + std::array, s_stringCount> m_volumeModels; + std::array, s_stringCount> m_panModels; + std::array, s_stringCount> m_detuneModels; + std::array, s_stringCount> m_randomModels; + std::array, s_stringCount> m_lengthModels; + std::array, s_stringCount> m_powerModels; + std::array, s_stringCount> m_graphModels; + std::array, s_stringCount> m_impulseModels; + std::array, s_stringCount> m_harmonicModels; friend class gui::VibedView; -} ; +}; namespace gui @@ -95,13 +100,12 @@ class VibedView : public InstrumentViewFixedSize { Q_OBJECT public: - VibedView( Instrument * _instrument, - QWidget * _parent ); + VibedView(Instrument* instrument, QWidget* parent); ~VibedView() override = default; public slots: - void showString( int _string ); - void contextMenuEvent( QContextMenuEvent * ) override; + void showString(int str); + void contextMenuEvent(QContextMenuEvent*) override; protected slots: void sinWaveClicked(); @@ -116,35 +120,32 @@ protected slots: private: void modelChanged() override; - // String-related - Knob * m_pickKnob; - Knob * m_pickupKnob; - Knob * m_stiffnessKnob; - Knob * m_volumeKnob; - Knob * m_panKnob; - Knob * m_detuneKnob; - Knob * m_randomKnob; - Knob * m_lengthKnob; - Graph * m_graph; - NineButtonSelector * m_harmonic; - LedCheckBox * m_impulse; - LedCheckBox * m_power; + Knob m_volumeKnob; + Knob m_stiffnessKnob; + Knob m_pickKnob; + Knob m_pickupKnob; + Knob m_panKnob; + Knob m_detuneKnob; + Knob m_randomKnob; + Knob m_lengthKnob; + Graph m_graph; + LedCheckBox m_impulse; + LedCheckBox m_power; + std::unique_ptr m_harmonic; // Not in model - NineButtonSelector * m_stringSelector; - PixmapButton * m_smoothBtn; - PixmapButton * m_normalizeBtn; + std::unique_ptr m_stringSelector; + PixmapButton m_smoothBtn; + PixmapButton m_normalizeBtn; // From impulse editor - PixmapButton * m_sinWaveBtn; - PixmapButton * m_triangleWaveBtn; - PixmapButton * m_sqrWaveBtn; - PixmapButton * m_sawWaveBtn; - PixmapButton * m_whiteNoiseWaveBtn; - PixmapButton * m_usrWaveBtn; - - + PixmapButton m_sinWaveBtn; + PixmapButton m_triangleWaveBtn; + PixmapButton m_sawWaveBtn; + PixmapButton m_sqrWaveBtn; + PixmapButton m_whiteNoiseWaveBtn; + PixmapButton m_usrWaveBtn; }; @@ -152,4 +153,4 @@ private: } // namespace lmms -#endif +#endif // LMMS_VIBED_H diff --git a/plugins/Vibed/VibratingString.cpp b/plugins/Vibed/VibratingString.cpp index e33ef0582..216a5fbf3 100644 --- a/plugins/Vibed/VibratingString.cpp +++ b/plugins/Vibed/VibratingString.cpp @@ -1,8 +1,8 @@ /* - * vibrating_sring.h - model of a vibrating string lifted from pluckedSynth + * VibratingString.cpp - model of a vibrating string lifted from pluckedSynth * * Copyright (c) 2006-2008 Danny McRae - * + * * This file is part of LMMS - https://lmms.io * * This program is free software; you can redistribute it and/or @@ -21,93 +21,64 @@ * Boston, MA 02110-1301 USA. * */ -#include #include "VibratingString.h" #include "interpolation.h" #include "AudioEngine.h" #include "Engine.h" +#include +#include + namespace lmms { -VibratingString::VibratingString( float _pitch, - float _pick, - float _pickup, - float * _impulse, - int _len, - sample_rate_t _sample_rate, - int _oversample, - float _randomize, - float _string_loss, - float _detune, - bool _state ) : - m_oversample( 2 * _oversample / (int)( _sample_rate / - Engine::audioEngine()->baseSampleRate() ) ), - m_randomize( _randomize ), - m_stringLoss( 1.0f - _string_loss ), - m_state( 0.1f ) +VibratingString::VibratingString(float pitch, float pick, float pickup, const float* impulse, int len, + sample_rate_t sampleRate, int oversample, float randomize, float stringLoss, float detune, bool state) : + m_oversample{2 * oversample / static_cast(sampleRate / Engine::audioEngine()->baseSampleRate())}, + m_randomize{randomize}, + m_stringLoss{1.0f - stringLoss}, + m_choice{static_cast(m_oversample * static_cast(std::rand()) / RAND_MAX)}, + m_state{0.1f}, + m_outsamp{std::make_unique(m_oversample)} { - m_outsamp = new sample_t[m_oversample]; - int string_length; - - string_length = static_cast( m_oversample * _sample_rate / - _pitch ) + 1; - string_length += static_cast( string_length * -_detune ); + int stringLength = static_cast(m_oversample * sampleRate / pitch) + 1; + stringLength += static_cast(stringLength * -detune); - int pick = static_cast( ceil( string_length * _pick ) ); - - if( ! _state ) + const int pickInt = static_cast(std::ceil(stringLength * pick)); + + if (!state) { - m_impulse = new float[string_length]; - resample( _impulse, _len, string_length ); + m_impulse = std::make_unique(stringLength); + resample(impulse, len, stringLength); } else - { - m_impulse = new float[_len]; - for( int i = 0; i < _len; i++ ) - { - m_impulse[i] = _impulse[i]; - } + { + m_impulse = std::make_unique(len); + std::copy_n(impulse, len, m_impulse.get()); } - - m_toBridge = VibratingString::initDelayLine( string_length, pick ); - m_fromBridge = VibratingString::initDelayLine( string_length, pick ); - - VibratingString::setDelayLine( m_toBridge, pick, - m_impulse, _len, 0.5f, - _state ); - VibratingString::setDelayLine( m_fromBridge, pick, - m_impulse, _len, 0.5f, - _state); - - m_choice = static_cast( m_oversample * - static_cast( rand() ) / RAND_MAX ); - - m_pickupLoc = static_cast( _pickup * string_length ); + m_toBridge = VibratingString::initDelayLine(stringLength); + m_fromBridge = VibratingString::initDelayLine(stringLength); + + VibratingString::setDelayLine(m_toBridge.get(), pickInt, m_impulse.get(), len, 0.5f, state); + VibratingString::setDelayLine(m_fromBridge.get(), pickInt, m_impulse.get(), len, 0.5f, state); + + m_pickupLoc = static_cast(pickup * stringLength); } - - - -VibratingString::delayLine * VibratingString::initDelayLine( int _len, - int _pick ) +std::unique_ptr VibratingString::initDelayLine(int len) { - auto dl = new VibratingString::delayLine[_len]; - dl->length = _len; - if( _len > 0 ) + auto dl = std::make_unique(); + dl->length = len; + if (len > 0) { - dl->data = new sample_t[_len]; - float r; - float offset = 0.0f; - for( int i = 0; i < dl->length; i++ ) + dl->data = std::make_unique(len); + for (int i = 0; i < dl->length; ++i) { - r = static_cast( 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; } } @@ -116,46 +87,25 @@ VibratingString::delayLine * VibratingString::initDelayLine( int _len, dl->data = nullptr; } - dl->pointer = dl->data; - dl->end = dl->data + _len - 1; + dl->pointer = dl->data.get(); + dl->end = dl->data.get() + len - 1; - return( dl ); + return dl; } - - - -void VibratingString::freeDelayLine( delayLine * _dl ) +void VibratingString::resample(const float* src, f_cnt_t srcFrames, f_cnt_t dstFrames) { - if( _dl ) + for (f_cnt_t frame = 0; frame < dstFrames; ++frame) { - delete[] _dl->data; - delete[] _dl; - } -} - - - - -void VibratingString::resample( float *_src, f_cnt_t _src_frames, - f_cnt_t _dst_frames ) -{ - for( f_cnt_t frame = 0; frame < _dst_frames; ++frame ) - { - const float src_frame_float = frame * - (float) _src_frames / - _dst_frames; - const float frac_pos = src_frame_float - - static_cast( src_frame_float ); - const f_cnt_t src_frame = qBound( - 1, static_cast( src_frame_float ), - _src_frames - 3 ); + const float srcFrameFloat = frame * static_cast(srcFrames) / dstFrames; + const float fracPos = srcFrameFloat - static_cast(srcFrameFloat); + const f_cnt_t srcFrame = std::clamp(static_cast(srcFrameFloat), 1, srcFrames - 3); m_impulse[frame] = cubicInterpolate( - _src[src_frame - 1], - _src[src_frame + 0], - _src[src_frame + 1], - _src[src_frame + 2], - frac_pos ); + src[srcFrame - 1], + src[srcFrame + 0], + src[srcFrame + 1], + src[srcFrame + 2], + fracPos); } } diff --git a/plugins/Vibed/VibratingString.h b/plugins/Vibed/VibratingString.h index ed7cbe38c..0ad5a6e6e 100644 --- a/plugins/Vibed/VibratingString.h +++ b/plugins/Vibed/VibratingString.h @@ -2,7 +2,7 @@ * VibratingString.h - model of a vibrating string lifted from pluckedSynth * * Copyright (c) 2006-2007 Danny McRae - * + * * This file is part of LMMS - https://lmms.io * * This program is free software; you can redistribute it and/or @@ -21,10 +21,12 @@ * Boston, MA 02110-1301 USA. * */ -#ifndef _VIBRATING_STRING_H -#define _VIBRATING_STRING_H -#include +#ifndef LMMS_VIBRATING_STRING_H +#define LMMS_VIBRATING_STRING_H + +#include +#include #include "lmms_basics.h" @@ -34,244 +36,214 @@ namespace lmms class VibratingString { - public: - VibratingString( float _pitch, - float _pick, - float _pickup, - float * impluse, - int _len, - sample_rate_t _sample_rate, - int _oversample, - float _randomize, - float _string_loss, - float _detune, - bool _state ); - - inline ~VibratingString() - { - delete[] m_outsamp; - delete[] m_impulse; - VibratingString::freeDelayLine( m_fromBridge ); - VibratingString::freeDelayLine( m_toBridge ); - } + VibratingString() = default; + VibratingString(float pitch, float pick, float pickup, const float* impulse, int len, + sample_rate_t sampleRate, int oversample, float randomize, float stringLoss, float detune, bool state); + ~VibratingString() = default; - inline sample_t nextSample() - { - sample_t ym0; - sample_t ypM; - for( int i = 0; i < m_oversample; i++) + VibratingString(const VibratingString&) = delete; + VibratingString& operator=(const VibratingString&) = delete; + VibratingString(VibratingString&&) noexcept = delete; + VibratingString& operator=(VibratingString&&) noexcept = default; + + sample_t nextSample() + { + for (int i = 0; i < m_oversample; ++i) { // Output at pickup position - m_outsamp[i] = fromBridgeAccess( m_fromBridge, - m_pickupLoc ); - m_outsamp[i] += toBridgeAccess( m_toBridge, - m_pickupLoc ); - + m_outsamp[i] = fromBridgeAccess(m_fromBridge.get(), m_pickupLoc); + m_outsamp[i] += toBridgeAccess(m_toBridge.get(), m_pickupLoc); + // Sample traveling into "bridge" - ym0 = toBridgeAccess( m_toBridge, 1 ); + sample_t ym0 = toBridgeAccess(m_toBridge.get(), 1); // Sample to "nut" - ypM = fromBridgeAccess( m_fromBridge, - m_fromBridge->length - 2 ); + sample_t ypM = fromBridgeAccess(m_fromBridge.get(), m_fromBridge->length - 2); // String state update // Decrement pointer and then update - fromBridgeUpdate( m_fromBridge, - -bridgeReflection( ym0 ) ); + fromBridgeUpdate(m_fromBridge.get(), -bridgeReflection(ym0)); // Update and then increment pointer - toBridgeUpdate( m_toBridge, -ypM ); + toBridgeUpdate(m_toBridge.get(), -ypM); } - return( m_outsamp[m_choice] ); + + return m_outsamp[m_choice]; } private: - struct delayLine + struct DelayLine { - sample_t * data; + std::unique_ptr data; int length; - sample_t * pointer; - sample_t * end; - } ; + sample_t* pointer; + sample_t* end; + }; - delayLine * m_fromBridge; - delayLine * m_toBridge; + std::unique_ptr m_fromBridge; + std::unique_ptr m_toBridge; int m_pickupLoc; int m_oversample; float m_randomize; float m_stringLoss; - - float * m_impulse; + + std::unique_ptr m_impulse; int m_choice; float m_state; - - sample_t * m_outsamp; - delayLine * initDelayLine( int _len, int _pick ); - static void freeDelayLine( delayLine * _dl ); - void resample( float *_src, f_cnt_t _src_frames, f_cnt_t _dst_frames ); - - /* setDelayLine initializes the string with an impulse at the pick + std::unique_ptr m_outsamp; + + std::unique_ptr initDelayLine(int len); + void resample(const float* src, f_cnt_t srcFrames, f_cnt_t dstFrames); + + /** + * setDelayLine initializes the string with an impulse at the pick * position unless the impulse is longer than the string, in which - * case the impulse gets truncated. */ - inline void setDelayLine( delayLine * _dl, - int _pick, - const float * _values, - int _len, - float _scale, - bool _state ) + * case the impulse gets truncated. + */ + void setDelayLine(DelayLine* dl, int pick, const float* values, int len, float scale, bool state) { - float r; - float offset; - - if( ! _state ) + if (!state) { - for( int i = 0; i < _pick; i++ ) + for (int i = 0; i < pick; ++i) { - r = static_cast( rand() ) / - RAND_MAX; - offset = ( m_randomize / 2.0f - - m_randomize ) * r; - _dl->data[i] = _scale * - _values[_dl->length - i - 1] + - offset; + 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++ ) + for (int i = pick; i < dl->length; ++i) { - r = static_cast( rand() ) / - RAND_MAX; - offset = ( m_randomize / 2.0f - - m_randomize ) * r; - _dl->data[i] = _scale * - _values[i - _pick] + offset ; + 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; } } else { - if( _len + _pick > _dl->length ) + if (len + pick > dl->length) { - for( int i = _pick; i < _dl->length; i++ ) + for (int i = pick; i < dl->length; ++i) { - r = static_cast( rand() ) / - RAND_MAX; - offset = ( m_randomize / 2.0f - - m_randomize ) * r; - _dl->data[i] = _scale * - _values[i-_pick] + - offset; + 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; } } else { - for( int i = 0; i < _len; i++ ) + for (int i = 0; i < len; ++i) { - r = static_cast( rand() ) / - RAND_MAX; - offset = ( m_randomize / 2.0f - - m_randomize ) * r; - _dl->data[i+_pick] = _scale * - _values[i] + - offset; + 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; } } } } - /* toBridgeUpdate(dl, insamp); - * Places "nut-reflected" sample from upper delay-line into - * current lower delay-line pointer position (which represents - * x = 0 position). The pointer is then incremented (i.e. the - * wave travels one sample to the left), turning the previous - * position into an "effective" x = L position for the next - * iteration. */ - inline void toBridgeUpdate( delayLine * _dl, sample_t _insamp ) + /** + * toBridgeUpdate(dl, insamp); + * Places "nut-reflected" sample from upper delay-line into + * current lower delay-line pointer position (which represents + * x = 0 position). The pointer is then incremented (i.e. the + * wave travels one sample to the left), turning the previous + * position into an "effective" x = L position for the next + * iteration. + */ + void toBridgeUpdate(DelayLine* dl, sample_t insamp) { - sample_t * ptr = _dl->pointer; - *ptr = _insamp * m_stringLoss; + sample_t* ptr = dl->pointer; + *ptr = insamp * m_stringLoss; ++ptr; - if( ptr > _dl->end ) + if (ptr > dl->end) { - ptr = _dl->data; + ptr = dl->data.get(); } - _dl->pointer = ptr; + dl->pointer = ptr; } - /* fromBridgeUpdate(dl, insamp); - * Decrements current upper delay-line pointer position (i.e. - * the wave travels one sample to the right), moving it to the - * "effective" x = 0 position for the next iteration. The - * "bridge-reflected" sample from lower delay-line is then placed - * into this position. */ - inline void fromBridgeUpdate( delayLine * _dl, - sample_t _insamp ) + /** + * fromBridgeUpdate(dl, insamp); + * Decrements current upper delay-line pointer position (i.e. + * the wave travels one sample to the right), moving it to the + * "effective" x = 0 position for the next iteration. The + * "bridge-reflected" sample from lower delay-line is then placed + * into this position. + */ + void fromBridgeUpdate(DelayLine* dl, sample_t insamp) { - sample_t * ptr = _dl->pointer; + sample_t* ptr = dl->pointer; --ptr; - if( ptr < _dl->data ) + if (ptr < dl->data.get()) { - ptr = _dl->end; + ptr = dl->end; } - *ptr = _insamp * m_stringLoss; - _dl->pointer = ptr; + *ptr = insamp * m_stringLoss; + dl->pointer = ptr; } - /* dlAccess(dl, position); - * Returns sample "position" samples into delay-line's past. - * Position "0" points to the most recently inserted sample. */ - static inline sample_t dlAccess( delayLine * _dl, int _position ) + /** + * dlAccess(dl, position); + * Returns sample "position" samples into delay-line's past. + * Position "0" points to the most recently inserted sample. + */ + static sample_t dlAccess(DelayLine* dl, int position) { - sample_t * outpos = _dl->pointer + _position; - while( outpos < _dl->data ) + sample_t* outpos = dl->pointer + position; + while (outpos < dl->data.get()) { - outpos += _dl->length; + outpos += dl->length; } - while( outpos > _dl->end ) + while (outpos > dl->end) { - outpos -= _dl->length; + outpos -= dl->length; } - return( *outpos ); + return *outpos; } /* - * Right-going delay line: - * -->---->---->--- - * x=0 - * (pointer) - * Left-going delay line: - * --<----<----<--- - * x=0 - * (pointer) - */ + * Right-going delay line: + * -->---->---->--- + * x=0 + * (pointer) + * Left-going delay line: + * --<----<----<--- + * x=0 + * (pointer) + */ - /* fromBridgeAccess(dl, position); - * Returns spatial sample at position "position", where position zero - * is equal to the current upper delay-line pointer position (x = 0). - * In a right-going delay-line, position increases to the right, and - * delay increases to the right => left = past and right = future. */ - static inline sample_t fromBridgeAccess( delayLine * _dl, - int _position ) + /** + * fromBridgeAccess(dl, position); + * Returns spatial sample at position "position", where position zero + * is equal to the current upper delay-line pointer position (x = 0). + * In a right-going delay-line, position increases to the right, and + * delay increases to the right => left = past and right = future. + */ + static sample_t fromBridgeAccess(DelayLine* dl, int position) { - return( dlAccess( _dl, _position ) ); + return dlAccess(dl, position); } - /* toBridgeAccess(dl, position); - * Returns spatial sample at position "position", where position zero - * is equal to the current lower delay-line pointer position (x = 0). - * In a left-going delay-line, position increases to the right, and - * delay DEcreases to the right => left = future and right = past. */ - static inline sample_t toBridgeAccess( delayLine * _dl, int _position ) + /** + * toBridgeAccess(dl, position); + * Returns spatial sample at position "position", where position zero + * is equal to the current lower delay-line pointer position (x = 0). + * In a left-going delay-line, position increases to the right, and + * delay DEcreases to the right => left = future and right = past. + */ + static sample_t toBridgeAccess(DelayLine* dl, int position) { - return( dlAccess( _dl, _position ) ); + return dlAccess(dl, position); } - inline sample_t bridgeReflection( sample_t _insamp ) + sample_t bridgeReflection(sample_t insamp) { - return( m_state = ( m_state + _insamp ) * 0.5 ); + m_state = (m_state + insamp) * 0.5; + return m_state; } - -} ; +}; } // namespace lmms -#endif +#endif // LMMS_VIBRATING_STRING_H 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/RemoteVstPlugin.cpp b/plugins/VstBase/RemoteVstPlugin.cpp index aaa88f6c8..0ec60bea4 100644 --- a/plugins/VstBase/RemoteVstPlugin.cpp +++ b/plugins/VstBase/RemoteVstPlugin.cpp @@ -127,7 +127,7 @@ struct ERect using namespace std; -static lmms::VstHostLanguages hlang = lmms::LanguageEnglish; +static lmms::VstHostLanguage hlang = lmms::VstHostLanguage::English; static bool EMBED = false; static bool EMBED_X11 = false; @@ -390,7 +390,7 @@ public: #endif private: - enum GuiThreadMessages + enum class GuiThreadMessage { None, ProcessPluginMessage, @@ -628,7 +628,7 @@ bool RemoteVstPlugin::processMessage( const message & _m ) break; case IdVstSetLanguage: - hlang = static_cast( _m.getInt() ); + hlang = static_cast( _m.getInt() ); break; case IdVstGetParameterDump: @@ -1763,7 +1763,7 @@ intptr_t RemoteVstPlugin::hostCallback( AEffect * _effect, int32_t _opcode, // call application idle routine (this will // call effEditIdle for all open editors too) #ifndef NATIVE_LINUX_VST - PostMessage( __MessageHwnd, WM_USER, GiveIdle, 0 ); + PostMessage( __MessageHwnd, WM_USER, static_cast(GuiThreadMessage::GiveIdle), 0 ); #else __plugin->sendX11Idle(); #endif @@ -2066,7 +2066,7 @@ intptr_t RemoteVstPlugin::hostCallback( AEffect * _effect, int32_t _opcode, case audioMasterGetLanguage: SHOW_CALLBACK( "amc: audioMasterGetLanguage\n" ); - return hlang; + return static_cast(hlang); case audioMasterGetDirectory: SHOW_CALLBACK( "amc: audioMasterGetDirectory\n" ); @@ -2077,7 +2077,7 @@ intptr_t RemoteVstPlugin::hostCallback( AEffect * _effect, int32_t _opcode, SHOW_CALLBACK( "amc: audioMasterUpdateDisplay\n" ); // something has changed, update 'multi-fx' display #ifndef NATIVE_LINUX_VST - PostMessage( __MessageHwnd, WM_USER, GiveIdle, 0 ); + PostMessage( __MessageHwnd, WM_USER, static_cast(GuiThreadMessage::GiveIdle), 0 ); #else __plugin->sendX11Idle(); #endif @@ -2234,7 +2234,7 @@ void * RemoteVstPlugin::processingThread(void * _param) #ifndef NATIVE_LINUX_VST PostMessage( __MessageHwnd, WM_USER, - ProcessPluginMessage, + static_cast(GuiThreadMessage::ProcessPluginMessage), (LPARAM) new message( m ) ); #else _this->queueMessage( m ); @@ -2244,7 +2244,7 @@ void * RemoteVstPlugin::processingThread(void * _param) // notify GUI thread about shutdown #ifndef NATIVE_LINUX_VST - PostMessage( __MessageHwnd, WM_USER, ClosePlugin, 0 ); + PostMessage( __MessageHwnd, WM_USER, static_cast(GuiThreadMessage::ClosePlugin), 0 ); return 0; #else @@ -2349,9 +2349,9 @@ LRESULT CALLBACK RemoteVstPlugin::wndProc( HWND hwnd, UINT uMsg, } else if( uMsg == WM_USER ) { - switch( wParam ) + switch( static_cast(wParam) ) { - case ProcessPluginMessage: + case GuiThreadMessage::ProcessPluginMessage: { message * m = (message *) lParam; __plugin->queueMessage( *m ); @@ -2363,11 +2363,11 @@ LRESULT CALLBACK RemoteVstPlugin::wndProc( HWND hwnd, UINT uMsg, return 0; } - case GiveIdle: + case GuiThreadMessage::GiveIdle: __plugin->idle(); return 0; - case ClosePlugin: + case GuiThreadMessage::ClosePlugin: PostQuitMessage(0); return 0; diff --git a/plugins/VstBase/RemoteVstPlugin32.cmake b/plugins/VstBase/RemoteVstPlugin32.cmake index 466752aa5..0f98d34e0 100644 --- a/plugins/VstBase/RemoteVstPlugin32.cmake +++ b/plugins/VstBase/RemoteVstPlugin32.cmake @@ -47,13 +47,6 @@ ELSEIF(LMMS_BUILD_WIN64 AND MSVC) 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 complie 32-bit binaries, please make sure you have 32-bit GCC libraries") - RETURN() - ENDIF() ExternalProject_Add(RemoteVstPlugin32 "${EXTERNALPROJECT_ARGS}" CMAKE_ARGS @@ -63,7 +56,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 700a14ac7..b23ae39bf 100644 --- a/plugins/VstBase/VstPlugin.cpp +++ b/plugins/VstBase/VstPlugin.cpp @@ -216,18 +216,18 @@ void VstPlugin::tryLoad( const QString &remoteVstPluginExecutable ) lock(); - VstHostLanguages hlang = LanguageEnglish; + VstHostLanguage hlang = VstHostLanguage::English; switch( QLocale::system().language() ) { - case QLocale::French: hlang = LanguageFrench; break; - case QLocale::German: hlang = LanguageGerman; break; - case QLocale::Italian: hlang = LanguageItalian; break; - case QLocale::Japanese: hlang = LanguageJapanese; break; - case QLocale::Korean: hlang = LanguageKorean; break; - case QLocale::Spanish: hlang = LanguageSpanish; break; + case QLocale::French: hlang = VstHostLanguage::French; break; + case QLocale::German: hlang = VstHostLanguage::German; break; + case QLocale::Italian: hlang = VstHostLanguage::Italian; break; + case QLocale::Japanese: hlang = VstHostLanguage::Japanese; break; + case QLocale::Korean: hlang = VstHostLanguage::Korean; break; + case QLocale::Spanish: hlang = VstHostLanguage::Spanish; break; default: break; } - sendMessage( message( IdVstSetLanguage ).addInt( hlang ) ); + sendMessage( message( IdVstSetLanguage ).addInt( static_cast(hlang) ) ); sendMessage( message( IdVstLoadPlugin ).addString( QSTR_TO_STDSTR( m_plugin ) ) ); waitForInitDone(); @@ -751,7 +751,7 @@ void VstPlugin::createUI( QWidget * parent ) QHBoxLayout * l = new QHBoxLayout( helper ); QWidget * target = new QWidget( helper ); l->setSpacing( 0 ); - l->setMargin( 0 ); + l->setContentsMargins(0, 0, 0, 0); l->addWidget( target ); // we've to call that for making sure, Qt created the windows diff --git a/plugins/VstBase/communication.h b/plugins/VstBase/communication.h index 1f32dd135..50351d38e 100644 --- a/plugins/VstBase/communication.h +++ b/plugins/VstBase/communication.h @@ -40,15 +40,15 @@ struct VstParameterDumpItem -enum VstHostLanguages +enum class VstHostLanguage { - LanguageEnglish = 1, - LanguageGerman, - LanguageFrench, - LanguageItalian, - LanguageSpanish, - LanguageJapanese, - LanguageKorean + English = 1, + German, + French, + Italian, + Spanish, + Japanese, + Korean } ; diff --git a/plugins/VstBase/vst_base.cpp b/plugins/VstBase/vst_base.cpp index 80cb9d736..154dca975 100644 --- a/plugins/VstBase/vst_base.cpp +++ b/plugins/VstBase/vst_base.cpp @@ -41,7 +41,7 @@ Plugin::Descriptor VSTBASE_EXPORT vstbase_plugin_descriptor = "library for all LMMS plugins dealing with VST-plugins", "Tobias Doerffel ", 0x0100, - Plugin::Library, + Plugin::Type::Library, nullptr, nullptr, } ; 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/VstEffect.cpp b/plugins/VstEffect/VstEffect.cpp index c3e40cfa7..bdbdea806 100644 --- a/plugins/VstEffect/VstEffect.cpp +++ b/plugins/VstEffect/VstEffect.cpp @@ -49,10 +49,10 @@ Plugin::Descriptor PLUGIN_EXPORT vsteffect_plugin_descriptor = "plugin for using arbitrary VST effects inside LMMS." ), "Tobias Doerffel ", 0x0200, - Plugin::Effect, + Plugin::Type::Effect, new PluginPixmapLoader("logo"), nullptr, - new VstSubPluginFeatures( Plugin::Effect ) + new VstSubPluginFeatures( Plugin::Type::Effect ) } ; } diff --git a/plugins/VstEffect/VstEffectControlDialog.cpp b/plugins/VstEffect/VstEffectControlDialog.cpp index 52160af57..0fb4913a3 100644 --- a/plugins/VstEffect/VstEffectControlDialog.cpp +++ b/plugins/VstEffect/VstEffectControlDialog.cpp @@ -48,6 +48,16 @@ VstEffectControlDialog::VstEffectControlDialog( VstEffectControls * _ctl ) : m_plugin( nullptr ), tbLabel( nullptr ) { +#if QT_VERSION < 0x50C00 + // Workaround for a bug in Qt versions below 5.12, + // where argument-dependent-lookup fails for QFlags operators + // declared inside a namepsace. + // This affects the Q_DECLARE_OPERATORS_FOR_FLAGS macro in Instrument.h + // See also: https://codereview.qt-project.org/c/qt/qtbase/+/225348 + + using ::operator|; +#endif + auto l = new QGridLayout(this); l->setContentsMargins( 10, 10, 10, 10 ); l->setVerticalSpacing( 2 ); @@ -236,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 e73530a09..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{}; @@ -316,6 +315,16 @@ namespace gui ManageVSTEffectView::ManageVSTEffectView( VstEffect * _eff, VstEffectControls * m_vi ) : m_effect( _eff ) { +#if QT_VERSION < 0x50C00 + // Workaround for a bug in Qt versions below 5.12, + // where argument-dependent-lookup fails for QFlags operators + // declared inside a namepsace. + // This affects the Q_DECLARE_OPERATORS_FOR_FLAGS macro in Instrument.h + // See also: https://codereview.qt-project.org/c/qt/qtbase/+/225348 + + using ::operator|; +#endif + m_vi2 = m_vi; widget = new QWidget(); m_vi->m_scrollArea = new QScrollArea( widget ); @@ -366,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; } @@ -379,7 +389,7 @@ ManageVSTEffectView::ManageVSTEffectView( VstEffect * _eff, VstEffectControls * sprintf(paramStr.data(), "param%d", i); s_dumpValues = dump[paramStr.data()].split(":"); - vstKnobs[ i ] = new CustomTextKnob( knobBright_26, widget, s_dumpValues.at( 1 ) ); + vstKnobs[ i ] = new CustomTextKnob( KnobType::Bright26, widget, s_dumpValues.at( 1 ) ); vstKnobs[ i ]->setDescription( s_dumpValues.at( 1 ) + ":" ); vstKnobs[ i ]->setLabel( s_dumpValues.at( 1 ).left( 15 ) ); @@ -442,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++ ) { @@ -453,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 ); } @@ -533,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++ ) { @@ -548,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/VstEffect/VstSubPluginFeatures.cpp b/plugins/VstEffect/VstSubPluginFeatures.cpp index e60fee0c1..7eab7a9bf 100644 --- a/plugins/VstEffect/VstSubPluginFeatures.cpp +++ b/plugins/VstEffect/VstSubPluginFeatures.cpp @@ -34,7 +34,7 @@ namespace lmms { -VstSubPluginFeatures::VstSubPluginFeatures( Plugin::PluginTypes _type ) : +VstSubPluginFeatures::VstSubPluginFeatures( Plugin::Type _type ) : SubPluginFeatures( _type ) { } @@ -82,7 +82,11 @@ void VstSubPluginFeatures::addPluginsFromDir( QStringList* filenames, QString pa } } QStringList dlls = QDir( ConfigManager::inst()->vstDir() + path ). - entryList( QStringList() << "*.dll", + entryList( QStringList() << "*.dll" +#ifdef LMMS_BUILD_LINUX + << "*.so" +#endif + , QDir::Files, QDir::Name ); for( int i = 0; i < dlls.size(); i++ ) { diff --git a/plugins/VstEffect/VstSubPluginFeatures.h b/plugins/VstEffect/VstSubPluginFeatures.h index c5dc87d14..a5673dfb7 100644 --- a/plugins/VstEffect/VstSubPluginFeatures.h +++ b/plugins/VstEffect/VstSubPluginFeatures.h @@ -38,7 +38,7 @@ namespace lmms class VstSubPluginFeatures : public Plugin::Descriptor::SubPluginFeatures { public: - VstSubPluginFeatures( Plugin::PluginTypes _type ); + VstSubPluginFeatures( Plugin::Type _type ); void fillDescriptionWidget( QWidget * _parent, const Key * _key ) const override; 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 9ceaaddb8..8e49942e1 100644 --- a/plugins/Watsyn/Watsyn.cpp +++ b/plugins/Watsyn/Watsyn.cpp @@ -52,7 +52,7 @@ Plugin::Descriptor PLUGIN_EXPORT watsyn_plugin_descriptor = "4-oscillator modulatable wavetable synth" ), "Vesa Kivimäki ", 0x0100, - Plugin::Instrument, + Plugin::Type::Instrument, new PluginPixmapLoader( "logo" ), nullptr, nullptr, @@ -329,7 +329,7 @@ WatsynInstrument::WatsynInstrument( InstrumentTrack * _instrument_track ) : void WatsynInstrument::playNote( NotePlayHandle * _n, sampleFrame * _working_buffer ) { - if ( _n->totalFramesPlayed() == 0 || _n->m_pluginData == nullptr ) + 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); @@ -445,8 +445,6 @@ void WatsynInstrument::playNote( NotePlayHandle * _n, } applyRelease( _working_buffer, _n ); - - instrumentTrack()->processAudioBuffer( _working_buffer, frames + offset, _n ); } @@ -816,7 +814,7 @@ WatsynView::WatsynView( Instrument * _instrument, pal = QPalette(); pal.setBrush( backgroundRole(), PLUGIN_NAME::getIconPixmap("wavegraph") ); // a1 graph - a1_graph = new Graph( this, Graph::LinearStyle, 224, 105 ); + a1_graph = new Graph( this, Graph::Style::Linear, 224, 105 ); a1_graph->move( 4, 141 ); a1_graph->setAutoFillBackground( true ); a1_graph->setGraphColor( QColor( 0x43, 0xb2, 0xff ) ); @@ -824,7 +822,7 @@ WatsynView::WatsynView( Instrument * _instrument, a1_graph->setPalette( pal ); // a2 graph - a2_graph = new Graph( this, Graph::LinearStyle, 224, 105 ); + a2_graph = new Graph( this, Graph::Style::Linear, 224, 105 ); a2_graph->move( 4, 141 ); a2_graph->setAutoFillBackground( true ); a2_graph->setGraphColor( QColor( 0x43, 0xb2, 0xff ) ); @@ -832,7 +830,7 @@ WatsynView::WatsynView( Instrument * _instrument, a2_graph->setPalette( pal ); // b1 graph - b1_graph = new Graph( this, Graph::LinearStyle, 224, 105 ); + b1_graph = new Graph( this, Graph::Style::Linear, 224, 105 ); b1_graph->move( 4, 141 ); b1_graph->setAutoFillBackground( true ); b1_graph->setGraphColor( QColor( 0xfc, 0x54, 0x31 ) ); @@ -840,7 +838,7 @@ WatsynView::WatsynView( Instrument * _instrument, b1_graph->setPalette( pal ); // b2 graph - b2_graph = new Graph( this, Graph::LinearStyle, 224, 105 ); + b2_graph = new Graph( this, Graph::Style::Linear, 224, 105 ); b2_graph->move( 4, 141 ); b2_graph->setAutoFillBackground( true ); b2_graph->setGraphColor( QColor( 0xfc, 0x54, 0x31 ) ); diff --git a/plugins/Watsyn/Watsyn.h b/plugins/Watsyn/Watsyn.h index 3c69be06f..d238edbde 100644 --- a/plugins/Watsyn/Watsyn.h +++ b/plugins/Watsyn/Watsyn.h @@ -32,21 +32,20 @@ #include "AutomatableModel.h" #include "TempoSyncKnob.h" #include -#include "MemoryManager.h" namespace lmms { #define makeknob( name, x, y, hint, unit, oname ) \ - name = new Knob( knobStyled, this ); \ + name = new Knob( KnobType::Styled, this ); \ name ->move( x, y ); \ name ->setHintText( hint, unit ); \ name ->setObjectName( oname ); \ name ->setFixedSize( 19, 19 ); #define maketsknob( name, x, y, hint, unit, oname ) \ - name = new TempoSyncKnob( knobStyled, this ); \ + name = new TempoSyncKnob( KnobType::Styled, this ); \ name ->move( x, y ); \ name ->setHintText( hint, unit ); \ name ->setObjectName( oname ); \ @@ -88,7 +87,6 @@ class WatsynView; class WatsynObject { - MM_OPERATORS public: WatsynObject( float * _A1wave, float * _A2wave, float * _B1wave, float * _B2wave, diff --git a/plugins/WaveShaper/WaveShaper.cpp b/plugins/WaveShaper/WaveShaper.cpp index 94845e672..acd5a933b 100644 --- a/plugins/WaveShaper/WaveShaper.cpp +++ b/plugins/WaveShaper/WaveShaper.cpp @@ -46,7 +46,7 @@ Plugin::Descriptor PLUGIN_EXPORT waveshaper_plugin_descriptor = "plugin for waveshaping" ), "Vesa Kivimäki ", 0x0100, - Plugin::Effect, + Plugin::Type::Effect, new PluginPixmapLoader("logo"), nullptr, nullptr, diff --git a/plugins/WaveShaper/WaveShaperControlDialog.cpp b/plugins/WaveShaper/WaveShaperControlDialog.cpp index 5ef061fdb..045f84763 100644 --- a/plugins/WaveShaper/WaveShaperControlDialog.cpp +++ b/plugins/WaveShaper/WaveShaperControlDialog.cpp @@ -48,7 +48,7 @@ WaveShaperControlDialog::WaveShaperControlDialog( setPalette( pal ); setFixedSize( 224, 274 ); - auto waveGraph = new Graph(this, Graph::LinearNonCyclicStyle, 204, 205); + auto waveGraph = new Graph(this, Graph::Style::LinearNonCyclic, 204, 205); waveGraph -> move( 10, 6 ); waveGraph -> setModel( &_controls -> m_wavegraphModel ); waveGraph -> setAutoFillBackground( true ); @@ -59,7 +59,7 @@ WaveShaperControlDialog::WaveShaperControlDialog( waveGraph->setGraphColor( QColor( 85, 204, 145 ) ); waveGraph -> setMaximumSize( 204, 205 ); - auto inputKnob = new Knob(knobBright_26, this); + auto inputKnob = new Knob(KnobType::Bright26, this); inputKnob -> setVolumeKnob( true ); inputKnob -> setVolumeRatio( 1.0 ); inputKnob -> move( 26, 225 ); @@ -67,7 +67,7 @@ WaveShaperControlDialog::WaveShaperControlDialog( inputKnob->setLabel( tr( "INPUT" ) ); inputKnob->setHintText( tr( "Input gain:" ) , "" ); - auto outputKnob = new Knob(knobBright_26, this); + auto outputKnob = new Knob(KnobType::Bright26, this); outputKnob -> setVolumeKnob( true ); outputKnob -> setVolumeRatio( 1.0 ); outputKnob -> move( 76, 225 ); @@ -103,7 +103,7 @@ WaveShaperControlDialog::WaveShaperControlDialog( subOneButton -> setInactiveGraphic( PLUGIN_NAME::getIconPixmap( "sub1_inactive" ) ); subOneButton->setToolTip(tr("Decrease wavegraph amplitude by 1 dB")); - auto clipInputToggle = new LedCheckBox("Clip input", this, tr("Clip input"), LedCheckBox::Green); + auto clipInputToggle = new LedCheckBox("Clip input", this, tr("Clip input"), LedCheckBox::LedColor::Green); clipInputToggle -> move( 131, 252 ); clipInputToggle -> setModel( &_controls -> m_clipModel ); clipInputToggle->setToolTip(tr("Clip input signal to 0 dB")); 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 18ac8c052..f738dc965 100644 --- a/plugins/Xpressive/Xpressive.cpp +++ b/plugins/Xpressive/Xpressive.cpp @@ -57,7 +57,7 @@ extern "C" { Plugin::Descriptor PLUGIN_EXPORT xpressive_plugin_descriptor = { LMMS_STRINGIFY( PLUGIN_NAME), "Xpressive", QT_TRANSLATE_NOOP("PluginBrowser", "Mathematical expression parser"), "Orr Dvori", 0x0100, - Plugin::Instrument, new PluginPixmapLoader("logo"), nullptr, nullptr }; + Plugin::Type::Instrument, new PluginPixmapLoader("logo"), nullptr, nullptr }; } @@ -201,7 +201,7 @@ void Xpressive::playNote(NotePlayHandle* nph, sampleFrame* working_buffer) { m_A2=m_parameterA2.value(); m_A3=m_parameterA3.value(); - if (nph->totalFramesPlayed() == 0 || nph->m_pluginData == nullptr) { + if (!nph->m_pluginData) { auto exprO1 = new ExprFront(m_outputExpression[0].constData(), Engine::audioEngine()->processingSampleRate()); // give the "last" function a whole second @@ -233,8 +233,6 @@ void Xpressive::playNote(NotePlayHandle* nph, sampleFrame* working_buffer) { const f_cnt_t offset = nph->noteOffset(); ps->renderOutput(frames, working_buffer + offset); - - instrumentTrack()->processAudioBuffer(working_buffer, frames + offset, nph); } void Xpressive::deleteNotePluginData(NotePlayHandle* nph) { @@ -258,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; } @@ -291,11 +288,11 @@ public: setLineWidth(3); } XpressiveKnob(QWidget * _parent, const QString & _name) : - Knob(knobStyled, _parent,_name) { + Knob(KnobType::Styled, _parent,_name) { setStyle(); } XpressiveKnob(QWidget * _parent) : - Knob(knobStyled, _parent) { + Knob(KnobType::Styled, _parent) { setStyle(); } @@ -325,7 +322,7 @@ XpressiveView::XpressiveView(Instrument * _instrument, QWidget * _parent) : pal.setBrush(backgroundRole(), PLUGIN_NAME::getIconPixmap("artwork")); setPalette(pal); - m_graph = new Graph(this, Graph::LinearStyle, 180, 81); + m_graph = new Graph(this, Graph::Style::Linear, 180, 81); m_graph->move(3, BASE_START + 1); m_graph->setAutoFillBackground(true); m_graph->setGraphColor(QColor(255, 255, 255)); @@ -338,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")); @@ -447,11 +437,11 @@ XpressiveView::XpressiveView(Instrument * _instrument, QWidget * _parent) : m_waveInterpolate = new LedCheckBox("Interpolate", this, tr("WaveInterpolate"), - LedCheckBox::Green); + LedCheckBox::LedColor::Green); m_waveInterpolate->move(2, 230); m_expressionValidToggle = new LedCheckBox("", this, tr("ExpressionValid"), - LedCheckBox::Red); + LedCheckBox::LedColor::Red); m_expressionValidToggle->move(168, EXPR_TEXT_Y+EXPR_TEXT_H-2); m_expressionValidToggle->setEnabled( false ); @@ -485,7 +475,7 @@ XpressiveView::XpressiveView(Instrument * _instrument, QWidget * _parent) : - m_smoothKnob=new Knob(knobStyled, this, "Smoothness"); + m_smoothKnob=new Knob(KnobType::Styled, this, "Smoothness"); m_smoothKnob->setFixedSize(25, 25); m_smoothKnob->setCenterPointX(12.5); m_smoothKnob->setCenterPointY(12.5); 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/Xpressive/exprtk b/plugins/Xpressive/exprtk index 93a9f44f9..f46bffcd6 160000 --- a/plugins/Xpressive/exprtk +++ b/plugins/Xpressive/exprtk @@ -1 +1 @@ -Subproject commit 93a9f44f99b910bfe07cd1e933371e83cea3841c +Subproject commit f46bffcd6966d38a09023fb37ba9335214c9b959 diff --git a/plugins/ZynAddSubFx/ZynAddSubFx.cpp b/plugins/ZynAddSubFx/ZynAddSubFx.cpp index d3d23c65f..c406e04e0 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" @@ -66,7 +65,7 @@ Plugin::Descriptor PLUGIN_EXPORT zynaddsubfx_plugin_descriptor = "Embedded ZynAddSubFX" ), "Tobias Doerffel ", 0x0100, - Plugin::Instrument, + Plugin::Type::Instrument, new PluginPixmapLoader( "logo" ), "xiz", nullptr, @@ -151,8 +150,8 @@ ZynAddSubFxInstrument::ZynAddSubFxInstrument( ZynAddSubFxInstrument::~ZynAddSubFxInstrument() { Engine::audioEngine()->removePlayHandlesOfTypes( instrumentTrack(), - PlayHandle::TypeNotePlayHandle - | PlayHandle::TypeInstrumentPlayHandle ); + PlayHandle::Type::NotePlayHandle + | PlayHandle::Type::InstrumentPlayHandle ); m_pluginMutex.lock(); delete m_plugin; @@ -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(); @@ -341,7 +340,6 @@ void ZynAddSubFxInstrument::play( sampleFrame * _buf ) m_plugin->processAudio( _buf ); } m_pluginMutex.unlock(); - instrumentTrack()->processAudioBuffer( _buf, Engine::audioEngine()->framesPerPeriod(), nullptr ); } @@ -380,7 +378,7 @@ bool ZynAddSubFxInstrument::handleMidiEvent( const MidiEvent& event, const TimeP void ZynAddSubFxInstrument::reloadPlugin() { // save state of current plugin instance - DataFile m( DataFile::InstrumentTrackSettings ); + DataFile m( DataFile::Type::InstrumentTrackSettings ); saveSettings( m, m.content() ); // init plugin (will delete current one and create a new instance) @@ -508,31 +506,31 @@ ZynAddSubFxView::ZynAddSubFxView( Instrument * _instrument, QWidget * _parent ) l->setVerticalSpacing( 16 ); l->setHorizontalSpacing( 10 ); - m_portamento = new Knob( knobBright_26, this ); + m_portamento = new Knob( KnobType::Bright26, this ); m_portamento->setHintText( tr( "Portamento:" ), "" ); m_portamento->setLabel( tr( "PORT" ) ); - m_filterFreq = new Knob( knobBright_26, this ); + m_filterFreq = new Knob( KnobType::Bright26, this ); m_filterFreq->setHintText( tr( "Filter frequency:" ), "" ); m_filterFreq->setLabel( tr( "FREQ" ) ); - m_filterQ = new Knob( knobBright_26, this ); + m_filterQ = new Knob( KnobType::Bright26, this ); m_filterQ->setHintText( tr( "Filter resonance:" ), "" ); m_filterQ->setLabel( tr( "RES" ) ); - m_bandwidth = new Knob( knobBright_26, this ); + m_bandwidth = new Knob( KnobType::Bright26, this ); m_bandwidth->setHintText( tr( "Bandwidth:" ), "" ); m_bandwidth->setLabel( tr( "BW" ) ); - m_fmGain = new Knob( knobBright_26, this ); + m_fmGain = new Knob( KnobType::Bright26, this ); m_fmGain->setHintText( tr( "FM gain:" ), "" ); m_fmGain->setLabel( tr( "FM GAIN" ) ); - m_resCenterFreq = new Knob( knobBright_26, this ); + m_resCenterFreq = new Knob( KnobType::Bright26, this ); m_resCenterFreq->setHintText( tr( "Resonance center frequency:" ), "" ); m_resCenterFreq->setLabel( tr( "RES CF" ) ); - m_resBandwidth = new Knob( knobBright_26, this ); + m_resBandwidth = new Knob( KnobType::Bright26, this ); m_resBandwidth->setHintText( tr( "Resonance bandwidth:" ), "" ); m_resBandwidth->setLabel( tr( "RES BW" ) ); @@ -542,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 996c187ee..a391203f3 100644 --- a/plugins/ZynAddSubFx/ZynAddSubFx.h +++ b/plugins/ZynAddSubFx/ZynAddSubFx.h @@ -88,7 +88,7 @@ public: Flags flags() const override { - return IsSingleStreamed | IsMidiBased; + return Flag::IsSingleStreamed | Flag::IsMidiBased; } gui::PluginView* instantiateView( QWidget * _parent ) override; diff --git a/plugins/ZynAddSubFx/zynaddsubfx b/plugins/ZynAddSubFx/zynaddsubfx index 551e816a6..7ad5663cb 160000 --- a/plugins/ZynAddSubFx/zynaddsubfx +++ b/plugins/ZynAddSubFx/zynaddsubfx @@ -1 +1 @@ -Subproject commit 551e816a6334fd190c74ce971378063b2757b47b +Subproject commit 7ad5663cbeebc02d73fd3ad666e428c1287f2cda diff --git a/src/3rdparty/CMakeLists.txt b/src/3rdparty/CMakeLists.txt index 24d156095..3339eb926 100644 --- a/src/3rdparty/CMakeLists.txt +++ b/src/3rdparty/CMakeLists.txt @@ -1,11 +1,17 @@ -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(rpmalloc) +ADD_SUBDIRECTORY(hiir) ADD_SUBDIRECTORY(weakjack) +if(MINGW) + option(MINGW_STDTHREADS_GENERATE_STDHEADERS "" ON) + add_subdirectory(mingw-std-threads) + set(LMMS_USE_MINGW_STD_THREADS ON PARENT_SCOPE) +endif() + # The lockless ring buffer library is compiled as part of the core SET(RINGBUFFER_DIR "${CMAKE_SOURCE_DIR}/src/3rdparty/ringbuffer/") SET(RINGBUFFER_DIR ${RINGBUFFER_DIR} PARENT_SCOPE) diff --git a/src/3rdparty/hiir/CMakeLists.txt b/src/3rdparty/hiir/CMakeLists.txt new file mode 100644 index 000000000..e954ff187 --- /dev/null +++ b/src/3rdparty/hiir/CMakeLists.txt @@ -0,0 +1,3 @@ +add_library(hiir INTERFACE) +target_include_directories(hiir INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}) +target_compile_features(hiir INTERFACE cxx_std_17) diff --git a/src/3rdparty/hiir/hiir b/src/3rdparty/hiir/hiir new file mode 160000 index 000000000..4a9a1e67f --- /dev/null +++ b/src/3rdparty/hiir/hiir @@ -0,0 +1 @@ +Subproject commit 4a9a1e67fa6f8ce7688e1c0c8a2b017cecd206a3 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/mingw-std-threads b/src/3rdparty/mingw-std-threads index 10665829d..6c2061b7d 160000 --- a/src/3rdparty/mingw-std-threads +++ b/src/3rdparty/mingw-std-threads @@ -1 +1 @@ -Subproject commit 10665829daaedc28629e5e9b014fe498c20d73f2 +Subproject commit 6c2061b7da41d6aa1b2162ff4383ec3ece864bc6 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 bd543779f..d71b34c59 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -12,7 +12,7 @@ SET(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}) # Enable C++17 SET(CMAKE_CXX_STANDARD 17) -IF(LMMS_BUILD_APPLE) +IF(LMMS_BUILD_APPLE AND CMAKE_CXX_COMPILER_ID MATCHES "Clang") SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++") ENDIF() @@ -58,8 +58,6 @@ FILE(RELATIVE_PATH PLUGIN_DIR_RELATIVE "/${BIN_DIR}" "/${PLUGIN_DIR}") ADD_DEFINITIONS(-DLIB_DIR="${LIB_DIR_RELATIVE}" -DPLUGIN_DIR="${PLUGIN_DIR_RELATIVE}" ${PULSEAUDIO_DEFINITIONS}) INCLUDE_DIRECTORIES( ${JACK_INCLUDE_DIRS} - ${SAMPLERATE_INCLUDE_DIRS} - ${SNDFILE_INCLUDE_DIRS} ${SNDIO_INCLUDE_DIRS} ${FFTW3F_INCLUDE_DIRS} ) @@ -79,10 +77,6 @@ IF(NOT ("${PULSEAUDIO_INCLUDE_DIR}" STREQUAL "")) INCLUDE_DIRECTORIES("${PULSEAUDIO_INCLUDE_DIR}") ENDIF() -IF(NOT ("${OGGVORBIS_INCLUDE_DIR}" STREQUAL "")) - INCLUDE_DIRECTORIES("${OGGVORBIS_INCLUDE_DIR}") -ENDIF() - IF(NOT ("${LV2_INCLUDE_DIRS}" STREQUAL "")) INCLUDE_DIRECTORIES(${LV2_INCLUDE_DIRS}) ENDIF() @@ -170,6 +164,14 @@ 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() + SET(LMMS_REQUIRED_LIBS ${LMMS_REQUIRED_LIBS} ${CMAKE_THREAD_LIBS_INIT} ${QT_LIBRARIES} @@ -180,15 +182,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 @@ -207,27 +207,18 @@ 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 $> +) +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" + ) +ELSEIF(NOT WIN32) if(CMAKE_INSTALL_MANDIR) SET(INSTALL_MANDIR ${CMAKE_INSTALL_MANDIR}) ELSE(CMAKE_INSTALL_MANDIR) 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/core/AudioEngine.cpp b/src/core/AudioEngine.cpp index a91e88f38..04e3c7c7c 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::Mode::Draft ), 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 ) { @@ -126,6 +121,9 @@ AudioEngine::AudioEngine( bool renderOnly ) : m_framesPerPeriod = DEFAULT_BUFFER_SIZE; } + // lmms works with chunks of size DEFAULT_BUFFER_SIZE (256) and only the final mix will use the actual + // buffer size. Plugins don't see a larger buffer size than 256. If m_framesPerPeriod is larger than + // DEFAULT_BUFFER_SIZE, it's set to DEFAULT_BUFFER_SIZE and the rest is handled by an increased fifoSize. else if( m_framesPerPeriod > DEFAULT_BUFFER_SIZE ) { fifoSize = m_framesPerPeriod / DEFAULT_BUFFER_SIZE; @@ -162,8 +160,6 @@ AudioEngine::AudioEngine( bool renderOnly ) : AudioEngine::~AudioEngine() { - runChangesInModel(); - for( int w = 0; w < m_numWorkers; ++w ) { m_workers[w]->quit(); @@ -229,8 +225,6 @@ void AudioEngine::startProcessing(bool needsFifo) } m_audioDev->startProcessing(); - - m_isProcessing = true; } @@ -238,8 +232,6 @@ void AudioEngine::startProcessing(bool needsFifo) void AudioEngine::stopProcessing() { - m_isProcessing = false; - if( m_fifoWriter != nullptr ) { m_fifoWriter->finish(); @@ -314,7 +306,7 @@ void AudioEngine::pushInputFrames( sampleFrame * _ab, const f_cnt_t _frames ) if( frames + _frames > size ) { - size = qMax( size * 2, frames + _frames ); + size = std::max(size * 2, frames + _frames); auto ab = new sampleFrame[size]; memcpy( ab, buf, frames * sizeof( sampleFrame ) ); delete [] buf; @@ -333,12 +325,9 @@ void AudioEngine::pushInputFrames( sampleFrame * _ab, const f_cnt_t _frames ) - -const surroundSampleFrame * AudioEngine::renderNextBuffer() +void AudioEngine::renderStageNoteSetup() { - m_profiler.startPeriod(); - - s_renderingThread = true; + AudioEngineProfiler::Probe profilerProbe(m_profiler, AudioEngineProfiler::DetailType::NoteSetup); if( m_clearSignal ) { @@ -357,7 +346,7 @@ const surroundSampleFrame * AudioEngine::renderNextBuffer() if( it != m_playHandles.end() ) { ( *it )->audioPort()->removePlayHandle( ( *it ) ); - if( ( *it )->type() == PlayHandle::TypeNotePlayHandle ) + if( ( *it )->type() == PlayHandle::Type::NotePlayHandle ) { NotePlayHandleManager::release( (NotePlayHandle*) *it ); } @@ -387,9 +376,26 @@ const surroundSampleFrame * AudioEngine::renderNextBuffer() m_newPlayHandles.free( e ); e = next; } +} - // STAGE 1: run and render all play handles - AudioEngineWorkerThread::fillJobQueue( m_playHandles ); + + +void AudioEngine::renderStageInstruments() +{ + AudioEngineProfiler::Probe profilerProbe(m_profiler, AudioEngineProfiler::DetailType::Instruments); + + AudioEngineWorkerThread::fillJobQueue(m_playHandles); + AudioEngineWorkerThread::startAndWaitForJobs(); +} + + + +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 @@ -405,7 +411,7 @@ const surroundSampleFrame * AudioEngine::renderNextBuffer() if( ( *it )->isFinished() ) { ( *it )->audioPort()->removePlayHandle( ( *it ) ); - if( ( *it )->type() == PlayHandle::TypeNotePlayHandle ) + if( ( *it )->type() == PlayHandle::Type::NotePlayHandle ) { NotePlayHandleManager::release( (NotePlayHandle*) *it ); } @@ -417,28 +423,43 @@ const surroundSampleFrame * AudioEngine::renderNextBuffer() ++it; } } - - // STAGE 2: process effects of all instrument- and sampletracks - AudioEngineWorkerThread::fillJobQueue >( m_audioPorts ); - AudioEngineWorkerThread::startAndWaitForJobs(); +} - // STAGE 3: do master mix in mixer + +void AudioEngine::renderStageMix() +{ + AudioEngineProfiler::Probe profilerProbe(m_profiler, AudioEngineProfiler::DetailType::Mixing); + + Mixer *mixer = Engine::mixer(); mixer->masterMix(m_outputBufferWrite); + MixHelpers::multiply(m_outputBufferWrite, m_masterGain, m_framesPerPeriod); emit nextAudioBuffer(m_outputBufferRead); - runChangesInModel(); - // and trigger LFOs EnvelopeAndLfoParameters::instances()->trigger(); Controller::triggerFrameCounter(); AutomatableModel::incrementPeriodCounter(); +} + + + +const surroundSampleFrame *AudioEngine::renderNextBuffer() +{ + const auto lock = std::lock_guard{m_changeMutex}; + + m_profiler.startPeriod(); + s_renderingThread = true; + + renderStageNoteSetup(); // STAGE 0: clear old play handles and buffers, setup new play handles + renderStageInstruments(); // STAGE 1: run and render all play handles + renderStageEffects(); // STAGE 2: process effects of all instrument- and sampletracks + renderStageMix(); // STAGE 3: do master mix in mixer s_renderingThread = false; - - m_profiler.finishPeriod( processingSampleRate(), m_framesPerPeriod ); + m_profiler.finishPeriod(processingSampleRate(), m_framesPerPeriod); return m_outputBufferRead; } @@ -464,12 +485,12 @@ void AudioEngine::handleMetronome() static tick_t lastMetroTicks = -1; Song * song = Engine::getSong(); - Song::PlayModes currentPlayMode = song->playMode(); + Song::PlayMode currentPlayMode = song->playMode(); bool metronomeSupported = - currentPlayMode == Song::Mode_PlayMidiClip - || currentPlayMode == Song::Mode_PlaySong - || currentPlayMode == Song::Mode_PlayPattern; + currentPlayMode == Song::PlayMode::MidiClip + || currentPlayMode == Song::PlayMode::Song + || currentPlayMode == Song::PlayMode::Pattern; if (!metronomeSupported || !m_metronomeActive || song->isExporting()) { @@ -534,7 +555,7 @@ void AudioEngine::clearInternal() // TODO: m_midiClient->noteOffAll(); for (auto ph : m_playHandles) { - if (ph->type() != PlayHandle::TypeInstrumentPlayHandle) + if (ph->type() != PlayHandle::Type::InstrumentPlayHandle) { m_playHandlesToRemove.push_back(ph); } @@ -551,8 +572,8 @@ AudioEngine::StereoSample AudioEngine::getPeakValues(sampleFrame * ab, const f_c for (f_cnt_t f = 0; f < frames; ++f) { - float const absLeft = qAbs(ab[f][0]); - float const absRight = qAbs(ab[f][1]); + float const absLeft = std::abs(ab[f][0]); + float const absRight = std::abs(ab[f][1]); if (absLeft > peakLeft) { peakLeft = absLeft; @@ -663,7 +684,7 @@ void AudioEngine::removeAudioPort(AudioPort * port) { requestChangeInModel(); - QVector::Iterator it = std::find(m_audioPorts.begin(), m_audioPorts.end(), port); + auto it = std::find(m_audioPorts.begin(), m_audioPorts.end(), port); if (it != m_audioPorts.end()) { m_audioPorts.erase(it); @@ -674,14 +695,17 @@ void AudioEngine::removeAudioPort(AudioPort * port) bool AudioEngine::addPlayHandle( PlayHandle* handle ) { - if( criticalXRuns() == false ) + // Only add play handles if we have the CPU capacity to process them. + // Instrument play handles are not added during playback, but when the + // associated instrument is created, so add those unconditionally. + if (handle->type() == PlayHandle::Type::InstrumentPlayHandle || !criticalXRuns()) { m_newPlayHandles.push( handle ); handle->audioPort()->addPlayHandle( handle ); return true; } - if( handle->type() == PlayHandle::TypeNotePlayHandle ) + if( handle->type() == PlayHandle::Type::NotePlayHandle ) { NotePlayHandleManager::release( (NotePlayHandle*)handle ); } @@ -732,7 +756,7 @@ void AudioEngine::removePlayHandle(PlayHandle * ph) // (See tobydox's 2008 commit 4583e48) if ( removedFromList ) { - if (ph->type() == PlayHandle::TypeNotePlayHandle) + if (ph->type() == PlayHandle::Type::NotePlayHandle) { NotePlayHandleManager::release(dynamic_cast(ph)); } @@ -749,7 +773,7 @@ void AudioEngine::removePlayHandle(PlayHandle * ph) -void AudioEngine::removePlayHandlesOfTypes(Track * track, const quint8 types) +void AudioEngine::removePlayHandlesOfTypes(Track * track, PlayHandle::Types types) { requestChangeInModel(); PlayHandleList::Iterator it = m_playHandles.begin(); @@ -758,7 +782,7 @@ void AudioEngine::removePlayHandlesOfTypes(Track * track, const quint8 types) if ((*it)->isFromTrack(track) && ((*it)->type() & types)) { ( *it )->audioPort()->removePlayHandle( ( *it ) ); - if( ( *it )->type() == PlayHandle::TypeNotePlayHandle ) + if( ( *it )->type() == PlayHandle::Type::NotePlayHandle ) { NotePlayHandleManager::release( (NotePlayHandle*) *it ); } @@ -778,57 +802,16 @@ void AudioEngine::removePlayHandlesOfTypes(Track * track, const quint8 types) 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) @@ -1264,29 +1247,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/AudioEngineProfiler.cpp b/src/core/AudioEngineProfiler.cpp index 5fd5a813c..82a412cbb 100644 --- a/src/core/AudioEngineProfiler.cpp +++ b/src/core/AudioEngineProfiler.cpp @@ -24,6 +24,8 @@ #include "AudioEngineProfiler.h" +#include + namespace lmms { @@ -38,10 +40,24 @@ AudioEngineProfiler::AudioEngineProfiler() : void AudioEngineProfiler::finishPeriod( sample_rate_t sampleRate, fpp_t framesPerPeriod ) { - int periodElapsed = m_periodTimer.elapsed(); + // Time taken to process all data and fill the audio buffer. + const unsigned int periodElapsed = m_periodTimer.elapsed(); + // Maximum time the processing can take before causing buffer underflow. Convert to us. + const uint64_t timeLimit = static_cast(1000000) * framesPerPeriod / sampleRate; - const float newCpuLoad = periodElapsed / 10000.0f * sampleRate / framesPerPeriod; - m_cpuLoad = qBound( 0, ( newCpuLoad * 0.1f + m_cpuLoad * 0.9f ), 100 ); + // Compute new overall CPU load and apply exponential averaging. + // The result is used for overload detection in AudioEngine::criticalXRuns() + // → the weight of a new sample must be high enough to allow relatively fast changes! + const auto newCpuLoad = 100.f * periodElapsed / timeLimit; + m_cpuLoad = newCpuLoad * 0.1f + m_cpuLoad * 0.9f; + + // Compute detailed load analysis. Can use stronger averaging to get more stable readout. + for (std::size_t i = 0; i < DetailCount; i++) + { + const auto newLoad = 100.f * m_detailTime[i] / timeLimit; + const auto oldLoad = m_detailLoad[i].load(std::memory_order_relaxed); + m_detailLoad[i].store(newLoad * 0.05f + oldLoad * 0.95f, std::memory_order_relaxed); + } if( m_outputFile.isOpen() ) { diff --git a/src/core/AudioEngineWorkerThread.cpp b/src/core/AudioEngineWorkerThread.cpp index 129b3accf..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__ @@ -91,7 +90,7 @@ void AudioEngineWorkerThread::JobQueue::run() } } // always exit loop if we're not in dynamic mode - processedJob = processedJob && ( m_opMode == Dynamic ); + processedJob = processedJob && ( m_opMode == OperationMode::Dynamic ); } } @@ -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 378ccd91e..abda0b43e 100644 --- a/src/core/AutomatableModel.cpp +++ b/src/core/AutomatableModel.cpp @@ -44,7 +44,7 @@ AutomatableModel::AutomatableModel( const float val, const float min, const float max, const float step, Model* parent, const QString & displayName, bool defaultConstructed ) : Model( parent, displayName, defaultConstructed ), - m_scaleType( Linear ), + m_scaleType( ScaleType::Linear ), m_minValue( min ), m_maxValue( max ), m_step( step ), @@ -71,7 +71,7 @@ AutomatableModel::~AutomatableModel() { while( m_linkedModels.empty() == false ) { - m_linkedModels.last()->unlinkModel( this ); + m_linkedModels.back()->unlinkModel(this); m_linkedModels.erase( m_linkedModels.end() - 1 ); } @@ -97,15 +97,15 @@ 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 ) { bool mustQuote = mustQuoteName(name); - if( isAutomated() || m_scaleType != Linear ) + if( isAutomated() || m_scaleType != ScaleType::Linear ) { // automation needs tuple of data (name, id, value) // scale type also needs an extra value @@ -114,7 +114,7 @@ void AutomatableModel::saveSettings( QDomDocument& doc, QDomElement& element, co QDomElement me = doc.createElement( mustQuote ? QString("automatablemodel") : name ); me.setAttribute( "id", ProjectJournal::idToSave( id() ) ); me.setAttribute( "value", m_value ); - me.setAttribute( "scale_type", m_scaleType == Logarithmic ? "log" : "linear" ); + me.setAttribute( "scale_type", m_scaleType == ScaleType::Logarithmic ? "log" : "linear" ); if(mustQuote) { me.setAttribute( "nodename", name ); } @@ -140,11 +140,11 @@ void AutomatableModel::saveSettings( QDomDocument& doc, QDomElement& element, co // the discardMIDIConnections option is true. auto controllerType = m_controllerConnection ? m_controllerConnection->getController()->type() - : Controller::DummyController; + : Controller::ControllerType::Dummy; bool skipMidiController = Engine::getSong()->isSavingProject() && Engine::getSong()->getSaveOptions().discardMIDIConnections.value(); - if (m_controllerConnection && controllerType != Controller::DummyController - && !(skipMidiController && controllerType == Controller::MidiController)) + if (m_controllerConnection && controllerType != Controller::ControllerType::Dummy + && !(skipMidiController && controllerType == Controller::ControllerType::Midi)) { QDomElement controllerElement; @@ -264,18 +264,18 @@ void AutomatableModel::loadSettings( const QDomElement& element, const QString& { if( nodeElement.attribute( "scale_type" ) == "linear" ) { - setScaleType( Linear ); + setScaleType( ScaleType::Linear ); } else if( nodeElement.attribute( "scale_type" ) == "log" ) { - setScaleType( Logarithmic ); + setScaleType( ScaleType::Logarithmic ); } } } else { - setScaleType( Linear ); + setScaleType( ScaleType::Linear ); if( element.hasAttribute( name ) ) // attribute => read the element's value from the attribute list @@ -335,7 +335,7 @@ template T AutomatableModel::logToLinearScale( T value ) const float AutomatableModel::scaledValue( float value ) const { - return m_scaleType == Linear + return m_scaleType == ScaleType::Linear ? value : logToLinearScale( ( value - minValue() ) / m_range ); } @@ -343,7 +343,7 @@ float AutomatableModel::scaledValue( float value ) const float AutomatableModel::inverseScaledValue( float value ) const { - return m_scaleType == Linear + return m_scaleType == ScaleType::Linear ? value : lmms::linearToLogScale( minValue(), maxValue(), value ); } @@ -354,8 +354,8 @@ float AutomatableModel::inverseScaledValue( float value ) const template void roundAt( T& value, const T& where, const T& step_size ) { - if( qAbs( value - where ) - < typeInfo::minEps() * qAbs( step_size ) ) + if (std::abs(value - where) + < typeInfo::minEps() * std::abs(step_size)) { value = where; } @@ -444,7 +444,7 @@ void AutomatableModel::setStep( const float step ) float AutomatableModel::fittedValue( float value ) const { - value = qBound( m_minValue, value, m_maxValue ); + value = std::clamp(value, m_minValue, m_maxValue); if( m_step != 0 && m_hasStrictStepSize ) { @@ -473,7 +473,8 @@ float AutomatableModel::fittedValue( float value ) const void AutomatableModel::linkModel( AutomatableModel* model ) { - if( !m_linkedModels.contains( model ) && model != this ) + auto containsModel = std::find(m_linkedModels.begin(), m_linkedModels.end(), model) != m_linkedModels.end(); + if (!containsModel && model != this) { m_linkedModels.push_back( model ); @@ -490,7 +491,7 @@ void AutomatableModel::linkModel( AutomatableModel* model ) void AutomatableModel::unlinkModel( AutomatableModel* model ) { - AutoModelVector::Iterator it = std::find( m_linkedModels.begin(), m_linkedModels.end(), model ); + auto it = std::find(m_linkedModels.begin(), m_linkedModels.end(), model); if( it != m_linkedModels.end() ) { m_linkedModels.erase( it ); @@ -504,7 +505,8 @@ void AutomatableModel::unlinkModel( AutomatableModel* model ) void AutomatableModel::linkModels( AutomatableModel* model1, AutomatableModel* model2 ) { - if (!model1->m_linkedModels.contains( model2 ) && model1 != model2) + auto model1ContainsModel2 = std::find(model1->m_linkedModels.begin(), model1->m_linkedModels.end(), model2) != model1->m_linkedModels.end(); + if (!model1ContainsModel2 && model1 != model2) { // copy data model1->m_value = model2->m_value; @@ -569,10 +571,10 @@ float AutomatableModel::controllerValue( int frameOffset ) const float v = 0; switch(m_scaleType) { - case Linear: + case ScaleType::Linear: v = minValue() + ( range() * controllerConnection()->currentValue( frameOffset ) ); break; - case Logarithmic: + case ScaleType::Logarithmic: v = logToLinearScale( controllerConnection()->currentValue( frameOffset )); break; @@ -583,12 +585,12 @@ float AutomatableModel::controllerValue( int frameOffset ) const } if( typeInfo::isEqual( m_step, 1 ) && m_hasStrictStepSize ) { - return qRound( v ); + return std::round(v); } return v; } - AutomatableModel* lm = m_linkedModels.first(); + AutomatableModel* lm = m_linkedModels.front(); if (lm->controllerConnection() && lm->useControllerValue()) { return fittedValue( lm->controllerValue( frameOffset ) ); @@ -611,23 +613,22 @@ 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(); float * nvalues = m_valueBuffer.values(); switch( m_scaleType ) { - case Linear: + case ScaleType::Linear: for( int i = 0; i < m_valueBuffer.length(); i++ ) { nvalues[i] = minValue() + ( range() * values[i] ); } break; - case Logarithmic: + case ScaleType::Logarithmic: for( int i = 0; i < m_valueBuffer.length(); i++ ) { nvalues[i] = logToLinearScale( values[i] ); @@ -649,12 +650,12 @@ ValueBuffer * AutomatableModel::valueBuffer() AutomatableModel* lm = nullptr; if (hasLinkedModels()) { - lm = m_linkedModels.first(); + lm = m_linkedModels.front(); } 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++) @@ -721,8 +722,8 @@ void AutomatableModel::reset() float AutomatableModel::globalAutomationValueAt( const TimePos& time ) { // get clips that connect to this model - QVector clips = AutomationClip::clipsForModel( this ); - if( clips.isEmpty() ) + auto clips = AutomationClip::clipsForModel(this); + if (clips.empty()) { // if no such clips exist, return current value return m_value; @@ -731,17 +732,17 @@ float AutomatableModel::globalAutomationValueAt( const TimePos& time ) { // of those clips: // find the clips which overlap with the time position - QVector clipsInRange; + std::vector clipsInRange; for (const auto& clip : clips) { int s = clip->startPosition(); int e = clip->endPosition(); - if (s <= time && e >= time) { clipsInRange += clip; } + if (s <= time && e >= time) { clipsInRange.push_back(clip); } } AutomationClip * latestClip = nullptr; - if( ! clipsInRange.isEmpty() ) + if (!clipsInRange.empty()) { // if there are more than one overlapping clips, just use the first one because // multiple clip behaviour is undefined anyway @@ -792,7 +793,7 @@ void AutomatableModel::setUseControllerValue(bool b) float FloatModel::getRoundedValue() const { - return qRound( value() / step() ) * step(); + return std::round(value() / step()) * step(); } diff --git a/src/core/AutomationClip.cpp b/src/core/AutomationClip.cpp index fb2d7dbb0..0e4271822 100644 --- a/src/core/AutomationClip.cpp +++ b/src/core/AutomationClip.cpp @@ -53,7 +53,7 @@ AutomationClip::AutomationClip( AutomationTrack * _auto_track ) : m_autoTrack( _auto_track ), m_objects(), m_tension( 1.0 ), - m_progressionType( DiscreteProgression ), + m_progressionType( ProgressionType::Discrete ), m_dragging( false ), m_isRecording( false ), m_lastRecordedValue( 0 ) @@ -63,11 +63,11 @@ AutomationClip::AutomationClip( AutomationTrack * _auto_track ) : { switch( getTrack()->trackContainer()->type() ) { - case TrackContainer::PatternContainer: + case TrackContainer::Type::Pattern: setAutoResize( true ); break; - case TrackContainer::SongContainer: + case TrackContainer::Type::Song: // move down default: setAutoResize( false ); @@ -104,11 +104,11 @@ AutomationClip::AutomationClip( const AutomationClip & _clip_to_copy ) : if (!getTrack()){ return; } switch( getTrack()->trackContainer()->type() ) { - case TrackContainer::PatternContainer: + case TrackContainer::Type::Pattern: setAutoResize( true ); break; - case TrackContainer::SongContainer: + case TrackContainer::Type::Song: // move down default: setAutoResize( false ); @@ -120,19 +120,19 @@ bool AutomationClip::addObject( AutomatableModel * _obj, bool _search_dup ) { QMutexLocker m(&m_clipMutex); - if( _search_dup && m_objects.contains(_obj) ) + if (_search_dup && std::find(m_objects.begin(), m_objects.end(), _obj) != m_objects.end()) { return false; } // the automation track is unconnected and there is nothing in the track - if( m_objects.isEmpty() && hasAutomation() == false ) + if (m_objects.empty() && hasAutomation() == false) { // then initialize first value putValue( TimePos(0), _obj->inverseScaledValue( _obj->value() ), false ); } - m_objects += _obj; + m_objects.push_back(_obj); connect( _obj, SIGNAL(destroyed(lmms::jo_id_t)), this, SLOT(objectDestroyed(lmms::jo_id_t)), @@ -147,13 +147,13 @@ bool AutomationClip::addObject( AutomatableModel * _obj, bool _search_dup ) void AutomationClip::setProgressionType( - ProgressionTypes _new_progression_type ) + ProgressionType _new_progression_type ) { QMutexLocker m(&m_clipMutex); - if ( _new_progression_type == DiscreteProgression || - _new_progression_type == LinearProgression || - _new_progression_type == CubicHermiteProgression ) + if ( _new_progression_type == ProgressionType::Discrete || + _new_progression_type == ProgressionType::Linear || + _new_progression_type == ProgressionType::CubicHermite ) { m_progressionType = _new_progression_type; emit dataChanged(); @@ -184,7 +184,7 @@ const AutomatableModel * AutomationClip::firstObject() const QMutexLocker m(&m_clipMutex); AutomatableModel* model; - if (!m_objects.isEmpty() && (model = m_objects.first()) != nullptr) + if (!m_objects.empty() && (model = m_objects.front()) != nullptr) { return model; } @@ -225,7 +225,7 @@ TimePos AutomationClip::timeMapLength() const void AutomationClip::updateLength() { // Do not resize down in case user manually extended up - changeLength(qMax(length(), timeMapLength())); + changeLength(std::max(length(), timeMapLength())); } @@ -374,17 +374,17 @@ void AutomationClip::removeNodes(const int tick0, const int tick1) return; } - auto start = TimePos(qMin(tick0, tick1)); - auto end = TimePos(qMax(tick0, tick1)); + auto start = TimePos(std::min(tick0, tick1)); + auto end = TimePos(std::max(tick0, tick1)); // Make a list of TimePos with nodes to be removed // because we can't simply remove the nodes from // the timeMap while we are iterating it. - QVector nodesToRemove; + std::vector nodesToRemove; for (auto it = m_timeMap.lowerBound(start), endIt = m_timeMap.upperBound(end); it != endIt; ++it) { - nodesToRemove.append(POS(it)); + nodesToRemove.push_back(POS(it)); } for (auto node: nodesToRemove) @@ -410,8 +410,8 @@ void AutomationClip::resetNodes(const int tick0, const int tick1) return; } - auto start = TimePos(qMin(tick0, tick1)); - auto end = TimePos(qMax(tick0, tick1)); + auto start = TimePos(std::min(tick0, tick1)); + auto end = TimePos(std::max(tick0, tick1)); for (auto it = m_timeMap.lowerBound(start), endIt = m_timeMap.upperBound(end); it != endIt; ++it) { @@ -422,6 +422,32 @@ void AutomationClip::resetNodes(const int tick0, const int tick1) +void AutomationClip::resetTangents(const int tick0, const int tick1) +{ + if (tick0 == tick1) + { + auto it = m_timeMap.find(TimePos(tick0)); + if (it != m_timeMap.end()) + { + it.value().setLockedTangents(false); + generateTangents(it, 1); + } + return; + } + + TimePos start = TimePos(std::min(tick0, tick1)); + TimePos end = TimePos(std::max(tick0, tick1)); + + for (auto it = m_timeMap.lowerBound(start), endIt = m_timeMap.upperBound(end); it != endIt; ++it) + { + it.value().setLockedTangents(false); + generateTangents(it, 1); + } +} + + + + void AutomationClip::recordValue(TimePos time, float value) { QMutexLocker m(&m_clipMutex); @@ -467,16 +493,31 @@ TimePos AutomationClip::setDragValue( // inValue m_dragKeepOutValue = false; + // We will set the tangents back to what they were if the node had + // its tangents locked + m_dragLockedTan = false; + // Check if we already have a node on the position we are dragging // and if we do, store the outValue so the discrete jump can be kept + // and information about the tangents timeMap::iterator it = m_timeMap.find(newTime); if (it != m_timeMap.end()) { + // If we don't have a discrete jump, the outValue will be the + // same as the inValue if (OFFSET(it) != 0) { m_dragKeepOutValue = true; m_dragOutValue = OUTVAL(it); } + // For the tangents, we will only keep them if the tangents were + // locked + if (LOCKEDTAN(it)) + { + m_dragLockedTan = true; + m_dragInTan = INTAN(it); + m_dragOutTan = OUTTAN(it); + } } this->removeNode(newTime); @@ -489,12 +530,31 @@ TimePos AutomationClip::setDragValue( generateTangents(); + TimePos returnedPos; + if (m_dragKeepOutValue) { - return this->putValues(time, value, m_dragOutValue, quantPos, controlKey); + returnedPos = this->putValues(time, value, m_dragOutValue, quantPos, controlKey); + } + else + { + returnedPos = this->putValue(time, value, quantPos, controlKey); } - return this->putValue(time, value, quantPos, controlKey); + // Set the tangents on the newly created node if they were locked + // before dragging + if (m_dragLockedTan) + { + timeMap::iterator it = m_timeMap.find(returnedPos); + if (it != m_timeMap.end()) + { + it.value().setInTangent(m_dragInTan); + it.value().setOutTangent(m_dragOutTan); + it.value().setLockedTangents(true); + } + } + + return returnedPos; } @@ -560,11 +620,11 @@ float AutomationClip::valueAt( timeMap::const_iterator v, int offset ) const // value if we do if (offset == 0) { return INVAL(v); } - if (m_progressionType == DiscreteProgression) + if (m_progressionType == ProgressionType::Discrete) { return OUTVAL(v); } - else if( m_progressionType == LinearProgression ) + else if( m_progressionType == ProgressionType::Linear ) { float slope = (INVAL(v + 1) - OUTVAL(v)) @@ -572,7 +632,7 @@ float AutomationClip::valueAt( timeMap::const_iterator v, int offset ) const return OUTVAL(v) + offset * slope; } - else /* CubicHermiteProgression */ + else /* ProgressionType::CubicHermite */ { // Implements a Cubic Hermite spline as explained at: // http://en.wikipedia.org/wiki/Cubic_Hermite_spline#Unit_interval_.280.2C_1.29 @@ -767,13 +827,13 @@ void AutomationClip::saveSettings( QDomDocument & _doc, QDomElement & _this ) _this.setAttribute( "pos", startPosition() ); _this.setAttribute( "len", length() ); _this.setAttribute( "name", name() ); - _this.setAttribute( "prog", QString::number( progressionType() ) ); + _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(); @@ -783,6 +843,9 @@ void AutomationClip::saveSettings( QDomDocument & _doc, QDomElement & _this ) element.setAttribute("pos", POS(it)); element.setAttribute("value", INVAL(it)); element.setAttribute("outValue", OUTVAL(it)); + element.setAttribute("inTan", INTAN(it)); + element.setAttribute("outTan", OUTTAN(it)); + element.setAttribute("lockedTan", static_cast(LOCKEDTAN(it))); _this.appendChild( element ); } @@ -804,11 +867,16 @@ void AutomationClip::loadSettings( const QDomElement & _this ) { QMutexLocker m(&m_clipMutex); + // Legacy compatibility: Previously tangents were not stored in + // the project file. So if any node doesn't have tangent information + // we will generate the tangents + bool shouldGenerateTangents = false; + clear(); movePosition( _this.attribute( "pos" ).toInt() ); setName( _this.attribute( "name" ) ); - setProgressionType( static_cast( _this.attribute( + setProgressionType( static_cast( _this.attribute( "prog" ).toInt() ) ); setTension( _this.attribute( "tens" ) ); setMuted(_this.attribute( "mute", QString::number( false ) ).toInt() ); @@ -828,17 +896,32 @@ void AutomationClip::loadSettings( const QDomElement & _this ) float timeMapOutValue = LocaleHelper::toFloat(element.attribute("outValue")); m_timeMap[timeMapPos] = AutomationNode(this, timeMapInValue, timeMapOutValue, timeMapPos); + + // Load tangents if there is information about it (it's enough to check for either inTan or outTan) + if (element.hasAttribute("inTan")) + { + float inTan = LocaleHelper::toFloat(element.attribute("inTan")); + float outTan = LocaleHelper::toFloat(element.attribute("outTan")); + bool lockedTan = static_cast(element.attribute("lockedTan", "0").toInt()); + + m_timeMap[timeMapPos].setInTangent(inTan); + m_timeMap[timeMapPos].setOutTangent(outTan); + m_timeMap[timeMapPos].setLockedTangents(lockedTan); + } + else + { + shouldGenerateTangents = true; + } } else if( element.tagName() == "object" ) { - m_idsToResolve << element.attribute( "id" ).toInt(); + m_idsToResolve.push_back(element.attribute("id").toInt()); } } - 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(); @@ -851,13 +934,14 @@ void AutomationClip::loadSettings( const QDomElement & _this ) { changeLength( len ); } - generateTangents(); + + if (shouldGenerateTangents) { generateTangents(); } } -const QString AutomationClip::name() const +QString AutomationClip::name() const { QMutexLocker m(&m_clipMutex); @@ -865,9 +949,9 @@ const QString AutomationClip::name() const { return Clip::name(); } - if( !m_objects.isEmpty() && m_objects.first() != nullptr ) + if (!m_objects.empty() && m_objects.front() != nullptr) { - return m_objects.first()->fullDisplayName(); + return m_objects.front()->fullDisplayName(); } return tr( "Drag a control while pressing <%1>" ).arg(UI_CTRL_KEY); } @@ -888,14 +972,10 @@ gui::ClipView * AutomationClip::createView( gui::TrackView * _tv ) bool AutomationClip::isAutomated( const AutomatableModel * _m ) { - TrackContainer::TrackList l; - l += Engine::getSong()->tracks(); - l += Engine::patternStore()->tracks(); - l += Engine::getSong()->globalAutomationTrack(); - - for (const auto& track : l) + auto l = combineAllTracks(); + for (const auto track : l) { - if (track->type() == Track::AutomationTrack || track->type() == Track::HiddenAutomationTrack) + if (track->type() == Track::Type::Automation || track->type() == Track::Type::HiddenAutomation) { for (const auto& clip : track->getClips()) { @@ -921,19 +1001,16 @@ bool AutomationClip::isAutomated( const AutomatableModel * _m ) * @brief returns a list of all the automation clips that are connected to a specific model * @param _m the model we want to look for */ -QVector AutomationClip::clipsForModel( const AutomatableModel * _m ) +std::vector AutomationClip::clipsForModel(const AutomatableModel* _m) { - QVector clips; - TrackContainer::TrackList tracks; - tracks += Engine::getSong()->tracks(); - tracks += Engine::patternStore()->tracks(); - tracks += Engine::getSong()->globalAutomationTrack(); + std::vector clips; + auto l = combineAllTracks(); // go through all tracks... - for (const auto& track : tracks) + for (const auto track : l) { // we want only automation tracks... - if (track->type() == Track::AutomationTrack || track->type() == Track::HiddenAutomationTrack ) + if (track->type() == Track::Type::Automation || track->type() == Track::Type::HiddenAutomation ) { // go through all the clips... for (const auto& trackClip : track->getClips()) @@ -953,7 +1030,7 @@ QVector AutomationClip::clipsForModel( const AutomatableModel } } // if the clips is connected to the model, add it to the list - if( has_object ) { clips += a; } + if (has_object) { clips.push_back(a); } } } } @@ -989,12 +1066,10 @@ AutomationClip * AutomationClip::globalAutomationClip( void AutomationClip::resolveAllIDs() { - TrackContainer::TrackList l = Engine::getSong()->tracks() + - Engine::patternStore()->tracks(); - l += Engine::getSong()->globalAutomationTrack(); + auto l = combineAllTracks(); for (const auto& track : l) { - if (track->type() == Track::AutomationTrack || track->type() == Track::HiddenAutomationTrack) + if (track->type() == Track::Type::Automation || track->type() == Track::Type::HiddenAutomation) { for (const auto& clip : track->getClips()) { @@ -1060,10 +1135,9 @@ void AutomationClip::objectDestroyed( jo_id_t _id ) // when switching samplerate) and real deletions because in the latter // case we had to remove ourselves if we're the global automation // clip of the destroyed object - m_idsToResolve += _id; + m_idsToResolve.push_back(_id); - for( objectVector::Iterator objIt = m_objects.begin(); - objIt != m_objects.end(); objIt++ ) + for (auto objIt = m_objects.begin(); objIt != m_objects.end(); objIt++) { Q_ASSERT( !(*objIt).isNull() ); if( (*objIt)->id() == _id ) @@ -1116,16 +1190,22 @@ void AutomationClip::generateTangents(timeMap::iterator it, int numToGenerate) { QMutexLocker m(&m_clipMutex); - if( m_timeMap.size() < 2 && numToGenerate > 0 ) + for (int i = 0; i < numToGenerate && it != m_timeMap.end(); ++i, ++it) { - it.value().setInTangent(0); - it.value().setOutTangent(0); - return; - } + // Skip the node if it has locked tangents (were manually edited) + if (LOCKEDTAN(it)) + { + continue; + } - for( int i = 0; i < numToGenerate; i++ ) - { - if( it == m_timeMap.begin() ) + if (it + 1 == m_timeMap.end()) + { + // Previously, the last value's tangent was always set to 0. That logic was kept for both tangents + // of the last node + it.value().setInTangent(0); + it.value().setOutTangent(0); + } + else if (it == m_timeMap.begin()) { // On the first node there's no curve behind it, so we will only calculate the outTangent // and inTangent will be set to 0. @@ -1133,14 +1213,6 @@ void AutomationClip::generateTangents(timeMap::iterator it, int numToGenerate) it.value().setInTangent(0); it.value().setOutTangent(tangent); } - else if( it+1 == m_timeMap.end() ) - { - // Previously, the last value's tangent was always set to 0. That logic was kept for both tangents - // of the last node - it.value().setInTangent(0); - it.value().setOutTangent(0); - return; - } else { // When we are in a node that is in the middle of two other nodes, we need to check if we @@ -1150,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); @@ -1162,15 +1232,28 @@ 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); } } - it++; } } -} // namespace lmms \ No newline at end of file +std::vector AutomationClip::combineAllTracks() +{ + std::vector combinedTrackList; + + auto& songTracks = Engine::getSong()->tracks(); + auto& patternStoreTracks = Engine::patternStore()->tracks(); + + combinedTrackList.insert(combinedTrackList.end(), songTracks.begin(), songTracks.end()); + combinedTrackList.insert(combinedTrackList.end(), patternStoreTracks.begin(), patternStoreTracks.end()); + combinedTrackList.push_back(Engine::getSong()->globalAutomationTrack()); + + return combinedTrackList; +} + +} // namespace lmms diff --git a/src/core/AutomationNode.cpp b/src/core/AutomationNode.cpp index eee4df8d2..f15c28f80 100644 --- a/src/core/AutomationNode.cpp +++ b/src/core/AutomationNode.cpp @@ -37,7 +37,8 @@ AutomationNode::AutomationNode() : m_inValue(0), m_outValue(0), m_inTangent(0), - m_outTangent(0) + m_outTangent(0), + m_lockedTangents(false) { } @@ -47,7 +48,8 @@ AutomationNode::AutomationNode(AutomationClip* clip, float value, int pos) : m_inValue(value), m_outValue(value), m_inTangent(0), - m_outTangent(0) + m_outTangent(0), + m_lockedTangents(false) { } @@ -57,7 +59,8 @@ AutomationNode::AutomationNode(AutomationClip* clip, float inValue, float outVal m_inValue(inValue), m_outValue(outValue), m_inTangent(0), - m_outTangent(0) + m_outTangent(0), + m_lockedTangents(false) { } diff --git a/src/core/BandLimitedWave.cpp b/src/core/BandLimitedWave.cpp index a7dd650c0..060ff510a 100644 --- a/src/core/BandLimitedWave.cpp +++ b/src/core/BandLimitedWave.cpp @@ -30,7 +30,7 @@ namespace lmms { -std::array BandLimitedWave::s_waveforms = { }; +std::array BandLimitedWave::s_waveforms = { }; bool BandLimitedWave::s_wavesGenerated = false; QString BandLimitedWave::s_wavetableDir = ""; @@ -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 @@ -84,12 +83,12 @@ void BandLimitedWave::generateWaves() { saw_file.open( QIODevice::ReadOnly ); QDataStream in( &saw_file ); - in >> s_waveforms[ BandLimitedWave::BLSaw ]; + in >> s_waveforms[static_cast(BandLimitedWave::Waveform::BLSaw)]; saw_file.close(); } 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; @@ -108,14 +107,14 @@ void BandLimitedWave::generateWaves() s += amp * /*a2 **/sin( static_cast( ph * harm ) / static_cast( len ) * F_2PI ); harm++; } while( hlen > 2.0 ); - s_waveforms[ BandLimitedWave::BLSaw ].setSampleAt( i, ph, s ); - max = qMax( max, qAbs( s ) ); + s_waveforms[static_cast(BandLimitedWave::Waveform::BLSaw)].setSampleAt( i, ph, s ); + max = std::max(max, std::abs(s)); } // normalize for( int ph = 0; ph < len; ph++ ) { - sample_t s = s_waveforms[ BandLimitedWave::BLSaw ].sampleAt( i, ph ) / max; - s_waveforms[ BandLimitedWave::BLSaw ].setSampleAt( i, ph, s ); + sample_t s = s_waveforms[static_cast(BandLimitedWave::Waveform::BLSaw)].sampleAt( i, ph ) / max; + s_waveforms[static_cast(BandLimitedWave::Waveform::BLSaw)].setSampleAt( i, ph, s ); } } } @@ -126,12 +125,12 @@ void BandLimitedWave::generateWaves() { sqr_file.open( QIODevice::ReadOnly ); QDataStream in( &sqr_file ); - in >> s_waveforms[ BandLimitedWave::BLSquare ]; + in >> s_waveforms[static_cast(BandLimitedWave::Waveform::BLSquare)]; sqr_file.close(); } 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; @@ -150,14 +149,14 @@ void BandLimitedWave::generateWaves() s += amp * /*a2 **/ sin( static_cast( ph * harm ) / static_cast( len ) * F_2PI ); harm += 2; } while( hlen > 2.0 ); - s_waveforms[ BandLimitedWave::BLSquare ].setSampleAt( i, ph, s ); - max = qMax( max, qAbs( s ) ); + s_waveforms[static_cast(BandLimitedWave::Waveform::BLSquare)].setSampleAt( i, ph, s ); + max = std::max(max, std::abs(s)); } // normalize for( int ph = 0; ph < len; ph++ ) { - sample_t s = s_waveforms[ BandLimitedWave::BLSquare ].sampleAt( i, ph ) / max; - s_waveforms[ BandLimitedWave::BLSquare ].setSampleAt( i, ph, s ); + sample_t s = s_waveforms[static_cast(BandLimitedWave::Waveform::BLSquare)].sampleAt( i, ph ) / max; + s_waveforms[static_cast(BandLimitedWave::Waveform::BLSquare)].setSampleAt( i, ph, s ); } } } @@ -167,12 +166,12 @@ void BandLimitedWave::generateWaves() { tri_file.open( QIODevice::ReadOnly ); QDataStream in( &tri_file ); - in >> s_waveforms[ BandLimitedWave::BLTriangle ]; + in >> s_waveforms[static_cast(BandLimitedWave::Waveform::BLTriangle)]; tri_file.close(); } 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; @@ -192,14 +191,14 @@ void BandLimitedWave::generateWaves() ( ( harm + 1 ) % 4 == 0 ? 0.5 : 0.0 ) ) * F_2PI ); harm += 2; } while( hlen > 2.0 ); - s_waveforms[ BandLimitedWave::BLTriangle ].setSampleAt( i, ph, s ); - max = qMax( max, qAbs( s ) ); + s_waveforms[static_cast(BandLimitedWave::Waveform::BLTriangle)].setSampleAt( i, ph, s ); + max = std::max(max, std::abs(s)); } // normalize for( int ph = 0; ph < len; ph++ ) { - sample_t s = s_waveforms[ BandLimitedWave::BLTriangle ].sampleAt( i, ph ) / max; - s_waveforms[ BandLimitedWave::BLTriangle ].setSampleAt( i, ph, s ); + sample_t s = s_waveforms[static_cast(BandLimitedWave::Waveform::BLTriangle)].sampleAt( i, ph ) / max; + s_waveforms[static_cast(BandLimitedWave::Waveform::BLTriangle)].setSampleAt( i, ph, s ); } } } @@ -210,21 +209,21 @@ void BandLimitedWave::generateWaves() { moog_file.open( QIODevice::ReadOnly ); QDataStream in( &moog_file ); - in >> s_waveforms[ BandLimitedWave::BLMoog ]; + in >> s_waveforms[static_cast(BandLimitedWave::Waveform::BLMoog)]; moog_file.close(); } else { - for( i = 0; i <= MAXTBL; i++ ) + for (int i = 0; i <= MAXTBL; i++) { const int len = TLENS[i]; for( int ph = 0; ph < len; ph++ ) { const int sawph = ( ph + static_cast( len * 0.75 ) ) % len; - const sample_t saw = s_waveforms[ BandLimitedWave::BLSaw ].sampleAt( i, sawph ); - const sample_t tri = s_waveforms[ BandLimitedWave::BLTriangle ].sampleAt( i, ph ); - s_waveforms[ BandLimitedWave::BLMoog ].setSampleAt( i, ph, ( saw + tri ) * 0.5f ); + const sample_t saw = s_waveforms[static_cast(BandLimitedWave::Waveform::BLSaw)].sampleAt( i, sawph ); + const sample_t tri = s_waveforms[static_cast(BandLimitedWave::Waveform::BLTriangle)].sampleAt( i, ph ); + s_waveforms[static_cast(BandLimitedWave::Waveform::BLMoog)].setSampleAt( i, ph, ( saw + tri ) * 0.5f ); } } } @@ -252,22 +251,22 @@ QFile moogfile( "path-to-wavetables/moog.bin" ); sawfile.open( QIODevice::WriteOnly ); QDataStream sawout( &sawfile ); -sawout << s_waveforms[ BandLimitedWave::BLSaw ]; +sawout << s_waveforms[static_cast(BandLimitedWave::Waveform::BLSaw)]; sawfile.close(); sqrfile.open( QIODevice::WriteOnly ); QDataStream sqrout( &sqrfile ); -sqrout << s_waveforms[ BandLimitedWave::BLSquare ]; +sqrout << s_waveforms[static_cast(BandLimitedWave::Waveform::BLSquare)]; sqrfile.close(); trifile.open( QIODevice::WriteOnly ); QDataStream triout( &trifile ); -triout << s_waveforms[ BandLimitedWave::BLTriangle ]; +triout << s_waveforms[static_cast(BandLimitedWave::Waveform::BLTriangle)]; trifile.close(); moogfile.open( QIODevice::WriteOnly ); QDataStream moogout( &moogfile ); -moogout << s_waveforms[ BandLimitedWave::BLMoog ]; +moogout << s_waveforms[static_cast(BandLimitedWave::Waveform::BLMoog)]; moogfile.close(); */ 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 d8b7e9784..5a0dd24b1 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 @@ -43,7 +44,6 @@ set(LMMS_SRCS core/LinkedModelGroups.cpp core/LocklessAllocator.cpp core/MemoryHelper.cpp - core/MemoryManager.cpp core/MeterModel.cpp core/MidiSwing.cpp core/MicroTimer.cpp @@ -71,18 +71,24 @@ 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 + core/LmmsSemaphore.cpp core/SerializingObject.cpp core/Song.cpp core/TempoSyncKnobModel.cpp + core/Timeline.cpp core/TimePos.cpp core/ToolPlugin.cpp core/Track.cpp core/TrackContainer.cpp + core/UpgradeExtendedNoteRange.h + core/UpgradeExtendedNoteRange.cpp core/Clip.cpp core/ValueBuffer.cpp core/VstSyncController.cpp @@ -116,6 +122,7 @@ set(LMMS_SRCS core/lv2/Lv2SubPluginFeatures.cpp core/lv2/Lv2UridCache.cpp core/lv2/Lv2UridMap.cpp + core/lv2/Lv2Worker.cpp core/midi/MidiAlsaRaw.cpp core/midi/MidiAlsaSeq.cpp diff --git a/src/core/Clip.cpp b/src/core/Clip.cpp index 74a168fbd..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() ) { @@ -92,7 +90,7 @@ Clip::~Clip() */ void Clip::movePosition( const TimePos & pos ) { - TimePos newPos = qMax(0, pos.getTicks()); + TimePos newPos = std::max(0, pos.getTicks()); if (m_startPosition != newPos) { Engine::audioEngine()->requestChangeInModel(); @@ -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 bfe9e31f1..61d84770a 100644 --- a/src/core/ConfigManager.cpp +++ b/src/core/ConfigManager.cpp @@ -77,9 +77,9 @@ ConfigManager::ConfigManager() : m_sf2Dir = m_workingDir + SF2_PATH; m_gigDir = m_workingDir + GIG_PATH; m_themeDir = defaultThemeDir(); - if (!qgetenv("LMMS_DATA_DIR").isEmpty()) + if (std::getenv("LMMS_DATA_DIR")) { - QDir::addSearchPath("data", QString::fromLocal8Bit(qgetenv("LMMS_DATA_DIR"))); + QDir::addSearchPath("data", QString::fromLocal8Bit(std::getenv("LMMS_DATA_DIR"))); } initDevelopmentWorkingDir(); @@ -173,7 +173,7 @@ void ConfigManager::upgrade() ProjectVersion createdWith = m_version; // Don't use old themes as they break the UI (i.e. 0.4 != 1.0, etc) - if (createdWith.setCompareType(ProjectVersion::Minor) != LMMS_VERSION) + if (createdWith.setCompareType(ProjectVersion::CompareType::Minor) != LMMS_VERSION) { m_themeDir = defaultThemeDir(); } @@ -334,10 +334,9 @@ void ConfigManager::addRecentlyOpenedProject(const QString & file) -const QString & ConfigManager::value(const QString & cls, - const QString & attribute) const +QString ConfigManager::value(const QString& cls, const QString& attribute, const QString& defaultVal) const { - if(m_settings.contains(cls)) + if (m_settings.find(cls) != m_settings.end()) { for (const auto& setting : m_settings[cls]) { @@ -347,18 +346,7 @@ const QString & ConfigManager::value(const QString & cls, } } } - static QString empty; - return empty; -} - - - -const QString & ConfigManager::value(const QString & cls, - const QString & attribute, - const QString & defaultVal) const -{ - const QString & val = value(cls, attribute); - return val.isEmpty() ? defaultVal : val; + return defaultVal; } @@ -569,8 +557,8 @@ void ConfigManager::loadConfigFile(const QString & configFile) upgrade(); QStringList searchPaths; - if(! qgetenv("LMMS_THEME_PATH").isNull()) - searchPaths << qgetenv("LMMS_THEME_PATH"); + if (std::getenv("LMMS_THEME_PATH")) + searchPaths << std::getenv("LMMS_THEME_PATH"); searchPaths << themeDir() << defaultThemeDir(); QDir::setSearchPaths("resources", searchPaths); @@ -719,7 +707,7 @@ unsigned int ConfigManager::legacyConfigVersion() { ProjectVersion createdWith = m_version; - createdWith.setCompareType(ProjectVersion::Build); + createdWith.setCompareType(ProjectVersion::CompareType::Build); if( createdWith < "1.1.90" ) { diff --git a/src/core/Controller.cpp b/src/core/Controller.cpp index aee6481cc..81fc13c86 100644 --- a/src/core/Controller.cpp +++ b/src/core/Controller.cpp @@ -25,8 +25,8 @@ */ #include -#include +#include #include "AudioEngine.h" #include "ControllerConnection.h" @@ -40,11 +40,11 @@ namespace lmms long Controller::s_periods = 0; -QVector Controller::s_controllers; +std::vector Controller::s_controllers; -Controller::Controller( ControllerTypes _type, Model * _parent, +Controller::Controller( ControllerType _type, Model * _parent, const QString & _display_name ) : Model( _parent, _display_name ), JournallingObject(), @@ -53,9 +53,9 @@ Controller::Controller( ControllerTypes _type, Model * _parent, m_connectionCount( 0 ), m_type( _type ) { - if( _type != DummyController && _type != MidiController ) + if( _type != ControllerType::Dummy && _type != ControllerType::Midi ) { - s_controllers.append( this ); + s_controllers.push_back(this); // Determine which name to use for ( uint i=s_controllers.size(); ; i++ ) { @@ -86,10 +86,10 @@ Controller::Controller( ControllerTypes _type, Model * _parent, Controller::~Controller() { - int idx = s_controllers.indexOf( this ); - if( idx >= 0 ) + auto it = std::find(s_controllers.begin(), s_controllers.end(), this); + if (it != s_controllers.end()) { - s_controllers.remove( idx ); + s_controllers.erase(it); } m_valueBuffer.clear(); @@ -182,30 +182,30 @@ void Controller::resetFrameCounter() -Controller * Controller::create( ControllerTypes _ct, Model * _parent ) +Controller * Controller::create( ControllerType _ct, Model * _parent ) { static Controller * dummy = nullptr; Controller * c = nullptr; switch( _ct ) { - case Controller::DummyController: + case ControllerType::Dummy: if (!dummy) - dummy = new Controller( DummyController, nullptr, + dummy = new Controller( ControllerType::Dummy, nullptr, QString() ); c = dummy; break; - case Controller::LfoController: + case ControllerType::Lfo: c = new class LfoController( _parent ); break; - case Controller::PeakController: + case ControllerType::Peak: //Already instantiated in EffectChain::loadSettings() Q_ASSERT( false ); break; - case Controller::MidiController: + case ControllerType::Midi: c = new class MidiController( _parent ); break; @@ -220,24 +220,12 @@ Controller * Controller::create( ControllerTypes _ct, Model * _parent ) Controller * Controller::create( const QDomElement & _this, Model * _parent ) { - Controller * c; - if( _this.attribute( "type" ).toInt() == Controller::PeakController ) - { - 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; } @@ -269,7 +257,7 @@ bool Controller::hasModel( const Model * m ) const void Controller::saveSettings( QDomDocument & _doc, QDomElement & _this ) { - _this.setAttribute( "type", type() ); + _this.setAttribute( "type", static_cast(type()) ); _this.setAttribute( "name", name() ); } @@ -277,7 +265,7 @@ void Controller::saveSettings( QDomDocument & _doc, QDomElement & _this ) void Controller::loadSettings( const QDomElement & _this ) { - if( _this.attribute( "type" ).toInt() != type() ) + if( static_cast(_this.attribute( "type" ).toInt()) != type() ) { qWarning( "controller-type does not match controller-type of " "settings-node!\n" ); diff --git a/src/core/ControllerConnection.cpp b/src/core/ControllerConnection.cpp index c65cd8ae9..fea907942 100644 --- a/src/core/ControllerConnection.cpp +++ b/src/core/ControllerConnection.cpp @@ -50,21 +50,21 @@ ControllerConnection::ControllerConnection(Controller * _controller) : } else { - m_controller = Controller::create( Controller::DummyController, + m_controller = Controller::create( Controller::ControllerType::Dummy, nullptr ); } - s_connections.append( this ); + s_connections.push_back(this); } ControllerConnection::ControllerConnection( int _controllerId ) : - m_controller( Controller::create( Controller::DummyController, nullptr ) ), + m_controller( Controller::create( Controller::ControllerType::Dummy, nullptr ) ), m_controllerId( _controllerId ), m_ownsController( false ) { - s_connections.append( this ); + s_connections.push_back(this); } @@ -72,11 +72,14 @@ ControllerConnection::ControllerConnection( int _controllerId ) : ControllerConnection::~ControllerConnection() { - if( m_controller && m_controller->type() != Controller::DummyController ) + if( m_controller && m_controller->type() != Controller::ControllerType::Dummy ) { m_controller->removeConnection( this ); } - s_connections.remove( s_connections.indexOf( this ) ); + + auto it = std::find(s_connections.begin(), s_connections.end(), this); + if (it != s_connections.end()) { s_connections.erase(it); }; + if( m_ownsController ) { delete m_controller; @@ -101,14 +104,14 @@ void ControllerConnection::setController( Controller * _controller ) m_controller = nullptr; } - if( m_controller && m_controller->type() != Controller::DummyController ) + if( m_controller && m_controller->type() != Controller::ControllerType::Dummy ) { m_controller->removeConnection( this ); } if( !_controller ) { - m_controller = Controller::create( Controller::DummyController, nullptr ); + m_controller = Controller::create( Controller::ControllerType::Dummy, nullptr ); } else { @@ -116,7 +119,7 @@ void ControllerConnection::setController( Controller * _controller ) } m_controllerId = -1; - if( _controller->type() != Controller::DummyController ) + if( _controller->type() != Controller::ControllerType::Dummy ) { _controller->addConnection( this ); QObject::connect( _controller, SIGNAL(valueChanged()), @@ -124,7 +127,7 @@ void ControllerConnection::setController( Controller * _controller ) } m_ownsController = - (_controller->type() == Controller::MidiController); + (_controller->type() == Controller::ControllerType::Midi); // If we don't own the controller, allow deletion of controller // to delete the connection @@ -165,7 +168,7 @@ void ControllerConnection::finalizeConnections() c->setController( Engine::getSong()-> controllers().at( c->m_controllerId ) ); } - else if (c->getController()->type() == Controller::DummyController) + else if (c->getController()->type() == Controller::ControllerType::Dummy) { delete c; --i; @@ -186,10 +189,12 @@ void ControllerConnection::saveSettings( QDomDocument & _doc, QDomElement & _thi } else { - int id = Engine::getSong()->controllers().indexOf( m_controller ); - if( id >= 0 ) + const auto& controllers = Engine::getSong()->controllers(); + const auto it = std::find(controllers.begin(), controllers.end(), m_controller); + if (it != controllers.end()) { - _this.setAttribute( "id", id ); + const int id = std::distance(controllers.begin(), it); + _this.setAttribute("id", id); } } } @@ -223,7 +228,7 @@ void ControllerConnection::loadSettings( const QDomElement & _this ) } else { - m_controller = Controller::create( Controller::DummyController, nullptr ); + m_controller = Controller::create( Controller::ControllerType::Dummy, nullptr ); } } } diff --git a/src/core/DataFile.cpp b/src/core/DataFile.cpp index 2e7b21e8b..00c6845f3 100644 --- a/src/core/DataFile.cpp +++ b/src/core/DataFile.cpp @@ -26,6 +26,7 @@ #include "DataFile.h" +#include #include #include @@ -34,6 +35,8 @@ #include #include #include +#include +#include #include "base64.h" #include "ConfigManager.h" @@ -41,12 +44,14 @@ #include "embed.h" #include "GuiApplication.h" #include "LocaleHelper.h" +#include "Note.h" #include "PluginFactory.h" #include "ProjectVersion.h" #include "SongEditor.h" #include "TextFloat.h" #include "Track.h" #include "PathUtil.h" +#include "UpgradeExtendedNoteRange.h" #include "lmmsversion.h" @@ -77,6 +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_loopsRename , &DataFile::upgrade_noteTypes, + &DataFile::upgrade_fixCMTDelays }; // Vector of all versions that have upgrade routines. @@ -97,17 +105,16 @@ namespace QString m_name; }; - const auto s_types = std::array - { - TypeDescStruct{ DataFile::UnknownType, "unknown" }, - TypeDescStruct{ DataFile::SongProject, "song" }, - TypeDescStruct{ DataFile::SongProjectTemplate, "songtemplate" }, - TypeDescStruct{ DataFile::InstrumentTrackSettings, "instrumenttracksettings" }, - TypeDescStruct{ DataFile::DragNDropData, "dnddata" }, - TypeDescStruct{ DataFile::ClipboardData, "clipboard-data" }, - TypeDescStruct{ DataFile::JournalData, "journaldata" }, - TypeDescStruct{ DataFile::EffectSettings, "effectsettings" }, - TypeDescStruct{ DataFile::MidiClip, "midiclip" } + const auto s_types = std::array{ + TypeDescStruct{ DataFile::Type::Unknown, "unknown" }, + TypeDescStruct{ DataFile::Type::SongProject, "song" }, + TypeDescStruct{ DataFile::Type::SongProjectTemplate, "songtemplate" }, + TypeDescStruct{ DataFile::Type::InstrumentTrackSettings, "instrumenttracksettings" }, + TypeDescStruct{ DataFile::Type::DragNDropData, "dnddata" }, + TypeDescStruct{ DataFile::Type::ClipboardData, "clipboard-data" }, + TypeDescStruct{ DataFile::Type::JournalData, "journaldata" }, + TypeDescStruct{ DataFile::Type::EffectSettings, "effectsettings" }, + TypeDescStruct{ DataFile::Type::MidiClip, "midiclip" } }; } @@ -213,7 +220,7 @@ bool DataFile::validate( QString extension ) return true; } break; - case Type::UnknownType: + case Type::Unknown: if (! ( extension == "mmp" || extension == "mpt" || extension == "mmpz" || extension == "xpf" || extension == "xml" || ( extension == "xiz" && ! getPluginFactory()->pluginSupportingExtension(extension).isNull()) || @@ -229,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; } @@ -250,7 +260,7 @@ QString DataFile::nameWithExtension( const QString & _fn ) const switch( type() ) { - case SongProject: + case Type::SongProject: if( extension != "mmp" && extension != "mpt" && extension != "mmpz" ) @@ -263,13 +273,13 @@ QString DataFile::nameWithExtension( const QString & _fn ) const return _fn + ".mmp"; } break; - case SongProjectTemplate: + case Type::SongProjectTemplate: if( extension != "mpt" ) { return _fn + ".mpt"; } break; - case InstrumentTrackSettings: + case Type::InstrumentTrackSettings: if( extension != "xpf" ) { return _fn + ".xpf"; @@ -285,8 +295,8 @@ QString DataFile::nameWithExtension( const QString & _fn ) const void DataFile::write( QTextStream & _strm ) { - if( type() == SongProject || type() == SongProjectTemplate - || type() == InstrumentTrackSettings ) + if( type() == Type::SongProject || type() == Type::SongProjectTemplate + || type() == Type::InstrumentTrackSettings ) { cleanMetaNodes( documentElement() ); } @@ -300,7 +310,7 @@ void DataFile::write( QTextStream & _strm ) bool DataFile::writeFile(const QString& filename, bool withResources) { // Small lambda function for displaying errors - auto showError = [this](QString title, QString body){ + auto showError = [](QString title, QString body){ if (gui::getGUI() != nullptr) { QMessageBox mb; @@ -374,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)); @@ -400,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; } @@ -584,21 +593,17 @@ bool DataFile::hasLocalPlugins(QDomElement parent /* = QDomElement()*/, bool fir DataFile::Type DataFile::type( const QString& typeName ) { - for( int i = 0; i < TypeCount; ++i ) - { - if( s_types[i].m_name == typeName ) - { - return static_cast( i ); - } - } + const auto it = std::find_if(s_types.begin(), s_types.end(), + [&typeName](const TypeDescStruct& type) { return type.m_name == typeName; }); + if (it != s_types.end()) { return it->m_type; } // compat code if( typeName == "channelsettings" ) { - return DataFile::InstrumentTrackSettings; + return Type::InstrumentTrackSettings; } - return UnknownType; + return Type::Unknown; } @@ -606,12 +611,7 @@ DataFile::Type DataFile::type( const QString& typeName ) QString DataFile::typeName( Type type ) { - if( type >= UnknownType && type < TypeCount ) - { - return s_types[type].m_name; - } - - return s_types[UnknownType].m_name; + return s_types[static_cast(type)].m_name; } @@ -975,8 +975,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 ); } } @@ -1111,7 +1110,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 ); } @@ -1196,12 +1195,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 ); } } @@ -1458,7 +1456,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") @@ -1669,6 +1667,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 * @@ -1677,74 +1731,10 @@ void DataFile::upgrade_automationNodes() */ void DataFile::upgrade_extendedNoteRange() { - auto affected = [](const QDomElement& instrument) - { - return instrument.attribute("name") == "zynaddsubfx" || - instrument.attribute("name") == "vestige" || - instrument.attribute("name") == "lv2instrument" || - instrument.attribute("name") == "carlapatchbay" || - instrument.attribute("name") == "carlarack"; - }; + auto root = documentElement(); + UpgradeExtendedNoteRange upgradeExtendedNoteRange(root); - if (!elementsByTagName("song").item(0).isNull()) - { - // Dealing with a project file, go through all the tracks - QDomNodeList tracks = elementsByTagName("track"); - for (int i = 0; !tracks.item(i).isNull(); i++) - { - // Ignore BB container tracks - if (tracks.item(i).toElement().attribute("type").toInt() == 1) { continue; } - - QDomNodeList instruments = tracks.item(i).toElement().elementsByTagName("instrument"); - if (instruments.isEmpty()) { continue; } - QDomElement instrument = instruments.item(0).toElement(); - // Raise the base note of every instrument by 12 to compensate for the change - // of A4 key code from 57 to 69. This ensures that notes are labeled correctly. - instrument.parentNode().toElement().setAttribute( - "basenote", - instrument.parentNode().toElement().attribute("basenote").toInt() + 12); - // Raise the pitch of all notes in patterns assigned to instruments not affected - // by #1857 by an octave. This negates the base note change for normal instruments, - // but leaves the MIDI-based instruments sounding an octave lower, preserving their - // pitch in existing projects. - if (!affected(instrument)) - { - QDomNodeList patterns = tracks.item(i).toElement().elementsByTagName("pattern"); - for (int i = 0; !patterns.item(i).isNull(); i++) - { - QDomNodeList notes = patterns.item(i).toElement().elementsByTagName("note"); - for (int i = 0; !notes.item(i).isNull(); i++) - { - notes.item(i).toElement().setAttribute( - "key", - notes.item(i).toElement().attribute("key").toInt() + 12 - ); - } - } - } - } - } - else - { - // Dealing with a preset, not a song - QDomNodeList presets = elementsByTagName("instrumenttrack"); - if (presets.item(0).isNull()) { return; } - QDomElement preset = presets.item(0).toElement(); - // Common correction for all instrument presets (make base notes match the new MIDI range). - // NOTE: Many older presets do not have any basenote defined, assume they were "made for 57". - // (Specifying a default like this also happens to fix a FileBrowser bug where previews of presets - // with undefined basenote always play with the basenote inherited from previously played preview.) - int oldBase = preset.attribute("basenote", "57").toInt(); - preset.setAttribute("basenote", oldBase + 12); - // Extra correction for Zyn, VeSTige, LV2 and Carla (to preserve the original buggy behavior). - QDomNodeList instruments = presets.item(0).toElement().elementsByTagName("instrument"); - if (instruments.isEmpty()) { return; } - QDomElement instrument = instruments.item(0).toElement(); - if (affected(instrument)) - { - preset.setAttribute("basenote", preset.attribute("basenote").toInt() + 12); - } - } + upgradeExtendedNoteRange.upgrade(); } @@ -1775,26 +1765,56 @@ void DataFile::upgrade_mixerRename() { // Change nodename to QDomNodeList fxmixer = elementsByTagName("fxmixer"); - for (int i = 0; !fxmixer.item(i).isNull(); ++i) + for (int i = 0; i < fxmixer.length(); ++i) { - fxmixer.item(i).toElement().setTagName("mixer"); + auto item = fxmixer.item(i).toElement(); + if (item.isNull()) + { + continue; + } + item.setTagName("mixer"); } // Change nodename to QDomNodeList fxchannel = elementsByTagName("fxchannel"); - for (int i = 0; !fxchannel.item(i).isNull(); ++i) + for (int i = 0; i < fxchannel.length(); ++i) { - fxchannel.item(i).toElement().setTagName("mixerchannel"); + auto item = fxchannel.item(i).toElement(); + if (item.isNull()) + { + continue; + } + item.setTagName("mixerchannel"); } // Change the attribute fxch of element to mixch QDomNodeList fxch = elementsByTagName("instrumenttrack"); - for(int i = 0; !fxch.item(i).isNull(); ++i) + for (int i = 0; i < fxch.length(); ++i) { - if(fxch.item(i).toElement().hasAttribute("fxch")) + auto item = fxch.item(i).toElement(); + if (item.isNull()) { - fxch.item(i).toElement().setAttribute("mixch", fxch.item(i).toElement().attribute("fxch")); - fxch.item(i).toElement().removeAttribute("fxch"); + continue; + } + if (item.hasAttribute("fxch")) + { + item.setAttribute("mixch", item.attribute("fxch")); + item.removeAttribute("fxch"); + } + } + // Change the attribute fxch of element to mixch + fxch = elementsByTagName("sampletrack"); + for (int i = 0; i < fxch.length(); ++i) + { + auto item = fxch.item(i).toElement(); + if (item.isNull()) + { + continue; + } + if (item.hasAttribute("fxch")) + { + item.setAttribute("mixch", item.attribute("fxch")); + item.removeAttribute("fxch"); } } } @@ -1825,8 +1845,8 @@ void DataFile::upgrade_bbTcoRename() for (int i = 0; !elements.item(i).isNull(); ++i) { auto e = elements.item(i).toElement(); - static_assert(Track::PatternTrack == 1, "Must be type=1 for backwards compatibility"); - if (e.attribute("type").toInt() == Track::PatternTrack) + static_assert(Track::Type::Pattern == static_cast(1), "Must be type=1 for backwards compatibility"); + if (static_cast(e.attribute("type").toInt()) == Track::Type::Pattern) { e.setAttribute("name", e.attribute("name").replace("Beat/Bassline", "Pattern")); } @@ -1834,6 +1854,113 @@ void DataFile::upgrade_bbTcoRename() } +// Set LFO speed to 0.01 on projects made before sample-and-hold PR +void DataFile::upgrade_sampleAndHold() +{ + QDomNodeList elements = elementsByTagName("lfocontroller"); + for (int i = 0; i < elements.length(); ++i) + { + if (elements.item(i).isNull()) { continue; } + auto e = elements.item(i).toElement(); + // Correct old random wave LFO speeds + if (e.attribute("wave").toInt() == 6) + { + e.setAttribute("speed", 0.01f); + } + } +} + + +// Change loops' filenames in s +void DataFile::upgrade_loopsRename() +{ + static constexpr auto loopBPMs = std::array{ + std::pair{"bassloops/briff01", "140"}, + std::pair{"bassloops/rave_bass01", "180"}, + std::pair{"bassloops/rave_bass02", "180"}, + std::pair{"bassloops/tb303_01", "123"}, + std::pair{"bassloops/techno_bass01", "140"}, + std::pair{"bassloops/techno_bass02", "140"}, + std::pair{"bassloops/techno_synth01", "140"}, + std::pair{"bassloops/techno_synth02", "140"}, + std::pair{"bassloops/techno_synth03", "130"}, + std::pair{"bassloops/techno_synth04", "140"}, + std::pair{"beats/909beat01", "122"}, + std::pair{"beats/break01", "168"}, + std::pair{"beats/break02", "141"}, + std::pair{"beats/break03", "168"}, + std::pair{"beats/electro_beat01", "120"}, + std::pair{"beats/electro_beat02", "119"}, + std::pair{"beats/house_loop01", "142"}, + std::pair{"beats/jungle01", "168"}, + std::pair{"beats/rave_hihat01", "180"}, + std::pair{"beats/rave_hihat02", "180"}, + std::pair{"beats/rave_kick01", "180"}, + std::pair{"beats/rave_kick02", "180"}, + std::pair{"beats/rave_snare01", "180"}, + std::pair{"latin/latin_brass01", "140"}, + std::pair{"latin/latin_guitar01", "126"}, + std::pair{"latin/latin_guitar02", "140"}, + std::pair{"latin/latin_guitar03", "120"}, + }; + + const QString prefix = "factorysample:", + extension = ".ogg"; + + // 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; } + for (const auto& cur : loopBPMs) + { + QString x = cur.first, // loop name + y = cur.second, // BPM + srcVal = item.attribute(srcAttr), + pattern = prefix + x + extension; + + if (srcVal == pattern) + { + // Add " - X BPM" to filename + item.setAttribute(srcAttr, + prefix + x + " - " + y + " BPM" + + extension); + } + } + } + } + } +} + +//! Update MIDI CC indexes, so that they are counted from 0. Older releases of LMMS +//! count the CCs from 1. +void DataFile::upgrade_midiCCIndexing() +{ + static constexpr std::array attributesToUpdate{"inputcontroller", "outputcontroller"}; + + QDomNodeList elements = elementsByTagName("Midicontroller"); + for(int i = 0; i < elements.length(); i++) + { + if (elements.item(i).isNull()) { continue; } + auto element = elements.item(i).toElement(); + for (const char* attrName : attributesToUpdate) + { + if (element.hasAttribute(attrName)) + { + int cc = element.attribute(attrName).toInt(); + element.setAttribute(attrName, cc - 1); + } + } + } +} + void DataFile::upgrade() { // Runs all necessary upgrade methods @@ -1854,7 +1981,7 @@ void DataFile::upgrade() documentElement().setAttribute( "creator", "LMMS" ); documentElement().setAttribute( "creatorversion", LMMS_VERSION ); - if( type() == SongProject || type() == SongProjectTemplate ) + if( type() == Type::SongProject || type() == Type::SongProjectTemplate ) { // Time-signature if ( !m_head.hasAttribute( "timesig_numerator" ) ) @@ -1932,8 +2059,8 @@ void DataFile::loadData( const QByteArray & _data, const QString & _sourceFile ) ProjectVersion createdWith = root.attribute("creatorversion"); ProjectVersion openedWith = LMMS_VERSION; - if (createdWith.setCompareType(ProjectVersion::Minor) - != openedWith.setCompareType(ProjectVersion::Minor) + if (createdWith.setCompareType(ProjectVersion::CompareType::Minor) + != openedWith.setCompareType(ProjectVersion::CompareType::Minor) && gui::getGUI() != nullptr && root.attribute("type") == "song" ){ auto projectType = _sourceFile.endsWith(".mpt") ? @@ -1984,5 +2111,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 bc5455c96..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) { @@ -273,7 +279,9 @@ int DrumSynth::GetDSFileSamples(QString dsfile, int16_t *&wave, int channels, sa //generation long Length, tpos=0, tplus, totmp, t, i, j; float x[3] = {0.f, 0.f, 0.f}; - float MasterTune, randmax, randmax2; + float MasterTune; + constexpr float randmax = 1.f / static_cast(RAND_MAX); + constexpr float randmax2 = 2.f / static_cast(RAND_MAX); int MainFilter, HighPass; long NON, NT, TON, DiON, TDroop=0, DStep; @@ -454,7 +462,6 @@ int DrumSynth::GetDSFileSamples(QString dsfile, int16_t *&wave, int channels, sa } //prepare envelopes - randmax = 1.f / RAND_MAX; randmax2 = 2.f * randmax; for (i=1;i<8;i++) { envData[i][NEXTT]=0; envData[i][PNT]=0; } Length = LongestEnv(); @@ -745,4 +752,4 @@ int DrumSynth::GetDSFileSamples(QString dsfile, int16_t *&wave, int channels, sa } -} // namespace lmms \ No newline at end of file +} // namespace lmms diff --git a/src/core/Effect.cpp b/src/core/Effect.cpp index 151eaf13e..7ede64e6b 100644 --- a/src/core/Effect.cpp +++ b/src/core/Effect.cpp @@ -52,9 +52,11 @@ 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; @@ -208,8 +210,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/EffectChain.cpp b/src/core/EffectChain.cpp index 024ddab64..4da5c5197 100644 --- a/src/core/EffectChain.cpp +++ b/src/core/EffectChain.cpp @@ -25,6 +25,7 @@ #include +#include #include "EffectChain.h" #include "Effect.h" @@ -56,7 +57,7 @@ EffectChain::~EffectChain() void EffectChain::saveSettings( QDomDocument & _doc, QDomElement & _this ) { m_enabledModel.saveSettings( _doc, _this, "enabled" ); - _this.setAttribute( "numofeffects", m_effects.count() ); + _this.setAttribute("numofeffects", static_cast(m_effects.size())); for( Effect* effect : m_effects) { @@ -121,7 +122,7 @@ void EffectChain::loadSettings( const QDomElement & _this ) void EffectChain::appendEffect( Effect * _effect ) { Engine::audioEngine()->requestChangeInModel(); - m_effects.append( _effect ); + m_effects.push_back(_effect); Engine::audioEngine()->doneChangeInModel(); m_enabledModel.setValue( true ); @@ -136,7 +137,7 @@ void EffectChain::removeEffect( Effect * _effect ) { Engine::audioEngine()->requestChangeInModel(); - Effect ** found = std::find( m_effects.begin(), m_effects.end(), _effect ); + auto found = std::find(m_effects.begin(), m_effects.end(), _effect); if( found == m_effects.end() ) { Engine::audioEngine()->doneChangeInModel(); @@ -146,7 +147,7 @@ void EffectChain::removeEffect( Effect * _effect ) Engine::audioEngine()->doneChangeInModel(); - if( m_effects.isEmpty() ) + if (m_effects.empty()) { m_enabledModel.setValue( false ); } @@ -159,10 +160,11 @@ void EffectChain::removeEffect( Effect * _effect ) void EffectChain::moveDown( Effect * _effect ) { - if( _effect != m_effects.last() ) + if (_effect != m_effects.back()) { - int i = m_effects.indexOf(_effect); - std::swap(m_effects[i + 1], m_effects[i]); + auto it = std::find(m_effects.begin(), m_effects.end(), _effect); + assert(it != m_effects.end()); + std::swap(*std::next(it), *it); } } @@ -171,10 +173,11 @@ void EffectChain::moveDown( Effect * _effect ) void EffectChain::moveUp( Effect * _effect ) { - if( _effect != m_effects.first() ) + if (_effect != m_effects.front()) { - int i = m_effects.indexOf(_effect); - std::swap(m_effects[i - 1], m_effects[i]); + auto it = std::find(m_effects.begin(), m_effects.end(), _effect); + assert(it != m_effects.end()); + std::swap(*std::prev(it), *it); } } @@ -228,9 +231,9 @@ void EffectChain::clear() Engine::audioEngine()->requestChangeInModel(); - while( m_effects.count() ) + while (m_effects.size()) { - Effect * e = m_effects[m_effects.count() - 1]; + auto e = m_effects[m_effects.size() - 1]; m_effects.pop_back(); delete e; } diff --git a/src/core/EnvelopeAndLfoParameters.cpp b/src/core/EnvelopeAndLfoParameters.cpp index 2aa77e6aa..611700c51 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 { @@ -113,12 +117,12 @@ EnvelopeAndLfoParameters::EnvelopeAndLfoParameters( SECS_PER_LFO_OSCILLATION * 1000.0, this, tr( "LFO frequency" ) ), m_lfoAmountModel( 0.0, -1.0, 1.0, 0.005, this, tr( "LFO mod amount" ) ), - m_lfoWaveModel( SineWave, 0, NumLfoShapes, this, tr( "LFO wave shape" ) ), + m_lfoWaveModel( static_cast(LfoShape::SineWave), 0, NumLfoShapes, this, tr( "LFO wave shape" ) ), m_x100Model( false, this, tr( "LFO frequency x 100" ) ), 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 ); @@ -209,28 +213,28 @@ inline sample_t EnvelopeAndLfoParameters::lfoShapeSample( fpp_t _frame_offset ) const float phase = frame / static_cast( m_lfoOscillationFrames ); sample_t shape_sample; - switch( m_lfoWaveModel.value() ) + switch( static_cast(m_lfoWaveModel.value()) ) { - case TriangleWave: + case LfoShape::TriangleWave: shape_sample = Oscillator::triangleSample( phase ); break; - case SquareWave: + case LfoShape::SquareWave: shape_sample = Oscillator::squareSample( phase ); break; - case SawWave: + case LfoShape::SawWave: shape_sample = Oscillator::sawSample( phase ); break; - case UserDefinedWave: - shape_sample = m_userWave.userWaveSample( phase ); + case LfoShape::UserDefinedWave: + shape_sample = Oscillator::userWaveSample(m_userWave.get(), phase); break; - case RandomWave: + case LfoShape::RandomWave: if( frame == 0 ) { m_random = Oscillator::noiseSample( 0.0f ); } shape_sample = m_random; break; - case SineWave: + case LfoShape::SineWave: default: shape_sample = Oscillator::sinSample( phase ); break; @@ -274,7 +278,7 @@ inline void EnvelopeAndLfoParameters::fillLfoLevel( float * _buf, } fpp_t offset = 0; - const float lafI = 1.0f / qMax( minimumFrames, m_lfoAttackFrames ); + const float lafI = 1.0f / std::max(minimumFrames, m_lfoAttackFrames); for( ; offset < _frames && _frame < m_lfoAttackFrames; ++offset, ++_frame ) { @@ -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(); } @@ -404,16 +415,16 @@ void EnvelopeAndLfoParameters::updateSampleVars() // TODO: Remove the expKnobVals, time should be linear const auto predelay_frames = static_cast(frames_per_env_seg * expKnobVal(m_predelayModel.value())); - const f_cnt_t attack_frames = qMax( minimumFrames, - static_cast( frames_per_env_seg * - expKnobVal( m_attackModel.value() ) ) ); + const f_cnt_t attack_frames = std::max(minimumFrames, + static_cast(frames_per_env_seg * + expKnobVal(m_attackModel.value()))); const auto hold_frames = static_cast(frames_per_env_seg * expKnobVal(m_holdModel.value())); - const f_cnt_t decay_frames = qMax( minimumFrames, - static_cast( frames_per_env_seg * - expKnobVal( m_decayModel.value() * - ( 1 - m_sustainModel.value() ) ) ) ); + const f_cnt_t decay_frames = std::max(minimumFrames, + static_cast(frames_per_env_seg * + expKnobVal(m_decayModel.value() * + (1 - m_sustainModel.value())))); m_sustainLevel = m_sustainModel.value(); m_amount = m_amountModel.value(); @@ -430,7 +441,7 @@ void EnvelopeAndLfoParameters::updateSampleVars() decay_frames; m_rFrames = static_cast( frames_per_env_seg * expKnobVal( m_releaseModel.value() ) ); - m_rFrames = qMax( minimumFrames, m_rFrames ); + m_rFrames = std::max(minimumFrames, m_rFrames); if( static_cast( floorf( m_amount * 1000.0f ) ) == 0 ) { diff --git a/src/core/GrooveExperiments.cpp b/src/core/GrooveExperiments.cpp index 45a7552d0..7b61e8014 100644 --- a/src/core/GrooveExperiments.cpp +++ b/src/core/GrooveExperiments.cpp @@ -58,7 +58,7 @@ void GrooveExperiments::init() Song* s = Engine::getSong(); connect(s, SIGNAL(projectLoaded()), this, SLOT(update())); connect(s, SIGNAL(lengthChanged(int)), this, SLOT(update())); - connect(s, SIGNAL(tempoChanged(bpm_t)), this, SLOT(update())); + connect(s, SIGNAL(tempoChanged(lmms::bpm_t)), this, SLOT(update())); connect(s, SIGNAL(timeSignatureChanged(int, int)), this, SLOT(update())); } diff --git a/src/core/HalfSwing.cpp b/src/core/HalfSwing.cpp index f4c86622d..6ed8c5fb7 100644 --- a/src/core/HalfSwing.cpp +++ b/src/core/HalfSwing.cpp @@ -60,7 +60,7 @@ void HalfSwing::init() Song* s = Engine::getSong(); connect(s, SIGNAL(projectLoaded()), this, SLOT(update())); connect(s, SIGNAL(lengthChanged(int)), this, SLOT(update())); - connect(s, SIGNAL(tempoChanged(bpm_t)), this, SLOT(update())); + connect(s, SIGNAL(tempoChanged(lmms::bpm_t)), this, SLOT(update())); connect(s, SIGNAL(timeSignatureChanged(int, int)), this, SLOT(update())); } diff --git a/src/core/HydrogenSwing.cpp b/src/core/HydrogenSwing.cpp index 78b0d1011..fee1bebd3 100644 --- a/src/core/HydrogenSwing.cpp +++ b/src/core/HydrogenSwing.cpp @@ -60,7 +60,7 @@ void HydrogenSwing::init() Song* s = Engine::getSong(); connect(s, SIGNAL(projectLoaded()), this, SLOT(update())); connect(s, SIGNAL(lengthChanged(int)), this, SLOT(update())); - connect(s, SIGNAL(tempoChanged(bpm_t)), this, SLOT(update())); + connect(s, SIGNAL(tempoChanged(lmms::bpm_t)), this, SLOT(update())); connect(s, SIGNAL(timeSignatureChanged(int, int)), this, SLOT(update())); } diff --git a/src/core/ImportFilter.cpp b/src/core/ImportFilter.cpp index 35c34078e..bc43b0c6c 100644 --- a/src/core/ImportFilter.cpp +++ b/src/core/ImportFilter.cpp @@ -61,7 +61,7 @@ void ImportFilter::import( const QString & _file_to_import, const bool j = Engine::projectJournal()->isJournalling(); Engine::projectJournal()->setJournalling( false ); - for (const Plugin::Descriptor* desc : getPluginFactory()->descriptors(Plugin::ImportFilter)) + for (const Plugin::Descriptor* desc : getPluginFactory()->descriptors(Plugin::Type::ImportFilter)) { unique_ptr p(Plugin::instantiate( desc->name, nullptr, s.data() )); if( dynamic_cast( p.get() ) != nullptr && diff --git a/src/core/Instrument.cpp b/src/core/Instrument.cpp index dde260fdc..a7cfc467b 100644 --- a/src/core/Instrument.cpp +++ b/src/core/Instrument.cpp @@ -179,21 +179,18 @@ void Instrument::applyFadeIn(sampleFrame * buf, NotePlayHandle * n) void Instrument::applyRelease( sampleFrame * buf, const NotePlayHandle * _n ) { - const fpp_t frames = _n->framesLeftForCurrentPeriod(); - const fpp_t fpp = Engine::audioEngine()->framesPerPeriod(); - const f_cnt_t fl = _n->framesLeft(); - if( fl <= desiredReleaseFrames()+fpp ) + const auto fpp = Engine::audioEngine()->framesPerPeriod(); + const auto releaseFrames = desiredReleaseFrames(); + + const auto endFrame = _n->framesLeft(); + const auto startFrame = std::max(0, endFrame - releaseFrames); + + for (auto f = startFrame; f < endFrame && f < fpp; f++) { - for( fpp_t f = (fpp_t)( ( fl > desiredReleaseFrames() ) ? - ( qMax( fpp - desiredReleaseFrames(), 0 ) + - fl % fpp ) : 0 ); f < frames; ++f ) + const float fac = (float)(endFrame - f) / (float)releaseFrames; + for (ch_cnt_t ch = 0; ch < DEFAULT_CHANNELS; ch++) { - const float fac = (float)( fl-f-1 ) / - desiredReleaseFrames(); - for( ch_cnt_t ch = 0; ch < DEFAULT_CHANNELS; ++ch ) - { - buf[f][ch] *= fac; - } + buf[f][ch] *= fac; } } } diff --git a/src/core/InstrumentFunctions.cpp b/src/core/InstrumentFunctions.cpp index 06e56666c..39c994ab6 100644 --- a/src/core/InstrumentFunctions.cpp +++ b/src/core/InstrumentFunctions.cpp @@ -177,12 +177,11 @@ bool InstrumentFunctionNoteStacking::Chord::hasSemiTone( int8_t semi_tone ) cons -InstrumentFunctionNoteStacking::ChordTable::ChordTable() : - QVector() +InstrumentFunctionNoteStacking::ChordTable::ChordTable() { for (const auto& chord : s_initTable) { - push_back(Chord(chord.m_name, chord.m_semiTones)); + m_chords.emplace_back(chord.m_name, chord.m_semiTones); } } @@ -191,10 +190,12 @@ InstrumentFunctionNoteStacking::ChordTable::ChordTable() : const InstrumentFunctionNoteStacking::Chord & InstrumentFunctionNoteStacking::ChordTable::getByName( const QString & name, bool is_scale ) const { - for( int i = 0; i < size(); i++ ) + for (const auto& chord : m_chords) { - if( at( i ).getName() == name && is_scale == at( i ).isScale() ) - return at( i ); + if (chord.getName() == name && is_scale == chord.isScale()) + { + return chord; + } } static Chord empty; @@ -211,9 +212,10 @@ InstrumentFunctionNoteStacking::InstrumentFunctionNoteStacking( Model * _parent m_chordRangeModel( 1.0f, 1.0f, 9.0f, 1.0f, this, tr( "Chord range" ) ) { const ChordTable & chord_table = ChordTable::getInstance(); - for( int i = 0; i < chord_table.size(); ++i ) + + for (const auto& chord : chord_table.chords()) { - m_chordsModel.addItem( chord_table[i].getName() ); + m_chordsModel.addItem(chord.getName()); } } @@ -232,7 +234,7 @@ void InstrumentFunctionNoteStacking::processNote( NotePlayHandle * _n ) // at the same time we only add sub-notes if nothing of the note was // played yet, because otherwise we would add chord-subnotes every // time an audio-buffer is rendered... - if( ( _n->origin() == NotePlayHandle::OriginArpeggio || ( _n->hasParent() == false && _n->instrumentTrack()->isArpeggioEnabled() == false ) ) && + if( ( _n->origin() == NotePlayHandle::Origin::Arpeggio || ( _n->hasParent() == false && _n->instrumentTrack()->isArpeggioEnabled() == false ) ) && _n->totalFramesPlayed() == 0 && m_chordsEnabledModel.value() == true && ! _n->isReleased() ) { @@ -244,10 +246,10 @@ void InstrumentFunctionNoteStacking::processNote( NotePlayHandle * _n ) const int sub_note_key_base = base_note_key + octave_cnt * KeysPerOctave; // process all notes in the chord - for( int i = 0; i < chord_table[selected_chord].size(); ++i ) + for( int i = 0; i < chord_table.chords()[selected_chord].size(); ++i ) { // add interval to sub-note-key - const int sub_note_key = sub_note_key_base + (int) chord_table[selected_chord][i]; + const int sub_note_key = sub_note_key_base + (int) chord_table.chords()[selected_chord][i]; // maybe we're out of range -> let's get outta // here! if( sub_note_key > NumKeys ) @@ -261,7 +263,7 @@ void InstrumentFunctionNoteStacking::processNote( NotePlayHandle * _n ) // different Engine::audioEngine()->addPlayHandle( NotePlayHandleManager::acquire( _n->instrumentTrack(), _n->offset(), _n->frames(), note_copy, - _n, -1, NotePlayHandle::OriginNoteStacking ) + _n, -1, NotePlayHandle::Origin::NoteStacking ) ); } } @@ -309,9 +311,9 @@ InstrumentFunctionArpeggio::InstrumentFunctionArpeggio( Model * _parent ) : m_arpModeModel( this, tr( "Arpeggio mode" ) ) { const InstrumentFunctionNoteStacking::ChordTable & chord_table = InstrumentFunctionNoteStacking::ChordTable::getInstance(); - for( int i = 0; i < chord_table.size(); ++i ) + for (auto& chord : chord_table.chords()) { - m_arpModel.addItem( chord_table[i].getName() ); + m_arpModel.addItem(chord.getName()); } m_arpDirectionModel.addItem( tr( "Up" ), std::make_unique( "arp_up" ) ); @@ -319,7 +321,7 @@ InstrumentFunctionArpeggio::InstrumentFunctionArpeggio( Model * _parent ) : m_arpDirectionModel.addItem( tr( "Up and down" ), std::make_unique( "arp_up_and_down" ) ); m_arpDirectionModel.addItem( tr( "Down and up" ), std::make_unique( "arp_up_and_down" ) ); m_arpDirectionModel.addItem( tr( "Random" ), std::make_unique( "arp_random" ) ); - m_arpDirectionModel.setInitValue( ArpDirUp ); + m_arpDirectionModel.setInitValue( static_cast(ArpDirection::Up) ); m_arpModeModel.addItem( tr( "Free" ), std::make_unique( "arp_free" ) ); m_arpModeModel.addItem( tr( "Sort" ), std::make_unique( "arp_sort" ) ); @@ -334,8 +336,8 @@ InstrumentFunctionArpeggio::InstrumentFunctionArpeggio( Model * _parent ) : void InstrumentFunctionArpeggio::processNote( NotePlayHandle * _n ) { const int base_note_key = _n->key(); - if( _n->origin() == NotePlayHandle::OriginArpeggio || - _n->origin() == NotePlayHandle::OriginNoteStacking || + if( _n->origin() == NotePlayHandle::Origin::Arpeggio || + _n->origin() == NotePlayHandle::Origin::NoteStacking || !m_arpEnabledModel.value() || _n->isReleased() ) { @@ -349,7 +351,7 @@ void InstrumentFunctionArpeggio::processNote( NotePlayHandle * _n ) ConstNotePlayHandleList cnphv = NotePlayHandle::nphsOfInstrumentTrack( _n->instrumentTrack() ); - if( m_arpModeModel.value() != FreeMode && cnphv.size() == 0 ) + if( static_cast(m_arpModeModel.value()) != ArpMode::Free && cnphv.size() == 0 ) { // maybe we're playing only a preset-preview-note? cnphv = PresetPreviewPlayHandle::nphsOfInstrumentTrack( _n->instrumentTrack() ); @@ -362,7 +364,7 @@ void InstrumentFunctionArpeggio::processNote( NotePlayHandle * _n ) } const InstrumentFunctionNoteStacking::ChordTable & chord_table = InstrumentFunctionNoteStacking::ChordTable::getInstance(); - const int cur_chord_size = chord_table[selected_arp].size(); + const int cur_chord_size = chord_table.chords()[selected_arp].size(); const int range = static_cast(cur_chord_size * m_arpRangeModel.value() * m_arpRepeatsModel.value()); const int total_range = range * cnphv.size(); @@ -373,11 +375,11 @@ void InstrumentFunctionArpeggio::processNote( NotePlayHandle * _n ) // used for calculating remaining frames for arp-note, we have to add // arp_frames-1, otherwise the first arp-note will not be setup // correctly... -> arp_frames frames silence at the start of every note! - int cur_frame = ( ( m_arpModeModel.value() != FreeMode ) ? + int cur_frame = ( ( static_cast(m_arpModeModel.value()) != ArpMode::Free ) ? cnphv.first()->totalFramesPlayed() : _n->totalFramesPlayed() ) + arp_frames - 1; // used for loop - f_cnt_t frames_processed = ( m_arpModeModel.value() != FreeMode ) ? cnphv.first()->noteOffset() : _n->noteOffset(); + f_cnt_t frames_processed = ( static_cast(m_arpModeModel.value()) != ArpMode::Free ) ? cnphv.first()->noteOffset() : _n->noteOffset(); while( frames_processed < Engine::audioEngine()->framesPerPeriod() ) { @@ -395,7 +397,7 @@ void InstrumentFunctionArpeggio::processNote( NotePlayHandle * _n ) // in sorted mode: is it our turn or do we have to be quiet for // now? - if( m_arpModeModel.value() == SortMode && + if( static_cast(m_arpModeModel.value()) == ArpMode::Sort && ( ( cur_frame / arp_frames ) % total_range ) / range != (f_cnt_t) _n->index() ) { // update counters @@ -407,7 +409,7 @@ void InstrumentFunctionArpeggio::processNote( NotePlayHandle * _n ) // Skip notes randomly if( m_arpSkipModel.value() ) { - if( 100 * ( (float) rand() / (float)( RAND_MAX + 1.0f ) ) < m_arpSkipModel.value() ) + if (100 * static_cast(rand()) / (static_cast(RAND_MAX) + 1.0f) < m_arpSkipModel.value()) { // update counters frames_processed += arp_frames; @@ -416,58 +418,40 @@ void InstrumentFunctionArpeggio::processNote( NotePlayHandle * _n ) } } - int dir = m_arpDirectionModel.value(); + auto dir = static_cast(m_arpDirectionModel.value()); // Miss notes randomly. We intercept int dir and abuse it // after need. :) if( m_arpMissModel.value() ) { - if( 100 * ( (float) rand() / (float)( RAND_MAX + 1.0f ) ) < m_arpMissModel.value() ) + if (100 * static_cast(rand()) / (static_cast(RAND_MAX) + 1.0f) < m_arpMissModel.value()) { - dir = ArpDirRandom; + dir = ArpDirection::Random; } } int cur_arp_idx = 0; // process according to arpeggio-direction... - if( dir == ArpDirUp ) + if (dir == ArpDirection::Up || dir == ArpDirection::Down) { cur_arp_idx = ( cur_frame / arp_frames ) % range; } - else if( dir == ArpDirDown ) - { - cur_arp_idx = range - ( cur_frame / arp_frames ) % - range - 1; - } - else if( dir == ArpDirUpAndDown && 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 == ArpDirDownAndUp && range > 1 ) - { - // copied from ArpDirUpAndDown 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 == ArpDirRandom ) + else if( dir == ArpDirection::Random ) { // just pick a random chord-index cur_arp_idx = (int)( range * ( (float) rand() / (float) RAND_MAX ) ); @@ -477,15 +461,21 @@ void InstrumentFunctionArpeggio::processNote( NotePlayHandle * _n ) cur_arp_idx = static_cast(cur_arp_idx / m_arpRepeatsModel.value()); // Cycle notes - if( m_arpCycleModel.value() && dir != ArpDirRandom ) + if( m_arpCycleModel.value() && dir != ArpDirection::Random ) { cur_arp_idx *= m_arpCycleModel.value() + 1; 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[selected_arp][cur_arp_idx % cur_chord_size]; + KeysPerOctave + chord_table.chords()[selected_arp][cur_arp_idx % cur_chord_size]; // range-checking if( sub_note_key >= NumKeys || @@ -505,7 +495,7 @@ void InstrumentFunctionArpeggio::processNote( NotePlayHandle * _n ) gated_frames, Note( TimePos( 0 ), TimePos( 0 ), sub_note_key, _n->getVolume(), _n->getPanning(), _n->detuning() ), - _n, -1, NotePlayHandle::OriginArpeggio ) + _n, -1, NotePlayHandle::Origin::Arpeggio ) ); // update counters diff --git a/src/core/InstrumentPlayHandle.cpp b/src/core/InstrumentPlayHandle.cpp index 8c272cd47..ef7d78f3f 100644 --- a/src/core/InstrumentPlayHandle.cpp +++ b/src/core/InstrumentPlayHandle.cpp @@ -24,18 +24,55 @@ #include "InstrumentPlayHandle.h" +#include "Instrument.h" #include "InstrumentTrack.h" +#include "Engine.h" +#include "AudioEngine.h" namespace lmms { -InstrumentPlayHandle::InstrumentPlayHandle( Instrument * instrument, InstrumentTrack* instrumentTrack ) : - PlayHandle( TypeInstrumentPlayHandle ), - m_instrument( instrument ) +InstrumentPlayHandle::InstrumentPlayHandle(Instrument * instrument, InstrumentTrack* instrumentTrack) : + PlayHandle(Type::InstrumentPlayHandle), + m_instrument(instrument) { - setAudioPort( instrumentTrack->audioPort() ); + setAudioPort(instrumentTrack->audioPort()); +} + +void InstrumentPlayHandle::play(sampleFrame * working_buffer) +{ + InstrumentTrack * instrumentTrack = m_instrument->instrumentTrack(); + + // ensure that all our nph's have been processed first + auto nphv = NotePlayHandle::nphsOfInstrumentTrack(instrumentTrack, true); + + bool nphsLeft; + do + { + nphsLeft = false; + for (const auto& handle : nphv) + { + if (handle->state() != ThreadableJob::ProcessingState::Done && !handle->isFinished()) + { + nphsLeft = true; + const_cast(handle)->process(); + } + } + } + while (nphsLeft); + + m_instrument->play(working_buffer); + + // Process the audio buffer that the instrument has just worked on... + const fpp_t frames = Engine::audioEngine()->framesPerPeriod(); + instrumentTrack->processAudioBuffer(working_buffer, frames, nullptr); +} + +bool InstrumentPlayHandle::isFromTrack(const Track* track) const +{ + return m_instrument->isFromTrack(track); } -} // namespace lmms \ No newline at end of file +} // namespace lmms diff --git a/src/core/InstrumentSoundShaping.cpp b/src/core/InstrumentSoundShaping.cpp index edf3b403c..07c3bbf7c 100644 --- a/src/core/InstrumentSoundShaping.cpp +++ b/src/core/InstrumentSoundShaping.cpp @@ -69,7 +69,7 @@ InstrumentSoundShaping::InstrumentSoundShaping( for( int i = 0; i < NumTargets; ++i ) { float value_for_zero_amount = 0.0; - if( i == Volume ) + if( static_cast(i) == Target::Volume ) { value_for_zero_amount = 1.0; } @@ -119,7 +119,7 @@ float InstrumentSoundShaping::volumeLevel( NotePlayHandle* n, const f_cnt_t fram } float level; - m_envLfoParameters[Volume]->fillLevel( &level, frame, envReleaseBegin, 1 ); + m_envLfoParameters[static_cast(Target::Volume)]->fillLevel( &level, frame, envReleaseBegin, 1 ); return level; } @@ -160,22 +160,22 @@ void InstrumentSoundShaping::processAudioBuffer( sampleFrame* buffer, { n->m_filter = std::make_unique>( Engine::audioEngine()->processingSampleRate() ); } - n->m_filter->setFilterType( m_filterModel.value() ); + n->m_filter->setFilterType( static_cast::FilterType>(m_filterModel.value()) ); - if( m_envLfoParameters[Cut]->isUsed() ) + if( m_envLfoParameters[static_cast(Target::Cut)]->isUsed() ) { - m_envLfoParameters[Cut]->fillLevel( cutBuffer.data(), envTotalFrames, envReleaseBegin, frames ); + m_envLfoParameters[static_cast(Target::Cut)]->fillLevel( cutBuffer.data(), envTotalFrames, envReleaseBegin, frames ); } - if( m_envLfoParameters[Resonance]->isUsed() ) + if( m_envLfoParameters[static_cast(Target::Resonance)]->isUsed() ) { - m_envLfoParameters[Resonance]->fillLevel( resBuffer.data(), envTotalFrames, envReleaseBegin, frames ); + m_envLfoParameters[static_cast(Target::Resonance)]->fillLevel( resBuffer.data(), envTotalFrames, envReleaseBegin, frames ); } const float fcv = m_filterCutModel.value(); const float frv = m_filterResModel.value(); - if( m_envLfoParameters[Cut]->isUsed() && - m_envLfoParameters[Resonance]->isUsed() ) + if( m_envLfoParameters[static_cast(Target::Cut)]->isUsed() && + m_envLfoParameters[static_cast(Target::Resonance)]->isUsed() ) { for( fpp_t frame = 0; frame < frames; ++frame ) { @@ -196,7 +196,7 @@ void InstrumentSoundShaping::processAudioBuffer( sampleFrame* buffer, buffer[frame][1] = n->m_filter->update( buffer[frame][1], 1 ); } } - else if( m_envLfoParameters[Cut]->isUsed() ) + else if( m_envLfoParameters[static_cast(Target::Cut)]->isUsed() ) { for( fpp_t frame = 0; frame < frames; ++frame ) { @@ -213,7 +213,7 @@ void InstrumentSoundShaping::processAudioBuffer( sampleFrame* buffer, buffer[frame][1] = n->m_filter->update( buffer[frame][1], 1 ); } } - else if( m_envLfoParameters[Resonance]->isUsed() ) + else if( m_envLfoParameters[static_cast(Target::Resonance)]->isUsed() ) { for( fpp_t frame = 0; frame < frames; ++frame ) { @@ -241,10 +241,10 @@ void InstrumentSoundShaping::processAudioBuffer( sampleFrame* buffer, } } - if( m_envLfoParameters[Volume]->isUsed() ) + if( m_envLfoParameters[static_cast(Target::Volume)]->isUsed() ) { QVarLengthArray volBuffer(frames); - m_envLfoParameters[Volume]->fillLevel( volBuffer.data(), envTotalFrames, envReleaseBegin, frames ); + m_envLfoParameters[static_cast(Target::Volume)]->fillLevel( volBuffer.data(), envTotalFrames, envReleaseBegin, frames ); for( fpp_t frame = 0; frame < frames; ++frame ) { @@ -255,7 +255,7 @@ void InstrumentSoundShaping::processAudioBuffer( sampleFrame* buffer, } } -/* else if( m_envLfoParameters[Volume]->isUsed() == false && m_envLfoParameters[PANNING]->isUsed() ) +/* else if( m_envLfoParameters[static_cast(Target::Volume)]->isUsed() == false && m_envLfoParameters[PANNING]->isUsed() ) { // only use panning-envelope... for( fpp_t frame = 0; frame < frames; ++frame ) @@ -275,11 +275,11 @@ void InstrumentSoundShaping::processAudioBuffer( sampleFrame* buffer, f_cnt_t InstrumentSoundShaping::envFrames( const bool _only_vol ) const { - f_cnt_t ret_val = m_envLfoParameters[Volume]->PAHD_Frames(); + f_cnt_t ret_val = m_envLfoParameters[static_cast(Target::Volume)]->PAHD_Frames(); if( _only_vol == false ) { - for( int i = Volume+1; i < NumTargets; ++i ) + for( int i = static_cast(Target::Volume)+1; i < NumTargets; ++i ) { if( m_envLfoParameters[i]->isUsed() && m_envLfoParameters[i]->PAHD_Frames() > ret_val ) @@ -303,21 +303,21 @@ f_cnt_t InstrumentSoundShaping::releaseFrames() const f_cnt_t ret_val = m_instrumentTrack->instrument()->desiredReleaseFrames(); - if( m_instrumentTrack->instrument()->flags().testFlag( Instrument::IsSingleStreamed ) ) + if( m_instrumentTrack->instrument()->flags().testFlag( Instrument::Flag::IsSingleStreamed ) ) { return ret_val; } - if( m_envLfoParameters[Volume]->isUsed() ) + if( m_envLfoParameters[static_cast(Target::Volume)]->isUsed() ) { - return m_envLfoParameters[Volume]->releaseFrames(); + return m_envLfoParameters[static_cast(Target::Volume)]->releaseFrames(); } - for( int i = Volume+1; i < NumTargets; ++i ) + for( int i = static_cast(Target::Volume)+1; i < NumTargets; ++i ) { if( m_envLfoParameters[i]->isUsed() ) { - ret_val = qMax( ret_val, m_envLfoParameters[i]->releaseFrames() ); + ret_val = std::max(ret_val, m_envLfoParameters[i]->releaseFrames()); } } return ret_val; diff --git a/src/core/Ladspa2LMMS.cpp b/src/core/Ladspa2LMMS.cpp index 24dfc76f1..440b052f8 100644 --- a/src/core/Ladspa2LMMS.cpp +++ b/src/core/Ladspa2LMMS.cpp @@ -40,12 +40,12 @@ Ladspa2LMMS::Ladspa2LMMS() ladspa_key_t key = plugin.second; LadspaManagerDescription * desc = getDescription( key ); - if( desc->type == SOURCE ) + if( desc->type == LadspaPluginType::Source ) { m_instruments.append( qMakePair( getName( key ), key ) ); } - else if( desc->type == TRANSFER && + else if( desc->type == LadspaPluginType::Transfer && ( desc->inputChannels == desc->outputChannels && ( desc->inputChannels == 1 || desc->inputChannels == 2 || @@ -55,7 +55,7 @@ Ladspa2LMMS::Ladspa2LMMS() m_validEffects.append( qMakePair( getName( key ), key ) ); } - else if( desc->type == TRANSFER && + else if( desc->type == LadspaPluginType::Transfer && ( desc->inputChannels != desc->outputChannels || ( desc->inputChannels != 1 && desc->inputChannels != 2 && @@ -65,12 +65,12 @@ Ladspa2LMMS::Ladspa2LMMS() m_invalidEffects.append( qMakePair( getName( key ), key ) ); } - else if( desc->type == SINK ) + else if( desc->type == LadspaPluginType::Sink ) { m_analysisTools.append( qMakePair( getName( key ), key ) ); } - else if( desc->type == OTHER ) + else if( desc->type == LadspaPluginType::Other ) { m_otherPlugins.append( qMakePair( getName( key ), key ) ); diff --git a/src/core/LadspaControl.cpp b/src/core/LadspaControl.cpp index 7e8e92623..3282a0c7a 100644 --- a/src/core/LadspaControl.cpp +++ b/src/core/LadspaControl.cpp @@ -53,7 +53,7 @@ LadspaControl::LadspaControl( Model * _parent, port_desc_t * _port, switch( m_port->data_type ) { - case TOGGLED: + case BufferDataType::Toggled: m_toggledModel.setInitValue( static_cast( m_port->def ) ); connect( &m_toggledModel, SIGNAL(dataChanged()), @@ -66,8 +66,8 @@ LadspaControl::LadspaControl( Model * _parent, port_desc_t * _port, m_toggledModel.setScaleLogarithmic( m_port->suggests_logscale ); break; - case INTEGER: - case ENUM: + case BufferDataType::Integer: + case BufferDataType::Enum: m_knobModel.setRange( static_cast( m_port->max ), static_cast( m_port->min ), 1 + static_cast( m_port->max - @@ -80,7 +80,7 @@ LadspaControl::LadspaControl( Model * _parent, port_desc_t * _port, m_knobModel.setScaleLogarithmic( m_port->suggests_logscale ); break; - case FLOATING: + case BufferDataType::Floating: m_knobModel.setRange( m_port->min, m_port->max, ( m_port->max - m_port->min ) / ( m_port->name.toUpper() == "GAIN" @@ -93,7 +93,7 @@ LadspaControl::LadspaControl( Model * _parent, port_desc_t * _port, m_knobModel.setScaleLogarithmic( m_port->suggests_logscale ); break; - case TIME: + case BufferDataType::Time: m_tempoSyncKnobModel.setRange( m_port->min, m_port->max, ( m_port->max - m_port->min ) / 800.0f ); @@ -116,13 +116,13 @@ LADSPA_Data LadspaControl::value() { switch( m_port->data_type ) { - case TOGGLED: + case BufferDataType::Toggled: return static_cast( m_toggledModel.value() ); - case INTEGER: - case ENUM: - case FLOATING: + case BufferDataType::Integer: + case BufferDataType::Enum: + case BufferDataType::Floating: return static_cast( m_knobModel.value() ); - case TIME: + case BufferDataType::Time: return static_cast( m_tempoSyncKnobModel.value() ); default: qWarning( "LadspaControl::value(): BAD BAD BAD\n" ); @@ -137,13 +137,13 @@ ValueBuffer * LadspaControl::valueBuffer() { switch( m_port->data_type ) { - case TOGGLED: - case INTEGER: - case ENUM: + case BufferDataType::Toggled: + case BufferDataType::Integer: + case BufferDataType::Enum: return nullptr; - case FLOATING: + case BufferDataType::Floating: return m_knobModel.valueBuffer(); - case TIME: + case BufferDataType::Time: return m_tempoSyncKnobModel.valueBuffer(); default: qWarning( "LadspaControl::valueBuffer(): BAD BAD BAD\n" ); @@ -159,17 +159,17 @@ void LadspaControl::setValue( LADSPA_Data _value ) { switch( m_port->data_type ) { - case TOGGLED: + case BufferDataType::Toggled: m_toggledModel.setValue( static_cast( _value ) ); break; - case INTEGER: - case ENUM: + case BufferDataType::Integer: + case BufferDataType::Enum: m_knobModel.setValue( static_cast( _value ) ); break; - case FLOATING: + case BufferDataType::Floating: m_knobModel.setValue( static_cast( _value ) ); break; - case TIME: + case BufferDataType::Time: m_tempoSyncKnobModel.setValue( static_cast( _value ) ); break; @@ -194,15 +194,15 @@ void LadspaControl::saveSettings( QDomDocument& doc, } switch( m_port->data_type ) { - case TOGGLED: + case BufferDataType::Toggled: m_toggledModel.saveSettings( doc, e, "data" ); break; - case INTEGER: - case ENUM: - case FLOATING: + case BufferDataType::Integer: + case BufferDataType::Enum: + case BufferDataType::Floating: m_knobModel.saveSettings( doc, e, "data" ); break; - case TIME: + case BufferDataType::Time: m_tempoSyncKnobModel.saveSettings( doc, e, "data" ); break; default: @@ -230,15 +230,15 @@ void LadspaControl::loadSettings( const QDomElement& parent, const QString& name m_linkEnabledModel.setValue(m_linkEnabledModel.initValue()); switch( m_port->data_type ) { - case TOGGLED: + case BufferDataType::Toggled: m_toggledModel.setValue(m_toggledModel.initValue()); break; - case INTEGER: - case ENUM: - case FLOATING: + case BufferDataType::Integer: + case BufferDataType::Enum: + case BufferDataType::Floating: m_knobModel.setValue(m_knobModel.initValue()); break; - case TIME: + case BufferDataType::Time: m_tempoSyncKnobModel.setValue(m_tempoSyncKnobModel.initValue()); break; default: @@ -265,15 +265,15 @@ void LadspaControl::loadSettings( const QDomElement& parent, const QString& name switch( m_port->data_type ) { - case TOGGLED: + case BufferDataType::Toggled: m_toggledModel.loadSettings( e, dataModelName ); break; - case INTEGER: - case ENUM: - case FLOATING: + case BufferDataType::Integer: + case BufferDataType::Enum: + case BufferDataType::Floating: m_knobModel.loadSettings( e, dataModelName ); break; - case TIME: + case BufferDataType::Time: m_tempoSyncKnobModel.loadSettings( e, dataModelName ); break; default: @@ -290,15 +290,15 @@ void LadspaControl::linkControls( LadspaControl * _control ) { switch( m_port->data_type ) { - case TOGGLED: + case BufferDataType::Toggled: BoolModel::linkModels( &m_toggledModel, _control->toggledModel() ); break; - case INTEGER: - case ENUM: - case FLOATING: + case BufferDataType::Integer: + case BufferDataType::Enum: + case BufferDataType::Floating: FloatModel::linkModels( &m_knobModel, _control->knobModel() ); break; - case TIME: + case BufferDataType::Time: TempoSyncKnobModel::linkModels( &m_tempoSyncKnobModel, _control->tempoSyncKnobModel() ); break; @@ -341,15 +341,15 @@ void LadspaControl::unlinkControls( LadspaControl * _control ) { switch( m_port->data_type ) { - case TOGGLED: + case BufferDataType::Toggled: BoolModel::unlinkModels( &m_toggledModel, _control->toggledModel() ); break; - case INTEGER: - case ENUM: - case FLOATING: + case BufferDataType::Integer: + case BufferDataType::Enum: + case BufferDataType::Floating: FloatModel::unlinkModels( &m_knobModel, _control->knobModel() ); break; - case TIME: + case BufferDataType::Time: TempoSyncKnobModel::unlinkModels( &m_tempoSyncKnobModel, _control->tempoSyncKnobModel() ); break; diff --git a/src/core/LadspaManager.cpp b/src/core/LadspaManager.cpp index 8c3ab8f97..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 ) ) @@ -159,21 +149,21 @@ void LadspaManager::addPlugins( if( plugIn->inputChannels == 0 && plugIn->outputChannels > 0 ) { - plugIn->type = SOURCE; + plugIn->type = LadspaPluginType::Source; } else if( plugIn->inputChannels > 0 && plugIn->outputChannels > 0 ) { - plugIn->type = TRANSFER; + plugIn->type = LadspaPluginType::Transfer; } else if( plugIn->inputChannels > 0 && plugIn->outputChannels == 0 ) { - plugIn->type = SINK; + plugIn->type = LadspaPluginType::Sink; } else { - plugIn->type = OTHER; + plugIn->type = LadspaPluginType::Other; } m_ladspaManagerMap[key] = plugIn; @@ -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 495ae54d4..152e0ad8b 100644 --- a/src/core/LfoController.cpp +++ b/src/core/LfoController.cpp @@ -23,32 +23,34 @@ * */ -#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 { LfoController::LfoController( Model * _parent ) : - Controller( Controller::LfoController, _parent, tr( "LFO Controller" ) ), + Controller( ControllerType::Lfo, _parent, tr( "LFO Controller" ) ), m_baseModel( 0.5, 0.0, 1.0, 0.001, this, tr( "Base value" ) ), m_speedModel( 2.0, 0.01, 20.0, 0.0001, 20000.0, this, tr( "Oscillator speed" ) ), m_amountModel( 1.0, -1.0, 1.0, 0.005, this, tr( "Oscillator amount" ) ), m_phaseModel( 0.0, 0.0, 360.0, 4.0, this, tr( "Oscillator phase" ) ), - m_waveModel( Oscillator::SineWave, 0, Oscillator::NumWaveShapes, + m_waveModel( static_cast(Oscillator::WaveShape::Sine), 0, Oscillator::NumWaveShapes, this, tr( "Oscillator waveform" ) ), m_multiplierModel( 0, 0, 2, this, tr( "Frequency Multiplier" ) ), m_duration( 1000 ), 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 ); @@ -88,6 +89,7 @@ void LfoController::updateValueBuffer() { m_phaseOffset = m_phaseModel.value() / 360.0; float phase = m_currentPhase + m_phaseOffset; + float phasePrev = 0.0f; // roll phase up until we're in sync with period counter m_bufferLastUpdated++; @@ -102,20 +104,45 @@ void LfoController::updateValueBuffer() ValueBuffer *amountBuffer = m_amountModel.valueBuffer(); int amountInc = amountBuffer ? 1 : 0; float *amountPtr = amountBuffer ? &(amountBuffer->values()[ 0 ] ) : &amount; + Oscillator::WaveShape waveshape = static_cast(m_waveModel.value()); for( float& f : m_valueBuffer ) { - const float currentSample = m_sampleFunction != nullptr - ? m_sampleFunction( phase ) - : m_userDefSampleBuffer->userWaveSample( phase ); + float currentSample = 0; + switch (waveshape) + { + case Oscillator::WaveShape::WhiteNoise: + { + if (absFraction(phase) < absFraction(phasePrev)) + { + // Resample when phase period has completed + m_heldSample = m_sampleFunction(phase); + } + currentSample = m_heldSample; + break; + } + case Oscillator::WaveShape::UserDefined: + { + currentSample = Oscillator::userWaveSample(m_userDefSampleBuffer.get(), phase); + break; + } + default: + { + if (m_sampleFunction != nullptr) + { + currentSample = m_sampleFunction(phase); + } + } + } - f = qBound( 0.0f, m_baseModel.value() + ( *amountPtr * currentSample / 2.0f ), 1.0f ); + f = std::clamp(m_baseModel.value() + (*amountPtr * currentSample / 2.0f), 0.0f, 1.0f); + phasePrev = phase; phase += 1.0 / m_duration; amountPtr += amountInc; } - m_currentPhase = absFraction( phase - m_phaseOffset ); + m_currentPhase = absFraction(phase - m_phaseOffset); m_bufferLastUpdated = s_periods; } @@ -149,30 +176,31 @@ void LfoController::updateDuration() void LfoController::updateSampleFunction() { - switch( m_waveModel.value() ) + switch( static_cast(m_waveModel.value()) ) { - case Oscillator::SineWave: + case Oscillator::WaveShape::Sine: + default: m_sampleFunction = &Oscillator::sinSample; break; - case Oscillator::TriangleWave: + case Oscillator::WaveShape::Triangle: m_sampleFunction = &Oscillator::triangleSample; break; - case Oscillator::SawWave: + case Oscillator::WaveShape::Saw: m_sampleFunction = &Oscillator::sawSample; break; - case Oscillator::SquareWave: + case Oscillator::WaveShape::Square: m_sampleFunction = &Oscillator::squareSample; break; - case Oscillator::MoogSawWave: + case Oscillator::WaveShape::MoogSaw: m_sampleFunction = &Oscillator::moogSawSample; break; - case Oscillator::ExponentialWave: + case Oscillator::WaveShape::Exponential: m_sampleFunction = &Oscillator::expSample; break; - case Oscillator::WhiteNoise: + case Oscillator::WaveShape::WhiteNoise: m_sampleFunction = &Oscillator::noiseSample; break; - case Oscillator::UserDefinedWave: + case Oscillator::WaveShape::UserDefined: m_sampleFunction = nullptr; /*TODO: If C++11 is allowed, should change the type of m_sampleFunction be std::function @@ -195,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()); } @@ -210,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/LmmsSemaphore.cpp b/src/core/LmmsSemaphore.cpp new file mode 100644 index 000000000..daa70a45b --- /dev/null +++ b/src/core/LmmsSemaphore.cpp @@ -0,0 +1,143 @@ +/* + * Semaphore.cpp - Semaphore implementation + * + * Copyright (c) 2022-2022 Johannes Lorenz + * + * This file is part of LMMS - https://lmms.io + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program (see COPYING); if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA. + * + */ + +/* + * This code has been copied and adapted from https://github.com/drobilla/jalv + * File src/zix/sem.h + */ + +#include "LmmsSemaphore.h" + +#if defined(LMMS_BUILD_WIN32) +# include +#else +# include +#endif + +#include + +namespace lmms { + +#ifdef LMMS_BUILD_APPLE +Semaphore::Semaphore(unsigned val) +{ + kern_return_t rval = semaphore_create(mach_task_self(), &m_sem, SYNC_POLICY_FIFO, val); + if(rval != 0) { + throw std::system_error(rval, std::system_category(), "Could not create semaphore"); + } +} + +Semaphore::~Semaphore() +{ + semaphore_destroy(mach_task_self(), m_sem); +} + +void Semaphore::post() +{ + semaphore_signal(m_sem); +} + +void Semaphore::wait() +{ + kern_return_t rval = semaphore_wait(m_sem); + if (rval != KERN_SUCCESS) { + throw std::system_error(rval, std::system_category(), "Waiting for semaphore failed"); + } +} + +bool Semaphore::tryWait() +{ + const mach_timespec_t zero = { 0, 0 }; + return semaphore_timedwait(m_sem, zero) == KERN_SUCCESS; +} + +#elif defined(LMMS_BUILD_WIN32) + +Semaphore::Semaphore(unsigned initial) +{ + if(CreateSemaphore(nullptr, initial, LONG_MAX, nullptr) == nullptr) { + throw std::system_error(GetLastError(), std::system_category(), "Could not create semaphore"); + } +} + +Semaphore::~Semaphore() +{ + CloseHandle(m_sem); +} + +void Semaphore::post() +{ + ReleaseSemaphore(m_sem, 1, nullptr); +} + +void Semaphore::wait() +{ + if (WaitForSingleObject(m_sem, INFINITE) != WAIT_OBJECT_0) { + throw std::system_error(GetLastError(), std::system_category(), "Waiting for semaphore failed"); + } +} + +bool Semaphore::tryWait() +{ + return WaitForSingleObject(m_sem, 0) == WAIT_OBJECT_0; +} + +#else /* !defined(LMMS_BUILD_APPLE) && !defined(LMMS_BUILD_WIN32) */ + +Semaphore::Semaphore(unsigned initial) +{ + if(sem_init(&m_sem, 0, initial) != 0) { + throw std::system_error(errno, std::generic_category(), "Could not create semaphore"); + } +} + +Semaphore::~Semaphore() +{ + sem_destroy(&m_sem); +} + +void Semaphore::post() +{ + sem_post(&m_sem); +} + +void Semaphore::wait() +{ + while (sem_wait(&m_sem) != 0) { + if (errno != EINTR) { + throw std::system_error(errno, std::generic_category(), "Waiting for semaphore failed"); + } + /* Otherwise, interrupted, so try again. */ + } +} + +bool Semaphore::tryWait() +{ + return (sem_trywait(&m_sem) == 0); +} + +#endif + +} // namespace lmms + diff --git a/src/core/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 02f3d8775..209640b70 100644 --- a/src/core/MixHelpers.cpp +++ b/src/core/MixHelpers.cpp @@ -121,7 +121,7 @@ bool sanitize( sampleFrame * src, int frames ) } else { - src[f][c] = qBound( -1000.0f, src[f][c], 1000.0f ); + src[f][c] = std::clamp(src[f][c], -1000.0f, 1000.0f); } } } @@ -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 52ce2732c..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() ); @@ -173,8 +172,8 @@ void MixerChannel::doProcessing() m_stillRunning = m_fxChain.processAudioBuffer( m_buffer, fpp, m_hasInput ); AudioEngine::StereoSample peakSamples = Engine::audioEngine()->getPeakValues(m_buffer, fpp); - m_peakLeft = qMax( m_peakLeft, peakSamples.left * v ); - m_peakRight = qMax( m_peakRight, peakSamples.right * v ); + m_peakLeft = std::max(m_peakLeft, peakSamples.left * v); + m_peakRight = std::max(m_peakRight, peakSamples.right * v); } else { @@ -190,20 +189,20 @@ void MixerChannel::doProcessing() Mixer::Mixer() : Model( nullptr ), JournallingObject(), - m_mixerChannels() + m_mixerChannels(), + m_lastSoloed(-1) { // create master channel createChannel(); - m_lastSoloed = -1; } Mixer::~Mixer() { - while( ! m_mixerRoutes.isEmpty() ) + while (!m_mixerRoutes.empty()) { - deleteChannelSend( m_mixerRoutes.first() ); + deleteChannelSend(m_mixerRoutes.front()); } while( m_mixerChannels.size() ) { @@ -224,6 +223,13 @@ int Mixer::createChannel() // reset channel state clearChannel( index ); + // if there is a soloed channel, mute the new track + if (m_lastSoloed != -1 && m_mixerChannels[m_lastSoloed]->m_soloModel.value()) + { + m_mixerChannels[index]->m_muteBeforeSolo = m_mixerChannels[index]->m_muteModel.value(); + m_mixerChannels[index]->m_muteModel.setValue(true); + } + return index; } @@ -286,12 +292,15 @@ void Mixer::deleteChannel( int index ) // go through every instrument and adjust for the channel index change TrackContainer::TrackList tracks; - tracks += Engine::getSong()->tracks(); - tracks += Engine::patternStore()->tracks(); + + auto& songTracks = Engine::getSong()->tracks(); + auto& patternStoreTracks = Engine::patternStore()->tracks(); + tracks.insert(tracks.end(), songTracks.begin(), songTracks.end()); + tracks.insert(tracks.end(), patternStoreTracks.begin(), patternStoreTracks.end()); for( Track* t : tracks ) { - if( t->type() == Track::InstrumentTrack ) + if( t->type() == Track::Type::Instrument ) { auto inst = dynamic_cast(t); int val = inst->mixerChannelModel()->value(0); @@ -307,7 +316,7 @@ void Mixer::deleteChannel( int index ) inst->mixerChannelModel()->setValue(val-1); } } - else if( t->type() == Track::SampleTrack ) + else if( t->type() == Track::Type::Sample ) { auto strk = dynamic_cast(t); int val = strk->mixerChannelModel()->value(0); @@ -328,13 +337,13 @@ void Mixer::deleteChannel( int index ) MixerChannel * ch = m_mixerChannels[index]; // delete all of this channel's sends and receives - while( ! ch->m_sends.isEmpty() ) + while (!ch->m_sends.empty()) { - deleteChannelSend( ch->m_sends.first() ); + deleteChannelSend(ch->m_sends.front()); } - while( ! ch->m_receives.isEmpty() ) + while (!ch->m_receives.empty()) { - deleteChannelSend( ch->m_receives.first() ); + deleteChannelSend(ch->m_receives.front()); } // if m_lastSoloed was our index, reset it @@ -343,7 +352,7 @@ void Mixer::deleteChannel( int index ) else if (m_lastSoloed > index) { --m_lastSoloed; } // actually delete the channel - m_mixerChannels.remove(index); + m_mixerChannels.erase(m_mixerChannels.begin() + index); delete ch; for( int i = index; i < m_mixerChannels.size(); ++i ) @@ -384,14 +393,14 @@ void Mixer::moveChannelLeft( int index ) else if (m_lastSoloed == b) { m_lastSoloed = a; } // go through every instrument and adjust for the channel index change - TrackContainer::TrackList songTrackList = Engine::getSong()->tracks(); - TrackContainer::TrackList patternTrackList = Engine::patternStore()->tracks(); + const TrackContainer::TrackList& songTrackList = Engine::getSong()->tracks(); + const TrackContainer::TrackList& patternTrackList = Engine::patternStore()->tracks(); for (const auto& trackList : {songTrackList, patternTrackList}) { for (const auto& track : trackList) { - if (track->type() == Track::InstrumentTrack) + if (track->type() == Track::Type::Instrument) { auto inst = (InstrumentTrack*)track; int val = inst->mixerChannelModel()->value(0); @@ -404,7 +413,7 @@ void Mixer::moveChannelLeft( int index ) inst->mixerChannelModel()->setValue(a); } } - else if (track->type() == Track::SampleTrack) + else if (track->type() == Track::Type::Sample) { auto strk = (SampleTrack*)track; int val = strk->mixerChannelModel()->value(0); @@ -470,13 +479,13 @@ MixerRoute * Mixer::createRoute( MixerChannel * from, MixerChannel * to, float a auto route = new MixerRoute(from, to, amount); // add us to from's sends - from->m_sends.append( route ); + from->m_sends.push_back(route); // add us to to's receives - to->m_receives.append( route ); + to->m_receives.push_back(route); // add us to mixer's list - Engine::mixer()->m_mixerRoutes.append( route ); + Engine::mixer()->m_mixerRoutes.push_back(route); Engine::audioEngine()->doneChangeInModel(); return route; @@ -505,12 +514,22 @@ void Mixer::deleteChannelSend( mix_ch_t fromChannel, mix_ch_t toChannel ) void Mixer::deleteChannelSend( MixerRoute * route ) { Engine::audioEngine()->requestChangeInModel(); + + auto removeFromMixerRoute = [route](MixerRouteVector& routeVec) + { + auto it = std::find(routeVec.begin(), routeVec.end(), route); + if (it != routeVec.end()) { routeVec.erase(it); } + }; + // remove us from from's sends - route->sender()->m_sends.remove( route->sender()->m_sends.indexOf( route ) ); + removeFromMixerRoute(route->sender()->m_sends); + // remove us from to's receives - route->receiver()->m_receives.remove( route->receiver()->m_receives.indexOf( route ) ); + removeFromMixerRoute(route->receiver()->m_receives); + // remove us from mixer's list - Engine::mixer()->m_mixerRoutes.remove( Engine::mixer()->m_mixerRoutes.indexOf( route ) ); + removeFromMixerRoute(Engine::mixer()->m_mixerRoutes); + delete route; Engine::audioEngine()->doneChangeInModel(); } @@ -610,7 +629,7 @@ void Mixer::masterMix( sampleFrame * _buf ) // also instantly add all muted channels as they don't need to care // about their senders, and can just increment the deps of their // recipients right away. - AudioEngineWorkerThread::resetJobQueue( AudioEngineWorkerThread::JobQueue::Dynamic ); + AudioEngineWorkerThread::resetJobQueue( AudioEngineWorkerThread::JobQueue::OperationMode::Dynamic ); for( MixerChannel * ch : m_mixerChannels ) { ch->m_muted = ch->m_muteModel.value(); @@ -702,14 +721,15 @@ 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) { // delete existing sends - while( ! ch->m_sends.isEmpty() ) + while (!ch->m_sends.empty()) { - deleteChannelSend( ch->m_sends.first() ); + deleteChannelSend(ch->m_sends.front()); } // add send to master @@ -717,9 +737,9 @@ void Mixer::clearChannel(mix_ch_t index) } // delete receives - while( ! ch->m_receives.isEmpty() ) + while (!ch->m_receives.empty()) { - deleteChannelSend( ch->m_receives.first() ); + deleteChannelSend(ch->m_receives.front()); } } @@ -739,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) @@ -785,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( @@ -825,5 +844,44 @@ void Mixer::validateChannelName( int index, int oldIndex ) } } +bool Mixer::isChannelInUse(int index) +{ + // check if the index mixer channel receives audio from any other channel + if (!m_mixerChannels[index]->m_receives.empty()) + { + return true; + } + + // check if the destination mixer channel on any instrument or sample track is the index mixer channel + TrackContainer::TrackList tracks; + + auto& songTracks = Engine::getSong()->tracks(); + auto& patternStoreTracks = Engine::patternStore()->tracks(); + tracks.insert(tracks.end(), songTracks.begin(), songTracks.end()); + tracks.insert(tracks.end(), patternStoreTracks.begin(), patternStoreTracks.end()); + + for (const auto t : tracks) + { + if (t->type() == Track::Type::Instrument) + { + auto inst = dynamic_cast(t); + if (inst->mixerChannelModel()->value() == index) + { + return true; + } + } + else if (t->type() == Track::Type::Sample) + { + auto strack = dynamic_cast(t); + if (strack->mixerChannelModel()->value() == index) + { + return true; + } + } + } + + return false; +} + } // namespace lmms diff --git a/src/core/Model.cpp b/src/core/Model.cpp index dd277bc4e..634e2fbed 100644 --- a/src/core/Model.cpp +++ b/src/core/Model.cpp @@ -27,22 +27,47 @@ namespace lmms { +Model::Model(Model* parent, QString displayName, bool defaultConstructed) : + QObject(parent), + m_displayName(displayName), + m_defaultConstructed(defaultConstructed) +{ +} + +bool Model::isDefaultConstructed() const +{ + return m_defaultConstructed; +} + +Model* Model::parentModel() const +{ + return dynamic_cast(parent()); +} + +QString Model::displayName() const +{ + return m_displayName; +} + +void Model::setDisplayName(const QString& displayName) +{ + m_displayName = displayName; +} + QString Model::fullDisplayName() const { - const QString & n = displayName(); - if( parentModel() ) + const QString n = displayName(); + + if (parentModel()) { const QString p = parentModel()->fullDisplayName(); - if( n.isEmpty() && p.isEmpty() ) + + if (!p.isEmpty()) { - return QString(); + return p + ">" + n; } - else if( p.isEmpty() ) - { - return n; - } - return p + ">" + n; } + return n; } diff --git a/src/core/Note.cpp b/src/core/Note.cpp index e4e5f0e5d..ed3a00f10 100644 --- a/src/core/Note.cpp +++ b/src/core/Note.cpp @@ -38,13 +38,13 @@ Note::Note( const TimePos & length, const TimePos & pos, int key, volume_t volume, panning_t panning, DetuningHelper * detuning ) : m_selected( false ), - m_oldKey( qBound( 0, key, NumKeys ) ), + m_oldKey(std::clamp(key, 0, NumKeys)), m_oldPos( pos ), m_oldLength( length ), m_isPlaying( false ), - m_key( qBound( 0, key, NumKeys ) ), - m_volume( qBound( MinVolume, volume, MaxVolume ) ), - m_panning( qBound( PanningLeft, panning, PanningRight ) ), + m_key(std::clamp(key, 0, NumKeys)), + m_volume(std::clamp(volume, MinVolume, MaxVolume)), + m_panning(std::clamp(panning, PanningLeft, PanningRight)), m_length( length ), m_pos( pos ), m_detuning( nullptr ) @@ -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 ) { @@ -114,7 +115,7 @@ void Note::setPos( const TimePos & pos ) void Note::setKey( const int key ) { - const int k = qBound( 0, key, NumKeys - 1 ); + const int k = std::clamp(key, 0, NumKeys - 1); m_key = k; } @@ -123,7 +124,7 @@ void Note::setKey( const int key ) void Note::setVolume( volume_t volume ) { - const volume_t v = qBound( MinVolume, volume, MaxVolume ); + const volume_t v = std::clamp(volume, MinVolume, MaxVolume); m_volume = v; } @@ -132,7 +133,7 @@ void Note::setVolume( volume_t volume ) void Note::setPanning( panning_t panning ) { - const panning_t p = qBound( PanningLeft, panning, PanningRight ); + const panning_t p = std::clamp(panning, PanningLeft, PanningRight); m_panning = p; } @@ -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 ) { @@ -192,11 +194,14 @@ void Note::saveSettings( QDomDocument & doc, QDomElement & parent ) void Note::loadSettings( const QDomElement & _this ) { const int oldKey = _this.attribute( "tone" ).toInt() + _this.attribute( "oct" ).toInt() * KeysPerOctave; - m_key = qMax( oldKey, _this.attribute( "key" ).toInt() ); + m_key = std::max(oldKey, _this.attribute("key").toInt()); m_volume = _this.attribute( "vol" ).toInt(); 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() ) { @@ -216,7 +221,7 @@ void Note::createDetuning() m_detuning = new DetuningHelper; (void) m_detuning->automationClip(); m_detuning->setRange( -MaxDetuning, MaxDetuning, 0.5f ); - m_detuning->automationClip()->setProgressionType( AutomationClip::LinearProgression ); + m_detuning->automationClip()->setProgressionType( AutomationClip::ProgressionType::Linear ); } } diff --git a/src/core/NotePlayHandle.cpp b/src/core/NotePlayHandle.cpp index e87637476..2c1c21931 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( TypeNotePlayHandle, _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 ), @@ -104,12 +104,12 @@ NotePlayHandle::NotePlayHandle( InstrumentTrack* instrumentTrack, setFrames( _frames ); // inform attached components about new MIDI note (used for recording in Piano Roll) - if( m_origin == OriginMidiInput ) + if( m_origin == Origin::MidiInput ) { m_instrumentTrack->midiNoteOn( *this ); } - if(m_instrumentTrack->instrument() && m_instrumentTrack->instrument()->flags() & Instrument::IsSingleStreamed ) + if(m_instrumentTrack->instrument() && m_instrumentTrack->instrument()->flags() & Instrument::Flag::IsSingleStreamed ) { setUsesBuffer( false ); } @@ -350,9 +350,9 @@ fpp_t NotePlayHandle::framesLeftForCurrentPeriod() const { if( m_totalFramesPlayed == 0 ) { - return (fpp_t) qMin( framesLeft(), Engine::audioEngine()->framesPerPeriod() - offset() ); + return static_cast(std::min(framesLeft(), Engine::audioEngine()->framesPerPeriod() - offset())); } - return (fpp_t) qMin( framesLeft(), Engine::audioEngine()->framesPerPeriod() ); + return static_cast(std::min(framesLeft(), Engine::audioEngine()->framesPerPeriod())); } @@ -384,7 +384,7 @@ void NotePlayHandle::noteOff( const f_cnt_t _s ) // then set some variables indicating release-state m_framesBeforeRelease = _s; - m_releaseFramesToDo = qMax( 0, actualReleaseFramesToDo() ); + m_releaseFramesToDo = std::max(0, actualReleaseFramesToDo()); if( m_hasMidiNote ) { @@ -400,7 +400,7 @@ void NotePlayHandle::noteOff( const f_cnt_t _s ) // inform attached components about MIDI finished (used for recording in Piano Roll) if (!instrumentTrack()->isSustainPedalPressed()) { - if( m_origin == OriginMidiInput ) + if( m_origin == Origin::MidiInput ) { setLength( TimePos( static_cast( totalFramesPlayed() / Engine::framesPerTick() ) ) ); m_instrumentTrack->midiNoteOff( *this ); @@ -557,14 +557,20 @@ void NotePlayHandle::updateFrequency() -void NotePlayHandle::processTimePos( const TimePos& time ) +void NotePlayHandle::processTimePos(const TimePos& time, float pitchValue, bool isRecording) { - if( detuning() && time >= songGlobalParentOffset()+pos() ) + if (!detuning() || time < songGlobalParentOffset() + pos()) { return; } + + if (isRecording && m_origin == Origin::MidiInput) { - const float v = detuning()->automationClip()->valueAt( time - songGlobalParentOffset() - pos() ); - if( !typeInfo::isEqual( v, m_baseDetuning->value() ) ) + detuning()->automationClip()->recordValue(time - songGlobalParentOffset() - pos(), pitchValue / 100); + } + else + { + const float v = detuning()->automationClip()->valueAt(time - songGlobalParentOffset() - pos()); + if (!typeInfo::isEqual(v, m_baseDetuning->value())) { - m_baseDetuning->setValue( v ); + m_baseDetuning->setValue(v); updateFrequency(); } } @@ -575,8 +581,8 @@ void NotePlayHandle::processTimePos( const TimePos& time ) void NotePlayHandle::resize( const bpm_t _new_tempo ) { - if (origin() == OriginMidiInput || - (origin() == OriginNoteStacking && m_parent->origin() == OriginMidiInput)) + if (origin() == Origin::MidiInput || + (origin() == Origin::NoteStacking && m_parent->origin() == Origin::MidiInput)) { // Don't resize notes from MIDI input - they should continue to play // until the key is released, and their large duration can cause @@ -604,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 ) { @@ -649,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 ) { @@ -664,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 189dede6e..0330fad58 100644 --- a/src/core/Oscillator.cpp +++ b/src/core/Oscillator.cpp @@ -90,21 +90,22 @@ void Oscillator::update(sampleFrame* ab, const fpp_t frames, const ch_cnt_t chnl m_isModulator = modulator; if (m_subOsc != nullptr) { - switch (m_modulationAlgoModel->value()) + switch (static_cast(m_modulationAlgoModel->value())) { - case PhaseModulation: + case ModulationAlgo::PhaseModulation: updatePM(ab, frames, chnl); break; - case AmplitudeModulation: + case ModulationAlgo::AmplitudeModulation: updateAM(ab, frames, chnl); break; - case SignalMix: + case ModulationAlgo::SignalMix: + default: updateMix(ab, frames, chnl); break; - case SynchronizedBySubOsc: + case ModulationAlgo::SynchronizedBySubOsc: updateSync(ab, frames, chnl); break; - case FrequencyModulation: + case ModulationAlgo::FrequencyModulation: updateFM(ab, frames, chnl); } } @@ -181,25 +182,29 @@ 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; } sample_t Oscillator::s_waveTables - [Oscillator::WaveShapes::NumWaveShapeTables] + [Oscillator::NumWaveShapeTables] [OscillatorConstants::WAVE_TABLES_PER_WAVEFORM_COUNT] [OscillatorConstants::WAVETABLE_LENGTH]; fftwf_plan Oscillator::s_fftPlan; @@ -235,9 +240,9 @@ void Oscillator::generateWaveTables() // Start from the table that contains the least number of bands, and re-use each table in the following // iteration, adding more bands in each step and avoiding repeated computation of earlier bands. using generator_t = void (*)(int, sample_t*, int); - auto simpleGen = [](WaveShapes shape, generator_t generator) + auto simpleGen = [](WaveShape shape, generator_t generator) { - const int shapeID = shape - FirstWaveShapeTable; + const int shapeID = static_cast(shape) - FirstWaveShapeTable; int lastBands = 0; // Clear the first wave table @@ -273,7 +278,7 @@ void Oscillator::generateWaveTables() Oscillator::s_sampleBuffer[i] = moogSawSample((float)i / (float)OscillatorConstants::WAVETABLE_LENGTH); } fftwf_execute(s_fftPlan); - generateFromFFT(OscillatorConstants::MAX_FREQ / freqFromWaveTableBand(i), s_waveTables[WaveShapes::MoogSawWave - FirstWaveShapeTable][i]); + generateFromFFT(OscillatorConstants::MAX_FREQ / freqFromWaveTableBand(i), s_waveTables[static_cast(WaveShape::MoogSaw) - FirstWaveShapeTable][i]); } // Generate exponential tables @@ -284,7 +289,7 @@ void Oscillator::generateWaveTables() s_sampleBuffer[i] = expSample((float)i / (float)OscillatorConstants::WAVETABLE_LENGTH); } fftwf_execute(s_fftPlan); - generateFromFFT(OscillatorConstants::MAX_FREQ / freqFromWaveTableBand(i), s_waveTables[WaveShapes::ExponentialWave - FirstWaveShapeTable][i]); + generateFromFFT(OscillatorConstants::MAX_FREQ / freqFromWaveTableBand(i), s_waveTables[static_cast(WaveShape::Exponential) - FirstWaveShapeTable][i]); } }; @@ -292,18 +297,18 @@ void Oscillator::generateWaveTables() // but since threading is not essential in this case, it is easier and more reliable to simply generate // the wavetables serially. Remove the the check and #else branch once std::thread is well supported. #if !defined(__MINGW32__) && !defined(__MINGW64__) - std::thread sawThread(simpleGen, WaveShapes::SawWave, generateSawWaveTable); - std::thread squareThread(simpleGen, WaveShapes::SquareWave, generateSquareWaveTable); - std::thread triangleThread(simpleGen, WaveShapes::TriangleWave, generateTriangleWaveTable); + std::thread sawThread(simpleGen, WaveShape::Saw, generateSawWaveTable); + std::thread squareThread(simpleGen, WaveShape::Square, generateSquareWaveTable); + std::thread triangleThread(simpleGen, WaveShape::Triangle, generateTriangleWaveTable); std::thread fftThread(fftGen); sawThread.join(); squareThread.join(); triangleThread.join(); fftThread.join(); #else - simpleGen(WaveShapes::SawWave, generateSawWaveTable); - simpleGen(WaveShapes::SquareWave, generateSquareWaveTable); - simpleGen(WaveShapes::TriangleWave, generateTriangleWaveTable); + simpleGen(WaveShape::Saw, generateSawWaveTable); + simpleGen(WaveShape::Square, generateSquareWaveTable); + simpleGen(WaveShape::Triangle, generateTriangleWaveTable); fftGen(); #endif } @@ -314,32 +319,32 @@ void Oscillator::generateWaveTables() void Oscillator::updateNoSub( sampleFrame * _ab, const fpp_t _frames, const ch_cnt_t _chnl ) { - switch( m_waveShapeModel->value() ) + switch( static_cast(m_waveShapeModel->value()) ) { - case SineWave: + case WaveShape::Sine: default: - updateNoSub( _ab, _frames, _chnl ); + updateNoSub( _ab, _frames, _chnl ); break; - case TriangleWave: - updateNoSub( _ab, _frames, _chnl ); + case WaveShape::Triangle: + updateNoSub( _ab, _frames, _chnl ); break; - case SawWave: - updateNoSub( _ab, _frames, _chnl ); + case WaveShape::Saw: + updateNoSub( _ab, _frames, _chnl ); break; - case SquareWave: - updateNoSub( _ab, _frames, _chnl ); + case WaveShape::Square: + updateNoSub( _ab, _frames, _chnl ); break; - case MoogSawWave: - updateNoSub( _ab, _frames, _chnl ); + case WaveShape::MoogSaw: + updateNoSub( _ab, _frames, _chnl ); break; - case ExponentialWave: - updateNoSub( _ab, _frames, _chnl ); + case WaveShape::Exponential: + updateNoSub( _ab, _frames, _chnl ); break; - case WhiteNoise: - updateNoSub( _ab, _frames, _chnl ); + case WaveShape::WhiteNoise: + updateNoSub( _ab, _frames, _chnl ); break; - case UserDefinedWave: - updateNoSub( _ab, _frames, _chnl ); + case WaveShape::UserDefined: + updateNoSub( _ab, _frames, _chnl ); break; } } @@ -350,32 +355,32 @@ void Oscillator::updateNoSub( sampleFrame * _ab, const fpp_t _frames, void Oscillator::updatePM( sampleFrame * _ab, const fpp_t _frames, const ch_cnt_t _chnl ) { - switch( m_waveShapeModel->value() ) + switch( static_cast(m_waveShapeModel->value()) ) { - case SineWave: + case WaveShape::Sine: default: - updatePM( _ab, _frames, _chnl ); + updatePM( _ab, _frames, _chnl ); break; - case TriangleWave: - updatePM( _ab, _frames, _chnl ); + case WaveShape::Triangle: + updatePM( _ab, _frames, _chnl ); break; - case SawWave: - updatePM( _ab, _frames, _chnl ); + case WaveShape::Saw: + updatePM( _ab, _frames, _chnl ); break; - case SquareWave: - updatePM( _ab, _frames, _chnl ); + case WaveShape::Square: + updatePM( _ab, _frames, _chnl ); break; - case MoogSawWave: - updatePM( _ab, _frames, _chnl ); + case WaveShape::MoogSaw: + updatePM( _ab, _frames, _chnl ); break; - case ExponentialWave: - updatePM( _ab, _frames, _chnl ); + case WaveShape::Exponential: + updatePM( _ab, _frames, _chnl ); break; - case WhiteNoise: - updatePM( _ab, _frames, _chnl ); + case WaveShape::WhiteNoise: + updatePM( _ab, _frames, _chnl ); break; - case UserDefinedWave: - updatePM( _ab, _frames, _chnl ); + case WaveShape::UserDefined: + updatePM( _ab, _frames, _chnl ); break; } } @@ -386,32 +391,32 @@ void Oscillator::updatePM( sampleFrame * _ab, const fpp_t _frames, void Oscillator::updateAM( sampleFrame * _ab, const fpp_t _frames, const ch_cnt_t _chnl ) { - switch( m_waveShapeModel->value() ) + switch( static_cast(m_waveShapeModel->value()) ) { - case SineWave: + case WaveShape::Sine: default: - updateAM( _ab, _frames, _chnl ); + updateAM( _ab, _frames, _chnl ); break; - case TriangleWave: - updateAM( _ab, _frames, _chnl ); + case WaveShape::Triangle: + updateAM( _ab, _frames, _chnl ); break; - case SawWave: - updateAM( _ab, _frames, _chnl ); + case WaveShape::Saw: + updateAM( _ab, _frames, _chnl ); break; - case SquareWave: - updateAM( _ab, _frames, _chnl ); + case WaveShape::Square: + updateAM( _ab, _frames, _chnl ); break; - case MoogSawWave: - updateAM( _ab, _frames, _chnl ); + case WaveShape::MoogSaw: + updateAM( _ab, _frames, _chnl ); break; - case ExponentialWave: - updateAM( _ab, _frames, _chnl ); + case WaveShape::Exponential: + updateAM( _ab, _frames, _chnl ); break; - case WhiteNoise: - updateAM( _ab, _frames, _chnl ); + case WaveShape::WhiteNoise: + updateAM( _ab, _frames, _chnl ); break; - case UserDefinedWave: - updateAM( _ab, _frames, _chnl ); + case WaveShape::UserDefined: + updateAM( _ab, _frames, _chnl ); break; } } @@ -422,32 +427,32 @@ void Oscillator::updateAM( sampleFrame * _ab, const fpp_t _frames, void Oscillator::updateMix( sampleFrame * _ab, const fpp_t _frames, const ch_cnt_t _chnl ) { - switch( m_waveShapeModel->value() ) + switch( static_cast(m_waveShapeModel->value()) ) { - case SineWave: + case WaveShape::Sine: default: - updateMix( _ab, _frames, _chnl ); + updateMix( _ab, _frames, _chnl ); break; - case TriangleWave: - updateMix( _ab, _frames, _chnl ); + case WaveShape::Triangle: + updateMix( _ab, _frames, _chnl ); break; - case SawWave: - updateMix( _ab, _frames, _chnl ); + case WaveShape::Saw: + updateMix( _ab, _frames, _chnl ); break; - case SquareWave: - updateMix( _ab, _frames, _chnl ); + case WaveShape::Square: + updateMix( _ab, _frames, _chnl ); break; - case MoogSawWave: - updateMix( _ab, _frames, _chnl ); + case WaveShape::MoogSaw: + updateMix( _ab, _frames, _chnl ); break; - case ExponentialWave: - updateMix( _ab, _frames, _chnl ); + case WaveShape::Exponential: + updateMix( _ab, _frames, _chnl ); break; - case WhiteNoise: - updateMix( _ab, _frames, _chnl ); + case WaveShape::WhiteNoise: + updateMix( _ab, _frames, _chnl ); break; - case UserDefinedWave: - updateMix( _ab, _frames, _chnl ); + case WaveShape::UserDefined: + updateMix( _ab, _frames, _chnl ); break; } } @@ -458,32 +463,32 @@ void Oscillator::updateMix( sampleFrame * _ab, const fpp_t _frames, void Oscillator::updateSync( sampleFrame * _ab, const fpp_t _frames, const ch_cnt_t _chnl ) { - switch( m_waveShapeModel->value() ) + switch( static_cast(m_waveShapeModel->value()) ) { - case SineWave: + case WaveShape::Sine: default: - updateSync( _ab, _frames, _chnl ); + updateSync( _ab, _frames, _chnl ); break; - case TriangleWave: - updateSync( _ab, _frames, _chnl ); + case WaveShape::Triangle: + updateSync( _ab, _frames, _chnl ); break; - case SawWave: - updateSync( _ab, _frames, _chnl ); + case WaveShape::Saw: + updateSync( _ab, _frames, _chnl ); break; - case SquareWave: - updateSync( _ab, _frames, _chnl ); + case WaveShape::Square: + updateSync( _ab, _frames, _chnl ); break; - case MoogSawWave: - updateSync( _ab, _frames, _chnl ); + case WaveShape::MoogSaw: + updateSync( _ab, _frames, _chnl ); break; - case ExponentialWave: - updateSync( _ab, _frames, _chnl ); + case WaveShape::Exponential: + updateSync( _ab, _frames, _chnl ); break; - case WhiteNoise: - updateSync( _ab, _frames, _chnl ); + case WaveShape::WhiteNoise: + updateSync( _ab, _frames, _chnl ); break; - case UserDefinedWave: - updateSync( _ab, _frames, _chnl ); + case WaveShape::UserDefined: + updateSync( _ab, _frames, _chnl ); break; } } @@ -494,32 +499,32 @@ void Oscillator::updateSync( sampleFrame * _ab, const fpp_t _frames, void Oscillator::updateFM( sampleFrame * _ab, const fpp_t _frames, const ch_cnt_t _chnl ) { - switch( m_waveShapeModel->value() ) + switch( static_cast(m_waveShapeModel->value()) ) { - case SineWave: + case WaveShape::Sine: default: - updateFM( _ab, _frames, _chnl ); + updateFM( _ab, _frames, _chnl ); break; - case TriangleWave: - updateFM( _ab, _frames, _chnl ); + case WaveShape::Triangle: + updateFM( _ab, _frames, _chnl ); break; - case SawWave: - updateFM( _ab, _frames, _chnl ); + case WaveShape::Saw: + updateFM( _ab, _frames, _chnl ); break; - case SquareWave: - updateFM( _ab, _frames, _chnl ); + case WaveShape::Square: + updateFM( _ab, _frames, _chnl ); break; - case MoogSawWave: - updateFM( _ab, _frames, _chnl ); + case WaveShape::MoogSaw: + updateFM( _ab, _frames, _chnl ); break; - case ExponentialWave: - updateFM( _ab, _frames, _chnl ); + case WaveShape::Exponential: + updateFM( _ab, _frames, _chnl ); break; - case WhiteNoise: - updateFM( _ab, _frames, _chnl ); + case WaveShape::WhiteNoise: + updateFM( _ab, _frames, _chnl ); break; - case UserDefinedWave: - updateFM( _ab, _frames, _chnl ); + case WaveShape::UserDefined: + updateFM( _ab, _frames, _chnl ); break; } } @@ -568,7 +573,7 @@ float Oscillator::syncInit( sampleFrame * _ab, const fpp_t _frames, // if we have no sub-osc, we can't do any modulation... just get our samples -template +template void Oscillator::updateNoSub( sampleFrame * _ab, const fpp_t _frames, const ch_cnt_t _chnl ) { @@ -586,7 +591,7 @@ void Oscillator::updateNoSub( sampleFrame * _ab, const fpp_t _frames, // do pm by using sub-osc as modulator -template +template void Oscillator::updatePM( sampleFrame * _ab, const fpp_t _frames, const ch_cnt_t _chnl ) { @@ -607,7 +612,7 @@ void Oscillator::updatePM( sampleFrame * _ab, const fpp_t _frames, // do am by using sub-osc as modulator -template +template void Oscillator::updateAM( sampleFrame * _ab, const fpp_t _frames, const ch_cnt_t _chnl ) { @@ -626,7 +631,7 @@ void Oscillator::updateAM( sampleFrame * _ab, const fpp_t _frames, // do mix by using sub-osc as mix-sample -template +template void Oscillator::updateMix( sampleFrame * _ab, const fpp_t _frames, const ch_cnt_t _chnl ) { @@ -646,7 +651,7 @@ void Oscillator::updateMix( sampleFrame * _ab, const fpp_t _frames, // sync with sub-osc (every time sub-osc starts new period, we also start new // period) -template +template void Oscillator::updateSync( sampleFrame * _ab, const fpp_t _frames, const ch_cnt_t _chnl ) { @@ -669,7 +674,7 @@ void Oscillator::updateSync( sampleFrame * _ab, const fpp_t _frames, // do fm by using sub-osc as modulator -template +template void Oscillator::updateFM( sampleFrame * _ab, const fpp_t _frames, const ch_cnt_t _chnl ) { @@ -690,7 +695,7 @@ void Oscillator::updateFM( sampleFrame * _ab, const fpp_t _frames, template<> -inline sample_t Oscillator::getSample(const float sample) +inline sample_t Oscillator::getSample(const float sample) { const float current_freq = m_freq * m_detuning_div_samplerate * Engine::audioEngine()->processingSampleRate(); @@ -708,12 +713,12 @@ inline sample_t Oscillator::getSample(const float sample) template<> -inline sample_t Oscillator::getSample( +inline sample_t Oscillator::getSample( const float _sample ) { if (m_useWaveTable && !m_isModulator) { - return wtSample(s_waveTables[WaveShapes::TriangleWave - FirstWaveShapeTable],_sample); + return wtSample(s_waveTables[static_cast(WaveShape::Triangle) - FirstWaveShapeTable],_sample); } else { @@ -725,12 +730,12 @@ inline sample_t Oscillator::getSample( template<> -inline sample_t Oscillator::getSample( +inline sample_t Oscillator::getSample( const float _sample ) { if (m_useWaveTable && !m_isModulator) { - return wtSample(s_waveTables[WaveShapes::SawWave - FirstWaveShapeTable], _sample); + return wtSample(s_waveTables[static_cast(WaveShape::Saw) - FirstWaveShapeTable], _sample); } else { @@ -742,12 +747,12 @@ inline sample_t Oscillator::getSample( template<> -inline sample_t Oscillator::getSample( +inline sample_t Oscillator::getSample( const float _sample ) { if (m_useWaveTable && !m_isModulator) { - return wtSample(s_waveTables[WaveShapes::SquareWave - FirstWaveShapeTable], _sample); + return wtSample(s_waveTables[static_cast(WaveShape::Square) - FirstWaveShapeTable], _sample); } else { @@ -759,12 +764,12 @@ inline sample_t Oscillator::getSample( template<> -inline sample_t Oscillator::getSample( +inline sample_t Oscillator::getSample( const float _sample ) { if (m_useWaveTable && !m_isModulator) { - return wtSample(s_waveTables[WaveShapes::MoogSawWave - FirstWaveShapeTable], _sample); + return wtSample(s_waveTables[static_cast(WaveShape::MoogSaw) - FirstWaveShapeTable], _sample); } else { @@ -776,12 +781,12 @@ inline sample_t Oscillator::getSample( template<> -inline sample_t Oscillator::getSample( +inline sample_t Oscillator::getSample( const float _sample ) { if (m_useWaveTable && !m_isModulator) { - return wtSample(s_waveTables[WaveShapes::ExponentialWave - FirstWaveShapeTable], _sample); + return wtSample(s_waveTables[static_cast(WaveShape::Exponential) - FirstWaveShapeTable], _sample); } else { @@ -793,7 +798,7 @@ inline sample_t Oscillator::getSample( template<> -inline sample_t Oscillator::getSample( +inline sample_t Oscillator::getSample( const float _sample ) { return( noiseSample( _sample ) ); @@ -803,16 +808,16 @@ inline sample_t Oscillator::getSample( template<> -inline sample_t Oscillator::getSample( +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 9af9bda6a..15a1d1f54 100644 --- a/src/core/PatternClip.cpp +++ b/src/core/PatternClip.cpp @@ -60,10 +60,11 @@ void PatternClip::saveSettings(QDomDocument& doc, QDomElement& element) element.setAttribute( "pos", startPosition() ); } 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()); } } @@ -78,7 +79,8 @@ void PatternClip::loadSettings(const QDomElement& element) movePosition( element.attribute( "pos" ).toInt() ); } changeLength( element.attribute( "len" ).toInt() ); - if( element.attribute( "muted" ).toInt() != isMuted() ) + setStartTimeOffset(element.attribute("off").toInt()); + if (static_cast(element.attribute("muted").toInt()) != isMuted()) { toggleMute(); } @@ -88,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/PatternStore.cpp b/src/core/PatternStore.cpp index f91c42b72..6af434f65 100644 --- a/src/core/PatternStore.cpp +++ b/src/core/PatternStore.cpp @@ -44,7 +44,7 @@ PatternStore::PatternStore() : // not change upon setCurrentPattern()-call connect(&m_patternComboBoxModel, SIGNAL(dataUnchanged()), this, SLOT(currentPatternChanged())); - setType(PatternContainer); + setType(Type::Pattern); } @@ -61,7 +61,7 @@ bool PatternStore::play(TimePos start, fpp_t frames, f_cnt_t offset, int clipNum start = start % (lengthOfPattern(clipNum) * TimePos::ticksPerBar()); - TrackList tl = tracks(); + const TrackList& tl = tracks(); for (Track * t : tl) { if (t->play(start, frames, offset, clipNum)) @@ -97,7 +97,7 @@ bar_t PatternStore::lengthOfPattern(int pattern) const // Don't create Clips here if they don't exist if (pattern < t->numOfClips()) { - maxLength = qMax(maxLength, t->getClip(pattern)->length()); + maxLength = std::max(maxLength, t->getClip(pattern)->length()); } } @@ -109,7 +109,7 @@ bar_t PatternStore::lengthOfPattern(int pattern) const int PatternStore::numOfPatterns() const { - return Engine::getSong()->countTracks(Track::PatternTrack); + return Engine::getSong()->countTracks(Track::Type::Pattern); } @@ -117,7 +117,7 @@ int PatternStore::numOfPatterns() const void PatternStore::removePattern(int pattern) { - TrackList tl = tracks(); + const TrackList& tl = tracks(); for (Track * t : tl) { delete t->getClip(pattern); @@ -125,7 +125,7 @@ void PatternStore::removePattern(int pattern) } if (pattern <= currentPattern()) { - setCurrentPattern(qMax(currentPattern() - 1, 0)); + setCurrentPattern(std::max(currentPattern() - 1, 0)); } } @@ -134,7 +134,7 @@ void PatternStore::removePattern(int pattern) void PatternStore::swapPattern(int pattern1, int pattern2) { - TrackList tl = tracks(); + const TrackList& tl = tracks(); for (Track * t : tl) { t->swapPositionOfClips(pattern1, pattern2); @@ -159,7 +159,7 @@ void PatternStore::updatePatternTrack(Clip* clip) void PatternStore::fixIncorrectPositions() { - TrackList tl = tracks(); + const TrackList& tl = tracks(); for (Track * t : tl) { for (int i = 0; i < numOfPatterns(); ++i) @@ -174,7 +174,7 @@ void PatternStore::fixIncorrectPositions() void PatternStore::play() { - if (Engine::getSong()->playMode() != Song::Mode_PlayPattern) + if (Engine::getSong()->playMode() != Song::PlayMode::Pattern) { Engine::getSong()->playPattern(); } @@ -215,10 +215,10 @@ void PatternStore::updateComboBox() void PatternStore::currentPatternChanged() { // now update all track-labels (the current one has to become white, the others gray) - TrackList tl = Engine::getSong()->tracks(); + const TrackList& tl = Engine::getSong()->tracks(); for (Track * t : tl) { - if (t->type() == Track::PatternTrack) + if (t->type() == Track::Type::Pattern) { t->dataChanged(); } @@ -230,7 +230,7 @@ void PatternStore::currentPatternChanged() void PatternStore::createClipsForPattern(int pattern) { - TrackList tl = tracks(); + const TrackList& tl = tracks(); for (Track * t : tl) { t->createClipsForPattern(pattern); diff --git a/src/core/PeakController.cpp b/src/core/PeakController.cpp index a9215325e..cfcd3765c 100644 --- a/src/core/PeakController.cpp +++ b/src/core/PeakController.cpp @@ -46,7 +46,7 @@ bool PeakController::m_buggedFile; PeakController::PeakController( Model * _parent, PeakControllerEffect * _peak_effect ) : - Controller( Controller::PeakController, _parent, tr( "Peak Controller" ) ), + Controller( ControllerType::Peak, _parent, tr( "Peak Controller" ) ), m_peakEffect( _peak_effect ), m_currentSample( 0.0f ) { @@ -161,12 +161,11 @@ void PeakController::loadSettings( const QDomElement & _this ) effectId = m_loadCount++; } - PeakControllerEffectVector::Iterator i; - for( i = s_effects.begin(); i != s_effects.end(); ++i ) + for (const auto& effect : s_effects) { - if( (*i)->m_effectId == effectId ) + if (effect->m_effectId == effectId) { - m_peakEffect = *i; + m_peakEffect = effect; return; } } @@ -190,16 +189,14 @@ PeakController * PeakController::getControllerBySetting(const QDomElement & _thi { int effectId = _this.attribute( "effectId" ).toInt(); - PeakControllerEffectVector::Iterator i; - //Backward compatibility for bug in <= 0.4.15 . For >= 1.0.0 , //foundCount should always be 1 because m_effectId is initialized with rand() int foundCount = 0; if( m_buggedFile == false ) { - for( i = s_effects.begin(); i != s_effects.end(); ++i ) + for (const auto& effect : s_effects) { - if( (*i)->m_effectId == effectId ) + if (effect->m_effectId == effectId) { foundCount++; } @@ -208,9 +205,9 @@ PeakController * PeakController::getControllerBySetting(const QDomElement & _thi { m_buggedFile = true; int newEffectId = 0; - for( i = s_effects.begin(); i != s_effects.end(); ++i ) + for (const auto& effect : s_effects) { - (*i)->m_effectId = newEffectId++; + effect->m_effectId = newEffectId++; } QMessageBox msgBox; msgBox.setIcon( QMessageBox::Information ); @@ -231,11 +228,11 @@ PeakController * PeakController::getControllerBySetting(const QDomElement & _thi } m_getCount++; //NB: m_getCount should be increased even m_buggedFile is false - for( i = s_effects.begin(); i != s_effects.end(); ++i ) + for (const auto& effect : s_effects) { - if( (*i)->m_effectId == effectId ) + if (effect->m_effectId == effectId) { - return (*i)->controller(); + return effect->controller(); } } diff --git a/src/core/Piano.cpp b/src/core/Piano.cpp index 82323526e..0ddbf5c60 100644 --- a/src/core/Piano.cpp +++ b/src/core/Piano.cpp @@ -49,12 +49,12 @@ namespace lmms */ static const auto KEY_ORDER = std::array { -// C CIS D DIS - Piano::WhiteKey, Piano::BlackKey, Piano::WhiteKey, Piano::BlackKey, -// E F FIS G - Piano::WhiteKey, Piano::WhiteKey, Piano::BlackKey, Piano::WhiteKey, -// GIS A AIS B - Piano::BlackKey, Piano::WhiteKey, Piano::BlackKey, Piano::WhiteKey +// C CIS D DIS + Piano::KeyType::White, Piano::KeyType::Black, Piano::KeyType::White, Piano::KeyType::Black, +// E F FIS G + Piano::KeyType::White, Piano::KeyType::White, Piano::KeyType::Black, Piano::KeyType::White, +// GIS A AIS B + Piano::KeyType::Black, Piano::KeyType::White, Piano::KeyType::Black, Piano::KeyType::White } ; @@ -127,7 +127,7 @@ bool Piano::isBlackKey(int key) { int keyCode = key % KeysPerOctave; - return KEY_ORDER[keyCode] == Piano::BlackKey; + return KEY_ORDER[keyCode] == Piano::KeyType::Black; } diff --git a/src/core/Plugin.cpp b/src/core/Plugin.cpp index edc183c23..f165ddf75 100644 --- a/src/core/Plugin.cpp +++ b/src/core/Plugin.cpp @@ -50,7 +50,7 @@ static Plugin::Descriptor dummyPluginDescriptor = QT_TRANSLATE_NOOP( "PluginBrowser", "no description" ), "Tobias Doerffel ", 0x0100, - Plugin::Undefined, + Plugin::Type::Undefined, &dummyLoader, nullptr } ; @@ -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/PluginFactory.cpp b/src/core/PluginFactory.cpp index 81a6122d1..ec0f4ec4e 100644 --- a/src/core/PluginFactory.cpp +++ b/src/core/PluginFactory.cpp @@ -85,8 +85,7 @@ void PluginFactory::setupSearchPaths() addRelativeIfExists(PLUGIN_DIR); #endif // Or via an environment variable: - QString env_path; - if (!(env_path = qgetenv("LMMS_PLUGIN_DIR")).isEmpty()) + if (const char* env_path = std::getenv("LMMS_PLUGIN_DIR")) QDir::addSearchPath("plugins", env_path); QDir::addSearchPath("plugins", ConfigManager::inst()->workingDir() + "plugins"); @@ -105,12 +104,12 @@ PluginFactory* getPluginFactory() return PluginFactory::instance(); } -const Plugin::DescriptorList PluginFactory::descriptors() const +Plugin::DescriptorList PluginFactory::descriptors() const { return m_descriptors.values(); } -const Plugin::DescriptorList PluginFactory::descriptors(Plugin::PluginTypes type) const +Plugin::DescriptorList PluginFactory::descriptors(Plugin::Type type) const { return m_descriptors.values(type); } @@ -120,12 +119,12 @@ const PluginFactory::PluginInfoList& PluginFactory::pluginInfos() const return m_pluginInfos; } -const PluginFactory::PluginInfoAndKey PluginFactory::pluginSupportingExtension(const QString& ext) +PluginFactory::PluginInfoAndKey PluginFactory::pluginSupportingExtension(const QString& ext) { return m_pluginByExt.value(ext, PluginInfoAndKey()); } -const PluginFactory::PluginInfo PluginFactory::pluginInfo(const char* name) const +PluginFactory::PluginInfo PluginFactory::pluginInfo(const char* name) const { for (const PluginInfo& info : m_pluginInfos) { @@ -248,7 +247,7 @@ void PluginFactory::discoverPlugins() -const QString PluginFactory::PluginInfo::name() const +QString PluginFactory::PluginInfo::name() const { return descriptor ? descriptor->name : QString(); } diff --git a/src/core/PluginIssue.cpp b/src/core/PluginIssue.cpp index f8ae06bc7..c9cf3400f 100644 --- a/src/core/PluginIssue.cpp +++ b/src/core/PluginIssue.cpp @@ -34,43 +34,43 @@ const char *PluginIssue::msgFor(const PluginIssueType &it) { switch (it) { - case unknownPortFlow: + case PluginIssueType::UnknownPortFlow: return "unknown port flow for mandatory port"; - case unknownPortType: + case PluginIssueType::UnknownPortType: return "unknown port type for mandatory port"; - case tooManyInputChannels: + case PluginIssueType::TooManyInputChannels: return "too many audio input channels"; - case tooManyOutputChannels: + case PluginIssueType::TooManyOutputChannels: return "too many audio output channels"; - case tooManyMidiInputChannels: + case PluginIssueType::TooManyMidiInputChannels: return "too many MIDI input channels"; - case tooManyMidiOutputChannels: + case PluginIssueType::TooManyMidiOutputChannels: return "too many MIDI output channels"; - case noOutputChannel: + case PluginIssueType::NoOutputChannel: return "no audio output channel"; - case portHasNoDef: + case PluginIssueType::PortHasNoDef: return "port is missing default value"; - case portHasNoMin: + case PluginIssueType::PortHasNoMin: return "port is missing min value"; - case portHasNoMax: + case PluginIssueType::PortHasNoMax: return "port is missing max value"; - case minGreaterMax: + case PluginIssueType::MinGreaterMax: return "port minimum is greater than maximum"; - case defaultValueNotInRange: + case PluginIssueType::DefaultValueNotInRange: return "default value is not in range [min, max]"; - case logScaleMinMissing: + case PluginIssueType::LogScaleMinMissing: return "logscale requires minimum value"; - case logScaleMaxMissing: + case PluginIssueType::LogScaleMaxMissing: return "logscale requires maximum value"; - case logScaleMinMaxDifferentSigns: + case PluginIssueType::LogScaleMinMaxDifferentSigns: return "logscale with min < 0 < max"; - case featureNotSupported: + case PluginIssueType::FeatureNotSupported: return "required feature not supported"; - case badPortType: + case PluginIssueType::BadPortType: return "unsupported port type"; - case blacklisted: + case PluginIssueType::Blacklisted: return "blacklisted plugin"; - case noIssue: + case PluginIssueType::NoIssue: return nullptr; } return nullptr; diff --git a/src/core/PresetPreviewPlayHandle.cpp b/src/core/PresetPreviewPlayHandle.cpp index 6a5b86424..0930de0de 100644 --- a/src/core/PresetPreviewPlayHandle.cpp +++ b/src/core/PresetPreviewPlayHandle.cpp @@ -48,7 +48,7 @@ public: m_dataMutex() { setJournalling( false ); - m_previewInstrumentTrack = dynamic_cast( Track::create( Track::InstrumentTrack, this ) ); + m_previewInstrumentTrack = dynamic_cast( Track::create( Track::Type::Instrument, this ) ); m_previewInstrumentTrack->setJournalling( false ); m_previewInstrumentTrack->setPreviewMode( true ); } @@ -116,7 +116,7 @@ PreviewTrackContainer * PresetPreviewPlayHandle::s_previewTC; PresetPreviewPlayHandle::PresetPreviewPlayHandle( const QString & _preset_file, bool _load_by_plugin, DataFile *dataFile ) : - PlayHandle( TypePresetPreviewHandle ), + PlayHandle( Type::PresetPreviewHandle ), m_previewNote(nullptr) { setUsesBuffer( false ); @@ -169,7 +169,7 @@ PresetPreviewPlayHandle::PresetPreviewPlayHandle( const QString & _preset_file, // make sure, our preset-preview-track does not appear in any MIDI- // devices list, so just disable receiving/sending MIDI-events at all s_previewTC->previewInstrumentTrack()-> - midiPort()->setMode( MidiPort::Disabled ); + midiPort()->setMode( MidiPort::Mode::Disabled ); Engine::audioEngine()->requestChangeInModel(); // create note-play-handle for it diff --git a/src/core/ProjectJournal.cpp b/src/core/ProjectJournal.cpp index bc1fee5c0..fc77c98e6 100644 --- a/src/core/ProjectJournal.cpp +++ b/src/core/ProjectJournal.cpp @@ -58,7 +58,7 @@ void ProjectJournal::undo() if( jo ) { - DataFile curState( DataFile::JournalData ); + DataFile curState( DataFile::Type::JournalData ); jo->saveState( curState, curState.content() ); m_redoCheckPoints.push( CheckPoint( c.joID, curState ) ); @@ -83,7 +83,7 @@ void ProjectJournal::redo() if( jo ) { - DataFile curState( DataFile::JournalData ); + DataFile curState( DataFile::Type::JournalData ); jo->saveState( curState, curState.content() ); m_undoCheckPoints.push( CheckPoint( c.joID, curState ) ); @@ -115,7 +115,7 @@ void ProjectJournal::addJournalCheckPoint( JournallingObject *jo ) { m_redoCheckPoints.clear(); - DataFile dataFile( DataFile::JournalData ); + DataFile dataFile( DataFile::Type::JournalData ); jo->saveState( dataFile, dataFile.content() ); m_undoCheckPoints.push( CheckPoint( jo->id(), dataFile ) ); diff --git a/src/core/ProjectRenderer.cpp b/src/core/ProjectRenderer.cpp index da6c729c8..3d83515f2 100644 --- a/src/core/ProjectRenderer.cpp +++ b/src/core/ProjectRenderer.cpp @@ -42,15 +42,15 @@ namespace lmms const std::array ProjectRenderer::fileEncodeDevices { - FileEncodeDevice{ ProjectRenderer::WaveFile, + FileEncodeDevice{ ProjectRenderer::ExportFileFormat::Wave, QT_TRANSLATE_NOOP( "ProjectRenderer", "WAV (*.wav)" ), ".wav", &AudioFileWave::getInst }, - FileEncodeDevice{ ProjectRenderer::FlacFile, + FileEncodeDevice{ ProjectRenderer::ExportFileFormat::Flac, QT_TRANSLATE_NOOP("ProjectRenderer", "FLAC (*.flac)"), ".flac", &AudioFileFlac::getInst }, - FileEncodeDevice{ ProjectRenderer::OggFile, + FileEncodeDevice{ ProjectRenderer::ExportFileFormat::Ogg, QT_TRANSLATE_NOOP( "ProjectRenderer", "OGG (*.ogg)" ), ".ogg", #ifdef LMMS_HAVE_OGGVORBIS @@ -59,7 +59,7 @@ const std::array ProjectRenderer::fileEnco nullptr #endif }, - FileEncodeDevice{ ProjectRenderer::MP3File, + FileEncodeDevice{ ProjectRenderer::ExportFileFormat::MP3, QT_TRANSLATE_NOOP( "ProjectRenderer", "MP3 (*.mp3)" ), ".mp3", #ifdef LMMS_HAVE_MP3LAME @@ -71,7 +71,7 @@ const std::array ProjectRenderer::fileEnco // Insert your own file-encoder infos here. // Maybe one day the user can add own encoders inside the program. - FileEncodeDevice{ ProjectRenderer::NumFileFormats, nullptr, nullptr, nullptr } + FileEncodeDevice{ ProjectRenderer::ExportFileFormat::Count, nullptr, nullptr, nullptr } } ; @@ -80,7 +80,7 @@ const std::array ProjectRenderer::fileEnco ProjectRenderer::ProjectRenderer( const AudioEngine::qualitySettings & qualitySettings, const OutputSettings & outputSettings, - ExportFileFormats exportFileFormat, + ExportFileFormat exportFileFormat, const QString & outputFilename ) : QThread( Engine::audioEngine() ), m_fileDev( nullptr ), @@ -88,7 +88,7 @@ ProjectRenderer::ProjectRenderer( const AudioEngine::qualitySettings & qualitySe m_progress( 0 ), m_abort( false ) { - AudioFileDeviceInstantiaton audioEncoderFactory = fileEncodeDevices[exportFileFormat].m_getDevInst; + AudioFileDeviceInstantiaton audioEncoderFactory = fileEncodeDevices[static_cast(exportFileFormat)].m_getDevInst; if (audioEncoderFactory) { @@ -110,11 +110,11 @@ ProjectRenderer::ProjectRenderer( const AudioEngine::qualitySettings & qualitySe // Little help function for getting file format from a file extension // (only for registered file-encoders). -ProjectRenderer::ExportFileFormats ProjectRenderer::getFileFormatFromExtension( +ProjectRenderer::ExportFileFormat ProjectRenderer::getFileFormatFromExtension( const QString & _ext ) { int idx = 0; - while( fileEncodeDevices[idx].m_fileFormat != NumFileFormats ) + while( fileEncodeDevices[idx].m_fileFormat != ExportFileFormat::Count ) { if( QString( fileEncodeDevices[idx].m_extension ) == _ext ) { @@ -123,16 +123,16 @@ ProjectRenderer::ExportFileFormats ProjectRenderer::getFileFormatFromExtension( ++idx; } - return( WaveFile ); // Default. + return( ExportFileFormat::Wave ); // Default. } QString ProjectRenderer::getFileExtensionFromFormat( - ExportFileFormats fmt ) + ExportFileFormat fmt ) { - return fileEncodeDevices[fmt].m_extension; + return fileEncodeDevices[static_cast(fmt)].m_extension; } @@ -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/ProjectVersion.cpp b/src/core/ProjectVersion.cpp index b6ee2843f..ffdb8cd43 100644 --- a/src/core/ProjectVersion.cpp +++ b/src/core/ProjectVersion.cpp @@ -95,12 +95,12 @@ int ProjectVersion::compare(const ProjectVersion & a, const ProjectVersion & b, if(aPat != bPat){ return aPat - bPat; } // Decide how many optional identifiers we care about - const int maxLabels = qMax(0, limit - 3); + const int maxLabels = std::max(0, limit - 3); const auto aLabels = a.getLabels().mid(0, maxLabels); const auto bLabels = b.getLabels().mid(0, maxLabels); // We can only compare identifiers if both versions have them - const int commonLabels = qMin(aLabels.size(), bLabels.size()); + const int commonLabels = std::min(aLabels.size(), bLabels.size()); // If one version has optional labels and the other doesn't, // the one without them is bigger if (commonLabels == 0){ return bLabels.size() - aLabels.size(); } diff --git a/src/core/RemotePlugin.cpp b/src/core/RemotePlugin.cpp index 14c6f3f32..088bc3cd8 100644 --- a/src/core/RemotePlugin.cpp +++ b/src/core/RemotePlugin.cpp @@ -359,7 +359,7 @@ bool RemotePlugin::process( const sampleFrame * _in_buf, sampleFrame * _out_buf memset( m_audioBuffer.get(), 0, m_audioBufferSize ); - ch_cnt_t inputs = qMin( m_inputCount, DEFAULT_CHANNELS ); + ch_cnt_t inputs = std::min(m_inputCount, DEFAULT_CHANNELS); if( _in_buf != nullptr && inputs > 0 ) { @@ -403,8 +403,8 @@ bool RemotePlugin::process( const sampleFrame * _in_buf, sampleFrame * _out_buf waitForMessage( IdProcessingDone ); unlock(); - const ch_cnt_t outputs = qMin( m_outputCount, - DEFAULT_CHANNELS ); + const ch_cnt_t outputs = std::min(m_outputCount, + DEFAULT_CHANNELS); if( m_splitChannels ) { for( ch_cnt_t ch = 0; ch < outputs; ++ch ) @@ -427,8 +427,8 @@ bool RemotePlugin::process( const sampleFrame * _in_buf, sampleFrame * _out_buf // clear buffer, if plugin didn't fill up both channels BufferManager::clear( _out_buf, frames ); - for( ch_cnt_t ch = 0; ch < - qMin( DEFAULT_CHANNELS, outputs ); ++ch ) + for (ch_cnt_t ch = 0; ch < + std::min(DEFAULT_CHANNELS, outputs); ++ch) { for( fpp_t frame = 0; frame < frames; ++frame ) { diff --git a/src/core/RenderManager.cpp b/src/core/RenderManager.cpp index 56b4ce9d7..d375b95ee 100644 --- a/src/core/RenderManager.cpp +++ b/src/core/RenderManager.cpp @@ -23,6 +23,7 @@ */ #include +#include #include "RenderManager.h" @@ -37,7 +38,7 @@ namespace lmms RenderManager::RenderManager( const AudioEngine::qualitySettings & qualitySettings, const OutputSettings & outputSettings, - ProjectRenderer::ExportFileFormats fmt, + ProjectRenderer::ExportFileFormat fmt, QString outputPath) : m_qualitySettings(qualitySettings), m_oldQualitySettings( Engine::audioEngine()->currentQualitySettings() ), @@ -69,7 +70,7 @@ void RenderManager::renderNextTrack() { m_activeRenderer.reset(); - if( m_tracksToRender.isEmpty() ) + if (m_tracksToRender.empty()) { // nothing left to render restoreMutedState(); @@ -97,29 +98,29 @@ void RenderManager::renderNextTrack() // Render the song into individual tracks void RenderManager::renderTracks() { - const TrackContainer::TrackList & tl = Engine::getSong()->tracks(); + const TrackContainer::TrackList& tl = Engine::getSong()->tracks(); // find all currently unnmuted tracks -- we want to render these. for (const auto& tk : tl) { - Track::TrackTypes type = tk->type(); + Track::Type type = tk->type(); // Don't render automation tracks if ( tk->isMuted() == false && - ( type == Track::InstrumentTrack || type == Track::SampleTrack ) ) + ( type == Track::Type::Instrument || type == Track::Type::Sample ) ) { m_unmuted.push_back(tk); } } - const TrackContainer::TrackList t2 = Engine::patternStore()->tracks(); + const TrackContainer::TrackList& t2 = Engine::patternStore()->tracks(); for (const auto& tk : t2) { - Track::TrackTypes type = tk->type(); + Track::Type type = tk->type(); // Don't render automation tracks if ( tk->isMuted() == false && - ( type == Track::InstrumentTrack || type == Track::SampleTrack ) ) + ( type == Track::Type::Instrument || type == Track::Type::Sample ) ) { m_unmuted.push_back(tk); } @@ -169,7 +170,7 @@ void RenderManager::render(QString outputPath) // Unmute all tracks that were muted while rendering tracks void RenderManager::restoreMutedState() { - while( !m_unmuted.isEmpty() ) + while (!m_unmuted.empty()) { Track* restoreTrack = m_unmuted.back(); m_unmuted.pop_back(); @@ -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/Sample.cpp b/src/core/Sample.cpp new file mode 100644 index 000000000..a07b100bf --- /dev/null +++ b/src/core/Sample.cpp @@ -0,0 +1,247 @@ +/* + * 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()->processingSampleRate() * 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 +{ + 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 677366fdb..6483dd522 100644 --- a/src/core/SampleBuffer.cpp +++ b/src/core/SampleBuffer.cpp @@ -23,1569 +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 - - bool fileLoadError = false; - 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.size() > fileSizeMax * 1024 * 1024) - { - fileLoadError = true; - } - else - { - // Use QFile to handle unicode file names on Windows - QFile f(file); - SNDFILE * sndFile; - SF_INFO sfInfo; - sfInfo.format = 0; - if (f.open(QIODevice::ReadOnly) && (sndFile = sf_open_fd(f.handle(), SFM_READ, &sfInfo, false))) - { - f_cnt_t frames = sfInfo.frames; - int rate = sfInfo.samplerate; - if (frames / rate > sampleLengthMax * 60) - { - fileLoadError = true; - } - sf_close(sndFile); - } - f.close(); - } - - if (!fileLoadError) - { -#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) // 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) - { - QString title = tr("Fail to open file"); - QString message = tr("Audio files are limited to %1 MB " - "in size and %2 minutes of playing time" - ).arg(fileSizeMax).arg(sampleLengthMax); - 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 = qBound(0, f_cnt_t(m_startFrame * oldRateToNewRateRatio), m_frames); - m_endFrame = qBound(m_startFrame, f_cnt_t(m_endFrame * oldRateToNewRateRatio), m_frames); - m_loopStartFrame = qBound(0, f_cnt_t(m_loopStartFrame * oldRateToNewRateRatio), m_frames); - m_loopEndFrame = qBound(m_loopStartFrame, f_cnt_t(m_loopEndFrame * oldRateToNewRateRatio), 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 = qMax(state->m_frameIndex, startFrame); - - if (loopMode == LoopOff) - { - if (playFrame >= endFrame || (endFrame - playFrame) / freqFactor == 0) - { - // the sample is done being played - return false; - } - } - else if (loopMode == LoopOn) - { - 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 LoopOff: - playFrame += srcData.input_frames_used; - break; - case LoopOn: - playFrame += srcData.input_frames_used; - playFrame = getLoopedIndex(playFrame, loopStartFrame, loopEndFrame); - break; - case LoopPingPong: - { - 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 LoopOff: - playFrame += frames; - break; - case LoopOn: - playFrame += frames; - playFrame = getLoopedIndex(playFrame, loopStartFrame, loopEndFrame); - break; - case LoopPingPong: - { - 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 == LoopOff) - { - if (index + frames <= end) - { - return m_data + index; - } - } - else if (loopMode == LoopOn) - { - if (index + frames <= loopEnd) - { - return m_data + index; - } - } - else - { - if (!*backwards && index + frames < loopEnd) - { - return m_data + index; - } - } - - *tmp = MM_ALLOC( frames); - - if (loopMode == LoopOff) - { - 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 == LoopOn) - { - f_cnt_t copied = qMin(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 = qMin(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 = qMin(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 = qMin(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 = qMin(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 = qMin(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 = qBound(minData, sqrtRmsData, maxData); - const float minRmsData = qBound(minData, -sqrtRmsData, 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 = qMin(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 sample_buffer.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 sample_buffer.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 6396d49f3..42d4f6441 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( Engine::getSong()->Mode_PlaySong ).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 @@ -74,11 +69,11 @@ SampleClip::SampleClip( Track * _track ) : switch( getTrack()->trackContainer()->type() ) { - case TrackContainer::PatternContainer: + case TrackContainer::Type::Pattern: setAutoResize( true ); break; - case TrackContainer::SongContainer: + case TrackContainer::Type::Song: // move down default: setAutoResize( false ); @@ -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(); } @@ -117,49 +109,49 @@ SampleClip::~SampleClip() void SampleClip::changeLength( const TimePos & _length ) { - Clip::changeLength( qMax( static_cast( _length ), 1 ) ); + Clip::changeLength(std::max(static_cast(_length), 1)); } -const QString & SampleClip::sampleFile() const +const QString& SampleClip::sampleFile() const { - return m_sampleBuffer->audioFile(); + return m_sample.sampleFile(); } - - -void SampleClip::setSampleBuffer( SampleBuffer* sb ) +void SampleClip::setSampleBuffer(std::shared_ptr sb) { - Engine::audioEngine()->requestChangeInModel(); - sharedObject::unref( m_sampleBuffer ); - Engine::audioEngine()->doneChangeInModel(); - m_sampleBuffer = sb; + { + const auto guard = Engine::audioEngine()->requestChangesGuard(); + m_sample = Sample(std::move(sb)); + } updateLength(); emit sampleChanged(); } - - -void SampleClip::setSampleFile( const QString & _sf ) +void SampleClip::setSampleFile(const QString& sf) { - int length; - if ( _sf.isEmpty() ) - { //When creating an empty sample clip make it a bar long - float nom = Engine::getSong()->getTimeSigModel().getNumerator(); - float den = Engine::getSong()->getTimeSigModel().getDenominator(); - length = DefaultTicksPerBar * ( nom / den ); - } - else - { //Otherwise set it to the sample's length - m_sampleBuffer->setAudioFile( _sf ); + int length = 0; + + if (!sf.isEmpty()) + { + //Otherwise set it to the sample's length + m_sample = Sample(gui::SampleLoader::createBufferFromFile(sf)); length = sampleLength(); } - changeLength(length); - setStartTimeOffset( 0 ); + if (length == 0) + { + //If there is no sample, make the clip a bar long + float nom = Engine::getSong()->getTimeSigModel().getNumerator(); + float den = Engine::getSong()->getTimeSigModel().getDenominator(); + length = DefaultTicksPerBar * (nom / den); + } + + changeLength(length); + setStartTimeOffset(0); emit sampleChanged(); emit playbackPositionChanged(); @@ -179,7 +171,7 @@ void SampleClip::toggleRecord() void SampleClip::playbackPositionChanged() { - Engine::audioEngine()->removePlayHandlesOfTypes( getTrack(), PlayHandle::TypeSamplePlayHandle ); + Engine::audioEngine()->removePlayHandlesOfTypes( getTrack(), PlayHandle::Type::SamplePlayHandle ); auto st = dynamic_cast(getTrack()); st->setPlayingClips( false ); } @@ -225,7 +217,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())); } @@ -233,7 +225,7 @@ TimePos SampleClip::sampleLength() const void SampleClip::setSampleStartFrame(f_cnt_t startFrame) { - m_sampleBuffer->setStartFrame( startFrame ); + m_sample.setStartFrame(startFrame); } @@ -241,7 +233,7 @@ void SampleClip::setSampleStartFrame(f_cnt_t startFrame) void SampleClip::setSamplePlayLength(f_cnt_t length) { - m_sampleBuffer->setEndFrame( length ); + m_sample.setEndFrame(length); } @@ -264,15 +256,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"); } @@ -288,32 +280,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()->processingSampleRate(); + + 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..94f8e387b --- /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()->processingSampleRate(); + 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 77658e1d8..61ded132a 100644 --- a/src/core/SamplePlayHandle.cpp +++ b/src/core/SamplePlayHandle.cpp @@ -35,9 +35,9 @@ namespace lmms { -SamplePlayHandle::SamplePlayHandle( SampleBuffer* sampleBuffer , bool ownAudioPort ) : - PlayHandle( TypeSamplePlayHandle ), - m_sampleBuffer( sharedObject::ref( sampleBuffer ) ), +SamplePlayHandle::SamplePlayHandle(Sample* sample, bool ownAudioPort) : + PlayHandle( Type::SamplePlayHandle ), + 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()->processingSampleRate()) / m_sample->sampleRate()); } diff --git a/src/core/SampleRecordHandle.cpp b/src/core/SampleRecordHandle.cpp index edebd27fd..6857efa83 100644 --- a/src/core/SampleRecordHandle.cpp +++ b/src/core/SampleRecordHandle.cpp @@ -37,7 +37,7 @@ namespace lmms SampleRecordHandle::SampleRecordHandle( SampleClip* clip ) : - PlayHandle( TypeSamplePlayHandle ), + PlayHandle( Type::SamplePlayHandle ), m_framesRecorded( 0 ), m_minLength( clip->length() ), m_track( clip->getTrack() ), @@ -51,13 +51,8 @@ SampleRecordHandle::SampleRecordHandle( SampleClip* clip ) : SampleRecordHandle::~SampleRecordHandle() { - if( !m_buffers.empty() ) - { - SampleBuffer* sb; - createSampleBuffer( &sb ); - m_clip->setSampleBuffer( sb ); - } - + if (!m_buffers.empty()) { m_clip->setSampleBuffer(createSampleBuffer()); } + while( !m_buffers.empty() ) { delete[] m_buffers.front().first; @@ -111,28 +106,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 d553534b0..a9dde6536 100644 --- a/src/core/Song.cpp +++ b/src/core/Song.cpp @@ -70,7 +70,7 @@ tick_t TimePos::s_ticksPerBar = DefaultTicksPerBar; Song::Song() : TrackContainer(), m_globalAutomationTrack( dynamic_cast( - Track::create( Track::HiddenAutomationTrack, + Track::create( Track::Type::HiddenAutomation, this ) ) ), m_globalGroove(GrooveFactory::create("none")), m_tempoModel( DefaultTempo, MinTempo, MaxTempo, this, tr( "Tempo" ) ), @@ -92,7 +92,7 @@ Song::Song() : m_savingProject( false ), m_loadingProject( false ), m_isCancelled( false ), - m_playMode( Mode_None ), + m_playMode( PlayMode::None ), m_length( 0 ), m_midiClipToPlay( nullptr ), m_loopMidiClip( false ), @@ -120,7 +120,7 @@ Song::Song() : this, SLOT(masterPitchChanged()));*/ qRegisterMetaType( "lmms::Note" ); - setType( SongContainer ); + setType( Type::Song ); for (auto& scale : m_scales) {scale = std::make_shared();} for (auto& keymap : m_keymaps) {keymap = std::make_shared();} @@ -188,14 +188,9 @@ void Song::setTimeSignature() -void Song::savePos() +void Song::savePlayStartPosition() { - gui::TimeLineWidget* tl = m_playPos[m_playMode].m_timeLine; - - if( tl != nullptr ) - { - tl->savePos( m_playPos[m_playMode] ); - } + getTimeline().setPlayStartPosition(getPlayPos()); } @@ -209,7 +204,7 @@ void Song::processNextBuffer() if (!m_playing) { return; } // At the beginning of the song, we have to reset the LFOs - if (m_playMode == Mode_PlaySong && getPlayPos() == 0) + if (m_playMode == PlayMode::Song && getPlayPos() == 0) { EnvelopeAndLfoParameters::instances()->reset(); } @@ -220,11 +215,11 @@ void Song::processNextBuffer() // Determine the list of tracks to play and the clip number switch (m_playMode) { - case Mode_PlaySong: + case PlayMode::Song: trackList = tracks(); break; - case Mode_PlayPattern: + case PlayMode::Pattern: if (Engine::patternStore()->numOfPatterns() > 0) { clipNum = Engine::patternStore()->currentPattern(); @@ -232,7 +227,7 @@ void Song::processNextBuffer() } break; - case Mode_PlayMidiClip: + case PlayMode::MidiClip: if (m_midiClipToPlay) { clipNum = m_midiClipToPlay->getTrack()->getClipNum(m_midiClipToPlay); @@ -262,16 +257,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); } @@ -295,23 +291,23 @@ void Song::processNextBuffer() // If we are playing a pattern track, or a MIDI clip with no loop enabled, // loop back to the beginning when we reach the end - if (m_playMode == Mode_PlayPattern) + if (m_playMode == PlayMode::Pattern) { enforceLoop(TimePos{0}, TimePos{Engine::patternStore()->lengthOfCurrentPattern(), 0}); } - else if (m_playMode == Mode_PlayMidiClip && m_loopMidiClip && !loopEnabled) + else if (m_playMode == PlayMode::MidiClip && m_loopMidiClip && !loopEnabled) { enforceLoop(TimePos{0}, m_midiClipToPlay->length()); } // 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--; @@ -353,9 +349,9 @@ void Song::processNextBuffer() frameOffsetInPeriod += framesToPlay; frameOffsetInTick += framesToPlay; getPlayPos().setCurrentFrame(frameOffsetInTick); - m_elapsedMilliSeconds[m_playMode] += TimePos::ticksToMilliseconds(framesToPlay / framesPerTick, getTempo()); - m_elapsedBars = m_playPos[Mode_PlaySong].getBar(); - m_elapsedTicks = (m_playPos[Mode_PlaySong].getTicks() % ticksPerBar()) / 48; + m_elapsedMilliSeconds[static_cast(m_playMode)] += TimePos::ticksToMilliseconds(framesToPlay / framesPerTick, getTempo()); + m_elapsedBars = getPlayPos(PlayMode::Song).getBar(); + m_elapsedTicks = (getPlayPos(PlayMode::Song).getTicks() % ticksPerBar()) / 48; } } @@ -371,12 +367,12 @@ void Song::processAutomations(const TrackList &tracklist, TimePos timeStart, fpp switch (m_playMode) { - case Mode_PlaySong: + case PlayMode::Song: break; - case Mode_PlayPattern: + case PlayMode::Pattern: { Q_ASSERT(tracklist.size() == 1); - Q_ASSERT(tracklist.at(0)->type() == Track::PatternTrack); + Q_ASSERT(tracklist.at(0)->type() == Track::Type::Pattern); auto patternTrack = dynamic_cast(tracklist.at(0)); container = Engine::patternStore(); clipNum = patternTrack->patternIndex(); @@ -387,12 +383,12 @@ void Song::processAutomations(const TrackList &tracklist, TimePos timeStart, fpp } values = container->automatedValuesAt(timeStart, clipNum); - TrackList tracks = container->tracks(); + const TrackList& tracks = container->tracks(); Track::clipVector clips; for (Track* track : tracks) { - if (track->type() == Track::AutomationTrack) { + if (track->type() == Track::Type::Automation) { track->getClipsInRange(clips, 0, timeStart); } } @@ -448,12 +444,12 @@ void Song::setModified(bool value) bool Song::isExportDone() const { - return !isExporting() || m_playPos[m_playMode] >= m_exportSongEnd; + return !isExporting() || getPlayPos() >= m_exportSongEnd; } int Song::getExportProgress() const { - TimePos pos = m_playPos[m_playMode]; + TimePos pos = getPlayPos(); if (pos >= m_exportSongEnd) { @@ -490,13 +486,13 @@ void Song::playSong() stop(); } - m_playMode = Mode_PlaySong; + m_playMode = PlayMode::Song; m_playing = true; m_paused = false; m_vstSyncController.setPlaybackState( true ); - savePos(); + savePlayStartPosition(); emit playbackStateChanged(); } @@ -529,13 +525,13 @@ void Song::playPattern() stop(); } - m_playMode = Mode_PlayPattern; + m_playMode = PlayMode::Pattern; m_playing = true; m_paused = false; m_vstSyncController.setPlaybackState( true ); - savePos(); + savePlayStartPosition(); emit playbackStateChanged(); } @@ -555,12 +551,12 @@ void Song::playMidiClip( const MidiClip* midiClipToPlay, bool loop ) if( m_midiClipToPlay != nullptr ) { - m_playMode = Mode_PlayMidiClip; + m_playMode = PlayMode::MidiClip; m_playing = true; m_paused = false; } - savePos(); + savePlayStartPosition(); emit playbackStateChanged(); } @@ -593,14 +589,14 @@ void Song::updateLength() -void Song::setPlayPos( tick_t ticks, PlayModes playMode ) +void Song::setPlayPos( tick_t ticks, PlayMode playMode ) { - tick_t ticksFromPlayMode = m_playPos[playMode].getTicks(); + tick_t ticksFromPlayMode = getPlayPos(playMode).getTicks(); m_elapsedTicks += ticksFromPlayMode - ticks; - m_elapsedMilliSeconds[playMode] += TimePos::ticksToMilliseconds( ticks - ticksFromPlayMode, getTempo() ); - m_playPos[playMode].setTicks( ticks ); - m_playPos[playMode].setCurrentFrame( 0.0f ); - m_playPos[playMode].setJumped( true ); + m_elapsedMilliSeconds[static_cast(playMode)] += TimePos::ticksToMilliseconds( ticks - ticksFromPlayMode, getTempo() ); + getPlayPos(playMode).setTicks( ticks ); + getPlayPos(playMode).setCurrentFrame( 0.0f ); + getPlayPos(playMode).setJumped( true ); // send a signal if playposition changes during playback if( isPlaying() ) @@ -622,6 +618,7 @@ void Song::togglePause() { m_playing = false; m_paused = true; + Engine::audioEngine()->clear(); } m_vstSyncController.setPlaybackState( m_playing ); @@ -635,7 +632,7 @@ void Song::togglePause() void Song::stop() { // do not stop/reset things again if we're stopped already - if( m_playMode == Mode_None ) + if( m_playMode == PlayMode::None ) { return; } @@ -645,49 +642,41 @@ void Song::stop() // To avoid race conditions with the processing threads Engine::audioEngine()->requestChangeInModel(); - TimeLineWidget * tl = m_playPos[m_playMode].m_timeLine; + auto& timeline = getTimeline(); m_paused = false; m_recording = true; - - if( tl ) - { - switch( tl->behaviourAtStop() ) - { - case TimeLineWidget::BackToZero: - m_playPos[m_playMode].setTicks(0); - m_elapsedMilliSeconds[m_playMode] = 0; - break; - - case TimeLineWidget::BackToStart: - if( tl->savedPos() >= 0 ) - { - m_playPos[m_playMode].setTicks(tl->savedPos().getTicks()); - setToTime(tl->savedPos()); - - tl->savePos( -1 ); - } - break; - - case TimeLineWidget::KeepStopPosition: - break; - } - } - else - { - m_playPos[m_playMode].setTicks( 0 ); - m_elapsedMilliSeconds[m_playMode] = 0; - } m_playing = false; - m_elapsedMilliSeconds[Mode_None] = m_elapsedMilliSeconds[m_playMode]; - m_playPos[Mode_None].setTicks(m_playPos[m_playMode].getTicks()); + switch (timeline.stopBehaviour()) + { + case Timeline::StopBehaviour::BackToZero: + getPlayPos().setTicks(0); + m_elapsedMilliSeconds[static_cast(m_playMode)] = 0; + break; - m_playPos[m_playMode].setCurrentFrame( 0 ); + 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()); + + getPlayPos().setCurrentFrame( 0 ); m_vstSyncController.setPlaybackState( m_exporting ); m_vstSyncController.setAbsolutePosition( - m_playPos[m_playMode].getTicks() - + m_playPos[m_playMode].currentFrame() + getPlayPos().getTicks() + + getPlayPos().currentFrame() / (double) Engine::framesPerTick() ); // remove all note-play-handles that are active @@ -702,7 +691,7 @@ void Song::stop() } m_oldAutomatedValues.clear(); - m_playMode = Mode_None; + m_playMode = PlayMode::None; Engine::audioEngine()->doneChangeInModel(); @@ -720,39 +709,37 @@ void Song::startExport() m_exporting = true; updateLength(); + const auto& timeline = getTimeline(PlayMode::Song); + if (m_renderBetweenMarkers) { - m_exportSongBegin = m_exportLoopBegin = m_playPos[Mode_PlaySong].m_timeLine->loopBegin(); - m_exportSongEnd = m_exportLoopEnd = m_playPos[Mode_PlaySong].m_timeLine->loopEnd(); + m_exportSongBegin = m_exportLoopBegin = timeline.loopBegin(); + m_exportSongEnd = m_exportLoopEnd = timeline.loopEnd(); - m_playPos[Mode_PlaySong].setTicks( m_playPos[Mode_PlaySong].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 && m_playPos[Mode_PlaySong].m_timeLine->loopEnd() > m_exportSongEnd) + if (m_loopRenderCount > 1 && timeline.loopEnd() > m_exportSongEnd) { - m_exportSongEnd = m_playPos[Mode_PlaySong].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 (m_playPos[Mode_PlaySong].m_timeLine) - { - m_exportLoopBegin = m_playPos[Mode_PlaySong].m_timeLine->loopBegin() < m_exportSongEnd && - m_playPos[Mode_PlaySong].m_timeLine->loopEnd() <= m_exportSongEnd ? - m_playPos[Mode_PlaySong].m_timeLine->loopBegin() : TimePos(0,0); - m_exportLoopEnd = m_playPos[Mode_PlaySong].m_timeLine->loopBegin() < m_exportSongEnd && - m_playPos[Mode_PlaySong].m_timeLine->loopEnd() <= m_exportSongEnd ? - m_playPos[Mode_PlaySong].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}; - m_playPos[Mode_PlaySong].setTicks( 0 ); + getPlayPos(PlayMode::Song).setTicks( 0 ); } m_exportEffectiveLength = (m_exportLoopBegin - m_exportSongBegin) + (m_exportLoopEnd - m_exportLoopBegin) @@ -785,7 +772,7 @@ void Song::insertBar() { // FIXME journal batch of tracks instead of each track individually if (track->numOfClips() > 0) { track->addJournalCheckPoint(); } - track->insertBar(m_playPos[Mode_PlaySong]); + track->insertBar(getPlayPos(PlayMode::Song)); } m_tracksMutex.unlock(); } @@ -800,7 +787,7 @@ void Song::removeBar() { // FIXME journal batch of tracks instead of each track individually if (track->numOfClips() > 0) { track->addJournalCheckPoint(); } - track->removeBar(m_playPos[Mode_PlaySong]); + track->removeBar(getPlayPos(PlayMode::Song)); } m_tracksMutex.unlock(); } @@ -810,7 +797,7 @@ void Song::removeBar() void Song::addPatternTrack() { - Track * t = Track::create(Track::PatternTrack, this); + Track * t = Track::create(Track::Type::Pattern, this); Engine::patternStore()->setCurrentPattern(dynamic_cast(t)->patternIndex()); } @@ -819,7 +806,7 @@ void Song::addPatternTrack() void Song::addSampleTrack() { - ( void )Track::create( Track::SampleTrack, this ); + ( void )Track::create( Track::Type::Sample, this ); } @@ -827,7 +814,7 @@ void Song::addSampleTrack() void Song::addAutomationTrack() { - ( void )Track::create( Track::AutomationTrack, this ); + ( void )Track::create( Track::Type::Automation, this ); } @@ -839,17 +826,11 @@ bpm_t Song::getTempo() } - - -AutomationClip * Song::tempoAutomationClip() -{ - return AutomationClip::globalAutomationClip( &m_tempoModel ); -} - - AutomatedValueMap Song::automatedValuesAt(TimePos time, int clipNum) const { - return TrackContainer::automatedValuesFromTracks(TrackList{m_globalAutomationTrack} << tracks(), time, clipNum); + auto trackList = TrackList{m_globalAutomationTrack}; + trackList.insert(trackList.end(), tracks().begin(), tracks().end()); + return TrackContainer::automatedValuesFromTracks(trackList, time, clipNum); } @@ -866,9 +847,9 @@ void Song::clearProject() stop(); } - for( int i = 0; i < Mode_Count; i++ ) + for( int i = 0; i < PlayModeCount; i++ ) { - setPlayPos( 0, ( PlayModes )i ); + setPlayPos( 0, ( PlayMode )i ); } @@ -966,16 +947,15 @@ void Song::createNewProject() m_oldFileName = ""; setProjectFileName(""); - Track * t; - t = Track::create( Track::InstrumentTrack, this ); - dynamic_cast( t )->loadInstrument( - "tripleoscillator" ); - t = Track::create(Track::InstrumentTrack, Engine::patternStore()); - dynamic_cast( t )->loadInstrument( - "kicker" ); - Track::create( Track::SampleTrack, this ); - Track::create( Track::PatternTrack, this ); - Track::create( Track::AutomationTrack, this ); + 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 ); m_tempoModel.setInitValue( DefaultTempo ); m_timeSigModel.reset(); @@ -1053,7 +1033,12 @@ void Song::loadProject( const QString & fileName ) else { QTextStream(stderr) << tr("Can't load project: " - "Project file contains local paths to plugins.") << endl; + "Project file contains local paths to plugins.") +#if (QT_VERSION >= QT_VERSION_CHECK(5,15,0)) + << Qt::endl; +#else + << endl; +#endif } } } @@ -1082,11 +1067,7 @@ void Song::loadProject( const QString & fileName ) m_masterVolumeModel.loadSettings( dataFile.head(), "mastervol" ); m_masterPitchModel.loadSettings( dataFile.head(), "masterpitch" ); - if( m_playPos[Mode_PlaySong].m_timeLine ) - { - // reset loop-point-state - m_playPos[Mode_PlaySong].m_timeLine->toggleLoopPoints( 0 ); - } + getTimeline(PlayMode::Song).setLoopEnabled(false); if( !dataFile.content().firstChildElement( "track" ).isNull() ) { @@ -1133,7 +1114,7 @@ void Song::loadProject( const QString & fileName ) if( nd.isElement() && nd.nodeName() == "track" ) { ++m_nLoadingTrack; - if (nd.toElement().attribute("type").toInt() == Track::PatternTrack) + if (static_cast(nd.toElement().attribute("type").toInt()) == Track::Type::Pattern) { n += nd.toElement().elementsByTagName("patterntrack").at(0) .toElement().firstChildElement().childNodes().count(); @@ -1181,9 +1162,9 @@ void Song::loadProject( const QString & fileName ) { getGUI()->getProjectNotes()->SerializingObject::restoreState( node.toElement() ); } - else if( node.nodeName() == m_playPos[Mode_PlaySong].m_timeLine->nodeName() ) + else if (node.nodeName() == getTimeline(PlayMode::Song).nodeName()) { - m_playPos[Mode_PlaySong].m_timeLine->restoreState( node.toElement() ); + getTimeline(PlayMode::Song).restoreState(node.toElement()); } } } @@ -1199,7 +1180,7 @@ void Song::loadProject( const QString & fileName ) // Remove dummy controllers that was added for correct connections m_controllers.erase(std::remove_if(m_controllers.begin(), m_controllers.end(), - [](Controller* c){return c->type() == Controller::DummyController;}), + [](Controller* c){return c->type() == Controller::ControllerType::Dummy;}), m_controllers.end()); // resolve all IDs so that autoModels are automated @@ -1249,7 +1230,7 @@ bool Song::saveProjectFile(const QString & filename, bool withResources) { using gui::getGUI; - DataFile dataFile( DataFile::SongProject ); + DataFile dataFile( DataFile::Type::SongProject ); m_savingProject = true; m_tempoModel.saveSettings( dataFile, dataFile.head(), "bpm" ); @@ -1273,7 +1254,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() ); - m_playPos[Mode_PlaySong].m_timeLine->saveState( dataFile, dataFile.content() ); + getTimeline(PlayMode::Song).saveState(dataFile, dataFile.content()); } saveControllerStates( dataFile, dataFile.content() ); @@ -1304,7 +1285,7 @@ bool Song::guiSaveProject() // Save the current song with the given filename bool Song::guiSaveProjectAs(const QString & filename) { - DataFile dataFile(DataFile::SongProject); + DataFile dataFile(DataFile::Type::SongProject); QString fileNameWithExtension = dataFile.nameWithExtension(filename); bool withResources = m_saveOptions.saveAsProjectBundle.value(); @@ -1352,8 +1333,7 @@ void Song::restoreControllerStates( const QDomElement & element ) else { // Fix indices to ensure correct connections - m_controllers.append(Controller::create( - Controller::DummyController, this)); + m_controllers.push_back(Controller::create(Controller::ControllerType::Dummy, this)); } node = node.nextSibling(); @@ -1471,9 +1451,10 @@ void Song::setProjectFileName(QString const & projectFileName) void Song::addController( Controller * controller ) { - if( controller && !m_controllers.contains( controller ) ) + bool containsController = std::find(m_controllers.begin(), m_controllers.end(), controller) != m_controllers.end(); + if (controller && !containsController) { - m_controllers.append( controller ); + m_controllers.push_back(controller); emit controllerAdded( controller ); this->setModified(); @@ -1485,10 +1466,10 @@ void Song::addController( Controller * controller ) void Song::removeController( Controller * controller ) { - int index = m_controllers.indexOf( controller ); - if( index != -1 ) + auto it = std::find(m_controllers.begin(), m_controllers.end(), controller); + if (it != m_controllers.end()) { - m_controllers.remove( index ); + m_controllers.erase(it); emit controllerRemoved( controller ); delete controller; diff --git a/src/core/StepRecorder.cpp b/src/core/StepRecorder.cpp index e8f31f644..9c2a216ae 100644 --- a/src/core/StepRecorder.cpp +++ b/src/core/StepRecorder.cpp @@ -91,7 +91,7 @@ void StepRecorder::notePressed(const Note & n) StepNote* stepNote = findCurStepNote(n.key()); if(stepNote == nullptr) { - m_curStepNotes.append(new StepNote(Note(m_curStepLength, m_curStepStartPos, n.key(), n.getVolume(), n.getPanning()))); + m_curStepNotes.push_back(new StepNote(Note(m_curStepLength, m_curStepStartPos, n.key(), n.getVolume(), n.getPanning()))); m_pianoRoll.update(); } else if (stepNote->isReleased()) @@ -175,15 +175,15 @@ void StepRecorder::setStepsLength(const TimePos& newLength) updateWidget(); } -QVector StepRecorder::getCurStepNotes() +std::vector StepRecorder::getCurStepNotes() { - QVector notes; + std::vector notes; if(m_isStepInProgress) { - for(StepNote* stepNote: m_curStepNotes) + for (StepNote* stepNote: m_curStepNotes) { - notes.append(&stepNote->m_note); + notes.push_back(&stepNote->m_note); } } @@ -288,18 +288,13 @@ void StepRecorder::removeNotesReleasedForTooLong() int nextTimout = std::numeric_limits::max(); bool notesRemoved = false; - QMutableVectorIterator itr(m_curStepNotes); - while (itr.hasNext()) + for (const auto& stepNote : m_curStepNotes) { - StepNote* stepNote = itr.next(); - - if(stepNote->isReleased()) + if (stepNote->isReleased()) { const int timeSinceReleased = stepNote->timeSinceReleased(); // capture value to avoid wraparound when calculting nextTimout if (timeSinceReleased >= REMOVE_RELEASED_NOTE_TIME_THRESHOLD_MS) { - delete stepNote; - itr.remove(); notesRemoved = true; } else @@ -309,6 +304,17 @@ void StepRecorder::removeNotesReleasedForTooLong() } } + m_curStepNotes.erase(std::remove_if(m_curStepNotes.begin(), m_curStepNotes.end(), [](auto stepNote) + { + bool shouldRemove = stepNote->isReleased() && stepNote->timeSinceReleased() >= REMOVE_RELEASED_NOTE_TIME_THRESHOLD_MS; + if (shouldRemove) + { + delete stepNote; + } + + return shouldRemove; + }), m_curStepNotes.end()); + if(notesRemoved) { m_pianoRoll.update(); diff --git a/src/core/TempoSyncKnobModel.cpp b/src/core/TempoSyncKnobModel.cpp index 6fb457f2c..7f707478f 100644 --- a/src/core/TempoSyncKnobModel.cpp +++ b/src/core/TempoSyncKnobModel.cpp @@ -40,8 +40,8 @@ TempoSyncKnobModel::TempoSyncKnobModel( const float _val, const float _min, const float _scale, Model * _parent, const QString & _display_name ) : FloatModel( _val, _min, _max, _step, _parent, _display_name ), - m_tempoSyncMode( SyncNone ), - m_tempoLastSyncMode( SyncNone ), + m_tempoSyncMode( SyncMode::None ), + m_tempoLastSyncMode( SyncMode::None ), m_scale( _scale ), m_custom( _parent ) { @@ -55,15 +55,15 @@ TempoSyncKnobModel::TempoSyncKnobModel( const float _val, const float _min, void TempoSyncKnobModel::setTempoSync( QAction * _item ) { - setTempoSync( _item->data().toInt() ); + setTempoSync( static_cast(_item->data().toInt()) ); } -void TempoSyncKnobModel::setTempoSync( int _note_type ) +void TempoSyncKnobModel::setTempoSync( SyncMode _note_type ) { - setSyncMode( ( TempoSyncMode ) _note_type ); + setSyncMode( _note_type ); Engine::getSong()->setModified(); } @@ -74,34 +74,34 @@ void TempoSyncKnobModel::calculateTempoSyncTime( bpm_t _bpm ) { float conversionFactor = 1.0; - if( m_tempoSyncMode ) + if( m_tempoSyncMode != SyncMode::None ) { switch( m_tempoSyncMode ) { - case SyncCustom: + case SyncMode::Custom: conversionFactor = static_cast( m_custom.getDenominator() ) / static_cast( m_custom.getNumerator() ); break; - case SyncDoubleWholeNote: + case SyncMode::DoubleWholeNote: conversionFactor = 0.125; break; - case SyncWholeNote: + case SyncMode::WholeNote: conversionFactor = 0.25; break; - case SyncHalfNote: + case SyncMode::HalfNote: conversionFactor = 0.5; break; - case SyncQuarterNote: + case SyncMode::QuarterNote: conversionFactor = 1.0; break; - case SyncEighthNote: + case SyncMode::EighthNote: conversionFactor = 2.0; break; - case SyncSixteenthNote: + case SyncMode::SixteenthNote: conversionFactor = 4.0; break; - case SyncThirtysecondNote: + case SyncMode::ThirtysecondNote: conversionFactor = 8.0; break; default: ; @@ -117,6 +117,10 @@ void TempoSyncKnobModel::calculateTempoSyncTime( bpm_t _bpm ) emit syncModeChanged( m_tempoSyncMode ); m_tempoLastSyncMode = m_tempoSyncMode; } + else if (m_tempoSyncMode == SyncMode::Custom) + { + emit syncModeChanged(m_tempoSyncMode); + } } @@ -138,18 +142,18 @@ void TempoSyncKnobModel::loadSettings( const QDomElement & _this, { FloatModel::loadSettings( _this, _name ); m_custom.loadSettings( _this, _name ); - setSyncMode( ( TempoSyncMode ) _this.attribute( _name + "_syncmode" ).toInt() ); + setSyncMode( ( SyncMode ) _this.attribute( _name + "_syncmode" ).toInt() ); } -void TempoSyncKnobModel::setSyncMode( TempoSyncMode _new_mode ) +void TempoSyncKnobModel::setSyncMode( SyncMode _new_mode ) { if( m_tempoSyncMode != _new_mode ) { m_tempoSyncMode = _new_mode; - if( _new_mode == SyncCustom ) + if( _new_mode == SyncMode::Custom ) { connect( &m_custom, SIGNAL(dataChanged()), this, SLOT(updateCustom()), @@ -174,7 +178,7 @@ void TempoSyncKnobModel::setScale( float _new_scale ) void TempoSyncKnobModel::updateCustom() { - setSyncMode( SyncCustom ); + setSyncMode( SyncMode::Custom ); } diff --git a/src/core/TimePos.cpp b/src/core/TimePos.cpp index f3b09474d..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 @@ -194,7 +195,7 @@ tick_t TimePos::ticksPerBar( const TimeSig &sig ) int TimePos::stepsPerBar() { int steps = ticksPerBar() / DefaultBeatsPerBar; - return qMax( 1, steps ); + return std::max(1, steps); } @@ -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/ToolPlugin.cpp b/src/core/ToolPlugin.cpp index 137029137..dfcacc1bc 100644 --- a/src/core/ToolPlugin.cpp +++ b/src/core/ToolPlugin.cpp @@ -42,7 +42,7 @@ ToolPlugin * ToolPlugin::instantiate( const QString & _plugin_name, Model * _par { Plugin * p = Plugin::instantiate( _plugin_name, _parent, nullptr ); // check whether instantiated plugin is a tool - if( p->type() == Plugin::Tool ) + if( p->type() == Plugin::Type::Tool ) { // everything ok, so return pointer return dynamic_cast( p ); diff --git a/src/core/Track.cpp b/src/core/Track.cpp index 084f302f8..6c4ba465e 100644 --- a/src/core/Track.cpp +++ b/src/core/Track.cpp @@ -56,7 +56,7 @@ namespace lmms * * \todo check the definitions of all the properties - are they OK? */ -Track::Track( TrackTypes type, TrackContainer * tc ) : +Track::Track( Type type, TrackContainer * tc ) : Model( tc ), /*!< The track Model */ m_trackContainer( tc ), /*!< The track container object */ m_type( type ), /*!< The track type */ @@ -64,10 +64,8 @@ Track::Track( TrackTypes 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; } @@ -84,9 +82,9 @@ Track::~Track() lock(); emit destroyedTrack(); - while( !m_clips.isEmpty() ) + while (!m_clips.empty()) { - delete m_clips.last(); + delete m_clips.back(); } m_trackContainer->removeTrack( this ); @@ -101,7 +99,7 @@ Track::~Track() * \param tt The type of track to create * \param tc The track container to attach to */ -Track * Track::create( TrackTypes tt, TrackContainer * tc ) +Track * Track::create( Type tt, TrackContainer * tc ) { Engine::audioEngine()->requestChangeInModel(); @@ -109,13 +107,13 @@ Track * Track::create( TrackTypes tt, TrackContainer * tc ) switch( tt ) { - case InstrumentTrack: t = new class InstrumentTrack( tc ); break; - case PatternTrack: t = new class PatternTrack( tc ); break; - case SampleTrack: t = new class SampleTrack( tc ); break; -// case EVENT_TRACK: -// case VIDEO_TRACK: - case AutomationTrack: t = new class AutomationTrack( tc ); break; - case HiddenAutomationTrack: + case Type::Instrument: t = new class InstrumentTrack( tc ); break; + case Type::Pattern: t = new class PatternTrack( tc ); break; + case Type::Sample: t = new class SampleTrack( tc ); break; +// case Type::Event: +// case Type::Video: + case Type::Automation: t = new class AutomationTrack( tc ); break; + case Type::HiddenAutomation: t = new class AutomationTrack( tc, true ); break; default: break; } @@ -145,7 +143,7 @@ Track * Track::create( const QDomElement & element, TrackContainer * tc ) Engine::audioEngine()->requestChangeInModel(); Track * t = create( - static_cast( element.attribute( "type" ).toInt() ), + static_cast( element.attribute( "type" ).toInt() ), tc ); if( t != nullptr ) { @@ -197,7 +195,7 @@ void Track::saveSettings( QDomDocument & doc, QDomElement & element ) { element.setTagName( "track" ); } - element.setAttribute( "type", type() ); + element.setAttribute( "type", static_cast(type()) ); element.setAttribute( "name", name() ); m_mutedModel.saveSettings( doc, element, "muted" ); m_soloModel.saveSettings( doc, element, "solo" ); @@ -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() ); @@ -249,7 +247,7 @@ void Track::saveSettings( QDomDocument & doc, QDomElement & element ) */ void Track::loadSettings( const QDomElement & element ) { - if( element.attribute( "type" ).toInt() != type() ) + if( static_cast(element.attribute( "type" ).toInt()) != type() ) { qWarning( "Current track-type does not match track-type of " "settings-node!\n" ); @@ -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(); @@ -365,9 +357,9 @@ void Track::removeClip( Clip * clip ) /*! \brief Remove all Clips from this track */ void Track::deleteClips() { - while( ! m_clips.isEmpty() ) + while (!m_clips.empty()) { - delete m_clips.first(); + delete m_clips.front(); } } @@ -613,7 +605,7 @@ void Track::toggleSolo() { track->setMuted(false); } - else if (soloLegacyBehavior || track->type() != AutomationTrack) + else if (soloLegacyBehavior || track->type() != Type::Automation) { track->setMuted(true); } @@ -626,7 +618,7 @@ void Track::toggleSolo() { // Unless we are on the sololegacybehavior mode, only restores the // mute state if the track isn't an Automation Track - if (soloLegacyBehavior || track->type() != AutomationTrack) + if (soloLegacyBehavior || track->type() != Type::Automation) { track->setMuted(track->m_mutedBeforeSolo); } @@ -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/TrackContainer.cpp b/src/core/TrackContainer.cpp index 0347773ed..d4120e761 100644 --- a/src/core/TrackContainer.cpp +++ b/src/core/TrackContainer.cpp @@ -156,13 +156,13 @@ void TrackContainer::loadSettings( const QDomElement & _this ) -int TrackContainer::countTracks( Track::TrackTypes _tt ) const +int TrackContainer::countTracks( Track::Type _tt ) const { int cnt = 0; m_tracksMutex.lockForRead(); for (const auto& track : m_tracks) { - if (track->type() == _tt || _tt == Track::NumTrackTypes) + if (track->type() == _tt || _tt == Track::Type::Count) { ++cnt; } @@ -176,7 +176,7 @@ int TrackContainer::countTracks( Track::TrackTypes _tt ) const void TrackContainer::addTrack( Track * _track ) { - if( _track->type() != Track::HiddenAutomationTrack ) + if( _track->type() != Track::Type::HiddenAutomation ) { _track->lock(); m_tracksMutex.lockForWrite(); @@ -196,14 +196,14 @@ void TrackContainer::removeTrack( Track * _track ) // After checking that index != -1, we need to upgrade the lock to a write locker before changing m_tracks. // But since Qt offers no function to promote a read lock to a write lock, we must start with the write locker. QWriteLocker lockTracksAccess(&m_tracksMutex); - int index = m_tracks.indexOf( _track ); - if( index != -1 ) + auto it = std::find(m_tracks.begin(), m_tracks.end(), _track); + if (it != m_tracks.end()) { // If the track is solo, all other tracks are muted. Change this before removing the solo track: if (_track->isSolo()) { _track->setSolo(false); } - m_tracks.remove( index ); + m_tracks.erase(it); lockTracksAccess.unlock(); if( Engine::getSong() ) @@ -226,9 +226,9 @@ void TrackContainer::updateAfterTrackAdd() void TrackContainer::clearAllTracks() { //m_tracksMutex.lockForWrite(); - while( !m_tracks.isEmpty() ) + while (!m_tracks.empty()) { - delete m_tracks.first(); + delete m_tracks.front(); } //m_tracksMutex.unlock(); } @@ -240,7 +240,7 @@ bool TrackContainer::isEmpty() const { for (const auto& track : m_tracks) { - if (!track->getClips().isEmpty()) + if (!track->getClips().empty()) { return false; } @@ -268,14 +268,14 @@ AutomatedValueMap TrackContainer::automatedValuesFromTracks(const TrackList &tra switch(track->type()) { - case Track::AutomationTrack: - case Track::HiddenAutomationTrack: - case Track::PatternTrack: + case Track::Type::Automation: + case Track::Type::HiddenAutomation: + case Track::Type::Pattern: if (clipNum < 0) { track->getClipsInRange(clips, 0, time); } else { Q_ASSERT(track->numOfClips() > clipNum); - clips << track->getClip(clipNum); + clips.push_back(track->getClip(clipNum)); } default: break; @@ -299,7 +299,7 @@ AutomatedValueMap TrackContainer::automatedValuesFromTracks(const TrackList &tra } TimePos relTime = time - p->startPosition(); if (! p->getAutoResize()) { - relTime = qMin(relTime, p->length()); + relTime = std::min(relTime, p->length()); } float value = p->valueAt(relTime); diff --git a/src/core/UpgradeExtendedNoteRange.cpp b/src/core/UpgradeExtendedNoteRange.cpp new file mode 100644 index 000000000..6ed98e63e --- /dev/null +++ b/src/core/UpgradeExtendedNoteRange.cpp @@ -0,0 +1,434 @@ +/* + * UpgradeExtendedNoteRange.cpp - Upgrades the extended note range + * + * 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 "UpgradeExtendedNoteRange.h" + +#include "Track.h" + +#include + +#include +#include + + +namespace lmms +{ + +/** + * @brief Used by the helper function that analyzes automation patterns. + */ +struct PatternAnalysisResult +{ + PatternAnalysisResult(bool hasBaseNoteAutomations, bool hasNonBaseNoteAutomations) + { + this->hasBaseNoteAutomations = hasBaseNoteAutomations; + this->hasNonBaseNoteAutomations = hasNonBaseNoteAutomations; + } + bool hasBaseNoteAutomations; + bool hasNonBaseNoteAutomations; +}; + +/** + * @brief Helper function that checks for an automation pattern if it contains automation for + * targets that are base notes and/or other targets. + * @param automationPattern The automation pattern to be checked. + * @param automatedBaseNoteIds A set of id of automated base notes that are used in the check. + * @return A struct that contains the results. + */ +static PatternAnalysisResult analyzeAutomationPattern(QDomElement const & automationPattern, std::set const & automatedBaseNoteIds) +{ + bool hasBaseNoteAutomations = false; + bool hasNonBaseNoteAutomations = false; + + // Iterate the objects. These contain the ids of the automated objects. + QDomElement object = automationPattern.firstChildElement("object"); + while (!object.isNull()) + { + unsigned int const id = object.attribute("id").toUInt(); + + // Check if the automated object is a base note. + if (automatedBaseNoteIds.find(id) != automatedBaseNoteIds.end()) + { + hasBaseNoteAutomations = true; + } + else + { + hasNonBaseNoteAutomations = true; + } + + object = object.nextSiblingElement("object"); + } + + return PatternAnalysisResult(hasBaseNoteAutomations, hasNonBaseNoteAutomations); +} + +static void fixNotePatterns(QDomNodeList & patterns) +{ + for (int i = 0; i < patterns.size(); ++i) + { + QDomNodeList notes = patterns.item(i).toElement().elementsByTagName("note"); + for (int j = 0; j < notes.size(); ++j) + { + QDomElement note = notes.item(j).toElement(); + if (note.hasAttribute("key")) + { + int const currentKey = note.attribute("key").toInt(); + note.setAttribute("key", currentKey + 12); + } + } + } +} + +static void fixInstrumentBaseNoteAndCollectIds(QDomElement & instrument, std::set & automatedBaseNoteIds) +{ + // Raise the base note of every instrument by 12 to compensate for the change + // of A4 key code from 57 to 69. This ensures that notes are labeled correctly. + QDomElement instrumentParent = instrument.parentNode().toElement(); + + // Correct the base note of the instrument. Base notes which are automated are + // stored as elements. Non-automated base notes are stored as attributes. + if (instrumentParent.hasAttribute("basenote")) + { + // TODO Base notes can have float values in the save file! This might need to be changed! + int const currentBaseNote = instrumentParent.attribute("basenote").toInt(); + instrumentParent.setAttribute("basenote", currentBaseNote + 12); + } + else + { + // Check if the instrument track has an automated base note. + // Correct the value of the base note and collect their ids while doing so. + // The ids are used later to find the automations that automate these corrected base + // notes so that we can correct the automation values as well. + QDomNodeList baseNotes = instrumentParent.elementsByTagName("basenote"); + for (int j = 0; j < baseNotes.size(); ++j) + { + QDomElement baseNote = baseNotes.item(j).toElement(); + if (!baseNote.isNull()) + { + if (baseNote.hasAttribute("value")) + { + // Base notes can have float values in the save file, e.g. if the file + // is saved after a linear automation has run on the base note. Therefore + // it is fixed here as a float even if the nominal values of base notes + // are integers. + float const value = baseNote.attribute("value").toFloat(); + baseNote.setAttribute("value", value + 12); + } + + // The ids of base notes are of type jo_id_t which are in fact uint32_t. + // So let's just use these here to save some casting. + unsigned int const id = baseNote.attribute("id").toUInt(); + automatedBaseNoteIds.insert(id); + } + } + } +} + +/** + * @brief Helper method that fixes the values and out values for an automation pattern. + * @param automationPattern The automation pattern to be fixed. + */ +static void fixAutomationPattern(QDomElement & automationPattern) +{ + QDomElement time = automationPattern.firstChildElement("time"); + while (!time.isNull()) + { + // Automation patterns can automate base notes as floats + // so we read and correct them as floats here. + float const value = time.attribute("value").toFloat(); + time.setAttribute("value", value + 12.); + + // The method "upgrade_automationNodes" adds some attributes + // with the name "outValue". We have to correct these as well. + float const outValue = time.attribute("outValue").toFloat(); + time.setAttribute("outValue", outValue + 12.); + + time = time.nextSiblingElement("time"); + }; +} + +static bool affected(QDomElement & instrument) +{ + assert(instrument.hasAttribute("name")); + QString const name = instrument.attribute("name"); + + return name == "zynaddsubfx" || + name == "vestige" || name == "lv2instrument" || + name == "carlapatchbay" || name == "carlarack"; +} + +static void fixTrack(QDomElement & track, std::set & automatedBaseNoteIds) +{ + if (!track.hasAttribute("type")) + { + return; + } + + Track::Type const trackType = static_cast(track.attribute("type").toInt()); + + // BB tracks need special handling because they contain a track container of their own + if (trackType == Track::Type::Pattern) + { + // Assuming that a BB track cannot contain another BB track here... + QDomNodeList subTracks = track.elementsByTagName("track"); + for (int i = 0; i < subTracks.size(); ++i) + { + QDomElement subTrack = subTracks.item(i).toElement(); + assert (static_cast(subTrack.attribute("type").toInt()) != Track::Type::Pattern); + fixTrack(subTrack, automatedBaseNoteIds); + } + } + else + { + QDomNodeList instruments = track.elementsByTagName("instrument"); + + for (int i = 0; i < instruments.size(); ++i) + { + QDomElement instrument = instruments.item(i).toElement(); + + fixInstrumentBaseNoteAndCollectIds(instrument, automatedBaseNoteIds); + + // Raise the pitch of all notes in patterns assigned to instruments not affected + // by #1857 by an octave. This negates the base note change for normal instruments, + // but leaves the MIDI-based instruments sounding an octave lower, preserving their + // pitch in existing projects. + if (!affected(instrument)) + { + QDomNodeList patterns = track.elementsByTagName("pattern"); + fixNotePatterns(patterns); + } + } + } +} + +static void fixAutomationTracks(QDomElement & song, std::set const & automatedBaseNoteIds) +{ + // Now fix all the automation tracks. + QDomNodeList tracks = song.elementsByTagName("track"); + + // We have to collect the tracks that we need to duplicate and cannot do this in-place + // because if we did the iteration might never stop. + std::vector tracksToDuplicate; + tracksToDuplicate.reserve(tracks.size()); + + // Iterate the tracks again. This time work on all automation tracks. + for (int i = 0; i < tracks.size(); ++i) + { + QDomElement currentTrack = tracks.item(i).toElement(); + if (static_cast(currentTrack.attribute("type").toInt()) != Track::Type::Automation) + { + continue; + } + + // Check each track for the types of automations it contains in its patterns. + bool containsPatternsWithBaseNoteTargets = false; + bool containsPatternsWithNonBaseNoteTargets = false; + + QDomElement automationPattern = currentTrack.firstChildElement("automationpattern"); + while (!automationPattern.isNull()) + { + auto const analysis = analyzeAutomationPattern(automationPattern, automatedBaseNoteIds); + containsPatternsWithBaseNoteTargets |= analysis.hasBaseNoteAutomations; + containsPatternsWithNonBaseNoteTargets |= analysis.hasNonBaseNoteAutomations; + + automationPattern = automationPattern.nextSiblingElement("automationpattern"); + } + + if (!containsPatternsWithBaseNoteTargets) + { + // No base notes are automated by this automation track so we have nothing to do + continue; + } + else + { + if (!containsPatternsWithNonBaseNoteTargets) + { + // Only base note targets. This means we can simply keep the track and fix it. + automationPattern = currentTrack.firstChildElement("automationpattern"); + while (!automationPattern.isNull()) + { + fixAutomationPattern(automationPattern); + + automationPattern = automationPattern.nextSiblingElement("automationpattern"); + } + } + else + { + // The automation track has automations for base notes and other targets in its patterns. + // We will later need to duplicate/split the track. + tracksToDuplicate.push_back(currentTrack); + } + } + } + + // Now fix the tracks that need duplication/splitting + for (QDomElement & track : tracksToDuplicate) + { + // First clone the original track + QDomNode cloneOfTrack = track.cloneNode(); + + // Now that we have the original and the clone we can manipulate both of them. + // The original track will keep only patterns without base note automations. + // Note: for the original track these might also be automation patterns without + // any targets. We will keep these because they might have been saved by the users + // like this. + QDomElement automationPattern = track.firstChildElement("automationpattern"); + while (!automationPattern.isNull()) + { + auto const analysis = analyzeAutomationPattern(automationPattern, automatedBaseNoteIds); + if (!analysis.hasBaseNoteAutomations) + { + // This pattern has no base note automations. Leave it alone. + automationPattern = automationPattern.nextSiblingElement("automationpattern"); + } + else if (!analysis.hasNonBaseNoteAutomations) + { + // The pattern only has base note automations. Remove it completely as it would become empty. + QDomElement patternToRemove = automationPattern; + automationPattern = automationPattern.nextSiblingElement("automationpattern"); + track.removeChild(patternToRemove); + } + else + { + // The pattern itself is mixed. Remove the base note objects. + QDomElement object = automationPattern.firstChildElement("object"); + while (!object.isNull()) + { + unsigned int const id = object.attribute("id").toUInt(); + + if (automatedBaseNoteIds.find(id) != automatedBaseNoteIds.end()) + { + QDomElement objectToRemove = object; + object = object.nextSiblingElement("object"); + automationPattern.removeChild(objectToRemove); + } + else + { + object = object.nextSiblingElement("object"); + } + } + + automationPattern = automationPattern.nextSiblingElement("automationpattern"); + } + } + + // The clone will only keep non-empty patterns with base note automations + // and the values of the patterns will be corrected. + automationPattern = cloneOfTrack.firstChildElement("automationpattern"); + while (!automationPattern.isNull()) + { + auto const analysis = analyzeAutomationPattern(automationPattern, automatedBaseNoteIds); + if (analysis.hasBaseNoteAutomations) + { + // This pattern has base note automations. Remove all other ones and fix the pattern. + QDomElement object = automationPattern.firstChildElement("object"); + while (!object.isNull()) + { + unsigned int const id = object.attribute("id").toUInt(); + + if (automatedBaseNoteIds.find(id) == automatedBaseNoteIds.end()) + { + QDomElement objectToRemove = object; + object = object.nextSiblingElement("object"); + automationPattern.removeChild(objectToRemove); + } + else + { + object = object.nextSiblingElement("object"); + } + } + + fixAutomationPattern(automationPattern); + + automationPattern = automationPattern.nextSiblingElement("automationpattern"); + } + else + { + // The pattern has no base note automations. Remove it completely. + QDomElement patternToRemove = automationPattern; + automationPattern = automationPattern.nextSiblingElement("automationpattern"); + cloneOfTrack.removeChild(patternToRemove); + } + } + track.parentNode().appendChild(cloneOfTrack); + } +} + + +UpgradeExtendedNoteRange::UpgradeExtendedNoteRange(QDomElement & domElement) : + m_domElement(domElement) +{ +} + +void UpgradeExtendedNoteRange::upgrade() +{ + QDomElement song = m_domElement.firstChildElement("song"); + while (!song.isNull()) + { + // This set will later contain all ids of automated base notes. They are + // used to find out which automation patterns must to be corrected, i.e. to + // check if an automation pattern has one or more base notes as its target. + std::set automatedBaseNoteIds; + + QDomElement trackContainer = song.firstChildElement("trackcontainer"); + while (!trackContainer.isNull()) + { + QDomElement track = trackContainer.firstChildElement("track"); + while (!track.isNull()) + { + fixTrack(track, automatedBaseNoteIds); + + track = track.nextSiblingElement("track"); + } + + trackContainer = trackContainer.nextSiblingElement("trackcontainer"); + } + + fixAutomationTracks(song, automatedBaseNoteIds); + + song = song.nextSiblingElement("song"); + }; + + if (m_domElement.elementsByTagName("song").item(0).isNull()) + { + // Dealing with a preset, not a song + QDomNodeList presets = m_domElement.elementsByTagName("instrumenttrack"); + if (presets.item(0).isNull()) { return; } + QDomElement preset = presets.item(0).toElement(); + // Common correction for all instrument presets (make base notes match the new MIDI range). + // NOTE: Many older presets do not have any basenote defined, assume they were "made for 57". + // (Specifying a default like this also happens to fix a FileBrowser bug where previews of presets + // with undefined basenote always play with the basenote inherited from previously played preview.) + int oldBase = preset.attribute("basenote", "57").toInt(); + preset.setAttribute("basenote", oldBase + 12); + // Extra correction for Zyn, VeSTige, LV2 and Carla (to preserve the original buggy behavior). + QDomNodeList instruments = presets.item(0).toElement().elementsByTagName("instrument"); + if (instruments.isEmpty()) { return; } + QDomElement instrument = instruments.item(0).toElement(); + if (affected(instrument)) + { + preset.setAttribute("basenote", preset.attribute("basenote").toInt() + 12); + } + } +} + +} // namespace lmms diff --git a/src/core/UpgradeExtendedNoteRange.h b/src/core/UpgradeExtendedNoteRange.h new file mode 100644 index 000000000..ae444e82a --- /dev/null +++ b/src/core/UpgradeExtendedNoteRange.h @@ -0,0 +1,47 @@ +/* + * UpgradeExtendedNoteRange.h - Upgrades the extended note range + * + * 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_UPGRADEEXTENDEDNOTERANGE_H +#define LMMS_UPGRADEEXTENDEDNOTERANGE_H + + +class QDomElement; + +namespace lmms +{ + +class UpgradeExtendedNoteRange +{ +public: + UpgradeExtendedNoteRange(QDomElement & domElement); + + void upgrade(); + +private: + QDomElement & m_domElement; +}; + +} // namespace lmms + +#endif // LMMS_UPGRADEEXTENDEDNOTERANGE_H diff --git a/src/core/audio/AudioAlsa.cpp b/src/core/audio/AudioAlsa.cpp index 8b3eb32be..d80bc8912 100644 --- a/src/core/audio/AudioAlsa.cpp +++ b/src/core/audio/AudioAlsa.cpp @@ -36,10 +36,10 @@ namespace lmms { AudioAlsa::AudioAlsa( bool & _success_ful, AudioEngine* _audioEngine ) : - AudioDevice( qBound( + AudioDevice(std::clamp( + ConfigManager::inst()->value("audioalsa", "channels").toInt(), DEFAULT_CHANNELS, - ConfigManager::inst()->value( "audioalsa", "channels" ).toInt(), - SURROUND_CHANNELS ), _audioEngine ), + SURROUND_CHANNELS), _audioEngine), m_handle( nullptr ), m_hwParams( nullptr ), m_swParams( nullptr ), @@ -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,11 +77,10 @@ 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 < qMax( 3, count ); ++i ) + for (int i = 0; i < std::max(3, count); ++i) { const int fd = ( i >= count ) ? ufds[0].fd+i : ufds[i].fd; int oldflags = fcntl( fd, F_GETFD, 0 ); @@ -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); @@ -261,25 +254,21 @@ void AudioAlsa::applyQualitySettings() snd_pcm_close( m_handle ); } - 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; } - 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 ) ); @@ -323,12 +312,9 @@ void AudioAlsa::run() } outbuf_size = frames * channels(); - convertToS16( temp, frames, - audioEngine()->masterGain(), - outbuf, - m_convertEndian ); + convertToS16(temp, frames, outbuf, m_convertEndian); } - int min_len = qMin( len, outbuf_size - outbuf_pos ); + int min_len = std::min(len, outbuf_size - outbuf_pos); memcpy( ptr, outbuf + outbuf_pos, min_len * sizeof( int_sample_t ) ); ptr += min_len; @@ -373,10 +359,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 +368,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 +376,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 +392,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 +401,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 +413,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 +443,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 +458,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 +467,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 +476,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 +496,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..743716622 100644 --- a/src/core/audio/AudioDevice.cpp +++ b/src/core/audio/AudioDevice.cpp @@ -66,10 +66,7 @@ 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; @@ -198,8 +195,7 @@ fpp_t AudioDevice::resample( const surroundSampleFrame * _src, 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 ) ) ) + if (int error = src_process(m_srcState, &m_srcData)) { printf( "AudioDevice::resample(): error while resampling: %s\n", src_strerror( error ) ); @@ -211,19 +207,17 @@ fpp_t AudioDevice::resample( const surroundSampleFrame * _src, 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 +230,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); } } } diff --git a/src/core/audio/AudioFileFlac.cpp b/src/core/audio/AudioFileFlac.cpp index 6af063683..097fbdd89 100644 --- a/src/core/audio/AudioFileFlac.cpp +++ b/src/core/audio/AudioFileFlac.cpp @@ -58,8 +58,8 @@ bool AudioFileFlac::startEncoding() switch (getOutputSettings().getBitDepth()) { - case OutputSettings::Depth_24Bit: - case OutputSettings::Depth_32Bit: + case OutputSettings::BitDepth::Depth24Bit: + case OutputSettings::BitDepth::Depth32Bit: // FLAC does not support 32bit sampling, so take it as 24. m_sfinfo.format |= SF_FORMAT_PCM_24; break; @@ -89,12 +89,12 @@ 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 ); - if (depth == OutputSettings::Depth_24Bit || depth == OutputSettings::Depth_32Bit) // Float encoding + if (depth == OutputSettings::BitDepth::Depth24Bit || depth == OutputSettings::BitDepth::Depth32Bit) // Float encoding { auto buf = std::vector(frames * channels()); for(fpp_t frame = 0; frame < frames; ++frame) @@ -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] = qMax( 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 4930e9ad6..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; @@ -94,11 +91,11 @@ MPEG_mode mapToMPEG_mode(OutputSettings::StereoMode stereoMode) { switch (stereoMode) { - case OutputSettings::StereoMode_Stereo: + case OutputSettings::StereoMode::Stereo: return STEREO; - case OutputSettings::StereoMode_JointStereo: + case OutputSettings::StereoMode::JointStereo: return JOINT_STEREO; - case OutputSettings::StereoMode_Mono: + case OutputSettings::StereoMode::Mono: return MONO; default: return NOT_SET; 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 b78b04403..612b98982 100644 --- a/src/core/audio/AudioFileWave.cpp +++ b/src/core/audio/AudioFileWave.cpp @@ -64,13 +64,13 @@ bool AudioFileWave::startEncoding() switch( getOutputSettings().getBitDepth() ) { - case OutputSettings::Depth_32Bit: + case OutputSettings::BitDepth::Depth32Bit: m_si.format |= SF_FORMAT_FLOAT; break; - case OutputSettings::Depth_24Bit: + case OutputSettings::BitDepth::Depth24Bit: m_si.format |= SF_FORMAT_PCM_24; break; - case OutputSettings::Depth_16Bit: + case OutputSettings::BitDepth::Depth16Bit: default: m_si.format |= SF_FORMAT_PCM_16; break; @@ -93,24 +93,18 @@ 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(); - if( bitDepth == OutputSettings::Depth_32Bit || bitDepth == OutputSettings::Depth_24Bit ) + if( bitDepth == OutputSettings::BitDepth::Depth32Bit || bitDepth == OutputSettings::BitDepth::Depth24Bit ) { auto buf = new float[_frames * channels()]; for( fpp_t frame = 0; frame < _frames; ++frame ) { 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 222ebf10d..64819cbeb 100644 --- a/src/core/audio/AudioJack.cpp +++ b/src/core/audio/AudioJack.cpp @@ -26,46 +26,46 @@ #ifdef LMMS_HAVE_JACK +#include #include -#include #include +#include "AudioEngine.h" +#include "ConfigManager.h" #include "Engine.h" #include "GuiApplication.h" -#include "gui_templates.h" -#include "ConfigManager.h" #include "LcdSpinBox.h" #include "MainWindow.h" -#include "AudioEngine.h" #include "MidiJack.h" - namespace lmms { -AudioJack::AudioJack( bool & _success_ful, AudioEngine* _audioEngine ) : - AudioDevice( qBound( - DEFAULT_CHANNELS, - ConfigManager::inst()->value( "audiojack", "channels" ).toInt(), - 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); } @@ -263,14 +234,11 @@ void AudioJack::stopProcessing() void AudioJack::applyQualitySettings() { - if( hqAudio() ) + if (hqAudio()) { - setSampleRate( Engine::audioEngine()->processingSampleRate() ); + setSampleRate(Engine::audioEngine()->processingSampleRate()); - if( jack_get_sample_rate( m_client ) != sampleRate() ) - { - setSampleRate( jack_get_sample_rate( m_client ) ); - } + if (jack_get_sample_rate(m_client) != sampleRate()) { setSampleRate(jack_get_sample_rate(m_client)); } } AudioDevice::applyQualitySettings(); @@ -279,106 +247,91 @@ void AudioJack::applyQualitySettings() -void AudioJack::registerPort( AudioPort * _port ) +void AudioJack::registerPort(AudioPort* port) { #ifdef AUDIO_PORT_SUPPORT // make sure, port is not already registered - unregisterPort( _port ); - const QString name[2] = { _port->name() + " L", - _port->name() + " R" } ; + unregisterPort(port); + const QString name[2] = {port->name() + " L", port->name() + " R"}; - for( ch_cnt_t ch = 0; ch < DEFAULT_CHANNELS; ++ch ) + for (ch_cnt_t ch = 0; ch < DEFAULT_CHANNELS; ++ch) { - m_portMap[_port].ports[ch] = jack_port_register( m_client, - name[ch].toLatin1().constData(), - JACK_DEFAULT_AUDIO_TYPE, - JackPortIsOutput, 0 ); + m_portMap[port].ports[ch] = jack_port_register( + m_client, name[ch].toLatin1().constData(), JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0); } +#else + (void)port; #endif } -void AudioJack::unregisterPort( AudioPort * _port ) +void AudioJack::unregisterPort(AudioPort* port) { #ifdef AUDIO_PORT_SUPPORT - if( m_portMap.contains( _port ) ) + if (m_portMap.contains(port)) { - for( ch_cnt_t ch = 0; ch < DEFAULT_CHANNELS; ++ch ) + for (ch_cnt_t ch = 0; ch < DEFAULT_CHANNELS; ++ch) { - if( m_portMap[_port].ports[ch] != nullptr ) - { - jack_port_unregister( m_client, - m_portMap[_port].ports[ch] ); - } + if (m_portMap[port].ports[ch] != nullptr) { jack_port_unregister(m_client, m_portMap[port].ports[ch]); } } - m_portMap.erase( m_portMap.find( _port ) ); + m_portMap.erase(m_portMap.find(port)); } +#else + (void)port; #endif } - - - -void AudioJack::renamePort( AudioPort * _port ) +void AudioJack::renamePort(AudioPort* port) { #ifdef AUDIO_PORT_SUPPORT - if( m_portMap.contains( _port ) ) + if (m_portMap.contains(port)) { - const QString name[2] = { _port->name() + " L", - _port->name() + " R" }; - for( ch_cnt_t ch = 0; ch < DEFAULT_CHANNELS; ++ch ) + const QString name[2] = {port->name() + " L", port->name() + " R"}; + for (ch_cnt_t ch = 0; ch < DEFAULT_CHANNELS; ++ch) { #ifdef LMMS_HAVE_JACK_PRENAME - jack_port_rename( m_client, m_portMap[_port].ports[ch], - name[ch].toLatin1().constData() ); + jack_port_rename(m_client, m_portMap[port].ports[ch], name[ch].toLatin1().constData()); #else - jack_port_set_name( m_portMap[_port].ports[ch], - name[ch].toLatin1().constData() ); + jack_port_set_name(m_portMap[port].ports[ch], name[ch].toLatin1().constData()); #endif } } +#else + (void)port; #endif // AUDIO_PORT_SUPPORT } -int AudioJack::processCallback( jack_nframes_t _nframes, void * _udata ) +int AudioJack::processCallback(jack_nframes_t nframes) { // do midi processing first so that midi input can // add to the following sound processing - if( m_midiClient && _nframes > 0 ) + if (m_midiClient && nframes > 0) { - m_midiClient.load()->JackMidiRead(_nframes); - m_midiClient.load()->JackMidiWrite(_nframes); + m_midiClient.load()->JackMidiRead(nframes); + m_midiClient.load()->JackMidiWrite(nframes); } - for( int c = 0; c < channels(); ++c ) + for (int c = 0; c < channels(); ++c) { - m_tempOutBufs[c] = - (jack_default_audio_sample_t *) jack_port_get_buffer( - m_outputPorts[c], _nframes ); + m_tempOutBufs[c] = (jack_default_audio_sample_t*)jack_port_get_buffer(m_outputPorts[c], nframes); } #ifdef AUDIO_PORT_SUPPORT - const int frames = qMin( _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 +340,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 = qMin( - _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 +365,12 @@ int AudioJack::processCallback( jack_nframes_t _nframes, void * _udata ) } } - if( _nframes != done ) + if (nframes != done) { - for( int c = 0; c < channels(); ++c ) + for (int c = 0; c < channels(); ++c) { - jack_default_audio_sample_t * b = m_tempOutBufs[c] + done; - memset( b, 0, sizeof( *b ) * ( _nframes - done ) ); + jack_default_audio_sample_t* b = m_tempOutBufs[c] + done; + memset(b, 0, sizeof(*b) * (nframes - done)); } } @@ -431,52 +380,44 @@ int AudioJack::processCallback( jack_nframes_t _nframes, void * _udata ) -int AudioJack::staticProcessCallback( jack_nframes_t _nframes, void * _udata ) +int AudioJack::staticProcessCallback(jack_nframes_t nframes, void* udata) { - return static_cast( _udata )-> - processCallback( _nframes, _udata ); + return static_cast(udata)->processCallback(nframes); } -void AudioJack::shutdownCallback( void * _udata ) +void AudioJack::shutdownCallback(void* udata) { - auto _this = static_cast(_udata); - _this->m_client = nullptr; - _this->zombified(); + auto thisClass = static_cast(udata); + thisClass->m_client = nullptr; + emit thisClass->zombified(); } - -AudioJack::setupWidget::setupWidget( QWidget * _parent ) : - AudioDeviceSetupWidget( AudioJack::name(), _parent ) +AudioJack::setupWidget::setupWidget(QWidget* parent) + : AudioDeviceSetupWidget(AudioJack::name(), parent) { - QString cn = ConfigManager::inst()->value( "audiojack", "clientname" ); - if( cn.isEmpty() ) - { - cn = "lmms"; - } - m_clientName = new QLineEdit( cn, this ); - m_clientName->setGeometry( 10, 20, 160, 20 ); + QFormLayout * form = new QFormLayout(this); - auto cn_lbl = new QLabel(tr("Client name"), this); - cn_lbl->setFont( pointSize<7>( cn_lbl->font() ) ); - cn_lbl->setGeometry( 10, 40, 160, 10 ); + QString cn = ConfigManager::inst()->value("audiojack", "clientname"); + if (cn.isEmpty()) { cn = "lmms"; } + m_clientName = new QLineEdit(cn, this); + + form->addRow(tr("Client name"), m_clientName); auto m = new gui::LcdSpinBoxModel(/* this */); - m->setRange( DEFAULT_CHANNELS, SURROUND_CHANNELS ); - m->setStep( 2 ); - m->setValue( ConfigManager::inst()->value( "audiojack", - "channels" ).toInt() ); + m->setRange(DEFAULT_CHANNELS, SURROUND_CHANNELS); + m->setStep(2); + m->setValue(ConfigManager::inst()->value("audiojack", "channels").toInt()); - m_channels = new gui::LcdSpinBox( 1, this ); - m_channels->setModel( m ); - m_channels->setLabel( tr( "Channels" ) ); - m_channels->move( 180, 20 ); + m_channels = new gui::LcdSpinBox(1, this); + m_channels->setModel(m); + form->addRow(tr("Channels"), m_channels); } @@ -492,14 +433,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 5166bad79..1653e93fa 100644 --- a/src/core/audio/AudioOss.cpp +++ b/src/core/audio/AudioOss.cpp @@ -27,14 +27,13 @@ #ifdef LMMS_HAVE_OSS #include -#include +#include #include #include "endian_handling.h" #include "LcdSpinBox.h" #include "AudioEngine.h" #include "Engine.h" -#include "gui_templates.h" #ifdef LMMS_HAVE_UNISTD_H #include @@ -70,10 +69,10 @@ static const QString PATH_DEV_DSP = AudioOss::AudioOss( bool & _success_ful, AudioEngine* _audioEngine ) : - AudioDevice( qBound( + AudioDevice(std::clamp( + ConfigManager::inst()->value("audiooss", "channels").toInt(), DEFAULT_CHANNELS, - ConfigManager::inst()->value( "audiooss", "channels" ).toInt(), - SURROUND_CHANNELS ), _audioEngine ), + SURROUND_CHANNELS), _audioEngine), m_convertEndian( false ) { _success_ful = false; @@ -303,7 +302,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; @@ -320,12 +319,11 @@ void AudioOss::run() AudioOss::setupWidget::setupWidget( QWidget * _parent ) : AudioDeviceSetupWidget( AudioOss::name(), _parent ) { - m_device = new QLineEdit( probeDevice(), this ); - m_device->setGeometry( 10, 20, 160, 20 ); + QFormLayout * form = new QFormLayout(this); - auto dev_lbl = new QLabel(tr("Device"), this); - dev_lbl->setFont( pointSize<7>( dev_lbl->font() ) ); - dev_lbl->setGeometry( 10, 40, 160, 10 ); + m_device = new QLineEdit( probeDevice(), this ); + + form->addRow(tr("Device"), m_device); auto m = new gui::LcdSpinBoxModel(/* this */); m->setRange( DEFAULT_CHANNELS, SURROUND_CHANNELS ); @@ -335,9 +333,8 @@ AudioOss::setupWidget::setupWidget( QWidget * _parent ) : m_channels = new gui::LcdSpinBox( 1, this ); m_channels->setModel( m ); - m_channels->setLabel( tr( "Channels" ) ); - m_channels->move( 180, 20 ); + form->addRow(tr("Channels"), m_channels); } diff --git a/src/core/audio/AudioPort.cpp b/src/core/audio/AudioPort.cpp index 4bbf509d8..7bae3db1c 100644 --- a/src/core/audio/AudioPort.cpp +++ b/src/core/audio/AudioPort.cpp @@ -121,7 +121,7 @@ void AudioPort::doProcessing() if( ph->buffer() ) { if( ph->usesBuffer() - && ( ph->type() == PlayHandle::TypeNotePlayHandle + && ( ph->type() == PlayHandle::Type::NotePlayHandle || !MixHelpers::isSilent( ph->buffer(), fpp ) ) ) { m_bufferUsage = true; diff --git a/src/core/audio/AudioPortAudio.cpp b/src/core/audio/AudioPortAudio.cpp index 0f5a4122f..1cb8c8eed 100644 --- a/src/core/audio/AudioPortAudio.cpp +++ b/src/core/audio/AudioPortAudio.cpp @@ -49,11 +49,10 @@ void AudioPortAudioSetupUtil::updateChannels() #ifdef LMMS_HAVE_PORTAUDIO -#include +#include #include "Engine.h" #include "ConfigManager.h" -#include "gui_templates.h" #include "ComboBox.h" #include "AudioEngine.h" @@ -62,10 +61,10 @@ namespace lmms AudioPortAudio::AudioPortAudio( bool & _success_ful, AudioEngine * _audioEngine ) : - AudioDevice( qBound( + AudioDevice(std::clamp( + ConfigManager::inst()->value("audioportaudio", "channels").toInt(), DEFAULT_CHANNELS, - ConfigManager::inst()->value( "audioportaudio", "channels" ).toInt(), - SURROUND_CHANNELS ), _audioEngine ), + SURROUND_CHANNELS), _audioEngine), m_paStream( nullptr ), m_wasPAInitError( false ), m_outBuf( new surroundSampleFrame[audioEngine()->framesPerPeriod()] ), @@ -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 ) { @@ -295,18 +293,14 @@ int AudioPortAudio::process_callback( } m_outBufSize = frames; } - const int min_len = qMin( (int)_framesPerBuffer, - m_outBufSize - m_outBufPos ); - - float master_gain = audioEngine()->masterGain(); + const int min_len = std::min(static_cast(_framesPerBuffer), + m_outBufSize - m_outBufPos); 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 +342,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 +365,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 +377,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 ); @@ -419,19 +410,13 @@ AudioPortAudio::setupWidget::setupWidget( QWidget * _parent ) : { using gui::ComboBox; - m_backend = new ComboBox( this, "BACKEND" ); - m_backend->setGeometry( 64, 15, 260, ComboBox::DEFAULT_HEIGHT ); + QFormLayout * form = new QFormLayout(this); - auto backend_lbl = new QLabel(tr("Backend"), this); - backend_lbl->setFont( pointSize<7>( backend_lbl->font() ) ); - backend_lbl->move( 8, 18 ); + m_backend = new ComboBox( this, "BACKEND" ); + form->addRow(tr("Backend"), m_backend); m_device = new ComboBox( this, "DEVICE" ); - m_device->setGeometry( 64, 35, 260, ComboBox::DEFAULT_HEIGHT ); - - auto dev_lbl = new QLabel(tr("Device"), this); - dev_lbl->setFont( pointSize<7>( dev_lbl->font() ) ); - dev_lbl->move( 8, 38 ); + form->addRow(tr("Device"), m_device); /* LcdSpinBoxModel * m = new LcdSpinBoxModel( ); m->setRange( DEFAULT_CHANNELS, SURROUND_CHANNELS ); @@ -496,12 +481,12 @@ void AudioPortAudio::setupWidget::show() const QString& device = ConfigManager::inst()->value( "audioportaudio", "device" ); - int i = qMax( 0, m_setupUtil.m_backendModel.findText( backend ) ); + int i = std::max(0, m_setupUtil.m_backendModel.findText(backend)); m_setupUtil.m_backendModel.setValue( i ); m_setupUtil.updateDevices(); - i = qMax( 0, m_setupUtil.m_deviceModel.findText( device ) ); + i = std::max(0, m_setupUtil.m_deviceModel.findText(device)); m_setupUtil.m_deviceModel.setValue( i ); } diff --git a/src/core/audio/AudioPulseAudio.cpp b/src/core/audio/AudioPulseAudio.cpp index bac997075..63b81a9e9 100644 --- a/src/core/audio/AudioPulseAudio.cpp +++ b/src/core/audio/AudioPulseAudio.cpp @@ -22,8 +22,8 @@ * */ +#include #include -#include #include "AudioPulseAudio.h" @@ -32,7 +32,6 @@ #include "ConfigManager.h" #include "LcdSpinBox.h" #include "AudioEngine.h" -#include "gui_templates.h" #include "Engine.h" namespace lmms @@ -47,10 +46,10 @@ static void stream_write_callback(pa_stream *s, size_t length, void *userdata) AudioPulseAudio::AudioPulseAudio( bool & _success_ful, AudioEngine* _audioEngine ) : - AudioDevice( qBound( + AudioDevice(std::clamp( + ConfigManager::inst()->value("audiopa", "channels").toInt(), DEFAULT_CHANNELS, - ConfigManager::inst()->value( "audiopa", "channels" ).toInt(), - SURROUND_CHANNELS ), _audioEngine ), + SURROUND_CHANNELS), _audioEngine), m_s( nullptr ), m_quit( false ), m_convertEndian( false ) @@ -278,10 +277,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, @@ -312,24 +308,21 @@ void AudioPulseAudio::signalConnected( bool connected ) AudioPulseAudio::setupWidget::setupWidget( QWidget * _parent ) : AudioDeviceSetupWidget( AudioPulseAudio::name(), _parent ) { + QFormLayout * form = new QFormLayout(this); + m_device = new QLineEdit( AudioPulseAudio::probeDevice(), this ); - m_device->setGeometry( 10, 20, 160, 20 ); + form->addRow(tr("Device"), m_device); - auto dev_lbl = new QLabel(tr("Device"), this); - dev_lbl->setFont( pointSize<7>( dev_lbl->font() ) ); - dev_lbl->setGeometry( 10, 40, 160, 10 ); - - auto m = new gui::LcdSpinBoxModel(/* this */); + auto m = new gui::LcdSpinBoxModel(); m->setRange( DEFAULT_CHANNELS, SURROUND_CHANNELS ); m->setStep( 2 ); m->setValue( ConfigManager::inst()->value( "audiopa", - "channels" ).toInt() ); + "channels" ).toInt() ); m_channels = new gui::LcdSpinBox( 1, this ); m_channels->setModel( m ); - m_channels->setLabel( tr( "Channels" ) ); - m_channels->move( 180, 20 ); + form->addRow(tr("Channels"), m_channels); } diff --git a/src/core/audio/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 e73ccadc1..da81886ac 100644 --- a/src/core/audio/AudioSdl.cpp +++ b/src/core/audio/AudioSdl.cpp @@ -26,13 +26,12 @@ #ifdef LMMS_HAVE_SDL -#include +#include #include #include #include "AudioEngine.h" #include "ConfigManager.h" -#include "gui_templates.h" namespace lmms { @@ -70,7 +69,7 @@ AudioSdl::AudioSdl( bool & _success_ful, AudioEngine* _audioEngine ) : // to convert the buffers #endif m_audioHandle.channels = channels(); - m_audioHandle.samples = qMax( 1024, audioEngine()->framesPerPeriod()*2 ); + m_audioHandle.samples = std::max(1024, audioEngine()->framesPerPeriod() * 2); m_audioHandle.callback = sdlAudioCallback; m_audioHandle.userdata = this; @@ -257,16 +256,9 @@ void AudioSdl::sdlAudioCallback( Uint8 * _buf, int _len ) m_currentBufferFramesCount = frames; } - const uint min_frames_count = qMin( _len/sizeof(sampleFrame), + const uint min_frames_count = std::min(_len/sizeof(sampleFrame), 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; - } + - m_currentBufferFramePos); memcpy( _buf, m_outBuf + m_currentBufferFramePos, min_frames_count*sizeof(sampleFrame) ); _buf += min_frames_count*sizeof(sampleFrame); @@ -291,13 +283,10 @@ 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 = qMin( _len, m_convertedBufSize - - m_convertedBufPos ); + const int min_len = std::min(_len, m_convertedBufSize + - m_convertedBufPos); memcpy( _buf, m_convertedBuf + m_convertedBufPos, min_len ); _buf += min_len; _len -= min_len; @@ -327,14 +316,12 @@ void AudioSdl::sdlInputAudioCallback(Uint8 *_buf, int _len) { AudioSdl::setupWidget::setupWidget( QWidget * _parent ) : AudioDeviceSetupWidget( AudioSdl::name(), _parent ) { + QFormLayout * form = new QFormLayout(this); + QString dev = ConfigManager::inst()->value( "audiosdl", "device" ); m_device = new QLineEdit( dev, this ); - m_device->setGeometry( 10, 20, 160, 20 ); - - auto dev_lbl = new QLabel(tr("Device"), this); - dev_lbl->setFont( pointSize<7>( dev_lbl->font() ) ); - dev_lbl->setGeometry( 10, 40, 160, 10 ); + form->addRow(tr("Device"), m_device); } diff --git a/src/core/audio/AudioSndio.cpp b/src/core/audio/AudioSndio.cpp index a8ea34ce1..7d8e7fa8c 100644 --- a/src/core/audio/AudioSndio.cpp +++ b/src/core/audio/AudioSndio.cpp @@ -28,14 +28,13 @@ #ifdef LMMS_HAVE_SNDIO #include -#include +#include #include #include "endian_handling.h" #include "LcdSpinBox.h" #include "AudioEngine.h" #include "Engine.h" -#include "gui_templates.h" #include "ConfigManager.h" @@ -44,10 +43,10 @@ namespace lmms { AudioSndio::AudioSndio(bool & _success_ful, AudioEngine * _audioEngine) : - AudioDevice( qBound( + AudioDevice(std::clamp( + ConfigManager::inst()->value("audiosndio", "channels").toInt(), DEFAULT_CHANNELS, - ConfigManager::inst()->value( "audiosndio", "channels" ).toInt(), - SURROUND_CHANNELS ), _audioEngine ), + SURROUND_CHANNELS), _audioEngine), m_convertEndian ( false ) { _success_ful = false; @@ -167,8 +166,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; @@ -183,12 +181,10 @@ void AudioSndio::run() AudioSndio::setupWidget::setupWidget( QWidget * _parent ) : AudioDeviceSetupWidget( AudioSndio::name(), _parent ) { - m_device = new QLineEdit( "", this ); - m_device->setGeometry( 10, 20, 160, 20 ); + QFormLayout * form = new QFormLayout(this); - QLabel * dev_lbl = new QLabel( tr( "Device" ), this ); - dev_lbl->setFont( pointSize<6>( dev_lbl->font() ) ); - dev_lbl->setGeometry( 10, 40, 160, 10 ); + m_device = new QLineEdit( "", this ); + form->addRow(tr("Device"), m_device); gui::LcdSpinBoxModel * m = new gui::LcdSpinBoxModel( /* this */ ); m->setRange( DEFAULT_CHANNELS, SURROUND_CHANNELS ); @@ -198,9 +194,8 @@ AudioSndio::setupWidget::setupWidget( QWidget * _parent ) : m_channels = new gui::LcdSpinBox( 1, this ); m_channels->setModel( m ); - m_channels->setLabel( tr( "Channels" ) ); - m_channels->move( 180, 20 ); + form->addRow(tr("Channels"), m_channels); } diff --git a/src/core/audio/AudioSoundIo.cpp b/src/core/audio/AudioSoundIo.cpp index 633808204..c16327a90 100644 --- a/src/core/audio/AudioSoundIo.cpp +++ b/src/core/audio/AudioSoundIo.cpp @@ -26,13 +26,12 @@ #ifdef LMMS_HAVE_SOUNDIO -#include +#include #include #include "Engine.h" #include "debug.h" #include "ConfigManager.h" -#include "gui_templates.h" #include "ComboBox.h" #include "AudioEngine.h" @@ -40,10 +39,10 @@ namespace lmms { AudioSoundIo::AudioSoundIo( bool & outSuccessful, AudioEngine * _audioEngine ) : - AudioDevice( qBound( + AudioDevice(std::clamp( + ConfigManager::inst()->value("audiosoundio", "channels").toInt(), DEFAULT_CHANNELS, - ConfigManager::inst()->value( "audiosoundio", "channels" ).toInt(), - SURROUND_CHANNELS ), _audioEngine ) + SURROUND_CHANNELS), _audioEngine) { outSuccessful = false; m_soundio = nullptr; @@ -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; @@ -451,19 +436,13 @@ AudioSoundIo::setupWidget::setupWidget( QWidget * _parent ) : { m_setupUtil.m_setupWidget = this; - m_backend = new gui::ComboBox( this, "BACKEND" ); - m_backend->setGeometry( 64, 15, 260, 20 ); + QFormLayout * form = new QFormLayout(this); - QLabel * backend_lbl = new QLabel( tr( "Backend" ), this ); - backend_lbl->setFont( pointSize<7>( backend_lbl->font() ) ); - backend_lbl->move( 8, 18 ); + m_backend = new gui::ComboBox( this, "BACKEND" ); + form->addRow(tr("Backend"), m_backend); m_device = new gui::ComboBox( this, "DEVICE" ); - m_device->setGeometry( 64, 35, 260, 20 ); - - QLabel * dev_lbl = new QLabel( tr( "Device" ), this ); - dev_lbl->setFont( pointSize<7>( dev_lbl->font() ) ); - dev_lbl->move( 8, 38 ); + form->addRow(tr("Device"), m_device); // Setup models m_soundio = soundio_create(); diff --git a/src/core/fft_helpers.cpp b/src/core/fft_helpers.cpp index 63088f655..35906e8d3 100644 --- a/src/core/fft_helpers.cpp +++ b/src/core/fft_helpers.cpp @@ -102,7 +102,7 @@ int notEmpty(const std::vector &spectrum) * * return -1 on error */ -int precomputeWindow(float *window, unsigned int length, FFT_WINDOWS type, bool normalized) +int precomputeWindow(float *window, unsigned int length, FFTWindow type, bool normalized) { if (window == nullptr) {return -1;} @@ -117,23 +117,23 @@ int precomputeWindow(float *window, unsigned int length, FFT_WINDOWS type, bool switch (type) { default: - case RECTANGULAR: + case FFTWindow::Rectangular: for (unsigned int i = 0; i < length; i++) {window[i] = 1.0;} gain = 1; return 0; - case BLACKMAN_HARRIS: + case FFTWindow::BlackmanHarris: a0 = 0.35875; a1 = 0.48829; a2 = 0.14128; a3 = 0.01168; break; - case HAMMING: + case FFTWindow::Hamming: a0 = 0.54; a1 = 1.0 - a0; a2 = 0; a3 = 0; break; - case HANNING: + case FFTWindow::Hanning: a0 = 0.5; a1 = 1.0 - a0; a2 = 0; diff --git a/src/core/lv2/Lv2ControlBase.cpp b/src/core/lv2/Lv2ControlBase.cpp index 3ab8b278c..5741866e9 100644 --- a/src/core/lv2/Lv2ControlBase.cpp +++ b/src/core/lv2/Lv2ControlBase.cpp @@ -1,7 +1,7 @@ /* * Lv2ControlBase.cpp - Lv2 control base class * - * Copyright (c) 2018-2020 Johannes Lorenz + * Copyright (c) 2018-2023 Johannes Lorenz * * This file is part of LMMS - https://lmms.io * @@ -39,7 +39,7 @@ namespace lmms { -Plugin::PluginTypes Lv2ControlBase::check(const LilvPlugin *plugin, +Plugin::Type Lv2ControlBase::check(const LilvPlugin *plugin, std::vector &issues) { // for some reason, all checks can be done by one processor... @@ -54,35 +54,12 @@ Lv2ControlBase::Lv2ControlBase(Model* that, const QString &uri) : { if (m_plugin) { - int channelsLeft = DEFAULT_CHANNELS; // LMMS plugins are stereo - while (channelsLeft > 0) - { - std::unique_ptr newOne = std::make_unique(m_plugin, that); - 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(); - } + init(that); } else { qCritical() << "No Lv2 plugin found for URI" << uri; - m_valid = false; + throw std::runtime_error("No Lv2 plugin found for given URI"); } } @@ -94,6 +71,41 @@ Lv2ControlBase::~Lv2ControlBase() = default; +void Lv2ControlBase::init(Model* meAsModel) +{ + int channelsLeft = DEFAULT_CHANNELS; // LMMS plugins are stereo + while (channelsLeft > 0) + { + std::unique_ptr newOne = std::make_unique(m_plugin, meAsModel); + 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(); +} + + + + +void Lv2ControlBase::shutdown() +{ + // currently nothing to do here +} + + + + +void Lv2ControlBase::reload() +{ + for (const auto& c : m_procs) { c->reload(); } +} + + + + LinkedModelGroup *Lv2ControlBase::getGroup(std::size_t idx) { return (m_procs.size() > idx) ? m_procs[idx].get() : nullptr; @@ -183,14 +195,6 @@ void Lv2ControlBase::loadFile(const QString &file) -void Lv2ControlBase::reloadPlugin() -{ - // TODO -} - - - - std::size_t Lv2ControlBase::controlCount() const { std::size_t res = 0; for (const auto& c : m_procs) { res += c->controlCount(); } diff --git a/src/core/lv2/Lv2Evbuf.cpp b/src/core/lv2/Lv2Evbuf.cpp index acfb9b8aa..07a3d58e6 100644 --- a/src/core/lv2/Lv2Evbuf.cpp +++ b/src/core/lv2/Lv2Evbuf.cpp @@ -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 be7fb6e28..25ee11544 100644 --- a/src/core/lv2/Lv2Features.cpp +++ b/src/core/lv2/Lv2Features.cpp @@ -48,7 +48,7 @@ Lv2Features::Lv2Features() { const Lv2Manager* man = Engine::getLv2Manager(); // create (yet empty) map feature URI -> feature - for(const char* uri : man->supportedFeatureURIs()) + for(auto uri : man->supportedFeatureURIs()) { m_featureByUri.emplace(uri, nullptr); } @@ -71,7 +71,7 @@ void Lv2Features::initCommon() void Lv2Features::createFeatureVectors() { // create vector of features - for(std::pair& pr : m_featureByUri) + for(const auto& [uri, feature] : m_featureByUri) { /* If pr.second is nullptr here, this means that the LV2_feature @@ -82,7 +82,7 @@ void Lv2Features::createFeatureVectors() vector creation (This can be done in Lv2Proc::initPluginSpecificFeatures or in Lv2Features::initCommon) */ - m_features.push_back(LV2_Feature { pr.first, pr.second }); + m_features.push_back(LV2_Feature{(const char*)uri.data(), (void*)feature}); } // create pointer vector (for lilv_plugin_instantiate) @@ -105,6 +105,19 @@ void *&Lv2Features::operator[](const char *featName) } + + +void Lv2Features::clear() +{ + m_features.clear(); + for (auto& [uri, feature] : m_featureByUri) + { + (void) uri; + feature = nullptr; + } +} + + } // namespace lmms #endif // LMMS_HAVE_LV2 diff --git a/src/core/lv2/Lv2Manager.cpp b/src/core/lv2/Lv2Manager.cpp index b1391b065..6a1b2a8af 100644 --- a/src/core/lv2/Lv2Manager.cpp +++ b/src/core/lv2/Lv2Manager.cpp @@ -1,7 +1,7 @@ /* * Lv2Manager.cpp - Implementation of Lv2Manager class * - * Copyright (c) 2018-2020 Johannes Lorenz + * Copyright (c) 2018-2023 Johannes Lorenz * * This file is part of LMMS - https://lmms.io * @@ -28,13 +28,14 @@ #include #include -#include #include #include #include +#include #include #include +#include "AudioEngine.h" #include "Engine.h" #include "Plugin.h" #include "Lv2ControlBase.h" @@ -46,7 +47,7 @@ namespace lmms { -const std::set Lv2Manager::pluginBlacklist = +const std::set Lv2Manager::pluginBlacklist = { // github.com/calf-studio-gear/calf, #278 "http://calf.sourceforge.net/plugins/Analyzer", @@ -60,6 +61,12 @@ const std::set Lv2Manager::pluginBlacklist = "http://calf.sourceforge.net/plugins/TransientDesigner", "http://calf.sourceforge.net/plugins/Vinyl", + // https://gitlab.com/drobilla/blop-lv2/-/issues/3 + "http://drobilla.net/plugins/blop/pulse", + "http://drobilla.net/plugins/blop/sawtooth", + "http://drobilla.net/plugins/blop/square", + "http://drobilla.net/plugins/blop/triangle", + // Visualization, meters, and scopes etc., won't work until we have gui support "http://distrho.sf.net/plugins/ProM", "http://distrho.sf.net/plugins/glBars", @@ -131,6 +138,26 @@ const std::set Lv2Manager::pluginBlacklist = "urn:juced:DrumSynth" }; +const std::set Lv2Manager::pluginBlacklistBuffersizeLessThan32 = +{ + "http://moddevices.com/plugins/mod-devel/2Voices", + "http://moddevices.com/plugins/mod-devel/Capo", + "http://moddevices.com/plugins/mod-devel/Drop", + "http://moddevices.com/plugins/mod-devel/Harmonizer", + "http://moddevices.com/plugins/mod-devel/Harmonizer2", + "http://moddevices.com/plugins/mod-devel/HarmonizerCS", + "http://moddevices.com/plugins/mod-devel/SuperCapo", + "http://moddevices.com/plugins/mod-devel/SuperWhammy", + "http://moddevices.com/plugins/mod-devel/Gx2Voices", + "http://moddevices.com/plugins/mod-devel/GxCapo", + "http://moddevices.com/plugins/mod-devel/GxDrop", + "http://moddevices.com/plugins/mod-devel/GxHarmonizer", + "http://moddevices.com/plugins/mod-devel/GxHarmonizer2", + "http://moddevices.com/plugins/mod-devel/GxHarmonizerCS", + "http://moddevices.com/plugins/mod-devel/GxSuperCapo", + "http://moddevices.com/plugins/mod-devel/GxSuperWhammy" +}; + @@ -146,10 +173,15 @@ Lv2Manager::Lv2Manager() : m_supportedFeatureURIs.insert(LV2_URID__map); m_supportedFeatureURIs.insert(LV2_URID__unmap); m_supportedFeatureURIs.insert(LV2_OPTIONS__options); + m_supportedFeatureURIs.insert(LV2_WORKER__schedule); // min/max is always passed in the options m_supportedFeatureURIs.insert(LV2_BUF_SIZE__boundedBlockLength); // block length is only changed initially in AudioEngine CTOR m_supportedFeatureURIs.insert(LV2_BUF_SIZE__fixedBlockLength); + if (const auto fpp = Engine::audioEngine()->framesPerPeriod(); (fpp & (fpp - 1)) == 0) // <=> ffp is power of 2 (for ffp > 0) + { + m_supportedFeatureURIs.insert(LV2_BUF_SIZE__powerOf2BlockLength); + } auto supportOpt = [this](Lv2UridCache::Id id) { @@ -211,7 +243,7 @@ void Lv2Manager::initPlugins() const LilvPlugin* curPlug = lilv_plugins_get(plugins, itr); std::vector issues; - Plugin::PluginTypes type = Lv2ControlBase::check(curPlug, issues); + Plugin::Type type = Lv2ControlBase::check(curPlug, issues); std::sort(issues.begin(), issues.end()); auto last = std::unique(issues.begin(), issues.end()); issues.erase(last, issues.end()); @@ -234,7 +266,7 @@ void Lv2Manager::initPlugins() { if(std::any_of(issues.begin(), issues.end(), [](const PluginIssue& iss) { - return iss.type() == PluginIssueType::blacklisted; })) + return iss.type() == PluginIssueType::Blacklisted; })) { ++blacklisted; } @@ -282,14 +314,6 @@ void Lv2Manager::initPlugins() -bool Lv2Manager::CmpStr::operator()(const char *a, const char *b) const -{ - return std::strcmp(a, b) < 0; -} - - - - bool Lv2Manager::isFeatureSupported(const char *featName) const { return m_supportedFeatureURIs.find(featName) != m_supportedFeatureURIs.end(); diff --git a/src/core/lv2/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 1ce1d6956..a4625936e 100644 --- a/src/core/lv2/Lv2Ports.cpp +++ b/src/core/lv2/Lv2Ports.cpp @@ -114,19 +114,19 @@ std::vector Meta::get(const LilvPlugin *plugin, m_optional = hasProperty(LV2_CORE__connectionOptional); - m_vis = hasProperty(LV2_CORE__integer) - ? Vis::Integer // WARNING: this may still be changed below + m_vis = hasProperty(LV2_CORE__toggled) + ? Vis::Toggled : hasProperty(LV2_CORE__enumeration) ? Vis::Enumeration - : hasProperty(LV2_CORE__toggled) - ? Vis::Toggled + : hasProperty(LV2_CORE__integer) + ? Vis::Integer // WARNING: this may still be changed below : Vis::Generic; if (isA(LV2_CORE__InputPort)) { m_flow = Flow::Input; } else if (isA(LV2_CORE__OutputPort)) { m_flow = Flow::Output; } else { m_flow = Flow::Unknown; - issue(unknownPortFlow, portName); + issue(PluginIssueType::UnknownPortFlow, portName); } m_def = .0f; @@ -145,7 +145,7 @@ std::vector Meta::get(const LilvPlugin *plugin, if (isA(LV2_CORE__CVPort)) { // currently not supported, but we can still check the metadata - issue(badPortType, "cvPort"); + issue(PluginIssueType::BadPortType, "cvPort"); } m_type = isA(LV2_CORE__CVPort) ? Type::Cv : Type::Control; @@ -172,21 +172,21 @@ std::vector Meta::get(const LilvPlugin *plugin, } }; - takeRangeValue(def.get(), m_def, portHasNoDef); + takeRangeValue(def.get(), m_def, PluginIssueType::PortHasNoDef); if (isToggle) { m_min = .0f; m_max = 1.f; if(def.get() && m_def != m_min && m_def != m_max) { - issue(defaultValueNotInRange, portName); + issue(PluginIssueType::DefaultValueNotInRange, portName); } } else { // take min/max - takeRangeValue(min.get(), m_min, portHasNoMin); - takeRangeValue(max.get(), m_max, portHasNoMax); + takeRangeValue(min.get(), m_min, PluginIssueType::PortHasNoMin); + takeRangeValue(max.get(), m_max, PluginIssueType::PortHasNoMax); if(m_type == Type::Cv) { // no range is allowed and bashed to [-1,+1], @@ -196,10 +196,10 @@ std::vector Meta::get(const LilvPlugin *plugin, m_min = -1.f; m_max = +1.f; } - else if(!m_min_set()) { issue(portHasNoMin, portName); } - else if(!m_max_set()) { issue(portHasNoMax, portName); } + else if(!m_min_set()) { issue(PluginIssueType::PortHasNoMin, portName); } + else if(!m_max_set()) { issue(PluginIssueType::PortHasNoMax, portName); } } - if(m_min > m_max) { issue(minGreaterMax, portName); } + if(m_min > m_max) { issue(PluginIssueType::MinGreaterMax, portName); } // sampleRate if (hasProperty(LV2_CORE__sampleRate)) { m_sampleRate = true; } @@ -207,7 +207,7 @@ std::vector Meta::get(const LilvPlugin *plugin, // default value if (def.get()) { - if (m_def < m_min) { issue(defaultValueNotInRange, portName); } + if (m_def < m_min) { issue(PluginIssueType::DefaultValueNotInRange, portName); } else if (m_def > m_max) { if(m_sampleRate) @@ -215,7 +215,7 @@ std::vector Meta::get(const LilvPlugin *plugin, // multiplying with sample rate will hopefully lead us // to a good default value } - else { issue(defaultValueNotInRange, portName); } + else { issue(PluginIssueType::DefaultValueNotInRange, portName); } } } @@ -254,7 +254,7 @@ std::vector Meta::get(const LilvPlugin *plugin, { if (m_optional) { m_used = false; } else { - issue(PluginIssueType::unknownPortType, portName); + issue(PluginIssueType::UnknownPortType, portName); } } @@ -265,16 +265,16 @@ std::vector Meta::get(const LilvPlugin *plugin, // be non-Lv2-conforming if(m_min == std::numeric_limits::lowest()) { - issue(PluginIssueType::logScaleMinMissing, portName); + issue(PluginIssueType::LogScaleMinMissing, portName); } if(m_max == std::numeric_limits::max()) { - issue(PluginIssueType::logScaleMaxMissing, portName); + issue(PluginIssueType::LogScaleMaxMissing, portName); } // forbid min < 0 < max if(m_min < 0.f && m_max > 0.f) { - issue(PluginIssueType::logScaleMinMaxDifferentSigns, portName); + issue(PluginIssueType::LogScaleMinMaxDifferentSigns, portName); } m_logarithmic = true; } diff --git a/src/core/lv2/Lv2Proc.cpp b/src/core/lv2/Lv2Proc.cpp index cbb4be2d2..4715aeb7f 100644 --- a/src/core/lv2/Lv2Proc.cpp +++ b/src/core/lv2/Lv2Proc.cpp @@ -1,7 +1,7 @@ /* * Lv2Proc.cpp - Lv2 processor class * - * Copyright (c) 2019-2020 Johannes Lorenz + * Copyright (c) 2019-2022 Johannes Lorenz * * This file is part of LMMS - https://lmms.io * @@ -30,7 +30,9 @@ #include #include #include +#include #include +#include #include #include "AudioEngine.h" @@ -43,6 +45,7 @@ #include "Lv2Evbuf.h" #include "MidiEvent.h" #include "MidiEventToByteSeq.h" +#include "NoCopyNoMove.h" namespace lmms @@ -60,7 +63,7 @@ struct MidiInputEvent -Plugin::PluginTypes Lv2Proc::check(const LilvPlugin *plugin, +Plugin::Type Lv2Proc::check(const LilvPlugin *plugin, std::vector& issues) { unsigned maxPorts = lilv_plugin_get_num_ports(plugin); @@ -74,11 +77,19 @@ Plugin::PluginTypes Lv2Proc::check(const LilvPlugin *plugin, // TODO: manage a global blacklist outside of the code // for now, this will help // this is only a fix for the meantime - const auto& pluginBlacklist = Lv2Manager::getPluginBlacklist(); - if (!Engine::ignorePluginBlacklist() && - pluginBlacklist.find(pluginUri) != pluginBlacklist.end()) + if (!Engine::ignorePluginBlacklist()) { - issues.emplace_back(blacklisted); + const auto& pluginBlacklist = Lv2Manager::getPluginBlacklist(); + const auto& pluginBlacklist32 = Lv2Manager::getPluginBlacklistBuffersizeLessThan32(); + if(pluginBlacklist.find(pluginUri) != pluginBlacklist.end()) + { + issues.emplace_back(PluginIssueType::Blacklisted); + } + else if(Engine::audioEngine()->framesPerPeriod() <= 32 && + pluginBlacklist32.find(pluginUri) != pluginBlacklist32.end()) + { + issues.emplace_back(PluginIssueType::Blacklisted); // currently no special blacklist category + } } for (unsigned portNum = 0; portNum < maxPorts; ++portNum) @@ -105,19 +116,19 @@ Plugin::PluginTypes Lv2Proc::check(const LilvPlugin *plugin, } if (audioChannels[inCount] > 2) - issues.emplace_back(tooManyInputChannels, + issues.emplace_back(PluginIssueType::TooManyInputChannels, std::to_string(audioChannels[inCount])); if (audioChannels[outCount] == 0) - issues.emplace_back(noOutputChannel); + issues.emplace_back(PluginIssueType::NoOutputChannel); else if (audioChannels[outCount] > 2) - issues.emplace_back(tooManyOutputChannels, + issues.emplace_back(PluginIssueType::TooManyOutputChannels, std::to_string(audioChannels[outCount])); if (midiChannels[inCount] > 1) - issues.emplace_back(tooManyMidiInputChannels, + issues.emplace_back(PluginIssueType::TooManyMidiInputChannels, std::to_string(midiChannels[inCount])); if (midiChannels[outCount] > 1) - issues.emplace_back(tooManyMidiOutputChannels, + issues.emplace_back(PluginIssueType::TooManyMidiOutputChannels, std::to_string(midiChannels[outCount])); AutoLilvNodes reqFeats(lilv_plugin_get_required_features(plugin)); @@ -127,7 +138,7 @@ Plugin::PluginTypes Lv2Proc::check(const LilvPlugin *plugin, lilv_nodes_get(reqFeats.get(), itr)); if(!Lv2Features::isFeatureSupported(reqFeatName)) { - issues.emplace_back(featureNotSupported, reqFeatName); + issues.emplace_back(PluginIssueType::FeatureNotSupported, reqFeatName); } } @@ -143,27 +154,50 @@ Plugin::PluginTypes Lv2Proc::check(const LilvPlugin *plugin, { // yes, this is not a Lv2 feature, // but it's a feature in abstract sense - issues.emplace_back(featureNotSupported, ro); + issues.emplace_back(PluginIssueType::FeatureNotSupported, ro); } } } return (audioChannels[inCount] > 2 || audioChannels[outCount] > 2) - ? Plugin::Undefined + ? Plugin::Type::Undefined : (audioChannels[inCount] > 0) - ? Plugin::Effect - : Plugin::Instrument; + ? Plugin::Type::Effect + : Plugin::Type::Instrument; } +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), + m_workLock(1), m_midiInputBuf(m_maxMidiInputEvents), m_midiInputReader(m_midiInputBuf) { + createPorts(); initPlugin(); } @@ -175,6 +209,11 @@ Lv2Proc::~Lv2Proc() { shutdownPlugin(); } +void Lv2Proc::reload() { Lv2ProcSuspender(this); } + + + + void Lv2Proc::dumpPorts() { std::size_t num = 0; @@ -331,7 +370,19 @@ void Lv2Proc::copyBuffersToCore(sampleFrame* buf, void Lv2Proc::run(fpp_t frames) { + if (m_worker) + { + // Process any worker replies + m_worker->emitResponses(); + } + lilv_instance_run(m_instance, static_cast(frames)); + + if (m_worker) + { + // Notify the plugin the run() cycle is finished + m_worker->notifyPluginThatRunFinished(); + } } @@ -374,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; } @@ -391,16 +438,22 @@ void Lv2Proc::initPlugin() initPluginSpecificFeatures(); m_features.createFeatureVectors(); - createPorts(); - m_instance = lilv_plugin_instantiate(m_plugin, Engine::audioEngine()->processingSampleRate(), m_features.featurePointers()); if (m_instance) { + 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 @@ -410,7 +463,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"); } } @@ -419,12 +472,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_options.clear(); } @@ -472,8 +525,18 @@ void Lv2Proc::initMOptions() void Lv2Proc::initPluginSpecificFeatures() { + // options initMOptions(); m_features[LV2_OPTIONS__options] = const_cast(m_options.feature()); + + // worker (if plugin has worker extension) + Lv2Manager* mgr = Engine::getLv2Manager(); + if (lilv_plugin_has_extension_data(m_plugin, mgr->uri(LV2_WORKER__interface).get())) { + bool threaded = !Engine::audioEngine()->renderOnly(); + m_worker.emplace(&m_workLock, threaded); + m_features[LV2_WORKER__schedule] = m_worker->feature(); + // note: the worker interface can not be instantiated yet - it requires m_instance. see initPlugin() + } } @@ -542,21 +605,35 @@ void Lv2Proc::createPort(std::size_t portNum) break; case Lv2Ports::Vis::Enumeration: { - auto comboModel = new ComboBoxModel(nullptr, dispName); - LilvScalePoints* sps = - lilv_port_get_scale_points(m_plugin, lilvPort); - LILV_FOREACH(scale_points, i, sps) + ComboBoxModel* comboModel = new ComboBoxModel(nullptr, dispName); + { - const LilvScalePoint* sp = lilv_scale_points_get(sps, i); - ctrl->m_scalePointMap.push_back(lilv_node_as_float( - lilv_scale_point_get_value(sp))); - comboModel->addItem( - lilv_node_as_string( - lilv_scale_point_get_label(sp))); + AutoLilvScalePoints sps (static_cast(lilv_port_get_scale_points(m_plugin, lilvPort))); + // temporary map, since lilv may return scale points in random order + std::map scalePointMap; + LILV_FOREACH(scale_points, i, sps.get()) + { + const LilvScalePoint* sp = lilv_scale_points_get(sps.get(), i); + const float f = lilv_node_as_float(lilv_scale_point_get_value(sp)); + const char* s = lilv_node_as_string(lilv_scale_point_get_label(sp)); + scalePointMap[f] = s; + } + for (const auto& [f,s] : scalePointMap) + { + ctrl->m_scalePointMap.push_back(f); + comboModel->addItem(s); + } + } + for(std::size_t i = 0; i < ctrl->m_scalePointMap.size(); ++i) + { + if(meta.def() == ctrl->m_scalePointMap[i]) + { + comboModel->setValue(i); + comboModel->setInitValue(i); + break; + } } - lilv_scale_points_free(sps); ctrl->m_connectedModel.reset(comboModel); - // TODO: use default value on comboModel, too? break; } case Lv2Ports::Vis::Toggled: diff --git a/src/core/lv2/Lv2SubPluginFeatures.cpp b/src/core/lv2/Lv2SubPluginFeatures.cpp index 4fb377b1c..135da3e2a 100644 --- a/src/core/lv2/Lv2SubPluginFeatures.cpp +++ b/src/core/lv2/Lv2SubPluginFeatures.cpp @@ -3,7 +3,7 @@ * Plugin::Descriptor::SubPluginFeatures for * hosting LV2 plugins * - * Copyright (c) 2018-2020 Johannes Lorenz + * Copyright (c) 2018-2023 Johannes Lorenz * * This file is part of LMMS - https://lmms.io * @@ -59,7 +59,7 @@ QString Lv2SubPluginFeatures::pluginName(const LilvPlugin *plug) -Lv2SubPluginFeatures::Lv2SubPluginFeatures(Plugin::PluginTypes type) : +Lv2SubPluginFeatures::Lv2SubPluginFeatures(Plugin::Type type) : SubPluginFeatures(type) { } @@ -81,7 +81,7 @@ void Lv2SubPluginFeatures::fillDescriptionWidget(QWidget *parent, auto maker = new QWidget(parent); auto l = new QHBoxLayout(maker); - l->setMargin(0); + l->setContentsMargins(0, 0, 0, 0); l->setSpacing(0); auto maker_label = new QLabel(maker); @@ -98,7 +98,7 @@ void Lv2SubPluginFeatures::fillDescriptionWidget(QWidget *parent, auto copyright = new QWidget(parent); l = new QHBoxLayout(copyright); - l->setMargin(0); + l->setContentsMargins(0, 0, 0, 0); l->setSpacing(0); copyright->setMinimumWidth(parent->minimumWidth()); diff --git a/src/core/lv2/Lv2Worker.cpp b/src/core/lv2/Lv2Worker.cpp new file mode 100644 index 000000000..c763bacad --- /dev/null +++ b/src/core/lv2/Lv2Worker.cpp @@ -0,0 +1,227 @@ +/* + * Lv2Worker.cpp - Lv2Worker implementation + * + * Copyright (c) 2022-2022 Johannes Lorenz + * + * This file is part of LMMS - https://lmms.io + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program (see COPYING); if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA. + * + */ + +#include "Lv2Worker.h" + +#include +#include + +#ifdef LMMS_HAVE_LV2 + +#include "Engine.h" + + +namespace lmms +{ + + +// static wrappers + +static LV2_Worker_Status +staticWorkerRespond(LV2_Worker_Respond_Handle handle, + uint32_t size, const void* data) +{ + Lv2Worker* worker = static_cast(handle); + return worker->respond(size, data); +} + + + + +std::size_t Lv2Worker::bufferSize() const +{ + // ardour uses this fixed size for ALSA: + return 8192 * 4; + // for jack, they use 4 * jack_port_type_get_buffer_size (..., JACK_DEFAULT_MIDI_TYPE) + // (possible extension for AudioDevice) +} + + + + +Lv2Worker::Lv2Worker(Semaphore* commonWorkLock, bool threaded) : + m_threaded(threaded), + m_response(bufferSize()), + m_requests(bufferSize()), + m_responses(bufferSize()), + m_requestsReader(m_requests), + m_responsesReader(m_responses), + m_sem(0), + m_workLock(commonWorkLock) +{ + m_scheduleFeature.handle = static_cast(this); + m_scheduleFeature.schedule_work = [](LV2_Worker_Schedule_Handle handle, + uint32_t size, const void* data) -> LV2_Worker_Status + { + Lv2Worker* worker = static_cast(handle); + return worker->scheduleWork(size, data); + }; + + if (threaded) { m_thread = std::thread(&Lv2Worker::workerFunc, this); } + + m_requests.mlock(); + m_responses.mlock(); +} + + + + +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; + if(m_threaded) { + m_sem.post(); + m_thread.join(); + } +} + + + + +// Let the worker send responses to the audio thread +LV2_Worker_Status Lv2Worker::respond(uint32_t size, const void* data) +{ + if(m_threaded) + { + if(m_responses.free() < sizeof(size) + size) + { + return LV2_WORKER_ERR_NO_SPACE; + } + else + { + m_responses.write((const char*)&size, sizeof(size)); + if(size && data) { m_responses.write((const char*)data, size); } + } + } + else + { + assert(m_handle); + assert(m_interface); + m_interface->work_response(m_handle, size, data); + } + return LV2_WORKER_SUCCESS; +} + + + + +// Let the worker receive work from the audio thread and "work" on it +void Lv2Worker::workerFunc() +{ + std::vector buf; + uint32_t size; + while (true) { + m_sem.wait(); + if (m_exit) { break; } + + const std::size_t readSpace = m_requestsReader.read_space(); + if (readSpace <= sizeof(size)) { continue; } // (should not happen) + + m_requestsReader.read(sizeof(size)).copy((char*)&size, sizeof(size)); + assert(size <= readSpace - sizeof(size)); + if(size > buf.size()) { buf.resize(size); } + if(size) { m_requestsReader.read(size).copy(buf.data(), size); } + + assert(m_handle); + assert(m_interface); + m_workLock->wait(); + m_interface->work(m_handle, staticWorkerRespond, this, size, buf.data()); + m_workLock->post(); + } +} + + + + +// Let the audio thread schedule work for the worker +LV2_Worker_Status Lv2Worker::scheduleWork(uint32_t size, const void *data) +{ + if (m_threaded) + { + if(m_requests.free() < sizeof(size) + size) + { + return LV2_WORKER_ERR_NO_SPACE; + } + else + { + // Schedule a request to be executed by the worker thread + m_requests.write((const char*)&size, sizeof(size)); + if(size && data) { m_requests.write((const char*)data, size); } + m_sem.post(); + } + } + else + { + assert(m_handle); + assert(m_interface); + // Execute work immediately in this thread + m_workLock->wait(); + m_interface->work(m_handle, staticWorkerRespond, this, size, data); + m_workLock->post(); + } + + return LV2_WORKER_SUCCESS; +} + + + + +// Let the audio thread read incoming worker responses, and process it +void Lv2Worker::emitResponses() +{ + std::size_t read_space = m_responsesReader.read_space(); + uint32_t size; + while (read_space > sizeof(size)) + { + 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_interface->work_response(m_handle, size, m_response.data()); + read_space -= sizeof(size) + size; + } +} + + +} // namespace lmms + +#endif // LMMS_HAVE_LV2 diff --git a/src/core/main.cpp b/src/core/main.cpp index da13181fe..cadffdafa 100644 --- a/src/core/main.cpp +++ b/src/core/main.cpp @@ -363,9 +363,9 @@ int main( int argc, char * * argv ) new QCoreApplication( argc, argv ) : new gui::MainApplication(argc, argv); - AudioEngine::qualitySettings qs( AudioEngine::qualitySettings::Mode_HighQuality ); - OutputSettings os( 44100, OutputSettings::BitRateSettings(160, false), OutputSettings::Depth_16Bit, OutputSettings::StereoMode_JointStereo ); - ProjectRenderer::ExportFileFormats eff = ProjectRenderer::WaveFile; + AudioEngine::qualitySettings qs( AudioEngine::qualitySettings::Mode::HighQuality ); + OutputSettings os( 44100, OutputSettings::BitRateSettings(160, false), OutputSettings::BitDepth::Depth16Bit, OutputSettings::StereoMode::JointStereo ); + ProjectRenderer::ExportFileFormat eff = ProjectRenderer::ExportFileFormat::Wave; // second of two command-line parsing stages for( int i = 1; i < argc; ++i ) @@ -517,23 +517,23 @@ int main( int argc, char * * argv ) if( ext == "wav" ) { - eff = ProjectRenderer::WaveFile; + eff = ProjectRenderer::ExportFileFormat::Wave; } #ifdef LMMS_HAVE_OGGVORBIS else if( ext == "ogg" ) { - eff = ProjectRenderer::OggFile; + eff = ProjectRenderer::ExportFileFormat::Ogg; } #endif #ifdef LMMS_HAVE_MP3LAME else if( ext == "mp3" ) { - eff = ProjectRenderer::MP3File; + eff = ProjectRenderer::ExportFileFormat::MP3; } #endif else if (ext == "flac") { - eff = ProjectRenderer::FlacFile; + eff = ProjectRenderer::ExportFileFormat::Flac; } else { @@ -596,15 +596,15 @@ int main( int argc, char * * argv ) if( mode == "s" ) { - os.setStereoMode(OutputSettings::StereoMode_Stereo); + os.setStereoMode(OutputSettings::StereoMode::Stereo); } else if( mode == "j" ) { - os.setStereoMode(OutputSettings::StereoMode_JointStereo); + os.setStereoMode(OutputSettings::StereoMode::JointStereo); } else if( mode == "m" ) { - os.setStereoMode(OutputSettings::StereoMode_Mono); + os.setStereoMode(OutputSettings::StereoMode::Mono); } else { @@ -613,7 +613,7 @@ int main( int argc, char * * argv ) } else if( arg =="--float" || arg == "-a" ) { - os.setBitDepth(OutputSettings::Depth_32Bit); + os.setBitDepth(OutputSettings::BitDepth::Depth32Bit); } else if( arg == "--interpolation" || arg == "-i" ) { @@ -629,19 +629,19 @@ int main( int argc, char * * argv ) if( ip == "linear" ) { - qs.interpolation = AudioEngine::qualitySettings::Interpolation_Linear; + qs.interpolation = AudioEngine::qualitySettings::Interpolation::Linear; } else if( ip == "sincfastest" ) { - qs.interpolation = AudioEngine::qualitySettings::Interpolation_SincFastest; + qs.interpolation = AudioEngine::qualitySettings::Interpolation::SincFastest; } else if( ip == "sincmedium" ) { - qs.interpolation = AudioEngine::qualitySettings::Interpolation_SincMedium; + qs.interpolation = AudioEngine::qualitySettings::Interpolation::SincMedium; } else if( ip == "sincbest" ) { - qs.interpolation = AudioEngine::qualitySettings::Interpolation_SincBest; + qs.interpolation = AudioEngine::qualitySettings::Interpolation::SincBest; } else { @@ -663,16 +663,16 @@ int main( int argc, char * * argv ) switch( o ) { case 1: - qs.oversampling = AudioEngine::qualitySettings::Oversampling_None; + qs.oversampling = AudioEngine::qualitySettings::Oversampling::None; break; case 2: - qs.oversampling = AudioEngine::qualitySettings::Oversampling_2x; + qs.oversampling = AudioEngine::qualitySettings::Oversampling::X2; break; case 4: - qs.oversampling = AudioEngine::qualitySettings::Oversampling_4x; + qs.oversampling = AudioEngine::qualitySettings::Oversampling::X4; break; case 8: - qs.oversampling = AudioEngine::qualitySettings::Oversampling_8x; + qs.oversampling = AudioEngine::qualitySettings::Oversampling::X8; break; default: return usageError( QString( "Invalid oversampling %1" ).arg( argv[i] ) ); @@ -906,19 +906,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 760840c77..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 ) ); @@ -245,16 +242,16 @@ void MidiAlsaSeq::applyPortMode( MidiPort * _port ) switch( _port->mode() ) { - case MidiPort::Duplex: + case MidiPort::Mode::Duplex: caps[1] |= SND_SEQ_PORT_CAP_READ | SND_SEQ_PORT_CAP_SUBS_READ; - case MidiPort::Input: + case MidiPort::Mode::Input: caps[0] |= SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_SUBS_WRITE; break; - case MidiPort::Output: + case MidiPort::Mode::Output: caps[1] |= SND_SEQ_PORT_CAP_READ | SND_SEQ_PORT_CAP_SUBS_READ; break; diff --git a/src/core/midi/MidiApple.cpp b/src/core/midi/MidiApple.cpp index 01836c50b..444f093e5 100644 --- a/src/core/midi/MidiApple.cpp +++ b/src/core/midi/MidiApple.cpp @@ -259,7 +259,7 @@ void MidiApple::HandleReadCallback( const MIDIPacketList *pktlist, void *srcConn nBytes = packet->length; // Check if this is the end of a continued SysEx message if (continueSysEx) { - unsigned int lengthToCopy = qMin(nBytes, SYSEX_LENGTH - sysExLength); + unsigned int lengthToCopy = std::min(nBytes, SYSEX_LENGTH - sysExLength); // Copy the message into our SysEx message buffer, // making sure not to overrun the buffer memcpy(sysExMessage + sysExLength, packet->data, lengthToCopy); @@ -298,7 +298,7 @@ void MidiApple::HandleReadCallback( const MIDIPacketList *pktlist, void *srcConn { // MIDI SysEx then we copy the rest of the message into the SysEx message buffer unsigned int lengthLeftInMessage = nBytes - iByte; - unsigned int lengthToCopy = qMin(lengthLeftInMessage, SYSEX_LENGTH); + unsigned int lengthToCopy = std::min(lengthLeftInMessage, SYSEX_LENGTH); memcpy(sysExMessage + sysExLength, packet->data, lengthToCopy); sysExLength += lengthToCopy; @@ -403,7 +403,7 @@ void MidiApple::midiInClose( MIDIEndpointRef reference ) -char *getName( MIDIObjectRef &object ) +char *getName( const MIDIObjectRef &object ) { // Returns the name of a given MIDIObjectRef as char * CFStringRef name = nullptr; diff --git a/src/core/midi/MidiClient.cpp b/src/core/midi/MidiClient.cpp index 78691de6f..030384c5e 100644 --- a/src/core/midi/MidiClient.cpp +++ b/src/core/midi/MidiClient.cpp @@ -71,8 +71,7 @@ void MidiClient::removePort( MidiPort* port ) return; } - QVector::Iterator it = - std::find( m_midiPorts.begin(), m_midiPorts.end(), port ); + auto it = std::find(m_midiPorts.begin(), m_midiPorts.end(), port); if( it != m_midiPorts.end() ) { m_midiPorts.erase( it ); diff --git a/src/core/midi/MidiController.cpp b/src/core/midi/MidiController.cpp index fbd48e945..112d9d974 100644 --- a/src/core/midi/MidiController.cpp +++ b/src/core/midi/MidiController.cpp @@ -33,9 +33,9 @@ namespace lmms MidiController::MidiController( Model * _parent ) : - Controller( Controller::MidiController, _parent, tr( "MIDI Controller" ) ), + Controller( ControllerType::Midi, _parent, tr( "MIDI Controller" ) ), MidiEventProcessor(), - m_midiPort( tr( "unnamed_midi_controller" ), Engine::audioEngine()->midiClient(), this, this, MidiPort::Input ), + m_midiPort( tr( "unnamed_midi_controller" ), Engine::audioEngine()->midiClient(), this, this, MidiPort::Mode::Input ), m_lastValue( 0.0f ), m_previousValue( 0.0f ) { @@ -72,25 +72,24 @@ void MidiController::updateName() -void MidiController::processInEvent( const MidiEvent& event, const TimePos& time, f_cnt_t offset ) +void MidiController::processInEvent(const MidiEvent& event, const TimePos& time, f_cnt_t offset) { - unsigned char controllerNum; - switch( event.type() ) + switch(event.type()) { case MidiControlChange: - controllerNum = event.controllerNumber(); + { + unsigned char controllerNum = event.controllerNumber(); - if( m_midiPort.inputController() == controllerNum + 1 && - ( m_midiPort.inputChannel() == event.channel() + 1 || - m_midiPort.inputChannel() == 0 ) ) + if (m_midiPort.inputController() == controllerNum && + (m_midiPort.inputChannel() == event.channel() + 1 || m_midiPort.inputChannel() == 0)) { unsigned char val = event.controllerValue(); m_previousValue = m_lastValue; - m_lastValue = (float)( val ) / 127.0f; + m_lastValue = static_cast(val) / 127.0f; emit valueChanged(); } break; - + } 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; iaddPort( this ); - m_readableModel.setValue( m_mode == Input || m_mode == Duplex ); - m_writableModel.setValue( m_mode == Output || m_mode == Duplex ); + m_readableModel.setValue( m_mode == Mode::Input || m_mode == Mode::Duplex ); + m_writableModel.setValue( m_mode == Mode::Output || m_mode == Mode::Duplex ); connect( &m_readableModel, SIGNAL(dataChanged()), this, SLOT(updateMidiPortMode()), Qt::DirectConnection ); @@ -169,6 +170,12 @@ void MidiPort::processOutEvent( const MidiEvent& event, const TimePos& time ) outEvent.setVelocity( fixedOutputVelocity() ); } + if( fixedOutputNote() >= 0 && + ( event.type() == MidiNoteOn || event.type() == MidiNoteOff || event.type() == MidiKeyPressure ) ) + { + outEvent.setKey( fixedOutputNote() ); + } + m_midiClient->processOutEvent( outEvent, time, this ); } } @@ -238,6 +245,7 @@ void MidiPort::loadSettings( const QDomElement& thisElement ) m_outputControllerModel.loadSettings( thisElement, "outputcontroller" ); m_fixedInputVelocityModel.loadSettings( thisElement, "fixedinputvelocity" ); m_fixedOutputVelocityModel.loadSettings( thisElement, "fixedoutputvelocity" ); + m_fixedOutputNoteModel.loadSettings( thisElement, "fixedoutputnote" ); m_outputProgramModel.loadSettings( thisElement, "outputprogram" ); m_baseVelocityModel.loadSettings( thisElement, "basevelocity" ); m_readableModel.loadSettings( thisElement, "readable" ); @@ -318,10 +326,10 @@ void MidiPort::subscribeWritablePort( const QString& port, bool subscribe ) void MidiPort::updateMidiPortMode() { // this small lookup-table makes everything easier - static const Modes modeTable[2][2] = + static const Mode modeTable[2][2] = { - { Disabled, Output }, - { Input, Duplex } + { Mode::Disabled, Mode::Output }, + { Mode::Input, Mode::Duplex } } ; setMode( modeTable[m_readableModel.value()][m_writableModel.value()] ); @@ -429,4 +437,4 @@ void MidiPort::invalidateCilent() } -} // namespace lmms \ No newline at end of file +} // namespace lmms diff --git a/src/core/midi/MidiSndio.cpp b/src/core/midi/MidiSndio.cpp index e644113b2..990670196 100644 --- a/src/core/midi/MidiSndio.cpp +++ b/src/core/midi/MidiSndio.cpp @@ -91,24 +91,21 @@ void MidiSndio::sendByte( const unsigned char c ) void MidiSndio::run() { struct pollfd pfd; - nfds_t nfds; - char buf[0x100], *p; - size_t n; - int ret; + char buf[0x100]; while( m_quit == false && m_hdl ) { - nfds = mio_pollfd( m_hdl, &pfd, POLLIN ); - ret = poll( &pfd, nfds, 100 ); + nfds_t nfds = mio_pollfd(m_hdl, &pfd, POLLIN); + int ret = poll(&pfd, nfds, 100); if ( ret < 0 ) break; if ( !ret || !( mio_revents( m_hdl, &pfd ) & POLLIN ) ) continue; - n = mio_read( m_hdl, buf, sizeof(buf) ); + size_t n = mio_read(m_hdl, buf, sizeof(buf)); if ( !n ) { break; } - for (p = buf; n > 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 4ea6d4c58..bc0ecde8e 100644 --- a/src/gui/AudioAlsaSetupWidget.cpp +++ b/src/gui/AudioAlsaSetupWidget.cpp @@ -23,7 +23,7 @@ */ #include -#include +#include #include "AudioAlsaSetupWidget.h" @@ -31,7 +31,6 @@ #include "ConfigManager.h" #include "LcdSpinBox.h" -#include "gui_templates.h" namespace lmms::gui { @@ -40,6 +39,8 @@ AudioAlsaSetupWidget::AudioAlsaSetupWidget( QWidget * _parent ) : AudioDeviceSetupWidget( AudioAlsa::name(), _parent ), m_selectedDevice(-1) { + QFormLayout * form = new QFormLayout(this); + m_deviceInfos = AudioAlsa::getAvailableDevices(); QString deviceText = ConfigManager::inst()->value( "audioalsa", "device" ); @@ -62,14 +63,11 @@ AudioAlsaSetupWidget::AudioAlsaSetupWidget( QWidget * _parent ) : m_selectedDevice = m_deviceComboBox->currentIndex(); - m_deviceComboBox->setGeometry( 10, 20, 160, 20 ); connect(m_deviceComboBox, SIGNAL(currentIndexChanged(int)), SLOT(onCurrentIndexChanged(int))); - auto dev_lbl = new QLabel(tr("DEVICE"), this); - dev_lbl->setFont( pointSize<7>( dev_lbl->font() ) ); - dev_lbl->setGeometry( 10, 40, 160, 10 ); + form->addRow(tr("Device"), m_deviceComboBox); auto m = new LcdSpinBoxModel(/* this */); m->setRange( DEFAULT_CHANNELS, SURROUND_CHANNELS ); @@ -79,9 +77,8 @@ AudioAlsaSetupWidget::AudioAlsaSetupWidget( QWidget * _parent ) : m_channels = new LcdSpinBox( 1, this ); m_channels->setModel( m ); - m_channels->setLabel( tr( "CHANNELS" ) ); - m_channels->move( 180, 20 ); + form->addRow(tr("Channels"), m_channels); } diff --git a/src/gui/AudioDeviceSetupWidget.cpp b/src/gui/AudioDeviceSetupWidget.cpp index b78800cec..98d03638f 100644 --- a/src/gui/AudioDeviceSetupWidget.cpp +++ b/src/gui/AudioDeviceSetupWidget.cpp @@ -28,7 +28,7 @@ namespace lmms::gui { AudioDeviceSetupWidget::AudioDeviceSetupWidget(const QString & caption, QWidget * parent) : - TabWidget(TabWidget::tr("Settings for %1").arg(tr(caption.toUtf8())), parent) + QGroupBox(QGroupBox::tr("Settings for %1").arg(tr(caption.toUtf8())), parent) { } @@ -38,4 +38,4 @@ void AudioDeviceSetupWidget::show() QWidget::show(); } -} // namespace lmms::gui \ No newline at end of file +} // namespace lmms::gui diff --git a/src/gui/AutomatableModelView.cpp b/src/gui/AutomatableModelView.cpp index 52ee1ed7e..0e364993f 100644 --- a/src/gui/AutomatableModelView.cpp +++ b/src/gui/AutomatableModelView.cpp @@ -176,7 +176,7 @@ void AutomatableModelView::mousePressEvent( QMouseEvent* event ) new gui::StringPairDrag( "automatable_model", QString::number( modelUntyped()->id() ), QPixmap(), widget() ); event->accept(); } - else if( event->button() == Qt::MidButton ) + else if( event->button() == Qt::MiddleButton ) { modelUntyped()->reset(); } diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt index b87079637..9505b693d 100644 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -14,6 +14,7 @@ SET(LMMS_SRCS gui/EffectView.cpp gui/embed.cpp gui/FileBrowser.cpp + gui/FileBrowserSearcher.cpp gui/GuiApplication.cpp gui/LadspaControlView.cpp gui/LfoControllerDialog.cpp @@ -26,14 +27,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 +62,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/InstrumentMiscView.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 +99,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/GrooveView.cpp gui/widgets/GroupBox.cpp @@ -109,12 +116,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 @@ -126,7 +135,6 @@ SET(LMMS_SRCS 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 7e0976c94..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&) @@ -258,8 +257,8 @@ int ControlLayout::doLayout(const QRect &rect, bool testOnly) const if (first) { // for the search bar, only show it if there are at least - // two control widgets (i.e. at least 3 widgets) - if (m_itemMap.size() > 2) { wid->show(); } + // five control widgets (i.e. at least 6 widgets) + if (m_itemMap.size() > 5) { wid->show(); } else { wid->hide(); } } else { wid->show(); } diff --git a/src/gui/ControllerView.cpp b/src/gui/ControllerView.cpp index 3e3f4b9d1..d32e8d49c 100644 --- a/src/gui/ControllerView.cpp +++ b/src/gui/ControllerView.cpp @@ -149,7 +149,7 @@ void ControllerView::renameController() if( ok && !new_name.isEmpty() ) { c->setName( new_name ); - if( getController()->type() == Controller::LfoController ) + if( getController()->type() == Controller::ControllerType::Lfo ) { m_controllerDlg->setWindowTitle( tr( "LFO" ) + " (" + new_name + ")" ); } diff --git a/src/gui/Controls.cpp b/src/gui/Controls.cpp index 8c007ee99..209b0fce1 100644 --- a/src/gui/Controls.cpp +++ b/src/gui/Controls.cpp @@ -95,7 +95,7 @@ AutomatableModelView* CheckControl::modelView() { return m_checkBox; } CheckControl::CheckControl(QWidget *parent) : m_widget(new QWidget(parent)), - m_checkBox(new LedCheckBox(nullptr, QString(), LedCheckBox::Green)), + m_checkBox(new LedCheckBox(nullptr, QString(), LedCheckBox::LedColor::Green)), m_label(new QLabel(m_widget)) { auto vbox = new QVBoxLayout(m_widget); diff --git a/src/gui/EffectRackView.cpp b/src/gui/EffectRackView.cpp index 75cac67dc..aa790d74d 100644 --- a/src/gui/EffectRackView.cpp +++ b/src/gui/EffectRackView.cpp @@ -42,7 +42,7 @@ EffectRackView::EffectRackView( EffectChain* model, QWidget* parent ) : ModelView( nullptr, this ) { auto mainLayout = new QVBoxLayout(this); - mainLayout->setMargin( 5 ); + mainLayout->setContentsMargins(5, 5, 5, 5); m_effectsGroupBox = new GroupBox( tr( "EFFECTS CHAIN" ) ); mainLayout->addWidget( m_effectsGroupBox ); diff --git a/src/gui/EffectView.cpp b/src/gui/EffectView.cpp index fed1ce4e9..76010232a 100644 --- a/src/gui/EffectView.cpp +++ b/src/gui/EffectView.cpp @@ -23,9 +23,11 @@ * */ +#include +#include +#include #include #include -#include #include "EffectView.h" #include "DummyEffect.h" @@ -47,34 +49,35 @@ EffectView::EffectView( Effect * _model, QWidget * _parent ) : PluginView( _model, _parent ), m_bg( embed::getIconPixmap( "effect_plugin" ) ), m_subWindow( nullptr ), - m_controlView( nullptr ) + m_controlView(nullptr), + m_dragging(false) { - setFixedSize( EffectView::DEFAULT_WIDTH, 60 ); + setFixedSize(EffectView::DEFAULT_WIDTH, EffectView::DEFAULT_HEIGHT); // Disable effects that are of type "DummyEffect" bool isEnabled = !dynamic_cast( effect() ); - m_bypass = new LedCheckBox( this, "", isEnabled ? LedCheckBox::Green : LedCheckBox::Red ); + m_bypass = new LedCheckBox( this, "", isEnabled ? LedCheckBox::LedColor::Green : LedCheckBox::LedColor::Red ); m_bypass->move( 3, 3 ); m_bypass->setEnabled( isEnabled ); m_bypass->setToolTip(tr("On/Off")); - m_wetDry = new Knob( knobBright_26, this ); + m_wetDry = new Knob( KnobType::Bright26, this ); m_wetDry->setLabel( tr( "W/D" ) ); m_wetDry->move( 40 - m_wetDry->width() / 2, 5 ); m_wetDry->setEnabled( isEnabled ); m_wetDry->setHintText( tr( "Wet Level:" ), "" ); - m_autoQuit = new TempoSyncKnob( knobBright_26, this ); + m_autoQuit = new TempoSyncKnob( KnobType::Bright26, this ); m_autoQuit->setLabel( tr( "DECAY" ) ); m_autoQuit->move( 78 - m_autoQuit->width() / 2, 5 ); m_autoQuit->setEnabled( isEnabled && !effect()->m_autoQuitDisabled ); m_autoQuit->setHintText( tr( "Time:" ), "ms" ); - m_gate = new Knob( knobBright_26, this ); + m_gate = new Knob( KnobType::Bright26, this ); m_gate->setLabel( tr( "GATE" ) ); m_gate->move( 116 - m_gate->width() / 2, 5 ); m_gate->setEnabled( isEnabled && !effect()->m_autoQuitDisabled ); @@ -87,7 +90,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())); @@ -116,7 +119,10 @@ EffectView::EffectView( Effect * _model, QWidget * _parent ) : m_subWindow->hide(); } } - + + m_opacityEffect = new QGraphicsOpacityEffect(this); + m_opacityEffect->setOpacity(1); + setGraphicsEffect(m_opacityEffect); //move above vst effect view creation //setModel( _model ); @@ -208,13 +214,50 @@ void EffectView::contextMenuEvent( QContextMenuEvent * ) +void EffectView::mousePressEvent(QMouseEvent* event) +{ + if (event->button() == Qt::LeftButton) + { + m_dragging = true; + m_opacityEffect->setOpacity(0.3); + QCursor c(Qt::SizeVerCursor); + QApplication::setOverrideCursor(c); + update(); + } +} + +void EffectView::mouseReleaseEvent(QMouseEvent* event) +{ + if (event->button() == Qt::LeftButton) + { + m_dragging = false; + m_opacityEffect->setOpacity(1); + QApplication::restoreOverrideCursor(); + update(); + } +} + +void EffectView::mouseMoveEvent(QMouseEvent* event) +{ + if (!m_dragging) { return; } + if (event->pos().y() < 0) + { + moveUp(); + } + else if (event->pos().y() > EffectView::DEFAULT_HEIGHT) + { + moveDown(); + } +} + + 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 4ef41217d..89201cb04 100644 --- a/src/gui/FileBrowser.cpp +++ b/src/gui/FileBrowser.cpp @@ -23,9 +23,11 @@ * */ +#include "FileBrowser.h" #include #include +#include #include #include #include @@ -37,7 +39,10 @@ #include #include +#include + #include "FileBrowser.h" +#include "FileBrowserSearcher.h" #include "AudioEngine.h" #include "ConfigManager.h" #include "DataFile.h" @@ -52,7 +57,9 @@ #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" @@ -76,7 +83,7 @@ void FileBrowser::addContentCheckBox() auto filterWidget = new QWidget(contentParent()); filterWidget->setFixedHeight(15); auto filterWidgetLayout = new QHBoxLayout(filterWidget); - filterWidgetLayout->setMargin(0); + filterWidgetLayout->setContentsMargins(0, 0, 0, 0); filterWidgetLayout->setSpacing(0); auto configCheckBox = [this, &filterWidgetLayout](QCheckBox* box) @@ -97,14 +104,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) { @@ -119,14 +125,15 @@ FileBrowser::FileBrowser(const QString & directories, const QString & filter, searchWidget->setFixedHeight( 24 ); auto searchWidgetLayout = new QHBoxLayout(searchWidget); - searchWidgetLayout->setMargin( 0 ); + 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( filterItems( 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" ) ); @@ -141,75 +148,171 @@ 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); + + auto searchTimer = new QTimer(this); + connect(searchTimer, &QTimer::timeout, this, &FileBrowser::buildSearchTree); + searchTimer->start(FileBrowserSearcher::MillisecondsPerMatch); + + 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); + m_previousFilterValue = ""; + reloadTree(); show(); } -bool FileBrowser::filterItems( const QString & filter, QTreeWidgetItem * item ) +void FileBrowser::saveDirectoriesStates() +{ + m_savedExpandedDirs = m_fileBrowserTreeWidget->expandedDirs(); +} + +void FileBrowser::restoreDirectoriesStates() { - // call with item=NULL to filter the entire tree - bool anyMatched = false; + expandItems(m_savedExpandedDirs); +} - int numChildren = item ? item->childCount() : m_fileBrowserTreeWidget->topLevelItemCount(); - for( int i = 0; i < numChildren; ++i ) +void FileBrowser::buildSearchTree() +{ + if (!m_currentSearch) { return; } + + const auto match = m_currentSearch->match(); + using State = FileBrowserSearcher::SearchFuture::State; + if ((m_currentSearch->state() == State::Completed && match.isEmpty()) + || m_currentSearch->state() == State::Cancelled) { - QTreeWidgetItem * it = item ? item->child( i ) : m_fileBrowserTreeWidget->topLevelItem(i); + m_currentSearch = nullptr; + m_searchIndicator->setMaximum(100); + return; + } + else if (match.isEmpty()) { return; } - // is directory? - if( it->childCount() ) + auto basePath = QString{}; + for (const auto& path : m_directories.split('*')) + { + 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) { - // matches filter? - if( it->text( 0 ). - contains( filter, Qt::CaseInsensitive ) ) + auto item = currentItem ? currentItem->child(i) : m_searchTreeWidget->topLevelItem(i); + if (item->text(0) == pathPart) { - // yes, then show everything below - it->setHidden( false ); - filterItems( QString(), it ); - anyMatched = true; + childItem = item; + break; + } + + } + + 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 { - // only show if item below matches filter - bool didMatch = filterItems( filter, it ); - it->setHidden( !didMatch ); - anyMatched = anyMatched || didMatch; + auto item = new FileItem(pathPart, currentDir.path()); + currentItem ? currentItem->addChild(item) : m_searchTreeWidget->addTopLevelItem(item); + childItem = item; } } - // a standard item (i.e. no file or directory item?) - else if( it->type() == QTreeWidgetItem::Type ) - { - // hide if there's any filter - it->setHidden( !filter.isEmpty() ); - } - else - { - // file matches filter? - bool didMatch = it->text( 0 ). - contains( filter, Qt::CaseInsensitive ); - it->setHidden( !didMatch ); - anyMatched = anyMatched || didMatch; - } + + currentItem = childItem; + if (!currentDir.cd(pathPart)) { break; } + } +} + + +void FileBrowser::onSearch(const QString& filter) +{ + if (filter.isEmpty()) + { + toggleSearch(false); + FileBrowserSearcher::instance()->cancel(); + return; } - return anyMatched; + 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(); + toggleSearch(true); + + auto browserExtensions = m_filter; + const auto searchExtensions = browserExtensions.remove("*.").split(' '); + m_currentSearch = FileBrowserSearcher::instance()->search(filter, directories, searchExtensions); +} + +void FileBrowser::toggleSearch(bool on) +{ + if (on) + { + m_searchTreeWidget->show(); + m_fileBrowserTreeWidget->hide(); + m_searchIndicator->setMaximum(0); + return; + } + + m_searchTreeWidget->hide(); + m_fileBrowserTreeWidget->show(); + m_searchIndicator->setMaximum(100); } void FileBrowser::reloadTree() { - QList expandedDirs = m_fileBrowserTreeWidget->expandedDirs(); - const QString text = m_filterEdit->text(); - m_filterEdit->clear(); + if (m_filterEdit->text().isEmpty()) + { + saveDirectoriesStates(); + } + m_fileBrowserTreeWidget->clear(); + QStringList paths = m_directories.split('*'); + if (m_showUserContent && !m_showUserContent->isChecked()) { paths.removeAll(m_userDir); } + if (m_showFactoryContent && !m_showFactoryContent->isChecked()) { paths.removeAll(m_factoryDir); @@ -222,34 +325,38 @@ void FileBrowser::reloadTree() addItems(path); } } - expandItems(nullptr, expandedDirs); - m_filterEdit->setText( text ); - filterItems( text ); + + if (m_filterEdit->text().isEmpty()) + { + restoreDirectoriesStates(); + } + else + { + 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) { - QTreeWidgetItem * it = item ? item->child( i ) : m_fileBrowserTreeWidget->topLevelItem(i); - if ( m_recurse ) - { - it->setExpanded( true ); - } + auto it = item ? item->child(i) : m_fileBrowserTreeWidget->topLevelItem(i); auto d = dynamic_cast(it); if (d) { - d->update(); - bool expand = expandedDirs.contains( d->fullName() ); - d->setExpanded( expand ); - } - if (m_recurse && it->childCount()) - { - expandItems(it, expandedDirs); + d->setExpanded(expandedDirs.contains(d->fullName())); + if (it->childCount() > 0) + { + expandItems(expandedDirs, it); + } } + + it->setHidden(false); } } @@ -269,6 +376,8 @@ void FileBrowser::giveFocusToFilter() void FileBrowser::addItems(const QString & path ) { + if (FileBrowser::directoryBlacklist().contains(path)) { return; } + if( m_dirsAsItems ) { m_fileBrowserTreeWidget->addTopLevelItem( new Directory( path, QString(), m_filter ) ); @@ -276,68 +385,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::directoryBlacklist().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); } } } @@ -420,8 +524,6 @@ QList FileBrowserTreeWidget::expandedDirs( QTreeWidgetItem * item ) con } - - void FileBrowserTreeWidget::keyPressEvent(QKeyEvent * ke ) { // Shorter names for some commonly used properties of the event @@ -444,7 +546,7 @@ void FileBrowserTreeWidget::keyPressEvent(QKeyEvent * ke ) if (file == nullptr) { return; } // When moving to a new sound, preview it. Skip presets, they can play forever - if (vertical && file->type() == FileItem::SampleFile) + if (vertical && file->type() == FileItem::FileType::Sample) { previewFileItem(file); } @@ -539,7 +641,7 @@ void FileBrowserTreeWidget::contextMenuEvent(QContextMenuEvent * e ) QList FileBrowserTreeWidget::getContextActions(FileItem* file, bool songEditor) { QList result = QList(); - const bool fileIsSample = file->type() == FileItem::SampleFile; + const bool fileIsSample = file->type() == FileItem::FileType::Sample; QString instrumentAction = fileIsSample ? tr("Send to new AudioFileProcessor instance") : @@ -607,7 +709,7 @@ void FileBrowserTreeWidget::previewFileItem(FileItem* file) // In special case of sample-files we do not care about // handling() rather than directly creating a SamplePlayHandle - if (file->type() == FileItem::SampleFile) + if (file->type() == FileItem::FileType::Sample) { TextFloat * tf = TextFloat::displayMessage( tr("Loading sample"), @@ -615,9 +717,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 ( @@ -625,15 +730,15 @@ void FileBrowserTreeWidget::previewFileItem(FileItem* file) ext == "gig" || ext == "pat") && !getPluginFactory()->pluginSupportingExtension(ext).isNull()) { - const bool isPlugin = file->handling() == FileItem::LoadByPlugin; + const bool isPlugin = file->handling() == FileItem::FileHandling::LoadByPlugin; newPPH = new PresetPreviewPlayHandle(fileName, isPlugin); } - else if (file->type() != FileItem::VstPluginFile && file->isTrack()) + else if (file->type() != FileItem::FileType::VstPlugin && file->isTrack()) { DataFile dataFile(fileName); if (dataFile.validate(ext)) { - const bool isPlugin = file->handling() == FileItem::LoadByPlugin; + const bool isPlugin = file->handling() == FileItem::FileHandling::LoadByPlugin; newPPH = new PresetPreviewPlayHandle(fileName, isPlugin, &dataFile); } else @@ -685,34 +790,34 @@ void FileBrowserTreeWidget::mouseMoveEvent( QMouseEvent * me ) { switch( f->type() ) { - case FileItem::PresetFile: - new StringPairDrag( f->handling() == FileItem::LoadAsPreset ? + case FileItem::FileType::Preset: + new StringPairDrag( f->handling() == FileItem::FileHandling::LoadAsPreset ? "presetfile" : "pluginpresetfile", f->fullName(), embed::getIconPixmap( "preset_file" ), this ); break; - case FileItem::SampleFile: + case FileItem::FileType::Sample: new StringPairDrag( "samplefile", f->fullName(), embed::getIconPixmap( "sample_file" ), this ); break; - case FileItem::SoundFontFile: + case FileItem::FileType::SoundFont: new StringPairDrag( "soundfontfile", f->fullName(), embed::getIconPixmap( "soundfont_file" ), this ); break; - case FileItem::PatchFile: + case FileItem::FileType::Patch: new StringPairDrag( "patchfile", f->fullName(), embed::getIconPixmap( "sample_file" ), this ); break; - case FileItem::VstPluginFile: + case FileItem::FileType::VstPlugin: new StringPairDrag( "vstpluginfile", f->fullName(), embed::getIconPixmap( "vst_plugin_file" ), this ); break; - case FileItem::MidiFile: + case FileItem::FileType::Midi: new StringPairDrag( "importedproject", f->fullName(), embed::getIconPixmap( "midi_file" ), this ); break; - case FileItem::ProjectFile: + case FileItem::FileType::Project: new StringPairDrag( "projectfile", f->fullName(), embed::getIconPixmap( "project_file" ), this ); break; @@ -736,7 +841,7 @@ void FileBrowserTreeWidget::mouseReleaseEvent(QMouseEvent * me ) if (m_previewPlayHandle == nullptr) { return; } // Only sample previews may continue after mouse up. Is this a sample preview? - bool isSample = m_previewPlayHandle->type() == PlayHandle::TypeSamplePlayHandle; + bool isSample = m_previewPlayHandle->type() == PlayHandle::Type::SamplePlayHandle; // Even sample previews should only continue if the user wants them to. Do they? bool shouldContinue = ConfigManager::inst()->value("ui", "letpreviewsfinish").toInt(); // If both are true the preview may continue, otherwise we stop it @@ -751,14 +856,14 @@ void FileBrowserTreeWidget::handleFile(FileItem * f, InstrumentTrack * it) Engine::audioEngine()->requestChangeInModel(); switch( f->handling() ) { - case FileItem::LoadAsProject: + case FileItem::FileHandling::LoadAsProject: if( getGUI()->mainWindow()->mayChangeProject(true) ) { Engine::getSong()->loadProject( f->fullName() ); } break; - case FileItem::LoadByPlugin: + case FileItem::FileHandling::LoadByPlugin: { const QString e = f->extension(); Instrument * i = it->instrument(); @@ -773,17 +878,17 @@ void FileBrowserTreeWidget::handleFile(FileItem * f, InstrumentTrack * it) break; } - case FileItem::LoadAsPreset: { + case FileItem::FileHandling::LoadAsPreset: { DataFile dataFile(f->fullName()); it->replaceInstrument(dataFile); break; } - case FileItem::ImportAsProject: + case FileItem::FileHandling::ImportAsProject: ImportFilter::import( f->fullName(), Engine::getSong() ); break; - case FileItem::NotSupported: + case FileItem::FileHandling::NotSupported: default: break; @@ -803,14 +908,14 @@ void FileBrowserTreeWidget::activateListItem(QTreeWidgetItem * item, return; } - if( f->handling() == FileItem::LoadAsProject || - f->handling() == FileItem::ImportAsProject ) + if( f->handling() == FileItem::FileHandling::LoadAsProject || + f->handling() == FileItem::FileHandling::ImportAsProject ) { handleFile( f, nullptr ); } - else if( f->handling() != FileItem::NotSupported ) + else if( f->handling() != FileItem::FileHandling::NotSupported ) { - auto it = dynamic_cast(Track::create(Track::InstrumentTrack, Engine::patternStore())); + auto it = dynamic_cast(Track::create(Track::Type::Instrument, Engine::patternStore())); handleFile( f, it ); } } @@ -822,7 +927,7 @@ void FileBrowserTreeWidget::openInNewInstrumentTrack(TrackContainer* tc, FileIte { if(item->isTrack()) { - auto it = dynamic_cast(Track::create(Track::InstrumentTrack, tc)); + auto it = dynamic_cast(Track::create(Track::Type::Instrument, tc)); handleFile(item, it); } } @@ -844,10 +949,10 @@ void FileBrowserTreeWidget::openInNewInstrumentTrack(FileItem* item, bool songEd bool FileBrowserTreeWidget::openInNewSampleTrack(FileItem* item) { // Can't add non-samples to a sample track - if (item->type() != FileItem::SampleFile) { return false; } + if (item->type() != FileItem::FileType::Sample) { return false; } // Create a new sample track for this sample - auto sampleTrack = static_cast(Track::create(Track::SampleTrack, Engine::getSong())); + auto sampleTrack = static_cast(Track::create(Track::Type::Sample, Engine::getSong())); // Add the sample clip to the track Engine::audioEngine()->requestChangeInModel(); @@ -908,74 +1013,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 @@ -1006,102 +1064,44 @@ void Directory::update() -bool Directory::addItems(const QString & path ) +bool Directory::addItems(const QString& path) { - QDir thisDir( path ); - if( !thisDir.isReadable() ) + if (FileBrowser::directoryBlacklist().contains(path)) { return false; } + + QDir thisDir(path); + if (!thisDir.isReadable()) { return false; } + + treeWidget()->setUpdatesEnabled(false); + + QFileInfoList entries + = thisDir.entryInfoList(m_filter.split(' '), FileBrowser::dirFilters(), FileBrowser::sortFlags()); + for (const auto& entry : entries) { - return false; - } + if (FileBrowser::directoryBlacklist().contains(entry.absoluteFilePath())) { continue; } - treeWidget()->setUpdatesEnabled( false ); - - bool added_something = false; - - // try to add all directories from file system alphabetically into the tree - QStringList files = thisDir.entryList( QDir::Dirs, QDir::Name ); - for( QStringList::const_iterator it = files.constBegin(); - it != files.constEnd(); ++it ) - { - QString cur_file = *it; - if( cur_file[0] != '.' ) + QString fileName = entry.fileName(); + if (entry.isDir()) { - bool orphan = true; - for( int i = 0; i < childCount(); ++i ) - { - auto d = dynamic_cast(child(i)); - if( d == nullptr || cur_file < d->text( 0 ) ) - { - // insert before item, we're done - insertChild( i, new Directory( cur_file, - path, m_filter ) ); - orphan = false; - m_dirCount++; - break; - } - else if( cur_file == d->text( 0 ) ) - { - // imagine we have top-level subdirs named "TripleOscillator" in - // two directories from FileBrowser::m_directories - // and imagine both have a sub folder named "xyz" - // then only add one tree widget for both - // so we don't add a new Directory - we just - // add the path to the current directory - d->addDirectory( path ); - orphan = false; - break; - } - } - if( orphan ) - { - // it has not yet been added yet, so it's (lexically) - // larger than all other dirs => append it at the bottom - addChild( new Directory( cur_file, path, - m_filter ) ); - m_dirCount++; - } - - added_something = true; + auto dir = new Directory(fileName, path, m_filter); + addChild(dir); + m_dirCount++; + } + else if (entry.isFile()) + { + auto fileItem = new FileItem(fileName, path); + addChild(fileItem); } } - - // sorts the path alphabetically instead of just appending to the bottom (see "orphans") - if (added_something) - sortChildren(0, Qt::AscendingOrder); - - QList items; - files = thisDir.entryList( QDir::Files, QDir::Name ); - files.sort(Qt::CaseInsensitive); - for( QStringList::const_iterator it = files.constBegin(); - it != files.constEnd(); ++it ) - { - QString cur_file = *it; - if( cur_file[0] != '.' && - thisDir.match( m_filter, cur_file.toLower() ) ) - { - items << new FileItem( cur_file, path ); - added_something = true; - } - } - addChildren( items ); - - treeWidget()->setUpdatesEnabled( true ); - - return added_something; + + treeWidget()->setUpdatesEnabled(true); + + // return true if we added any child items + return childCount() > 0; } -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 ), @@ -1127,72 +1127,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 ProjectFile: - setIcon( 0, *s_projectFilePixmap ); + case FileType::Project: + setIcon(0, s_projectFilePixmap); break; - case PresetFile: - setIcon( 0, *s_presetFilePixmap ); + case FileType::Preset: + setIcon(0, s_presetFilePixmap); break; - case SoundFontFile: - setIcon( 0, *s_soundfontFilePixmap ); + case FileType::SoundFont: + setIcon(0, s_soundfontFilePixmap); break; - case VstPluginFile: - setIcon( 0, *s_vstPluginFilePixmap ); + case FileType::VstPlugin: + setIcon(0, s_vstPluginFilePixmap); break; - case SampleFile: - case PatchFile: // TODO - setIcon( 0, *s_sampleFilePixmap ); + case FileType::Sample: + case FileType::Patch: // TODO + setIcon(0, s_sampleFilePixmap); break; - case MidiFile: - setIcon( 0, *s_midiFilePixmap ); + case FileType::Midi: + setIcon(0, s_midiFilePixmap); break; - case UnknownFile: + case FileType::Unknown: default: - setIcon( 0, *s_unknownFilePixmap ); + setIcon(0, s_unknownFilePixmap); break; } } @@ -1202,36 +1168,36 @@ void FileItem::initPixmaps() void FileItem::determineFileType() { - m_handling = NotSupported; + m_handling = FileHandling::NotSupported; const QString ext = extension(); if( ext == "mmp" || ext == "mpt" || ext == "mmpz" ) { - m_type = ProjectFile; - m_handling = LoadAsProject; + m_type = FileType::Project; + m_handling = FileHandling::LoadAsProject; } else if( ext == "xpf" || ext == "xml" ) { - m_type = PresetFile; - m_handling = LoadAsPreset; + m_type = FileType::Preset; + m_handling = FileHandling::LoadAsPreset; } else if( ext == "xiz" && ! getPluginFactory()->pluginSupportingExtension(ext).isNull() ) { - m_type = PresetFile; - m_handling = LoadByPlugin; + m_type = FileType::Preset; + m_handling = FileHandling::LoadByPlugin; } else if( ext == "sf2" || ext == "sf3" ) { - m_type = SoundFontFile; + m_type = FileType::SoundFont; } else if( ext == "pat" ) { - m_type = PatchFile; + m_type = FileType::Patch; } else if( ext == "mid" || ext == "midi" || ext == "rmi" ) { - m_type = MidiFile; - m_handling = ImportAsProject; + m_type = FileType::Midi; + m_handling = FileHandling::ImportAsProject; } else if( ext == "dll" #ifdef LMMS_BUILD_LINUX @@ -1239,28 +1205,28 @@ void FileItem::determineFileType() #endif ) { - m_type = VstPluginFile; - m_handling = LoadByPlugin; + m_type = FileType::VstPlugin; + m_handling = FileHandling::LoadByPlugin; } else if ( ext == "lv2" ) { - m_type = PresetFile; - m_handling = LoadByPlugin; + m_type = FileType::Preset; + m_handling = FileHandling::LoadByPlugin; } else { - m_type = UnknownFile; + m_type = FileType::Unknown; } - if( m_handling == NotSupported && + if( m_handling == FileHandling::NotSupported && !ext.isEmpty() && ! getPluginFactory()->pluginSupportingExtension(ext).isNull() ) { - m_handling = LoadByPlugin; + m_handling = FileHandling::LoadByPlugin; // classify as sample if not classified by anything yet but can // be handled by a certain plugin - if( m_type == UnknownFile ) + if( m_type == FileType::Unknown ) { - m_type = SampleFile; + m_type = FileType::Sample; } } } @@ -1281,5 +1247,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 -} // namespace lmms::gui \ No newline at end of file + 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/FileBrowserSearcher.cpp b/src/gui/FileBrowserSearcher.cpp new file mode 100644 index 000000000..80c238058 --- /dev/null +++ b/src/gui/FileBrowserSearcher.cpp @@ -0,0 +1,135 @@ +/* + * FileBrowserSearcher.cpp - Batch processor for searching the filesystem + * + * 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 "FileBrowserSearcher.h" + +#include +#include + +#include "FileBrowser.h" + +namespace lmms::gui { + +FileBrowserSearcher::~FileBrowserSearcher() +{ + m_cancelRunningSearch = true; + + { + const auto lock = std::lock_guard{m_workerMutex}; + m_workerStopped = true; + } + + m_workerCond.notify_one(); + m_worker.join(); +} + +auto FileBrowserSearcher::search(const QString& filter, const QStringList& paths, const QStringList& extensions) + -> std::shared_ptr +{ + m_cancelRunningSearch = true; + auto future = std::make_shared(filter, paths, extensions); + + { + const auto lock = std::lock_guard{m_workerMutex}; + m_searchQueue.push(future); + m_cancelRunningSearch = false; + } + + m_workerCond.notify_one(); + return future; +} + +auto FileBrowserSearcher::run() -> void +{ + while (true) + { + auto lock = std::unique_lock{m_workerMutex}; + m_workerCond.wait(lock, [this] { return m_workerStopped || !m_searchQueue.empty(); }); + + if (m_workerStopped) { return; } + + const auto future = m_searchQueue.front(); + future->m_state = SearchFuture::State::Running; + m_searchQueue.pop(); + + auto cancelled = false; + for (const auto& path : future->m_paths) + { + if (FileBrowser::directoryBlacklist().contains(path)) { continue; } + + if (!process(future.get(), path)) + { + future->m_state = SearchFuture::State::Cancelled; + cancelled = true; + break; + } + } + + if (!cancelled) { future->m_state = SearchFuture::State::Completed; } + } +} + +auto FileBrowserSearcher::process(SearchFuture* searchFuture, const QString& path) -> bool +{ + auto stack = QFileInfoList{}; + + auto dir = QDir{path}; + stack.append(dir.entryInfoList(FileBrowser::dirFilters(), FileBrowser::sortFlags())); + + while (!stack.empty()) + { + if (m_cancelRunningSearch) + { + m_cancelRunningSearch = false; + return false; + } + + const auto info = stack.takeFirst(); + const auto path = info.absoluteFilePath(); + if (FileBrowser::directoryBlacklist().contains(path)) { continue; } + + const auto name = info.fileName(); + const auto validFile = info.isFile() && searchFuture->m_extensions.contains(info.suffix(), Qt::CaseInsensitive); + const auto passesFilter = name.contains(searchFuture->m_filter, Qt::CaseInsensitive); + + // Only when a directory doesn't pass the filter should we search further + if (info.isDir() && !passesFilter) + { + dir.setPath(path); + auto entries = dir.entryInfoList(FileBrowser::dirFilters(), FileBrowser::sortFlags()); + + // Reverse to maintain the sorting within this directory when popped + std::reverse(entries.begin(), entries.end()); + + for (const auto& entry : entries) + { + stack.push_front(entry); + } + } + else if ((validFile || info.isDir()) && passesFilter) { searchFuture->addMatch(path); } + } + return true; +} + +} // 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/LadspaControlView.cpp b/src/gui/LadspaControlView.cpp index 38ad4a146..dbc3b8059 100644 --- a/src/gui/LadspaControlView.cpp +++ b/src/gui/LadspaControlView.cpp @@ -43,7 +43,7 @@ LadspaControlView::LadspaControlView( QWidget * _parent, m_ctl( _ctl ) { auto layout = new QHBoxLayout(this); - layout->setMargin( 0 ); + layout->setContentsMargins(0, 0, 0, 0); layout->setSpacing( 0 ); LedCheckBox * link = nullptr; @@ -60,9 +60,9 @@ LadspaControlView::LadspaControlView( QWidget * _parent, switch( m_ctl->port()->data_type ) { - case TOGGLED: + case BufferDataType::Toggled: { - auto toggle = new LedCheckBox(m_ctl->port()->name, this, QString(), LedCheckBox::Green); + auto toggle = new LedCheckBox(m_ctl->port()->name, this, QString(), LedCheckBox::LedColor::Green); toggle->setModel( m_ctl->toggledModel() ); layout->addWidget( toggle ); if( link != nullptr ) @@ -78,14 +78,14 @@ LadspaControlView::LadspaControlView( QWidget * _parent, break; } - case INTEGER: - case ENUM: - case FLOATING: - knb = new Knob( knobBright_26, this, m_ctl->port()->name ); + case BufferDataType::Integer: + case BufferDataType::Enum: + case BufferDataType::Floating: + knb = new Knob( KnobType::Bright26, this, m_ctl->port()->name ); break; - case TIME: - knb = new TempoSyncKnob( knobBright_26, this, m_ctl->port()->name ); + case BufferDataType::Time: + knb = new TempoSyncKnob( KnobType::Bright26, this, m_ctl->port()->name ); break; default: @@ -94,7 +94,7 @@ LadspaControlView::LadspaControlView( QWidget * _parent, if( knb != nullptr ) { - if( m_ctl->port()->data_type != TIME ) + if( m_ctl->port()->data_type != BufferDataType::Time ) { knb->setModel( m_ctl->knobModel() ); } diff --git a/src/gui/LfoControllerDialog.cpp b/src/gui/LfoControllerDialog.cpp index 64602cd74..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 { @@ -61,22 +62,22 @@ LfoControllerDialog::LfoControllerDialog( Controller * _model, QWidget * _parent setWindowIcon( embed::getIconPixmap( "controller" ) ); setFixedSize( 240, 58 ); - m_baseKnob = new Knob( knobBright_26, this ); + m_baseKnob = new Knob( KnobType::Bright26, this ); m_baseKnob->setLabel( tr( "BASE" ) ); m_baseKnob->move( CD_LFO_BASE_CD_KNOB_X, CD_LFO_CD_KNOB_Y ); m_baseKnob->setHintText( tr( "Base:" ), "" ); - m_speedKnob = new TempoSyncKnob( knobBright_26, this ); + m_speedKnob = new TempoSyncKnob( KnobType::Bright26, this ); m_speedKnob->setLabel( tr( "FREQ" ) ); m_speedKnob->move( CD_LFO_SPEED_CD_KNOB_X, CD_LFO_CD_KNOB_Y ); m_speedKnob->setHintText( tr( "LFO frequency:" ), "" ); - m_amountKnob = new Knob( knobBright_26, this ); + m_amountKnob = new Knob( KnobType::Bright26, this ); m_amountKnob->setLabel( tr( "AMNT" ) ); m_amountKnob->move( CD_LFO_AMOUNT_CD_KNOB_X, CD_LFO_CD_KNOB_Y ); m_amountKnob->setHintText( tr( "Modulation amount:" ), "" ); - m_phaseKnob = new Knob( knobBright_26, this ); + m_phaseKnob = new Knob( KnobType::Bright26, this ); m_phaseKnob->setLabel( tr( "PHS" ) ); m_phaseKnob->move( CD_LFO_PHASE_CD_KNOB_X, CD_LFO_CD_KNOB_Y ); m_phaseKnob->setHintText( tr( "Phase offset:" ) , "" + tr( " degrees" ) ); @@ -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/LmmsPalette.cpp b/src/gui/LmmsPalette.cpp index 48d6de0df..0d135f52c 100644 --- a/src/gui/LmmsPalette.cpp +++ b/src/gui/LmmsPalette.cpp @@ -75,7 +75,7 @@ QPalette LmmsPalette::palette() const { QPalette pal = QApplication::style()->standardPalette(); - pal.setColor( QPalette::Background, background() ); + pal.setColor( QPalette::Window, background() ); pal.setColor( QPalette::WindowText, windowText() ); pal.setColor( QPalette::Base, base() ); pal.setColor( QPalette::ButtonText, buttonText() ); diff --git a/src/gui/LmmsStyle.cpp b/src/gui/LmmsStyle.cpp index 060a97811..50a2a9de4 100644 --- a/src/gui/LmmsStyle.cpp +++ b/src/gui/LmmsStyle.cpp @@ -189,7 +189,7 @@ void LmmsStyle::drawComplexControl( ComplexControl control, /* else if( control == CC_ScrollBar ) { painter->fillRect( option->rect, QApplication::palette().color( QPalette::Active, - QPalette::Background ) ); + QPalette::Window ) ); }*/ QProxyStyle::drawComplexControl( control, option, painter, widget ); diff --git a/src/gui/Lv2ViewBase.cpp b/src/gui/Lv2ViewBase.cpp index 4347c37cd..4fcf6b77b 100644 --- a/src/gui/Lv2ViewBase.cpp +++ b/src/gui/Lv2ViewBase.cpp @@ -1,7 +1,7 @@ /* * Lv2ViewBase.cpp - base class for Lv2 plugin views * - * Copyright (c) 2018-2020 Johannes Lorenz + * Copyright (c) 2018-2023 Johannes Lorenz * * This file is part of LMMS - https://lmms.io * @@ -39,6 +39,7 @@ #include "GuiApplication.h" #include "embed.h" #include "gui_templates.h" +#include "lmms_math.h" #include "Lv2ControlBase.h" #include "Lv2Manager.h" #include "Lv2Proc.h" @@ -51,13 +52,13 @@ namespace lmms::gui { -Lv2ViewProc::Lv2ViewProc(QWidget* parent, Lv2Proc* ctrlBase, int colNum) : - LinkedModelGroupView (parent, ctrlBase, colNum) +Lv2ViewProc::Lv2ViewProc(QWidget* parent, Lv2Proc* proc, int colNum) : + LinkedModelGroupView (parent, proc, colNum) { - class SetupWidget : public Lv2Ports::ConstVisitor + class SetupTheWidget : public Lv2Ports::ConstVisitor { public: - QWidget* m_par; // input + QWidget* m_parent; // input const LilvNode* m_commentUri; // input Control* m_control = nullptr; // output void visit(const Lv2Ports::Control& port) override @@ -69,20 +70,22 @@ Lv2ViewProc::Lv2ViewProc(QWidget* parent, Lv2Proc* ctrlBase, int colNum) : switch (port.m_vis) { case PortVis::Generic: - m_control = new KnobControl(m_par); + m_control = new KnobControl(m_parent); break; case PortVis::Integer: { sample_rate_t sr = Engine::audioEngine()->processingSampleRate(); - m_control = new LcdControl((port.max(sr) <= 9.0f) ? 1 : 2, - m_par); + auto pMin = port.min(sr); + auto pMax = port.max(sr); + int numDigits = std::max(numDigitsAsInt(pMin), numDigitsAsInt(pMax)); + m_control = new LcdControl(numDigits, m_parent); break; } case PortVis::Enumeration: - m_control = new ComboControl(m_par); + m_control = new ComboControl(m_parent); break; case PortVis::Toggled: - m_control = new CheckControl(m_par); + m_control = new CheckControl(m_parent); break; } m_control->setText(port.name()); @@ -100,14 +103,14 @@ Lv2ViewProc::Lv2ViewProc(QWidget* parent, Lv2Proc* ctrlBase, int colNum) : }; AutoLilvNode commentUri = uri(LILV_NS_RDFS "comment"); - ctrlBase->foreach_port( + proc->foreach_port( [this, &commentUri](const Lv2Ports::PortBase* port) { if(!lilv_port_has_property(port->m_plugin, port->m_port, uri(LV2_PORT_PROPS__notOnGUI).get())) { - SetupWidget setup; - setup.m_par = this; + SetupTheWidget setup; + setup.m_parent = this; setup.m_commentUri = commentUri.get(); port->accept(setup); @@ -134,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); @@ -153,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); @@ -169,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); @@ -178,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(); @@ -200,6 +204,7 @@ Lv2ViewBase::Lv2ViewBase(QWidget* meAsWidget, Lv2ControlBase *ctrlBase) Lv2ViewBase::~Lv2ViewBase() { + closeHelpWindow(); // TODO: hide UI if required } @@ -225,6 +230,14 @@ void Lv2ViewBase::toggleHelp(bool visible) +void Lv2ViewBase::closeHelpWindow() +{ + if (m_helpWindow) { m_helpWindow->close(); } +} + + + + void Lv2ViewBase::modelChanged(Lv2ControlBase *ctrlBase) { // reconnect models @@ -245,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/MainApplication.cpp b/src/gui/MainApplication.cpp index ab45ce304..d33ede4d2 100644 --- a/src/gui/MainApplication.cpp +++ b/src/gui/MainApplication.cpp @@ -39,6 +39,19 @@ MainApplication::MainApplication(int& argc, char** argv) : QApplication(argc, argv), m_queuedFile() { +#if !defined(LMMS_BUILD_WIN32) && !defined(LMMS_BUILD_APPLE) && !defined(LMMS_BUILD_HAIKU) + // Work around a bug of KXmlGui < 5.55 + // which breaks the recent files menu + // https://bugs.kde.org/show_bug.cgi?id=337491 + for (auto child : children()) + { + if (child->inherits("KCheckAcceleratorsInitializer")) + { + delete child; + } + } +#endif + #if defined(LMMS_BUILD_WIN32) installNativeEventFilter(this); #endif diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp index 706d63e27..072edc0ec 100644 --- a/src/gui/MainWindow.cpp +++ b/src/gui/MainWindow.cpp @@ -33,7 +33,6 @@ #include #include #include -#include #include #include "AboutDialog.h" @@ -78,22 +77,6 @@ namespace lmms::gui { -#if !defined(LMMS_BUILD_WIN32) && !defined(LMMS_BUILD_APPLE) && !defined(LMMS_BUILD_HAIKU) -//Work around an issue on KDE5 as per https://bugs.kde.org/show_bug.cgi?id=337491#c21 -void disableAutoKeyAccelerators(QWidget* mainWindow) -{ - using DisablerFunc = void(*)(QWidget*); - QLibrary kf5WidgetsAddon("KF5WidgetsAddons", 5); - auto setNoAccelerators - = reinterpret_cast(kf5WidgetsAddon.resolve("_ZN19KAcceleratorManager10setNoAccelEP7QWidget")); - if(setNoAccelerators) - { - setNoAccelerators(mainWindow); - } - kf5WidgetsAddon.unload(); -} -#endif - MainWindow::MainWindow() : m_workspace( nullptr ), @@ -101,22 +84,19 @@ MainWindow::MainWindow() : m_autoSaveTimer( this ), m_viewMenu( nullptr ), m_metronomeToggle( 0 ), - m_session( Normal ) + m_session( SessionState::Normal ) { -#if !defined(LMMS_BUILD_WIN32) && !defined(LMMS_BUILD_APPLE) && !defined(LMMS_BUILD_HAIKU) - disableAutoKeyAccelerators(this); -#endif setAttribute( Qt::WA_DeleteOnClose ); auto main_widget = new QWidget(this); auto vbox = new QVBoxLayout(main_widget); vbox->setSpacing( 0 ); - vbox->setMargin( 0 ); + vbox->setContentsMargins(0, 0, 0, 0); auto w = new QWidget(main_widget); auto hbox = new QHBoxLayout(w); hbox->setSpacing( 0 ); - hbox->setMargin( 0 ); + hbox->setContentsMargins(0, 0, 0, 0); auto sideBar = new SideBar(Qt::Vertical, w); @@ -132,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" ); @@ -181,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); @@ -226,7 +198,7 @@ MainWindow::MainWindow() : // add layout for organizing quite complex toolbar-layouting m_toolBarLayout = new QGridLayout( m_toolBar/*, 2, 1*/ ); - m_toolBarLayout->setMargin( 0 ); + m_toolBarLayout->setContentsMargins(0, 0, 0, 0); m_toolBarLayout->setSpacing( 0 ); vbox->addWidget( m_toolBar ); @@ -254,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())); @@ -383,10 +353,12 @@ void MainWindow::finalize() } edit_menu->addSeparator(); - edit_menu->addAction( embed::getIconPixmap( "setup_general" ), - tr( "Settings" ), - this, SLOT(showSettingsDialog())); - connect( edit_menu, SIGNAL(aboutToShow()), this, SLOT(updateUndoRedoButtons())); + edit_menu->addAction(embed::getIconPixmap("microtuner"), tr("Scales and keymaps"), + this, SLOT(toggleMicrotunerWin())); + edit_menu->addAction(embed::getIconPixmap("setup_general"), tr("Settings"), + this, SLOT(showSettingsDialog())); + + connect(edit_menu, SIGNAL(aboutToShow()), this, SLOT(updateUndoRedoButtons())); m_viewMenu = new QMenu( this ); menuBar()->addMenu( m_viewMenu )->setText( tr( "&View" ) ); @@ -397,7 +369,7 @@ void MainWindow::finalize() m_toolsMenu = new QMenu( this ); - for( const Plugin::Descriptor* desc : getPluginFactory()->descriptors(Plugin::Tool) ) + for( const Plugin::Descriptor* desc : getPluginFactory()->descriptors(Plugin::Type::Tool) ) { m_toolsMenu->addAction( desc->logo->pixmap(), desc->displayName ); m_tools.push_back( ToolPlugin::instantiate( desc->name, /*this*/nullptr ) @@ -505,10 +477,6 @@ void MainWindow::finalize() tr("Show/hide project notes") + " (Ctrl+7)", this, SLOT(toggleProjectNotesWin()), m_toolBar); project_notes_window->setShortcut( Qt::CTRL + Qt::Key_7 ); - auto microtuner_window = new ToolButton(embed::getIconPixmap("microtuner"), - tr("Microtuner configuration") + " (Ctrl+8)", this, SLOT(toggleMicrotunerWin()), m_toolBar); - microtuner_window->setShortcut( Qt::CTRL + Qt::Key_8 ); - m_toolBarLayout->addWidget( song_editor_window, 1, 1 ); m_toolBarLayout->addWidget( pattern_editor_window, 1, 2 ); m_toolBarLayout->addWidget( piano_roll_window, 1, 3 ); @@ -516,7 +484,6 @@ void MainWindow::finalize() m_toolBarLayout->addWidget( mixer_window, 1, 5 ); m_toolBarLayout->addWidget( controllers_window, 1, 6 ); m_toolBarLayout->addWidget( project_notes_window, 1, 7 ); - m_toolBarLayout->addWidget( microtuner_window, 1, 8 ); m_toolBarLayout->setColumnStretch( 100, 1 ); // setup-dialog opened before? @@ -534,7 +501,7 @@ void MainWindow::finalize() ConfigManager::inst()->value( "audioengine", "audiodev" ) ) ) { // if so, offer the audio settings section of the setup dialog - SetupDialog sd( SetupDialog::AudioSettings ); + SetupDialog sd( SetupDialog::ConfigTab::AudioSettings ); sd.exec(); } @@ -593,13 +560,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; } @@ -620,7 +595,7 @@ void MainWindow::resetWindowTitle() title += '*'; } - if( getSession() == Recover ) + if( getSession() == SessionState::Recover ) { title += " - " + tr( "Recover session. Please save your work!" ); } @@ -638,7 +613,7 @@ bool MainWindow::mayChangeProject(bool stopPlayback) Engine::getSong()->stop(); } - if( !Engine::getSong()->isModified() && getSession() != Recover ) + if( !Engine::getSong()->isModified() && getSession() != SessionState::Recover ) { return( true ); } @@ -655,9 +630,9 @@ bool MainWindow::mayChangeProject(bool stopPlayback) "last saving. Do you want to save it " "now?" ); - QMessageBox mb( ( getSession() == Recover ? + QMessageBox mb( ( getSession() == SessionState::Recover ? messageTitleRecovered : messageTitleUnsaved ), - ( getSession() == Recover ? + ( getSession() == SessionState::Recover ? messageRecovered : messageUnsaved ), QMessageBox::Question, QMessageBox::Save, @@ -672,7 +647,7 @@ bool MainWindow::mayChangeProject(bool stopPlayback) } else if( answer == QMessageBox::Discard ) { - if( getSession() == Recover ) + if( getSession() == SessionState::Recover ) { sessionCleanup(); } @@ -815,7 +790,7 @@ bool MainWindow::saveProject() } else if( this->guiSaveProject() ) { - if( getSession() == Recover ) + if( getSession() == SessionState::Recover ) { sessionCleanup(); } @@ -870,7 +845,7 @@ bool MainWindow::saveProjectAs() } if( this->guiSaveProjectAs( fname ) ) { - if( getSession() == Recover ) + if( getSession() == SessionState::Recover ) { sessionCleanup(); } @@ -1120,10 +1095,6 @@ void MainWindow::updateViewMenu() tr( "Project Notes" ) + "\tCtrl+7", this, SLOT(toggleProjectNotesWin()) ); - m_viewMenu->addAction(embed::getIconPixmap( "microtuner" ), - tr( "Microtuner" ) + "\tCtrl+8", - this, SLOT(toggleMicrotunerWin()) - ); m_viewMenu->addSeparator(); @@ -1137,8 +1108,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() ); @@ -1234,19 +1204,19 @@ void MainWindow::updatePlayPauseIcons() { switch( Engine::getSong()->playMode() ) { - case Song::Mode_PlaySong: + case Song::PlayMode::Song: getGUI()->songEditor()->setPauseIcon( true ); break; - case Song::Mode_PlayAutomationClip: + case Song::PlayMode::AutomationClip: getGUI()->automationEditor()->setPauseIcon( true ); break; - case Song::Mode_PlayPattern: + case Song::PlayMode::Pattern: getGUI()->patternEditor()->setPauseIcon( true ); break; - case Song::Mode_PlayMidiClip: + case Song::PlayMode::MidiClip: getGUI()->pianoRoll()->setPauseIcon( true ); break; @@ -1308,7 +1278,7 @@ void MainWindow::sessionCleanup() { // delete recover session files QFile::remove( ConfigManager::inst()->recoveryFile() ); - setSession( Normal ); + setSession( SessionState::Normal ); } @@ -1500,7 +1470,7 @@ void MainWindow::exportProject(bool multiExport) efd.setFileMode( FileDialog::AnyFile ); int idx = 0; QStringList types; - while( ProjectRenderer::fileEncodeDevices[idx].m_fileFormat != ProjectRenderer::NumFileFormats) + while( ProjectRenderer::fileEncodeDevices[idx].m_fileFormat != ProjectRenderer::ExportFileFormat::Count) { if(ProjectRenderer::fileEncodeDevices[idx].isAvailable()) { types << tr(ProjectRenderer::fileEncodeDevices[idx].m_description); @@ -1633,42 +1603,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::BackToZero: - if( songEditor && ( tl->autoScroll() == TimeLineWidget::AutoScrollEnabled ) ) - { - songEditor->m_editor->updatePosition(0); - } - break; - - case TimeLineWidget::BackToStart: - if( tl->savedPos() >= 0 ) - { - if(songEditor && ( tl->autoScroll() == TimeLineWidget::AutoScrollEnabled ) ) - { - songEditor->m_editor->updatePosition( TimePos(tl->savedPos().getTicks() ) ); - } - tl->savePos( -1 ); - } - break; - - case TimeLineWidget::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 316bf54d6..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" @@ -56,16 +56,26 @@ namespace lmms::gui MicrotunerConfig::MicrotunerConfig() : QWidget(), - m_scaleComboModel(nullptr, tr("Selected scale")), - m_keymapComboModel(nullptr, tr("Selected keymap")), + m_scaleComboModel(nullptr, tr("Selected scale slot")), + m_keymapComboModel(nullptr, tr("Selected keymap slot")), m_firstKeyModel(0, 0, NumKeys - 1, nullptr, tr("First key")), m_lastKeyModel(NumKeys - 1, 0, NumKeys - 1, nullptr, tr("Last key")), m_middleKeyModel(DefaultMiddleKey, 0, NumKeys - 1, nullptr, tr("Middle key")), m_baseKeyModel(DefaultBaseKey, 0, NumKeys - 1, nullptr, tr("Base key")), m_baseFreqModel(DefaultBaseFreq, 0.1f, 9999.999f, 0.001f, nullptr, tr("Base note frequency")) { +#if QT_VERSION < 0x50C00 + // Workaround for a bug in Qt versions below 5.12, + // where argument-dependent-lookup fails for QFlags operators + // declared inside a namepsace. + // This affects the Q_DECLARE_OPERATORS_FOR_FLAGS macro in Instrument.h + // See also: https://codereview.qt-project.org/c/qt/qtbase/+/225348 + + using ::operator|; +#endif + setWindowIcon(embed::getIconPixmap("microtuner")); - setWindowTitle(tr("Microtuner")); + setWindowTitle(tr("Microtuner Configuration")); // Organize into 2 main columns: scales and keymaps auto microtunerLayout = new QGridLayout(); @@ -74,7 +84,7 @@ MicrotunerConfig::MicrotunerConfig() : // ---------------------------------- // Scale sub-column // - auto scaleLabel = new QLabel(tr("Scale:")); + auto scaleLabel = new QLabel(tr("Scale slot to edit:")); microtunerLayout->addWidget(scaleLabel, 0, 0, 1, 2, Qt::AlignBottom); for (unsigned int i = 0; i < MaxScaleCount; i++) @@ -92,6 +102,8 @@ MicrotunerConfig::MicrotunerConfig() : auto loadScaleButton = new QPushButton(tr("Load")); auto saveScaleButton = new QPushButton(tr("Save")); + loadScaleButton->setToolTip(tr("Load scale definition from a file.")); + saveScaleButton->setToolTip(tr("Save scale definition to a file.")); microtunerLayout->addWidget(loadScaleButton, 3, 0, 1, 1); microtunerLayout->addWidget(saveScaleButton, 3, 1, 1, 1); connect(loadScaleButton, &QPushButton::clicked, [=] {loadScaleFromFile();}); @@ -102,14 +114,15 @@ MicrotunerConfig::MicrotunerConfig() : m_scaleTextEdit->setToolTip(tr("Enter intervals on separate lines. Numbers containing a decimal point are treated as cents.\nOther inputs are treated as integer ratios and must be in the form of \'a/b\' or \'a\'.\nUnity (0.0 cents or ratio 1/1) is always present as a hidden first value; do not enter it manually.")); microtunerLayout->addWidget(m_scaleTextEdit, 4, 0, 2, 2); - auto applyScaleButton = new QPushButton(tr("Apply scale")); + auto applyScaleButton = new QPushButton(tr("Apply scale changes")); + applyScaleButton->setToolTip(tr("Verify and apply changes made to the selected scale. To use the scale, select it in the settings of a supported instrument.")); microtunerLayout->addWidget(applyScaleButton, 6, 0, 1, 2); connect(applyScaleButton, &QPushButton::clicked, [=] {applyScale();}); // ---------------------------------- // Mapping sub-column // - auto keymapLabel = new QLabel(tr("Keymap:")); + auto keymapLabel = new QLabel(tr("Keymap slot to edit:")); microtunerLayout->addWidget(keymapLabel, 0, 2, 1, 2, Qt::AlignBottom); for (unsigned int i = 0; i < MaxKeymapCount; i++) @@ -127,6 +140,8 @@ MicrotunerConfig::MicrotunerConfig() : auto loadKeymapButton = new QPushButton(tr("Load")); auto saveKeymapButton = new QPushButton(tr("Save")); + loadKeymapButton->setToolTip(tr("Load key mapping definition from a file.")); + saveKeymapButton->setToolTip(tr("Save key mapping definition to a file.")); microtunerLayout->addWidget(loadKeymapButton, 3, 2, 1, 1); microtunerLayout->addWidget(saveKeymapButton, 3, 3, 1, 1); connect(loadKeymapButton, &QPushButton::clicked, [=] {loadKeymapFromFile();}); @@ -171,7 +186,8 @@ MicrotunerConfig::MicrotunerConfig() : baseFreqSpin->setToolTip(tr("Base note frequency")); keymapRangeLayout->addWidget(baseFreqSpin, 1, 1, 1, 2); - auto applyKeymapButton = new QPushButton(tr("Apply keymap")); + auto applyKeymapButton = new QPushButton(tr("Apply keymap changes")); + applyKeymapButton->setToolTip(tr("Verify and apply changes made to the selected key mapping. To use the mapping, select it in the settings of a supported instrument.")); microtunerLayout->addWidget(applyKeymapButton, 6, 2, 1, 2); connect(applyKeymapButton, &QPushButton::clicked, [=] {applyKeymap();}); @@ -326,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; @@ -341,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;} @@ -374,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; @@ -408,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()); @@ -419,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); } @@ -454,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/MidiCCRackView.cpp b/src/gui/MidiCCRackView.cpp index 7e0d71f78..a0b1496fb 100644 --- a/src/gui/MidiCCRackView.cpp +++ b/src/gui/MidiCCRackView.cpp @@ -89,7 +89,7 @@ MidiCCRackView::MidiCCRackView(InstrumentTrack * track) : // Adds the controller knobs for (int i = 0; i < MidiControllerCount; ++i) { - m_controllerKnob[i] = new Knob(knobBright_26); + m_controllerKnob[i] = new Knob(KnobType::Bright26); m_controllerKnob[i]->setLabel(tr("CC %1").arg(i)); knobsAreaLayout->addWidget(m_controllerKnob[i], i/4, i%4); } diff --git a/src/gui/MidiSetupWidget.cpp b/src/gui/MidiSetupWidget.cpp index 4f620fb0e..0e6678727 100644 --- a/src/gui/MidiSetupWidget.cpp +++ b/src/gui/MidiSetupWidget.cpp @@ -24,11 +24,10 @@ #include "MidiSetupWidget.h" -#include +#include #include #include "ConfigManager.h" -#include "gui_templates.h" namespace lmms::gui @@ -37,7 +36,7 @@ namespace lmms::gui MidiSetupWidget::MidiSetupWidget(const QString & caption, const QString & configSection, const QString & devName, QWidget * parent) : - TabWidget(TabWidget::tr("Settings for %1").arg(tr(caption.toUtf8())), parent), + QGroupBox(QGroupBox::tr("Settings for %1").arg(tr(caption.toUtf8())), parent), m_configSection(configSection), m_device(nullptr) { @@ -45,12 +44,11 @@ MidiSetupWidget::MidiSetupWidget(const QString & caption, const QString & config // to indicate that there is no editable device field if (!devName.isNull()) { - m_device = new QLineEdit(devName, this); - m_device->setGeometry(10, 20, 160, 20); + QFormLayout * form = new QFormLayout(this); - auto dev_lbl = new QLabel(tr("Device"), this); - dev_lbl->setFont(pointSize<7>(dev_lbl->font())); - dev_lbl->setGeometry(10, 40, 160, 10); + m_device = new QLineEdit(devName, this); + + form->addRow(tr("Device"), m_device); } } diff --git a/src/gui/MixerChannelView.cpp b/src/gui/MixerChannelView.cpp new file mode 100644 index 000000000..22251d551 --- /dev/null +++ b/src/gui/MixerChannelView.cpp @@ -0,0 +1,488 @@ +/* + * 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) + { + setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Minimum); + + 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->addStretch(); + mainLayout->addWidget(m_renameLineEditView, 0, Qt::AlignHCenter); + mainLayout->addLayout(soloMuteLayout, 0); + mainLayout->addWidget(m_fader, 0, 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 030ea892e..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( knobBright_26, 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 62d4cb7ce..e97b5414f 100644 --- a/src/gui/MixerView.cpp +++ b/src/gui/MixerView.cpp @@ -31,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" @@ -50,60 +50,71 @@ namespace lmms::gui { -MixerView::MixerView() : +MixerView::MixerView(Mixer* mixer) : QWidget(), - ModelView( nullptr, this ), - SerializingObjectHook() + ModelView(nullptr, this), + SerializingObjectHook(), + m_mixer(mixer) { - Mixer * m = Engine::mixer(); - m->setHook( this ); +#if QT_VERSION < 0x50C00 + // Workaround for a bug in Qt versions below 5.12, + // where argument-dependent-lookup fails for QFlags operators + // declared inside a namepsace. + // This affects the Q_DECLARE_OPERATORS_FOR_FLAGS macro in Instrument.h + // See also: https://codereview.qt-project.org/c/qt/qtbase/+/225348 + + using ::operator|; +#endif + + mixer->setHook(this); //QPalette pal = palette(); - //pal.setColor( QPalette::Background, 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->setMargin( 0 ); + chLayout = new QHBoxLayout(m_channelAreaWidget); + chLayout->setSizeConstraint(QLayout::SetMinimumSize); + chLayout->setSpacing(0); + chLayout->setContentsMargins(0, 0, 0, 0); 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, Qt::AlignTop); - 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 @@ -111,83 +122,77 @@ 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); + + int const scrollBarExtent = style()->pixelMetric(QStyle::PM_ScrollBarExtent); + channelArea->setFixedHeight(mixerChannelSize.height() + scrollBarExtent); + + ml->addWidget(channelArea, 1, Qt::AlignTop); // 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->setFixedSize(mixerChannelSize); + connect(newChannelBtn, SIGNAL(clicked()), this, SLOT(addNewChannel())); + ml->addWidget(newChannelBtn, 0, Qt::AlignTop); // 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 ); + QMdiSubWindow* subWin = mainWindow->addWindowedWidget(this); Qt::WindowFlags flags = subWin->windowFlags(); flags &= ~Qt::WindowMaximizeButtonHint; - subWin->setWindowFlags( flags ); - layout()->setSizeConstraint( QLayout::SetMinimumSize ); - subWin->layout()->setSizeConstraint( QLayout::SetMinAndMaxSize ); + subWin->setWindowFlags(flags); + 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(); @@ -198,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(); @@ -236,20 +243,20 @@ void MixerView::refreshDisplay() // update the and max. channel number for every instrument void MixerView::updateMaxChannelSelector() { - TrackContainer::TrackList songTracks = Engine::getSong()->tracks(); - TrackContainer::TrackList patternStoreTracks = Engine::patternStore()->tracks(); + const TrackContainer::TrackList& songTracks = Engine::getSong()->tracks(); + const TrackContainer::TrackList& patternStoreTracks = Engine::patternStore()->tracks(); for (const auto& trackList : {songTracks, patternStoreTracks}) { for (const auto& track : trackList) { - if (track->type() == Track::InstrumentTrack) + if (track->type() == Track::Type::Instrument) { auto inst = (InstrumentTrack*)track; inst->mixerChannelModel()->setRange(0, m_mixerChannelViews.size()-1,1); } - else if (track->type() == Track::SampleTrack) + else if (track->type() == Track::Type::Sample) { auto strk = (SampleTrack*)track; strk->mixerChannelModel()->setRange(0, @@ -260,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); @@ -374,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(); } @@ -383,82 +373,57 @@ void MixerView::updateMixerLine(int index) void MixerView::deleteChannel(int index) { // can't delete master - if( index == 0 ) return; + if (index == 0) 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; im_mixerLine->setChannelIndex(i-1); + m_mixerChannelViews[i]->setChannelIndex(i - 1); } m_mixerChannelViews.remove(index); // select the next channel - if( selLine >= m_mixerChannelViews.size() ) + if (selLine >= m_mixerChannelViews.size()) { - selLine = m_mixerChannelViews.size()-1; + selLine = m_mixerChannelViews.size() - 1; } - setCurrentMixerLine(selLine); + setCurrentMixerChannel(selLine); updateMaxChannelSelector(); } - - void MixerView::deleteUnusedChannels() { - TrackContainer::TrackList tracks; - tracks += Engine::getSong()->tracks(); - tracks += Engine::patternStore()->tracks(); + Mixer* mix = getMixer(); - std::vector inUse(m_mixerChannelViews.size(), false); - - //Populate inUse by checking the destination channel for every track - for (Track* t: tracks) + // Check all channels except master, delete those with no incoming sends + for (int i = m_mixerChannelViews.size() - 1; i > 0; --i) { - //The channel that this track sends to. Since master channel is always in use, - //setting this to 0 is a safe default (for tracks that don't sent to the mixer). - int channel = 0; - if (t->type() == Track::InstrumentTrack) + if (!mix->isChannelInUse(i)) { - auto inst = dynamic_cast(t); - channel = inst->mixerChannelModel()->value(); + deleteChannel(i); } - else if (t->type() == Track::SampleTrack) - { - auto strack = dynamic_cast(t); - channel = strack->mixerChannelModel()->value(); - } - inUse[channel] = true; - } - - //Check all channels except master, delete those with no incoming sends - for(int i = m_mixerChannelViews.size()-1; i > 0; --i) - { - if (!inUse[i] && Engine::mixer()->mixerChannel(i)->m_receives.isEmpty()) - { deleteChannel(i); } } } @@ -467,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(); } @@ -509,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(); } @@ -542,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(); } @@ -559,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]); } } @@ -576,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(); } @@ -586,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 a19c80df0..963609c43 100644 --- a/src/gui/PluginBrowser.cpp +++ b/src/gui/PluginBrowser.cpp @@ -27,6 +27,7 @@ #include #include #include +#include #include #include #include @@ -34,7 +35,10 @@ #include "embed.h" #include "Engine.h" +#include "InstrumentTrack.h" +#include "Song.h" #include "StringPairDrag.h" +#include "TrackContainerView.h" #include "PluginFactory.h" namespace lmms::gui @@ -52,7 +56,7 @@ PluginBrowser::PluginBrowser( QWidget * _parent ) : addContentWidget( m_view ); auto view_layout = new QVBoxLayout(m_view); - view_layout->setMargin( 5 ); + view_layout->setContentsMargins(5, 5, 5, 5); view_layout->setSpacing( 5 ); @@ -64,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 ); @@ -157,7 +162,7 @@ void PluginBrowser::addPlugins() m_descTree->clear(); // Fetch and sort all instrument plugin descriptors - auto descs = getPluginFactory()->descriptors(Plugin::Instrument); + auto descs = getPluginFactory()->descriptors(Plugin::Type::Instrument); std::sort(descs.begin(), descs.end(), [](auto d1, auto d2) { @@ -277,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 ); @@ -287,4 +292,24 @@ void PluginDescWidget::mousePressEvent( QMouseEvent * _me ) } +void PluginDescWidget::contextMenuEvent(QContextMenuEvent* e) +{ + QMenu contextMenu(this); + contextMenu.addAction( + tr("Send to new instrument track"), + [=]{ openInNewInstrumentTrack(m_pluginKey.desc->name); } + ); + contextMenu.exec(e->globalPos()); +} + + +void PluginDescWidget::openInNewInstrumentTrack(QString value) +{ + TrackContainer* tc = Engine::getSong(); + auto it = dynamic_cast(Track::create(Track::Type::Instrument, tc)); + auto ilt = new InstrumentLoaderThread(this, it, value); + ilt->start(); +} + + } // namespace lmms::gui 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 78b899a09..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 @@ -54,19 +52,25 @@ SampleTrackWindow::SampleTrackWindow(SampleTrackView * tv) : m_track(tv->model()), m_stv(tv) { +#if QT_VERSION < 0x50C00 + // Workaround for a bug in Qt versions below 5.12, + // where argument-dependent-lookup fails for QFlags operators + // declared inside a namepsace. + // This affects the Q_DECLARE_OPERATORS_FOR_FLAGS macro in Instrument.h + // See also: https://codereview.qt-project.org/c/qt/qtbase/+/225348 + + using ::operator|; +#endif + // init own layout + widgets setFocusPolicy(Qt::StrongFocus); auto vlayout = new QVBoxLayout(this); - vlayout->setMargin(0); + 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); @@ -74,7 +78,6 @@ SampleTrackWindow::SampleTrackWindow(SampleTrackView * tv) : // setup line edit for changing sample track name m_nameLineEdit = new QLineEdit; - m_nameLineEdit->setFont(pointSize<9>(m_nameLineEdit->font())); connect(m_nameLineEdit, SIGNAL(textChanged(const QString&)), this, SLOT(textChanged(const QString&))); @@ -94,7 +97,7 @@ SampleTrackWindow::SampleTrackWindow(SampleTrackView * tv) : Qt::Alignment widgetAlignment = Qt::AlignHCenter | Qt::AlignCenter; // set up volume knob - m_volumeKnob = new Knob(knobBright_26, nullptr, tr("Sample volume")); + m_volumeKnob = new Knob(KnobType::Bright26, nullptr, tr("Sample volume")); m_volumeKnob->setVolumeKnob(true); m_volumeKnob->setHintText(tr("Volume:"), "%"); @@ -108,7 +111,7 @@ SampleTrackWindow::SampleTrackWindow(SampleTrackView * tv) : // set up panning knob - m_panningKnob = new Knob(knobBright_26, nullptr, tr("Panning")); + m_panningKnob = new Knob(KnobType::Bright26, nullptr, tr("Panning")); m_panningKnob->setHintText(tr("Panning:"), ""); basicControlsLayout->addWidget(m_panningKnob, 0, 1); @@ -124,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..68b09b482 --- /dev/null +++ b/src/gui/SampleWaveform.cpp @@ -0,0 +1,87 @@ +/* + * 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 auto x = rect.x(); + const auto height = rect.height(); + const auto width = rect.width(); + const auto centerY = rect.center().y(); + + const auto halfHeight = height / 2; + + const auto color = painter.pen().color(); + const auto rmsColor = color.lighter(123); + + const auto framesPerPixel = std::max(1, parameters.size / width); + + constexpr auto maxFramesPerPixel = 512; + const auto resolution = std::max(1, framesPerPixel / maxFramesPerPixel); + const auto framesPerResolution = framesPerPixel / resolution; + + const auto numPixels = std::min(parameters.size, width); + auto min = std::vector(numPixels, 1); + auto max = std::vector(numPixels, -1); + auto squared = std::vector(numPixels); + + const auto maxFrames = numPixels * framesPerPixel; + for (int i = 0; i < maxFrames; i += resolution) + { + const auto pixelIndex = i / framesPerPixel; + const auto frameIndex = !parameters.reversed ? i : maxFrames - i; + + const auto& frame = parameters.buffer[frameIndex]; + const auto 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; + } + + for (int i = 0; i < numPixels; i++) + { + const auto lineY1 = centerY - max[i] * halfHeight * parameters.amplification; + const auto lineY2 = centerY - min[i] * halfHeight * parameters.amplification; + const auto lineX = i + x; + painter.drawLine(lineX, lineY1, lineX, lineY2); + + const auto rms = std::sqrt(squared[i] / framesPerResolution); + const auto maxRMS = std::clamp(rms, min[i], max[i]); + const auto minRMS = std::clamp(-rms, min[i], max[i]); + + const auto rmsLineY1 = centerY - maxRMS * halfHeight * parameters.amplification; + const auto 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/SideBarWidget.cpp b/src/gui/SideBarWidget.cpp index cf0233af1..c218bedd3 100644 --- a/src/gui/SideBarWidget.cpp +++ b/src/gui/SideBarWidget.cpp @@ -44,7 +44,7 @@ SideBarWidget::SideBarWidget( const QString & _title, const QPixmap & _icon, m_contents = new QWidget( this ); m_layout = new QVBoxLayout( m_contents ); m_layout->setSpacing( 5 ); - m_layout->setMargin( 0 ); + m_layout->setContentsMargins(0, 0, 0, 0); m_closeBtn = new QPushButton(embed::getIconPixmap("close"), QString(), this); m_closeBtn->resize(m_buttonSize); m_closeBtn->setToolTip(tr("Close")); @@ -62,16 +62,16 @@ void SideBarWidget::paintEvent( QPaintEvent * ) QFont f = p.font(); f.setBold( true ); - f.setUnderline( true ); + f.setUnderline(false); f.setPointSize( f.pointSize() + 2 ); p.setFont( f ); p.setPen( palette().highlightedText().color() ); - const int tx = m_icon.width()+4; + const int tx = m_icon.width() + 8; QFontMetrics metrics( f ); - const int ty = metrics.ascent(); + const int ty = (metrics.ascent() + m_icon.height()) / 2; p.drawText( tx, ty, m_title ); p.drawPixmap( 2, 2, m_icon.transformed( QTransform().rotate( -90 ) ) ); diff --git a/src/gui/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 6fa91efdb..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(); } @@ -191,10 +185,10 @@ void AutomationClipView::constructContextMenu( QMenu * _cm ) _cm->addAction( embed::getIconPixmap( "flip_x" ), tr( "Flip Horizontally (Visible)" ), this, SLOT(flipX())); - if( !m_clip->m_objects.isEmpty() ) + if (!m_clip->m_objects.empty()) { _cm->addSeparator(); - auto m = new QMenu(tr("%1 Connections").arg(m_clip->m_objects.count()), _cm); + auto m = new QMenu(tr("%1 Connections").arg(m_clip->m_objects.size()), _cm); for (const auto& object : m_clip->m_objects) { if (object) @@ -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::DiscreteProgression ) - { - 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 e2da11439..5c8a12b91 100644 --- a/src/gui/clips/ClipView.cpp +++ b/src/gui/clips/ClipView.cpp @@ -25,6 +25,7 @@ #include "ClipView.h" #include +#include #include #include @@ -43,6 +44,8 @@ #include "MidiClip.h" #include "MidiClipView.h" #include "Note.h" +#include "PatternClip.h" +#include "PatternStore.h" #include "SampleClip.h" #include "Song.h" #include "SongEditor.h" @@ -85,7 +88,7 @@ ClipView::ClipView( Clip * clip, m_initialClipPos( TimePos(0) ), m_initialClipEnd( TimePos(0) ), m_clip( clip ), - m_action( NoAction ), + m_action( Action::None ), m_initialMousePos( QPoint( 0, 0 ) ), m_initialMouseGlobalPos( QPoint( 0, 0 ) ), m_initialOffsets( QVector() ), @@ -123,7 +126,7 @@ ClipView::ClipView( Clip * clip, connect( m_clip, SIGNAL(lengthChanged()), this, SLOT(updateLength())); - connect( getGUI()->songEditor()->m_editor->zoomingModel(), SIGNAL(dataChanged()), this, SLOT(updateLength())); + connect(getGUI()->songEditor()->m_editor, &SongEditor::pixelsPerBarChanged, this, &ClipView::updateLength); connect( m_clip, SIGNAL(positionChanged()), this, SLOT(updatePosition())); connect( m_clip, SIGNAL(destroyedClip()), this, SLOT(close())); @@ -133,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 ); @@ -291,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(); } @@ -312,10 +326,9 @@ void ClipView::updateLength() } else { - setFixedWidth( - static_cast( m_clip->length() * pixelsPerBar() / - TimePos::ticksPerBar() ) + 1 /*+ - BORDER_WIDTH * 2-1*/ ); + // this std::max function is needed for clips that do not start or end on the beat, otherwise, they "disappear" when zooming to min + // 3 is the minimun width needed to make a clip visible + setFixedWidth(std::max(static_cast(m_clip->length() * pixelsPerBar() / TimePos::ticksPerBar() + 1), 3)); } m_trackView->trackContainerView()->update(); } @@ -338,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().background().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(); @@ -395,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. * @@ -434,7 +425,7 @@ void ClipView::dragEnterEvent( QDragEnterEvent * dee ) else { StringPairDrag::processDragEnterEvent( dee, "clip_" + - QString::number( m_clip->getTrack()->type() ) ); + QString::number( static_cast(m_clip->getTrack()->type()) ) ); } } @@ -456,7 +447,7 @@ void ClipView::dropEvent( QDropEvent * de ) QString value = StringPairDrag::decodeValue( de ); // Track must be the same type to paste into - if( type != ( "clip_" + QString::number( m_clip->getTrack()->type() ) ) ) + if( type != ( "clip_" + QString::number( static_cast(m_clip->getTrack()->type()) ) ) ) { return; } @@ -502,10 +493,11 @@ void ClipView::dropEvent( QDropEvent * de ) void ClipView::updateCursor(QMouseEvent * me) { auto sClip = dynamic_cast(m_clip); + auto pClip = dynamic_cast(m_clip); // If we are at the edges, use the resize cursor if (!me->buttons() && !m_clip->getAutoResize() && !isSelected() - && ((me->x() > width() - RESIZE_GRIP_WIDTH) || (me->x() < RESIZE_GRIP_WIDTH && sClip))) + && ((me->x() > width() - RESIZE_GRIP_WIDTH) || (me->x() < RESIZE_GRIP_WIDTH && (sClip || pClip)))) { setCursor(Qt::SizeHorCursor); } @@ -535,17 +527,18 @@ DataFile ClipView::createClipDataFiles( { Track * t = m_trackView->getTrack(); TrackContainer * tc = t->trackContainer(); - DataFile dataFile( DataFile::DragNDropData ); + DataFile dataFile( DataFile::Type::DragNDropData ); QDomElement clipParent = dataFile.createElement("clips"); for (const auto& clipView : clipViews) { // Insert into the dom under the "clips" element Track* clipTrack = clipView->m_trackView->getTrack(); - int trackIndex = tc->tracks().indexOf( clipTrack ); + int trackIndex = std::distance(tc->tracks().begin(), std::find(tc->tracks().begin(), tc->tracks().end(), clipTrack)); + assert(trackIndex != tc->tracks().size()); QDomElement clipElement = dataFile.createElement("clip"); clipElement.setAttribute( "trackIndex", trackIndex ); - clipElement.setAttribute( "trackType", clipTrack->type() ); + clipElement.setAttribute( "trackType", static_cast(clipTrack->type()) ); clipElement.setAttribute( "trackName", clipTrack->name() ); clipView->m_clip->saveState(dataFile, clipElement); clipParent.appendChild( clipElement ); @@ -554,12 +547,15 @@ DataFile ClipView::createClipDataFiles( dataFile.content().appendChild( clipParent ); // Add extra metadata needed for calculations later - int initialTrackIndex = tc->tracks().indexOf( t ); - if( initialTrackIndex < 0 ) + + const auto initialTrackIt = std::find(tc->tracks().begin(), tc->tracks().end(), t); + if (initialTrackIt == tc->tracks().end()) { printf("Failed to find selected track in the TrackContainer.\n"); return dataFile; } + + const int initialTrackIndex = std::distance(tc->tracks().begin(), initialTrackIt); QDomElement metadata = dataFile.createElement( "copyMetadata" ); // initialTrackIndex is the index of the track that was touched metadata.setAttribute( "initialTrackIndex", initialTrackIndex ); @@ -633,33 +629,34 @@ void ClipView::mousePressEvent( QMouseEvent * me ) if( !fixedClips() && me->button() == Qt::LeftButton ) { auto sClip = dynamic_cast(m_clip); + auto pClip = dynamic_cast(m_clip); const bool knifeMode = m_trackView->trackContainerView()->knifeMode(); if ( me->modifiers() & Qt::ControlModifier && !(sClip && knifeMode) ) { if( isSelected() ) { - m_action = CopySelection; + m_action = Action::CopySelection; } else { - m_action = ToggleSelected; + m_action = Action::ToggleSelected; } } else { if( isSelected() ) { - m_action = MoveSelection; + m_action = Action::MoveSelection; } else { getGUI()->songEditor()->m_editor->selectAllClips( false ); m_clip->addJournalCheckPoint(); - // Move, Resize and ResizeLeft - // Split action doesn't disable Clip journalling - if (m_action == Move || m_action == Resize || m_action == ResizeLeft) + // Action::Move, Action::Resize and Action::ResizeLeft + // Action::Split action doesn't disable Clip journalling + if (m_action == Action::Move || m_action == Action::Resize || m_action == Action::ResizeLeft) { m_clip->setJournalling(false); } @@ -669,22 +666,22 @@ void ClipView::mousePressEvent( QMouseEvent * me ) if( m_clip->getAutoResize() ) { // Always move clips that can't be manually resized - m_action = Move; + m_action = Action::Move; setCursor( Qt::SizeAllCursor ); } else if( me->x() >= width() - RESIZE_GRIP_WIDTH ) { - m_action = Resize; + m_action = Action::Resize; setCursor( Qt::SizeHorCursor ); } - else if( me->x() < RESIZE_GRIP_WIDTH && sClip ) + else if( me->x() < RESIZE_GRIP_WIDTH && (sClip || pClip) ) { - m_action = ResizeLeft; + m_action = Action::ResizeLeft; setCursor( Qt::SizeHorCursor ); } else if( sClip && knifeMode ) { - m_action = Split; + m_action = Action::Split; setCursor( m_cursorKnife ); setMarkerPos( knifeMarkerPos( me ) ); setMarkerEnabled( true ); @@ -692,11 +689,11 @@ void ClipView::mousePressEvent( QMouseEvent * me ) } else { - m_action = Move; + m_action = Action::Move; setCursor( Qt::SizeAllCursor ); } - if( m_action == Move ) + if( m_action == Action::Move ) { s_textFloat->setTitle( tr( "Current position" ) ); s_textFloat->setText( QString( "%1:%2" ). @@ -704,7 +701,7 @@ void ClipView::mousePressEvent( QMouseEvent * me ) arg( m_clip->startPosition().getTicks() % TimePos::ticksPerBar() ) ); } - else if( m_action == Resize || m_action == ResizeLeft ) + else if( m_action == Action::Resize || m_action == Action::ResizeLeft ) { s_textFloat->setTitle( tr( "Current length" ) ); s_textFloat->setText( tr( "%1:%2 (%3:%4 to %5:%6)" ). @@ -721,11 +718,11 @@ void ClipView::mousePressEvent( QMouseEvent * me ) // s_textFloat->reparent( this ); // setup text-float as if Clip was already moved/resized s_textFloat->moveGlobal( this, QPoint( width() + 2, height() + 2) ); - if ( m_action != Split) { s_textFloat->show(); } + if ( m_action != Action::Split) { s_textFloat->show(); } } delete m_hint; - QString hint = m_action == Move || m_action == MoveSelection + QString hint = m_action == Action::Move || m_action == Action::MoveSelection ? tr( "Press <%1> and drag to make a copy." ) : tr( "Press <%1> for free resizing." ); m_hint = TextFloat::displayMessage( tr( "Hint" ), hint.arg(UI_CTRL_KEY), @@ -742,9 +739,9 @@ void ClipView::mousePressEvent( QMouseEvent * me ) { remove( active ); } - if (m_action == Split) + if (m_action == Action::Split) { - m_action = NoAction; + m_action = Action::None; auto sClip = dynamic_cast(m_clip); if (sClip) { @@ -784,12 +781,12 @@ void ClipView::mousePressEvent( QMouseEvent * me ) */ void ClipView::mouseMoveEvent( QMouseEvent * me ) { - if( m_action == CopySelection || m_action == ToggleSelected ) + if( m_action == Action::CopySelection || m_action == Action::ToggleSelected ) { if( mouseMovedDistance( me, 2 ) == true ) { QVector clipViews; - if( m_action == CopySelection ) + if( m_action == Action::CopySelection ) { // Collect all selected Clips QVector so = @@ -810,7 +807,7 @@ void ClipView::mouseMoveEvent( QMouseEvent * me ) } // Clear the action here because mouseReleaseEvent will not get // triggered once we go into drag. - m_action = NoAction; + m_action = Action::None; // Write the Clips to the DataFile for copying DataFile dataFile = createClipDataFiles( clipViews ); @@ -821,7 +818,7 @@ void ClipView::mouseMoveEvent( QMouseEvent * me ) Qt::KeepAspectRatio, Qt::SmoothTransformation ); new StringPairDrag( QString( "clip_%1" ).arg( - m_clip->getTrack()->type() ), + static_cast(m_clip->getTrack()->type()) ), dataFile.toString(), thumbnail, this ); } } @@ -833,10 +830,9 @@ void ClipView::mouseMoveEvent( QMouseEvent * me ) } const float ppb = m_trackView->trackContainerView()->pixelsPerBar(); - if( m_action == Move ) + if( m_action == Action::Move ) { TimePos newPos = draggedClipPos( me ); - m_clip->movePosition(newPos); newPos = m_clip->startPosition(); // Get the real position the Clip was dragged to for the label m_trackView->getTrackContentWidget()->changePosition(); @@ -846,7 +842,7 @@ void ClipView::mouseMoveEvent( QMouseEvent * me ) TimePos::ticksPerBar() ) ); s_textFloat->moveGlobal( this, QPoint( width() + 2, height() + 2 ) ); } - else if( m_action == MoveSelection ) + else if( m_action == Action::MoveSelection ) { // 1: Find the position we want to move the grabbed Clip to TimePos newPos = draggedClipPos( me ); @@ -876,13 +872,13 @@ void ClipView::mouseMoveEvent( QMouseEvent * me ) ( *it )->movePosition( newPos + m_initialOffsets[index] ); } } - else if( m_action == Resize || m_action == ResizeLeft ) + else if( m_action == Action::Resize || m_action == Action::ResizeLeft ) { const float snapSize = getGUI()->songEditor()->m_editor->getSnapSize(); // Length in ticks of one snap increment const TimePos snapLength = TimePos( (int)(snapSize * TimePos::ticksPerBar()) ); - if( m_action == Resize ) + if( m_action == Action::Resize ) { // The clip's new length TimePos l = static_cast( me->x() * TimePos::ticksPerBar() / ppb ); @@ -916,7 +912,8 @@ void ClipView::mouseMoveEvent( QMouseEvent * me ) else { auto sClip = dynamic_cast(m_clip); - if( sClip ) + auto pClip = dynamic_cast(m_clip); + if( sClip || pClip ) { const int x = mapToParent( me->pos() ).x() - m_initialMousePos.x(); @@ -948,12 +945,27 @@ void ClipView::mouseMoveEvent( QMouseEvent * me ) t = qMin( m_initialClipEnd - minLength, m_initialClipPos + offset ); } - TimePos oldPos = m_clip->startPosition(); - if( m_clip->length() + ( oldPos - t ) >= 1 ) + TimePos positionOffset = m_clip->startPosition() - t; + if (m_clip->length() + positionOffset >= 1) { - m_clip->movePosition( t ); - m_clip->changeLength( m_clip->length() + ( oldPos - t ) ); - sClip->setStartTimeOffset( sClip->startTimeOffset() + ( oldPos - t ) ); + m_clip->movePosition(t); + m_clip->changeLength(m_clip->length() + positionOffset); + if (sClip) + { + sClip->setStartTimeOffset(sClip->startTimeOffset() + positionOffset); + } + else if (pClip) + { + // Modulus the start time offset as we need it only for offsets + // inside the pattern length. This is done to prevent a value overflow. + // The start time offset may still become larger than the pattern length + // whenever the pattern length decreases without a clip resize following. + // To deal safely with it, always modulus before use. + tick_t patternLength = Engine::patternStore()->lengthOfPattern(pClip->patternIndex()) + * TimePos::ticksPerBar(); + TimePos position = (pClip->startTimeOffset() + positionOffset) % patternLength; + pClip->setStartTimeOffset(position); + } } } } @@ -969,7 +981,7 @@ void ClipView::mouseMoveEvent( QMouseEvent * me ) TimePos::ticksPerBar() ) ); s_textFloat->moveGlobal( this, QPoint( width() + 2, height() + 2) ); } - else if( m_action == Split ) + else if( m_action == Action::Split ) { auto sClip = dynamic_cast(m_clip); if (sClip) { @@ -994,21 +1006,21 @@ void ClipView::mouseMoveEvent( QMouseEvent * me ) */ void ClipView::mouseReleaseEvent( QMouseEvent * me ) { - // If the CopySelection was chosen as the action due to mouse movement, + // If the Action::CopySelection was chosen as the action due to mouse movement, // it will have been cleared. At this point Toggle is the desired action. // An active StringPairDrag will prevent this method from being called, - // so a real CopySelection would not have occurred. - if( m_action == CopySelection || - ( m_action == ToggleSelected && mouseMovedDistance( me, 2 ) == false ) ) + // so a real Action::CopySelection would not have occurred. + if( m_action == Action::CopySelection || + ( m_action == Action::ToggleSelected && mouseMovedDistance( me, 2 ) == false ) ) { setSelected( !isSelected() ); } - else if( m_action == Move || m_action == Resize || m_action == ResizeLeft ) + else if( m_action == Action::Move || m_action == Action::Resize || m_action == Action::ResizeLeft ) { // TODO: Fix m_clip->setJournalling() consistency m_clip->setJournalling( true ); } - else if( m_action == Split ) + else if( m_action == Action::Split ) { const float ppb = m_trackView->trackContainerView()->pixelsPerBar(); const TimePos relPos = me->pos().x() * TimePos::ticksPerBar() / ppb; @@ -1018,7 +1030,7 @@ void ClipView::mouseReleaseEvent( QMouseEvent * me ) ); } - m_action = NoAction; + m_action = Action::None; delete m_hint; m_hint = nullptr; s_textFloat->hide(); @@ -1058,7 +1070,7 @@ void ClipView::contextMenuEvent( QContextMenuEvent * cme ) individualClip ? tr("Delete (middle mousebutton)") : tr("Delete selection (middle mousebutton)"), - [this](){ contextMenuAction( Remove ); } ); + [this](){ contextMenuAction( ContextMenuAction::Remove ); } ); contextMenu.addSeparator(); @@ -1067,14 +1079,14 @@ void ClipView::contextMenuEvent( QContextMenuEvent * cme ) individualClip ? tr("Cut") : tr("Cut selection"), - [this](){ contextMenuAction( Cut ); } ); + [this](){ contextMenuAction( ContextMenuAction::Cut ); } ); if (canMergeSelection(selectedClips)) { contextMenu.addAction( embed::getIconPixmap("edit_merge"), tr("Merge Selection"), - [this]() { contextMenuAction(Merge); } + [this]() { contextMenuAction(ContextMenuAction::Merge); } ); } } @@ -1084,12 +1096,12 @@ void ClipView::contextMenuEvent( QContextMenuEvent * cme ) individualClip ? tr("Copy") : tr("Copy selection"), - [this](){ contextMenuAction( Copy ); } ); + [this](){ contextMenuAction( ContextMenuAction::Copy ); } ); contextMenu.addAction( embed::getIconPixmap( "edit_paste" ), tr( "Paste" ), - [this](){ contextMenuAction( Paste ); } ); + [this](){ contextMenuAction( ContextMenuAction::Paste ); } ); contextMenu.addSeparator(); @@ -1098,7 +1110,7 @@ void ClipView::contextMenuEvent( QContextMenuEvent * cme ) (individualClip ? tr("Mute/unmute (<%1> + middle click)") : tr("Mute/unmute selection (<%1> + middle click)")).arg(UI_CTRL_KEY), - [this](){ contextMenuAction( Mute ); } ); + [this](){ contextMenuAction( ContextMenuAction::Mute ); } ); contextMenu.addSeparator(); @@ -1122,22 +1134,22 @@ void ClipView::contextMenuAction( ContextMenuAction action ) switch( action ) { - case Remove: + case ContextMenuAction::Remove: remove( active ); break; - case Cut: + case ContextMenuAction::Cut: cut( active ); break; - case Copy: + case ContextMenuAction::Copy: copy( active ); break; - case Paste: + case ContextMenuAction::Paste: paste(); break; - case Mute: + case ContextMenuAction::Mute: toggleMute( active ); break; - case Merge: + case ContextMenuAction::Merge: mergeClips(active); break; } @@ -1184,7 +1196,7 @@ void ClipView::copy( QVector clipvs ) DataFile dataFile = createClipDataFiles( clipvs ); // Copy the Clip type as a key and the Clip data file to the clipboard - copyStringPair( QString( "clip_%1" ).arg( m_clip->getTrack()->type() ), + copyStringPair( QString( "clip_%1" ).arg( static_cast(m_clip->getTrack()->type()) ), dataFile.toString() ); } @@ -1287,7 +1299,7 @@ void ClipView::mergeClips(QVector clipvs) continue; } - NoteVector currentClipNotes = mcView->getMidiClip()->notes(); + const NoteVector& currentClipNotes = mcView->getMidiClip()->notes(); TimePos mcViewPos = mcView->getMidiClip()->startPosition(); for (Note* note: currentClipNotes) @@ -1460,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; @@ -1475,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 ) ) @@ -1485,7 +1493,7 @@ QColor ClipView::getColorForDisplay( QColor defaultColor ) { if( muted ) { - c = m_clip->hasColor() + c = hasCustomColor() ? mutedCustomColor.darker( 250 ) : mutedBackgroundColor(); } @@ -1499,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 e3ef9fd20..0a6fece31 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(""); } @@ -204,7 +192,7 @@ void MidiClipView::transposeSelection() void MidiClipView::constructContextMenu( QMenu * _cm ) { - bool isBeat = m_clip->type() == MidiClip::BeatClip; + bool isBeat = m_clip->type() == MidiClip::Type::BeatClip; auto a = new QAction(embed::getIconPixmap("piano"), tr("Open in piano-roll"), _cm); _cm->insertAction( _cm->actions()[0], a ); @@ -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::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 @@ -311,7 +304,7 @@ void MidiClipView::mouseDoubleClickEvent(QMouseEvent *_me) _me->ignore(); return; } - if( m_clip->m_clipType == MidiClip::MelodyClip || !fixedClips() ) + if( m_clip->m_clipType == MidiClip::Type::MelodyClip || !fixedClips() ) { openInPianoRoll(); } @@ -322,9 +315,9 @@ void MidiClipView::mouseDoubleClickEvent(QMouseEvent *_me) void MidiClipView::wheelEvent(QWheelEvent * we) { - if(m_clip->m_clipType == MidiClip::BeatClip && + 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... @@ -400,7 +393,7 @@ void MidiClipView::paintEvent( QPaintEvent * ) QColor c; bool const muted = m_clip->getTrack()->isMuted() || m_clip->isMuted(); bool current = getGUI()->pianoRoll()->currentMidiClip() == m_clip; - bool beatClip = m_clip->m_clipType == MidiClip::BeatClip; + bool beatClip = m_clip->m_clipType == MidiClip::Type::BeatClip; if( beatClip ) { @@ -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::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. @@ -523,9 +577,10 @@ void MidiClipView::paintEvent( QPaintEvent * ) p.scale(width(), height() - distanceToTop - 2 * notesBorder); // set colour based on mute status - QColor noteFillColor = muted ? getMutedNoteFillColor() : getNoteFillColor(); + QColor noteFillColor = muted ? getMutedNoteFillColor().lighter(200) + : (c.lightness() > 175 ? getNoteFillColor().darker(400) : getNoteFillColor()); QColor noteBorderColor = muted ? getMutedNoteBorderColor() - : ( m_clip->hasColor() ? c.lighter( 200 ) : getNoteBorderColor() ); + : (hasCustomColor() ? c.lighter(200) : getNoteBorderColor()); bool const drawAsLines = height() < 64; if (drawAsLines) @@ -573,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/PatternClipView.cpp b/src/gui/clips/PatternClipView.cpp index 477a90052..bf12440c7 100644 --- a/src/gui/clips/PatternClipView.cpp +++ b/src/gui/clips/PatternClipView.cpp @@ -115,14 +115,18 @@ void PatternClipView::paintEvent(QPaintEvent*) // bar lines const int lineSize = 3; + int pixelsPerPattern = Engine::patternStore()->lengthOfPattern(m_patternClip->patternIndex()) * pixelsPerBar(); + int offset = static_cast(m_patternClip->startTimeOffset() * (pixelsPerBar() / TimePos::ticksPerBar())) + % pixelsPerPattern; + if (offset < 2) { + offset += pixelsPerPattern; + } + p.setPen( c.darker( 200 ) ); - bar_t t = Engine::patternStore()->lengthOfPattern(m_patternClip->patternIndex()); - if (m_patternClip->length() > TimePos::ticksPerBar() && t > 0) + if (pixelsPerPattern > 0) { - for( int x = static_cast( t * pixelsPerBar() ); - x < width() - 2; - x += static_cast( t * pixelsPerBar() ) ) + for (int x = offset; x < width() - 2; x += pixelsPerPattern) { p.drawLine( x, BORDER_WIDTH, x, BORDER_WIDTH + lineSize ); p.drawLine( x, rect().bottom() - ( BORDER_WIDTH + lineSize ), diff --git a/src/gui/clips/SampleClipView.cpp b/src/gui/clips/SampleClipView.cpp index e21a7e30b..ef46325e4 100644 --- a/src/gui/clips/SampleClipView.cpp +++ b/src/gui/clips/SampleClipView.cpp @@ -28,10 +28,13 @@ #include #include +#include "GuiApplication.h" +#include "AutomationEditor.h" #include "embed.h" #include "PathUtil.h" -#include "SampleBuffer.h" #include "SampleClip.h" +#include "SampleLoader.h" +#include "SampleWaveform.h" #include "Song.h" #include "StringPairDrag.h" @@ -60,9 +63,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") + ); } @@ -83,6 +88,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()) + ); } @@ -112,8 +123,7 @@ 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(); @@ -171,12 +181,12 @@ void SampleClipView::mouseReleaseEvent(QMouseEvent *_me) void SampleClipView::mouseDoubleClickEvent( QMouseEvent * ) { - QString af = m_clip->m_sampleBuffer->openAudioFile(); + QString af = SampleLoader::openAudioFile(); if ( af.isEmpty() ) {} //Don't do anything if no file is loaded - else if ( af == m_clip->m_sampleBuffer->audioFile() ) + else if (af == m_clip->m_sample.sampleFile()) { //Instead of reloading the existing file, just reset the size - int length = (int) ( m_clip->m_sampleBuffer->frames() / Engine::framesPerTick() ); + int length = static_cast(m_clip->m_sample.sampleSize() / Engine::framesPerTick()); m_clip->changeLength(length); } else @@ -231,11 +241,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); @@ -263,9 +269,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 @@ -318,13 +327,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 db56557a4..46521b3f0 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,28 @@ #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_toolMove = nullptr; -QPixmap * AutomationEditor::s_toolYFlip = nullptr; -QPixmap * AutomationEditor::s_toolXFlip = nullptr; - -const QVector AutomationEditor::m_zoomXLevels = +const std::array AutomationEditor::m_zoomXLevels = { 0.125f, 0.25f, 0.5f, 1.0f, 2.0f, 4.0f, 8.0f }; @@ -90,13 +87,13 @@ AutomationEditor::AutomationEditor() : m_bottomLevel( 0 ), m_topLevel( 0 ), m_currentPosition(), - m_action( NONE ), + m_action( Action::None ), m_drawLastLevel( 0.0f ), m_drawLastTick( 0 ), m_ppb( DEFAULT_PPB ), m_y_delta( DEFAULT_Y_DELTA ), m_y_auto( true ), - m_editMode( DRAW ), + m_editMode( EditMode::Draw ), m_mouseDownLeft(false), m_mouseDownRight( false ), m_scrollBack( false ), @@ -106,9 +103,11 @@ AutomationEditor::AutomationEditor() : m_graphColor(Qt::SolidPattern), m_nodeInValueColor(0, 0, 0), m_nodeOutValueColor(0, 0, 0), + m_nodeTangentLineColor(0, 0, 0), m_scaleColor(Qt::SolidPattern), m_crossColor(0, 0, 0), - m_backgroundShade(0, 0, 0) + m_backgroundShade(0, 0, 0), + m_ghostNoteColor(0, 0, 0) { connect( this, SIGNAL(currentClipChanged()), this, SLOT(updateAfterClipChange()), @@ -133,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::Mode_PlayAutomationClip ), - m_currentPosition, - Song::Mode_PlayAutomationClip, 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& ) ) ); @@ -167,24 +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_toolMove == nullptr) - { - s_toolMove = new QPixmap(embed::getIconPixmap("edit_move")); - } - setCurrentClip(nullptr); setMouseTracking( true ); @@ -351,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() ) { @@ -363,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 ); } @@ -470,6 +417,17 @@ void AutomationEditor::mousePressEvent( QMouseEvent* mouseEvent ) Engine::getSong()->setModified(); } }; + auto resetTangent = [this](timeMap::iterator node) + { + if (node != m_clip->getTimeMap().end()) + { + // Unlock the tangents from that node + node.value().setLockedTangents(false); + // Recalculate the tangents + m_clip->generateTangents(node, 1); + Engine::getSong()->setModified(); + } + }; // If we clicked inside the AutomationEditor viewport (where the nodes are represented) if (mouseEvent->y() > TOP_MARGIN && mouseEvent->x() >= VALUES_WIDTH) @@ -492,15 +450,15 @@ void AutomationEditor::mousePressEvent( QMouseEvent* mouseEvent ) // a node, while others require that we know if we clicked the outValue // of a node. bool editingOutValue = ( - m_editMode == DRAW_OUTVALUES - || (m_editMode == ERASE && m_mouseDownRight) + m_editMode == EditMode::DrawOutValues + || (m_editMode == EditMode::Erase && m_mouseDownRight) ); timeMap::iterator clickedNode = getNodeAt(mouseEvent->x(), mouseEvent->y(), editingOutValue); switch (m_editMode) { - case DRAW: + case EditMode::Draw: { m_clip->addJournalCheckPoint(); @@ -518,7 +476,7 @@ void AutomationEditor::mousePressEvent( QMouseEvent* mouseEvent ) m_drawLastLevel = level; // Changes the action to drawing a line of nodes - m_action = DRAW_LINE; + m_action = Action::DrawLine; } else // No shift, we are just creating/moving nodes { @@ -531,7 +489,7 @@ void AutomationEditor::mousePressEvent( QMouseEvent* mouseEvent ) : POS(clickedNode) ), level, - true, + clickedNode == tm.end(), mouseEvent->modifiers() & Qt::ControlModifier ); @@ -540,8 +498,8 @@ void AutomationEditor::mousePressEvent( QMouseEvent* mouseEvent ) // is being dragged, so if we don't update it we have a bogus iterator clickedNode = tm.find(newTime); - // Set the action to MOVE_VALUE so moveMouseEvent() knows we are moving a node - m_action = MOVE_VALUE; + // Set the action to Action::MoveValue so moveMouseEvent() knows we are moving a node + m_action = Action::MoveValue; // Calculate the offset from the place the mouse click happened in comparison // to the center of the node @@ -559,7 +517,7 @@ void AutomationEditor::mousePressEvent( QMouseEvent* mouseEvent ) Engine::getSong()->setModified(); } - else if (m_mouseDownRight) // Right click on DRAW mode erases values + else if (m_mouseDownRight) // Right click on EditMode::Draw mode erases values { // Update the last clicked position so we remove all nodes from // that point up to the point we release the mouse button @@ -568,11 +526,11 @@ void AutomationEditor::mousePressEvent( QMouseEvent* mouseEvent ) // If we right-clicked a node, remove it eraseNode(clickedNode); - m_action = ERASE_VALUES; + m_action = Action::EraseValues; } break; } - case ERASE: + case EditMode::Erase: { m_clip->addJournalCheckPoint(); @@ -586,7 +544,7 @@ void AutomationEditor::mousePressEvent( QMouseEvent* mouseEvent ) // If we right-clicked a node, remove it eraseNode(clickedNode); - m_action = ERASE_VALUES; + m_action = Action::EraseValues; } else if (m_mouseDownRight) // And right click resets outValues { @@ -597,11 +555,11 @@ void AutomationEditor::mousePressEvent( QMouseEvent* mouseEvent ) // that point up to the point we release the mouse button m_drawLastTick = posTicks; - m_action = RESET_OUTVALUES; + m_action = Action::ResetOutValues; } break; } - case DRAW_OUTVALUES: + case EditMode::DrawOutValues: { m_clip->addJournalCheckPoint(); @@ -615,7 +573,7 @@ void AutomationEditor::mousePressEvent( QMouseEvent* mouseEvent ) clickedNode.value().setOutValue(level); - m_action = MOVE_OUTVALUE; + m_action = Action::MoveOutValue; Engine::getSong()->setModified(); } @@ -635,7 +593,7 @@ void AutomationEditor::mousePressEvent( QMouseEvent* mouseEvent ) m_draggedOutValueKey = POS(clickedNode); clickedNode.value().setOutValue(level); - m_action = MOVE_OUTVALUE; + m_action = Action::MoveOutValue; Engine::getSong()->setModified(); } @@ -650,7 +608,48 @@ void AutomationEditor::mousePressEvent( QMouseEvent* mouseEvent ) // that point up to the point we release the mouse button m_drawLastTick = posTicks; - m_action = RESET_OUTVALUES; + m_action = Action::ResetOutValues; + } + break; + } + case EditMode::EditTangents: + { + if (!m_clip->canEditTangents()) + { + update(); + return; + } + + m_clip->addJournalCheckPoint(); + + // Gets the closest node to the mouse click + timeMap::iterator node = getClosestNode(mouseEvent->x()); + + // Starts dragging a tangent + if (m_mouseDownLeft && node != tm.end()) + { + // Lock the tangents from that node, so it can only be + // manually edited + node.value().setLockedTangents(true); + + m_draggedTangentTick = POS(node); + + // Are we dragging the out or in tangent? + m_draggedOutTangent = posTicks >= m_draggedTangentTick; + + m_action = Action::MoveTangent; + } + // Resets node's tangent + else if (m_mouseDownRight) + { + // Resets tangent from node + resetTangent(node); + + // Update the last clicked position so we reset all tangents from + // that point up to the point we release the mouse button + m_drawLastTick = posTicks; + + m_action = Action::ResetTangents; } break; } @@ -671,13 +670,13 @@ void AutomationEditor::mouseDoubleClickEvent(QMouseEvent * mouseEvent) if (mouseEvent->y() <= TOP_MARGIN || mouseEvent->x() < VALUES_WIDTH) { return; } // Are we fine tuning the inValue or outValue? - const bool isOutVal = (m_editMode == DRAW_OUTVALUES); + const bool isOutVal = (m_editMode == EditMode::DrawOutValues); timeMap::iterator clickedNode = getNodeAt(mouseEvent->x(), mouseEvent->y(), isOutVal); switch (m_editMode) { - case DRAW: - case DRAW_OUTVALUES: + case EditMode::Draw: + case EditMode::DrawOutValues: if (fineTuneValue(clickedNode, isOutVal)) { update(); } break; default: @@ -703,9 +702,9 @@ void AutomationEditor::mouseReleaseEvent(QMouseEvent * mouseEvent ) mustRepaint = true; } - if (m_editMode == DRAW) + if (m_editMode == EditMode::Draw) { - if (m_action == MOVE_VALUE) + if (m_action == Action::MoveValue) { // Actually apply the value of the node being dragged m_clip->applyDragValue(); @@ -714,7 +713,7 @@ void AutomationEditor::mouseReleaseEvent(QMouseEvent * mouseEvent ) QApplication::restoreOverrideCursor(); } - m_action = NONE; + m_action = Action::None; if (mustRepaint) { repaint(); } } @@ -742,12 +741,12 @@ void AutomationEditor::mouseMoveEvent(QMouseEvent * mouseEvent ) switch (m_editMode) { - case DRAW: + case EditMode::Draw: { // We are dragging a node if (m_mouseDownLeft) { - if (m_action == MOVE_VALUE) + if (m_action == Action::MoveValue) { // When we clicked the node, we might have clicked slightly off // so we account for that offset for a smooth drag @@ -770,7 +769,7 @@ void AutomationEditor::mouseMoveEvent(QMouseEvent * mouseEvent ) Engine::getSong()->setModified(); } - /* else if (m_action == DRAW_LINE) + /* else if (m_action == Action::DrawLine) { // We are drawing a line. For now do nothing (as before), but later logic // could be added here so the line is updated according to the new mouse position @@ -779,7 +778,7 @@ void AutomationEditor::mouseMoveEvent(QMouseEvent * mouseEvent ) } else if (m_mouseDownRight) // We are removing nodes { - if (m_action == ERASE_VALUES) + if (m_action == Action::EraseValues) { // If we moved the mouse past the beginning correct the position in ticks posTicks = qMax(posTicks, 0); @@ -794,7 +793,7 @@ void AutomationEditor::mouseMoveEvent(QMouseEvent * mouseEvent ) } break; } - case ERASE: + case EditMode::Erase: { // If we moved the mouse past the beginning correct the position in ticks posTicks = qMax(posTicks, 0); @@ -802,7 +801,7 @@ void AutomationEditor::mouseMoveEvent(QMouseEvent * mouseEvent ) // Left button removes nodes if (m_mouseDownLeft) { - if (m_action == ERASE_VALUES) + if (m_action == Action::EraseValues) { // Removing automation nodes @@ -814,7 +813,7 @@ void AutomationEditor::mouseMoveEvent(QMouseEvent * mouseEvent ) } else if (m_mouseDownRight) // Right button resets outValues { - if (m_action == RESET_OUTVALUES) + if (m_action == Action::ResetOutValues) { // Reseting outValues @@ -826,7 +825,7 @@ void AutomationEditor::mouseMoveEvent(QMouseEvent * mouseEvent ) } break; } - case DRAW_OUTVALUES: + case EditMode::DrawOutValues: { // If we moved the mouse past the beginning correct the position in ticks posTicks = qMax(posTicks, 0); @@ -834,7 +833,7 @@ void AutomationEditor::mouseMoveEvent(QMouseEvent * mouseEvent ) // Left button moves outValues if (m_mouseDownLeft) { - if (m_action == MOVE_OUTVALUE) + if (m_action == Action::MoveOutValue) { // We are moving the outValue of the node timeMap & tm = m_clip->getTimeMap(); @@ -850,7 +849,7 @@ void AutomationEditor::mouseMoveEvent(QMouseEvent * mouseEvent ) } else if (m_mouseDownRight) // Right button resets them { - if (m_action == RESET_OUTVALUES) + if (m_action == Action::ResetOutValues) { // Reseting outValues @@ -862,6 +861,51 @@ void AutomationEditor::mouseMoveEvent(QMouseEvent * mouseEvent ) } break; } + case EditMode::EditTangents: + { + // If we moved the mouse past the beginning correct the position in ticks + posTicks = std::max(posTicks, 0); + + if (m_mouseDownLeft && m_action == Action::MoveTangent) + { + timeMap& tm = m_clip->getTimeMap(); + auto it = tm.find(m_draggedTangentTick); + + // Safety check + if (it == tm.end()) + { + update(); + return; + } + + // Calculate new tangent + float y = m_draggedOutTangent + ? yCoordOfLevel(OUTVAL(it)) + : yCoordOfLevel(INVAL(it)); + float dy = m_draggedOutTangent + ? y - mouseEvent->y() + : mouseEvent->y() - y; + float dx = std::abs(posTicks - POS(it)); + float newTangent = dy / std::max(dx, 1.0f); + + if (m_draggedOutTangent) + { + it.value().setOutTangent(newTangent); + } + else + { + it.value().setInTangent(newTangent); + } + } + else if (m_mouseDownRight && m_action == Action::ResetTangents) + { + // Resets all tangents from the last clicked tick up to the current position tick + m_clip->resetTangents(m_drawLastTick, posTicks); + + Engine::getSong()->setModified(); + } + break; + } } } else // If the mouse Y position is above the AutomationEditor viewport @@ -913,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 @@ -922,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); @@ -937,6 +980,53 @@ inline void AutomationEditor::drawAutomationPoint(QPainter & p, timeMap::iterato +inline void AutomationEditor::drawAutomationTangents(QPainter& p, timeMap::iterator it) +{ + int x = xCoordOfTick(POS(it)); + + // The tangent value correlates the variation in the node value related to the increase + // in ticks. So to have a proportionate drawing of the tangent line, we need to find the + // relation between the number of pixels per tick and the number of pixels per value level. + float viewportHeight = (height() - SCROLLBAR_SIZE - 1) - TOP_MARGIN; + float pixelsPerTick = m_ppb / TimePos::ticksPerBar(); + // std::abs just in case the topLevel is smaller than the bottomLevel for some reason + float pixelsPerLevel = std::abs(viewportHeight / (m_topLevel - m_bottomLevel)); + float proportion = pixelsPerLevel / pixelsPerTick; + + p.setPen(QPen(m_nodeTangentLineColor)); + p.setBrush(QBrush(m_nodeTangentLineColor)); + + 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); + + p.setBrush(QBrush(m_nodeTangentLineColor)); + + y = yCoordOfLevel(OUTVAL(it)); + tx = x + 20; + ty = y - 20 * OUTTAN(it) * proportion; + p.drawLine(x, y, tx, ty); + p.setBrush(QBrush(m_nodeTangentLineColor.darker(200))); + p.drawEllipse(tx - 3, ty - 3, 6, 6); +} + +void AutomationEditor::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 ) { QStyleOption opt; @@ -950,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; @@ -987,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 ); @@ -1002,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, @@ -1025,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 ); @@ -1049,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 ); @@ -1073,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); @@ -1112,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 ); @@ -1171,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::DiscreteProgression ) - { - 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; @@ -1197,6 +1352,11 @@ void AutomationEditor::paintEvent(QPaintEvent * pe ) // Draw circle drawAutomationPoint(p, it); + // Draw tangents if necessary (only for manually edited tangents) + if (m_clip->canEditTangents() && LOCKEDTAN(it)) + { + drawAutomationTangents(p, it); + } ++it; } @@ -1213,20 +1373,25 @@ void AutomationEditor::paintEvent(QPaintEvent * pe ) } // Draw circle(the last one) drawAutomationPoint(p, it); + // Draw tangents if necessary (only for manually edited tangents) + if (m_clip->canEditTangents() && LOCKEDTAN(it)) + { + drawAutomationTangents(p, it); + } } } else { - 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 @@ -1248,23 +1413,28 @@ void AutomationEditor::paintEvent(QPaintEvent * pe ) // draw current edit-mode-icon below the cursor switch( m_editMode ) { - case DRAW: + case EditMode::Draw: { - if (m_action == ERASE_VALUES) { cursor = s_toolErase; } - else if (m_action == MOVE_VALUE) { 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 ERASE: + case EditMode::Erase: { - cursor = s_toolErase; + cursor = &m_toolErase; break; } - case DRAW_OUTVALUES: + case EditMode::DrawOutValues: { - if (m_action == RESET_OUTVALUES) { cursor = s_toolErase; } - else if (m_action == MOVE_OUTVALUE) { 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 ? &m_toolMove : &m_toolEditTangents; break; } } @@ -1314,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 @@ -1398,11 +1554,7 @@ void AutomationEditor::resizeEvent(QResizeEvent * re) } centerTopBottomScroll(); - if( Engine::getSong() ) - { - Engine::getSong()->getPlayPos( Song::Mode_PlayAutomationClip - ).m_timeLine->setFixedWidth( width() ); - } + m_timeLine->setFixedWidth(width()); updateTopBottomLevels(); update(); @@ -1525,7 +1677,7 @@ void AutomationEditor::play() if( !m_clip->getTrack() ) { - if( Engine::getSong()->playMode() != Song::Mode_PlayMidiClip ) + if( Engine::getSong()->playMode() != Song::PlayMode::MidiClip ) { Engine::getSong()->stop(); Engine::getSong()->playMidiClip( getGUI()->pianoRoll()->currentMidiClip() ); @@ -1599,7 +1751,7 @@ void AutomationEditor::verScrolled(int new_pos ) -void AutomationEditor::setEditMode(AutomationEditor::EditModes mode) +void AutomationEditor::setEditMode(AutomationEditor::EditMode mode) { if (m_editMode == mode) return; @@ -1614,13 +1766,13 @@ void AutomationEditor::setEditMode(AutomationEditor::EditModes mode) void AutomationEditor::setEditMode(int mode) { - setEditMode((AutomationEditor::EditModes) mode); + setEditMode((AutomationEditor::EditMode) mode); } -void AutomationEditor::setProgressionType(AutomationClip::ProgressionTypes type) +void AutomationEditor::setProgressionType(AutomationClip::ProgressionType type) { if (validClip()) { @@ -1633,7 +1785,7 @@ void AutomationEditor::setProgressionType(AutomationClip::ProgressionTypes type) void AutomationEditor::setProgressionType(int type) { - setProgressionType((AutomationClip::ProgressionTypes) type); + setProgressionType((AutomationClip::ProgressionType) type); } @@ -1655,7 +1807,7 @@ void AutomationEditor::updatePosition(const TimePos & t ) { if( ( Engine::getSong()->isPlaying() && Engine::getSong()->playMode() == - Song::Mode_PlayAutomationClip ) || + Song::PlayMode::AutomationClip ) || m_scrollBack == true ) { const int w = width() - VALUES_WIDTH; @@ -1819,6 +1971,49 @@ AutomationEditor::timeMap::iterator AutomationEditor::getNodeAt(int x, int y, bo return tm.end(); } +AutomationEditor::timeMap::iterator AutomationEditor::getClosestNode(int x) +{ + // Remove the VALUES_WIDTH from the x position, so we have the actual viewport x + x -= VALUES_WIDTH; + // Convert the x position to the position in ticks + int posTicks = (x * TimePos::ticksPerBar() / m_ppb) + m_currentPosition; + + // Get our pattern timeMap and create a iterator so we can check the nodes + timeMap& tm = m_clip->getTimeMap(); + + if (tm.isEmpty()) { return tm.end(); } + + // Get the node with an equal or higher position + auto it = tm.lowerBound(posTicks); + + // If there are no nodes equal or higher than the position return + // the one before it + if (it == tm.end()) + { + --it; + return it; + } + // If the node returned is the first, return it + else if (it == tm.begin()) + { + return it; + } + // Else return the closest node + else + { + // Distance from node to the right + int distanceRight = std::abs(POS(it) - posTicks); + // Distance from node to the left + int distanceLeft = std::abs(POS(--it) - posTicks); + + if (distanceLeft >= distanceRight) + { + ++it; + } + return it; + } +} + @@ -1839,24 +2034,29 @@ AutomationEditorWindow::AutomationEditorWindow() : DropToolBar *editActionsToolBar = addDropToolBarToTop(tr("Edit actions")); auto editModeGroup = new ActionGroup(this); - QAction* drawAction = editModeGroup->addAction(embed::getIconPixmap("edit_draw"), tr("Draw mode (Shift+D)")); - drawAction->setShortcut(Qt::SHIFT | Qt::Key_D); - drawAction->setChecked(true); + m_drawAction = editModeGroup->addAction(embed::getIconPixmap("edit_draw"), tr("Draw mode (Shift+D)")); + m_drawAction->setShortcut(Qt::SHIFT | Qt::Key_D); + m_drawAction->setChecked(true); - QAction* eraseAction = editModeGroup->addAction(embed::getIconPixmap("edit_erase"), tr("Erase mode (Shift+E)")); - eraseAction->setShortcut(Qt::SHIFT | Qt::Key_E); + m_eraseAction = editModeGroup->addAction(embed::getIconPixmap("edit_erase"), tr("Erase mode (Shift+E)")); + m_eraseAction->setShortcut(Qt::SHIFT | Qt::Key_E); - QAction* drawOutAction = editModeGroup->addAction(embed::getIconPixmap("edit_draw_outvalue"), tr("Draw outValues mode (Shift+C)")); - drawOutAction->setShortcut(Qt::SHIFT | Qt::Key_C); + m_drawOutAction = editModeGroup->addAction(embed::getIconPixmap("edit_draw_outvalue"), tr("Draw outValues mode (Shift+C)")); + m_drawOutAction->setShortcut(Qt::SHIFT | Qt::Key_C); + + m_editTanAction = editModeGroup->addAction(embed::getIconPixmap("edit_tangent"), tr("Edit tangents mode (Shift+T)")); + m_editTanAction->setShortcut(Qt::SHIFT | Qt::Key_T); + m_editTanAction->setEnabled(false); m_flipYAction = new QAction(embed::getIconPixmap("flip_y"), tr("Flip vertically"), this); m_flipXAction = new QAction(embed::getIconPixmap("flip_x"), tr("Flip horizontally"), this); connect(editModeGroup, SIGNAL(triggered(int)), m_editor, SLOT(setEditMode(int))); - editActionsToolBar->addAction(drawAction); - editActionsToolBar->addAction(eraseAction); - editActionsToolBar->addAction(drawOutAction); + editActionsToolBar->addAction(m_drawAction); + editActionsToolBar->addAction(m_eraseAction); + editActionsToolBar->addAction(m_drawOutAction); + editActionsToolBar->addAction(m_editTanAction); editActionsToolBar->addAction(m_flipXAction); editActionsToolBar->addAction(m_flipYAction); @@ -1874,10 +2074,10 @@ AutomationEditorWindow::AutomationEditorWindow() : m_cubicHermiteAction = progression_type_group->addAction( embed::getIconPixmap("progression_cubic_hermite"), tr( "Cubic Hermite progression")); - connect(progression_type_group, SIGNAL(triggered(int)), m_editor, SLOT(setProgressionType(int))); + connect(progression_type_group, SIGNAL(triggered(int)), this, SLOT(setProgressionType(int))); // setup tension-stuff - m_tensionKnob = new Knob( knobSmall_17, this, "Tension" ); + m_tensionKnob = new Knob( KnobType::Small17, this, "Tension" ); m_tensionKnob->setModel(m_editor->m_tensionModel); m_tensionKnob->setToolTip(tr("Tension value for spline")); @@ -1956,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 ); @@ -1989,15 +2199,15 @@ void AutomationEditorWindow::setCurrentClip(AutomationClip* clip) switch(m_editor->m_clip->progressionType()) { - case AutomationClip::DiscreteProgression: + case AutomationClip::ProgressionType::Discrete: m_discreteAction->setChecked(true); m_tensionKnob->setEnabled(false); break; - case AutomationClip::LinearProgression: + case AutomationClip::ProgressionType::Linear: m_linearAction->setChecked(true); m_tensionKnob->setEnabled(false); break; - case AutomationClip::CubicHermiteProgression: + case AutomationClip::ProgressionType::CubicHermite: m_cubicHermiteAction->setChecked(true); m_tensionKnob->setEnabled(true); break; @@ -2014,6 +2224,7 @@ void AutomationEditorWindow::setCurrentClip(AutomationClip* clip) connect(m_flipYAction, SIGNAL(triggered()), clip, SLOT(flipY())); } + updateEditTanButton(); emit currentClipChanged(); } @@ -2102,5 +2313,17 @@ void AutomationEditorWindow::updateWindowTitle() setWindowTitle( tr( "Automation Editor - %1" ).arg( m_editor->m_clip->name() ) ); } +void AutomationEditorWindow::setProgressionType(int progType) +{ + m_editor->setProgressionType(progType); + updateEditTanButton(); +} + +void AutomationEditorWindow::updateEditTanButton() +{ + auto progType = currentClip()->progressionType(); + m_editTanAction->setEnabled(AutomationClip::supportsTangentEditing(progType)); + if (!m_editTanAction->isEnabled() && m_editTanAction->isChecked()) { m_drawAction->trigger(); } +} } // namespace lmms::gui diff --git a/src/gui/editors/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/PatternEditor.cpp b/src/gui/editors/PatternEditor.cpp index b2d2f5c3f..5237690a7 100644 --- a/src/gui/editors/PatternEditor.cpp +++ b/src/gui/editors/PatternEditor.cpp @@ -69,11 +69,11 @@ void PatternEditor::cloneSteps() void PatternEditor::removeSteps() { - TrackContainer::TrackList tl = model()->tracks(); + const TrackContainer::TrackList& tl = model()->tracks(); for (const auto& track : tl) { - if (track->type() == Track::InstrumentTrack) + if (track->type() == Track::Type::Instrument) { auto p = static_cast(track->getClip(m_ps->currentPattern())); p->removeSteps(); @@ -86,7 +86,7 @@ void PatternEditor::removeSteps() void PatternEditor::addSampleTrack() { - (void) Track::create( Track::SampleTrack, model() ); + (void) Track::create( Track::Type::Sample, model() ); } @@ -94,7 +94,7 @@ void PatternEditor::addSampleTrack() void PatternEditor::addAutomationTrack() { - (void) Track::create( Track::AutomationTrack, model() ); + (void) Track::create( Track::Type::Automation, model() ); } @@ -176,11 +176,11 @@ void PatternEditor::updatePosition() void PatternEditor::makeSteps( bool clone ) { - TrackContainer::TrackList tl = model()->tracks(); + const TrackContainer::TrackList& tl = model()->tracks(); for (const auto& track : tl) { - if (track->type() == Track::InstrumentTrack) + if (track->type() == Track::Type::Instrument) { auto p = static_cast(track->getClip(m_ps->currentPattern())); if( clone ) @@ -306,7 +306,7 @@ QSize PatternEditorWindow::sizeHint() const void PatternEditorWindow::play() { - if (Engine::getSong()->playMode() != Song::Mode_PlayPattern) + if (Engine::getSong()->playMode() != Song::PlayMode::Pattern) { Engine::getSong()->playPattern(); } diff --git a/src/gui/editors/PianoRoll.cpp b/src/gui/editors/PianoRoll.cpp index 5f294d6e8..7d4a9552a 100644 --- a/src/gui/editors/PianoRoll.cpp +++ b/src/gui/editors/PianoRoll.cpp @@ -59,13 +59,13 @@ #include "DetuningHelper.h" #include "embed.h" #include "GuiApplication.h" -#include "gui_templates.h" #include "InstrumentTrack.h" #include "MainWindow.h" #include "MidiClip.h" #include "PatternStore.h" #include "PianoView.h" #include "PositionLine.h" +#include "SimpleTextFloat.h" #include "SongEditor.h" #include "StepRecorderWidget.h" #include "TextFloat.h" @@ -112,24 +112,18 @@ const int RESIZE_AREA_WIDTH = 9; const int NOTE_EDIT_LINE_WIDTH = 3; // key where to start -const int INITIAL_START_KEY = Key_C + Octave_4 * KeysPerOctave; +const int INITIAL_START_KEY = Octave::Octave_4 + Key::C; // number of each note to provide in quantization and note lengths const int NUM_EVEN_LENGTHS = 6; const int NUM_TRIPLET_LENGTHS = 5; +SimpleTextFloat * PianoRoll::s_textFloat = nullptr; - -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; - -TextFloat * PianoRoll::s_textFloat = nullptr; - -static std::array s_noteStrings {"C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"}; +static std::array s_noteStrings { + "C", "C\u266F / D\u266D", "D", "D\u266F / E\u266D", "E", "F", "F\u266F / G\u266D", + "G", "G\u266F / A\u266D", "A", "A\u266F / B\u266D", "B" +}; static QString getNoteString(int key) { @@ -137,25 +131,24 @@ static QString getNoteString(int key) } // used for drawing of piano -std::array PianoRoll::prKeyOrder +std::array PianoRoll::prKeyOrder { - PR_WHITE_KEY_SMALL, PR_BLACK_KEY, PR_WHITE_KEY_BIG, PR_BLACK_KEY, - PR_WHITE_KEY_SMALL, PR_WHITE_KEY_SMALL, PR_BLACK_KEY, PR_WHITE_KEY_BIG, - PR_BLACK_KEY, PR_WHITE_KEY_BIG, PR_BLACK_KEY, PR_WHITE_KEY_SMALL + KeyType::WhiteSmall, KeyType::Black, KeyType::WhiteBig, KeyType::Black, + KeyType::WhiteSmall, KeyType::WhiteSmall, KeyType::Black, KeyType::WhiteBig, + KeyType::Black, KeyType::WhiteBig, KeyType::Black, KeyType::WhiteSmall } ; const int DEFAULT_PR_PPB = DEFAULT_CELL_WIDTH * DefaultStepsPerBar; -const QVector PianoRoll::m_zoomLevels = +const std::vector PianoRoll::m_zoomLevels = {0.125f, 0.25f, 0.5f, 1.0f, 1.5f, 2.0f, 4.0f, 8.0f}; -const QVector PianoRoll::m_zoomYLevels = +const std::vector PianoRoll::m_zoomYLevels = {0.25f, 0.5f, 1.0f, 1.5f, 2.0f, 2.5f, 3.0f, 4.0f}; PianoRoll::PianoRoll() : - m_nemStr( QVector() ), m_noteEditMenu( nullptr ), m_semiToneMarkerMenu( nullptr ), m_zoomingModel(), @@ -167,9 +160,10 @@ PianoRoll::PianoRoll() : m_midiClip( nullptr ), m_currentPosition(), m_recording( false ), + m_doAutoQuantization(ConfigManager::inst()->value("midi", "autoquantize").toInt() != 0), m_currentNote( nullptr ), - m_action( ActionNone ), - m_noteEditMode( NoteEditVolume ), + m_action( Action::None ), + m_noteEditMode( NoteEditMode::Volume ), m_moveBoundaryLeft( 0 ), m_moveBoundaryTop( 0 ), m_moveBoundaryRight( 0 ), @@ -191,8 +185,8 @@ PianoRoll::PianoRoll() : m_minResizeLen( 0 ), m_startKey( INITIAL_START_KEY ), m_lastKey( 0 ), - m_editMode( ModeDraw ), - m_ctrlMode( ModeDraw ), + m_editMode( EditMode::Draw ), + m_ctrlMode( EditMode::Draw ), m_mouseDownRight( false ), m_scrollBack( false ), m_stepRecorderWidget(this, DEFAULT_PR_PPB, PR_TOP_MARGIN, PR_BOTTOM_MARGIN + m_notesEditHeight, WHITE_KEY_WIDTH, 0), @@ -241,12 +235,21 @@ PianoRoll::PianoRoll() : auto unmarkAllAction = new QAction(tr("Unmark all"), this); auto copyAllNotesAction = new QAction(tr("Select all notes on this key"), this); - connect( markSemitoneAction, &QAction::triggered, [this](){ markSemiTone(stmaMarkCurrentSemiTone); }); - connect( markAllOctaveSemitonesAction, &QAction::triggered, [this](){ markSemiTone(stmaMarkAllOctaveSemiTones); }); - connect( markScaleAction, &QAction::triggered, [this](){ markSemiTone(stmaMarkCurrentScale); }); - connect( markChordAction, &QAction::triggered, [this](){ markSemiTone(stmaMarkCurrentChord); }); - connect( unmarkAllAction, &QAction::triggered, [this](){ markSemiTone(stmaUnmarkAll); }); - connect( copyAllNotesAction, &QAction::triggered, [this](){ markSemiTone(stmaCopyAllNotesOnKey); }); + connect( markSemitoneAction, &QAction::triggered, [this](){ markSemiTone(SemiToneMarkerAction::MarkCurrentSemiTone); }); + connect( markAllOctaveSemitonesAction, &QAction::triggered, [this](){ markSemiTone(SemiToneMarkerAction::MarkAllOctaveSemiTones); }); + connect( markScaleAction, &QAction::triggered, [this](){ markSemiTone(SemiToneMarkerAction::MarkCurrentScale); }); + 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 ); @@ -261,48 +264,21 @@ 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 ) { - s_textFloat = new TextFloat; + s_textFloat = new SimpleTextFloat; } setAttribute( Qt::WA_OpaquePaintEvent, true ); // add time-line m_timeLine = new TimeLineWidget(m_whiteKeyWidth, 0, m_ppb, - Engine::getSong()->getPlayPos( - Song::Mode_PlayMidiClip ), - m_currentPosition, - Song::Mode_PlayMidiClip, 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& ) ) ); @@ -314,12 +290,9 @@ PianoRoll::PianoRoll() : this, SLOT( updatePositionStepRecording( const lmms::TimePos& ) ) ); // update timeline when in record-accompany mode - connect( Engine::getSong()->getPlayPos( Song::Mode_PlaySong ).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::Mode_PlayPattern ).m_timeLine, +/* connect( engine::getSong()->getPlayPos( Song::PlayMode::Pattern ).m_timeLine, SIGNAL( positionChanged( const lmms::TimePos& ) ), this, SLOT( updatePositionAccompany( const lmms::TimePos& ) ) );*/ @@ -405,7 +378,7 @@ PianoRoll::PianoRoll() : InstrumentFunctionNoteStacking::ChordTable::getInstance(); m_scaleModel.addItem( tr("No scale") ); - for( const InstrumentFunctionNoteStacking::Chord& chord : chord_table ) + for (const InstrumentFunctionNoteStacking::Chord& chord : chord_table.chords()) { if( chord.isScale() ) { @@ -422,7 +395,7 @@ PianoRoll::PianoRoll() : // Set up chord model m_chordModel.addItem( tr("No chord") ); - for( const InstrumentFunctionNoteStacking::Chord& chord : chord_table ) + for (const InstrumentFunctionNoteStacking::Chord& chord : chord_table.chords()) { if( ! chord.isScale() ) { @@ -524,7 +497,7 @@ void PianoRoll::changeNoteEditMode( int i ) } -void PianoRoll::markSemiTone(int i, bool fromMenu) +void PianoRoll::markSemiTone(SemiToneMarkerAction i, bool fromMenu) { const int key = fromMenu ? getKey(mapFromGlobal(m_semiToneMarkerMenu->pos()).y()) @@ -533,14 +506,14 @@ void PianoRoll::markSemiTone(int i, bool fromMenu) // if "No key" is selected, key is -1, unmark all semitones // or if scale changed from toolbar to "No scale", unmark all semitones - if (!fromMenu && (key < 0 || m_scaleModel.value() == 0)) { i = stmaUnmarkAll; } + if (!fromMenu && (key < 0 || m_scaleModel.value() == 0)) { i = SemiToneMarkerAction::UnmarkAll; } - switch( static_cast( i ) ) + switch(i) { - case stmaUnmarkAll: + case SemiToneMarkerAction::UnmarkAll: m_markedSemiTones.clear(); break; - case stmaMarkCurrentSemiTone: + case SemiToneMarkerAction::MarkCurrentSemiTone: { QList::iterator it = std::find( m_markedSemiTones.begin(), m_markedSemiTones.end(), key ); if( it != m_markedSemiTones.end() ) @@ -553,7 +526,7 @@ void PianoRoll::markSemiTone(int i, bool fromMenu) } break; } - case stmaMarkAllOctaveSemiTones: + case SemiToneMarkerAction::MarkAllOctaveSemiTones: { QList aok = getAllOctavesForKey(key); @@ -578,10 +551,10 @@ void PianoRoll::markSemiTone(int i, bool fromMenu) break; } - case stmaMarkCurrentScale: + case SemiToneMarkerAction::MarkCurrentScale: chord = & InstrumentFunctionNoteStacking::ChordTable::getInstance() .getScaleByName( m_scaleModel.currentText() ); - case stmaMarkCurrentChord: + case SemiToneMarkerAction::MarkCurrentChord: { if( ! chord ) { @@ -600,7 +573,7 @@ void PianoRoll::markSemiTone(int i, bool fromMenu) const int first = chord->isScale() ? 0 : key; const int last = chord->isScale() ? NumKeys : key + chord->last(); - const int cap = ( chord->isScale() || chord->last() == 0 ) ? KeysPerOctave : chord->last(); + const int cap = (chord->isScale() || chord->last() == 0) ? trackOctaveSize() : chord->last(); for( int i = first; i <= last; i++ ) { @@ -611,7 +584,7 @@ void PianoRoll::markSemiTone(int i, bool fromMenu) } break; } - case stmaCopyAllNotesOnKey: + case SemiToneMarkerAction::CopyAllNotesOnKey: { selectNotesOnKey(); break; @@ -741,10 +714,10 @@ void PianoRoll::fitNoteLengths(bool fill) { if (!hasValidMidiClip()) { return; } m_midiClip->addJournalCheckPoint(); + m_midiClip->rearrangeAllNotes(); // Reference notes - NoteVector refNotes = m_midiClip->notes(); - std::sort(refNotes.begin(), refNotes.end(), Note::lessThan); + const NoteVector& refNotes = m_midiClip->notes(); // Notes to edit NoteVector notes = getSelectedNotes(); @@ -762,7 +735,7 @@ void PianoRoll::fitNoteLengths(bool fill) } int length; - NoteVector::iterator ref = refNotes.begin(); + auto ref = refNotes.begin(); for (Note* note : notes) { // Fast forward to next reference note @@ -774,7 +747,7 @@ void PianoRoll::fitNoteLengths(bool fill) { if (!fill) { break; } // Last notes stretch to end of last bar - length = notes.last()->endPos().nextFullBar() * TimePos::ticksPerBar() - note->pos(); + length = notes.back()->endPos().nextFullBar() * TimePos::ticksPerBar() - note->pos(); } else { @@ -797,14 +770,11 @@ void PianoRoll::constrainNoteLengths(bool constrainMax) if (!hasValidMidiClip()) { return; } m_midiClip->addJournalCheckPoint(); - NoteVector notes = getSelectedNotes(); - if (notes.empty()) - { - notes = m_midiClip->notes(); - } + const NoteVector selectedNotes = getSelectedNotes(); + const auto& notes = selectedNotes.empty() ? m_midiClip->notes() : selectedNotes; - TimePos bound = m_lenOfNewNotes; // will be length of last note - for (Note* note : notes) + TimePos bound = m_lenOfNewNotes; // will be length of last note + for (auto note : notes) { if (constrainMax ? note->length() > bound : note->length() < bound) { @@ -858,8 +828,7 @@ void PianoRoll::setCurrentMidiClip( MidiClip* newMidiClip ) } // force the song-editor to stop playing if it played a MIDI clip before - if( Engine::getSong()->isPlaying() && - Engine::getSong()->playMode() == Song::Mode_PlayMidiClip ) + if (Engine::getSong()->playMode() == Song::PlayMode::MidiClip) { Engine::getSong()->playMidiClip( nullptr ); } @@ -938,6 +907,15 @@ void PianoRoll::hideMidiClip( MidiClip* clip ) } } + +int PianoRoll::trackOctaveSize() const +{ + if (!m_midiClip) { return KeysPerOctave; } + auto ut = m_midiClip->instrumentTrack()->microtuner(); + return ut->enabled() ? ut->octaveSize() : KeysPerOctave; +} + + void PianoRoll::selectRegionFromPixels( int xStart, int xEnd ) { @@ -1138,12 +1116,12 @@ void PianoRoll::drawDetuningInfo( QPainter & _p, const Note * _n, int _x, // node to the other switch (_n->detuning()->automationClip()->progressionType()) { - case AutomationClip::DiscreteProgression: + case AutomationClip::ProgressionType::Discrete: _p.drawLine(old_x, pre_y, cur_x, pre_y); _p.drawLine(cur_x, pre_y, cur_x, cur_y); break; - case AutomationClip::CubicHermiteProgression: /* TODO */ - case AutomationClip::LinearProgression: + case AutomationClip::ProgressionType::CubicHermite: /* TODO */ + case AutomationClip::ProgressionType::Linear: _p.drawLine(old_x, pre_y, cur_x, cur_y); break; } @@ -1199,11 +1177,11 @@ void PianoRoll::shiftSemiTone(int amount) //Shift notes by amount semitones auto selectedNotes = getSelectedNotes(); //If no notes are selected, shift all of them, otherwise shift selection - if (selectedNotes.empty()) { return shiftSemiTone(m_midiClip->notes(), amount); } - else { return shiftSemiTone(selectedNotes, amount); } + if (selectedNotes.empty()) { shiftSemiTone(m_midiClip->notes(), amount); } + else { shiftSemiTone(selectedNotes, amount); } } -void PianoRoll::shiftSemiTone(NoteVector notes, int amount) +void PianoRoll::shiftSemiTone(const NoteVector& notes, int amount) { m_midiClip->addJournalCheckPoint(); for (Note *note : notes) { note->setKey( note->key() + amount ); } @@ -1224,19 +1202,19 @@ void PianoRoll::shiftPos(int amount) //Shift notes pos by amount auto selectedNotes = getSelectedNotes(); //If no notes are selected, shift all of them, otherwise shift selection - if (selectedNotes.empty()) { return shiftPos(m_midiClip->notes(), amount); } - else { return shiftPos(selectedNotes, amount); } + if (selectedNotes.empty()) { shiftPos(m_midiClip->notes(), amount); } + else { shiftPos(selectedNotes, amount); } } -void PianoRoll::shiftPos(NoteVector notes, int amount) +void PianoRoll::shiftPos(const NoteVector& notes, int amount) { m_midiClip->addJournalCheckPoint(); - if (notes.isEmpty()) { + if (notes.empty()) { return; } - auto leftMostPos = notes.first()->pos(); + auto leftMostPos = notes.front()->pos(); //Limit leftwards shifts to prevent moving left of clip start auto shiftAmount = (leftMostPos > -amount) ? amount : -leftMostPos; if (shiftAmount == 0) { return; } @@ -1291,7 +1269,7 @@ void PianoRoll::keyPressEvent(QKeyEvent* ke) if( hasValidMidiClip() && ke->modifiers() == Qt::NoModifier ) { - const int key_num = PianoView::getKeyFromKeyEvent( ke ) + ( DefaultOctave - 1 ) * KeysPerOctave; + const int key_num = PianoView::getKeyFromKeyEvent( ke ); if (!ke->isAutoRepeat() && key_num > -1) { @@ -1308,7 +1286,7 @@ void PianoRoll::keyPressEvent(QKeyEvent* ke) case Qt::Key_Down: { int direction = (ke->key() == Qt::Key_Up ? +1 : -1); - if( ( ke->modifiers() & Qt::ControlModifier ) && m_action == ActionNone ) + if( ( ke->modifiers() & Qt::ControlModifier ) && m_action == Action::None ) { // shift selection by one octave // if nothing selected, shift _everything_ @@ -1326,7 +1304,7 @@ void PianoRoll::keyPressEvent(QKeyEvent* ke) } } } - else if((ke->modifiers() & Qt::ShiftModifier) && m_action == ActionNone) + else if((ke->modifiers() & Qt::ShiftModifier) && m_action == Action::None) { // Move selected notes by one semitone if (hasValidMidiClip()) @@ -1342,8 +1320,8 @@ void PianoRoll::keyPressEvent(QKeyEvent* ke) // if they are moving notes around or resizing, // recalculate the note/resize position - if( m_action == ActionMoveNote || - m_action == ActionResizeNote ) + if( m_action == Action::MoveNote || + m_action == Action::ResizeNote ) { dragNotes( m_lastMouseX, @@ -1362,7 +1340,7 @@ void PianoRoll::keyPressEvent(QKeyEvent* ke) case Qt::Key_Left: { int direction = (ke->key() == Qt::Key_Right ? +1 : -1); - if( ke->modifiers() & Qt::ControlModifier && m_action == ActionNone ) + if( ke->modifiers() & Qt::ControlModifier && m_action == Action::None ) { // Move selected notes by one bar to the left if (hasValidMidiClip()) @@ -1370,7 +1348,7 @@ void PianoRoll::keyPressEvent(QKeyEvent* ke) shiftPos( direction * TimePos::ticksPerBar() ); } } - else if( ke->modifiers() & Qt::ShiftModifier && m_action == ActionNone) + else if( ke->modifiers() & Qt::ShiftModifier && m_action == Action::None) { // move notes if (hasValidMidiClip()) @@ -1401,8 +1379,8 @@ void PianoRoll::keyPressEvent(QKeyEvent* ke) // if they are moving notes around or resizing, // recalculate the note/resize position - if( m_action == ActionMoveNote || - m_action == ActionResizeNote ) + if( m_action == Action::MoveNote || + m_action == Action::ResizeNote ) { dragNotes( m_lastMouseX, @@ -1438,7 +1416,7 @@ void PianoRoll::keyPressEvent(QKeyEvent* ke) case Qt::Key_Escape: // On the Knife mode, ESC cancels it - if (m_editMode == ModeEditKnife) + if (m_editMode == EditMode::Knife) { cancelKnifeAction(); } @@ -1493,7 +1471,7 @@ void PianoRoll::keyPressEvent(QKeyEvent* ke) case Qt::Key_Control: // Ctrl will not enter selection mode if we are // in Knife mode, but unquantize it - if (m_editMode == ModeEditKnife) + if (m_editMode == EditMode::Knife) { break; } @@ -1504,7 +1482,7 @@ void PianoRoll::keyPressEvent(QKeyEvent* ke) if ( !( ke->modifiers() & Qt::ShiftModifier ) && isActiveWindow() ) { m_ctrlMode = m_editMode; - m_editMode = ModeSelect; + m_editMode = EditMode::Select; setCursor( Qt::ArrowCursor ); ke->accept(); } @@ -1523,7 +1501,7 @@ void PianoRoll::keyReleaseEvent(QKeyEvent* ke ) { if( hasValidMidiClip() && ke->modifiers() == Qt::NoModifier ) { - const int key_num = PianoView::getKeyFromKeyEvent( ke ) + ( DefaultOctave - 1 ) * KeysPerOctave; + const int key_num = PianoView::getKeyFromKeyEvent( ke ); if (!ke->isAutoRepeat() && key_num > -1) { m_midiClip->instrumentTrack()->pianoModel()->handleKeyRelease(key_num); @@ -1536,7 +1514,7 @@ void PianoRoll::keyReleaseEvent(QKeyEvent* ke ) switch( ke->key() ) { case Qt::Key_Control: - if (m_editMode == ModeEditKnife) + if (m_editMode == EditMode::Knife) { break; } @@ -1629,14 +1607,14 @@ void PianoRoll::mousePressEvent(QMouseEvent * me ) } // -- Knife - if (m_editMode == ModeEditKnife && me->button() == Qt::LeftButton) + if (m_editMode == EditMode::Knife && me->button() == Qt::LeftButton) { NoteVector n; Note* note = noteUnderMouse(); if (note) { - n.append(note); + n.push_back(note); updateKnifePos(me); @@ -1648,7 +1626,7 @@ void PianoRoll::mousePressEvent(QMouseEvent * me ) return; } - if( m_editMode == ModeEditDetuning && noteUnderMouse() ) + if( m_editMode == EditMode::Detuning && noteUnderMouse() ) { static QPointer detuningClip = nullptr; if (detuningClip.data() != nullptr) @@ -1662,15 +1640,16 @@ 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; } // if holding control, go to selection mode unless shift is also pressed - if( me->modifiers() & Qt::ControlModifier && m_editMode != ModeSelect ) + if( me->modifiers() & Qt::ControlModifier && m_editMode != EditMode::Select ) { m_ctrlMode = m_editMode; - m_editMode = ModeSelect; + m_editMode = EditMode::Select; setCursor( Qt::ArrowCursor ); update(); } @@ -1686,7 +1665,7 @@ void PianoRoll::mousePressEvent(QMouseEvent * me ) me->y() > keyAreaBottom() && me->y() < noteEditTop()) { // resizing the note edit area - m_action = ActionResizeNoteEditArea; + m_action = Action::ResizeNoteEditArea; return; } @@ -1714,10 +1693,10 @@ void PianoRoll::mousePressEvent(QMouseEvent * me ) const NoteVector & notes = m_midiClip->notes(); // will be our iterator in the following loop - NoteVector::ConstIterator it = notes.begin()+notes.size()-1; + auto it = notes.rbegin(); // loop through whole note-vector... - for( int i = 0; i < notes.size(); ++i ) + while (it != notes.rend()) { Note *note = *it; TimePos len = note->length(); @@ -1742,7 +1721,7 @@ void PianoRoll::mousePressEvent(QMouseEvent * me ) { break; } - --it; + ++it; } // first check whether the user clicked in note-edit- @@ -1756,7 +1735,7 @@ void PianoRoll::mousePressEvent(QMouseEvent * me ) } // left button?? else if( me->button() == Qt::LeftButton && - m_editMode == ModeDraw ) + m_editMode == EditMode::Draw ) { // whether this action creates new note(s) or not bool is_new_note = false; @@ -1764,7 +1743,7 @@ void PianoRoll::mousePressEvent(QMouseEvent * me ) Note * created_new_note = nullptr; // did it reach end of vector because // there's no note?? - if( it == notes.begin()-1 ) + if (it == notes.rend()) { is_new_note = true; m_midiClip->addJournalCheckPoint(); @@ -1811,8 +1790,8 @@ void PianoRoll::mousePressEvent(QMouseEvent * me ) // reset it so that it can be used for // ops (move, resize) after this // code-block - it = notes.begin(); - while( it != notes.end() && *it != created_new_note ) + it = notes.rbegin(); + while (it != notes.rend() && *it != created_new_note) { ++it; } @@ -1837,9 +1816,9 @@ void PianoRoll::mousePressEvent(QMouseEvent * me ) auto selectedNotes = getSelectedNotes(); - m_moveBoundaryLeft = selectedNotes.first()->pos().getTicks(); - m_moveBoundaryRight = selectedNotes.first()->endPos(); - m_moveBoundaryBottom = selectedNotes.first()->key(); + m_moveBoundaryLeft = selectedNotes.front()->pos().getTicks(); + m_moveBoundaryRight = selectedNotes.front()->endPos(); + m_moveBoundaryBottom = selectedNotes.front()->key(); m_moveBoundaryTop = m_moveBoundaryBottom; //Figure out the bounding box of all the selected notes @@ -1863,7 +1842,7 @@ void PianoRoll::mousePressEvent(QMouseEvent * me ) { m_midiClip->addJournalCheckPoint(); // then resize the note - m_action = ActionResizeNote; + m_action = Action::ResizeNote; //Calculate the minimum length we should allow when resizing //each note, and let all notes use the smallest one found @@ -1893,7 +1872,7 @@ void PianoRoll::mousePressEvent(QMouseEvent * me ) } // otherwise move it - m_action = ActionMoveNote; + m_action = Action::MoveNote; // set move-cursor setCursor( Qt::SizeAllCursor ); @@ -1923,12 +1902,12 @@ void PianoRoll::mousePressEvent(QMouseEvent * me ) Engine::getSong()->setModified(); } else if( ( me->buttons() == Qt::RightButton && - m_editMode == ModeDraw ) || - m_editMode == ModeErase ) + m_editMode == EditMode::Draw ) || + m_editMode == EditMode::Erase ) { // erase single note m_mouseDownRight = true; - if( it != notes.begin()-1 ) + if (it != notes.rend()) { m_midiClip->addJournalCheckPoint(); m_midiClip->removeNote( *it ); @@ -1936,7 +1915,7 @@ void PianoRoll::mousePressEvent(QMouseEvent * me ) } } else if( me->button() == Qt::LeftButton && - m_editMode == ModeSelect ) + m_editMode == EditMode::Select ) { // select an area of notes @@ -1944,7 +1923,7 @@ void PianoRoll::mousePressEvent(QMouseEvent * me ) m_selectedTick = 0; m_selectStartKey = key_num; m_selectedKeys = 1; - m_action = ActionSelectNotes; + m_action = Action::SelectNotes; // call mousemove to fix glitch where selection // appears in wrong spot on mousedown @@ -1982,7 +1961,7 @@ void PianoRoll::mousePressEvent(QMouseEvent * me ) { // clicked in the box below the keys to the left of note edit area m_noteEditMode = (NoteEditMode)(((int)m_noteEditMode)+1); - if( m_noteEditMode == NoteEditCount ) + if( m_noteEditMode == NoteEditMode::Count ) { m_noteEditMode = (NoteEditMode) 0; } @@ -2027,7 +2006,7 @@ void PianoRoll::mouseDoubleClickEvent(QMouseEvent * me ) { if( i->withinRange( ticks_start, ticks_end ) || ( i->selected() && !altPressed ) ) { - nv += i; + nv.push_back(i); } } // make sure we're on a note @@ -2045,8 +2024,8 @@ void PianoRoll::mouseDoubleClickEvent(QMouseEvent * me ) if( dist < closest_dist ) { closest = i; closest_dist = dist; } } // ... then remove all notes from the vector that aren't on the same exact time - NoteVector::Iterator it = nv.begin(); - while( it != nv.end() ) + auto it = nv.begin(); + while (it != nv.end()) { const Note *note = *it; if( note->pos().getTicks() != closest->pos().getTicks() ) @@ -2155,11 +2134,11 @@ void PianoRoll::pauseChordNotes(int key) void PianoRoll::setKnifeAction() { - if (m_editMode != ModeEditKnife) + if (m_editMode != EditMode::Knife) { m_knifeMode = m_editMode; - m_editMode = ModeEditKnife; - m_action = ActionKnife; + m_editMode = EditMode::Knife; + m_action = Action::Knife; setCursor(Qt::ArrowCursor); update(); } @@ -2168,7 +2147,7 @@ void PianoRoll::setKnifeAction() void PianoRoll::cancelKnifeAction() { m_editMode = m_knifeMode; - m_action = ActionNone; + m_action = Action::None; update(); } @@ -2273,7 +2252,7 @@ void PianoRoll::mouseReleaseEvent( QMouseEvent * me ) s_textFloat->hide(); // Quit knife mode if we pressed and released the right mouse button - if (m_editMode == ModeEditKnife && me->button() == Qt::RightButton) + if (m_editMode == EditMode::Knife && me->button() == Qt::RightButton) { cancelKnifeAction(); } @@ -2282,14 +2261,14 @@ void PianoRoll::mouseReleaseEvent( QMouseEvent * me ) { mustRepaint = true; - if( m_action == ActionSelectNotes && m_editMode == ModeSelect ) + if( m_action == Action::SelectNotes && m_editMode == EditMode::Select ) { // select the notes within the selection rectangle and // then destroy the selection rectangle computeSelectedNotes( me->modifiers() & Qt::ShiftModifier ); } - else if( m_action == ActionMoveNote ) + else if( m_action == Action::MoveNote ) { // we moved one or more notes so they have to be // moved properly according to new starting- @@ -2298,7 +2277,7 @@ void PianoRoll::mouseReleaseEvent( QMouseEvent * me ) } - if( m_action == ActionMoveNote || m_action == ActionResizeNote ) + if( m_action == Action::MoveNote || m_action == Action::ResizeNote ) { // if we only moved one note, deselect it so we can // edit the notes in the note edit area @@ -2337,12 +2316,12 @@ void PianoRoll::mouseReleaseEvent( QMouseEvent * me ) m_currentNote = nullptr; - if (m_action != ActionKnife) + if (m_action != Action::Knife) { - m_action = ActionNone; + m_action = Action::None; } - if( m_editMode == ModeDraw ) + if( m_editMode == EditMode::Draw ) { setCursor( Qt::ArrowCursor ); } @@ -2364,7 +2343,7 @@ void PianoRoll::mouseMoveEvent( QMouseEvent * me ) return; } - if( m_action == ActionNone && me->buttons() == 0 ) + if( m_action == Action::None && me->buttons() == 0 ) { // When cursor is between note editing area and volume/panning // area show vertical size cursor. @@ -2374,7 +2353,7 @@ void PianoRoll::mouseMoveEvent( QMouseEvent * me ) return; } } - else if( m_action == ActionResizeNoteEditArea ) + else if( m_action == Action::ResizeNoteEditArea ) { // Don't try to show more keys than the full keyboard, bail if trying to if (m_pianoKeysVisible == NumKeys && me->y() > m_moveStartY) @@ -2398,22 +2377,22 @@ void PianoRoll::mouseMoveEvent( QMouseEvent * me ) } // Update Knife position if we are on knife mode - if (m_editMode == ModeEditKnife) + if (m_editMode == EditMode::Knife) { updateKnifePos(me); } - if( me->y() > PR_TOP_MARGIN || m_action != ActionNone ) + if( me->y() > PR_TOP_MARGIN || m_action != Action::None ) { bool edit_note = ( me->y() > noteEditTop() ) - && m_action != ActionSelectNotes; + && m_action != Action::SelectNotes; int key_num = getKey( me->y() ); int x = me->x(); // see if they clicked on the keyboard on the left - if (x < m_whiteKeyWidth && m_action == ActionNone + if (x < m_whiteKeyWidth && m_action == Action::None && ! edit_note && key_num != m_lastKey && me->buttons() & Qt::LeftButton ) { @@ -2426,14 +2405,14 @@ void PianoRoll::mouseMoveEvent( QMouseEvent * me ) x -= m_whiteKeyWidth; if( me->buttons() & Qt::LeftButton - && m_editMode == ModeDraw - && (m_action == ActionMoveNote || m_action == ActionResizeNote ) ) + && m_editMode == EditMode::Draw + && (m_action == Action::MoveNote || m_action == Action::ResizeNote ) ) { // handle moving notes and resizing them bool replay_note = key_num != m_lastKey - && m_action == ActionMoveNote; + && m_action == Action::MoveNote; - if( replay_note || ( m_action == ActionMoveNote && ( me->modifiers() & Qt::ShiftModifier ) && ! m_startedWithShift ) ) + if( replay_note || ( m_action == Action::MoveNote && ( me->modifiers() & Qt::ShiftModifier ) && ! m_startedWithShift ) ) { pauseTestNotes(); } @@ -2446,13 +2425,13 @@ void PianoRoll::mouseMoveEvent( QMouseEvent * me ) me->modifiers() & Qt::ControlModifier ); - if( replay_note && m_action == ActionMoveNote && ! ( ( me->modifiers() & Qt::ShiftModifier ) && ! m_startedWithShift ) ) + if( replay_note && m_action == Action::MoveNote && ! ( ( me->modifiers() & Qt::ShiftModifier ) && ! m_startedWithShift ) ) { pauseTestNotes( false ); } } - else if( m_editMode != ModeErase && - ( edit_note || m_action == ActionChangeNoteProperty ) && + else if( m_editMode != EditMode::Erase && + ( edit_note || m_action == Action::ChangeNoteProperty ) && ( me->buttons() & Qt::LeftButton || me->buttons() & Qt::MiddleButton || ( me->buttons() & Qt::RightButton && me->modifiers() & Qt::ShiftModifier ) ) ) { @@ -2493,12 +2472,12 @@ void PianoRoll::mouseMoveEvent( QMouseEvent * me ) PanningRight); } - if( m_noteEditMode == NoteEditVolume ) + if( m_noteEditMode == NoteEditMode::Volume ) { m_lastNoteVolume = vol; showVolTextFloat( vol, me->pos() ); } - else if( m_noteEditMode == NoteEditPanning ) + else if( m_noteEditMode == NoteEditMode::Panning ) { m_lastNotePanning = pan; showPanTextFloat( pan, me->pos() ); @@ -2508,8 +2487,8 @@ void PianoRoll::mouseMoveEvent( QMouseEvent * me ) bool altPressed = me->modifiers() & Qt::AltModifier; // We iterate from last note in MIDI clip to the first, // chronologically - NoteVector::ConstIterator it = notes.begin()+notes.size()-1; - for( int i = 0; i < notes.size(); ++i ) + auto it = notes.rbegin(); + while (it != notes.rend()) { Note* n = *it; @@ -2525,7 +2504,7 @@ void PianoRoll::mouseMoveEvent( QMouseEvent * me ) ( isUnderPosition && n->selected() && altPressed ) ) { - if( m_noteEditMode == NoteEditVolume ) + if( m_noteEditMode == NoteEditMode::Volume ) { n->setVolume( vol ); @@ -2533,7 +2512,7 @@ void PianoRoll::mouseMoveEvent( QMouseEvent * me ) m_midiClip->instrumentTrack()->processInEvent( MidiEvent( MidiKeyPressure, -1, n->key(), n->midiVelocity( baseVelocity ) ) ); } - else if( m_noteEditMode == NoteEditPanning ) + else if( m_noteEditMode == NoteEditMode::Panning ) { n->setPanning( pan ); MidiEvent evt( MidiMetaEvent, -1, n->key(), panningToMidi( pan ) ); @@ -2551,14 +2530,14 @@ void PianoRoll::mouseMoveEvent( QMouseEvent * me ) } - --it; + ++it; } // Emit MIDI clip has changed m_midiClip->dataChanged(); } - else if( me->buttons() == Qt::NoButton && m_editMode == ModeDraw ) + else if( me->buttons() == Qt::NoButton && m_editMode == EditMode::Draw ) { // set move- or resize-cursor @@ -2570,10 +2549,10 @@ void PianoRoll::mouseMoveEvent( QMouseEvent * me ) const NoteVector & notes = m_midiClip->notes(); // will be our iterator in the following loop - NoteVector::ConstIterator it = notes.begin()+notes.size()-1; + auto it = notes.rbegin(); // loop through whole note-vector... - for( int i = 0; i < notes.size(); ++i ) + while (it != notes.rend()) { Note *note = *it; // and check whether the cursor is over an @@ -2586,12 +2565,12 @@ void PianoRoll::mouseMoveEvent( QMouseEvent * me ) { break; } - --it; + ++it; } // did it reach end of vector because there's // no note?? - if( it != notes.begin()-1 ) + if (it != notes.rend()) { Note *note = *it; // x coordinate of the right edge of the note @@ -2611,8 +2590,8 @@ void PianoRoll::mouseMoveEvent( QMouseEvent * me ) } } else if( me->buttons() & Qt::LeftButton && - m_editMode == ModeSelect && - m_action == ActionSelectNotes ) + m_editMode == EditMode::Select && + m_action == Action::SelectNotes ) { // change size of selection @@ -2632,8 +2611,8 @@ void PianoRoll::mouseMoveEvent( QMouseEvent * me ) --m_selectedKeys; } } - else if( ( m_editMode == ModeDraw && me->buttons() & Qt::RightButton ) - || ( m_editMode == ModeErase && me->buttons() ) ) + else if( ( m_editMode == EditMode::Draw && me->buttons() & Qt::RightButton ) + || ( m_editMode == EditMode::Erase && me->buttons() ) ) { // holding down right-click to delete notes or holding down // any key if in erase mode @@ -2647,7 +2626,7 @@ void PianoRoll::mouseMoveEvent( QMouseEvent * me ) const NoteVector & notes = m_midiClip->notes(); // will be our iterator in the following loop - NoteVector::ConstIterator it = notes.begin(); + auto it = notes.begin(); // loop through whole note-vector... while( it != notes.end() ) @@ -2676,7 +2655,7 @@ void PianoRoll::mouseMoveEvent( QMouseEvent * me ) ) { // delete this note - m_midiClip->removeNote( note ); + it = m_midiClip->removeNote(it); Engine::getSong()->setModified(); } else @@ -2685,7 +2664,7 @@ void PianoRoll::mouseMoveEvent( QMouseEvent * me ) } } } - else if (me->buttons() == Qt::NoButton && m_editMode != ModeDraw && m_editMode != ModeEditKnife) + else if (me->buttons() == Qt::NoButton && m_editMode != EditMode::Draw && m_editMode != EditMode::Knife) { // Is needed to restore cursor when it previously was set to // Qt::SizeVerCursor (between keyAreaBottom and noteEditTop) @@ -2695,8 +2674,8 @@ void PianoRoll::mouseMoveEvent( QMouseEvent * me ) else { if( me->buttons() & Qt::LeftButton && - m_editMode == ModeSelect && - m_action == ActionSelectNotes ) + m_editMode == EditMode::Select && + m_action == Action::SelectNotes ) { int x = me->x() - m_whiteKeyWidth; @@ -2815,11 +2794,11 @@ void PianoRoll::dragNotes(int x, int y, bool alt, bool shift, bool ctrl) // get note-vector of current MIDI clip const NoteVector & notes = m_midiClip->notes(); - if (m_action == ActionMoveNote) + if (m_action == Action::MoveNote) { // Calculate the offset for either Nudge or Snap modes int noteOffset = off_ticks; - if (m_gridMode == gridSnap && quantization () > 1) + if (m_gridMode == GridMode::Snap && quantization () > 1) { // Get the mouse timeline absolute position TimePos mousePos(m_currentNote->oldPos().getTicks() + off_ticks); @@ -2841,7 +2820,7 @@ void PianoRoll::dragNotes(int x, int y, bool alt, bool shift, bool ctrl) ? mousePosEndQ.getTicks() - m_currentNote->oldPos().getTicks() - m_currentNote->oldLength().getTicks() : mousePosQ.getTicks() - m_currentNote->oldPos().getTicks(); } - else if (m_gridMode == gridNudge) + else if (m_gridMode == GridMode::Nudge) { // if they're not holding alt, quantize the offset if (!alt) @@ -2869,7 +2848,7 @@ void PianoRoll::dragNotes(int x, int y, bool alt, bool shift, bool ctrl) { // Quick resize is only enabled on Nudge mode, since resizing the note // while in Snap mode breaks the calculation of the note offset - if (shift && ! m_startedWithShift && m_gridMode == gridNudge) + if (shift && ! m_startedWithShift && m_gridMode == GridMode::Nudge) { // quick resize, toggled by holding shift after starting a note move, but not before int ticks_new = note->oldLength().getTicks() + noteOffset; @@ -2893,7 +2872,7 @@ void PianoRoll::dragNotes(int x, int y, bool alt, bool shift, bool ctrl) } } } - else if (m_action == ActionResizeNote) + else if (m_action == Action::ResizeNote) { // When resizing notes: // If shift is not pressed, resize the selected notes but do not rearrange them @@ -2994,7 +2973,7 @@ void PianoRoll::dragNotes(int x, int y, bool alt, bool shift, bool ctrl) // shift is not pressed; stretch length of selected notes but not their position int minLength = alt ? 1 : m_minResizeLen.getTicks(); - if (m_gridMode == gridSnap) + if (m_gridMode == GridMode::Snap) { // Calculate the end point of the note being dragged TimePos oldEndPoint = m_currentNote->oldPos() + m_currentNote->oldLength(); @@ -3060,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) { @@ -3088,10 +3066,10 @@ 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 != ActionResizeNoteEditArea && partialKeyVisible != 0) + if (m_action != Action::ResizeNoteEditArea && partialKeyVisible != 0) { // calculate the height change adding and subtracting the partial key int noteAreaPlus = (m_notesEditHeight + partialKeyVisible) - m_userSetNotesEditHeight; @@ -3145,11 +3123,11 @@ void PianoRoll::paintEvent(QPaintEvent * pe ) { switch (prKeyOrder[key % KeysPerOctave]) { - case PR_WHITE_KEY_BIG: + case KeyType::WhiteBig: return m_whiteKeyBigHeight; - case PR_WHITE_KEY_SMALL: + case KeyType::WhiteSmall: return m_whiteKeySmallHeight; - case PR_BLACK_KEY: + case KeyType::Black: return m_blackKeyHeight; } return 0; // should never happen @@ -3162,15 +3140,15 @@ void PianoRoll::paintEvent(QPaintEvent * pe ) const int keyCode = key % KeysPerOctave; switch (prKeyOrder[keyCode]) { - case PR_WHITE_KEY_BIG: + case KeyType::WhiteBig: return m_whiteKeySmallHeight; - case PR_WHITE_KEY_SMALL: + case KeyType::WhiteSmall: // These two keys need to adjust up small height instead of only key line height - if (keyCode == Key_C || keyCode == Key_F) + if (static_cast(keyCode) == Key::C || static_cast(keyCode) == Key::F) { return m_whiteKeySmallHeight; } - case PR_BLACK_KEY: + case KeyType::Black: return m_blackKeyHeight; } return 0; // should never happen @@ -3181,10 +3159,10 @@ void PianoRoll::paintEvent(QPaintEvent * pe ) { switch (prKeyOrder[key % KeysPerOctave]) { - case PR_WHITE_KEY_SMALL: - case PR_WHITE_KEY_BIG: + case KeyType::WhiteSmall: + case KeyType::WhiteBig: return m_whiteKeyWidth; - case PR_BLACK_KEY: + case KeyType::Black: return m_blackKeyWidth; } return 0; // should never happen @@ -3204,8 +3182,8 @@ void PianoRoll::paintEvent(QPaintEvent * pe ) p.setPen(QColor(0, 0, 0)); switch (prKeyOrder[keyCode]) { - case PR_WHITE_KEY_SMALL: - case PR_WHITE_KEY_BIG: + case KeyType::WhiteSmall: + case KeyType::WhiteBig: if (mapped) { if (pressed) { p.setBrush(m_whiteKeyActiveBackground); } @@ -3216,7 +3194,7 @@ void PianoRoll::paintEvent(QPaintEvent * pe ) p.setBrush(m_whiteKeyDisabledBackground); } break; - case PR_BLACK_KEY: + case KeyType::Black: if (mapped) { if (pressed) { p.setBrush(m_blackKeyActiveBackground); } @@ -3230,7 +3208,7 @@ void PianoRoll::paintEvent(QPaintEvent * pe ) // draw key p.drawRect(PIANO_X, yt, kw, kh); // draw note name - if (keyCode == Key_C || (drawNoteNames && Piano::isWhiteKey(key))) + if (static_cast(keyCode) == Key::C || (drawNoteNames && Piano::isWhiteKey(key))) { // small font sizes have 1 pixel offset instead of 2 auto zoomOffset = m_zoomYLevels[m_zoomingYModel.value()] > 1.0f ? 2 : 1; @@ -3244,7 +3222,7 @@ void PianoRoll::paintEvent(QPaintEvent * pe ) p.setPen(pressed ? m_whiteKeyActiveTextShadow : m_whiteKeyInactiveTextShadow); p.drawText(textRect.adjusted(0, 1, 1, 0), Qt::AlignRight | Qt::AlignHCenter, noteString); p.setPen(pressed ? m_whiteKeyActiveTextColor : m_whiteKeyInactiveTextColor); - // if (keyCode == Key_C) { p.setPen(textColor()); } + // if (static_cast(keyCode) == Key::C) { p.setPen(textColor()); } // else { p.setPen(textColorLight()); } p.drawText(textRect, Qt::AlignRight | Qt::AlignHCenter, noteString); } @@ -3255,17 +3233,17 @@ void PianoRoll::paintEvent(QPaintEvent * pe ) const int y ) { - if (key % KeysPerOctave == Key_C) { p.setPen(m_beatLineColor); } + if (static_cast(key % KeysPerOctave) == Key::C) { p.setPen(m_beatLineColor); } else { p.setPen(m_lineColor); } p.drawLine(m_whiteKeyWidth, y, width(), y); }; // correct y offset of the top key switch (prKeyOrder[topNote]) { - case PR_WHITE_KEY_SMALL: - case PR_WHITE_KEY_BIG: + case KeyType::WhiteSmall: + case KeyType::WhiteBig: break; - case PR_BLACK_KEY: + case KeyType::Black: // draw extra white key drawKey(topKey + 1, grid_line_y - m_keyLineHeight); } @@ -3346,11 +3324,11 @@ void PianoRoll::paintEvent(QPaintEvent * pe ) for(x = 0; x < m_markedSemiTones.size(); ++x) { const int key_num = m_markedSemiTones.at(x); - const int y = keyAreaBottom() + 5 - m_keyLineHeight * + const int y = keyAreaBottom() - 1 - m_keyLineHeight * (key_num - m_startKey + 1); - if(y > keyAreaBottom()) { break; } + if(y >= keyAreaBottom() - 1) { break; } p.fillRect(m_whiteKeyWidth + 1, - y - m_keyLineHeight / 2, + y, width() - 10, m_keyLineHeight + 1, m_markedSemitoneColor); @@ -3366,14 +3344,14 @@ 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()), Qt::AlignCenter | Qt::TextWordWrap, - m_nemStr.at( m_noteEditMode ) + ":" ); + m_nemStr.at(static_cast(m_noteEditMode)) + ":" ); // set clipping area, because we are not allowed to paint over // keyboard... @@ -3490,16 +3468,20 @@ 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 int editHandleTop = 0; - if( m_noteEditMode == NoteEditVolume ) + if( m_noteEditMode == NoteEditMode::Volume ) { QColor color = m_barColor.lighter(30 + (note->getVolume() * 90 / MaxVolume)); if( note->selected() ) @@ -3517,7 +3499,7 @@ void PianoRoll::paintEvent(QPaintEvent * pe ) noteEditLeft() + x + 0.5, noteEditBottom() + 0.5 ) ); } - else if( m_noteEditMode == NoteEditPanning ) + else if( m_noteEditMode == NoteEditMode::Panning ) { QColor color = m_noteColor; if( note->selected() ) @@ -3551,7 +3533,7 @@ void PianoRoll::paintEvent(QPaintEvent * pe ) } // -- Knife tool (draw cut line) - if (m_action == ActionKnife) + if (m_action == Action::Knife) { auto xCoordOfTick = [this](int tick) { return m_whiteKeyWidth + ( @@ -3625,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, @@ -3688,24 +3670,32 @@ void PianoRoll::paintEvent(QPaintEvent * pe ) // draw current edit-mode-icon below the cursor switch( m_editMode ) { - case ModeDraw: + case EditMode::Draw: if( m_mouseDownRight ) { - cursor = s_toolErase; + cursor = &m_toolErase; } - else if( m_action == ActionMoveNote ) + else if( m_action == Action::MoveNote ) { - cursor = s_toolMove; + cursor = &m_toolMove; } else { - cursor = s_toolDraw; + cursor = &m_toolDraw; } break; - case ModeErase: cursor = s_toolErase; break; - case ModeSelect: cursor = s_toolSelect; break; - case ModeEditDetuning: cursor = s_toolOpen; break; - case ModeEditKnife: 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()) @@ -3748,8 +3738,7 @@ void PianoRoll::resizeEvent(QResizeEvent* re) { updatePositionLineHeight(); updateScrollbars(); - Engine::getSong()->getPlayPos(Song::Mode_PlayMidiClip) - .m_timeLine->setFixedWidth(width()); + m_timeLine->setFixedWidth(width()); update(); } @@ -3780,13 +3769,13 @@ void PianoRoll::wheelEvent(QWheelEvent * we ) { if( i->withinRange( ticks_start, ticks_end ) || ( i->selected() && !altPressed ) ) { - nv += i; + nv.push_back(i); } } if( nv.size() > 0 ) { const int step = we->angleDelta().y() > 0 ? 1 : -1; - if( m_noteEditMode == NoteEditVolume ) + if( m_noteEditMode == NoteEditMode::Volume ) { for ( Note * n : nv ) { @@ -3805,7 +3794,7 @@ void PianoRoll::wheelEvent(QWheelEvent * we ) showVolTextFloat(nv[0]->getVolume(), position(we), 1000); } } - else if( m_noteEditMode == NoteEditPanning ) + else if( m_noteEditMode == NoteEditMode::Panning ) { for ( Note * n : nv ) { @@ -3913,9 +3902,9 @@ void PianoRoll::focusOutEvent( QFocusEvent * ) m_midiClip->instrumentTrack()->pianoModel()->setKeyState( i, false ); } } - if (m_editMode == ModeEditKnife) { + if (m_editMode == EditMode::Knife) { m_editMode = m_knifeMode; - m_action = ActionNone; + m_action = Action::None; } else { m_editMode = m_ctrlMode; } @@ -3951,7 +3940,8 @@ QList PianoRoll::getAllOctavesForKey( int keyToMirror ) const { QList keys; - for (int i=keyToMirror % KeysPerOctave; i < NumKeys; i += KeysPerOctave) + int trackKeysPerOctave = trackOctaveSize(); + for (int i = keyToMirror % trackKeysPerOctave; i < NumKeys; i += trackKeysPerOctave) { keys.append(i); } @@ -3959,13 +3949,13 @@ QList PianoRoll::getAllOctavesForKey( int keyToMirror ) const return keys; } -Song::PlayModes PianoRoll::desiredPlayModeForAccompany() const +Song::PlayMode PianoRoll::desiredPlayModeForAccompany() const { if (m_midiClip->getTrack()->trackContainer() == Engine::patternStore()) { - return Song::Mode_PlayPattern; + return Song::PlayMode::Pattern; } - return Song::Mode_PlaySong; + return Song::PlayMode::Song; } @@ -3978,7 +3968,7 @@ void PianoRoll::play() return; } - if( Engine::getSong()->playMode() != Song::Mode_PlayMidiClip ) + if( Engine::getSong()->playMode() != Song::PlayMode::MidiClip ) { Engine::getSong()->playMidiClip( m_midiClip ); } @@ -4056,7 +4046,7 @@ bool PianoRoll::toggleStepRecording() { m_stepRecorder.start( Engine::getSong()->getPlayPos( - Song::Mode_PlayMidiClip), newNoteLen()); + Song::PlayMode::MidiClip), newNoteLen()); } } } @@ -4071,7 +4061,7 @@ void PianoRoll::stop() { Engine::getSong()->stop(); m_recording = false; - m_scrollBack = ( m_timeLine->autoScroll() == TimeLineWidget::AutoScrollEnabled ); + m_scrollBack = ( m_timeLine->autoScroll() == TimeLineWidget::AutoScrollState::Enabled ); } @@ -4084,10 +4074,10 @@ void PianoRoll::startRecordNote(const Note & n ) if( m_recording && Engine::getSong()->isPlaying() && (Engine::getSong()->playMode() == desiredPlayModeForAccompany() || - Engine::getSong()->playMode() == Song::Mode_PlayMidiClip )) + Engine::getSong()->playMode() == Song::PlayMode::MidiClip )) { TimePos sub; - if( Engine::getSong()->playMode() == Song::Mode_PlaySong ) + if( Engine::getSong()->playMode() == Song::PlayMode::Song ) { sub = m_midiClip->startPosition(); } @@ -4118,18 +4108,23 @@ void PianoRoll::finishRecordNote(const Note & n ) ( Engine::getSong()->playMode() == desiredPlayModeForAccompany() || Engine::getSong()->playMode() == - Song::Mode_PlayMidiClip ) ) + Song::PlayMode::MidiClip ) ) { for( QList::Iterator it = m_recordingNotes.begin(); it != m_recordingNotes.end(); ++it ) { if( it->key() == n.key() ) { - Note n1( n.length(), it->pos(), + Note n1(n.length(), it->pos(), it->key(), it->getVolume(), - it->getPanning() ); - n1.quantizeLength( quantization() ); - m_midiClip->addNote( n1 ); + it->getPanning(), n.detuning()); + + if (m_doAutoQuantization) + { + n1.quantizeLength(quantization()); + n1.quantizePos(quantization()); + } + m_midiClip->addNote(n1, false); update(); m_recordingNotes.erase( it ); break; @@ -4170,7 +4165,7 @@ void PianoRoll::verScrolled( int new_pos ) void PianoRoll::setEditMode(int mode) { - m_ctrlMode = m_editMode = (EditModes) mode; + m_ctrlMode = m_editMode = (EditMode) mode; } @@ -4262,7 +4257,7 @@ void PianoRoll::selectNotesOnKey() void PianoRoll::enterValue( NoteVector* nv ) { - if( m_noteEditMode == NoteEditVolume ) + if( m_noteEditMode == NoteEditMode::Volume ) { bool ok; int new_val; @@ -4281,7 +4276,7 @@ void PianoRoll::enterValue( NoteVector* nv ) m_lastNoteVolume = new_val; } } - else if( m_noteEditMode == NoteEditPanning ) + else if( m_noteEditMode == NoteEditMode::Panning ) { bool ok; int new_val; @@ -4329,7 +4324,7 @@ void PianoRoll::copyToClipboard( const NoteVector & notes ) const // For copyString() and MimeType enum class using namespace Clipboard; - DataFile dataFile( DataFile::ClipboardData ); + DataFile dataFile( DataFile::Type::ClipboardData ); QDomElement note_list = dataFile.createElement( "note-list" ); dataFile.content().appendChild( note_list ); @@ -4486,8 +4481,8 @@ void PianoRoll::autoScroll( const TimePos & t ) void PianoRoll::updatePosition( const TimePos & t ) { if( ( Engine::getSong()->isPlaying() - && Engine::getSong()->playMode() == Song::Mode_PlayMidiClip - && m_timeLine->autoScroll() == TimeLineWidget::AutoScrollEnabled + && Engine::getSong()->playMode() == Song::PlayMode::MidiClip + && m_timeLine->autoScroll() == TimeLineWidget::AutoScrollState::Enabled ) || m_scrollBack ) { autoScroll( t ); @@ -4523,16 +4518,16 @@ void PianoRoll::updatePositionAccompany( const TimePos & t ) Song * s = Engine::getSong(); if( m_recording && hasValidMidiClip() && - s->playMode() != Song::Mode_PlayMidiClip ) + s->playMode() != Song::PlayMode::MidiClip ) { TimePos pos = t; - if (s->playMode() != Song::Mode_PlayPattern) + if (s->playMode() != Song::PlayMode::Pattern) { pos -= m_midiClip->startPosition(); } if( (int) pos > 0 ) { - s->getPlayPos( Song::Mode_PlayMidiClip ).setTicks( pos ); + s->getPlayPos( Song::PlayMode::MidiClip ).setTicks( pos ); autoScroll( pos ); } } @@ -4587,7 +4582,7 @@ void PianoRoll::noteLengthChanged() void PianoRoll::keyChanged() { - markSemiTone(stmaMarkCurrentScale, false); + markSemiTone(SemiToneMarkerAction::MarkCurrentScale, false); } int PianoRoll::quantization() const @@ -4608,7 +4603,7 @@ int PianoRoll::quantization() const } -void PianoRoll::quantizeNotes(QuantizeActions mode) +void PianoRoll::quantizeNotes(QuantizeAction mode) { if( ! hasValidMidiClip() ) { @@ -4636,11 +4631,11 @@ void PianoRoll::quantizeNotes(QuantizeActions mode) Note copy(*n); m_midiClip->removeNote( n ); - if (mode == QuantizeBoth || mode == QuantizePos) + if (mode == QuantizeAction::Both || mode == QuantizeAction::Pos) { copy.quantizePos(quantization()); } - if (mode == QuantizeBoth || mode == QuantizeLength) + if (mode == QuantizeAction::Both || mode == QuantizeAction::Length) { copy.quantizeLength(quantization()); } @@ -4728,9 +4723,9 @@ Note * PianoRoll::noteUnderMouse() void PianoRoll::changeSnapMode() { - // gridNudge, - // gridSnap, - // gridFree - to be implemented + // GridMode::Nudge, + // GridMode::Snap, + // GridMode::Free - to be implemented m_gridMode = static_cast(m_snapModel.value()); } @@ -4774,8 +4769,8 @@ PianoRollWindow::PianoRollWindow() : auto quantizeLengthAction = new QAction(tr("Quantize lengths"), this); connect(quantizeAction, &QAction::triggered, [this](){ m_editor->quantizeNotes(); }); - connect(quantizePosAction, &QAction::triggered, [this](){ m_editor->quantizeNotes(PianoRoll::QuantizePos); }); - connect(quantizeLengthAction, &QAction::triggered, [this](){ m_editor->quantizeNotes(PianoRoll::QuantizeLength); }); + connect(quantizePosAction, &QAction::triggered, [this](){ m_editor->quantizeNotes(PianoRoll::QuantizeAction::Pos); }); + connect(quantizeLengthAction, &QAction::triggered, [this](){ m_editor->quantizeNotes(PianoRoll::QuantizeAction::Length); }); quantizeButton->setPopupMode(QToolButton::MenuButtonPopup); quantizeButton->setDefaultAction(quantizeAction); @@ -5180,6 +5175,9 @@ void PianoRollWindow::saveSettings( QDomDocument & doc, QDomElement & de ) de.appendChild(markedSemiTonesRoot); } + de.setAttribute("stopbehaviour", static_cast( + Engine::getSong()->getTimeline(Song::PlayMode::MidiClip).stopBehaviour())); + MainWindow::saveWidgetState( this, de ); } @@ -5193,6 +5191,9 @@ void PianoRollWindow::loadSettings( const QDomElement & de ) MainWindow::restoreWidgetState( this, de ); + 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 // WHITE_KEY_WIDTH default @@ -5268,7 +5269,7 @@ void PianoRollWindow::exportMidiClip() exportDialog.setDefaultSuffix(suffix); const QString fullPath = exportDialog.selectedFiles()[0]; - DataFile dataFile(DataFile::MidiClip); + DataFile dataFile(DataFile::Type::MidiClip); m_editor->m_midiClip->saveSettings(dataFile, dataFile.content()); if (dataFile.writeFile(fullPath)) diff --git a/src/gui/editors/PositionLine.cpp b/src/gui/editors/PositionLine.cpp index 7dd8b3b13..8b938443d 100644 --- a/src/gui/editors/PositionLine.cpp +++ b/src/gui/editors/PositionLine.cpp @@ -64,8 +64,8 @@ void PositionLine::paintEvent(QPaintEvent* pe) // If gradient is enabled, we're in focus and we're playing, enable gradient if (m_hasTailGradient && Engine::getSong()->isPlaying() && - (Engine::getSong()->playMode() == Song::Mode_PlaySong || - Engine::getSong()->playMode() == Song::Mode_PlayMidiClip)) + (Engine::getSong()->playMode() == Song::PlayMode::Song || + Engine::getSong()->playMode() == Song::PlayMode::MidiClip)) { c.setAlpha(60); gradient.setColorAt(w, c); diff --git a/src/gui/editors/SongEditor.cpp b/src/gui/editors/SongEditor.cpp index b00995f60..c9ec9d5f2 100644 --- a/src/gui/editors/SongEditor.cpp +++ b/src/gui/editors/SongEditor.cpp @@ -24,11 +24,14 @@ #include "SongEditor.h" +#include + #include #include #include #include #include +#include #include #include "ActionGroup.h" @@ -58,25 +61,35 @@ namespace lmms::gui { +namespace +{ + +constexpr int MIN_PIXELS_PER_BAR = 2; +constexpr int MAX_PIXELS_PER_BAR = 400; +constexpr int ZOOM_STEPS = 200; + +constexpr std::array SNAP_SIZES{8.f, 4.f, 2.f, 1.f, 1/2.f, 1/4.f, 1/8.f, 1/16.f}; +constexpr std::array PROPORTIONAL_SNAP_SIZES{64.f, 32.f, 16.f, 8.f, 4.f, 2.f, 1.f, 1/2.f, 1/4.f, 1/8.f, 1/16.f, 1/32.f, 1/64.f}; + +} + -const QVector SongEditor::m_zoomLevels = - { 0.125f, 0.25f, 0.5f, 1.0f, 2.0f, 4.0f, 8.0f, 16.0f }; SongEditor::SongEditor( Song * song ) : TrackContainerView( song ), m_song( song ), - m_zoomingModel(new ComboBoxModel()), + m_zoomingModel(new IntModel(calculateZoomSliderValue(DEFAULT_PIXELS_PER_BAR), 0, ZOOM_STEPS, nullptr, tr("Zoom"))), m_snappingModel(new ComboBoxModel()), m_proportionalSnap( false ), m_scrollBack( false ), m_smoothScroll( ConfigManager::inst()->value( "ui", "smoothscroll" ).toInt() ), - m_mode(DrawMode), + m_mode(EditMode::Draw), m_origin(), m_scrollPos(), m_mousePos(), m_rubberBandStartTrackview(0), m_rubberbandStartTimePos(0), - m_currentZoomingValue(m_zoomingModel->value()), + m_rubberbandPixelsPerBar(DEFAULT_PIXELS_PER_BAR), m_trackHeadWidth(ConfigManager::inst()->value("ui", "compacttrackbuttons").toInt()==1 ? DEFAULT_SETTINGS_WIDGET_WIDTH_COMPACT + TRACK_OP_WIDTH_COMPACT : DEFAULT_SETTINGS_WIDGET_WIDTH + TRACK_OP_WIDTH), @@ -84,14 +97,13 @@ SongEditor::SongEditor( Song * song ) : { m_zoomingModel->setParent(this); m_snappingModel->setParent(this); - m_timeLine = new TimeLineWidget( m_trackHeadWidth, 32, - pixelsPerBar(), - m_song->m_playPos[Song::Mode_PlaySong], - m_currentPosition, - Song::Mode_PlaySong, this ); - connect( this, SIGNAL( positionChanged( const lmms::TimePos& ) ), - m_song->m_playPos[Song::Mode_PlaySong].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)), @@ -104,12 +116,15 @@ SongEditor::SongEditor( Song * song ) : m_positionLine = new PositionLine(this); static_cast( layout() )->insertWidget( 1, m_timeLine ); - + connect( m_song, SIGNAL(playbackStateChanged()), m_positionLine, SLOT(update())); - connect( this, SIGNAL(zoomingValueChanged(float)), - m_positionLine, SLOT(zoomChange(float))); - + + // When zoom changes, update position line + // But we must convert pixels per bar to a zoom factor where 1.0 is 100% + connect(this, &SongEditor::pixelsPerBarChanged, m_positionLine, + [this]() { m_positionLine->zoomChange(pixelsPerBar() / float(DEFAULT_PIXELS_PER_BAR)); }); + // Ensure loop markers snap to same increments as clips. Zoom & proportional // snap changes are handled in zoomingChanged() and toggleProportionalSnap() connect(m_snappingModel, &ComboBoxModel::dataChanged, @@ -177,8 +192,8 @@ SongEditor::SongEditor( Song * song ) : SLOT(hideMasterVolumeFloat())); m_mvsStatus = new TextFloat; - m_mvsStatus->setTitle( tr( "Master volume" ) ); - m_mvsStatus->setPixmap( embed::getIconPixmap( "master_volume" ) ); + m_mvsStatus->setTitle(tr("Master volume")); + m_mvsStatus->setPixmap(embed::getIconPixmap("master_volume")); getGUI()->mainWindow()->addWidgetToToolBar( master_vol_lbl ); getGUI()->mainWindow()->addWidgetToToolBar( m_masterVolumeSlider ); @@ -219,7 +234,7 @@ SongEditor::SongEditor( Song * song ) : // create widget for oscilloscope- and cpu-load-widget auto vc_w = new QWidget(tb); auto vcw_layout = new QVBoxLayout(vc_w); - vcw_layout->setMargin( 0 ); + vcw_layout->setContentsMargins(0, 0, 0, 0); vcw_layout->setSpacing( 0 ); vcw_layout->addStretch(); @@ -246,33 +261,23 @@ SongEditor::SongEditor( Song * song ) : connect(contentWidget()->verticalScrollBar(), SIGNAL(valueChanged(int)),this, SLOT(updateRubberband())); connect(m_timeLine, SIGNAL(selectionFinished()), this, SLOT(stopSelectRegion())); + //zoom connects + connect(m_zoomingModel, SIGNAL(dataChanged()), this, SLOT(zoomingChanged())); - //Set up zooming model - for( float const & zoomLevel : m_zoomLevels ) + // Set up snapping model + for (float bars : SNAP_SIZES) { - m_zoomingModel->addItem( QString( "%1\%" ).arg( zoomLevel * 100 ) ); - } - m_zoomingModel->setInitValue( - m_zoomingModel->findText( "100%" ) ); - connect( m_zoomingModel, SIGNAL(dataChanged()), - this, SLOT(zoomingChanged())); - connect( m_zoomingModel, SIGNAL(dataChanged()), - m_positionLine, SLOT(update())); - - //Set up snapping model, 2^i - for ( int i = 3; i >= -4; i-- ) - { - if ( i > 0 ) + if (bars > 1.0f) { - m_snappingModel->addItem( QString( "%1 Bars").arg( 1 << i ) ); + m_snappingModel->addItem(QString("%1 Bars").arg(bars)); } - else if ( i == 0 ) + else if (bars == 1.0f) { m_snappingModel->addItem( "1 Bar" ); } else { - m_snappingModel->addItem( QString( "1/%1 Bar" ).arg( 1 << (-i) ) ); + m_snappingModel->addItem(QString("1/%1 Bar").arg(1 / bars)); } } m_snappingModel->setInitValue( m_snappingModel->findText( "1 Bar" ) ); @@ -297,42 +302,40 @@ void SongEditor::loadSettings( const QDomElement& element ) +/*! \brief Return grid size as number of bars */ float SongEditor::getSnapSize() const { - // 1 Bar is the third value in the snapping dropdown - int val = -m_snappingModel->value() + 3; + float snapSize = SNAP_SIZES[m_snappingModel->value()]; + // If proportional snap is on, we snap to finer values when zoomed in if (m_proportionalSnap) { - val = val - m_zoomingModel->value() + 3; + // Finds the closest available snap size + const float optimalSize = snapSize * DEFAULT_PIXELS_PER_BAR / pixelsPerBar(); + return *std::min_element(PROPORTIONAL_SNAP_SIZES.begin(), PROPORTIONAL_SNAP_SIZES.end(), [optimalSize](float a, float b) + { + return std::abs(a - optimalSize) < std::abs(b - optimalSize); + }); } - val = std::max(val, -6); // -6 gives 1/64th bar snapping. Lower values cause crashing. - if ( val >= 0 ){ - return 1 << val; - } - else { - return 1.0 / ( 1 << -val ); - } + return snapSize; } QString SongEditor::getSnapSizeString() const { - int val = -m_snappingModel->value() + 3; - val = val - m_zoomingModel->value() + 3; - val = std::max(val, -6); // -6 gives 1/64th bar snapping. Lower values cause crashing. + float bars = getSnapSize(); - if ( val >= 0 ){ - int bars = 1 << val; - if ( bars == 1 ) { return QString("1 Bar"); } - else - { - return QString( "%1 Bars" ).arg(bars); - } + if (bars < 1) + { + return QString(tr("1/%1 Bar")).arg(round(1 / bars)); } - else { - int div = ( 1 << -val ); - return QString( "1/%1 Bar" ).arg(div); + else if (bars >= 2) + { + return QString(tr("%1 Bars")).arg(bars); + } + else + { + return QString("1 Bar"); } } @@ -342,8 +345,8 @@ QString SongEditor::getSnapSizeString() const void SongEditor::setHighQuality( bool hq ) { Engine::audioEngine()->changeQuality( AudioEngine::qualitySettings( - hq ? AudioEngine::qualitySettings::Mode_HighQuality : - AudioEngine::qualitySettings::Mode_Draft ) ); + hq ? AudioEngine::qualitySettings::Mode::HighQuality : + AudioEngine::qualitySettings::Mode::Draft ) ); } @@ -373,7 +376,7 @@ void SongEditor::selectRegionFromPixels(int xStart, int xEnd) //we save the position of scrollbars, mouse position and zooming level m_origin = QPoint(xStart, 0); m_scrollPos = QPoint(m_leftRightScroll->value(), contentWidget()->verticalScrollBar()->value()); - m_currentZoomingValue = zoomingModel()->value(); + m_rubberbandPixelsPerBar = pixelsPerBar(); //calculate the song position where the mouse was clicked m_rubberbandStartTimePos = TimePos((xStart - m_trackHeadWidth) @@ -405,10 +408,9 @@ void SongEditor::updateRubberband() int originX = m_origin.x(); //take care of the zooming - if (m_currentZoomingValue != m_zoomingModel->value()) + if (m_rubberbandPixelsPerBar != pixelsPerBar()) { - originX = m_trackHeadWidth + (originX - m_trackHeadWidth) - * m_zoomLevels[m_zoomingModel->value()] / m_zoomLevels[m_currentZoomingValue]; + originX = m_trackHeadWidth + (originX - m_trackHeadWidth) * pixelsPerBar() / m_rubberbandPixelsPerBar; } //take care of the scrollbar position @@ -458,17 +460,17 @@ void SongEditor::setEditMode( EditMode mode ) void SongEditor::setEditModeDraw() { - setEditMode(DrawMode); + setEditMode(EditMode::Draw); } void SongEditor::setEditModeKnife() { - setEditMode(KnifeMode); + setEditMode(EditMode::Knife); } void SongEditor::setEditModeSelect() { - setEditMode(SelectMode); + setEditMode(EditMode::Select); } void SongEditor::toggleProportionalSnap() @@ -498,7 +500,7 @@ void SongEditor::keyPressEvent( QKeyEvent * ke ) tick_t t = m_song->currentTick() - TimePos::ticksPerBar(); if( t >= 0 ) { - m_song->setPlayPos( t, Song::Mode_PlaySong ); + m_song->setPlayPos( t, Song::PlayMode::Song ); } } else if( ke->key() == Qt::Key_Right ) @@ -506,12 +508,12 @@ void SongEditor::keyPressEvent( QKeyEvent * ke ) tick_t t = m_song->currentTick() + TimePos::ticksPerBar(); if( t < MaxSongLength ) { - m_song->setPlayPos( t, Song::Mode_PlaySong ); + m_song->setPlayPos( t, Song::PlayMode::Song ); } } else if( ke->key() == Qt::Key_Home ) { - m_song->setPlayPos( 0, Song::Mode_PlaySong ); + m_song->setPlayPos( 0, Song::PlayMode::Song ); } else if( ke->key() == Qt::Key_Delete || ke->key() == Qt::Key_Backspace ) { @@ -530,9 +532,13 @@ void SongEditor::keyPressEvent( QKeyEvent * ke ) { selectAllClips( false ); } + else if (ke->key() == Qt::Key_0 && ke->modifiers() & Qt::ControlModifier) + { + m_zoomingModel->reset(); + } else { - QWidget::keyPressEvent( ke ); + QWidget::keyPressEvent(ke); } } @@ -541,35 +547,24 @@ void SongEditor::keyPressEvent( QKeyEvent * ke ) void SongEditor::wheelEvent( QWheelEvent * we ) { - if( we->modifiers() & Qt::ControlModifier ) + if ((we->modifiers() & Qt::ControlModifier) && (position(we).x() > m_trackHeadWidth)) { - int z = m_zoomingModel->value(); - - if(we->angleDelta().y() > 0) - { - z++; - } - else if(we->angleDelta().y() < 0) - { - z--; - } - z = qBound( 0, z, m_zoomingModel->size() - 1 ); - - int x = position(we).x() - m_trackHeadWidth; // bar based on the mouse x-position where the scroll wheel was used int bar = x / pixelsPerBar(); - // what would be the bar in the new zoom level on the very same mouse x - int newBar = x / DEFAULT_PIXELS_PER_BAR / m_zoomLevels[z]; - // scroll so the bar "selected" by the mouse x doesn't move on the screen + + // move zoom slider (pixelsPerBar will change automatically) + int step = we->modifiers() & Qt::ShiftModifier ? 1 : 5; + // when Alt is pressed, wheelEvent returns delta for x coordinate (mimics horizontal mouse wheel) + int direction = (we->angleDelta().y() + we->angleDelta().x()) > 0 ? 1 : -1; + m_zoomingModel->incValue(step * direction); + + // scroll to zooming around cursor's bar + int newBar = static_cast(x / pixelsPerBar()); m_leftRightScroll->setValue(m_leftRightScroll->value() + bar - newBar); - // update combobox with zooming-factor - m_zoomingModel->setValue( z ); - // update timeline - m_song->m_playPos[Song::Mode_PlaySong].m_timeLine-> - setPixelsPerBar( pixelsPerBar() ); + m_timeLine->setPixelsPerBar(pixelsPerBar()); // and make sure, all Clip's are resized and relocated realignTracks(); } @@ -618,7 +613,7 @@ void SongEditor::mousePressEvent(QMouseEvent *me) //we save the position of scrollbars, mouse position and zooming level m_scrollPos = QPoint(m_leftRightScroll->value(), contentWidget()->verticalScrollBar()->value()); m_origin = contentWidget()->mapFromParent(QPoint(me->pos().x(), me->pos().y())); - m_currentZoomingValue = zoomingModel()->value(); + m_rubberbandPixelsPerBar = pixelsPerBar(); //paint the rubberband rubberBand()->setEnabled(true); @@ -661,12 +656,12 @@ void SongEditor::setMasterVolume( int new_val ) { updateMasterVolumeFloat( new_val ); - if( !m_mvsStatus->isVisible() && !m_song->m_loadingProject + if (!m_mvsStatus->isVisible() && !m_song->m_loadingProject && m_masterVolumeSlider->showStatus() ) { - m_mvsStatus->moveGlobal( m_masterVolumeSlider, + m_mvsStatus->moveGlobal(m_masterVolumeSlider, QPoint( m_masterVolumeSlider->width() + 2, -2 ) ); - m_mvsStatus->setVisibilityTimeOut( 1000 ); + m_mvsStatus->setVisibilityTimeOut(1000); } Engine::audioEngine()->setMasterGain( new_val / 100.0f ); } @@ -676,7 +671,7 @@ void SongEditor::setMasterVolume( int new_val ) void SongEditor::showMasterVolumeFloat( void ) { - m_mvsStatus->moveGlobal( m_masterVolumeSlider, + m_mvsStatus->moveGlobal(m_masterVolumeSlider, QPoint( m_masterVolumeSlider->width() + 2, -2 ) ); m_mvsStatus->show(); updateMasterVolumeFloat( m_song->m_masterVolumeModel.value() ); @@ -687,7 +682,7 @@ void SongEditor::showMasterVolumeFloat( void ) void SongEditor::updateMasterVolumeFloat( int new_val ) { - m_mvsStatus->setText( tr( "Value: %1%" ).arg( new_val ) ); + m_mvsStatus->setText(tr("Value: %1%").arg(new_val)); } @@ -785,20 +780,12 @@ 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::Mode_PlaySong - && m_timeLine->autoScroll() == TimeLineWidget::AutoScrollEnabled) || + if( ( m_song->isPlaying() && m_song->m_playMode == Song::PlayMode::Song + && m_timeLine->autoScroll() == TimeLineWidget::AutoScrollState::Enabled) || m_scrollBack == true ) { m_smoothScroll = ConfigManager::inst()->value( "ui", "smoothscroll" ).toInt(); @@ -817,8 +804,7 @@ void SongEditor::updatePosition( const TimePos & t ) m_scrollBack = false; } - const int x = m_song->m_playPos[Song::Mode_PlaySong].m_timeLine-> - markerX( t ) + 8; + const int x = m_timeLine->markerX(t); if( x >= trackOpWidth + widgetWidth -1 ) { m_positionLine->show(); @@ -843,17 +829,50 @@ void SongEditor::updatePositionLine() +//! Convert zoom slider's value to bar width in pixels +int SongEditor::calculatePixelsPerBar() const +{ + // What we need to raise 2 by to get MIN_PIXELS_PER_BAR and MAX_PIXELS_PER_BAR + static const double minExp = std::log2(MIN_PIXELS_PER_BAR); + static const double maxExp = std::log2(MAX_PIXELS_PER_BAR); + static const double stepsInv = 1 / static_cast(ZOOM_STEPS) * (maxExp - minExp); + double exponent = m_zoomingModel->value() * stepsInv + minExp; + + double ppb = std::exp2(exponent); + + return static_cast(std::round(ppb)); +} + + + + +//! Convert bar width in pixels to zoom slider value +int SongEditor::calculateZoomSliderValue(int pixelsPerBar) const +{ + // What we need to raise 2 by to get MIN_PIXELS_PER_BAR and MAX_PIXELS_PER_BAR + static const double minExp = std::log2(MIN_PIXELS_PER_BAR); + static const double maxExp = std::log2(MAX_PIXELS_PER_BAR); + double exponent = std::log2(pixelsPerBar); + + double sliderValue = (exponent - minExp) / (maxExp - minExp) * ZOOM_STEPS; + + return static_cast(std::round(sliderValue)); +} + + + + void SongEditor::zoomingChanged() { - setPixelsPerBar( m_zoomLevels[m_zoomingModel->value()] * DEFAULT_PIXELS_PER_BAR ); + int ppb = calculatePixelsPerBar(); + setPixelsPerBar(ppb); - m_song->m_playPos[Song::Mode_PlaySong].m_timeLine-> - setPixelsPerBar( pixelsPerBar() ); + m_timeLine->setPixelsPerBar(ppb); realignTracks(); updateRubberband(); m_timeLine->setSnapSize(getSnapSize()); - - emit zoomingValueChanged( m_zoomLevels[m_zoomingModel->value()] ); + + emit pixelsPerBarChanged(ppb); } @@ -871,7 +890,7 @@ void SongEditor::selectAllClips( bool select ) bool SongEditor::allowRubberband() const { - return m_mode == SelectMode; + return m_mode == EditMode::Select; } @@ -879,7 +898,7 @@ bool SongEditor::allowRubberband() const bool SongEditor::knifeMode() const { - return m_mode == KnifeMode; + return m_mode == EditMode::Knife; } @@ -905,14 +924,6 @@ int SongEditor::indexOfTrackView(const TrackView *tv) -ComboBoxModel *SongEditor::zoomingModel() const -{ - return m_zoomingModel; -} - - - - ComboBoxModel *SongEditor::snappingModel() const { return m_snappingModel; @@ -997,16 +1008,19 @@ SongEditorWindow::SongEditorWindow(Song* song) : auto zoom_lbl = new QLabel(m_toolBar); zoom_lbl->setPixmap( embed::getIconPixmap( "zoom" ) ); - //Set up zooming-stuff - m_zoomingComboBox = new ComboBox( m_toolBar ); - m_zoomingComboBox->setFixedSize( 80, ComboBox::DEFAULT_HEIGHT ); - m_zoomingComboBox->move( 580, 4 ); - m_zoomingComboBox->setModel(m_editor->m_zoomingModel); - m_zoomingComboBox->setToolTip(tr("Horizontal zooming")); - connect(m_editor->zoomingModel(), SIGNAL(dataChanged()), this, SLOT(updateSnapLabel())); + // Set slider zoom + m_zoomingSlider = new AutomatableSlider(m_toolBar, tr("Zoom")); + m_zoomingSlider->setModel(m_editor->m_zoomingModel); + m_zoomingSlider->setOrientation(Qt::Horizontal); + m_zoomingSlider->setPageStep(1); + m_zoomingSlider->setFocusPolicy(Qt::NoFocus); + m_zoomingSlider->setFixedSize(100, 26); + m_zoomingSlider->setToolTip(tr("Zoom")); + m_zoomingSlider->setContextMenuPolicy(Qt::NoContextMenu); + connect(m_editor->m_zoomingModel, SIGNAL(dataChanged()), this, SLOT(updateSnapLabel())); zoomToolBar->addWidget( zoom_lbl ); - zoomToolBar->addWidget( m_zoomingComboBox ); + zoomToolBar->addWidget(m_zoomingSlider); DropToolBar *snapToolBar = addDropToolBarToTop(tr("Snap controls")); auto snap_lbl = new QLabel(m_toolBar); @@ -1040,7 +1054,7 @@ SongEditorWindow::SongEditorWindow(Song* song) : QSize SongEditorWindow::sizeHint() const { - return {720, 300}; + return {900, 300}; } void SongEditorWindow::updateSnapLabel(){ @@ -1085,7 +1099,7 @@ void SongEditorWindow::changeEvent(QEvent *event) void SongEditorWindow::play() { emit playTriggered(); - if( Engine::getSong()->playMode() != Song::Mode_PlaySong ) + if( Engine::getSong()->playMode() != Song::PlayMode::Song ) { Engine::getSong()->playSong(); } @@ -1146,3 +1160,5 @@ void SongEditorWindow::adjustUiAfterProjectLoad() } // namespace lmms::gui + + diff --git a/src/gui/editors/TimeLineWidget.cpp b/src/gui/editors/TimeLineWidget.cpp index 12b6a11f5..7657e2916 100644 --- a/src/gui/editors/TimeLineWidget.cpp +++ b/src/gui/editors/TimeLineWidget.cpp @@ -22,75 +22,49 @@ * */ +#include "TimeLineWidget.h" + +#include #include -#include +#include +#include #include #include +#include #include - -#include "TimeLineWidget.h" +#include "ConfigManager.h" #include "embed.h" -#include "NStateButton.h" #include "GuiApplication.h" +#include "NStateButton.h" #include "TextFloat.h" namespace lmms::gui { - -QPixmap * TimeLineWidget::s_posMarkerPixmap = nullptr; - -TimeLineWidget::TimeLineWidget( const int xoff, const int yoff, const float ppb, - Song::PlayPos & pos, const TimePos & begin, Song::PlayModes 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( AutoScrollEnabled ), - m_loopPoints( LoopPointsDisabled ), - m_behaviourAtStop( 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( NoAction ), - m_moveXOff( 0 ) +namespace { - m_loopPos[0] = 0; - m_loopPos[1] = DefaultTicksPerBar; - - if( s_posMarkerPixmap == nullptr ) - { - s_posMarkerPixmap = new QPixmap( embed::getIconPixmap( - "playpos_marker" ) ); - } + constexpr int MIN_BAR_LABEL_DISTANCE = 35; +} +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} +{ 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())); @@ -101,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; } @@ -113,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); @@ -132,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" ), @@ -147,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( 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", m_loopPoints ); - _this.setAttribute( "stopbehaviour", 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( 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 ); + 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 ); @@ -239,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()); @@ -267,15 +176,16 @@ 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; + while (barLabelInterval * m_ppb < MIN_BAR_LABEL_DISTANCE) { barLabelInterval *= 2; } for( int i = 0; x + i * m_ppb < width(); ++i ) { ++barNumber; - if( ( barNumber - 1 ) % - qMax( 1, qRound( 1.0f / 3.0f * - TimePos::ticksPerBar() / m_ppb ) ) == 0 ) + if ((barNumber - 1) % barLabelInterval == 0) { const int cx = x + qRound( i * m_ppb ); p.setPen( barLineColor ); @@ -287,145 +197,261 @@ 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 = 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 = SelectSongClip; + 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 ? MoveLoopBegin : MoveLoopEnd; - std::sort(std::begin(m_loopPos), std::end(m_loopPos)); - m_loopPos[( m_action == MoveLoopBegin ) ? 0 : 1] = t; + m_action = Action::MovePositionMarker; } - if( m_action == MoveLoopBegin || m_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 MovePositionMarker: - m_pos.setTicks(t.getTicks()); - Engine::getSong()->setToTime(t, m_mode); + case Action::MovePositionMarker: + m_pos.setTicks(timeAtCursor.getTicks()); + Engine::getSong()->setToTime(timeAtCursor, m_mode); if (!( Engine::getSong()->isPlaying())) { - //Song::Mode_None is used when nothing is being played. - Engine::getSong()->setToTime(t, Song::Mode_None); + //Song::PlayMode::None is used when nothing is being played. + Engine::getSong()->setToTime(timeAtCursor, Song::PlayMode::None); } m_pos.setCurrentFrame( 0 ); m_pos.setJumped( true ); updatePosition(); - positionMarkerMoved(); break; - case MoveLoopBegin: - case MoveLoopEnd: + case Action::MoveLoopBegin: + case Action::MoveLoopEnd: { - const int i = m_action - MoveLoopBegin; // i == 0 || i == 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 == 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 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; m_hint = nullptr; - if ( m_action == SelectSongClip ) { emit selectionFinished(); } - m_action = NoAction; + if ( m_action == Action::SelectSongClip ) { emit selectionFinished(); } + 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/editors/TrackContainerView.cpp b/src/gui/editors/TrackContainerView.cpp index 351fe8295..60a468380 100644 --- a/src/gui/editors/TrackContainerView.cpp +++ b/src/gui/editors/TrackContainerView.cpp @@ -90,13 +90,13 @@ TrackContainerView::TrackContainerView( TrackContainer * _tc ) : //keeps the direction of the widget, undepended on the locale setLayoutDirection( Qt::LeftToRight ); auto layout = new QVBoxLayout(this); - layout->setMargin( 0 ); + layout->setContentsMargins(0, 0, 0, 0); layout->setSpacing( 0 ); layout->addWidget( m_scrollArea ); auto scrollContent = new QWidget; m_scrollLayout = new QVBoxLayout( scrollContent ); - m_scrollLayout->setMargin( 0 ); + m_scrollLayout->setContentsMargins(0, 0, 0, 0); m_scrollLayout->setSpacing( 0 ); m_scrollLayout->setSizeConstraint( QLayout::SetMinAndMaxSize ); @@ -199,8 +199,8 @@ void TrackContainerView::moveTrackView( TrackView * trackView, int indexTo ) Track * track = m_tc->m_tracks[indexFrom]; - m_tc->m_tracks.remove( indexFrom ); - m_tc->m_tracks.insert( indexTo, track ); + m_tc->m_tracks.erase(m_tc->m_tracks.begin() + indexFrom); + m_tc->m_tracks.insert(m_tc->m_tracks.begin() + indexTo, track); m_trackViews.move( indexFrom, indexTo ); realignTracks(); @@ -372,8 +372,8 @@ void TrackContainerView::dragEnterEvent( QDragEnterEvent * _dee ) QString( "presetfile,pluginpresetfile,samplefile,instrument," "importedproject,soundfontfile,patchfile,vstpluginfile,projectfile," "track_%1,track_%2" ). - arg( Track::InstrumentTrack ). - arg( Track::SampleTrack ) ); + arg( static_cast(Track::Type::Instrument) ). + arg( static_cast(Track::Type::Sample) ) ); } @@ -394,7 +394,7 @@ void TrackContainerView::dropEvent( QDropEvent * _de ) QString value = StringPairDrag::decodeValue( _de ); if( type == "instrument" ) { - auto it = dynamic_cast(Track::create(Track::InstrumentTrack, m_tc)); + auto it = dynamic_cast(Track::create(Track::Type::Instrument, m_tc)); auto ilt = new InstrumentLoaderThread(this, it, value); ilt->start(); //it->toggledInstrumentTrackButton( true ); @@ -404,7 +404,7 @@ void TrackContainerView::dropEvent( QDropEvent * _de ) || type == "soundfontfile" || type == "vstpluginfile" || type == "patchfile" ) { - auto it = dynamic_cast(Track::create(Track::InstrumentTrack, m_tc)); + auto it = dynamic_cast(Track::create(Track::Type::Instrument, m_tc)); PluginFactory::PluginInfoAndKey piakn = getPluginFactory()->pluginSupportingExtension(FileItem::extension(value)); Instrument * i = it->loadInstrument(piakn.info.name(), &piakn.key); @@ -415,7 +415,7 @@ void TrackContainerView::dropEvent( QDropEvent * _de ) else if( type == "presetfile" ) { DataFile dataFile( value ); - auto it = dynamic_cast(Track::create(Track::InstrumentTrack, m_tc)); + auto it = dynamic_cast(Track::create(Track::Type::Instrument, m_tc)); it->setSimpleSerializing(); it->loadSettings( dataFile.content().toElement() ); //it->toggledInstrumentTrackButton( true ); diff --git a/src/gui/instrument/EnvelopeAndLfoView.cpp b/src/gui/instrument/EnvelopeAndLfoView.cpp index df0e1120d..c2e642b01 100644 --- a/src/gui/instrument/EnvelopeAndLfoView.cpp +++ b/src/gui/instrument/EnvelopeAndLfoView.cpp @@ -23,218 +23,170 @@ * */ -#include -#include - #include "EnvelopeAndLfoView.h" + +#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" #include "TextFloat.h" #include "Track.h" +#include + + 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 = [&](const QString& activePixmap, const QString& 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( knobBright_26, 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( knobBright_26, 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( knobBright_26, 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( knobBright_26, 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( knobBright_26, 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( knobBright_26, 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( knobBright_26, 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( knobBright_26, 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( knobBright_26, 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( knobBright_26, 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( knobBright_26, 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 +203,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 +211,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,47 +225,11 @@ 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, QString( "samplefile,clip_%1" ).arg( - Track::SampleTrack ) ); + static_cast(Track::Type::Sample) ) ); } @@ -322,21 +241,21 @@ 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(EnvelopeAndLfoParameters::UserDefinedWave); + m_params->m_lfoWaveModel.setValue(static_cast(EnvelopeAndLfoParameters::LfoShape::UserDefinedWave)); _de->accept(); update(); } - else if( type == QString( "clip_%1" ).arg( Track::SampleTrack ) ) + 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(EnvelopeAndLfoParameters::UserDefinedWave); + m_params->m_lfoWaveModel.setValue(static_cast(EnvelopeAndLfoParameters::LfoShape::UserDefinedWave)); _de->accept(); update(); } @@ -345,181 +264,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( m_params->m_lfoWaveModel.value() ) - { - case EnvelopeAndLfoParameters::SineWave: - val = Oscillator::sinSample( phase ); - break; - case EnvelopeAndLfoParameters::TriangleWave: - val = Oscillator::triangleSample( - phase ); - break; - case EnvelopeAndLfoParameters::SawWave: - val = Oscillator::sawSample( phase ); - break; - case EnvelopeAndLfoParameters::SquareWave: - val = Oscillator::squareSample( phase ); - break; - case EnvelopeAndLfoParameters::RandomWave: - if( x % (int)( 900 * m_lfoSpeedKnob->value() + 1 ) == 0 ) - { - m_randomGraph = Oscillator::noiseSample( 0.0f ); - } - val = m_randomGraph; - break; - case EnvelopeAndLfoParameters::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( m_params->m_lfoWaveModel.value() == - EnvelopeAndLfoParameters::UserDefinedWave ) + 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." ), @@ -528,11 +278,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 99c68a5ed..ad8abe735 100644 --- a/src/gui/instrument/InstrumentFunctionViews.cpp +++ b/src/gui/instrument/InstrumentFunctionViews.cpp @@ -44,10 +44,10 @@ InstrumentFunctionNoteStackingView::InstrumentFunctionNoteStackingView( Instrume m_cc( cc ), m_chordsGroupBox( new GroupBox( tr( "STACKING" ) ) ), m_chordsComboBox( new ComboBox() ), - m_chordRangeKnob( new Knob( knobBright_26 ) ) + m_chordRangeKnob( new Knob( KnobType::Bright26 ) ) { auto topLayout = new QHBoxLayout(this); - topLayout->setMargin( 0 ); + topLayout->setContentsMargins(0, 0, 0, 0); topLayout->addWidget( m_chordsGroupBox ); auto mainLayout = new QGridLayout(m_chordsGroupBox); @@ -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)" ) ); @@ -98,18 +98,18 @@ InstrumentFunctionArpeggioView::InstrumentFunctionArpeggioView( InstrumentFuncti m_a( arp ), m_arpGroupBox( new GroupBox( tr( "ARPEGGIO" ) ) ), m_arpComboBox( new ComboBox() ), - m_arpRangeKnob( new Knob( knobBright_26 ) ), - m_arpRepeatsKnob( new Knob( knobBright_26 ) ), - m_arpCycleKnob( new Knob( knobBright_26 ) ), - m_arpSkipKnob( new Knob( knobBright_26 ) ), - m_arpMissKnob( new Knob( knobBright_26 ) ), - m_arpTimeKnob( new TempoSyncKnob( knobBright_26 ) ), - m_arpGateKnob( new Knob( knobBright_26 ) ), + m_arpRangeKnob( new Knob( KnobType::Bright26 ) ), + m_arpRepeatsKnob( new Knob( KnobType::Bright26 ) ), + m_arpCycleKnob( new Knob( KnobType::Bright26 ) ), + m_arpSkipKnob( new Knob( KnobType::Bright26 ) ), + m_arpMissKnob( new Knob( KnobType::Bright26 ) ), + m_arpTimeKnob( new TempoSyncKnob( KnobType::Bright26 ) ), + m_arpGateKnob( new Knob( KnobType::Bright26 ) ), m_arpDirectionComboBox( new ComboBox() ), m_arpModeComboBox( new ComboBox() ) { auto topLayout = new QHBoxLayout(this); - topLayout->setMargin( 0 ); + topLayout->setContentsMargins(0, 0, 0, 0); topLayout->addWidget( m_arpGroupBox ); auto mainLayout = new QGridLayout(m_arpGroupBox); @@ -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 8d7cf9dda..e3f10bd1a 100644 --- a/src/gui/instrument/InstrumentMidiIOView.cpp +++ b/src/gui/instrument/InstrumentMidiIOView.cpp @@ -48,7 +48,7 @@ InstrumentMidiIOView::InstrumentMidiIOView( QWidget* parent ) : m_wpBtn( nullptr ) { auto layout = new QVBoxLayout(this); - layout->setMargin( 5 ); + layout->setContentsMargins(5, 5, 5, 5); m_midiInputGroupBox = new GroupBox( tr( "ENABLE MIDI INPUT" ) ); layout->addWidget( m_midiInputGroupBox ); @@ -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 dd5c14a09..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( knobBright_26, 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( knobBright_26, 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 70c11976a..86d9086c8 100644 --- a/src/gui/instrument/InstrumentTrackWindow.cpp +++ b/src/gui/instrument/InstrumentTrackWindow.cpp @@ -42,14 +42,13 @@ #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" #include "InstrumentMidiIOView.h" -#include "InstrumentMiscView.h" +#include "InstrumentTuningView.h" #include "InstrumentSoundShapingView.h" #include "InstrumentTrack.h" #include "InstrumentTrackView.h" @@ -90,16 +89,12 @@ InstrumentTrackWindow::InstrumentTrackWindow( InstrumentTrackView * _itv ) : // init own layout + widgets setFocusPolicy( Qt::StrongFocus ); auto vlayout = new QVBoxLayout(this); - vlayout->setMargin( 0 ); + 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& ) ) ); @@ -148,7 +142,7 @@ InstrumentTrackWindow::InstrumentTrackWindow( InstrumentTrackView * _itv ) : Qt::Alignment widgetAlignment = Qt::AlignHCenter | Qt::AlignCenter; // set up volume knob - m_volumeKnob = new Knob( knobBright_26, nullptr, tr( "Volume" ) ); + m_volumeKnob = new Knob( KnobType::Bright26, nullptr, tr( "Volume" ) ); m_volumeKnob->setVolumeKnob( true ); m_volumeKnob->setHintText( tr( "Volume:" ), "%" ); @@ -162,7 +156,7 @@ InstrumentTrackWindow::InstrumentTrackWindow( InstrumentTrackView * _itv ) : // set up panning knob - m_panningKnob = new Knob( knobBright_26, nullptr, tr( "Panning" ) ); + m_panningKnob = new Knob( KnobType::Bright26, nullptr, tr( "Panning" ) ); m_panningKnob->setHintText( tr( "Panning:" ), "" ); basicControlsLayout->addWidget( m_panningKnob, 0, 1 ); @@ -178,7 +172,7 @@ InstrumentTrackWindow::InstrumentTrackWindow( InstrumentTrackView * _itv ) : // set up pitch knob - m_pitchKnob = new Knob( knobBright_26, nullptr, tr( "Pitch" ) ); + m_pitchKnob = new Knob( KnobType::Bright26, nullptr, tr( "Pitch" ) ); m_pitchKnob->setHintText( tr( "Pitch:" ), " " + tr( "cents" ) ); basicControlsLayout->addWidget( m_pitchKnob, 0, 3 ); @@ -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 ); @@ -246,7 +240,7 @@ InstrumentTrackWindow::InstrumentTrackWindow( InstrumentTrackView * _itv ) : // FUNC tab auto instrumentFunctions = new QWidget(m_tabWidget); auto instrumentFunctionsLayout = new QVBoxLayout(instrumentFunctions); - instrumentFunctionsLayout->setMargin( 5 ); + instrumentFunctionsLayout->setContentsMargins(5, 5, 5, 5); m_noteStackingView = new InstrumentFunctionNoteStackingView( &m_track->m_noteStacking ); m_arpeggioView = new InstrumentFunctionArpeggioView( &m_track->m_arpeggio ); @@ -255,25 +249,25 @@ InstrumentTrackWindow::InstrumentTrackWindow( InstrumentTrackView * _itv ) : instrumentFunctionsLayout->addStretch(); // MIDI tab - m_midiView = new InstrumentMidiIOView( m_tabWidget ); + m_midiView = new InstrumentMidiIOView(m_tabWidget); // FX tab - m_effectView = new EffectRackView( m_track->m_audioPort.effects(), m_tabWidget ); + m_effectView = new EffectRackView(m_track->m_audioPort.effects(), m_tabWidget); - // MISC tab - m_miscView = new InstrumentMiscView( m_track, m_tabWidget ); + // Tuning tab + m_tuningView = new InstrumentTuningView(m_track, m_tabWidget); - m_tabWidget->addTab( m_ssView, tr( "Envelope, filter & LFO" ), "env_lfo_tab", 1 ); - m_tabWidget->addTab( instrumentFunctions, tr( "Chord stacking & arpeggio" ), "func_tab", 2 ); - m_tabWidget->addTab( m_effectView, tr( "Effects" ), "fx_tab", 3 ); - m_tabWidget->addTab( m_midiView, tr( "MIDI" ), "midi_tab", 4 ); - m_tabWidget->addTab( m_miscView, tr( "Miscellaneous" ), "misc_tab", 5 ); + m_tabWidget->addTab(m_ssView, tr("Envelope, filter & LFO"), "env_lfo_tab", 1); + m_tabWidget->addTab(instrumentFunctions, tr("Chord stacking & arpeggio"), "func_tab", 2); + m_tabWidget->addTab(m_effectView, tr("Effects"), "fx_tab", 3); + m_tabWidget->addTab(m_midiView, tr("MIDI"), "midi_tab", 4); + m_tabWidget->addTab(m_tuningView, tr("Tuning and transposition"), "tuning_tab", 5); adjustTabSize(m_ssView); adjustTabSize(instrumentFunctions); m_effectView->resize(EffectRackView::DEFAULT_WIDTH, INSTRUMENT_HEIGHT - 4 - 1); adjustTabSize(m_midiView); - adjustTabSize(m_miscView); + adjustTabSize(m_tuningView); // setup piano-widget m_pianoView = new PianoView( this ); @@ -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::IsNotBendable ) == false ) + if( m_track->instrument() && m_track->instrument()->flags().testFlag( Instrument::Flag::IsNotBendable ) == false ) { m_pitchKnob->setModel( &m_track->m_pitchModel ); m_pitchRangeSpinBox->setModel( &m_track->m_pitchRangeModel ); @@ -374,14 +368,16 @@ void InstrumentTrackWindow::modelChanged() m_pitchRangeLabel->hide(); } - if (m_track->instrument() && m_track->instrument()->flags().testFlag(Instrument::IsMidiBased)) + if (m_track->instrument() && m_track->instrument()->flags().testFlag(Instrument::Flag::IsMidiBased)) { - m_miscView->microtunerGroupBox()->hide(); + m_tuningView->microtunerNotSupportedLabel()->show(); + m_tuningView->microtunerGroupBox()->hide(); m_track->m_microtuner.enabledModel()->setValue(false); } else { - m_miscView->microtunerGroupBox()->show(); + m_tuningView->microtunerNotSupportedLabel()->hide(); + m_tuningView->microtunerGroupBox()->show(); } m_ssView->setModel( &m_track->m_soundShaping ); @@ -389,11 +385,11 @@ void InstrumentTrackWindow::modelChanged() m_arpeggioView->setModel( &m_track->m_arpeggio ); m_midiView->setModel( &m_track->m_midiPort ); m_effectView->setModel( m_track->m_audioPort.effects() ); - m_miscView->pitchGroupBox()->setModel(&m_track->m_useMasterPitchModel); - m_miscView->microtunerGroupBox()->setModel(m_track->m_microtuner.enabledModel()); - m_miscView->scaleCombo()->setModel(m_track->m_microtuner.scaleModel()); - m_miscView->keymapCombo()->setModel(m_track->m_microtuner.keymapModel()); - m_miscView->rangeImportCheckbox()->setModel(m_track->m_microtuner.keyRangeImportModel()); + m_tuningView->pitchGroupBox()->setModel(&m_track->m_useMasterPitchModel); + m_tuningView->microtunerGroupBox()->setModel(m_track->m_microtuner.enabledModel()); + m_tuningView->scaleCombo()->setModel(m_track->m_microtuner.scaleModel()); + m_tuningView->keymapCombo()->setModel(m_track->m_microtuner.keymapModel()); + m_tuningView->rangeImportCheckbox()->setModel(m_track->m_microtuner.keyRangeImportModel()); updateName(); } @@ -418,14 +414,14 @@ 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 && !sfd.selectedFiles().isEmpty() && !sfd.selectedFiles().first().isEmpty() ) { - DataFile dataFile(DataFile::InstrumentTrackSettings); + DataFile dataFile(DataFile::Type::InstrumentTrackSettings); QDomElement& content(dataFile.content()); m_track->setSimpleSerializing(); @@ -466,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::IsSingleStreamed ) ); + m_ssView->setFunctionsHidden( m_track->m_instrument->flags().testFlag( Instrument::Flag::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/InstrumentMiscView.cpp b/src/gui/instrument/InstrumentTuningView.cpp similarity index 69% rename from src/gui/instrument/InstrumentMiscView.cpp rename to src/gui/instrument/InstrumentTuningView.cpp index ae7bd85cd..9950f3879 100644 --- a/src/gui/instrument/InstrumentMiscView.cpp +++ b/src/gui/instrument/InstrumentTuningView.cpp @@ -1,8 +1,8 @@ /* - * InstrumentMiscView.cpp - Miscellaneous instrument settings + * InstrumentTuningView.cpp - Instrument settings for tuning and transpositions * * Copyright (c) 2005-2014 Tobias Doerffel - * Copyright (c) 2020 Martin Pavelek + * Copyright (c) 2020-2022 Martin Pavelek * * This file is part of LMMS - https://lmms.io * @@ -23,28 +23,32 @@ * */ -#include "InstrumentMiscView.h" +#include "InstrumentTuningView.h" #include #include +#include #include #include "ComboBox.h" #include "GroupBox.h" +#include "GuiApplication.h" #include "gui_templates.h" #include "InstrumentTrack.h" #include "LedCheckBox.h" +#include "MainWindow.h" +#include "PixmapButton.h" namespace lmms::gui { -InstrumentMiscView::InstrumentMiscView(InstrumentTrack *it, QWidget *parent) : +InstrumentTuningView::InstrumentTuningView(InstrumentTrack *it, QWidget *parent) : QWidget(parent) { auto layout = new QVBoxLayout(this); - layout->setMargin(5); + layout->setContentsMargins(5, 5, 5, 5); // Master pitch toggle m_pitchGroupBox = new GroupBox(tr("GLOBAL TRANSPOSITION")); @@ -56,10 +60,15 @@ InstrumentMiscView::InstrumentMiscView(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 + m_microtunerNotSupportedLabel = new QLabel(tr("Microtuner is not available for MIDI-based instruments.")); + m_microtunerNotSupportedLabel->setWordWrap(true); + m_microtunerNotSupportedLabel->hide(); + layout->addWidget(m_microtunerNotSupportedLabel); + m_microtunerGroupBox = new GroupBox(tr("MICROTUNER")); m_microtunerGroupBox->setModel(it->m_microtuner.enabledModel()); layout->addWidget(m_microtunerGroupBox); @@ -67,8 +76,22 @@ InstrumentMiscView::InstrumentMiscView(InstrumentTrack *it, QWidget *parent) : auto microtunerLayout = new QVBoxLayout(m_microtunerGroupBox); microtunerLayout->setContentsMargins(8, 18, 8, 8); + auto scaleEditLayout = new QHBoxLayout(); + scaleEditLayout->setContentsMargins(0, 0, 4, 0); + microtunerLayout->addLayout(scaleEditLayout); + auto scaleLabel = new QLabel(tr("Active scale:")); - microtunerLayout->addWidget(scaleLabel); + scaleEditLayout->addWidget(scaleLabel); + + QPixmap editPixmap(embed::getIconPixmap("edit_draw_small")); + auto editPixButton = new PixmapButton(this, tr("Edit scales and keymaps")); + editPixButton->setToolTip(tr("Edit scales and keymaps")); + editPixButton->setInactiveGraphic(editPixmap); + editPixButton->setActiveGraphic(editPixmap); + editPixButton->setFixedSize(16, 16); + connect(editPixButton, SIGNAL(clicked()), getGUI()->mainWindow(), SLOT(toggleMicrotunerWin())); + + scaleEditLayout->addWidget(editPixButton); m_scaleCombo = new ComboBox(); m_scaleCombo->setModel(it->m_microtuner.scaleModel()); @@ -96,7 +119,7 @@ InstrumentMiscView::InstrumentMiscView(InstrumentTrack *it, QWidget *parent) : grooveLayout->setContentsMargins(8, 18, 8, 8); QLabel *grooveLabel = new QLabel(tr("Enables the use of Groove")); - grooveLabel->setFont(pointSize<8>(tlabel->font())); + grooveLabel->setFont(adjustedToPixelSize(tlabel->font(), 8)); grooveLayout->addWidget(grooveLabel); // Fill remaining space 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 7478c8204..13628d97e 100644 --- a/src/gui/instrument/PianoView.cpp +++ b/src/gui/instrument/PianoView.cpp @@ -64,18 +64,9 @@ namespace lmms::gui */ auto WhiteKeys = std::array { - Key_C, Key_D, Key_E, Key_F, Key_G, Key_A, Key_H + 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 */ @@ -95,35 +86,10 @@ PianoView::PianoView(QWidget *parent) : QWidget(parent), /*!< Our parent */ ModelView(nullptr, this), /*!< Our view Model */ m_piano(nullptr), /*!< Our piano Model */ - m_startKey(Key_C + Octave_3*KeysPerOctave), /*!< The first key displayed? */ + m_startKey(Octave::Octave_3 + Key::C), /*!< The first key displayed? */ 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); @@ -138,7 +104,7 @@ PianoView::PianoView(QWidget *parent) : m_pianoScroll = new QScrollBar( Qt::Horizontal, this ); m_pianoScroll->setSingleStep( 1 ); m_pianoScroll->setPageStep( 20 ); - m_pianoScroll->setValue(Octave_3 * Piano::WhiteKeysPerOctave); + m_pianoScroll->setValue(static_cast(Octave::Octave_3) * Piano::WhiteKeysPerOctave); // and connect it to this widget connect( m_pianoScroll, SIGNAL(valueChanged(int)), @@ -147,7 +113,7 @@ PianoView::PianoView(QWidget *parent) : // create a layout for ourselves auto layout = new QVBoxLayout(this); layout->setSpacing( 0 ); - layout->setMargin( 0 ); + layout->setContentsMargins(0, 0, 0, 0); layout->addSpacing( PIANO_BASE+PW_WHITE_KEY_HEIGHT ); layout->addWidget( m_pianoScroll ); @@ -155,14 +121,10 @@ PianoView::PianoView(QWidget *parent) : connect(Engine::getSong(), SIGNAL(keymapListChanged(int)), this, SLOT(update())); } -/*! \brief Map a keyboard key being pressed to a note in our keyboard view - * - * \param _k The keyboard scan code of the key being pressed. - * \todo check the scan codes for ',' = c, 'L' = c#, '.' = d, ':' = d#, - * '/' = d, '[' = f', '=' = f'#, ']' = g' - Paul's additions - */ -int PianoView::getKeyFromKeyEvent( QKeyEvent * _ke ) +static int getKeyOffsetFromKeyEvent( QKeyEvent * _ke ) { + // TODO: check the scan codes for ',' = c, 'L' = c#, '.' = d, ':' = d#, + // '/' = d, '[' = f', '=' = f'#, ']' = g' - Paul's additions #ifdef LMMS_BUILD_APPLE const int k = _ke->nativeVirtualKey(); #else @@ -297,8 +259,14 @@ int PianoView::getKeyFromKeyEvent( QKeyEvent * _ke ) return -100; } - - +/*! \brief Map a keyboard key being pressed to a note in our keyboard view + * + */ +int PianoView::getKeyFromKeyEvent( QKeyEvent * ke ) +{ + const auto key = static_cast(getKeyOffsetFromKeyEvent(ke)); + return DefaultOctave + key - KeysPerOctave; +} /*! \brief Register a change to this piano display view * @@ -320,70 +288,65 @@ void PianoView::modelChanged() -// gets the key from the given mouse-position +// Gets the key from the given mouse position /*! \brief Get the key from the mouse position in the piano display * - * First we determine it roughly by the position of the point given in - * white key widths from our start. We then add in any black keys that - * might have been skipped over (they take a key number, but no 'white - * key' space). We then add in our starting key number. - * - * We then determine whether it was a black key that was pressed by - * checking whether it was within the vertical range of black keys. - * Black keys sit exactly between white keys on this keyboard, so - * we then shift the note down or up if we were in the left or right - * half of the white note. We only do this, of course, if the white - * note has a black key on that side, so to speak. - * - * This function returns const because there is a linear mapping from - * the point given to the key returned that never changes. - * - * \param _p The point that the mouse was pressed. + * \param p The point that the mouse was pressed. */ -int PianoView::getKeyFromMouse( const QPoint & _p ) const +int PianoView::getKeyFromMouse(const QPoint& p) const { - int offset = _p.x() % PW_WHITE_KEY_WIDTH; - if( offset < 0 ) offset += PW_WHITE_KEY_WIDTH; - int key_num = ( _p.x() - offset) / PW_WHITE_KEY_WIDTH; + // The left-most key visible in the piano display is always white + const int startingWhiteKey = m_pianoScroll->value(); - for( int i = 0; i <= key_num; ++i ) + // Adjust the mouse x position as if x == 0 was the left side of the lowest key + const int adjX = p.x() + (startingWhiteKey * PW_WHITE_KEY_WIDTH); + + // Can early return for notes too low + if (adjX <= 0) { return 0; } + + // Now we can calculate the key number (in only white keys) and the octave + const int whiteKey = adjX / PW_WHITE_KEY_WIDTH; + const int octave = whiteKey / Piano::WhiteKeysPerOctave; + + // Calculate for full octaves + int key = octave * KeysPerOctave; + + // Adjust for white notes in the current octave + // (WhiteKeys maps each white key to the number of notes to their left in the octave) + key += static_cast(WhiteKeys[whiteKey % Piano::WhiteKeysPerOctave]); + + // Might be a black key, which would require further adjustment + if (p.y() < PIANO_BASE + PW_BLACK_KEY_HEIGHT) { - if ( Piano::isBlackKey( m_startKey+i ) ) + // Maps white keys to neighboring black keys + static constexpr std::array neighboringKeyMap { + std::pair{ 0, 1 }, // C --> no B#; C# + std::pair{ 1, 1 }, // D --> C#; D# + std::pair{ 1, 0 }, // E --> D#; no E# + std::pair{ 0, 1 }, // F --> no E#; F# + std::pair{ 1, 1 }, // G --> F#; G# + std::pair{ 1, 1 }, // A --> G#; A# + std::pair{ 1, 0 }, // B --> A#; no B# + }; + + const auto neighboringBlackKeys = neighboringKeyMap[whiteKey % Piano::WhiteKeysPerOctave]; + const int offset = adjX - (whiteKey * PW_WHITE_KEY_WIDTH); // mouse X offset from white key + + if (offset < PW_BLACK_KEY_WIDTH / 2) { - ++key_num; + // At the location of a (possibly non-existent) black key on the left side + key -= neighboringBlackKeys.first; } - } - for( int i = 0; i >= key_num; --i ) - { - if ( Piano::isBlackKey( m_startKey+i ) ) + else if (offset > PW_WHITE_KEY_WIDTH - (PW_BLACK_KEY_WIDTH / 2)) { - --key_num; + // At the location of a (possibly non-existent) black key on the right side + key += neighboringBlackKeys.second; } + + // For white keys in between black keys, no further adjustment is needed } - key_num += m_startKey; - - // is it a black key? - if( _p.y() < PIANO_BASE + PW_BLACK_KEY_HEIGHT ) - { - // then do extra checking whether the mouse-cursor is over - // a black key - if( key_num > 0 && Piano::isBlackKey( key_num-1 ) && - offset <= ( PW_WHITE_KEY_WIDTH / 2 ) - - ( PW_BLACK_KEY_WIDTH / 2 ) ) - { - --key_num; - } - if( key_num < NumKeys - 1 && Piano::isBlackKey( key_num+1 ) && - offset >= ( PW_WHITE_KEY_WIDTH - - PW_BLACK_KEY_WIDTH / 2 ) ) - { - ++key_num; - } - } - - // some range-checking-stuff - return qBound( 0, key_num, NumKeys - 1 ); + return std::clamp(key, 0, NumKeys - 1); } @@ -394,12 +357,12 @@ int PianoView::getKeyFromMouse( const QPoint & _p ) const * * We need to update our start key position based on the new position. * - * \param _new_pos the new key position. + * \param newPos the new key position, counting only white keys. */ -void PianoView::pianoScrolled(int new_pos) +void PianoView::pianoScrolled(int newPos) { - m_startKey = WhiteKeys[new_pos % Piano::WhiteKeysPerOctave] + - (new_pos / Piano::WhiteKeysPerOctave) * KeysPerOctave; + m_startKey = static_cast(newPos / Piano::WhiteKeysPerOctave) + + WhiteKeys[newPos % Piano::WhiteKeysPerOctave]; update(); } @@ -625,8 +588,7 @@ void PianoView::mouseMoveEvent( QMouseEvent * _me ) */ void PianoView::keyPressEvent( QKeyEvent * _ke ) { - const int key_num = getKeyFromKeyEvent( _ke ) + - ( DefaultOctave - 1 ) * KeysPerOctave; + const int key_num = getKeyFromKeyEvent( _ke ); if( _ke->isAutoRepeat() == false && key_num > -1 ) { @@ -654,8 +616,7 @@ void PianoView::keyPressEvent( QKeyEvent * _ke ) */ void PianoView::keyReleaseEvent( QKeyEvent * _ke ) { - const int key_num = getKeyFromKeyEvent( _ke ) + - ( DefaultOctave - 1 ) * KeysPerOctave; + const int key_num = getKeyFromKeyEvent( _ke ); if( _ke->isAutoRepeat() == false && key_num > -1 ) { if( m_piano != nullptr ) @@ -846,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 @@ -899,21 +860,21 @@ 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; - if ((Keys)(cur_key % KeysPerOctave) == Key_C) + if ((Key)(cur_key % KeysPerOctave) == Key::C) { // label key of note C with "C" and number of current octave p.drawText(x - PW_WHITE_KEY_WIDTH, LABEL_TEXT_SIZE + 2, @@ -927,22 +888,22 @@ void PianoView::paintEvent( QPaintEvent * ) int white_cnt = 0; int startKey = m_startKey; - if (startKey > 0 && Piano::isBlackKey(static_cast(--startKey))) + if (startKey > 0 && Piano::isBlackKey(--startKey)) { if (m_piano && m_piano->instrumentTrack()->isKeyMapped(startKey)) { 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); } } @@ -956,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 b1ddf71c9..6c573fdf5 100644 --- a/src/gui/menus/MidiPortMenu.cpp +++ b/src/gui/menus/MidiPortMenu.cpp @@ -24,17 +24,15 @@ */ #include "MidiPortMenu.h" -#include "gui_templates.h" namespace lmms::gui { -MidiPortMenu::MidiPortMenu( MidiPort::Modes _mode ) : +MidiPortMenu::MidiPortMenu( MidiPort::Mode _mode ) : ModelView( nullptr, this ), m_mode( _mode ) { - setFont( pointSize<9>( font() ) ); connect( this, SIGNAL(triggered(QAction*)), this, SLOT(activatedPort(QAction*))); } @@ -46,12 +44,12 @@ MidiPortMenu::MidiPortMenu( MidiPort::Modes _mode ) : void MidiPortMenu::modelChanged() { auto mp = castModel(); - if( m_mode == MidiPort::Input ) + if( m_mode == MidiPort::Mode::Input ) { connect( mp, SIGNAL(readablePortsChanged()), this, SLOT(updateMenu())); } - else if( m_mode == MidiPort::Output ) + else if( m_mode == MidiPort::Mode::Output ) { connect( mp, SIGNAL(writablePortsChanged()), this, SLOT(updateMenu())); @@ -64,12 +62,12 @@ void MidiPortMenu::modelChanged() void MidiPortMenu::activatedPort( QAction * _item ) { - if( m_mode == MidiPort::Input ) + if( m_mode == MidiPort::Mode::Input ) { castModel()->subscribeReadablePort( _item->text(), _item->isChecked() ); } - else if( m_mode == MidiPort::Output ) + else if( m_mode == MidiPort::Mode::Output ) { castModel()->subscribeWritablePort( _item->text(), _item->isChecked() ); @@ -82,7 +80,7 @@ void MidiPortMenu::activatedPort( QAction * _item ) void MidiPortMenu::updateMenu() { auto mp = castModel(); - const MidiPort::Map & map = ( m_mode == MidiPort::Input ) ? + const MidiPort::Map & map = ( m_mode == MidiPort::Mode::Input ) ? mp->readablePorts() : mp->writablePorts(); clear(); for( MidiPort::Map::ConstIterator it = map.begin(); diff --git a/src/gui/modals/ControllerConnectionDialog.cpp b/src/gui/modals/ControllerConnectionDialog.cpp index 149303da8..12e26d03c 100644 --- a/src/gui/modals/ControllerConnectionDialog.cpp +++ b/src/gui/modals/ControllerConnectionDialog.cpp @@ -54,7 +54,7 @@ public: AutoDetectMidiController( Model* parent ) : MidiController( parent ), m_detectedMidiChannel( 0 ), - m_detectedMidiController( 0 ) + m_detectedMidiController(NONE) { updateName(); } @@ -69,7 +69,7 @@ public: ( m_midiPort.inputChannel() == 0 || m_midiPort.inputChannel() == event.channel() + 1 ) ) { m_detectedMidiChannel = event.channel() + 1; - m_detectedMidiController = event.controllerNumber() + 1; + m_detectedMidiController = event.controllerNumber(); m_detectedMidiPort = Engine::audioEngine()->midiClient()->sourcePortName( event ); emit valueChanged(); @@ -152,7 +152,7 @@ ControllerConnectionDialog::ControllerConnectionDialog( QWidget * _parent, m_midiControllerSpinBox = new LcdSpinBox( 3, m_midiGroupBox, tr( "Input controller" ) ); - m_midiControllerSpinBox->addTextForValue( 0, "---" ); + m_midiControllerSpinBox->addTextForValue(MidiController::NONE, "---" ); m_midiControllerSpinBox->setLabel( tr( "CONTROLLER" ) ); m_midiControllerSpinBox->move( 68, 24 ); @@ -169,7 +169,7 @@ ControllerConnectionDialog::ControllerConnectionDialog( QWidget * _parent, // our port-menus when being clicked if( !Engine::audioEngine()->midiClient()->isRaw() ) { - m_readablePorts = new MidiPortMenu( MidiPort::Input ); + m_readablePorts = new MidiPortMenu( MidiPort::Mode::Input ); connect( m_readablePorts, SIGNAL(triggered(QAction*)), this, SLOT(enableAutoDetect(QAction*))); auto rp_btn = new ToolButton(m_midiGroupBox); @@ -215,7 +215,7 @@ ControllerConnectionDialog::ControllerConnectionDialog( QWidget * _parent, auto btn_layout = new QHBoxLayout(buttons); btn_layout->setSpacing( 0 ); - btn_layout->setMargin( 0 ); + btn_layout->setContentsMargins(0, 0, 0, 0); auto select_btn = new QPushButton(embed::getIconPixmap("add"), tr("OK"), buttons); connect( select_btn, SIGNAL(clicked()), @@ -242,9 +242,9 @@ ControllerConnectionDialog::ControllerConnectionDialog( QWidget * _parent, { cc = m_targetModel->controllerConnection(); - if( cc && cc->getController()->type() != Controller::DummyController && Engine::getSong() ) + if( cc && cc->getController()->type() != Controller::ControllerType::Dummy && Engine::getSong() ) { - if ( cc->getController()->type() == Controller::MidiController ) + if ( cc->getController()->type() == Controller::ControllerType::Midi ) { m_midiGroupBox->model()->setValue( true ); // ensure controller is created @@ -258,10 +258,12 @@ ControllerConnectionDialog::ControllerConnectionDialog( QWidget * _parent, } else { - int idx = Engine::getSong()->controllers().indexOf( cc->getController() ); + auto& controllers = Engine::getSong()->controllers(); + auto it = std::find(controllers.begin(), controllers.end(), cc->getController()); - if( idx >= 0 ) + if (it != controllers.end()) { + int idx = std::distance(controllers.begin(), it); m_userGroupBox->model()->setValue( true ); m_userController->model()->setValue( idx ); } @@ -297,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() ) @@ -406,7 +407,7 @@ void ControllerConnectionDialog::userSelected() void ControllerConnectionDialog::autoDetectToggled() { - if( m_midiAutoDetect.value() ) + if (m_midiAutoDetect.value() && m_midiController) { m_midiController->reset(); } diff --git a/src/gui/modals/EffectSelectDialog.cpp b/src/gui/modals/EffectSelectDialog.cpp index 9ce3f90b6..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::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->setMargin( 4 ); - textWidgetLayout->setSpacing( 0 ); + textWidgetLayout->setContentsMargins(4, 4, 4, 4); + textWidgetLayout->setSpacing(8); - if ( m_currentSelection.desc->subPluginFeatures ) + if (m_currentSelection.desc->subPluginFeatures) { auto subWidget = new QWidget(textualInfoWidget); auto subLayout = new QVBoxLayout(subWidget); - subLayout->setMargin( 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 1f937c374..fe39082e4 100644 --- a/src/gui/modals/ExportProjectDialog.cpp +++ b/src/gui/modals/ExportProjectDialog.cpp @@ -66,7 +66,7 @@ ExportProjectDialog::ExportProjectDialog( const QString & _file_name, // Add to combo box. fileFormatCB->addItem( ProjectRenderer::tr( ProjectRenderer::fileEncodeDevices[i].m_description ), - QVariant( ProjectRenderer::fileEncodeDevices[i].m_fileFormat ) // Format tag; later used for identification. + QVariant( static_cast(ProjectRenderer::fileEncodeDevices[i].m_fileFormat) ) // Format tag; later used for identification. ); // If this is our extension, select it. @@ -142,13 +142,13 @@ OutputSettings::StereoMode mapToStereoMode(int index) switch (index) { case 0: - return OutputSettings::StereoMode_Mono; + return OutputSettings::StereoMode::Mono; case 1: - return OutputSettings::StereoMode_Stereo; + return OutputSettings::StereoMode::Stereo; case 2: - return OutputSettings::StereoMode_JointStereo; + return OutputSettings::StereoMode::JointStereo; default: - return OutputSettings::StereoMode_Stereo; + return OutputSettings::StereoMode::Stereo; } } @@ -216,27 +216,27 @@ void ExportProjectDialog::onFileFormatChanged(int index) // and adjust the UI properly. QVariant format_tag = fileFormatCB->itemData(index); bool successful_conversion = false; - auto exportFormat = static_cast( + auto exportFormat = static_cast( format_tag.toInt(&successful_conversion) ); Q_ASSERT(successful_conversion); - bool stereoModeVisible = (exportFormat == ProjectRenderer::MP3File); + bool stereoModeVisible = (exportFormat == ProjectRenderer::ExportFileFormat::MP3); - bool sampleRateControlsVisible = (exportFormat != ProjectRenderer::MP3File); + bool sampleRateControlsVisible = (exportFormat != ProjectRenderer::ExportFileFormat::MP3); bool bitRateControlsEnabled = - (exportFormat == ProjectRenderer::OggFile || - exportFormat == ProjectRenderer::MP3File); + (exportFormat == ProjectRenderer::ExportFileFormat::Ogg || + exportFormat == ProjectRenderer::ExportFileFormat::MP3); bool bitDepthControlEnabled = - (exportFormat == ProjectRenderer::WaveFile || - exportFormat == ProjectRenderer::FlacFile); + (exportFormat == ProjectRenderer::ExportFileFormat::Wave || + exportFormat == ProjectRenderer::ExportFileFormat::Flac); - bool variableBitrateVisible = !(exportFormat == ProjectRenderer::MP3File || exportFormat == ProjectRenderer::FlacFile); + bool variableBitrateVisible = !(exportFormat == ProjectRenderer::ExportFileFormat::MP3 || exportFormat == ProjectRenderer::ExportFileFormat::Flac); #ifdef LMMS_HAVE_SF_COMPLEVEL - bool compressionLevelVisible = (exportFormat == ProjectRenderer::FlacFile); + bool compressionLevelVisible = (exportFormat == ProjectRenderer::ExportFileFormat::Flac); compressionWidget->setVisible(compressionLevelVisible); #endif @@ -251,12 +251,12 @@ void ExportProjectDialog::onFileFormatChanged(int index) void ExportProjectDialog::startBtnClicked() { - m_ft = ProjectRenderer::NumFileFormats; + m_ft = ProjectRenderer::ExportFileFormat::Count; // Get file format from current menu selection. bool successful_conversion = false; QVariant tag = fileFormatCB->itemData(fileFormatCB->currentIndex()); - m_ft = static_cast( + m_ft = static_cast( tag.toInt(&successful_conversion) ); diff --git a/src/gui/modals/FileDialog.cpp b/src/gui/modals/FileDialog.cpp index 512d7179f..a6cf4827a 100644 --- a/src/gui/modals/FileDialog.cpp +++ b/src/gui/modals/FileDialog.cpp @@ -26,11 +26,12 @@ #include #include #include +#include +#include #include "ConfigManager.h" #include "FileDialog.h" - namespace lmms::gui { @@ -45,19 +46,38 @@ FileDialog::FileDialog( QWidget *parent, const QString &caption, setOption( QFileDialog::DontUseNativeDialog ); - // Add additional locations to the sidebar +#ifdef LMMS_BUILD_LINUX + QList urls; +#else QList urls = sidebarUrls(); - urls << QUrl::fromLocalFile( QStandardPaths::writableLocation( QStandardPaths::DesktopLocation ) ); - // Find downloads directory - QDir downloadDir( QDir::homePath() + "/Downloads" ); - if ( ! downloadDir.exists() ) - downloadDir.setPath(QStandardPaths::writableLocation( QStandardPaths::DownloadLocation )); - if ( downloadDir.exists() ) - urls << QUrl::fromLocalFile( downloadDir.absolutePath() ); +#endif - urls << QUrl::fromLocalFile( QStandardPaths::writableLocation( QStandardPaths::MusicLocation ) ); - urls << QUrl::fromLocalFile( ConfigManager::inst()->workingDir() ); + QDir desktopDir; + desktopDir.setPath(QStandardPaths::writableLocation(QStandardPaths::DesktopLocation)); + if (desktopDir.exists()) + { + urls << QUrl::fromLocalFile(desktopDir.absolutePath()); + } + + QDir downloadDir(QDir::homePath() + "/Downloads"); + if (!downloadDir.exists()) + { + downloadDir.setPath(QStandardPaths::writableLocation(QStandardPaths::DownloadLocation)); + } + if (downloadDir.exists()) + { + urls << QUrl::fromLocalFile(downloadDir.absolutePath()); + } + QDir musicDir; + musicDir.setPath(QStandardPaths::writableLocation(QStandardPaths::MusicLocation)); + if (musicDir.exists()) + { + urls << QUrl::fromLocalFile(musicDir.absolutePath()); + } + + urls << QUrl::fromLocalFile(ConfigManager::inst()->workingDir()); + // Add `/Volumes` directory on OS X systems, this allows the user to browse // external disk drives. #ifdef LMMS_BUILD_APPLE @@ -66,6 +86,22 @@ FileDialog::FileDialog( QWidget *parent, const QString &caption, urls << QUrl::fromLocalFile( volumesDir.absolutePath() ); #endif +#ifdef LMMS_BUILD_LINUX + + // FileSystem types : https://www.javatpoint.com/linux-file-system + QStringList usableFileSystems = {"ext", "ext2", "ext3", "ext4", "jfs", "reiserfs", "ntfs3", "fuse.sshfs", "fuseblk"}; + + for(QStorageInfo storage : QStorageInfo::mountedVolumes()) + { + storage.refresh(); + + if (usableFileSystems.contains(QString(storage.fileSystemType()), Qt::CaseInsensitive) && storage.isValid() && storage.isReady()) + { + urls << QUrl::fromLocalFile(storage.rootPath()); + } + } +#endif + setSidebarUrls(urls); } diff --git a/src/gui/modals/SetupDialog.cpp b/src/gui/modals/SetupDialog.cpp index 5140a63b5..ba7814f31 100644 --- a/src/gui/modals/SetupDialog.cpp +++ b/src/gui/modals/SetupDialog.cpp @@ -23,20 +23,20 @@ */ +#include #include +#include #include #include #include #include #include -#include "AudioDeviceSetupWidget.h" #include "AudioEngine.h" #include "debug.h" #include "embed.h" #include "Engine.h" #include "FileDialog.h" -#include "gui_templates.h" #include "MainWindow.h" #include "MidiSetupWidget.h" #include "ProjectJournal.h" @@ -79,19 +79,18 @@ inline void labelWidget(QWidget * w, const QString & txt) auto title = new QLabel(txt, w); QFont f = title->font(); f.setBold(true); - title->setFont(pointSize<12>(f)); + title->setFont(f); + QBoxLayout * boxLayout = dynamic_cast(w->layout()); + assert(boxLayout); - assert(dynamic_cast(w->layout()) != nullptr); - - dynamic_cast(w->layout())->addSpacing(5); - dynamic_cast(w->layout())->addWidget(title); + boxLayout->addWidget(title); } -SetupDialog::SetupDialog(ConfigTabs tab_to_open) : +SetupDialog::SetupDialog(ConfigTab tab_to_open) : m_displaydBFS(ConfigManager::inst()->value( "app", "displaydbfs").toInt()), m_tooltips(!ConfigManager::inst()->value( @@ -112,12 +111,15 @@ SetupDialog::SetupDialog(ConfigTabs tab_to_open) : "app", "sololegacybehavior", "0").toInt()), m_trackDeletionWarning(ConfigManager::inst()->value( "ui", "trackdeletionwarning", "1").toInt()), + m_mixerChannelDeletionWarning(ConfigManager::inst()->value( + "ui", "mixerchanneldeletionwarning", "1").toInt()), m_MMPZ(!ConfigManager::inst()->value( "app", "nommpz").toInt()), m_disableBackup(!ConfigManager::inst()->value( "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( @@ -144,6 +146,8 @@ SetupDialog::SetupDialog(ConfigTabs tab_to_open) : "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())), @@ -157,30 +161,24 @@ SetupDialog::SetupDialog(ConfigTabs 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); - setFixedSize(454, 400); Engine::projectJournal()->setJournalling(false); - // Constants for positioning LED check boxes. - const int XDelta = 10; - const int YDelta = 18; - // Main widget. auto main_w = new QWidget(this); // Vertical layout. auto vlayout = new QVBoxLayout(this); vlayout->setSpacing(0); - vlayout->setMargin(0); + vlayout->setContentsMargins(0, 0, 0, 0); // Horizontal layout. auto hlayout = new QHBoxLayout(main_w); hlayout->setSpacing(0); - hlayout->setMargin(0); + hlayout->setContentsMargins(0, 0, 0, 0); // Tab bar for the main tabs. m_tabBar = new TabBar(main_w, QBoxLayout::TopToBottom); @@ -189,76 +187,113 @@ SetupDialog::SetupDialog(ConfigTabs tab_to_open) : // Settings widget. auto settings_w = new QWidget(main_w); - settings_w->setFixedSize(360, 360); + + QVBoxLayout * settingsLayout = new QVBoxLayout(settings_w); // General widget. auto general_w = new QWidget(settings_w); auto general_layout = new QVBoxLayout(general_w); general_layout->setSpacing(10); - general_layout->setMargin(0); + general_layout->setContentsMargins(0, 0, 0, 0); labelWidget(general_w, tr("General")); - auto addLedCheckBox = [&XDelta, &YDelta, this](const QString& ledText, TabWidget* tw, int& counter, - bool initialState, const char* toggledSlot, bool showRestartWarning) { - auto checkBox = new LedCheckBox(ledText, tw); - counter++; - checkBox->move(XDelta, YDelta * counter); + // General scroll area. + auto generalScroll = new QScrollArea(general_w); + generalScroll->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn); + generalScroll->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + + // General controls widget. + auto generalControls = new QWidget(general_w); + + // Path selectors layout. + auto generalControlsLayout = new QVBoxLayout; + generalControlsLayout->setSpacing(10); + generalControlsLayout->setContentsMargins(0, 0, 0, 0); + + auto addCheckBox = [&](const QString& ledText, QWidget* parent, QBoxLayout * layout, + bool initialState, const char* toggledSlot, bool showRestartWarning) -> QCheckBox * { + auto checkBox = new QCheckBox(ledText, parent); checkBox->setChecked(initialState); connect(checkBox, SIGNAL(toggled(bool)), this, toggledSlot); + if (showRestartWarning) { connect(checkBox, SIGNAL(toggled(bool)), this, SLOT(showRestartWarning())); } + + if (layout) + { + layout->addWidget(checkBox); + } + + return checkBox; }; - int counter = 0; - // GUI tab. - auto gui_tw = new TabWidget(tr("Graphical user interface (GUI)"), general_w); + QGroupBox * guiGroupBox = new QGroupBox(tr("Graphical user interface (GUI)"), generalControls); + QVBoxLayout * guiGroupLayout = new QVBoxLayout(guiGroupBox); - addLedCheckBox(tr("Display volume as dBFS "), gui_tw, counter, + addCheckBox(tr("Display volume as dBFS "), guiGroupBox, guiGroupLayout, m_displaydBFS, SLOT(toggleDisplaydBFS(bool)), true); - addLedCheckBox(tr("Enable tooltips"), gui_tw, counter, + addCheckBox(tr("Enable tooltips"), guiGroupBox, guiGroupLayout, m_tooltips, SLOT(toggleTooltips(bool)), true); - addLedCheckBox(tr("Enable master oscilloscope by default"), gui_tw, counter, + addCheckBox(tr("Enable master oscilloscope by default"), guiGroupBox, guiGroupLayout, m_displayWaveform, SLOT(toggleDisplayWaveform(bool)), true); - addLedCheckBox(tr("Enable all note labels in piano roll"), gui_tw, counter, + addCheckBox(tr("Enable all note labels in piano roll"), guiGroupBox, guiGroupLayout, m_printNoteLabels, SLOT(toggleNoteLabels(bool)), false); - addLedCheckBox(tr("Enable compact track buttons"), gui_tw, counter, + addCheckBox(tr("Enable compact track buttons"), guiGroupBox, guiGroupLayout, m_compactTrackButtons, SLOT(toggleCompactTrackButtons(bool)), true); - addLedCheckBox(tr("Enable one instrument-track-window mode"), gui_tw, counter, + addCheckBox(tr("Enable one instrument-track-window mode"), guiGroupBox, guiGroupLayout, m_oneInstrumentTrackWindow, SLOT(toggleOneInstrumentTrackWindow(bool)), true); - addLedCheckBox(tr("Show sidebar on the right-hand side"), gui_tw, counter, + addCheckBox(tr("Show sidebar on the right-hand side"), guiGroupBox, guiGroupLayout, m_sideBarOnRight, SLOT(toggleSideBarOnRight(bool)), true); - addLedCheckBox(tr("Let sample previews continue when mouse is released"), gui_tw, counter, + addCheckBox(tr("Let sample previews continue when mouse is released"), guiGroupBox, guiGroupLayout, m_letPreviewsFinish, SLOT(toggleLetPreviewsFinish(bool)), false); - addLedCheckBox(tr("Mute automation tracks during solo"), gui_tw, counter, + addCheckBox(tr("Mute automation tracks during solo"), guiGroupBox, guiGroupLayout, m_soloLegacyBehavior, SLOT(toggleSoloLegacyBehavior(bool)), false); - addLedCheckBox(tr("Show warning when deleting tracks"), gui_tw, counter, + addCheckBox(tr("Show warning when deleting tracks"), guiGroupBox, guiGroupLayout, m_trackDeletionWarning, SLOT(toggleTrackDeletionWarning(bool)), false); + addCheckBox(tr("Show warning when deleting a mixer channel that is in use"), guiGroupBox, guiGroupLayout, + m_mixerChannelDeletionWarning, SLOT(toggleMixerChannelDeletionWarning(bool)), false); - gui_tw->setFixedHeight(YDelta + YDelta * counter); + 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"); - counter = 0; + 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); // Projects tab. - auto projects_tw = new TabWidget(tr("Projects"), general_w); + QGroupBox * projectsGroupBox = new QGroupBox(tr("Projects"), generalControls); + QVBoxLayout * projectsGroupLayout = new QVBoxLayout(projectsGroupBox); - addLedCheckBox(tr("Compress project files by default"), projects_tw, counter, + addCheckBox(tr("Compress project files by default"), projectsGroupBox, projectsGroupLayout, m_MMPZ, SLOT(toggleMMPZ(bool)), true); - addLedCheckBox(tr("Create a backup file when saving a project"), projects_tw, counter, + addCheckBox(tr("Create a backup file when saving a project"), projectsGroupBox, projectsGroupLayout, m_disableBackup, SLOT(toggleDisableBackup(bool)), false); - addLedCheckBox(tr("Reopen last project on startup"), projects_tw, counter, + addCheckBox(tr("Reopen last project on startup"), projectsGroupBox, projectsGroupLayout, m_openLastProject, SLOT(toggleOpenLastProject(bool)), false); - projects_tw->setFixedHeight(YDelta + YDelta * counter); + generalControlsLayout->addWidget(projectsGroupBox); + + generalControlsLayout->addSpacing(10); // Language tab. - auto lang_tw = new TabWidget(tr("Language"), general_w); - lang_tw->setFixedHeight(48); - auto changeLang = new QComboBox(lang_tw); - changeLang->move(XDelta, 20); + QGroupBox * languageGroupBox = new QGroupBox(tr("Language"), generalControls); + QVBoxLayout * languageGroupLayout = new QVBoxLayout(languageGroupBox); + + auto changeLang = new QComboBox(languageGroupBox); + languageGroupLayout->addWidget(changeLang); QDir dir(ConfigManager::inst()->localeDir()); QStringList fileNames = dir.entryList(QStringList("*.qm")); @@ -310,13 +345,15 @@ SetupDialog::SetupDialog(ConfigTabs tab_to_open) : connect(changeLang, SIGNAL(currentIndexChanged(int)), this, SLOT(showRestartWarning())); + generalControlsLayout->addWidget(languageGroupBox); + generalControlsLayout->addSpacing(10); // General layout ordering. - general_layout->addWidget(gui_tw); - general_layout->addWidget(projects_tw); - general_layout->addWidget(lang_tw); - general_layout->addStretch(); - + generalControlsLayout->addStretch(); + generalControls->setLayout(generalControlsLayout); + generalScroll->setWidget(generalControls); + generalScroll->setWidgetResizable(true); + general_layout->addWidget(generalScroll, 1); @@ -324,77 +361,69 @@ SetupDialog::SetupDialog(ConfigTabs tab_to_open) : auto performance_w = new QWidget(settings_w); auto performance_layout = new QVBoxLayout(performance_w); performance_layout->setSpacing(10); - performance_layout->setMargin(0); + performance_layout->setContentsMargins(0, 0, 0, 0); labelWidget(performance_w, tr("Performance")); // Autosave tab. - auto auto_save_tw = new TabWidget(tr("Autosave"), performance_w); - auto_save_tw->setFixedHeight(106); + QGroupBox * autoSaveBox = new QGroupBox(tr("Autosave"), performance_w); + QVBoxLayout * autoSaveLayout = new QVBoxLayout(autoSaveBox); + QHBoxLayout * autoSaveSubLayout = new QHBoxLayout(); - m_saveIntervalSlider = new QSlider(Qt::Horizontal, auto_save_tw); + m_saveIntervalSlider = new QSlider(Qt::Horizontal, autoSaveBox); m_saveIntervalSlider->setValue(m_saveInterval); m_saveIntervalSlider->setRange(1, 20); m_saveIntervalSlider->setTickInterval(1); m_saveIntervalSlider->setPageStep(1); - m_saveIntervalSlider->setGeometry(10, 18, 340, 18); m_saveIntervalSlider->setTickPosition(QSlider::TicksBelow); connect(m_saveIntervalSlider, SIGNAL(valueChanged(int)), this, SLOT(setAutoSaveInterval(int))); - m_saveIntervalLbl = new QLabel(auto_save_tw); - m_saveIntervalLbl->setGeometry(10, 40, 200, 24); - setAutoSaveInterval(m_saveIntervalSlider->value()); - - m_autoSave = new LedCheckBox( - tr("Enable autosave"), auto_save_tw); - m_autoSave->move(10, 70); - m_autoSave->setChecked(m_enableAutoSave); - connect(m_autoSave, SIGNAL(toggled(bool)), - this, SLOT(toggleAutoSave(bool))); - - m_runningAutoSave = new LedCheckBox( - tr("Allow autosave while playing"), auto_save_tw); - m_runningAutoSave->move(20, 88); - m_runningAutoSave->setChecked(m_enableRunningAutoSave); - connect(m_runningAutoSave, SIGNAL(toggled(bool)), - this, SLOT(toggleRunningAutoSave(bool))); - - auto autoSaveResetBtn = new QPushButton(embed::getIconPixmap("reload"), "", auto_save_tw); - autoSaveResetBtn->setGeometry(320, 70, 28, 28); + auto autoSaveResetBtn = new QPushButton(embed::getIconPixmap("reload"), "", autoSaveBox); + autoSaveResetBtn->setFixedSize(32, 32); connect(autoSaveResetBtn, SIGNAL(clicked()), - this, SLOT(resetAutoSave())); + this, SLOT(resetAutoSave())); + + autoSaveSubLayout->addWidget(m_saveIntervalSlider); + autoSaveSubLayout->addWidget(autoSaveResetBtn); + + autoSaveLayout->addLayout(autoSaveSubLayout); + + m_saveIntervalLbl = new QLabel(autoSaveBox); + setAutoSaveInterval(m_saveIntervalSlider->value()); + autoSaveLayout->addWidget(m_saveIntervalLbl); + + m_autoSave = addCheckBox(tr("Enable autosave"), autoSaveBox, autoSaveLayout, + m_enableAutoSave, SLOT(toggleAutoSave(bool)), false); + + m_runningAutoSave = addCheckBox(tr("Allow autosave while playing"), autoSaveBox, autoSaveLayout, + m_enableRunningAutoSave, SLOT(toggleRunningAutoSave(bool)), false); m_saveIntervalSlider->setEnabled(m_enableAutoSave); m_runningAutoSave->setVisible(m_enableAutoSave); - counter = 0; - // UI effect vs. performance tab. - auto ui_fx_tw = new TabWidget(tr("User interface (UI) effects vs. performance"), performance_w); + QGroupBox * uiFxBox = new QGroupBox(tr("User interface (UI) effects vs. performance"), performance_w); + QVBoxLayout * uiFxLayout = new QVBoxLayout(uiFxBox); - addLedCheckBox(tr("Smooth scroll in song editor"), ui_fx_tw, counter, + addCheckBox(tr("Smooth scroll in song editor"), uiFxBox, uiFxLayout, m_smoothScroll, SLOT(toggleSmoothScroll(bool)), false); - addLedCheckBox(tr("Display playback cursor in AudioFileProcessor"), ui_fx_tw, counter, + addCheckBox(tr("Display playback cursor in AudioFileProcessor"), uiFxBox, uiFxLayout, m_animateAFP, SLOT(toggleAnimateAFP(bool)), false); - ui_fx_tw->setFixedHeight(YDelta + YDelta * counter); + // Plugins group + QGroupBox * pluginsBox = new QGroupBox(tr("Plugins"), performance_w); + QVBoxLayout * pluginsLayout = new QVBoxLayout(pluginsBox); - counter = 0; - - // Plugins tab. - auto plugins_tw = new TabWidget(tr("Plugins"), performance_w); - - m_vstEmbedLbl = new QLabel(plugins_tw); - m_vstEmbedLbl->move(XDelta, YDelta * ++counter); + m_vstEmbedLbl = new QLabel(pluginsBox); m_vstEmbedLbl->setText(tr("VST plugins embedding:")); + pluginsLayout->addWidget(m_vstEmbedLbl); - m_vstEmbedComboBox = new QComboBox(plugins_tw); - m_vstEmbedComboBox->move(XDelta, YDelta * ++counter); + m_vstEmbedComboBox = new QComboBox(pluginsBox); QStringList embedMethods = ConfigManager::availableVstEmbedMethods(); m_vstEmbedComboBox->addItem(tr("No embedding"), "none"); @@ -413,27 +442,19 @@ SetupDialog::SetupDialog(ConfigTabs tab_to_open) : m_vstEmbedComboBox->setCurrentIndex(m_vstEmbedComboBox->findData(m_vstEmbedMethod)); connect(m_vstEmbedComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(vstEmbedMethodChanged())); + pluginsLayout->addWidget(m_vstEmbedComboBox); - counter += 2; + m_vstAlwaysOnTopCheckBox = addCheckBox(tr("Keep plugin windows on top when not embedded"), pluginsBox, pluginsLayout, + m_vstAlwaysOnTop, SLOT(toggleVSTAlwaysOnTop(bool)), false); - m_vstAlwaysOnTopCheckBox = new LedCheckBox( - tr("Keep plugin windows on top when not embedded"), plugins_tw); - m_vstAlwaysOnTopCheckBox->move(20, 66); - m_vstAlwaysOnTopCheckBox->setChecked(m_vstAlwaysOnTop); - m_vstAlwaysOnTopCheckBox->setVisible(m_vstEmbedMethod == "none"); - connect(m_vstAlwaysOnTopCheckBox, SIGNAL(toggled(bool)), - this, SLOT(toggleVSTAlwaysOnTop(bool))); - - addLedCheckBox(tr("Keep effects running even without input"), plugins_tw, counter, + addCheckBox(tr("Keep effects running even without input"), pluginsBox, pluginsLayout, m_disableAutoQuit, SLOT(toggleDisableAutoQuit(bool)), false); - plugins_tw->setFixedHeight(YDelta + YDelta * counter); - // Performance layout ordering. - performance_layout->addWidget(auto_save_tw); - performance_layout->addWidget(ui_fx_tw); - performance_layout->addWidget(plugins_tw); + performance_layout->addWidget(autoSaveBox); + performance_layout->addWidget(uiFxBox); + performance_layout->addWidget(pluginsBox); performance_layout->addStretch(); @@ -442,25 +463,23 @@ SetupDialog::SetupDialog(ConfigTabs tab_to_open) : auto audio_w = new QWidget(settings_w); auto audio_layout = new QVBoxLayout(audio_w); audio_layout->setSpacing(10); - audio_layout->setMargin(0); + audio_layout->setContentsMargins(0, 0, 0, 0); labelWidget(audio_w, tr("Audio")); - // Audio interface tab. - auto audioiface_tw = new TabWidget(tr("Audio interface"), audio_w); - audioiface_tw->setFixedHeight(56); - - m_audioInterfaces = new QComboBox(audioiface_tw); - m_audioInterfaces->setGeometry(10, 20, 240, 28); + // Audio interface group + QGroupBox * audioInterfaceBox = new QGroupBox(tr("Audio interface"), audio_w); + QVBoxLayout * audioInterfaceLayout = new QVBoxLayout(audioInterfaceBox); + m_audioInterfaces = new QComboBox(audioInterfaceBox); + audioInterfaceLayout->addWidget(m_audioInterfaces); // Ifaces-settings-widget. auto as_w = new QWidget(audio_w); - as_w->setFixedHeight(60); auto as_w_layout = new QHBoxLayout(as_w); as_w_layout->setSpacing(0); - as_w_layout->setMargin(0); + as_w_layout->setContentsMargins(0, 0, 0, 0); #ifdef LMMS_HAVE_JACK m_audioIfaceSetupWidgets[AudioJack::name()] = @@ -536,54 +555,58 @@ SetupDialog::SetupDialog(ConfigTabs tab_to_open) : this, SLOT(audioInterfaceChanged(const QString&))); // Advanced setting, hidden for now - if(false) - { - auto useNaNHandler = new LedCheckBox(tr("Use built-in NaN handler"), audio_w); - useNaNHandler->setChecked(m_NaNHandler); - } + // // TODO Handle or remove. + // auto useNaNHandler = new LedCheckBox(tr("Use built-in NaN handler"), audio_w); + // audio_layout->addWidget(useNaNHandler); + // useNaNHandler->setChecked(m_NaNHandler); - // HQ mode LED. - auto hqaudio = new LedCheckBox(tr("HQ mode for output audio device"), audio_w); - hqaudio->move(10, 0); - hqaudio->setChecked(m_hqAudioDev); - connect(hqaudio, SIGNAL(toggled(bool)), - this, SLOT(toggleHQAudioDev(bool))); + // HQ mode checkbox + auto hqaudio = addCheckBox(tr("HQ mode for output audio device"), audioInterfaceBox, nullptr, + m_hqAudioDev, SLOT(toggleHQAudioDev(bool)), false); + // Buffer size group + QGroupBox * bufferSizeBox = new QGroupBox(tr("Buffer size"), audio_w); + QVBoxLayout * bufferSizeLayout = new QVBoxLayout(bufferSizeBox); + QHBoxLayout * bufferSizeSubLayout = new QHBoxLayout(); - // Buffer size tab. - auto bufferSize_tw = new TabWidget(tr("Buffer size"), audio_w); - bufferSize_tw->setFixedHeight(76); - - m_bufferSizeSlider = new QSlider(Qt::Horizontal, bufferSize_tw); + m_bufferSizeSlider = new QSlider(Qt::Horizontal, bufferSizeBox); m_bufferSizeSlider->setRange(1, 128); m_bufferSizeSlider->setTickInterval(8); m_bufferSizeSlider->setPageStep(8); m_bufferSizeSlider->setValue(m_bufferSize / BUFFERSIZE_RESOLUTION); - m_bufferSizeSlider->setGeometry(10, 18, 340, 18); m_bufferSizeSlider->setTickPosition(QSlider::TicksBelow); connect(m_bufferSizeSlider, SIGNAL(valueChanged(int)), this, SLOT(setBufferSize(int))); connect(m_bufferSizeSlider, SIGNAL(valueChanged(int)), this, SLOT(showRestartWarning())); + bufferSizeSubLayout->addWidget(m_bufferSizeSlider, 1); - m_bufferSizeLbl = new QLabel(bufferSize_tw); - m_bufferSizeLbl->setGeometry(10, 40, 200, 24); - setBufferSize(m_bufferSizeSlider->value()); - - auto bufferSize_reset_btn = new QPushButton(embed::getIconPixmap("reload"), "", bufferSize_tw); - bufferSize_reset_btn->setGeometry(320, 40, 28, 28); + auto bufferSize_reset_btn = new QPushButton(embed::getIconPixmap("reload"), "", bufferSizeBox); + bufferSize_reset_btn->setFixedSize(32, 32); connect(bufferSize_reset_btn, SIGNAL(clicked()), - this, SLOT(resetBufferSize())); + this, SLOT(resetBufferSize())); bufferSize_reset_btn->setToolTip( - tr("Reset to default value")); + tr("Reset to default value")); + + bufferSizeSubLayout->addWidget(bufferSize_reset_btn); + bufferSizeLayout->addLayout(bufferSizeSubLayout); + + m_bufferSizeLbl = new QLabel(bufferSizeBox); + bufferSizeLayout->addWidget(m_bufferSizeLbl); + + m_bufferSizeWarnLbl = new QLabel(bufferSizeBox); + m_bufferSizeWarnLbl->setWordWrap(true); + bufferSizeLayout->addWidget(m_bufferSizeWarnLbl); + + setBufferSize(m_bufferSizeSlider->value()); // Audio layout ordering. - audio_layout->addWidget(audioiface_tw); + audio_layout->addWidget(audioInterfaceBox); audio_layout->addWidget(as_w); audio_layout->addWidget(hqaudio); - audio_layout->addWidget(bufferSize_tw); + audio_layout->addWidget(bufferSizeBox); audio_layout->addStretch(); @@ -592,24 +615,22 @@ SetupDialog::SetupDialog(ConfigTabs tab_to_open) : auto midi_w = new QWidget(settings_w); auto midi_layout = new QVBoxLayout(midi_w); midi_layout->setSpacing(10); - midi_layout->setMargin(0); - labelWidget(midi_w, - tr("MIDI")); + midi_layout->setContentsMargins(0, 0, 0, 0); + labelWidget(midi_w, tr("MIDI")); - // MIDI interface tab. - auto midiiface_tw = new TabWidget(tr("MIDI interface"), midi_w); - midiiface_tw->setFixedHeight(56); + // MIDI interface group + QGroupBox * midiInterfaceBox = new QGroupBox(tr("MIDI interface"), midi_w); + QVBoxLayout * midiInterfaceLayout = new QVBoxLayout(midiInterfaceBox); - m_midiInterfaces = new QComboBox(midiiface_tw); - m_midiInterfaces->setGeometry(10, 20, 240, 28); + m_midiInterfaces = new QComboBox(midiInterfaceBox); + midiInterfaceLayout->addWidget(m_midiInterfaces); // Ifaces-settings-widget. auto ms_w = new QWidget(midi_w); - ms_w->setFixedHeight(60); auto ms_w_layout = new QHBoxLayout(ms_w); ms_w_layout->setSpacing(0); - ms_w_layout->setMargin(0); + ms_w_layout->setContentsMargins(0, 0, 0, 0); #ifdef LMMS_HAVE_ALSA m_midiIfaceSetupWidgets[MidiAlsaSeq::name()] = @@ -675,12 +696,12 @@ SetupDialog::SetupDialog(ConfigTabs tab_to_open) : this, SLOT(midiInterfaceChanged(const QString&))); - // MIDI autoassign tab. - auto midiAutoAssign_tw = new TabWidget(tr("Automatically assign MIDI controller to selected track"), midi_w); - midiAutoAssign_tw->setFixedHeight(56); + // MIDI autoassign group + QGroupBox * midiAutoAssignBox = new QGroupBox(tr("Automatically assign MIDI controller to selected track"), midi_w); + QVBoxLayout * midiAutoAssignLayout = new QVBoxLayout(midiAutoAssignBox); - m_assignableMidiDevices = new QComboBox(midiAutoAssign_tw); - m_assignableMidiDevices->setGeometry(10, 20, 240, 28); + m_assignableMidiDevices = new QComboBox(midiAutoAssignBox); + midiAutoAssignLayout->addWidget(m_assignableMidiDevices); m_assignableMidiDevices->addItem("none"); if ( !Engine::audioEngine()->midiClient()->isRaw() ) { @@ -696,10 +717,22 @@ SetupDialog::SetupDialog(ConfigTabs 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(midiiface_tw); + midi_layout->addWidget(midiInterfaceBox); midi_layout->addWidget(ms_w); - midi_layout->addWidget(midiAutoAssign_tw); + midi_layout->addWidget(midiAutoAssignBox); + midi_layout->addWidget(midiRecordingTab); midi_layout->addStretch(); @@ -709,7 +742,7 @@ SetupDialog::SetupDialog(ConfigTabs tab_to_open) : auto paths_layout = new QVBoxLayout(paths_w); paths_layout->setSpacing(10); - paths_layout->setMargin(0); + paths_layout->setContentsMargins(0, 0, 0, 0); labelWidget(paths_w, tr("Paths")); @@ -722,29 +755,29 @@ SetupDialog::SetupDialog(ConfigTabs tab_to_open) : // Path selectors widget. auto pathSelectors = new QWidget(paths_w); - const int txtLength = 284; - const int btnStart = 300; - // Path selectors layout. auto pathSelectorsLayout = new QVBoxLayout; pathSelectorsLayout->setSpacing(10); + pathSelectorsLayout->setContentsMargins(0, 0, 0, 0); auto addPathEntry = [&](const QString& caption, const QString& content, const char* setSlot, const char* openSlot, QLineEdit*& lineEdit, const char* pixmap = "project_open") { - auto newTw = new TabWidget(caption, pathSelectors); - newTw->setFixedHeight(48); + auto pathEntryGroupBox = new QGroupBox(caption, pathSelectors); + QHBoxLayout * pathEntryLayout = new QHBoxLayout(pathEntryGroupBox); - lineEdit = new QLineEdit(content, newTw); - lineEdit->setGeometry(10, 20, txtLength, 16); + lineEdit = new QLineEdit(content, pathEntryGroupBox); connect(lineEdit, SIGNAL(textChanged(const QString&)), this, setSlot); - auto selectBtn = new QPushButton(embed::getIconPixmap(pixmap, 16, 16), "", newTw); + pathEntryLayout->addWidget(lineEdit, 1); + + auto selectBtn = new QPushButton(embed::getIconPixmap(pixmap, 16, 16), "", pathEntryGroupBox); selectBtn->setFixedSize(24, 24); - selectBtn->move(btnStart, 16); connect(selectBtn, SIGNAL(clicked()), this, openSlot); - pathSelectorsLayout->addWidget(newTw); + pathEntryLayout->addWidget(selectBtn, 0); + + pathSelectorsLayout->addWidget(pathEntryGroupBox); pathSelectorsLayout->addSpacing(10); }; @@ -790,27 +823,35 @@ SetupDialog::SetupDialog(ConfigTabs tab_to_open) : pathsScroll->setWidget(pathSelectors); pathsScroll->setWidgetResizable(true); - paths_layout->addWidget(pathsScroll); + paths_layout->addWidget(pathsScroll, 1); paths_layout->addStretch(); + // Add all main widgets to the layout of the settings widget + // This is needed so that we automatically get the correct sizes. + settingsLayout->addWidget(general_w); + settingsLayout->addWidget(performance_w); + settingsLayout->addWidget(audio_w); + settingsLayout->addWidget(midi_w); + settingsLayout->addWidget(paths_w); + // Major tabs ordering. m_tabBar->addTab(general_w, - tr("General"), 0, false, true)->setIcon( + tr("General"), 0, false, true, false)->setIcon( embed::getIconPixmap("setup_general")); m_tabBar->addTab(performance_w, - tr("Performance"), 1, false, true)->setIcon( + tr("Performance"), 1, false, true, false)->setIcon( embed::getIconPixmap("setup_performance")); m_tabBar->addTab(audio_w, - tr("Audio"), 2, false, true)->setIcon( + tr("Audio"), 2, false, true, false)->setIcon( embed::getIconPixmap("setup_audio")); m_tabBar->addTab(midi_w, - tr("MIDI"), 3, false, true)->setIcon( + tr("MIDI"), 3, false, true, false)->setIcon( embed::getIconPixmap("setup_midi")); m_tabBar->addTab(paths_w, - tr("Paths"), 4, true, true)->setIcon( + tr("Paths"), 4, true, true, false)->setIcon( embed::getIconPixmap("setup_directories")); - m_tabBar->setActiveTab(tab_to_open); + m_tabBar->setActiveTab(static_cast(tab_to_open)); // Horizontal layout ordering. hlayout->addSpacing(2); @@ -823,7 +864,7 @@ SetupDialog::SetupDialog(ConfigTabs tab_to_open) : auto extras_w = new QWidget(this); auto extras_layout = new QHBoxLayout(extras_w); extras_layout->setSpacing(0); - extras_layout->setMargin(0); + extras_layout->setContentsMargins(0, 0, 0, 0); // Restart warning label. restartWarningLbl = new QLabel( @@ -850,11 +891,14 @@ SetupDialog::SetupDialog(ConfigTabs tab_to_open) : extras_layout->addSpacing(10); // Vertical layout ordering. - vlayout->addWidget(main_w); + vlayout->addWidget(main_w, 1); vlayout->addSpacing(10); vlayout->addWidget(extras_w); vlayout->addSpacing(10); + // Ensure that we cannot make the dialog smaller than it wants to be + setMinimumWidth(width()); + show(); } @@ -896,12 +940,15 @@ void SetupDialog::accept() QString::number(m_soloLegacyBehavior)); ConfigManager::inst()->setValue("ui", "trackdeletionwarning", QString::number(m_trackDeletionWarning)); + ConfigManager::inst()->setValue("ui", "mixerchanneldeletionwarning", + QString::number(m_mixerChannelDeletionWarning)); ConfigManager::inst()->setValue("app", "nommpz", QString::number(!m_MMPZ)); ConfigManager::inst()->setValue("app", "disablebackup", 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)); @@ -931,6 +978,7 @@ void SetupDialog::accept() 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)); @@ -1017,6 +1065,11 @@ void SetupDialog::toggleTrackDeletionWarning(bool enabled) m_trackDeletionWarning = enabled; } +void SetupDialog::toggleMixerChannelDeletionWarning(bool enabled) +{ + m_mixerChannelDeletionWarning = enabled; +} + void SetupDialog::toggleMMPZ(bool enabled) { @@ -1036,6 +1089,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]; @@ -1138,6 +1197,28 @@ void SetupDialog::audioInterfaceChanged(const QString & iface) } +void SetupDialog::updateBufferSizeWarning(int value) +{ + QString text = "
    "; + // 'value' is not a power of 2 (for value > 0) and under 256. On buffer sizes larger than 256 + // lmms works with chunks of size 256 and only the final mix will use the actual buffer size. + // Plugins don't see a larger buffer size than 256 so anything larger than this is functionally + // a 'power of 2' value. + if(((value & (value - 1)) != 0) && value < 256) + { + text += "
  • " + tr("The currently selected value is not a power of 2 " + "(32, 64, 128, 256). Some plugins may not be available.") + "
  • "; + } + if(value <= 32) + { + text += "
  • " + tr("The currently selected value is less than or equal to 32. " + "Some plugins may not be available.") + "
  • "; + } + text += "
"; + m_bufferSizeWarnLbl->setText(text); +} + + void SetupDialog::setBufferSize(int value) { const int step = DEFAULT_BUFFER_SIZE / BUFFERSIZE_RESOLUTION; @@ -1163,6 +1244,7 @@ void SetupDialog::setBufferSize(int value) m_bufferSize = value * BUFFERSIZE_RESOLUTION; m_bufferSizeLbl->setText(tr("Frames: %1\nLatency: %2 ms").arg(m_bufferSize).arg( 1000.0f * m_bufferSize / Engine::audioEngine()->processingSampleRate(), 0, 'f', 1)); + updateBufferSizeWarning(m_bufferSize); } @@ -1185,6 +1267,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/tracks/InstrumentTrackView.cpp b/src/gui/tracks/InstrumentTrackView.cpp index 5daa51381..c812999fd 100644 --- a/src/gui/tracks/InstrumentTrackView.cpp +++ b/src/gui/tracks/InstrumentTrackView.cpp @@ -40,6 +40,7 @@ #include "Mixer.h" #include "MixerView.h" #include "GuiApplication.h" +#include "Instrument.h" #include "InstrumentTrack.h" #include "InstrumentTrackWindow.h" #include "MainWindow.h" @@ -62,8 +63,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->move( 3, 1 ); + m_tlb->setIcon(determinePixmap(_it)); m_tlb->show(); connect( m_tlb, SIGNAL(toggled(bool)), @@ -75,32 +75,21 @@ InstrumentTrackView::InstrumentTrackView( InstrumentTrack * _it, TrackContainerV connect(ConfigManager::inst(), SIGNAL(valueChanged(QString,QString,QString)), this, SLOT(handleConfigChange(QString,QString,QString))); - // creation of widgets for track-settings-widget - int widgetWidth; - if( ConfigManager::inst()->value( "ui", - "compacttrackbuttons" ).toInt() ) - { - widgetWidth = DEFAULT_SETTINGS_WIDGET_WIDTH_COMPACT; - } - else - { - widgetWidth = DEFAULT_SETTINGS_WIDGET_WIDTH; - } + m_mixerChannelNumber = new MixerChannelLcdSpinBox(2, getTrackSettingsWidget(), tr("Mixer channel"), this); + m_mixerChannelNumber->show(); - m_volumeKnob = new Knob( knobSmall_17, getTrackSettingsWidget(), + m_volumeKnob = new Knob( KnobType::Small17, getTrackSettingsWidget(), tr( "Volume" ) ); m_volumeKnob->setVolumeKnob( true ); m_volumeKnob->setModel( &_it->m_volumeModel ); m_volumeKnob->setHintText( tr( "Volume:" ), "%" ); - m_volumeKnob->move( widgetWidth-2*24, 2 ); m_volumeKnob->setLabel( tr( "VOL" ) ); m_volumeKnob->show(); - m_panningKnob = new Knob( knobSmall_17, getTrackSettingsWidget(), + m_panningKnob = new Knob( KnobType::Small17, getTrackSettingsWidget(), tr( "Panning" ) ); m_panningKnob->setModel( &_it->m_panningModel ); m_panningKnob->setHintText(tr("Panning:"), "%"); - m_panningKnob->move( widgetWidth-24, 2 ); m_panningKnob->setLabel( tr( "PAN" ) ); m_panningKnob->show(); @@ -110,9 +99,9 @@ InstrumentTrackView::InstrumentTrackView( InstrumentTrack * _it, TrackContainerV if( !Engine::audioEngine()->midiClient()->isRaw() ) { _it->m_midiPort.m_readablePortsMenu = new MidiPortMenu( - MidiPort::Input ); + MidiPort::Mode::Input ); _it->m_midiPort.m_writablePortsMenu = new MidiPortMenu( - MidiPort::Output ); + MidiPort::Mode::Output ); _it->m_midiPort.m_readablePortsMenu->setModel( &_it->m_midiPort ); _it->m_midiPort.m_writablePortsMenu->setModel( @@ -151,9 +140,22 @@ InstrumentTrackView::InstrumentTrackView( InstrumentTrack * _it, TrackContainerV QApplication::palette().color( QPalette::Active, QPalette::BrightText).darker(), getTrackSettingsWidget() ); - m_activityIndicator->setGeometry( - widgetWidth-2*24-11, 2, 8, 28 ); + m_activityIndicator->setFixedSize(8, 28); m_activityIndicator->show(); + + auto 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); + layout->addWidget(m_mixerChannelNumber); + 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())); connect( m_activityIndicator, SIGNAL(released()), @@ -211,7 +213,7 @@ InstrumentTrackWindow * InstrumentTrackView::topLevelInstrumentTrackWindow() getGUI()->mainWindow()->workspace()->subWindowList( QMdiArea::ActivationHistoryOrder ) ) { - if( sw->isVisible() && sw->widget()->inherits( "InstrumentTrackWindow" ) ) + if( sw->isVisible() && sw->widget()->inherits( "lmms::gui::InstrumentTrackWindow" ) ) { w = qobject_cast( sw->widget() ); } @@ -230,7 +232,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); } @@ -243,7 +245,7 @@ void InstrumentTrackView::assignMixerLine(int channelIndex) { model()->mixerChannelModel()->setValue( channelIndex ); - getGUI()->mixerView()->setCurrentMixerLine( channelIndex ); + getGUI()->mixerView()->setCurrentMixerChannel(channelIndex); } @@ -268,6 +270,13 @@ void InstrumentTrackView::handleConfigChange(QString cls, QString attr, QString } } +void InstrumentTrackView::modelChanged() +{ + TrackView::modelChanged(); + auto st = castModel(); + m_mixerChannelNumber->setModel(&st->m_mixerChannelModel); +} + void InstrumentTrackView::dragEnterEvent( QDragEnterEvent * _dee ) { InstrumentTrackWindow::dragEnterEventGeneric( _dee ); @@ -388,5 +397,33 @@ QMenu * InstrumentTrackView::createMixerMenu(QString title, QString newMixerLabe return mixerMenu; } +QPixmap InstrumentTrackView::determinePixmap() +{ + return determinePixmap(dynamic_cast(getTrack())); +} + + +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 08ddee863..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 @@ -56,39 +56,49 @@ SampleTrackView::SampleTrackView( SampleTrack * _t, TrackContainerView* tcv ) : connect(m_tlb, SIGNAL(clicked(bool)), this, SLOT(showEffects())); m_tlb->setIcon(embed::getIconPixmap("sample_track")); - m_tlb->move(3, 1); m_tlb->show(); - m_volumeKnob = new Knob( knobSmall_17, getTrackSettingsWidget(), + m_mixerChannelNumber = new MixerChannelLcdSpinBox(2, getTrackSettingsWidget(), tr("Mixer channel"), this); + m_mixerChannelNumber->show(); + + m_volumeKnob = new Knob( KnobType::Small17, getTrackSettingsWidget(), tr( "Track volume" ) ); m_volumeKnob->setVolumeKnob( true ); m_volumeKnob->setModel( &_t->m_volumeModel ); m_volumeKnob->setHintText( tr( "Channel volume:" ), "%" ); - int settingsWidgetWidth = ConfigManager::inst()-> - value( "ui", "compacttrackbuttons" ).toInt() - ? DEFAULT_SETTINGS_WIDGET_WIDTH_COMPACT - : DEFAULT_SETTINGS_WIDGET_WIDTH; - m_volumeKnob->move( settingsWidgetWidth - 2 * 24, 2 ); m_volumeKnob->setLabel( tr( "VOL" ) ); m_volumeKnob->show(); - m_panningKnob = new Knob( knobSmall_17, getTrackSettingsWidget(), + m_panningKnob = new Knob( KnobType::Small17, getTrackSettingsWidget(), tr( "Panning" ) ); m_panningKnob->setModel( &_t->m_panningModel ); m_panningKnob->setHintText( tr( "Panning:" ), "%" ); - m_panningKnob->move( settingsWidgetWidth - 24, 2 ); m_panningKnob->setLabel( tr( "PAN" ) ); m_panningKnob->show(); m_activityIndicator = new FadeButton( - QApplication::palette().color(QPalette::Active, QPalette::Background), + QApplication::palette().color(QPalette::Active, QPalette::Window), QApplication::palette().color(QPalette::Active, QPalette::BrightText), QApplication::palette().color(QPalette::Active, QPalette::BrightText).darker(), getTrackSettingsWidget() ); - m_activityIndicator->setGeometry(settingsWidgetWidth - 2 * 24 - 11, 2, 8, 28); + m_activityIndicator->setFixedSize(8, 28); m_activityIndicator->show(); + + auto 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); + layout->addWidget(m_mixerChannelNumber); + 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())); setModel( _t ); @@ -170,6 +180,7 @@ void SampleTrackView::modelChanged() { auto st = castModel(); m_volumeKnob->setModel(&st->m_volumeModel); + m_mixerChannelNumber->setModel(&st->m_mixerChannelModel); TrackView::modelChanged(); } @@ -221,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); } @@ -234,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 74ab016ea..619eff831 100644 --- a/src/gui/tracks/TrackContentWidget.cpp +++ b/src/gui/tracks/TrackContentWidget.cpp @@ -291,7 +291,7 @@ void TrackContentWidget::dragEnterEvent( QDragEnterEvent * dee ) else { StringPairDrag::processDragEnterEvent( dee, "clip_" + - QString::number( getTrack()->type() ) ); + QString::number( static_cast(getTrack()->type()) ) ); } } @@ -325,8 +325,7 @@ bool TrackContentWidget::canPasteSelection( TimePos clipPos, const QMimeData* md QString value = decodeValue( md ); // We can only paste into tracks of the same type - if( type != ( "clip_" + QString::number( t->type() ) ) || - m_trackView->trackContainerView()->fixedClips() == true ) + if (type != ("clip_" + QString::number(static_cast(t->type())))) { return false; } @@ -345,8 +344,9 @@ bool TrackContentWidget::canPasteSelection( TimePos clipPos, const QMimeData* md const int initialTrackIndex = tiAttr.value().toInt(); // Get the current track's index - const TrackContainer::TrackList tracks = t->trackContainer()->tracks(); - const int currentTrackIndex = tracks.indexOf( t ); + const TrackContainer::TrackList& tracks = t->trackContainer()->tracks(); + const auto currentTrackIt = std::find(tracks.begin(), tracks.end(), t); + const int currentTrackIndex = currentTrackIt != tracks.end() ? std::distance(tracks.begin(), currentTrackIt) : -1; // Don't paste if we're on the same bar and allowSameBar is false auto sourceTrackContainerId = metadata.attributeNode( "trackContainerId" ).value().toUInt(); @@ -360,6 +360,14 @@ bool TrackContentWidget::canPasteSelection( TimePos clipPos, const QMimeData* md QDomElement clipParent = dataFile.content().firstChildElement("clips"); QDomNodeList clipNodes = clipParent.childNodes(); + // If we are pasting into the PatternEditor, only a single Clip is allowed to be pasted + // so we don't have the unexpected behavior of pasting on different PatternTracks + if (m_trackView->trackContainerView()->fixedClips() == true && + clipNodes.length() > 1) + { + return false; + } + // Determine if all the Clips will land on a valid track for( int i = 0; i < clipNodes.length(); i++ ) { @@ -374,7 +382,7 @@ bool TrackContentWidget::canPasteSelection( TimePos clipPos, const QMimeData* md } // Track must be of the same type - auto startTrackType = clipElement.attributeNode("trackType").value().toInt(); + auto startTrackType = static_cast(clipElement.attributeNode("trackType").value().toInt()); Track * endTrack = tracks.at( finalTrackIndex ); if( startTrackType != endTrack->type() ) { @@ -435,8 +443,9 @@ bool TrackContentWidget::pasteSelection( TimePos clipPos, const QMimeData * md, TimePos grabbedClipPos = clipPosAttr.value().toInt(); // Snap the mouse position to the beginning of the dropped bar, in ticks - const TrackContainer::TrackList tracks = getTrack()->trackContainer()->tracks(); - const int currentTrackIndex = tracks.indexOf( getTrack() ); + const TrackContainer::TrackList& tracks = getTrack()->trackContainer()->tracks(); + const auto currentTrackIt = std::find(tracks.begin(), tracks.end(), getTrack()); + const int currentTrackIndex = currentTrackIt != tracks.end() ? std::distance(tracks.begin(), currentTrackIt) : -1; bool wasSelection = m_trackView->trackContainerView()->rubberBand()->selectedObjects().count(); @@ -529,7 +538,7 @@ void TrackContentWidget::mousePressEvent( QMouseEvent * me ) // Enable box select if control is held when clicking an empty space // (If we had clicked a Clip it would have intercepted the mouse event) if( me->modifiers() & Qt::ControlModifier ){ - getGUI()->songEditor()->m_editor->setEditMode(SongEditor::EditMode::SelectMode); + getGUI()->songEditor()->m_editor->setEditMode(SongEditor::EditMode::Select); } // Forward event to allow box select if the editor supports it and is in that mode if( m_trackView->trackContainerView()->allowRubberband() == true ) @@ -646,7 +655,7 @@ void TrackContentWidget::contextMenuEvent( QContextMenuEvent * cme ) QMenu contextMenu( this ); QAction *pasteA = contextMenu.addAction( embed::getIconPixmap( "edit_paste" ), - tr( "Paste" ), [this, cme](){ contextMenuAction( cme, Paste ); } ); + tr( "Paste" ), [this, cme](){ contextMenuAction( cme, ContextMenuAction::Paste ); } ); // If we can't paste in the current TCW for some reason, disable the action so the user knows pasteA->setEnabled( canPasteSelection( getPosition( cme->x() ), getMimeData() ) ? true : false ); @@ -660,7 +669,7 @@ void TrackContentWidget::contextMenuAction( QContextMenuEvent * cme, ContextMenu switch( action ) { - case Paste: + case ContextMenuAction::Paste: // Paste the selection on the TimePos of the context menu event TimePos clipPos = getPosition( cme->x() ); diff --git a/src/gui/tracks/TrackLabelButton.cpp b/src/gui/tracks/TrackLabelButton.cpp index 3f1b45871..c164b780e 100644 --- a/src/gui/tracks/TrackLabelButton.cpp +++ b/src/gui/tracks/TrackLabelButton.cpp @@ -30,13 +30,11 @@ #include "ConfigManager.h" #include "embed.h" -#include "Engine.h" -#include "Instrument.h" -#include "InstrumentTrack.h" +#include "InstrumentTrackView.h" #include "RenameDialog.h" -#include "Song.h" #include "TrackRenameLineEdit.h" #include "TrackView.h" +#include "Track.h" namespace lmms::gui { @@ -53,7 +51,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 +75,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 +83,6 @@ void TrackLabelButton::rename() if( txt != text() ) { m_trackView->getTrack()->setName( txt ); - Engine::getSong()->setModified(); } } else @@ -103,7 +100,7 @@ void TrackLabelButton::rename() void TrackLabelButton::renameFinished() { - if( !( ConfigManager::inst()->value( "ui", "compacttrackbuttons" ).toInt() ) ) + if (!isInCompactMode()) { m_renameLineEdit->clearFocus(); m_renameLineEdit->hide(); @@ -113,7 +110,6 @@ void TrackLabelButton::renameFinished() { setText( elideName( m_renameLineEdit->text() ) ); m_trackView->getTrack()->setName( m_renameLineEdit->text() ); - Engine::getSong()->setModified(); } } } @@ -183,37 +179,18 @@ void TrackLabelButton::mouseReleaseEvent( QMouseEvent *_me ) } - - -void TrackLabelButton::paintEvent( QPaintEvent * _pe ) +void TrackLabelButton::paintEvent(QPaintEvent* pe) { - if( m_trackView->getTrack()->type() == Track::InstrumentTrack ) + InstrumentTrackView* instrumentTrackView = dynamic_cast(m_trackView); + if (instrumentTrackView) { - auto it = dynamic_cast(m_trackView->getTrack()); - 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() && - it->instrument()->descriptor() && - ( pl = get_logo(it) ) ) - { - if( pl->pixmapName() != m_iconName ) - { - m_iconName = pl->pixmapName(); - setIcon( pl->pixmap() ); - } - } + setIcon(instrumentTrackView->determinePixmap()); } - QToolButton::paintEvent( _pe ); + + QToolButton::paintEvent(pe); } - - void TrackLabelButton::resizeEvent(QResizeEvent *_re) { setText( elideName( m_trackView->getTrack()->displayName() ) ); @@ -237,5 +214,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 ddbd2eacd..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" @@ -64,7 +63,6 @@ TrackOperationsWidget::TrackOperationsWidget( TrackView * parent ) : "to begin a new drag'n'drop action." ).arg(UI_CTRL_KEY) ); auto toMenu = new QMenu(this); - toMenu->setFont( pointSize<9>( toMenu->font() ) ); connect( toMenu, SIGNAL(aboutToShow()), this, SLOT(updateMenu())); @@ -137,12 +135,12 @@ void TrackOperationsWidget::mousePressEvent( QMouseEvent * me ) { if( me->button() == Qt::LeftButton && me->modifiers() & Qt::ControlModifier && - m_trackView->getTrack()->type() != Track::PatternTrack) + m_trackView->getTrack()->type() != Track::Type::Pattern) { - DataFile dataFile( DataFile::DragNDropData ); + DataFile dataFile( DataFile::Type::DragNDropData ); m_trackView->getTrack()->saveState( dataFile, dataFile.content() ); new StringPairDrag( QString( "track_%1" ).arg( - m_trackView->getTrack()->type() ), + static_cast(m_trackView->getTrack()->type()) ), dataFile.toString(), m_trackView->getTrackSettingsWidget()->grab(), this ); } @@ -173,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")); @@ -196,7 +194,7 @@ bool TrackOperationsWidget::confirmRemoval() QString messageTitleRemoveTrack = tr("Confirm removal"); QString askAgainText = tr("Don't ask again"); auto askAgainCheckBox = new QCheckBox(askAgainText, nullptr); - connect(askAgainCheckBox, &QCheckBox::stateChanged, [this](int state){ + connect(askAgainCheckBox, &QCheckBox::stateChanged, [](int state){ // Invert button state, if it's checked we *shouldn't* ask again ConfigManager::inst()->setValue("ui", "trackdeletionwarning", state ? "0" : "1"); }); @@ -266,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(); } @@ -282,7 +280,7 @@ void TrackOperationsWidget::resetTrackColor() { auto track = m_trackView->getTrack(); track->addJournalCheckPoint(); - track->resetColor(); + track->setColor(std::nullopt); Engine::getSong()->setModified(); } @@ -299,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 0aabc705d..e23236021 100644 --- a/src/gui/tracks/TrackView.cpp +++ b/src/gui/tracks/TrackView.cpp @@ -65,7 +65,7 @@ TrackView::TrackView( Track * track, TrackContainerView * tcv ) : m_trackOperationsWidget( this ), /*!< Our trackOperationsWidget */ m_trackSettingsWidget( this ), /*!< Our trackSettingsWidget */ m_trackContentWidget( this ), /*!< Our trackContentWidget */ - m_action( NoAction ) /*!< The action we're currently performing */ + m_action( Action::None ) /*!< The action we're currently performing */ { setAutoFillBackground( true ); QPalette pal; @@ -75,7 +75,7 @@ TrackView::TrackView( Track * track, TrackContainerView * tcv ) : m_trackSettingsWidget.setAutoFillBackground( true ); auto layout = new QHBoxLayout(this); - layout->setMargin( 0 ); + layout->setContentsMargins(0, 0, 0, 0); layout->setSpacing( 0 ); layout->addWidget( &m_trackOperationsWidget ); layout->addWidget( &m_trackSettingsWidget ); @@ -207,7 +207,7 @@ void TrackView::modelChanged() void TrackView::dragEnterEvent( QDragEnterEvent * dee ) { StringPairDrag::processDragEnterEvent( dee, "track_" + - QString::number( m_track->type() ) ); + QString::number( static_cast(m_track->type()) ) ); } @@ -225,7 +225,7 @@ void TrackView::dropEvent( QDropEvent * de ) { QString type = StringPairDrag::decodeKey( de ); QString value = StringPairDrag::decodeValue( de ); - if( type == ( "track_" + QString::number( m_track->type() ) ) ) + if( type == ( "track_" + QString::number( static_cast(m_track->type()) ) ) ) { // value contains our XML-data so simply create a // DataFile which does the rest for us... @@ -278,7 +278,7 @@ void TrackView::mousePressEvent( QMouseEvent * me ) { if( me->modifiers() & Qt::ShiftModifier ) { - m_action = ResizeTrack; + m_action = Action::Resize; QCursor::setPos( mapToGlobal( QPoint( me->x(), height() ) ) ); QCursor c( Qt::SizeVerCursor); @@ -292,7 +292,7 @@ void TrackView::mousePressEvent( QMouseEvent * me ) return; } - m_action = MoveTrack; + m_action = Action::Move; QCursor c( Qt::SizeVerCursor ); QApplication::setOverrideCursor( c ); @@ -338,7 +338,7 @@ void TrackView::mouseMoveEvent( QMouseEvent * me ) { QWidget::mouseMoveEvent( me ); } - else if( m_action == MoveTrack ) + else if( m_action == Action::Move ) { // look which track-widget the mouse-cursor is over const int yPos = @@ -362,11 +362,9 @@ void TrackView::mouseMoveEvent( QMouseEvent * me ) } } } - else if( m_action == ResizeTrack ) + 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 ) @@ -383,7 +381,7 @@ void TrackView::mouseMoveEvent( QMouseEvent * me ) */ void TrackView::mouseReleaseEvent( QMouseEvent * me ) { - m_action = NoAction; + m_action = Action::None; while( QApplication::overrideCursor() != nullptr ) { QApplication::restoreOverrideCursor(); @@ -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/CPULoadWidget.cpp b/src/gui/widgets/CPULoadWidget.cpp index 799e037ef..db1f5cacc 100644 --- a/src/gui/widgets/CPULoadWidget.cpp +++ b/src/gui/widgets/CPULoadWidget.cpp @@ -24,6 +24,7 @@ */ +#include #include #include "AudioEngine.h" @@ -72,10 +73,9 @@ void CPULoadWidget::paintEvent( QPaintEvent * ) QPainter p( &m_temp ); p.drawPixmap( 0, 0, m_background ); - // as load-indicator consists of small 2-pixel wide leds with - // 1 pixel spacing, we have to make sure, only whole leds are - // shown which we achieve by the following formula - int w = ( m_leds.width() * m_currentLoad / 300 ) * 3; + // Normally the CPU load indicator moves smoothly, with 1 pixel resolution. However, some themes may want to + // draw discrete elements (like LEDs), so the stepSize property can be used to specify a larger step size. + int w = (m_leds.width() * std::min(m_currentLoad, 100) / (stepSize() * 100)) * stepSize(); if( w > 0 ) { p.drawPixmap( 23, 3, m_leds, 0, 0, w, @@ -91,10 +91,21 @@ void CPULoadWidget::paintEvent( QPaintEvent * ) void CPULoadWidget::updateCpuLoad() { - // smooth load-values a bit - int new_load = ( m_currentLoad + Engine::audioEngine()->cpuLoad() ) / 2; - if( new_load != m_currentLoad ) + // Additional display smoothing for the main load-value. Stronger averaging + // cannot be used directly in the profiler: cpuLoad() must react fast enough + // to be useful as overload indicator in AudioEngine::criticalXRuns(). + const int new_load = (m_currentLoad + Engine::audioEngine()->cpuLoad()) / 2; + + if (new_load != m_currentLoad) { + auto engine = Engine::audioEngine(); + setToolTip( + tr("DSP total: %1%").arg(new_load) + "\n" + + tr(" - Notes and setup: %1%").arg(engine->detailLoad(AudioEngineProfiler::DetailType::NoteSetup)) + "\n" + + tr(" - Instruments: %1%").arg(engine->detailLoad(AudioEngineProfiler::DetailType::Instruments)) + "\n" + + tr(" - Effects: %1%").arg(engine->detailLoad(AudioEngineProfiler::DetailType::Effects)) + "\n" + + tr(" - Mixing: %1%").arg(engine->detailLoad(AudioEngineProfiler::DetailType::Mixing)) + ); m_currentLoad = new_load; m_changed = true; update(); @@ -102,4 +113,4 @@ void CPULoadWidget::updateCpuLoad() } -} // namespace lmms::gui \ No newline at end of file +} // namespace lmms::gui diff --git a/src/gui/widgets/ComboBox.cpp b/src/gui/widgets/ComboBox.cpp index bdf78ccce..eb019876a 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,23 +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() ) ); - m_menu.setFont( pointSize<8>( m_menu.font() ) ); + setFont(adjustedToPixelSize(font(), 10)); connect( &m_menu, SIGNAL(triggered(QAction*)), this, SLOT(setItem(QAction*))); @@ -137,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(); } @@ -173,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(); @@ -195,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 ) { diff --git a/src/gui/widgets/CustomTextKnob.cpp b/src/gui/widgets/CustomTextKnob.cpp index 9f6e19b90..a4edde47c 100644 --- a/src/gui/widgets/CustomTextKnob.cpp +++ b/src/gui/widgets/CustomTextKnob.cpp @@ -1,10 +1,34 @@ +/* + * CustomTextKnob.cpp + * + * Copyright (c) 2020 Ibuki Sugiyama
+ * + * 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 "CustomTextKnob.h" namespace lmms::gui { -CustomTextKnob::CustomTextKnob( knobTypes _knob_num, QWidget * _parent, const QString & _name, const QString & _value_text ) : +CustomTextKnob::CustomTextKnob( KnobType _knob_num, QWidget * _parent, const QString & _name, const QString & _value_text ) : Knob( _knob_num, _parent, _name ), m_value_text( _value_text ) {} diff --git a/src/gui/widgets/Fader.cpp b/src/gui/widgets/Fader.cpp index f853ff707..1eb06756a 100644 --- a/src/gui/widgets/Fader.cpp +++ b/src/gui/widgets/Fader.cpp @@ -49,128 +49,71 @@ #include #include #include +#include #include "lmms_math.h" #include "embed.h" #include "CaptionMenu.h" #include "ConfigManager.h" -#include "TextFloat.h" +#include "SimpleTextFloat.h" namespace lmms::gui { +SimpleTextFloat* Fader::s_textFloat = nullptr; -TextFloat * 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 TextFloat; - } - 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" ) ); + s_textFloat = new SimpleTextFloat; } - 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 TextFloat; - } - - 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,20 @@ void Fader::mouseReleaseEvent( QMouseEvent * mouseEvent ) } -void Fader::wheelEvent ( QWheelEvent *ev ) +void Fader::wheelEvent (QWheelEvent* ev) { ev->accept(); if (ev->angleDelta().y() > 0) { - model()->incValue( 1 ); + model()->incValue(1); } else { - model()->incValue( -1 ); + model()->incValue(-1); } updateTextFloat(); - s_textFloat->setVisibilityTimeOut( 1000 ); + s_textFloat->setVisibilityTimeOut(1000); } @@ -271,21 +211,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 +226,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,168 +252,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() - ( *m_knob ).width() - 5, knobPosY() - 46 ) ); + + 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 \ No newline at end of file +} // namespace lmms::gui diff --git a/src/gui/widgets/FloatModelEditorBase.cpp b/src/gui/widgets/FloatModelEditorBase.cpp new file mode 100644 index 000000000..dd6be8958 --- /dev/null +++ b/src/gui/widgets/FloatModelEditorBase.cpp @@ -0,0 +1,470 @@ +/* + * FloatModelEditorBase.cpp - Base editor for float models + * + * Copyright (c) 2004-2014 Tobias Doerffel + * Copyright (c) 2023 Michael Gregorius + * + * This file is part of LMMS - https://lmms.io + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program (see COPYING); if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA. + * + */ + +#include "FloatModelEditorBase.h" + +#include +#include +#include + +#ifndef __USE_XOPEN +#define __USE_XOPEN +#endif + +#include "lmms_math.h" +#include "CaptionMenu.h" +#include "ControllerConnection.h" +#include "GuiApplication.h" +#include "LocaleHelper.h" +#include "MainWindow.h" +#include "ProjectJournal.h" +#include "SimpleTextFloat.h" +#include "StringPairDrag.h" + + +namespace lmms::gui +{ + +SimpleTextFloat * FloatModelEditorBase::s_textFloat = nullptr; + +FloatModelEditorBase::FloatModelEditorBase(DirectionOfManipulation directionOfManipulation, QWidget * parent, const QString & name) : + QWidget(parent), + FloatModelView(new FloatModel(0, 0, 0, 1, nullptr, name, true), this), + m_volumeKnob(false), + m_volumeRatio(100.0, 0.0, 1000000.0), + m_buttonPressed(false), + m_directionOfManipulation(directionOfManipulation) +{ + initUi(name); +} + + +void FloatModelEditorBase::initUi(const QString & name) +{ + if (s_textFloat == nullptr) + { + s_textFloat = new SimpleTextFloat; + } + + setWindowTitle(name); + + setFocusPolicy(Qt::ClickFocus); + + doConnections(); +} + + +void FloatModelEditorBase::showTextFloat(int msecBeforeDisplay, int msecDisplayTime) +{ + s_textFloat->setText(displayValue()); + s_textFloat->moveGlobal(this, QPoint(width() + 2, 0)); + s_textFloat->showWithDelay(msecBeforeDisplay, msecDisplayTime); +} + + +float FloatModelEditorBase::getValue(const QPoint & p) +{ + // Find out which direction/coordinate is relevant for this control + int const coordinate = m_directionOfManipulation == DirectionOfManipulation::Vertical ? p.y() : -p.x(); + + // knob value increase is linear to mouse movement + float value = .4f * coordinate; + + // if shift pressed we want slower movement + if (getGUI()->mainWindow()->isShiftPressed()) + { + value /= 4.0f; + value = qBound(-4.0f, value, 4.0f); + } + + return value * pageSize(); +} + + +void FloatModelEditorBase::contextMenuEvent(QContextMenuEvent *) +{ + // for the case, the user clicked right while pressing left mouse- + // button, the context-menu appears while mouse-cursor is still hidden + // and it isn't shown again until user does something which causes + // an QApplication::restoreOverrideCursor()-call... + mouseReleaseEvent(nullptr); + + CaptionMenu contextMenu(model()->displayName(), this); + addDefaultActions(&contextMenu); + contextMenu.addAction(QPixmap(), + model()->isScaleLogarithmic() ? tr("Set linear") : tr("Set logarithmic"), + this, SLOT(toggleScale())); + contextMenu.addSeparator(); + contextMenu.exec(QCursor::pos()); +} + + +void FloatModelEditorBase::toggleScale() +{ + model()->setScaleLogarithmic(! model()->isScaleLogarithmic()); + update(); +} + + +void FloatModelEditorBase::dragEnterEvent(QDragEnterEvent * dee) +{ + StringPairDrag::processDragEnterEvent(dee, "float_value," + "automatable_model"); +} + + +void FloatModelEditorBase::dropEvent(QDropEvent * de) +{ + QString type = StringPairDrag::decodeKey(de); + QString val = StringPairDrag::decodeValue(de); + if (type == "float_value") + { + model()->setValue(LocaleHelper::toFloat(val)); + de->accept(); + } + else if (type == "automatable_model") + { + auto mod = dynamic_cast(Engine::projectJournal()->journallingObject(val.toInt())); + if (mod != nullptr) + { + AutomatableModel::linkModels(model(), mod); + mod->setValue(model()->value()); + } + } +} + + +void FloatModelEditorBase::mousePressEvent(QMouseEvent * me) +{ + if (me->button() == Qt::LeftButton && + ! (me->modifiers() & Qt::ControlModifier) && + ! (me->modifiers() & Qt::ShiftModifier)) + { + AutomatableModel *thisModel = model(); + if (thisModel) + { + thisModel->addJournalCheckPoint(); + thisModel->saveJournallingState(false); + } + + const QPoint & p = me->pos(); + m_lastMousePos = p; + m_leftOver = 0.0f; + + emit sliderPressed(); + + showTextFloat(0, 0); + + s_textFloat->setText(displayValue()); + s_textFloat->moveGlobal(this, + QPoint(width() + 2, 0)); + s_textFloat->show(); + m_buttonPressed = true; + } + else if (me->button() == Qt::LeftButton && + (me->modifiers() & Qt::ShiftModifier)) + { + new StringPairDrag("float_value", + QString::number(model()->value()), + QPixmap(), this); + } + else + { + FloatModelView::mousePressEvent(me); + } +} + + +void FloatModelEditorBase::mouseMoveEvent(QMouseEvent * me) +{ + if (m_buttonPressed && me->pos() != m_lastMousePos) + { + // knob position is changed depending on last mouse position + setPosition(me->pos() - m_lastMousePos); + emit sliderMoved(model()->value()); + // original position for next time is current position + m_lastMousePos = me->pos(); + } + s_textFloat->setText(displayValue()); + s_textFloat->show(); +} + + +void FloatModelEditorBase::mouseReleaseEvent(QMouseEvent* event) +{ + if (event && event->button() == Qt::LeftButton) + { + AutomatableModel *thisModel = model(); + if (thisModel) + { + thisModel->restoreJournallingState(); + } + } + + m_buttonPressed = false; + + emit sliderReleased(); + + QApplication::restoreOverrideCursor(); + + s_textFloat->hide(); +} + + +void FloatModelEditorBase::enterEvent(QEvent *event) +{ + showTextFloat(700, 2000); +} + + +void FloatModelEditorBase::leaveEvent(QEvent *event) +{ + s_textFloat->hide(); +} + + +void FloatModelEditorBase::focusOutEvent(QFocusEvent * fe) +{ + // make sure we don't loose mouse release event + mouseReleaseEvent(nullptr); + QWidget::focusOutEvent(fe); +} + + +void FloatModelEditorBase::mouseDoubleClickEvent(QMouseEvent *) +{ + enterValue(); +} + + +void FloatModelEditorBase::paintEvent(QPaintEvent *) +{ + QPainter p(this); + + QColor const foreground(3, 94, 97); + + auto const * mod = model(); + auto const minValue = mod->minValue(); + auto const maxValue = mod->maxValue(); + auto const range = maxValue - minValue; + + // Compute the percentage + // min + x * (max - min) = v <=> x = (v - min) / (max - min) + auto const percentage = range == 0 ? 1. : (mod->value() - minValue) / range; + + QRect r = rect(); + p.setPen(foreground); + p.setBrush(foreground); + p.drawRect(QRect(r.topLeft(), QPoint(r.width() * percentage, r.height()))); +} + + +void FloatModelEditorBase::wheelEvent(QWheelEvent * we) +{ + we->accept(); + const int deltaY = we->angleDelta().y(); + float direction = deltaY > 0 ? 1 : -1; + + auto * m = model(); + float const step = m->step(); + float const range = m->range(); + + // This is the default number of steps or mouse wheel events that it takes to sweep + // from the lowest value to the highest value. + // It might be modified if the user presses modifier keys. See below. + float numberOfStepsForFullSweep = 100.; + + auto const modKeys = we->modifiers(); + if (modKeys == Qt::ShiftModifier) + { + // The shift is intended to go through the values in very coarse steps as in: + // "Shift into overdrive" + numberOfStepsForFullSweep = 10; + } + else if (modKeys == Qt::ControlModifier) + { + // The control key gives more control, i.e. it enables more fine-grained adjustments + numberOfStepsForFullSweep = 1000; + } + else if (modKeys == Qt::AltModifier) + { + // The alt key enables even finer adjustments + numberOfStepsForFullSweep = 2000; + + // It seems that on some systems pressing Alt with mess with the directions, + // i.e. scrolling the mouse wheel is interpreted as pressing the mouse wheel + // left and right. Account for this quirk. + if (deltaY == 0) + { + int const deltaX = we->angleDelta().x(); + if (deltaX != 0) + { + direction = deltaX > 0 ? 1 : -1; + } + } + } + + // Compute the number of steps but make sure that we always do at least one step + const float stepMult = std::max(range / numberOfStepsForFullSweep / step, 1.f); + const int inc = direction * stepMult; + model()->incValue(inc); + + s_textFloat->setText(displayValue()); + s_textFloat->moveGlobal(this, QPoint(width() + 2, 0)); + s_textFloat->setVisibilityTimeOut(1000); + + emit sliderMoved(model()->value()); +} + + +void FloatModelEditorBase::setPosition(const QPoint & p) +{ + const float value = getValue(p) + m_leftOver; + const auto step = model()->step(); + const float oldValue = model()->value(); + + if (model()->isScaleLogarithmic()) // logarithmic code + { + const float pos = model()->minValue() < 0 + ? oldValue / qMax(qAbs(model()->maxValue()), qAbs(model()->minValue())) + : (oldValue - model()->minValue()) / model()->range(); + const float ratio = 0.1f + qAbs(pos) * 15.f; + float newValue = value * ratio; + if (qAbs(newValue) >= step) + { + float roundedValue = qRound((oldValue - value) / step) * step; + model()->setValue(roundedValue); + m_leftOver = 0.0f; + } + else + { + m_leftOver = value; + } + } + + else // linear code + { + if (qAbs(value) >= step) + { + float roundedValue = qRound((oldValue - value) / step) * step; + model()->setValue(roundedValue); + m_leftOver = 0.0f; + } + else + { + m_leftOver = value; + } + } +} + + +void FloatModelEditorBase::enterValue() +{ + bool ok; + float new_val; + + if (isVolumeKnob() && + ConfigManager::inst()->value("app", "displaydbfs").toInt()) + { + 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 53a0a130d..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" @@ -36,7 +37,7 @@ namespace lmms namespace gui { -Graph::Graph( QWidget * _parent, graphStyle _style, int _width, +Graph::Graph( QWidget * _parent, Style _style, int _width, int _height ) : QWidget( _parent ), /* TODO: size, background? */ @@ -305,7 +306,7 @@ void Graph::paintEvent( QPaintEvent * ) switch( m_graphStyle ) { - case Graph::LinearStyle: + case Style::Linear: p.setRenderHints( QPainter::Antialiasing, true ); for( int i=0; i < length; i++ ) @@ -329,7 +330,7 @@ void Graph::paintEvent( QPaintEvent * ) break; - case Graph::NearestStyle: + case Style::Nearest: for( int i=0; i < length; i++ ) { p.drawLine(2+static_cast(i*xscale), @@ -350,7 +351,7 @@ void Graph::paintEvent( QPaintEvent * ) 2+static_cast( ( (*samps)[length] - maxVal ) * yscale ) ); break; - case Graph::LinearNonCyclicStyle: + case Style::LinearNonCyclic: p.setRenderHints( QPainter::Antialiasing, true ); for( int i=0; i < length; i++ ) @@ -369,7 +370,7 @@ void Graph::paintEvent( QPaintEvent * ) p.setRenderHints( QPainter::Antialiasing, false ); break; - case Graph::BarStyle: + case Style::Bar: for( int i=0; i <= length; i++ ) { p.fillRect( 2+static_cast( i*xscale ), @@ -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 049a86be1..d282f72c2 100644 --- a/src/gui/widgets/Knob.cpp +++ b/src/gui/widgets/Knob.cpp @@ -22,11 +22,8 @@ * */ -#include -#include -#include -#include -#include +#include "Knob.h" + #include #ifndef __USE_XOPEN @@ -34,38 +31,19 @@ #endif #include "lmms_math.h" -#include "Knob.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 "StringPairDrag.h" -#include "TextFloat.h" namespace lmms::gui { -TextFloat * Knob::s_textFloat = nullptr; - - - - -Knob::Knob( knobTypes _knob_num, QWidget * _parent, const QString & _name ) : - QWidget( _parent ), - FloatModelView( new FloatModel( 0, 0, 0, 1, nullptr, _name, true ), this ), +Knob::Knob( KnobType _knob_num, QWidget * _parent, const QString & _name ) : + 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 ), @@ -75,7 +53,7 @@ Knob::Knob( knobTypes _knob_num, QWidget * _parent, const QString & _name ) : } Knob::Knob( QWidget * _parent, const QString & _name ) : - Knob( knobBright_26, _parent, _name ) + Knob( KnobType::Bright26, _parent, _name ) { } @@ -84,18 +62,10 @@ Knob::Knob( QWidget * _parent, const QString & _name ) : void Knob::initUi( const QString & _name ) { - if( s_textFloat == nullptr ) - { - s_textFloat = new TextFloat; - } - - 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. // @@ -106,15 +76,15 @@ void Knob::initUi( const QString & _name ) // overrides that color. switch (knobNum()) { - case knobSmall_17: - case knobBright_26: - case knobDark_28: + case KnobType::Small17: + case KnobType::Bright26: + case KnobType::Dark28: m_lineActiveColor = QApplication::palette().color(QPalette::Active, QPalette::WindowText); m_arcActiveColor = QColor(QApplication::palette().color( QPalette::Active, QPalette::WindowText)); m_arcActiveColor.setAlpha(70); break; - case knobVintage_32: + case KnobType::Vintage32: m_lineActiveColor = QApplication::palette().color(QPalette::Active, QPalette::Shadow); m_arcActiveColor = QColor(QApplication::palette().color( QPalette::Active, QPalette::Shadow)); @@ -123,33 +93,29 @@ void Knob::initUi( const QString & _name ) default: break; } - - doConnections(); } - - void Knob::onKnobNumUpdated() { - if( m_knobNum != knobStyled ) + if( m_knobNum != KnobType::Styled ) { QString knobFilename; switch (m_knobNum) { - case knobDark_28: + case KnobType::Dark28: knobFilename = "knob01"; break; - case knobBright_26: + case KnobType::Bright26: knobFilename = "knob02"; break; - case knobSmall_17: + case KnobType::Small17: knobFilename = "knob03"; break; - case knobVintage_32: + case KnobType::Vintage32: knobFilename = "knob05"; break; - case knobStyled: // only here to stop the compiler from complaining + case KnobType::Styled: // only here to stop the compiler from complaining break; } @@ -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); } @@ -251,7 +217,7 @@ void Knob::setOuterRadius( float r ) -knobTypes Knob::knobNum() const +KnobType Knob::knobNum() const { return m_knobNum; } @@ -259,7 +225,7 @@ knobTypes Knob::knobNum() const -void Knob::setknobNum( knobTypes k ) +void Knob::setknobNum( KnobType k ) { if( m_knobNum != k ) { @@ -397,7 +363,7 @@ void Knob::drawKnob( QPainter * _p ) QPoint mid; - if( m_knobNum == knobStyled ) + if( m_knobNum == KnobType::Styled ) { p.setRenderHint( QPainter::Antialiasing ); @@ -448,17 +414,17 @@ void Knob::drawKnob( QPainter * _p ) p.setPen(QPen(currentLineColor, 2)); switch( m_knobNum ) { - case knobSmall_17: + case KnobType::Small17: { p.drawLine( calculateLine( mid, radius-2 ) ); break; } - case knobBright_26: + case KnobType::Bright26: { p.drawLine( calculateLine( mid, radius-5 ) ); break; } - case knobDark_28: + case KnobType::Dark28: { const float rb = qMax( ( radius - 10 ) / 3.0, 0.0 ); @@ -468,12 +434,12 @@ void Knob::drawKnob( QPainter * _p ) p.drawLine( ln ); break; } - case knobVintage_32: + case KnobType::Vintage32: { p.drawLine( calculateLine( mid, radius-2, 2 ) ); break; } - case knobStyled: + case KnobType::Styled: break; } @@ -484,181 +450,6 @@ void Knob::drawKnob( QPainter * _p ) _p->drawImage( 0, 0, m_cache ); } -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(); - - 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 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() ); -} - - - - -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::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 ); @@ -668,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, @@ -676,166 +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 float stepMult = model()->range() / 2000 / model()->step(); - const int inc = ((we->angleDelta().y() > 0 ) ? 1 : -1) * ((stepMult < 1 ) ? 1 : 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 e57012e10..c71d66568 100644 --- a/src/gui/widgets/LcdFloatSpinBox.cpp +++ b/src/gui/widgets/LcdFloatSpinBox.cpp @@ -38,6 +38,7 @@ #include #include "CaptionMenu.h" +#include "DeprecationHelper.h" #include "embed.h" #include "GuiApplication.h" #include "gui_templates.h" @@ -48,6 +49,7 @@ namespace lmms::gui LcdFloatSpinBox::LcdFloatSpinBox(int numWhole, int numFrac, const QString& name, QWidget* parent) : + QWidget(parent), FloatModelView(new FloatModel(0, 0, 0, 0, nullptr, name, true), this), m_wholeDisplay(numWhole, parent, name, false), m_fractionDisplay(numFrac, parent, name, true), @@ -61,6 +63,7 @@ LcdFloatSpinBox::LcdFloatSpinBox(int numWhole, int numFrac, const QString& name, LcdFloatSpinBox::LcdFloatSpinBox(int numWhole, int numFrac, const QString& style, const QString& name, QWidget* parent) : + QWidget(parent), FloatModelView(new FloatModel(0, 0, 0, 0, nullptr, name, true), this), m_wholeDisplay(numWhole, style, parent, name, false), m_fractionDisplay(numFrac, style, parent, name, true), @@ -100,16 +103,22 @@ void LcdFloatSpinBox::layoutSetup(const QString &style) outerLayout->setContentsMargins(0, 0, 0, 0); outerLayout->setSizeConstraint(QLayout::SetFixedSize); this->setLayout(outerLayout); + this->setFixedHeight(32); } void LcdFloatSpinBox::update() { - const int whole = static_cast(model()->value()); - const float fraction = model()->value() - whole; - const int intFraction = fraction * std::pow(10.f, m_fractionDisplay.numDigits()); - m_wholeDisplay.setValue(whole); - m_fractionDisplay.setValue(intFraction); + const int digitValue = std::pow(10.f, m_fractionDisplay.numDigits()); + float value = model()->value(); + int fraction = std::abs(std::round((value - static_cast(value)) * digitValue)); + if (fraction == digitValue) + { + value += std::copysign(1, value); + fraction = 0; + } + m_wholeDisplay.setValue(value); + m_fractionDisplay.setValue(fraction); QWidget::update(); } @@ -125,6 +134,9 @@ void LcdFloatSpinBox::contextMenuEvent(QContextMenuEvent* event) void LcdFloatSpinBox::mousePressEvent(QMouseEvent* event) { + // switch between integer and fractional step based on cursor position + m_intStep = event->x() < m_wholeDisplay.width(); + if (event->button() == Qt::LeftButton && !(event->modifiers() & Qt::ControlModifier) && event->y() < m_wholeDisplay.cellHeight() + 2) @@ -148,10 +160,6 @@ void LcdFloatSpinBox::mousePressEvent(QMouseEvent* event) void LcdFloatSpinBox::mouseMoveEvent(QMouseEvent* event) { - // switch between integer and fractional step based on cursor position - if (event->x() < m_wholeDisplay.width()) { m_intStep = true; } - else { m_intStep = false; } - if (m_mouseMoving) { int dy = event->globalY() - m_origMousePos.y(); @@ -179,7 +187,7 @@ void LcdFloatSpinBox::mouseReleaseEvent(QMouseEvent*) void LcdFloatSpinBox::wheelEvent(QWheelEvent *event) { // switch between integer and fractional step based on cursor position - if (event->x() < m_wholeDisplay.width()) { m_intStep = true; } + if (position(event).x() < m_wholeDisplay.width()) { m_intStep = true; } else { m_intStep = false; } event->accept(); @@ -237,11 +245,11 @@ 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().width(m_label) / 2 + 1, height(), m_label); + p.drawText(width() / 2 - p.fontMetrics().boundingRect(m_label).width() / 2 + 1, height(), m_label); p.setPen(m_wholeDisplay.textColor()); - p.drawText(width() / 2 - p.fontMetrics().width(m_label) / 2, height() - 1, m_label); + p.drawText(width() / 2 - p.fontMetrics().boundingRect(m_label).width() / 2, height() - 1, m_label); } } diff --git a/src/gui/widgets/LcdWidget.cpp b/src/gui/widgets/LcdWidget.cpp index 0f5e13466..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,9 +78,31 @@ void LcdWidget::setValue(int value) } } - m_display = s; + if (m_display != s) + { + m_display = s; - update(); + update(); + } +} + +void LcdWidget::setValue(float value) +{ + if (-1 < value && value < 0) + { + QString s = QString::number(static_cast(value)); + s.prepend('-'); + + if (m_display != s) + { + m_display = s; + update(); + } + } + else + { + setValue(static_cast(value)); + } } @@ -146,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); } @@ -158,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 ); } @@ -173,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 @@ -207,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, @@ -259,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 ); @@ -278,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 1be072815..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, LedColors _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, LedColors _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,42 +70,30 @@ 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() ); } -void LedCheckBox::initUi( LedColors _color ) +void LedCheckBox::initUi( LedColor _color ) { setCheckable( true ); - if( _color >= NumColors || _color < Yellow ) - { - _color = Yellow; - } - m_ledOnPixmap = new QPixmap( embed::getIconPixmap( - names[_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 ); } @@ -124,9 +102,38 @@ void LedCheckBox::initUi( LedColors _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 938177dce..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 @@ -43,12 +42,12 @@ MeterDialog::MeterDialog( QWidget * _parent, bool _simple ) : { auto vlayout = new QVBoxLayout(this); vlayout->setSpacing( 0 ); - vlayout->setMargin( 0 ); + vlayout->setContentsMargins(0, 0, 0, 0); auto num = new QWidget(this); auto num_layout = new QHBoxLayout(num); num_layout->setSpacing( 0 ); - num_layout->setMargin( 0 ); + num_layout->setContentsMargins(0, 0, 0, 0); m_numerator = new LcdSpinBox( 2, num, tr( "Meter Numerator" ) ); @@ -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 ); } @@ -69,7 +67,7 @@ MeterDialog::MeterDialog( QWidget * _parent, bool _simple ) : auto den = new QWidget(this); auto den_layout = new QHBoxLayout(den); den_layout->setSpacing( 0 ); - den_layout->setMargin( 0 ); + den_layout->setContentsMargins(0, 0, 0, 0); m_denominator = new LcdSpinBox( 2, den, tr( "Meter Denominator" ) ); m_denominator->setToolTip(tr("Meter denominator")); @@ -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/SimpleTextFloat.cpp b/src/gui/widgets/SimpleTextFloat.cpp new file mode 100644 index 000000000..e37753229 --- /dev/null +++ b/src/gui/widgets/SimpleTextFloat.cpp @@ -0,0 +1,93 @@ +/* + * TextFloat.cpp - class textFloat, a floating text-label + * + * Copyright (c) LMMS team + * + * 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 "SimpleTextFloat.h" + +#include +#include +#include +#include + +#include "GuiApplication.h" +#include "MainWindow.h" + +namespace lmms::gui +{ + + +SimpleTextFloat::SimpleTextFloat() : + QWidget(getGUI()->mainWindow(), Qt::ToolTip) +{ + QHBoxLayout * layout = new QHBoxLayout(this); + layout->setMargin(3); + setLayout(layout); + + m_textLabel = new QLabel(this); + layout->addWidget(m_textLabel); + + m_showTimer = new QTimer(this); + m_showTimer->setSingleShot(true); + QObject::connect(m_showTimer, &QTimer::timeout, this, &SimpleTextFloat::show); + + m_hideTimer = new QTimer(this); + m_hideTimer->setSingleShot(true); + QObject::connect(m_hideTimer, &QTimer::timeout, this, &SimpleTextFloat::hide); +} + +void SimpleTextFloat::setText(const QString & text) +{ + m_textLabel->setText(text); +} + +void SimpleTextFloat::showWithDelay(int msecBeforeDisplay, int msecDisplayTime) +{ + if (msecBeforeDisplay != 0) + { + m_showTimer->start(msecBeforeDisplay); + } + else + { + show(); + } + + if (msecDisplayTime != 0) + { + m_hideTimer->start(msecBeforeDisplay + msecDisplayTime); + } +} + +void SimpleTextFloat::hide() +{ + m_showTimer->stop(); + m_hideTimer->stop(); + QWidget::hide(); +} + +void SimpleTextFloat::setVisibilityTimeOut(int msecs) +{ + QTimer::singleShot(msecs, this, SLOT(hide())); + show(); +} + +} // namespace lmms::gui diff --git a/src/gui/widgets/TabBar.cpp b/src/gui/widgets/TabBar.cpp index d3c1fa0c5..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 @@ -37,14 +36,14 @@ TabBar::TabBar( QWidget * _parent, QBoxLayout::Direction _dir ) : m_layout( new QBoxLayout( _dir, this ) ), m_exclusive( false ) { - m_layout->setMargin( 8 ); + m_layout->setContentsMargins(8, 8, 8, 8); m_layout->setSpacing( 0 ); setLayout( m_layout ); } TabButton * TabBar::addTab( QWidget * _w, const QString & _text, int _id, - bool _add_stretch, bool _text_is_tooltip ) + bool _add_stretch, bool _text_is_tooltip, bool fixWidgetToParentSize ) { // already tab with id? if( m_tabs.contains( _id ) ) @@ -83,12 +82,12 @@ TabButton * TabBar::addTab( QWidget * _w, const QString & _text, int _id, m_layout->addStretch(); } - - // we assume, parent-widget is a widget acting as widget-stack so all - // widgets have the same size and only the one on the top is visible - _w->setFixedSize( _w->parentWidget()->size() ); - - b->setFont( pointSize<8>( b->font() ) ); + if (fixWidgetToParentSize) + { + // we assume, parent-widget is a widget acting as widget-stack so all + // widgets have the same size and only the one on the top is visible + _w->setFixedSize( _w->parentWidget()->size() ); + } return( b ); } diff --git a/src/gui/widgets/TabWidget.cpp b/src/gui/widgets/TabWidget.cpp index 106387e42..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::Background ). darker( 132 ); + setAutoFillBackground(true); + QColor bg_color = QApplication::palette().color(QPalette::Active, QPalette::Window).darker(132); QPalette pal = palette(); - pal.setColor( QPalette::Background, 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/TempoSyncKnob.cpp b/src/gui/widgets/TempoSyncKnob.cpp index 86ee6df3c..473cee28c 100644 --- a/src/gui/widgets/TempoSyncKnob.cpp +++ b/src/gui/widgets/TempoSyncKnob.cpp @@ -42,7 +42,7 @@ namespace lmms::gui -TempoSyncKnob::TempoSyncKnob( knobTypes _knob_num, QWidget * _parent, +TempoSyncKnob::TempoSyncKnob( KnobType _knob_num, QWidget * _parent, const QString & _name ) : Knob( _knob_num, _parent, _name ), m_tempoSyncIcon( embed::getIconPixmap( "tempo_sync" ) ), @@ -75,8 +75,7 @@ void TempoSyncKnob::modelChanged() { m_custom->setModel( &model()->m_custom ); } - connect( model(), SIGNAL(syncModeChanged(lmms::TempoSyncKnobModel::TempoSyncMode)), - this, SLOT(updateDescAndIcon())); + connect(model(), &TempoSyncKnobModel::syncModeChanged, this, &TempoSyncKnob::updateDescAndIcon); connect( this, SIGNAL(sliderMoved(float)), model(), SLOT(disableSync())); updateDescAndIcon(); @@ -104,51 +103,51 @@ void TempoSyncKnob::contextMenuEvent( QContextMenuEvent * ) connect( syncMenu, SIGNAL(triggered(QAction*)), model(), SLOT(setTempoSync(QAction*))); syncMenu->addAction( embed::getIconPixmap( "note_none" ), - tr( "No Sync" ) )->setData( (int) TempoSyncKnobModel::SyncNone ); + tr( "No Sync" ) )->setData( (int) TempoSyncKnobModel::SyncMode::None ); if( limit / 0.125f <= model()->maxValue() ) { syncMenu->addAction( embed::getIconPixmap( "note_double_whole" ), tr( "Eight beats" ) )->setData( - (int) TempoSyncKnobModel::SyncDoubleWholeNote ); + (int) TempoSyncKnobModel::SyncMode::DoubleWholeNote ); } if( limit / 0.25f <= model()->maxValue() ) { syncMenu->addAction( embed::getIconPixmap( "note_whole" ), tr( "Whole note" ) )->setData( - (int) TempoSyncKnobModel::SyncWholeNote ); + (int) TempoSyncKnobModel::SyncMode::WholeNote ); } if( limit / 0.5f <= model()->maxValue() ) { syncMenu->addAction( embed::getIconPixmap( "note_half" ), tr( "Half note" ) )->setData( - (int) TempoSyncKnobModel::SyncHalfNote ); + (int) TempoSyncKnobModel::SyncMode::HalfNote ); } if( limit <= model()->maxValue() ) { syncMenu->addAction( embed::getIconPixmap( "note_quarter" ), tr( "Quarter note" ) )->setData( - (int) TempoSyncKnobModel::SyncQuarterNote ); + (int) TempoSyncKnobModel::SyncMode::QuarterNote ); } if( limit / 2.0f <= model()->maxValue() ) { syncMenu->addAction( embed::getIconPixmap( "note_eighth" ), tr( "8th note" ) )->setData( - (int) TempoSyncKnobModel::SyncEighthNote ); + (int) TempoSyncKnobModel::SyncMode::EighthNote ); } if( limit / 4.0f <= model()->maxValue() ) { syncMenu->addAction( embed::getIconPixmap( "note_sixteenth" ), tr( "16th note" ) )->setData( - (int) TempoSyncKnobModel::SyncSixteenthNote ); + (int) TempoSyncKnobModel::SyncMode::SixteenthNote ); } syncMenu->addAction( embed::getIconPixmap( "note_thirtysecond" ), tr( "32nd note" ) )->setData( - (int) TempoSyncKnobModel::SyncThirtysecondNote ); + (int) TempoSyncKnobModel::SyncMode::ThirtysecondNote ); syncMenu->addAction( embed::getIconPixmap( "dont_know" ), tr( "Custom..." ), this, SLOT(showCustom()) )->setData( - (int) TempoSyncKnobModel::SyncCustom ); + (int) TempoSyncKnobModel::SyncMode::Custom ); contextMenu.addSeparator(); } @@ -162,11 +161,11 @@ void TempoSyncKnob::contextMenuEvent( QContextMenuEvent * ) void TempoSyncKnob::updateDescAndIcon() { - if( model()->m_tempoSyncMode ) + if( model()->m_tempoSyncMode != TempoSyncKnobModel::SyncMode::None ) { switch( model()->m_tempoSyncMode ) { - case TempoSyncKnobModel::SyncCustom: + case TempoSyncKnobModel::SyncMode::Custom: m_tempoSyncDescription = tr( "Custom " ) + "(" + QString::number( model()->m_custom.numeratorModel().value() ) + @@ -174,31 +173,31 @@ void TempoSyncKnob::updateDescAndIcon() QString::number( model()->m_custom.denominatorModel().value() ) + ")"; break; - case TempoSyncKnobModel::SyncDoubleWholeNote: + case TempoSyncKnobModel::SyncMode::DoubleWholeNote: m_tempoSyncDescription = tr( "Synced to Eight Beats" ); break; - case TempoSyncKnobModel::SyncWholeNote: + case TempoSyncKnobModel::SyncMode::WholeNote: m_tempoSyncDescription = tr( "Synced to Whole Note" ); break; - case TempoSyncKnobModel::SyncHalfNote: + case TempoSyncKnobModel::SyncMode::HalfNote: m_tempoSyncDescription = tr( "Synced to Half Note" ); break; - case TempoSyncKnobModel::SyncQuarterNote: + case TempoSyncKnobModel::SyncMode::QuarterNote: m_tempoSyncDescription = tr( "Synced to Quarter Note" ); break; - case TempoSyncKnobModel::SyncEighthNote: + case TempoSyncKnobModel::SyncMode::EighthNote: m_tempoSyncDescription = tr( "Synced to 8th Note" ); break; - case TempoSyncKnobModel::SyncSixteenthNote: + case TempoSyncKnobModel::SyncMode::SixteenthNote: m_tempoSyncDescription = tr( "Synced to 16th Note" ); break; - case TempoSyncKnobModel::SyncThirtysecondNote: + case TempoSyncKnobModel::SyncMode::ThirtysecondNote: m_tempoSyncDescription = tr( "Synced to 32nd Note" ); break; @@ -210,38 +209,38 @@ void TempoSyncKnob::updateDescAndIcon() m_tempoSyncDescription = tr( "Tempo Sync" ); } if( m_custom != nullptr && - model()->m_tempoSyncMode != TempoSyncKnobModel::SyncCustom ) + model()->m_tempoSyncMode != TempoSyncKnobModel::SyncMode::Custom ) { m_custom->parentWidget()->hide(); } switch( model()->m_tempoSyncMode ) { - case TempoSyncKnobModel::SyncNone: + case TempoSyncKnobModel::SyncMode::None: m_tempoSyncIcon = embed::getIconPixmap( "tempo_sync" ); break; - case TempoSyncKnobModel::SyncCustom: + case TempoSyncKnobModel::SyncMode::Custom: m_tempoSyncIcon = embed::getIconPixmap( "dont_know" ); break; - case TempoSyncKnobModel::SyncDoubleWholeNote: + case TempoSyncKnobModel::SyncMode::DoubleWholeNote: m_tempoSyncIcon = embed::getIconPixmap( "note_double_whole" ); break; - case TempoSyncKnobModel::SyncWholeNote: + case TempoSyncKnobModel::SyncMode::WholeNote: m_tempoSyncIcon = embed::getIconPixmap( "note_whole" ); break; - case TempoSyncKnobModel::SyncHalfNote: + case TempoSyncKnobModel::SyncMode::HalfNote: m_tempoSyncIcon = embed::getIconPixmap( "note_half" ); break; - case TempoSyncKnobModel::SyncQuarterNote: + case TempoSyncKnobModel::SyncMode::QuarterNote: m_tempoSyncIcon = embed::getIconPixmap( "note_quarter" ); break; - case TempoSyncKnobModel::SyncEighthNote: + case TempoSyncKnobModel::SyncMode::EighthNote: m_tempoSyncIcon = embed::getIconPixmap( "note_eighth" ); break; - case TempoSyncKnobModel::SyncSixteenthNote: + case TempoSyncKnobModel::SyncMode::SixteenthNote: m_tempoSyncIcon = embed::getIconPixmap( "note_sixteenth" ); break; - case TempoSyncKnobModel::SyncThirtysecondNote: + case TempoSyncKnobModel::SyncMode::ThirtysecondNote: m_tempoSyncIcon = embed::getIconPixmap( "note_thirtysecond" ); break; default: @@ -305,7 +304,7 @@ void TempoSyncKnob::showCustom() m_custom->setModel( &model()->m_custom ); } m_custom->parentWidget()->show(); - model()->setTempoSync( TempoSyncKnobModel::SyncCustom ); + model()->setTempoSync( TempoSyncKnobModel::SyncMode::Custom ); } diff --git a/src/gui/widgets/TextFloat.cpp b/src/gui/widgets/TextFloat.cpp index 99eb5531f..4eb14bd50 100644 --- a/src/gui/widgets/TextFloat.cpp +++ b/src/gui/widgets/TextFloat.cpp @@ -22,12 +22,15 @@ * */ +#include "TextFloat.h" + #include #include #include +#include +#include +#include -#include "TextFloat.h" -#include "gui_templates.h" #include "GuiApplication.h" #include "MainWindow.h" @@ -36,170 +39,97 @@ namespace lmms::gui TextFloat::TextFloat() : - QWidget( getGUI()->mainWindow(), Qt::ToolTip ), - m_title(), - m_text(), - m_pixmap() + TextFloat("", "", QPixmap()) { - resize( 20, 20 ); - hide(); - - setAttribute( Qt::WA_TranslucentBackground, true ); - setStyle( QApplication::style() ); - setFont( pointSize<8>( font() ) ); } - - - -void TextFloat::setTitle( const QString & _title ) +TextFloat::TextFloat(const QString & title, const QString & text, const QPixmap & pixmap) : + QWidget(getGUI()->mainWindow(), Qt::ToolTip) { - m_title = _title; - updateSize(); + QHBoxLayout * mainLayout = new QHBoxLayout(); + setLayout(mainLayout); + + // Create the label that displays the pixmap + m_pixmapLabel = new QLabel(this); + mainLayout->addWidget(m_pixmapLabel); + + // Create the widget that displays the title and the text + QWidget * titleAndTextWidget = new QWidget(this); + QVBoxLayout * titleAndTextLayout = new QVBoxLayout(); + titleAndTextWidget->setLayout(titleAndTextLayout); + + m_titleLabel = new QLabel(titleAndTextWidget); + m_titleLabel->setStyleSheet("font-weight: bold;"); + titleAndTextLayout->addWidget(m_titleLabel); + + m_textLabel = new QLabel(titleAndTextWidget); + titleAndTextLayout->addWidget(m_textLabel); + + mainLayout->addWidget(titleAndTextWidget); + + // Call the setters so that the hidden state is updated + setTitle(title); + setText(text); + setPixmap(pixmap); } - - - -void TextFloat::setText( const QString & _text ) +void TextFloat::setTitle(const QString & title) { - m_text = _text; - updateSize(); + m_titleLabel->setText(title); + m_titleLabel->setHidden(title.isEmpty()); } - - - -void TextFloat::setPixmap( const QPixmap & _pixmap ) +void TextFloat::setText(const QString & text) { - m_pixmap = _pixmap; - updateSize(); + m_textLabel->setText(text); + m_textLabel->setHidden(text.isEmpty()); } - - - -void TextFloat::setVisibilityTimeOut( int _msecs ) +void TextFloat::setPixmap(const QPixmap & pixmap) { - QTimer::singleShot( _msecs, this, SLOT(hide())); + m_pixmapLabel->setPixmap(pixmap); + m_pixmapLabel->setHidden(pixmap.isNull()); +} + +void TextFloat::setVisibilityTimeOut(int msecs) +{ + QTimer::singleShot(msecs, this, SLOT(hide())); show(); } - - - -TextFloat * TextFloat::displayMessage( const QString & _msg, int _timeout, - QWidget * _parent, int _add_y_margin ) +TextFloat * TextFloat::displayMessage(const QString & title, + const QString & msg, + const QPixmap & pixmap, + int timeout, QWidget * parent) { - QWidget * mw = getGUI()->mainWindow(); - auto tf = new TextFloat; - if( _parent != nullptr ) - { - tf->moveGlobal( _parent, QPoint( _parent->width() + 2, 0 ) ); - } - else - { - tf->moveGlobal( mw, QPoint( 32, mw->height() - tf->height() - 8 - _add_y_margin ) ); - } - tf->setText( _msg ); + auto tf = new TextFloat(title, msg, pixmap); + + // Show the widget so that the correct height is calculated in the code that follows tf->show(); - if( _timeout > 0 ) + + if(parent != nullptr) { - tf->setAttribute( Qt::WA_DeleteOnClose, true ); - QTimer::singleShot( _timeout, tf, SLOT(close())); - } - return( tf ); -} - - - - -TextFloat * TextFloat::displayMessage( const QString & _title, - const QString & _msg, - const QPixmap & _pixmap, - int _timeout, QWidget * _parent ) -{ - TextFloat * tf = displayMessage( _msg, _timeout, _parent, 16 ); - tf->setTitle( _title ); - tf->setPixmap( _pixmap ); - return( tf ); -} - - - - -void TextFloat::paintEvent( QPaintEvent * _pe ) -{ - QStyleOption opt; - opt.init( this ); - QPainter p( this ); - p.fillRect( 0, 0, width(), height(), QColor( 0, 0, 0, 0 ) ); - -/* p.setPen( p.pen().brush().color() ); - p.setBrush( p.background() );*/ - - p.setFont( pointSize<8>( p.font() ) ); - - style()->drawPrimitive( QStyle::PE_Widget, &opt, &p, this ); - -/* p.drawRect( 0, 0, rect().right(), rect().bottom() );*/ - - if( m_title.isEmpty() ) - { - p.drawText( opt.rect, Qt::AlignCenter, m_text ); + tf->moveGlobal(parent, QPoint(parent->width() + 2, 0)); } else { - int text_x = opt.rect.left() + 2; - int text_y = opt.rect.top() + 12; - if( m_pixmap.isNull() == false ) - { - p.drawPixmap( opt.rect.topLeft() + QPoint( 5, 5 ), m_pixmap ); - text_x += m_pixmap.width() + 8; - } - p.drawText( text_x, text_y + 16, m_text ); - QFont f = p.font(); - f.setBold( true ); - p.setFont( f ); - p.drawText( text_x, text_y, m_title ); + // If no parent is given move the window to the lower left area of the main window + QWidget * mw = getGUI()->mainWindow(); + tf->moveGlobal(mw, QPoint(32, mw->height() - tf->height() - 8)); } + + if (timeout > 0) + { + tf->setAttribute(Qt::WA_DeleteOnClose, true); + QTimer::singleShot(timeout, tf, SLOT(close())); + } + + return tf; } - - - -void TextFloat::mousePressEvent( QMouseEvent * ) +void TextFloat::mousePressEvent(QMouseEvent *) { close(); } - - - -void TextFloat::updateSize() -{ - QFontMetrics metrics( pointSize<8>( font() ) ); - QRect textBound = metrics.boundingRect( m_text ); - if( !m_title.isEmpty() ) - { - QFont f = pointSize<8>( font() ); - f.setBold( true ); - int title_w = QFontMetrics( f ).boundingRect( m_title ).width(); - if( title_w > textBound.width() ) - { - textBound.setWidth( title_w ); - } - textBound.setHeight( textBound.height() * 2 + 8 ); - } - if( m_pixmap.isNull() == false ) - { - textBound.setWidth( textBound.width() + m_pixmap.width() + 10 ); - } - resize( textBound.width() + 5, textBound.height()+2 ); - //move( QPoint( parentWidget()->width() + 5, 5 ) ); - update(); -} - - - } // namespace lmms::gui diff --git a/src/gui/widgets/TimeDisplayWidget.cpp b/src/gui/widgets/TimeDisplayWidget.cpp index cefa0fa8f..92eaf1efe 100644 --- a/src/gui/widgets/TimeDisplayWidget.cpp +++ b/src/gui/widgets/TimeDisplayWidget.cpp @@ -36,14 +36,14 @@ namespace lmms::gui TimeDisplayWidget::TimeDisplayWidget() : QWidget(), - m_displayMode( MinutesSeconds ), + m_displayMode( DisplayMode::MinutesSeconds ), m_spinBoxesLayout( this ), m_majorLCD( 4, this ), m_minorLCD( 2, this ), m_milliSecondsLCD( 3, this ) { m_spinBoxesLayout.setSpacing( 0 ); - m_spinBoxesLayout.setMargin( 0 ); + m_spinBoxesLayout.setContentsMargins(0, 0, 0, 0); m_spinBoxesLayout.addWidget( &m_majorLCD ); m_spinBoxesLayout.addWidget( &m_minorLCD ); m_spinBoxesLayout.addWidget( &m_milliSecondsLCD ); @@ -65,13 +65,13 @@ void TimeDisplayWidget::setDisplayMode( DisplayMode displayMode ) switch( m_displayMode ) { - case MinutesSeconds: + case DisplayMode::MinutesSeconds: m_majorLCD.setLabel( tr( "MIN" ) ); m_minorLCD.setLabel( tr( "SEC" ) ); m_milliSecondsLCD.setLabel( tr( "MSEC" ) ); break; - case BarsTicks: + case DisplayMode::BarsTicks: m_majorLCD.setLabel( tr( "BAR" ) ); m_minorLCD.setLabel( tr( "BEAT" ) ); m_milliSecondsLCD.setLabel( tr( "TICK" ) ); @@ -90,25 +90,26 @@ void TimeDisplayWidget::updateTime() switch( m_displayMode ) { - case MinutesSeconds: - int msec; - msec = s->getMilliseconds(); + case DisplayMode::MinutesSeconds: + { + int msec = s->getMilliseconds(); m_majorLCD.setValue(msec / 60000); m_minorLCD.setValue((msec / 1000) % 60); m_milliSecondsLCD.setValue(msec % 1000); break; - - case BarsTicks: - int tick; - tick = s->getPlayPos().getTicks(); + } + case DisplayMode::BarsTicks: + { + 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; } } @@ -119,13 +120,13 @@ void TimeDisplayWidget::mousePressEvent( QMouseEvent* mouseEvent ) { if( mouseEvent->button() == Qt::LeftButton ) { - if( m_displayMode == MinutesSeconds ) + if( m_displayMode == DisplayMode::MinutesSeconds ) { - setDisplayMode( BarsTicks ); + setDisplayMode( DisplayMode::BarsTicks ); } else { - setDisplayMode( MinutesSeconds ); + setDisplayMode( DisplayMode::MinutesSeconds ); } } } 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/AutomationTrack.cpp b/src/tracks/AutomationTrack.cpp index deb6f763c..e353197f8 100644 --- a/src/tracks/AutomationTrack.cpp +++ b/src/tracks/AutomationTrack.cpp @@ -34,7 +34,7 @@ namespace lmms AutomationTrack::AutomationTrack( TrackContainer* tc, bool _hidden ) : - Track( _hidden ? HiddenAutomationTrack : Track::AutomationTrack, tc ) + Track( _hidden ? Type::HiddenAutomation : Type::Automation, tc ) { setName( tr( "Automation track" ) ); } @@ -77,7 +77,7 @@ void AutomationTrack::saveTrackSpecificSettings( QDomDocument & _doc, void AutomationTrack::loadTrackSpecificSettings( const QDomElement & _this ) { // just in case something somehow wrent wrong... - if( type() == HiddenAutomationTrack ) + if( type() == Type::HiddenAutomation ) { setMuted( false ); } diff --git a/src/tracks/InstrumentTrack.cpp b/src/tracks/InstrumentTrack.cpp index 862f953da..4cba33d90 100644 --- a/src/tracks/InstrumentTrack.cpp +++ b/src/tracks/InstrumentTrack.cpp @@ -28,6 +28,7 @@ #include "ConfigManager.h" #include "ControllerConnection.h" #include "DataFile.h" +#include "GuiApplication.h" #include "Mixer.h" #include "InstrumentTrackView.h" #include "Instrument.h" @@ -37,6 +38,7 @@ #include "MixHelpers.h" #include "PatternStore.h" #include "PatternTrack.h" +#include "PianoRoll.h" #include "Pitch.h" #include "Song.h" @@ -45,7 +47,7 @@ namespace lmms InstrumentTrack::InstrumentTrack( TrackContainer* tc ) : - Track( Track::InstrumentTrack, tc ), + Track( Track::Type::Instrument, tc ), MidiEventProcessor(), m_midiPort( tr( "unnamed_track" ), Engine::audioEngine()->midiClient(), this, this ), @@ -75,6 +77,7 @@ InstrumentTrack::InstrumentTrack( TrackContainer* tc ) : m_microtuner() { m_pitchModel.setCenterValue( 0 ); + m_pitchModel.setStrictStepSize(true); m_panningModel.setCenterValue( DefaultPanning ); m_baseNoteModel.setInitValue( DefaultKey ); m_firstKeyModel.setInitValue(0); @@ -228,7 +231,7 @@ InstrumentTrack::~InstrumentTrack() void InstrumentTrack::processAudioBuffer( sampleFrame* buf, const fpp_t frames, NotePlayHandle* n ) { // we must not play the sound if this InstrumentTrack is muted... - if( isMuted() || ( Engine::getSong()->playMode() != Song::Mode_PlayMidiClip && + if( isMuted() || ( Engine::getSong()->playMode() != Song::PlayMode::MidiClip && n && n->isPatternTrackMuted() ) || ! m_instrument ) { return; @@ -238,7 +241,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::IsSingleStreamed ) && + if( m_instrument->flags().testFlag( Instrument::Flag::IsSingleStreamed ) && MixHelpers::isSilent( buf, frames ) ) { // at least pass one silent buffer to allow @@ -268,12 +271,12 @@ 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::IsSingleStreamed ) == false && n != nullptr ) + if( m_instrument->flags().testFlag( Instrument::Flag::IsSingleStreamed ) == false && n != nullptr ) { const f_cnt_t offset = n->noteOffset(); m_soundShaping.processAudioBuffer( buf + offset, frames - offset, n ); const float vol = ( (float) n->getVolume() * DefaultVolumeRatio ); - const panning_t pan = qBound( PanningLeft, n->getPanning(), PanningRight ); + const panning_t pan = std::clamp(n->getPanning(), PanningLeft, PanningRight); StereoVolumeVector vv = panningToVolumeVector( pan, vol ); for( f_cnt_t f = offset; f < frames; ++f ) { @@ -349,9 +352,10 @@ void InstrumentTrack::processInEvent( const MidiEvent& event, const TimePos& tim NotePlayHandleManager::acquire( this, offset, typeInfo::max() / 2, - Note( TimePos(), TimePos(), event.key(), event.volume( midiPort()->baseVelocity() ) ), + Note(TimePos(), Engine::getSong()->getPlayPos(Engine::getSong()->playMode()), + event.key(), event.volume(midiPort()->baseVelocity())), nullptr, event.channel(), - NotePlayHandle::OriginMidiInput ); + NotePlayHandle::Origin::MidiInput); m_notes[event.key()] = nph; if( ! Engine::audioEngine()->addPlayHandle( nph ) ) { @@ -371,7 +375,7 @@ void InstrumentTrack::processInEvent( const MidiEvent& event, const TimePos& tim m_notes[event.key()]->noteOff( offset ); if (isSustainPedalPressed() && m_notes[event.key()]->origin() == - m_notes[event.key()]->OriginMidiInput) + NotePlayHandle::Origin::MidiInput) { m_sustainedNotes << m_notes[event.key()]; } @@ -411,7 +415,7 @@ void InstrumentTrack::processInEvent( const MidiEvent& event, const TimePos& tim if (nph && nph->isReleased()) { if( nph->origin() == - nph->OriginMidiInput) + NotePlayHandle::Origin::MidiInput) { nph->setLength( TimePos( static_cast( @@ -544,10 +548,10 @@ void InstrumentTrack::silenceAllNotes( bool removeIPH ) // invalidate all NotePlayHandles and PresetPreviewHandles linked to this track m_processHandles.clear(); - quint8 flags = PlayHandle::TypeNotePlayHandle | PlayHandle::TypePresetPreviewHandle; + auto flags = PlayHandle::Type::NotePlayHandle | PlayHandle::Type::PresetPreviewHandle; if( removeIPH ) { - flags |= PlayHandle::TypeInstrumentPlayHandle; + flags |= PlayHandle::Type::InstrumentPlayHandle; } Engine::audioEngine()->removePlayHandlesOfTypes( this, flags ); Engine::audioEngine()->doneChangeInModel(); @@ -574,6 +578,10 @@ f_cnt_t InstrumentTrack::beatLen( NotePlayHandle * _n ) const void InstrumentTrack::playNote( NotePlayHandle* n, sampleFrame* workingBuffer ) { + // Note: under certain circumstances the working buffer is a nullptr. + // These cases are triggered in PlayHandle::doProcessing when the play method is called with a nullptr. + // TODO: Find out if we can skip processing at a higher level if the buffer is nullptr. + // arpeggio- and chord-widget has to do its work -> adding sub-notes // for chords/arpeggios m_noteStacking.processNote( n ); @@ -583,6 +591,15 @@ void InstrumentTrack::playNote( NotePlayHandle* n, sampleFrame* workingBuffer ) { // all is done, so now lets play the note! m_instrument->playNote( n, workingBuffer ); + + // This is effectively the same as checking if workingBuffer is not a nullptr. + // Calling processAudioBuffer with a nullptr leads to crashes. Hence the check. + if (n->usesBuffer()) + { + const fpp_t frames = n->framesLeftForCurrentPeriod(); + const f_cnt_t offset = n->noteOffset(); + processAudioBuffer(workingBuffer, frames + offset, n); + } } } @@ -617,8 +634,6 @@ void InstrumentTrack::setName( const QString & _new_name ) Track::setName( _new_name ); m_midiPort.setName( name() ); m_audioPort.setName( name() ); - - emit nameChanged(); } @@ -676,7 +691,7 @@ int InstrumentTrack::masterKey( int _midi_key ) const { int key = baseNote(); - return qBound( 0, _midi_key - ( key - DefaultKey ), NumKeys ); + return std::clamp(_midi_key - (key - DefaultKey), 0, NumKeys); } @@ -720,7 +735,8 @@ bool InstrumentTrack::play( const TimePos & _start, const fpp_t _frames, // Handle automation: detuning for (const auto& processHandle : m_processHandles) { - processHandle->processTimePos(_start); + processHandle->processTimePos( + _start, m_pitchModel.value(), gui::getGUI() && gui::getGUI()->pianoRoll()->isRecording()); } if ( clips.size() == 0 ) @@ -740,7 +756,7 @@ bool InstrumentTrack::play( const TimePos & _start, const fpp_t _frames, auto c = dynamic_cast(clip); // everything which is not a MIDI clip won't be played // A MIDI clip playing in the Piano Roll window will always play - if (c == nullptr || (Engine::getSong()->playMode() != Song::Mode_PlayMidiClip && clip->isMuted())) + if (c == nullptr || (Engine::getSong()->playMode() != Song::PlayMode::MidiClip && clip->isMuted())) { continue; } @@ -753,13 +769,12 @@ bool InstrumentTrack::play( const TimePos & _start, const fpp_t _frames, // get all notes from the given clip... const NoteVector & notes = c->notes(); // ...and set our index to zero - NoteVector::ConstIterator nit = notes.begin(); + auto nit = notes.begin(); Groove * groove = this->groove(); if (!groove) { groove = globalGroove; } - int groove_offset = 0; // very effective algorithm for playing notes that are // posated within the current sample-frame @@ -778,20 +793,22 @@ bool InstrumentTrack::play( const TimePos & _start, const fpp_t _frames, } } */ - Note * cur_note; - while( nit != notes.end() ) + while (nit != notes.end()) { - cur_note = *nit; - groove_offset = groove->isInTick(&cur_start, _frames, _offset, cur_note, c); - if (groove_offset >= 0) + const auto currentNote = *nit; + const auto grooveOffset = groove->isInTick(&cur_start, _frames, _offset, currentNote, c); + if (grooveOffset >= 0) { - const f_cnt_t note_frames = - cur_note->length().frames( frames_per_tick ); + // 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, groove_offset, note_frames, *cur_note ); + NotePlayHandle* notePlayHandle = NotePlayHandleManager::acquire(this, grooveOffset, noteFrames, *currentNote); notePlayHandle->setPatternTrack(pattern_track); // are we playing global song? - if( _clip_num < 0 ) + if ( _clip_num < 0 ) { // then set song-global offset of pattern in order to // properly perform the note detuning @@ -896,9 +913,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 e5bff8b6f..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,17 +39,10 @@ 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 ), - m_clipType( BeatClip ), + m_clipType( Type::BeatClip ), m_steps( TimePos::stepsPerBar() ) { if (_instrument_track->trackContainer() == Engine::patternStore()) @@ -76,11 +70,11 @@ MidiClip::MidiClip( const MidiClip& other ) : init(); switch( getTrack()->trackContainer()->type() ) { - case TrackContainer::PatternContainer: + case TrackContainer::Type::Pattern: setAutoResize( true ); break; - case TrackContainer::SongContainer: + case TrackContainer::Type::Song: // move down default: setAutoResize( false ); @@ -111,15 +105,15 @@ void MidiClip::resizeToFirstTrack() m_instrumentTrack->trackContainer()->tracks(); for (const auto& track : tracks) { - if (track->type() == Track::InstrumentTrack) + if (track->type() == Track::Type::Instrument) { if (track != m_instrumentTrack) { - unsigned int currentClip = m_instrumentTrack-> - getClips().indexOf(this); - m_steps = static_cast - (track->getClip(currentClip)) - ->m_steps; + const auto& instrumentTrackClips = m_instrumentTrack->getClips(); + const auto currentClipIt = std::find(instrumentTrackClips.begin(), instrumentTrackClips.end(), this); + unsigned int currentClip = currentClipIt != instrumentTrackClips.end() ? + std::distance(instrumentTrackClips.begin(), currentClipIt) : -1; + m_steps = static_cast(track->getClip(currentClip))->m_steps; } break; } @@ -144,7 +138,7 @@ void MidiClip::init() void MidiClip::updateLength() { - if( m_clipType == BeatClip ) + if( m_clipType == Type::BeatClip ) { changeLength( beatClipLength() ); updatePatternTrack(); @@ -157,7 +151,7 @@ void MidiClip::updateLength() { if (note->length() > 0) { - max_length = qMax(max_length, note->endPos()); + max_length = std::max(max_length, note->endPos()); } } changeLength( TimePos( max_length ).nextFullBar() * @@ -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 = qMax(max_length, note->pos() + 1); + 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,36 +208,48 @@ 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(); - NoteVector::Iterator it = m_notes.begin(); - while( it != m_notes.end() ) - { - if( *it == _note_to_del ) - { - delete *it; - m_notes.erase( it ); - break; - } - ++it; - } + 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(); + + auto it = std::find(m_notes.begin(), m_notes.end(), note); + if (it != m_notes.end()) + { + delete *it; + it = m_notes.erase(it); + } + + instrumentTrack()->unlock(); + + checkType(); + 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; } @@ -281,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); } @@ -308,7 +315,7 @@ void MidiClip::setStep( int step, bool enabled ) -void MidiClip::splitNotes(NoteVector notes, TimePos pos) +void MidiClip::splitNotes(const NoteVector& notes, TimePos pos) { if (notes.empty()) { return; } @@ -340,10 +347,10 @@ void MidiClip::splitNotes(NoteVector notes, TimePos pos) -void MidiClip::setType( MidiClipTypes _new_clip_type ) +void MidiClip::setType( Type _new_clip_type ) { - if( _new_clip_type == BeatClip || - _new_clip_type == MelodyClip ) + if( _new_clip_type == Type::BeatClip || + _new_clip_type == Type::MelodyClip ) { m_clipType = _new_clip_type; } @@ -354,17 +361,10 @@ void MidiClip::setType( MidiClipTypes _new_clip_type ) void MidiClip::checkType() { - NoteVector::Iterator it = m_notes.begin(); - while( it != m_notes.end() ) - { - if( ( *it )->length() > 0 ) - { - setType( MelodyClip ); - return; - } - ++it; - } - setType( 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); } @@ -372,12 +372,12 @@ void MidiClip::checkType() void MidiClip::saveSettings( QDomDocument & _doc, QDomElement & _this ) { - _this.setAttribute( "type", m_clipType ); + _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 @@ -395,9 +395,9 @@ void MidiClip::saveSettings( QDomDocument & _doc, QDomElement & _this ) _this.setAttribute( "steps", m_steps ); // now save settings of all notes - for (const auto& note : m_notes) + for (auto& note : m_notes) { - note->saveState( _doc, _this ); + note->saveState(_doc, _this); } } @@ -406,25 +406,20 @@ void MidiClip::saveSettings( QDomDocument & _doc, QDomElement & _this ) void MidiClip::loadSettings( const QDomElement & _this ) { - m_clipType = static_cast( _this.attribute( "type" + 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() ); } - if( _this.attribute( "muted" ).toInt() != isMuted() ) + if (static_cast(_this.attribute("muted").toInt()) != isMuted()) { toggleMute(); } @@ -477,9 +472,10 @@ MidiClip * MidiClip::nextMidiClip() const MidiClip * MidiClip::adjacentMidiClipByOffset(int offset) const { - QVector clips = m_instrumentTrack->getClips(); + auto& clips = m_instrumentTrack->getClips(); int clipNum = m_instrumentTrack->getClipNum(this); - return dynamic_cast(clips.value(clipNum + offset, nullptr)); + if (clipNum < 0 || clipNum > clips.size() - 1) { return nullptr; } + return dynamic_cast(clips[clipNum + offset]); } @@ -595,8 +591,8 @@ void MidiClip::changeTimeSignature() } } last_pos = last_pos.nextFullBar() * TimePos::ticksPerBar(); - m_steps = qMax( TimePos::stepsPerBar(), - last_pos.getBar() * TimePos::stepsPerBar() ); + m_steps = std::max(TimePos::stepsPerBar(), + last_pos.getBar() * TimePos::stepsPerBar()); updateLength(); } diff --git a/src/tracks/PatternTrack.cpp b/src/tracks/PatternTrack.cpp index 9fcc2c831..bdde4780c 100644 --- a/src/tracks/PatternTrack.cpp +++ b/src/tracks/PatternTrack.cpp @@ -41,7 +41,7 @@ PatternTrack::infoMap PatternTrack::s_infoMap; PatternTrack::PatternTrack(TrackContainer* tc) : - Track(Track::PatternTrack, tc) + Track(Track::Type::Pattern, tc) { int patternNum = s_infoMap.size(); s_infoMap[this] = patternNum; @@ -61,9 +61,9 @@ PatternTrack::PatternTrack(TrackContainer* tc) : PatternTrack::~PatternTrack() { Engine::audioEngine()->removePlayHandlesOfTypes( this, - PlayHandle::TypeNotePlayHandle - | PlayHandle::TypeInstrumentPlayHandle - | PlayHandle::TypeSamplePlayHandle ); + PlayHandle::Type::NotePlayHandle + | PlayHandle::Type::InstrumentPlayHandle + | PlayHandle::Type::SamplePlayHandle ); const int pattern = s_infoMap[this]; Engine::patternStore()->removePattern(pattern); @@ -108,19 +108,27 @@ bool PatternTrack::play( const TimePos & _start, const fpp_t _frames, } TimePos lastPosition; - TimePos lastLen; + TimePos lastLength; + tick_t lastOffset = 0; for (const auto& clip : clips) { if (!clip->isMuted() && clip->startPosition() >= lastPosition) { lastPosition = clip->startPosition(); - lastLen = clip->length(); + lastLength = clip->length(); + tick_t patternLength = Engine::patternStore()->lengthOfPattern(static_cast(clip)->patternIndex()) + * TimePos::ticksPerBar(); + lastOffset = patternLength - (clip->startTimeOffset() % patternLength); + if (lastOffset == patternLength) + { + lastOffset = 0; + } } } - if( _start - lastPosition < lastLen ) + if( _start - lastPosition < lastLength ) { - return Engine::patternStore()->play(_start - lastPosition, _frames, _offset, s_infoMap[this]); + return Engine::patternStore()->play(_start - lastPosition + lastOffset, _frames, _offset, s_infoMap[this]); } return false; } @@ -240,4 +248,4 @@ void PatternTrack::swapPatternTracks(Track* track1, Track* track2) } -} // namespace lmms \ No newline at end of file +} // namespace lmms diff --git a/src/tracks/SampleTrack.cpp b/src/tracks/SampleTrack.cpp index af5cbef6d..130502856 100644 --- a/src/tracks/SampleTrack.cpp +++ b/src/tracks/SampleTrack.cpp @@ -45,7 +45,7 @@ namespace lmms SampleTrack::SampleTrack(TrackContainer* tc) : - Track(Track::SampleTrack, tc), + Track(Track::Type::Sample, tc), m_volumeModel(DefaultVolume, MinVolume, MaxVolume, 0.1f, this, tr("Volume")), m_panningModel(DefaultPanning, PanningLeft, PanningRight, 0.1f, this, tr("Panning")), m_mixerChannelModel(0, 0, 0, this, tr("Mixer channel")), @@ -64,7 +64,7 @@ SampleTrack::SampleTrack(TrackContainer* tc) : SampleTrack::~SampleTrack() { - Engine::audioEngine()->removePlayHandlesOfTypes( this, PlayHandle::TypeSamplePlayHandle ); + Engine::audioEngine()->removePlayHandlesOfTypes( this, PlayHandle::Type::SamplePlayHandle ); } @@ -108,10 +108,10 @@ bool SampleTrack::play( const TimePos & _start, const fpp_t _frames, { if( sClip->isPlaying() == false && _start >= (sClip->startPosition() + sClip->startTimeOffset()) ) { - 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; @@ -229,7 +229,7 @@ void SampleTrack::loadTrackSpecificSettings( const QDomElement & _this ) void SampleTrack::updateClips() { - Engine::audioEngine()->removePlayHandlesOfTypes( this, PlayHandle::TypeSamplePlayHandle ); + Engine::audioEngine()->removePlayHandlesOfTypes( this, PlayHandle::Type::SamplePlayHandle ); setPlayingClips( false ); } diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 6ff9c41e9..9a609922c 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1,32 +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 6d375e6c6..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()) - { - failed += QTest::qExec(suite, argc, argv); - } - 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 new file mode 100644 index 000000000..1eba265e9 --- /dev/null +++ b/tests/src/core/ArrayVectorTest.cpp @@ -0,0 +1,831 @@ +/* + * ArrayVectorTest.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 "ArrayVector.h" + +#include +#include +#include +#include + +using lmms::ArrayVector; + +struct ShouldNotConstruct +{ + ShouldNotConstruct() { QFAIL("should not construct"); } +}; + +struct ShouldNotDestruct +{ + ~ShouldNotDestruct() { QFAIL("should not destruct"); } +}; + +enum class Construction { Default, Copy, Move, CopyAssign, MoveAssign }; + +struct Constructible +{ + Constructible() : construction{Construction::Default} {} + Constructible(const Constructible&) : construction{Construction::Copy} {} + Constructible(Constructible&&) : construction{Construction::Move} {} + Constructible& operator=(const Constructible&) { construction = Construction::CopyAssign; return *this; } + Constructible& operator=(Constructible&&) { construction = Construction::MoveAssign; return *this; } + Construction construction; +}; + +struct DestructorCheck +{ + ~DestructorCheck() { *destructed = true; } + bool* destructed; +}; + +class ArrayVectorTest : public QObject +{ + Q_OBJECT +private slots: + void defaultConstructorTest() + { + // Ensure no elements are constructed + const auto v = ArrayVector(); + // Ensure the container is empty + QVERIFY(v.empty()); + } + + void copyConstructorTest() + { + { + // Ensure all elements are copy constructed + const auto v = ArrayVector{{}}; + const auto copy = v; + for (const auto& element : copy) { + QCOMPARE(element.construction, Construction::Copy); + } + } + { + // Ensure corresponding elements are used + const auto v = ArrayVector{1, 2, 3}; + const auto copy = v; + const auto expected = std::array{1, 2, 3}; + QVERIFY(std::equal(copy.begin(), copy.end(), expected.begin(), expected.end())); + } + } + + void moveConstructorTest() + { + { + // Ensure all elements are move constructed + auto v = ArrayVector{{}}; + const auto moved = std::move(v); + for (const auto& element : moved) { + QCOMPARE(element.construction, Construction::Move); + } + } + { + // Ensure corresponding elements are used + auto v = ArrayVector{1, 2, 3}; + const auto moved = std::move(v); + const auto expected = std::array{1, 2, 3}; + QVERIFY(std::equal(moved.begin(), moved.end(), expected.begin(), expected.end())); + // Move construction should leave the source empty + QVERIFY(v.empty()); + } + } + + void fillValueConstructorTest() + { + // Ensure all elements are copy constructed + const auto v = ArrayVector(1, {}); + for (const auto& element : v) { + QCOMPARE(element.construction, Construction::Copy); + } + // Ensure the container has the correct size + QCOMPARE(v.size(), std::size_t{1}); + } + + void fillDefaultConstructorTest() + { + // Ensure all elements are copy constructed + const auto v = ArrayVector(1); + for (const auto& element : v) { + QCOMPARE(element.construction, Construction::Default); + } + // Ensure the container has the correct size + QCOMPARE(v.size(), std::size_t{1}); + } + + void rangeConstructorTest() + { + { + // Ensure the elements are copy constructed from normal iterators + const auto data = std::array{Constructible{}}; + const auto v = ArrayVector(data.begin(), data.end()); + for (const auto& element : v) { + QCOMPARE(element.construction, Construction::Copy); + } + } + { + // Ensure the elements are move constructed from move iterators + auto data = std::array{Constructible{}}; + const auto v = ArrayVector( + std::move_iterator{data.begin()}, std::move_iterator{data.end()}); + for (const auto& element : v) { + QCOMPARE(element.construction, Construction::Move); + } + } + { + // Ensure corresponding elements are used + const auto data = std::array{1, 2, 3}; + const auto v = ArrayVector(data.begin(), data.end()); + QVERIFY(std::equal(v.begin(), v.end(), data.begin(), data.end())); + } + } + + void initializerListConstructorTest() + { + // Ensure the container is constructed with the correct data + const auto v = ArrayVector{1, 2, 3}; + const auto expected = std::array{1, 2, 3}; + QVERIFY(std::equal(v.begin(), v.end(), expected.begin(), expected.end())); + } + + void destructorTest() + { + { + // Should not call destructors for space without elements + const auto v = ArrayVector{}; + } + { + // Should call destructors for all elements + auto destructed = false; + { + const auto v = ArrayVector{{&destructed}}; + } + QVERIFY(destructed); + } + } + + void copyAssignmentTest() + { + { + // Self-assignment should not change the contents + auto v = ArrayVector{1, 2, 3}; + const auto oldValue = v; + v = v; + QCOMPARE(v, oldValue); + } + { + // Assignment to a larger container should copy assign + const auto src = ArrayVector(3); + auto dst = ArrayVector(5); + dst = src; + QCOMPARE(dst.size(), std::size_t{3}); + for (const auto& element : dst) { + QCOMPARE(element.construction, Construction::CopyAssign); + } + } + { + // Assignment to a smaller container should copy construct + const auto src = ArrayVector(3); + auto dst = ArrayVector{}; + dst = src; + QCOMPARE(dst.size(), std::size_t{3}); + for (const auto& element : dst) { + QCOMPARE(element.construction, Construction::Copy); + } + } + { + // Ensure corresponding elements are used + const auto src = ArrayVector{1, 2, 3}; + auto dst = ArrayVector{}; + dst = src; + QCOMPARE(dst, (ArrayVector{1, 2, 3})); + } + } + + void moveAssignmentTest() + { + { + // Self-assignment should not change the contents + auto v = ArrayVector{1, 2, 3}; + const auto oldValue = v; + v = std::move(v); + QCOMPARE(v, oldValue); + } + { + // Assignment to a larger container should move assign + auto src = ArrayVector(3); + auto dst = ArrayVector(5); + dst = std::move(src); + QCOMPARE(dst.size(), std::size_t{3}); + for (const auto& element : dst) { + QCOMPARE(element.construction, Construction::MoveAssign); + } + } + { + // Assignment to a smaller container should move construct + auto src = ArrayVector(3); + auto dst = ArrayVector{}; + dst = std::move(src); + QCOMPARE(dst.size(), std::size_t{3}); + for (const auto& element : dst) { + QCOMPARE(element.construction, Construction::Move); + } + } + { + // Ensure corresponding elements are used + auto src = ArrayVector{1, 2, 3}; + auto dst = ArrayVector{}; + dst = std::move(src); + QCOMPARE(dst, (ArrayVector{1, 2, 3})); + } + } + + void initializerListAssignmentTest() + { + { + // Assignment to a larger container should copy assign + auto v = ArrayVector(2); + v = {Constructible{}}; + QCOMPARE(v.size(), std::size_t{1}); + for (const auto& element : v) { + QCOMPARE(element.construction, Construction::CopyAssign); + } + } + { + // Assignment to a smaller container should copy construct + auto v = ArrayVector{}; + v = {Constructible{}}; + QCOMPARE(v.size(), std::size_t{1}); + for (const auto& element : v) { + QCOMPARE(element.construction, Construction::Copy); + } + } + { + // Ensure corresponding elements are used + auto v = ArrayVector{}; + v = {1, 2, 3}; + QCOMPARE(v, (ArrayVector{1, 2, 3})); + } + } + + void fillValueAssignTest() + { + { + // Assignment to a larger container should copy assign + auto v = ArrayVector(5); + v.assign(3, {}); + QCOMPARE(v.size(), std::size_t{3}); + for (const auto& element : v) { + QCOMPARE(element.construction, Construction::CopyAssign); + } + } + { + // Assignment to a smaller container should copy construct + auto v = ArrayVector{}; + v.assign(3, {}); + QCOMPARE(v.size(), std::size_t{3}); + for (const auto& element : v) { + QCOMPARE(element.construction, Construction::Copy); + } + } + { + // Ensure correct value is filled + auto v = ArrayVector{}; + v.assign(3, 1); + QCOMPARE(v, (ArrayVector{1, 1, 1})); + } + } + + void rangeAssignTest() + { + { + // Assignment to a larger container should copy assign + const auto data = std::array{Constructible{}}; + auto v = ArrayVector(2); + v.assign(data.begin(), data.end()); + QCOMPARE(v.size(), std::size_t{1}); + for (const auto& element : v) { + QCOMPARE(element.construction, Construction::CopyAssign); + } + } + { + // Assignment to a smaller container should copy construct + const auto data = std::array{Constructible{}}; + auto v = ArrayVector{}; + v.assign(data.begin(), data.end()); + QCOMPARE(v.size(), std::size_t{1}); + for (const auto& element : v) { + QCOMPARE(element.construction, Construction::Copy); + } + } + { + // Ensure correct value is filled + const auto data = std::array{1, 2, 3}; + auto v = ArrayVector{}; + v.assign(data.begin(), data.end()); + QCOMPARE(v, (ArrayVector{1, 2, 3})); + } + } + + void atTest() + { + { + // Non-const version + auto v = ArrayVector{1, 2, 3}; + QCOMPARE(v.at(1), 2); + QVERIFY_EXCEPTION_THROWN(v.at(3), std::out_of_range); + } + { + // Const version + const auto v = ArrayVector{1, 2, 3}; + QCOMPARE(v.at(1), 2); + QVERIFY_EXCEPTION_THROWN(v.at(3), std::out_of_range); + } + } + + void subscriptTest() + { + { + // Non-const version + auto v = ArrayVector{1, 2, 3}; + QCOMPARE(v[1], 2); + } + { + // Const version + const auto v = ArrayVector{1, 2, 3}; + QCOMPARE(v[1], 2); + } + } + + void frontTest() + { + { + // Non-const version + auto v = ArrayVector{1, 2, 3}; + QCOMPARE(v.front(), 1); + } + { + // Const version + const auto v = ArrayVector{1, 2, 3}; + QCOMPARE(v.front(), 1); + } + } + + void backTest() + { + { + // Non-const version + auto v = ArrayVector{1, 2, 3}; + QCOMPARE(v.back(), 3); + } + { + // Const version + const auto v = ArrayVector{1, 2, 3}; + QCOMPARE(v.back(), 3); + } + } + + void dataTest() + { + { + // Non-const version + auto v = ArrayVector{1, 2, 3}; + QCOMPARE(v.data(), &v.front()); + } + { + // Const version + const auto v = ArrayVector{1, 2, 3}; + QCOMPARE(v.data(), &v.front()); + } + } + + void beginEndTest() + { + const auto expected = std::array{1, 2, 3}; + { + // Non-const version + auto v = ArrayVector{1, 2, 3}; + QVERIFY(std::equal(v.begin(), v.end(), expected.begin(), expected.end())); + QVERIFY(std::equal(v.cbegin(), v.cend(), expected.begin(), expected.end())); + } + { + // Const version + const auto v = ArrayVector{1, 2, 3}; + QVERIFY(std::equal(v.begin(), v.end(), expected.begin(), expected.end())); + } + } + + void rbeginRendTest() + { + const auto expected = std::array{3, 2, 1}; + { + // Non-const version + auto v = ArrayVector{1, 2, 3}; + QVERIFY(std::equal(v.rbegin(), v.rend(), expected.begin(), expected.end())); + QVERIFY(std::equal(v.crbegin(), v.crend(), expected.begin(), expected.end())); + } + { + // Const version + const auto v = ArrayVector{1, 2, 3}; + QVERIFY(std::equal(v.rbegin(), v.rend(), expected.begin(), expected.end())); + } + } + + void emptyFullSizeMaxCapacityTest() + { + auto v = ArrayVector{}; + QVERIFY(v.empty()); + QVERIFY(!v.full()); + QCOMPARE(v.size(), std::size_t{0}); + QCOMPARE(v.max_size(), std::size_t{2}); + QCOMPARE(v.capacity(), std::size_t{2}); + + v.push_back(1); + QVERIFY(!v.empty()); + QVERIFY(!v.full()); + QCOMPARE(v.size(), std::size_t{1}); + QCOMPARE(v.max_size(), std::size_t{2}); + QCOMPARE(v.capacity(), std::size_t{2}); + + v.push_back(2); + QVERIFY(!v.empty()); + QVERIFY(v.full()); + QCOMPARE(v.size(), std::size_t{2}); + QCOMPARE(v.max_size(), std::size_t{2}); + QCOMPARE(v.capacity(), std::size_t{2}); + + auto empty = ArrayVector{}; + QVERIFY(empty.empty()); + QVERIFY(empty.full()); + QCOMPARE(empty.size(), std::size_t{0}); + QCOMPARE(empty.max_size(), std::size_t{0}); + QCOMPARE(empty.capacity(), std::size_t{0}); + } + + void insertValueTest() + { + { + // Copy + const auto data = Constructible{}; + auto v = ArrayVector{}; + v.insert(v.cbegin(), data); + QCOMPARE(v.size(), std::size_t{1}); + QCOMPARE(v[0].construction, Construction::Copy); + } + { + // Move + auto v = ArrayVector{}; + v.insert(v.cbegin(), Constructible{}); + QCOMPARE(v.size(), std::size_t{1}); + QCOMPARE(v[0].construction, Construction::Move); + } + { + // Ensure the correct value is used (copy) + const auto data = 1; + auto v = ArrayVector{2, 3}; + v.insert(v.cbegin(), data); + QCOMPARE(v, (ArrayVector{1, 2, 3})); + } + { + // Ensure the correct value is used (move) + auto v = ArrayVector{2, 3}; + v.insert(v.cbegin(), 1); + QCOMPARE(v, (ArrayVector{1, 2, 3})); + } + } + + void insertFillValueTest() + { + { + // Insertion should copy construct + auto v = ArrayVector{}; + v.insert(v.cbegin(), 3, {}); + QCOMPARE(v.size(), std::size_t{3}); + for (const auto& element : v) { + QCOMPARE(element.construction, Construction::Copy); + } + } + { + // Ensure correct value is filled + auto v = ArrayVector{1, 3}; + v.insert(v.cbegin() + 1, 3, 2); + QCOMPARE(v, (ArrayVector{1, 2, 2, 2, 3})); + } + } + + void insertRangeTest() + { + { + // Insertion should copy construct + const auto data = std::array{Constructible{}}; + auto v = ArrayVector{}; + v.insert(v.cbegin(), data.begin(), data.end()); + QCOMPARE(v.size(), std::size_t{1}); + for (const auto& element : v) { + QCOMPARE(element.construction, Construction::Copy); + } + } + { + // Ensure correct value is filled + const auto data = std::array{2, 3}; + auto v = ArrayVector{1, 4}; + v.insert(v.cbegin() + 1, data.begin(), data.end()); + QCOMPARE(v, (ArrayVector{1, 2, 3, 4})); + } + } + + void insertInitializerListTest() + { + { + // Insertion should copy construct + auto v = ArrayVector{}; + v.insert(v.cbegin(), {Constructible{}}); + QCOMPARE(v.size(), std::size_t{1}); + for (const auto& element : v) { + QCOMPARE(element.construction, Construction::Copy); + } + } + { + // Ensure corresponding elements are used + auto v = ArrayVector{1, 4}; + v.insert(v.cbegin() + 1, {2, 3}); + QCOMPARE(v, (ArrayVector{1, 2, 3, 4})); + } + } + + void emplaceTest() + { + { + // Ensure the value is constructed in-place + auto v = ArrayVector{}; + v.emplace(v.cbegin()); + QCOMPARE(v.size(), std::size_t{1}); + QCOMPARE(v[0].construction, Construction::Default); + } + { + // Ensure the correct value is used (move) + auto v = ArrayVector{2, 3}; + v.emplace(v.cbegin(), 1); + QCOMPARE(v, (ArrayVector{1, 2, 3})); + } + } + + void eraseTest() + { + { + // Ensure destructors are run + auto destructed = false; + auto v = ArrayVector{{&destructed}}; + v.erase(v.cbegin()); + QVERIFY(destructed); + } + { + // Ensure the result is correct + auto v = ArrayVector{10, 1, 2, 3}; + v.erase(v.cbegin()); + QCOMPARE(v, (ArrayVector{1, 2, 3})); + } + } + + void eraseRangeTest() + { + { + // Ensure destructors are run + auto destructed = false; + auto v = ArrayVector{{&destructed}}; + v.erase(v.cbegin(), v.cend()); + QVERIFY(destructed); + } + { + // Ensure the result is correct + auto v = ArrayVector{1, 20, 21, 2, 3}; + v.erase(v.cbegin() + 1, v.cbegin() + 3); + QCOMPARE(v, (ArrayVector{1, 2, 3})); + } + } + + void pushBackTest() + { + { + // Copy + const auto data = Constructible{}; + auto v = ArrayVector{}; + v.push_back(data); + QCOMPARE(v.size(), std::size_t{1}); + QCOMPARE(v[0].construction, Construction::Copy); + } + { + // Move + auto v = ArrayVector{}; + v.push_back({}); + QCOMPARE(v.size(), std::size_t{1}); + QCOMPARE(v[0].construction, Construction::Move); + } + { + // Ensure the correct value is used (copy) + const auto data = 3; + auto v = ArrayVector{1, 2}; + v.push_back(data); + QCOMPARE(v, (ArrayVector{1, 2, 3})); + } + { + // Ensure the correct value is used (move) + auto v = ArrayVector{1, 2}; + v.push_back(3); + QCOMPARE(v, (ArrayVector{1, 2, 3})); + } + } + + void emplaceBackTest() + { + { + // Ensure the value is constructed in-place + auto v = ArrayVector{}; + v.emplace_back(); + QCOMPARE(v.size(), std::size_t{1}); + QCOMPARE(v[0].construction, Construction::Default); + } + { + // Ensure the correct value is used (move) + auto v = ArrayVector{1, 2}; + v.emplace_back(3); + QCOMPARE(v, (ArrayVector{1, 2, 3})); + } + } + + void popBackTest() + { + { + // Ensure destructors are run + auto destructed = false; + auto v = ArrayVector{{&destructed}}; + v.pop_back(); + QVERIFY(destructed); + } + { + // Ensure the result is correct + auto v = ArrayVector{1, 2, 3}; + v.pop_back(); + QCOMPARE(v, (ArrayVector{1, 2})); + } + } + + void resizeDefaultTest() + { + { + // Smaller + auto destructed = false; + auto v = ArrayVector{{&destructed}}; + QCOMPARE(v.size(), std::size_t{1}); + v.resize(0); + QCOMPARE(v.size(), std::size_t{0}); + QVERIFY(destructed); + } + { + // Bigger + auto v = ArrayVector{}; + QCOMPARE(v.size(), std::size_t{0}); + v.resize(1); + QCOMPARE(v.size(), std::size_t{1}); + QCOMPARE(v[0].construction, Construction::Default); + } + { + // Too big + auto v = ArrayVector{}; + QVERIFY_EXCEPTION_THROWN(v.resize(2), std::length_error); + } + } + + void resizeValueTest() + { + { + // Smaller + auto dummy = false; + auto destructed = false; + auto v = ArrayVector{{&destructed}}; + QCOMPARE(v.size(), std::size_t{1}); + v.resize(0, {&dummy}); + QCOMPARE(v.size(), std::size_t{0}); + QVERIFY(destructed); + } + { + // Bigger + auto v = ArrayVector{}; + QCOMPARE(v.size(), std::size_t{0}); + v.resize(1, {}); + QCOMPARE(v.size(), std::size_t{1}); + QCOMPARE(v[0].construction, Construction::Copy); + } + { + // Too big + auto v = ArrayVector{}; + QVERIFY_EXCEPTION_THROWN(v.resize(2), std::length_error); + } + { + // Ensure the correct value is used + auto v = ArrayVector{}; + v.resize(1, 1); + QCOMPARE(v, (ArrayVector{1})); + } + } + + void clearTest() + { + { + // Ensure destructors are run + auto destructed = false; + auto v = ArrayVector{{&destructed}}; + v.clear(); + QVERIFY(destructed); + } + { + // Ensure the result is correct + auto v = ArrayVector{1, 2, 3}; + v.clear(); + QCOMPARE(v, (ArrayVector{})); + } + } + + void memberSwapTest() + { + auto a = ArrayVector{1, 2, 3, 4}; + auto b = ArrayVector{2, 4, 6}; + + const auto aOriginal = a; + const auto bOriginal = b; + + a.swap(b); + + QCOMPARE(a, bOriginal); + QCOMPARE(b, aOriginal); + } + + void freeSwapTest() + { + auto a = ArrayVector{1, 2, 3, 4}; + auto b = ArrayVector{2, 4, 6}; + + const auto aOriginal = a; + const auto bOriginal = b; + + swap(a, b); + + QCOMPARE(a, bOriginal); + QCOMPARE(b, aOriginal); + } + + void comparisonTest() + { + const auto v = ArrayVector{1, 2, 3}; + const auto l = ArrayVector{1, 2, 2}; + const auto e = ArrayVector{1, 2, 3}; + const auto g = ArrayVector{1, 3, 3}; + + QVERIFY(l < v); + QVERIFY(!(e < v)); + QVERIFY(!(g < v)); + + QVERIFY(l <= v); + QVERIFY(e <= v); + QVERIFY(!(g <= v)); + + QVERIFY(!(l > v)); + QVERIFY(!(e > v)); + QVERIFY(g > v); + + QVERIFY(!(l >= v)); + QVERIFY(e >= v); + QVERIFY(g >= v); + + QVERIFY(!(l == v)); + QVERIFY(e == v); + QVERIFY(!(g == v)); + + QVERIFY(l != v); + QVERIFY(!(e != v)); + QVERIFY(g != v); + } +}; + +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 new file mode 100644 index 000000000..00694c44f --- /dev/null +++ b/tests/src/core/MathTest.cpp @@ -0,0 +1,54 @@ +/* + * MathTest.cpp + * + * Copyright (c) 2023 Johannes Lorenz + * + * This file is part of LMMS - https://lmms.io + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program (see COPYING); if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA. + * + */ + +#include +#include +#include + +#include "lmms_math.h" + +class MathTest : public QObject +{ + Q_OBJECT +private slots: + void NumDigitsTest() + { + using namespace lmms; + QCOMPARE(numDigitsAsInt(1.f), 1); + QCOMPARE(numDigitsAsInt(9.9f), 2); + QCOMPARE(numDigitsAsInt(10.f), 2); + QCOMPARE(numDigitsAsInt(0.f), 1); + QCOMPARE(numDigitsAsInt(-100.f), 4); + QCOMPARE(numDigitsAsInt(-99.f), 3); + QCOMPARE(numDigitsAsInt(-0.4f), 1); // there is no "-0" for LED spinbox + QCOMPARE(numDigitsAsInt(-0.99f), 2); + QCOMPARE(numDigitsAsInt(1000000000), 10); + QCOMPARE(numDigitsAsInt(-1000000000), 11); + QCOMPARE(numDigitsAsInt(900000000), 9); + QCOMPARE(numDigitsAsInt(-900000000), 10); + } +}; + +QTEST_GUILESS_MAIN(MathTest) +#include "MathTest.moc" diff --git a/tests/src/core/ProjectVersionTest.cpp b/tests/src/core/ProjectVersionTest.cpp index 2d066dca5..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: @@ -34,13 +34,13 @@ private slots: { using namespace lmms; - QVERIFY(ProjectVersion("1.1.0", ProjectVersion::Minor) > "1.0.3"); - QVERIFY(ProjectVersion("1.1.0", ProjectVersion::Major) < "2.1.0"); - QVERIFY(ProjectVersion("1.1.0", ProjectVersion::Release) > "0.2.1"); - QVERIFY(ProjectVersion("1.1.4", ProjectVersion::Release) < "1.1.10"); - QVERIFY(ProjectVersion("1.1.0", ProjectVersion::Minor) == "1.1.5"); - QVERIFY( ! ( ProjectVersion("3.1.0", ProjectVersion::Minor) < "2.2.5" ) ); - QVERIFY( ! ( ProjectVersion("2.5.0", ProjectVersion::Release) < "2.2.5" ) ); + QVERIFY(ProjectVersion("1.1.0", ProjectVersion::CompareType::Minor) > "1.0.3"); + QVERIFY(ProjectVersion("1.1.0", ProjectVersion::CompareType::Major) < "2.1.0"); + QVERIFY(ProjectVersion("1.1.0", ProjectVersion::CompareType::Release) > "0.2.1"); + QVERIFY(ProjectVersion("1.1.4", ProjectVersion::CompareType::Release) < "1.1.10"); + QVERIFY(ProjectVersion("1.1.0", ProjectVersion::CompareType::Minor) == "1.1.5"); + QVERIFY( ! ( ProjectVersion("3.1.0", ProjectVersion::CompareType::Minor) < "2.2.5" ) ); + QVERIFY( ! ( ProjectVersion("2.5.0", ProjectVersion::CompareType::Release) < "2.2.5" ) ); //A pre-release version has lower precedence than a normal version QVERIFY(ProjectVersion("1.1.0") > "1.1.0-alpha"); //But higher precedence than the previous version @@ -62,7 +62,7 @@ private slots: QVERIFY(ProjectVersion("1.2.3.42") == "1.2.3"); //CompareVersion "All" should compare every identifier QVERIFY( - ProjectVersion("1.0.0-a.b.c.d.e.f.g.h.i.j.k.l", ProjectVersion::All) + ProjectVersion("1.0.0-a.b.c.d.e.f.g.h.i.j.k.l", ProjectVersion::CompareType::All) < "1.0.0-a.b.c.d.e.f.g.h.i.j.k.m" ); //Prerelease identifiers may contain hyphens @@ -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 2bf875992..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() @@ -52,7 +60,7 @@ private slots: using namespace lmms; AutomationClip c(nullptr); - c.setProgressionType(AutomationClip::LinearProgression); + c.setProgressionType(AutomationClip::ProgressionType::Linear); c.putValue(0, 0.0, false); c.putValue(100, 1.0, false); @@ -69,7 +77,7 @@ private slots: using namespace lmms; AutomationClip c(nullptr); - c.setProgressionType(AutomationClip::DiscreteProgression); + c.setProgressionType(AutomationClip::ProgressionType::Discrete); c.putValue(0, 0.0, false); c.putValue(100, 1.0, false); @@ -89,14 +97,14 @@ private slots: AutomationTrack track(song); AutomationClip c1(&track); - c1.setProgressionType(AutomationClip::LinearProgression); + c1.setProgressionType(AutomationClip::ProgressionType::Linear); c1.putValue(0, 0.0, false); c1.putValue(10, 1.0, false); c1.movePosition(0); c1.addObject(&model); AutomationClip c2(&track); - c2.setProgressionType(AutomationClip::LinearProgression); + c2.setProgressionType(AutomationClip::ProgressionType::Linear); c2.putValue(0, 0.0, false); c2.putValue(100, 1.0, false); c2.movePosition(100); @@ -125,7 +133,7 @@ private slots: AutomationTrack track(song); AutomationClip c(&track); - c.setProgressionType(AutomationClip::LinearProgression); + c.setProgressionType(AutomationClip::ProgressionType::Linear); c.addObject(&model); c.putValue(0, 0.0, false); @@ -148,17 +156,16 @@ private slots: auto song = Engine::getSong(); - InstrumentTrack* instrumentTrack = - dynamic_cast(Track::create(Track::InstrumentTrack, 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(); auto clip = dh->automationClip(); - clip->setProgressionType( AutomationClip::LinearProgression ); + clip->setProgressionType( AutomationClip::ProgressionType::Linear ); clip->putValue(TimePos(0, 0), 0.0); clip->putValue(TimePos(4, 0), 1.0); @@ -175,15 +182,16 @@ private slots: auto song = Engine::getSong(); auto patternStore = Engine::patternStore(); PatternTrack patternTrack(song); - Track* automationTrack = Track::create(Track::AutomationTrack, 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; - c1->setProgressionType(AutomationClip::LinearProgression); + c1->setProgressionType(AutomationClip::ProgressionType::Linear); c1->putValue(0, 0.0, false); c1->putValue(10, 1.0, false); c1->addObject(&model); @@ -222,8 +230,8 @@ private slots: AutomationClip localClip(&localTrack); FloatModel model; - globalClip.setProgressionType(AutomationClip::DiscreteProgression); - localClip.setProgressionType(AutomationClip::DiscreteProgression); + globalClip.setProgressionType(AutomationClip::ProgressionType::Discrete); + localClip.setProgressionType(AutomationClip::ProgressionType::Discrete); globalClip.addObject(&model); localClip.addObject(&model); globalClip.putValue(0, 100.0f, false); @@ -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 + } + ] +}