Merge pull request #410 from rerdavies/dev v1.5.94

Dev v1.5.94 merge
This commit is contained in:
Robin Davies
2025-10-02 13:49:17 -04:00
committed by GitHub
29 changed files with 1107 additions and 328 deletions
+2 -2
View File
@@ -1,13 +1,13 @@
cmake_minimum_required(VERSION 3.16.0) cmake_minimum_required(VERSION 3.16.0)
project(pipedal project(pipedal
VERSION 1.5.93 VERSION 1.5.94
DESCRIPTION "PiPedal Guitar Effect Pedal For Raspberry Pi" DESCRIPTION "PiPedal Guitar Effect Pedal For Raspberry Pi"
HOMEPAGE_URL "https://rerdavies.github.io/pipedal" HOMEPAGE_URL "https://rerdavies.github.io/pipedal"
) )
execute_process( COMMAND dpkg --print-architecture COMMAND tr -d '\n' OUTPUT_VARIABLE DEBIAN_ARCHITECTURE ) execute_process( COMMAND dpkg --print-architecture COMMAND tr -d '\n' OUTPUT_VARIABLE DEBIAN_ARCHITECTURE )
set (DISPLAY_VERSION "PiPedal v1.5.93-Experimental") set (DISPLAY_VERSION "PiPedal v1.5.94-Experimental")
set (PACKAGE_ARCHITECTURE ${DEBIAN_ARCHITECTURE}) set (PACKAGE_ARCHITECTURE ${DEBIAN_ARCHITECTURE})
set (CMAKE_INSTALL_PREFIX "/usr/") set (CMAKE_INSTALL_PREFIX "/usr/")
+2 -2
View File
@@ -9,11 +9,11 @@
<img src="https://img.shields.io/github/downloads/rerdavies/pipedal/total?color=%23808080&link=https%3A%2F%2Frerdavies.github.io%2Fpipedal%2Fdownload.html"/> <img src="https://img.shields.io/github/downloads/rerdavies/pipedal/total?color=%23808080&link=https%3A%2F%2Frerdavies.github.io%2Fpipedal%2Fdownload.html"/>
Download:&nbsp;<a href='https://rerdavies.github.io/pipedal/download.html'>v1.5.93</a> Download:&nbsp;<a href='https://rerdavies.github.io/pipedal/download.html'>v1.5.94</a>
Website:&nbsp;[https://rerdavies.github.io/pipedal](https://rerdavies.github.io/pipedal). Website:&nbsp;[https://rerdavies.github.io/pipedal](https://rerdavies.github.io/pipedal).
Documentation:&nbsp;[https://rerdavies.github.io/pipedal/Documentation.html](https://rerdavies.github.io/pipedal/Documentation.html). Documentation:&nbsp;[https://rerdavies.github.io/pipedal/Documentation.html](https://rerdavies.github.io/pipedal/Documentation.html).
#### NEW version 1.5.93 Release. See the [release notes](https://rerdavies.github.io/pipedal/ReleaseNotes) for details. TooB NAM calibration, manipulation of i/r files in Toob Convolution Reverb, new Parametric EQ and 3 Band EQ plugins, and MORE! #### NEW version 1.5.94 Release. See the [release notes](https://rerdavies.github.io/pipedal/ReleaseNotes) for details. TooB NAM calibration, manipulation of i/r files in Toob Convolution Reverb, new Parametric EQ and 3 Band EQ plugins, and MORE!
&nbsp; &nbsp;
+5 -5
View File
@@ -13,18 +13,18 @@ page_icon: img/Install4.jpg
Download the most recent Debian (.deb) package for your platform: Download the most recent Debian (.deb) package for your platform:
- [Raspberry Pi OS bookworm (aarch64) v1.5.93](https://github.com/rerdavies/pipedal/releases/download/v1.5.93/pipedal_1.5.93_arm64.deb) - [Raspberry Pi OS bookworm (aarch64) v1.5.94](https://github.com/rerdavies/pipedal/releases/download/v1.5.94/pipedal_1.5.94_arm64.deb)
- [Ubuntu 24.04 through 25.04 (aarch64) v1.5.93](https://github.com/rerdavies/pipedal/releases/download/v1.5.93/pipedal_1.5.93_arm64.deb) - [Ubuntu 24.04 through 25.04 (aarch64) v1.5.94](https://github.com/rerdavies/pipedal/releases/download/v1.5.94/pipedal_1.5.94_arm64.deb)
- [Ubuntu 24.04 through 25.04 (amd64) v1.5.93](https://github.com/rerdavies/pipedal/releases/download/v1.5.93/pipedal_1.5.93_amd64.deb) - [Ubuntu 24.04 through 25.04 (amd64) v1.5.94](https://github.com/rerdavies/pipedal/releases/download/v1.5.94/pipedal_1.5.94_amd64.deb)
Version 1.5.93 has been tested on Raspberry Pi OS bookworm, Ubuntu 24.04 (amd64), Ubuntu 24.10 (aarch64), and Ubuntu 25.04 (aarch64). Download the appropriate package for your platform, and install using the following procedure: Version 1.5.94 has been tested on Raspberry Pi OS bookworm, Ubuntu 24.04 (amd64), Ubuntu 24.10 (aarch64), and Ubuntu 25.04 (aarch64). Download the appropriate package for your platform, and install using the following procedure:
``` ```
sudo apt update sudo apt update
sudo apt upgrade sudo apt upgrade
cd ~/Downloads cd ~/Downloads
sudo apt-get install ./pipedal_1.5.93_arm64.deb sudo apt-get install ./pipedal_1.5.94_arm64.deb
``` ```
You MUST use `apt-get`. `apt` will not install downloaded packages; and `dpkg -i` will not install dependencies. You MUST use `apt-get`. `apt` will not install downloaded packages; and `dpkg -i` will not install dependencies.
+11
View File
@@ -1,5 +1,16 @@
# Release Notes # Release Notes
## PiPedal 1.5.94 Beta
A minor release that allows use of plugins with audio sidechains.
### Features in this release
- **Sidechains**. You can now load plugins that have sidechain inputs. This allows you to use compressor and gate plugins, particularly, that often have sidechains, but also enables a broad class of dynamics processing plugins that would not be loadable in previous versions of PiPedal. Plugins that have sidechain inputs will show a dropdown control in their control view which allows you to select the source of the sidechain input. The source plugin for the sidechain must appear earlier in the signal chain.
Although sidechain processing is occasionally useful in guitar effect pedalboards, the more important part of this feature is that it allows you to load plugins that were rejected by previous versions of PiPedal because they had optional sidechain inputs.
## PiPedal 1.5.93 Release ## PiPedal 1.5.93 Release
### Major Features in This Build ### Major Features in This Build
+1 -1
View File
@@ -17,7 +17,7 @@ Here is a brief list of other particularly recommended plugin collections.
| Collection | To Install | Description | | Collection | To Install | Description |
|---------------------------------|---------------------------------------|------------------| |---------------------------------|---------------------------------------|------------------|
| [Guitarix](https://guitarix.org) ★★★☆☆ | `sudo apt install guitarix-lv2` | A large collection of guitar amplifiers and effects. Prior to the advent of neural network amplifier emulations, Guitarix was the go-to source for open-source amp modeling.| | [Guitarix](https://guitarix.org) ★★★☆☆ | `sudo apt install guitarix-lv2` | A large collection of guitar amplifiers and effects. Prior to the advent of neural network amplifier emulations, Guitarix was the go-to source for open-source amp modeling.|
| [GxPlugins](https://github.com/brummer10/GxPlugins.lv2) ★★★★★ | `sudo apt install gxplugins` | Additional amps and effects from the the principle developer of Guitarix, many of which are significantly superior to the original Guitarix plugins. Most of these plugins | [GxPlugins](https://github.com/brummer10/GxPlugins.lv2) ★★★★★ | On Raspberry Pi Os: `sudo apt install gxplugins`, on amd64/x64 donwload from [Github](https://github.com/brummer10/GxPlugins.lv2/releases) | Additional amps and effects from the the principle developer of Guitarix, many of which are significantly superior to the original Guitarix plugins. Most of these plugins
use circuit simulation to directly model actual original circuits. The results are uniformly excellent.| use circuit simulation to directly model actual original circuits. The results are uniformly excellent.|
| [DPF Plugins](https://github.com/DISTRHO/DPF-Plugins) ★★★★★ | `sudo apt install dpf-plugins-lv2` | Useful and interesting guitar effect plugins. Bit crusher, pitch shift, reverbs, 3-band EQ and more. | | [DPF Plugins](https://github.com/DISTRHO/DPF-Plugins) ★★★★★ | `sudo apt install dpf-plugins-lv2` | Useful and interesting guitar effect plugins. Bit crusher, pitch shift, reverbs, 3-band EQ and more. |
| [MDA Plugins](http://drobilla.net/software/mda-lv2.html) ★★★★☆ | `sudo apt install mda-lv2` | 36 high-quality plugs | | [MDA Plugins](http://drobilla.net/software/mda-lv2.html) ★★★★☆ | `sudo apt install mda-lv2` | 36 high-quality plugs |
+4 -4
View File
@@ -4,9 +4,9 @@
Download the most recent Debian (.deb) package for your platform: Download the most recent Debian (.deb) package for your platform:
- [Raspberry Pi OS bookworm (aarch64) v1.5.93 Beta](https://github.com/rerdavies/pipedal/releases/download/v1.5.93/pipedal_1.5.93_arm64.deb) - [Raspberry Pi OS bookworm (aarch64) v1.5.94 Beta](https://github.com/rerdavies/pipedal/releases/download/v1.5.94/pipedal_1.5.94_arm64.deb)
- [Ubuntu 24.x, 25.04 (aarch64) v1.5.93 Alpha](https://github.com/rerdavies/pipedal/releases/download/v1.5.93/pipedal_1.5.93_arm64.deb) - [Ubuntu 24.x, 25.04 (aarch64) v1.5.94 Alpha](https://github.com/rerdavies/pipedal/releases/download/v1.5.94/pipedal_1.5.94_arm64.deb)
- [Ubuntu 24.x, 25.04 (amd64) v1.5.93 Alpha](https://github.com/rerdavies/pipedal/releases/download/v1.5.93/pipedal_1.5.93_amd64.deb) - [Ubuntu 24.x, 25.04 (amd64) v1.5.94 Alpha](https://github.com/rerdavies/pipedal/releases/download/v1.5.94/pipedal_1.5.94_amd64.deb)
Install the package by running Install the package by running
@@ -14,7 +14,7 @@ Install the package by running
``` ```
sudo apt update sudo apt update
cd ~/Downloads cd ~/Downloads
sudo apt-get install ./pipedal_1.5.93_arm64.deb sudo apt-get install ./pipedal_1.5.94_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 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. expected, and can be safely ignored.
+3
View File
@@ -54,7 +54,9 @@ namespace pipedal {
virtual int GetNumberOfInputAudioBuffers() const = 0; // may be different if plugin has zero inputs. virtual int GetNumberOfInputAudioBuffers() const = 0; // may be different if plugin has zero inputs.
virtual int GetNumberOfOutputAudioBuffers() const = 0; // may be different if plugin has zero inputs. virtual int GetNumberOfOutputAudioBuffers() const = 0; // may be different if plugin has zero inputs.
virtual int GetNumberOfSidechainAudioBuffers() const { return 0; }
virtual float *GetAudioInputBuffer(int index) const = 0; virtual float *GetAudioInputBuffer(int index) const = 0;
virtual float *GetAudioSidechainBuffer(int index) const { throw std::runtime_error("Not implemented"); }
virtual float *GetAudioOutputBuffer(int index) const = 0; virtual float *GetAudioOutputBuffer(int index) const = 0;
virtual void ResetAtomBuffers() = 0; virtual void ResetAtomBuffers() = 0;
@@ -65,6 +67,7 @@ namespace pipedal {
virtual void SetAudioInputBuffer(int index, float *buffer) = 0; virtual void SetAudioInputBuffer(int index, float *buffer) = 0;
virtual void SetAudioOutputBuffer(int index, float*buffer) = 0; virtual void SetAudioOutputBuffer(int index, float*buffer) = 0;
virtual void SetAudioSidechainBuffer(int index, float *buffer) { throw std::runtime_error("Not implemented");}
virtual void Activate() = 0; virtual void Activate() = 0;
+78 -5
View File
@@ -384,7 +384,12 @@ void Lv2Effect::PreparePortIndices()
{ {
if (port->is_input()) if (port->is_input())
{ {
this->inputAudioPortIndices.push_back(portIndex); if (port->is_sidechain())
{
this->inputSidechainPortIndices.push_back(portIndex);
} else {
this->inputAudioPortIndices.push_back(portIndex);
}
} }
else else
{ {
@@ -432,6 +437,7 @@ void Lv2Effect::PreparePortIndices()
this->maxInputControlPort = maxInputControlPort; this->maxInputControlPort = maxInputControlPort;
inputAudioBuffers.resize(inputAudioPortIndices.size()); inputAudioBuffers.resize(inputAudioPortIndices.size());
inputSidechainBuffers.resize(inputSidechainPortIndices.size());
outputAudioBuffers.resize(outputAudioPortIndices.size()); outputAudioBuffers.resize(outputAudioPortIndices.size());
inputAtomBuffers.resize(inputAtomPortIndices.size()); inputAtomBuffers.resize(inputAtomPortIndices.size());
outputAtomBuffers.resize(outputAtomPortIndices.size()); outputAtomBuffers.resize(outputAtomPortIndices.size());
@@ -439,9 +445,7 @@ void Lv2Effect::PreparePortIndices()
if (RequiresBufferStaging()) if (RequiresBufferStaging())
{ {
EnableBufferStaging( EnableBufferStaging(
GetStagedBufferSize(), GetStagedBufferSize());
this->GetNumberOfInputAudioBuffers(),
this->GetNumberOfOutputAudioBuffers());
} }
} }
@@ -473,6 +477,7 @@ void Lv2Effect::PrepareNoInputEffect(int numberOfInputs, size_t maxBufferSize)
} }
} }
void Lv2Effect::SetAudioInputBuffer(int index, float *buffer) void Lv2Effect::SetAudioInputBuffer(int index, float *buffer)
{ {
this->inputAudioBuffers.at(index) = buffer; this->inputAudioBuffers.at(index) = buffer;
@@ -512,6 +517,32 @@ void Lv2Effect::SetAudioInputBuffer(int index, float *buffer)
// } // }
} }
} }
void Lv2Effect::SetAudioSidechainBuffer(int index, float *buffer)
{
this->inputSidechainBuffers.at(index) = buffer;
if (borrowedEffect)
{
// Already running on the realtime thread,
// so don't update the audio ports until the effect gets placed on the realtime thread.
return;
}
if (stagingBufferSize != 0)
{
int pluginIndex = this->inputSidechainPortIndices.at(index);
if (index >= sidechainStagingBufferPointers.size())
{
throw std::runtime_error("Invalid input staging buffer index.");
}
lilv_instance_connect_port(this->pInstance, pluginIndex, sidechainStagingBufferPointers.at(index));
}
else
{
int pluginIndex = this->inputSidechainPortIndices.at(index);
lilv_instance_connect_port(this->pInstance, pluginIndex, buffer);
}
}
void Lv2Effect::SetAudioInputBuffer(float *left) void Lv2Effect::SetAudioInputBuffer(float *left)
{ {
@@ -664,6 +695,14 @@ void Lv2Effect::UpdateAudioPorts()
lilv_instance_connect_port(pInstance, portIndex, outputStagingBufferPointers.at(i)); lilv_instance_connect_port(pInstance, portIndex, outputStagingBufferPointers.at(i));
} }
} }
for (size_t i = 0; i < this->inputSidechainPortIndices.size(); ++i)
{
int portIndex = this->inputSidechainPortIndices.at(i);
if (sidechainStagingBufferPointers.at(i) != nullptr)
{
lilv_instance_connect_port(pInstance, portIndex, sidechainStagingBufferPointers.at(i));
}
}
for (size_t i = 0; i < this->inputAtomPortIndices.size(); ++i) for (size_t i = 0; i < this->inputAtomPortIndices.size(); ++i)
{ {
if (i == 0) if (i == 0)
@@ -710,6 +749,15 @@ void Lv2Effect::UpdateAudioPorts()
lilv_instance_connect_port(pInstance, portIndex, GetAudioOutputBuffer(i)); lilv_instance_connect_port(pInstance, portIndex, GetAudioOutputBuffer(i));
} }
} }
for (size_t i = 0; i < this->inputSidechainPortIndices.size(); ++i)
{
int portIndex = this->inputSidechainPortIndices.at(i);
if (GetAudioSidechainBuffer(i) != nullptr)
{
lilv_instance_connect_port(pInstance, portIndex, GetAudioSidechainBuffer(i));
}
}
for (size_t i = 0; i < this->inputAtomPortIndices.size(); ++i) for (size_t i = 0; i < this->inputAtomPortIndices.size(); ++i)
{ {
auto atomInputBuffer = this->GetAtomInputBuffer(i); auto atomInputBuffer = this->GetAtomInputBuffer(i);
@@ -877,6 +925,16 @@ size_t Lv2Effect::stageToInput(size_t inputSampleOffset, size_t samples)
pOutput[i] = pInput[i]; pOutput[i] = pInput[i];
} }
} }
for (size_t nSidechain = 0; nSidechain < this->inputSidechainBuffers.size(); ++nSidechain)
{
float *restrict pInput = this->inputSidechainBuffers[nSidechain] + inputSampleOffset;
float *restrict pOutput = this->sidechainStagingBufferPointers.at(nSidechain) + this->stagingInputIx;
for (size_t i = 0; i < thisTime; ++i)
{
pOutput[i] = pInput[i];
}
}
this->stagingInputIx += thisTime; this->stagingInputIx += thisTime;
inputSampleOffset += thisTime; inputSampleOffset += thisTime;
@@ -1487,8 +1545,11 @@ void Lv2Effect::SetPathPatchProperty(const std::string &propertyUri, const std::
mainThreadPathProperties[propertyUri] = jsonAtom; mainThreadPathProperties[propertyUri] = jsonAtom;
} }
void Lv2Effect::EnableBufferStaging(size_t bufferSize, size_t nInputs, size_t nOutputs) void Lv2Effect::EnableBufferStaging(size_t bufferSize )
{ {
size_t nInputs = this->GetNumberOfInputAudioBuffers();
size_t nSidechainInputs = this->GetNumberOfSidechainAudioBuffers();
size_t nOutputs = this->GetNumberOfOutputAudioBuffers();
stagingBufferSize = bufferSize; stagingBufferSize = bufferSize;
stagingOutputIx = bufferSize; stagingOutputIx = bufferSize;
@@ -1520,6 +1581,12 @@ void Lv2Effect::EnableBufferStaging(size_t bufferSize, size_t nInputs, size_t nO
inputStagingBuffers[i][bufferSize] = 99.9f; // guard entry inputStagingBuffers[i][bufferSize] = 99.9f; // guard entry
inputStagingBufferPointers.at(i) = inputStagingBuffers.at(i).data(); inputStagingBufferPointers.at(i) = inputStagingBuffers.at(i).data();
} }
for (size_t i = 0; i < nSidechainInputs; ++i)
{
sidechainStagingBuffers.at(i).resize(bufferSize + 1);
sidechainStagingBuffers[i][bufferSize] = 99.9f; // guard entry
sidechainStagingBufferPointers.at(i) = sidechainStagingBuffers.at(i).data();
}
for (size_t i = 0; i < nOutputs; ++i) for (size_t i = 0; i < nOutputs; ++i)
{ {
outputStagingBuffers.at(i).resize(bufferSize + 1); outputStagingBuffers.at(i).resize(bufferSize + 1);
@@ -1571,6 +1638,12 @@ float *Lv2Effect::GetAudioInputBuffer(int index) const
throw std::range_error("Lv2Effect::GetAudioInputBuffer"); throw std::range_error("Lv2Effect::GetAudioInputBuffer");
return this->inputAudioBuffers.at(index); return this->inputAudioBuffers.at(index);
} }
float *Lv2Effect::GetAudioSidechainBuffer(int index) const
{
if (index < 0 || index >= this->inputSidechainBuffers.size())
throw std::range_error("Lv2Effect::GetAudioSidechainBuffer");
return this->inputSidechainBuffers.at(index);
}
float *Lv2Effect::GetAudioOutputBuffer(int index) const float *Lv2Effect::GetAudioOutputBuffer(int index) const
{ {
+20 -11
View File
@@ -81,6 +81,8 @@ namespace pipedal
std::vector<int> inputAudioPortIndices; std::vector<int> inputAudioPortIndices;
std::vector<int> outputAudioPortIndices; std::vector<int> outputAudioPortIndices;
std::vector<int> inputSidechainPortIndices;
std::vector<int> inputAtomPortIndices; std::vector<int> inputAtomPortIndices;
std::vector<int> outputAtomPortIndices; std::vector<int> outputAtomPortIndices;
@@ -90,6 +92,7 @@ namespace pipedal
std::vector<int> midiInputIndices; std::vector<int> midiInputIndices;
std::vector<float *> inputAudioBuffers; std::vector<float *> inputAudioBuffers;
std::vector<float *> inputSidechainBuffers;
std::vector<float *> outputAudioBuffers; std::vector<float *> outputAudioBuffers;
std::vector<char *> inputAtomBuffers; std::vector<char *> inputAtomBuffers;
@@ -212,7 +215,7 @@ namespace pipedal
bool borrowedEffect = false; bool borrowedEffect = false;
bool activated = false; bool activated = false;
void EnableBufferStaging(size_t bufferSize, size_t nInputs, size_t nOutputs); void EnableBufferStaging(size_t bufferSize);
void CheckStagingBufferSentries(); void CheckStagingBufferSentries();
public: public:
@@ -262,8 +265,10 @@ namespace pipedal
size_t stagingInputIx = 0; size_t stagingInputIx = 0;
size_t stagingOutputIx = 0; size_t stagingOutputIx = 0;
std::vector<std::vector<float>> inputStagingBuffers; std::vector<std::vector<float>> inputStagingBuffers;
std::vector<std::vector<float>> sidechainStagingBuffers;
std::vector<std::vector<float>> outputStagingBuffers; std::vector<std::vector<float>> outputStagingBuffers;
std::vector<float*> inputStagingBufferPointers; std::vector<float*> inputStagingBufferPointers;
std::vector<float*> sidechainStagingBufferPointers;
std::vector<float*> outputStagingBufferPointers; std::vector<float*> outputStagingBufferPointers;
std::vector<uint8_t> stagedInputAtomBuffer; std::vector<uint8_t> stagedInputAtomBuffer;
@@ -297,22 +302,26 @@ namespace pipedal
virtual void ResetAtomBuffers(); virtual void ResetAtomBuffers();
virtual uint64_t GetInstanceId() const { return instanceId; } virtual uint64_t GetInstanceId() const { return instanceId; }
virtual int GetNumberOfInputAudioPorts() const { return inputAudioPortIndices.size(); } virtual int GetNumberOfInputAudioPorts() const override { return inputAudioPortIndices.size(); }
virtual int GetNumberOfOutputAudioPorts() const { return outputAudioPortIndices.size(); } virtual int GetNumberOfOutputAudioPorts() const override { return outputAudioPortIndices.size(); }
virtual int GetNumberOfInputAtomPorts() const { return inputAtomPortIndices.size(); } virtual int GetNumberOfInputAtomPorts() const { return inputAtomPortIndices.size(); }
virtual int GetNumberOfOutputAtomPorts() const { return outputAtomPortIndices.size(); } virtual int GetNumberOfOutputAtomPorts() const { return outputAtomPortIndices.size(); }
virtual int GetNumberOfMidiInputPorts() const { return inputMidiPortIndices.size(); } virtual int GetNumberOfMidiInputPorts() const { return inputMidiPortIndices.size(); }
virtual int GetNumberOfMidiOutputPorts() const { return outputMidiPortIndices.size(); } virtual int GetNumberOfMidiOutputPorts() const { return outputMidiPortIndices.size(); }
virtual int GetNumberOfInputAudioBuffers() const { return this->inputAudioBuffers.size(); } virtual int GetNumberOfInputAudioBuffers() const override { return this->inputAudioBuffers.size(); }
virtual int GetNumberOfOutputAudioBuffers() const {return this->outputAudioBuffers.size(); } virtual int GetNumberOfSidechainAudioBuffers() const override { return this->inputSidechainBuffers.size(); }
virtual int GetNumberOfOutputAudioBuffers() const override {return this->outputAudioBuffers.size(); }
virtual void SetAudioInputBuffer(int index, float *buffer); virtual void SetAudioInputBuffer(int index, float *buffer) override;
virtual float *GetAudioInputBuffer(int index) const; virtual float *GetAudioInputBuffer(int index) const override;
virtual void SetAudioSidechainBuffer(int index, float *buffer) override;
virtual float *GetAudioSidechainBuffer(int index) const override;
virtual void SetAudioInputBuffer(float *buffer); virtual void SetAudioInputBuffer(float *buffer);
+54 -1
View File
@@ -176,6 +176,60 @@ std::vector<float *> Lv2Pedalboard::PrepareItems(
auto bufferR = inputBuffers[1]; auto bufferR = inputBuffers[1];
} }
} }
// Connect sidechain buffers.
if (pLv2Effect->GetNumberOfSidechainAudioBuffers() != 0)
{
if (item.sideChainInputId() == -2) // -2 means "use pedalboard inputs".
{
for (size_t i = 0; i < pLv2Effect->GetNumberOfSidechainAudioBuffers(); ++i)
{
if (i < this->pedalboardInputBuffers.size())
{
pLv2Effect->SetAudioSidechainBuffer(i, this->pedalboardInputBuffers[i]);
}
else
{
// just use the first output buffer for all sidechain inputs.
pLv2Effect->SetAudioSidechainBuffer(i, this->pedalboardInputBuffers[0]);
}
}
} else if (item.sideChainInputId() != -1) {
IEffect *pSideChainInput = GetEffect(item.sideChainInputId());
if (pSideChainInput)
{
for (size_t i = 0; i < pLv2Effect->GetNumberOfSidechainAudioBuffers(); ++i)
{
if (i < pSideChainInput->GetNumberOfOutputAudioBuffers())
{
pLv2Effect->SetAudioSidechainBuffer(i, pSideChainInput->GetAudioOutputBuffer(i));
}
else
{
// just use the first output buffer for all sidechain inputs.
pLv2Effect->SetAudioSidechainBuffer(i, pSideChainInput->GetAudioOutputBuffer(0));
}
}
} else {
throw std::runtime_error("Internal error: Sidechain input IEffect not found.");
}
}
else
{
// No sidechain input. Feed zero buffer to all sidechain audio ports..
for (size_t i = 0; i < pLv2Effect->GetNumberOfSidechainAudioBuffers(); ++i)
{
// just use one buffer for all plugins
if (!this->pedalboardSidechainBuffer)
{
this->pedalboardSidechainBuffer = CreateNewAudioBuffer();
}
pLv2Effect->SetAudioSidechainBuffer(i, this->pedalboardSidechainBuffer);
}
}
}
// check to see whether we need buffer staging. // check to see whether we need buffer staging.
bool requiresBufferStaging = false; bool requiresBufferStaging = false;
@@ -194,7 +248,6 @@ std::vector<float *> Lv2Pedalboard::PrepareItems(
} }
} }
if (!requiresBufferStaging) if (!requiresBufferStaging)
{ {
this->processActions.push_back( this->processActions.push_back(
+1
View File
@@ -57,6 +57,7 @@ namespace pipedal
BufferPool bufferPool; BufferPool bufferPool;
std::vector<float *> pedalboardInputBuffers; std::vector<float *> pedalboardInputBuffers;
std::vector<float *> pedalboardOutputBuffers; std::vector<float *> pedalboardOutputBuffers;
float *pedalboardSidechainBuffer = nullptr;
std::vector<std::shared_ptr<IEffect>> effects; std::vector<std::shared_ptr<IEffect>> effects;
std::vector<IEffect *> realtimeEffects; std::vector<IEffect *> realtimeEffects;
+1
View File
@@ -534,6 +534,7 @@ JSON_MAP_BEGIN(PedalboardItem)
JSON_MAP_REFERENCE(PedalboardItem,title) JSON_MAP_REFERENCE(PedalboardItem,title)
JSON_MAP_REFERENCE(PedalboardItem,useModUi) JSON_MAP_REFERENCE(PedalboardItem,useModUi)
JSON_MAP_REFERENCE(PedalboardItem,iconColor) JSON_MAP_REFERENCE(PedalboardItem,iconColor)
JSON_MAP_REFERENCE(PedalboardItem,sideChainInputId)
JSON_MAP_END() JSON_MAP_END()
+2
View File
@@ -107,6 +107,7 @@ public:
std::string title_; std::string title_;
bool useModUi_ = false; bool useModUi_ = false;
std::string iconColor_; std::string iconColor_;
int64_t sideChainInputId_ = -1;
// non persistent state. // non persistent state.
PropertyMap patchProperties; PropertyMap patchProperties;
@@ -138,6 +139,7 @@ public:
GETTER_SETTER_REF(title) GETTER_SETTER_REF(title)
GETTER_SETTER_REF(iconColor) GETTER_SETTER_REF(iconColor)
GETTER_SETTER(useModUi) GETTER_SETTER(useModUi)
GETTER_SETTER(sideChainInputId)
Lv2PluginState&lv2State() { return lv2State_; } // non-const version. Lv2PluginState&lv2State() { return lv2State_; } // non-const version.
GETTER_SETTER_REF(lilvPresetUri) GETTER_SETTER_REF(lilvPresetUri)
+1 -1
View File
@@ -2428,7 +2428,7 @@ void PiPedalModel::OnNotifyPathPatchPropertyReceived(
auto i = pedalboardItem->pathProperties_.find(pathPatchPropertyUri); auto i = pedalboardItem->pathProperties_.find(pathPatchPropertyUri);
if (i != pedalboardItem->pathProperties_.end()) if (i != pedalboardItem->pathProperties_.end())
{ {
std::string abstractAtomString = storage.ToAbstractPathJson(atomString); std::string abstractAtomString = storage.ToAbstractPathFromJson(atomString);
pedalboardItem->pathProperties_[pathPatchPropertyUri] = abstractAtomString; pedalboardItem->pathProperties_[pathPatchPropertyUri] = abstractAtomString;
std::vector<IPiPedalModelSubscriber::ptr> t{subscribers.begin(), subscribers.end()}; std::vector<IPiPedalModelSubscriber::ptr> t{subscribers.begin(), subscribers.end()};
+111 -7
View File
@@ -121,6 +121,7 @@ void PluginHost::LilvUris::Initialize(LilvWorld *pWorld)
enumeration_property_uri = lilv_new_uri(pWorld, LV2_CORE__enumeration); enumeration_property_uri = lilv_new_uri(pWorld, LV2_CORE__enumeration);
core__toggled = lilv_new_uri(pWorld, LV2_CORE__toggled); core__toggled = lilv_new_uri(pWorld, LV2_CORE__toggled);
core__connectionOptional = lilv_new_uri(pWorld, LV2_CORE__connectionOptional); core__connectionOptional = lilv_new_uri(pWorld, LV2_CORE__connectionOptional);
core__isSideChain = lilv_new_uri(pWorld, LV2_CORE_PREFIX "isSideChain"); // missing in lv2.h
portprops__not_on_gui_property_uri = lilv_new_uri(pWorld, LV2_PORT_PROPS__notOnGUI); portprops__not_on_gui_property_uri = lilv_new_uri(pWorld, LV2_PORT_PROPS__notOnGUI);
portprops__trigger = lilv_new_uri(pWorld, LV2_PORT_PROPS__trigger); portprops__trigger = lilv_new_uri(pWorld, LV2_PORT_PROPS__trigger);
midi__event = lilv_new_uri(pWorld, LV2_MIDI__MidiEvent); midi__event = lilv_new_uri(pWorld, LV2_MIDI__MidiEvent);
@@ -134,6 +135,7 @@ void PluginHost::LilvUris::Initialize(LilvWorld *pWorld)
atom__bufferType = lilv_new_uri(pWorld, LV2_ATOM__bufferType); atom__bufferType = lilv_new_uri(pWorld, LV2_ATOM__bufferType);
atom__Path = lilv_new_uri(pWorld, LV2_ATOM__Path); atom__Path = lilv_new_uri(pWorld, LV2_ATOM__Path);
atom__String = lilv_new_uri(pWorld, LV2_ATOM__String);
presets__preset = lilv_new_uri(pWorld, LV2_PRESETS__Preset); presets__preset = lilv_new_uri(pWorld, LV2_PRESETS__Preset);
state__state = lilv_new_uri(pWorld, LV2_STATE__state); state__state = lilv_new_uri(pWorld, LV2_STATE__state);
rdfs__label = lilv_new_uri(pWorld, LILV_NS_RDFS "label"); rdfs__label = lilv_new_uri(pWorld, LILV_NS_RDFS "label");
@@ -214,6 +216,7 @@ void PluginHost::LilvUris::Initialize(LilvWorld *pWorld)
buf_size__maxBlockLength = lilv_new_uri(pWorld, LV2_BUF_SIZE__maxBlockLength); buf_size__maxBlockLength = lilv_new_uri(pWorld, LV2_BUF_SIZE__maxBlockLength);
buf_size__fixedBlockLength = lilv_new_uri(pWorld, LV2_BUF_SIZE__fixedBlockLength); buf_size__fixedBlockLength = lilv_new_uri(pWorld, LV2_BUF_SIZE__fixedBlockLength);
buf_size__coarseBlockLength = lilv_new_uri(pWorld, LV2_BUF_SIZE__coarseBlockLength); buf_size__coarseBlockLength = lilv_new_uri(pWorld, LV2_BUF_SIZE__coarseBlockLength);
port_groups__sideChainOf = lilv_new_uri(pWorld, LV2_PORT_GROUPS__sideChainOf);
} }
@@ -794,7 +797,12 @@ Lv2PluginInfo::FindWritablePathProperties(PluginHost *lv2Host, const LilvPlugin
} }
else else
{ {
unsupportedPatchProperty = true; if (lilv_world_ask(pWorld, propertyUri, lv2Host->lilvUris->rdfs__range, lv2Host->lilvUris->atom__String))
{
} else {
unsupportedPatchProperty = true;
}
} }
} }
} }
@@ -945,6 +953,60 @@ Lv2PluginInfo::Lv2PluginInfo(PluginHost *lv2Host, LilvWorld *pWorld, const LilvP
std::sort(ports_.begin(), ports_.end(), ports_sort_compare); std::sort(ports_.begin(), ports_.end(), ports_sort_compare);
// Check the portgroup for audio inputs to see if they are sidechains.
std::string last_sidechain_title;
for (auto&port: this->ports_)
{
if (port->is_audio_port() && port->is_input()) {
if (!port->port_group().empty())
{
for (auto &pg: this->port_groups_)
{
if (pg->uri() == port->port_group())
{
if (!pg->sideChainOf().empty())
{
port->is_sidechain(true);
audio_sidechain_title_ = pg->name();
if (last_sidechain_title.empty()) {
last_sidechain_title = pg->name();
}
else if (last_sidechain_title != pg->name()) {
// multiple sidechain titles!
isValid = false;
Lv2Log::debug("Plugin %s (%s) has multiple sidechain titles (%s and %s).",
this->name_.c_str(),
this->uri_.c_str(),
last_sidechain_title.c_str(),
pg->name().c_str());
}
}
break;
}
}
} else {
if (port->is_sidechain()) {
// port is marked as sidechain, but has no port group.
if (last_sidechain_title.empty())
{
last_sidechain_title = port->name();
this->audio_sidechain_title_ = port->name();
}
else if (last_sidechain_title != port->name()) {
isValid = false;
Lv2Log::debug("Plugin %s (%s) has multiple sidechain ports, but no portgroup. (%s)",
this->name_.c_str(),
this->uri_.c_str(),
port->name().c_str());
}
}
}
}
}
// Fetch patch properties. // Fetch patch properties.
{ {
AutoLilvNodes patchWritables = lilv_world_find_nodes(pWorld, plugUri, lv2Host->lilvUris->patch__writable, nullptr); AutoLilvNodes patchWritables = lilv_world_find_nodes(pWorld, plugUri, lv2Host->lilvUris->patch__writable, nullptr);
@@ -1034,17 +1096,28 @@ Lv2PluginInfo::Lv2PluginInfo(PluginHost *lv2Host, LilvWorld *pWorld, const LilvP
} }
int nInputs = 0; int nInputs = 0;
int nSideChains = 0;
for (size_t i = 0; i < ports_.size(); ++i) for (size_t i = 0; i < ports_.size(); ++i)
{ {
auto port = ports_[i]; auto port = ports_[i];
if (port->is_audio_port() && port->is_input()) if (port->is_audio_port() && port->is_input())
{ {
if (nInputs >= 2 && !port->connection_optional()) if (!port->is_sidechain()) {
{ if (nInputs >= 2)
isValid = false; {
break; isValid = false;
Lv2Log::debug("Plugin %s (%s) has more than 2 audio inputs.", this->name_.c_str(), this->uri_.c_str());
break;
}
++nInputs;
} else {
if (nSideChains >= 2) {
Lv2Log::debug("Plugin %s (%s) has more than 2 audio sidechain inputs.", this->name_.c_str(), this->uri_.c_str());
isValid = false;
break;
}
++nSideChains;
} }
++nInputs;
} }
} }
int nOutputs = 0; int nOutputs = 0;
@@ -1199,6 +1272,7 @@ Lv2PortInfo::Lv2PortInfo(PluginHost *host, const LilvPlugin *plugin, const LilvP
this->not_on_gui_ = lilv_port_has_property(plugin, pPort, host->lilvUris->portprops__not_on_gui_property_uri); this->not_on_gui_ = lilv_port_has_property(plugin, pPort, host->lilvUris->portprops__not_on_gui_property_uri);
this->connection_optional_ = lilv_port_has_property(plugin, pPort, host->lilvUris->core__connectionOptional); this->connection_optional_ = lilv_port_has_property(plugin, pPort, host->lilvUris->core__connectionOptional);
this->trigger_property_ = lilv_port_has_property(plugin, pPort, host->lilvUris->portprops__trigger); this->trigger_property_ = lilv_port_has_property(plugin, pPort, host->lilvUris->portprops__trigger);
this->is_sidechain_ = lilv_port_has_property(plugin,pPort,host->lilvUris->core__isSideChain);
AutoLilvNode port_ledColor = lilv_port_get(plugin, pPort, host->lilvUris->pipedalUI__ledColor); AutoLilvNode port_ledColor = lilv_port_get(plugin, pPort, host->lilvUris->pipedalUI__ledColor);
if (port_ledColor) if (port_ledColor)
@@ -1402,7 +1476,12 @@ Lv2PluginUiInfo::Lv2PluginUiInfo(PluginHost *pHost, const Lv2PluginInfo *plugin)
} }
else if (port->is_audio_port()) else if (port->is_audio_port())
{ {
++audio_inputs_; if (port->is_sidechain()) {
// sidechain outputs don't count.
++audio_side_chain_inputs_;
} else {
++audio_inputs_;
}
} }
} }
else if (port->is_output()) else if (port->is_output())
@@ -1918,12 +1997,29 @@ Lv2PortGroup::Lv2PortGroup(PluginHost *lv2Host, const std::string &groupUri)
{ {
LilvWorld *pWorld = lv2Host->pWorld; LilvWorld *pWorld = lv2Host->pWorld;
// Enumerate all Lilv nodes for groupUri that have an 'a' predicate
this->uri_ = groupUri; this->uri_ = groupUri;
AutoLilvNode uri = lilv_new_uri(pWorld, groupUri.c_str()); AutoLilvNode uri = lilv_new_uri(pWorld, groupUri.c_str());
AutoLilvNode aPredicate = lilv_new_uri(pWorld, "http://www.w3.org/1999/02/22-rdf-syntax-ns#type");
AutoLilvNodes types = lilv_world_find_nodes(pWorld, uri, aPredicate, nullptr);
LILV_FOREACH(nodes, iNode, types)
{
AutoLilvNode typeNode = lilv_nodes_get(types, iNode);
std::string typeUri = nodeAsString(typeNode);
this->isA_.push_back(std::move(typeUri));
}
AutoLilvNode symbolNode = lilv_world_get(pWorld, uri, lv2Host->lilvUris->lv2core__symbol, nullptr); AutoLilvNode symbolNode = lilv_world_get(pWorld, uri, lv2Host->lilvUris->lv2core__symbol, nullptr);
symbol_ = nodeAsString(symbolNode); symbol_ = nodeAsString(symbolNode);
AutoLilvNode nameNode = lilv_world_get(pWorld, uri, lv2Host->lilvUris->lv2core__name, nullptr); AutoLilvNode nameNode = lilv_world_get(pWorld, uri, lv2Host->lilvUris->lv2core__name, nullptr);
name_ = nodeAsString(nameNode); name_ = nodeAsString(nameNode);
AutoLilvNode sideChainOfNode = lilv_world_get(pWorld, uri, lv2Host->lilvUris->port_groups__sideChainOf, nullptr);
sideChainOf_ = nodeAsString(sideChainOfNode);
} }
bool Lv2PluginInfo::isSplit() const bool Lv2PluginInfo::isSplit() const
@@ -2187,6 +2283,7 @@ json_map::storage_type<Lv2PortInfo> Lv2PortInfo::jmap{
json_map::reference("is_input", &Lv2PortInfo::is_input_), json_map::reference("is_input", &Lv2PortInfo::is_input_),
json_map::reference("is_output", &Lv2PortInfo::is_output_), json_map::reference("is_output", &Lv2PortInfo::is_output_),
json_map::reference("is_sidechain", &Lv2PortInfo::is_sidechain_),
json_map::reference("is_control_port", &Lv2PortInfo::is_control_port_), json_map::reference("is_control_port", &Lv2PortInfo::is_control_port_),
json_map::reference("is_audio_port", &Lv2PortInfo::is_audio_port_), json_map::reference("is_audio_port", &Lv2PortInfo::is_audio_port_),
@@ -2226,7 +2323,11 @@ json_map::storage_type<Lv2PortInfo> Lv2PortInfo::jmap{
json_map::storage_type<Lv2PortGroup> Lv2PortGroup::jmap{{ json_map::storage_type<Lv2PortGroup> Lv2PortGroup::jmap{{
MAP_REF(Lv2PortGroup, isA),
MAP_REF(Lv2PortGroup, uri), MAP_REF(Lv2PortGroup, uri),
MAP_REF(Lv2PortGroup, name),
MAP_REF(Lv2PortGroup, symbol),
MAP_REF(Lv2PortGroup, sideChainOf),
}}; }};
@@ -2281,6 +2382,7 @@ json_map::storage_type<Lv2PluginUiPort> Lv2PluginUiPort::jmap{{
json_map::reference("name", &Lv2PluginUiPort::name_), json_map::reference("name", &Lv2PluginUiPort::name_),
MAP_REF(Lv2PluginUiPort, index), MAP_REF(Lv2PluginUiPort, index),
MAP_REF(Lv2PluginUiPort, is_input), MAP_REF(Lv2PluginUiPort, is_input),
MAP_REF(Lv2PluginUiPort, is_sidechain),
MAP_REF(Lv2PluginUiPort, min_value), MAP_REF(Lv2PluginUiPort, min_value),
MAP_REF(Lv2PluginUiPort, max_value), MAP_REF(Lv2PluginUiPort, max_value),
MAP_REF(Lv2PluginUiPort, default_value), MAP_REF(Lv2PluginUiPort, default_value),
@@ -2324,7 +2426,9 @@ json_map::storage_type<Lv2PluginUiInfo>
json_map::reference("author_name", &Lv2PluginUiInfo::author_name_), json_map::reference("author_name", &Lv2PluginUiInfo::author_name_),
json_map::reference("author_homepage", &Lv2PluginUiInfo::author_homepage_), json_map::reference("author_homepage", &Lv2PluginUiInfo::author_homepage_),
json_map::reference("audio_inputs", &Lv2PluginUiInfo::audio_inputs_), json_map::reference("audio_inputs", &Lv2PluginUiInfo::audio_inputs_),
json_map::reference("audio_side_chain_inputs", &Lv2PluginUiInfo::audio_side_chain_inputs_),
json_map::reference("audio_outputs", &Lv2PluginUiInfo::audio_outputs_), json_map::reference("audio_outputs", &Lv2PluginUiInfo::audio_outputs_),
json_map::reference("audio_side_chain_title", &Lv2PluginUiInfo::audio_side_chain_title_),
json_map::reference("description", &Lv2PluginUiInfo::description_), json_map::reference("description", &Lv2PluginUiInfo::description_),
json_map::reference("has_midi_input", &Lv2PluginUiInfo::has_midi_input_), json_map::reference("has_midi_input", &Lv2PluginUiInfo::has_midi_input_),
json_map::reference("has_midi_output", &Lv2PluginUiInfo::has_midi_output_), json_map::reference("has_midi_output", &Lv2PluginUiInfo::has_midi_output_),
+61 -49
View File
@@ -33,7 +33,7 @@
#include <set> #include <set>
#include "ModGui.hpp" #include "ModGui.hpp"
//#include "lv2.h" // #include "lv2.h"
#include "Units.hpp" #include "Units.hpp"
#include "PluginPreset.hpp" #include "PluginPreset.hpp"
@@ -206,6 +206,7 @@ namespace pipedal
bool is_control_port_ = false; bool is_control_port_ = false;
bool is_audio_port_ = false; bool is_audio_port_ = false;
bool is_sidechain_ = false;
bool is_atom_port_ = false; bool is_atom_port_ = false;
bool is_cv_port_ = false; bool is_cv_port_ = false;
bool connection_optional_ = false; bool connection_optional_ = false;
@@ -287,6 +288,7 @@ namespace pipedal
LV2_PROPERTY_GETSET_SCALAR(is_output); LV2_PROPERTY_GETSET_SCALAR(is_output);
LV2_PROPERTY_GETSET_SCALAR(is_control_port); LV2_PROPERTY_GETSET_SCALAR(is_control_port);
LV2_PROPERTY_GETSET_SCALAR(is_audio_port); LV2_PROPERTY_GETSET_SCALAR(is_audio_port);
LV2_PROPERTY_GETSET_SCALAR(is_sidechain);
LV2_PROPERTY_GETSET_SCALAR(is_atom_port); LV2_PROPERTY_GETSET_SCALAR(is_atom_port);
LV2_PROPERTY_GETSET_SCALAR(connection_optional); LV2_PROPERTY_GETSET_SCALAR(connection_optional);
LV2_PROPERTY_GETSET_SCALAR(is_cv_port); LV2_PROPERTY_GETSET_SCALAR(is_cv_port);
@@ -321,7 +323,6 @@ namespace pipedal
return Lv2BufferType::Unknown; return Lv2BufferType::Unknown;
} }
public: public:
Lv2PortInfo() {} Lv2PortInfo() {}
~Lv2PortInfo() = default; ~Lv2PortInfo() = default;
@@ -330,7 +331,8 @@ namespace pipedal
static json_map::storage_type<Lv2PortInfo> jmap; static json_map::storage_type<Lv2PortInfo> jmap;
}; };
class Lv2PatchPropertyInfo { class Lv2PatchPropertyInfo
{
private: private:
std::string uri_; std::string uri_;
@@ -343,9 +345,10 @@ namespace pipedal
std::string shortName_; std::string shortName_;
std::vector<std::string> fileTypes_; std::vector<std::string> fileTypes_;
std::vector<std::string> supportedExtensions_; std::vector<std::string> supportedExtensions_;
public: public:
Lv2PatchPropertyInfo() {} Lv2PatchPropertyInfo() {}
Lv2PatchPropertyInfo(PluginHost *pluginHost, const LilvNode *propertyUri); Lv2PatchPropertyInfo(PluginHost *pluginHost, const LilvNode *propertyUri);
LV2_PROPERTY_GETSET(uri); LV2_PROPERTY_GETSET(uri);
LV2_PROPERTY_GETSET(writable); LV2_PROPERTY_GETSET(writable);
@@ -359,21 +362,24 @@ namespace pipedal
LV2_PROPERTY_GETSET(supportedExtensions); LV2_PROPERTY_GETSET(supportedExtensions);
DECLARE_JSON_MAP(Lv2PatchPropertyInfo); DECLARE_JSON_MAP(Lv2PatchPropertyInfo);
}; };
class Lv2PortGroup class Lv2PortGroup
{ {
private: private:
std::vector<std::string> isA_;
std::string uri_; std::string uri_;
std::string symbol_; std::string symbol_;
std::string name_; std::string name_;
std::string sideChainOf_;
public: public:
LV2_PROPERTY_GETSET(isA);
LV2_PROPERTY_GETSET(uri); LV2_PROPERTY_GETSET(uri);
LV2_PROPERTY_GETSET(symbol); LV2_PROPERTY_GETSET(symbol);
LV2_PROPERTY_GETSET(name); LV2_PROPERTY_GETSET(name);
LV2_PROPERTY_GETSET(sideChainOf);
Lv2PortGroup() {} Lv2PortGroup() {}
Lv2PortGroup(PluginHost *lv2Host, const std::string &groupUri); Lv2PortGroup(PluginHost *lv2Host, const std::string &groupUri);
@@ -389,10 +395,11 @@ namespace pipedal
using ptr = std::shared_ptr<Lv2PluginInfo>; using ptr = std::shared_ptr<Lv2PluginInfo>;
Lv2PluginInfo(PluginHost *lv2Host, LilvWorld *pWorld, const LilvPlugin *); Lv2PluginInfo(PluginHost *lv2Host, LilvWorld *pWorld, const LilvPlugin *);
Lv2PluginInfo() {} Lv2PluginInfo() {}
bool isSplit() const ; bool isSplit() const;
private: private:
struct FindWritablePathPropertiesResult { struct FindWritablePathPropertiesResult
{
std::shared_ptr<PiPedalUI> pipedalUi; std::shared_ptr<PiPedalUI> pipedalUi;
bool hasUnsupportedPatchProperties = false; bool hasUnsupportedPatchProperties = false;
}; };
@@ -406,7 +413,7 @@ namespace pipedal
std::string name_; std::string name_;
std::string plugin_class_; std::string plugin_class_;
std::string brand_; std::string brand_;
std::string label_; std::string label_;
std::vector<std::string> supported_features_; std::vector<std::string> supported_features_;
std::vector<std::string> required_features_; std::vector<std::string> required_features_;
std::vector<std::string> optional_features_; std::vector<std::string> optional_features_;
@@ -420,9 +427,9 @@ namespace pipedal
std::vector<std::shared_ptr<Lv2PortInfo>> ports_; std::vector<std::shared_ptr<Lv2PortInfo>> ports_;
std::vector<std::shared_ptr<Lv2PortGroup>> port_groups_; std::vector<std::shared_ptr<Lv2PortGroup>> port_groups_;
std::vector<Lv2PatchPropertyInfo> patchProperties_; std::vector<Lv2PatchPropertyInfo> patchProperties_;
std::string audio_sidechain_title_;
bool hasDefaultState_; bool hasDefaultState_;
bool is_valid_ = false; bool is_valid_ = false;
PiPedalUI::ptr piPedalUI_; PiPedalUI::ptr piPedalUI_;
ModGui::ptr modGui_; ModGui::ptr modGui_;
@@ -463,6 +470,7 @@ namespace pipedal
LV2_PROPERTY_GETSET(minBlockLength) LV2_PROPERTY_GETSET(minBlockLength)
LV2_PROPERTY_GETSET(maxBlockLength) LV2_PROPERTY_GETSET(maxBlockLength)
LV2_PROPERTY_GETSET(powerOf2BlockLength) LV2_PROPERTY_GETSET(powerOf2BlockLength)
LV2_PROPERTY_GETSET(audio_sidechain_title)
bool WantsWorkerThread() const; bool WantsWorkerThread() const;
@@ -529,9 +537,11 @@ namespace pipedal
class Lv2PluginUiPortGroup class Lv2PluginUiPortGroup
{ {
private: private:
std::vector<std::string> isA_;
std::string uri_; std::string uri_;
std::string symbol_; std::string symbol_;
std::string name_; std::string name_;
std::string sideChainOf_;
std::string parent_group_; std::string parent_group_;
int32_t program_list_id_ = -1; // used by VST3. int32_t program_list_id_ = -1; // used by VST3.
@@ -546,9 +556,7 @@ namespace pipedal
public: public:
Lv2PluginUiPortGroup() {} Lv2PluginUiPortGroup() {}
Lv2PluginUiPortGroup(Lv2PortGroup *pPortGroup) Lv2PluginUiPortGroup(Lv2PortGroup *pPortGroup)
: symbol_(pPortGroup->symbol()) : isA_(pPortGroup->isA()), symbol_(pPortGroup->symbol()), name_(pPortGroup->name()), uri_(pPortGroup->uri()), sideChainOf_(pPortGroup->sideChainOf())
, name_(pPortGroup->name())
, uri_(pPortGroup->uri())
{ {
} }
Lv2PluginUiPortGroup( Lv2PluginUiPortGroup(
@@ -570,14 +578,18 @@ namespace pipedal
} }
Lv2PluginUiPort(const Lv2PluginInfo *pPlugin, const Lv2PortInfo *pPort) Lv2PluginUiPort(const Lv2PluginInfo *pPlugin, const Lv2PortInfo *pPort)
: symbol_(pPort->symbol()), index_(pPort->index()), : symbol_(pPort->symbol()), index_(pPort->index()),
is_input_(pPort->is_input()), name_(pPort->name()), min_value_(pPort->min_value()), max_value_(pPort->max_value()), is_input_(pPort->is_input()),
is_sidechain_(pPort->is_sidechain()),
name_(pPort->name()),
min_value_(pPort->min_value()),
max_value_(pPort->max_value()),
default_value_(pPort->default_value()), range_steps_(pPort->range_steps()), display_priority_(pPort->display_priority()), default_value_(pPort->default_value()), range_steps_(pPort->range_steps()), display_priority_(pPort->display_priority()),
is_logarithmic_(pPort->is_logarithmic()), is_logarithmic_(pPort->is_logarithmic()),
integer_property_(pPort->integer_property()), integer_property_(pPort->integer_property()),
mod_momentaryOffByDefault_(pPort->mod_momentaryOffByDefault()), mod_momentaryOffByDefault_(pPort->mod_momentaryOffByDefault()),
mod_momentaryOnByDefault_(pPort->mod_momentaryOnByDefault()), mod_momentaryOnByDefault_(pPort->mod_momentaryOnByDefault()),
pipedal_graphicEq_(pPort->pipedal_graphicEq()), pipedal_graphicEq_(pPort->pipedal_graphicEq()),
enumeration_property_(pPort->enumeration_property()), enumeration_property_(pPort->enumeration_property()),
toggled_property_(pPort->toggled_property()), not_on_gui_(pPort->not_on_gui()), scale_points_(pPort->scale_points()), toggled_property_(pPort->toggled_property()), not_on_gui_(pPort->not_on_gui()), scale_points_(pPort->scale_points()),
trigger_property_(pPort->trigger_property()), trigger_property_(pPort->trigger_property()),
@@ -606,7 +618,7 @@ namespace pipedal
} }
} }
} }
is_bypass_ = is_bypass_ =
pPort->is_bypass() || pPort->is_bypass() ||
name_ == "bypass" || name_ == "Bypass" || symbol_ == "bypass" || symbol_ == "Bypass"; name_ == "bypass" || name_ == "Bypass" || symbol_ == "bypass" || symbol_ == "Bypass";
} }
@@ -616,6 +628,7 @@ namespace pipedal
int index_; int index_;
std::string name_; std::string name_;
bool is_input_ = true; bool is_input_ = true;
bool is_sidechain_ = false;
float min_value_ = 0, max_value_ = 1, default_value_ = 0; float min_value_ = 0, max_value_ = 1, default_value_ = 0;
bool is_logarithmic_ = false; bool is_logarithmic_ = false;
int display_priority_ = -1; int display_priority_ = -1;
@@ -682,7 +695,8 @@ namespace pipedal
std::string uri_; std::string uri_;
std::string name_; std::string name_;
uint32_t minorVersion_ = 0; uint32_t minorVersion_ = 0;
uint32_t microVersion_ = 0;; uint32_t microVersion_ = 0;
;
std::string brand_; std::string brand_;
std::string label_; std::string label_;
std::string author_name_; std::string author_name_;
@@ -690,12 +704,17 @@ namespace pipedal
PluginType plugin_type_; PluginType plugin_type_;
std::string plugin_display_type_; std::string plugin_display_type_;
int audio_inputs_ = 0; int audio_inputs_ = 0;
int audio_side_chain_inputs_ = 0;
int audio_outputs_ = 0; int audio_outputs_ = 0;
std::string audio_side_chain_title_;
int has_midi_input_ = false; int has_midi_input_ = false;
int has_midi_output_ = false; int has_midi_output_ = false;
std::string description_; std::string description_;
bool is_vst3_ = false; bool is_vst3_ = false;
int64_t side_chain_input_id = -1;
std::vector<Lv2PluginUiPort> controls_; std::vector<Lv2PluginUiPort> controls_;
std::vector<Lv2PluginUiPortGroup> port_groups_; std::vector<Lv2PluginUiPortGroup> port_groups_;
std::vector<UiFileProperty::ptr> fileProperties_; std::vector<UiFileProperty::ptr> fileProperties_;
@@ -704,7 +723,6 @@ namespace pipedal
ModGui::ptr modGui_; ModGui::ptr modGui_;
std::vector<Lv2PatchPropertyInfo> patchProperties_; std::vector<Lv2PatchPropertyInfo> patchProperties_;
public: public:
LV2_PROPERTY_GETSET(uri) LV2_PROPERTY_GETSET(uri)
LV2_PROPERTY_GETSET(name) LV2_PROPERTY_GETSET(name)
@@ -718,6 +736,7 @@ namespace pipedal
LV2_PROPERTY_GETSET(plugin_display_type) LV2_PROPERTY_GETSET(plugin_display_type)
LV2_PROPERTY_GETSET_SCALAR(audio_inputs) LV2_PROPERTY_GETSET_SCALAR(audio_inputs)
LV2_PROPERTY_GETSET_SCALAR(audio_outputs) LV2_PROPERTY_GETSET_SCALAR(audio_outputs)
LV2_PROPERTY_GETSET(audio_side_chain_title)
LV2_PROPERTY_GETSET_SCALAR(has_midi_input) LV2_PROPERTY_GETSET_SCALAR(has_midi_input)
LV2_PROPERTY_GETSET_SCALAR(has_midi_output) LV2_PROPERTY_GETSET_SCALAR(has_midi_output)
LV2_PROPERTY_GETSET_SCALAR(description) LV2_PROPERTY_GETSET_SCALAR(description)
@@ -729,6 +748,7 @@ namespace pipedal
LV2_PROPERTY_GETSET(uiPortNotifications) LV2_PROPERTY_GETSET(uiPortNotifications)
LV2_PROPERTY_GETSET(modGui) LV2_PROPERTY_GETSET(modGui)
LV2_PROPERTY_GETSET(patchProperties) LV2_PROPERTY_GETSET(patchProperties)
LV2_PROPERTY_GETSET_SCALAR(audio_side_chain_inputs)
static json_map::storage_type<Lv2PluginUiInfo> jmap; static json_map::storage_type<Lv2PluginUiInfo> jmap;
}; };
@@ -772,6 +792,7 @@ namespace pipedal
AutoLilvNode enumeration_property_uri; AutoLilvNode enumeration_property_uri;
AutoLilvNode core__toggled; AutoLilvNode core__toggled;
AutoLilvNode core__connectionOptional; AutoLilvNode core__connectionOptional;
AutoLilvNode core__isSideChain;
AutoLilvNode portprops__not_on_gui_property_uri; AutoLilvNode portprops__not_on_gui_property_uri;
AutoLilvNode portprops__trigger; AutoLilvNode portprops__trigger;
AutoLilvNode midi__event; AutoLilvNode midi__event;
@@ -782,9 +803,9 @@ namespace pipedal
AutoLilvNode invada_units__unit; // typo in invada plugins. AutoLilvNode invada_units__unit; // typo in invada plugins.
AutoLilvNode invada_portprops__logarithmic; // typo in invada plugins. AutoLilvNode invada_portprops__logarithmic; // typo in invada plugins.
AutoLilvNode atom__bufferType; AutoLilvNode atom__bufferType;
AutoLilvNode atom__Path; AutoLilvNode atom__Path;
AutoLilvNode atom__String;
AutoLilvNode presets__preset; AutoLilvNode presets__preset;
AutoLilvNode state__state; AutoLilvNode state__state;
AutoLilvNode rdfs__label; AutoLilvNode rdfs__label;
@@ -816,7 +837,6 @@ namespace pipedal
AutoLilvNode pipedalUI__width; AutoLilvNode pipedalUI__width;
AutoLilvNode pipedalUI__graphicEq; AutoLilvNode pipedalUI__graphicEq;
AutoLilvNode pipedalUI__outputPorts; AutoLilvNode pipedalUI__outputPorts;
AutoLilvNode pipedalUI__text; AutoLilvNode pipedalUI__text;
@@ -846,7 +866,6 @@ namespace pipedal
AutoLilvNode patch__readable; AutoLilvNode patch__readable;
AutoLilvNode pipedal_patch__readable; AutoLilvNode pipedal_patch__readable;
AutoLilvNode mod__brand; AutoLilvNode mod__brand;
AutoLilvNode mod__label; AutoLilvNode mod__label;
AutoLilvNode mod__preferMomentaryOffByDefault; AutoLilvNode mod__preferMomentaryOffByDefault;
@@ -861,11 +880,9 @@ namespace pipedal
AutoLilvNode buf_size__maxBlockLength; AutoLilvNode buf_size__maxBlockLength;
AutoLilvNode buf_size__fixedBlockLength; AutoLilvNode buf_size__fixedBlockLength;
AutoLilvNode buf_size__coarseBlockLength; AutoLilvNode buf_size__coarseBlockLength;
AutoLilvNode port_groups__sideChainOf;
}; };
LilvUris* lilvUris = nullptr; LilvUris *lilvUris = nullptr;
private: private:
bool vst3Enabled = true; bool vst3Enabled = true;
@@ -896,7 +913,7 @@ namespace pipedal
void free_world(); void free_world();
std::vector<std::shared_ptr<Lv2PluginInfo>> plugins_; std::vector<std::shared_ptr<Lv2PluginInfo>> plugins_;
std::map<std::string,std::shared_ptr<Lv2PluginInfo>> pluginsByUri; std::map<std::string, std::shared_ptr<Lv2PluginInfo>> pluginsByUri;
std::vector<Lv2PluginUiInfo> ui_plugins_; std::vector<Lv2PluginUiInfo> ui_plugins_;
std::map<std::string, std::shared_ptr<Lv2PluginClass>> classesMap; std::map<std::string, std::shared_ptr<Lv2PluginClass>> classesMap;
@@ -947,22 +964,19 @@ namespace pipedal
public: public:
virtual MapFeature &GetMapFeature() override { return this->mapFeature; } virtual MapFeature &GetMapFeature() override { return this->mapFeature; }
void CheckForResourceInitialization(const std::string& pluginUri,const std::filesystem::path& pluginUploadDirectory); void CheckForResourceInitialization(const std::string &pluginUri, const std::filesystem::path &pluginUploadDirectory);
std::string MapResourcePath(const std::string &uri, const std::string &relativePath);
std::string MapResourcePath(const std::string&uri, const std::string&relativePath);
void ReloadPlugins(); void ReloadPlugins();
// equivalent to LV2 MapPath AbstractPath features. // equivalent to LV2 MapPath AbstractPath features.
std::string MapPath(const std::string &abstractPath); std::string MapPath(const std::string &abstractPath);
std::string AbstractPath(const std::string&path); std::string AbstractPath(const std::string &path);
json_variant MapPath(const json_variant&json); json_variant MapPath(const json_variant &json);
json_variant AbstractPath(const json_variant&json); json_variant AbstractPath(const json_variant &json);
private: private:
std::set<std::string> pluginsThatHaveBeenCheckedForResources; std::set<std::string> pluginsThatHaveBeenCheckedForResources;
void CheckForResourceInitization(const std::string pluginUri); void CheckForResourceInitization(const std::string pluginUri);
@@ -973,12 +987,11 @@ namespace pipedal
} }
static void PortValueCallback(const char *symbol, void *user_data, const void *value, uint32_t size, uint32_t type); static void PortValueCallback(const char *symbol, void *user_data, const void *value, uint32_t size, uint32_t type);
static void StateRestoreCallback( static void StateRestoreCallback(
const char* port_symbol, const char *port_symbol,
void* user_data, void *user_data,
const void* value, const void *value,
uint32_t size, uint32_t size,
uint32_t type); uint32_t type);
virtual IEffect *CreateEffect(PedalboardItem &pedalboardItem); virtual IEffect *CreateEffect(PedalboardItem &pedalboardItem);
void LoadPluginClassesFromLilv(); void LoadPluginClassesFromLilv();
@@ -995,9 +1008,9 @@ namespace pipedal
IHost *asIHost() { return this; } IHost *asIHost() { return this; }
virtual Lv2Pedalboard *CreateLv2Pedalboard(Pedalboard &pedalboard,Lv2PedalboardErrorList &errorList); virtual Lv2Pedalboard *CreateLv2Pedalboard(Pedalboard &pedalboard, Lv2PedalboardErrorList &errorList);
virtual Lv2Pedalboard *UpdateLv2PedalboardStructure(Pedalboard &pedalboard,Lv2Pedalboard *existingPedalboard,Lv2PedalboardErrorList &errorList); virtual Lv2Pedalboard *UpdateLv2PedalboardStructure(Pedalboard &pedalboard, Lv2Pedalboard *existingPedalboard, Lv2PedalboardErrorList &errorList);
void setSampleRate(double sampleRate) void setSampleRate(double sampleRate)
{ {
@@ -1010,9 +1023,8 @@ namespace pipedal
class Urids; class Urids;
Urids *urids = nullptr; Urids *urids = nullptr;
ModGuiUris* mod_gui_uris = nullptr; ModGuiUris *mod_gui_uris = nullptr;
void OnConfigurationChanged(const JackConfiguration &configuration, const JackChannelSelection &settings); void OnConfigurationChanged(const JackConfiguration &configuration, const JackChannelSelection &settings);
@@ -1021,7 +1033,7 @@ namespace pipedal
std::shared_ptr<Lv2PluginClass> GetLv2PluginClass() const; std::shared_ptr<Lv2PluginClass> GetLv2PluginClass() const;
const std::vector<std::shared_ptr<Lv2PluginInfo>>& GetPlugins() const { return plugins_; } const std::vector<std::shared_ptr<Lv2PluginInfo>> &GetPlugins() const { return plugins_; }
const std::vector<Lv2PluginUiInfo> &GetUiPlugins() const { return ui_plugins_; } const std::vector<Lv2PluginUiInfo> &GetUiPlugins() const { return ui_plugins_; }
virtual std::shared_ptr<Lv2PluginInfo> GetPluginInfo(const std::string &uri) const; virtual std::shared_ptr<Lv2PluginInfo> GetPluginInfo(const std::string &uri) const;
+14 -2
View File
@@ -1717,7 +1717,7 @@ void Storage::ToAbstractPaths(PluginPreset &pluginPreset)
std::map<std::string, std::string> newPathProperties; std::map<std::string, std::string> newPathProperties;
for (const auto &pair : pluginPreset.pathProperties_) for (const auto &pair : pluginPreset.pathProperties_)
{ {
newPathProperties[pair.first] = this->ToAbstractPathJson(pair.second); newPathProperties[pair.first] = this->ToAbstractPathFromJson(pair.second);
} }
pluginPreset.pathProperties_ = newPathProperties; pluginPreset.pathProperties_ = newPathProperties;
} }
@@ -2999,7 +2999,19 @@ std::string Storage::GetTone3000Auth() const
return tone3000Auth; return tone3000Auth;
} }
std::string Storage::ToAbstractPathJson(const std::string &pathJson) std::filesystem::path Storage::FromAbstractPathString(const std::string &stringPath)
{
if (stringPath.empty()) {
return "";
}
fs::path path { stringPath};
if (path.is_absolute()) {
return path;
}
return GetPluginUploadDirectory() / path;
}
std::string Storage::ToAbstractPathFromJson(const std::string &pathJson)
{ {
json_variant v = json_variant::parse(pathJson); json_variant v = json_variant::parse(pathJson);
+2 -1
View File
@@ -129,8 +129,9 @@ public:
const std::filesystem::path &GetPluginUploadDirectory() const; const std::filesystem::path &GetPluginUploadDirectory() const;
std::string ToAbstractPathJson(const std::string &jsonAtomPath); std::string ToAbstractPathFromJson(const std::string &jsonAtomPath);
std::string FromAbstractPathJson(const std::string &jsonAtomPath); std::string FromAbstractPathJson(const std::string &jsonAtomPath);
std::filesystem::path FromAbstractPathString(const std::string &stringPath);
//std::vector<std::string> GetPedalboards(); //std::vector<std::string> GetPedalboards();
+1 -1
View File
@@ -520,7 +520,7 @@ public:
res.set(HttpField::content_type, "application/json"); res.set(HttpField::content_type, "application/json");
res.set(HttpField::cache_control, "no-cache"); res.set(HttpField::cache_control, "no-cache");
fs::path path = model->GetStorage().FromAbstractPathJson(request_uri.query("path")); fs::path path = model->GetStorage().FromAbstractPathString(request_uri.query("path"));
if (!fs::exists(path) || !this->model->IsInUploadsDirectory(path) || HasDotDot(path)) if (!fs::exists(path) || !this->model->IsInUploadsDirectory(path) || HasDotDot(path))
{ {
+147 -123
View File
@@ -24,17 +24,40 @@ import { withStyles } from "tss-react/mui";
import { createStyles } from './WithStyles'; import { createStyles } from './WithStyles';
import FullscreenIcon from '@mui/icons-material/Fullscreen'; import FullscreenIcon from '@mui/icons-material/Fullscreen';
import IconButtonEx from './IconButtonEx'; import IconButtonEx from './IconButtonEx';
import { isDarkMode } from './DarkMode';
import Backdrop from '@mui/material/Backdrop'; import Backdrop from '@mui/material/Backdrop';
import CloseIcon from '@mui/icons-material/Close'; import ArrowBackIcon from '@mui/icons-material/ArrowBack';
import Rect from './Rect'; import Rect from './Rect';
import { css } from '@emotion/react';
import { PiPedalModel, PiPedalModelFactory } from './PiPedalModel'; import { PiPedalModel, PiPedalModelFactory } from './PiPedalModel';
const styles = (theme: Theme) => createStyles({ const styles = (theme: Theme) => createStyles({
pgraph: { pgraph: css({
paddingBottom: 16 paddingBottom: 16
} }),
backdrop: css({
zIndex: 1101,
overflow: "clip",
background: theme.palette.background.default,
}),
noToolbar: css({
display: "none"
}),
topToolBar: css({
position: "absolute",
zIndex: 1101,
top: 0,
display: "flex", flexFlow: "row nowrap", justifyContent: "start", columnGap: 8,
}),
rightToolbar: css({
position: "absolute",
zIndex: 1101,
top: 24,
display: "flex", flexFlow: "column nowrap", rowGap: 8
})
}); });
@@ -42,6 +65,7 @@ export interface AutoZoomProps extends WithStyles<typeof styles> {
leftPad: number, leftPad: number,
rightPad: number, rightPad: number,
children?: React.ReactNode, children?: React.ReactNode,
toolbarChildren?: React.ReactNode[],
contentReady: boolean, contentReady: boolean,
showZoomed: boolean, showZoomed: boolean,
setShowZoomed: (value: boolean) => void; setShowZoomed: (value: boolean) => void;
@@ -53,6 +77,15 @@ export interface AutoZoomState {
} }
// non-react zoom state.
class UnzoomedLayout {
horizontalToolbar = false;
effectiveClientRect: Rect = new Rect();
showToolbar = false;
zoom: number = 1;
showZoomButton = false;
contentRect: Rect = new Rect();
}
const AutoZoom = withStyles( const AutoZoom = withStyles(
class extends ResizeResponsiveComponent<AutoZoomProps, AutoZoomState> { class extends ResizeResponsiveComponent<AutoZoomProps, AutoZoomState> {
@@ -97,38 +130,43 @@ const AutoZoom = withStyles(
if (this.normalRef) { if (this.normalRef) {
this.updateZoom(this.normalRef); this.updateZoom(this.normalRef);
} }
if (this.maximizedRef) {
this.updateMaximizedZoom();
}
} }
} }
resizeObserver: ResizeObserver | null = null; resizeObserver: ResizeObserver | null = null;
calculateZoomedClientRect( calculateUnzoomedLayout(
clientRect: Rect, clientRect: Rect,
contentWidth: number, contentWidth: number,
contentHeight: number, contentHeight: number,
buttonPadding: number buttonPadding: number
): Rect { ): UnzoomedLayout {
let result = new UnzoomedLayout();
console.log("Zoom: " + clientRect.toString() + " content: " + contentWidth + "x" + contentHeight + " buttonPadding: " + buttonPadding);
let rightButtonZoom = Math.min((clientRect.width - buttonPadding) / contentWidth, clientRect.height / contentHeight); let rightButtonZoom = Math.min((clientRect.width - buttonPadding) / contentWidth, clientRect.height / contentHeight);
let topButtonZoom = Math.min((clientRect.width - buttonPadding) / contentWidth, (clientRect.height - buttonPadding) / contentHeight); let topButtonZoom = Math.min((clientRect.width - buttonPadding) / contentWidth, (clientRect.height - buttonPadding) / contentHeight);
let effectiveClientRect = clientRect.copy(); let effectiveClientRect = clientRect.copy();
if (rightButtonZoom > topButtonZoom) { if (rightButtonZoom > topButtonZoom) {
result.horizontalToolbar = false;
effectiveClientRect.width -= buttonPadding; effectiveClientRect.width -= buttonPadding;
} else { } else {
result.horizontalToolbar = true;
effectiveClientRect.y += buttonPadding; effectiveClientRect.y += buttonPadding;
effectiveClientRect.height -= buttonPadding; effectiveClientRect.height -= buttonPadding;
} }
result.effectiveClientRect = effectiveClientRect;
let zoom = Math.min(effectiveClientRect.width / contentWidth, effectiveClientRect.height / contentHeight); let zoom = Math.min(effectiveClientRect.width / contentWidth, effectiveClientRect.height / contentHeight);
if (zoom > 1) { if (zoom > 1) {
zoom = 1; zoom = 1;
} }
result.zoom = zoom;
result.showZoomButton = zoom !== 1;
result.showToolbar = result.showZoomButton || !!this.props.toolbarChildren;
let leftPad = (effectiveClientRect.width - contentWidth * zoom) / 2; let leftPad = (effectiveClientRect.width - contentWidth * zoom) / 2;
if (leftPad < 0) { if (leftPad < 0) {
leftPad = 0; leftPad = 0;
@@ -137,7 +175,8 @@ const AutoZoom = withStyles(
if (topPad < 0) { if (topPad < 0) {
topPad = 0; topPad = 0;
} }
return new Rect(effectiveClientRect.x + leftPad, effectiveClientRect.y + topPad, contentWidth * zoom, contentHeight * zoom); result.contentRect = new Rect(effectiveClientRect.x + leftPad, effectiveClientRect.y + topPad, contentWidth * zoom, contentHeight * zoom);
return result;
} }
updateZoom(ref: HTMLDivElement) { updateZoom(ref: HTMLDivElement) {
@@ -145,59 +184,58 @@ const AutoZoom = withStyles(
return; return;
} }
let content = ref.firstElementChild as HTMLElement | null; let content = ref.firstElementChild as HTMLElement | null;
const classes = withStyles.getClasses(this.props);
if (content) { if (content) {
const { leftPad, rightPad } = this.props; const { leftPad, rightPad } = this.props;
let bounds = new Rect(leftPad, 0, ref.clientWidth - leftPad - rightPad, ref.clientHeight - 8); let bounds = new Rect(leftPad, 0, ref.clientWidth - leftPad - rightPad, ref.clientHeight - 8);
let zoomedClientRect = this.calculateZoomedClientRect( let layout = this.calculateUnzoomedLayout(
bounds, content.clientWidth, content.clientHeight, 48); bounds, content.clientWidth, content.clientHeight, 48);
let contentRect = layout.contentRect;
content.style.left = "0px"; content.style.left = "0px";
content.style.top = "0px"; content.style.top = "0px";
content.style.transformOrigin = "0 0"; // top-left corner content.style.transformOrigin = "0 0"; // top-left corner
content.style.transform = content.style.transform =
`translate(${zoomedClientRect.x}px, ${zoomedClientRect.y}px) scale(${zoomedClientRect.width / content.clientWidth}, ${zoomedClientRect.height / content.clientHeight})`; `translate(${contentRect.x}px, ${contentRect.y}px) scale(${layout.zoom}, ${layout.zoom})`;
if (this.zoomButton) { if (this.zoomToolbar) {
this.zoomButton.style.display = zoomedClientRect.width < content.clientWidth ? "block" : "none"; if (!layout.showToolbar) {
let rightSizeSpace = ref.clientWidth - zoomedClientRect.right;
if (rightSizeSpace - rightPad >= 48) { this.zoomToolbar.className = classes.noToolbar;
this.zoomButton.style.top = `${zoomedClientRect.y + 24}px`; } else if (layout.horizontalToolbar) {
this.zoomButton.style.left = `${zoomedClientRect.right + 4}px`; this.zoomToolbar.className = classes.topToolBar;
this.zoomToolbar.style.left = `${contentRect.x +16}px`
this.zoomToolbar.style.top = `${contentRect.y - 48}px`;
this.zoomToolbar.style.width = `${contentRect.width}px`;
} else { } else {
this.zoomButton.style.top = `${zoomedClientRect.y - 48}px`; this.zoomToolbar.className = classes.rightToolbar;
this.zoomButton.style.left = `${zoomedClientRect.right - 64 - 12}px`; this.zoomToolbar.style.left = "";
this.zoomToolbar.style.top = `${contentRect.y}px`;
this.zoomToolbar.style.left = `${contentRect.right + 4}px`;
this.zoomToolbar.style.width = "";
} }
} }
if (this.zoomButton) {
this.zoomButton.style.display = layout.showZoomButton ? "block" : "none";
}
// save the position of the Mod UI in page coordinates for later zooming. // save the position of the Mod UI in page coordinates for later zooming.
this.zoomStartRect = zoomedClientRect.copy(); this.zoomStartRect = layout.contentRect.copy();
let refBounds = ref.getBoundingClientRect(); let refBounds = ref.getBoundingClientRect();
this.zoomStartRect.x += refBounds.left; this.zoomStartRect.x += refBounds.left;
this.zoomStartRect.y += refBounds.top; this.zoomStartRect.y += refBounds.top;
} else {
if (this.zoomToolbar) {
this.zoomToolbar.className = classes.noToolbar;
}
} }
} }
updateMaximizedZoom() { updateMaximizedZoom() {
let ref = this.maximizedRef; if (this.normalRef) {
if (!this.mounted || !ref) { this.updateZoom(this.normalRef);
return;
}
let content = ref.firstElementChild as HTMLElement | null;
if (content) {
let leftPad = 16; let rightPad = 16;
let topPad = 16, bottomPad = 16;
let bounds: Rect;
bounds = new Rect(leftPad, topPad, ref.clientWidth - leftPad - rightPad, ref.clientHeight - topPad - bottomPad);
let zoomedClientRect = this.calculateZoomedClientRect(
bounds, content.clientWidth, content.clientHeight, 48);
content.style.left = "0px";
content.style.top = "0px";
content.style.transformOrigin = "0 0"; // top-left corner
content.style.transform = `translate(${zoomedClientRect.x}px, ${zoomedClientRect.y}px) scale(${zoomedClientRect.width / content.clientWidth}, ${zoomedClientRect.height / content.clientHeight})`;
//console.log("Zoomed to: ", zoomedClientRect.toString());
} }
} }
@@ -234,20 +272,20 @@ const AutoZoom = withStyles(
} }
this.maximizedAnimateFrameHandle = window.requestAnimationFrame(() => { this.maximizedAnimateFrameHandle = window.requestAnimationFrame(() => {
this.maximizedAnimateFrameHandle = null; this.maximizedAnimateFrameHandle = null;
if (this.maximizedRef) { this.updateMaximizedZoom();
this.updateMaximizedZoom();
}
}); });
} }
private zoomToolbar: HTMLElement | null = null;
private zoomButton: HTMLElement | null = null; private zoomButton: HTMLElement | null = null;
private normalRef: HTMLDivElement | null = null; private normalRef: HTMLDivElement | null = null;
setNormalRef(ref: HTMLDivElement | null) { setContentRef(ref: HTMLDivElement | null) {
this.normalRef = ref; this.normalRef = ref;
if (ref) { if (ref) {
this.zoomButton = ref.querySelector("#maximize-button") as HTMLDivElement | null; this.zoomToolbar = ref.querySelector("#zoom-toolbar") as HTMLDivElement | null;
this.zoomButton = ref.querySelector("#zoom-button") as HTMLDivElement | null;
this.resizeObserver = new ResizeObserver(() => { this.resizeObserver = new ResizeObserver(() => {
this.requestZoomUpdate(); this.requestZoomUpdate();
}); });
@@ -261,36 +299,15 @@ const AutoZoom = withStyles(
if (this.resizeObserver) { if (this.resizeObserver) {
this.resizeObserver.disconnect(); this.resizeObserver.disconnect();
this.resizeObserver = null; this.resizeObserver = null;
this.zoomButton = null; this.zoomToolbar = null;
} }
this.zoomStartRect = null; this.zoomStartRect = null;
this.zoomToolbar = null;
this.zoomButton = null;
} }
} }
private maximizedRef: HTMLDivElement | null = null;
private maximedResizeObserver: ResizeObserver | null = null;
setMaximizedRef(ref: HTMLDivElement | null) {
this.maximizedRef = ref;
if (ref) {
this.maximedResizeObserver = new ResizeObserver(() => {
this.requestMaximizedZoomUpdate();
});
this.maximedResizeObserver.observe(ref);
let child = ref.firstElementChild as HTMLElement | null;
if (child) {
this.maximedResizeObserver.observe(child);
}
this.requestMaximizedZoomUpdate();
} else {
if (this.maximedResizeObserver) {
this.maximedResizeObserver.disconnect();
this.maximedResizeObserver = null;
}
}
}
private zoomStartRect: Rect | null = null; private zoomStartRect: Rect | null = null;
startZoom() { startZoom() {
@@ -300,70 +317,77 @@ const AutoZoom = withStyles(
this.props.setShowZoomed(true); this.props.setShowZoomed(true);
} }
render() { content() {
const classes = withStyles.getClasses(this.props); const classes = withStyles.getClasses(this.props);
void classes; // suppress unused variable warning void classes; // suppress unused variable warning
const landscape = this.state.screenWidth > this.state.screenHeight; return (
return (<div style={{ overflow: "hidden", height: "100%", width: "100%" }} ref={(ref) => { this.setNormalRef(ref); }}> <div ref={(ref) => { this.setContentRef(ref); }}
<div style={{ style={{ width: "100%", height: "100%" }}
display: "inline-block",
position: "absolute",
visibility: this.props.contentReady ? "visible" : "hidden",
}}>
{!this.props.showZoomed && this.props.children}
</div>
<div id="maximize-button" style={{
display: "none", position: "absolute", left: 0, top: 0, width: 52, height: 64, zIndex: 1101,
borderRadius: "0px 26px 26px 0px",
}}
> >
<IconButtonEx tooltip="Fullscreen view" <div style={{
style={{ margin: 2, background: isDarkMode() ? "#444" : "#eee" }} display: "inline-block",
onClick={() => { position: "absolute",
this.startZoom(); visibility: this.props.contentReady ? "visible" : "hidden"
}}>
{this.props.children}
</div>
<div id="zoom-toolbar" className={classes.noToolbar}
style={{
visibility: this.props.contentReady ? "visible" : "hidden"
}} }}
> >
<FullscreenIcon style={{ color: "white" }} /> {!this.props.showZoomed ? (
</IconButtonEx> <IconButtonEx id="zoom-button" tooltip="Fullscreen view"
</div> onClick={() => {
{this.props.showZoomed && ( this.startZoom();
<Backdrop id="modGuiZoomBackdrop"
sx={(theme) => ({
zIndex: this.props.showZoomed ? 1101 : -1,
overflow: "clip",
background: theme.palette.mode === 'dark' ? 'rgba(0, 0, 0, 0.9)' : 'rgba(255, 255, 255, 0.9)'
})}
open={this.props.showZoomed}
>
<div ref={(ref) => { this.setMaximizedRef(ref); }}
style={{
position: "absolute", left: 0, top: 0, right: 0, bottom: 0, overflow: "hidden",
visibility: this.props.contentReady ? "visible" : "hidden"
}}>
<div style={{ display: "inline-block", position: "relative" }}>
{
this.props.showZoomed && this.props.children
}
</div>
<IconButtonEx tooltip="Exit fullscreen"
style={{
position: "absolute",
right: landscape? 80: 16, // avoid potential cutout in landscape mode
top: 16, zIndex: 1104,
}} }}
>
<FullscreenIcon style={{ opacity: 0.6 }}
/>
</IconButtonEx>
) : (
<IconButtonEx tooltip="Exit fullscreen"
style={{}}
onClick={(e) => { onClick={(e) => {
e.stopPropagation(); e.stopPropagation();
this.props.setShowZoomed(false); this.props.setShowZoomed(false);
}} }}
> >
<CloseIcon /> <ArrowBackIcon style={{
opacity: 0.6,
}} />
</IconButtonEx> </IconButtonEx>
</div> )}
{this.props.toolbarChildren && this.props.toolbarChildren.map((child, index) => (
<div key={index} style={{ display: "inline-block" }}>
{child}
</div>
))}
</div>
</div>
);
}
render() {
const classes = withStyles.getClasses(this.props);
void classes; // suppress unused variable warning
return (<div style={{ overflow: "hidden", height: "100%", width: "100%" }} >
{!this.props.showZoomed && this.content()}
{this.props.showZoomed && (
<Backdrop id="modGuiZoomBackdrop"
className={classes.backdrop}
open={this.props.showZoomed}
>
{this.content()}
</Backdrop> </Backdrop>
)} )}
</div>); </div>);
+12
View File
@@ -47,6 +47,7 @@ export class Port implements Deserializable<Port> {
this.scale_points = ScalePoint.deserialize_array(input.scale_points); this.scale_points = ScalePoint.deserialize_array(input.scale_points);
this.is_input = input.is_input; this.is_input = input.is_input;
this.is_output = input.is_output; this.is_output = input.is_output;
this.is_sidechain = input.is_sidechain;
this.is_control_port = input.is_control_port; this.is_control_port = input.is_control_port;
this.is_audio_port = input.is_audio_port; this.is_audio_port = input.is_audio_port;
this.is_atom_port = input.is_atom_port; this.is_atom_port = input.is_atom_port;
@@ -80,6 +81,7 @@ export class Port implements Deserializable<Port> {
is_output: boolean = false is_output: boolean = false
is_control_port: boolean = false; is_control_port: boolean = false;
is_audio_port: boolean = false; is_audio_port: boolean = false;
is_sidechain: boolean = false;
is_atom_port: boolean = false; is_atom_port: boolean = false;
is_valid: boolean = false; is_valid: boolean = false;
supports_midi: boolean = false; supports_midi: boolean = false;
@@ -92,9 +94,11 @@ export class Port implements Deserializable<Port> {
export class PortGroup { export class PortGroup {
deserialize(input: any): PortGroup { deserialize(input: any): PortGroup {
this.isA = input.isA;
this.uri = input.uri; this.uri = input.uri;
this.symbol = input.symbol; this.symbol = input.symbol;
this.name = input.name; this.name = input.name;
this.sideChainof = input.sideChainOf;
this.parent_group = input.parent_group; this.parent_group = input.parent_group;
this.program_list_id = input.program_list_id ?? -1; this.program_list_id = input.program_list_id ?? -1;
@@ -108,9 +112,12 @@ export class PortGroup {
return result; return result;
} }
isA: string[] = [];
uri: string = ""; uri: string = "";
symbol: string = ""; symbol: string = "";
name: string = ""; name: string = "";
sideChainof: string = "";
parent_group: string = ""; parent_group: string = "";
program_list_id: number = -1; program_list_id: number = -1;
}; };
@@ -967,6 +974,8 @@ export class UiPlugin implements Deserializable<UiPlugin> {
this.author_name = input.author_name; this.author_name = input.author_name;
this.author_homepage = input.author_homepage; this.author_homepage = input.author_homepage;
this.audio_inputs = input.audio_inputs; this.audio_inputs = input.audio_inputs;
this.audio_side_chain_inputs = input.audio_side_chain_inputs ?? 0;
this.audio_side_chain_title = input.audio_side_chain_title ?? "";
this.audio_outputs = input.audio_outputs; this.audio_outputs = input.audio_outputs;
this.has_midi_input = input.has_midi_input; this.has_midi_input = input.has_midi_input;
this.has_midi_output = input.has_midi_output; this.has_midi_output = input.has_midi_output;
@@ -1057,7 +1066,9 @@ export class UiPlugin implements Deserializable<UiPlugin> {
author_name: string = ""; author_name: string = "";
author_homepage: string = ""; author_homepage: string = "";
audio_inputs: number = 0; audio_inputs: number = 0;
audio_side_chain_inputs: number = 0;
audio_outputs: number = 0; audio_outputs: number = 0;
audio_side_chain_title: string = "";
has_midi_input: number = 0; has_midi_input: number = 0;
has_midi_output: number = 0; has_midi_output: number = 0;
description: string = ""; description: string = "";
@@ -1084,6 +1095,7 @@ export function makeSplitUiPlugin(): UiPlugin {
author_name: "", author_name: "",
author_homepage: "", author_homepage: "",
audio_inputs: 1, audio_inputs: 1,
audio_side_chain_inputs: 0,
audio_outputs: 1, audio_outputs: 1,
has_midi_input: 0, has_midi_input: 0,
has_midi_output: 0, has_midi_output: 0,
+37
View File
@@ -81,6 +81,7 @@ export class PedalboardItem implements Deserializable<PedalboardItem> {
this.pathProperties = input.pathProperties; this.pathProperties = input.pathProperties;
this.useModUi = input.useModUi ?? false; this.useModUi = input.useModUi ?? false;
this.iconColor = input.iconColor??""; this.iconColor = input.iconColor??"";
this.sideChainInputId = input.sideChainInputId ?? -1;
return this; return this;
} }
@@ -217,6 +218,7 @@ export class PedalboardItem implements Deserializable<PedalboardItem> {
pathProperties: {[Name: string]: string} = {}; pathProperties: {[Name: string]: string} = {};
useModUi: boolean = false; // true if this item should use the mod-ui. useModUi: boolean = false; // true if this item should use the mod-ui.
iconColor: string = ""; iconColor: string = "";
sideChainInputId: number = -1; // -1 means no sidechain input.
}; };
export class SnapshotValue { export class SnapshotValue {
@@ -392,6 +394,7 @@ export class Pedalboard implements Deserializable<Pedalboard> {
pathProperties: {[Name: string]: string} = {}; pathProperties: {[Name: string]: string} = {};
selectedPlugin: number = -1; selectedPlugin: number = -1;
// yields all items in the pedalboard, including split items. Splits are yielded before their children.
*itemsGenerator(): Generator<PedalboardItem, void, undefined> { *itemsGenerator(): Generator<PedalboardItem, void, undefined> {
let it = itemGenerator_(this.items); let it = itemGenerator_(this.items);
while (true) while (true)
@@ -401,6 +404,16 @@ export class Pedalboard implements Deserializable<Pedalboard> {
yield v.value; yield v.value;
} }
} }
// same as itemsGenerator, but yields split items after their chains.
*itemsGeneratorSplitAfter(): Generator<PedalboardItem, void, undefined> {
let it = itemGeneratorSplitAfter_(this.items);
while (true)
{
let v = it.next();
if (v.done) break;
yield v.value;
}
}
makeSnapshot(): Snapshot { makeSnapshot(): Snapshot {
let result = new Snapshot(); let result = new Snapshot();
@@ -791,4 +804,28 @@ function* itemGenerator_(items: PedalboardItem[]): Generator<PedalboardItem, voi
} }
} }
function* itemGeneratorSplitAfter_(items: PedalboardItem[]): Generator<PedalboardItem, void, undefined> {
for (let i = 0; i < items.length; ++i) {
let item = items[i];
if (item.uri === SPLIT_PEDALBOARD_ITEM_URI) {
let splitItem = item as PedalboardSplitItem;
let it = itemGenerator_(splitItem.topChain);
while (true) {
let v = it.next();
if (v.done) break;
yield v.value;
}
it = itemGenerator_(splitItem.bottomChain);
while (true) {
let v = it.next();
if (v.done) break;
yield v.value;
}
}
yield item;
}
}
+76 -11
View File
@@ -550,19 +550,16 @@ export class PiPedalModel //implements PiPedalModel
); );
zoomedUiControl: ObservableProperty<ZoomedControlInfo | undefined> = new ObservableProperty<ZoomedControlInfo | undefined>(undefined); zoomedUiControl: ObservableProperty<ZoomedControlInfo | undefined> = new ObservableProperty<ZoomedControlInfo | undefined>(undefined);
uiPluginsByUri: Map<string, UiPlugin> = new Map<string, UiPlugin>();
svgImgUrl(svgImage: string): string { svgImgUrl(svgImage: string): string {
//return this.varServerUrl + "img/" + svgImage; // return this.varServerUrl + "img/" + svgImage;
return "img/" + svgImage; return "img/" + svgImage;
} }
getUiPlugin(uri: string): UiPlugin | null { getUiPlugin(uri: string): UiPlugin | null {
let plugins: UiPlugin[] = this.ui_plugins.get(); return this.uiPluginsByUri.get(uri) ?? null;
for (let plugin of plugins) {
if (plugin.uri === uri) return plugin;
}
return null;
} }
androidHost?: AndroidHostInterface; androidHost?: AndroidHostInterface;
@@ -662,6 +659,7 @@ export class PiPedalModel //implements PiPedalModel
} }
private setModelPedalboard(pedalboard: Pedalboard) { private setModelPedalboard(pedalboard: Pedalboard) {
this.removeInvalidSidechains(pedalboard);
this.pedalboard.set(pedalboard); this.pedalboard.set(pedalboard);
this.selectedSnapshot.set(pedalboard.selectedSnapshot); this.selectedSnapshot.set(pedalboard.selectedSnapshot);
this.updateEnabledItems(pedalboard); this.updateEnabledItems(pedalboard);
@@ -1210,6 +1208,11 @@ export class PiPedalModel //implements PiPedalModel
this.ui_plugins.set( this.ui_plugins.set(
UiPlugin.deserialize_array(await this.getWebSocket().request<any>("plugins")) UiPlugin.deserialize_array(await this.getWebSocket().request<any>("plugins"))
); );
// index ui plugins.
this.uiPluginsByUri = new Map<string, UiPlugin>();
for (let i of this.ui_plugins.get()) {
this.uiPluginsByUri.set(i.uri, i);
}
this.setModelPedalboard( this.setModelPedalboard(
new Pedalboard().deserialize( new Pedalboard().deserialize(
await this.getWebSocket().request<Pedalboard>("currentPedalboard") await this.getWebSocket().request<Pedalboard>("currentPedalboard")
@@ -1761,13 +1764,17 @@ export class PiPedalModel //implements PiPedalModel
} }
let it = newPedalboard.itemsGenerator(); let it = newPedalboard.itemsGenerator();
let oldInstanceId = -1;
let newInstanceId = -1;
while (true) { while (true) {
let v = it.next(); let v = it.next();
if (v.done) break; if (v.done) break;
let item = v.value; let item = v.value;
if (item.instanceId === itemId) { if (item.instanceId === itemId) {
oldInstanceId = item.instanceId;
item.deserialize(new PedalboardItem()); // skeezy way to re-initialize. item.deserialize(new PedalboardItem()); // skeezy way to re-initialize.
item.instanceId = ++newPedalboard.nextInstanceId; item.instanceId = ++newPedalboard.nextInstanceId;
newInstanceId = item.instanceId;
item.uri = selectedUri; item.uri = selectedUri;
item.pluginName = plugin.name; item.pluginName = plugin.name;
item.controlValues = this.getDefaultValues(item.uri); item.controlValues = this.getDefaultValues(item.uri);
@@ -1782,6 +1789,9 @@ export class PiPedalModel //implements PiPedalModel
item.pathProperties[fileProperty.patchProperty] = "null"; item.pathProperties[fileProperty.patchProperty] = "null";
} }
newPedalboard.selectedPlugin = item.instanceId; newPedalboard.selectedPlugin = item.instanceId;
if (oldInstanceId !== -1) {
this.updateSidechainReferences(newPedalboard, oldInstanceId, newInstanceId);
}
this.setModelPedalboard(newPedalboard); this.setModelPedalboard(newPedalboard);
this.updateServerPedalboard() this.updateServerPedalboard()
return item.instanceId; return item.instanceId;
@@ -1936,6 +1946,9 @@ export class PiPedalModel //implements PiPedalModel
newPedalboard.replaceItem(fromInstanceId, emptyItem); newPedalboard.replaceItem(fromInstanceId, emptyItem);
newPedalboard.replaceItem(toInstanceId, fromItem); newPedalboard.replaceItem(toInstanceId, fromItem);
newPedalboard.selectedPlugin = fromItem.instanceId; newPedalboard.selectedPlugin = fromItem.instanceId;
if (!toItem.isEmpty()) {
this.updateSidechainReferences(newPedalboard, toItem.instanceId, fromItem.instanceId);
}
this.setModelPedalboard(newPedalboard); this.setModelPedalboard(newPedalboard);
this.updateServerPedalboard(); this.updateServerPedalboard();
@@ -3543,14 +3556,66 @@ export class PiPedalModel //implements PiPedalModel
} }
requestBankPresets(bankInstanceId: number): Promise<PresetIndexEntry[]> { requestBankPresets(bankInstanceId: number): Promise<PresetIndexEntry[]> {
return nullCast(this.webSocket).request<PresetIndexEntry[]>("requestBankPresets", {bankInstanceId: bankInstanceId}); return nullCast(this.webSocket).request<PresetIndexEntry[]>("requestBankPresets", { bankInstanceId: bankInstanceId });
} }
importPresetsFromBank(bankInstanceId: number, presets: number[]): Promise<number> { importPresetsFromBank(bankInstanceId: number, presets: number[]): Promise<number> {
return nullCast(this.webSocket).request<number>("importPresetsFromBank", {bankInstanceId: bankInstanceId, presets: presets}); return nullCast(this.webSocket).request<number>("importPresetsFromBank", { bankInstanceId: bankInstanceId, presets: presets });
} }
copyPresetsToBank(bankInstanceId: number, presets: number[]): Promise<number> { copyPresetsToBank(bankInstanceId: number, presets: number[]): Promise<number> {
return nullCast(this.webSocket).request<number>("copyPresetsToBank", {bankInstanceId: bankInstanceId, presets: presets}); return nullCast(this.webSocket).request<number>("copyPresetsToBank", { bankInstanceId: bankInstanceId, presets: presets });
}
setPedalboardSideChainInput(instanceId: number, sideChainInputId: number): void {
let pedalboard = this.pedalboard.get().clone();
let pedalboardItem = pedalboard.getItem(instanceId);
if (!pedalboardItem) {
return;
}
pedalboardItem.sideChainInputId = sideChainInputId;
let oldId = pedalboardItem.instanceId;
let newId = ++pedalboard.nextInstanceId;
pedalboardItem.instanceId = newId; // force reload of pedalboard.
pedalboard.selectedPlugin = pedalboardItem.instanceId;
this.updateSidechainReferences(pedalboard, oldId, newId);
this.setModelPedalboard(pedalboard);
this.updateServerPedalboard();
}
removeInvalidSidechains(pedalboard: Pedalboard) {
let it = pedalboard.itemsGeneratorSplitAfter();
let previousItemIds: Set<number> = new Set<number>();
previousItemIds.add(-2); // start is visible.
while (true) {
let r = it.next();
if (r.done) {
break;
}
let item = r.value;
if (item.sideChainInputId !== -1) {
if (!previousItemIds.has(item.sideChainInputId)) {
item.sideChainInputId = -1;
}
}
if (!item.isEmpty()) { // empty items are not valid sidechain targets.
previousItemIds.add(item.instanceId);
}
}
}
updateSidechainReferences(pedalboard: Pedalboard, oldItemId: number, newItemId: number) {
let it = pedalboard.itemsGenerator();
while (true) {
let r = it.next();
if (r.done) break;
let item = r.value;
if (item.sideChainInputId === oldItemId) {
item.sideChainInputId = newItemId;
}
}
this.removeInvalidSidechains(pedalboard);
} }
}; };
+114 -3
View File
@@ -22,6 +22,9 @@ import { Theme } from '@mui/material/styles';
import WithStyles, { withTheme } from './WithStyles'; import WithStyles, { withTheme } from './WithStyles';
import { createStyles } from './WithStyles'; import { createStyles } from './WithStyles';
import { css } from '@emotion/react'; import { css } from '@emotion/react';
import IconButtonEx from './IconButtonEx';
import SidechainConnectIcon from './svg/ic_sidechain_open_48.svg?react';
import AutoZoom from './AutoZoom'; import AutoZoom from './AutoZoom';
@@ -37,6 +40,7 @@ import {
} from './Pedalboard'; } from './Pedalboard';
import PluginControl from './PluginControl'; import PluginControl from './PluginControl';
import ResizeResponsiveComponent from './ResizeResponsiveComponent'; import ResizeResponsiveComponent from './ResizeResponsiveComponent';
import SideChainSelectControl, {SideChainSelectDialog} from './SideChainSelectControl';
import VuMeter from './VuMeter'; import VuMeter from './VuMeter';
import { nullCast } from './Utility' import { nullCast } from './Utility'
import { PiPedalStateError } from './PiPedalError'; import { PiPedalStateError } from './PiPedalError';
@@ -66,6 +70,11 @@ export interface ICustomizationHost {
isLandscapeGrid(): boolean; isLandscapeGrid(): boolean;
} }
interface SideChainSelectItem {
instanceId: number;
title: string;
}
function makeIoPluginInfo(name: string, uri: string): UiPlugin { function makeIoPluginInfo(name: string, uri: string): UiPlugin {
let result = new UiPlugin(); let result = new UiPlugin();
result.name = name; result.name = name;
@@ -367,11 +376,12 @@ type PluginControlViewState = {
dialogFileValue: string, dialogFileValue: string,
modGuiContentReady: boolean, modGuiContentReady: boolean,
showModGuiZoomed: boolean, showModGuiZoomed: boolean,
sidechainDialogOpen: boolean
}; };
const PluginControlView = const PluginControlView =
withTheme(withStyles( withTheme(withStyles(
class extends ResizeResponsiveComponent<PluginControlViewProps, PluginControlViewState> implements ICustomizationHost { class extends ResizeResponsiveComponent<PluginControlViewProps, PluginControlViewState> {
model: PiPedalModel; model: PiPedalModel;
constructor(props: PluginControlViewProps) { constructor(props: PluginControlViewProps) {
@@ -387,7 +397,8 @@ const PluginControlView =
dialogFileProperty: new UiFileProperty(), dialogFileProperty: new UiFileProperty(),
dialogFileValue: "", dialogFileValue: "",
modGuiContentReady: false, modGuiContentReady: false,
showModGuiZoomed: false showModGuiZoomed: false,
sidechainDialogOpen: false
} }
this.onPedalboardChanged = this.onPedalboardChanged.bind(this); this.onPedalboardChanged = this.onPedalboardChanged.bind(this);
@@ -553,6 +564,68 @@ const PluginControlView =
} }
} }
private getSidechainSelectItems(): SideChainSelectItem[] {
let myInstanceId = this.props.item.instanceId;
let items: SideChainSelectItem[] = [];
items.push({ instanceId: -1, title: "None" });
items.push({ instanceId: -2, title: "Input" });
let pedalboard = this.model.pedalboard.get();
if (!pedalboard) return items;
let it = pedalboard.itemsGenerator();
let found = false;
while (true) {
let v = it.next();
if (v.done) {
break;
}
let pedalboardItem = v.value;
if (pedalboardItem.isSplit()) {
continue;
}
if (pedalboardItem.instanceId === myInstanceId) {
found = true;
break;
}
if (pedalboardItem.uri.length === 0) {
continue;
}
let pluginInfo = this.model.getUiPlugin(pedalboardItem.uri);
if (!pluginInfo) continue;
let name = pluginInfo.name;
if (pedalboardItem.title.length !== 0) {
name = pedalboardItem.title;
}
items.push({ instanceId: pedalboardItem.instanceId, title: name });
}
if (!found) {
return [{ instanceId: -1, title: "None" }];
}
return items;
}
private makeSideChainSelect(uiPlugin: UiPlugin) {
let items = this.getSidechainSelectItems();
let selectedInstanceId = this.props.item.sideChainInputId;
let title = uiPlugin.audio_side_chain_title ? uiPlugin.audio_side_chain_title : "Side chain";
return (
<SideChainSelectControl
title={title}
selectItems={items}
selectedInstanceId={selectedInstanceId}
onChanged={(instanceId: number) => {
this.model.setPedalboardSideChainInput(this.props.item.instanceId, instanceId);
}}
/>
);
}
isLandscapeGrid(): boolean { isLandscapeGrid(): boolean {
return this.state.landscapeGrid; return this.state.landscapeGrid;
} }
@@ -575,7 +648,7 @@ const PluginControlView =
} }
return ( return (
<div key={key} className={!isLandscapeGrid ? classes.portGroup : classes.portGroupLandscape} <div key={key} className={!isLandscapeGrid ? classes.portGroup : classes.portGroupLandscape}
style={{ borderWidth: (controlGroup.name === "" ? 0: undefined) }} style={{ borderWidth: (controlGroup.name === "" ? 0 : undefined) }}
> >
{controlGroup.name !== "" && ( {controlGroup.name !== "" && (
<div className={classes.portGroupTitle}> <div className={classes.portGroupTitle}>
@@ -711,6 +784,11 @@ const PluginControlView =
); );
} }
} }
if (plugin.audio_side_chain_inputs !== 0) {
result.push(this.makeSideChainSelect(plugin));
}
return result; return result;
} }
@@ -803,6 +881,9 @@ const PluginControlView =
return false; return false;
} }
handleSelectSidechainInput() {
this.setState({sidechainDialogOpen: true});
}
handleModGuiFileProperty(instanceId: number, filePropertyUri: string, selectedFile: string) { handleModGuiFileProperty(instanceId: number, filePropertyUri: string, selectedFile: string) {
let plugin = this.model.getUiPlugin(this.props.item.uri); let plugin = this.model.getUiPlugin(this.props.item.uri);
@@ -827,10 +908,30 @@ const PluginControlView =
if (!uiPlugin) { if (!uiPlugin) {
return (<div />); return (<div />);
} }
let toolBarChildren: ReactNode[] = [];
if (uiPlugin.audio_side_chain_inputs > 0) {
toolBarChildren.push(
<IconButtonEx tooltip="Select sidechain input"
onClick={() => {
this.handleSelectSidechainInput();
}}
>
<SidechainConnectIcon
style={{
opacity: 0.6,
width: 24,
height: 24,
fill: this.props.theme.palette.text.primary
}}
/>
</IconButtonEx>
);
}
return ( return (
<div style={{ width: "100%", height: "100%", }}> <div style={{ width: "100%", height: "100%", }}>
<AutoZoom leftPad={36} rightPad={52} contentReady={this.state.modGuiContentReady} <AutoZoom leftPad={36} rightPad={52} contentReady={this.state.modGuiContentReady}
showZoomed={this.state.showModGuiZoomed} showZoomed={this.state.showModGuiZoomed}
toolbarChildren={toolBarChildren}
setShowZoomed={ setShowZoomed={
(value) => { this.setState({ showModGuiZoomed: value }); } (value) => { this.setState({ showModGuiZoomed: value }); }
} }
@@ -1012,6 +1113,16 @@ const PluginControlView =
} }
/> />
)} )}
{this.state.sidechainDialogOpen && (
<SideChainSelectDialog open={this.state.sidechainDialogOpen}
onClose={()=>{ this.setState({sidechainDialogOpen: false});}}
selectItems={this.getSidechainSelectItems()}
selectedInstanceId={this.props.item.sideChainInputId}
onChanged={(instanceId: number) => {
this.model.setPedalboardSideChainInput(this.props.item.instanceId, instanceId);
}}
/>
)}
{/* xxx: I don't think we need this anyore. */} {/* xxx: I don't think we need this anyore. */}
<FullScreenIME uiControl={this.state.imeUiControl} value={this.state.imeValue} <FullScreenIME uiControl={this.state.imeUiControl} value={this.state.imeValue}
+96 -93
View File
@@ -20,7 +20,7 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import { Theme } from '@mui/material/styles'; import { Theme } from '@mui/material/styles';
import WithStyles from './WithStyles'; import WithStyles from './WithStyles';
import {createStyles} from './WithStyles'; import { createStyles } from './WithStyles';
import { withStyles } from "tss-react/mui"; import { withStyles } from "tss-react/mui";
import { UiControl } from './Lv2Plugin'; import { UiControl } from './Lv2Plugin';
@@ -47,8 +47,7 @@ const defaultVuColors: VuColor[] = [
function getVuColor(vuColors: VuColor[], levelDb: number): string { function getVuColor(vuColors: VuColor[], levelDb: number): string {
let ix = 0; let ix = 0;
for (let i = 0; i < vuColors.length; ++i) for (let i = 0; i < vuColors.length; ++i) {
{
let c = vuColors[i]; let c = vuColors[i];
if (levelDb > c.minDb && levelDb <= c.maxDb) { if (levelDb > c.minDb && levelDb <= c.maxDb) {
ix = i; ix = i;
@@ -122,18 +121,18 @@ const styles = (theme: Theme) => createStyles({
}), }),
titleSection: css({ titleSection: css({
flex: "0 0 auto", alignSelf:"stretch", marginBottom: 8, marginLeft: 0, marginRight: 0 flex: "0 0 auto", alignSelf: "stretch", marginBottom: 8, marginLeft: 0, marginRight: 0
}), }),
midSection: css({ midSection: css({
flex: "1 1 1", display: "flex",flexFlow: "column nowrap",alignContent: "center",justifyContent: "center" flex: "1 1 1", display: "flex", flexFlow: "column nowrap", alignContent: "center", justifyContent: "center"
}), }),
editSection: css({ editSection: css({
flex: "0 0 28", position: "relative", width: 60, height: 28, minHeight: 28 flex: "0 0 28", position: "relative", width: 60, height: 28, minHeight: 28
}), }),
editSectionNoContent: css({ editSectionNoContent: css({
flex: "0 0 28", position: "relative", width: 1, height: 28, minHeight: 28 flex: "0 0 28", position: "relative", width: 1, height: 28, minHeight: 28
}) })
}); });
@@ -153,12 +152,12 @@ const PluginOutputControl =
class extends Component<PluginOutputControlProps, PluginOutputControlState> { class extends Component<PluginOutputControlProps, PluginOutputControlState> {
private model: PiPedalModel; private model: PiPedalModel;
private vuRef: React.RefObject<HTMLDivElement|null>; private vuRef: React.RefObject<HTMLDivElement | null>;
private progressRef: React.RefObject<HTMLDivElement|null>; private progressRef: React.RefObject<HTMLDivElement | null>;
private dbVuRef: React.RefObject<HTMLDivElement|null>; private dbVuRef: React.RefObject<HTMLDivElement | null>;
private dbVuTelltaleRef: React.RefObject<HTMLDivElement|null>; private dbVuTelltaleRef: React.RefObject<HTMLDivElement | null>;
private dbVuTextRef: React.RefObject<HTMLDivElement|null>; private dbVuTextRef: React.RefObject<HTMLDivElement | null>;
private lampRef: React.RefObject<HTMLDivElement|null>; private lampRef: React.RefObject<HTMLDivElement | null>;
constructor(props: PluginOutputControlProps) { constructor(props: PluginOutputControlProps) {
@@ -210,7 +209,7 @@ const PluginOutputControl =
} }
} }
private PROGRESS_WIDTH = 40-4; private PROGRESS_WIDTH = 40 - 4;
private VU_HEIGHT = 60 - 4; private VU_HEIGHT = 60 - 4;
private DB_VU_HEIGHT = 60 - 4; private DB_VU_HEIGHT = 60 - 4;
private animationHandle: number | undefined = undefined; private animationHandle: number | undefined = undefined;
@@ -240,26 +239,23 @@ const PluginOutputControl =
} }
if (this.dbVuTextRef.current) { if (this.dbVuTextRef.current) {
let text: string; let text: string;
if (this.dbVuTelltale <= this.props.uiControl.min_value) if (this.dbVuTelltale <= this.props.uiControl.min_value) {
{
text = "-"; text = "-";
} else { } else {
if (Math.abs(this.dbVuTelltale) >= 99.5) if (Math.abs(this.dbVuTelltale) >= 99.5) {
{
text = Math.round(this.dbVuTelltale).toString(); text = Math.round(this.dbVuTelltale).toString();
} else { } else {
text = this.dbVuTelltale.toFixed(1); text = this.dbVuTelltale.toFixed(1);
} }
if (!text.startsWith("-")) { if (!text.startsWith("-")) {
text = '+'+text; text = '+' + text;
} }
} }
if (this.lastDbText !== text) if (this.lastDbText !== text) {
{
this.lastDbText = text; this.lastDbText = text;
this.dbVuTextRef.current.innerText = text; this.dbVuTextRef.current.innerText = text;
} }
} }
this.animationHandle = undefined; this.animationHandle = undefined;
this.updateDbVuTelltale(); this.updateDbVuTelltale();
} }
@@ -322,7 +318,7 @@ const PluginOutputControl =
) )
} }
} }
else if (this.vuRef.current) { else if (this.vuRef.current) {
let control = this.props.uiControl; let control = this.props.uiControl;
let range = (value - control.min_value) / (control.max_value - control.min_value); let range = (value - control.min_value) / (control.max_value - control.min_value);
@@ -389,8 +385,7 @@ const PluginOutputControl =
if (value > control.max_value) { if (value > control.max_value) {
value = control.max_value; value = control.max_value;
} }
if (value < control.min_value) if (value < control.min_value) {
{
value = control.min_value; value = control.min_value;
} }
let y = (control.max_value - value) * this.DB_VU_HEIGHT / (control.max_value - control.min_value); let y = (control.max_value - value) * this.DB_VU_HEIGHT / (control.max_value - control.min_value);
@@ -439,13 +434,13 @@ const PluginOutputControl =
} else if (control.isProgress()) { } else if (control.isProgress()) {
item_width = undefined; item_width = undefined;
return ( return (
<div className={classes.controlFrame} <div className={classes.controlFrame}
style={{ width: item_width }}> style={{ width: item_width }}>
{/* TITLE SECTION */} {/* TITLE SECTION */}
<div className={classes.titleSection} <div className={classes.titleSection}
style={{ width: "100%" }}> style={{ width: "100%" }}>
<ControlTooltip uiControl={control}> <ControlTooltip uiControl={control}>
<Typography variant="caption" display="block" style={{ <Typography variant="caption" display="block" style={{
width: "100%", width: "100%",
textAlign: "center" textAlign: "center"
}}> {control.name === "" ? "\u00A0" : control.name}</Typography> }}> {control.name === "" ? "\u00A0" : control.name}</Typography>
@@ -453,15 +448,18 @@ const PluginOutputControl =
</div> </div>
{/* CONTROL SECTION */} {/* CONTROL SECTION */}
<div className={classes.midSection} <div className={classes.midSection}
style={{ flex: "1 1 1", display: "flex", justifyContent: "center", alignItems: "start", flexFlow: "row nowrap" }}> style={{ flex: "1 1 1", display: "flex", justifyContent: "center", alignItems: "start", flexFlow: "row nowrap" }}>
<div style={{ width: this.PROGRESS_WIDTH+2, height: 12, marginLeft: 8, marginRight: 8, background: "#181818", }}> <div style={{ width: this.PROGRESS_WIDTH + 2, height: 12, marginLeft: 8, marginRight: 8, background: "#181818", }}>
<div style={{ height: 8, width: this.PROGRESS_WIDTH, overflow: "hidden", position: "absolute", <div style={{
margin: "1px 1px 1px 1px", background: "#282828" }}> height: 8, width: this.PROGRESS_WIDTH, overflow: "hidden", position: "absolute",
<div ref={this.progressRef} style={{ height: 10, width: this.PROGRESS_WIDTH, position: "absolute", marginTop: 0, background: "#0C0" }} /> margin: "1px 1px 1px 1px", background: "#282828"
<div style={{ height: 10, width: this.PROGRESS_WIDTH, position: "absolute", }}>
<div ref={this.progressRef} style={{ height: 10, width: this.PROGRESS_WIDTH, position: "absolute", marginTop: 0, background: "#0C0" }} />
<div style={{
height: 10, width: this.PROGRESS_WIDTH, position: "absolute",
boxShadow: "inset 0px 2px 4px #000D", background: "transparent" boxShadow: "inset 0px 2px 4px #000D", background: "transparent"
}} /> }} />
</div> </div>
</div> </div>
@@ -478,45 +476,48 @@ const PluginOutputControl =
let vuColors = defaultVuColors; let vuColors = defaultVuColors;
return ( return (
<div className={classes.controlFrame} <div className={classes.controlFrame}
style={{ width: item_width }}> style={{ width: item_width }}>
{/* TITLE SECTION */} {/* TITLE SECTION */}
<div className={classes.titleSection} <div className={classes.titleSection}
style={{ width: "100%" }}> style={{ width: "100%" }}>
<ControlTooltip uiControl={control}> <Typography variant="caption" display="block" style={{
<Typography variant="caption" display="block" style={{ width: "100%",
width: "100%", textAlign: "center"
textAlign: "center" }}> {control.name === "" ? "\u00A0" : control.name}</Typography>
}}> {control.name === "" ? "\u00A0" : control.name}</Typography>
</ControlTooltip>
</div> </div>
{/* CONTROL SECTION */} {/* CONTROL SECTION */}
<div className={classes.midSection} <div className={classes.midSection}
style={{ flex: "1 1 1", display: "flex", justifyContent: "center", alignItems: "start", flexFlow: "row nowrap" }}> style={{ flex: "1 1 1", display: "flex", justifyContent: "center", alignItems: "start", flexFlow: "row nowrap" }}>
<div style={{ width: 8, height: this.DB_VU_HEIGHT + 4, background: "#000", }}> <ControlTooltip uiControl={control}>
<div style={{ height: this.DB_VU_HEIGHT, width: 4, overflow: "hidden", position: "absolute", margin: 2 }}>
<div style={{ width: 4, height: this.DB_VU_HEIGHT, position: "absolute", left: 0, top: 0 }}>
{
vuColors.map((vuColor,ix)=>{
let top = Math.floor(this.dbVuMap(vuColor.maxDb));
let bottom = Math.ceil(this.dbVuMap(vuColor.minDb))+1;
return ( <div style={{ width: 8, height: this.DB_VU_HEIGHT + 4, background: "#000", }}>
<div key={ix} <div style={{ height: this.DB_VU_HEIGHT, width: 4, overflow: "hidden", position: "absolute", margin: 2 }}>
style={{ position: "absolute", width: 4, height: (bottom-top), <div style={{ width: 4, height: this.DB_VU_HEIGHT, position: "absolute", left: 0, top: 0 }}>
top: top, left:0, background: vuColor.color }} /> {
); vuColors.map((vuColor, ix) => {
}) let top = Math.floor(this.dbVuMap(vuColor.maxDb));
} let bottom = Math.ceil(this.dbVuMap(vuColor.minDb)) + 1;
return (
<div key={ix}
style={{
position: "absolute", width: 4, height: (bottom - top),
top: top, left: 0, background: vuColor.color
}} />
);
})
}
</div>
<div ref={this.dbVuRef} style={{
width: 4, position: "absolute", marginTop: 0, height: this.VU_HEIGHT, background: "#000"
}} />
<div ref={this.dbVuTelltaleRef} style={{ width: 4, position: "absolute", marginTop: 100, height: 3, background: "#C00" }} />
</div> </div>
<div ref={this.dbVuRef} style={{
width: 4, position: "absolute", marginTop: 0, height: this.VU_HEIGHT, background: "#000"
}} />
<div ref={this.dbVuTelltaleRef} style={{ width: 4, position: "absolute", marginTop: 100, height: 3, background: "#C00" }} />
</div> </div>
</div> </ControlTooltip>
</div> </div>
<div className={classes.editSection}> <div className={classes.editSection}>
@@ -533,26 +534,28 @@ const PluginOutputControl =
else if (control.isVu()) { else if (control.isVu()) {
item_width = undefined; item_width = undefined;
return ( return (
<div className={classes.controlFrame} style={{ width: item_width}}> <div className={classes.controlFrame} style={{ width: item_width }}>
{/* TITLE SECTION */} {/* TITLE SECTION */}
<div className={classes.titleSection} <div className={classes.titleSection}
style={{ width: "100%" }}> style={{ width: "100%" }}>
<ControlTooltip uiControl={control}>
<Typography noWrap display="block" variant="caption" style={{ <Typography noWrap display="block" variant="caption" style={{
width: item_width, width: item_width,
textAlign: "center" textAlign: "center"
}}> {control.name === "" ? "\u00A0" : control.name}</Typography> }}> {control.name === "" ? "\u00A0" : control.name}</Typography>
</ControlTooltip>
</div> </div>
{/* CONTROL SECTION */} {/* CONTROL SECTION */}
<div className={classes.midSection} <div className={classes.midSection}
style={{ display: "flex", justifyContent: "center", alignItems: "start", flexFlow: "row nowrap" }}> style={{ display: "flex", justifyContent: "center", alignItems: "start", flexFlow: "row nowrap" }}>
<div style={{ width: 8, height: this.VU_HEIGHT + 4, background: "#000", }}> <ControlTooltip uiControl={control}>
<div style={{ height: this.VU_HEIGHT, overflow: "hidden", position: "absolute", margin: 2 }}> <div style={{ width: 8, height: this.VU_HEIGHT + 4, background: "#000", }}>
<div ref={this.vuRef} style={{ width: 4, height: this.VU_HEIGHT, background: "#0C0", }} /> <div style={{ height: this.VU_HEIGHT, overflow: "hidden", position: "absolute", margin: 2 }}>
<div ref={this.vuRef} style={{ width: 4, height: this.VU_HEIGHT, background: "#0C0", }} />
</div>
</div> </div>
</div> </ControlTooltip>
</div> </div>
@@ -575,11 +578,11 @@ const PluginOutputControl =
} }
return ( return (
<div className={classes.controlFrame} <div className={classes.controlFrame}
style={{width: attachedLamp? 15: item_width}} style={{ width: attachedLamp ? 15 : item_width }}
> >
{/* TITLE SECTION */} {/* TITLE SECTION */}
<div className={classes.titleSection} <div className={classes.titleSection}
style={{ flex: "0 0 auto", width: "100%"}}> style={{ flex: "0 0 auto", width: "100%" }}>
<ControlTooltip uiControl={control}> <ControlTooltip uiControl={control}>
<Typography noWrap variant="caption" display="block" style={{ <Typography noWrap variant="caption" display="block" style={{
width: "100%", width: "100%",
@@ -589,7 +592,7 @@ const PluginOutputControl =
</div> </div>
{/* CONTROL SECTION */} {/* CONTROL SECTION */}
<div className={classes.midSection} <div className={classes.midSection}
style={{ style={{
display: "flex", justifyContent: "center", alignItems: "center", flexFlow: "row nowrap", display: "flex", justifyContent: "center", alignItems: "center", flexFlow: "row nowrap",
}}> }}>
@@ -613,23 +616,23 @@ const PluginOutputControl =
); );
} if (control.isOutputText()) { } if (control.isOutputText()) {
return ( return (
<div className={classes.controlFrame} <div className={classes.controlFrame}
style={{ display: "flex", flexDirection: "column", width: item_width }}> style={{ display: "flex", flexDirection: "column", width: item_width }}>
{/* TITLE SECTION */} {/* TITLE SECTION */}
<div className={classes.titleSection} <div className={classes.titleSection}
style={{ flex: "0 0 auto", width: "100%" }}> style={{ flex: "0 0 auto", width: "100%" }}>
<ControlTooltip uiControl={control}> <ControlTooltip uiControl={control}>
<Typography noWrap variant="caption" display="block" style={{ <Typography noWrap variant="caption" display="block" style={{
width: "100%", width: "100%",
textAlign: "left" textAlign: "left"
}}> {control.name === "" ? "\u00A0": control.name}</Typography> }}> {control.name === "" ? "\u00A0" : control.name}</Typography>
</ControlTooltip> </ControlTooltip>
</div> </div>
{/* CONTROL SECTION */} {/* CONTROL SECTION */}
<div className={classes.midSection} style={{ <div className={classes.midSection} style={{
display: "flex", justifyContent: "center", display: "flex", justifyContent: "center",
flexFlow: "row nowrap", paddingTop: 6 flexFlow: "row nowrap", paddingTop: 6
}}> }}>
@@ -646,10 +649,10 @@ const PluginOutputControl =
} else { } else {
return ( return (
<div className={classes.controlFrame} <div className={classes.controlFrame}
style={{ display: "flex", flexDirection: "column", width: item_width }}> style={{ display: "flex", flexDirection: "column", width: item_width }}>
{/* TITLE SECTION */} {/* TITLE SECTION */}
<div className={classes.titleSection} <div className={classes.titleSection}
style={{ flex: "0 0 auto", width: "100%" }}> style={{ flex: "0 0 auto", width: "100%" }}>
<ControlTooltip uiControl={control}> <ControlTooltip uiControl={control}>
<Typography noWrap variant="caption" display="block" style={{ <Typography noWrap variant="caption" display="block" style={{
@@ -660,7 +663,7 @@ const PluginOutputControl =
</div> </div>
{/* CONTROL SECTION */} {/* CONTROL SECTION */}
<div className={classes.midSection} <div className={classes.midSection}
style={{ display: "flex", justifyContent: "start", alignItems: "center", flexFlow: "row nowrap" }}> style={{ display: "flex", justifyContent: "start", alignItems: "center", flexFlow: "row nowrap" }}>
<Typography noWrap variant="caption" display="block" style={{ width: "100%" }}> <Typography noWrap variant="caption" display="block" style={{ width: "100%" }}>
{text} {text}
+223
View File
@@ -0,0 +1,223 @@
/*
* MIT License
*
* Copyright (c) Robin E. R. Davies
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
* of the Software, and to permit persons to whom the Software is furnished to do
* so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
import { Component } from 'react';
import { Theme } from '@mui/material/styles';
import { css } from '@emotion/react';
import DialogEx from './DialogEx';
import DialogTitle from '@mui/material/DialogTitle';
import ArrowBackIcon from '@mui/icons-material/ArrowBack';
import Toolbar from '@mui/material/Toolbar';
import DialogContent from '@mui/material/DialogContent';
import IconButtonEx from './IconButtonEx';
import Typography from '@mui/material/Typography';
import { PiPedalModel, PiPedalModelFactory, State } from './PiPedalModel';
import Select from '@mui/material/Select';
import MenuItem from '@mui/material/MenuItem';
export const StandardItemSize = { width: 80, height: 140 }
import { withStyles } from "tss-react/mui";
import WithStyles from './WithStyles';
import { withTheme } from './WithStyles';
const styles = (theme: Theme) => {
return {
frame: css({
position: "relative",
margin: "12px"
}),
controlFrame: css({
display: "flex", flexDirection: "column", alignItems: "start", justifyContent: "space-between", height: 116
}),
displayValue: css({
position: "absolute",
top: 0,
left: 0,
right: 0,
bottom: 4,
textAlign: "center",
background: theme.mainBackground,
color: theme.palette.text.secondary,
// zIndex: -1,
}),
titleSection: css({
flex: "0 0 auto", alignSelf: "stretch", marginBottom: 8, marginLeft: 8, marginRight: 0
}),
midSection: css({
flex: "1 1 1", display: "flex", flexFlow: "column nowrap", alignContent: "center", justifyContent: "center"
}),
editSection: css({
flex: "0 0 0", position: "relative", width: 60, height: 28, minHeight: 28
})
}
}
;
export interface SideChainSelectControlProps extends WithStyles<typeof styles> {
title: string;
selectItems: { instanceId: number, title: string }[];
selectedInstanceId: number;
onChanged(instanceId: number): void;
theme: Theme;
}
type SideChainSelectControlState = {
};
const SideChainSelectControl =
withTheme(withStyles(
class extends Component<SideChainSelectControlProps, SideChainSelectControlState> {
model: PiPedalModel;
constructor(props: SideChainSelectControlProps) {
super(props);
this.state = {
};
this.model = PiPedalModelFactory.getInstance();
this.onStateChanged = this.onStateChanged.bind(this);
}
onStateChanged(state: State) {
}
mounted: boolean = false;
componentDidMount() {
this.model.state.addOnChangedHandler(this.onStateChanged);
this.mounted = true;
}
componentWillUnmount() {
this.mounted = false;
this.model.state.removeOnChangedHandler(this.onStateChanged);
}
componentDidUpdate(prevProps: Readonly<SideChainSelectControlProps>, prevState: Readonly<SideChainSelectControlState>, snapshot?: any): void {
}
render() {
//const classes = withStyles.getClasses(this.props);
const classes = withStyles.getClasses(this.props);
return (
<div className={classes.controlFrame}
style={{ width: 120 }}>
{/* TITLE SECTION */}
<div className={classes.titleSection}
>
<Typography variant="caption" display="block" noWrap style={{
width: "100%",
textAlign: "start"
}}> {this.props.title}</Typography>
</div>
{/* CONTROL SECTION */}
<div className={classes.midSection} style={{ width: "100%", paddingLeft: 8 }}>
<Select
variant="standard"
onChange={(event) => {
this.props.onChanged(event.target.value as number);
}}
value={this.props.selectedInstanceId}
style={{ width: "100%", fontSize: "0.8rem" }}
>
{this.props.selectItems.map((item) => {
return (
<MenuItem key={item.instanceId} value={item.instanceId}
selected={item.instanceId === this.props.selectedInstanceId}>
{item.title}
</MenuItem>
);
})}
</Select>
</div>
{/* LABEL/EDIT SECTION*/}
<div className={classes.editSection} >
</div>
</div >
);
}
}
, styles)
)
;
export default SideChainSelectControl;
export interface SideChainSelectDialogProps {
open: boolean,
onClose: () => void,
selectItems: { instanceId: number, title: string }[];
selectedInstanceId: number;
onChanged(instanceId: number): void;
}
export function SideChainSelectDialog(props: SideChainSelectDialogProps) {
let { open, onClose, selectItems, selectedInstanceId, onChanged } = props;
return (
<DialogEx tag="nameDialog" open={open} fullWidth maxWidth="xs" onClose={onClose} aria-labelledby="Rename-dialog-title"
style={{ userSelect: "none" }}
onEnterKey={() => { }}
>
<DialogTitle id="alert-dialog-title" style={{ userSelect: "none", padding: 0, margin: 0 }}>
<Toolbar style={{ paddingLeft: 24, paddingRight: 24, margin: 0 }} >
<IconButtonEx
tooltip="Back" edge="start" color="inherit" onClick={() => { onClose(); }} aria-label="back"
>
<ArrowBackIcon style={{opacity: 0.6}} />
</IconButtonEx>
<Typography noWrap variant="body1" style={{ flex: "1 1 auto" }}>Sidechain Input</Typography>
</Toolbar>
</DialogTitle>
<DialogContent >
<Select
variant="standard"
onChange={(event) => {
onChanged(event.target.value as number);
}}
value={selectedInstanceId}
style={{ width: "100%" }}
>
{selectItems.map((item) => {
return (
<MenuItem key={item.instanceId} value={item.instanceId}
selected={item.instanceId === selectedInstanceId}>
{item.title}
</MenuItem>
);
})}
</Select>
</DialogContent>
</DialogEx>
);
}
+10 -5
View File
@@ -24,7 +24,7 @@ import CloseIcon from '@mui/icons-material/Close';
import AppBar from '@mui/material/AppBar'; import AppBar from '@mui/material/AppBar';
import Toolbar from '@mui/material/Toolbar'; import Toolbar from '@mui/material/Toolbar';
import CssBaseline from '@mui/material/CssBaseline'; import CssBaseline from '@mui/material/CssBaseline';
import {createStyles} from './WithStyles'; import { createStyles } from './WithStyles';
import { withStyles } from "tss-react/mui"; import { withStyles } from "tss-react/mui";
import IconButtonEx from './IconButtonEx'; import IconButtonEx from './IconButtonEx';
@@ -148,10 +148,12 @@ const SnapshotEditor = withStyles(
this.model_.zoomedUiControl.addOnChangedHandler( this.model_.zoomedUiControl.addOnChangedHandler(
() => { () => {
this.setState({ if (this.mounted93) {
zoomedControlOpen: this.model_.zoomedUiControl.get() !== undefined, this.setState({
zoomedControlInfo: this.model_.zoomedUiControl.get() zoomedControlOpen: this.model_.zoomedUiControl.get() !== undefined,
}); zoomedControlInfo: this.model_.zoomedUiControl.get()
});
}
} }
); );
@@ -182,9 +184,11 @@ const SnapshotEditor = withStyles(
this.setState({ isFullScreen: !this.state.isFullScreen }); this.setState({ isFullScreen: !this.state.isFullScreen });
} }
private mounted93: boolean = false;
componentDidMount() { componentDidMount() {
super.componentDidMount(); super.componentDidMount();
this.mounted93 = true;
this.model_.showStatusMonitor.addOnChangedHandler(this.showStatusMonitorHandler); this.model_.showStatusMonitor.addOnChangedHandler(this.showStatusMonitorHandler);
} }
@@ -206,6 +210,7 @@ const SnapshotEditor = withStyles(
} }
componentWillUnmount() { componentWillUnmount() {
this.mounted93 = false;
super.componentWillUnmount(); super.componentWillUnmount();
this.model_.banks.removeOnChangedHandler(this.showStatusMonitorHandler); this.model_.banks.removeOnChangedHandler(this.showStatusMonitorHandler);
} }
@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="24"
height="24"
viewBox="0 0 6.35 6.35"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<path
id="path4452"
d="M 3.1501953 1.1797729 C 2.3438819 1.1900494 1.6500313 1.6826477 1.3451375 2.38125 L 1.786971 2.38125 C 2.0624356 1.9000342 2.5807156 1.5761312 3.1760335 1.5761312 C 4.0608593 1.5761312 4.7733521 2.2912075 4.7733521 3.1760335 C 4.7733521 4.0608594 4.0608593 4.7733521 3.1760335 4.7733521 C 2.580068 4.7733521 2.0611725 4.450254 1.7859375 3.96875 L 1.3446208 3.96875 C 1.6524938 4.6748172 2.3583381 5.1697103 3.1760335 5.1697103 C 4.2753461 5.1697103 5.1697103 4.2753461 5.1697103 3.1760335 C 5.1697103 2.0767208 4.2753461 1.1797729 3.1760335 1.1797729 C 3.1674451 1.1797729 3.1587586 1.1796638 3.1501953 1.1797729 z " />
<path
id="ellipse4674"
d="M 3.1760335 2.3300903 C 2.7944551 2.3300903 2.4622893 2.5720679 2.3455933 2.9098999 L 0.64078776 2.9098999 L 0.64078776 3.4395833 L 2.3450765 3.4395833 C 2.4612025 3.7778767 2.7937654 4.019393 3.1760335 4.0193929 C 3.651938 4.0193929 4.0488484 3.6453545 4.0488485 3.1760335 C 4.0488485 2.7067124 3.6519381 2.3300904 3.1760335 2.3300903 z M 3.1760335 2.7264486 C 3.4466224 2.7264486 3.6524902 2.9307799 3.6524902 3.1760335 C 3.6524901 3.4212871 3.4466224 3.6230347 3.1760335 3.6230347 C 2.9054446 3.6230347 2.6975099 3.4212871 2.6975098 3.1760335 C 2.6975098 2.9307799 2.9054446 2.7264486 3.1760335 2.7264486 z " />
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB