diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 9c812fd9b..f6560a23e 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -339,7 +339,7 @@ jobs: CCACHE_MAXSIZE: 500M msvc: name: msvc-x64 - runs-on: windows-2019 + runs-on: windows-2022 env: CCACHE_MAXSIZE: 0 CCACHE_NOCOMPRESS: 1 @@ -421,3 +421,68 @@ jobs: ccache --show-stats --verbose env: CCACHE_MAXSIZE: 500MB + msys2: + name: windows-arm64 + runs-on: windows-11-arm + defaults: + run: + shell: msys2 {0} + env: + CMAKE_OPTS: >- + -DCMAKE_BUILD_TYPE=RelWithDebInfo + -DUSE_COMPILE_CACHE=ON + -DCPACK_NSIS_EXECUTABLE=/clang64/bin/makensis.exe + CCACHE_MAXSIZE: 0 + CCACHE_NOCOMPRESS: 1 + MAKEFLAGS: -j2 + steps: + - name: Check out + uses: actions/checkout@v3 + with: + fetch-depth: 0 + submodules: recursive + - name: Cache msys2 dependencies + id: cache-deps + uses: actions/cache@v3 + with: + key: windows-arm64-${{ hashFiles('.github/workflows/deps-msys2-clangarm64.txt') }} + restore-keys: | + windows-arm64- + path: \msys64\var\cache\pacman\pkg + - name: Install msys2 + uses: msys2/setup-msys2@v2 + with: + msystem: CLANGARM64 + update: true + - name: Install dependencies + run: pacman --needed --noconfirm -S - < .github/workflows/deps-msys2-clangarm64.txt + - name: Cache ccache data + uses: actions/cache@v3 + with: + key: ccache-${{ github.job }}-${{ github.ref }}-${{ github.run_id }} + restore-keys: | + ccache-${{ github.job }}-${{ github.ref }}- + ccache-${{ github.job }}- + path: ~\AppData\Local\ccache + - name: Configure + run: | + /clang64/bin/ccache.exe --zero-stats + cmake -B build $CMAKE_OPTS + - name: Build + run: cmake --build build + - name: Package + run: cmake --build build --target package + - name: Upload artifacts + uses: actions/upload-artifact@v4 + with: + name: windows-arm64 + path: build\lmms-*.exe + - name: Trim ccache and print statistics + run: | + /clang64/bin/ccache.exe --cleanup + echo "[ccache config]" + /clang64/bin/ccache.exe --show-config + echo "[ccache stats]" + /clang64/bin/ccache.exe --show-stats --verbose + env: + CCACHE_MAXSIZE: 500MB diff --git a/.github/workflows/deps-msys2-clangarm64.txt b/.github/workflows/deps-msys2-clangarm64.txt new file mode 100644 index 000000000..c3feb0264 --- /dev/null +++ b/.github/workflows/deps-msys2-clangarm64.txt @@ -0,0 +1,25 @@ +base +base-devel +filesystem +git +mingw-w64-clang-aarch64-SDL2 +mingw-w64-clang-aarch64-ccache +mingw-w64-clang-aarch64-clang +mingw-w64-clang-aarch64-cmake +mingw-w64-clang-aarch64-fftw +mingw-w64-clang-aarch64-fltk +mingw-w64-clang-aarch64-fluidsynth +mingw-w64-clang-aarch64-libgig +mingw-w64-clang-aarch64-libsamplerate +mingw-w64-clang-aarch64-libsndfile +mingw-w64-clang-aarch64-lilv +mingw-w64-clang-aarch64-lv2 +mingw-w64-clang-aarch64-qt5-base +mingw-w64-clang-aarch64-qt5-svg +mingw-w64-clang-aarch64-suil +mingw-w64-clang-aarch64-stk +mingw-w64-clang-x86_64-nsis +mingw-w64-clang-x86_64-ccache +msys2-runtime +perl-List-MoreUtils +perl-XML-Parser \ No newline at end of file diff --git a/.gitignore b/.gitignore index cc2823ba0..5afe75307 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,5 @@ /plugins/ZynAddSubFx/zynaddsubfx/doc/gen/Makefile /data/locale/*.qm Brewfile.lock.json +/.cache/ +compile_commands.json diff --git a/CMakeLists.txt b/CMakeLists.txt index 3c01bebf0..f439815cb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,5 @@ CMAKE_MINIMUM_REQUIRED(VERSION 3.13) +SET(CMAKE_EXPORT_COMPILE_COMMANDS ON) # 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 @@ -633,11 +634,20 @@ ENDIF() SET(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -DLMMS_DEBUG") SET(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -DLMMS_DEBUG") +if(NOT CMAKE_BUILD_TYPE STREQUAL "Debug") + add_compile_definitions(NDEBUG) + SET(STATUS_ASSERTIONS "Disabled") +else() + remove_definitions(-DNDEBUG) + SET(STATUS_ASSERTIONS "Enabled") +endif() + if(CMAKE_VERSION VERSION_GREATER_EQUAL "3.16") set(NOOP_COMMAND "${CMAKE_COMMAND}" "-E" "true") else() set(NOOP_COMMAND "${CMAKE_COMMAND}" "-E" "echo") endif() + if(STRIP) # TODO CMake 3.19: Now that CONFIG generator expressions support testing for # multiple configurations, combine the OR into a single CONFIG expression. @@ -864,6 +874,7 @@ MESSAGE( "* Debug using UBSanitizer : ${STATUS_DEBUG_UBSAN}\n" "* Debug packaging commands : ${STATUS_DEBUG_CPACK}\n" "* Profile using GNU profiler : ${STATUS_GPROF}\n" +"* Debug assertions : ${STATUS_ASSERTIONS}\n" ) MESSAGE( diff --git a/data/themes/classic/auto_resize.png b/data/themes/classic/auto_resize.png new file mode 100644 index 000000000..491966a8e Binary files /dev/null and b/data/themes/classic/auto_resize.png differ diff --git a/data/themes/classic/auto_resize_disable.png b/data/themes/classic/auto_resize_disable.png new file mode 100644 index 000000000..e7c6c2c83 Binary files /dev/null and b/data/themes/classic/auto_resize_disable.png differ diff --git a/data/themes/classic/automation_ghost_note.png b/data/themes/classic/automation_ghost_note.png index d4b1416f5..4b06b4073 100644 Binary files a/data/themes/classic/automation_ghost_note.png and b/data/themes/classic/automation_ghost_note.png differ diff --git a/data/themes/classic/clear_ghost_note.png b/data/themes/classic/clear_ghost_note.png index a005b6034..fb5fe089c 100644 Binary files a/data/themes/classic/clear_ghost_note.png and b/data/themes/classic/clear_ghost_note.png differ diff --git a/data/themes/classic/clear_notes_out_of_bounds.png b/data/themes/classic/clear_notes_out_of_bounds.png new file mode 100644 index 000000000..d3f27a0f5 Binary files /dev/null and b/data/themes/classic/clear_notes_out_of_bounds.png differ diff --git a/data/themes/classic/cursor_knife.png b/data/themes/classic/cursor_knife.png index 591df2d62..8ddbd3ac4 100644 Binary files a/data/themes/classic/cursor_knife.png and b/data/themes/classic/cursor_knife.png differ diff --git a/data/themes/classic/cut_overlaps.png b/data/themes/classic/cut_overlaps.png new file mode 100644 index 000000000..e79e4950a Binary files /dev/null and b/data/themes/classic/cut_overlaps.png differ diff --git a/data/themes/classic/edit_knife.png b/data/themes/classic/edit_knife.png index b6b6bf934..0afdae5b7 100644 Binary files a/data/themes/classic/edit_knife.png and b/data/themes/classic/edit_knife.png differ diff --git a/data/themes/classic/file.png b/data/themes/classic/file.png new file mode 100644 index 000000000..e74f14df6 Binary files /dev/null and b/data/themes/classic/file.png differ diff --git a/data/themes/classic/fill.png b/data/themes/classic/fill.png new file mode 100644 index 000000000..6bc07d1e8 Binary files /dev/null and b/data/themes/classic/fill.png differ diff --git a/data/themes/classic/ghost_note.png b/data/themes/classic/ghost_note.png index 2c3ace519..9d3b31632 100644 Binary files a/data/themes/classic/ghost_note.png and b/data/themes/classic/ghost_note.png differ diff --git a/data/themes/classic/glue.png b/data/themes/classic/glue.png new file mode 100644 index 000000000..c5d059a09 Binary files /dev/null and b/data/themes/classic/glue.png differ diff --git a/data/themes/classic/gridmode.png b/data/themes/classic/gridmode.png new file mode 100644 index 000000000..20b65fa47 Binary files /dev/null and b/data/themes/classic/gridmode.png differ diff --git a/data/themes/classic/insert_bar.png b/data/themes/classic/insert_bar.png new file mode 100644 index 000000000..fbc9f4775 Binary files /dev/null and b/data/themes/classic/insert_bar.png differ diff --git a/data/themes/classic/max_length.png b/data/themes/classic/max_length.png new file mode 100644 index 000000000..00162eb06 Binary files /dev/null and b/data/themes/classic/max_length.png differ diff --git a/data/themes/classic/min_length.png b/data/themes/classic/min_length.png new file mode 100644 index 000000000..be1c6e94f Binary files /dev/null and b/data/themes/classic/min_length.png differ diff --git a/data/themes/classic/proportional_snap.png b/data/themes/classic/proportional_snap.png index 485ced02b..be687c9df 100644 Binary files a/data/themes/classic/proportional_snap.png and b/data/themes/classic/proportional_snap.png differ diff --git a/data/themes/classic/receive_bg_arrow.png b/data/themes/classic/receive_bg_arrow.png index 8aaeae606..8e01e9df8 100644 Binary files a/data/themes/classic/receive_bg_arrow.png and b/data/themes/classic/receive_bg_arrow.png differ diff --git a/data/themes/classic/remove_bar.png b/data/themes/classic/remove_bar.png new file mode 100644 index 000000000..143766a37 Binary files /dev/null and b/data/themes/classic/remove_bar.png differ diff --git a/data/themes/classic/send_bg_arrow.png b/data/themes/classic/send_bg_arrow.png index cc29b8649..2cbde2603 100644 Binary files a/data/themes/classic/send_bg_arrow.png and b/data/themes/classic/send_bg_arrow.png differ diff --git a/data/themes/classic/hq_mode.png b/data/themes/classic/star.png similarity index 100% rename from data/themes/classic/hq_mode.png rename to data/themes/classic/star.png diff --git a/data/themes/classic/style.css b/data/themes/classic/style.css index 13047dc5d..a0fb48209 100644 --- a/data/themes/classic/style.css +++ b/data/themes/classic/style.css @@ -30,9 +30,8 @@ lmms--gui--AutomationEditor { qproperty-barLineColor: #808080; qproperty-graphColor: rgba(153, 175, 255, 200); - qproperty-scaleColor: qlineargradient(spread:reflect, - x1:0, y1:0.5, x2:1, y2:0.5, - stop:0 #333, stop:1 #202020); + 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); @@ -69,7 +68,8 @@ QTextEdit, QLineEdit:focus, QComboBox:focus, QSpinBox:focus, QDoubleSpinBox:focu QToolTip { 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)); + 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; border: 1.0px solid rgba(0,0,0,255); color: #4afd85; @@ -77,7 +77,8 @@ QToolTip { 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)); + 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; border: 1.0px solid rgba(0,0,0,255); color: #4afd85; @@ -248,45 +249,39 @@ QScrollBar::add-page:vertical:pressed, QScrollBar::sub-page:vertical:pressed { /* scrollbar: handles (sliders) */ QScrollBar::handle:horizontal { - background: qlineargradient(spread:reflect, - x1:0.5, y1:0, x2:0.5, y2:1, - stop:0 #969696, stop:0.5 #c9c9c9, stop:1 #aaa); + background: qlineargradient(spread:reflect, x1:0.5, y1:0, x2:0.5, y2:1, + stop:0 #969696, stop:0.5 #c9c9c9, stop:1 #aaa); border: 1px outset #888; border-radius: 2px; min-width: 24px; } QScrollBar::handle:horizontal:hover { - background: qlineargradient(spread:reflect, - x1:0.5, y1:0, x2:0.5, y2:1, - stop:0 #969696, stop:0.5 #f0f0f0, stop:1 #aaa); + background: qlineargradient(spread:reflect, x1:0.5, y1:0, x2:0.5, y2:1, + stop:0 #969696, stop:0.5 #f0f0f0, stop:1 #aaa); } QScrollBar::handle:horizontal:pressed { - background: qlineargradient(spread:reflect, - x1:0.5, y1:0, x2:0.5, y2:1, - stop:0 #747474, stop:1 #c9c9c9); + background: qlineargradient(spread:reflect, x1:0.5, y1:0, x2:0.5, y2:1, + stop:0 #747474, stop:1 #c9c9c9); } QScrollBar::handle:vertical { - background: qlineargradient(spread:reflect, - x1:0, y1:0.5, x2:1, y2:0.5, - stop:0 #969696, stop:0.5 #c9c9c9, stop:1 #aaa); + background: qlineargradient(spread:reflect, x1:0, y1:0.5, x2:1, y2:0.5, + stop:0 #969696, stop:0.5 #c9c9c9, stop:1 #aaa); border: 1px outset #888; border-radius: 2px; min-height: 24px; } QScrollBar::handle:vertical:hover { - background: qlineargradient(spread:reflect, - x1:0, y1:0.5, x2:1, y2:0.5, - stop:0 #969696, stop:0.5 #f0f0f0, stop:1 #aaa); + background: qlineargradient(spread:reflect, x1:0, y1:0.5, x2:1, y2:0.5, + stop:0 #969696, stop:0.5 #f0f0f0, stop:1 #aaa); } QScrollBar::handle:vertical:pressed { - background: qlineargradient(spread:reflect, - x1:0, y1:0.5, x2:1, y2:0.5, - stop:0 #747474, stop:1 #c9c9c9); + background: qlineargradient(spread:reflect, x1:0, y1:0.5, x2:1, y2:0.5, + stop:0 #747474, stop:1 #c9c9c9); } QScrollBar::handle:horizontal:disabled, QScrollBar::handle:vertical:disabled { @@ -298,7 +293,8 @@ QScrollBar::handle:horizontal:disabled, QScrollBar::handle:vertical:disabled { /* arrow buttons */ QScrollBar::add-line, QScrollBar::sub-line { - background: qradialgradient(cx:0.3, cy:0.3, radius:0.8, fx:0.3, fy:0.3, stop:0 #c9c9c9, stop:1 #969696 ); + background: qradialgradient(cx:0.3, cy:0.3, radius:0.8, fx:0.3, fy:0.3, + stop:0 #c9c9c9, stop:1 #969696 ); border-radius: 1px; border: 1px solid #131313; subcontrol-origin: margin; @@ -310,11 +306,13 @@ QScrollBar::add-line:vertical { subcontrol-position: bottom; height: 12px;} QScrollBar::sub-line:vertical { subcontrol-position: top; height: 12px;} QScrollBar::add-line:hover, QScrollBar::sub-line:hover { - background: qradialgradient(cx:0.3, cy:0.3, radius:0.8, fx:0.3, fy:0.3, stop:0 #e0e0e0, stop:0.5 #c9c9c9, stop:1 #969696 ); + background: qradialgradient(cx:0.3, cy:0.3, radius:0.8, fx:0.3, fy:0.3, + stop:0 #e0e0e0, stop:0.5 #c9c9c9, stop:1 #969696 ); } QScrollBar::add-line:pressed, QScrollBar::sub-line:pressed { - background: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 #969696, stop:0.5 #c9c9c9, stop:1 #969696 ); + background: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 #969696, + stop:0.5 #c9c9c9, stop:1 #969696 ); } QScrollBar::add-line:disabled, QScrollBar::sub-line:disabled { @@ -356,8 +354,10 @@ lmms--gui--TrackView > QWidget { /* track background config */ lmms--gui--TrackContentWidget { /* colors */ - qproperty-darkerColor: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 rgb(50, 50, 50), stop:0.33 rgb(20, 20, 20), stop:1 rgb(15, 15, 15)); - qproperty-lighterColor: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 rgb(50, 50, 50), stop:0.33 rgb(40, 40, 40), stop:1 rgb(30, 30, 30)); + qproperty-darkerColor: qlineargradient(x1:0, y1:0, x2:0, y2:1, + stop:0 rgb(50, 50, 50), stop:0.33 rgb(20, 20, 20), stop:1 rgb(15, 15, 15)); + qproperty-lighterColor: qlineargradient(x1:0, y1:0, x2:0, y2:1, + stop:0 rgb(50, 50, 50), stop:0.33 rgb(40, 40, 40), stop:1 rgb(30, 30, 30)); qproperty-coarseGridColor: rgba(0, 0, 0, 160); qproperty-fineGridColor: rgba(0, 0, 0, 80); qproperty-horizontalColor: rgba(0, 0, 0, 160); @@ -376,32 +376,61 @@ lmms--gui--TrackContentWidget { /* gear button in tracks */ + +/* 20px = 1px border + 2px padding + 14px icon + 2px padding + 1px border */ +QPushButton#btn-mute, +QPushButton#btn-mute-inv:checked, +QPushButton#btn-solo, lmms--gui--TrackOperationsWidget QPushButton { - max-height: 26px; - max-width: 26px; - min-height: 26px; - min-width: 26px; - background: none; - border: none; + min-height: 14; + max-height: 14; + min-width: 14; + max-width: 14; + padding: 2; + border: 1 solid #0f1621; + border-top: 1 solid #18202b; + border-bottom: 1 solid #02060f; + border-radius: 4; + background: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 rgba(255, 255, 255, 20%), stop:0.1 rgba(255, 255, 255, 2%), stop:0.9 rgba(0, 0, 0, 2%), stop:1 rgba(0, 0, 0, 40%)); } lmms--gui--TrackOperationsWidget QPushButton::menu-indicator { - image: url("resources:trackop.png"); - subcontrol-origin: padding; - subcontrol-position: center; - position: relative; - top: 1px; + image: none; } -lmms--gui--TrackOperationsWidget QPushButton::menu-indicator:hover { - image: url("resources:trackop_h.png"); +lmms--gui--TrackOperationsWidget QPushButton { + image: url("resources:gear.svg"); } -lmms--gui--TrackOperationsWidget QPushButton::menu-indicator:pressed, -lmms--gui--TrackOperationsWidget QPushButton::menu-indicator:checked { - image: url("resources:trackop_c.png"); - position: relative; - top: 2px; +QPushButton#btn-mute, +QPushButton#btn-mute-inv:checked { + image: url("resources:speaker.svg"); +} + +QPushButton#btn-solo { + image: url("resources:headphones.svg"); +} + +QPushButton#btn-mute:hover, +QPushButton#btn-solo:hover, +lmms--gui--TrackOperationsWidget QPushButton:hover { + background: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 rgba(255, 255, 255, 25%), stop:0.1 rgba(255, 255, 255, 7%), stop:0.9 transparent, stop:1 rgba(0, 0, 0, 35%)); +} + +lmms--gui--TrackOperationsWidget QPushButton:pressed { + background: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 rgba(0, 0, 0, 25%), stop:0.1 transparent, stop:0.8 rgba(0, 0, 0, 15%), stop:1 rgba(0, 0, 0, 35%)); +} + +QPushButton#btn-mute-inv, +QPushButton#btn-mute:checked { + image: url("resources:speaker_slash.svg"); + border: 1 solid #890120; + background: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 #e2515b, stop:0.1 #d40237, stop:0.9 #d40237, stop:1 #900122); +} + +QPushButton#btn-solo:checked { + border: 1 solid #055f89; + background: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 #5aa8d9, stop:0.1 #2696d1, stop:0.9 #2696d1, stop:1 #16638c); } /* actually has no effect yet so disabled */ @@ -544,7 +573,8 @@ QToolButton:pressed { } QToolButton:checked { - background: qradialgradient(cx:0.3, cy:0.3, radius:0.8, fx:0.3, fy:0.3, stop:0 #e0e0e0, stop:0.8 #c9c9c9, stop:1 #c0c0c0 ); + background: qradialgradient(cx:0.3, cy:0.3, radius:0.8, fx:0.3, fy:0.3, + stop:0 #e0e0e0, stop:0.8 #c9c9c9, stop:1 #c0c0c0 ); padding: 2px 1px 0px 1px; color: black; } @@ -578,7 +608,8 @@ lmms--gui--TrackLabelButton { } lmms--gui--TrackLabelButton:hover { - background-color: qlineargradient(spread:reflect, x1:0, y1:0, x2:0, y2:0.5, stop:0 #5b6571, stop:0.75 #7b838d, stop:1 #7b838d ); + background-color: qlineargradient(spread:reflect, x1:0, y1:0, x2:0, y2:0.5, + stop:0 #5b6571, stop:0.75 #7b838d, stop:1 #7b838d ); color: white; border: 1px solid rgba(0,0,0,64); padding: 1px 0px; @@ -586,7 +617,8 @@ lmms--gui--TrackLabelButton:hover { } lmms--gui--TrackLabelButton:pressed { - background: qlineargradient(spread:reflect, x1:0.5, y1:0, x2:0.5, y2:1, stop:0 #49515b, stop:0.3 #5b6571, stop:1 #6b7581 ); + background: qlineargradient(spread:reflect, x1:0.5, y1:0, x2:0.5, y2:1, + stop:0 #49515b, stop:0.3 #5b6571, stop:1 #6b7581 ); color: white; border: 1px solid rgba(0,0,0,64); padding: 2px 0px 0px; @@ -594,7 +626,8 @@ lmms--gui--TrackLabelButton:pressed { } lmms--gui--TrackLabelButton:checked { - background: qlineargradient(spread:reflect, x1:0.5, y1:0, x2:0.5, y2:1, stop:0 #49515b, stop:0.3 #5b6571, stop:1 #6b7581 ); + background: qlineargradient(spread:reflect, x1:0.5, y1:0, x2:0.5, y2:1, + stop:0 #49515b, stop:0.3 #5b6571, stop:1 #6b7581 ); color: white; border: 1px solid rgba(0,0,0,128); padding: 2px 0px 0px; @@ -602,17 +635,19 @@ lmms--gui--TrackLabelButton:checked { } lmms--gui--TrackLabelButton:checked:hover { - background-color: qlineargradient(spread:reflect, x1:0, y1:0, x2:0, y2:0.5, stop:0 #5b6571, stop:0.75 #7b838d, stop:1 #7b838d ); + background-color: qlineargradient(spread:reflect, x1:0, y1:0, x2:0, y2:0.5, + stop:0 #5b6571, stop:0.75 #7b838d, stop:1 #7b838d ); } lmms--gui--TrackLabelButton:checked:pressed { - background: qlineargradient(spread:reflect, x1:0.5, y1:0, x2:0.5, y2:1, stop:0 #49515b, stop:0.3 #5b6571, stop:1 #6b7581 ); + background: qlineargradient(spread:reflect, x1:0.5, y1:0, x2:0.5, y2:1, + stop:0 #49515b, stop:0.3 #5b6571, stop:1 #6b7581 ); } /* sidebar, sidebar buttons */ lmms--gui--SideBar { - background: qlineargradient(x1:0, y1:0, x2:1, y2:0, stop: 0 #98a2a7, stop: 1.0 #5b646f); + background: qlineargradient(x1:0, y1:0, x2:1, y2:0, stop:0 #98a2a7, stop:1 #5b646f); } lmms--gui--SideBar QToolButton { @@ -649,8 +684,7 @@ lmms--gui--ControllerRackView QPushButton { lmms--gui--MixerChannelView { background: #5b6571; color: #e0e0e0; - qproperty-backgroundActive: qlineargradient(spread:reflect, x1:0, y1:0, x2:1, y2:0, - stop:0 #7b838d, stop:1 #6b7581 ); + qproperty-backgroundActive: #7d8691; qproperty-strokeOuterActive: rgb( 0, 0, 0 ); qproperty-strokeOuterInactive: rgba( 0, 0, 0, 50 ); qproperty-strokeInnerActive: rgba( 255, 255, 255, 100 ); @@ -684,16 +718,14 @@ lmms--gui--TimeLineWidget { min-height: 1.5em; max-height: 1.5em; - background-color: qlineargradient( x1: 0, y1: 0, x2: 0, y2: 1, - stop: 0 #8796a7, stop: 1.0 #3e454e ); + background-color: qlineargradient( x1:0, y1:0, x2:0, y2:1, stop:0 #8796a7, stop:1 #3e454e ); 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-activeLoopBrush: qlineargradient( x1:0, y1:0, x2:0, y2:1, stop:0 #378d59, stop:1 #297e36 ); qproperty-activeLoopInnerColor: rgba( 74, 155, 100, 255 ); qproperty-activeLoopHandleColor: rgba( 192, 192, 192, 200 ); @@ -729,6 +761,7 @@ lmms--gui--ClipView { qproperty-textBackgroundColor: rgba(0, 0, 0, 75); qproperty-textShadowColor: rgb( 0, 0, 0 ); qproperty-gradient: true; /* boolean property, set true to have a gradient */ + qproperty-markerColor: rgb(0, 0, 0); /* finger tip offset of cursor */ qproperty-mouseHotspotHand: 3px 3px; qproperty-mouseHotspotKnife: 0px 0px; @@ -766,10 +799,8 @@ lmms--gui--PatternClipView { /* Subwindows in MDI-Area */ lmms--gui--SubWindow { - color: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, - stop: 0 #4b525c, stop: 1.0 #31363d); - qproperty-activeColor: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, - stop: 0 #33383e, stop: 1.0 #1a1c20); + color: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 #4b525c, stop:1 #31363d); + qproperty-activeColor: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 #33383e, stop:1 #1a1c20); qproperty-textShadowColor: rgb( 0, 0, 0 ); qproperty-borderColor: rgb( 0, 0, 0 ); } diff --git a/data/themes/classic/tool.png b/data/themes/classic/tool.png new file mode 100644 index 000000000..183470438 Binary files /dev/null and b/data/themes/classic/tool.png differ diff --git a/data/themes/default/auto_resize.png b/data/themes/default/auto_resize.png new file mode 100644 index 000000000..52b65742d Binary files /dev/null and b/data/themes/default/auto_resize.png differ diff --git a/data/themes/default/auto_resize_disable.png b/data/themes/default/auto_resize_disable.png new file mode 100644 index 000000000..a997c81b6 Binary files /dev/null and b/data/themes/default/auto_resize_disable.png differ diff --git a/data/themes/default/clear_notes_out_of_bounds.png b/data/themes/default/clear_notes_out_of_bounds.png new file mode 100644 index 000000000..8387db843 Binary files /dev/null and b/data/themes/default/clear_notes_out_of_bounds.png differ diff --git a/data/themes/default/fader_knob.png b/data/themes/default/fader_knob.png deleted file mode 100644 index 2190451d8..000000000 Binary files a/data/themes/default/fader_knob.png and /dev/null differ diff --git a/data/themes/default/fader_knob.svg b/data/themes/default/fader_knob.svg new file mode 100644 index 000000000..8a5d9e908 --- /dev/null +++ b/data/themes/default/fader_knob.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/data/themes/default/gear.svg b/data/themes/default/gear.svg new file mode 100644 index 000000000..dfcd7d4d9 --- /dev/null +++ b/data/themes/default/gear.svg @@ -0,0 +1,26 @@ + + + + + + + + + Fawn Sannar + + + + + + + + + + + + diff --git a/data/themes/default/headphones.svg b/data/themes/default/headphones.svg new file mode 100644 index 000000000..ecdc5a18e --- /dev/null +++ b/data/themes/default/headphones.svg @@ -0,0 +1,44 @@ + + + LMMS solo button (inactive) + + + + + LMMS solo button (inactive) + + + Rebecca Noel Ati, Stakeout Punch + + + + + + + + + + + diff --git a/data/themes/default/mute_active.svg b/data/themes/default/mute_active.svg deleted file mode 100644 index 600144697..000000000 --- a/data/themes/default/mute_active.svg +++ /dev/null @@ -1,37 +0,0 @@ - - - LMMS mute button (active) - - - - - LMMS mute button (active) - - - Rebecca Noel Ati, Stakeout Punch - - - - - - - - - - - - - - - diff --git a/data/themes/default/mute_inactive.svg b/data/themes/default/mute_inactive.svg deleted file mode 100644 index 6042cc767..000000000 --- a/data/themes/default/mute_inactive.svg +++ /dev/null @@ -1,37 +0,0 @@ - - - LMMS mute button (inactive) - - - - - LMMS mute button (inactive) - - - Rebecca Noel Ati, Stakeout Punch - - - - - - - - - - - - - - - diff --git a/data/themes/default/solo_active.svg b/data/themes/default/solo_active.svg deleted file mode 100644 index d5c151be3..000000000 --- a/data/themes/default/solo_active.svg +++ /dev/null @@ -1,37 +0,0 @@ - - - LMMS solo button (active) - - - - - LMMS solo button (active) - - - Rebecca Noel Ati, Stakeout Punch - - - - - - - - - - - - - - - diff --git a/data/themes/default/solo_inactive.svg b/data/themes/default/solo_inactive.svg deleted file mode 100644 index 57788c607..000000000 --- a/data/themes/default/solo_inactive.svg +++ /dev/null @@ -1,37 +0,0 @@ - - - LMMS solo button (inactive) - - - - - LMMS solo button (inactive) - - - Rebecca Noel Ati, Stakeout Punch - - - - - - - - - - - - - - - diff --git a/data/themes/default/speaker.svg b/data/themes/default/speaker.svg new file mode 100644 index 000000000..1f43f5517 --- /dev/null +++ b/data/themes/default/speaker.svg @@ -0,0 +1,42 @@ + + + LMMS mute button (inactive) + + + + + LMMS mute button (inactive) + + + Rebecca Noel Ati, Stakeout Punch + + + + + + + + + + + diff --git a/data/themes/default/speaker_slash.svg b/data/themes/default/speaker_slash.svg new file mode 100644 index 000000000..9b42db7ac --- /dev/null +++ b/data/themes/default/speaker_slash.svg @@ -0,0 +1,44 @@ + + + LMMS mute button (active) + + + + + LMMS mute button (active) + + + Rebecca Noel Ati, Stakeout Punch + + + + + + + + + + + diff --git a/data/themes/default/hq_mode.png b/data/themes/default/star.png similarity index 100% rename from data/themes/default/hq_mode.png rename to data/themes/default/star.png diff --git a/data/themes/default/style.css b/data/themes/default/style.css index 184bc9b56..f78df0982 100644 --- a/data/themes/default/style.css +++ b/data/themes/default/style.css @@ -387,6 +387,24 @@ lmms--gui--TrackView > QWidget { } +QPushButton#btn { + color: #d1d8e4; + padding: 2 4; + border: 1 solid #000; + border-radius: 3; + background: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 rgba(255, 255, 255, 20%), stop:0.1 rgba(255, 255, 255, 4%), stop:0.9 rgba(0, 0, 0, 4%), stop:1 rgba(0, 0, 0, 40%)); +} + +QPushButton#btn:hover { + background: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 rgba(255, 255, 255, 25%), stop:0.1 rgba(255, 255, 255, 7%), stop:0.9 rgba(255, 255, 255, 4%), stop:1 rgba(0, 0, 0, 35%)); +} + +QPushButton#btn:pressed, +QPushButton#btn:checked { + color: #02ee89; + background: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 rgba(0, 0, 0, 25%), stop:0.1 transparent, stop:0.9 rgba(0, 0, 0, 15%), stop:1 rgba(0, 0, 0, 35%)); +} + /* autoscroll, loop, stop behaviour toggle buttons */ /* track background config */ @@ -412,30 +430,64 @@ lmms--gui--TrackContentWidget { /* gear button in tracks */ + +/* 20px = 1px border + 2px padding + 14px icon + 2px padding + 1px border */ +QPushButton#btn-mute, +QPushButton#btn-mute-inv:checked, +QPushButton#btn-solo, lmms--gui--TrackOperationsWidget QPushButton { - max-height: 26px; - max-width: 26px; - min-height: 26px; - min-width: 26px; - background: none; - border: none; + min-height: 14; + max-height: 14; + min-width: 14; + max-width: 14; + padding: 2; + border: 1 solid #0f1621; + border-top: 1 solid #18202b; + border-bottom: 1 solid #02060f; + border-radius: 2; + background: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 rgba(255, 255, 255, 20%), stop:0.1 rgba(255, 255, 255, 2%), stop:0.9 rgba(0, 0, 0, 2%), stop:1 rgba(0, 0, 0, 40%)); } lmms--gui--TrackOperationsWidget QPushButton::menu-indicator { - image: url("resources:trackop.png"); - subcontrol-origin: padding; - subcontrol-position: center; - position: relative; - top: 1px; + image: none; } -lmms--gui--TrackOperationsWidget QPushButton::menu-indicator:pressed, -lmms--gui--TrackOperationsWidget QPushButton::menu-indicator:checked { - image: url("resources:trackop.png"); - position: relative; - top: 2px; +lmms--gui--TrackOperationsWidget QPushButton { + image: url("resources:gear.svg"); } +QPushButton#btn-mute, +QPushButton#btn-mute-inv:checked { + image: url("resources:speaker.svg"); +} + +QPushButton#btn-solo { + image: url("resources:headphones.svg"); +} + +QPushButton#btn-mute:hover, +QPushButton#btn-solo:hover, +lmms--gui--TrackOperationsWidget QPushButton:hover { + background: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 rgba(255, 255, 255, 25%), stop:0.1 rgba(255, 255, 255, 7%), stop:0.9 transparent, stop:1 rgba(0, 0, 0, 35%)); +} + +lmms--gui--TrackOperationsWidget QPushButton:pressed { + background: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 rgba(0, 0, 0, 25%), stop:0.1 transparent, stop:0.8 rgba(0, 0, 0, 15%), stop:1 rgba(0, 0, 0, 35%)); +} + +QPushButton#btn-mute-inv, +QPushButton#btn-mute:checked { + image: url("resources:speaker_slash.svg"); + border: 1 solid #890120; + background: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 #e2515b, stop:0.1 #d40237, stop:0.9 #d40237, stop:1 #900122); +} + +QPushButton#btn-solo:checked { + border: 1 solid #055f89; + background: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 #5aa8d9, stop:0.1 #2696d1, stop:0.9 #2696d1, stop:1 #16638c); +} + + /* font sizes */ lmms--gui--Sf2InstrumentView > QLabel { @@ -770,6 +822,7 @@ lmms--gui--ClipView { qproperty-textBackgroundColor: rgba(0, 0, 0, 75); qproperty-textShadowColor: rgba(0,0,0,200); qproperty-gradient: false; /* boolean property, set true to have a gradient */ + qproperty-markerColor: rgb(0, 0, 0); /* finger tip offset of cursor */ qproperty-mouseHotspotHand: 7px 2px; qproperty-mouseHotspotKnife: 0px 0px; diff --git a/data/themes/default/trackop.png b/data/themes/default/trackop.png deleted file mode 100644 index a4f90e35c..000000000 Binary files a/data/themes/default/trackop.png and /dev/null differ diff --git a/include/AudioBufferView.h b/include/AudioBufferView.h new file mode 100644 index 000000000..56fff10fa --- /dev/null +++ b/include/AudioBufferView.h @@ -0,0 +1,276 @@ +/* + * AudioBufferView.h - Non-owning views for interleaved and + * non-interleaved (planar) buffers + * + * Copyright (c) 2025 Dalton Messmer + * + * 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_BUFFER_VIEW_H +#define LMMS_AUDIO_BUFFER_VIEW_H + +#include +#include +#include + +#include "LmmsTypes.h" + +namespace lmms +{ + +//! Use when the number of channels is not known at compile time +inline constexpr auto DynamicChannelCount = static_cast(-1); + + +namespace detail { + +// For static channel count +template +class BufferViewData +{ +public: + constexpr BufferViewData() = default; + constexpr BufferViewData(const BufferViewData&) = default; + + constexpr BufferViewData(SampleT* data, proc_ch_t channels, f_cnt_t frames) noexcept + : m_data{data} + , m_frames{frames} + { + assert(channels == channelCount); + } + + constexpr BufferViewData(SampleT* data, f_cnt_t frames) noexcept + : m_data{data} + , m_frames{frames} + { + } + + constexpr auto data() const noexcept -> SampleT* { return m_data; } + constexpr auto channels() const noexcept -> proc_ch_t { return channelCount; } + constexpr auto frames() const noexcept -> f_cnt_t { return m_frames; } + +protected: + SampleT* m_data = nullptr; + f_cnt_t m_frames = 0; +}; + +// For dynamic channel count +template +class BufferViewData +{ +public: + constexpr BufferViewData() = default; + constexpr BufferViewData(const BufferViewData&) = default; + + constexpr BufferViewData(SampleT* data, proc_ch_t channels, f_cnt_t frames) noexcept + : m_data{data} + , m_channels{channels} + , m_frames{frames} + { + assert(channels != DynamicChannelCount); + } + + constexpr auto data() const noexcept -> SampleT* { return m_data; } + constexpr auto channels() const noexcept -> proc_ch_t { return m_channels; } + constexpr auto frames() const noexcept -> f_cnt_t { return m_frames; } + +protected: + SampleT* m_data = nullptr; + proc_ch_t m_channels = 0; + f_cnt_t m_frames = 0; +}; + +} // namespace detail + + +/** + * Non-owning view for multi-channel interleaved audio data + * + * TODO C++23: Use std::mdspan? + */ +template +class InterleavedBufferView : public detail::BufferViewData +{ + using Base = detail::BufferViewData; + +public: + using Base::Base; + + //! Contruct const from mutable (static channel count) + template requires (std::is_const_v && channelCount != DynamicChannelCount) + constexpr InterleavedBufferView(InterleavedBufferView, channelCount> other) noexcept + : Base{other.data(), other.frames()} + { + } + + //! Contruct const from mutable (dynamic channel count) + template requires (std::is_const_v && channelCount == DynamicChannelCount) + constexpr InterleavedBufferView(InterleavedBufferView, channelCount> other) noexcept + : Base{other.data(), other.channels(), other.frames()} + { + } + + //! Construct dynamic channel count from static + template + requires (channelCount == DynamicChannelCount && otherChannels != DynamicChannelCount) + constexpr InterleavedBufferView(InterleavedBufferView other) noexcept + : Base{other.data(), otherChannels, other.frames()} + { + } + + constexpr auto empty() const noexcept -> bool + { + return !this->m_data || this->channels() == 0 || this->m_frames == 0; + } + + //! @return the frame at the given index + constexpr auto frame(f_cnt_t index) const noexcept + { + if constexpr (channelCount == DynamicChannelCount) + { + return std::span{framePtr(index), this->channels()}; + } + else + { + return std::span{framePtr(index), this->channels()}; + } + } + + /** + * @return pointer to the frame at the given index. + * The size of the frame is `channels()`. + */ + constexpr auto framePtr(f_cnt_t index) const noexcept -> SampleT* + { + assert(index < this->m_frames); + return this->m_data + index * this->channels(); + } + + /** + * @return pointer to the frame at the given index. + * The size of the frame is `channels()`. + */ + constexpr auto operator[](f_cnt_t index) const noexcept -> SampleT* + { + return framePtr(index); + } +}; + +// Check that the std::span-like space optimization works +static_assert(sizeof(InterleavedBufferView) > sizeof(InterleavedBufferView)); +static_assert(sizeof(InterleavedBufferView) == sizeof(void*) + sizeof(f_cnt_t)); + + +/** + * Non-owning view for multi-channel non-interleaved audio data + * + * The data type is `SampleT* const*` which is a 2D array accessed as data[channel][frame index] + * where each channel's buffer contains `frames()` frames. + * + * TODO C++23: Use std::mdspan? + */ +template +class PlanarBufferView : public detail::BufferViewData +{ + using Base = detail::BufferViewData; + +public: + using Base::Base; + + //! Contruct const from mutable (static channel count) + template requires (std::is_const_v && channelCount != DynamicChannelCount) + constexpr PlanarBufferView(PlanarBufferView, channelCount> other) noexcept + : Base{other.data(), other.frames()} + { + } + + //! Contruct const from mutable (dynamic channel count) + template requires (std::is_const_v && channelCount == DynamicChannelCount) + constexpr PlanarBufferView(PlanarBufferView, channelCount> other) noexcept + : Base{other.data(), other.channels(), other.frames()} + { + } + + //! Construct dynamic channel count from static + template + requires (channelCount == DynamicChannelCount && otherChannels != DynamicChannelCount) + constexpr PlanarBufferView(PlanarBufferView other) noexcept + : Base{other.data(), otherChannels, other.frames()} + { + } + + constexpr auto empty() const noexcept -> bool + { + return !this->m_data || this->channels() == 0 || this->m_frames == 0; + } + + //! @return the buffer of the given channel + constexpr auto buffer(proc_ch_t channel) const noexcept -> std::span + { + return {bufferPtr(channel), this->m_frames}; + } + + //! @return the buffer of the given channel + template requires (channelCount != DynamicChannelCount) + constexpr auto buffer() const noexcept -> std::span + { + return {bufferPtr(), this->m_frames}; + } + + /** + * @return pointer to the buffer of the given channel. + * The size of the buffer is `frames()`. + */ + constexpr auto bufferPtr(proc_ch_t channel) const noexcept -> SampleT* + { + assert(channel < this->channels()); + assert(this->m_data != nullptr); + return this->m_data[channel]; + } + + /** + * @return pointer to the buffer of the given channel. + * The size of the buffer is `frames()`. + */ + template requires (channelCount != DynamicChannelCount) + constexpr auto bufferPtr() const noexcept -> SampleT* + { + static_assert(channel < channelCount); + assert(this->m_data != nullptr); + return this->m_data[channel]; + } + + /** + * @return pointer to the buffer of a given channel. + * The size of the buffer is `frames()`. + */ + constexpr auto operator[](proc_ch_t channel) const noexcept -> SampleT* + { + return bufferPtr(channel); + } +}; + +// Check that the std::span-like space optimization works +static_assert(sizeof(PlanarBufferView) > sizeof(PlanarBufferView)); +static_assert(sizeof(PlanarBufferView) == sizeof(void**) + sizeof(f_cnt_t)); + +} // namespace lmms + +#endif // LMMS_AUDIO_BUFFER_VIEW_H diff --git a/include/AudioDevice.h b/include/AudioDevice.h index 228b1c1aa..0a649bbe0 100644 --- a/include/AudioDevice.h +++ b/include/AudioDevice.h @@ -28,7 +28,7 @@ #include #include -#include "lmms_basics.h" +#include "LmmsTypes.h" class QThread; diff --git a/include/AudioEngine.h b/include/AudioEngine.h index 921fa1b34..04f08bb08 100644 --- a/include/AudioEngine.h +++ b/include/AudioEngine.h @@ -34,7 +34,7 @@ #include #include "AudioDevice.h" -#include "lmms_basics.h" +#include "LmmsTypes.h" #include "SampleFrame.h" #include "LocklessList.h" #include "FifoBuffer.h" @@ -50,7 +50,6 @@ class MidiClient; class AudioBusHandle; class AudioEngineWorkerThread; - constexpr fpp_t MINIMUM_BUFFER_SIZE = 32; constexpr fpp_t DEFAULT_BUFFER_SIZE = 256; constexpr fpp_t MAXIMUM_BUFFER_SIZE = 4096; @@ -61,6 +60,8 @@ constexpr int BYTES_PER_FRAME = sizeof(SampleFrame); constexpr float OUTPUT_SAMPLE_MULTIPLIER = 32767.0f; +constexpr auto SUPPORTED_SAMPLERATES = std::array{44100, 48000, 88200, 96000, 192000}; + class LMMS_EXPORT AudioEngine : public QObject { Q_OBJECT diff --git a/include/AudioEngineProfiler.h b/include/AudioEngineProfiler.h index b0d62a1dc..cee1ea0dd 100644 --- a/include/AudioEngineProfiler.h +++ b/include/AudioEngineProfiler.h @@ -29,7 +29,7 @@ #include #include -#include "lmms_basics.h" +#include "LmmsTypes.h" #include "MicroTimer.h" namespace lmms diff --git a/include/AudioJack.h b/include/AudioJack.h index e13b4a5ef..84d85a649 100644 --- a/include/AudioJack.h +++ b/include/AudioJack.h @@ -53,11 +53,6 @@ namespace lmms class MidiJack; -namespace gui -{ -class LcdSpinBox; -} - class AudioJack : public QObject, public AudioDevice { @@ -82,13 +77,10 @@ public: { public: setupWidget(QWidget* parent); - ~setupWidget() override; - void saveSettings() override; private: QLineEdit* m_clientName; - gui::LcdSpinBox* m_channels; }; private slots: @@ -96,6 +88,7 @@ private slots: private: bool initJackClient(); + void resizeInputBuffer(jack_nframes_t nframes); void startProcessing() override; void stopProcessing() override; @@ -116,7 +109,9 @@ private: std::atomic m_midiClient; std::vector m_outputPorts; + std::vector m_inputPorts; jack_default_audio_sample_t** m_tempOutBufs; + std::vector m_inputFrameBuffer; SampleFrame* m_outBuf; f_cnt_t m_framesDoneInCurBuf; diff --git a/include/AutomationClip.h b/include/AutomationClip.h index 0b49978c7..abc713869 100644 --- a/include/AutomationClip.h +++ b/include/AutomationClip.h @@ -68,7 +68,6 @@ public: using TimemapIterator = timeMap::const_iterator; AutomationClip( AutomationTrack * _auto_track ); - AutomationClip( const AutomationClip & _clip_to_copy ); ~AutomationClip() override = default; bool addObject( AutomatableModel * _obj, bool _search_dup = true ); @@ -90,7 +89,7 @@ public: void setTension( QString _new_tension ); TimePos timeMapLength() const; - void updateLength(); + void updateLength() override; TimePos putValue( const TimePos & time, @@ -196,12 +195,22 @@ public: static int quantization() { return s_quantization; } static void setQuantization(int q) { s_quantization = q; } + AutomationClip* clone() override + { + return new AutomationClip(*this); + } + + void clearObjects() { m_objects.clear(); } + public slots: void clear(); void objectDestroyed( lmms::jo_id_t ); void flipY( int min, int max ); void flipY(); - void flipX( int length = -1 ); + void flipX(int start = -1, int end = -1); + +protected: + AutomationClip( const AutomationClip & _clip_to_copy ); private: void cleanObjects(); diff --git a/include/AutomationClipView.h b/include/AutomationClipView.h index bdd2f0568..e55c44228 100644 --- a/include/AutomationClipView.h +++ b/include/AutomationClipView.h @@ -75,6 +75,8 @@ private: QStaticText m_staticTextName; void scaleTimemapToFit( float oldMin, float oldMax ); + + bool isResizableBeforeStart() override { return false; } } ; diff --git a/include/AutomationEditor.h b/include/AutomationEditor.h index eb3d229a3..c7eaca8aa 100644 --- a/include/AutomationEditor.h +++ b/include/AutomationEditor.h @@ -37,7 +37,7 @@ #include "MidiClip.h" #include "SampleClip.h" #include "TimePos.h" -#include "lmms_basics.h" +#include "LmmsTypes.h" #include "SampleThumbnail.h" class QPainter; @@ -74,6 +74,7 @@ class AutomationEditor : public QWidget, public JournallingObject Q_PROPERTY(QColor ghostNoteColor MEMBER m_ghostNoteColor) Q_PROPERTY(QColor detuningNoteColor MEMBER m_detuningNoteColor) Q_PROPERTY(QColor ghostSampleColor MEMBER m_ghostSampleColor) + Q_PROPERTY(QColor outOfBoundsShade MEMBER m_outOfBoundsShade) public: void setCurrentClip(AutomationClip * new_clip); void setGhostMidiClip(MidiClip* newMidiClip); @@ -291,6 +292,7 @@ private: QColor m_ghostNoteColor; QColor m_detuningNoteColor; QColor m_ghostSampleColor; + QColor m_outOfBoundsShade; SampleThumbnail m_sampleThumbnail; diff --git a/include/BandLimitedWave.h b/include/BandLimitedWave.h index c70b8f6eb..0c38413ef 100644 --- a/include/BandLimitedWave.h +++ b/include/BandLimitedWave.h @@ -31,7 +31,7 @@ class QString; #include "lmms_export.h" #include "interpolation.h" -#include "lmms_basics.h" +#include "LmmsTypes.h" #include "lmms_math.h" #include "Engine.h" #include "AudioEngine.h" diff --git a/include/BasicFilters.h b/include/BasicFilters.h index 51d617480..a0ab32871 100644 --- a/include/BasicFilters.h +++ b/include/BasicFilters.h @@ -36,7 +36,9 @@ #include #include -#include "lmms_basics.h" +#include "lmms_constants.h" +#include "LmmsTypes.h" + namespace lmms { @@ -207,7 +209,7 @@ public: inline float update( float s, ch_cnt_t ch ) { - if (std::abs(s) < 1.0e-10f && std::abs(m_z1[ch]) < 1.0e-10f) { return 0.0f; } + if (std::abs(s) < F_EPSILON && std::abs(m_z1[ch]) < F_EPSILON) { return 0.0f; } return m_z1[ch] = s * m_a0 + m_z1[ch] * m_b1; } @@ -593,7 +595,7 @@ public: case FilterType::Formantfilter: case FilterType::FastFormant: { - if (std::abs(_in0) < 1.0e-10f && std::abs(m_vflast[0][_chnl]) < 1.0e-10f) { return 0.0f; } // performance hack - skip processing when the numbers get too small + if (std::abs(_in0) < F_EPSILON && std::abs(m_vflast[0][_chnl]) < F_EPSILON) { return 0.0f; } // performance hack - skip processing when the numbers get too small const int os = m_type == FilterType::FastFormant ? 1 : 4; // no oversampling for fast formant for( int o = 0; o < os; ++o ) diff --git a/include/BufferManager.h b/include/BufferManager.h index 84602f121..0a39114ca 100644 --- a/include/BufferManager.h +++ b/include/BufferManager.h @@ -27,7 +27,7 @@ #define LMMS_BUFFER_MANAGER_H #include "lmms_export.h" -#include "lmms_basics.h" +#include "LmmsTypes.h" namespace lmms { diff --git a/include/CPULoadWidget.h b/include/CPULoadWidget.h index bed10b05e..17a046e37 100644 --- a/include/CPULoadWidget.h +++ b/include/CPULoadWidget.h @@ -31,7 +31,7 @@ #include #include -#include "lmms_basics.h" +#include "LmmsTypes.h" namespace lmms::gui diff --git a/include/Clip.h b/include/Clip.h index a520ad4e4..706b982c8 100644 --- a/include/Clip.h +++ b/include/Clip.h @@ -100,12 +100,28 @@ public: * resized by clicking and dragging its edge. * */ - inline void setAutoResize( const bool r ) + inline void setResizable( const bool r ) + { + m_resizable = r; + } + + inline const bool getResizable() const + { + return m_resizable; + } + + /*! \brief Set whether a clip has been resized yet by the user or the knife tool. + * + * If a clip has been resized previously, it will not automatically + * resize when editing it. + * + */ + void setAutoResize(const bool r) { m_autoResize = r; } - inline const bool getAutoResize() const + bool getAutoResize() const { return m_autoResize; } @@ -115,6 +131,7 @@ public: virtual void movePosition( const TimePos & pos ); virtual void changeLength( const TimePos & length ); + virtual void updateLength() {}; virtual gui::ClipView * createView( gui::TrackView * tv ) = 0; @@ -137,6 +154,12 @@ public: // Will copy the state of a clip to another clip static void copyStateTo( Clip *src, Clip *dst ); + /** + * Creates a copy of this clip + * @return pointer to the new clip object + */ + virtual Clip* clone() = 0; + public slots: void toggleMute(); @@ -147,6 +170,8 @@ signals: void destroyedClip(); void colorChanged(); +protected: + Clip(const Clip& other); private: Track * m_track; @@ -158,7 +183,8 @@ private: BoolModel m_mutedModel; BoolModel m_soloModel; - bool m_autoResize; + bool m_resizable = true; + bool m_autoResize = true; bool m_selectViewOnCreate; diff --git a/include/ClipView.h b/include/ClipView.h index 14898db65..6e904b856 100644 --- a/include/ClipView.h +++ b/include/ClipView.h @@ -63,6 +63,7 @@ class ClipView : public selectableObject, public ModelView Q_PROPERTY( QColor textShadowColor READ textShadowColor WRITE setTextShadowColor ) Q_PROPERTY( QColor patternClipBackground READ patternClipBackground WRITE setPatternClipBackground ) Q_PROPERTY( bool gradient READ gradient WRITE setGradient ) + Q_PROPERTY(QColor markerColor READ markerColor WRITE setMarkerColor) // We have to use a QSize here because using QPoint isn't supported. // width -> x, height -> y Q_PROPERTY( QSize mouseHotspotHand MEMBER m_mouseHotspotHand ) @@ -94,6 +95,7 @@ public: QColor textBackgroundColor() const; QColor textShadowColor() const; QColor patternClipBackground() const; + QColor markerColor() const; bool gradient() const; void setMutedColor( const QColor & c ); void setMutedBackgroundColor( const QColor & c ); @@ -103,6 +105,7 @@ public: void setTextShadowColor( const QColor & c ); void setPatternClipBackground(const QColor& c); void setGradient( const bool & b ); + void setMarkerColor(const QColor& c); // access needsUpdate member variable bool needsUpdate(); @@ -121,10 +124,8 @@ public: // some metadata to be written to the clipboard. static void remove( QVector clipvs ); static void toggleMute( QVector clipvs ); - static void mergeClips(QVector clipvs); - // Returns true if selection can be merged and false if not - static bool canMergeSelection(QVector clipvs); + void toggleSelectedAutoResize(); QColor getColorForDisplay( QColor ); @@ -147,8 +148,7 @@ protected: Cut, Copy, Paste, - Mute, - Merge + Mute }; TrackView * m_trackView; @@ -176,7 +176,7 @@ protected: } bool unquantizedModHeld( QMouseEvent * me ); - TimePos quantizeSplitPos( TimePos, bool shiftMode ); + TimePos quantizeSplitPos(TimePos); float pixelsPerBar(); @@ -224,6 +224,7 @@ private: QColor m_textShadowColor; QColor m_patternClipBackground; bool m_gradient; + QColor m_markerColor; QSize m_mouseHotspotHand; // QSize must be used because QPoint QSize m_mouseHotspotKnife; // isn't supported by property system QCursor m_cursorHand; @@ -244,8 +245,24 @@ private: TimePos draggedClipPos( QMouseEvent * me ); int knifeMarkerPos( QMouseEvent * me ); 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; }; + + //! Returns whether the user can left-resize this clip so that the start of the clip bounds is before the start of the clip content. + virtual bool isResizableBeforeStart() { return true; }; + /** + * Split this Clip into two clips + * @param pos the position of the split, relative to the start of the clip + * @return true if the clip could be split + */ + bool splitClip(const TimePos pos); + /** + * Destructively split this Clip into two clips. If the clip type does not implement this feature, it will default to normal splitting. + * @param pos the position of the split, relative to the start of the clip + * @return true if the clip could be split + */ + virtual bool destructiveSplitClip(const TimePos pos) + { + return splitClip(pos); + } void updateCursor(QMouseEvent * me); } ; diff --git a/include/ComboBoxModel.h b/include/ComboBoxModel.h index 7037495ea..904629ee2 100644 --- a/include/ComboBoxModel.h +++ b/include/ComboBoxModel.h @@ -45,6 +45,7 @@ public: bool isDefaultConstructed = false ) : IntModel( 0, 0, 0, parent, displayName, isDefaultConstructed ) { + setJournalling(false); } void addItem( QString item, std::unique_ptr loader = nullptr ); diff --git a/include/ConfigManager.h b/include/ConfigManager.h index 3cba834e1..bf98f5999 100644 --- a/include/ConfigManager.h +++ b/include/ConfigManager.h @@ -214,6 +214,8 @@ public: return m_recentlyOpenedProjects; } + const QStringList& favoriteItems() { return m_favoriteItems; } + QString localeDir() const { return m_dataDir + LOCALE_PATH; @@ -240,6 +242,10 @@ public: void addRecentlyOpenedProject(const QString & _file); + void addFavoriteItem(const QString& item); + void removeFavoriteItem(const QString& item); + bool isFavoriteItem(const QString& item); + QString value(const QString& cls, const QString& attribute, const QString& defaultVal = "") const; void setValue(const QString & cls, const QString & attribute, @@ -265,6 +271,7 @@ public: signals: void valueChanged( QString cls, QString attribute, QString value ); + void favoritesChanged(); private: static ConfigManager * s_instanceOfMe; @@ -299,6 +306,7 @@ private: QString m_version; unsigned int m_configVersion; QStringList m_recentlyOpenedProjects; + QStringList m_favoriteItems; using stringPairVector = std::vector>; using settingsMap = QMap; diff --git a/include/ControllerRackView.h b/include/ControllerRackView.h index 93d1e8438..436618303 100644 --- a/include/ControllerRackView.h +++ b/include/ControllerRackView.h @@ -29,7 +29,6 @@ #include #include "SerializingObject.h" -#include "lmms_basics.h" class QPushButton; diff --git a/include/Controls.h b/include/Controls.h index 5ed19027e..243f77690 100644 --- a/include/Controls.h +++ b/include/Controls.h @@ -80,7 +80,7 @@ public: FloatModel* model() override; AutomatableModelView* modelView() override; - KnobControl(QWidget* parent = nullptr); + KnobControl(const QString& text, QWidget* parent = nullptr); ~KnobControl() override = default; }; diff --git a/include/CustomTextKnob.h b/include/CustomTextKnob.h index 31a58415e..34434f3ae 100644 --- a/include/CustomTextKnob.h +++ b/include/CustomTextKnob.h @@ -36,9 +36,7 @@ class LMMS_EXPORT CustomTextKnob : public Knob protected: inline void setHintText( const QString & _txt_before, const QString & _txt_after ) {} // inaccessible public: - 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 + CustomTextKnob( KnobType _knob_num, const QString& label, QWidget * _parent = nullptr, const QString & _name = QString(), const QString & _value_text = QString() ); CustomTextKnob( const Knob& other ) = delete; diff --git a/include/Delay.h b/include/Delay.h index 8ead1c37b..199c592ce 100644 --- a/include/Delay.h +++ b/include/Delay.h @@ -28,7 +28,7 @@ #include -#include "lmms_basics.h" +#include "LmmsTypes.h" namespace lmms { diff --git a/include/DetuningHelper.h b/include/DetuningHelper.h index da8eb5983..b76708082 100644 --- a/include/DetuningHelper.h +++ b/include/DetuningHelper.h @@ -39,6 +39,10 @@ public: InlineAutomation() { } + DetuningHelper(const DetuningHelper& _copy) : + InlineAutomation(_copy) + { + } ~DetuningHelper() override = default; diff --git a/include/DrumSynth.h b/include/DrumSynth.h index 3b418abd6..2894817fe 100644 --- a/include/DrumSynth.h +++ b/include/DrumSynth.h @@ -28,7 +28,7 @@ #include -#include "lmms_basics.h" +#include "LmmsTypes.h" class QString; diff --git a/include/DspEffectLibrary.h b/include/DspEffectLibrary.h index 656d5b1dd..8b3060334 100644 --- a/include/DspEffectLibrary.h +++ b/include/DspEffectLibrary.h @@ -28,7 +28,7 @@ #include #include "lmms_math.h" -#include "lmms_basics.h" +#include "LmmsTypes.h" #include "SampleFrame.h" namespace lmms::DspEffectLibrary diff --git a/include/Editor.h b/include/Editor.h index a5b667166..46caab538 100644 --- a/include/Editor.h +++ b/include/Editor.h @@ -57,6 +57,13 @@ protected: DropToolBar * addDropToolBar(QWidget * parent, Qt::ToolBarArea whereToAdd, QString const & windowTitle); void closeEvent(QCloseEvent * event) override; + void keyPressEvent(QKeyEvent *ke) override; +public slots: + //! Called by pressing the space key. Plays or stops. + void togglePlayStop(); + //! Called by pressing shift+space. Toggles pause state. + void togglePause(); + protected slots: virtual void play() {} virtual void record() {} @@ -65,12 +72,6 @@ protected slots: virtual void stop() {} private slots: - /// Called by pressing the space key. Plays or stops. - void togglePlayStop(); - - /// Called by pressing shift+space. Toggles pause state. - void togglePause(); - void toggleMaximize(); signals: diff --git a/include/EffectRackView.h b/include/EffectRackView.h index fec627a56..f166612e2 100644 --- a/include/EffectRackView.h +++ b/include/EffectRackView.h @@ -30,7 +30,6 @@ #include "EffectChain.h" #include "ModelView.h" -#include "lmms_basics.h" class QScrollArea; class QVBoxLayout; diff --git a/include/Engine.h b/include/Engine.h index 7e19e2e84..cc37ecee8 100644 --- a/include/Engine.h +++ b/include/Engine.h @@ -30,7 +30,7 @@ #include "lmmsconfig.h" #include "lmms_export.h" -#include "lmms_basics.h" +#include "LmmsTypes.h" namespace lmms { diff --git a/include/EnvelopeAndLfoParameters.h b/include/EnvelopeAndLfoParameters.h index 50bfdf787..34cbae5ec 100644 --- a/include/EnvelopeAndLfoParameters.h +++ b/include/EnvelopeAndLfoParameters.h @@ -32,7 +32,7 @@ #include "AutomatableModel.h" #include "SampleBuffer.h" #include "TempoSyncKnobModel.h" -#include "lmms_basics.h" +#include "LmmsTypes.h" namespace lmms { diff --git a/include/Fader.h b/include/Fader.h index 9d6e21590..27e5e07a1 100644 --- a/include/Fader.h +++ b/include/Fader.h @@ -171,6 +171,7 @@ private: QElapsedTimer m_lastPeakTimer_R; QPixmap m_knob {embed::getIconPixmap("fader_knob")}; + QSize m_knobSize; /** * @brief Stores the offset to the knob center when the user drags the fader knob diff --git a/include/FileBrowser.h b/include/FileBrowser.h index 6c10ee763..71a878fc1 100644 --- a/include/FileBrowser.h +++ b/include/FileBrowser.h @@ -61,19 +61,22 @@ class FileBrowser : public SideBarWidget { Q_OBJECT public: + enum class Type + { + Normal, + Favorites + }; + /** - Create a file browser side bar widget - @param directories '*'-separated list of directories to search for. - If a directory of factory files should be in the list it - must be the last one (for the factory files delimiter to work) - @param filter Filter as used in QDir::match - @param recurse *to be documented* - */ - FileBrowser( const QString & directories, const QString & filter, - const QString & title, const QPixmap & pm, - QWidget * parent, bool dirs_as_items = false, - const QString& userDir = "", - const QString& factoryDir = ""); + Create a file browser side bar widget + @param directories '*'-separated list of directories to search for. + If a directory of factory files should be in the list it + must be the last one (for the factory files delimiter to work) + @param filter Filter as used in QDir::match + @param recurse *to be documented* + */ + FileBrowser(Type type, const QString& directories, const QString& filter, const QString& title, const QPixmap& pm, + QWidget* parent, bool dirs_as_items = false, const QString& userDir = "", const QString& factoryDir = ""); ~FileBrowser() override = default; @@ -90,6 +93,7 @@ public: }; return s_excludedPaths; } + static QDir::Filters dirFilters() { return QDir::AllDirs | QDir::Files | QDir::NoDotAndDotDot | QDir::Hidden; } static QDir::SortFlags sortFlags() { return QDir::LocaleAware | QDir::DirsFirst | QDir::Name | QDir::IgnoreCase; } @@ -101,7 +105,7 @@ private slots: private: void keyPressEvent( QKeyEvent * ke ) override; - void addItems( const QString & path ); + void addItems(const QString & path); void saveDirectoriesStates(); void restoreDirectoriesStates(); @@ -117,6 +121,7 @@ private: FileBrowserTreeWidget * m_searchTreeWidget; QLineEdit * m_filterEdit; + Type m_type; std::shared_ptr m_currentSearch; QProgressBar* m_searchIndicator = nullptr; @@ -216,8 +221,7 @@ public: { path += QDir::separator(); } - return( QDir::cleanPath( path + text( 0 ) ) + - QDir::separator() ); + return QDir::cleanPath(path + text(0)); } inline void addDirectory( const QString & dir ) diff --git a/include/FileRevealer.h b/include/FileRevealer.h index feb6d1223..46c52cd8c 100644 --- a/include/FileRevealer.h +++ b/include/FileRevealer.h @@ -22,12 +22,12 @@ * */ -#ifndef LMMS_FILE_REVEALER_H -#define LMMS_FILE_REVEALER_H +#ifndef LMMS_GUI_FILE_REVEALER_H +#define LMMS_GUI_FILE_REVEALER_H #include -namespace lmms { +namespace lmms::gui { /** * @class FileRevealer @@ -73,5 +73,6 @@ protected: static bool supportsArg(const QString& command, const QString& arg); }; -} // namespace lmms -#endif // LMMS_FILE_REVEALER_H +} // namespace lmms::gui + +#endif // LMMS_GUI_FILE_REVEALER_H diff --git a/include/FontHelper.h b/include/FontHelper.h index ccef24775..c1c0ee665 100644 --- a/include/FontHelper.h +++ b/include/FontHelper.h @@ -22,8 +22,8 @@ * */ -#ifndef LMMS_FONT_HELPER_H -#define LMMS_FONT_HELPER_H +#ifndef LMMS_GUI_FONT_HELPER_H +#define LMMS_GUI_FONT_HELPER_H #include #include @@ -44,4 +44,4 @@ inline QFont adjustedToPixelSize(QFont font, int size) } // namespace lmms::gui -#endif // LMMS_FONT_HELPER_H +#endif // LMMS_GUI_FONT_HELPER_H diff --git a/include/Graph.h b/include/Graph.h index cc87b913e..4cb3af512 100644 --- a/include/Graph.h +++ b/include/Graph.h @@ -32,7 +32,7 @@ #include "Model.h" #include "ModelView.h" -#include "lmms_basics.h" +#include "LmmsTypes.h" namespace lmms { diff --git a/include/GuiApplication.h b/include/GuiApplication.h index 3a0851499..02fb6ed67 100644 --- a/include/GuiApplication.h +++ b/include/GuiApplication.h @@ -26,6 +26,7 @@ #define LMMS_GUI_GUI_APPLICATION_H #include +#include #include "lmms_export.h" #include "lmmsconfig.h" @@ -53,10 +54,14 @@ public: ~GuiApplication() override; static GuiApplication* instance(); + static void sigintHandler(int); + static bool isWayland(); #ifdef LMMS_BUILD_WIN32 static QFont getWin32SystemFont(); #endif + void createSocketNotifier(); + MainWindow* mainWindow() { return m_mainWindow; } MixerView* mixerView() { return m_mixerView; } SongEditorWindow* songEditor() { return m_songEditor; } @@ -67,11 +72,15 @@ public: AutomationEditorWindow* automationEditor() { return m_automationEditor; } ControllerRackView* getControllerRackView() { return m_controllerRackView; } + //! File descriptors for unix socketpair, used to receive SIGINT + static inline int s_sigintFd[2]; + public slots: void displayInitProgress(const QString &msg); private slots: void childDestroyed(QObject *obj); + void sigintOccurred(); private: static GuiApplication* s_instance; @@ -86,6 +95,7 @@ private: MicrotunerConfig* m_microtunerConfig; ControllerRackView* m_controllerRackView; QLabel* m_loadingProgressLabel; + QSocketNotifier* m_sigintNotifier; }; // Short-hand function diff --git a/include/InlineAutomation.h b/include/InlineAutomation.h index 3e27e137b..32241d451 100644 --- a/include/InlineAutomation.h +++ b/include/InlineAutomation.h @@ -27,27 +27,28 @@ #include "AutomationNode.h" #include "AutomationClip.h" -#include "shared_object.h" namespace lmms { -class InlineAutomation : public FloatModel, public sharedObject +class InlineAutomation : public FloatModel { public: InlineAutomation() : - FloatModel(), - sharedObject(), - m_autoClip( nullptr ) + FloatModel() { } + InlineAutomation(const InlineAutomation& _copy) : + FloatModel(_copy.value(), _copy.minValue(), _copy.maxValue(), _copy.step()), + m_autoClip(_copy.m_autoClip->clone()) + { + m_autoClip->clearObjects(); + m_autoClip->addObject(this); + } + ~InlineAutomation() override { - if( m_autoClip ) - { - delete m_autoClip; - } } virtual float defaultValue() const = 0; @@ -81,10 +82,10 @@ public: { if( m_autoClip == nullptr ) { - m_autoClip = new AutomationClip( nullptr ); + m_autoClip = std::make_unique(nullptr); m_autoClip->addObject( this ); } - return m_autoClip; + return m_autoClip.get(); } void saveSettings( QDomDocument & _doc, QDomElement & _parent ) override; @@ -92,7 +93,7 @@ public: private: - AutomationClip * m_autoClip; + std::unique_ptr m_autoClip; } ; diff --git a/include/Instrument.h b/include/Instrument.h index 3f701f12e..21353f725 100644 --- a/include/Instrument.h +++ b/include/Instrument.h @@ -30,7 +30,7 @@ #include "Flags.h" #include "lmms_export.h" -#include "lmms_basics.h" +#include "LmmsTypes.h" #include "Plugin.h" #include "TimePos.h" diff --git a/include/InstrumentTrackWindow.h b/include/InstrumentTrackWindow.h index 5d26ba9a2..6f0245875 100644 --- a/include/InstrumentTrackWindow.h +++ b/include/InstrumentTrackWindow.h @@ -27,8 +27,8 @@ #include +#include "AutomatableButton.h" #include "ModelView.h" -#include "PixmapButton.h" #include "SerializingObject.h" #include "PluginView.h" @@ -148,8 +148,8 @@ private: Knob * m_volumeKnob; Knob * m_panningKnob; Knob * m_pitchKnob; - PixmapButton *m_muteBtn; - PixmapButton *m_soloBtn; + AutomatableButton* m_muteBtn; + AutomatableButton* m_soloBtn; QLabel * m_pitchLabel; LcdSpinBox* m_pitchRangeSpinBox; QLabel * m_pitchRangeLabel; diff --git a/include/JournallingObject.h b/include/JournallingObject.h index f3e134f34..bc6a89076 100644 --- a/include/JournallingObject.h +++ b/include/JournallingObject.h @@ -27,7 +27,7 @@ #include -#include "lmms_basics.h" +#include "LmmsTypes.h" #include "SerializingObject.h" namespace lmms diff --git a/include/Knob.h b/include/Knob.h index 3c3339a6f..5ec8fd70f 100644 --- a/include/Knob.h +++ b/include/Knob.h @@ -77,11 +77,65 @@ class LMMS_EXPORT Knob : public FloatModelEditorBase void onKnobNumUpdated(); //!< to be called when you updated @a m_knobNum public: + /** + * @brief Determines how the label of the knob is rendered. + * + * Labels can be rendered using the font that is set for the knob or using a + * font with a fixed size which is determined by SMALL_FONT_SIZE. + */ + enum class LabelRendering + { + /** + * @brief Renders the label using the font that is set for the widget. + * + * The space that's needed for the label is determined using the font metrics of the knob's font. + */ + WidgetFont, + + /** + * @brief Renders the labels in legacy mode. This uses a fixed font size and does not adhere + * to the font size that's set for the widget's font. + * + * @deprecated Do not use this mode in new code as it is considered deprecated and might be removed in the future. + */ + LegacyFixedFontSize + }; + + /** + * @brief Construct a Knob with the given style and no label. + * + * @param _knob_num Style of the knob + * @param _parent Parent widget + * @param _name Object name of the widget + */ Knob( KnobType _knob_num, QWidget * _parent = nullptr, const QString & _name = QString() ); + + /** + * @brief Construct a Knob with the given style and label text. + * + * @param knobNum Style of the knob + * @param labelText Text for the label + * @param parent Parent widget + * @param labelRendering Determines if the label uses the widget font or a font with a fixed size of 12 pixels (LegacyFixedFontSize). The default is to use the widget font. + * @param name Object name of the widget + */ + Knob(KnobType knobNum, const QString& labelText, QWidget* parent = nullptr, LabelRendering labelRendering = LabelRendering::WidgetFont, const QString& name = QString()); + + /** + * @brief Constructs a knob with a label font in the pixel size. + * + * @param knobNum Style of the knob + * @param labelText Text for the label + * @param labelPixelSize Pixel size for the label + * @param parent Parent widget + * @param name Object name of the widget + */ + Knob(KnobType knobNum, const QString& labelText, int labelPixelSize, QWidget* parent, const QString& name = QString()); + Knob( QWidget * _parent = nullptr, const QString & _name = QString() ); //!< default ctor + Knob( const Knob& other ) = delete; - void setLabel( const QString & txt ); void setHtmlLabel( const QString &htmltxt ); void setTotalAngle( float angle ); @@ -113,15 +167,44 @@ public: protected: - void paintEvent( QPaintEvent * _me ) override; + void setLabel(const QString& txt); + + void paintEvent(QPaintEvent*) override; void changeEvent(QEvent * ev) override; + /*! + * Affects how the label of the knob is rendered. + * + * The default mode returns false. The height of the label text is taken into account when a new fixed + * size is computed for the Knob. When the label text is painted the descent of the font is used to + * compute the base line. The default mode returns false. + * + * Enabling fixed font size rendering mode leads to the following behavior: + * * The height of the label is not taken into account when the new fixed height of the Knob is computed. + * Instead a fixed size of 10 is added for the label. + * * When the knob is painted the baseline of the font is always set to 2 pixels away from the lower side + * of the Knob's rectangle. + * * The label is always rendered with a size of SMALL_FONT_SIZE. + */ + bool fixedFontSizeLabelRendering() const { return m_fixedFontSizeLabelRendering; } + + /*! + * Set the button to legacy rendering mode which uses a fixed font size and that does not take the size + * of the widget's font into account. + * + * This can be thought of as a legacy mode which reinstates the old behavior of the knob. + * + * @see fixedFontSizeLabelRendering(). + */ + void setFixedFontSizeLabelRendering(); + private: QLineF calculateLine( const QPointF & _mid, float _radius, float _innerRadius = 1) const; void drawKnob( QPainter * _p ); + void drawLabel(QPainter& p); bool updateAngle(); int angleFromValue( float value, float minValue, float maxValue, float totalAngle ) const @@ -129,7 +212,10 @@ private: return static_cast( ( value - 0.5 * ( minValue + maxValue ) ) / ( maxValue - minValue ) * m_totalAngle ) % 360; } + void updateFixedSize(); + QString m_label; + bool m_fixedFontSizeLabelRendering = false; bool m_isHtmlLabel; QTextDocument* m_tdRenderer; diff --git a/include/LadspaManager.h b/include/LadspaManager.h index 1a3360231..0d2defafd 100644 --- a/include/LadspaManager.h +++ b/include/LadspaManager.h @@ -36,7 +36,7 @@ #include "lmms_export.h" -#include "lmms_basics.h" +#include "LmmsTypes.h" namespace lmms diff --git a/include/LfoController.h b/include/LfoController.h index 01b4b1862..e884cd5e4 100644 --- a/include/LfoController.h +++ b/include/LfoController.h @@ -31,8 +31,8 @@ #include "AutomatableModel.h" #include "Controller.h" #include "ControllerDialog.h" +#include "SampleBuffer.h" #include "TempoSyncKnobModel.h" -#include "Oscillator.h" namespace lmms { diff --git a/include/debug.h b/include/LmmsCommonMacros.h similarity index 64% rename from include/debug.h rename to include/LmmsCommonMacros.h index 7cf51acaa..9840240f9 100644 --- a/include/debug.h +++ b/include/LmmsCommonMacros.h @@ -1,8 +1,8 @@ /* - * debug.h - header file to be included for debugging purposes - * - * Copyright (c) 2004-2008 Tobias Doerffel + * LmmsCommonMacros.h - defines some common macros used in the codebase * + * Copyright (c) 2025 Roshan M R (Ross Maxx) + * * This file is part of LMMS - https://lmms.io * * This program is free software; you can redistribute it and/or @@ -22,20 +22,15 @@ * */ -#ifndef LMMS_DEBUG_H -#define LMMS_DEBUG_H +#ifndef LMMS_COMMON_MACROS_H +#define LMMS_COMMON_MACROS_H -#include "lmmsconfig.h" +namespace lmms +{ -// Define standard macro NDEBUG when building without debug flag to make sure asserts become no-ops. -#ifndef LMMS_DEBUG -#ifndef NDEBUG - #define NDEBUG -#endif -#endif // LMMS_DEBUG +#define LMMS_STRINGIFY(s) LMMS_STR(s) // a macro used to stringify the plugin name +#define LMMS_STR(PN) #PN -#include -#include +} // namespace lmms - -#endif // LMMS_DEBUG_H +#endif // LMMS_COMMON_MACROS_H diff --git a/include/lmms_basics.h b/include/LmmsTypes.h similarity index 50% rename from include/lmms_basics.h rename to include/LmmsTypes.h index ea9371603..c348a85bf 100644 --- a/include/lmms_basics.h +++ b/include/LmmsTypes.h @@ -1,5 +1,5 @@ /* - * lmms_basics.h - typedefs for common types that are used in the whole app + * LmmsTypes.h - typedefs for common types that are used in the whole app * * Copyright (c) 2004-2009 Tobias Doerffel * @@ -26,48 +26,31 @@ #define LMMS_TYPES_H #include - -#include "lmmsconfig.h" - #include - namespace lmms { -using bar_t = int32_t; -using tick_t = int32_t; -using volume_t = uint8_t; -using panning_t = int8_t; +using bar_t = std::int32_t; +using tick_t = std::int32_t; +using volume_t = std::uint8_t; +using panning_t = std::int8_t; -using sample_t = float; // standard sample-type -using int_sample_t = int16_t; // 16-bit-int-sample +using sample_t = float; // standard sample-type +using int_sample_t = std::int16_t; // 16-bit-int-sample -using sample_rate_t = uint32_t; // sample-rate -using fpp_t = size_t; // frames per period (0-16384) -using f_cnt_t = size_t; // standard frame-count -using ch_cnt_t = uint8_t; // channel-count (0-DEFAULT_CHANNELS) -using bpm_t = uint16_t; // tempo (MIN_BPM to MAX_BPM) -using bitrate_t = uint16_t; // bitrate in kbps -using mix_ch_t = uint16_t; // Mixer-channel (0 to MAX_CHANNEL) +using sample_rate_t = std::uint32_t; // sample-rate +using fpp_t = std::size_t; // frames per period (0-16384) +using f_cnt_t = std::size_t; // standard frame-count +using ch_cnt_t = std::uint8_t; // channel-count (0-DEFAULT_CHANNELS) +using bpm_t = std::uint16_t; // tempo (MIN_BPM to MAX_BPM) +using bitrate_t = std::uint16_t; // bitrate in kbps +using mix_ch_t = std::uint16_t; // Mixer-channel (0 to MAX_CHANNEL) +using track_ch_t = std::uint16_t; // track channel index/count (0-256) +using proc_ch_t = std::uint16_t; // audio processor channel index/count -using jo_id_t = uint32_t; // (unique) ID of a journalling object - - -constexpr ch_cnt_t DEFAULT_CHANNELS = 2; - -constexpr char LADSPA_PATH_SEPERATOR = -#ifdef LMMS_BUILD_WIN32 -';'; -#else -':'; -#endif - - - -#define LMMS_STRINGIFY(s) LMMS_STR(s) -#define LMMS_STR(PN) #PN +using jo_id_t = std::uint32_t; // (unique) ID of a journalling object } // namespace lmms diff --git a/include/LocklessRingBuffer.h b/include/LocklessRingBuffer.h index ec3f4fc52..45dab858f 100644 --- a/include/LocklessRingBuffer.h +++ b/include/LocklessRingBuffer.h @@ -30,7 +30,7 @@ #include -#include "lmms_basics.h" +#include "LmmsTypes.h" namespace lmms { diff --git a/include/Lv2Ports.h b/include/Lv2Ports.h index 1b2986938..8bbf7b4d2 100644 --- a/include/Lv2Ports.h +++ b/include/Lv2Ports.h @@ -34,7 +34,7 @@ #include #include "Flags.h" -#include "lmms_basics.h" +#include "LmmsTypes.h" #include "PluginIssue.h" diff --git a/include/MainWindow.h b/include/MainWindow.h index 1330de8c5..d2efd4ac0 100644 --- a/include/MainWindow.h +++ b/include/MainWindow.h @@ -46,6 +46,7 @@ class ConfigManager; namespace gui { +class FileBrowser; class PluginView; class SubWindow; class ToolButton; diff --git a/include/Midi.h b/include/Midi.h index 21db021b3..d68dda318 100644 --- a/include/Midi.h +++ b/include/Midi.h @@ -25,8 +25,6 @@ #ifndef LMMS_MIDI_H #define LMMS_MIDI_H -#include "lmms_basics.h" - namespace lmms { diff --git a/include/MidiClip.h b/include/MidiClip.h index f3150ba6f..076ec93b0 100644 --- a/include/MidiClip.h +++ b/include/MidiClip.h @@ -53,12 +53,11 @@ public: } ; MidiClip( InstrumentTrack* instrumentTrack ); - MidiClip( const MidiClip& other ); ~MidiClip() override; void init(); - void updateLength(); + void updateLength() override; // note management Note * addNote( const Note & _new_note, const bool _quant_pos = true ); @@ -79,6 +78,9 @@ public: Note * addStepNote( int step ); void setStep( int step, bool enabled ); + //! Horizontally flip the positions of the given notes. + void reverseNotes(const NoteVector& notes); + // Split the list of notes on the given position void splitNotes(const NoteVector& notes, TimePos pos); @@ -114,6 +116,11 @@ public: gui::ClipView * createView( gui::TrackView * _tv ) override; + MidiClip* clone() override + { + return new MidiClip(*this); + } + using Model::dataChanged; @@ -124,6 +131,7 @@ public slots: void clear(); protected: + MidiClip( const MidiClip& other ); void updatePatternTrack(); protected slots: diff --git a/include/MidiClipView.h b/include/MidiClipView.h index 4285bf9da..9f1894755 100644 --- a/include/MidiClipView.h +++ b/include/MidiClipView.h @@ -63,6 +63,11 @@ public: QColor const & getMutedNoteBorderColor() const { return m_mutedNoteBorderColor; } void setMutedNoteBorderColor(QColor const & color) { m_mutedNoteBorderColor = color; } + // Returns true if selection can be merged and false if not + static bool canMergeSelection(QVector clipvs); + static void mergeClips(QVector clipvs); + static void bulkClearNotesOutOfBounds(QVector clipvs); + public slots: lmms::MidiClip* getMidiClip(); void update() override; @@ -76,6 +81,7 @@ protected slots: void resetName(); void changeName(); void transposeSelection(); + void clearNotesOutOfBounds(); protected: @@ -103,6 +109,10 @@ private: QStaticText m_staticTextName; bool m_legacySEPattern; + + bool isResizableBeforeStart() override { return false; } + + bool destructiveSplitClip(const TimePos pos) override; } ; diff --git a/include/MixHelpers.h b/include/MixHelpers.h index a55ad6058..3b0ecf968 100644 --- a/include/MixHelpers.h +++ b/include/MixHelpers.h @@ -25,7 +25,7 @@ #ifndef LMMS_MIX_HELPERS_H #define LMMS_MIX_HELPERS_H -#include "lmms_basics.h" +#include "LmmsTypes.h" namespace lmms { diff --git a/include/MixerChannelView.h b/include/MixerChannelView.h index 3d5f4ffb6..ed0d4f1ff 100644 --- a/include/MixerChannelView.h +++ b/include/MixerChannelView.h @@ -61,7 +61,7 @@ public: void contextMenuEvent(QContextMenuEvent*) override; void mousePressEvent(QMouseEvent*) override; void mouseDoubleClickEvent(QMouseEvent*) override; - bool eventFilter(QObject* dist, QEvent* event) override; + void keyPressEvent(QKeyEvent* ke) override; void reset(); int channelIndex() const { return m_channelIndex; } @@ -115,8 +115,8 @@ private: QLineEdit* m_renameLineEdit; QGraphicsView* m_renameLineEditView; QLabel* m_sendArrow; - PixmapButton* m_muteButton; - PixmapButton* m_soloButton; + AutomatableButton* m_muteButton; + AutomatableButton* m_soloButton; PeakIndicator* m_peakIndicator = nullptr; Fader* m_fader; EffectRackView* m_effectRackView; diff --git a/include/Note.h b/include/Note.h index 08cbce3db..7f81f7d13 100644 --- a/include/Note.h +++ b/include/Note.h @@ -26,6 +26,7 @@ #ifndef LMMS_NOTE_H #define LMMS_NOTE_H +#include #include #include @@ -103,10 +104,15 @@ public: int key = DefaultKey, volume_t volume = DefaultVolume, panning_t panning = DefaultPanning, - DetuningHelper * detuning = nullptr ); + std::shared_ptr detuning = nullptr); Note( const Note & note ); ~Note() override; + Note& operator=(const Note& note); + + //! Performs a deep copy and returns an owning raw pointer + Note* clone() const; + // Note types enum class Type { @@ -234,10 +240,8 @@ public: static TimePos quantized( const TimePos & m, const int qGrid ); - DetuningHelper * detuning() const - { - return m_detuning; - } + const std::shared_ptr& detuning() const { return m_detuning; } + bool hasDetuningInfo() const; bool withinRange(int tickStart, int tickEnd) const; @@ -262,7 +266,7 @@ private: panning_t m_panning; TimePos m_length; TimePos m_pos; - DetuningHelper * m_detuning; + std::shared_ptr m_detuning; Type m_type = Type::Regular; }; diff --git a/include/Oscillator.h b/include/Oscillator.h index 13d8264be..84e9264c9 100644 --- a/include/Oscillator.h +++ b/include/Oscillator.h @@ -34,7 +34,6 @@ #include "Engine.h" #include "lmms_math.h" -#include "lmmsconfig.h" #include "AudioEngine.h" #include "OscillatorConstants.h" #include "SampleBuffer.h" diff --git a/include/OscillatorConstants.h b/include/OscillatorConstants.h index 85ae38fe8..5aaab6f7a 100644 --- a/include/OscillatorConstants.h +++ b/include/OscillatorConstants.h @@ -28,7 +28,7 @@ #include -#include "lmms_basics.h" +#include "LmmsTypes.h" namespace lmms::OscillatorConstants { diff --git a/include/Oscilloscope.h b/include/Oscilloscope.h index 1e3c52b1e..8675cea33 100644 --- a/include/Oscilloscope.h +++ b/include/Oscilloscope.h @@ -28,7 +28,7 @@ #include #include -#include "lmms_basics.h" +#include "LmmsTypes.h" namespace lmms { diff --git a/include/OutputSettings.h b/include/OutputSettings.h index 8a7ebc993..283826543 100644 --- a/include/OutputSettings.h +++ b/include/OutputSettings.h @@ -26,7 +26,7 @@ #ifndef LMMS_OUTPUT_SETTINGS_H #define LMMS_OUTPUT_SETTINGS_H -#include "lmms_basics.h" +#include "LmmsTypes.h" namespace lmms { diff --git a/include/PathUtil.h b/include/PathUtil.h index 9b410d014..9bc71c1a1 100644 --- a/include/PathUtil.h +++ b/include/PathUtil.h @@ -31,8 +31,8 @@ namespace lmms::PathUtil { - enum class Base { Absolute, ProjectDir, FactorySample, UserSample, UserVST, Preset, - UserLADSPA, DefaultLADSPA, UserSoundfont, DefaultSoundfont, UserGIG, DefaultGIG, + enum class Base { Absolute, ProjectDir, FactoryProjects, FactorySample, UserSample, UserVST, Preset, + FactoryPresets, UserLADSPA, DefaultLADSPA, UserSoundfont, DefaultSoundfont, UserGIG, DefaultGIG, LocalDir }; //! Return the directory associated with a given base as a QString diff --git a/include/PatternClip.h b/include/PatternClip.h index 968a0b198..0be217407 100644 --- a/include/PatternClip.h +++ b/include/PatternClip.h @@ -51,6 +51,11 @@ public: gui::ClipView * createView( gui::TrackView * _tv ) override; + PatternClip* clone() override + { + return new PatternClip(*this); + } + private: friend class PatternClipView; } ; diff --git a/include/PeakController.h b/include/PeakController.h index a22257374..de9da3b1c 100644 --- a/include/PeakController.h +++ b/include/PeakController.h @@ -78,7 +78,8 @@ private: static int m_loadCount; static bool m_buggedFile; - float m_coeff; + float m_attackCoeff; + float m_decayCoeff; bool m_coeffNeedsUpdate; } ; diff --git a/include/PianoRoll.h b/include/PianoRoll.h index a1d045ff4..96557f33d 100644 --- a/include/PianoRoll.h +++ b/include/PianoRoll.h @@ -35,7 +35,7 @@ #include "ComboBoxModel.h" #include "SerializingObject.h" #include "Note.h" -#include "lmms_basics.h" +#include "LmmsTypes.h" #include "Song.h" #include "StepRecorder.h" #include "StepRecorderWidget.h" @@ -90,6 +90,7 @@ class PianoRoll : public QWidget Q_PROPERTY(int ghostNoteOpacity MEMBER m_ghostNoteOpacity) Q_PROPERTY(bool ghostNoteBorders MEMBER m_ghostNoteBorders) Q_PROPERTY(QColor backgroundShade MEMBER m_backgroundShade) + Q_PROPERTY(QColor outOfBoundsShade MEMBER m_outOfBoundsShade) /* white key properties */ Q_PROPERTY(int whiteKeyWidth MEMBER m_whiteKeyWidth) @@ -112,7 +113,8 @@ public: Erase, Select, Detuning, - Knife + Knife, + Strum }; /*! \brief Resets settings to default when e.g. creating a new project */ @@ -247,6 +249,7 @@ protected slots: void clearGhostClip(); void glueNotes(); void fitNoteLengths(bool fill); + void reverseNotes(); void constrainNoteLengths(bool constrainMax); void changeSnapMode(); @@ -268,7 +271,8 @@ private: SelectNotes, ChangeNoteProperty, ResizeNoteEditArea, - Knife + Knife, + Strum }; enum class NoteEditMode @@ -324,6 +328,9 @@ private: void setKnifeAction(); void cancelKnifeAction(); + void setStrumAction(); + void cancelStrumAction(); + void updateScrollbars(); void updatePositionLineHeight(); @@ -347,6 +354,7 @@ private: QPixmap m_toolMove = embed::getIconPixmap("edit_move"); QPixmap m_toolOpen = embed::getIconPixmap("automation"); QPixmap m_toolKnife = embed::getIconPixmap("edit_knife"); + QPixmap m_toolStrum = embed::getIconPixmap("arp_free"); static std::array prKeyOrder; @@ -437,6 +445,7 @@ private: 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 + EditMode m_strumMode; //< mode they where in before entering strum mode bool m_mouseDownRight; //true if right click is being held down @@ -465,6 +474,21 @@ private: void updateKnifePos(QMouseEvent* me, bool initial); + //! Stores the chords for the strum tool + std::vector m_selectedChords; + //! Computes which notes belong to which chords from the selection + void setupSelectedChords(); + + TimePos m_strumStartTime; + TimePos m_strumCurrentTime; + int m_strumStartVertical = 0; + int m_strumCurrentVertical = 0; + float m_strumHeightRatio = 0.0f; + bool m_strumEnabled = false; + //! Handles updating all of the note positions when performing a strum + void updateStrumPos(QMouseEvent* me, bool initial, bool warp); + + friend class PianoRollWindow; StepRecorderWidget m_stepRecorderWidget; @@ -493,6 +517,7 @@ private: bool m_noteBorders; bool m_ghostNoteBorders; QColor m_backgroundShade; + QColor m_outOfBoundsShade; /* white key properties */ int m_whiteKeyWidth; QColor m_whiteKeyActiveTextColor; diff --git a/include/PlayHandle.h b/include/PlayHandle.h index 5f0256a1a..fbe45471b 100644 --- a/include/PlayHandle.h +++ b/include/PlayHandle.h @@ -32,7 +32,7 @@ #include "Flags.h" #include "ThreadableJob.h" -#include "lmms_basics.h" +#include "LmmsTypes.h" class QThread; diff --git a/include/PositionLine.h b/include/PositionLine.h index 3e798948f..7a619c450 100644 --- a/include/PositionLine.h +++ b/include/PositionLine.h @@ -28,6 +28,8 @@ #include +#include "Song.h" + namespace lmms::gui { @@ -37,7 +39,7 @@ class PositionLine : public QWidget Q_PROPERTY(bool tailGradient MEMBER m_hasTailGradient) Q_PROPERTY(QColor lineColor MEMBER m_lineColor) public: - PositionLine(QWidget* parent); + PositionLine(QWidget* parent, Song::PlayMode playMode); public slots: void zoomChange(float zoom); @@ -45,6 +47,8 @@ public slots: private: void paintEvent(QPaintEvent* pe) override; + Song::PlayMode m_playMode; + bool m_hasTailGradient; QColor m_lineColor; }; diff --git a/include/ProjectJournal.h b/include/ProjectJournal.h index 841bbf094..b5b8fc138 100644 --- a/include/ProjectJournal.h +++ b/include/ProjectJournal.h @@ -28,7 +28,7 @@ #include #include -#include "lmms_basics.h" +#include "LmmsTypes.h" #include "DataFile.h" diff --git a/include/ProjectRenderer.h b/include/ProjectRenderer.h index 14c584a2e..3183fc478 100644 --- a/include/ProjectRenderer.h +++ b/include/ProjectRenderer.h @@ -26,7 +26,6 @@ #define LMMS_PROJECT_RENDERER_H #include "AudioFileDevice.h" -#include "lmmsconfig.h" #include "AudioEngine.h" #include "OutputSettings.h" diff --git a/include/RemotePluginBase.h b/include/RemotePluginBase.h index 787742fc0..53ccce1c7 100644 --- a/include/RemotePluginBase.h +++ b/include/RemotePluginBase.h @@ -27,6 +27,8 @@ #include "MidiEvent.h" +#include "lmmsconfig.h" + #include #include #include diff --git a/include/RingBuffer.h b/include/RingBuffer.h index 41595be19..0a7bb550a 100644 --- a/include/RingBuffer.h +++ b/include/RingBuffer.h @@ -28,7 +28,7 @@ #include #include -#include "lmms_basics.h" +#include "LmmsTypes.h" #include "lmms_export.h" diff --git a/include/SampleBuffer.h b/include/SampleBuffer.h index 8ec6c5886..d21bacf84 100644 --- a/include/SampleBuffer.h +++ b/include/SampleBuffer.h @@ -34,7 +34,7 @@ #include "AudioEngine.h" #include "Engine.h" -#include "lmms_basics.h" +#include "LmmsTypes.h" #include "lmms_export.h" namespace lmms { diff --git a/include/SampleClip.h b/include/SampleClip.h index 3beca338b..ea7853386 100644 --- a/include/SampleClip.h +++ b/include/SampleClip.h @@ -49,13 +49,11 @@ class SampleClip : public Clip public: 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; - void changeLengthToSampleLength(); const QString& sampleFile() const; bool hasSampleFileLoaded(const QString & filename) const; @@ -81,6 +79,11 @@ public: void setIsPlaying(bool isPlaying); void setSampleBuffer(std::shared_ptr sb); + SampleClip* clone() override + { + return new SampleClip(*this); + } + public slots: void setSampleFile(const QString& sf); void updateLength(); @@ -88,6 +91,8 @@ public slots: void playbackPositionChanged(); void updateTrackClips(); +protected: + SampleClip( const SampleClip& orig ); private: Sample m_sample; diff --git a/include/SampleClipView.h b/include/SampleClipView.h index 07d04d9d0..b0304fd06 100644 --- a/include/SampleClipView.h +++ b/include/SampleClipView.h @@ -68,7 +68,7 @@ private: SampleClip * m_clip; SampleThumbnail m_sampleThumbnail; QPixmap m_paintPixmap; - bool splitClip( const TimePos pos ) override; + long m_paintPixmapXPosition; } ; diff --git a/include/SampleDecoder.h b/include/SampleDecoder.h index 2cfd00977..9109d4121 100644 --- a/include/SampleDecoder.h +++ b/include/SampleDecoder.h @@ -31,7 +31,6 @@ #include #include -#include "lmms_basics.h" #include "SampleFrame.h" namespace lmms { diff --git a/include/SampleFrame.h b/include/SampleFrame.h index 238a85dea..43db2f6e6 100644 --- a/include/SampleFrame.h +++ b/include/SampleFrame.h @@ -26,7 +26,8 @@ #ifndef LMMS_SAMPLEFRAME_H #define LMMS_SAMPLEFRAME_H -#include "lmms_basics.h" +#include "LmmsTypes.h" +#include "lmms_constants.h" #include #include diff --git a/include/SampleThumbnail.h b/include/SampleThumbnail.h index b8e1e85af..7d1082437 100644 --- a/include/SampleThumbnail.h +++ b/include/SampleThumbnail.h @@ -54,10 +54,8 @@ public: { QRect sampleRect; //!< A rectangle that covers the entire range of samples. - QRect drawRect; //!< Specifies the location in `sampleRect` where the waveform will be drawn. Equals - //!< `sampleRect` when null. - - QRect viewportRect; //!< Clips `drawRect`. Equals `drawRect` when null. + QRect viewportRect; //!< Specifies the location in `sampleRect` where the waveform will be drawn. Equals + //!< `sampleRect` when null. float amplification = 1.0f; //!< The amount of amplification to apply to the waveform. @@ -95,8 +93,8 @@ private: Peak operator+(const Peak& other) const { return Peak(std::min(min, other.min), std::max(max, other.max)); } Peak operator+(const SampleFrame& frame) const { return *this + Peak{frame}; } - float min = std::numeric_limits::max(); - float max = std::numeric_limits::min(); + float min = std::numeric_limits::infinity(); + float max = -std::numeric_limits::infinity(); }; Thumbnail() = default; @@ -105,6 +103,7 @@ private: Thumbnail zoomOut(float factor) const; + Peak* data() { return m_peaks.data(); } Peak& operator[](size_t index) { return m_peaks[index]; } const Peak& operator[](size_t index) const { return m_peaks[index]; } @@ -134,7 +133,7 @@ private: using ThumbnailCache = std::vector; std::shared_ptr m_thumbnailCache = std::make_shared(); - + std::shared_ptr m_buffer = SampleBuffer::emptyBuffer(); inline static std::unordered_map, Hash> s_sampleThumbnailCacheMap; }; diff --git a/include/SampleTrackWindow.h b/include/SampleTrackWindow.h index 01adb0080..f9aa0f71c 100644 --- a/include/SampleTrackWindow.h +++ b/include/SampleTrackWindow.h @@ -28,9 +28,9 @@ #include #include "ModelView.h" -#include "PixmapButton.h" #include "SampleTrack.h" #include "SerializingObject.h" +#include "AutomatableButton.h" class QLineEdit; @@ -91,8 +91,8 @@ private: QLineEdit * m_nameLineEdit; Knob * m_volumeKnob; Knob * m_panningKnob; - PixmapButton *m_muteBtn; - PixmapButton *m_soloBtn; + AutomatableButton* m_muteBtn; + AutomatableButton* m_soloBtn; MixerChannelLcdSpinBox * m_mixerChannelNumber; EffectRackView * m_effectRack; diff --git a/include/SetupDialog.h b/include/SetupDialog.h index 23589f91a..c314ff42d 100644 --- a/include/SetupDialog.h +++ b/include/SetupDialog.h @@ -30,7 +30,6 @@ #include "AudioDevice.h" #include "AudioDeviceSetupWidget.h" -#include "lmmsconfig.h" #include "MidiClient.h" #include "MidiSetupWidget.h" @@ -183,6 +182,8 @@ private: QSlider * m_bufferSizeSlider; QLabel * m_bufferSizeLbl; QLabel * m_bufferSizeWarnLbl; + int m_sampleRate; + QSlider* m_sampleRateSlider; // MIDI settings widgets. QComboBox * m_midiInterfaces; diff --git a/include/SimpleTextFloat.h b/include/SimpleTextFloat.h index bde6c84fa..2821ca411 100644 --- a/include/SimpleTextFloat.h +++ b/include/SimpleTextFloat.h @@ -23,8 +23,8 @@ */ -#ifndef SIMPLE_TEXT_FLOAT_H -#define SIMPLE_TEXT_FLOAT_H +#ifndef LMMS_GUI_SIMPLE_TEXT_FLOAT_H +#define LMMS_GUI_SIMPLE_TEXT_FLOAT_H #include @@ -47,7 +47,10 @@ public: void showWithDelay(int msecBeforeDisplay, int msecDisplayTime); - void setVisibilityTimeOut(int msecs); + void showWithTimeout(int msec) + { + showWithDelay(0, msec); + } void moveGlobal(QWidget * w, const QPoint & offset) { @@ -64,4 +67,4 @@ private: } // namespace lmms::gui -#endif +#endif // LMMS_GUI_SIMPLE_TEXT_FLOAT_H diff --git a/include/Song.h b/include/Song.h index f08edfff6..7fedaa44d 100644 --- a/include/Song.h +++ b/include/Song.h @@ -257,6 +257,7 @@ public: return m_playMode; } + PlayMode lastPlayMode() const { return m_lastPlayMode; } inline PlayPos & getPlayPos( PlayMode pm ) { return m_playPos[static_cast(pm)]; @@ -492,6 +493,7 @@ private: std::array m_timelines; PlayMode m_playMode; + PlayMode m_lastPlayMode; PlayPos m_playPos[PlayModeCount]; bar_t m_length; diff --git a/include/SweepOscillator.h b/include/SweepOscillator.h deleted file mode 100644 index c7c7938d1..000000000 --- a/include/SweepOscillator.h +++ /dev/null @@ -1,72 +0,0 @@ -/* - * SweepOscillator.h - sweeping oscillator - * - * Copyright (c) 2006-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_SWEEP_OSCILLATOR_H -#define LMMS_SWEEP_OSCILLATOR_H - -#include "Oscillator.h" -#include "DspEffectLibrary.h" - -namespace lmms -{ - -template -class SweepOscillator -{ -public: - SweepOscillator( const FX & _fx = FX() ) : - m_phase( 0.0f ), - m_FX( _fx ) - { - } - - virtual ~SweepOscillator() = default; - - void update( SampleFrame* buf, const fpp_t frames, const float freq1, const float freq2, const float sampleRate ) - { - const float df = freq2 - freq1; - for( fpp_t frame = 0; frame < frames; ++frame ) - { - const sample_t s = Oscillator::sinSample( m_phase ); - buf[frame][0] = s; - buf[frame][1] = s; - m_FX.nextSample( buf[frame][0], buf[frame][1] ); - m_phase += ( freq1 + ( frame * df / frames ) ) / sampleRate; - } - } - - -private: - float m_phase; - FX m_FX; - -// inline sample_t getSample( const float _sample ); -// inline void recalcPhase(); - -} ; - - -} // namespace lmms - -#endif // LMMS_SWEEP_OSCILLATOR_H diff --git a/include/TempoSyncKnob.h b/include/TempoSyncKnob.h index b86320d13..656f145ab 100644 --- a/include/TempoSyncKnob.h +++ b/include/TempoSyncKnob.h @@ -42,6 +42,29 @@ class LMMS_EXPORT TempoSyncKnob : public Knob Q_OBJECT public: TempoSyncKnob( KnobType knobNum, QWidget* parent = nullptr, const QString& name = QString() ); + + /** + * @brief Construct a TempoSyncKnob with the given style and label text. + * + * @param knobNum Style of the knob + * @param labelText Text for the label + * @param parent Parent widget + * @param labelRendering Determines if the label uses the widget font or a font with a fixed size of 12 pixels (LegacyFixedFontSize). The default is to use the widget font. + * @param name Object name of the widget + */ + TempoSyncKnob(KnobType knobNum, const QString& labelText, QWidget* parent = nullptr, LabelRendering labelRendering = LabelRendering::WidgetFont, const QString& name = QString()); + + /** + * @brief Constructs a tempo sync knob with a label font in the pixel size. + * + * @param knobNum Style of the knob + * @param labelText Text for the label + * @param labelPixelSize Pixel size for the label + * @param parent Parent widget + * @param name Object name of the widget + */ + TempoSyncKnob(KnobType knobNum, const QString& labelText, int labelPixelSize, QWidget* parent, const QString& name = QString()); + ~TempoSyncKnob() override; const QString & syncDescription(); diff --git a/include/ThreadableJob.h b/include/ThreadableJob.h index 9d5a0beee..2f65ed5d9 100644 --- a/include/ThreadableJob.h +++ b/include/ThreadableJob.h @@ -25,7 +25,7 @@ #ifndef LMMS_THREADABLE_JOB_H #define LMMS_THREADABLE_JOB_H -#include "lmms_basics.h" +#include "LmmsTypes.h" #include diff --git a/include/TimePos.h b/include/TimePos.h index 68f3bd01b..02338c6e7 100644 --- a/include/TimePos.h +++ b/include/TimePos.h @@ -29,7 +29,7 @@ #include #include #include "lmms_export.h" -#include "lmms_basics.h" +#include "LmmsTypes.h" namespace lmms { @@ -70,7 +70,7 @@ public: TimePos( const bar_t bar, const tick_t ticks ); TimePos( const tick_t ticks = 0 ); - TimePos quantize(float) const; + TimePos quantize(float bars, bool forceRoundDown = false) const; TimePos toAbsoluteBar() const { return getBar() * s_ticksPerBar; } TimePos& operator+=(const TimePos& time) diff --git a/include/Track.h b/include/Track.h index a152640e1..274e7a1fc 100644 --- a/include/Track.h +++ b/include/Track.h @@ -31,7 +31,7 @@ #include "AutomatableModel.h" #include "JournallingObject.h" -#include "lmms_basics.h" +#include "LmmsTypes.h" #include diff --git a/include/TrackOperationsWidget.h b/include/TrackOperationsWidget.h index 8417298b4..3eb9215b9 100644 --- a/include/TrackOperationsWidget.h +++ b/include/TrackOperationsWidget.h @@ -26,6 +26,7 @@ #define LMMS_GUI_TRACK_OPERATIONS_WIDGET_H #include +#include "AutomatableButton.h" class QPushButton; @@ -69,8 +70,8 @@ private: TrackGrip* m_trackGrip; QPushButton * m_trackOps; - PixmapButton * m_muteBtn; - PixmapButton * m_soloBtn; + AutomatableButton* m_muteBtn; + AutomatableButton* m_soloBtn; friend class TrackView; diff --git a/include/embed.h b/include/embed.h index 40a3622c6..489a06270 100644 --- a/include/embed.h +++ b/include/embed.h @@ -32,7 +32,8 @@ #include #include "lmms_export.h" -#include "lmms_basics.h" +#include "LmmsCommonMacros.h" + namespace lmms { @@ -51,6 +52,24 @@ auto LMMS_EXPORT getIconPixmap(std::string_view name, int width = -1, int height = -1, const char* const* xpm = nullptr) -> QPixmap; auto LMMS_EXPORT getText(std::string_view name) -> QString; +/** + * @brief Temporary shim for QPixmap::deviceIndependentSize. + * @param pixmap The pixmap to get the size of. + * @return The device-independent size of the pixmap. + */ +#if QT_VERSION >= QT_VERSION_CHECK(6, 2, 0) +[[deprecated("Use QPixmap::deviceIndependentSize() instead; See " + "https://doc.qt.io/qt-6/qpixmap.html#deviceIndependentSize")]] +#endif +inline auto logicalSize(const QPixmap &pixmap) noexcept +{ +#if QT_VERSION >= QT_VERSION_CHECK(6, 2, 0) + return pixmap.deviceIndependentSize().toSize(); +#else + return pixmap.isNull() ? QSize() : pixmap.size() / pixmap.devicePixelRatio(); +#endif +} + } // namespace embed class PixmapLoader diff --git a/include/endian_handling.h b/include/endian_handling.h index 7ddb22f22..793b22e46 100644 --- a/include/endian_handling.h +++ b/include/endian_handling.h @@ -25,10 +25,9 @@ #ifndef LMMS_ENDIAN_HANDLING_H #define LMMS_ENDIAN_HANDLING_H +#include #include -#include "lmms_basics.h" - namespace lmms { diff --git a/include/lmms_constants.h b/include/lmms_constants.h index 782e6849d..8be44a404 100644 --- a/include/lmms_constants.h +++ b/include/lmms_constants.h @@ -25,17 +25,23 @@ #ifndef LMMS_CONSTANTS_H #define LMMS_CONSTANTS_H +#include "lmmsconfig.h" +#include "LmmsTypes.h" + namespace lmms { - // Prefer using `approximatelyEqual()` from lmms_math.h rather than // using this directly inline constexpr float F_EPSILON = 1.0e-10f; // 10^-10 +inline constexpr ch_cnt_t DEFAULT_CHANNELS = 2; + // Microtuner inline constexpr unsigned MaxScaleCount = 10; //!< number of scales per project inline constexpr unsigned MaxKeymapCount = 10; //!< number of keyboard mappings per project + +// Note: All constants below are used only in spectrum analyser // Frequency ranges (in Hz). // Arbitrary low limit for logarithmic frequency scale; >1 Hz. inline constexpr auto LOWEST_LOG_FREQ = 5; @@ -79,6 +85,15 @@ inline constexpr auto ARANGE_LOUD_END = 0; inline constexpr auto ARANGE_SILENT_START = -60; inline constexpr auto ARANGE_SILENT_END = -10; + +// This macro is used to handle path seperation properly in windows +constexpr char LADSPA_PATH_SEPERATOR = +#ifdef LMMS_BUILD_WIN32 +';'; +#else +':'; +#endif + } // namespace lmms #endif // LMMS_CONSTANTS_H diff --git a/include/lmms_math.h b/include/lmms_math.h index 1d3de249d..5129743de 100644 --- a/include/lmms_math.h +++ b/include/lmms_math.h @@ -34,7 +34,6 @@ #include #include -#include "lmmsconfig.h" #include "lmms_constants.h" namespace lmms diff --git a/include/panning.h b/include/panning.h index 2945988ba..fa9cde9ee 100644 --- a/include/panning.h +++ b/include/panning.h @@ -26,7 +26,7 @@ #ifndef LMMS_PANNING_H #define LMMS_PANNING_H -#include "lmms_basics.h" +#include "LmmsTypes.h" #include "Midi.h" #include "volume.h" diff --git a/include/shared_object.h b/include/shared_object.h deleted file mode 100644 index e9fdb57a3..000000000 --- a/include/shared_object.h +++ /dev/null @@ -1,89 +0,0 @@ -/* - * shared_object.h - class sharedObject for use among other objects - * - * Copyright (c) 2006-2007 Javier Serrano Polo - * 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 - * 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_SHARED_OBJECT_H -#define LMMS_SHARED_OBJECT_H - -#include - -namespace lmms -{ - -class sharedObject -{ -public: - sharedObject() : - m_referenceCount(1) - { - } - - virtual ~sharedObject() = default; - - template - static T* ref( T* object ) - { - // Incrementing an atomic reference count can be relaxed since no action - // is ever taken as a result of increasing the count. - // Other loads and stores can be reordered around this without consequence. - object->m_referenceCount.fetch_add(1, std::memory_order_relaxed); - return object; - } - - template - static void unref( T* object ) - { - // When decrementing an atomic reference count, we need to provide - // two ordering guarantees: - // 1. All reads and writes to the referenced object occur before - // the count reaches zero. - // 2. Deletion occurs after the count reaches zero. - // - // To accomplish this, each decrement must be store-released, - // and the final thread (which is deleting the referenced data) - // must load-acquire those stores. - // The simplest way to do this to give the decrement acquire-release - // semantics. - // - // See https://www.boost.org/doc/libs/1_67_0/doc/html/atomic/usage_examples.html - // for further discussion, along with a slightly more complicated - // (but possibly more performant on weakly-ordered hardware like ARM) - // approach. - const bool deleteObject = - object->m_referenceCount.fetch_sub(1, std::memory_order_acq_rel) == 1; - - if ( deleteObject ) - { - object->deleteLater(); - } - } - -private: - std::atomic_int m_referenceCount; -} ; - - -} // namespace lmms - -#endif // LMMS_SHARED_OBJECT_H diff --git a/include/versioninfo.h b/include/versioninfo.h index 7495299c2..f2a535313 100644 --- a/include/versioninfo.h +++ b/include/versioninfo.h @@ -1,7 +1,7 @@ #ifndef LMMS_VERSION_INFO_H #define LMMS_VERSION_INFO_H -#include "lmms_basics.h" +#include "LmmsCommonMacros.h" #if defined(__GNUC__) constexpr const char* LMMS_BUILDCONF_COMPILER_VERSION = "GCC " __VERSION__; diff --git a/include/volume.h b/include/volume.h index 382f76780..76c6c37de 100644 --- a/include/volume.h +++ b/include/volume.h @@ -26,7 +26,7 @@ #ifndef LMMS_VOLUME_H #define LMMS_VOLUME_H -#include "lmms_basics.h" +#include "LmmsTypes.h" namespace lmms { diff --git a/plugins/Amplifier/AmplifierControlDialog.cpp b/plugins/Amplifier/AmplifierControlDialog.cpp index 1fbc3729a..9bf0bb649 100644 --- a/plugins/Amplifier/AmplifierControlDialog.cpp +++ b/plugins/Amplifier/AmplifierControlDialog.cpp @@ -28,6 +28,9 @@ #include "embed.h" #include "Knob.h" +#include + + namespace lmms::gui { @@ -38,23 +41,22 @@ AmplifierControlDialog::AmplifierControlDialog(AmplifierControls* controls) : QPalette pal; 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; - }; - 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); + QGridLayout* gridLayout = new QGridLayout(this); + + auto makeKnob = [this](const QString& label, const QString& hintText, const QString& unit, FloatModel* model, bool isVolume) + { + Knob* newKnob = new Knob(KnobType::Bright26, label, this); + newKnob->setModel(model); + newKnob->setHintText(hintText, unit); + newKnob->setVolumeKnob(isVolume); + return newKnob; + }; + + gridLayout->addWidget(makeKnob(tr("VOL"), tr("Volume:"), "%", &controls->m_volumeModel, true), 0, 0, Qt::AlignHCenter); + gridLayout->addWidget(makeKnob(tr("PAN"), tr("Panning:"), "%", &controls->m_panModel, false), 0, 1, Qt::AlignHCenter); + gridLayout->addWidget(makeKnob(tr("LEFT"), tr("Left gain:"), "%", &controls->m_leftModel, true), 1, 0, Qt::AlignHCenter); + gridLayout->addWidget(makeKnob(tr("RIGHT"), tr("Right gain:"), "%", &controls->m_rightModel, true), 1, 1, Qt::AlignHCenter); } } // namespace lmms::gui diff --git a/plugins/Amplifier/CMakeLists.txt b/plugins/Amplifier/CMakeLists.txt index 3e57ba5ef..2d465fc55 100644 --- a/plugins/Amplifier/CMakeLists.txt +++ b/plugins/Amplifier/CMakeLists.txt @@ -1,3 +1,3 @@ INCLUDE(BuildPlugin) -BUILD_PLUGIN(amplifier Amplifier.cpp AmplifierControls.cpp AmplifierControlDialog.cpp MOCFILES AmplifierControls.h AmplifierControlDialog.h EMBEDDED_RESOURCES artwork.png logo.png) +BUILD_PLUGIN(amplifier Amplifier.cpp AmplifierControls.cpp AmplifierControlDialog.cpp MOCFILES AmplifierControls.h AmplifierControlDialog.h EMBEDDED_RESOURCES artwork.svg logo.svg) diff --git a/plugins/Amplifier/artwork.png b/plugins/Amplifier/artwork.png deleted file mode 100644 index 5598b32db..000000000 Binary files a/plugins/Amplifier/artwork.png and /dev/null differ diff --git a/plugins/Amplifier/artwork.svg b/plugins/Amplifier/artwork.svg new file mode 100644 index 000000000..df574cfda --- /dev/null +++ b/plugins/Amplifier/artwork.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/plugins/Amplifier/logo.png b/plugins/Amplifier/logo.png deleted file mode 100644 index 9340da708..000000000 Binary files a/plugins/Amplifier/logo.png and /dev/null differ diff --git a/plugins/Amplifier/logo.svg b/plugins/Amplifier/logo.svg new file mode 100644 index 000000000..7920fa6b0 --- /dev/null +++ b/plugins/Amplifier/logo.svg @@ -0,0 +1,3 @@ + + + diff --git a/plugins/AudioFileProcessor/AudioFileProcessor.cpp b/plugins/AudioFileProcessor/AudioFileProcessor.cpp index 4cc14ba9c..6754624df 100644 --- a/plugins/AudioFileProcessor/AudioFileProcessor.cpp +++ b/plugins/AudioFileProcessor/AudioFileProcessor.cpp @@ -30,7 +30,7 @@ #include "SampleLoader.h" #include "Song.h" -#include "lmms_basics.h" +#include "LmmsTypes.h" #include "plugin_export.h" #include diff --git a/plugins/AudioFileProcessor/AudioFileProcessor.h b/plugins/AudioFileProcessor/AudioFileProcessor.h index acdbc45f7..0548d5b45 100644 --- a/plugins/AudioFileProcessor/AudioFileProcessor.h +++ b/plugins/AudioFileProcessor/AudioFileProcessor.h @@ -32,7 +32,7 @@ #include "Instrument.h" #include "Sample.h" -#include "lmms_basics.h" +#include "LmmsTypes.h" namespace lmms diff --git a/plugins/AudioFileProcessor/AudioFileProcessorView.cpp b/plugins/AudioFileProcessor/AudioFileProcessorView.cpp index 298e79c5e..02098c540 100644 --- a/plugins/AudioFileProcessor/AudioFileProcessorView.cpp +++ b/plugins/AudioFileProcessor/AudioFileProcessorView.cpp @@ -120,15 +120,15 @@ AudioFileProcessorView::AudioFileProcessorView(Instrument* instrument, m_ampKnob->setHintText(tr("Amplify:"), "%"); m_startKnob = new AudioFileProcessorWaveView::knob(this); - m_startKnob->move(45, 108); + m_startKnob->move(50, 108); m_startKnob->setHintText(tr("Start point:"), ""); m_endKnob = new AudioFileProcessorWaveView::knob(this); - m_endKnob->move(125, 108); + m_endKnob->move(130, 108); m_endKnob->setHintText(tr("End point:"), ""); m_loopKnob = new AudioFileProcessorWaveView::knob(this); - m_loopKnob->move(85, 108); + m_loopKnob->move(90, 108); m_loopKnob->setHintText(tr("Loopback point:"), ""); // interpolation selector diff --git a/plugins/AudioFileProcessor/AudioFileProcessorWaveView.h b/plugins/AudioFileProcessor/AudioFileProcessorWaveView.h index 6440570e6..69dea0b1b 100644 --- a/plugins/AudioFileProcessor/AudioFileProcessorWaveView.h +++ b/plugins/AudioFileProcessor/AudioFileProcessorWaveView.h @@ -73,7 +73,6 @@ public: m_waveView(0), m_relatedKnob(0) { - setFixedSize(37, 47); } void setWaveView(const AudioFileProcessorWaveView* wv) diff --git a/plugins/BassBooster/BassBoosterControlDialog.cpp b/plugins/BassBooster/BassBoosterControlDialog.cpp index 9efa07c0d..fcdf10cc2 100644 --- a/plugins/BassBooster/BassBoosterControlDialog.cpp +++ b/plugins/BassBooster/BassBoosterControlDialog.cpp @@ -43,26 +43,22 @@ BassBoosterControlDialog::BassBoosterControlDialog( BassBoosterControls* control QPalette pal; pal.setBrush( backgroundRole(), PLUGIN_NAME::getIconPixmap( "artwork" ) ); setPalette( pal ); - setFixedSize( 120, 60 ); auto tl = new QVBoxLayout(this); tl->addSpacing( 4 ); auto l = new QHBoxLayout; - auto freqKnob = new Knob(KnobType::Bright26, this); + auto freqKnob = new Knob(KnobType::Bright26, tr("FREQ"), this); freqKnob->setModel( &controls->m_freqModel ); - freqKnob->setLabel( tr( "FREQ" ) ); freqKnob->setHintText( tr( "Frequency:" ) , "Hz" ); - auto gainKnob = new Knob(KnobType::Bright26, this); + auto gainKnob = new Knob(KnobType::Bright26, tr("GAIN"), this); gainKnob->setModel( &controls->m_gainModel ); - gainKnob->setLabel( tr( "GAIN" ) ); gainKnob->setHintText( tr( "Gain:" ) , "" ); - auto ratioKnob = new Knob(KnobType::Bright26, this); + auto ratioKnob = new Knob(KnobType::Bright26, tr("RATIO"), this); ratioKnob->setModel( &controls->m_ratioModel ); - ratioKnob->setLabel( tr( "RATIO" ) ); ratioKnob->setHintText( tr( "Ratio:" ) , "" ); l->addWidget( freqKnob ); diff --git a/plugins/BassBooster/CMakeLists.txt b/plugins/BassBooster/CMakeLists.txt index 35bfb5049..955a6584c 100644 --- a/plugins/BassBooster/CMakeLists.txt +++ b/plugins/BassBooster/CMakeLists.txt @@ -1,3 +1,3 @@ INCLUDE(BuildPlugin) -BUILD_PLUGIN(bassbooster BassBooster.cpp BassBoosterControls.cpp BassBoosterControlDialog.cpp MOCFILES BassBoosterControls.h BassBoosterControlDialog.h EMBEDDED_RESOURCES artwork.png logo.png) +BUILD_PLUGIN(bassbooster BassBooster.cpp BassBoosterControls.cpp BassBoosterControlDialog.cpp MOCFILES BassBoosterControls.h BassBoosterControlDialog.h EMBEDDED_RESOURCES artwork.svg logo.svg) diff --git a/plugins/BassBooster/artwork.png b/plugins/BassBooster/artwork.png deleted file mode 100644 index c16b8ca2b..000000000 Binary files a/plugins/BassBooster/artwork.png and /dev/null differ diff --git a/plugins/BassBooster/artwork.svg b/plugins/BassBooster/artwork.svg new file mode 100644 index 000000000..f98447e18 --- /dev/null +++ b/plugins/BassBooster/artwork.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/plugins/BassBooster/logo.png b/plugins/BassBooster/logo.png deleted file mode 100644 index 9340da708..000000000 Binary files a/plugins/BassBooster/logo.png and /dev/null differ diff --git a/plugins/BassBooster/logo.svg b/plugins/BassBooster/logo.svg new file mode 100644 index 000000000..7920fa6b0 --- /dev/null +++ b/plugins/BassBooster/logo.svg @@ -0,0 +1,3 @@ + + + diff --git a/plugins/Bitcrush/BitcrushControlDialog.cpp b/plugins/Bitcrush/BitcrushControlDialog.cpp index 64c9b6361..3036c802a 100644 --- a/plugins/Bitcrush/BitcrushControlDialog.cpp +++ b/plugins/Bitcrush/BitcrushControlDialog.cpp @@ -29,6 +29,7 @@ #include "embed.h" #include "BitcrushControlDialog.h" #include "BitcrushControls.h" +#include "FontHelper.h" #include "LedCheckBox.h" #include "Knob.h" @@ -46,37 +47,37 @@ BitcrushControlDialog::BitcrushControlDialog( BitcrushControls * controls ) : setFixedSize( 181, 128 ); // labels + const auto labelFont = adjustedToPixelSize(font(), DEFAULT_FONT_SIZE); + auto inLabel = new QLabel(tr("IN"), this); + inLabel->setFont(labelFont); inLabel->move( 24, 15 ); auto outLabel = new QLabel(tr("OUT"), this); + outLabel->setFont(labelFont); outLabel->move( 139, 15 ); // input knobs - auto inGain = new Knob(KnobType::Bright26, this); + auto inGain = new Knob(KnobType::Bright26, tr("GAIN"), SMALL_FONT_SIZE, this); inGain->move( 16, 32 ); inGain->setModel( & controls->m_inGain ); - inGain->setLabel( tr( "GAIN" ) ); inGain->setHintText( tr( "Input gain:" ) , " dBFS" ); - auto inNoise = new Knob(KnobType::Bright26, this); + auto inNoise = new Knob(KnobType::Bright26, tr("NOISE"), SMALL_FONT_SIZE, this); inNoise->move( 14, 76 ); inNoise->setModel( & controls->m_inNoise ); - inNoise->setLabel( tr( "NOISE" ) ); inNoise->setHintText( tr( "Input noise:" ) , "%" ); // output knobs - auto outGain = new Knob(KnobType::Bright26, this); + auto outGain = new Knob(KnobType::Bright26, tr("GAIN"), SMALL_FONT_SIZE, this); outGain->move( 138, 32 ); outGain->setModel( & controls->m_outGain ); - outGain->setLabel( tr( "GAIN" ) ); outGain->setHintText( tr( "Output gain:" ) , " dBFS" ); - auto outClip = new Knob(KnobType::Bright26, this); + auto outClip = new Knob(KnobType::Bright26, tr("CLIP"), SMALL_FONT_SIZE, this); outClip->move( 138, 76 ); outClip->setModel( & controls->m_outClip ); - outClip->setLabel( tr( "CLIP" ) ); outClip->setHintText( tr( "Output clip:" ) , " dBFS"); @@ -94,24 +95,21 @@ BitcrushControlDialog::BitcrushControlDialog( BitcrushControls * controls ) : // rate crushing knobs - auto rate = new Knob(KnobType::Bright26, this); + auto rate = new Knob(KnobType::Bright26, tr("FREQ"), SMALL_FONT_SIZE, this); rate->move( 59, 32 ); rate->setModel( & controls->m_rate ); - rate->setLabel( tr( "FREQ" ) ); rate->setHintText( tr( "Sample rate:" ) , " Hz" ); - auto stereoDiff = new Knob(KnobType::Bright26, this); + auto stereoDiff = new Knob(KnobType::Bright26, tr("STEREO"), SMALL_FONT_SIZE, this); stereoDiff->move( 72, 76 ); stereoDiff->setModel( & controls->m_stereoDiff ); - stereoDiff->setLabel( tr( "STEREO" ) ); stereoDiff->setHintText( tr( "Stereo difference:" ) , "%" ); // depth crushing knob - auto levels = new Knob(KnobType::Bright26, this); + auto levels = new Knob(KnobType::Bright26, tr("QUANT"), SMALL_FONT_SIZE, this); levels->move( 92, 32 ); levels->setModel( & controls->m_levels ); - levels->setLabel( tr( "QUANT" ) ); levels->setHintText( tr( "Levels:" ) , "" ); } diff --git a/plugins/Bitcrush/CMakeLists.txt b/plugins/Bitcrush/CMakeLists.txt index f4810183e..88507f860 100644 --- a/plugins/Bitcrush/CMakeLists.txt +++ b/plugins/Bitcrush/CMakeLists.txt @@ -1,3 +1,3 @@ INCLUDE(BuildPlugin) -BUILD_PLUGIN(bitcrush Bitcrush.cpp BitcrushControls.cpp BitcrushControlDialog.cpp MOCFILES BitcrushControls.h BitcrushControlDialog.h EMBEDDED_RESOURCES artwork.png logo.png) +BUILD_PLUGIN(bitcrush Bitcrush.cpp BitcrushControls.cpp BitcrushControlDialog.cpp MOCFILES BitcrushControls.h BitcrushControlDialog.h EMBEDDED_RESOURCES artwork.svg logo.svg) diff --git a/plugins/Bitcrush/artwork.png b/plugins/Bitcrush/artwork.png deleted file mode 100755 index 72d980c91..000000000 Binary files a/plugins/Bitcrush/artwork.png and /dev/null differ diff --git a/plugins/Bitcrush/artwork.svg b/plugins/Bitcrush/artwork.svg new file mode 100644 index 000000000..dfc583974 --- /dev/null +++ b/plugins/Bitcrush/artwork.svg @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/plugins/Bitcrush/logo.png b/plugins/Bitcrush/logo.png deleted file mode 100644 index 9340da708..000000000 Binary files a/plugins/Bitcrush/logo.png and /dev/null differ diff --git a/plugins/Bitcrush/logo.svg b/plugins/Bitcrush/logo.svg new file mode 100644 index 000000000..7920fa6b0 --- /dev/null +++ b/plugins/Bitcrush/logo.svg @@ -0,0 +1,3 @@ + + + diff --git a/plugins/CarlaBase/Carla.cpp b/plugins/CarlaBase/Carla.cpp index 37cba078a..b572a13d4 100644 --- a/plugins/CarlaBase/Carla.cpp +++ b/plugins/CarlaBase/Carla.cpp @@ -1004,34 +1004,32 @@ void CarlaParamsView::refreshKnobs() QStringList groupNameList; groupNameList.reserve(m_carlaInstrument->m_paramGroupCount); - for (uint32_t i = 0; i < m_carlaInstrument->m_paramModels.size(); ++i) + for (const auto currentParamModel : m_carlaInstrument->m_paramModels) { - bool enabled = m_carlaInstrument->m_paramModels[i]->enabled(); - 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); - m_knobs[i]->setObjectName(name); // this is being used for filtering the knobs. + bool enabled = currentParamModel->enabled(); + const QString name = currentParamModel->displayName(); + + auto currentKnob = new Knob(KnobType::Dark28, name, m_inputScrollAreaWidgetContent, Knob::LabelRendering::LegacyFixedFontSize); + currentKnob->setHintText(name, ""); + currentKnob->setObjectName(name); // this is being used for filtering the knobs. // Set the newly created model to the knob. - m_knobs[i]->setModel(m_carlaInstrument->m_paramModels[i]); - m_knobs[i]->setEnabled(enabled); + currentKnob->setModel(currentParamModel); + currentKnob->setEnabled(enabled); + + m_knobs.push_back(currentKnob); if (enabled) { // Collect group names - if (!groupNameList.contains(m_carlaInstrument->m_paramModels[i]->groupName())) + if (!groupNameList.contains(currentParamModel->groupName())) { - groupNameList.append(m_carlaInstrument->m_paramModels[i]->groupName()); + groupNameList.append(currentParamModel->groupName()); } - // Store biggest knob width per group (so we can calc how many - // knobs we can horizontaly fit) - uint8_t groupId = m_carlaInstrument->m_paramModels[i]->groupId(); - if (m_maxKnobWidthPerGroup[groupId] < m_knobs[i]->width()) - { - m_maxKnobWidthPerGroup[groupId] = m_knobs[i]->width(); - } + // Store biggest knob width per group (so we can calc how many knobs we can fit horizontally) + auto & maxGroupWidth = m_maxKnobWidthPerGroup[currentParamModel->groupId()]; + maxGroupWidth = std::max(maxGroupWidth, static_cast(currentKnob->width())); } } diff --git a/plugins/CrossoverEQ/CMakeLists.txt b/plugins/CrossoverEQ/CMakeLists.txt index 447c92c11..32e700d6a 100644 --- a/plugins/CrossoverEQ/CMakeLists.txt +++ b/plugins/CrossoverEQ/CMakeLists.txt @@ -1,3 +1,3 @@ INCLUDE(BuildPlugin) -BUILD_PLUGIN(crossovereq CrossoverEQ.cpp CrossoverEQControls.cpp CrossoverEQControlDialog.cpp MOCFILES CrossoverEQControls.h CrossoverEQControlDialog.h EMBEDDED_RESOURCES artwork.png fader_bg.png fader_empty.png fader_knob2.png logo.png) +BUILD_PLUGIN(crossovereq CrossoverEQ.cpp CrossoverEQControls.cpp CrossoverEQControlDialog.cpp MOCFILES CrossoverEQControls.h CrossoverEQControlDialog.h EMBEDDED_RESOURCES artwork.svg logo.svg) diff --git a/plugins/CrossoverEQ/CrossoverEQControlDialog.cpp b/plugins/CrossoverEQ/CrossoverEQControlDialog.cpp index e7202556b..a04606601 100644 --- a/plugins/CrossoverEQ/CrossoverEQControlDialog.cpp +++ b/plugins/CrossoverEQ/CrossoverEQControlDialog.cpp @@ -28,93 +28,88 @@ #include "CrossoverEQControlDialog.h" #include "CrossoverEQControls.h" #include "embed.h" +#include "FontHelper.h" #include "LedCheckBox.h" #include "Knob.h" #include "Fader.h" +#include "PixmapButton.h" #include +#include +#include namespace lmms::gui { -CrossoverEQControlDialog::CrossoverEQControlDialog( CrossoverEQControls * controls ) : - EffectControlDialog( controls ) +CrossoverEQControlDialog::CrossoverEQControlDialog(CrossoverEQControls *controls) : + EffectControlDialog(controls) { - setAutoFillBackground( true ); + setAutoFillBackground(true); QPalette pal; - pal.setBrush( backgroundRole(), PLUGIN_NAME::getIconPixmap( "artwork" ) ); - setPalette( pal ); - setFixedSize( 167, 178 ); + pal.setBrush(backgroundRole(), PLUGIN_NAME::getIconPixmap("artwork")); + setPalette(pal); + setFixedSize(167, 218); + auto layout = new QVBoxLayout(this); + + auto knobsLayout = new QHBoxLayout(); + layout->addLayout(knobsLayout); + + const auto makeKnob = [this, knobsLayout]( + FloatModel* model, + const QString& label, + const QString& txt_before + ) { + auto k = new Knob(KnobType::Bright26, label, SMALL_FONT_SIZE, this); + k->setModel(model); + k->setHintText(txt_before, "Hz"); + knobsLayout->addWidget(k, 0, Qt::AlignHCenter); + }; + + makeKnob(&controls->m_xover12, "1/2", tr("Band 1/2 crossover")); + makeKnob(&controls->m_xover23, "2/3", tr("Band 2/3 crossover")); + makeKnob(&controls->m_xover34, "3/4", tr("Band 3/4 crossover")); - // knobs - 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 bandsLayout = new QGridLayout(); + bandsLayout->setContentsMargins(4, 10, 4, 5); + layout->addLayout(bandsLayout); - 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" ); + const auto makeFader = [this, bandsLayout]( + FloatModel* model, + const QString& label, + int column + ) { + auto f = new Fader(model, label, this, false); + f->setHintText(label, "dBFS"); + f->setDisplayConversion(false); + f->setRenderUnityLine(false); + bandsLayout->addWidget(f, 0, column, Qt::AlignHCenter); + }; - 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" ); - - QPixmap const fader_knob(PLUGIN_NAME::getIconPixmap("fader_knob2")); - - // faders - auto gain1 = new Fader(&controls->m_gain1, tr("Band 1 gain"), this, fader_knob, false); - gain1->move( 7, 56 ); - gain1->setDisplayConversion( false ); - gain1->setHintText( tr( "Band 1 gain:" ), " dBFS" ); - gain1->setRenderUnityLine(false); + makeFader(&controls->m_gain1, tr("Band 1 gain"), 0); + makeFader(&controls->m_gain2, tr("Band 2 gain"), 1); + makeFader(&controls->m_gain3, tr("Band 3 gain"), 2); + makeFader(&controls->m_gain4, tr("Band 4 gain"), 3); - auto gain2 = new Fader(&controls->m_gain2, tr("Band 2 gain"), this, fader_knob, false); - gain2->move( 47, 56 ); - gain2->setDisplayConversion( false ); - gain2->setHintText( tr( "Band 2 gain:" ), " dBFS" ); - gain2->setRenderUnityLine(false); + const auto makeMuteBtn = [this, bandsLayout]( + BoolModel* model, + const QString& label, + int column + ) { + auto b = new AutomatableButton(this, label); + b->setCheckable(true); + b->setModel(model); + b->setToolTip(label); + b->setObjectName("btn-mute-inv"); + bandsLayout->addWidget(b, 1, column, Qt::AlignCenter); + }; - auto gain3 = new Fader(&controls->m_gain3, tr("Band 3 gain"), this, fader_knob, false); - 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, fader_knob, false); - gain4->move( 127, 56 ); - gain4->setDisplayConversion( false ); - gain4->setHintText( tr( "Band 4 gain:" ), " dBFS" ); - gain4->setRenderUnityLine(false); - - // leds - auto mute1 = new LedCheckBox("", this, tr("Band 1 mute"), LedCheckBox::LedColor::Green); - 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::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::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::LedColor::Green); - mute4->move( 135, 154 ); - mute4->setModel( & controls->m_mute4 ); - mute4->setToolTip(tr("Mute band 4")); + makeMuteBtn(&controls->m_mute1, tr("Mute band 1"), 0); + makeMuteBtn(&controls->m_mute2, tr("Mute band 2"), 1); + makeMuteBtn(&controls->m_mute3, tr("Mute band 3"), 2); + makeMuteBtn(&controls->m_mute4, tr("Mute band 4"), 3); } -} // namespace lmms::gui \ No newline at end of file +} // namespace lmms::gui diff --git a/plugins/CrossoverEQ/CrossoverEQControls.cpp b/plugins/CrossoverEQ/CrossoverEQControls.cpp index 9758d804a..928797dc5 100644 --- a/plugins/CrossoverEQ/CrossoverEQControls.cpp +++ b/plugins/CrossoverEQ/CrossoverEQControls.cpp @@ -121,4 +121,4 @@ void CrossoverEQControls::sampleRateChanged() } -} // namespace lmms \ No newline at end of file +} // namespace lmms diff --git a/plugins/CrossoverEQ/artwork.png b/plugins/CrossoverEQ/artwork.png deleted file mode 100644 index ffd2788ab..000000000 Binary files a/plugins/CrossoverEQ/artwork.png and /dev/null differ diff --git a/plugins/CrossoverEQ/artwork.svg b/plugins/CrossoverEQ/artwork.svg new file mode 100644 index 000000000..59ccf992a --- /dev/null +++ b/plugins/CrossoverEQ/artwork.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/plugins/CrossoverEQ/fader_knob2.png b/plugins/CrossoverEQ/fader_knob2.png deleted file mode 100644 index b5238ac06..000000000 Binary files a/plugins/CrossoverEQ/fader_knob2.png and /dev/null differ diff --git a/plugins/CrossoverEQ/logo.png b/plugins/CrossoverEQ/logo.png deleted file mode 100644 index 9340da708..000000000 Binary files a/plugins/CrossoverEQ/logo.png and /dev/null differ diff --git a/plugins/CrossoverEQ/logo.svg b/plugins/CrossoverEQ/logo.svg new file mode 100644 index 000000000..7920fa6b0 --- /dev/null +++ b/plugins/CrossoverEQ/logo.svg @@ -0,0 +1,3 @@ + + + diff --git a/plugins/Delay/DelayControlsDialog.cpp b/plugins/Delay/DelayControlsDialog.cpp index 065b3d1e4..6d3e4a3ec 100644 --- a/plugins/Delay/DelayControlsDialog.cpp +++ b/plugins/Delay/DelayControlsDialog.cpp @@ -44,32 +44,28 @@ DelayControlsDialog::DelayControlsDialog( DelayControls *controls ) : setPalette( pal ); setFixedSize( 300, 208 ); - auto sampleDelayKnob = new TempoSyncKnob(KnobType::Bright26, this); + auto sampleDelayKnob = new TempoSyncKnob(KnobType::Bright26, tr("DELAY"), this, Knob::LabelRendering::LegacyFixedFontSize); 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(KnobType::Bright26, this); + auto feedbackKnob = new Knob(KnobType::Bright26, tr("FDBK"), this, Knob::LabelRendering::LegacyFixedFontSize); 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(KnobType::Bright26, this); + auto lfoFreqKnob = new TempoSyncKnob(KnobType::Bright26, tr("RATE"), this, Knob::LabelRendering::LegacyFixedFontSize); 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(KnobType::Bright26, this); + auto lfoAmtKnob = new TempoSyncKnob(KnobType::Bright26, tr("AMNT"), this, Knob::LabelRendering::LegacyFixedFontSize); lfoAmtKnob->move( 11, 159 ); lfoAmtKnob->setVolumeKnob( false ); lfoAmtKnob->setModel( &controls->m_lfoAmountModel ); - lfoAmtKnob->setLabel( tr( "AMNT" ) ); lfoAmtKnob->setHintText( tr ( "LFO amount" ) + " " , " s" ); auto outFader diff --git a/plugins/Delay/StereoDelay.cpp b/plugins/Delay/StereoDelay.cpp index 3187b3f2a..7381dc3e3 100644 --- a/plugins/Delay/StereoDelay.cpp +++ b/plugins/Delay/StereoDelay.cpp @@ -24,7 +24,7 @@ #include "StereoDelay.h" -#include "lmms_basics.h" +#include "LmmsTypes.h" #include "SampleFrame.h" namespace lmms diff --git a/plugins/Delay/StereoDelay.h b/plugins/Delay/StereoDelay.h index ad0e020b5..72b49a9df 100644 --- a/plugins/Delay/StereoDelay.h +++ b/plugins/Delay/StereoDelay.h @@ -25,7 +25,7 @@ #ifndef STEREODELAY_H #define STEREODELAY_H -#include "lmms_basics.h" +#include "LmmsTypes.h" namespace lmms diff --git a/plugins/Dispersion/CMakeLists.txt b/plugins/Dispersion/CMakeLists.txt index a40e04b80..190a4ae86 100644 --- a/plugins/Dispersion/CMakeLists.txt +++ b/plugins/Dispersion/CMakeLists.txt @@ -1,3 +1,3 @@ INCLUDE(BuildPlugin) -BUILD_PLUGIN(dispersion Dispersion.cpp DispersionControls.cpp DispersionControlDialog.cpp MOCFILES DispersionControls.h DispersionControlDialog.h EMBEDDED_RESOURCES *.png) +BUILD_PLUGIN(dispersion Dispersion.cpp DispersionControls.cpp DispersionControlDialog.cpp MOCFILES DispersionControls.h DispersionControlDialog.h EMBEDDED_RESOURCES logo.svg) diff --git a/plugins/Dispersion/DispersionControlDialog.cpp b/plugins/Dispersion/DispersionControlDialog.cpp index 2879e7613..aacb48290 100644 --- a/plugins/Dispersion/DispersionControlDialog.cpp +++ b/plugins/Dispersion/DispersionControlDialog.cpp @@ -24,12 +24,14 @@ #include "DispersionControlDialog.h" -#include "DispersionControls.h" +#include + +#include "AutomatableButton.h" +#include "DispersionControls.h" #include "embed.h" #include "Knob.h" #include "LcdSpinBox.h" -#include "PixmapButton.h" namespace lmms::gui @@ -40,42 +42,37 @@ DispersionControlDialog::DispersionControlDialog(DispersionControls* controls) : EffectControlDialog(controls) { setAutoFillBackground(true); - QPalette pal; - pal.setBrush(backgroundRole(), PLUGIN_NAME::getIconPixmap("artwork")); - setPalette(pal); - setFixedSize(207, 50); + auto layout = new QHBoxLayout(this); + layout->setSpacing(5); - 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")); + auto amountBox = new LcdSpinBox(3, this, "Amount"); + amountBox->setModel(&controls->m_amountModel); + amountBox->setLabel(tr("AMOUNT")); + amountBox->setToolTip(tr("Number of all-pass filters")); + layout->addWidget(amountBox); - Knob * freqKnob = new Knob(KnobType::Bright26, this); - freqKnob->move(59, 8); + auto freqKnob = new Knob(KnobType::Bright26, tr("FREQ"), this); freqKnob->setModel(&controls->m_freqModel); - freqKnob->setLabel(tr("FREQ")); - freqKnob->setHintText(tr("Frequency:") , " Hz"); + freqKnob->setHintText(tr("Frequency:") , tr("Hz")); + layout->addWidget(freqKnob); - Knob * resoKnob = new Knob(KnobType::Bright26, this); - resoKnob->move(99, 8); + auto resoKnob = new Knob(KnobType::Bright26, tr("RESO"), this); resoKnob->setModel(&controls->m_resoModel); - resoKnob->setLabel(tr("RESO")); - resoKnob->setHintText(tr("Resonance:") , " octaves"); + resoKnob->setHintText(tr("Resonance:") , tr("octaves")); + layout->addWidget(resoKnob); - Knob * feedbackKnob = new Knob(KnobType::Bright26, this); - feedbackKnob->move(139, 8); + auto feedbackKnob = new Knob(KnobType::Bright26, tr("FEED"), this); feedbackKnob->setModel(&controls->m_feedbackModel); - feedbackKnob->setLabel(tr("FEED")); feedbackKnob->setHintText(tr("Feedback:") , ""); + layout->addWidget(feedbackKnob); - 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")); + auto dcButton = new AutomatableButton(this, tr("DC Offset Removal")); dcButton->setCheckable(true); + dcButton->setText(tr("DC")); dcButton->setModel(&controls->m_dcModel); dcButton->setToolTip(tr("Remove DC Offset")); + dcButton->setObjectName("btn"); + layout->addWidget(dcButton); } diff --git a/plugins/Dispersion/artwork.png b/plugins/Dispersion/artwork.png deleted file mode 100644 index 17e3b9a11..000000000 Binary files a/plugins/Dispersion/artwork.png and /dev/null differ diff --git a/plugins/Dispersion/dc_active.png b/plugins/Dispersion/dc_active.png deleted file mode 100644 index d9c8c9378..000000000 Binary files a/plugins/Dispersion/dc_active.png and /dev/null differ diff --git a/plugins/Dispersion/dc_inactive.png b/plugins/Dispersion/dc_inactive.png deleted file mode 100644 index 9a0ee0693..000000000 Binary files a/plugins/Dispersion/dc_inactive.png and /dev/null differ diff --git a/plugins/Dispersion/logo.png b/plugins/Dispersion/logo.png deleted file mode 100644 index 9340da708..000000000 Binary files a/plugins/Dispersion/logo.png and /dev/null differ diff --git a/plugins/Dispersion/logo.svg b/plugins/Dispersion/logo.svg new file mode 100644 index 000000000..7920fa6b0 --- /dev/null +++ b/plugins/Dispersion/logo.svg @@ -0,0 +1,3 @@ + + + diff --git a/plugins/DualFilter/CMakeLists.txt b/plugins/DualFilter/CMakeLists.txt index 9aca1a7d5..a9839b4db 100644 --- a/plugins/DualFilter/CMakeLists.txt +++ b/plugins/DualFilter/CMakeLists.txt @@ -1,3 +1,3 @@ INCLUDE(BuildPlugin) -BUILD_PLUGIN(dualfilter DualFilter.cpp DualFilterControls.cpp DualFilterControlDialog.cpp MOCFILES DualFilterControls.h DualFilterControlDialog.h EMBEDDED_RESOURCES artwork.png logo.png) +BUILD_PLUGIN(dualfilter DualFilter.cpp DualFilterControls.cpp DualFilterControlDialog.cpp MOCFILES DualFilterControls.h DualFilterControlDialog.h EMBEDDED_RESOURCES artwork.svg logo.svg) diff --git a/plugins/DualFilter/DualFilterControlDialog.cpp b/plugins/DualFilter/DualFilterControlDialog.cpp index a674a4a42..6d051b256 100644 --- a/plugins/DualFilter/DualFilterControlDialog.cpp +++ b/plugins/DualFilter/DualFilterControlDialog.cpp @@ -26,6 +26,7 @@ #include "DualFilterControlDialog.h" #include "DualFilterControls.h" +#include "FontHelper.h" #include "Knob.h" #include "LedCheckBox.h" #include "ComboBox.h" @@ -35,10 +36,9 @@ namespace lmms::gui #define makeknob( name, x, y, model, label, hint, unit ) \ - Knob * name = new Knob( KnobType::Bright26, this); \ + Knob * name = new Knob(KnobType::Bright26, label, SMALL_FONT_SIZE, this); \ (name) -> move( x, y ); \ (name) ->setModel( &controls-> model ); \ - (name) ->setLabel( label ); \ (name) ->setHintText( hint, unit ); diff --git a/plugins/DualFilter/artwork.png b/plugins/DualFilter/artwork.png deleted file mode 100644 index 00458f101..000000000 Binary files a/plugins/DualFilter/artwork.png and /dev/null differ diff --git a/plugins/DualFilter/artwork.svg b/plugins/DualFilter/artwork.svg new file mode 100644 index 000000000..d26ec9113 --- /dev/null +++ b/plugins/DualFilter/artwork.svg @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/plugins/DualFilter/logo.png b/plugins/DualFilter/logo.png deleted file mode 100644 index 9340da708..000000000 Binary files a/plugins/DualFilter/logo.png and /dev/null differ diff --git a/plugins/DualFilter/logo.svg b/plugins/DualFilter/logo.svg new file mode 100644 index 000000000..7920fa6b0 --- /dev/null +++ b/plugins/DualFilter/logo.svg @@ -0,0 +1,3 @@ + + + diff --git a/plugins/DynamicsProcessor/DynamicsProcessorControlDialog.cpp b/plugins/DynamicsProcessor/DynamicsProcessorControlDialog.cpp index bd076b946..62838d5f4 100644 --- a/plugins/DynamicsProcessor/DynamicsProcessorControlDialog.cpp +++ b/plugins/DynamicsProcessor/DynamicsProcessorControlDialog.cpp @@ -28,6 +28,7 @@ #include "DynamicsProcessorControlDialog.h" #include "DynamicsProcessorControls.h" #include "embed.h" +#include "FontHelper.h" #include "Graph.h" #include "Knob.h" #include "PixmapButton.h" @@ -58,32 +59,28 @@ DynProcControlDialog::DynProcControlDialog( waveGraph->setGraphColor( QColor( 85, 204, 145 ) ); waveGraph -> setMaximumSize( 204, 205 ); - auto inputKnob = new Knob(KnobType::Bright26, this); + auto inputKnob = new Knob(KnobType::Bright26, tr("INPUT"), SMALL_FONT_SIZE, this); inputKnob -> setVolumeKnob( true ); inputKnob -> setVolumeRatio( 1.0 ); inputKnob -> move( 26, 223 ); inputKnob->setModel( &_controls->m_inputModel ); - inputKnob->setLabel( tr( "INPUT" ) ); inputKnob->setHintText( tr( "Input gain:" ) , "" ); - auto outputKnob = new Knob(KnobType::Bright26, this); + auto outputKnob = new Knob(KnobType::Bright26, tr("OUTPUT"), SMALL_FONT_SIZE, this); outputKnob -> setVolumeKnob( true ); outputKnob -> setVolumeRatio( 1.0 ); outputKnob -> move( 76, 223 ); outputKnob->setModel( &_controls->m_outputModel ); - outputKnob->setLabel( tr( "OUTPUT" ) ); outputKnob->setHintText( tr( "Output gain:" ) , "" ); - auto attackKnob = new Knob(KnobType::Bright26, this); + auto attackKnob = new Knob(KnobType::Bright26, tr("ATTACK"), SMALL_FONT_SIZE, 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(KnobType::Bright26, this); + auto releaseKnob = new Knob(KnobType::Bright26, tr("RELEASE"), SMALL_FONT_SIZE, this); releaseKnob -> move( 74, 268 ); releaseKnob->setModel( &_controls->m_releaseModel ); - releaseKnob->setLabel( tr( "RELEASE" ) ); releaseKnob->setHintText( tr( "Peak release time:" ) , "ms" ); //wavegraph control buttons diff --git a/plugins/Eq/EqSpectrumView.h b/plugins/Eq/EqSpectrumView.h index 448c0e378..8246c89bb 100644 --- a/plugins/Eq/EqSpectrumView.h +++ b/plugins/Eq/EqSpectrumView.h @@ -27,7 +27,7 @@ #include #include "fft_helpers.h" -#include "lmms_basics.h" +#include "LmmsTypes.h" namespace lmms { diff --git a/plugins/Flanger/CMakeLists.txt b/plugins/Flanger/CMakeLists.txt index e74b2838f..54ecdd85e 100644 --- a/plugins/Flanger/CMakeLists.txt +++ b/plugins/Flanger/CMakeLists.txt @@ -3,5 +3,5 @@ INCLUDE(BuildPlugin) BUILD_PLUGIN( flanger FlangerEffect.cpp FlangerControls.cpp FlangerControlsDialog.cpp MonoDelay.cpp MOCFILES FlangerControls.h FlangerControlsDialog.h - EMBEDDED_RESOURCES artwork.png logo.png + EMBEDDED_RESOURCES artwork.svg logo.svg ) diff --git a/plugins/Flanger/FlangerControlsDialog.cpp b/plugins/Flanger/FlangerControlsDialog.cpp index 3ac5dc9c6..e39b122ce 100644 --- a/plugins/Flanger/FlangerControlsDialog.cpp +++ b/plugins/Flanger/FlangerControlsDialog.cpp @@ -29,6 +29,8 @@ #include "LedCheckBox.h" #include "TempoSyncKnob.h" +#include + namespace lmms::gui { @@ -40,55 +42,52 @@ FlangerControlsDialog::FlangerControlsDialog( FlangerControls *controls ) : QPalette pal; pal.setBrush( backgroundRole(), PLUGIN_NAME::getIconPixmap( "artwork" ) ); setPalette( pal ); - setFixedSize( 233, 75 ); - auto delayKnob = new Knob(KnobType::Bright26, this); - delayKnob->move( 10,10 ); + auto mainLayout = new QVBoxLayout(this); + auto knobLayout = new QHBoxLayout(); + mainLayout->addLayout(knobLayout); + + auto delayKnob = new Knob(KnobType::Bright26, tr("DELAY"), this); delayKnob->setVolumeKnob( false ); delayKnob->setModel( &controls->m_delayTimeModel ); - delayKnob->setLabel( tr( "DELAY" ) ); delayKnob->setHintText( tr( "Delay time:" ) + " ", "s" ); - auto lfoFreqKnob = new TempoSyncKnob(KnobType::Bright26, this); - lfoFreqKnob->move( 48,10 ); + auto lfoFreqKnob = new TempoSyncKnob(KnobType::Bright26, tr("RATE"), this); lfoFreqKnob->setVolumeKnob( false ); lfoFreqKnob->setModel( &controls->m_lfoFrequencyModel ); - lfoFreqKnob->setLabel( tr( "RATE" ) ); lfoFreqKnob->setHintText( tr( "Period:" ) , " Sec" ); - auto lfoAmtKnob = new Knob(KnobType::Bright26, this); - lfoAmtKnob->move( 85,10 ); + auto lfoAmtKnob = new Knob(KnobType::Bright26, tr("AMNT"), this); lfoAmtKnob->setVolumeKnob( false ); lfoAmtKnob->setModel( &controls->m_lfoAmountModel ); - lfoAmtKnob->setLabel( tr( "AMNT" ) ); lfoAmtKnob->setHintText( tr( "Amount:" ) , "" ); - auto lfoPhaseKnob = new Knob(KnobType::Bright26, this); - lfoPhaseKnob->move( 123,10 ); + auto lfoPhaseKnob = new Knob(KnobType::Bright26, tr("PHASE"), this); lfoPhaseKnob->setVolumeKnob( false ); lfoPhaseKnob->setModel( &controls->m_lfoPhaseModel ); - lfoPhaseKnob->setLabel( tr( "PHASE" ) ); lfoPhaseKnob->setHintText( tr( "Phase:" ) , " degrees" ); - auto feedbackKnob = new Knob(KnobType::Bright26, this); - feedbackKnob->move( 160,10 ); + auto feedbackKnob = new Knob(KnobType::Bright26, tr("FDBK"), this); feedbackKnob->setVolumeKnob( true) ; feedbackKnob->setModel( &controls->m_feedbackModel ); - feedbackKnob->setLabel( tr( "FDBK" ) ); feedbackKnob->setHintText( tr( "Feedback amount:" ) , "" ); - auto whiteNoiseKnob = new Knob(KnobType::Bright26, this); - whiteNoiseKnob->move( 196,10 ); + auto whiteNoiseKnob = new Knob(KnobType::Bright26, tr("NOISE"), this); whiteNoiseKnob->setVolumeKnob( true) ; whiteNoiseKnob->setModel( &controls->m_whiteNoiseAmountModel ); - whiteNoiseKnob->setLabel( tr( "NOISE" ) ); whiteNoiseKnob->setHintText( tr( "White noise amount:" ) , "" ); + knobLayout->addWidget(delayKnob); + knobLayout->addWidget(lfoFreqKnob); + knobLayout->addWidget(lfoAmtKnob); + knobLayout->addWidget(lfoPhaseKnob); + knobLayout->addWidget(feedbackKnob); + knobLayout->addWidget(whiteNoiseKnob); + auto invertCb = new LedCheckBox(tr("Invert"), this); - invertCb->move( 10,53 ); - - - + invertCb->setModel(&controls->m_invertFeedbackModel); + + mainLayout->addWidget(invertCb, 0, Qt::AlignLeft); } diff --git a/plugins/Flanger/MonoDelay.h b/plugins/Flanger/MonoDelay.h index 2e9324a7d..95bf460c5 100644 --- a/plugins/Flanger/MonoDelay.h +++ b/plugins/Flanger/MonoDelay.h @@ -25,7 +25,7 @@ #ifndef MONODELAY_H #define MONODELAY_H -#include "lmms_basics.h" +#include "LmmsTypes.h" namespace lmms { diff --git a/plugins/Flanger/artwork.png b/plugins/Flanger/artwork.png deleted file mode 100644 index bbe80e598..000000000 Binary files a/plugins/Flanger/artwork.png and /dev/null differ diff --git a/plugins/Flanger/artwork.svg b/plugins/Flanger/artwork.svg new file mode 100644 index 000000000..e2aaa2862 --- /dev/null +++ b/plugins/Flanger/artwork.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/plugins/Flanger/logo.png b/plugins/Flanger/logo.png deleted file mode 100644 index 9340da708..000000000 Binary files a/plugins/Flanger/logo.png and /dev/null differ diff --git a/plugins/Flanger/logo.svg b/plugins/Flanger/logo.svg new file mode 100644 index 000000000..7920fa6b0 --- /dev/null +++ b/plugins/Flanger/logo.svg @@ -0,0 +1,3 @@ + + + diff --git a/plugins/LOMM/LOMMControlDialog.cpp b/plugins/LOMM/LOMMControlDialog.cpp index e53987a05..b3cf34506 100644 --- a/plugins/LOMM/LOMMControlDialog.cpp +++ b/plugins/LOMM/LOMMControlDialog.cpp @@ -47,24 +47,24 @@ LOMMControlDialog::LOMMControlDialog(LOMMControls* controls) : 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")); @@ -78,27 +78,27 @@ LOMMControlDialog::LOMMControlDialog(LOMMControls* controls) : 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")); @@ -107,14 +107,15 @@ LOMMControlDialog::LOMMControlDialog(LOMMControls* controls) : 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")); - + initButton->setCheckable(false); + 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(); } @@ -161,7 +162,7 @@ void LOMMControlDialog::paintEvent(QPaintEvent *event) 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) { @@ -239,10 +240,10 @@ void LOMMControlDialog::mouseMoveEvent(QMouseEvent * event) 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) diff --git a/plugins/LadspaEffect/LadspaMatrixControlDialog.h b/plugins/LadspaEffect/LadspaMatrixControlDialog.h index c5949fa15..fa9a6e1b3 100644 --- a/plugins/LadspaEffect/LadspaMatrixControlDialog.h +++ b/plugins/LadspaEffect/LadspaMatrixControlDialog.h @@ -28,7 +28,7 @@ #include "EffectControlDialog.h" -#include "lmms_basics.h" +#include "LmmsTypes.h" class QGridLayout; diff --git a/plugins/Lb302/Lb302.cpp b/plugins/Lb302/Lb302.cpp index 5a085df4d..ab910d134 100644 --- a/plugins/Lb302/Lb302.cpp +++ b/plugins/Lb302/Lb302.cpp @@ -831,22 +831,18 @@ Lb302SynthView::Lb302SynthView( Instrument * _instrument, QWidget * _parent ) : 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( KnobType::Bright26, this ); m_vcfResKnob->move( 120, 130 ); m_vcfResKnob->setHintText( tr( "Resonance:" ), "" ); - m_vcfResKnob->setLabel( "" ); 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( KnobType::Bright26, this ); m_vcfDecKnob->move( 210, 130 ); m_vcfDecKnob->setHintText( tr( "Decay:" ), "" ); - m_vcfDecKnob->setLabel( "" ); m_slideToggle = new LedCheckBox( "", this ); m_slideToggle->move( 10, 180 ); @@ -867,12 +863,10 @@ Lb302SynthView::Lb302SynthView( Instrument * _instrument, QWidget * _parent ) : 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( KnobType::Bright26, this ); m_distKnob->move( 210, 190 ); m_distKnob->setHintText( tr( "DIST:" ), "" ); - m_distKnob->setLabel( tr( "")); // Shapes diff --git a/plugins/MidiImport/MidiImport.cpp b/plugins/MidiImport/MidiImport.cpp index 785cd079a..24fed85df 100644 --- a/plugins/MidiImport/MidiImport.cpp +++ b/plugins/MidiImport/MidiImport.cpp @@ -43,7 +43,6 @@ #include "GuiApplication.h" #include "MainWindow.h" #include "TimePos.h" -#include "debug.h" #include "Song.h" #include "plugin_export.h" diff --git a/plugins/MultitapEcho/MultitapEcho.cpp b/plugins/MultitapEcho/MultitapEcho.cpp index 3d92e5ae8..ab84138aa 100644 --- a/plugins/MultitapEcho/MultitapEcho.cpp +++ b/plugins/MultitapEcho/MultitapEcho.cpp @@ -25,7 +25,7 @@ #include "MultitapEcho.h" #include "embed.h" -#include "lmms_basics.h" +#include "LmmsTypes.h" #include "lmms_math.h" #include "plugin_export.h" diff --git a/plugins/MultitapEcho/MultitapEchoControlDialog.cpp b/plugins/MultitapEcho/MultitapEchoControlDialog.cpp index e89137bf0..8a7c40a37 100644 --- a/plugins/MultitapEcho/MultitapEchoControlDialog.cpp +++ b/plugins/MultitapEcho/MultitapEchoControlDialog.cpp @@ -27,6 +27,7 @@ #include "MultitapEchoControlDialog.h" #include "MultitapEchoControls.h" #include "embed.h" +#include "FontHelper.h" #include "Graph.h" #include "LedCheckBox.h" #include "Knob.h" @@ -78,22 +79,19 @@ MultitapEchoControlDialog::MultitapEchoControlDialog( MultitapEchoControls * con // knobs - auto stepLength = new TempoSyncKnob(KnobType::Bright26, this); + auto stepLength = new TempoSyncKnob(KnobType::Bright26, tr("Length"), SMALL_FONT_SIZE, this); stepLength->move( 100, 245 ); stepLength->setModel( & controls->m_stepLength ); - stepLength->setLabel( tr( "Length" ) ); stepLength->setHintText( tr( "Step length:" ) , " ms" ); - auto dryGain = new Knob(KnobType::Bright26, this); + auto dryGain = new Knob(KnobType::Bright26, tr("Dry"), SMALL_FONT_SIZE, this); dryGain->move( 150, 245 ); dryGain->setModel( & controls->m_dryGain ); - dryGain->setLabel( tr( "Dry" ) ); dryGain->setHintText( tr( "Dry gain:" ) , " dBFS" ); - auto stages = new Knob(KnobType::Bright26, this); + auto stages = new Knob(KnobType::Bright26, tr("Stages"), SMALL_FONT_SIZE, this); stages->move( 200, 245 ); stages->setModel( & controls->m_stages ); - stages->setLabel( tr( "Stages" ) ); stages->setHintText( tr( "Low-pass stages:" ) , "x" ); // switch led diff --git a/plugins/OpulenZ/OpulenZ.cpp b/plugins/OpulenZ/OpulenZ.cpp index 48dfb6c8c..f5c0e1a8d 100644 --- a/plugins/OpulenZ/OpulenZ.cpp +++ b/plugins/OpulenZ/OpulenZ.cpp @@ -51,7 +51,6 @@ #include #include "embed.h" -#include "debug.h" #include "Knob.h" #include "PixmapButton.h" diff --git a/plugins/PeakControllerEffect/CMakeLists.txt b/plugins/PeakControllerEffect/CMakeLists.txt index 0bdb73123..eca93f671 100644 --- a/plugins/PeakControllerEffect/CMakeLists.txt +++ b/plugins/PeakControllerEffect/CMakeLists.txt @@ -1,3 +1,3 @@ INCLUDE(BuildPlugin) -BUILD_PLUGIN(peakcontrollereffect PeakControllerEffect.cpp PeakControllerEffectControls.cpp PeakControllerEffectControlDialog.cpp PeakControllerEffect.h PeakControllerEffectControls.h PeakControllerEffectControlDialog.h MOCFILES PeakControllerEffectControls.h PeakControllerEffectControlDialog.h EMBEDDED_RESOURCES artwork.png logo.png) +BUILD_PLUGIN(peakcontrollereffect PeakControllerEffect.cpp PeakControllerEffectControls.cpp PeakControllerEffectControlDialog.cpp PeakControllerEffect.h PeakControllerEffectControls.h PeakControllerEffectControlDialog.h MOCFILES PeakControllerEffectControls.h PeakControllerEffectControlDialog.h EMBEDDED_RESOURCES artwork.svg logo.svg) diff --git a/plugins/PeakControllerEffect/PeakControllerEffect.cpp b/plugins/PeakControllerEffect/PeakControllerEffect.cpp index b6d053257..394a80efd 100644 --- a/plugins/PeakControllerEffect/PeakControllerEffect.cpp +++ b/plugins/PeakControllerEffect/PeakControllerEffect.cpp @@ -132,18 +132,8 @@ Effect::ProcessStatus PeakControllerEffect::processImpl(SampleFrame* buf, const float curRMS = sqrt_neg(sum / frames); const float tres = c.m_tresholdModel.value(); const float amount = c.m_amountModel.value() * c.m_amountMultModel.value(); - const float attack = 1.0f - c.m_attackModel.value(); - const float decay = 1.0f - c.m_decayModel.value(); - curRMS = qAbs( curRMS ) < tres ? 0.0f : curRMS; - float target = c.m_baseModel.value() + amount * curRMS; - // Use decay when the volume is decreasing, attack otherwise. - // Since direction can change as often as every sampleBuffer, it's difficult - // to witness attack/decay working in isolation unless using large buffer sizes. - const float t = target < m_lastSample ? decay : attack; - // Set m_lastSample to the interpolation between itself and target. - // When t is 1.0, m_lastSample snaps to target. When t is 0.0, m_lastSample shouldn't change. - m_lastSample = std::clamp(m_lastSample + t * (target - m_lastSample), 0.0f, 1.0f); + m_lastSample = qBound( 0.0f, c.m_baseModel.value() + amount * curRMS, 1.0f ); return ProcessStatus::Continue; } diff --git a/plugins/PeakControllerEffect/PeakControllerEffectControlDialog.cpp b/plugins/PeakControllerEffect/PeakControllerEffectControlDialog.cpp index e44d5bcc2..7ce05bc45 100644 --- a/plugins/PeakControllerEffect/PeakControllerEffectControlDialog.cpp +++ b/plugins/PeakControllerEffect/PeakControllerEffectControlDialog.cpp @@ -47,35 +47,28 @@ PeakControllerEffectControlDialog::PeakControllerEffectControlDialog( QPalette pal; pal.setBrush( backgroundRole(), PLUGIN_NAME::getIconPixmap( "artwork" ) ); setPalette( pal ); - setFixedSize( 240, 80 ); - m_baseKnob = new Knob( KnobType::Bright26, this ); - m_baseKnob->setLabel( tr( "BASE" ) ); + m_baseKnob = new Knob(KnobType::Bright26, tr("BASE"), this); m_baseKnob->setModel( &_controls->m_baseModel ); m_baseKnob->setHintText( tr( "Base:" ) , "" ); - m_amountKnob = new Knob( KnobType::Bright26, this ); - m_amountKnob->setLabel( tr( "AMNT" ) ); + m_amountKnob = new Knob(KnobType::Bright26, tr("AMNT"), this); m_amountKnob->setModel( &_controls->m_amountModel ); m_amountKnob->setHintText( tr( "Modulation amount:" ) , "" ); - m_amountMultKnob = new Knob( KnobType::Bright26, this ); - m_amountMultKnob->setLabel( tr( "MULT" ) ); + m_amountMultKnob = new Knob(KnobType::Bright26, tr("MULT"), this); m_amountMultKnob->setModel( &_controls->m_amountMultModel ); m_amountMultKnob->setHintText( tr( "Amount multiplicator:" ) , "" ); - m_attackKnob = new Knob( KnobType::Bright26, this ); - m_attackKnob->setLabel( tr( "ATCK" ) ); + m_attackKnob = new Knob(KnobType::Bright26, tr("ATCK"), this); m_attackKnob->setModel( &_controls->m_attackModel ); m_attackKnob->setHintText( tr( "Attack:" ) , "" ); - m_decayKnob = new Knob( KnobType::Bright26, this ); - m_decayKnob->setLabel( tr( "DCAY" ) ); + m_decayKnob = new Knob(KnobType::Bright26, tr("DCAY"), this); m_decayKnob->setModel( &_controls->m_decayModel ); m_decayKnob->setHintText( tr( "Release:" ) , "" ); - m_tresholdKnob = new Knob( KnobType::Bright26, this ); - m_tresholdKnob->setLabel( tr( "TRSH" ) ); + m_tresholdKnob = new Knob(KnobType::Bright26, tr("TRSH"), this); m_tresholdKnob->setModel( &_controls->m_tresholdModel ); m_tresholdKnob->setHintText( tr( "Treshold:" ) , "" ); @@ -99,7 +92,6 @@ PeakControllerEffectControlDialog::PeakControllerEffectControlDialog( ledLayout->addWidget( m_muteLed ); ledLayout->addWidget( m_absLed ); - mainLayout->setContentsMargins( 3, 10, 0, 0 ); mainLayout->addLayout( knobLayout ); mainLayout->addLayout( ledLayout ); diff --git a/plugins/PeakControllerEffect/artwork.png b/plugins/PeakControllerEffect/artwork.png deleted file mode 100644 index 5977f83cc..000000000 Binary files a/plugins/PeakControllerEffect/artwork.png and /dev/null differ diff --git a/plugins/PeakControllerEffect/artwork.svg b/plugins/PeakControllerEffect/artwork.svg new file mode 100644 index 000000000..b3e7ba076 --- /dev/null +++ b/plugins/PeakControllerEffect/artwork.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/plugins/PeakControllerEffect/logo.png b/plugins/PeakControllerEffect/logo.png deleted file mode 100644 index 9340da708..000000000 Binary files a/plugins/PeakControllerEffect/logo.png and /dev/null differ diff --git a/plugins/PeakControllerEffect/logo.svg b/plugins/PeakControllerEffect/logo.svg new file mode 100644 index 000000000..7920fa6b0 --- /dev/null +++ b/plugins/PeakControllerEffect/logo.svg @@ -0,0 +1,3 @@ + + + diff --git a/plugins/ReverbSC/CMakeLists.txt b/plugins/ReverbSC/CMakeLists.txt index f6f6c7ee7..4c0d7623c 100644 --- a/plugins/ReverbSC/CMakeLists.txt +++ b/plugins/ReverbSC/CMakeLists.txt @@ -15,5 +15,5 @@ BUILD_PLUGIN( MOCFILES ReverbSCControls.h ReverbSCControlDialog.h - EMBEDDED_RESOURCES artwork.png logo.png + EMBEDDED_RESOURCES artwork.svg logo.svg ) diff --git a/plugins/ReverbSC/ReverbSCControlDialog.cpp b/plugins/ReverbSC/ReverbSCControlDialog.cpp index 615d3823e..3be156396 100644 --- a/plugins/ReverbSC/ReverbSCControlDialog.cpp +++ b/plugins/ReverbSC/ReverbSCControlDialog.cpp @@ -29,6 +29,8 @@ #include "Knob.h" #include "ReverbSCControls.h" +#include + namespace lmms::gui { @@ -40,31 +42,29 @@ ReverbSCControlDialog::ReverbSCControlDialog( ReverbSCControls* controls ) : QPalette pal; pal.setBrush( backgroundRole(), PLUGIN_NAME::getIconPixmap( "artwork" ) ); setPalette( pal ); - setFixedSize( 185, 55 ); - auto inputGainKnob = new Knob(KnobType::Bright26, this); - inputGainKnob -> move( 16, 10 ); + auto knobLayout = new QHBoxLayout(this); + + auto inputGainKnob = new Knob(KnobType::Bright26, tr("Input"), this); inputGainKnob->setModel( &controls->m_inputGainModel ); - inputGainKnob->setLabel( tr( "Input" ) ); inputGainKnob->setHintText( tr( "Input gain:" ) , "dB" ); - auto sizeKnob = new Knob(KnobType::Bright26, this); - sizeKnob -> move( 57, 10 ); + auto sizeKnob = new Knob(KnobType::Bright26, tr("Size"), this); sizeKnob->setModel( &controls->m_sizeModel ); - sizeKnob->setLabel( tr( "Size" ) ); sizeKnob->setHintText( tr( "Size:" ) , "" ); - auto colorKnob = new Knob(KnobType::Bright26, this); - colorKnob -> move( 98, 10 ); + auto colorKnob = new Knob(KnobType::Bright26, tr("Color"), this); colorKnob->setModel( &controls->m_colorModel ); - colorKnob->setLabel( tr( "Color" ) ); colorKnob->setHintText( tr( "Color:" ) , "" ); - auto outputGainKnob = new Knob(KnobType::Bright26, this); - outputGainKnob -> move( 139, 10 ); + auto outputGainKnob = new Knob(KnobType::Bright26, tr("Output"), this); outputGainKnob->setModel( &controls->m_outputGainModel ); - outputGainKnob->setLabel( tr( "Output" ) ); outputGainKnob->setHintText( tr( "Output gain:" ) , "dB" ); + + knobLayout->addWidget(inputGainKnob); + knobLayout->addWidget(sizeKnob); + knobLayout->addWidget(colorKnob); + knobLayout->addWidget(outputGainKnob); } diff --git a/plugins/ReverbSC/artwork.png b/plugins/ReverbSC/artwork.png deleted file mode 100644 index 6fe5168fa..000000000 Binary files a/plugins/ReverbSC/artwork.png and /dev/null differ diff --git a/plugins/ReverbSC/artwork.svg b/plugins/ReverbSC/artwork.svg new file mode 100644 index 000000000..04f0bb87e --- /dev/null +++ b/plugins/ReverbSC/artwork.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/plugins/ReverbSC/logo.png b/plugins/ReverbSC/logo.png deleted file mode 100644 index 9340da708..000000000 Binary files a/plugins/ReverbSC/logo.png and /dev/null differ diff --git a/plugins/ReverbSC/logo.svg b/plugins/ReverbSC/logo.svg new file mode 100644 index 000000000..7920fa6b0 --- /dev/null +++ b/plugins/ReverbSC/logo.svg @@ -0,0 +1,3 @@ + + + diff --git a/plugins/SlicerT/SlicerT.h b/plugins/SlicerT/SlicerT.h index 53b8bfb2a..6021f44a3 100644 --- a/plugins/SlicerT/SlicerT.h +++ b/plugins/SlicerT/SlicerT.h @@ -36,7 +36,7 @@ #include "Sample.h" #include "SampleBuffer.h" #include "SlicerTView.h" -#include "lmms_basics.h" +#include "LmmsTypes.h" namespace lmms { diff --git a/plugins/SlicerT/SlicerTView.cpp b/plugins/SlicerT/SlicerTView.cpp index 7af2db143..1fc3effe7 100644 --- a/plugins/SlicerT/SlicerTView.cpp +++ b/plugins/SlicerT/SlicerTView.cpp @@ -71,6 +71,12 @@ SlicerTView::SlicerTView(SlicerT* instrument, QWidget* parent) m_syncToggle->setToolTip(tr("Enable BPM sync")); m_syncToggle->setModel(&m_slicerTParent->m_enableSync); + m_clearButton = new PixmapButton(this, tr("Clear all slices")); + m_clearButton->setActiveGraphic(PLUGIN_NAME::getIconPixmap("clear_slices_active")); + m_clearButton->setInactiveGraphic(PLUGIN_NAME::getIconPixmap("clear_slices_inactive")); + m_clearButton->setToolTip(tr("Clear all slices")); + connect(m_clearButton, &PixmapButton::clicked, this, &SlicerTView::clearSlices); + m_bpmBox = new LcdSpinBox(3, "19purple", this); m_bpmBox->setToolTip(tr("Original sample BPM")); m_bpmBox->setModel(&m_slicerTParent->m_originalBPM); @@ -111,6 +117,19 @@ Knob* SlicerTView::createStyledKnob() return newKnob; } +// Clear all notes +void SlicerTView::clearSlices() +{ + m_slicerTParent->m_slicePoints.clear(); + + // Points are added to the start (0) and end (1) of the sample, + // so the whole sample can still be copied using MIDI. + m_slicerTParent->m_slicePoints.emplace_back(0); + m_slicerTParent->m_slicePoints.emplace_back(1); + + emit m_slicerTParent->dataChanged(); +} + // copied from piano roll void SlicerTView::exportMidi() { @@ -261,7 +280,7 @@ void SlicerTView::resizeEvent(QResizeEvent* re) { m_y1 = height() - s_bottomBoxOffset; - // left box + // Left box m_noteThresholdKnob->move(s_x1 - 25, m_y1); m_fadeOutKnob->move(s_x2 - 25, m_y1); @@ -271,8 +290,10 @@ void SlicerTView::resizeEvent(QResizeEvent* re) m_bpmBox->move(s_x5 - 13, m_y1 + 4); m_snapSetting->move(s_x6 - 8, m_y1 + 3); - // right box - m_syncToggle->move((width() - 100), m_y1 + 5); + // Right box + // For explanation on the choice of constants, look at #7850 + m_syncToggle->move((width() - 100), m_y1 - 7); + m_clearButton->move((width() - 100), m_y1 + 17); m_folderButton->move(width() - 20, height() - s_bottomBoxHeight - s_sampleBoxHeight + 1); diff --git a/plugins/SlicerT/SlicerTView.h b/plugins/SlicerT/SlicerTView.h index f24246621..c4d0da959 100644 --- a/plugins/SlicerT/SlicerTView.h +++ b/plugins/SlicerT/SlicerTView.h @@ -49,6 +49,7 @@ class SlicerTView : public InstrumentView public slots: void exportMidi(); void openFiles(); + void clearSlices(); public: SlicerTView(SlicerT* instrument, QWidget* parent); @@ -87,6 +88,7 @@ private: LcdSpinBox* m_bpmBox; ComboBox* m_snapSetting; PixmapButton* m_syncToggle; + PixmapButton* m_clearButton; PixmapButton* m_folderButton; QPushButton* m_resetButton; diff --git a/plugins/SlicerT/clear_slices_active.png b/plugins/SlicerT/clear_slices_active.png new file mode 100644 index 000000000..36b7f0f7e Binary files /dev/null and b/plugins/SlicerT/clear_slices_active.png differ diff --git a/plugins/SlicerT/clear_slices_inactive.png b/plugins/SlicerT/clear_slices_inactive.png new file mode 100644 index 000000000..b39fb4974 Binary files /dev/null and b/plugins/SlicerT/clear_slices_inactive.png differ diff --git a/plugins/SpectrumAnalyzer/Analyzer.cpp b/plugins/SpectrumAnalyzer/Analyzer.cpp index 7b6086ed2..725c175a4 100644 --- a/plugins/SpectrumAnalyzer/Analyzer.cpp +++ b/plugins/SpectrumAnalyzer/Analyzer.cpp @@ -33,7 +33,7 @@ #endif #include "embed.h" -#include "lmms_basics.h" +#include "LmmsTypes.h" #include "plugin_export.h" namespace lmms diff --git a/plugins/SpectrumAnalyzer/SaControlsDialog.cpp b/plugins/SpectrumAnalyzer/SaControlsDialog.cpp index 8b2c0f35e..9645f7b83 100644 --- a/plugins/SpectrumAnalyzer/SaControlsDialog.cpp +++ b/plugins/SpectrumAnalyzer/SaControlsDialog.cpp @@ -32,6 +32,7 @@ #include "ComboBox.h" #include "ComboBoxModel.h" +#include "FontHelper.h" #include "Knob.h" #include "LedCheckBox.h" #include "PixmapButton.h" @@ -236,41 +237,36 @@ SaControlsDialog::SaControlsDialog(SaControls *controls, SaProcessor *processor) controls_layout->setStretchFactor(advanced_widget, 10); // Peak envelope resolution - auto envelopeResolutionKnob = new Knob(KnobType::Small17, this); + auto envelopeResolutionKnob = new Knob(KnobType::Small17, tr("Envelope res."), SMALL_FONT_SIZE, 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.")); envelopeResolutionKnob->setHintText(tr("Maximum number of envelope points drawn per pixel:"), ""); advanced_layout->addWidget(envelopeResolutionKnob, 0, 0, 1, 1, Qt::AlignCenter); // Spectrum graph resolution - auto spectrumResolutionKnob = new Knob(KnobType::Small17, this); + auto spectrumResolutionKnob = new Knob(KnobType::Small17, tr("Spectrum res."), SMALL_FONT_SIZE, 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.")); spectrumResolutionKnob->setHintText(tr("Maximum number of spectrum points drawn per pixel:"), ""); advanced_layout->addWidget(spectrumResolutionKnob, 1, 0, 1, 1, Qt::AlignCenter); // Peak falloff speed - auto peakDecayFactorKnob = new Knob(KnobType::Small17, this); + auto peakDecayFactorKnob = new Knob(KnobType::Small17, tr("Falloff factor"), SMALL_FONT_SIZE, this); peakDecayFactorKnob->setModel(&controls->m_peakDecayFactorModel); - peakDecayFactorKnob->setLabel(tr("Falloff factor")); peakDecayFactorKnob->setToolTip(tr("Decrease to make peaks fall faster.")); peakDecayFactorKnob->setHintText(tr("Multiply buffered value by"), ""); advanced_layout->addWidget(peakDecayFactorKnob, 0, 1, 1, 1, Qt::AlignCenter); // Averaging weight - auto averagingWeightKnob = new Knob(KnobType::Small17, this); + auto averagingWeightKnob = new Knob(KnobType::Small17, tr("Averaging weight"), SMALL_FONT_SIZE, this); averagingWeightKnob->setModel(&controls->m_averagingWeightModel); - averagingWeightKnob->setLabel(tr("Averaging weight")); averagingWeightKnob->setToolTip(tr("Decrease to make averaging slower and smoother.")); averagingWeightKnob->setHintText(tr("New sample contributes"), ""); advanced_layout->addWidget(averagingWeightKnob, 1, 1, 1, 1, Qt::AlignCenter); // Waterfall history size - auto waterfallHeightKnob = new Knob(KnobType::Small17, this); + auto waterfallHeightKnob = new Knob(KnobType::Small17, tr("Waterfall height"), SMALL_FONT_SIZE, 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.")); waterfallHeightKnob->setHintText(tr("Number of lines to keep:"), ""); advanced_layout->addWidget(waterfallHeightKnob, 0, 2, 1, 1, Qt::AlignCenter); @@ -278,25 +274,22 @@ SaControlsDialog::SaControlsDialog(SaControls *controls, SaProcessor *processor) connect(&controls->m_waterfallHeightModel, &FloatModel::dataChanged, [=] {processor->reallocateBuffers();}); // Waterfall gamma correction - auto waterfallGammaKnob = new Knob(KnobType::Small17, this); + auto waterfallGammaKnob = new Knob(KnobType::Small17, tr("Waterfall gamma"), SMALL_FONT_SIZE, 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.")); waterfallGammaKnob->setHintText(tr("Gamma value:"), ""); advanced_layout->addWidget(waterfallGammaKnob, 1, 2, 1, 1, Qt::AlignCenter); // FFT window overlap - auto windowOverlapKnob = new Knob(KnobType::Small17, this); + auto windowOverlapKnob = new Knob(KnobType::Small17, tr("Window overlap"), SMALL_FONT_SIZE, 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.")); windowOverlapKnob->setHintText(tr("Number of times each sample is processed:"), ""); advanced_layout->addWidget(windowOverlapKnob, 0, 3, 1, 1, Qt::AlignCenter); // FFT zero padding - auto zeroPaddingKnob = new Knob(KnobType::Small17, this); + auto zeroPaddingKnob = new Knob(KnobType::Small17, tr("Zero padding"), SMALL_FONT_SIZE, this); zeroPaddingKnob->setModel(&controls->m_zeroPaddingModel); - zeroPaddingKnob->setLabel(tr("Zero padding")); zeroPaddingKnob->setToolTip(tr("Increase to get smoother-looking spectrum. Warning: high CPU usage.")); zeroPaddingKnob->setHintText(tr("Processing buffer is"), tr(" steps larger than input block")); advanced_layout->addWidget(zeroPaddingKnob, 1, 3, 1, 1, Qt::AlignCenter); diff --git a/plugins/SpectrumAnalyzer/SaProcessor.cpp b/plugins/SpectrumAnalyzer/SaProcessor.cpp index 1ea80f126..d4e5456dc 100644 --- a/plugins/SpectrumAnalyzer/SaProcessor.cpp +++ b/plugins/SpectrumAnalyzer/SaProcessor.cpp @@ -27,11 +27,9 @@ #include #include "lmms_math.h" -#ifdef SA_DEBUG - #include -#endif #include #ifdef SA_DEBUG + #include #include #include #endif diff --git a/plugins/SpectrumAnalyzer/SaProcessor.h b/plugins/SpectrumAnalyzer/SaProcessor.h index 3903bf9d6..12163f63b 100644 --- a/plugins/SpectrumAnalyzer/SaProcessor.h +++ b/plugins/SpectrumAnalyzer/SaProcessor.h @@ -33,7 +33,7 @@ #include #include -#include "lmms_basics.h" +#include "LmmsTypes.h" namespace lmms diff --git a/plugins/StereoEnhancer/StereoEnhancerControlDialog.cpp b/plugins/StereoEnhancer/StereoEnhancerControlDialog.cpp index 05c78616e..1440a4be5 100644 --- a/plugins/StereoEnhancer/StereoEnhancerControlDialog.cpp +++ b/plugins/StereoEnhancer/StereoEnhancerControlDialog.cpp @@ -40,9 +40,8 @@ StereoEnhancerControlDialog::StereoEnhancerControlDialog( { auto l = new QHBoxLayout(this); - auto widthKnob = new Knob(KnobType::Bright26, this); + auto widthKnob = new Knob(KnobType::Bright26, tr("WIDTH"), this); widthKnob->setModel( &_controls->m_widthModel ); - widthKnob->setLabel( tr( "WIDTH" ) ); widthKnob->setHintText( tr( "Width:" ) , " samples" ); l->addWidget( widthKnob ); diff --git a/plugins/StereoMatrix/CMakeLists.txt b/plugins/StereoMatrix/CMakeLists.txt index 4e6de02ca..2990f8ec8 100644 --- a/plugins/StereoMatrix/CMakeLists.txt +++ b/plugins/StereoMatrix/CMakeLists.txt @@ -1,4 +1,4 @@ INCLUDE(BuildPlugin) -BUILD_PLUGIN(stereomatrix StereoMatrix.cpp StereoMatrixControls.cpp StereoMatrixControlDialog.cpp StereoMatrix.h StereoMatrixControls.h StereoMatrixControlDialog.h MOCFILES StereoMatrixControls.h StereoMatrixControlDialog.h EMBEDDED_RESOURCES artwork.png logo.png) +BUILD_PLUGIN(stereomatrix StereoMatrix.cpp StereoMatrixControls.cpp StereoMatrixControlDialog.cpp StereoMatrix.h StereoMatrixControls.h StereoMatrixControlDialog.h MOCFILES StereoMatrixControls.h StereoMatrixControlDialog.h EMBEDDED_RESOURCES artwork.svg logo.svg) diff --git a/plugins/StereoMatrix/StereoMatrixControlDialog.cpp b/plugins/StereoMatrix/StereoMatrixControlDialog.cpp index da9a3aa9e..1530a0d25 100644 --- a/plugins/StereoMatrix/StereoMatrixControlDialog.cpp +++ b/plugins/StereoMatrix/StereoMatrixControlDialog.cpp @@ -23,10 +23,9 @@ */ - - #include "StereoMatrixControlDialog.h" +#include #include "embed.h" #include "Knob.h" #include "StereoMatrixControls.h" @@ -35,38 +34,32 @@ namespace lmms::gui { -StereoMatrixControlDialog::StereoMatrixControlDialog( - StereoMatrixControls * _controls ) : - EffectControlDialog( _controls ) +StereoMatrixControlDialog::StereoMatrixControlDialog(StereoMatrixControls* controls) : + EffectControlDialog(controls) { - - setFixedSize( 160, 185 ); - - setAutoFillBackground( true ); QPalette pal; - pal.setBrush( backgroundRole(), - PLUGIN_NAME::getIconPixmap( "artwork" ) ); - setPalette( pal ); + setAutoFillBackground(true); + setFixedSize(160, 185); + pal.setBrush(backgroundRole(), PLUGIN_NAME::getIconPixmap("artwork")); + setPalette(pal); - auto llKnob = new Knob(KnobType::Bright26, this); - llKnob->setModel( &_controls->m_llModel ); - llKnob->setHintText( tr( "Left to Left Vol:" ) , "" ); - llKnob->move( 10, 79 ); + auto layout = new QHBoxLayout(this); - auto lrKnob = new Knob(KnobType::Bright26, this); - lrKnob->setModel( &_controls->m_lrModel ); - lrKnob->setHintText( tr( "Left to Right Vol:" ) , "" ); - lrKnob->move( 48, 79 ); + const auto makeKnob = [this, layout]( + FloatModel *model, + const QString &txt_before + ) { + auto k = new Knob(KnobType::Bright26, this); + k->setModel(model); + k->setHintText(txt_before, ""); + layout->addWidget(k, 0, Qt::AlignHCenter); + return k; + }; - 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(KnobType::Bright26, this); - rrKnob->setModel( &_controls->m_rrModel ); - rrKnob->setHintText( tr( "Right to Right Vol:" ) , "" ); - rrKnob->move( 123, 79 ); + makeKnob(&controls->m_llModel, tr("Left to Left Vol:")); + makeKnob(&controls->m_lrModel, tr("Left to Right Vol:")); + makeKnob(&controls->m_rlModel, tr("Right to Left Vol:")); + makeKnob(&controls->m_rrModel, tr("Right to Right Vol:")); } diff --git a/plugins/StereoMatrix/artwork.png b/plugins/StereoMatrix/artwork.png deleted file mode 100644 index f76567fd2..000000000 Binary files a/plugins/StereoMatrix/artwork.png and /dev/null differ diff --git a/plugins/StereoMatrix/artwork.svg b/plugins/StereoMatrix/artwork.svg new file mode 100644 index 000000000..b54fa8530 --- /dev/null +++ b/plugins/StereoMatrix/artwork.svg @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/plugins/StereoMatrix/logo.png b/plugins/StereoMatrix/logo.png deleted file mode 100644 index 9340da708..000000000 Binary files a/plugins/StereoMatrix/logo.png and /dev/null differ diff --git a/plugins/StereoMatrix/logo.svg b/plugins/StereoMatrix/logo.svg new file mode 100644 index 000000000..7920fa6b0 --- /dev/null +++ b/plugins/StereoMatrix/logo.svg @@ -0,0 +1,3 @@ + + + diff --git a/plugins/Stk/Mallets/Mallets.cpp b/plugins/Stk/Mallets/Mallets.cpp index fecb15a76..5c9c11cca 100644 --- a/plugins/Stk/Mallets/Mallets.cpp +++ b/plugins/Stk/Mallets/Mallets.cpp @@ -37,6 +37,7 @@ #include "AudioEngine.h" #include "ConfigManager.h" #include "Engine.h" +#include "FontHelper.h" #include "GuiApplication.h" #include "InstrumentTrack.h" @@ -454,13 +455,11 @@ MalletsInstrumentView::MalletsInstrumentView( MalletsInstrument * _instrument, connect( &_instrument->m_presetsModel, SIGNAL( dataChanged() ), this, SLOT( changePreset() ) ); - m_spreadKnob = new Knob( KnobType::Vintage32, this ); - m_spreadKnob->setLabel( tr( "Spread" ) ); + m_spreadKnob = new Knob(KnobType::Vintage32, tr("Spread"), SMALL_FONT_SIZE, this); m_spreadKnob->move( 190, 140 ); m_spreadKnob->setHintText( tr( "Spread:" ), "" ); - m_randomKnob = new Knob(KnobType::Vintage32, this); - m_randomKnob->setLabel(tr("Random")); + m_randomKnob = new Knob(KnobType::Vintage32, tr("Random"), SMALL_FONT_SIZE, this); m_randomKnob->move(190, 190); m_randomKnob->setHintText(tr("Random:"), ""); @@ -495,28 +494,23 @@ QWidget * MalletsInstrumentView::setupModalBarControls( QWidget * _parent ) auto widget = new QWidget(_parent); widget->setFixedSize( 250, 250 ); - m_hardnessKnob = new Knob( KnobType::Vintage32, widget ); - m_hardnessKnob->setLabel( tr( "Hardness" ) ); + m_hardnessKnob = new Knob(KnobType::Vintage32, tr("Hardness"), SMALL_FONT_SIZE, widget); m_hardnessKnob->move( 30, 90 ); m_hardnessKnob->setHintText( tr( "Hardness:" ), "" ); - m_positionKnob = new Knob( KnobType::Vintage32, widget ); - m_positionKnob->setLabel( tr( "Position" ) ); + m_positionKnob = new Knob(KnobType::Vintage32, tr("Position"), SMALL_FONT_SIZE, widget); m_positionKnob->move( 110, 90 ); m_positionKnob->setHintText( tr( "Position:" ), "" ); - m_vibratoGainKnob = new Knob( KnobType::Vintage32, widget ); - m_vibratoGainKnob->setLabel( tr( "Vibrato gain" ) ); + m_vibratoGainKnob = new Knob(KnobType::Vintage32, tr("Vibrato gain"), SMALL_FONT_SIZE, widget); m_vibratoGainKnob->move( 30, 140 ); m_vibratoGainKnob->setHintText( tr( "Vibrato gain:" ), "" ); - m_vibratoFreqKnob = new Knob( KnobType::Vintage32, widget ); - m_vibratoFreqKnob->setLabel( tr( "Vibrato frequency" ) ); + m_vibratoFreqKnob = new Knob(KnobType::Vintage32, tr("Vibrato frequency"), SMALL_FONT_SIZE, widget); m_vibratoFreqKnob->move( 110, 140 ); m_vibratoFreqKnob->setHintText( tr( "Vibrato frequency:" ), "" ); - m_stickKnob = new Knob( KnobType::Vintage32, widget ); - m_stickKnob->setLabel( tr( "Stick mix" ) ); + m_stickKnob = new Knob(KnobType::Vintage32, tr("Stick mix"), SMALL_FONT_SIZE, widget); m_stickKnob->move( 190, 90 ); m_stickKnob->setHintText( tr( "Stick mix:" ), "" ); @@ -531,28 +525,23 @@ QWidget * MalletsInstrumentView::setupTubeBellControls( QWidget * _parent ) auto widget = new QWidget(_parent); widget->setFixedSize( 250, 250 ); - m_modulatorKnob = new Knob( KnobType::Vintage32, widget ); - m_modulatorKnob->setLabel( tr( "Modulator" ) ); + m_modulatorKnob = new Knob(KnobType::Vintage32, tr("Modulator"), SMALL_FONT_SIZE, widget); m_modulatorKnob->move( 30, 90 ); m_modulatorKnob->setHintText( tr( "Modulator:" ), "" ); - m_crossfadeKnob = new Knob( KnobType::Vintage32, widget ); - m_crossfadeKnob->setLabel( tr( "Crossfade" ) ); + m_crossfadeKnob = new Knob(KnobType::Vintage32, tr("Crossfade"), SMALL_FONT_SIZE, widget); m_crossfadeKnob->move( 110, 90 ); m_crossfadeKnob->setHintText( tr( "Crossfade:" ), "" ); - m_lfoSpeedKnob = new Knob( KnobType::Vintage32, widget ); - m_lfoSpeedKnob->setLabel( tr( "LFO speed" ) ); + m_lfoSpeedKnob = new Knob(KnobType::Vintage32, tr("LFO speed"), SMALL_FONT_SIZE, widget); m_lfoSpeedKnob->move( 30, 140 ); m_lfoSpeedKnob->setHintText( tr( "LFO speed:" ), "" ); - m_lfoDepthKnob = new Knob( KnobType::Vintage32, widget ); - m_lfoDepthKnob->setLabel( tr( "LFO depth" ) ); + m_lfoDepthKnob = new Knob(KnobType::Vintage32, tr("LFO depth"), SMALL_FONT_SIZE, widget); m_lfoDepthKnob->move( 110, 140 ); m_lfoDepthKnob->setHintText( tr( "LFO depth:" ), "" ); - m_adsrKnob = new Knob( KnobType::Vintage32, widget ); - m_adsrKnob->setLabel( tr( "ADSR" ) ); + m_adsrKnob = new Knob(KnobType::Vintage32, tr("ADSR"), SMALL_FONT_SIZE, widget); m_adsrKnob->move( 190, 90 ); m_adsrKnob->setHintText( tr( "ADSR:" ), "" ); @@ -571,23 +560,19 @@ QWidget * MalletsInstrumentView::setupBandedWGControls( QWidget * _parent ) /* m_strikeLED = new LedCheckBox( tr( "Bowed" ), widget ); m_strikeLED->move( 138, 25 );*/ - m_pressureKnob = new Knob( KnobType::Vintage32, widget ); - m_pressureKnob->setLabel( tr( "Pressure" ) ); + m_pressureKnob = new Knob(KnobType::Vintage32, tr("Pressure"), SMALL_FONT_SIZE, widget); m_pressureKnob->move( 30, 90 ); m_pressureKnob->setHintText( tr( "Pressure:" ), "" ); -/* m_motionKnob = new Knob( KnobType::Vintage32, widget ); - m_motionKnob->setLabel( tr( "Motion" ) ); +/* m_motionKnob = new Knob(KnobType::Vintage32, tr("Motion"), SMALL_FONT_SIZE, widget); m_motionKnob->move( 110, 90 ); m_motionKnob->setHintText( tr( "Motion:" ), "" );*/ - m_velocityKnob = new Knob( KnobType::Vintage32, widget ); - m_velocityKnob->setLabel( tr( "Speed" ) ); + m_velocityKnob = new Knob(KnobType::Vintage32, tr("Speed"), SMALL_FONT_SIZE, widget); m_velocityKnob->move( 30, 140 ); m_velocityKnob->setHintText( tr( "Speed:" ), "" ); -/* m_vibratoKnob = new Knob( KnobType::Vintage32, widget, tr( "Vibrato" ) ); - m_vibratoKnob->setLabel( tr( "Vibrato" ) ); +/* m_vibratoKnob = new Knob(KnobType::Vintage32, tr("Vibrato"), SMALL_FONT_SIZE, widget); m_vibratoKnob->move( 110, 140 ); m_vibratoKnob->setHintText( tr( "Vibrato:" ), "" );*/ diff --git a/plugins/TripleOscillator/TripleOscillator.cpp b/plugins/TripleOscillator/TripleOscillator.cpp index 61a6c4919..55ad80082 100644 --- a/plugins/TripleOscillator/TripleOscillator.cpp +++ b/plugins/TripleOscillator/TripleOscillator.cpp @@ -29,7 +29,6 @@ #include "TripleOscillator.h" #include "AudioEngine.h" #include "AutomatableButton.h" -#include "debug.h" #include "Engine.h" #include "InstrumentTrack.h" #include "Knob.h" diff --git a/plugins/Vestige/CMakeLists.txt b/plugins/Vestige/CMakeLists.txt index 0a5847889..874e8f61d 100644 --- a/plugins/Vestige/CMakeLists.txt +++ b/plugins/Vestige/CMakeLists.txt @@ -11,5 +11,5 @@ if(LMMS_BUILD_LINUX) 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") +build_plugin(vestige Vestige.cpp Vestige.h MOCFILES Vestige.h EMBEDDED_RESOURCES "${CMAKE_CURRENT_SOURCE_DIR}/*.png;*.svg") target_link_libraries(vestige vstbase) diff --git a/plugins/Vestige/Vestige.cpp b/plugins/Vestige/Vestige.cpp index 05716008e..1bf9f257c 100644 --- a/plugins/Vestige/Vestige.cpp +++ b/plugins/Vestige/Vestige.cpp @@ -79,10 +79,14 @@ Plugin::Descriptor Q_DECL_EXPORT vestige_plugin_descriptor = 0x0100, Plugin::Type::Instrument, new PluginPixmapLoader( "logo" ), -#ifdef LMMS_BUILD_LINUX - "dll,so", -#else +#if defined(LMMS_BUILD_WIN32) "dll", +#elif defined(LMMS_BUILD_LINUX) +# if defined(LMMS_HAVE_VST_32) || defined(LMMS_HAVE_VST_64) + "dll,so", +# else + "so", +# endif #endif nullptr, } ; @@ -669,13 +673,17 @@ void VestigeInstrumentView::openPlugin() // set filters QStringList types; - types << tr( "DLL-files (*.dll)" ) - << tr( "EXE-files (*.exe)" ) -#ifdef LMMS_BUILD_LINUX - << tr( "SO-files (*.so)" ) +#if defined(LMMS_BUILD_WIN32) + types << tr("VST2 files (*.dll)"); +#elif defined(LMMS_BUILD_LINUX) +# if defined(LMMS_HAVE_VST_32) || defined(LMMS_HAVE_VST_64) + types << tr("All VST files (*.dll *.so)") + << tr("Windows VST2 files (*.dll)"); +# endif + types << tr("LinuxVST files (*.so)"); #endif - ; - ofd.setNameFilters( types ); + + ofd.setNameFilters(types); if( m_vi->m_pluginDLL != "" ) { @@ -990,9 +998,11 @@ ManageVestigeInstrumentView::ManageVestigeInstrumentView( Instrument * _instrume std::snprintf(paramStr.data(), paramStr.size(), "param%d", i); s_dumpValues = dump[paramStr.data()].split(":"); - 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 ) ); + const auto & description = s_dumpValues.at(1); + + auto knob = new CustomTextKnob(KnobType::Bright26, description.left(15), this, description); + knob->setDescription(description + ":"); + vstKnobs[i] = knob; if( !hasKnobModel ) { diff --git a/plugins/Vestige/artwork.png b/plugins/Vestige/artwork.png deleted file mode 100644 index cd08153eb..000000000 Binary files a/plugins/Vestige/artwork.png and /dev/null differ diff --git a/plugins/Vestige/artwork.svg b/plugins/Vestige/artwork.svg new file mode 100644 index 000000000..47e0275c3 --- /dev/null +++ b/plugins/Vestige/artwork.svg @@ -0,0 +1,105 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/plugins/Vestige/controls.png b/plugins/Vestige/controls.png deleted file mode 100644 index 4e03eb351..000000000 Binary files a/plugins/Vestige/controls.png and /dev/null differ diff --git a/plugins/Vestige/controls.svg b/plugins/Vestige/controls.svg new file mode 100644 index 000000000..dc900a770 --- /dev/null +++ b/plugins/Vestige/controls.svg @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/plugins/Vestige/controls_active.png b/plugins/Vestige/controls_active.png deleted file mode 100644 index 5759cd7bb..000000000 Binary files a/plugins/Vestige/controls_active.png and /dev/null differ diff --git a/plugins/Vestige/controls_active.svg b/plugins/Vestige/controls_active.svg new file mode 100644 index 000000000..42b6c563d --- /dev/null +++ b/plugins/Vestige/controls_active.svg @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/plugins/Vestige/logo-symbolic.svg b/plugins/Vestige/logo-symbolic.svg new file mode 100644 index 000000000..136c204e8 --- /dev/null +++ b/plugins/Vestige/logo-symbolic.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/plugins/Vestige/logo.png b/plugins/Vestige/logo.png deleted file mode 100644 index b8aa149b0..000000000 Binary files a/plugins/Vestige/logo.png and /dev/null differ diff --git a/plugins/Vestige/logo.svg b/plugins/Vestige/logo.svg new file mode 100644 index 000000000..01d4c0a28 --- /dev/null +++ b/plugins/Vestige/logo.svg @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/plugins/Vestige/select_file.png b/plugins/Vestige/select_file.png deleted file mode 100644 index 63af460f6..000000000 Binary files a/plugins/Vestige/select_file.png and /dev/null differ diff --git a/plugins/Vestige/select_file.svg b/plugins/Vestige/select_file.svg new file mode 100644 index 000000000..1af383dc3 --- /dev/null +++ b/plugins/Vestige/select_file.svg @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/plugins/Vestige/select_file_active.png b/plugins/Vestige/select_file_active.png deleted file mode 100644 index 029e58cfc..000000000 Binary files a/plugins/Vestige/select_file_active.png and /dev/null differ diff --git a/plugins/Vestige/select_file_active.svg b/plugins/Vestige/select_file_active.svg new file mode 100644 index 000000000..a5b84daa2 --- /dev/null +++ b/plugins/Vestige/select_file_active.svg @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/plugins/Vibed/VibratingString.cpp b/plugins/Vibed/VibratingString.cpp index 5a09a4549..00e178e24 100644 --- a/plugins/Vibed/VibratingString.cpp +++ b/plugins/Vibed/VibratingString.cpp @@ -26,7 +26,7 @@ #include "interpolation.h" #include "AudioEngine.h" #include "Engine.h" -#include "lmms_basics.h" +#include "LmmsTypes.h" #include #include diff --git a/plugins/Vibed/VibratingString.h b/plugins/Vibed/VibratingString.h index d1691415b..3f61e39f0 100644 --- a/plugins/Vibed/VibratingString.h +++ b/plugins/Vibed/VibratingString.h @@ -28,7 +28,7 @@ #include #include -#include "lmms_basics.h" +#include "LmmsTypes.h" namespace lmms { diff --git a/plugins/VstBase/RemoteVstPlugin.cpp b/plugins/VstBase/RemoteVstPlugin.cpp index f8bf39a37..077216b30 100644 --- a/plugins/VstBase/RemoteVstPlugin.cpp +++ b/plugins/VstBase/RemoteVstPlugin.cpp @@ -41,10 +41,6 @@ #ifdef LMMS_BUILD_LINUX -#ifndef NOMINMAX -#define NOMINMAX -#endif - #ifndef O_BINARY #define O_BINARY 0 #endif @@ -114,7 +110,7 @@ struct ERect #endif -#include "lmms_basics.h" +#include "LmmsTypes.h" #include "Midi.h" #include "communication.h" #include "IoHelper.h" @@ -1521,9 +1517,9 @@ void RemoteVstPlugin::savePreset( const std::string & _file ) void RemoteVstPlugin::loadPresetFile( const std::string & _file ) { void * chunk = nullptr; - auto pLen = new unsigned int[1]; + unsigned int pLen; unsigned int len = 0; - auto pBank = (sBank*)new char[sizeof(sBank)]; + sBank pBank; FILE * stream = F_OPEN_UTF8( _file, "rb" ); if (!stream) { @@ -1531,36 +1527,34 @@ void RemoteVstPlugin::loadPresetFile( const std::string & _file ) "Error opening file for loading preset.\n" ); return; } - if ( fread ( pBank, 1, 56, stream ) != 56 ) + if ( fread ( &pBank, 1, 56, stream ) != 56 ) { fprintf( stderr, "Error loading preset file.\n" ); } - pBank->fxID = endian_swap( pBank->fxID ); - pBank->numPrograms = endian_swap( pBank->numPrograms ); + pBank.fxID = endian_swap( pBank.fxID ); + pBank.numPrograms = endian_swap( pBank.numPrograms ); unsigned int toUInt; float * pFloat; - if (static_cast(m_plugin->uniqueID) != pBank->fxID) { + if (static_cast(m_plugin->uniqueID) != pBank.fxID) { sendMessage( message( IdVstCurrentProgramName ). addString( "Error: Plugin UniqID not match" ) ); fclose( stream ); - delete[] (unsigned int*)pLen; - delete[] (sBank*)pBank; return; } if( _file.substr( _file.find_last_of( "." ) + 1 ) != "fxp" ) fseek ( stream , 156 , SEEK_SET ); - if(pBank->fxMagic != 0x6B427846) { - if(pBank->fxMagic != 0x6B437846) { - if ( fread (pLen, 1, 4, stream) != 4 ) + if(pBank.fxMagic != 0x6B427846) { + if(pBank.fxMagic != 0x6B437846) { + if ( fread (&pLen, 1, 4, stream) != 4 ) { fprintf( stderr, "Error loading preset file.\n" ); } - chunk = new char[len = endian_swap(*pLen)]; - } else chunk = new char[len = sizeof(float)*pBank->numPrograms]; + chunk = new char[len = endian_swap(pLen)]; + } else chunk = new char[len = sizeof(float)*pBank.numPrograms]; if ( fread (chunk, len, 1, stream) != 1 ) { fprintf( stderr, "Error loading preset file.\n" ); @@ -1569,14 +1563,14 @@ void RemoteVstPlugin::loadPresetFile( const std::string & _file ) } if(_file.substr(_file.find_last_of(".") + 1) == "fxp") { - pBank->prgName[23] = 0; - pluginDispatch( 4, 0, 0, pBank->prgName ); - if(pBank->fxMagic != 0x6B437846) + pBank.prgName[23] = 0; + pluginDispatch( 4, 0, 0, pBank.prgName ); + if(pBank.fxMagic != 0x6B437846) pluginDispatch( 24, 1, len, chunk ); else { auto toUIntArray = reinterpret_cast(chunk); - for (auto i = 0u; i < pBank->numPrograms; i++) + for (auto i = 0u; i < pBank.numPrograms; i++) { toUInt = endian_swap( toUIntArray[ i ] ); pFloat = ( float* ) &toUInt; @@ -1584,16 +1578,16 @@ void RemoteVstPlugin::loadPresetFile( const std::string & _file ) } } } else { - if(pBank->fxMagic != 0x6B427846) { + if(pBank.fxMagic != 0x6B427846) { pluginDispatch( 24, 0, len, chunk ); } else { - int numPrograms = pBank->numPrograms; + int numPrograms = pBank.numPrograms; unsigned int * toUIntArray; int currProgram = pluginDispatch( effGetProgram ); chunk = new char[ len = sizeof(float)*m_plugin->numParams ]; toUIntArray = reinterpret_cast( chunk ); for (int i =0; i < numPrograms; i++) { - if ( fread (pBank, 1, 56, stream) != 56 ) + if ( fread (&pBank, 1, 56, stream) != 56 ) { fprintf( stderr, "Error loading preset file.\n" ); @@ -1604,8 +1598,8 @@ void RemoteVstPlugin::loadPresetFile( const std::string & _file ) "Error loading preset file.\n" ); } pluginDispatch( effSetProgram, 0, i ); - pBank->prgName[23] = 0; - pluginDispatch( 4, 0, 0, pBank->prgName ); + pBank.prgName[23] = 0; + pluginDispatch( 4, 0, 0, pBank.prgName ); for (int j = 0; j < m_plugin->numParams; j++ ) { toUInt = endian_swap( toUIntArray[ j ] ); pFloat = ( float* ) &toUInt; @@ -1619,8 +1613,6 @@ void RemoteVstPlugin::loadPresetFile( const std::string & _file ) sendCurrentProgramName(); - delete[] (unsigned int*)pLen; - delete[] (sBank*)pBank; delete[] (char*)chunk; } diff --git a/plugins/VstBase/VstPlugin.cpp b/plugins/VstBase/VstPlugin.cpp index 5dbe7a698..f26245728 100644 --- a/plugins/VstBase/VstPlugin.cpp +++ b/plugins/VstBase/VstPlugin.cpp @@ -49,12 +49,12 @@ #include "AudioEngine.h" #include "ConfigManager.h" +#include "FileDialog.h" #include "GuiApplication.h" #include "LocaleHelper.h" #include "MainWindow.h" #include "PathUtil.h" #include "Song.h" -#include "FileDialog.h" #ifdef LMMS_BUILD_LINUX # include @@ -404,9 +404,8 @@ bool VstPlugin::processMessage( const message & _m ) { case IdVstPluginWindowID: m_pluginWindowID = _m.getInt(); - if( m_embedMethod == "none" - && ConfigManager::inst()->value( - "ui", "vstalwaysontop" ).toInt() ) + if (m_embedMethod == "none" && !gui::GuiApplication::isWayland() + && ConfigManager::inst()->value("ui", "vstalwaysontop").toInt()) { #ifdef LMMS_BUILD_WIN32 // We're changing the owner, not the parent, diff --git a/plugins/VstBase/vst_base.cpp b/plugins/VstBase/vst_base.cpp index 154dca975..544d05e0b 100644 --- a/plugins/VstBase/vst_base.cpp +++ b/plugins/VstBase/vst_base.cpp @@ -24,6 +24,7 @@ */ +#include "LmmsCommonMacros.h" #include "Plugin.h" #include "vstbase_export.h" diff --git a/plugins/VstEffect/CMakeLists.txt b/plugins/VstEffect/CMakeLists.txt index 3cbe8e8cc..7bb42dc24 100644 --- a/plugins/VstEffect/CMakeLists.txt +++ b/plugins/VstEffect/CMakeLists.txt @@ -13,5 +13,5 @@ ELSE() SET(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/${PLUGIN_DIR}") 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) +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 *.svg) TARGET_LINK_LIBRARIES(vsteffect vstbase) diff --git a/plugins/VstEffect/VstEffectControls.cpp b/plugins/VstEffect/VstEffectControls.cpp index ef8bd38d0..c01aef0ce 100644 --- a/plugins/VstEffect/VstEffectControls.cpp +++ b/plugins/VstEffect/VstEffectControls.cpp @@ -389,9 +389,11 @@ ManageVSTEffectView::ManageVSTEffectView( VstEffect * _eff, VstEffectControls * std::snprintf(paramStr.data(), paramStr.size(), "param%d", i); s_dumpValues = dump[paramStr.data()].split(":"); - 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 ) ); + const auto & description = s_dumpValues.at(1); + + auto knob = new CustomTextKnob(KnobType::Bright26, description.left(15), widget, description); + knob->setDescription(description + ":"); + vstKnobs[i] = knob; if( !hasKnobModel ) { diff --git a/plugins/VstEffect/controls.png b/plugins/VstEffect/controls.png deleted file mode 100644 index 4e03eb351..000000000 Binary files a/plugins/VstEffect/controls.png and /dev/null differ diff --git a/plugins/VstEffect/controls.svg b/plugins/VstEffect/controls.svg new file mode 100644 index 000000000..dc900a770 --- /dev/null +++ b/plugins/VstEffect/controls.svg @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/plugins/VstEffect/controls_active.png b/plugins/VstEffect/controls_active.png deleted file mode 100644 index 5759cd7bb..000000000 Binary files a/plugins/VstEffect/controls_active.png and /dev/null differ diff --git a/plugins/VstEffect/controls_active.svg b/plugins/VstEffect/controls_active.svg new file mode 100644 index 000000000..42b6c563d --- /dev/null +++ b/plugins/VstEffect/controls_active.svg @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/plugins/VstEffect/logo.png b/plugins/VstEffect/logo.png deleted file mode 100644 index ca98f15d4..000000000 Binary files a/plugins/VstEffect/logo.png and /dev/null differ diff --git a/plugins/VstEffect/logo.svg b/plugins/VstEffect/logo.svg new file mode 100644 index 000000000..5d70a5784 --- /dev/null +++ b/plugins/VstEffect/logo.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/plugins/WaveShaper/WaveShaperControlDialog.cpp b/plugins/WaveShaper/WaveShaperControlDialog.cpp index 045f84763..17955fe28 100644 --- a/plugins/WaveShaper/WaveShaperControlDialog.cpp +++ b/plugins/WaveShaper/WaveShaperControlDialog.cpp @@ -28,6 +28,7 @@ #include "WaveShaperControlDialog.h" #include "WaveShaperControls.h" #include "embed.h" +#include "FontHelper.h" #include "Graph.h" #include "Knob.h" #include "PixmapButton.h" @@ -59,20 +60,18 @@ WaveShaperControlDialog::WaveShaperControlDialog( waveGraph->setGraphColor( QColor( 85, 204, 145 ) ); waveGraph -> setMaximumSize( 204, 205 ); - auto inputKnob = new Knob(KnobType::Bright26, this); + auto inputKnob = new Knob(KnobType::Bright26, tr("INPUT"), SMALL_FONT_SIZE, this); inputKnob -> setVolumeKnob( true ); inputKnob -> setVolumeRatio( 1.0 ); inputKnob -> move( 26, 225 ); inputKnob->setModel( &_controls->m_inputModel ); - inputKnob->setLabel( tr( "INPUT" ) ); inputKnob->setHintText( tr( "Input gain:" ) , "" ); - auto outputKnob = new Knob(KnobType::Bright26, this); + auto outputKnob = new Knob(KnobType::Bright26, tr("OUTPUT"), SMALL_FONT_SIZE, this); outputKnob -> setVolumeKnob( true ); outputKnob -> setVolumeRatio( 1.0 ); outputKnob -> move( 76, 225 ); outputKnob->setModel( &_controls->m_outputModel ); - outputKnob->setLabel( tr( "OUTPUT" ) ); outputKnob->setHintText( tr( "Output gain:" ), "" ); auto resetButton = new PixmapButton(this, tr("Reset wavegraph")); diff --git a/plugins/ZynAddSubFx/ZynAddSubFx.cpp b/plugins/ZynAddSubFx/ZynAddSubFx.cpp index 19864932d..27e286b66 100644 --- a/plugins/ZynAddSubFx/ZynAddSubFx.cpp +++ b/plugins/ZynAddSubFx/ZynAddSubFx.cpp @@ -109,7 +109,7 @@ ZynAddSubFxInstrument::ZynAddSubFxInstrument( m_plugin( nullptr ), m_remotePlugin( nullptr ), m_portamentoModel( 0, 0, 127, 1, this, tr( "Portamento" ) ), - m_filterFreqModel( 127, 0, 127, 1, this, tr( "Filter frequency" ) ), + m_filterFreqModel( 64, 0, 127, 1, this, tr( "Filter frequency" ) ), m_filterQModel( 64, 0, 127, 1, this, tr( "Filter resonance" ) ), m_bandwidthModel( 64, 0, 127, 1, this, tr( "Bandwidth" ) ), m_fmGainModel( 127, 0, 127, 1, this, tr( "FM gain" ) ), @@ -143,11 +143,6 @@ ZynAddSubFxInstrument::ZynAddSubFxInstrument( connect( instrumentTrack()->pitchRangeModel(), SIGNAL( dataChanged() ), this, SLOT( updatePitchRange() ), Qt::DirectConnection ); - - // ZynAddSubFX's internal value that LMMS's FREQ knob controls - // isn't set properly when the instrument is first loaded in, - // and doesn't update until the FREQ knob is moved - updateFilterFreq(); } @@ -512,33 +507,26 @@ ZynAddSubFxView::ZynAddSubFxView( Instrument * _instrument, QWidget * _parent ) l->setVerticalSpacing( 16 ); l->setHorizontalSpacing( 10 ); - m_portamento = new Knob( KnobType::Bright26, this ); + m_portamento = new Knob(KnobType::Bright26, tr("PORT"), SMALL_FONT_SIZE, this); m_portamento->setHintText( tr( "Portamento:" ), "" ); - m_portamento->setLabel( tr( "PORT" ) ); - m_filterFreq = new Knob( KnobType::Bright26, this ); + m_filterFreq = new Knob(KnobType::Bright26, tr("FREQ"), SMALL_FONT_SIZE, this); m_filterFreq->setHintText( tr( "Filter frequency:" ), "" ); - m_filterFreq->setLabel( tr( "FREQ" ) ); - m_filterQ = new Knob( KnobType::Bright26, this ); + m_filterQ = new Knob(KnobType::Bright26, tr("RES"), SMALL_FONT_SIZE, this); m_filterQ->setHintText( tr( "Filter resonance:" ), "" ); - m_filterQ->setLabel( tr( "RES" ) ); - m_bandwidth = new Knob( KnobType::Bright26, this ); + m_bandwidth = new Knob(KnobType::Bright26, tr("BW"), SMALL_FONT_SIZE, this); m_bandwidth->setHintText( tr( "Bandwidth:" ), "" ); - m_bandwidth->setLabel( tr( "BW" ) ); - m_fmGain = new Knob( KnobType::Bright26, this ); + m_fmGain = new Knob(KnobType::Bright26, tr("FM GAIN"), SMALL_FONT_SIZE, this); m_fmGain->setHintText( tr( "FM gain:" ), "" ); - m_fmGain->setLabel( tr( "FM GAIN" ) ); - m_resCenterFreq = new Knob( KnobType::Bright26, this ); + m_resCenterFreq = new Knob(KnobType::Bright26, tr("RES CF"), SMALL_FONT_SIZE, this); m_resCenterFreq->setHintText( tr( "Resonance center frequency:" ), "" ); - m_resCenterFreq->setLabel( tr( "RES CF" ) ); - m_resBandwidth = new Knob( KnobType::Bright26, this ); + m_resBandwidth = new Knob(KnobType::Bright26, tr("RES BW"), SMALL_FONT_SIZE, this); m_resBandwidth->setHintText( tr( "Resonance bandwidth:" ), "" ); - m_resBandwidth->setLabel( tr( "RES BW" ) ); m_forwardMidiCC = new LedCheckBox( tr( "Forward MIDI control changes" ), this ); diff --git a/plugins/ZynAddSubFx/zynaddsubfx b/plugins/ZynAddSubFx/zynaddsubfx index 9903fc44f..db8ffedb7 160000 --- a/plugins/ZynAddSubFx/zynaddsubfx +++ b/plugins/ZynAddSubFx/zynaddsubfx @@ -1 +1 @@ -Subproject commit 9903fc44ff61c932914fc5b43358c06b1946c446 +Subproject commit db8ffedb7212a7ed9777ffacb9118afbbf779d7c diff --git a/src/3rdparty/qt5-x11embed b/src/3rdparty/qt5-x11embed index 022b39a1d..499d737c8 160000 --- a/src/3rdparty/qt5-x11embed +++ b/src/3rdparty/qt5-x11embed @@ -1 +1 @@ -Subproject commit 022b39a1d496d72eb3e5b5188e5559f66afca957 +Subproject commit 499d737c8e8d6882c55ef6d26981d313b296e5ca diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 9612190bf..eb69fd1a2 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,6 +1,18 @@ ADD_SUBDIRECTORY(3rdparty) CONFIGURE_FILE("lmmsconfig.h.in" "${CMAKE_BINARY_DIR}/lmmsconfig.h") + +# Provide config flags to lmmsversion.h +get_cmake_property(_define_vars VARIABLES) +foreach (_define_var ${_define_vars}) + if(_define_var MATCHES "^WANT|LMMS_(HAVE|DEBUG)" ) + list(APPEND LMMS_BUILD_OPTIONS "${_define_var}='${${_define_var}}'") + endif() +endforeach() + +# Format for readibility +string(REPLACE ";" " " LMMS_BUILD_OPTIONS "${LMMS_BUILD_OPTIONS}") + CONFIGURE_FILE("lmmsversion.h.in" "${CMAKE_BINARY_DIR}/lmmsversion.h") SET(LMMS_SRCS "") diff --git a/src/core/AudioEngine.cpp b/src/core/AudioEngine.cpp index b7f95018e..968de777a 100644 --- a/src/core/AudioEngine.cpp +++ b/src/core/AudioEngine.cpp @@ -36,7 +36,6 @@ #include "EnvelopeAndLfoParameters.h" #include "NotePlayHandle.h" #include "ConfigManager.h" -#include "SamplePlayHandle.h" // platform-specific audio-interface-classes #include "AudioAlsa.h" @@ -74,7 +73,7 @@ static thread_local bool s_renderingThread = false; AudioEngine::AudioEngine( bool renderOnly ) : m_renderOnly( renderOnly ), m_framesPerPeriod( DEFAULT_BUFFER_SIZE ), - m_baseSampleRate(std::max(ConfigManager::inst()->value("audioengine", "samplerate").toInt(), 44100)), + m_baseSampleRate(std::max(ConfigManager::inst()->value("audioengine", "samplerate").toInt(), SUPPORTED_SAMPLERATES.front())), m_inputBufferRead( 0 ), m_inputBufferWrite( 1 ), m_outputBufferRead(nullptr), diff --git a/src/core/AutomatableModel.cpp b/src/core/AutomatableModel.cpp index e006be651..fa523e106 100644 --- a/src/core/AutomatableModel.cpp +++ b/src/core/AutomatableModel.cpp @@ -754,7 +754,7 @@ float AutomatableModel::globalAutomationValueAt( const TimePos& time ) if( latestClip ) { // scale/fit the value appropriately and return it - const float value = latestClip->valueAt( time - latestClip->startPosition() ); + const float value = latestClip->valueAt(time - latestClip->startPosition() + latestClip->startTimeOffset()); const float scaled_value = scaledValue( value ); return fittedValue( scaled_value ); } diff --git a/src/core/AutomationClip.cpp b/src/core/AutomationClip.cpp index ef57a60d5..56ba8e9cf 100644 --- a/src/core/AutomationClip.cpp +++ b/src/core/AutomationClip.cpp @@ -65,13 +65,13 @@ AutomationClip::AutomationClip( AutomationTrack * _auto_track ) : switch( getTrack()->trackContainer()->type() ) { case TrackContainer::Type::Pattern: - setAutoResize( true ); + setResizable(false); break; case TrackContainer::Type::Song: // move down default: - setAutoResize( false ); + setResizable(true); break; } } @@ -81,14 +81,17 @@ AutomationClip::AutomationClip( AutomationTrack * _auto_track ) : AutomationClip::AutomationClip( const AutomationClip & _clip_to_copy ) : - Clip( _clip_to_copy.m_autoTrack ), + Clip(_clip_to_copy), #if (QT_VERSION < QT_VERSION_CHECK(5,14,0)) m_clipMutex(QMutex::Recursive), #endif m_autoTrack( _clip_to_copy.m_autoTrack ), m_objects( _clip_to_copy.m_objects ), m_tension( _clip_to_copy.m_tension ), - m_progressionType( _clip_to_copy.m_progressionType ) + m_progressionType(_clip_to_copy.m_progressionType), + m_dragging(false), + m_isRecording(_clip_to_copy.m_isRecording), + m_lastRecordedValue(0) { // Locks the mutex of the copied AutomationClip to make sure it // doesn't change while it's being copied @@ -106,13 +109,13 @@ AutomationClip::AutomationClip( const AutomationClip & _clip_to_copy ) : switch( getTrack()->trackContainer()->type() ) { case TrackContainer::Type::Pattern: - setAutoResize( true ); + setResizable(false); break; case TrackContainer::Type::Song: // move down default: - setAutoResize( false ); + setResizable(true); break; } } @@ -225,8 +228,15 @@ TimePos AutomationClip::timeMapLength() const void AutomationClip::updateLength() { - // Do not resize down in case user manually extended up - changeLength(std::max(length(), timeMapLength())); + // Technically it only matters if the clip has been resized from the right, but this + // checks if it has been resized from either direction. + if (getAutoResize()) + { + // Using 1 bar as the min length for an un-resized clip. + // This does not prevent the user from resizing the clip to be less than a bar later on. + changeLength(std::max(TimePos::ticksPerBar(), static_cast(timeMapLength()))); + setStartTimeOffset(TimePos(0)); + } } @@ -253,6 +263,7 @@ TimePos AutomationClip::putValue( cleanObjects(); TimePos newTime = quantPos ? Note::quantized(time, quantization()) : time; + newTime = std::max(TimePos(0), newTime); // Create a node or replace the existing one on newTime m_timeMap[newTime] = AutomationNode(this, value, newTime); @@ -308,6 +319,7 @@ TimePos AutomationClip::putValues( cleanObjects(); TimePos newTime = quantPos ? Note::quantized(time, quantization()) : time; + newTime = std::max(TimePos(0), newTime); // Create a node or replace the existing one on newTime m_timeMap[newTime] = AutomationNode(this, inValue, outValue, newTime); @@ -455,12 +467,12 @@ void AutomationClip::recordValue(TimePos time, float value) if( value != m_lastRecordedValue ) { - putValue( time, value, true ); + putValue(time - startTimeOffset(), value, true); m_lastRecordedValue = value; } - else if( valueAt( time ) != value ) + else if( valueAt(time - startTimeOffset()) != value ) { - removeNode(time); + removeNode(time - startTimeOffset()); } } @@ -721,90 +733,61 @@ void AutomationClip::flipY() -void AutomationClip::flipX(int length) +void AutomationClip::flipX(int start, int end) { QMutexLocker m(&m_clipMutex); - timeMap::const_iterator it = m_timeMap.lowerBound(0); + timeMap::const_iterator firstIterator = m_timeMap.lowerBound(0); - if (it == m_timeMap.end()) { return; } + if (firstIterator == m_timeMap.end()) { return; } + + if (start == -1 && end == -1) { start = 0; end = length() - startTimeOffset(); } + else if (!(end >= 0 && start >= 0 && end > start)) { return; } // Temporary map where we will store the flipped version // of our clip timeMap tempMap; - float tempValue = 0; - float tempOutValue = 0; - - // We know the QMap isn't empty, making this safe: - float realLength = m_timeMap.lastKey(); - - // If we have a positive length, we want to flip the area covered by that - // length, even if it goes beyond the clip. A negative length means that - // we just want to flip the nodes we have - if (length >= 0 && length != realLength) + for (auto it = m_timeMap.begin(); it != m_timeMap.end(); ++it) { - // If length to be flipped is bigger than the real length - if (realLength < length) + if (POS(it) < start || POS(it) > end) { - // We are flipping an area that goes beyond the last node. So we add a node to the - // beginning of the flipped timeMap representing the value of the end of the area - tempValue = valueAt(length); - tempMap[0] = AutomationNode(this, tempValue, 0); - - // Now flip the nodes we have in relation to the length - do - { - // We swap the inValue and outValue when flipping horizontally - tempValue = OUTVAL(it); - tempOutValue = INVAL(it); - auto newTime = TimePos(length - POS(it)); - - tempMap[newTime] = AutomationNode(this, tempValue, tempOutValue, newTime); - - ++it; - } while (it != m_timeMap.end()); + tempMap[POS(it)] = *it; } - else // If the length to be flipped is smaller than the real length + else { - do + // If the first node in the clip is not at the start, it can break things when clipping, so + // we have to set its in value to 0. + if (it == firstIterator && POS(firstIterator) > 0) { - TimePos newTime; - - // Only flips the length to be flipped and keep the remaining values in place - // We also only swap the inValue and outValue if we are flipping the node - if (POS(it) <= length) - { - newTime = length - POS(it); - tempValue = OUTVAL(it); - tempOutValue = INVAL(it); - } - else - { - newTime = POS(it); - tempValue = INVAL(it); - tempOutValue = OUTVAL(it); - } - - tempMap[newTime] = AutomationNode(this, tempValue, tempOutValue, newTime); - - ++it; - } while (it != m_timeMap.end()); + tempMap[end - (POS(it) - start)] = AutomationNode(this, OUTVAL(it), 0, end - (POS(it) - start)); + } + else + { + tempMap[end - (POS(it) - start)] = AutomationNode(this, OUTVAL(it), INVAL(it), end - (POS(it) - start)); + } } } - else // Length to be flipped is the same as the real length + + if (m_timeMap.contains(start) && m_timeMap.contains(end)) { - do - { - // Swap the inValue and outValue - tempValue = OUTVAL(it); - tempOutValue = INVAL(it); - - auto newTime = TimePos(realLength - POS(it)); - tempMap[newTime] = AutomationNode(this, tempValue, tempOutValue, newTime); - - ++it; - } while (it != m_timeMap.end()); + tempMap[start] = AutomationNode(this, m_timeMap[start].getInValue(), m_timeMap[end].getInValue(), start); + tempMap[end] = AutomationNode(this, m_timeMap[start].getOutValue(), m_timeMap[end].getOutValue(), end); + } + else if (m_timeMap.contains(start)) + { + tempMap[start] = AutomationNode(this, m_timeMap[start].getInValue(), valueAt(end), start); + tempMap[end] = AutomationNode(this, m_timeMap[start].getOutValue(), valueAt(end), end); + } + else if (m_timeMap.contains(end)) + { + tempMap[start] = AutomationNode(this, valueAt(start), m_timeMap[end].getInValue(), start); + tempMap[end] = AutomationNode(this, valueAt(start), m_timeMap[end].getOutValue(), end); + } + else + { + tempMap[start] = AutomationNode(this, valueAt(start), valueAt(end), start); + tempMap[end] = AutomationNode(this, valueAt(start), valueAt(end), end); } m_timeMap.clear(); @@ -830,6 +813,8 @@ void AutomationClip::saveSettings( QDomDocument & _doc, QDomElement & _this ) _this.setAttribute( "prog", QString::number( static_cast(progressionType()) ) ); _this.setAttribute( "tens", QString::number( getTension() ) ); _this.setAttribute( "mute", QString::number( isMuted() ) ); + _this.setAttribute("off", startTimeOffset()); + _this.setAttribute("autoresize", QString::number(getAutoResize())); if (const auto& c = color()) { @@ -880,6 +865,8 @@ void AutomationClip::loadSettings( const QDomElement & _this ) "prog" ).toInt() ) ); setTension( _this.attribute( "tens" ) ); setMuted(_this.attribute( "mute", QString::number( false ) ).toInt() ); + setAutoResize(_this.attribute("autoresize", "1").toInt()); + setStartTimeOffset(_this.attribute("off").toInt()); for( QDomNode node = _this.firstChild(); !node.isNull(); node = node.nextSibling() ) diff --git a/src/core/BufferManager.cpp b/src/core/BufferManager.cpp index 47598c633..9059064ae 100644 --- a/src/core/BufferManager.cpp +++ b/src/core/BufferManager.cpp @@ -28,8 +28,6 @@ #include "SampleFrame.h" -#include - namespace lmms { diff --git a/src/core/Clip.cpp b/src/core/Clip.cpp index b18391df1..ea7d1f933 100644 --- a/src/core/Clip.cpp +++ b/src/core/Clip.cpp @@ -61,7 +61,30 @@ Clip::Clip( Track * track ) : } - +/*! \brief Copy a Clip + * + * Creates a duplicate clip of the one provided. + * + * \param other The clip object which will be copied. + */ +Clip::Clip(const Clip& other): + Model(other.m_track), + m_track(other.m_track), + m_name(other.m_name), + m_startPosition(other.m_startPosition), + m_length(other.m_length), + m_startTimeOffset(other.m_startTimeOffset), + m_mutedModel(other.m_mutedModel.value(), this, tr( "Mute" )), + m_resizable(other.m_resizable), + m_autoResize(other.m_autoResize), + m_selectViewOnCreate{other.m_selectViewOnCreate}, + m_color(other.m_color) +{ + if (getTrack()) + { + getTrack()->addClip(this); + } +} /*! \brief Destroy a Clip * diff --git a/src/core/ConfigManager.cpp b/src/core/ConfigManager.cpp index d3c973020..bdf4d6c32 100644 --- a/src/core/ConfigManager.cpp +++ b/src/core/ConfigManager.cpp @@ -22,19 +22,19 @@ * */ +#include "ConfigManager.h" -#include -#include -#include +#include #include +#include +#include +#include #include #include -#include "ConfigManager.h" +#include "GuiApplication.h" #include "MainWindow.h" #include "ProjectVersion.h" -#include "GuiApplication.h" - #include "lmmsversion.h" namespace lmms @@ -299,9 +299,6 @@ void ConfigManager::setBackgroundPicFile(const QString & backgroundPicFile) m_backgroundPicFile = backgroundPicFile; } - - - void ConfigManager::createWorkingDir() { QDir().mkpath(m_workingDir); @@ -335,8 +332,27 @@ void ConfigManager::addRecentlyOpenedProject(const QString & file) } } +void ConfigManager::addFavoriteItem(const QString& item) +{ + m_favoriteItems.push_back(item); + saveConfigFile(); + emit favoritesChanged(); +} +void ConfigManager::removeFavoriteItem(const QString& item) +{ + m_favoriteItems.removeAll(item); + saveConfigFile(); + emit favoritesChanged(); +} +bool ConfigManager::isFavoriteItem(const QString& item) +{ + const auto& items = favoriteItems(); + const auto it = std::find_if(items.begin(), items.end(), + [&](const auto& favoriteItem) { return QFileInfo{item} == QFileInfo{favoriteItem}; }); + return it != items.end(); +} QString ConfigManager::value(const QString& cls, const QString& attribute, const QString& defaultVal) const { @@ -466,8 +482,20 @@ void ConfigManager::loadConfigFile(const QString & configFile) { if(n.isElement() && n.toElement().hasAttributes()) { - m_recentlyOpenedProjects << - n.toElement().attribute("path"); + m_recentlyOpenedProjects << n.toElement().attribute("path"); + } + n = n.nextSibling(); + } + } + else if (node.nodeName() == "favoriteitems") + { + m_favoriteItems.clear(); + QDomNode n = node.firstChild(); + while (!n.isNull()) + { + if (n.isElement() && n.toElement().hasAttributes()) + { + m_favoriteItems << n.toElement().attribute("path"); } n = n.nextSibling(); } @@ -571,6 +599,16 @@ void ConfigManager::loadConfigFile(const QString & configFile) { createWorkingDir(); } + + for (auto& file : m_recentlyOpenedProjects) + { + file = PathUtil::toAbsolute(file); + } + + for (auto& file : m_favoriteItems) + { + file = PathUtil::toAbsolute(file); + } } @@ -614,11 +652,22 @@ void ConfigManager::saveConfigFile() for (const auto& recentlyOpenedProject : m_recentlyOpenedProjects) { QDomElement n = doc.createElement("file"); - n.setAttribute("path", recentlyOpenedProject); + n.setAttribute("path", PathUtil::toShortestRelative(recentlyOpenedProject)); recent_files.appendChild(n); } lmms_config.appendChild(recent_files); + QDomElement favorite_items = doc.createElement("favoriteitems"); + + for (const auto& favoriteItem : m_favoriteItems) + { + QDomElement n = doc.createElement("item"); + n.setAttribute("path", PathUtil::toShortestRelative(favoriteItem)); + favorite_items.appendChild(n); + } + + lmms_config.appendChild(favorite_items); + QString xml = "\n" + doc.toString(2); QFile outfile(m_lmmsRcFile); diff --git a/src/core/FileSearch.cpp b/src/core/FileSearch.cpp index 8a668360e..cc9d49af2 100644 --- a/src/core/FileSearch.cpp +++ b/src/core/FileSearch.cpp @@ -26,8 +26,6 @@ #include #include -#include - #include namespace lmms { diff --git a/src/core/Instrument.cpp b/src/core/Instrument.cpp index cae16dee8..07614c314 100644 --- a/src/core/Instrument.cpp +++ b/src/core/Instrument.cpp @@ -29,7 +29,7 @@ #include "DummyInstrument.h" #include "InstrumentTrack.h" -#include "lmms_basics.h" +#include "LmmsTypes.h" namespace lmms { diff --git a/src/core/LadspaManager.cpp b/src/core/LadspaManager.cpp index 5b94cac3f..4c982397b 100644 --- a/src/core/LadspaManager.cpp +++ b/src/core/LadspaManager.cpp @@ -36,6 +36,7 @@ #include "ConfigManager.h" #include "LadspaManager.h" #include "PluginFactory.h" +#include "lmms_constants.h" namespace lmms diff --git a/src/core/LfoController.cpp b/src/core/LfoController.cpp index 96ea71f7b..dcdb59b7f 100644 --- a/src/core/LfoController.cpp +++ b/src/core/LfoController.cpp @@ -29,6 +29,7 @@ #include #include "AudioEngine.h" +#include "Oscillator.h" #include "PathUtil.h" #include "SampleLoader.h" #include "Song.h" diff --git a/src/core/Mixer.cpp b/src/core/Mixer.cpp index 0add6008d..6007be466 100644 --- a/src/core/Mixer.cpp +++ b/src/core/Mixer.cpp @@ -26,7 +26,6 @@ #include "AudioEngine.h" #include "AudioEngineWorkerThread.h" -#include "BufferManager.h" #include "Mixer.h" #include "MixHelpers.h" #include "Song.h" diff --git a/src/core/Note.cpp b/src/core/Note.cpp index ed3a00f10..de57f63d2 100644 --- a/src/core/Note.cpp +++ b/src/core/Note.cpp @@ -36,7 +36,7 @@ namespace lmms Note::Note( const TimePos & length, const TimePos & pos, int key, volume_t volume, panning_t panning, - DetuningHelper * detuning ) : + std::shared_ptr detuning ) : m_selected( false ), m_oldKey(std::clamp(key, 0, NumKeys)), m_oldPos( pos ), @@ -46,14 +46,10 @@ Note::Note( const TimePos & length, const TimePos & pos, m_volume(std::clamp(volume, MinVolume, MaxVolume)), m_panning(std::clamp(panning, PanningLeft, PanningRight)), m_length( length ), - m_pos( pos ), - m_detuning( nullptr ) + m_pos(pos), + m_detuning(std::move(detuning)) { - if( detuning ) - { - m_detuning = sharedObject::ref( detuning ); - } - else + if (!detuning) { createDetuning(); } @@ -74,24 +70,41 @@ 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(note.m_detuning), m_type(note.m_type) { - if( note.m_detuning ) - { - m_detuning = sharedObject::ref( note.m_detuning ); - } } +Note& Note::operator=(const Note& note) +{ + m_selected = note.m_selected; + m_oldKey = note.m_oldKey; + m_oldPos = note.m_oldPos; + m_oldLength = note.m_oldLength; + m_isPlaying = note.m_isPlaying; + m_key = note.m_key; + m_volume = note.m_volume; + m_panning = note.m_panning; + m_length = note.m_length; + m_pos = note.m_pos; + m_type = note.m_type; + m_detuning = note.m_detuning; + + return *this; +} + + +Note* Note::clone() const +{ + Note* newNote = new Note(*this); + newNote->m_detuning = std::make_shared(*newNote->m_detuning); + return newNote; +} Note::~Note() { - if( m_detuning ) - { - sharedObject::unref( m_detuning ); - } } @@ -218,7 +231,7 @@ void Note::createDetuning() { if( m_detuning == nullptr ) { - m_detuning = new DetuningHelper; + m_detuning = std::make_shared(); (void) m_detuning->automationClip(); m_detuning->setRange( -MaxDetuning, MaxDetuning, 0.5f ); m_detuning->automationClip()->setProgressionType( AutomationClip::ProgressionType::Linear ); diff --git a/src/core/NotePlayHandle.cpp b/src/core/NotePlayHandle.cpp index 0c27529df..01e78fe2e 100644 --- a/src/core/NotePlayHandle.cpp +++ b/src/core/NotePlayHandle.cpp @@ -54,7 +54,7 @@ NotePlayHandle::NotePlayHandle( InstrumentTrack* instrumentTrack, int midiEventChannel, Origin origin ) : PlayHandle( PlayHandle::Type::NotePlayHandle, _offset ), - Note( n.length(), n.pos(), n.key(), n.getVolume(), n.getPanning(), n.detuning() ), + Note(n), m_pluginData( nullptr ), m_instrumentTrack( instrumentTrack ), m_frames( 0 ), @@ -84,7 +84,7 @@ NotePlayHandle::NotePlayHandle( InstrumentTrack* instrumentTrack, lock(); if( hasParent() == false ) { - m_baseDetuning = new BaseDetuning( detuning() ); + m_baseDetuning = new BaseDetuning(detuning().get()); m_instrumentTrack->m_processHandles.push_back( this ); } else diff --git a/src/core/Oscillator.cpp b/src/core/Oscillator.cpp index 5b48ddf3e..7aad9c58e 100644 --- a/src/core/Oscillator.cpp +++ b/src/core/Oscillator.cpp @@ -31,7 +31,6 @@ #endif #include -#include "BufferManager.h" #include "Engine.h" #include "AudioEngine.h" #include "AutomatableModel.h" diff --git a/src/core/PathUtil.cpp b/src/core/PathUtil.cpp index 03ec465a9..f374c60af 100644 --- a/src/core/PathUtil.cpp +++ b/src/core/PathUtil.cpp @@ -9,8 +9,8 @@ namespace lmms::PathUtil { - auto relativeBases = std::array{ Base::ProjectDir, Base::FactorySample, Base::UserSample, Base::UserVST, Base::Preset, - Base::UserLADSPA, Base::DefaultLADSPA, Base::UserSoundfont, Base::DefaultSoundfont, Base::UserGIG, Base::DefaultGIG, + auto relativeBases = std::array{ Base::ProjectDir, Base::FactoryProjects, Base::FactorySample, Base::UserSample, Base::UserVST, Base::Preset, + Base::FactoryPresets, Base::UserLADSPA, Base::DefaultLADSPA, Base::UserSoundfont, Base::DefaultSoundfont, Base::UserGIG, Base::DefaultGIG, Base::LocalDir }; QString baseLocation(const Base base, bool* error /* = nullptr*/) @@ -22,6 +22,11 @@ namespace lmms::PathUtil switch (base) { case Base::ProjectDir : loc = ConfigManager::inst()->userProjectsDir(); break; + case Base::FactoryProjects : + { + QDir fpd = QDir(ConfigManager::inst()->factoryProjectsDir()); + loc = fpd.absolutePath(); break; + } case Base::FactorySample : { QDir fsd = QDir(ConfigManager::inst()->factorySamplesDir()); @@ -30,6 +35,11 @@ namespace lmms::PathUtil case Base::UserSample : loc = ConfigManager::inst()->userSamplesDir(); break; case Base::UserVST : loc = ConfigManager::inst()->userVstDir(); break; case Base::Preset : loc = ConfigManager::inst()->userPresetsDir(); break; + case Base::FactoryPresets : + { + QDir fpd = QDir(ConfigManager::inst()->factoryPresetsDir()); + loc = fpd.absolutePath(); break; + } case Base::UserLADSPA : loc = ConfigManager::inst()->ladspaDir(); break; case Base::DefaultLADSPA : loc = ConfigManager::inst()->userLadspaDir(); break; case Base::UserSoundfont : loc = ConfigManager::inst()->sf2Dir(); break; @@ -70,10 +80,12 @@ namespace lmms::PathUtil switch (base) { case Base::ProjectDir : return QStringLiteral("userprojects:"); + case Base::FactoryProjects : return QStringLiteral("factoryprojects:"); case Base::FactorySample : return QStringLiteral("factorysample:"); case Base::UserSample : return QStringLiteral("usersample:"); case Base::UserVST : return QStringLiteral("uservst:"); case Base::Preset : return QStringLiteral("preset:"); + case Base::FactoryPresets : return QStringLiteral("factorypreset:"); case Base::UserLADSPA : return QStringLiteral("userladspa:"); case Base::DefaultLADSPA : return QStringLiteral("defaultladspa:"); case Base::UserSoundfont : return QStringLiteral("usersoundfont:"); @@ -103,9 +115,9 @@ namespace lmms::PathUtil return path.mid( basePrefix(baseLookup(path)).length() ); } - QString cleanName(const QString & path) + QString cleanName(const QString& path) { - return stripPrefix(QFileInfo(path).baseName()); + return stripPrefix(QFileInfo(path).completeBaseName()); } diff --git a/src/core/PatternClip.cpp b/src/core/PatternClip.cpp index 15a1d1f54..abc8a65fd 100644 --- a/src/core/PatternClip.cpp +++ b/src/core/PatternClip.cpp @@ -45,7 +45,7 @@ PatternClip::PatternClip(Track* track) : changeLength( TimePos( t, 0 ) ); restoreJournallingState(); } - setAutoResize( false ); + setResizable(true); } void PatternClip::saveSettings(QDomDocument& doc, QDomElement& element) @@ -62,6 +62,7 @@ void PatternClip::saveSettings(QDomDocument& doc, QDomElement& element) element.setAttribute( "len", length() ); element.setAttribute("off", startTimeOffset()); element.setAttribute( "muted", isMuted() ); + element.setAttribute("autoresize", QString::number(getAutoResize())); if (const auto& c = color()) { element.setAttribute("color", c->name()); @@ -79,6 +80,7 @@ void PatternClip::loadSettings(const QDomElement& element) movePosition( element.attribute( "pos" ).toInt() ); } changeLength( element.attribute( "len" ).toInt() ); + setAutoResize(element.attribute("autoresize", "1").toInt()); setStartTimeOffset(element.attribute("off").toInt()); if (static_cast(element.attribute("muted").toInt()) != isMuted()) { diff --git a/src/core/PeakController.cpp b/src/core/PeakController.cpp index 1b819982a..1c38cf4cb 100644 --- a/src/core/PeakController.cpp +++ b/src/core/PeakController.cpp @@ -80,7 +80,9 @@ void PeakController::updateValueBuffer() { if( m_coeffNeedsUpdate ) { - m_coeff = 100.0f / Engine::audioEngine()->outputSampleRate(); + const float ratio = 44100.0f / Engine::audioEngine()->outputSampleRate(); + m_attackCoeff = 1.0f - powf( 2.0f, -0.3f * ( 1.0f - m_peakEffect->attackModel()->value() ) * ratio ); + m_decayCoeff = 1.0f - powf( 2.0f, -0.3f * ( 1.0f - m_peakEffect->decayModel()->value() ) * ratio ); m_coeffNeedsUpdate = false; } @@ -95,7 +97,14 @@ void PeakController::updateValueBuffer() for( f_cnt_t f = 0; f < frames; ++f ) { const float diff = ( targetSample - m_currentSample ); - m_currentSample += diff * m_coeff; + if( m_currentSample < targetSample ) // going up... + { + m_currentSample += diff * m_attackCoeff; + } + else if( m_currentSample > targetSample ) // going down + { + m_currentSample += diff * m_decayCoeff; + } values[f] = m_currentSample; } } diff --git a/src/core/RemotePlugin.cpp b/src/core/RemotePlugin.cpp index e0eeff524..25085e3b6 100644 --- a/src/core/RemotePlugin.cpp +++ b/src/core/RemotePlugin.cpp @@ -33,7 +33,6 @@ #include #endif -#include "BufferManager.h" #include "AudioEngine.h" #include "Engine.h" #include "Song.h" diff --git a/src/core/SampleBuffer.cpp b/src/core/SampleBuffer.cpp index fda3f2f66..3283a7142 100644 --- a/src/core/SampleBuffer.cpp +++ b/src/core/SampleBuffer.cpp @@ -27,7 +27,6 @@ #include "PathUtil.h" #include "SampleDecoder.h" -#include "lmms_basics.h" namespace lmms { diff --git a/src/core/SampleClip.cpp b/src/core/SampleClip.cpp index 5ef001e20..007265c0c 100644 --- a/src/core/SampleClip.cpp +++ b/src/core/SampleClip.cpp @@ -70,13 +70,13 @@ SampleClip::SampleClip(Track* _track, Sample sample, bool isPlaying) switch( getTrack()->trackContainer()->type() ) { case TrackContainer::Type::Pattern: - setAutoResize( true ); + setResizable(false); break; case TrackContainer::Type::Song: // move down default: - setAutoResize( false ); + setResizable(true); break; } updateTrackClips(); @@ -88,8 +88,48 @@ SampleClip::SampleClip(Track* track) } SampleClip::SampleClip(const SampleClip& orig) : - SampleClip(orig.getTrack(), orig.m_sample, orig.m_isPlaying) + Clip(orig), + m_sample(std::move(orig.m_sample)), + m_isPlaying(orig.m_isPlaying) { + saveJournallingState( false ); + setSampleFile( "" ); + restoreJournallingState(); + + // we need to receive bpm-change-events, because then we have to + // change length of this Clip + connect( Engine::getSong(), SIGNAL(tempoChanged(lmms::bpm_t)), + this, SLOT(updateLength()), Qt::DirectConnection ); + connect( Engine::getSong(), SIGNAL(timeSignatureChanged(int,int)), + this, SLOT(updateLength())); + + //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 and jumps + connect( Engine::getSong(), SIGNAL(updateSampleTracks()), + this, SLOT(playbackPositionChanged()), Qt::DirectConnection ); + //care about mute Clips + connect( this, SIGNAL(dataChanged()), this, SLOT(playbackPositionChanged())); + //care about mute track + connect( getTrack()->getMutedModel(), SIGNAL(dataChanged()), + this, SLOT(playbackPositionChanged()), Qt::DirectConnection ); + //care about Clip position + connect( this, SIGNAL(positionChanged()), this, SLOT(updateTrackClips())); + + switch( getTrack()->trackContainer()->type() ) + { + case TrackContainer::Type::Pattern: + setResizable(false); + break; + + case TrackContainer::Type::Song: + // move down + default: + setResizable(true); + break; + } + updateTrackClips(); } @@ -112,12 +152,6 @@ void SampleClip::changeLength( const TimePos & _length ) Clip::changeLength(std::max(static_cast(_length), 1)); } -void SampleClip::changeLengthToSampleLength() -{ - int length = m_sample.sampleSize() / Engine::framesPerTick(); - changeLength(length); -} - const QString& SampleClip::sampleFile() const @@ -145,26 +179,21 @@ void SampleClip::setSampleBuffer(std::shared_ptr sb) void SampleClip::setSampleFile(const QString& sf) { - int length = 0; - + // Remove any prior offset in the clip + setStartTimeOffset(0); if (!sf.isEmpty()) { - //Otherwise set it to the sample's length m_sample = Sample(gui::SampleLoader::createBufferFromFile(sf)); - length = sampleLength(); + updateLength(); } - - if (length == 0) + else { - //If there is no sample, make the clip a bar long + // 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(DefaultTicksPerBar * (nom / den)); } - changeLength(length); - setStartTimeOffset(0); - emit sampleChanged(); emit playbackPositionChanged(); } @@ -221,6 +250,14 @@ void SampleClip::setIsPlaying(bool isPlaying) void SampleClip::updateLength() { + // If the clip has already been manually resized, don't automatically resize it. + // Unless we are in a pattern, where you can't resize stuff manually + if (getAutoResize() || !getResizable()) + { + changeLength(sampleLength()); + setStartTimeOffset(0); + } + emit sampleChanged(); Engine::getSong()->setModified(); @@ -267,6 +304,7 @@ void SampleClip::saveSettings( QDomDocument & _doc, QDomElement & _this ) _this.setAttribute( "muted", isMuted() ); _this.setAttribute( "src", sampleFile() ); _this.setAttribute( "off", startTimeOffset() ); + _this.setAttribute("autoresize", QString::number(getAutoResize())); if( sampleFile() == "" ) { QString s; @@ -315,6 +353,7 @@ void SampleClip::loadSettings( const QDomElement & _this ) changeLength( _this.attribute( "len" ).toInt() ); setMuted( _this.attribute( "muted" ).toInt() ); setStartTimeOffset( _this.attribute( "off" ).toInt() ); + setAutoResize(_this.attribute("autoresize", "1").toInt()); if (_this.hasAttribute("color")) { diff --git a/src/core/SampleDecoder.cpp b/src/core/SampleDecoder.cpp index d3ee091f4..eb640447c 100644 --- a/src/core/SampleDecoder.cpp +++ b/src/core/SampleDecoder.cpp @@ -37,7 +37,7 @@ #include "AudioEngine.h" #include "DrumSynth.h" #include "Engine.h" -#include "lmms_basics.h" +#include "LmmsTypes.h" namespace lmms { diff --git a/src/core/SampleRecordHandle.cpp b/src/core/SampleRecordHandle.cpp index c378d467e..e38082c6a 100644 --- a/src/core/SampleRecordHandle.cpp +++ b/src/core/SampleRecordHandle.cpp @@ -29,7 +29,6 @@ #include "PatternTrack.h" #include "SampleBuffer.h" #include "SampleClip.h" -#include "debug.h" namespace lmms diff --git a/src/core/Song.cpp b/src/core/Song.cpp index ea60e349b..b052c775a 100644 --- a/src/core/Song.cpp +++ b/src/core/Song.cpp @@ -290,7 +290,7 @@ void Song::processNextBuffer() } else if (m_playMode == PlayMode::MidiClip && m_loopMidiClip && !loopEnabled) { - enforceLoop(TimePos{0}, m_midiClipToPlay->length()); + enforceLoop(-m_midiClipToPlay->startTimeOffset(), m_midiClipToPlay->length() - m_midiClipToPlay->startTimeOffset()); } // Handle loop points, and inform VST plugins of the loop status @@ -366,7 +366,7 @@ void Song::processAutomations(const TrackList &tracklist, TimePos timeStart, fpp break; case PlayMode::Pattern: { - Q_ASSERT(tracklist.size() == 1); + if (tracklist.empty()) { return; } Q_ASSERT(tracklist.at(0)->type() == Track::Type::Pattern); auto patternTrack = dynamic_cast(tracklist.at(0)); container = Engine::patternStore(); @@ -493,6 +493,7 @@ void Song::playSong() } m_playMode = PlayMode::Song; + m_lastPlayMode = m_playMode; m_playing = true; m_paused = false; @@ -532,6 +533,7 @@ void Song::playPattern() } m_playMode = PlayMode::Pattern; + m_lastPlayMode = m_playMode; m_playing = true; m_paused = false; @@ -558,6 +560,7 @@ void Song::playMidiClip( const MidiClip* midiClipToPlay, bool loop ) if( m_midiClipToPlay != nullptr ) { m_playMode = PlayMode::MidiClip; + m_lastPlayMode = m_playMode; m_playing = true; m_paused = false; } @@ -660,7 +663,14 @@ void Song::stop() switch (timeline.stopBehaviour()) { case Timeline::StopBehaviour::BackToZero: - getPlayPos().setTicks(0); + if (m_playMode == PlayMode::MidiClip) + { + getPlayPos().setTicks(std::max(0, -m_midiClipToPlay->startTimeOffset())); + } + else + { + getPlayPos().setTicks(0); + } m_elapsedMilliSeconds[static_cast(m_playMode)] = 0; break; diff --git a/src/core/TimePos.cpp b/src/core/TimePos.cpp index 6e5e034fd..6cf657c72 100644 --- a/src/core/TimePos.cpp +++ b/src/core/TimePos.cpp @@ -53,7 +53,7 @@ TimePos::TimePos( const tick_t ticks ) : { } -TimePos TimePos::quantize(float bars) const +TimePos TimePos::quantize(float bars, bool forceRoundDown) const { //The intervals we should snap to, our new position should be a factor of this int interval = s_ticksPerBar * bars; @@ -65,7 +65,7 @@ TimePos TimePos::quantize(float bars) const // Ternary expression is making sure that the snap happens in the direction to // the right even if m_ticks is negative and the offset is exactly half-way // More details on issue #5840 and PR #5847 - int snapUp = ((2 * offset) == -interval) + int snapUp = forceRoundDown || ((2 * offset) == -interval) ? 0 : (2 * offset) / interval; diff --git a/src/core/TrackContainer.cpp b/src/core/TrackContainer.cpp index d4120e761..c92d6edf0 100644 --- a/src/core/TrackContainer.cpp +++ b/src/core/TrackContainer.cpp @@ -297,9 +297,9 @@ AutomatedValueMap TrackContainer::automatedValuesFromTracks(const TrackList &tra if (! p->hasAutomation()) { continue; } - TimePos relTime = time - p->startPosition(); - if (! p->getAutoResize()) { - relTime = std::min(relTime, p->length()); + TimePos relTime = time - p->startPosition() - p->startTimeOffset(); + if (p->getResizable()) { + relTime = std::min(static_cast(relTime), p->length() - p->startTimeOffset()); } float value = p->valueAt(relTime); diff --git a/src/core/audio/AudioDevice.cpp b/src/core/audio/AudioDevice.cpp index c02ce5f99..c5d56c997 100644 --- a/src/core/audio/AudioDevice.cpp +++ b/src/core/audio/AudioDevice.cpp @@ -27,7 +27,6 @@ #include "AudioDevice.h" #include "AudioEngine.h" #include "ConfigManager.h" -#include "debug.h" namespace lmms { diff --git a/src/core/audio/AudioJack.cpp b/src/core/audio/AudioJack.cpp index b6da7c845..9abbcc347 100644 --- a/src/core/audio/AudioJack.cpp +++ b/src/core/audio/AudioJack.cpp @@ -34,7 +34,6 @@ #include "ConfigManager.h" #include "Engine.h" #include "GuiApplication.h" -#include "LcdSpinBox.h" #include "MainWindow.h" #include "MidiJack.h" @@ -44,13 +43,7 @@ namespace lmms AudioJack::AudioJack(bool& successful, AudioEngine* audioEngineParam) : AudioDevice( - // clang-format off - std::clamp( - ConfigManager::inst()->value("audiojack", "channels").toInt(), - DEFAULT_CHANNELS, - DEFAULT_CHANNELS - ), - // clang-format on + DEFAULT_CHANNELS, audioEngineParam) , m_client(nullptr) , m_active(false) @@ -66,6 +59,8 @@ AudioJack::AudioJack(bool& successful, AudioEngine* audioEngineParam) if (successful) { connect(this, SIGNAL(zombified()), this, SLOT(restartAfterZombified()), Qt::QueuedConnection); } + + m_supportsCapture = true; } @@ -154,6 +149,16 @@ bool AudioJack::initJackClient() clientName.toLatin1().constData(), jack_get_client_name(m_client)); } + resizeInputBuffer(jack_get_buffer_size(m_client)); + + // set buffer-size callback + jack_set_buffer_size_callback(m_client, + [](jack_nframes_t nframes, void* udata) -> int { + static_cast(udata)->resizeInputBuffer(nframes); + return 0; + }, + this); + // set process-callback jack_set_process_callback(m_client, staticProcessCallback, this); @@ -167,6 +172,10 @@ bool AudioJack::initJackClient() 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)); + + QString input_name = QString("master in ") + ((ch % 2) ? "R" : "L") + QString::number(ch / 2 + 1); + m_inputPorts.push_back(jack_port_register(m_client, input_name.toLatin1().constData(), JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput, 0)); + if (m_outputPorts.back() == nullptr) { printf("no more JACK-ports available!\n"); @@ -180,6 +189,14 @@ bool AudioJack::initJackClient() +void AudioJack::resizeInputBuffer(jack_nframes_t nframes) +{ + m_inputFrameBuffer.resize(nframes); +} + + + + void AudioJack::startProcessing() { if (m_active || m_client == nullptr) @@ -290,7 +307,6 @@ void AudioJack::renamePort(AudioBusHandle* port) 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) @@ -356,6 +372,16 @@ int AudioJack::processCallback(jack_nframes_t nframes) } } + for (int c = 0; c < channels(); ++c) + { + jack_default_audio_sample_t* jack_input_buffer = (jack_default_audio_sample_t*) jack_port_get_buffer(m_inputPorts[c], nframes); + + for (jack_nframes_t frame = 0; frame < nframes; frame++) + { + m_inputFrameBuffer[frame][c] = static_cast(jack_input_buffer[frame]); + } + } + audioEngine()->pushInputFrames (m_inputFrameBuffer.data(), nframes); return 0; } @@ -390,24 +416,6 @@ AudioJack::setupWidget::setupWidget(QWidget* parent) m_clientName = new QLineEdit(cn, this); form->addRow(tr("Client name"), m_clientName); - - auto m = new gui::LcdSpinBoxModel(/* this */); - m->setRange(DEFAULT_CHANNELS, DEFAULT_CHANNELS); - m->setStep(2); - m->setValue(ConfigManager::inst()->value("audiojack", "channels").toInt()); - - m_channels = new gui::LcdSpinBox(1, this); - m_channels->setModel(m); - - form->addRow(tr("Channels"), m_channels); -} - - - - -AudioJack::setupWidget::~setupWidget() -{ - delete m_channels->model(); } @@ -416,7 +424,6 @@ 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())); } diff --git a/src/core/audio/AudioSampleRecorder.cpp b/src/core/audio/AudioSampleRecorder.cpp index 0e51c9d2d..2e577d1c6 100644 --- a/src/core/audio/AudioSampleRecorder.cpp +++ b/src/core/audio/AudioSampleRecorder.cpp @@ -26,7 +26,6 @@ #include "AudioSampleRecorder.h" #include "SampleBuffer.h" -#include "debug.h" namespace lmms diff --git a/src/core/audio/AudioSdl.cpp b/src/core/audio/AudioSdl.cpp index 8f533119c..dfbcde737 100644 --- a/src/core/audio/AudioSdl.cpp +++ b/src/core/audio/AudioSdl.cpp @@ -23,7 +23,7 @@ */ #include "AudioSdl.h" -#include "lmms_basics.h" +#include "LmmsTypes.h" #ifdef LMMS_HAVE_SDL diff --git a/src/core/audio/AudioSoundIo.cpp b/src/core/audio/AudioSoundIo.cpp index 851592018..01f804935 100644 --- a/src/core/audio/AudioSoundIo.cpp +++ b/src/core/audio/AudioSoundIo.cpp @@ -30,7 +30,6 @@ #include #include "Engine.h" -#include "debug.h" #include "ConfigManager.h" #include "ComboBox.h" #include "AudioEngine.h" diff --git a/src/core/lv2/Lv2ControlBase.cpp b/src/core/lv2/Lv2ControlBase.cpp index 0147ebd6e..27e6348ae 100644 --- a/src/core/lv2/Lv2ControlBase.cpp +++ b/src/core/lv2/Lv2ControlBase.cpp @@ -31,6 +31,7 @@ #include #include "Engine.h" +#include "lmms_constants.h" #include "Lv2Manager.h" #include "Lv2Proc.h" diff --git a/src/core/main.cpp b/src/core/main.cpp index 995b49d2c..fb54feeab 100644 --- a/src/core/main.cpp +++ b/src/core/main.cpp @@ -55,7 +55,7 @@ #include #endif -#include +#include // To register the signal handler #include "MainApplication.h" #include "ConfigManager.h" @@ -76,12 +76,12 @@ #include // For feenableexcept #include // For backtrace and backtrace_symbols_fd #include // For STDERR_FILENO -#include // To register the signal handler #endif #ifdef LMMS_DEBUG_FPE -void signalHandler( int signum ) { +void sigfpeHandler(int signum) +{ // Get a back trace void *array[10]; @@ -140,15 +140,16 @@ inline void loadTranslation( const QString & tname, void printVersion( char *executableName ) { - printf( "LMMS %s\n(%s %s, Qt %s, %s)\n\n" + printf("LMMS %s\n(%s %s, Qt %s, %s)\n\n" + "Build options:\n%s\n\n" "Copyright (c) %s\n\n" "This program is free software; you can redistribute it and/or\n" "modify it under the terms of the GNU General Public\n" "License as published by the Free Software Foundation; either\n" "version 2 of the License, or (at your option) any later version.\n\n" "Try \"%s --help\" for more information.\n\n", LMMS_VERSION, - LMMS_BUILDCONF_PLATFORM, LMMS_BUILDCONF_MACHINE, QT_VERSION_STR, LMMS_BUILDCONF_COMPILER_VERSION, - LMMS_PROJECT_COPYRIGHT, executableName ); + LMMS_BUILDCONF_PLATFORM, LMMS_BUILDCONF_MACHINE, QT_VERSION_STR, LMMS_BUILDCONF_COMPILER_VERSION, LMMS_BUILD_OPTIONS, + LMMS_PROJECT_COPYRIGHT, executableName); } @@ -314,8 +315,9 @@ int main( int argc, char * * argv ) // Install the trap handler // register signal SIGFPE and signal handler - signal(SIGFPE, signalHandler); + signal(SIGFPE, sigfpeHandler); #endif + signal(SIGINT, gui::GuiApplication::sigintHandler); #ifdef LMMS_BUILD_WIN32 // Don't touch redirected streams here diff --git a/src/gui/AudioAlsaSetupWidget.cpp b/src/gui/AudioAlsaSetupWidget.cpp index 43872a12f..9764bd863 100644 --- a/src/gui/AudioAlsaSetupWidget.cpp +++ b/src/gui/AudioAlsaSetupWidget.cpp @@ -31,6 +31,7 @@ #include "ConfigManager.h" #include "LcdSpinBox.h" +#include "lmms_constants.h" namespace lmms::gui { diff --git a/src/gui/Controls.cpp b/src/gui/Controls.cpp index 209b0fce1..d75da6a7d 100644 --- a/src/gui/Controls.cpp +++ b/src/gui/Controls.cpp @@ -38,7 +38,11 @@ namespace lmms::gui { -void KnobControl::setText(const QString &text) { m_knob->setLabel(text); } +void KnobControl::setText(const QString& text) +{ + // For KnobControls the text is set in the constructor + // so we do nothing here +} QWidget *KnobControl::topWidget() { return m_knob; } @@ -51,8 +55,8 @@ FloatModel *KnobControl::model() { return m_knob->model(); } AutomatableModelView* KnobControl::modelView() { return m_knob; } -KnobControl::KnobControl(QWidget *parent) : - m_knob(new Knob(parent)) {} +KnobControl::KnobControl(const QString& text, QWidget *parent) : + m_knob(new Knob(KnobType::Bright26, text, parent)) {} void ComboControl::setText(const QString &text) { m_label->setText(text); } diff --git a/src/gui/EffectRackView.cpp b/src/gui/EffectRackView.cpp index 6a4b3124d..c6d62b334 100644 --- a/src/gui/EffectRackView.cpp +++ b/src/gui/EffectRackView.cpp @@ -65,6 +65,7 @@ EffectRackView::EffectRackView( EffectChain* model, QWidget* parent ) : auto addButton = new QPushButton; addButton->setText( tr( "Add effect" ) ); + addButton->setFocusPolicy(Qt::NoFocus); effectsLayout->addWidget( addButton ); diff --git a/src/gui/EffectView.cpp b/src/gui/EffectView.cpp index a5095ee6d..a77fc1d96 100644 --- a/src/gui/EffectView.cpp +++ b/src/gui/EffectView.cpp @@ -63,23 +63,19 @@ EffectView::EffectView( Effect * _model, QWidget * _parent ) : m_bypass->setToolTip(tr("On/Off")); - - m_wetDry = new Knob( KnobType::Bright26, this ); - m_wetDry->setLabel( tr( "W/D" ) ); + m_wetDry = new Knob(KnobType::Bright26, tr("W/D"), this, Knob::LabelRendering::LegacyFixedFontSize); m_wetDry->move( 40 - m_wetDry->width() / 2, 5 ); m_wetDry->setEnabled( isEnabled ); m_wetDry->setHintText( tr( "Wet Level:" ), "" ); - m_autoQuit = new TempoSyncKnob( KnobType::Bright26, this ); - m_autoQuit->setLabel( tr( "DECAY" ) ); + m_autoQuit = new TempoSyncKnob(KnobType::Bright26, tr("DECAY"), this, Knob::LabelRendering::LegacyFixedFontSize); 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( KnobType::Bright26, this ); - m_gate->setLabel( tr( "GATE" ) ); + m_gate = new Knob(KnobType::Bright26, tr("GATE"), this, Knob::LabelRendering::LegacyFixedFontSize); m_gate->move( 116 - m_gate->width() / 2, 5 ); m_gate->setEnabled( isEnabled && !effect()->m_autoQuitDisabled ); m_gate->setHintText( tr( "Gate:" ), "" ); @@ -93,6 +89,7 @@ EffectView::EffectView( Effect * _model, QWidget * _parent ) : QFont f = ctls_btn->font(); ctls_btn->setFont(adjustedToPixelSize(f, DEFAULT_FONT_SIZE)); ctls_btn->setGeometry( 150, 14, 50, 20 ); + ctls_btn->setFocusPolicy(Qt::NoFocus); connect( ctls_btn, SIGNAL(clicked()), this, SLOT(editControls())); diff --git a/src/gui/FileBrowser.cpp b/src/gui/FileBrowser.cpp index 5e8c84e33..11e48c00f 100644 --- a/src/gui/FileBrowser.cpp +++ b/src/gui/FileBrowser.cpp @@ -25,6 +25,7 @@ #include "FileBrowser.h" +#include #include #include #include @@ -77,18 +78,15 @@ enum TreeWidgetItemTypes TypeDirectoryItem } ; - -FileBrowser::FileBrowser(const QString & directories, const QString & filter, - const QString & title, const QPixmap & pm, - 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_userDir(userDir), - m_factoryDir(factoryDir) +FileBrowser::FileBrowser(Type type, const QString& directories, const QString& filter, const QString& title, const QPixmap& pm, + QWidget* parent, bool dirs_as_items, const QString& userDir, const QString& factoryDir) + : SideBarWidget(title, pm, parent) + , m_type(type) + , m_directories(directories) + , m_filter(filter) + , m_dirsAsItems(dirs_as_items) + , m_userDir(userDir) + , m_factoryDir(factoryDir) { setWindowTitle( tr( "Browser" ) ); @@ -136,6 +134,14 @@ FileBrowser::FileBrowser(const QString & directories, const QString & filter, m_previousFilterValue = ""; + if (m_type == Type::Favorites) + { + connect(ConfigManager::inst(), &ConfigManager::favoritesChanged, [this] { + m_directories = ConfigManager::inst()->favoriteItems().join("*"); + reloadTree(); + }); + } + reloadTree(); show(); } @@ -329,7 +335,7 @@ void FileBrowser::reloadTree() m_fileBrowserTreeWidget->clear(); - QStringList paths = m_directories.split('*'); + auto paths = m_directories.isEmpty() ? QStringList{} : m_directories.split('*'); if (m_showUserContent && !m_showUserContent->isChecked()) { @@ -341,12 +347,37 @@ void FileBrowser::reloadTree() paths.removeAll(m_factoryDir); } - if (!paths.isEmpty()) + switch (m_type) { + case Type::Favorites: + for (auto& path : paths) + { + while (path.endsWith('/') || path.endsWith('\\') || path.endsWith(".")) + { + path.chop(1); + } + + auto info = QFileInfo{PathUtil::toAbsolute(path)}; + + if (info.isDir()) + { + auto dir = new Directory(info.fileName(), info.absolutePath(), m_filter); + dir->update(); + m_fileBrowserTreeWidget->addTopLevelItem(dir); + } + else if (info.isFile()) + { + auto file = new FileItem(info.fileName(), info.path()); + m_fileBrowserTreeWidget->addTopLevelItem(file); + } + } + break; + case Type::Normal: for (const auto& path : paths) { addItems(path); } + break; } if (m_filterEdit->text().isEmpty()) @@ -642,17 +673,29 @@ void FileBrowserTreeWidget::contextMenuEvent(QContextMenuEvent* e) { case TypeFileItem: { auto file = dynamic_cast(item); - - if (file->isTrack()) - { - contextMenu.addAction( - tr("Send to active instrument-track"), [=, this] { sendToActiveInstrumentTrack(file); }); - contextMenu.addSeparator(); - } + const auto path = QFileInfo{file->fullName()}.absoluteFilePath(); contextMenu.addAction(QIcon(embed::getIconPixmap("folder")), tr("Show in %1").arg(fileManager), [=] { FileRevealer::reveal(file->fullName()); }); + if (ConfigManager::inst()->isFavoriteItem(file->fullName())) + { + contextMenu.addAction( + QIcon(embed::getIconPixmap("star")), tr("Remove favorite file"), [path] { ConfigManager::inst()->removeFavoriteItem(path); }); + } + else + { + contextMenu.addAction( + QIcon(embed::getIconPixmap("star")), tr("Add favorite file"), [path] { ConfigManager::inst()->addFavoriteItem(path); }); + } + + if (file->isTrack()) + { + contextMenu.addSeparator(); + contextMenu.addAction( + tr("Send to active instrument-track"), [=, this] { sendToActiveInstrumentTrack(file); }); + } + auto songEditorHeader = new QAction(tr("Song Editor"), nullptr); songEditorHeader->setDisabled(true); contextMenu.addAction( songEditorHeader ); @@ -666,9 +709,20 @@ void FileBrowserTreeWidget::contextMenuEvent(QContextMenuEvent* e) } case TypeDirectoryItem: { auto dir = dynamic_cast(item); + const auto path = QFileInfo{dir->fullName()}.absoluteFilePath(); + contextMenu.addAction(QIcon(embed::getIconPixmap("folder")), tr("Open in %1").arg(fileManager), [=] { FileRevealer::openDir(dir->fullName()); }); + + if (ConfigManager::inst()->isFavoriteItem(dir->fullName())) + { + contextMenu.addAction(QIcon(embed::getIconPixmap("star")), tr("Remove favorite folder"), [path] { ConfigManager::inst()->removeFavoriteItem(path); }); + } + else + { + contextMenu.addAction(QIcon(embed::getIconPixmap("star")), tr("Add favorite folder"), [path] { ConfigManager::inst()->addFavoriteItem(path); }); + } break; } } @@ -1222,15 +1276,21 @@ void FileItem::determineFileType() m_type = FileType::Midi; m_handling = FileHandling::ImportAsProject; } - else if( ext == "dll" -#ifdef LMMS_BUILD_LINUX - || ext == "so" -#endif +#ifdef LMMS_HAVE_VST + else if ( +# if defined(LMMS_BUILD_LINUX) + ext == "so" || +# endif +# if defined(LMMS_HAVE_VST_32) || defined(LMMS_HAVE_VST_64) + ext == "dll" || +# endif + false ) { m_type = FileType::VstPlugin; m_handling = FileHandling::LoadByPlugin; } +#endif else if ( ext == "lv2" ) { m_type = FileType::Preset; diff --git a/src/gui/FileRevealer.cpp b/src/gui/FileRevealer.cpp index e93cc7aed..6e2ad56b4 100644 --- a/src/gui/FileRevealer.cpp +++ b/src/gui/FileRevealer.cpp @@ -35,7 +35,7 @@ #include "lmmsconfig.h" -namespace lmms { +namespace lmms::gui { bool FileRevealer::s_canSelect = false; const QString& FileRevealer::getDefaultFileManager() @@ -180,4 +180,4 @@ bool FileRevealer::supportsArg(const QString& command, const QString& arg) return output.contains(arg); } -} // namespace lmms +} // namespace lmms::gui diff --git a/src/gui/GuiApplication.cpp b/src/gui/GuiApplication.cpp index 8c674112d..292284b3c 100644 --- a/src/gui/GuiApplication.cpp +++ b/src/gui/GuiApplication.cpp @@ -41,14 +41,22 @@ #include "SongEditor.h" #include +#include #include #include #include #include #include +#include +#include #ifdef LMMS_BUILD_WIN32 +#include +#include #include +#else +#include +#include #endif namespace lmms @@ -71,10 +79,18 @@ GuiApplication* GuiApplication::instance() return s_instance; } +bool GuiApplication::isWayland() +{ + return QGuiApplication::platformName().contains("wayland"); +} + GuiApplication::GuiApplication() { + // Immediately register our SIGINT handler + createSocketNotifier(); + // prompt the user to create the LMMS working directory (e.g. ~/Documents/lmms) if it doesn't exist if ( !ConfigManager::inst()->hasWorkingDir() && QMessageBox::question( nullptr, @@ -240,6 +256,52 @@ void GuiApplication::childDestroyed(QObject *obj) } } +/** \brief Called from main when SIGINT is fired + * + * Unix signal handlers can only call async-signal-safe functions: + * write(fd) --> QSocketNotifier --> SLOT sigintOccurred() + * + * See https://doc.qt.io/qt-6/unix-signals.html + */ +void GuiApplication::sigintHandler(int) +{ +#ifdef LMMS_BUILD_WIN32 + char message[] = "Sorry, SIGINT is unhandled on this platform\n"; + std::ignore = _write(_fileno(stderr), message, sizeof(message)); +#else + char a = 1; + std::ignore = ::write(s_sigintFd[0], &a, sizeof(a)); +#endif +} + +// Create our unix signal notifiers +void GuiApplication::createSocketNotifier() +{ +#ifdef LMMS_BUILD_WIN32 + // no-op +#else + if (::socketpair(AF_UNIX, SOCK_STREAM, 0, s_sigintFd)) + { + qFatal("Couldn't create SIGINT socketpair"); + return; + } + + // Listen on the file descriptor for SIGINT + m_sigintNotifier = new QSocketNotifier(s_sigintFd[1], QSocketNotifier::Read, this); + connect(m_sigintNotifier, SIGNAL(activated(QSocketDescriptor)), this, SLOT(sigintOccurred()), Qt::QueuedConnection); +#endif +} + +// Handle the shutdown event +void GuiApplication::sigintOccurred() +{ + m_sigintNotifier->setEnabled(false); + qDebug() << "Shutting down..."; + // cleanup, etc + qApp->exit(3); + m_sigintNotifier->setEnabled(true); +} + #ifdef LMMS_BUILD_WIN32 /*! * @brief Returns the Windows System font. diff --git a/src/gui/LadspaControlView.cpp b/src/gui/LadspaControlView.cpp index dbc3b8059..294500b75 100644 --- a/src/gui/LadspaControlView.cpp +++ b/src/gui/LadspaControlView.cpp @@ -81,11 +81,11 @@ LadspaControlView::LadspaControlView( QWidget * _parent, case BufferDataType::Integer: case BufferDataType::Enum: case BufferDataType::Floating: - knb = new Knob( KnobType::Bright26, this, m_ctl->port()->name ); + knb = new Knob(KnobType::Bright26, m_ctl->port()->name, this, Knob::LabelRendering::WidgetFont, m_ctl->port()->name); break; case BufferDataType::Time: - knb = new TempoSyncKnob( KnobType::Bright26, this, m_ctl->port()->name ); + knb = new TempoSyncKnob(KnobType::Bright26, m_ctl->port()->name, this, Knob::LabelRendering::WidgetFont, m_ctl->port()->name); break; default: @@ -102,7 +102,6 @@ LadspaControlView::LadspaControlView( QWidget * _parent, { knb->setModel( m_ctl->tempoSyncKnobModel() ); } - knb->setLabel( m_ctl->port()->name ); knb->setHintText( tr( "Value:" ), "" ); layout->addWidget( knb ); if( link != nullptr ) diff --git a/src/gui/LfoControllerDialog.cpp b/src/gui/LfoControllerDialog.cpp index 559ac1336..4a0ff158f 100644 --- a/src/gui/LfoControllerDialog.cpp +++ b/src/gui/LfoControllerDialog.cpp @@ -27,6 +27,7 @@ #include "embed.h" +#include "FontHelper.h" #include "LfoController.h" #include "Knob.h" #include "TempoSyncKnob.h" @@ -62,23 +63,19 @@ LfoControllerDialog::LfoControllerDialog( Controller * _model, QWidget * _parent setWindowIcon( embed::getIconPixmap( "controller" ) ); setFixedSize( 240, 58 ); - m_baseKnob = new Knob( KnobType::Bright26, this ); - m_baseKnob->setLabel( tr( "BASE" ) ); + m_baseKnob = new Knob(KnobType::Bright26, tr("BASE"), SMALL_FONT_SIZE, this); m_baseKnob->move( CD_LFO_BASE_CD_KNOB_X, CD_LFO_CD_KNOB_Y ); m_baseKnob->setHintText( tr( "Base:" ), "" ); - m_speedKnob = new TempoSyncKnob( KnobType::Bright26, this ); - m_speedKnob->setLabel( tr( "FREQ" ) ); + m_speedKnob = new TempoSyncKnob(KnobType::Bright26, tr("FREQ"), SMALL_FONT_SIZE, this); m_speedKnob->move( CD_LFO_SPEED_CD_KNOB_X, CD_LFO_CD_KNOB_Y ); m_speedKnob->setHintText( tr( "LFO frequency:" ), "" ); - m_amountKnob = new Knob( KnobType::Bright26, this ); - m_amountKnob->setLabel( tr( "AMNT" ) ); + m_amountKnob = new Knob(KnobType::Bright26, tr("AMNT"), SMALL_FONT_SIZE, this); m_amountKnob->move( CD_LFO_AMOUNT_CD_KNOB_X, CD_LFO_CD_KNOB_Y ); m_amountKnob->setHintText( tr( "Modulation amount:" ), "" ); - m_phaseKnob = new Knob( KnobType::Bright26, this ); - m_phaseKnob->setLabel( tr( "PHS" ) ); + m_phaseKnob = new Knob(KnobType::Bright26, tr("PHS"), SMALL_FONT_SIZE, this); m_phaseKnob->move( CD_LFO_PHASE_CD_KNOB_X, CD_LFO_CD_KNOB_Y ); m_phaseKnob->setHintText( tr( "Phase offset:" ) , "" + tr( " degrees" ) ); diff --git a/src/gui/Lv2ViewBase.cpp b/src/gui/Lv2ViewBase.cpp index d6a25af83..0cd6a0ee4 100644 --- a/src/gui/Lv2ViewBase.cpp +++ b/src/gui/Lv2ViewBase.cpp @@ -70,7 +70,7 @@ Lv2ViewProc::Lv2ViewProc(QWidget* parent, Lv2Proc* proc, int colNum) : switch (port.m_vis) { case PortVis::Generic: - m_control = new KnobControl(m_parent); + m_control = new KnobControl(port.name(), m_parent); break; case PortVis::Integer: { diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp index 275ef4d29..e18bb3cbb 100644 --- a/src/gui/MainWindow.cpp +++ b/src/gui/MainWindow.cpp @@ -111,30 +111,27 @@ MainWindow::MainWindow() : emit initProgress(tr("Preparing plugin browser")); sideBar->appendTab( new PluginBrowser( splitter ) ); emit initProgress(tr("Preparing file browsers")); - sideBar->appendTab( new FileBrowser( - confMgr->userProjectsDir() + "*" + - confMgr->factoryProjectsDir(), - "*.mmp *.mmpz *.xml *.mid *.mpt", - tr( "My Projects" ), - embed::getIconPixmap( "project_file" ).transformed( QTransform().rotate( 90 ) ), - splitter, false, - confMgr->userProjectsDir(), - confMgr->factoryProjectsDir())); - 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, - confMgr->userPresetsDir(), - confMgr->factoryPresetsDir())); - sideBar->appendTab(new FileBrowser(QDir::homePath(), FileItem::defaultFilters(), tr("My Home"), - embed::getIconPixmap("home").transformed(QTransform().rotate(90)), splitter, false)); + + sideBar->appendTab(new FileBrowser(FileBrowser::Type::Favorites, ConfigManager::inst()->favoriteItems().join("*"), FileItem::defaultFilters(), "My Favorites", + embed::getIconPixmap("star").transformed(QTransform().rotate(90)), splitter, false, "", "")); + + sideBar->appendTab(new FileBrowser(FileBrowser::Type::Normal, + confMgr->userProjectsDir() + "*" + confMgr->factoryProjectsDir(), "*.mmp *.mmpz *.xml *.mid *.mpt", + tr("My Projects"), embed::getIconPixmap("project_file").transformed(QTransform().rotate(90)), splitter, false, + confMgr->userProjectsDir(), confMgr->factoryProjectsDir())); + + sideBar->appendTab(new FileBrowser(FileBrowser::Type::Normal, + 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(FileBrowser::Type::Normal, + confMgr->userPresetsDir() + "*" + confMgr->factoryPresetsDir(), "*.xpf *.cs.xml *.xiz *.lv2", tr("My Presets"), + embed::getIconPixmap("preset_file").transformed(QTransform().rotate(90)), splitter, false, + confMgr->userPresetsDir(), confMgr->factoryPresetsDir())); + + sideBar->appendTab(new FileBrowser(FileBrowser::Type::Normal, QDir::homePath(), FileItem::defaultFilters(), + tr("My Home"), embed::getIconPixmap("home").transformed(QTransform().rotate(90)), splitter, false)); QStringList root_paths; QString title = tr("Root Directory"); @@ -156,7 +153,7 @@ MainWindow::MainWindow() : } #endif - sideBar->appendTab(new FileBrowser(root_paths.join("*"), FileItem::defaultFilters(), title, + sideBar->appendTab(new FileBrowser(FileBrowser::Type::Normal, root_paths.join("*"), FileItem::defaultFilters(), title, embed::getIconPixmap("computer").transformed(QTransform().rotate(90)), splitter, dirs_as_items)); m_workspace = new MovableQMdiArea(splitter); @@ -315,7 +312,7 @@ void MainWindow::finalize() SLOT(onExportProject()), combine(Qt::CTRL, Qt::Key_E)); project_menu->addAction( embed::getIconPixmap( "project_export" ), - tr( "E&xport Tracks..." ), + tr("Export &Tracks..."), this, SLOT(onExportProjectTracks()), combine(Qt::CTRL, Qt::SHIFT, Qt::Key_E)); @@ -1287,6 +1284,31 @@ void MainWindow::keyPressEvent( QKeyEvent * _ke ) case Qt::Key_Control: m_keyMods.m_ctrl = true; break; case Qt::Key_Shift: m_keyMods.m_shift = true; break; case Qt::Key_Alt: m_keyMods.m_alt = true; break; + case Qt::Key_Space: + { + Editor* lastEditor = nullptr; + switch (Engine::getSong()->lastPlayMode()) + { + case Song::PlayMode::Song: + lastEditor = getGUI()->songEditor(); + break; + case Song::PlayMode::MidiClip: + lastEditor = getGUI()->pianoRoll(); + break; + case Song::PlayMode::Pattern: + lastEditor = getGUI()->patternEditor(); + break; + case Song::PlayMode::AutomationClip: + lastEditor = getGUI()->automationEditor(); + break; + default: + lastEditor = getGUI()->songEditor(); + break; + } + if (m_keyMods.m_shift) { lastEditor->togglePause(); } + else { lastEditor->togglePlayStop(); } + break; + } default: { InstrumentTrackWindow * w = @@ -1642,7 +1664,16 @@ void MainWindow::MovableQMdiArea::mouseMoveEvent(QMouseEvent* event) scrollY = scrollY < 0 && minY >= minYBoundary ? 0 : scrollY; scrollY = scrollY > 0 && maxY <= maxYBoundary ? 0 : scrollY; - scrollContentsBy(-scrollX, -scrollY); + for (auto* curWindow : subWindows) + { + // if widgets are maximized, then they shouldn't be moved + // moving a maximized window's normalGeometry is not implemented because of difficulties + if (curWindow->isMaximized() == false) + { + curWindow->move(curWindow->x() - scrollX, curWindow->y() - scrollY); + } + } + m_lastX = event->x(); m_lastY = event->y(); } diff --git a/src/gui/MidiCCRackView.cpp b/src/gui/MidiCCRackView.cpp index a0b1496fb..efeb46d46 100644 --- a/src/gui/MidiCCRackView.cpp +++ b/src/gui/MidiCCRackView.cpp @@ -79,6 +79,7 @@ MidiCCRackView::MidiCCRackView(InstrumentTrack * track) : auto knobsScrollArea = new QScrollArea(); auto knobsArea = new QWidget(); auto knobsAreaLayout = new QGridLayout(); + knobsAreaLayout->setVerticalSpacing(10); knobsArea->setLayout(knobsAreaLayout); knobsScrollArea->setWidget(knobsArea); @@ -86,24 +87,21 @@ MidiCCRackView::MidiCCRackView(InstrumentTrack * track) : knobsGroupBoxLayout->addWidget(knobsScrollArea); - // Adds the controller knobs + // Adds the controller knobs and sets their models for (int i = 0; i < MidiControllerCount; ++i) { - 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); + auto knob = new Knob(KnobType::Bright26, tr("CC %1").arg(i), this); + knob->setModel(m_track->m_midiCCModel[i].get()); + knobsAreaLayout->addWidget(knob, i/4, i%4, Qt::AlignHCenter); + + // TODO It seems that this is not really used/needed? + m_controllerKnob[i] = knob; } // Set all the models // Set the LED button to enable/disable the track midi cc m_midiCCGroupBox->setModel(m_track->m_midiCCEnable.get()); - // Set the model for each Knob - for (int i = 0; i < MidiControllerCount; ++i) - { - m_controllerKnob[i]->setModel(m_track->m_midiCCModel[i].get()); - } - // Connection to update the name of the track on the label connect(m_track, SIGNAL(nameChanged()), this, SLOT(renameWindow())); diff --git a/src/gui/MixerChannelView.cpp b/src/gui/MixerChannelView.cpp index 1eb2fd1bb..3391affce 100644 --- a/src/gui/MixerChannelView.cpp +++ b/src/gui/MixerChannelView.cpp @@ -107,18 +107,16 @@ MixerChannelView::MixerChannelView(QWidget* parent, MixerView* mixerView, int ch renameLineEditProxy->setRotation(-90); m_renameLineEditView->setFixedSize(m_renameLineEdit->height() + 5, m_renameLineEdit->width() + 5); - m_muteButton = new PixmapButton(this, tr("Mute")); + m_muteButton = new AutomatableButton(this, tr("Mute")); m_muteButton->setModel(&mixerChannel->m_muteModel); - m_muteButton->setActiveGraphic(embed::getIconPixmap("mute_active")); - m_muteButton->setInactiveGraphic(embed::getIconPixmap("mute_inactive")); m_muteButton->setCheckable(true); + m_muteButton->setObjectName("btn-mute"); m_muteButton->setToolTip(tr("Mute this channel")); - m_soloButton = new PixmapButton(this, tr("Solo")); + m_soloButton = new AutomatableButton(this, tr("Solo")); m_soloButton->setModel(&mixerChannel->m_soloModel); - m_soloButton->setActiveGraphic(embed::getIconPixmap("solo_active")); - m_soloButton->setInactiveGraphic(embed::getIconPixmap("solo_inactive")); m_soloButton->setCheckable(true); + m_soloButton->setObjectName("btn-solo"); m_soloButton->setToolTip(tr("Solo this channel")); auto soloMuteLayout = new QVBoxLayout(); @@ -149,6 +147,8 @@ MixerChannelView::MixerChannelView(QWidget* parent, MixerView* mixerView, int ch mainLayout->addWidget(m_fader, 1, Qt::AlignHCenter); connect(m_renameLineEdit, &QLineEdit::editingFinished, this, &MixerChannelView::renameFinished); + + setFocusPolicy(Qt::StrongFocus); } void MixerChannelView::contextMenuEvent(QContextMenuEvent*) @@ -222,23 +222,23 @@ void MixerChannelView::mouseDoubleClickEvent(QMouseEvent*) renameChannel(); } -bool MixerChannelView::eventFilter(QObject*, QEvent* event) +void MixerChannelView::keyPressEvent(QKeyEvent* ke) { - // If we are in a rename, capture the enter/return events and handle them - if (event->type() == QEvent::KeyPress) + if (ke->key() == Qt::Key_Enter || ke->key() == Qt::Key_Return) { - auto keyEvent = static_cast(event); - if (keyEvent->key() == Qt::Key_Enter || keyEvent->key() == Qt::Key_Return) + if (m_inRename) { - if (m_inRename) - { - renameFinished(); - event->accept(); // Stop the event from propagating - return true; - } + renameFinished(); } } - return false; + else if (ke->key() == Qt::Key_Space) + { + m_fader->adjustByDialog(); + } + else + { + ke->ignore(); + } } void MixerChannelView::setChannelIndex(int index) diff --git a/src/gui/MixerView.cpp b/src/gui/MixerView.cpp index d05ff097d..fd6391bf0 100644 --- a/src/gui/MixerView.cpp +++ b/src/gui/MixerView.cpp @@ -22,6 +22,7 @@ * */ +#include "MixerView.h" #include #include @@ -29,10 +30,7 @@ #include #include -#include "lmms_math.h" - #include "MixerChannelView.h" -#include "MixerView.h" #include "Knob.h" #include "Mixer.h" #include "GuiApplication.h" @@ -376,6 +374,7 @@ void MixerView::updateMixerChannel(int index) } thisLine->m_sendButton->updateLightStatus(); + thisLine->m_renameLineEdit->setText(thisLine->elideName(thisLine->mixerChannel()->m_name)); thisLine->update(); } @@ -537,15 +536,8 @@ void MixerView::keyPressEvent(QKeyEvent * e) case Qt::Key_F2: renameChannel(m_currentMixerChannel->channelIndex()); break; - case Qt::Key_Space: - { - auto* mixerChannel = currentMixerChannel(); - - if (mixerChannel) - { - mixerChannel->fader()->adjustByDialog(); - } - } + default: + e->ignore(); break; } } diff --git a/src/gui/SampleThumbnail.cpp b/src/gui/SampleThumbnail.cpp index c31c0d93e..8ec18b5cb 100644 --- a/src/gui/SampleThumbnail.cpp +++ b/src/gui/SampleThumbnail.cpp @@ -70,6 +70,7 @@ SampleThumbnail::Thumbnail SampleThumbnail::Thumbnail::zoomOut(float factor) con } SampleThumbnail::SampleThumbnail(const Sample& sample) + : m_buffer(sample.buffer()) { auto entry = SampleThumbnailEntry{sample.sampleFile(), QFileInfo{sample.sampleFile()}.lastModified()}; if (!entry.filePath.isEmpty()) @@ -91,11 +92,9 @@ SampleThumbnail::SampleThumbnail(const Sample& sample) s_sampleThumbnailCacheMap[std::move(entry)] = m_thumbnailCache; } - if (!sample.buffer()) { throw std::runtime_error{"Cannot create a sample thumbnail with no buffer"}; } - if (sample.sampleSize() == 0) { return; } - - const auto fullResolutionWidth = sample.sampleSize() * DEFAULT_CHANNELS; - m_thumbnailCache->emplace_back(&sample.buffer()->data()->left(), fullResolutionWidth, fullResolutionWidth); + const auto flatBuffer = m_buffer->data()->data(); + const auto flatBufferSize = m_buffer->size() * DEFAULT_CHANNELS; + m_thumbnailCache->emplace_back(flatBuffer, flatBufferSize, flatBufferSize / AggregationPerZoomStep); while (m_thumbnailCache->back().width() >= AggregationPerZoomStep) { @@ -107,48 +106,71 @@ SampleThumbnail::SampleThumbnail(const Sample& sample) void SampleThumbnail::visualize(VisualizeParameters parameters, QPainter& painter) const { const auto& sampleRect = parameters.sampleRect; - const auto& drawRect = parameters.drawRect.isNull() ? sampleRect : parameters.drawRect; - const auto& viewportRect = parameters.viewportRect.isNull() ? drawRect : parameters.viewportRect; + const auto& viewportRect = parameters.viewportRect.isNull() ? sampleRect : parameters.viewportRect; - const auto renderRect = sampleRect.intersected(drawRect).intersected(viewportRect); + const auto renderRect = sampleRect.intersected(viewportRect); if (renderRect.isNull()) { return; } const auto sampleRange = parameters.sampleEnd - parameters.sampleStart; - if (sampleRange <= 0 || sampleRange > 1) { return; } + if (sampleRange <= 0.0f || sampleRange > 1.0f) { return; } - const auto targetThumbnailWidth = static_cast(static_cast(sampleRect.width()) / sampleRange); + const auto targetThumbnailWidth = static_cast(sampleRect.width() / sampleRange); const auto finerThumbnail = std::find_if(m_thumbnailCache->rbegin(), m_thumbnailCache->rend(), [&](const auto& thumbnail) { return thumbnail.width() >= targetThumbnailWidth; }); - if (finerThumbnail == m_thumbnailCache->rend()) - { - qDebug() << "Could not find closest finer thumbnail for a target width of" << targetThumbnailWidth; - return; - } + const auto useOriginalBuffer = finerThumbnail == m_thumbnailCache->rend(); + const auto drawOriginalBuffer = static_cast(targetThumbnailWidth) == m_buffer->size(); painter.save(); painter.setRenderHint(QPainter::Antialiasing, true); - const auto thumbnailBeginForward = std::max(renderRect.x() - sampleRect.x(), static_cast(parameters.sampleStart * targetThumbnailWidth)); - const auto thumbnailEndForward = std::max(renderRect.x() + renderRect.width() - sampleRect.x(), static_cast(parameters.sampleEnd * targetThumbnailWidth)); + const auto thumbnailBeginForward = std::max(renderRect.x() - sampleRect.x(), parameters.sampleStart * targetThumbnailWidth); + const auto thumbnailEndForward = std::max(renderRect.x() + renderRect.width() - sampleRect.x(), parameters.sampleEnd * targetThumbnailWidth); const auto thumbnailBegin = parameters.reversed ? targetThumbnailWidth - thumbnailBeginForward - 1 : thumbnailBeginForward; const auto thumbnailEnd = parameters.reversed ? targetThumbnailWidth - thumbnailEndForward : thumbnailEndForward; const auto advanceThumbnailBy = parameters.reversed ? -1 : 1; - const auto finerThumbnailScaleFactor = static_cast(finerThumbnail->width()) / targetThumbnailWidth; - const auto yScale = drawRect.height() / 2 * parameters.amplification; + const auto finerThumbnailWidth = useOriginalBuffer ? m_buffer->size() : finerThumbnail->width(); + const auto finerThumbnailScaleFactor = static_cast(finerThumbnailWidth) / targetThumbnailWidth; + const auto yScale = renderRect.height() / 2 * parameters.amplification; for (auto x = renderRect.x(), i = thumbnailBegin; x < renderRect.x() + renderRect.width() && i != thumbnailEnd; - ++x, i += advanceThumbnailBy) + ++x, i += advanceThumbnailBy) { - const auto beginAggregationAt = &(*finerThumbnail)[static_cast(std::floor(i * finerThumbnailScaleFactor))]; - const auto endAggregationAt = &(*finerThumbnail)[static_cast(std::ceil((i + 1) * finerThumbnailScaleFactor))]; - const auto peak = std::accumulate(beginAggregationAt, endAggregationAt, Thumbnail::Peak{}); + if (useOriginalBuffer && drawOriginalBuffer) + { + const auto value = m_buffer->data()->data()[i]; + painter.drawPoint(x, renderRect.center().y() - value * yScale); + continue; + } + else + { + const auto beginIndex = std::clamp(std::floor(i * finerThumbnailScaleFactor), 0, finerThumbnail->width() - 1); + const auto endIndex = std::clamp(std::ceil((i + 1) * finerThumbnailScaleFactor), 0, finerThumbnail->width() - 1); - const auto yMin = drawRect.center().y() - peak.min * yScale; - const auto yMax = drawRect.center().y() - peak.max * yScale; + auto minPeak = 0.f; + auto maxPeak = 0.f; - painter.drawLine(x, yMin, x, yMax); + if (useOriginalBuffer) + { + const auto flatBuffer = m_buffer->data()->data(); + const auto [min, max] = std::minmax_element(flatBuffer + beginIndex, flatBuffer + endIndex); + minPeak = *min; + maxPeak = *max; + } + else + { + const auto beginAggregationAt = finerThumbnail->data() + beginIndex; + const auto endAggregationAt = finerThumbnail->data() + endIndex; + const auto peak = std::accumulate(beginAggregationAt, endAggregationAt, Thumbnail::Peak{}); + minPeak = peak.min; + maxPeak = peak.max; + } + + const auto yMin = renderRect.center().y() - minPeak * yScale; + const auto yMax = renderRect.center().y() - maxPeak * yScale; + painter.drawLine(x, yMin, x, yMax); + } } painter.restore(); diff --git a/src/gui/SampleTrackWindow.cpp b/src/gui/SampleTrackWindow.cpp index e8bd5970d..921ab3fce 100644 --- a/src/gui/SampleTrackWindow.cpp +++ b/src/gui/SampleTrackWindow.cpp @@ -32,7 +32,6 @@ #include #include "EffectRackView.h" -#include "PixmapButton.h" #include "embed.h" #include "GuiApplication.h" #include "Knob.h" @@ -101,18 +100,16 @@ SampleTrackWindow::SampleTrackWindow(SampleTrackView * tv) : soloMuteLayout->setContentsMargins(0, 0, 2, 0); soloMuteLayout->setSpacing(2); - m_muteBtn = new PixmapButton(this, tr("Mute")); + m_muteBtn = new AutomatableButton(this, tr("Mute")); m_muteBtn->setModel(&m_track->m_mutedModel); - m_muteBtn->setActiveGraphic(embed::getIconPixmap("mute_active")); - m_muteBtn->setInactiveGraphic(embed::getIconPixmap("mute_inactive")); + m_muteBtn->setObjectName("btn-mute"); m_muteBtn->setCheckable(true); m_muteBtn->setToolTip(tr("Mute this sample track")); soloMuteLayout->addWidget(m_muteBtn, 0, widgetAlignment); - m_soloBtn = new PixmapButton(this, tr("Solo")); + m_soloBtn = new AutomatableButton(this, tr("Solo")); m_soloBtn->setModel(&m_track->m_soloModel); - m_soloBtn->setActiveGraphic(embed::getIconPixmap("solo_active")); - m_soloBtn->setInactiveGraphic(embed::getIconPixmap("solo_inactive")); + m_soloBtn->setObjectName("btn-solo"); m_soloBtn->setCheckable(true); m_soloBtn->setToolTip(tr("Solo this sample track")); soloMuteLayout->addWidget(m_soloBtn, 0, widgetAlignment); @@ -270,7 +267,7 @@ void SampleTrackWindow::closeEvent(QCloseEvent* ce) hide(); } - m_stv->m_tlb->setFocus(); + m_stv->setFocus(); m_stv->m_tlb->setChecked(false); } diff --git a/src/gui/clips/AutomationClipView.cpp b/src/gui/clips/AutomationClipView.cpp index e098710d9..89fbd4f8e 100644 --- a/src/gui/clips/AutomationClipView.cpp +++ b/src/gui/clips/AutomationClipView.cpp @@ -23,6 +23,8 @@ */ #include "AutomationClipView.h" +#include + #include #include #include @@ -37,6 +39,8 @@ #include "StringPairDrag.h" #include "TextFloat.h" #include "Track.h" +#include "TrackContainerView.h" +#include "TrackView.h" #include "Engine.h" @@ -149,7 +153,7 @@ void AutomationClipView::flipY() void AutomationClipView::flipX() { - m_clip->flipX( m_clip->length() ); + m_clip->flipX(std::max(0, -m_clip->startTimeOffset()), std::max(0, m_clip->length() - m_clip->startTimeOffset())); update(); } @@ -183,6 +187,7 @@ void AutomationClipView::constructContextMenu( QMenu * _cm ) _cm->addAction( embed::getIconPixmap( "flip_x" ), tr( "Flip Horizontally (Visible)" ), this, SLOT(flipX())); + if (!m_clip->m_objects.empty()) { _cm->addSeparator(); @@ -207,6 +212,8 @@ void AutomationClipView::constructContextMenu( QMenu * _cm ) void AutomationClipView::mouseDoubleClickEvent( QMouseEvent * me ) { + if (m_trackView->trackContainerView()->knifeMode()) { return; } + if(me->button() != Qt::LeftButton) { me->ignore(); @@ -269,6 +276,7 @@ void AutomationClipView::paintEvent( QPaintEvent * ) const float y_scale = max - min; const float h = ( height() - 2 * BORDER_WIDTH ) / y_scale; const float ppTick = ppb / TimePos::ticksPerBar(); + const int offset = m_clip->startTimeOffset() * ppTick; p.translate( 0.0f, max * height() / y_scale - BORDER_WIDTH ); p.scale( 1.0f, -h ); @@ -289,7 +297,7 @@ void AutomationClipView::paintEvent( QPaintEvent * ) { if( it+1 == m_clip->getTimeMap().end() ) { - const float x1 = POS(it) * ppTick; + const float x1 = POS(it) * ppTick + offset; const auto x2 = (float)(width() - BORDER_WIDTH); if( x1 > ( width() - BORDER_WIDTH ) ) break; // We are drawing the space after the last node, so we use the outValue @@ -317,20 +325,19 @@ void AutomationClipView::paintEvent( QPaintEvent * ) : INVAL(it + 1); QPainterPath path; - QPointF origin = QPointF(POS(it) * ppTick, 0.0f); - path.moveTo( origin ); - path.moveTo(QPointF(POS(it) * ppTick,values[0])); + QPointF origin = QPointF(POS(it) * ppTick + offset, 0.0f); + path.moveTo(origin); + path.moveTo(QPointF(POS(it) * ppTick + offset, values[0])); for (int i = POS(it) + 1; i < POS(it + 1); i++) { - float x = i * ppTick; - if( x > ( width() - BORDER_WIDTH ) ) break; + float x = i * ppTick + offset; + if(x > (width() - BORDER_WIDTH)) break; float value = values[i - POS(it)]; - path.lineTo( QPointF( x, value ) ); - + path.lineTo(QPointF(x, value)); } - path.lineTo((POS(it + 1)) * ppTick, nextValue); - path.lineTo((POS(it + 1)) * ppTick, 0.0f); - path.lineTo( origin ); + path.lineTo((POS(it + 1)) * ppTick + offset, nextValue); + path.lineTo((POS(it + 1)) * ppTick + offset, 0.0f); + path.lineTo(origin); if( gradient() ) { @@ -355,10 +362,10 @@ void AutomationClipView::paintEvent( QPaintEvent * ) const int bx = BORDER_WIDTH + static_cast(ppb * b) - 2; //top line - p.drawLine(bx, BORDER_WIDTH, bx, BORDER_WIDTH + lineSize); + p.drawLine(bx + offset, BORDER_WIDTH, bx + offset, BORDER_WIDTH + lineSize); //bottom line - p.drawLine(bx, rect().bottom() - (lineSize + BORDER_WIDTH), bx, rect().bottom() - BORDER_WIDTH); + p.drawLine(bx + offset, rect().bottom() - (lineSize + BORDER_WIDTH), bx + offset, rect().bottom() - BORDER_WIDTH); } // recording icon for when recording automation @@ -388,6 +395,12 @@ void AutomationClipView::paintEvent( QPaintEvent * ) p.drawPixmap( spacing, height() - ( size + spacing ), embed::getIconPixmap( "muted", size, size ) ); } + + if (m_marker) + { + p.setPen(markerColor()); + p.drawLine(m_markerPos, rect().bottom(), m_markerPos, rect().top()); + } p.end(); @@ -484,5 +497,4 @@ void AutomationClipView::scaleTimemapToFit( float oldMin, float oldMax ) m_clip->generateTangents(); } - } // namespace lmms::gui diff --git a/src/gui/clips/ClipView.cpp b/src/gui/clips/ClipView.cpp index f98351f37..89d5a5e79 100644 --- a/src/gui/clips/ClipView.cpp +++ b/src/gui/clips/ClipView.cpp @@ -101,6 +101,7 @@ ClipView::ClipView( Clip * clip, m_textShadowColor( 0, 0, 0 ), m_patternClipBackground( 0, 0, 0 ), m_gradient( true ), + m_markerColor(0, 0, 0), m_mouseHotspotHand( 0, 0 ), m_mouseHotspotKnife( 0, 0 ), m_cursorHand( QCursor( embed::getIconPixmap( "hand" ) ) ), @@ -232,6 +233,9 @@ QColor ClipView::patternClipBackground() const bool ClipView::gradient() const { return m_gradient; } +QColor ClipView::markerColor() const +{ return m_markerColor; } + //! \brief CSS theming qproperty access method void ClipView::setMutedColor( const QColor & c ) { m_mutedColor = QColor( c ); } @@ -259,6 +263,9 @@ void ClipView::setPatternClipBackground( const QColor & c ) void ClipView::setGradient( const bool & b ) { m_gradient = b; } +void ClipView::setMarkerColor(const QColor & c) +{ m_markerColor = QColor(c); } + // access needsUpdate member variable bool ClipView::needsUpdate() { return m_needsUpdate; } @@ -306,6 +313,8 @@ void ClipView::remove() // 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(); + + m_trackView->update(); } @@ -492,17 +501,14 @@ 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 || pClip)))) + if (!me->buttons() && m_clip->getResizable() && !isSelected() + && ((me->x() > width() - RESIZE_GRIP_WIDTH) || (me->x() < RESIZE_GRIP_WIDTH))) { setCursor(Qt::SizeHorCursor); } // If we are in the middle on knife mode, use the knife cursor - else if (sClip && m_trackView->trackContainerView()->knifeMode() && !isSelected()) + else if (m_trackView->trackContainerView()->knifeMode() && !isSelected()) { setCursor(m_cursorKnife); } @@ -629,11 +635,9 @@ void ClipView::mousePressEvent( QMouseEvent * me ) setInitialOffsets(); 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() & KBD_COPY_MODIFIER && !(sClip && knifeMode)) + if (me->modifiers() & KBD_COPY_MODIFIER && !knifeMode) { if( isSelected() ) { @@ -665,7 +669,7 @@ void ClipView::mousePressEvent( QMouseEvent * me ) setInitialPos( me->pos() ); setInitialOffsets(); - if( m_clip->getAutoResize() ) + if (!m_clip->getResizable() && !knifeMode) { // Always move clips that can't be manually resized m_action = Action::Move; setCursor( Qt::SizeAllCursor ); @@ -675,12 +679,12 @@ void ClipView::mousePressEvent( QMouseEvent * me ) m_action = Action::Resize; setCursor( Qt::SizeHorCursor ); } - else if( me->x() < RESIZE_GRIP_WIDTH && (sClip || pClip) ) + else if (me->x() < RESIZE_GRIP_WIDTH) { m_action = Action::ResizeLeft; setCursor( Qt::SizeHorCursor ); } - else if( sClip && knifeMode ) + else if (knifeMode) { m_action = Action::Split; setCursor( m_cursorKnife ); @@ -723,9 +727,21 @@ void ClipView::mousePressEvent( QMouseEvent * me ) } delete m_hint; - 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." ); + QString hint; + if (m_action == Action::Move || m_action == Action::MoveSelection) + { + hint = tr("Press <%1> and drag to make a copy."); + } + else if (m_action == Action::Split) + { + hint = dynamic_cast(this) + ? tr("Press <%1> or for unquantized splitting.\nPress for destructive splitting.") + : tr("Press <%1> or for unquantized splitting."); + } + else + { + hint = tr("Press <%1> or for unquantized resizing."); + } m_hint = TextFloat::displayMessage( tr( "Hint" ), hint.arg(UI_COPY_KEY), embed::getIconPixmap( "hint" ), 0 ); } @@ -743,12 +759,8 @@ void ClipView::mousePressEvent( QMouseEvent * me ) if (m_action == Action::Split) { m_action = Action::None; - auto sClip = dynamic_cast(m_clip); - if (sClip) - { - setMarkerEnabled( false ); - update(); - } + setMarkerEnabled(false); + update(); } } else if( me->button() == Qt::MiddleButton ) @@ -891,6 +903,7 @@ void ClipView::mouseMoveEvent( QMouseEvent * me ) setInitialPos( m_initialMousePos ); // Don't resize to less than 1 tick m_clip->changeLength( qMax( 1, l ) ); + m_clip->setAutoResize(false); } else if ( me->modifiers() & Qt::ShiftModifier ) { // If shift is held, quantize clip's end position @@ -899,6 +912,7 @@ void ClipView::mouseMoveEvent( QMouseEvent * me ) TimePos min = m_initialClipPos.quantize( snapSize ); if ( min <= m_initialClipPos ) min += snapLength; m_clip->changeLength( qMax(min - m_initialClipPos, end - m_initialClipPos) ); + m_clip->setAutoResize(false); } else { // Otherwise, resize in fixed increments @@ -908,66 +922,70 @@ void ClipView::mouseMoveEvent( QMouseEvent * me ) auto min = TimePos(initialLength % snapLength); if (min < 1) min += snapLength; m_clip->changeLength( qMax( min, initialLength + offset) ); + m_clip->setAutoResize(false); } } else { - auto sClip = dynamic_cast(m_clip); auto pClip = dynamic_cast(m_clip); - if( sClip || pClip ) + + const int x = mapToParent( me->pos() ).x() - m_initialMousePos.x(); + + TimePos t = qMax( 0, (int) + m_trackView->trackContainerView()->currentPosition() + + static_cast( x * TimePos::ticksPerBar() / ppb ) ); + + if (!isResizableBeforeStart()) { - const int x = mapToParent( me->pos() ).x() - m_initialMousePos.x(); + t = std::max(t, static_cast(m_clip->startPosition() + m_clip->startTimeOffset())); + } - TimePos t = qMax( 0, (int) - m_trackView->trackContainerView()->currentPosition() + - static_cast( x * TimePos::ticksPerBar() / ppb ) ); + if( unquantizedModHeld(me) ) + { // We want to preserve this adjusted offset, + // even if the user switches to snapping later + setInitialPos( m_initialMousePos ); + //Don't resize to less than 1 tick + t = qMin( m_initialClipEnd - 1, t); + } + else if( me->modifiers() & Qt::ShiftModifier ) + { // If shift is held, quantize clip's start position + // Don't let the start position move past the end position + TimePos max = m_initialClipEnd.quantize( snapSize ); + if ( max >= m_initialClipEnd ) max -= snapLength; + t = qMin( max, t.quantize( snapSize ) ); + } + else + { // Otherwise, resize in fixed increments + // Don't resize to less than 1 tick + TimePos initialLength = m_initialClipEnd - m_initialClipPos; + auto minLength = TimePos(initialLength % snapLength); + if (minLength < 1) minLength += snapLength; + TimePos offset = TimePos(t - m_initialClipPos).quantize( snapSize ); + t = qMin( m_initialClipEnd - minLength, m_initialClipPos + offset ); + } - if( unquantizedModHeld(me) ) - { // We want to preserve this adjusted offset, - // even if the user switches to snapping later - setInitialPos( m_initialMousePos ); - //Don't resize to less than 1 tick - t = qMin( m_initialClipEnd - 1, t); - } - else if( me->modifiers() & Qt::ShiftModifier ) - { // If shift is held, quantize clip's start position - // Don't let the start position move past the end position - TimePos max = m_initialClipEnd.quantize( snapSize ); - if ( max >= m_initialClipEnd ) max -= snapLength; - t = qMin( max, t.quantize( snapSize ) ); + TimePos positionOffset = m_clip->startPosition() - t; + if (m_clip->length() + positionOffset >= 1) + { + m_clip->movePosition(t); + m_clip->changeLength(m_clip->length() + positionOffset); + 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); } else - { // Otherwise, resize in fixed increments - // Don't resize to less than 1 tick - TimePos initialLength = m_initialClipEnd - m_initialClipPos; - auto minLength = TimePos(initialLength % snapLength); - if (minLength < 1) minLength += snapLength; - TimePos offset = TimePos(t - m_initialClipPos).quantize( snapSize ); - t = qMin( m_initialClipEnd - minLength, m_initialClipPos + offset ); - } - - TimePos positionOffset = m_clip->startPosition() - t; - if (m_clip->length() + positionOffset >= 1) { - 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); - } + m_clip->setStartTimeOffset(m_clip->startTimeOffset() + positionOffset); } + m_clip->setAutoResize(false); } } s_textFloat->setText( tr( "%1:%2 (%3:%4 to %5:%6)" ). @@ -984,11 +1002,8 @@ void ClipView::mouseMoveEvent( QMouseEvent * me ) } else if( m_action == Action::Split ) { - auto sClip = dynamic_cast(m_clip); - if (sClip) { - setCursor( m_cursorKnife ); - setMarkerPos( knifeMarkerPos( me ) ); - } + setCursor(m_cursorKnife); + setMarkerPos(knifeMarkerPos(me)); update(); } // None of the actions above, we will just handle the cursor @@ -1025,10 +1040,15 @@ void ClipView::mouseReleaseEvent( QMouseEvent * me ) { const float ppb = m_trackView->trackContainerView()->pixelsPerBar(); const TimePos relPos = me->pos().x() * TimePos::ticksPerBar() / ppb; - splitClip(unquantizedModHeld(me) ? - relPos : - quantizeSplitPos(relPos, me->modifiers() & Qt::ShiftModifier) - ); + if (me->modifiers() & Qt::ShiftModifier) + { + destructiveSplitClip(unquantizedModHeld(me) ? relPos : quantizeSplitPos(relPos)); + } + else + { + splitClip(unquantizedModHeld(me) ? relPos : quantizeSplitPos(relPos)); + } + setMarkerEnabled(false); } m_action = Action::None; @@ -1081,15 +1101,6 @@ void ClipView::contextMenuEvent( QContextMenuEvent * cme ) ? tr("Cut") : tr("Cut selection"), [this](){ contextMenuAction( ContextMenuAction::Cut ); } ); - - if (canMergeSelection(selectedClips)) - { - contextMenu.addAction( - embed::getIconPixmap("edit_merge"), - tr("Merge Selection"), - [this]() { contextMenuAction(ContextMenuAction::Merge); } - ); - } } contextMenu.addAction( @@ -1122,6 +1133,12 @@ void ClipView::contextMenuEvent( QContextMenuEvent * cme ) colorMenu.addAction(tr("Pick random"), this, SLOT(randomizeColor())); contextMenu.addMenu(&colorMenu); + contextMenu.addAction( + m_clip->getAutoResize() ? embed::getIconPixmap("auto_resize_disable") : embed::getIconPixmap("auto_resize"), + m_clip->getAutoResize() ? tr("Disable auto-resize") : tr("Enable auto-resize"), + this, &ClipView::toggleSelectedAutoResize + ); + constructContextMenu( &contextMenu ); contextMenu.exec( QCursor::pos() ); @@ -1150,9 +1167,6 @@ void ClipView::contextMenuAction( ContextMenuAction action ) case ContextMenuAction::Mute: toggleMute( active ); break; - case ContextMenuAction::Merge: - mergeClips(active); - break; } } @@ -1236,102 +1250,19 @@ void ClipView::toggleMute( QVector clipvs ) } } -bool ClipView::canMergeSelection(QVector clipvs) +void ClipView::toggleSelectedAutoResize() { - // Can't merge a single Clip - if (clipvs.size() < 2) { return false; } - - // We check if the owner of the first Clip is an Instrument Track - bool isInstrumentTrack = dynamic_cast(clipvs.at(0)->getTrackView()); - - // Then we create a set with all the Clips owners - std::set ownerTracks; - for (auto clipv: clipvs) { ownerTracks.insert(clipv->getTrackView()); } - - // Can merge if there's only one owner track and it's an Instrument Track - return isInstrumentTrack && ownerTracks.size() == 1; + const bool newState = !m_clip->getAutoResize(); + std::set journaledTracks; + for (auto clipv: getClickedClips()) + { + Clip* clip = clipv->getClip(); + if (journaledTracks.insert(clip->getTrack()).second) { clip->getTrack()->addJournalCheckPoint(); } + clip->setAutoResize(newState); + clip->updateLength(); + } } -void ClipView::mergeClips(QVector clipvs) -{ - // Get the track that we are merging Clips in - auto track = dynamic_cast(clipvs.at(0)->getTrackView()->getTrack()); - - if (!track) - { - qWarning("Warning: Couldn't retrieve InstrumentTrack in mergeClips()"); - return; - } - - // For Undo/Redo - track->addJournalCheckPoint(); - track->saveJournallingState(false); - - // Find the earliest position of all the selected ClipVs - const auto earliestClipV = std::min_element(clipvs.constBegin(), clipvs.constEnd(), - [](ClipView* a, ClipView* b) - { - return a->getClip()->startPosition() < - b->getClip()->startPosition(); - } - ); - - const TimePos earliestPos = (*earliestClipV)->getClip()->startPosition(); - - // Create a clip where all notes will be added - auto newMidiClip = dynamic_cast(track->createClip(earliestPos)); - if (!newMidiClip) - { - qWarning("Warning: Failed to convert Clip to MidiClip on mergeClips"); - return; - } - - newMidiClip->saveJournallingState(false); - - // Add the notes and remove the Clips that are being merged - for (auto clipv: clipvs) - { - // Convert ClipV to MidiClipView - auto mcView = dynamic_cast(clipv); - - if (!mcView) - { - qWarning("Warning: Non-MidiClip Clip on InstrumentTrack"); - continue; - } - - const NoteVector& currentClipNotes = mcView->getMidiClip()->notes(); - TimePos mcViewPos = mcView->getMidiClip()->startPosition(); - - for (Note* note: currentClipNotes) - { - Note* newNote = newMidiClip->addNote(*note, false); - TimePos originalNotePos = newNote->pos(); - newNote->setPos(originalNotePos + (mcViewPos - earliestPos)); - } - - // We disable the journalling system before removing, so the - // removal doesn't get added to the undo/redo history - clipv->getClip()->saveJournallingState(false); - // No need to check for nullptr because we check while building the clipvs QVector - clipv->remove(); - } - - // Update length since we might have moved notes beyond the end of the MidiClip length - newMidiClip->updateLength(); - // Rearrange notes because we might have moved them - newMidiClip->rearrangeAllNotes(); - // Restore journalling states now that the operation is finished - newMidiClip->restoreJournallingState(); - track->restoreJournallingState(); - // Update song - Engine::getSong()->setModified(); - getGUI()->songEditor()->update(); -} - - - - /*! \brief How many pixels a bar takes for this ClipView. * * \return the number of pixels per bar. @@ -1438,7 +1369,7 @@ int ClipView::knifeMarkerPos( QMouseEvent * me ) const float ppb = m_trackView->trackContainerView()->pixelsPerBar(); TimePos midiPos = markerPos * TimePos::ticksPerBar() / ppb; //2: Snap to the correct position, based on modifier keys - midiPos = quantizeSplitPos( midiPos, me->modifiers() & Qt::ShiftModifier ); + midiPos = quantizeSplitPos(midiPos); //3: Convert back to a pixel position return midiPos * ppb / TimePos::ticksPerBar(); } @@ -1447,23 +1378,20 @@ int ClipView::knifeMarkerPos( QMouseEvent * me ) -TimePos ClipView::quantizeSplitPos( TimePos midiPos, bool shiftMode ) +TimePos ClipView::quantizeSplitPos(TimePos midiPos) { const float snapSize = getGUI()->songEditor()->m_editor->getSnapSize(); - if ( shiftMode ) - { //If shift is held we quantize the length of the new left clip... - const TimePos leftPos = midiPos.quantize( snapSize ); - //...or right clip... - const TimePos rightOff = m_clip->length() - midiPos; - const TimePos rightPos = m_clip->length() - rightOff.quantize( snapSize ); - //...whichever gives a position closer to the cursor - if (std::abs(leftPos - midiPos) < std::abs(rightPos - midiPos)) { return leftPos; } - else { return rightPos; } - } - else - { - return TimePos(midiPos + m_initialClipPos).quantize( snapSize ) - m_initialClipPos; - } + // quantize the length of the new left clip... + const TimePos leftPos = midiPos.quantize(snapSize); + //...or right clip... + const TimePos rightOff = m_clip->length() - midiPos; + const TimePos rightPos = m_clip->length() - rightOff.quantize(snapSize); + //...or the global gridlines + const TimePos globalPos = TimePos(midiPos + m_initialClipPos).quantize(snapSize) - m_initialClipPos; + //...whichever gives a position closer to the cursor + if (abs(leftPos - midiPos) <= abs(rightPos - midiPos) && abs(leftPos - midiPos) <= abs(globalPos - midiPos)) { return leftPos; } + else if (abs(rightPos - midiPos) <= abs(leftPos - midiPos) && abs(rightPos - midiPos) <= abs(globalPos - midiPos)) { return rightPos; } + else { return globalPos; } } @@ -1513,4 +1441,30 @@ auto ClipView::hasCustomColor() const -> bool return m_clip->color().has_value() || m_clip->getTrack()->color().has_value(); } +bool ClipView::splitClip(const TimePos pos) +{ + const TimePos splitPos = m_initialClipPos + pos; + + // Don't split if we slid off the Clip or if we're on the clip's start/end + // Cutting at exactly the start/end position would create a zero length + // clip (bad), and a clip the same length as the original one (pointless). + if (splitPos <= m_initialClipPos || splitPos >= m_initialClipEnd) { return false; } + + m_clip->getTrack()->addJournalCheckPoint(); + m_clip->getTrack()->saveJournallingState(false); + + auto rightClip = m_clip->clone(); + + m_clip->changeLength(splitPos - m_initialClipPos); + m_clip->setAutoResize(false); + + rightClip->movePosition(splitPos); + rightClip->changeLength(m_initialClipEnd - splitPos); + rightClip->setStartTimeOffset(m_clip->startTimeOffset() - m_clip->length()); + rightClip->setAutoResize(false); + + m_clip->getTrack()->restoreJournallingState(); + return true; +} + } // namespace lmms::gui diff --git a/src/gui/clips/MidiClipView.cpp b/src/gui/clips/MidiClipView.cpp index b735913e4..70f41eb89 100644 --- a/src/gui/clips/MidiClipView.cpp +++ b/src/gui/clips/MidiClipView.cpp @@ -33,14 +33,18 @@ #include #include #include +#include #include "AutomationEditor.h" #include "ConfigManager.h" #include "DeprecationHelper.h" #include "GuiApplication.h" +#include "InstrumentTrackView.h" #include "MidiClip.h" #include "PianoRoll.h" #include "RenameDialog.h" +#include "SongEditor.h" +#include "TrackContainerView.h" #include "TrackView.h" namespace lmms::gui @@ -215,6 +219,18 @@ void MidiClipView::constructContextMenu( QMenu * _cm ) _cm->addAction( embed::getIconPixmap( "edit_erase" ), tr( "Clear all notes" ), m_clip, SLOT(clear())); + + if (canMergeSelection(getClickedClips())) + { + _cm->addAction( + embed::getIconPixmap("edit_merge"), + tr("Merge Selection"), + [this]() { mergeClips(getClickedClips()); } + ); + } + + _cm->addAction(embed::getIconPixmap("clear_notes_out_of_bounds"), tr("Clear notes out of bounds"), [this]() { bulkClearNotesOutOfBounds(getClickedClips()); }); + if (!isBeat) { _cm->addAction(embed::getIconPixmap("scale"), tr("Transpose"), this, &MidiClipView::transposeSelection); @@ -242,6 +258,168 @@ void MidiClipView::constructContextMenu( QMenu * _cm ) +bool MidiClipView::canMergeSelection(QVector clipvs) +{ + // Can't merge a single Clip + if (clipvs.size() < 2) { return false; } + + // We check if the owner of the first Clip is an Instrument Track + bool isInstrumentTrack = dynamic_cast(clipvs.at(0)->getTrackView()); + + // Then we create a set with all the Clips owners + std::set ownerTracks; + for (auto clipv: clipvs) { ownerTracks.insert(clipv->getTrackView()); } + + // Can merge if there's only one owner track and it's an Instrument Track + return isInstrumentTrack && ownerTracks.size() == 1; +} + +void MidiClipView::mergeClips(QVector clipvs) +{ + // Get the track that we are merging Clips in + auto track = dynamic_cast(clipvs.at(0)->getTrackView()->getTrack()); + + if (!track) + { + qWarning("Warning: Couldn't retrieve InstrumentTrack in mergeClips()"); + return; + } + + // For Undo/Redo + track->addJournalCheckPoint(); + track->saveJournallingState(false); + + // Find the earliest position of all the selected ClipVs + const auto earliestClipV = std::min_element(clipvs.constBegin(), clipvs.constEnd(), + [](ClipView* a, ClipView* b) + { + return a->getClip()->startPosition() < + b->getClip()->startPosition(); + } + ); + const TimePos earliestPos = (*earliestClipV)->getClip()->startPosition(); + + // Find the latest position of all the selected ClipVs + const auto latestClipV = std::max_element(clipvs.constBegin(), clipvs.constEnd(), + [](ClipView* a, ClipView* b) + { + return a->getClip()->endPosition() < + b->getClip()->endPosition(); + } + ); + const TimePos latestPos = (*latestClipV)->getClip()->endPosition(); + + + // Create a clip where all notes will be added + auto newMidiClip = dynamic_cast(track->createClip(earliestPos)); + if (!newMidiClip) + { + qWarning("Warning: Failed to convert Clip to MidiClip on mergeClips"); + return; + } + + newMidiClip->saveJournallingState(false); + + // Add the notes and remove the Clips that are being merged + for (auto clipv: clipvs) + { + // Convert ClipV to MidiClipView + auto mcView = dynamic_cast(clipv); + + if (!mcView) + { + qWarning("Warning: Non-MidiClip Clip on InstrumentTrack"); + continue; + } + + const NoteVector& currentClipNotes = mcView->getMidiClip()->notes(); + TimePos mcViewPos = mcView->getMidiClip()->startPosition() + mcView->getMidiClip()->startTimeOffset(); + + const TimePos clipStartTime = -mcView->getMidiClip()->startTimeOffset(); + const TimePos clipEndTime = mcView->getMidiClip()->length() - mcView->getMidiClip()->startTimeOffset(); + + for (Note* note: currentClipNotes) + { + const TimePos newNoteStart = std::max(note->pos(), clipStartTime); + const TimePos newNoteEnd = std::min(note->endPos(), clipEndTime); + const TimePos newLength = newNoteEnd - newNoteStart; + if (newLength > 0) + { + Note* newNote = newMidiClip->addNote(*note, false); + newNote->setPos(newNoteStart + (mcViewPos - earliestPos)); + newNote->setLength(newLength); + } + } + + // We disable the journalling system before removing, so the + // removal doesn't get added to the undo/redo history + clipv->getClip()->saveJournallingState(false); + // No need to check for nullptr because we check while building the clipvs QVector + clipv->remove(); + } + + // Update length to extend from the start of the first clip to the end of the last clip + newMidiClip->changeLength(latestPos - earliestPos); + newMidiClip->setAutoResize(false); + // Rearrange notes because we might have moved them + newMidiClip->rearrangeAllNotes(); + // Restore journalling states now that the operation is finished + newMidiClip->restoreJournallingState(); + track->restoreJournallingState(); + // Update song + Engine::getSong()->setModified(); + getGUI()->songEditor()->update(); +} + +void MidiClipView::clearNotesOutOfBounds() +{ + m_clip->getTrack()->addJournalCheckPoint(); + m_clip->getTrack()->saveJournallingState(false); + + auto newClip = new MidiClip(static_cast(m_clip->getTrack())); + newClip->setAutoResize(m_clip->getAutoResize()); + newClip->movePosition(m_clip->startPosition()); + + TimePos startBound = -m_clip->startTimeOffset(); + TimePos endBound = m_clip->length() - m_clip->startTimeOffset(); + + for (Note const* note: m_clip->m_notes) + { + const TimePos newNoteStart = std::max(note->pos(), startBound) - startBound; + const TimePos newNoteEnd = std::min(note->endPos(), endBound) - startBound; + const TimePos newLength = newNoteEnd - newNoteStart; + if (newLength > 0) + { + Note newNote = Note{*note}; + newNote.setPos(newNoteStart); + newNote.setLength(newLength); + newClip->addNote(newNote, false); + } + } + newClip->changeLength(m_clip->length()); + newClip->updateLength(); + + remove(); + m_clip->getTrack()->restoreJournallingState(); +} + +void MidiClipView::bulkClearNotesOutOfBounds(QVector clipvs) +{ + for (auto clipv: clipvs) + { + // Convert ClipV to MidiClipView + auto mcView = dynamic_cast(clipv); + if (!mcView) + { + qWarning("Warning: Non-MidiClip Clip on InstrumentTrack"); + continue; + } + mcView->clearNotesOutOfBounds(); + } + Engine::getSong()->setModified(); + getGUI()->songEditor()->update(); +} + void MidiClipView::mousePressEvent( QMouseEvent * _me ) { @@ -299,6 +477,8 @@ void MidiClipView::mousePressEvent( QMouseEvent * _me ) void MidiClipView::mouseDoubleClickEvent(QMouseEvent *_me) { + if (m_trackView->trackContainerView()->knifeMode()) { return; } + if( _me->button() != Qt::LeftButton ) { _me->ignore(); @@ -442,11 +622,12 @@ void MidiClipView::paintEvent( QPaintEvent * ) // Compute pixels per bar const int baseWidth = fixedClips() ? parentWidget()->width() - 2 * BORDER_WIDTH : width() - BORDER_WIDTH; - const float pixelsPerBar = baseWidth / (float) m_clip->length().getBar(); + const float pixelsPerBar = 1.0f * baseWidth / m_clip->length() * TimePos::ticksPerBar(); + + const int offset = m_clip->startTimeOffset(); // Length of one bar/beat in the [0,1] x [0,1] coordinate system - const float barLength = 1. / m_clip->length().getBar(); - const float tickLength = barLength / TimePos::ticksPerBar(); + const float tickLength = 1.0f / m_clip->length(); const int x_base = BORDER_WIDTH; @@ -608,7 +789,7 @@ void MidiClipView::paintEvent( QPaintEvent * ) int mappedNoteKey = currentNote->key() - minKey; int invertedMappedNoteKey = adjustedNoteRange - mappedNoteKey - 1; - float const noteStartX = currentNote->pos() * tickLength; + float const noteStartX = (currentNote->pos() + offset) * tickLength; float const noteLength = currentNote->length() * tickLength; float const noteStartY = invertedMappedNoteKey * noteHeight; @@ -633,14 +814,15 @@ void MidiClipView::paintEvent( QPaintEvent * ) const int lineSize = 3; p.setPen( c.darker( 200 ) ); - for( bar_t t = 1; t < m_clip->length().getBar(); ++t ) + for(float t = (offset % TimePos::ticksPerBar()) * pixelsPerBar / TimePos::ticksPerBar(); t < m_clip->length(); t += pixelsPerBar) { - p.drawLine( x_base + static_cast( pixelsPerBar * t ) - 1, - BORDER_WIDTH, x_base + static_cast( - pixelsPerBar * t ) - 1, BORDER_WIDTH + lineSize ); - p.drawLine( x_base + static_cast( pixelsPerBar * t ) - 1, + p.drawLine( x_base + t - 1, + BORDER_WIDTH, + x_base + t - 1, + BORDER_WIDTH + lineSize ); + p.drawLine( x_base + t - 1, rect().bottom() - ( lineSize + BORDER_WIDTH ), - x_base + static_cast( pixelsPerBar * t ) - 1, + x_base + t - 1, rect().bottom() - BORDER_WIDTH ); } @@ -670,9 +852,80 @@ void MidiClipView::paintEvent( QPaintEvent * ) p.drawPixmap( spacing, height() - ( size + spacing ), embed::getIconPixmap( "muted", size, size ) ); } + + if (m_marker) + { + p.setPen(markerColor()); + p.drawLine(m_markerPos, rect().bottom(), m_markerPos, rect().top()); + } painter.drawPixmap( 0, 0, m_paintPixmap ); } +bool MidiClipView::destructiveSplitClip(const TimePos pos) +{ + const TimePos splitPos = m_initialClipPos + pos; + const TimePos internalSplitPos = pos - m_clip->startTimeOffset(); + + // Don't split if we slid off the Clip or if we're on the clip's start/end + // Cutting at exactly the start/end position would create a zero length + // clip (bad), and a clip the same length as the original one (pointless). + if (splitPos <= m_initialClipPos || splitPos >= m_initialClipEnd) { return false; } + + m_clip->getTrack()->addJournalCheckPoint(); + m_clip->getTrack()->saveJournallingState(false); + + auto leftClip = m_clip->clone(); + leftClip->clearNotes(); + auto rightClip = m_clip->clone(); + rightClip->clearNotes(); + + for (Note const* note : m_clip->m_notes) + { + if (note->pos() >= internalSplitPos) + { + auto movedNote = Note{*note}; + movedNote.setPos(note->pos() - internalSplitPos); + rightClip->addNote(movedNote, false); + } + else if (note->endPos() > internalSplitPos) + { + auto movedNote = Note{*note}; + movedNote.setPos(0); + movedNote.setLength(note->endPos() - internalSplitPos); + rightClip->addNote(movedNote, false); + } + } + + for (Note const* note : m_clip->m_notes) + { + if (note->endPos() <= internalSplitPos) + { + leftClip->addNote(*note, false); + } + else if (note->pos() < internalSplitPos) + { + auto movedNote = Note{*note}; + movedNote.setLength(internalSplitPos - note->pos()); + leftClip->addNote(movedNote, false); + } + } + + leftClip->movePosition(m_initialClipPos); + leftClip->setAutoResize(false); + leftClip->changeLength(splitPos - m_initialClipPos); + leftClip->setStartTimeOffset(m_clip->startTimeOffset()); + + rightClip->movePosition(splitPos); + rightClip->setAutoResize(false); + rightClip->changeLength(m_initialClipEnd - splitPos); + rightClip->setStartTimeOffset(0); + + remove(); + m_clip->getTrack()->restoreJournallingState(); + return true; +} + + } // namespace lmms::gui diff --git a/src/gui/clips/PatternClipView.cpp b/src/gui/clips/PatternClipView.cpp index bf12440c7..e30c26c1e 100644 --- a/src/gui/clips/PatternClipView.cpp +++ b/src/gui/clips/PatternClipView.cpp @@ -34,6 +34,8 @@ #include "PatternClip.h" #include "PatternStore.h" #include "RenameDialog.h" +#include "TrackContainerView.h" +#include "TrackView.h" namespace lmms::gui { @@ -70,6 +72,8 @@ void PatternClipView::constructContextMenu(QMenu* _cm) void PatternClipView::mouseDoubleClickEvent(QMouseEvent*) { + if (m_trackView->trackContainerView()->knifeMode()) { return; } + openInPatternEditor(); } @@ -155,6 +159,12 @@ void PatternClipView::paintEvent(QPaintEvent*) embed::getIconPixmap( "muted", size, size ) ); } + if (m_marker) + { + p.setPen(markerColor()); + p.drawLine(m_markerPos, rect().bottom(), m_markerPos, rect().top()); + } + p.end(); painter.drawPixmap( 0, 0, m_paintPixmap ); @@ -195,5 +205,4 @@ void PatternClipView::update() ClipView::update(); } - } // namespace lmms::gui diff --git a/src/gui/clips/SampleClipView.cpp b/src/gui/clips/SampleClipView.cpp index 836a2019a..eee100cf8 100644 --- a/src/gui/clips/SampleClipView.cpp +++ b/src/gui/clips/SampleClipView.cpp @@ -39,6 +39,8 @@ #include "SampleThumbnail.h" #include "Song.h" #include "StringPairDrag.h" +#include "TrackContainerView.h" +#include "TrackView.h" namespace lmms::gui { @@ -47,7 +49,8 @@ namespace lmms::gui SampleClipView::SampleClipView( SampleClip * _clip, TrackView * _tv ) : ClipView( _clip, _tv ), m_clip( _clip ), - m_paintPixmap() + m_paintPixmap(), + m_paintPixmapXPosition(0) { // update UI and tooltip updateSample(); @@ -188,15 +191,13 @@ void SampleClipView::mouseReleaseEvent(QMouseEvent *_me) void SampleClipView::mouseDoubleClickEvent( QMouseEvent * ) { + if (m_trackView->trackContainerView()->knifeMode()) { return; } + const QString selectedAudioFile = SampleLoader::openAudioFile(); if (selectedAudioFile.isEmpty()) { return; } - if (m_clip->hasSampleFileLoaded(selectedAudioFile)) - { - m_clip->changeLengthToSampleLength(); - } - else + if (!m_clip->hasSampleFileLoaded(selectedAudioFile)) { auto sampleBuffer = SampleLoader::createBufferFromFile(selectedAudioFile); if (sampleBuffer != SampleBuffer::emptyBuffer()) @@ -204,6 +205,7 @@ void SampleClipView::mouseDoubleClickEvent( QMouseEvent * ) m_clip->setSampleBuffer(sampleBuffer); } } + m_clip->updateLength(); } @@ -215,15 +217,22 @@ void SampleClipView::paintEvent( QPaintEvent * pe ) if( !needsUpdate() ) { - painter.drawPixmap( 0, 0, m_paintPixmap ); + painter.drawPixmap(m_paintPixmapXPosition, 0, m_paintPixmap); return; } setNeedsUpdate( false ); - if (m_paintPixmap.isNull() || m_paintPixmap.size() != size()) + const auto trackViewWidth = getTrackView()->rect().width(); + + // Use the clip's height to avoid artifacts when rendering while something else is overlaying the clip. + const auto viewPortRect = QRect(0, 0, trackViewWidth * 2, rect().height()); + + m_paintPixmapXPosition = std::max(0, pe->rect().x() - trackViewWidth); + + if (m_paintPixmap.isNull() || m_paintPixmap.size() != viewPortRect.size()) { - m_paintPixmap = QPixmap(size()); + m_paintPixmap = QPixmap(viewPortRect.size()); } QPainter p( &m_paintPixmap ); @@ -279,12 +288,14 @@ void SampleClipView::paintEvent( QPaintEvent * pe ) float sampleLength = m_clip->sampleLength() * ppb / ticksPerBar; const auto& sample = m_clip->m_sample; + + const auto sampleRextX = static_cast(offsetStart) - m_paintPixmapXPosition; + if (sample.sampleSize() > 0) { const auto param = SampleThumbnail::VisualizeParameters{ - .sampleRect = QRect(offsetStart, spacing, sampleLength, height() - spacing), - .drawRect = QRect(0, spacing, width(), height() - spacing), - .viewportRect = pe->rect(), + .sampleRect = QRect(sampleRextX, spacing, sampleLength, height() - spacing), + .viewportRect = viewPortRect, .amplification = sample.amplification(), .reversed = sample.reversed() }; @@ -300,12 +311,15 @@ void SampleClipView::paintEvent( QPaintEvent * pe ) // inner border p.setPen( c.lighter( 135 ) ); - p.drawRect( 1, 1, rect().right() - BORDER_WIDTH, + p.drawRect( + -m_paintPixmapXPosition + 1, + 1, + rect().right() - BORDER_WIDTH, rect().bottom() - BORDER_WIDTH ); // outer border p.setPen( c.darker( 200 ) ); - p.drawRect( 0, 0, rect().right(), rect().bottom() ); + p.drawRect(-m_paintPixmapXPosition, 0, rect().right(), rect().bottom()); // draw the 'muted' pixmap only if the clip was manualy muted if( m_clip->isMuted() ) @@ -318,6 +332,7 @@ void SampleClipView::paintEvent( QPaintEvent * pe ) if ( m_marker ) { + p.setPen(markerColor()); p.drawLine(m_markerPos, rect().bottom(), m_markerPos, rect().top()); } // recording sample tracks is not possible at the moment @@ -353,7 +368,7 @@ void SampleClipView::paintEvent( QPaintEvent * pe ) p.end(); - painter.drawPixmap( 0, 0, m_paintPixmap ); + painter.drawPixmap(m_paintPixmapXPosition, 0, m_paintPixmap); } @@ -377,37 +392,6 @@ void SampleClipView::setAutomationGhost() aEditor->show(); aEditor->setFocus(); } - -//! Split this Clip. -/*! \param pos the position of the split, relative to the start of the clip */ -bool SampleClipView::splitClip( const TimePos pos ) -{ - setMarkerEnabled( false ); - - const TimePos splitPos = m_initialClipPos + pos; - - //Don't split if we slid off the Clip or if we're on the clip's start/end - //Cutting at exactly the start/end position would create a zero length - //clip (bad), and a clip the same length as the original one (pointless). - if ( splitPos > m_initialClipPos && splitPos < m_initialClipEnd ) - { - m_clip->getTrack()->addJournalCheckPoint(); - m_clip->getTrack()->saveJournallingState( false ); - - auto rightClip = new SampleClip(*m_clip); - - m_clip->changeLength( splitPos - m_initialClipPos ); - - rightClip->movePosition( splitPos ); - rightClip->changeLength( m_initialClipEnd - splitPos ); - rightClip->setStartTimeOffset( m_clip->startTimeOffset() - m_clip->length() ); - - m_clip->getTrack()->restoreJournallingState(); - return true; - } - else { return false; } -} - bool SampleClipView::recordingCapabilitiesAvailable() const { return Engine::audioEngine()->captureDeviceAvailable(); diff --git a/src/gui/editors/AutomationEditor.cpp b/src/gui/editors/AutomationEditor.cpp index 78f5d112a..6872f26a4 100644 --- a/src/gui/editors/AutomationEditor.cpp +++ b/src/gui/editors/AutomationEditor.cpp @@ -58,7 +58,6 @@ #include "StringPairDrag.h" #include "TextFloat.h" #include "TimeLineWidget.h" -#include "debug.h" #include "embed.h" #include "FontHelper.h" @@ -103,7 +102,8 @@ AutomationEditor::AutomationEditor() : m_scaleColor(Qt::SolidPattern), m_crossColor(0, 0, 0), m_backgroundShade(0, 0, 0), - m_ghostNoteColor(0, 0, 0) + m_ghostNoteColor(0, 0, 0), + m_outOfBoundsShade(0, 0, 0, 128) { connect( this, SIGNAL(currentClipChanged()), this, SLOT(updateAfterClipChange()), @@ -114,10 +114,13 @@ AutomationEditor::AutomationEditor() : //keeps the direction of the widget, undepended on the locale setLayoutDirection( Qt::LeftToRight ); + // Set up tension model m_tensionModel = new FloatModel(1.f, 0.f, 1.f, 0.01f); + m_tensionModel->setJournalling(false); connect( m_tensionModel, SIGNAL(dataChanged()), this, SLOT(setTension())); + // Set up quantization model for (auto q : Quantizations) { m_quantizeModel.addItem(QString("1/%1").arg(q)); } @@ -183,6 +186,7 @@ void AutomationEditor::setCurrentClip(AutomationClip * new_clip ) if (m_clip != nullptr) { connect(m_clip, SIGNAL(dataChanged()), this, SLOT(update())); + connect(m_clip, &AutomationClip::lengthChanged, this, qOverload<>(&QWidget::update)); } emit currentClipChanged(); @@ -288,6 +292,7 @@ void AutomationEditor::keyPressEvent(QKeyEvent * ke ) break; default: + ke->ignore(); break; } } @@ -1217,6 +1222,7 @@ void AutomationEditor::paintEvent(QPaintEvent * pe ) const auto param = SampleThumbnail::VisualizeParameters{ .sampleRect = QRect(startPos, yOffset, sampleWidth, sampleHeight), + .viewportRect = rect(), .amplification = sample.amplification(), .sampleStart = static_cast(sample.startFrame()) / sample.sampleSize(), .sampleEnd = static_cast(sample.endFrame()) / sample.sampleSize(), @@ -1380,6 +1386,22 @@ void AutomationEditor::paintEvent(QPaintEvent * pe ) drawAutomationTangents(p, it); } } + + // draw clip bounds overlay + p.fillRect( + xCoordOfTick(m_clip->length() - m_clip->startTimeOffset()), + TOP_MARGIN, + width() - 10, + grid_bottom, + m_outOfBoundsShade + ); + p.fillRect( + 0, + TOP_MARGIN, + xCoordOfTick(-m_clip->startTimeOffset()), + grid_bottom, + m_outOfBoundsShade + ); } else { @@ -1396,7 +1418,7 @@ void AutomationEditor::paintEvent(QPaintEvent * pe ) } // TODO: Get this out of paint event - int l = validClip() ? (int) m_clip->length() : 0; + int l = validClip() ? (int) m_clip->length() - m_clip->startTimeOffset() : 0; // reset scroll-range if( m_leftRightScroll->maximum() != l ) diff --git a/src/gui/editors/Editor.cpp b/src/gui/editors/Editor.cpp index c32fb5437..750a28fc2 100644 --- a/src/gui/editors/Editor.cpp +++ b/src/gui/editors/Editor.cpp @@ -118,7 +118,6 @@ Editor::Editor(bool record, bool stepRecord) : connect(m_recordAccompanyAction, SIGNAL(triggered()), this, SLOT(recordAccompany())); connect(m_toggleStepRecordingAction, SIGNAL(triggered()), this, SLOT(toggleStepRecording())); connect(m_stopAction, SIGNAL(triggered()), this, SLOT(stop())); - new QShortcut(Qt::Key_Space, this, SLOT(togglePlayStop())); new QShortcut(QKeySequence(combine(Qt::SHIFT, Qt::Key_Space)), this, SLOT(togglePause())); new QShortcut(QKeySequence(combine(Qt::SHIFT, Qt::Key_F11)), this, SLOT(toggleMaximize())); @@ -155,6 +154,16 @@ void Editor::closeEvent(QCloseEvent * event) event->ignore(); } + void Editor::keyPressEvent(QKeyEvent *ke) + { + if (ke->key() == Qt::Key_Space) + { + togglePlayStop(); + return; + } + ke->ignore(); + } + DropToolBar::DropToolBar(QWidget* parent) : QToolBar(parent) { setAcceptDrops(true); diff --git a/src/gui/editors/PatternEditor.cpp b/src/gui/editors/PatternEditor.cpp index 291578698..42bb47e9c 100644 --- a/src/gui/editors/PatternEditor.cpp +++ b/src/gui/editors/PatternEditor.cpp @@ -49,6 +49,8 @@ PatternEditor::PatternEditor(PatternStore* ps) : m_ps(ps) { setModel(ps); + setFocusPolicy(Qt::StrongFocus); + setFocus(); } diff --git a/src/gui/editors/PianoRoll.cpp b/src/gui/editors/PianoRoll.cpp index f0f54f0ba..3ef056c43 100644 --- a/src/gui/editors/PianoRoll.cpp +++ b/src/gui/editors/PianoRoll.cpp @@ -50,7 +50,6 @@ #include "ComboBox.h" #include "ConfigManager.h" #include "DataFile.h" -#include "debug.h" #include "DeprecationHelper.h" #include "DetuningHelper.h" #include "embed.h" @@ -58,6 +57,7 @@ #include "FontHelper.h" #include "InstrumentTrack.h" #include "KeyboardShortcuts.h" +#include "lmms_math.h" #include "MainWindow.h" #include "MidiClip.h" #include "PatternStore.h" @@ -211,6 +211,7 @@ PianoRoll::PianoRoll() : m_noteBorders( true ), m_ghostNoteBorders( true ), m_backgroundShade( 0, 0, 0 ), + m_outOfBoundsShade(0, 0, 0, 128), m_whiteKeyWidth(WHITE_KEY_WIDTH), m_blackKeyWidth(BLACK_KEY_WIDTH) { @@ -282,7 +283,7 @@ PianoRoll::PianoRoll() : this, SLOT( updatePosition( const lmms::TimePos& ) ) ); // white position line follows timeline marker - m_positionLine = new PositionLine(this); + m_positionLine = new PositionLine(this, Song::PlayMode::MidiClip); //update timeline when in step-recording mode connect( &m_stepRecorderWidget, SIGNAL( positionChanged( const lmms::TimePos& ) ), @@ -450,14 +451,7 @@ void PianoRoll::showTextFloat(const QString &text, const QPoint &pos, int timeou s_textFloat->setText( text ); // show the float, offset slightly so as to not obscure anything s_textFloat->moveGlobal( this, pos + QPoint(4, 16) ); - if (timeout == -1) - { - s_textFloat->show(); - } - else - { - s_textFloat->setVisibilityTimeOut( timeout ); - } + s_textFloat->showWithTimeout(timeout); } @@ -786,6 +780,20 @@ void PianoRoll::constrainNoteLengths(bool constrainMax) Engine::getSong()->setModified(); } +void PianoRoll::reverseNotes() +{ + if (!hasValidMidiClip()) { return; } + + const NoteVector selectedNotes = getSelectedNotes(); + const auto& notes = selectedNotes.empty() ? m_midiClip->notes() : selectedNotes; + + m_midiClip->reverseNotes(notes); + + update(); + getGUI()->songEditor()->update(); + Engine::getSong()->setModified(); +} + void PianoRoll::loadMarkedSemiTones(const QDomElement & de) { @@ -854,7 +862,8 @@ void PianoRoll::setCurrentMidiClip( MidiClip* newMidiClip ) return; } - m_leftRightScroll->setValue( 0 ); + // Scroll horizontally to the start of the clip, minus a bar for aesthetics. + m_leftRightScroll->setValue(std::max(0, -m_midiClip->startTimeOffset() - TimePos::ticksPerBar())); // determine the central key so that we can scroll to it int central_key = 0; @@ -874,6 +883,13 @@ void PianoRoll::setCurrentMidiClip( MidiClip* newMidiClip ) m_startKey = qBound(0, central_key, NumKeys); } + // Make sure the playhead position isn't out of the clip bounds. + Engine::getSong()->getPlayPos(Song::PlayMode::MidiClip).setTicks(std::clamp( + Engine::getSong()->getPlayPos(Song::PlayMode::MidiClip).getTicks(), + std::max(0, -m_midiClip->startTimeOffset()), + m_midiClip->length() - m_midiClip->startTimeOffset() + )); + // resizeEvent() does the rest for us (scrolling, range-checking // of start-notes and so on...) resizeEvent( nullptr ); @@ -891,6 +907,7 @@ void PianoRoll::setCurrentMidiClip( MidiClip* newMidiClip ) connect(m_midiClip->instrumentTrack()->microtuner()->keymapModel(), SIGNAL(dataChanged()), this, SLOT(update())); connect(m_midiClip->instrumentTrack()->microtuner()->keyRangeImportModel(), SIGNAL(dataChanged()), this, SLOT(update())); + connect(m_midiClip, &MidiClip::lengthChanged, this, qOverload<>(&QWidget::update)); update(); emit currentMidiClipChanged(); @@ -1276,6 +1293,7 @@ void PianoRoll::keyPressEvent(QKeyEvent* ke) // if a chord is set, play all chord notes (simulate click on all): playChordNotes(key_num); ke->accept(); + return; } } @@ -1396,7 +1414,7 @@ void PianoRoll::keyPressEvent(QKeyEvent* ke) } case Qt::Key_A: - if( ke->modifiers() & Qt::ControlModifier ) + if (ke->modifiers() & Qt::ControlModifier && m_editMode != EditMode::Strum && m_editMode != EditMode::Knife) { ke->accept(); if (ke->modifiers() & Qt::ShiftModifier) @@ -1414,11 +1432,15 @@ void PianoRoll::keyPressEvent(QKeyEvent* ke) break; case Qt::Key_Escape: - // On the Knife mode, ESC cancels it + // On the Knife mode or Strum mode, ESC cancels it if (m_editMode == EditMode::Knife) { cancelKnifeAction(); } + else if (m_editMode == EditMode::Strum) + { + cancelStrumAction(); + } else { // Same as Ctrl + Shift + A @@ -1487,6 +1509,7 @@ void PianoRoll::keyPressEvent(QKeyEvent* ke) } break; default: + ke->ignore(); break; } @@ -1507,6 +1530,7 @@ void PianoRoll::keyReleaseEvent(QKeyEvent* ke ) // if a chord is set, simulate click release on all chord notes pauseChordNotes(key_num); ke->accept(); + return; } } @@ -1519,6 +1543,7 @@ void PianoRoll::keyReleaseEvent(QKeyEvent* ke ) } computeSelectedNotes( ke->modifiers() & Qt::ShiftModifier); m_editMode = m_ctrlMode; + if (m_editMode == EditMode::Strum) { setupSelectedChords(); } update(); break; @@ -1530,6 +1555,9 @@ void PianoRoll::keyReleaseEvent(QKeyEvent* ke ) update(); } break; + default: + ke->ignore(); + break; } update(); @@ -1614,6 +1642,19 @@ void PianoRoll::mousePressEvent(QMouseEvent * me ) return; } + if (m_editMode == EditMode::Strum && me->button() == Qt::LeftButton) + { + // Only strum if the user is dragging a selected note + const auto& selectedNotes = getSelectedNotes(); + if (std::find(selectedNotes.begin(), selectedNotes.end(), noteUnderMouse()) != selectedNotes.end()) + { + updateStrumPos(me, true, me->modifiers() & Qt::ShiftModifier); + m_strumEnabled = true; + update(); + } + return; + } + if( m_editMode == EditMode::Detuning && noteUnderMouse() ) { static QPointer detuningClip = nullptr; @@ -2141,7 +2182,27 @@ void PianoRoll::cancelKnifeAction() update(); } +void PianoRoll::setStrumAction() +{ + if (m_editMode != EditMode::Strum) + { + m_strumMode = m_editMode; + m_editMode = EditMode::Strum; + m_action = Action::Strum; + m_strumEnabled = false; + setupSelectedChords(); + setCursor(Qt::ArrowCursor); + update(); + } +} +void PianoRoll::cancelStrumAction() +{ + m_editMode = m_strumMode; + m_action = Action::None; + m_strumEnabled = false; + update(); +} void PianoRoll::testPlayKey( int key, int velocity, int pan ) { @@ -2241,11 +2302,15 @@ void PianoRoll::mouseReleaseEvent( QMouseEvent * me ) s_textFloat->hide(); - // Quit knife mode if we pressed and released the right mouse button + // Quit knife mode or strum mode if we pressed and released the right mouse button if (m_editMode == EditMode::Knife && me->button() == Qt::RightButton) { cancelKnifeAction(); } + else if (m_editMode == EditMode::Strum && me->button() == Qt::RightButton) + { + cancelStrumAction(); + } if( me->button() & Qt::LeftButton ) { @@ -2266,6 +2331,10 @@ void PianoRoll::mouseReleaseEvent( QMouseEvent * me ) m_midiClip->rearrangeAllNotes(); } + else if (m_action == Action::Strum || m_strumEnabled) + { + m_strumEnabled = false; + } else if (m_action == Action::Knife && hasValidMidiClip()) { bool deleteShortEnds = me->modifiers() & Qt::ShiftModifier; @@ -2313,7 +2382,7 @@ void PianoRoll::mouseReleaseEvent( QMouseEvent * me ) m_currentNote = nullptr; - if (m_action != Action::Knife) + if (m_action != Action::Knife && m_action != Action::Strum) { m_action = Action::None; } @@ -2379,6 +2448,12 @@ void PianoRoll::mouseMoveEvent( QMouseEvent * me ) updateKnifePos(me, false); } + // Update Strum position if we are on knife mode + if (m_editMode == EditMode::Strum && m_strumEnabled) + { + updateStrumPos(me, false, me->modifiers() & Qt::ShiftModifier); + } + if( me->y() > PR_TOP_MARGIN || m_action != Action::None ) { bool edit_note = ( me->y() > noteEditTop() ) @@ -2661,7 +2736,7 @@ void PianoRoll::mouseMoveEvent( QMouseEvent * me ) } } } - else if (me->buttons() == Qt::NoButton && m_editMode != EditMode::Draw && m_editMode != EditMode::Knife) + else if (me->buttons() == Qt::NoButton && m_editMode != EditMode::Draw && m_editMode != EditMode::Knife && m_editMode != EditMode::Strum) { // Is needed to restore cursor when it previously was set to // Qt::SizeVerCursor (between keyAreaBottom and noteEditTop) @@ -2780,6 +2855,120 @@ void PianoRoll::updateKnifePos(QMouseEvent* me, bool initial) m_knifeEndKey = mouseKey; } +/* + * Setup chords + * + * A chord is an island of notes--as the loop goes over the notes, if the notes overlap, + * they are part of the same chord. Else, they are part of a new chord. +*/ +void PianoRoll::setupSelectedChords() +{ + if (!hasValidMidiClip()) { return; } + m_selectedChords.clear(); + m_midiClip->rearrangeAllNotes(); + + const NoteVector& selectedNotes = getSelectedNotes(); + if (selectedNotes.empty()) { return; } + + int maxTime = -1; + NoteVector currentChord; + for (Note* note: selectedNotes) + { + // If the note is not in the current chord range (and this isn't the first chord), start a new chord. + if (note->pos() >= maxTime && maxTime != -1) + { + // Sort the notes by key before adding the chord to the vector + std::sort(currentChord.begin(), currentChord.end(), [](Note* a, Note* b){ return a->key() < b->key(); }); + m_selectedChords.push_back(currentChord); + currentChord.clear(); + maxTime = note->endPos(); + } + maxTime = std::max(maxTime, static_cast(note->endPos())); + currentChord.push_back(note); + } + // Add final chord + std::sort(currentChord.begin(), currentChord.end(), [](Note* a, Note* b){ return a->key() < b->key(); }); + m_selectedChords.push_back(currentChord); +} + +/* + * Perform the Strum + * + * Notes above the clicked note (relative to each chord) will be strummed down, notes below will be strummed up. + * Holding shift raises the amount of movement to a power, causing the strum to be curved/warped. +*/ +void PianoRoll::updateStrumPos(QMouseEvent* me, bool initial, bool warp) +{ + if (!hasValidMidiClip()) { return; } + // Calculate the TimePos from the mouse + int mouseViewportPos = me->x() - m_whiteKeyWidth; + int mouseTickPos = mouseViewportPos * TimePos::ticksPerBar() / m_ppb + m_currentPosition; + // Should we add quantization? probably not? + if (initial) + { + m_strumStartTime = mouseTickPos; + m_strumStartVertical = me->y(); + } + m_strumCurrentTime = mouseTickPos; + m_strumCurrentVertical = me->y(); + int strumTicksHorizontal = m_strumCurrentTime - m_strumStartTime; + float strumPower = fastPow10f(0.01f * (m_strumCurrentVertical - m_strumStartVertical)); + + if (initial) + { + m_midiClip->addJournalCheckPoint(); + + Note* clickedNote = noteUnderMouse(); + if (clickedNote == nullptr) { return; } + + for (NoteVector chord: m_selectedChords) + { + for (Note* note: chord) + { + // Save the current note position + note->setOldPos(note->pos()); + // if this is the clicked note, calculate it's ratio up the chord + if (note == clickedNote && chord.size() > 1) + { + m_strumHeightRatio = 1.f * std::distance(chord.begin(), std::find(chord.begin(), chord.end(), clickedNote)) / (chord.size() - 1); + } + } + } + } + + for (NoteVector chord: m_selectedChords) + { + // Don't strum a chord with only one note + if (chord.size() <= 1) { continue; } + for (size_t i = 0; i < chord.size(); ++i) + { + float heightRatio = 1.f * i / (chord.size() - 1); + float ratio = 0.0f; + + if (heightRatio == m_strumHeightRatio) + { + ratio = 1.f; + } + else if (heightRatio < m_strumHeightRatio) + { + ratio = heightRatio / m_strumHeightRatio; + } + else + { + ratio = (1.f - heightRatio) / (1.f - m_strumHeightRatio); + } + + if (warp) + { + ratio = std::pow(ratio, strumPower); + } + chord.at(i)->setPos(std::max(0, static_cast(chord.at(i)->oldPos() + ratio * strumTicksHorizontal))); + } + } + m_midiClip->rearrangeAllNotes(); + m_midiClip->updateLength(); + m_midiClip->dataChanged(); +} @@ -3028,6 +3217,12 @@ void PianoRoll::paintEvent(QPaintEvent * pe ) // G-1 is one of the widest; plus one pixel margin for the shadow QRect const boundingRect = fontMetrics.boundingRect(QString("G-1")) + QMargins(0, 0, 1, 0); + auto xCoordOfTick = [this](int tick) { + return m_whiteKeyWidth + ( + (tick - m_currentPosition) * m_ppb / TimePos::ticksPerBar() + ); + }; + // Order of drawing // - vertical quantization lines // - piano roll + horizontal key lines @@ -3102,11 +3297,7 @@ void PianoRoll::paintEvent(QPaintEvent * pe ) // allow quantization grid up to 1/32 for normal notes else if (q < 6) { q = 6; } } - auto xCoordOfTick = [this](int tick) { - return m_whiteKeyWidth + ( - (tick - m_currentPosition) * m_ppb / TimePos::ticksPerBar() - ); - }; + p.setPen(m_lineColor); for (tick = m_currentPosition - m_currentPosition % q, x = xCoordOfTick(tick); @@ -3536,13 +3727,25 @@ void PianoRoll::paintEvent(QPaintEvent * pe ) } } + // draw clip bounds + p.fillRect( + xCoordOfTick(m_midiClip->length() - m_midiClip->startTimeOffset()), + PR_TOP_MARGIN, + width() - 10, + noteEditBottom(), + m_outOfBoundsShade + ); + p.fillRect( + 0, + PR_TOP_MARGIN, + xCoordOfTick(-m_midiClip->startTimeOffset()), + noteEditBottom(), + m_outOfBoundsShade + ); + // -- Knife tool (draw cut line) if (m_action == Action::Knife && m_knifeDown) { - auto xCoordOfTick = [this](int tick) { - return m_whiteKeyWidth + ( - (tick - m_currentPosition) * m_ppb / TimePos::ticksPerBar()); - }; int x1 = xCoordOfTick(m_knifeStartTickPos); int y1 = y_base - (m_knifeStartKey - m_startKey + 1) * m_keyLineHeight; int x2 = xCoordOfTick(m_knifeEndTickPos); @@ -3621,7 +3824,7 @@ void PianoRoll::paintEvent(QPaintEvent * pe ) p.drawRect(x + m_whiteKeyWidth, y, w, h); // TODO: Get this out of paint event - int l = ( hasValidMidiClip() )? (int) m_midiClip->length() : 0; + int l = ( hasValidMidiClip() )? (int) m_midiClip->length() - m_midiClip->startTimeOffset() : 0; // reset scroll-range if( m_leftRightScroll->maximum() != l ) @@ -3682,6 +3885,9 @@ void PianoRoll::paintEvent(QPaintEvent * pe ) case EditMode::Knife: cursor = &m_toolKnife; break; + case EditMode::Strum: + cursor = &m_toolStrum; + break; } QPoint mousePosition = mapFromGlobal( QCursor::pos() ); if( cursor != nullptr && mousePosition.y() > keyAreaTop() && mousePosition.x() > noteEditLeft()) @@ -3896,7 +4102,14 @@ void PianoRoll::focusOutEvent( QFocusEvent * ) if (m_editMode == EditMode::Knife) { m_editMode = m_knifeMode; m_action = Action::None; - } else { + } + else if (m_editMode == EditMode::Strum) + { + m_editMode = m_strumMode; + m_action = Action::None; + } + else + { m_editMode = m_ctrlMode; } update(); @@ -4841,6 +5054,10 @@ PianoRollWindow::PianoRollWindow() : connect(knifeAction, &QAction::triggered, m_editor, &PianoRoll::setKnifeAction); knifeAction->setShortcut(combine(Qt::SHIFT, Qt::Key_K)); + auto strumAction = new QAction(embed::getIconPixmap("arp_free"), tr("Strum"), noteToolsButton); + connect(strumAction, &QAction::triggered, m_editor, &PianoRoll::setStrumAction); + strumAction->setShortcut(combine(Qt::SHIFT, Qt::Key_J)); + auto fillAction = new QAction(embed::getIconPixmap("fill"), tr("Fill"), noteToolsButton); connect(fillAction, &QAction::triggered, [this](){ m_editor->fitNoteLengths(true); }); fillAction->setShortcut(combine(Qt::SHIFT, Qt::Key_F)); @@ -4855,12 +5072,18 @@ PianoRollWindow::PianoRollWindow() : auto maxLengthAction = new QAction(embed::getIconPixmap("max_length"), tr("Max length as last"), noteToolsButton); connect(maxLengthAction, &QAction::triggered, [this](){ m_editor->constrainNoteLengths(true); }); + auto reverseAction = new QAction(embed::getIconPixmap("flip_x"), tr("Reverse Notes"), noteToolsButton); + connect(reverseAction, &QAction::triggered, [this](){ m_editor->reverseNotes(); }); + reverseAction->setShortcut(combine(Qt::SHIFT, Qt::Key_R)); + noteToolsButton->addAction(glueAction); noteToolsButton->addAction(knifeAction); + noteToolsButton->addAction(strumAction); noteToolsButton->addAction(fillAction); noteToolsButton->addAction(cutOverlapsAction); noteToolsButton->addAction(minLengthAction); noteToolsButton->addAction(maxLengthAction); + noteToolsButton->addAction(reverseAction); notesActionsToolBar->addWidget(noteToolsButton); diff --git a/src/gui/editors/PositionLine.cpp b/src/gui/editors/PositionLine.cpp index dda44e8f7..1b43d6383 100644 --- a/src/gui/editors/PositionLine.cpp +++ b/src/gui/editors/PositionLine.cpp @@ -26,14 +26,13 @@ #include -#include "Song.h" - namespace lmms::gui { -PositionLine::PositionLine(QWidget* parent) : +PositionLine::PositionLine(QWidget* parent, Song::PlayMode playMode) : QWidget(parent), + m_playMode(playMode), m_hasTailGradient(false), m_lineColor(0, 0, 0, 0) { @@ -64,8 +63,7 @@ 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::PlayMode::Song || - Engine::getSong()->playMode() == Song::PlayMode::MidiClip)) + (Engine::getSong()->playMode() == m_playMode)) { c.setAlpha(60); gradient.setColorAt(w, c); diff --git a/src/gui/editors/SongEditor.cpp b/src/gui/editors/SongEditor.cpp index fc6fc1463..472401562 100644 --- a/src/gui/editors/SongEditor.cpp +++ b/src/gui/editors/SongEditor.cpp @@ -93,9 +93,7 @@ SongEditor::SongEditor( Song * song ) : : DEFAULT_SETTINGS_WIDGET_WIDTH + TRACK_OP_WIDTH), m_selectRegion(false) { - m_zoomingModel->setParent(this); - m_snappingModel->setParent(this); - + // Set up timeline m_timeLine = new TimeLineWidget(m_trackHeadWidth, 32, pixelsPerBar(), m_song->getPlayPos(Song::PlayMode::Song), m_song->getTimeline(Song::PlayMode::Song), @@ -112,7 +110,7 @@ SongEditor::SongEditor( Song * song ) : // when tracks realign, adjust height of position line connect(this, &TrackContainerView::tracksRealigned, this, &SongEditor::updatePositionLine); - m_positionLine = new PositionLine(this); + m_positionLine = new PositionLine(this, Song::PlayMode::Song); static_cast( layout() )->insertWidget( 1, m_timeLine ); connect( m_song, SIGNAL(playbackStateChanged()), @@ -243,10 +241,15 @@ SongEditor::SongEditor( Song * song ) : connect(contentWidget()->verticalScrollBar(), SIGNAL(valueChanged(int)),this, SLOT(updateRubberband())); connect(m_timeLine, SIGNAL(selectionFinished()), this, SLOT(stopSelectRegion())); - //zoom connects + + // Set up zooming model + m_zoomingModel->setParent(this); + m_zoomingModel->setJournalling(false); connect(m_zoomingModel, SIGNAL(dataChanged()), this, SLOT(zoomingChanged())); + // Set up snapping model + m_snappingModel->setParent(this); for (float bars : SNAP_SIZES) { if (bars > 1.0f) @@ -958,7 +961,7 @@ SongEditorWindow::SongEditorWindow(Song* song) : m_editModeGroup = new ActionGroup(this); m_drawModeAction = m_editModeGroup->addAction(embed::getIconPixmap("edit_draw"), tr("Draw mode")); - m_knifeModeAction = m_editModeGroup->addAction(embed::getIconPixmap("edit_knife"), tr("Knife mode (split sample clips)")); + m_knifeModeAction = m_editModeGroup->addAction(embed::getIconPixmap("edit_knife"), tr("Knife mode (split clips)")); m_selectModeAction = m_editModeGroup->addAction(embed::getIconPixmap("edit_select"), tr("Edit mode (select and move)")); m_drawModeAction->setChecked(true); diff --git a/src/gui/instrument/EnvelopeAndLfoView.cpp b/src/gui/instrument/EnvelopeAndLfoView.cpp index 90e2aa30e..02dd014f4 100644 --- a/src/gui/instrument/EnvelopeAndLfoView.cpp +++ b/src/gui/instrument/EnvelopeAndLfoView.cpp @@ -57,8 +57,7 @@ EnvelopeAndLfoView::EnvelopeAndLfoView(QWidget * parent) : // Helper lambdas for consistent repeated buiding of certain widgets auto buildKnob = [&](const QString& label, const QString& hintText) { - auto knob = new Knob(KnobType::Bright26, this); - knob->setLabel(label); + auto knob = new Knob(KnobType::Bright26, label, this, Knob::LabelRendering::LegacyFixedFontSize); knob->setHintText(hintText, ""); return knob; @@ -170,8 +169,7 @@ EnvelopeAndLfoView::EnvelopeAndLfoView(QWidget * parent) : 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 = new TempoSyncKnob(KnobType::Bright26, tr("SPD"), this, Knob::LabelRendering::LegacyFixedFontSize); m_lfoSpeedKnob->setHintText(tr("Frequency:"), ""); lfoKnobsLayout->addWidget(m_lfoSpeedKnob); diff --git a/src/gui/instrument/EnvelopeGraph.cpp b/src/gui/instrument/EnvelopeGraph.cpp index 3483f46a0..c9d60646e 100644 --- a/src/gui/instrument/EnvelopeGraph.cpp +++ b/src/gui/instrument/EnvelopeGraph.cpp @@ -30,7 +30,6 @@ #include #include "EnvelopeAndLfoParameters.h" -#include "lmms_math.h" #include "ColorHelper.h" #include diff --git a/src/gui/instrument/InstrumentFunctionViews.cpp b/src/gui/instrument/InstrumentFunctionViews.cpp index a60fa64f9..7c0a62490 100644 --- a/src/gui/instrument/InstrumentFunctionViews.cpp +++ b/src/gui/instrument/InstrumentFunctionViews.cpp @@ -44,7 +44,7 @@ InstrumentFunctionNoteStackingView::InstrumentFunctionNoteStackingView( Instrume m_cc( cc ), m_chordsGroupBox( new GroupBox( tr( "STACKING" ) ) ), m_chordsComboBox( new ComboBox() ), - m_chordRangeKnob( new Knob( KnobType::Bright26 ) ) + m_chordRangeKnob(new Knob(KnobType::Bright26, tr("RANGE"), this, Knob::LabelRendering::LegacyFixedFontSize)) { auto topLayout = new QHBoxLayout(this); topLayout->setContentsMargins(0, 0, 0, 0); @@ -59,7 +59,6 @@ InstrumentFunctionNoteStackingView::InstrumentFunctionNoteStackingView( Instrume auto chordLabel = new QLabel(tr("Chord:")); chordLabel->setFont(adjustedToPixelSize(chordLabel->font(), DEFAULT_FONT_SIZE)); - m_chordRangeKnob->setLabel( tr( "RANGE" ) ); m_chordRangeKnob->setHintText( tr( "Chord range:" ), " " + tr( "octave(s)" ) ); mainLayout->addWidget( chordLabel, 0, 0 ); @@ -98,13 +97,13 @@ InstrumentFunctionArpeggioView::InstrumentFunctionArpeggioView( InstrumentFuncti m_a( arp ), m_arpGroupBox( new GroupBox( tr( "ARPEGGIO" ) ) ), m_arpComboBox( new ComboBox() ), - 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_arpRangeKnob(new Knob(KnobType::Bright26, tr("RANGE"), this, Knob::LabelRendering::LegacyFixedFontSize)), + m_arpRepeatsKnob(new Knob(KnobType::Bright26, tr("REP"), this, Knob::LabelRendering::LegacyFixedFontSize)), + m_arpCycleKnob(new Knob(KnobType::Bright26, tr("CYCLE"), this, Knob::LabelRendering::LegacyFixedFontSize)), + m_arpSkipKnob(new Knob(KnobType::Bright26, tr("SKIP"), this, Knob::LabelRendering::LegacyFixedFontSize)), + m_arpMissKnob(new Knob(KnobType::Bright26, tr("MISS"), this, Knob::LabelRendering::LegacyFixedFontSize)), + m_arpTimeKnob(new TempoSyncKnob(KnobType::Bright26, tr("TIME"), this, Knob::LabelRendering::LegacyFixedFontSize)), + m_arpGateKnob(new Knob(KnobType::Bright26, tr("GATE"), this, Knob::LabelRendering::LegacyFixedFontSize)), m_arpDirectionComboBox( new ComboBox() ), m_arpModeComboBox( new ComboBox() ) { @@ -118,31 +117,12 @@ InstrumentFunctionArpeggioView::InstrumentFunctionArpeggioView( InstrumentFuncti mainLayout->setHorizontalSpacing( 20 ); mainLayout->setVerticalSpacing( 1 ); - m_arpRangeKnob->setLabel( tr( "RANGE" ) ); m_arpRangeKnob->setHintText( tr( "Arpeggio range:" ), " " + tr( "octave(s)" ) ); - - - m_arpRepeatsKnob->setLabel( tr( "REP" ) ); m_arpRepeatsKnob->setHintText( tr( "Note repeats:" ) + " ", " " + tr( "time(s)" ) ); - - - m_arpCycleKnob->setLabel( tr( "CYCLE" ) ); m_arpCycleKnob->setHintText( tr( "Cycle notes:" ) + " ", " " + tr( "note(s)" ) ); - - - m_arpSkipKnob->setLabel( tr( "SKIP" ) ); m_arpSkipKnob->setHintText( tr( "Skip rate:" ), tr( "%" ) ); - - - m_arpMissKnob->setLabel( tr( "MISS" ) ); m_arpMissKnob->setHintText( tr( "Miss rate:" ), tr( "%" ) ); - - - m_arpTimeKnob->setLabel( tr( "TIME" ) ); m_arpTimeKnob->setHintText( tr( "Arpeggio time:" ), " " + tr( "ms" ) ); - - - m_arpGateKnob->setLabel( tr( "GATE" ) ); m_arpGateKnob->setHintText( tr( "Arpeggio gate:" ), tr( "%" ) ); auto arpChordLabel = new QLabel(tr("Chord:")); diff --git a/src/gui/instrument/InstrumentSoundShapingView.cpp b/src/gui/instrument/InstrumentSoundShapingView.cpp index e0d6a6e98..7f786867d 100644 --- a/src/gui/instrument/InstrumentSoundShapingView.cpp +++ b/src/gui/instrument/InstrumentSoundShapingView.cpp @@ -68,13 +68,11 @@ InstrumentSoundShapingView::InstrumentSoundShapingView(QWidget* parent) : 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 = new Knob(KnobType::Bright26, tr("FREQ"), m_filterGroupBox, Knob::LabelRendering::LegacyFixedFontSize); 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 = new Knob(KnobType::Bright26, tr("Q/RESO"), m_filterGroupBox, Knob::LabelRendering::LegacyFixedFontSize); m_filterResKnob->setHintText(tr("Q/Resonance:"), ""); filterLayout->addWidget(m_filterResKnob); diff --git a/src/gui/instrument/InstrumentTrackWindow.cpp b/src/gui/instrument/InstrumentTrackWindow.cpp index d6c32a205..f6a0d6955 100644 --- a/src/gui/instrument/InstrumentTrackWindow.cpp +++ b/src/gui/instrument/InstrumentTrackWindow.cpp @@ -144,19 +144,17 @@ InstrumentTrackWindow::InstrumentTrackWindow( InstrumentTrackView * _itv ) : soloMuteLayout->setContentsMargins(0, 0, 2, 0); soloMuteLayout->setSpacing(2); - m_muteBtn = new PixmapButton(this, tr("Mute")); + m_muteBtn = new AutomatableButton(this, tr("Mute")); m_muteBtn->setModel(&m_track->m_mutedModel); - m_muteBtn->setActiveGraphic(embed::getIconPixmap("mute_active")); - m_muteBtn->setInactiveGraphic(embed::getIconPixmap("mute_inactive")); m_muteBtn->setCheckable(true); + m_muteBtn->setObjectName("btn-mute"); m_muteBtn->setToolTip(tr("Mute this instrument")); soloMuteLayout->addWidget(m_muteBtn, 0, widgetAlignment); - m_soloBtn = new PixmapButton(this, tr("Solo")); + m_soloBtn = new AutomatableButton(this, tr("Solo")); m_soloBtn->setModel(&m_track->m_soloModel); - m_soloBtn->setActiveGraphic(embed::getIconPixmap("solo_active")); - m_soloBtn->setInactiveGraphic(embed::getIconPixmap("solo_inactive")); m_soloBtn->setCheckable(true); + m_soloBtn->setObjectName("btn-solo"); m_soloBtn->setToolTip(tr("Solo this instrument")); soloMuteLayout->addWidget(m_soloBtn, 0, widgetAlignment); @@ -541,8 +539,8 @@ void InstrumentTrackWindow::closeEvent( QCloseEvent* event ) hide(); } - m_itv->m_tlb->setFocus(); - m_itv->m_tlb->setChecked( false ); + m_itv->setFocus(); + m_itv->m_tlb->setChecked(false); } diff --git a/src/gui/modals/ExportProjectDialog.cpp b/src/gui/modals/ExportProjectDialog.cpp index 016ed447b..01d7c23b0 100644 --- a/src/gui/modals/ExportProjectDialog.cpp +++ b/src/gui/modals/ExportProjectDialog.cpp @@ -98,6 +98,14 @@ ExportProjectDialog::ExportProjectDialog( const QString & _file_name, compressionWidget->setVisible(false); #endif + for (const auto sampleRate : SUPPORTED_SAMPLERATES) + { + samplerateCB->addItem(tr("%1 Hz").arg(sampleRate), sampleRate); + } + + const auto currentIndex = std::max(0, samplerateCB->findData(Engine::audioEngine()->outputSampleRate())); + samplerateCB->setCurrentIndex(currentIndex); + connect( startButton, SIGNAL(clicked()), this, SLOT(startBtnClicked())); } @@ -156,12 +164,9 @@ void ExportProjectDialog::startExport() { auto qs = AudioEngine::qualitySettings( static_cast(interpolationCB->currentIndex())); - const auto samplerates = std::array{44100, 48000, 88200, 96000, 192000}; const auto bitrates = std::array{64, 128, 160, 192, 256, 320}; - const auto bitrate = bitrates[bitrateCB->currentIndex()]; - - OutputSettings os = OutputSettings(samplerates[samplerateCB->currentIndex()], bitrate, + OutputSettings os = OutputSettings(samplerateCB->currentData().toInt(), bitrates[bitrateCB->currentIndex()], static_cast(depthCB->currentIndex()), mapToStereoMode(stereoModeComboBox->currentIndex())); diff --git a/src/gui/modals/SetupDialog.cpp b/src/gui/modals/SetupDialog.cpp index 06f228ab7..4ea0d4ddd 100644 --- a/src/gui/modals/SetupDialog.cpp +++ b/src/gui/modals/SetupDialog.cpp @@ -33,7 +33,6 @@ #include #include "AudioEngine.h" -#include "debug.h" #include "embed.h" #include "Engine.h" #include "FileDialog.h" @@ -144,6 +143,8 @@ SetupDialog::SetupDialog(ConfigTab tab_to_open) : "app", "nanhandler", "1").toInt()), m_bufferSize(ConfigManager::inst()->value( "audioengine", "framesperaudiobuffer").toInt()), + m_sampleRate(ConfigManager::inst()->value( + "audioengine", "samplerate").toInt()), m_midiAutoQuantize(ConfigManager::inst()->value( "midi", "autoquantize", "0").toInt() != 0), m_workingDir(QDir::toNativeSeparators(ConfigManager::inst()->workingDir())), @@ -558,6 +559,44 @@ SetupDialog::SetupDialog(ConfigTab tab_to_open) : // audio_layout->addWidget(useNaNHandler); // useNaNHandler->setChecked(m_NaNHandler); + auto sampleRateBox = new QGroupBox{tr("Sample rate"), audio_w}; + + m_sampleRateSlider = new QSlider{Qt::Horizontal}; + m_sampleRateSlider->setRange(0, SUPPORTED_SAMPLERATES.size() - 1); + m_sampleRateSlider->setTickPosition(QSlider::TicksBelow); + + auto sampleRateResetButton = new QPushButton{embed::getIconPixmap("reload"), ""}; + sampleRateResetButton->setFixedSize(32, 32); + + auto sampleRateSubLayout = new QHBoxLayout{}; + sampleRateSubLayout->addWidget(m_sampleRateSlider); + sampleRateSubLayout->addWidget(sampleRateResetButton); + + auto sampleRateLabel = new QLabel{sampleRateBox}; + auto sampleRateLayout = new QVBoxLayout{sampleRateBox}; + sampleRateLayout->addLayout(sampleRateSubLayout); + sampleRateLayout->addWidget(sampleRateLabel); + + auto setSampleRate = [this, sampleRateLabel](int sampleRate) + { + const auto it = std::find(SUPPORTED_SAMPLERATES.begin(), SUPPORTED_SAMPLERATES.end(), sampleRate); + const auto index = it == SUPPORTED_SAMPLERATES.end() ? 0 : std::distance(SUPPORTED_SAMPLERATES.begin(), it); + + m_sampleRate = SUPPORTED_SAMPLERATES[index]; + m_sampleRateSlider->setValue(index); + sampleRateLabel->setText(tr("Sample rate: %1").arg(m_sampleRate)); + }; + + setSampleRate(m_sampleRate); + + connect(m_sampleRateSlider, &QSlider::valueChanged, this, &SetupDialog::showRestartWarning); + + connect(m_sampleRateSlider, &QSlider::valueChanged, this, + [setSampleRate](int value) { setSampleRate(SUPPORTED_SAMPLERATES[value]); }); + + connect(sampleRateResetButton, &QPushButton::clicked, this, + [setSampleRate] { setSampleRate(SUPPORTED_SAMPLERATES.front()); }); + // Buffer size group QGroupBox * bufferSizeBox = new QGroupBox(tr("Buffer size"), audio_w); QVBoxLayout * bufferSizeLayout = new QVBoxLayout(bufferSizeBox); @@ -599,6 +638,7 @@ SetupDialog::SetupDialog(ConfigTab tab_to_open) : // Audio layout ordering. audio_layout->addWidget(audioInterfaceBox); audio_layout->addWidget(as_w); + audio_layout->addWidget(sampleRateBox); audio_layout->addWidget(bufferSizeBox); audio_layout->addStretch(); @@ -963,6 +1003,8 @@ void SetupDialog::accept() m_audioIfaceNames[m_audioInterfaces->currentText()]); ConfigManager::inst()->setValue("app", "nanhandler", QString::number(m_NaNHandler)); + ConfigManager::inst()->setValue("audioengine", "samplerate", + QString::number(m_sampleRate)); ConfigManager::inst()->setValue("audioengine", "framesperaudiobuffer", QString::number(m_bufferSize)); ConfigManager::inst()->setValue("audioengine", "mididev", diff --git a/src/gui/modals/export_project.ui b/src/gui/modals/export_project.ui index b047bec08..2a923021f 100644 --- a/src/gui/modals/export_project.ui +++ b/src/gui/modals/export_project.ui @@ -122,33 +122,7 @@ - - - - 44100 Hz - - - - - 48000 Hz - - - - - 88200 Hz - - - - - 96000 Hz - - - - - 192000 Hz - - - + diff --git a/src/gui/tracks/InstrumentTrackView.cpp b/src/gui/tracks/InstrumentTrackView.cpp index b8ca43bcd..b6681beeb 100644 --- a/src/gui/tracks/InstrumentTrackView.cpp +++ b/src/gui/tracks/InstrumentTrackView.cpp @@ -35,6 +35,7 @@ #include "ConfigManager.h" #include "Engine.h" #include "FadeButton.h" +#include "FontHelper.h" #include "Knob.h" #include "MidiCCRackView.h" #include "Mixer.h" @@ -77,19 +78,15 @@ InstrumentTrackView::InstrumentTrackView( InstrumentTrack * _it, TrackContainerV m_mixerChannelNumber = new MixerChannelLcdSpinBox(2, getTrackSettingsWidget(), tr("Mixer channel"), this); m_mixerChannelNumber->show(); - m_volumeKnob = new Knob( KnobType::Small17, getTrackSettingsWidget(), - tr( "Volume" ) ); + m_volumeKnob = new Knob(KnobType::Small17, tr("VOL"), getTrackSettingsWidget(), Knob::LabelRendering::LegacyFixedFontSize, tr("VOL")); m_volumeKnob->setVolumeKnob( true ); m_volumeKnob->setModel( &_it->m_volumeModel ); m_volumeKnob->setHintText( tr( "Volume:" ), "%" ); - m_volumeKnob->setLabel( tr( "VOL" ) ); m_volumeKnob->show(); - m_panningKnob = new Knob( KnobType::Small17, getTrackSettingsWidget(), - tr( "Panning" ) ); + m_panningKnob = new Knob(KnobType::Small17, tr("PAN"), getTrackSettingsWidget(), Knob::LabelRendering::LegacyFixedFontSize, tr("Panning")); m_panningKnob->setModel( &_it->m_panningModel ); m_panningKnob->setHintText(tr("Panning:"), "%"); - m_panningKnob->setLabel( tr( "PAN" ) ); m_panningKnob->show(); m_midiMenu = new QMenu( tr( "MIDI" ), this ); diff --git a/src/gui/tracks/SampleTrackView.cpp b/src/gui/tracks/SampleTrackView.cpp index 064cc5206..34495e735 100644 --- a/src/gui/tracks/SampleTrackView.cpp +++ b/src/gui/tracks/SampleTrackView.cpp @@ -31,12 +31,14 @@ #include "embed.h" #include "Engine.h" #include "FadeButton.h" +#include "FontHelper.h" #include "Mixer.h" #include "MixerView.h" #include "GuiApplication.h" #include "Knob.h" #include "SampleClip.h" #include "SampleTrackWindow.h" +#include "SongEditor.h" #include "StringPairDrag.h" #include "TrackContainerView.h" #include "TrackLabelButton.h" @@ -61,20 +63,15 @@ SampleTrackView::SampleTrackView( SampleTrack * _t, TrackContainerView* tcv ) : 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 = new Knob(KnobType::Small17, tr("VOL"), getTrackSettingsWidget(), Knob::LabelRendering::LegacyFixedFontSize, tr("Track volume")); m_volumeKnob->setVolumeKnob( true ); m_volumeKnob->setModel( &_t->m_volumeModel ); m_volumeKnob->setHintText( tr( "Channel volume:" ), "%" ); - - m_volumeKnob->setLabel( tr( "VOL" ) ); m_volumeKnob->show(); - m_panningKnob = new Knob( KnobType::Small17, getTrackSettingsWidget(), - tr( "Panning" ) ); + m_panningKnob = new Knob(KnobType::Small17, tr("PAN"), getTrackSettingsWidget(), Knob::LabelRendering::LegacyFixedFontSize, tr("Panning")); m_panningKnob->setModel( &_t->m_panningModel ); m_panningKnob->setHintText( tr( "Panning:" ), "%" ); - m_panningKnob->setLabel( tr( "PAN" ) ); m_panningKnob->show(); m_activityIndicator = new FadeButton( @@ -211,11 +208,12 @@ void SampleTrackView::dropEvent(QDropEvent *de) ? trackHeadWidth : de->pos().x(); + const float snapSize = getGUI()->songEditor()->m_editor->getSnapSize(); TimePos clipPos = trackContainerView()->fixedClips() ? TimePos(0) : TimePos(((xPos - trackHeadWidth) / trackContainerView()->pixelsPerBar() * TimePos::ticksPerBar()) + trackContainerView()->currentPosition() - ).quantize(1.0); + ).quantize(snapSize, true); auto sClip = static_cast(getTrack()->createClip(clipPos)); if (sClip) { sClip->setSampleFile(value); } diff --git a/src/gui/tracks/TrackContentWidget.cpp b/src/gui/tracks/TrackContentWidget.cpp index 1d39f97b3..397d9feee 100644 --- a/src/gui/tracks/TrackContentWidget.cpp +++ b/src/gui/tracks/TrackContentWidget.cpp @@ -598,8 +598,8 @@ void TrackContentWidget::mousePressEvent( QMouseEvent * me ) so.at( i )->setSelected( false); } getTrack()->addJournalCheckPoint(); - const TimePos pos = getPosition( me->x() ).getBar() * - TimePos::ticksPerBar(); + const float snapSize = getGUI()->songEditor()->m_editor->getSnapSize(); + const TimePos pos = TimePos(getPosition(me->x())).quantize(snapSize, true); getTrack()->createClip(pos); } } diff --git a/src/gui/tracks/TrackLabelButton.cpp b/src/gui/tracks/TrackLabelButton.cpp index c203fad8c..12b4dc4b4 100644 --- a/src/gui/tracks/TrackLabelButton.cpp +++ b/src/gui/tracks/TrackLabelButton.cpp @@ -47,6 +47,7 @@ TrackLabelButton::TrackLabelButton( TrackView * _tv, QWidget * _parent ) : m_iconName() { setAcceptDrops( true ); + setFocusPolicy(Qt::NoFocus); setCursor( QCursor( embed::getIconPixmap( "hand" ), 3, 3 ) ); setToolButtonStyle( Qt::ToolButtonTextBesideIcon ); diff --git a/src/gui/tracks/TrackOperationsWidget.cpp b/src/gui/tracks/TrackOperationsWidget.cpp index c43cd022c..238ef8955 100644 --- a/src/gui/tracks/TrackOperationsWidget.cpp +++ b/src/gui/tracks/TrackOperationsWidget.cpp @@ -83,7 +83,7 @@ TrackOperationsWidget::TrackOperationsWidget( TrackView * parent ) : // buttons in a layout. auto operationsWidget = new QWidget(this); auto operationsLayout = new QHBoxLayout(operationsWidget); - operationsLayout->setContentsMargins(0, 3, 0, 0); + operationsLayout->setContentsMargins(2, 6, 0, 6); operationsLayout->setSpacing(2); m_trackOps = new QPushButton(operationsWidget); @@ -91,44 +91,18 @@ TrackOperationsWidget::TrackOperationsWidget( TrackView * parent ) : m_trackOps->setMenu( toMenu ); m_trackOps->setToolTip(tr("Actions")); - // This helper lambda wraps a PixmapButton in a QWidget. This is necessary due to some strange effect where the - // PixmapButtons are resized to a size that's larger than their minimum/fixed size when the method "show" is called - // in "TrackContainerView::realignTracks". Specifically, with the default theme the buttons are resized from - // (16, 14) to (26, 26). This then makes them behave not as expected in layouts. - // The resizing is not done for QWidgets. Therefore we wrap the PixmapButton in a QWidget which is set to a - // fixed size that will be able to show the active and inactive pixmap. We can then use the QWidget in layouts - // without any disturbances. - // - // The resizing only seems to affect the track view hierarchy and is triggered by Qt's internal mechanisms. - // For example the buttons in the mixer view do not seem to be affected. - // If you want to debug this simply override "PixmapButton::resizeEvent" and trigger a break point in there. - auto buildPixmapButtonWrappedInWidget = [](QWidget* parent, const QString& toolTip, - std::string_view activeGraphic, std::string_view inactiveGraphic, PixmapButton*& pixmapButton) - { - const auto activePixmap = embed::getIconPixmap(activeGraphic); - const auto inactivePixmap = embed::getIconPixmap(inactiveGraphic); - - auto wrapperWidget = new QWidget(parent); - - auto button = new PixmapButton(wrapperWidget, toolTip); - button->setCheckable(true); - button->setActiveGraphic(activePixmap); - button->setInactiveGraphic(inactivePixmap); - button->setToolTip(toolTip); - - wrapperWidget->setFixedSize(button->minimumSizeHint()); - - pixmapButton = button; - - return wrapperWidget; - }; - - auto muteWidget = buildPixmapButtonWrappedInWidget(operationsWidget, tr("Mute"), "mute_active", "mute_inactive", m_muteBtn); - auto soloWidget = buildPixmapButtonWrappedInWidget(operationsWidget, tr("Solo"), "solo_active", "solo_inactive", m_soloBtn); + m_muteBtn = new AutomatableButton(operationsWidget, tr("Mute")); + m_muteBtn->setCheckable(true); + m_muteBtn->setToolTip(tr("Mute")); + m_muteBtn->setObjectName("btn-mute"); + m_soloBtn = new AutomatableButton(operationsWidget, tr("Solo")); + m_soloBtn->setCheckable(true); + m_soloBtn->setToolTip(tr("Solo")); + m_soloBtn->setObjectName("btn-solo"); operationsLayout->addWidget(m_trackOps); - operationsLayout->addWidget(muteWidget); - operationsLayout->addWidget(soloWidget); + operationsLayout->addWidget(m_muteBtn); + operationsLayout->addWidget(m_soloBtn); layout->addWidget(operationsWidget, 0, Qt::AlignTop | Qt::AlignLeading); diff --git a/src/gui/widgets/AutomatableButton.cpp b/src/gui/widgets/AutomatableButton.cpp index 6e9cd23e4..c205b75cf 100644 --- a/src/gui/widgets/AutomatableButton.cpp +++ b/src/gui/widgets/AutomatableButton.cpp @@ -29,6 +29,7 @@ #include "CaptionMenu.h" #include "StringPairDrag.h" +#include "KeyboardShortcuts.h" namespace lmms::gui @@ -111,7 +112,7 @@ void AutomatableButton::contextMenuEvent( QContextMenuEvent * _me ) void AutomatableButton::mousePressEvent( QMouseEvent * _me ) { if( _me->button() == Qt::LeftButton && - ! ( _me->modifiers() & Qt::ControlModifier ) ) + ! ( _me->modifiers() & KBD_COPY_MODIFIER ) ) { // User simply clicked, toggle if needed if( isCheckable() ) diff --git a/src/gui/widgets/ComboBox.cpp b/src/gui/widgets/ComboBox.cpp index 945d30aa3..3683cd68c 100644 --- a/src/gui/widgets/ComboBox.cpp +++ b/src/gui/widgets/ComboBox.cpp @@ -69,7 +69,7 @@ ComboBox::ComboBox( QWidget * _parent, const QString & _name ) : void ComboBox::selectNext() { - model()->setInitValue( model()->value() + 1 ); + model()->setValue(model()->value() + 1); } @@ -77,7 +77,7 @@ void ComboBox::selectNext() void ComboBox::selectPrevious() { - model()->setInitValue( model()->value() - 1 ); + model()->setValue(model()->value() - 1); } @@ -221,7 +221,7 @@ void ComboBox::wheelEvent( QWheelEvent* event ) if( model() ) { const int direction = (event->angleDelta().y() < 0 ? 1 : -1) * (event->inverted() ? -1 : 1); - model()->setInitValue(model()->value() + direction); + model()->setValue(model()->value() + direction); update(); event->accept(); } @@ -234,7 +234,7 @@ void ComboBox::setItem( QAction* item ) { if( model() ) { - model()->setInitValue( item->data().toInt() ); + model()->setValue(item->data().toInt()); } } diff --git a/src/gui/widgets/CustomTextKnob.cpp b/src/gui/widgets/CustomTextKnob.cpp index a4edde47c..062359157 100644 --- a/src/gui/widgets/CustomTextKnob.cpp +++ b/src/gui/widgets/CustomTextKnob.cpp @@ -23,18 +23,19 @@ */ #include "CustomTextKnob.h" +#include "FontHelper.h" namespace lmms::gui { -CustomTextKnob::CustomTextKnob( KnobType _knob_num, QWidget * _parent, const QString & _name, const QString & _value_text ) : +CustomTextKnob::CustomTextKnob( KnobType _knob_num, const QString& label, QWidget * _parent, const QString & _name, const QString & _value_text ) : Knob( _knob_num, _parent, _name ), - m_value_text( _value_text ) {} - -CustomTextKnob::CustomTextKnob( QWidget * _parent, const QString & _name, const QString & _value_text ) : //!< default ctor - Knob( _parent, _name ), - m_value_text( _value_text ) {} + m_value_text( _value_text ) +{ + setFont(adjustedToPixelSize(font(), SMALL_FONT_SIZE)); + setLabel(label); +} QString CustomTextKnob::displayValue() const { diff --git a/src/gui/widgets/Fader.cpp b/src/gui/widgets/Fader.cpp index 153d8ca1a..46f336cb7 100644 --- a/src/gui/widgets/Fader.cpp +++ b/src/gui/widgets/Fader.cpp @@ -73,6 +73,7 @@ SimpleTextFloat* Fader::s_textFloat = nullptr; Fader::Fader(FloatModel* model, const QString& name, QWidget* parent, bool modelIsLinear) : QWidget(parent), FloatModelView(model, this), + m_knobSize(embed::logicalSize(m_knob)), m_modelIsLinear(modelIsLinear) { if (s_textFloat == nullptr) @@ -121,7 +122,7 @@ void Fader::adjustByDecibelDelta(float value) adjustModelByDBDelta(value); updateTextFloat(); - s_textFloat->setVisibilityTimeOut(1000); + s_textFloat->showWithTimeout(1000); } void Fader::adjustByDialog() @@ -198,7 +199,7 @@ void Fader::mousePressEvent(QMouseEvent* mouseEvent) const int localY = mouseEvent->y(); const auto knobLowerPosY = calculateKnobPosYFromModel(); - const auto knobUpperPosY = knobLowerPosY - m_knob.height(); + const auto knobUpperPosY = knobLowerPosY - m_knobSize.height(); const auto clickedOnKnob = localY >= knobUpperPosY && localY <= knobLowerPosY; @@ -207,7 +208,7 @@ void Fader::mousePressEvent(QMouseEvent* mouseEvent) // If the users clicked on the knob we want to compensate for the offset to the center line // of the knob when dealing with mouse move events. // This will make it feel like the users have grabbed the knob where they clicked. - const auto knobCenterPos = knobLowerPosY - (m_knob.height() / 2); + const auto knobCenterPos = knobLowerPosY - (m_knobSize.height() / 2); m_knobCenterOffset = localY - knobCenterPos; // In this case we also will not call setVolumeByLocalPixelValue, i.e. we do not make any immediate @@ -361,9 +362,9 @@ int Fader::calculateKnobPosYFromModel() const const auto scaledRatio = computeScaledRatio(actualDb); // This returns results between: - // * m_knob.height() for a ratio of 1 + // * m_knobSize.height() for a ratio of 1 // * height() for a ratio of 0 - return height() - (height() - m_knob.height()) * scaledRatio; + return height() - (height() - m_knobSize.height()) * scaledRatio; } } else @@ -375,9 +376,9 @@ int Fader::calculateKnobPosYFromModel() const auto const ratio = (clampedValue - minV) / (maxV - minV); // This returns results between: - // * m_knob.height() for a ratio of 1 + // * m_knobSize.height() for a ratio of 1 // * height() for a ratio of 0 - return height() - (height() - m_knob.height()) * ratio; + return height() - (height() - m_knobSize.height()) * ratio; } } @@ -391,11 +392,11 @@ void Fader::setVolumeByLocalPixelValue(int y) // The y parameter gives us where the mouse click went. // Assume that the middle of the fader should go there. - int const lowerFaderKnob = y + (m_knob.height() / 2); + int const lowerFaderKnob = y + (m_knobSize.height() / 2); // In some cases we need the clamped lower position of the fader knob so we can ensure // that we only set allowed values in the model range. - int const clampedLowerFaderKnob = std::clamp(lowerFaderKnob, m_knob.height(), height()); + int const clampedLowerFaderKnob = std::clamp(lowerFaderKnob, m_knobSize.height(), height()); if (modelIsLinear()) { @@ -411,7 +412,7 @@ void Fader::setVolumeByLocalPixelValue(int y) // First map the lower knob position to [0, 1] so that we can apply some curve mapping, e.g. // square, cube, etc. - LinearMap knobMap(float(m_knob.height()), 1., float(height()), 0.); + LinearMap knobMap(float(m_knobSize.height()), 1., float(height()), 0.); // Apply the inverse of what is done in calculateKnobPosYFromModel auto const knobPos = std::pow(knobMap.map(clampedLowerFaderKnob), 1./c_dBScalingExponent); @@ -433,7 +434,7 @@ void Fader::setVolumeByLocalPixelValue(int y) } else { - LinearMap valueMap(float(m_knob.height()), model()->maxValue(), float(height()), model()->minValue()); + LinearMap valueMap(float(m_knobSize.height()), model()->maxValue(), float(height()), model()->minValue()); model()->setValue(valueMap.map(clampedLowerFaderKnob)); } @@ -512,9 +513,16 @@ void Fader::modelValueChanged() QString Fader::getModelValueAsDbString() const { - const auto value = model()->value(); - QString label(tr("Volume: %1 dB")); + // Check that the pointer isn't dangling, which can happen if the + // model was dropped in order to load a new project from an existing one. + auto* newModel = model(); + if (!newModel) + { + // model() was a dangling pointer, so return a sane default value. + return label.arg(tr("-inf")); + } + const auto value = newModel->value(); if (modelIsLinear()) { @@ -546,7 +554,7 @@ void Fader::paintEvent(QPaintEvent* ev) } // Draw the knob - painter.drawPixmap((width() - m_knob.width()) / 2, calculateKnobPosYFromModel() - m_knob.height(), m_knob); + painter.drawPixmap((width() - m_knobSize.width()) / 2, calculateKnobPosYFromModel() - m_knobSize.height(), m_knob); } void Fader::paintLevels(QPaintEvent* ev, QPainter& painter, bool linear) @@ -720,7 +728,7 @@ void Fader::paintFaderTicks(QPainter& painter) for (float i = startValue; i >= c_faderMinDb; i-= stepSize) { const auto scaledRatio = computeScaledRatio(i); - const auto maxHeight = height() - (height() - m_knob.height()) * scaledRatio - (m_knob.height() / 2); + const auto maxHeight = height() - (height() - m_knobSize.height()) * scaledRatio - (m_knobSize.height() / 2); if (approximatelyEqual(i, 0.)) { diff --git a/src/gui/widgets/FloatModelEditorBase.cpp b/src/gui/widgets/FloatModelEditorBase.cpp index 3c7fe93c7..ed09fa261 100644 --- a/src/gui/widgets/FloatModelEditorBase.cpp +++ b/src/gui/widgets/FloatModelEditorBase.cpp @@ -338,7 +338,7 @@ void FloatModelEditorBase::wheelEvent(QWheelEvent * we) s_textFloat->setText(displayValue()); s_textFloat->moveGlobal(this, QPoint(width() + 2, 0)); - s_textFloat->setVisibilityTimeOut(1000); + s_textFloat->showWithTimeout(1000); emit sliderMoved(model()->value()); } diff --git a/src/gui/widgets/Knob.cpp b/src/gui/widgets/Knob.cpp index 25d2e3e3f..624ea287d 100644 --- a/src/gui/widgets/Knob.cpp +++ b/src/gui/widgets/Knob.cpp @@ -32,6 +32,8 @@ #include "embed.h" #include "FontHelper.h" +#include + namespace lmms::gui { @@ -49,6 +51,24 @@ Knob::Knob( KnobType _knob_num, QWidget * _parent, const QString & _name ) : initUi( _name ); } +Knob::Knob(KnobType knobNum, const QString& labelText, QWidget* parent, LabelRendering labelRendering, const QString& name) : + Knob(knobNum, parent, name) +{ + setLabel(labelText); + + if (labelRendering == LabelRendering::LegacyFixedFontSize) + { + setFixedFontSizeLabelRendering(); + } +} + +Knob::Knob(KnobType knobNum, const QString& labelText, int labelPixelSize, QWidget* parent, const QString& name) : + Knob(knobNum, parent, name) +{ + setFont(adjustedToPixelSize(font(), labelPixelSize)); + setLabel(labelText); +} + Knob::Knob( QWidget * _parent, const QString & _name ) : Knob( KnobType::Bright26, _parent, _name ) { @@ -56,7 +76,6 @@ Knob::Knob( QWidget * _parent, const QString & _name ) : - void Knob::initUi( const QString & _name ) { onKnobNumUpdated(); @@ -129,16 +148,12 @@ void Knob::onKnobNumUpdated() -void Knob::setLabel( const QString & txt ) +void Knob::setLabel(const QString& txt) { m_label = txt; m_isHtmlLabel = false; - if( m_knobPixmap ) - { - setFixedSize(qMax( m_knobPixmap->width(), - horizontalAdvance(QFontMetrics(adjustedToPixelSize(font(), SMALL_FONT_SIZE)), m_label)), - m_knobPixmap->height() + 10); - } + + updateFixedSize(); update(); } @@ -165,8 +180,49 @@ void Knob::setHtmlLabel(const QString &htmltxt) update(); } +void Knob::setFixedFontSizeLabelRendering() +{ + m_fixedFontSizeLabelRendering = true; + updateFixedSize(); + update(); +} + +void Knob::updateFixedSize() +{ + if (fixedFontSizeLabelRendering()) + { + if (m_knobPixmap) + { + // In legacy mode only the width of the label is taken into account while the height is not + const int labelWidth = horizontalAdvance(QFontMetrics(adjustedToPixelSize(font(), SMALL_FONT_SIZE)), m_label); + const int width = std::max(m_knobPixmap->width(), labelWidth); + + // Legacy mode assumes that the label will fit into 10 pixels plus some of the pixmap area + setFixedSize(width, m_knobPixmap->height() + 10); + } + } + else + { + // Styled knobs do not use pixmaps and have no labels. Their size is set from the outside and + // they are painted within these limits. Hence we should not compute a new size from a pixmap + // and/or label the case of styled knobs. + if (knobNum() == KnobType::Styled) + { + return; + } + + QSize pixmapSize = m_knobPixmap ? m_knobPixmap->size() : QSize(0, 0); + + auto fm = QFontMetrics(font()); + + const int width = std::max(pixmapSize.width(), horizontalAdvance(fm, m_label)); + const int height = pixmapSize.height() + fm.height(); + + setFixedSize(width, height); + } +} void Knob::setTotalAngle( float angle ) { @@ -447,31 +503,42 @@ void Knob::drawKnob( QPainter * _p ) _p->drawImage( 0, 0, m_cache ); } -void Knob::paintEvent( QPaintEvent * _me ) +void Knob::drawLabel(QPainter& p) { - QPainter p( this ); - - drawKnob( &p ); if( !m_label.isEmpty() ) { if (!m_isHtmlLabel) { - p.setFont(adjustedToPixelSize(p.font(), SMALL_FONT_SIZE)); + if (fixedFontSizeLabelRendering()) + { + p.setFont(adjustedToPixelSize(p.font(), SMALL_FONT_SIZE)); + } + auto fm = p.fontMetrics(); + const auto x = (width() - horizontalAdvance(fm, m_label)) / 2; + const auto descent = fixedFontSizeLabelRendering() ? 2 : fm.descent(); + const auto y = height() - descent; + p.setPen(textColor()); - p.drawText(width() / 2 - - horizontalAdvance(p.fontMetrics(), m_label) / 2, - height() - 2, m_label); + p.drawText(x, y, m_label); } else { // TODO setHtmlLabel is never called so this will never be executed. Remove functionality? - m_tdRenderer->setDefaultFont(adjustedToPixelSize(p.font(), SMALL_FONT_SIZE)); + m_tdRenderer->setDefaultFont(font()); p.translate((width() - m_tdRenderer->idealWidth()) / 2, (height() - m_tdRenderer->pageSize().height()) / 2); m_tdRenderer->drawContents(&p); } } } +void Knob::paintEvent(QPaintEvent*) +{ + QPainter p(this); + + drawKnob(&p); + drawLabel(p); +} + void Knob::changeEvent(QEvent * ev) { if (ev->type() == QEvent::EnabledChange) @@ -484,6 +551,14 @@ void Knob::changeEvent(QEvent * ev) m_cache = QImage(); update(); } + else if (ev->type() == QEvent::FontChange) + { + // The size of the label might have changed so update + // the size of this widget. + updateFixedSize(); + + update(); + } } diff --git a/src/gui/widgets/Oscilloscope.cpp b/src/gui/widgets/Oscilloscope.cpp index bf66fa465..122a13ce9 100644 --- a/src/gui/widgets/Oscilloscope.cpp +++ b/src/gui/widgets/Oscilloscope.cpp @@ -34,7 +34,6 @@ #include "Engine.h" #include "Song.h" #include "embed.h" -#include "BufferManager.h" namespace lmms::gui { diff --git a/src/gui/widgets/PixmapButton.cpp b/src/gui/widgets/PixmapButton.cpp index bd683ed71..b456926ad 100644 --- a/src/gui/widgets/PixmapButton.cpp +++ b/src/gui/widgets/PixmapButton.cpp @@ -102,17 +102,13 @@ void PixmapButton::mouseDoubleClickEvent( QMouseEvent * _me ) } - - -void PixmapButton::setActiveGraphic( const QPixmap & _pm ) +void PixmapButton::setActiveGraphic(const QPixmap &pm) { - m_activePixmap = _pm; - resize(m_activePixmap.size() / m_activePixmap.devicePixelRatio()); + m_activePixmap = pm; + resize(embed::logicalSize(m_activePixmap)); } - - void PixmapButton::setInactiveGraphic( const QPixmap & _pm, bool _update ) { m_inactivePixmap = _pm; @@ -129,9 +125,8 @@ QSize PixmapButton::sizeHint() const QSize PixmapButton::minimumSizeHint() const { - const auto activeSize = m_activePixmap.size() / m_activePixmap.devicePixelRatio(); - const auto inactiveSize = m_inactivePixmap.size() / m_inactivePixmap.devicePixelRatio(); - return activeSize.expandedTo(inactiveSize); + return embed::logicalSize(m_activePixmap) + .expandedTo(embed::logicalSize(m_inactivePixmap)); } bool PixmapButton::isActive() const diff --git a/src/gui/widgets/SimpleTextFloat.cpp b/src/gui/widgets/SimpleTextFloat.cpp index e37753229..1be683837 100644 --- a/src/gui/widgets/SimpleTextFloat.cpp +++ b/src/gui/widgets/SimpleTextFloat.cpp @@ -62,7 +62,7 @@ void SimpleTextFloat::setText(const QString & text) void SimpleTextFloat::showWithDelay(int msecBeforeDisplay, int msecDisplayTime) { - if (msecBeforeDisplay != 0) + if (msecBeforeDisplay > 0) { m_showTimer->start(msecBeforeDisplay); } @@ -71,7 +71,7 @@ void SimpleTextFloat::showWithDelay(int msecBeforeDisplay, int msecDisplayTime) show(); } - if (msecDisplayTime != 0) + if (msecDisplayTime > 0) { m_hideTimer->start(msecBeforeDisplay + msecDisplayTime); } @@ -84,10 +84,4 @@ void SimpleTextFloat::hide() QWidget::hide(); } -void SimpleTextFloat::setVisibilityTimeOut(int msecs) -{ - QTimer::singleShot(msecs, this, SLOT(hide())); - show(); -} - } // namespace lmms::gui diff --git a/src/gui/widgets/TempoSyncKnob.cpp b/src/gui/widgets/TempoSyncKnob.cpp index 473cee28c..eedefd4ad 100644 --- a/src/gui/widgets/TempoSyncKnob.cpp +++ b/src/gui/widgets/TempoSyncKnob.cpp @@ -30,6 +30,7 @@ #include "Engine.h" #include "CaptionMenu.h" #include "embed.h" +#include "FontHelper.h" #include "GuiApplication.h" #include "MainWindow.h" #include "MeterDialog.h" @@ -51,6 +52,24 @@ TempoSyncKnob::TempoSyncKnob( KnobType _knob_num, QWidget * _parent, { } +TempoSyncKnob::TempoSyncKnob(KnobType knobNum, const QString& labelText, QWidget* parent, LabelRendering labelRendering, const QString& name) : + TempoSyncKnob(knobNum, parent, name) +{ + setLabel(labelText); + + if (labelRendering == Knob::LabelRendering::LegacyFixedFontSize) + { + setFixedFontSizeLabelRendering(); + } +} + + +TempoSyncKnob::TempoSyncKnob(KnobType knobNum, const QString& labelText, int labelPixelSize, QWidget* parent, const QString& name) : + TempoSyncKnob(knobNum, parent, name) +{ + setFont(adjustedToPixelSize(font(), labelPixelSize)); + setLabel(labelText); +} @@ -63,8 +82,6 @@ TempoSyncKnob::~TempoSyncKnob() } - - void TempoSyncKnob::modelChanged() { if( model() == nullptr ) diff --git a/src/gui/widgets/TextFloat.cpp b/src/gui/widgets/TextFloat.cpp index bd3ed03bc..4eb14bd50 100644 --- a/src/gui/widgets/TextFloat.cpp +++ b/src/gui/widgets/TextFloat.cpp @@ -44,7 +44,7 @@ TextFloat::TextFloat() : } TextFloat::TextFloat(const QString & title, const QString & text, const QPixmap & pixmap) : - QWidget(getGUI()->mainWindow(), Qt::Tool | Qt::FramelessWindowHint) + QWidget(getGUI()->mainWindow(), Qt::ToolTip) { QHBoxLayout * mainLayout = new QHBoxLayout(); setLayout(mainLayout); diff --git a/src/lmmsconfig.h.in b/src/lmmsconfig.h.in index 867a04a45..e63ce1fa4 100644 --- a/src/lmmsconfig.h.in +++ b/src/lmmsconfig.h.in @@ -33,6 +33,8 @@ #cmakedefine LMMS_HAVE_SDL #cmakedefine LMMS_HAVE_STK #cmakedefine LMMS_HAVE_VST +#cmakedefine LMMS_HAVE_VST_32 +#cmakedefine LMMS_HAVE_VST_64 #cmakedefine LMMS_HAVE_SF_COMPLEVEL #cmakedefine LMMS_DEBUG_FPE diff --git a/src/lmmsversion.h.in b/src/lmmsversion.h.in index dc7ae84d0..47bdba52a 100644 --- a/src/lmmsversion.h.in +++ b/src/lmmsversion.h.in @@ -1,2 +1,3 @@ #define LMMS_VERSION "@VERSION@" #define LMMS_PROJECT_COPYRIGHT "@PROJECT_COPYRIGHT@" +#define LMMS_BUILD_OPTIONS "@LMMS_BUILD_OPTIONS@" diff --git a/src/tracks/InstrumentTrack.cpp b/src/tracks/InstrumentTrack.cpp index 70ac7432e..8370807ae 100644 --- a/src/tracks/InstrumentTrack.cpp +++ b/src/tracks/InstrumentTrack.cpp @@ -109,6 +109,8 @@ InstrumentTrack::InstrumentTrack(TrackContainer* tc) : connect(&m_pitchModel, SIGNAL(dataChanged()), this, SLOT(updatePitch()), Qt::DirectConnection); connect(&m_pitchRangeModel, SIGNAL(dataChanged()), this, SLOT(updatePitchRange()), Qt::DirectConnection); connect(&m_mixerChannelModel, SIGNAL(dataChanged()), this, SLOT(updateMixerChannel()), Qt::DirectConnection); + + autoAssignMidiDevice(true); } @@ -749,7 +751,7 @@ bool InstrumentTrack::play( const TimePos & _start, const fpp_t _frames, TimePos cur_start = _start; if( _clip_num < 0 ) { - cur_start -= c->startPosition(); + cur_start -= c->startPosition() + c->startTimeOffset(); } // get all notes from the given clip... @@ -760,25 +762,33 @@ bool InstrumentTrack::play( const TimePos & _start, const fpp_t _frames, // very effective algorithm for playing notes that are // posated within the current sample-frame - if( cur_start > 0 ) { - // skip notes which are posated before start-bar - while( nit != notes.end() && ( *nit )->pos() < cur_start ) + // skip notes which end before start-bar + while( nit != notes.end() && ( *nit )->endPos() < cur_start ) { ++nit; } } - while (nit != notes.end() && (*nit)->pos() == cur_start) + while (nit != notes.end() && (*nit)->pos() < c->length() - c->startTimeOffset()) { const auto currentNote = *nit; + // Skip any notes note at the current time pos or not overlapping with the start. + if (!(currentNote->pos() == cur_start + || (cur_start == -c->startTimeOffset() && (*nit)->pos() < cur_start && (*nit)->endPos() > cur_start))) + { + ++nit; + continue; + } + // Calculate the overlap of the note over the clip end. + const auto noteOverlap = std::max(0, currentNote->endPos() - (c->length() - c->startTimeOffset())); // 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); + : (currentNote->endPos() - cur_start - noteOverlap) * frames_per_tick; NotePlayHandle* notePlayHandle = NotePlayHandleManager::acquire(this, _offset, noteFrames, *currentNote); notePlayHandle->setPatternTrack(pattern_track); @@ -787,7 +797,7 @@ bool InstrumentTrack::play( const TimePos & _start, const fpp_t _frames, { // then set song-global offset of clip in order to // properly perform the note detuning - notePlayHandle->setSongGlobalParentOffset( c->startPosition() ); + notePlayHandle->setSongGlobalParentOffset( c->startPosition() + c->startTimeOffset()); } Engine::audioEngine()->addPlayHandle( notePlayHandle ); diff --git a/src/tracks/MidiClip.cpp b/src/tracks/MidiClip.cpp index ab5321687..e9b507df5 100644 --- a/src/tracks/MidiClip.cpp +++ b/src/tracks/MidiClip.cpp @@ -48,36 +48,40 @@ MidiClip::MidiClip( InstrumentTrack * _instrument_track ) : if (_instrument_track->trackContainer() == Engine::patternStore()) { resizeToFirstTrack(); + setResizable(false); + } + else + { + setResizable(true); } init(); - setAutoResize( true ); } MidiClip::MidiClip( const MidiClip& other ) : - Clip( other.m_instrumentTrack ), + Clip(other), m_instrumentTrack( other.m_instrumentTrack ), m_clipType( other.m_clipType ), m_steps( other.m_steps ) { for (const auto& note : other.m_notes) { - m_notes.push_back(new Note(*note)); + m_notes.push_back(note->clone()); } init(); switch( getTrack()->trackContainer()->type() ) { case TrackContainer::Type::Pattern: - setAutoResize( true ); + setResizable(false); break; case TrackContainer::Type::Song: // move down default: - setAutoResize( false ); + setResizable(true); break; } } @@ -145,18 +149,24 @@ void MidiClip::updateLength() return; } - tick_t max_length = TimePos::ticksPerBar(); - - for (const auto& note : m_notes) + // If the clip has already been manually resized, don't automatically resize it. + // Unless we are in a pattern, where you can't resize stuff manually + if (getAutoResize() || !getResizable()) { - if (note->length() > 0) + tick_t max_length = TimePos::ticksPerBar(); + + for (const auto& note : m_notes) { - max_length = std::max(max_length, note->endPos()); + if (note->length() > 0) + { + max_length = std::max(max_length, note->endPos()); + } } + changeLength( TimePos( max_length ).nextFullBar() * + TimePos::ticksPerBar() ); + setStartTimeOffset(TimePos(0)); + updatePatternTrack(); } - changeLength( TimePos( max_length ).nextFullBar() * - TimePos::ticksPerBar() ); - updatePatternTrack(); } @@ -187,7 +197,7 @@ TimePos MidiClip::beatClipLength() const Note * MidiClip::addNote( const Note & _new_note, const bool _quant_pos ) { - auto new_note = new Note(_new_note); + auto new_note = _new_note.clone(); if (_quant_pos && gui::getGUI()->pianoRoll()) { new_note->quantizePos(gui::getGUI()->pianoRoll()->quantization()); @@ -314,6 +324,27 @@ void MidiClip::setStep( int step, bool enabled ) +void MidiClip::reverseNotes(const NoteVector& notes) +{ + if (notes.empty()) { return; } + + addJournalCheckPoint(); + + // Find the very first start position and the very last end position of all the notes. + TimePos firstPos = (*std::min_element(notes.begin(), notes.end(), [](const Note* n1, const Note* n2){ return Note::lessThan(n1, n2); }))->pos(); + TimePos lastPos = (*std::max_element(notes.begin(), notes.end(), [](const Note* n1, const Note* n2){ return n1->endPos() < n2->endPos(); }))->endPos(); + + for (auto note : notes) + { + TimePos newStart = lastPos - (note->pos() - firstPos) - note->length(); + note->setPos(newStart); + } + + rearrangeAllNotes(); + emit dataChanged(); +} + + void MidiClip::splitNotes(const NoteVector& notes, TimePos pos) { @@ -416,6 +447,8 @@ void MidiClip::saveSettings( QDomDocument & _doc, QDomElement & _this ) { _this.setAttribute( "type", static_cast(m_clipType) ); _this.setAttribute( "name", name() ); + _this.setAttribute("autoresize", QString::number(getAutoResize())); + _this.setAttribute("off", startTimeOffset()); if (const auto& c = color()) { @@ -435,6 +468,7 @@ void MidiClip::saveSettings( QDomDocument & _doc, QDomElement & _this ) } _this.setAttribute( "muted", isMuted() ); _this.setAttribute( "steps", m_steps ); + _this.setAttribute( "len", length() ); // now save settings of all notes for (auto& note : m_notes) @@ -488,7 +522,20 @@ void MidiClip::loadSettings( const QDomElement & _this ) } checkType(); - updateLength(); + + int len = _this.attribute("len").toInt(); + if (len <= 0) + { + // TODO: Handle with an upgrade method + updateLength(); + } + else + { + changeLength(len); + } + + setAutoResize(_this.attribute("autoresize", "1").toInt()); + setStartTimeOffset(_this.attribute("off").toInt()); emit dataChanged(); } diff --git a/tests/src/tracks/AutomationTrackTest.cpp b/tests/src/tracks/AutomationTrackTest.cpp index b4f6effd9..18e981115 100644 --- a/tests/src/tracks/AutomationTrackTest.cpp +++ b/tests/src/tracks/AutomationTrackTest.cpp @@ -163,8 +163,7 @@ private slots: Note* note = midiClip.addNote(Note(TimePos(4, 0)), false); note->createDetuning(); - DetuningHelper* dh = note->detuning(); - auto clip = dh->automationClip(); + auto clip = note->detuning()->automationClip(); clip->setProgressionType( AutomationClip::ProgressionType::Linear ); clip->putValue(TimePos(0, 0), 0.0); clip->putValue(TimePos(4, 0), 1.0);