diff --git a/.vscode/launch.json b/.vscode/launch.json
index 300d90f..de4a777 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -114,7 +114,8 @@
{
"name": "LD_LIBRARY_PATH",
"value": "/usr/local/lib/aarch64-linux-gnu"
- }
+ },
+
],
"externalConsole": false,
"MIMode": "gdb",
diff --git a/.vscode/settings.json b/.vscode/settings.json
index c4e2792..d95b5fd 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -114,11 +114,14 @@
"Bassman",
"Chowdhury",
"Chowdhury's",
+ "datasheet",
+ "datasheets",
"discursus",
"distro",
"distros",
"dpkg",
"flac",
+ "Focusrite",
"Freeverb",
"Guitarix",
"gxplugins",
@@ -150,7 +153,9 @@
"pipedalui",
"rdfs",
"RTAS",
+ "Scarlett",
"setlist",
+ "Sonus",
"stompboxes",
"ToobAmp",
"toolsets",
@@ -163,7 +168,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 9864b62..6faa786 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,13 +1,13 @@
cmake_minimum_required(VERSION 3.16.0)
project(pipedal
- VERSION 1.4.79
+ VERSION 1.4.86
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.4.79-Beta")
+set (DISPLAY_VERSION "PiPedal v1.4.86-Experimental")
set (PACKAGE_ARCHITECTURE ${DEBIAN_ARCHITECTURE})
set (CMAKE_INSTALL_PREFIX "/usr/")
@@ -96,7 +96,7 @@ set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "IoT guitar effect pedal for Raspberry Pi"
set(CPACK_DEBIAN_PACKAGE_SECTION sound)
set(CPACK_DEBIAN_PACKAGE_SHLIBDEPS ON)
set(CPACK_DEBIAN_PACKAGE_CONTROL_STRICT_PERMISSION TRUE)
-set(CPACK_DEBIAN_PACKAGE_DEPENDS "lv2-dev, ffmpeg, authbind, curl, gpg, alsa-base| pipewire, alsa-utils" )
+set(CPACK_DEBIAN_PACKAGE_DEPENDS "libicu-dev ,lv2-dev, ffmpeg, authbind, curl, gpg, alsa-base| pipewire, alsa-utils" )
set(CPACK_DEBIAN_PACKAGE_ARCHITECTURE ${DEBIAN_ARCHITECTURE})
set(CPACK_PACKAGING_INSTALL_PREFIX /usr)
set(CPACK_PROJECT_NAME ${PROJECT_NAME})
diff --git a/PiPedalCommon/src/AlsaSequencer.cpp b/PiPedalCommon/src/AlsaSequencer.cpp
index 7506e02..4e21ee4 100644
--- a/PiPedalCommon/src/AlsaSequencer.cpp
+++ b/PiPedalCommon/src/AlsaSequencer.cpp
@@ -259,6 +259,8 @@ namespace pipedal
// Open sequencer in input mode
int rc;
+ Lv2Log::debug("Opening ALSA Sequencer");
+
rc = snd_seq_open(&seqHandle, "default", SND_SEQ_OPEN_DUPLEX, 0);
if (rc < 0)
{
diff --git a/PiPedalCommon/src/include/restrict.hpp b/PiPedalCommon/src/include/restrict.hpp
new file mode 100644
index 0000000..3cf0756
--- /dev/null
+++ b/PiPedalCommon/src/include/restrict.hpp
@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) 2022 Robin E. R. Davies
+ * All rights reserved.
+
+ * 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.
+ */
+#pragma once
+
+
+// define restrict keyword for C++ compilers (which don't yet support it)
+#ifndef restrict
+#if defined(__GNUC__) || defined(__clang__)
+#define restrict __restrict__
+#elif defined(_MSC_VER)
+#define restrict __restrict
+#endif
+#endif
\ No newline at end of file
diff --git a/README.md b/README.md
index 4645e27..f0305c1 100644
--- a/README.md
+++ b/README.md
@@ -9,11 +9,11 @@
-Download: v1.4.79
+Download: v1.4.86
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.4.79 Release. See the [release notes](https://rerdavies.github.io/pipedal/ReleaseNotes) for details. New Phaser and Graphic Equalizer plugins.
+#### NEW version 1.4.86 Release. See the [release notes](https://rerdavies.github.io/pipedal/ReleaseNotes) for details. New Phaser and Graphic Equalizer plugins.
@@ -57,13 +57,17 @@ https://github.com/user-attachments/assets/9a9fd0c6-78fc-4284-8b44-6a1929c00cc6
### [Command-Line Configuration of PiPedal](https://rerdavies.github.io/pipedal/CommandLine.html)
### [Changing the Web Server Port](https://rerdavies.github.io/pipedal/ChangingTheWebServerPort.html)
-%nbsp;
+
### [Using LV2 Audio Plugins](https://rerdavies.github.io/pipedal/UsingLv2Plugins.md)
### [Which LV2 Plugins does PiPedal support?](https://rerdavies.github.io/pipedal/WhichLv2PluginsAreSupported.html)
### [Support for LV2 Plugins with MOD User Interfaces](https://rerdavies.github.io/pipedal/ModUiSupport.html)
-
+
+
+### [Frequently Asked Questions](https://rerdavies.github.io/pipedal/FAQ.html)
+
+
### [Building PiPedal from Source](https://rerdavies.github.io/pipedal/BuildingPiPedalFromSource.html)
### [Build Prerequisites](https://rerdavies.github.io/pipedal/BuildPrerequisites.html)
diff --git a/artifacts/fx_nam.svgz b/artifacts/fx_nam.svgz
new file mode 100644
index 0000000..627571a
Binary files /dev/null and b/artifacts/fx_nam.svgz differ
diff --git a/docs/BuildingPiPedalFromSource.md b/docs/BuildingPiPedalFromSource.md
index f6d64bb..2c17acf 100644
--- a/docs/BuildingPiPedalFromSource.md
+++ b/docs/BuildingPiPedalFromSource.md
@@ -16,4 +16,4 @@ As a last resort, you be able to configure VSCode and/or CMake to cross-compile
--------
-[<< Which LV2 Plugins Does PiPedal Support?](WhichLv2PluginsAreSupported.md) | [Up](Documentation.md) | [Build Prerequisites >>](BuildPrerequisites.md)
+[<< Frequently Asked Questions](FAQ.md) | [Up](Documentation.md) | [Build Prerequisites >>](BuildPrerequisites.md)
diff --git a/docs/Documentation.md b/docs/Documentation.md
index 7703e50..3b15b4d 100644
--- a/docs/Documentation.md
+++ b/docs/Documentation.md
@@ -24,6 +24,10 @@
+#### [Frequently Asked Questions](FAQ.md)
+
+
+
#### [Building PiPedal from Source](BuildingPiPedalFromSource.md)
#### [Build Prerequisites](BuildPrerequisites.md)
#### [The Build Systems](TheBuildSystem.md)
diff --git a/docs/FAQ.md b/docs/FAQ.md
new file mode 100644
index 0000000..0aff010
--- /dev/null
+++ b/docs/FAQ.md
@@ -0,0 +1,105 @@
+---
+page_icon: img/faq.jpg
+icon_width: 320px
+icon_float: right
+---
+
+## Frequently Asked Questions
+
+{% include pageIcon.html %}
+
+### Q. Why is it so important to use a good USB audio adapter?
+
+If you use a cheap consumer-level USB audio adapter, you will not get the best possible results from PiPedal. You will, in fact, get good results, possibly better results than anything you have used before. But not the best possible results, which, I am somewhat embarrassed to say, are astounding, particularly when using TooB Neural Amp Modeler amp models. The quality of Toob Neural Amp Modeler amp models is so good that it is actually possible to make a guitar effects box that sounds better than retail guitar effects boxes that cost thousands of dollars. But you can't get there without a good USB audio adapter.
+
+Consumer-level USB audio adapters provide somewhere around 96dB of actual dynamic range between the loudest possible signal and the noise floor. And disturbingly, many consumer-level USB audio adapters have a noise floors that are much worse. I, personally, have a USB adapter from a brand that most people would consider reputable, that only has a 60dB of measured dynamic range. That would be 10 bits of actual audio data, and 14 bits of pure noise, all packed into 24-bit words, for those of you who are counting.
+
+Support for 24-bit audio is a marketing gimmick. The actual spec you should be interested in is the dynamic range of the device, which is the difference between the loudest possible signal and the noise floor.
+
+Dynamic range matters enormously. By the time you have run that -96dB noise signal through a heavily-overdrive Neural Amp Modeler amp model, you will end up with a dynamic range on output somewhere around -12dB, or perhaps even less if you are into very heavy metal. It still sounds ok; but it doesn't sound nearly as good as it could. Models tend to sound muddy, and indistinct, and there's an audible hiss in the background. Still good. But they could be so much better.
+
+Professional-level USB audio adapters, on the other hand, provide much higher dynamic ranges. A good professional-level USB audio adapter will provide somewhere between 110dB and 120dB of of usable dynamic range. That difference sounds may modest, but it makes a huge difference when using NAM amp models. It's like you were playing under a blanket before, and suddenly the blanket is gone, your eyes suddenly clear, and a light shines down from heaven. The emulations have clarity, and definition, and genuinely sound and feel like the real thing. As a point of reference, the current generation of Line6 Helix guitar effects boxes manage to provide a126dB of audio input dynamic range (120dB on output), which is admittedly impressive, and plays a very significant role in how good they do sound. (Although to my ears, the Helix amp models feel cold, and are unforgiving to play, compared to NAM amp models).
+
+It's perfectly OK to use consumer-level USB audio adapters with PiPedal when you are trying out the software. I think you will like the result. But I would like to encourage you in the strongest possible terms to invest an a decent professional-level USB audio adapter. They are not that expensive -- somewhere in the $100 to $200 range. You will not get the full experience of using PiPedal at its best without a good USB audio adapter. I promise you, you will not regret the investment. With a consumer-level USB audio adapter, you end up with a pretty good stomp box, frankly. It does sound good. But with a pro level adapter, you are going to get a guitar stomp box that provides significantly genuinely better sound quality with a quality that exceeds that of retail guitar effects boxes like Helix that cost thousands of dollars.
+
+I can only claim partial credit for this. The TooB Neural Amp Modeler, is based on Steven Atkins' open-source Neural Amp Modeler Core library which is the real hero here. The quality of TooB Neural Amp Modeler emulations is simply astounding. To be honest, I very much see PiPedal as a way to put the Neural Amp Modeler models into the hands of as many guitarists as possible, and to make it easy for them to do so. Those of us who work with Neural Amp Modeler often comment on how game-changing this technology is. How NAM changes everything. And how it is the future of guitar effects because it is so much better than anything that has come before it.
+
+But you can't actually experience the full impact of NAM unless you give PiPedal a good USB audio adapter to work with.
+
+Investing in a professional-level USB audio adapter is not going to be an investment that you will regret. "Professional-level" in this context means something in the $100 to $200 price range, not in a $30-$60 price range. The particular spec you need to look for on datasheets is "110dB dynamic range". "-130.5 dbU EIN A-weighted is actually not quite the right spec, but is a solid indicator that you are dealing with a device in the right class. "dB A-weighted" SN/R slightly inflates SN/R numbers, if you see it on a datasheet. But consumer-level USB audio adapters with only 96dB of actual dynamic range will not be able to quote an an A-weighted SN/R or dynamic range in the 110+ range no matter how hard they try. Generally, if the datasheet doesn't say what the device's SN/R is, you can assume that it not good, and not a professional-level device. The device that I personally use is a MOTU M2, which provides 115db of dynamic range (-129dbU EIN), and is a very solidly built device, with great controls, and extremely usable meters.
+
+Devices that would make excellent choices:
+
+- MOTU M2
+- Focusrite Scarlett 2i2 (4th gen)
+ -PreSonus Quantum ES 2 USB-C Audio Interface
+- Solid State Logic SSL 2 MK II
+- Universal Audio Volt 176
+
+Or other devices in that sort of class and price range.
+
+The MOTU M2, and the Focusrite Scarlett 2i2 are relatively affordable.
+
+#### Q. My Neural Amp Modeler amp models don't sound that great. Am I doing something wrong? the output signal not loud enough.
+
+You probably are.
+
+To get best results, you must make sure that analog input and output signal levels are trimmed correctly. If you have knobs and dials and buttons, make sure those knobs and dials and buttons are set correctly. If you don't have knobs and dials and buttons, see the following question for instructions on how to trim input and output levels using `alsamixer`.
+
+The knobs and dials on your USB audio adapter (whether physical, or `alsamixer` controls) are used to set the analog signal levels being presented the Analog to Digital Converter (ADC). You want to set the input trim levels so that the digital audio signal that PiPedal gets is as close to 0dB as possible, without clipping (going into the red) at any point. If you set it too low, you will lose accuracy in the digital audio data, and your amp models will sound muddy and unpleasant. If you set it too high, you will cause clipping in the digital audio data, and which sounds extremely harsh and unpleasant. Avoid clipping at all costs.
+
+If you click on the start node and end nodes of a Pipedal preset, you will see VU meters that measure the loudness of the digital audio coming into and going out of Pipedal. These meters are a good way to check that the analog trim levels of your input and output signals are correct. You should, in fact, NEVER use PiPedal's input and output trim controls to control the volume of your audio, unless you have a compelling reason to do so. Use your knobs and dials (or `alsamixer` controls) to set the analog input and control voume of audio comping out of the audio output jacks.
+
+The quality of your USB audio adapter is also important. See "Q. Why is it so important to use a good USB audio adapter?", above.
+
+#### Q. The signal levels on my USB adapter are too low. The input signal is too quiet, or the output signal not loud enough.
+
+A. This is a common problem with USB audio adapters, especially inexpensive ones. USB adapters are designed to work with a wide range of signal levels. Most USB audio devices have knobs and controls and buttons on the front panel that allow you to adjust the analog input and output levels. But some USB adapters don't have those controls. For these devices, you must use Linux commandline tools to adjust the signal levels. Unfortunately, the default levels that are set by the operating system for these devices are much too low; so if you have a device like this, you will run into a problem.
+
+To fix this, you can use the `alsamixer` command to adjust the levels. Open a terminal window and type `alsamixer`. This will open a graphical mixer interface in the terminal. Use the arrow keys to navigate to the input and output channels, and use the up and down arrow keys to adjust the levels. Make sure to set the levels appropriately for both input and output channels.
+
+Fortunately, settings made in `alsamixer` are persistent, and will remain in effect even if you reboot your system, so you will usually only have to do this procedure once.
+
+
+#### Q. Dial control movements are too coarse. How do I make them finer?
+
+A. If you are using a desktop browser, the Ctrl and Shift keys modify the behavior of the dial control.
+If you press the Shift key while you are dragging the control, the values will change in 10x smaller increments.
+If you press the Ctrl key while you are dragging the control, the values will change in 50x smaller increments.
+
+If you are using
+a touch interface, the dials will change in 10x smaller increments if you have two fingers touching the screen while you are dragging the control,
+and 50x smaller increments if you have three fingers touching the screen while you are dragging the control. The three-finger touch gesture is a bit
+awkward. I find the best way to do it is to touch the screen with your middle finger first, and then touch the screen with your index finger and your ring finger. The position of the first finger to touch the screen determines how controls change.
+
+#### Q. What do the buffer size controls do? How do I choose good values?
+
+A. When configuring your audio device, you must choose a buffer size, and how many buffers to use.
+
+The simple answer is: use 32x4 if your hardware can handle it, and use larger sizes if your hardware can't.
+
+The longer answer is complicated (but not too complicated).
+
+The buffer size determines how much audio data is processed at once by PiPedal. Pipedal processes audio data in chunks, the buffer size sets how large those chunks are. For example, if the buffer size is set to 64 samples, PiPedal will process audio 64 samples at a time. There is a slight cost to processing each chunk of audio, in addition to the cost of processing each sample of audio. So smaller buffer sizes mean that more CPU overhead is incurred for processing the buffer. Larger buffer sizes mean more efficient use of CPU, at the const of increased latency. As a loose guideline, choosing a buffer size of 32 samples incurs a few percentage points of CPU overhead -- not enough to be a problem on most hardware; a buffer size of 16 samples incurs a few percentage points more (5 to 10% depending on the effects you are using); and a buffer size of 64 samples has almost neglibible CPU overhead. The actual overhead depends on the hardware you are using, and the effects you are using. If you are determined to squeeze every last ounce of performance out of your hardware, you may want to experiment to find absolutely optimal settings; but 32x4 gives
+sufficiently good results for most users.
+
+The number of buffers determines how large a queue of audio buffers Pipedal uses. The longer the queue, the less likely the hardware is going to run out of audio data to send or run out of space to store newly received audio data. For example, if the number of buffers is set to 4, Pipedal will use one of those buffers to process audio, and will do its best to keep the audio queue filled with 3 additional buffers that have already been processed, and are waiting to be sent to the hardware, one of which will be being actively processed by the drivers and/or hardware at any given time.
+
+If the audio output queue runs out of buffers, this is known as an "underrun"; if the input queue runs out of buffers, this is known as an "overrun". And Pipedal refers to either or both of the conditions as "XRuns". You can see the number of XRuns in the status message that appears at the bottom of the screen, or at the top of the Settings page (if you have turned of status messages on the main screen).
+
+XRuns are bad. They cause snaps and pops in the audio, and can sometimes cause the audio device to stop working altogether until it is restarted. So you want to avoid them at all costs.
+
+The buffer size, and the number of buffers determine the latency of the audio system—the time it takes for audio to travel from the input jack to the output jack. The larger the buffer size, and the more buffers used, the longer the latency. Generally, you want to ensure that the overall latency, jack to jack, is less than 10 milliseconds. Latency longer than 10ms will make your instrument feel unpleasant to play. And very long latencies (in the order of 50ms) can actually make it impossible to play!
+
+So smaller values are better when it comes to latency. And larger values are better when it comes to CPU efficiency and stability.
+
+Unfortunately, choosing the right values depends on the the hardware you are using, and the operating system you are running, which drivers the device is using, and how heavily you are using the CPU when processing audio plugins, perhaps on which USB port you are using, and even the type of USB cable you are using. So there is no one-size-fits-all answer to this question of how to choose good values.
+
+Fortunately, there is a soft and loose rule that works well with almost all modern hardware, whether consumer-level or professional-level. and that works well with almost all professional-level audio adapters ever built. And that rule is: use a 32x4 buffer configuration. This provides excellent latency, and is stable on most audio adapters. If you are using old, or cheap hardware, you may need to increase the buffer size, or the number of buffers, or both in order to prevent the audio device from x-running. Increase buffer sizes to the smallest value that prevents device from x-running. 16x4 is an excellent configuration too, with even less latency, but it does use more CPU. The CPU overhead of a 32x configuration is small, and the overhead of a 64x configuration is vanishingly small.
+
+If you have to use larger buffers, there is absolutely nothing wrong with a 64x3 or 32x6 configuration. But by the time you get up to 128x buffer configurations, you are probably getting into the range where latency is going to be a problem.
+
+For what it's worth, hardware doesn't really care whether buffer settings are 64x3 or 32x6. The hardware actually just sees one big buffer, and chases reads and writes memory as fast as it can. The interesting difference between 64x3 and 32x6 is that the 32x6 configuration gives the hardware more queued data. In the 64x3 configuration, PiPedal uses one buffer, the hardware uses another buffer, and there is (hopefully) one queued 64-sample buffer waiting to be sent. In the 32x6 configuration, there is one buffer being used by PiPedal, one buffer being used by the hardware, and there are (hopefully) 4 queued 32-sample buffers waiting to be sent. So twice as much queued data when using a 32x6 configuration.
+
+--------
+[<< LV2 Plugins with MOD User Interfaces](ModUiSupport.md) | [Up](Documentation.md) | [BuildingPiPedal from Source >>](BuildingPiPedalFromSource.md)
diff --git a/docs/Installing.md b/docs/Installing.md
index f8935b8..955c485 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.4.79](https://github.com/rerdavies/pipedal/releases/download/v1.4.79/pipedal_1.4.79_arm64.deb)
-- [Ubuntu 24.x (aarch64) v1.4.79](https://github.com/rerdavies/pipedal/releases/download/v1.4.79/pipedal_1.4.79_arm64.deb)
-- [Ubuntu 24.x (amd64) v1.4.79](https://github.com/rerdavies/pipedal/releases/download/v1.4.79/pipedal_1.4.79_amd64.deb)
+- [Raspberry Pi OS bookworm (aarch64) v1.4.86](https://github.com/rerdavies/pipedal/releases/download/v1.4.86/pipedal_1.4.86_arm64.deb)
+- [Ubuntu 24.x (aarch64) v1.4.86](https://github.com/rerdavies/pipedal/releases/download/v1.4.86/pipedal_1.4.86_arm64.deb)
+- [Ubuntu 24.x (amd64) v1.4.86](https://github.com/rerdavies/pipedal/releases/download/v1.4.86/pipedal_1.4.86_amd64.deb)
-Version 1.4.79 has been tested on Raspberry Pi OS bookworm, Ubuntu 24.04 (amd64), and Ubuntu 24.10 (aarch64). Download the appropriate package for your platform, and install using the following procedure:
+Version 1.4.86 has been tested on Raspberry Pi OS bookworm, Ubuntu 24.04 (amd64), and Ubuntu 24.10 (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.4.79_arm64.deb
+ sudo apt-get install ./pipedal_1.4.86_arm64.deb
```
You MUST use `apt-get`. `apt` will not install downloaded packages; and `dpkg -i` will not install dependencies.
diff --git a/docs/ModUiSupport.md b/docs/ModUiSupport.md
index 993b4f9..ded38c9 100644
--- a/docs/ModUiSupport.md
+++ b/docs/ModUiSupport.md
@@ -32,4 +32,4 @@ Support for MOD UIs in PiPedal is still experimental, so you may encounter some
--------
-[<< Which Plugins are Supported?](WhichLv2PluginsAreSupported.md) | [Up](Documentation.md) | [BuildingPiPedal from Source >>](BuildingPiPedalFromSource.md)
+[<< Which Plugins are Supported?](WhichLv2PluginsAreSupported.md) | [Up](Documentation.md) | [Frequenty Asked Questions >>](FAQ.md)
diff --git a/docs/ReleaseNotes.md b/docs/ReleaseNotes.md
index 3d45137..a18618f 100644
--- a/docs/ReleaseNotes.md
+++ b/docs/ReleaseNotes.md
@@ -1,4 +1,14 @@
# Release Notes
+## PiPedal 1.4.86 Beta
+The Big Feature:
+- Code optimizations for Raspberry Pi 5 (Cortex-A76), providing a potential 2x performance improvement for TooB Neural Amp Modeler.
+- 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: Switch to the NeuralAudio NAM Backend
+Bug fixes:
+- Compatibility with plugins expecting certain MOD host features.
+- Improvements to recovery from audio underruns and overruns.
+- Fixes for loss of currently selected plugin when modifying pedalboard structure.
+
## Pipedal 1.4.79 Beta
The Big Feature:
diff --git a/docs/download.md b/docs/download.md
index 8e8b412..372c013 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.4.79 Beta](https://github.com/rerdavies/pipedal/releases/download/v1.4.79/pipedal_1.4.79_arm64.deb)
-- [Ubuntu 24.x (aarch64) v1.4.79 Alpha](https://github.com/rerdavies/pipedal/releases/download/v1.4.79/pipedal_1.4.79_arm64.deb)
-- [Ubuntu 24.x (amd64) v1.4.79 Alpha](https://github.com/rerdavies/pipedal/releases/download/v1.4.79/pipedal_1.4.79_amd64.deb)
+- [Raspberry Pi OS bookworm (aarch64) v1.4.86 Beta](https://github.com/rerdavies/pipedal/releases/download/v1.4.86/pipedal_1.4.86_arm64.deb)
+- [Ubuntu 24.x (aarch64) v1.4.86 Alpha](https://github.com/rerdavies/pipedal/releases/download/v1.4.86/pipedal_1.4.86_arm64.deb)
+- [Ubuntu 24.x (amd64) v1.4.86 Alpha](https://github.com/rerdavies/pipedal/releases/download/v1.4.86/pipedal_1.4.86_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.4.79_arm64.deb
+ sudo apt-get install ./pipedal_1.4.86_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/docs/img/faq.jpg b/docs/img/faq.jpg
new file mode 100644
index 0000000..c5ab8b6
Binary files /dev/null and b/docs/img/faq.jpg differ
diff --git a/lv2/aarch64/ToobAmp.lv2/CabIR.ttl b/lv2/aarch64/ToobAmp.lv2/CabIR.ttl
index fa85f89..1a9348a 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 64 ;
+ lv2:microVersion 66 ;
rdfs:comment """
TooB Cab IR is a convolution-based guitar cabinet impulse response simulator.
diff --git a/lv2/aarch64/ToobAmp.lv2/CabSim.ttl b/lv2/aarch64/ToobAmp.lv2/CabSim.ttl
index 8a17811..512f8c0 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 64 ;
+ lv2:microVersion 66 ;
mod:brand "TooB";
mod:label "TooB CabSim";
diff --git a/lv2/aarch64/ToobAmp.lv2/ConvolutionReverb.ttl b/lv2/aarch64/ToobAmp.lv2/ConvolutionReverb.ttl
index ee9eb6c..23918a1 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 64 ;
+ lv2:microVersion 66 ;
rdfs:comment """
Convolution reverb is a notoriously compute-intensive effect. If you are having performance issues, use the Max T control to constrain the length of the impulse file to
diff --git a/lv2/aarch64/ToobAmp.lv2/ConvolutionReverbStereo.ttl b/lv2/aarch64/ToobAmp.lv2/ConvolutionReverbStereo.ttl
index 2328645..c09b552 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 64 ;
+ lv2:microVersion 66 ;
rdfs:comment """
diff --git a/lv2/aarch64/ToobAmp.lv2/InputStage.ttl b/lv2/aarch64/ToobAmp.lv2/InputStage.ttl
index 95cac11..76ea7d7 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 64 ;
+ lv2:microVersion 66 ;
mod:brand "TooB";
mod:label "TooB Input";
diff --git a/lv2/aarch64/ToobAmp.lv2/PowerStage2.ttl b/lv2/aarch64/ToobAmp.lv2/PowerStage2.ttl
index f7cb004..d6c6c1f 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 64 ;
+ lv2:microVersion 66 ;
mod:brand "TooB";
mod:label "Power Stage";
diff --git a/lv2/aarch64/ToobAmp.lv2/PowerStage2l.ttl b/lv2/aarch64/ToobAmp.lv2/PowerStage2l.ttl
deleted file mode 100644
index ab5a4b0..0000000
--- a/lv2/aarch64/ToobAmp.lv2/PowerStage2l.ttl
+++ /dev/null
@@ -1,594 +0,0 @@
-@prefix doap: .
-@prefix lv2: .
-@prefix rdf: .
-@prefix rdfs: .
-@prefix units: .
-@prefix urid: .
-@prefix atom: .
-@prefix midi: .
-@prefix epp: .
-@prefix uiext: .
-@prefix idpy: .
-@prefix foaf: .
-@prefix mod: .
-@prefix param: .
-@prefix work: .
-@prefix pg: .
-
-@prefix atom: .
-@prefix patch: .
-@prefix plug: .
-@prefix rdfs: .
-@prefix state: .
-@prefix urid: .
-@prefix xsd: .
-@prefix modgui: .
-@prefix pstage: .
-
-
-pstage:stage1
- a param:ControlGroup ,
- pg:InputGroup ;
- lv2:name "Stage 1" ;
- lv2:symbol "stage1" .
-
-pstage:stage2
- a param:ControlGroup ,
- pg:InputGroup ;
- lv2:name "Stage 2" ;
- lv2:symbol "stage2" .
-
-
-pstage:stage3
- a param:ControlGroup ,
- pg:InputGroup ;
- lv2:name "Stage 3" ;
- lv2:symbol "stage3" .
-
-
-
- a lv2:Parameter ;
- rdfs:label "uiState" ;
- rdfs:range atom:Vector .
-
-
-
- a foaf:Person ;
- foaf:name "Robin Davies" ;
- foaf:mbox ;
- foaf:homepage .
-
-
- a lv2:Plugin ,
- lv2:SimulatorPlugin ;
-
- doap:name "TooB Power Stage";
-
- doap:license ;
- doap:maintainer ;
- lv2:minorVersion 0 ;
- lv2:microVersion 53 ;
-
- mod:brand "TooB";
- mod:label "Power Stage";
- lv2:requiredFeature urid:map ;
- lv2:optionalFeature lv2:hardRTCapable;
- patch:readable
- ;
-
-rdfs:comment """
-The TooB Power Stage provides flexible and nuanced emulations of guitar amp power stages.
-The goal of TooB Power Stage is emulate the best features of real tube amps without slavishly
-reproducing the undesirable features as well.
-
-TooB Power Stage provides 3 consecutive gain stages, each of which can be overdriven to varying
-degrees. Power transformer sag is emulated using the SAG and SAGD controls, which emulate the
-volume and distortion effects introduced when power transformer voltage sags.
-
-Each gain section expects an input level that is nominally in the range of 12db to 0db.
-It's ok if the levels are slightly over 0db, but they should not be massively over. Use the
-Trim control to adjust input signal levels.
-
-The Gain controls use oversampled atan wave shaping to emulate the effect of overdriving
-gain stages. Atan wave shaping provides rich even-order harmonics similar to those produced by a tube
-amp power stage. A modest amount of gain will produce the characteristically "warm" sound of a tube
-gain stage. More extreme settings produce harder overdrive.
-
-Judicious use of the LO CUT and HI CUT controls have interesting and subtle effects on the overall sound.
-You are encouraged to play with these controls to see what happens. Tightening up low and high frequencies,
-in early gain sections can allow you to drive the signal much harder in later gain sections without totally
-breaking up. Real amps will usually cut low frequency response at somewhere between 80Hz (Fender)
-and 140Hz (Marshall).
-
-General experience seems to suggest that gradually increasing the gain in each section allows you to drive
-the signal harder without introducing undesirable effects in the output. However, there definitely interesting
-and subtle differences in tone that can be acheived by not adhering to that rule of thumb.
-
-The SAG and SAGD controls the emulation of "forgiveness", and "compression" that are provided in a real tube
-amp by sagging of power supply voltage. SAG controls how much
-the overall volume of the output decreases as the transform sags. It provides the subtle "compression" that
-tube amps provide, which is quite different from the compression provided by compressors. SAGD controls
-the amount of additional distortion that occurs as the gain stages receive reduced voltage. It provides
-the "forgiveness" of a good tube amp. Because SAGD operates in a feedback loop that runs through
-all three gain stages. Increasing the distortion in the gain stages increases the power use of the gain stages;
-increasing the power use of the gain stages increases the effect of SAG and SAGD which has an opposing effect
-on power use. The net result is that SAGD causes signals to break up more gradually
-(hence the perceived "forgiveness"). The effects are non-linear, so not easy to describe completely. You
-are strongly urged to experiment with SAG and SAGD settings in order to get best tone results. Use very
-moderate settings to emulate tube amp output; higher settings of SAG and SAGD will cause audible compression
-and pumping of the outputs which may or may not be desireable.
-
-The TooB Power Stage design is based on an experimental design, that unexpectedly produced extraordinarily
-good amp tones. We have not completely explored the sonic possibilities of the
-TooB Power Stage. What's known so far: it produces exceptionally pretty clean tones; it can be driven really
-hard, and seems to preserve an unusual amount of detail in the signal even when being driven hard; and it
-can definitely produce signals that have many of the most prized features of tube amplifiers: warmth, detail,
-subtle "compression" without coloration, and forgiving break up.
-
-Explore!
-
-The TooB Input Stage is part of the TooB Amp Emulation Toolkit. Typically, you would
-use the following plugins in the following order.
-
-- TooB Input Stage
-
-- TooB Tone Stack
-
-- Toob Power Stage
-
-- Toob Cabinet Simulator
-""";
- lv2:port
- [
- a lv2:InputPort ,
- lv2:ControlPort ;
-
- lv2:index 0 ;
- pg:group pstage:stage1 ;
- lv2:symbol "trim1" ;
- lv2:name "Trim";
- lv2:default 0.0 ;
- lv2:minimum -20.0 ;
- lv2:maximum 20.0 ;
- units:unit units:db ;
- rdfs:comment "Trim input level (stage 1)" ;
- ],
- [
- a lv2:InputPort ,
- lv2:ControlPort ;
-
- lv2:index 1 ;
- pg:group pstage:stage1 ;
- lv2:symbol "locut1" ;
- lv2:name "Lo Cut";
- lv2:default 30.0 ;
- lv2:minimum 30.0 ;
- lv2:maximum 300.0 ;
- units:unit units:hz;
- lv2:designation param:cutoffFrequency ;
-
- lv2:scalePoint [
- rdfs:label "OFF" ;
- rdf:value 30.0
- ];
- rdfs:comment "Low frequency cutoff (stage 1). Set to minimum to disable." ;
-
- ], [
- a lv2:InputPort ,
- lv2:ControlPort ;
-
- lv2:index 2 ;
- pg:group pstage:stage1 ;
- lv2:symbol "hicut1" ;
- lv2:name "Hi Cut";
- lv2:default 19000.0 ;
- lv2:minimum 1000.0 ;
- lv2:maximum 19000.0;
- units:unit units:hz;
- lv2:designation param:cutoffFrequency ;
- lv2:scalePoint [
- rdfs:label "OFF" ;
- rdf:value 19000.0
- ];
- rdfs:comment "High cutoff frequency (stage 1). Set to maximum to disable." ;
-
- ],
- [
- a lv2:InputPort ,
- lv2:ControlPort ;
- pg:group pstage:stage1 ;
-
- lv2:index 3 ;
- lv2:symbol "shape1" ;
- lv2:name "Shape1";
- lv2:default 0.0 ;
- lv2:minimum 0.0 ;
- lv2:maximum 1.0;
- lv2:rangeStep 2;
-
- lv2:portProperty lv2:enumeration ;
- rdfs:comment "Wave Shape" ;
- lv2:scalePoint [
- rdfs:label "atan" ;
- rdf:value 0.0
- ],
- [
- rdfs:label "triode" ;
- rdf:value 1.0
- ];
- ],
- [
- a lv2:InputPort ,
- lv2:ControlPort ;
-
- lv2:index 4 ;
- pg:group pstage:stage1 ;
- lv2:symbol "gain1" ;
- lv2:name "Gain";
- lv2:default 0.0 ;
- lv2:minimum 0.0 ;
- lv2:maximum 1.0 ;
- rdfs:comment "Gain (stage 1)"
- ],
- [
- a lv2:InputPort ,
- lv2:ControlPort ;
- pg:group pstage:stage1 ;
-
- lv2:index 5 ;
- lv2:symbol "bias1" ;
- lv2:name "Bias";
- lv2:default 0.0 ;
- lv2:minimum -2.0 ;
- lv2:maximum 2.0;
- rdfs:comment "Waveshape bias (stage 1)"
- ],
-
-
- [
- a lv2:InputPort ,
- lv2:ControlPort ;
-
- lv2:index 6 ;
- pg:group pstage:stage2 ;
- lv2:symbol "trim2" ;
- lv2:name "Trim";
- lv2:default 0.0 ;
- lv2:minimum -20.0 ;
- lv2:maximum 20.0 ;
- units:unit units:db ;
- rdfs:comment "Trim input (stage 2)" ;
- ],
-
- [
- a lv2:InputPort ,
- lv2:ControlPort ;
-
- lv2:index 7 ;
- pg:group pstage:stage2 ;
- lv2:symbol "locut2" ;
- lv2:name "Lo Cut";
- lv2:default 30.0 ;
- lv2:minimum 30.0 ;
- lv2:maximum 300.0 ;
- units:unit units:hz;
- lv2:designation param:cutoffFrequency ;
-
- lv2:scalePoint [
- rdfs:label "OFF" ;
- rdf:value 30.0
- ]
- ;
- rdfs:comment "Low frequency cutoff (stage 2). Set to minimum to disable." ;
-
- ],
- [
- a lv2:InputPort ,
- lv2:ControlPort ;
-
- lv2:index 8 ;
- pg:group pstage:stage2 ;
- lv2:symbol "hicut2" ;
- lv2:name "Hi Cut";
- lv2:default 19000.0 ;
- lv2:minimum 1000.0 ;
- lv2:maximum 19000.0;
- units:unit units:hz;
- lv2:designation param:cutoffFrequency ;
- lv2:scalePoint [
- rdfs:label "OFF" ;
- rdf:value 19000.0
- ];
- rdfs:comment "High cutoff frequency (stage 2). Set to maximum to disable." ;
-
- ],
-
- [
- a lv2:InputPort ,
- lv2:ControlPort ;
- pg:group pstage:stage2 ;
-
- lv2:index 9 ;
- lv2:symbol "shape2" ;
- lv2:name "Shape2";
- lv2:default 0.0 ;
- lv2:minimum 0.0 ;
- lv2:maximum 1.0;
- lv2:rangeStep 2;
-
- lv2:portProperty lv2:enumeration ;
- rdfs:comment "Wave Shape" ;
- lv2:scalePoint [
- rdfs:label "atan" ;
- rdf:value 0.0
- ],
- [
- rdfs:label "triode" ;
- rdf:value 1.0
- ]
- ],
- [
- a lv2:InputPort ,
- lv2:ControlPort ;
-
- lv2:index 10 ;
- pg:group pstage:stage2 ;
- lv2:symbol "gain2" ;
- lv2:name "Gain";
- lv2:default 0.0 ;
- lv2:minimum 0.0 ;
- lv2:maximum 1.0 ;
- rdfs:comment "Gain (stage 2)"
- ],
-
- [
- a lv2:InputPort ,
- lv2:ControlPort ;
- pg:group pstage:stage2 ;
-
- lv2:index 11 ;
- lv2:symbol "bias2" ;
- lv2:name "Bias";
- lv2:default 0.0 ;
- lv2:minimum -2.0 ;
- lv2:maximum 2.0;
- rdfs:comment "Waveshape bias (stage 2)"
-
- ],
- [
- a lv2:InputPort ,
- lv2:ControlPort ;
-
- lv2:index 12 ;
- pg:group pstage:stage2 ;
- lv2:symbol "gain2_enable" ;
- lv2:name "Enable2";
- lv2:default 1 ;
- lv2:minimum 0 ;
- lv2:maximum 1;
- lv2:portProperty lv2:toggled ;
- rdfs:comment "Enable gain stage 2" ;
- ],
-
- [
- a lv2:InputPort ,
- lv2:ControlPort ;
-
- lv2:index 13 ;
- pg:group pstage:stage3 ;
- lv2:symbol "trim3" ;
- lv2:name "Trim";
- lv2:default 0.0 ;
- lv2:minimum -20.0 ;
- lv2:maximum 20.0 ;
- units:unit units:db ;
- rdfs:comment "Trim input level (stage 3)." ;
- ],
- [
- a lv2:InputPort ,
- lv2:ControlPort ;
-
- lv2:index 14 ;
- pg:group pstage:stage3 ;
- lv2:symbol "locut3" ;
- lv2:name "Lo Cut";
- lv2:default 30.0 ;
- lv2:minimum 30.0 ;
- lv2:maximum 300.0 ;
- units:unit units:hz;
- lv2:designation param:cutoffFrequency ;
-
- lv2:scalePoint [
- rdfs:label "OFF" ;
- rdf:value 30.0
- ];
- rdfs:comment "Low frequency cutoff (stage 3). Set to minimum to disable." ;
-
- ],
- [
- a lv2:InputPort ,
- lv2:ControlPort ;
-
- lv2:index 15 ;
- pg:group pstage:stage3 ;
- lv2:symbol "hicut3" ;
- lv2:name "Hi Cut";
- lv2:default 19000.0 ;
- lv2:minimum 1000.0 ;
- lv2:maximum 19000.0;
- units:unit units:hz;
- lv2:designation param:cutoffFrequency ;
- lv2:scalePoint [
- rdfs:label "OFF" ;
- rdf:value 19000
- ];
- rdfs:comment "High cutoff frequency (stage 3). Set to minimum to disable." ;
-
- ],
- [
- a lv2:InputPort ,
- lv2:ControlPort ;
- pg:group pstage:stage3 ;
-
- lv2:index 16 ;
- lv2:symbol "shape3" ;
- lv2:name "Shape3";
- lv2:default 0.0 ;
- lv2:minimum 0.0 ;
- lv2:maximum 1.0;
- lv2:rangeStep 2;
-
- lv2:portProperty lv2:enumeration ;
- rdfs:comment "Wave Shape" ;
- lv2:scalePoint [
- rdfs:label "atan" ;
- rdf:value 0.0
- ],
- [
- rdfs:label "triode" ;
- rdf:value 1.0
- ]
- ],
-
-
- [
- a lv2:InputPort ,
- lv2:ControlPort ;
-
- lv2:index 17 ;
- pg:group pstage:stage3 ;
- lv2:symbol "gain3" ;
- lv2:name "Gain";
- lv2:default 0.0 ;
- lv2:minimum 0.0 ;
- lv2:maximum 1.0 ;
- rdfs:comment "Gain (stage 3)."
- ],
- [
- a lv2:InputPort ,
- lv2:ControlPort ;
- pg:group pstage:stage3 ;
-
- lv2:index 18 ;
- lv2:symbol "bias3" ;
- lv2:name "Bias";
- lv2:default 0.0 ;
- lv2:minimum -2.0 ;
- lv2:maximum 2.0;
- rdfs:comment "Waveshape bias (stage 3)"
-
- ],
-
-
-
- [
- a lv2:InputPort ,
- lv2:ControlPort ;
-
- lv2:index 19 ;
- pg:group pstage:stage3 ;
- lv2:symbol "gain3_enable" ;
- lv2:name "Enable3";
- lv2:default 1 ;
- lv2:minimum 0 ;
- lv2:maximum 1;
- lv2:portProperty lv2:toggled ;
- rdfs:comment "Enable gain stage 3" ;
- ],
-
- [
- a lv2:InputPort ,
- lv2:ControlPort ;
-
- lv2:index 20 ;
- lv2:symbol "sag" ;
- lv2:name "Sag Vol";
- lv2:default 0.0 ;
- lv2:minimum 0.0 ;
- lv2:maximum 1.0;
- rdfs:comment "Transformer sag (volume)." ;
- ],
- [
- a lv2:InputPort ,
- lv2:ControlPort ;
-
- lv2:index 21 ;
- lv2:symbol "sagd" ;
- lv2:name "Sag Dist";
- lv2:default 0.0 ;
- lv2:minimum 0.0 ;
- lv2:maximum 1.0;
- rdfs:comment "Transformer sag (distortion)." ;
- ],
- [
- a lv2:InputPort ,
- lv2:ControlPort ;
-
- lv2:index 22 ;
- lv2:symbol "master" ;
- lv2:name "Master";
- lv2:default -0.0 ;
- lv2:minimum -60.0 ;
- lv2:maximum 30.0;
- units:unit units:db;
- lv2:scalePoint [
- rdfs:label "-60db" ;
- rdf:value -60.0
- ] , [
- rdfs:label "0db" ;
- rdf:value 0.0
- ], [
- rdfs:label "30db" ;
- rdf:value -30.0
- ];
- rdfs:comment "Master volume." ;
-
- ],
- [
- a lv2:AudioPort ,
- lv2:InputPort ;
- lv2:index 23 ;
- lv2:symbol "in" ;
- lv2:name "In"
- ] , [
- a lv2:AudioPort ,
- lv2:OutputPort ;
- lv2:index 24 ;
- lv2:symbol "out" ;
- lv2:name "Out"
- ],[
- a atom:AtomPort ,
- lv2:InputPort;
- atom:bufferType atom:Sequence ;
- # atom:supports patch:Message;
- lv2:designation lv2:control ;
- lv2:index 25 ;
- lv2:symbol "control" ;
- lv2:name "Control" ;
- rdfs:comment "Plugin to GUI communication" ;
- ] , [
- a atom:AtomPort ,
- lv2:OutputPort ;
- atom:bufferType atom:Sequence ;
- # atom:supports patch:Message;
- lv2:designation lv2:control ;
- lv2:index 26 ;
- lv2:symbol "notify" ;
- lv2:name "Notify" ;
- rdfs:comment "Plugin to GUI communication" ;
- ],
- [
- a lv2:InputPort ,
- lv2:ControlPort ;
-
- lv2:index 27 ;
- lv2:symbol "sagf" ;
- lv2:name "SagF";
- lv2:default 13.0 ;
- lv2:minimum 5.0 ;
- lv2:maximum 25.0;
- rdfs:comment "Sag filter Fc." ;
- ]
- .
-
-
diff --git a/lv2/aarch64/ToobAmp.lv2/SpectrumAnalyzer.ttl b/lv2/aarch64/ToobAmp.lv2/SpectrumAnalyzer.ttl
index 84011d8..1a624a1 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 64 ;
+ lv2:microVersion 66 ;
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 05526a2..7ef4d50 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 64 ;
+ lv2:microVersion 66 ;
uiext:ui ;
diff --git a/lv2/aarch64/ToobAmp.lv2/ToobAmp.so b/lv2/aarch64/ToobAmp.lv2/ToobAmp.so
old mode 100644
new mode 100755
index e0d9698..40cfb54
Binary files a/lv2/aarch64/ToobAmp.lv2/ToobAmp.so and b/lv2/aarch64/ToobAmp.lv2/ToobAmp.so differ
diff --git a/lv2/aarch64/ToobAmp.lv2/ToobAmpUI.so b/lv2/aarch64/ToobAmp.lv2/ToobAmpUI.so
old mode 100644
new mode 100755
index a78d9a8..008c5d9
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 6b0d594..d330dc3 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 64 ;
+ lv2:microVersion 66 ;
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 d8e95a7..b7743d6 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 64 ;
+ lv2:microVersion 66 ;
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 a4601a6..dc4437b 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 64 ;
+ lv2:microVersion 66 ;
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 dcef7d7..9974648 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 64 ;
+ lv2:microVersion 66 ;
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 c19a1da..2066069 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 64 ;
+ lv2:microVersion 66 ;
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 7581cc8..0ecc35f 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 64 ;
+ lv2:microVersion 66 ;
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 4cc44e8..73287e9 100644
--- a/lv2/aarch64/ToobAmp.lv2/ToobLooperFour.ttl
+++ b/lv2/aarch64/ToobAmp.lv2/ToobLooperFour.ttl
@@ -92,7 +92,7 @@ myprefix:output_group
doap:license ;
doap:maintainer ;
lv2:minorVersion 1 ;
- lv2:microVersion 64 ;
+ lv2:microVersion 66 ;
ui:ui ;
diff --git a/lv2/aarch64/ToobAmp.lv2/ToobLooperOne.ttl b/lv2/aarch64/ToobAmp.lv2/ToobLooperOne.ttl
index e37f594..b1cfa06 100644
--- a/lv2/aarch64/ToobAmp.lv2/ToobLooperOne.ttl
+++ b/lv2/aarch64/ToobAmp.lv2/ToobLooperOne.ttl
@@ -78,7 +78,7 @@ myprefix:output_group
doap:license ;
doap:maintainer ;
lv2:minorVersion 1 ;
- lv2:microVersion 64 ;
+ lv2:microVersion 66 ;
ui:ui ;
diff --git a/lv2/aarch64/ToobAmp.lv2/ToobLooperStereo.ttl b/lv2/aarch64/ToobAmp.lv2/ToobLooperStereo.ttl
deleted file mode 100644
index d290636..0000000
--- a/lv2/aarch64/ToobAmp.lv2/ToobLooperStereo.ttl
+++ /dev/null
@@ -1,726 +0,0 @@
-@prefix doap: .
-@prefix lv2: .
-@prefix rdfs: .
-@prefix rdf: .
-@prefix units: .
-@prefix urid: .
-@prefix atom: .
-@prefix midi: .
-@prefix epp: .
-@prefix uiext: .
-@prefix idpy: .
-@prefix foaf: .
-@prefix mod: .
-@prefix param: .
-@prefix work: .
-@prefix pg: .
-@prefix atom: .
-@prefix patch: .
-@prefix state: .
-@prefix urid: .
-@prefix xsd: .
-@prefix ui: .
-@prefix pprop: .
-@prefix pipedal_ui: .
-
-
-@prefix looperPrefix: .
-
-@prefix myprefix: .
-
-
-myprefix:stereoInGroup
- a pg:StereoGroup, pg:InputGroup ;
- rdfs:label "Stereo Input" ;
- lv2:symbol "in_g" .
-
-myprefix:steroOutGroup a pg:Group ;
- a pg:StereoGroup, pg:OuputGroup ;
- rdfs:label "Stereo Output" ;
- lv2:symbol "out_g" .
-
-
-myprefix:syncGroup
- a pg:ControlGroup ;
- lv2:name "Clock" ;
- lv2:symbol "sync_g" .
-
-
-myprefix:loop_main_group
- a pg:ControlGroup ;
- lv2:name "Main Loop" ;
- lv2:symbol "loop_main_group" .
-
-myprefix:loop2_group
- a pg:ControlGroup ;
- lv2:name "Loop 2" ;
- lv2:symbol "loop2_group" .
-
-myprefix:loop3_group
- a pg:ControlGroup ;
- lv2:name "Loop 3" ;
- lv2:symbol "loop3_group" .
-
-myprefix:loop4_group
- a pg:ControlGroup ;
- lv2:name "Loop 4" ;
- lv2:symbol "loop4_group" .
-
-myprefix:options_group
- a pg:ControlGroup ;
- lv2:name "Options" ;
- lv2:symbol "options_group" .
-
-myprefix:output_group
- a pg:ControlGroup ;
- lv2:name "Output" ;
- lv2:symbol "output_group" .
-
-
-
-
- a foaf:Person ;
- foaf:name "Robin Davies" ;
- foaf:mbox ;
- foaf:homepage .
-
-
-
- a lv2:Plugin ,
- lv2:UtilityPlugin ;
- doap:name "TooB 4Looper (stereo)"
- ;
- doap:license ;
- doap:maintainer ;
- lv2:minorVersion 1 ;
- lv2:microVersion 57 ;
-
- ui:ui ;
-
- patch:readable
- looperPrefix:audioFile;
- patch:writable
- looperPrefix:audioFile;
-
- lv2:extensionData state:interface ;
-
- rdfs:comment """
-A Stereo Looper.
-
-- A single tap on the bound midi controller will start recording.
-- A second tap with mark the end of the loop, and start playback.
-- Subsequent taps will toggle overdub mode. When overdubbing, new audio input will be blended with the existing loop.
-- A touble tap will stop playback.
-- To erase an overdub, long press the MIDI controller. The loop will return to the state that it was in before the overdub was made.
-
-
-
-""" ;
-
- mod:brand "TooB";
- mod:label "4Looper";
- lv2:optionalFeature lv2:hardRTCapable;
-
- lv2:port
- ################# MAIN Loop
- [
- a lv2:InputPort ,
- lv2:ControlPort ;
-
- lv2:index 0;
- lv2:symbol "stop1" ;
- lv2:name "⏹";
- rdfs:comment "Reset loop 1.";
- lv2:default 0 ;
- lv2:minimum 0.0;
- lv2:maximum 1.0;
- lv2:portProperty lv2:toggled,pprop:trigger;
- pg:group myprefix:loop_main_group ;
- ],
- [
- a lv2:InputPort ,
- lv2:ControlPort ;
-
- lv2:index 1;
- lv2:symbol "record1" ;
- lv2:name "⏺";
- rdfs:comment "Record/Overdub loop 1";
- lv2:default 0 ;
- lv2:minimum 0.0;
- lv2:maximum 1.0;
- lv2:portProperty lv2:toggled,pprop:trigger;
- pg:group myprefix:loop_main_group ;
- ],
- [
- a lv2:OutputPort ,
- lv2:ControlPort ;
-
- lv2:index 2;
- lv2:symbol "record_led1" ;
- lv2:name "";
- lv2:portProperty lv2:toggled ;
- lv2:default 0 ;
- lv2:minimum 0.0;
- lv2:maximum 1.0;
- pipedal_ui:ledColor "red";
- pg:group myprefix:loop_main_group ;
- ],
- [
- a lv2:InputPort ,
- lv2:ControlPort ;
-
- lv2:index 3;
- lv2:symbol "play1" ;
- lv2:name "⏵";
- rdfs:comment "Play Loop 1. Click again to stop.";
- lv2:default 0 ;
- lv2:minimum 0.0;
- lv2:maximum 1.0;
- lv2:portProperty lv2:toggled,pprop:trigger;
- pg:group myprefix:loop_main_group ;
- ],
- [
- a lv2:OutputPort ,
- lv2:ControlPort ;
-
- lv2:index 4;
- lv2:symbol "play_led1" ;
- lv2:name "";
- lv2:portProperty lv2:toggled ;
- lv2:default 0 ;
- lv2:minimum 0.0;
- lv2:maximum 1.0;
- pipedal_ui:ledColor "green";
- pg:group myprefix:loop_main_group ;
- ],
- [
- a lv2:OutputPort ,
- lv2:ControlPort ;
-
- lv2:index 5;
- lv2:symbol "position1" ;
- rdfs:comment "Time";
- lv2:name "\u00A0\u00A0\u00A0\u00A0";
- units:unit units:pc ;
-
- lv2:default 0.0 ;
- lv2:minimum 0.0 ;
- lv2:maximum 100.0 ;
- pg:group myprefix:loop_main_group ;
- ],
-
-
- ################# Loop2
- [
- a lv2:InputPort ,
- lv2:ControlPort ;
-
- lv2:index 6;
- lv2:symbol "stop2" ;
- lv2:name "⏹";
- rdfs:comment "Reset loop 2.";
- lv2:default 0 ;
- lv2:minimum 0.0;
- lv2:maximum 1.0;
- lv2:portProperty lv2:toggled,pprop:trigger;
- pg:group myprefix:loop2_group ;
- ],
- [
- a lv2:InputPort ,
- lv2:ControlPort ;
-
- lv2:index 7;
- lv2:symbol "record2" ;
- lv2:name "⏺";
- rdfs:comment "Record/Overdub loop 2";
- lv2:default 0 ;
- lv2:minimum 0.0;
- lv2:maximum 1.0;
- lv2:portProperty lv2:toggled,pprop:trigger;
- pg:group myprefix:loop2_group ;
- ],
- [
- a lv2:OutputPort ,
- lv2:ControlPort ;
-
- lv2:index 8;
- lv2:symbol "record_led2" ;
- lv2:name "";
- lv2:portProperty lv2:toggled ;
- lv2:default 0 ;
- lv2:minimum 0.0;
- lv2:maximum 1.0;
- pipedal_ui:ledColor "red";
- pg:group myprefix:loop2_group ;
- ],
- [
- a lv2:InputPort ,
- lv2:ControlPort ;
-
- lv2:index 9;
- lv2:symbol "play2" ;
- lv2:name "⏵";
- rdfs:comment "Play Loop 2. Click again to stop.";
- lv2:default 0 ;
- lv2:minimum 0.0;
- lv2:maximum 1.0;
- lv2:portProperty lv2:toggled,pprop:trigger;
- pg:group myprefix:loop2_group ;
- ],
- [
- a lv2:OutputPort ,
- lv2:ControlPort ;
-
- lv2:index 10;
- lv2:symbol "play_led2" ;
- lv2:name "";
- lv2:portProperty lv2:toggled ;
- lv2:default 0 ;
- lv2:minimum 0.0;
- lv2:maximum 1.0;
- pipedal_ui:ledColor "green";
- pg:group myprefix:loop2_group ;
- ],
- [
- a lv2:OutputPort ,
- lv2:ControlPort ;
-
- lv2:index 11;
- lv2:symbol "position2" ;
- rdfs:comment "Time";
- lv2:name "\u00A0\u00A0\u00A0\u00A0";
- units:unit units:pc ;
-
- lv2:default 0.0 ;
- lv2:minimum 0.0 ;
- lv2:maximum 100.0 ;
- pg:group myprefix:loop2_group ;
- ],
-
- ################# Loop3
- [
- a lv2:InputPort ,
- lv2:ControlPort ;
-
- lv2:index 12;
- lv2:symbol "stop3" ;
- lv2:name "⏹";
- rdfs:comment "Reset loop 3.";
- lv2:default 0 ;
- lv2:minimum 0.0;
- lv2:maximum 1.0;
- lv2:portProperty lv2:toggled,pprop:trigger;
- pg:group myprefix:loop3_group ;
- ],
- [
- a lv2:InputPort ,
- lv2:ControlPort ;
-
- lv2:index 13;
- lv2:symbol "record3" ;
- lv2:name "⏺";
- rdfs:comment "Record/Overdub loop 3";
- lv2:default 0 ;
- lv2:minimum 0.0;
- lv2:maximum 1.0;
- lv2:portProperty lv2:toggled,pprop:trigger;
- pg:group myprefix:loop3_group ;
- ],
- [
- a lv2:OutputPort ,
- lv2:ControlPort ;
-
- lv2:index 14;
- lv2:symbol "record_led3" ;
- lv2:name "";
- lv2:portProperty lv2:toggled ;
- lv2:default 0 ;
- lv2:minimum 0.0;
- lv2:maximum 1.0;
- pipedal_ui:ledColor "red";
- pg:group myprefix:loop3_group ;
- ],
- [
- a lv2:InputPort ,
- lv2:ControlPort ;
-
- lv2:index 15;
- lv2:symbol "play3" ;
- lv2:name "⏵";
- rdfs:comment "Play Loop 3. Click again to stop.";
- lv2:default 0 ;
- lv2:minimum 0.0;
- lv2:maximum 1.0;
- lv2:portProperty lv2:toggled,pprop:trigger;
- pg:group myprefix:loop3_group ;
- ],
- [
- a lv2:OutputPort ,
- lv2:ControlPort ;
-
- lv2:index 16;
- lv2:symbol "play_led3" ;
- lv2:name "";
- lv2:portProperty lv2:toggled ;
- lv2:default 0 ;
- lv2:minimum 0.0;
- lv2:maximum 1.0;
- pipedal_ui:ledColor "green";
- pg:group myprefix:loop3_group ;
- ],
- [
- a lv2:OutputPort ,
- lv2:ControlPort ;
-
- lv2:index 17;
- lv2:symbol "position3" ;
- rdfs:comment "Time";
- lv2:name "\u00A0\u00A0\u00A0\u00A0";
- units:unit units:pc ;
-
- lv2:default 0.0 ;
- lv2:minimum 0.0 ;
- lv2:maximum 100.0 ;
- pg:group myprefix:loop3_group ;
- ],
-
-
- ################# Loop4
- [
- a lv2:InputPort ,
- lv2:ControlPort ;
-
- lv2:index 18;
- lv2:symbol "stop4" ;
- lv2:name "⏹";
- rdfs:comment "Reset loop 4.";
- lv2:default 0 ;
- lv2:minimum 0.0;
- lv2:maximum 1.0;
- lv2:portProperty lv2:toggled,pprop:trigger;
- pg:group myprefix:loop4_group ;
- ],
- [
- a lv2:InputPort ,
- lv2:ControlPort ;
-
- lv2:index 19;
- lv2:symbol "record4" ;
- lv2:name "⏺";
- rdfs:comment "Record/Overdub loop 4";
- lv2:default 0 ;
- lv2:minimum 0.0;
- lv2:maximum 1.0;
- lv2:portProperty lv2:toggled,pprop:trigger;
- pg:group myprefix:loop4_group ;
- ],
- [
- a lv2:OutputPort ,
- lv2:ControlPort ;
-
- lv2:index 20;
- lv2:symbol "record_led4" ;
- lv2:name "";
- lv2:portProperty lv2:toggled ;
- lv2:default 0 ;
- lv2:minimum 0.0;
- lv2:maximum 1.0;
- pipedal_ui:ledColor "red";
- pg:group myprefix:loop4_group ;
- ],
- [
- a lv2:InputPort ,
- lv2:ControlPort ;
-
- lv2:index 21;
- lv2:symbol "play4" ;
- lv2:name "⏵";
- rdfs:comment "Play Loop 4. Click again to stop.";
- lv2:default 0 ;
- lv2:minimum 0.0;
- lv2:maximum 1.0;
- lv2:portProperty lv2:toggled,pprop:trigger;
- pg:group myprefix:loop4_group ;
- ],
- [
- a lv2:OutputPort ,
- lv2:ControlPort ;
-
- lv2:index 22;
- lv2:symbol "play_led4" ;
- lv2:name "";
- lv2:portProperty lv2:toggled ;
- lv2:default 0 ;
- lv2:minimum 0.0;
- lv2:maximum 1.0;
- pipedal_ui:ledColor "green";
- pg:group myprefix:loop4_group ;
- ],
- [
- a lv2:OutputPort ,
- lv2:ControlPort ;
-
- lv2:index 23;
- lv2:symbol "position4" ;
- rdfs:comment "Time";
- lv2:name "\u00A0\u00A0\u00A0\u00A0";
- units:unit units:pc ;
-
- lv2:default 0.0 ;
- lv2:minimum 0.0 ;
- lv2:maximum 100.0 ;
- pg:group myprefix:loop4_group ;
- ],
-
-
- ###### Sync
- [
- a lv2:OutputPort ,
- lv2:ControlPort ;
-
- lv2:index 24;
- lv2:symbol "bar_led" ;
- lv2:name "\u00A0\u00A0Bar\u00A0\u00A0";
- lv2:portProperty lv2:toggled ;
- lv2:default 0 ;
- lv2:minimum 0.0;
- lv2:maximum 1.0;
- pipedal_ui:ledColor "red";
- pg:group myprefix:syncGroup ;
- ],
- [
- a lv2:OutputPort ,
- lv2:ControlPort ;
-
- lv2:index 25;
- lv2:symbol "beat_led" ;
- lv2:name "\u00A0Beat\u00A0";
- lv2:portProperty lv2:toggled ;
- lv2:default 0 ;
- lv2:minimum 0.0;
- lv2:maximum 1.0;
- pipedal_ui:ledColor "red";
- pg:group myprefix:syncGroup ;
- ],
- [
- a lv2:InputPort ,
- lv2:ControlPort ;
-
- lv2:index 26;
- lv2:symbol "tempo" ;
- lv2:name "Tempo";
- lv2:default 120.0 ;
- lv2:minimum 40.0;
- lv2:maximum 240.0;
- units:unit units:bpm;
- rdfs:comment "Tempo in quarter-notes per minute"
- ; pg:group myprefix:syncGroup
- ],
- [
- a lv2:InputPort ,
- lv2:ControlPort ;
-
- lv2:index 27;
- lv2:symbol "timesig" ;
- rdfs:comment "Time Signature";
- lv2:name "TimeSig";
- lv2:default 2 ;
- lv2:minimum 0;
- lv2:maximum 5;
- lv2:portProperty lv2:enumeration ;
-
- lv2:scalePoint [
- rdfs:label "2/2" ;
- rdf:value 0
- ] , [
- rdfs:label "3/4" ;
- rdf:value 1
- ] , [
- rdfs:label "4/4" ;
- rdf:value 2
- ] ,
- [
- rdfs:label "5/4" ;
- rdf:value 3
- ] ,
- [
- rdfs:label "6/8" ;
- rdf:value 4
- ] ,
- [
- rdfs:label "7/4" ;
- rdf:value 5
- ];
- pg:group myprefix:syncGroup
- ],
-
- ########## Options
-
- [
- lv2:name "Count In" ;
-
- a lv2:InputPort ,
- lv2:ControlPort ;
-
- lv2:index 28;
- lv2:symbol "rec_count_in" ;
- lv2:default 1.0 ;
- lv2:minimum 0.0;
- lv2:maximum 1.0;
- lv2:portProperty lv2:toggled;
- rdfs:comment "Whether to count in when starting the main loop.";
-
- pg:group myprefix:options_group ;
- ],
- [
- a lv2:InputPort ,
- lv2:ControlPort ;
-
- lv2:index 29;
- lv2:symbol "bars" ;
- lv2:name "Bars";
- lv2:default 0 ;
- lv2:minimum 0;
- lv2:maximum 32;
- units:unit units:bar ;
- lv2:portProperty lv2:integer;
- rdfs:comment "Loop length in bars. Set to minimum for arbitrary length loops.";
- pg:group myprefix:options_group ;
- lv2:scalePoint [
- rdfs:label "Free" ;
- rdf:value 0.0
- ]
- ],
- [
- lv2:name "Rec Sync" ;
-
- a lv2:InputPort ,
- lv2:ControlPort ;
-
- lv2:index 30;
- lv2:symbol "rec_sync_option" ;
- lv2:default 1.0 ;
- lv2:minimum 0.0;
- lv2:maximum 1.0;
- lv2:portProperty lv2:toggled;
- rdfs:comment "Whether to sync the start of the loop with the main loop when recording secondary loops.";
-
- pg:group myprefix:options_group ;
- ],
- [
- a lv2:InputPort ,
- lv2:ControlPort ;
-
- lv2:index 31;
- lv2:symbol "loop_end_option" ;
- lv2:name "On Rec End";
- lv2:default 1.0 ;
- lv2:minimum 1.0;
- lv2:maximum 2.0;
- lv2:portProperty lv2:enumeration;
- rdfs:comment "(One-control MIDI operation only) Whether to go to OVERDUB or PLAY state when recording of the main loop ends.";
-
- lv2:scalePoint [
- rdfs:label "REC->PLAY" ;
- rdf:value 1.0
- ] , [
- rdfs:label "REC->DUB" ;
- rdf:value 2.0
- ] ;
-
- pg:group myprefix:options_group ;
- ],
-
- [
- a lv2:InputPort ,
- lv2:ControlPort ;
-
- lv2:index 32;
- lv2:symbol "level" ;
- lv2:name "Level";
- lv2:name "Level";
- pg:group myprefix:output_group ;
-
- lv2:default 0 ;
- lv2:minimum -60.0;
- lv2:maximum 30.0;
- lv2:scalePoint [
- rdfs:label "-INF" ;
- rdf:value -60.0
- ];
- units:unit units:db
- ],
-
- ################# Audio/Control Busses
-
- [
- a lv2:AudioPort ,
- lv2:InputPort ;
- lv2:index 33 ;
- lv2:symbol "in" ;
- lv2:name "In";
- pg:group myprefix:stereoInGroup ;
- lv2:designation pg:left
-
- ],
- [
- a lv2:AudioPort ,
- lv2:OutputPort ;
- lv2:index 34 ;
- lv2:symbol "out" ;
- lv2:name "Out" ;
- pg:group myprefix:stereoOutGroup ;
- lv2:designation pg:left
- ],
-
- [
- a atom:AtomPort ,
- lv2:InputPort ;
- lv2:index 35 ;
- lv2:symbol "controlIn" ;
- lv2:name "ControlIn"
- ],
- [
- a atom:AtomPort ,
- lv2:OutputPort ;
- lv2:index 36 ;
- lv2:symbol "controlOut" ;
- lv2:name "ControlOut"
- ],
- [
- a lv2:AudioPort ,
- lv2:InputPort ;
- lv2:index 37;
- lv2:symbol "inR" ;
- lv2:name "InR" ;
- pg:group myprefix:stereoInGroup ;
- lv2:designation pg:right
-
- ],
- [
- a lv2:AudioPort ,
- lv2:OutputPort ;
- lv2:index 38 ;
- lv2:symbol "outR" ;
- lv2:name "OutR" ;
- pg:group myprefix:stereoOutGroup ;
- lv2:designation pg:right
- ]
-
-
- ;
-
- .
-
-
-
-
- a ui:X11UI ;
- lv2:binary <.so>;
-
- lv2:extensionData ui:resize ;
- lv2:extensionData ui:idleInterface ;
- lv2:requiredFeature ui:idleInterface ;
-.
-
-
diff --git a/lv2/aarch64/ToobAmp.lv2/ToobML.ttl b/lv2/aarch64/ToobAmp.lv2/ToobML.ttl
index de2a07a..2c6ecb3 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 64 ;
+ lv2:microVersion 66 ;
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 aa0ae6a..f85beb6 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 64 ;
+ lv2:microVersion 66 ;
rdfs:comment """
Remix a stereo input signal.
diff --git a/lv2/aarch64/ToobAmp.lv2/ToobNeuralAmpModeler.ttl b/lv2/aarch64/ToobAmp.lv2/ToobNeuralAmpModeler.ttl
index 67ede49..8cee2d3 100644
--- a/lv2/aarch64/ToobAmp.lv2/ToobNeuralAmpModeler.ttl
+++ b/lv2/aarch64/ToobAmp.lv2/ToobNeuralAmpModeler.ttl
@@ -61,36 +61,66 @@ toobNam:eqGroup
doap:license ;
doap:maintainer ;
lv2:minorVersion 0 ;
- lv2:microVersion 64 ;
+ lv2:microVersion 66 ;
rdfs:comment """
-A port of Steven Atkinson's Neural Amp Modeler to LV2.
+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
+TooB Neural Amp Modeler. The resulting models produce startlingly realistic emulations of the original equipment.
-TooB Neural Amp Modeler uses uploadable .nam model files. Download .nam model files from http://tone3000.com, and then load them into TooB Neural Amp Modeler.
+The .nam file format (and the NAM technology itself) was developed by the open-source Neural Amp Modeler Core project. The NAM Core project provides both code that can be used by Neural Amp Modeler
+plugins, and code that can be used to train models for use in NAM plugins. Currently, NAM plugins are available on a wide variety of operating systems and audio platforms, and are
+increasingly being used in commercial pedals and amps.
-The TONE3000 website contains a huge collection of community-developed amp models that can be used with TooB Neural Amp Modeler. You can also use .nam models from
-other sources. Using TONE3000 models with TooB Neural Amp Modeler is a two-step process. First, download model files to your local system using a
+Although NAM is, first and foremost, used to model guitar amps, it is equally good at emulating a wide variety of other guitar effects. Overdrive and distortion pedals,
+compressors, limiters, tube preamps on studio mixing strips, and many other effects can be simulated extremely well using NAM models. NAM will definitely become
+your go-to source for great amp emulations; but it should probably also become your go-to source for emulating distorion and overdrive pedals. And developers
+are constantly coming up with new and ingenious applications for NAM models.
+
+.nam model files can be donwloaded from the internet. There are many online sources for .nam model files, both free, and commercial. Foremost among these is the
+http://tone3000.com website, which provides a vast collection of free .nam models files that have been developed and uploaded by a large online community of NAM users.
+
+Loading .nam model files into PiPedal is a two-step process. First, download model files to your local computer, or your phone or tablet, using a
web browser. Then upload the model files to the PiPedal server using the PiPedal web interface. You can find an "Upload" button in
-the file browser when you click on the "Model" control in the PiPedal web interface.
-
+the file browser when you click on the "Model" control in the plugin's user interface within the PiPedal web interface.
If you are not using PiPedal, just click on the Model control, and use the file browser to select the .nam file directly on your local system.
TooB Neural Amp Modeler supports a much wider range of amp models than ToobML, but usually uses more CPU.
You will need at least a Pi 4 to use Toob Neural Amp Modeler, and you may need to increase your audio buffer sizes to prevent overruns.
If you are having trouble with CPU usage, tone3000.com does contain some smaller amp models. Search for the "feather" tag to find
-models that use less CPU. Generally, you can use up to three NAM models at once in a given preset on a Pi 4, and up to 5 MAM models
-simultaneously on a Pi 5. N100 micro PCs could presumably do even better. However, exact CPU use varies depending on the complexity of the amp models you are using.
-Users have reported success using NAM on Raspberry Pi 3 devices, but this is not recommended (or really supported), as CPU usage is very high on a Raspberry Pi 3.
+models that use less CPU. Generally, you can use up to three NAM plugins at once in a given preset on a Pi 4, and up to 16 NAM plugins
+simultaneously on a Pi 5. However, exact CPU use varies depending on the complexity of the amp models you are using.
+
If you are interested in profiling your own amps and effect pedals, please visit https://www.tone3000.com/capture
-TooB Neural Amp Modeler contains code optimizations that allow use of a third model on Raspberry PI 4 devices. These optimizations
-were offered to the upstream NAM project, but we recommended that they not be merged into upstream sources, because they
-significantly affect maintainability of the codebase. and the performance increase they provide (about 30%) is really only
-relevant on Raspberry PI 4-class devices. If you are interested in these optimizations, they have been published in ToobAmp
-project sources on GitHub.
+When the Threaded control is on, neural amp modelling calculations are run on a separate thread from the
+real-time audio thread. This introduces one extra audio buffer of latency, but allows the plugin to make full
+use of all available processor cores on your Pi server. On a Pi 5, you can run more instance than you
+could ever practically use when using threading; on a Pi 4 you can run 5 simultanous instances when using threading.
-TooB Neural Amp Modeler uses code from the NeuralAmp Modeler Core project (https://www.neuralampmodeler.com/). The TooB team wishes
-to express gratitude to Steven Atkinson for making this revolutionary technology available as open-source code.
+When using more than a couple of NAM instances, you need to check that the PiPedal server's CPU is not temperature throttling
+when using this feature.
+If the CPU temperature exceeds 80 degrees Celcius on a Raspberry Pi, the CPU clock rate is reduced, and
+the performance of the plugins will be affected. Having a good heat sink, or active cooling system is recommended when using more than a couple of
+NAM instances. PiPedal displays the current CPU temperature in a status message at the bottom of the display (unless you've turned
+that off in settings), and also displays the current CPU temperature in the status message found at the top
+of the Settings page. If you are running on a system that is not a Raspberry Pi, please consult your manufacturer's
+help documentation to find out what temperature heat throttling starts at on your particular computer, if you really need to know
+what the threshold is; but the effect on performance is pretty obvious when heat throttling does start.
+
+Note that heat throttling is done to protect your processor from permanent damage due to overheating; but there's no real risk of actual
+damage, since heat throttling thresholds are set well below the point at which damage occurs.
+
+TooB Neural Amp Modeler is based on technology developed by the NeuralAmp Modeler Core project (https://www.neuralampmodeler.com/). The TooB team wishes
+to express gratitude to Steven Atkinson for making this revolutionary technology available as open-source code. This is groundbreaking
+technology that changes everything. None of this would have happened without the work that Steven has done.
+
+TooB Neural Amp Modeler uses code from the NeuralAudio project (https://www.neuralampmodeler.com/). The Neural Audio project provides highly
+optimized implementations of Neural Amp models. The TooB team wishes to express gratitude to Mike Oliphant for
+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 NeuralAmpModelerCore project (https://github.com/sdatkinson/NeuralAmpModelerCore) is provided
under the following license.
@@ -117,6 +147,30 @@ 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.
+Code from the the NeuralAudio project (https://github.com/sdatkinson/NeuralAmpModelerCore) is provided
+under the following license.
+
+MIT License
+
+Copyright (c) 2024 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
+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.
""" ;
@@ -180,6 +234,21 @@ SOFTWARE.
lv2:ControlPort ;
lv2:index 3;
+ lv2:symbol "buffer" ;
+ lv2:name "Threaded";
+ lv2:default 0.0 ;
+ lv2:minimum 0.0;
+ lv2:maximum 1.0;
+ lv2:portProperty lv2:toggled;
+ rdfs:comment "Run computation on a separate thread. This allows more instances of NAM to run simultaneously, but adds one extra audio buffer of latency.";
+ rdfs:comment "Output gain";
+
+ ],
+ [
+ a lv2:InputPort ,
+ lv2:ControlPort ;
+
+ lv2:index 4;
lv2:symbol "gate" ;
lv2:name "Noise Gate";
lv2:default -100.0 ;
@@ -198,7 +267,7 @@ SOFTWARE.
a lv2:OutputPort ,
lv2:ControlPort ;
- lv2:index 4;
+ lv2:index 5;
lv2:symbol "gateOut" ;
lv2:name "\u00A0";
lv2:default 0.0;
@@ -212,7 +281,7 @@ SOFTWARE.
a lv2:InputPort ,
lv2:ControlPort ;
- lv2:index 5 ;
+ lv2:index 6 ;
lv2:symbol "toneStack" ;
lv2:name "Type";
lv2:default 3.0 ;
@@ -241,7 +310,7 @@ SOFTWARE.
a lv2:InputPort ,
lv2:ControlPort ;
- lv2:index 6;
+ lv2:index 7;
lv2:symbol "bass" ;
lv2:name "Bass";
lv2:default 5.0;
@@ -255,7 +324,7 @@ SOFTWARE.
a lv2:InputPort ,
lv2:ControlPort ;
- lv2:index 7;
+ lv2:index 8;
lv2:symbol "mid" ;
lv2:name "Mid";
lv2:default 5.0;
@@ -268,7 +337,7 @@ SOFTWARE.
a lv2:InputPort ,
lv2:ControlPort ;
- lv2:index 8;
+ lv2:index 9;
lv2:symbol "treble" ;
lv2:name "Treble";
lv2:default 5.0;
@@ -280,14 +349,14 @@ SOFTWARE.
[
a lv2:AudioPort ,
lv2:InputPort ;
- lv2:index 9 ;
+ lv2:index 10 ;
lv2:symbol "in" ;
lv2:name "In"
],
[
a lv2:AudioPort ,
lv2:OutputPort ;
- lv2:index 10 ;
+ lv2:index 11 ;
lv2:symbol "out" ;
lv2:name "Out"
],
@@ -299,7 +368,7 @@ SOFTWARE.
lv2:designation lv2:control ;
atom:supports patch:Message ;
- lv2:index 11 ;
+ lv2:index 12 ;
lv2:symbol "control" ;
lv2:name "Control" ;
rdfs:comment "Control" ;
@@ -309,7 +378,7 @@ SOFTWARE.
atom:bufferType atom:Sequence ;
# atom:supports patch:Message;
lv2:designation lv2:control ;
- lv2:index 12;
+ lv2:index 13;
lv2:symbol "notify" ;
lv2:name "Notify" ;
rdfs:comment "Notification" ;
@@ -351,10 +420,9 @@ toobNam:ui
pipedal_ui:frequencyPlot
[
pipedal_ui:patchProperty toobNam:FrequencyResponse;
- lv2:index 9 ;
+ lv2:index 10 ;
pg:group toobNam:eqGroup ;
pipedal_ui:width: 200;
]
-
.
diff --git a/lv2/aarch64/ToobAmp.lv2/ToobNoiseGate.ttl b/lv2/aarch64/ToobAmp.lv2/ToobNoiseGate.ttl
index 5672117..6bc719f 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 64 ;
+ lv2:microVersion 66 ;
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/ToobPhaser.ttl b/lv2/aarch64/ToobAmp.lv2/ToobPhaser.ttl
index 3676f05..db9c38e 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 64 ;
+ lv2:microVersion 66 ;
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 c11a17b..dc49767 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 64 ;
+ lv2:microVersion 66 ;
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 841fb12..d7d7efa 100644
--- a/lv2/aarch64/ToobAmp.lv2/ToobRecordMono.ttl
+++ b/lv2/aarch64/ToobAmp.lv2/ToobRecordMono.ttl
@@ -51,7 +51,7 @@ recordPrefix:audioFile
doap:license ;
doap:maintainer ;
lv2:minorVersion 1 ;
- lv2:microVersion 64 ;
+ lv2:microVersion 66 ;
ui:ui ;
diff --git a/lv2/aarch64/ToobAmp.lv2/ToobRecordStereo.ttl b/lv2/aarch64/ToobAmp.lv2/ToobRecordStereo.ttl
index 9669716..c013b8d 100644
--- a/lv2/aarch64/ToobAmp.lv2/ToobRecordStereo.ttl
+++ b/lv2/aarch64/ToobAmp.lv2/ToobRecordStereo.ttl
@@ -88,7 +88,7 @@ myprefix:loop3_group
doap:license ;
doap:maintainer ;
lv2:minorVersion 1 ;
- lv2:microVersion 64 ;
+ lv2:microVersion 66 ;
ui:ui ;
diff --git a/lv2/aarch64/ToobAmp.lv2/ToobTuner.ttl b/lv2/aarch64/ToobAmp.lv2/ToobTuner.ttl
index bcaf601..3f58b62 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 64 ;
+ lv2:microVersion 66 ;
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 be47dd1..97047aa 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 64 ;
+ lv2:microVersion 66 ;
rdfs:comment """
Volume control.
diff --git a/lv2/aarch64/ToobAmp.lv2/bin/ToobAmp-a72.so b/lv2/aarch64/ToobAmp.lv2/bin/ToobAmp-a72.so
new file mode 100755
index 0000000..1702ce2
Binary files /dev/null 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
new file mode 100755
index 0000000..1335f65
Binary files /dev/null and b/lv2/aarch64/ToobAmp.lv2/bin/ToobAmp-a76.so differ
diff --git a/lv2/x86_64/toobamp_1.1.64_amd64.deb b/lv2/x86_64/toobamp_1.1.66_amd64.deb
similarity index 56%
rename from lv2/x86_64/toobamp_1.1.64_amd64.deb
rename to lv2/x86_64/toobamp_1.1.66_amd64.deb
index 91d5362..aadcc89 100644
Binary files a/lv2/x86_64/toobamp_1.1.64_amd64.deb and b/lv2/x86_64/toobamp_1.1.66_amd64.deb differ
diff --git a/src/AlsaDriver.cpp b/src/AlsaDriver.cpp
index 195cec6..bcf3871 100644
--- a/src/AlsaDriver.cpp
+++ b/src/AlsaDriver.cpp
@@ -245,6 +245,9 @@ namespace pipedal
class AlsaDriverImpl : public AudioDriver
{
private:
+ void TraceBuffers(size_t framesInBuffers, char code = ' ');
+ std::recursive_mutex restartMutex;
+
pipedal::CpuUse cpuUse;
#ifdef ALSADRIVER_CONFIG_DBG
@@ -378,16 +381,21 @@ namespace pipedal
}
private:
- void AlsaCleanup()
+ void AlsaCloseAudio()
{
+ std::lock_guard lock{restartMutex};
if (captureHandle)
{
+ Lv2Log::debug("ALSA capture handle closed.");
+ snd_pcm_drain(captureHandle);
snd_pcm_close(captureHandle);
captureHandle = nullptr;
}
if (playbackHandle)
{
+ Lv2Log::debug("ALSA playback handle closed.");
+ snd_pcm_drain(playbackHandle);
snd_pcm_close(playbackHandle);
playbackHandle = nullptr;
}
@@ -411,7 +419,10 @@ namespace pipedal
snd_pcm_sw_params_free(playbackSwParams);
playbackSwParams = nullptr;
}
- this->alsaSequencer = nullptr;
+ }
+ void AlsaCleanup()
+ {
+ AlsaCloseAudio();
}
std::string discover_alsa_using_apps()
@@ -1085,6 +1096,40 @@ namespace pipedal
}
}
+ void RestartAlsa()
+ {
+ std::lock_guard lock{restartMutex};
+ Lv2Log::info("Restarting ALSA devices.");
+
+ try
+ {
+ AlsaCloseAudio();
+ }
+ catch (const std::exception &e)
+ {
+ Lv2Log::error(SS("Error cleaning up ALSA: " << e.what()));
+ throw std::runtime_error("Unable to restart the audio stream.");
+ }
+ try
+ {
+ OpenAudio(this->jackServerSettings, this->channelSelection);
+ validate_capture_handle();
+ FillOutputBuffer();
+ audioRunning = true;
+ }
+ catch (const std::exception &e)
+ {
+ Lv2Log::error(SS("Error opening ALSA: " << e.what()));
+ 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.");
+ }
+ }
+
void PrepareCaptureFunctions(snd_pcm_format_t captureFormat)
{
this->captureFormat = captureFormat;
@@ -1221,6 +1266,8 @@ namespace pipedal
void OpenAudio(const JackServerSettings &jackServerSettings, const JackChannelSelection &channelSelection)
{
+ std::lock_guard lock{restartMutex};
+
int err;
alsa_device_name = jackServerSettings.GetAlsaInputDevice();
@@ -1245,7 +1292,7 @@ namespace pipedal
{
message =
SS("Device " << alsa_device_name << " in use. The following applications are using your soundcard: " << apps
- << ". Stop them as neccesary before trying to pipedald.");
+ << ". Stop them as neccesary before trying to start pipedald.");
}
else
{
@@ -1370,7 +1417,7 @@ namespace pipedal
{
throw PiPedalStateException(SS("Audio playback failed. " << snd_strerror(err)));
}
- std::this_thread::sleep_for(std::chrono::milliseconds(100));
+ std::this_thread::sleep_for(std::chrono::milliseconds(10));
continue;
}
if (avail == 0)
@@ -1386,94 +1433,106 @@ namespace pipedal
}
validate_capture_handle();
}
- void recover_from_output_underrun(snd_pcm_t *capture_handle, snd_pcm_t *playback_handle, int err)
+ void recover_from_output_underrun(snd_pcm_t *capture_handle, snd_pcm_t *playback_handle, int err, size_t framesRead)
{
validate_capture_handle();
- if (err == -EPIPE)
+ try
{
- err = snd_pcm_prepare(playback_handle);
- if (err < 0)
+
+ TraceBuffers(framesRead,'w');
+ if (err == -EPIPE)
{
- throw PiPedalStateException(SS("Can't recover from ALSA output underrun. (" << snd_strerror(err) << ")"));
+ err = snd_pcm_prepare(playback_handle);
+ if (err < 0)
+ {
+ Lv2Log::error(SS("Can't recover from ALSA output underrun. (" << snd_strerror(err) << ")"));
+ throw PiPedalStateException(SS("Can't recover from ALSA output underrun. (" << snd_strerror(err) << ")"));
+ }
+ snd_pcm_drain(capture_handle);
+ FillOutputBuffer();
+ }
+ else
+ {
+ 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) << ")"));
}
- FillOutputBuffer();
}
- else
+ catch (const std::exception &e)
{
- throw PiPedalStateException(SS("Can't recover from ALSA output error. (" << snd_strerror(err) << ")"));
+ Lv2Log::info(SS("Soft audio restart failed. " << e.what()));
+ RestartAlsa();
+ audioRunning = true;
}
validate_capture_handle();
}
- void recover_from_input_underrun(snd_pcm_t *capture_handle, snd_pcm_t *playback_handle, int err)
+ void recover_from_input_underrun(snd_pcm_t *capture_handle, snd_pcm_t *playback_handle, int err, size_t bufferedFrames)
{
validate_capture_handle();
- if (err == -EPIPE)
+ try
{
+ TraceBuffers(bufferedFrames,'r');
+ if (err == -EPIPE)
+ {
- // Unlink the streams before recovery
- snd_pcm_unlink(capture_handle);
+ // Unlink the streams before recovery
+ snd_pcm_unlink(capture_handle);
- err = snd_pcm_drop(capture_handle);
- if (err < 0)
- {
- throw PiPedalStateException(SS("Can't recover from ALSA underrun. (" << snd_strerror(err) << ")"));
- }
- err = snd_pcm_drop(playback_handle);
- if (err < 0)
- {
- throw PiPedalStateException(SS("Can't recover from ALSA underrun. (" << snd_strerror(err) << ")"));
- }
+ // Prepare both streams
+ if ((err = snd_pcm_prepare(playback_handle)) < 0)
+ {
+ throw std::runtime_error(SS("Cannot prepare playback stream: " << snd_strerror(err)));
+ }
+ if ((err = snd_pcm_prepare(capture_handle)) < 0)
+ {
+ throw std::runtime_error(SS("Cannot prepare capture stream: " << snd_strerror(err)));
+ }
- // Prepare both streams
- if ((err = snd_pcm_prepare(playback_handle)) < 0)
- {
- throw std::runtime_error(SS("Cannot prepare playback stream: " << snd_strerror(err)));
- }
- if ((err = snd_pcm_prepare(capture_handle)) < 0)
- {
- throw std::runtime_error(SS("Cannot prepare capture stream: " << snd_strerror(err)));
- }
+ // Resynchronize the streams
+ if ((err = snd_pcm_link(capture_handle, playback_handle)) < 0)
+ {
+ throw std::runtime_error(SS("Cannot relink streams: " << snd_strerror(err)));
+ }
- // Fill the playback buffer with silence
- FillOutputBuffer();
+ // Start the streams
+ FillOutputBuffer();
+ if ((err = snd_pcm_start(capture_handle)) < 0)
+ {
+ throw std::runtime_error(SS("Cannot restart capture stream: " << snd_strerror(err)));
+ }
- // Resynchronize the streams
- if ((err = snd_pcm_link(capture_handle, playback_handle)) < 0)
- {
- throw std::runtime_error(SS("Cannot relink streams: " << snd_strerror(err)));
+ validate_capture_handle();
}
+ else if (err == ESTRPIPE)
+ {
+ audioRunning = false;
+ validate_capture_handle();
- // Start the streams
- if ((err = snd_pcm_start(capture_handle)) < 0)
- {
- throw std::runtime_error(SS("Cannot restart capture stream: " << snd_strerror(err)));
- }
- validate_capture_handle();
- }
- else if (err == ESTRPIPE)
- {
- audioRunning = false;
- validate_capture_handle();
-
- while ((err = snd_pcm_resume(capture_handle)) == -EAGAIN)
- {
- sleep(1);
- }
- if (err < 0)
- {
- err = snd_pcm_prepare(capture_handle);
+ while ((err = snd_pcm_resume(capture_handle)) == -EAGAIN)
+ {
+ std::this_thread::sleep_for(std::chrono::milliseconds(1));
+ }
if (err < 0)
{
- throw PiPedalStateException(SS("Can't recover from ALSA suspend. (" << snd_strerror(err) << ")"));
+ err = snd_pcm_prepare(capture_handle);
+ if (err < 0)
+ {
+ throw PiPedalStateException(SS("Can't recover from ALSA suspend. (" << snd_strerror(err) << ")"));
+ }
}
+ audioRunning = true;
+ validate_capture_handle();
+ }
+ else
+ {
+ throw PiPedalStateException(SS("Can't recover from ALSA input error. (" << snd_strerror(err) << ")"));
}
- audioRunning = true;
- validate_capture_handle();
}
- else
+ catch (const std::exception &e)
{
- throw std::runtime_error(SS("Can't restart audio: " << snd_strerror(err)));
+ Lv2Log::info(SS("Soft audio restart failed. " << e.what()));
+ RestartAlsa();
+ audioRunning = true;
}
}
@@ -1516,6 +1575,7 @@ namespace pipedal
{
snd_pcm_wait(captureHandle, 1);
}
+ TraceBuffers(framesRead);
} while (frames > 0);
return framesRead;
}
@@ -1526,6 +1586,11 @@ namespace pipedal
AlsaMidiMessage message;
midiEventCount = 0;
+ auto alsaSequener = this->alsaSequencer; // take an addref
+ if (!alsaSequener)
+ {
+ return;
+ }
while (alsaSequencer->ReadMessage(message, 0))
{
size_t messageSize = message.size;
@@ -1580,6 +1645,7 @@ namespace pipedal
void AudioThread()
{
SetThreadName("alsaDriver");
+
try
{
SetThreadPriority(SchedulerPriority::RealtimeAudio);
@@ -1597,7 +1663,7 @@ namespace pipedal
}
CrashGuardLock crashGuardLock;
-
+
cpuUse.SetStartTime(cpuUse.Now());
while (true)
{
@@ -1628,7 +1694,7 @@ namespace pipedal
framesToRead)) < 0)
{
this->driverHost->OnUnderrun();
- recover_from_input_underrun(captureHandle, playbackHandle, nFrames);
+ recover_from_input_underrun(captureHandle, playbackHandle, nFrames, framesRead);
xrun = true;
break;
}
@@ -1665,7 +1731,8 @@ namespace pipedal
if (err < 0)
{
this->driverHost->OnUnderrun();
- recover_from_output_underrun(captureHandle, playbackHandle, err);
+
+ recover_from_output_underrun(captureHandle, playbackHandle, err, framesRead);
}
cpuUse.AddSample(ProfileCategory::Write);
}
@@ -1675,11 +1742,11 @@ namespace pipedal
Lv2Log::error(e.what());
Lv2Log::error("ALSA audio thread terminated abnormally.");
}
- this->driverHost->OnAlsaDriverStopped();
// if we terminated abnormally, pump messages until we have been terminated.
if (!terminateAudio())
{
+ this->driverHost->OnAlsaDriverStopped();
// zero out input buffers.
for (size_t i = 0; i < this->captureBuffers.size(); ++i)
{
@@ -1694,7 +1761,6 @@ namespace pipedal
while (!terminateAudio())
{
std::this_thread::sleep_for(std::chrono::milliseconds(10));
- // zero out input buffers.
this->driverHost->OnProcess(this->bufferSize);
}
}
@@ -1766,7 +1832,7 @@ namespace pipedal
}
audioThread = std::make_unique([this]()
- { AudioThread(); });
+ { AudioThread(); });
}
virtual void Deactivate()
@@ -1848,6 +1914,8 @@ namespace pipedal
Deactivate();
AlsaCleanup();
DeleteBuffers();
+ this->alsaSequencer = nullptr;
+
std::atomic_thread_fence(std::memory_order::release);
}
@@ -1940,7 +2008,7 @@ namespace pipedal
try
{
int err;
- for (int retry = 0; retry < 2; ++retry)
+ for (int retry = 0; retry < 4; ++retry)
{
err = snd_pcm_open(&playbackHandle, alsaDeviceName.c_str(), SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK);
if (err < 0) // field report of a device that is present, but won't immediately open.
@@ -2202,6 +2270,36 @@ namespace pipedal
#endif
}
+ struct BufferTrace
+ {
+ snd_pcm_sframes_t inAvail;
+ snd_pcm_sframes_t outAvail;
+ snd_pcm_sframes_t buffered;
+ snd_pcm_sframes_t total;
+ char code;
+ };
+
+ std::vector bufferTraces(1000);
+ size_t bufferTraceIndex;
+
+ void AlsaDriverImpl::TraceBuffers(size_t framesInBuffer,char code)
+ {
+ auto inAvail = snd_pcm_avail_update(this->captureHandle);
+ auto outAvail = snd_pcm_avail_update(this->playbackHandle);
+
+ auto total = inAvail + outAvail + framesInBuffer;
+ bufferTraces[bufferTraceIndex++] = {
+ inAvail,
+ outAvail,
+ (snd_pcm_sframes_t)framesInBuffer,
+ (snd_pcm_sframes_t)total,
+ code};
+ if (bufferTraceIndex == bufferTraces.size())
+ {
+ bufferTraceIndex = 0;
+ }
+ }
+
void FreeAlsaGlobals()
{
snd_config_update_free_global(); // to get a clean Valgrind report.
diff --git a/src/AudioHost.cpp b/src/AudioHost.cpp
index 3cdb7cc..79afb3c 100644
--- a/src/AudioHost.cpp
+++ b/src/AudioHost.cpp
@@ -34,6 +34,7 @@
#include "PluginHost.hpp"
#include "PatchPropertyWriter.hpp"
#include "CpuTemperatureMonitor.hpp"
+#include "restrict.hpp"
using namespace pipedal;
@@ -618,7 +619,7 @@ private:
{
for (size_t i = 0; i < audioDriver->OutputBufferCount(); ++i)
{
- float *out = (float *)audioDriver->GetOutputBuffer(i);
+ float * out = (float *)audioDriver->GetOutputBuffer(i);
if (out)
{
ZeroBuffer(out, nframes);
@@ -1141,7 +1142,7 @@ private:
{
try
{
- float *in, *out;
+ float * restrict in , * restrict out;
Lv2Pedalboard *pedalboard = nullptr;
pedalboard = this->realtimeActivePedalboard;
@@ -1724,6 +1725,7 @@ public:
{
Close();
active = false;
+ isOpen = false;
throw;
}
}
diff --git a/src/AvahiService.cpp b/src/AvahiService.cpp
index a3feb36..c88487d 100644
--- a/src/AvahiService.cpp
+++ b/src/AvahiService.cpp
@@ -40,6 +40,40 @@
using namespace pipedal;
+int avahiPollLockCount = 0;
+static std::mutex avahiLockMutex;
+class AvahiPollLock {
+public:
+ AvahiPollLock() = delete;
+ AvahiPollLock(AvahiThreadedPoll*threadedPoll)
+ : threadedPoll(threadedPoll) {
+ std::lock_guard lock(avahiLockMutex);
+ if (!threadedPoll) {
+ throw std::runtime_error("AvahiPollLock: threadedPoll is null.");
+ }
+ if (++avahiPollLockCount > 1) {
+ throw std::runtime_error("AvahiPollLock: nested locks are not allowed.");
+ }
+ avahi_threaded_poll_lock(threadedPoll);
+ }
+ void release() {
+ std::lock_guard lock(avahiLockMutex);
+ if (threadedPoll)
+ {
+ if (avahiPollLockCount <= 0) {
+ throw std::runtime_error("AvahiPollLock: release called without a lock.");
+ }
+ --avahiPollLockCount;
+ avahi_threaded_poll_unlock(threadedPoll);
+ threadedPoll = nullptr;
+ }
+ }
+ ~AvahiPollLock() {
+ release();
+ }
+private:
+ AvahiThreadedPoll *threadedPoll;
+};
void AvahiService::Announce(
int portNumber,
const std::string &name,
@@ -75,7 +109,7 @@ void AvahiService::Announce(
else
{
// already started, so we have to use poll_lock instead.
- avahi_threaded_poll_lock(threadedPoll);
+ AvahiPollLock lock(threadedPoll);
// all state that we have to protect with the lock now that the avahi serv3ce is running.
this->portNumber = portNumber;
@@ -104,7 +138,7 @@ void AvahiService::Announce(
Lv2Log::error("Failed to update mDNS service because of a synch problem.");
}
}
- avahi_threaded_poll_unlock(threadedPoll);
+ lock.release();
if (wait)
{
Wait();
@@ -118,30 +152,31 @@ void AvahiService::Unannounce(bool wait)
return;
if (this->threadedPoll)
{
- avahi_threaded_poll_lock(threadedPoll);
- if (started)
{
- if (this->createPending)
+ AvahiPollLock lock(threadedPoll);
+ if (started)
{
- // if service is starting up, and we haven't yet made our first announcement,
- // just signal that we don't want to announc.
- this->makeAnnouncement = false;
- }
- else
- {
- this->makeAnnouncement = false;
- // we have previously made a successful announcement. Retract it.
- if (group)
+ if (this->createPending)
{
- int rc = avahi_entry_group_reset(group);
- if (rc < 0)
+ // if service is starting up, and we haven't yet made our first announcement,
+ // just signal that we don't want to announc.
+ this->makeAnnouncement = false;
+ }
+ else
+ {
+ this->makeAnnouncement = false;
+ // we have previously made a successful announcement. Retract it.
+ if (group)
{
- Lv2Log::error("Avahi: failed to un-announce.");
+ int rc = avahi_entry_group_reset(group);
+ if (rc < 0)
+ {
+ Lv2Log::error("Avahi: failed to un-announce.");
+ }
}
}
}
}
- avahi_threaded_poll_unlock(threadedPoll);
if (wait)
{
WaitForUnannounce();
@@ -411,6 +446,7 @@ void AvahiService::Start()
if (!client)
{
+ AvahiPollLock lock(threadedPoll);
/* Allocate a new client */
client = avahi_client_new(avahi_threaded_poll_get(threadedPoll), AvahiClientFlags::AVAHI_CLIENT_NO_FAIL, client_callback, (void *)this, &error);
/* Check wether creating the client object succeeded */
@@ -442,20 +478,22 @@ void AvahiService::Stop()
if (threadedPoll)
{
- avahi_threaded_poll_lock(threadedPoll);
- if (group)
{
- avahi_entry_group_free(group);
- group = nullptr;
+ AvahiPollLock lock(threadedPoll);
+ if (group)
+ {
+ avahi_entry_group_free(group);
+ group = nullptr;
+ }
}
- avahi_threaded_poll_unlock(threadedPoll);
- avahi_threaded_poll_lock(threadedPoll);
- if (client)
{
- avahi_client_free(client);
- client = nullptr;
+ AvahiPollLock lock(threadedPoll);
+ if (client)
+ {
+ avahi_client_free(client);
+ client = nullptr;
+ }
}
- avahi_threaded_poll_unlock(threadedPoll);
avahi_threaded_poll_stop(threadedPoll);
avahi_threaded_poll_free(threadedPoll);
diff --git a/src/IHost.hpp b/src/IHost.hpp
index 0cc7eae..ab02649 100644
--- a/src/IHost.hpp
+++ b/src/IHost.hpp
@@ -46,7 +46,6 @@ namespace pipedal {
virtual int GetNumberOfInputAudioChannels() const = 0;
virtual int GetNumberOfOutputAudioChannels() const = 0;
virtual std::shared_ptr GetPluginInfo(const std::string &uri) const = 0;
- virtual std::shared_ptr GetHostWorkerThread() = 0;
virtual IEffect *CreateEffect(PedalboardItem &pedalboard) = 0;
diff --git a/src/Lv2Effect.cpp b/src/Lv2Effect.cpp
index 740a59f..c964222 100644
--- a/src/Lv2Effect.cpp
+++ b/src/Lv2Effect.cpp
@@ -18,6 +18,7 @@
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#include "pch.h"
+#include "restrict.hpp"
#include "Lv2Effect.hpp"
#include "PiPedalException.hpp"
#include
@@ -41,6 +42,7 @@
#include "AudioHost.hpp"
#include
#include "RingBufferReader.hpp"
+#include "Worker.hpp"
using namespace pipedal;
namespace fs = std::filesystem;
@@ -70,6 +72,11 @@ Lv2Effect::Lv2Effect(
this->bypass = pedalboardItem.isEnabled();
+ this->workerThread = std::make_unique();
+ if (info_->WantsWorkerThread())
+ {
+ workerThread->StartThread();
+ }
// stash a list of known file properties that we want to keep synced.
if (info->piPedalUI())
{
@@ -179,10 +186,8 @@ Lv2Effect::Lv2Effect(
const LV2_Worker_Interface *worker_interface =
(const LV2_Worker_Interface *)lilv_instance_get_extension_data(pInstance,
LV2_WORKER__interface);
- if (worker_interface)
- {
- this->worker = std::make_unique(pHost->GetHostWorkerThread(), pInstance, 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);
@@ -510,15 +515,15 @@ int Lv2Effect::GetControlIndex(const std::string &key) const
Lv2Effect::~Lv2Effect()
{
- if (activated)
- {
- Deactivate();
- }
if (worker)
{
worker->Close();
worker = nullptr; // delete the worker first!
}
+ if (activated)
+ {
+ Deactivate();
+ }
if (pInstance)
{
lilv_instance_free(pInstance);
@@ -631,7 +636,7 @@ void Lv2Effect::Deactivate()
lilv_instance_deactivate(pInstance);
}
-static inline void CopyBuffer(float *input, float *output, uint32_t frames)
+static inline void CopyBuffer(float *restrict input, float *restrict output, uint32_t frames)
{
for (uint32_t i = 0; i < frames; ++i)
{
@@ -669,7 +674,7 @@ void Lv2Effect::Run(uint32_t samples, RealtimeRingBufferWriter *realtimeRingBuff
{
for (size_t i = 0; i < this->outputMixBuffers.size(); ++i)
{
- float *__restrict input;
+ float *restrict input;
if (i >= this->inputAudioBuffers.size())
{
if (this->inputAudioBuffers.size() == 0)
@@ -682,8 +687,8 @@ void Lv2Effect::Run(uint32_t samples, RealtimeRingBufferWriter *realtimeRingBuff
{
input = this->inputAudioBuffers[i];
}
- float *__restrict pluginOutput = this->outputMixBuffers[i].data();
- float *__restrict finalOutput = this->outputAudioBuffers[i];
+ float *restrict pluginOutput = this->outputMixBuffers[i].data();
+ float *restrict finalOutput = this->outputAudioBuffers[i];
for (uint32_t i = 0; i < samples; ++i)
{
@@ -694,11 +699,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[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[i];
+ float *restrict finalOutput = this->outputAudioBuffers[i];
for (uint32_t i = 0; i < samples; ++i)
{
@@ -746,8 +751,8 @@ void Lv2Effect::Run(uint32_t samples, RealtimeRingBufferWriter *realtimeRingBuff
if (this->outputAudioBuffers.size() == 1)
{
- float *input = this->inputAudioBuffers[0];
- float *output = this->outputAudioBuffers[0];
+ float * restrict input = this->inputAudioBuffers[0];
+ float * restrict output = this->outputAudioBuffers[0];
for (uint32_t i = 0; i < samples; ++i)
{
output[i] = currentBypass * output[i] + (1 - currentBypass) * input[i];
@@ -762,8 +767,8 @@ void Lv2Effect::Run(uint32_t samples, RealtimeRingBufferWriter *realtimeRingBuff
}
else
{
- float *inputL;
- float *inputR;
+ float * restrict inputL;
+ float * restrict inputR;
if (this->inputAudioBuffers.size() == 1)
{
inputL = inputR = inputAudioBuffers[0];
@@ -773,8 +778,8 @@ void Lv2Effect::Run(uint32_t samples, RealtimeRingBufferWriter *realtimeRingBuff
inputL = inputAudioBuffers[0];
inputR = inputAudioBuffers[1];
}
- float *outputL = outputAudioBuffers[0];
- float *outputR = outputAudioBuffers[1];
+ float * restrict outputL = outputAudioBuffers[0];
+ float * restrict outputR = outputAudioBuffers[1];
for (uint32_t i = 0; i < samples; ++i)
{
outputL[i] = currentBypass * outputL[i] + (1 - currentBypass) * inputL[i];
diff --git a/src/Lv2Effect.hpp b/src/Lv2Effect.hpp
index 0dd80e3..9878b4a 100644
--- a/src/Lv2Effect.hpp
+++ b/src/Lv2Effect.hpp
@@ -40,12 +40,12 @@
#include "StateInterface.hpp"
#include "LogFeature.hpp"
-
namespace pipedal
{
class RealtimeRingBufferWriter;
class IPatchWriterCallback;
+ class HostWorkerThread;
class Lv2Effect : public IEffect, private LogFeature::LogMessageListener
{
@@ -57,6 +57,10 @@ namespace pipedal
private:
+
+ std::shared_ptr workerThread;
+ std::unique_ptr worker;
+
std::unordered_map controlIndex;
FileBrowserFilesFeature fileBrowserFilesFeature;
@@ -70,7 +74,6 @@ namespace pipedal
int numberOfOutputs = 0;
int numberOfMidiInputs = 0;
- std::unique_ptr worker;
LilvInstance *pInstance;
std::shared_ptr info;
std::vector controlValues;
diff --git a/src/Lv2Pedalboard.cpp b/src/Lv2Pedalboard.cpp
index df6d0bd..4721f95 100644
--- a/src/Lv2Pedalboard.cpp
+++ b/src/Lv2Pedalboard.cpp
@@ -28,6 +28,7 @@
#include "Lv2EventBufferWriter.hpp"
#include "Lv2Log.hpp"
#include "CrashGuard.hpp"
+#include "restrict.hpp"
using namespace pipedal;
@@ -417,7 +418,7 @@ void Lv2Pedalboard::Deactivate()
}
}
-static void Copy(float *input, float *output, uint32_t samples)
+static void Copy(float *restrict input, float *restrict output, uint32_t samples)
{
for (uint32_t i = 0; i < samples; ++i)
{
@@ -434,6 +435,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();
diff --git a/src/PluginHost.cpp b/src/PluginHost.cpp
index 5a796cb..673797f 100644
--- a/src/PluginHost.cpp
+++ b/src/PluginHost.cpp
@@ -47,6 +47,7 @@
#include "StdErrorCapture.hpp"
#include "util.hpp"
#include "ModFileTypes.hpp"
+#include
#include "Locale.hpp"
@@ -301,7 +302,6 @@ PluginHost::PluginHost()
this->urids = new Urids(mapFeature);
- pHostWorkerThread = std::make_shared();
}
void PluginHost::OnConfigurationChanged(const JackConfiguration &configuration, const JackChannelSelection &settings)
@@ -870,6 +870,13 @@ Lv2PluginInfo::Lv2PluginInfo(PluginHost *lv2Host, LilvWorld *pWorld, const LilvP
const LilvPluginClass *pClass = lilv_plugin_get_class(pPlugin);
this->plugin_class_ = nodeAsString(lilv_plugin_class_get_uri(pClass));
+ if (this->plugin_class_ == "") // carla does this.
+ {
+ // set to plain Plugin.
+ this->plugin_class_ = "http://lv2plug.in/ns/lv2core#Plugin";
+ }
+
+
AutoLilvNodes required_features = lilv_plugin_get_required_features(pPlugin);
this->required_features_ = nodeAsStringArray(required_features);
@@ -1668,10 +1675,6 @@ bool Lv2PluginInfo::isSplit() const
{
return uri_ == SPLIT_PEDALBOARD_ITEM_URI;
}
-std::shared_ptr PluginHost::GetHostWorkerThread()
-{
- return pHostWorkerThread;
-}
class ResourceInfo
{
@@ -1883,6 +1886,13 @@ Lv2PatchPropertyInfo::Lv2PatchPropertyInfo(PluginHost *pluginHost, const LilvNod
}
}
+// ffs.
+static inline bool contains(const std::vector &vec, const std::string &value) {
+ return std::find(vec.begin(),vec.end(),value) != vec.end();
+}
+bool Lv2PluginInfo::WantsWorkerThread() const {
+ return contains(this->required_features_,LV2_WORKER__schedule) || contains(this->supported_features_,LV2_WORKER__schedule);
+}
// void PiPedalHostLogError(const std::string &error)
// {
// Lv2Log::error("%s",error.c_str());
diff --git a/src/PluginHost.hpp b/src/PluginHost.hpp
index 5b9c202..f4c7de1 100644
--- a/src/PluginHost.hpp
+++ b/src/PluginHost.hpp
@@ -454,6 +454,8 @@ namespace pipedal
LV2_PROPERTY_GETSET(patchProperties)
LV2_PROPERTY_GETSET(hasDefaultState)
+ bool WantsWorkerThread() const;
+
const Lv2PortInfo &getPort(const std::string &symbol)
{
for (size_t i = 0; i < ports_.size(); ++i)
@@ -913,7 +915,6 @@ namespace pipedal
}
private:
- std::shared_ptr pHostWorkerThread;
// IHost implementation.
virtual void SetMaxAudioBufferSize(size_t size) { maxBufferSize = size; }
virtual size_t GetMaxAudioBufferSize() const { return maxBufferSize; }
@@ -922,7 +923,6 @@ namespace pipedal
virtual int GetNumberOfInputAudioChannels() const { return numberOfAudioInputChannels; }
virtual int GetNumberOfOutputAudioChannels() const { return numberOfAudioOutputChannels; }
virtual LV2_Feature *const *GetLv2Features() const { return (LV2_Feature *const *)&(this->lv2Features[0]); }
- virtual std::shared_ptr GetHostWorkerThread();
public:
virtual MapFeature &GetMapFeature() override { return this->mapFeature; }
diff --git a/src/SchedulerPriority.cpp b/src/SchedulerPriority.cpp
index 0eb5194..8effce9 100644
--- a/src/SchedulerPriority.cpp
+++ b/src/SchedulerPriority.cpp
@@ -104,7 +104,7 @@ static void SetPriority(int realtimePriority, const char *priorityName)
int result = sched_setscheduler(0, SCHED_RR, ¶m);
if (result == 0)
{
- Lv2Log::debug("Service thread priority successfully boosted.");
+ Lv2Log::debug(SS("Service thread priority successfully boosted. (" << priorityName << ")"));
return;
}
else
diff --git a/src/Worker.cpp b/src/Worker.cpp
index f94b72d..3903f62 100644
--- a/src/Worker.cpp
+++ b/src/Worker.cpp
@@ -15,7 +15,7 @@
*/
// (Borrows heavily from worker.c by David Robillard.)
-// Copyright (c) 2022 Robin Davies
+// Copyright (c) 2024 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
@@ -143,6 +143,7 @@ bool Worker::EmitResponses()
void Worker::WaitForAllResponses()
{
+ // do what we can to clear the response queue in order to avoid memory/object leaks
using Clock = std::chrono::steady_clock;
auto startTime = Clock::now();
while (true)
@@ -157,11 +158,15 @@ void Worker::WaitForAllResponses()
break;
}
}
- std::chrono::seconds waitDuration = std::chrono::duration_cast(Clock::now()-startTime);
- if (waitDuration.count() > 5) {
- throw std::logic_error("Timed out waiting for a Worker task to complete.");
+ // pump the plugin with a zero-length buffer.
+
+ std::this_thread::sleep_for(std::chrono::milliseconds(50));
+ std::chrono::milliseconds waitDuration = std::chrono::duration_cast(Clock::now()-startTime);
+ if (waitDuration.count() > 1500) {
+ // better to leak than to terminate the application.
+ Lv2Log::error("Timed out waiting for a Worker task to complete.");
+ break;
}
- std::this_thread::sleep_for(std::chrono::milliseconds(15));
}
}
@@ -242,26 +247,34 @@ void HostWorkerThread::ThreadProc() noexcept
HostWorkerThread::HostWorkerThread()
{
this->dataBuffer.resize(16*1024);
- pThread = std::make_unique([this]()
- { this->ThreadProc(); });
+}
+
+bool HostWorkerThread::StartThread()
+{
+ if (!pThread)
+ {
+ if (closed) {
+ return false;
+ }
+ pThread = std::make_unique([this]()
+ { this->ThreadProc(); });
+ }
+ return true;
}
void HostWorkerThread::Close()
{
- bool sendClose = false;
{
std::lock_guard lock{submitMutex};
- if (!closed)
- {
- closed = true;
- sendClose = true;
+ if (pThread) {
+ if (!closed)
+ {
+ closed = true;
+ ScheduleWorkNoLock(nullptr, 0, nullptr);
+ }
}
}
- if (sendClose)
- {
- ScheduleWork(nullptr, 0, nullptr);
- }
}
HostWorkerThread::~HostWorkerThread()
{
@@ -278,7 +291,10 @@ LV2_Worker_Status HostWorkerThread::ScheduleWork(Worker *worker, size_t size, co
{
std::lock_guard lock(submitMutex);
- if (exiting)
+ if (!StartThread()) { // ensures thread is running, when plugins haven't declared they want a worker thread.
+ return LV2_Worker_Status::LV2_WORKER_ERR_NO_SPACE;
+ }
+ if (exiting )
{
return LV2_Worker_Status::LV2_WORKER_ERR_NO_SPACE;
}
@@ -286,6 +302,11 @@ LV2_Worker_Status HostWorkerThread::ScheduleWork(Worker *worker, size_t size, co
{
exiting = true;
}
+ return ScheduleWorkNoLock(worker, size, data);
+
+}
+LV2_Worker_Status HostWorkerThread::ScheduleWorkNoLock(Worker *worker, size_t size, const void *data)
+{
if (requestRingBuffer.writeSpace() < sizeof(worker) + sizeof(size) + size)
{
@@ -307,7 +328,12 @@ LV2_Worker_Status HostWorkerThread::ScheduleWork(Worker *worker, size_t size, co
void Worker::RunBackgroundTask(size_t size, uint8_t *data)
{
- workerInterface->work(lilvInstance->lv2_handle, worker_respond_fn, (LV2_Handle)this, size, data);
+ try {
+ workerInterface->work(lilvInstance->lv2_handle, worker_respond_fn, (LV2_Handle)this, size, data);
+ } catch (const std::exception &e)
+ {
+ Lv2Log::error(SS("Unhandled exception on LV2 Worker thread: " << e.what()));
+ }
{
std::lock_guard lock { this->outstandingRequestMutex};
--this->outstandingRequests;
diff --git a/src/Worker.hpp b/src/Worker.hpp
index 8c54507..154ec3d 100644
--- a/src/Worker.hpp
+++ b/src/Worker.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
@@ -32,6 +32,7 @@
#include "lv2/atom/atom.h"
#include "lv2/worker/worker.h"
#include "condition_variable"
+#include
#include