0.1.8-beta-4

This commit is contained in:
Robin Davies
2022-04-26 02:07:05 -04:00
parent 3db29505bb
commit c31bea9ecd
33 changed files with 886 additions and 572 deletions
+2 -2
View File
@@ -1,10 +1,10 @@
cmake_minimum_required(VERSION 3.16.0)
project(pipedal
VERSION 0.1.7
VERSION 0.1.8
DESCRIPTION "PiPedal Guitar Effect Pedal For Raspberry Pi"
HOMEPAGE_URL "https://rerdavies.github.io/pipedal"
)
set (DISPLAY_VERSION "v0.1.7-beta.3")
set (DISPLAY_VERSION "v0.1.8-beta-4")
set (CMAKE_INSTALL_PREFIX "/usr/")
+1 -1
View File
@@ -1,5 +1,5 @@
<img src='docs/Pi-Logo-3.png' /> <a href='docs/download.md'>v0.1.7-beta-3</a>
<img src='docs/Pi-Logo-3.png' /> <a href='docs/download.md'>v0.1.8-beta-4</a>
Use your Raspberry Pi as a guitar effects pedal. Configure and control PiPedal with your phone or tablet.
+23 -14
View File
@@ -4,6 +4,29 @@
"parent_uri": "http://lv2plug.in/ns/lv2core#PluginBase",
"plugin_type": "Plugin",
"children": [
{
"uri": "http://two_play.com/ns/pluginClass#ampsNode",
"display_name": "Amp",
"parent_uri": "http://lv2plug.in/ns/lv2core#Plugin",
"plugin_type": "PiPedalAmpsNode",
"children": [
{
"uri": "http://lv2plug.in/ns/lv2core#AmplifierPlugin",
"display_name": "Amplifier",
"parent_uri": "http://two_play.com/ns/pluginClass#amp",
"plugin_type": "AmplifierPlugin",
"children": [ ]
},
{
"uri": "http://lv2plug.in/ns/lv2core#SimulatorPlugin",
"display_name": "Simulator",
"parent_uri": "http://two_play.com/ns/pluginClass#amp",
"plugin_type": "SimulatorPlugin",
"children": [ ]
}
]
},
{
"uri": "http://lv2plug.in/ns/lv2core#DelayPlugin",
"display_name": "Delay",
@@ -40,13 +63,6 @@
"parent_uri": "http://lv2plug.in/ns/lv2core#Plugin",
"plugin_type": "DynamicsPlugin",
"children": [
{
"uri": "http://lv2plug.in/ns/lv2core#AmplifierPlugin",
"display_name": "Amplifier",
"parent_uri": "http://lv2plug.in/ns/lv2core#DynamicsPlugin",
"plugin_type": "AmplifierPlugin",
"children": [ ]
},
{
"uri": "http://lv2plug.in/ns/lv2core#CompressorPlugin",
"display_name": "Compressor",
@@ -207,13 +223,6 @@
}
]
},
{
"uri": "http://lv2plug.in/ns/lv2core#SimulatorPlugin",
"display_name": "Simulator",
"parent_uri": "http://lv2plug.in/ns/lv2core#Plugin",
"plugin_type": "SimulatorPlugin",
"children": [ ]
},
{
"uri": "http://lv2plug.in/ns/lv2core#SpatialPlugin",
"display_name": "Spatial",
+23 -14
View File
@@ -4,6 +4,29 @@
"parent_uri": "http://lv2plug.in/ns/lv2core#PluginBase",
"plugin_type": "Plugin",
"children": [
{
"uri": "http://two_play.com/ns/pluginClass#ampsNode",
"display_name": "Amp",
"parent_uri": "http://lv2plug.in/ns/lv2core#Plugin",
"plugin_type": "PiPedalAmpsNode",
"children": [
{
"uri": "http://lv2plug.in/ns/lv2core#AmplifierPlugin",
"display_name": "Amplifier",
"parent_uri": "http://two_play.com/ns/pluginClass#amp",
"plugin_type": "AmplifierPlugin",
"children": [ ]
},
{
"uri": "http://lv2plug.in/ns/lv2core#SimulatorPlugin",
"display_name": "Simulator",
"parent_uri": "http://two_play.com/ns/pluginClass#amp",
"plugin_type": "SimulatorPlugin",
"children": [ ]
}
]
},
{
"uri": "http://lv2plug.in/ns/lv2core#DelayPlugin",
"display_name": "Delay",
@@ -40,13 +63,6 @@
"parent_uri": "http://lv2plug.in/ns/lv2core#Plugin",
"plugin_type": "DynamicsPlugin",
"children": [
{
"uri": "http://lv2plug.in/ns/lv2core#AmplifierPlugin",
"display_name": "Amplifier",
"parent_uri": "http://lv2plug.in/ns/lv2core#DynamicsPlugin",
"plugin_type": "AmplifierPlugin",
"children": [ ]
},
{
"uri": "http://lv2plug.in/ns/lv2core#CompressorPlugin",
"display_name": "Compressor",
@@ -207,13 +223,6 @@
}
]
},
{
"uri": "http://lv2plug.in/ns/lv2core#SimulatorPlugin",
"display_name": "Simulator",
"parent_uri": "http://lv2plug.in/ns/lv2core#Plugin",
"plugin_type": "SimulatorPlugin",
"children": [ ]
},
{
"uri": "http://lv2plug.in/ns/lv2core#SpatialPlugin",
"display_name": "Spatial",
+2 -2
View File
@@ -4,14 +4,14 @@
Download the most recent Debian (.deb) package for your platform:
- [Ubuntu or Raspberry Pi OS (64-bit)](https://github.com/rerdavies/pipedal/releases/download/v0.1.7-beta-3/pipedal_0.1.7_arm64.deb) v0.1.7-beta-3
- [Ubuntu or Raspberry Pi OS (64-bit)](https://github.com/rerdavies/pipedal/releases/download/v0.1.8-beta-4/pipedal_0.1.8_arm64.deb) v0.1.8-beta-4
Install the package by running
```
sudo apt update
cd ~/Downloads
sudo apt-get install ./pipedal_0.1.7_arm64.deb
sudo apt-get install ./pipedal_0.1.8_arm64.deb
```
--------
+2 -2
View File
@@ -4,14 +4,14 @@
Download the most recent Debian (.deb) package for your platform:
- [Ubuntu or Raspberry Pi OS (64-bit)](https://github.com/rerdavies/pipedal/releases/download/v0.1.7-beta-3/pipedal_0.1.7_arm64.deb) v0.1.7-beta-3
- [Ubuntu or Raspberry Pi OS (64-bit)](https://github.com/rerdavies/pipedal/releases/download/v0.1.8-beta-4/pipedal_0.1.8_arm64.deb) v0.1.8-beta-4
Install the package by running
```
sudo apt update
cd ~/Downloads
sudo apt-get install ./pipedal_0.1.7_arm64.deb
sudo apt-get install ./pipedal_0.1.8_arm64.deb
```
Follow the instructions in [_Configuring PiPedal After Installation_](https://rerdavies.github.io/pipedal/Configuring.html) to complete the installation.
+1 -1
View File
@@ -1,6 +1,6 @@
<i>v0.1.7-beta-3</i>
<i>v0.1.8-beta-4</i>
Use your Raspberry Pi as a guitar effects pedal. Configure and control PiPedal with your phone or tablet.
+91 -81
View File
@@ -21,87 +21,97 @@ add_custom_command(
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/react
DEPENDS
src/SearchControl.tsx
src/SplitControlView.tsx
src/WifiConfigSettings.tsx
src/FullScreenIME.tsx
src/JackHostStatus.tsx
src/SearchFilter.tsx
src/MainPage.tsx
src/PiPedalModel.tsx
src/PluginPresetSelector.tsx
src/Pedal.tsx
src/ToobMLView.tsx
src/SettingsDialog.tsx
src/PiPedalSocket.tsx
src/ToobFrequencyResponseView.tsx
src/ControlViewFactory.tsx
src/Utility.tsx
src/ToobToneStackView.tsx
src/SelectHoverBackground.tsx
src/MidiBindingsDialog.tsx
src/TemporaryDrawer.tsx
src/GxTunerView.tsx
src/ToobInputStageView.tsx
src/DraggableGrid.tsx
src/JackStatusView.tsx
src/PluginControl.tsx
src/XxxSnippet.tsx
src/PluginPreset.tsx
src/Jack.tsx
src/AlsaDeviceInfo.tsx
src/MidiBinding.tsx
src/PiPedalError.tsx
src/IControlViewFactory.tsx
src/Lv2Plugin.tsx
src/PedalBoard.tsx
src/PedalBoardView.tsx
src/PresetDialog.tsx
src/AppThemed.tsx
src/ZoomedDial.tsx
src/RenameDialog.tsx
src/SplitUiControls.tsx
src/GovernorSettings.tsx
src/AboutDialog.tsx
src/ToobSpectrumResponseView.tsx
src/PluginControlView.tsx
src/NoChangePassword.tsx
src/ToobSpectrumAnalyzerView.tsx
src/MidiBindingView.tsx
src/GxTunerControl.tsx
src/Banks.tsx
src/PluginPresetsDialog.tsx
src/BankDialog.tsx
src/UploadDialog.tsx
src/ToobPowerStage2View.tsx
src/index.tsx
src/Units.tsx
src/VuMeter.tsx
src/App.test.tsx
src/DialogEx.tsx
src/StringBuilder.tsx
src/SelectChannelsDialog.tsx
src/Draggable.tsx
src/ZoomedUiControl.tsx
src/Rect.tsx
src/PluginClass.tsx
src/PluginIcon.tsx
src/App.tsx
src/ResizeResponsiveComponent.tsx
src/SvgPathBuilder.tsx
src/NumericInput.tsx
src/JackServerSettings.tsx
src/ListSelectDialog.tsx
src/JackServerSettingsDialog.tsx
src/ObservableProperty.tsx
src/SelectMidiChannelsDialog.tsx
src/WifiChannel.tsx
src/LoadPluginDialog.tsx
src/WifiConfigDialog.tsx
src/PluginInfoDialog.tsx
src/ToobWaveShapeView.tsx
src/ToobCabSimView.tsx
src/PresetSelector.tsx
src/SearchControl.tsx
src/SplitControlView.tsx
src/WifiConfigSettings.tsx
src/FullScreenIME.tsx
src/JackHostStatus.tsx
src/index.css
src/setupTests.ts
src/SearchFilter.tsx
src/MainPage.tsx
src/PiPedalModel.tsx
src/AppThemed.css
src/PluginPresetSelector.tsx
src/Pedal.tsx
src/ToobMLView.tsx
src/SettingsDialog.tsx
src/WifiDirectConfigDialog.tsx
src/WifiDirectConfigSettings.tsx
src/PiPedalSocket.tsx
src/ToobFrequencyResponseView.tsx
src/ControlViewFactory.tsx
src/Utility.tsx
src/ToobToneStackView.tsx
src/SelectHoverBackground.tsx
src/MidiBindingsDialog.tsx
src/TemporaryDrawer.tsx
src/GxTunerView.tsx
src/ToobInputStageView.tsx
src/DraggableGrid.tsx
src/JackStatusView.tsx
src/PluginControl.tsx
src/XxxSnippet.tsx
src/PluginPreset.tsx
src/Jack.tsx
src/AlsaDeviceInfo.tsx
src/MidiBinding.tsx
src/PiPedalError.tsx
src/IControlViewFactory.tsx
src/Lv2Plugin.tsx
src/PedalBoard.tsx
src/AndroidHost.tsx
src/PedalBoardView.tsx
src/PresetDialog.tsx
src/AppThemed.tsx
src/ZoomedDial.tsx
src/RenameDialog.tsx
src/SplitUiControls.tsx
src/GovernorSettings.tsx
src/AboutDialog.tsx
src/ToobSpectrumResponseView.tsx
src/PluginControlView.tsx
src/NoChangePassword.tsx
src/ToobSpectrumAnalyzerView.tsx
src/MidiBindingView.tsx
src/GxTunerControl.tsx
src/Banks.tsx
src/PluginPresetsDialog.tsx
src/BankDialog.tsx
src/UploadDialog.tsx
src/ToobPowerStage2View.tsx
src/index.tsx
src/Units.tsx
src/VuMeter.tsx
src/App.test.tsx
src/DialogEx.tsx
src/StringBuilder.tsx
src/SelectChannelsDialog.tsx
src/Draggable.tsx
src/ZoomedUiControl.tsx
src/logo.svg
src/Rect.tsx
src/PluginClass.tsx
src/reportWebVitals.ts
src/PluginIcon.tsx
src/App.tsx
src/ResizeResponsiveComponent.tsx
src/SvgPathBuilder.tsx
src/NumericInput.tsx
src/JackServerSettings.tsx
src/ListSelectDialog.tsx
src/react-app-env.d.ts
src/JackServerSettingsDialog.tsx
src/ObservableProperty.tsx
src/SelectMidiChannelsDialog.tsx
src/WifiChannel.tsx
src/LoadPluginDialog.tsx
src/WifiConfigDialog.tsx
src/PluginInfoDialog.tsx
src/ToobWaveShapeView.tsx
src/ToobCabSimView.tsx
src/PresetSelector.tsx
src/OkCancelDialog.tsx
public/css/roboto.css
public/fonts/Roboto-BlackItalic.woff2
+273 -264
View File
@@ -4,273 +4,282 @@
"parent_uri": "http://lv2plug.in/ns/lv2core#PluginBase",
"plugin_type": "Plugin",
"children": [
{
"uri": "http://lv2plug.in/ns/lv2core#DelayPlugin",
"display_name": "Delay",
"parent_uri": "http://lv2plug.in/ns/lv2core#Plugin",
"plugin_type": "DelayPlugin",
"children": [
{
"uri": "http://lv2plug.in/ns/lv2core#ReverbPlugin",
"display_name": "Reverb",
"parent_uri": "http://lv2plug.in/ns/lv2core#DelayPlugin",
"plugin_type": "ReverbPlugin",
"children": []
}
]
},
{
"uri": "http://lv2plug.in/ns/lv2core#DistortionPlugin",
"display_name": "Distortion",
"parent_uri": "http://lv2plug.in/ns/lv2core#Plugin",
"plugin_type": "DistortionPlugin",
"children": [
{
"uri": "http://lv2plug.in/ns/lv2core#WaveshaperPlugin",
"display_name": "Waveshaper",
"parent_uri": "http://lv2plug.in/ns/lv2core#DistortionPlugin",
"plugin_type": "WaveshaperPlugin",
"children": []
}
]
},
{
"uri": "http://lv2plug.in/ns/lv2core#DynamicsPlugin",
"display_name": "Dynamics",
"parent_uri": "http://lv2plug.in/ns/lv2core#Plugin",
"plugin_type": "DynamicsPlugin",
"children": [
{
"uri": "http://lv2plug.in/ns/lv2core#AmplifierPlugin",
"display_name": "Amplifier",
"parent_uri": "http://lv2plug.in/ns/lv2core#DynamicsPlugin",
"plugin_type": "AmplifierPlugin",
"children": []
},
{
"uri": "http://lv2plug.in/ns/lv2core#CompressorPlugin",
"display_name": "Compressor",
"parent_uri": "http://lv2plug.in/ns/lv2core#DynamicsPlugin",
"plugin_type": "CompressorPlugin",
"children": []
},
{
"uri": "http://lv2plug.in/ns/lv2core#EnvelopePlugin",
"display_name": "Envelope",
"parent_uri": "http://lv2plug.in/ns/lv2core#DynamicsPlugin",
"plugin_type": "EnvelopePlugin",
"children": []
},
{
"uri": "http://lv2plug.in/ns/lv2core#ExpanderPlugin",
"display_name": "Expander",
"parent_uri": "http://lv2plug.in/ns/lv2core#DynamicsPlugin",
"plugin_type": "ExpanderPlugin",
"children": []
},
{
"uri": "http://lv2plug.in/ns/lv2core#GatePlugin",
"display_name": "Gate",
"parent_uri": "http://lv2plug.in/ns/lv2core#DynamicsPlugin",
"plugin_type": "GatePlugin",
"children": []
},
{
"uri": "http://lv2plug.in/ns/lv2core#LimiterPlugin",
"display_name": "Limiter",
"parent_uri": "http://lv2plug.in/ns/lv2core#DynamicsPlugin",
"plugin_type": "LimiterPlugin",
"children": []
}
]
},
{
"uri": "http://lv2plug.in/ns/lv2core#FilterPlugin",
"display_name": "Filter",
"parent_uri": "http://lv2plug.in/ns/lv2core#Plugin",
"plugin_type": "FilterPlugin",
"children": [
{
"uri": "http://lv2plug.in/ns/lv2core#AllpassPlugin",
"display_name": "Allpass",
"parent_uri": "http://lv2plug.in/ns/lv2core#FilterPlugin",
"plugin_type": "AllpassPlugin",
"children": []
},
{
"uri": "http://lv2plug.in/ns/lv2core#BandpassPlugin",
"display_name": "Bandpass",
"parent_uri": "http://lv2plug.in/ns/lv2core#FilterPlugin",
"plugin_type": "BandpassPlugin",
"children": []
},
{
"uri": "http://lv2plug.in/ns/lv2core#CombPlugin",
"display_name": "Comb",
"parent_uri": "http://lv2plug.in/ns/lv2core#FilterPlugin",
"plugin_type": "CombPlugin",
"children": []
},
{
"uri": "http://lv2plug.in/ns/lv2core#EQPlugin",
"display_name": "Equaliser",
"parent_uri": "http://lv2plug.in/ns/lv2core#FilterPlugin",
"plugin_type": "EQPlugin",
"children": [
{
"uri": "http://lv2plug.in/ns/lv2core#MultiEQPlugin",
"display_name": "Multiband",
"parent_uri": "http://lv2plug.in/ns/lv2core#EQPlugin",
"plugin_type": "MultiEQPlugin",
"children": []
},
{
"uri": "http://lv2plug.in/ns/lv2core#ParaEQPlugin",
"display_name": "Parametric",
"parent_uri": "http://lv2plug.in/ns/lv2core#EQPlugin",
"plugin_type": "ParaEQPlugin",
"children": []
}
]
},
{
"uri": "http://lv2plug.in/ns/lv2core#HighpassPlugin",
"display_name": "Highpass",
"parent_uri": "http://lv2plug.in/ns/lv2core#FilterPlugin",
"plugin_type": "HighpassPlugin",
"children": []
},
{
"uri": "http://lv2plug.in/ns/lv2core#LowpassPlugin",
"display_name": "Lowpass",
"parent_uri": "http://lv2plug.in/ns/lv2core#FilterPlugin",
"plugin_type": "LowpassPlugin",
"children": []
}
]
},
{
"uri": "http://lv2plug.in/ns/lv2core#GeneratorPlugin",
"display_name": "Generator",
"parent_uri": "http://lv2plug.in/ns/lv2core#Plugin",
"plugin_type": "GeneratorPlugin",
"children": [
{
"uri": "http://lv2plug.in/ns/lv2core#ConstantPlugin",
"display_name": "Constant",
"parent_uri": "http://lv2plug.in/ns/lv2core#GeneratorPlugin",
"plugin_type": "ConstantPlugin",
"children": []
},
{
"uri": "http://lv2plug.in/ns/lv2core#InstrumentPlugin",
"display_name": "Instrument",
"parent_uri": "http://lv2plug.in/ns/lv2core#GeneratorPlugin",
"plugin_type": "InstrumentPlugin",
"children": []
},
{
"uri": "http://lv2plug.in/ns/lv2core#OscillatorPlugin",
"display_name": "Oscillator",
"parent_uri": "http://lv2plug.in/ns/lv2core#GeneratorPlugin",
"plugin_type": "OscillatorPlugin",
"children": []
}
]
},
{
"uri": "http://lv2plug.in/ns/lv2core#ModulatorPlugin",
"display_name": "Modulator",
"parent_uri": "http://lv2plug.in/ns/lv2core#Plugin",
"plugin_type": "ModulatorPlugin",
"children": [
{
"uri": "http://lv2plug.in/ns/lv2core#ChorusPlugin",
"display_name": "Chorus",
"parent_uri": "http://lv2plug.in/ns/lv2core#ModulatorPlugin",
"plugin_type": "ChorusPlugin",
"children": []
},
{
"uri": "http://lv2plug.in/ns/lv2core#FlangerPlugin",
"display_name": "Flanger",
"parent_uri": "http://lv2plug.in/ns/lv2core#ModulatorPlugin",
"plugin_type": "FlangerPlugin",
"children": []
},
{
"uri": "http://lv2plug.in/ns/lv2core#PhaserPlugin",
"display_name": "Phaser",
"parent_uri": "http://lv2plug.in/ns/lv2core#ModulatorPlugin",
"plugin_type": "PhaserPlugin",
"children": []
}
]
},
{
{
"uri": "http://two_play.com/ns/pluginClass#amp",
"display_name": "Amps",
"parent_uri": "http://lv2plug.in/ns/lv2core#Plugin",
"plugin_type": "PiPedalAmpsNode",
"children": [
{
"uri": "http://lv2plug.in/ns/lv2core#AmplifierPlugin",
"display_name": "Amplifier",
"parent_uri": "http://two_play.com/ns/pluginClass#amp",
"plugin_type": "AmplifierPlugin",
"children": [ ]
},
{
"uri": "http://lv2plug.in/ns/lv2core#SimulatorPlugin",
"display_name": "Simulator",
"parent_uri": "http://lv2plug.in/ns/lv2core#Plugin",
"parent_uri": "http://two_play.com/ns/pluginClass#amp",
"plugin_type": "SimulatorPlugin",
"children": []
},
{
"uri": "http://lv2plug.in/ns/lv2core#SpatialPlugin",
"display_name": "Spatial",
"parent_uri": "http://lv2plug.in/ns/lv2core#Plugin",
"plugin_type": "SpatialPlugin",
"children": []
},
{
"uri": "http://lv2plug.in/ns/lv2core#SpectralPlugin",
"display_name": "Spectral",
"parent_uri": "http://lv2plug.in/ns/lv2core#Plugin",
"plugin_type": "SpectralPlugin",
"children": [ ]
}
]
},
{
"uri": "http://lv2plug.in/ns/lv2core#DelayPlugin",
"display_name": "Delay",
"parent_uri": "http://lv2plug.in/ns/lv2core#Plugin",
"plugin_type": "DelayPlugin",
"children": [
{
"uri": "http://lv2plug.in/ns/lv2core#ReverbPlugin",
"display_name": "Reverb",
"parent_uri": "http://lv2plug.in/ns/lv2core#DelayPlugin",
"plugin_type": "ReverbPlugin",
"children": [ ]
}
]
},
{
"uri": "http://lv2plug.in/ns/lv2core#DistortionPlugin",
"display_name": "Distortion",
"parent_uri": "http://lv2plug.in/ns/lv2core#Plugin",
"plugin_type": "DistortionPlugin",
"children": [
{
"uri": "http://lv2plug.in/ns/lv2core#WaveshaperPlugin",
"display_name": "Waveshaper",
"parent_uri": "http://lv2plug.in/ns/lv2core#DistortionPlugin",
"plugin_type": "WaveshaperPlugin",
"children": [ ]
}
]
},
{
"uri": "http://lv2plug.in/ns/lv2core#DynamicsPlugin",
"display_name": "Dynamics",
"parent_uri": "http://lv2plug.in/ns/lv2core#Plugin",
"plugin_type": "DynamicsPlugin",
"children": [
{
"uri": "http://lv2plug.in/ns/lv2core#CompressorPlugin",
"display_name": "Compressor",
"parent_uri": "http://lv2plug.in/ns/lv2core#DynamicsPlugin",
"plugin_type": "CompressorPlugin",
"children": [ ]
},
{
"uri": "http://lv2plug.in/ns/lv2core#EnvelopePlugin",
"display_name": "Envelope",
"parent_uri": "http://lv2plug.in/ns/lv2core#DynamicsPlugin",
"plugin_type": "EnvelopePlugin",
"children": [ ]
},
{
"uri": "http://lv2plug.in/ns/lv2core#ExpanderPlugin",
"display_name": "Expander",
"parent_uri": "http://lv2plug.in/ns/lv2core#DynamicsPlugin",
"plugin_type": "ExpanderPlugin",
"children": [ ]
},
{
"uri": "http://lv2plug.in/ns/lv2core#GatePlugin",
"display_name": "Gate",
"parent_uri": "http://lv2plug.in/ns/lv2core#DynamicsPlugin",
"plugin_type": "GatePlugin",
"children": [ ]
},
{
"uri": "http://lv2plug.in/ns/lv2core#LimiterPlugin",
"display_name": "Limiter",
"parent_uri": "http://lv2plug.in/ns/lv2core#DynamicsPlugin",
"plugin_type": "LimiterPlugin",
"children": [ ]
}
]
},
{
"uri": "http://lv2plug.in/ns/lv2core#FilterPlugin",
"display_name": "Filter",
"parent_uri": "http://lv2plug.in/ns/lv2core#Plugin",
"plugin_type": "FilterPlugin",
"children": [
{
"uri": "http://lv2plug.in/ns/lv2core#AllpassPlugin",
"display_name": "Allpass",
"parent_uri": "http://lv2plug.in/ns/lv2core#FilterPlugin",
"plugin_type": "AllpassPlugin",
"children": [ ]
},
{
"uri": "http://lv2plug.in/ns/lv2core#BandpassPlugin",
"display_name": "Bandpass",
"parent_uri": "http://lv2plug.in/ns/lv2core#FilterPlugin",
"plugin_type": "BandpassPlugin",
"children": [ ]
},
{
"uri": "http://lv2plug.in/ns/lv2core#CombPlugin",
"display_name": "Comb",
"parent_uri": "http://lv2plug.in/ns/lv2core#FilterPlugin",
"plugin_type": "CombPlugin",
"children": [ ]
},
{
"uri": "http://lv2plug.in/ns/lv2core#EQPlugin",
"display_name": "Equaliser",
"parent_uri": "http://lv2plug.in/ns/lv2core#FilterPlugin",
"plugin_type": "EQPlugin",
"children": [
{
"uri": "http://lv2plug.in/ns/lv2core#PitchPlugin",
"display_name": "Pitch Shifter",
"parent_uri": "http://lv2plug.in/ns/lv2core#SpectralPlugin",
"plugin_type": "PitchPlugin",
"children": []
}
{
"uri": "http://lv2plug.in/ns/lv2core#MultiEQPlugin",
"display_name": "Multiband",
"parent_uri": "http://lv2plug.in/ns/lv2core#EQPlugin",
"plugin_type": "MultiEQPlugin",
"children": [ ]
},
{
"uri": "http://lv2plug.in/ns/lv2core#ParaEQPlugin",
"display_name": "Parametric",
"parent_uri": "http://lv2plug.in/ns/lv2core#EQPlugin",
"plugin_type": "ParaEQPlugin",
"children": [ ]
}
]
},
{
"uri": "http://lv2plug.in/ns/lv2core#UtilityPlugin",
"display_name": "Utility",
"parent_uri": "http://lv2plug.in/ns/lv2core#Plugin",
"plugin_type": "UtilityPlugin",
"children": [
{
"uri": "http://lv2plug.in/ns/lv2core#AnalyserPlugin",
"display_name": "Analyser",
"parent_uri": "http://lv2plug.in/ns/lv2core#UtilityPlugin",
"plugin_type": "AnalyserPlugin",
"children": []
},
{
"uri": "http://lv2plug.in/ns/lv2core#ConverterPlugin",
"display_name": "Converter",
"parent_uri": "http://lv2plug.in/ns/lv2core#UtilityPlugin",
"plugin_type": "ConverterPlugin",
"children": []
},
{
"uri": "http://lv2plug.in/ns/lv2core#FunctionPlugin",
"display_name": "Function",
"parent_uri": "http://lv2plug.in/ns/lv2core#UtilityPlugin",
"plugin_type": "FunctionPlugin",
"children": []
},
{
"uri": "http://lv2plug.in/ns/lv2core#MixerPlugin",
"display_name": "Mixer",
"parent_uri": "http://lv2plug.in/ns/lv2core#UtilityPlugin",
"plugin_type": "MixerPlugin",
"children": []
}
]
}
},
{
"uri": "http://lv2plug.in/ns/lv2core#HighpassPlugin",
"display_name": "Highpass",
"parent_uri": "http://lv2plug.in/ns/lv2core#FilterPlugin",
"plugin_type": "HighpassPlugin",
"children": [ ]
},
{
"uri": "http://lv2plug.in/ns/lv2core#LowpassPlugin",
"display_name": "Lowpass",
"parent_uri": "http://lv2plug.in/ns/lv2core#FilterPlugin",
"plugin_type": "LowpassPlugin",
"children": [ ]
}
]
},
{
"uri": "http://lv2plug.in/ns/lv2core#GeneratorPlugin",
"display_name": "Generator",
"parent_uri": "http://lv2plug.in/ns/lv2core#Plugin",
"plugin_type": "GeneratorPlugin",
"children": [
{
"uri": "http://lv2plug.in/ns/lv2core#ConstantPlugin",
"display_name": "Constant",
"parent_uri": "http://lv2plug.in/ns/lv2core#GeneratorPlugin",
"plugin_type": "ConstantPlugin",
"children": [ ]
},
{
"uri": "http://lv2plug.in/ns/lv2core#InstrumentPlugin",
"display_name": "Instrument",
"parent_uri": "http://lv2plug.in/ns/lv2core#GeneratorPlugin",
"plugin_type": "InstrumentPlugin",
"children": [ ]
},
{
"uri": "http://lv2plug.in/ns/lv2core#OscillatorPlugin",
"display_name": "Oscillator",
"parent_uri": "http://lv2plug.in/ns/lv2core#GeneratorPlugin",
"plugin_type": "OscillatorPlugin",
"children": [ ]
}
]
},
{
"uri": "http://lv2plug.in/ns/lv2core#ModulatorPlugin",
"display_name": "Modulator",
"parent_uri": "http://lv2plug.in/ns/lv2core#Plugin",
"plugin_type": "ModulatorPlugin",
"children": [
{
"uri": "http://lv2plug.in/ns/lv2core#ChorusPlugin",
"display_name": "Chorus",
"parent_uri": "http://lv2plug.in/ns/lv2core#ModulatorPlugin",
"plugin_type": "ChorusPlugin",
"children": [ ]
},
{
"uri": "http://lv2plug.in/ns/lv2core#FlangerPlugin",
"display_name": "Flanger",
"parent_uri": "http://lv2plug.in/ns/lv2core#ModulatorPlugin",
"plugin_type": "FlangerPlugin",
"children": [ ]
},
{
"uri": "http://lv2plug.in/ns/lv2core#PhaserPlugin",
"display_name": "Phaser",
"parent_uri": "http://lv2plug.in/ns/lv2core#ModulatorPlugin",
"plugin_type": "PhaserPlugin",
"children": [ ]
}
]
},
{
"uri": "http://lv2plug.in/ns/lv2core#SpatialPlugin",
"display_name": "Spatial",
"parent_uri": "http://lv2plug.in/ns/lv2core#Plugin",
"plugin_type": "SpatialPlugin",
"children": [ ]
},
{
"uri": "http://lv2plug.in/ns/lv2core#SpectralPlugin",
"display_name": "Spectral",
"parent_uri": "http://lv2plug.in/ns/lv2core#Plugin",
"plugin_type": "SpectralPlugin",
"children": [
{
"uri": "http://lv2plug.in/ns/lv2core#PitchPlugin",
"display_name": "Pitch Shifter",
"parent_uri": "http://lv2plug.in/ns/lv2core#SpectralPlugin",
"plugin_type": "PitchPlugin",
"children": [ ]
}
]
},
{
"uri": "http://lv2plug.in/ns/lv2core#UtilityPlugin",
"display_name": "Utility",
"parent_uri": "http://lv2plug.in/ns/lv2core#Plugin",
"plugin_type": "UtilityPlugin",
"children": [
{
"uri": "http://lv2plug.in/ns/lv2core#AnalyserPlugin",
"display_name": "Analyser",
"parent_uri": "http://lv2plug.in/ns/lv2core#UtilityPlugin",
"plugin_type": "AnalyserPlugin",
"children": [ ]
},
{
"uri": "http://lv2plug.in/ns/lv2core#ConverterPlugin",
"display_name": "Converter",
"parent_uri": "http://lv2plug.in/ns/lv2core#UtilityPlugin",
"plugin_type": "ConverterPlugin",
"children": [ ]
},
{
"uri": "http://lv2plug.in/ns/lv2core#FunctionPlugin",
"display_name": "Function",
"parent_uri": "http://lv2plug.in/ns/lv2core#UtilityPlugin",
"plugin_type": "FunctionPlugin",
"children": [ ]
},
{
"uri": "http://lv2plug.in/ns/lv2core#MixerPlugin",
"display_name": "Mixer",
"parent_uri": "http://lv2plug.in/ns/lv2core#UtilityPlugin",
"plugin_type": "MixerPlugin",
"children": [ ]
}
]
}
]
}
}
+7 -12
View File
@@ -6,7 +6,7 @@ import { PiPedalModel, PiPedalModelFactory, State } from './PiPedalModel';
import AppBar from '@mui/material/AppBar';
import Toolbar from '@mui/material/Toolbar';
import ArrowBackIcon from '@mui/icons-material//ArrowBack';
import Divider from '@mui/material/Divider';
import JackHostStatus from './JackHostStatus';
import {PiPedalError} from './PiPedalError';
import DialogEx from './DialogEx';
@@ -219,22 +219,17 @@ const AboutDialog = withStyles(styles, { withTheme: true })(
</span>
</Typography>
<Typography display="block" variant="body2" style={{marginBottom: 12}} >
Copyright &#169; 2021 Robin Davies. All rights reserved.
Copyright &#169; 2022 Robin Davies.
</Typography>
<Divider/>
<Typography display="block" variant="caption" >
JACK AUDIO STATUS
<Typography display="block" variant="body2" style={{marginBottom: 12}} >
{this.model.getAndroidHostVersion() }
</Typography>
<div style={{paddingTop: 8, paddingBottom: 24}}>
{
JackHostStatus.getDisplayView("",this.state.jackStatus)
}
</div>
<Divider/>
<Typography display="block" variant="caption" >
LEGAL NOTICES
</Typography>
<div dangerouslySetInnerHTML={{__html: this.state.openSourceNotices}}>
<div dangerouslySetInnerHTML={{__html: this.state.openSourceNotices}} style={{fontSize: "0.8em" }}>
</div>
</div>
+2 -2
View File
@@ -26,7 +26,7 @@ export interface AndroidHostInterface {
isAndroidHosted(): boolean;
getHostVersion() : string;
chooseNewDevice() : void;
onLostConnection(): void;
setDisconnected(isDisconnected: boolean): void;
};
@@ -41,7 +41,7 @@ export class FakeAndroidHost implements AndroidHostInterface
chooseNewDevice(): void {
}
onLostConnection(): void {
setDisconnected(isDisconnected: boolean): void {
}
+1 -1
View File
@@ -36,7 +36,7 @@ declare module '@mui/styles/defaultTheme' {
const theme = createTheme({
palette: {
primary: {
main: "#6750A4"
main: "#6750A4" // #5B5690 #60529A #5C5694
},
secondary: {
main: "#FF6060"
+5 -3
View File
@@ -114,7 +114,7 @@ const appStyles = (theme: Theme) => createStyles({
marginTop: 0,
fontWeight: 500,
fontSize: "13pt",
maxWidth: 250,
maxWidth: 350,
opacity: 1,
zIndex: 2010,
paddingTop: 12,
@@ -785,7 +785,9 @@ const AppThemed = withStyles(appStyles)(class extends ResizeResponsiveComponent<
</DialogActions>
</Dialog>
<JackStatusView />
<div className={classes.errorContent} style={{ display: this.state.displayState === State.Reconnecting ? "block" : "none" }}
<div className={classes.errorContent} style={{
display: (this.state.displayState === State.Reconnecting || this.state.displayState === State.ApplyingChanges)
? "block" : "none" }}
>
<div className={classes.errorContentMask} />
@@ -794,7 +796,7 @@ const AppThemed = withStyles(appStyles)(class extends ResizeResponsiveComponent<
<CircularProgress color="inherit" className={classes.loadingBoxItem} />
</div>
<Typography variant="body2" className={classes.progressText}>
Reconnecting...
{ this.state.displayState === State.ApplyingChanges ? "Applying\u00A0changes..." : "Reconnecting..."}
</Typography>
</div>
</div>
+29 -20
View File
@@ -21,6 +21,7 @@ import React, { ReactNode, SyntheticEvent, CSSProperties } from 'react';
import { PiPedalModel, PiPedalModelFactory, FavoritesList } from './PiPedalModel';
import { UiPlugin, PluginType } from './Lv2Plugin';
import TextField from '@mui/material/TextField';
import ButtonBase from '@mui/material/ButtonBase';
import Button from '@mui/material/Button';
import MenuItem from '@mui/material/MenuItem';
@@ -34,9 +35,7 @@ import DialogTitle from '@mui/material/DialogTitle';
import SelectHoverBackground from './SelectHoverBackground';
import ArrowBackIcon from '@mui/icons-material/ArrowBack';
import IconButton from '@mui/material/IconButton';
import Select from '@mui/material/Select';
import PluginClass from './PluginClass'
import ClearIcon from '@mui/icons-material/Clear';
import ResizeResponsiveComponent from './ResizeResponsiveComponent';
import SearchControl from './SearchControl';
import SearchFilter from './SearchFilter';
@@ -46,6 +45,9 @@ import StarBorderIcon from '@mui/icons-material/StarBorder';
import Slide, { SlideProps } from '@mui/material/Slide';
import { createStyles, Theme } from '@mui/material/styles';
import { WithStyles, withStyles } from '@mui/styles';
import FilterListIcon from '@mui/icons-material/FilterList';
import FilterListOffIcon from '@mui/icons-material/FilterListOff';
export type CloseEventHandler = () => void;
@@ -504,15 +506,15 @@ export const LoadPluginDialog =
</div>
<div className={classes.content2}>
<div className={classes.label} >
<Typography color="textPrimary" noWrap sx={{display: "inline"}} >
{value.name}
{
isFavorite && (
<StarBorderIcon sx={{ color: "#C80", fontSize:16 }} />
<Typography color="textPrimary" noWrap sx={{ display: "inline" }} >
{value.name}
{
isFavorite && (
<StarBorderIcon sx={{ color: "#C80", fontSize: 16 }} />
)
}
</Typography>
)
}
</Typography>
</div>
<Typography color="textSecondary" noWrap>
{value.plugin_display_type} {this.stereo_indicator(value)}
@@ -598,19 +600,26 @@ export const LoadPluginDialog =
}}
/>
</div>
<Select variant="standard"
defaultValue={this.state.filterType}
key={this.state.filterType}
onChange={(e) => { this.onFilterChange(e); }}
style={{ flex: "0 0 160px" }}
>
{this.createFilterOptions()}
</Select>
<div style={{ flex: "0 0 auto", marginRight: 24, visibility: this.state.filterType === PluginType.Plugin ? "hidden" : "visible" }} >
<div style={{ flex: "0 0 auto" }} >
<IconButton onClick={() => { this.onClearFilter(); }}>
<ClearIcon fontSize='small' style={{ opacity: 0.6 }} />
{this.state.filterType === PluginType.Plugin ? (
<FilterListIcon fontSize='small' style={{ opacity: 0.75 }} />
) : (
<FilterListOffIcon fontSize='small' style={{ opacity: 0.75 }} />
)}
</IconButton>
</div>
<div style={{ flex: "0 0 160px",marginRight: 24 }} >
<TextField select variant="standard"
defaultValue={this.state.filterType}
key={this.state.filterType}
onChange={(e) => { this.onFilterChange(e); }}
sx={{ minWidth: 160}}
>
{this.createFilterOptions()}
</TextField>
</div>
</div>
</DialogTitle>
<DialogContent dividers style={{
+3
View File
@@ -189,6 +189,9 @@ export enum PluginType {
UtilityPlugin = "UtilityPlugin",
WaveshaperPlugin = "WaveshaperPlugin",
// psuedo plugin type for the Amps node of the filter dialog.
PiPedalAmpsNode = "PiPedalAmpsNode"
}
export enum ControlType {
+67 -6
View File
@@ -45,7 +45,8 @@ export enum State {
Ready,
Error,
Background,
Reconnecting
Reconnecting,
ApplyingChanges
};
export type ControlValueChangedHandler = (key: string, value: number) => void;
@@ -503,11 +504,18 @@ class PiPedalModelImpl implements PiPedalModel {
this.onSocketReconnecting = this.onSocketReconnecting.bind(this);
this.onSocketReconnected = this.onSocketReconnected.bind(this);
this.onVisibilityChanged = this.onVisibilityChanged.bind(this);
this.onSocketConnectionLost = this.onSocketConnectionLost.bind(this);
}
onSocketReconnecting(retry: number, maxRetries: number): void {
if (this.visibilityState.get() === VisibilityState.Hidden) return;
//if (retry !== 0) {
this.setState(State.Reconnecting);
if (this.restartExpected)
{
this.setState(State.ApplyingChanges);
} else {
this.setState(State.Reconnecting);
}
//}
}
@@ -697,7 +705,20 @@ class PiPedalModelImpl implements PiPedalModel {
return this.webSocket;
}
onSocketConnectionLost() {
if (this.isAndroidHosted())
{
this.androidHost?.setDisconnected(true);
}
}
onSocketReconnected() {
if (this.isAndroidHosted())
{
this.androidHost?.setDisconnected(false);
}
this.restartExpected = false;
if (this.visibilityState.get() === VisibilityState.Hidden) return;
// reload state, but not configuration.
@@ -778,6 +799,7 @@ class PiPedalModelImpl implements PiPedalModel {
maxUploadSize: number = 512 * 1024;
debug: boolean = false;
requestConfig(): Promise<boolean> {
const myRequest = new Request(this.varRequest('config.json'));
return fetch(myRequest)
@@ -808,10 +830,14 @@ class PiPedalModelImpl implements PiPedalModel {
this.webSocket = new PiPedalSocket(
this.socketServerUrl,
this.onSocketMessage,
this.onSocketError,
this.onSocketReconnecting,
this.onSocketReconnected);
{
onMessageReceived : this.onSocketMessage,
onError: this.onSocketError,
onConnectionLost: this.onSocketConnectionLost,
onReconnect: this.onSocketReconnected,
onReconnecting: this.onSocketReconnecting
}
);
return this.webSocket.connect();
})
.then(() => {
@@ -948,7 +974,15 @@ class PiPedalModelImpl implements PiPedalModel {
}
backgroundStateTimeout?: NodeJS.Timeout = undefined;
exitBackgroundState() {
if (this.backgroundStateTimeout)
{
clearTimeout(this.backgroundStateTimeout);
this.backgroundStateTimeout = undefined;
return;
}
if (this.state.get() === State.Background) {
console.log("Exiting background state.");
this.visibilityState.set(VisibilityState.Visible);
@@ -957,6 +991,24 @@ class PiPedalModelImpl implements PiPedalModel {
}
enterBackgroundState() {
// on Android, delay entering background state by 3 seconds,
// so that screen-flips don't trigger disconnects.
if (this.isAndroidHosted())
{
if (this.backgroundStateTimeout)
{
clearTimeout(this.backgroundStateTimeout);
}
this.backgroundStateTimeout = setTimeout(() => {
this.backgroundStateTimeout = undefined;
this.enterBackgroundState_();
}, 3000);
} else {
this.enterBackgroundState_();
}
}
enterBackgroundState_() {
if (this.state.get() !== State.Background) {
console.log("Entering background state.");
this.visibilityState.set(VisibilityState.Hidden);
@@ -1438,7 +1490,16 @@ class PiPedalModelImpl implements PiPedalModel {
showAlert(message: string): void {
this.alertMessage.set(message);
}
restartExpected: boolean = false;
expectRestart() {
this.restartExpected = true;
}
setJackSettings(jackSettings: JackChannelSelection): void {
this.expectRestart();
this.webSocket?.send("setJackSettings", jackSettings);
}
+24 -23
View File
@@ -36,13 +36,18 @@ export type PiPedalMessageHeader = {
type ReplyHandler = (header: PiPedalMessageHeader, body: any | null) => void;
export interface PiPedalSocketListener {
onMessageReceived : (header: PiPedalMessageHeader, body: any | null) => void;
onError: (message: string, exception?: Error) => void;
onConnectionLost: () => void;
onReconnect: () => void;
onReconnecting: (retry: number, maxRetries: number) => void;
};
class PiPedalSocket {
onMessageReceived: MessageHandler;
onError: ErrorHandler;
onReconnecting: ReconnectingHandler;
onReconnect: ReconnectHandler;
listener: PiPedalSocketListener;
socket?: WebSocket;
nextResponseCode: number = 0;
@@ -54,15 +59,10 @@ class PiPedalSocket {
constructor(
url: string,
onMessageReceived: MessageHandler,
onError: ErrorHandler,
onReconnecting: ReconnectingHandler,
onReconnect: ReconnectHandler) {
listener: PiPedalSocketListener
) {
this.url = url;
this.onMessageReceived = onMessageReceived;
this.onError = onError;
this.onReconnecting = onReconnecting;
this.onReconnect = onReconnect;
this.listener = listener;
}
handleOpen(event: Event): any {
@@ -160,11 +160,11 @@ class PiPedalSocket {
if (header.message === "error") {
throw new PiPedalStateError("Server error: " + body);
}
this.onMessageReceived(header, body);
this.listener.onMessageReceived(header, body);
}
} catch (error) {
if (this.onError) {
this.onError("Invalid server response. " + error, error as Error);
if (this.listener) {
this.listener.onError("Invalid server response. " + error, error as Error);
} else {
throw new PiPedalStateError("Invalid server response.");
}
@@ -172,8 +172,8 @@ class PiPedalSocket {
}
handleError(_event: Event): any {
if (this.onError) {
this.onError("Server connection lost.");
if (this.listener) {
this.listener.onError("Server connection lost.");
} else {
throw new PiPedalStateError("Server connection lost.");
}
@@ -184,8 +184,8 @@ class PiPedalSocket {
handleClose(_event: any): any {
if (this.retrying) {
// treat this as a fatal error.
if (this.onError) {
this.onError("Server connection lost.");
if (this.listener) {
this.listener.onError("Server connection lost.");
} else {
throw new PiPedalStateError("Server connection closed.");
}
@@ -194,6 +194,7 @@ class PiPedalSocket {
}
if (this.canReconnect)
{
this.listener.onConnectionLost();
this._reconnect();
}
}
@@ -229,19 +230,19 @@ class PiPedalSocket {
this.close();
}
this.onReconnecting(this.retryCount,MAX_RETRIES);
this.listener.onReconnecting(this.retryCount,MAX_RETRIES);
++this.retryCount;
this.connectInternal_()
.then((socket) => {
this.socket = socket;
this.retrying = false;
this.onReconnect();
this.listener.onReconnect();
})
.catch(error => {
if (this.totalRetryDelay === MAX_RETRY_TIME)
{
this.onError("Server connection lost.");
this.listener.onError("Server connection lost.");
return;
} else {
this.totalRetryDelay += this.retryDelay;
+10 -8
View File
@@ -70,7 +70,6 @@ function SelectChannelsDialog(props: SelectChannelsDialogProps) {
const { onClose, selectedChannels, availableChannels, open } = props;
const [currentSelection, setCurrentSelection] = useState(selectedChannels);
let showCheckboxes = availableChannels.length > 2;
if (showCheckboxes) {
@@ -120,24 +119,27 @@ function SelectChannelsDialog(props: SelectChannelsDialogProps) {
} else {
const handleClose = () => {
onClose(selectedChannels);
const handleCancel = () => {
onClose(null);
}
const handleOk = (channels: string[]) => {
onClose(channels);
};
const handleListItemClick = (value: string) => {
switch (value) {
case "Stereo":
onClose(availableChannels);
handleOk(availableChannels);
break;
case "Left":
onClose([availableChannels[0]]);
handleOk([availableChannels[0]]);
break;
case "Right":
onClose([availableChannels[1]]);
handleOk([availableChannels[1]]);
break;
case "None":
default:
onClose([availableChannels[1]]);
handleOk([]);
}
};
let selectionKey = "None";
@@ -152,7 +154,7 @@ function SelectChannelsDialog(props: SelectChannelsDialogProps) {
}
return (
<Dialog onClose={handleClose} aria-labelledby="select-channels-title" open={open}>
<Dialog onClose={handleCancel} aria-labelledby="select-channels-title" open={open}>
<List style={{ marginLeft: 0, marginRight: 0}}>
<ListItem button onClick={() => handleListItemClick("Stereo")} key={"Stereo"} selected={selectionKey === "Stereo"} >
+197 -77
View File
@@ -23,12 +23,14 @@
*/
import React from 'react';
import IconButton from '@mui/material/IconButton';
import ArrowBackIcon from '@mui/icons-material/ArrowBack';
import Button from '@mui/material/Button';
import TextField from '@mui/material/TextField';
import DialogEx from './DialogEx';
import DialogActions from '@mui/material/DialogActions';
import DialogContent from '@mui/material/DialogContent';
import DialogTitle from '@mui/material/DialogTitle';
import Switch from '@mui/material/Switch';
import FormControlLabel from '@mui/material/FormControlLabel';
import ResizeResponsiveComponent from './ResizeResponsiveComponent';
@@ -37,9 +39,6 @@ import { Theme } from '@mui/material/styles';
import { WithStyles } from '@mui/styles';
import withStyles from '@mui/styles/withStyles';
import createStyles from '@mui/styles/createStyles';
import Select from '@mui/material/Select';
import FormControl from '@mui/material/FormControl';
import InputLabel from '@mui/material/InputLabel';
import MenuItem from '@mui/material/MenuItem';
import WifiChannel from './WifiChannel';
import { PiPedalModel, PiPedalModelFactory } from './PiPedalModel';
@@ -72,6 +71,9 @@ export interface WifiDirectConfigState {
pinErrorMessage: string;
countryCode: string;
channel: string;
controlWidth: number;
landscapeLayout: boolean;
controlSize: ("medium" | "small" | undefined);
wifiChannels: WifiChannel[];
}
@@ -88,7 +90,7 @@ const WifiDirectConfigDialog = withStyles(styles, { withTheme: true })(
super(props);
this.model = PiPedalModelFactory.getInstance();
this.state = {
fullScreen: false,
fullScreen: this.useFullScreen(),
enabled: this.props.wifiDirectConfigSettings.enable,
name: this.props.wifiDirectConfigSettings.hotspotName,
pin: this.props.wifiDirectConfigSettings.pin,
@@ -98,6 +100,10 @@ const WifiDirectConfigDialog = withStyles(styles, { withTheme: true })(
pinErrorMessage: NBSP,
countryCode: this.props.wifiDirectConfigSettings.countryCode,
channel: this.props.wifiDirectConfigSettings.channel,
controlWidth: 260,
controlSize: this.windowSize.height < 600 ? "small" : "medium",
landscapeLayout: this.useLandscapeLayout(),
wifiChannels: []
};
this.model.getWifiChannels(this.props.wifiDirectConfigSettings.countryCode)
@@ -108,6 +114,10 @@ const WifiDirectConfigDialog = withStyles(styles, { withTheme: true })(
this.refName = React.createRef<HTMLInputElement>();
this.refPassword = React.createRef<HTMLInputElement>();
}
useLandscapeLayout(): boolean {
return (this.windowSize.height < 690 && this.windowSize.width > this.windowSize.height && this.windowSize.width > 690);
}
mounted: boolean = false;
handleEnableChanged(e: any) {
@@ -116,7 +126,15 @@ const WifiDirectConfigDialog = withStyles(styles, { withTheme: true })(
onWindowSizeChanged(width: number, height: number): void {
this.setState({ fullScreen: height < 200 })
this.setState({
fullScreen: this.useFullScreen(),
controlSize: height < 600 ? "small" : "medium",
landscapeLayout: this.useLandscapeLayout()
})
}
useFullScreen(): boolean {
return this.windowSize.height < 390;
}
@@ -288,84 +306,94 @@ const WifiDirectConfigDialog = withStyles(styles, { withTheme: true })(
};
return (
<DialogEx tag="WifiDirectConfigDialog" open={open} fullWidth onClose={handleClose} style={{ userSelect: "none" }}
fullScreen={this.state.fullScreen}
<DialogEx tag="WifiDirectConfigDialog" open={open} onClose={handleClose} style={{ userSelect: "none", }}
fullScreen={this.state.fullScreen} fullWidth={this.useLandscapeLayout()}
>
{this.state.fullScreen && (
<DialogTitle>Wi-fi Hotspot</DialogTitle>
)}
<DialogContent>
<FormControlLabel
control={(
<Switch
checked={this.state.enabled}
onChange={(e: any) => this.handleEnableChanged(e)}
color="secondary"
{this.state.landscapeLayout && (
<DialogContent >
<div style={{ display: "flex", gap: 16, alignItems: "left" }}>
<IconButton
edge="start"
color="inherit"
onClick={()=> {this.props.onClose(); }}
aria-label="back"
size="large">
<ArrowBackIcon htmlColor="#888"/>
</IconButton>
<FormControlLabel
control={(
<Switch
checked={this.state.enabled}
onChange={(e: any) => this.handleEnableChanged(e)}
color="secondary"
/>
)}
label="Wi-Fi Direct Hotspot"
/>
)}
label="Enable Wi-Fi Direct Hotspot"
/>
<div style={{ marginTop: 16 }}>
<TextField
required
autoComplete="off"
id="name"
spellCheck="false"
label="SSID"
type="text"
fullWidth
error={this.state.nameError}
helperText={this.state.nameErrorMessage}
value={this.state.name}
onChange={(e) => this.setState({ name: e.target.value, nameError: false, nameErrorMessage: NBSP })}
inputRef={this.refName}
disabled={!this.state.enabled}
variant="filled"
/>
</div>
<div style={{ marginBottom: 0, verticalAlign: "baseline" }}>
<TextField
inputRef={this.refPassword}
autoComplete="off"
onChange={(e): void => { this.setState({ pin: e.target.value, pinError: false, pinErrorMessage: NBSP }); }}
label="Pin"
error={this.state.pinError}
helperText={this.state.pinErrorMessage}
defaultValue={this.state.pin}
disabled={!this.state.enabled}
variant="filled"
/>
<FormControl>
<div style={{ marginLeft: 16, marginTop: 18 }}>
<Button size="medium" disabled={!this.state.enabled}
</div>
<div style={{ marginTop: 16, display: 'flex', alignItems: 'center', gap: 16 }}>
<TextField
required
sx={{ maxWidth: this.state.controlWidth }}
autoComplete="off"
id="name"
spellCheck="false"
label="SSID"
type="text"
size={this.state.controlSize}
fullWidth
error={this.state.nameError}
helperText={this.state.nameErrorMessage}
value={this.state.name}
onChange={(e) => this.setState({ name: e.target.value, nameError: false, nameErrorMessage: NBSP })}
inputRef={this.refName}
disabled={!this.state.enabled}
variant="filled"
/>
<TextField
inputRef={this.refPassword}
sx={{ maxWidth: this.state.controlWidth }}
size={this.state.controlSize}
autoComplete="off"
onChange={(e): void => { this.setState({ pin: e.target.value, pinError: false, pinErrorMessage: NBSP }); }}
label="Pin"
inputProps={{ inputMode: 'numeric' }}
fullWidth
error={this.state.pinError}
helperText={this.state.pinErrorMessage}
defaultValue={this.state.pin}
disabled={!this.state.enabled}
variant="filled"
/>
</div>
<div style={{ width: this.state.controlWidth * 2 }}>
<div style={{ marginTop: 0, marginBottom: 16, width: this.state.controlWidth, marginLeft: "auto", marginRight: 0, textAlign: "right" }}>
<Button size="small" disabled={!this.state.enabled}
onClick={() => this.onGenerateRandomPin()}
>Generate random pin</Button>
</div>
</FormControl>
</div>
<div style={{ marginBottom: 24 }}>
<FormControl variant="filled" sx={{ minWidth: 330 }}>
<InputLabel htmlFor="countryCodeSelect">Country</InputLabel>
<Select id="countryCodeSelect" label="Country" value={this.state.countryCode} disabled={!this.state.enabled}
onChange={(event) => this.handleCountryChanged(event)} >
</div>
<div style={{ marginTop: 0, display: 'flex', alignItems: 'stretch', gap: 16 }}>
<TextField select label="Country" size={this.state.controlSize} value={this.state.countryCode} variant="filled"
sx={{ width: this.state.controlWidth }}
disabled={!this.state.enabled}
onChange={(event) => { this.handleCountryChanged(event); }}
>
{Object.entries(this.model.countryCodes).map(([key, value]) => {
return (
<MenuItem value={key}>{value}</MenuItem>
);
})}
</Select>
</FormControl>
</TextField>
</div>
<div style={{ marginBottom: 24 }}>
<FormControl variant="filled" sx={{ minWidth: 330 }} >
<InputLabel htmlFor="channelSelect">Channel</InputLabel>
<Select id="channelSelect" label="Channel" value={this.state.channel} disabled={!this.state.enabled} onChange={(e) => {
this.handleChannelChange(e);
}}>
<TextField select id="channelSelect" size={this.state.controlSize} label="Channel"
value={this.state.channel} variant="filled" sx={{ width: this.state.controlWidth }}
disabled={!this.state.enabled}
onChange={(event) => { this.handleChannelChange(event); }}
>
{this.state.wifiChannels.map((channel) => {
let t = channel.channelId;
if (t.startsWith("a")) t = t.substring(1);
@@ -378,12 +406,104 @@ const WifiDirectConfigDialog = withStyles(styles, { withTheme: true })(
<MenuItem value={t}>{name}</MenuItem>
)
})}
</Select>
</FormControl>
</TextField>
</div>
</div>
</DialogContent>
)}
{(!this.state.landscapeLayout) && (
<DialogContent sx={{minHeight: 96}} >
<FormControlLabel
control={(
<Switch
checked={this.state.enabled}
onChange={(e: any) => this.handleEnableChanged(e)}
color="secondary"
/>
)}
label="Wi-Fi Direct Hotspot"
/>
<div style={{ marginTop: 16 }}>
<TextField
required
sx={{ maxWidth: this.state.controlWidth }}
autoComplete="off"
id="name"
spellCheck="false"
label="SSID"
type="text"
size={this.state.controlSize}
fullWidth
error={this.state.nameError}
helperText={this.state.nameErrorMessage}
value={this.state.name}
onChange={(e) => this.setState({ name: e.target.value, nameError: false, nameErrorMessage: NBSP })}
inputRef={this.refName}
disabled={!this.state.enabled}
variant="filled"
/>
</div><div>
<TextField
inputRef={this.refPassword}
sx={{ maxWidth: this.state.controlWidth }}
style={{ maxWidth: this.state.controlWidth }}
size={this.state.controlSize}
autoComplete="off"
onChange={(e): void => { this.setState({ pin: e.target.value, pinError: false, pinErrorMessage: NBSP }); }}
label="Pin"
inputProps={{ inputMode: 'numeric' }}
fullWidth
error={this.state.pinError}
helperText={this.state.pinErrorMessage}
defaultValue={this.state.pin}
disabled={!this.state.enabled}
variant="filled"
/>
</div>
<div style={{ marginTop: 0, marginBottom: 16, width: this.state.controlWidth, marginLeft: "auto", marginRight: 0, textAlign: "right" }}>
<Button size="small" disabled={!this.state.enabled}
onClick={() => this.onGenerateRandomPin()}
>Generate random pin</Button>
</div>
<div style={{ marginBottom: 16 }}>
<TextField select label="Country" size={this.state.controlSize} value={this.state.countryCode} variant="filled" sx={{ width: this.state.controlWidth }}
disabled={!this.state.enabled}
onChange={(event) => { this.handleCountryChanged(event); }}
>
{Object.entries(this.model.countryCodes).map(([key, value]) => {
return (
<MenuItem value={key}>{value}</MenuItem>
);
})}
</TextField>
</div>
<div style={{ marginBottom: 24 }}>
<TextField select id="channelSelect" size={this.state.controlSize} label="Channel" value={this.state.channel} variant="filled" sx={{ width: this.state.controlWidth }}
disabled={!this.state.enabled}
onChange={(event) => { this.handleChannelChange(event); }}
>
{this.state.wifiChannels.map((channel) => {
let t = channel.channelId;
if (t.startsWith("a")) t = t.substring(1);
if (t.startsWith("g")) t = t.substring(1);
let name = channel.channelName;
if (name.startsWith("1 ") || name.startsWith("6 ") || name.startsWith("11 ")) {
name += " *Recommended";
}
return (
<MenuItem value={t}>{name}</MenuItem>
)
})}
</TextField>
</div>
</DialogContent>
)}
</DialogContent>
<DialogActions>
<Button onClick={handleClose} color="primary" style={{ width: 120 }}>
Cancel
+1 -1
View File
@@ -55,7 +55,7 @@ export default class WifiDirectConfigSettings {
} else if (!this.enable) {
result = "Disabled.";
} else {
result = this.hotspotName;
result = "Enabled. (" + this.hotspotName + ")";
}
if (this.rebootRequired)
{
+49 -14
View File
@@ -42,13 +42,15 @@ void AvahiService::Announce(
int portNumber,
const std::string &name,
const std::string &instanceId,
const std::string &mdnsName)
const std::string &mdnsName,
bool addTestGroup)
{
Unannounce();
this->portNumber = portNumber;
this->instanceId = instanceId;
this->mdnsName = mdnsName;
this->addTestGroup = addTestGroup;
this->name = avahi_strdup(name.c_str());
@@ -63,6 +65,9 @@ void AvahiService::Unannounce()
avahi_free(name);
this->name = nullptr;
}
this->group = nullptr;
this->client = nullptr;
this->threadedPoll = nullptr;
}
void AvahiService::entry_group_callback(AvahiEntryGroup *g, AvahiEntryGroupState state, void *userData)
@@ -87,7 +92,7 @@ void AvahiService::EntryGroupCallback(AvahiEntryGroup *g, AvahiEntryGroupState s
n = avahi_alternative_service_name(name);
avahi_free(name);
name = n;
Lv2Log::debug(SS("Service name collision, renaming service to '" << name << "'\n"));
Lv2Log::error(SS("Service name collision, renaming service to '" << name << "'\n"));
/* And recreate the services */
create_group(avahi_entry_group_get_client(g));
break;
@@ -102,31 +107,32 @@ void AvahiService::EntryGroupCallback(AvahiEntryGroup *g, AvahiEntryGroupState s
}
}
static int toRawDns(char*rawResult,size_t size, const std::string &name)
static int toRawDns(char *rawResult, size_t size, const std::string &name)
{
// from standard name format to <nn>xyz<nn>foo<nn>com<00> format
size_t len = name.length();
rawResult[0] = 0;
if (len+1 >= size-1) return 0;
if (len + 1 >= size - 1)
return 0;
rawResult[len+1] = 0;
rawResult[len + 1] = 0;
int count = 0;
for (int i = len-1; i >= 0; --i)
for (int i = len - 1; i >= 0; --i)
{
if (name[i] == '.')
{
rawResult[i+1] = (char)count;
rawResult[i + 1] = (char)count;
count = 0;
} else {
rawResult[i+1] = name[i];
}
else
{
rawResult[i + 1] = name[i];
++count;
}
}
rawResult[0] = count;
return len+1;
return len + 1;
}
void AvahiService::create_group(AvahiClient *c)
{
@@ -149,14 +155,14 @@ void AvahiService::create_group(AvahiClient *c)
{
Lv2Log::debug(SS("Adding service '" << name));
std::string instanceTxtRecord = SS("pipedal_id=" << this->instanceId);
std::string instanceTxtRecord = SS("id=" << this->instanceId);
#define PIPEDAL_SERVICE_TYPE "_pipedal._tcp"
if ((ret = avahi_entry_group_add_service(
group,
AVAHI_IF_UNSPEC,
AVAHI_PROTO_INET, // IPv4 for now, until we figure out why dnsqmasq borks IPv6 routing.)
AVAHI_PROTO_UNSPEC, // IPv4 for now, until we figure out why dnsqmasq borks IPv6 routing.)
(AvahiPublishFlags)0,
name,
PIPEDAL_SERVICE_TYPE,
@@ -174,12 +180,41 @@ void AvahiService::create_group(AvahiClient *c)
goto fail;
}
if (this->addTestGroup)
{
Lv2Log::info("Added tests DNS/SD service.");
std::string instanceTxtRecord = SS("id=" << "0a6045b0-1753-4104-b3e4-b9713b9cc360");
if ((ret = avahi_entry_group_add_service(
group,
AVAHI_IF_UNSPEC,
AVAHI_PROTO_INET, // IPv4 for now, until we figure out why dnsqmasq borks IPv6 routing.)
(AvahiPublishFlags)0,
"Ed's PiPedal",
PIPEDAL_SERVICE_TYPE,
NULL,
NULL,
portNumber,
// txt records.
instanceTxtRecord.c_str(),
NULL)) < 0)
{
if (ret == AVAHI_ERR_COLLISION)
goto collision;
Lv2Log::error(SS("Failed to add _pipedal._tcp service: " << avahi_strerror(ret)));
goto fail;
}
}
/* Tell the server to register the service */
if ((ret = avahi_entry_group_commit(group)) < 0)
{
Lv2Log::error(SS("Failed to commit entry group: " << avahi_strerror(ret)));
goto fail;
}
Lv2Log::info(SS("DNS/SD service announced."));
}
return;
collision:
+3 -1
View File
@@ -39,7 +39,8 @@ namespace pipedal {
class AvahiService {
public:
~AvahiService() { Unannounce(); }
void Announce(int portNumber, const std::string &name, const std::string &instanceId, const std::string&mdnsName);
void Announce(
int portNumber, const std::string &name, const std::string &instanceId, const std::string&mdnsName,bool addTestGroup = false);
void Unannounce();
@@ -62,6 +63,7 @@ namespace pipedal {
int portNumber = -1;
std::string instanceId;
std::string mdnsName;
bool addTestGroup = false;
AvahiClient *client = NULL;
AvahiEntryGroup *group = NULL;
+1
View File
@@ -948,6 +948,7 @@ int main(int argc, char **argv)
settings.valid_ = true;
settings.enable_ = false;
SetWifiDirectConfig(settings);
RestartService(true);
}
else if (enable_ap)
{
+20
View File
@@ -18,6 +18,7 @@
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#include "pch.h"
#include "DeviceIdFile.hpp"
#include <sched.h>
#include "PiPedalModel.hpp"
#include "JackHost.hpp"
@@ -151,8 +152,10 @@ void PiPedalModel::LoadLv2PluginInfo(const PiPedalConfiguration&configuration)
void PiPedalModel::Load(const PiPedalConfiguration &configuration)
{
this->webRoot = configuration.GetWebRoot();
this->webPort = (uint16_t)configuration.GetSocketServerPort();
this->jackServerSettings.ReadJackConfiguration();
adminClient.MonitorGovernor(storage.GetGovernorSettings());
// lv2Host.Load(configuration.GetLv2Path().c_str());
@@ -678,6 +681,16 @@ void PiPedalModel::SetWifiConfigSettings(const WifiConfigSettings &wifiConfigSet
delete[] t;
}
}
void PiPedalModel::UpdateDnsSd()
{
avahiService.Unannounce();
DeviceIdFile deviceIdFile;
deviceIdFile.Load();
avahiService.Announce(webPort,deviceIdFile.deviceName,deviceIdFile.uuid,"pipedal");
}
void PiPedalModel::SetWifiDirectConfigSettings(const WifiDirectConfigSettings &wifiDirectConfigSettings)
{
std::lock_guard<std::recursive_mutex> guard(mutex);
@@ -701,7 +714,14 @@ void PiPedalModel::SetWifiDirectConfigSettings(const WifiDirectConfigSettings &w
t[i]->OnWifiDirectConfigSettingsChanged(tWifiDirectConfigSettings);
}
delete[] t;
// update NSD-SD announement.
UpdateDnsSd();
}
}
WifiConfigSettings PiPedalModel::GetWifiConfigSettings()
+8
View File
@@ -35,6 +35,7 @@
#include "WifiConfigSettings.hpp"
#include "WifiDirectConfigSettings.hpp"
#include "AdminClient.hpp"
#include "AvahiService.hpp"
@@ -71,6 +72,10 @@ public:
class PiPedalModel: private IJackHostCallbacks {
private:
AvahiService avahiService;
uint16_t webPort;
PiPedalAlsaDevices alsaDevices;
std::recursive_mutex mutex;
@@ -149,8 +154,11 @@ public:
PiPedalModel();
virtual ~PiPedalModel();
void Close();
void UpdateDnsSd();
AdminClient&GetAdminClient() { return adminClient; }
+7
View File
@@ -83,6 +83,11 @@ static std::pair<std::string,PluginType> urisToNames[] {
URI_TO_TYPE_MAP_ENTRY(UtilityPlugin),
URI_TO_TYPE_MAP_ENTRY(WaveshaperPlugin),
URI_TO_TYPE_MAP_ENTRY(MIDIPlugin),
// Artificial node in filters plugin that serves as parent of AmplifierPlugin and SimulatorPlugin
{std::string("http://two_play.com/ns/pluginClass#ampsNode"),PluginType::PiPedalAmpsNode }
};
PluginType pipedal::uri_to_plugin_type(const std::string&uri)
@@ -155,6 +160,8 @@ static std::pair<std::string,PluginType> strings_to_type_map[] {
STRING_TO_TYPE_MAP_ENTRY(UtilityPlugin),
STRING_TO_TYPE_MAP_ENTRY(WaveshaperPlugin),
STRING_TO_TYPE_MAP_ENTRY(MIDIPlugin),
STRING_TO_TYPE_MAP_ENTRY(MIDIPlugin),
STRING_TO_TYPE_MAP_ENTRY(PiPedalAmpsNode),
};
PluginType pipedal::string_to_plugin_type(const std::string&uri)
+2
View File
@@ -71,6 +71,8 @@ enum class PluginType {
UtilityPlugin,
WaveshaperPlugin,
MIDIPlugin,
PiPedalAmpsNode, // Pseudo-plugin type used for Amps folder in filter view.
};
const std::string &plugin_type_to_uri(PluginType type);
+11 -10
View File
@@ -500,22 +500,23 @@ void pipedal::SetWifiDirectConfig(const WifiDirectConfigSettings &settings)
sysExec(SYSTEMCTL_BIN " daemon-reload");
sysExec(SYSTEMCTL_BIN " restart dnsmasq");
sysExec(SYSTEMCTL_BIN " enable dnsmasq");
sysExec(SYSTEMCTL_BIN " stop pipedal_p2pd");
sysExec(SYSTEMCTL_BIN " stop dnsmasq");
sysExec(SYSTEMCTL_BIN " stop wpa_supplicant");
sysExec(SYSTEMCTL_BIN " restart dhcpcd");
sysExec(SYSTEMCTL_BIN " enable dhcpcd");
sysExec(SYSTEMCTL_BIN " unmask pipedal_p2pd");
if (sysExec(SYSTEMCTL_BIN " restart pipedal_p2pd") != 0)
sysExec(SYSTEMCTL_BIN " unmask wpa_supplicant");
sysExec(SYSTEMCTL_BIN " start wpa_supplicant");
sysExec(SYSTEMCTL_BIN " enable wpa_supplicant");
sysExec(SYSTEMCTL_BIN " start dnsmasq");
sysExec(SYSTEMCTL_BIN " enable dnsmasq");
sysExec(SYSTEMCTL_BIN " start pipedal_p2pd");
if (sysExec(SYSTEMCTL_BIN " start pipedal_p2pd") != 0)
{
throw PiPedalException("Unable to start the Wi-Fi Direct access point.");
}
sysExec(SYSTEMCTL_BIN " enable pipedal_p2pd");
sysExec(SYSTEMCTL_BIN " restart pipedald");
}
}
+8
View File
@@ -143,6 +143,14 @@ void WifiDirectConfigSettings::Save() const
}
void WifiDirectConfigSettings::Load()
{
this->enable_ = false;
std::string strEnable;
if (ConfigUtil::GetConfigLine("/etc/pipedal/config/pipedal_p2pd.conf","enabled",&strEnable))
{
this->enable_ = (strEnable == "true" || strEnable == "1");
}
if (!ConfigUtil::GetConfigLine("/etc/pipedal/config/pipedal_p2pd.conf","p2p_device_name",&this->hotspotName_))
{
this->hotspotName_ = "PiPedal";
+4 -9
View File
@@ -504,6 +504,7 @@ int main(int argc, char *argv[])
bool help = false;
bool error = false;
bool systemd = false;
bool testExtraDevice = false;
std::string portOption;
CommandLineParser parser;
@@ -511,6 +512,7 @@ int main(int argc, char *argv[])
parser.AddOption("--help", &help);
parser.AddOption("-systemd", &systemd);
parser.AddOption("-port", &portOption);
parser.AddOption("-test-extra-device", &testExtraDevice); // advertise two different devices (for testing multi-device connect)
try
{
@@ -700,17 +702,10 @@ int main(int argc, char *argv[])
server->AddRequestHandler(downloadIntercept);
{
// Publish DNS Service.
DeviceIdFile deviceIdFile;
deviceIdFile.Load();
AvahiService service;
service.Announce(
configuration.GetSocketServerPort(),deviceIdFile.deviceName,deviceIdFile.uuid,"pipedal");
server->RunInBackground(SIGUSR1);
model.UpdateDnsSd(); // now that the server is running, publish a DNS-SD announcement.
sem_wait(&signalSemaphore);
if (systemd)
+4 -1
View File
@@ -1,13 +1,16 @@
[Unit]
Description=PiPedal P2P Session Manager
After=network.target
After=wpa_supplicant.service
After=dhcpcd.service
[Service]
ExecStart=${COMMAND} --systemd --log-level debug
Type=notify
WorkingDirectory=/var/pipedal
TimeoutStopSec=5
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target
+2
View File
@@ -17,6 +17,8 @@ Group=pipedal_d
Restart=always
TimeoutStartSec=60
RestartSec=25
TimeoutStopSec=15
WorkingDirectory=/var/pipedal
Environment=JACK_PROMISCUOUS_SERVER=audio
Environment=JACK_NO_AUDIO_RESERVATION=1