diff --git a/.vscode/settings.json b/.vscode/settings.json index af215ac..e6e4f3a 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -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": { diff --git a/CMakeLists.txt b/CMakeLists.txt index 1b9c992..3f9ecbd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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/") diff --git a/NetworkManagerP2P/src/wpa_init b/NetworkManagerP2P/src/wpa_init index faf8e15..d9a3eb3 100755 --- a/NetworkManagerP2P/src/wpa_init +++ b/NetworkManagerP2P/src/wpa_init @@ -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 diff --git a/PiPedalCommon/src/include/json.hpp b/PiPedalCommon/src/include/json.hpp index def138f..7792535 100644 --- a/PiPedalCommon/src/include/json.hpp +++ b/PiPedalCommon/src/include/json.hpp @@ -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::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::max_digits10) << f; // round-trip format } } - + void write(double d) + { + write_double(d); + } template void write(const std::vector &value) diff --git a/README.md b/README.md index 942c565..e8f35fc 100644 --- a/README.md +++ b/README.md @@ -9,11 +9,11 @@ -Download: v1.5.91 +Download: v1.5.92 Website: [https://rerdavies.github.io/pipedal](https://rerdavies.github.io/pipedal). Documentation: [https://rerdavies.github.io/pipedal/Documentation.html](https://rerdavies.github.io/pipedal/Documentation.html). -#### NEW version 1.5.91 Release. See the [release notes](https://rerdavies.github.io/pipedal/ReleaseNotes) for details. TooB NAM calibration, manipulation of i/r files in Toob Convolution Reverb, new Parametric EQ and 3 Band EQ plugins, and MORE! +#### NEW version 1.5.92 Release. See the [release notes](https://rerdavies.github.io/pipedal/ReleaseNotes) for details. TooB NAM calibration, manipulation of i/r files in Toob Convolution Reverb, new Parametric EQ and 3 Band EQ plugins, and MORE!   @@ -79,7 +79,7 @@ https://github.com/user-attachments/assets/9a9fd0c6-78fc-4284-8b44-6a1929c00cc6 ### [The Build System](https://rerdavies.github.io/pipedal/TheBuildSystem.html) ### [How to Debug PiPedal](https://rerdavies.github.io/pipedal/Debugging.html) -   +  #### [PiPedal Architecture](https://rerdavies.github.io/pipedal/Architecture.html) diff --git a/build-prod.sh b/build-prod.sh new file mode 100755 index 0000000..5758e83 --- /dev/null +++ b/build-prod.sh @@ -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 + diff --git a/debugDocs.sh b/debugDocs.sh index 2261a47..bebd254 100755 --- a/debugDocs.sh +++ b/debugDocs.sh @@ -6,4 +6,4 @@ # bundle install # cd docs -bundle exec jekyll serve --host \ No newline at end of file +bundle exec jekyll serve --host 0.0.0.0 \ No newline at end of file diff --git a/default_presets/presets/Default+Bank.bank b/default_presets/presets/Default+Bank.bank index d4e6a1a..496d9a4 100644 --- a/default_presets/presets/Default+Bank.bank +++ b/default_presets/presets/Default+Bank.bank @@ -1,1849 +1 @@ -{ - "name": "", - "nextInstanceId": 21, - "selectedPreset": 19, - "presets": [ - { - "instanceId": 19, - "preset": { - "name": "Heavy", - "input_volume_db": 0, - "output_volume_db": 0, - "items": [ - { - "instanceId": 16, - "uri": "http://two-play.com/plugins/toob-tuner", - "isEnabled": true, - "controlValues": [ - { - "key": "REFFREQ", - "value": 440 - }, - { - "key": "THRESHOLD", - "value": -23.1093884 - }, - { - "key": "MUTE", - "value": 0 - } - ], - "pluginName": "TooB Tuner", - "midiBindings": [], - "midiChannelBinding": null, - "stateUpdateCount": 0, - "lv2State": [ - false, - {} - ], - "lilvPresetUri": "", - "pathProperties": {}, - "title": "", - "useModUi": false - }, - { - "instanceId": 49, - "uri": "http://two-play.com/plugins/toob-noise-gate", - "isEnabled": true, - "controlValues": [ - { - "key": "threshold", - "value": -60 - }, - { - "key": "hysteresis", - "value": -6 - }, - { - "key": "trigger_led", - "value": 0 - }, - { - "key": "reduction", - "value": -60 - }, - { - "key": "attack", - "value": 1 - }, - { - "key": "hold", - "value": 100 - }, - { - "key": "release", - "value": 330 - }, - { - "key": "gate_level", - "value": -60 - } - ], - "pluginName": "TooB Noise Gate", - "midiBindings": [], - "midiChannelBinding": null, - "stateUpdateCount": 0, - "lv2State": [ - false, - {} - ], - "lilvPresetUri": "", - "pathProperties": {}, - "title": "", - "useModUi": false - }, - { - "instanceId": 42, - "uri": "http://two-play.com/plugins/toob-chorus", - "isEnabled": false, - "controlValues": [ - { - "key": "rate", - "value": 0.291666657 - }, - { - "key": "depth", - "value": 0.375 - }, - { - "key": "dryWet", - "value": 1 - } - ], - "pluginName": "TooB CE-2 Chorus", - "midiBindings": [], - "midiChannelBinding": null, - "stateUpdateCount": 0, - "lv2State": [ - false, - {} - ], - "lilvPresetUri": "", - "pathProperties": {}, - "title": "", - "useModUi": false - }, - { - "instanceId": 41, - "uri": "http://two-play.com/plugins/toob-delay", - "isEnabled": false, - "controlValues": [ - { - "key": "delay", - "value": 340 - }, - { - "key": "level", - "value": 27 - }, - { - "key": "feedback", - "value": 39.1666679 - } - ], - "pluginName": "TooB Delay", - "midiBindings": [], - "midiChannelBinding": null, - "stateUpdateCount": 0, - "lv2State": [ - false, - {} - ], - "lilvPresetUri": "", - "pathProperties": {}, - "title": "", - "useModUi": false - }, - { - "instanceId": 39, - "uri": "http://two-play.com/plugins/toob-ml", - "isEnabled": true, - "controlValues": [ - { - "key": "trim", - "value": 1 - }, - { - "key": "bass", - "value": 0.5 - }, - { - "key": "mid", - "value": 0.5 - }, - { - "key": "treble", - "value": 0.5 - }, - { - "key": "model", - "value": 18 - }, - { - "key": "gain", - "value": 2.41666675 - }, - { - "key": "master", - "value": -4.5 - }, - { - "key": "sag", - "value": 0 - }, - { - "key": "sagd", - "value": 0 - }, - { - "key": "sagf", - "value": 13 - } - ], - "pluginName": "TooB ML Amplifier", - "midiBindings": [], - "midiChannelBinding": null, - "stateUpdateCount": 2, - "lv2State": [ - true, - { - "http://two-play.com/plugins/toob-ml#modelFile": { - "flags": 3, - "atomType": "http://lv2plug.in/ns/ext/atom#Path", - "value": "ToobMlModels/HT40_Overdrive.json" - }, - "http://two-play.com/plugins/toob-ml#version": { - "flags": 3, - "atomType": "http://lv2plug.in/ns/ext/atom#Float", - "value": 1 - } - } - ], - "lilvPresetUri": "", - "pathProperties": { - "http://two-play.com/plugins/toob-ml#modelFile": "{\"otype_\": \"Path\",\"value\": \"/var/pipedal/audio_uploads/ToobMlModels/HT40_Overdrive.json\"}" - }, - "title": "", - "useModUi": false - }, - { - "instanceId": 31, - "uri": "http://two-play.com/plugins/toob-freeverb", - "isEnabled": true, - "controlValues": [ - { - "key": "dryWet", - "value": 0.25 - }, - { - "key": "roomSize", - "value": 0.733333349 - }, - { - "key": "damping", - "value": 0.574999988 - }, - { - "key": "bypass", - "value": 1 - }, - { - "key": "tails", - "value": 1 - } - ], - "pluginName": "TooB Freeverb", - "midiBindings": [], - "midiChannelBinding": null, - "stateUpdateCount": 0, - "lv2State": [ - false, - {} - ], - "lilvPresetUri": "", - "pathProperties": {}, - "title": "", - "useModUi": false - }, - { - "instanceId": 51, - "uri": "http://two-play.com/plugins/toob-parametric-eq", - "isEnabled": true, - "controlValues": [ - { - "key": "loCut", - "value": 50.7010994 - }, - { - "key": "hiCut", - "value": 21 - }, - { - "key": "lfLevel", - "value": 0 - }, - { - "key": "lfC", - "value": 120 - }, - { - "key": "lmfLevel", - "value": 0 - }, - { - "key": "lmfC", - "value": 400 - }, - { - "key": "lmfQ", - "value": 1.5 - }, - { - "key": "hmfLevel", - "value": 0 - }, - { - "key": "hmfC", - "value": 2 - }, - { - "key": "hmfQ", - "value": 1.5 - }, - { - "key": "hfLevel", - "value": -3.25 - }, - { - "key": "hfC", - "value": 3.875 - }, - { - "key": "gain", - "value": 0 - } - ], - "pluginName": "TooB Parametric EQ", - "midiBindings": [], - "midiChannelBinding": null, - "stateUpdateCount": 0, - "lv2State": [ - false, - {} - ], - "lilvPresetUri": "", - "pathProperties": {}, - "title": "", - "useModUi": false - } - ], - "nextInstanceId": 51, - "snapshots": [], - "selectedSnapshot": -1, - "selectedPlugin": 16 - } - }, - { - "instanceId": 17, - "preset": { - "name": "Sparkling Clean", - "input_volume_db": 0, - "output_volume_db": 0, - "items": [ - { - "instanceId": 16, - "uri": "http://two-play.com/plugins/toob-tuner", - "isEnabled": true, - "controlValues": [ - { - "key": "REFFREQ", - "value": 440 - }, - { - "key": "THRESHOLD", - "value": -23.1093884 - }, - { - "key": "MUTE", - "value": 0 - } - ], - "pluginName": "TooB Tuner", - "midiBindings": [], - "midiChannelBinding": null, - "stateUpdateCount": 0, - "lv2State": [ - false, - {} - ], - "lilvPresetUri": "", - "pathProperties": {}, - "title": "", - "useModUi": false - }, - { - "instanceId": 55, - "uri": "http://two-play.com/plugins/toob-noise-gate", - "isEnabled": true, - "controlValues": [ - { - "key": "threshold", - "value": -65 - }, - { - "key": "hysteresis", - "value": -6 - }, - { - "key": "trigger_led", - "value": 0 - }, - { - "key": "reduction", - "value": -60 - }, - { - "key": "attack", - "value": 1 - }, - { - "key": "hold", - "value": 100 - }, - { - "key": "release", - "value": 330 - }, - { - "key": "gate_level", - "value": -60 - } - ], - "pluginName": "TooB Noise Gate", - "midiBindings": [], - "midiChannelBinding": null, - "stateUpdateCount": 0, - "lv2State": [ - false, - {} - ], - "lilvPresetUri": "", - "pathProperties": {}, - "title": "", - "useModUi": false - }, - { - "instanceId": 53, - "uri": "http://two-play.com/plugins/toob-phaser", - "isEnabled": true, - "controlValues": [ - { - "key": "rate", - "value": 0.424131542 - }, - { - "key": "dryWet", - "value": 0.400000006 - } - ], - "pluginName": "TooB Phaser", - "midiBindings": [], - "midiChannelBinding": null, - "stateUpdateCount": 0, - "lv2State": [ - false, - {} - ], - "lilvPresetUri": "", - "pathProperties": {}, - "title": "", - "useModUi": false - }, - { - "instanceId": 41, - "uri": "http://two-play.com/plugins/toob-delay", - "isEnabled": false, - "controlValues": [ - { - "key": "delay", - "value": 340 - }, - { - "key": "level", - "value": 36.6666679 - }, - { - "key": "feedback", - "value": 33.1666679 - } - ], - "pluginName": "TooB Delay", - "midiBindings": [], - "midiChannelBinding": null, - "stateUpdateCount": 0, - "lv2State": [ - false, - {} - ], - "lilvPresetUri": "", - "pathProperties": {}, - "title": "", - "useModUi": false - }, - { - "instanceId": 39, - "uri": "http://two-play.com/plugins/toob-ml", - "isEnabled": true, - "controlValues": [ - { - "key": "trim", - "value": -2.5 - }, - { - "key": "bass", - "value": 0.5 - }, - { - "key": "mid", - "value": 0.625 - }, - { - "key": "treble", - "value": 0.5 - }, - { - "key": "model", - "value": 19 - }, - { - "key": "gain", - "value": 0 - }, - { - "key": "master", - "value": 0.5 - }, - { - "key": "sag", - "value": 0 - }, - { - "key": "sagd", - "value": 0 - }, - { - "key": "sagf", - "value": 13 - } - ], - "pluginName": "TooB ML Amplifier", - "midiBindings": [], - "midiChannelBinding": null, - "stateUpdateCount": 0, - "lv2State": [ - true, - { - "http://two-play.com/plugins/toob-ml#modelFile": { - "flags": 3, - "atomType": "http://lv2plug.in/ns/ext/atom#Path", - "value": "ToobMlModels/MesaBoogie_Studio22_Clean.json" - }, - "http://two-play.com/plugins/toob-ml#version": { - "flags": 3, - "atomType": "http://lv2plug.in/ns/ext/atom#Float", - "value": 1 - } - } - ], - "lilvPresetUri": "", - "pathProperties": { - "http://two-play.com/plugins/toob-ml#modelFile": "{\"otype_\": \"Path\",\"value\": \"/var/pipedal/audio_uploads/ToobMlModels/MesaBoogie_Studio22_Clean.json\"}" - }, - "title": "", - "useModUi": false - }, - { - "instanceId": 51, - "uri": "http://two-play.com/plugins/toob-convolution-reverb-stereo", - "isEnabled": true, - "controlValues": [ - { - "key": "bypass", - "value": 1 - }, - { - "key": "time", - "value": 30 - }, - { - "key": "direct_mix", - "value": -6 - }, - { - "key": "reverb_mix", - "value": -4 - }, - { - "key": "width", - "value": 1 - }, - { - "key": "pan", - "value": 0 - }, - { - "key": "predelay", - "value": -1 - }, - { - "key": "predelay_new", - "value": 1 - }, - { - "key": "stretch", - "value": 1 - }, - { - "key": "decay", - "value": 0 - }, - { - "key": "tails", - "value": 1 - }, - { - "key": "loading_state", - "value": 0 - } - ], - "pluginName": "TooB Convolution Reverb (Stereo)", - "midiBindings": [], - "midiChannelBinding": null, - "stateUpdateCount": 0, - "lv2State": [ - true, - { - "http://two-play.com/plugins/toob-impulse#impulseFile": { - "flags": 3, - "atomType": "http://lv2plug.in/ns/ext/atom#Path", - "value": "ReverbImpulseFiles/Genesis 6 Studio Live Room.wav" - } - } - ], - "lilvPresetUri": "", - "pathProperties": { - "http://two-play.com/plugins/toob-impulse#impulseFile": "{\"otype_\": \"Path\",\"value\": \"/var/pipedal/audio_uploads/ReverbImpulseFiles/Genesis 6 Studio Live Room.wav\"}" - }, - "title": "", - "useModUi": false - } - ], - "nextInstanceId": 55, - "snapshots": [], - "selectedSnapshot": -1, - "selectedPlugin": 16 - } - }, - { - "instanceId": 20, - "preset": { - "name": "Screaming Lead", - "input_volume_db": 0, - "output_volume_db": 0, - "items": [ - { - "instanceId": 16, - "uri": "http://two-play.com/plugins/toob-tuner", - "isEnabled": true, - "controlValues": [ - { - "key": "REFFREQ", - "value": 440 - }, - { - "key": "THRESHOLD", - "value": -54 - }, - { - "key": "MUTE", - "value": 0 - } - ], - "pluginName": "TooB Tuner", - "midiBindings": [], - "midiChannelBinding": null, - "stateUpdateCount": 0, - "lv2State": [ - false, - {} - ], - "lilvPresetUri": "", - "pathProperties": {}, - "title": "", - "useModUi": false - }, - { - "instanceId": 17, - "uri": "http://two-play.com/plugins/toob-input_stage", - "isEnabled": true, - "controlValues": [ - { - "key": "trim", - "value": -15.75 - }, - { - "key": "locut", - "value": 196.5 - }, - { - "key": "bright", - "value": 0 - }, - { - "key": "brightf", - "value": 1300 - }, - { - "key": "hicut", - "value": 13000 - }, - { - "key": "gate_t", - "value": -66.5 - }, - { - "key": "boost", - "value": 0 - } - ], - "pluginName": "TooB Input Stage", - "midiBindings": [ - { - "channel": -1, - "symbol": "__bypass", - "bindingType": 2, - "note": 72, - "control": 4, - "minControlValue": 0, - "maxControlValue": 127, - "minValue": 0, - "maxValue": 1, - "rotaryScale": 1, - "linearControlType": 0, - "switchControlType": 0 - } - ], - "midiChannelBinding": null, - "stateUpdateCount": 0, - "lv2State": [ - false, - {} - ], - "lilvPresetUri": "", - "pathProperties": {}, - "title": "", - "useModUi": false - }, - { - "instanceId": 41, - "uri": "http://two-play.com/plugins/toob-delay", - "isEnabled": true, - "controlValues": [ - { - "key": "delay", - "value": 340 - }, - { - "key": "level", - "value": 18.666666 - }, - { - "key": "feedback", - "value": 13.3333349 - } - ], - "pluginName": "TooB Delay", - "midiBindings": [], - "midiChannelBinding": null, - "stateUpdateCount": 0, - "lv2State": [ - false, - {} - ], - "lilvPresetUri": "", - "pathProperties": {}, - "title": "", - "useModUi": false - }, - { - "instanceId": 42, - "uri": "http://two-play.com/plugins/toob-chorus", - "isEnabled": false, - "controlValues": [ - { - "key": "rate", - "value": 0.291666657 - }, - { - "key": "depth", - "value": 0.375 - }, - { - "key": "dryWet", - "value": 1 - } - ], - "pluginName": "TooB CE-2 Chorus", - "midiBindings": [], - "midiChannelBinding": null, - "stateUpdateCount": 0, - "lv2State": [ - false, - {} - ], - "lilvPresetUri": "", - "pathProperties": {}, - "title": "", - "useModUi": false - }, - { - "instanceId": 47, - "uri": "http://two-play.com/plugins/toob-ml", - "isEnabled": true, - "controlValues": [ - { - "key": "trim", - "value": 12.5 - }, - { - "key": "model", - "value": 29 - }, - { - "key": "gain", - "value": 3 - }, - { - "key": "master", - "value": -0.5 - }, - { - "key": "bass", - "value": 0.5 - }, - { - "key": "mid", - "value": 0.5 - }, - { - "key": "treble", - "value": 0.5 - }, - { - "key": "sag", - "value": 0 - }, - { - "key": "sagd", - "value": 0 - }, - { - "key": "sagf", - "value": 13 - } - ], - "pluginName": "TooB ML Amplifier", - "midiBindings": [], - "midiChannelBinding": null, - "stateUpdateCount": 0, - "lv2State": [ - true, - { - "http://two-play.com/plugins/toob-ml#modelFile": { - "flags": 3, - "atomType": "http://lv2plug.in/ns/ext/atom#Path", - "value": "ToobMlModels/TS9_FullD.json" - }, - "http://two-play.com/plugins/toob-ml#version": { - "flags": 3, - "atomType": "http://lv2plug.in/ns/ext/atom#Float", - "value": 1 - } - } - ], - "lilvPresetUri": "", - "pathProperties": { - "http://two-play.com/plugins/toob-ml#modelFile": "{\"otype_\": \"Path\",\"value\": \"/var/pipedal/audio_uploads/ToobMlModels/TS9_FullD.json\"}" - }, - "title": "", - "useModUi": false - }, - { - "instanceId": 39, - "uri": "http://two-play.com/plugins/toob-ml", - "isEnabled": true, - "controlValues": [ - { - "key": "trim", - "value": -4 - }, - { - "key": "bass", - "value": 0.5 - }, - { - "key": "mid", - "value": 0.5 - }, - { - "key": "treble", - "value": 0.5 - }, - { - "key": "model", - "value": 12 - }, - { - "key": "gain", - "value": 10 - }, - { - "key": "master", - "value": -5 - }, - { - "key": "sag", - "value": 0 - }, - { - "key": "sagd", - "value": 0 - }, - { - "key": "sagf", - "value": 13 - } - ], - "pluginName": "TooB ML Amplifier", - "midiBindings": [], - "midiChannelBinding": null, - "stateUpdateCount": 0, - "lv2State": [ - true, - { - "http://two-play.com/plugins/toob-ml#modelFile": { - "flags": 3, - "atomType": "http://lv2plug.in/ns/ext/atom#Path", - "value": "ToobMlModels/HT40_Overdrive.json" - }, - "http://two-play.com/plugins/toob-ml#version": { - "flags": 3, - "atomType": "http://lv2plug.in/ns/ext/atom#Float", - "value": 1 - } - } - ], - "lilvPresetUri": "", - "pathProperties": { - "http://two-play.com/plugins/toob-ml#modelFile": "{\"otype_\": \"Path\",\"value\": \"/var/pipedal/audio_uploads/ToobMlModels/HT40_Overdrive.json\"}" - }, - "title": "", - "useModUi": false - }, - { - "instanceId": 51, - "uri": "http://two-play.com/plugins/toob-flanger-stereo", - "isEnabled": true, - "controlValues": [ - { - "key": "manual", - "value": 0.5 - }, - { - "key": "depth", - "value": 0.5 - }, - { - "key": "rate", - "value": 0.5 - }, - { - "key": "lfo", - "value": 0 - }, - { - "key": "res", - "value": 0.5 - }, - { - "key": "dryWet", - "value": 0.508333325 - } - ], - "pluginName": "TooB BF-2 Stereo Flanger", - "midiBindings": [], - "midiChannelBinding": null, - "stateUpdateCount": 0, - "lv2State": [ - false, - {} - ], - "lilvPresetUri": "", - "pathProperties": {}, - "title": "", - "useModUi": false - }, - { - "instanceId": 53, - "uri": "http://two-play.com/plugins/toob-freeverb", - "isEnabled": true, - "controlValues": [ - { - "key": "bypass", - "value": 1 - }, - { - "key": "dryWet", - "value": 0.150000006 - }, - { - "key": "roomSize", - "value": 0.649999976 - }, - { - "key": "damping", - "value": 0 - }, - { - "key": "tails", - "value": 1 - } - ], - "pluginName": "TooB Freeverb", - "midiBindings": [], - "midiChannelBinding": null, - "stateUpdateCount": 0, - "lv2State": [ - false, - {} - ], - "lilvPresetUri": "", - "pathProperties": {}, - "title": "", - "useModUi": false - } - ], - "nextInstanceId": 53, - "snapshots": [], - "selectedSnapshot": -1, - "selectedPlugin": 16 - } - }, - { - "instanceId": 18, - "preset": { - "name": "Light Crunch", - "input_volume_db": 0, - "output_volume_db": 0, - "items": [ - { - "instanceId": 16, - "uri": "http://two-play.com/plugins/toob-tuner", - "isEnabled": true, - "controlValues": [ - { - "key": "REFFREQ", - "value": 440 - }, - { - "key": "THRESHOLD", - "value": -23.1093884 - }, - { - "key": "MUTE", - "value": 0 - } - ], - "pluginName": "TooB Tuner", - "midiBindings": [], - "midiChannelBinding": null, - "stateUpdateCount": 0, - "lv2State": [ - false, - {} - ], - "lilvPresetUri": "", - "pathProperties": {}, - "title": "", - "useModUi": false - }, - { - "instanceId": 49, - "uri": "http://two-play.com/plugins/toob-noise-gate", - "isEnabled": true, - "controlValues": [ - { - "key": "threshold", - "value": -102 - }, - { - "key": "hysteresis", - "value": -6 - }, - { - "key": "trigger_led", - "value": 0 - }, - { - "key": "reduction", - "value": -60 - }, - { - "key": "attack", - "value": 1 - }, - { - "key": "hold", - "value": 100 - }, - { - "key": "release", - "value": 330 - }, - { - "key": "gate_level", - "value": -60 - } - ], - "pluginName": "TooB Noise Gate", - "midiBindings": [], - "midiChannelBinding": null, - "stateUpdateCount": 0, - "lv2State": [ - false, - {} - ], - "lilvPresetUri": "", - "pathProperties": {}, - "title": "", - "useModUi": false - }, - { - "instanceId": 42, - "uri": "http://two-play.com/plugins/toob-chorus", - "isEnabled": false, - "controlValues": [ - { - "key": "rate", - "value": 0.291666657 - }, - { - "key": "depth", - "value": 0.375 - }, - { - "key": "dryWet", - "value": 1 - } - ], - "pluginName": "TooB CE-2 Chorus", - "midiBindings": [], - "midiChannelBinding": null, - "stateUpdateCount": 0, - "lv2State": [ - false, - {} - ], - "lilvPresetUri": "", - "pathProperties": {}, - "title": "", - "useModUi": false - }, - { - "instanceId": 41, - "uri": "http://two-play.com/plugins/toob-delay", - "isEnabled": true, - "controlValues": [ - { - "key": "delay", - "value": 340 - }, - { - "key": "level", - "value": 27 - }, - { - "key": "feedback", - "value": 39 - } - ], - "pluginName": "TooB Delay", - "midiBindings": [], - "midiChannelBinding": null, - "stateUpdateCount": 0, - "lv2State": [ - false, - {} - ], - "lilvPresetUri": "", - "pathProperties": {}, - "title": "", - "useModUi": false - }, - { - "instanceId": 39, - "uri": "http://two-play.com/plugins/toob-ml", - "isEnabled": true, - "controlValues": [ - { - "key": "trim", - "value": -0.5 - }, - { - "key": "bass", - "value": 0.650000036 - }, - { - "key": "mid", - "value": 0.5 - }, - { - "key": "treble", - "value": 0.266666681 - }, - { - "key": "model", - "value": 17 - }, - { - "key": "gain", - "value": 7.75 - }, - { - "key": "master", - "value": -4 - }, - { - "key": "sag", - "value": 0 - }, - { - "key": "sagd", - "value": 0 - }, - { - "key": "sagf", - "value": 13 - } - ], - "pluginName": "TooB ML Amplifier", - "midiBindings": [], - "midiChannelBinding": null, - "stateUpdateCount": 1, - "lv2State": [ - true, - { - "http://two-play.com/plugins/toob-ml#modelFile": { - "flags": 3, - "atomType": "http://lv2plug.in/ns/ext/atom#Path", - "value": "ToobMlModels/ProCoRat.json" - }, - "http://two-play.com/plugins/toob-ml#version": { - "flags": 3, - "atomType": "http://lv2plug.in/ns/ext/atom#Float", - "value": 1 - } - } - ], - "lilvPresetUri": "", - "pathProperties": { - "http://two-play.com/plugins/toob-ml#modelFile": "{\"otype_\": \"Path\",\"value\": \"/var/pipedal/audio_uploads/ToobMlModels/ProCoRat.json\"}" - }, - "title": "", - "useModUi": false - }, - { - "instanceId": 51, - "uri": "http://two-play.com/plugins/toob-ml", - "isEnabled": true, - "controlValues": [ - { - "key": "trim", - "value": -3 - }, - { - "key": "trimOut", - "value": -35 - }, - { - "key": "model", - "value": -1 - }, - { - "key": "gain", - "value": 3 - }, - { - "key": "master", - "value": -6 - }, - { - "key": "bass", - "value": 0.5 - }, - { - "key": "mid", - "value": 0.5 - }, - { - "key": "treble", - "value": 0.5 - }, - { - "key": "sag", - "value": 0 - }, - { - "key": "sagd", - "value": 0 - }, - { - "key": "sagf", - "value": 13 - }, - { - "key": "gainEnable", - "value": 0 - } - ], - "pluginName": "TooB ML Amplifier", - "midiBindings": [], - "midiChannelBinding": null, - "stateUpdateCount": 0, - "lv2State": [ - true, - { - "http://two-play.com/plugins/toob-ml#modelFile": { - "flags": 3, - "atomType": "http://lv2plug.in/ns/ext/atom#Path", - "value": "ToobMlModels/FenderPrinceton_highLevel.json" - }, - "http://two-play.com/plugins/toob-ml#version": { - "flags": 3, - "atomType": "http://lv2plug.in/ns/ext/atom#Float", - "value": 1 - } - } - ], - "lilvPresetUri": "", - "pathProperties": { - "http://two-play.com/plugins/toob-ml#modelFile": "{\"otype_\": \"Path\",\"value\": \"/var/pipedal/audio_uploads/ToobMlModels/FenderPrinceton_highLevel.json\"}" - }, - "title": "", - "useModUi": false - }, - { - "instanceId": 58, - "uri": "http://two-play.com/plugins/toob-nam", - "isEnabled": true, - "controlValues": [ - { - "key": "inputGain", - "value": -12.666667 - }, - { - "key": "inputGainOut", - "value": -35 - }, - { - "key": "outputGain", - "value": 0 - }, - { - "key": "gate", - "value": -120 - }, - { - "key": "gateOut", - "value": 0 - }, - { - "key": "buffer", - "value": 0 - }, - { - "key": "toneStack", - "value": 3 - }, - { - "key": "bass", - "value": 5 - }, - { - "key": "mid", - "value": 5 - }, - { - "key": "treble", - "value": 5 - }, - { - "key": "inputCalibrationMode", - "value": 1 - }, - { - "key": "calibration", - "value": -6 - }, - { - "key": "outputCalibration", - "value": 0 - }, - { - "key": "version", - "value": 1 - } - ], - "pluginName": "TooB Neural Amp Modeler", - "midiBindings": [], - "midiChannelBinding": null, - "stateUpdateCount": 1, - "lv2State": [ - true, - {} - ], - "lilvPresetUri": "", - "pathProperties": { - "http://two-play.com/plugins/toob-nam#modelFile": "{\"otype_\": \"Path\",\"value\": \"\"}" - }, - "title": "", - "useModUi": false - }, - { - "instanceId": 53, - "uri": "http://two-play.com/plugins/toob-parametric-eq", - "isEnabled": true, - "controlValues": [ - { - "key": "loCut", - "value": 27.2704601 - }, - { - "key": "hiCut", - "value": 21 - }, - { - "key": "lfLevel", - "value": 0 - }, - { - "key": "lfC", - "value": 120 - }, - { - "key": "lmfLevel", - "value": -6.5 - }, - { - "key": "lmfC", - "value": 551.471313 - }, - { - "key": "lmfQ", - "value": 0.850504637 - }, - { - "key": "hmfLevel", - "value": -7.5 - }, - { - "key": "hmfC", - "value": 2.5746665 - }, - { - "key": "hmfQ", - "value": 1.5 - }, - { - "key": "hfLevel", - "value": -6.5 - }, - { - "key": "hfC", - "value": 2.25 - }, - { - "key": "gain", - "value": 0 - } - ], - "pluginName": "TooB Parametric EQ", - "midiBindings": [], - "midiChannelBinding": null, - "stateUpdateCount": 0, - "lv2State": [ - false, - {} - ], - "lilvPresetUri": "", - "pathProperties": {}, - "title": "", - "useModUi": false - }, - { - "instanceId": 31, - "uri": "http://two-play.com/plugins/toob-freeverb", - "isEnabled": true, - "controlValues": [ - { - "key": "dryWet", - "value": 0.416666687 - }, - { - "key": "roomSize", - "value": 0.625 - }, - { - "key": "damping", - "value": 0.574999988 - }, - { - "key": "bypass", - "value": 1 - }, - { - "key": "tails", - "value": 1 - } - ], - "pluginName": "TooB Freeverb", - "midiBindings": [], - "midiChannelBinding": null, - "stateUpdateCount": 0, - "lv2State": [ - false, - {} - ], - "lilvPresetUri": "", - "pathProperties": {}, - "title": "", - "useModUi": false - } - ], - "nextInstanceId": 58, - "snapshots": [], - "selectedSnapshot": -1, - "selectedPlugin": 16 - } - }, - { - "instanceId": 15, - "preset": { - "name": "Jazz ML", - "input_volume_db": 0, - "output_volume_db": 0, - "items": [ - { - "instanceId": 16, - "uri": "http://two-play.com/plugins/toob-tuner", - "isEnabled": true, - "controlValues": [ - { - "key": "REFFREQ", - "value": 440 - }, - { - "key": "THRESHOLD", - "value": -23.1093884 - }, - { - "key": "MUTE", - "value": 0 - } - ], - "pluginName": "TooB Tuner", - "midiBindings": [], - "midiChannelBinding": null, - "stateUpdateCount": 0, - "lv2State": [ - false, - {} - ], - "lilvPresetUri": "", - "pathProperties": {}, - "title": "", - "useModUi": false - }, - { - "instanceId": 45, - "uri": "http://two-play.com/plugins/toob-noise-gate", - "isEnabled": true, - "controlValues": [ - { - "key": "threshold", - "value": -53 - }, - { - "key": "hysteresis", - "value": -6 - }, - { - "key": "trigger_led", - "value": 0 - }, - { - "key": "reduction", - "value": -60 - }, - { - "key": "attack", - "value": 1 - }, - { - "key": "hold", - "value": 100 - }, - { - "key": "release", - "value": 330 - }, - { - "key": "gate_level", - "value": -60 - } - ], - "pluginName": "TooB Noise Gate", - "midiBindings": [], - "midiChannelBinding": null, - "stateUpdateCount": 0, - "lv2State": [ - false, - {} - ], - "lilvPresetUri": "", - "pathProperties": {}, - "title": "", - "useModUi": false - }, - { - "instanceId": 41, - "uri": "http://two-play.com/plugins/toob-chorus", - "isEnabled": false, - "controlValues": [ - { - "key": "rate", - "value": 0.208333328 - }, - { - "key": "depth", - "value": 0.5 - }, - { - "key": "dryWet", - "value": 1 - } - ], - "pluginName": "TooB CE-2 Chorus", - "midiBindings": [], - "midiChannelBinding": null, - "stateUpdateCount": 0, - "lv2State": [ - false, - {} - ], - "lilvPresetUri": "", - "pathProperties": {}, - "title": "", - "useModUi": false - }, - { - "instanceId": 47, - "uri": "http://two-play.com/plugins/toob-parametric-eq", - "isEnabled": true, - "controlValues": [ - { - "key": "loCut", - "value": 41.8936462 - }, - { - "key": "hiCut", - "value": 21 - }, - { - "key": "lfLevel", - "value": 0 - }, - { - "key": "lfC", - "value": 120 - }, - { - "key": "lmfLevel", - "value": -5.5 - }, - { - "key": "lmfC", - "value": 238.107834 - }, - { - "key": "lmfQ", - "value": 0.837899804 - }, - { - "key": "hmfLevel", - "value": 0 - }, - { - "key": "hmfC", - "value": 2 - }, - { - "key": "hmfQ", - "value": 1.5 - }, - { - "key": "hfLevel", - "value": -12 - }, - { - "key": "hfC", - "value": 1 - }, - { - "key": "gain", - "value": 5.83333349 - } - ], - "pluginName": "TooB Parametric EQ", - "midiBindings": [], - "midiChannelBinding": null, - "stateUpdateCount": 0, - "lv2State": [ - false, - {} - ], - "lilvPresetUri": "", - "pathProperties": {}, - "title": "", - "useModUi": false - }, - { - "instanceId": 39, - "uri": "http://two-play.com/plugins/toob-ml", - "isEnabled": true, - "controlValues": [ - { - "key": "trim", - "value": -13 - }, - { - "key": "bass", - "value": 0.5 - }, - { - "key": "mid", - "value": 0.5 - }, - { - "key": "treble", - "value": 0.5 - }, - { - "key": "model", - "value": 0 - }, - { - "key": "gain", - "value": 4.66666651 - }, - { - "key": "master", - "value": 5 - }, - { - "key": "sag", - "value": 0.550000012 - }, - { - "key": "sagd", - "value": 0.900000036 - }, - { - "key": "sagf", - "value": 13 - } - ], - "pluginName": "TooB ML Amplifier", - "midiBindings": [], - "midiChannelBinding": null, - "stateUpdateCount": 2, - "lv2State": [ - true, - { - "http://two-play.com/plugins/toob-ml#modelFile": { - "flags": 3, - "atomType": "http://lv2plug.in/ns/ext/atom#Path", - "value": "ToobMlModels/FenderPrinceton_clean.json" - }, - "http://two-play.com/plugins/toob-ml#version": { - "flags": 3, - "atomType": "http://lv2plug.in/ns/ext/atom#Float", - "value": 1 - } - } - ], - "lilvPresetUri": "", - "pathProperties": { - "http://two-play.com/plugins/toob-ml#modelFile": "{\"otype_\": \"Path\",\"value\": \"/var/pipedal/audio_uploads/ToobMlModels/FenderPrinceton_clean.json\"}" - }, - "title": "", - "useModUi": false - }, - { - "instanceId": 31, - "uri": "http://two-play.com/plugins/toob-freeverb", - "isEnabled": true, - "controlValues": [ - { - "key": "dryWet", - "value": 0.416666657 - }, - { - "key": "roomSize", - "value": 0.625 - }, - { - "key": "damping", - "value": 0.574999988 - }, - { - "key": "bypass", - "value": 1 - }, - { - "key": "tails", - "value": 1 - } - ], - "pluginName": "TooB Freeverb", - "midiBindings": [], - "midiChannelBinding": null, - "stateUpdateCount": 0, - "lv2State": [ - false, - {} - ], - "lilvPresetUri": "", - "pathProperties": {}, - "title": "", - "useModUi": false - } - ], - "nextInstanceId": 48, - "snapshots": [], - "selectedSnapshot": -1, - "selectedPlugin": 16 - } - } - ] -} \ No newline at end of file +{"name": "","nextInstanceId": 12,"selectedPreset": 7,"presets": [{"instanceId": 7,"preset": {"name": "Heavy","input_volume_db": 0,"output_volume_db": 0,"items": [{"instanceId": 16,"uri": "http://two-play.com/plugins/toob-tuner","isEnabled": true,"controlValues": [{"key": "REFFREQ","value": 440},{"key": "THRESHOLD","value": -23.1093884},{"key": "MUTE","value": 0}],"pluginName": "TooB Tuner","midiBindings": [ ],"midiChannelBinding": null,"stateUpdateCount": 0,"lv2State": [false,{}],"lilvPresetUri": "","pathProperties": {},"title": "","useModUi": false},{"instanceId": 49,"uri": "http://two-play.com/plugins/toob-noise-gate","isEnabled": true,"controlValues": [{"key": "threshold","value": -60},{"key": "hysteresis","value": -6},{"key": "trigger_led","value": 0},{"key": "reduction","value": -60},{"key": "attack","value": 1},{"key": "hold","value": 100},{"key": "release","value": 330},{"key": "gate_level","value": -60}],"pluginName": "TooB Noise Gate","midiBindings": [ ],"midiChannelBinding": null,"stateUpdateCount": 0,"lv2State": [false,{}],"lilvPresetUri": "","pathProperties": {},"title": "","useModUi": false},{"instanceId": 41,"uri": "http://two-play.com/plugins/toob-delay","isEnabled": false,"controlValues": [{"key": "delay","value": 340},{"key": "level","value": 27},{"key": "feedback","value": 39.1666679}],"pluginName": "TooB Delay","midiBindings": [ ],"midiChannelBinding": null,"stateUpdateCount": 0,"lv2State": [false,{}],"lilvPresetUri": "","pathProperties": {},"title": "","useModUi": false},{"instanceId": 39,"uri": "http://two-play.com/plugins/toob-ml","isEnabled": true,"controlValues": [{"key": "trim","value": 1},{"key": "bass","value": 0.5},{"key": "mid","value": 0.5},{"key": "treble","value": 0.5},{"key": "model","value": 18},{"key": "gain","value": 2.41666675},{"key": "master","value": -4.5},{"key": "sag","value": 0},{"key": "sagd","value": 0},{"key": "sagf","value": 13}],"pluginName": "TooB ML Amplifier","midiBindings": [ ],"midiChannelBinding": null,"stateUpdateCount": 2,"lv2State": [true,{"http://two-play.com/plugins/toob-ml#modelFile": {"flags": 3,"atomType": "http://lv2plug.in/ns/ext/atom#Path","value": "ToobMlModels/HT40_Overdrive.json"},"http://two-play.com/plugins/toob-ml#version": {"flags": 3,"atomType": "http://lv2plug.in/ns/ext/atom#Float","value": 1}}],"lilvPresetUri": "","pathProperties": {"http://two-play.com/plugins/toob-ml#modelFile": "{\"otype_\": \"Path\",\"value\": \"ToobMlModels/HT40_Overdrive.json\"}"},"title": "","useModUi": false},{"instanceId": 31,"uri": "http://two-play.com/plugins/toob-freeverb","isEnabled": true,"controlValues": [{"key": "dryWet","value": 0.25},{"key": "roomSize","value": 0.733333349},{"key": "damping","value": 0.574999988},{"key": "bypass","value": 1},{"key": "tails","value": 1}],"pluginName": "TooB Freeverb","midiBindings": [ ],"midiChannelBinding": null,"stateUpdateCount": 0,"lv2State": [false,{}],"lilvPresetUri": "","pathProperties": {},"title": "","useModUi": false},{"instanceId": 51,"uri": "http://two-play.com/plugins/toob-parametric-eq","isEnabled": true,"controlValues": [{"key": "loCut","value": 50.7010994},{"key": "hiCut","value": 21},{"key": "lfLevel","value": 0},{"key": "lfC","value": 120},{"key": "lmfLevel","value": 0},{"key": "lmfC","value": 400},{"key": "lmfQ","value": 1.5},{"key": "hmfLevel","value": 0},{"key": "hmfC","value": 2},{"key": "hmfQ","value": 1.5},{"key": "hfLevel","value": -3.25},{"key": "hfC","value": 3.875},{"key": "gain","value": 0}],"pluginName": "TooB Parametric EQ","midiBindings": [ ],"midiChannelBinding": null,"stateUpdateCount": 0,"lv2State": [false,{}],"lilvPresetUri": "","pathProperties": {},"title": "","useModUi": false}],"nextInstanceId": 52,"snapshots": [ ],"selectedSnapshot": -1,"selectedPlugin": 41}},{"instanceId": 8,"preset": {"name": "Sparkling Clean","input_volume_db": 0,"output_volume_db": 0,"items": [{"instanceId": 16,"uri": "http://two-play.com/plugins/toob-tuner","isEnabled": true,"controlValues": [{"key": "REFFREQ","value": 440},{"key": "THRESHOLD","value": -23.1093884},{"key": "MUTE","value": 0}],"pluginName": "TooB Tuner","midiBindings": [ ],"midiChannelBinding": null,"stateUpdateCount": 0,"lv2State": [false,{}],"lilvPresetUri": "","pathProperties": {},"title": "","useModUi": false},{"instanceId": 55,"uri": "http://two-play.com/plugins/toob-noise-gate","isEnabled": true,"controlValues": [{"key": "threshold","value": -65},{"key": "hysteresis","value": -6},{"key": "trigger_led","value": 0},{"key": "reduction","value": -60},{"key": "attack","value": 1},{"key": "hold","value": 100},{"key": "release","value": 330},{"key": "gate_level","value": -60}],"pluginName": "TooB Noise Gate","midiBindings": [ ],"midiChannelBinding": null,"stateUpdateCount": 0,"lv2State": [false,{}],"lilvPresetUri": "","pathProperties": {},"title": "","useModUi": false},{"instanceId": 53,"uri": "http://two-play.com/plugins/toob-phaser","isEnabled": true,"controlValues": [{"key": "rate","value": 0.424131542},{"key": "dryWet","value": 0.400000006}],"pluginName": "TooB Phaser","midiBindings": [ ],"midiChannelBinding": null,"stateUpdateCount": 0,"lv2State": [false,{}],"lilvPresetUri": "","pathProperties": {},"title": "","useModUi": false},{"instanceId": 41,"uri": "http://two-play.com/plugins/toob-delay","isEnabled": false,"controlValues": [{"key": "delay","value": 340},{"key": "level","value": 36.6666679},{"key": "feedback","value": 33.1666679}],"pluginName": "TooB Delay","midiBindings": [ ],"midiChannelBinding": null,"stateUpdateCount": 0,"lv2State": [false,{}],"lilvPresetUri": "","pathProperties": {},"title": "","useModUi": false},{"instanceId": 39,"uri": "http://two-play.com/plugins/toob-ml","isEnabled": true,"controlValues": [{"key": "trim","value": -2.5},{"key": "bass","value": 0.5},{"key": "mid","value": 0.625},{"key": "treble","value": 0.5},{"key": "model","value": 19},{"key": "gain","value": 0},{"key": "master","value": 0.5},{"key": "sag","value": 0},{"key": "sagd","value": 0},{"key": "sagf","value": 13}],"pluginName": "TooB ML Amplifier","midiBindings": [ ],"midiChannelBinding": null,"stateUpdateCount": 0,"lv2State": [true,{"http://two-play.com/plugins/toob-ml#modelFile": {"flags": 3,"atomType": "http://lv2plug.in/ns/ext/atom#Path","value": "ToobMlModels/MesaBoogie_Studio22_Clean.json"},"http://two-play.com/plugins/toob-ml#version": {"flags": 3,"atomType": "http://lv2plug.in/ns/ext/atom#Float","value": 1}}],"lilvPresetUri": "","pathProperties": {"http://two-play.com/plugins/toob-ml#modelFile": "{\"otype_\": \"Path\",\"value\": \"/var/pipedal/audio_uploads/ToobMlModels/MesaBoogie_Studio22_Clean.json\"}"},"title": "","useModUi": false},{"instanceId": 51,"uri": "http://two-play.com/plugins/toob-convolution-reverb-stereo","isEnabled": true,"controlValues": [{"key": "bypass","value": 1},{"key": "time","value": 30},{"key": "direct_mix","value": -6},{"key": "reverb_mix","value": -4},{"key": "width","value": 1},{"key": "pan","value": 0},{"key": "predelay","value": -1},{"key": "predelay_new","value": 1},{"key": "stretch","value": 1},{"key": "decay","value": 0},{"key": "tails","value": 1},{"key": "loading_state","value": 0}],"pluginName": "TooB Convolution Reverb (Stereo)","midiBindings": [ ],"midiChannelBinding": null,"stateUpdateCount": 0,"lv2State": [true,{"http://two-play.com/plugins/toob-impulse#impulseFile": {"flags": 3,"atomType": "http://lv2plug.in/ns/ext/atom#Path","value": "ReverbImpulseFiles/Genesis 6 Studio Live Room.wav"}}],"lilvPresetUri": "","pathProperties": {"http://two-play.com/plugins/toob-impulse#impulseFile": "{\"otype_\": \"Path\",\"value\": \"/var/pipedal/audio_uploads/ReverbImpulseFiles/Genesis 6 Studio Live Room.wav\"}"},"title": "","useModUi": false}],"nextInstanceId": 55,"snapshots": [ ],"selectedSnapshot": -1,"selectedPlugin": 16}},{"instanceId": 12,"preset": {"name": "Clean-Lead Split","input_volume_db": 0,"output_volume_db": 0,"items": [{"instanceId": 16,"uri": "http://two-play.com/plugins/toob-tuner","isEnabled": true,"controlValues": [{"key": "REFFREQ","value": 440},{"key": "THRESHOLD","value": -54},{"key": "MUTE","value": 0}],"pluginName": "TooB Tuner","midiBindings": [ ],"midiChannelBinding": null,"stateUpdateCount": 0,"lv2State": [false,{}],"lilvPresetUri": "","pathProperties": {},"title": "","useModUi": false},{"instanceId": 66,"uri": "http://two-play.com/plugins/toob-noise-gate","isEnabled": true,"controlValues": [{"key": "threshold","value": -96},{"key": "hysteresis","value": -6},{"key": "trigger_led","value": 0},{"key": "reduction","value": -60},{"key": "attack","value": 1},{"key": "hold","value": 31.622776},{"key": "release","value": 330},{"key": "gate_level","value": -60}],"pluginName": "TooB Noise Gate","midiBindings": [ ],"midiChannelBinding": null,"stateUpdateCount": 0,"lv2State": [false,{}],"lilvPresetUri": "","pathProperties": {},"title": "","useModUi": false},{"instanceId": 54,"uri": "uri://two-play/pipedal/pedalboard#Split","isEnabled": true,"controlValues": [{"key": "splitType","value": 0},{"key": "select","value": 1},{"key": "mix","value": 0},{"key": "panL","value": 0},{"key": "volL","value": -3},{"key": "panR","value": 0},{"key": "volR","value": -3}],"pluginName": "","topChain": [{"instanceId": 72,"uri": "http://two-play.com/plugins/toob-chorus","isEnabled": true,"controlValues": [{"key": "rate","value": 0.5},{"key": "depth","value": 0.5},{"key": "dryWet","value": 0.575000048}],"pluginName": "TooB CE-2 Chorus","midiBindings": [ ],"midiChannelBinding": null,"stateUpdateCount": 0,"lv2State": [false,{}],"lilvPresetUri": "","pathProperties": {},"title": "","useModUi": false},{"instanceId": 58,"uri": "http://two-play.com/plugins/toob-ml","isEnabled": true,"controlValues": [{"key": "trim","value": 0},{"key": "trimOut","value": -35},{"key": "model","value": -1},{"key": "gain","value": 2.25},{"key": "master","value": 0},{"key": "bass","value": 0.5},{"key": "mid","value": 0.5},{"key": "treble","value": 0.5},{"key": "sag","value": 0},{"key": "sagd","value": 0},{"key": "sagf","value": 13},{"key": "gainEnable","value": 0}],"pluginName": "TooB ML Amplifier","midiBindings": [ ],"midiChannelBinding": null,"stateUpdateCount": 1,"lv2State": [true,{"http://two-play.com/plugins/toob-ml#modelFile": {"flags": 3,"atomType": "http://lv2plug.in/ns/ext/atom#Path","value": "ToobMlModels/BluesJR.json"},"http://two-play.com/plugins/toob-ml#version": {"flags": 3,"atomType": "http://lv2plug.in/ns/ext/atom#Float","value": 1}}],"lilvPresetUri": "","pathProperties": {"http://two-play.com/plugins/toob-ml#modelFile": "{\"otype_\": \"Path\",\"value\": \"ToobMlModels/BluesJR.json\"}"},"title": "Blues Jr","useModUi": false},{"instanceId": 64,"uri": "http://two-play.com/plugins/toob-cab-ir","isEnabled": true,"controlValues": [{"key": "reverb_mix","value": -6},{"key": "reverb_mix2","value": 0},{"key": "reverb_mix3","value": 0},{"key": "time","value": 1.5},{"key": "direct_mix","value": -40},{"key": "predelay","value": 0},{"key": "loading_state","value": 0}],"pluginName": "TooB Cab IR","midiBindings": [ ],"midiChannelBinding": null,"stateUpdateCount": 0,"lv2State": [true,{"http://two-play.com/plugins/toob-cab-ir#impulseFile": {"flags": 3,"atomType": "http://lv2plug.in/ns/ext/atom#Path","value": "CabIR/Modern US 001.wav"},"http://two-play.com/plugins/toob-cab-ir#impulseFile2": {"flags": 3,"atomType": "http://lv2plug.in/ns/ext/atom#Path","value": "CabIR/Modern US 002.wav"},"http://two-play.com/plugins/toob-cab-ir#impulseFile3": {"flags": 3,"atomType": "http://lv2plug.in/ns/ext/atom#String","value": ""}}],"lilvPresetUri": "","pathProperties": {"http://two-play.com/plugins/toob-cab-ir#impulseFile": "{\"otype_\": \"Path\",\"value\": \"CabIR/Modern US 001.wav\"}","http://two-play.com/plugins/toob-cab-ir#impulseFile2": "{\"otype_\": \"Path\",\"value\": \"CabIR/Modern US 002.wav\"}","http://two-play.com/plugins/toob-cab-ir#impulseFile3": "{\"otype_\": \"Path\",\"value\": \"\"}"},"title": "","useModUi": false},{"instanceId": 87,"uri": "http://two-play.com/plugins/toob-volume","isEnabled": true,"controlValues": [{"key": "vol","value": -2.25}],"pluginName": "TooB Volume","midiBindings": [ ],"midiChannelBinding": null,"stateUpdateCount": 0,"lv2State": [false,{}],"lilvPresetUri": "","pathProperties": {},"title": "","useModUi": false},{"instanceId": 69,"uri": "http://two-play.com/plugins/toob-convolution-reverb-stereo","isEnabled": true,"controlValues": [{"key": "bypass","value": 1},{"key": "time","value": 30},{"key": "direct_mix","value": -0.666666687},{"key": "reverb_mix","value": -1.39999998},{"key": "width","value": 1},{"key": "pan","value": 0},{"key": "predelay","value": -1},{"key": "predelay_new","value": 1},{"key": "stretch","value": 1.27456057},{"key": "decay","value": 0},{"key": "tails","value": 1},{"key": "loading_state","value": 0}],"pluginName": "TooB Convolution Reverb (Stereo)","midiBindings": [ ],"midiChannelBinding": null,"stateUpdateCount": 1,"lv2State": [true,{"http://two-play.com/plugins/toob-impulse#impulseFile": {"flags": 3,"atomType": "http://lv2plug.in/ns/ext/atom#Path","value": "ReverbImpulseFiles/St. Margaret's Church.wav"}}],"lilvPresetUri": "","pathProperties": {"http://two-play.com/plugins/toob-impulse#impulseFile": "{\"otype_\": \"Path\",\"value\": \"ReverbImpulseFiles/St. Margaret's Church.wav\"}"},"title": "","useModUi": false}],"bottomChain": [{"instanceId": 81,"uri": "http://two-play.com/plugins/toob-ml","isEnabled": true,"controlValues": [{"key": "trim","value": -10.5},{"key": "trimOut","value": -35},{"key": "model","value": -1},{"key": "gain","value": 8.83333302},{"key": "master","value": 0},{"key": "bass","value": 0.5},{"key": "mid","value": 0.5},{"key": "treble","value": 0.5},{"key": "sag","value": 0},{"key": "sagd","value": 0},{"key": "sagf","value": 13},{"key": "gainEnable","value": 0}],"pluginName": "TooB ML Amplifier","midiBindings": [ ],"midiChannelBinding": null,"stateUpdateCount": 2,"lv2State": [true,{"http://two-play.com/plugins/toob-ml#modelFile": {"flags": 3,"atomType": "http://lv2plug.in/ns/ext/atom#Path","value": "ToobMlModels/Proteus_Tone_Packs/AmpPack1/BlackstarHT40_AmpHighGain.json"},"http://two-play.com/plugins/toob-ml#version": {"flags": 3,"atomType": "http://lv2plug.in/ns/ext/atom#Float","value": 1}}],"lilvPresetUri": "","pathProperties": {"http://two-play.com/plugins/toob-ml#modelFile": "{\"otype_\": \"Path\",\"value\": \"ToobMlModels/Proteus_Tone_Packs/AmpPack1/BlackstarHT40_AmpHighGain.json\"}"},"title": "HT40","useModUi": false},{"instanceId": 62,"uri": "http://two-play.com/plugins/toob-cab-ir","isEnabled": true,"controlValues": [{"key": "reverb_mix","value": -18.5},{"key": "reverb_mix2","value": -5.5},{"key": "reverb_mix3","value": 0},{"key": "time","value": 1.5},{"key": "direct_mix","value": -13.5},{"key": "predelay","value": 0},{"key": "loading_state","value": 0}],"pluginName": "TooB Cab IR","midiBindings": [ ],"midiChannelBinding": null,"stateUpdateCount": 0,"lv2State": [true,{"http://two-play.com/plugins/toob-cab-ir#impulseFile": {"flags": 3,"atomType": "http://lv2plug.in/ns/ext/atom#Path","value": "CabIR/Modern US 002.wav"},"http://two-play.com/plugins/toob-cab-ir#impulseFile2": {"flags": 3,"atomType": "http://lv2plug.in/ns/ext/atom#Path","value": "CabIR/Modern US 004.wav"},"http://two-play.com/plugins/toob-cab-ir#impulseFile3": {"flags": 3,"atomType": "http://lv2plug.in/ns/ext/atom#String","value": ""}}],"lilvPresetUri": "","pathProperties": {"http://two-play.com/plugins/toob-cab-ir#impulseFile": "{\"otype_\": \"Path\",\"value\": \"CabIR/Modern US 002.wav\"}","http://two-play.com/plugins/toob-cab-ir#impulseFile2": "{\"otype_\": \"Path\",\"value\": \"CabIR/Modern US 004.wav\"}","http://two-play.com/plugins/toob-cab-ir#impulseFile3": "{\"otype_\": \"Path\",\"value\": \"\"}"},"title": "","useModUi": false},{"instanceId": 85,"uri": "http://two-play.com/plugins/toob-volume","isEnabled": true,"controlValues": [{"key": "vol","value": 3.75}],"pluginName": "TooB Volume","midiBindings": [ ],"midiChannelBinding": null,"stateUpdateCount": 0,"lv2State": [false,{}],"lilvPresetUri": "","pathProperties": {},"title": "","useModUi": false},{"instanceId": 83,"uri": "http://two-play.com/plugins/toob-freeverb","isEnabled": true,"controlValues": [{"key": "bypass","value": 1},{"key": "dryWet","value": 0.383333325},{"key": "roomSize","value": 0.683333337},{"key": "damping","value": 0},{"key": "tails","value": 1}],"pluginName": "TooB Freeverb","midiBindings": [ ],"midiChannelBinding": null,"stateUpdateCount": 0,"lv2State": [false,{}],"lilvPresetUri": "","pathProperties": {},"title": "","useModUi": false}],"midiBindings": [ ],"midiChannelBinding": null,"stateUpdateCount": 0,"lv2State": [false,{}],"lilvPresetUri": "","pathProperties": {},"title": "","useModUi": false}],"nextInstanceId": 88,"snapshots": [ ],"selectedSnapshot": -1,"selectedPlugin": 81}},{"instanceId": 9,"preset": {"name": "Screaming Lead","input_volume_db": 0,"output_volume_db": 0,"items": [{"instanceId": 16,"uri": "http://two-play.com/plugins/toob-tuner","isEnabled": true,"controlValues": [{"key": "REFFREQ","value": 440},{"key": "THRESHOLD","value": -54},{"key": "MUTE","value": 0}],"pluginName": "TooB Tuner","midiBindings": [ ],"midiChannelBinding": null,"stateUpdateCount": 0,"lv2State": [false,{}],"lilvPresetUri": "","pathProperties": {},"title": "","useModUi": false},{"instanceId": 17,"uri": "http://two-play.com/plugins/toob-input_stage","isEnabled": true,"controlValues": [{"key": "trim","value": -15.75},{"key": "locut","value": 196.5},{"key": "bright","value": 0},{"key": "brightf","value": 1300},{"key": "hicut","value": 13000},{"key": "gate_t","value": -66.5},{"key": "boost","value": 0}],"pluginName": "TooB Input Stage","midiBindings": [{"channel": -1,"symbol": "__bypass","bindingType": 2,"note": 72,"control": 4,"minControlValue": 0,"maxControlValue": 127,"minValue": 0,"maxValue": 1,"rotaryScale": 1,"linearControlType": 0,"switchControlType": 0}],"midiChannelBinding": null,"stateUpdateCount": 0,"lv2State": [false,{}],"lilvPresetUri": "","pathProperties": {},"title": "","useModUi": false},{"instanceId": 41,"uri": "http://two-play.com/plugins/toob-delay","isEnabled": true,"controlValues": [{"key": "delay","value": 340},{"key": "level","value": 18.666666},{"key": "feedback","value": 13.3333349}],"pluginName": "TooB Delay","midiBindings": [ ],"midiChannelBinding": null,"stateUpdateCount": 0,"lv2State": [false,{}],"lilvPresetUri": "","pathProperties": {},"title": "","useModUi": false},{"instanceId": 42,"uri": "http://two-play.com/plugins/toob-chorus","isEnabled": false,"controlValues": [{"key": "rate","value": 0.291666657},{"key": "depth","value": 0.375},{"key": "dryWet","value": 1}],"pluginName": "TooB CE-2 Chorus","midiBindings": [ ],"midiChannelBinding": null,"stateUpdateCount": 0,"lv2State": [false,{}],"lilvPresetUri": "","pathProperties": {},"title": "","useModUi": false},{"instanceId": 47,"uri": "http://two-play.com/plugins/toob-ml","isEnabled": true,"controlValues": [{"key": "trim","value": 12.5},{"key": "model","value": 29},{"key": "gain","value": 3},{"key": "master","value": -0.5},{"key": "bass","value": 0.5},{"key": "mid","value": 0.5},{"key": "treble","value": 0.5},{"key": "sag","value": 0},{"key": "sagd","value": 0},{"key": "sagf","value": 13}],"pluginName": "TooB ML Amplifier","midiBindings": [ ],"midiChannelBinding": null,"stateUpdateCount": 0,"lv2State": [true,{"http://two-play.com/plugins/toob-ml#modelFile": {"flags": 3,"atomType": "http://lv2plug.in/ns/ext/atom#Path","value": "ToobMlModels/TS9_FullD.json"},"http://two-play.com/plugins/toob-ml#version": {"flags": 3,"atomType": "http://lv2plug.in/ns/ext/atom#Float","value": 1}}],"lilvPresetUri": "","pathProperties": {"http://two-play.com/plugins/toob-ml#modelFile": "{\"otype_\": \"Path\",\"value\": \"/var/pipedal/audio_uploads/ToobMlModels/TS9_FullD.json\"}"},"title": "","useModUi": false},{"instanceId": 39,"uri": "http://two-play.com/plugins/toob-ml","isEnabled": true,"controlValues": [{"key": "trim","value": -4},{"key": "bass","value": 0.5},{"key": "mid","value": 0.5},{"key": "treble","value": 0.5},{"key": "model","value": 12},{"key": "gain","value": 10},{"key": "master","value": -5},{"key": "sag","value": 0},{"key": "sagd","value": 0},{"key": "sagf","value": 13}],"pluginName": "TooB ML Amplifier","midiBindings": [ ],"midiChannelBinding": null,"stateUpdateCount": 0,"lv2State": [true,{"http://two-play.com/plugins/toob-ml#modelFile": {"flags": 3,"atomType": "http://lv2plug.in/ns/ext/atom#Path","value": "ToobMlModels/HT40_Overdrive.json"},"http://two-play.com/plugins/toob-ml#version": {"flags": 3,"atomType": "http://lv2plug.in/ns/ext/atom#Float","value": 1}}],"lilvPresetUri": "","pathProperties": {"http://two-play.com/plugins/toob-ml#modelFile": "{\"otype_\": \"Path\",\"value\": \"/var/pipedal/audio_uploads/ToobMlModels/HT40_Overdrive.json\"}"},"title": "","useModUi": false},{"instanceId": 51,"uri": "http://two-play.com/plugins/toob-flanger-stereo","isEnabled": true,"controlValues": [{"key": "manual","value": 0.5},{"key": "depth","value": 0.5},{"key": "rate","value": 0.5},{"key": "lfo","value": 0},{"key": "res","value": 0.5},{"key": "dryWet","value": 0.508333325}],"pluginName": "TooB BF-2 Stereo Flanger","midiBindings": [ ],"midiChannelBinding": null,"stateUpdateCount": 0,"lv2State": [false,{}],"lilvPresetUri": "","pathProperties": {},"title": "","useModUi": false},{"instanceId": 53,"uri": "http://two-play.com/plugins/toob-freeverb","isEnabled": true,"controlValues": [{"key": "bypass","value": 1},{"key": "dryWet","value": 0.150000006},{"key": "roomSize","value": 0.649999976},{"key": "damping","value": 0},{"key": "tails","value": 1}],"pluginName": "TooB Freeverb","midiBindings": [ ],"midiChannelBinding": null,"stateUpdateCount": 0,"lv2State": [false,{}],"lilvPresetUri": "","pathProperties": {},"title": "","useModUi": false}],"nextInstanceId": 53,"snapshots": [ ],"selectedSnapshot": -1,"selectedPlugin": 16}},{"instanceId": 10,"preset": {"name": "Light Crunch","input_volume_db": 0,"output_volume_db": 0,"items": [{"instanceId": 16,"uri": "http://two-play.com/plugins/toob-tuner","isEnabled": true,"controlValues": [{"key": "REFFREQ","value": 440},{"key": "THRESHOLD","value": -23.1093884},{"key": "MUTE","value": 0}],"pluginName": "TooB Tuner","midiBindings": [ ],"midiChannelBinding": null,"stateUpdateCount": 0,"lv2State": [false,{}],"lilvPresetUri": "","pathProperties": {},"title": "","useModUi": false},{"instanceId": 49,"uri": "http://two-play.com/plugins/toob-noise-gate","isEnabled": true,"controlValues": [{"key": "threshold","value": -102},{"key": "hysteresis","value": -6},{"key": "trigger_led","value": 0},{"key": "reduction","value": -60},{"key": "attack","value": 1},{"key": "hold","value": 100},{"key": "release","value": 330},{"key": "gate_level","value": -60}],"pluginName": "TooB Noise Gate","midiBindings": [ ],"midiChannelBinding": null,"stateUpdateCount": 0,"lv2State": [false,{}],"lilvPresetUri": "","pathProperties": {},"title": "","useModUi": false},{"instanceId": 42,"uri": "http://two-play.com/plugins/toob-chorus","isEnabled": false,"controlValues": [{"key": "rate","value": 0.291666657},{"key": "depth","value": 0.375},{"key": "dryWet","value": 1}],"pluginName": "TooB CE-2 Chorus","midiBindings": [ ],"midiChannelBinding": null,"stateUpdateCount": 0,"lv2State": [false,{}],"lilvPresetUri": "","pathProperties": {},"title": "","useModUi": false},{"instanceId": 41,"uri": "http://two-play.com/plugins/toob-delay","isEnabled": true,"controlValues": [{"key": "delay","value": 340},{"key": "level","value": 27},{"key": "feedback","value": 39}],"pluginName": "TooB Delay","midiBindings": [ ],"midiChannelBinding": null,"stateUpdateCount": 0,"lv2State": [false,{}],"lilvPresetUri": "","pathProperties": {},"title": "","useModUi": false},{"instanceId": 39,"uri": "http://two-play.com/plugins/toob-ml","isEnabled": true,"controlValues": [{"key": "trim","value": -0.5},{"key": "bass","value": 0.650000036},{"key": "mid","value": 0.5},{"key": "treble","value": 0.266666681},{"key": "model","value": 17},{"key": "gain","value": 7.75},{"key": "master","value": -4},{"key": "sag","value": 0},{"key": "sagd","value": 0},{"key": "sagf","value": 13}],"pluginName": "TooB ML Amplifier","midiBindings": [ ],"midiChannelBinding": null,"stateUpdateCount": 1,"lv2State": [true,{"http://two-play.com/plugins/toob-ml#modelFile": {"flags": 3,"atomType": "http://lv2plug.in/ns/ext/atom#Path","value": "ToobMlModels/ProCoRat.json"},"http://two-play.com/plugins/toob-ml#version": {"flags": 3,"atomType": "http://lv2plug.in/ns/ext/atom#Float","value": 1}}],"lilvPresetUri": "","pathProperties": {"http://two-play.com/plugins/toob-ml#modelFile": "{\"otype_\": \"Path\",\"value\": \"/var/pipedal/audio_uploads/ToobMlModels/ProCoRat.json\"}"},"title": "","useModUi": false},{"instanceId": 51,"uri": "http://two-play.com/plugins/toob-ml","isEnabled": true,"controlValues": [{"key": "trim","value": -3},{"key": "trimOut","value": -35},{"key": "model","value": -1},{"key": "gain","value": 3},{"key": "master","value": -6},{"key": "bass","value": 0.5},{"key": "mid","value": 0.5},{"key": "treble","value": 0.5},{"key": "sag","value": 0},{"key": "sagd","value": 0},{"key": "sagf","value": 13},{"key": "gainEnable","value": 0}],"pluginName": "TooB ML Amplifier","midiBindings": [ ],"midiChannelBinding": null,"stateUpdateCount": 0,"lv2State": [true,{"http://two-play.com/plugins/toob-ml#modelFile": {"flags": 3,"atomType": "http://lv2plug.in/ns/ext/atom#Path","value": "ToobMlModels/FenderPrinceton_highLevel.json"},"http://two-play.com/plugins/toob-ml#version": {"flags": 3,"atomType": "http://lv2plug.in/ns/ext/atom#Float","value": 1}}],"lilvPresetUri": "","pathProperties": {"http://two-play.com/plugins/toob-ml#modelFile": "{\"otype_\": \"Path\",\"value\": \"/var/pipedal/audio_uploads/ToobMlModels/FenderPrinceton_highLevel.json\"}"},"title": "","useModUi": false},{"instanceId": 58,"uri": "http://two-play.com/plugins/toob-nam","isEnabled": true,"controlValues": [{"key": "inputGain","value": -12.666667},{"key": "inputGainOut","value": -35},{"key": "outputGain","value": 0},{"key": "gate","value": -120},{"key": "gateOut","value": 0},{"key": "buffer","value": 0},{"key": "toneStack","value": 3},{"key": "bass","value": 5},{"key": "mid","value": 5},{"key": "treble","value": 5},{"key": "inputCalibrationMode","value": 1},{"key": "calibration","value": -6},{"key": "outputCalibration","value": 0},{"key": "version","value": 1}],"pluginName": "TooB Neural Amp Modeler","midiBindings": [ ],"midiChannelBinding": null,"stateUpdateCount": 1,"lv2State": [true,{}],"lilvPresetUri": "","pathProperties": {"http://two-play.com/plugins/toob-nam#modelFile": "{\"otype_\": \"Path\",\"value\": \"\"}"},"title": "","useModUi": false},{"instanceId": 53,"uri": "http://two-play.com/plugins/toob-parametric-eq","isEnabled": true,"controlValues": [{"key": "loCut","value": 27.2704601},{"key": "hiCut","value": 21},{"key": "lfLevel","value": 0},{"key": "lfC","value": 120},{"key": "lmfLevel","value": -6.5},{"key": "lmfC","value": 551.471313},{"key": "lmfQ","value": 0.850504637},{"key": "hmfLevel","value": -7.5},{"key": "hmfC","value": 2.5746665},{"key": "hmfQ","value": 1.5},{"key": "hfLevel","value": -6.5},{"key": "hfC","value": 2.25},{"key": "gain","value": 0}],"pluginName": "TooB Parametric EQ","midiBindings": [ ],"midiChannelBinding": null,"stateUpdateCount": 0,"lv2State": [false,{}],"lilvPresetUri": "","pathProperties": {},"title": "","useModUi": false},{"instanceId": 31,"uri": "http://two-play.com/plugins/toob-freeverb","isEnabled": true,"controlValues": [{"key": "dryWet","value": 0.416666687},{"key": "roomSize","value": 0.625},{"key": "damping","value": 0.574999988},{"key": "bypass","value": 1},{"key": "tails","value": 1}],"pluginName": "TooB Freeverb","midiBindings": [ ],"midiChannelBinding": null,"stateUpdateCount": 0,"lv2State": [false,{}],"lilvPresetUri": "","pathProperties": {},"title": "","useModUi": false}],"nextInstanceId": 58,"snapshots": [ ],"selectedSnapshot": -1,"selectedPlugin": 16}},{"instanceId": 11,"preset": {"name": "Jazz","input_volume_db": 0,"output_volume_db": 0,"items": [{"instanceId": 16,"uri": "http://two-play.com/plugins/toob-tuner","isEnabled": true,"controlValues": [{"key": "REFFREQ","value": 440},{"key": "THRESHOLD","value": -23.1093884},{"key": "MUTE","value": 0}],"pluginName": "TooB Tuner","midiBindings": [ ],"midiChannelBinding": null,"stateUpdateCount": 0,"lv2State": [false,{}],"lilvPresetUri": "","pathProperties": {},"title": "","useModUi": false},{"instanceId": 45,"uri": "http://two-play.com/plugins/toob-noise-gate","isEnabled": true,"controlValues": [{"key": "threshold","value": -53},{"key": "hysteresis","value": -6},{"key": "trigger_led","value": 0},{"key": "reduction","value": -60},{"key": "attack","value": 1},{"key": "hold","value": 100},{"key": "release","value": 330},{"key": "gate_level","value": -60}],"pluginName": "TooB Noise Gate","midiBindings": [ ],"midiChannelBinding": null,"stateUpdateCount": 0,"lv2State": [false,{}],"lilvPresetUri": "","pathProperties": {},"title": "","useModUi": false},{"instanceId": 41,"uri": "http://two-play.com/plugins/toob-chorus","isEnabled": false,"controlValues": [{"key": "rate","value": 0.208333328},{"key": "depth","value": 0.5},{"key": "dryWet","value": 1}],"pluginName": "TooB CE-2 Chorus","midiBindings": [ ],"midiChannelBinding": null,"stateUpdateCount": 0,"lv2State": [false,{}],"lilvPresetUri": "","pathProperties": {},"title": "","useModUi": false},{"instanceId": 47,"uri": "http://two-play.com/plugins/toob-parametric-eq","isEnabled": true,"controlValues": [{"key": "loCut","value": 41.8936462},{"key": "hiCut","value": 21},{"key": "lfLevel","value": 0},{"key": "lfC","value": 120},{"key": "lmfLevel","value": -5.5},{"key": "lmfC","value": 238.107834},{"key": "lmfQ","value": 0.837899804},{"key": "hmfLevel","value": 0},{"key": "hmfC","value": 2},{"key": "hmfQ","value": 1.5},{"key": "hfLevel","value": -12},{"key": "hfC","value": 1},{"key": "gain","value": 5.83333349}],"pluginName": "TooB Parametric EQ","midiBindings": [ ],"midiChannelBinding": null,"stateUpdateCount": 0,"lv2State": [false,{}],"lilvPresetUri": "","pathProperties": {},"title": "","useModUi": false},{"instanceId": 39,"uri": "http://two-play.com/plugins/toob-ml","isEnabled": true,"controlValues": [{"key": "trim","value": -13},{"key": "bass","value": 0.5},{"key": "mid","value": 0.5},{"key": "treble","value": 0.5},{"key": "model","value": 0},{"key": "gain","value": 4.66666651},{"key": "master","value": 5},{"key": "sag","value": 0.550000012},{"key": "sagd","value": 0.900000036},{"key": "sagf","value": 13}],"pluginName": "TooB ML Amplifier","midiBindings": [ ],"midiChannelBinding": null,"stateUpdateCount": 2,"lv2State": [true,{"http://two-play.com/plugins/toob-ml#modelFile": {"flags": 3,"atomType": "http://lv2plug.in/ns/ext/atom#Path","value": "ToobMlModels/FenderPrinceton_clean.json"},"http://two-play.com/plugins/toob-ml#version": {"flags": 3,"atomType": "http://lv2plug.in/ns/ext/atom#Float","value": 1}}],"lilvPresetUri": "","pathProperties": {"http://two-play.com/plugins/toob-ml#modelFile": "{\"otype_\": \"Path\",\"value\": \"ToobMlModels/FenderPrinceton_clean.json\"}"},"title": "","useModUi": false},{"instanceId": 31,"uri": "http://two-play.com/plugins/toob-freeverb","isEnabled": true,"controlValues": [{"key": "dryWet","value": 0.416666657},{"key": "roomSize","value": 0.625},{"key": "damping","value": 0.574999988},{"key": "bypass","value": 1},{"key": "tails","value": 1}],"pluginName": "TooB Freeverb","midiBindings": [ ],"midiChannelBinding": null,"stateUpdateCount": 0,"lv2State": [false,{}],"lilvPresetUri": "","pathProperties": {},"title": "","useModUi": false}],"nextInstanceId": 48,"snapshots": [ ],"selectedSnapshot": -1,"selectedPlugin": 16}}]} \ No newline at end of file diff --git a/docs/BuildPrerequisites.md b/docs/BuildPrerequisites.md index 78ab8af..3f1a9b6 100644 --- a/docs/BuildPrerequisites.md +++ b/docs/BuildPrerequisites.md @@ -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 \ diff --git a/docs/Installing.md b/docs/Installing.md index ee33e09..a3ad872 100644 --- a/docs/Installing.md +++ b/docs/Installing.md @@ -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. diff --git a/docs/ReleaseNotes.md b/docs/ReleaseNotes.md index f2d3b66..7dd3972 100644 --- a/docs/ReleaseNotes.md +++ b/docs/ReleaseNotes.md @@ -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 diff --git a/docs/TheBuildSystem.md b/docs/TheBuildSystem.md index 7f69410..ad271a9 100644 --- a/docs/TheBuildSystem.md +++ b/docs/TheBuildSystem.md @@ -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. diff --git a/docs/download.md b/docs/download.md index 6a985e6..2d1f9be 100644 --- a/docs/download.md +++ b/docs/download.md @@ -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. diff --git a/lv2/aarch64/ToobAmp.lv2/CabIR.ttl b/lv2/aarch64/ToobAmp.lv2/CabIR.ttl index 72633ec..d88d73a 100644 --- a/lv2/aarch64/ToobAmp.lv2/CabIR.ttl +++ b/lv2/aarch64/ToobAmp.lv2/CabIR.ttl @@ -93,7 +93,7 @@ cabir:impulseFile3 doap:license ; doap:maintainer ; 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 ; diff --git a/lv2/aarch64/ToobAmp.lv2/CabSim.ttl b/lv2/aarch64/ToobAmp.lv2/CabSim.ttl index 8342e28..2789e8d 100644 --- a/lv2/aarch64/ToobAmp.lv2/CabSim.ttl +++ b/lv2/aarch64/ToobAmp.lv2/CabSim.ttl @@ -50,7 +50,7 @@ toob:frequencyResponseVector doap:license ; doap:maintainer ; lv2:minorVersion 0 ; - lv2:microVersion 68 ; + lv2:microVersion 70 ; mod:brand "TooB"; mod:label "TooB CabSim"; diff --git a/lv2/aarch64/ToobAmp.lv2/ConvolutionReverb.ttl b/lv2/aarch64/ToobAmp.lv2/ConvolutionReverb.ttl index 3310124..7794f2c 100644 --- a/lv2/aarch64/ToobAmp.lv2/ConvolutionReverb.ttl +++ b/lv2/aarch64/ToobAmp.lv2/ConvolutionReverb.ttl @@ -53,7 +53,7 @@ toobimpulse:impulseFile doap:license ; doap:maintainer ; 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 diff --git a/lv2/aarch64/ToobAmp.lv2/ConvolutionReverbStereo.ttl b/lv2/aarch64/ToobAmp.lv2/ConvolutionReverbStereo.ttl index b46af8b..b754268 100644 --- a/lv2/aarch64/ToobAmp.lv2/ConvolutionReverbStereo.ttl +++ b/lv2/aarch64/ToobAmp.lv2/ConvolutionReverbStereo.ttl @@ -51,7 +51,7 @@ toobimpulse:impulseFile doap:license ; doap:maintainer ; lv2:minorVersion 0 ; - lv2:microVersion 68 ; + lv2:microVersion 70 ; rdfs:comment """ diff --git a/lv2/aarch64/ToobAmp.lv2/InputStage.ttl b/lv2/aarch64/ToobAmp.lv2/InputStage.ttl index a1eb734..229bc9a 100644 --- a/lv2/aarch64/ToobAmp.lv2/InputStage.ttl +++ b/lv2/aarch64/ToobAmp.lv2/InputStage.ttl @@ -66,7 +66,7 @@ inputStage:filterGroup doap:license ; doap:maintainer ; lv2:minorVersion 0 ; - lv2:microVersion 68 ; + lv2:microVersion 70 ; mod:brand "TooB"; mod:label "TooB Input"; diff --git a/lv2/aarch64/ToobAmp.lv2/PowerStage2.ttl b/lv2/aarch64/ToobAmp.lv2/PowerStage2.ttl index 274beac..375bcb9 100644 --- a/lv2/aarch64/ToobAmp.lv2/PowerStage2.ttl +++ b/lv2/aarch64/ToobAmp.lv2/PowerStage2.ttl @@ -68,7 +68,7 @@ pstage:stage3 doap:license ; doap:maintainer ; lv2:minorVersion 0 ; - lv2:microVersion 68 ; + lv2:microVersion 70 ; mod:brand "TooB"; mod:label "Power Stage"; diff --git a/lv2/aarch64/ToobAmp.lv2/SpectrumAnalyzer.ttl b/lv2/aarch64/ToobAmp.lv2/SpectrumAnalyzer.ttl index 68777ab..5ae2b18 100644 --- a/lv2/aarch64/ToobAmp.lv2/SpectrumAnalyzer.ttl +++ b/lv2/aarch64/ToobAmp.lv2/SpectrumAnalyzer.ttl @@ -57,7 +57,7 @@ doap:license ; doap:maintainer ; lv2:minorVersion 0 ; - lv2:microVersion 68 ; + lv2:microVersion 70 ; rdfs:comment "TooB spectrum analyzer" ; mod:brand "TooB"; diff --git a/lv2/aarch64/ToobAmp.lv2/ToneStack.ttl b/lv2/aarch64/ToobAmp.lv2/ToneStack.ttl index 2b40c2d..c306598 100644 --- a/lv2/aarch64/ToobAmp.lv2/ToneStack.ttl +++ b/lv2/aarch64/ToobAmp.lv2/ToneStack.ttl @@ -57,7 +57,7 @@ tonestack:eqGroup doap:license ; doap:maintainer ; lv2:minorVersion 0 ; - lv2:microVersion 68 ; + lv2:microVersion 70 ; uiext:ui ; diff --git a/lv2/aarch64/ToobAmp.lv2/Toob3BandEq.ttl b/lv2/aarch64/ToobAmp.lv2/Toob3BandEq.ttl index b5eb121..45a1d4f 100644 --- a/lv2/aarch64/ToobAmp.lv2/Toob3BandEq.ttl +++ b/lv2/aarch64/ToobAmp.lv2/Toob3BandEq.ttl @@ -52,7 +52,7 @@ toob:frequencyResponseVector doap:license ; doap:maintainer ; lv2:minorVersion 0 ; - lv2:microVersion 68 ; + lv2:microVersion 70 ; uiext:ui ; diff --git a/lv2/aarch64/ToobAmp.lv2/ToobAmpUI.so b/lv2/aarch64/ToobAmp.lv2/ToobAmpUI.so index c12ccc9..a409b12 100755 Binary files a/lv2/aarch64/ToobAmp.lv2/ToobAmpUI.so and b/lv2/aarch64/ToobAmp.lv2/ToobAmpUI.so differ diff --git a/lv2/aarch64/ToobAmp.lv2/ToobChorus.ttl b/lv2/aarch64/ToobAmp.lv2/ToobChorus.ttl index c2b2405..7217dfc 100644 --- a/lv2/aarch64/ToobAmp.lv2/ToobChorus.ttl +++ b/lv2/aarch64/ToobAmp.lv2/ToobChorus.ttl @@ -40,7 +40,7 @@ doap:license ; doap:maintainer ; lv2:minorVersion 0 ; - lv2:microVersion 68 ; + lv2:microVersion 70 ; rdfs:comment """ Emulation of a Boss CE-2 Chorus. diff --git a/lv2/aarch64/ToobAmp.lv2/ToobDelay.ttl b/lv2/aarch64/ToobAmp.lv2/ToobDelay.ttl index d148f4c..99e3e5f 100644 --- a/lv2/aarch64/ToobAmp.lv2/ToobDelay.ttl +++ b/lv2/aarch64/ToobAmp.lv2/ToobDelay.ttl @@ -41,7 +41,7 @@ doap:license ; doap:maintainer ; lv2:minorVersion 0 ; - lv2:microVersion 68 ; + lv2:microVersion 70 ; rdfs:comment """ A straightforward no-frills digital delay. diff --git a/lv2/aarch64/ToobAmp.lv2/ToobFlanger.ttl b/lv2/aarch64/ToobAmp.lv2/ToobFlanger.ttl index ff92a78..9e74eaf 100644 --- a/lv2/aarch64/ToobAmp.lv2/ToobFlanger.ttl +++ b/lv2/aarch64/ToobAmp.lv2/ToobFlanger.ttl @@ -41,7 +41,7 @@ doap:license ; doap:maintainer ; 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. diff --git a/lv2/aarch64/ToobAmp.lv2/ToobFlangerStereo.ttl b/lv2/aarch64/ToobAmp.lv2/ToobFlangerStereo.ttl index 0a51e5d..933a93d 100644 --- a/lv2/aarch64/ToobAmp.lv2/ToobFlangerStereo.ttl +++ b/lv2/aarch64/ToobAmp.lv2/ToobFlangerStereo.ttl @@ -41,7 +41,7 @@ doap:license ; doap:maintainer ; lv2:minorVersion 0 ; - lv2:microVersion 68 ; + lv2:microVersion 70 ; rdfs:comment """ Digital emulation of a Boss BF-2 Flanger. diff --git a/lv2/aarch64/ToobAmp.lv2/ToobFreeverb.ttl b/lv2/aarch64/ToobAmp.lv2/ToobFreeverb.ttl index d935312..d0cdf79 100644 --- a/lv2/aarch64/ToobAmp.lv2/ToobFreeverb.ttl +++ b/lv2/aarch64/ToobAmp.lv2/ToobFreeverb.ttl @@ -41,7 +41,7 @@ doap:license ; doap:maintainer ; 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. diff --git a/lv2/aarch64/ToobAmp.lv2/ToobGraphicEq.ttl b/lv2/aarch64/ToobAmp.lv2/ToobGraphicEq.ttl index 8b82ba1..763cba0 100644 --- a/lv2/aarch64/ToobAmp.lv2/ToobGraphicEq.ttl +++ b/lv2/aarch64/ToobAmp.lv2/ToobGraphicEq.ttl @@ -41,7 +41,7 @@ doap:license ; doap:maintainer ; 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. """ ; diff --git a/lv2/aarch64/ToobAmp.lv2/ToobLooperFour.ttl b/lv2/aarch64/ToobAmp.lv2/ToobLooperFour.ttl index bcb43f1..de45efc 100644 --- a/lv2/aarch64/ToobAmp.lv2/ToobLooperFour.ttl +++ b/lv2/aarch64/ToobAmp.lv2/ToobLooperFour.ttl @@ -91,8 +91,8 @@ myprefix:output_group ; doap:license ; doap:maintainer ; - lv2:minorVersion 1 ; - lv2:microVersion 68 ; + lv2:minorVersion 2 ; + lv2:microVersion 70 ; ui:ui ; diff --git a/lv2/aarch64/ToobAmp.lv2/ToobLooperOne.ttl b/lv2/aarch64/ToobAmp.lv2/ToobLooperOne.ttl index 96eba70..8053e48 100644 --- a/lv2/aarch64/ToobAmp.lv2/ToobLooperOne.ttl +++ b/lv2/aarch64/ToobAmp.lv2/ToobLooperOne.ttl @@ -77,8 +77,8 @@ myprefix:output_group doap:license ; doap:maintainer ; - lv2:minorVersion 1 ; - lv2:microVersion 68 ; + lv2:minorVersion 2 ; + lv2:microVersion 70 ; ui:ui ; diff --git a/lv2/aarch64/ToobAmp.lv2/ToobML.ttl b/lv2/aarch64/ToobAmp.lv2/ToobML.ttl index f9df9e1..4d176c1 100644 --- a/lv2/aarch64/ToobAmp.lv2/ToobML.ttl +++ b/lv2/aarch64/ToobAmp.lv2/ToobML.ttl @@ -67,7 +67,7 @@ toobml:sagGroup doap:license ; doap:maintainer ; 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. diff --git a/lv2/aarch64/ToobAmp.lv2/ToobMix.ttl b/lv2/aarch64/ToobAmp.lv2/ToobMix.ttl index 8b9524b..c22d803 100644 --- a/lv2/aarch64/ToobAmp.lv2/ToobMix.ttl +++ b/lv2/aarch64/ToobAmp.lv2/ToobMix.ttl @@ -40,7 +40,7 @@ doap:license ; doap:maintainer ; lv2:minorVersion 0 ; - lv2:microVersion 68 ; + lv2:microVersion 70 ; rdfs:comment """ Remix a stereo input signal. diff --git a/lv2/aarch64/ToobAmp.lv2/ToobNeuralAmpModeler.ttl b/lv2/aarch64/ToobAmp.lv2/ToobNeuralAmpModeler.ttl index 5001381..8cd1756 100644 --- a/lv2/aarch64/ToobAmp.lv2/ToobNeuralAmpModeler.ttl +++ b/lv2/aarch64/ToobAmp.lv2/ToobNeuralAmpModeler.ttl @@ -72,7 +72,7 @@ toobNam:calibrationGroup doap:license ; doap:maintainer ; 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 diff --git a/lv2/aarch64/ToobAmp.lv2/ToobNoiseGate.ttl b/lv2/aarch64/ToobAmp.lv2/ToobNoiseGate.ttl index 8119804..b3f4b8c 100644 --- a/lv2/aarch64/ToobAmp.lv2/ToobNoiseGate.ttl +++ b/lv2/aarch64/ToobAmp.lv2/ToobNoiseGate.ttl @@ -54,7 +54,7 @@ noisegate:envelope_group doap:license ; doap:maintainer ; 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. diff --git a/lv2/aarch64/ToobAmp.lv2/ToobParametricEq.ttl b/lv2/aarch64/ToobAmp.lv2/ToobParametricEq.ttl index a3c2b37..2c49908 100644 --- a/lv2/aarch64/ToobAmp.lv2/ToobParametricEq.ttl +++ b/lv2/aarch64/ToobAmp.lv2/ToobParametricEq.ttl @@ -87,7 +87,7 @@ parametric_eq:hfGroup doap:license ; doap:maintainer ; lv2:minorVersion 0 ; - lv2:microVersion 68 ; + lv2:microVersion 70 ; uiext:ui ; diff --git a/lv2/aarch64/ToobAmp.lv2/ToobPhaser.ttl b/lv2/aarch64/ToobAmp.lv2/ToobPhaser.ttl index 23f068b..438c2c3 100644 --- a/lv2/aarch64/ToobAmp.lv2/ToobPhaser.ttl +++ b/lv2/aarch64/ToobAmp.lv2/ToobPhaser.ttl @@ -40,7 +40,7 @@ doap:license ; doap:maintainer ; lv2:minorVersion 0 ; - lv2:microVersion 68 ; + lv2:microVersion 70 ; rdfs:comment """ A loose emulation of an MXR® Phase 90 Phaser. diff --git a/lv2/aarch64/ToobAmp.lv2/ToobPlayer.ttl b/lv2/aarch64/ToobAmp.lv2/ToobPlayer.ttl index 46bb805..125c7ed 100644 --- a/lv2/aarch64/ToobAmp.lv2/ToobPlayer.ttl +++ b/lv2/aarch64/ToobAmp.lv2/ToobPlayer.ttl @@ -60,7 +60,7 @@ toobPlayer:seek doap:license ; doap:maintainer ; 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. diff --git a/lv2/aarch64/ToobAmp.lv2/ToobRecordMono.ttl b/lv2/aarch64/ToobAmp.lv2/ToobRecordMono.ttl index 27e4372..6014e8a 100644 --- a/lv2/aarch64/ToobAmp.lv2/ToobRecordMono.ttl +++ b/lv2/aarch64/ToobAmp.lv2/ToobRecordMono.ttl @@ -50,8 +50,8 @@ recordPrefix:audioFile ; doap:license ; doap:maintainer ; - lv2:minorVersion 1 ; - lv2:microVersion 68 ; + lv2:minorVersion 2 ; + lv2:microVersion 70 ; ui:ui ; diff --git a/lv2/aarch64/ToobAmp.lv2/ToobRecordStereo.ttl b/lv2/aarch64/ToobAmp.lv2/ToobRecordStereo.ttl index b3f8461..c42d2b6 100644 --- a/lv2/aarch64/ToobAmp.lv2/ToobRecordStereo.ttl +++ b/lv2/aarch64/ToobAmp.lv2/ToobRecordStereo.ttl @@ -87,8 +87,8 @@ myprefix:loop3_group ; doap:license ; doap:maintainer ; - lv2:minorVersion 1 ; - lv2:microVersion 68 ; + lv2:minorVersion 2 ; + lv2:microVersion 70 ; ui:ui ; diff --git a/lv2/aarch64/ToobAmp.lv2/ToobTuner.ttl b/lv2/aarch64/ToobAmp.lv2/ToobTuner.ttl index 55e3b8b..791cc20 100644 --- a/lv2/aarch64/ToobAmp.lv2/ToobTuner.ttl +++ b/lv2/aarch64/ToobAmp.lv2/ToobTuner.ttl @@ -40,7 +40,7 @@ doap:license ; doap:maintainer ; lv2:minorVersion 0 ; - lv2:microVersion 68 ; + lv2:microVersion 70 ; rdfs:comment """ TooB Tuner is a chromatic guitar tuner. """ ; diff --git a/lv2/aarch64/ToobAmp.lv2/ToobVolume.ttl b/lv2/aarch64/ToobAmp.lv2/ToobVolume.ttl index 4fedcf1..f6bc3be 100644 --- a/lv2/aarch64/ToobAmp.lv2/ToobVolume.ttl +++ b/lv2/aarch64/ToobAmp.lv2/ToobVolume.ttl @@ -39,7 +39,7 @@ doap:license ; doap:maintainer ; lv2:minorVersion 0 ; - lv2:microVersion 68 ; + lv2:microVersion 70 ; rdfs:comment """ Volume control. diff --git a/lv2/aarch64/ToobAmp.lv2/bin/ToobAmp-a72.so b/lv2/aarch64/ToobAmp.lv2/bin/ToobAmp-a72.so index 9b514a4..ba1a57b 100755 Binary files a/lv2/aarch64/ToobAmp.lv2/bin/ToobAmp-a72.so and b/lv2/aarch64/ToobAmp.lv2/bin/ToobAmp-a72.so differ diff --git a/lv2/aarch64/ToobAmp.lv2/bin/ToobAmp-a76.so b/lv2/aarch64/ToobAmp.lv2/bin/ToobAmp-a76.so index 93badf2..cb02a43 100755 Binary files a/lv2/aarch64/ToobAmp.lv2/bin/ToobAmp-a76.so and b/lv2/aarch64/ToobAmp.lv2/bin/ToobAmp-a76.so differ diff --git a/lv2/x86_64/toobamp_1.2.69_amd64.deb b/lv2/x86_64/toobamp_1.2.70_amd64.deb similarity index 59% rename from lv2/x86_64/toobamp_1.2.69_amd64.deb rename to lv2/x86_64/toobamp_1.2.70_amd64.deb index 53479c8..6d318df 100644 Binary files a/lv2/x86_64/toobamp_1.2.69_amd64.deb and b/lv2/x86_64/toobamp_1.2.70_amd64.deb differ diff --git a/makePackage.sh b/makePackage.sh index 617a733..0ede2d0 100755 --- a/makePackage.sh +++ b/makePackage.sh @@ -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 .. diff --git a/profiler_tools/ToobNam_profile b/profiler_tools/ToobNam_profile index c616390..db5b384 100755 --- a/profiler_tools/ToobNam_profile +++ b/profiler_tools/ToobNam_profile @@ -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 + diff --git a/profiler_tools/nam_profile_web b/profiler_tools/nam_profile_web index 70827d4..13b6462 100755 --- a/profiler_tools/nam_profile_web +++ b/profiler_tools/nam_profile_web @@ -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 diff --git a/provisionLv2 b/provisionLv2 index dc8b9ea..29f07b2 100755 --- a/provisionLv2 +++ b/provisionLv2 @@ -1,4 +1,4 @@ - +#!/bin/bash rm -rf ./lv2/aarch64/* cp -R /usr/lib/lv2/ToobAmp.lv2/ ./lv2/aarch64/ diff --git a/src/AlsaDriver.cpp b/src/AlsaDriver.cpp index a374068..319d494 100644 --- a/src/AlsaDriver.cpp +++ b/src/AlsaDriver.cpp @@ -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()) diff --git a/src/AlsaDriver.hpp b/src/AlsaDriver.hpp index c579813..1b9ee0e 100644 --- a/src/AlsaDriver.hpp +++ b/src/AlsaDriver.hpp @@ -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 diff --git a/src/AlsaDriverTest.cpp b/src/AlsaDriverTest.cpp index 29b1f2c..2508570 100644 --- a/src/AlsaDriverTest.cpp +++ b/src/AlsaDriverTest.cpp @@ -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) diff --git a/src/AudioHost.cpp b/src/AudioHost.cpp index 2740516..2b39bdf 100644 --- a/src/AudioHost.cpp +++ b/src/AudioHost.cpp @@ -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()); } diff --git a/src/AudioHost.hpp b/src/AudioHost.hpp index 17ad7da..f582547 100644 --- a/src/AudioHost.hpp +++ b/src/AudioHost.hpp @@ -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 diff --git a/src/BufferPool.hpp b/src/BufferPool.hpp index 6c62458..ea2eeb3 100644 --- a/src/BufferPool.hpp +++ b/src/BufferPool.hpp @@ -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); } diff --git a/src/CpuUse.hpp b/src/CpuUse.hpp index e2dedac..d5b96d2 100644 --- a/src/CpuUse.hpp +++ b/src/CpuUse.hpp @@ -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; } diff --git a/src/DummyAudioDriver.cpp b/src/DummyAudioDriver.cpp index a03e034..185e99e 100644 --- a/src/DummyAudioDriver.cpp +++ b/src/DummyAudioDriver.cpp @@ -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; } diff --git a/src/FileBrowserFilesFeature.cpp b/src/FileBrowserFilesFeature.cpp index 92d8709..818c417 100644 --- a/src/FileBrowserFilesFeature.cpp +++ b/src/FileBrowserFilesFeature.cpp @@ -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 &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 installedFiles; - std::unordered_set 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 &BrowserFilesVersionInfo::InstalledFiles() const { +const std::vector &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()) diff --git a/src/FileBrowserFilesFeature.hpp b/src/FileBrowserFilesFeature.hpp index 6d9cb05..9efcdbd 100644 --- a/src/FileBrowserFilesFeature.hpp +++ b/src/FileBrowserFilesFeature.hpp @@ -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 #include #include +#include +#include +#include - - -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 &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 installedFiles; + std::unordered_set 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; + private: + static void MakeDirectoryMap(const std::filesystem::path &rootBrowserDirectory); + + using WellKnownDirectoryMap = std::map; static std::mutex g_DirectoryMap_mutex; static std::unique_ptr 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 wellKnownPaths; + std::map 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); }; } \ No newline at end of file diff --git a/src/JackServerSettings.cpp b/src/JackServerSettings.cpp index 93c2de1..62aef81 100644 --- a/src/JackServerSettings.cpp +++ b/src/JackServerSettings.cpp @@ -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) diff --git a/src/JackServerSettings.hpp b/src/JackServerSettings.hpp index da1f511..f978d74 100644 --- a/src/JackServerSettings.hpp +++ b/src/JackServerSettings.hpp @@ -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); diff --git a/src/Lv2Effect.cpp b/src/Lv2Effect.cpp index 2b32224..fd339b0 100644 --- a/src/Lv2Effect.cpp +++ b/src/Lv2Effect.cpp @@ -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 &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(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(pHost, &(this->features[0]), pInstance, state_interface); + this->stateInterface = std::make_unique(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(pHost->GetMaxAudioBufferSize()); lilv_instance_connect_port(pInstance, pluginIndex, buffer); @@ -606,7 +755,7 @@ void Lv2Effect::AssignUnconnectedPorts() if (GetAudioOutputBuffer(i) == nullptr) { float *buffer = bufferPool.AllocateBuffer(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(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(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); +} diff --git a/src/Lv2Effect.hpp b/src/Lv2Effect.hpp index 6dd7c63..4abba0d 100644 --- a/src/Lv2Effect.hpp +++ b/src/Lv2Effect.hpp @@ -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 #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 workerThread; std::unique_ptr 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 pathProperties; std::vector 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> inputStagingBuffers; + std::vector> outputStagingBuffers; + std::vector inputStagingBufferPointers; + std::vector outputStagingBufferPointers; + + std::vector stagedInputAtomBuffer; + void *stagedInputAtomBufferPointer = nullptr; + std::vector 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(); }; diff --git a/src/Lv2Pedalboard.cpp b/src/Lv2Pedalboard.cpp index 4721f95..780ce82 100644 --- a/src/Lv2Pedalboard.cpp +++ b/src/Lv2Pedalboard.cpp @@ -59,6 +59,7 @@ int Lv2Pedalboard::GetControlIndex(uint64_t instanceId, const std::string &symbo } return -1; } + std::vector Lv2Pedalboard::PrepareItems( std::vector &items, std::vector inputBuffers, @@ -88,8 +89,8 @@ std::vector Lv2Pedalboard::PrepareItems( this->processActions.push_back(preMixAction); - std::vector topResult = PrepareItems(item.topChain(), topInputs, errorList,existingEffects); - std::vector bottomResult = PrepareItems(item.bottomChain(), bottomInputs, errorList,existingEffects); + std::vector topResult = PrepareItems(item.topChain(), topInputs, errorList, existingEffects); + std::vector bottomResult = PrepareItems(item.bottomChain(), bottomInputs, errorList, existingEffects); this->processActions.push_back( [pSplit](uint32_t frames) @@ -114,11 +115,10 @@ std::vector Lv2Pedalboard::PrepareItems( { std::shared_ptr 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 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 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 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()) { diff --git a/src/OptionsFeature.hpp b/src/OptionsFeature.hpp index 23212a8..8730e7a 100644 --- a/src/OptionsFeature.hpp +++ b/src/OptionsFeature.hpp @@ -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() diff --git a/src/PiLatencyMain.cpp b/src/PiLatencyMain.cpp index c7e7147..e1616ac 100644 --- a/src/PiLatencyMain.cpp +++ b/src/PiLatencyMain.cpp @@ -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 [] \n\n"; - pp << "where 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 [] []\n\n"; + pp << "where is the name of an ALSA capture device and is the name of a playback device. " + "If 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 { diff --git a/src/PiPedalAlsa.cpp b/src/PiPedalAlsa.cpp index af1be7e..06be795 100644 --- a/src/PiPedalAlsa.cpp +++ b/src/PiPedalAlsa.cpp @@ -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 +#include +#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 getProcAlsaDevices() +{ + std::vector 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 PiPedalAlsaDevices::GetAlsaDevices() { @@ -55,85 +163,111 @@ std::vector PiPedalAlsaDevices::GetAlsaDevices() int cardNum = -1; // Start with first card int err; - for (;;) + std::vector procAlsaDevices = getProcAlsaDevices(); + + for (const auto &procAlsaDevice: procAlsaDevices) { - if ((err = snd_card_next(&cardNum)) < 0) - { - Lv2Log::error("Unexpected error enumerating ALSA devices."); - break; + std::stringstream ss; + if (!procAlsaDevice.audioCapture && !procAlsaDevice.audioPlayback) { + continue; } - if (cardNum < 0) - // No more cards - break; + ss << "hw:" << procAlsaDevice.cardId; + std::string cardId = ss.str(); + + snd_ctl_t *hDevice = nullptr; + + if ((err = snd_ctl_open(&hDevice, cardId.c_str(), 0)) < 0) { - std::stringstream ss; - ss << "hw:" << cardNum; - std::string cardId = ss.str(); + continue; + } - snd_ctl_t *hDevice = nullptr; + Finally ffhDevice{[hDevice]() + { snd_ctl_close(hDevice); }}; - if ((err = snd_ctl_open(&hDevice, cardId.c_str(), 0)) < 0) + snd_ctl_card_info_t *alsaInfo = nullptr; + if (snd_ctl_card_info_malloc(&alsaInfo) != 0) + { + Lv2Log::error("Failed to allocate ALSA card info"); + continue; + } + + Finally ffCardInfo{[alsaInfo]() + { snd_ctl_card_info_free(alsaInfo); }}; + + err = snd_ctl_card_info(hDevice, alsaInfo); + if (err == 0) + { + AlsaDeviceInfo info; + info.cardId_ = cardNum; + info.id_ = std::string("hw:") + snd_ctl_card_info_get_id(alsaInfo); + const char *driver = snd_ctl_card_info_get_driver(alsaInfo); + (void)driver; + + info.name_ = snd_ctl_card_info_get_name(alsaInfo); + info.longName_ = snd_ctl_card_info_get_longname(alsaInfo); + + // we can't read our own device if it's open so use data that gets + // cached before we open audio devices. + + AlsaDeviceInfo cachedInfo; + if (getCachedDevice(info.name_, &cachedInfo)) { + // may have been plugged into a different USB connector. + cachedInfo.cardId_ = info.cardId_; + cachedInfo.id_ = info.id_; + result.push_back(cachedInfo); continue; } + snd_pcm_t *captureDevice = nullptr; + snd_pcm_t *playbackDevice = nullptr; + auto rc = snd_pcm_open(&captureDevice, cardId.c_str(), SND_PCM_STREAM_CAPTURE, SND_PCM_NONBLOCK); + bool captureOk = rc == 0; - snd_ctl_card_info_t *alsaInfo; - if (snd_ctl_card_info_malloc(&alsaInfo) != 0) + Finally ffCaptureDevice{ + [captureDevice] + { if (captureDevice) snd_pcm_close(captureDevice); }}; + + rc = snd_pcm_open(&playbackDevice, cardId.c_str(), SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK); + bool playbackOk = rc == 0; + + Finally ffPlaybackDevice{ + [playbackDevice] + { if (playbackDevice) snd_pcm_close(playbackDevice); }}; + if (procAlsaDevice.audioCapture && !captureOk) { - Lv2Log::error("Failed to allocate ALSA card info"); - snd_ctl_close(hDevice); - continue; + info.captureBusy_ = true; + } + if (procAlsaDevice.audioPlayback && !playbackOk) + { + info.playbackBusy_ = true; } - err = snd_ctl_card_info(hDevice, alsaInfo); - if (err == 0) + info.supportsCapture_ = captureOk; + info.supportsPlayback_ = playbackOk; + + if (captureOk || playbackOk) { - AlsaDeviceInfo info; - info.cardId_ = cardNum; - info.id_ = std::string("hw:") + snd_ctl_card_info_get_id(alsaInfo); - const char *driver = snd_ctl_card_info_get_driver(alsaInfo); + snd_pcm_t *hDevice = captureOk ? captureDevice : playbackDevice; + snd_pcm_hw_params_t *params = nullptr; + err = snd_pcm_hw_params_malloc(¶ms); - info.name_ = snd_ctl_card_info_get_name(alsaInfo); - info.longName_ = snd_ctl_card_info_get_longname(alsaInfo); - - snd_pcm_t *hDevice = nullptr; - - // must support capture AND playback - err = snd_pcm_open(&hDevice, cardId.c_str(), SND_PCM_STREAM_CAPTURE, 0); if (err == 0) { - snd_pcm_close(hDevice); - } - if (err == 0) - { - err = snd_pcm_open(&hDevice, cardId.c_str(), SND_PCM_STREAM_PLAYBACK, 0); - } - if (err == 0) - { - snd_pcm_hw_params_t *params = nullptr; - err = snd_pcm_hw_params_malloc(¶ms); + Finally ffParams{[params] + { snd_pcm_hw_params_free(params); }}; + + err = snd_pcm_hw_params_any(hDevice, params); if (err == 0) { - err = snd_pcm_hw_params_any(hDevice, params); + unsigned int minRate = 0, maxRate = 0; + snd_pcm_uframes_t minBufferSize = 0, maxBufferSize = 0; + int dir; + + err = snd_pcm_hw_params_get_rate_min(params, &minRate, &dir); if (err == 0) { - unsigned int minRate = 0, maxRate = 0; - snd_pcm_uframes_t minBufferSize = 0, maxBufferSize = 0; - int dir; - err = snd_pcm_hw_params_get_rate_min(params, &minRate, &dir); - if (err == 0) - { - err = snd_pcm_hw_params_get_rate_max(params, &maxRate, &dir); - } - if (err == 0) - { - err = snd_pcm_hw_params_get_buffer_size_min(params, &minBufferSize); - } - if (err == 0) - { - err = snd_pcm_hw_params_get_buffer_size_max(params, &maxBufferSize); - } + err = snd_pcm_hw_params_get_rate_max(params, &maxRate, &dir); if (err == 0) { for (size_t i = 0; i < sizeof(RATES) / sizeof(RATES[0]); ++i) @@ -144,42 +278,71 @@ std::vector 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 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 *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) diff --git a/src/PiPedalAlsa.hpp b/src/PiPedalAlsa.hpp index cf602b2..e9061e0 100644 --- a/src/PiPedalAlsa.hpp +++ b/src/PiPedalAlsa.hpp @@ -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 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:"); diff --git a/src/PluginHost.cpp b/src/PluginHost.cpp index 2fd40fd..8aff3dd 100644 --- a/src/PluginHost.cpp +++ b/src/PluginHost.cpp @@ -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::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::jmap{{ MAP_REF(Lv2PortGroup, uri), @@ -2242,6 +2254,8 @@ json_map::storage_type 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_), }}; diff --git a/src/PluginHost.hpp b/src/PluginHost.hpp index a1a46c5..c0edb66 100644 --- a/src/PluginHost.hpp +++ b/src/PluginHost.hpp @@ -26,7 +26,6 @@ #include #include "MapFeature.hpp" #include "FileMetadataFeature.hpp" -#include "OptionsFeature.hpp" #include #include #include @@ -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 lv2Features; MapFeature mapFeature; FileMetadataFeature fileMetadataFeature; - OptionsFeature optionsFeature; std::string pluginStoragePath; static void fn_LilvSetPortValueFunc(const char *port_symbol, diff --git a/src/Storage.cpp b/src/Storage.cpp index 8e81a04..d5b65b1 100644 --- a/src/Storage.cpp +++ b/src/Storage.cpp @@ -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 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(); } diff --git a/src/Storage.hpp b/src/Storage.hpp index 796a23a..e31212c 100644 --- a/src/Storage.hpp +++ b/src/Storage.hpp @@ -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); diff --git a/src/WebServerConfig.cpp b/src/WebServerConfig.cpp index 1fff419..af19e1c 100644 --- a/src/WebServerConfig.cpp +++ b/src/WebServerConfig.cpp @@ -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)); diff --git a/valgrind.sh b/valgrind.sh index 58d1723..e721ccb 100755 --- a/valgrind.sh +++ b/valgrind.sh @@ -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 diff --git a/vite/package-lock.json b/vite/package-lock.json index 80a7e7d..0dfc88e 100644 --- a/vite/package-lock.json +++ b/vite/package-lock.json @@ -18,9 +18,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": { @@ -53,14 +55,14 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.26.2", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz", - "integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", + "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", "license": "MIT", "dependencies": { - "@babel/helper-validator-identifier": "^7.25.9", + "@babel/helper-validator-identifier": "^7.27.1", "js-tokens": "^4.0.0", - "picocolors": "^1.0.0" + "picocolors": "^1.1.1" }, "engines": { "node": ">=6.9.0" @@ -182,18 +184,18 @@ } }, "node_modules/@babel/helper-string-parser": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz", - "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", - "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", + "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", "license": "MIT", "engines": { "node": ">=6.9.0" @@ -210,26 +212,26 @@ } }, "node_modules/@babel/helpers": { - "version": "7.26.9", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.26.9.tgz", - "integrity": "sha512-Mz/4+y8udxBKdmzt/UjPACs4G3j5SshJJEFFKxlCGPydG4JAHXxjWjAwjd09tf6oINvl1VfMJo+nB7H2YKQ0dA==", + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.4.tgz", + "integrity": "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==", "dev": true, "license": "MIT", "dependencies": { - "@babel/template": "^7.26.9", - "@babel/types": "^7.26.9" + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.4" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/parser": { - "version": "7.26.9", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.9.tgz", - "integrity": "sha512-81NWa1njQblgZbQHxWHpxxCzNsa3ZwvFqpUg7P+NNUU6f3UU2jBEg4OlF/J6rl8+PQGh1q6/zWScd001YwcA5A==", + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.4.tgz", + "integrity": "sha512-yZbBqeM6TkpP9du/I2pUZnJsRMGGvOuIrhjzC1AwHwW+6he4mni6Bp/m8ijn0iOuZuPI2BfkCoSRunpyjnrQKg==", "license": "MIT", "dependencies": { - "@babel/types": "^7.26.9" + "@babel/types": "^7.28.4" }, "bin": { "parser": "bin/babel-parser.js" @@ -283,14 +285,14 @@ } }, "node_modules/@babel/template": { - "version": "7.26.9", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.26.9.tgz", - "integrity": "sha512-qyRplbeIpNZhmzOysF/wFMuP9sctmh2cFzRAZOn1YapxBsE1i9bJIY586R/WBLfLcmcBlM8ROBiQURnnNy+zfA==", + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", + "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.26.2", - "@babel/parser": "^7.26.9", - "@babel/types": "^7.26.9" + "@babel/code-frame": "^7.27.1", + "@babel/parser": "^7.27.2", + "@babel/types": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -324,13 +326,13 @@ } }, "node_modules/@babel/types": { - "version": "7.26.9", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.9.tgz", - "integrity": "sha512-Y3IR1cRnOxOCDvMmNiym7XpXQ93iGDDPHx+Zj+NM+rg0fBaShfQLkg+hKPaZCEvg5N/LeCo4+Rj/i3FuJsIQaw==", + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.4.tgz", + "integrity": "sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q==", "license": "MIT", "dependencies": { - "@babel/helper-string-parser": "^7.25.9", - "@babel/helper-validator-identifier": "^7.25.9" + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -914,9 +916,9 @@ } }, "node_modules/@eslint-community/eslint-utils": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.1.tgz", - "integrity": "sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA==", + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz", + "integrity": "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==", "dev": true, "license": "MIT", "dependencies": { @@ -956,9 +958,9 @@ } }, "node_modules/@eslint/config-array": { - "version": "0.19.2", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.19.2.tgz", - "integrity": "sha512-GNKqxfHG2ySmJOBSHg7LxeUx4xpuCoFjacmlCoYWEbaPXLwvfIjixRI12xCQZeULksQb23uiA8F40w5TojpV7w==", + "version": "0.21.0", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.0.tgz", + "integrity": "sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -970,10 +972,20 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, + "node_modules/@eslint/config-helpers": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.3.1.tgz", + "integrity": "sha512-xR93k9WhrDYpXHORXpxVL5oHj3Era7wo6k/Wd8/IsQNnZUTzkGS29lyn3nAT05v6ltUuTFVCCYDEGfy2Or/sPA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, "node_modules/@eslint/core": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.11.0.tgz", - "integrity": "sha512-DWUB2pksgNEb6Bz2fggIy1wh6fGgZP4Xyy/Mt0QZPiloKKXerbqq9D3SBQTlCRYOrcRPu4vuz+CGjwdfqxnoWA==", + "version": "0.15.2", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.15.2.tgz", + "integrity": "sha512-78Md3/Rrxh83gCxoUc0EiciuOHsIITzLy53m3d9UyiW8y9Dj2D29FeETqyKA+BRK76tnTp6RXWb3pCay8Oyomg==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -984,9 +996,9 @@ } }, "node_modules/@eslint/eslintrc": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.2.0.tgz", - "integrity": "sha512-grOjVNN8P3hjJn/eIETF1wwd12DdnwFDoyceUJLYYdkpbwq3nLi+4fqrTAONx7XDALqlL220wC/RHSC/QTI/0w==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz", + "integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==", "dev": true, "license": "MIT", "dependencies": { @@ -1021,13 +1033,16 @@ } }, "node_modules/@eslint/js": { - "version": "9.20.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.20.0.tgz", - "integrity": "sha512-iZA07H9io9Wn836aVTytRaNqh00Sad+EamwOVJT12GTLw1VGMFV/4JaME+JjLtr9fiGaoWgYnS54wrfWsSs4oQ==", + "version": "9.35.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.35.0.tgz", + "integrity": "sha512-30iXE9whjlILfWobBkNerJo+TXYsgVM5ERQwMcMKCHckHflCmf7wXDAHlARoWnh0s1U72WqlbeyE7iAcCzuCPw==", "dev": true, "license": "MIT", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" } }, "node_modules/@eslint/object-schema": { @@ -1041,32 +1056,19 @@ } }, "node_modules/@eslint/plugin-kit": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.5.tgz", - "integrity": "sha512-lB05FkqEdUg2AA0xEbUz0SnkXT1LcCTa438W4IWTUh4hdOnVbQyOJ81OrDXsJk/LSiJHubgGEFoR5EHq1NsH1A==", + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.5.tgz", + "integrity": "sha512-Z5kJ+wU3oA7MMIqVR9tyZRtjYPr4OC004Q4Rw7pgOKUOKkJfZ3O24nz3WYfGRpMDNmcOi3TwQOmgm7B7Tpii0w==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@eslint/core": "^0.10.0", + "@eslint/core": "^0.15.2", "levn": "^0.4.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, - "node_modules/@eslint/plugin-kit/node_modules/@eslint/core": { - "version": "0.10.0", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.10.0.tgz", - "integrity": "sha512-gFHJ+xBOo4G3WRlR1e/3G8A6/KZAH6zcE/hkLRCZTi/B9avAG365QhFA8uOGzTMqgTghpn7/fSnscW++dpMSAw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@types/json-schema": "^7.0.15" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, "node_modules/@fontsource/roboto": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/@fontsource/roboto/-/roboto-5.1.1.tgz", @@ -1126,9 +1128,9 @@ } }, "node_modules/@humanwhocodes/retry": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.1.tgz", - "integrity": "sha512-c7hNEllBlenFTHBky65mhq8WD2kbN9Q6gk0bTk8lSBvc554jpXSkST1iePudpt7+A/AQvuHs9EMqjHDXMY1lrA==", + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", "dev": true, "license": "Apache-2.0", "engines": { @@ -2187,13 +2189,39 @@ "@babel/types": "^7.20.7" } }, + "node_modules/@types/debug": { + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", + "integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==", + "license": "MIT", + "dependencies": { + "@types/ms": "*" + } + }, "node_modules/@types/estree": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz", "integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==", - "dev": true, "license": "MIT" }, + "node_modules/@types/estree-jsx": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree-jsx/-/estree-jsx-1.0.5.tgz", + "integrity": "sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==", + "license": "MIT", + "dependencies": { + "@types/estree": "*" + } + }, + "node_modules/@types/hast": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, "node_modules/@types/json-schema": { "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", @@ -2210,6 +2238,12 @@ "@types/unist": "^2" } }, + "node_modules/@types/ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", + "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", + "license": "MIT" + }, "node_modules/@types/parse-json": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz", @@ -2404,9 +2438,9 @@ } }, "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "dev": true, "license": "MIT", "dependencies": { @@ -2484,6 +2518,12 @@ "url": "https://opencollective.com/typescript-eslint" } }, + "node_modules/@ungap/structured-clone": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", + "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", + "license": "ISC" + }, "node_modules/@vitejs/plugin-react": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.3.4.tgz", @@ -2505,9 +2545,9 @@ } }, "node_modules/acorn": { - "version": "8.14.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", - "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", "bin": { @@ -2600,9 +2640,9 @@ "license": "MIT" }, "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, "license": "MIT", "dependencies": { @@ -2699,6 +2739,16 @@ ], "license": "CC-BY-4.0" }, + "node_modules/ccount": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", + "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -2726,6 +2776,16 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/character-entities-html4": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz", + "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/character-entities-legacy": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-1.1.4.tgz", @@ -2872,6 +2932,29 @@ } } }, + "node_modules/decode-named-character-reference": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.2.0.tgz", + "integrity": "sha512-c6fcElNV6ShtZXmsgNgFFV5tVX2PaV4g+MOAkb8eXHvn6sryJBrZa9r0zV6+dtTyoCKxtDy5tyQ5ZwQuidtd+Q==", + "license": "MIT", + "dependencies": { + "character-entities": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/decode-named-character-reference/node_modules/character-entities": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz", + "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", @@ -2879,6 +2962,28 @@ "dev": true, "license": "MIT" }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/devlop": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", + "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", + "license": "MIT", + "dependencies": { + "dequal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/dom-helpers": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", @@ -2993,22 +3098,23 @@ } }, "node_modules/eslint": { - "version": "9.20.1", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.20.1.tgz", - "integrity": "sha512-m1mM33o6dBUjxl2qb6wv6nGNwCAsns1eKtaQ4l/NPHeTvhiUPbtdfMyktxN4B3fgHIgsYh1VT3V9txblpQHq+g==", + "version": "9.35.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.35.0.tgz", + "integrity": "sha512-QePbBFMJFjgmlE+cXAlbHZbHpdFVS2E/6vzCy7aKlebddvl1vadiC4JFV5u/wqTkNUwEV8WrQi257jf5f06hrg==", "dev": true, "license": "MIT", "dependencies": { - "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", - "@eslint/config-array": "^0.19.0", - "@eslint/core": "^0.11.0", - "@eslint/eslintrc": "^3.2.0", - "@eslint/js": "9.20.0", - "@eslint/plugin-kit": "^0.2.5", + "@eslint/config-array": "^0.21.0", + "@eslint/config-helpers": "^0.3.1", + "@eslint/core": "^0.15.2", + "@eslint/eslintrc": "^3.3.1", + "@eslint/js": "9.35.0", + "@eslint/plugin-kit": "^0.3.5", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", - "@humanwhocodes/retry": "^0.4.1", + "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "@types/json-schema": "^7.0.15", "ajv": "^6.12.4", @@ -3016,9 +3122,9 @@ "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^8.2.0", - "eslint-visitor-keys": "^4.2.0", - "espree": "^10.3.0", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", @@ -3076,9 +3182,9 @@ } }, "node_modules/eslint-scope": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.2.0.tgz", - "integrity": "sha512-PHlWUfG6lvPc3yvP5A4PNyBL1W8fkDUccmI21JUu/+GKZBoH/W5u6usENXUrWFRsyoW5ACUjFGgAFQp5gUlb/A==", + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", "dev": true, "license": "BSD-2-Clause", "dependencies": { @@ -3093,9 +3199,9 @@ } }, "node_modules/eslint-visitor-keys": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", - "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", "dev": true, "license": "Apache-2.0", "engines": { @@ -3106,15 +3212,15 @@ } }, "node_modules/espree": { - "version": "10.3.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz", - "integrity": "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==", + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", "dev": true, "license": "BSD-2-Clause", "dependencies": { - "acorn": "^8.14.0", + "acorn": "^8.15.0", "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^4.2.0" + "eslint-visitor-keys": "^4.2.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3159,6 +3265,16 @@ "node": ">=4.0" } }, + "node_modules/estree-util-is-identifier-name": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/estree-util-is-identifier-name/-/estree-util-is-identifier-name-3.0.0.tgz", + "integrity": "sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/estree-walker": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", @@ -3421,6 +3537,135 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/hast-util-is-element": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-is-element/-/hast-util-is-element-3.0.0.tgz", + "integrity": "sha512-Val9mnv2IWpLbNPqc/pUem+a7Ipj2aHacCwgNfTiK0vJKl0LF+4Ba4+v1oPHFpf3bLYmreq0/l3Gud9S5OH42g==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-jsx-runtime": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.6.tgz", + "integrity": "sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "devlop": "^1.0.0", + "estree-util-is-identifier-name": "^3.0.0", + "hast-util-whitespace": "^3.0.0", + "mdast-util-mdx-expression": "^2.0.0", + "mdast-util-mdx-jsx": "^3.0.0", + "mdast-util-mdxjs-esm": "^2.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0", + "style-to-js": "^1.0.0", + "unist-util-position": "^5.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-jsx-runtime/node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "license": "MIT" + }, + "node_modules/hast-util-to-jsx-runtime/node_modules/comma-separated-tokens": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", + "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/hast-util-to-jsx-runtime/node_modules/property-information": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.1.0.tgz", + "integrity": "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/hast-util-to-jsx-runtime/node_modules/space-separated-tokens": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", + "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/hast-util-to-jsx-runtime/node_modules/unist-util-position": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz", + "integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-jsx-runtime/node_modules/unist-util-stringify-position": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", + "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-jsx-runtime/node_modules/vfile-message": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.3.tgz", + "integrity": "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-whitespace": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz", + "integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/hoist-non-react-statics": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", @@ -3436,6 +3681,16 @@ "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", "license": "MIT" }, + "node_modules/html-url-attributes": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/html-url-attributes/-/html-url-attributes-3.0.1.tgz", + "integrity": "sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/hyphenate-style-name": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/hyphenate-style-name/-/hyphenate-style-name-1.1.0.tgz", @@ -3484,6 +3739,18 @@ "integrity": "sha512-7NXolsK4CAS5+xvdj5OMMbI962hU/wvwoxk+LWR9Ek9bVtyuuYScDN6eS0rUm6TxApFpw7CX1o4uJzcd4AyD3Q==", "license": "MIT" }, + "node_modules/is-absolute-url": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-absolute-url/-/is-absolute-url-4.0.1.tgz", + "integrity": "sha512-/51/TKE88Lmm7Gc4/8btclNXWS+g50wXhYJq8HWIBAGUBnoAdRu1aXeh364t/O7wXDAcTJDP8PNuNKWUDWie+A==", + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-alphabetical": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-1.0.4.tgz", @@ -3841,6 +4108,16 @@ "dev": true, "license": "MIT" }, + "node_modules/longest-streak": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz", + "integrity": "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/loose-envify": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", @@ -3932,6 +4209,511 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/mdast-util-mdx-expression": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-mdx-expression/-/mdast-util-mdx-expression-2.0.1.tgz", + "integrity": "sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx-expression/node_modules/@types/mdast": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", + "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/mdast-util-mdx-expression/node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "license": "MIT" + }, + "node_modules/mdast-util-mdx-expression/node_modules/mdast-util-from-markdown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.2.tgz", + "integrity": "sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark": "^4.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx-expression/node_modules/mdast-util-to-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz", + "integrity": "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx-expression/node_modules/micromark": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.2.tgz", + "integrity": "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "@types/debug": "^4.0.0", + "debug": "^4.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/mdast-util-mdx-expression/node_modules/unist-util-stringify-position": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", + "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx-jsx": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/mdast-util-mdx-jsx/-/mdast-util-mdx-jsx-3.2.0.tgz", + "integrity": "sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "ccount": "^2.0.0", + "devlop": "^1.1.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0", + "parse-entities": "^4.0.0", + "stringify-entities": "^4.0.0", + "unist-util-stringify-position": "^4.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx-jsx/node_modules/@types/mdast": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", + "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/mdast-util-mdx-jsx/node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "license": "MIT" + }, + "node_modules/mdast-util-mdx-jsx/node_modules/character-entities-legacy": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", + "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/mdast-util-mdx-jsx/node_modules/character-reference-invalid": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz", + "integrity": "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/mdast-util-mdx-jsx/node_modules/is-alphabetical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz", + "integrity": "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/mdast-util-mdx-jsx/node_modules/is-alphanumerical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-2.0.1.tgz", + "integrity": "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==", + "license": "MIT", + "dependencies": { + "is-alphabetical": "^2.0.0", + "is-decimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/mdast-util-mdx-jsx/node_modules/is-decimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-2.0.1.tgz", + "integrity": "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/mdast-util-mdx-jsx/node_modules/is-hexadecimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz", + "integrity": "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/mdast-util-mdx-jsx/node_modules/mdast-util-from-markdown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.2.tgz", + "integrity": "sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark": "^4.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx-jsx/node_modules/mdast-util-to-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz", + "integrity": "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx-jsx/node_modules/micromark": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.2.tgz", + "integrity": "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "@types/debug": "^4.0.0", + "debug": "^4.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/mdast-util-mdx-jsx/node_modules/parse-entities": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-4.0.2.tgz", + "integrity": "sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^2.0.0", + "character-entities-legacy": "^3.0.0", + "character-reference-invalid": "^2.0.0", + "decode-named-character-reference": "^1.0.0", + "is-alphanumerical": "^2.0.0", + "is-decimal": "^2.0.0", + "is-hexadecimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/mdast-util-mdx-jsx/node_modules/parse-entities/node_modules/@types/unist": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz", + "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==", + "license": "MIT" + }, + "node_modules/mdast-util-mdx-jsx/node_modules/unist-util-stringify-position": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", + "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx-jsx/node_modules/vfile-message": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.3.tgz", + "integrity": "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdxjs-esm": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-mdxjs-esm/-/mdast-util-mdxjs-esm-2.0.1.tgz", + "integrity": "sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdxjs-esm/node_modules/@types/mdast": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", + "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/mdast-util-mdxjs-esm/node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "license": "MIT" + }, + "node_modules/mdast-util-mdxjs-esm/node_modules/mdast-util-from-markdown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.2.tgz", + "integrity": "sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark": "^4.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdxjs-esm/node_modules/mdast-util-to-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz", + "integrity": "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdxjs-esm/node_modules/micromark": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.2.tgz", + "integrity": "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "@types/debug": "^4.0.0", + "debug": "^4.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/mdast-util-mdxjs-esm/node_modules/unist-util-stringify-position": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", + "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-phrasing": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-4.1.0.tgz", + "integrity": "sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-phrasing/node_modules/@types/mdast": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", + "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/mdast-util-phrasing/node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "license": "MIT" + }, + "node_modules/mdast-util-phrasing/node_modules/unist-util-is": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.0.tgz", + "integrity": "sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/mdast-util-to-hast": { "version": "10.2.0", "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-10.2.0.tgz", @@ -3981,6 +4763,97 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/mdast-util-to-markdown": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.2.tgz", + "integrity": "sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "longest-streak": "^3.0.0", + "mdast-util-phrasing": "^4.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "unist-util-visit": "^5.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-markdown/node_modules/@types/mdast": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", + "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/mdast-util-to-markdown/node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "license": "MIT" + }, + "node_modules/mdast-util-to-markdown/node_modules/mdast-util-to-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz", + "integrity": "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-markdown/node_modules/unist-util-is": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.0.tgz", + "integrity": "sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-markdown/node_modules/unist-util-visit": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.0.0.tgz", + "integrity": "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-markdown/node_modules/unist-util-visit-parents": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.1.tgz", + "integrity": "sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/mdast-util-to-string": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-2.0.0.tgz", @@ -4033,6 +4906,413 @@ "parse-entities": "^2.0.0" } }, + "node_modules/micromark-core-commonmark": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.3.tgz", + "integrity": "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-factory-destination": "^2.0.0", + "micromark-factory-label": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-factory-title": "^2.0.0", + "micromark-factory-whitespace": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-html-tag-name": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-destination": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz", + "integrity": "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-label": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-2.0.1.tgz", + "integrity": "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-space": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-title": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-2.0.1.tgz", + "integrity": "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-whitespace": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.1.tgz", + "integrity": "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-chunked": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz", + "integrity": "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-classify-character": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-2.0.1.tgz", + "integrity": "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-combine-extensions": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.1.tgz", + "integrity": "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-chunked": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-decode-numeric-character-reference": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.2.tgz", + "integrity": "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-decode-string": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-2.0.1.tgz", + "integrity": "sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-encode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz", + "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-html-tag-name": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz", + "integrity": "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-normalize-identifier": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.1.tgz", + "integrity": "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-resolve-all": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.1.tgz", + "integrity": "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-sanitize-uri": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz", + "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-subtokenize": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.1.0.tgz", + "integrity": "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-types": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.2.tgz", + "integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, "node_modules/micromatch": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", @@ -4398,6 +5678,321 @@ "integrity": "sha512-H91OHcwjZsbq3ClIDHMzBShc1rotbfACdWENsmEf0IFvZ3FgGPtdHMcsv45bQ1hAbgdfiA8SnxTKfDS+x/8m2g==", "license": "MIT" }, + "node_modules/react-markdown": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/react-markdown/-/react-markdown-10.1.0.tgz", + "integrity": "sha512-qKxVopLT/TyA6BX3Ue5NwabOsAzm0Q7kAPwq6L+wWDwisYs7R8vZ0nRXqq6rkueboxpkjvLGU9fWifiX/ZZFxQ==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "hast-util-to-jsx-runtime": "^2.0.0", + "html-url-attributes": "^3.0.0", + "mdast-util-to-hast": "^13.0.0", + "remark-parse": "^11.0.0", + "remark-rehype": "^11.0.0", + "unified": "^11.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + }, + "peerDependencies": { + "@types/react": ">=18", + "react": ">=18" + } + }, + "node_modules/react-markdown/node_modules/@types/mdast": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", + "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/react-markdown/node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "license": "MIT" + }, + "node_modules/react-markdown/node_modules/bail": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz", + "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/react-markdown/node_modules/is-plain-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/react-markdown/node_modules/mdast-util-from-markdown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.2.tgz", + "integrity": "sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark": "^4.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/react-markdown/node_modules/mdast-util-to-hast": { + "version": "13.2.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.0.tgz", + "integrity": "sha512-QGYKEuUsYT9ykKBCMOEDLsU5JRObWQusAolFMeko/tYPufNkRffBAQjIE+99jbA87xv6FgmjLtwjh9wBWajwAA==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@ungap/structured-clone": "^1.0.0", + "devlop": "^1.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "trim-lines": "^3.0.0", + "unist-util-position": "^5.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/react-markdown/node_modules/mdast-util-to-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz", + "integrity": "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/react-markdown/node_modules/micromark": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.2.tgz", + "integrity": "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "@types/debug": "^4.0.0", + "debug": "^4.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/react-markdown/node_modules/remark-parse": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-11.0.0.tgz", + "integrity": "sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-from-markdown": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/react-markdown/node_modules/remark-rehype": { + "version": "11.1.2", + "resolved": "https://registry.npmjs.org/remark-rehype/-/remark-rehype-11.1.2.tgz", + "integrity": "sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "mdast-util-to-hast": "^13.0.0", + "unified": "^11.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/react-markdown/node_modules/trough": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/trough/-/trough-2.2.0.tgz", + "integrity": "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/react-markdown/node_modules/unified": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz", + "integrity": "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "bail": "^2.0.0", + "devlop": "^1.0.0", + "extend": "^3.0.0", + "is-plain-obj": "^4.0.0", + "trough": "^2.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/react-markdown/node_modules/unist-util-is": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.0.tgz", + "integrity": "sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/react-markdown/node_modules/unist-util-position": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz", + "integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/react-markdown/node_modules/unist-util-stringify-position": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", + "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/react-markdown/node_modules/unist-util-visit": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.0.0.tgz", + "integrity": "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/react-markdown/node_modules/unist-util-visit-parents": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.1.tgz", + "integrity": "sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/react-markdown/node_modules/vfile": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", + "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/react-markdown/node_modules/vfile-message": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.3.tgz", + "integrity": "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/react-refresh": { "version": "0.14.2", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz", @@ -4479,6 +6074,82 @@ "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", "license": "MIT" }, + "node_modules/rehype-external-links": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/rehype-external-links/-/rehype-external-links-3.0.0.tgz", + "integrity": "sha512-yp+e5N9V3C6bwBeAC4n796kc86M4gJCdlVhiMTxIrJG5UHDMh+PJANf9heqORJbt1nrCbDwIlAZKjANIaVBbvw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@ungap/structured-clone": "^1.0.0", + "hast-util-is-element": "^3.0.0", + "is-absolute-url": "^4.0.0", + "space-separated-tokens": "^2.0.0", + "unist-util-visit": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rehype-external-links/node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "license": "MIT" + }, + "node_modules/rehype-external-links/node_modules/space-separated-tokens": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", + "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/rehype-external-links/node_modules/unist-util-is": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.0.tgz", + "integrity": "sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rehype-external-links/node_modules/unist-util-visit": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.0.0.tgz", + "integrity": "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rehype-external-links/node_modules/unist-util-visit-parents": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.1.tgz", + "integrity": "sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/rehype-react": { "version": "6.2.1", "resolved": "https://registry.npmjs.org/rehype-react/-/rehype-react-6.2.1.tgz", @@ -4702,6 +6373,30 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/stringify-entities": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz", + "integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==", + "license": "MIT", + "dependencies": { + "character-entities-html4": "^2.0.0", + "character-entities-legacy": "^3.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/stringify-entities/node_modules/character-entities-legacy": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", + "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/strip-json-comments": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", @@ -4715,6 +6410,30 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/style-to-js": { + "version": "1.1.17", + "resolved": "https://registry.npmjs.org/style-to-js/-/style-to-js-1.1.17.tgz", + "integrity": "sha512-xQcBGDxJb6jjFCTzvQtfiPn6YvvP2O8U1MDIPNfJQlWMYfktPy+iGsHE7cssjs7y84d9fQaK4UF3RIJaAHSoYA==", + "license": "MIT", + "dependencies": { + "style-to-object": "1.0.9" + } + }, + "node_modules/style-to-js/node_modules/inline-style-parser": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.4.tgz", + "integrity": "sha512-0aO8FkhNZlj/ZIbNi7Lxxr12obT7cL1moPfE4tg1LkX7LlLfC6DeX4l2ZEud1ukP9jNQyNnfzQVqwbwmAATY4Q==", + "license": "MIT" + }, + "node_modules/style-to-js/node_modules/style-to-object": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-1.0.9.tgz", + "integrity": "sha512-G4qppLgKu/k6FwRpHiGiKPaPTFcG3g4wNVX/Qsfu+RqQM30E7Tyu/TEgxcL9PNLF5pdRLwQdE3YKKf+KF2Dzlw==", + "license": "MIT", + "dependencies": { + "inline-style-parser": "0.2.4" + } + }, "node_modules/style-to-object": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-0.3.0.tgz", @@ -4826,6 +6545,16 @@ "node": ">=8.0" } }, + "node_modules/trim-lines": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", + "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/trough": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/trough/-/trough-1.0.5.tgz", @@ -5295,6 +7024,16 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } + }, + "node_modules/zwitch": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", + "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } } } } diff --git a/vite/package.json b/vite/package.json index ff3e4fd..6225727 100644 --- a/vite/package.json +++ b/vite/package.json @@ -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": { diff --git a/vite/src/pipedal/AlsaDeviceInfo.tsx b/vite/src/pipedal/AlsaDeviceInfo.tsx index 0cd10ff..71899b1 100644 --- a/vite/src/pipedal/AlsaDeviceInfo.tsx +++ b/vite/src/pipedal/AlsaDeviceInfo.tsx @@ -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; }; \ No newline at end of file diff --git a/vite/src/pipedal/AlsaMidiDeviceInfo.tsx b/vite/src/pipedal/AlsaMidiDeviceInfo.tsx index 87ab63d..6207ab2 100644 --- a/vite/src/pipedal/AlsaMidiDeviceInfo.tsx +++ b/vite/src/pipedal/AlsaMidiDeviceInfo.tsx @@ -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 diff --git a/vite/src/pipedal/AlsaSequencer.tsx b/vite/src/pipedal/AlsaSequencer.tsx index a49aff0..33922d9 100644 --- a/vite/src/pipedal/AlsaSequencer.tsx +++ b/vite/src/pipedal/AlsaSequencer.tsx @@ -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 diff --git a/vite/src/pipedal/FilePropertyDialog.tsx b/vite/src/pipedal/FilePropertyDialog.tsx index f2a7958..e1d1614 100644 --- a/vite/src/pipedal/FilePropertyDialog.tsx +++ b/vite/src/pipedal/FilePropertyDialog.tsx @@ -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 && ( + this.setState({ textFileName: undefined })} + /> + )} ); } + 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); + } } } diff --git a/vite/src/pipedal/JackHostStatus.tsx b/vite/src/pipedal/JackHostStatus.tsx index 1f5c1f5..67239e9 100644 --- a/vite/src/pipedal/JackHostStatus.tsx +++ b/vite/src/pipedal/JackHostStatus.tsx @@ -189,4 +189,46 @@ export default class JackHostStatus { } } + static getDisplayViewNoCpu(label: string, status?: JackHostStatus): React.ReactNode { + if (!status) { + return (
+ {label} +   +
); + } + if (status.restarting) { + return ( +
+ {label} + + Restarting   + +
+ ); + + } else if (!status.active) { + return ( +
+ {label} + + + {status.errorMessage === "" ? "Audio\u00A0Stopped" : status.errorMessage}   + +
+ ); + } else { + let underrunError = status.msSinceLastUnderrun < 15 * 1000; + return ( +
+ {label} + + + XRuns: {status.underruns + ""}   + + +
+ ); + } + } + }; \ No newline at end of file diff --git a/vite/src/pipedal/JackServerSettings.tsx b/vite/src/pipedal/JackServerSettings.tsx index 9ece894..ab1e1e7 100644 --- a/vite/src/pipedal/JackServerSettings.tsx +++ b/vite/src/pipedal/JackServerSettings.tsx @@ -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}`; } } \ No newline at end of file diff --git a/vite/src/pipedal/JackServerSettingsDialog.tsx b/vite/src/pipedal/JackServerSettingsDialog.tsx index ceff6e9..3bb5e71 100644 --- a/vite/src/pipedal/JackServerSettingsDialog.tsx +++ b/vite/src/pipedal/JackServerSettingsDialog.tsx @@ -18,12 +18,10 @@ // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -import { Component } from 'react'; - import { Theme } from '@mui/material/styles'; import WithStyles from './WithStyles'; -import {createStyles} from './WithStyles'; +import { createStyles } from './WithStyles'; import { withStyles } from "tss-react/mui"; @@ -31,6 +29,7 @@ import Button from '@mui/material/Button'; import DialogActions from '@mui/material/DialogActions'; import DialogEx from './DialogEx'; import JackServerSettings from './JackServerSettings'; +import JackHostStatus from './JackHostStatus'; import InputLabel from '@mui/material/InputLabel'; @@ -40,11 +39,43 @@ import DialogContent from '@mui/material/DialogContent'; import MenuItem from '@mui/material/MenuItem'; import Typography from '@mui/material/Typography'; import { PiPedalModel, PiPedalModelFactory } from './PiPedalModel'; +import IconButtonEx from './IconButtonEx'; +import RefreshIcon from '@mui/icons-material/Refresh'; +import Checkbox from '@mui/material/Checkbox'; +import FormControlLabel from '@mui/material/FormControlLabel'; import AlsaDeviceInfo from './AlsaDeviceInfo'; +import ResizeResponsiveComponent from './ResizeResponsiveComponent'; -const MIN_BUFFER_SIZE = 16; -const MAX_BUFFER_SIZE = 2048; +function filterDevices(devices: AlsaDeviceInfo[]): AlsaDeviceInfo[] { + return devices.filter(d => { + const name = (d.name + ' ' + d.longName).toLowerCase(); + return !(name.includes('hdmi') + || name.includes('bcm2835') // Pi 4 headphones out. + ); + }); +} + +function sortDevices(devices: AlsaDeviceInfo[]): AlsaDeviceInfo[] { + function category(d: AlsaDeviceInfo): number { + if (d.supportsCapture && d.supportsPlayback) return 0; + if (d.longName.toLowerCase().includes("usb")) return 1; + return 2; + } + let copy = [...devices]; + copy.sort((a, b) => { + let ca = category(a); + let cb = category(b); + if (ca !== cb) return ca - cb; + return a.name.localeCompare(b.name); + }); + return copy; +} + +const MIN_BUFFER_SIZE = 16; +const MAX_BUFFER_SIZE = 2048; + +const valid_buffer_sizes = [16, 32, 48, 64, 128, 256, 512, 1024, 2048]; interface BufferSetting { @@ -52,12 +83,25 @@ interface BufferSetting { numberOfBuffers: number; }; -const INVALID_DEVICE_ID = "_invalid_"; +// empty string used when no valid device is selected - the default. +const INVALID_DEVICE_ID = ""; + +enum WarningDialogType { + None, + Apply, + Ok, +}; interface JackServerSettingsDialogState { latencyText: string; jackServerSettings: JackServerSettings; + jackHostStatus?: JackHostStatus; alsaDevices?: AlsaDeviceInfo[]; okEnabled: boolean; + fullScreen: boolean; + compactWidth: boolean; + warningDialogType: WarningDialogType; + suppressDeviceWarning: boolean; + windowSize: { width: number, height: number}; } const styles = (theme: Theme) => @@ -69,6 +113,9 @@ const styles = (theme: Theme) => selectEmpty: { marginTop: theme.spacing(2), }, + inputLabel: { + whiteSpace: "nowrap" + } }); export interface JackServerSettingsDialogProps extends WithStyles { open: boolean; @@ -77,469 +124,685 @@ export interface JackServerSettingsDialogProps extends WithStyles onApply: (jackServerSettings: JackServerSettings) => void; } -function getLatencyText(settings?: JackServerSettings ): string { - if (!settings) - { +function getLatencyText(settings?: JackServerSettings): string { + if (!settings) { return "\u00A0"; } - if (!settings.valid) return "\u00A0"; - let ms = settings.bufferSize * settings.numberOfBuffers / settings.sampleRate * 1000; + if (!settings.sampleRate || !settings.bufferSize || !settings.numberOfBuffers) { + return "\u00A0"; + } + + let ms = (settings.bufferSize * settings.numberOfBuffers) / settings.sampleRate * 1000; return ms.toFixed(1) + "ms"; } -function getValidBufferCounts(bufferSize: number, alsaDeviceInfo?: AlsaDeviceInfo): number[] -{ - if (!alsaDeviceInfo) - { +function getValidBufferCounts(bufferSize: number, alsaDeviceInfo?: AlsaDeviceInfo): number[] { + if (!alsaDeviceInfo) { return []; } let result: number[] = []; - if (bufferSize * 2 >= alsaDeviceInfo.minBufferSize) - { - result = [2,3,4,5,6]; + if (bufferSize * 2 >= alsaDeviceInfo.minBufferSize) { + result = [2, 3, 4, 5, 6]; } else { - let minBuffers = Math.ceil(alsaDeviceInfo.minBufferSize/bufferSize/2-0.0001); - result = [minBuffers*2,minBuffers*3, minBuffers*4, minBuffers*5, minBuffers*6]; + let minBuffers = Math.ceil(alsaDeviceInfo.minBufferSize / bufferSize / 2 - 0.0001); + result = [minBuffers * 2, minBuffers * 3, minBuffers * 4, minBuffers * 5, minBuffers * 6]; } - for (let i = 0; i < result.length; ++i) - { - let selectedSize = result[i]*bufferSize; - if (selectedSize < alsaDeviceInfo.minBufferSize || selectedSize > alsaDeviceInfo.maxBufferSize) - { - result.splice(i,1); + for (let i = 0; i < result.length; ++i) { + let selectedSize = result[i] * bufferSize; + if (selectedSize < alsaDeviceInfo.minBufferSize || selectedSize > alsaDeviceInfo.maxBufferSize) { + result.splice(i, 1); --i; } } return result; } -function getValidBufferSizes(alsaDeviceInfo? : AlsaDeviceInfo): number[] -{ - if (!alsaDeviceInfo ) - { + +function intersectArrays(a: number[], b: number[]): number[] { + return a.filter((v) => b.indexOf(v) !== -1); +} + +function getValidBufferCountsMultiple(bufferSize: number, inDevice?: AlsaDeviceInfo, outDevice?: AlsaDeviceInfo): number[] { + let c1 = getValidBufferCounts(bufferSize, inDevice); + if (!outDevice) return c1; + return intersectArrays(c1, getValidBufferCounts(bufferSize, outDevice)); +} + +function getValidBufferSizesMultiple(inDevice?: AlsaDeviceInfo, outDevice?: AlsaDeviceInfo): number[] { + let s1 = getValidBufferSizes(inDevice); + if (!outDevice) return s1; + return intersectArrays(s1, getValidBufferSizes(outDevice)); +} +function getValidBufferSizes(alsaDeviceInfo?: AlsaDeviceInfo): number[] { + if (!alsaDeviceInfo) { return []; } let result: number[] = []; - for (let i = MIN_BUFFER_SIZE; i <= MAX_BUFFER_SIZE; i *= 2) - { - if (getValidBufferCounts(i,alsaDeviceInfo).length !== 0) - { - result.push(i); + for (let i = 0; i < valid_buffer_sizes.length; i++) { + let v = valid_buffer_sizes[i]; + if (getValidBufferCounts(v, alsaDeviceInfo).length !== 0) { + result.push(v); } } return result; } function getBestBuffers( - alsaDeviceInfo: AlsaDeviceInfo | undefined, - bufferSize: number, + inDevice: AlsaDeviceInfo | undefined, + outDevice: AlsaDeviceInfo | undefined, + bufferSize: number, numberOfBuffers: number, bufferSizeChanging: boolean = false -) : BufferSetting { - if (!alsaDeviceInfo) - { - return { bufferSize:bufferSize, numberOfBuffers: numberOfBuffers}; +): BufferSetting { + if (!inDevice && !outDevice) { + return { bufferSize: bufferSize, numberOfBuffers: numberOfBuffers }; } // If the numberOfbuffers is fine as is, don't change the number of Buffers. // set default values. Otherwise, choose the best buffer count from available buffer counts. - let validBuffercounts = getValidBufferCounts(bufferSize,alsaDeviceInfo); + let validBuffercounts = getValidBufferCountsMultiple(bufferSize, inDevice, outDevice); + const minSize = Math.max(inDevice?.minBufferSize ?? MIN_BUFFER_SIZE, outDevice?.minBufferSize ?? MIN_BUFFER_SIZE); + const maxSize = Math.min(inDevice?.maxBufferSize ?? MAX_BUFFER_SIZE, outDevice?.maxBufferSize ?? MAX_BUFFER_SIZE); let ix = validBuffercounts.indexOf(numberOfBuffers); if (ix !== -1) { - if (bufferSize*numberOfBuffers <= alsaDeviceInfo.maxBufferSize - && bufferSize*numberOfBuffers >= alsaDeviceInfo.minBufferSize) - { - return {bufferSize: bufferSize, numberOfBuffers: numberOfBuffers}; + if (bufferSize * numberOfBuffers <= maxSize + && bufferSize * numberOfBuffers >= minSize) { + return { bufferSize: bufferSize, numberOfBuffers: numberOfBuffers }; } } // if numberOfBuffers is not valid, and the user has just selected a new buffers size, // then choose from available buffer counts. - if (bufferSizeChanging) - { - if (validBuffercounts.length !== 0) - { + if (bufferSizeChanging) { + if (validBuffercounts.length !== 0) { // prefer the one thats divisible by 3. - for (let i = 0; i < validBuffercounts.length; ++i) - { - if (validBuffercounts[i] % 3 === 0) - { + for (let i = 0; i < validBuffercounts.length; ++i) { + if (validBuffercounts[i] % 3 === 0) { numberOfBuffers = validBuffercounts[i]; - return {bufferSize: bufferSize, numberOfBuffers:numberOfBuffers}; + return { bufferSize: bufferSize, numberOfBuffers: numberOfBuffers }; } } numberOfBuffers = validBuffercounts[0]; - return {bufferSize: bufferSize, numberOfBuffers:numberOfBuffers}; + return { bufferSize: bufferSize, numberOfBuffers: numberOfBuffers }; } } // otherwise select a sensible starting value. - // favored default: 64x3. - if (64*3 >= alsaDeviceInfo.minBufferSize && 64*3 <= alsaDeviceInfo.maxBufferSize) - { - return { bufferSize: 64, numberOfBuffers: 3}; + // Build candidate sizes with 32 first if present. + let validBufferSizes = getValidBufferSizesMultiple(inDevice, outDevice); + validBufferSizes.sort((a, b) => a - b); + if (validBufferSizes.indexOf(32) !== -1) { + validBufferSizes = [32, ...validBufferSizes.filter(v => v !== 32)]; } - // if that isn't possible then minBufferSize/2 x 4. - bufferSize = alsaDeviceInfo.minBufferSize/2; + + function tryPick(count: number): BufferSetting | undefined { + for (let bs of validBufferSizes) { + const counts = getValidBufferCountsMultiple(bs, inDevice, outDevice); + if (counts.indexOf(count) !== -1) { + if (bs * count >= minSize && bs * count <= maxSize) { + return { bufferSize: bs, numberOfBuffers: count }; + } + } + } + return undefined; + } + + let result = tryPick(4); + if (!result) result = tryPick(3); + if (!result) result = tryPick(2); + if (result) return result; + + // Fallback to previous behaviour. + if (64 * 3 >= minSize && 64 * 3 <= maxSize) { + return { bufferSize: 64, numberOfBuffers: 3 }; + } + bufferSize = minSize / 2; numberOfBuffers = 4; - // otherwise, minBufferSize/2 x 2. - if (bufferSize*numberOfBuffers > alsaDeviceInfo.maxBufferSize) - { + if (bufferSize * numberOfBuffers > maxSize) { numberOfBuffers = 2; } - return { bufferSize: bufferSize, numberOfBuffers: numberOfBuffers}; + return { bufferSize: bufferSize, numberOfBuffers: numberOfBuffers }; }; -function isOkEnabled(jackServerSettings: JackServerSettings, alsaDevices?: AlsaDeviceInfo[]) -{ - if (!jackServerSettings.valid) return false; +function isOkEnabled(jackServerSettings: JackServerSettings, alsaDevices?: AlsaDeviceInfo[]) { if (!alsaDevices) return false; - let alsaDevice: AlsaDeviceInfo | undefined = undefined; - for (let i = 0; i < alsaDevices.length; ++i) - { - if (alsaDevices[i].id === jackServerSettings.alsaDevice) - { - alsaDevice = alsaDevices[i]; - } - } - if (!alsaDevice) - { - return false; - } - let deviceBufferSize = jackServerSettings.bufferSize * jackServerSettings.numberOfBuffers; - if (deviceBufferSize < alsaDevice.minBufferSize || deviceBufferSize > alsaDevice.maxBufferSize) - { - return false; - } - let validBufferCounts = getValidBufferCounts(jackServerSettings.bufferSize, alsaDevice); - let ix = validBufferCounts.indexOf(jackServerSettings.numberOfBuffers); - if (ix === -1) - { - return false; - } + if (!jackServerSettings.alsaInputDevice || !jackServerSettings.alsaOutputDevice) return false; + + const inDevice = alsaDevices.find(d => d.id === jackServerSettings.alsaInputDevice && d.supportsCapture); + const outDevice = alsaDevices.find(d => d.id === jackServerSettings.alsaOutputDevice && d.supportsPlayback); + if (!inDevice || !outDevice) return false; + + const sampleRates = intersectArrays(inDevice.sampleRates, outDevice.sampleRates); + if (sampleRates.indexOf(jackServerSettings.sampleRate) === -1) return false; + + const deviceBufferSize = jackServerSettings.bufferSize * jackServerSettings.numberOfBuffers; + const minSize = Math.max(inDevice.minBufferSize, outDevice.minBufferSize); + const maxSize = Math.min(inDevice.maxBufferSize, outDevice.maxBufferSize); + if (deviceBufferSize < minSize || deviceBufferSize > maxSize) return false; + + const validBufferCounts = getValidBufferCountsMultiple(jackServerSettings.bufferSize, inDevice, outDevice); + if (validBufferCounts.indexOf(jackServerSettings.numberOfBuffers) === -1) return false; + return true; } const JackServerSettingsDialog = withStyles( - class extends Component { + class extends ResizeResponsiveComponent { model: PiPedalModel; constructor(props: JackServerSettingsDialogProps) { super(props); this.model = PiPedalModelFactory.getInstance(); - + + let suppressWarnings = true; //localStorage.getItem("suppressSeparateDeviceWarning") === "1"; this.state = { latencyText: getLatencyText(props.jackServerSettings), jackServerSettings: props.jackServerSettings.clone(), // invalid, but not nullish alsaDevices: undefined, - okEnabled: false + okEnabled: false, + fullScreen: this.getFullScreen(), + compactWidth: this.windowSize.width < 600, + warningDialogType: WarningDialogType.None, + suppressDeviceWarning: suppressWarnings, + windowSize: this.windowSize, + + jackHostStatus: undefined }; } mounted: boolean = false; + + getFullScreen() { + return false; + // return document.documentElement.clientWidth < 420 || + // document.documentElement.clientHeight < 700; + } + + onWindowSizeChanged(width: number, height: number): void { + // super.onWindowSizeChanged(width, height); + // const fullScreen = this.getFullScreen(); + const compactWidth = width < 600; + this.setState({ compactWidth: compactWidth, windowSize: { width: width, height: height } }); + } + requestAlsaInfo() { this.model.getAlsaDevices() .then((devices) => { if (this.mounted) { if (this.props.open) { - let settings = this.applyAlsaDevices(this.props.jackServerSettings, devices); + let f = filterDevices(devices); + let settings = this.applyAlsaDevices(this.state.jackServerSettings, f); this.setState({ - alsaDevices: devices, + alsaDevices: f, jackServerSettings: settings, latencyText: getLatencyText(settings), - okEnabled: isOkEnabled(settings,devices) + okEnabled: isOkEnabled(settings, f) }); } else { - this.setState({ alsaDevices: devices }); + this.setState({ alsaDevices: filterDevices(devices) }); } } }) .catch((error) => { - + console.log("Failed to get ALSA device info: " + error.message); }); } applyAlsaDevices(jackServerSettings: JackServerSettings, alsaDevices?: AlsaDeviceInfo[]) { let result = jackServerSettings.clone(); - if (!alsaDevices) { - return result; - } - result.valid = false; - if (alsaDevices.length === 0) { - result.alsaDevice = INVALID_DEVICE_ID; + if (!alsaDevices || alsaDevices.length === 0) { + result.valid = false; + result.alsaInputDevice = INVALID_DEVICE_ID; + result.alsaOutputDevice = INVALID_DEVICE_ID; return result; } - let selectedDevice: AlsaDeviceInfo | undefined = undefined; - for (let i = 0; i < alsaDevices.length; ++i) { - if (alsaDevices[i].id === result.alsaDevice) { - selectedDevice = alsaDevices[i] - break; + let devices = filterDevices(alsaDevices); + let inDevice = devices.find(d => d.id === result.alsaInputDevice && d.supportsCapture); + let outDevice = devices.find(d => d.id === result.alsaOutputDevice && d.supportsPlayback); + + if (!inDevice || !outDevice) { + const capture = devices.filter(d => d.supportsCapture); + const playback = devices.filter(d => d.supportsPlayback); + if (capture.length === 1 && playback.length === 1) { + inDevice = capture[0]; + outDevice = playback[0]; + result.alsaInputDevice = inDevice.id; + result.alsaOutputDevice = outDevice.id; + } else if (capture.length === 1 && capture[0].supportsPlayback && playback.length === 0) { + inDevice = capture[0]; + outDevice = capture[0]; + result.alsaInputDevice = inDevice.id; + result.alsaOutputDevice = outDevice.id; } } - if (!selectedDevice) { - selectedDevice = alsaDevices[0]; - result.alsaDevice = selectedDevice.id; + if (!inDevice || !outDevice) { + result.valid = false; + if (!inDevice) result.alsaInputDevice = INVALID_DEVICE_ID; + if (!outDevice) result.alsaOutputDevice = INVALID_DEVICE_ID; + return result; } - if (result.sampleRate === 0) result.sampleRate = 48000; - result.sampleRate = selectedDevice.closestSampleRate(result.sampleRate); - let bestBuffers = getBestBuffers(selectedDevice,result.bufferSize,result.numberOfBuffers); + let sampleRates = intersectArrays(inDevice.sampleRates, outDevice.sampleRates); + if (sampleRates.length !== 0 && sampleRates.indexOf(result.sampleRate) === -1) { + let bestSr = sampleRates[0]; + let bestErr = 1e36; + for (let sr of sampleRates) { + let err = (sr - result.sampleRate) * (sr - result.sampleRate); + if (err < bestErr) { bestErr = err; bestSr = sr; } + } + result.sampleRate = bestSr; + } + + let bestBuffers = getBestBuffers(inDevice, outDevice, result.bufferSize, result.numberOfBuffers); result.bufferSize = bestBuffers.bufferSize; result.numberOfBuffers = bestBuffers.numberOfBuffers; - - result.valid = true; return result; } + + tick() { + this.model.getJackStatus() + .then((jackStatus) => + this.setState( + { + jackHostStatus: jackStatus + }) + ) + .catch((error) => { }); + } + componentDidMount() { + super.componentDidMount(); this.mounted = true; - if (this.props.open) { - this.requestAlsaInfo(); - } + + this.updateActive(); } - componentDidUpdate(oldProps: JackServerSettingsDialogProps) { - if ((this.props.open && !oldProps.open) && this.mounted) { - let settings = this.applyAlsaDevices(this.props.jackServerSettings.clone(), this.state.alsaDevices); - this.setState({ - jackServerSettings: settings, - latencyText: getLatencyText(settings), - okEnabled: isOkEnabled(settings,this.state.alsaDevices) - }); - if (!this.state.alsaDevices) { + + private active: boolean = false; + private timerHandle: number | null = null; + + updateActive() { + let active = this.mounted && this.props.open; + if (active !== this.active) { + this.active = active; + if (this.active) { + + + this.setState({ + jackServerSettings: this.props.jackServerSettings.clone(), + alsaDevices: undefined, + latencyText: "", + jackHostStatus: undefined, + okEnabled: false + }); this.requestAlsaInfo(); + + this.timerHandle = setInterval(() => this.tick(), 1000); + + } else { + if (this.timerHandle !== null) { + clearInterval(this.timerHandle); + this.timerHandle = null; + } } + } } + + componentDidUpdate(oldProps: JackServerSettingsDialogProps) { + + this.updateActive(); + } + + componentWillUnmount() { + super.componentWillUnmount(); this.mounted = false; + this.updateActive(); + } - getSelectedDevice(deviceId: string): AlsaDeviceInfo | null { - if (!this.state.alsaDevices) return null; + + getSelectedDevice(deviceId: string): AlsaDeviceInfo | undefined { + if (!this.state.alsaDevices) return undefined; for (let i = 0; i < this.state.alsaDevices.length; ++i) { if (this.state.alsaDevices[i].id === deviceId) { return this.state.alsaDevices[i]; } } - return null; + return undefined; } - handleDeviceChanged(e: any) { - let device = e.target.value as string; - - let selectedDevice = this.getSelectedDevice(device); - if (!selectedDevice) return; - let settings = this.state.jackServerSettings.clone(); - settings.alsaDevice = device; - settings = this.applyAlsaDevices(settings,this.state.alsaDevices); - - this.setState({ - jackServerSettings: settings, - latencyText: getLatencyText(settings), - okEnabled: isOkEnabled(settings,this.state.alsaDevices) - }); - } handleRateChanged(e: any) { let rate = e.target.value as number; - console.log("New rate: " + rate); - let selectedDevice = this.getSelectedDevice(this.state.jackServerSettings.alsaDevice); - if (!selectedDevice) return; + let inDev = this.getSelectedDevice(this.state.jackServerSettings.alsaInputDevice); + let outDev = this.getSelectedDevice(this.state.jackServerSettings.alsaOutputDevice); + if (!inDev && !outDev) return; let settings = this.state.jackServerSettings.clone(); settings.sampleRate = rate; + settings.valid = false; this.setState({ jackServerSettings: settings, latencyText: getLatencyText(settings), - okEnabled: isOkEnabled(settings,this.state.alsaDevices) + okEnabled: isOkEnabled(settings, this.state.alsaDevices) }); } handleSizeChanged(e: any) { let size = e.target.value as number; - let selectedDevice = this.getSelectedDevice(this.state.jackServerSettings.alsaDevice); - if (!selectedDevice) return; + let inDev = this.getSelectedDevice(this.state.jackServerSettings.alsaInputDevice); + let outDev = this.getSelectedDevice(this.state.jackServerSettings.alsaOutputDevice); + if (!inDev && !outDev) return; let settings = this.state.jackServerSettings.clone(); settings.bufferSize = size; - let bestBufferSetting = getBestBuffers(selectedDevice,settings.bufferSize,settings.numberOfBuffers,true); + settings.valid = false; + let bestBufferSetting = getBestBuffers(inDev, outDev, settings.bufferSize, settings.numberOfBuffers, true); settings.bufferSize = bestBufferSetting.bufferSize; settings.numberOfBuffers = bestBufferSetting.numberOfBuffers; this.setState({ jackServerSettings: settings, latencyText: getLatencyText(settings), - okEnabled: isOkEnabled(settings,this.state.alsaDevices) + okEnabled: isOkEnabled(settings, this.state.alsaDevices) }); } handleNumberOfBuffersChanged(e: any) { let bufferCount = e.target.value as number; - let selectedDevice = this.getSelectedDevice(this.state.jackServerSettings.alsaDevice); - if (!selectedDevice) return; + let inDev = this.getSelectedDevice(this.state.jackServerSettings.alsaInputDevice); + let outDev = this.getSelectedDevice(this.state.jackServerSettings.alsaOutputDevice); + if (!inDev && !outDev) return; let settings = this.state.jackServerSettings.clone(); settings.numberOfBuffers = bufferCount; + settings.valid = false; this.setState({ jackServerSettings: settings, latencyText: getLatencyText(settings), - okEnabled: isOkEnabled(settings,this.state.alsaDevices) + okEnabled: isOkEnabled(settings, this.state.alsaDevices) }); } + applySettings() { + const settings = this.state.jackServerSettings.clone(); + settings.valid = true; + this.props.onApply(settings); + + } handleApply() { - if (this.state.okEnabled) - { - this.props.onApply(this.state.jackServerSettings.clone()); + if (!this.state.okEnabled) return; + + if (this.state.jackServerSettings.alsaInputDevice !== this.state.jackServerSettings.alsaOutputDevice + && this.state.suppressDeviceWarning === false) { + this.setState({ warningDialogType: WarningDialogType.Apply }); + } else { + this.applySettings(); + }; + } + + handleOk() { + if (!this.state.okEnabled) return; + + if (this.state.jackServerSettings.alsaInputDevice !== this.state.jackServerSettings.alsaOutputDevice + && this.state.suppressDeviceWarning === false) { + this.setState({ warningDialogType: WarningDialogType.Ok }); + } else { + this.applySettings(); + this.props.onClose(); // Close the dialog } }; + handleWarningProceed() { + this.setState({ warningDialogType: WarningDialogType.None }); + + if (this.state.warningDialogType === WarningDialogType.Ok) { + this.applySettings(); + this.props.onClose(); // Close the dialog after the warning + } else if (this.state.warningDialogType === WarningDialogType.Apply) { + this.applySettings(); + } + } + + handleWarningCancel() { + this.setState({ warningDialogType: WarningDialogType.None }); + } + + handleWarningCheck(e: any, checked: boolean) { + this.setState({ suppressDeviceWarning: checked }); + localStorage.setItem("suppressSeparateDeviceWarning", checked ? "1" : "0"); + } + handleInputDeviceChanged(e: any) { + const d = e.target.value as string; + let s = this.state.jackServerSettings.clone(); + s.alsaInputDevice = d; + s.valid = false; + let settings = this.applyAlsaDevices(s, this.state.alsaDevices); + this.setState({ + jackServerSettings: settings, + latencyText: getLatencyText(settings), + okEnabled: isOkEnabled(settings, this.state.alsaDevices) + }); + } + + handleOutputDeviceChanged(e: any) { + const d = e.target.value as string; + let s = this.state.jackServerSettings.clone(); + s.alsaOutputDevice = d; + s.valid = false; + let settings = this.applyAlsaDevices(s, this.state.alsaDevices); + this.setState({ + jackServerSettings: settings, + latencyText: getLatencyText(settings), + okEnabled: isOkEnabled(settings, this.state.alsaDevices) + }); + } + + handleClose() { + this.props.onClose(); + }; + render() { const classes = withStyles.getClasses(this.props); - const { onClose, open } = this.props; - const handleClose = () => { - onClose(); - }; - let waitingForDevices = !this.state.alsaDevices - let noDevices = this.state.alsaDevices && this.state.alsaDevices.length === 0; - let selectedDevice: AlsaDeviceInfo | undefined = undefined; - if (this.state.alsaDevices) { - for (let device of this.state.alsaDevices) { - if (device.id === this.state.jackServerSettings.alsaDevice) { - selectedDevice = device; - break; - } - } - } - let bufferSizes: number[] = getValidBufferSizes(selectedDevice); - let bufferCounts = getValidBufferCounts(this.state.jackServerSettings.bufferSize,selectedDevice); - let bufferSizeDisabled = !selectedDevice; - let bufferCountDisabled = !selectedDevice; + + const sortedDevices = sortDevices(this.state.alsaDevices ?? []); + + let selectedInputDevice = this.getSelectedDevice(this.state.jackServerSettings.alsaInputDevice); + let selectedOutputDevice = this.getSelectedDevice(this.state.jackServerSettings.alsaOutputDevice); + + const devicesSelected = (selectedInputDevice && selectedOutputDevice); + + let bufferSizes: number[] = devicesSelected ? + getValidBufferSizesMultiple(selectedInputDevice, selectedOutputDevice) : [this.state.jackServerSettings.bufferSize]; + let bufferCounts = devicesSelected ? + getValidBufferCountsMultiple(this.state.jackServerSettings.bufferSize, selectedInputDevice, selectedOutputDevice) : [this.state.jackServerSettings.numberOfBuffers ]; + let bufferSizeDisabled = !devicesSelected; + let bufferCountDisabled = !devicesSelected; + let sampleRates = devicesSelected && selectedInputDevice && selectedOutputDevice ? + intersectArrays(selectedInputDevice.sampleRates, selectedOutputDevice.sampleRates) + : []; + let sampleRateOptions = sampleRates.length === 0 && this.state.jackServerSettings.sampleRate ? [this.state.jackServerSettings.sampleRate] : sampleRates; return ( - { - this.handleApply(); - }} + <> + { this.handleClose(); }} aria-labelledby="select-channels-title" open={this.props.open} + onEnterKey={() => { + this.handleOk(); + }} + fullScreen={this.state.fullScreen} - > - -
- - Device - this.handleInputDeviceChanged(e)} + disabled={!this.state.alsaDevices || this.state.alsaDevices.length === 0} + style={{ width: 220 }} + > + {sortedDevices.filter(d => + (d.supportsCapture || d.captureBusy) + ).map(d => ( + {d.name} + ))} + + - ) - )} - - -
- - Sample rate - - -
- - Buffer size + {/* Audio Output Device */} + + Output Device + + +
+ this.requestAlsaInfo()} aria-label="refresh-audio-devices"> + + +
+
+ + Sample rate - - Buffers - this.handleSizeChanged(e)} + value={this.state.jackServerSettings.bufferSize} + disabled={bufferSizeDisabled} + inputProps={{ + name: 'Buffer size', + id: 'jsd_bufferSize', + }} + > + {bufferSizes.map((buffSize) => + ( + {buffSize} + + ) + )} + + + + Buffers + - + ); + }) + } + + +
- - - Latency: {this.state.latencyText} - -
+ + Latency: {this.state.latencyText} + + + { + JackHostStatus.getDisplayView("Status: ", this.state.jackHostStatus) + } + - - - - + -
+ + + + + + +
+ {this.state.warningDialogType !== WarningDialogType.None && ( + this.handleWarningProceed()} + onClose={() => this.handleWarningCancel()} + style={{ userSelect: "none" }} + > + + + For best results, you should the same device for both audio input and output. + Are you sure you want to continue? + + this.handleWarningCheck(e, c)} />} + label="Don't show me this message again" + /> + + + + + + + )} + ); } }, diff --git a/vite/src/pipedal/SettingsDialog.tsx b/vite/src/pipedal/SettingsDialog.tsx index cef8440..b7f2a5f 100644 --- a/vite/src/pipedal/SettingsDialog.tsx +++ b/vite/src/pipedal/SettingsDialog.tsx @@ -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) ? (
- Status: Not configured. + Status: + Not configured. {(!this.props.onboarding) && ( Governor: )} @@ -702,7 +705,6 @@ const SettingsDialog = withStyles( onClose={() => this.setState({ showJackServerSettingsDialog: false })} onApply={(jackServerSettings) => { this.setState({ - showJackServerSettingsDialog: false, jackServerSettings: jackServerSettings }); this.model.setJackServerSettings(jackServerSettings); diff --git a/vite/src/pipedal/TextInfoDialog.tsx b/vite/src/pipedal/TextInfoDialog.tsx new file mode 100644 index 0000000..694ffb0 --- /dev/null +++ b/vite/src/pipedal/TextInfoDialog.tsx @@ -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 +) { + return (); +}); + + +const TextInfoDialog = class extends ResizeResponsiveComponent { + + model: PiPedalModel; + refNotices: React.RefObject; + + + 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, prevState: Readonly, snapshot: any): void { + super.componentDidUpdate?.(prevProps, prevState, snapshot); + } + + handleDialogClose(_e: SyntheticEvent) { + this.props.onClose(); + } + + + render() { + return ( + { this.props.onClose() }} TransitionComponent={Transition} + onEnterKey={() => { this.props.onClose() }} + style={{ userSelect: "none" }} + > + +
+
+ + + + + + + {this.props.title} + + + +
+
+
+
+
+ + {this.state.textFileContent} + +
+
+
+
+
+ + + ); + + } + +}; + + +export default TextInfoDialog; \ No newline at end of file diff --git a/vite/src/pipedal/ToolTipEx.tsx b/vite/src/pipedal/ToolTipEx.tsx index 95590d1..d451fb2 100644 --- a/vite/src/pipedal/ToolTipEx.tsx +++ b/vite/src/pipedal/ToolTipEx.tsx @@ -31,13 +31,13 @@ export interface ToolTipExProps extends React.ComponentProps { /* 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(0); let [timeoutInstance, setTimeoutInstance] = React.useState(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) { 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) { 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], + }, + }, + ], + } }} - /> + />
) }