Merge pull request #173 from rerdavies/dev

v1.2.40
This commit is contained in:
Robin Davies
2024-08-20 21:12:06 -04:00
committed by GitHub
35 changed files with 954 additions and 796 deletions
+2 -2
View File
@@ -1,10 +1,10 @@
cmake_minimum_required(VERSION 3.16.0) cmake_minimum_required(VERSION 3.16.0)
project(pipedal project(pipedal
VERSION 1.2.38 VERSION 1.2.40
DESCRIPTION "PiPedal Guitar Effect Pedal For Raspberry Pi" DESCRIPTION "PiPedal Guitar Effect Pedal For Raspberry Pi"
HOMEPAGE_URL "https://rerdavies.github.io/pipedal" HOMEPAGE_URL "https://rerdavies.github.io/pipedal"
) )
set (DISPLAY_VERSION "v1.2.38beta5") set (DISPLAY_VERSION "v1.2.40Beta6")
set (CMAKE_INSTALL_PREFIX "/usr/") set (CMAKE_INSTALL_PREFIX "/usr/")
+6 -4
View File
@@ -6,16 +6,18 @@
<a href="https://rerdavies.github.io/pipedal/LicensePiPedal.html"><img src="https://img.shields.io/badge/MIT-MIT?label=license&color=%23808080"/></a> <a href="https://rerdavies.github.io/pipedal/LicensePiPedal.html"><img src="https://img.shields.io/badge/MIT-MIT?label=license&color=%23808080"/></a>
<a href="https://github.com/rerdavies/pipedal/actions"><img src="https://img.shields.io/github/actions/workflow/status/rerdavies/pipedal/cmake.yml?branch=main"/></a> <a href="https://github.com/rerdavies/pipedal/actions"><img src="https://img.shields.io/github/actions/workflow/status/rerdavies/pipedal/cmake.yml?branch=main"/></a>
Download:&nbsp;<a href='https://rerdavies.github.io/pipedal/download.html'>v1.2.38 Beta5</a>&nbsp;&nbsp;&nbsp;&nbsp; Website:&nbsp;[https://rerdavies.github.io/pipedal](https://rerdavies.github.io/pipedal). Download:&nbsp;<a href='https://rerdavies.github.io/pipedal/download.html'>v1.2.40 Beta6</a>
Website:&nbsp;[https://rerdavies.github.io/pipedal](https://rerdavies.github.io/pipedal).
Documentation:&nbsp;[https://rerdavies.github.io/pipedal/Documentation.html](https://rerdavies.github.io/pipedal/Documentation.html).
&nbsp; &nbsp;
#### NEW version 1.2.38 Beta5 release, providing support for Raspberry Pi OS Bookworm. See the [release notes](https://rerdavies.github.io/pipedal/ReleaseNotes) for details. #### NEW version 1.2.40 Beta6 release, providing support for Raspberry Pi OS Bookworm. See the [release notes](https://rerdavies.github.io/pipedal/ReleaseNotes) for details.
&nbsp; &nbsp;
Use your Raspberry Pi as a guitar effects pedal. Configure and control PiPedal with your phone or tablet. Use your Raspberry Pi as a guitar effects pedal. Configure and control PiPedal with your phone or tablet.
PiPedal running on a Raspberry Pi 4 provides stable super-low-latency audio via external USB audio devices, or internal Raspberry Pi audio hats.  PiPedal running on a Raspberry Pi 4 or Pi 5 provides stable super-low-latency audio via external USB audio devices, or internal Raspberry Pi audio hats. 
PiPedal's user interface has been specifically designed to work well on small form-factor touch devices like phones or tablets. Clip a phone or tablet on your microphone stand on stage, and you're ready to play! Or connect via a desktop browser, for a slightly more luxurious experience. The PiPedal user-interface adapts to the screen size and orientation of your device, providing easy control of your guitar effects across a broad variety devices and screen sizes. PiPedal's user interface has been specifically designed to work well on small form-factor touch devices like phones or tablets. Clip a phone or tablet on your microphone stand on stage, and you're ready to play! Or connect via a desktop browser, for a slightly more luxurious experience. The PiPedal user-interface adapts to the screen size and orientation of your device, providing easy control of your guitar effects across a broad variety devices and screen sizes.
@@ -43,7 +45,7 @@ If your USB audio adapter has MIDI connectors, you can use MIDI devices (keyboar
### [Using LV2 Audio Plugins](https://rerdavies.github.io/pipedal/UsingLv2Plugins.md) ### [Using LV2 Audio Plugins](https://rerdavies.github.io/pipedal/UsingLv2Plugins.md)
### [Which LV2 Plugins does PiPedal support?](https://rerdavies.github.io/pipedal/WhichLv2PluginsAreSupported.html) ### [Which LV2 Plugins does PiPedal support?](https://rerdavies.github.io/pipedal/WhichLv2PluginsAreSupported.html)
   &nbsp;
### [Building PiPedal from Source](https://rerdavies.github.io/pipedal/BuildingPiPedalFromSource.html) ### [Building PiPedal from Source](https://rerdavies.github.io/pipedal/BuildingPiPedalFromSource.html)
### [Build Prerequisites](https://rerdavies.github.io/pipedal/BuildPrerequisites.html) ### [Build Prerequisites](https://rerdavies.github.io/pipedal/BuildPrerequisites.html)
+3 -3
View File
@@ -13,17 +13,17 @@ page_icon: img/Install4.jpg
Download the most recent Debian (.deb) package for your platform: Download the most recent Debian (.deb) package for your platform:
- [Raspberry Pi OS bookworm (64-bit) v1.2.38 Beta5](https://github.com/rerdavies/pipedal/releases/download/) - [Raspberry Pi OS bookworm (64-bit) v1.2.40 Beta6](https://github.com/rerdavies/pipedal/releases/download/)
- [Ubuntu/Raspberry Pi OS bullseyeye (64-bit) v1.2.31](https://github.com/rerdavies/pipedal/releases/download/v1.1.31/pipedal_1.1.31_arm64.deb) - [Ubuntu/Raspberry Pi OS bullseyeye (64-bit) v1.2.31](https://github.com/rerdavies/pipedal/releases/download/v1.1.31/pipedal_1.1.31_arm64.deb)
Version 1.2.38 has not yet been tested on Ubuntu or Raspberry Pi OS bullseye. On these platforms, we recommend that you use version 1.1.31. Version 1.2.40 has not yet been tested on Ubuntu or Raspberry Pi OS bullseye. On these platforms, we recommend that you use version 1.1.31.
Install the package by running Install the package by running
``` ```
sudo apt update sudo apt update
cd ~/Downloads cd ~/Downloads
sudo apt-get install pipedal_1.2.38_arm64.deb sudo apt-get install pipedal_1.2.40_arm64.deb
``` ```
Adjust accordingly if you have downloaded v1.1.31. Adjust accordingly if you have downloaded v1.1.31.
+5 -1
View File
@@ -1,5 +1,9 @@
# Release Notes # Release Notes
## Pipedal 1.2.38 Beta5 ## Pipedal 1.2.40 Beta6
- PiPedal now monitors lv2 directories in order to automatically add newly-installed LV2 plugins.
- Toggle and on/off switch colors changed from red/pink to purple.
## Pipedal 1.2.38 Beta6
Fixes: Fixes:
- PiPedal won't run on non-en-US locales. - PiPedal won't run on non-en-US locales.
+2 -2
View File
@@ -201,8 +201,8 @@
+ "px; left: 0px; top: 0px; opacity: 0.0;" + "px; left: 0px; top: 0px; opacity: 0.0;"
+ " display: flex; flex-flow: row none; justify-content: space-between;align-items: end; padding:20; opacity: 0.8;'>"); + " display: flex; flex-flow: row none; justify-content: space-between;align-items: end; padding:20; opacity: 0.8;'>");
{ {
document.writeln("<div><span style='cursor: default;font-size: 52px;color: #444;'></span></div>"); document.writeln("<div><span style='cursor: default;font-size: 52px;color: #444;'><img src="img/arrow_back.svg"/></span></div>");
document.writeln("<div><span style='cursor: default;font-size: 52px;color: #444;'></span></div>"); document.writeln("<div><span style='cursor: default;font-size: 52px;color: #444;'><img src="img/arrow_forward.svg"/></span></div>");
} }
document.writeln("</div>"); document.writeln("</div>");
+3 -3
View File
@@ -4,17 +4,17 @@
Download the most recent Debian (.deb) package for your platform: Download the most recent Debian (.deb) package for your platform:
- <a href="https://github.com/rerdavies/pipedal/releases/download/v1.2.38/pipedal_1.2.38_arm64.deb">Raspberry Pi OS Bookworm (64-bit) v1.2.38 Beta5</a> - <a href="https://github.com/rerdavies/pipedal/releases/download/v1.2.40/pipedal_1.2.40_arm64.deb">Raspberry Pi OS Bookworm (64-bit) v1.2.40 Beta6</a>
- <a href="https://github.com/rerdavies/pipedal/releases/download/v1.1.31/pipedal_1.1.31_arm64.deb">Ubuntu 21.04 or Raspberry Pi OS bullseyeyeye (64-bit) v1.1.31</a> - <a href="https://github.com/rerdavies/pipedal/releases/download/v1.1.31/pipedal_1.1.31_arm64.deb">Ubuntu 21.04 or Raspberry Pi OS bullseyeyeye (64-bit) v1.1.31</a>
v1.2.38 Beta5 is does not currently support Ubuntu 21.04, or older versions of Raspberry Pi OS. v1.2.40 Beta6 is does not currently support Ubuntu 21.04, or older versions of Raspberry Pi OS.
Install the package by running Install the package by running
``` ```
sudo apt update sudo apt update
cd ~/Downloads cd ~/Downloads
sudo apt-get install ./pipedal_1.2.38_arm64.deb sudo apt-get install ./pipedal_1.2.40_arm64.deb
``` ```
Follow the instructions in [_Configuring PiPedal After Installation_](https://rerdavies.github.io/pipedal/Configuring.html) to complete the installation. Follow the instructions in [_Configuring PiPedal After Installation_](https://rerdavies.github.io/pipedal/Configuring.html) to complete the installation.
+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#5f6368"><path d="M400-80 0-480l400-400 71 71-329 329 329 329-71 71Z"/></svg>

After

Width:  |  Height:  |  Size: 175 B

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#5f6368"><path d="m321-80-71-71 329-329-329-329 71-71 400 400L321-80Z"/></svg>

After

Width:  |  Height:  |  Size: 176 B

+5 -3
View File
@@ -1,17 +1,19 @@
<img src="GithubBanner.png" width="100%"/> <img src="GithubBanner.png" width="100%"/>
<a href="Installing.html"><i>v1.2.38</i></a> <a href="Installing.html"><i>v1.2.40</i></a>
&nbsp; &nbsp;
To download PiPedal, click [here](download.md). To download PiPedal, click [here](download.md).
To view PiPedal documentation, click [here](Documentation.md).
> NEW version 1.2.38 Beta5 release. See the [release notes](https://rerdavies.github.io/pipedal/ReleaseNotes) for details.
> NEW version 1.2.40 Beta6 release. See the [release notes](https://rerdavies.github.io/pipedal/ReleaseNotes) for details.
Use your Raspberry Pi as a guitar effects pedal. Configure and control PiPedal with your phone or tablet. Use your Raspberry Pi as a guitar effects pedal. Configure and control PiPedal with your phone or tablet.
PiPedal running on a Raspberry Pi 4 provides stable super-low-latency audio via external USB audio devices, or internal Raspberry Pi audio hats. PiPedal running on a Raspberry Pi 4 or Pi 5 provides stable super-low-latency audio via external USB audio devices, or internal Raspberry Pi audio hats.
{% include gallery.html %} {% include gallery.html %}
+53 -136
View File
@@ -21,9 +21,57 @@ add_custom_command(
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/react WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/react
DEPENDS DEPENDS
src/SelectThemeDialog.tsx
src/OkCancelDialog.tsx src/OkCancelDialog.tsx
src/PluginControl.tsx src/PluginControl.tsx
src/svg/ic_save_bank_as.svg
src/svg/fx_constant.svg
src/svg/fx_distortion.svg
src/svg/ic_help_outline.svg
src/svg/fx_oscillator.svg
src/svg/fx_filter.svg
src/svg/fx_function.svg
src/svg/fx_generator.svg
src/svg/fx_compressor.svg
src/svg/file_download_black_24dp.svg
src/svg/fx_filter_hp.svg
src/svg/fx_plugin.svg
src/svg/fx_error.svg
src/svg/fx_gate.svg
src/svg/ic_midi.svg
src/svg/ic_pluginpreset.svg
src/svg/fx_split_a.svg
src/svg/fx_utility.svg
src/svg/ic_pluginpreset2.svg
src/svg/fx_parametric_eq.svg
src/svg/fx_terminal.svg
src/svg/ic_presets.svg
src/svg/fx_flanger.svg
src/svg/fx_analyzer.svg
src/svg/fx_delay.svg
src/svg/fx_spectral.svg
src/svg/fx_chorus.svg
src/svg/fx_simulator.svg
src/svg/drive_file_rename_outline_black_24dp.svg
src/svg/fx_mixer.svg
src/svg/fx_eq.svg
src/svg/fx_modulator.svg
src/svg/file_upload_black_24dp.svg
src/svg/fx_pitch.svg
src/svg/fx_phaser.svg
src/svg/fx_amplifier.svg
src/svg/fx_lr.svg
src/svg/fx_instrument.svg
src/svg/fx_reverb.svg
src/svg/ic_edit_banks.svg
src/svg/fx_limiter.svg
src/svg/fx_empty.svg
src/svg/fx_dial.svg
src/svg/fx_flanger2.svg
src/svg/fx_split_b.svg
src/svg/fx_spatial.svg
src/svg/fx_converter.svg
src/svg/old_delete_outline_24dp.svg
src/svg/ic_settings.svg
src/reportWebVitals.ts src/reportWebVitals.ts
src/JackHostStatus.tsx src/JackHostStatus.tsx
src/PluginPresetsDialog.tsx src/PluginPresetsDialog.tsx
@@ -39,6 +87,7 @@ add_custom_command(
src/StringBuilder.tsx src/StringBuilder.tsx
src/FilePropertyDialog.tsx src/FilePropertyDialog.tsx
src/GxTunerControl.tsx src/GxTunerControl.tsx
src/DarkMode.tsx
src/App.tsx src/App.tsx
src/GxTunerView.tsx src/GxTunerView.tsx
src/Rect.tsx src/Rect.tsx
@@ -66,12 +115,14 @@ add_custom_command(
src/ToobPowerStage2View.tsx src/ToobPowerStage2View.tsx
src/ListSelectDialog.tsx src/ListSelectDialog.tsx
src/ToobWaveShapeView.tsx src/ToobWaveShapeView.tsx
src/SelectThemeDialog.tsx
src/PluginInfoDialog.tsx src/PluginInfoDialog.tsx
src/SearchFilter.tsx src/SearchFilter.tsx
src/SearchControl.tsx src/SearchControl.tsx
src/JackServerSettings.tsx src/JackServerSettings.tsx
src/WifiChannel.tsx src/WifiChannel.tsx
src/WifiConfigDialog.tsx src/WifiConfigDialog.tsx
src/FilePropertyDirectoryTree.tsx
src/App.test.tsx src/App.test.tsx
src/PluginPresetSelector.tsx src/PluginPresetSelector.tsx
src/ObservableProperty.tsx src/ObservableProperty.tsx
@@ -97,6 +148,7 @@ add_custom_command(
src/AlsaMidiDeviceInfo.tsx src/AlsaMidiDeviceInfo.tsx
src/ResizeResponsiveComponent.tsx src/ResizeResponsiveComponent.tsx
src/RenameDialog.tsx src/RenameDialog.tsx
src/FilePropertyDirectorySelectDialog.tsx
src/SystemMidiBindingView.tsx src/SystemMidiBindingView.tsx
src/OldDeleteIcon.tsx src/OldDeleteIcon.tsx
src/ToobInputStageView.tsx src/ToobInputStageView.tsx
@@ -123,141 +175,6 @@ add_custom_command(
src/PluginIcon.tsx src/PluginIcon.tsx
src/UploadFileDialog.tsx src/UploadFileDialog.tsx
public/css/roboto.css
public/logo192x.png
public/robots.txt
public/img/ic_drawer_2.png
public/img/fx_constant.svg
public/img/ic_presets_white.svg
public/img/cx_mono.svg
public/img/cx_stereo.svg
public/img/fx_distortion.svg
public/img/Pi-Logo-3.png
public/img/fx_oscillator.svg
src/svg/ic_save_bank_as.svg
src/svg/ic_edit_banks.svg
src/svg/ic_settings.svg
public/img/fx_filter.svg
public/img/delete_outline_black_24dp.svg
public/img/fx_function.svg
public/img/fx_generator.svg
public/img/old_delete_outline_black_24dp.svg
public/img/ic_bank.svg
public/img/fx_compressor.svg
public/img/fx_filter_hp.svg
public/img/fx_plugin.svg
public/img/fx_error.svg
public/img/fx_gate.svg
public/img/cc-by.png
public/img/fx_split_a.svg
public/img/fx_utility.svg
src/svg/ic_help_outline.svg
public/img/ic_navigate_before.svg
public/img/ic_drawer_2.svg
public/img/fx_parametric_eq.svg
public/img/fx_terminal.svg
public/img/ic_presets.svg
public/img/VST_Logo_Steinberg.ico
public/img/fx_flanger.svg
public/img/fx_analyzer.svg
public/img/fx_delay.svg
public/img/fx_spectral.svg
public/img/fx_chorus.svg
public/img/fx_simulator.svg
public/img/fx_mixer.svg
public/img/fx_eq.svg
public/img/fx_modulator.svg
public/img/fx_pitch.svg
public/img/ic_navigate_next.svg
public/img/fx_phaser.svg
public/img/ic_drawer.svg
public/img/fx_amplifier.svg
public/img/fx_lr.svg
public/img/fx_instrument.svg
public/img/Pi-Logo-3.svg
public/img/fx_reverb.svg
public/img/fx_limiter.svg
public/img/VST_Logo_Steinberg.png
public/img/fx_empty.svg
public/img/fx_dial.svg
public/img/ic_logo.svg
public/img/fx_flanger2.svg
public/img/old_delete_outline_white_24dp.svg
public/img/fx_split_b.svg
public/img/fx_spatial.svg
public/img/fx_converter.svg
public/img/vst.svg
public/img/vst.png
public/sample_lv2_plugins.json
public/serve.json
public/fonts/Roboto-LightItalic.woff2
public/fonts/Roboto-Light.woff2
public/fonts/Roboto-BlackItalic.woff2
public/fonts/Roboto-ThinItalic.woff2
public/fonts/Roboto-Bold.woff2
public/fonts/Roboto-Italic.woff2
public/fonts/Roboto-Black.woff2
public/fonts/Roboto-BoldItalic.woff2
public/fonts/Roboto-Regular.woff2
public/fonts/Roboto-Medium.woff2
public/fonts/Roboto-Thin.woff2
public/fonts/LICENSE.txt
public/fonts/Roboto-MediumItalic.woff2
public/favicon.ico
public/iso_codes.json
public/logo192.png
public/logo512.png
public/index.html
public/manifest.json
public/var/config.json
public/var/current_pedalboard.json
public/var/uiplugins.json
public/var/plugin_classes.json
src/svg/fx_constant.svg
src/svg/fx_distortion.svg
src/svg/fx_oscillator.svg
src/svg/fx_filter.svg
src/svg/fx_function.svg
src/svg/fx_generator.svg
src/svg/fx_compressor.svg
src/svg/file_download_black_24dp.svg
src/svg/fx_filter_hp.svg
src/svg/fx_plugin.svg
src/svg/fx_error.svg
src/svg/fx_gate.svg
src/svg/ic_midi.svg
src/svg/ic_pluginpreset.svg
src/svg/fx_split_a.svg
src/svg/fx_utility.svg
src/svg/ic_pluginpreset2.svg
src/svg/fx_parametric_eq.svg
src/svg/fx_terminal.svg
src/svg/ic_presets.svg
src/svg/fx_flanger.svg
src/svg/fx_analyzer.svg
src/svg/fx_delay.svg
src/svg/fx_spectral.svg
src/svg/fx_chorus.svg
src/svg/fx_simulator.svg
src/svg/fx_mixer.svg
src/svg/fx_eq.svg
src/svg/fx_modulator.svg
src/svg/file_upload_black_24dp.svg
src/svg/fx_pitch.svg
src/svg/fx_phaser.svg
src/svg/fx_amplifier.svg
src/svg/fx_lr.svg
src/svg/fx_instrument.svg
src/svg/fx_reverb.svg
src/svg/fx_limiter.svg
src/svg/fx_empty.svg
src/svg/fx_dial.svg
src/svg/fx_flanger2.svg
src/svg/fx_split_b.svg
src/svg/fx_spatial.svg
src/svg/fx_converter.svg
src/svg/old_delete_outline_24dp.svg
) )
add_custom_target ( add_custom_target (
+1 -1
View File
@@ -1,5 +1,5 @@
{ {
"socket_server_port": 8080, "socket_server_port": 80,
"socket_server_address": "*", "socket_server_address": "*",
"debug": true, "debug": true,
"max_upload_size": 536870912, "max_upload_size": 536870912,
+17 -5
View File
@@ -511,7 +511,7 @@ const AppThemed = withStyles(appStyles)(class extends ResizeResponsiveComponent<
} }
private unmountListener(e: Event) { private unmountListener(e: Event) {
if ((!this.model_.reloadRequested) && this.model_.state.get() === State.Ready && !this.model_.isAndroidHosted()) { if (this.model_.state.get() === State.Ready && !this.model_.isAndroidHosted()) {
e.preventDefault(); e.preventDefault();
(e as any).returnValue = "Are you sure you want to leave this page?"; (e as any).returnValue = "Are you sure you want to leave this page?";
return "Are you sure you want to leave this page?"; return "Are you sure you want to leave this page?";
@@ -608,8 +608,7 @@ setDisplayState(newState: State): void {
displayState: newState, displayState: newState,
canFullScreen: supportsFullScreen() && !this.model_.isAndroidHosted() canFullScreen: supportsFullScreen() && !this.model_.isAndroidHosted()
}); });
if(newState === State.Ready) if (newState === State.Ready) {
{
if (this.model_.isOnboarding()) { if (this.model_.isOnboarding()) {
this.handleDisplayOnboarding(); this.handleDisplayOnboarding();
} }
@@ -656,6 +655,16 @@ handleReload() {
window.location.reload(); window.location.reload();
} }
} }
getReloadingMessage(): string {
switch (this.state.displayState) {
case State.ApplyingChanges:
return "Applying\u00A0changes...";
case State.ReloadingPlugins:
return "Reloading\u00A0plugins...";
default:
return "Reconnecting...";
}
}
render() { render() {
const { classes } = this.props; const { classes } = this.props;
@@ -887,7 +896,10 @@ render() {
{this.state.showStatusMonitor && (<JackStatusView />)} {this.state.showStatusMonitor && (<JackStatusView />)}
<div className={classes.errorContent} style={{ <div className={classes.errorContent} style={{
display: (this.state.displayState === State.Reconnecting || this.state.displayState === State.ApplyingChanges) display: (
this.state.displayState === State.Reconnecting
|| this.state.displayState === State.ApplyingChanges
|| this.state.displayState === State.ReloadingPlugins)
? "block" : "none" ? "block" : "none"
}} }}
> >
@@ -898,7 +910,7 @@ render() {
<CircularProgress color="inherit" className={classes.loadingBoxItem} /> <CircularProgress color="inherit" className={classes.loadingBoxItem} />
</div> </div>
<Typography noWrap variant="body2" className={classes.progressText}> <Typography noWrap variant="body2" className={classes.progressText}>
{this.state.displayState === State.ApplyingChanges ? "Applying\u00A0changes..." : "Reconnecting..."} {this.getReloadingMessage()}
</Typography> </Typography>
</div> </div>
</div> </div>
+50 -30
View File
@@ -42,6 +42,7 @@ import SearchFilter from './SearchFilter';
import { FixedSizeGrid } from 'react-window'; import { FixedSizeGrid } from 'react-window';
import AutoSizer from 'react-virtualized-auto-sizer'; import AutoSizer from 'react-virtualized-auto-sizer';
import StarBorderIcon from '@mui/icons-material/StarBorder'; import StarBorderIcon from '@mui/icons-material/StarBorder';
import StarIcon from '@mui/icons-material/Star';
import { createStyles, Theme } from '@mui/material/styles'; import { createStyles, Theme } from '@mui/material/styles';
import { WithStyles, withStyles } from '@mui/styles'; import { WithStyles, withStyles } from '@mui/styles';
import FilterListIcon from '@mui/icons-material/FilterList'; import FilterListIcon from '@mui/icons-material/FilterList';
@@ -172,8 +173,9 @@ type PluginGridState = {
grid_cell_width: number, grid_cell_width: number,
grid_cell_columns: number, grid_cell_columns: number,
minimumItemWidth: number, minimumItemWidth: number,
favoritesList: FavoritesList favoritesList: FavoritesList,
gridItems: UiPlugin[]; uiPlugins: UiPlugin[]
//gridItems: UiPlugin[];
} }
@@ -181,8 +183,7 @@ type PluginGridState = {
export const LoadPluginDialog = export const LoadPluginDialog =
withStyles(pluginGridStyles, { withTheme: true })( withStyles(pluginGridStyles, { withTheme: true })(
class extends ResizeResponsiveComponent<PluginGridProps, PluginGridState> class extends ResizeResponsiveComponent<PluginGridProps, PluginGridState> {
{
model: PiPedalModel; model: PiPedalModel;
searchInputRef: React.RefObject<HTMLInputElement>; searchInputRef: React.RefObject<HTMLInputElement>;
@@ -208,7 +209,7 @@ export const LoadPluginDialog =
grid_cell_columns: this.getCellColumns(window.innerWidth), grid_cell_columns: this.getCellColumns(window.innerWidth),
minimumItemWidth: props.minimumItemWidth ? props.minimumItemWidth : 220, minimumItemWidth: props.minimumItemWidth ? props.minimumItemWidth : 220,
favoritesList: this.model.favorites.get(), favoritesList: this.model.favorites.get(),
gridItems: this.getFilteredPlugins("", filterType_, this.model.favorites.get()) uiPlugins: this.model.ui_plugins.get()
}; };
@@ -218,11 +219,12 @@ export const LoadPluginDialog =
this.handleSearchStringReady = this.handleSearchStringReady.bind(this); this.handleSearchStringReady = this.handleSearchStringReady.bind(this);
this.handleKeyPress = this.handleKeyPress.bind(this); this.handleKeyPress = this.handleKeyPress.bind(this);
this.handleFavoritesChanged = this.handleFavoritesChanged.bind(this); this.handleFavoritesChanged = this.handleFavoritesChanged.bind(this);
this.handlePluginsChanged = this.handlePluginsChanged.bind(this);
this.requestScrollTo(); this.requestScrollTo();
} }
nominal_column_width: number = 250; nominal_column_width: number = 350;
margin_reserve: number = 30; margin_reserve: number = 30;
getCellColumns(width: number) { getCellColumns(width: number) {
@@ -281,23 +283,31 @@ export const LoadPluginDialog =
this.setState( this.setState(
{ {
favoritesList: favorites, favoritesList: favorites,
gridItems: this.getFilteredPlugins(null, null, favorites)
}); });
} }
handlePluginsChanged(plugins: UiPlugin[]) {
this.setState(
{
uiPlugins: plugins
}
);
}
componentDidMount() { componentDidMount() {
super.componentDidMount(); super.componentDidMount();
this.updateWindowSize(); this.updateWindowSize();
window.addEventListener('resize', this.updateWindowSize); window.addEventListener('resize', this.updateWindowSize);
this.model.favorites.addOnChangedHandler(this.handleFavoritesChanged); this.model.favorites.addOnChangedHandler(this.handleFavoritesChanged);
let favorites = this.model.favorites.get(); this.model.ui_plugins.addOnChangedHandler(this.handlePluginsChanged);
this.setState({ this.setState({
favoritesList: favorites, favoritesList: this.model.favorites.get(),
gridItems: this.getFilteredPlugins(null, null, favorites) uiPlugins: this.model.ui_plugins.get()
}); });
} }
componentWillUnmount() { componentWillUnmount() {
this.model.ui_plugins.removeOnChangedHandler(this.handlePluginsChanged);
this.model.favorites.removeOnChangedHandler(this.handleFavoritesChanged); this.model.favorites.removeOnChangedHandler(this.handleFavoritesChanged);
super.componentWillUnmount(); super.componentWillUnmount();
window.removeEventListener('resize', this.updateWindowSize); window.removeEventListener('resize', this.updateWindowSize);
@@ -311,7 +321,6 @@ export const LoadPluginDialog =
this.setState({ this.setState({
search_string: "", search_string: "",
search_collapsed: true, search_collapsed: true,
gridItems: this.getFilteredPlugins("", null, null)
}); });
} }
this.requestScrollTo(); this.requestScrollTo();
@@ -332,7 +341,6 @@ export const LoadPluginDialog =
this.requestScrollTo(); this.requestScrollTo();
this.setState({ this.setState({
filterType: filterValue, filterType: filterValue,
gridItems: this.getFilteredPlugins(null, filterValue, null)
}); });
} }
onClearFilter(): void { onClearFilter(): void {
@@ -341,8 +349,7 @@ export const LoadPluginDialog =
if (this.state.filterType !== value) { if (this.state.filterType !== value) {
this.requestScrollTo(); this.requestScrollTo();
this.setState({ this.setState({
filterType: value, filterType: value
gridItems: this.getFilteredPlugins(null, value, null)
}); });
} }
} }
@@ -481,7 +488,8 @@ export const LoadPluginDialog =
return result; return result;
} }
getFilteredPlugins(searchString: string | null, filterType: PluginType | null, favoritesList: FavoritesList | null): UiPlugin[] { getFilteredPlugins(plugins: UiPlugin[], searchString: string | null, filterType: PluginType | null, favoritesList: FavoritesList | null): UiPlugin[] {
try {
if (searchString === null) { if (searchString === null) {
searchString = this.state.search_string; searchString = this.state.search_string;
} }
@@ -491,15 +499,14 @@ export const LoadPluginDialog =
if (favoritesList === null) { if (favoritesList === null) {
favoritesList = this.state.favoritesList; favoritesList = this.state.favoritesList;
} }
let plugins = this.model.ui_plugins.get();
let results: { score: number; plugin: UiPlugin }[] = []; let results: { score: number; plugin: UiPlugin }[] = [];
let searchFilter = new SearchFilter(searchString); let searchFilter = new SearchFilter(searchString);
let rootClass = this.model.plugin_classes.get(); let rootClass = this.model.plugin_classes.get();
for (let i = 0; i < plugins.length; ++i) { for (let i = 0; i < plugins.length; ++i) {
let plugin = plugins[i]; let plugin = plugins[i];
try {
if (filterType === PluginType.Plugin || rootClass.is_type_of(filterType, plugin.plugin_type)) { if (filterType === PluginType.Plugin || rootClass.is_type_of(filterType, plugin.plugin_type)) {
let score: number = 0; let score: number = 0;
if (plugin.is_vst3) { if (plugin.is_vst3) {
@@ -515,6 +522,10 @@ export const LoadPluginDialog =
results.push({ score: score, plugin: plugin }); results.push({ score: score, plugin: plugin });
} }
} }
} catch (e: any) {
alert("Bad plugin:" + (plugin?.name ?? "null") + " plugin type: " + (plugin?.plugin_type ?? "null"
+ " " + e.toString()));
}
} }
results.sort((left: { score: number; plugin: UiPlugin }, right: { score: number; plugin: UiPlugin }) => { results.sort((left: { score: number; plugin: UiPlugin }, right: { score: number; plugin: UiPlugin }) => {
if (right.score < left.score) return -1; if (right.score < left.score) return -1;
@@ -526,6 +537,10 @@ export const LoadPluginDialog =
t.push(results[i].plugin); t.push(results[i].plugin);
} }
return t; return t;
} catch (e: any) {
alert("getFilteredPlugins: " + e.toString());
throw e;
}
} }
changedSearchString?: string = undefined; changedSearchString?: string = undefined;
@@ -536,12 +551,14 @@ export const LoadPluginDialog =
this.scrollToRequested = true; this.scrollToRequested = true;
} }
private cachedGridItems: UiPlugin[] = []; // retained for the scrollTo callback.
handleScrollToCallback(element: FixedSizeGrid): void { handleScrollToCallback(element: FixedSizeGrid): void {
if (element) { if (element) {
if (this.scrollToRequested) { if (this.scrollToRequested) {
this.scrollToRequested = false; this.scrollToRequested = false;
let position = -1; let position = -1;
let gridItems = this.state.gridItems; let gridItems = this.cachedGridItems;
for (let i = 0; i < gridItems.length; ++i) { for (let i = 0; i < gridItems.length; ++i) {
if (this.state.selected_uri === gridItems[i].uri) { if (this.state.selected_uri === gridItems[i].uri) {
position = i; position = i;
@@ -560,8 +577,7 @@ export const LoadPluginDialog =
if (this.changedSearchString !== undefined) { if (this.changedSearchString !== undefined) {
this.requestScrollTo(); this.requestScrollTo();
this.setState({ this.setState({
search_string: this.changedSearchString, search_string: this.changedSearchString
gridItems: this.getFilteredPlugins(this.changedSearchString, null, null)
}); });
this.changedSearchString = undefined; this.changedSearchString = undefined;
} }
@@ -580,9 +596,8 @@ export const LoadPluginDialog =
2000); 2000);
} }
renderItem(row: number, column: number): React.ReactNode { renderItem(gridItems: UiPlugin[], row: number, column: number): React.ReactNode {
let item: number = (row) * this.gridColumnCount + (column); let item: number = (row) * this.gridColumnCount + (column);
let gridItems = this.state.gridItems;
if (item >= gridItems.length) { if (item >= gridItems.length) {
return (<div />); return (<div />);
} }
@@ -613,7 +628,7 @@ export const LoadPluginDialog =
{ {
isFavorite && ( isFavorite && (
<div style={{ flex: "0 0 auto" }}> <div style={{ flex: "0 0 auto" }}>
<StarBorderIcon sx={{ color: "#C80", fontSize: 16, marginRight: "2px" }} /> <StarIcon sx={{ color: "#C80", fontSize: 16, marginRight: "2px" }} />
</div> </div>
) )
} }
@@ -639,6 +654,7 @@ export const LoadPluginDialog =
} }
gridColumnCount: number = 1; gridColumnCount: number = 1;
render() { render() {
const { classes } = this.props; const { classes } = this.props;
@@ -693,8 +709,7 @@ export const LoadPluginDialog =
this.requestScrollTo(); this.requestScrollTo();
this.setState({ this.setState({
search_collapsed: true, search_collapsed: true,
search_string: "", search_string: ""
gridItems: this.getFilteredPlugins("", null, null)
}); });
} else { } else {
@@ -710,8 +725,7 @@ export const LoadPluginDialog =
this.requestScrollTo(); this.requestScrollTo();
this.setState({ this.setState({
search_collapsed: true, search_collapsed: true,
search_string: "", search_string: ""
gridItems: this.getFilteredPlugins("", null, null)
}); });
} else { } else {
@@ -756,7 +770,9 @@ export const LoadPluginDialog =
} }
let width = arg.width ?? 1; let width = arg.width ?? 1;
let height = arg.height ?? 1; let height = arg.height ?? 1;
let gridItems = this.state.gridItems; let gridItems = this.getFilteredPlugins(
this.state.uiPlugins, this.state.search_string, this.state.filterType, this.state.favoritesList);
this.cachedGridItems = gridItems;
let scrollRef = (grid: FixedSizeGrid) => { this.handleScrollToCallback(grid); } let scrollRef = (grid: FixedSizeGrid) => { this.handleScrollToCallback(grid); }
@@ -785,7 +801,7 @@ export const LoadPluginDialog =
{(arg: { columnIndex: number, rowIndex: number, style: CSSProperties }) => ( {(arg: { columnIndex: number, rowIndex: number, style: CSSProperties }) => (
<div style={arg.style} > <div style={arg.style} >
{ {
this.renderItem(arg.rowIndex, arg.columnIndex) this.renderItem(gridItems, arg.rowIndex, arg.columnIndex)
} }
</div> </div>
)} )}
@@ -829,11 +845,15 @@ export const LoadPluginDialog =
color: isFavorite ? "#F80" : "#CCC", display: (this.state.selected_uri ? "block" : "block"), color: isFavorite ? "#F80" : "#CCC", display: (this.state.selected_uri ? "block" : "block"),
position: "relative" position: "relative"
}}> }}>
{
this.state.selected_uri !== "uri://two-play/pipedal/pedalboard#Empty" && (
<IconButton color="inherit" aria-label="Set as favorite" <IconButton color="inherit" aria-label="Set as favorite"
onClick={() => { this.setFavorite(this.state.selected_uri, !isFavorite); }} onClick={() => { this.setFavorite(this.state.selected_uri, !isFavorite); }}
> >
<StarBorderIcon /> <StarBorderIcon />
</IconButton> </IconButton>
)
}
</div> </div>
</div> </div>
<div className={classes.bottom} style={{ height: 64 }}> <div className={classes.bottom} style={{ height: 64 }}>
+38 -19
View File
@@ -48,7 +48,14 @@ export enum State {
Error, Error,
Background, Background,
Reconnecting, Reconnecting,
ApplyingChanges ApplyingChanges,
ReloadingPlugins
};
export enum ReconnectReason {
Disconnected,
LoadingSettings,
ReloadingPlugins,
}; };
export type ControlValueChangedHandler = (key: string, value: number) => void; export type ControlValueChangedHandler = (key: string, value: number) => void;
@@ -356,6 +363,7 @@ export class PiPedalModel //implements PiPedalModel
lv2Path: string = ""; lv2Path: string = "";
webSocket?: PiPedalSocket; webSocket?: PiPedalSocket;
ui_plugins: ObservableProperty<UiPlugin[]> ui_plugins: ObservableProperty<UiPlugin[]>
= new ObservableProperty<UiPlugin[]>([]); = new ObservableProperty<UiPlugin[]>([]);
state: ObservableProperty<State> = new ObservableProperty<State>(State.Loading); state: ObservableProperty<State> = new ObservableProperty<State>(State.Loading);
@@ -426,13 +434,20 @@ export class PiPedalModel //implements PiPedalModel
onSocketReconnecting(retry: number, maxRetries: number): void { onSocketReconnecting(retry: number, maxRetries: number): void {
if (this.visibilityState.get() === VisibilityState.Hidden) return; if (this.visibilityState.get() === VisibilityState.Hidden) return;
//if (retry !== 0) { //if (retry !== 0) {
if (this.restartExpected) { switch (this.reconnectReason)
this.setState(State.ApplyingChanges); {
this.restartExpected = false; case ReconnectReason.Disconnected:
} else { default:
this.setState(State.Reconnecting); this.setState(State.Reconnecting);
break;
case ReconnectReason.LoadingSettings:
this.setState(State.ApplyingChanges);
break;
case ReconnectReason.ReloadingPlugins:
this.setState(State.ReloadingPlugins);
break;
} }
//}
} }
@@ -602,9 +617,16 @@ export class PiPedalModel //implements PiPedalModel
{ {
this.showAlert(body as string); this.showAlert(body as string);
} else {
throw new PiPedalStateError("Unrecognized message received from server: " + message);
} }
else if (message === "onLv2PluginsChanging")
{
this.onLv2PluginsChanging();
}
}
onLv2PluginsChanging() : void {
this.reconnectReason = ReconnectReason.ReloadingPlugins;
// this.webSocket?.reconnect(); // let the server do it for us.
} }
setError(message: string): void { setError(message: string): void {
this.errorMessage.set(message); this.errorMessage.set(message);
@@ -630,6 +652,11 @@ export class PiPedalModel //implements PiPedalModel
} }
private reconnectReason: ReconnectReason = ReconnectReason.Disconnected;
isReloading(): boolean {
return this.state.get() !== State.Ready;
}
getWebSocket(): PiPedalSocket { getWebSocket(): PiPedalSocket {
if (this.webSocket === undefined) { if (this.webSocket === undefined) {
@@ -654,7 +681,7 @@ export class PiPedalModel //implements PiPedalModel
this.androidHost?.setDisconnected(false); this.androidHost?.setDisconnected(false);
} }
this.restartExpected = false; this.reconnectReason = ReconnectReason.Disconnected;
if (this.visibilityState.get() === VisibilityState.Hidden) return; if (this.visibilityState.get() === VisibilityState.Hidden) return;
// reload state, but not configuration. // reload state, but not configuration.
@@ -1689,15 +1716,9 @@ export class PiPedalModel //implements PiPedalModel
this.alertMessage.set(message); this.alertMessage.set(message);
} }
restartExpected: boolean = false;
expectRestart() {
this.restartExpected = true;
}
setJackSettings(jackSettings: JackChannelSelection): void { setJackSettings(jackSettings: JackChannelSelection): void {
this.expectRestart(); this.reconnectReason = ReconnectReason.LoadingSettings;
this.webSocket?.send("setJackSettings", jackSettings); this.webSocket?.send("setJackSettings", jackSettings);
} }
@@ -2423,7 +2444,7 @@ export class PiPedalModel //implements PiPedalModel
resolve(); resolve();
}); });
this.restartExpected = true; this.reconnectReason = ReconnectReason.LoadingSettings;
this.webSocket?.reconnect(); // close immediately, and wait for recoonnect. this.webSocket?.reconnect(); // close immediately, and wait for recoonnect.
return result; return result;
} }
@@ -2619,10 +2640,8 @@ export class PiPedalModel //implements PiPedalModel
} }
} }
reloadRequested: boolean = false;
reloadPage() { reloadPage() {
this.reloadRequested = true;
// eslint-disable-next-line no-restricted-globals // eslint-disable-next-line no-restricted-globals
let url = window.location.href.split('#')[0]; let url = window.location.href.split('#')[0];
window.location.href = url; window.location.href = url;
+4 -4
View File
@@ -62,7 +62,7 @@ const styles = (theme: Theme) => createStyles({
transition: theme.transitions.create(['opacity', 'background-color'], { transition: theme.transitions.create(['opacity', 'background-color'], {
duration: theme.transitions.duration.shortest duration: theme.transitions.duration.shortest
}), }),
backgroundColor: theme.palette.secondary.main, backgroundColor: theme.palette.primary.main,
opacity: theme.palette.mode === 'light' ? 0.38 : 0.3 opacity: theme.palette.mode === 'light' ? 0.38 : 0.3
}, },
displayValue: { displayValue: {
@@ -525,7 +525,7 @@ const PluginControl =
if (control.isOnOffSwitch()) { if (control.isOnOffSwitch()) {
// normal gray unchecked state. // normal gray unchecked state.
return ( return (
<Switch checked={value !== 0} color="secondary" <Switch checked={value !== 0} color="primary"
onChange={(event) => { onChange={(event) => {
this.onCheckChanged(event.target.checked); this.onCheckChanged(event.target.checked);
}} }}
@@ -535,14 +535,14 @@ const PluginControl =
if (control.isAbToggle()) { if (control.isAbToggle()) {
// unchecked color is not gray. // unchecked color is not gray.
return ( return (
<Switch checked={value !== 0} color="secondary" <Switch checked={value !== 0} color="primary"
onChange={(event) => { onChange={(event) => {
this.onCheckChanged(event.target.checked); this.onCheckChanged(event.target.checked);
}} }}
classes={{ classes={{
track: this.props.classes.switchTrack track: this.props.classes.switchTrack
}} }}
style={{ color: this.props.theme.palette.secondary.main }} style={{ color: this.props.theme.palette.primary.main }}
/> />
); );
} else { } else {
+1 -1
View File
@@ -272,7 +272,7 @@ const WifiConfigDialog = withStyles(styles, { withTheme: true })(
<Switch <Switch
checked={this.state.enabled} checked={this.state.enabled}
onChange={(e: any) => this.handleEnableChanged(e)} onChange={(e: any) => this.handleEnableChanged(e)}
color="secondary" color="primary"
/> />
)} )}
label="Enable" label="Enable"
+1 -1
View File
@@ -325,7 +325,7 @@ const WifiDirectConfigDialog = withStyles(styles, { withTheme: true })(
<Switch <Switch
checked={this.state.enabled} checked={this.state.enabled}
onChange={(e: any) => this.handleEnableChanged(e)} onChange={(e: any) => this.handleEnableChanged(e)}
color="secondary" color="primary"
/> />
)} )}
label="Wi-Fi Direct Hotspot" label="Wi-Fi Direct Hotspot"
+4 -4
View File
@@ -44,7 +44,7 @@ const styles = (theme: Theme) => createStyles({
transition: theme.transitions.create(['opacity', 'background-color'], { transition: theme.transitions.create(['opacity', 'background-color'], {
duration: theme.transitions.duration.shortest duration: theme.transitions.duration.shortest
}), }),
backgroundColor: theme.palette.secondary.main, backgroundColor: theme.palette.primary.main,
opacity: theme.palette.mode === 'light' ? 0.38 : 0.3 opacity: theme.palette.mode === 'light' ? 0.38 : 0.3
} }
@@ -155,7 +155,7 @@ const ZoomedUiControl = withStyles(styles, { withTheme: true })(
if (control.isOnOffSwitch()) { if (control.isOnOffSwitch()) {
// normal gray unchecked state. // normal gray unchecked state.
return ( return (
<Switch checked={value !== 0} color="secondary" <Switch checked={value !== 0} color="primary"
onChange={(event) => { onChange={(event) => {
this.onCheckChanged(event.target.checked); this.onCheckChanged(event.target.checked);
}} }}
@@ -165,14 +165,14 @@ const ZoomedUiControl = withStyles(styles, { withTheme: true })(
if (control.isAbToggle()) { if (control.isAbToggle()) {
// unchecked color is not gray. // unchecked color is not gray.
return ( return (
<Switch checked={value !== 0} color="secondary" <Switch checked={value !== 0} color="primary"
onChange={(event) => { onChange={(event) => {
this.onCheckChanged(event.target.checked); this.onCheckChanged(event.target.checked);
}} }}
classes={{ classes={{
track: this.props.classes.switchTrack track: this.props.classes.switchTrack
}} }}
style={{ color: this.props.theme.palette.secondary.main }} style={{ color: this.props.theme.palette.primary.main }}
/> />
); );
} else { } else {
-45
View File
@@ -1,45 +0,0 @@
src/svg/
src/svg/fx_constant.svg
src/svg/fx_distortion.svg
src/svg/fx_oscillator.svg
src/svg/fx_filter.svg
src/svg/fx_function.svg
src/svg/fx_generator.svg
src/svg/fx_compressor.svg
src/svg/file_download_black_24dp.svg
src/svg/fx_filter_hp.svg
src/svg/fx_plugin.svg
src/svg/fx_error.svg
src/svg/fx_gate.svg
src/svg/ic_midi.svg
src/svg/ic_pluginpreset.svg
src/svg/fx_split_a.svg
src/svg/fx_utility.svg
src/svg/ic_pluginpreset2.svg
src/svg/fx_parametric_eq.svg
src/svg/fx_terminal.svg
src/svg/ic_presets.svg
src/svg/fx_flanger.svg
src/svg/fx_analyzer.svg
src/svg/fx_delay.svg
src/svg/fx_spectral.svg
src/svg/fx_chorus.svg
src/svg/fx_simulator.svg
src/svg/fx_mixer.svg
src/svg/fx_eq.svg
src/svg/fx_modulator.svg
src/svg/file_upload_black_24dp.svg
src/svg/fx_pitch.svg
src/svg/fx_phaser.svg
src/svg/fx_amplifier.svg
src/svg/fx_lr.svg
src/svg/fx_instrument.svg
src/svg/fx_reverb.svg
src/svg/fx_limiter.svg
src/svg/fx_empty.svg
src/svg/fx_dial.svg
src/svg/fx_flanger2.svg
src/svg/fx_split_b.svg
src/svg/fx_spatial.svg
src/svg/fx_converter.svg
src/svg/old_delete_outline_24dp.svg
+1
View File
@@ -132,6 +132,7 @@ else()
endif() endif()
set (PIPEDAL_SOURCES set (PIPEDAL_SOURCES
Lv2PluginChangeMonitor.cpp Lv2PluginChangeMonitor.hpp
WebServerConfig.cpp WebServerConfig.hpp WebServerConfig.cpp WebServerConfig.hpp
Locale.hpp Locale.cpp Locale.hpp Locale.cpp
Finally.hpp Finally.hpp
+2 -1
View File
@@ -18,6 +18,7 @@
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#include <iostream> #include <iostream>
#include <utility>
#include <stdlib.h> #include <stdlib.h>
#include <unistd.h> #include <unistd.h>
#include "CommandLineParser.hpp" #include "CommandLineParser.hpp"
@@ -603,7 +604,7 @@ void SetVarPermissions(
using namespace std::filesystem; using namespace std::filesystem;
try { try {
if (fs::exists(path)) { if (fs::exists(path)) {
chown(path.c_str(),uid,gid); std::ignore = chown(path.c_str(),uid,gid);
if (fs::is_directory(path)) { if (fs::is_directory(path)) {
fs::permissions(path, directoryPermissions, fs::perm_options::replace); fs::permissions(path, directoryPermissions, fs::perm_options::replace);
for (const auto& entry : fs::recursive_directory_iterator(path)) { for (const auto& entry : fs::recursive_directory_iterator(path)) {
+2 -1
View File
@@ -22,6 +22,7 @@
#include <sstream> #include <sstream>
#include <cstdint> #include <cstdint>
#include <string> #include <string>
#include <utility>
#include "json.hpp" #include "json.hpp"
#include "json_variant.hpp" #include "json_variant.hpp"
@@ -46,7 +47,7 @@ TEST_CASE("inverting_mutext test", "[inverting_mutex_test]")
std::thread thread( std::thread thread(
[&mutex]() mutable [&mutex]() mutable
{ {
nice(5); std::ignore = nice(5);
{ {
std::lock_guard<inverting_mutex> lock(mutex); std::lock_guard<inverting_mutex> lock(mutex);
SetThreadName("imTest"); SetThreadName("imTest");
+4 -6
View File
@@ -39,18 +39,16 @@ std::string timeTag()
using namespace std::chrono; using namespace std::chrono;
auto t = std::chrono::system_clock::now()- timeZero; auto now = std::chrono::system_clock::now();
std::time_t now_c = std::chrono::system_clock::to_time_t(now);
auto milliseconds = std::chrono::duration_cast<std::chrono::milliseconds>(now.time_since_epoch()) % 1000;
auto hours_ = duration_cast<hours>(t).count() % 24;
auto minutes_ = duration_cast<minutes>(t).count() % 60;
auto seconds_ = duration_cast<seconds>(t).count() % 60;
auto milliseconds_ = duration_cast<milliseconds>(t).count() % 1000;
std::stringstream s; std::stringstream s;
using namespace std; using namespace std;
s << setfill('0') << setw(2) << hours_ << ':' << setw(2) << minutes_ << ':' << setw(2) << seconds_ << "." << setw(3) << milliseconds_ << " "; s << std::put_time(std::localtime(&now_c), "%Y-%m-%d %H:%M:%S") << "." << std::setw(3) << std::setfill('0') << milliseconds.count() << " ";
return s.str(); return s.str();
} }
+142
View File
@@ -0,0 +1,142 @@
// Copyright (c) 2024 Robin Davies
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of
// this software and associated documentation files (the "Software"), to deal in
// 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 "Lv2PluginChangeMonitor.hpp"
#include "Lv2Log.hpp"
#include <sys/inotify.h>
#include <unistd.h>
#include <fcntl.h>
#include <poll.h>
#include <Finally.hpp>
#include <chrono>
#include <sys/eventfd.h>
#include "PiPedalModel.hpp"
using namespace pipedal;
Lv2PluginChangeMonitor::Lv2PluginChangeMonitor(PiPedalModel&model)
:model(model)
{
shutdown_eventfd = eventfd(0, 0);
monitorThread = std::make_unique<std::thread>([this]() { ThreadProc();});
}
void Lv2PluginChangeMonitor::Shutdown()
{
if (monitorThread)
{
terminateThread = true;
monitorThread->join();
monitorThread = nullptr;
}
}
Lv2PluginChangeMonitor::~Lv2PluginChangeMonitor()
{
Shutdown();
}
void Lv2PluginChangeMonitor::ThreadProc()
{
using clock = std::chrono::steady_clock;
int inotify_fd = inotify_init();
if (inotify_fd == -1) {
Lv2Log::error("Failed to initialize inotify");
return;
}
Finally f1 ([inotify_fd]() {
close(inotify_fd);
});
// Add the directory to the inotify watch list
int watch_descriptor = inotify_add_watch(inotify_fd, "/usr/lib/lv2", IN_MODIFY | IN_CREATE | IN_DELETE);
if (watch_descriptor == -1) {
Lv2Log::error("Failed to add directory to inotify watch list");
return;
}
Finally f2([inotify_fd,watch_descriptor]() {
inotify_rm_watch(inotify_fd, watch_descriptor);
});
bool updating = false;
clock::time_point updateTime;
// Monitor for file system events
while (true) {
struct pollfd pfds[2] = {
{.fd = inotify_fd, .events = POLLIN},
{.fd = shutdown_eventfd, .events = POLLIN}
};
int ret = poll(pfds, 2,500); // infinite wait
if (ret == -1) {
Lv2Log::error("Error in poll()");
break;
}
if (ret == 0)
{
// timeout.
if (updating && clock::now() >= updateTime)
{
updating = false;
model.OnLv2PluginsChanged();
}
continue;
}
if (pfds[1].revents & POLLIN) {
// Shutdown event received
break;
}
char buffer[4096];
ssize_t num_bytes = read(inotify_fd, buffer, sizeof(buffer));
if (num_bytes == -1) {
Lv2Log::error("Error reading from inotify");
break;
}
size_t i = 0;
bool updated = false;
while (i < static_cast<size_t>(num_bytes)) {
struct inotify_event* event = reinterpret_cast<struct inotify_event*>(&buffer[i]);
if (event->len > 0) {
if (event->mask & IN_MODIFY) {
updated = true;
} else if (event->mask & IN_CREATE) {
updated = true;
} else if (event->mask & IN_DELETE) {
updated = true;
}
}
i += sizeof(struct inotify_event) + event->len;
}
if (updated)
{
updating = true;
updateTime = clock::now() + std::chrono::duration_cast<clock::duration>(std::chrono::seconds(5));
}
}
// Clean up
}
+42
View File
@@ -0,0 +1,42 @@
// Copyright (c) 2024 Robin Davies
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of
// this software and associated documentation files (the "Software"), to deal in
// 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 <thread>
#include <atomic>
namespace pipedal
{
class PiPedalModel;
class Lv2PluginChangeMonitor {
public:
Lv2PluginChangeMonitor(PiPedalModel&model);
~Lv2PluginChangeMonitor();
void Shutdown();
private:
void ThreadProc();
int shutdown_eventfd;
bool isClosed = false;
PiPedalModel&model;
std::unique_ptr<std::thread> monitorThread;
std::atomic<bool> terminateThread {false};
};
}
+2 -1
View File
@@ -19,6 +19,7 @@
#include "MimeTypes.hpp" #include "MimeTypes.hpp"
#include "ss.hpp"
using namespace pipedal; using namespace pipedal;
@@ -136,7 +137,7 @@ static std::string toLower(const std::string&value)
void MimeTypes::AddMimeType(const std::string&extension_, const std::string&mimeType) void MimeTypes::AddMimeType(const std::string&extension_, const std::string&mimeType)
{ {
std::string extension = std::string(".") + toLower(extension_); std::string extension = SS("." << toLower(extension_));
mimeTypeToExtension[mimeType] = extension; mimeTypeToExtension[mimeType] = extension;
extensionToMimeType[extension] = mimeType; extensionToMimeType[extension] = mimeType;
if (mimeType.starts_with("audio/")) if (mimeType.starts_with("audio/"))
+63 -30
View File
@@ -18,6 +18,7 @@
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#include "pch.h" #include "pch.h"
#include <future>
#include "ServiceConfiguration.hpp" #include "ServiceConfiguration.hpp"
#include "AudioConfig.hpp" #include "AudioConfig.hpp"
#include "ConfigUtil.hpp" #include "ConfigUtil.hpp"
@@ -33,6 +34,7 @@
#include "RingBufferReader.hpp" #include "RingBufferReader.hpp"
#include "PiPedalUI.hpp" #include "PiPedalUI.hpp"
#include "atom_object.hpp" #include "atom_object.hpp"
#include "Lv2PluginChangeMonitor.hpp"
#ifndef NO_MLOCK #ifndef NO_MLOCK
#include <sys/mman.h> #include <sys/mman.h>
@@ -61,8 +63,8 @@ static std::string BytesToHex(const std::vector<uint8_t> &bytes)
} }
PiPedalModel::PiPedalModel() PiPedalModel::PiPedalModel()
: lv2Host(), : pluginHost(),
atomConverter(lv2Host.GetMapFeature()) atomConverter(pluginHost.GetMapFeature())
{ {
this->pedalboard = Pedalboard::MakeDefault(); this->pedalboard = Pedalboard::MakeDefault();
#if JACK_HOST #if JACK_HOST
@@ -98,6 +100,7 @@ void PiPedalModel::Close()
PiPedalModel::~PiPedalModel() PiPedalModel::~PiPedalModel()
{ {
pluginChangeMonitor = nullptr;
try try
{ {
adminClient.UnmonitorGovernor(); adminClient.UnmonitorGovernor();
@@ -133,12 +136,14 @@ PiPedalModel::~PiPedalModel()
void PiPedalModel::Init(const PiPedalConfiguration &configuration) void PiPedalModel::Init(const PiPedalConfiguration &configuration)
{ {
std::lock_guard<std::recursive_mutex> lock(mutex); // prevent callbacks while we're initializing.
this->configuration = configuration; this->configuration = configuration;
lv2Host.SetConfiguration(configuration); pluginHost.SetConfiguration(configuration);
storage.SetConfigRoot(configuration.GetDocRoot()); storage.SetConfigRoot(configuration.GetDocRoot());
storage.SetDataRoot(configuration.GetLocalStoragePath()); storage.SetDataRoot(configuration.GetLocalStoragePath());
storage.Initialize(); storage.Initialize();
lv2Host.SetPluginStoragePath(storage.GetPluginUploadDirectory()); pluginHost.SetPluginStoragePath(storage.GetPluginUploadDirectory());
this->systemMidiBindings = storage.GetSystemMidiBindings(); this->systemMidiBindings = storage.GetSystemMidiBindings();
@@ -157,7 +162,7 @@ void PiPedalModel::LoadLv2PluginInfo()
{ {
if (!std::filesystem::exists(pluginClassesPath)) if (!std::filesystem::exists(pluginClassesPath))
throw PiPedalException("File not found."); throw PiPedalException("File not found.");
lv2Host.LoadPluginClassesFromJson(pluginClassesPath); pluginHost.LoadPluginClassesFromJson(pluginClassesPath);
} }
catch (const std::exception &e) catch (const std::exception &e)
{ {
@@ -166,18 +171,19 @@ void PiPedalModel::LoadLv2PluginInfo()
throw PiPedalException(s.str().c_str()); throw PiPedalException(s.str().c_str());
} }
lv2Host.Load(configuration.GetLv2Path().c_str()); pluginChangeMonitor = std::make_unique<Lv2PluginChangeMonitor>(*this);
pluginHost.Load(configuration.GetLv2Path().c_str());
// Copy all presets out of Lilv data to json files // Copy all presets out of Lilv data to json files
// so that we can close lilv while we're actually // so that we can close lilv while we're actually
// running. // running.
for (const auto &plugin : lv2Host.GetPlugins()) for (const auto &plugin : pluginHost.GetPlugins())
{ {
if (plugin->has_factory_presets()) if (plugin->has_factory_presets())
{ {
if (!storage.HasPluginPresets(plugin->uri())) if (!storage.HasPluginPresets(plugin->uri()))
{ {
PluginPresets pluginPresets = lv2Host.GetFactoryPluginPresets(plugin->uri()); PluginPresets pluginPresets = pluginHost.GetFactoryPluginPresets(plugin->uri());
storage.SavePluginPresets(plugin->uri(), pluginPresets); storage.SavePluginPresets(plugin->uri(), pluginPresets);
} }
} }
@@ -191,7 +197,7 @@ void PiPedalModel::Load()
adminClient.MonitorGovernor(storage.GetGovernorSettings()); adminClient.MonitorGovernor(storage.GetGovernorSettings());
// lv2Host.Load(configuration.GetLv2Path().c_str()); // pluginHost.Load(configuration.GetLv2Path().c_str());
this->pedalboard = storage.GetCurrentPreset(); // the current *saved* preset. this->pedalboard = storage.GetCurrentPreset(); // the current *saved* preset.
@@ -211,7 +217,7 @@ void PiPedalModel::Load()
} }
UpdateDefaults(&this->pedalboard); UpdateDefaults(&this->pedalboard);
std::unique_ptr<AudioHost> p{AudioHost::CreateInstance(lv2Host.asIHost())}; std::unique_ptr<AudioHost> p{AudioHost::CreateInstance(pluginHost.asIHost())};
this->audioHost = std::move(p); this->audioHost = std::move(p);
this->audioHost->SetNotificationCallbacks(this); this->audioHost->SetNotificationCallbacks(this);
@@ -270,7 +276,7 @@ void PiPedalModel::Load()
JackChannelSelection selection = storage.GetJackChannelSelection(jackConfiguration); JackChannelSelection selection = storage.GetJackChannelSelection(jackConfiguration);
selection = selection.RemoveInvalidChannels(jackConfiguration); selection = selection.RemoveInvalidChannels(jackConfiguration);
this->lv2Host.OnConfigurationChanged(jackConfiguration, selection); this->pluginHost.OnConfigurationChanged(jackConfiguration, selection);
try try
{ {
audioHost->Open(this->jackServerSettings, selection); audioHost->Open(this->jackServerSettings, selection);
@@ -380,7 +386,6 @@ void PiPedalModel::OnNotifyMaybeLv2StateChanged(uint64_t instanceId)
item->stateUpdateCount(item->stateUpdateCount() + 1); item->stateUpdateCount(item->stateUpdateCount() + 1);
IPiPedalModelSubscriber **t = new IPiPedalModelSubscriber *[this->subscribers.size()]; IPiPedalModelSubscriber **t = new IPiPedalModelSubscriber *[this->subscribers.size()];
Lv2PluginState newState = item->lv2State(); Lv2PluginState newState = item->lv2State();
@@ -1151,7 +1156,6 @@ void PiPedalModel::RestartAudio()
// do a complete reload. // do a complete reload.
this->audioHost->SetPedalboard(nullptr); this->audioHost->SetPedalboard(nullptr);
this->jackConfiguration.AlsaInitialize(this->jackServerSettings); this->jackConfiguration.AlsaInitialize(this->jackServerSettings);
@@ -1183,7 +1187,7 @@ void PiPedalModel::RestartAudio()
} }
this->audioHost->Open(this->jackServerSettings, channelSelection); this->audioHost->Open(this->jackServerSettings, channelSelection);
this->lv2Host.OnConfigurationChanged(jackConfiguration, channelSelection); this->pluginHost.OnConfigurationChanged(jackConfiguration, channelSelection);
std::vector<std::string> errorMessages; std::vector<std::string> errorMessages;
@@ -1205,7 +1209,7 @@ void PiPedalModel::SetJackChannelSelection(int64_t clientId, const JackChannelSe
std::lock_guard<std::recursive_mutex> lock(mutex); // copy atomically. std::lock_guard<std::recursive_mutex> lock(mutex); // copy atomically.
this->storage.SetJackChannelSelection(channelSelection); this->storage.SetJackChannelSelection(channelSelection);
this->lv2Host.OnConfigurationChanged(jackConfiguration, channelSelection); this->pluginHost.OnConfigurationChanged(jackConfiguration, channelSelection);
} }
RestartAudio(); // no lock to avoid mutex deadlock when reader thread is sending notifications.. RestartAudio(); // no lock to avoid mutex deadlock when reader thread is sending notifications..
@@ -1283,7 +1287,7 @@ void PiPedalModel::OnNotifyMidiValueChanged(int64_t instanceId, int portIndex, f
} }
else else
{ {
pPluginInfo = lv2Host.GetPluginInfo(item->uri()); pPluginInfo = pluginHost.GetPluginInfo(item->uri());
} }
if (pPluginInfo) if (pPluginInfo)
{ {
@@ -1475,7 +1479,7 @@ void PiPedalModel::SendSetPatchProperty(
} }
}}; }};
LV2_URID urid = this->lv2Host.GetLv2Urid(propertyUri.c_str()); LV2_URID urid = this->pluginHost.GetLv2Urid(propertyUri.c_str());
RealtimePatchPropertyRequest *request = new RealtimePatchPropertyRequest( RealtimePatchPropertyRequest *request = new RealtimePatchPropertyRequest(
onRequestComplete, onRequestComplete,
@@ -1536,7 +1540,7 @@ void PiPedalModel::SendGetPatchProperty(
} }
}}; }};
LV2_URID urid = this->lv2Host.GetLv2Urid(uri.c_str()); LV2_URID urid = this->pluginHost.GetLv2Urid(uri.c_str());
RealtimePatchPropertyRequest *request = new RealtimePatchPropertyRequest( RealtimePatchPropertyRequest *request = new RealtimePatchPropertyRequest(
onRequestComplete, onRequestComplete,
clientId, instanceId, urid, onSuccess, onError); clientId, instanceId, urid, onSuccess, onError);
@@ -1669,7 +1673,7 @@ void PiPedalModel::SetJackServerSettings(const JackServerSettings &jackServerSet
FireJackConfigurationChanged(this->jackConfiguration); FireJackConfigurationChanged(this->jackConfiguration);
// restart the pedalboard on a new instance. // restart the pedalboard on a new instance.
std::shared_ptr<Lv2Pedalboard> lv2Pedalboard{this->lv2Host.CreateLv2Pedalboard(this->pedalboard)}; std::shared_ptr<Lv2Pedalboard> lv2Pedalboard{this->pluginHost.CreateLv2Pedalboard(this->pedalboard)};
this->lv2Pedalboard = lv2Pedalboard; this->lv2Pedalboard = lv2Pedalboard;
audioHost->SetPedalboard(lv2Pedalboard); audioHost->SetPedalboard(lv2Pedalboard);
@@ -1684,7 +1688,7 @@ void PiPedalModel::SetJackServerSettings(const JackServerSettings &jackServerSet
void PiPedalModel::UpdateDefaults(PedalboardItem *pedalboardItem) void PiPedalModel::UpdateDefaults(PedalboardItem *pedalboardItem)
{ {
std::shared_ptr<Lv2PluginInfo> pPlugin = lv2Host.GetPluginInfo(pedalboardItem->uri()); std::shared_ptr<Lv2PluginInfo> pPlugin = pluginHost.GetPluginInfo(pedalboardItem->uri());
if (!pPlugin) if (!pPlugin)
{ {
if (pedalboardItem->uri() == SPLIT_PEDALBOARD_ITEM_URI) if (pedalboardItem->uri() == SPLIT_PEDALBOARD_ITEM_URI)
@@ -1817,16 +1821,16 @@ void PiPedalModel::DeleteMidiListeners(int64_t clientId)
audioHost->SetListenForMidiEvent(midiEventListeners.size() != 0); audioHost->SetListenForMidiEvent(midiEventListeners.size() != 0);
} }
void PiPedalModel::OnPatchSetReply(uint64_t instanceId, LV2_URID patchSetProperty, const LV2_Atom *atomValue) void PiPedalModel::OnPatchSetReply(uint64_t instanceId, LV2_URID patchSetProperty, const LV2_Atom *atomValue)
{ {
std::lock_guard<std::recursive_mutex> lock(mutex); std::lock_guard<std::recursive_mutex> lock(mutex);
std::string propertyUri = lv2Host.GetMapFeature().UridToString(patchSetProperty); std::string propertyUri = pluginHost.GetMapFeature().UridToString(patchSetProperty);
{ {
PedalboardItem *item = pedalboard.GetItem((int64_t)instanceId); PedalboardItem *item = pedalboard.GetItem((int64_t)instanceId);
if (item == nullptr) return; if (item == nullptr)
return;
atom_object atomObject{atomValue}; atom_object atomObject{atomValue};
PedalboardItem::PropertyMap &properties = item->PatchProperties(); PedalboardItem::PropertyMap &properties = item->PatchProperties();
@@ -1924,7 +1928,7 @@ void PiPedalModel::MonitorPatchProperty(int64_t clientId, int64_t clientHandle,
LV2_URID propertyUrid = 0; LV2_URID propertyUrid = 0;
if (propertyUri.length() != 0) if (propertyUri.length() != 0)
{ {
propertyUrid = lv2Host.GetMapFeature().GetUrid(propertyUri.c_str()); propertyUrid = pluginHost.GetMapFeature().GetUrid(propertyUri.c_str());
} }
AtomOutputListener listener{clientId, clientHandle, instanceId, propertyUrid}; AtomOutputListener listener{clientId, clientHandle, instanceId, propertyUrid};
atomOutputListeners.push_back(listener); atomOutputListeners.push_back(listener);
@@ -2072,16 +2076,13 @@ std::string PiPedalModel::CreateNewSampleDirectory(const std::string&relativePat
{ {
std::lock_guard<std::recursive_mutex> lock(mutex); std::lock_guard<std::recursive_mutex> lock(mutex);
return storage.CreateNewSampleDirectory(relativePath, uiFileProperty); return storage.CreateNewSampleDirectory(relativePath, uiFileProperty);
} }
FilePropertyDirectoryTree::ptr PiPedalModel::GetFilePropertydirectoryTree(const UiFileProperty &uiFileProperty) FilePropertyDirectoryTree::ptr PiPedalModel::GetFilePropertydirectoryTree(const UiFileProperty &uiFileProperty)
{ {
std::lock_guard<std::recursive_mutex> lock(mutex); std::lock_guard<std::recursive_mutex> lock(mutex);
return storage.GetFilePropertydirectoryTree(uiFileProperty); return storage.GetFilePropertydirectoryTree(uiFileProperty);
} }
std::string PiPedalModel::UploadUserFile(const std::string &directory, const std::string &patchProperty, const std::string &filename, std::istream &stream, size_t contentLength) std::string PiPedalModel::UploadUserFile(const std::string &directory, const std::string &patchProperty, const std::string &filename, std::istream &stream, size_t contentLength)
{ {
return storage.UploadUserFile(directory, patchProperty, filename, stream, contentLength); return storage.UploadUserFile(directory, patchProperty, filename, stream, contentLength);
@@ -2100,15 +2101,14 @@ void PiPedalModel::CheckForResourceInitialization(Pedalboard &pedalboard)
{ {
if (!item->isSplit()) if (!item->isSplit())
{ {
lv2Host.CheckForResourceInitialization(item->uri(),storage.GetPluginUploadDirectory()); pluginHost.CheckForResourceInitialization(item->uri(), storage.GetPluginUploadDirectory());
} }
} }
} }
bool PiPedalModel::LoadCurrentPedalboard() bool PiPedalModel::LoadCurrentPedalboard()
{ {
Lv2PedalboardErrorList errorMessages; Lv2PedalboardErrorList errorMessages;
std::shared_ptr<Lv2Pedalboard> lv2Pedalboard{this->lv2Host.CreateLv2Pedalboard(this->pedalboard, errorMessages)}; std::shared_ptr<Lv2Pedalboard> lv2Pedalboard{this->pluginHost.CreateLv2Pedalboard(this->pedalboard, errorMessages)};
this->lv2Pedalboard = lv2Pedalboard; this->lv2Pedalboard = lv2Pedalboard;
// apply the error messages to the lv2Pedalboard. // apply the error messages to the lv2Pedalboard.
@@ -2139,3 +2139,36 @@ std::filesystem::path PiPedalModel::GetPluginUploadDirectory() const
{ {
return storage.GetPluginUploadDirectory(); return storage.GetPluginUploadDirectory();
} }
void PiPedalModel::OnLv2PluginsChanged()
{
Lv2Log::info("Lv2 plugins have changed. Reloading plugins.");
std::lock_guard<std::recursive_mutex> lock(mutex);
{
// Notify clients.
size_t n = subscribers.size();
IPiPedalModelSubscriber **t = new IPiPedalModelSubscriber *[n];
for (size_t i = 0; i < n; ++i)
{
t[i] = this->subscribers[i];
}
for (size_t i = 0; i < n; ++i)
{
t[i]->OnLv2PluginsChanging();
}
delete[] t;
}
std::thread(
[this]()
{
// wait for the message to propagate. It would be better to use some kind of flush()
// operation, but it's not clear how to do that with asyncio.
std::this_thread::sleep_for(std::chrono::milliseconds(2000));
restartListener();
})
.detach();
}
void PiPedalModel::SetRestartListener(std::function<void(void)> &&listener)
{
this->restartListener = std::move(listener);
}
+10 -4
View File
@@ -46,6 +46,7 @@ namespace pipedal
struct RealtimeMidiProgramRequest; struct RealtimeMidiProgramRequest;
struct RealtimeNextMidiProgramRequest; struct RealtimeNextMidiProgramRequest;
class Lv2PluginChangeMonitor;
class IPiPedalModelSubscriber class IPiPedalModelSubscriber
{ {
@@ -78,12 +79,16 @@ namespace pipedal
virtual void OnSystemMidiBindingsChanged(const std::vector<MidiBinding>&bindings) = 0; virtual void OnSystemMidiBindingsChanged(const std::vector<MidiBinding>&bindings) = 0;
//virtual void OnPatchPropertyChanged(int64_t clientId, int64_t instanceId,const std::string& propertyUri,const json_variant& value) = 0; //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 OnErrorMessage(const std::string&message) = 0;
virtual void OnLv2PluginsChanging() = 0;
virtual void Close() = 0; virtual void Close() = 0;
}; };
class PiPedalModel : private IAudioHostCallbacks class PiPedalModel : private IAudioHostCallbacks
{ {
private: private:
std::function<void(void)> restartListener;
std::unique_ptr<Lv2PluginChangeMonitor> pluginChangeMonitor;
std::unique_ptr<std::jthread> pingThread; std::unique_ptr<std::jthread> pingThread;
@@ -123,8 +128,8 @@ namespace pipedal
std::vector<AtomOutputListener> atomOutputListeners; std::vector<AtomOutputListener> atomOutputListeners;
JackServerSettings jackServerSettings; JackServerSettings jackServerSettings;
PluginHost lv2Host; PluginHost pluginHost;
AtomConverter atomConverter; // must be AFTER lv2Host! AtomConverter atomConverter; // must be AFTER pluginHost!
Pedalboard pedalboard; Pedalboard pedalboard;
Storage storage; Storage storage;
@@ -146,7 +151,6 @@ namespace pipedal
void UpdateDefaults(PedalboardItem *pedalboardItem); void UpdateDefaults(PedalboardItem *pedalboardItem);
void UpdateDefaults(Pedalboard *pedalboard); void UpdateDefaults(Pedalboard *pedalboard);
class VuSubscription class VuSubscription
{ {
public: public:
@@ -199,6 +203,8 @@ namespace pipedal
std::filesystem::path GetPluginUploadDirectory() const; std::filesystem::path GetPluginUploadDirectory() const;
void Close(); void Close();
void SetRestartListener(std::function<void(void)> &&listener);
void OnLv2PluginsChanged();
void SetOnboarding(bool value); void SetOnboarding(bool value);
void UpdateDnsSd(); void UpdateDnsSd();
@@ -210,7 +216,7 @@ namespace pipedal
void LoadLv2PluginInfo(); void LoadLv2PluginInfo();
void Load(); void Load();
const PluginHost &GetLv2Host() const { return lv2Host; } const PluginHost &GetLv2Host() const { return pluginHost; }
Pedalboard GetCurrentPedalboardCopy() Pedalboard GetCurrentPedalboardCopy()
{ {
std::lock_guard<std::recursive_mutex> guard(mutex); std::lock_guard<std::recursive_mutex> guard(mutex);
+9
View File
@@ -1587,6 +1587,12 @@ private:
Send("onLv2StateChanged",message); Send("onLv2StateChanged",message);
} }
virtual void OnLv2PluginsChanging() override {
Send("onLv2PluginsChanging",true);
Flush();
}
virtual void OnErrorMessage(const std::string&message) virtual void OnErrorMessage(const std::string&message)
{ {
Send("onErrorMessage",message); Send("onErrorMessage",message);
@@ -1778,6 +1784,9 @@ private:
} }
} }
void Flush() {
}
int outstandingNotifyAtomOutputs = 0; int outstandingNotifyAtomOutputs = 0;
class PendingNotifyAtomOutput class PendingNotifyAtomOutput
+1
View File
@@ -1470,6 +1470,7 @@ void PluginHost::CheckForResourceInitialization(const std::string &pluginUri,con
} }
} }
} }
// void PiPedalHostLogError(const std::string &error) // void PiPedalHostLogError(const std::string &error)
// { // {
// Lv2Log::error("%s",error.c_str()); // Lv2Log::error("%s",error.c_str());
+1
View File
@@ -813,6 +813,7 @@ namespace pipedal
public: public:
virtual MapFeature &GetMapFeature() { return this->mapFeature; } virtual MapFeature &GetMapFeature() { return this->mapFeature; }
void CheckForResourceInitialization(const std::string& pluginUri,const std::filesystem::path& pluginUploadDirectory); void CheckForResourceInitialization(const std::string& pluginUri,const std::filesystem::path& pluginUploadDirectory);
void ReloadPlugins();
private: private:
+3 -2
View File
@@ -39,7 +39,8 @@
#include <lv2/lv2plug.in/ns/ext/worker/worker.h> #include <lv2/lv2plug.in/ns/ext/worker/worker.h>
#include "Lv2Log.hpp" #include "Lv2Log.hpp"
#include <iostream> #include <iostream>
#include <unistd.h> // for nice() #include <unistd.h> // for nice(
#include <utility>
#include "util.hpp" #include "util.hpp"
using namespace pipedal; using namespace pipedal;
@@ -190,7 +191,7 @@ void HostWorkerThread::ThreadProc() noexcept
// run nice +2 (priority -2 on Windows) // run nice +2 (priority -2 on Windows)
SetThreadName("lv2_worker"); SetThreadName("lv2_worker");
errno = 0; errno = 0;
(void)nice(2); std::ignore = nice(2);
if (errno != 0) if (errno != 0)
{ {
std::cout << "Warning: Unable to run Lv2 schedule thread at nice +1" << std::endl; std::cout << "Warning: Unable to run Lv2 schedule thread at nice +1" << std::endl;
+27 -42
View File
@@ -47,19 +47,13 @@
#include <systemd/sd-daemon.h> #include <systemd/sd-daemon.h>
using namespace pipedal; using namespace pipedal;
#ifdef __ARM_ARCH_ISA_A64 #ifdef __ARM_ARCH_ISA_A64
#define AARCH64 #define AARCH64
#endif #endif
sem_t signalSemaphore;
bool HasAlsaDevice(const std::vector<AlsaDeviceInfo> devices, const std::string &deviceId) bool HasAlsaDevice(const std::vector<AlsaDeviceInfo> devices, const std::string &deviceId)
{ {
for (auto &device : devices) for (auto &device : devices)
@@ -77,21 +71,16 @@ public:
std::string message(int ev) const { return "error message"; } std::string message(int ev) const { return "error message"; }
}; };
static volatile bool g_SigBreak = false; static std::atomic<bool> g_SigBreak = false;
std::atomic<bool> ga_SigBreak { false }; static std::atomic<bool> g_restart = false;
void sig_handler(int signo) void sig_handler(int signo)
{ {
if (!g_SigBreak) // we're using sig_wait. No need to do anything.
{
g_SigBreak = true;
sem_post(&signalSemaphore);
}
} }
void throwSystemError(int error) void throwSystemError(int error)
{ {
} }
static bool isJackServiceRunning() static bool isJackServiceRunning()
@@ -104,8 +93,6 @@ static bool isJackServiceRunning()
int main(int argc, char *argv[]) int main(int argc, char *argv[])
{ {
sem_init(&signalSemaphore, 0, 0);
#ifndef WIN32 #ifndef WIN32
umask(002); // newly created files in /var/pipedal get 775-ish permissions, which improves debugging/live-service interaction. umask(002); // newly created files in /var/pipedal get 775-ish permissions, which improves debugging/live-service interaction.
@@ -176,7 +163,6 @@ int main(int argc, char *argv[])
} }
SetThreadName("main"); SetThreadName("main");
std::filesystem::path doc_root = parser.Arguments()[0]; std::filesystem::path doc_root = parser.Arguments()[0];
std::filesystem::path web_root = doc_root; std::filesystem::path web_root = doc_root;
if (parser.Arguments().size() >= 2) if (parser.Arguments().size() >= 2)
@@ -194,7 +180,7 @@ int main(int argc, char *argv[])
std::stringstream s; std::stringstream s;
s << "Unable to read configuration from '" << (doc_root / "config.json") << "'. (" << e.what() << ")"; s << "Unable to read configuration from '" << (doc_root / "config.json") << "'. (" << e.what() << ")";
Lv2Log::error(s.str()); Lv2Log::error(s.str());
return EXIT_FAILURE; return EXIT_SUCCESS; // indicate to systemd that we don't want a restart.
} }
Lv2Log::log_level(configuration.GetLogLevel()); Lv2Log::log_level(configuration.GetLogLevel());
@@ -225,7 +211,7 @@ int main(int argc, char *argv[])
std::stringstream s; std::stringstream s;
s << "Fatal error: " << e.what() << std::endl; s << "Fatal error: " << e.what() << std::endl;
Lv2Log::error(s.str()); Lv2Log::error(s.str());
return EXIT_FAILURE; return EXIT_SUCCESS; // indiate to systemd that we don't want a restart.
} }
try try
@@ -233,15 +219,25 @@ int main(int argc, char *argv[])
{ {
auto locale = Locale::GetInstance(); auto locale = Locale::GetInstance();
Lv2Log::info(SS("Locale: " << locale->CurrentLocale())); Lv2Log::info(SS("Locale: " << locale->CurrentLocale()));
try { try
{
auto collator = locale->GetCollator(); auto collator = locale->GetCollator();
} catch (std::exception&e) }
catch (std::exception &e)
{ {
Lv2Log::error(e.what()); Lv2Log::error(e.what());
return EXIT_SUCCESS; // tell systemd not to auto-restart. return EXIT_SUCCESS; // tell systemd not to auto-restart.
} }
} }
PiPedalModel model; PiPedalModel model;
model.SetRestartListener(
[]()
{
g_restart = true;
raise(SIGTERM); // throws an exception under gdb, but correctly restarts the service when running live.
});
model.Init(configuration); model.Init(configuration);
// Get heavy IO out of the way before letting dependent (Jack/ALSA) services run. // Get heavy IO out of the way before letting dependent (Jack/ALSA) services run.
@@ -254,10 +250,8 @@ int main(int argc, char *argv[])
(unsigned long)getpid()); (unsigned long)getpid());
} }
auto serverSettings = model.GetJackServerSettings(); auto serverSettings = model.GetJackServerSettings();
{ {
// Wait for selected audio device to be initialized. // Wait for selected audio device to be initialized.
// It may take some time for ALSA to publish all available devices when rebooting. // It may take some time for ALSA to publish all available devices when rebooting.
@@ -270,7 +264,8 @@ int main(int argc, char *argv[])
if (HasAlsaDevice(devices, serverSettings.GetAlsaInputDevice())) if (HasAlsaDevice(devices, serverSettings.GetAlsaInputDevice()))
{ {
Lv2Log::info(SS("Found ALSA device " << serverSettings.GetAlsaInputDevice() << ".")); Lv2Log::info(SS("Found ALSA device " << serverSettings.GetAlsaInputDevice() << "."));
} else }
else
{ {
for (int i = 0; i < 5; ++i) for (int i = 0; i < 5; ++i)
{ {
@@ -292,13 +287,16 @@ int main(int argc, char *argv[])
if (found) if (found)
{ {
Lv2Log::info(SS("Found ALSA device " << serverSettings.GetAlsaInputDevice() << ".")); Lv2Log::info(SS("Found ALSA device " << serverSettings.GetAlsaInputDevice() << "."));
} else { }
else
{
Lv2Log::info(SS("ALSA device " << serverSettings.GetAlsaInputDevice() << " not found.")); Lv2Log::info(SS("ALSA device " << serverSettings.GetAlsaInputDevice() << " not found."));
} }
} }
} else { }
else
{
Lv2Log::info("No ALSA device selected."); Lv2Log::info("No ALSA device selected.");
} }
// pre-cache device info before we let audio services run. // pre-cache device info before we let audio services run.
@@ -368,21 +366,6 @@ int main(int argc, char *argv[])
model.UpdateDnsSd(); // now that the server is running, publish a DNS-SD announcement. model.UpdateDnsSd(); // now that the server is running, publish a DNS-SD announcement.
SetThreadName("main"); SetThreadName("main");
// AARCH64 sem_wait pins CPU 100%.
// static_assert(std::atomic<bool>::is_always_lock_free);
// while (true)
// {
// auto sigBreak = ga_SigBreak.load();
// if (sigBreak)
// {
// break;
// }
// std::this_thread::sleep_for(std::chrono::milliseconds(50));
// }
{ {
sigwait(&sigSet, &sig); sigwait(&sigSet, &sig);
@@ -401,6 +384,8 @@ int main(int argc, char *argv[])
server->Join(); server->Join();
Lv2Log::info("Shutdown complete."); Lv2Log::info("Shutdown complete.");
if (g_restart) return EXIT_FAILURE; // indicate to systemd that we want a restart.
} }
catch (const std::exception &e) catch (const std::exception &e)
{ {
+1 -1
View File
@@ -16,7 +16,7 @@ User=pipedal_d
Group=pipedal_d Group=pipedal_d
Restart=always Restart=always
TimeoutStartSec=60 TimeoutStartSec=60
RestartSec=25 RestartSec=5
TimeoutStopSec=15 TimeoutStopSec=15
WorkingDirectory=/var/pipedal WorkingDirectory=/var/pipedal
+4 -1
View File
@@ -1,5 +1,8 @@
- change icons on gallery in index.html
- provide direct link to docs on both landing pages.
- feature: redo ./makepackage so that it runs without sudo.
- Bug: text Cursor hovering over the breadcrumb bar in file property dialog. - Bug: text Cursor hovering over the breadcrumb bar in file property dialog.
- Turning off wifi-direct sould re-enable NetworkManager. - Bug: Turning off wifi-direct sould re-enable NetworkManager.
- ToobML save/restore state. - ToobML save/restore state.