diff --git a/.github/workflows/build-plugin.yml b/.github/workflows/build-plugin.yml new file mode 100644 index 0000000..971de05 --- /dev/null +++ b/.github/workflows/build-plugin.yml @@ -0,0 +1,158 @@ +name: Build VCV Rack Plugin +on: [push, pull_request] + +env: + rack-sdk-version: latest + rack-plugin-toolchain-dir: /home/build/rack-plugin-toolchain + +defaults: + run: + shell: bash + +jobs: + + modify-plugin-version: + name: Modify plugin version + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions/cache@v3 + id: plugin-version-cache + with: + path: plugin.json + key: ${{ github.sha }}-${{ github.run_id }} + - run: | + gitrev=`git rev-parse --short HEAD` + pluginversion=`jq -r '.version' plugin.json` + echo "Set plugin version from $pluginversion to $pluginversion-$gitrev" + cat <<< `jq --arg VERSION "$pluginversion-$gitrev" '.version=$VERSION' plugin.json` > plugin.json + # only modify plugin version if no tag was created + if: "! startsWith(github.ref, 'refs/tags/v')" + + build: + name: ${{ matrix.platform }} + needs: modify-plugin-version + runs-on: ubuntu-latest + container: + image: ghcr.io/qno/rack-plugin-toolchain-win-linux + options: --user root + strategy: + matrix: + platform: [win-x64, lin-x64] + steps: + - uses: actions/checkout@v3 + with: + submodules: recursive + - uses: actions/cache@v3 + id: plugin-version-cache + with: + path: plugin.json + key: ${{ github.sha }}-${{ github.run_id }} + - name: Build plugin + run: | + export PLUGIN_DIR=$GITHUB_WORKSPACE + pushd ${{ env.rack-plugin-toolchain-dir }} + make plugin-build-${{ matrix.platform }} + - name: Upload artifact + uses: actions/upload-artifact@v3 + with: + path: ${{ env.rack-plugin-toolchain-dir }}/plugin-build + name: ${{ matrix.platform }} + + build-mac: + name: mac + needs: modify-plugin-version + runs-on: macos-latest + strategy: + fail-fast: false + matrix: + platform: [x64, arm64] + steps: + - uses: actions/checkout@v3 + with: + submodules: recursive + - uses: actions/cache@v3 + id: plugin-version-cache + with: + path: plugin.json + key: ${{ github.sha }}-${{ github.run_id }} + - name: Get Rack-SDK + run: | + pushd $HOME + wget -O Rack-SDK.zip https://vcvrack.com/downloads/Rack-SDK-${{ env.rack-sdk-version }}-mac-${{ matrix.platform }}.zip + unzip Rack-SDK.zip + - name: Build plugin + run: | + CROSS_COMPILE_TARGET_x64=x86_64-apple-darwin + CROSS_COMPILE_TARGET_arm64=arm64-apple-darwin + export RACK_DIR=$HOME/Rack-SDK + export CROSS_COMPILE=$CROSS_COMPILE_TARGET_${{ matrix.platform }} + make dep + make dist + - name: Upload artifact + uses: actions/upload-artifact@v3 + with: + path: dist/*.vcvplugin + name: mac-${{ matrix.platform }} + + publish: + name: Publish plugin + # only create a release if a tag was created that is called e.g. v1.2.3 + # see also https://vcvrack.com/manual/Manifest#version + if: startsWith(github.ref, 'refs/tags/v') + runs-on: ubuntu-latest + needs: [build, build-mac] + steps: + - uses: actions/checkout@v3 + - uses: FranzDiebold/github-env-vars-action@v2 + - name: Check if plugin version matches tag + run: | + pluginversion=`jq -r '.version' plugin.json` + if [ "v$pluginversion" != "${{ env.CI_REF_NAME }}" ]; then + echo "Plugin version from plugin.json 'v$pluginversion' doesn't match with tag version '${{ env.CI_REF_NAME }}'" + exit 1 + fi + - name: Create Release + uses: softprops/action-gh-release@v1 + with: + tag_name: ${{ github.ref }} + name: Release ${{ env.CI_REF_NAME }} + body: | + ${{ env.CI_REPOSITORY_NAME }} VCV Rack Plugin ${{ env.CI_REF_NAME }} + draft: false + prerelease: false + - uses: actions/download-artifact@v3 + with: + path: _artifacts + - name: Upload release assets + uses: svenstaro/upload-release-action@v2 + with: + repo_token: ${{ secrets.GITHUB_TOKEN }} + file: _artifacts/**/*.vcvplugin + tag: ${{ github.ref }} + file_glob: true + + publish-nightly: + name: Publish Nightly + # only create a release if a tag was created that is called e.g. v1.2.3 + # see also https://vcvrack.com/manual/Manifest#version + if: ${{ github.ref == 'refs/heads/v2-dev' && github.repository_owner == 'stoermelder' }} + runs-on: ubuntu-latest + needs: [build, build-mac] + steps: + - uses: actions/download-artifact@v3 + with: + path: _artifacts + - name: Delete old release assets + uses: mknejp/delete-release-assets@v1 + with: + token: ${{ github.token }} + tag: Nightly # This may also be of the form 'refs/tags/staging' + assets: '*' + - name: Upload release assets + uses: svenstaro/upload-release-action@v2 + with: + repo_token: ${{ secrets.GITHUB_TOKEN }} + file: _artifacts/**/*.vcvplugin + tag: Nightly + file_glob: true \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index ea1e0a8..5ac015b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,48 @@ +## 2.0.beta4 + +### Fixes and Changes + +- Added panel option to follow Rack's dark panel setting +- Modules [8FACE, 8FACEx2](./docs/EightFace.md) + - Allow disabling of "long-press" for changing the number of active slots (#354) +- Module [8FACE mk2](./docs/EightFaceMk2.md) + - Allow disabling of "long-press" for changing the number of active slots (#354) + - Added HSL color picker for bound modules' box + - Fixed broken module-id mapping when adding using STRIP or STRIP++ + - Fixed crash while exceding 0..10V in Volt-mode (#377) + - Increased maximum number of expanders to 15 + - Added missing reset-handling for "Trigger random", "Trigger pseudo-random" and "Trigger random walk" +- Module [GLUE](./docs/Glue.md) + - Added HSL color picker +- Module [GRIP](./docs/Grip.md) + - Fixed broken parameter locking (#360) +- Module [GOTO](./docs/Goto.md) + - Fixed broken zoom behavior when jumping by buttons on the panel + - Improved smooth transition speed on long distances (#376) +- Module [MB](./docs/Mb.md) + - Fixed crash on exiting Rack's after adding MB (#352) + - Fixed wrong hotkey modifier on Mac (Ctrl instead of Cmd) on Space-key + - Added missing template loading after adding a module (#369) +- Module [ROTOR mod A](./docs/RotorA.md) + - Fixed occasional crashes (#365) +- Module [SAIL](./docs/Sail.md) + - Fixed occasional crash (#358) +- Module [STRIP](./docs/Strip.md) + - Fixed crash in rare cases (Surge-modules) (#366) + - Fixed wrong hotkey modifier on Mac (Ctrl instead of Cmd) on Cmd+Shift+L +- Module [STRIP++](./docs/StripPp.md) + - Fixed wrong hotkey modifier on Mac (Ctrl instead of Cmd) +- Module [STROKE](./docs/Stroke.md) + - Added commands "Zoom to specific module" and "Zoom to specific module (smooth)" (#357) + - Fixed wrong hotkey modifier on Mac (Ctrl instead of Cmd) + - Fixed broken "Zoom to module" and "Zoom toggle" commands (#382) +- Module [SPIN](./docs/Spin.md) + - Fixed middle mouse button handling in Rack v2 (#372) +- Module [TRANSIT](./docs/Transit.md) + - Allow disabling of "long-press" for changing the number of active snapshots (#354) + - Increased maximum number of expanders to 15 (#381) + - Added missing reset-handling for "Trigger random", "Trigger pseudo-random" and "Trigger random walk" + ## 2.0.beta3 ### New modules diff --git a/Makefile b/Makefile index df0d805..6b94b53 100644 --- a/Makefile +++ b/Makefile @@ -20,4 +20,4 @@ endif @# Copy distributables cp -R $(DISTRIBUTABLES) dist/$(SLUG)/ @# Create vcvplugin package - cd dist && tar -c $(SLUG) | zstd -$(ZSTD_COMPRESSION_LEVEL) -o "$(SLUG)"-"$(VERSION)"-$(ARCH_OS_NAME).vcvplugin \ No newline at end of file + cd dist && tar -c $(SLUG) | zstd -$(ZSTD_COMPRESSION_LEVEL) -o "$(SLUG)"-"$(VERSION)"-$(ARCH_NAME).vcvplugin \ No newline at end of file diff --git a/README.md b/README.md index cf53d50..200dbb4 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,7 @@ # stoermelder PackOne -![Version](https://img.shields.io/badge/version-2.0.beta2-green.svg?style=flat-square) -![Rack SDK](https://img.shields.io/badge/Rack--SDK-2.1.0-red.svg?style=flat-square) +![Version](https://img.shields.io/badge/version-2.0.beta4-green.svg?style=flat-square) ![License](https://img.shields.io/badge/license-GPLv3+-blue.svg?style=flat-square) ![Language](https://img.shields.io/badge/language-C++-yellow.svg?style=flat-square) diff --git a/azure-pipelines.yml b/azure-pipelines.yml deleted file mode 100644 index 8bf0c9e..0000000 --- a/azure-pipelines.yml +++ /dev/null @@ -1,160 +0,0 @@ -# Starter pipeline -# Start with a minimal pipeline that you can customize to build and deploy your code. -# Add steps that build, run tests, deploy, and more: -# https://aka.ms/yaml - -trigger: -- v2-dev - -pr: -- v2-dev - -jobs: -- job: Build - strategy: - matrix: - mac: - imageName: 'macos-11' - isMac: true - windows: - imageName: 'windows-2019' - isWindows: true - linux: - imageName: 'ubuntu-20.04' - isLinux: true - - pool: - vmImage: $(imageName) - - steps: - - checkout: self - fetchDepth: 1 - # submodules: recursive # can't do submodules here b'cuz depth=1 fails with Github - - - bash: | - uname -a - git submodule update --init --recursive - pushd $AGENT_TEMPDIRECTORY - curl -o Rack-SDK.zip https://vcvrack.com/downloads/Rack-SDK-2.1.0-win.zip - unzip Rack-SDK.zip - displayName: Get Windows Rack-SDK - condition: variables.isWindows - - - bash: | - uname -a - git submodule update --init --recursive - pushd $AGENT_TEMPDIRECTORY - curl -o Rack-SDK.zip https://vcvrack.com/downloads/Rack-SDK-2.1.0-mac.zip - unzip Rack-SDK.zip - displayName: Get Mac Rack-SDK - condition: variables.isMac - - - bash: | - uname -a - git submodule update --init --recursive - pushd $AGENT_TEMPDIRECTORY - curl -o Rack-SDK.zip https://vcvrack.com/downloads/Rack-SDK-2.1.0-lin.zip - unzip Rack-SDK.zip - displayName: Get Linux Rack-SDK - condition: variables.isLinux - - - bash: | - chmod +x scripts/resetversion.sh - ./scripts/resetversion.sh - displayName: Update Version in plugins.json - - - bash: | - export RACK_DIR=$AGENT_TEMPDIRECTORY/Rack-SDK - export CC=gcc - make win-dist - mkdir products_win/ - cp dist/*vcvplugin products_win/ - displayName: Build Windows Plugins - condition: variables.isWindows - - - bash: | - export RACK_DIR=$AGENT_TEMPDIRECTORY/Rack-SDK - make dist - mkdir products_mac/ - cp dist/*vcvplugin products_mac/ - displayName: Build Mac Plugins - condition: variables.isMac - - - bash: | - export RACK_DIR=$AGENT_TEMPDIRECTORY/Rack-SDK - sudo apt-get update - sudo apt-get install libglu-dev --fix-missing - make dist - mkdir products_lin/ - cp dist/*vcvplugin products_lin/ - displayName: Build Linux Plugins - condition: variables.isLinux - - - task: PublishPipelineArtifact@0 - inputs: - artifactName: 'PACKONE_ZIP_LINUX' - targetPath: 'products_lin/' - displayName: Publish Linux vcvplugin - condition: variables.isLinux - - - task: PublishPipelineArtifact@0 - inputs: - artifactName: 'PACKONE_ZIP_MACOS' - targetPath: 'products_mac/' - displayName: Publish macOS vcvplugin - condition: variables.isMac - - - task: PublishPipelineArtifact@0 - inputs: - artifactName: 'PACKONE_ZIP_WIN' - targetPath: 'products_win/' - displayName: Publish Windows vcvplugin - condition: variables.isWindows - -- job: UpdateGithubRelease - dependsOn: Build - condition: and(succeeded(), or(eq(variables['Build.SourceBranch'], 'refs/heads/v2-dev'), eq(variables['Build.SourceBranch'], 'refs/heads/azure-test'))) - - pool: - vmImage: 'ubuntu-latest' - - steps: - - task: DownloadPipelineArtifact@0 - inputs: - artifactName: 'PACKONE_ZIP_LINUX' - targetPath: $(Build.ArtifactStagingDirectory) - - - task: DownloadPipelineArtifact@0 - inputs: - artifactName: 'PACKONE_ZIP_MACOS' - targetPath: $(Build.ArtifactStagingDirectory) - - - task: DownloadPipelineArtifact@0 - inputs: - artifactName: 'PACKONE_ZIP_WIN' - targetPath: $(Build.ArtifactStagingDirectory) - - - bash: | - ls -l $(Build.ArtifactStagingDirectory) - export EXTEND_TAG=`date "+%Y%m%d"` - for file in $(Build.ArtifactStagingDirectory)/*.vcvplugin; do mv "$file" "${file/.vcvplugin/-${EXTEND_TAG}.vcvplugin}"; done - ls -l $(Build.ArtifactStagingDirectory) - displayName: Tag asset names with Date - - - bash: | - chmod +x scripts/release-notes.sh - scripts/release-notes.sh > $(Build.ArtifactStagingDirectory)/ReleaseNotes.md - displayName: Fake up release notes - - - task: GitHubRelease@0 - displayName: "Update Github Release" - inputs: - gitHubConnection: stoermelder - repositoryName: stoermelder/vcvrack-packone - action: edit - tag: Nightly - target: '$(Build.SourceVersion)' - addChangeLog: false - releaseNotesFile: $(Build.ArtifactStagingDirectory)/ReleaseNotes.md - isPreRelease: true - assets: $(Build.ArtifactStagingDirectory)/*.vcvplugin \ No newline at end of file diff --git a/docs/EightFace.md b/docs/EightFace.md index cb30d77..71b63e0 100644 --- a/docs/EightFace.md +++ b/docs/EightFace.md @@ -95,4 +95,5 @@ With the option "Autoload first preset" on the context menu you can autoload the - Added retrigger-function for CV-input channel 2 in C4 mode (#330) - Fixed unconnected modules after patch reload (#338) - Fixed broken reset-behavior for "Trigger forward", "Trigger reverse" and "Trigger pingpong" (#347) - - Added missing reset-handling for "Trigger alternating" and "Trigger shuffle" \ No newline at end of file + - Added missing reset-handling for "Trigger alternating" and "Trigger shuffle" + - Allow disabling of "long press" for changing the number of active slots (#354) \ No newline at end of file diff --git a/docs/EightFaceMk2.md b/docs/EightFaceMk2.md index 01cf4a5..7763bd9 100644 --- a/docs/EightFaceMk2.md +++ b/docs/EightFaceMk2.md @@ -97,7 +97,7 @@ Modes for _CV_ on the contextual menu: ## +8 expander -8FACE mk2 provides 8 snapshot-slots and supports extending this number with +8 expanders: The expander must be placed on the right side of 8FACE mk2. Up to seven instances of +8 can be added to one instance of 8FACE mk2, providing 8 * 8 = 64 snapshot-slots in total. +8FACE mk2 provides 8 snapshot-slots and supports extending this number with +8 expanders: The expander must be placed on the right side of 8FACE mk2. Up to fifeteen instances of +8 can be added to one instance of 8FACE mk2, providing 8 * 16 = 128 snapshot-slots in total. Once placed next to 8FACE mk2 the expander works and behaves the same way 8FACE mk2 does and the setup is done analogously. +8 itself provides no further options. ![+8 expander](./EightFaceMk2-8.gif) @@ -120,4 +120,10 @@ Once placed next to 8FACE mk2 the expander works and behaves the same way 8FACE - Added retrigger-function for CV-input channel 2 in C4 mode (#330) - Added "Bind module (select multiple)" option (#291) - Fixed broken reset-behavior for "Trigger forward", "Trigger reverse" and "Trigger pingpong" (#347) - - Added missing reset-handling for "Trigger alternating" and "Trigger shuffle" \ No newline at end of file + - Added missing reset-handling for "Trigger alternating" and "Trigger shuffle" + - Added missing reset-handling for "Trigger random", "Trigger pseudo-random" and "Trigger random walk" + - Allow disabling of "long press" for changing the number of active slots (#354) + - Added HSL color picker for bound modules' box + - Fixed broken module-id mapping when adding using STRIP or STRIP++ + - Fixed crash while exceding 0..10V in Volt-mode (#377) + - Increased maximum number of expanders to 15 \ No newline at end of file diff --git a/docs/Glue.md b/docs/Glue.md index 099664a..26c1930 100644 --- a/docs/Glue.md +++ b/docs/Glue.md @@ -70,4 +70,5 @@ The context menu option "Duplicate" arms the labeling mode. Click on any module - Added option to consolidate all GLUE modules into the current one - v2.0.0 - Changed "Add label" hotkey from Ctrl+A to Ctrl+G (#305) - - Added hotkey Ctrl+Shift+G for "Lock" \ No newline at end of file + - Added hotkey Ctrl+Shift+G for "Lock" + - Added HSL color picker \ No newline at end of file diff --git a/docs/Goto.md b/docs/Goto.md index 49b44a3..b0c58d3 100644 --- a/docs/Goto.md +++ b/docs/Goto.md @@ -35,4 +35,5 @@ It is possible to trigger a view-port change by CV which is especially useful wi - v2.0.0 - Added "top left" as a modules reference point for jump destination - Removed setting "Center module" as the disabled state did not work correctly - - Fixed crash on patch-loading inside Rack VST (#342) \ No newline at end of file + - Fixed crash on patch-loading inside Rack VST (#342) + - Fixed broken zoom behavior when jumping by buttons on the panel \ No newline at end of file diff --git a/docs/MidiCat.md b/docs/MidiCat.md index 2c78a87..7658519 100644 --- a/docs/MidiCat.md +++ b/docs/MidiCat.md @@ -191,7 +191,7 @@ Additionally MIDI-CAT CTX provides a button for activating mapping on the first CLK is a companion module for MIDI-CAT which provides four additional clock-inputs. These clock-inputs can be used to time-quantize the incoming MIDI messages to running clocks or triggers or gates: When enabled the incoming MIDI message is buffered and applied to the parameter on the next trigger. CLK for MIDI-CAT must be placed on the right side of MIDI-CAT and can be used together with the other expanders. -![CTX workflow](./MidiCat-Clk.png) +![CLK workflow](./MidiCat-Clk.png) There are two modes with different behavior for MIDI-feedback: diff --git a/docs/Strip.md b/docs/Strip.md index ca96d02..febe4fa 100644 --- a/docs/Strip.md +++ b/docs/Strip.md @@ -82,6 +82,8 @@ The file-format "vcvss" for storing strips is very close to Rack's own format fo - Fixed high CPU usage in High/Low-mode for bypass - Remember last used folder for strips and selections on dialogs (#307) - "randomizeEnabled" of parameters is respected when randomizing (#349) + - Fixed crash in rare cases (Surge-modules) (#366) + - Fixed wrong hotkey modifier on Mac (Ctrl instead of Cmd) on Cmd+Shift+L # stoermelder STRIP-BAY diff --git a/docs/Stroke.md b/docs/Stroke.md index 3d0a8e5..e3756cc 100644 --- a/docs/Stroke.md +++ b/docs/Stroke.md @@ -35,6 +35,10 @@ The module also supports mouse-button events: If your mouse has more than three Centers the module on the screen which is currently hovered by the mouse pointer. The zoom level can be user-defined in the context menu. - **Zoom level to module (smooth)** (added in v1.9.0) Same as "Zoom level to module" but changes the view smoothly. +- **Zoom to specific module** (added in v2.0.0) + Centers a specific module of the patch on the screen. Before this command can be used a module has to be "learned" by the option in the context sub-menu and pointing to a module. While learning a module is active the slot display turns red temporarily. +- **Zoom to specific module (smooth)** (added in v2.0.0) + Same as "Zoom to specific module" but changes the view smoothly. - **Zoom out** Zooms out the current view so that everything fits on the screen. - **Zoom out (smooth)** (added in v1.9.0) @@ -122,4 +126,7 @@ The module also supports mouse-button events: If your mouse has more than three - v1.10.0 - Improved behavior of parameter copy/paste commands (#273) - v2.0.0 - - Added commands "Add random module", "Save module preset" and "Save module default preset" (#345) \ No newline at end of file + - Added commands "Add random module", "Save module preset" and "Save module default preset" (#345) + - Added commands "Zoom to specific module" and "Zoom to specific module (smooth)" (#357) + - Fixed wrong hotkey modifier on Mac (Ctrl instead of Cmd) + - Fixed broken "Zoom to module" and "Zoom toggle" commands (#382) \ No newline at end of file diff --git a/docs/Transit.md b/docs/Transit.md index f4b44c7..5076464 100644 --- a/docs/Transit.md +++ b/docs/Transit.md @@ -128,7 +128,7 @@ Note: These modes are unavailable if _SEL_-port operates in Phase-mode. ## +T expander -TRANSIT provides 12 snapshot-slots and supports extending this number with +T expanders: The expander must be placed on the right side of TRANSIT. Up to seven instances of +T can be added to one instance of TRANSIT, providing 12 * 8 = 96 snapshot-slots in total. +TRANSIT provides 12 snapshot-slots and supports extending this number with +T expanders: The expander must be placed on the right side of TRANSIT. Up to fifeteen instances of +T can be added to one instance of TRANSIT, providing 12 * 16 = 192 snapshot-slots in total. Once placed next to TRANSIT the expander works and behaves the same way TRANSIT does and the setup is done analogously. +T itself provides no further options. ![+T expander](./Transit-t.gif) @@ -165,4 +165,7 @@ Once placed next to TRANSIT the expander works and behaves the same way TRANSIT - Fixed premature end of processing and not reaching stored snapshot state (#329) - Fixed broken Auto/Write-modes if CV-port is set to "Phase" (#282) - Fixed broken reset-behavior for "Trigger forward", "Trigger reverse" and "Trigger pingpong" (#347) - - Added missing reset-handling for "Trigger alternating" and "Trigger shuffle" \ No newline at end of file + - Added missing reset-handling for "Trigger alternating" and "Trigger shuffle" + - Added missing reset-handling for "Trigger random", "Trigger pseudo-random" and "Trigger random walk" + - Allow disabling of "long press" for changing the number of active slots (#354) + - Increased maximum number of expanders to 15 (#381) \ No newline at end of file diff --git a/plugin.json b/plugin.json index a4b2539..33fc9e6 100644 --- a/plugin.json +++ b/plugin.json @@ -1,6 +1,6 @@ { "slug": "Stoermelder-P1", - "version": "2.0.beta3", + "version": "2.0.beta4", "license": "GPL-3.0-or-later", "author": "Benjamin Dill", "name": "PackOne", @@ -359,7 +359,7 @@ { "slug": "Dirt", "name": "DIRT", - "description": "Crosstalk and noise for polyphonic cables (preview)", + "description": "Crosstalk and noise for polyphonic cables", "tags": ["Polyphonic"], "manualUrl": "https://github.com/stoermelder/vcvrack-packone/blob/v2/docs/Dirt.md" }, diff --git a/res-src/MidiCat.afdesign b/res-src/MidiCat.afdesign index a6485ca..29a2012 100644 Binary files a/res-src/MidiCat.afdesign and b/res-src/MidiCat.afdesign differ diff --git a/res-src/dark/MidiCat.afdesign b/res-src/dark/MidiCat.afdesign index 7127673..e48014a 100644 Binary files a/res-src/dark/MidiCat.afdesign and b/res-src/dark/MidiCat.afdesign differ diff --git a/res/MidiCat.svg b/res/MidiCat.svg index b6a91bd..525025e 100644 --- a/res/MidiCat.svg +++ b/res/MidiCat.svg @@ -5,10 +5,19 @@ + + + + + + + + + - + @@ -17,13 +26,13 @@ - + - + @@ -40,19 +49,10 @@ - + - - - - - - - - - @@ -61,7 +61,7 @@ - + @@ -73,10 +73,10 @@ - + - + @@ -84,13 +84,13 @@ - + - + - + @@ -105,10 +105,10 @@ - + - + @@ -120,43 +120,80 @@ - + - + - + - + - + - + - + - + - + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/res/dark/MidiCat.svg b/res/dark/MidiCat.svg index b7de4b3..db7ad7b 100644 --- a/res/dark/MidiCat.svg +++ b/res/dark/MidiCat.svg @@ -17,7 +17,7 @@ - + @@ -26,13 +26,13 @@ - + - + @@ -49,7 +49,7 @@ - + @@ -61,7 +61,7 @@ - + @@ -73,10 +73,10 @@ - + - + @@ -84,13 +84,13 @@ - + - + - + @@ -105,10 +105,10 @@ - + - + @@ -120,43 +120,80 @@ - + - + - + - + - + - + - + - + - + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/scripts/plugin-builder.sh b/scripts/plugin-builder.sh deleted file mode 100644 index 81f94f7..0000000 --- a/scripts/plugin-builder.sh +++ /dev/null @@ -1,195 +0,0 @@ -#!/bin/bash - -set -e -set -o xtrace -RACK_FROM_SOURCE=1 - -help_message() -{ - cat < $tf -mv $tf plugin.json \ No newline at end of file diff --git a/src/EightFace.cpp b/src/EightFace.cpp index 933a094..e511c72 100644 --- a/src/EightFace.cpp +++ b/src/EightFace.cpp @@ -34,7 +34,7 @@ enum class CTRLMODE { WRITE }; -template < int NUM_PRESETS > +template struct EightFaceModule : Module { enum ParamIds { CTRLMODE_PARAM, @@ -80,10 +80,14 @@ struct EightFaceModule : Module { /** [Stored to JSON] */ json_t* presetSlot[NUM_PRESETS]; + const int presetMax = NUM_PRESETS; /** [Stored to JSON] */ int preset = 0; /** [Stored to JSON] */ int presetCount = NUM_PRESETS; + /** [Stored to JSON] */ + bool presetCountLongPress = true; + /** [Stored to JSON] */ AUTOLOAD autoload = AUTOLOAD::OFF; @@ -326,9 +330,11 @@ struct EightFaceModule : Module { case LongPressButton::NO_PRESS: break; case LongPressButton::SHORT_PRESS: - presetLoad(t, i, slotCvMode == SLOTCVMODE::ARM, true); break; + presetLoad(t, i, slotCvMode == SLOTCVMODE::ARM, true); + break; case LongPressButton::LONG_PRESS: - presetSetCount(i + 1); break; + if (presetCountLongPress) presetSetCount(i + 1); + break; } } } @@ -343,9 +349,11 @@ struct EightFaceModule : Module { case LongPressButton::NO_PRESS: break; case LongPressButton::SHORT_PRESS: - presetSave(t, i); break; + presetSave(t, i); + break; case LongPressButton::LONG_PRESS: - presetClear(i); break; + presetClear(i); + break; } } } @@ -497,6 +505,7 @@ struct EightFaceModule : Module { json_object_set_new(rootJ, "slotCvMode", json_integer((int)slotCvMode)); json_object_set_new(rootJ, "preset", json_integer(preset)); json_object_set_new(rootJ, "presetCount", json_integer(presetCount)); + json_object_set_new(rootJ, "presetCountLongPress", json_boolean(presetCountLongPress)); json_t* presetsJ = json_array(); for (int i = 0; i < NUM_PRESETS; i++) { @@ -531,6 +540,8 @@ struct EightFaceModule : Module { slotCvMode = (SLOTCVMODE)json_integer_value(json_object_get(rootJ, "slotCvMode")); preset = json_integer_value(json_object_get(rootJ, "preset")); presetCount = json_integer_value(json_object_get(rootJ, "presetCount")); + json_t* presetCountLongPressJ = json_object_get(rootJ, "presetCountLongPress"); + if (presetCountLongPressJ) presetCountLongPress = json_boolean_value(presetCountLongPressJ); for (int i = 0; i < NUM_PRESETS; i++) { if (presetSlotUsed[i]) { @@ -588,8 +599,65 @@ struct WhiteRedLight : GrayModuleLightWidget { }; -template < typename MODULE > +template struct EightFaceWidgetTemplate : ModuleWidget { + struct NumberOfSlotsSlider : ui::Slider { + struct NumberOfSlotsQuantity : Quantity { + MODULE* module; + float v = -1.f; + + NumberOfSlotsQuantity(MODULE* module) { + this->module = module; + } + void setValue(float value) override { + v = clamp(value, 1.f, float(module->presetMax)); + module->presetSetCount(int(v)); + } + float getValue() override { + if (v < 0.f) v = module->presetCount; + return v; + } + float getDefaultValue() override { + return 8.f; + } + float getMinValue() override { + return 1.f; + } + float getMaxValue() override { + return float(module->presetMax); + } + float getDisplayValue() override { + return getValue(); + } + std::string getDisplayValueString() override { + int i = int(getValue()); + return string::f("%i", i); + } + void setDisplayValue(float displayValue) override { + setValue(displayValue); + } + std::string getLabel() override { + return "Slots"; + } + std::string getUnit() override { + return ""; + } + }; + + NumberOfSlotsSlider(MODULE* module) { + box.size.x = 160.0; + quantity = new NumberOfSlotsQuantity(module); + } + ~NumberOfSlotsSlider() { + delete quantity; + } + void onDragMove(const event::DragMove& e) override { + if (quantity) { + quantity->moveScaledValue(0.002f * e.mouseDelta.x); + } + } + }; + void appendContextMenu(Menu* menu) override { MODULE* module = dynamic_cast(this->module); assert(module); @@ -601,8 +669,15 @@ struct EightFaceWidgetTemplate : ModuleWidget { } menu->addChild(new MenuSeparator()); + menu->addChild(createSubmenuItem("Number of slots", string::f("%i", module->presetCount), + [=](Menu* menu) { + menu->addChild(new NumberOfSlotsSlider(module)); + menu->addChild(createBoolPtrMenuItem("Set by long-press", "", &module->presetCountLongPress)); + } + )); + menu->addChild(createSubmenuItem("Port SLOT mode", "", - [=](Menu* menu) { + [=](Menu* menu) { struct SlotCvModeItem : MenuItem { MODULE* module; SLOTCVMODE slotCvMode; diff --git a/src/EightFaceMk2.cpp b/src/EightFaceMk2.cpp index e3c1a8e..02b2d2a 100644 --- a/src/EightFaceMk2.cpp +++ b/src/EightFaceMk2.cpp @@ -3,7 +3,9 @@ #include "helpers/TaskWorker.hpp" #include "components/MenuColorLabel.hpp" #include "components/MenuColorField.hpp" +#include "components/MenuColorPicker.hpp" #include "ui/ModuleSelectProcessor.hpp" +#include "ui/ViewportHelper.hpp" #include "EightFace.hpp" #include "EightFaceMk2Base.hpp" #include @@ -27,7 +29,7 @@ std::string trim(const std::string& s) { return rtrim(ltrim(s)); } -const int MAX_EXPANDERS = 7; +const int MAX_EXPANDERS = 15; enum class SLOTCVMODE { OFF = -1, @@ -72,6 +74,8 @@ struct EightFaceMk2Module : EightFaceMk2Base { int preset; /** [Stored to JSON] Number of currently active snapshots */ int presetCount; + /** [Stored to JSON] */ + bool presetCountLongPress = true; /** Total number of snapshots including expanders */ int presetTotal; @@ -258,6 +262,9 @@ struct EightFaceMk2Module : EightFaceMk2Base { resetTimer.reset(); switch (slotCvMode) { case SLOTCVMODE::TRIG_FWD: + case SLOTCVMODE::TRIG_RANDOM: + case SLOTCVMODE::TRIG_RANDOM_WALK: + case SLOTCVMODE::TRIG_RANDOM_WO_REPEAT: presetLoad(0); break; case SLOTCVMODE::TRIG_REV: @@ -287,7 +294,7 @@ struct EightFaceMk2Module : EightFaceMk2Base { if (Module::inputs[INPUT_CV].isConnected()) { switch (slotCvMode) { case SLOTCVMODE::VOLT: - presetLoad(std::floor(rescale(Module::inputs[INPUT_CV].getVoltage(), 0.f, 10.f, 0, presetCount))); + presetLoad(std::floor(rescale(clamp(Module::inputs[INPUT_CV].getVoltage(), 0.f, 10.f - 1e-6f), 0.f, 10.f, 0, presetCount))); break; case SLOTCVMODE::C4: presetLoad(std::round(clamp(Module::inputs[INPUT_CV].getVoltage() * 12.f, 0.f, presetTotal - 1.f))); @@ -390,9 +397,11 @@ struct EightFaceMk2Module : EightFaceMk2Base { case LongPressButton::NO_PRESS: break; case LongPressButton::SHORT_PRESS: - presetLoad(i, slotCvMode == SLOTCVMODE::ARM, true); break; + presetLoad(i, slotCvMode == SLOTCVMODE::ARM, true); + break; case LongPressButton::LONG_PRESS: - presetSetCount(i + 1); break; + if (presetCountLongPress) presetSetCount(i + 1); + break; } } } @@ -409,9 +418,11 @@ struct EightFaceMk2Module : EightFaceMk2Base { case LongPressButton::NO_PRESS: break; case LongPressButton::SHORT_PRESS: - presetSave(i); break; + presetSave(i); + break; case LongPressButton::LONG_PRESS: - presetClear(i); break; + presetClear(i); + break; } } } @@ -685,6 +696,7 @@ struct EightFaceMk2Module : EightFaceMk2Base { json_object_set_new(rootJ, "slotCvMode", json_integer((int)slotCvMode)); json_object_set_new(rootJ, "preset", json_integer(preset)); json_object_set_new(rootJ, "presetCount", json_integer(presetCount)); + json_object_set_new(rootJ, "presetCountLongPress", json_boolean(presetCountLongPress)); json_object_set_new(rootJ, "boxDraw", json_boolean(boxDraw)); json_object_set_new(rootJ, "boxColor", json_string(color::toHexString(boxColor).c_str())); @@ -709,6 +721,8 @@ struct EightFaceMk2Module : EightFaceMk2Base { slotCvMode = (SLOTCVMODE)json_integer_value(json_object_get(rootJ, "slotCvMode")); preset = json_integer_value(json_object_get(rootJ, "preset")); presetCount = json_integer_value(json_object_get(rootJ, "presetCount")); + json_t* presetCountLongPressJ = json_object_get(rootJ, "presetCountLongPress"); + if (presetCountLongPressJ) presetCountLongPress = json_boolean_value(presetCountLongPressJ); boxDraw = json_boolean_value(json_object_get(rootJ, "boxDraw")); json_t* boxColorJ = json_object_get(rootJ, "boxColor"); @@ -751,7 +765,6 @@ struct EightFaceMk2Module : EightFaceMk2Base { } inChange = false; - BASE::idFixClearMap(); BASE::dataFromJson(rootJ); Module::params[PARAM_RW].setValue(0.f); @@ -902,6 +915,63 @@ struct EightFaceMk2Widget : ThemedModuleWidget> MODULE* module = dynamic_cast(this->module); assert(module); + struct NumberOfSlotsSlider : ui::Slider { + struct NumberOfSlotsQuantity : Quantity { + MODULE* module; + float v = -1.f; + + NumberOfSlotsQuantity(MODULE* module) { + this->module = module; + } + void setValue(float value) override { + v = clamp(value, 1.f, float(module->presetTotal)); + module->presetSetCount(int(v)); + } + float getValue() override { + if (v < 0.f) v = module->presetCount; + return v; + } + float getDefaultValue() override { + return 8.f; + } + float getMinValue() override { + return 1.f; + } + float getMaxValue() override { + return float(module->presetTotal); + } + float getDisplayValue() override { + return getValue(); + } + std::string getDisplayValueString() override { + int i = int(getValue()); + return string::f("%i", i); + } + void setDisplayValue(float displayValue) override { + setValue(displayValue); + } + std::string getLabel() override { + return "Slots"; + } + std::string getUnit() override { + return ""; + } + }; + + NumberOfSlotsSlider(MODULE* module) { + box.size.x = 160.0; + quantity = new NumberOfSlotsQuantity(module); + } + ~NumberOfSlotsSlider() { + delete quantity; + } + void onDragMove(const event::DragMove& e) override { + if (quantity) { + quantity->moveScaledValue(0.002f * e.mouseDelta.x); + } + } + }; + struct SlotCvModeMenuItem : MenuItem { struct SlotCvModeItem : MenuItem { MODULE* module; @@ -1041,25 +1111,23 @@ struct EightFaceMk2Widget : ThemedModuleWidget> rightText = RIGHT_ARROW; } Menu* createChildMenu() override { - struct ColorField : MenuColorField { - MODULE* module; - NVGcolor initColor() override { - return module->boxColor; - } - void returnColor(NVGcolor color) override { - module->boxColor = color; - } - }; - Menu* menu = new Menu; - MenuColorLabel* colorLabel = construct(&MenuColorLabel::fillColor, module->boxColor); - menu->addChild(colorLabel); - menu->addChild(construct(&ColorField::module, module, &MenuColorField::colorLabel, colorLabel)); + menu->addChild(construct(&MenuColorLabel::fillColor, &module->boxColor)); + menu->addChild(new MenuSeparator); + menu->addChild(construct(&MenuColorPicker::color, &module->boxColor)); + menu->addChild(new MenuSeparator); + menu->addChild(construct(&MenuColorField::color, &module->boxColor)); return menu; } }; menu->addChild(new MenuSeparator()); + menu->addChild(createSubmenuItem("Number of slots", string::f("%i", module->presetCount), + [=](Menu* menu) { + menu->addChild(new NumberOfSlotsSlider(module)); + menu->addChild(createBoolPtrMenuItem("Set by long-press", "", &module->presetCountLongPress)); + } + )); menu->addChild(construct(&MenuItem::text, "Port CV mode", &SlotCvModeMenuItem::module, module)); //menu->addChild(construct(&MenuItem::text, "Autoload", &AutoloadMenuItem::module, module)); menu->addChild(new MenuSeparator()); diff --git a/src/EightFaceMk2Base.hpp b/src/EightFaceMk2Base.hpp index f6374f5..9f1e699 100644 --- a/src/EightFaceMk2Base.hpp +++ b/src/EightFaceMk2Base.hpp @@ -96,10 +96,17 @@ struct EightFaceMk2Base : Module, StripIdFixModule { json_t* vJ; size_t j; json_array_foreach(slotJ, j, vJ) { - preset[presetIndex].push_back(json_deep_copy(vJ)); + vJ = json_deep_copy(vJ); + json_t* idJ = json_object_get(vJ, "id"); + int64_t moduleId = json_integer_value(idJ); + moduleId = idFix(moduleId); + json_object_set(vJ, "id", json_integer(moduleId)); + preset[presetIndex].push_back(vJ); } } } + + idFixClearMap(); } }; diff --git a/src/Glue.cpp b/src/Glue.cpp index 9b4d0f9..3932be7 100644 --- a/src/Glue.cpp +++ b/src/Glue.cpp @@ -3,6 +3,7 @@ #include "helpers/StripIdFixModule.hpp" #include "components/MenuColorLabel.hpp" #include "components/MenuColorField.hpp" +#include "components/MenuColorPicker.hpp" namespace StoermelderPackOne { namespace Glue { @@ -601,6 +602,7 @@ struct LabelWidget : widget::TransparentWidget { NVGcolor color; void onAction(const event::Action& e) override { label->fontColor = color; + e.unconsume(); } void step() override { rightText = color::toHexString(label->fontColor) == color::toHexString(color) ? "✔" : ""; @@ -608,22 +610,14 @@ struct LabelWidget : widget::TransparentWidget { } }; - struct CustomFontColorField : MenuColorField { - Label* label; - NVGcolor initColor() override { - return label->fontColor; - } - void returnColor(NVGcolor color) override { - label->fontColor = color; - } - }; - Menu* menu = new Menu; - MenuColorLabel* colorLabel = construct(&MenuColorLabel::fillColor, label->fontColor); - menu->addChild(colorLabel); + menu->addChild(construct(&MenuColorLabel::fillColor, &label->fontColor)); + menu->addChild(new MenuSeparator); + menu->addChild(construct(&MenuColorPicker::color, &label->fontColor)); + menu->addChild(new MenuSeparator); menu->addChild(construct(&MenuItem::text, "Black", &FontColorItem::label, label, &FontColorItem::color, LABEL_FONTCOLOR_DEFAULT)); menu->addChild(construct(&MenuItem::text, "White", &FontColorItem::label, label, &FontColorItem::color, LABEL_FONTCOLOR_WHITE)); - menu->addChild(construct(&MenuColorField::colorLabel, colorLabel, &CustomFontColorField::label, label, &MenuColorField::textSelected, textSelected)); + menu->addChild(construct(&MenuColorField::color, &label->fontColor, &MenuColorField::textSelected, textSelected)); return menu; } }; @@ -641,6 +635,7 @@ struct LabelWidget : widget::TransparentWidget { NVGcolor color; void onAction(const event::Action& e) override { label->color = color; + e.unconsume(); } void step() override { rightText = color::toHexString(label->color) == color::toHexString(color) ? "✔" : ""; @@ -648,26 +643,18 @@ struct LabelWidget : widget::TransparentWidget { } }; - struct CustomColorField : MenuColorField { - Label* label; - NVGcolor initColor() override { - return label->color; - } - void returnColor(NVGcolor color) override { - label->color = color; - } - }; - Menu* menu = new Menu; - MenuColorLabel* colorLabel = construct(&MenuColorLabel::fillColor, label->color); - menu->addChild(colorLabel); + menu->addChild(construct(&MenuColorLabel::fillColor, &label->color)); + menu->addChild(new MenuSeparator); + menu->addChild(construct(&MenuColorPicker::color, &label->color)); + menu->addChild(new MenuSeparator); menu->addChild(construct(&MenuItem::text, "Yellow", &ColorItem::label, label, &ColorItem::color, LABEL_COLOR_YELLOW)); menu->addChild(construct(&MenuItem::text, "Red", &ColorItem::label, label, &ColorItem::color, LABEL_COLOR_RED)); menu->addChild(construct(&MenuItem::text, "Cyan", &ColorItem::label, label, &ColorItem::color, LABEL_COLOR_CYAN)); menu->addChild(construct(&MenuItem::text, "Green", &ColorItem::label, label, &ColorItem::color, LABEL_COLOR_GREEN)); menu->addChild(construct(&MenuItem::text, "Pink", &ColorItem::label, label, &ColorItem::color, LABEL_COLOR_PINK)); menu->addChild(construct(&MenuItem::text, "White", &ColorItem::label, label, &ColorItem::color, LABEL_COLOR_WHITE)); - menu->addChild(construct(&MenuColorField::colorLabel, colorLabel, &CustomColorField::label, label, &MenuColorField::textSelected, textSelected)); + menu->addChild(construct(&MenuColorField::color, &label->color, &MenuColorField::textSelected, textSelected)); return menu; } }; @@ -1241,6 +1228,7 @@ struct GlueWidget : ThemedModuleWidget { NVGcolor color; void onAction(const event::Action& e) override { module->defaultFontColor = color; + e.unconsume(); } void step() override { rightText = color::toHexString(module->defaultFontColor) == color::toHexString(color) ? "✔" : ""; @@ -1248,22 +1236,14 @@ struct GlueWidget : ThemedModuleWidget { } }; - struct CustomFontColorField : MenuColorField { - GlueModule* module; - NVGcolor initColor() override { - return module->defaultFontColor; - } - void returnColor(NVGcolor color) override { - module->defaultFontColor = color; - } - }; - Menu* menu = new Menu; - MenuColorLabel* colorLabel = construct(&MenuColorLabel::fillColor, module->defaultFontColor); - menu->addChild(colorLabel); + menu->addChild(construct(&MenuColorLabel::fillColor, &module->defaultFontColor)); + menu->addChild(new MenuSeparator); + menu->addChild(construct(&MenuColorPicker::color, &module->defaultFontColor)); + menu->addChild(new MenuSeparator); menu->addChild(construct(&MenuItem::text, "Black", &FontColorItem::module, module, &FontColorItem::color, LABEL_FONTCOLOR_DEFAULT)); menu->addChild(construct(&MenuItem::text, "White", &FontColorItem::module, module, &FontColorItem::color, LABEL_FONTCOLOR_WHITE)); - menu->addChild(construct(&MenuColorField::colorLabel, colorLabel, &CustomFontColorField::module, module)); + menu->addChild(construct(&MenuColorField::color, &module->defaultFontColor)); return menu; } }; @@ -1280,6 +1260,7 @@ struct GlueWidget : ThemedModuleWidget { NVGcolor color; void onAction(const event::Action& e) override { module->defaultColor = color; + e.unconsume(); } void step() override { rightText = color::toHexString(module->defaultColor) == color::toHexString(color) ? "✔" : ""; @@ -1287,26 +1268,19 @@ struct GlueWidget : ThemedModuleWidget { } }; - struct CustomColorField : MenuColorField { - GlueModule* module; - NVGcolor initColor() override { - return module->defaultColor; - } - void returnColor(NVGcolor color) override { - module->defaultColor = color; - } - }; - Menu* menu = new Menu; - MenuColorLabel* colorLabel = construct(&MenuColorLabel::fillColor, module->defaultColor); + MenuColorLabel* colorLabel = construct(&MenuColorLabel::fillColor, &module->defaultColor); menu->addChild(colorLabel); + menu->addChild(new MenuSeparator); + menu->addChild(construct(&MenuColorPicker::color, &module->defaultColor)); + menu->addChild(new MenuSeparator); menu->addChild(construct(&MenuItem::text, "Yellow", &ColorItem::module, module, &ColorItem::color, LABEL_COLOR_YELLOW)); menu->addChild(construct(&MenuItem::text, "Red", &ColorItem::module, module, &ColorItem::color, LABEL_COLOR_RED)); menu->addChild(construct(&MenuItem::text, "Cyan", &ColorItem::module, module, &ColorItem::color, LABEL_COLOR_CYAN)); menu->addChild(construct(&MenuItem::text, "Green", &ColorItem::module, module, &ColorItem::color, LABEL_COLOR_GREEN)); menu->addChild(construct(&MenuItem::text, "Pink", &ColorItem::module, module, &ColorItem::color, LABEL_COLOR_PINK)); menu->addChild(construct(&MenuItem::text, "White", &ColorItem::module, module, &ColorItem::color, LABEL_COLOR_WHITE)); - menu->addChild(construct(&MenuColorField::colorLabel, colorLabel, &CustomColorField::module, module)); + menu->addChild(construct(&MenuColorField::color, &module->defaultColor)); return menu; } }; diff --git a/src/Goto.cpp b/src/Goto.cpp index 6e71165..9058142 100644 --- a/src/Goto.cpp +++ b/src/Goto.cpp @@ -1,4 +1,5 @@ #include "plugin.hpp" +#include "ui/ViewportHelper.hpp" namespace StoermelderPackOne { namespace Goto { @@ -232,37 +233,51 @@ struct GotoContainer : widget::Widget { } } + void triggerJump(int i) { + module->jumpTrigger = i; + } + void executeJump(int i) { if (module->jumpPoints[i].moduleId >= 0) { ModuleWidget* mw = APP->scene->rack->getModule(module->jumpPoints[i].moduleId); if (mw) { + float zoom = !module->ignoreZoom ? module->jumpPoints[i].zoom : std::log2(APP->scene->rackScroll->getZoom()); if (module->smoothTransition) { - float zoom = !module->ignoreZoom ? module->jumpPoints[i].zoom : std::log2(APP->scene->rackScroll->getZoom()); switch (module->jumpPos) { - case JUMPPOS::ABSOLUTE: + case JUMPPOS::ABSOLUTE: { viewportCenterSmooth.trigger(Vec(module->jumpPoints[i].x, module->jumpPoints[i].y), zoom, 1.f / APP->window->getLastFrameDuration()); break; - case JUMPPOS::MODULE_CENTER: - viewportCenterSmooth.trigger(mw, zoom, 1.f / APP->window->getLastFrameDuration()); + } + case JUMPPOS::MODULE_CENTER: { + //viewportCenterSmooth.trigger(mw, zoom, 1.f / APP->window->getLastFrameDuration()); + Vec source = APP->scene->rackScroll->offset / APP->scene->rackScroll->getZoom(); + Vec center = APP->scene->rackScroll->getSize() * (1.f / APP->scene->rackScroll->getZoom()) * 0.5f; + Vec p1 = source + center; + Vec p2 = mw->getBox().getCenter(); + float f = sqrt((p1.x - p2.x) * (p1.x - p2.x) + (p1.y - p2.y) * (p1.y - p2.y)); + viewportCenterSmooth.trigger(mw, zoom, 1.f / APP->window->getLastFrameDuration(), f / 1000.f); break; - case JUMPPOS::MODULE_TOPLEFT: + } + case JUMPPOS::MODULE_TOPLEFT: { + // not implemented break; + } } } else { switch (module->jumpPos) { - case JUMPPOS::ABSOLUTE: + case JUMPPOS::ABSOLUTE: { StoermelderPackOne::Rack::ViewportCenter{Vec(module->jumpPoints[i].x, module->jumpPoints[i].y)}; break; - case JUMPPOS::MODULE_CENTER: - StoermelderPackOne::Rack::ViewportCenter{mw}; + } + case JUMPPOS::MODULE_CENTER: { + StoermelderPackOne::Rack::ViewportCenter{mw, -1.f, zoom}; break; - case JUMPPOS::MODULE_TOPLEFT: - StoermelderPackOne::Rack::ViewportTopLeft{mw}; + } + case JUMPPOS::MODULE_TOPLEFT: { + StoermelderPackOne::Rack::ViewportTopLeft{mw, -1.f, zoom}; break; - } - if (!module->ignoreZoom) { - APP->scene->rackScroll->setZoom(std::pow(2.f, module->jumpPoints[i].zoom)); + } } } } @@ -301,7 +316,7 @@ struct GotoButton : LEDButton { case LongPressButton::NO_PRESS: break; case LongPressButton::SHORT_PRESS: - gotoContainer->executeJump(id); + gotoContainer->triggerJump(id); break; case LongPressButton::LONG_PRESS: gotoContainer->learnJump(id); diff --git a/src/Grip.cpp b/src/Grip.cpp index c333278..ac168ad 100644 --- a/src/Grip.cpp +++ b/src/Grip.cpp @@ -57,16 +57,18 @@ struct GripModule : CVMapModuleBase { ParamQuantity* paramQuantity = getParamQuantity(i); if (paramQuantity == NULL) continue; - // Set ParamQuantity - //paramQuantity->setScaledValue(lastValue[i]); - float vScaled = math::rescale(lastValue[i], 0.f, 1.f, paramQuantity->getMinValue(), paramQuantity->getMaxValue()); - paramQuantity->getParam()->setValue(vScaled); + if (paramQuantity->getScaledValue() != lastValue[i]) { + // Set ParamQuantity + paramQuantity->setScaledValue(lastValue[i]); + } } } if (lightDivider.process()) { lights[LIGHT_BIND].setBrightness(learningId >= 0 ? 1.f : 0.f); } + + CVMapModuleBase::process(args); } void commitLearn() override { diff --git a/src/Hive.cpp b/src/Hive.cpp index 64731d2..fbbc1f5 100644 --- a/src/Hive.cpp +++ b/src/Hive.cpp @@ -211,7 +211,7 @@ struct HiveModule : Module { grid.cursor[i].pos.r = grid.cursor[i].startPos.r = (grid.usedRadius + 1) / NUM_PORTS * i; /// Divide across SW edge grid.cursor[i].dir = grid.cursor[i].startDir = DIRECTION::NE; grid.cursor[i].turnMode = TURNMODE::SIXTY; /// Start with small turns - grid.cursor[i].ninetyState = TURNMODE::SIXTY; /// Turnmode 90 starts with a small turn first + grid.cursor[i].ninetyState = TURNMODE::SIXTY; /// Turnmode 90 starts with a small turn first grid.cursor[i].outMode = OUTMODE::UNI_3V; resetTimer[i].reset(); grid.cursor[i].ratchetingEnabled = RATCHETMODE::DEFAULT; diff --git a/src/IntermixBase.hpp b/src/IntermixBase.hpp index 33d9f19..88bd1e0 100644 --- a/src/IntermixBase.hpp +++ b/src/IntermixBase.hpp @@ -6,7 +6,7 @@ namespace Intermix { template struct IntermixBase { - typedef float (*IntermixMatrix)[PORTS]; + typedef float (*IntermixMatrix)[PORTS]; virtual IntermixMatrix expGetCurrentMatrix() { return NULL; } virtual int expGetChannelCount() { return 0; } virtual void expSetFade(int i, float* fadeIn, float* fadeOut) { } diff --git a/src/Mb.cpp b/src/Mb.cpp index 6936a27..d1c20dc 100644 --- a/src/Mb.cpp +++ b/src/Mb.cpp @@ -151,6 +151,25 @@ BrowserOverlay::BrowserOverlay() { mbWidgetBackup->hide(); APP->scene->removeChild(mbWidgetBackup); + // Clear all framebuffers of the default module browser - if Rack is shut down after adding MB + // the default module browser is deleted after the window and the GL context has been destroyed + // This causes Rack crashing on the Framebuffer's destruction. + std::list l; + l.push_back(mbWidgetBackup); + while (!l.empty()) { + Widget* w = l.front(); + l.pop_front(); + FramebufferWidget* fb = dynamic_cast(w); + if (fb) { + fb->setDirty(); + fb->deleteFramebuffer(); + } + for (Widget* _w : w->children) { + l.push_back(_w); + } + } + + mbV06 = new v06::ModuleBrowser; addChild(mbV06); diff --git a/src/MidiKey.cpp b/src/MidiKey.cpp index 16a05b0..464608b 100644 --- a/src/MidiKey.cpp +++ b/src/MidiKey.cpp @@ -2,6 +2,7 @@ #include "components/MidiWidget.hpp" #include "ui/keyboard.hpp" #include "ui/ModuleSelectProcessor.hpp" +#include "ui/ViewportHelper.hpp" namespace StoermelderPackOne { namespace MidiKey { @@ -293,19 +294,21 @@ struct MidiKeyModule : Module { // Skip duplicate events if ((value > 0 && slot[id].active) || (value == 0 && !slot[id].active)) return; - event::HoverKey e; - e.key = slot[id].key; - e.scancode = glfwGetKeyScancode(e.key); - e.keyName = glfwGetKeyName(e.key, e.scancode); - e.action = value > 0 ? GLFW_PRESS : GLFW_RELEASE; - e.mods = 0; - if (slot[ID_CTRL].active || (slot[id].mods & RACK_MOD_CTRL)) - e.mods = e.mods | RACK_MOD_CTRL; - if (slot[ID_ALT].active || (slot[id].mods & GLFW_MOD_ALT)) - e.mods = e.mods | GLFW_MOD_ALT; - if (slot[ID_SHIFT].active || (slot[id].mods & GLFW_MOD_SHIFT)) - e.mods = e.mods | GLFW_MOD_SHIFT; - keyEventQueue.push(std::make_tuple(e, slot[id].moduleId)); + if (slot[id].key != -1) { + event::HoverKey e; + e.key = slot[id].key; + e.scancode = glfwGetKeyScancode(e.key); + e.keyName = glfwGetKeyName(e.key, e.scancode); + e.action = value > 0 ? GLFW_PRESS : GLFW_RELEASE; + e.mods = 0; + if (slot[ID_CTRL].active || (slot[id].mods & RACK_MOD_CTRL)) + e.mods = e.mods | RACK_MOD_CTRL; + if (slot[ID_ALT].active || (slot[id].mods & GLFW_MOD_ALT)) + e.mods = e.mods | GLFW_MOD_ALT; + if (slot[ID_SHIFT].active || (slot[id].mods & GLFW_MOD_SHIFT)) + e.mods = e.mods | GLFW_MOD_SHIFT; + keyEventQueue.push(std::make_tuple(e, slot[id].moduleId)); + } slot[id].active = value > 0; return; } diff --git a/src/MidiMon.cpp b/src/MidiMon.cpp index 975361e..6ea64b1 100644 --- a/src/MidiMon.cpp +++ b/src/MidiMon.cpp @@ -1,6 +1,7 @@ #include "plugin.hpp" #include "components/LedTextDisplay.hpp" #include "components/MidiWidget.hpp" +#include "components/LogDisplay.hpp" #include #include @@ -249,47 +250,8 @@ struct MidiMonModule : Module { }; -struct MidiDisplay : LedTextDisplay { - MidiMonModule* module; - std::list>* buffer; - bool dirty = true; - - MidiDisplay() { - color = nvgRGB(0xf0, 0xf0, 0xf0); - bgColor.a = 0.f; - fontSize = 9.2f; - textOffset.y += 2.f; - } - - void step() override { - LedTextDisplay::step(); - if (dirty) { - text = ""; - size_t size = std::min(buffer->size(), (size_t)(box.size.x / fontSize - 1)); - size_t i = 0; - for (std::tuple s : *buffer) { - if (i >= size) break; - float timestamp = std::get<0>(s); - if (timestamp >= 0.f) { - text += string::f("[%9.4f] %s\n", timestamp, std::get<1>(s).c_str()); - } - else { - text += string::f("%s\n", std::get<1>(s).c_str()); - } - i++; - } - } - } - - void reset() { - buffer->clear(); - dirty = true; - } -}; - - struct MidiMonWidget : ThemedModuleWidget { - MidiDisplay* textField; + LogDisplay* logDisplay; std::list> buffer; MidiMonWidget(MidiMonModule* module) @@ -310,11 +272,10 @@ struct MidiMonWidget : ThemedModuleWidget { textDisplay->box.size = Vec(219.9f, 234.1f); addChild(textDisplay); - textField = createWidget(Vec()); - textField->module = module; - textField->buffer = &buffer; - textField->box.size = textDisplay->box.size.minus(Vec(0.f, 4.f)); - textDisplay->addChild(textField); + logDisplay = createWidget(Vec()); + logDisplay->buffer = &buffer; + logDisplay->box.size = textDisplay->box.size.minus(Vec(0.f, 4.f)); + textDisplay->addChild(logDisplay); } void step() override { @@ -325,7 +286,7 @@ struct MidiMonWidget : ThemedModuleWidget { if (buffer.size() == BUFFERSIZE) buffer.pop_back(); std::tuple s = module->midiLogMessages.shift(); buffer.push_front(s); - textField->dirty = true; + logDisplay->dirty = true; } } @@ -356,7 +317,7 @@ struct MidiMonWidget : ThemedModuleWidget { void resetLog() { buffer.clear(); module->resetTimestamp(); - textField->reset(); + logDisplay->reset(); } void exportLog(std::string filename) { diff --git a/src/RotorA.cpp b/src/RotorA.cpp index d479437..14d26cf 100644 --- a/src/RotorA.cpp +++ b/src/RotorA.cpp @@ -80,7 +80,7 @@ struct RotorAModule : Module { float mod = clamp(inputs[MOD_INPUT].getVoltage(), 0.f, 10.f); float mod_p = mod / channelsSplit; - int mod_c = floor(mod_p); + int mod_c = std::min(std::max((int)floor(mod_p), 0), 14); float mod_p2 = mod_p - (float)mod_c; float mod_p1 = 1.f - mod_p2; diff --git a/src/Sail.cpp b/src/Sail.cpp index 7d198c4..a831169 100644 --- a/src/Sail.cpp +++ b/src/Sail.cpp @@ -53,6 +53,7 @@ struct SailModule : Module { uint16_t overlayMessageId = 0; bool fineMod; + bool isSwitch; float inVoltBase; float inVoltTarget; @@ -61,10 +62,8 @@ struct SailModule : Module { float valueBaseOut; float valuePrevious; - bool hoveredWidgetNull = true; - WeakPtr hoveredWidget; - int64_t hoveredModuleId = -1; - int hoveredParamId = -1; + ParamQuantity* paramQuantity; + ParamQuantity* paramQuantityPriv; dsp::SchmittTrigger incTrigger; dsp::SchmittTrigger decTrigger; @@ -93,6 +92,7 @@ struct SailModule : Module { void onReset() override { Module::onReset(); + paramQuantity = NULL; inMode = IN_MODE::DIFF; outMode = OUT_MODE::REDUCED; slewLimiter.reset(); @@ -111,24 +111,18 @@ struct SailModule : Module { incdecTarget -= step; } - ParamQuantity* paramQuantity = NULL; - // paramQuantity is guaranteed to be existing after this point as we are in the middle of a sample - if (!hoveredWidgetNull && hoveredWidget.get() != nullptr) { - paramQuantity = hoveredWidget->getParamQuantity(); - } - - if (processDivider.process() && paramQuantity && paramQuantity->module != this) { - if (paramQuantity->module->id != hoveredModuleId || paramQuantity->paramId != hoveredParamId) { - hoveredModuleId = paramQuantity->module->id; - hoveredParamId = paramQuantity->paramId; + if (processDivider.process()) { + // Copy to second variable as paramQuantity might become NULL through the app thread + if (paramQuantity != paramQuantityPriv) { + paramQuantityPriv = paramQuantity; overlayMessageId++; // Current parameter value - valuePrevious = paramQuantity->getScaledValue(); + valuePrevious = paramQuantityPriv ? paramQuantityPriv->getScaledValue() : 0.f; inVoltTarget = incdecTarget = slewLimiter.out = valuePrevious; inVoltBase = clamp(inputs[INPUT_VALUE].getVoltage() / 10.f, 0.f, 1.f); } - if (paramQuantity->isBounded()) { + if (paramQuantityPriv && paramQuantityPriv->isBounded() && paramQuantityPriv->module != this) { float valueNext = valuePrevious; if (inputs[INPUT_VALUE].isConnected()) { @@ -164,20 +158,22 @@ struct SailModule : Module { valueNext = incdecTarget; } - // Apply slew limiting - float slew = inputs[INPUT_SLEW].isConnected() ? clamp(inputs[INPUT_SLEW].getVoltage(), 0.f, 5.f) : params[PARAM_SLEW].getValue(); - if (slew > 0.f) { - slew = (1.f / slew) * 10.f; - slewLimiter.setRiseFall(slew, slew); - valueNext = slewLimiter.process(args.sampleTime * processDivider.getDivision(), valueNext); - } + if (!isSwitch) { + // Apply slew limiting + float slew = inputs[INPUT_SLEW].isConnected() ? clamp(inputs[INPUT_SLEW].getVoltage(), 0.f, 5.f) : params[PARAM_SLEW].getValue(); + if (slew > 0.f) { + slew = (1.f / slew) * 10.f; + slewLimiter.setRiseFall(slew, slew); + valueNext = slewLimiter.process(args.sampleTime * processDivider.getDivision(), valueNext); + } - // Determine the relative change - float delta = valueNext - valuePrevious; - if (delta != 0.f) { - paramQuantity->moveScaledValue(delta); - valueBaseOut = paramQuantity->getScaledValue(); - if (overlayEnabled && overlayQueue.capacity() > 0) overlayQueue.push(overlayMessageId); + // Determine the relative change + float delta = valueNext - valuePrevious; + if (delta != 0.f) { + paramQuantityPriv->moveScaledValue(delta); + valueBaseOut = paramQuantityPriv->getScaledValue(); + if (overlayEnabled && overlayQueue.capacity() > 0) overlayQueue.push(overlayMessageId); + } } valuePrevious = valueNext; @@ -185,14 +181,14 @@ struct SailModule : Module { if (outputs[OUTPUT].isConnected()) { switch (outMode) { case OUT_MODE::REDUCED: { - float v = paramQuantity->getScaledValue(); + float v = paramQuantityPriv->getScaledValue(); if (v != valueBaseOut) { outputs[OUTPUT].setVoltage(v * 10.f); } break; } case OUT_MODE::FULL: { - outputs[OUTPUT].setVoltage(paramQuantity->getScaledValue() * 10.f); + outputs[OUTPUT].setVoltage(paramQuantityPriv->getScaledValue() * 10.f); break; } } @@ -201,25 +197,11 @@ struct SailModule : Module { } if (lightDivider.process()) { - bool active = paramQuantity && paramQuantity->isBounded() && paramQuantity->module != this; + bool active = paramQuantityPriv && paramQuantityPriv->isBounded() && paramQuantityPriv->module != this; lights[LIGHT_ACTIVE].setSmoothBrightness(active ? 1.f : 0.f, args.sampleTime * lightDivider.getDivision()); } } - void setHoveredWidget(ParamWidget* pw) { - if (pw) { - hoveredWidgetNull = false; - if (hoveredModuleId != pw->module->id || hoveredParamId != pw->paramId) { - hoveredWidget = pw; - } - } - else { - hoveredWidgetNull = true; - hoveredModuleId = -1; - hoveredParamId = -1; - } - } - json_t* dataToJson() override { json_t *rootJ = json_object(); json_object_set_new(rootJ, "panelTheme", json_integer(panelTheme)); @@ -276,11 +258,19 @@ struct SailWidget : ThemedModuleWidget, OverlayMessageProvider { void step() override { ThemedModuleWidget::step(); if (!module) return; - module->fineMod = APP->window->getMods() & GLFW_MOD_SHIFT; Widget* w = APP->event->getHoveredWidget(); - ParamWidget* pw = dynamic_cast(w); - module->setHoveredWidget(pw); + if (!w) { module->paramQuantity = NULL; return; } + ParamWidget* p = dynamic_cast(w); + if (!p) { module->paramQuantity = NULL; return; } + ParamQuantity* q = p->getParamQuantity(); + if (!q) { module->paramQuantity = NULL; return; } + + Switch* sw = dynamic_cast(p); + + module->paramQuantity = q; + module->fineMod = APP->window->getMods() & GLFW_MOD_SHIFT; + module->isSwitch = sw != NULL; } int nextOverlayMessageId() override { @@ -290,10 +280,8 @@ struct SailWidget : ThemedModuleWidget, OverlayMessageProvider { } void getOverlayMessage(int id, Message& m) override { - if (module->hoveredWidgetNull || module->overlayMessageId != id) return; - ParamWidget* pw = module->hoveredWidget.get(); - if (pw == nullptr) return; - ParamQuantity* paramQuantity = pw->getParamQuantity(); + if (module->overlayMessageId != id) return; + ParamQuantity* paramQuantity = module->paramQuantityPriv; if (!paramQuantity) return; m.title = paramQuantity->getDisplayValueString() + paramQuantity->getUnit(); diff --git a/src/Spin.cpp b/src/Spin.cpp index 6285d34..6763419 100644 --- a/src/Spin.cpp +++ b/src/Spin.cpp @@ -192,13 +192,13 @@ struct SpinWidget : ThemedModuleWidget { mwContainer = new SpinContainer; mwContainer->module = module; // This is where the magic happens: add a new widget on top-level to Rack - APP->scene->rack->addChild(mwContainer); + APP->scene->addChild(mwContainer); } } ~SpinWidget() { if (module) { - APP->scene->rack->removeChild(mwContainer); + APP->scene->removeChild(mwContainer); delete mwContainer; } } diff --git a/src/Strip.cpp b/src/Strip.cpp index 6f692c1..3f04691 100644 --- a/src/Strip.cpp +++ b/src/Strip.cpp @@ -136,7 +136,8 @@ struct StripModule : StripModuleBase { } void groupDisable(bool val, bool useHistory) { - taskWorker.work([=]() { groupDisableWorker(val, useHistory); }); + //taskWorker.work([=]() { groupDisableWorker(val, useHistory); }); + taskWorker.work([=]() { groupDisableWorker(val, false); }); } /** @@ -162,11 +163,6 @@ struct StripModule : StripModuleBase { // This is what "Module.hpp" says about bypass: // "Module subclasses should not read/write this variable." APP->engine->bypassModule(m->rightExpander.module, val); - // Clear outputs and set to 1 channel - for (Output& output : m->rightExpander.module->outputs) { - // This zeros all voltages, but the channel is set to 1 if connected - output.setChannels(0); - } if (useHistory) { // history::ModuleBypass @@ -188,11 +184,6 @@ struct StripModule : StripModuleBase { // This is what "Module.hpp" says about bypass: // "Module subclasses should not read/write this variable." APP->engine->bypassModule(m->leftExpander.module, val); - // Clear outputs and set to 1 channel - for (Output& output : m->leftExpander.module->outputs) { - // This zeros all voltages, but the channel is set to 1 if connected - output.setChannels(0); - } if (useHistory) { // history::ModuleBypass @@ -242,7 +233,7 @@ struct StripModule : StripModuleBase { if (!mw) return; for (ParamWidget* param : mw->getParams()) { ParamQuantity* paramQuantity = param->getParamQuantity(); - if (!paramQuantity->randomizeEnabled) continue; + if (!paramQuantity || !paramQuantity->randomizeEnabled) continue; switch (randomExcl) { case RANDOMEXCL::NONE: @@ -288,7 +279,7 @@ struct StripModule : StripModuleBase { if (!mw) return; for (ParamWidget* param : mw->getParams()) { ParamQuantity* paramQuantity = param->getParamQuantity(); - if (!paramQuantity->randomizeEnabled) continue; + if (!paramQuantity || !paramQuantity->randomizeEnabled) continue; switch (randomExcl) { case RANDOMEXCL::NONE: @@ -430,10 +421,13 @@ struct ExcludeButton : TL1105 { // Check if a ParamWidget was touched // NB: unstable API ParamWidget* touchedParam = APP->scene->rack->touchedParam; - if (touchedParam && touchedParam->getParamQuantity() && touchedParam->getParamQuantity()->module != module) { - int64_t moduleId = touchedParam->getParamQuantity()->module->id; - int paramId = touchedParam->getParamQuantity()->paramId; - groupExcludeParam(moduleId, paramId); + if (touchedParam) { + ParamQuantity* paramQuantity = touchedParam->getParamQuantity(); + if (paramQuantity && paramQuantity->module != module) { + int64_t moduleId = paramQuantity->module->id; + int paramId = paramQuantity->paramId; + groupExcludeParam(moduleId, paramId); + } } } @@ -483,7 +477,8 @@ struct ExcludeButton : TL1105 { if (m->rightExpander.moduleId == moduleId) { ModuleWidget* mw = APP->scene->rack->getModule(m->rightExpander.moduleId); for (ParamWidget* param : mw->getParams()) { - if (param->getParamQuantity() && param->getParamQuantity()->paramId == paramId) { + ParamQuantity* paramQuantity = param->getParamQuantity(); + if (paramQuantity && paramQuantity->paramId == paramId) { // Aquire excludeMutex to get exclusive access to excludedParams std::lock_guard lockGuard(module->excludeMutex); module->excludedParams.insert(std::make_tuple(moduleId, paramId)); @@ -503,7 +498,8 @@ struct ExcludeButton : TL1105 { if (m->leftExpander.moduleId == moduleId) { ModuleWidget* mw = APP->scene->rack->getModule(m->leftExpander.moduleId); for (ParamWidget* param : mw->getParams()) { - if (param->getParamQuantity() && param->getParamQuantity()->paramId == paramId) { + ParamQuantity* paramQuantity = param->getParamQuantity(); + if (paramQuantity && paramQuantity->paramId == paramId) { // Aquire excludeMutex to get exclusive access to excludedParams std::lock_guard lockGuard(module->excludeMutex); module->excludedParams.insert(std::make_tuple(moduleId, paramId)); @@ -602,11 +598,13 @@ struct ExcludeButton : TL1105 { if (!moduleWidget) continue; ParamWidget* paramWidget = moduleWidget->getParam(paramId); if (!paramWidget) continue; - + ParamQuantity* paramQuantity = paramWidget->getParamQuantity(); + if (!paramQuantity) continue; + std::string text = "Parameter \""; text += moduleWidget->model->name; text += " "; - text += paramWidget->getParamQuantity()->getLabel(); + text += paramQuantity->getLabel(); text += "\""; ui::MenuLabel* modelLabel = new ui::MenuLabel; diff --git a/src/Strip.hpp b/src/Strip.hpp index 428f6c9..531341c 100644 --- a/src/Strip.hpp +++ b/src/Strip.hpp @@ -1118,7 +1118,7 @@ struct StripWidgetBase : ThemedModuleWidget { groupLoadFileDialog(false); e.consume(this); } - if ((e.mods & RACK_MOD_MASK) == (GLFW_MOD_SHIFT | GLFW_MOD_CONTROL)) { + if ((e.mods & RACK_MOD_MASK) == (GLFW_MOD_SHIFT | RACK_MOD_CTRL)) { groupLoadFileDialog(true); e.consume(this); } diff --git a/src/StripBay.cpp b/src/StripBay.cpp index 4b49737..235963b 100644 --- a/src/StripBay.cpp +++ b/src/StripBay.cpp @@ -4,7 +4,7 @@ namespace StoermelderPackOne { namespace StripBay { -template +template struct StripBayModule : Strip::StripBayBase { enum ParamIds { NUM_PARAMS @@ -29,15 +29,15 @@ struct StripBayModule : Strip::StripBayBase { StripBayModule() { panelTheme = pluginSettings.panelThemeDefault; config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); - for (size_t i = 0; i < PORTS; i++) { - configInput(INPUT + i, string::f("Patchbay %lli", i + 1)); - configOutput(OUTPUT + i, string::f("Patchbay %lli", i + 1)); + for (int i = 0; i < PORTS; i++) { + configInput(INPUT + i, string::f("Patchbay %i", i + 1)); + configOutput(OUTPUT + i, string::f("Patchbay %i", i + 1)); } onReset(); } void process(const ProcessArgs& args) override { - for (size_t i = 0; i < PORTS; i++) { + for (int i = 0; i < PORTS; i++) { outputs[OUTPUT + i].writeVoltages(inputs[INPUT + i].getVoltages()); outputs[OUTPUT + i].setChannels(inputs[INPUT + i].getChannels()); } diff --git a/src/StripPp.cpp b/src/StripPp.cpp index 5b5f40e..3695d66 100644 --- a/src/StripPp.cpp +++ b/src/StripPp.cpp @@ -77,7 +77,7 @@ struct StripPpWidget : StripWidgetBase { } void onHoverKey(const event::HoverKey& e) override { - if (e.action == GLFW_PRESS && (e.mods & RACK_MOD_MASK) == (GLFW_MOD_SHIFT | GLFW_MOD_CONTROL)) { + if (e.action == GLFW_PRESS && (e.mods & RACK_MOD_MASK) == (GLFW_MOD_SHIFT | RACK_MOD_CTRL)) { switch (e.key) { case GLFW_KEY_V: mw->groupSelectionPasteClipboard(); diff --git a/src/Stroke.cpp b/src/Stroke.cpp index 2f8509e..5e597f0 100644 --- a/src/Stroke.cpp +++ b/src/Stroke.cpp @@ -1,8 +1,10 @@ #include "plugin.hpp" #include "components/MenuColorLabel.hpp" #include "components/MenuColorField.hpp" +#include "components/MenuColorPicker.hpp" #include "ui/ModuleSelectProcessor.hpp" #include "ui/keyboard.hpp" +#include "ui/ViewportHelper.hpp" namespace StoermelderPackOne { namespace Stroke { @@ -21,6 +23,8 @@ enum class KEY_MODE { S_ZOOM_MODULE_30_SMOOTH = 141, S_ZOOM_MODULE_CUSTOM = 16, S_ZOOM_MODULE_CUSTOM_SMOOTH = 161, + S_ZOOM_MODULE_ID = 17, + S_ZOOM_MODULE_ID_SMOOTH = 171, S_ZOOM_OUT = 13, S_ZOOM_OUT_SMOOTH = 131, S_ZOOM_TOGGLE = 15, @@ -197,7 +201,7 @@ struct StrokeModule : Module { json_t* keyJ = json_array_get(keysJ, i); keys[i].button = json_integer_value(json_object_get(keyJ, "button")); keys[i].key = keyFix(json_integer_value(json_object_get(keyJ, "key"))); - keys[i].mods = json_integer_value(json_object_get(keyJ, "mods")) & (GLFW_MOD_ALT | GLFW_MOD_CONTROL | GLFW_MOD_SHIFT); + keys[i].mods = json_integer_value(json_object_get(keyJ, "mods")) & (GLFW_MOD_ALT | RACK_MOD_CTRL | GLFW_MOD_SHIFT); keys[i].mode = (KEY_MODE)json_integer_value(json_object_get(keyJ, "mode")); keys[i].high = json_boolean_value(json_object_get(keyJ, "high")); json_t* dataJ = json_object_get(keyJ, "data"); @@ -331,6 +335,38 @@ struct CmdZoomModuleCustomSmooth : CmdBase { }; // struct CmdZoomModuleCustomSmooth +struct CmdZoomModuleId : CmdBase { + std::string* data; + float scale; + void initialCmd(KEY_MODE keyMode) override { + if (*data == "") return; + int64_t moduleId = std::stoll(*data); + ModuleWidget* mw = APP->scene->rack->getModule(moduleId); + if (!mw) return; + StoermelderPackOne::Rack::ViewportCenter{mw, scale}; + } +}; // struct CmdZoomModuleId + + +struct CmdZoomModuleIdSmooth : CmdBase { + std::string* data; + float scale; + StoermelderPackOne::Rack::ViewportCenterSmooth viewportCenterSmooth; + void initialCmd(KEY_MODE keyMode) override { + if (*data == "") return; + int64_t moduleId = std::stoll(*data); + ModuleWidget* mw = APP->scene->rack->getModule(moduleId); + if (!mw) return; + Vec p = mw->box.size.mult(Vec(1.f - scale, 1.f - scale)); + viewportCenterSmooth.trigger(mw->box.grow(p), 1.f / APP->window->getLastFrameDuration(), 0.6f); + } + void step() override { + viewportCenterSmooth.process(); + } +}; // struct CmdZoomModuleIdSmooth + + + struct CmdZoomOut : CmdBase { void initialCmd(KEY_MODE keyMode) override { zoomOut(); @@ -906,6 +942,10 @@ struct KeyContainer : Widget { processCmd(&CmdZoomModuleCustom::data, &module->keyTemp->data); break; case KEY_MODE::S_ZOOM_MODULE_CUSTOM_SMOOTH: processCmd(&CmdZoomModuleCustomSmooth::data, &module->keyTemp->data); break; + case KEY_MODE::S_ZOOM_MODULE_ID: + processCmd(&CmdZoomModuleId::data, &module->keyTemp->data, &CmdZoomModuleId::scale, 0.9f); break; + case KEY_MODE::S_ZOOM_MODULE_ID_SMOOTH: + processCmd(&CmdZoomModuleIdSmooth::data, &module->keyTemp->data, &CmdZoomModuleIdSmooth::scale, 0.95f); break; case KEY_MODE::S_ZOOM_OUT: processCmd(); break; case KEY_MODE::S_ZOOM_OUT_SMOOTH: @@ -988,8 +1028,8 @@ struct KeyContainer : Widget { } void onButton(const event::Button& e) override { - if (module && !module->isBypassed() && (e.button > 2 || (e.mods & (GLFW_MOD_ALT | GLFW_MOD_CONTROL | GLFW_MOD_SHIFT))) != 0) { - int e_mods = e.mods & (GLFW_MOD_ALT | GLFW_MOD_CONTROL | GLFW_MOD_SHIFT); + if (module && !module->isBypassed() && (e.button > 2 || (e.mods & (GLFW_MOD_ALT | RACK_MOD_CTRL | GLFW_MOD_SHIFT))) != 0) { + int e_mods = e.mods & (GLFW_MOD_ALT | RACK_MOD_CTRL | GLFW_MOD_SHIFT); if (e.action == GLFW_PRESS) { if (learnIdx >= 0) { @@ -1032,7 +1072,7 @@ struct KeyContainer : Widget { void onHoverKey(const event::HoverKey& e) override { if (module && !module->isBypassed()) { - int e_mods = e.mods & (GLFW_MOD_ALT | GLFW_MOD_CONTROL | GLFW_MOD_SHIFT); + int e_mods = e.mods & (GLFW_MOD_ALT | RACK_MOD_CTRL | GLFW_MOD_SHIFT); int e_key = keyFix(e.key); if (e.action == GLFW_PRESS) { @@ -1116,7 +1156,7 @@ struct KeyDisplay : StoermelderLedDisplay { color = nvgRGBA(0xef, 0xef, 0xef, 0xff); text = module->keys[idx].key >= 0 ? keyName(module->keys[idx].key) : module->keys[idx].button >= 0 ? string::f("MB %i", module->keys[idx].button + 1) : ""; module->lights[StrokeModule::LIGHT_ALT + idx].setBrightness(module->keys[idx].mods & GLFW_MOD_ALT ? 0.7f : 0.f); - module->lights[StrokeModule::LIGHT_CTRL + idx].setBrightness(module->keys[idx].mods & GLFW_MOD_CONTROL ? 0.7f : 0.f); + module->lights[StrokeModule::LIGHT_CTRL + idx].setBrightness(module->keys[idx].mods & RACK_MOD_CTRL ? 0.7f : 0.f); module->lights[StrokeModule::LIGHT_SHIFT + idx].setBrightness(module->keys[idx].mods & GLFW_MOD_SHIFT ? 0.7f : 0.f); } StoermelderLedDisplay::step(); @@ -1168,6 +1208,7 @@ struct KeyDisplay : StoermelderLedDisplay { struct ViewMenuItem : MenuItem { StrokeModule* module; + KeyContainer* keyContainer; int idx; void step() override { rightText = @@ -1181,6 +1222,8 @@ struct KeyDisplay : StoermelderLedDisplay { module->keys[idx].mode == KEY_MODE::S_ZOOM_OUT_SMOOTH || module->keys[idx].mode == KEY_MODE::S_ZOOM_TOGGLE || module->keys[idx].mode == KEY_MODE::S_ZOOM_TOGGLE_SMOOTH || + module->keys[idx].mode == KEY_MODE::S_ZOOM_MODULE_ID || + module->keys[idx].mode == KEY_MODE::S_ZOOM_MODULE_ID_SMOOTH || module->keys[idx].mode == KEY_MODE::S_SCROLL_LEFT || module->keys[idx].mode == KEY_MODE::S_SCROLL_RIGHT || module->keys[idx].mode == KEY_MODE::S_SCROLL_UP || @@ -1190,7 +1233,7 @@ struct KeyDisplay : StoermelderLedDisplay { } Menu* createChildMenu() override { - struct ModeZoomModuleCustomItem : MenuItem { + struct ZoomModuleCustomItem : MenuItem { StrokeModule* module; KEY_MODE mode; int idx; @@ -1260,13 +1303,64 @@ struct KeyDisplay : StoermelderLedDisplay { } }; + struct ZoomModuleIdItem : ModeMenuItem { + KeyContainer* keyContainer; + void onAction(const event::Action& e) override { + ModeMenuItem::module->keys[ModeMenuItem::idx].mode = ModeMenuItem::mode; + ModeMenuItem::module->keys[ModeMenuItem::idx].high = false; + ModeMenuItem::module->keys[ModeMenuItem::idx].data = ""; + } + + Menu* createChildMenu() override { + struct LearnItem : MenuItem { + KeyContainer* keyContainer; + int idx; + void onAction(const event::Action& e) override { + keyContainer->learnIdx = keyContainer->learnIdxEx = idx; + keyContainer->module->keys[idx].data = ""; + KeyContainer* _keyContainer = keyContainer; + std::string* _data = &keyContainer->module->keys[idx].data; + auto callback = [_keyContainer,_data](ModuleWidget* mw, Vec pos) { + *_data = string::f("%lld", (long long)mw->module->getId()); + _keyContainer->learnIdx = _keyContainer->learnIdxEx = -1; + }; + keyContainer->moduleSelectProcessor.startLearn(callback); + } + }; + + if (ModeMenuItem::module->keys[ModeMenuItem::idx].mode == ModeMenuItem::mode) { + Menu* menu = new Menu; + LearnItem* learnItem = construct(&MenuItem::text, "Learn module", &LearnItem::keyContainer, keyContainer, &LearnItem::idx, ModeMenuItem::idx); + menu->addChild(learnItem); + + if (ModeMenuItem::module->keys[ModeMenuItem::idx].data != "") { + int64_t moduleId = std::stoll(ModeMenuItem::module->keys[ModeMenuItem::idx].data); + ModuleWidget* mw = APP->scene->rack->getModule(moduleId); + if (mw) { + std::string name = mw->model->plugin->brand + " " + mw->module->model->name; + menu->addChild(new MenuSeparator); + menu->addChild(construct(&MenuLabel::text, name)); + menu->addChild(construct(&MenuLabel::text, string::f("ID %lld", (long long)mw->module->getId()))); + menu->addChild(createMenuItem("Center module", "", [mw]() { StoermelderPackOne::Rack::ViewportCenter{mw}; })); + } + } + + return menu; + } + return NULL; + } + }; + Menu* menu = new Menu; menu->addChild(construct(&MenuItem::text, "Zoom to module", &ModeMenuItem::module, module, &ModeMenuItem::idx, idx, &ModeMenuItem::mode, KEY_MODE::S_ZOOM_MODULE_90)); menu->addChild(construct(&MenuItem::text, "Zoom to module (smooth)", &ModeMenuItem::module, module, &ModeMenuItem::idx, idx, &ModeMenuItem::mode, KEY_MODE::S_ZOOM_MODULE_90_SMOOTH)); menu->addChild(construct(&MenuItem::text, "Zoom to module 1/3", &ModeMenuItem::module, module, &ModeMenuItem::idx, idx, &ModeMenuItem::mode, KEY_MODE::S_ZOOM_MODULE_30)); menu->addChild(construct(&MenuItem::text, "Zoom to module 1/3 (smooth)", &ModeMenuItem::module, module, &ModeMenuItem::idx, idx, &ModeMenuItem::mode, KEY_MODE::S_ZOOM_MODULE_30_SMOOTH)); - menu->addChild(construct(&MenuItem::text, "Zoom level to module", &ModeZoomModuleCustomItem::module, module, &ModeZoomModuleCustomItem::idx, idx, &ModeZoomModuleCustomItem::mode, KEY_MODE::S_ZOOM_MODULE_CUSTOM)); - menu->addChild(construct(&MenuItem::text, "Zoom level to module (smooth)", &ModeZoomModuleCustomItem::module, module, &ModeZoomModuleCustomItem::idx, idx, &ModeZoomModuleCustomItem::mode, KEY_MODE::S_ZOOM_MODULE_CUSTOM_SMOOTH)); + menu->addChild(construct(&MenuItem::text, "Zoom level to module", &ZoomModuleCustomItem::module, module, &ZoomModuleCustomItem::idx, idx, &ZoomModuleCustomItem::mode, KEY_MODE::S_ZOOM_MODULE_CUSTOM)); + menu->addChild(construct(&MenuItem::text, "Zoom level to module (smooth)", &ZoomModuleCustomItem::module, module, &ZoomModuleCustomItem::idx, idx, &ZoomModuleCustomItem::mode, KEY_MODE::S_ZOOM_MODULE_CUSTOM_SMOOTH)); + menu->addChild(construct(&MenuItem::text, "Zoom to specific module", &ModeMenuItem::module, module, &ModeMenuItem::idx, idx, &ModeMenuItem::mode, KEY_MODE::S_ZOOM_MODULE_ID, &ZoomModuleIdItem::keyContainer, keyContainer)); + menu->addChild(construct(&MenuItem::text, "Zoom to specific module (smooth)", &ModeMenuItem::module, module, &ModeMenuItem::idx, idx, &ModeMenuItem::mode, KEY_MODE::S_ZOOM_MODULE_ID_SMOOTH, &ZoomModuleIdItem::keyContainer, keyContainer)); + menu->addChild(new MenuSeparator); menu->addChild(construct(&MenuItem::text, "Zoom out", &ModeMenuItem::module, module, &ModeMenuItem::idx, idx, &ModeMenuItem::mode, KEY_MODE::S_ZOOM_OUT)); menu->addChild(construct(&MenuItem::text, "Zoom out (smooth)", &ModeMenuItem::module, module, &ModeMenuItem::idx, idx, &ModeMenuItem::mode, KEY_MODE::S_ZOOM_OUT_SMOOTH)); menu->addChild(construct(&MenuItem::text, "Zoom toggle", &ModeMenuItem::module, module, &ModeMenuItem::idx, idx, &ModeMenuItem::mode, KEY_MODE::S_ZOOM_TOGGLE)); @@ -1412,8 +1506,15 @@ struct KeyDisplay : StoermelderLedDisplay { json_error_t error; json_t* oJ = json_loads(ModeMenuItem::module->keys[ModeMenuItem::idx].data.c_str(), 0, &error); std::string name = json_string_value(json_object_get(oJ, "name")); + int64_t moduleId = json_integer_value(json_object_get(oJ, "moduleId")); + ModuleWidget* mw = APP->scene->rack->getModule(moduleId); + menu->addChild(new MenuSeparator); - menu->addChild(construct(&MenuLabel::text, name)); + if (mw) { + menu->addChild(construct(&MenuLabel::text, name)); + menu->addChild(construct(&MenuLabel::text, string::f("ID %lld", (long long)mw->module->getId()))); + menu->addChild(createMenuItem("Center module", "", [mw]() { StoermelderPackOne::Rack::ViewportCenter{mw}; })); + } json_t* keyJ = json_object_get(oJ, "key"); json_t* modsJ = json_object_get(oJ, "mods"); @@ -1421,7 +1522,7 @@ struct KeyDisplay : StoermelderLedDisplay { std::string key = keyName(json_integer_value(keyJ)); int mods = json_integer_value(modsJ); std::string alt = mods & GLFW_MOD_ALT ? RACK_MOD_ALT_NAME "+" : ""; - std::string ctrl = mods & GLFW_MOD_CONTROL ? RACK_MOD_CTRL_NAME "+" : ""; + std::string ctrl = mods & RACK_MOD_CTRL ? RACK_MOD_CTRL_NAME "+" : ""; std::string shift = mods & GLFW_MOD_SHIFT ? RACK_MOD_SHIFT_NAME "+" : ""; std::string s = string::f("Hotkey: %s%s%s%s", alt.c_str(), ctrl.c_str(), shift.c_str(), key.c_str()); menu->addChild(construct(&MenuLabel::text, s)); @@ -1472,33 +1573,38 @@ struct KeyDisplay : StoermelderLedDisplay { struct CableColorMenuItem : MenuItem { StrokeModule* module; int idx; + NVGcolor color; + bool firstRun = true; + void step() override { - rightText = module->keys[idx].mode == KEY_MODE::S_CABLE_COLOR ? "✔ " RIGHT_ARROW : ""; + if (module->keys[idx].mode == KEY_MODE::S_CABLE_COLOR) { + if (firstRun) { + color = color::fromHexString(module->keys[idx].data); + firstRun = false; + } + module->keys[idx].data = color::toHexString(color); + rightText = "✔ " RIGHT_ARROW; + } MenuItem::step(); } + void onAction(const event::Action& e) override { - module->keys[idx].mode = KEY_MODE::S_CABLE_COLOR; - module->keys[idx].high = false; - module->keys[idx].data = color::toHexString(color::BLACK); + if (module->keys[idx].mode != KEY_MODE::S_CABLE_COLOR) { + module->keys[idx].mode = KEY_MODE::S_CABLE_COLOR; + module->keys[idx].high = false; + module->keys[idx].data = color::toHexString(color::BLACK); + } } Menu* createChildMenu() override { - struct ColorField : MenuColorField { - StrokeModule* module; - int idx; - void returnColor(NVGcolor color) override { - module->keys[idx].data = color::toHexString(color); - } - NVGcolor initColor() override { - return module->keys[idx].data != "" ? color::fromHexString(module->keys[idx].data) : color::BLACK; - } - }; - if (module->keys[idx].mode == KEY_MODE::S_CABLE_COLOR) { + Menu* menu = new Menu; - MenuColorLabel* colorLabel = construct(&MenuColorLabel::fillColor, color::fromHexString(module->keys[idx].data)); - menu->addChild(colorLabel); - menu->addChild(construct(&ColorField::module, module, &MenuColorField::colorLabel, colorLabel, &ColorField::idx, idx)); + menu->addChild(construct(&MenuColorLabel::fillColor, &color)); + menu->addChild(new MenuSeparator); + menu->addChild(construct(&MenuColorPicker::color, &color)); + menu->addChild(new MenuSeparator); + menu->addChild(construct(&MenuColorField::color, &color)); return menu; } return NULL; @@ -1558,7 +1664,7 @@ struct KeyDisplay : StoermelderLedDisplay { menu->addChild(construct(&MenuItem::text, "Toggle", &ModeMenuItem::module, module, &ModeMenuItem::idx, idx, &ModeMenuItem::mode, KEY_MODE::CV_TOGGLE)); menu->addChild(new MenuSeparator); menu->addChild(construct(&MenuLabel::text, "Commands")); - menu->addChild(construct(&MenuItem::text, "View", &ViewMenuItem::module, module, &ViewMenuItem::idx, idx)); + menu->addChild(construct(&MenuItem::text, "View", &ViewMenuItem::module, module, &ViewMenuItem::idx, idx, &ViewMenuItem::keyContainer, keyContainer)); menu->addChild(construct(&MenuItem::text, "Parameters", &ParamMenuItem::module, module, &ParamMenuItem::idx, idx)); menu->addChild(construct(&MenuItem::text, "Modules", &ModuleMenuItem::module, module, &ModuleMenuItem::idx, idx, &ModuleMenuItem::keyContainer, keyContainer)); menu->addChild(construct(&MenuItem::text, "Cables", &CableMenuItem::module, module, &CableMenuItem::idx, idx)); @@ -1607,6 +1713,10 @@ struct KeyDisplay : StoermelderLedDisplay { text = "View: Zoom level to module"; break; case KEY_MODE::S_ZOOM_MODULE_CUSTOM_SMOOTH: text = "View: Zoom level to module (smooth)"; break; + case KEY_MODE::S_ZOOM_MODULE_ID: + text = "View: Zoom to specific module"; break; + case KEY_MODE::S_ZOOM_MODULE_ID_SMOOTH: + text = "View: Zoom to specific module (smooth)"; break; case KEY_MODE::S_ZOOM_OUT: text = "View: Zoom out"; break; case KEY_MODE::S_ZOOM_OUT_SMOOTH: diff --git a/src/Transit.cpp b/src/Transit.cpp index 37f0805..bb608a8 100644 --- a/src/Transit.cpp +++ b/src/Transit.cpp @@ -10,7 +10,7 @@ namespace StoermelderPackOne { namespace Transit { -const int MAX_EXPANDERS = 7; +const int MAX_EXPANDERS = 15; enum class SLOTCVMODE { OFF = -1, @@ -70,6 +70,8 @@ struct TransitModule : TransitBase { int preset; /** [Stored to JSON] Number of currently active snapshots */ int presetCount; + /** [Stored to JSON] */ + bool presetCountLongPress = true; /** Total number of snapshots including expanders */ int presetTotal; @@ -279,25 +281,41 @@ struct TransitModule : TransitBase { resetTimer.reset(); switch (slotCvMode) { case SLOTCVMODE::TRIG_FWD: + case SLOTCVMODE::TRIG_RANDOM: + case SLOTCVMODE::TRIG_RANDOM_WALK: + case SLOTCVMODE::TRIG_RANDOM_WO_REPEAT: { presetLoad(0); break; - case SLOTCVMODE::TRIG_REV: + } + case SLOTCVMODE::TRIG_REV: { presetLoad(presetCount - 1); break; - case SLOTCVMODE::TRIG_PINGPONG: + } + case SLOTCVMODE::TRIG_PINGPONG: { slotCvModeDir = 1; presetLoad(0); break; - case SLOTCVMODE::TRIG_ALT: + } + case SLOTCVMODE::TRIG_ALT: { slotCvModeDir = 1; slotCvModeAlt = 0; presetLoad(0); break; - case SLOTCVMODE::TRIG_SHUFFLE: + } + case SLOTCVMODE::TRIG_SHUFFLE: { slotCvModeShuffle.clear(); + for (int i = 0; i < presetCount; i++) { + slotCvModeShuffle.push_back(i); + } + std::random_shuffle(std::begin(slotCvModeShuffle), std::end(slotCvModeShuffle)); + int p = std::min(std::max(0, slotCvModeShuffle.back()), presetCount - 1); + slotCvModeShuffle.pop_back(); + presetLoad(p); break; - default: + } + default: { break; + } } } else { @@ -411,9 +429,11 @@ struct TransitModule : TransitBase { case LongPressButton::NO_PRESS: break; case LongPressButton::SHORT_PRESS: - presetLoad(i, slotCvMode == SLOTCVMODE::ARM, true); break; + presetLoad(i, slotCvMode == SLOTCVMODE::ARM, true); + break; case LongPressButton::LONG_PRESS: - presetSetCount(i + 1); break; + if (presetCountLongPress) presetSetCount(i + 1); + break; } } } @@ -430,9 +450,11 @@ struct TransitModule : TransitBase { case LongPressButton::NO_PRESS: break; case LongPressButton::SHORT_PRESS: - presetSave(i); break; + presetSave(i); + break; case LongPressButton::LONG_PRESS: - presetClear(i); break; + presetClear(i); + break; } } } @@ -872,6 +894,7 @@ struct TransitModule : TransitBase { json_object_set_new(rootJ, "outMode", json_integer((int)outMode)); json_object_set_new(rootJ, "preset", json_integer(preset)); json_object_set_new(rootJ, "presetCount", json_integer(presetCount)); + json_object_set_new(rootJ, "presetCountLongPress", json_boolean(presetCountLongPress)); json_t* sourceMapsJ = json_array(); for (size_t i = 0; i < sourceHandles.size(); i++) { @@ -894,6 +917,8 @@ struct TransitModule : TransitBase { outMode = (OUTMODE)json_integer_value(json_object_get(rootJ, "outMode")); preset = json_integer_value(json_object_get(rootJ, "preset")); presetCount = json_integer_value(json_object_get(rootJ, "presetCount")); + json_t* presetCountLongPressJ = json_object_get(rootJ, "presetCountLongPress"); + if (presetCountLongPressJ) presetCountLongPress = json_boolean_value(presetCountLongPressJ); if (preset >= presetCount) { preset = -1; @@ -1068,18 +1093,79 @@ struct TransitWidget : ThemedModuleWidget> { int sampleRate = int(APP->engine->getSampleRate()); MODULE* module = dynamic_cast(this->module); - struct MappingIndicatorHiddenItem : MenuItem { - MODULE* module; + struct NumberOfSlotsSlider : ui::Slider { + struct NumberOfSlotsQuantity : Quantity { + MODULE* module; + float v = -1.f; + + NumberOfSlotsQuantity(MODULE* module) { + this->module = module; + } + void setValue(float value) override { + v = clamp(value, 1.f, float(module->presetTotal)); + module->presetSetCount(int(v)); + } + float getValue() override { + if (v < 0.f) v = module->presetCount; + return v; + } + float getDefaultValue() override { + return 8.f; + } + float getMinValue() override { + return 1.f; + } + float getMaxValue() override { + return float(module->presetTotal); + } + float getDisplayValue() override { + return getValue(); + } + std::string getDisplayValueString() override { + int i = int(getValue()); + return string::f("%i", i); + } + void setDisplayValue(float displayValue) override { + setValue(displayValue); + } + std::string getLabel() override { + return "Slots"; + } + std::string getUnit() override { + return ""; + } + }; + + NumberOfSlotsSlider(MODULE* module) { + box.size.x = 160.0; + quantity = new NumberOfSlotsQuantity(module); + } + ~NumberOfSlotsSlider() { + delete quantity; + } + void onDragMove(const event::DragMove& e) override { + if (quantity) { + quantity->moveScaledValue(0.002f * e.mouseDelta.x); + } + } + }; + + struct BindParameterItem : MenuItem { + WIDGET* widget; + int mode; + std::string rightText = ""; void onAction(const event::Action& e) override { - module->mappingIndicatorHidden ^= true; + widget->enableLearn(mode); } void step() override { - rightText = module->mappingIndicatorHidden ? "✔" : ""; + MenuItem::rightText = widget->learn == mode ? "Active" : rightText; MenuItem::step(); } }; - auto precisionMenuItem = StoermelderPackOne::Rack::createMapSubmenuItem("Precision", { + menu->addChild(new MenuSeparator()); + menu->addChild(createBoolPtrMenuItem("Hide mapping indicators", "", &module->mappingIndicatorHidden)); + menu->addChild(StoermelderPackOne::Rack::createMapSubmenuItem("Precision", { { 1, string::f("Audio rate (%i Hz)", sampleRate / 1) }, { 8, string::f("Lower CPU (%i Hz)", sampleRate / 8) }, { 64, string::f("Lowest CPU (%i Hz)", sampleRate / 64) }, @@ -1092,9 +1178,17 @@ struct TransitWidget : ThemedModuleWidget> { [=](int division) { module->setProcessDivision(division); } - ); + )); + + menu->addChild(new MenuSeparator()); + menu->addChild(createSubmenuItem("Number of snapshots", string::f("%i", module->presetCount), + [=](Menu* menu) { + menu->addChild(new NumberOfSlotsSlider(module)); + menu->addChild(createBoolPtrMenuItem("Set by long-press", "", &module->presetCountLongPress)); + } + )); - struct SlotCvModeMenuItem : MenuItem { + menu->addChild(createSubmenuItem("Port CV mode", "", [=](Menu* menu) { struct SlotCvModeItem : MenuItem { MODULE* module; SLOTCVMODE slotCvMode; @@ -1108,33 +1202,24 @@ struct TransitWidget : ThemedModuleWidget> { } }; - MODULE* module; - SlotCvModeMenuItem() { - rightText = RIGHT_ARROW; - } - - Menu* createChildMenu() override { - Menu* menu = new Menu; - menu->addChild(construct(&MenuItem::text, "Trigger forward", &SlotCvModeItem::module, module, &SlotCvModeItem::slotCvMode, SLOTCVMODE::TRIG_FWD)); - menu->addChild(construct(&MenuItem::text, "Trigger reverse", &SlotCvModeItem::module, module, &SlotCvModeItem::slotCvMode, SLOTCVMODE::TRIG_REV)); - menu->addChild(construct(&MenuItem::text, "Trigger pingpong", &SlotCvModeItem::module, module, &SlotCvModeItem::slotCvMode, SLOTCVMODE::TRIG_PINGPONG)); - menu->addChild(construct(&MenuItem::text, "Trigger alternating", &SlotCvModeItem::module, module, &SlotCvModeItem::slotCvMode, SLOTCVMODE::TRIG_ALT)); - menu->addChild(construct(&MenuItem::text, "Trigger random", &SlotCvModeItem::module, module, &SlotCvModeItem::slotCvMode, SLOTCVMODE::TRIG_RANDOM)); - menu->addChild(construct(&MenuItem::text, "Trigger pseudo-random", &SlotCvModeItem::module, module, &SlotCvModeItem::slotCvMode, SLOTCVMODE::TRIG_RANDOM_WO_REPEAT)); - menu->addChild(construct(&MenuItem::text, "Trigger random walk", &SlotCvModeItem::module, module, &SlotCvModeItem::slotCvMode, SLOTCVMODE::TRIG_RANDOM_WALK)); - menu->addChild(construct(&MenuItem::text, "Trigger shuffle", &SlotCvModeItem::module, module, &SlotCvModeItem::slotCvMode, SLOTCVMODE::TRIG_SHUFFLE)); - menu->addChild(construct(&MenuItem::text, "0..10V", &SlotCvModeItem::module, module, &SlotCvModeItem::slotCvMode, SLOTCVMODE::VOLT)); - menu->addChild(construct(&MenuItem::text, "C4", &SlotCvModeItem::module, module, &SlotCvModeItem::slotCvMode, SLOTCVMODE::C4)); - menu->addChild(construct(&MenuItem::text, "Arm", &SlotCvModeItem::module, module, &SlotCvModeItem::slotCvMode, SLOTCVMODE::ARM)); - menu->addChild(new MenuSeparator); - menu->addChild(construct(&MenuItem::text, "Phase", &SlotCvModeItem::module, module, &SlotCvModeItem::slotCvMode, SLOTCVMODE::PHASE)); - menu->addChild(new MenuSeparator); - menu->addChild(construct(&MenuItem::text, "Off", &SlotCvModeItem::rightTextEx, RACK_MOD_SHIFT_NAME "+Q", &SlotCvModeItem::module, module, &SlotCvModeItem::slotCvMode, SLOTCVMODE::OFF)); - return menu; - } - }; - - struct OutModeMenuItem : MenuItem { + menu->addChild(construct(&MenuItem::text, "Trigger forward", &SlotCvModeItem::module, module, &SlotCvModeItem::slotCvMode, SLOTCVMODE::TRIG_FWD)); + menu->addChild(construct(&MenuItem::text, "Trigger reverse", &SlotCvModeItem::module, module, &SlotCvModeItem::slotCvMode, SLOTCVMODE::TRIG_REV)); + menu->addChild(construct(&MenuItem::text, "Trigger pingpong", &SlotCvModeItem::module, module, &SlotCvModeItem::slotCvMode, SLOTCVMODE::TRIG_PINGPONG)); + menu->addChild(construct(&MenuItem::text, "Trigger alternating", &SlotCvModeItem::module, module, &SlotCvModeItem::slotCvMode, SLOTCVMODE::TRIG_ALT)); + menu->addChild(construct(&MenuItem::text, "Trigger random", &SlotCvModeItem::module, module, &SlotCvModeItem::slotCvMode, SLOTCVMODE::TRIG_RANDOM)); + menu->addChild(construct(&MenuItem::text, "Trigger pseudo-random", &SlotCvModeItem::module, module, &SlotCvModeItem::slotCvMode, SLOTCVMODE::TRIG_RANDOM_WO_REPEAT)); + menu->addChild(construct(&MenuItem::text, "Trigger random walk", &SlotCvModeItem::module, module, &SlotCvModeItem::slotCvMode, SLOTCVMODE::TRIG_RANDOM_WALK)); + menu->addChild(construct(&MenuItem::text, "Trigger shuffle", &SlotCvModeItem::module, module, &SlotCvModeItem::slotCvMode, SLOTCVMODE::TRIG_SHUFFLE)); + menu->addChild(construct(&MenuItem::text, "0..10V", &SlotCvModeItem::module, module, &SlotCvModeItem::slotCvMode, SLOTCVMODE::VOLT)); + menu->addChild(construct(&MenuItem::text, "C4", &SlotCvModeItem::module, module, &SlotCvModeItem::slotCvMode, SLOTCVMODE::C4)); + menu->addChild(construct(&MenuItem::text, "Arm", &SlotCvModeItem::module, module, &SlotCvModeItem::slotCvMode, SLOTCVMODE::ARM)); + menu->addChild(new MenuSeparator); + menu->addChild(construct(&MenuItem::text, "Phase", &SlotCvModeItem::module, module, &SlotCvModeItem::slotCvMode, SLOTCVMODE::PHASE)); + menu->addChild(new MenuSeparator); + menu->addChild(construct(&MenuItem::text, "Off", &SlotCvModeItem::rightTextEx, RACK_MOD_SHIFT_NAME "+Q", &SlotCvModeItem::module, module, &SlotCvModeItem::slotCvMode, SLOTCVMODE::OFF)); + })); + + menu->addChild(createSubmenuItem("Port OUT mode", "", [=](Menu* menu) { struct OutModeItem : MenuItem { MODULE* module; OUTMODE outMode; @@ -1147,129 +1232,27 @@ struct TransitWidget : ThemedModuleWidget> { } }; - MODULE* module; - OutModeMenuItem() { - rightText = RIGHT_ARROW; - } - - Menu* createChildMenu() override { - bool phaseMode = module->slotCvMode == SLOTCVMODE::PHASE; - Menu* menu = new Menu; - menu->addChild(construct(&MenuItem::text, "Envelope", &OutModeItem::module, module, &OutModeItem::outMode, OUTMODE::ENV, &OutModeItem::disabled, phaseMode)); - menu->addChild(construct(&MenuItem::text, "Gate", &OutModeItem::module, module, &OutModeItem::outMode, OUTMODE::GATE, &OutModeItem::disabled, phaseMode)); - menu->addChild(construct(&MenuItem::text, "Trigger snapshot change", &OutModeItem::module, module, &OutModeItem::outMode, OUTMODE::TRIG_SNAPSHOT, &OutModeItem::disabled, phaseMode)); - menu->addChild(construct(&MenuItem::text, "Trigger fade start", &OutModeItem::module, module, &OutModeItem::outMode, OUTMODE::TRIG_SOC, &OutModeItem::disabled, phaseMode)); - menu->addChild(construct(&MenuItem::text, "Trigger fade end", &OutModeItem::module, module, &OutModeItem::outMode, OUTMODE::TRIG_EOC, &OutModeItem::disabled, phaseMode)); - menu->addChild(new MenuSeparator); - menu->addChild(construct(&MenuItem::text, "Polyphonic", &OutModeItem::module, module, &OutModeItem::outMode, OUTMODE::POLY, &OutModeItem::disabled, phaseMode)); - menu->addChild(new MenuSeparator); - menu->addChild(construct(&MenuItem::text, "Phase", &OutModeItem::module, module, &OutModeItem::outMode, OUTMODE::PHASE, &OutModeItem::disabled, !phaseMode)); - return menu; - } - }; - - struct BindModuleItem : MenuItem { - MODULE* module; - WIDGET* widget; - void onAction(const event::Action& e) override { - widget->disableLearn(); - module->bindModuleExpander(); - } - }; - - struct BindModuleSelectItem : MenuItem { - WIDGET* widget; - void onAction(const event::Action& e) override { - widget->enableLearn(1); - } - }; - - struct BindParameterItem : MenuItem { - WIDGET* widget; - int mode; - std::string rightText = ""; - void onAction(const event::Action& e) override { - widget->enableLearn(mode); - } - void step() override { - MenuItem::rightText = widget->learn == mode ? "Active" : rightText; - MenuItem::step(); - } - }; - - struct ParameterMenuItem : MenuItem { - struct ParameterItem : MenuItem { - struct IndicateItem : MenuItem { - MODULE* module; - ParamHandleIndicator* handle; - void onAction(const event::Action& e) override { - ModuleWidget* mw = APP->scene->rack->getModule(handle->moduleId); - handle->indicate(mw); - } - }; - - struct UnbindItem : MenuItem { - MODULE* module; - ParamHandleIndicator* handle; - void onAction(const event::Action& e) override { - APP->engine->updateParamHandle(handle, -1, 0, true); - } - }; - - MODULE* module; - ParamHandleIndicator* handle; - ParameterItem() { - rightText = RIGHT_ARROW; - } - Menu* createChildMenu() override { - Menu* menu = new Menu; - menu->addChild(construct(&MenuItem::text, "Locate and indicate", &IndicateItem::module, module, &IndicateItem::handle, handle)); - menu->addChild(construct(&MenuItem::text, "Unbind", &UnbindItem::module, module, &UnbindItem::handle, handle)); - return menu; - } - }; - - MODULE* module; - ParameterMenuItem() { - rightText = RIGHT_ARROW; - } - - Menu* createChildMenu() override { - Menu* menu = new Menu; - for (size_t i = 0; i < module->sourceHandles.size(); i++) { - ParamHandleIndicator* handle = module->sourceHandles[i]; - ModuleWidget* moduleWidget = APP->scene->rack->getModule(handle->moduleId); - if (!moduleWidget) continue; - ParamWidget* paramWidget = moduleWidget->getParam(handle->paramId); - if (!paramWidget) continue; - - std::string text = string::f("%s %s", moduleWidget->model->name.c_str(), paramWidget->getParamQuantity()->getLabel().c_str()); - menu->addChild(construct(&MenuItem::text, text, &ParameterItem::module, module, &ParameterItem::handle, handle)); - } - return menu; - } - }; - - struct ModuleMenuItem : MenuItem { - MODULE* module; - ModuleMenuItem() { - rightText = RIGHT_ARROW; - } + bool phaseMode = module->slotCvMode == SLOTCVMODE::PHASE; + menu->addChild(construct(&MenuItem::text, "Envelope", &OutModeItem::module, module, &OutModeItem::outMode, OUTMODE::ENV, &OutModeItem::disabled, phaseMode)); + menu->addChild(construct(&MenuItem::text, "Gate", &OutModeItem::module, module, &OutModeItem::outMode, OUTMODE::GATE, &OutModeItem::disabled, phaseMode)); + menu->addChild(construct(&MenuItem::text, "Trigger snapshot change", &OutModeItem::module, module, &OutModeItem::outMode, OUTMODE::TRIG_SNAPSHOT, &OutModeItem::disabled, phaseMode)); + menu->addChild(construct(&MenuItem::text, "Trigger fade start", &OutModeItem::module, module, &OutModeItem::outMode, OUTMODE::TRIG_SOC, &OutModeItem::disabled, phaseMode)); + menu->addChild(construct(&MenuItem::text, "Trigger fade end", &OutModeItem::module, module, &OutModeItem::outMode, OUTMODE::TRIG_EOC, &OutModeItem::disabled, phaseMode)); + menu->addChild(new MenuSeparator); + menu->addChild(construct(&MenuItem::text, "Polyphonic", &OutModeItem::module, module, &OutModeItem::outMode, OUTMODE::POLY, &OutModeItem::disabled, phaseMode)); + menu->addChild(new MenuSeparator); + menu->addChild(construct(&MenuItem::text, "Phase", &OutModeItem::module, module, &OutModeItem::outMode, OUTMODE::PHASE, &OutModeItem::disabled, !phaseMode)); + })); - Menu* createChildMenu() override { - struct UnbindItem : MenuItem { - MODULE* module; - int64_t moduleId; - void onAction(const event::Action& e) override { - for (size_t i = 0; i < module->sourceHandles.size(); i++) { - ParamHandle* handle = module->sourceHandles[i]; - if (handle->moduleId != moduleId) continue; - APP->engine->updateParamHandle(handle, -1, 0, true); - } - } - }; + menu->addChild(new MenuSeparator()); + menu->addChild(createMenuItem("Bind module (left)", "", [=]() { disableLearn(); module->bindModuleExpander(); })); + menu->addChild(createMenuItem("Bind module (select)", "", [=]() { enableLearn(1); })); + menu->addChild(construct(&MenuItem::text, "Bind single parameter", &BindParameterItem::rightText, RACK_MOD_SHIFT_NAME "+B", &BindParameterItem::widget, this, &BindParameterItem::mode, 2)); + menu->addChild(construct(&MenuItem::text, "Bind multiple parameters", &BindParameterItem::rightText, RACK_MOD_SHIFT_NAME "+A", &BindParameterItem::widget, this, &BindParameterItem::mode, 3)); - Menu* menu = new Menu; + if (module->sourceHandles.size() > 0) { + menu->addChild(new MenuSeparator()); + menu->addChild(createSubmenuItem("Bound modules", "", [=](Menu* menu) { std::set moduleIds; for (size_t i = 0; i < module->sourceHandles.size(); i++) { ParamHandle* handle = module->sourceHandles[i]; @@ -1281,28 +1264,31 @@ struct TransitWidget : ThemedModuleWidget> { ModuleWidget* moduleWidget = APP->scene->rack->getModule(moduleId); if (!moduleWidget) continue; std::string text = string::f("Unbind \"%s %s\"", moduleWidget->model->plugin->name.c_str(), moduleWidget->model->name.c_str()); - menu->addChild(construct(&MenuItem::text, text, &UnbindItem::module, module, &UnbindItem::moduleId, moduleId)); + menu->addChild(createMenuItem(text, "", [=]() { + for (size_t i = 0; i < module->sourceHandles.size(); i++) { + ParamHandle* handle = module->sourceHandles[i]; + if (handle->moduleId != moduleId) continue; + APP->engine->updateParamHandle(handle, -1, 0, true); + } + })); } - return menu; - } - }; + })); - menu->addChild(new MenuSeparator()); - menu->addChild(construct(&MenuItem::text, "Hide mapping indicators", &MappingIndicatorHiddenItem::module, module)); - menu->addChild(precisionMenuItem); - menu->addChild(new MenuSeparator()); - menu->addChild(construct(&MenuItem::text, "Port CV mode", &SlotCvModeMenuItem::module, module)); - menu->addChild(construct(&MenuItem::text, "Port OUT mode", &OutModeMenuItem::module, module)); - menu->addChild(new MenuSeparator()); - menu->addChild(construct(&MenuItem::text, "Bind module (left)", &BindModuleItem::widget, this, &BindModuleItem::module, module)); - menu->addChild(construct(&MenuItem::text, "Bind module (select)", &BindModuleSelectItem::widget, this)); - menu->addChild(construct(&MenuItem::text, "Bind single parameter", &BindParameterItem::rightText, RACK_MOD_SHIFT_NAME "+B", &BindParameterItem::widget, this, &BindParameterItem::mode, 2)); - menu->addChild(construct(&MenuItem::text, "Bind multiple parameters", &BindParameterItem::rightText, RACK_MOD_SHIFT_NAME "+A", &BindParameterItem::widget, this, &BindParameterItem::mode, 3)); - - if (module->sourceHandles.size() > 0) { - menu->addChild(new MenuSeparator()); - menu->addChild(construct(&MenuItem::text, "Bound modules", &ModuleMenuItem::module, module)); - menu->addChild(construct(&MenuItem::text, "Bound parameters", &ParameterMenuItem::module, module)); + menu->addChild(createSubmenuItem("Bound parameters", "", [=](Menu* menu) { + for (size_t i = 0; i < module->sourceHandles.size(); i++) { + ParamHandleIndicator* handle = module->sourceHandles[i]; + ModuleWidget* moduleWidget = APP->scene->rack->getModule(handle->moduleId); + if (!moduleWidget) continue; + ParamWidget* paramWidget = moduleWidget->getParam(handle->paramId); + if (!paramWidget) continue; + + std::string text = string::f("%s %s", moduleWidget->model->name.c_str(), paramWidget->getParamQuantity()->getLabel().c_str()); + menu->addChild(createSubmenuItem(text, "", [=](Menu* menu) { + menu->addChild(createMenuItem("Locate and indicate", "", [=]() { handle->indicate(APP->scene->rack->getModule(handle->moduleId)); })); + menu->addChild(createMenuItem("Unbind", "", [=]() { APP->engine->updateParamHandle(handle, -1, 0, true); })); + })); + } + })); } } }; diff --git a/src/components/LedTextDisplay.hpp b/src/components/LedTextDisplay.hpp index 138c025..62c3c4b 100644 --- a/src/components/LedTextDisplay.hpp +++ b/src/components/LedTextDisplay.hpp @@ -57,7 +57,7 @@ struct LedTextDisplay : OpaqueWidget { nvgFillColor(args.vg, color); nvgFontFaceId(args.vg, font->handle); nvgTextLetterSpacing(args.vg, 0.0); - nvgFontSize(args.vg, 12); + nvgFontSize(args.vg, fontSize); nvgTextBox(args.vg, textOffset.x, textOffset.y + fontSize, box.size.x - 2 * textOffset.x, text.c_str(), NULL); } nvgResetScissor(args.vg); diff --git a/src/components/LogDisplay.hpp b/src/components/LogDisplay.hpp new file mode 100644 index 0000000..fed9e5d --- /dev/null +++ b/src/components/LogDisplay.hpp @@ -0,0 +1,43 @@ +#pragma once +#include "../plugin.hpp" + +namespace StoermelderPackOne { + +struct LogDisplay : LedTextDisplay { + std::list>* buffer; + bool dirty = true; + + LogDisplay() { + color = nvgRGB(0xf0, 0xf0, 0xf0); + bgColor.a = 0.f; + fontSize = 9.2f; + textOffset.y += 2.f; + } + + void step() override { + LedTextDisplay::step(); + if (dirty) { + text = ""; + size_t size = std::min(buffer->size(), (size_t)(box.size.x / fontSize) + 1); + size_t i = 0; + for (std::tuple s : *buffer) { + if (i >= size) break; + float timestamp = std::get<0>(s); + if (timestamp >= 0.f) { + text += string::f("[%9.4f] %s\n", timestamp, std::get<1>(s).c_str()); + } + else { + text += string::f("%s\n", std::get<1>(s).c_str()); + } + i++; + } + } + } + + void reset() { + buffer->clear(); + dirty = true; + } +}; + +} \ No newline at end of file diff --git a/src/components/MenuColorField.hpp b/src/components/MenuColorField.hpp index 890f987..e8f103e 100644 --- a/src/components/MenuColorField.hpp +++ b/src/components/MenuColorField.hpp @@ -5,8 +5,8 @@ namespace StoermelderPackOne { struct MenuColorField : ui::TextField { - bool firstRun = true; - MenuColorLabel* colorLabel = NULL; + NVGcolor* color; + NVGcolor textColor; bool* textSelected = NULL; MenuColorField() { @@ -14,23 +14,24 @@ struct MenuColorField : ui::TextField { } void step() override { - if (firstRun) { - text = color::toHexString(initColor()); - firstRun = false; + if (!color::isEqual(*color, textColor)) { + // color has been modified outside of this widget + text = color::toHexString(*color); + textColor = *color; } ui::TextField::step(); } void onSelectKey(const event::SelectKey& e) override { - if (colorLabel) { - colorLabel->fillColor = color::fromHexString(rack::string::trim(text)); - } if (e.action == GLFW_PRESS && e.key == GLFW_KEY_ENTER) { - returnColor(color::fromHexString(rack::string::trim(text))); ui::MenuOverlay* overlay = getAncestorOfType(); overlay->requestDelete(); e.consume(this); } + if (e.action == GLFW_RELEASE) { + *color = color::fromHexString(rack::string::trim(text)); + textColor = *color; + } if (!e.getTarget()) { ui::TextField::onSelectKey(e); } @@ -40,9 +41,6 @@ struct MenuColorField : ui::TextField { if (textSelected) *textSelected = false; TextField::onButton(e); } - - virtual void returnColor(NVGcolor color) { } - virtual NVGcolor initColor() { return color::BLACK; } }; diff --git a/src/components/MenuColorLabel.hpp b/src/components/MenuColorLabel.hpp index d491579..f10b831 100644 --- a/src/components/MenuColorLabel.hpp +++ b/src/components/MenuColorLabel.hpp @@ -4,7 +4,7 @@ namespace StoermelderPackOne { struct MenuColorLabel : MenuLabel { - NVGcolor fillColor; + NVGcolor* fillColor; MenuColorLabel() { box.size.y *= 1.4f; @@ -12,7 +12,7 @@ struct MenuColorLabel : MenuLabel { void draw(const DrawArgs& args) override { nvgBeginPath(args.vg); nvgRoundedRect(args.vg, 2.f, 2.f, box.size.x - 4.f, box.size.y - 4.f, 2.f); - nvgFillColor(args.vg, fillColor); + nvgFillColor(args.vg, *fillColor); nvgFill(args.vg); } }; // struct MenuColorLabel diff --git a/src/components/MenuColorPicker.hpp b/src/components/MenuColorPicker.hpp new file mode 100644 index 0000000..23b3e6b --- /dev/null +++ b/src/components/MenuColorPicker.hpp @@ -0,0 +1,309 @@ +#pragma once +#include "plugin.hpp" + +namespace StoermelderPackOne { + +struct MenuColorPicker : MenuEntry { + NVGcolor* color; + NVGcolor hslcolor; + float h = 0.5f; + float s = 1.f; + float l = 0.5f; + + struct hGradient : OpaqueWidget { + MenuColorPicker* picker; + + hGradient(MenuColorPicker* picker) { + this->picker = picker; + } + + void draw(const DrawArgs& args) override { + nvgBeginPath(args.vg); + float x = box.size.x - 4.f; + float y = box.size.y - 4.f; + nvgRoundedRect(args.vg, 2.f, 2.f, x, y, 3.f); + + for (int i = 0; i < 6; i++) { + float x1 = float(i) * 1.f / 6.f; + float x2 = (float(i) + 1.f) * 1.f / 6.f; + + nvgScissor(args.vg, x1 * box.size.x, 0.f, x2 * box.size.x + 0.3f, box.size.y); + NVGpaint paint = nvgLinearGradient(args.vg, x1 * box.size.x, 0.f, x2 * box.size.x, 0.f, nvgHSL(x1, picker->s, picker->l), nvgHSL(x2, picker->s, picker->l)); + nvgFillPaint(args.vg, paint); + nvgFill(args.vg); + nvgResetScissor(args.vg); + } + } + }; + + struct hSlider : ui::Slider { + struct hQuantity : Quantity { + MenuColorPicker* picker; + + hQuantity(MenuColorPicker* picker) { + this->picker = picker; + } + void setValue(float value) override { + picker->h = clamp(value, 0.f, 1.f); + picker->updateColor(); + } + float getValue() override { + return picker->h; + } + float getDefaultValue() override { + return 0.f; + } + float getMinValue() override { + return 0.f; + } + float getMaxValue() override { + return 1.f; + } + float getDisplayValue() override { + return getValue(); + } + std::string getDisplayValueString() override { + return string::f("%.2f", picker->h * 360.f); + } + void setDisplayValue(float displayValue) override { + setValue(displayValue); + } + std::string getLabel() override { + return "Hue"; + } + std::string getUnit() override { + return "°"; + } + }; + + hSlider(MenuColorPicker* picker) { + quantity = new hQuantity(picker); + } + ~hSlider() { + delete quantity; + } + }; + + struct sGradient : OpaqueWidget { + MenuColorPicker* picker; + + sGradient(MenuColorPicker* picker) { + this->picker = picker; + } + + void draw(const DrawArgs& args) override { + nvgBeginPath(args.vg); + float x = box.size.x - 4.f; + float y = box.size.y - 4.f; + nvgRoundedRect(args.vg, 2.f, 2.f, x, y, 3.f); + NVGpaint paint = nvgLinearGradient(args.vg, 0.f, 0.f, box.size.x, 0.f, nvgHSL(picker->h, 0.f, picker->l), nvgHSL(picker->h, 1.f, picker->l)); + nvgFillPaint(args.vg, paint); + nvgFill(args.vg); + } + }; + + struct sSlider : ui::Slider { + struct sQuantity : Quantity { + MenuColorPicker* picker; + + sQuantity(MenuColorPicker* picker) { + this->picker = picker; + } + void setValue(float value) override { + picker->s = clamp(value, 0.f, 1.f); + picker->updateColor(); + } + float getValue() override { + return picker->s; + } + float getDefaultValue() override { + return 1.f; + } + float getMinValue() override { + return 0.f; + } + float getMaxValue() override { + return 1.f; + } + float getDisplayValue() override { + return getValue(); + } + std::string getDisplayValueString() override { + return string::f("%.2f", picker->s * 100.f); + } + void setDisplayValue(float displayValue) override { + setValue(displayValue); + } + std::string getLabel() override { + return "Saturation"; + } + std::string getUnit() override { + return "%"; + } + }; + + sSlider(MenuColorPicker* picker) { + quantity = new sQuantity(picker); + } + ~sSlider() { + delete quantity; + } + }; + + struct lGradient : OpaqueWidget { + MenuColorPicker* picker; + + lGradient(MenuColorPicker* picker) { + this->picker = picker; + } + + void draw(const DrawArgs& args) override { + nvgBeginPath(args.vg); + float x = box.size.x - 4.f; + float y = box.size.y - 4.f; + nvgRoundedRect(args.vg, 2.f, 2.f, x, y, 3.f); + NVGpaint paint = nvgLinearGradient(args.vg, 0.f, 0.f, box.size.x, 0.f, nvgHSL(picker->h, picker->s, 0.f), nvgHSL(picker->h, picker->s, 1.f)); + nvgFillPaint(args.vg, paint); + nvgFill(args.vg); + } + }; + + struct lSlider : ui::Slider { + struct lQuantity : Quantity { + MenuColorPicker* picker; + + lQuantity(MenuColorPicker* picker) { + this->picker = picker; + } + void setValue(float value) override { + picker->l = clamp(value, 0.f, 1.f); + picker->updateColor(); + } + float getValue() override { + return picker->l; + } + float getDefaultValue() override { + return 0.5f; + } + float getMinValue() override { + return 0.f; + } + float getMaxValue() override { + return 1.f; + } + float getDisplayValue() override { + return getValue(); + } + std::string getDisplayValueString() override { + return string::f("%.2f", picker->l * 100.f); + } + void setDisplayValue(float displayValue) override { + setValue(displayValue); + } + std::string getLabel() override { + return "Lightness"; + } + std::string getUnit() override { + return "%"; + } + }; + + lSlider(MenuColorPicker* picker) { + quantity = new lQuantity(picker); + } + ~lSlider() { + delete quantity; + } + }; + + MenuColorPicker() { + const float width = 280.f; + const float pad = 4.0f; + + hGradient* hgradient = new hGradient(this); + hgradient->box.size = Vec(width, 50.f); + addChild(hgradient); + + hSlider* hslider = new hSlider(this); + hslider->box.pos = hgradient->box.getBottomLeft() + Vec(pad, -BND_WIDGET_HEIGHT - pad); + hslider->box.size = Vec(width - 2.f * pad, BND_WIDGET_HEIGHT); + addChild(hslider); + + sGradient* sgradient = new sGradient(this); + sgradient->box.pos = Vec(0.f, hgradient->box.getBottomLeft().y + 2.f); + sgradient->box.size = Vec(width, 50.f); + addChild(sgradient); + + sSlider* sslider = new sSlider(this); + sslider->box.pos = sgradient->box.getBottomLeft() + Vec(pad, -BND_WIDGET_HEIGHT - pad); + sslider->box.size = Vec(width - 2.f * pad, BND_WIDGET_HEIGHT); + addChild(sslider); + + lGradient* lgradient = new lGradient(this); + lgradient->box.pos = Vec(0.f, sgradient->box.getBottomLeft().y + 2.f); + lgradient->box.size = Vec(width, 50.f); + addChild(lgradient); + + lSlider* lslider = new lSlider(this); + lslider->box.pos = lgradient->box.getBottomLeft() + Vec(pad, -BND_WIDGET_HEIGHT - pad); + lslider->box.size = Vec(width - 2.f * pad, BND_WIDGET_HEIGHT); + addChild(lslider); + + box.size = Vec(width, lgradient->box.getBottomLeft().y); + } + + void draw(const DrawArgs& args) override { + bndMenuLabel(args.vg, 0.0, 0.0, box.size.x, box.size.y, -1, ""); + OpaqueWidget::draw(args); + } + + void step() override { + if (!color::isEqual(*color, hslcolor)) { + // color has been modified outside of this widget + + // Convert rgb to hsl + // Find greatest and smallest channel values + float cmin = std::min(color->r, std::min(color->g, color->b)); + float cmax = std::max(color->r, std::max(color->g, color->b)); + float delta = cmax - cmin; + + // Calculate hue + // No difference + if (delta == 0.f) + h = 0.f; + // Red is max + else if (cmax == color->r) + h = int((color->g - color->b) / delta) % 6; + // Green is max + else if (cmax == color->g) + h = (color->b - color->r) / delta + 2.f; + // Blue is max + else + h = (color->r - color->g) / delta + 4.f; + + h = round(h * 60.f); + // Make negative hues positive behind 360° + if (h < 0.f) + h += 360.f; + + h /= 360.f; + // Calculate lightness + l = (cmax + cmin) / 2; + // Calculate saturation + s = delta == 0 ? 0 : delta / (1 - abs(2 * l - 1)); + + hslcolor = *color; + } + OpaqueWidget::step(); + } + + void onAction(const ActionEvent& e) override { + e.consume(this); + } + + void updateColor() { + *color = hslcolor = nvgHSL(h, s, l); + } +}; // struct MenuColorPicker + +} // namespace StoermelderPackOne \ No newline at end of file diff --git a/src/components/ParamHandleIndicator.hpp b/src/components/ParamHandleIndicator.hpp index fbdcc42..3069028 100644 --- a/src/components/ParamHandleIndicator.hpp +++ b/src/components/ParamHandleIndicator.hpp @@ -1,5 +1,6 @@ #pragma once #include "plugin.hpp" +#include "../ui/ViewportHelper.hpp" namespace StoermelderPackOne { diff --git a/src/helpers.hpp b/src/helpers.hpp index 20b18d7..6e037d3 100644 --- a/src/helpers.hpp +++ b/src/helpers.hpp @@ -5,121 +5,6 @@ namespace StoermelderPackOne { namespace Rack { -/** Move the view-port smoothly and center a Widget - */ -struct ViewportCenterSmooth { - Vec source, target; - float sourceZoom, targetZoom; - int framecount = 0; - int frame = 0; - - void trigger(Widget* w, float zoom, float framerate, float transitionTime = 1.f) { - Vec target = w->getBox().getCenter(); - zoom = std::pow(2.f, zoom); - trigger(target, zoom, framerate, transitionTime); - } - - void trigger(Rect rect, float framerate, float transitionTime = 1.f) { - float zx = APP->scene->rackScroll->box.size.x / rect.size.x * 0.9f; - float zy = APP->scene->rackScroll->box.size.y / rect.size.y * 0.9f; - float zoom = std::min(zx, zy); - trigger(rect.getCenter(), zoom, framerate, transitionTime); - } - - void trigger(Vec target, float zoom, float framerate, float transitionTime = 1.f) { - // source is at top-left, translate to center of screen - Vec source = APP->scene->rackScroll->offset / APP->scene->rackScroll->getZoom(); - Vec center = APP->scene->rackScroll->getSize() * (1.f / APP->scene->rackScroll->getZoom()) * 0.5f; - - this->source = source + center; - this->target = target; - this->sourceZoom = APP->scene->rackScroll->getZoom(); - this->targetZoom = zoom; - this->framecount = int(transitionTime * framerate); - this->frame = 0; - } - - void reset() { - frame = framecount = 0; - } - - void process() { - if (framecount == frame) return; - - float t = float(frame) / float(framecount - 1); - // Sigmoid - t = t * 8.f - 4.f; - t = 1.f / (1.f + std::exp(-t)); - t = rescale(t, 0.0179f, 0.98201f, 0.f, 1.f); - - // Calculate interpolated view-point and zoom - Vec p1 = source.mult(1.f - t); - Vec p2 = target.mult(t); - Vec p = p1.plus(p2); - - // Ignore tiny changes in zoom as they will cause graphical artifacts - if (std::abs(sourceZoom - targetZoom) > 0.01f) { - float z = sourceZoom * (1.f - t) + targetZoom * t; - APP->scene->rackScroll->setZoom(z); - } - - // Move the view - Vec center = APP->scene->rackScroll->getSize() * (1.f / APP->scene->rackScroll->getZoom()) * 0.5f; - APP->scene->rackScroll->setGridOffset((p - center - RACK_OFFSET) / RACK_GRID_SIZE); - - frame++; - } -}; - -struct ViewportCenter { - ViewportCenter(Widget* w, float zoomToWidget = -1.f, float zoom = -1.f) { - float z; - if (zoomToWidget > 0.f) - z = APP->scene->rackScroll->getSize().y / w->getSize().y * zoomToWidget; - else if (zoom > 0.f) - z = std::pow(2.f, zoom); - else - z = 2.0f; - Vec target = w->getBox().getCenter(); - Vec viewport = APP->scene->rackScroll->getSize() * (1.f / z); - APP->scene->rackScroll->setZoom(z); - APP->scene->rackScroll->setGridOffset((target - viewport * 0.5f - RACK_OFFSET) / RACK_GRID_SIZE); - } - - ViewportCenter(Vec target) { - float z = APP->scene->rackScroll->getZoom(); - Vec viewport = APP->scene->rackScroll->getSize() * (1.f / z); - APP->scene->rackScroll->setZoom(z); - APP->scene->rackScroll->setGridOffset((target - viewport * 0.5f - RACK_OFFSET) / RACK_GRID_SIZE); - } - - ViewportCenter(Rect rect) { - Vec target = rect.getCenter(); - float zx = APP->scene->rackScroll->getSize().x / rect.size.x * 0.9f; - float zy = APP->scene->rackScroll->getSize().y / rect.size.y * 0.9f; - float z = std::min(zx, zy); - Vec viewport = APP->scene->rackScroll->getSize() * (1.f / z); - APP->scene->rackScroll->setZoom(z); - APP->scene->rackScroll->setGridOffset((target - viewport * 0.5f - RACK_OFFSET) / RACK_GRID_SIZE); - } -}; - -struct ViewportTopLeft { - ViewportTopLeft(Widget* w, float zoomToWidget = -1.f, float zoom = -1.f) { - float z; - if (zoomToWidget > 0.f) - z = APP->scene->rackScroll->getSize().y / w->getSize().y * zoomToWidget; - else if (zoom > 0.f) - z = std::pow(2.f, zoom); - else - z = 2.0f; - Vec target = w->getBox().getTopLeft(); - APP->scene->rackScroll->setZoom(z); - APP->scene->rackScroll->setGridOffset((target - RACK_OFFSET) / RACK_GRID_SIZE); - } -}; - - /** Creates a MenuItem that when hovered, opens a submenu with several MenuItems identified by a map. Example: menu->addChild(createMapSubmenuItem("Mode", diff --git a/src/mb/Mb_v06.hpp b/src/mb/Mb_v06.hpp index 337f85b..9aacdb2 100644 --- a/src/mb/Mb_v06.hpp +++ b/src/mb/Mb_v06.hpp @@ -174,6 +174,9 @@ struct ModelItem : BrowserListItem { if (!moduleWidget) return; APP->scene->rack->addModuleAtMouse(moduleWidget); + // Load template preset + moduleWidget->loadTemplate(); + // Push ModuleAdd history action history::ModuleAdd* h = new history::ModuleAdd; h->name = "create module"; diff --git a/src/mb/Mb_v1.cpp b/src/mb/Mb_v1.cpp index 742cbed..8b5cc85 100644 --- a/src/mb/Mb_v1.cpp +++ b/src/mb/Mb_v1.cpp @@ -135,6 +135,9 @@ static ModuleWidget* chooseModel(plugin::Model* model) { assert(moduleWidget); APP->scene->rack->addModuleAtMouse(moduleWidget); + // Load template preset + moduleWidget->loadTemplate(); + // Push ModuleAdd history action history::ModuleAdd* h = new history::ModuleAdd; h->name = "create module"; @@ -583,7 +586,7 @@ struct BrowserSearchField : ui::TextField { browser->favorites ^= true; e.consume(this); } - if ((e.mods & RACK_MOD_MASK) == GLFW_MOD_CONTROL) { + if ((e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) { ModuleBrowser* browser = getAncestorOfType(); browser->hidden ^= true; setText(string::trim(text)); diff --git a/src/pluginsettings.hpp b/src/pluginsettings.hpp index 2fe37d7..453774b 100644 --- a/src/pluginsettings.hpp +++ b/src/pluginsettings.hpp @@ -2,7 +2,7 @@ struct StoermelderSettings { - int panelThemeDefault = 0; + int panelThemeDefault = -1; json_t* mbModelsJ; float mbV1zoom = 0.85f; diff --git a/src/ui/ThemedModuleWidget.hpp b/src/ui/ThemedModuleWidget.hpp index 245b833..e74c0e5 100644 --- a/src/ui/ThemedModuleWidget.hpp +++ b/src/ui/ThemedModuleWidget.hpp @@ -60,18 +60,6 @@ struct ThemedModuleWidget : BASE { } Menu* createChildMenu() override { - struct PanelThemeItem : MenuItem { - MODULE* module; - int theme; - void onAction(const event::Action& e) override { - module->panelTheme = theme; - } - void step() override { - rightText = module->panelTheme == theme ? "✔" : ""; - MenuItem::step(); - } - }; - struct PanelThemeDefaultItem : MenuItem { int theme; void onAction(const event::Action& e) override { @@ -85,11 +73,22 @@ struct ThemedModuleWidget : BASE { }; Menu* menu = new Menu; - menu->addChild(construct(&MenuItem::text, "Blue", &PanelThemeItem::module, module, &PanelThemeItem::theme, 0)); - menu->addChild(construct(&MenuItem::text, "Dark", &PanelThemeItem::module, module, &PanelThemeItem::theme, 1)); + menu->addChild(StoermelderPackOne::Rack::createValuePtrMenuItem("Blue", &module->panelTheme, 0)); + menu->addChild(StoermelderPackOne::Rack::createValuePtrMenuItem("Dark", &module->panelTheme, 1)); menu->addChild(new MenuSeparator); menu->addChild(construct(&MenuItem::text, "Blue as default", &PanelThemeDefaultItem::theme, 0)); menu->addChild(construct(&MenuItem::text, "Dark as default", &PanelThemeDefaultItem::theme, 1)); + menu->addChild(new MenuSeparator); + menu->addChild(createBoolMenuItem("Use Rack setting", "", + [=]() { + return module->panelTheme == -1; + }, + [=](bool b) { + pluginSettings.panelThemeDefault = -1; + pluginSettings.saveToJson(); + module->panelTheme = -1; + } + )); return menu; } }; @@ -100,9 +99,17 @@ struct ThemedModuleWidget : BASE { } void step() override { - if (module && module->panelTheme != panelTheme) { - panelTheme = module->panelTheme; - BASE::setPanel(APP->window->loadSvg(asset::plugin(pluginInstance, panel()))); + if (module) { + if (module->panelTheme == -1) { + if ((int)settings::preferDarkPanels != panelTheme) { + panelTheme = (int)settings::preferDarkPanels; + BASE::setPanel(APP->window->loadSvg(asset::plugin(pluginInstance, panel()))); + } + } + else if (module->panelTheme != panelTheme) { + panelTheme = module->panelTheme; + BASE::setPanel(APP->window->loadSvg(asset::plugin(pluginInstance, panel()))); + } } BASE::step(); } diff --git a/src/ui/ViewportHelper.hpp b/src/ui/ViewportHelper.hpp new file mode 100644 index 0000000..b680551 --- /dev/null +++ b/src/ui/ViewportHelper.hpp @@ -0,0 +1,132 @@ +#pragma once +#include "plugin.hpp" + +namespace StoermelderPackOne { +namespace Rack { + +/** Move the view-port smoothly and center a Widget + */ +struct ViewportCenterSmooth { + Vec source, target; + float sourceZoom, targetZoom; + int framecount = 0; + int frame = 0; + + void trigger(Widget* w, float zoom, float framerate, float transitionTime = 1.f) { + Vec target = w->getBox().getCenter(); + zoom = std::pow(2.f, zoom); + trigger(target, zoom, framerate, transitionTime); + } + + void trigger(Rect rect, float framerate, float transitionTime = 1.f) { + float zx = APP->scene->rackScroll->box.size.x / rect.size.x * 0.9f; + float zy = APP->scene->rackScroll->box.size.y / rect.size.y * 0.9f; + float zoom = std::min(zx, zy); + trigger(rect.getCenter(), zoom, framerate, transitionTime); + } + + void trigger(Vec target, float zoom, float framerate, float transitionTime = 1.f) { + // source is at top-left, translate to center of screen + Vec source = APP->scene->rackScroll->offset / APP->scene->rackScroll->getZoom(); + Vec center = APP->scene->rackScroll->getSize() * (1.f / APP->scene->rackScroll->getZoom()) * 0.5f; + + this->source = source + center; + this->target = target; + this->sourceZoom = APP->scene->rackScroll->getZoom(); + this->targetZoom = zoom; + this->framecount = int(transitionTime * framerate); + this->frame = 0; + } + + void reset() { + frame = framecount = 0; + } + + void process() { + if (framecount == frame) return; + + float t = float(frame) / float(framecount - 1); + // Sigmoid + t = t * 8.f - 4.f; + t = 1.f / (1.f + std::exp(-t)); + t = rescale(t, 0.0179f, 0.98201f, 0.f, 1.f); + + // Calculate interpolated view-point and zoom + Vec p1 = source.mult(1.f - t); + Vec p2 = target.mult(t); + Vec p = p1.plus(p2); + + // Ignore tiny changes in zoom as they will cause graphical artifacts + if (std::abs(sourceZoom - targetZoom) > 0.01f) { + float z = sourceZoom * (1.f - t) + targetZoom * t; + APP->scene->rackScroll->setZoom(z); + } + + // Move the view + Vec center = APP->scene->rackScroll->getSize() * (1.f / APP->scene->rackScroll->getZoom()) * 0.5f; + APP->scene->rackScroll->setGridOffset((p - center - RACK_OFFSET) / RACK_GRID_SIZE); + + frame++; + } +}; + +struct ViewportCenter { + ViewportCenter(Widget* w, float zoomToWidget = -1.f, float zoom = std::numeric_limits::infinity()) { + float z; + if (zoomToWidget > 0.f) + z = APP->scene->rackScroll->getSize().y / w->getSize().y * zoomToWidget; + else if (zoom != std::numeric_limits::infinity()) + z = std::pow(2.f, zoom); + else + z = 2.0f; + Vec target = w->getBox().getCenter(); + Vec viewport = APP->scene->rackScroll->getSize() * (1.f / z); + + float oldZoom = APP->scene->rackScroll->getZoom(); + APP->scene->rackScroll->setZoom(z); + APP->scene->rackScroll->setGridOffset((target - viewport * 0.5f - RACK_OFFSET) / RACK_GRID_SIZE); + if (zoom == std::numeric_limits::infinity() && zoomToWidget == -1.f) { + APP->scene->rackScroll->setZoom(oldZoom); + } + } + + ViewportCenter(Vec target) { + float z = APP->scene->rackScroll->getZoom(); + Vec viewport = APP->scene->rackScroll->getSize() * (1.f / z); + APP->scene->rackScroll->setZoom(z); + APP->scene->rackScroll->setGridOffset((target - viewport * 0.5f - RACK_OFFSET) / RACK_GRID_SIZE); + } + + ViewportCenter(Rect rect) { + Vec target = rect.getCenter(); + float zx = APP->scene->rackScroll->getSize().x / rect.size.x * 0.9f; + float zy = APP->scene->rackScroll->getSize().y / rect.size.y * 0.9f; + float z = std::min(zx, zy); + Vec viewport = APP->scene->rackScroll->getSize() * (1.f / z); + APP->scene->rackScroll->setZoom(z); + APP->scene->rackScroll->setGridOffset((target - viewport * 0.5f - RACK_OFFSET) / RACK_GRID_SIZE); + } +}; + +struct ViewportTopLeft { + ViewportTopLeft(Widget* w, float zoomToWidget = -1.f, float zoom = std::numeric_limits::infinity()) { + float z; + if (zoomToWidget > 0.f) + z = APP->scene->rackScroll->getSize().y / w->getSize().y * zoomToWidget; + else if (zoom != std::numeric_limits::infinity()) + z = std::pow(2.f, zoom); + else + z = 2.0f; + Vec target = w->getBox().getTopLeft(); + + float oldZoom = APP->scene->rackScroll->getZoom(); + APP->scene->rackScroll->setZoom(z); + APP->scene->rackScroll->setGridOffset((target - RACK_OFFSET) / RACK_GRID_SIZE); + if (zoom == std::numeric_limits::infinity()) { + APP->scene->rackScroll->setZoom(oldZoom); + } + } +}; + +} // namespace Rack +} // namespace StoermelderPackOne \ No newline at end of file