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)
project(pipedal
VERSION 1.5.93
VERSION 1.5.94
DESCRIPTION "PiPedal Guitar Effect Pedal For Raspberry Pi"
HOMEPAGE_URL "https://rerdavies.github.io/pipedal"
)
execute_process( COMMAND dpkg --print-architecture COMMAND tr -d '\n' OUTPUT_VARIABLE DEBIAN_ARCHITECTURE )
set (DISPLAY_VERSION "PiPedal v1.5.93-Experimental")
set (DISPLAY_VERSION "PiPedal v1.5.94-Experimental")
set (PACKAGE_ARCHITECTURE ${DEBIAN_ARCHITECTURE})
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"/>
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).
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;
+5 -5
View File
@@ -13,18 +13,18 @@ page_icon: img/Install4.jpg
Download the most recent Debian (.deb) package for your platform:
- [Raspberry Pi OS bookworm (aarch64) v1.5.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.93](https://github.com/rerdavies/pipedal/releases/download/v1.5.93/pipedal_1.5.93_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)
- [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.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.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 upgrade
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.
+11
View File
@@ -1,5 +1,16 @@
# 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
### 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 |
|---------------------------------|---------------------------------------|------------------|
| [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.|
| [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 |
+4 -4
View File
@@ -4,9 +4,9 @@
Download the most recent Debian (.deb) package for your platform:
- [Raspberry Pi OS bookworm (aarch64) v1.5.93 Beta](https://github.com/rerdavies/pipedal/releases/download/v1.5.93/pipedal_1.5.93_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 (amd64) v1.5.93 Alpha](https://github.com/rerdavies/pipedal/releases/download/v1.5.93/pipedal_1.5.93_amd64.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.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.94 Alpha](https://github.com/rerdavies/pipedal/releases/download/v1.5.94/pipedal_1.5.94_amd64.deb)
Install the package by running
@@ -14,7 +14,7 @@ Install the package by running
```
sudo apt update
cd ~/Downloads
sudo apt-get install ./pipedal_1.5.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
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 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 *GetAudioSidechainBuffer(int index) const { throw std::runtime_error("Not implemented"); }
virtual float *GetAudioOutputBuffer(int index) const = 0;
virtual void ResetAtomBuffers() = 0;
@@ -65,6 +67,7 @@ namespace pipedal {
virtual void SetAudioInputBuffer(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;
+78 -5
View File
@@ -384,7 +384,12 @@ void Lv2Effect::PreparePortIndices()
{
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
{
@@ -432,6 +437,7 @@ void Lv2Effect::PreparePortIndices()
this->maxInputControlPort = maxInputControlPort;
inputAudioBuffers.resize(inputAudioPortIndices.size());
inputSidechainBuffers.resize(inputSidechainPortIndices.size());
outputAudioBuffers.resize(outputAudioPortIndices.size());
inputAtomBuffers.resize(inputAtomPortIndices.size());
outputAtomBuffers.resize(outputAtomPortIndices.size());
@@ -439,9 +445,7 @@ void Lv2Effect::PreparePortIndices()
if (RequiresBufferStaging())
{
EnableBufferStaging(
GetStagedBufferSize(),
this->GetNumberOfInputAudioBuffers(),
this->GetNumberOfOutputAudioBuffers());
GetStagedBufferSize());
}
}
@@ -473,6 +477,7 @@ void Lv2Effect::PrepareNoInputEffect(int numberOfInputs, size_t maxBufferSize)
}
}
void Lv2Effect::SetAudioInputBuffer(int index, float *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)
{
@@ -664,6 +695,14 @@ void Lv2Effect::UpdateAudioPorts()
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)
{
if (i == 0)
@@ -710,6 +749,15 @@ void Lv2Effect::UpdateAudioPorts()
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)
{
auto atomInputBuffer = this->GetAtomInputBuffer(i);
@@ -877,6 +925,16 @@ size_t Lv2Effect::stageToInput(size_t inputSampleOffset, size_t samples)
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;
inputSampleOffset += thisTime;
@@ -1487,8 +1545,11 @@ void Lv2Effect::SetPathPatchProperty(const std::string &propertyUri, const std::
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;
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
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)
{
outputStagingBuffers.at(i).resize(bufferSize + 1);
@@ -1571,6 +1638,12 @@ float *Lv2Effect::GetAudioInputBuffer(int index) const
throw std::range_error("Lv2Effect::GetAudioInputBuffer");
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
{
+20 -11
View File
@@ -81,6 +81,8 @@ namespace pipedal
std::vector<int> inputAudioPortIndices;
std::vector<int> outputAudioPortIndices;
std::vector<int> inputSidechainPortIndices;
std::vector<int> inputAtomPortIndices;
std::vector<int> outputAtomPortIndices;
@@ -90,6 +92,7 @@ namespace pipedal
std::vector<int> midiInputIndices;
std::vector<float *> inputAudioBuffers;
std::vector<float *> inputSidechainBuffers;
std::vector<float *> outputAudioBuffers;
std::vector<char *> inputAtomBuffers;
@@ -212,7 +215,7 @@ namespace pipedal
bool borrowedEffect = false;
bool activated = false;
void EnableBufferStaging(size_t bufferSize, size_t nInputs, size_t nOutputs);
void EnableBufferStaging(size_t bufferSize);
void CheckStagingBufferSentries();
public:
@@ -262,8 +265,10 @@ namespace pipedal
size_t stagingInputIx = 0;
size_t stagingOutputIx = 0;
std::vector<std::vector<float>> inputStagingBuffers;
std::vector<std::vector<float>> sidechainStagingBuffers;
std::vector<std::vector<float>> outputStagingBuffers;
std::vector<float*> inputStagingBufferPointers;
std::vector<float*> sidechainStagingBufferPointers;
std::vector<float*> outputStagingBufferPointers;
std::vector<uint8_t> stagedInputAtomBuffer;
@@ -297,22 +302,26 @@ namespace pipedal
virtual void ResetAtomBuffers();
virtual uint64_t GetInstanceId() const { return instanceId; }
virtual int GetNumberOfInputAudioPorts() const { return inputAudioPortIndices.size(); }
virtual int GetNumberOfOutputAudioPorts() const { return outputAudioPortIndices.size(); }
virtual int GetNumberOfInputAudioPorts() const override { return inputAudioPortIndices.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 GetNumberOfMidiInputPorts() const { return inputMidiPortIndices.size(); }
virtual int GetNumberOfMidiOutputPorts() const { return outputMidiPortIndices.size(); }
virtual int GetNumberOfOutputAtomPorts() const { return outputAtomPortIndices.size(); }
virtual int GetNumberOfMidiInputPorts() const { return inputMidiPortIndices.size(); }
virtual int GetNumberOfMidiOutputPorts() const { return outputMidiPortIndices.size(); }
virtual int GetNumberOfInputAudioBuffers() const { return this->inputAudioBuffers.size(); }
virtual int GetNumberOfOutputAudioBuffers() const {return this->outputAudioBuffers.size(); }
virtual int GetNumberOfInputAudioBuffers() const override { return this->inputAudioBuffers.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 float *GetAudioInputBuffer(int index) const;
virtual void SetAudioInputBuffer(int index, float *buffer) override;
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);
+54 -1
View File
@@ -176,6 +176,60 @@ std::vector<float *> Lv2Pedalboard::PrepareItems(
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.
bool requiresBufferStaging = false;
@@ -194,7 +248,6 @@ std::vector<float *> Lv2Pedalboard::PrepareItems(
}
}
if (!requiresBufferStaging)
{
this->processActions.push_back(
+1
View File
@@ -57,6 +57,7 @@ namespace pipedal
BufferPool bufferPool;
std::vector<float *> pedalboardInputBuffers;
std::vector<float *> pedalboardOutputBuffers;
float *pedalboardSidechainBuffer = nullptr;
std::vector<std::shared_ptr<IEffect>> effects;
std::vector<IEffect *> realtimeEffects;
+1
View File
@@ -534,6 +534,7 @@ JSON_MAP_BEGIN(PedalboardItem)
JSON_MAP_REFERENCE(PedalboardItem,title)
JSON_MAP_REFERENCE(PedalboardItem,useModUi)
JSON_MAP_REFERENCE(PedalboardItem,iconColor)
JSON_MAP_REFERENCE(PedalboardItem,sideChainInputId)
JSON_MAP_END()
+2
View File
@@ -107,6 +107,7 @@ public:
std::string title_;
bool useModUi_ = false;
std::string iconColor_;
int64_t sideChainInputId_ = -1;
// non persistent state.
PropertyMap patchProperties;
@@ -138,6 +139,7 @@ public:
GETTER_SETTER_REF(title)
GETTER_SETTER_REF(iconColor)
GETTER_SETTER(useModUi)
GETTER_SETTER(sideChainInputId)
Lv2PluginState&lv2State() { return lv2State_; } // non-const version.
GETTER_SETTER_REF(lilvPresetUri)
+1 -1
View File
@@ -2428,7 +2428,7 @@ void PiPedalModel::OnNotifyPathPatchPropertyReceived(
auto i = pedalboardItem->pathProperties_.find(pathPatchPropertyUri);
if (i != pedalboardItem->pathProperties_.end())
{
std::string abstractAtomString = storage.ToAbstractPathJson(atomString);
std::string abstractAtomString = storage.ToAbstractPathFromJson(atomString);
pedalboardItem->pathProperties_[pathPatchPropertyUri] = abstractAtomString;
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);
core__toggled = lilv_new_uri(pWorld, LV2_CORE__toggled);
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__trigger = lilv_new_uri(pWorld, LV2_PORT_PROPS__trigger);
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__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);
state__state = lilv_new_uri(pWorld, LV2_STATE__state);
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__fixedBlockLength = lilv_new_uri(pWorld, LV2_BUF_SIZE__fixedBlockLength);
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
{
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);
// 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.
{
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 nSideChains = 0;
for (size_t i = 0; i < ports_.size(); ++i)
{
auto port = ports_[i];
if (port->is_audio_port() && port->is_input())
{
if (nInputs >= 2 && !port->connection_optional())
{
isValid = false;
break;
if (!port->is_sidechain()) {
if (nInputs >= 2)
{
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;
@@ -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->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->is_sidechain_ = lilv_port_has_property(plugin,pPort,host->lilvUris->core__isSideChain);
AutoLilvNode port_ledColor = lilv_port_get(plugin, pPort, host->lilvUris->pipedalUI__ledColor);
if (port_ledColor)
@@ -1402,7 +1476,12 @@ Lv2PluginUiInfo::Lv2PluginUiInfo(PluginHost *pHost, const Lv2PluginInfo *plugin)
}
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())
@@ -1918,12 +1997,29 @@ Lv2PortGroup::Lv2PortGroup(PluginHost *lv2Host, const std::string &groupUri)
{
LilvWorld *pWorld = lv2Host->pWorld;
// Enumerate all Lilv nodes for groupUri that have an 'a' predicate
this->uri_ = groupUri;
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);
symbol_ = nodeAsString(symbolNode);
AutoLilvNode nameNode = lilv_world_get(pWorld, uri, lv2Host->lilvUris->lv2core__name, nullptr);
name_ = nodeAsString(nameNode);
AutoLilvNode sideChainOfNode = lilv_world_get(pWorld, uri, lv2Host->lilvUris->port_groups__sideChainOf, nullptr);
sideChainOf_ = nodeAsString(sideChainOfNode);
}
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_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_audio_port", &Lv2PortInfo::is_audio_port_),
@@ -2226,7 +2323,11 @@ json_map::storage_type<Lv2PortInfo> Lv2PortInfo::jmap{
json_map::storage_type<Lv2PortGroup> Lv2PortGroup::jmap{{
MAP_REF(Lv2PortGroup, isA),
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_),
MAP_REF(Lv2PluginUiPort, index),
MAP_REF(Lv2PluginUiPort, is_input),
MAP_REF(Lv2PluginUiPort, is_sidechain),
MAP_REF(Lv2PluginUiPort, min_value),
MAP_REF(Lv2PluginUiPort, max_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_homepage", &Lv2PluginUiInfo::author_homepage_),
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_side_chain_title", &Lv2PluginUiInfo::audio_side_chain_title_),
json_map::reference("description", &Lv2PluginUiInfo::description_),
json_map::reference("has_midi_input", &Lv2PluginUiInfo::has_midi_input_),
json_map::reference("has_midi_output", &Lv2PluginUiInfo::has_midi_output_),
+61 -49
View File
@@ -33,7 +33,7 @@
#include <set>
#include "ModGui.hpp"
//#include "lv2.h"
// #include "lv2.h"
#include "Units.hpp"
#include "PluginPreset.hpp"
@@ -206,6 +206,7 @@ namespace pipedal
bool is_control_port_ = false;
bool is_audio_port_ = false;
bool is_sidechain_ = false;
bool is_atom_port_ = false;
bool is_cv_port_ = false;
bool connection_optional_ = false;
@@ -287,6 +288,7 @@ namespace pipedal
LV2_PROPERTY_GETSET_SCALAR(is_output);
LV2_PROPERTY_GETSET_SCALAR(is_control_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(connection_optional);
LV2_PROPERTY_GETSET_SCALAR(is_cv_port);
@@ -321,7 +323,6 @@ namespace pipedal
return Lv2BufferType::Unknown;
}
public:
Lv2PortInfo() {}
~Lv2PortInfo() = default;
@@ -330,7 +331,8 @@ namespace pipedal
static json_map::storage_type<Lv2PortInfo> jmap;
};
class Lv2PatchPropertyInfo {
class Lv2PatchPropertyInfo
{
private:
std::string uri_;
@@ -343,9 +345,10 @@ namespace pipedal
std::string shortName_;
std::vector<std::string> fileTypes_;
std::vector<std::string> supportedExtensions_;
public:
Lv2PatchPropertyInfo() {}
Lv2PatchPropertyInfo(PluginHost *pluginHost, const LilvNode *propertyUri);
Lv2PatchPropertyInfo(PluginHost *pluginHost, const LilvNode *propertyUri);
LV2_PROPERTY_GETSET(uri);
LV2_PROPERTY_GETSET(writable);
@@ -359,21 +362,24 @@ namespace pipedal
LV2_PROPERTY_GETSET(supportedExtensions);
DECLARE_JSON_MAP(Lv2PatchPropertyInfo);
};
class Lv2PortGroup
{
private:
std::vector<std::string> isA_;
std::string uri_;
std::string symbol_;
std::string name_;
std::string sideChainOf_;
public:
LV2_PROPERTY_GETSET(isA);
LV2_PROPERTY_GETSET(uri);
LV2_PROPERTY_GETSET(symbol);
LV2_PROPERTY_GETSET(name);
LV2_PROPERTY_GETSET(sideChainOf);
Lv2PortGroup() {}
Lv2PortGroup(PluginHost *lv2Host, const std::string &groupUri);
@@ -389,10 +395,11 @@ namespace pipedal
using ptr = std::shared_ptr<Lv2PluginInfo>;
Lv2PluginInfo(PluginHost *lv2Host, LilvWorld *pWorld, const LilvPlugin *);
Lv2PluginInfo() {}
bool isSplit() const ;
bool isSplit() const;
private:
struct FindWritablePathPropertiesResult {
struct FindWritablePathPropertiesResult
{
std::shared_ptr<PiPedalUI> pipedalUi;
bool hasUnsupportedPatchProperties = false;
};
@@ -406,7 +413,7 @@ namespace pipedal
std::string name_;
std::string plugin_class_;
std::string brand_;
std::string label_;
std::string label_;
std::vector<std::string> supported_features_;
std::vector<std::string> required_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<Lv2PortGroup>> port_groups_;
std::vector<Lv2PatchPropertyInfo> patchProperties_;
std::string audio_sidechain_title_;
bool hasDefaultState_;
bool is_valid_ = false;
PiPedalUI::ptr piPedalUI_;
ModGui::ptr modGui_;
@@ -463,6 +470,7 @@ namespace pipedal
LV2_PROPERTY_GETSET(minBlockLength)
LV2_PROPERTY_GETSET(maxBlockLength)
LV2_PROPERTY_GETSET(powerOf2BlockLength)
LV2_PROPERTY_GETSET(audio_sidechain_title)
bool WantsWorkerThread() const;
@@ -529,9 +537,11 @@ namespace pipedal
class Lv2PluginUiPortGroup
{
private:
std::vector<std::string> isA_;
std::string uri_;
std::string symbol_;
std::string name_;
std::string sideChainOf_;
std::string parent_group_;
int32_t program_list_id_ = -1; // used by VST3.
@@ -546,9 +556,7 @@ namespace pipedal
public:
Lv2PluginUiPortGroup() {}
Lv2PluginUiPortGroup(Lv2PortGroup *pPortGroup)
: symbol_(pPortGroup->symbol())
, name_(pPortGroup->name())
, uri_(pPortGroup->uri())
: isA_(pPortGroup->isA()), symbol_(pPortGroup->symbol()), name_(pPortGroup->name()), uri_(pPortGroup->uri()), sideChainOf_(pPortGroup->sideChainOf())
{
}
Lv2PluginUiPortGroup(
@@ -570,14 +578,18 @@ namespace pipedal
}
Lv2PluginUiPort(const Lv2PluginInfo *pPlugin, const Lv2PortInfo *pPort)
: 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()),
is_logarithmic_(pPort->is_logarithmic()),
integer_property_(pPort->integer_property()),
is_logarithmic_(pPort->is_logarithmic()),
integer_property_(pPort->integer_property()),
mod_momentaryOffByDefault_(pPort->mod_momentaryOffByDefault()),
mod_momentaryOnByDefault_(pPort->mod_momentaryOnByDefault()),
pipedal_graphicEq_(pPort->pipedal_graphicEq()),
enumeration_property_(pPort->enumeration_property()),
toggled_property_(pPort->toggled_property()), not_on_gui_(pPort->not_on_gui()), scale_points_(pPort->scale_points()),
trigger_property_(pPort->trigger_property()),
@@ -606,7 +618,7 @@ namespace pipedal
}
}
}
is_bypass_ =
is_bypass_ =
pPort->is_bypass() ||
name_ == "bypass" || name_ == "Bypass" || symbol_ == "bypass" || symbol_ == "Bypass";
}
@@ -616,6 +628,7 @@ namespace pipedal
int index_;
std::string name_;
bool is_input_ = true;
bool is_sidechain_ = false;
float min_value_ = 0, max_value_ = 1, default_value_ = 0;
bool is_logarithmic_ = false;
int display_priority_ = -1;
@@ -682,7 +695,8 @@ namespace pipedal
std::string uri_;
std::string name_;
uint32_t minorVersion_ = 0;
uint32_t microVersion_ = 0;;
uint32_t microVersion_ = 0;
;
std::string brand_;
std::string label_;
std::string author_name_;
@@ -690,12 +704,17 @@ namespace pipedal
PluginType plugin_type_;
std::string plugin_display_type_;
int audio_inputs_ = 0;
int audio_side_chain_inputs_ = 0;
int audio_outputs_ = 0;
std::string audio_side_chain_title_;
int has_midi_input_ = false;
int has_midi_output_ = false;
std::string description_;
bool is_vst3_ = false;
int64_t side_chain_input_id = -1;
std::vector<Lv2PluginUiPort> controls_;
std::vector<Lv2PluginUiPortGroup> port_groups_;
std::vector<UiFileProperty::ptr> fileProperties_;
@@ -704,7 +723,6 @@ namespace pipedal
ModGui::ptr modGui_;
std::vector<Lv2PatchPropertyInfo> patchProperties_;
public:
LV2_PROPERTY_GETSET(uri)
LV2_PROPERTY_GETSET(name)
@@ -718,6 +736,7 @@ namespace pipedal
LV2_PROPERTY_GETSET(plugin_display_type)
LV2_PROPERTY_GETSET_SCALAR(audio_inputs)
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_output)
LV2_PROPERTY_GETSET_SCALAR(description)
@@ -729,6 +748,7 @@ namespace pipedal
LV2_PROPERTY_GETSET(uiPortNotifications)
LV2_PROPERTY_GETSET(modGui)
LV2_PROPERTY_GETSET(patchProperties)
LV2_PROPERTY_GETSET_SCALAR(audio_side_chain_inputs)
static json_map::storage_type<Lv2PluginUiInfo> jmap;
};
@@ -772,6 +792,7 @@ namespace pipedal
AutoLilvNode enumeration_property_uri;
AutoLilvNode core__toggled;
AutoLilvNode core__connectionOptional;
AutoLilvNode core__isSideChain;
AutoLilvNode portprops__not_on_gui_property_uri;
AutoLilvNode portprops__trigger;
AutoLilvNode midi__event;
@@ -782,9 +803,9 @@ namespace pipedal
AutoLilvNode invada_units__unit; // typo in invada plugins.
AutoLilvNode invada_portprops__logarithmic; // typo in invada plugins.
AutoLilvNode atom__bufferType;
AutoLilvNode atom__Path;
AutoLilvNode atom__String;
AutoLilvNode presets__preset;
AutoLilvNode state__state;
AutoLilvNode rdfs__label;
@@ -816,7 +837,6 @@ namespace pipedal
AutoLilvNode pipedalUI__width;
AutoLilvNode pipedalUI__graphicEq;
AutoLilvNode pipedalUI__outputPorts;
AutoLilvNode pipedalUI__text;
@@ -846,7 +866,6 @@ namespace pipedal
AutoLilvNode patch__readable;
AutoLilvNode pipedal_patch__readable;
AutoLilvNode mod__brand;
AutoLilvNode mod__label;
AutoLilvNode mod__preferMomentaryOffByDefault;
@@ -861,11 +880,9 @@ namespace pipedal
AutoLilvNode buf_size__maxBlockLength;
AutoLilvNode buf_size__fixedBlockLength;
AutoLilvNode buf_size__coarseBlockLength;
AutoLilvNode port_groups__sideChainOf;
};
LilvUris* lilvUris = nullptr;
LilvUris *lilvUris = nullptr;
private:
bool vst3Enabled = true;
@@ -896,7 +913,7 @@ namespace pipedal
void free_world();
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::map<std::string, std::shared_ptr<Lv2PluginClass>> classesMap;
@@ -947,22 +964,19 @@ namespace pipedal
public:
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();
// equivalent to LV2 MapPath AbstractPath features.
std::string MapPath(const std::string &abstractPath);
std::string AbstractPath(const std::string&path);
json_variant MapPath(const json_variant&json);
json_variant AbstractPath(const json_variant&json);
std::string AbstractPath(const std::string &path);
json_variant MapPath(const json_variant &json);
json_variant AbstractPath(const json_variant &json);
private:
std::set<std::string> pluginsThatHaveBeenCheckedForResources;
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 StateRestoreCallback(
const char* port_symbol,
void* user_data,
const void* value,
uint32_t size,
uint32_t type);
const char *port_symbol,
void *user_data,
const void *value,
uint32_t size,
uint32_t type);
virtual IEffect *CreateEffect(PedalboardItem &pedalboardItem);
void LoadPluginClassesFromLilv();
@@ -995,9 +1008,9 @@ namespace pipedal
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)
{
@@ -1010,9 +1023,8 @@ namespace pipedal
class Urids;
Urids *urids = nullptr;
ModGuiUris* mod_gui_uris = nullptr;
Urids *urids = nullptr;
ModGuiUris *mod_gui_uris = nullptr;
void OnConfigurationChanged(const JackConfiguration &configuration, const JackChannelSelection &settings);
@@ -1021,7 +1033,7 @@ namespace pipedal
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_; }
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;
for (const auto &pair : pluginPreset.pathProperties_)
{
newPathProperties[pair.first] = this->ToAbstractPathJson(pair.second);
newPathProperties[pair.first] = this->ToAbstractPathFromJson(pair.second);
}
pluginPreset.pathProperties_ = newPathProperties;
}
@@ -2999,7 +2999,19 @@ std::string Storage::GetTone3000Auth() const
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);
+2 -1
View File
@@ -129,8 +129,9 @@ public:
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::filesystem::path FromAbstractPathString(const std::string &stringPath);
//std::vector<std::string> GetPedalboards();
+1 -1
View File
@@ -520,7 +520,7 @@ public:
res.set(HttpField::content_type, "application/json");
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))
{
+147 -123
View File
@@ -24,17 +24,40 @@ import { withStyles } from "tss-react/mui";
import { createStyles } from './WithStyles';
import FullscreenIcon from '@mui/icons-material/Fullscreen';
import IconButtonEx from './IconButtonEx';
import { isDarkMode } from './DarkMode';
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 { css } from '@emotion/react';
import { PiPedalModel, PiPedalModelFactory } from './PiPedalModel';
const styles = (theme: Theme) => createStyles({
pgraph: {
pgraph: css({
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,
rightPad: number,
children?: React.ReactNode,
toolbarChildren?: React.ReactNode[],
contentReady: boolean,
showZoomed: boolean,
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(
class extends ResizeResponsiveComponent<AutoZoomProps, AutoZoomState> {
@@ -97,38 +130,43 @@ const AutoZoom = withStyles(
if (this.normalRef) {
this.updateZoom(this.normalRef);
}
if (this.maximizedRef) {
this.updateMaximizedZoom();
}
}
}
resizeObserver: ResizeObserver | null = null;
calculateZoomedClientRect(
calculateUnzoomedLayout(
clientRect: Rect,
contentWidth: number,
contentHeight: 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 topButtonZoom = Math.min((clientRect.width - buttonPadding) / contentWidth, (clientRect.height - buttonPadding) / contentHeight);
let effectiveClientRect = clientRect.copy();
if (rightButtonZoom > topButtonZoom) {
result.horizontalToolbar = false;
effectiveClientRect.width -= buttonPadding;
} else {
result.horizontalToolbar = true;
effectiveClientRect.y += buttonPadding;
effectiveClientRect.height -= buttonPadding;
}
result.effectiveClientRect = effectiveClientRect;
let zoom = Math.min(effectiveClientRect.width / contentWidth, effectiveClientRect.height / contentHeight);
if (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;
if (leftPad < 0) {
leftPad = 0;
@@ -137,7 +175,8 @@ const AutoZoom = withStyles(
if (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) {
@@ -145,59 +184,58 @@ const AutoZoom = withStyles(
return;
}
let content = ref.firstElementChild as HTMLElement | null;
const classes = withStyles.getClasses(this.props);
if (content) {
const { leftPad, rightPad } = this.props;
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);
let contentRect = layout.contentRect;
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})`;
`translate(${contentRect.x}px, ${contentRect.y}px) scale(${layout.zoom}, ${layout.zoom})`;
if (this.zoomButton) {
this.zoomButton.style.display = zoomedClientRect.width < content.clientWidth ? "block" : "none";
let rightSizeSpace = ref.clientWidth - zoomedClientRect.right;
if (rightSizeSpace - rightPad >= 48) {
this.zoomButton.style.top = `${zoomedClientRect.y + 24}px`;
this.zoomButton.style.left = `${zoomedClientRect.right + 4}px`;
if (this.zoomToolbar) {
if (!layout.showToolbar) {
this.zoomToolbar.className = classes.noToolbar;
} else if (layout.horizontalToolbar) {
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 {
this.zoomButton.style.top = `${zoomedClientRect.y - 48}px`;
this.zoomButton.style.left = `${zoomedClientRect.right - 64 - 12}px`;
this.zoomToolbar.className = classes.rightToolbar;
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.
this.zoomStartRect = zoomedClientRect.copy();
this.zoomStartRect = layout.contentRect.copy();
let refBounds = ref.getBoundingClientRect();
this.zoomStartRect.x += refBounds.left;
this.zoomStartRect.y += refBounds.top;
} else {
if (this.zoomToolbar) {
this.zoomToolbar.className = classes.noToolbar;
}
}
}
updateMaximizedZoom() {
let ref = this.maximizedRef;
if (!this.mounted || !ref) {
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());
if (this.normalRef) {
this.updateZoom(this.normalRef);
}
}
@@ -234,20 +272,20 @@ const AutoZoom = withStyles(
}
this.maximizedAnimateFrameHandle = window.requestAnimationFrame(() => {
this.maximizedAnimateFrameHandle = null;
if (this.maximizedRef) {
this.updateMaximizedZoom();
}
this.updateMaximizedZoom();
});
}
private zoomToolbar: HTMLElement | null = null;
private zoomButton: HTMLElement | null = null;
private normalRef: HTMLDivElement | null = null;
setNormalRef(ref: HTMLDivElement | null) {
setContentRef(ref: HTMLDivElement | null) {
this.normalRef = 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.requestZoomUpdate();
});
@@ -261,36 +299,15 @@ const AutoZoom = withStyles(
if (this.resizeObserver) {
this.resizeObserver.disconnect();
this.resizeObserver = null;
this.zoomButton = null;
this.zoomToolbar = 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;
startZoom() {
@@ -300,70 +317,77 @@ const AutoZoom = withStyles(
this.props.setShowZoomed(true);
}
render() {
content() {
const classes = withStyles.getClasses(this.props);
void classes; // suppress unused variable warning
const landscape = this.state.screenWidth > this.state.screenHeight;
return (<div style={{ overflow: "hidden", height: "100%", width: "100%" }} ref={(ref) => { this.setNormalRef(ref); }}>
<div style={{
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",
}}
return (
<div ref={(ref) => { this.setContentRef(ref); }}
style={{ width: "100%", height: "100%" }}
>
<IconButtonEx tooltip="Fullscreen view"
style={{ margin: 2, background: isDarkMode() ? "#444" : "#eee" }}
onClick={() => {
this.startZoom();
<div style={{
display: "inline-block",
position: "absolute",
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" }} />
</IconButtonEx>
</div>
{this.props.showZoomed && (
<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,
{!this.props.showZoomed ? (
<IconButtonEx id="zoom-button" tooltip="Fullscreen view"
onClick={() => {
this.startZoom();
}}
>
<FullscreenIcon style={{ opacity: 0.6 }}
/>
</IconButtonEx>
) : (
<IconButtonEx tooltip="Exit fullscreen"
style={{}}
onClick={(e) => {
e.stopPropagation();
this.props.setShowZoomed(false);
}}
>
<CloseIcon />
<ArrowBackIcon style={{
opacity: 0.6,
}} />
</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>
)}
</div>);
+12
View File
@@ -47,6 +47,7 @@ export class Port implements Deserializable<Port> {
this.scale_points = ScalePoint.deserialize_array(input.scale_points);
this.is_input = input.is_input;
this.is_output = input.is_output;
this.is_sidechain = input.is_sidechain;
this.is_control_port = input.is_control_port;
this.is_audio_port = input.is_audio_port;
this.is_atom_port = input.is_atom_port;
@@ -80,6 +81,7 @@ export class Port implements Deserializable<Port> {
is_output: boolean = false
is_control_port: boolean = false;
is_audio_port: boolean = false;
is_sidechain: boolean = false;
is_atom_port: boolean = false;
is_valid: boolean = false;
supports_midi: boolean = false;
@@ -92,9 +94,11 @@ export class Port implements Deserializable<Port> {
export class PortGroup {
deserialize(input: any): PortGroup {
this.isA = input.isA;
this.uri = input.uri;
this.symbol = input.symbol;
this.name = input.name;
this.sideChainof = input.sideChainOf;
this.parent_group = input.parent_group;
this.program_list_id = input.program_list_id ?? -1;
@@ -108,9 +112,12 @@ export class PortGroup {
return result;
}
isA: string[] = [];
uri: string = "";
symbol: string = "";
name: string = "";
sideChainof: string = "";
parent_group: string = "";
program_list_id: number = -1;
};
@@ -967,6 +974,8 @@ export class UiPlugin implements Deserializable<UiPlugin> {
this.author_name = input.author_name;
this.author_homepage = input.author_homepage;
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.has_midi_input = input.has_midi_input;
this.has_midi_output = input.has_midi_output;
@@ -1057,7 +1066,9 @@ export class UiPlugin implements Deserializable<UiPlugin> {
author_name: string = "";
author_homepage: string = "";
audio_inputs: number = 0;
audio_side_chain_inputs: number = 0;
audio_outputs: number = 0;
audio_side_chain_title: string = "";
has_midi_input: number = 0;
has_midi_output: number = 0;
description: string = "";
@@ -1084,6 +1095,7 @@ export function makeSplitUiPlugin(): UiPlugin {
author_name: "",
author_homepage: "",
audio_inputs: 1,
audio_side_chain_inputs: 0,
audio_outputs: 1,
has_midi_input: 0,
has_midi_output: 0,
+37
View File
@@ -81,6 +81,7 @@ export class PedalboardItem implements Deserializable<PedalboardItem> {
this.pathProperties = input.pathProperties;
this.useModUi = input.useModUi ?? false;
this.iconColor = input.iconColor??"";
this.sideChainInputId = input.sideChainInputId ?? -1;
return this;
}
@@ -217,6 +218,7 @@ export class PedalboardItem implements Deserializable<PedalboardItem> {
pathProperties: {[Name: string]: string} = {};
useModUi: boolean = false; // true if this item should use the mod-ui.
iconColor: string = "";
sideChainInputId: number = -1; // -1 means no sidechain input.
};
export class SnapshotValue {
@@ -392,6 +394,7 @@ export class Pedalboard implements Deserializable<Pedalboard> {
pathProperties: {[Name: string]: string} = {};
selectedPlugin: number = -1;
// yields all items in the pedalboard, including split items. Splits are yielded before their children.
*itemsGenerator(): Generator<PedalboardItem, void, undefined> {
let it = itemGenerator_(this.items);
while (true)
@@ -401,6 +404,16 @@ export class Pedalboard implements Deserializable<Pedalboard> {
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 {
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);
uiPluginsByUri: Map<string, UiPlugin> = new Map<string, UiPlugin>();
svgImgUrl(svgImage: string): string {
//return this.varServerUrl + "img/" + svgImage;
// return this.varServerUrl + "img/" + svgImage;
return "img/" + svgImage;
}
getUiPlugin(uri: string): UiPlugin | null {
let plugins: UiPlugin[] = this.ui_plugins.get();
for (let plugin of plugins) {
if (plugin.uri === uri) return plugin;
}
return null;
return this.uiPluginsByUri.get(uri) ?? null;
}
androidHost?: AndroidHostInterface;
@@ -662,6 +659,7 @@ export class PiPedalModel //implements PiPedalModel
}
private setModelPedalboard(pedalboard: Pedalboard) {
this.removeInvalidSidechains(pedalboard);
this.pedalboard.set(pedalboard);
this.selectedSnapshot.set(pedalboard.selectedSnapshot);
this.updateEnabledItems(pedalboard);
@@ -1210,6 +1208,11 @@ export class PiPedalModel //implements PiPedalModel
this.ui_plugins.set(
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(
new Pedalboard().deserialize(
await this.getWebSocket().request<Pedalboard>("currentPedalboard")
@@ -1761,13 +1764,17 @@ export class PiPedalModel //implements PiPedalModel
}
let it = newPedalboard.itemsGenerator();
let oldInstanceId = -1;
let newInstanceId = -1;
while (true) {
let v = it.next();
if (v.done) break;
let item = v.value;
if (item.instanceId === itemId) {
oldInstanceId = item.instanceId;
item.deserialize(new PedalboardItem()); // skeezy way to re-initialize.
item.instanceId = ++newPedalboard.nextInstanceId;
newInstanceId = item.instanceId;
item.uri = selectedUri;
item.pluginName = plugin.name;
item.controlValues = this.getDefaultValues(item.uri);
@@ -1782,6 +1789,9 @@ export class PiPedalModel //implements PiPedalModel
item.pathProperties[fileProperty.patchProperty] = "null";
}
newPedalboard.selectedPlugin = item.instanceId;
if (oldInstanceId !== -1) {
this.updateSidechainReferences(newPedalboard, oldInstanceId, newInstanceId);
}
this.setModelPedalboard(newPedalboard);
this.updateServerPedalboard()
return item.instanceId;
@@ -1936,6 +1946,9 @@ export class PiPedalModel //implements PiPedalModel
newPedalboard.replaceItem(fromInstanceId, emptyItem);
newPedalboard.replaceItem(toInstanceId, fromItem);
newPedalboard.selectedPlugin = fromItem.instanceId;
if (!toItem.isEmpty()) {
this.updateSidechainReferences(newPedalboard, toItem.instanceId, fromItem.instanceId);
}
this.setModelPedalboard(newPedalboard);
this.updateServerPedalboard();
@@ -3543,14 +3556,66 @@ export class PiPedalModel //implements PiPedalModel
}
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> {
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> {
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 { createStyles } from './WithStyles';
import { css } from '@emotion/react';
import IconButtonEx from './IconButtonEx';
import SidechainConnectIcon from './svg/ic_sidechain_open_48.svg?react';
import AutoZoom from './AutoZoom';
@@ -37,6 +40,7 @@ import {
} from './Pedalboard';
import PluginControl from './PluginControl';
import ResizeResponsiveComponent from './ResizeResponsiveComponent';
import SideChainSelectControl, {SideChainSelectDialog} from './SideChainSelectControl';
import VuMeter from './VuMeter';
import { nullCast } from './Utility'
import { PiPedalStateError } from './PiPedalError';
@@ -66,6 +70,11 @@ export interface ICustomizationHost {
isLandscapeGrid(): boolean;
}
interface SideChainSelectItem {
instanceId: number;
title: string;
}
function makeIoPluginInfo(name: string, uri: string): UiPlugin {
let result = new UiPlugin();
result.name = name;
@@ -367,11 +376,12 @@ type PluginControlViewState = {
dialogFileValue: string,
modGuiContentReady: boolean,
showModGuiZoomed: boolean,
sidechainDialogOpen: boolean
};
const PluginControlView =
withTheme(withStyles(
class extends ResizeResponsiveComponent<PluginControlViewProps, PluginControlViewState> implements ICustomizationHost {
class extends ResizeResponsiveComponent<PluginControlViewProps, PluginControlViewState> {
model: PiPedalModel;
constructor(props: PluginControlViewProps) {
@@ -387,7 +397,8 @@ const PluginControlView =
dialogFileProperty: new UiFileProperty(),
dialogFileValue: "",
modGuiContentReady: false,
showModGuiZoomed: false
showModGuiZoomed: false,
sidechainDialogOpen: false
}
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 {
return this.state.landscapeGrid;
}
@@ -575,7 +648,7 @@ const PluginControlView =
}
return (
<div key={key} className={!isLandscapeGrid ? classes.portGroup : classes.portGroupLandscape}
style={{ borderWidth: (controlGroup.name === "" ? 0: undefined) }}
style={{ borderWidth: (controlGroup.name === "" ? 0 : undefined) }}
>
{controlGroup.name !== "" && (
<div className={classes.portGroupTitle}>
@@ -711,6 +784,11 @@ const PluginControlView =
);
}
}
if (plugin.audio_side_chain_inputs !== 0) {
result.push(this.makeSideChainSelect(plugin));
}
return result;
}
@@ -803,6 +881,9 @@ const PluginControlView =
return false;
}
handleSelectSidechainInput() {
this.setState({sidechainDialogOpen: true});
}
handleModGuiFileProperty(instanceId: number, filePropertyUri: string, selectedFile: string) {
let plugin = this.model.getUiPlugin(this.props.item.uri);
@@ -827,10 +908,30 @@ const PluginControlView =
if (!uiPlugin) {
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 (
<div style={{ width: "100%", height: "100%", }}>
<AutoZoom leftPad={36} rightPad={52} contentReady={this.state.modGuiContentReady}
showZoomed={this.state.showModGuiZoomed}
toolbarChildren={toolBarChildren}
setShowZoomed={
(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. */}
<FullScreenIME uiControl={this.state.imeUiControl} value={this.state.imeValue}
+96 -93
View File
@@ -20,7 +20,7 @@
import React, { Component } from 'react';
import { Theme } from '@mui/material/styles';
import WithStyles from './WithStyles';
import {createStyles} from './WithStyles';
import { createStyles } from './WithStyles';
import { withStyles } from "tss-react/mui";
import { UiControl } from './Lv2Plugin';
@@ -47,8 +47,7 @@ const defaultVuColors: VuColor[] = [
function getVuColor(vuColors: VuColor[], levelDb: number): string {
let ix = 0;
for (let i = 0; i < vuColors.length; ++i)
{
for (let i = 0; i < vuColors.length; ++i) {
let c = vuColors[i];
if (levelDb > c.minDb && levelDb <= c.maxDb) {
ix = i;
@@ -122,18 +121,18 @@ const styles = (theme: Theme) => createStyles({
}),
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({
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({
flex: "0 0 28", position: "relative", width: 60, height: 28, minHeight: 28
}),
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> {
private model: PiPedalModel;
private vuRef: React.RefObject<HTMLDivElement|null>;
private progressRef: React.RefObject<HTMLDivElement|null>;
private dbVuRef: React.RefObject<HTMLDivElement|null>;
private dbVuTelltaleRef: React.RefObject<HTMLDivElement|null>;
private dbVuTextRef: React.RefObject<HTMLDivElement|null>;
private lampRef: React.RefObject<HTMLDivElement|null>;
private vuRef: React.RefObject<HTMLDivElement | null>;
private progressRef: React.RefObject<HTMLDivElement | null>;
private dbVuRef: React.RefObject<HTMLDivElement | null>;
private dbVuTelltaleRef: React.RefObject<HTMLDivElement | null>;
private dbVuTextRef: React.RefObject<HTMLDivElement | null>;
private lampRef: React.RefObject<HTMLDivElement | null>;
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 DB_VU_HEIGHT = 60 - 4;
private animationHandle: number | undefined = undefined;
@@ -240,26 +239,23 @@ const PluginOutputControl =
}
if (this.dbVuTextRef.current) {
let text: string;
if (this.dbVuTelltale <= this.props.uiControl.min_value)
{
if (this.dbVuTelltale <= this.props.uiControl.min_value) {
text = "-";
} else {
if (Math.abs(this.dbVuTelltale) >= 99.5)
{
if (Math.abs(this.dbVuTelltale) >= 99.5) {
text = Math.round(this.dbVuTelltale).toString();
} else {
text = this.dbVuTelltale.toFixed(1);
}
if (!text.startsWith("-")) {
text = '+'+text;
}
text = '+' + text;
}
}
if (this.lastDbText !== text)
{
if (this.lastDbText !== text) {
this.lastDbText = text;
this.dbVuTextRef.current.innerText = text;
}
}
}
this.animationHandle = undefined;
this.updateDbVuTelltale();
}
@@ -322,7 +318,7 @@ const PluginOutputControl =
)
}
}
}
else if (this.vuRef.current) {
let control = this.props.uiControl;
let range = (value - control.min_value) / (control.max_value - control.min_value);
@@ -389,8 +385,7 @@ const PluginOutputControl =
if (value > control.max_value) {
value = control.max_value;
}
if (value < control.min_value)
{
if (value < control.min_value) {
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()) {
item_width = undefined;
return (
<div className={classes.controlFrame}
<div className={classes.controlFrame}
style={{ width: item_width }}>
{/* TITLE SECTION */}
<div className={classes.titleSection}
style={{ width: "100%" }}>
<div className={classes.titleSection}
style={{ width: "100%" }}>
<ControlTooltip uiControl={control}>
<Typography variant="caption" display="block" style={{
<Typography variant="caption" display="block" style={{
width: "100%",
textAlign: "center"
}}> {control.name === "" ? "\u00A0" : control.name}</Typography>
@@ -453,15 +448,18 @@ const PluginOutputControl =
</div>
{/* CONTROL SECTION */}
<div className={classes.midSection}
<div className={classes.midSection}
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={{ height: 8, width: this.PROGRESS_WIDTH, overflow: "hidden", position: "absolute",
margin: "1px 1px 1px 1px", background: "#282828" }}>
<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",
<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",
margin: "1px 1px 1px 1px", background: "#282828"
}}>
<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"
}} />
}} />
</div>
</div>
@@ -478,45 +476,48 @@ const PluginOutputControl =
let vuColors = defaultVuColors;
return (
<div className={classes.controlFrame}
<div className={classes.controlFrame}
style={{ width: item_width }}>
{/* TITLE SECTION */}
<div className={classes.titleSection}
style={{ width: "100%" }}>
<ControlTooltip uiControl={control}>
<Typography variant="caption" display="block" style={{
width: "100%",
textAlign: "center"
}}> {control.name === "" ? "\u00A0" : control.name}</Typography>
</ControlTooltip>
<div className={classes.titleSection}
style={{ width: "100%" }}>
<Typography variant="caption" display="block" style={{
width: "100%",
textAlign: "center"
}}> {control.name === "" ? "\u00A0" : control.name}</Typography>
</div>
{/* CONTROL SECTION */}
<div className={classes.midSection}
<div className={classes.midSection}
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", }}>
<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;
<ControlTooltip uiControl={control}>
return (
<div key={ix}
style={{ position: "absolute", width: 4, height: (bottom-top),
top: top, left:0, background: vuColor.color }} />
);
})
}
<div style={{ width: 8, height: this.DB_VU_HEIGHT + 4, background: "#000", }}>
<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 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 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>
</ControlTooltip>
</div>
<div className={classes.editSection}>
@@ -533,26 +534,28 @@ const PluginOutputControl =
else if (control.isVu()) {
item_width = undefined;
return (
<div className={classes.controlFrame} style={{ width: item_width}}>
<div className={classes.controlFrame} style={{ width: item_width }}>
{/* TITLE SECTION */}
<div className={classes.titleSection}
<div className={classes.titleSection}
style={{ width: "100%" }}>
<ControlTooltip uiControl={control}>
<Typography noWrap display="block" variant="caption" style={{
width: item_width,
textAlign: "center"
}}> {control.name === "" ? "\u00A0" : control.name}</Typography>
</ControlTooltip>
<Typography noWrap display="block" variant="caption" style={{
width: item_width,
textAlign: "center"
}}> {control.name === "" ? "\u00A0" : control.name}</Typography>
</div>
{/* CONTROL SECTION */}
<div className={classes.midSection}
style={{ display: "flex", justifyContent: "center", alignItems: "start", flexFlow: "row nowrap" }}>
<div style={{ width: 8, height: this.VU_HEIGHT + 4, background: "#000", }}>
<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", }} />
style={{ display: "flex", justifyContent: "center", alignItems: "start", flexFlow: "row nowrap" }}>
<ControlTooltip uiControl={control}>
<div style={{ width: 8, height: this.VU_HEIGHT + 4, background: "#000", }}>
<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>
</ControlTooltip>
</div>
@@ -575,11 +578,11 @@ const PluginOutputControl =
}
return (
<div className={classes.controlFrame}
style={{width: attachedLamp? 15: item_width}}
>
style={{ width: attachedLamp ? 15 : item_width }}
>
{/* TITLE SECTION */}
<div className={classes.titleSection}
style={{ flex: "0 0 auto", width: "100%"}}>
<div className={classes.titleSection}
style={{ flex: "0 0 auto", width: "100%" }}>
<ControlTooltip uiControl={control}>
<Typography noWrap variant="caption" display="block" style={{
width: "100%",
@@ -589,7 +592,7 @@ const PluginOutputControl =
</div>
{/* CONTROL SECTION */}
<div className={classes.midSection}
<div className={classes.midSection}
style={{
display: "flex", justifyContent: "center", alignItems: "center", flexFlow: "row nowrap",
}}>
@@ -613,23 +616,23 @@ const PluginOutputControl =
);
} if (control.isOutputText()) {
return (
<div className={classes.controlFrame}
style={{ display: "flex", flexDirection: "column", width: item_width }}>
<div className={classes.controlFrame}
style={{ display: "flex", flexDirection: "column", width: item_width }}>
{/* TITLE SECTION */}
<div className={classes.titleSection}
<div className={classes.titleSection}
style={{ flex: "0 0 auto", width: "100%" }}>
<ControlTooltip uiControl={control}>
<Typography noWrap variant="caption" display="block" style={{
width: "100%",
textAlign: "left"
}}> {control.name === "" ? "\u00A0": control.name}</Typography>
}}> {control.name === "" ? "\u00A0" : control.name}</Typography>
</ControlTooltip>
</div>
{/* CONTROL SECTION */}
<div className={classes.midSection} style={{
display: "flex", justifyContent: "center",
display: "flex", justifyContent: "center",
flexFlow: "row nowrap", paddingTop: 6
}}>
@@ -646,10 +649,10 @@ const PluginOutputControl =
} else {
return (
<div className={classes.controlFrame}
style={{ display: "flex", flexDirection: "column", width: item_width }}>
<div className={classes.controlFrame}
style={{ display: "flex", flexDirection: "column", width: item_width }}>
{/* TITLE SECTION */}
<div className={classes.titleSection}
<div className={classes.titleSection}
style={{ flex: "0 0 auto", width: "100%" }}>
<ControlTooltip uiControl={control}>
<Typography noWrap variant="caption" display="block" style={{
@@ -660,7 +663,7 @@ const PluginOutputControl =
</div>
{/* CONTROL SECTION */}
<div className={classes.midSection}
<div className={classes.midSection}
style={{ display: "flex", justifyContent: "start", alignItems: "center", flexFlow: "row nowrap" }}>
<Typography noWrap variant="caption" display="block" style={{ width: "100%" }}>
{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 Toolbar from '@mui/material/Toolbar';
import CssBaseline from '@mui/material/CssBaseline';
import {createStyles} from './WithStyles';
import { createStyles } from './WithStyles';
import { withStyles } from "tss-react/mui";
import IconButtonEx from './IconButtonEx';
@@ -148,10 +148,12 @@ const SnapshotEditor = withStyles(
this.model_.zoomedUiControl.addOnChangedHandler(
() => {
this.setState({
zoomedControlOpen: this.model_.zoomedUiControl.get() !== undefined,
zoomedControlInfo: this.model_.zoomedUiControl.get()
});
if (this.mounted93) {
this.setState({
zoomedControlOpen: this.model_.zoomedUiControl.get() !== undefined,
zoomedControlInfo: this.model_.zoomedUiControl.get()
});
}
}
);
@@ -182,9 +184,11 @@ const SnapshotEditor = withStyles(
this.setState({ isFullScreen: !this.state.isFullScreen });
}
private mounted93: boolean = false;
componentDidMount() {
super.componentDidMount();
this.mounted93 = true;
this.model_.showStatusMonitor.addOnChangedHandler(this.showStatusMonitorHandler);
}
@@ -206,6 +210,7 @@ const SnapshotEditor = withStyles(
}
componentWillUnmount() {
this.mounted93 = false;
super.componentWillUnmount();
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