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