Merge branch 'dev' of https://github.com/rerdavies/pipedal into dev
This commit is contained in:
+2
-2
@@ -1,13 +1,13 @@
|
||||
cmake_minimum_required(VERSION 3.16.0)
|
||||
project(pipedal
|
||||
VERSION 2.0.101
|
||||
VERSION 2.0.102
|
||||
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 v2.0.101-Alpha")
|
||||
set (DISPLAY_VERSION "PiPedal v2.0.102-Alpha")
|
||||
set (PACKAGE_ARCHITECTURE ${DEBIAN_ARCHITECTURE})
|
||||
set (CMAKE_INSTALL_PREFIX "/usr/")
|
||||
|
||||
|
||||
@@ -208,8 +208,9 @@ void DBusDispatcher::WaitForClose()
|
||||
serviceThread.join();
|
||||
}
|
||||
}
|
||||
catch (const std::exception &)
|
||||
catch (const std::exception &e)
|
||||
{
|
||||
std::cout << "DBusDispatcher: " << e.what() << std::endl;
|
||||
}
|
||||
if (eventFd != -1)
|
||||
{
|
||||
|
||||
@@ -78,7 +78,7 @@ private:
|
||||
std::atomic<bool> stopping;
|
||||
|
||||
std::vector<CallbackEntry> postedEvents;
|
||||
bool threadStarted;
|
||||
bool threadStarted = false;
|
||||
std::thread serviceThread;
|
||||
std::mutex postMutex;
|
||||
};
|
||||
@@ -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: <a href='https://rerdavies.github.io/pipedal/download.html'>v2.0.101</a>
|
||||
Download: <a href='https://rerdavies.github.io/pipedal/download.html'>v2.0.102</a>
|
||||
Website: [https://rerdavies.github.io/pipedal](https://rerdavies.github.io/pipedal).
|
||||
Documentation: [https://rerdavies.github.io/pipedal/Documentation.html](https://rerdavies.github.io/pipedal/Documentation.html).
|
||||
|
||||
#### NEW version 2.0.101 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 2.0.102 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!
|
||||
|
||||
|
||||
|
||||
|
||||
+5
-5
@@ -13,18 +13,18 @@ page_icon: img/Install4.jpg
|
||||
|
||||
Download the most recent Debian (.deb) package for your platform:
|
||||
|
||||
- [Raspberry Pi OS bookworm (aarch64) v2.0.101](https://github.com/rerdavies/pipedal/releases/download/v2.0.101/pipedal_2.0.101_arm64.deb)
|
||||
- [Ubuntu 24.04 through 25.04 (aarch64) v2.0.101](https://github.com/rerdavies/pipedal/releases/download/v2.0.101/pipedal_2.0.101_arm64.deb)
|
||||
- [Ubuntu 24.04 through 25.04 (amd64) v2.0.101](https://github.com/rerdavies/pipedal/releases/download/v2.0.101/pipedal_2.0.101_amd64.deb)
|
||||
- [Raspberry Pi OS bookworm (aarch64) v2.0.102](https://github.com/rerdavies/pipedal/releases/download/v2.0.102/pipedal_2.0.102_arm64.deb)
|
||||
- [Ubuntu 24.04 through 25.04 (aarch64) v2.0.102](https://github.com/rerdavies/pipedal/releases/download/v2.0.102/pipedal_2.0.102_arm64.deb)
|
||||
- [Ubuntu 24.04 through 25.04 (amd64) v2.0.102](https://github.com/rerdavies/pipedal/releases/download/v2.0.102/pipedal_2.0.102_amd64.deb)
|
||||
|
||||
|
||||
Version 2.0.101 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 2.0.102 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_2.0.101_arm64.deb
|
||||
sudo apt-get install ./pipedal_2.0.102_arm64.deb
|
||||
```
|
||||
You MUST use `apt-get`. `apt` will not install downloaded packages; and `dpkg -i` will not install dependencies.
|
||||
|
||||
|
||||
+30
-6
@@ -1,15 +1,39 @@
|
||||
# Release Notes
|
||||
|
||||
## PiPedal 2.0.101 Internal
|
||||
## PiPedal 2.0.102 Alpha
|
||||
|
||||
- TooB Tone parameter tweaks.
|
||||
PiPedal 2.0 provides support for Neural Amp Modeler A2 models.
|
||||
|
||||
## PiPedal 2.0.100 Internal
|
||||
NAM A2 provides significant improvements in sound quality and performance compared to NAM A1. NAM A2 also provides "Slimable" models. Slimmable models provide multiple levels of performance and sound quality in a single model file, allowing you to choose the level of performance and sound quality that best suits your needs. This is particularly useful for users of Raspberry Pi 4, since NAM A2 models can provide better sound quality than NAM A1 models, while consuming significantly less CPU resources.
|
||||
|
||||
- Tone3000 direct uploads to PiPedal server (Toob NAM and Toob CabIr)
|
||||
- New TooB Tone plugin: a simple one-knob tone control.
|
||||
- Channel Routing: configure audio channels as Aux or re-amp channels.
|
||||
A new integrated Tone3000 model downloader provides a convenient way to download the latest NAM A2 models and IIR files from Tone3000 directly to the PiPedal server. All existing NAM A1 models on Tone3000 have been retrained for NAM A2. When
|
||||
you download from Tone3000 using the integrated downloader, you will get NAM A2 models. Just click on the DOWNLOAD MODELS FROM TONE3000 button.
|
||||
|
||||
Currently, the best way to get access to NAM A2 models is via the integrated Tone3000 model downloader. Models downloaded externally from the Tone3000 website will still be NAM A1 models; however this should change soon with a forthcoming announcement from Tone3000.
|
||||
|
||||
A new channel routing dialog replaces the channel configurationg dialog. The Channel Routing dialog allows global (i.e. not per-preset) configuration of audio channels. You can now configure audio channels as Aux channels (e.g. using the non-guitar channel on your audio interface as an input for backing tracks), or sending unprocessed guitar input signal to an unused output channel for re-amping in a DAW or external hardware. Refer to the (?) help dialog in the Channel Routing Dialog for more information on how to use this feature.
|
||||
|
||||
### Features
|
||||
|
||||
- Support for Neural Amp Modeler A2 models. The latest version of the TooB Neural Amp Modeler plugin now supports NAM A2 models, which provide improved performance and sound quality compared to older NAM A1 models.
|
||||
|
||||
- Download Tone3000 NAM models and IIR files directly to the PiPedal server. Click on the DOWNLOAD MODELS FROM TONE3000 button in the NAM file selection dialog to download the latest NAM A2 models.
|
||||
|
||||
- Download IIR files directly into TooB Cab IR from Tone3000.
|
||||
|
||||
- New Channel Routing Dialog.
|
||||
|
||||
- New TooB Tone plugin: a simple one-knob tone control. The new TooB Tone plugin provides a simple one-knob tone control that can be used to adjust the overall tone of your guitar signal. It is a great tool for quickly shaping your tone without having to use a more complex EQ plugin.
|
||||
|
||||
- Copy and Paste plugins (and their settings) between pedalboards. Accessible via the "Copy Plugin" and "Paste Plugin" menu items in the former Plugin Preset selector menu.
|
||||
|
||||
### Minor features and bug fixes
|
||||
|
||||
- Better handling of step sizes in numeric edit controls.
|
||||
|
||||
- Short names of TooB CE-2 Chorus and TooB BF-2 Flanger changed to "TooB Chorus" and "TooB Flanger", repsectively.
|
||||
|
||||
- Split controls now support double-tap to reset to a default value. (Other controls already support this feature).
|
||||
|
||||
## PiPedal 1.5.99 Beta
|
||||
|
||||
|
||||
+4
-4
@@ -4,9 +4,9 @@
|
||||
|
||||
Download the most recent Debian (.deb) package for your platform:
|
||||
|
||||
- [Raspberry Pi OS bookworm (aarch64) v2.0.101 Beta](https://github.com/rerdavies/pipedal/releases/download/v2.0.101/pipedal_2.0.101_arm64.deb)
|
||||
- [Ubuntu 24.x, 25.04 (aarch64) v2.0.101 Alpha](https://github.com/rerdavies/pipedal/releases/download/v2.0.101/pipedal_2.0.101_arm64.deb)
|
||||
- [Ubuntu 24.x, 25.04 (amd64) v2.0.101 Alpha](https://github.com/rerdavies/pipedal/releases/download/v2.0.101/pipedal_2.0.101_amd64.deb)
|
||||
- [Raspberry Pi OS bookworm (aarch64) v2.0.102 Beta](https://github.com/rerdavies/pipedal/releases/download/v2.0.102/pipedal_2.0.102_arm64.deb)
|
||||
- [Ubuntu 24.x, 25.04 (aarch64) v2.0.102 Alpha](https://github.com/rerdavies/pipedal/releases/download/v2.0.102/pipedal_2.0.102_arm64.deb)
|
||||
- [Ubuntu 24.x, 25.04 (amd64) v2.0.102 Alpha](https://github.com/rerdavies/pipedal/releases/download/v2.0.102/pipedal_2.0.102_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_2.0.101_arm64.deb
|
||||
sudo apt-get install ./pipedal_2.0.102_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.
|
||||
|
||||
Binary file not shown.
@@ -207,6 +207,7 @@ else()
|
||||
endif()
|
||||
|
||||
set (PIPEDAL_SOURCES
|
||||
Tone3000Tone.hpp Tone3000Tone.cpp
|
||||
AesDigest.cpp AesDigest.hpp
|
||||
ChannelRouterSettings.cpp ChannelRouterSettings.hpp
|
||||
Curl.cpp Curl.hpp
|
||||
|
||||
+228
-145
@@ -47,6 +47,7 @@
|
||||
#include "AudioFiles.hpp"
|
||||
#include "CrashGuard.hpp"
|
||||
#include "Tone3000Downloader.hpp"
|
||||
#include "HtmlHelper.hpp"
|
||||
|
||||
#ifndef NO_MLOCK
|
||||
#include <sys/mman.h>
|
||||
@@ -80,7 +81,6 @@ PiPedalModel::PiPedalModel()
|
||||
atomConverter(pluginHost.GetMapFeature())
|
||||
{
|
||||
this->updater = Updater::Create();
|
||||
this->updater->Start();
|
||||
this->currentUpdateStatus = updater->GetCurrentStatus();
|
||||
this->pedalboard = Pedalboard::MakeDefault();
|
||||
|
||||
@@ -211,6 +211,10 @@ PiPedalModel::~PiPedalModel()
|
||||
void PiPedalModel::Init(const PiPedalConfiguration &configuration)
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> lock(mutex); // prevent callbacks while we're initializing.
|
||||
if (updaterEnabled)
|
||||
{
|
||||
this->updater->Start();
|
||||
}
|
||||
|
||||
this->configuration = configuration;
|
||||
pluginHost.SetConfiguration(configuration);
|
||||
@@ -239,10 +243,9 @@ void PiPedalModel::Init(const PiPedalConfiguration &configuration)
|
||||
|
||||
int64_t PiPedalModel::DownloadModelsFromTone3000(
|
||||
const std::string &uri,
|
||||
const Tone3000PkceParams&pckeParams,
|
||||
const Tone3000PkceParams &pckeParams,
|
||||
const std::string &downloadPath,
|
||||
Tone3000DownloadType downloadType
|
||||
)
|
||||
Tone3000DownloadType downloadType)
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> lock(mutex);
|
||||
|
||||
@@ -271,8 +274,7 @@ int64_t PiPedalModel::DownloadModelsFromTone3000(
|
||||
uri,
|
||||
pckeParams,
|
||||
downloadPath,
|
||||
downloadType
|
||||
);
|
||||
downloadType);
|
||||
}
|
||||
|
||||
void PiPedalModel::CancelTone3000Download(
|
||||
@@ -290,8 +292,13 @@ void PiPedalModel::CancelTone3000Download(
|
||||
// Tone3000Downloader::Listener implementation
|
||||
void PiPedalModel::OnStartTone3000Download(int64_t handle, const std::string &title)
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> lock(mutex);
|
||||
for (auto subscriber : this->subscribers)
|
||||
|
||||
SubscriberList subscribers;
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> lock(mutex);
|
||||
subscribers = this->subscribers;
|
||||
}
|
||||
for (auto &subscriber : this->subscribers)
|
||||
{
|
||||
subscriber->OnTone3000DownloadStarted(handle, title);
|
||||
}
|
||||
@@ -299,8 +306,12 @@ void PiPedalModel::OnStartTone3000Download(int64_t handle, const std::string &ti
|
||||
|
||||
void PiPedalModel::OnTone3000Progress(const Tone3000DownloadProgress &progress)
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> lock(mutex);
|
||||
for (auto subscriber : this->subscribers)
|
||||
SubscriberList subscribers;
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> lock(mutex);
|
||||
subscribers = this->subscribers;
|
||||
}
|
||||
for (auto &subscriber : subscribers)
|
||||
{
|
||||
subscriber->OnTone3000DownloadProgress(progress);
|
||||
}
|
||||
@@ -308,8 +319,12 @@ void PiPedalModel::OnTone3000Progress(const Tone3000DownloadProgress &progress)
|
||||
|
||||
void PiPedalModel::OnTone3000DownloadComplete(int64_t handle, const std::string &resultPath)
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> lock(mutex);
|
||||
for (auto subscriber : this->subscribers)
|
||||
SubscriberList subscribers;
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> lock(mutex);
|
||||
subscribers = this->subscribers;
|
||||
}
|
||||
for (auto subscriber : subscribers)
|
||||
{
|
||||
subscriber->OnTone3000DownloadComplete(handle, resultPath);
|
||||
}
|
||||
@@ -317,8 +332,12 @@ void PiPedalModel::OnTone3000DownloadComplete(int64_t handle, const std::string
|
||||
|
||||
void PiPedalModel::OnTone3000DownloadError(int64_t handle, const std::string &errorMessage)
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> lock(mutex);
|
||||
for (auto subscriber : this->subscribers)
|
||||
SubscriberList subscribers;
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> lock(mutex);
|
||||
subscribers = this->subscribers;
|
||||
}
|
||||
for (auto subscriber : subscribers)
|
||||
{
|
||||
subscriber->OnTone3000DownloadError(handle, errorMessage);
|
||||
}
|
||||
@@ -551,12 +570,15 @@ void PiPedalModel::SetInputVolume(float value)
|
||||
{
|
||||
PreviewInputVolume(value);
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> lock(mutex);
|
||||
SubscriberList subscribers;
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> lock(mutex);
|
||||
subscribers = this->subscribers;
|
||||
|
||||
this->pedalboard.input_volume_db(value);
|
||||
this->pedalboard.input_volume_db(value);
|
||||
}
|
||||
// take a snapshot incase a client unsusbscribes in the notification handler (in which case the mutex won't protect us)
|
||||
std::vector<IPiPedalModelSubscriber::ptr> t{subscribers.begin(), subscribers.end()};
|
||||
for (auto &subscriber : t)
|
||||
for (auto &subscriber : subscribers)
|
||||
{
|
||||
subscriber->OnInputVolumeChanged(value);
|
||||
}
|
||||
@@ -568,11 +590,14 @@ void PiPedalModel::SetOutputVolume(float value)
|
||||
{
|
||||
PreviewOutputVolume(value);
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> lock(mutex);
|
||||
this->pedalboard.output_volume_db(value);
|
||||
// take a snapshot incase a client unsusbscribes in the notification handler (in which case the mutex won't protect us)
|
||||
std::vector<IPiPedalModelSubscriber::ptr> t{subscribers.begin(), subscribers.end()};
|
||||
for (auto &subscriber : t)
|
||||
SubscriberList subscribers;
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> lock(mutex);
|
||||
subscribers = this->subscribers;
|
||||
|
||||
this->pedalboard.output_volume_db(value);
|
||||
}
|
||||
for (auto &subscriber : subscribers)
|
||||
{
|
||||
subscriber->OnOutputVolumeChanged(value);
|
||||
}
|
||||
@@ -591,8 +616,10 @@ void PiPedalModel::PreviewOutputVolume(float value)
|
||||
|
||||
void PiPedalModel::SetControl(int64_t clientId, int64_t pedalItemId, const std::string &symbol, float value)
|
||||
{
|
||||
SubscriberList subscribers;
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> lock(mutex);
|
||||
subscribers = this->subscribers;
|
||||
|
||||
if (!this->pedalboard.SetControlValue(pedalItemId, symbol, value))
|
||||
{
|
||||
@@ -609,27 +636,28 @@ void PiPedalModel::SetControl(int64_t clientId, int64_t pedalItemId, const std::
|
||||
return;
|
||||
}
|
||||
PreviewControl(clientId, pedalItemId, symbol, value);
|
||||
|
||||
{
|
||||
|
||||
// take a snapshot incase a client unsusbscribes in the notification handler (in which case the mutex won't protect us)
|
||||
std::vector<IPiPedalModelSubscriber::ptr> t{subscribers.begin(), subscribers.end()};
|
||||
for (auto &subscriber : t)
|
||||
{
|
||||
subscriber->OnControlChanged(clientId, pedalItemId, symbol, value);
|
||||
}
|
||||
|
||||
this->SetPresetChanged(clientId, true);
|
||||
}
|
||||
}
|
||||
|
||||
for (auto &subscriber : subscribers)
|
||||
{
|
||||
subscriber->OnControlChanged(clientId, pedalItemId, symbol, value);
|
||||
}
|
||||
|
||||
this->SetPresetChanged(clientId, true);
|
||||
}
|
||||
|
||||
void PiPedalModel::FireJackConfigurationChanged(const JackConfiguration &jackConfiguration)
|
||||
{
|
||||
|
||||
SubscriberList subscribers;
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> lock(mutex);
|
||||
subscribers = this->subscribers;
|
||||
}
|
||||
|
||||
// noify subscribers.
|
||||
|
||||
std::vector<IPiPedalModelSubscriber::ptr> t{subscribers.begin(), subscribers.end()};
|
||||
for (auto &subscriber : t)
|
||||
for (auto &subscriber : subscribers)
|
||||
{
|
||||
subscriber->OnJackConfigurationChanged(jackConfiguration);
|
||||
}
|
||||
@@ -637,9 +665,13 @@ void PiPedalModel::FireJackConfigurationChanged(const JackConfiguration &jackCon
|
||||
|
||||
void PiPedalModel::FireBanksChanged(int64_t clientId)
|
||||
{
|
||||
SubscriberList subscribers;
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> lock(mutex);
|
||||
subscribers = this->subscribers;
|
||||
}
|
||||
// noify subscribers.
|
||||
std::vector<IPiPedalModelSubscriber::ptr> t{subscribers.begin(), subscribers.end()};
|
||||
for (auto &subscriber : t)
|
||||
for (auto &subscriber : subscribers)
|
||||
{
|
||||
subscriber->OnBankIndexChanged(this->storage.GetBanks());
|
||||
}
|
||||
@@ -647,20 +679,25 @@ void PiPedalModel::FireBanksChanged(int64_t clientId)
|
||||
|
||||
void PiPedalModel::FirePedalboardChanged(int64_t clientId, bool loadAudioThread)
|
||||
{
|
||||
if (loadAudioThread)
|
||||
SubscriberList subscribers;
|
||||
{
|
||||
// notify the audio thread.
|
||||
if (audioHost && audioHost->IsOpen())
|
||||
{
|
||||
LoadCurrentPedalboard();
|
||||
std::lock_guard<std::recursive_mutex> lock(mutex);
|
||||
subscribers = this->subscribers;
|
||||
|
||||
UpdateRealtimeVuSubscriptions();
|
||||
UpdateRealtimeMonitorPortSubscriptions();
|
||||
if (loadAudioThread)
|
||||
{
|
||||
// notify the audio thread.
|
||||
if (audioHost && audioHost->IsOpen())
|
||||
{
|
||||
LoadCurrentPedalboard();
|
||||
|
||||
UpdateRealtimeVuSubscriptions();
|
||||
UpdateRealtimeMonitorPortSubscriptions();
|
||||
}
|
||||
}
|
||||
}
|
||||
// noify subscribers.
|
||||
std::vector<IPiPedalModelSubscriber::ptr> t{subscribers.begin(), subscribers.end()};
|
||||
for (auto &subscriber : t)
|
||||
for (auto &subscriber : subscribers)
|
||||
{
|
||||
subscriber->OnPedalboardChanged(clientId, this->pedalboard);
|
||||
}
|
||||
@@ -671,45 +708,53 @@ void PiPedalModel::SetPedalboard(int64_t clientId, Pedalboard &pedalboard)
|
||||
std::lock_guard<std::recursive_mutex> lock(mutex);
|
||||
this->pedalboard = pedalboard;
|
||||
UpdateDefaults(&this->pedalboard);
|
||||
|
||||
this->FirePedalboardChanged(clientId);
|
||||
this->SetPresetChanged(clientId, true);
|
||||
}
|
||||
this->FirePedalboardChanged(clientId);
|
||||
this->SetPresetChanged(clientId, true);
|
||||
}
|
||||
|
||||
void PiPedalModel::SetSnapshot(int64_t selectedSnapshot)
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> lock(mutex);
|
||||
if (this->pedalboard.ApplySnapshot(selectedSnapshot, pluginHost))
|
||||
bool pedalboardChanged = false;
|
||||
{
|
||||
this->pedalboard.selectedSnapshot(selectedSnapshot);
|
||||
for (auto snapshot : this->pedalboard.snapshots())
|
||||
std::lock_guard<std::recursive_mutex> lock(mutex);
|
||||
if (this->pedalboard.ApplySnapshot(selectedSnapshot, pluginHost))
|
||||
{
|
||||
if (snapshot)
|
||||
this->pedalboard.selectedSnapshot(selectedSnapshot);
|
||||
for (auto snapshot : this->pedalboard.snapshots())
|
||||
{
|
||||
snapshot->isModified_ = false;
|
||||
if (snapshot)
|
||||
{
|
||||
snapshot->isModified_ = false;
|
||||
}
|
||||
}
|
||||
pedalboardChanged = true;
|
||||
}
|
||||
}
|
||||
if (pedalboardChanged)
|
||||
{
|
||||
FirePedalboardChanged(-1, true);
|
||||
}
|
||||
}
|
||||
|
||||
void PiPedalModel::SetSnapshots(std::vector<std::shared_ptr<Snapshot>> &snapshots, int64_t selectedSnapshot)
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> lock(mutex);
|
||||
|
||||
UpdateVst3Settings(pedalboard);
|
||||
|
||||
this->pedalboard.snapshots(std::move(snapshots));
|
||||
|
||||
if (selectedSnapshot != -1)
|
||||
{
|
||||
this->pedalboard.selectedSnapshot(selectedSnapshot);
|
||||
for (auto &snapshot : pedalboard.snapshots())
|
||||
std::lock_guard<std::recursive_mutex> lock(mutex);
|
||||
|
||||
UpdateVst3Settings(pedalboard);
|
||||
|
||||
this->pedalboard.snapshots(std::move(snapshots));
|
||||
|
||||
if (selectedSnapshot != -1)
|
||||
{
|
||||
if (snapshot)
|
||||
this->pedalboard.selectedSnapshot(selectedSnapshot);
|
||||
for (auto &snapshot : pedalboard.snapshots())
|
||||
{
|
||||
snapshot->isModified_ = false;
|
||||
if (snapshot)
|
||||
{
|
||||
snapshot->isModified_ = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -745,10 +790,9 @@ void PiPedalModel::UpdateCurrentPedalboard(int64_t clientId, Pedalboard &pedalbo
|
||||
|
||||
UpdateRealtimeVuSubscriptions();
|
||||
UpdateRealtimeMonitorPortSubscriptions();
|
||||
|
||||
this->FirePedalboardChanged(clientId, false);
|
||||
this->SetPresetChanged(clientId, true);
|
||||
}
|
||||
this->FirePedalboardChanged(clientId, false);
|
||||
this->SetPresetChanged(clientId, true);
|
||||
}
|
||||
|
||||
void PiPedalModel::SetPedalboardItemUseModUi(int64_t clientId, int64_t instanceId, bool enabled)
|
||||
@@ -763,14 +807,17 @@ void PiPedalModel::SetPedalboardItemUseModUi(int64_t clientId, int64_t instanceI
|
||||
{
|
||||
subscriber->OnItemUseModUiChanged(clientId, instanceId, enabled);
|
||||
}
|
||||
this->SetPresetChanged(clientId, true);
|
||||
}
|
||||
this->SetPresetChanged(clientId, true);
|
||||
}
|
||||
|
||||
void PiPedalModel::SetPedalboardItemEnable(int64_t clientId, int64_t pedalItemId, bool enabled)
|
||||
{
|
||||
SubscriberList subscribers;
|
||||
std::lock_guard<std::recursive_mutex> guard{mutex};
|
||||
{
|
||||
subscribers = this->subscribers;
|
||||
|
||||
this->pedalboard.SetItemEnabled(pedalItemId, enabled);
|
||||
PedalboardItem *pPedalboardItem = this->pedalboard.GetItem(pedalItemId);
|
||||
if (pPedalboardItem)
|
||||
@@ -787,18 +834,16 @@ void PiPedalModel::SetPedalboardItemEnable(int64_t clientId, int64_t pedalItemId
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Notify clients.
|
||||
std::vector<IPiPedalModelSubscriber::ptr> t{subscribers.begin(), subscribers.end()};
|
||||
for (auto &subscriber : t)
|
||||
{
|
||||
subscriber->OnItemEnabledChanged(clientId, pedalItemId, enabled);
|
||||
}
|
||||
this->SetPresetChanged(clientId, true);
|
||||
|
||||
// Notify audo thread.
|
||||
this->audioHost->SetBypass(pedalItemId, enabled);
|
||||
}
|
||||
this->audioHost->SetBypass(pedalItemId, enabled);
|
||||
|
||||
// Notify clients.
|
||||
for (auto &subscriber : subscribers)
|
||||
{
|
||||
subscriber->OnItemEnabledChanged(clientId, pedalItemId, enabled);
|
||||
}
|
||||
this->SetPresetChanged(clientId, true);
|
||||
}
|
||||
|
||||
void PiPedalModel::GetPresets(PresetIndex *pResult)
|
||||
@@ -845,72 +890,73 @@ void PiPedalModel::SetPresetChanged(int64_t clientId, bool value, bool changeSna
|
||||
|
||||
void PiPedalModel::FireSnapshotModified(int64_t snapshotIndex, bool modified)
|
||||
{
|
||||
SubscriberList subscribers;
|
||||
std::lock_guard<std::recursive_mutex> guard{mutex};
|
||||
{
|
||||
// take a snapshot incase a client unsusbscribes in the notification handler (in which case the mutex won't protect us)
|
||||
std::vector<IPiPedalModelSubscriber::ptr> t{subscribers.begin(), subscribers.end()};
|
||||
for (auto &subscriber : t)
|
||||
{
|
||||
subscriber->OnSnapshotModified(snapshotIndex, modified);
|
||||
}
|
||||
subscribers = this->subscribers;
|
||||
}
|
||||
for (auto &subscriber : subscribers)
|
||||
{
|
||||
subscriber->OnSnapshotModified(snapshotIndex, modified);
|
||||
}
|
||||
}
|
||||
|
||||
void PiPedalModel::FireSelectedSnapshotChanged(int64_t selectedSnapshot)
|
||||
{
|
||||
SubscriberList subscribers;
|
||||
std::lock_guard<std::recursive_mutex> guard{mutex};
|
||||
{
|
||||
subscribers = this->subscribers;
|
||||
// take a snapshot incase a client unsusbscribes in the notification handler (in which case the mutex won't protect us)
|
||||
std::vector<IPiPedalModelSubscriber::ptr> t{subscribers.begin(), subscribers.end()};
|
||||
for (auto &subscriber : t)
|
||||
{
|
||||
subscriber->OnSelectedSnapshotChanged(selectedSnapshot);
|
||||
}
|
||||
}
|
||||
for (auto &subscriber : subscribers)
|
||||
{
|
||||
subscriber->OnSelectedSnapshotChanged(selectedSnapshot);
|
||||
}
|
||||
}
|
||||
|
||||
void PiPedalModel::FirePresetChanged(bool changed)
|
||||
{
|
||||
SubscriberList subscribers;
|
||||
PresetIndex presets;
|
||||
std::lock_guard<std::recursive_mutex> guard{mutex};
|
||||
{
|
||||
subscribers = this->subscribers;
|
||||
// take a snapshot incase a client unsusbscribes in the notification handler (in which case the mutex won't protect us)
|
||||
|
||||
PresetIndex presets;
|
||||
GetPresets(&presets);
|
||||
|
||||
std::vector<IPiPedalModelSubscriber::ptr> t{subscribers.begin(), subscribers.end()};
|
||||
for (auto &subscriber : t)
|
||||
{
|
||||
subscriber->OnPresetChanged(changed);
|
||||
}
|
||||
}
|
||||
for (auto &subscriber : subscribers)
|
||||
{
|
||||
subscriber->OnPresetChanged(changed);
|
||||
}
|
||||
}
|
||||
|
||||
void PiPedalModel::FirePresetsChanged(int64_t clientId)
|
||||
{
|
||||
SubscriberList subscribers;
|
||||
PresetIndex presets;
|
||||
std::lock_guard<std::recursive_mutex> guard{mutex};
|
||||
{
|
||||
|
||||
PresetIndex presets;
|
||||
subscribers = this->subscribers;
|
||||
GetPresets(&presets);
|
||||
|
||||
std::vector<IPiPedalModelSubscriber::ptr> t{subscribers.begin(), subscribers.end()};
|
||||
for (auto &subscriber : t)
|
||||
{
|
||||
subscriber->OnPresetsChanged(clientId, presets);
|
||||
}
|
||||
}
|
||||
for (auto &subscriber : subscribers)
|
||||
{
|
||||
subscriber->OnPresetsChanged(clientId, presets);
|
||||
}
|
||||
}
|
||||
void PiPedalModel::FirePluginPresetsChanged(const std::string &pluginUri)
|
||||
{
|
||||
SubscriberList subscribers;
|
||||
std::lock_guard<std::recursive_mutex> guard{mutex};
|
||||
{
|
||||
subscribers = this->subscribers;
|
||||
// take a snapshot incase a client unsusbscribes in the notification handler (in which case the mutex won't protect us)
|
||||
std::vector<IPiPedalModelSubscriber::ptr> t{subscribers.begin(), subscribers.end()};
|
||||
for (auto &subscriber : t)
|
||||
{
|
||||
subscriber->OnPluginPresetsChanged(pluginUri);
|
||||
}
|
||||
}
|
||||
for (auto &subscriber : subscribers)
|
||||
{
|
||||
subscriber->OnPluginPresetsChanged(pluginUri);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -940,10 +986,13 @@ void PiPedalModel::UpdateVst3Settings(Pedalboard &pedalboard)
|
||||
|
||||
void PiPedalModel::FireLv2StateChanged(int64_t instanceId, const Lv2PluginState &lv2State)
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> guard{mutex};
|
||||
std::vector<IPiPedalModelSubscriber::ptr> t{subscribers.begin(), subscribers.end()};
|
||||
SubscriberList subscribers;
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> guard{mutex};
|
||||
subscribers = this->subscribers;
|
||||
}
|
||||
|
||||
for (auto &subscriber : t)
|
||||
for (auto &subscriber : subscribers)
|
||||
{
|
||||
subscriber->OnLv2StateChanged(instanceId, lv2State);
|
||||
}
|
||||
@@ -1737,12 +1786,15 @@ void PiPedalModel::OnNotifyMidiValueChanged(int64_t instanceId, int portIndex, f
|
||||
|
||||
void PiPedalModel::OnNotifyVusSubscription(const std::vector<VuUpdateX> &updates)
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> lock(mutex);
|
||||
std::vector<IPiPedalModelSubscriber::ptr> subscribers;
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> lock(mutex);
|
||||
subscribers = this->subscribers;
|
||||
}
|
||||
for (size_t i = 0; i < updates.size(); ++i)
|
||||
{
|
||||
// take a snapshot incase a client unsusbscribes in the notification handler (in which case the mutex won't protect us)
|
||||
std::vector<IPiPedalModelSubscriber::ptr> t{subscribers.begin(), subscribers.end()};
|
||||
for (auto &subscriber : t)
|
||||
for (auto &subscriber : subscribers)
|
||||
{
|
||||
subscriber->OnVuMeterUpdate(updates);
|
||||
}
|
||||
@@ -1797,10 +1849,10 @@ int64_t PiPedalModel::MonitorPort(int64_t instanceId, const std::string &key, fl
|
||||
int64_t subscriptionId = ++nextSubscriptionId;
|
||||
activeMonitorPortSubscriptions.push_back(
|
||||
MonitorPortSubscription{
|
||||
subscriptionId,
|
||||
instanceId,
|
||||
key,
|
||||
updateInterval,
|
||||
subscriptionId,
|
||||
instanceId,
|
||||
key,
|
||||
updateInterval,
|
||||
onUpdate});
|
||||
|
||||
UpdateRealtimeMonitorPortSubscriptions();
|
||||
@@ -2513,28 +2565,38 @@ void PiPedalModel::DeleteMidiListeners(int64_t clientId)
|
||||
|
||||
void PiPedalModel::OnPatchSetReply(uint64_t instanceId, LV2_URID patchSetProperty, const LV2_Atom *atomValue)
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> lock(mutex);
|
||||
|
||||
std::string propertyUri = pluginHost.GetMapFeature().UridToString(patchSetProperty);
|
||||
|
||||
std::vector<IPiPedalModelSubscriber::ptr> subscribers;
|
||||
std::vector<AtomOutputListener> atomOutputListeners;
|
||||
std::string propertyUri;
|
||||
{
|
||||
PedalboardItem *item = pedalboard.GetItem((int64_t)instanceId);
|
||||
if (item == nullptr)
|
||||
return;
|
||||
atom_object atomObject{atomValue};
|
||||
std::lock_guard<std::recursive_mutex> lock(mutex);
|
||||
|
||||
PedalboardItem::PropertyMap &properties = item->PatchProperties();
|
||||
if (properties.contains(propertyUri) && properties[propertyUri] == atomObject)
|
||||
{
|
||||
// do noting.
|
||||
}
|
||||
else
|
||||
{
|
||||
properties[propertyUri] = std::move(atomObject);
|
||||
}
|
||||
subscribers = this->subscribers;
|
||||
atomOutputListeners = this->atomOutputListeners;
|
||||
propertyUri = pluginHost.GetMapFeature().UridToString(patchSetProperty);
|
||||
|
||||
OnNotifyMaybeLv2StateChanged(instanceId);
|
||||
{
|
||||
PedalboardItem *item = pedalboard.GetItem((int64_t)instanceId);
|
||||
if (item == nullptr)
|
||||
return;
|
||||
atom_object atomObject{atomValue};
|
||||
|
||||
PedalboardItem::PropertyMap &properties = item->PatchProperties();
|
||||
if (properties.contains(propertyUri) && properties[propertyUri] == atomObject)
|
||||
{
|
||||
// do noting.
|
||||
}
|
||||
else
|
||||
{
|
||||
properties[propertyUri] = std::move(atomObject);
|
||||
}
|
||||
}
|
||||
if (audioHost)
|
||||
{
|
||||
audioHost->SetListenForAtomOutput(atomOutputListeners.size() != 0);
|
||||
}
|
||||
}
|
||||
OnNotifyMaybeLv2StateChanged(instanceId);
|
||||
|
||||
bool hasAtomJson = false;
|
||||
std::string atomJson;
|
||||
@@ -2561,10 +2623,6 @@ void PiPedalModel::OnPatchSetReply(uint64_t instanceId, LV2_URID patchSetPropert
|
||||
}
|
||||
}
|
||||
}
|
||||
if (audioHost)
|
||||
{
|
||||
audioHost->SetListenForAtomOutput(atomOutputListeners.size() != 0);
|
||||
}
|
||||
}
|
||||
|
||||
void PiPedalModel::OnNotifyPathPatchPropertyReceived(
|
||||
@@ -3451,4 +3509,29 @@ std::string PiPedalModel::Tone3000ThumbnailDirectory()
|
||||
return "/var/pipedal/tone3000_thumbnails";
|
||||
}
|
||||
|
||||
void PiPedalModel::EnableUpdater(bool enable)
|
||||
{
|
||||
this->updaterEnabled = enable;
|
||||
}
|
||||
|
||||
static bool IsSafeMediaPath(const fs::path path)
|
||||
{
|
||||
if (!HtmlHelper::IsSafeFileName(path))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (!(path.string().starts_with("/var/pipedal/audio_uploads")))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void PiPedalModel::WriteTone3000Readme(const std::filesystem::path &filePath, const tone3000::Tone &tone, const std::string &thumbnailUrl)
|
||||
{
|
||||
if (!IsSafeMediaPath(filePath))
|
||||
{
|
||||
throw std::runtime_error("Invalid media path.");
|
||||
}
|
||||
tone3000::WriteTone3000Readme(filePath, tone, thumbnailUrl);
|
||||
}
|
||||
|
||||
+11
-11
@@ -44,6 +44,7 @@
|
||||
#include <unordered_map>
|
||||
#include "Tone3000Downloader.hpp"
|
||||
#include "Uri.hpp"
|
||||
#include "Tone3000Tone.hpp"
|
||||
|
||||
namespace pipedal
|
||||
{
|
||||
@@ -94,7 +95,7 @@ namespace pipedal
|
||||
|
||||
// virtual void OnPatchPropertyChanged(int64_t clientId, int64_t instanceId,const std::string& propertyUri,const json_variant& value) = 0;
|
||||
virtual void OnErrorMessage(const std::string &message) = 0;
|
||||
|
||||
|
||||
virtual void OnTone3000DownloadStarted(int64_t handle, const std::string &title) = 0;
|
||||
virtual void OnTone3000DownloadProgress(const Tone3000DownloadProgress &progress) = 0;
|
||||
virtual void OnTone3000DownloadComplete(int64_t handle, const std::string &resultPath) = 0;
|
||||
@@ -119,6 +120,7 @@ namespace pipedal
|
||||
using NetworkChangedListener = std::function<void(void)>;
|
||||
|
||||
private:
|
||||
bool updaterEnabled = true;
|
||||
PedalboardItem *GetPedalboardItemForFileProperty(const UiFileProperty &fileProperty);
|
||||
|
||||
// Tone3000Downloader::Listener implementation
|
||||
@@ -127,7 +129,6 @@ namespace pipedal
|
||||
virtual void OnTone3000DownloadComplete(int64_t handle, const std::string &resultPath) override;
|
||||
virtual void OnTone3000DownloadError(int64_t handle, const std::string &errorMessage) override;
|
||||
|
||||
|
||||
std::shared_ptr<Tone3000Downloader> tone3000Downloader;
|
||||
void CancelAudioRetry();
|
||||
clock::time_point lastRestartTime = clock::time_point::min();
|
||||
@@ -211,7 +212,8 @@ namespace pipedal
|
||||
std::shared_ptr<Lv2Pedalboard> lv2Pedalboard;
|
||||
std::filesystem::path webRoot;
|
||||
|
||||
std::vector<std::shared_ptr<IPiPedalModelSubscriber>> subscribers;
|
||||
using SubscriberList = std::vector<std::shared_ptr<IPiPedalModelSubscriber>>;
|
||||
SubscriberList subscribers;
|
||||
void SetPresetChanged(int64_t clientId, bool value, bool changeSnapshotSelect = true);
|
||||
void FireSnapshotModified(int64_t snapshotIndex, bool modified);
|
||||
void FireSelectedSnapshotChanged(int64_t selectedSnapshot);
|
||||
@@ -296,13 +298,12 @@ namespace pipedal
|
||||
Decrease
|
||||
};
|
||||
|
||||
|
||||
void EnableUpdater(bool enable);
|
||||
|
||||
Storage &GetStorage() { return storage; }
|
||||
|
||||
virtual std::string Tone3000ThumbnailDirectory() override;
|
||||
|
||||
|
||||
bool GetHasWifi();
|
||||
|
||||
void NextBank(Direction direction = Direction::Increase);
|
||||
@@ -311,16 +312,14 @@ namespace pipedal
|
||||
void PreviousPreset() { NextPreset(Direction::Decrease); }
|
||||
|
||||
int64_t DownloadModelsFromTone3000(
|
||||
const std::string&responseuri,
|
||||
const Tone3000PkceParams& pkce,
|
||||
const std::string &responseuri,
|
||||
const Tone3000PkceParams &pkce,
|
||||
const std::string &downloadPath,
|
||||
Tone3000DownloadType downloadType
|
||||
);
|
||||
Tone3000DownloadType downloadType);
|
||||
|
||||
void CancelTone3000Download(
|
||||
int64_t clientId,
|
||||
int64_t downloadHandle
|
||||
);
|
||||
int64_t downloadHandle);
|
||||
void RequestShutdown(bool restart);
|
||||
|
||||
virtual PostHandle Post(PostCallback &&fn);
|
||||
@@ -530,6 +529,7 @@ namespace pipedal
|
||||
|
||||
void SetChannelRouterSettings(int64_t clientId, ChannelRouterSettings::ptr &settings);
|
||||
ChannelRouterSettings::ptr GetChannelRouterSettings();
|
||||
void WriteTone3000Readme(const std::filesystem::path &filePath, const tone3000::Tone &tone, const std::string &thumbnailUrl);
|
||||
};
|
||||
|
||||
} // namespace pipedal.
|
||||
@@ -36,6 +36,7 @@
|
||||
#include "Promise.hpp"
|
||||
#include <mutex>
|
||||
#include "Tone3000Downloader.hpp"
|
||||
#include "Tone3000Tone.hpp"
|
||||
|
||||
#include "AdminClient.hpp"
|
||||
#include "WifiConfigSettings.hpp"
|
||||
@@ -62,6 +63,20 @@ JSON_MAP_REFERENCE(CopyPresetsToBankBody, bankInstanceId)
|
||||
JSON_MAP_REFERENCE(CopyPresetsToBankBody, presets)
|
||||
JSON_MAP_END();
|
||||
|
||||
class WriteTone3000ReadmeBody {
|
||||
public:
|
||||
std::string filePath_;
|
||||
tone3000::Tone tone_;
|
||||
std::string thumbnailUrl_;
|
||||
DECLARE_JSON_MAP(WriteTone3000ReadmeBody);
|
||||
};
|
||||
|
||||
JSON_MAP_BEGIN(WriteTone3000ReadmeBody)
|
||||
JSON_MAP_REFERENCE(WriteTone3000ReadmeBody, filePath)
|
||||
JSON_MAP_REFERENCE(WriteTone3000ReadmeBody, tone)
|
||||
JSON_MAP_REFERENCE(WriteTone3000ReadmeBody, thumbnailUrl)
|
||||
JSON_MAP_END();
|
||||
|
||||
class DownloadModelsFromTone3000Body
|
||||
{
|
||||
public:
|
||||
@@ -1193,6 +1208,15 @@ public:
|
||||
}
|
||||
REGISTER_MESSAGE_HANDLER(makeTone3000Pkce);
|
||||
|
||||
|
||||
void handle_writeTone3000Readme(int replyTo, json_reader*pReader)
|
||||
{
|
||||
WriteTone3000ReadmeBody body;
|
||||
pReader->read(&body);
|
||||
model.WriteTone3000Readme(body.filePath_, body.tone_,body.thumbnailUrl_);
|
||||
}
|
||||
REGISTER_MESSAGE_HANDLER(writeTone3000Readme)
|
||||
|
||||
void handle_sha256Base64url(int replyTo, json_reader *pReader)
|
||||
{
|
||||
std::string input;
|
||||
|
||||
@@ -125,6 +125,7 @@ void PluginHost::LilvUris::Initialize(LilvWorld *pWorld)
|
||||
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);
|
||||
portprops__expensive = lilv_new_uri(pWorld, LV2_PORT_PROPS__expensive);
|
||||
midi__event = lilv_new_uri(pWorld, LV2_MIDI__MidiEvent);
|
||||
core__designation = lilv_new_uri(pWorld, LV2_CORE__designation);
|
||||
portgroups__group = lilv_new_uri(pWorld, LV2_PORT_GROUPS__group);
|
||||
@@ -1265,6 +1266,7 @@ Lv2PortInfo::Lv2PortInfo(PluginHost *host, const LilvPlugin *plugin, const LilvP
|
||||
this->integer_property_ = lilv_port_has_property(plugin, pPort, host->lilvUris->integer_property_uri);
|
||||
this->mod_momentaryOffByDefault_ = lilv_port_has_property(plugin, pPort, host->lilvUris->mod__preferMomentaryOffByDefault);
|
||||
this->mod_momentaryOnByDefault_ = lilv_port_has_property(plugin, pPort, host->lilvUris->mod__preferMomentaryOnByDefault);
|
||||
this->is_expensive_ = lilv_port_has_property(plugin, pPort, host->lilvUris->portprops__expensive);
|
||||
this->pipedal_graphicEq_ = lilv_port_has_property(plugin, pPort, host->lilvUris->pipedalUI__graphicEq);
|
||||
|
||||
this->enumeration_property_ = lilv_port_has_property(plugin, pPort, host->lilvUris->enumeration_property_uri);
|
||||
@@ -2321,6 +2323,7 @@ json_map::storage_type<Lv2PortInfo> Lv2PortInfo::jmap{
|
||||
MAP_REF(Lv2PortInfo, toggled_property),
|
||||
MAP_REF(Lv2PortInfo, mod_momentaryOffByDefault),
|
||||
MAP_REF(Lv2PortInfo, mod_momentaryOnByDefault),
|
||||
MAP_REF(Lv2PortInfo, is_expensive),
|
||||
MAP_REF(Lv2PortInfo, pipedal_graphicEq),
|
||||
MAP_REF(Lv2PortInfo, not_on_gui),
|
||||
MAP_REF(Lv2PortInfo, buffer_type),
|
||||
@@ -2408,6 +2411,7 @@ json_map::storage_type<Lv2PluginUiPort> Lv2PluginUiPort::jmap{{
|
||||
|
||||
MAP_REF(Lv2PluginUiPort, mod_momentaryOffByDefault),
|
||||
MAP_REF(Lv2PluginUiPort, mod_momentaryOnByDefault),
|
||||
MAP_REF(Lv2PluginUiPort, is_expensive),
|
||||
MAP_REF(Lv2PluginUiPort, pipedal_graphicEq),
|
||||
MAP_REF(Lv2PluginUiPort, enumeration_property),
|
||||
MAP_REF(Lv2PluginUiPort, not_on_gui),
|
||||
|
||||
@@ -226,6 +226,7 @@ namespace pipedal
|
||||
|
||||
bool mod_momentaryOffByDefault_ = false;
|
||||
bool mod_momentaryOnByDefault_ = false;
|
||||
bool is_expensive_ = false;
|
||||
bool pipedal_graphicEq_ = false;
|
||||
|
||||
bool not_on_gui_ = false;
|
||||
@@ -304,6 +305,7 @@ namespace pipedal
|
||||
LV2_PROPERTY_GETSET_SCALAR(integer_property);
|
||||
LV2_PROPERTY_GETSET_SCALAR(mod_momentaryOffByDefault);
|
||||
LV2_PROPERTY_GETSET_SCALAR(mod_momentaryOnByDefault);
|
||||
LV2_PROPERTY_GETSET_SCALAR(is_expensive);
|
||||
LV2_PROPERTY_GETSET_SCALAR(pipedal_graphicEq);
|
||||
LV2_PROPERTY_GETSET_SCALAR(enumeration_property);
|
||||
LV2_PROPERTY_GETSET_SCALAR(toggled_property);
|
||||
@@ -591,6 +593,7 @@ namespace pipedal
|
||||
integer_property_(pPort->integer_property()),
|
||||
mod_momentaryOffByDefault_(pPort->mod_momentaryOffByDefault()),
|
||||
mod_momentaryOnByDefault_(pPort->mod_momentaryOnByDefault()),
|
||||
is_expensive_(pPort->is_expensive()),
|
||||
pipedal_graphicEq_(pPort->pipedal_graphicEq()),
|
||||
|
||||
enumeration_property_(pPort->enumeration_property()),
|
||||
@@ -641,6 +644,7 @@ namespace pipedal
|
||||
|
||||
bool mod_momentaryOffByDefault_ = false;
|
||||
bool mod_momentaryOnByDefault_ = false;
|
||||
bool is_expensive_ = false;
|
||||
bool pipedal_graphicEq_ = false;
|
||||
|
||||
bool enumeration_property_ = false;
|
||||
@@ -671,6 +675,10 @@ namespace pipedal
|
||||
LV2_PROPERTY_GETSET_SCALAR(display_priority);
|
||||
LV2_PROPERTY_GETSET_SCALAR(is_logarithmic);
|
||||
LV2_PROPERTY_GETSET_SCALAR(integer_property);
|
||||
LV2_PROPERTY_GETSET_SCALAR(mod_momentaryOffByDefault);
|
||||
LV2_PROPERTY_GETSET_SCALAR(mod_momentaryOnByDefault);
|
||||
LV2_PROPERTY_GETSET_SCALAR(is_expensive);
|
||||
LV2_PROPERTY_GETSET_SCALAR(pipedal_graphicEq);
|
||||
LV2_PROPERTY_GETSET_SCALAR(enumeration_property);
|
||||
LV2_PROPERTY_GETSET_SCALAR(toggled_property);
|
||||
LV2_PROPERTY_GETSET_SCALAR(trigger_property);
|
||||
@@ -798,6 +806,7 @@ namespace pipedal
|
||||
AutoLilvNode core__isSideChain;
|
||||
AutoLilvNode portprops__not_on_gui_property_uri;
|
||||
AutoLilvNode portprops__trigger;
|
||||
AutoLilvNode portprops__expensive;
|
||||
AutoLilvNode midi__event;
|
||||
AutoLilvNode core__designation;
|
||||
AutoLilvNode portgroups__group;
|
||||
|
||||
@@ -0,0 +1,487 @@
|
||||
/*
|
||||
* Copyright (c) 2026 Robin E. R. Davies
|
||||
* All rights reserved.
|
||||
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
#include "Tone3000Tone.hpp"
|
||||
#include "ss.hpp"
|
||||
|
||||
|
||||
|
||||
using namespace pipedal;
|
||||
using namespace pipedal::tone3000;
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
|
||||
Tone::Tone(const std::string &json)
|
||||
{
|
||||
std::istringstream ss(json);
|
||||
json_reader reader(ss);
|
||||
reader.read(this);
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
static std::string mdSanitize(const std::string &str, const std::string &illegalCharacters = "")
|
||||
{
|
||||
std::ostringstream os;
|
||||
for (char c : str)
|
||||
{
|
||||
if ((unsigned char)c < 0x20)
|
||||
continue;
|
||||
if (c == '<')
|
||||
{
|
||||
os << "<";
|
||||
continue;
|
||||
}
|
||||
auto npos = illegalCharacters.find_first_of(c);
|
||||
if (npos != std::string::npos)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
os << c;
|
||||
}
|
||||
return os.str();
|
||||
}
|
||||
|
||||
static std::string mdDate(const tone3000_time_point &date_)
|
||||
{
|
||||
std::ostringstream os;
|
||||
// C++ doesn't handle timezones. Just zap the timezone, and replace it with "Z"
|
||||
std::string date = date_;
|
||||
|
||||
// Remove timezone offset if present and replace with Z
|
||||
size_t plusPos = date.find('+');
|
||||
if (plusPos != std::string::npos)
|
||||
{
|
||||
date = date.substr(0, plusPos) + "Z";
|
||||
}
|
||||
// Parse ISO 8601 date string into tm
|
||||
std::tm tm = {};
|
||||
std::istringstream ss(date);
|
||||
ss >> std::get_time(&tm, "%Y-%m-%dT%H:%M:%S");
|
||||
if (ss.fail())
|
||||
{
|
||||
os << date;
|
||||
return os.str();
|
||||
}
|
||||
|
||||
os << std::put_time(&tm, "%x");
|
||||
return os.str();
|
||||
}
|
||||
|
||||
static std::string mdEnum(const std::string &value, const std::map<std::string, std::string> &enumValues)
|
||||
{
|
||||
auto ff = enumValues.find(value);
|
||||
if (ff == enumValues.end())
|
||||
{
|
||||
return value;
|
||||
}
|
||||
return ff->second;
|
||||
}
|
||||
|
||||
static std::string mdEnumList(
|
||||
const std::vector<std::string> &values,
|
||||
const std::map<std::string, std::string> &enumValues)
|
||||
{
|
||||
if (values.size() == 1)
|
||||
{
|
||||
return mdEnum(values[0], enumValues);
|
||||
}
|
||||
std::ostringstream os;
|
||||
os << '[';
|
||||
bool first = true;
|
||||
for (const auto &value : values)
|
||||
{
|
||||
if (first)
|
||||
{
|
||||
first = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
os << ", ";
|
||||
}
|
||||
os << mdEnum(value, enumValues);
|
||||
}
|
||||
os << ']';
|
||||
return os.str();
|
||||
}
|
||||
|
||||
static std::map<std::string, std::string> sizeEnumValues =
|
||||
{
|
||||
{"standard", "Standard"},
|
||||
{"lite", "Lite"},
|
||||
{"feather", "Feather"},
|
||||
{"nano", "Nano"},
|
||||
{"custom", "Custom"}};
|
||||
static std::string mdSizes(const std::vector<std::string> &sizes)
|
||||
{
|
||||
return mdEnumList(sizes, sizeEnumValues);
|
||||
}
|
||||
|
||||
static std::map<std::string, std::string> gearEnumValues =
|
||||
{
|
||||
{"amp", "Amp only"},
|
||||
{"full-rig", "Full rig"},
|
||||
{"pedal", "Pedal"},
|
||||
{"outboard", "Outboard"},
|
||||
{"ir", "I/R"}};
|
||||
|
||||
static std::string mdGear(const std::string &gear)
|
||||
{
|
||||
return mdEnum(gear, gearEnumValues);
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
enum class LicenseFlags
|
||||
{
|
||||
None = 0,
|
||||
Cc = 1,
|
||||
By = 2,
|
||||
CcBy = 3, // = Cc | By
|
||||
Sa = 4,
|
||||
Nc = 8,
|
||||
Nd = 16,
|
||||
Cc0 = 32,
|
||||
};
|
||||
|
||||
static bool operator&(LicenseFlags v1, LicenseFlags v2)
|
||||
{
|
||||
return (((int)v1) & ((int)v2)) != 0;
|
||||
}
|
||||
static LicenseFlags operator|(LicenseFlags v1, LicenseFlags v2)
|
||||
{
|
||||
return (LicenseFlags)(((int)(v1)) | ((int)(v2)));
|
||||
}
|
||||
|
||||
struct LicenseInfo
|
||||
{
|
||||
std::string key;
|
||||
std::string displayName;
|
||||
std::string url;
|
||||
LicenseFlags licenseFlags;
|
||||
};
|
||||
}
|
||||
|
||||
static std::vector<LicenseInfo> licenses{
|
||||
{.key = "t3k",
|
||||
.displayName = "T3K",
|
||||
.url = "licenses/t3k_license.html",
|
||||
.licenseFlags = LicenseFlags::None},
|
||||
{.key = "cc-by",
|
||||
.displayName = "CC BY 4.0",
|
||||
.url = "https://creativecommons.org/licenses/by/4.0/",
|
||||
.licenseFlags = LicenseFlags::CcBy},
|
||||
{.key = "cc-by-sa",
|
||||
.displayName = "CC BY-SA 4.0",
|
||||
.url = "https://creativecommons.org/licenses/by-sa/4.0/",
|
||||
.licenseFlags = LicenseFlags::CcBy | LicenseFlags::Sa},
|
||||
{.key = "cc-by-nc",
|
||||
.displayName = "CC BY-NC 4.0",
|
||||
.url = "https://creativecommons.org/licenses/by-nc/4.0/",
|
||||
.licenseFlags = LicenseFlags::CcBy | LicenseFlags::Nc},
|
||||
{.key = "cc-by-nc-sa",
|
||||
.displayName = "CC BY-NC-SA 4.0",
|
||||
.url = "https://creativecommons.org/licenses/by-nc-sa/4.0/",
|
||||
.licenseFlags = LicenseFlags::CcBy | LicenseFlags::Nc | LicenseFlags::Sa},
|
||||
{.key = "cc-by-nd",
|
||||
.displayName = "CC BY-ND 4.0",
|
||||
.url = "https://creativecommons.org/licenses/by-nd/4.0/",
|
||||
.licenseFlags = LicenseFlags::CcBy | LicenseFlags::Nd},
|
||||
{.key = "cc-by-nc-nd",
|
||||
.displayName = "CC BY-NC-ND 4.0",
|
||||
.url = "https://creativecommons.org/licenses/by-nc-nd/4.0/",
|
||||
.licenseFlags = LicenseFlags::CcBy | LicenseFlags::Nc | LicenseFlags::Nd},
|
||||
{
|
||||
.key = "cco",
|
||||
.displayName = "CC0",
|
||||
.url = "https://creativecommons.org/publicdomain/zero/1.0/",
|
||||
.licenseFlags = LicenseFlags::Cc | LicenseFlags::Cc0,
|
||||
|
||||
},
|
||||
};
|
||||
|
||||
static std::map<std::string, LicenseInfo> makeLicenseEnum()
|
||||
{
|
||||
std::map<std::string, LicenseInfo> result;
|
||||
for (const auto &license : licenses)
|
||||
{
|
||||
result[license.key] = license;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static std::map<std::string, LicenseInfo> licenseEnum = makeLicenseEnum();
|
||||
|
||||
static std::string mdLicenseIcon(LicenseFlags licenseFlag)
|
||||
{
|
||||
std::ostringstream os;
|
||||
std::string url;
|
||||
|
||||
switch (licenseFlag)
|
||||
{
|
||||
case LicenseFlags::Cc:
|
||||
url = "img/cc.svg";
|
||||
break;
|
||||
case LicenseFlags::By:
|
||||
url = "img/by.svg";
|
||||
break;
|
||||
case LicenseFlags::Sa:
|
||||
url = "img/sa.svg";
|
||||
break;
|
||||
case LicenseFlags::Nc:
|
||||
url = "img/nc.svg";
|
||||
break;
|
||||
case LicenseFlags::Nd:
|
||||
url = "img/nd.svg";
|
||||
break;
|
||||
case LicenseFlags::Cc0:
|
||||
url = "img/cc0.svg";
|
||||
break;
|
||||
|
||||
case LicenseFlags::None:
|
||||
throw std::runtime_error("Invalid argument.");
|
||||
}
|
||||
os << "<img id='cc_img' src='" << url << "'/>";
|
||||
return os.str();
|
||||
}
|
||||
|
||||
static void mdEscapeString(std::ostream &f, const std::string &str)
|
||||
{
|
||||
size_t ix = 0;
|
||||
while (ix < str.size())
|
||||
{
|
||||
char c = str[ix++];
|
||||
if (c == '\n')
|
||||
{
|
||||
if (ix < str.size() && str[ix] == '\n')
|
||||
{
|
||||
f << "\n\n";
|
||||
}
|
||||
else
|
||||
{
|
||||
f << " \n"; // a <br> in md.
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
f << c;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static std::string mdHref(const std::string &url)
|
||||
{
|
||||
std::ostringstream os;
|
||||
os << '\'';
|
||||
for (char c : url)
|
||||
{
|
||||
if (c == '\'')
|
||||
{
|
||||
continue;
|
||||
}
|
||||
os << c;
|
||||
}
|
||||
os << '\'';
|
||||
return os.str();
|
||||
}
|
||||
static std::string mdLicense(const std::string &license)
|
||||
{
|
||||
|
||||
auto ff = licenseEnum.find(license);
|
||||
LicenseInfo licenseInfo;
|
||||
if (ff != licenseEnum.end())
|
||||
{
|
||||
licenseInfo = ff->second;
|
||||
}
|
||||
else
|
||||
{
|
||||
licenseInfo.displayName = license;
|
||||
}
|
||||
|
||||
std::ostringstream os;
|
||||
|
||||
if (licenseInfo.licenseFlags != LicenseFlags::None)
|
||||
{
|
||||
for (LicenseFlags licenseFlag : std::vector<LicenseFlags>{LicenseFlags::Cc, LicenseFlags::By, LicenseFlags::Cc0, LicenseFlags::Nc, LicenseFlags::Sa, LicenseFlags::Nd})
|
||||
{
|
||||
if (licenseInfo.licenseFlags & licenseFlag)
|
||||
{
|
||||
os << mdLicenseIcon(licenseFlag);
|
||||
}
|
||||
}
|
||||
os << " ";
|
||||
}
|
||||
|
||||
if (licenseInfo.url != "")
|
||||
{
|
||||
os << "<a href="
|
||||
<< mdHref(licenseInfo.url)
|
||||
<< " target='_blank' rel='noopener noreferrer' >" << mdSanitize(licenseInfo.displayName) << "</a>";
|
||||
}
|
||||
else
|
||||
{
|
||||
os << mdSanitize(licenseInfo.displayName);
|
||||
}
|
||||
return os.str();
|
||||
}
|
||||
// static std::string mdTestLicenses()
|
||||
// {
|
||||
// std::ostringstream os;
|
||||
// for (auto license: licenseEnum)
|
||||
// {
|
||||
// os << "<br/>" << mdLicense(license.first) << "\n";
|
||||
// }
|
||||
// return os.str();
|
||||
// }
|
||||
static std::map<std::string, std::string> platformEnumValues =
|
||||
{
|
||||
{"nam", "NAM"},
|
||||
{"ir", "I/R"},
|
||||
{"aida-x", "Aida X"},
|
||||
{"aa-snapshot", "aa-snapshot"},
|
||||
{"proteus", "Proteus"}};
|
||||
static std::string mdPlatform(const std::string &platform)
|
||||
{
|
||||
return mdEnum(platform, platformEnumValues);
|
||||
}
|
||||
static std::string mdUser(const tone3000::User &user)
|
||||
{
|
||||
return mdSanitize(user.username());
|
||||
}
|
||||
|
||||
static void mdLink(std::ostream &f, const std::string &label, const std::string &url)
|
||||
{
|
||||
f << "[" << mdSanitize(label, "]") << "](" << mdSanitize(url, ")") << ")";
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
void pipedal::tone3000::WriteTone3000Readme(const std::filesystem::path &filePath, const tone3000::Tone &tone, const std::string &thumbnailUrl_)
|
||||
{
|
||||
std::string thumbnailUrl = SS("/var/tone3000_thumbnail?id=" << tone.id());
|
||||
std::ofstream of{filePath};
|
||||
if (!of.is_open())
|
||||
{
|
||||
throw std::runtime_error(SS("Unable to create file " << filePath));
|
||||
}
|
||||
|
||||
// clang-format off
|
||||
of <<
|
||||
|
||||
"<div id='tone3000_float' >\n";
|
||||
if (!thumbnailUrl.empty())
|
||||
{
|
||||
|
||||
of << " <img id='tone3000_thumbnail' src='"
|
||||
<< thumbnailUrl << "' alt='' />\n";
|
||||
}
|
||||
of << " <div id='tone3000_float_content' >\n"
|
||||
<< " <h3 id='tone3000_title'>" << mdSanitize(tone.title()) << "</h3>\n"
|
||||
<< " <div>\n"
|
||||
<< " <p id='tone3000_subtitle'>\n"
|
||||
<< mdDate(tone.updated_at())
|
||||
<< " "
|
||||
<< mdUser(tone.user())
|
||||
<< " <br/>\n"
|
||||
<< " ";
|
||||
if (tone.sizes().has_value() && tone.sizes().value().size() != 0) {
|
||||
of
|
||||
<< mdSizes(tone.sizes().value()) << ", "
|
||||
;
|
||||
}
|
||||
if (tone.platform() == "ir")
|
||||
{
|
||||
of
|
||||
<< (mdPlatform(tone.platform()));
|
||||
} else {
|
||||
of
|
||||
<< mdGear(tone.gear()) << ", "
|
||||
<< mdPlatform(tone.platform());
|
||||
}
|
||||
of << "<br/>\n";
|
||||
if (tone.license() != "") {
|
||||
of << " License: " << mdLicense(tone.license()) << "\n";
|
||||
}
|
||||
of
|
||||
// << " " << mdTestLicenses()
|
||||
<< " </p>\n"
|
||||
<< " </div>\n"
|
||||
<< " </div>\n"
|
||||
<< "</div>\n";
|
||||
// clang-format on
|
||||
of << "\n\n";
|
||||
mdEscapeString(of, tone.description());
|
||||
|
||||
if (tone.links().size() > 0)
|
||||
{
|
||||
of << std::endl;
|
||||
of << "## Links" << std::endl
|
||||
<< std::endl;
|
||||
for (auto &link : tone.links())
|
||||
{
|
||||
of << "- " << "[" << link << "](" << link << ")" << std::endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
JSON_MAP_BEGIN(Model)
|
||||
JSON_MAP_REFERENCE(Model, id)
|
||||
JSON_MAP_REFERENCE(Model, name)
|
||||
JSON_MAP_REFERENCE(Model, model_url)
|
||||
JSON_MAP_REFERENCE(Model, created_at)
|
||||
JSON_MAP_REFERENCE(Model, size)
|
||||
JSON_MAP_REFERENCE(Model, user_id)
|
||||
JSON_MAP_END()
|
||||
|
||||
JSON_MAP_BEGIN(User)
|
||||
JSON_MAP_REFERENCE(User, id)
|
||||
JSON_MAP_REFERENCE(User, username)
|
||||
JSON_MAP_REFERENCE(User, avatar_url)
|
||||
JSON_MAP_REFERENCE(User, url)
|
||||
JSON_MAP_END()
|
||||
|
||||
JSON_MAP_BEGIN(Tone)
|
||||
JSON_MAP_REFERENCE(Tone, id)
|
||||
JSON_MAP_REFERENCE(Tone, user_id)
|
||||
JSON_MAP_REFERENCE(Tone, title)
|
||||
JSON_MAP_REFERENCE(Tone, description)
|
||||
JSON_MAP_REFERENCE(Tone, created_at)
|
||||
JSON_MAP_REFERENCE(Tone, updated_at)
|
||||
JSON_MAP_REFERENCE(Tone, gear)
|
||||
JSON_MAP_REFERENCE(Tone, images)
|
||||
JSON_MAP_REFERENCE(Tone, is_public)
|
||||
JSON_MAP_REFERENCE(Tone, links)
|
||||
JSON_MAP_REFERENCE(Tone, platform)
|
||||
JSON_MAP_REFERENCE(Tone, models_count)
|
||||
JSON_MAP_REFERENCE(Tone, favorites_count)
|
||||
JSON_MAP_REFERENCE(Tone, downloads_count)
|
||||
JSON_MAP_REFERENCE(Tone, license)
|
||||
JSON_MAP_REFERENCE(Tone, sizes)
|
||||
JSON_MAP_REFERENCE(Tone, a1_models_count)
|
||||
JSON_MAP_REFERENCE(Tone, a2_models_count)
|
||||
JSON_MAP_REFERENCE(Tone, irs_count)
|
||||
JSON_MAP_REFERENCE(Tone, custom_models_count)
|
||||
JSON_MAP_REFERENCE(Tone, user)
|
||||
JSON_MAP_REFERENCE(Tone, url)
|
||||
JSON_MAP_REFERENCE(Tone, user)
|
||||
JSON_MAP_END();
|
||||
@@ -0,0 +1,141 @@
|
||||
/*
|
||||
* Copyright (c) 2026 Robin E. R. Davies
|
||||
* All rights reserved.
|
||||
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <optional>
|
||||
#include "json.hpp"
|
||||
#include <filesystem>
|
||||
#include <chrono>
|
||||
#include <functional>
|
||||
#include "Tone3000DownloadProgress.hpp"
|
||||
#include <memory>
|
||||
|
||||
namespace pipedal
|
||||
{
|
||||
namespace tone3000
|
||||
{
|
||||
|
||||
using tone3000_time_point = std::string; // Date in ISO 8601 format. (C++ doesn't handle timezones)
|
||||
|
||||
class Model
|
||||
{
|
||||
private:
|
||||
int64_t id_ = -1;
|
||||
std::string name_;
|
||||
std::string model_url_;
|
||||
tone3000_time_point created_at_;
|
||||
std::optional<std::string> size_;
|
||||
std::string user_id_;
|
||||
|
||||
public:
|
||||
JSON_GETTER_SETTER(id)
|
||||
JSON_GETTER_SETTER_REF(name)
|
||||
JSON_GETTER_SETTER_REF(model_url)
|
||||
JSON_GETTER_SETTER_REF(created_at)
|
||||
JSON_GETTER_SETTER_REF(size)
|
||||
JSON_GETTER_SETTER_REF(user_id)
|
||||
|
||||
DECLARE_JSON_MAP(Model);
|
||||
};
|
||||
|
||||
class User
|
||||
{
|
||||
private:
|
||||
std::string id_;
|
||||
std::optional<std::string> avatar_url_;
|
||||
std::string username_;
|
||||
std::string url_;
|
||||
|
||||
public:
|
||||
JSON_GETTER_SETTER_REF(id)
|
||||
JSON_GETTER_SETTER(username)
|
||||
JSON_GETTER_SETTER(url)
|
||||
|
||||
DECLARE_JSON_MAP(User);
|
||||
};
|
||||
|
||||
class Tone
|
||||
{
|
||||
private:
|
||||
int64_t id_ = -1;
|
||||
std::string user_id_;
|
||||
std::string title_;
|
||||
std::string description_;
|
||||
tone3000_time_point created_at_;
|
||||
tone3000_time_point updated_at_;
|
||||
std::string gear_;
|
||||
std::vector<std::string> images_;
|
||||
bool is_public_ = true;
|
||||
std::vector<std::string> links_;
|
||||
std::string platform_;
|
||||
int64_t models_count_ = 0;
|
||||
int64_t favorites_count_ = 0;
|
||||
int64_t downloads_count_ = 0;
|
||||
std::string license_;
|
||||
std::optional<std::vector<std::string>> sizes_;
|
||||
int64_t a1_models_count_ = 0;
|
||||
int64_t a2_models_count_ = 0;
|
||||
int64_t irs_count_ = 0;
|
||||
int64_t custom_models_count_ = 0;
|
||||
User user_;
|
||||
std::string url_;
|
||||
|
||||
public:
|
||||
Tone() = default;
|
||||
Tone(const std::string &json);
|
||||
|
||||
JSON_GETTER_SETTER(id)
|
||||
JSON_GETTER_SETTER_REF(user_id)
|
||||
JSON_GETTER_SETTER_REF(title)
|
||||
JSON_GETTER_SETTER_REF(description)
|
||||
JSON_GETTER_SETTER_REF(created_at)
|
||||
JSON_GETTER_SETTER_REF(updated_at)
|
||||
JSON_GETTER_SETTER_REF(gear)
|
||||
JSON_GETTER_SETTER_REF(images)
|
||||
JSON_GETTER_SETTER(is_public)
|
||||
JSON_GETTER_SETTER_REF(links)
|
||||
JSON_GETTER_SETTER_REF(platform)
|
||||
JSON_GETTER_SETTER(models_count)
|
||||
JSON_GETTER_SETTER(favorites_count)
|
||||
JSON_GETTER_SETTER(downloads_count)
|
||||
JSON_GETTER_SETTER_REF(license)
|
||||
JSON_GETTER_SETTER_REF(sizes)
|
||||
JSON_GETTER_SETTER_REF(a1_models_count)
|
||||
JSON_GETTER_SETTER_REF(a2_models_count)
|
||||
JSON_GETTER_SETTER_REF(irs_count)
|
||||
JSON_GETTER_SETTER_REF(custom_models_count)
|
||||
JSON_GETTER_SETTER_REF(user)
|
||||
JSON_GETTER_SETTER_REF(url)
|
||||
|
||||
DECLARE_JSON_MAP(Tone);
|
||||
};
|
||||
|
||||
void WriteTone3000Readme(const std::filesystem::path &filePath, const tone3000::Tone &tone, const std::string &thumbnailUrl);
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
+22
-2
@@ -86,7 +86,23 @@ static bool IsSafeMediaPath(const fs::path path)
|
||||
if (!HtmlHelper::IsSafeFileName(path)) {
|
||||
return false;
|
||||
}
|
||||
if (!(path.string().starts_with("/var/pipedal/audio_uploads")))
|
||||
if (!(path.string().starts_with("/var/pipedal/audio_uploads/")))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
static bool IsSafeThumbnailPath(const fs::path path)
|
||||
{
|
||||
if (!HtmlHelper::IsSafeFileName(path)) {
|
||||
return false;
|
||||
}
|
||||
if (!(path.string().starts_with("/var/pipedal/tone3000_thumbnails/")))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
auto extension = path.extension();
|
||||
if (extension != ".jpg" && extension != ".jpeg" && extension != ".png" && extension != ".webm")
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -383,6 +399,10 @@ public:
|
||||
{
|
||||
throw PiPedalException("Can't download files of this type.");
|
||||
}
|
||||
if (!mimeType.starts_with("image/"))
|
||||
{
|
||||
throw PiPedalException("Can't download files of this type.");
|
||||
}
|
||||
res.set(HttpField::content_type, mimeType);
|
||||
size_t contentLength = std::filesystem::file_size(path);
|
||||
res.setContentLength(contentLength);
|
||||
@@ -984,7 +1004,7 @@ public:
|
||||
{
|
||||
throw std::runtime_error("Invalid path");
|
||||
}
|
||||
if (!IsSafeMediaPath(targetPath))
|
||||
if (!IsSafeMediaPath(targetPath) && !IsSafeThumbnailPath(targetPath))
|
||||
{
|
||||
throw std::runtime_error("Unsafe path.");
|
||||
}
|
||||
|
||||
@@ -132,7 +132,9 @@ void profilePlugin(const ProfileOptions &profileOptions)
|
||||
throw std::runtime_error(s.str());
|
||||
}
|
||||
|
||||
model.EnableUpdater(false);
|
||||
model.Init(configuration);
|
||||
model.GetPluginHost().asIHost()->SetMaxAudioBufferSize(64);
|
||||
|
||||
model.LoadLv2PluginInfo();
|
||||
|
||||
|
||||
@@ -7,6 +7,6 @@
|
||||
"ui_plugins": [],
|
||||
"enable_auto_update": true,
|
||||
"has_wifi_device": false,
|
||||
"tone3000_A2_models": false,
|
||||
"tone3000_A2_models": true,
|
||||
"end": true
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
// Copyright (c) 2022 Robin Davies
|
||||
// Copyright (c) Fulgencio Ruiz Rubio.
|
||||
//
|
||||
// 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
|
||||
|
||||
@@ -572,8 +572,12 @@ export default withStyles(
|
||||
return;
|
||||
}
|
||||
if (resultPath.startsWith(this.state.navDirectory)) {
|
||||
let selectedFile = resultPath;
|
||||
if (selectedFile.endsWith("/")) {
|
||||
selectedFile = selectedFile.slice(0, -1);
|
||||
}
|
||||
this.setState({
|
||||
selectedFile: resultPath,
|
||||
selectedFile: selectedFile,
|
||||
});
|
||||
this.requestFiles(this.state.navDirectory);
|
||||
}
|
||||
|
||||
@@ -593,6 +593,7 @@ export class UiControl implements Deserializable<UiControl> {
|
||||
this.max_value = input.max_value;
|
||||
this.default_value = input.default_value;
|
||||
this.is_logarithmic = input.is_logarithmic;
|
||||
this.is_expensive = input.is_expensive;
|
||||
this.display_priority = input.display_priority;
|
||||
this.range_steps = input.range_steps;
|
||||
this.integer_property = input.integer_property;
|
||||
@@ -715,6 +716,7 @@ export class UiControl implements Deserializable<UiControl> {
|
||||
integer_property: boolean = false;
|
||||
mod_momentaryOffByDefault: boolean = false;
|
||||
mod_momentaryOnByDefault: boolean = false;
|
||||
is_expensive: boolean = false;
|
||||
pipedal_graphicEq: boolean = false;
|
||||
enumeration_property: boolean = false;
|
||||
trigger_property: boolean = false;
|
||||
|
||||
@@ -427,7 +427,6 @@ export const MainPage =
|
||||
let title = "";
|
||||
let author = "";
|
||||
let infoPluginUri = "";
|
||||
let presetsUri = "";
|
||||
let missing = false;
|
||||
let canEditTitle = false;
|
||||
let modGuiButtonVisible = false;
|
||||
@@ -439,7 +438,6 @@ export const MainPage =
|
||||
} else if (pedalboardItem.isSyntheticItem()) {
|
||||
title = pedalboardItem.pluginName ?? "#error";
|
||||
author = "";
|
||||
presetsUri = "";
|
||||
infoPluginUri = "";
|
||||
}
|
||||
else {
|
||||
@@ -456,7 +454,6 @@ export const MainPage =
|
||||
title = uiPlugin.name;
|
||||
author = uiPlugin.author_name;
|
||||
}
|
||||
presetsUri = uiPlugin.uri;
|
||||
// if (uiPlugin.description.length > 20) {
|
||||
// }
|
||||
infoPluginUri = uiPlugin.uri;
|
||||
@@ -544,10 +541,6 @@ export const MainPage =
|
||||
<div style={{ flex: "0 0 auto", verticalAlign: "center" }}>
|
||||
<PluginInfoDialog plugin_uri={infoPluginUri} />
|
||||
</div>
|
||||
<div style={{ flex: "0 0 auto" }}>
|
||||
<PluginPresetSelector pluginUri={presetsUri} instanceId={pedalboardItem?.instanceId ?? 0}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{modGuiButtonVisible && (
|
||||
<div style={{ flex: "0 0 auto" }}>
|
||||
@@ -634,6 +627,7 @@ export const MainPage =
|
||||
canShowModUi = this.canShowModUi(pedalboardItem);
|
||||
canDelete = pedalboard.canDeleteItem(pedalboardItem.instanceId);
|
||||
instanceId = pedalboardItem.instanceId;
|
||||
pluginUri = pedalboardItem.uri;
|
||||
if (pedalboardItem.isEmpty()) {
|
||||
canInsert = true;
|
||||
canAppend = true;
|
||||
@@ -699,7 +693,12 @@ export const MainPage =
|
||||
</div>
|
||||
{this.props.enableStructureEditing && (
|
||||
<div style={{ flex: "0 0 auto", display: "flex", flexFlow: "row nowrap", alignItems: "center" }}>
|
||||
<div style={{ flex: "0 0 auto", display: (canInsert || canAppend) ? "block" : "none", paddingRight: 8 }}>
|
||||
<div style={{ flex: "0 0 auto" }}>
|
||||
<PluginPresetSelector pedalboardItem={pedalboardItem} instanceId={pedalboardItem?.instanceId ?? 0}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div style={{ flex: "0 0 auto", display: (canInsert || canAppend) ? "block" : "none" }}>
|
||||
<IconButtonEx tooltip="Add pedal slot" onClick={(e) => { this.onAddClick(e) }} size="large">
|
||||
<AddIcon style={{ height: 24, width: 24, fill: this.props.theme.palette.text.primary, opacity: 0.6 }} />
|
||||
</IconButtonEx>
|
||||
@@ -718,7 +717,7 @@ export const MainPage =
|
||||
{canAppend && (<MenuItem onClick={() => this.onAppendSplit(instanceId)}>Append split</MenuItem>)}
|
||||
</Menu>
|
||||
</div>
|
||||
<div style={{ flex: "0 0 auto", display: canDelete ? "block" : "none", paddingRight: 8 }}>
|
||||
<div style={{ flex: "0 0 auto", display: canDelete ? "block" : "none", }}>
|
||||
<IconButtonEx tooltip="Delete pedal"
|
||||
onClick={() => { this.onDeletePedal(pedalboardItem?.instanceId ?? -1) }}
|
||||
size="large">
|
||||
|
||||
@@ -59,6 +59,9 @@ export class ControlValue implements Deserializable<ControlValue> {
|
||||
}
|
||||
|
||||
export class PedalboardItem implements Deserializable<PedalboardItem> {
|
||||
clone() {
|
||||
return new PedalboardItem().deserialize(this);
|
||||
}
|
||||
deserializePedalboardItem(input: any): PedalboardItem {
|
||||
this.instanceId = input.instanceId ?? -1;
|
||||
this.title = input.title ?? "";
|
||||
@@ -753,11 +756,14 @@ export class Pedalboard implements Deserializable<Pedalboard> {
|
||||
|
||||
replaceItem(instanceId: number, newItem: PedalboardItem)
|
||||
{
|
||||
newItem.instanceId = ++this.nextInstanceId;
|
||||
|
||||
let result = this._replaceItem(this.items,instanceId,newItem);
|
||||
if (!result)
|
||||
{
|
||||
throw new PiPedalArgumentError("instanceId not found.");
|
||||
}
|
||||
return newItem.instanceId;
|
||||
}
|
||||
private _addItem(items: PedalboardItem[], newItem: PedalboardItem, instanceId: number, append: boolean)
|
||||
{
|
||||
|
||||
@@ -50,6 +50,7 @@ import { AlsaSequencerConfiguration, AlsaSequencerPortSelection } from './AlsaSe
|
||||
import { getDefaultModGuiPreference } from './ModGuiHost';
|
||||
import ChannelRouterSettings from './ChannelRouterSettings';
|
||||
import Tone3000DownloadProgress from './Tone3000DownloadProgress';
|
||||
import { Tone } from './t3k/types';
|
||||
|
||||
|
||||
export enum State {
|
||||
@@ -1331,6 +1332,23 @@ export class PiPedalModel //implements PiPedalModel
|
||||
}
|
||||
}
|
||||
|
||||
async writeTone3000Readme(
|
||||
filePath: string,
|
||||
tone: Tone,
|
||||
thumbnailUrl: string
|
||||
|
||||
): Promise<void> {
|
||||
if (!this.webSocket) {
|
||||
throw new Error("Server disconnected.");
|
||||
}
|
||||
await this.webSocket.request<void>(
|
||||
"writeTone3000Readme",
|
||||
{
|
||||
filePath: filePath,
|
||||
tone: tone,
|
||||
thumbnailUrl: thumbnailUrl
|
||||
});
|
||||
}
|
||||
|
||||
maxFileUploadSize: number = 512 * 1024 * 1024;
|
||||
maxPresetUploadSize: number = 1024 * 1024;
|
||||
@@ -2058,7 +2076,10 @@ export class PiPedalModel //implements PiPedalModel
|
||||
previewPedalboardValue(instanceId: number, key: string, value: number): void {
|
||||
// mouse is down. Don't update EVERYBODY, but we must change
|
||||
// the control on the running audio plugin.
|
||||
// TODO: respect "expensive" port attribute.
|
||||
|
||||
// respect "expensive" port attribute.
|
||||
|
||||
// Handle special cases for input/output volume
|
||||
if (instanceId === Pedalboard.START_CONTROL_ID && key === "volume_db") {
|
||||
this.previewInputVolume(value);
|
||||
return;
|
||||
@@ -2067,6 +2088,23 @@ export class PiPedalModel //implements PiPedalModel
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the control info to check if it's expensive
|
||||
let pedalboard = this.pedalboard.get();
|
||||
if (pedalboard) {
|
||||
let item = pedalboard.tryGetItem(instanceId);
|
||||
if (item) {
|
||||
let plugin = this.getUiPlugin(item.uri);
|
||||
if (plugin) {
|
||||
let control = plugin.getControl(key);
|
||||
if (control && control.is_expensive) {
|
||||
// Don't send preview for expensive controls
|
||||
// The final value will be sent when the control is committed
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this._setServerControl("previewControl", instanceId, key, value);
|
||||
}
|
||||
|
||||
@@ -2086,6 +2124,23 @@ export class PiPedalModel //implements PiPedalModel
|
||||
return result;
|
||||
|
||||
|
||||
}
|
||||
replacePedalboarditem(instanceId: number, newItem: PedalboardItem): void {
|
||||
let pedalboard = this.pedalboard.get();
|
||||
let newPedalboard = pedalboard.clone();
|
||||
|
||||
this.updateVst3State(newPedalboard);
|
||||
|
||||
let fromItem = newPedalboard.getItem(instanceId);
|
||||
if (fromItem === null) {
|
||||
throw new PiPedalArgumentError("fromInstanceId not found.");
|
||||
}
|
||||
|
||||
let newInstanceId = newPedalboard.replaceItem(instanceId, newItem.clone());
|
||||
newPedalboard.selectedPlugin = newInstanceId;
|
||||
this.setModelPedalboard(newPedalboard);
|
||||
this.updateServerPedalboard();
|
||||
|
||||
}
|
||||
movePedalboardItemBefore(fromInstanceId: number, toInstanceId: number): void {
|
||||
if (fromInstanceId === toInstanceId) return;
|
||||
|
||||
@@ -153,6 +153,11 @@ function makeControls(plugin: UiPlugin, controlHeadeClass: string) {
|
||||
if (portGroup !== lastPortGroup) {
|
||||
if (portGroup !== null)
|
||||
{
|
||||
// Hide Tone port group on Toob NAM.
|
||||
if (portGroup.uri === 'http://two-play.com/plugins/toob-nam#eqGroup')
|
||||
{
|
||||
continue;
|
||||
}
|
||||
trs.push((
|
||||
<tr>
|
||||
<td className={controlHeadeClass} style={{ verticalAlign: "top", paddingTop: 8 }} colSpan={2}>
|
||||
|
||||
@@ -23,29 +23,30 @@ import { PiPedalModel, PiPedalModelFactory, PluginPresetsChangedHandle } from '.
|
||||
import { Theme } from '@mui/material/styles';
|
||||
import WithStyles from './WithStyles';
|
||||
import { withStyles } from "tss-react/mui";
|
||||
import {createStyles} from './WithStyles';
|
||||
import { createStyles } from './WithStyles';
|
||||
|
||||
import PluginPresetsDialog from './PluginPresetsDialog';
|
||||
import Menu from '@mui/material/Menu';
|
||||
import MenuItem from '@mui/material/MenuItem';
|
||||
import Fade from '@mui/material/Fade';
|
||||
import RenameDialog from './RenameDialog'
|
||||
import {PluginUiPresets} from './PluginPreset';
|
||||
import { PluginUiPresets } from './PluginPreset';
|
||||
|
||||
import Divider from "@mui/material/Divider";
|
||||
|
||||
import PluginPresetsIcon from "./svg/ic_pluginpreset.svg?react";
|
||||
import PluginPresetIcon from "./svg/ic_pluginpreset2.svg?react";
|
||||
import { Pedalboard, PedalboardItem } from './Pedalboard';
|
||||
|
||||
interface PluginPresetSelectorProps extends WithStyles<typeof styles> {
|
||||
pluginUri?: string;
|
||||
pedalboardItem: PedalboardItem | null;
|
||||
instanceId: number;
|
||||
}
|
||||
|
||||
|
||||
interface PluginPresetSelectorState {
|
||||
presets : PluginUiPresets;
|
||||
enabled: boolean;
|
||||
presets: PluginUiPresets;
|
||||
//enabled: boolean;
|
||||
presetChanged: boolean;
|
||||
showPresetsDialog: boolean;
|
||||
showEditPresetsDialog: boolean;
|
||||
@@ -57,6 +58,10 @@ interface PluginPresetSelectorState {
|
||||
renameDialogOnOk?: (name: string) => void;
|
||||
|
||||
saveAsName: string;
|
||||
|
||||
isVisible: boolean;
|
||||
hasPresets: boolean;
|
||||
isPastePluginEnabled: boolean;
|
||||
}
|
||||
|
||||
|
||||
@@ -65,15 +70,17 @@ const styles = (theme: Theme) => createStyles({
|
||||
width: 24, height: 24, marginRight: "4px", opacity: 0.6
|
||||
},
|
||||
pluginIcon: {
|
||||
width: 24, height: 24, opacity: 0.6,fill: theme.palette.text.primary
|
||||
width: 24, height: 24, opacity: 0.6, fill: theme.palette.text.primary
|
||||
},
|
||||
pluginMenuIcon: {
|
||||
width: 24, height: 24, opacity: 0.6,fill: theme.palette.text.primary,marginRight: 4
|
||||
width: 24, height: 24, opacity: 0.6, fill: theme.palette.text.primary, marginRight: 4
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
|
||||
let pluginClipboardContents: PedalboardItem | null = null;
|
||||
|
||||
const PluginPresetSelector =
|
||||
withStyles(
|
||||
class extends Component<PluginPresetSelectorProps, PluginPresetSelectorState> {
|
||||
@@ -85,7 +92,7 @@ const PluginPresetSelector =
|
||||
this.model = PiPedalModelFactory.getInstance();
|
||||
this.state = {
|
||||
presets: new PluginUiPresets(),
|
||||
enabled: false,
|
||||
isVisible: this.isVisible(props.pedalboardItem),
|
||||
presetChanged: false,
|
||||
showPresetsDialog: false,
|
||||
showEditPresetsDialog: false,
|
||||
@@ -94,26 +101,44 @@ const PluginPresetSelector =
|
||||
renameDialogDefaultName: "",
|
||||
renameDialogActionName: "",
|
||||
renameDialogOnOk: undefined,
|
||||
saveAsName: ""
|
||||
saveAsName: "",
|
||||
hasPresets: this.hasPresets(this.props.pedalboardItem),
|
||||
isPastePluginEnabled: pluginClipboardContents !== null
|
||||
|
||||
|
||||
};
|
||||
this.handleDialogClose = this.handleDialogClose.bind(this);
|
||||
this.handlePresetsMenuClose = this.handlePresetsMenuClose.bind(this);
|
||||
}
|
||||
|
||||
handleLoadPluginPreset(instanceId: number)
|
||||
{
|
||||
this.handlePresetsMenuClose();
|
||||
this.model.loadPluginPreset(this.props.instanceId,instanceId);
|
||||
let presetName = this.getPresetName(instanceId);
|
||||
this.setState({saveAsName: presetName});
|
||||
hasPresets(pedalboardItem: PedalboardItem | null): boolean {
|
||||
if (pedalboardItem === null) return false;
|
||||
if (!pedalboardItem.uri) return false;
|
||||
if (pedalboardItem.isStart() || pedalboardItem.isEnd()
|
||||
|| pedalboardItem.isEmpty() || pedalboardItem.isSplit()) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
getPresetName(instanceId: number)
|
||||
{
|
||||
for (let preset of this.state.presets.presets)
|
||||
{
|
||||
if (preset.instanceId === instanceId)
|
||||
{
|
||||
isVisible(pedalboardItem: PedalboardItem | null): boolean {
|
||||
if (pedalboardItem === null) return false;
|
||||
if (!pedalboardItem.uri) return false;
|
||||
if (pedalboardItem.isStart() || pedalboardItem.isEnd()
|
||||
|| pedalboardItem.isSplit()) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
handleLoadPluginPreset(instanceId: number) {
|
||||
this.handlePresetsMenuClose();
|
||||
this.model.loadPluginPreset(this.props.instanceId, instanceId);
|
||||
let presetName = this.getPresetName(instanceId);
|
||||
this.setState({ saveAsName: presetName });
|
||||
}
|
||||
getPresetName(instanceId: number) {
|
||||
for (let preset of this.state.presets.presets) {
|
||||
if (preset.instanceId === instanceId) {
|
||||
return preset.label;
|
||||
}
|
||||
}
|
||||
@@ -135,8 +160,8 @@ const PluginPresetSelector =
|
||||
|
||||
this.renameDialogOpen(name, "Save As")
|
||||
.then((newName) => {
|
||||
this.setState({saveAsName: newName});
|
||||
return this.model.saveCurrentPluginPresetAs(this.props.instanceId,newName);
|
||||
this.setState({ saveAsName: newName });
|
||||
return this.model.saveCurrentPluginPresetAs(this.props.instanceId, newName);
|
||||
})
|
||||
.then((newInstanceId) => {
|
||||
// s'fine. dealt with by updates, but we do need error handling.
|
||||
@@ -170,60 +195,62 @@ const PluginPresetSelector =
|
||||
return result;
|
||||
}
|
||||
|
||||
loadPresets():void {
|
||||
if (!this.currentUri) return;
|
||||
let captureUri: string = this.currentUri;
|
||||
loadPresets(): void {
|
||||
if (this.props.pedalboardItem === null) return;
|
||||
if (!this.hasPresets(this.props.pedalboardItem)) return;
|
||||
let captureUri: string = this.props.pedalboardItem.uri;
|
||||
this.model.getPluginPresets(captureUri)
|
||||
.then((presets: PluginUiPresets) => {
|
||||
if (captureUri === this.currentUri)
|
||||
{
|
||||
this.setState({presets: presets});
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
if (captureUri === this.currentUri)
|
||||
{
|
||||
this.setState({presets: new PluginUiPresets()});
|
||||
}
|
||||
});
|
||||
.then((presets: PluginUiPresets) => {
|
||||
if (captureUri === this.props.pedalboardItem?.uri) {
|
||||
this.setState({ presets: presets });
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
if (captureUri === this.props.pedalboardItem?.uri) {
|
||||
this.setState({ presets: new PluginUiPresets() });
|
||||
}
|
||||
});
|
||||
}
|
||||
onPluginPresetsChanged(pluginUri: string): void {
|
||||
if (pluginUri === this.props.pluginUri)
|
||||
{
|
||||
if (pluginUri === this.props.pedalboardItem?.uri) {
|
||||
this.loadPresets();
|
||||
|
||||
}
|
||||
}
|
||||
_pluginPresetsChangedHandle?: PluginPresetsChangedHandle;
|
||||
|
||||
componentDidMount()
|
||||
{
|
||||
componentDidMount() {
|
||||
this._pluginPresetsChangedHandle = this.model.addPluginPresetsChangedListener(
|
||||
(pluginUri: string) => {
|
||||
this.onPluginPresetsChanged(pluginUri);
|
||||
}
|
||||
);
|
||||
}
|
||||
componentWillUnmount()
|
||||
{
|
||||
if (this._pluginPresetsChangedHandle)
|
||||
{
|
||||
componentWillUnmount() {
|
||||
if (this._pluginPresetsChangedHandle) {
|
||||
this.model.removePluginPresetsChangedListener(this._pluginPresetsChangedHandle);
|
||||
this._pluginPresetsChangedHandle = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
currentUri?: string = "";
|
||||
componentDidUpdate()
|
||||
currentPedalboard?: PedalboardItem;
|
||||
componentDidUpdate(
|
||||
prevProps: Readonly<PluginPresetSelectorProps>,
|
||||
prevState: Readonly<PluginPresetSelectorState>,
|
||||
snapshot?: any): void
|
||||
{
|
||||
if (this.currentUri !== this.props.pluginUri)
|
||||
{
|
||||
this.currentUri = this.props.pluginUri;
|
||||
if (!this.props.pluginUri)
|
||||
{
|
||||
this.setState({presets: new PluginUiPresets()});
|
||||
} else {
|
||||
this.loadPresets();
|
||||
if (this.props.pedalboardItem !== prevProps.pedalboardItem) {
|
||||
this.setState({
|
||||
isVisible: this.isVisible(this.props.pedalboardItem),
|
||||
hasPresets: this.hasPresets(this.props.pedalboardItem) });
|
||||
if (this.props.pedalboardItem) {
|
||||
if (this.props.pedalboardItem.isEmpty())
|
||||
{
|
||||
this.setState({ presets: new PluginUiPresets() });
|
||||
} else
|
||||
{
|
||||
this.loadPresets();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -244,8 +271,38 @@ const PluginPresetSelector =
|
||||
|
||||
}
|
||||
|
||||
isMenuCopyPluginEnabled(): boolean {
|
||||
return this.state.hasPresets;
|
||||
}
|
||||
handleMenuCopyPlugin(): void {
|
||||
this.handlePresetsMenuClose();
|
||||
let pedalboard: Pedalboard = this.model.pedalboard.get();;
|
||||
|
||||
handleMenuEditPluginPresets(): void {
|
||||
|
||||
let pedalboardItem: PedalboardItem | null = pedalboard.tryGetItem(this.props.instanceId);
|
||||
if (pedalboardItem === null) {
|
||||
pluginClipboardContents = null;
|
||||
this.setState({ isPastePluginEnabled: false });
|
||||
return;
|
||||
}
|
||||
pluginClipboardContents = pedalboardItem.clone();
|
||||
this.setState({ isPastePluginEnabled: true });
|
||||
|
||||
}
|
||||
|
||||
private isMenuPastePluginEnabled(): boolean {
|
||||
return this.state.isPastePluginEnabled;
|
||||
}
|
||||
private handleMenuPastePlugin(): void {
|
||||
this.handlePresetsMenuClose();
|
||||
|
||||
if (pluginClipboardContents) {
|
||||
this.model.replacePedalboarditem(this.props.instanceId, pluginClipboardContents.clone());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private handleMenuEditPluginPresets(): void {
|
||||
this.handlePresetsMenuClose();
|
||||
this.showEditPluginPresetsDialog(true);
|
||||
}
|
||||
@@ -282,21 +339,50 @@ const PluginPresetSelector =
|
||||
//this.model.loadPreset(event.target.value as number);
|
||||
}
|
||||
|
||||
buildMenuItems() {
|
||||
let result : React.ReactNode[] = [];
|
||||
if (this.state.hasPresets) {
|
||||
result.push((<MenuItem key="menuSaveAs" onClick={(e) => this.handlePluginPresetsMenuSaveAs(e)}>Save plugin preset...</MenuItem>));
|
||||
result.push((<MenuItem key="menuEdit" onClick={(e) => this.handleMenuEditPluginPresets()}>Manage plugin presets...</MenuItem>));
|
||||
result.push((<Divider key="divider1" />));
|
||||
}
|
||||
let hasPresets = false;
|
||||
for (let preset of this.state.presets.presets) {
|
||||
result.push((
|
||||
<MenuItem key={preset.instanceId}
|
||||
onClick={(e) => this.handleLoadPluginPreset(preset.instanceId)}
|
||||
>
|
||||
<PluginPresetIcon className={this.props.classes?.pluginMenuIcon ?? ""} />
|
||||
|
||||
{preset.label}</MenuItem>
|
||||
));
|
||||
hasPresets = true;
|
||||
}
|
||||
if (hasPresets) {
|
||||
result.push((<Divider key="divider2" />));
|
||||
}
|
||||
result.push(
|
||||
<MenuItem key="menuCopy" disabled={!this.isMenuCopyPluginEnabled()} onClick={(e) => this.handleMenuCopyPlugin()}>Copy plugin</MenuItem>
|
||||
);
|
||||
result.push(
|
||||
<MenuItem key="menuPaste" disabled={!this.isMenuPastePluginEnabled()} style={{ opacity: this.isMenuPastePluginEnabled() ? 1 : 0.4 }} onClick={(e) => this.handleMenuPastePlugin()}>Paste plugin</MenuItem>
|
||||
);
|
||||
return result;
|
||||
}
|
||||
render() {
|
||||
const classes = withStyles.getClasses(this.props);
|
||||
|
||||
//const classes = withStyles.getClasses(this.props);
|
||||
//const classes = withStyles.getClasses(this.props);
|
||||
if ((!this.props.pluginUri))
|
||||
{
|
||||
return (<div/>);
|
||||
if (!this.state.isVisible) {
|
||||
return (<div />);
|
||||
}
|
||||
return (
|
||||
<div >
|
||||
<IconButtonEx
|
||||
<IconButtonEx
|
||||
tooltip="Plugin presets"
|
||||
onClick={(e)=> this.handlePresetMenuClick(e)} size="large">
|
||||
<PluginPresetsIcon className={classes.pluginIcon}/>
|
||||
onClick={(e) => this.handlePresetMenuClick(e)} size="large">
|
||||
<PluginPresetsIcon className={classes.pluginIcon} />
|
||||
</IconButtonEx>
|
||||
<Menu
|
||||
id="edit-plugin-presets-menu"
|
||||
@@ -306,34 +392,23 @@ const PluginPresetSelector =
|
||||
TransitionComponent={Fade}
|
||||
MenuListProps={
|
||||
{
|
||||
style: {minWidth: 180}
|
||||
style: { minWidth: 180 }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
>
|
||||
<MenuItem onClick={(e) => this.handlePluginPresetsMenuSaveAs(e)}>Save plugin preset...</MenuItem>
|
||||
<MenuItem onClick={(e) => this.handleMenuEditPluginPresets()}>Manage plugin presets...</MenuItem>
|
||||
|
||||
{ this.state.presets.presets.length !== 0 &&
|
||||
(<Divider/>)
|
||||
}
|
||||
{
|
||||
this.state.presets.presets.map((preset) => {
|
||||
return (<MenuItem key={preset.instanceId}
|
||||
onClick={(e) => this.handleLoadPluginPreset(preset.instanceId)}
|
||||
>
|
||||
<PluginPresetIcon className={classes.pluginMenuIcon}/>
|
||||
|
||||
{preset.label}</MenuItem>);
|
||||
})
|
||||
this.buildMenuItems()
|
||||
}
|
||||
</Menu>
|
||||
<PluginPresetsDialog
|
||||
{this.state.hasPresets && this.state.showPresetsDialog && (
|
||||
<PluginPresetsDialog
|
||||
instanceId={this.props.instanceId}
|
||||
presets={this.state.presets}
|
||||
show={this.state.showPresetsDialog}
|
||||
isEditDialog={this.state.showEditPresetsDialog}
|
||||
isEditDialog={this.state.showEditPresetsDialog}
|
||||
onDialogClose={() => this.handleDialogClose()} />
|
||||
)}
|
||||
<RenameDialog open={this.state.renameDialogOpen}
|
||||
title="Rename"
|
||||
defaultName={this.state.renameDialogDefaultName}
|
||||
@@ -346,6 +421,6 @@ const PluginPresetSelector =
|
||||
|
||||
}
|
||||
},
|
||||
styles);
|
||||
styles);
|
||||
|
||||
export default PluginPresetSelector;
|
||||
|
||||
@@ -238,6 +238,10 @@ export class Tone3000DownloadHandler {
|
||||
if (!tokenResponse.ok) {
|
||||
throw new Error(tokenResponse.error);
|
||||
}
|
||||
if (tokenResponse.canceled) {
|
||||
this.onTone3000DownloadComplete("");
|
||||
return;
|
||||
}
|
||||
if (this.checkForCancel()) {
|
||||
return;
|
||||
}
|
||||
@@ -308,15 +312,14 @@ export class Tone3000DownloadHandler {
|
||||
let lastUpdateTime = Date.now();
|
||||
|
||||
|
||||
|
||||
for (const model of models) {
|
||||
for (const model of models) {
|
||||
this.progress.title = model.name;;
|
||||
if (Date.now() - lastUpdateTime > 250) {
|
||||
this.onTone3000DownloadProgress(this.progress);
|
||||
lastUpdateTime = Date.now();
|
||||
}
|
||||
|
||||
if (!await this.throttleRequests()) return;
|
||||
if (!await this.throttleRequests()) return;
|
||||
|
||||
if (!model.model_url) {
|
||||
throw new Error("Model " + model.name + " does not have a model URL.");
|
||||
@@ -377,15 +380,86 @@ export class Tone3000DownloadHandler {
|
||||
throw new Error(`Upload failed: ${uploadResult.error ?? "Unknown error."}`);
|
||||
}
|
||||
this.progress.progress++;
|
||||
}
|
||||
// the image file and readme.
|
||||
let toobThubmnailUrl: string = "";
|
||||
if (tone.images && tone.images.length > 0) {
|
||||
|
||||
await this.throttleRequests();
|
||||
|
||||
let accessToken = await this.t3kClient.getAccessToken();
|
||||
let thumbnailUrl = tone.images[0];
|
||||
let thumbnailResult = await fetch(thumbnailUrl,
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${accessToken}`,
|
||||
},
|
||||
duplex: 'half',
|
||||
} as RequestInit & { duplex: 'half' }
|
||||
|
||||
);
|
||||
if (!thumbnailResult.ok) {
|
||||
throw new Error(`Thumbnail download failed: ${thumbnailResult.status} ${thumbnailResult.statusText}`);
|
||||
}
|
||||
if (this.checkForCancel()) {
|
||||
return;
|
||||
}
|
||||
let mediaType = thumbnailResult.headers.get("Content-Type");
|
||||
if (mediaType == null) {
|
||||
throw new Error("Thumbnail download failed: Content-Type header is missing.");
|
||||
}
|
||||
let extension: string;
|
||||
switch (mediaType) {
|
||||
case "image/jpeg":
|
||||
extension = ".jpeg";
|
||||
break;
|
||||
case "image/webp":
|
||||
extension = ".webp";
|
||||
break;
|
||||
case "image/png":
|
||||
extension = ".png";
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Thumbnail download failed: Unexpected media type '${mediaType}'.`);
|
||||
}
|
||||
let blob = await thumbnailResult.blob();
|
||||
const TONE3000_THUMBNAIL_PATH = "/var/pipedal/tone3000_thumbnails/";
|
||||
let thumbnailUploadPath = TONE3000_THUMBNAIL_PATH + tone.id + extension;
|
||||
|
||||
let serverUrl = this.model.varServerUrl + "t3k_uploadAsset?path="
|
||||
+ encodeURIComponent(thumbnailUploadPath);
|
||||
|
||||
const uploadResponse = await fetch(serverUrl, {
|
||||
method: 'POST',
|
||||
body: blob,
|
||||
headers: {
|
||||
'Content-Type': mediaType,
|
||||
"Content-Length": blob.size.toString(),
|
||||
"Transfer-Encoding": "chunked"
|
||||
},
|
||||
});
|
||||
|
||||
if (!uploadResponse.ok) {
|
||||
throw new Error(`Thumbnail upload failed: ${uploadResponse.statusText}`);
|
||||
}
|
||||
let uploadResult: any = await uploadResponse.json();
|
||||
|
||||
if (!uploadResult.ok === true) {
|
||||
throw new Error(`Thumbnail upload failed: ${uploadResult.error ?? "Unknown error."}`);
|
||||
}
|
||||
toobThubmnailUrl = "/var/t3k_thumbnail?id=" + tone.id;
|
||||
|
||||
}
|
||||
// yyy: get the image file.
|
||||
// yyy: write the reaadme file.
|
||||
let readmePath = toneUploadPath + "README.md";
|
||||
this.model.writeTone3000Readme(readmePath, tone, toobThubmnailUrl);
|
||||
|
||||
this.onTone3000DownloadComplete(uploadPath);
|
||||
this.onTone3000DownloadComplete(toneUploadPath);
|
||||
} catch (error) {
|
||||
this.onTone3000DownloadError(getErrorMessage(error)
|
||||
+ " " + this.progress.progress.toString() + "/" + this.progress.total.toString() + " models downloaded.");
|
||||
let message = getErrorMessage(error);
|
||||
if (this.progress.progress > 0) {
|
||||
message += " (" + this.progress.progress.toString() + "/" + this.progress.total.toString() + " models downloaded)";
|
||||
}
|
||||
this.onTone3000DownloadError(message);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
// Copyright (c) 2022 Robin Davies
|
||||
// Copyright (c) Fulgencio Ruiz Rubio.
|
||||
//
|
||||
// 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
|
||||
|
||||
@@ -30,17 +30,27 @@ import { PiPedalModelFactory, PiPedalModel, ListenHandle, State } from "./PiPeda
|
||||
import { PedalboardItem, ControlValue } from './Pedalboard';
|
||||
import PluginControlView, { ICustomizationHost, ControlGroup, ControlViewCustomization } from './PluginControlView';
|
||||
import { UiPlugin, UiControl, ControlType } from './Lv2Plugin';
|
||||
import Select from '@mui/material/Select/Select';
|
||||
import MenuItem from '@mui/material/MenuItem/MenuItem';
|
||||
import { CustomPluginControl } from './PluginControl';
|
||||
|
||||
|
||||
const TOOB_NAM__MODEL_METADATA_URI = "http://two-play.com/plugins/toob-nam#model_metadata";
|
||||
const TOOB_NAM__MODEL_WEIGHT_URI = "http://two-play.com/plugins/toob-nam#modelWeight";
|
||||
|
||||
|
||||
enum NamModelType {
|
||||
None = 0,
|
||||
A1 = 1,
|
||||
A2 = 2
|
||||
};
|
||||
|
||||
function floatToModelType(value: number): NamModelType {
|
||||
switch (value) {
|
||||
case 0: return NamModelType.None;
|
||||
case 1: return NamModelType.A1;
|
||||
case 2: return NamModelType.A2;
|
||||
default: return NamModelType.None;
|
||||
}
|
||||
}
|
||||
// offset 0: an integer with the folloing bits set.
|
||||
// Must match ToobAmp/src/NeuralAmpModeler_Lv2Extensions.hpp enum TOOB_NAM_METADATA_OFFSETS
|
||||
const MAX_SLIMMABLE_WEIGHTS = 16;
|
||||
class TOOB_NAM_METADATA_OFFSETS {
|
||||
static readonly flags = 0;
|
||||
static readonly preset_version = 1;
|
||||
@@ -48,11 +58,10 @@ class TOOB_NAM_METADATA_OFFSETS {
|
||||
static readonly gain = 3;
|
||||
static readonly input_level_dbu = 4;
|
||||
static readonly output_level_dbu = 5;
|
||||
static readonly is_a2 = 6;
|
||||
static readonly has_slimmable_weights = 6;
|
||||
static readonly model_weight = 7;
|
||||
static readonly slimmable_weights_size = 8;
|
||||
static readonly slimmable_weights = 9;
|
||||
static readonly max_medatadata = TOOB_NAM_METADATA_OFFSETS.slimmable_weights + MAX_SLIMMABLE_WEIGHTS;
|
||||
static readonly model_type = 8;
|
||||
static readonly max_medatadata = 9;
|
||||
};
|
||||
|
||||
|
||||
@@ -81,12 +90,9 @@ class ModelMetadata {
|
||||
this.inputlevelDBU = metadataValues[TOOB_NAM_METADATA_OFFSETS.input_level_dbu];
|
||||
this.outputlevelDBU = metadataValues[TOOB_NAM_METADATA_OFFSETS.output_level_dbu];
|
||||
this.preset_version = metadataValues[TOOB_NAM_METADATA_OFFSETS.preset_version];
|
||||
this.isA2 = metadataValues[TOOB_NAM_METADATA_OFFSETS.is_a2] !== 0;
|
||||
this.hasSlimmableWeights = metadataValues[TOOB_NAM_METADATA_OFFSETS.has_slimmable_weights] !== 0;
|
||||
this.modelWeight = metadataValues[TOOB_NAM_METADATA_OFFSETS.model_weight];
|
||||
let slimmableWeightsSize = metadataValues[TOOB_NAM_METADATA_OFFSETS.slimmable_weights_size];
|
||||
for (let i = 0; i < slimmableWeightsSize && i < 16; ++i) {
|
||||
this.slimmable_weights.push(metadataValues[TOOB_NAM_METADATA_OFFSETS.slimmable_weights + i]);
|
||||
}
|
||||
this.modelType = floatToModelType(metadataValues[TOOB_NAM_METADATA_OFFSETS.model_type]);
|
||||
} else {
|
||||
this.hasModel = false;
|
||||
this.hasLoudness = false;
|
||||
@@ -96,34 +102,29 @@ class ModelMetadata {
|
||||
|
||||
this.loudness = 0;
|
||||
this.gain = 0;
|
||||
this.hasSlimmableWeights = false;
|
||||
this.inputlevelDBU = 0;
|
||||
this.outputlevelDBU = 0;
|
||||
this.preset_version = 1;
|
||||
this.modelWeight = -1;
|
||||
this.isA2 = false;
|
||||
this.slimmable_weights = [];
|
||||
}
|
||||
}
|
||||
|
||||
preset_version: number;
|
||||
isA2: boolean = false;
|
||||
hasSlimmableWeights: boolean = false;
|
||||
modelWeight: number = 1.0;
|
||||
slimmable_weights: number[] = [];
|
||||
hasModel: boolean;
|
||||
hasLoudness: boolean;
|
||||
hasGain: boolean;
|
||||
hasInputLevelDBU: boolean;
|
||||
hasOutputLevelDBU: boolean;
|
||||
modelType: NamModelType = NamModelType.None;
|
||||
hasModel: boolean = false;
|
||||
hasLoudness: boolean = false;
|
||||
hasGain: boolean = false;
|
||||
hasInputLevelDBU: boolean = false;
|
||||
hasOutputLevelDBU: boolean = false;
|
||||
|
||||
loudness: number;
|
||||
gain: number;
|
||||
inputlevelDBU: number;
|
||||
outputlevelDBU: number;
|
||||
|
||||
hasSlimmableWeights(): boolean {
|
||||
return this.slimmable_weights.length > 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -213,10 +214,9 @@ const ToobNamView =
|
||||
}
|
||||
|
||||
modifyControls(host: ICustomizationHost, controls: (React.ReactNode | ControlGroup)[]): (React.ReactNode | ControlGroup)[] {
|
||||
let EqPos = 7;
|
||||
const CalibrationGroupPos = 8;
|
||||
const ModelSizeControlPos = 9;
|
||||
const ModelWeightControlPos = 10;
|
||||
const ModelWeightControlPos = 3;
|
||||
const EqPos = 8;
|
||||
const CalibrationGroupPos = 9;
|
||||
|
||||
|
||||
let calibrationGroup = controls[CalibrationGroupPos] as ControlGroup;
|
||||
@@ -250,57 +250,15 @@ const ToobNamView =
|
||||
} else {
|
||||
controls[CalibrationGroupPos] = null;
|
||||
}
|
||||
controls[ModelSizeControlPos] = null;
|
||||
controls[ModelWeightControlPos] = null;
|
||||
if (!this.state.showEqSection) {
|
||||
controls.splice(EqPos, 1);
|
||||
controls[EqPos] = null;
|
||||
}
|
||||
if (this.state.modelMetadata.hasSlimmableWeights()) {
|
||||
controls.splice(3, 0, this.MakeModelWeightSelector(host));
|
||||
if (!this.state.modelMetadata.hasSlimmableWeights) {
|
||||
controls[ModelWeightControlPos] = null;
|
||||
}
|
||||
return controls;
|
||||
}
|
||||
|
||||
ModelWeightToString(weight: number): string {
|
||||
switch (weight) {
|
||||
case 1.0: return "Standard";
|
||||
case 0.5: return "Lite";
|
||||
case 0.25: return "Feather";
|
||||
default: return weight.toString();
|
||||
}
|
||||
}
|
||||
|
||||
SelectModelWeight(weight: number) {
|
||||
this.setState({ modelWeight: weight });
|
||||
this.model.setPatchProperty(this.props.instanceId, TOOB_NAM__MODEL_WEIGHT_URI, weight);
|
||||
|
||||
}
|
||||
MakeModelWeightSelector(host: ICustomizationHost): React.ReactNode {
|
||||
if ((this.state.modelMetadata.modelWeight === undefined || this.state.modelMetadata.modelWeight || this.state.modelMetadata.hasSlimmableWeights()) === false) {
|
||||
return null;
|
||||
}
|
||||
let selectControl = (
|
||||
<Select key="model_weight_control" variant="standard" size="small" value={this.state.modelMetadata.modelWeight}
|
||||
onChange={(e) => { this.SelectModelWeight(Number(e.target.value)) }}
|
||||
style={{ marginLeft: 4, marginRight: 4, width: 140, fontSize: 12, marginTop: 4 }}
|
||||
>
|
||||
{this.state.modelMetadata.slimmable_weights.map((weight, index) => {
|
||||
return (
|
||||
<MenuItem key={weight.toString()} value={weight}
|
||||
style={{ fontSize: 14 }}
|
||||
>
|
||||
{this.ModelWeightToString(weight)}
|
||||
</MenuItem>
|
||||
);
|
||||
})}
|
||||
|
||||
</Select>
|
||||
);
|
||||
return (
|
||||
<div style={{ marginLeft: 12 }}>
|
||||
<CustomPluginControl title="Model Weight" mainControl={selectControl} isSelect={true} />
|
||||
</div>);
|
||||
}
|
||||
|
||||
private handleConnectionStateChanged(state: State) {
|
||||
if (state === State.Ready) {
|
||||
|
||||
@@ -542,7 +542,7 @@ export async function handleOAuthCallback(
|
||||
console.debug("PiPedal handleOAuthCallback Response: " + JSON.stringify(data).replace(/\n/g, ' '));
|
||||
}
|
||||
|
||||
if (toneId === undefined && modelId === undefined) {
|
||||
if (toneId === undefined && modelId === undefined && !canceled) {
|
||||
throw new Error('Missing both toneId and modelId in OAuth callback response');
|
||||
}
|
||||
return { ok: true, tokens, toneId, modelId, ...(canceled ? { canceled: true } : {}) };
|
||||
|
||||
Reference in New Issue
Block a user