Merge pull request #393 from rerdavies/dev

v1.5.92 Dev merge.
This commit is contained in:
Robin Davies
2025-09-12 09:17:46 -04:00
committed by GitHub
86 changed files with 4197 additions and 2686 deletions
+1 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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
+10 -3
View File
@@ -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)
+3 -3
View File
@@ -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:&nbsp;<a href='https://rerdavies.github.io/pipedal/download.html'>v1.5.91</a>
Download:&nbsp;<a href='https://rerdavies.github.io/pipedal/download.html'>v1.5.92</a>
Website:&nbsp;[https://rerdavies.github.io/pipedal](https://rerdavies.github.io/pipedal).
Documentation:&nbsp;[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!
&nbsp;
@@ -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)
&nbsp;
&nbsp;
#### [PiPedal Architecture](https://rerdavies.github.io/pipedal/Architecture.html)
Executable
+16
View File
@@ -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
View File
@@ -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
+1
View File
@@ -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
View File
@@ -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.
+85
View File
@@ -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
+3 -2
View File
@@ -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
View File
@@ -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.
+4 -4
View File
@@ -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 ;
+1 -1
View File
@@ -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 """
+1 -1
View File
@@ -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";
+1 -1
View File
@@ -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";
+1 -1
View File
@@ -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";
+1 -1
View File
@@ -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>;
+1 -1
View File
@@ -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.
+1 -1
View File
@@ -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.
+1 -1
View File
@@ -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.
+1 -1
View File
@@ -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.
+1 -1
View File
@@ -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.
+1 -1
View File
@@ -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.
""" ;
+2 -2
View File
@@ -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>;
+2 -2
View File
@@ -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>;
+1 -1
View File
@@ -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.
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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.
+1 -1
View File
@@ -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>;
+1 -1
View File
@@ -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.
+1 -1
View File
@@ -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.
+2 -2
View File
@@ -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>;
+2 -2
View File
@@ -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>;
+1 -1
View File
@@ -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.
""" ;
+1 -1
View File
@@ -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.
+2 -1
View File
@@ -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 ..
+2
View File
@@ -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
View File
@@ -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
View File
@@ -1,4 +1,4 @@
#!/bin/bash
rm -rf ./lv2/aarch64/*
cp -R /usr/lib/lv2/ToobAmp.lv2/ ./lv2/aarch64/
+27 -14
View File
@@ -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
View File
@@ -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
+1 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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;
}
+2
View File
@@ -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;
}
+6 -24
View File
@@ -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())
+52 -35
View File
@@ -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);
};
}
+12 -3
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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())
{
+3
View File
@@ -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
View File
@@ -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
View File
@@ -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(&params);
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(&params);
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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
+1839 -100
View File
File diff suppressed because it is too large Load Diff
+2
View File
@@ -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": {
+32
View File
@@ -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;
};
+2 -1
View File
@@ -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
+2 -1
View File
@@ -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
+36 -3
View File
@@ -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);
}
}
}
+42
View File
@@ -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">&nbsp;</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&nbsp;&nbsp;</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}&nbsp;&nbsp;</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:&nbsp;{status.underruns + ""}&nbsp;&nbsp;
</Typography>
</span>
</div>
);
}
}
};
+40 -12
View File
@@ -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
+5 -3
View File
@@ -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);
+189
View File
@@ -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 -17
View File
@@ -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>
)
}