Vendored
+2
-1
@@ -22,7 +22,8 @@
|
||||
// Resolved by CMake Tools:
|
||||
"program": "${command:cmake.launchTargetPath}",
|
||||
"args": [
|
||||
"[ProfileThumbnails]"
|
||||
"hw:M2",
|
||||
"hw:M2"
|
||||
],
|
||||
"stopAtEntry": false,
|
||||
"cwd": "${workspaceFolder}",
|
||||
|
||||
+2
-2
@@ -1,13 +1,13 @@
|
||||
cmake_minimum_required(VERSION 3.16.0)
|
||||
project(pipedal
|
||||
VERSION 1.5.92
|
||||
VERSION 1.5.93
|
||||
DESCRIPTION "PiPedal Guitar Effect Pedal For Raspberry Pi"
|
||||
HOMEPAGE_URL "https://rerdavies.github.io/pipedal"
|
||||
)
|
||||
|
||||
execute_process( COMMAND dpkg --print-architecture COMMAND tr -d '\n' OUTPUT_VARIABLE DEBIAN_ARCHITECTURE )
|
||||
|
||||
set (DISPLAY_VERSION "PiPedal v1.5.92-Experimental")
|
||||
set (DISPLAY_VERSION "PiPedal v1.5.93-Experimental")
|
||||
set (PACKAGE_ARCHITECTURE ${DEBIAN_ARCHITECTURE})
|
||||
set (CMAKE_INSTALL_PREFIX "/usr/")
|
||||
|
||||
|
||||
@@ -9,11 +9,11 @@
|
||||
<img src="https://img.shields.io/github/downloads/rerdavies/pipedal/total?color=%23808080&link=https%3A%2F%2Frerdavies.github.io%2Fpipedal%2Fdownload.html"/>
|
||||
|
||||
|
||||
Download: <a href='https://rerdavies.github.io/pipedal/download.html'>v1.5.92</a>
|
||||
Download: <a href='https://rerdavies.github.io/pipedal/download.html'>v1.5.93</a>
|
||||
Website: [https://rerdavies.github.io/pipedal](https://rerdavies.github.io/pipedal).
|
||||
Documentation: [https://rerdavies.github.io/pipedal/Documentation.html](https://rerdavies.github.io/pipedal/Documentation.html).
|
||||
|
||||
#### NEW version 1.5.92 Release. See the [release notes](https://rerdavies.github.io/pipedal/ReleaseNotes) for details. TooB NAM calibration, manipulation of i/r files in Toob Convolution Reverb, new Parametric EQ and 3 Band EQ plugins, and MORE!
|
||||
#### NEW version 1.5.93 Release. See the [release notes](https://rerdavies.github.io/pipedal/ReleaseNotes) for details. TooB NAM calibration, manipulation of i/r files in Toob Convolution Reverb, new Parametric EQ and 3 Band EQ plugins, and MORE!
|
||||
|
||||
|
||||
|
||||
|
||||
+3
-1
@@ -1,5 +1,5 @@
|
||||
#!/bin/bash
|
||||
# Run a CMake build.
|
||||
# Run a Clean Production CMake build.
|
||||
|
||||
set -e
|
||||
|
||||
@@ -14,3 +14,5 @@ cd ..
|
||||
|
||||
time cmake --build ./build --target all --config Release -- -j 3
|
||||
|
||||
./makePackage.sh
|
||||
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
|
||||
2
|
||||
+5
-5
@@ -13,18 +13,18 @@ page_icon: img/Install4.jpg
|
||||
|
||||
Download the most recent Debian (.deb) package for your platform:
|
||||
|
||||
- [Raspberry Pi OS bookworm (aarch64) v1.5.92](https://github.com/rerdavies/pipedal/releases/download/v1.5.92/pipedal_1.5.92_arm64.deb)
|
||||
- [Ubuntu 24.04 through 25.04 (aarch64) v1.5.92](https://github.com/rerdavies/pipedal/releases/download/v1.5.92/pipedal_1.5.92_arm64.deb)
|
||||
- [Ubuntu 24.04 through 25.04 (amd64) v1.5.92](https://github.com/rerdavies/pipedal/releases/download/v1.5.92/pipedal_1.5.92_amd64.deb)
|
||||
- [Raspberry Pi OS bookworm (aarch64) v1.5.93](https://github.com/rerdavies/pipedal/releases/download/v1.5.93/pipedal_1.5.93_arm64.deb)
|
||||
- [Ubuntu 24.04 through 25.04 (aarch64) v1.5.93](https://github.com/rerdavies/pipedal/releases/download/v1.5.93/pipedal_1.5.93_arm64.deb)
|
||||
- [Ubuntu 24.04 through 25.04 (amd64) v1.5.93](https://github.com/rerdavies/pipedal/releases/download/v1.5.93/pipedal_1.5.93_amd64.deb)
|
||||
|
||||
|
||||
Version 1.5.92 has been tested on Raspberry Pi OS bookworm, Ubuntu 24.04 (amd64), Ubuntu 24.10 (aarch64), and Ubuntu 25.04 (aarch64). Download the appropriate package for your platform, and install using the following procedure:
|
||||
Version 1.5.93 has been tested on Raspberry Pi OS bookworm, Ubuntu 24.04 (amd64), Ubuntu 24.10 (aarch64), and Ubuntu 25.04 (aarch64). Download the appropriate package for your platform, and install using the following procedure:
|
||||
|
||||
```
|
||||
sudo apt update
|
||||
sudo apt upgrade
|
||||
cd ~/Downloads
|
||||
sudo apt-get install ./pipedal_1.5.92_arm64.deb
|
||||
sudo apt-get install ./pipedal_1.5.93_arm64.deb
|
||||
```
|
||||
You MUST use `apt-get`. `apt` will not install downloaded packages; and `dpkg -i` will not install dependencies.
|
||||
|
||||
|
||||
@@ -1,5 +1,26 @@
|
||||
# Release Notes
|
||||
|
||||
## PiPedal 1.5.93 Release
|
||||
|
||||
### Major Features in This Build
|
||||
|
||||
- **New TooB Tremolo plugin**. Normal and harmonic tremolo in mono and stereo versions. Harmonic tremolo is a tremolo variant that modulates the amplitude of high-frequency and low-frequency content in opposite phase. It follows the lead of many classic and modern Fender amps, which also provide harmonic tremolo. Stereo harmonic tremolo is particularly smooth and pleasant.
|
||||
|
||||
- **Colored plugin icons**. Tap on the title of the plugin in the middle toolbar to edit the display name of the plugin and choose a color for the icon.
|
||||
|
||||
- **Tools for copying presets between banks**. You can now import presets from another bank using the Presets menu found at the top of the main screen, or you can import presets from another bank, or export presets from the current bank to another bank, using menu commands found in the "Manage Presets" dialog.
|
||||
|
||||
- **Multi-select in the Manage Presets dialog** makes it easier to perform bulk exports to another bank or delete multiple presets. To select multiple presets when using a mouse, press the Ctrl key and click on presets to toggle selection, or press the Shift key and click on a preset to select all presets from the last selected to the newly selected preset. On touch devices, long-press on a preset (without dragging) to start multi-select action mode (an Android touch-UI convention), then tap on individual presets to toggle selection.
|
||||
|
||||
### Bug fixes
|
||||
|
||||
- several bugs relating to the new Factory Presets bank: preset amp model files were not getting properly installed; one preset used an amp model that is not included in factory installed presets. And a new preset showing off the new TooB Tremolo plugin has been added.
|
||||
|
||||
- pipedal_config will no longer accept the --port option without also specifying the --install option. (It used to silently fail).
|
||||
|
||||
- pipedal_latency_test now provides instructions on how to enable missing real-time scheduling permissions for non-root users.
|
||||
|
||||
|
||||
## PiPedal 1.5.92 Release
|
||||
|
||||
### Major Features in This Build
|
||||
|
||||
+4
-4
@@ -4,9 +4,9 @@
|
||||
|
||||
Download the most recent Debian (.deb) package for your platform:
|
||||
|
||||
- [Raspberry Pi OS bookworm (aarch64) v1.5.92 Beta](https://github.com/rerdavies/pipedal/releases/download/v1.5.92/pipedal_1.5.92_arm64.deb)
|
||||
- [Ubuntu 24.x, 25.04 (aarch64) v1.5.92 Alpha](https://github.com/rerdavies/pipedal/releases/download/v1.5.92/pipedal_1.5.92_arm64.deb)
|
||||
- [Ubuntu 24.x, 25.04 (amd64) v1.5.92 Alpha](https://github.com/rerdavies/pipedal/releases/download/v1.5.92/pipedal_1.5.92_amd64.deb)
|
||||
- [Raspberry Pi OS bookworm (aarch64) v1.5.93 Beta](https://github.com/rerdavies/pipedal/releases/download/v1.5.93/pipedal_1.5.93_arm64.deb)
|
||||
- [Ubuntu 24.x, 25.04 (aarch64) v1.5.93 Alpha](https://github.com/rerdavies/pipedal/releases/download/v1.5.93/pipedal_1.5.93_arm64.deb)
|
||||
- [Ubuntu 24.x, 25.04 (amd64) v1.5.93 Alpha](https://github.com/rerdavies/pipedal/releases/download/v1.5.93/pipedal_1.5.93_amd64.deb)
|
||||
|
||||
|
||||
Install the package by running
|
||||
@@ -14,7 +14,7 @@ Install the package by running
|
||||
```
|
||||
sudo apt update
|
||||
cd ~/Downloads
|
||||
sudo apt-get install ./pipedal_1.5.92_arm64.deb
|
||||
sudo apt-get install ./pipedal_1.5.93_arm64.deb
|
||||
```
|
||||
You MUST use `apt-get` to install the package. `apt install` will NOT install the package correctly. The message about missing permissions given by `apt-get` is
|
||||
expected, and can be safely ignored.
|
||||
|
||||
@@ -93,7 +93,7 @@ cabir:impulseFile3
|
||||
doap:license <https://rerdavies.github.io/pipedal/LicenseToobAmp> ;
|
||||
doap:maintainer <http://two-play.com/rerdavies#me> ;
|
||||
lv2:minorVersion 0 ;
|
||||
lv2:microVersion 70 ;
|
||||
lv2:microVersion 71 ;
|
||||
rdfs:comment """
|
||||
|
||||
TooB Cab IR is a convolution-based guitar cabinet impulse response simulator.
|
||||
|
||||
@@ -50,7 +50,7 @@ toob:frequencyResponseVector
|
||||
doap:license <https://rerdavies.github.io/pipedal/LicenseToobAmp> ;
|
||||
doap:maintainer <http://two-play.com/rerdavies#me> ;
|
||||
lv2:minorVersion 0 ;
|
||||
lv2:microVersion 70 ;
|
||||
lv2:microVersion 71 ;
|
||||
|
||||
mod:brand "TooB";
|
||||
mod:label "TooB CabSim";
|
||||
|
||||
@@ -53,7 +53,7 @@ toobimpulse:impulseFile
|
||||
doap:license <https://rerdavies.github.io/pipedal/LicenseToobAmp> ;
|
||||
doap:maintainer <http://two-play.com/rerdavies#me> ;
|
||||
lv2:minorVersion 0 ;
|
||||
lv2:microVersion 70 ;
|
||||
lv2:microVersion 71 ;
|
||||
rdfs:comment """
|
||||
|
||||
Toob Convolution Reverb Stereo uses convolution reverb impulse/response files in order to produce highly
|
||||
|
||||
@@ -51,7 +51,7 @@ toobimpulse:impulseFile
|
||||
doap:license <https://rerdavies.github.io/pipedal/LicenseToobAmp> ;
|
||||
doap:maintainer <http://two-play.com/rerdavies#me> ;
|
||||
lv2:minorVersion 0 ;
|
||||
lv2:microVersion 70 ;
|
||||
lv2:microVersion 71 ;
|
||||
rdfs:comment """
|
||||
|
||||
|
||||
|
||||
@@ -66,7 +66,7 @@ inputStage:filterGroup
|
||||
doap:license <https://two-play.com/TooB/licenses/isc> ;
|
||||
doap:maintainer <http://two-play.com/rerdavies#me> ;
|
||||
lv2:minorVersion 0 ;
|
||||
lv2:microVersion 70 ;
|
||||
lv2:microVersion 71 ;
|
||||
|
||||
mod:brand "TooB";
|
||||
mod:label "TooB Input";
|
||||
|
||||
@@ -68,7 +68,7 @@ pstage:stage3
|
||||
doap:license <https://rerdavies.github.io/pipedal/LicenseToobAmp> ;
|
||||
doap:maintainer <http://two-play.com/rerdavies#me> ;
|
||||
lv2:minorVersion 0 ;
|
||||
lv2:microVersion 70 ;
|
||||
lv2:microVersion 71 ;
|
||||
|
||||
mod:brand "TooB";
|
||||
mod:label "Power Stage";
|
||||
|
||||
@@ -57,7 +57,7 @@
|
||||
doap:license <https://rerdavies.github.io/pipedal/LicenseToobAmp> ;
|
||||
doap:maintainer <http://two-play.com/rerdavies#me> ;
|
||||
lv2:minorVersion 0 ;
|
||||
lv2:microVersion 70 ;
|
||||
lv2:microVersion 71 ;
|
||||
rdfs:comment "TooB spectrum analyzer" ;
|
||||
|
||||
mod:brand "TooB";
|
||||
|
||||
@@ -57,7 +57,7 @@ tonestack:eqGroup
|
||||
doap:license <https://rerdavies.github.io/pipedal/LicenseToobAmp> ;
|
||||
doap:maintainer <http://two-play.com/rerdavies#me> ;
|
||||
lv2:minorVersion 0 ;
|
||||
lv2:microVersion 70 ;
|
||||
lv2:microVersion 71 ;
|
||||
|
||||
uiext:ui <http://two-play.com/plugins/toob-tone-stack-ui>;
|
||||
|
||||
|
||||
@@ -52,7 +52,7 @@ toob:frequencyResponseVector
|
||||
doap:license <https://rerdavies.github.io/pipedal/LicenseToobAmp> ;
|
||||
doap:maintainer <http://two-play.com/rerdavies#me> ;
|
||||
lv2:minorVersion 0 ;
|
||||
lv2:microVersion 70 ;
|
||||
lv2:microVersion 71 ;
|
||||
|
||||
uiext:ui <http://two-play.com/plugins/toob-three-band-eq-ui>;
|
||||
|
||||
|
||||
Binary file not shown.
@@ -40,7 +40,7 @@
|
||||
doap:license <https://rerdavies.github.io/pipedal/LicenseToobAmp> ;
|
||||
doap:maintainer <http://two-play.com/rerdavies#me> ;
|
||||
lv2:minorVersion 0 ;
|
||||
lv2:microVersion 70 ;
|
||||
lv2:microVersion 71 ;
|
||||
rdfs:comment """
|
||||
Emulation of a Boss CE-2 Chorus.
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
doap:license <https://rerdavies.github.io/pipedal/LicenseToobAmp> ;
|
||||
doap:maintainer <http://two-play.com/rerdavies#me> ;
|
||||
lv2:minorVersion 0 ;
|
||||
lv2:microVersion 70 ;
|
||||
lv2:microVersion 71 ;
|
||||
rdfs:comment """
|
||||
A straightforward no-frills digital delay.
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
doap:license <https://rerdavies.github.io/pipedal/LicenseToobAmp> ;
|
||||
doap:maintainer <http://two-play.com/rerdavies#me> ;
|
||||
lv2:minorVersion 0 ;
|
||||
lv2:microVersion 70 ;
|
||||
lv2:microVersion 71 ;
|
||||
rdfs:comment """
|
||||
Emulation of a Boss BF-2 Flanger, based on circuit analysis and simulation of the original.
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
doap:license <https://rerdavies.github.io/pipedal/LicenseToobAmp> ;
|
||||
doap:maintainer <http://two-play.com/rerdavies#me> ;
|
||||
lv2:minorVersion 0 ;
|
||||
lv2:microVersion 70 ;
|
||||
lv2:microVersion 71 ;
|
||||
rdfs:comment """
|
||||
Digital emulation of a Boss BF-2 Flanger.
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
doap:license <https://rerdavies.github.io/pipedal/LicenseToobAmp> ;
|
||||
doap:maintainer <http://two-play.com/rerdavies#me> ;
|
||||
lv2:minorVersion 0 ;
|
||||
lv2:microVersion 70 ;
|
||||
lv2:microVersion 71 ;
|
||||
rdfs:comment """
|
||||
Toob Freeverb is an Lv2 implementation of the famous Freeverb reverb effect. FreeVerb delivers a well-balanced reverb with very little tonal coloration.
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
doap:license <https://rerdavies.github.io/pipedal/LicenseToobAmp> ;
|
||||
doap:maintainer <http://two-play.com/rerdavies#me> ;
|
||||
lv2:minorVersion 0 ;
|
||||
lv2:microVersion 70 ;
|
||||
lv2:microVersion 71 ;
|
||||
rdfs:comment """
|
||||
A 7 octave graphic equalizer, with frequencies chosen to be useful for EQ-ing guitar signals.
|
||||
""" ;
|
||||
|
||||
@@ -92,7 +92,7 @@ myprefix:output_group
|
||||
doap:license <https://opensource.org/license/mit/> ;
|
||||
doap:maintainer <http://two-play.com/rerdavies#me> ;
|
||||
lv2:minorVersion 2 ;
|
||||
lv2:microVersion 70 ;
|
||||
lv2:microVersion 71 ;
|
||||
|
||||
ui:ui <http://two-play.com/plugins/toob-looper-four-ui>;
|
||||
|
||||
|
||||
@@ -78,7 +78,7 @@ myprefix:output_group
|
||||
doap:license <https://opensource.org/license/mit/> ;
|
||||
doap:maintainer <http://two-play.com/rerdavies#me> ;
|
||||
lv2:minorVersion 2 ;
|
||||
lv2:microVersion 70 ;
|
||||
lv2:microVersion 71 ;
|
||||
|
||||
ui:ui <http://two-play.com/plugins/toob-looper-one-ui>;
|
||||
|
||||
|
||||
@@ -67,7 +67,7 @@ toobml:sagGroup
|
||||
doap:license <https://rerdavies.github.io/pipedal/LicenseToobAmp> ;
|
||||
doap:maintainer <http://two-play.com/rerdavies#me> ;
|
||||
lv2:minorVersion 0 ;
|
||||
lv2:microVersion 70 ;
|
||||
lv2:microVersion 71 ;
|
||||
rdfs:comment """
|
||||
The TooB ML Amplifier plugin provides emulation of a variety of amplifiers and overdrive pedals that are implemented
|
||||
using neural-network-based machine learning models of real amplifiers.
|
||||
|
||||
@@ -40,7 +40,7 @@
|
||||
doap:license <https://rerdavies.github.io/pipedal/LicenseToobAmp> ;
|
||||
doap:maintainer <http://two-play.com/rerdavies#me> ;
|
||||
lv2:minorVersion 0 ;
|
||||
lv2:microVersion 70 ;
|
||||
lv2:microVersion 71 ;
|
||||
rdfs:comment """
|
||||
Remix a stereo input signal.
|
||||
|
||||
|
||||
@@ -72,7 +72,7 @@ toobNam:calibrationGroup
|
||||
doap:license <https://rerdavies.github.io/pipedal/LicenseToobAmp> ;
|
||||
doap:maintainer <http://two-play.com/rerdavies#me> ;
|
||||
lv2:minorVersion 0 ;
|
||||
lv2:microVersion 70 ;
|
||||
lv2:microVersion 71 ;
|
||||
rdfs:comment """
|
||||
TooB Neural Amp Modeler is a neural network based Amp Simulator. It uses .nam model files that are generated by training nueral networks
|
||||
on audio recordings of actual guitar amps, effects pedals and other equipment. .nam files contain data from the trained models, which can be loaded into
|
||||
|
||||
@@ -54,7 +54,7 @@ noisegate:envelope_group
|
||||
doap:license <https://rerdavies.github.io/pipedal/LicenseToobAmp> ;
|
||||
doap:maintainer <http://two-play.com/rerdavies#me> ;
|
||||
lv2:minorVersion 0 ;
|
||||
lv2:microVersion 70 ;
|
||||
lv2:microVersion 71 ;
|
||||
rdfs:comment """
|
||||
A noise gate is an audio processing tool that controls the volume of an audio signal
|
||||
by allowing it to pass through only when it exceeds a set threshold.
|
||||
|
||||
@@ -79,7 +79,7 @@ parametric_eq:hfGroup
|
||||
|
||||
<http://two-play.com/plugins/toob-parametric-eq>
|
||||
a lv2:Plugin ,
|
||||
lv2:EQPlugin ;
|
||||
lv2:ParaEQPlugin ;
|
||||
doap:name "TooB Parametric EQ" ,
|
||||
"TooB Parametric EQ"@en-gb-gb
|
||||
;
|
||||
@@ -87,7 +87,7 @@ parametric_eq:hfGroup
|
||||
doap:license <https://rerdavies.github.io/pipedal/LicenseToobAmp> ;
|
||||
doap:maintainer <http://two-play.com/rerdavies#me> ;
|
||||
lv2:minorVersion 0 ;
|
||||
lv2:microVersion 70 ;
|
||||
lv2:microVersion 71 ;
|
||||
|
||||
uiext:ui <http://two-play.com/plugins/toob-parametric-eq-ui>;
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@
|
||||
doap:license <https://rerdavies.github.io/pipedal/LicenseToobAmp> ;
|
||||
doap:maintainer <http://two-play.com/rerdavies#me> ;
|
||||
lv2:minorVersion 0 ;
|
||||
lv2:microVersion 70 ;
|
||||
lv2:microVersion 71 ;
|
||||
rdfs:comment """
|
||||
A loose emulation of an MXR® Phase 90 Phaser.
|
||||
|
||||
|
||||
@@ -60,7 +60,7 @@ toobPlayer:seek
|
||||
doap:license <https://rerdavies.github.io/pipedal/LicenseToobAmp> ;
|
||||
doap:maintainer <http://two-play.com/rerdavies#me> ;
|
||||
lv2:minorVersion 0 ;
|
||||
lv2:microVersion 70 ;
|
||||
lv2:microVersion 71 ;
|
||||
rdfs:comment """
|
||||
Play audio files. Audio files can optionally be loooped by pressing the "Set loop" button.
|
||||
|
||||
|
||||
@@ -51,7 +51,7 @@ recordPrefix:audioFile
|
||||
doap:license <https://opensource.org/license/mit/> ;
|
||||
doap:maintainer <http://two-play.com/rerdavies#me> ;
|
||||
lv2:minorVersion 2 ;
|
||||
lv2:microVersion 70 ;
|
||||
lv2:microVersion 71 ;
|
||||
|
||||
ui:ui <http://two-play.com/plugins/toob-record-mono-ui>;
|
||||
|
||||
|
||||
@@ -88,7 +88,7 @@ myprefix:loop3_group
|
||||
doap:license <https://opensource.org/license/mit/> ;
|
||||
doap:maintainer <http://two-play.com/rerdavies#me> ;
|
||||
lv2:minorVersion 2 ;
|
||||
lv2:microVersion 70 ;
|
||||
lv2:microVersion 71 ;
|
||||
|
||||
ui:ui <http://two-play.com/plugins/toob-record-stereo-ui>;
|
||||
|
||||
|
||||
@@ -0,0 +1,199 @@
|
||||
@prefix doap: <http://usefulinc.com/ns/doap#> .
|
||||
@prefix lv2: <http://lv2plug.in/ns/lv2core#> .
|
||||
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
|
||||
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
|
||||
@prefix units: <http://lv2plug.in/ns/extensions/units#> .
|
||||
@prefix urid: <http://lv2plug.in/ns/ext/urid#> .
|
||||
@prefix atom: <http://lv2plug.in/ns/ext/atom#> .
|
||||
@prefix midi: <http://lv2plug.in/ns/ext/midi#> .
|
||||
@prefix epp: <http://lv2plug.in/ns/ext/port-props#> .
|
||||
@prefix uiext: <http://lv2plug.in/ns/extensions/ui#> .
|
||||
@prefix idpy: <http://harrisonconsoles.com/lv2/inlinedisplay#> .
|
||||
@prefix foaf: <http://xmlns.com/foaf/0.1/> .
|
||||
@prefix mod: <http://moddevices.com/ns/mod#> .
|
||||
@prefix param: <http://lv2plug.in/ns/ext/parameters#> .
|
||||
@prefix work: <http://lv2plug.in/ns/ext/worker#> .
|
||||
@prefix pg: <http://lv2plug.in/ns/ext/port-groups#> .
|
||||
|
||||
@prefix atom: <http://lv2plug.in/ns/ext/atom#> .
|
||||
@prefix patch: <http://lv2plug.in/ns/ext/patch#> .
|
||||
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
|
||||
@prefix state: <http://lv2plug.in/ns/ext/state#> .
|
||||
@prefix urid: <http://lv2plug.in/ns/ext/urid#> .
|
||||
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
|
||||
@prefix toobTremolo: <http://two-play.com/plugins/toob-tremolo#> .
|
||||
@prefix pipedal_ui: <http://github.com/rerdavies/pipedal/ui#> .
|
||||
|
||||
|
||||
<http://two-play.com/rerdavies#me>
|
||||
a foaf:Person ;
|
||||
foaf:name "Robin Davies" ;
|
||||
foaf:mbox <mailto:rerdavies@gmail.com> ;
|
||||
foaf:homepage <https://github.com/sponsors/rerdavies> .
|
||||
|
||||
<http://two-play.com/plugins/toob-tremolo>
|
||||
a lv2:Plugin ,
|
||||
lv2:ModulatorPlugin ;
|
||||
doap:name "TooB Tremolo (Stereo)" ,
|
||||
"TooB Tremolo (Stereo)"@en-us-us
|
||||
;
|
||||
uiext:ui <http://two-play.com/plugins/toob-tremolo-ui> ;
|
||||
doap:license <https://rerdavies.github.io/pipedal/LicenseToobAmp> ;
|
||||
doap:maintainer <http://two-play.com/rerdavies#me> ;
|
||||
lv2:minorVersion 0 ;
|
||||
lv2:microVersion 71 ;
|
||||
rdfs:comment """
|
||||
A tremolo effect, equivalent to "vibrato" on vintage Fender tube amps.
|
||||
|
||||
TooB Tremolo supports both normal tremolo and harmonic tremolo. When the Harmonic switch is turned on,
|
||||
the tremolo effect is applied
|
||||
with opposite phase to the low and high-frequency portions of the input signal. This replicates the harmonic
|
||||
tremolo feature found on classic Fender amps like the Bandmaster and Vibroverb. Harmonic tremolo is favored for
|
||||
sounding smoother than normal tremolo, and can introduce a pleasant and subtle effect that sounds a bit like a gentle phaser.
|
||||
""" ;
|
||||
|
||||
mod:brand "TooB";
|
||||
mod:label "Tremolo";
|
||||
lv2:optionalFeature lv2:hardRTCapable;
|
||||
|
||||
lv2:port
|
||||
[
|
||||
a lv2:InputPort ,
|
||||
lv2:ControlPort ;
|
||||
|
||||
lv2:index 0 ;
|
||||
lv2:symbol "rate" ;
|
||||
lv2:name "Rate";
|
||||
|
||||
lv2:default 5.0 ;
|
||||
lv2:minimum 0.25 ;
|
||||
lv2:maximum 10.0 ;
|
||||
units:unit units:hz ;
|
||||
rdfs:comment "Rate";
|
||||
],
|
||||
[
|
||||
a lv2:InputPort ,
|
||||
lv2:ControlPort ;
|
||||
|
||||
lv2:index 1 ;
|
||||
lv2:symbol "depth" ;
|
||||
lv2:name "Depth";
|
||||
|
||||
lv2:default 0.5 ;
|
||||
lv2:minimum 0.0 ;
|
||||
lv2:maximum 1.0 ;
|
||||
rdfs:comment "Depth";
|
||||
],
|
||||
[
|
||||
a lv2:InputPort ,
|
||||
lv2:ControlPort ;
|
||||
|
||||
lv2:index 2 ;
|
||||
lv2:portProperty lv2:toggled ;
|
||||
lv2:symbol "harmonic" ;
|
||||
lv2:name "Harmonic";
|
||||
|
||||
lv2:default 0.0 ;
|
||||
lv2:minimum 0.0 ;
|
||||
lv2:maximum 1.0 ;
|
||||
rdfs:comment "Harmonic Tremelo";
|
||||
],
|
||||
[
|
||||
a lv2:InputPort ,
|
||||
lv2:ControlPort ;
|
||||
|
||||
lv2:index 3 ;
|
||||
lv2:symbol "shape" ;
|
||||
lv2:name "Shape";
|
||||
|
||||
lv2:default 0.0 ;
|
||||
lv2:minimum 0.0 ;
|
||||
lv2:maximum 1.0 ;
|
||||
rdfs:comment "Controls the shape of the LFO";
|
||||
],
|
||||
[
|
||||
a lv2:AudioPort ,
|
||||
lv2:InputPort ;
|
||||
lv2:index 4 ;
|
||||
lv2:symbol "inl" ;
|
||||
lv2:name "In L"
|
||||
],
|
||||
[
|
||||
a lv2:AudioPort ,
|
||||
lv2:InputPort ;
|
||||
lv2:index 5 ;
|
||||
lv2:symbol "inr" ;
|
||||
lv2:name "In R"
|
||||
],
|
||||
[
|
||||
a lv2:AudioPort ,
|
||||
lv2:OutputPort ;
|
||||
lv2:index 6 ;
|
||||
lv2:symbol "outl" ;
|
||||
lv2:name "Out L"
|
||||
],
|
||||
[
|
||||
a lv2:AudioPort ,
|
||||
lv2:OutputPort ;
|
||||
lv2:index 7 ;
|
||||
lv2:symbol "outr" ;
|
||||
lv2:name "OutR"
|
||||
],
|
||||
|
||||
[
|
||||
a atom:AtomPort ,
|
||||
lv2:InputPort;
|
||||
atom:bufferType atom:Sequence ;
|
||||
# atom:supports patch:Message;
|
||||
lv2:designation lv2:control ;
|
||||
atom:supports patch:Message ;
|
||||
|
||||
lv2:index 8 ;
|
||||
lv2:symbol "control" ;
|
||||
lv2:name "Control" ;
|
||||
rdfs:comment "Control" ;
|
||||
] , [
|
||||
a atom:AtomPort ,
|
||||
lv2:OutputPort ;
|
||||
atom:bufferType atom:Sequence ;
|
||||
# atom:supports patch:Message;
|
||||
lv2:designation lv2:control ;
|
||||
lv2:index 9;
|
||||
lv2:symbol "notify" ;
|
||||
lv2:name "Notify" ;
|
||||
rdfs:comment "Notification" ;
|
||||
]
|
||||
|
||||
.
|
||||
|
||||
<http://two-play.com/plugins/toob-tremolo-ui>
|
||||
a uiext:X11UI ;
|
||||
lv2:binary <ToobAmpUI.so>;
|
||||
|
||||
lv2:extensionData uiext::idle ;
|
||||
lv2:extensionData uiext:resize ;
|
||||
lv2:extensionData uiext:idleInterface;
|
||||
lv2:requiredFeature uiext:idleInterface ;
|
||||
.
|
||||
|
||||
<http://two-play.com/plugins/toob-tremolo>
|
||||
pipedal_ui:ui <http://two-play.com/plugins/toob-tremolo#piPedalUI>.
|
||||
|
||||
|
||||
|
||||
<http://two-play.com/plugins/toob-tremolo#piPedalUI>
|
||||
a pipedal_ui:ui ;
|
||||
pipedal_ui:frequencyPlot
|
||||
[
|
||||
lv2:index 4 ;
|
||||
pipedal_ui:patchProperty <http://two-play.com/plugins/toob-tremolo#lfoShape>;
|
||||
pipedal_ui:xLeft 0 ;
|
||||
pipedal_ui:xRight 1 ;
|
||||
pipedal_ui:yTop 1.1;
|
||||
pipedal_ui:yBottom -0.1 ;
|
||||
pipedal_ui:xLog 0 ;
|
||||
pipedal_ui:yDb 0 ;
|
||||
pipedal_ui:width 120 ;
|
||||
]
|
||||
.
|
||||
|
||||
@@ -0,0 +1,184 @@
|
||||
@prefix doap: <http://usefulinc.com/ns/doap#> .
|
||||
@prefix lv2: <http://lv2plug.in/ns/lv2core#> .
|
||||
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
|
||||
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
|
||||
@prefix units: <http://lv2plug.in/ns/extensions/units#> .
|
||||
@prefix urid: <http://lv2plug.in/ns/ext/urid#> .
|
||||
@prefix atom: <http://lv2plug.in/ns/ext/atom#> .
|
||||
@prefix midi: <http://lv2plug.in/ns/ext/midi#> .
|
||||
@prefix epp: <http://lv2plug.in/ns/ext/port-props#> .
|
||||
@prefix uiext: <http://lv2plug.in/ns/extensions/ui#> .
|
||||
@prefix idpy: <http://harrisonconsoles.com/lv2/inlinedisplay#> .
|
||||
@prefix foaf: <http://xmlns.com/foaf/0.1/> .
|
||||
@prefix mod: <http://moddevices.com/ns/mod#> .
|
||||
@prefix param: <http://lv2plug.in/ns/ext/parameters#> .
|
||||
@prefix work: <http://lv2plug.in/ns/ext/worker#> .
|
||||
@prefix pg: <http://lv2plug.in/ns/ext/port-groups#> .
|
||||
|
||||
@prefix atom: <http://lv2plug.in/ns/ext/atom#> .
|
||||
@prefix patch: <http://lv2plug.in/ns/ext/patch#> .
|
||||
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
|
||||
@prefix state: <http://lv2plug.in/ns/ext/state#> .
|
||||
@prefix urid: <http://lv2plug.in/ns/ext/urid#> .
|
||||
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
|
||||
@prefix toobTremeloMono: <http://two-play.com/plugins/toob-tremolo-mono#> .
|
||||
@prefix pipedal_ui: <http://github.com/rerdavies/pipedal/ui#> .
|
||||
|
||||
|
||||
<http://two-play.com/rerdavies#me>
|
||||
a foaf:Person ;
|
||||
foaf:name "Robin Davies" ;
|
||||
foaf:mbox <mailto:rerdavies@gmail.com> ;
|
||||
foaf:homepage <https://github.com/sponsors/rerdavies> .
|
||||
|
||||
<http://two-play.com/plugins/toob-tremolo-mono>
|
||||
a lv2:Plugin ,
|
||||
lv2:ModulatorPlugin ;
|
||||
doap:name "TooB Tremolo (Mono)" ,
|
||||
"TooB Tremolo (Mono)"@en-us-us
|
||||
;
|
||||
uiext:ui <http://two-play.com/plugins/toob-tremolo-mono-ui> ;
|
||||
doap:license <https://rerdavies.github.io/pipedal/LicenseToobAmp> ;
|
||||
doap:maintainer <http://two-play.com/rerdavies#me> ;
|
||||
lv2:minorVersion 0 ;
|
||||
lv2:microVersion 71 ;
|
||||
rdfs:comment """
|
||||
A tremolo effect, equivalent to "vibrato" on vintage Fender tube amps.
|
||||
|
||||
TooB Tremolo supports both normal tremolo and harmonic tremolo. When the Harmonic switch is turned on,
|
||||
the tremolo effect is applied
|
||||
with opposite phase to the low and high-frequency portions of the input signal. This replicates the harmonic
|
||||
tremolo feature found on classic Fender amps like the Bandmaster and Vibroverb. Harmonic tremolo is favored for
|
||||
sounding smoother than normal tremolo, and can introduce a pleasant and subtle effect that sounds a bit like a gentle phaser.
|
||||
""" ;
|
||||
|
||||
mod:brand "TooB";
|
||||
mod:label "Tremolo";
|
||||
lv2:optionalFeature lv2:hardRTCapable;
|
||||
|
||||
lv2:port
|
||||
[
|
||||
a lv2:InputPort ,
|
||||
lv2:ControlPort ;
|
||||
|
||||
lv2:index 0 ;
|
||||
lv2:symbol "rate" ;
|
||||
lv2:name "Rate";
|
||||
|
||||
lv2:default 5.0 ;
|
||||
lv2:minimum 0.25 ;
|
||||
lv2:maximum 10.0 ;
|
||||
units:unit units:hz ;
|
||||
rdfs:comment "Rate";
|
||||
],
|
||||
[
|
||||
a lv2:InputPort ,
|
||||
lv2:ControlPort ;
|
||||
|
||||
lv2:index 1 ;
|
||||
lv2:symbol "depth" ;
|
||||
lv2:name "Depth";
|
||||
|
||||
lv2:default 0.5 ;
|
||||
lv2:minimum 0.0 ;
|
||||
lv2:maximum 1.0 ;
|
||||
rdfs:comment "Depth";
|
||||
],
|
||||
[
|
||||
a lv2:InputPort ,
|
||||
lv2:ControlPort ;
|
||||
|
||||
lv2:index 2 ;
|
||||
lv2:portProperty lv2:toggled ;
|
||||
lv2:symbol "harmonic" ;
|
||||
lv2:name "Harmonic";
|
||||
|
||||
lv2:default 0.0 ;
|
||||
lv2:minimum 0.0 ;
|
||||
lv2:maximum 1.0 ;
|
||||
rdfs:comment "Harmonic Tremelo";
|
||||
],
|
||||
[
|
||||
a lv2:InputPort ,
|
||||
lv2:ControlPort ;
|
||||
|
||||
lv2:index 3 ;
|
||||
lv2:symbol "shape" ;
|
||||
lv2:name "Shape";
|
||||
|
||||
lv2:default 0.0 ;
|
||||
lv2:minimum 0.0 ;
|
||||
lv2:maximum 1.0 ;
|
||||
rdfs:comment "Controls the shape of the LFO";
|
||||
],
|
||||
[
|
||||
a lv2:AudioPort ,
|
||||
lv2:InputPort ;
|
||||
lv2:index 4 ;
|
||||
lv2:symbol "inl" ;
|
||||
lv2:name "In L"
|
||||
],
|
||||
[
|
||||
a lv2:AudioPort ,
|
||||
lv2:OutputPort ;
|
||||
lv2:index 5 ;
|
||||
lv2:symbol "outl" ;
|
||||
lv2:name "Out L"
|
||||
],
|
||||
[
|
||||
a atom:AtomPort ,
|
||||
lv2:InputPort;
|
||||
atom:bufferType atom:Sequence ;
|
||||
# atom:supports patch:Message;
|
||||
lv2:designation lv2:control ;
|
||||
atom:supports patch:Message ;
|
||||
|
||||
lv2:index 6 ;
|
||||
lv2:symbol "control" ;
|
||||
lv2:name "Control" ;
|
||||
rdfs:comment "Control" ;
|
||||
] , [
|
||||
a atom:AtomPort ,
|
||||
lv2:OutputPort ;
|
||||
atom:bufferType atom:Sequence ;
|
||||
# atom:supports patch:Message;
|
||||
lv2:designation lv2:control ;
|
||||
lv2:index 7;
|
||||
lv2:symbol "notify" ;
|
||||
lv2:name "Notify" ;
|
||||
rdfs:comment "Notification" ;
|
||||
]
|
||||
|
||||
.
|
||||
|
||||
<http://two-play.com/plugins/toob-tremolo-mono-ui>
|
||||
a uiext:X11UI ;
|
||||
lv2:binary <ToobAmpUI.so>;
|
||||
|
||||
lv2:extensionData uiext::idle ;
|
||||
lv2:extensionData uiext:resize ;
|
||||
lv2:extensionData uiext:idleInterface;
|
||||
lv2:requiredFeature uiext:idleInterface ;
|
||||
.
|
||||
|
||||
<http://two-play.com/plugins/toob-tremolo-mono>
|
||||
pipedal_ui:ui <http://two-play.com/plugins/toob-tremolo-mono#piPedalUI>.
|
||||
|
||||
|
||||
|
||||
<http://two-play.com/plugins/toob-tremolo-mono#piPedalUI>
|
||||
a pipedal_ui:ui ;
|
||||
pipedal_ui:frequencyPlot
|
||||
[
|
||||
lv2:index 4 ;
|
||||
pipedal_ui:patchProperty <http://two-play.com/plugins/toob-tremolo#lfoShape>;
|
||||
pipedal_ui:xLeft 0 ;
|
||||
pipedal_ui:xRight 1 ;
|
||||
pipedal_ui:yTop 1.1;
|
||||
pipedal_ui:yBottom -0.1 ;
|
||||
pipedal_ui:xLog 0 ;
|
||||
pipedal_ui:yDb 0 ;
|
||||
pipedal_ui:width 120 ;
|
||||
]
|
||||
.
|
||||
|
||||
@@ -40,7 +40,7 @@
|
||||
doap:license <https://rerdavies.github.io/pipedal/LicenseToobAmp> ;
|
||||
doap:maintainer <http://two-play.com/rerdavies#me> ;
|
||||
lv2:minorVersion 0 ;
|
||||
lv2:microVersion 70 ;
|
||||
lv2:microVersion 71 ;
|
||||
rdfs:comment """
|
||||
TooB Tuner is a chromatic guitar tuner.
|
||||
""" ;
|
||||
|
||||
@@ -39,7 +39,7 @@
|
||||
doap:license <https://rerdavies.github.io/pipedal/LicenseToobAmp> ;
|
||||
doap:maintainer <http://two-play.com/rerdavies#me> ;
|
||||
lv2:minorVersion 0 ;
|
||||
lv2:microVersion 70 ;
|
||||
lv2:microVersion 71 ;
|
||||
rdfs:comment """
|
||||
Volume control.
|
||||
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -1,26 +0,0 @@
|
||||
The following files have been graciously provided by Kristoffer Ekstrand (akak Adventure Kid),
|
||||
and are licensed under a CC-BY-4.0 license:
|
||||
|
||||
https://creativecommons.org/licenses/by/4.0/
|
||||
|
||||
80s UK 001.wav
|
||||
80s UK 002.wav
|
||||
80s US 001.wav
|
||||
80s US 002.wav
|
||||
Modern UK 001.wav
|
||||
Modern UK 002.wav
|
||||
Modern US 001.wav
|
||||
Modern US 002.wav
|
||||
Modern US 003.wav
|
||||
Modern US 004.wav
|
||||
Radio 001.wav
|
||||
Speaker 001.wav
|
||||
Speaker 002.wav
|
||||
Vintage UK 001.wav
|
||||
Vintage UK 002.wav
|
||||
Vintage US 001.wav
|
||||
Vintage US 002.wav
|
||||
|
||||
By: Adventure Kid Research & Technology, Kristoffer Ekstrand.
|
||||
|
||||
Source: https://www.adventurekid.se/akrt/free-reverb-impulse-responses/
|
||||
@@ -1,52 +0,0 @@
|
||||
Toob Convolution Reverb's impulse files were provided by the OpenAir project.
|
||||
https://www.openair.hosted.york.ac.uk/?page_id=571, under CC-By-4.0 licenses.
|
||||
|
||||
|
||||
Arthur Sykes Rymer Auditorium.wav
|
||||
---------------------------------------
|
||||
By: www.openairlib.net, University of York
|
||||
|
||||
This work is provided under a Creative Commons Attribution 4.0 International License.
|
||||
https://creativecommons.org/licenses/by/4.0/
|
||||
|
||||
Source: https://www.openair.hosted.york.ac.uk/?page_id=425
|
||||
|
||||
|
||||
Jack Lyons Hall, University of York.wav
|
||||
---------------------------------------
|
||||
By: www.openairlib.net, Audiolab University of York, Alex Duffell, Aishwarya Sridhar, Zhong Li
|
||||
|
||||
This work is provided under a Creative Commons Attribution 4.0 International License.
|
||||
https://creativecommons.org/licenses/by/4.0/
|
||||
|
||||
Source: https://www.openair.hosted.york.ac.uk/?page_id=571
|
||||
|
||||
|
||||
Koli National Park - Winter.wav
|
||||
---------------------------------------
|
||||
By: www.openairlib.net, Audio Lab University of York, Andrew Chadwick, Simon Shelley
|
||||
|
||||
This work is provided under a Creative Commons Attribution 4.0 International License.
|
||||
https://creativecommons.org/licenses/by/4.0/
|
||||
|
||||
Source: https://www.openair.hosted.york.ac.uk/?page_id=584
|
||||
|
||||
|
||||
St. Margaret’s Church.wav
|
||||
---------------------------------------
|
||||
By: www.openairlib.net, AudioLab University of York, www.ncem.co.uk
|
||||
|
||||
This work is provided under a Creative Commons Attribution 4.0 International License.
|
||||
https://creativecommons.org/licenses/by/4.0/
|
||||
|
||||
Source: https://www.openair.hosted.york.ac.uk/?page_id=702
|
||||
|
||||
|
||||
Genesis 6 Studio Live Room.wav
|
||||
---------------------------------------
|
||||
By: www.openairlib.net
|
||||
|
||||
This work is provided under a Creative Commons Attribution 4.0 International License.
|
||||
https://creativecommons.org/licenses/by/4.0/
|
||||
|
||||
Source: https://www.openair.hosted.york.ac.uk/?page_id=483
|
||||
@@ -106,6 +106,14 @@
|
||||
lv2:binary <ToobAmp.so> ;
|
||||
rdfs:seeAlso <ToobMix.ttl> .
|
||||
|
||||
<http://two-play.com/plugins/toob-tremolo> a lv2:Plugin ;
|
||||
lv2:binary <ToobAmp.so> ;
|
||||
rdfs:seeAlso <ToobTremolo.ttl> .
|
||||
|
||||
<http://two-play.com/plugins/toob-tremolo-mono> a lv2:Plugin ;
|
||||
lv2:binary <ToobAmp.so> ;
|
||||
rdfs:seeAlso <ToobTremoloMono.ttl> .
|
||||
|
||||
<http://two-play.com/plugins/toob-player> a lv2:Plugin ;
|
||||
lv2:binary <ToobAmp.so> ;
|
||||
rdfs:seeAlso <ToobPlayer.ttl> .
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
# NeuralPi Tones
|
||||
Binary file not shown.
+303
-271
@@ -23,316 +23,348 @@
|
||||
#include "Pedalboard.hpp"
|
||||
#include "PiPedalException.hpp"
|
||||
|
||||
namespace pipedal {
|
||||
namespace pipedal
|
||||
{
|
||||
|
||||
#define GETTER_SETTER_REF(name) \
|
||||
decltype(name##_)& name() { return name##_;} \
|
||||
const decltype(name##_)& name() const { return name##_;} \
|
||||
#define GETTER_SETTER_REF(name) \
|
||||
decltype(name##_) &name() { return name##_; } \
|
||||
const decltype(name##_) &name() const { return name##_; } \
|
||||
void name(const decltype(name##_) &value) { name##_ = value; }
|
||||
|
||||
#define GETTER_SETTER_VEC(name) \
|
||||
decltype(name##_)& name() { return name##_;} \
|
||||
const decltype(name##_)& name() const { return name##_;}
|
||||
#define GETTER_SETTER_VEC(name) \
|
||||
decltype(name##_) &name() { return name##_; } \
|
||||
const decltype(name##_) &name() const { return name##_; }
|
||||
|
||||
|
||||
#define GETTER_SETTER(name) \
|
||||
decltype(name##_) name() const { return name##_;} \
|
||||
#define GETTER_SETTER(name) \
|
||||
decltype(name##_) name() const { return name##_; } \
|
||||
void name(decltype(name##_) value) { name##_ = value; }
|
||||
|
||||
class PresetIndexEntry
|
||||
{
|
||||
int64_t instanceId_ = 0;
|
||||
std::string name_;
|
||||
|
||||
class PresetIndexEntry {
|
||||
int64_t instanceId_ = 0;
|
||||
std::string name_;
|
||||
public:
|
||||
GETTER_SETTER(instanceId);
|
||||
GETTER_SETTER(name);
|
||||
DECLARE_JSON_MAP(PresetIndexEntry);
|
||||
};
|
||||
|
||||
class PresetIndex {
|
||||
int64_t selectedInstanceId_ = -1;
|
||||
bool presetChanged_ = false;
|
||||
std::vector<PresetIndexEntry> presets_;
|
||||
public:
|
||||
bool GetPresetName(int64_t instanceId, std::string*pResult) {
|
||||
for (size_t i = 0; i < presets_.size(); ++i)
|
||||
public:
|
||||
PresetIndexEntry() {}
|
||||
PresetIndexEntry(uint64_t instanceId, const std::string &name)
|
||||
: instanceId_(instanceId), name_(name)
|
||||
{
|
||||
if (presets_[i].instanceId() == instanceId)
|
||||
}
|
||||
|
||||
GETTER_SETTER(instanceId);
|
||||
GETTER_SETTER(name);
|
||||
DECLARE_JSON_MAP(PresetIndexEntry);
|
||||
};
|
||||
|
||||
class PresetIndex
|
||||
{
|
||||
int64_t selectedInstanceId_ = -1;
|
||||
bool presetChanged_ = false;
|
||||
std::vector<PresetIndexEntry> presets_;
|
||||
|
||||
public:
|
||||
bool GetPresetName(int64_t instanceId, std::string *pResult)
|
||||
{
|
||||
for (size_t i = 0; i < presets_.size(); ++i)
|
||||
{
|
||||
*pResult = presets_[i].name();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
GETTER_SETTER(selectedInstanceId);
|
||||
GETTER_SETTER_VEC(presets);
|
||||
GETTER_SETTER(presetChanged);
|
||||
|
||||
DECLARE_JSON_MAP(PresetIndex);
|
||||
};
|
||||
|
||||
|
||||
class BankFileEntry {
|
||||
int64_t instanceId_;
|
||||
Pedalboard preset_;
|
||||
public:
|
||||
GETTER_SETTER(instanceId);
|
||||
GETTER_SETTER_REF(preset);
|
||||
|
||||
DECLARE_JSON_MAP(BankFileEntry);
|
||||
};
|
||||
class BankFile {
|
||||
std::string name_;
|
||||
int64_t nextInstanceId_ = 0;
|
||||
int64_t selectedPreset_ = -1;
|
||||
std::vector<std::unique_ptr<BankFileEntry>> presets_;
|
||||
|
||||
public:
|
||||
GETTER_SETTER(name);
|
||||
GETTER_SETTER(nextInstanceId);
|
||||
GETTER_SETTER(selectedPreset);
|
||||
GETTER_SETTER_VEC(presets);
|
||||
|
||||
void clear()
|
||||
{
|
||||
nextInstanceId_ = 0;
|
||||
presets_.clear();
|
||||
selectedPreset_ = -1;
|
||||
}
|
||||
|
||||
void move(size_t from, size_t to)
|
||||
{
|
||||
if (from >= this->presets_.size()) {
|
||||
throw std::invalid_argument("Argument out of range.");
|
||||
}
|
||||
if (to >= this->presets_.size()) {
|
||||
throw std::invalid_argument("Argument out of range.");
|
||||
}
|
||||
std::unique_ptr<BankFileEntry> t = std::move(this->presets_[from]);
|
||||
presets_.erase(presets_.begin()+from);
|
||||
presets_.insert(presets_.begin()+to,std::move(t));
|
||||
}
|
||||
void updateNextIndex() {
|
||||
int64_t t = 0;
|
||||
for (size_t i = 0; i < this->presets_.size(); ++i)
|
||||
{
|
||||
int64_t instanceId = this->presets_[i]->instanceId();
|
||||
if (instanceId > t) t = instanceId;
|
||||
|
||||
}
|
||||
this->nextInstanceId_ = t;
|
||||
}
|
||||
int64_t addPreset(const Pedalboard&preset, int64_t afterItem = -1)
|
||||
{
|
||||
if (hasName(preset.name()))
|
||||
{
|
||||
throw PiPedalStateException("A preset by that name already exists.");
|
||||
}
|
||||
std::unique_ptr<BankFileEntry> entry = std::make_unique<BankFileEntry>();
|
||||
entry->instanceId(++nextInstanceId_);
|
||||
entry->preset(preset);
|
||||
int64_t instanceId = entry->instanceId();
|
||||
if (afterItem == -1)
|
||||
{
|
||||
this->presets_.push_back(std::move(entry));
|
||||
} else {
|
||||
for (auto it = this->presets_.begin(); it != this->presets_.end(); ++it)
|
||||
{
|
||||
if ((*it)->instanceId() == afterItem)
|
||||
if (presets_[i].instanceId() == instanceId)
|
||||
{
|
||||
++it;
|
||||
this->presets_.insert(it,std::move(entry));
|
||||
break;
|
||||
*pResult = presets_[i].name();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return instanceId;
|
||||
}
|
||||
bool hasItem(int64_t instanceId)
|
||||
{
|
||||
for (size_t i = 0; i < presets_.size(); ++i)
|
||||
{
|
||||
if (presets_[i]->instanceId() == instanceId)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
bool hasName(const std::string &name) {
|
||||
for (size_t i = 0; i < presets_.size(); ++i)
|
||||
{
|
||||
if (presets_[i]->preset().name() == name)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
bool renamePreset(int64_t instanceId, const std::string&name)
|
||||
{
|
||||
if (hasName(name))
|
||||
{
|
||||
throw PiPedalStateException("A preset by that name already exists.");
|
||||
}
|
||||
for (size_t i = 0; i < presets_.size(); ++i)
|
||||
{
|
||||
if (presets_[i]->instanceId() ==instanceId)
|
||||
{
|
||||
presets_[i]->preset().name(name);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
GETTER_SETTER(selectedInstanceId);
|
||||
GETTER_SETTER_VEC(presets);
|
||||
GETTER_SETTER(presetChanged);
|
||||
|
||||
}
|
||||
DECLARE_JSON_MAP(PresetIndex);
|
||||
};
|
||||
|
||||
BankFileEntry &getItem(int64_t instanceId)
|
||||
class BankFileEntry
|
||||
{
|
||||
for (size_t i = 0; i < presets_.size(); ++i)
|
||||
int64_t instanceId_;
|
||||
Pedalboard preset_;
|
||||
|
||||
public:
|
||||
GETTER_SETTER(instanceId);
|
||||
GETTER_SETTER_REF(preset);
|
||||
|
||||
DECLARE_JSON_MAP(BankFileEntry);
|
||||
};
|
||||
class BankFile
|
||||
{
|
||||
std::string name_;
|
||||
int64_t nextInstanceId_ = 0;
|
||||
int64_t selectedPreset_ = -1;
|
||||
std::vector<std::unique_ptr<BankFileEntry>> presets_;
|
||||
|
||||
public:
|
||||
GETTER_SETTER(name);
|
||||
GETTER_SETTER(nextInstanceId);
|
||||
GETTER_SETTER(selectedPreset);
|
||||
GETTER_SETTER_VEC(presets);
|
||||
|
||||
void clear()
|
||||
{
|
||||
if (presets_[i]->instanceId() == instanceId)
|
||||
{
|
||||
return *(presets_[i].get());
|
||||
}
|
||||
nextInstanceId_ = 0;
|
||||
presets_.clear();
|
||||
selectedPreset_ = -1;
|
||||
}
|
||||
throw PiPedalArgumentException("Instance not found.");
|
||||
}
|
||||
int64_t deletePreset(int64_t instanceId) {
|
||||
for (size_t i = 0; i < presets_.size(); ++i)
|
||||
|
||||
void move(size_t from, size_t to)
|
||||
{
|
||||
if (presets_[i]->instanceId() == instanceId)
|
||||
if (from >= this->presets_.size())
|
||||
{
|
||||
presets_.erase(presets_.begin()+i);
|
||||
int64_t newSelection;
|
||||
if (i < presets_.size())
|
||||
throw std::invalid_argument("Argument out of range.");
|
||||
}
|
||||
if (to >= this->presets_.size())
|
||||
{
|
||||
throw std::invalid_argument("Argument out of range.");
|
||||
}
|
||||
std::unique_ptr<BankFileEntry> t = std::move(this->presets_[from]);
|
||||
presets_.erase(presets_.begin() + from);
|
||||
presets_.insert(presets_.begin() + to, std::move(t));
|
||||
}
|
||||
void updateNextIndex()
|
||||
{
|
||||
int64_t t = 0;
|
||||
for (size_t i = 0; i < this->presets_.size(); ++i)
|
||||
{
|
||||
int64_t instanceId = this->presets_[i]->instanceId();
|
||||
if (instanceId > t)
|
||||
t = instanceId;
|
||||
}
|
||||
this->nextInstanceId_ = t;
|
||||
}
|
||||
int64_t addPreset(const Pedalboard &preset, int64_t afterItem = -1)
|
||||
{
|
||||
if (hasName(preset.name()))
|
||||
{
|
||||
throw PiPedalStateException("A preset by that name already exists.");
|
||||
}
|
||||
std::unique_ptr<BankFileEntry> entry = std::make_unique<BankFileEntry>();
|
||||
entry->instanceId(++nextInstanceId_);
|
||||
entry->preset(preset);
|
||||
int64_t instanceId = entry->instanceId();
|
||||
if (afterItem == -1)
|
||||
{
|
||||
this->presets_.push_back(std::move(entry));
|
||||
}
|
||||
else
|
||||
{
|
||||
for (auto it = this->presets_.begin(); it != this->presets_.end(); ++it)
|
||||
{
|
||||
newSelection = presets_[i]->instanceId();
|
||||
} else if (presets_.size() > 1) {
|
||||
newSelection = presets_[0]->instanceId();
|
||||
} else {
|
||||
// zero length? We can never have a zero-length bank.
|
||||
// Add a default preset and make it the selected preset.
|
||||
Pedalboard pedalboard = Pedalboard::MakeDefault();
|
||||
this->addPreset(pedalboard);
|
||||
newSelection = presets_[0]->instanceId();
|
||||
if ((*it)->instanceId() == afterItem)
|
||||
{
|
||||
++it;
|
||||
this->presets_.insert(it, std::move(entry));
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (instanceId == this->selectedPreset_)
|
||||
}
|
||||
return instanceId;
|
||||
}
|
||||
bool hasItem(int64_t instanceId)
|
||||
{
|
||||
for (size_t i = 0; i < presets_.size(); ++i)
|
||||
{
|
||||
if (presets_[i]->instanceId() == instanceId)
|
||||
{
|
||||
this->selectedPreset_ = newSelection;
|
||||
return true;
|
||||
}
|
||||
return newSelection;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
throw PiPedalStateException("Preset not found.");
|
||||
}
|
||||
|
||||
DECLARE_JSON_MAP(BankFile);
|
||||
};
|
||||
|
||||
class BankIndexEntry {
|
||||
int64_t instanceId_ = 0;
|
||||
std::string name_;
|
||||
public:
|
||||
GETTER_SETTER(instanceId);
|
||||
GETTER_SETTER_REF(name);
|
||||
|
||||
DECLARE_JSON_MAP(BankIndexEntry);
|
||||
};
|
||||
|
||||
class BankIndex {
|
||||
int64_t selectedBank_ = 0;
|
||||
int64_t nextInstanceId_ = 0;
|
||||
std::vector<BankIndexEntry> entries_;
|
||||
public:
|
||||
GETTER_SETTER(selectedBank);
|
||||
GETTER_SETTER_VEC(entries);
|
||||
|
||||
DECLARE_JSON_MAP(BankIndex);
|
||||
|
||||
bool hasName(const std::string&name) const {
|
||||
for (size_t i = 0; i < this->entries_.size(); ++i)
|
||||
bool hasName(const std::string &name)
|
||||
{
|
||||
if (this->entries_[i].name() == name) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void move(size_t from, size_t to)
|
||||
{
|
||||
if (from >= this->entries_.size()) {
|
||||
throw std::invalid_argument("Argument out of range.");
|
||||
}
|
||||
if (to >= this->entries_.size()) {
|
||||
throw std::invalid_argument("Argument out of range.");
|
||||
}
|
||||
BankIndexEntry t = std::move(this->entries_[from]);
|
||||
entries_.erase(entries_.begin()+from);
|
||||
entries_.insert(entries_.begin()+to,std::move(t));
|
||||
}
|
||||
|
||||
void clear() {
|
||||
entries_.clear();
|
||||
selectedBank_ = 0;
|
||||
}
|
||||
int64_t addBank(int64_t afterId,const std::string& name)
|
||||
{
|
||||
BankIndexEntry bank;
|
||||
bank.name(name);
|
||||
bank.instanceId(++nextInstanceId_);
|
||||
|
||||
for (size_t i = 0; i < this->entries_.size(); ++i)
|
||||
{
|
||||
if (entries_[i].instanceId() == afterId)
|
||||
for (size_t i = 0; i < presets_.size(); ++i)
|
||||
{
|
||||
entries_.insert(entries_.begin()+(i+1), bank);
|
||||
return bank.instanceId();
|
||||
if (presets_[i]->preset().name() == name)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
// else at the end.
|
||||
entries_.push_back(bank);
|
||||
return bank.instanceId();
|
||||
}
|
||||
BankIndexEntry* getEntryByName(const std::string &name)
|
||||
{
|
||||
for (size_t i = 0; i < entries_.size(); ++i)
|
||||
bool renamePreset(int64_t instanceId, const std::string &name)
|
||||
{
|
||||
if (entries_[i].name() == name)
|
||||
if (hasName(name))
|
||||
{
|
||||
return &entries_[i];
|
||||
throw PiPedalStateException("A preset by that name already exists.");
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
BankIndexEntry&getBankIndexEntry(int64_t instanceId)
|
||||
{
|
||||
for (size_t i = 0; i < entries_.size(); ++i)
|
||||
{
|
||||
if (entries_[i].instanceId() == instanceId)
|
||||
for (size_t i = 0; i < presets_.size(); ++i)
|
||||
{
|
||||
return entries_[i];
|
||||
if (presets_[i]->instanceId() == instanceId)
|
||||
{
|
||||
presets_[i]->preset().name(name);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
throw PiPedalArgumentException("Bank not found.");
|
||||
}
|
||||
const BankIndexEntry&getBankIndexEntry(int64_t instanceId) const
|
||||
{
|
||||
for (size_t i = 0; i < entries_.size(); ++i)
|
||||
{
|
||||
if (entries_[i].instanceId() == instanceId)
|
||||
{
|
||||
return entries_[i];
|
||||
}
|
||||
}
|
||||
throw PiPedalArgumentException("Bank not found.");
|
||||
}
|
||||
|
||||
};
|
||||
BankFileEntry &getItem(int64_t instanceId)
|
||||
{
|
||||
for (size_t i = 0; i < presets_.size(); ++i)
|
||||
{
|
||||
if (presets_[i]->instanceId() == instanceId)
|
||||
{
|
||||
return *(presets_[i].get());
|
||||
}
|
||||
}
|
||||
throw PiPedalArgumentException("Instance not found.");
|
||||
}
|
||||
int64_t deletePreset(int64_t instanceId)
|
||||
{
|
||||
for (size_t i = 0; i < presets_.size(); ++i)
|
||||
{
|
||||
if (presets_[i]->instanceId() == instanceId)
|
||||
{
|
||||
presets_.erase(presets_.begin() + i);
|
||||
int64_t newSelection;
|
||||
if (i < presets_.size())
|
||||
{
|
||||
newSelection = presets_[i]->instanceId();
|
||||
}
|
||||
else if (i == presets_.size() && i != 0) {
|
||||
newSelection = presets_[i-1]->instanceId();
|
||||
|
||||
}
|
||||
else if (presets_.size() >= 1)
|
||||
{
|
||||
newSelection = presets_[0]->instanceId();
|
||||
}
|
||||
else
|
||||
{
|
||||
// zero length? We can never have a zero-length bank.
|
||||
// Add a default preset and make it the selected preset.
|
||||
Pedalboard pedalboard = Pedalboard::MakeDefault();
|
||||
this->addPreset(pedalboard);
|
||||
newSelection = presets_[0]->instanceId();
|
||||
}
|
||||
if (instanceId == this->selectedPreset_)
|
||||
{
|
||||
this->selectedPreset_ = newSelection;
|
||||
}
|
||||
return newSelection;
|
||||
}
|
||||
}
|
||||
throw PiPedalStateException("Preset not found.");
|
||||
}
|
||||
|
||||
DECLARE_JSON_MAP(BankFile);
|
||||
};
|
||||
|
||||
class BankIndexEntry
|
||||
{
|
||||
int64_t instanceId_ = 0;
|
||||
std::string name_;
|
||||
|
||||
public:
|
||||
GETTER_SETTER(instanceId);
|
||||
GETTER_SETTER_REF(name);
|
||||
|
||||
DECLARE_JSON_MAP(BankIndexEntry);
|
||||
};
|
||||
|
||||
class BankIndex
|
||||
{
|
||||
int64_t selectedBank_ = 0;
|
||||
int64_t nextInstanceId_ = 0;
|
||||
std::vector<BankIndexEntry> entries_;
|
||||
|
||||
public:
|
||||
GETTER_SETTER(selectedBank);
|
||||
GETTER_SETTER_VEC(entries);
|
||||
|
||||
DECLARE_JSON_MAP(BankIndex);
|
||||
|
||||
bool hasName(const std::string &name) const
|
||||
{
|
||||
for (size_t i = 0; i < this->entries_.size(); ++i)
|
||||
{
|
||||
if (this->entries_[i].name() == name)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void move(size_t from, size_t to)
|
||||
{
|
||||
if (from >= this->entries_.size())
|
||||
{
|
||||
throw std::invalid_argument("Argument out of range.");
|
||||
}
|
||||
if (to >= this->entries_.size())
|
||||
{
|
||||
throw std::invalid_argument("Argument out of range.");
|
||||
}
|
||||
BankIndexEntry t = std::move(this->entries_[from]);
|
||||
entries_.erase(entries_.begin() + from);
|
||||
entries_.insert(entries_.begin() + to, std::move(t));
|
||||
}
|
||||
|
||||
void clear()
|
||||
{
|
||||
entries_.clear();
|
||||
selectedBank_ = 0;
|
||||
}
|
||||
int64_t addBank(int64_t afterId, const std::string &name)
|
||||
{
|
||||
BankIndexEntry bank;
|
||||
bank.name(name);
|
||||
bank.instanceId(++nextInstanceId_);
|
||||
|
||||
for (size_t i = 0; i < this->entries_.size(); ++i)
|
||||
{
|
||||
if (entries_[i].instanceId() == afterId)
|
||||
{
|
||||
entries_.insert(entries_.begin() + (i + 1), bank);
|
||||
return bank.instanceId();
|
||||
}
|
||||
}
|
||||
// else at the end.
|
||||
entries_.push_back(bank);
|
||||
return bank.instanceId();
|
||||
}
|
||||
BankIndexEntry *getEntryByName(const std::string &name)
|
||||
{
|
||||
for (size_t i = 0; i < entries_.size(); ++i)
|
||||
{
|
||||
if (entries_[i].name() == name)
|
||||
{
|
||||
return &entries_[i];
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
BankIndexEntry &getBankIndexEntry(int64_t instanceId)
|
||||
{
|
||||
for (size_t i = 0; i < entries_.size(); ++i)
|
||||
{
|
||||
if (entries_[i].instanceId() == instanceId)
|
||||
{
|
||||
return entries_[i];
|
||||
}
|
||||
}
|
||||
throw PiPedalArgumentException("Bank not found.");
|
||||
}
|
||||
const BankIndexEntry &getBankIndexEntry(int64_t instanceId) const
|
||||
{
|
||||
for (size_t i = 0; i < entries_.size(); ++i)
|
||||
{
|
||||
if (entries_[i].instanceId() == instanceId)
|
||||
{
|
||||
return entries_[i];
|
||||
}
|
||||
}
|
||||
throw PiPedalArgumentException("Bank not found.");
|
||||
}
|
||||
};
|
||||
#undef GETTER_SETTER
|
||||
#undef GETTER_SETTER_VEC
|
||||
#undef GETTER_SETTER_REF
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
+80
-60
@@ -562,7 +562,6 @@ void InstallAudioService()
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
static bool IsP2pServiceEnabled()
|
||||
{
|
||||
WifiDirectConfigSettings settings;
|
||||
@@ -742,7 +741,7 @@ void SetVarPermissions(
|
||||
catch (const std::exception &e)
|
||||
{
|
||||
// spurious errors on pulse config files.
|
||||
// std::cout << "Error: failed to set permissions on file " << entry.path() << std::endl;
|
||||
// std::cout << "Error: failed to set permissions on file " << entry.path() << std::endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -926,26 +925,26 @@ void InstallPgpKey()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static uint32_t autoSelectPorts[] = {
|
||||
static uint32_t autoSelectPorts[] = {
|
||||
80,
|
||||
81, // unofficial alternate
|
||||
8080, // unofficial alternate
|
||||
8081, // unofficial alternate
|
||||
8008, // unofficial alternate
|
||||
83 // Not official at all.
|
||||
};
|
||||
81, // unofficial alternate
|
||||
8080, // unofficial alternate
|
||||
8081, // unofficial alternate
|
||||
8008, // unofficial alternate
|
||||
83 // Not official at all.
|
||||
};
|
||||
|
||||
|
||||
|
||||
static bool isPortInUse(int port) {
|
||||
static bool isPortInUse(int port)
|
||||
{
|
||||
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
|
||||
if (sockfd < 0) {
|
||||
if (sockfd < 0)
|
||||
{
|
||||
throw std::runtime_error(SS("Socket creation failed: " << strerror(errno)));
|
||||
}
|
||||
Finally ff{[sockfd]() {
|
||||
close(sockfd);
|
||||
}};
|
||||
Finally ff{[sockfd]()
|
||||
{
|
||||
close(sockfd);
|
||||
}};
|
||||
struct sockaddr_in addr;
|
||||
memset(&addr, 0, sizeof(addr));
|
||||
addr.sin_family = AF_INET;
|
||||
@@ -953,23 +952,27 @@ static bool isPortInUse(int port) {
|
||||
addr.sin_port = htons(port);
|
||||
|
||||
// Try to bind to the port
|
||||
int result = bind(sockfd, (struct sockaddr*)&addr, sizeof(addr));
|
||||
int result = bind(sockfd, (struct sockaddr *)&addr, sizeof(addr));
|
||||
|
||||
if (result < 0) {
|
||||
if (errno == EADDRINUSE) {
|
||||
return true; // Port is in use
|
||||
} else {
|
||||
if (result < 0)
|
||||
{
|
||||
if (errno == EADDRINUSE)
|
||||
{
|
||||
return true; // Port is in use
|
||||
}
|
||||
else
|
||||
{
|
||||
cout << "Warning: Unexpected error binding socket to port " << port << ": " << strerror(errno) << endl;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false; // Port is free
|
||||
return false; // Port is free
|
||||
}
|
||||
|
||||
|
||||
static std::string AutoSelectPort() {
|
||||
constexpr size_t MAX_PORT = sizeof(autoSelectPorts)/sizeof(autoSelectPorts[0]);
|
||||
static std::string AutoSelectPort()
|
||||
{
|
||||
constexpr size_t MAX_PORT = sizeof(autoSelectPorts) / sizeof(autoSelectPorts[0]);
|
||||
|
||||
int32_t port = -1;
|
||||
for (size_t i = 0; i < MAX_PORT; ++i)
|
||||
@@ -980,29 +983,32 @@ static std::string AutoSelectPort() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (port == -1) {
|
||||
if (port == -1)
|
||||
{
|
||||
cout << "Warning: Can't find an available HTTP port. Setting to port 80 anyway." << endl;
|
||||
port = 80;
|
||||
} else if (port != 80)
|
||||
}
|
||||
else if (port != 80)
|
||||
{
|
||||
cout << "Warning: Port 80 is already in use. Using port " << port << " instead." << endl;
|
||||
cout << "Warning: Port 80 is already in use. Using port " << port << " instead." << endl;
|
||||
}
|
||||
|
||||
return SS(port);
|
||||
|
||||
}
|
||||
|
||||
|
||||
bool SetWebServerPort(std::string portOption)
|
||||
{
|
||||
|
||||
try {
|
||||
try
|
||||
{
|
||||
auto nPos = portOption.find_last_of(':');
|
||||
std::string strPort;
|
||||
if (nPos != std::string::npos)
|
||||
{
|
||||
strPort = portOption.substr(nPos+1);
|
||||
} else {
|
||||
strPort = portOption.substr(nPos + 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
strPort = portOption;
|
||||
}
|
||||
std::istringstream ss(strPort);
|
||||
@@ -1021,7 +1027,8 @@ bool SetWebServerPort(std::string portOption)
|
||||
StartService(true);
|
||||
|
||||
cout << "PiPedal web server is listening on port " << iPort << endl;
|
||||
} catch (const std::exception&e)
|
||||
}
|
||||
catch (const std::exception &e)
|
||||
{
|
||||
cout << "Error: " << e.what() << endl;
|
||||
return false;
|
||||
@@ -1031,7 +1038,8 @@ bool SetWebServerPort(std::string portOption)
|
||||
|
||||
void CheckPreemptDynamicConfig()
|
||||
{
|
||||
try {
|
||||
try
|
||||
{
|
||||
BootConfig bootConfig;
|
||||
|
||||
if (bootConfig.KernelType() == "PREEMPT_DYNAMIC")
|
||||
@@ -1044,13 +1052,12 @@ void CheckPreemptDynamicConfig()
|
||||
cout << endl;
|
||||
}
|
||||
}
|
||||
|
||||
} catch(const std::exception& /* ignore */)
|
||||
}
|
||||
catch (const std::exception & /* ignore */)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void Install(const fs::path &programPrefix, const std::string endpointAddress)
|
||||
{
|
||||
cout << "Configuring pipedal" << endl;
|
||||
@@ -1058,10 +1065,10 @@ void Install(const fs::path &programPrefix, const std::string endpointAddress)
|
||||
if (!UsingNetworkManager())
|
||||
{
|
||||
cout << "Warning: The current OS is not using the NetworkManager network stack." << endl;
|
||||
cout << " The PiPedal Auto-Hotspot feature will be disabled." << endl << endl;
|
||||
cout << " The PiPedal Auto-Hotspot feature will be disabled." << endl
|
||||
<< endl;
|
||||
cout << " Consult PiPedal documentation to find out how to correct this " << endl;
|
||||
cout << " issue on Ubuntu Server." << endl;
|
||||
|
||||
}
|
||||
try
|
||||
{
|
||||
@@ -1301,10 +1308,11 @@ void Install(const fs::path &programPrefix, const std::string endpointAddress)
|
||||
if (endpointAddress.starts_with(ADDR_ANY))
|
||||
{
|
||||
cout << "PiPedal web server is listening on port " << endpointAddress.substr(ADDR_ANY.length()) << endl;
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
cout << "PiPedal web server is listening on port " << endpointAddress << endl;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static std::string GetCurrentWebServicePort()
|
||||
@@ -1494,25 +1502,27 @@ void ListValidCountryCodes()
|
||||
if (ccs.size() == 0)
|
||||
{
|
||||
cout << "No regulatory domains found." << endl;
|
||||
} else {
|
||||
using pair = std::pair<std::string,std::string>;
|
||||
}
|
||||
else
|
||||
{
|
||||
using pair = std::pair<std::string, std::string>;
|
||||
std::vector<pair> list;
|
||||
for (auto &cc: ccs)
|
||||
for (auto &cc : ccs)
|
||||
{
|
||||
list.push_back(pair(cc.first,cc.second));
|
||||
list.push_back(pair(cc.first, cc.second));
|
||||
}
|
||||
|
||||
auto collator = Locale::GetInstance()->GetCollator();
|
||||
auto compare = [&collator](
|
||||
pair &left,
|
||||
pair &right)
|
||||
pair &left,
|
||||
pair &right)
|
||||
{
|
||||
return collator->Compare(
|
||||
left.second, right.second) < 0;
|
||||
left.second, right.second) < 0;
|
||||
};
|
||||
std::sort(list.begin(), list.end(), compare);
|
||||
|
||||
for (const auto &entry: list)
|
||||
for (const auto &entry : list)
|
||||
{
|
||||
cout << entry.first << " - " << entry.second << endl;
|
||||
}
|
||||
@@ -1521,12 +1531,15 @@ void ListValidCountryCodes()
|
||||
|
||||
static std::string ProcessPortOption(std::string portOption)
|
||||
{
|
||||
if (portOption == "" || portOption == "0") // allow testing of port autodetect.
|
||||
if (portOption == "" || portOption == "0") // allow testing of port autodetect.
|
||||
{
|
||||
std::string t = GetCurrentWebServicePort();
|
||||
if (portOption != "0") {// allows easy debugging of port AutoSelectPort.
|
||||
if (portOption != "0")
|
||||
{ // allows easy debugging of port AutoSelectPort.
|
||||
portOption = t;
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
portOption = "";
|
||||
}
|
||||
if (portOption == "")
|
||||
@@ -1569,7 +1582,7 @@ int main(int argc, char **argv)
|
||||
bool noWifi = false;
|
||||
|
||||
parser.AddOption("--nosudo", &nosudo); // strictly a debugging aid. Run without sudo, until we fail because of permissions.
|
||||
parser.AddOption("--list-wifi-country-codes",&list_wifi_country_codes);
|
||||
parser.AddOption("--list-wifi-country-codes", &list_wifi_country_codes);
|
||||
parser.AddOption("--install", &install);
|
||||
parser.AddOption("--uninstall", &uninstall);
|
||||
parser.AddOption("--stop", &stop);
|
||||
@@ -1589,15 +1602,17 @@ int main(int argc, char **argv)
|
||||
parser.AddOption("--alsa-devices", &alsaDevices);
|
||||
parser.AddOption("--alsa-device", &alsaDevice);
|
||||
parser.AddOption("--fix-permissions", &fix_permissions);
|
||||
|
||||
try
|
||||
{
|
||||
parser.Parse(argc, (const char **)argv);
|
||||
|
||||
int actionCount =
|
||||
(!alsaDevice.empty()) + alsaDevices + help + get_current_port + install +
|
||||
uninstall + stop + start + enable + disable + enable_hotspot +
|
||||
disable_hotspot + restart + enable_p2p + disable_p2p + list_p2p_channels +
|
||||
fix_permissions + list_wifi_country_codes;
|
||||
(!alsaDevice.empty()) + alsaDevices + help + get_current_port + install +
|
||||
uninstall + stop + start + enable + disable + enable_hotspot +
|
||||
disable_hotspot + restart + enable_p2p + disable_p2p + list_p2p_channels +
|
||||
fix_permissions + list_wifi_country_codes;
|
||||
|
||||
if (actionCount == 0 && portOption.length() != 0)
|
||||
{
|
||||
++actionCount;
|
||||
@@ -1615,6 +1630,12 @@ int main(int argc, char **argv)
|
||||
|
||||
throw std::runtime_error("No action provided.");
|
||||
}
|
||||
|
||||
if (portOption.length() != 0 && !install)
|
||||
{
|
||||
throw std::runtime_error("The --port option can only be specified in conjunction with the --install option.");
|
||||
}
|
||||
|
||||
if ((!enable_p2p) && (!enable_hotspot) && (!list_p2p_channels))
|
||||
{
|
||||
if (parser.Arguments().size() != 0)
|
||||
@@ -1744,7 +1765,6 @@ int main(int argc, char **argv)
|
||||
|
||||
Install(prefix, portOption);
|
||||
FileSystemSync();
|
||||
|
||||
}
|
||||
catch (const std::exception &e)
|
||||
{
|
||||
|
||||
+4
-2
@@ -153,12 +153,13 @@ bool Pedalboard::SetControlValue(int64_t pedalItemId, const std::string &symbol,
|
||||
return item->SetControlValue(symbol,value);
|
||||
}
|
||||
|
||||
bool Pedalboard::SetItemTitle(int64_t pedalItemId, const std::string &title)
|
||||
bool Pedalboard::SetItemTitle(int64_t pedalItemId, const std::string &title, const std::string&iconColor)
|
||||
{
|
||||
PedalboardItem*item = GetItem(pedalItemId);
|
||||
if (!item) return false;
|
||||
if (item->title() == title) return false; // no change.
|
||||
if (item->title() == title && item->iconColor() == iconColor) return false; // no change.
|
||||
item->title(title);
|
||||
item->iconColor(iconColor);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -532,6 +533,7 @@ JSON_MAP_BEGIN(PedalboardItem)
|
||||
JSON_MAP_REFERENCE(PedalboardItem,pathProperties)
|
||||
JSON_MAP_REFERENCE(PedalboardItem,title)
|
||||
JSON_MAP_REFERENCE(PedalboardItem,useModUi)
|
||||
JSON_MAP_REFERENCE(PedalboardItem,iconColor)
|
||||
JSON_MAP_END()
|
||||
|
||||
|
||||
|
||||
+3
-1
@@ -106,6 +106,7 @@ public:
|
||||
std::map<std::string,std::string> pathProperties_;
|
||||
std::string title_;
|
||||
bool useModUi_ = false;
|
||||
std::string iconColor_;
|
||||
|
||||
// non persistent state.
|
||||
PropertyMap patchProperties;
|
||||
@@ -135,6 +136,7 @@ public:
|
||||
GETTER_SETTER(stateUpdateCount)
|
||||
GETTER_SETTER_REF(lv2State)
|
||||
GETTER_SETTER_REF(title)
|
||||
GETTER_SETTER_REF(iconColor)
|
||||
GETTER_SETTER(useModUi)
|
||||
|
||||
Lv2PluginState&lv2State() { return lv2State_; } // non-const version.
|
||||
@@ -232,7 +234,7 @@ public:
|
||||
static constexpr int64_t INPUT_VOLUME_ID = -2; // synthetic PedalboardItem for input volume.
|
||||
static constexpr int64_t OUTPUT_VOLUME_ID = -3; // synthetic PedalboardItem for output volume.
|
||||
bool SetControlValue(int64_t pedalItemId, const std::string &symbol, float value);
|
||||
bool SetItemTitle(int64_t pedalItemId, const std::string &title);
|
||||
bool SetItemTitle(int64_t pedalItemId, const std::string &title, const std::string&iconColor);
|
||||
bool SetItemEnabled(int64_t pedalItemId, bool enabled);
|
||||
bool SetItemUseModUi(int64_t pedalItemId, bool enabled);
|
||||
void SetCurrentSnapshotModified(bool modified);
|
||||
|
||||
+60
-28
@@ -33,6 +33,7 @@
|
||||
#include <iomanip>
|
||||
#include <chrono>
|
||||
#include <thread>
|
||||
#include <sched.h>
|
||||
|
||||
using namespace pipedal;
|
||||
|
||||
@@ -59,7 +60,7 @@ void PrintHelp()
|
||||
pp << Indent(0) << "Syntax\n\n";
|
||||
pp << Indent(2) << "pipedal_latency_test [<options>] <input-device> [<output-device>]\n\n";
|
||||
pp << "where <input-device> is the name of an ALSA capture device and <output-device> is the name of a playback device. "
|
||||
"If <output-device> is omitted, the input device will be used for both capture and playback. Typically the device names start with 'hw:'.\n\n";
|
||||
"If <output-device> is omitted, the input device will be used for both capture and playback. Typically the device names start with 'hw:'.\n\n";
|
||||
pp << Indent(0) << "Options\n\n";
|
||||
pp << Indent(15);
|
||||
|
||||
@@ -85,13 +86,13 @@ void PrintHelp()
|
||||
<< "PiPedal Latency Tester measures internal buffer delays as well as operating system and "
|
||||
<< "signal delays in hardware peripherals. Latency figures will therefore be somewhat higher than "
|
||||
<< "most reported latency figures which typically only include internal buffer delays.\n\n";
|
||||
|
||||
pp
|
||||
<< "The tests run over a variety of buffer sizes. A nominal compute load is provided in order to put some "
|
||||
"stress on the audio system.\n\n"
|
||||
|
||||
<< "You may need to stop the pipedald audio service in order to access the ALSA device:\n\n"
|
||||
<< Indent(6) << "sudo systemctl stop pipedald\n\n";
|
||||
pp
|
||||
<< "The tests run over a variety of buffer sizes. A nominal compute load is provided in order to put some "
|
||||
"stress on the audio system.\n\n"
|
||||
|
||||
<< "You may need to stop the pipedald audio service in order to access the ALSA device:\n\n"
|
||||
<< Indent(6) << "sudo systemctl stop pipedald\n\n";
|
||||
|
||||
pp << Indent(0) << "Examples\n\n";
|
||||
pp << Indent(2) << "pipedal_latency_test --list\n\n";
|
||||
@@ -123,7 +124,6 @@ void ListDevices()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
using ChannelsT = std::vector<int>;
|
||||
|
||||
class AlsaTester : private AudioDriverHost
|
||||
@@ -169,12 +169,13 @@ public:
|
||||
delete[] inputBuffers;
|
||||
delete[] outputBuffers;
|
||||
}
|
||||
std::vector<std::string> SelectChannels(const std::vector<std::string>&available, const std::vector<int>& selection)
|
||||
std::vector<std::string> SelectChannels(const std::vector<std::string> &available, const std::vector<int> &selection)
|
||||
{
|
||||
if (selection.size() == 0) return available;
|
||||
if (selection.size() == 0)
|
||||
return available;
|
||||
|
||||
std::vector<std::string> result;
|
||||
for (int sel: selection)
|
||||
for (int sel : selection)
|
||||
{
|
||||
if (sel < 0 || sel >= available.size())
|
||||
{
|
||||
@@ -195,14 +196,13 @@ public:
|
||||
JackConfiguration jackConfiguration;
|
||||
jackConfiguration.AlsaInitialize(serverSettings);
|
||||
|
||||
auto & availableInputs = jackConfiguration.inputAudioPorts();
|
||||
auto & availableOutputs = jackConfiguration.outputAudioPorts();
|
||||
|
||||
auto &availableInputs = jackConfiguration.inputAudioPorts();
|
||||
auto &availableOutputs = jackConfiguration.outputAudioPorts();
|
||||
|
||||
std::vector<std::string> inputAudioPorts, outputAudioPorts;
|
||||
|
||||
inputAudioPorts = SelectChannels(availableInputs,this->inputChannels);
|
||||
outputAudioPorts = SelectChannels(availableOutputs,this->outputChannels);
|
||||
inputAudioPorts = SelectChannels(availableInputs, this->inputChannels);
|
||||
outputAudioPorts = SelectChannels(availableOutputs, this->outputChannels);
|
||||
|
||||
JackChannelSelection channelSelection(
|
||||
inputAudioPorts, outputAudioPorts,
|
||||
@@ -360,7 +360,6 @@ public:
|
||||
}
|
||||
virtual void OnAudioTerminated()
|
||||
{
|
||||
|
||||
}
|
||||
virtual void OnProcess(size_t nFrames)
|
||||
{
|
||||
@@ -413,7 +412,7 @@ TestResult RunLatencyTest(
|
||||
const ChannelsT &outputChannels,
|
||||
uint32_t sampleRate, int bufferSize, int buffers)
|
||||
{
|
||||
AlsaTester tester(inputDeviceId, outputDeviceId, inputChannels, outputChannels, sampleRate, bufferSize, buffers);
|
||||
AlsaTester tester(inputDeviceId, outputDeviceId, inputChannels, outputChannels, sampleRate, bufferSize, buffers);
|
||||
return tester.Test();
|
||||
}
|
||||
|
||||
@@ -432,6 +431,28 @@ static std::string overheadDisplay(float value)
|
||||
return s.str();
|
||||
}
|
||||
|
||||
static bool testRealtimePriorityPrivileges()
|
||||
{
|
||||
|
||||
struct sched_param currentParam;
|
||||
int currentPolicy = sched_getscheduler(0);
|
||||
if (currentPolicy == -1) {
|
||||
return false;
|
||||
}
|
||||
if (sched_getparam(0, ¤tParam) != 0) {
|
||||
return false;
|
||||
}
|
||||
struct sched_param param;
|
||||
param.sched_priority = 85;
|
||||
if (sched_setscheduler(0, SCHED_RR, ¶m) != 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
// Restore normal priority
|
||||
sched_setscheduler(0, currentPolicy, ¤tParam);
|
||||
return true;
|
||||
}
|
||||
|
||||
void RunLatencyTest(
|
||||
const std::string &inputDeviceId,
|
||||
const std::string &outputDeviceId,
|
||||
@@ -439,8 +460,20 @@ void RunLatencyTest(
|
||||
const ChannelsT &outputChannels,
|
||||
uint32_t sampleRate)
|
||||
{
|
||||
|
||||
PrettyPrinter pp;
|
||||
pp << "Input: " << inputDeviceId << " Output: " << outputDeviceId << " Rate: " << sampleRate << "\n\n";
|
||||
|
||||
if (!testRealtimePriorityPrivileges())
|
||||
{
|
||||
pp << "Unable to enable realtime scheduling. Add your user id to the pipedal_d group to fix this problem:" << "\n\n";
|
||||
pp.AddIndent(4);
|
||||
pp << "sudo usermod -a -G pipedal_d <your user id>" << "\n\n";
|
||||
pp.AddIndent(-4);
|
||||
pp << "You will need to log out or reboot your system in order for the change to take effect.\n\n";
|
||||
throw std::runtime_error("Unable to set relatime thread priority.");
|
||||
}
|
||||
|
||||
pp << "Input: " << inputDeviceId << " Output: " << outputDeviceId << " Rate: " << sampleRate << "\n\n";
|
||||
|
||||
const int SIZE_COLUMN_WIDTH = 8;
|
||||
const int BUFFERS_COLUMN_WIDTH = 20;
|
||||
@@ -467,7 +500,7 @@ void RunLatencyTest(
|
||||
|
||||
for (auto bufferCount : bufferCounts)
|
||||
{
|
||||
auto result = RunLatencyTest(inputDeviceId, outputDeviceId, inputChannels,outputChannels, sampleRate, bufferSize, bufferCount);
|
||||
auto result = RunLatencyTest(inputDeviceId, outputDeviceId, inputChannels, outputChannels, sampleRate, bufferSize, bufferCount);
|
||||
|
||||
pp.Column(column);
|
||||
column += BUFFERS_COLUMN_WIDTH;
|
||||
@@ -495,8 +528,7 @@ void RunLatencyTest(
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
ChannelsT ParseChannels(const std::string&channels)
|
||||
ChannelsT ParseChannels(const std::string &channels)
|
||||
{
|
||||
ChannelsT result;
|
||||
std::stringstream s(channels);
|
||||
@@ -504,9 +536,11 @@ ChannelsT ParseChannels(const std::string&channels)
|
||||
while (true)
|
||||
{
|
||||
int c = s.peek();
|
||||
if (c == -1) break;
|
||||
if (c == -1)
|
||||
break;
|
||||
|
||||
if (c == ',') {
|
||||
if (c == ',')
|
||||
{
|
||||
s.get();
|
||||
c = s.peek();
|
||||
}
|
||||
@@ -518,14 +552,12 @@ ChannelsT ParseChannels(const std::string&channels)
|
||||
while (s.peek() >= '0' && s.peek() <= '9')
|
||||
{
|
||||
c = s.get();
|
||||
v = v*10 + c-'0';
|
||||
v = v * 10 + c - '0';
|
||||
}
|
||||
result.push_back(v);
|
||||
}
|
||||
|
||||
return result;
|
||||
|
||||
|
||||
}
|
||||
|
||||
int main(int argc, const char **argv)
|
||||
@@ -567,7 +599,7 @@ std:
|
||||
inputChannels = ParseChannels(strInputChannels);
|
||||
outputChannels = ParseChannels(strOutputChannels);
|
||||
|
||||
std::string inDev = parser.Arguments()[0];
|
||||
std::string inDev = parser.Arguments()[0];
|
||||
std::string outDev = parser.Arguments().size() == 2 ? parser.Arguments()[1] : inDev;
|
||||
RunLatencyTest(inDev, outDev, inputChannels, outputChannels, sampleRate);
|
||||
}
|
||||
|
||||
+41
-13
@@ -887,7 +887,7 @@ int64_t PiPedalModel::SavePluginPresetAs(int64_t instanceId, const std::string &
|
||||
return presetId;
|
||||
}
|
||||
|
||||
int64_t PiPedalModel::SaveCurrentPresetAs(int64_t clientId, const std::string &name, int64_t saveAfterInstanceId)
|
||||
int64_t PiPedalModel::SaveCurrentPresetAs(int64_t clientId, int64_t bankInstanceId,const std::string &name, int64_t saveAfterInstanceId)
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> guard{mutex};
|
||||
|
||||
@@ -897,7 +897,7 @@ int64_t PiPedalModel::SaveCurrentPresetAs(int64_t clientId, const std::string &n
|
||||
|
||||
UpdateVst3Settings(pedalboard);
|
||||
pedalboard.name(name);
|
||||
int64_t result = storage.SaveCurrentPresetAs(pedalboard, name, saveAfterInstanceId);
|
||||
int64_t result = storage.SaveCurrentPresetAs(pedalboard, bankInstanceId, name, saveAfterInstanceId);
|
||||
FirePresetsChanged(clientId);
|
||||
return result;
|
||||
}
|
||||
@@ -1174,12 +1174,12 @@ int64_t PiPedalModel::DeleteBank(int64_t clientId, int64_t instanceId)
|
||||
return newSelection;
|
||||
}
|
||||
|
||||
int64_t PiPedalModel::DeletePreset(int64_t clientId, int64_t instanceId)
|
||||
int64_t PiPedalModel::DeletePresets(int64_t clientId, const std::vector<int64_t> &presetInstanceIds)
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> guard{mutex};
|
||||
int64_t oldSelection = storage.GetCurrentPresetId();
|
||||
int64_t newSelection = storage.DeletePreset(instanceId);
|
||||
this->FirePresetsChanged(clientId); // fire now.
|
||||
int64_t newSelection = storage.DeletePresets(presetInstanceIds);
|
||||
this->FirePresetsChanged(clientId); // fire BEFORE we load a new preset.
|
||||
if (oldSelection != newSelection)
|
||||
{
|
||||
this->LoadPreset(
|
||||
@@ -1745,15 +1745,18 @@ void PiPedalModel::SendSetPatchProperty(
|
||||
{
|
||||
std::shared_ptr<Lv2PluginInfo> pluginInfo = GetPluginInfo(pedalboardItem->uri_);
|
||||
auto pipedalUi = pluginInfo->piPedalUI();
|
||||
auto fileProperty = pipedalUi->GetFileProperty(propertyUri);
|
||||
if (fileProperty)
|
||||
if (pipedalUi)
|
||||
{
|
||||
auto fileProperty = pipedalUi->GetFileProperty(propertyUri);
|
||||
if (fileProperty)
|
||||
{
|
||||
|
||||
json_variant abstractPath = pluginHost.AbstractPath(value);
|
||||
std::string atomString = abstractPath.to_string();
|
||||
pedalboardItem->pathProperties_[propertyUri] = atomString;
|
||||
json_variant abstractPath = pluginHost.AbstractPath(value);
|
||||
std::string atomString = abstractPath.to_string();
|
||||
pedalboardItem->pathProperties_[propertyUri] = atomString;
|
||||
}
|
||||
this->SetPresetChanged(clientId, true);
|
||||
}
|
||||
this->SetPresetChanged(clientId, true);
|
||||
}
|
||||
LV2_Atom *atomValue = atomConverter.ToAtom(value);
|
||||
|
||||
@@ -3226,10 +3229,10 @@ void PiPedalModel::MoveAudioFile(
|
||||
AudioDirectoryInfo::Ptr dir = AudioDirectoryInfo::Create(directory);
|
||||
dir->MoveAudioFile(directory, fromPosition, toPosition);
|
||||
}
|
||||
void PiPedalModel::SetPedalboardItemTitle(int64_t instanceId, const std::string &title)
|
||||
void PiPedalModel::SetPedalboardItemTitle(int64_t instanceId, const std::string &title, const std::string &colorKey)
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> lock(mutex);
|
||||
if (!this->pedalboard.SetItemTitle(instanceId, title))
|
||||
if (!this->pedalboard.SetItemTitle(instanceId, title, colorKey))
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -3254,3 +3257,28 @@ bool PiPedalModel::HasTone3000Auth() const
|
||||
{
|
||||
return storage.GetTone3000Auth() != "";
|
||||
}
|
||||
|
||||
std::vector<PresetIndexEntry> PiPedalModel::RequestBankPresets(int64_t bankInstanceId)
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> lock(mutex);
|
||||
|
||||
return storage.RequestBankPresets(bankInstanceId);
|
||||
|
||||
}
|
||||
|
||||
int64_t PiPedalModel::ImportPresetsFromBank(int64_t bankInstanceId, const std::vector<int64_t> &presets)
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> lock(mutex);
|
||||
uint64_t lastAdded = storage.ImportPresetsFromBank(bankInstanceId, presets);
|
||||
|
||||
FirePresetsChanged(-1);
|
||||
return lastAdded;
|
||||
|
||||
}
|
||||
int64_t PiPedalModel::CopyPresetsToBank(int64_t bankInstanceId, const std::vector<int64_t> &presets)
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> lock(mutex);
|
||||
uint64_t lastAdded = storage.CopyPresetsToBank(bankInstanceId, presets);
|
||||
return lastAdded;
|
||||
|
||||
}
|
||||
|
||||
@@ -378,13 +378,14 @@ namespace pipedal
|
||||
int64_t UploadPreset(const BankFile &bankFile, int64_t uploadAfter = -1);
|
||||
void UploadPluginPresets(const PluginPresets &pluginPresets);
|
||||
void SaveCurrentPreset(int64_t clientId);
|
||||
int64_t SaveCurrentPresetAs(int64_t clientId, const std::string &name, int64_t saveAfterInstanceId = -1);
|
||||
int64_t SaveCurrentPresetAs(int64_t clientId, int64_t bankInstanceId, const std::string &name, int64_t saveAfterInstanceId = -1);
|
||||
int64_t SavePluginPresetAs(int64_t instanceId, const std::string &name);
|
||||
|
||||
|
||||
void LoadPreset(int64_t clientId, int64_t instanceId);
|
||||
bool UpdatePresets(int64_t clientId, const PresetIndex &presets);
|
||||
void UpdatePluginPresets(const PluginUiPresets &pluginPresets);
|
||||
int64_t DeletePreset(int64_t clientId, int64_t instanceId);
|
||||
int64_t DeletePresets(int64_t clientId, const std::vector<int64_t> &presetInstanceIds);
|
||||
bool RenamePreset(int64_t clientId, int64_t instanceId, const std::string &name);
|
||||
int64_t CopyPreset(int64_t clientId, int64_t fromIndex, int64_t toIndex);
|
||||
uint64_t CopyPluginPreset(const std::string &pluginUri, uint64_t presetId);
|
||||
@@ -486,13 +487,16 @@ namespace pipedal
|
||||
int32_t from,
|
||||
int32_t to);
|
||||
|
||||
void SetPedalboardItemTitle(int64_t instanceId, const std::string &title);
|
||||
void SetPedalboardItemTitle(int64_t instanceId, const std::string &title, const std::string&iconColor);
|
||||
|
||||
void SetTone3000Auth(const std::string &apiKey);
|
||||
bool HasTone3000Auth() const;
|
||||
|
||||
void SetSelectedPedalboardPlugin(uint64_t clientId, uint64_t pedalboardId);
|
||||
|
||||
std::vector<PresetIndexEntry> RequestBankPresets(int64_t bankInstanceId);
|
||||
int64_t ImportPresetsFromBank(int64_t bankInstanceId, const std::vector<int64_t> &presets);
|
||||
int64_t CopyPresetsToBank(int64_t bankInstanceId, const std::vector<int64_t> &presets);
|
||||
};
|
||||
|
||||
} // namespace pipedal.
|
||||
+62
-7
@@ -46,6 +46,38 @@
|
||||
using namespace std;
|
||||
using namespace pipedal;
|
||||
|
||||
class CopyPresetsToBankBody {
|
||||
public:
|
||||
int64_t bankInstanceId_;
|
||||
std::vector<int64_t> presets_;
|
||||
DECLARE_JSON_MAP(CopyPresetsToBankBody);
|
||||
};
|
||||
JSON_MAP_BEGIN(CopyPresetsToBankBody)
|
||||
JSON_MAP_REFERENCE(CopyPresetsToBankBody, bankInstanceId)
|
||||
JSON_MAP_REFERENCE(CopyPresetsToBankBody, presets)
|
||||
JSON_MAP_END()
|
||||
|
||||
|
||||
class ImportPresetsFromBankBody {
|
||||
public:
|
||||
int64_t bankInstanceId_;
|
||||
std::vector<int64_t> presets_;
|
||||
DECLARE_JSON_MAP(ImportPresetsFromBankBody);
|
||||
};
|
||||
JSON_MAP_BEGIN(ImportPresetsFromBankBody)
|
||||
JSON_MAP_REFERENCE(ImportPresetsFromBankBody, bankInstanceId)
|
||||
JSON_MAP_REFERENCE(ImportPresetsFromBankBody, presets)
|
||||
JSON_MAP_END()
|
||||
|
||||
class RequestBankPresetsBody {
|
||||
public:
|
||||
int64_t bankInstanceId_;
|
||||
DECLARE_JSON_MAP(RequestBankPresetsBody);
|
||||
};
|
||||
JSON_MAP_BEGIN(RequestBankPresetsBody)
|
||||
JSON_MAP_REFERENCE(RequestBankPresetsBody, bankInstanceId)
|
||||
JSON_MAP_END()
|
||||
|
||||
class PathPatchPropertyChangedBody
|
||||
{
|
||||
public:
|
||||
@@ -179,12 +211,14 @@ class SetPedalboardItemTitleBody
|
||||
public:
|
||||
uint64_t instanceId_;
|
||||
std::string title_;
|
||||
std::string colorKey_;
|
||||
DECLARE_JSON_MAP(SetPedalboardItemTitleBody);
|
||||
};
|
||||
|
||||
JSON_MAP_BEGIN(SetPedalboardItemTitleBody)
|
||||
JSON_MAP_REFERENCE(SetPedalboardItemTitleBody, instanceId)
|
||||
JSON_MAP_REFERENCE(SetPedalboardItemTitleBody, title)
|
||||
JSON_MAP_REFERENCE(SetPedalboardItemTitleBody, colorKey)
|
||||
JSON_MAP_END()
|
||||
|
||||
|
||||
@@ -334,6 +368,7 @@ class SaveCurrentPresetAsBody
|
||||
{
|
||||
public:
|
||||
int64_t clientId_ = -1;
|
||||
int64_t bankInstanceId_ = -1;
|
||||
std::string name_;
|
||||
int64_t saveAfterInstanceId_ = -1;
|
||||
|
||||
@@ -341,6 +376,7 @@ public:
|
||||
};
|
||||
JSON_MAP_BEGIN(SaveCurrentPresetAsBody)
|
||||
JSON_MAP_REFERENCE(SaveCurrentPresetAsBody, clientId)
|
||||
JSON_MAP_REFERENCE(SaveCurrentPresetAsBody, bankInstanceId)
|
||||
JSON_MAP_REFERENCE(SaveCurrentPresetAsBody, name)
|
||||
JSON_MAP_REFERENCE(SaveCurrentPresetAsBody, saveAfterInstanceId)
|
||||
JSON_MAP_END()
|
||||
@@ -1312,7 +1348,7 @@ public:
|
||||
{
|
||||
SaveCurrentPresetAsBody body;
|
||||
pReader->read(&body);
|
||||
int64_t result = this->model.SaveCurrentPresetAs(this->clientId, body.name_, body.saveAfterInstanceId_);
|
||||
int64_t result = this->model.SaveCurrentPresetAs(this->clientId, body.bankInstanceId_,body.name_, body.saveAfterInstanceId_);
|
||||
Reply(replyTo, "saveCurrentPresetsAs", result);
|
||||
}
|
||||
else if (message == "setSelectedPedalboardPlugin")
|
||||
@@ -1445,12 +1481,12 @@ public:
|
||||
model.RequestShutdown(true);
|
||||
this->Reply(replyTo, "restart");
|
||||
}
|
||||
else if (message == "deletePresetItem")
|
||||
else if (message == "deletePresetItems")
|
||||
{
|
||||
int64_t instanceId = 0;
|
||||
pReader->read(&instanceId);
|
||||
int64_t result = model.DeletePreset(this->clientId, instanceId);
|
||||
this->Reply(replyTo, "deletePresetItem", result);
|
||||
std::vector<int64_t> items;
|
||||
pReader->read(&items);
|
||||
int64_t result = model.DeletePresets(this->clientId, items);
|
||||
this->Reply(replyTo, "deletePresetItems", result);
|
||||
}
|
||||
else if (message == "deleteBankItem")
|
||||
{
|
||||
@@ -1569,7 +1605,7 @@ public:
|
||||
{
|
||||
SetPedalboardItemTitleBody body;
|
||||
pReader->read(&body);
|
||||
model.SetPedalboardItemTitle(body.instanceId_, body.title_);
|
||||
model.SetPedalboardItemTitle(body.instanceId_, body.title_, body.colorKey_);
|
||||
}
|
||||
else if (message == "getPatchProperty")
|
||||
{
|
||||
@@ -1792,6 +1828,25 @@ public:
|
||||
{
|
||||
std::vector<AlsaSequencerPortSelection> result = model.GetAlsaSequencerPorts();
|
||||
this->Reply(replyTo,"getAlsaSequencerPorts", result);
|
||||
} else if (message == "requestBankPresets") {
|
||||
|
||||
RequestBankPresetsBody args;
|
||||
pReader->read(&args);
|
||||
auto result = this->model.RequestBankPresets(args.bankInstanceId_);
|
||||
this->Reply(replyTo,"requestBankPresets",result);
|
||||
|
||||
}
|
||||
else if (message == "importPresetsFromBank") {
|
||||
ImportPresetsFromBankBody args;
|
||||
pReader->read(&args);
|
||||
auto result = this->model.ImportPresetsFromBank(args.bankInstanceId_, args.presets_);
|
||||
this->Reply(replyTo,"importPresetsFromBank",result);
|
||||
}
|
||||
else if (message == "copyPresetsToBank") {
|
||||
CopyPresetsToBankBody args;
|
||||
pReader->read(&args);
|
||||
auto result = this->model.CopyPresetsToBank(args.bankInstanceId_, args.presets_);
|
||||
this->Reply(replyTo,"copyPresetsToBank",result);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
+3
-1
@@ -538,7 +538,8 @@ UiFrequencyPlot::UiFrequencyPlot(PluginHost*pHost, const LilvNode*node,
|
||||
this->xRight_ = GetFloat(pWorld,node,pHost->lilvUris->pipedalUI__xRight,22000);
|
||||
this->yTop_ = GetFloat(pWorld,node,pHost->lilvUris->pipedalUI__yTop,5);
|
||||
this->yBottom_ = GetFloat(pWorld,node,pHost->lilvUris->pipedalUI__yBottom,-35);
|
||||
this->xLog_ = GetFloat(pWorld,node,pHost->lilvUris->pipedalUI__xLog,-35);
|
||||
this->xLog_ = GetFloat(pWorld,node,pHost->lilvUris->pipedalUI__xLog,1);
|
||||
this->yDb_ = GetFloat(pWorld,node,pHost->lilvUris->pipedalUI__yDb,1);
|
||||
this->width_ = GetFloat(pWorld,node,pHost->lilvUris->pipedalUI__width,176);
|
||||
}
|
||||
|
||||
@@ -727,6 +728,7 @@ JSON_MAP_REFERENCE(UiFrequencyPlot, portGroup)
|
||||
JSON_MAP_REFERENCE(UiFrequencyPlot, xLeft)
|
||||
JSON_MAP_REFERENCE(UiFrequencyPlot, xRight)
|
||||
JSON_MAP_REFERENCE(UiFrequencyPlot, xLog)
|
||||
JSON_MAP_REFERENCE(UiFrequencyPlot, yDb)
|
||||
JSON_MAP_REFERENCE(UiFrequencyPlot, yTop)
|
||||
JSON_MAP_REFERENCE(UiFrequencyPlot, yBottom)
|
||||
JSON_MAP_REFERENCE(UiFrequencyPlot, width)
|
||||
|
||||
@@ -63,6 +63,7 @@
|
||||
#define PIPEDAL_UI__xLeft PIPEDAL_UI_PREFIX "xLeft"
|
||||
#define PIPEDAL_UI__xRight PIPEDAL_UI_PREFIX "xRight"
|
||||
#define PIPEDAL_UI__xLog PIPEDAL_UI_PREFIX "xLog"
|
||||
#define PIPEDAL_UI__yDb PIPEDAL_UI_PREFIX "yDb"
|
||||
#define PIPEDAL_UI__yTop PIPEDAL_UI_PREFIX "yTop"
|
||||
#define PIPEDAL_UI__yBottom PIPEDAL_UI_PREFIX "yBottom"
|
||||
#define PIPEDAL_UI__width PIPEDAL_UI_PREFIX "width"
|
||||
@@ -196,6 +197,7 @@ namespace pipedal
|
||||
float yTop_ = 5;
|
||||
float yBottom_ = -30;
|
||||
bool xLog_ = true;
|
||||
bool yDb_ = true;
|
||||
float width_ = 60;
|
||||
|
||||
public:
|
||||
@@ -213,6 +215,7 @@ namespace pipedal
|
||||
float yTop() const { return yTop_; }
|
||||
float yBottom() const { return yBottom_; }
|
||||
float width() const { return width_; }
|
||||
bool yDb() const { return yDb_; }
|
||||
|
||||
public:
|
||||
DECLARE_JSON_MAP(UiFrequencyPlot);
|
||||
|
||||
@@ -165,6 +165,7 @@ void PluginHost::LilvUris::Initialize(LilvWorld *pWorld)
|
||||
pipedalUI__yTop = lilv_new_uri(pWorld, PIPEDAL_UI__yTop);
|
||||
pipedalUI__yBottom = lilv_new_uri(pWorld, PIPEDAL_UI__yBottom);
|
||||
pipedalUI__xLog = lilv_new_uri(pWorld, PIPEDAL_UI__xLog);
|
||||
pipedalUI__yDb = lilv_new_uri(pWorld, PIPEDAL_UI__yDb);
|
||||
pipedalUI__width = lilv_new_uri(pWorld, PIPEDAL_UI__width);
|
||||
pipedalUI__graphicEq = lilv_new_uri(pWorld, PIPEDAL_UI__graphicEq);
|
||||
|
||||
|
||||
@@ -810,6 +810,7 @@ namespace pipedal
|
||||
AutoLilvNode pipedalUI__xLeft;
|
||||
AutoLilvNode pipedalUI__xRight;
|
||||
AutoLilvNode pipedalUI__xLog;
|
||||
AutoLilvNode pipedalUI__yDb;
|
||||
AutoLilvNode pipedalUI__yTop;
|
||||
AutoLilvNode pipedalUI__yBottom;
|
||||
AutoLilvNode pipedalUI__width;
|
||||
|
||||
+171
-15
@@ -225,7 +225,6 @@ static void CopyDirectory(const std::filesystem::path &source, const std::filesy
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void Storage::MaybeCopyDefaultPresets()
|
||||
{
|
||||
auto presetsDirectory = this->GetPresetsDirectory();
|
||||
@@ -239,13 +238,17 @@ void Storage::MaybeCopyDefaultPresets()
|
||||
}
|
||||
|
||||
static void removeFileNoThrow(const std::filesystem::path &path)
|
||||
{
|
||||
try {
|
||||
{
|
||||
try
|
||||
{
|
||||
fs::remove(path);
|
||||
} catch (const std::exception&)
|
||||
}
|
||||
catch (const std::exception &)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void Storage::UpgradeFactoryPresets()
|
||||
{
|
||||
auto presetsDirectory = this->GetPresetsDirectory();
|
||||
@@ -520,7 +523,6 @@ void Storage::SavePluginPresetIndex()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void Storage::SaveBankIndex()
|
||||
{
|
||||
pipedal::ofstream_synced os;
|
||||
@@ -653,17 +655,162 @@ void Storage::SaveCurrentPreset(const Pedalboard &pedalboard)
|
||||
item.preset(pedalboard);
|
||||
SaveCurrentBank();
|
||||
}
|
||||
int64_t Storage::SaveCurrentPresetAs(const Pedalboard &pedalboard, const std::string &name, int64_t saveAfterInstanceId)
|
||||
int64_t Storage::SaveCurrentPresetAs(const Pedalboard &pedalboard, int64_t bankInstanceId, const std::string &name, int64_t saveAfterInstanceId)
|
||||
{
|
||||
Pedalboard newPedalboard = pedalboard;
|
||||
newPedalboard.name(name);
|
||||
|
||||
int64_t newInstanceId = currentBank.addPreset(newPedalboard, saveAfterInstanceId);
|
||||
currentBank.selectedPreset(newInstanceId);
|
||||
SaveCurrentBank();
|
||||
return newInstanceId;
|
||||
if (bankInstanceId == this->bankIndex.selectedBank())
|
||||
{
|
||||
int64_t newInstanceId = currentBank.addPreset(newPedalboard, saveAfterInstanceId);
|
||||
currentBank.selectedPreset(newInstanceId);
|
||||
SaveCurrentBank();
|
||||
return newInstanceId;
|
||||
}
|
||||
else
|
||||
{
|
||||
auto indexEntry = this->bankIndex.getBankIndexEntry(bankInstanceId);
|
||||
|
||||
try
|
||||
{
|
||||
BankFile bankFile;
|
||||
LoadBankFile(indexEntry.name(), &(bankFile));
|
||||
int64_t newInstanceId = bankFile.addPreset(newPedalboard, -1);
|
||||
SaveBankFile(indexEntry.name(), bankFile);
|
||||
return -1;
|
||||
}
|
||||
catch (const std::exception &e)
|
||||
{
|
||||
throw std::logic_error(SS("Bank file corrupted. " << e.what() << "(" << GetBankFileName(indexEntry.name()) << ")"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static std::string stripNumericSuffix(const std::string &name) {
|
||||
// remove (digit*) from the end of the string.
|
||||
// Find the last '(' character
|
||||
size_t pos = name.find_last_of('(');
|
||||
if (pos == std::string::npos) {
|
||||
return name;
|
||||
}
|
||||
|
||||
// Check if everything between '(' and ')' is digits
|
||||
size_t len = name.length()-1;
|
||||
if (name[len] != ')')
|
||||
{
|
||||
return name;
|
||||
}
|
||||
bool allDigits = true;
|
||||
for (size_t i = pos + 1; i < len - 1; ++i) {
|
||||
if (name[i] < '0' || name[i] > '9') {
|
||||
allDigits = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!allDigits) {
|
||||
return name;
|
||||
}
|
||||
|
||||
// Remove trailing spaces before the '('
|
||||
while (pos > 0 && name[pos - 1] == ' ') {
|
||||
--pos;
|
||||
}
|
||||
|
||||
return name.substr(0, pos);
|
||||
}
|
||||
static std::string makeUniqueName(const std::string &name, const std::set<std::string>&existingNames)
|
||||
{
|
||||
if (!existingNames.contains(name))
|
||||
{
|
||||
return name;
|
||||
}
|
||||
std::string baseName = stripNumericSuffix(name);
|
||||
|
||||
size_t i = 2;
|
||||
while (true) {
|
||||
std::string newName = SS(baseName << " (" << i << ")");
|
||||
if (!existingNames.contains(newName)) {
|
||||
return newName;
|
||||
}
|
||||
++i;
|
||||
}
|
||||
}
|
||||
int64_t Storage::ImportPresetsFromBank(int64_t bankInstanceId, const std::vector<int64_t> &presets)
|
||||
{
|
||||
if (bankIndex.selectedBank() == bankInstanceId) {
|
||||
throw std::runtime_error("Can't import to self.");
|
||||
}
|
||||
std::set<int64_t> presetsSet { presets.begin(), presets.end()};
|
||||
auto indexEntry = this->bankIndex.getBankIndexEntry(bankInstanceId);
|
||||
|
||||
std::set<std::string> existingNames;
|
||||
|
||||
for (auto&preset: this->currentBank.presets()) {
|
||||
existingNames.insert(preset->preset().name());
|
||||
}
|
||||
BankFile bankFile;
|
||||
LoadBankFile(indexEntry.name(),&bankFile);
|
||||
int64_t lastPresetId = -1;
|
||||
for (auto &presetEntry: bankFile.presets()) {
|
||||
if (presetsSet.contains(presetEntry->instanceId())) {
|
||||
std::string uniqueName = makeUniqueName(presetEntry->preset().name(),existingNames);
|
||||
existingNames.insert(uniqueName);
|
||||
Pedalboard t = presetEntry->preset();
|
||||
t.name(uniqueName);
|
||||
lastPresetId = this->currentBank.addPreset(t);
|
||||
}
|
||||
}
|
||||
SaveCurrentBank();
|
||||
return lastPresetId;
|
||||
}
|
||||
int64_t Storage::CopyPresetsToBank(int64_t bankInstanceId, const std::vector<int64_t> &presets)
|
||||
{
|
||||
if (bankIndex.selectedBank() == bankInstanceId) {
|
||||
throw std::runtime_error("Can't copy to self.");
|
||||
}
|
||||
|
||||
auto indexEntry = this->bankIndex.getBankIndexEntry(bankInstanceId);
|
||||
BankFile bankFile;
|
||||
LoadBankFile(indexEntry.name(),&bankFile);
|
||||
|
||||
std::set<int64_t> presetsSet { presets.begin(), presets.end()};
|
||||
|
||||
std::set<std::string> existingNames;
|
||||
|
||||
for (auto&preset: bankFile.presets()) {
|
||||
existingNames.insert(preset->preset().name());
|
||||
}
|
||||
for (auto &presetEntry: this->currentBank.presets()) {
|
||||
if (presetsSet.contains(presetEntry->instanceId())) {
|
||||
std::string uniqueName = makeUniqueName(presetEntry->preset().name(),existingNames);
|
||||
existingNames.insert(uniqueName);
|
||||
Pedalboard t = presetEntry->preset();
|
||||
t.name(uniqueName);
|
||||
bankFile.addPreset(t);
|
||||
}
|
||||
}
|
||||
SaveBankFile(indexEntry.name(),bankFile);
|
||||
return -1;
|
||||
}
|
||||
|
||||
std::vector<PresetIndexEntry> Storage::RequestBankPresets(int64_t bankInstanceId)
|
||||
{
|
||||
auto indexEntry = this->bankIndex.getBankIndexEntry(bankInstanceId);
|
||||
|
||||
std::vector<PresetIndexEntry> result;
|
||||
BankFile bankFile;
|
||||
LoadBankFile(indexEntry.name(),&bankFile);
|
||||
for (auto &preset: bankFile.presets()) {
|
||||
result.push_back(
|
||||
PresetIndexEntry(preset->instanceId(),preset->preset().name())
|
||||
);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
|
||||
void Storage::SetPresetIndex(const PresetIndex &presets)
|
||||
{
|
||||
// painful because we must move unique_ptrs.
|
||||
@@ -734,9 +881,12 @@ Pedalboard Storage::GetPreset(int64_t instanceId) const
|
||||
throw PiPedalException("Not found.");
|
||||
}
|
||||
|
||||
int64_t Storage::DeletePreset(int64_t presetId)
|
||||
int64_t Storage::DeletePresets(const std::vector<int64_t> &presetInstanceIds)
|
||||
{
|
||||
int64_t newSelection = currentBank.deletePreset(presetId);
|
||||
int64_t newSelection = currentBank.selectedPreset();
|
||||
for (auto presetId: presetInstanceIds) {
|
||||
newSelection = currentBank.deletePreset(presetId);
|
||||
}
|
||||
SaveCurrentBank();
|
||||
return newSelection;
|
||||
}
|
||||
@@ -826,25 +976,30 @@ int64_t Storage::CreateNewPreset()
|
||||
}
|
||||
}
|
||||
newPedalboard.name(name);
|
||||
return this->currentBank.addPreset(newPedalboard, -1);
|
||||
auto t = this->currentBank.addPreset(newPedalboard, -1);
|
||||
SaveCurrentBank();
|
||||
return t;
|
||||
}
|
||||
|
||||
int64_t Storage::CopyPreset(int64_t fromId, int64_t toId)
|
||||
{
|
||||
auto &fromItem = this->currentBank.getItem(fromId);
|
||||
int64_t result;
|
||||
if (toId == -1)
|
||||
{
|
||||
Pedalboard newPedalboard = fromItem.preset();
|
||||
std::string name = GetPresetCopyName(fromItem.preset().name());
|
||||
newPedalboard.name(name);
|
||||
return this->currentBank.addPreset(newPedalboard, fromId);
|
||||
result = this->currentBank.addPreset(newPedalboard, fromId);
|
||||
}
|
||||
else
|
||||
{
|
||||
auto &toItem = this->currentBank.getItem(toId);
|
||||
toItem.preset(fromItem.preset());
|
||||
return toId;
|
||||
result = toId;
|
||||
}
|
||||
SaveCurrentBank();
|
||||
return result;
|
||||
}
|
||||
|
||||
void Storage::SetJackChannelSelection(const JackChannelSelection &channelSelection)
|
||||
@@ -2861,6 +3016,7 @@ std::string Storage::FromAbstractPathJson(const std::string &pathJson)
|
||||
return v.to_string();
|
||||
}
|
||||
|
||||
|
||||
JSON_MAP_BEGIN(UserSettings)
|
||||
JSON_MAP_REFERENCE(UserSettings, governor)
|
||||
JSON_MAP_REFERENCE(UserSettings, showStatusMonitor)
|
||||
|
||||
+7
-2
@@ -149,7 +149,7 @@ public:
|
||||
int64_t GetBankByMidiBankNumber(uint8_t bankNumber);
|
||||
const Pedalboard& GetCurrentPreset();
|
||||
void SaveCurrentPreset(const Pedalboard&pedalboard);
|
||||
int64_t SaveCurrentPresetAs(const Pedalboard&pedalboard, const std::string&namne,int64_t saveAfterInstanceId = -1);
|
||||
int64_t SaveCurrentPresetAs(const Pedalboard&pedalboard, int64_t bankInstanceId,const std::string&namne,int64_t saveAfterInstanceId = -1);
|
||||
int64_t GetCurrentPresetId() const;
|
||||
|
||||
void GetPresetIndex(PresetIndex*pResult);
|
||||
@@ -162,7 +162,7 @@ public:
|
||||
|
||||
|
||||
bool LoadPreset(int64_t presetId);
|
||||
int64_t DeletePreset(int64_t presetId);
|
||||
int64_t DeletePresets(const std::vector<int64_t>& presetInstanceIds);
|
||||
bool RenamePreset(int64_t presetId, const std::string&name);
|
||||
int64_t CopyPreset(int64_t fromId, int64_t toId = -1);
|
||||
int64_t CreateNewPreset();
|
||||
@@ -268,6 +268,11 @@ public:
|
||||
void LoadTone3000Auth();
|
||||
void SetTone3000Auth(const std::string&apiKey);
|
||||
std::string GetTone3000Auth() const;
|
||||
|
||||
std::vector<PresetIndexEntry> RequestBankPresets(int64_t bankInstanceId);
|
||||
int64_t ImportPresetsFromBank(int64_t bankInstanceId, const std::vector<int64_t> &presets);
|
||||
int64_t CopyPresetsToBank(int64_t bankInstanceId, const std::vector<int64_t> &presets);
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
||||
+129
-15
@@ -43,10 +43,11 @@ std::string psystem(const std::string &command)
|
||||
{
|
||||
throw std::runtime_error("popen() failed!");
|
||||
}
|
||||
Finally ff{[pipe]() {
|
||||
pclose(pipe);
|
||||
}};
|
||||
while (fgets(buffer.data(), buffer.size()-1, pipe) != nullptr)
|
||||
Finally ff{[pipe]()
|
||||
{
|
||||
pclose(pipe);
|
||||
}};
|
||||
while (fgets(buffer.data(), buffer.size() - 1, pipe) != nullptr)
|
||||
{
|
||||
result += buffer.data();
|
||||
}
|
||||
@@ -68,7 +69,8 @@ void SignPackage()
|
||||
{
|
||||
packagePath = SS("build/pipedal_" << PROJECT_VER << "_amd64.deb");
|
||||
packagePath = fs::absolute(packagePath);
|
||||
if (!fs::exists(packagePath)) {
|
||||
if (!fs::exists(packagePath))
|
||||
{
|
||||
packagePath = SS("build/pipedal_" << PROJECT_VER << "_*.deb");
|
||||
throw std::runtime_error(SS("File does not exist: " << packagePath));
|
||||
}
|
||||
@@ -82,8 +84,8 @@ void SignPackage()
|
||||
|
||||
std::string signCmd =
|
||||
SS("/usr/bin/gpg --pinentry-mode loopback --yes --default-key " << UPDATE_GPG_FINGERPRINT2
|
||||
<< " --armor --output " << packagePath << ".asc"
|
||||
<< " --detach-sign " << packagePath.c_str());
|
||||
<< " --armor --output " << packagePath << ".asc"
|
||||
<< " --detach-sign " << packagePath.c_str());
|
||||
int result = system(signCmd.c_str());
|
||||
if (result != EXIT_SUCCESS)
|
||||
{
|
||||
@@ -226,6 +228,8 @@ public:
|
||||
bool draft = false, prerelease = false;
|
||||
std::vector<DownloadCountAsset> assets;
|
||||
std::string published_at;
|
||||
double release_downloads = 0;
|
||||
double average_daily_downloads = 0;
|
||||
};
|
||||
|
||||
static std::string timePointToISO8601(const std::chrono::system_clock::time_point &tp)
|
||||
@@ -233,11 +237,33 @@ static std::string timePointToISO8601(const std::chrono::system_clock::time_poin
|
||||
auto tt = std::chrono::system_clock::to_time_t(tp);
|
||||
std::tm tm = *std::gmtime(&tt);
|
||||
std::stringstream ss;
|
||||
ss << std::put_time(&tm,"%Y-%m-%dT%H:%M:%S") << "Z";
|
||||
ss << std::put_time(&tm, "%Y-%m-%dT%H:%M:%S") << "Z";
|
||||
return ss.str();
|
||||
|
||||
}
|
||||
|
||||
static std::chrono::system_clock::time_point iso8601ToTimePoint(const std::string&date)
|
||||
{
|
||||
std::istringstream ss(date);
|
||||
std::tm tm = {};
|
||||
char zulu;
|
||||
ss >> std::get_time(&tm, "%Y-%m-%dT%H:%M:%S") >> zulu;
|
||||
if (ss.fail()) {
|
||||
throw std::runtime_error("Failed to parse ISO8601 date: " + date);
|
||||
}
|
||||
return std::chrono::system_clock::from_time_t(std::mktime(&tm));
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
static bool endsWith(const std::string &value, const std::string &end)
|
||||
{
|
||||
if (end.length() > value.length())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return value.compare(value.length() - end.length(), end.length(), end) == 0;
|
||||
}
|
||||
void GetDownloadCounts(bool gplotDownloads)
|
||||
{
|
||||
// error handling and temporary file use is different enough that it justifies
|
||||
@@ -321,15 +347,65 @@ void GetDownloadCounts(bool gplotDownloads)
|
||||
return left.published_at < right.published_at; // date ascending
|
||||
});
|
||||
|
||||
for (size_t i = 0; i < releases.size()-1; ++i)
|
||||
uint64_t cumulativeCount = 0;
|
||||
|
||||
for (auto i = releases.begin(); i != releases.end(); /**/)
|
||||
{
|
||||
releases[i].published_at = releases[i+1].published_at;
|
||||
if (i->name.find("Experimental") != string::npos
|
||||
|| i->name.find("Retracted") != string::npos
|
||||
|| i->name.find("Testing") != string::npos
|
||||
)
|
||||
{
|
||||
i = releases.erase(i);
|
||||
}
|
||||
else
|
||||
{
|
||||
++i;
|
||||
}
|
||||
}
|
||||
for (auto &release : releases)
|
||||
{
|
||||
size_t releaseDownloads = 0;
|
||||
for (const auto &asset : release.assets)
|
||||
{
|
||||
if (asset.name.ends_with(".deb"))
|
||||
{
|
||||
releaseDownloads += asset.downloads;
|
||||
}
|
||||
}
|
||||
release.release_downloads = releaseDownloads;
|
||||
}
|
||||
|
||||
|
||||
|
||||
for (size_t i = 0; i < releases.size(); ++i)
|
||||
{
|
||||
std::chrono::system_clock::time_point nextTime;
|
||||
if (i == releases.size()-1)
|
||||
{
|
||||
nextTime = std::chrono::system_clock::now();
|
||||
} else {
|
||||
nextTime = iso8601ToTimePoint(releases.at(i+1).published_at);
|
||||
}
|
||||
auto thisTime = iso8601ToTimePoint(releases.at(i).published_at);
|
||||
|
||||
auto seconds = std::chrono::duration_cast<std::chrono::seconds>(nextTime-thisTime).count();
|
||||
auto days = seconds/(60.0*60*24);
|
||||
|
||||
|
||||
releases[i].average_daily_downloads = releases[i].release_downloads/
|
||||
(days);
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < releases.size() - 1; ++i)
|
||||
{
|
||||
releases[i].published_at = releases[i + 1].published_at;
|
||||
}
|
||||
if (releases.size() >= 1)
|
||||
{
|
||||
releases[releases.size()-1].published_at = timePointToISO8601(std::chrono::system_clock::now());
|
||||
releases[releases.size() - 1].published_at = timePointToISO8601(std::chrono::system_clock::now());
|
||||
}
|
||||
uint64_t cumulativeCount = 0;
|
||||
|
||||
for (const auto &release : releases)
|
||||
{
|
||||
for (const auto &asset : release.assets)
|
||||
@@ -339,7 +415,10 @@ void GetDownloadCounts(bool gplotDownloads)
|
||||
cumulativeCount += asset.downloads;
|
||||
}
|
||||
}
|
||||
cout << release.published_at << " " << cumulativeCount << endl;
|
||||
cout << release.published_at << " " << cumulativeCount
|
||||
<< " " << release.average_daily_downloads
|
||||
<< " " << "\"" << release.name << "\""
|
||||
<< endl;
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -351,14 +430,49 @@ void GetDownloadCounts(bool gplotDownloads)
|
||||
{
|
||||
return left.published_at > right.published_at; // latest date first.
|
||||
});
|
||||
|
||||
size_t total = 0;
|
||||
size_t amdTotal = 0;
|
||||
size_t amdAscTotal = 0;
|
||||
size_t aarch64Total = 0;
|
||||
size_t aarch64AscTotal = 0;
|
||||
size_t otherTotal = 0;
|
||||
|
||||
for (const auto &release : releases)
|
||||
{
|
||||
cout << release.name << endl;
|
||||
cout << release.name << " " << release.published_at << endl;
|
||||
for (const auto &asset : release.assets)
|
||||
{
|
||||
total += asset.downloads;
|
||||
if (endsWith(asset.name, "arm64.deb"))
|
||||
{
|
||||
aarch64Total += asset.downloads;
|
||||
}
|
||||
else if (endsWith(asset.name, "amd64.deb"))
|
||||
{
|
||||
amdTotal += asset.downloads;
|
||||
}
|
||||
else if (endsWith(asset.name, "arm64.deb.asc"))
|
||||
{
|
||||
aarch64AscTotal += asset.downloads;
|
||||
}
|
||||
else if (endsWith(asset.name, "amd64.deb.asc"))
|
||||
{
|
||||
amdAscTotal += asset.downloads;
|
||||
}
|
||||
else
|
||||
{
|
||||
otherTotal += asset.downloads;
|
||||
}
|
||||
cout << " " << asset.name << ": " << asset.downloads << endl;
|
||||
}
|
||||
}
|
||||
cout << endl
|
||||
<< "Total: " << total << endl;
|
||||
|
||||
cout << "arm64:" << aarch64Total << " asc: " << aarch64AscTotal << endl;
|
||||
cout << "amd64:" << amdTotal << " asc: " << amdAscTotal << endl;
|
||||
cout << "other:" << otherTotal << endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,4 @@
|
||||
|
||||
double select on double-click/ok in file dialog.
|
||||
sudo apt install libbz2-dev
|
||||
|
||||
README, license files in file browser.
|
||||
|
||||
|
||||
|
||||
ls
|
||||
Carla Project icons.
|
||||
|
||||
Executable
+4
@@ -0,0 +1,4 @@
|
||||
#!/bin/sudo /bin/bash
|
||||
rm -rf /etc/pipedal/react
|
||||
mkdir -p /etc/pipedal/react
|
||||
cp -r dist/* /etc/pipedal/react/
|
||||
@@ -28,6 +28,7 @@ import { isDarkMode } from './DarkMode';
|
||||
import Tone3000AuthComplete from './Tone3000AuthComplete';
|
||||
import FontTest from './FontTest';
|
||||
|
||||
import IconTest from './IconTest';
|
||||
|
||||
declare module '@mui/material/styles' {
|
||||
interface Theme {
|
||||
@@ -38,6 +39,12 @@ declare module '@mui/material/styles' {
|
||||
mainBackground?: React.CSSProperties['color'];
|
||||
toolbarColor?: React.CSSProperties['color'];
|
||||
}
|
||||
interface Palette {
|
||||
actionBar: Palette['primary'];
|
||||
}
|
||||
interface PaletteOptions {
|
||||
actionBar: PaletteOptions['primary'];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -142,7 +149,12 @@ const theme = createTheme(
|
||||
},
|
||||
secondary: {
|
||||
main: "#FF6060"
|
||||
},
|
||||
actionBar: {
|
||||
main: '#130b22ff',
|
||||
contrastText: '#FFFFFF'
|
||||
}
|
||||
|
||||
},
|
||||
mainBackground: "#222",
|
||||
toolbarColor: '#222'
|
||||
@@ -208,8 +220,14 @@ const theme = createTheme(
|
||||
},
|
||||
secondary: {
|
||||
main: "#FF6060"
|
||||
},
|
||||
actionBar: {
|
||||
main: '#130b22ff',
|
||||
contrastText: '#FFFFFF'
|
||||
}
|
||||
|
||||
|
||||
|
||||
},
|
||||
mainBackground: "#FFFFFF",
|
||||
toolbarColor: '#FFFFFF'
|
||||
@@ -235,6 +253,11 @@ function isFontTest() {
|
||||
let param = url.searchParams.get("fontTest");
|
||||
return (param !== null)
|
||||
}
|
||||
function isIconTest() {
|
||||
let url = new URL(window.location.href);
|
||||
let param = url.searchParams.get("iconTest");
|
||||
return (param !== null)
|
||||
}
|
||||
|
||||
const App = (class extends React.Component {
|
||||
// Before the component mounts, we initialise our state
|
||||
@@ -258,6 +281,7 @@ const App = (class extends React.Component {
|
||||
{
|
||||
isTone3000Auth() && (<Tone3000AuthComplete />)
|
||||
|| isFontTest() && (<FontTest />)
|
||||
|| isIconTest() && (<IconTest />)
|
||||
|| (<AppThemed />)
|
||||
}
|
||||
</ThemeProvider>
|
||||
|
||||
@@ -0,0 +1,180 @@
|
||||
// Copyright (c) Robin E. R. Davies
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
// this software and associated documentation files (the "Software"), to deal in
|
||||
// the Software without restriction, including without limitation the rights to
|
||||
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
// the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
// subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
import Toolbar from "@mui/material/Toolbar";
|
||||
import IconButtonEx from "./IconButtonEx";
|
||||
import ArrowBackIcon from "@mui/icons-material/ArrowBack";
|
||||
import Typography from "@mui/material/Typography";
|
||||
import Select from '@mui/material/Select';
|
||||
import Button from '@mui/material/Button';
|
||||
import InputLabel from '@mui/material/InputLabel';
|
||||
import DialogEx from './DialogEx';
|
||||
import DialogTitle from '@mui/material/DialogTitle';
|
||||
import MenuItem from '@mui/material/MenuItem';
|
||||
import DialogActions from '@mui/material/DialogActions';
|
||||
import DialogContent from '@mui/material/DialogContent';
|
||||
import ResizeResponsiveComponent from './ResizeResponsiveComponent';
|
||||
import { PiPedalModel } from './PiPedalModel';
|
||||
import { BankIndexEntry } from './Banks';
|
||||
|
||||
|
||||
export interface CopyPresetsToBankDialogProps {
|
||||
open: boolean,
|
||||
onOk: (bankInstanceId: number) => void,
|
||||
onClose: () => void
|
||||
};
|
||||
|
||||
export interface CopyPresetsToBankDialogState {
|
||||
selectedBank: number;
|
||||
banks: BankIndexEntry[];
|
||||
fullScreen: boolean;
|
||||
};
|
||||
|
||||
function setSavedDefaultBankSelection(bankInstanceId: number) {
|
||||
localStorage.setItem('pipedal_copyTo_defaultBankSelection', bankInstanceId.toString());
|
||||
}
|
||||
function getSavedDefaultBankSelection() {
|
||||
let defaultValue = localStorage.getItem('pipedal_copyTo_defaultBankSelection');
|
||||
if (defaultValue) {
|
||||
return parseInt(defaultValue);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
export default class CopyPresetsToBankDialog extends ResizeResponsiveComponent<CopyPresetsToBankDialogProps, CopyPresetsToBankDialogState> {
|
||||
|
||||
model: PiPedalModel;
|
||||
constructor(props: CopyPresetsToBankDialogProps) {
|
||||
super(props);
|
||||
this.model = PiPedalModel.getInstance();
|
||||
let banks = this.getBanks();
|
||||
let selectedBank = this.getDefaultBankSelection(banks);
|
||||
this.state = {
|
||||
banks: banks,
|
||||
selectedBank: selectedBank,
|
||||
fullScreen: false,
|
||||
|
||||
};
|
||||
}
|
||||
private getBanks() {
|
||||
let bankIndex = this.model.banks.get();
|
||||
let result: BankIndexEntry[] = [];
|
||||
for (let entry of bankIndex.entries) {
|
||||
if (entry.instanceId === bankIndex.selectedBank) continue;
|
||||
result.push(entry);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
private getDefaultBankSelection(banks: BankIndexEntry[]) {
|
||||
if (banks.length === 0) return -1;
|
||||
let defaultSelection = getSavedDefaultBankSelection();
|
||||
for (let bank of banks) {
|
||||
if (bank.instanceId === defaultSelection) return defaultSelection;
|
||||
}
|
||||
return banks[0].instanceId;
|
||||
}
|
||||
|
||||
|
||||
|
||||
componentDidMount() {
|
||||
super.componentDidMount();
|
||||
}
|
||||
componentWillUnmount() {
|
||||
super.componentWillUnmount();
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
}
|
||||
|
||||
handleBankChanged(bankId: number) {
|
||||
setSavedDefaultBankSelection(bankId);
|
||||
this.setState({ selectedBank: bankId });
|
||||
}
|
||||
|
||||
handleOk() {
|
||||
this.props.onOk(this.state.selectedBank);
|
||||
}
|
||||
|
||||
render() {
|
||||
let props = this.props;
|
||||
let { open, onClose } = props;
|
||||
|
||||
const handleClose = () => {
|
||||
onClose();
|
||||
};
|
||||
return (
|
||||
<DialogEx tag="importPreset" open={open} fullWidth maxWidth="xs" onClose={handleClose} aria-labelledby="Rename-dialog-title"
|
||||
fullScreen={this.state.fullScreen}
|
||||
style={{ userSelect: "none" }}
|
||||
onEnterKey={() => { }}
|
||||
>
|
||||
<div style={{ display: "flex", flexFlow: "column nowrap", height: this.state.fullScreen ? "100vh" : undefined }}>
|
||||
{!this.state.fullScreen && (
|
||||
<DialogTitle style={{ paddingTop: 8, paddingBottom: 0 }}>
|
||||
<Toolbar style={{ padding: 0 }}>
|
||||
<IconButtonEx
|
||||
tooltip="Close"
|
||||
edge="start"
|
||||
color="inherit"
|
||||
aria-label="cancel"
|
||||
style={{ opacity: 0.6 }}
|
||||
onClick={() => { onClose(); }}
|
||||
>
|
||||
<ArrowBackIcon style={{ width: 24, height: 24 }} />
|
||||
</IconButtonEx>
|
||||
|
||||
<Typography noWrap component="div" sx={{ flexGrow: 1 }}>
|
||||
Copy Presets to Bank
|
||||
</Typography>
|
||||
|
||||
</Toolbar>
|
||||
</DialogTitle>
|
||||
)}
|
||||
|
||||
<DialogContent style={{ flex: "0 0 auto", paddingTop: 8, paddingBottom: 8, }}>
|
||||
<InputLabel style={{ fontSize: "0.75rem", fontWeight: 400, marginTop: 0 }}>Bank</InputLabel>
|
||||
|
||||
<Select variant="standard" fullWidth value={this.state.selectedBank} style={{ marginBottom: 8 }}
|
||||
onChange={(e) => { this.handleBankChanged(e.target.value as number); }}>
|
||||
{this.state.banks.map((bankEntry) => {
|
||||
return (
|
||||
<MenuItem key={bankEntry.instanceId} value={bankEntry.instanceId}
|
||||
selected={bankEntry.instanceId === this.state.selectedBank}
|
||||
>
|
||||
{bankEntry.name}
|
||||
</MenuItem>
|
||||
);
|
||||
})}
|
||||
</Select>
|
||||
</DialogContent>
|
||||
<DialogActions style={{ flex: "0 0 auto" }}>
|
||||
<Button onClick={handleClose} variant="dialogSecondary" >
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => { this.handleOk(); }} variant="dialogPrimary"
|
||||
disabled={false}
|
||||
>
|
||||
OK
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</div>
|
||||
</DialogEx>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -27,7 +27,7 @@ import { PiPedalStateError } from './PiPedalError';
|
||||
import { css } from '@emotion/react';
|
||||
|
||||
|
||||
const SELECT_SCALE = 1.5;
|
||||
const SELECT_SCALE = 1.2;
|
||||
|
||||
const AUTOSCROLL_TICK_DELAY = 30;
|
||||
const AUTOSCROLL_THRESHOLD = 48;
|
||||
@@ -58,6 +58,7 @@ export interface DraggableProps extends WithStyles<typeof styles> {
|
||||
|
||||
children?: ReactNode | ReactNode[];
|
||||
draggable?: boolean;
|
||||
style?: React.CSSProperties;
|
||||
|
||||
|
||||
}
|
||||
@@ -149,6 +150,7 @@ const Draggable =
|
||||
startClientY: number = 0;
|
||||
dragStarted: boolean = false;
|
||||
savedIndex: string = "";
|
||||
savedOpacity: string = "";
|
||||
lastPointerDown: number = 0;
|
||||
originalBounds?: DOMRect;
|
||||
captureElement?: HTMLDivElement;
|
||||
@@ -244,6 +246,8 @@ const Draggable =
|
||||
this.pointerId = e.pointerId;
|
||||
this.pointerType = e.pointerType;
|
||||
this.savedIndex = e.currentTarget.style.zIndex;
|
||||
this.savedOpacity = e.currentTarget.style.opacity;
|
||||
e.currentTarget.style.opacity = "0.9";
|
||||
if (this.pointerType !== "touch") {
|
||||
this.dragTarget.style.transform = "scale(" + SELECT_SCALE + ")";
|
||||
this.dragTarget.style.zIndex = "3";
|
||||
@@ -254,6 +258,7 @@ const Draggable =
|
||||
clearDragTransform(element: HTMLDivElement): void {
|
||||
element.style.transform = "";
|
||||
element.style.zIndex = this.savedIndex;
|
||||
element.style.opacity = this.savedOpacity;
|
||||
}
|
||||
|
||||
onPointerCancel(e: PointerEvent<HTMLDivElement>) {
|
||||
@@ -486,7 +491,7 @@ const Draggable =
|
||||
render() {
|
||||
const classes = withStyles.getClasses(this.props);
|
||||
return (
|
||||
<div className={classes.frame} style={{ transform: "" }}
|
||||
<div className={classes.frame} style={{ transform: "", ...this.props.style }}
|
||||
onPointerDown={this.onPointerDown}
|
||||
onPointerMove={this.onPointerMove}
|
||||
onPointerCancel={this.onPointerCancel}
|
||||
|
||||
@@ -82,7 +82,7 @@ export interface DraggableGridProps
|
||||
|
||||
defaultSelectedIndex?: number;
|
||||
|
||||
onLongPress?: (itemIndex: number) => void;
|
||||
onLongPress?: (event: PointerEvent<HTMLDivElement>, itemIndex: number) => void;
|
||||
|
||||
canDrag?: boolean;
|
||||
|
||||
@@ -209,7 +209,7 @@ const DraggableGrid =
|
||||
}
|
||||
|
||||
bringSelectedItemIntoView() {
|
||||
if (this.state.indexToSelect && this.state.indexToSelect >= 0)
|
||||
if (this.state.indexToSelect !== undefined && this.state.indexToSelect >= 0)
|
||||
{
|
||||
let grid = this.refGrid.current;
|
||||
if (grid)
|
||||
@@ -295,6 +295,7 @@ const DraggableGrid =
|
||||
startClientX: number = 0;
|
||||
startClientY: number = 0;
|
||||
dragStarted: boolean = false;
|
||||
dragThresholdWasExceeded: boolean = false;
|
||||
savedIndex: string = "";
|
||||
savedBackground: string = "";
|
||||
lastPointerDown: number = 0;
|
||||
@@ -529,6 +530,11 @@ const DraggableGrid =
|
||||
if (this.startIndex !== this.currentIndex) {
|
||||
this.props.moveElement(this.startIndex, this.currentIndex);
|
||||
}
|
||||
if (!this.dragThresholdWasExceeded) {
|
||||
if (this.props.onLongPress) {
|
||||
this.props.onLongPress(e,this.startIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
this.cancelDrag();
|
||||
}
|
||||
@@ -693,6 +699,7 @@ const DraggableGrid =
|
||||
///window.addEventListener("touchstart",this.handleTouchStart, {passive: false});
|
||||
|
||||
this.dragStarted = false;
|
||||
this.dragThresholdWasExceeded = false;
|
||||
this.startX = e.clientX;
|
||||
this.startY = e.clientY;
|
||||
this.startClientX = e.clientX;
|
||||
@@ -756,8 +763,11 @@ const DraggableGrid =
|
||||
|
||||
handlePointerMove(e: PointerEvent<HTMLDivElement>): boolean {
|
||||
if (this.isCapturedPointer(e)) {
|
||||
if (!this.dragStarted && this.dragThresholdExceeded(e)) {
|
||||
this.startDrag();
|
||||
if (this.dragThresholdExceeded(e)) {
|
||||
if (!this.dragStarted) {
|
||||
this.startDrag();
|
||||
}
|
||||
this.dragThresholdWasExceeded = true;
|
||||
}
|
||||
if (this.dragStarted) {
|
||||
this.lastX = e.clientX;
|
||||
|
||||
@@ -0,0 +1,155 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) Robin E. R. Davies
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to
|
||||
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
* of the Software, and to permit persons to whom the Software is furnished to do
|
||||
* so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
import React, { useState } from 'react';
|
||||
import ButtonBase from '@mui/material/ButtonBase';
|
||||
import Menu from '@mui/material/Menu';
|
||||
import MenuItem from '@mui/material/MenuItem';
|
||||
import { useTheme } from '@mui/material/styles';
|
||||
import PluginIcon, { getIconColorSelections, getIconColor, IconColorSelect } from './PluginIcon';
|
||||
import ArrowDropDownIcon from '@mui/icons-material/ArrowDropDown';
|
||||
import { PluginType } from './Lv2Plugin';
|
||||
|
||||
|
||||
export enum DropdownAlignment {
|
||||
SE, SW
|
||||
}
|
||||
|
||||
interface ColorListDropdownButtonProps {
|
||||
colorKey: string;
|
||||
colorList?: IconColorSelect[];
|
||||
pluginType?: PluginType;
|
||||
|
||||
onColorChange: (selection: IconColorSelect) => void;
|
||||
dropdownAlignment: DropdownAlignment;
|
||||
}
|
||||
|
||||
const IconColorDropdownButton: React.FC<ColorListDropdownButtonProps> = (props: ColorListDropdownButtonProps) => {
|
||||
let { colorList, colorKey, onColorChange, dropdownAlignment } = props;
|
||||
if (!colorList) colorList = getIconColorSelections();
|
||||
const theme = useTheme();
|
||||
|
||||
const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null);
|
||||
|
||||
let selectedColor = getIconColor(colorKey);
|
||||
if (selectedColor === undefined) {
|
||||
selectedColor = theme.palette.text.secondary;
|
||||
}
|
||||
|
||||
const handleClick = (event: React.MouseEvent<HTMLButtonElement>) => {
|
||||
setAnchorEl(event.currentTarget);
|
||||
};
|
||||
|
||||
const handleClose = () => {
|
||||
setAnchorEl(null);
|
||||
};
|
||||
|
||||
const handleColorSelect = (selection: IconColorSelect) => {
|
||||
handleClose();
|
||||
onColorChange(selection);
|
||||
};
|
||||
return (
|
||||
<div>
|
||||
<ButtonBase
|
||||
onClick={handleClick}
|
||||
style={{
|
||||
height: 48, padding: 12, borderRadius: 8, margin: 4
|
||||
}}
|
||||
>
|
||||
<div style={{ display: "flex", flexFlow: "row nowrap", alignItems: "center", gap: 0 }} >
|
||||
{props.pluginType ? (
|
||||
<div style={{ margin: 4 }}>
|
||||
<PluginIcon
|
||||
pluginType={props.pluginType}
|
||||
color={selectedColor}
|
||||
size={24}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<div style={{
|
||||
width: 24, height: 24,
|
||||
background: selectedColor,
|
||||
borderRadius: 6,
|
||||
borderColor: theme.palette.text.secondary,
|
||||
borderWidth: 1,
|
||||
borderStyle: "solid"
|
||||
}} />
|
||||
|
||||
)}
|
||||
<ArrowDropDownIcon />
|
||||
</div>
|
||||
|
||||
</ButtonBase>
|
||||
<Menu
|
||||
anchorEl={anchorEl}
|
||||
open={anchorEl !== null}
|
||||
onClose={handleClose}
|
||||
anchorOrigin={{
|
||||
vertical: 'bottom',
|
||||
horizontal: (dropdownAlignment === DropdownAlignment.SW ? 'right' : 'left')
|
||||
}}
|
||||
transformOrigin={{
|
||||
vertical: 'top',
|
||||
horizontal: (dropdownAlignment === DropdownAlignment.SW ? 'right' : 'left'),
|
||||
}}
|
||||
>
|
||||
<div style={{ display: "flex", flexFlow: "row nowrap", columnGap: 8, padding: 8 }}>
|
||||
|
||||
<MenuItem key={"default"} onClick={() => handleColorSelect({ key: "default", value: undefined })}
|
||||
style={{
|
||||
width: 48, height: 48, borderRadius: 6, margin: 4,
|
||||
borderStyle: "solid",
|
||||
borderWidth: colorKey === "default" ? 2 : 0.25,
|
||||
borderColor: colorKey == "default" ?
|
||||
theme.palette.text.primary
|
||||
: theme.palette.text.secondary,
|
||||
backgroundColor: theme.palette.text.secondary
|
||||
}}
|
||||
></MenuItem>
|
||||
|
||||
<div style={{ display: "flex", flexFlow: "row wrap", width: 56 * 4 + 2 }}>
|
||||
{colorList.map((color) => {
|
||||
let selected = color.key === selectedColor;
|
||||
return (
|
||||
<MenuItem key={color.key} onClick={() => handleColorSelect(color)}
|
||||
style={{
|
||||
width: 48, height: 48, borderRadius: 6, margin: 4,
|
||||
borderStyle: "solid",
|
||||
borderWidth: selected ? 2 : 0.25,
|
||||
borderColor: color.key === selectedColor ?
|
||||
theme.palette.text.primary
|
||||
: theme.palette.text.secondary,
|
||||
backgroundColor: color.value
|
||||
}}
|
||||
></MenuItem>
|
||||
)
|
||||
}
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</Menu>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default IconColorDropdownButton;
|
||||
@@ -0,0 +1,73 @@
|
||||
|
||||
import { useState } from "react";
|
||||
import { PluginType } from "./Lv2Plugin";
|
||||
import PluginIcon, { getIconColorSelections } from './PluginIcon';
|
||||
import IconColorDropdownButton, {DropdownAlignment} from "./IconColorDropdownButton";
|
||||
|
||||
function IconTest() {
|
||||
const [selectedColor, setSelectedColor] = useState<string | undefined>(undefined);
|
||||
const [selectedKey, setSelectedKey] = useState<string>("");
|
||||
return (
|
||||
<>
|
||||
<div style={{ display: "flex", flexFlow: "row wrap", rowGap: 8, columnGap: 8, padding: 16, background: "#282828" }}>
|
||||
{
|
||||
getIconColorSelections().map((color) => {
|
||||
return (
|
||||
<div key={color.key} style={{ width: 48, height: 48, background: color.value, borderRadius: 4,opacity: 0.8 }}
|
||||
onClick={() => {
|
||||
setSelectedColor(color.value );
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
</div>
|
||||
<div style={{ display: "flex", flexFlow: "row wrap", rowGap: 8, columnGap: 8, padding: 16, background: "#E8E8E8" }}>
|
||||
{
|
||||
getIconColorSelections(false).map((color) => {
|
||||
return (
|
||||
<div key={color.key} style={{ width: 48, height: 48, background: color.value, borderRadius: 4,opacity: 0.8 }}
|
||||
onClick={() => { setSelectedColor(color.value ); }}
|
||||
/>
|
||||
);
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
</div>
|
||||
<div>
|
||||
<IconColorDropdownButton
|
||||
colorKey={selectedKey}
|
||||
onColorChange={(selection) =>
|
||||
{
|
||||
setSelectedColor(selection.value);
|
||||
setSelectedKey(selection.key);
|
||||
}
|
||||
}
|
||||
dropdownAlignment={DropdownAlignment.SE} />
|
||||
</div>
|
||||
<div style={{ display: "flex", flexFlow: "row wrap", rowGap: 8, columnGap: 8, padding: 16 }}>
|
||||
{
|
||||
Object.values(PluginType).map((pluginType) => (
|
||||
<div key={pluginType}>
|
||||
<PluginIcon pluginType={pluginType} size={32} opacity={0.8} color={selectedColor} />
|
||||
</div>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
<div style={{ display: "flex", flexFlow: "row wrap", rowGap: 8, columnGap: 8, padding: 16, background: "#E8E8E8" }}>
|
||||
{
|
||||
Object.values(PluginType).map((pluginType) => (
|
||||
<div key={pluginType}>
|
||||
<PluginIcon pluginType={pluginType} size={32} opacity={0.8} color={selectedColor} />
|
||||
</div>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default IconTest;
|
||||
@@ -0,0 +1,282 @@
|
||||
// Copyright (c) Robin E. R. Davies
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
// this software and associated documentation files (the "Software"), to deal in
|
||||
// the Software without restriction, including without limitation the rights to
|
||||
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
// the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
// subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
import Divider from '@mui/material/Divider';
|
||||
import Toolbar from "@mui/material/Toolbar";
|
||||
import IconButtonEx from "./IconButtonEx";
|
||||
import ArrowBackIcon from "@mui/icons-material/ArrowBack";
|
||||
import Typography from "@mui/material/Typography";
|
||||
import List from '@mui/material/List';
|
||||
import ListItem from '@mui/material/ListItem';
|
||||
import ListItemIcon from '@mui/material/ListItemIcon';
|
||||
import ListItemText from '@mui/material/ListItemText';
|
||||
import Checkbox from '@mui/material/Checkbox';
|
||||
import Select from '@mui/material/Select';
|
||||
import Button from '@mui/material/Button';
|
||||
import InputLabel from '@mui/material/InputLabel';
|
||||
import DialogEx from './DialogEx';
|
||||
import DialogTitle from '@mui/material/DialogTitle';
|
||||
import MenuItem from '@mui/material/MenuItem';
|
||||
import DialogActions from '@mui/material/DialogActions';
|
||||
import DialogContent from '@mui/material/DialogContent';
|
||||
import ResizeResponsiveComponent from './ResizeResponsiveComponent';
|
||||
import { PiPedalModel, PresetIndexEntry } from './PiPedalModel';
|
||||
import { BankIndexEntry } from './Banks';
|
||||
|
||||
|
||||
export interface ImportPresetFromBankDialogProps {
|
||||
open: boolean,
|
||||
onOk: (bankInstanceId: number, presets: number[]) => void,
|
||||
onClose: () => void
|
||||
};
|
||||
|
||||
export interface ImportPresetFromBankDialogState {
|
||||
selectedBank: number;
|
||||
banks: BankIndexEntry[];
|
||||
bankPresets: PresetIndexEntry[] | null;
|
||||
selectedPresets: number[];
|
||||
|
||||
fullScreen: boolean;
|
||||
};
|
||||
|
||||
function setSavedDefaultBankSelection(bankInstanceId: number) {
|
||||
localStorage.setItem('pipedal_defaultBankSelection', bankInstanceId.toString());
|
||||
}
|
||||
function getSavedDefaultBankSelection() {
|
||||
let defaultValue = localStorage.getItem('pipedal_defaultBankSelection');
|
||||
if (defaultValue) {
|
||||
return parseInt(defaultValue);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
export default class ImportPresetFromBankDialog extends ResizeResponsiveComponent<ImportPresetFromBankDialogProps, ImportPresetFromBankDialogState> {
|
||||
|
||||
model: PiPedalModel;
|
||||
constructor(props: ImportPresetFromBankDialogProps) {
|
||||
super(props);
|
||||
this.model = PiPedalModel.getInstance();
|
||||
let banks = this.getBanks();
|
||||
let selectedBank = this.getDefaultBankSelection(banks);
|
||||
this.state = {
|
||||
banks: banks,
|
||||
selectedBank: selectedBank,
|
||||
fullScreen: this.windowSize.height < 550,
|
||||
bankPresets: null,
|
||||
selectedPresets: []
|
||||
|
||||
|
||||
};
|
||||
}
|
||||
private getBanks() {
|
||||
let bankIndex = this.model.banks.get();
|
||||
let result: BankIndexEntry[] = [];
|
||||
for (let entry of bankIndex.entries) {
|
||||
if (entry.instanceId === bankIndex.selectedBank) continue;
|
||||
result.push(entry);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
private getDefaultBankSelection(banks: BankIndexEntry[]) {
|
||||
if (banks.length === 0) return -1;
|
||||
let defaultSelection = getSavedDefaultBankSelection();
|
||||
for (let bank of banks) {
|
||||
if (bank.instanceId === defaultSelection) return defaultSelection;
|
||||
}
|
||||
return banks[0].instanceId;
|
||||
}
|
||||
|
||||
private mounted: boolean = false;
|
||||
|
||||
|
||||
|
||||
onWindowSizeChanged(width: number, height: number): void {
|
||||
this.setState({ fullScreen: height < 550 })
|
||||
}
|
||||
|
||||
|
||||
requestPresets(bankInstanceId: number) {
|
||||
this.model.requestBankPresets(bankInstanceId).then((presets) => {
|
||||
if (!this.mounted) return;
|
||||
this.setState({ bankPresets: presets, selectedPresets: [] });
|
||||
}).catch((error) => {
|
||||
this.model.showAlert("Error requesting presets: " + error);
|
||||
});
|
||||
}
|
||||
componentDidMount() {
|
||||
super.componentDidMount();
|
||||
this.mounted = true;
|
||||
this.requestPresets(this.state.selectedBank);
|
||||
}
|
||||
componentWillUnmount() {
|
||||
super.componentWillUnmount();
|
||||
this.mounted = false;
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
}
|
||||
checkForIllegalCharacters(filename: string) {
|
||||
}
|
||||
|
||||
handleBankChanged(bankId: number) {
|
||||
setSavedDefaultBankSelection(bankId);
|
||||
this.setState({ selectedBank: bankId });
|
||||
this.requestPresets(bankId);
|
||||
}
|
||||
|
||||
handleOk() {
|
||||
if (this.state.selectedPresets.length === 0) return;
|
||||
this.props.onOk(this.state.selectedBank, this.state.selectedPresets);
|
||||
}
|
||||
|
||||
render() {
|
||||
let props = this.props;
|
||||
let { open, onClose } = props;
|
||||
|
||||
const handleClose = () => {
|
||||
onClose();
|
||||
};
|
||||
return (
|
||||
<DialogEx tag="importPreset" open={open} fullWidth maxWidth="xs" onClose={handleClose} aria-labelledby="Rename-dialog-title"
|
||||
fullScreen={this.state.fullScreen}
|
||||
style={{ userSelect: "none" }}
|
||||
onEnterKey={() => { }}
|
||||
>
|
||||
<div style={{ display: "flex", flexFlow: "column nowrap", height: this.state.fullScreen ? "100vh" : "80vh" }}>
|
||||
{!this.state.fullScreen ? (
|
||||
<>
|
||||
<DialogTitle style={{ paddingTop: 8, paddingBottom: 0 }}>
|
||||
<Toolbar style={{ padding: 0 }}>
|
||||
<IconButtonEx
|
||||
tooltip="Close"
|
||||
edge="start"
|
||||
color="inherit"
|
||||
aria-label="cancel"
|
||||
style={{ opacity: 0.6 }}
|
||||
onClick={() => { onClose(); }}
|
||||
>
|
||||
<ArrowBackIcon style={{ width: 24, height: 24 }} />
|
||||
</IconButtonEx>
|
||||
|
||||
<Typography noWrap component="div" sx={{ flexGrow: 1 }}>
|
||||
Import Presets from Bank
|
||||
</Typography>
|
||||
|
||||
</Toolbar>
|
||||
</DialogTitle>
|
||||
<DialogContent style={{ flex: "0 0 auto", paddingTop: 8, paddingBottom: 8, }}>
|
||||
<InputLabel style={{ fontSize: "0.75rem", fontWeight: 400, marginTop: 0 }}>Bank</InputLabel>
|
||||
|
||||
<Select variant="standard" fullWidth value={this.state.selectedBank} style={{ marginBottom: 8 }}
|
||||
onChange={(e) => { this.handleBankChanged(e.target.value as number); }}>
|
||||
{this.state.banks.map((bankEntry) => {
|
||||
return (
|
||||
<MenuItem key={bankEntry.instanceId} value={bankEntry.instanceId}
|
||||
selected={bankEntry.instanceId === this.state.selectedBank}
|
||||
>
|
||||
{bankEntry.name}
|
||||
</MenuItem>
|
||||
);
|
||||
})}
|
||||
</Select>
|
||||
</DialogContent>
|
||||
</>
|
||||
) : (
|
||||
<div style={{ display: "flex", flexFlow: "row nowrap", alignItems: "top", flex: "0 0 auto", paddingTop: 8, paddingBottom: 8, paddingLeft: 16, paddingRight: 16 }}>
|
||||
<IconButtonEx
|
||||
tooltip="Close"
|
||||
edge="start"
|
||||
color="inherit"
|
||||
aria-label="cancel"
|
||||
style={{ opacity: 0.6 }}
|
||||
onClick={() => { onClose(); }}
|
||||
>
|
||||
<ArrowBackIcon style={{ width: 24, height: 24 }} />
|
||||
</IconButtonEx>
|
||||
|
||||
<div style={{ marginLeft: 24, display: "flex", flex: "0 0 auto", flexFlow: "column nowrap", justifyContent: "start" }}>
|
||||
<InputLabel style={{ fontSize: "0.75rem", fontWeight: 400, marginTop: 0 }}>Bank</InputLabel>
|
||||
|
||||
<Select variant="standard" fullWidth value={this.state.selectedBank} style={{ marginBottom: 8 }}
|
||||
onChange={(e) => { this.handleBankChanged(e.target.value as number); }}>
|
||||
{this.state.banks.map((bankEntry) => {
|
||||
return (
|
||||
<MenuItem key={bankEntry.instanceId} value={bankEntry.instanceId}
|
||||
selected={bankEntry.instanceId === this.state.selectedBank}
|
||||
>
|
||||
{bankEntry.name}
|
||||
</MenuItem>
|
||||
);
|
||||
})}
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
)}
|
||||
|
||||
<Divider />
|
||||
<List sx={{ flex: "1 1 auto", width: '100%', bgColor: 'background.paper', overflowX: 'auto' }}>
|
||||
{this.state.bankPresets?.map((presetEntry) => {
|
||||
const labelId = `checkbox-list-label-${presetEntry.instanceId}`;
|
||||
|
||||
return (
|
||||
<ListItem key={presetEntry.instanceId}
|
||||
onClick={() => {
|
||||
let selectedPresets = this.state.selectedPresets;
|
||||
let index = selectedPresets.indexOf(presetEntry.instanceId);
|
||||
if (index === -1) {
|
||||
selectedPresets.push(presetEntry.instanceId);
|
||||
} else {
|
||||
selectedPresets.splice(index, 1);
|
||||
}
|
||||
this.setState({ selectedPresets: [...selectedPresets] });
|
||||
}}>
|
||||
<ListItemIcon style={{ marginLeft: 16 }}>
|
||||
<Checkbox
|
||||
edge="start"
|
||||
checked={this.state.selectedPresets.indexOf(presetEntry.instanceId) !== -1}
|
||||
tabIndex={-1}
|
||||
disableRipple
|
||||
inputProps={{ 'aria-labelledby': labelId }}
|
||||
/>
|
||||
</ListItemIcon>
|
||||
<ListItemText id={labelId} primary={presetEntry.name} />
|
||||
</ListItem>
|
||||
);
|
||||
})}
|
||||
</List>
|
||||
|
||||
<Divider />
|
||||
<DialogActions style={{ flex: "0 0 auto" }}>
|
||||
<Button onClick={handleClose} variant="dialogSecondary" >
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => { this.handleOk(); }} variant="dialogPrimary"
|
||||
disabled={this.state.selectedPresets.length === 0}
|
||||
style={{ opacity: this.state.selectedPresets.length === 0 ? 0.5 : 1.0 }}
|
||||
>
|
||||
OK
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</div>
|
||||
</DialogEx >
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -223,6 +223,7 @@ export class UiFrequencyPlot {
|
||||
this.xLeft = input.xLeft;
|
||||
this.xRight = input.xRight;
|
||||
this.xLog = input.xLog;
|
||||
this.yDb = input.yDb;
|
||||
this.yTop = input.yTop;
|
||||
this.yBottom = input.yBottom;
|
||||
this.width = input.width;
|
||||
@@ -242,6 +243,7 @@ export class UiFrequencyPlot {
|
||||
xLeft: number = -1;
|
||||
xRight: number = -1;
|
||||
xLog: boolean = true;
|
||||
yDb: boolean = true;
|
||||
yTop: number = -1;
|
||||
yBottom: number = -1;
|
||||
width: number = -1;
|
||||
|
||||
@@ -24,7 +24,7 @@ import { withStyles } from "tss-react/mui";
|
||||
import IconButtonEx from './IconButtonEx';
|
||||
import ButtonEx from './ButtonEx';
|
||||
import ButtonBase from '@mui/material/ButtonBase';
|
||||
import RenameDialog from './RenameDialog';
|
||||
import PluginIcon, { getIconColor } from './PluginIcon';
|
||||
|
||||
import ToolTipEx from './ToolTipEx';
|
||||
import { PiPedalModel, PiPedalModelFactory } from './PiPedalModel';
|
||||
@@ -64,9 +64,11 @@ import PipedalUiIcon from './svg/pp_ui.svg?react';
|
||||
import SnapshotDialog from './SnapshotDialog';
|
||||
import { css } from '@emotion/react';
|
||||
import { setDefaultModGuiPreference } from './ModGuiHost';
|
||||
import PluginNameDialog from './PluginNameDialog';
|
||||
|
||||
|
||||
const SPLIT_CONTROLBAR_THRESHHOLD = 750;
|
||||
const SHOW_ICON_THRESHHOLD = 475;
|
||||
const DISPLAY_AUTHOR_THRESHHOLD = 750;
|
||||
const DISPLAY_AUTHOR_SPLIT_THRESHOLD = 500;
|
||||
const HORIZONTAL_CONTROL_SCROLL_HEIGHT_BREAK = 500;
|
||||
@@ -128,6 +130,7 @@ interface MainState {
|
||||
splitControlBar: boolean;
|
||||
displayAuthor: boolean;
|
||||
horizontalScrollLayout: boolean;
|
||||
canDisplayPluginIcon: boolean;
|
||||
showMidiBindingsDialog: boolean;
|
||||
screenHeight: number;
|
||||
displayNameDialogOpen: boolean;
|
||||
@@ -147,6 +150,9 @@ export const MainPage =
|
||||
getSplitToolbar() {
|
||||
return this.windowSize.width < SPLIT_CONTROLBAR_THRESHHOLD && this.windowSize.height >= HORIZONTAL_CONTROL_SCROLL_HEIGHT_BREAK;
|
||||
}
|
||||
getShowPluginIcon() {
|
||||
return this.windowSize.width > SHOW_ICON_THRESHHOLD;
|
||||
}
|
||||
getDisplayAuthor() {
|
||||
if (this.getSplitToolbar()) {
|
||||
return this.windowSize.width >= DISPLAY_AUTHOR_SPLIT_THRESHOLD;
|
||||
@@ -173,6 +179,7 @@ export const MainPage =
|
||||
addMenuAnchorEl: null,
|
||||
splitControlBar: this.getSplitToolbar(),
|
||||
displayAuthor: this.getDisplayAuthor(),
|
||||
canDisplayPluginIcon: this.getShowPluginIcon(),
|
||||
horizontalScrollLayout: this.windowSize.height < HORIZONTAL_CONTROL_SCROLL_HEIGHT_BREAK,
|
||||
showMidiBindingsDialog: false,
|
||||
screenHeight: this.windowSize.height,
|
||||
@@ -304,6 +311,7 @@ export const MainPage =
|
||||
this.setState({
|
||||
splitControlBar: this.getSplitToolbar(),
|
||||
displayAuthor: this.getDisplayAuthor(),
|
||||
canDisplayPluginIcon: this.getShowPluginIcon(),
|
||||
horizontalScrollLayout: this.windowSize.height < HORIZONTAL_CONTROL_SCROLL_HEIGHT_BREAK,
|
||||
screenHeight: this.windowSize.height
|
||||
});
|
||||
@@ -415,6 +423,7 @@ export const MainPage =
|
||||
return pedalboardItem.uri;
|
||||
}
|
||||
titleBar(pedalboardItem: PedalboardItem | null, canShowModUi: boolean): React.ReactNode {
|
||||
let uiPlugin = pedalboardItem ? this.model.getUiPlugin(pedalboardItem?.uri) : null;
|
||||
let title = "";
|
||||
let author = "";
|
||||
let infoPluginUri = "";
|
||||
@@ -489,21 +498,45 @@ export const MainPage =
|
||||
this.handleEditPluginDisplayName();
|
||||
}}
|
||||
>
|
||||
<div style={{ flex: "0 1 auto" }}>
|
||||
<span>
|
||||
<span className={classes.title}>{title}</span>
|
||||
{this.state.displayAuthor && (
|
||||
<span className={classes.author}>{author}</span>
|
||||
)}
|
||||
</span>
|
||||
<div style={{ flex: "0 1 auto", display: "flex", flexFlow: "row nowrap", alignItems: "center", overflow: "hidden" }}>
|
||||
{this.state.canDisplayPluginIcon && uiPlugin && (
|
||||
<div style={{ flex: "0 0 auto", marginRight: 8 }}>
|
||||
<PluginIcon
|
||||
pluginType={uiPlugin.plugin_type}
|
||||
color={getIconColor(pedalboardItem?.iconColor ?? "")}
|
||||
size={24}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<div style={{ flex: "0 1 auto", minWidth: 0, overflow: "hidden", textOverflow: "ellipsis" }}>
|
||||
<span>
|
||||
<span className={classes.title}>{title}</span>
|
||||
{this.state.displayAuthor && (
|
||||
<span className={classes.author}>{author}</span>
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</ButtonBase>)
|
||||
: (
|
||||
<div style={{ flex: "0 1 auto", paddingLeft: 8, paddingRight: 8, minWidth: 0, overflow: "hidden", textOverflow: "ellipsis" }}>
|
||||
<span className={classes.title}>{title}</span>
|
||||
{this.state.displayAuthor && (
|
||||
<span className={classes.author}>{author}</span>
|
||||
<div style={{ flex: "0 1 auto", display: "flex", flexFlow: "row nowrap", alignItems: "center", overflow: "hidden" }}>
|
||||
{this.state.canDisplayPluginIcon && uiPlugin && (
|
||||
<div style={{ flex: "0 0 auto", marginRight: 8 }}>
|
||||
<PluginIcon
|
||||
pluginType={uiPlugin.plugin_type}
|
||||
color={getIconColor(pedalboardItem?.iconColor ?? "")}
|
||||
size={24}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<div style={{ flex: "0 1 auto", minWidth: 0, overflow: "hidden", textOverflow: "ellipsis" }}>
|
||||
<span>
|
||||
<span className={classes.title}>{title}</span>
|
||||
{this.state.displayAuthor && (
|
||||
<span className={classes.author}>{author}</span>
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
)}
|
||||
@@ -770,17 +803,14 @@ export const MainPage =
|
||||
<SnapshotDialog open={this.state.snapshotDialogOpen} onOk={() => this.onSnapshotDialogOk()} />
|
||||
)}
|
||||
{(this.state.displayNameDialogOpen) && (
|
||||
<RenameDialog open={this.state.displayNameDialogOpen}
|
||||
<PluginNameDialog open={this.state.displayNameDialogOpen}
|
||||
pedalboardItem={pedalboardItem}
|
||||
allowEmpty={true}
|
||||
defaultName={pedalboardItem?.title ?? ""}
|
||||
title="Plugin Display Name"
|
||||
acceptActionName="OK"
|
||||
onClose={() => this.setState({ displayNameDialogOpen: false })}
|
||||
onOk={(newName: string) => {
|
||||
onApply={(newName: string, color: string) => {
|
||||
if (pedalboardItem) {
|
||||
this.model.setPedalboardItemTitle(pedalboardItem.instanceId, newName);
|
||||
this.model.setPedalboardItemTitle(pedalboardItem.instanceId, newName, color);
|
||||
}
|
||||
this.setState({ displayNameDialogOpen: false });
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -80,6 +80,7 @@ export class PedalboardItem implements Deserializable<PedalboardItem> {
|
||||
this.lilvPresetUri = input.lilvPresetUri;
|
||||
this.pathProperties = input.pathProperties;
|
||||
this.useModUi = input.useModUi ?? false;
|
||||
this.iconColor = input.iconColor??"";
|
||||
|
||||
return this;
|
||||
}
|
||||
@@ -215,6 +216,7 @@ export class PedalboardItem implements Deserializable<PedalboardItem> {
|
||||
lilvPresetUri: string = "";
|
||||
pathProperties: {[Name: string]: string} = {};
|
||||
useModUi: boolean = false; // true if this item should use the mod-ui.
|
||||
iconColor: string = "";
|
||||
};
|
||||
|
||||
export class SnapshotValue {
|
||||
|
||||
+899
-856
File diff suppressed because it is too large
Load Diff
@@ -19,16 +19,17 @@
|
||||
|
||||
|
||||
import { Theme } from '@mui/material/styles';
|
||||
import BankIcon from './svg/ic_bank.svg?react';
|
||||
import Typography from '@mui/material/Typography';
|
||||
import SaveIconOutline from '@mui/icons-material/Save';
|
||||
import WithStyles from './WithStyles';
|
||||
import {createStyles} from './WithStyles';
|
||||
import { createStyles } from './WithStyles';
|
||||
|
||||
import { withStyles } from "tss-react/mui";
|
||||
import { PiPedalModel, PiPedalModelFactory, PresetIndex } from './PiPedalModel';
|
||||
import ResizeResponsiveComponent from './ResizeResponsiveComponent';
|
||||
import ArrowBackIcon from '@mui/icons-material/ArrowBack';
|
||||
import IconButtonEx from './IconButtonEx';
|
||||
import AppBar from '@mui/material/AppBar';
|
||||
import SnapshotPanel from './SnapshotPanel';
|
||||
import ArrowBackIosIcon from '@mui/icons-material/ArrowBackIos';
|
||||
import ArrowForwardIosIcon from '@mui/icons-material/ArrowForwardIos';
|
||||
@@ -41,13 +42,17 @@ import { BankIndex } from './Banks';
|
||||
import SnapshotEditor from './SnapshotEditor';
|
||||
import { Snapshot } from './Pedalboard';
|
||||
import JackStatusView from './JackStatusView';
|
||||
import {IDialogStackable, popDialogStack, pushDialogStack} from './DialogStack';
|
||||
import { IDialogStackable, popDialogStack, pushDialogStack } from './DialogStack';
|
||||
import { css } from '@emotion/react';
|
||||
|
||||
|
||||
const selectColor = isDarkMode() ? "#888" : "#FFFFFF";
|
||||
|
||||
const styles = (theme: Theme) => createStyles({
|
||||
listIcon: css({
|
||||
width: 24, height: 24,
|
||||
opacity: 0.6, fill: theme.palette.text.primary
|
||||
}),
|
||||
frame: css({
|
||||
position: "absolute", display: "flex", flexDirection: "column", flexWrap: "nowrap",
|
||||
justifyContent: "flex-start", left: "0px", top: "0px", bottom: "0px", right: "0px", overflow: "hidden"
|
||||
@@ -75,7 +80,8 @@ interface PerformanceViewProps extends WithStyles<typeof styles> {
|
||||
}
|
||||
|
||||
interface PerformanceViewState {
|
||||
wrapSelects: boolean
|
||||
wrapSelects: boolean;
|
||||
largeAppBar: boolean;
|
||||
presets: PresetIndex;
|
||||
banks: BankIndex;
|
||||
showSnapshotEditor: boolean;
|
||||
@@ -86,7 +92,7 @@ interface PerformanceViewState {
|
||||
|
||||
|
||||
export const PerformanceView =
|
||||
withStyles(
|
||||
withStyles(
|
||||
class extends ResizeResponsiveComponent<PerformanceViewProps, PerformanceViewState> implements IDialogStackable {
|
||||
model: PiPedalModel;
|
||||
|
||||
@@ -100,6 +106,7 @@ export const PerformanceView =
|
||||
presets: this.model.presets.get(),
|
||||
banks: this.model.banks.get(),
|
||||
wrapSelects: false,
|
||||
largeAppBar: this.updateAppBarZoom(),
|
||||
showSnapshotEditor: false,
|
||||
showStatusMonitor: this.model.showStatusMonitor.get(),
|
||||
snapshotEditorIndex: 0,
|
||||
@@ -112,6 +119,9 @@ export const PerformanceView =
|
||||
|
||||
}
|
||||
|
||||
updateAppBarZoom() {
|
||||
return this.windowSize.width > 700;
|
||||
}
|
||||
|
||||
showStatusMonitorHandler() {
|
||||
this.setState({
|
||||
@@ -119,7 +129,7 @@ export const PerformanceView =
|
||||
});
|
||||
}
|
||||
|
||||
getTag() { return "performView"}
|
||||
getTag() { return "performView" }
|
||||
isOpen() { return this.props.open }
|
||||
onDialogStackClose() {
|
||||
this.props.onClose();
|
||||
@@ -128,15 +138,13 @@ export const PerformanceView =
|
||||
|
||||
private hasHooks: boolean = false;
|
||||
|
||||
updateHooks() : void {
|
||||
updateHooks(): void {
|
||||
let wantHooks = this.mounted && this.props.open;
|
||||
|
||||
if (wantHooks !== this.hasHooks)
|
||||
{
|
||||
if (wantHooks !== this.hasHooks) {
|
||||
this.hasHooks = wantHooks;
|
||||
|
||||
if (this.hasHooks)
|
||||
{
|
||||
|
||||
if (this.hasHooks) {
|
||||
|
||||
pushDialogStack(this);
|
||||
} else {
|
||||
@@ -144,9 +152,12 @@ export const PerformanceView =
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
onWindowSizeChanged(width: number, height: number): void {
|
||||
this.setState({ wrapSelects: width < 700 });
|
||||
this.setState({
|
||||
wrapSelects: width < 700,
|
||||
largeAppBar: this.updateAppBarZoom(),
|
||||
});
|
||||
}
|
||||
|
||||
onPresetsChanged(newValue: PresetIndex) {
|
||||
@@ -156,7 +167,7 @@ export const PerformanceView =
|
||||
this.setState({ banks: this.model.banks.get() })
|
||||
}
|
||||
onPresetChangedChanged(newValue: boolean) {
|
||||
this.setState({ presetModified:newValue});
|
||||
this.setState({ presetModified: newValue });
|
||||
}
|
||||
|
||||
private mounted: boolean = false;
|
||||
@@ -216,84 +227,145 @@ export const PerformanceView =
|
||||
});
|
||||
return true;
|
||||
}
|
||||
handleSnapshotEditOk(index: number, name: string, color: string,newSnapshots: (Snapshot|null)[])
|
||||
{
|
||||
handleSnapshotEditOk(index: number, name: string, color: string, newSnapshots: (Snapshot | null)[]) {
|
||||
// results have been sent to the server. we would perform a short-cut commit to our local state, to avoid laggy display updates.
|
||||
// but we don't actually have that in our state.
|
||||
|
||||
}
|
||||
|
||||
handlePreviousBank()
|
||||
{
|
||||
handlePreviousBank() {
|
||||
this.model.previousBank();
|
||||
}
|
||||
|
||||
handleNextBank()
|
||||
{
|
||||
handleNextBank() {
|
||||
this.model.nextBank();
|
||||
}
|
||||
handlePreviousPreset()
|
||||
{
|
||||
handlePreviousPreset() {
|
||||
this.model.previousPreset();
|
||||
}
|
||||
handleNextPreset()
|
||||
{
|
||||
handleNextPreset() {
|
||||
this.model.nextPreset();
|
||||
}
|
||||
|
||||
render() {
|
||||
const classes = withStyles.getClasses(this.props);
|
||||
let wrapSelects = this.state.wrapSelects;
|
||||
let presets = this.state.presets;
|
||||
let banks = this.state.banks;
|
||||
let appBarIconSize: "large" | undefined = this.state.largeAppBar ? undefined : "large";
|
||||
return (
|
||||
<div className={classes.frame} style={{ overflow: "clip", height: "100%" }} >
|
||||
<div className={classes.frame} style={{ overflow: "clip", height: "100%" }} >
|
||||
<AppBar id="select-plugin-dialog-title"
|
||||
<div id="select-plugin-dialog-title"
|
||||
style={{
|
||||
position: "static", flex: "0 0 auto", height: wrapSelects ? 108 : 58,
|
||||
display: this.state.showSnapshotEditor ? "none" : undefined
|
||||
position: "static", flex: "0 0 auto", height: undefined,
|
||||
display: this.state.showSnapshotEditor ? "none" : undefined,
|
||||
background: "transparent"
|
||||
}}
|
||||
>
|
||||
<div style={{
|
||||
display: "flex", flexFlow: "row nowrap", alignContent: "center",
|
||||
paddingTop: 3, paddingBottom: 3, paddingRight: 8, paddingLeft: 8,
|
||||
alignItems: "start"
|
||||
}}>
|
||||
<IconButtonEx tooltip="Back" aria-label="menu" color="inherit"
|
||||
onClick={() => { this.props.onClose(); }}
|
||||
style={{
|
||||
position: "relative",top: 3,
|
||||
flex: "0 0 auto" }} >
|
||||
<ArrowBackIcon />
|
||||
</IconButtonEx>
|
||||
|
||||
{this.state.largeAppBar ? (
|
||||
<div style={{
|
||||
flex: "1 1 1px", display: "flex",
|
||||
flexFlow: wrapSelects ? "column nowrap" : "row nowrap",
|
||||
alignItems: wrapSelects ? "stretch" : undefined,
|
||||
justifyContent: wrapSelects ? undefined : "space-evenly",
|
||||
marginLeft: 8,
|
||||
gap: 8
|
||||
display: "flex", flexFlow: "row nowrap", alignContent: "center",
|
||||
paddingTop: 3, paddingBottom: 3, paddingRight: 8, paddingLeft: 8,
|
||||
alignItems: "center", justifyContent: "space-between"
|
||||
|
||||
}}>
|
||||
{/********* BANKS *******************/}
|
||||
<div style={{ flex: "1 1 1px", display: "flex", flexFlow: "row nowrap", maxWidth: 500 }}>
|
||||
<IconButtonEx tooltip="Previous bank"
|
||||
aria-label="previous-bank"
|
||||
onClick={() => { this.handlePreviousBank(); }}
|
||||
size="medium"
|
||||
color="inherit"
|
||||
style={{
|
||||
borderTopRightRadius: "3px", borderBottomRightRadius: "3px", marginRight: 4
|
||||
}}
|
||||
>
|
||||
<ArrowBackIosIcon style={{ opacity: 0.75 }} />
|
||||
</IconButtonEx>
|
||||
<IconButtonEx tooltip="Back" aria-label="menu" color="inherit"
|
||||
onClick={() => { this.props.onClose(); }}
|
||||
style={{
|
||||
position: "relative", top: 3,
|
||||
flex: "0 0 auto"
|
||||
}} >
|
||||
<ArrowBackIcon fontSize="large" />
|
||||
</IconButtonEx>
|
||||
|
||||
{/********* PRESETS *******************/}
|
||||
<div style={{
|
||||
flex: "2 2 auto", display: "flex", flexFlow: "row nowrap", alignItems: "center", marginLeft: 24
|
||||
|
||||
}}>
|
||||
<IconButtonEx tooltip="Previous preset"
|
||||
aria-label="previous-preset"
|
||||
onClick={() => { this.handlePreviousPreset(); }}
|
||||
color="inherit"
|
||||
style={{ borderTopRightRadius: "3px", borderBottomRightRadius: "3px", marginRight: 4 }}
|
||||
>
|
||||
<ArrowBackIosIcon style={{ opacity: 0.75 }} fontSize={appBarIconSize} />
|
||||
</IconButtonEx>
|
||||
<Select variant="standard"
|
||||
className={classes.select}
|
||||
style={{ flex: "1 1 1px", width: "100%", position: "relative", top: 0, color: "#FFFFFF" }} disabled={false}
|
||||
style={{
|
||||
flex: "1 1 1px", width: "100%", color: "#FFFFFF",
|
||||
fontSize: (this.state.largeAppBar ? "2.0rem" : undefined),
|
||||
}} disabled={false}
|
||||
displayEmpty
|
||||
onClose={(e) => this.handlePresetSelectClose(e)}
|
||||
value={presets.selectedInstanceId === 0 ? '' : presets.selectedInstanceId}
|
||||
|
||||
inputProps={{
|
||||
classes: { icon: classes.select_icon },
|
||||
'aria-label': "Select preset"
|
||||
}
|
||||
}
|
||||
>
|
||||
{
|
||||
presets.presets.map((preset) => {
|
||||
let name = preset.name;
|
||||
if (this.state.presets.selectedInstanceId === preset.instanceId && this.state.presetModified) {
|
||||
name += "*";
|
||||
}
|
||||
return (
|
||||
<MenuItem key={preset.instanceId} value={preset.instanceId} >
|
||||
{name}
|
||||
</MenuItem>
|
||||
);
|
||||
})
|
||||
}
|
||||
</Select>
|
||||
|
||||
<IconButtonEx tooltip="Next preset"
|
||||
aria-label="next-preset"
|
||||
onClick={() => { this.handleNextPreset(); }}
|
||||
color="inherit"
|
||||
style={{ borderTopLeftRadius: "3px", borderBottomLeftRadius: "3px", marginLeft: 4 }}
|
||||
>
|
||||
<ArrowForwardIosIcon style={{ opacity: 0.75 }} fontSize={appBarIconSize} />
|
||||
</IconButtonEx>
|
||||
|
||||
<IconButtonEx tooltip="Save preset"
|
||||
aria-label="save-preset"
|
||||
onClick={() => { this.model.saveCurrentPreset(); }}
|
||||
size="medium"
|
||||
color="inherit"
|
||||
style={{ flexShrink: 0, visibility: this.state.presetModified ? "visible" : "hidden", marginRight: 16 }}
|
||||
>
|
||||
<SaveIconOutline style={{ opacity: 0.75 }} color="inherit" fontSize={appBarIconSize} />
|
||||
</IconButtonEx>
|
||||
</div>
|
||||
{/********* BANKS *******************/}
|
||||
|
||||
<div style={{
|
||||
flex: "1 1 auto", display: "flex", flexFlow: "row nowrap", maxWidth: 500, marginRight: 16
|
||||
}}>
|
||||
<Select variant="standard"
|
||||
className={classes.select}
|
||||
renderValue={(selected) => {
|
||||
let entry = banks.entries.find(e => e.instanceId === selected);
|
||||
return <div style={{
|
||||
display: "flex", flexFlow: "row nowrap", gap: 4,
|
||||
alignItems: "center", justifyContent: "stretch",
|
||||
}}>
|
||||
<BankIcon className={classes.listIcon} style={{ flex: "0 0 auto", width: 20, height: 20 }} />
|
||||
<Typography noWrap style={{ flex: "1 1 auto", textAlign: "left" }}
|
||||
variant="body2"
|
||||
>
|
||||
{entry ? entry.name : "-"}
|
||||
</Typography>
|
||||
</div>;
|
||||
}}
|
||||
style={{
|
||||
flex: "1 1 1px", width: "100%", position: "relative", top: 0, color: "#FFFFFF",
|
||||
fontSize: "1.0rem",
|
||||
}} disabled={false}
|
||||
displayEmpty
|
||||
onClose={(e) => this.handleBankSelectClose(e)}
|
||||
value={banks.selectedBank === 0 ? undefined : banks.selectedBank}
|
||||
@@ -312,98 +384,141 @@ export const PerformanceView =
|
||||
})
|
||||
}
|
||||
</Select>
|
||||
|
||||
<IconButtonEx tooltip="Next bank"
|
||||
aria-label="next-bank"
|
||||
onClick={() => { this.handleNextBank(); }}
|
||||
color="inherit"
|
||||
style={{ borderTopLeftRadius: "3px", borderBottomLeftRadius: "3px", marginLeft: 4 }}
|
||||
>
|
||||
<ArrowForwardIosIcon style={{ opacity: 0.75 }} />
|
||||
|
||||
</IconButtonEx>
|
||||
|
||||
{/** spacer */}
|
||||
<IconButtonEx
|
||||
tooltip="Next bank"
|
||||
aria-label="next-bank"
|
||||
onClick={() => { this.handleNextBank(); }}
|
||||
size="medium"
|
||||
color="inherit"
|
||||
style={{visibility: "hidden"}}
|
||||
>
|
||||
<ArrowForwardIosIcon style={{ opacity: 0.75 }} />
|
||||
|
||||
</IconButtonEx>
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
{/********* PRESETS *******************/}
|
||||
<div style={{ flex: "1 1 1px", display: "flex", flexFlow: "row nowrap", maxWidth: 500 }}>
|
||||
<IconButtonEx tooltip="Previous preset"
|
||||
aria-label="previous-preset"
|
||||
onClick={() => { this.handlePreviousPreset(); }}
|
||||
color="inherit"
|
||||
style={{ borderTopRightRadius: "3px", borderBottomRightRadius: "3px", marginRight: 4 }}
|
||||
>
|
||||
<ArrowBackIosIcon style={{ opacity: 0.75 }} />
|
||||
</IconButtonEx>
|
||||
<Select variant="standard"
|
||||
className={classes.select}
|
||||
style={{
|
||||
flex: "1 1 1px", width: "100%", color: "#FFFFFF"
|
||||
}} disabled={false}
|
||||
displayEmpty
|
||||
onClose={(e) => this.handlePresetSelectClose(e)}
|
||||
value={presets.selectedInstanceId === 0 ? '' : presets.selectedInstanceId}
|
||||
|
||||
inputProps={{
|
||||
classes: { icon: classes.select_icon },
|
||||
'aria-label': "Select preset"
|
||||
}
|
||||
}
|
||||
>
|
||||
{
|
||||
presets.presets.map((preset) => {
|
||||
let name = preset.name;
|
||||
if (this.state.presets.selectedInstanceId === preset.instanceId && this.state.presetModified)
|
||||
{
|
||||
name += "*";
|
||||
}
|
||||
return (
|
||||
<MenuItem key={preset.instanceId} value={preset.instanceId} >
|
||||
{name}
|
||||
</MenuItem>
|
||||
);
|
||||
})
|
||||
}
|
||||
</Select>
|
||||
|
||||
<IconButtonEx tooltip="Next preset"
|
||||
aria-label="next-preset"
|
||||
onClick={() => { this.handleNextPreset(); }}
|
||||
color="inherit"
|
||||
style={{ borderTopLeftRadius: "3px", borderBottomLeftRadius: "3px", marginLeft: 4 }}
|
||||
>
|
||||
<ArrowForwardIosIcon style={{ opacity: 0.75 }} />
|
||||
</IconButtonEx>
|
||||
|
||||
<IconButtonEx tooltip="Save preset"
|
||||
aria-label="save-preset"
|
||||
onClick={() => { this.model.saveCurrentPreset(); }}
|
||||
size="medium"
|
||||
color="inherit"
|
||||
style={{flexShrink: 0,visibility: this.state.presetModified? "visible": "hidden"}}
|
||||
>
|
||||
<SaveIconOutline style={{ opacity: 0.75 }} color="inherit" />
|
||||
</IconButtonEx>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</AppBar >
|
||||
<div style={{ flex: "1 0 auto", display: "flex", marginTop: 16,marginBottom: 20 }}>
|
||||
) : (
|
||||
|
||||
<div style={{
|
||||
display: "grid",
|
||||
gridTemplateColumns: "min-content min-content auto min-content min-content",
|
||||
}}>
|
||||
<IconButtonEx tooltip="Back" aria-label="menu" color="inherit"
|
||||
onClick={() => { this.props.onClose(); }}
|
||||
style={{
|
||||
position: "relative", top: 3,
|
||||
flex: "0 0 auto"
|
||||
}} >
|
||||
<ArrowBackIcon fontSize="medium" />
|
||||
</IconButtonEx>
|
||||
|
||||
<div/>
|
||||
{/********* BANKS *******************/}
|
||||
<Select variant="standard"
|
||||
className={classes.select}
|
||||
renderValue={(selected) => {
|
||||
let entry = banks.entries.find(e => e.instanceId === selected);
|
||||
return <div style={{
|
||||
display: "flex", flexFlow: "row nowrap", gap: 4,
|
||||
alignItems: "center", justifyContent: "stretch",
|
||||
}}>
|
||||
<BankIcon className={classes.listIcon} style={{ flex: "0 0 auto", width: 20, height: 20 }} />
|
||||
<Typography noWrap style={{ flex: "1 1 auto", textAlign: "left" }}
|
||||
variant="body2"
|
||||
>
|
||||
{entry ? entry.name : "-"}
|
||||
</Typography>
|
||||
</div>;
|
||||
}}
|
||||
style={{
|
||||
flex: "1 1 1px", width: "100%", position: "relative", top: 0, color: "#FFFFFF",
|
||||
fontSize: "1.0rem",
|
||||
}} disabled={false}
|
||||
displayEmpty
|
||||
onClose={(e) => this.handleBankSelectClose(e)}
|
||||
value={banks.selectedBank === 0 ? undefined : banks.selectedBank}
|
||||
inputProps={{
|
||||
classes: { icon: classes.select_icon },
|
||||
'aria-label': "Select preset"
|
||||
}}
|
||||
>
|
||||
{
|
||||
banks.entries.map((entry) => {
|
||||
return (
|
||||
<MenuItem key={entry.instanceId} value={entry.instanceId} >
|
||||
{entry.name}
|
||||
</MenuItem>
|
||||
);
|
||||
})
|
||||
}
|
||||
</Select>
|
||||
|
||||
<div/>
|
||||
|
||||
|
||||
<div/>
|
||||
|
||||
{/**** 2nd row ****/}
|
||||
<div/>
|
||||
{/********* PRESETS *******************/}
|
||||
|
||||
<IconButtonEx tooltip="Previous preset"
|
||||
aria-label="previous-preset"
|
||||
onClick={() => { this.handlePreviousPreset(); }}
|
||||
color="inherit"
|
||||
style={{ borderTopRightRadius: "3px", borderBottomRightRadius: "3px", marginRight: 4 }}
|
||||
>
|
||||
|
||||
<ArrowBackIosIcon style={{ opacity: 0.75 }} fontSize={appBarIconSize} />
|
||||
</IconButtonEx>
|
||||
<Select variant="standard"
|
||||
className={classes.select}
|
||||
style={{
|
||||
flex: "1 1 1px", width: "100%", color: "#FFFFFF",
|
||||
fontSize: (this.state.largeAppBar ? "2.0rem" : undefined),
|
||||
}} disabled={false}
|
||||
displayEmpty
|
||||
onClose={(e) => this.handlePresetSelectClose(e)}
|
||||
value={presets.selectedInstanceId === 0 ? '' : presets.selectedInstanceId}
|
||||
|
||||
inputProps={{
|
||||
classes: { icon: classes.select_icon },
|
||||
'aria-label': "Select preset"
|
||||
}
|
||||
}
|
||||
>
|
||||
{
|
||||
presets.presets.map((preset) => {
|
||||
let name = preset.name;
|
||||
if (this.state.presets.selectedInstanceId === preset.instanceId && this.state.presetModified) {
|
||||
name += "*";
|
||||
}
|
||||
return (
|
||||
<MenuItem key={preset.instanceId} value={preset.instanceId} >
|
||||
{name}
|
||||
</MenuItem>
|
||||
);
|
||||
})
|
||||
}
|
||||
</Select>
|
||||
|
||||
<IconButtonEx tooltip="Next preset"
|
||||
aria-label="next-preset"
|
||||
onClick={() => { this.handleNextPreset(); }}
|
||||
color="inherit"
|
||||
style={{ borderTopLeftRadius: "3px", borderBottomLeftRadius: "3px", marginLeft: 4 }}
|
||||
>
|
||||
<ArrowForwardIosIcon style={{ opacity: 0.75 }} fontSize={appBarIconSize} />
|
||||
</IconButtonEx>
|
||||
|
||||
<IconButtonEx tooltip="Save preset"
|
||||
aria-label="save-preset"
|
||||
onClick={() => { this.model.saveCurrentPreset(); }}
|
||||
size="medium"
|
||||
color="inherit"
|
||||
style={{ flexShrink: 0, visibility: this.state.presetModified ? "visible" : "hidden" }}
|
||||
>
|
||||
<SaveIconOutline style={{ opacity: 0.75 }} color="inherit" fontSize={appBarIconSize} />
|
||||
</IconButtonEx>
|
||||
|
||||
</div>
|
||||
|
||||
)}
|
||||
</div >
|
||||
<div style={{ flex: "1 0 auto", display: "flex", marginTop: 16, marginBottom: 20 }}>
|
||||
<SnapshotPanel onEdit={(index) => { return this.handleOnEdit(index); }} />
|
||||
</div>
|
||||
|
||||
@@ -413,15 +528,15 @@ export const PerformanceView =
|
||||
)}
|
||||
</div>
|
||||
{this.state.showSnapshotEditor && (
|
||||
<SnapshotEditor snapshotIndex={this.state.snapshotEditorIndex}
|
||||
onClose={() => {
|
||||
<SnapshotEditor snapshotIndex={this.state.snapshotEditorIndex}
|
||||
onClose={() => {
|
||||
this.setState({ showSnapshotEditor: false });
|
||||
}}
|
||||
onOk={(index,name,color,newSnapshots)=>{
|
||||
this.setState({ showSnapshotEditor: false});
|
||||
this.handleSnapshotEditOk(index,name,color,newSnapshots);
|
||||
onOk={(index, name, color, newSnapshots) => {
|
||||
this.setState({ showSnapshotEditor: false });
|
||||
this.handleSnapshotEditOk(index, name, color, newSnapshots);
|
||||
}}
|
||||
/>
|
||||
/>
|
||||
)}
|
||||
</div >
|
||||
)
|
||||
|
||||
@@ -491,6 +491,9 @@ export class PiPedalModel //implements PiPedalModel
|
||||
webSocket?: PiPedalSocket;
|
||||
|
||||
|
||||
static getInstance(): PiPedalModel {
|
||||
return PiPedalModelFactory.getInstance();
|
||||
}
|
||||
hasTone3000Auth: ObservableProperty<boolean> = new ObservableProperty<boolean>(false);
|
||||
|
||||
canKeepScreenOn: boolean = false;
|
||||
@@ -2046,13 +2049,14 @@ export class PiPedalModel //implements PiPedalModel
|
||||
}
|
||||
|
||||
|
||||
saveCurrentPresetAs(newName: string, saveAfterInstanceId = -1): Promise<number> {
|
||||
saveCurrentPresetAs(bankInstanceId: number, newName: string, saveAfterInstanceId = -1): Promise<number> {
|
||||
// default behaviour is to save after the currently selected preset.
|
||||
if (saveAfterInstanceId === -1) {
|
||||
saveAfterInstanceId = this.presets.get().selectedInstanceId;
|
||||
}
|
||||
let request: any = {
|
||||
clientId: this.clientId,
|
||||
bankInstanceId: bankInstanceId,
|
||||
name: newName,
|
||||
saveAfterInstanceId: saveAfterInstanceId
|
||||
|
||||
@@ -2061,7 +2065,9 @@ export class PiPedalModel //implements PiPedalModel
|
||||
return nullCast(this.webSocket)
|
||||
.request<number>("saveCurrentPresetAs", request)
|
||||
.then((newPresetId) => {
|
||||
this.loadPreset(newPresetId);
|
||||
if (bankInstanceId === this.banks.get().selectedBank) {
|
||||
this.loadPreset(newPresetId);
|
||||
}
|
||||
return newPresetId;
|
||||
});
|
||||
}
|
||||
@@ -2186,8 +2192,8 @@ export class PiPedalModel //implements PiPedalModel
|
||||
}
|
||||
|
||||
|
||||
deletePresetItem(instanceId: number): Promise<number> {
|
||||
return nullCast(this.webSocket).request<number>("deletePresetItem", instanceId);
|
||||
deletePresetItems(instanceIds: Set<number>): Promise<number> {
|
||||
return nullCast(this.webSocket).request<number>("deletePresetItems", Array.from(instanceIds));
|
||||
|
||||
}
|
||||
deleteBankItem(instanceId: number): Promise<number> {
|
||||
@@ -3376,9 +3382,9 @@ export class PiPedalModel //implements PiPedalModel
|
||||
this.cancelOnNetworkChanging();
|
||||
},
|
||||
30 * 1000);
|
||||
|
||||
}
|
||||
setPedalboardItemTitle(instanceId: number, title: string): void {
|
||||
|
||||
setPedalboardItemTitle(instanceId: number, title: string, iconColor: string): void {
|
||||
let pedalboard = this.pedalboard.get();
|
||||
if (!pedalboard) {
|
||||
throw new PiPedalStateError("Pedalboard not loaded.");
|
||||
@@ -3386,13 +3392,14 @@ export class PiPedalModel //implements PiPedalModel
|
||||
let newPedalboard = pedalboard.clone();
|
||||
this.updateVst3State(newPedalboard);
|
||||
let item = newPedalboard.getItem(instanceId);
|
||||
if (item.title === title) {
|
||||
if (item.title === title && item.iconColor === iconColor) {
|
||||
return;
|
||||
}
|
||||
item.title = title;
|
||||
item.iconColor = iconColor;
|
||||
this.pedalboard.set(newPedalboard);
|
||||
// notify the server.
|
||||
this.webSocket?.send("setPedalboardItemTitle", { instanceId: instanceId, title: title });
|
||||
this.webSocket?.send("setPedalboardItemTitle", { instanceId: instanceId, title: title, colorKey: iconColor });
|
||||
}
|
||||
setAlsaSequencerConfiguration(alsaSequencerConfiguration: AlsaSequencerConfiguration): void {
|
||||
this.webSocket?.send("setAlsaSequencerConfiguration", alsaSequencerConfiguration);
|
||||
@@ -3535,6 +3542,16 @@ export class PiPedalModel //implements PiPedalModel
|
||||
this.webSocket?.send("setSelectedPedalboardPlugin", { clientId: this.clientId, pluginInstanceId: pluginId });
|
||||
|
||||
}
|
||||
requestBankPresets(bankInstanceId: number): Promise<PresetIndexEntry[]> {
|
||||
return nullCast(this.webSocket).request<PresetIndexEntry[]>("requestBankPresets", {bankInstanceId: bankInstanceId});
|
||||
}
|
||||
|
||||
importPresetsFromBank(bankInstanceId: number, presets: number[]): Promise<number> {
|
||||
return nullCast(this.webSocket).request<number>("importPresetsFromBank", {bankInstanceId: bankInstanceId, presets: presets});
|
||||
}
|
||||
copyPresetsToBank(bankInstanceId: number, presets: number[]): Promise<number> {
|
||||
return nullCast(this.webSocket).request<number>("copyPresetsToBank", {bankInstanceId: bankInstanceId, presets: presets});
|
||||
}
|
||||
};
|
||||
|
||||
let instance: PiPedalModel | undefined = undefined;
|
||||
|
||||
@@ -307,7 +307,7 @@ const PluginControl =
|
||||
e.stopPropagation();
|
||||
}
|
||||
|
||||
isValidPointer(e: PointerEvent<SVGSVGElement>): boolean {
|
||||
isValidPointer(e: PointerEvent): boolean {
|
||||
if (e.pointerType === "mouse") {
|
||||
return e.button === 0;
|
||||
} else if (e.pointerType === "pen") {
|
||||
@@ -347,7 +347,7 @@ const PluginControl =
|
||||
pointerId: number = 0;
|
||||
pointerType: string = "";
|
||||
|
||||
isCapturedPointer(e: PointerEvent<SVGSVGElement>): boolean {
|
||||
isCapturedPointer(e: PointerEvent): boolean {
|
||||
return this.mouseDown
|
||||
&& e.pointerId === this.pointerId
|
||||
&& e.pointerType === this.pointerType;
|
||||
@@ -361,7 +361,7 @@ const PluginControl =
|
||||
pointersDown: number = 0;
|
||||
captureElement?: SVGSVGElement = undefined;
|
||||
|
||||
onPointerDown(e: PointerEvent<SVGSVGElement>): void {
|
||||
onPointerDown(e: PointerEvent): void {
|
||||
if (!this.mouseDown && this.isValidPointer(e)) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
@@ -387,7 +387,7 @@ const PluginControl =
|
||||
this.mouseDown = true;
|
||||
if (this.pointersDown === 1) {
|
||||
this.isTap = true;
|
||||
this.tapStartMs = Date.now();
|
||||
this.tapStartMs = e.timeStamp;
|
||||
} else {
|
||||
this.isTap = false;
|
||||
}
|
||||
@@ -429,7 +429,7 @@ const PluginControl =
|
||||
|
||||
}
|
||||
|
||||
onPointerLostCapture(e: PointerEvent<SVGSVGElement>) {
|
||||
onPointerLostCapture(e: PointerEvent) {
|
||||
if (this.isCapturedPointer(e)) {
|
||||
if (this.pointersDown !== 0) {
|
||||
--this.pointersDown;
|
||||
@@ -442,7 +442,7 @@ const PluginControl =
|
||||
|
||||
}
|
||||
|
||||
updateRange(e: PointerEvent<SVGSVGElement>): number {
|
||||
updateRange(e: PointerEvent): number {
|
||||
|
||||
let ultraHigh = false;
|
||||
let high = false;
|
||||
@@ -486,9 +486,11 @@ const PluginControl =
|
||||
}
|
||||
}
|
||||
}
|
||||
onPointerTap() {
|
||||
let tapTime = Date.now();
|
||||
onPointerTap(e: PointerEvent) {
|
||||
let tapTime = e.timeStamp;
|
||||
let dT = tapTime - this.lastTapMs;
|
||||
|
||||
|
||||
this.lastTapMs = tapTime;
|
||||
|
||||
if (dT < 500) {
|
||||
@@ -497,7 +499,7 @@ const PluginControl =
|
||||
}
|
||||
private tapStartMs: number = 0;
|
||||
|
||||
onPointerUp(e: PointerEvent<SVGSVGElement>) {
|
||||
onPointerUp(e: PointerEvent) {
|
||||
|
||||
if (this.isCapturedPointer(e)) {
|
||||
if (this.pointersDown !== 0) {
|
||||
@@ -514,9 +516,9 @@ const PluginControl =
|
||||
|
||||
this.releaseCapture(e);
|
||||
if (this.isTap) {
|
||||
let ms = Date.now() - this.tapStartMs;
|
||||
let ms = e.timeStamp - this.tapStartMs;
|
||||
if (ms < 200) {
|
||||
this.onPointerTap();
|
||||
this.onPointerTap(e);
|
||||
}
|
||||
}
|
||||
// prevent click from firing on other elements
|
||||
@@ -531,7 +533,7 @@ const PluginControl =
|
||||
}
|
||||
}
|
||||
|
||||
releaseCapture(e: PointerEvent<SVGSVGElement>) {
|
||||
releaseCapture(e: PointerEvent) {
|
||||
let img = this.imgRef.current;
|
||||
|
||||
if (img && img.style) {
|
||||
@@ -556,7 +558,7 @@ const PluginControl =
|
||||
clickSlop() {
|
||||
return 5; // maybe larger on touch devices.
|
||||
}
|
||||
onPointerMove(e: PointerEvent<SVGSVGElement>): void {
|
||||
onPointerMove(e: PointerEvent): void {
|
||||
if (this.isCapturedPointer(e)) {
|
||||
e.preventDefault();
|
||||
let dRange = this.updateRange(e)
|
||||
@@ -1138,7 +1140,7 @@ const PluginControl =
|
||||
);
|
||||
}
|
||||
/*
|
||||
isSamePointer(PointerEvent<SVGSVGElement> e): boolean
|
||||
isSamePointer(PointerEvent e): boolean
|
||||
{
|
||||
return e.pointerId === this.pointerId
|
||||
&& e.pointerType === this.pointerType;
|
||||
|
||||
@@ -477,6 +477,7 @@ const PluginControlView =
|
||||
return ((
|
||||
<ToobFrequencyResponseView instanceId={this.props.instanceId}
|
||||
propertyName={frequencyPlot.patchProperty}
|
||||
frequencyPlot={frequencyPlot}
|
||||
width={frequencyPlot.width}
|
||||
/>
|
||||
));
|
||||
|
||||
@@ -23,7 +23,6 @@ import WithStyles from './WithStyles';
|
||||
import {createStyles} from './WithStyles';
|
||||
|
||||
import { withStyles } from "tss-react/mui";
|
||||
import { PiPedalModelFactory } from "./PiPedalModel";
|
||||
import { PluginType } from './Lv2Plugin';
|
||||
|
||||
|
||||
@@ -67,6 +66,104 @@ import FxEmptyIcon from './svg/fx_empty.svg?react';
|
||||
import FxTerminalIcon from './svg/fx_terminal.svg?react';
|
||||
|
||||
import { isDarkMode } from './DarkMode';
|
||||
import { PiPedalModel } from './PiPedalModel';
|
||||
|
||||
export interface IconColorSelect {
|
||||
key: string,
|
||||
value: string | undefined,
|
||||
};
|
||||
|
||||
export const getIconColorSelections = (darkMode?: boolean): IconColorSelect[] => {
|
||||
if (darkMode === undefined) darkMode = isDarkMode();
|
||||
let result: IconColorSelect[] = [];
|
||||
|
||||
let l: number;
|
||||
let c: number;
|
||||
if (darkMode) {
|
||||
l = 0.8
|
||||
c = 0.25;
|
||||
} else {
|
||||
l = 0.5;
|
||||
c = 0.20;
|
||||
}
|
||||
|
||||
for (let angle = 0; angle < 360; angle += 22.5) {
|
||||
if (angle === 270-22.5)
|
||||
{
|
||||
// for some reason, this one is useless. Patch in a more useful light blue. :-/
|
||||
let a2 = 270;
|
||||
let l2: number;
|
||||
let c2: number;
|
||||
if (darkMode) {
|
||||
l2 = 0.9
|
||||
c2 = 0.25;
|
||||
} else {
|
||||
l2 = 0.7;
|
||||
c2 = 0.20;
|
||||
}
|
||||
result.push(
|
||||
{
|
||||
key: "oklch" + Math.round(angle*10).toString(),
|
||||
value: ("oklch(" + l2.toString() + " " + c2.toString() + " " + (a2).toString() + ")")
|
||||
|
||||
}
|
||||
);
|
||||
|
||||
} else if (angle === 270-45)
|
||||
{
|
||||
// for some reason, this one is useless. Patch in a more useful light blue. :-/
|
||||
let a2 = 180;
|
||||
let l2: number;
|
||||
let c2: number;
|
||||
if (darkMode) {
|
||||
l2 = 0.95
|
||||
c2 = 0.25;
|
||||
} else {
|
||||
l2 = 0.75;
|
||||
c2 = 0.20;
|
||||
}
|
||||
result.push(
|
||||
{
|
||||
key: "oklch" + Math.round(angle*10).toString(),
|
||||
value: ("oklch(" + l2.toString() + " " + c2.toString() + " " + (a2).toString() + ")")
|
||||
|
||||
}
|
||||
);
|
||||
|
||||
} else {
|
||||
result.push(
|
||||
{
|
||||
key: "oklch" + Math.round(angle*10).toString(),
|
||||
value: ("oklch(" + l.toString() + " " + c.toString() + " " + (angle).toString() + ")")
|
||||
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
|
||||
};
|
||||
|
||||
|
||||
function makeDefaultColorMap(): {[key:string]: string} {
|
||||
let result: {[key:string]: string} = {};
|
||||
for (let selection of getIconColorSelections())
|
||||
{
|
||||
result[selection.key] = selection.value??"";
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
const iconColors: {[key:string]: string} = makeDefaultColorMap();
|
||||
|
||||
export const getIconColor = (key?: string): string| undefined => {
|
||||
if (iconColors.hasOwnProperty(key!))
|
||||
{
|
||||
return iconColors[key!];
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -86,13 +183,13 @@ const styles = (theme: Theme) =>
|
||||
export interface PluginIconProps extends WithStyles<typeof styles> {
|
||||
size?: number;
|
||||
opacity?: number;
|
||||
color?: string;
|
||||
offsetY?: number;
|
||||
pluginType: PluginType;
|
||||
pluginMissing?: boolean;
|
||||
}
|
||||
|
||||
export function SelectSvgIcon(plugin_type: PluginType, className: string, size: number = 24, opacity: number = 1.0, missingPlugin: boolean = false) {
|
||||
let color = "";
|
||||
export function SelectSvgIcon(plugin_type: PluginType, className: string, size: number = 24, opacity: number = 1.0, missingPlugin: boolean = false, color = "") {
|
||||
if (missingPlugin) {
|
||||
color = isDarkMode() ? "#C00000" : "#B00000";
|
||||
}
|
||||
@@ -264,11 +361,11 @@ export function SelectBaseIcon(plugin_type: PluginType): string {
|
||||
|
||||
export function SelectIconUri(plugin_type: PluginType) {
|
||||
let icon = SelectBaseIcon(plugin_type);
|
||||
return PiPedalModelFactory.getInstance().svgImgUrl(icon);
|
||||
return PiPedalModel.getInstance().svgImgUrl(icon);
|
||||
}
|
||||
|
||||
const PluginIcon = withStyles((props: PluginIconProps) => {
|
||||
const { pluginType, opacity, pluginMissing } = props;
|
||||
const { pluginType, opacity, pluginMissing,color } = props;
|
||||
const classes = withStyles.getClasses(props);
|
||||
|
||||
let pluginMissing_: boolean = pluginMissing ?? false;
|
||||
@@ -277,7 +374,7 @@ const PluginIcon = withStyles((props: PluginIconProps) => {
|
||||
if (props.size) size = props.size;
|
||||
let topVal: number = (props.offsetY ?? 0);
|
||||
|
||||
let svgIcon = SelectSvgIcon(pluginType, classes.icon, size, opacity, pluginMissing_);
|
||||
let svgIcon = SelectSvgIcon(pluginType, classes.icon, size, opacity, pluginMissing_,color? color: "" );
|
||||
if (svgIcon) {
|
||||
return svgIcon;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,190 @@
|
||||
// Copyright (c) Robin E. R. Davies
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
// this software and associated documentation files (the "Software"), to deal in
|
||||
// the Software without restriction, including without limitation the rights to
|
||||
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
// the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
// subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER`
|
||||
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
import React from 'react';
|
||||
import Typography from '@mui/material/Typography';
|
||||
import IconButtonEx from './IconButtonEx';
|
||||
import ArrowBackIcon from '@mui/icons-material/ArrowBack';
|
||||
import IconColorDropdownButton, { DropdownAlignment } from './IconColorDropdownButton';
|
||||
import InputLabel from '@mui/material/InputLabel';
|
||||
import DialogEx from './DialogEx';
|
||||
import DialogContent from '@mui/material/DialogContent';
|
||||
import DialogTitle from '@mui/material/DialogContent';
|
||||
import { nullCast } from './Utility';
|
||||
import ResizeResponsiveComponent from './ResizeResponsiveComponent';
|
||||
import { PiPedalModel } from './PiPedalModel';
|
||||
//import TextFieldEx from './TextFieldEx';
|
||||
import TextField from '@mui/material/TextField';
|
||||
import { PedalboardItem } from './Pedalboard';
|
||||
|
||||
|
||||
export interface PluginNameDialogProps {
|
||||
open: boolean,
|
||||
pedalboardItem: PedalboardItem | null,
|
||||
allowEmpty?: boolean,
|
||||
label?: string,
|
||||
onApply: (text: string, color: string) => void,
|
||||
onClose: () => void
|
||||
};
|
||||
|
||||
export interface PluginNameDialogState {
|
||||
iconColor: string;
|
||||
};
|
||||
|
||||
function isTouchUi() {
|
||||
return 'ontouchstart' in window || navigator.maxTouchPoints > 0;
|
||||
}
|
||||
export default class PluginNameDialog extends ResizeResponsiveComponent<PluginNameDialogProps, PluginNameDialogState> {
|
||||
|
||||
refText: React.RefObject<HTMLInputElement | null>;
|
||||
|
||||
constructor(props: PluginNameDialogProps) {
|
||||
super(props);
|
||||
this.state = {
|
||||
iconColor: this.props.pedalboardItem?.iconColor ?? ""
|
||||
};
|
||||
this.refText = React.createRef<HTMLInputElement>();
|
||||
}
|
||||
mounted: boolean = false;
|
||||
|
||||
|
||||
|
||||
onWindowSizeChanged(width: number, height: number): void {
|
||||
}
|
||||
|
||||
|
||||
componentDidMount() {
|
||||
super.componentDidMount();
|
||||
this.mounted = true;
|
||||
}
|
||||
componentWillUnmount() {
|
||||
super.componentWillUnmount();
|
||||
this.mounted = false;
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
}
|
||||
checkForIllegalCharacters(filename: string) {
|
||||
if (filename.indexOf('/') !== -1) {
|
||||
throw new Error("Illegal character: '/'");
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
let props = this.props;
|
||||
let { open, onClose, onApply } = props;
|
||||
let model = PiPedalModel.getInstance();
|
||||
|
||||
|
||||
if (!open) return null;
|
||||
let pedalboardItem = props.pedalboardItem;
|
||||
if (!pedalboardItem) return null;
|
||||
|
||||
let uiPlugin = model.getUiPlugin(pedalboardItem.uri);
|
||||
if (uiPlugin === null) {
|
||||
return null;
|
||||
}
|
||||
let defaultName = props.pedalboardItem?.title ?? "";
|
||||
const handleClose = () => {
|
||||
onClose();
|
||||
};
|
||||
|
||||
const handleOk = () => {
|
||||
let text = nullCast(this.refText.current).value;
|
||||
text = text.trim();
|
||||
try {
|
||||
this.checkForIllegalCharacters(text);
|
||||
} catch (e: any) {
|
||||
let model: PiPedalModel = PiPedalModel.getInstance();
|
||||
model.showAlert(e.toString());
|
||||
return;
|
||||
}
|
||||
if (text.length === 0 && props.allowEmpty !== true) return;
|
||||
onApply(text, this.state.iconColor);
|
||||
onClose();
|
||||
}
|
||||
const handleKeyDown = (event: React.KeyboardEvent<HTMLDivElement>): void => {
|
||||
// 'keypress' event misbehaves on mobile so we track 'Enter' key via 'keydown' event
|
||||
if (event.key === 'Enter') {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
handleOk();
|
||||
}
|
||||
};
|
||||
return (
|
||||
<DialogEx tag="nameDialog" open={open} maxWidth="md" onClose={handleClose} aria-labelledby="Rename-dialog-title"
|
||||
style={{ userSelect: "none" }}
|
||||
onEnterKey={() => { }}
|
||||
>
|
||||
<DialogTitle style={{paddingBottom: 0, paddingTop: 8}}>
|
||||
<div style={{display: "flex",flexFlow: "row nowrap", alignItems: "center"}} >
|
||||
<IconButtonEx tooltip="Back" edge="start" color="inherit" onClick={() => onClose()} aria-label="back"
|
||||
>
|
||||
<ArrowBackIcon />
|
||||
</IconButtonEx>
|
||||
<Typography noWrap variant="h6" >
|
||||
Plugin Properties
|
||||
</Typography>
|
||||
</div>
|
||||
</DialogTitle>
|
||||
<DialogContent >
|
||||
<div style={{ display: "flex", flexFlow: "column nowrap", alignItems: "start", gap: 8, marginBottom: 8 }}>
|
||||
<TextField
|
||||
style={{ flex: "1 1 auto", width: 240 }}
|
||||
autoFocus={!isTouchUi()}
|
||||
onKeyDown={handleKeyDown}
|
||||
variant="standard"
|
||||
autoComplete="off"
|
||||
autoCorrect="off"
|
||||
onChange={(e) => { this.props.onApply(e.target.value.toString(), this.state.iconColor) }}
|
||||
autoCapitalize="off"
|
||||
slotProps={{
|
||||
input: {
|
||||
style: { scrollMargin: 24 }
|
||||
},
|
||||
inputLabel: {
|
||||
shrink: true
|
||||
}
|
||||
}}
|
||||
id="name"
|
||||
type="text"
|
||||
label="Plugin Display name"
|
||||
placeholder={uiPlugin?.label ?? ""}
|
||||
defaultValue={defaultName}
|
||||
inputRef={this.refText}
|
||||
/>
|
||||
<div>
|
||||
<InputLabel style={{ fontSize: "0.75rem", fontWeight: 400, marginTop: 16 }}>Icon color</InputLabel>
|
||||
<IconColorDropdownButton colorKey={this.state.iconColor}
|
||||
onColorChange={(selection) => {
|
||||
if (this.props.pedalboardItem) {
|
||||
this.setState({ iconColor: selection.key });
|
||||
onApply(this.props.pedalboardItem.title, selection.key);
|
||||
}
|
||||
}}
|
||||
pluginType={uiPlugin.plugin_type}
|
||||
dropdownAlignment={DropdownAlignment.SE}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</DialogEx>
|
||||
);
|
||||
}
|
||||
}
|
||||
+315
-136
@@ -17,34 +17,37 @@
|
||||
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
import React, { SyntheticEvent,Component } from 'react';
|
||||
import React, { SyntheticEvent } from 'react';
|
||||
import ImportPresetFromBankDialog from './ImportPresetFromBankDialog';
|
||||
import CopyPresetsToBankDialog from './CopyPresetsToBankDialog';
|
||||
import Icon from '@mui/material/Icon';
|
||||
import Divider from '@mui/material/Divider';
|
||||
import { css } from '@emotion/react';
|
||||
import {isDarkMode} from './DarkMode';
|
||||
import { isDarkMode } from './DarkMode';
|
||||
import IconButtonEx from './IconButtonEx';
|
||||
import Typography from '@mui/material/Typography';
|
||||
import { PiPedalModel, PiPedalModelFactory, PresetIndexEntry, PresetIndex } from './PiPedalModel';
|
||||
import Button from '@mui/material/Button';
|
||||
import ButtonBase from "@mui/material/ButtonBase";
|
||||
import DialogEx from './DialogEx';
|
||||
import AppBar from '@mui/material/AppBar';
|
||||
import Toolbar from '@mui/material/Toolbar';
|
||||
import DraggableGrid, { ScrollDirection } from './DraggableGrid';
|
||||
import CloseIcon from '@mui/icons-material/Close';
|
||||
import MoreVertIcon from '@mui/icons-material/MoreVert';
|
||||
import ListItemButton from '@mui/material/ListItemButton';
|
||||
import ListItemIcon from '@mui/material/ListItemIcon';
|
||||
import ListItemText from '@mui/material/ListItemText';
|
||||
import Menu from '@mui/material/Menu';
|
||||
import MenuItem from '@mui/material/MenuItem';
|
||||
import Fade from '@mui/material/Fade';
|
||||
import UploadPresetDialog from './UploadPresetDialog';
|
||||
import ResizeResponsiveComponent from './ResizeResponsiveComponent';
|
||||
|
||||
import SelectHoverBackground from './SelectHoverBackground';
|
||||
import CloseIcon from '@mui/icons-material/Close';
|
||||
import ArrowBackIcon from '@mui/icons-material/ArrowBack';
|
||||
import EditIcon from '@mui/icons-material/Edit';
|
||||
import RenameDialog from './RenameDialog';
|
||||
|
||||
import Slide, {SlideProps} from '@mui/material/Slide';
|
||||
import {createStyles} from './WithStyles';
|
||||
import Slide, { SlideProps } from '@mui/material/Slide';
|
||||
import { createStyles } from './WithStyles';
|
||||
|
||||
import { Theme } from '@mui/material/styles';
|
||||
import WithStyles from './WithStyles';
|
||||
@@ -53,10 +56,13 @@ import { withStyles } from "tss-react/mui";
|
||||
import DownloadIcon from './svg/file_download_black_24dp.svg?react';
|
||||
import UploadIcon from './svg/file_upload_black_24dp.svg?react';
|
||||
|
||||
// function isTouchUi() {
|
||||
// return 'ontouchstart' in window || navigator.maxTouchPoints > 0;
|
||||
// }
|
||||
|
||||
|
||||
interface PresetDialogProps extends WithStyles<typeof styles> {
|
||||
show: boolean;
|
||||
isEditDialog: boolean;
|
||||
onDialogClose: () => void;
|
||||
|
||||
};
|
||||
@@ -64,11 +70,16 @@ interface PresetDialogProps extends WithStyles<typeof styles> {
|
||||
interface PresetDialogState {
|
||||
presets: PresetIndex;
|
||||
|
||||
showActionBar: boolean;
|
||||
multiSelect: boolean;
|
||||
showTouchActionBar: boolean;
|
||||
|
||||
selectedItem: number;
|
||||
|
||||
currentItem: number;
|
||||
selectedItems: Set<number>;
|
||||
|
||||
renameOpen: boolean;
|
||||
importOpen: boolean;
|
||||
copyToOpen: boolean;
|
||||
|
||||
moreMenuAnchorEl: HTMLElement | null;
|
||||
openUploadPresetDialog: boolean;
|
||||
@@ -79,7 +90,7 @@ interface PresetDialogState {
|
||||
|
||||
const styles = (theme: Theme) => createStyles({
|
||||
listIcon: css({
|
||||
width: 24, height: 24, opacity: 0.6, fill: theme.palette.text.primary
|
||||
width: 24, height: 24, opacity: 0.6, fill: theme.palette.text.primary
|
||||
}),
|
||||
dialogAppBar: css({
|
||||
position: 'relative',
|
||||
@@ -88,7 +99,8 @@ const styles = (theme: Theme) => createStyles({
|
||||
dialogActionBar: css({
|
||||
position: 'relative',
|
||||
top: 0, left: 0,
|
||||
background: "black"
|
||||
background: theme.palette.actionBar.main,
|
||||
color: theme.palette.actionBar.contrastText
|
||||
}),
|
||||
dialogTitle: css({
|
||||
marginLeft: theme.spacing(2),
|
||||
@@ -106,7 +118,7 @@ const styles = (theme: Theme) => createStyles({
|
||||
alignItems: "center",
|
||||
textAlign: "left",
|
||||
justifyContent: "center",
|
||||
paddingLeft: 8
|
||||
paddibomngLeft: 8
|
||||
}),
|
||||
iconFrame: css({
|
||||
flex: "0 0 auto",
|
||||
@@ -131,7 +143,7 @@ const Transition = React.forwardRef(function Transition(
|
||||
|
||||
|
||||
const PresetDialog = withStyles(
|
||||
class extends Component<PresetDialogProps, PresetDialogState> {
|
||||
class extends ResizeResponsiveComponent<PresetDialogProps, PresetDialogState> {
|
||||
|
||||
model: PiPedalModel;
|
||||
|
||||
@@ -145,9 +157,13 @@ const PresetDialog = withStyles(
|
||||
let presets = this.model.presets.get();
|
||||
this.state = {
|
||||
presets: presets,
|
||||
showActionBar: false,
|
||||
selectedItem: presets.selectedInstanceId,
|
||||
multiSelect: false,
|
||||
showTouchActionBar: false,
|
||||
currentItem: presets.selectedInstanceId,
|
||||
selectedItems: new Set<number>([presets.selectedInstanceId]),
|
||||
renameOpen: false,
|
||||
importOpen: false,
|
||||
copyToOpen: false,
|
||||
moreMenuAnchorEl: null,
|
||||
openUploadPresetDialog: false
|
||||
|
||||
@@ -156,12 +172,33 @@ const PresetDialog = withStyles(
|
||||
|
||||
}
|
||||
|
||||
onWindowSizeChanged(width: number, height: number) {
|
||||
super.onWindowSizeChanged(width, height);
|
||||
}
|
||||
handleSelectionUpdated(instanceIds: Set<number>) {
|
||||
this.setState({
|
||||
multiSelect: instanceIds.size > 1
|
||||
})
|
||||
|
||||
}
|
||||
setSelections(instanceIds: Set<number>) {
|
||||
this.setState({
|
||||
selectedItems: instanceIds,
|
||||
});
|
||||
this.handleSelectionUpdated(instanceIds);
|
||||
}
|
||||
setSelection(instanceId: number) {
|
||||
let selectedItems = new Set<number>([instanceId]);
|
||||
this.setState({
|
||||
currentItem: instanceId,
|
||||
selectedItems: selectedItems
|
||||
});
|
||||
this.handleSelectionUpdated(selectedItems);
|
||||
}
|
||||
|
||||
selectItemAtIndex(index: number) {
|
||||
let instanceId = this.state.presets.presets[index].instanceId;
|
||||
this.setState({ selectedItem: instanceId });
|
||||
}
|
||||
isEditMode() {
|
||||
return this.state.showActionBar || this.props.isEditDialog;
|
||||
this.setSelection(instanceId);
|
||||
}
|
||||
|
||||
onMoreClick(e: SyntheticEvent): void {
|
||||
@@ -170,7 +207,7 @@ const PresetDialog = withStyles(
|
||||
|
||||
handleDownloadPreset() {
|
||||
this.handleMoreClose();
|
||||
this.model.download("downloadPreset", this.state.selectedItem);
|
||||
this.model.download("downloadPreset", this.state.currentItem);
|
||||
}
|
||||
handleUploadPreset() {
|
||||
this.handleMoreClose();
|
||||
@@ -189,8 +226,11 @@ const PresetDialog = withStyles(
|
||||
if (!presets.areEqual(this.state.presets, false)) // avoid a bunch of peculiar effects if we update while a drag is in progress
|
||||
{
|
||||
// if we don't have a valid selection, then use the current preset.
|
||||
if (this.state.presets.getItem(this.state.selectedItem) == null) {
|
||||
this.setState({ presets: presets, selectedItem: presets.selectedInstanceId });
|
||||
if (this.state.presets.getItem(this.state.currentItem) == null) {
|
||||
this.setState({
|
||||
presets: presets, currentItem: presets.selectedInstanceId,
|
||||
selectedItems: new Set<number>([presets.selectedInstanceId])
|
||||
});
|
||||
} else {
|
||||
this.setState({ presets: presets });
|
||||
}
|
||||
@@ -207,8 +247,10 @@ const PresetDialog = withStyles(
|
||||
this.model.presets.removeOnChangedHandler(this.handlePresetsChanged);
|
||||
}
|
||||
|
||||
getSelectedIndex() {
|
||||
let instanceId = this.isEditMode() ? this.state.selectedItem : this.state.presets.selectedInstanceId;
|
||||
getSelectedIndex(instanceId?: number): number {
|
||||
if (instanceId === undefined) {
|
||||
instanceId = this.state.currentItem;
|
||||
}
|
||||
let presets = this.state.presets;
|
||||
for (let i = 0; i < presets.presets.length; ++i) {
|
||||
if (presets.presets[i].instanceId === instanceId) return i;
|
||||
@@ -217,33 +259,59 @@ const PresetDialog = withStyles(
|
||||
}
|
||||
|
||||
handleDeleteClick() {
|
||||
if (!this.state.selectedItem) return;
|
||||
let selectedItem = this.state.selectedItem;
|
||||
if (selectedItem !== -1) {
|
||||
this.model.deletePresetItem(selectedItem)
|
||||
.then((selectedItem: number) => {
|
||||
this.setState({ selectedItem: selectedItem });
|
||||
})
|
||||
.catch((error) => {
|
||||
this.model.showAlert(error);
|
||||
});
|
||||
|
||||
if (this.state.selectedItems.size === 0) {
|
||||
return;
|
||||
}
|
||||
this.model.deletePresetItems(this.state.selectedItems)
|
||||
.then((currentItem: number) => {
|
||||
this.setSelection(currentItem);
|
||||
})
|
||||
.catch((error) => {
|
||||
this.model.showAlert(error);
|
||||
});
|
||||
}
|
||||
handleDialogClose() {
|
||||
this.props.onDialogClose();
|
||||
}
|
||||
|
||||
handleItemClick(instanceId: number): void {
|
||||
if (this.isEditMode()) {
|
||||
this.setState({ selectedItem: instanceId });
|
||||
handleItemClick(e: React.MouseEvent<HTMLElement>, instanceId: number): void {
|
||||
e.stopPropagation();
|
||||
if (e.ctrlKey || this.state.showTouchActionBar) {
|
||||
let selectedItems = new Set<number>(this.state.selectedItems);
|
||||
if (selectedItems.has(instanceId)) {
|
||||
selectedItems.delete(instanceId);
|
||||
this.setSelections(selectedItems);
|
||||
} else {
|
||||
selectedItems.add(instanceId);
|
||||
this.setState({
|
||||
selectedItems: selectedItems,
|
||||
currentItem: instanceId
|
||||
});
|
||||
this.handleSelectionUpdated(selectedItems);
|
||||
}
|
||||
} else if (e.shiftKey) {
|
||||
let presets = this.state.presets;
|
||||
let startIndex = this.getSelectedIndex();
|
||||
let endIndex = this.getSelectedIndex(instanceId);
|
||||
if (startIndex === -1 || endIndex === -1) return; // should not happen.
|
||||
|
||||
let selectedItems = new Set<number>();
|
||||
|
||||
if (endIndex < startIndex) {
|
||||
let t = startIndex;
|
||||
startIndex = endIndex;
|
||||
endIndex = t;
|
||||
}
|
||||
for (let i = startIndex; i <= endIndex; ++i) {
|
||||
selectedItems.add(presets.presets[i].instanceId);
|
||||
}
|
||||
this.setSelections(selectedItems);
|
||||
} else {
|
||||
this.model.loadPreset(instanceId);
|
||||
this.props.onDialogClose();
|
||||
this.setSelection(instanceId);
|
||||
}
|
||||
}
|
||||
showActionBar(show: boolean): void {
|
||||
this.setState({ showActionBar: show });
|
||||
showTouchActionBar(show: boolean): void {
|
||||
this.setState({ showTouchActionBar: show });
|
||||
|
||||
}
|
||||
|
||||
@@ -251,27 +319,23 @@ const PresetDialog = withStyles(
|
||||
mapElement(el: any): React.ReactNode {
|
||||
let presetEntry = el as PresetIndexEntry;
|
||||
const classes = withStyles.getClasses(this.props);
|
||||
let selectedItem = this.isEditMode() ? this.state.selectedItem : this.state.presets.selectedInstanceId;
|
||||
let selected = this.state.selectedItems.has(presetEntry.instanceId);
|
||||
return (
|
||||
<div key={presetEntry.instanceId} className="itemBackground">
|
||||
|
||||
<ButtonBase style={{ width: "100%", height: 48 }}
|
||||
onClick={() => this.handleItemClick(presetEntry.instanceId)}
|
||||
<div key={presetEntry.instanceId} id={"psetdlgItem_" + presetEntry.instanceId} className="itemBackground">
|
||||
<ListItemButton
|
||||
selected={selected}
|
||||
onClick={(e) => this.handleItemClick(e, presetEntry.instanceId)}
|
||||
style={{ height: 48 }}
|
||||
>
|
||||
<SelectHoverBackground selected={presetEntry.instanceId === selectedItem} showHover={true} />
|
||||
<div className={classes.itemFrame}>
|
||||
<div className={classes.iconFrame}>
|
||||
<img
|
||||
src={isDarkMode()? "img/ic_presets_white.svg": "img/ic_presets.svg"}
|
||||
className={classes.itemIcon} alt="" />
|
||||
</div>
|
||||
<div className={classes.itemLabel}>
|
||||
<Typography>
|
||||
{presetEntry.name}
|
||||
</Typography>
|
||||
</div>
|
||||
</div>
|
||||
</ButtonBase>
|
||||
<ListItemIcon>
|
||||
<img
|
||||
src={isDarkMode() ? "img/ic_presets_white.svg" : "img/ic_presets.svg"}
|
||||
className={classes.itemIcon} alt="" />
|
||||
</ListItemIcon>
|
||||
<ListItemText primary={presetEntry.name}
|
||||
/>
|
||||
|
||||
</ListItemButton>
|
||||
</div>
|
||||
|
||||
);
|
||||
@@ -287,30 +351,33 @@ const PresetDialog = withStyles(
|
||||
moveElement(from: number, to: number): void {
|
||||
let newPresets = this.state.presets.clone();
|
||||
newPresets.movePreset(from, to);
|
||||
let toInstanceId = newPresets.presets[to].instanceId;
|
||||
this.setState({
|
||||
presets: newPresets,
|
||||
selectedItem: newPresets.presets[to].instanceId
|
||||
currentItem: toInstanceId,
|
||||
selectedItems: new Set<number>([toInstanceId])
|
||||
|
||||
});
|
||||
this.updateServerPresets(newPresets);
|
||||
}
|
||||
|
||||
getSelectedName(): string {
|
||||
let item = this.state.presets.getItem(this.state.selectedItem);
|
||||
let item = this.state.presets.getItem(this.state.currentItem);
|
||||
if (item) return item.name;
|
||||
return "";
|
||||
}
|
||||
|
||||
handleRenameClick() {
|
||||
let item = this.state.presets.getItem(this.state.selectedItem);
|
||||
let item = this.state.presets.getItem(this.state.currentItem);
|
||||
if (item) {
|
||||
this.setState({ renameOpen: true });
|
||||
}
|
||||
}
|
||||
handleRenameOk(text: string) {
|
||||
let item = this.state.presets.getItem(this.state.selectedItem);
|
||||
let item = this.state.presets.getItem(this.state.currentItem);
|
||||
if (!item) return;
|
||||
if (item.name !== text) {
|
||||
this.model.renamePresetItem(this.state.selectedItem, text)
|
||||
this.model.renamePresetItem(this.state.currentItem, text)
|
||||
.catch((error) => {
|
||||
this.onError(error);
|
||||
});
|
||||
@@ -319,11 +386,14 @@ const PresetDialog = withStyles(
|
||||
this.setState({ renameOpen: false });
|
||||
}
|
||||
handleCopy() {
|
||||
let item = this.state.presets.getItem(this.state.selectedItem);
|
||||
let item = this.state.presets.getItem(this.state.currentItem);
|
||||
if (!item) return;
|
||||
this.model.duplicatePreset(this.state.selectedItem)
|
||||
this.model.duplicatePreset(this.state.currentItem)
|
||||
.then((newId) => {
|
||||
this.setState({ selectedItem: newId });
|
||||
this.setState({
|
||||
currentItem: newId,
|
||||
selectedItems: new Set<number>([newId])
|
||||
});
|
||||
}).catch((error) => {
|
||||
this.onError(error);
|
||||
});
|
||||
@@ -333,81 +403,144 @@ const PresetDialog = withStyles(
|
||||
this.model?.showAlert(error);
|
||||
}
|
||||
|
||||
handleCloseActionBar(e: SyntheticEvent) {
|
||||
e.stopPropagation();
|
||||
this.setState({
|
||||
showTouchActionBar: false,
|
||||
selectedItems: new Set<number>([this.state.currentItem]),
|
||||
multiSelect: false
|
||||
});
|
||||
}
|
||||
handleImportPresetsFromBank() {
|
||||
this.handleMoreClose();
|
||||
this.setState({ importOpen: true });
|
||||
}
|
||||
handleImportDialogOk(bankInstanceId: number, presets: number[]): void {
|
||||
this.setState({ importOpen: false });
|
||||
this.model.importPresetsFromBank(bankInstanceId, presets)
|
||||
.then((instanceId) => {
|
||||
if (instanceId !== -1) {
|
||||
this.setSelection(instanceId);
|
||||
setTimeout(() => {
|
||||
let el = document.getElementById("psetdlgItem_" + instanceId);
|
||||
if (el) {
|
||||
el.scrollIntoView({ behavior: "smooth", block: "center" });
|
||||
}
|
||||
}, 0);
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
this.model.showAlert(error);
|
||||
});
|
||||
}
|
||||
handleCopyToBankDialogOk(bankInstanceId: number): void {
|
||||
this.setState({ copyToOpen: false });
|
||||
let selectedItems: number[] = [];
|
||||
for (let i = 0; i < this.state.presets.presets.length; ++i) {
|
||||
let preset = this.state.presets.presets[i];
|
||||
if (this.state.selectedItems.has(preset.instanceId)) {
|
||||
selectedItems.push(preset.instanceId);
|
||||
}
|
||||
}
|
||||
|
||||
this.model.copyPresetsToBank(bankInstanceId, selectedItems)
|
||||
.then((instanceId) => {
|
||||
})
|
||||
.catch((error) => {
|
||||
this.model.showAlert(error);
|
||||
});
|
||||
}
|
||||
|
||||
handleCopyPresetsToBank() {
|
||||
this.handleMoreClose();
|
||||
this.setState({ copyToOpen: true });
|
||||
}
|
||||
|
||||
render() {
|
||||
const classes = withStyles.getClasses(this.props);
|
||||
|
||||
let actionBarClass = this.props.isEditDialog ? classes.dialogAppBar : classes.dialogActionBar;
|
||||
let defaultSelectedIndex = this.getSelectedIndex();
|
||||
|
||||
const showActionBar = this.state.multiSelect || this.state.showTouchActionBar;
|
||||
return (
|
||||
<DialogEx tag="preset" fullScreen open={this.props.show}
|
||||
onClose={() => { this.handleDialogClose() }} TransitionComponent={Transition}
|
||||
style={{userSelect: "none"}}
|
||||
onEnterKey={()=>{}}
|
||||
>
|
||||
style={{ userSelect: "none" }}
|
||||
onEnterKey={() => { }}
|
||||
>
|
||||
<div style={{ display: "flex", flexDirection: "column", flexWrap: "nowrap", width: "100%", height: "100%", overflow: "hidden" }}>
|
||||
<div style={{ flex: "0 0 auto" }}>
|
||||
<AppBar className={classes.dialogAppBar} style={{ display: this.isEditMode() ? "none" : "block" }} >
|
||||
<AppBar className={classes.dialogActionBar} style={{ display: showActionBar ? "block" : "none" }}
|
||||
>
|
||||
<Toolbar>
|
||||
<IconButtonEx tooltip="Back" edge="start" color="inherit" onClick={this.handleDialogClose} aria-label="back"
|
||||
disabled={this.isEditMode()}
|
||||
<IconButtonEx tooltip="Back" edge="start" color="inherit" aria-label="back"
|
||||
onClick={(e) => { this.handleCloseActionBar(e); }}
|
||||
>
|
||||
<ArrowBackIcon />
|
||||
<CloseIcon />
|
||||
</IconButtonEx>
|
||||
<Typography variant="h6" className={classes.dialogTitle}>
|
||||
Presets
|
||||
<Typography variant="h6" className={classes.dialogTitle} noWrap>
|
||||
{this.state.selectedItems.size.toString() + " items selected"}
|
||||
</Typography>
|
||||
<IconButtonEx tooltip="Edit"color="inherit" onClick={(e) => this.showActionBar(true)} >
|
||||
<EditIcon />
|
||||
<Button color="inherit"
|
||||
onClick={() => {
|
||||
this.handleCopyPresetsToBank();
|
||||
}} >
|
||||
Copy to
|
||||
</Button>
|
||||
<IconButtonEx tooltip="Delete" color="inherit" onClick={(e) => { this.handleDeleteClick(); }} >
|
||||
<img src="/img/old_delete_outline_white_24dp.svg" alt="Delete" style={{ width: 24, height: 24, opacity: 0.6 }} />
|
||||
</IconButtonEx>
|
||||
</Toolbar>
|
||||
</AppBar>
|
||||
<AppBar className={actionBarClass} style={{ display: this.isEditMode() ? "block" : "none" }}
|
||||
<AppBar className={classes.dialogAppBar} style={{ display: showActionBar ? "none" : "block" }}
|
||||
onClick={(e) => { e.stopPropagation(); e.preventDefault(); }}
|
||||
>
|
||||
<Toolbar>
|
||||
{(!this.props.isEditDialog) ? (
|
||||
<IconButtonEx tooltip="Close" edge="start" color="inherit" onClick={(e) => this.showActionBar(false)} aria-label="close">
|
||||
<CloseIcon />
|
||||
</IconButtonEx>
|
||||
) : (
|
||||
<IconButtonEx edge="start" tooltip="Back" color="inherit" onClick={this.handleDialogClose} aria-label="back"
|
||||
>
|
||||
<ArrowBackIcon />
|
||||
</IconButtonEx>
|
||||
|
||||
)}
|
||||
<Typography variant="h6" className={classes.dialogTitle}>
|
||||
<IconButtonEx edge="start" tooltip="Back" color="inherit" onClick={this.handleDialogClose} aria-label="back"
|
||||
>
|
||||
<ArrowBackIcon />
|
||||
</IconButtonEx>
|
||||
<Typography variant="h6" className={classes.dialogTitle} noWrap>
|
||||
Presets
|
||||
</Typography>
|
||||
{(this.state.presets.getItem(this.state.selectedItem) != null)
|
||||
{(this.state.presets.getItem(this.state.currentItem) != null)
|
||||
&& (
|
||||
|
||||
<div style={{ flex: "0 0 auto", display: "flex", flexFlow: "row nowrap", alignItems: "center"
|
||||
|
||||
}}>
|
||||
<Button color="inherit" onClick={(e) => this.handleCopy()}>
|
||||
Copy
|
||||
</Button>
|
||||
<Button color="inherit" onClick={() => this.handleRenameClick()}>
|
||||
Rename
|
||||
</Button>
|
||||
<RenameDialog
|
||||
title="Rename"
|
||||
open={this.state.renameOpen}
|
||||
defaultName={this.getSelectedName()}
|
||||
acceptActionName={"Rename"}
|
||||
onClose={() => { this.setState({ renameOpen: false }) }}
|
||||
onOk={(text: string) => {
|
||||
this.handleRenameOk(text);
|
||||
}
|
||||
}
|
||||
/>
|
||||
<IconButtonEx tooltip="Delete" color="inherit" onClick={(e) => this.handleDeleteClick()} >
|
||||
<img src="/img/old_delete_outline_white_24dp.svg" alt="Delete" style={{ width: 24, height: 24, opacity: 0.6 }} />
|
||||
</IconButtonEx>
|
||||
<div style={{
|
||||
flex: "0 0 auto", display: "flex", flexFlow: "row nowrap", alignItems: "center"
|
||||
|
||||
}}>
|
||||
{this.state.selectedItems.size === 1 && (
|
||||
<Button color="inherit" onClick={(e) => this.handleCopy()}
|
||||
>
|
||||
Copy
|
||||
</Button>
|
||||
)}
|
||||
{this.state.selectedItems.size === 1 && (
|
||||
|
||||
<Button color="inherit" onClick={() => this.handleRenameClick()}
|
||||
>
|
||||
Rename
|
||||
</Button>
|
||||
)}
|
||||
{this.state.renameOpen && (
|
||||
<RenameDialog
|
||||
title="Rename"
|
||||
open={this.state.renameOpen}
|
||||
defaultName={this.getSelectedName()}
|
||||
acceptActionName={"Rename"}
|
||||
onClose={() => { this.setState({ renameOpen: false }) }}
|
||||
onOk={(text: string) => {
|
||||
this.handleRenameOk(text);
|
||||
}
|
||||
}
|
||||
/>
|
||||
)}
|
||||
{this.state.selectedItems.size === 1 && (
|
||||
<IconButtonEx tooltip="Delete" color="inherit" onClick={(e) => this.handleDeleteClick()}
|
||||
style={{ opacity: this.state.selectedItems.size !== 1 ? 0.5 : 1.0 }}
|
||||
>
|
||||
<img src="/img/old_delete_outline_white_24dp.svg" alt="Delete" style={{ width: 24, height: 24, opacity: 0.6 }} />
|
||||
</IconButtonEx>
|
||||
)}
|
||||
<IconButtonEx tooltip="More..." color="inherit" onClick={(e) => { this.onMoreClick(e) }} >
|
||||
<MoreVertIcon />
|
||||
</IconButtonEx>
|
||||
@@ -419,15 +552,43 @@ const PresetDialog = withStyles(
|
||||
onClose={() => this.handleMoreClose()}
|
||||
TransitionComponent={Fade}
|
||||
>
|
||||
<MenuItem onClick={() => { this.handleDownloadPreset(); }} >
|
||||
{this.state.selectedItems.size !== 0 && (
|
||||
<MenuItem onClick={() => {
|
||||
this.handleMoreClose();
|
||||
this.handleCopyPresetsToBank();
|
||||
}}
|
||||
>
|
||||
<ListItemIcon>
|
||||
<Icon />
|
||||
</ListItemIcon>
|
||||
<ListItemText>
|
||||
Copy to bank...
|
||||
</ListItemText>
|
||||
|
||||
</MenuItem>
|
||||
)}
|
||||
<MenuItem onClick={() => { this.handleImportPresetsFromBank(); }} >
|
||||
<ListItemIcon>
|
||||
<DownloadIcon className={classes.listIcon} />
|
||||
<Icon />
|
||||
</ListItemIcon>
|
||||
<ListItemText>
|
||||
Download preset
|
||||
Import presets from bank...
|
||||
</ListItemText>
|
||||
|
||||
</MenuItem>
|
||||
|
||||
<Divider />
|
||||
{this.state.selectedItems.size !== 0 && (
|
||||
<MenuItem onClick={() => { this.handleDownloadPreset(); }} >
|
||||
<ListItemIcon>
|
||||
<DownloadIcon className={classes.listIcon} />
|
||||
</ListItemIcon>
|
||||
<ListItemText>
|
||||
Download preset
|
||||
</ListItemText>
|
||||
|
||||
</MenuItem>
|
||||
)}
|
||||
<MenuItem onClick={() => { this.handleUploadPreset() }}>
|
||||
<ListItemIcon>
|
||||
<UploadIcon className={classes.listIcon} />
|
||||
@@ -449,12 +610,16 @@ const PresetDialog = withStyles(
|
||||
</div>
|
||||
<div style={{ flex: "1 1 auto", position: "relative", overflow: "hidden" }} >
|
||||
<DraggableGrid
|
||||
onLongPress={(item) => this.showActionBar(true)}
|
||||
canDrag={this.isEditMode()}
|
||||
onLongPress={(e,item) => {
|
||||
if (e.pointerType === "touch")
|
||||
{
|
||||
this.showTouchActionBar(true);
|
||||
}
|
||||
}}
|
||||
canDrag={!this.state.multiSelect && !this.state.showTouchActionBar}
|
||||
onDragStart={(index, x, y) => { this.selectItemAtIndex(index) }}
|
||||
moveElement={(from, to) => { this.moveElement(from, to); }}
|
||||
scroll={ScrollDirection.Y}
|
||||
defaultSelectedIndex={defaultSelectedIndex}
|
||||
>
|
||||
{
|
||||
this.state.presets.presets.map((element) => {
|
||||
@@ -464,14 +629,28 @@ const PresetDialog = withStyles(
|
||||
</DraggableGrid>
|
||||
</div>
|
||||
</div>
|
||||
<UploadPresetDialog
|
||||
title='Upload preset'
|
||||
extension='.piPreset'
|
||||
uploadPage='uploadPreset'
|
||||
onUploaded={(instanceId) => this.setState({selectedItem: instanceId}) }
|
||||
uploadAfter={this.state.selectedItem}
|
||||
open={this.state.openUploadPresetDialog}
|
||||
onClose={() => { this.setState({ openUploadPresetDialog: false }) }} />
|
||||
<UploadPresetDialog
|
||||
title='Upload preset'
|
||||
extension='.piPreset'
|
||||
uploadPage='uploadPreset'
|
||||
onUploaded={(instanceId) => this.setSelection(instanceId)}
|
||||
uploadAfter={this.state.currentItem}
|
||||
open={this.state.openUploadPresetDialog}
|
||||
onClose={() => { this.setState({ openUploadPresetDialog: false }) }} />
|
||||
{this.state.importOpen && (
|
||||
<ImportPresetFromBankDialog
|
||||
open={this.state.importOpen}
|
||||
onClose={() => this.setState({ importOpen: false })}
|
||||
onOk={(bankInstanceId, presets) => this.handleImportDialogOk(bankInstanceId, presets)}
|
||||
/>
|
||||
)}
|
||||
{this.state.copyToOpen && (
|
||||
<CopyPresetsToBankDialog
|
||||
open={this.state.copyToOpen}
|
||||
onClose={() => this.setState({ copyToOpen: false })}
|
||||
onOk={(bankInstanceId) => this.handleCopyToBankDialogOk(bankInstanceId)}
|
||||
/>
|
||||
)}
|
||||
|
||||
</DialogEx>
|
||||
|
||||
|
||||
@@ -17,7 +17,8 @@
|
||||
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
import { SyntheticEvent, Component } from 'react';
|
||||
import { SyntheticEvent } from 'react';
|
||||
import ArrowRightIcon from '@mui/icons-material/ArrowRight';
|
||||
import IconButtonEx from './IconButtonEx';
|
||||
import { PiPedalModel, PiPedalModelFactory, PresetIndex } from './PiPedalModel';
|
||||
import SaveIconOutline from '@mui/icons-material/Save';
|
||||
@@ -25,7 +26,7 @@ import MoreVertIcon from '@mui/icons-material/MoreVert';
|
||||
import { Theme } from '@mui/material/styles';
|
||||
import WithStyles from './WithStyles';
|
||||
import { withStyles } from "tss-react/mui";
|
||||
import {createStyles} from './WithStyles';
|
||||
import { createStyles } from './WithStyles';
|
||||
|
||||
import PresetDialog from './PresetDialog';
|
||||
import Menu from '@mui/material/Menu';
|
||||
@@ -33,9 +34,13 @@ import MenuItem from '@mui/material/MenuItem';
|
||||
import Fade from '@mui/material/Fade';
|
||||
import Divider from '@mui/material/Divider';
|
||||
import RenameDialog from './RenameDialog'
|
||||
import SavePresetAsDialog from './SavePresetAsDialog';
|
||||
import ImportPresetFromBankDialog from './ImportPresetFromBankDialog';
|
||||
|
||||
import Select from '@mui/material/Select';
|
||||
import UploadPresetDialog from './UploadPresetDialog';
|
||||
import {isDarkMode} from './DarkMode';
|
||||
import { isDarkMode } from './DarkMode';
|
||||
import ResizeResponsiveComponent from './ResizeResponsiveComponent';
|
||||
|
||||
interface PresetSelectorProps extends WithStyles<typeof styles> {
|
||||
|
||||
@@ -48,7 +53,12 @@ interface PresetSelectorState {
|
||||
showPresetsDialog: boolean;
|
||||
showEditPresetsDialog: boolean;
|
||||
presetsMenuAnchorRef: HTMLElement | null;
|
||||
presetsSubmenuAnchorRef: HTMLElement | null;
|
||||
compactHorizontalLayoutMenu: boolean;
|
||||
|
||||
saveAsDialogOpen: boolean;
|
||||
importDialogOpen: boolean;
|
||||
|
||||
renameDialogOpen: boolean;
|
||||
renameDialogTitle: string;
|
||||
renameDialogDefaultName: string;
|
||||
@@ -59,7 +69,7 @@ interface PresetSelectorState {
|
||||
}
|
||||
|
||||
|
||||
const selectColor = isDarkMode()? "#888": "#FFFFFF";
|
||||
const selectColor = isDarkMode() ? "#888" : "#FFFFFF";
|
||||
|
||||
const styles = (theme: Theme) => createStyles({
|
||||
select: { // fu fu fu.Overrides for white selector on dark background.
|
||||
@@ -81,8 +91,8 @@ const styles = (theme: Theme) => createStyles({
|
||||
|
||||
const PresetSelector =
|
||||
withStyles(
|
||||
class extends Component<PresetSelectorProps, PresetSelectorState> {
|
||||
|
||||
class extends ResizeResponsiveComponent<PresetSelectorProps, PresetSelectorState> {
|
||||
private MENU_THRESHOLD: number = 570;
|
||||
model: PiPedalModel;
|
||||
|
||||
constructor(props: PresetSelectorProps) {
|
||||
@@ -92,10 +102,14 @@ const PresetSelector =
|
||||
presets:
|
||||
this.model.presets.get(),
|
||||
enabled: false,
|
||||
compactHorizontalLayoutMenu: this.windowSize.height < this.MENU_THRESHOLD,
|
||||
showPresetsDialog: false,
|
||||
showEditPresetsDialog: false,
|
||||
presetsMenuAnchorRef: null,
|
||||
presetsSubmenuAnchorRef: null,
|
||||
renameDialogOpen: false,
|
||||
saveAsDialogOpen: false,
|
||||
importDialogOpen: false,
|
||||
renameDialogTitle: "",
|
||||
renameDialogDefaultName: "",
|
||||
renameDialogActionName: "",
|
||||
@@ -109,12 +123,34 @@ const PresetSelector =
|
||||
this.handlePresetsMenuClose = this.handlePresetsMenuClose.bind(this);
|
||||
}
|
||||
|
||||
onWindowSizeChanged(width: number, height: number): void {
|
||||
super.onWindowSizeChanged(width,height);
|
||||
this.setState({ compactHorizontalLayoutMenu: height < this.MENU_THRESHOLD });
|
||||
}
|
||||
|
||||
handlePresetMenuClick(event: SyntheticEvent): void {
|
||||
handlePresetsMenuClick(event: SyntheticEvent): void {
|
||||
this.setState({ presetsMenuAnchorRef: (event.currentTarget as HTMLElement) });
|
||||
}
|
||||
handlePresetsSubmenuClick(event: SyntheticEvent): void {
|
||||
this.setState({ presetsSubmenuAnchorRef: (event.currentTarget as HTMLElement) });
|
||||
}
|
||||
handlePresetsMenuClose(): void {
|
||||
this.setState({ presetsMenuAnchorRef: null });
|
||||
this.setState({
|
||||
presetsMenuAnchorRef: null,
|
||||
presetsSubmenuAnchorRef: null,
|
||||
});
|
||||
}
|
||||
handlePresetsSubmenuClose(): void {
|
||||
this.setState({
|
||||
presetsSubmenuAnchorRef: null,
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
handlePresetsMenuImport(e: SyntheticEvent): void {
|
||||
this.handlePresetsMenuClose();
|
||||
e.stopPropagation();
|
||||
this.setState({ importDialogOpen: true });
|
||||
}
|
||||
|
||||
handleDownloadPreset(e: SyntheticEvent) {
|
||||
@@ -144,21 +180,8 @@ const PresetSelector =
|
||||
let currentPresets = this.model.presets.get();
|
||||
let item = currentPresets.getItem(currentPresets.selectedInstanceId);
|
||||
if (item == null) return;
|
||||
let name = item.name;
|
||||
|
||||
this.renameDialogOpen(name, "Save Preset As", "OK")
|
||||
.then((newName) => {
|
||||
return this.model.saveCurrentPresetAs(newName);
|
||||
})
|
||||
.then((newInstanceId) => {
|
||||
|
||||
})
|
||||
.catch((error) => {
|
||||
this.showError(error);
|
||||
})
|
||||
;
|
||||
|
||||
|
||||
this.setState({ saveAsDialogOpen: true });
|
||||
}
|
||||
handlePresetsMenuRename(e: SyntheticEvent): void {
|
||||
this.handlePresetsMenuClose();
|
||||
@@ -198,7 +221,7 @@ const PresetSelector =
|
||||
showError(error: string) {
|
||||
this.model.showAlert(error);
|
||||
}
|
||||
renameDialogOpen(defaultText: string, title: string,acceptButtonText: string): Promise<string> {
|
||||
renameDialogOpen(defaultText: string, title: string, acceptButtonText: string): Promise<string> {
|
||||
let result = new Promise<string>(
|
||||
(resolve, reject) => {
|
||||
this.setState(
|
||||
@@ -219,6 +242,30 @@ const PresetSelector =
|
||||
return result;
|
||||
}
|
||||
|
||||
handleSaveAsDialogOk(bankInstanceId: number, name: string): void {
|
||||
this.setState({ saveAsDialogOpen: false });
|
||||
|
||||
this.model.saveCurrentPresetAs(bankInstanceId, name)
|
||||
.then((instanceId) => {
|
||||
this.model.loadPreset(instanceId);
|
||||
})
|
||||
.catch((error) => {
|
||||
this.showError(error);
|
||||
});
|
||||
}
|
||||
handleImportDialogOk(bankInstanceId: number, presets: number[]): void {
|
||||
this.setState({ importDialogOpen: false });
|
||||
this.model.importPresetsFromBank(bankInstanceId, presets)
|
||||
.then((instanceId) => {
|
||||
if (instanceId !== -1) {
|
||||
this.model.loadPreset(instanceId);
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
this.showError(error);
|
||||
});
|
||||
}
|
||||
|
||||
handleRenameDialogClose(): void {
|
||||
this.setState({
|
||||
renameDialogOpen: false,
|
||||
@@ -259,11 +306,13 @@ const PresetSelector =
|
||||
this.updatePresetState();
|
||||
}
|
||||
componentDidMount() {
|
||||
super.componentDidMount();
|
||||
this.model.presets.addOnChangedHandler(this.handlePresetsChanged);
|
||||
this.updatePresetState();
|
||||
}
|
||||
componentWillUnmount() {
|
||||
this.model.presets.removeOnChangedHandler(this.handlePresetsChanged)
|
||||
super.componentWillUnmount();
|
||||
}
|
||||
showPresetDialog(show: boolean) {
|
||||
this.setState({
|
||||
@@ -318,7 +367,7 @@ const PresetSelector =
|
||||
onChange={(e, extra) => this.handleChange(e, extra)}
|
||||
onClose={(e) => this.handleSelectClose(e)}
|
||||
displayEmpty
|
||||
value={presets.selectedInstanceId === 0? '' : presets.selectedInstanceId}
|
||||
value={presets.selectedInstanceId === 0 ? '' : presets.selectedInstanceId}
|
||||
inputProps={{
|
||||
classes: { icon: classes.icon },
|
||||
'aria-label': "Select preset"
|
||||
@@ -339,13 +388,13 @@ const PresetSelector =
|
||||
}
|
||||
</Select>
|
||||
</div>
|
||||
<div style={{ flex: "0 0 auto"}}>
|
||||
<div style={{ flex: "0 0 auto" }}>
|
||||
<IconButtonEx
|
||||
tooltip="More..."
|
||||
style={{ flex: "0 0 auto", color: "#FFFFFF" }}
|
||||
onClick={(e) => this.handlePresetMenuClick(e)}
|
||||
style={{ flex: "0 0 auto", color: "#FFFFFF" }}
|
||||
onClick={(e) => this.handlePresetsMenuClick(e)}
|
||||
size="large"
|
||||
>
|
||||
>
|
||||
<MoreVertIcon style={{ opacity: 0.75 }} color="inherit" />
|
||||
</IconButtonEx>
|
||||
<Menu
|
||||
@@ -356,9 +405,24 @@ const PresetSelector =
|
||||
TransitionComponent={Fade}
|
||||
>
|
||||
<MenuItem onClick={(e) => this.handlePresetsMenuSave(e)}>Save preset</MenuItem>
|
||||
<MenuItem onClick={(e) => this.handlePresetsMenuSaveAs(e)}>Save preset as...</MenuItem>
|
||||
<MenuItem onClick={(e) => this.handlePresetsMenuRename(e)}>Rename...</MenuItem>
|
||||
<MenuItem onClick={(e) => this.handlePresetsMenuNew(e)}>New...</MenuItem>
|
||||
|
||||
{this.state.compactHorizontalLayoutMenu ? (
|
||||
<MenuItem key="a" onClick={(e) => this.handlePresetsSubmenuClick(e)}
|
||||
style={{ display: 'flex', flexFlow: "row nowrap", alignItems: 'center', justifyContent: 'space-between' }}
|
||||
>
|
||||
Edit
|
||||
<ArrowRightIcon/>
|
||||
</MenuItem>
|
||||
)
|
||||
:(
|
||||
[
|
||||
(<MenuItem key="b" onClick={(e) => this.handlePresetsMenuSaveAs(e)}>Save preset as...</MenuItem>),
|
||||
(<MenuItem key="c" onClick={(e) => this.handlePresetsMenuRename(e)}>Rename...</MenuItem>),
|
||||
(<MenuItem key="d" onClick={(e) => this.handlePresetsMenuImport(e)}>Import from bank...</MenuItem>),
|
||||
(<MenuItem key="e" onClick={(e) => this.handlePresetsMenuNew(e)}>New...</MenuItem>),
|
||||
]
|
||||
|
||||
)}
|
||||
<Divider />
|
||||
<MenuItem onClick={(e) => { this.handleDownloadPreset(e); }} >Download preset</MenuItem>
|
||||
<MenuItem onClick={(e) => { this.handleUploadPreset(e) }}>Upload preset</MenuItem>
|
||||
@@ -366,7 +430,39 @@ const PresetSelector =
|
||||
<MenuItem onClick={(e) => this.handleMenuEditPresets()}>Manage presets...</MenuItem>
|
||||
</Menu>
|
||||
</div>
|
||||
<PresetDialog show={this.state.showPresetsDialog} isEditDialog={this.state.showEditPresetsDialog} onDialogClose={() => this.handleDialogClose()} />
|
||||
|
||||
{/* Submenu */}
|
||||
<Menu key="b" onClose={() => this.handlePresetsSubmenuClose()}
|
||||
anchorOrigin={{ vertical: 'top', horizontal: 'right' }}
|
||||
anchorPosition={{left: 48, top: 0}}
|
||||
anchorEl={this.state.presetsSubmenuAnchorRef}
|
||||
open={this.state.presetsSubmenuAnchorRef !== null} >
|
||||
<MenuItem onClick={(e) => this.handlePresetsMenuSaveAs(e)}>Save preset as...</MenuItem>
|
||||
<MenuItem onClick={(e) => this.handlePresetsMenuRename(e)}>Rename...</MenuItem>
|
||||
<MenuItem onClick={(e) => this.handlePresetsMenuImport(e)}>Import from bank...</MenuItem>
|
||||
<MenuItem onClick={(e) => this.handlePresetsMenuNew(e)}>New...</MenuItem>
|
||||
</Menu>
|
||||
|
||||
|
||||
{this.state.showPresetsDialog&& (
|
||||
<PresetDialog show={this.state.showPresetsDialog} onDialogClose={() => this.handleDialogClose()}
|
||||
/>
|
||||
)}
|
||||
{this.state.saveAsDialogOpen && (
|
||||
<SavePresetAsDialog open={this.state.saveAsDialogOpen}
|
||||
defaultName={presets.getItem(presets.selectedInstanceId)?.name ?? "My Preset"}
|
||||
onClose={() => { this.setState({ saveAsDialogOpen: false }) }}
|
||||
onOk={(bankInstanceId, name) => {
|
||||
this.handleSaveAsDialogOk(bankInstanceId, name);
|
||||
}} />
|
||||
)}
|
||||
{this.state.importDialogOpen && (
|
||||
<ImportPresetFromBankDialog open={this.state.importDialogOpen}
|
||||
onClose={() => { this.setState({ importDialogOpen: false }) }}
|
||||
onOk={(bankInstanceId, presets) => {
|
||||
this.handleImportDialogOk(bankInstanceId, presets);
|
||||
}} />
|
||||
)}
|
||||
<RenameDialog open={this.state.renameDialogOpen}
|
||||
title={this.state.renameDialogTitle}
|
||||
defaultName={this.state.renameDialogDefaultName}
|
||||
@@ -387,6 +483,6 @@ const PresetSelector =
|
||||
|
||||
}
|
||||
},
|
||||
styles);
|
||||
styles);
|
||||
|
||||
export default PresetSelector;
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) 2022 Robin Davies
|
||||
// Copyright (c) Robin E. R. Davies
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
// this software and associated documentation files (the "Software"), to deal in
|
||||
@@ -136,7 +136,16 @@ export default class RenameDialog extends ResizeResponsiveComponent<RenameDialog
|
||||
autoFocus={!isTouchUi()}
|
||||
onKeyDown={handleKeyDown}
|
||||
variant="standard"
|
||||
autoComplete="off"
|
||||
autoCorrect="on"
|
||||
autoCapitalize="on"
|
||||
spellCheck={false}
|
||||
|
||||
slotProps={{
|
||||
inputLabel: {
|
||||
shrink: true
|
||||
},
|
||||
|
||||
input: {
|
||||
style: { scrollMargin: 24 }
|
||||
}
|
||||
|
||||
@@ -0,0 +1,189 @@
|
||||
// Copyright (c) Robin E. R. Davies
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
// this software and associated documentation files (the "Software"), to deal in
|
||||
// the Software without restriction, including without limitation the rights to
|
||||
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
// the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
// subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
import React from 'react';
|
||||
import Select from '@mui/material/Select';
|
||||
import Button from '@mui/material/Button';
|
||||
import InputLabel from '@mui/material/InputLabel';
|
||||
import DialogEx from './DialogEx';
|
||||
import DialogTitle from '@mui/material/DialogTitle';
|
||||
import MenuItem from '@mui/material/MenuItem';
|
||||
import DialogActions from '@mui/material/DialogActions';
|
||||
import DialogContent from '@mui/material/DialogContent';
|
||||
import { nullCast } from './Utility';
|
||||
import ResizeResponsiveComponent from './ResizeResponsiveComponent';
|
||||
import {PiPedalModel,PiPedalModelFactory} from './PiPedalModel';
|
||||
//import TextFieldEx from './TextFieldEx';
|
||||
import TextField from '@mui/material/TextField';
|
||||
import { BankIndex } from './Banks';
|
||||
|
||||
|
||||
export interface SavePresetAsDialogProps {
|
||||
open: boolean,
|
||||
defaultName: string,
|
||||
onOk: (bankInstanceId: number, text: string) => void,
|
||||
onClose: () => void
|
||||
};
|
||||
|
||||
export interface SavePresetAsDialogState {
|
||||
selectedBank: number;
|
||||
banks: BankIndex;
|
||||
|
||||
fullScreen: boolean;
|
||||
};
|
||||
|
||||
function isTouchUi()
|
||||
{
|
||||
return 'ontouchstart' in window || navigator.maxTouchPoints > 0;
|
||||
}
|
||||
export default class SavePresetAsDialog extends ResizeResponsiveComponent<SavePresetAsDialogProps, SavePresetAsDialogState> {
|
||||
|
||||
refText: React.RefObject<HTMLInputElement|null>;
|
||||
|
||||
model: PiPedalModel;
|
||||
constructor(props: SavePresetAsDialogProps) {
|
||||
super(props);
|
||||
this.model = PiPedalModel.getInstance();
|
||||
this.state = {
|
||||
banks: this.model.banks.get(),
|
||||
selectedBank: this.model.banks.get().selectedBank,
|
||||
fullScreen: false
|
||||
|
||||
};
|
||||
this.refText = React.createRef<HTMLInputElement>();
|
||||
}
|
||||
mounted: boolean = false;
|
||||
|
||||
|
||||
|
||||
onWindowSizeChanged(width: number, height: number): void
|
||||
{
|
||||
this.setState({fullScreen: height < 200})
|
||||
}
|
||||
|
||||
|
||||
componentDidMount()
|
||||
{
|
||||
super.componentDidMount();
|
||||
this.mounted = true;
|
||||
}
|
||||
componentWillUnmount()
|
||||
{
|
||||
super.componentWillUnmount();
|
||||
this.mounted = false;
|
||||
}
|
||||
|
||||
componentDidUpdate()
|
||||
{
|
||||
}
|
||||
checkForIllegalCharacters(filename: string) {
|
||||
}
|
||||
|
||||
render() {
|
||||
let props = this.props;
|
||||
let { open, defaultName, onClose, onOk } = props;
|
||||
|
||||
const handleClose = () => {
|
||||
onClose();
|
||||
};
|
||||
|
||||
const handleOk = () => {
|
||||
let text = nullCast(this.refText.current).value;
|
||||
text = text.trim();
|
||||
try {
|
||||
this.checkForIllegalCharacters(text);
|
||||
} catch (e:any)
|
||||
{
|
||||
let model:PiPedalModel = PiPedalModelFactory.getInstance();
|
||||
model.showAlert(e.toString());
|
||||
return;
|
||||
}
|
||||
if (text.length === 0) return;
|
||||
onOk(this.state.selectedBank,text);
|
||||
}
|
||||
const handleKeyDown = (event: React.KeyboardEvent<HTMLDivElement>): void => {
|
||||
// 'keypress' event misbehaves on mobile so we track 'Enter' key via 'keydown' event
|
||||
if (event.key === 'Enter') {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
handleOk();
|
||||
}
|
||||
};
|
||||
return (
|
||||
<DialogEx tag="savePresetAs" open={open} fullWidth maxWidth="xs" onClose={handleClose} aria-labelledby="Rename-dialog-title"
|
||||
fullScreen={this.state.fullScreen}
|
||||
style={{userSelect: "none"}}
|
||||
onEnterKey={()=>{}}
|
||||
>
|
||||
<DialogTitle>
|
||||
Save Preset As
|
||||
</DialogTitle>
|
||||
|
||||
<DialogContent >
|
||||
<InputLabel style={{ fontSize: "0.75rem", fontWeight: 400, marginTop: 16 }}>Bank</InputLabel>
|
||||
|
||||
<Select variant="standard" fullWidth value={this.state.selectedBank} style={{marginBottom: 16}}
|
||||
onChange={(e) => this.setState({ selectedBank: e.target.value as number })}>
|
||||
{this.state.banks.entries.map((bankEntry) => {
|
||||
return (
|
||||
<MenuItem key={bankEntry.instanceId} value={bankEntry.instanceId}
|
||||
selected={bankEntry.instanceId === this.state.selectedBank}
|
||||
>
|
||||
{bankEntry.name}
|
||||
</MenuItem>
|
||||
);
|
||||
})}
|
||||
</Select>
|
||||
|
||||
<TextField
|
||||
autoFocus={!isTouchUi()}
|
||||
onKeyDown={handleKeyDown}
|
||||
autoComplete="off"
|
||||
autoCorrect="on"
|
||||
autoCapitalize="on"
|
||||
spellCheck={false}
|
||||
variant="standard"
|
||||
slotProps={{
|
||||
inputLabel: {
|
||||
shrink: true
|
||||
},
|
||||
input: {
|
||||
style: { scrollMargin: 24 }
|
||||
}
|
||||
}}
|
||||
id="name"
|
||||
type="text"
|
||||
label={"Name"}
|
||||
fullWidth
|
||||
defaultValue={defaultName}
|
||||
inputRef={this.refText}
|
||||
/>
|
||||
</DialogContent>
|
||||
<DialogActions style={{flexShrink: 1}}>
|
||||
<Button onClick={handleClose} variant="dialogSecondary" >
|
||||
Cancel
|
||||
</Button>
|
||||
<Button onClick={handleOk} variant="dialogPrimary" >
|
||||
OK
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</DialogEx>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -57,6 +57,7 @@ interface HoverProps extends WithStyles<typeof styles> {
|
||||
selected: boolean;
|
||||
showHover?: boolean;
|
||||
borderRadius?: number;
|
||||
clipChildren?: boolean;
|
||||
children?: React.ReactNode;
|
||||
}
|
||||
|
||||
|
||||
@@ -75,15 +75,15 @@ export default class SnapshotButton extends ResizeResponsiveComponent<SnapshotBu
|
||||
if (this.is2x3Layout(width,height)) {
|
||||
uiScale = width / 400;
|
||||
} else {
|
||||
uiScale = (width/3)/(400/2);
|
||||
uiScale = (width/3)/(400/2)*1.5;
|
||||
}
|
||||
if (height < width) {
|
||||
// 1 at 400, 2 at 600
|
||||
let hUiScale = (height-340)/(400-340);
|
||||
hUiScale += (height-400)/(460-400);
|
||||
let hUiScale = (height-200)/(400-200);
|
||||
hUiScale += 1;
|
||||
uiScale = Math.min(hUiScale,uiScale);
|
||||
}
|
||||
if (uiScale < 1) uiScale = 1;
|
||||
if (uiScale < 1.3) uiScale = 1.3;
|
||||
if (uiScale > 4) uiScale = 4;
|
||||
this.setState({uiScale: uiScale});
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@ import { withStyles } from "tss-react/mui";
|
||||
import { PiPedalModelFactory, PiPedalModel, State } from "./PiPedalModel";
|
||||
import Utility from './Utility';
|
||||
import SvgPathBuilder from './SvgPathBuilder';
|
||||
import { UiFrequencyPlot } from './Lv2Plugin';
|
||||
|
||||
|
||||
const StandardItemSize = { width: 80, height: 110 };
|
||||
@@ -50,6 +51,7 @@ interface ToobFrequencyResponseProps extends WithStyles<typeof styles> {
|
||||
instanceId: number;
|
||||
propertyName?: string;
|
||||
width?: number;
|
||||
frequencyPlot?: UiFrequencyPlot
|
||||
|
||||
}
|
||||
interface ToobFrequencyResponseState {
|
||||
@@ -57,6 +59,8 @@ interface ToobFrequencyResponseState {
|
||||
maxDb: number;
|
||||
minF: number;
|
||||
maxF: number;
|
||||
logX: boolean;
|
||||
dbY: boolean;
|
||||
path: string;
|
||||
}
|
||||
|
||||
@@ -74,10 +78,12 @@ const ToobFrequencyResponseView =
|
||||
this.model = PiPedalModelFactory.getInstance();
|
||||
this.state = {
|
||||
path: "",
|
||||
minDb: -30,
|
||||
maxDb: 5,
|
||||
minF: 30,
|
||||
maxF: 20000
|
||||
minDb: this.props.frequencyPlot?.yBottom ?? -30,
|
||||
maxDb: this.props.frequencyPlot?.yTop ?? 5,
|
||||
minF: this.props.frequencyPlot?.xLeft ?? 30,
|
||||
maxF: this.props.frequencyPlot?.xRight ?? 20000,
|
||||
logX: this.props.frequencyPlot?.xLog ?? true,
|
||||
dbY: this.props.frequencyPlot?.yDb ?? true
|
||||
};
|
||||
|
||||
this.pathRef = React.createRef();
|
||||
@@ -140,16 +146,22 @@ const ToobFrequencyResponseView =
|
||||
|
||||
}
|
||||
toY(value: number): number {
|
||||
value = Math.abs(value);
|
||||
if (this.state.dbY)
|
||||
{
|
||||
value = Math.abs(value);
|
||||
|
||||
var db;
|
||||
if (value < this.MIN_DB_AF) {
|
||||
db = -192.0;
|
||||
var db;
|
||||
if (value < this.MIN_DB_AF) {
|
||||
db = -192.0;
|
||||
} else {
|
||||
db = 20 * Math.log10(value);
|
||||
}
|
||||
var y = (db - this.dbMin) / (this.dbMax - this.dbMin) * (this.yMax - this.yMin) + this.yMin;
|
||||
return y;
|
||||
} else {
|
||||
db = 20 * Math.log10(value);
|
||||
var y = value-this.dbMin /(this.dbMax-this.dbMin)* (this.yMax - this.yMin) + this.yMin;
|
||||
return y;
|
||||
}
|
||||
var y = (db - this.dbMin) / (this.dbMax - this.dbMin) * (this.yMax - this.yMin) + this.yMin;
|
||||
return y;
|
||||
}
|
||||
|
||||
onFrequencyResponseUpdated(data: number[]) {
|
||||
@@ -159,6 +171,7 @@ const ToobFrequencyResponseView =
|
||||
let dbMin = data[2];
|
||||
let dbMax = data[3];
|
||||
let xMax = this.xMax();
|
||||
let dbY = this.state.dbY;
|
||||
|
||||
let n = data.length-4;
|
||||
|
||||
@@ -166,16 +179,22 @@ const ToobFrequencyResponseView =
|
||||
return (xMax - this.xMin) * bin/n;
|
||||
|
||||
};
|
||||
let toY_ = (value: number): number => {
|
||||
|
||||
var db;
|
||||
if (value < this.MIN_DB_AF) {
|
||||
db = -192.0;
|
||||
} else {
|
||||
db = 20 * Math.log10(value);
|
||||
let toY_ = dbY?
|
||||
(value: number): number => {
|
||||
var db;
|
||||
if (value < this.MIN_DB_AF) {
|
||||
db = -192.0;
|
||||
} else {
|
||||
db = 20 * Math.log10(value);
|
||||
}
|
||||
var y = (db - dbMin) / (dbMax - dbMin) * (this.yMax - this.yMin) + this.yMin;
|
||||
return y;
|
||||
}
|
||||
var y = (db - dbMin) / (dbMax - dbMin) * (this.yMax - this.yMin) + this.yMin;
|
||||
return y;
|
||||
:
|
||||
(value: number): number => {
|
||||
var y = (value - dbMin) / (dbMax - dbMin) * (this.yMax - this.yMin) + this.yMin;
|
||||
return y;
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -242,7 +261,7 @@ const ToobFrequencyResponseView =
|
||||
}
|
||||
grid(): React.ReactNode[] {
|
||||
let result: React.ReactNode[] = [];
|
||||
|
||||
if (!this.state.dbY) return result;
|
||||
let xMax = this.xMax();
|
||||
for (var db = Math.ceil(this.dbMax / this.dbTickSpacing) * this.dbTickSpacing; db < this.dbMin; db += this.dbTickSpacing) {
|
||||
var y = (db - this.dbMin) / (this.dbMax - this.dbMin) * (this.yMax - this.yMin);
|
||||
@@ -277,6 +296,7 @@ const ToobFrequencyResponseView =
|
||||
|
||||
dbMin: number = 5;
|
||||
dbMax: number = -35;
|
||||
dbY: boolean = true;
|
||||
|
||||
private nextKey: number = 0;
|
||||
render() {
|
||||
@@ -284,6 +304,7 @@ const ToobFrequencyResponseView =
|
||||
this.nextKey = 0;
|
||||
this.dbMax = this.state.minDb;
|
||||
this.dbMin = this.state.maxDb;
|
||||
this.dbY = this.state.dbY;
|
||||
|
||||
this.fMin = this.state.minF;
|
||||
this.fMax = this.state.maxF;
|
||||
|
||||
@@ -1,25 +1,28 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="48px"
|
||||
height="48px" viewBox="0 0 48 48" enable-background="new 0 0 48 48" xml:space="preserve">
|
||||
<g id="x">
|
||||
</g>
|
||||
<g id="Layer_1">
|
||||
<path d="M47,41c0,3.313-2.688,6-6,6H7c-3.313,0-6-2.688-6-6V7c0-3.313,2.687-6,6-6h34c3.313,0,6,2.687,6,6V41z"/>
|
||||
<line fill="none" stroke="#FFFFFF" stroke-width="3" stroke-linecap="square" x1="10.167" y1="32.584" x2="38.584" y2="32.584"/>
|
||||
<line fill="none" stroke="#FFFFFF" stroke-width="3" stroke-linecap="square" x1="12.5" y1="32.584" x2="12.5" y2="12"/>
|
||||
<line fill="none" stroke="#FFFFFF" stroke-width="3" stroke-linecap="square" x1="20.5" y1="32.584" x2="20.5" y2="19"/>
|
||||
<line fill="none" stroke="#FFFFFF" stroke-width="3" stroke-linecap="square" x1="28.5" y1="32.584" x2="28.5" y2="15"/>
|
||||
<line fill="none" stroke="#FFFFFF" stroke-width="3" stroke-linecap="square" x1="36.5" y1="32.584" x2="36.5" y2="23"/>
|
||||
</g>
|
||||
<g id="Compound" display="none">
|
||||
<g display="inline">
|
||||
<path d="M41,1H7C3.687,1,1,3.687,1,7v34c0,3.313,2.687,6,6,6h34c3.313,0,6-2.688,6-6V7C47,3.687,44.313,1,41,1z M24,40.928
|
||||
c-9.334,0-16.928-7.594-16.928-16.928S14.666,7.072,24,7.072S40.928,14.666,40.928,24S33.334,40.928,24,40.928z"/>
|
||||
<path d="M24,9.072C15.769,9.072,9.072,15.769,9.072,24c0,8.231,6.697,14.928,14.928,14.928c8.231,0,14.928-6.696,14.928-14.928
|
||||
C38.928,15.769,32.231,9.072,24,9.072z M31.296,20.515l-2.568-1.528l-4.205,7.069l-2.579-1.533l4.206-7.07l-2.567-1.527
|
||||
l7.831-4.384L31.296,20.515z"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
|
||||
<svg
|
||||
version="1.1"
|
||||
x="0px"
|
||||
y="0px"
|
||||
width="48px"
|
||||
height="48px"
|
||||
viewBox="0 0 48 48"
|
||||
enable-background="new 0 0 48 48"
|
||||
xml:space="preserve"
|
||||
id="svg870"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"><defs
|
||||
id="defs874">
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</defs>
|
||||
<path
|
||||
id="path850"
|
||||
d="M 7 1 C 3.6870033 1 1 3.6870033 1 7 L 1 41 C 1 44.311997 3.6870033 47 7 47 L 41 47 C 44.311997 47 47 44.312997 47 41 L 47 7 C 47 3.6870033 44.312997 1 41 1 L 7 1 z M 11 10.5 L 14 10.5 L 14 12 L 14 31.083984 L 19 31.083984 L 19 19 L 19 17.5 L 22 17.5 L 22 19 L 22 31.083984 L 27 31.083984 L 27 15 L 27 13.5 L 30 13.5 L 30 15 L 30 31.083984 L 35 31.083984 L 35 23 L 35 21.5 L 38 21.5 L 38 23 L 38 31.083984 L 38.583984 31.083984 L 40.083984 31.083984 L 40.083984 34.083984 L 38.583984 34.083984 L 38 34.083984 L 35 34.083984 L 10.167969 34.083984 L 8.6679688 34.083984 L 8.6679688 31.083984 L 10.167969 31.083984 L 11 31.083984 L 11 12 L 11 10.5 z " />
|
||||
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 1.1 KiB |
Reference in New Issue
Block a user