Vendored
+1
-1
@@ -170,7 +170,7 @@
|
||||
"cSpell.ignoreWords": [
|
||||
"nammodel"
|
||||
],
|
||||
"cSpell.enabled": true,
|
||||
"cSpell.enabled": false,
|
||||
|
||||
// Disable all automatic completion suggestions - only show when Ctrl+Space is pressed
|
||||
"editor.quickSuggestions": {
|
||||
|
||||
+2
-2
@@ -1,13 +1,13 @@
|
||||
cmake_minimum_required(VERSION 3.16.0)
|
||||
project(pipedal
|
||||
VERSION 1.5.91
|
||||
VERSION 1.5.92
|
||||
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.91-Experimental")
|
||||
set (DISPLAY_VERSION "PiPedal v1.5.92-Experimental")
|
||||
set (PACKAGE_ARCHITECTURE ${DEBIAN_ARCHITECTURE})
|
||||
set (CMAKE_INSTALL_PREFIX "/usr/")
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#!/bin/bash
|
||||
wpa_cli -ip2p-dev-wlan0 GET device_name pipedal
|
||||
wpa_cli -ip2p-dev-wlan0 GET country CA
|
||||
wpa_cli -ip2p-dev-wlan0 GET device_type 1-0050F204-1
|
||||
|
||||
@@ -410,7 +410,7 @@ namespace pipedal
|
||||
{
|
||||
write(string_view(s.c_str()));
|
||||
}
|
||||
void write(float f)
|
||||
void write_float(float f)
|
||||
{
|
||||
if ((std::isnan(f) || std::isinf(f)))
|
||||
{
|
||||
@@ -426,7 +426,11 @@ namespace pipedal
|
||||
os << std::setprecision(std::numeric_limits<float>::max_digits10) << f; // round-trip format
|
||||
}
|
||||
}
|
||||
void write(double f)
|
||||
void write(float f) {
|
||||
write_float(f);
|
||||
}
|
||||
|
||||
void write_double(double f)
|
||||
{
|
||||
if ((std::isnan(f) || std::isinf(f)))
|
||||
{
|
||||
@@ -442,7 +446,10 @@ namespace pipedal
|
||||
os << std::setprecision(std::numeric_limits<double>::max_digits10) << f; // round-trip format
|
||||
}
|
||||
}
|
||||
|
||||
void write(double d)
|
||||
{
|
||||
write_double(d);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void write(const std::vector<T> &value)
|
||||
|
||||
@@ -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.91</a>
|
||||
Download: <a href='https://rerdavies.github.io/pipedal/download.html'>v1.5.92</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.91 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.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!
|
||||
|
||||
|
||||
|
||||
@@ -79,7 +79,7 @@ https://github.com/user-attachments/assets/9a9fd0c6-78fc-4284-8b44-6a1929c00cc6
|
||||
### [The Build System](https://rerdavies.github.io/pipedal/TheBuildSystem.html)
|
||||
### [How to Debug PiPedal](https://rerdavies.github.io/pipedal/Debugging.html)
|
||||
|
||||
|
||||
|
||||
|
||||
#### [PiPedal Architecture](https://rerdavies.github.io/pipedal/Architecture.html)
|
||||
|
||||
|
||||
Executable
+16
@@ -0,0 +1,16 @@
|
||||
#!/bin/bash
|
||||
# Run a CMake build.
|
||||
|
||||
set -e
|
||||
|
||||
# clean build
|
||||
rm -rf build
|
||||
|
||||
# configure for release
|
||||
mkdir -p build
|
||||
cd build
|
||||
cmake .. -D CMAKE_BUILD_TYPE=Release -D CMAKE_VERBOSE_MAKEFILE=ON -G Ninja
|
||||
cd ..
|
||||
|
||||
time cmake --build ./build --target all --config Release -- -j 3
|
||||
|
||||
+1
-1
@@ -6,4 +6,4 @@
|
||||
# bundle install
|
||||
#
|
||||
cd docs
|
||||
bundle exec jekyll serve --host
|
||||
bundle exec jekyll serve --host 0.0.0.0
|
||||
File diff suppressed because one or more lines are too long
@@ -21,6 +21,7 @@ Run the following commands to install dependent libraries required by the PiPeda
|
||||
|
||||
sudo apt update
|
||||
sudo apt upgrade
|
||||
|
||||
sudo apt install -y liblilv-dev libboost-dev \
|
||||
libsystemd-dev catch libasound2-dev uuid-dev \
|
||||
authbind libavahi-client-dev libnm-dev libicu-dev \
|
||||
|
||||
+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.91](https://github.com/rerdavies/pipedal/releases/download/v1.5.91/pipedal_1.5.91_arm64.deb)
|
||||
- [Ubuntu 24.04 through 25.04 (aarch64) v1.5.91](https://github.com/rerdavies/pipedal/releases/download/v1.5.91/pipedal_1.5.91_arm64.deb)
|
||||
- [Ubuntu 24.04 through 25.04 (amd64) v1.5.91](https://github.com/rerdavies/pipedal/releases/download/v1.5.91/pipedal_1.5.91_amd64.deb)
|
||||
- [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)
|
||||
|
||||
|
||||
Version 1.5.91 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.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:
|
||||
|
||||
```
|
||||
sudo apt update
|
||||
sudo apt upgrade
|
||||
cd ~/Downloads
|
||||
sudo apt-get install ./pipedal_1.5.91_arm64.deb
|
||||
sudo apt-get install ./pipedal_1.5.92_arm64.deb
|
||||
```
|
||||
You MUST use `apt-get`. `apt` will not install downloaded packages; and `dpkg -i` will not install dependencies.
|
||||
|
||||
|
||||
@@ -1,5 +1,90 @@
|
||||
# Release Notes
|
||||
|
||||
## PiPedal 1.5.92 Release
|
||||
|
||||
### Major Features in This Build
|
||||
|
||||
- New default presets that make better use of plugins that have been added since the original set of default presets were created.
|
||||
|
||||
- The installer creates a new "Factory Presets" bank. If you are upgrading, you can access the new presets in the Factory Presets bank. New factory presets added in future PiPedal updates may add or modify presets in the Factory Presets bank. If you want to use or modify the new Factory Presets, you should copy them to a new bank, or copy individual presets to a bank you currently use for your own presets.
|
||||
|
||||
- Stream between audio adapters. You can now select different audio adapters for input and output. (Thanks to Gabriel Hernandez)
|
||||
|
||||
### Major Features (Since PiPedal 1.4.76 Release)
|
||||
|
||||
- Support for Ubuntu 25.04. Compatibility fixes for Ubuntu 25.04 may also make it easier to build PiPedal on
|
||||
other Debian-based Linux distros.
|
||||
|
||||
- Code optimizations for Raspberry Pi 5 (Cortex-A76), providing substantial performance improvements for TooB Neural Amp Modeler. ToobAmp
|
||||
automatically loads A72- or A76-optimized code depending on which processor you are using.
|
||||
|
||||
- New "Threaded" button optionally executes TooB NAM computation on a separate thread. Allows up to 5 NAM instances on Pi 4, and practically unlimited NAM instances on Pi 5.
|
||||
|
||||
- TooB Neural Amp Modeler: Input and output calibration. See [Using TooB Neural Amp Modeler](https://rerdavies.github.io/pipedal/UsingNAM.html) for guidance on how to
|
||||
use this feature.
|
||||
|
||||
- TooB Convolution Reverb: Predelay, Stretch and Decay controls allow adjustments of impulse response characteristics.
|
||||
|
||||
- TooB Convolution Reverb, Freeverb: New "Tails" control, which allows reverb tails to continue after bypassing the effect.
|
||||
|
||||
- TooB Parametric EQ: New plugin providing a 4-band parametric EQ. A musically useful Parametric EQ that's suitable both for
|
||||
shaping guitar tone before amp models, or for shaping overall sound at the end of plugin chains. This should become your
|
||||
go-to EQ plugin when using PiPedal.
|
||||
|
||||
- New TooB Player plugin that allows you to play (and loop) audio files (even large ones).
|
||||
|
||||
- TooB 3 Band EQ: New plugin providing a simple 3-band EQ. (Replicates the EQ stage on Steve Atkinson's Windows and Mac-based
|
||||
Neural Amp Modeler plugin).
|
||||
|
||||
- TooB CE-2 Chorus, TooB BF-2 Flanger, TooB Phaser: New "Wet/Dry" controls.
|
||||
|
||||
- Noise Gates threshold ranges have been increased in TooB NAM, TooB Input and TooB Noise Gate plugins.
|
||||
|
||||
- MIDI bindings allow users to specify minimum and maximum control values, in order to better support expression pedals.
|
||||
|
||||
- Editable plugin labels. You can now edit the labels of plugins in the PiPedal UI. Tap the plugin label to override
|
||||
the default label with a custom label. Useful when you have multiple instances of the same plugin in a preset.
|
||||
|
||||
- Multi-select support in file browser dialogs. Allows you to easily delete, copy or move multiple files at once.
|
||||
|
||||
- long-press and hover Tooltips are now displayed for all controls in the PiPedal UI.
|
||||
|
||||
- TooB Neural Amp Modeler: Switch to the NeuralAudio NAM Backend (Thanks to Mike Oliphant for providing
|
||||
MIT-licensed sources that provide significant performance improvements).
|
||||
|
||||
- Support for custom MOD User Interfaces for plugins that provide a MOD UI.
|
||||
|
||||
- Switch from ALSA rawmidi devices to ALSA sequencer devices for MIDI input. This provides
|
||||
a number of useful improvements. PiPedal now supports Bluetooth MIDI controllers.
|
||||
PiPedal provides better support for subdevices (multiple MIDI connections on the same device).
|
||||
Connections to the PiPedal MIDI input port can now be made at runtime by other applications, or
|
||||
by using`aconnect`. Changing MIDI devices does not require a restart of the audio stream. Disconnected
|
||||
MIDI devices that have been selected will now be automatically reconnected when they are
|
||||
plugged in again.
|
||||
|
||||
- Dial control values are displayed in a fly-out when editing, which makes it easier to determine the
|
||||
current value of a control when using touch user interfaces.
|
||||
|
||||
- Direct link to TONE3000 website from the TooB Neural Amp Modeler file browser.
|
||||
|
||||
- Double-tap gestures now supported in touch user interfaces. Double-tap a dial to reset it to its default value. Double-tap
|
||||
a file in the File Browser to select it. Double-tap a plugin label to edit it.
|
||||
|
||||
- Improvements to long-press handling in touch and mouse user interfaces. Long-press a control to display
|
||||
a tooltip for the control, and triggers multi-select in the file browser dialog.
|
||||
|
||||
- Audio files in the Tracks directory (only) are displayed using audio-file metadata and artwork if available.
|
||||
|
||||
### Minor features in This Build
|
||||
|
||||
- Improved enumeration of available audio devices. ALSA devices that are currently in use by other applications are now shown in the list of available audio devices, but are disabled.
|
||||
|
||||
- Select README.md or LICENSE.md files in File Selection dialogs to view their contents inside the PiPedal UI.
|
||||
|
||||
- PiPedal respects LV2 plugins' minimum-buffer-size requirements. If PiPedal is using a buffer size smaller than the plugin's minimum size, PiPedal will assemble buffers of the currect size before passing them to the plugin. This allows use of plugins written in Faust, including a number of useful plugins from Guitarix/Lv2 and Gx/Lv2 projects.
|
||||
|
||||
Please refer to Beta release notes below for a more complete list of features and bug fixes since the last Release build.
|
||||
|
||||
## PiPedal 1.5.91 Beta (Release Candidate)
|
||||
|
||||
#### Themes for this release
|
||||
|
||||
@@ -13,15 +13,16 @@ available through the Code plugins store) has configured itself, build commands
|
||||
If you are not using Visual Studio Code, you can configure, build and install PiPedal using CMake build tools. For your convenience,
|
||||
the following shell scripts have been provided in the root of the project.
|
||||
|
||||
./init.sh # Configure the CMake build for the first time, or if you
|
||||
./init.sh # Configure the CMake build for the first time, or if you
|
||||
# have changed one of the CMakeList.txt files. (release build)
|
||||
|
||||
./mk.sh # Build all targets (release build)
|
||||
|
||||
sudo ./install.sh # Deploy all targets
|
||||
sudo ./install.sh # Deploy all targets
|
||||
|
||||
sudo ./makePackage.sh # Build a .deb file for distribution.
|
||||
|
||||
|
||||
If you are using a development environment other than Visual Studio Code, it should be fairly straightforward to figure out how
|
||||
to incorporate the PiPedal build procedure into your IDE workflow by using the contents of the build shell scripts as a model.
|
||||
|
||||
|
||||
+4
-4
@@ -4,9 +4,9 @@
|
||||
|
||||
Download the most recent Debian (.deb) package for your platform:
|
||||
|
||||
- [Raspberry Pi OS bookworm (aarch64) v1.5.91 Beta](https://github.com/rerdavies/pipedal/releases/download/v1.5.91/pipedal_1.5.91_arm64.deb)
|
||||
- [Ubuntu 24.x, 25.04 (aarch64) v1.5.91 Alpha](https://github.com/rerdavies/pipedal/releases/download/v1.5.91/pipedal_1.5.91_arm64.deb)
|
||||
- [Ubuntu 24.x, 25.04 (amd64) v1.5.91 Alpha](https://github.com/rerdavies/pipedal/releases/download/v1.5.91/pipedal_1.5.91_amd64.deb)
|
||||
- [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)
|
||||
|
||||
|
||||
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.91_arm64.deb
|
||||
sudo apt-get install ./pipedal_1.5.92_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 68 ;
|
||||
lv2:microVersion 70 ;
|
||||
rdfs:comment """
|
||||
|
||||
TooB Cab IR is a convolution-based guitar cabinet impulse response simulator.
|
||||
@@ -174,7 +174,7 @@ Limitations of Liability that apply to these works.
|
||||
pg:group cabir:impulseFile1Group ;
|
||||
|
||||
|
||||
lv2:default -10.0 ;
|
||||
lv2:default -3.0 ;
|
||||
lv2:minimum -40.0 ;
|
||||
lv2:maximum 20.0 ;
|
||||
units:unit units:db ;
|
||||
@@ -194,7 +194,7 @@ Limitations of Liability that apply to these works.
|
||||
lv2:name "Volume";
|
||||
pg:group cabir:impulseFile2Group ;
|
||||
|
||||
lv2:default -10.0 ;
|
||||
lv2:default -3.0 ;
|
||||
lv2:minimum -40.0 ;
|
||||
lv2:maximum 20.0 ;
|
||||
units:unit units:db ;
|
||||
@@ -214,7 +214,7 @@ Limitations of Liability that apply to these works.
|
||||
lv2:name "Volume";
|
||||
pg:group cabir:impulseFile3Group ;
|
||||
|
||||
lv2:default -10.0 ;
|
||||
lv2:default -3.0 ;
|
||||
lv2:minimum -40.0 ;
|
||||
lv2:maximum 20.0 ;
|
||||
units:unit units:db ;
|
||||
|
||||
@@ -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 68 ;
|
||||
lv2:microVersion 70 ;
|
||||
|
||||
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 68 ;
|
||||
lv2:microVersion 70 ;
|
||||
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 68 ;
|
||||
lv2:microVersion 70 ;
|
||||
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 68 ;
|
||||
lv2:microVersion 70 ;
|
||||
|
||||
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 68 ;
|
||||
lv2:microVersion 70 ;
|
||||
|
||||
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 68 ;
|
||||
lv2:microVersion 70 ;
|
||||
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 68 ;
|
||||
lv2:microVersion 70 ;
|
||||
|
||||
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 68 ;
|
||||
lv2:microVersion 70 ;
|
||||
|
||||
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 68 ;
|
||||
lv2:microVersion 70 ;
|
||||
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 68 ;
|
||||
lv2:microVersion 70 ;
|
||||
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 68 ;
|
||||
lv2:microVersion 70 ;
|
||||
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 68 ;
|
||||
lv2:microVersion 70 ;
|
||||
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 68 ;
|
||||
lv2:microVersion 70 ;
|
||||
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 68 ;
|
||||
lv2:microVersion 70 ;
|
||||
rdfs:comment """
|
||||
A 7 octave graphic equalizer, with frequencies chosen to be useful for EQ-ing guitar signals.
|
||||
""" ;
|
||||
|
||||
@@ -91,8 +91,8 @@ myprefix:output_group
|
||||
;
|
||||
doap:license <https://opensource.org/license/mit/> ;
|
||||
doap:maintainer <http://two-play.com/rerdavies#me> ;
|
||||
lv2:minorVersion 1 ;
|
||||
lv2:microVersion 68 ;
|
||||
lv2:minorVersion 2 ;
|
||||
lv2:microVersion 70 ;
|
||||
|
||||
ui:ui <http://two-play.com/plugins/toob-looper-four-ui>;
|
||||
|
||||
|
||||
@@ -77,8 +77,8 @@ myprefix:output_group
|
||||
|
||||
doap:license <https://opensource.org/license/mit/> ;
|
||||
doap:maintainer <http://two-play.com/rerdavies#me> ;
|
||||
lv2:minorVersion 1 ;
|
||||
lv2:microVersion 68 ;
|
||||
lv2:minorVersion 2 ;
|
||||
lv2:microVersion 70 ;
|
||||
|
||||
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 68 ;
|
||||
lv2:microVersion 70 ;
|
||||
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 68 ;
|
||||
lv2:microVersion 70 ;
|
||||
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 68 ;
|
||||
lv2:microVersion 70 ;
|
||||
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
|
||||
@@ -133,12 +133,12 @@ making his optimizations work available as open-source code.
|
||||
Steve and Mike have done all of the hard work that was required to make NAM technology available through TooB Neural Amp Modeler. They
|
||||
deserve all the credit.
|
||||
|
||||
Code from the the NeuralAudio project (https://github.com/sdatkinson/NeuralAmpModelerCore) is provided
|
||||
Code from the the NeuralAudio project (https://github.com/mikeoliphant/NeuralAudio) is provided
|
||||
under the following license.
|
||||
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2024 Mike Oliphant
|
||||
Copyright (c) 2024, 2025 Mike Oliphant
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
@@ -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 68 ;
|
||||
lv2:microVersion 70 ;
|
||||
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.
|
||||
|
||||
@@ -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 68 ;
|
||||
lv2:microVersion 70 ;
|
||||
|
||||
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 68 ;
|
||||
lv2:microVersion 70 ;
|
||||
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 68 ;
|
||||
lv2:microVersion 70 ;
|
||||
rdfs:comment """
|
||||
Play audio files. Audio files can optionally be loooped by pressing the "Set loop" button.
|
||||
|
||||
|
||||
@@ -50,8 +50,8 @@ recordPrefix:audioFile
|
||||
;
|
||||
doap:license <https://opensource.org/license/mit/> ;
|
||||
doap:maintainer <http://two-play.com/rerdavies#me> ;
|
||||
lv2:minorVersion 1 ;
|
||||
lv2:microVersion 68 ;
|
||||
lv2:minorVersion 2 ;
|
||||
lv2:microVersion 70 ;
|
||||
|
||||
ui:ui <http://two-play.com/plugins/toob-record-mono-ui>;
|
||||
|
||||
|
||||
@@ -87,8 +87,8 @@ myprefix:loop3_group
|
||||
;
|
||||
doap:license <https://opensource.org/license/mit/> ;
|
||||
doap:maintainer <http://two-play.com/rerdavies#me> ;
|
||||
lv2:minorVersion 1 ;
|
||||
lv2:microVersion 68 ;
|
||||
lv2:minorVersion 2 ;
|
||||
lv2:microVersion 70 ;
|
||||
|
||||
ui:ui <http://two-play.com/plugins/toob-record-stereo-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 68 ;
|
||||
lv2:microVersion 70 ;
|
||||
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 68 ;
|
||||
lv2:microVersion 70 ;
|
||||
rdfs:comment """
|
||||
Volume control.
|
||||
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
+2
-1
@@ -1,3 +1,4 @@
|
||||
#!/bin/bash
|
||||
cd build
|
||||
cpack -G DEB -C Release -config CPackConfig.cmake
|
||||
cpack -G DEB -C Release -config CPackConfig.cmake
|
||||
cd ..
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
#!/bin/bash
|
||||
SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
|
||||
${SCRIPT_DIR}/../build/src/profilePlugin -s 100 -w ToobNam_Profile -o /tmp/ToobNam.perf &&\
|
||||
google-pprof --text ${SCRIPT_DIR}/../build/src/profilePlugin --add_lib /usr/lib/lv2/ToobAmp.lv2/ToobAmp.so /tmp/ToobNam.perf >./ToobNam.txt
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#!/bin/bash
|
||||
SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
|
||||
${SCRIPT_DIR}/../build/src/profilePlugin -w Nam_Profile -o /tmp/nam.perf &&\
|
||||
google-pprof --web ${SCRIPT_DIR}/../build/src/profilePlugin --add_lib /usr/lib/lv2/neural_amp_modeler.lv2/neural_amp_modeler.so /tmp/nam.perf >./nam.txt
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
|
||||
#!/bin/bash
|
||||
rm -rf ./lv2/aarch64/*
|
||||
|
||||
cp -R /usr/lib/lv2/ToobAmp.lv2/ ./lv2/aarch64/
|
||||
|
||||
+27
-14
@@ -271,7 +271,7 @@ namespace pipedal
|
||||
auto inAvail = snd_pcm_avail_update(this->captureHandle);
|
||||
auto outAvail = snd_pcm_avail_update(this->playbackHandle);
|
||||
|
||||
auto total = inAvail + outAvail + framesInBuffer;
|
||||
auto total = (inAvail >= 0? inAvail: 0) + (outAvail >= 0 ? outAvail: 0) + framesInBuffer;
|
||||
bufferTraces[bufferTraceIndex++] = {
|
||||
time,
|
||||
inAvail,
|
||||
@@ -683,6 +683,7 @@ namespace pipedal
|
||||
|
||||
if (this->captureHandle)
|
||||
{
|
||||
this->alsa_device_name = this->jackServerSettings.GetAlsaInputDevice();
|
||||
AlsaConfigureStream(
|
||||
this->alsa_device_name,
|
||||
"capture",
|
||||
@@ -695,6 +696,7 @@ namespace pipedal
|
||||
}
|
||||
if (this->playbackHandle)
|
||||
{
|
||||
this->alsa_device_name = this->jackServerSettings.GetAlsaOutputDevice();
|
||||
AlsaConfigureStream(
|
||||
this->alsa_device_name,
|
||||
"playback",
|
||||
@@ -1178,11 +1180,13 @@ namespace pipedal
|
||||
throw std::runtime_error("Unable to restart the audio stream.");
|
||||
}
|
||||
int err;
|
||||
|
||||
if ((err = snd_pcm_start(captureHandle)) < 0)
|
||||
{
|
||||
Lv2Log::error(SS("Unable to restart ALSA capture: " << snd_strerror(err)));
|
||||
throw PiPedalStateException("Unable to restart ALSA capture.");
|
||||
}
|
||||
TraceBufferPositions(0,'+');
|
||||
audioRunning = true;
|
||||
}
|
||||
|
||||
@@ -1326,7 +1330,9 @@ namespace pipedal
|
||||
|
||||
int err;
|
||||
|
||||
alsa_device_name = jackServerSettings.GetAlsaInputDevice();
|
||||
std::string inputName = jackServerSettings.GetAlsaInputDevice();
|
||||
std::string outputName = jackServerSettings.GetAlsaOutputDevice();
|
||||
|
||||
|
||||
this->numberOfBuffers = jackServerSettings.GetNumberOfBuffers();
|
||||
this->bufferSize = jackServerSettings.GetBufferSize();
|
||||
@@ -1335,7 +1341,8 @@ namespace pipedal
|
||||
try
|
||||
{
|
||||
|
||||
err = snd_pcm_open(&playbackHandle, alsa_device_name.c_str(), SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK);
|
||||
this->alsa_device_name = outputName;
|
||||
err = snd_pcm_open(&playbackHandle, outputName.c_str(), SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK);
|
||||
if (err < 0)
|
||||
{
|
||||
switch (errno)
|
||||
@@ -1369,8 +1376,9 @@ namespace pipedal
|
||||
{
|
||||
snd_pcm_nonblock(playbackHandle, 0);
|
||||
}
|
||||
|
||||
err = snd_pcm_open(&captureHandle, alsa_device_name.c_str(), SND_PCM_STREAM_CAPTURE, SND_PCM_NONBLOCK);
|
||||
|
||||
this->alsa_device_name = inputName;
|
||||
err = snd_pcm_open(&captureHandle, inputName.c_str(), SND_PCM_STREAM_CAPTURE, SND_PCM_NONBLOCK);
|
||||
|
||||
if (err < 0)
|
||||
{
|
||||
@@ -1512,9 +1520,11 @@ namespace pipedal
|
||||
}
|
||||
snd_pcm_drain(capture_handle);
|
||||
FillOutputBuffer();
|
||||
TraceBufferPositions(framesRead, 'x');
|
||||
}
|
||||
else
|
||||
{
|
||||
TraceBufferPositions(framesRead, 'z');
|
||||
Lv2Log::error(SS("Can't recover from ALSA output underrun. (" << snd_strerror(err) << ")"));
|
||||
throw PiPedalStateException(SS("Can't recover from ALSA output error. (" << snd_strerror(err) << ")"));
|
||||
}
|
||||
@@ -1615,7 +1625,7 @@ namespace pipedal
|
||||
// expand running status if neccessary.
|
||||
// deal with regular and sysex messages split across
|
||||
// buffer boundaries (but discard them)
|
||||
snd_pcm_sframes_t framesRead;
|
||||
snd_pcm_sframes_t framesRead = 0;
|
||||
|
||||
auto state = snd_pcm_state(handle);
|
||||
auto frame_bytes = this->captureFrameSize;
|
||||
@@ -2363,16 +2373,19 @@ namespace pipedal
|
||||
{
|
||||
auto&bufferTrace = bufferTraces[ix];
|
||||
|
||||
int64_t dt = (int64_t)bufferTrace.time - (int64_t)t0;
|
||||
if (bufferTrace.time != 0)
|
||||
{
|
||||
int64_t dt = (int64_t)bufferTrace.time - (int64_t)t0;
|
||||
|
||||
|
||||
cout << bufferTrace.code << " "
|
||||
<< fixed << setprecision(3) << dt*0.001
|
||||
<< " " << "inAvail: " << bufferTrace.inAvail
|
||||
<< " " << "outAvail: " << bufferTrace.outAvail
|
||||
<< " " << "bufferd: " << bufferTrace.buffered
|
||||
<< " " << "total: " << bufferTrace.total
|
||||
<< endl;
|
||||
cout << bufferTrace.code << " "
|
||||
<< fixed << setprecision(3) << dt*0.001
|
||||
<< " " << "inAvail: " << bufferTrace.inAvail
|
||||
<< " " << "outAvail: " << bufferTrace.outAvail
|
||||
<< " " << "buffered: " << bufferTrace.buffered
|
||||
<< " " << "total: " << bufferTrace.total
|
||||
<< endl;
|
||||
}
|
||||
|
||||
++ix;
|
||||
if (ix == bufferTraces.size())
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2022 Robin E. R. Davies
|
||||
* Copyright (c) 2025 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
|
||||
|
||||
@@ -64,7 +64,7 @@ public:
|
||||
|
||||
AlsaFormatEncodeDecodeTest(this);
|
||||
|
||||
JackServerSettings serverSettings("hw:M2",48000,32,3);
|
||||
JackServerSettings serverSettings("hw:M2","hw:M2",48000,32,3);
|
||||
|
||||
JackConfiguration jackConfiguration;
|
||||
if (useJack)
|
||||
|
||||
+3
-1
@@ -1,4 +1,5 @@
|
||||
// Copyright (c) 2022-2023 Robin Davies
|
||||
// Copyright (c) Robin E. R. Davies
|
||||
// Copyright (c) Gabriel Hernandez
|
||||
//
|
||||
// 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
|
||||
@@ -227,6 +228,7 @@ namespace pipedal
|
||||
|
||||
for (const auto &patchProperty : pathPatchProperties)
|
||||
{
|
||||
// yyy: only if the property changed!.
|
||||
effect->SetPatchProperty(
|
||||
patchProperty.propertyUrid, patchProperty.atomBuffer.size(), (LV2_Atom *)patchProperty.atomBuffer.data());
|
||||
}
|
||||
|
||||
+2
-1
@@ -1,4 +1,5 @@
|
||||
// Copyright (c) 2022-2023 Robin Davies
|
||||
// Copyright (c) Robin E. R. Davies
|
||||
// Copyright (c) Gabriel Hernandez
|
||||
//
|
||||
// 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
|
||||
|
||||
+1
-1
@@ -49,7 +49,7 @@ public:
|
||||
void Clear() {
|
||||
for (int i = 0; i < allocatedBuffers.size(); ++i)
|
||||
{
|
||||
delete[] (char*)allocatedBuffers[i];
|
||||
delete[] (char*)(allocatedBuffers[i]);
|
||||
}
|
||||
allocatedBuffers.resize(0);
|
||||
}
|
||||
|
||||
+11
-6
@@ -140,14 +140,19 @@ namespace pipedal
|
||||
overheadTime = readTime;
|
||||
}
|
||||
|
||||
SampleT totalTime = writeTime+ readTime + processingTime;
|
||||
SampleT maxTime = waitTime+processingTime;
|
||||
|
||||
SampleT totalTime = writeTime + readTime + processingTime;
|
||||
SampleT maxTime = waitTime + processingTime;
|
||||
|
||||
float result = 100.0f*(processingTime)/(maxTime);
|
||||
float overhead = 100.0F*(overheadTime*2)/totalTime;
|
||||
float result = 0.0f;
|
||||
float overhead = 0.0f;
|
||||
if (maxTime != 0 && totalTime != 0)
|
||||
{
|
||||
std::lock_guard lock { sync};
|
||||
result = 100.0f * (processingTime) / (maxTime);
|
||||
overhead = 100.0f * (overheadTime * 2) / totalTime;
|
||||
}
|
||||
|
||||
{
|
||||
std::lock_guard lock{sync};
|
||||
currentCpuUse = result;
|
||||
currentOverhead = overhead;
|
||||
}
|
||||
|
||||
@@ -517,6 +517,8 @@ namespace pipedal
|
||||
result.sampleRates_.push_back(48000);
|
||||
result.minBufferSize_ = 16;
|
||||
result.maxBufferSize_ = 1024;
|
||||
result.supportsCapture_ = true;
|
||||
result.supportsPlayback_ = true;
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
@@ -37,32 +37,14 @@ using namespace pipedal::implementation;
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
|
||||
namespace pipedal::implementation {
|
||||
class BrowserFilesVersionInfo
|
||||
{
|
||||
public:
|
||||
bool Load(std::filesystem::path &path);
|
||||
void Save(std::filesystem::path &path);
|
||||
const std::vector<std::string> &InstalledFiles() const;
|
||||
bool IsInstalled(const std::string &file) const;
|
||||
void AddFile(const std::string &file);
|
||||
uint32_t Version() const;
|
||||
void Version(uint32_t value);
|
||||
|
||||
private:
|
||||
uint32_t version = 0;
|
||||
std::vector<std::string> installedFiles;
|
||||
std::unordered_set<std::string> installedFileMap;
|
||||
};
|
||||
};
|
||||
uint32_t BrowserFilesVersionInfo::Version() const { return version; }
|
||||
void BrowserFilesVersionInfo::Version(uint32_t value) { version = value; }
|
||||
|
||||
inline uint32_t BrowserFilesVersionInfo::Version() const { return version; }
|
||||
inline void BrowserFilesVersionInfo::Version(uint32_t value) { version = value; }
|
||||
|
||||
inline const std::vector<std::string> &BrowserFilesVersionInfo::InstalledFiles() const {
|
||||
const std::vector<std::string> &BrowserFilesVersionInfo::InstalledFiles() const {
|
||||
return installedFiles;
|
||||
}
|
||||
inline bool BrowserFilesVersionInfo::IsInstalled(const std::string &file) const
|
||||
bool BrowserFilesVersionInfo::IsInstalled(const std::string &file) const
|
||||
{
|
||||
return installedFileMap.contains(file);
|
||||
}
|
||||
@@ -75,7 +57,7 @@ void BrowserFilesVersionInfo::AddFile(const std::string &file)
|
||||
}
|
||||
}
|
||||
|
||||
bool BrowserFilesVersionInfo::Load(std::filesystem::path &path)
|
||||
bool BrowserFilesVersionInfo::Load(const std::filesystem::path &path)
|
||||
{
|
||||
std::ifstream f{path};
|
||||
if (!f.is_open())
|
||||
@@ -102,7 +84,7 @@ FileBrowserFilesFeature::FileBrowserFilesFeature()
|
||||
feature.URI = LV2_FILEBROWSER__files;
|
||||
feature.data = &(this->featureData);
|
||||
}
|
||||
void BrowserFilesVersionInfo::Save(std::filesystem::path &path)
|
||||
void BrowserFilesVersionInfo::Save(const std::filesystem::path &path)
|
||||
{
|
||||
pipedal::ofstream_synced f{path};
|
||||
if (!f.is_open())
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
*
|
||||
* Copyright (c) 2023 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
|
||||
@@ -30,62 +30,79 @@
|
||||
#include <lv2/log/logger.h>
|
||||
#include <mutex>
|
||||
#include <filesystem>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <unordered_set>
|
||||
|
||||
|
||||
|
||||
namespace pipedal {
|
||||
namespace pipedal
|
||||
{
|
||||
// Private forward declaration
|
||||
namespace implementation {
|
||||
class BrowserFilesVersionInfo;
|
||||
namespace implementation
|
||||
{
|
||||
class BrowserFilesVersionInfo
|
||||
{
|
||||
public:
|
||||
bool Load(const std::filesystem::path &path);
|
||||
void Save(const std::filesystem::path &path);
|
||||
const std::vector<std::string> &InstalledFiles() const;
|
||||
bool IsInstalled(const std::string &file) const;
|
||||
void AddFile(const std::string &file);
|
||||
uint32_t Version() const;
|
||||
void Version(uint32_t value);
|
||||
|
||||
private:
|
||||
uint32_t version = 0;
|
||||
std::vector<std::string> installedFiles;
|
||||
std::unordered_set<std::string> installedFileMap;
|
||||
};
|
||||
};
|
||||
|
||||
class FileBrowserFilesFeature {
|
||||
class FileBrowserFilesFeature
|
||||
{
|
||||
public:
|
||||
FileBrowserFilesFeature();
|
||||
void Initialize(
|
||||
const LV2_URID_Map *lv2Map,
|
||||
const LV2_Log_Log *lv2Log,
|
||||
const std::string&bundleDirectory,
|
||||
const std::string&browserDirectory);
|
||||
const LV2_Feature*GetFeature() { return &feature; }
|
||||
private:
|
||||
static void MakeDirectoryMap(const std::filesystem::path&rootBrowserDirectory);
|
||||
const std::string &bundleDirectory,
|
||||
const std::string &browserDirectory);
|
||||
const LV2_Feature *GetFeature() { return &feature; }
|
||||
|
||||
using WellKnownDirectoryMap = std::map<std::string,std::string>;
|
||||
private:
|
||||
static void MakeDirectoryMap(const std::filesystem::path &rootBrowserDirectory);
|
||||
|
||||
using WellKnownDirectoryMap = std::map<std::string, std::string>;
|
||||
static std::mutex g_DirectoryMap_mutex;
|
||||
static std::unique_ptr<WellKnownDirectoryMap> g_wellKnownDirectoryMap;
|
||||
|
||||
|
||||
LV2_Feature feature;
|
||||
const LV2_URID_Map *lv2Map = nullptr;
|
||||
LV2_Log_Logger lv2Logger = {nullptr,0,0,0,0};
|
||||
LV2_Log_Logger lv2Logger = {nullptr, 0, 0, 0, 0};
|
||||
|
||||
std::filesystem::path bundleDirectory;
|
||||
std::filesystem::path browserRootDirectory;
|
||||
std::filesystem::path privateDirectory;
|
||||
LV2_FileBrowser_Files featureData;
|
||||
|
||||
std::map<std::string,std::string> wellKnownPaths;
|
||||
std::map<std::string, std::string> wellKnownPaths;
|
||||
|
||||
static char *FN_get_upload_path(LV2_FileBrowser_Files_Handle handle, const char *fileBrowserDirectory);
|
||||
static char *FN_map_path(LV2_FileBrowser_Files_Handle handle, const char *path, const char *resourcePathBase, const char *fileBrowserDirectory);
|
||||
static void FN_free_path(LV2_FileBrowser_Files_Handle handle, char *path);
|
||||
static LV2_FileBrowser_Status FN_publish_resource_files(LV2_FileBrowser_Files_Handle handle, uint32_t version, const char *resourcePath, const char *uploadDirectory);
|
||||
|
||||
static char* FN_get_upload_path(LV2_FileBrowser_Files_Handle handle, const char* fileBrowserDirectory);
|
||||
static char* FN_map_path(LV2_FileBrowser_Files_Handle handle, const char* path, const char *resourcePathBase,const char*fileBrowserDirectory);
|
||||
static void FN_free_path(LV2_FileBrowser_Files_Handle handle, char* path);
|
||||
static LV2_FileBrowser_Status FN_publish_resource_files(LV2_FileBrowser_Files_Handle handle,uint32_t version,const char*resourcePath, const char*uploadDirectory);
|
||||
const char *GetWellKnownDirectory(const std::string &directory);
|
||||
|
||||
const char*GetWellKnownDirectory(const std::string&directory);
|
||||
|
||||
void LogError(const char*message);
|
||||
char* GetUploadPath(const char* fileBrowserDirectory);
|
||||
char* MapPath(const char* path,const char*resourcePathBase,const char* fileBrowserDirectory);
|
||||
void FreePath(char* path);
|
||||
LV2_FileBrowser_Status PublishResourceFiles(uint32_t version,const char*resourcePath, const char*uploadDirectory);
|
||||
void LogError(const char *message);
|
||||
char *GetUploadPath(const char *fileBrowserDirectory);
|
||||
char *MapPath(const char *path, const char *resourcePathBase, const char *fileBrowserDirectory);
|
||||
void FreePath(char *path);
|
||||
LV2_FileBrowser_Status PublishResourceFiles(uint32_t version, const char *resourcePath, const char *uploadDirectory);
|
||||
|
||||
void PublishRecursive(
|
||||
implementation::BrowserFilesVersionInfo& versionInfo,
|
||||
const std::filesystem::path& rootResourcePath,
|
||||
const std::filesystem::path& resourcePath,
|
||||
const std::filesystem::path& browserPath
|
||||
);
|
||||
implementation::BrowserFilesVersionInfo &versionInfo,
|
||||
const std::filesystem::path &rootResourcePath,
|
||||
const std::filesystem::path &resourcePath,
|
||||
const std::filesystem::path &browserPath);
|
||||
};
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
// Copyright (c) 2022 Robin Davies
|
||||
// Copyright (c) Robin E. R. Davies
|
||||
// Copyright (c) Gabriel Hernandez
|
||||
//
|
||||
// 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
|
||||
@@ -148,7 +149,13 @@ void JackServerSettings::ReadJackDaemonConfiguration()
|
||||
this->bufferSize_ = GetJackArg(argv, "-p", "--period");
|
||||
this->numberOfBuffers_ = (uint32_t)GetJackArg(argv, "-n", "--nperiods");
|
||||
this->sampleRate_ = (uint32_t)GetJackArg(argv, "-r", "--rate");
|
||||
this->alsaDevice_ = GetJackStringArg(argv,"-d", "--device");
|
||||
// read new dual device flags, fallback on old -d/--device
|
||||
std::string capDev = GetJackStringArg(argv, "-C", "--capture");
|
||||
std::string playDev = GetJackStringArg(argv, "-P", "--playback");
|
||||
std::string dev = "";
|
||||
try { dev = GetJackStringArg(argv, "-d", "--device"); } catch(...) {}
|
||||
this->alsaInputDevice_ = capDev.empty() ? dev : capDev;
|
||||
this->alsaOutputDevice_ = playDev.empty() ? dev : playDev;
|
||||
this->valid_ = true;
|
||||
}
|
||||
catch (std::exception &)
|
||||
@@ -246,7 +253,9 @@ JSON_MAP_REFERENCE(JackServerSettings, valid)
|
||||
JSON_MAP_REFERENCE(JackServerSettings, isOnboarding)
|
||||
JSON_MAP_REFERENCE(JackServerSettings, rebootRequired)
|
||||
JSON_MAP_REFERENCE(JackServerSettings, isJackAudio)
|
||||
JSON_MAP_REFERENCE(JackServerSettings, alsaDevice)
|
||||
JSON_MAP_REFERENCE(JackServerSettings, alsaDevice) // legacy field
|
||||
JSON_MAP_REFERENCE(JackServerSettings, alsaInputDevice)
|
||||
JSON_MAP_REFERENCE(JackServerSettings, alsaOutputDevice)
|
||||
JSON_MAP_REFERENCE(JackServerSettings, sampleRate)
|
||||
JSON_MAP_REFERENCE(JackServerSettings, bufferSize)
|
||||
JSON_MAP_REFERENCE(JackServerSettings, numberOfBuffers)
|
||||
|
||||
+32
-17
@@ -1,4 +1,5 @@
|
||||
// Copyright (c) 2022 Robin Davies
|
||||
// Copyright (c) Robin E. R. Davies
|
||||
// Copyright (c) Gabriel Hernandez
|
||||
//
|
||||
// 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
|
||||
@@ -31,7 +32,9 @@ namespace pipedal
|
||||
bool isOnboarding_ = true;
|
||||
bool isJackAudio_ = JACK_HOST ? true : false;
|
||||
bool rebootRequired_ = false;
|
||||
std::string alsaDevice_;
|
||||
std::string alsaInputDevice_;
|
||||
std::string alsaOutputDevice_;
|
||||
std::string alsaDevice_; // legacy
|
||||
uint64_t sampleRate_ = 0;
|
||||
uint32_t bufferSize_ = 64;
|
||||
uint32_t numberOfBuffers_ = 3;
|
||||
@@ -39,10 +42,14 @@ namespace pipedal
|
||||
public:
|
||||
JackServerSettings();
|
||||
JackServerSettings(
|
||||
const std::string alsaInputDevice,
|
||||
uint64_t sampleRate, uint32_t bufferSize, uint32_t numberOfBuffers)
|
||||
const std::string &alsaInputDevice,
|
||||
const std::string &alsaOutputDevice,
|
||||
uint64_t sampleRate,
|
||||
uint32_t bufferSize,
|
||||
uint32_t numberOfBuffers)
|
||||
: valid_(true),
|
||||
alsaDevice_(alsaInputDevice),
|
||||
alsaInputDevice_(alsaInputDevice),
|
||||
alsaOutputDevice_(alsaOutputDevice),
|
||||
sampleRate_(sampleRate),
|
||||
bufferSize_(bufferSize),
|
||||
numberOfBuffers_(numberOfBuffers),
|
||||
@@ -51,32 +58,35 @@ namespace pipedal
|
||||
}
|
||||
|
||||
uint64_t GetSampleRate() const { return sampleRate_; }
|
||||
|
||||
uint32_t GetBufferSize() const { return bufferSize_; }
|
||||
uint32_t GetNumberOfBuffers() const { return numberOfBuffers_; }
|
||||
const std::string &GetAlsaInputDevice() const { return alsaDevice_; }
|
||||
const std::string &GetAlsaInputDevice() const { return alsaInputDevice_; }
|
||||
const std::string &GetAlsaOutputDevice() const { return alsaOutputDevice_; }
|
||||
const std::string &GetLegacyAlsaDevice() const { return alsaDevice_; } //legacy
|
||||
|
||||
void SetAlsaInputDevice(const std::string &d){ alsaInputDevice_ = d; }
|
||||
void SetAlsaOutputDevice(const std::string &d){ alsaOutputDevice_ = d; }
|
||||
void SetLegacyAlsaDevice(const std::string &d) { alsaDevice_ = d; }
|
||||
|
||||
void UseDummyAudioDevice() {
|
||||
this->valid_ = true;
|
||||
if (sampleRate_ == 0) sampleRate_ = 48000;
|
||||
this->alsaDevice_ = "dummy:channels_2";
|
||||
this->alsaInputDevice_ = "dummy:channels_2";
|
||||
this->alsaOutputDevice_ = "dummy:channels_2";
|
||||
}
|
||||
bool IsDummyAudioDevice() const {
|
||||
return
|
||||
this->alsaDevice_.starts_with("__DUMMY_AUDIO__")
|
||||
|| this->alsaDevice_.starts_with("dummy:");
|
||||
|| this->alsaDevice_.starts_with("dummy:")
|
||||
|| this->alsaInputDevice_.starts_with("dummy:");
|
||||
}
|
||||
|
||||
void ReadJackDaemonConfiguration();
|
||||
|
||||
bool IsValid() const { return valid_; }
|
||||
|
||||
// JackServerSettings(uint64_t sampleRate, uint32_t bufferSize, uint32_t numberOfBuffers)
|
||||
// {
|
||||
// this->valid_ = true;
|
||||
// this->rebootRequired_ = true;
|
||||
// this->sampleRate_ = sampleRate;
|
||||
// this->bufferSize_ = bufferSize;
|
||||
// this->numberOfBuffers_ = numberOfBuffers;
|
||||
// }
|
||||
void WriteDaemonConfig(); // requires root perms.
|
||||
void SetRebootRequired(bool value)
|
||||
{
|
||||
@@ -87,9 +97,14 @@ namespace pipedal
|
||||
isOnboarding_ = value;
|
||||
}
|
||||
|
||||
bool Equals(const JackServerSettings &other)
|
||||
bool Equals(const JackServerSettings &other) const
|
||||
{
|
||||
return this->alsaDevice_ == other.alsaDevice_ && this->sampleRate_ == other.sampleRate_ && this->bufferSize_ == other.bufferSize_ && this->numberOfBuffers_ == other.numberOfBuffers_;
|
||||
return this->alsaInputDevice_ == other.alsaInputDevice_ &&
|
||||
this->alsaOutputDevice_ == other.alsaOutputDevice_ &&
|
||||
this->alsaDevice_ == other.alsaDevice_ &&
|
||||
this->sampleRate_ == other.sampleRate_ &&
|
||||
this->bufferSize_ == other.bufferSize_ &&
|
||||
this->numberOfBuffers_ == other.numberOfBuffers_;
|
||||
}
|
||||
|
||||
DECLARE_JSON_MAP(JackServerSettings);
|
||||
|
||||
+504
-92
@@ -58,6 +58,28 @@ static fs::path makeAbsolutePath(const std::filesystem::path &path, const std::f
|
||||
return parentPath / path;
|
||||
}
|
||||
|
||||
inline void Lv2Effect::CheckStagingBufferSentries()
|
||||
{
|
||||
#ifndef NDEBUG
|
||||
for (size_t i = 0; i < inputStagingBuffers.size(); ++i)
|
||||
{
|
||||
if (inputStagingBuffers[i].at(stagingBufferSize) != 99.9f)
|
||||
{
|
||||
throw std::logic_error("Staging buffer sentry overwritten.");
|
||||
}
|
||||
|
||||
}
|
||||
for (size_t i = 0; i < outputStagingBuffers.size(); ++i)
|
||||
{
|
||||
if (outputStagingBuffers[i].at(stagingBufferSize) != 99.9f)
|
||||
{
|
||||
throw std::logic_error("Staging buffer sentry overwritten.");
|
||||
}
|
||||
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
Lv2Effect::Lv2Effect(
|
||||
IHost *pHost_,
|
||||
const std::shared_ptr<Lv2PluginInfo> &info_,
|
||||
@@ -66,8 +88,12 @@ Lv2Effect::Lv2Effect(
|
||||
{
|
||||
auto pWorld = pHost_->getWorld();
|
||||
|
||||
size_t stagedBufferSize = GetStagedBufferSize();
|
||||
|
||||
logFeature.Prepare(&pHost_->GetMapFeature(), info_->name() + ": ", this);
|
||||
|
||||
optionsFeature.Prepare(pHost->GetMapFeature(), 44100, stagedBufferSize, pHost->GetAtomBufferSize());
|
||||
|
||||
this->bypassStartingSamples = (uint32_t)(pHost->GetSampleRate() * BYPASS_TIME_S);
|
||||
|
||||
this->bypass = pedalboardItem.isEnabled();
|
||||
@@ -97,14 +123,15 @@ Lv2Effect::Lv2Effect(
|
||||
LV2_URID_Map *map = this->pHost->GetLv2UridMap();
|
||||
lv2_atom_forge_init(&inputForgeRt, map);
|
||||
lv2_atom_forge_init(&outputForgeRt, map);
|
||||
lv2_atom_forge_init(&stagedInputForgeRt, map);
|
||||
|
||||
const LilvPlugins *plugins = lilv_world_get_all_plugins(pWorld);
|
||||
|
||||
// xxx: could we not stash the pPlugin in the plugin info?
|
||||
// FIXME: could we not stash the pPlugin in the plugin info?
|
||||
auto uriNode = lilv_new_uri(pWorld, pedalboardItem.uri().c_str());
|
||||
const LilvPlugin *pPlugin = lilv_plugins_get_by_uri(plugins, uriNode);
|
||||
|
||||
for (auto&port : info->ports())
|
||||
for (auto &port : info->ports())
|
||||
{
|
||||
if (port->is_bypass())
|
||||
{
|
||||
@@ -115,7 +142,7 @@ Lv2Effect::Lv2Effect(
|
||||
lilv_node_free(uriNode);
|
||||
{
|
||||
AutoLilvNode bundleUri = lilv_plugin_get_bundle_uri(pPlugin);
|
||||
char* bundleUriString = lilv_file_uri_parse(bundleUri.AsUri().c_str(), nullptr);
|
||||
char *bundleUriString = lilv_file_uri_parse(bundleUri.AsUri().c_str(), nullptr);
|
||||
|
||||
std::string storagePath = pHost_->GetPluginStoragePath();
|
||||
|
||||
@@ -123,7 +150,7 @@ Lv2Effect::Lv2Effect(
|
||||
pHost_->GetMapFeature().GetMap(),
|
||||
logFeature.GetLog(),
|
||||
bundleUriString,
|
||||
storagePath);
|
||||
storagePath);
|
||||
|
||||
mapPathFeature.Prepare(&(pHost_->GetMapFeature()));
|
||||
mapPathFeature.SetPluginStoragePath(pHost_->GetPluginStoragePath());
|
||||
@@ -132,12 +159,10 @@ Lv2Effect::Lv2Effect(
|
||||
const auto &fileProperties = info_->piPedalUI()->fileProperties();
|
||||
for (const auto &fileProperty : fileProperties)
|
||||
{
|
||||
fs::path targetPath = fileProperty->directory() / std::filesystem::path(bundleUriString).parent_path().filename();
|
||||
mapPathFeature.AddResourceFileMapping({
|
||||
bundleUriString,
|
||||
storagePath / targetPath,
|
||||
fileProperty->fileTypes()
|
||||
});
|
||||
fs::path targetPath = fileProperty->directory() / std::filesystem::path(bundleUriString).parent_path().filename();
|
||||
mapPathFeature.AddResourceFileMapping({bundleUriString,
|
||||
storagePath / targetPath,
|
||||
fileProperty->fileTypes()});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -146,12 +171,16 @@ Lv2Effect::Lv2Effect(
|
||||
|
||||
LV2_Feature *const *features = pHost_->GetLv2Features();
|
||||
|
||||
this->features.push_back(logFeature.GetFeature());
|
||||
|
||||
for (auto p = features; *p != nullptr; ++p)
|
||||
{
|
||||
this->features.push_back(*p);
|
||||
if (strcmp((*p)->URI, LV2_LOG__log) != 0)
|
||||
{ // ommit the host's LOG feature.
|
||||
this->features.push_back(*p);
|
||||
}
|
||||
}
|
||||
this->features.push_back(logFeature.GetFeature());
|
||||
this->features.push_back(optionsFeature.GetFeature());
|
||||
|
||||
this->features.push_back(mapPathFeature.GetMapPathFeature());
|
||||
this->features.push_back(mapPathFeature.GetMakePathFeature());
|
||||
this->features.push_back(mapPathFeature.GetFreePathFeature());
|
||||
@@ -173,7 +202,7 @@ Lv2Effect::Lv2Effect(
|
||||
}
|
||||
this->features.push_back(nullptr);
|
||||
|
||||
const LV2_Feature **myFeatures = &this->features[0];
|
||||
const LV2_Feature **myFeatures = &this->features.at(0);
|
||||
|
||||
LilvInstance *pInstance = nullptr;
|
||||
try
|
||||
@@ -195,14 +224,14 @@ Lv2Effect::Lv2Effect(
|
||||
(const LV2_Worker_Interface *)lilv_instance_get_extension_data(pInstance,
|
||||
LV2_WORKER__interface);
|
||||
this->worker = std::make_unique<Worker>(workerThread, pInstance, worker_interface);
|
||||
|
||||
|
||||
const LV2_State_Interface *state_interface =
|
||||
(const LV2_State_Interface *)lilv_instance_get_extension_data(pInstance,
|
||||
LV2_STATE__interface);
|
||||
|
||||
if (state_interface)
|
||||
{
|
||||
this->stateInterface = std::make_unique<StateInterface>(pHost, &(this->features[0]), pInstance, state_interface);
|
||||
this->stateInterface = std::make_unique<StateInterface>(pHost, &(this->features.at(0)), pInstance, state_interface);
|
||||
}
|
||||
|
||||
this->instanceId = pedalboardItem.instanceId();
|
||||
@@ -235,7 +264,7 @@ Lv2Effect::Lv2Effect(
|
||||
int index = GetControlIndex(v.key());
|
||||
if (index != -1)
|
||||
{
|
||||
this->controlValues[index] = v.value();
|
||||
this->controlValues.at(index) = v.value();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -322,20 +351,20 @@ void Lv2Effect::ConnectControlPorts()
|
||||
int controlArrayLength = 0;
|
||||
for (int i = 0; i < info->ports().size(); ++i)
|
||||
{
|
||||
if (info->ports()[i]->index() >= controlArrayLength)
|
||||
if (info->ports().at(i)->index() >= controlArrayLength)
|
||||
{
|
||||
controlArrayLength = info->ports()[i]->index() + 1;
|
||||
controlArrayLength = info->ports().at(i)->index() + 1;
|
||||
}
|
||||
}
|
||||
this->realtimePortInfo.resize(controlArrayLength);
|
||||
for (int i = 0; i < info->ports().size(); ++i)
|
||||
{
|
||||
const auto &port = info->ports()[i];
|
||||
const auto &port = info->ports().at(i);
|
||||
if (port->is_control_port())
|
||||
{
|
||||
int index = port->index();
|
||||
realtimePortInfo[index] = port.get();
|
||||
lilv_instance_connect_port(pInstance, i, &this->controlValues[index]);
|
||||
realtimePortInfo.at(index) = port.get();
|
||||
lilv_instance_connect_port(pInstance, i, &this->controlValues.at(index));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -348,7 +377,7 @@ void Lv2Effect::PreparePortIndices()
|
||||
|
||||
for (int i = 0; i < info->ports().size(); ++i)
|
||||
{
|
||||
const auto &port = info->ports()[i];
|
||||
const auto &port = info->ports().at(i);
|
||||
|
||||
int portIndex = port->index();
|
||||
if (port->is_audio_port())
|
||||
@@ -386,17 +415,17 @@ void Lv2Effect::PreparePortIndices()
|
||||
controlIndex[port->symbol()] = portIndex;
|
||||
if (port->is_input())
|
||||
{
|
||||
this->isInputControlPort[portIndex] = true;
|
||||
this->defaultInputControlValues[portIndex] = port->default_value();
|
||||
this->isInputControlPort.at(portIndex) = true;
|
||||
this->defaultInputControlValues.at(portIndex) = port->default_value();
|
||||
if (port->trigger_property())
|
||||
{
|
||||
this->isInputTriggerControlPort[portIndex] = true;
|
||||
this->isInputTriggerControlPort.at(portIndex) = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
size_t maxInputControlPort = isInputControlPort.size();
|
||||
while (maxInputControlPort != 0 && !isInputControlPort[maxInputControlPort - 1])
|
||||
while (maxInputControlPort != 0 && !isInputControlPort.at(maxInputControlPort - 1))
|
||||
{
|
||||
--maxInputControlPort;
|
||||
}
|
||||
@@ -406,6 +435,14 @@ void Lv2Effect::PreparePortIndices()
|
||||
outputAudioBuffers.resize(outputAudioPortIndices.size());
|
||||
inputAtomBuffers.resize(inputAtomPortIndices.size());
|
||||
outputAtomBuffers.resize(outputAtomPortIndices.size());
|
||||
|
||||
if (RequiresBufferStaging())
|
||||
{
|
||||
EnableBufferStaging(
|
||||
GetStagedBufferSize(),
|
||||
this->GetNumberOfInputAudioBuffers(),
|
||||
this->GetNumberOfOutputAudioBuffers());
|
||||
}
|
||||
}
|
||||
|
||||
void Lv2Effect::PrepareNoInputEffect(int numberOfInputs, size_t maxBufferSize)
|
||||
@@ -425,20 +462,20 @@ void Lv2Effect::PrepareNoInputEffect(int numberOfInputs, size_t maxBufferSize)
|
||||
outputMixBuffers.resize(outputAudioPortIndices.size());
|
||||
for (size_t i = 0; i < outputMixBuffers.size(); ++i)
|
||||
{
|
||||
outputMixBuffers[i].resize(maxBufferSize);
|
||||
outputMixBuffers.at(i).resize(maxBufferSize);
|
||||
}
|
||||
// connect the plugin to the mix buffer instead of output buffer.
|
||||
for (size_t i = 0; i < outputAudioPortIndices.size(); ++i)
|
||||
{
|
||||
int pluginIndex = this->outputAudioPortIndices[i];
|
||||
lilv_instance_connect_port(this->pInstance, pluginIndex, outputMixBuffers[i].data());
|
||||
int pluginIndex = this->outputAudioPortIndices.at(i);
|
||||
lilv_instance_connect_port(this->pInstance, pluginIndex, outputMixBuffers.at(i).data());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Lv2Effect::SetAudioInputBuffer(int index, float *buffer)
|
||||
{
|
||||
this->inputAudioBuffers[index] = buffer;
|
||||
this->inputAudioBuffers.at(index) = buffer;
|
||||
|
||||
if (borrowedEffect)
|
||||
{
|
||||
@@ -449,8 +486,20 @@ void Lv2Effect::SetAudioInputBuffer(int index, float *buffer)
|
||||
|
||||
if (inputAudioPortIndices.size() == inputAudioBuffers.size())
|
||||
{
|
||||
int pluginIndex = this->inputAudioPortIndices[index];
|
||||
lilv_instance_connect_port(this->pInstance, pluginIndex, buffer);
|
||||
if (stagingBufferSize != 0)
|
||||
{
|
||||
int pluginIndex = this->inputAudioPortIndices.at(index);
|
||||
if (index >= inputStagingBufferPointers.size())
|
||||
{
|
||||
throw std::runtime_error("Invalid input staging buffer index.");
|
||||
}
|
||||
lilv_instance_connect_port(this->pInstance, pluginIndex, inputStagingBufferPointers.at(index));
|
||||
}
|
||||
else
|
||||
{
|
||||
int pluginIndex = this->inputAudioPortIndices.at(index);
|
||||
lilv_instance_connect_port(this->pInstance, pluginIndex, buffer);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -458,7 +507,7 @@ void Lv2Effect::SetAudioInputBuffer(int index, float *buffer)
|
||||
// // cases: 1->0, 1->1, 2->0, 2->1
|
||||
// if (index < inputAudioPortIndices.size())
|
||||
// {
|
||||
// int pluginIndex = this->inputAudioPortIndices[index];
|
||||
// int pluginIndex = this->inputAudioPortIndices.at(index);
|
||||
// lilv_instance_connect_port(this->pInstance, pluginIndex, buffer);
|
||||
// }
|
||||
}
|
||||
@@ -492,7 +541,7 @@ void Lv2Effect::SetAudioInputBuffers(float *left, float *right)
|
||||
|
||||
void Lv2Effect::SetAudioOutputBuffer(int index, float *buffer)
|
||||
{
|
||||
this->outputAudioBuffers[index] = buffer;
|
||||
this->outputAudioBuffers.at(index) = buffer;
|
||||
|
||||
if (borrowedEffect)
|
||||
{
|
||||
@@ -503,10 +552,25 @@ void Lv2Effect::SetAudioOutputBuffer(int index, float *buffer)
|
||||
|
||||
if (this->inputAudioPortIndices.size() != 0) // i.e. we're not mixing a zero-input control
|
||||
{
|
||||
if ((size_t)index < this->outputAudioPortIndices.size())
|
||||
if (this->stagingBufferSize != 0)
|
||||
{
|
||||
int pluginIndex = this->outputAudioPortIndices[index];
|
||||
lilv_instance_connect_port(pInstance, pluginIndex, buffer);
|
||||
if ((size_t)index < this->outputStagingBufferPointers.size())
|
||||
{
|
||||
int pluginIndex = this->outputAudioPortIndices.at(index);
|
||||
lilv_instance_connect_port(pInstance, pluginIndex, outputStagingBufferPointers.at(index));
|
||||
}
|
||||
else
|
||||
{
|
||||
throw std::runtime_error("outputStagingBufferPointers index out of range.");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if ((size_t)index < this->outputAudioPortIndices.size())
|
||||
{
|
||||
int pluginIndex = this->outputAudioPortIndices.at(index);
|
||||
lilv_instance_connect_port(pInstance, pluginIndex, buffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -523,6 +587,16 @@ int Lv2Effect::GetControlIndex(const std::string &key) const
|
||||
|
||||
Lv2Effect::~Lv2Effect()
|
||||
{
|
||||
if (deleted)
|
||||
{
|
||||
try {
|
||||
throw std::runtime_error("Deleted twice!");
|
||||
} catch (const std::exception&e)
|
||||
{
|
||||
std::terminate();
|
||||
}
|
||||
}
|
||||
deleted = true;
|
||||
if (worker)
|
||||
{
|
||||
worker->Close();
|
||||
@@ -531,6 +605,7 @@ Lv2Effect::~Lv2Effect()
|
||||
if (activated)
|
||||
{
|
||||
Deactivate();
|
||||
activated = false;
|
||||
}
|
||||
if (pInstance)
|
||||
{
|
||||
@@ -540,7 +615,9 @@ Lv2Effect::~Lv2Effect()
|
||||
if (work_schedule_feature)
|
||||
{
|
||||
free(work_schedule_feature->data);
|
||||
work_schedule_feature->data = nullptr;
|
||||
free(work_schedule_feature);
|
||||
work_schedule_feature = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -556,9 +633,11 @@ void Lv2Effect::Activate()
|
||||
if (this->bypassControlIndex == -1)
|
||||
{
|
||||
this->BypassDezipperSet(this->bypass ? 1.0f : 0.0f);
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
this->BypassDezipperSet(1.0f);
|
||||
this->controlValues[this->bypassControlIndex] = this->bypass ? 1.0f: 0.0f;
|
||||
this->controlValues.at(this->bypassControlIndex) = this->bypass ? 1.0f : 0.0f;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -567,21 +646,91 @@ void Lv2Effect::UpdateAudioPorts()
|
||||
// called on realtime thread to switch borrowed effects to the new buffer pointers.
|
||||
if (borrowedEffect)
|
||||
{
|
||||
for (size_t i = 0; i < this->inputAudioPortIndices.size(); ++i)
|
||||
if (stagingBufferSize != 0)
|
||||
{
|
||||
int portIndex = this->inputAudioPortIndices[i];
|
||||
if (GetAudioInputBuffer(i) != nullptr)
|
||||
for (size_t i = 0; i < this->inputAudioPortIndices.size(); ++i)
|
||||
{
|
||||
lilv_instance_connect_port(pInstance, portIndex, GetAudioInputBuffer(i));
|
||||
int portIndex = this->inputAudioPortIndices.at(i);
|
||||
if (inputStagingBufferPointers.at(i) != nullptr)
|
||||
{
|
||||
lilv_instance_connect_port(pInstance, portIndex, inputStagingBufferPointers.at(i));
|
||||
}
|
||||
}
|
||||
}
|
||||
for (size_t i = 0; i < this->outputAudioPortIndices.size(); ++i)
|
||||
{
|
||||
int portIndex = this->outputAudioPortIndices[i];
|
||||
if (GetAudioOutputBuffer(i) != nullptr)
|
||||
for (size_t i = 0; i < this->outputAudioPortIndices.size(); ++i)
|
||||
{
|
||||
lilv_instance_connect_port(pInstance, portIndex, GetAudioOutputBuffer(i));
|
||||
int portIndex = this->outputAudioPortIndices.at(i);
|
||||
if (outputStagingBufferPointers.at(i) != nullptr)
|
||||
{
|
||||
lilv_instance_connect_port(pInstance, portIndex, outputStagingBufferPointers.at(i));
|
||||
}
|
||||
}
|
||||
for (size_t i = 0; i < this->inputAtomPortIndices.size(); ++i)
|
||||
{
|
||||
if (i == 0)
|
||||
{
|
||||
if (stagedInputAtomBufferPointer == nullptr)
|
||||
{
|
||||
throw std::runtime_error("Invalid astagedInputAtomBufferPointer");
|
||||
}
|
||||
lilv_instance_connect_port(pInstance, inputAtomPortIndices[i],stagedInputAtomBufferPointer);
|
||||
} else {
|
||||
auto atomInputBuffer = this->GetAtomInputBuffer(i);
|
||||
lilv_instance_connect_port(pInstance, inputAtomPortIndices[i],atomInputBuffer);
|
||||
}
|
||||
}
|
||||
for (size_t i = 0; i < this->outputAtomPortIndices.size(); ++i)
|
||||
{
|
||||
if (i == 0)
|
||||
{
|
||||
if (stagedOutputAtomBufferPointer == nullptr)
|
||||
{
|
||||
throw std::runtime_error("Invalid astagedOutputAtomBufferPointer");
|
||||
}
|
||||
lilv_instance_connect_port(pInstance, outputAtomPortIndices[i],stagedOutputAtomBufferPointer);
|
||||
} else {
|
||||
auto atomOutputBuffer = this->GetAtomOutputBuffer(i);
|
||||
lilv_instance_connect_port(pInstance, outputAtomPortIndices[i],atomOutputBuffer);
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
for (size_t i = 0; i < this->inputAudioPortIndices.size(); ++i)
|
||||
{
|
||||
int portIndex = this->inputAudioPortIndices.at(i);
|
||||
if (GetAudioInputBuffer(i) != nullptr)
|
||||
{
|
||||
lilv_instance_connect_port(pInstance, portIndex, GetAudioInputBuffer(i));
|
||||
}
|
||||
}
|
||||
for (size_t i = 0; i < this->outputAudioPortIndices.size(); ++i)
|
||||
{
|
||||
int portIndex = this->outputAudioPortIndices.at(i);
|
||||
if (GetAudioOutputBuffer(i) != nullptr)
|
||||
{
|
||||
lilv_instance_connect_port(pInstance, portIndex, GetAudioOutputBuffer(i));
|
||||
}
|
||||
}
|
||||
for (size_t i = 0; i < this->inputAtomPortIndices.size(); ++i)
|
||||
{
|
||||
auto atomInputBuffer = this->GetAtomInputBuffer(i);
|
||||
lilv_instance_connect_port(pInstance, inputAtomPortIndices[i],atomInputBuffer);
|
||||
}
|
||||
for (size_t i = 0; i < this->outputAtomPortIndices.size(); ++i)
|
||||
{
|
||||
auto atomOutputBuffer = this->GetAtomOutputBuffer(i);
|
||||
lilv_instance_connect_port(pInstance, outputAtomPortIndices[i],atomOutputBuffer);
|
||||
}
|
||||
// for (size_t i = 0; i < this->inputMidiPortIndices.size(); ++i)
|
||||
// {
|
||||
// auto midiInputBuffer = this->GetMidiInputBuffer(i);
|
||||
// lilv_instance_connect_port(pInstance, inputMidiPortIndices[i],midiInputBuffer);
|
||||
// }
|
||||
// for (size_t i = 0; i < this->outputMidiPortIndices.size(); ++i)
|
||||
// {
|
||||
// auto midiOutputBuffer = this->GetMidiOutputBuffer(i);
|
||||
// lilv_instance_connect_port(pInstance, outputMidiPortIndices[i],midiOutputBuffer);
|
||||
// }
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -592,7 +741,7 @@ void Lv2Effect::AssignUnconnectedPorts()
|
||||
{
|
||||
if (GetAudioInputBuffer(i) == nullptr)
|
||||
{
|
||||
int pluginIndex = this->inputAudioPortIndices[i];
|
||||
int pluginIndex = this->inputAudioPortIndices.at(i);
|
||||
|
||||
float *buffer = bufferPool.AllocateBuffer<float>(pHost->GetMaxAudioBufferSize());
|
||||
lilv_instance_connect_port(pInstance, pluginIndex, buffer);
|
||||
@@ -606,7 +755,7 @@ void Lv2Effect::AssignUnconnectedPorts()
|
||||
if (GetAudioOutputBuffer(i) == nullptr)
|
||||
{
|
||||
float *buffer = bufferPool.AllocateBuffer<float>(pHost->GetMaxAudioBufferSize());
|
||||
int pluginIndex = this->outputAudioPortIndices[i];
|
||||
int pluginIndex = this->outputAudioPortIndices.at(i);
|
||||
lilv_instance_connect_port(pInstance, pluginIndex, buffer);
|
||||
}
|
||||
}
|
||||
@@ -615,24 +764,42 @@ void Lv2Effect::AssignUnconnectedPorts()
|
||||
{
|
||||
if (GetAtomInputBuffer(i) == nullptr)
|
||||
{
|
||||
int pluginIndex = this->inputAtomPortIndices[i];
|
||||
int pluginIndex = this->inputAtomPortIndices.at(i);
|
||||
|
||||
uint8_t *buffer = bufferPool.AllocateBuffer<uint8_t>(pHost->GetAtomBufferSize());
|
||||
lilv_instance_connect_port(pInstance, pluginIndex, buffer);
|
||||
if (stagedInputAtomBufferPointer && i == 0)
|
||||
{
|
||||
lilv_instance_connect_port(pInstance, pluginIndex, stagedInputAtomBufferPointer);
|
||||
ResetInputAtomBuffer((char *)(stagedInputAtomBufferPointer));
|
||||
}
|
||||
else
|
||||
{
|
||||
lilv_instance_connect_port(pInstance, pluginIndex, buffer);
|
||||
}
|
||||
ResetInputAtomBuffer((char *)buffer);
|
||||
this->inputAtomBuffers[i] = (char *)buffer;
|
||||
this->inputAtomBuffers.at(i) = (char *)buffer;
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < this->GetNumberOfOutputAtomPorts(); ++i)
|
||||
{
|
||||
if (GetAtomOutputBuffer(i) == nullptr)
|
||||
{
|
||||
int pluginIndex = this->outputAtomPortIndices[i];
|
||||
int pluginIndex = this->outputAtomPortIndices.at(i);
|
||||
|
||||
uint8_t *buffer = bufferPool.AllocateBuffer<uint8_t>(pHost->GetAtomBufferSize());
|
||||
ResetOutputAtomBuffer((char *)buffer);
|
||||
|
||||
if (stagedOutputAtomBufferPointer && i == 0)
|
||||
{
|
||||
lilv_instance_connect_port(pInstance, pluginIndex, stagedOutputAtomBufferPointer);
|
||||
}
|
||||
else
|
||||
{
|
||||
lilv_instance_connect_port(pInstance, pluginIndex, buffer);
|
||||
}
|
||||
|
||||
lilv_instance_connect_port(pInstance, pluginIndex, buffer);
|
||||
this->outputAtomBuffers[i] = (char *)buffer;
|
||||
this->outputAtomBuffers.at(i) = (char *)buffer;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -658,21 +825,154 @@ static inline void CopyBuffer(float *restrict input, float *restrict output, uin
|
||||
}
|
||||
}
|
||||
|
||||
void Lv2Effect::Run(uint32_t samples, RealtimeRingBufferWriter *realtimeRingBufferWriter)
|
||||
size_t Lv2Effect::stageToOutput(size_t outputIndex, size_t nFrames)
|
||||
{
|
||||
// close off the atom input frame.
|
||||
size_t thisTime = nFrames - outputIndex;
|
||||
size_t stagedOutputAvailable = this->stagingBufferSize - this->stagingOutputIx;
|
||||
if (stagedOutputAvailable < thisTime)
|
||||
{
|
||||
thisTime = stagedOutputAvailable;
|
||||
}
|
||||
if (thisTime)
|
||||
{
|
||||
for (size_t ch = 0; ch < this->GetNumberOfOutputAudioBuffers(); ++ch)
|
||||
{
|
||||
float *restrict pIn = this->outputStagingBufferPointers.at(ch) + this->stagingOutputIx;
|
||||
float *restrict pOut = this->GetAudioOutputBuffer(ch) + outputIndex;
|
||||
for (size_t i = 0; i < thisTime; ++i)
|
||||
{
|
||||
pOut[i] = pIn[i];
|
||||
}
|
||||
}
|
||||
this->stagingOutputIx += thisTime;
|
||||
}
|
||||
return outputIndex + thisTime;
|
||||
}
|
||||
|
||||
void Lv2Effect::copyAtomBufferEventSequence(LV2_Atom_Sequence *controlInput, LV2_Atom_Forge &outputForge)
|
||||
{
|
||||
LV2_ATOM_SEQUENCE_FOREACH(controlInput, ev)
|
||||
{
|
||||
lv2_atom_forge_frame_time(&outputForge, ev->time.frames);
|
||||
lv2_atom_forge_raw(&outputForge, &(ev->body), ev->body.size); // literal copy of the atom body.
|
||||
}
|
||||
}
|
||||
|
||||
size_t Lv2Effect::stageToInput(size_t inputSampleOffset, size_t samples)
|
||||
{
|
||||
|
||||
size_t thisTime = samples - inputSampleOffset;
|
||||
size_t inputAvailable = this->stagingBufferSize - this->stagingInputIx;
|
||||
if (thisTime > inputAvailable)
|
||||
{
|
||||
thisTime = inputAvailable;
|
||||
}
|
||||
// copy into staging buffers.
|
||||
for (size_t nInput = 0; nInput < this->inputAudioBuffers.size(); ++nInput)
|
||||
{
|
||||
float *restrict pInput = this->inputAudioBuffers[nInput] + inputSampleOffset;
|
||||
float *restrict pOutput = this->inputStagingBufferPointers.at(nInput) + this->stagingInputIx;
|
||||
for (size_t i = 0; i < thisTime; ++i)
|
||||
{
|
||||
pOutput[i] = pInput[i];
|
||||
}
|
||||
}
|
||||
this->stagingInputIx += thisTime;
|
||||
inputSampleOffset += thisTime;
|
||||
|
||||
if (stagingInputIx == this->stagingBufferSize)
|
||||
{
|
||||
// close off the atom input frame.
|
||||
if (stagedInputAtomBufferPointer)
|
||||
{
|
||||
lv2_atom_forge_pop(&this->stagedInputForgeRt, &staged_input_frame);
|
||||
}
|
||||
if (stagedOutputAtomBufferPointer)
|
||||
{
|
||||
ResetOutputAtomBuffer((char *)stagedOutputAtomBufferPointer);
|
||||
}
|
||||
|
||||
lilv_instance_run(pInstance, this->stagingBufferSize);
|
||||
|
||||
if (worker)
|
||||
{
|
||||
|
||||
worker->EmitResponses();
|
||||
}
|
||||
if (stagedOutputAtomBufferPointer)
|
||||
{
|
||||
copyAtomBufferEventSequence((LV2_Atom_Sequence *)stagedOutputAtomBufferPointer, this->outputForgeRt);
|
||||
}
|
||||
|
||||
this->stagingInputIx = 0;
|
||||
this->stagingOutputIx = 0;
|
||||
this->resetStagedInputAtomBuffer();
|
||||
}
|
||||
return inputSampleOffset;
|
||||
}
|
||||
|
||||
void Lv2Effect::resetStagedInputAtomBuffer()
|
||||
{
|
||||
if (stagedInputAtomBufferPointer)
|
||||
{
|
||||
const uint32_t notify_capacity = pHost->GetAtomBufferSize();
|
||||
lv2_atom_forge_set_buffer(
|
||||
&(this->stagedInputForgeRt), (uint8_t *)(this->stagedInputAtomBufferPointer), notify_capacity);
|
||||
lv2_atom_forge_sequence_head(&this->inputForgeRt, &staged_input_frame, urids.units__frame);
|
||||
}
|
||||
}
|
||||
void Lv2Effect::RunWithBufferStaging(uint32_t samples, RealtimeRingBufferWriter *realtimeRingBufferWriter)
|
||||
{
|
||||
// accumulte control input sequence until we can execute a run operation.
|
||||
if (this->inputAtomBuffers.size() != 0)
|
||||
{
|
||||
lv2_atom_forge_pop(&this->inputForgeRt, &input_frame);
|
||||
|
||||
LV2_Atom_Sequence *controlInput = (LV2_Atom_Sequence *)GetAtomInputBuffer(0);
|
||||
copyAtomBufferEventSequence(controlInput, this->stagedInputForgeRt);
|
||||
}
|
||||
|
||||
lilv_instance_run(pInstance, samples);
|
||||
|
||||
if (worker)
|
||||
// Prepare ACTUAL control output port.
|
||||
if (this->stagedOutputAtomBufferPointer)
|
||||
{
|
||||
// relay worker response
|
||||
worker->EmitResponses();
|
||||
const uint32_t notify_capacity = pHost->GetAtomBufferSize();
|
||||
lv2_atom_forge_set_buffer(
|
||||
&(this->outputForgeRt), (uint8_t *)(this->inputAtomBuffers.at(0)), notify_capacity);
|
||||
lv2_atom_forge_sequence_head(&this->outputForgeRt, &output_frame, urids.units__frame);
|
||||
}
|
||||
|
||||
uint32_t inputSampleOffset = 0;
|
||||
uint32_t outputSampleOffset = 0;
|
||||
|
||||
while (true)
|
||||
{
|
||||
outputSampleOffset = stageToOutput(outputSampleOffset, samples);
|
||||
|
||||
CheckStagingBufferSentries();
|
||||
|
||||
if (inputSampleOffset == samples)
|
||||
{
|
||||
break;
|
||||
}
|
||||
inputSampleOffset = stageToInput(inputSampleOffset, samples);
|
||||
}
|
||||
// no staging data avaialble? Output zeros.
|
||||
if (outputSampleOffset != samples)
|
||||
{
|
||||
size_t thisTime = samples - outputSampleOffset;
|
||||
for (size_t ch = 0; ch < this->GetNumberOfOutputAudioBuffers(); ++ch)
|
||||
{
|
||||
float *pOut = this->GetAudioOutputBuffer(ch) + outputSampleOffset;
|
||||
for (size_t i = 0; i < thisTime; ++i)
|
||||
{
|
||||
pOut[i] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
MixOutput(samples, realtimeRingBufferWriter);
|
||||
}
|
||||
|
||||
inline void Lv2Effect::MixOutput(uint32_t samples, RealtimeRingBufferWriter *realtimeRingBufferWriter)
|
||||
{
|
||||
// for zero-input plugins, mix the plugin output with the input signal.
|
||||
if (this->inputAudioPortIndices.size() == 0)
|
||||
{
|
||||
@@ -695,14 +995,14 @@ void Lv2Effect::Run(uint32_t samples, RealtimeRingBufferWriter *realtimeRingBuff
|
||||
{
|
||||
break;
|
||||
}
|
||||
input = this->inputAudioBuffers[0];
|
||||
input = this->inputAudioBuffers.at(0);
|
||||
}
|
||||
else
|
||||
{
|
||||
input = this->inputAudioBuffers[i];
|
||||
input = this->inputAudioBuffers.at(i);
|
||||
}
|
||||
float *restrict pluginOutput = this->outputMixBuffers[i].data();
|
||||
float *restrict finalOutput = this->outputAudioBuffers[i];
|
||||
float *restrict pluginOutput = this->outputMixBuffers.at(i).data();
|
||||
float *restrict finalOutput = this->outputAudioBuffers.at(i);
|
||||
|
||||
for (uint32_t i = 0; i < samples; ++i)
|
||||
{
|
||||
@@ -713,11 +1013,11 @@ void Lv2Effect::Run(uint32_t samples, RealtimeRingBufferWriter *realtimeRingBuff
|
||||
else if (this->outputAudioPortIndices.size() == 1 && this->outputAudioBuffers.size() == 2)
|
||||
{
|
||||
// 1 plugin output into 2 outputs.
|
||||
float *restrict pluginOutput = this->outputMixBuffers[0].data();
|
||||
float *restrict pluginOutput = this->outputMixBuffers.at(0).data();
|
||||
for (size_t i = 0; i < this->outputMixBuffers.size(); ++i)
|
||||
{
|
||||
float *restrict input = this->inputAudioBuffers[i];
|
||||
float *restrict finalOutput = this->outputAudioBuffers[i];
|
||||
float *restrict input = this->inputAudioBuffers.at(i);
|
||||
float *restrict finalOutput = this->outputAudioBuffers.at(i);
|
||||
|
||||
for (uint32_t i = 0; i < samples; ++i)
|
||||
{
|
||||
@@ -740,19 +1040,19 @@ void Lv2Effect::Run(uint32_t samples, RealtimeRingBufferWriter *realtimeRingBuff
|
||||
// replace the contents of the output buffer(s) with the input buffer(s).
|
||||
if (this->outputAudioBuffers.size() == 1)
|
||||
{
|
||||
CopyBuffer(this->inputAudioBuffers[0], this->outputAudioBuffers[0], samples);
|
||||
CopyBuffer(this->inputAudioBuffers.at(0), this->outputAudioBuffers.at(0), samples);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (this->inputAudioBuffers.size() == 1)
|
||||
{
|
||||
CopyBuffer(this->inputAudioBuffers[0], this->outputAudioBuffers[0], samples);
|
||||
CopyBuffer(this->inputAudioBuffers[0], this->outputAudioBuffers[1], samples);
|
||||
CopyBuffer(this->inputAudioBuffers.at(0), this->outputAudioBuffers.at(0), samples);
|
||||
CopyBuffer(this->inputAudioBuffers.at(0), this->outputAudioBuffers.at(1), samples);
|
||||
}
|
||||
else
|
||||
{
|
||||
CopyBuffer(this->inputAudioBuffers[0], this->outputAudioBuffers[0], samples);
|
||||
CopyBuffer(this->inputAudioBuffers[1], this->outputAudioBuffers[1], samples);
|
||||
CopyBuffer(this->inputAudioBuffers.at(0), this->outputAudioBuffers.at(0), samples);
|
||||
CopyBuffer(this->inputAudioBuffers.at(1), this->outputAudioBuffers.at(1), samples);
|
||||
}
|
||||
}
|
||||
} // else leave the output alone.
|
||||
@@ -765,8 +1065,8 @@ void Lv2Effect::Run(uint32_t samples, RealtimeRingBufferWriter *realtimeRingBuff
|
||||
|
||||
if (this->outputAudioBuffers.size() == 1)
|
||||
{
|
||||
float * restrict input = this->inputAudioBuffers[0];
|
||||
float * restrict output = this->outputAudioBuffers[0];
|
||||
float *restrict input = this->inputAudioBuffers.at(0);
|
||||
float *restrict output = this->outputAudioBuffers.at(0);
|
||||
for (uint32_t i = 0; i < samples; ++i)
|
||||
{
|
||||
output[i] = currentBypass * output[i] + (1 - currentBypass) * input[i];
|
||||
@@ -781,19 +1081,19 @@ void Lv2Effect::Run(uint32_t samples, RealtimeRingBufferWriter *realtimeRingBuff
|
||||
}
|
||||
else
|
||||
{
|
||||
float * restrict inputL;
|
||||
float * restrict inputR;
|
||||
float *restrict inputL;
|
||||
float *restrict inputR;
|
||||
if (this->inputAudioBuffers.size() == 1)
|
||||
{
|
||||
inputL = inputR = inputAudioBuffers[0];
|
||||
inputL = inputR = inputAudioBuffers.at(0);
|
||||
}
|
||||
else
|
||||
{
|
||||
inputL = inputAudioBuffers[0];
|
||||
inputR = inputAudioBuffers[1];
|
||||
inputL = inputAudioBuffers.at(0);
|
||||
inputR = inputAudioBuffers.at(1);
|
||||
}
|
||||
float * restrict outputL = outputAudioBuffers[0];
|
||||
float * restrict outputR = outputAudioBuffers[1];
|
||||
float *restrict outputL = outputAudioBuffers.at(0);
|
||||
float *restrict outputR = outputAudioBuffers.at(1);
|
||||
for (uint32_t i = 0; i < samples; ++i)
|
||||
{
|
||||
outputL[i] = currentBypass * outputL[i] + (1 - currentBypass) * inputL[i];
|
||||
@@ -822,6 +1122,24 @@ void Lv2Effect::Run(uint32_t samples, RealtimeRingBufferWriter *realtimeRingBuff
|
||||
RelayPatchSetMessages(this->instanceId, realtimeRingBufferWriter);
|
||||
}
|
||||
|
||||
void Lv2Effect::Run(uint32_t samples, RealtimeRingBufferWriter *realtimeRingBufferWriter)
|
||||
{
|
||||
// close off the atom input frame.
|
||||
if (this->inputAtomBuffers.size() != 0)
|
||||
{
|
||||
lv2_atom_forge_pop(&this->inputForgeRt, &input_frame);
|
||||
}
|
||||
lilv_instance_run(pInstance, samples);
|
||||
|
||||
if (worker)
|
||||
{
|
||||
// relay worker response
|
||||
worker->EmitResponses();
|
||||
}
|
||||
|
||||
MixOutput(samples, realtimeRingBufferWriter);
|
||||
}
|
||||
|
||||
LV2_Worker_Status Lv2Effect::worker_schedule_fn(LV2_Worker_Schedule_Handle handle,
|
||||
uint32_t size,
|
||||
const void *data)
|
||||
@@ -880,17 +1198,17 @@ void Lv2Effect::ResetAtomBuffers()
|
||||
{
|
||||
for (size_t i = 0; i < this->inputAtomBuffers.size(); ++i)
|
||||
{
|
||||
ResetInputAtomBuffer(this->inputAtomBuffers[i]);
|
||||
ResetInputAtomBuffer(this->inputAtomBuffers.at(i));
|
||||
}
|
||||
for (size_t i = 0; i < this->outputAtomBuffers.size(); ++i)
|
||||
{
|
||||
ResetOutputAtomBuffer(this->outputAtomBuffers[i]);
|
||||
ResetOutputAtomBuffer(this->outputAtomBuffers.at(i));
|
||||
}
|
||||
if (inputAtomBuffers.size() != 0)
|
||||
{
|
||||
const uint32_t notify_capacity = pHost->GetAtomBufferSize();
|
||||
lv2_atom_forge_set_buffer(
|
||||
&(this->inputForgeRt), (uint8_t *)(this->inputAtomBuffers[0]), notify_capacity);
|
||||
&(this->inputForgeRt), (uint8_t *)(this->inputAtomBuffers.at(0)), notify_capacity);
|
||||
|
||||
// Start a sequence in the notify input port.
|
||||
|
||||
@@ -1147,13 +1465,13 @@ uint64_t Lv2Effect::GetMaxInputControl() const { return maxInputControlPort; }
|
||||
|
||||
bool Lv2Effect::IsInputControl(uint64_t index) const
|
||||
{
|
||||
if (index < 0 || index >= isInputControlPort.size())
|
||||
if (index >= isInputControlPort.size())
|
||||
return false;
|
||||
return isInputControlPort[index];
|
||||
return isInputControlPort.at(index);
|
||||
}
|
||||
float Lv2Effect::GetDefaultInputControlValue(uint64_t index) const
|
||||
{
|
||||
return defaultInputControlValues[index];
|
||||
return defaultInputControlValues.at(index);
|
||||
}
|
||||
|
||||
std::string Lv2Effect::GetPathPatchProperty(const std::string &propertyUri)
|
||||
@@ -1168,3 +1486,97 @@ void Lv2Effect::SetPathPatchProperty(const std::string &propertyUri, const std::
|
||||
{
|
||||
mainThreadPathProperties[propertyUri] = jsonAtom;
|
||||
}
|
||||
|
||||
void Lv2Effect::EnableBufferStaging(size_t bufferSize, size_t nInputs, size_t nOutputs)
|
||||
{
|
||||
|
||||
stagingBufferSize = bufferSize;
|
||||
stagingOutputIx = bufferSize;
|
||||
stagingInputIx = 0;
|
||||
inputStagingBuffers.resize(nInputs);
|
||||
outputStagingBuffers.resize(nOutputs);
|
||||
inputStagingBufferPointers.resize(nInputs);
|
||||
outputStagingBufferPointers.resize(nOutputs);
|
||||
|
||||
if (inputAtomBuffers.size() != 0)
|
||||
{
|
||||
stagedInputAtomBuffer.resize(pHost->GetAtomBufferSize());
|
||||
stagedInputAtomBufferPointer = stagedInputAtomBuffer.data();
|
||||
resetStagedInputAtomBuffer();
|
||||
}
|
||||
else
|
||||
{
|
||||
stagedInputAtomBufferPointer = nullptr;
|
||||
}
|
||||
stagedOutputAtomBufferPointer = nullptr;
|
||||
if (outputAtomBuffers.size() != 0)
|
||||
{
|
||||
stagedOutputAtomBuffer.resize(pHost->GetAtomBufferSize());
|
||||
stagedOutputAtomBufferPointer = stagedOutputAtomBuffer.data();
|
||||
}
|
||||
for (size_t i = 0; i < nInputs; ++i)
|
||||
{
|
||||
inputStagingBuffers.at(i).resize(bufferSize + 1);
|
||||
inputStagingBuffers[i][bufferSize] = 99.9f; // guard entry
|
||||
inputStagingBufferPointers.at(i) = inputStagingBuffers.at(i).data();
|
||||
}
|
||||
for (size_t i = 0; i < nOutputs; ++i)
|
||||
{
|
||||
outputStagingBuffers.at(i).resize(bufferSize + 1);
|
||||
outputStagingBuffers[i][bufferSize] = 99.9f; // guard entry
|
||||
outputStagingBufferPointers.at(i) = outputStagingBuffers.at(i).data();
|
||||
}
|
||||
}
|
||||
|
||||
static size_t NextPowerOfTwo(size_t value)
|
||||
{
|
||||
|
||||
size_t i = 1;
|
||||
while (i < value && i < 65536UL)
|
||||
{
|
||||
i *= 2;
|
||||
}
|
||||
return i;
|
||||
}
|
||||
|
||||
size_t Lv2Effect::GetStagedBufferSize() const
|
||||
{
|
||||
size_t pluginBlockLength = pHost->GetMaxAudioBufferSize();
|
||||
if (info->minBlockLength() != -1 || info->maxBlockLength() != -1)
|
||||
{
|
||||
if (info->minBlockLength() != -1 && pluginBlockLength < info->minBlockLength())
|
||||
{
|
||||
pluginBlockLength = info->minBlockLength();
|
||||
}
|
||||
if (info->maxBlockLength() != -1 && pluginBlockLength > info->maxBlockLength())
|
||||
{
|
||||
pluginBlockLength = info->maxBlockLength();
|
||||
}
|
||||
if (info->powerOf2BlockLength())
|
||||
{
|
||||
pluginBlockLength = NextPowerOfTwo(pluginBlockLength);
|
||||
}
|
||||
}
|
||||
return pluginBlockLength;
|
||||
}
|
||||
|
||||
bool Lv2Effect::RequiresBufferStaging() const
|
||||
{
|
||||
return GetStagedBufferSize() != pHost->GetMaxAudioBufferSize();
|
||||
}
|
||||
|
||||
float *Lv2Effect::GetAudioInputBuffer(int index) const
|
||||
{
|
||||
if (index < 0 || index >= this->inputAudioBuffers.size())
|
||||
throw std::range_error("Lv2Effect::GetAudioInputBuffer");
|
||||
return this->inputAudioBuffers.at(index);
|
||||
}
|
||||
|
||||
float *Lv2Effect::GetAudioOutputBuffer(int index) const
|
||||
{
|
||||
if (index < 0 || index >= this->outputAudioBuffers.size())
|
||||
{
|
||||
throw std::range_error("Lv2Effect::GetAudioOutputBuffer");
|
||||
}
|
||||
return this->outputAudioBuffers.at(index);
|
||||
}
|
||||
|
||||
+37
-4
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) 2022 Robin Davies
|
||||
// Copyright (c) 2025 Robin 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
|
||||
@@ -28,6 +28,7 @@
|
||||
#include "PatchPropertyWriter.hpp"
|
||||
#include <unordered_map>
|
||||
#include "MapPathFeature.hpp"
|
||||
#include "OptionsFeature.hpp"
|
||||
|
||||
#include "IEffect.hpp"
|
||||
#include "Worker.hpp"
|
||||
@@ -56,7 +57,7 @@ namespace pipedal
|
||||
virtual void OnLogDebug(const char*message);
|
||||
|
||||
private:
|
||||
|
||||
size_t GetStagedBufferSize() const;
|
||||
|
||||
std::shared_ptr<HostWorkerThread> workerThread;
|
||||
std::unique_ptr<Worker> worker;
|
||||
@@ -115,6 +116,7 @@ namespace pipedal
|
||||
LV2_Atom_Forge_Frame input_frame;
|
||||
|
||||
LV2_Atom_Forge outputForgeRt;
|
||||
LV2_Atom_Forge_Frame output_frame;
|
||||
|
||||
std::vector<LV2_URID> pathProperties;
|
||||
std::vector<PatchPropertyWriter> pathPropertyWriters;
|
||||
@@ -210,8 +212,11 @@ namespace pipedal
|
||||
|
||||
bool borrowedEffect = false;
|
||||
bool activated = false;
|
||||
void EnableBufferStaging(size_t bufferSize, size_t nInputs, size_t nOutputs);
|
||||
void CheckStagingBufferSentries();
|
||||
|
||||
public:
|
||||
bool RequiresBufferStaging() const;
|
||||
bool IsBorrowedEffect() const { return borrowedEffect; }
|
||||
void SetBorrowedEffect(bool value) { borrowedEffect = value; }
|
||||
void UpdateAudioPorts();
|
||||
@@ -247,9 +252,36 @@ namespace pipedal
|
||||
return (uint8_t*)this->outputAtomBuffers[0];
|
||||
|
||||
}
|
||||
OptionsFeature optionsFeature;
|
||||
|
||||
bool hasErrorMessage = false;
|
||||
char errorMessage[1024];
|
||||
|
||||
bool deleted = false;
|
||||
size_t stagingBufferSize = 0;
|
||||
size_t stagingInputIx = 0;
|
||||
size_t stagingOutputIx = 0;
|
||||
std::vector<std::vector<float>> inputStagingBuffers;
|
||||
std::vector<std::vector<float>> outputStagingBuffers;
|
||||
std::vector<float*> inputStagingBufferPointers;
|
||||
std::vector<float*> outputStagingBufferPointers;
|
||||
|
||||
std::vector<uint8_t> stagedInputAtomBuffer;
|
||||
void *stagedInputAtomBufferPointer = nullptr;
|
||||
std::vector<uint8_t> stagedOutputAtomBuffer;
|
||||
void *stagedOutputAtomBufferPointer = nullptr;
|
||||
|
||||
size_t stageToOutput(size_t outputIndex, size_t nFrames);
|
||||
size_t stageToInput(size_t inputIndex, size_t nFrames);
|
||||
|
||||
|
||||
LV2_Atom_Forge stagedInputForgeRt;
|
||||
LV2_Atom_Forge_Frame staged_input_frame;
|
||||
|
||||
void MixOutput(uint32_t samples, RealtimeRingBufferWriter *realtimeRingBufferWriter);
|
||||
void copyAtomBufferEventSequence(LV2_Atom_Sequence *sequence, LV2_Atom_Forge &outputForge);
|
||||
void resetStagedInputAtomBuffer();
|
||||
|
||||
public:
|
||||
Lv2Effect(
|
||||
IHost *pHost,
|
||||
@@ -280,14 +312,14 @@ namespace pipedal
|
||||
virtual int GetNumberOfOutputAudioBuffers() const {return this->outputAudioBuffers.size(); }
|
||||
|
||||
virtual void SetAudioInputBuffer(int index, float *buffer);
|
||||
virtual float *GetAudioInputBuffer(int index) const { return this->inputAudioBuffers[index]; }
|
||||
virtual float *GetAudioInputBuffer(int index) const;
|
||||
|
||||
|
||||
virtual void SetAudioInputBuffer(float *buffer);
|
||||
virtual void SetAudioInputBuffers(float *left, float *right);
|
||||
|
||||
virtual void SetAudioOutputBuffer(int index, float *buffer);
|
||||
virtual float *GetAudioOutputBuffer(int index) const { return this->outputAudioBuffers[index]; }
|
||||
virtual float *GetAudioOutputBuffer(int index) const;
|
||||
|
||||
virtual void SetAtomInputBuffer(int index, void *buffer) { this->inputAtomBuffers[index] = (char*)buffer;}
|
||||
virtual void *GetAtomInputBuffer(int index) const { return this->inputAtomBuffers[index]; }
|
||||
@@ -345,6 +377,7 @@ namespace pipedal
|
||||
|
||||
virtual void Activate();
|
||||
virtual void Run(uint32_t samples, RealtimeRingBufferWriter *realtimeRingBufferWriter);
|
||||
virtual void RunWithBufferStaging(uint32_t samples, RealtimeRingBufferWriter *realtimeRingBufferWriter);
|
||||
virtual void Deactivate();
|
||||
};
|
||||
|
||||
|
||||
+42
-19
@@ -59,6 +59,7 @@ int Lv2Pedalboard::GetControlIndex(uint64_t instanceId, const std::string &symbo
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
std::vector<float *> Lv2Pedalboard::PrepareItems(
|
||||
std::vector<PedalboardItem> &items,
|
||||
std::vector<float *> inputBuffers,
|
||||
@@ -88,8 +89,8 @@ std::vector<float *> Lv2Pedalboard::PrepareItems(
|
||||
|
||||
this->processActions.push_back(preMixAction);
|
||||
|
||||
std::vector<float *> topResult = PrepareItems(item.topChain(), topInputs, errorList,existingEffects);
|
||||
std::vector<float *> bottomResult = PrepareItems(item.bottomChain(), bottomInputs, errorList,existingEffects);
|
||||
std::vector<float *> topResult = PrepareItems(item.topChain(), topInputs, errorList, existingEffects);
|
||||
std::vector<float *> bottomResult = PrepareItems(item.bottomChain(), bottomInputs, errorList, existingEffects);
|
||||
|
||||
this->processActions.push_back(
|
||||
[pSplit](uint32_t frames)
|
||||
@@ -114,11 +115,10 @@ std::vector<float *> Lv2Pedalboard::PrepareItems(
|
||||
{
|
||||
std::shared_ptr<IEffect> pLv2Effect;
|
||||
|
||||
if (existingEffects && existingEffects->contains(item.instanceId())
|
||||
)
|
||||
if (existingEffects && existingEffects->contains(item.instanceId()))
|
||||
{
|
||||
pLv2Effect = existingEffects->at(item.instanceId());
|
||||
((Lv2Effect*)pLv2Effect.get())->SetBorrowedEffect(true);
|
||||
((Lv2Effect *)pLv2Effect.get())->SetBorrowedEffect(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -177,16 +177,38 @@ std::vector<float *> Lv2Pedalboard::PrepareItems(
|
||||
}
|
||||
}
|
||||
|
||||
this->processActions.push_back(
|
||||
[pLv2Effect, this](uint32_t frames)
|
||||
{
|
||||
pLv2Effect->Run(frames, this->ringBufferWriter);
|
||||
});
|
||||
|
||||
// Reset any trigger controls to default state after processing
|
||||
// check to see whether we need buffer staging.
|
||||
bool requiresBufferStaging = false;
|
||||
if (pLv2Effect->IsLv2Effect())
|
||||
{
|
||||
Lv2Effect *lv2Effect = (Lv2Effect *)pLv2Effect.get();
|
||||
|
||||
if (lv2Effect->RequiresBufferStaging())
|
||||
{
|
||||
requiresBufferStaging = true;
|
||||
this->processActions.push_back(
|
||||
[lv2Effect, this](uint32_t frames)
|
||||
{
|
||||
lv2Effect->RunWithBufferStaging(frames, this->ringBufferWriter);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (!requiresBufferStaging)
|
||||
{
|
||||
this->processActions.push_back(
|
||||
[pLv2Effect, this](uint32_t frames)
|
||||
{
|
||||
pLv2Effect->Run(frames, this->ringBufferWriter);
|
||||
});
|
||||
}
|
||||
|
||||
// reset any trigger controls to default state after processing
|
||||
if (pLv2Effect->IsLv2Effect())
|
||||
{
|
||||
Lv2Effect *lv2Effect = (Lv2Effect *)pLv2Effect.get();
|
||||
|
||||
auto pluginInfo = pHost->GetPluginInfo(item.uri());
|
||||
if (pluginInfo)
|
||||
{
|
||||
@@ -212,7 +234,8 @@ std::vector<float *> Lv2Pedalboard::PrepareItems(
|
||||
}
|
||||
if (pEffect)
|
||||
{
|
||||
this->effects.push_back(pEffect); // for ownership.
|
||||
this->effects.push_back(pEffect); // for ownership.
|
||||
|
||||
this->realtimeEffects.push_back(pEffect.get()); // because std::shared_ptr is not threadsafe.
|
||||
|
||||
std::vector<float *> effectOutput;
|
||||
@@ -326,7 +349,6 @@ void Lv2Pedalboard::PrepareMidiMap(const PedalboardItem &pedalboardItem)
|
||||
mapping.midiBinding = binding;
|
||||
mapping.instanceId = pedalboardItem.instanceId();
|
||||
|
||||
|
||||
if (pPortInfo->mod_momentaryOffByDefault() || pPortInfo->mod_momentaryOnByDefault())
|
||||
{
|
||||
mapping.mappingType = MidiControlType::MomentarySwitch;
|
||||
@@ -397,14 +419,14 @@ void Lv2Pedalboard::UpdateAudioPorts()
|
||||
{
|
||||
Lv2Effect *lv2Effect = (Lv2Effect *)effect;
|
||||
lv2Effect->UpdateAudioPorts();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Lv2Pedalboard::Activate()
|
||||
{
|
||||
CrashGuardLock crashGuardLock;
|
||||
|
||||
|
||||
for (int i = 0; i < this->effects.size(); ++i)
|
||||
{
|
||||
this->realtimeEffects[i]->Activate();
|
||||
@@ -435,7 +457,7 @@ bool Lv2Pedalboard::Run(float **inputBuffers, float **outputBuffers, uint32_t sa
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
for (size_t i = 0; i < samples; ++i)
|
||||
{
|
||||
float volume = this->inputVolume.Tick();
|
||||
@@ -581,12 +603,13 @@ void Lv2Pedalboard::ProcessParameterRequests(RealtimePatchPropertyRequest *pPara
|
||||
else if (pEffect->IsVst3())
|
||||
{
|
||||
pParameterRequests->errorMessage = "Not supported for VST3 plugins";
|
||||
} else if (pParameterRequests->sampleTimeout < 0)
|
||||
}
|
||||
else if (pParameterRequests->sampleTimeout < 0)
|
||||
{
|
||||
pParameterRequests->sampleTimeout = 0;
|
||||
pParameterRequests->errorMessage = "Timed out.";
|
||||
}
|
||||
else
|
||||
else
|
||||
{
|
||||
if (pEffect->IsLv2Effect())
|
||||
{
|
||||
|
||||
@@ -42,6 +42,9 @@ namespace pipedal {
|
||||
double sampleRate,
|
||||
int32_t blockLength,
|
||||
int32_t atomBufferBlockLength);
|
||||
|
||||
void SetBlockLength(int32_t blockLength) { this->blockLength = blockLength; }
|
||||
|
||||
~OptionsFeature();
|
||||
public:
|
||||
const LV2_Feature* GetFeature()
|
||||
|
||||
+25
-16
@@ -57,9 +57,9 @@ void PrintHelp()
|
||||
pp << "Copyright (c) 2022 Robin Davies\n";
|
||||
pp << "\n";
|
||||
pp << Indent(0) << "Syntax\n\n";
|
||||
pp << Indent(2) << "pipedal_latency_test [<options>] <device-name>\n\n";
|
||||
pp << "where <device-name> is the name of an ALSA device. Typically this should be the name of a hardware "
|
||||
"device (a device name starting with 'hw:').\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";
|
||||
pp << Indent(0) << "Options\n\n";
|
||||
pp << Indent(15);
|
||||
|
||||
@@ -95,7 +95,8 @@ void PrintHelp()
|
||||
|
||||
pp << Indent(0) << "Examples\n\n";
|
||||
pp << Indent(2) << "pipedal_latency_test --list\n\n";
|
||||
pp << Indent(2) << "pipedal_latency_test hw:M2\n\n";
|
||||
pp << Indent(2) << "pipedal_latency_test hw:M2 hw:M2\n";
|
||||
pp << Indent(2) << "pipedal_latency_test hw:M2 hw:Device2\n\n";
|
||||
}
|
||||
|
||||
void ListDevices()
|
||||
@@ -138,7 +139,8 @@ public:
|
||||
private:
|
||||
AudioDriver *audioDriver = nullptr;
|
||||
|
||||
const std::string &deviceId;
|
||||
const std::string &inputDeviceId;
|
||||
const std::string &outputDeviceId;
|
||||
ChannelsT inputChannels;
|
||||
ChannelsT outputChannels;
|
||||
uint32_t sampleRate;
|
||||
@@ -147,11 +149,13 @@ private:
|
||||
|
||||
public:
|
||||
AlsaTester(
|
||||
const std::string &deviceId,
|
||||
const std::string &inputDeviceId,
|
||||
const std::string &outputDeviceId,
|
||||
const ChannelsT &inputChannels,
|
||||
const ChannelsT &outputChannels,
|
||||
uint32_t sampleRate, int bufferSize, int buffers)
|
||||
: deviceId(deviceId),
|
||||
: inputDeviceId(inputDeviceId),
|
||||
outputDeviceId(outputDeviceId),
|
||||
sampleRate(sampleRate),
|
||||
inputChannels(inputChannels),
|
||||
outputChannels(outputChannels),
|
||||
@@ -186,7 +190,7 @@ public:
|
||||
TestResult result;
|
||||
try
|
||||
{
|
||||
JackServerSettings serverSettings(deviceId, sampleRate, bufferSize, buffers);
|
||||
JackServerSettings serverSettings(inputDeviceId, outputDeviceId, sampleRate, bufferSize, buffers);
|
||||
|
||||
JackConfiguration jackConfiguration;
|
||||
jackConfiguration.AlsaInitialize(serverSettings);
|
||||
@@ -403,12 +407,13 @@ public:
|
||||
};
|
||||
|
||||
TestResult RunLatencyTest(
|
||||
const std::string deviceId,
|
||||
const std::string inputDeviceId,
|
||||
const std::string outputDeviceId,
|
||||
const ChannelsT &inputChannels,
|
||||
const ChannelsT &outputChannels,
|
||||
uint32_t sampleRate, int bufferSize, int buffers)
|
||||
{
|
||||
AlsaTester tester(deviceId, inputChannels, outputChannels, sampleRate, bufferSize, buffers);
|
||||
AlsaTester tester(inputDeviceId, outputDeviceId, inputChannels, outputChannels, sampleRate, bufferSize, buffers);
|
||||
return tester.Test();
|
||||
}
|
||||
|
||||
@@ -428,13 +433,14 @@ static std::string overheadDisplay(float value)
|
||||
}
|
||||
|
||||
void RunLatencyTest(
|
||||
const std::string &deviceId,
|
||||
const std::string &inputDeviceId,
|
||||
const std::string &outputDeviceId,
|
||||
const ChannelsT &inputChannels,
|
||||
const ChannelsT &outputChannels,
|
||||
uint32_t sampleRate)
|
||||
{
|
||||
PrettyPrinter pp;
|
||||
pp << "Device: " << deviceId << " Rate: " << sampleRate << "\n\n";
|
||||
pp << "Input: " << inputDeviceId << " Output: " << outputDeviceId << " Rate: " << sampleRate << "\n\n";
|
||||
|
||||
const int SIZE_COLUMN_WIDTH = 8;
|
||||
const int BUFFERS_COLUMN_WIDTH = 20;
|
||||
@@ -461,7 +467,7 @@ void RunLatencyTest(
|
||||
|
||||
for (auto bufferCount : bufferCounts)
|
||||
{
|
||||
auto result = RunLatencyTest(deviceId, inputChannels,outputChannels, sampleRate, bufferSize, bufferCount);
|
||||
auto result = RunLatencyTest(inputDeviceId, outputDeviceId, inputChannels,outputChannels, sampleRate, bufferSize, bufferCount);
|
||||
|
||||
pp.Column(column);
|
||||
column += BUFFERS_COLUMN_WIDTH;
|
||||
@@ -556,11 +562,14 @@ std:
|
||||
{
|
||||
ListDevices();
|
||||
}
|
||||
else if (parser.Arguments().size() == 1)
|
||||
else if (parser.Arguments().size() >= 1 && parser.Arguments().size() <= 2)
|
||||
{
|
||||
inputChannels = ParseChannels(strInputChannels);
|
||||
outputChannels = ParseChannels(strInputChannels);
|
||||
RunLatencyTest(parser.Arguments()[0], inputChannels, outputChannels, sampleRate);
|
||||
outputChannels = ParseChannels(strOutputChannels);
|
||||
|
||||
std::string inDev = parser.Arguments()[0];
|
||||
std::string outDev = parser.Arguments().size() == 2 ? parser.Arguments()[1] : inDev;
|
||||
RunLatencyTest(inDev, outDev, inputChannels, outputChannels, sampleRate);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
+254
-85
@@ -1,4 +1,5 @@
|
||||
// Copyright (c) 2022 Robin Davies
|
||||
// Copyright (c) Robin E. R. Davies
|
||||
// Copyright (c) Gabriel Hernandez
|
||||
//
|
||||
// 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
|
||||
@@ -22,6 +23,8 @@
|
||||
#include "alsa/asoundlib.h"
|
||||
#include "Lv2Log.hpp"
|
||||
#include <mutex>
|
||||
#include <algorithm>
|
||||
#include "Finally.hpp"
|
||||
|
||||
using namespace pipedal;
|
||||
|
||||
@@ -45,6 +48,111 @@ void PiPedalAlsaDevices::cacheDevice(const std::string &name, const AlsaDeviceIn
|
||||
cachedDevices[name] = deviceInfo;
|
||||
}
|
||||
|
||||
static bool isSupportedAudioDevice(const AlsaDeviceInfo &d)
|
||||
{
|
||||
std::string name = d.name_ + " " + d.longName_;
|
||||
std::transform(name.begin(), name.end(), name.begin(), [](char c)
|
||||
{ return std::tolower(c); });
|
||||
if (name.find("hdmi") != std::string::npos) return false;
|
||||
// if (name.find("bcm2835") != std::string::npos) return false;
|
||||
return true;
|
||||
};
|
||||
|
||||
|
||||
struct ProcAlsaDevice {
|
||||
int cardId;
|
||||
int subdeviceId;
|
||||
bool audioCapture;
|
||||
bool audioPlayback;
|
||||
bool rawMidi;
|
||||
};
|
||||
|
||||
static std::vector<ProcAlsaDevice> getProcAlsaDevices()
|
||||
{
|
||||
std::vector<ProcAlsaDevice> result;
|
||||
|
||||
std::ifstream f {"/proc/asound/devices"};
|
||||
if (f.is_open())
|
||||
{
|
||||
std::string line;
|
||||
while (std::getline(f, line))
|
||||
{
|
||||
// Parse each line of /proc/alsa/devices
|
||||
// Format: cardnum: [devicenum- subdevicenum]: type : name
|
||||
std::istringstream iss(line);
|
||||
std::string token;
|
||||
|
||||
// Skip leading whitespace and get card number
|
||||
if (!std::getline(iss, token, ':'))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
int cardId = std::stoi(token.substr(token.find_first_not_of(" \t")));
|
||||
|
||||
// Get device-subdevice part
|
||||
if (!std::getline(iss, token, ':'))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
size_t dashPos = token.find('-');
|
||||
if (dashPos == std::string::npos)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
int deviceId = std::stoi(token.substr(token.find_first_not_of(" \t["), dashPos));
|
||||
int subdeviceId = std::stoi(token.substr(dashPos + 1, token.find(']') - dashPos - 1));
|
||||
|
||||
// Get type
|
||||
if (!std::getline(iss, token, ':'))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
std::string type = token.substr(token.find_first_not_of(" \t"));
|
||||
|
||||
ProcAlsaDevice *pDevice = nullptr;
|
||||
for (size_t i = 0; i < result.size(); ++i)
|
||||
{
|
||||
if (result[i].cardId == deviceId && result[i].subdeviceId == subdeviceId)
|
||||
{
|
||||
pDevice = &result[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (pDevice == nullptr)
|
||||
{
|
||||
ProcAlsaDevice newDevice;
|
||||
newDevice.cardId = deviceId;
|
||||
newDevice.subdeviceId = subdeviceId;
|
||||
newDevice.audioCapture = false;
|
||||
newDevice.audioPlayback = false;
|
||||
newDevice.rawMidi = false;
|
||||
result.push_back(newDevice);
|
||||
pDevice = &(result[result.size()-1]);
|
||||
}
|
||||
if (type.find("digital audio capture") != std::string::npos)
|
||||
{
|
||||
pDevice->audioCapture = true;
|
||||
}
|
||||
if ((type.find("digital audio playback") != std::string::npos))
|
||||
{
|
||||
pDevice->audioPlayback = true;
|
||||
}
|
||||
if (type.find("rawmidi") != std::string::npos)
|
||||
{
|
||||
pDevice->rawMidi = true;
|
||||
}
|
||||
} catch (const std::exception &e)
|
||||
{
|
||||
Lv2Log::error(SS("invalid ALSA proc entry: " << line));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
std::vector<AlsaDeviceInfo> PiPedalAlsaDevices::GetAlsaDevices()
|
||||
{
|
||||
|
||||
@@ -55,85 +163,111 @@ std::vector<AlsaDeviceInfo> PiPedalAlsaDevices::GetAlsaDevices()
|
||||
int cardNum = -1; // Start with first card
|
||||
int err;
|
||||
|
||||
for (;;)
|
||||
std::vector<ProcAlsaDevice> procAlsaDevices = getProcAlsaDevices();
|
||||
|
||||
for (const auto &procAlsaDevice: procAlsaDevices)
|
||||
{
|
||||
if ((err = snd_card_next(&cardNum)) < 0)
|
||||
{
|
||||
Lv2Log::error("Unexpected error enumerating ALSA devices.");
|
||||
break;
|
||||
std::stringstream ss;
|
||||
if (!procAlsaDevice.audioCapture && !procAlsaDevice.audioPlayback) {
|
||||
continue;
|
||||
}
|
||||
if (cardNum < 0)
|
||||
// No more cards
|
||||
break;
|
||||
ss << "hw:" << procAlsaDevice.cardId;
|
||||
|
||||
std::string cardId = ss.str();
|
||||
|
||||
snd_ctl_t *hDevice = nullptr;
|
||||
|
||||
if ((err = snd_ctl_open(&hDevice, cardId.c_str(), 0)) < 0)
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << "hw:" << cardNum;
|
||||
std::string cardId = ss.str();
|
||||
continue;
|
||||
}
|
||||
|
||||
snd_ctl_t *hDevice = nullptr;
|
||||
Finally ffhDevice{[hDevice]()
|
||||
{ snd_ctl_close(hDevice); }};
|
||||
|
||||
if ((err = snd_ctl_open(&hDevice, cardId.c_str(), 0)) < 0)
|
||||
snd_ctl_card_info_t *alsaInfo = nullptr;
|
||||
if (snd_ctl_card_info_malloc(&alsaInfo) != 0)
|
||||
{
|
||||
Lv2Log::error("Failed to allocate ALSA card info");
|
||||
continue;
|
||||
}
|
||||
|
||||
Finally ffCardInfo{[alsaInfo]()
|
||||
{ snd_ctl_card_info_free(alsaInfo); }};
|
||||
|
||||
err = snd_ctl_card_info(hDevice, alsaInfo);
|
||||
if (err == 0)
|
||||
{
|
||||
AlsaDeviceInfo info;
|
||||
info.cardId_ = cardNum;
|
||||
info.id_ = std::string("hw:") + snd_ctl_card_info_get_id(alsaInfo);
|
||||
const char *driver = snd_ctl_card_info_get_driver(alsaInfo);
|
||||
(void)driver;
|
||||
|
||||
info.name_ = snd_ctl_card_info_get_name(alsaInfo);
|
||||
info.longName_ = snd_ctl_card_info_get_longname(alsaInfo);
|
||||
|
||||
// we can't read our own device if it's open so use data that gets
|
||||
// cached before we open audio devices.
|
||||
|
||||
AlsaDeviceInfo cachedInfo;
|
||||
if (getCachedDevice(info.name_, &cachedInfo))
|
||||
{
|
||||
// may have been plugged into a different USB connector.
|
||||
cachedInfo.cardId_ = info.cardId_;
|
||||
cachedInfo.id_ = info.id_;
|
||||
result.push_back(cachedInfo);
|
||||
continue;
|
||||
}
|
||||
snd_pcm_t *captureDevice = nullptr;
|
||||
snd_pcm_t *playbackDevice = nullptr;
|
||||
auto rc = snd_pcm_open(&captureDevice, cardId.c_str(), SND_PCM_STREAM_CAPTURE, SND_PCM_NONBLOCK);
|
||||
bool captureOk = rc == 0;
|
||||
|
||||
snd_ctl_card_info_t *alsaInfo;
|
||||
if (snd_ctl_card_info_malloc(&alsaInfo) != 0)
|
||||
Finally ffCaptureDevice{
|
||||
[captureDevice]
|
||||
{ if (captureDevice) snd_pcm_close(captureDevice); }};
|
||||
|
||||
rc = snd_pcm_open(&playbackDevice, cardId.c_str(), SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK);
|
||||
bool playbackOk = rc == 0;
|
||||
|
||||
Finally ffPlaybackDevice{
|
||||
[playbackDevice]
|
||||
{ if (playbackDevice) snd_pcm_close(playbackDevice); }};
|
||||
if (procAlsaDevice.audioCapture && !captureOk)
|
||||
{
|
||||
Lv2Log::error("Failed to allocate ALSA card info");
|
||||
snd_ctl_close(hDevice);
|
||||
continue;
|
||||
info.captureBusy_ = true;
|
||||
}
|
||||
if (procAlsaDevice.audioPlayback && !playbackOk)
|
||||
{
|
||||
info.playbackBusy_ = true;
|
||||
}
|
||||
|
||||
err = snd_ctl_card_info(hDevice, alsaInfo);
|
||||
if (err == 0)
|
||||
info.supportsCapture_ = captureOk;
|
||||
info.supportsPlayback_ = playbackOk;
|
||||
|
||||
if (captureOk || playbackOk)
|
||||
{
|
||||
AlsaDeviceInfo info;
|
||||
info.cardId_ = cardNum;
|
||||
info.id_ = std::string("hw:") + snd_ctl_card_info_get_id(alsaInfo);
|
||||
const char *driver = snd_ctl_card_info_get_driver(alsaInfo);
|
||||
snd_pcm_t *hDevice = captureOk ? captureDevice : playbackDevice;
|
||||
snd_pcm_hw_params_t *params = nullptr;
|
||||
err = snd_pcm_hw_params_malloc(¶ms);
|
||||
|
||||
info.name_ = snd_ctl_card_info_get_name(alsaInfo);
|
||||
info.longName_ = snd_ctl_card_info_get_longname(alsaInfo);
|
||||
|
||||
snd_pcm_t *hDevice = nullptr;
|
||||
|
||||
// must support capture AND playback
|
||||
err = snd_pcm_open(&hDevice, cardId.c_str(), SND_PCM_STREAM_CAPTURE, 0);
|
||||
if (err == 0)
|
||||
{
|
||||
snd_pcm_close(hDevice);
|
||||
}
|
||||
if (err == 0)
|
||||
{
|
||||
err = snd_pcm_open(&hDevice, cardId.c_str(), SND_PCM_STREAM_PLAYBACK, 0);
|
||||
}
|
||||
if (err == 0)
|
||||
{
|
||||
snd_pcm_hw_params_t *params = nullptr;
|
||||
err = snd_pcm_hw_params_malloc(¶ms);
|
||||
Finally ffParams{[params]
|
||||
{ snd_pcm_hw_params_free(params); }};
|
||||
|
||||
err = snd_pcm_hw_params_any(hDevice, params);
|
||||
if (err == 0)
|
||||
{
|
||||
err = snd_pcm_hw_params_any(hDevice, params);
|
||||
unsigned int minRate = 0, maxRate = 0;
|
||||
snd_pcm_uframes_t minBufferSize = 0, maxBufferSize = 0;
|
||||
int dir;
|
||||
|
||||
err = snd_pcm_hw_params_get_rate_min(params, &minRate, &dir);
|
||||
if (err == 0)
|
||||
{
|
||||
unsigned int minRate = 0, maxRate = 0;
|
||||
snd_pcm_uframes_t minBufferSize = 0, maxBufferSize = 0;
|
||||
int dir;
|
||||
err = snd_pcm_hw_params_get_rate_min(params, &minRate, &dir);
|
||||
if (err == 0)
|
||||
{
|
||||
err = snd_pcm_hw_params_get_rate_max(params, &maxRate, &dir);
|
||||
}
|
||||
if (err == 0)
|
||||
{
|
||||
err = snd_pcm_hw_params_get_buffer_size_min(params, &minBufferSize);
|
||||
}
|
||||
if (err == 0)
|
||||
{
|
||||
err = snd_pcm_hw_params_get_buffer_size_max(params, &maxBufferSize);
|
||||
}
|
||||
err = snd_pcm_hw_params_get_rate_max(params, &maxRate, &dir);
|
||||
if (err == 0)
|
||||
{
|
||||
for (size_t i = 0; i < sizeof(RATES) / sizeof(RATES[0]); ++i)
|
||||
@@ -144,42 +278,71 @@ std::vector<AlsaDeviceInfo> PiPedalAlsaDevices::GetAlsaDevices()
|
||||
info.sampleRates_.push_back(rate);
|
||||
}
|
||||
}
|
||||
if (minBufferSize < 16)
|
||||
{
|
||||
minBufferSize = 16;
|
||||
}
|
||||
|
||||
info.minBufferSize_ = (uint32_t)minBufferSize;
|
||||
info.maxBufferSize_ = (uint32_t)maxBufferSize;
|
||||
cacheDevice(info.name_, info);
|
||||
result.push_back(info);
|
||||
}
|
||||
else
|
||||
{
|
||||
Lv2Log::warning(SS("Failed to get maximum sample rate for device '" << info.name_ << "'."));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Lv2Log::warning(SS("Failed to get minimum sample rate for device '" << info.name_ << "'."));
|
||||
}
|
||||
|
||||
if (err == 0)
|
||||
{
|
||||
err = snd_pcm_hw_params_get_buffer_size_min(params, &minBufferSize);
|
||||
if (err == 0)
|
||||
{
|
||||
err = snd_pcm_hw_params_get_buffer_size_max(params, &maxBufferSize);
|
||||
}
|
||||
}
|
||||
if (err == 0)
|
||||
{
|
||||
if (minBufferSize < 16)
|
||||
{
|
||||
minBufferSize = 16;
|
||||
}
|
||||
|
||||
info.minBufferSize_ = (uint32_t)minBufferSize;
|
||||
info.maxBufferSize_ = (uint32_t)maxBufferSize;
|
||||
}
|
||||
}
|
||||
if (params != nullptr)
|
||||
snd_pcm_hw_params_free(params);
|
||||
snd_pcm_close(hDevice);
|
||||
}
|
||||
else
|
||||
if (!info.captureBusy_ && !info.playbackBusy_)
|
||||
{
|
||||
if (getCachedDevice(info.name_, &info))
|
||||
{
|
||||
result.push_back(info);
|
||||
}
|
||||
cacheDevice(info.name_, info);
|
||||
result.push_back(info);
|
||||
}
|
||||
} else {
|
||||
if (info.captureBusy_ || info.playbackBusy_)
|
||||
{
|
||||
result.push_back(info);
|
||||
}
|
||||
|
||||
}
|
||||
snd_ctl_card_info_free(alsaInfo);
|
||||
snd_ctl_close(hDevice);
|
||||
}
|
||||
}
|
||||
snd_config_update_free_global();
|
||||
|
||||
Lv2Log::debug("GetAlsaDevices --");
|
||||
|
||||
std::vector<AlsaDeviceInfo> filtered;
|
||||
for (auto &device : result)
|
||||
{
|
||||
Lv2Log::debug(SS(" " << device.name_ << " " << device.longName_ << " " << device.cardId_));
|
||||
if (isSupportedAudioDevice(device))
|
||||
{
|
||||
filtered.push_back(device);
|
||||
Lv2Log::debug(
|
||||
SS(" "
|
||||
<< device.name_ << " " << device.longName_ << " " << device.cardId_
|
||||
<< (device.supportsCapture_ ? " in" : "")
|
||||
<< (device.supportsPlayback_ ? " out" : "")
|
||||
<< (device.captureBusy_ ? " in(busy)" : "")
|
||||
<< (device.captureBusy_ ? " out(busy)" : "")
|
||||
));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
return filtered;
|
||||
}
|
||||
|
||||
static void AddMidiCardDevicesToList(snd_ctl_t *ctl, int card, int device, AlsaMidiDeviceInfo::Direction direction, std::vector<AlsaMidiDeviceInfo> *result)
|
||||
@@ -250,11 +413,12 @@ static void AddMidiCardDevicesToList(snd_ctl_t *ctl, int card, int device, AlsaM
|
||||
name = snd_rawmidi_info_get_name(info);
|
||||
sub_name = snd_rawmidi_info_get_subdevice_name(info);
|
||||
// get card name.
|
||||
|
||||
|
||||
std::string cardName;
|
||||
snd_ctl_card_info_t *card_info = nullptr;
|
||||
snd_ctl_card_info_malloc(&card_info);
|
||||
if (snd_ctl_card_info(ctl, card_info) == 0) {
|
||||
if (snd_ctl_card_info(ctl, card_info) == 0)
|
||||
{
|
||||
cardName = snd_ctl_card_info_get_name(card_info);
|
||||
}
|
||||
snd_ctl_card_info_free(card_info);
|
||||
@@ -262,7 +426,7 @@ static void AddMidiCardDevicesToList(snd_ctl_t *ctl, int card, int device, AlsaM
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if (sub == 0 && sub_name[0] == '\0')
|
||||
{
|
||||
AlsaMidiDeviceInfo info;
|
||||
@@ -296,7 +460,8 @@ static void AddMidiCardDevicesToList(snd_ctl_t *ctl, int card, int device, AlsaM
|
||||
{
|
||||
AlsaMidiDeviceInfo info;
|
||||
info.name_ = SS("hw:CARD=" << cardName << ",DEV=" << device);
|
||||
if (sub != 0) {
|
||||
if (sub != 0)
|
||||
{
|
||||
info.name_ = SS(info.name_ << "," << sub);
|
||||
}
|
||||
info.description_ = sub_name;
|
||||
@@ -480,6 +645,10 @@ JSON_MAP_REFERENCE(AlsaDeviceInfo, longName)
|
||||
JSON_MAP_REFERENCE(AlsaDeviceInfo, sampleRates)
|
||||
JSON_MAP_REFERENCE(AlsaDeviceInfo, minBufferSize)
|
||||
JSON_MAP_REFERENCE(AlsaDeviceInfo, maxBufferSize)
|
||||
JSON_MAP_REFERENCE(AlsaDeviceInfo, supportsCapture)
|
||||
JSON_MAP_REFERENCE(AlsaDeviceInfo, supportsPlayback)
|
||||
JSON_MAP_REFERENCE(AlsaDeviceInfo, captureBusy)
|
||||
JSON_MAP_REFERENCE(AlsaDeviceInfo, playbackBusy)
|
||||
JSON_MAP_END()
|
||||
|
||||
JSON_MAP_BEGIN(AlsaMidiDeviceInfo)
|
||||
|
||||
+6
-1
@@ -1,4 +1,5 @@
|
||||
// Copyright (c) 2022 Robin Davies
|
||||
// Copyright (c) Robin E. R. Davies
|
||||
// Copyright (c) Gabriel Hernandez
|
||||
//
|
||||
// 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
|
||||
@@ -31,6 +32,10 @@ namespace pipedal {
|
||||
std::string longName_;
|
||||
std::vector<uint32_t> sampleRates_;
|
||||
uint32_t minBufferSize_ = 0,maxBufferSize_ = 0;
|
||||
bool supportsCapture_ = false;
|
||||
bool supportsPlayback_ = false;
|
||||
bool captureBusy_ = false;
|
||||
bool playbackBusy_ = false;
|
||||
|
||||
bool isDummyDevice() const {
|
||||
return id_.starts_with("dummy:");
|
||||
|
||||
+19
-5
@@ -30,7 +30,6 @@
|
||||
#include "Pedalboard.hpp"
|
||||
#include "Lv2Effect.hpp"
|
||||
#include "Lv2Pedalboard.hpp"
|
||||
#include "OptionsFeature.hpp"
|
||||
#include "JackConfiguration.hpp"
|
||||
#include "lv2/urid/urid.h"
|
||||
#include "lv2/ui/ui.h"
|
||||
@@ -209,6 +208,13 @@ void PluginHost::LilvUris::Initialize(LilvWorld *pWorld)
|
||||
|
||||
mod__fileTypes = lilv_new_uri(pWorld, "http://moddevices.com/ns/mod#fileTypes");
|
||||
mod__supportedExtensions = lilv_new_uri(pWorld, "http://moddevices.com/ns/mod#supportedExtensions");
|
||||
|
||||
buf_size__minBlockLength = lilv_new_uri(pWorld, LV2_BUF_SIZE__minBlockLength);
|
||||
buf_size__maxBlockLength = lilv_new_uri(pWorld, LV2_BUF_SIZE__maxBlockLength);
|
||||
buf_size__fixedBlockLength = lilv_new_uri(pWorld, LV2_BUF_SIZE__fixedBlockLength);
|
||||
buf_size__coarseBlockLength = lilv_new_uri(pWorld, LV2_BUF_SIZE__coarseBlockLength);
|
||||
|
||||
|
||||
}
|
||||
|
||||
void PluginHost::LilvUris::Free()
|
||||
@@ -296,8 +302,6 @@ PluginHost::PluginHost()
|
||||
lv2Features.push_back(mapFeature.GetMapFeature());
|
||||
lv2Features.push_back(mapFeature.GetUnmapFeature());
|
||||
|
||||
optionsFeature.Prepare(mapFeature, 44100, this->GetMaxAudioBufferSize(), this->GetAtomBufferSize());
|
||||
lv2Features.push_back(optionsFeature.GetFeature());
|
||||
|
||||
fileMetadataFeature.Prepare(mapFeature);
|
||||
lv2Features.push_back(fileMetadataFeature.GetFeature());
|
||||
@@ -315,7 +319,6 @@ void PluginHost::OnConfigurationChanged(const JackConfiguration &configuration,
|
||||
this->numberOfAudioInputChannels = settings.GetInputAudioPorts().size();
|
||||
this->numberOfAudioOutputChannels = settings.GetOutputAudioPorts().size();
|
||||
this->maxBufferSize = configuration.blockLength();
|
||||
optionsFeature.Prepare(this->mapFeature, configuration.sampleRate(), configuration.blockLength(), GetAtomBufferSize());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -934,6 +937,11 @@ Lv2PluginInfo::Lv2PluginInfo(PluginHost *lv2Host, LilvWorld *pWorld, const LilvP
|
||||
isValid = false;
|
||||
}
|
||||
}
|
||||
AutoLilvNode minBlockLength = lilv_world_get(pWorld,plugUri, lv2Host->lilvUris->buf_size__minBlockLength,nullptr);
|
||||
this->minBlockLength_ = minBlockLength.AsFloat(-1);
|
||||
AutoLilvNode maxBlockLength = lilv_world_get(pWorld,plugUri, lv2Host->lilvUris->buf_size__maxBlockLength,nullptr);
|
||||
this->maxBlockLength_ = maxBlockLength.AsFloat(-1);
|
||||
|
||||
|
||||
std::sort(ports_.begin(), ports_.end(), ports_sort_compare);
|
||||
// Fetch patch properties.
|
||||
@@ -2210,7 +2218,11 @@ json_map::storage_type<Lv2PortInfo> Lv2PortInfo::jmap{
|
||||
|
||||
json_map::enum_reference("units", &Lv2PortInfo::units_, get_units_enum_converter()),
|
||||
MAP_REF(Lv2PortInfo, custom_units),
|
||||
MAP_REF(Lv2PortInfo, comment)}};
|
||||
MAP_REF(Lv2PortInfo, comment),
|
||||
|
||||
}};
|
||||
|
||||
|
||||
|
||||
json_map::storage_type<Lv2PortGroup> Lv2PortGroup::jmap{{
|
||||
MAP_REF(Lv2PortGroup, uri),
|
||||
@@ -2242,6 +2254,8 @@ json_map::storage_type<Lv2PluginInfo> Lv2PluginInfo::jmap{{
|
||||
json_map::reference("modGui", &Lv2PluginInfo::modGui_),
|
||||
json_map::reference("patchProperties", &Lv2PluginInfo::patchProperties_),
|
||||
json_map::reference("hasDefaultState", &Lv2PluginInfo::hasDefaultState_),
|
||||
json_map::reference("minBlockLength", &Lv2PluginInfo::minBlockLength_),
|
||||
json_map::reference("maxBlockLength", &Lv2PluginInfo::maxBlockLength_),
|
||||
|
||||
}};
|
||||
|
||||
|
||||
+13
-2
@@ -26,7 +26,6 @@
|
||||
#include <lilv/lilv.h>
|
||||
#include "MapFeature.hpp"
|
||||
#include "FileMetadataFeature.hpp"
|
||||
#include "OptionsFeature.hpp"
|
||||
#include <filesystem>
|
||||
#include <cmath>
|
||||
#include <string>
|
||||
@@ -432,6 +431,10 @@ namespace pipedal
|
||||
|
||||
bool IsSupportedFeature(const std::string &feature) const;
|
||||
|
||||
bool powerOf2BlockLength_ = false;
|
||||
float minBlockLength_ = -1;
|
||||
float maxBlockLength_ = -1;
|
||||
|
||||
public:
|
||||
LV2_PROPERTY_GETSET(bundle_path)
|
||||
LV2_PROPERTY_GETSET(uri)
|
||||
@@ -457,6 +460,9 @@ namespace pipedal
|
||||
LV2_PROPERTY_GETSET(modGui)
|
||||
LV2_PROPERTY_GETSET(patchProperties)
|
||||
LV2_PROPERTY_GETSET(hasDefaultState)
|
||||
LV2_PROPERTY_GETSET(minBlockLength)
|
||||
LV2_PROPERTY_GETSET(maxBlockLength)
|
||||
LV2_PROPERTY_GETSET(powerOf2BlockLength)
|
||||
|
||||
bool WantsWorkerThread() const;
|
||||
|
||||
@@ -850,6 +856,12 @@ namespace pipedal
|
||||
AutoLilvNode mod__supportedExtensions;
|
||||
AutoLilvNode pipedalui__fileTypes;
|
||||
|
||||
AutoLilvNode buf_size__minBlockLength;
|
||||
AutoLilvNode buf_size__maxBlockLength;
|
||||
AutoLilvNode buf_size__fixedBlockLength;
|
||||
AutoLilvNode buf_size__coarseBlockLength;
|
||||
|
||||
|
||||
|
||||
};
|
||||
LilvUris* lilvUris = nullptr;
|
||||
@@ -871,7 +883,6 @@ namespace pipedal
|
||||
std::vector<const LV2_Feature *> lv2Features;
|
||||
MapFeature mapFeature;
|
||||
FileMetadataFeature fileMetadataFeature;
|
||||
OptionsFeature optionsFeature;
|
||||
std::string pluginStoragePath;
|
||||
|
||||
static void fn_LilvSetPortValueFunc(const char *port_symbol,
|
||||
|
||||
+162
-16
@@ -1,4 +1,5 @@
|
||||
// Copyright (c) 2022-2024 Robin Davies
|
||||
// Copyright (c) Robin E. R. Davies
|
||||
// Copyright (c) Gabriel Hernandez
|
||||
//
|
||||
// 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
|
||||
@@ -41,6 +42,7 @@
|
||||
#include "AudioFiles.hpp"
|
||||
#include "Utf8Utils.hpp"
|
||||
#include "AtomConverter.hpp"
|
||||
#include "FileBrowserFilesFeature.hpp"
|
||||
|
||||
using namespace pipedal;
|
||||
namespace fs = std::filesystem;
|
||||
@@ -222,23 +224,136 @@ static void CopyDirectory(const std::filesystem::path &source, const std::filesy
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void Storage::MaybeCopyDefaultPresets()
|
||||
{
|
||||
auto presetsDirectory = this->GetPresetsDirectory();
|
||||
auto presetsConfigDirectory = this->configRoot / "default_presets" / "presets";
|
||||
|
||||
if (!std::filesystem::exists(presetsDirectory / "index.banks"))
|
||||
{
|
||||
CopyDirectory(this->configRoot / "default_presets" / "presets",
|
||||
presetsDirectory);
|
||||
fs::copy(presetsConfigDirectory / "index.banks", presetsDirectory / "index.banks");
|
||||
fs::copy(presetsConfigDirectory / "Default+Bank.bank", presetsDirectory / "Default+Bank.bank");
|
||||
}
|
||||
}
|
||||
|
||||
// Obsolete: TooB effects now have correct preset declarations.
|
||||
// auto pluginDirectory = this->GetPluginPresetsDirectory();
|
||||
// if (!std::filesystem::exists(pluginDirectory / "index.json"))
|
||||
// {
|
||||
// CopyDirectory(this->configRoot / "default_presets" / "plugin_presets",
|
||||
// pluginDirectory);
|
||||
// }
|
||||
static void removeFileNoThrow(const std::filesystem::path &path)
|
||||
{
|
||||
try {
|
||||
fs::remove(path);
|
||||
} catch (const std::exception&)
|
||||
{
|
||||
}
|
||||
}
|
||||
void Storage::UpgradeFactoryPresets()
|
||||
{
|
||||
auto presetsDirectory = this->GetPresetsDirectory();
|
||||
auto presetsConfigDirectory = this->configRoot / "default_presets" / "presets";
|
||||
|
||||
using namespace ::pipedal::implementation;
|
||||
|
||||
BrowserFilesVersionInfo defaultConfigPresetsVersion;
|
||||
fs::path defaultConfigPresetsVersionFile = presetsConfigDirectory / "banks.versionInfo";
|
||||
defaultConfigPresetsVersion.Load(defaultConfigPresetsVersionFile);
|
||||
|
||||
BrowserFilesVersionInfo presetsVersion;
|
||||
fs::path defaultPresetsVersionFile = presetsDirectory / "banks.versionInfo";
|
||||
presetsVersion.Load(defaultPresetsVersionFile);
|
||||
|
||||
// Maybe install or upgrade factory presets.
|
||||
if (defaultConfigPresetsVersion.Version() > presetsVersion.Version() || defaultConfigPresetsVersion.Version() == 0)
|
||||
{
|
||||
// remove TooB ML README.md
|
||||
removeFileNoThrow("/usr/lib/lv2/ToobAmp.lv2/models/tones/README.md");
|
||||
removeFileNoThrow("/var/pipedal/audio_uploads/ToobMlModels/model.index");
|
||||
|
||||
removeFileNoThrow("/var/pipedal/audio_uploads/ToobMlModels/README.md");
|
||||
removeFileNoThrow("/var/pipedal/audio_uploads/ToobMlModels/model.index");
|
||||
|
||||
std::string name = "Factory Presets";
|
||||
BankFile newFactoryPresets;
|
||||
{
|
||||
fs::path defaultBankPath = presetsConfigDirectory / "Default+Bank.bank";
|
||||
try
|
||||
{
|
||||
std::ifstream is(defaultBankPath);
|
||||
json_reader reader(is);
|
||||
reader.read(&newFactoryPresets);
|
||||
}
|
||||
catch (const std::exception &e)
|
||||
{
|
||||
Lv2Log::error(SS("Failed to isntall factory presets. Can't read " << defaultBankPath << "."));
|
||||
}
|
||||
}
|
||||
newFactoryPresets.name("Factory Presets");
|
||||
|
||||
BankIndexEntry *existingEntry = bankIndex.getEntryByName(name);
|
||||
if (existingEntry == nullptr)
|
||||
{
|
||||
BankFile bankFile;
|
||||
bankFile.name(name);
|
||||
|
||||
for (auto &presetEntry : newFactoryPresets.presets())
|
||||
{
|
||||
bankFile.addPreset(presetEntry->preset(), -1);
|
||||
}
|
||||
|
||||
int64_t instanceId = bankFile.presets()[0]->instanceId();
|
||||
bankFile.selectedPreset(instanceId);
|
||||
SaveBankFile(name, bankFile);
|
||||
this->bankIndex.addBank(-1, name);
|
||||
this->SaveBankIndex();
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
// either use the current bank (if the factory bank is selected), or create a new one.
|
||||
BankFile bankFile;
|
||||
bankFile.name(name);
|
||||
BankFile *pFactoryPresetsBank = nullptr;
|
||||
bool usingCurrentBank = false;
|
||||
if (bankIndex.selectedBank() == existingEntry->instanceId())
|
||||
{
|
||||
usingCurrentBank = true;
|
||||
pFactoryPresetsBank = &(this->currentBank);
|
||||
}
|
||||
else
|
||||
{
|
||||
LoadBankFile(name, &bankFile);
|
||||
pFactoryPresetsBank = &bankFile;
|
||||
}
|
||||
// index existing presets.
|
||||
std::unordered_map<std::string, size_t> nameToPositionIndex;
|
||||
for (size_t i = 0; i < pFactoryPresetsBank->presets().size(); ++i)
|
||||
{
|
||||
auto &preset = pFactoryPresetsBank->presets()[i];
|
||||
nameToPositionIndex[preset->preset().name()] = i;
|
||||
}
|
||||
|
||||
// merge new presets into the existing ones (overwriting as neccessary)
|
||||
for (auto &newPresetEntry : newFactoryPresets.presets())
|
||||
{
|
||||
const std::string name = newPresetEntry->preset().name();
|
||||
|
||||
auto f = nameToPositionIndex.find(name);
|
||||
if (f != nameToPositionIndex.end())
|
||||
{
|
||||
size_t postition = f->second;
|
||||
// overwrite the existing entry.
|
||||
pFactoryPresetsBank->presets()[postition]->preset(newPresetEntry->preset());
|
||||
}
|
||||
else
|
||||
{
|
||||
pFactoryPresetsBank->addPreset(newPresetEntry->preset());
|
||||
}
|
||||
}
|
||||
SaveBankFile(name, *pFactoryPresetsBank);
|
||||
|
||||
presetsVersion.Version(defaultConfigPresetsVersion.Version());
|
||||
presetsVersion.Save(defaultPresetsVersionFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
void Storage::Initialize()
|
||||
{
|
||||
@@ -269,6 +384,7 @@ void Storage::Initialize()
|
||||
LoadWifiConfigSettings();
|
||||
LoadWifiDirectConfigSettings();
|
||||
LoadUserSettings();
|
||||
UpgradeFactoryPresets();
|
||||
}
|
||||
|
||||
void Storage::LoadBank(int64_t instanceId)
|
||||
@@ -1248,7 +1364,6 @@ void Storage::SetPluginPresetIndexVersion(uint64_t version)
|
||||
this->pluginPresetIndex.version_ = version;
|
||||
SavePluginPresetIndex();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
bool Storage::HasPluginPresets(const std::string &pluginUri) const
|
||||
@@ -1428,7 +1543,7 @@ PluginPresetValues Storage::GetPluginPresetValues(const std::string &pluginUri,
|
||||
{
|
||||
result.controls.push_back(ControlValue(valuePair.first.c_str(), valuePair.second));
|
||||
}
|
||||
for (const auto&pair: preset.pathProperties_)
|
||||
for (const auto &pair : preset.pathProperties_)
|
||||
{
|
||||
result.pathProperties[pair.first] = pair.second;
|
||||
}
|
||||
@@ -1503,8 +1618,7 @@ uint64_t Storage::SavePluginPreset(
|
||||
|
||||
uint64_t Storage::SavePluginPreset(
|
||||
const std::string &pluginUri,
|
||||
PluginPreset &pluginPreset
|
||||
)
|
||||
PluginPreset &pluginPreset)
|
||||
{
|
||||
auto presets = GetPluginPresets(pluginUri);
|
||||
uint64_t result = -1;
|
||||
@@ -1645,6 +1759,14 @@ pipedal::JackServerSettings Storage::GetJackServerSettings()
|
||||
{
|
||||
json_reader reader(f);
|
||||
reader.read(&result);
|
||||
if (!result.GetLegacyAlsaDevice().empty() &&
|
||||
result.GetAlsaInputDevice().empty() &&
|
||||
result.GetAlsaOutputDevice().empty())
|
||||
{
|
||||
result.SetAlsaInputDevice(result.GetLegacyAlsaDevice());
|
||||
result.SetAlsaOutputDevice(result.GetLegacyAlsaDevice());
|
||||
result.SetLegacyAlsaDevice("");
|
||||
}
|
||||
}
|
||||
#if JACK_HOST
|
||||
result.Initialize();
|
||||
@@ -1797,6 +1919,17 @@ static bool ensureNoDotDot(const std::filesystem::path &path)
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool isInfoFile(const FileEntry &l)
|
||||
{
|
||||
if (l.displayName_.starts_with("LICENSE"))
|
||||
return true;
|
||||
if (l.displayName_.starts_with("README"))
|
||||
return true;
|
||||
if (l.displayName_.find(".md") == l.displayName_.length() - 3)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
static void AddFilesToResult(
|
||||
FileRequestResult &result,
|
||||
const ModFileTypes::ModDirectory *modDirectoryInfo, // yyx
|
||||
@@ -1862,6 +1995,12 @@ static void AddFilesToResult(
|
||||
{
|
||||
return l.isDirectory_ > r.isDirectory_;
|
||||
}
|
||||
bool lIsInfoFile = isInfoFile(l);
|
||||
bool rIsInfoFile = isInfoFile(r);
|
||||
if (lIsInfoFile != rIsInfoFile)
|
||||
{
|
||||
return lIsInfoFile > rIsInfoFile;
|
||||
}
|
||||
return collator->Compare(l.displayName_,r.displayName_) < 0; });
|
||||
}
|
||||
|
||||
@@ -1931,6 +2070,13 @@ static void AddTracksToResult(
|
||||
{
|
||||
return l.isDirectory_ > r.isDirectory_;
|
||||
}
|
||||
bool lIsInfoFile = isInfoFile(l);
|
||||
bool rIsInfoFile = isInfoFile(r);
|
||||
if (lIsInfoFile != rIsInfoFile)
|
||||
{
|
||||
return lIsInfoFile < rIsInfoFile;
|
||||
}
|
||||
|
||||
return collator->Compare(l.displayName_, r.displayName_) < 0;
|
||||
});
|
||||
// Add audio files.
|
||||
@@ -2702,7 +2848,7 @@ std::string Storage::ToAbstractPathJson(const std::string &pathJson)
|
||||
{
|
||||
json_variant v = json_variant::parse(pathJson);
|
||||
|
||||
v = AtomConverter::AbstractPath(v,GetPluginUploadDirectory().string());
|
||||
v = AtomConverter::AbstractPath(v, GetPluginUploadDirectory().string());
|
||||
|
||||
return v.to_string();
|
||||
}
|
||||
@@ -2710,7 +2856,7 @@ std::string Storage::FromAbstractPathJson(const std::string &pathJson)
|
||||
{
|
||||
json_variant v = json_variant::parse(pathJson);
|
||||
|
||||
v = AtomConverter::MapPath(v,GetPluginUploadDirectory().string());
|
||||
v = AtomConverter::MapPath(v, GetPluginUploadDirectory().string());
|
||||
|
||||
return v.to_string();
|
||||
}
|
||||
|
||||
+1
-3
@@ -66,8 +66,6 @@ struct PluginPresetValues {
|
||||
};
|
||||
|
||||
|
||||
// controls user-defined storage. Implmentation hidden to allow to later migration to a database (perhaps)
|
||||
|
||||
class Storage {
|
||||
private:
|
||||
std::filesystem::path dataRoot;
|
||||
@@ -81,7 +79,7 @@ private:
|
||||
|
||||
private:
|
||||
void FillSampleDirectoryTree(FilePropertyDirectoryTree*node, const std::filesystem::path&directory) const;
|
||||
|
||||
void UpgradeFactoryPresets();
|
||||
void MaybeCopyDefaultPresets();
|
||||
static std::string SafeEncodeName(const std::string& name);
|
||||
static std::string SafeDecodeName(const std::string& name);
|
||||
|
||||
+38
-10
@@ -312,7 +312,14 @@ public:
|
||||
auto mimeType = GetMimeType(path);
|
||||
if (mimeType.empty())
|
||||
{
|
||||
throw PiPedalException("Can't download files of this type.");
|
||||
if (isInfoFile(path))
|
||||
{
|
||||
mimeType = "text/plain";
|
||||
}
|
||||
else
|
||||
{
|
||||
throw PiPedalException("Can't download files of this type.");
|
||||
}
|
||||
}
|
||||
res.set(HttpField::content_type, mimeType);
|
||||
res.set(HttpField::cache_control, "no-cache");
|
||||
@@ -403,6 +410,21 @@ public:
|
||||
path));
|
||||
}
|
||||
|
||||
bool isInfoFile(const fs::path &path)
|
||||
{
|
||||
auto extension = path.extension();
|
||||
if (extension == ".md" || extension == ".txt")
|
||||
{
|
||||
return true;
|
||||
}
|
||||
auto filename = path.stem();
|
||||
if (filename == "LICENSE" || filename == "README") {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
virtual void get_response(
|
||||
const uri &request_uri,
|
||||
HttpRequest &req,
|
||||
@@ -417,9 +439,6 @@ public:
|
||||
{
|
||||
fs::path path = request_uri.query("path");
|
||||
|
||||
bool t = this->model->IsInUploadsDirectory(path);
|
||||
std::cout << (t ? "true" : "false") << std::endl;
|
||||
(void)t;
|
||||
if (!fs::exists(path) || !this->model->IsInUploadsDirectory(path) || HasDotDot(path))
|
||||
{
|
||||
throw PiPedalException("File not found.");
|
||||
@@ -427,7 +446,14 @@ public:
|
||||
auto mimeType = GetMimeType(path);
|
||||
if (mimeType.empty())
|
||||
{
|
||||
throw PiPedalException("Can't download files of this type.");
|
||||
if (isInfoFile(path))
|
||||
{
|
||||
mimeType = "text/plain";
|
||||
}
|
||||
else
|
||||
{
|
||||
throw PiPedalException("Can't download files of this type.");
|
||||
}
|
||||
}
|
||||
res.set(HttpField::content_type, mimeType);
|
||||
res.set(HttpField::cache_control, "no-cache");
|
||||
@@ -739,17 +765,19 @@ public:
|
||||
{
|
||||
std::string segment = request_uri.segment(1);
|
||||
|
||||
if (segment == "Tone3000Auth") {
|
||||
// https://www.tone3000.com/api/v1/auth?redirect_url=http://10.0.0.151:8080/var/Tone3000Auth&otp_only=true
|
||||
if (segment == "Tone3000Auth")
|
||||
{
|
||||
// https://www.tone3000.com/api/v1/auth?redirect_url=http://10.0.0.151:8080/var/Tone3000Auth&otp_only=true
|
||||
std::string apiKey = request_uri.query("api_key");
|
||||
|
||||
model->SetTone3000Auth(apiKey);
|
||||
|
||||
res.set(HttpField::content_type, "application/json");
|
||||
res.set(HttpField::cache_control, "no-cache");
|
||||
|
||||
|
||||
res.setBody("\"OK\"");
|
||||
} else if (segment == "uploadPluginPresets")
|
||||
}
|
||||
else if (segment == "uploadPluginPresets")
|
||||
{
|
||||
PluginPresets presets;
|
||||
fs::path filePath = req.get_body_temporary_file();
|
||||
@@ -921,7 +949,7 @@ public:
|
||||
{
|
||||
fs::path inputPath{inputFile};
|
||||
std::string extension = inputPath.extension();
|
||||
if (extensionChecker.IsValidExtension(extension))
|
||||
if (extensionChecker.IsValidExtension(extension) || isInfoFile(inputFile))
|
||||
{
|
||||
auto si = zipFile->GetFileInputStream(inputFile);
|
||||
std::string path = this->model->UploadUserFile(directory, instanceId, patchProperty, inputFile, si, zipFile->GetFileSize(inputFile));
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
#!/bin/bash
|
||||
valgrind --leak-check=full --log-file=vg.output ./build/src/pipedald /etc/pipedal/config ./vite/dist -port 0.0.0.0:8080 -log-level debug
|
||||
|
||||
Generated
+1839
-100
File diff suppressed because it is too large
Load Diff
@@ -20,9 +20,11 @@
|
||||
"@types/react-window": "^1.8.8",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0",
|
||||
"react-markdown": "^10.1.0",
|
||||
"react-remark": "^2.1.0",
|
||||
"react-virtualized-auto-sizer": "^1.0.25",
|
||||
"react-window": "^1.8.11",
|
||||
"rehype-external-links": "^3.0.0",
|
||||
"tss-react": "^4.9.15"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -1,3 +1,27 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) Robin E. R. Davies
|
||||
* Copyright (c) Gabriel Hernandez
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
|
||||
export default class AlsaDeviceInfo {
|
||||
@@ -9,6 +33,10 @@ export default class AlsaDeviceInfo {
|
||||
this.sampleRates = input.sampleRates as number[];
|
||||
this.minBufferSize = input.minBufferSize;
|
||||
this.maxBufferSize = input.maxBufferSize;
|
||||
this.supportsCapture = input.supportsCapture ? true : false;
|
||||
this.supportsPlayback = input.supportsPlayback ? true : false;
|
||||
this.captureBusy = input.captureBusy;
|
||||
this.playbackBusy = input.playbackBusy;
|
||||
return this;
|
||||
}
|
||||
static deserialize_array(input: any): AlsaDeviceInfo[]
|
||||
@@ -63,4 +91,8 @@ export default class AlsaDeviceInfo {
|
||||
sampleRates: number[] = [];
|
||||
minBufferSize: number = 0;
|
||||
maxBufferSize: number = 0;
|
||||
supportsCapture: boolean = true;
|
||||
supportsPlayback: boolean = true;
|
||||
captureBusy: boolean = false;
|
||||
playbackBusy: boolean = false;
|
||||
};
|
||||
@@ -1,7 +1,8 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2022 Robin E. R. Davies
|
||||
* Copyright (c) Robin E. R. Davies
|
||||
* Copyright (c) Gabriel Hernandez
|
||||
*
|
||||
* 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
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
// Copyright (c) 2025 Robin E. R. Davies
|
||||
// Copyright (c) Robin E. R. Davies
|
||||
// Copyright (c) Gabriel Hernandez
|
||||
//
|
||||
// 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
|
||||
|
||||
@@ -22,6 +22,8 @@ import React from 'react';
|
||||
import { createStyles } from './WithStyles';
|
||||
|
||||
// import Tone3000Dialog from './Tone3000Dialog';
|
||||
|
||||
import TextInfoDialog from './TextInfoDialog';
|
||||
import Tone3000HelpDialog from './Tone3000HelpDialog';
|
||||
import GuitarMLHelpDialog from './GuitarMlHelpDialog';
|
||||
import HelpOutlineIcon from '@mui/icons-material/HelpOutline';
|
||||
@@ -172,7 +174,9 @@ export interface FilePropertyDialogState {
|
||||
selectedFiles: string[],
|
||||
//openTone3000Dialog: boolean,
|
||||
openTone3000Help: boolean,
|
||||
openGuitarMlHelp: boolean
|
||||
openGuitarMlHelp: boolean,
|
||||
|
||||
textFileName? : string;
|
||||
|
||||
};
|
||||
|
||||
@@ -702,7 +706,9 @@ export default withStyles(
|
||||
if (this.state.previousSelection == selectedItem) {
|
||||
return;
|
||||
}
|
||||
this.props.onApply(fileProperty, selectedItem);
|
||||
if (!this.isLicenseFile(selectedItem) && !this.isFolderArtwork(selectedItem)) {
|
||||
this.props.onApply(fileProperty, selectedItem);
|
||||
}
|
||||
this.setState({previousSelection: selectedItem});
|
||||
}
|
||||
|
||||
@@ -1754,9 +1760,32 @@ export default withStyles(
|
||||
onClose={() => this.setState({ openGuitarMlHelp: false })}
|
||||
/>
|
||||
)}
|
||||
{this.state.textFileName !== undefined && (
|
||||
<TextInfoDialog open={true}
|
||||
title={pathFileNameOnly(this.state.textFileName)}
|
||||
fileName={this.state.textFileName} onClose={() => this.setState({ textFileName: undefined })}
|
||||
/>
|
||||
)}
|
||||
</DialogEx>
|
||||
);
|
||||
}
|
||||
isLicenseFile(fileName: string) {
|
||||
let extension = pathExtension(fileName);
|
||||
if (extension === ".txt" || extension === ".md"){
|
||||
return true;
|
||||
}
|
||||
let fileNameOnly = pathFileNameOnly(fileName);
|
||||
if (fileNameOnly === "LICENSE" || fileNameOnly === "README")
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
||||
}
|
||||
handleShowTextFile(fileName: string) {
|
||||
this.setState({ textFileName: fileName });
|
||||
}
|
||||
|
||||
openSelectedFile(): void {
|
||||
if (this.state.multiSelect || this.state.reordering) {
|
||||
return;
|
||||
@@ -1768,7 +1797,11 @@ export default withStyles(
|
||||
this.requestFiles(this.state.selectedFile);
|
||||
this.setState({ navDirectory: this.state.selectedFile });
|
||||
} else {
|
||||
this.props.onOk(this.props.fileProperty, this.state.selectedFile);
|
||||
if (this.isLicenseFile(this.state.selectedFile)) {
|
||||
this.handleShowTextFile(this.state.selectedFile);
|
||||
} else {
|
||||
this.props.onOk(this.props.fileProperty, this.state.selectedFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -189,4 +189,46 @@ export default class JackHostStatus {
|
||||
}
|
||||
}
|
||||
|
||||
static getDisplayViewNoCpu(label: string, status?: JackHostStatus): React.ReactNode {
|
||||
if (!status) {
|
||||
return (<div style={{ whiteSpace: "nowrap" }}>
|
||||
<Typography variant="caption" color="inherit">{label}</Typography>
|
||||
<Typography variant="caption"> </Typography>
|
||||
</div>);
|
||||
}
|
||||
if (status.restarting) {
|
||||
return (
|
||||
<div style={{ whiteSpace: "nowrap" }}>
|
||||
<Typography variant="caption" color="inherit">{label}</Typography>
|
||||
<span style={{ color: RED_COLOR }}>
|
||||
<Typography variant="caption" color="inherit">Restarting </Typography>
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
|
||||
} else if (!status.active) {
|
||||
return (
|
||||
<div style={{ whiteSpace: "nowrap" }}>
|
||||
<Typography variant="caption" color="inherit">{label}</Typography>
|
||||
|
||||
<span style={{ color: RED_COLOR }}>
|
||||
<Typography variant="caption" color="inherit">{status.errorMessage === "" ? "Audio\u00A0Stopped" : status.errorMessage} </Typography>
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
let underrunError = status.msSinceLastUnderrun < 15 * 1000;
|
||||
return (
|
||||
<div style={{ whiteSpace: "nowrap" }}>
|
||||
<Typography variant="caption" color="inherit">{label}</Typography>
|
||||
<span style={{ color: underrunError ? RED_COLOR : GREEN_COLOR }}>
|
||||
<Typography variant="caption" color="inherit">
|
||||
XRuns: {status.underruns + ""}
|
||||
</Typography>
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
@@ -20,12 +20,13 @@
|
||||
|
||||
|
||||
export default class JackServerSettings {
|
||||
deserialize(input: any) : JackServerSettings{
|
||||
deserialize(input: any): JackServerSettings {
|
||||
this.valid = input.valid;
|
||||
this.isOnboarding = input.isOnboarding;
|
||||
this.isJackAudio = input.isJackAudio;
|
||||
this.rebootRequired = input.rebootRequired;
|
||||
this.alsaDevice = input.alsaDevice?? "";
|
||||
this.alsaInputDevice = input.alsaInputDevice ?? "";
|
||||
this.alsaOutputDevice = input.alsaOutputDevice ?? "";
|
||||
this.sampleRate = input.sampleRate;
|
||||
this.bufferSize = input.bufferSize;
|
||||
this.numberOfBuffers = input.numberOfBuffers;
|
||||
@@ -40,27 +41,54 @@ export default class JackServerSettings {
|
||||
// this.valid = true;
|
||||
// }
|
||||
// }
|
||||
clone(): JackServerSettings
|
||||
{
|
||||
clone(): JackServerSettings {
|
||||
return new JackServerSettings().deserialize(this);
|
||||
}
|
||||
valid: boolean = false;
|
||||
isOnboarding: boolean = true;
|
||||
rebootRequired = false;
|
||||
isJackAudio = false;
|
||||
alsaDevice: string = "";
|
||||
alsaInputDevice: string = "";
|
||||
alsaOutputDevice: string = "";
|
||||
sampleRate = 48000;
|
||||
bufferSize = 64;
|
||||
numberOfBuffers = 3;
|
||||
|
||||
getSummaryText() {
|
||||
if (this.valid) {
|
||||
let device = this.alsaDevice;
|
||||
if (device.startsWith("hw:")) device = device.substring(3);
|
||||
return device + " - Rate: " + this.sampleRate + " BufferSize: " + this.bufferSize + " Buffers: " + this.numberOfBuffers;
|
||||
} else {
|
||||
return "Not configured";
|
||||
/**
|
||||
* Configure this instance to use the dummy audio device. This mirrors the
|
||||
* behaviour of the backend JackServerSettings::UseDummyAudioDevice method
|
||||
* and is used when temporarily releasing ALSA devices.
|
||||
* Needed when changing devices and when testing new settings with apply button.
|
||||
*/
|
||||
useDummyAudioDevice() {
|
||||
if (this.sampleRate === 0) {
|
||||
this.sampleRate = 48000;
|
||||
}
|
||||
this.valid = true;
|
||||
this.alsaInputDevice = "__DUMMY_AUDIO__dummy:channels_2";
|
||||
this.alsaOutputDevice = "__DUMMY_AUDIO__dummy:channels_2";
|
||||
}
|
||||
|
||||
getSummaryText() {
|
||||
if (!this.valid || !this.alsaInputDevice || !this.alsaOutputDevice) {
|
||||
return "Not selected";
|
||||
}
|
||||
|
||||
let inDev = this.alsaInputDevice.startsWith("hw:")
|
||||
? this.alsaInputDevice.substring(3)
|
||||
: this.alsaInputDevice;
|
||||
let outDev = this.alsaOutputDevice.startsWith("hw:")
|
||||
? this.alsaOutputDevice.substring(3)
|
||||
: this.alsaOutputDevice;
|
||||
|
||||
let device: string;
|
||||
if (inDev === outDev) {
|
||||
device = inDev;
|
||||
|
||||
} else {
|
||||
device = inDev + "-> " + outDev;
|
||||
}
|
||||
return `${device} ${this.sampleRate} ${this.bufferSize}x${this.numberOfBuffers}`;
|
||||
}
|
||||
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,4 +1,5 @@
|
||||
// Copyright (c) 2022 Robin Davies
|
||||
// Copyright (c) Robin E. R. Davies
|
||||
// Copyright (c) Gabriel Hernandez
|
||||
//
|
||||
// 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
|
||||
@@ -23,6 +24,7 @@ import OkCancelDialog from './OkCancelDialog';
|
||||
import RadioSelectDialog from './RadioSelectDialog';
|
||||
import IconButtonEx from './IconButtonEx';
|
||||
import Typography from '@mui/material/Typography';
|
||||
import {isDarkMode} from './DarkMode';
|
||||
import { PiPedalModel, PiPedalModelFactory, State } from './PiPedalModel';
|
||||
import { ColorTheme } from './DarkMode';
|
||||
import ButtonBase from "@mui/material/ButtonBase";
|
||||
@@ -661,7 +663,8 @@ const SettingsDialog = withStyles(
|
||||
{(!isConfigValid) ?
|
||||
(
|
||||
<div className={classes.cpuStatusColor} style={{ paddingLeft: 48, position: "relative", top: -12 }}>
|
||||
<Typography display="block" variant="caption" color="textSecondary">Status: <span style={{ color: "#F00" }}>Not configured.</span></Typography>
|
||||
<Typography display="block" variant="caption" color="textSecondary">Status:
|
||||
<span style={{ color: isDarkMode() ? "#F88" : "#F00" }}>Not configured.</span></Typography>
|
||||
{(!this.props.onboarding) && (
|
||||
<Typography display="block" variant="caption" color="inherit">Governor: </Typography>
|
||||
)}
|
||||
@@ -702,7 +705,6 @@ const SettingsDialog = withStyles(
|
||||
onClose={() => this.setState({ showJackServerSettingsDialog: false })}
|
||||
onApply={(jackServerSettings) => {
|
||||
this.setState({
|
||||
showJackServerSettingsDialog: false,
|
||||
jackServerSettings: jackServerSettings
|
||||
});
|
||||
this.model.setJackServerSettings(jackServerSettings);
|
||||
|
||||
@@ -0,0 +1,189 @@
|
||||
// Copyright (c) 2025 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, { SyntheticEvent } from 'react';
|
||||
import IconButtonEx from './IconButtonEx';
|
||||
import Typography from '@mui/material/Typography';
|
||||
import { PiPedalModel, PiPedalModelFactory, State } from './PiPedalModel';
|
||||
import AppBar from '@mui/material/AppBar';
|
||||
import Toolbar from '@mui/material/Toolbar';
|
||||
import ArrowBackIcon from '@mui/icons-material/ArrowBack';
|
||||
import ReactMarkdown from 'react-markdown';
|
||||
import rehypeExternalLinks from 'rehype-external-links'
|
||||
import { PiPedalError } from './PiPedalError';
|
||||
import DialogEx from './DialogEx';
|
||||
import ResizeResponsiveComponent from './ResizeResponsiveComponent';
|
||||
|
||||
|
||||
|
||||
import Slide, { SlideProps } from '@mui/material/Slide';
|
||||
|
||||
interface TextInfoDialogProps {
|
||||
open: boolean;
|
||||
fileName: string;
|
||||
title: string;
|
||||
onClose: () => void;
|
||||
};
|
||||
|
||||
interface TextInfoDialogState {
|
||||
|
||||
textFileContent: string;
|
||||
fullScreen: boolean;
|
||||
};
|
||||
|
||||
|
||||
const Transition = React.forwardRef(function Transition(
|
||||
props: SlideProps, ref: React.Ref<unknown>
|
||||
) {
|
||||
return (<Slide direction="up" ref={ref} {...props} />);
|
||||
});
|
||||
|
||||
|
||||
const TextInfoDialog = class extends ResizeResponsiveComponent<TextInfoDialogProps, TextInfoDialogState> {
|
||||
|
||||
model: PiPedalModel;
|
||||
refNotices: React.RefObject<HTMLDivElement | null >;
|
||||
|
||||
|
||||
constructor(props: TextInfoDialogProps) {
|
||||
super(props);
|
||||
this.model = PiPedalModelFactory.getInstance();
|
||||
this.refNotices = React.createRef();
|
||||
this.handleDialogClose = this.handleDialogClose.bind(this);
|
||||
this.state = {
|
||||
textFileContent: "",
|
||||
fullScreen: this.windowSize.width < 600
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
onWindowSizeChanged(width: number, height: number): void {
|
||||
this.setState({fullScreen: width < 600});
|
||||
}
|
||||
|
||||
mounted: boolean = false;
|
||||
subscribed: boolean = false;
|
||||
|
||||
tick() {
|
||||
if (this.model.state.get() === State.Ready) {
|
||||
}
|
||||
}
|
||||
|
||||
timerHandle?: number;
|
||||
|
||||
|
||||
startTextFileRequest() {
|
||||
let url = this.model.varServerUrl + "downloadMediaFile?path=" + encodeURIComponent(this.props.fileName);
|
||||
if (this.state.textFileContent === "") {
|
||||
fetch(url)
|
||||
.then((request) => {
|
||||
if (!request.ok) {
|
||||
throw new PiPedalError("Info file request failed.");
|
||||
}
|
||||
return request.text();
|
||||
})
|
||||
.then((text) => {
|
||||
if (this.mounted) {
|
||||
this.setState({ textFileContent: text });
|
||||
}
|
||||
|
||||
})
|
||||
.catch((err) => {
|
||||
// ok in debug builds. File doesn't get placed until install time.
|
||||
console.log("Failed to fetch text file. " + err.toString());
|
||||
this.setState({textFileContent: err.toString()});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
super.componentDidMount?.();
|
||||
this.mounted = true;
|
||||
this.startTextFileRequest();
|
||||
}
|
||||
componentWillUnmount() {
|
||||
super.componentWillUnmount?.();
|
||||
this.mounted = false;
|
||||
}
|
||||
componentDidUpdate(prevProps: Readonly<TextInfoDialogProps>, prevState: Readonly<TextInfoDialogState>, snapshot: any): void {
|
||||
super.componentDidUpdate?.(prevProps, prevState, snapshot);
|
||||
}
|
||||
|
||||
handleDialogClose(_e: SyntheticEvent) {
|
||||
this.props.onClose();
|
||||
}
|
||||
|
||||
|
||||
render() {
|
||||
return (
|
||||
<DialogEx tag="info" fullScreen={this.state.fullScreen} open={this.props.open}
|
||||
onClose={() => { this.props.onClose() }} TransitionComponent={Transition}
|
||||
onEnterKey={() => { this.props.onClose() }}
|
||||
style={{ userSelect: "none" }}
|
||||
>
|
||||
|
||||
<div style={{ display: "flex", flexDirection: "column", flexWrap: "nowrap", width: "100%", height: "100%", overflow: "hidden" }}>
|
||||
<div style={{ flex: "0 0 auto" }}>
|
||||
<AppBar style={{
|
||||
position: 'relative',
|
||||
top: 0, left: 0
|
||||
}} >
|
||||
<Toolbar>
|
||||
<IconButtonEx tooltip="Back" edge="start" color="inherit" onClick={this.handleDialogClose} aria-label="back"
|
||||
>
|
||||
<ArrowBackIcon />
|
||||
</IconButtonEx>
|
||||
<Typography noWrap variant="h6"
|
||||
sx={{ marginLeft: 2, flex: 1 }}
|
||||
>
|
||||
{this.props.title}
|
||||
</Typography>
|
||||
</Toolbar>
|
||||
</AppBar>
|
||||
</div>
|
||||
<div style={{
|
||||
flex: "1 1 auto", position: "relative", overflow: "hidden",
|
||||
overflowX: "hidden", overflowY: "auto", userSelect: "text"
|
||||
}}
|
||||
>
|
||||
<div style={{ padding: "16px 32px",
|
||||
display: "flex", flexDirection: "row", flexWrap: "nowrap", alignItems: "start",
|
||||
}}>
|
||||
<div style={{ flex: "1 1 0px" }} />
|
||||
<div style={{ flex: "1 1 auto",maxWidth: 650 }} >
|
||||
<ReactMarkdown rehypePlugins={[[rehypeExternalLinks, {target: '_blank'}]]}>
|
||||
{this.state.textFileContent}
|
||||
</ReactMarkdown>
|
||||
</div>
|
||||
<div style={{ flex: " 5 5 0px" }} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</DialogEx >
|
||||
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
||||
export default TextInfoDialog;
|
||||
@@ -31,13 +31,13 @@ export interface ToolTipExProps extends React.ComponentProps<typeof Tooltip> {
|
||||
/* ToolTipEx: a reimplementation of the MUI Tooltip that works with pointer events and long presses. */
|
||||
|
||||
function ToolTipEx(props: ToolTipExProps) {
|
||||
let {title, valueTooltip, ...extras} = props;
|
||||
let [open,setOpen]= React.useState(false);
|
||||
let { title, valueTooltip, ...extras } = props;
|
||||
let [open, setOpen] = React.useState(false);
|
||||
let [isLongPress, setIsLongPress] = React.useState(false);
|
||||
let [longPressLeaving, setLongPressLeaving] = React.useState(false);
|
||||
let [timeout, setTimeout] = React.useState<number>(0);
|
||||
let [timeoutInstance, setTimeoutInstance] = React.useState<number>(0);
|
||||
let [pointerDownPoint, setPointerdownPoint] = React.useState<{x: number, y: number} | null>(null);
|
||||
let [pointerDownPoint, setPointerdownPoint] = React.useState<{ x: number, y: number } | null>(null);
|
||||
|
||||
const hoverTimeout = 1250;
|
||||
const longpressTimeout = 500;
|
||||
@@ -60,7 +60,7 @@ function ToolTipEx(props: ToolTipExProps) {
|
||||
setOpen(false);
|
||||
stopTimeout();
|
||||
setLongPressLeaving(false);
|
||||
}
|
||||
}
|
||||
|
||||
function handlePointerDownCapture(event: React.PointerEvent<HTMLDivElement>) {
|
||||
let pointerType = (event as any).pointerType || "n/a";
|
||||
@@ -71,11 +71,11 @@ function ToolTipEx(props: ToolTipExProps) {
|
||||
} else { // pen, or touch.
|
||||
setTimeout(longpressTimeout);
|
||||
setOpen(false);
|
||||
setPointerdownPoint({x: event.clientX, y: event.clientY});
|
||||
setPointerdownPoint({ x: event.clientX, y: event.clientY });
|
||||
}
|
||||
}
|
||||
|
||||
React.useEffect(()=> {
|
||||
React.useEffect(() => {
|
||||
let t = timeout;
|
||||
let handle: number | null = null;
|
||||
if (valueTooltip === undefined) // no timeout if there's a value tooltip
|
||||
@@ -84,7 +84,7 @@ function ToolTipEx(props: ToolTipExProps) {
|
||||
// console.log("ToolTipEx: starting timeout for ", t);
|
||||
handle = window.setTimeout(() => {
|
||||
setOpen(true);
|
||||
},t);
|
||||
}, t);
|
||||
}
|
||||
}
|
||||
return () => {
|
||||
@@ -93,9 +93,9 @@ function ToolTipEx(props: ToolTipExProps) {
|
||||
window.clearTimeout(handle);
|
||||
}
|
||||
};
|
||||
},[timeoutInstance]);
|
||||
}, [timeoutInstance]);
|
||||
|
||||
React.useEffect(()=> {
|
||||
React.useEffect(() => {
|
||||
if (longPressLeaving) {
|
||||
let handle: number | null = null;
|
||||
handle = window.setTimeout(() => {
|
||||
@@ -110,7 +110,7 @@ function ToolTipEx(props: ToolTipExProps) {
|
||||
} else {
|
||||
return () => { };
|
||||
}
|
||||
},[longPressLeaving]);
|
||||
}, [longPressLeaving]);
|
||||
|
||||
function handlePointerCancel(event: React.PointerEvent<HTMLDivElement>) {
|
||||
setOpen(false);
|
||||
@@ -158,15 +158,17 @@ function ToolTipEx(props: ToolTipExProps) {
|
||||
if (isLongPress) {
|
||||
// If this is a long press, we want to leave the tooltip open for a while.
|
||||
setLongPressLeaving(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
let effectiveTitle: React.ReactNode | null = null;
|
||||
let placement: "top-start" | "right" = "top-start";
|
||||
let popperOffset = 0;
|
||||
if (valueTooltip !== undefined && !isLongPress) {
|
||||
effectiveTitle = valueTooltip; // Show value tooltip if available
|
||||
placement = "right";
|
||||
popperOffset = 16;
|
||||
}
|
||||
else {
|
||||
if (open || isLongPress) {
|
||||
@@ -186,13 +188,13 @@ function ToolTipEx(props: ToolTipExProps) {
|
||||
handleClickCapture(e);
|
||||
}}
|
||||
|
||||
onMouseEnter={(e)=> {
|
||||
onMouseEnter={(e) => {
|
||||
// console.log("ToolTipEx: onMouseEnter");
|
||||
if (!longPressLeaving) {// Don't handle mouse enter if we're in a long press leaving state
|
||||
handleMouseEnter(e);
|
||||
}
|
||||
}}
|
||||
onMouseLeave={(e)=> {
|
||||
onMouseLeave={(e) => {
|
||||
// console.log("ToolTipEx: onMouseLeave");
|
||||
handleMouseLeave(e);
|
||||
}}
|
||||
@@ -227,14 +229,26 @@ function ToolTipEx(props: ToolTipExProps) {
|
||||
disableFocusListener={true}
|
||||
|
||||
title={effectiveTitle}
|
||||
placement={ placement}
|
||||
arrow enterDelay={1500} enterNextDelay={1500}
|
||||
placement={placement}
|
||||
|
||||
arrow enterDelay={1500} enterNextDelay={1500}
|
||||
slotProps={{
|
||||
transition: {
|
||||
timeout: 0 // Disable transition for the tooltip
|
||||
},
|
||||
popper: {
|
||||
modifiers: [
|
||||
{
|
||||
name: 'offset',
|
||||
options: {
|
||||
offset: [0, popperOffset],
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
}
|
||||
}}
|
||||
/>
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user